Repository navigation
fix(react-headless-components-preview): dismiss Popover on focus exit by default - #36929
Open
Paul Mardling (PaulGMardling) wants to merge 13 commits into
Open
Paul Mardling (PaulGMardling) wants to merge 13 commits into
Paul Mardling (PaulGMardling) wants to merge 13 commits into
Conversation
…t dismissal Preserve destination focus when a non-modal Popover's logical focus boundary is exited. Keep the behavior opt-in and honor controlled state, native closing, and existing modal semantics. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 44bec1f8-d9c2-4333-a285-7270b37fe5da
📊 Bundle size reportUnchanged fixtures
|
|
Pull request demo site: URL |
Make non-modal focus-exit dismissal built-in accessibility behavior rather than a consumer opt-in. Remove the proposed public API additions and cover default TeachingPopover and InfoLabel behavior. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 44bec1f8-d9c2-4333-a285-7270b37fe5da
…tion Preserve logical React portal focus through external shadow roots, retain native listeners across commits, and detect confirmed iframe focus exits without treating window blur alone as dismissal. Add unit and real-browser regressions while keeping the public API unchanged. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 44bec1f8-d9c2-4333-a285-7270b37fe5da
…ship Classify the live focus destination after Portal layout ownership settles, observe both trigger and surface portal roots, and retain bounded frame observation with direct same-origin events. Preserve native close intent through concurrent content updates and add native-browser and cleanup regressions. Keep the public API unchanged. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 44bec1f8-d9c2-4333-a285-7270b37fe5da
…root chains Observe every shadow ancestor for owned React focus, including nested closed roots. Cover whole-Popover and Fluent Portal nesting plus the approved bounded opaque-frame fallback without mixing InfoLabel changes into this branch. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 44bec1f8-d9c2-4333-a285-7270b37fe5da
…ycle Preserve native-close focus ownership through content replacement, resolve owned focus across accessible iframe chains, and retire navigated document observers safely. Guard reentrant unmounts against stale observations and resource allocation while retaining cached-document subscriptions for Back/Forward restoration. Add native browser and unit regressions without changing the public API. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 44bec1f8-d9c2-4333-a285-7270b37fe5da
Paul Mardling (PaulGMardling)
marked this pull request as ready for review
October 20, 2026 16:00
Keep master's released dependency versions and align the test-only Portal dependency with version 9.8.17. Preserve the existing Popover focus engine and public API reports. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 44bec1f8-d9c2-4333-a285-7270b37fe5da
Normalize the test-only Portal dependency, close native state before releasing the surface in legacy React roots, and compact private observation state with one scoped frame sampler. Preserve the existing public API and 10% bundle budget. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 44bec1f8-d9c2-4333-a285-7270b37fe5da
…rame focus Keep hover-triggered content visible on keyboard focus exit while its trigger or surface remains hovered. Observe reachable same-origin ancestor documents and resolve the live focused frame chain, excluding embedding-only frames from sampling while retaining ancestor-portal opaque transitions. Add native controlled-state regressions and runnable Storybook examples without expanding the public API or bundle budget. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 44bec1f8-d9c2-4333-a285-7270b37fe5da
Track live owned pointer targets across Portal content and invalidate removed hover sources. Cache trigger focus passively while closed so opening observes shadow roots before the first exit, including StrictMode replay. Add controlled/native regressions and runnable stories while preserving the public API, close lifecycle, and existing bundle budget. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 44bec1f8-d9c2-4333-a285-7270b37fe5da
Share React-only fixtures and replace redundant mode permutations with focused boundary coverage. Preserve native lifecycle, iframe, hover, shadow-root, and controlled-state regressions without changing production behavior. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 44bec1f8-d9c2-4333-a285-7270b37fe5da
Move boundary browser coverage into PopoverFocus and frame observation units into the existing Popover suite. Remove three test files without removing test cases or changing production code. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 44bec1f8-d9c2-4333-a285-7270b37fe5da
Keep one example export per story file for SSR discovery and render the unowned shadow sibling into an element rather than directly into a ShadowRoot. Preserve story IDs and keyboard reproduction behavior. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 44bec1f8-d9c2-4333-a285-7270b37fe5da
Contributor
Author
|
Downstream Modern integration draft: https://dev.azure.com/office/Office/_git/1JS/pullrequest/5905914 (internal access required). This adds regression coverage and a native-keyboard reproduction for 39630; it does not yet deliver the runtime fix. |
This branch has not been deployed
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.


