Skip to content

fix: RangeCalendar range selection in shadow DOM - #10347

Closed
AKnassa wants to merge 2 commits into
adobe:mainfrom
AKnassa:rocky/issue-10330-shadow-range-calendar
Closed

fix: RangeCalendar range selection in shadow DOM#10347
AKnassa wants to merge 2 commits into
adobe:mainfrom
AKnassa:rocky/issue-10330-shadow-range-calendar

Conversation

@AKnassa

@AKnassa AKnassa commented Jul 21, 2026

Copy link
Copy Markdown

Closes #10330

When a RangeCalendar (including the calendar inside DateRangePicker) is rendered inside a shadow root with enableShadowDOM(), clicking a single date immediately committed a range where start === end, so a multi-day range could never be selected by clicking. Dragging a range had a second symptom from the same cause: onChange fired twice, once from the early commit and once from the cell's own press.

The window-level pointerup listener in useRangeCalendar read the raw e.target. For events coming from inside a shadow root the browser retargets that to the shadow host, so the calendar always believed the pointer was released outside of it and committed the in-progress selection on every click. The listener now resolves the real target with the existing shadow-safe getEventTarget() helper, as suggested by @snowystinger in the issue.

Resolving the real target also feeds the closest('button, [role="button"]') check on the line below, which is what stops the month navigation buttons from committing an in-progress selection. That path was broken inside a shadow root too, and is now covered.

Nested shadow roots work as well — the window listener only ever sees the outermost host, and composedPath()[0] resolves through every boundary. One known limit, unchanged by this PR and shared with useInteractOutside / useToastRegion: closest() does not pierce shadow boundaries, so it would still miss a navigation button living in a deeper shadow root than the calendar itself. That looked like a separate, wider change rather than something to fold in here.

✅ Pull Request Checklist:

  • Included link to corresponding React Spectrum GitHub Issue.
  • Added/updated unit tests and storybook for this change (for new code or code which already has tests).
  • Filled out test instructions.
  • Updated documentation (if it already exists for this component).
  • Looked at the Accessibility Practices for this feature - Aria Practices

📝 Test Instructions:

  1. yarn jest packages/react-aria-components/test/RangeCalendar.test.tsx — the new shadow DOM describe has eight tests. Five of them fail on main and pass with the fix:

    • selecting a range by clicking two dates — onChange fires with start === end on the first click
    • selecting a range by dragging — onChange fires twice
    • pressing the month navigation buttons mid-selection — commits early
    • commitBehavior="clear" — the first click wipes the selection instead of anchoring it
    • selecting a range inside nested shadow roots

    The other three pass with and without the fix and are there as guards on the inverse paths, so a future change can't over-correct: releasing outside the calendar, releasing outside the calendar but inside the same shadow root, and committing when focus leaves the shadow root.

  2. Manual: call enableShadowDOM() from @react-stately/flags, render a RangeCalendar inside an open shadow root, and click two dates — the range commits only on the second click, matching light DOM behavior.

🧢 Your Project:

No response

The window-level pointerup listener in useRangeCalendar read the raw
e.target, which is retargeted to the shadow host for events originating
inside a shadow root. nodeContains() then always reported the release
as outside the calendar, committing {start === end} on the first click.
Resolve the real target with the shadow-safe getEventTarget() helper.

Fixes adobe#10330
@snowystinger snowystinger added the waiting Waiting on Issue Author label Aug 3, 2026
@AKnassa
AKnassa marked this pull request as ready for review August 8, 2026 21:54
Extend the shadow DOM describe with paths the first regression test did
not reach, all of which fail without the getEventTarget() fix: drag
selection (onChange fired twice), the month navigation buttons (the
closest('button') branch), commitBehavior "clear", and nested shadow
roots.

Add guards on the inverse paths so a later change cannot over-correct:
releasing outside the calendar but inside the same shadow root, and
committing when focus leaves the shadow root.

Share the per-test setup through a renderInShadowRoot() helper, and note
that enableShadowDOM() is a one way flag so this describe must stay last.
@snowystinger

Copy link
Copy Markdown
Member

Just noticed I have this covered in chore: additional shadow dom tests and fixes
I'll review the tests here and add them there as well. Sorry about that, slipped by me.
Thanks, I'm going to close this one out

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

Labels

waiting Waiting on Issue Author

Projects

None yet

Development

Successfully merging this pull request may close these issues.

RangeCalendar inside shadow DOM commits range on first click (endDragging uses retargeted e.target)

2 participants