Skip to content

feat(events)!: DOM-agnostic Pointer dispatch + XR controller pointers#64

Open
bigmistqke wants to merge 7 commits into
solidjs-community:next-cleanupfrom
bigmistqke:next-xr-pointers
Open

feat(events)!: DOM-agnostic Pointer dispatch + XR controller pointers#64
bigmistqke wants to merge 7 commits into
solidjs-community:next-cleanupfrom
bigmistqke:next-xr-pointers

Conversation

@bigmistqke
Copy link
Copy Markdown
Contributor

Decouples solid-three's pointer-event dispatch from the DOM behind a Pointer abstraction, opens it to multiple simultaneous pointers via pointerId, 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 shared hoveredSet, one context.raycaster). That blocked XR — controllers emit selectstart/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; ScreenRaycaster adds setCursor (camera + NDC); ControllerRaycaster casts from an Object3D's matrixWorld.
  • DOMPointerManager (src/pointer-managers.ts) — the built-in screen source: owns the canvas listeners, a Pointer per native pointerId (multi-touch), and a primary Pointer for click/dblclick/contextmenu/wheel (which are MouseEvents with no pointerId).
  • XRPointerManager + createXR wiring — each controller (renderer.xr.getController(i)) becomes a Pointer with a ControllerRaycaster; selectstart/selectend drive onPointerDown/onPointerUp and synthesize onClick. Created on sessionstart, torn down on sessionend/disconnect, and guarded so it only engages when the renderer exposes getController.
  • 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 with onPointer* (pointer events fire for mouse input and are a superset) and absent from react-three-fiber. Migration: use the onPointer* equivalents.
  • EventRaycaster.update(event, context) removed in favour of cast + setCursor (affects custom raycasters).
  • The mouse path keeps native DOM 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/xr synthesizes 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 removed onMouse* blocks (and two api-coverage tests migrated off the removed .update()). New tests cover Pointer dispatch, EventRaycaster.cast, multi-touch (independent hover per pointerId), and XR controller select → onPointerDown/onPointerUp/onClick.
  • pnpm lint:types — clean.
  • pnpm lint:code — clean.

…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.
@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Jun 3, 2026

commit: 3ff10f7

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant