Skip to content

feat(events)!: void events — a per-gesture onVoid* family replacing *Missed#75

Draft
bigmistqke wants to merge 12 commits into
solidjs-community:nextfrom
bigmistqke:feat/void-events
Draft

feat(events)!: void events — a per-gesture onVoid* family replacing *Missed#75
bigmistqke wants to merge 12 commits into
solidjs-community:nextfrom
bigmistqke:feat/void-events

Conversation

@bigmistqke

Copy link
Copy Markdown
Contributor

Summary

Replaces the per-object *Missed handlers with a positive, canvas-level onVoid* family — onVoidClick, onVoidDoubleClick, onVoidContextMenu, onVoidWheel, onVoidPointerDown, onVoidPointerUp. A void event fires when a gesture runs no handler on any object: the click (or wheel, or pointer press) that reaches nothing interactive.

The model

A backdrop sits behind the scene, and onVoidClick is its onClick. A click runs any onClick along its path — the objects it intersects and their ancestors — and reaches the backdrop only when none has one. An empty-space click and a click on an object without an onClick are the same event: both reach the backdrop. (The backdrop is conceptual — the "no object handler ran" branch surfaced as a <Canvas> prop, not a real mesh.)

Two properties follow:

  • Exclusive — for a gesture, the canvas-level handler and its onVoid* never both fire. A click that runs an onClick bubbles up to the canvas-level onClick; a click that runs none fires onVoidClick; a click consumed by stopPropagation fires neither.
  • Gesture-scoped — each void event asks only about its own gesture's handler, judged over the hit's bubble chain (a handler on the object or an ancestor counts). A mesh with onPointerMove but no onClick is transparent to clicking, so clicking it is a void click.

Engine

propagate() now reports whether any object-level handler fired, and each gesture caller owns its tail — canvasLevel (fire the canvas handler on !stopped) for hover and captured drags, finishVoidable (bubbled hit → canvas handler, otherwise → onVoid<Kind>) for the void family. The per-object complement-set dispatch and its re-raycast phases are removed; the engine is otherwise unchanged.

Breaking changes

Removes onClickMissed / onDoubleClickMissed / onContextMenuMissed (object- and canvas-level).

Migration:

  • Canvas-level "clicked nothing" → onVoidClick (direct swap).
  • Per-object "deselect when the user clicks elsewhere" → selection state: set it on an object's onClick, clear it on the canvas onVoidClick. To also clear when another object is clicked, put the clearing handler on the root and stopPropagation() on objects that shouldn't clear it.

Tests

A dedicated void events describe covers the matrix across all six gestures: firing on empty/hit, exclusivity (and silence under stopPropagation), gesture-scoped transparency, chain-aware delivery (an ancestor's handler counts), the ray firedOnObject rule, and the void-event payload contract. 229 tests pass.

Docs

README, the events API reference, and the Tour chapter are migrated, with a new onVoidWheel demo. The CONTEXT.md glossary's "Missed event" entry is replaced with "Void event" + "Backdrop".

Closes #49
Refs #21

@pkg-pr-new

pkg-pr-new Bot commented Jun 8, 2026

Copy link
Copy Markdown

commit: 19a1bbf

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