refactor(window-group): reactive stacking + deferred layout (fixes #23, #24)#25
Merged
Conversation
…layout Resolves the root-cause shared by #23 (unbounded z-index escalation) and #24 (layout presets silently no-op on dynamically rendered windows) by replacing the imperative ref-based stacking model with a reactive one and deferring layout application until the registry and container are ready. - WindowGroup: introduce stackOrder state, derive normalized z-index from it - WindowGroup: bound the increment counter via maxZIndex wrap to initialZIndex - WindowGroup: defer applyLayout when registry or container dims are not ready; flushed once both become available - Expose initialZIndex, maxZIndex, zIndexStrategy on WindowGroup and Window - Expose stackOrder on WindowGroupContextValue for introspection - Docs: new "Dynamic Windows" and "Z-index Management" sections with demos - Upgrade guide: v3.1 behavioral notes and migration snippets
Contributor
There was a problem hiding this comment.
Pull request overview
Refactors Window.Group stacking/layout logic to be reactive and adds bounded z-index behavior to address long-running z-index escalation and dynamic .map() registration/layout timing issues.
Changes:
- Introduces
zIndexStrategy(increment/normalize) andstackOrderforWindow.Group, plusinitialZIndex/maxZIndexoptions. - Adds
initialZIndex/maxZIndexto stand-aloneWindowz-index handling (with wrap-around behavior). - Defers
applyLayoutuntil registry and container measurements are ready; updates docs/demos/tests accordingly.
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| package/src/hooks/use-window-state.ts | Adds stand-alone initialZIndex/maxZIndex logic and a test reset helper for module counters |
| package/src/hooks/use-mantine-window.ts | Wires new z-index props through to window state hook |
| package/src/WindowGroup.tsx | Implements reactive stacking (stackOrder), new z-index strategies, and deferred applyLayout flushing |
| package/src/WindowGroup.context.ts | Adds ZIndexStrategy type and exposes stackOrder on the group context value |
| package/src/Window.tsx | Adds public props for stand-alone z-index configuration |
| package/src/Window.test.tsx | Adds tests for group z-index strategies, deferred layout, and stand-alone z-index props |
| docs/migrations.mdx | Documents v3.1 behavioral changes and new APIs |
| docs/docs.mdx | Adds docs sections for Dynamic Windows and Z-index Management |
| docs/demos/index.ts | Exports new demos |
| docs/demos/Window.demo.zIndexStrategy.tsx | New demo showcasing z-index strategies |
| docs/demos/Window.demo.dynamicWindows.tsx | New demo showcasing dynamic .map() windows and layout presets |
- Stand-alone z-index: seed the module counter up to `initialZIndex` before incrementing so `bringToFront` never moves a window backward; also raise `maxZIndex` to at least `initialZIndex` to avoid a degenerate wrap range. Reveals a real bug where `<Window initialZIndex=500 />` would snap to 201 on first click. - `registerWindow`: move the `incrementMap.has(id)` check inside a functional setState so unmount+remount in the same commit (StrictMode / .map() churn) always consults the latest map instead of a stale closure snapshot. - `applyLayout`: defer only when the registry is empty; when the registry is populated but nothing is visible, treat as a no-op (pre-v3.1 behavior) instead of parking the layout in `pendingLayoutRef` where visibility changes can't trigger a flush. - `maxZIndex` JSDoc: clarify that the prop is ignored under `'normalize'`, matching the current implementation. - Dynamic Windows demo: derive the next id from `prev.length` inside the state updater and stop double-prefixing ids with `win_` (was producing `win_win_c` in state). - Tests: tighten stand-alone z-index coverage to verify `initialZIndex` seeding and exact wrap-to-initialZIndex; add a regression test for `applyLayout` on a populated-but-all-hidden registry.
- Update Yarn to 4.14.1 and adjust yarnPath - Upgrade Mantine core, hooks, and code-highlight to 9.0.2 - Bump Next, React, and React-DOM to the latest patch releases - Update Storybook, oxlint, postcss, rollup, and vite to newer patch versions - Keep package.json and docs/package.json in sync with new versions
This was referenced Apr 21, 2026
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
Unified refactor that addresses both open issues by replacing the imperative ref-based stacking/registry model with a reactive one.
'increment'counter now honors amaxZIndexcap and wraps back toinitialZIndexwhen exceeded; new'normalize'strategy derives z-indexes from a reactivestackOrderso values always stay compact (initialZIndex .. initialZIndex + N - 1). Stand-aloneWindowalso gainsinitialZIndex/maxZIndexprops..map()windows):applyLayoutnow defers when the registry is empty or the container has not been measured, and flushes once both become ready via arequestAnimationFrame.New public API
Window.Group—initialZIndex,maxZIndex,zIndexStrategy('increment'|'normalize')Window(stand-alone) —initialZIndex,maxZIndexWindowGroupContextValue— adds read-onlystackOrder: string[]All additive — the default
zIndexStrategy='increment'with nomaxZIndexpreserves the previous behavior.Behavioral changes (documented in Upgrade guide)
Windowinstances still share a module-level z-index counter per scope (portal vs container), but the counter now wraps toinitialZIndexwhen it would exceedmaxZIndex. Before, the counter grew unbounded.Window.Groupstacking is state-driven instead of ref-driven. Externally observable behavior is unchanged unless you read intermediate z-index values via internals.Test plan
.map()registration, deferred layout, and stand-alone z-index propsyarn test(syncpack + oxfmt + typecheck + lint + jest) greenyarn build+yarn docgencleanyarn docs:buildcleanWindow.Group/Multiple Windowsdemos