refactor(dialog): extract shared overlay utilities#16
Closed
ivanbanov wants to merge 2 commits into
Closed
Conversation
New packages, extracted from the dialog's internals so the whole overlay family (drawer, alert-dialog, popover, menu, combobox) can build on them: - @dunky.dev/dom-layer-stack — shared registry of open overlay layers (topmost resolution, nesting containment, aria-hidden/inert sync) - @dunky.dev/react-use-layer-stack — LayerDepthContext + useLayerDepth - @dunky.dev/dom-interact-outside — document-level outside-interaction tracking for non-modal overlays - @dunky.dev/react-use-interact-outside — useInteractOutside lifecycle @dunky.dev/react-dialog now consumes the shared layer stack instead of its private one. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Refine the extracted overlay-utility packages: - @dunky.dev/dom-layer-stack — shared registry of open overlay layers (topmost resolution, nesting containment, aria-hidden/inert sync) - @dunky.dev/react-use-layer-stack — LayerPathContext + useLayerPath (renamed from the depth-based API) - @dunky.dev/dom-interact-outside — document-level outside-interaction tracking for non-modal overlays - @dunky.dev/react-use-interact-outside — useInteractOutside lifecycle Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
5 tasks
Member
Author
|
Superseded by #29. This branch extracted overlay utilities along different lines (separate #29 carries the same intent — one shared overlay-coordination layer for the whole family — rebuilt on today's |
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
The overlay family — dialog, drawer, alert-dialog, popover, menu, combobox — shares the same coordination problems: which layer owns Escape and outside presses when overlays stack, how nesting is tracked across portals, how the rest of the page is hidden from assistive tech, and how a non-modal overlay detects an outside interaction with no backdrop to catch it. This PR extracts that behavior from the dialog's internals into shared packages so every upcoming overlay primitive builds on one implementation instead of growing its own. The overlay-component PRs are stacked on this branch.
New packages:
@dunky.dev/dom-layer-stack— one shared registry of open overlay layers.registerLayer({ id, path, element, modal })returns an unregister disposer; thepath(the enclosing layers' ids, outermost first) carries nesting depth in its length and ancestry in its contents.isTopmostLayeranswers which layer owns Escape, the focus trap, and outside presses (longest path wins, open order breaks ties);layerContainsTargetlets outside-press detection treat descendant layers as inside while siblings and unrelated stacks stay outside. Registering also keeps assistive-tech containment in sync: everything outside the topmost modal layer getsaria-hidden+inert— layers stacked above it stay reachable — with an exact undo.@dunky.dev/react-use-layer-stack— the React half:LayerPathContext+useLayerPath()carry the nesting chain across portals, where DOM order would invert logical nesting; every overlay root appends its id and provides the extended path.@dunky.dev/dom-interact-outside— document-level outside-interaction detection for non-modal overlays:trackInteractOutside(container, { onInteractOutside, ignore })fires on capture-phasepointerdown/focusinoutside the container, reporting a press that also moves focus once, not twice. A touch press reports only once itsclickconfirms a tap, so a scroll or pan that starts outside never dismisses.@dunky.dev/react-use-interact-outside— its React lifecycle:useInteractOutside(ref, options)binds while mounted and reads the handler andignorepredicate through refs, so inline closures never re-bind the document listeners.Changes
@dunky.dev/react-dialogdrops its privatesrc/utils/stack.tsandsrc/utils/hide-outside.tsand consumes the shared layer stack instead — same behavior, one refinement: a non-modal layer opening above a modal dialog no longer drops the page's assistive-tech containment; the containment anchors on the topmost modal layer and keeps the layers above it reachable, which is what lets a popover or menu live inside a dialog.SPEC.mdupdated to reference the shared utilities.tsconfig.json,tsdown.config.ts, lockfile) for the four new packages, plus a changeset.Test plan
pnpm test:ci— 104 tests, 11 files, all passingpnpm typecheckpnpm lintpnpm format:checkpnpm build— publint clean for all packages🤖 Generated with Claude Code