From d80023d63468ef0b3044abd9fe0b42f4bb06903b Mon Sep 17 00:00:00 2001 From: Reese Norris Date: Mon, 27 Jul 2026 15:48:57 -0400 Subject: [PATCH] airport-editor: stop status flash from scrolling the map on vertex grab Vertex drag auto-selects a surface; the select tip unhid a banner and reflowed the layout under the cursor. Skip the tip while dragging and keep the status strip always one line tall so messages never shove the map. --- .../web/static/css/openfsd/airport-editor.css | 19 ++++++++++ .../static/js/openfsd/airport-editor/main.js | 36 +++++++++++-------- .../js/openfsd/airport-editor/map-layers.js | 9 +++++ internal/web/templates/airport_editor.html | 9 ++--- 4 files changed, 54 insertions(+), 19 deletions(-) diff --git a/internal/web/static/css/openfsd/airport-editor.css b/internal/web/static/css/openfsd/airport-editor.css index c122f63..6599a92 100644 --- a/internal/web/static/css/openfsd/airport-editor.css +++ b/internal/web/static/css/openfsd/airport-editor.css @@ -179,6 +179,25 @@ border-radius: 2px; } +/* + * Live editor status strip: always occupies one line of vertical space so + * show/hide never reflows the toolbar/map (vertex grab was scrolling the page). + * Empty state is invisible but still in normal flow. + */ +.apted-flash[data-js="status"] { + min-height: calc(1.25em + 0.7rem + 2px); + box-sizing: border-box; + margin: 0.25rem 0 0; + /* Avoid browser scrolling the flash into view on updates */ + overflow-anchor: none; +} +.apted-flash[data-js="status"].is-empty { + visibility: hidden; + border-color: transparent; + background: transparent; + color: transparent; +} + .apted-flash-ok { background: #d1e7dd; color: var(--apted-ok); diff --git a/internal/web/static/js/openfsd/airport-editor/main.js b/internal/web/static/js/openfsd/airport-editor/main.js index be3fad9..b2189d4 100644 --- a/internal/web/static/js/openfsd/airport-editor/main.js +++ b/internal/web/static/js/openfsd/airport-editor/main.js @@ -111,7 +111,11 @@ function main() { if (doc.mode !== MODE_SELECT) return; doc.selection = normalizeSelection(sel); refresh(); - syncSurfaceSelectTip(); + // Vertex grab auto-selects the surface — do not flash a status banner + // (unhiding the flash strip reflows the page and scrolls the map). + if (!overlays.isVertexDragging()) { + syncSurfaceSelectTip(); + } }, onVertexDrag(si, vi, lat, lon) { setVertex(doc, si, vi, { lat, lon }); @@ -756,29 +760,31 @@ function main() { function showStatus(msg, isError) { const el = root.querySelector('[data-js="status"]'); if (!el) return; - el.hidden = !msg; + // Keep the strip in layout always (never [hidden]) so messages do not + // expand the page and shove the map / scroll the viewport. + el.hidden = false; el.textContent = msg || ''; - el.classList.toggle('apted-flash-err', !!isError); + el.classList.toggle('is-empty', !msg); + el.classList.toggle('apted-flash-err', !!isError && !!msg); el.classList.toggle('apted-flash-ok', !isError && !!msg); - el.setAttribute('role', isError ? 'alert' : 'status'); + el.setAttribute('role', isError && msg ? 'alert' : 'status'); + el.setAttribute('aria-hidden', msg ? 'false' : 'true'); } - /** Status banner for surface select in Select mode (not mid-drag). */ + /** + * Select tip lives in a reserved status strip (no layout jump). We no longer + * push a banner on every surface select — mode tip covers "drag vertices". + * Clear only our old tip text if present so deselect stays quiet. + */ const SURFACE_SELECT_TIP = 'Drag any white vertex to reshape. Click empty map to deselect.'; - /** - * Show tip when selection is a surface; clear it when selection leaves a - * surface (empty-map deselect, aircraft select). Only on selection change. - */ function syncSurfaceSelectTip() { - if (doc.mode === MODE_SELECT && doc.selection?.type === 'surface') { - showStatus(SURFACE_SELECT_TIP, false); - return; - } - // Drop only our tip — do not wipe errors or other status flashes. const el = root.querySelector('[data-js="status"]'); - if (el && el.textContent === SURFACE_SELECT_TIP) { + if (!el) return; + // Quiet: do not inject tip on select (was causing scroll-on-grab). + // Clear stale tip if it is still showing from an older build/session. + if (el.textContent === SURFACE_SELECT_TIP) { showStatus('', false); } } diff --git a/internal/web/static/js/openfsd/airport-editor/map-layers.js b/internal/web/static/js/openfsd/airport-editor/map-layers.js index 23d195f..af114a1 100644 --- a/internal/web/static/js/openfsd/airport-editor/map-layers.js +++ b/internal/web/static/js/openfsd/airport-editor/map-layers.js @@ -788,6 +788,15 @@ export class OverlayController { this.vertexEditActive = !!active; } + /** + * True while a vertex pointer-drag is active (including the select that starts it). + * Callers use this to avoid layout-shifting status flashes mid-grab. + * @returns {boolean} + */ + isVertexDragging() { + return !!(this._vertexDrag || this._dragging); + } + /** * Draw preview polyline for in-progress draw session. * @param {{ lat: number, lon: number }[]} points diff --git a/internal/web/templates/airport_editor.html b/internal/web/templates/airport_editor.html index 18d85f1..7646d34 100644 --- a/internal/web/templates/airport_editor.html +++ b/internal/web/templates/airport_editor.html @@ -41,7 +41,8 @@ {{ end }} - + {{/* Always in flow (is-empty) so status text never reflows the map. */}} +