feat(react-overflow): add OverflowReorderObserver to fix stale state after React-driven reorder (#36068)#36231
Draft
layershifter wants to merge 5 commits into
Draft
Conversation
…ow on React-driven reorder Fluent's overflow manager only listens to ResizeObserver, so reordering items via React state (without a container resize) leaves visibility flags stale — items that should now overflow stay visible, and items that should now be visible stay display:none. OverflowReorderObserver is a renderless opt-in component that observes the container for direct-child mutations via MutationObserver and triggers updateOverflow(). Drop it inside <Overflow> when items can be reordered, added, or removed via React state. Fixes microsoft#36068. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
📊 Bundle size reportUnchanged fixtures
|
|
Pull request demo site: URL |
…solution - Read targetDocument.defaultView via useFluent_unstable instead of el.ownerDocument.defaultView. Matches the v9 rule of not touching window/document directly. - Add @fluentui/react-shared-contexts as a dependency. - Simplify the Cypress test: drop the without-observer regression-guard scenario; ReorderExample always renders the observer now. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… field Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…server Demonstrates the React-state-driven reorder scenario from microsoft#36068 and shows how dropping OverflowReorderObserver into the container restores correct visibility without a resize. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…er story Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
| @@ -0,0 +1,7 @@ | |||
| { | |||
There was a problem hiding this comment.
🕵🏾♀️ visual changes to review in the Visual Change Report
vr-tests-react-components/Avatar Converged 1 screenshots
| Image Name | Diff(in Pixels) | Image Type |
|---|---|---|
| vr-tests-react-components/Avatar Converged.badgeMask - RTL.normal.chromium.png | 2 | Changed |
vr-tests-react-components/Charts-DonutChart 1 screenshots
| Image Name | Diff(in Pixels) | Image Type |
|---|---|---|
| vr-tests-react-components/Charts-DonutChart.Dynamic - RTL.default.chromium.png | 5570 | Changed |
vr-tests-react-components/Positioning 2 screenshots
| Image Name | Diff(in Pixels) | Image Type |
|---|---|---|
| vr-tests-react-components/Positioning.Positioning end.updated 2 times.chromium.png | 724 | Changed |
| vr-tests-react-components/Positioning.Positioning end.chromium.png | 726 | Changed |
vr-tests-react-components/ProgressBar converged 2 screenshots
| Image Name | Diff(in Pixels) | Image Type |
|---|---|---|
| vr-tests-react-components/ProgressBar converged.Indeterminate + thickness - Dark Mode.default.chromium.png | 51 | Changed |
| vr-tests-react-components/ProgressBar converged.Indeterminate + thickness - High Contrast.default.chromium.png | 69 | Changed |
vr-tests-react-components/TagPicker 2 screenshots
| Image Name | Diff(in Pixels) | Image Type |
|---|---|---|
| vr-tests-react-components/TagPicker.disabled - Dark Mode.disabled input hover.chromium.png | 658 | Changed |
| vr-tests-react-components/TagPicker.disabled - RTL.disabled input hover.chromium.png | 635 | Changed |
There were 2 duplicate changes discarded. Check the build logs for more information.
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.

Summary
Fluent's overflow manager only listens to
ResizeObserveron the container, so when items inside<Overflow>are reordered, added, or removed via React state — without a container resize — the manager never recomputes. Items keep their staleisVisibleflags glued to the same ids while their DOM positions shift around them. The result is the bug reported in #36068: items that should now be visible staydisplay:none, and items that should now overflow stay rendered.This PR adds an opt-in renderless component
<OverflowReorderObserver />that, when included inside<Overflow>, observes the container with aMutationObserver({ childList: true })and callsupdateOverflow()when direct children mutate. The manager then re-runsprocessOverflowItems()— its heap comparator already readscompareDocumentPositionlive, so it self-corrects under the new DOM order.containerRef+updateOverflowfrom the existingOverflowContext.containerRefis added toOverflowContextValueas optional, so external consumers that hand-build a context value are not broken.@fluentui/react-componentsfor parity with other Overflow primitives.Usage
Fixes #36068.
Test plan
react-overflow(type-check, build, generate-api).Overflow.cy.tsx:recomputes overflow when items are reordered via React state— fix verification.without the observer, reorder leaves visibility flags stale— regression guard documenting that the bug still exists without the component (preserves backwards-compatible default).data-overflowingflags; post-fix the visibility correctly flips onsetItems([...items].reverse())without any resize.🤖 Generated with Claude Code