Skip to content

fix(runtime): reach inner-child handlers via subtree descent (offscreen-safe clicks) - #35

Merged
joelgwebber merged 2 commits into
mainfrom
fix/click-subtree-descent
Sep 11, 2026
Merged

fix(runtime): reach inner-child handlers via subtree descent (offscreen-safe clicks)#35
joelgwebber merged 2 commits into
mainfrom
fix/click-subtree-descent

Conversation

@joelgwebber

Copy link
Copy Markdown
Collaborator

clickElement routed its dispatch through document.elementFromPoint(centre) to
land on the topmost node — needed because a custom option often carries its
click handler on an inner child (JetBlue: <jb-select-option> > div.body; a click
on the outer node bubbles past the handler and no-ops). But elementFromPoint
returns null for a point outside the viewport, so a far-scrolled / offscreen
option
fell back to the outer node and silently didn't commit.

Fix

Replace it with deepestElementAt(root, x, y): descend the element's own
subtree to the deepest node whose getBoundingClientRect contains the centre,
then dispatch there so the event bubbles up to whichever inner node holds the
handler. Own-rects are valid off-screen, so it reaches the inner child and
survives far-scroll — no viewport dependency, no settle loop.

The elementFromPoint variant is preserved as clickElementAtPoint (documented,
unused) for the narrow "hit whatever paints on top at this point" case.

Validation

Live on JetBlue's month/day/year/state/country custom selects, driven atomically
with no scaffolding (including the exact far-scroll repro: day 28 → 3). Runtime
suite green (click.test.ts covers subtree-descent reach, the offscreen guard,
and the preserved clickElementAtPoint).

…en-safe clicks)

clickElement routed its dispatch through document.elementFromPoint(centre) to land on the topmost node -- needed because a custom option often carries its handler on an inner child (jetblue: <jb-select-option> > div.body; a click on the outer node bubbles past the handler and no-ops). But elementFromPoint returns null for a point outside the viewport, so a far-scrolled/offscreen option fell back to the outer node and silently didn't commit.

Replace it with deepestElementAt(root,x,y): descend the element's OWN subtree to the deepest node whose getBoundingClientRect contains the centre, then dispatch there so the event bubbles up to whichever inner node holds the handler. Own-rects are valid off-screen, so it reaches the inner child AND survives far-scroll -- no viewport dependency, no settle loop. The elementFromPoint variant is preserved as clickElementAtPoint (documented, unused) for the narrow hit-whatever-paints-on-top case. Validated live on jetblue's month/day/year/state/country selects (atomic, no scaffolding); 54 runtime tests green.

Signed-off-by: Joel Webber <joel@fullstory.com>
The subtree-descent change to packages/runtime/src/dom.ts rebuilt the
runtime bundle, but the committed copy embedded in the Go binary was not
regenerated, so CI's 'Embedded runtime bundle in sync' check failed. Rebuild
+ re-embed via 'go generate ./runtimebundle/...'.

Signed-off-by: Joel Webber <joel@fullstory.com>
@joelgwebber
joelgwebber merged commit 468f00d into main Sep 11, 2026
2 checks passed
@joelgwebber
joelgwebber deleted the fix/click-subtree-descent branch September 11, 2026 16:05
joelgwebber added a commit that referenced this pull request Sep 11, 2026
…en-safe clicks) (#35)

* fix(runtime): reach inner-child handlers via subtree descent (offscreen-safe clicks)

clickElement routed its dispatch through document.elementFromPoint(centre) to land on the topmost node -- needed because a custom option often carries its handler on an inner child (jetblue: <jb-select-option> > div.body; a click on the outer node bubbles past the handler and no-ops). But elementFromPoint returns null for a point outside the viewport, so a far-scrolled/offscreen option fell back to the outer node and silently didn't commit.

Replace it with deepestElementAt(root,x,y): descend the element's OWN subtree to the deepest node whose getBoundingClientRect contains the centre, then dispatch there so the event bubbles up to whichever inner node holds the handler. Own-rects are valid off-screen, so it reaches the inner child AND survives far-scroll -- no viewport dependency, no settle loop. The elementFromPoint variant is preserved as clickElementAtPoint (documented, unused) for the narrow hit-whatever-paints-on-top case. Validated live on jetblue's month/day/year/state/country selects (atomic, no scaffolding); 54 runtime tests green.

Signed-off-by: Joel Webber <joel@fullstory.com>

* chore(runtime): resync embedded runtime bundle

The subtree-descent change to packages/runtime/src/dom.ts rebuilt the
runtime bundle, but the committed copy embedded in the Go binary was not
regenerated, so CI's 'Embedded runtime bundle in sync' check failed. Rebuild
+ re-embed via 'go generate ./runtimebundle/...'.

Signed-off-by: Joel Webber <joel@fullstory.com>

---------

Signed-off-by: Joel Webber <joel@fullstory.com>
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