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.
This commit is contained in:
Reese Norris
2026-07-27 15:48:57 -04:00
parent 32386cb219
commit d80023d634
4 changed files with 54 additions and 19 deletions

View File

@@ -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);

View File

@@ -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);
}
}

View File

@@ -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

View File

@@ -41,7 +41,8 @@
<div class="apted-flash apted-flash-err" role="alert">{{ .FlashError }}</div>
{{ end }}
<p class="apted-flash" data-js="status" hidden role="status"></p>
{{/* Always in flow (is-empty) so status text never reflows the map. */}}
<p class="apted-flash is-empty" data-js="status" role="status" aria-hidden="true"></p>
<div class="apted-toolbar" role="toolbar" aria-label="Editor tools">
<label class="btn btn-sm btn-outline-dark apted-file-btn">
@@ -212,7 +213,7 @@
Static assets are go:embed into the openfsd binary — restart the process after
JS/CSS changes. Query string busts browser cache when the binary is rebuilt.
*/}}
<script src="/static/js/leaflet.js?v=vertex-edit-5"></script>
<script src="/static/js/openfsd/leaflet.rotatedmarker.js?v=vertex-edit-5"></script>
<script type="module" src="/static/js/openfsd/airport-editor/main.js?v=vertex-edit-5"></script>
<script src="/static/js/leaflet.js?v=vertex-edit-6"></script>
<script src="/static/js/openfsd/leaflet.rotatedmarker.js?v=vertex-edit-6"></script>
<script type="module" src="/static/js/openfsd/airport-editor/main.js?v=vertex-edit-6"></script>
{{ end }}