fix: preserve keyboard focus and separate Space activation from voice

Keep focus visible across controls and stable during Contacts, Cockpit and mission roster updates. Preserve native short Space activation, and claim push-to-talk only after a 500 ms hold. Add panel Escape handling and keyboard-accessible attribution controls.

Retains the Map Source foundation contributed in https://github.com/bilawalsidhu/gods-eye-view/pull/171.
This commit is contained in:
manjunath22466 authored and Sameh Khamis committed 2026-09-10 23:48:12 -07:00
1 parent c82fdd7478
commit 5228ab9e40
22 files changed
+3500 -157

No files matched your search

+153 -5
View File
@@ -4,13 +4,21 @@ import { readFileSync } from 'node:fs';
// Exercise the installed event routes and central close method, without WebGL.
const source = readFileSync(new URL('./ui.js', import.meta.url), 'utf8');
const markup = readFileSync(new URL('../index.html', import.meta.url), 'utf8');
const locationMarkup = markup.slice(markup.indexOf('<div id="location-bar"'), markup.indexOf('<div id="left-panel-stack"'));
const locationToggleMarkup = locationMarkup.match(/<button\b([^>]*\bid="location-bar-toggle"[^>]*)>([\s\S]*?)<\/button>/);
const locationToggleAttributes = Object.fromEntries(
[...(locationToggleMarkup?.[1] || '').matchAll(/([\w-]+)="([^"]*)"/g)].map((match) => [match[1], match[2]]),
);
const initStart = source.indexOf(' _initAutoHoverPanel(');
const initEnd = source.indexOf(' /**\n * Sets up drag-to-reposition', initStart);
const escapeStart = source.indexOf(' _collapsePanelOnEscape(');
const escapeEnd = source.indexOf(' _initCommandDockPins(', escapeStart);
const closeStart = source.indexOf(' setPanelCollapsed(panelId, collapsed, {');
const closeEnd = source.indexOf(' /**\n * Toggles "clean view"', closeStart);
const syncStart = source.indexOf(' _syncPanelCollapseButton(panelEl) {');
const syncEnd = source.indexOf(' /**\n * Converts a panel', syncStart);
assert.ok(initStart >= 0 && initEnd > initStart
assert.ok(initStart >= 0 && initEnd > initStart && escapeStart >= 0 && escapeEnd > escapeStart
&& closeStart >= 0 && closeEnd > closeStart);
assert.ok(syncStart >= 0 && syncEnd > syncStart);
@@ -46,13 +54,16 @@ function harness({ hidden = false, selected = true, noChips = false } = {}) {
location.classes.add('collapsed');
const disclosure = makeNode('control-panel-toggle');
disclosure.isDisclosure = true;
const locationDisclosure = locationToggleMarkup ? makeNode('location-bar-toggle', { ...locationToggleAttributes }) : null;
if (locationDisclosure) locationDisclosure.isDisclosure = true;
const locationSearch = makeNode('location-search');
const first = makeNode('photoreal');
const active = makeNode('osm');
const elsewhere = makeNode('elsewhere');
const nodes = [panel, location, disclosure, first, active, elsewhere].filter(Boolean);
const nodes = [panel, location, disclosure, locationDisclosure, locationSearch, first, active, elsewhere].filter(Boolean);
document.getElementById = (id) => nodes.find((node) => node.id === id);
panel.contains = (node) => [panel, disclosure, first, active].includes(node);
location.contains = (node) => Boolean(node) && [location].includes(node);
location.contains = (node) => Boolean(node) && [location, locationDisclosure, locationSearch].includes(node);
panel.querySelector = (selector) => {
if (selector.startsWith('[data-dock-toggle-target')) return disclosure;
if (selector.split(',').some((part) => part.trim() === '.panel-title')) return { textContent: 'VISUAL STYLES' };
@@ -61,6 +72,9 @@ function harness({ hidden = false, selected = true, noChips = false } = {}) {
return selector === '.map-stack-chip' ? first : null;
};
location.querySelector = (selector) => {
if (selector === '[data-dock-toggle-target="location-bar"]') {
return locationDisclosure?.getAttribute('data-dock-toggle-target') === 'location-bar' ? locationDisclosure : null;
}
if (selector.split(',').some((part) => part.trim() === '.location-toolbar-label')) return { textContent: 'LOCATION' };
return null;
};
@@ -86,6 +100,7 @@ function harness({ hidden = false, selected = true, noChips = false } = {}) {
}
};
disclosure.focus = () => focus(disclosure);
if (locationDisclosure) locationDisclosure.focus = () => focus(locationDisclosure);
for (const chip of [first, active]) {
chip.focus = () => { focusCalls += 1; if (!hidden) focus(chip); };
}
@@ -93,7 +108,7 @@ function harness({ hidden = false, selected = true, noChips = false } = {}) {
setTimeout(callback, delay) { timers.set(++nextTimer, { callback, at: now + delay }); return nextTimer; },
};
const methods = new Function('document', 'window', 'clearTimeout', 'performance', 'requestAnimationFrame',
`return ({${source.slice(initStart, initEnd)},\n${source.slice(closeStart, closeEnd)},\n${source.slice(syncStart, syncEnd)}});`)(
`return ({${source.slice(initStart, initEnd)},\n${source.slice(escapeStart, escapeEnd)},\n${source.slice(closeStart, closeEnd)},\n${source.slice(syncStart, syncEnd)}});`)(
document, window, (id) => timers.delete(id), { now: () => now }, () => {},
);
const saves = [];
@@ -117,14 +132,28 @@ function harness({ hidden = false, selected = true, noChips = false } = {}) {
timers.delete(next[0]); now = next[1].at; next[1].callback();
};
return {
manager, panel, location, disclosure,
manager, panel, location, disclosure, locationDisclosure, locationSearch,
first, active, elsewhere, document, timers, emit, focus, tick, saves, claims,
calls: () => focusCalls,
shareSyncs: () => shareSyncs,
show() { hidden = false; },
select(value) { selected = value; },
key(key = 'Enter', repeat = false) { return emit(disclosure, 'keydown', { key, repeat }); },
keyUp(key = ' ', repeat = false) {
const event = emit(disclosure, 'keyup', { key, repeat });
if (key === ' ' && !repeat && document.activeElement === disclosure && !event.defaultPrevented) emit(disclosure, 'click', { detail: 0 });
return event;
},
escape() { emit(panel, 'keydown', { key: 'Escape' }); },
locationKey(key = 'Enter', repeat = false) { return emit(locationDisclosure, 'keydown', { key, repeat }); },
locationKeyUp(key = ' ', repeat = false) {
const event = emit(locationDisclosure, 'keyup', { key, repeat });
if (key === ' ' && !repeat && document.activeElement === locationDisclosure && !event.defaultPrevented) {
emit(locationDisclosure, 'click', { detail: 0 });
}
return event;
},
locationEscape() { return emit(location, 'keydown', { key: 'Escape', target: document.activeElement }); },
click(detail = 1) { emit(disclosure, 'click', { detail }); },
drain() { for (let i = 0; i < 40 && timers.size; i += 1) tick(); },
};
@@ -136,6 +165,17 @@ test('Enter focuses selected OSM rather than the first chip', () => {
assert.equal(h.panel.classList.contains('collapsed'), false);
assert.equal(h.timers.size, 0);
});
test('Space waits for release before its synthesized click opens the tray', () => {
const h = harness();
const down = h.key(' ');
assert.equal(down.defaultPrevented, false);
assert.equal(down.propagationStopped, false);
assert.equal(h.panel.classList.contains('collapsed'), true);
assert.equal(h.timers.size, 0);
h.keyUp(' '); h.tick();
assert.equal(h.panel.classList.contains('collapsed'), false);
assert.equal(h.document.activeElement, h.active);
});
test('repeated keydown does not close an opening tray or replace its timer', () => {
const h = harness(); h.key(); const timer = [...h.timers.keys()][0]; h.key('Enter', true);
assert.equal([...h.timers.keys()][0], timer);
@@ -230,6 +270,114 @@ test('pointer leave preserves established keyboard focus and the open tray', ()
});
test('Location markup provides one named native disclosure linked to its popover', () => {
assert.ok(locationToggleMarkup, 'Location must expose a native button in the real markup');
assert.equal(locationToggleAttributes.type, 'button');
assert.equal(locationToggleAttributes['data-dock-toggle-target'], 'location-bar');
assert.equal(locationToggleAttributes['aria-controls'], 'location-bar-popover');
assert.equal(locationToggleAttributes['aria-expanded'], 'false');
assert.match(locationToggleAttributes['aria-label'], /expand.*location/i);
assert.match(locationToggleAttributes.class, /(?:^|\s)dock-tray-toggle(?:\s|$)/);
assert.match(locationToggleMarkup[2], /class="location-toolbar-label"/);
assert.doesNotMatch(locationToggleMarkup[2], /<button\b/);
assert.match(locationMarkup.slice(locationToggleMarkup.index + locationToggleMarkup[0].length),
/^\s*<button class="panel-collapse-btn" data-collapse-target="location-bar"/);
assert.match(locationMarkup, /<div id="location-bar-popover" class="dock-popover-content">/);
});
test('Location Enter stays immediate while Space waits for native key release', () => {
for (const key of ['Enter', ' ']) {
const h = harness(); h.focus(h.locationDisclosure); h.drain();
const event = h.locationKey(key);
assert.equal(event.defaultPrevented, key === 'Enter');
assert.equal(event.propagationStopped, key === 'Enter');
if (key === ' ') {
assert.equal(h.location.classList.contains('collapsed'), true);
h.locationKeyUp(key);
}
assert.equal(h.location.classList.contains('collapsed'), false);
assert.equal(h.locationDisclosure.getAttribute('aria-expanded'), 'true');
assert.equal(h.locationDisclosure.getAttribute('aria-label'), 'Collapse LOCATION');
assert.equal(h.document.activeElement, h.locationDisclosure);
assert.equal(h.timers.size, 0);
assert.equal(h.calls(), 0);
const repeated = h.locationKey(key, true);
assert.equal(repeated.defaultPrevented, key === 'Enter');
assert.equal(repeated.propagationStopped, key === 'Enter');
assert.equal(h.location.classList.contains('collapsed'), false);
assert.equal(h.timers.size, 0);
const release = key === ' ' ? h.locationKeyUp(key, true) : h.emit(h.locationDisclosure, 'keyup', { key });
assert.equal(release.defaultPrevented, false, 'global voice release must remain reachable');
assert.equal(release.propagationStopped, false);
h.locationKey(key);
if (key === ' ') h.locationKeyUp(key);
assert.equal(h.location.classList.contains('collapsed'), true);
assert.equal(h.locationDisclosure.getAttribute('aria-label'), 'Expand LOCATION');
}
});
test('Location synthesized click and Escape retain focus and its accessible name', () => {
const h = harness(); h.focus(h.locationDisclosure); h.drain();
h.emit(h.locationDisclosure, 'click', { detail: 0 });
assert.equal(h.location.classList.contains('collapsed'), false);
assert.equal(h.calls(), 0);
assert.equal(h.timers.size, 0);
h.focus(h.locationSearch);
const escape = h.locationEscape();
assert.equal(escape.defaultPrevented, true);
assert.equal(h.location.classList.contains('collapsed'), true);
assert.equal(h.locationDisclosure.getAttribute('aria-expanded'), 'false');
assert.equal(h.locationDisclosure.getAttribute('aria-label'), 'Expand LOCATION');
assert.equal(h.locationDisclosure.title, 'Expand LOCATION');
assert.equal(h.document.activeElement, h.locationDisclosure);
h.drain();
assert.equal(h.calls(), 0);
});
test('Location keyboard opening cancels the unpinned sibling and cannot revive its old callback', () => {
const h = harness({ hidden: true }); h.key(); h.tick();
const stale = [...h.timers.values()][0].callback;
h.focus(h.locationDisclosure); h.locationKey();
assert.equal(h.panel.classList.contains('collapsed'), true);
assert.equal(h.location.classList.contains('collapsed'), false);
const attemptsBefore = h.calls();
h.show(); stale(); h.drain();
assert.equal(h.calls(), attemptsBefore);
assert.equal(h.document.activeElement, h.locationDisclosure);
});
test('Location keyboard opening preserves a pinned sibling and a pinned Location survives sibling opening', () => {
const h = harness(); h.panel.classes.add('dock-pinned'); h.key(); h.tick();
h.focus(h.locationDisclosure); h.locationKey(); h.drain();
assert.equal(h.panel.classList.contains('collapsed'), false);
assert.equal(h.location.classList.contains('collapsed'), false);
assert.equal(h.document.activeElement, h.locationDisclosure);
h.location.classes.add('dock-pinned');
h.manager.setPanelCollapsed('control-panel', true);
h.focus(h.disclosure); h.key(); h.tick();
assert.equal(h.location.classList.contains('collapsed'), false);
assert.equal(h.locationDisclosure.getAttribute('aria-expanded'), 'true');
assert.equal(h.document.activeElement, h.active);
});
test('Location restoration syncs its real name without claiming focus, collapsing its sibling or persisting', () => {
const h = harness(); h.key(); h.tick();
const saves = h.saves.length;
const claims = h.claims.length;
const shareSyncs = h.shareSyncs();
h.manager.setPanelCollapsed('location-bar', false, { restore: true, persist: false, syncShare: false });
assert.equal(h.panel.classList.contains('collapsed'), false);
assert.equal(h.location.classList.contains('collapsed'), false);
assert.equal(h.locationDisclosure.getAttribute('aria-label'), 'Collapse LOCATION');
assert.equal(h.document.activeElement, h.active);
assert.equal(h.saves.length, saves);
assert.equal(h.claims.length, claims);
assert.equal(h.shareSyncs(), shareSyncs);
h.locationDisclosure.setAttribute('aria-label', 'stale');
h.manager.setPanelCollapsed('location-bar', false, { restore: true, persist: false, syncShare: false });
assert.equal(h.locationDisclosure.getAttribute('aria-label'), 'Collapse LOCATION', 'same-state synchronization keeps the Location name');
assert.equal(h.timers.size, 0);
});
test('disposed UI cannot complete a pending handoff', () => {
const h = harness({ hidden: true }); h.key(); h.tick();