fix(street-level): viewer lifecycle, hover cost, keyboard focus, panel lift

From the IC8 review of #768 (P2s), each with a test that fails before:

- Switching the layer off and on during the first photo download no longer
  leaves the viewer stuck on "not mounted": an outdated mount only unmounts
  an adapter nobody else holds and subscribes poses only once current, and
  the MapillaryJS adapter never builds a viewer after it was unmounted.
- Hover picks at most every 120 ms (the CCTV convention), not while the
  camera moves or a button is held, and not after the layer went off.
- Esc shrinks the expanded viewer wherever focus is (document capture), so
  the map keeps its selection, and Tab from outside returns into it.
  Closing a photo moves focus to the panel instead of dropping it on body.
- Portable panels lift only after a mouse or pen moves past the drag
  threshold: a click on a docked panel's edge or a touch swipe on its
  header no longer turns it into a floating window, and pointercancel
  saves nothing and puts the panel back.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
Said TurkseverandClaude Opus 5.5 committed 2026-10-05 15:55:18 +01:00
1 parent b1924ebb3e
commit 539d514adb
10 files changed
+793 -101

No files matched your search

+10 -10
View File
@@ -76,19 +76,19 @@ export function createViewerHost({ state, parts }) {
}
const adapter = entry.instance.viewer;
const promise = (async () => {
const unsubscribe = adapter.onPose(applyPose);
try {
await adapter.mount(state.street.host);
} catch (error) {
unsubscribe();
throw error;
}
await adapter.mount(state.street.host);
if (mounting?.promise !== promise) {
// Unmounted (layer off, provider switched) while loading.
unsubscribe();
adapter.unmount();
// Unmounted (layer off, provider switched) while loading. A newer
// mount of this provider shares the adapter and its viewer: tearing
// it down here would leave that mount active with no viewer.
const reused =
active?.id === entry.def.id || mounting?.id === entry.def.id;
if (!reused) adapter.unmount();
throw new Error('Street-level viewer was closed');
}
// Listen only once current: an outdated mount releasing the same
// `applyPose` would otherwise unhook the mount that replaced it.
const unsubscribe = adapter.onPose(applyPose);
active = { id: entry.def.id, adapter, unsubscribe };
warmed.delete(entry.def.id);
adapter.setRenderMode?.(state.street.renderMode);