feat(events)!: DOM-agnostic Pointer dispatch + XR controller pointers#64
Open
bigmistqke wants to merge 7 commits into
Open
feat(events)!: DOM-agnostic Pointer dispatch + XR controller pointers#64bigmistqke wants to merge 7 commits into
bigmistqke wants to merge 7 commits into
Conversation
…ointer* family) Ports the hover/missable/default dispatch from the per-kind registries into one per-pointer Pointer class with its own hover state, fed by a manager's raycaster. Adds context.eventRegistry (the single registry the Pointer raycasts; populated in a later task). Additive + unwired — existing suite unchanged (175/2 → +5).
…ster cast(registry, context) aims the ray and intersects (ported from the old raycast); ScreenRaycaster adds setCursor; ControllerRaycaster casts from a space's matrixWorld. Legacy update() kept (now optional) so the pre-Pointer engine still runs. Additive.
…d + primary) Owns the canvas listeners, a Pointer per native pointerId (multi-touch), and a primary Pointer for the family-agnostic click/dblclick/contextmenu/wheel gestures. Additive — not yet wired (the clean cut replaces createEvents next).
…op onMouse* createEvents now registers handler-bearing objects into the single context.eventRegistry and connects the DOMPointerManager (per-pointerId Pointers + a primary Pointer for click/wheel). Deletes the three per-kind registries, the old raycast, eventNameMap, createRegistry, and the duplicated createThreeEvent. BREAKING: onMouse* handlers removed (redundant with onPointer*; absent from r3f). Their test blocks are removed; every other test passes unchanged (185 → 172), confirming the new dispatch is behavior-identical for the mouse path.
The pre-Pointer engine was the only caller; cast() replaces it. Drops update() from EventRaycaster + Cursor/CenterRaycaster and the unused RayEvent type; migrates the two api-coverage tests that probed update() to the setCursor/cast API.
…ck (event-driven) Each XR controller (renderer.xr.getController(i)) becomes a Pointer with a ControllerRaycaster bound to its targetRay space; selectstart/selectend drive down/up and synthesize click. createXR wires it on sessionstart, tears down on sessionend/disconnect — guarded so the renderer must expose getController and the connected value be a full Context, leaving createXR's existing fakes unaffected. Sweep-hover (needs per-frame) is deferred.
commit: |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Decouples solid-three's pointer-event dispatch from the DOM behind a
Pointerabstraction, opens it to multiple simultaneous pointers viapointerId, and adds an API to wire XR controllers as pointers. Event-driven, and behavior-preserving for the mouse path.Why
The event system was DOM-triggered (each registry listened on
context.canvas) and single-pointer (one sharedhoveredSet, onecontext.raycaster). That blocked XR — controllers emitselectstart/selectend, never DOM events — and couldn't track multi-touch or multiple controllers. Dispatch now lives on a per-pointer object fed by pluggable sources, so a mouse, two fingers, and two controllers can all dispatch independently through the same code, while the mouse path stays byte-identical.What changed
Pointer(src/pointers.ts) — per-pointer, DOM-agnostic dispatch + hover/press state. The enter/leave ancestor diff, bubbling +stopPropagation, click +-Missed, and down/up/wheel logic are ported from the old per-kind registries; each pointer keeps its own hover set.EventRaycaster.cast+ScreenRaycaster+ControllerRaycaster(src/raycasters.tsx) — the ray strategy is a property of a pointer.cast(registry, context)aims the ray and intersects;ScreenRaycasteraddssetCursor(camera + NDC);ControllerRaycastercasts from anObject3D'smatrixWorld.DOMPointerManager(src/pointer-managers.ts) — the built-in screen source: owns the canvas listeners, aPointerper nativepointerId(multi-touch), and a primaryPointerfor click/dblclick/contextmenu/wheel (which areMouseEvents with nopointerId).XRPointerManager+createXRwiring — each controller (renderer.xr.getController(i)) becomes aPointerwith aControllerRaycaster;selectstart/selectenddriveonPointerDown/onPointerUpand synthesizeonClick. Created onsessionstart, torn down onsessionend/disconnect, and guarded so it only engages when the renderer exposesgetController.context.eventRegistry— one refcounted registry of handler-bearing objects that every pointer raycasts, replacing the three per-kind registries.Breaking
onMouse*removed (onMouseDown/onMouseUp/onMouseMove/onMouseEnter/onMouseLeave). Fully redundant withonPointer*(pointer events fire for mouse input and are a superset) and absent from react-three-fiber. Migration: use theonPointer*equivalents.EventRaycaster.update(event, context)removed in favour ofcast+setCursor(affects custom raycasters).click/dblclick/contextmenu; only XR controllers synthesize click from select-down/up. This is a deliberate hybrid — r3f-core is native-click-only,@react-three/xrsynthesizes uniformly; we keep the mouse byte-identical and synthesize only where there is no DOM click.Deferred (not in this PR)
Per-frame driving, and XR controller sweep-hover (enter/leave as a controller ray moves while held still) — controllers emit no move events, so that needs a per-frame tick. This PR is event-driven: point-and-click/select works now; controller hover-while-moving comes with the per-frame phase.
Test plan
pnpm test— 175 passed, 2 todo (16 files). Behavior-preserving: every pre-existing test passes unchanged except the removedonMouse*blocks (and twoapi-coveragetests migrated off the removed.update()). New tests coverPointerdispatch,EventRaycaster.cast, multi-touch (independent hover perpointerId), and XR controller select →onPointerDown/onPointerUp/onClick.pnpm lint:types— clean.pnpm lint:code— clean.