Canvas pointer + keyboard actions on the picked element (issue #1917).
Left-click opens the element's authored link; right-click opens the shared
context menu with Copy, Open link in new tab and Copy link address.
Before this, nothing in the viewer listened for click, contextmenu,
mousedown or pointerdown on the canvas at all — both gestures were
swallowed by the camera controls and replaced by nothing.
Telling a click from a camera gesture
Movement, never the button. Which button orbits and which pans is
platform-dependent — naturalDrag defaults to isMacPlatform(), and the
ortho controls map RIGHT to nothing at all — so any button-based rule would
be wrong on half the installs. A press that moves no further than
CLICK_SLOP_PX is a click, whatever gesture it would otherwise have
begun; anything further is left to the controls, which have been receiving
it all along.
The menu opens on pointerup, NOT on the contextmenu event: on macOS that
event fires at press time, so a right-drag to rotate would pop a menu the
instant the drag began. A contextmenu listener is still registered, purely
to preventDefault the native menu — the controls already do this in both
orbit and fly mode, so it is belt-and-braces against that changing.
Touch
A finger neither hovers nor right-clicks, so the mouse model above has no
touch equivalent on its own. Touch-like pointers (isTouchLikePointer) get:
a wider slop (TOUCH_CLICK_SLOP_PX); tap = pick at the tap
(PickGenerationPort.pickAt, the tooltip shows through the normal result
path) and then the click action, after first discarding residual user-input
damping in orbit, ortho, or fly mode so accepted drift cannot immediately
clear that result; any navigation is deferred by DOUBLE_TAP_MS so a
second tap can pre-empt it; long-press =
the element menu, after which the release is inert; double-tap = the
second tap pre-empts the first tap's navigation and picks nothing — the
re-frame itself is double-tap-to-fit.ts, installed for every scene, since
this module only exists once picking is provisioned. Mouse and pen-as-mouse
paths are untouched.
Dependency injection
window.open, the clipboard, the toast and the menu all arrive as ports.
window.open in particular is called nowhere else in the viewer and is
stubbed in no existing test, so injecting it is the only way the URL a click
would navigate to can be asserted without a real popup.
Canvas pointer + keyboard actions on the picked element (issue #1917).
Left-click opens the element's authored
link; right-click opens the shared context menu withCopy,Open link in new tabandCopy link address. Before this, nothing in the viewer listened forclick,contextmenu,mousedownorpointerdownon the canvas at all — both gestures were swallowed by the camera controls and replaced by nothing.Telling a click from a camera gesture
Movement, never the button. Which button orbits and which pans is platform-dependent —
naturalDragdefaults toisMacPlatform(), and the ortho controls map RIGHT to nothing at all — so any button-based rule would be wrong on half the installs. A press that moves no further than CLICK_SLOP_PX is a click, whatever gesture it would otherwise have begun; anything further is left to the controls, which have been receiving it all along.The menu opens on
pointerup, NOT on thecontextmenuevent: on macOS that event fires at press time, so a right-drag to rotate would pop a menu the instant the drag began. Acontextmenulistener is still registered, purely topreventDefaultthe native menu — the controls already do this in both orbit and fly mode, so it is belt-and-braces against that changing.Touch
A finger neither hovers nor right-clicks, so the mouse model above has no touch equivalent on its own. Touch-like pointers (
isTouchLikePointer) get: a wider slop (TOUCH_CLICK_SLOP_PX); tap = pick at the tap (PickGenerationPort.pickAt, the tooltip shows through the normal result path) and then the click action, after first discarding residual user-input damping in orbit, ortho, or fly mode so accepted drift cannot immediately clear that result; any navigation is deferred by DOUBLE_TAP_MS so a second tap can pre-empt it; long-press = the element menu, after which the release is inert; double-tap = the second tap pre-empts the first tap's navigation and picks nothing — the re-frame itself isdouble-tap-to-fit.ts, installed for every scene, since this module only exists once picking is provisioned. Mouse and pen-as-mouse paths are untouched.Dependency injection
window.open, the clipboard, the toast and the menu all arrive as ports.window.openin particular is called nowhere else in the viewer and is stubbed in no existing test, so injecting it is the only way the URL a click would navigate to can be asserted without a real popup.