Previous Behavior
Non-modal headless Popover stays open after Tab moves focus outside.
New Behavior
When you Tab out of a non-modal Popover, it now closes and leaves focus on the control you moved to. Moving focus between the trigger, popup, or content belonging to either keeps it open, including content in portals, shadow roots, and iframes. Hovering over the trigger, popup, or its linked content also keeps it open.
No new props are needed. Controlled Popovers still decide whether to accept a close request, and modal Popovers work as before.
For iframe focus changes we can't detect directly, we check every 100 ms while needed. A quick move out and back between checks can still be missed.
Related Issue(s)
Validation
At
2522d104b8: three test files consolidated without removing cases; production unchanged from57963a1093.yarn nx run react-headless-components-preview:test --runTestsByPath src/components/Popover/Popover.test.tsx --runInBand --skip-nx-cacheyarn nx run react-headless-components-preview:e2e --spec src/components/Popover/Popover.cy.tsx,src/components/Popover/PopoverFocus.cy.tsx,src/components/Popover/PopoverLifecycle.cy.tsx,src/components/TeachingPopover/TeachingPopover.cy.tsx,src/components/InfoLabel/InfoLabel.cy.tsx --browser chrome --config retries=0 --skip-nx-cacheyarn nx run react-headless-components-preview:test-rit--17--e2e --command='yarn run -T rit --project-id portal-focus-fixes --react 17 --run e2e --verbose' --excludeTaskDependencies --skip-nx-cacheyarn nx run react-headless-components-preview:test-rit--18--e2e --command='yarn run -T rit --project-id portal-focus-fixes --react 18 --run e2e --verbose' --excludeTaskDependencies --skip-nx-cacheyarn nx run react-headless-components-preview:type-check --excludeTaskDependenciesyarn nx run react-headless-components-preview:lintAt
9cde4434ae, the story-only CI repairs also passedyarn nx run react-headless-components-preview-stories:test-ssr --skip-nx-cache,yarn nx run-many -p react-headless-components-preview-stories -t test-rit--17--type-check,test-rit--18--type-check --excludeTaskDependencies --skip-nx-cache(after no-install preparation), andyarn nx run react-headless-components-preview-stories:lint. Both Portal stories retained their URLs and passed Chrome keyboard/hover checks with no runtime errors. Fresh hosted CI is pending.RIT used prepared local scaffolds selecting the three Popover specs. Normal commit hooks passed. Latest-head hosted CI and Windows/Narrator/TCS verification remain outstanding; the earlier hosted Drawer chunk-load failure has not been established as unrelated.
Reproduction and fix verification
Open the non-modal Popover focus-exit repro
false, and focus stays on After popover. Deployed behavior observed on 23 October 2026: focus moved, but the popup stayed open.This editable link runs the deployed playground, not the PR build; deployments can change.
Against the fix: from an initialized task checkout at
2522d104b8, runyarn nx run react-headless-components-preview-stories:storybook --port=44123 --ci --no-open. Move keyboard focus into each preview; do not click external Portal controls, which can invoke native light dismiss.Fixtures:
packages/react-components/react-headless-components-preview/stories/src/Popover/(PopoverOpenOnHover,PopoverInFrame,PopoverHoverWithPortal,PopoverTriggerPortal).Verified 26 October 2026 in Chrome 155 at
57963a1093; test consolidation preserved production behavior; both Portal stories were rechecked in Chrome 155 at9cde4434aeafter their SSR/React 17 compatibility repair. Baselines92e88b4e9e(first two stories) ande9260d0c3f(Portal stories) respectively hid hovered content or missed dismissal. Baseline checks used component-source bundles in memory, not historical Storybook builds. Evidence covers browser focus/visibility, not screen-reader speech.