From e6189bf907f5dfe1fc34f07ce48c86861ed6347b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matheus=20Vila=C3=A7a=20de=20Jesus?= Date: Sat, 24 Oct 2026 23:00:29 -0300 Subject: [PATCH 1/2] chore: start #550 From a75cc03d6b52832eb79001101ab52150848900a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matheus=20Vila=C3=A7a=20de=20Jesus?= Date: Sat, 24 Oct 2026 23:43:35 -0300 Subject: [PATCH 2/2] feat: layers.modalOpen store, hover UI never opens under a modal (#550) Fixes #550 --- .agents/skills/codeatlas-ui/API.md | 10 +++ .changeset/layers-store-modal-gate.md | 5 ++ AGENTS.md | 2 + .../lib/components/HoverCard/HoverCard.svelte | 4 +- .../NavigationMenu/NavigationMenuItem.svelte | 4 +- .../Overlays/ModalGate.svelte.test.ts | 77 +++++++++++++++++++ .../components/Overlays/ModalGate.test.svelte | 44 +++++++++++ .../src/lib/components/Tooltip/Tooltip.svelte | 3 +- packages/ui/src/lib/index.ts | 1 + packages/ui/src/lib/primitives/index.ts | 1 + packages/ui/src/lib/primitives/layer.ts | 17 ++++ packages/ui/src/lib/stores/index.ts | 2 + packages/ui/src/lib/stores/layers.svelte.ts | 30 ++++++++ site/src/content/docs/feedback/dialog.mdx | 15 ++++ .../content/docs/getting-started/choosing.mdx | 1 + .../docs/getting-started/workarounds.mdx | 2 +- 16 files changed, 212 insertions(+), 6 deletions(-) create mode 100644 .changeset/layers-store-modal-gate.md create mode 100644 packages/ui/src/lib/components/Overlays/ModalGate.svelte.test.ts create mode 100644 packages/ui/src/lib/components/Overlays/ModalGate.test.svelte create mode 100644 packages/ui/src/lib/stores/layers.svelte.ts diff --git a/.agents/skills/codeatlas-ui/API.md b/.agents/skills/codeatlas-ui/API.md index 598808c3..b84e3206 100644 --- a/.agents/skills/codeatlas-ui/API.md +++ b/.agents/skills/codeatlas-ui/API.md @@ -4145,6 +4145,16 @@ Type guard: true for a `NavSection` (a titled group of items), false for a plain function isNavSection(item: NavItem | NavSection): item is NavSection ``` +### layers + +`import { layers } from "@codeatlasdev/ui"` + +Reactive state of the layer stack. `layers.modalOpen` is true while a dialog, drawer or sheet is open; `layers.blocks(el)` is false for a trigger inside the topmost modal. + +```ts +const layers: { readonly modalOpen: boolean; blocks(trigger?: Element | null): boolean; } +``` + ### letterSpacing `import { letterSpacing } from "@codeatlasdev/ui"` diff --git a/.changeset/layers-store-modal-gate.md b/.changeset/layers-store-modal-gate.md new file mode 100644 index 00000000..5ac2c628 --- /dev/null +++ b/.changeset/layers-store-modal-gate.md @@ -0,0 +1,5 @@ +--- +"@codeatlasdev/ui": minor +--- + +New `layers` store: `layers.modalOpen` is reactive and true while a modal layer (Dialog, AlertDialog, Drawer, Sheet) is open; `layers.blocks(trigger)` is the same but false for a trigger inside the topmost modal. `Tooltip`, `HoverCard` and `NavigationMenuItem` no longer open on pointer movement while a modal is open (unless their trigger is inside it), so a hover panel can no longer appear under a dialog. Gate your own hover or edge UI with the store. diff --git a/AGENTS.md b/AGENTS.md index 61604033..0755c215 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -150,6 +150,8 @@ Reading failures: locators fail with an ARIA snapshot; traces are in `__traces__ 10a. **Cursor**: never write `cursor: pointer` for a clickable element. The reset (`@layer ca.reset`) gives `pointer` to `button`, `a[href]`, `label[for]`, `summary`, `[role=button|tab|option|menuitem*]` and `not-allowed` to `:disabled` / `[aria-disabled=true]`; a custom clickable tile is ` +{layers.modalOpen} + + +Behind + + {#snippet triggerChild(props)}@behind{/snippet} + Behind card + + + + Behind link + + + + + Inside + + {#snippet trigger()}Inside menu{/snippet} + + {#snippet trigger()} + Inside sub + {/snippet} + Inside item + + + diff --git a/packages/ui/src/lib/components/Tooltip/Tooltip.svelte b/packages/ui/src/lib/components/Tooltip/Tooltip.svelte index 39ba38b8..5da45991 100644 --- a/packages/ui/src/lib/components/Tooltip/Tooltip.svelte +++ b/packages/ui/src/lib/components/Tooltip/Tooltip.svelte @@ -7,6 +7,7 @@ + +
{ if (!layers.modalOpen) peek = true; }}>
+``` + +`layers.modalOpen` is reactive and true while a modal layer (not one already closing) is open. `layers.blocks(trigger)` is the same but false for a trigger inside the topmost modal, so a panel that lives in a dialog keeps working. + A host that draws outside the DOM (a native view over a webview) can follow the stack with `onLayerChange`. It fires on every open and close with the open layers, bottom to top, and their rectangles, and returns the function that unsubscribes: ```ts diff --git a/site/src/content/docs/getting-started/choosing.mdx b/site/src/content/docs/getting-started/choosing.mdx index acaa276e..6240e0f8 100644 --- a/site/src/content/docs/getting-started/choosing.mdx +++ b/site/src/content/docs/getting-started/choosing.mdx @@ -63,6 +63,7 @@ Every overlay below is a native `` or a `popover` element in the browser | A menu on right-click, long-press or Shift+F10 over a region | [`ContextMenu`](/docs/navigation/context-menu/) | The `menu` snippet takes the `DropdownMenu` items. Never the only way to reach an action. | | A rich preview of a link or person on hover and focus (touch: the link's own action) | [`HoverCard`](/docs/navigation/hover-card/) | `openDelay`, `closeDelay`, Tab reaches links inside. Tap-to-open content is `Popover`. | | A label on hover or focus | [`Tooltip`](/docs/display/tooltip/) | Text only. Never put a control or the only copy of a fact in it. | +| Your own hover or edge UI that must not open under a dialog | [`layers`](/docs/feedback/dialog/) store | `layers.modalOpen`, `layers.blocks(trigger)`. Kit hover UI already obeys it. | | A full-screen mobile drill-down (chat thread, detail) | [`MobilePane`](/docs/layout/mobile-pane/) | Fixed, safe-area aware, back button. Replaces a hand-made fixed panel. | | Photos or videos at full size | [`Lightbox`](/docs/feedback/lightbox/) | `src` and `alt` for one image, `items` for a gallery. `alt` is required. | | Jump to a route or run a command from the keyboard | [`CommandPalette`](/docs/navigation/command-palette/) | Opens with Cmd+K or Ctrl+K. Takes your `modules` and `onNavigate`. | diff --git a/site/src/content/docs/getting-started/workarounds.mdx b/site/src/content/docs/getting-started/workarounds.mdx index 7ebefcaa..2d69a12d 100644 --- a/site/src/content/docs/getting-started/workarounds.mdx +++ b/site/src/content/docs/getting-started/workarounds.mdx @@ -37,8 +37,8 @@ Imports are from `@codeatlasdev/ui` unless the last column names a subpath. | `justify-content: flex-start` and `margin-left: auto` on `.ca-btn__trailing` | `