Skip to content

feat(react-overflow): add OverflowReorderObserver to fix stale state after React-driven reorder (#36068)#36231

Draft
layershifter wants to merge 5 commits into
microsoft:masterfrom
layershifter:feat/overflow-reorder-observer
Draft

feat(react-overflow): add OverflowReorderObserver to fix stale state after React-driven reorder (#36068)#36231
layershifter wants to merge 5 commits into
microsoft:masterfrom
layershifter:feat/overflow-reorder-observer

Conversation

@layershifter
Copy link
Copy Markdown
Member

Summary

Fluent's overflow manager only listens to ResizeObserver on 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 stale isVisible flags 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 stay display: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 a MutationObserver({ childList: true }) and calls updateOverflow() when direct children mutate. The manager then re-runs processOverflowItems() — its heap comparator already reads compareDocumentPosition live, so it self-corrects under the new DOM order.

  • Opt-in by inclusion: zero default-behavior change, no observer attaches unless the component is used.
  • Renderless: consumes containerRef + updateOverflow from the existing OverflowContext.
  • containerRef is added to OverflowContextValue as optional, so external consumers that hand-build a context value are not broken.
  • Re-exported through @fluentui/react-components for parity with other Overflow primitives.

Usage

<Overflow>
  <div style={{ display: 'flex' }}>
    <OverflowReorderObserver />
    {items.map(id => (
      <OverflowItem key={id} id={id} priority={1}>{id}</OverflowItem>
    ))}
  </div>
</Overflow>

Fixes #36068.

Test plan

  • CI passes on react-overflow (type-check, build, generate-api).
  • New Cypress tests in 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).
  • Verified locally end-to-end in a Vite + React 19 app reproducing the original issue: pre-fix the reorder leaves stale data-overflowing flags; post-fix the visibility correctly flips on setItems([...items].reverse()) without any resize.

🤖 Generated with Claude Code

…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>
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 19, 2026

📊 Bundle size report

Package & Exports Baseline (minified/GZIP) PR Change
react-charts
AreaChart
402.329 kB
125.686 kB
402.344 kB
125.691 kB
15 B
5 B
react-charts
DeclarativeChart
753.15 kB
219.855 kB
753.165 kB
219.859 kB
15 B
4 B
react-charts
DonutChart
312.743 kB
96.309 kB
312.758 kB
96.313 kB
15 B
4 B
react-charts
FunnelChart
304.29 kB
93.131 kB
304.305 kB
93.136 kB
15 B
5 B
react-charts
GanttChart
385.429 kB
119.988 kB
385.444 kB
119.991 kB
15 B
3 B
react-charts
GaugeChart
312.178 kB
95.686 kB
312.193 kB
95.691 kB
15 B
5 B
react-charts
GroupedVerticalBarChart
393.301 kB
122.713 kB
393.316 kB
122.718 kB
15 B
5 B
react-charts
HeatMapChart
387.505 kB
121.082 kB
387.52 kB
121.088 kB
15 B
6 B
react-charts
HorizontalBarChart
292.472 kB
88.88 kB
292.487 kB
88.884 kB
15 B
4 B
react-charts
Legends
231.996 kB
69.616 kB
232.011 kB
69.621 kB
15 B
5 B
react-charts
LineChart
413.661 kB
128.56 kB
413.676 kB
128.564 kB
15 B
4 B
react-charts
PolarChart
341.139 kB
106.205 kB
341.154 kB
106.21 kB
15 B
5 B
react-charts
ScatterChart
393.044 kB
122.692 kB
393.059 kB
122.697 kB
15 B
5 B
react-charts
VerticalBarChart
429.788 kB
127.562 kB
429.803 kB
127.566 kB
15 B
4 B
react-charts
VerticalStackedBarChart
399.373 kB
124.094 kB
399.388 kB
124.098 kB
15 B
4 B
react-components
react-components: entire library
1.293 MB
324.749 kB
1.293 MB
324.808 kB
391 B
59 B
Unchanged fixtures
Package & Exports Size (minified/GZIP)
react-breadcrumb
@fluentui/react-breadcrumb - package
102.926 kB
28.76 kB
react-charts
HorizontalBarChartWithAxis
63 B
83 B
react-charts
SankeyChart
209.004 kB
66.601 kB
react-charts
Sparkline
80.503 kB
26.644 kB
react-components
react-components: Button, FluentProvider & webLightTheme
66.328 kB
19.02 kB
react-components
react-components: Accordion, Button, FluentProvider, Image, Menu, Popover
226.773 kB
68.079 kB
react-components
react-components: FluentProvider & webLightTheme
39.525 kB
13.113 kB
react-headless-components-preview
react-headless-components-preview: entire library
130.447 kB
38.034 kB
react-overflow
hooks only
11.966 kB
4.565 kB
react-portal-compat
PortalCompatProvider
5.567 kB
2.237 kB
react-timepicker-compat
TimePicker
104.461 kB
34.894 kB
🤖 This report was generated against 1031517ee91349057a25fecb81eaab5794651d87

@github-actions
Copy link
Copy Markdown

Pull request demo site: URL

layershifter and others added 4 commits May 19, 2026 17:54
…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 @@
{
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🕵🏾‍♀️ 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.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Overflow manager stale state after item reorder (via React)

1 participant