@godxjp/ui 33.3.0 → 33.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/agent/START-HERE.md +4 -4
- package/agent/components/TopbarItem.json +1 -1
- package/agent/components.json +1 -1
- package/agent/index.json +4 -4
- package/agent/llms.txt +5 -5
- package/agent/tokens.json +16 -4
- package/dist/components/data-display/popover.js +8 -6
- package/dist/components/feedback/overlay-close-focus.js +3 -2
- package/dist/components/feedback/tooltip.js +2 -1
- package/dist/components/layout/topbar-item.js +1 -1
- package/dist/contracts/measurement.json +1 -1
- package/dist/lab/mega-menu.js +3 -2
- package/dist/lib/active-element.d.ts +12 -0
- package/dist/lib/active-element.js +10 -0
- package/dist/lib/shortcut.js +3 -2
- package/dist/props/components/layout.prop.d.ts +3 -0
- package/dist/styles/layers.json +1 -1
- package/dist/styles/shell-layout.css +19 -3
- package/dist/styles.css +1 -1
- package/dist/tokens/components/shell.css +4 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -18,7 +18,7 @@ npm i @godxjp/ui
|
|
|
18
18
|
|
|
19
19
|
<!-- agent-catalog-counts:start -->
|
|
20
20
|
|
|
21
|
-
The catalog — 166 components,
|
|
21
|
+
The catalog — 166 components, 2232 tokens, 44 cardinal rules — is published in two forms from one
|
|
22
22
|
source. **Entry point for either: [`AGENTS.md`](AGENTS.md).**
|
|
23
23
|
<!-- agent-catalog-counts:end -->
|
|
24
24
|
|
|
@@ -56,7 +56,7 @@ static files straight from this public repo — no hosting, no deploy step:
|
|
|
56
56
|
| [`agent/components-index.json`](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/components-index.json) | 50 KB | all 166 as name + group + tagline — **fetch this first** |
|
|
57
57
|
| [`agent/components/<Name>.json`](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/components/Select.json) | 1 KB–37 KB | one file per component, each with its `importPath` — **this is the route to take** |
|
|
58
58
|
| [`agent/components.json`](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/components.json) | 1.3 MB | all 166 entries in one file — most URL fetchers truncate this silently; prefer the per-component files |
|
|
59
|
-
| [`agent/tokens.json`](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/tokens.json) |
|
|
59
|
+
| [`agent/tokens.json`](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/tokens.json) | 858 KB | all 2232 design tokens, their values and why they exist |
|
|
60
60
|
| [`agent/vocabulary.json`](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/vocabulary.json) | 7 KB | the controlled prop vocabulary |
|
|
61
61
|
| [`agent/rules.json`](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/rules.json) | 23 KB | the 44 cardinal rules |
|
|
62
62
|
<!-- prettier-ignore-end -->
|
package/agent/START-HERE.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
You are about to write code against a design system you did not author. This file is the whole
|
|
4
4
|
contract. Read it before you write JSX.
|
|
5
5
|
|
|
6
|
-
**This catalog describes `@godxjp/ui` 33.
|
|
6
|
+
**This catalog describes `@godxjp/ui` 33.4.0.** If the project you are editing has a different
|
|
7
7
|
version in its `package.json`, read the pinned catalog for THAT version instead
|
|
8
8
|
(`…/v<their-version>/agent/…`). A catalog newer than the installed package describes props that do
|
|
9
9
|
not exist yet; older, and it hides props that do. Neither failure announces itself.
|
|
@@ -61,10 +61,10 @@ screen anatomy, the visual target the rules below do not give you. Then fetch in
|
|
|
61
61
|
its `importPath`, and its examples. Fetch only the handful you picked in step 1.
|
|
62
62
|
3. `rules.json` — 44 cardinal rules. The ones about raw HTML and hardcoded colour are not
|
|
63
63
|
style advice.
|
|
64
|
-
4. `tokens.json` —
|
|
64
|
+
4. `tokens.json` — 2232 design tokens, each tagged with its `tier`. **If you were handed a
|
|
65
65
|
brand, read the 216 `foundation` entries first** — `--primary`, `--background`,
|
|
66
66
|
`--radius`, `--font-size-base` are the handful everything else derives from. The
|
|
67
|
-
|
|
67
|
+
1903 `component` entries are per-part knobs; reach for one only when a role is
|
|
68
68
|
right everywhere except one component.
|
|
69
69
|
5. `anti-ai-tells.json` — 35 shapes that make generated UI look generated, each with the
|
|
70
70
|
fix. Read before you reach for a gradient hero or a wall of coloured chips.
|
|
@@ -150,7 +150,7 @@ has stopped following the brand.
|
|
|
150
150
|
|---|---|---|---|
|
|
151
151
|
| `foundation` | 216 | the seeds — `--primary`, `--background`, `--foreground`, `--radius`, `--font-size-base`, `--shadow-color`. Everything below is derived from these | **yes — this is the main road.** The default `--primary` is a neutral placeholder: set it (`:root { --primary: <H> <S>% <L>%; }`, HSL components, no `hsl()` wrapper) or import a theme preset such as `@godxjp/ui/themes/godx.css` |
|
|
152
152
|
| `semantic` | 113 | named roles that follow the seeds — `--ring`, `--text-link`, `--primary-hover`, `--overlay-background` | only when the seed is right and ONE role must differ. That role then stops following a later brand change |
|
|
153
|
-
| `component` |
|
|
153
|
+
| `component` | 1903 | per-part knobs, `--{component}-{part}-{property}` | rarely. Most are declared `initial` with the real default at the call site — deliberate, so a scoped override re-resolves instead of freezing at `:root` |
|
|
154
154
|
|
|
155
155
|
A token whose `value` is `initial` is not empty and not broken: `initial` is the guaranteed-invalid
|
|
156
156
|
value, so the real default is computed where the element paints it. Set it and yours wins.
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"type": "BreakpointProp"
|
|
21
21
|
},
|
|
22
22
|
{
|
|
23
|
-
"description": "Overlaid count; does not change the bar cell width. Rendered under asChild too (gh#1171): the pill goes inside the child element after its children, so `<TopbarItem asChild badge={3}><a href=\"/notifications\">…</a></TopbarItem>` keeps its count.",
|
|
23
|
+
"description": "Overlaid count; does not change the bar cell width. Rendered under asChild too (gh#1171): the pill goes inside the child element after its children, so `<TopbarItem asChild badge={3}><a href=\"/notifications\">…</a></TopbarItem>` keeps its count. Nothing renders for `undefined`, `\"\"` or the number `0` (gh#1341) — pass the count as it is, never `count === 0 ? undefined : count`; pass the string `\"0\"` to show a zero on purpose.",
|
|
24
24
|
"name": "badge",
|
|
25
25
|
"type": "ReactNode"
|
|
26
26
|
},
|
package/agent/components.json
CHANGED
|
@@ -1791,7 +1791,7 @@
|
|
|
1791
1791
|
"type": "BreakpointProp"
|
|
1792
1792
|
},
|
|
1793
1793
|
{
|
|
1794
|
-
"description": "Overlaid count; does not change the bar cell width. Rendered under asChild too (gh#1171): the pill goes inside the child element after its children, so `<TopbarItem asChild badge={3}><a href=\"/notifications\">…</a></TopbarItem>` keeps its count.",
|
|
1794
|
+
"description": "Overlaid count; does not change the bar cell width. Rendered under asChild too (gh#1171): the pill goes inside the child element after its children, so `<TopbarItem asChild badge={3}><a href=\"/notifications\">…</a></TopbarItem>` keeps its count. Nothing renders for `undefined`, `\"\"` or the number `0` (gh#1341) — pass the count as it is, never `count === 0 ? undefined : count`; pass the string `\"0\"` to show a zero on purpose.",
|
|
1795
1795
|
"name": "badge",
|
|
1796
1796
|
"type": "ReactNode"
|
|
1797
1797
|
},
|
package/agent/index.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"components": 166,
|
|
5
5
|
"patterns": 28,
|
|
6
6
|
"rules": 44,
|
|
7
|
-
"tokens":
|
|
7
|
+
"tokens": 2232,
|
|
8
8
|
"vocabulary": 14
|
|
9
9
|
},
|
|
10
10
|
"design": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/DESIGN.md",
|
|
@@ -54,19 +54,19 @@
|
|
|
54
54
|
"note": "Pin to the tag that matches the @godxjp/ui version you installed. A catalog newer than your package describes props you do not have; older, and it hides props you do.",
|
|
55
55
|
"read": {
|
|
56
56
|
"live": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/index.json",
|
|
57
|
-
"pinned": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/v33.
|
|
57
|
+
"pinned": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/v33.4.0/agent/index.json"
|
|
58
58
|
},
|
|
59
59
|
"source": "mcp/src/data — the same data @godxjp/ui-mcp serves — plus the foundation and semantic token tiers, read from src/tokens/*.css",
|
|
60
60
|
"start": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/START-HERE.md",
|
|
61
61
|
"tokenTiers": {
|
|
62
62
|
"component": "per-part knobs, --{component}-{part}-{property}; usually leave these alone",
|
|
63
63
|
"counts": {
|
|
64
|
-
"component":
|
|
64
|
+
"component": 1903,
|
|
65
65
|
"foundation": 216,
|
|
66
66
|
"semantic": 113
|
|
67
67
|
},
|
|
68
68
|
"foundation": "the seeds a consumer is invited to set — --primary, --background, --radius",
|
|
69
69
|
"semantic": "named roles that follow the seeds — --ring, --text-link, --overlay-background"
|
|
70
70
|
},
|
|
71
|
-
"version": "33.
|
|
71
|
+
"version": "33.4.0"
|
|
72
72
|
}
|
package/agent/llms.txt
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# @godxjp/ui
|
|
2
2
|
|
|
3
|
-
> A Japanese-enterprise React design system: 166 components,
|
|
4
|
-
> 44 cardinal rules. This file is the entry point for AI agents. Catalog version 33.
|
|
3
|
+
> A Japanese-enterprise React design system: 166 components, 2232 design tokens,
|
|
4
|
+
> 44 cardinal rules. This file is the entry point for AI agents. Catalog version 33.4.0.
|
|
5
5
|
|
|
6
6
|
If your client can run a process, do not read these files — run the MCP server instead
|
|
7
|
-
(`npx @godxjp/ui-mcp@33.
|
|
7
|
+
(`npx @godxjp/ui-mcp@33.4.0`). It is searchable and version-locked. These files exist for agents
|
|
8
8
|
that can only fetch URLs.
|
|
9
9
|
|
|
10
10
|
## Start
|
|
@@ -19,7 +19,7 @@ that can only fetch URLs.
|
|
|
19
19
|
- [components-index.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/components-index.json): 50 KB — all 166 components as name, group, tagline, plus `absorbed`: the names that do NOT exist and map to it (`Combobox` → `Select`).
|
|
20
20
|
- [components/<Name>.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/components/Select.json): one file per component (1 KB–37 KB, median 6 KB). Read the index, then fetch only the ones you chose — this is the selective route, and the reason you do not need the blob.
|
|
21
21
|
- [components.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/components.json): 1.3 MB — every entry in one file. Most URL fetchers truncate a response this size without saying so; prefer the per-component files.
|
|
22
|
-
- [tokens.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/tokens.json): every design token with its value, the reason it exists, and its `tier` — 216 `foundation` seeds (`--primary`, `--background`, `--radius`: set these when you are handed a brand), 113 `semantic` roles,
|
|
22
|
+
- [tokens.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/tokens.json): every design token with its value, the reason it exists, and its `tier` — 216 `foundation` seeds (`--primary`, `--background`, `--radius`: set these when you are handed a brand), 113 `semantic` roles, 1903 `component` knobs.
|
|
23
23
|
- [vocabulary.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/vocabulary.json): the controlled prop vocabulary — which prop name means what, across every component.
|
|
24
24
|
- [rules.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/rules.json): 44 cardinal rules.
|
|
25
25
|
- [anti-ai-tells.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/anti-ai-tells.json): 35 shapes that make generated UI look generated, each with its fix.
|
|
@@ -27,7 +27,7 @@ that can only fetch URLs.
|
|
|
27
27
|
## Pinning
|
|
28
28
|
|
|
29
29
|
Every URL above tracks `main`. To pin to the release a project actually installed, swap `main` for
|
|
30
|
-
the tag: `.../godx-jp/godxjp-ui/v33.
|
|
30
|
+
the tag: `.../godx-jp/godxjp-ui/v33.4.0/agent/...`. A catalog that does not match the installed
|
|
31
31
|
package describes props that are absent, or hides props that are present, and says nothing either way.
|
|
32
32
|
|
|
33
33
|
Pinned catalogs only exist for releases whose tag actually contains `agent/`. If `…/v<version>/agent/index.json` returns 404, that release predates this catalog: read `…/main/…` instead and compare `index.json` → `version` against the package you have, so you at least know which way it drifted.
|
package/agent/tokens.json
CHANGED
|
@@ -10968,16 +10968,16 @@
|
|
|
10968
10968
|
"value": "var(--secondary)"
|
|
10969
10969
|
},
|
|
10970
10970
|
{
|
|
10971
|
-
"description": "
|
|
10971
|
+
"description": "How far a one-digit pill's top edge (block) and end edge (inline) sit OUTSIDE the glyph's. Fractions of the pill, not a flat length (gh#1341 — Material 3 badges / Apple HIG): half a pill inline centres it on the glyph's corner, three quarters block leaves a quarter of it on the glyph, so at most a quarter of the glyph is covered; a longer count stays inside the cell and grows back across the glyph, still covering at most a quarter of it. They used to be a flat 4px, which left a 16px pill covering 56% of a 16px bell. Declared `initial` with the default resolved at the call site, so a scoped --topbar-item-badge-size still moves them (the freeze rule). The ring is the surface colour drawn round the pill so it reads cut out of the glyph; role-mirror knob, default = hsl(var(--card)) (the bar) at the call site, and the nav rail sets it to its own surface. default = calc(var(--topbar-item-badge-size) * 3 / 4)",
|
|
10972
10972
|
"name": "--topbar-item-badge-offset-block",
|
|
10973
10973
|
"tier": "component",
|
|
10974
|
-
"value": "
|
|
10974
|
+
"value": "initial"
|
|
10975
10975
|
},
|
|
10976
10976
|
{
|
|
10977
|
-
"description": "
|
|
10977
|
+
"description": "default = calc(var(--topbar-item-badge-size) / 2)",
|
|
10978
10978
|
"name": "--topbar-item-badge-offset-inline",
|
|
10979
10979
|
"tier": "component",
|
|
10980
|
-
"value": "
|
|
10980
|
+
"value": "initial"
|
|
10981
10981
|
},
|
|
10982
10982
|
{
|
|
10983
10983
|
"description": "Shell (sidebar / topbar / kbd) component tokens — small-by-design text knobs (rule #45/#46). A service re-tunes chrome text without moving the global scale.",
|
|
@@ -10985,6 +10985,18 @@
|
|
|
10985
10985
|
"tier": "component",
|
|
10986
10986
|
"value": "1rem"
|
|
10987
10987
|
},
|
|
10988
|
+
{
|
|
10989
|
+
"description": "Shell (sidebar / topbar / kbd) component tokens — small-by-design text knobs (rule #45/#46). A service re-tunes chrome text without moving the global scale.",
|
|
10990
|
+
"name": "--topbar-item-badge-ring-width",
|
|
10991
|
+
"tier": "component",
|
|
10992
|
+
"value": "var(--stroke-md)"
|
|
10993
|
+
},
|
|
10994
|
+
{
|
|
10995
|
+
"description": "Shell (sidebar / topbar / kbd) component tokens — small-by-design text knobs (rule #45/#46). A service re-tunes chrome text without moving the global scale.",
|
|
10996
|
+
"name": "--topbar-item-badge-ring-color",
|
|
10997
|
+
"tier": "component",
|
|
10998
|
+
"value": "initial"
|
|
10999
|
+
},
|
|
10988
11000
|
{
|
|
10989
11001
|
"description": "Shell (sidebar / topbar / kbd) component tokens — small-by-design text knobs (rule #45/#46). A service re-tunes chrome text without moving the global scale.",
|
|
10990
11002
|
"name": "--topbar-item-badge-padding-inline",
|
|
@@ -5,6 +5,7 @@ import * as React from "react";
|
|
|
5
5
|
import { chain, mergeRefs } from "@react-aria/utils";
|
|
6
6
|
import { Popover as AriaPopover } from "react-aria-components";
|
|
7
7
|
import { cn } from "../../lib/utils.js";
|
|
8
|
+
import { getActiveElement } from "../../lib/active-element.js";
|
|
8
9
|
import { Slot } from "../../lib/slot.js";
|
|
9
10
|
import { useOverlayCloseFocus } from "../feedback/overlay-close-focus.js";
|
|
10
11
|
function toPlacement(side, align) {
|
|
@@ -278,7 +279,7 @@ function ClickContent({
|
|
|
278
279
|
if (!event.defaultPrevented) {
|
|
279
280
|
const target = firstTabbable(node);
|
|
280
281
|
target?.focus({ preventScroll: true });
|
|
281
|
-
if (!node.contains(
|
|
282
|
+
if (!node.contains(getActiveElement(node))) {
|
|
282
283
|
node.focus({ preventScroll: true });
|
|
283
284
|
}
|
|
284
285
|
}
|
|
@@ -287,11 +288,11 @@ function ClickContent({
|
|
|
287
288
|
React.useEffect(() => {
|
|
288
289
|
if (!open) return;
|
|
289
290
|
const onPointerDown = (event) => {
|
|
290
|
-
const
|
|
291
|
-
|
|
292
|
-
if (contentRef.current
|
|
293
|
-
if (triggerRef.current
|
|
294
|
-
if (anchored && anchorRef.current
|
|
291
|
+
const path = event.composedPath();
|
|
292
|
+
const within = (node) => node !== null && path.includes(node);
|
|
293
|
+
if (within(contentRef.current)) return;
|
|
294
|
+
if (within(triggerRef.current)) return;
|
|
295
|
+
if (anchored && within(anchorRef.current)) return;
|
|
295
296
|
setOpen(false);
|
|
296
297
|
};
|
|
297
298
|
document.addEventListener("pointerdown", onPointerDown);
|
|
@@ -305,6 +306,7 @@ function ClickContent({
|
|
|
305
306
|
isOpen: root.open,
|
|
306
307
|
onOpenChange: onAriaOpenChange,
|
|
307
308
|
isNonModal: root.isNonModal,
|
|
309
|
+
shouldCloseOnInteractOutside: (element) => !triggerRef.current?.contains(element),
|
|
308
310
|
triggerRef: root.anchored ? root.anchorRef : root.triggerRef,
|
|
309
311
|
placement: toPlacement(side, align),
|
|
310
312
|
offset: sideOffset,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import * as React from "react";
|
|
3
|
+
import { getActiveElement } from "../../lib/active-element.js";
|
|
3
4
|
function useOverlayCloseFocus(isOpen, onCloseAutoFocus, contentRef) {
|
|
4
5
|
const nodeToRestore = React.useRef(null);
|
|
5
6
|
const renderedOpen = React.useRef(false);
|
|
@@ -7,7 +8,7 @@ function useOverlayCloseFocus(isOpen, onCloseAutoFocus, contentRef) {
|
|
|
7
8
|
if (isOpen !== renderedOpen.current) {
|
|
8
9
|
renderedOpen.current = isOpen;
|
|
9
10
|
if (isOpen && typeof document !== "undefined") {
|
|
10
|
-
const active =
|
|
11
|
+
const active = getActiveElement();
|
|
11
12
|
nodeToRestore.current = active === document.body ? null : active;
|
|
12
13
|
}
|
|
13
14
|
}
|
|
@@ -17,7 +18,7 @@ function useOverlayCloseFocus(isOpen, onCloseAutoFocus, contentRef) {
|
|
|
17
18
|
onCloseAutoFocus?.(event);
|
|
18
19
|
const node = nodeToRestore.current;
|
|
19
20
|
nodeToRestore.current = null;
|
|
20
|
-
const active =
|
|
21
|
+
const active = getActiveElement();
|
|
21
22
|
const focusMovedAway = contentRef != null && active != null && active !== document.body && active.isConnected && contentRef.current?.contains(active) !== true;
|
|
22
23
|
if (!event.defaultPrevented && !focusMovedAway && node?.isConnected === true) {
|
|
23
24
|
node.focus();
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
TooltipTriggerStateContext
|
|
11
11
|
} from "react-aria-components";
|
|
12
12
|
import { cn } from "../../lib/utils.js";
|
|
13
|
+
import { getActiveElement } from "../../lib/active-element.js";
|
|
13
14
|
import { Slot } from "../../lib/slot.js";
|
|
14
15
|
const DEFAULT_DELAY_DURATION = 200;
|
|
15
16
|
const TooltipAnchorContext = React.createContext(
|
|
@@ -166,7 +167,7 @@ function EllipsisTooltip({
|
|
|
166
167
|
show(DEFAULT_DELAY_DURATION);
|
|
167
168
|
};
|
|
168
169
|
const onFocusIn = () => {
|
|
169
|
-
const active =
|
|
170
|
+
const active = getActiveElement(node);
|
|
170
171
|
if (active && (active === host || node.contains(active)) && isFocusVisible(active)) show(0);
|
|
171
172
|
};
|
|
172
173
|
node.addEventListener("pointerenter", onPointerEnter);
|
|
@@ -16,7 +16,7 @@ const TopbarItem = React.forwardRef(function TopbarItem2({
|
|
|
16
16
|
...props
|
|
17
17
|
}, ref) {
|
|
18
18
|
const Comp = asChild ? Slot : "button";
|
|
19
|
-
const showBadge = badge !== void 0 && badge !== "";
|
|
19
|
+
const showBadge = badge !== void 0 && badge !== "" && badge !== 0;
|
|
20
20
|
const iconNode = icon !== void 0 && icon !== null && icon !== false ? /* @__PURE__ */ jsx(
|
|
21
21
|
"span",
|
|
22
22
|
{
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$comment": "AUTO-GENERATED by scripts/gen-measurement-contract.mjs — do not edit. Read this instead of guessing: docs/MEASUREMENT-CONTRACT.md.",
|
|
3
|
-
"version": "33.
|
|
3
|
+
"version": "33.4.0",
|
|
4
4
|
"targetSize": {
|
|
5
5
|
"standard": "WCAG 2.2 SC 2.5.8 Target Size (Minimum), level AA — 24×24 CSS px",
|
|
6
6
|
"min": 24,
|
package/dist/lab/mega-menu.js
CHANGED
|
@@ -3,6 +3,7 @@ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import { ChevronDown } from "lucide-react";
|
|
5
5
|
import { cn } from "../lib/utils.js";
|
|
6
|
+
import { getActiveElement } from "../lib/active-element.js";
|
|
6
7
|
import { useTranslation } from "../i18n/use-translation.js";
|
|
7
8
|
const OPEN_DELAY_MS = 0;
|
|
8
9
|
const CLOSE_DELAY_MS = 100;
|
|
@@ -216,7 +217,7 @@ const MegaMenu = React.forwardRef(function MegaMenu2({
|
|
|
216
217
|
if (openKey === null) return;
|
|
217
218
|
const onPointerDown = (event) => {
|
|
218
219
|
const node = rootRef.current;
|
|
219
|
-
if (node && event.
|
|
220
|
+
if (node && !event.composedPath().includes(node)) close();
|
|
220
221
|
};
|
|
221
222
|
document.addEventListener("pointerdown", onPointerDown);
|
|
222
223
|
return () => document.removeEventListener("pointerdown", onPointerDown);
|
|
@@ -381,7 +382,7 @@ const MegaMenu = React.forwardRef(function MegaMenu2({
|
|
|
381
382
|
closeTimer.current = setTimeout(() => {
|
|
382
383
|
closeTimer.current = null;
|
|
383
384
|
const node = rootRef.current;
|
|
384
|
-
if (node && node.contains(
|
|
385
|
+
if (node && node.contains(getActiveElement(node))) return;
|
|
385
386
|
commitOpen(null);
|
|
386
387
|
}, closeDelay);
|
|
387
388
|
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The element that REALLY has focus, through shadow roots (gh#1340).
|
|
3
|
+
*
|
|
4
|
+
* `document.activeElement` stops at the shadow HOST: with the app mounted in a shadow root (GoDX
|
|
5
|
+
* Dock, `OverlayPortalProvider`), every `node.contains(document.activeElement)` in this library
|
|
6
|
+
* read "focus is not in here" while focus sat on an input inside the panel.
|
|
7
|
+
*
|
|
8
|
+
* With `scope`, the walk starts at that node's own root (`getRootNode().activeElement`), which also
|
|
9
|
+
* sees into a CLOSED shadow root the node lives in; it returns `null` when focus is not inside that
|
|
10
|
+
* root at all. Without it, the walk starts at `document` and descends through open shadow roots.
|
|
11
|
+
*/
|
|
12
|
+
export declare function getActiveElement(scope?: Node | null): Element | null;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
function getActiveElement(scope) {
|
|
2
|
+
if (typeof document === "undefined") return null;
|
|
3
|
+
const root = scope?.getRootNode();
|
|
4
|
+
let active = root instanceof Document || root instanceof ShadowRoot ? root.activeElement : document.activeElement;
|
|
5
|
+
while (active?.shadowRoot?.activeElement) active = active.shadowRoot.activeElement;
|
|
6
|
+
return active;
|
|
7
|
+
}
|
|
8
|
+
export {
|
|
9
|
+
getActiveElement
|
|
10
|
+
};
|
package/dist/lib/shortcut.js
CHANGED
|
@@ -53,9 +53,10 @@ function isEditableTarget(target) {
|
|
|
53
53
|
}
|
|
54
54
|
function shouldHandleShortcut(spec, event, owner) {
|
|
55
55
|
if (isImeComposing(event) || !shortcutMatches(spec, event)) return false;
|
|
56
|
-
|
|
56
|
+
const target = event.composedPath()[0] ?? event.target;
|
|
57
|
+
if (inForeignLayer(target, owner)) return false;
|
|
57
58
|
const commandModifier = spec.mod || spec.meta || spec.ctrl || spec.alt;
|
|
58
|
-
return commandModifier || !isEditableTarget(
|
|
59
|
+
return commandModifier || !isEditableTarget(target);
|
|
59
60
|
}
|
|
60
61
|
const SHORTCUT_LAYER = '[role="dialog"], [role="alertdialog"], [aria-modal="true"], [role="menu"], [role="listbox"]';
|
|
61
62
|
function inForeignLayer(target, owner) {
|
|
@@ -1656,6 +1656,9 @@ export type TopbarItemProp = Omit<React.ButtonHTMLAttributes<HTMLButtonElement>,
|
|
|
1656
1656
|
* Drawn under `asChild` too (gh#1171): the pill goes inside the child element, after its own
|
|
1657
1657
|
* children, so a bell that is a link (`<TopbarItem asChild badge={3}><a …/></TopbarItem>`) keeps
|
|
1658
1658
|
* its count.
|
|
1659
|
+
*
|
|
1660
|
+
* Nothing renders for `undefined`, `""` or the NUMBER `0` (gh#1341), so pass the count as it is.
|
|
1661
|
+
* To show a zero deliberately, pass the string `"0"`.
|
|
1659
1662
|
*/
|
|
1660
1663
|
badge?: ReactNode;
|
|
1661
1664
|
/**
|
package/dist/styles/layers.json
CHANGED
|
@@ -203,6 +203,14 @@
|
|
|
203
203
|
--control-height-sm: var(--app-shell-nav-rail-item-size);
|
|
204
204
|
}
|
|
205
205
|
|
|
206
|
+
.app-nav-rail {
|
|
207
|
+
--topbar-item-badge-ring-color: var(--app-shell-nav-rail-background, hsl(var(--muted)));
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
.app-mobile-nav-rail {
|
|
211
|
+
--topbar-item-badge-ring-color: var(--sheet-surface-background, hsl(var(--background)));
|
|
212
|
+
}
|
|
213
|
+
|
|
206
214
|
.app-nav-rail[data-edge="end"] {
|
|
207
215
|
border-inline-start: var(--stroke-hairline) solid hsl(var(--border));
|
|
208
216
|
border-inline-end: 0;
|
|
@@ -836,6 +844,8 @@
|
|
|
836
844
|
flex-direction: column;
|
|
837
845
|
border-inline-end: var(--stroke-hairline) solid hsl(var(--border));
|
|
838
846
|
inline-size: var(--app-shell-nav-rail-width);
|
|
847
|
+
|
|
848
|
+
padding-block: var(--app-shell-nav-rail-inset);
|
|
839
849
|
overflow-y: auto;
|
|
840
850
|
overscroll-behavior: contain;
|
|
841
851
|
}
|
|
@@ -2315,10 +2325,13 @@
|
|
|
2315
2325
|
position: absolute;
|
|
2316
2326
|
|
|
2317
2327
|
inset-block-start: calc(
|
|
2318
|
-
50% - var(--topbar-icon-size) / 2 -
|
|
2328
|
+
50% - var(--topbar-icon-size) / 2 -
|
|
2329
|
+
var(--topbar-item-badge-offset-block, var(--topbar-item-badge-size) * 3 / 4)
|
|
2319
2330
|
);
|
|
2320
|
-
inset-inline-end:
|
|
2321
|
-
|
|
2331
|
+
inset-inline-end: max(
|
|
2332
|
+
var(--topbar-item-badge-ring-width),
|
|
2333
|
+
50% - var(--topbar-icon-size) / 2 -
|
|
2334
|
+
var(--topbar-item-badge-offset-inline, var(--topbar-item-badge-size) / 2)
|
|
2322
2335
|
);
|
|
2323
2336
|
display: inline-flex;
|
|
2324
2337
|
min-inline-size: var(--topbar-item-badge-size);
|
|
@@ -2333,6 +2346,9 @@
|
|
|
2333
2346
|
font-weight: var(--font-weight-medium);
|
|
2334
2347
|
line-height: 1;
|
|
2335
2348
|
|
|
2349
|
+
box-shadow: 0 0 0 var(--topbar-item-badge-ring-width)
|
|
2350
|
+
var(--topbar-item-badge-ring-color, hsl(var(--card)));
|
|
2351
|
+
|
|
2336
2352
|
pointer-events: none;
|
|
2337
2353
|
}
|
|
2338
2354
|
|