Native context-menu ownership — the secondary click belongs to the viewer on
every surface the viewer owns, not just on the canvas.
Right-drag orbits the camera, so both controls implementations already
preventDefault the contextmenu event on the canvas they listen to
(luxar-orbit-controls.ts, luxar-fly-controls/listeners.ts), and
canvas-actions.ts repeats it. In Chromium that is enough: every scene
overlay floating above the canvas is pointer-events: none, so the hit test
walks past the overlay and the event target IS the canvas.
WebKit resolves the context-menu target geometrically instead, so inside the
WKWebView of an exported --native macos app the target is whichever
overlay happens to sit under the cursor — a turntable's matte <canvas>,
its <video>, a logo <img>. None of those is the canvas and none of their
ancestors carried a handler, so the native "Copy Image / Share Image…" sheet
opened over the scene the moment a right-drag began, and the camera never
moved. Reported against the ESM kiosk export, 2026-09-09.
Rather than chase each overlay, one delegated listener on the viewer
container covers everything the viewer mounts — present and future,
pointer-events or not. It is scoped two ways so it cannot eat a menu that
is not ours to eat:
Ownership. Only a target that is the canvas, or that sits inside an
element carrying a luxar- class, is suppressed. Universal ancestors
(the container, body and document element) do not establish ownership:
the container defaults to document.body, which an embedder may share
with their own UI (see utils/viewer-container).
Text entry. A typing surface keeps its native menu, which is the only
way to paste — while range, checkbox, radio and select controls remain
viewer controls whose menu should be suppressed.
Interactive overlay actions. A live selection keeps Copy, and a link
keeps its native link actions. The overlay's pointer events already
prevent a right-drag from reaching the camera controls.
Capture phase, so an inner handler that calls stopPropagation (the
dimension sliders' play-button menu does) cannot leave the native menu
behind it.
Native context-menu ownership — the secondary click belongs to the viewer on every surface the viewer owns, not just on the canvas.
Right-drag orbits the camera, so both controls implementations already
preventDefaultthecontextmenuevent on the canvas they listen to (luxar-orbit-controls.ts,luxar-fly-controls/listeners.ts), andcanvas-actions.tsrepeats it. In Chromium that is enough: every scene overlay floating above the canvas ispointer-events: none, so the hit test walks past the overlay and the event target IS the canvas.WebKit resolves the context-menu target geometrically instead, so inside the WKWebView of an exported
--native macosapp the target is whichever overlay happens to sit under the cursor — a turntable's matte<canvas>, its<video>, a logo<img>. None of those is the canvas and none of their ancestors carried a handler, so the native "Copy Image / Share Image…" sheet opened over the scene the moment a right-drag began, and the camera never moved. Reported against the ESM kiosk export, 2026-09-09.Rather than chase each overlay, one delegated listener on the viewer container covers everything the viewer mounts — present and future,
pointer-eventsor not. It is scoped two ways so it cannot eat a menu that is not ours to eat:luxar-class, is suppressed. Universal ancestors (the container, body and document element) do not establish ownership: the container defaults todocument.body, which an embedder may share with their own UI (seeutils/viewer-container).Capture phase, so an inner handler that calls
stopPropagation(the dimension sliders' play-button menu does) cannot leave the native menu behind it.