@godxjp/ui 33.11.2 → 33.12.1

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 CHANGED
@@ -18,7 +18,7 @@ npm i @godxjp/ui
18
18
 
19
19
  <!-- agent-catalog-counts:start -->
20
20
 
21
- The catalog — 169 components, 2238 tokens, 44 cardinal rules — is published in two forms from one
21
+ The catalog — 169 components, 2241 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
 
@@ -53,10 +53,10 @@ static files straight from this public repo — no hosting, no deploy step:
53
53
  | [`agent/llms.txt`](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/llms.txt) | 3 KB | the [llms.txt](https://llmstxt.org/) entry point |
54
54
  | [`agent/DESIGN.md`](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/DESIGN.md) | 18 KB | the visual spec: tokens, colour scarcity, admin screen anatomy, do/don't |
55
55
  | [`agent/index.json`](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/index.json) | 3 KB | manifest: version, counts, every file's URL |
56
- | [`agent/components-index.json`](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/components-index.json) | 52 KB | all 169 as name + group + tagline — **fetch this first** |
56
+ | [`agent/components-index.json`](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/components-index.json) | 53 KB | all 169 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 169 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) | 858 KB | all 2238 design tokens, their values and why they exist |
59
+ | [`agent/tokens.json`](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/tokens.json) | 858 KB | all 2241 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 -->
@@ -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.11.2.** If the project you are editing has a different
6
+ **This catalog describes `@godxjp/ui` 33.12.1.** 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.
@@ -52,7 +52,7 @@ screen anatomy, the visual target the rules below do not give you. Then fetch in
52
52
  task is a task** — "build a settings page", "confirm a destructive delete", "a list page with
53
53
  filters" — start HERE, not at the components. Then fetch `patterns/<name>.json` for complete,
54
54
  copy-paste-ready code. A component index answers "does X exist"; it cannot answer "build Y".
55
- 1. `components-index.json` — 52 KB, all 169 components as name + group +
55
+ 1. `components-index.json` — 53 KB, all 169 components as name + group +
56
56
  tagline. Read this when you already know the SHAPE you need. Each entry may carry `absorbed`:
57
57
  names that **do not exist** and map to it — `Combobox`, `Autocomplete`, `CountrySelect` and
58
58
  `SearchSelect` are all `Select`. If you are about to hand-roll something, search this field
@@ -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` — 2238 design tokens, each tagged with its `tier`. **If you were handed a
65
- brand, read the 216 `foundation` entries first** — `--primary`, `--background`,
64
+ 4. `tokens.json` — 2241 design tokens, each tagged with its `tier`. **If you were handed a
65
+ brand, read the 218 `foundation` entries first** — `--primary`, `--background`,
66
66
  `--radius`, `--font-size-base` are the handful everything else derives from. The
67
- 1909 `component` entries are per-part knobs; reach for one only when a role is
67
+ 1910 `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.
@@ -148,9 +148,9 @@ has stopped following the brand.
148
148
 
149
149
  | `tier` | count | what it is | set it? |
150
150
  |---|---|---|---|
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` |
151
+ | `foundation` | 218 | 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` | 1909 | 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` |
153
+ | `component` | 1910 | 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.
@@ -105,7 +105,7 @@
105
105
  45
106
106
  ],
107
107
  "storyPath": "navigation/anchor.tsx",
108
- "tagline": "Ant Design `Anchor` (6.6.5): the in-page section navigation, and the only thing in this library that COMPUTES which section is current. `NavList activeId` takes that answer as a prop. Resolves by antd's rule — the last section whose edge has crossed a single decision line — which is a pure function of scroll position and therefore cannot flicker, suppresses itself during the programmatic scroll a click starts, reads the landing hash before any scroll event, and jumps rather than tweens under `prefers-reduced-motion`.",
108
+ "tagline": "Ant Design `Anchor` (6.6.5): the in-page section navigation, and the only thing in this library that COMPUTES which section is current. `NavList activeId` takes that answer as a prop. Resolves by antd's rule — the last section whose edge has crossed a single decision line — which is a pure function of scroll position and therefore cannot flicker, suppresses itself during the programmatic scroll a click starts, reads the landing hash before any scroll event, and jumps rather than tweens under `prefers-reduced-motion`. Keyboard focus uses the shared inset ring in clipping surfaces; scrollports reveal partially clipped keyboard targets without changing their layout.",
109
109
  "tier": "lab",
110
110
  "usage": [
111
111
  "DO give every target a real `id` and point `href` at it as `#id`. The links are real anchors: middle-clickable, deep-linkable, and correct before JavaScript boots.",
@@ -135,7 +135,7 @@
135
135
  23
136
136
  ],
137
137
  "storyPath": "layout/app-shell.tsx",
138
- "tagline": "Root application shell — composes sidebar, topbar rail, main content area, and optional footer.",
138
+ "tagline": "Root application shell — composes sidebar, topbar rail, main content area, and optional footer. Keyboard focus in clipped chrome stays inset; the main scroll region shows an inset brand ring by default (--region-focus-ring-width).",
139
139
  "usage": [
140
140
  "DO put a sidebar collapse toggle IN the rail, not floating in the bar (ADR C10): the shell does not render one, so pass it through `Sidebar action={(collapsed) => <Button variant=\"ghost\" size=\"icon\" aria-label={t(\"toggleSidebar\")} aria-expanded={!collapsed} …/>}` (or the `footer` render-prop), which keeps it attached to the rail in both states. A toggle in `Topbar start` under `topbarSpan=\"full\"` lands beside the brand, detached from the rail it controls. The shell has no edge-mounted toggle (a control straddling the rail border); `Sidebar action` keeps it inside the rail head, which is the supported placement. Below the navigation boundary the drawer has its own trigger; render no collapse toggle there.",
141
141
  "useAppShellNavigationMode() returns drawer/docked at the same 56.25rem boundary as AppShell. The root also publishes data-navigation. Use this instead of a consumer media query.",
@@ -236,7 +236,7 @@
236
236
  37
237
237
  ],
238
238
  "storyPath": "data-display/data-table/index.tsx",
239
- "tagline": "The one TanStack-powered compound admin list — sticky header, sorting, global search, column visibility ('set view'), bulk selection, BOTH cursor and numbered pagination, density, and built-in empty/loading states. Keep the SIMPLE `data` + lean `columns` (ColumnDef) API for the common case; opt into the full grid chrome via the compound parts. Internally driven by @tanstack/react-table (a real dependency). Lives on @godxjp/ui/data-display only (it is NOT on the runtime-neutral root/admin barrel because it pulls TanStack).",
239
+ "tagline": "The one TanStack-powered compound admin list — sticky header, sorting, global search, column visibility ('set view'), bulk selection, BOTH cursor and numbered pagination, density, and built-in empty/loading states. Keep the SIMPLE `data` + lean `columns` (ColumnDef) API for the common case; opt into the full grid chrome via the compound parts. Internally driven by @tanstack/react-table (a real dependency). Lives on @godxjp/ui/data-display only (it is NOT on the runtime-neutral root/admin barrel because it pulls TanStack). Keyboard focus uses the shared inset ring in clipping surfaces; scrollports reveal partially clipped keyboard targets without changing their layout.",
240
240
  "usage": [
241
241
  "DO use `striped` on dense list tables (many columns, a row the eye must follow across the width). To stripe EVERY Table and DataTable in a service, set it ONCE in the theme — `:root { --table-row-striped-alpha: 100%; }` — instead of passing `striped` at each call site; `striped={false}` then opts one table out. Retint with `--table-row-striped-background`, never with a `rowClassName` utility or `:nth-child` page CSS (those count DOM rows, so an expanded detail row shifts every stripe after it).",
242
242
  "DO pass loading={isFetching} during data fetches — it renders a loading row in the table body and suppresses the empty state. Never show a spinner outside DataTable while the table is visible.",
@@ -109,7 +109,7 @@
109
109
  "PaginationNext",
110
110
  "PaginationPrevious"
111
111
  ],
112
- "tagline": "Offset/page-based pagination bar. Sits below a table card.",
112
+ "tagline": "Offset/page-based pagination bar. Sits below a table card. Keyboard focus uses the shared inset ring in clipping surfaces; scrollports reveal partially clipped keyboard targets without changing their layout.",
113
113
  "usage": [
114
114
  "DO always control Pagination externally: store `value` (page) and `pageSize` in React state (or URL params), and update both in the `onValueChange(page, pageSize)` callback. Pagination is fully controlled — it has no internal state and will not move unless `value` changes.",
115
115
  "DO let Pagination hide itself for zero items and single pages (`hideOnSinglePage`, default true) — it is navigation between multiple result pages. Render it inside a table footer only in the DATA state: never during loading, empty, error, or an unmet prerequisite. Pass `hideOnSinglePage={false}` only when you still want the bar on one page to keep `showTotal` visible.",
@@ -51,7 +51,7 @@
51
51
  "ResizableHandle",
52
52
  "ResizablePanelGroup"
53
53
  ],
54
- "tagline": "Resizable panel group/child/handle primitives from react-resizable-panels.",
54
+ "tagline": "Resizable panel group/child/handle primitives from react-resizable-panels. --resizable-handle-size sets separator thickness; ARIA orientation controls its axis. Handles use the shared inset focus ring, including empty handles.",
55
55
  "usage": [
56
56
  "DO put the layout on `<ResizablePanelGroup orientation=\"horizontal|vertical\">`, the resizable regions in `<ResizablePanel>`, and a `<ResizableHandle>` BETWEEN every adjacent pair — a group of N panels needs N-1 handles or there is nothing to drag.",
57
57
  "DO express `defaultSize`/`minSize`/`maxSize` as PERCENTAGE STRINGS like `defaultSize=\"35%\"` (react-resizable-panels v4: a bare number is PIXELS, so `defaultSize={35}` renders a 35px sliver, NOT 35% — pass the string). Don't fight sizing with a fixed `w-[280px]` className on the panel.",
@@ -76,7 +76,7 @@
76
76
  "subParts": [
77
77
  "ScrollBar"
78
78
  ],
79
- "tagline": "A native scrolling box (no scroll library since v23): one `overflow: auto` element whose scrollbar is styled from --scroll-area-* tokens. Always set an explicit height/max-height, or nothing overflows and no scrollbar appears. Owns the scrolling element, so it also owns reaching it (viewportRef) and bottom anchoring for a live stream (anchor).",
79
+ "tagline": "Keyboard focus on the scroll viewport and its descendants uses the shared inset brand ring (--focus-ring-inset-offset), without padding changes. A native scrolling box (no scroll library since v23): one `overflow: auto` element whose scrollbar is styled from --scroll-area-* tokens. Always set an explicit height/max-height, or nothing overflows and no scrollbar appears. Owns the scrolling element, so it also owns reaching it (viewportRef) and bottom anchoring for a live stream (anchor).",
80
80
  "usage": [
81
81
  "DO set an explicit height or max-height on ScrollArea via className (e.g. `className=\"h-64\"` or `className=\"max-h-[min(300px,50vh)]\"`). Without a height constraint the viewport grows to fit content and the scrollbar is never rendered.",
82
82
  "DO wrap content in a single child element inside ScrollArea — the Viewport observes its single child's size to decide whether overflow exists.",
@@ -100,7 +100,7 @@
100
100
  "TableHeader",
101
101
  "TableRow"
102
102
  ],
103
- "tagline": "Primitive table shell (Table/TableHeader/TableBody/TableRow/TableHead/TableCell). Prefer DataTable for admin lists; use these for custom one-off tables.",
103
+ "tagline": "Primitive table shell (Table/TableHeader/TableBody/TableRow/TableHead/TableCell). Prefer DataTable for admin lists; use these for custom one-off tables. Keyboard focus uses the shared inset ring in clipping surfaces; scrollports reveal partially clipped keyboard targets without changing their layout.",
104
104
  "usage": [
105
105
  "DO compose all six sub-parts in order: wrap with `<Table>`, then `<TableHeader>` containing `<TableRow><TableHead>…</TableRow>`, then `<TableBody>` containing one or more `<TableRow><TableCell>…` rows. Skipping any layer (e.g. bare `<th>` inside `<Table>`) bypasses the design tokens and hover/border styles.",
106
106
  "DO use `TableHead` (not `TableCell`) for header cells — it renders `<th>` with `data-slot=\"table-head\"` and the `--table-row-height` CSS variable for consistent header sizing across the design system. `TableCell` renders `<td>` with `data-slot=\"table-cell\"` and is for body rows only.",
@@ -72,7 +72,7 @@
72
72
  {
73
73
  "group": "layout",
74
74
  "name": "AppShell",
75
- "tagline": "Root application shell — composes sidebar, topbar rail, main content area, and optional footer."
75
+ "tagline": "Root application shell — composes sidebar, topbar rail, main content area, and optional footer. Keyboard focus in clipped chrome stays inset; the main scroll region shows an inset brand ring by default (--region-focus-ring-width)."
76
76
  },
77
77
  {
78
78
  "absorbed": [
@@ -210,7 +210,7 @@
210
210
  ],
211
211
  "group": "data-display",
212
212
  "name": "DataTable",
213
- "tagline": "The one TanStack-powered compound admin list — sticky header, sorting, global search, column visibility ('set view'), bulk selection, BOTH cursor and numbered pagination, density, and built-in empty/loading states. Keep the SIMPLE `data` + lean `columns` (ColumnDef) API for the common case; opt into the full grid chrome via the compound parts. Internally driven by @tanstack/react-table (a real dependency). Lives on @godxjp/ui/data-display only (it is NOT on the runtime-neutral root/admin barrel because it pulls TanStack)."
213
+ "tagline": "The one TanStack-powered compound admin list — sticky header, sorting, global search, column visibility ('set view'), bulk selection, BOTH cursor and numbered pagination, density, and built-in empty/loading states. Keep the SIMPLE `data` + lean `columns` (ColumnDef) API for the common case; opt into the full grid chrome via the compound parts. Internally driven by @tanstack/react-table (a real dependency). Lives on @godxjp/ui/data-display only (it is NOT on the runtime-neutral root/admin barrel because it pulls TanStack). Keyboard focus uses the shared inset ring in clipping surfaces; scrollports reveal partially clipped keyboard targets without changing their layout."
214
214
  },
215
215
  {
216
216
  "group": "data-display",
@@ -342,7 +342,7 @@
342
342
  {
343
343
  "group": "data-display",
344
344
  "name": "Table",
345
- "tagline": "Primitive table shell (Table/TableHeader/TableBody/TableRow/TableHead/TableCell). Prefer DataTable for admin lists; use these for custom one-off tables."
345
+ "tagline": "Primitive table shell (Table/TableHeader/TableBody/TableRow/TableHead/TableCell). Prefer DataTable for admin lists; use these for custom one-off tables. Keyboard focus uses the shared inset ring in clipping surfaces; scrollports reveal partially clipped keyboard targets without changing their layout."
346
346
  },
347
347
  {
348
348
  "group": "data-display",
@@ -493,7 +493,7 @@
493
493
  {
494
494
  "group": "navigation",
495
495
  "name": "Pagination",
496
- "tagline": "Offset/page-based pagination bar. Sits below a table card."
496
+ "tagline": "Offset/page-based pagination bar. Sits below a table card. Keyboard focus uses the shared inset ring in clipping surfaces; scrollports reveal partially clipped keyboard targets without changing their layout."
497
497
  },
498
498
  {
499
499
  "group": "navigation",
@@ -612,7 +612,7 @@
612
612
  {
613
613
  "group": "data-display",
614
614
  "name": "ScrollArea",
615
- "tagline": "A native scrolling box (no scroll library since v23): one `overflow: auto` element whose scrollbar is styled from --scroll-area-* tokens. Always set an explicit height/max-height, or nothing overflows and no scrollbar appears. Owns the scrolling element, so it also owns reaching it (viewportRef) and bottom anchoring for a live stream (anchor)."
615
+ "tagline": "Keyboard focus on the scroll viewport and its descendants uses the shared inset brand ring (--focus-ring-inset-offset), without padding changes. A native scrolling box (no scroll library since v23): one `overflow: auto` element whose scrollbar is styled from --scroll-area-* tokens. Always set an explicit height/max-height, or nothing overflows and no scrollbar appears. Owns the scrolling element, so it also owns reaching it (viewportRef) and bottom anchoring for a live stream (anchor)."
616
616
  },
617
617
  {
618
618
  "group": "data-display",
@@ -703,7 +703,7 @@
703
703
  {
704
704
  "group": "layout",
705
705
  "name": "ResizablePanel",
706
- "tagline": "Resizable panel group/child/handle primitives from react-resizable-panels."
706
+ "tagline": "Resizable panel group/child/handle primitives from react-resizable-panels. --resizable-handle-size sets separator thickness; ARIA orientation controls its axis. Handles use the shared inset focus ring, including empty handles."
707
707
  },
708
708
  {
709
709
  "group": "data-display",
@@ -923,7 +923,7 @@
923
923
  {
924
924
  "group": "navigation",
925
925
  "name": "Anchor",
926
- "tagline": "Ant Design `Anchor` (6.6.5): the in-page section navigation, and the only thing in this library that COMPUTES which section is current. `NavList activeId` takes that answer as a prop. Resolves by antd's rule — the last section whose edge has crossed a single decision line — which is a pure function of scroll position and therefore cannot flicker, suppresses itself during the programmatic scroll a click starts, reads the landing hash before any scroll event, and jumps rather than tweens under `prefers-reduced-motion`.",
926
+ "tagline": "Ant Design `Anchor` (6.6.5): the in-page section navigation, and the only thing in this library that COMPUTES which section is current. `NavList activeId` takes that answer as a prop. Resolves by antd's rule — the last section whose edge has crossed a single decision line — which is a pure function of scroll position and therefore cannot flicker, suppresses itself during the programmatic scroll a click starts, reads the landing hash before any scroll event, and jumps rather than tweens under `prefers-reduced-motion`. Keyboard focus uses the shared inset ring in clipping surfaces; scrollports reveal partially clipped keyboard targets without changing their layout.",
927
927
  "tier": "lab"
928
928
  }
929
929
  ]
@@ -1323,7 +1323,7 @@
1323
1323
  23
1324
1324
  ],
1325
1325
  "storyPath": "layout/app-shell.tsx",
1326
- "tagline": "Root application shell — composes sidebar, topbar rail, main content area, and optional footer.",
1326
+ "tagline": "Root application shell — composes sidebar, topbar rail, main content area, and optional footer. Keyboard focus in clipped chrome stays inset; the main scroll region shows an inset brand ring by default (--region-focus-ring-width).",
1327
1327
  "usage": [
1328
1328
  "DO put a sidebar collapse toggle IN the rail, not floating in the bar (ADR C10): the shell does not render one, so pass it through `Sidebar action={(collapsed) => <Button variant=\"ghost\" size=\"icon\" aria-label={t(\"toggleSidebar\")} aria-expanded={!collapsed} …/>}` (or the `footer` render-prop), which keeps it attached to the rail in both states. A toggle in `Topbar start` under `topbarSpan=\"full\"` lands beside the brand, detached from the rail it controls. The shell has no edge-mounted toggle (a control straddling the rail border); `Sidebar action` keeps it inside the rail head, which is the supported placement. Below the navigation boundary the drawer has its own trigger; render no collapse toggle there.",
1329
1329
  "useAppShellNavigationMode() returns drawer/docked at the same 56.25rem boundary as AppShell. The root also publishes data-navigation. Use this instead of a consumer media query.",
@@ -3625,7 +3625,7 @@
3625
3625
  37
3626
3626
  ],
3627
3627
  "storyPath": "data-display/data-table/index.tsx",
3628
- "tagline": "The one TanStack-powered compound admin list — sticky header, sorting, global search, column visibility ('set view'), bulk selection, BOTH cursor and numbered pagination, density, and built-in empty/loading states. Keep the SIMPLE `data` + lean `columns` (ColumnDef) API for the common case; opt into the full grid chrome via the compound parts. Internally driven by @tanstack/react-table (a real dependency). Lives on @godxjp/ui/data-display only (it is NOT on the runtime-neutral root/admin barrel because it pulls TanStack).",
3628
+ "tagline": "The one TanStack-powered compound admin list — sticky header, sorting, global search, column visibility ('set view'), bulk selection, BOTH cursor and numbered pagination, density, and built-in empty/loading states. Keep the SIMPLE `data` + lean `columns` (ColumnDef) API for the common case; opt into the full grid chrome via the compound parts. Internally driven by @tanstack/react-table (a real dependency). Lives on @godxjp/ui/data-display only (it is NOT on the runtime-neutral root/admin barrel because it pulls TanStack). Keyboard focus uses the shared inset ring in clipping surfaces; scrollports reveal partially clipped keyboard targets without changing their layout.",
3629
3629
  "usage": [
3630
3630
  "DO use `striped` on dense list tables (many columns, a row the eye must follow across the width). To stripe EVERY Table and DataTable in a service, set it ONCE in the theme — `:root { --table-row-striped-alpha: 100%; }` — instead of passing `striped` at each call site; `striped={false}` then opts one table out. Retint with `--table-row-striped-background`, never with a `rowClassName` utility or `:nth-child` page CSS (those count DOM rows, so an expanded detail row shifts every stripe after it).",
3631
3631
  "DO pass loading={isFetching} during data fetches — it renders a loading row in the table body and suppresses the empty state. Never show a spinner outside DataTable while the table is visible.",
@@ -5646,7 +5646,7 @@
5646
5646
  "TableHeader",
5647
5647
  "TableRow"
5648
5648
  ],
5649
- "tagline": "Primitive table shell (Table/TableHeader/TableBody/TableRow/TableHead/TableCell). Prefer DataTable for admin lists; use these for custom one-off tables.",
5649
+ "tagline": "Primitive table shell (Table/TableHeader/TableBody/TableRow/TableHead/TableCell). Prefer DataTable for admin lists; use these for custom one-off tables. Keyboard focus uses the shared inset ring in clipping surfaces; scrollports reveal partially clipped keyboard targets without changing their layout.",
5650
5650
  "usage": [
5651
5651
  "DO compose all six sub-parts in order: wrap with `<Table>`, then `<TableHeader>` containing `<TableRow><TableHead>…</TableRow>`, then `<TableBody>` containing one or more `<TableRow><TableCell>…` rows. Skipping any layer (e.g. bare `<th>` inside `<Table>`) bypasses the design tokens and hover/border styles.",
5652
5652
  "DO use `TableHead` (not `TableCell`) for header cells — it renders `<th>` with `data-slot=\"table-head\"` and the `--table-row-height` CSS variable for consistent header sizing across the design system. `TableCell` renders `<td>` with `data-slot=\"table-cell\"` and is for body rows only.",
@@ -8747,7 +8747,7 @@
8747
8747
  "PaginationNext",
8748
8748
  "PaginationPrevious"
8749
8749
  ],
8750
- "tagline": "Offset/page-based pagination bar. Sits below a table card.",
8750
+ "tagline": "Offset/page-based pagination bar. Sits below a table card. Keyboard focus uses the shared inset ring in clipping surfaces; scrollports reveal partially clipped keyboard targets without changing their layout.",
8751
8751
  "usage": [
8752
8752
  "DO always control Pagination externally: store `value` (page) and `pageSize` in React state (or URL params), and update both in the `onValueChange(page, pageSize)` callback. Pagination is fully controlled — it has no internal state and will not move unless `value` changes.",
8753
8753
  "DO let Pagination hide itself for zero items and single pages (`hideOnSinglePage`, default true) — it is navigation between multiple result pages. Render it inside a table footer only in the DATA state: never during loading, empty, error, or an unmet prerequisite. Pass `hideOnSinglePage={false}` only when you still want the bar on one page to keep `showTotal` visible.",
@@ -11674,7 +11674,7 @@
11674
11674
  "subParts": [
11675
11675
  "ScrollBar"
11676
11676
  ],
11677
- "tagline": "A native scrolling box (no scroll library since v23): one `overflow: auto` element whose scrollbar is styled from --scroll-area-* tokens. Always set an explicit height/max-height, or nothing overflows and no scrollbar appears. Owns the scrolling element, so it also owns reaching it (viewportRef) and bottom anchoring for a live stream (anchor).",
11677
+ "tagline": "Keyboard focus on the scroll viewport and its descendants uses the shared inset brand ring (--focus-ring-inset-offset), without padding changes. A native scrolling box (no scroll library since v23): one `overflow: auto` element whose scrollbar is styled from --scroll-area-* tokens. Always set an explicit height/max-height, or nothing overflows and no scrollbar appears. Owns the scrolling element, so it also owns reaching it (viewportRef) and bottom anchoring for a live stream (anchor).",
11678
11678
  "usage": [
11679
11679
  "DO set an explicit height or max-height on ScrollArea via className (e.g. `className=\"h-64\"` or `className=\"max-h-[min(300px,50vh)]\"`). Without a height constraint the viewport grows to fit content and the scrollbar is never rendered.",
11680
11680
  "DO wrap content in a single child element inside ScrollArea — the Viewport observes its single child's size to decide whether overflow exists.",
@@ -13227,7 +13227,7 @@
13227
13227
  "ResizableHandle",
13228
13228
  "ResizablePanelGroup"
13229
13229
  ],
13230
- "tagline": "Resizable panel group/child/handle primitives from react-resizable-panels.",
13230
+ "tagline": "Resizable panel group/child/handle primitives from react-resizable-panels. --resizable-handle-size sets separator thickness; ARIA orientation controls its axis. Handles use the shared inset focus ring, including empty handles.",
13231
13231
  "usage": [
13232
13232
  "DO put the layout on `<ResizablePanelGroup orientation=\"horizontal|vertical\">`, the resizable regions in `<ResizablePanel>`, and a `<ResizableHandle>` BETWEEN every adjacent pair — a group of N panels needs N-1 handles or there is nothing to drag.",
13233
13233
  "DO express `defaultSize`/`minSize`/`maxSize` as PERCENTAGE STRINGS like `defaultSize=\"35%\"` (react-resizable-panels v4: a bare number is PIXELS, so `defaultSize={35}` renders a 35px sliver, NOT 35% — pass the string). Don't fight sizing with a fixed `w-[280px]` className on the panel.",
@@ -16687,7 +16687,7 @@
16687
16687
  45
16688
16688
  ],
16689
16689
  "storyPath": "navigation/anchor.tsx",
16690
- "tagline": "Ant Design `Anchor` (6.6.5): the in-page section navigation, and the only thing in this library that COMPUTES which section is current. `NavList activeId` takes that answer as a prop. Resolves by antd's rule — the last section whose edge has crossed a single decision line — which is a pure function of scroll position and therefore cannot flicker, suppresses itself during the programmatic scroll a click starts, reads the landing hash before any scroll event, and jumps rather than tweens under `prefers-reduced-motion`.",
16690
+ "tagline": "Ant Design `Anchor` (6.6.5): the in-page section navigation, and the only thing in this library that COMPUTES which section is current. `NavList activeId` takes that answer as a prop. Resolves by antd's rule — the last section whose edge has crossed a single decision line — which is a pure function of scroll position and therefore cannot flicker, suppresses itself during the programmatic scroll a click starts, reads the landing hash before any scroll event, and jumps rather than tweens under `prefers-reduced-motion`. Keyboard focus uses the shared inset ring in clipping surfaces; scrollports reveal partially clipped keyboard targets without changing their layout.",
16691
16691
  "tier": "lab",
16692
16692
  "usage": [
16693
16693
  "DO give every target a real `id` and point `href` at it as `#id`. The links are real anchors: middle-clickable, deep-linkable, and correct before JavaScript boots.",
package/agent/index.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "components": 169,
5
5
  "patterns": 28,
6
6
  "rules": 44,
7
- "tokens": 2238,
7
+ "tokens": 2241,
8
8
  "vocabulary": 14
9
9
  },
10
10
  "design": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/DESIGN.md",
@@ -16,7 +16,7 @@
16
16
  },
17
17
  {
18
18
  "file": "components-index.json",
19
- "note": "52 KB — name + group + tagline for all 169. FETCH THIS FIRST, then fetch only the components you chose.",
19
+ "note": "53 KB — name + group + tagline for all 169. FETCH THIS FIRST, then fetch only the components you chose.",
20
20
  "url": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/components-index.json"
21
21
  },
22
22
  {
@@ -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.11.2/agent/index.json"
57
+ "pinned": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/v33.12.1/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": 1909,
65
- "foundation": 216,
64
+ "component": 1910,
65
+ "foundation": 218,
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.11.2"
71
+ "version": "33.12.1"
72
72
  }
package/agent/llms.txt CHANGED
@@ -1,10 +1,10 @@
1
1
  # @godxjp/ui
2
2
 
3
- > A Japanese-enterprise React design system: 169 components, 2238 design tokens,
4
- > 44 cardinal rules. This file is the entry point for AI agents. Catalog version 33.11.2.
3
+ > A Japanese-enterprise React design system: 169 components, 2241 design tokens,
4
+ > 44 cardinal rules. This file is the entry point for AI agents. Catalog version 33.12.1.
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.11.2`). It is searchable and version-locked. These files exist for agents
7
+ (`npx @godxjp/ui-mcp@33.12.1`). It is searchable and version-locked. These files exist for agents
8
8
  that can only fetch URLs.
9
9
 
10
10
  ## Start
@@ -16,10 +16,10 @@ that can only fetch URLs.
16
16
  ## Catalog
17
17
 
18
18
  - [patterns-index.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/patterns-index.json): 28 whole-task patterns (name, tagline, tags). Start here when the task is a TASK — "build a settings page" — then fetch `patterns/<name>.json` for complete code.
19
- - [components-index.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/components-index.json): 52 KB — all 169 components as name, group, tagline, plus `absorbed`: the names that do NOT exist and map to it (`Combobox` → `Select`).
19
+ - [components-index.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/components-index.json): 53 KB — all 169 components as name, group, tagline, plus `absorbed`: the names that do NOT exist and map to it (`Combobox` → `Select`).
20
20
  - [components/&lt;Name&gt;.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, 1909 `component` knobs.
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` — 218 `foundation` seeds (`--primary`, `--background`, `--radius`: set these when you are handed a brand), 113 `semantic` roles, 1910 `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.11.2/agent/...`. A catalog that does not match the installed
30
+ the tag: `.../godx-jp/godxjp-ui/v33.12.1/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
@@ -665,6 +665,18 @@
665
665
  "tier": "foundation",
666
666
  "value": "var(--focus-outline-offset)"
667
667
  },
668
+ {
669
+ "description": "Inside clipping surfaces, preserve the mark and gap without changing layout.",
670
+ "name": "--focus-ring-inset-offset",
671
+ "tier": "foundation",
672
+ "value": "calc(-1 * (var(--focus-ring-width) + var(--focus-outline-offset)))"
673
+ },
674
+ {
675
+ "description": "Foundation tokens: color, typography, raw spacing, ratio, radius, shadow.",
676
+ "name": "--focus-ring-contained-offset",
677
+ "tier": "foundation",
678
+ "value": "initial"
679
+ },
668
680
  {
669
681
  "description": "Ring headroom for clipped chrome (`overflow-clip-margin` on the topbar family, gh#291). MUST stay a plain length consumed via a bare var(): Chromium rejects any calc() in overflow-clip-margin at parse time (even calc(2 * 2px)), silently dropping the declaration to 0. Value = max ring paint (3px open/focus) + rounding immunity: at 4px a flush-edge ring had only 1px of slack, which device-pixel rounding on non-retina (dpr 1) displays or fractional browser zoom could consume, shaving the ring's outer edge. 8px keeps ≥5px of slack under any rounding regime.",
670
682
  "name": "--focus-ring-clip-margin",
@@ -672,10 +684,10 @@
672
684
  "value": "8px"
673
685
  },
674
686
  {
675
- "description": "REGION focus ring — the keyboard-scroll affordance on large scroll REGIONS (`.app-main`, gh#271/gh#276), separate from the control ring above. A control-strength 2px brand frame around the entire content area reads as a glitch to mouse-first users (`:focus-visible` promotes on any keypress after a click-focus), so the DEFAULT IS OFF — a deliberate, product-owner tradeoff against WCAG 2.4.7 for keyboard-scroll visibility. A service that needs the visible affordance opts in with ONE token: --region-focus-ring-width: var(--focus-ring-width); `--region-focus-ring-color` is declared `initial` (NOT a :root var binding — the freeze rule) and resolves to the live focus-ring hue at the element that paints it.",
687
+ "description": "Keyboard-scroll regions use an inset brand mark by default. Both the region weight and the global focus switch remain configurable.",
676
688
  "name": "--region-focus-ring-width",
677
689
  "tier": "foundation",
678
- "value": "0"
690
+ "value": "var(--focus-ring-weight)"
679
691
  },
680
692
  {
681
693
  "description": "default = hsl(var(--focus-ring-color, var(--ring)))",
@@ -10061,6 +10073,12 @@
10061
10073
  "tier": "component",
10062
10074
  "value": "var(--space-2)"
10063
10075
  },
10076
+ {
10077
+ "description": "Room for the shared inset focus mark and gap, including handles without a grip.",
10078
+ "name": "--resizable-handle-size",
10079
+ "tier": "component",
10080
+ "value": "var(--space-2)"
10081
+ },
10064
10082
  {
10065
10083
  "description": "`thin` reproduces the old 10px rail closely on the platforms that draw a classic scrollbar; `auto` gives the full-width platform bar, which is what a touch/coarse-pointer console wants. Only `auto | thin | none` are valid — a length is not (that is the standard property's whole bargain, and the reason `--scroll-area-bar-size` could not survive as a rem).",
10066
10084
  "name": "--scroll-area-scrollbar-width",
@@ -1,5 +1,6 @@
1
1
  "use client";
2
2
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import { revealKeyboardFocus } from "../../lib/focus-reveal.js";
3
4
  import * as React from "react";
4
5
  import {
5
6
  columnFilteringFeature,
@@ -1000,6 +1001,7 @@ DataTable.Content = function DataTableContent() {
1000
1001
  "div",
1001
1002
  {
1002
1003
  ref: scrollRef,
1004
+ onFocusCapture: revealKeyboardFocus,
1003
1005
  className: cn(
1004
1006
  "ui-data-table-scroll",
1005
1007
  hasPinEnd && "ui-data-table-has-pin-end",
@@ -1,5 +1,6 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { revealKeyboardFocus } from "../../lib/focus-reveal.js";
3
4
  import * as React from "react";
4
5
  import { useTranslation } from "../../i18n/use-translation.js";
5
6
  import { tableHeadHeightClass } from "../../lib/control-styles.js";
@@ -153,7 +154,9 @@ const Table = React.forwardRef(
153
154
  "div",
154
155
  {
155
156
  ref: scrollRef,
157
+ onFocusCapture: revealKeyboardFocus,
156
158
  className: cn(
159
+ "ui-table-scroll",
157
160
  scrollable ? "relative w-full overflow-auto" : "relative w-full",
158
161
  preset === "action-collection" && "ui-table-collection",
159
162
  preset === "stacked-record-collection" && "ui-table-stacked-collection"
@@ -1,5 +1,6 @@
1
1
  "use client";
2
2
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import { revealKeyboardFocus } from "../../lib/focus-reveal.js";
3
4
  import * as React from "react";
4
5
  import { Menu } from "lucide-react";
5
6
  import { useMediaQuery } from "../../lib/hooks.js";
@@ -80,10 +81,10 @@ function AppShell({
80
81
  /* @__PURE__ */ jsx("span", { className: "app-topbar-brand", "data-hide-below": logoCompactBelow, children: logo }),
81
82
  /* @__PURE__ */ jsx("span", { className: "app-topbar-brand", "data-hide-from": logoCompactBelow, children: logoCompact })
82
83
  ] });
83
- const resolvedTopbar = topbar !== void 0 ? /* @__PURE__ */ jsxs("div", { className: "app-topbar-rail", children: [
84
+ const resolvedTopbar = topbar !== void 0 ? /* @__PURE__ */ jsxs("div", { onFocusCapture: revealKeyboardFocus, className: "app-topbar-rail", children: [
84
85
  !logoInRail && logo !== void 0 && /* @__PURE__ */ jsx("div", { className: "app-topbar-logo", children: brand }),
85
86
  /* @__PURE__ */ jsx("div", { className: "app-topbar-custom", children: topbar })
86
- ] }) : /* @__PURE__ */ jsxs("div", { className: "app-topbar-rail", children: [
87
+ ] }) : /* @__PURE__ */ jsxs("div", { onFocusCapture: revealKeyboardFocus, className: "app-topbar-rail", children: [
87
88
  !logoInRail && logo !== void 0 && /* @__PURE__ */ jsx("div", { className: "app-topbar-logo", children: brand }),
88
89
  topbarLeft !== void 0 && /* @__PURE__ */ jsx("div", { className: "app-topbar-left", children: topbarLeft }),
89
90
  /* @__PURE__ */ jsx("div", { className: "app-topbar-spacer" }),
@@ -167,10 +168,19 @@ function AppShell({
167
168
  rail,
168
169
  bar
169
170
  ] }),
170
- /* @__PURE__ */ jsxs("main", { className: "app-main", "aria-label": t("layout.appShell.mainLabel"), tabIndex: 0, children: [
171
- breadcrumb !== void 0 && /* @__PURE__ */ jsx("div", { className: "app-breadcrumb", children: breadcrumb }),
172
- children
173
- ] }),
171
+ /* @__PURE__ */ jsxs(
172
+ "main",
173
+ {
174
+ onFocusCapture: revealKeyboardFocus,
175
+ className: "app-main",
176
+ "aria-label": t("layout.appShell.mainLabel"),
177
+ tabIndex: 0,
178
+ children: [
179
+ breadcrumb !== void 0 && /* @__PURE__ */ jsx("div", { className: "app-breadcrumb", children: breadcrumb }),
180
+ children
181
+ ]
182
+ }
183
+ ),
174
184
  footer !== void 0 && /* @__PURE__ */ jsx("footer", { className: "app-footer", "aria-label": t("layout.appShell.footerLabel"), children: footer })
175
185
  ]
176
186
  }
@@ -5,4 +5,4 @@ export type { TopbarProp, TopbarProp as TopbarProps } from "../../props/componen
5
5
  * Topbar — a PURE SLOT bar. It positions three clusters (`start` / `center` / `end`) and owns the
6
6
  * bar's flex layout; it does NOT bake any chrome.
7
7
  */
8
- export declare function Topbar({ start, center, end, identity, className, children, height, overflow, pad, style, ...props }: TopbarProp): React.JSX.Element;
8
+ export declare function Topbar({ start, center, end, identity, className, children, height, overflow, pad, style, onFocusCapture, ...props }: TopbarProp): React.JSX.Element;
@@ -1,5 +1,6 @@
1
1
  "use client";
2
2
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import { revealKeyboardFocus } from "../../lib/focus-reveal.js";
3
4
  import { MoreHorizontal } from "lucide-react";
4
5
  import * as React from "react";
5
6
  import { useTranslation } from "../../i18n/use-translation.js";
@@ -18,6 +19,7 @@ function Topbar({
18
19
  overflow = "scroll",
19
20
  pad,
20
21
  style,
22
+ onFocusCapture,
21
23
  ...props
22
24
  }) {
23
25
  const barRef = React.useRef(null);
@@ -34,6 +36,10 @@ function Topbar({
34
36
  "div",
35
37
  {
36
38
  ref: barRef,
39
+ onFocusCapture: (event) => {
40
+ onFocusCapture?.(event);
41
+ if (!event.defaultPrevented) revealKeyboardFocus(event);
42
+ },
37
43
  "data-height": height,
38
44
  "data-identity": hasIdentity ? "true" : void 0,
39
45
  "data-overflow": overflow,
@@ -47,6 +53,7 @@ function Topbar({
47
53
  "div",
48
54
  {
49
55
  ref: controlsRef,
56
+ onFocusCapture: revealKeyboardFocus,
50
57
  "data-slot": "topbar-controls",
51
58
  "data-overflow": overflow,
52
59
  className: "ui-topbar ui-topbar-controls",
@@ -1,5 +1,6 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { revealKeyboardFocus } from "../../lib/focus-reveal.js";
3
4
  import * as React from "react";
4
5
  import { ChevronLeft, ChevronRight, MoreHorizontal } from "lucide-react";
5
6
  import { useTranslation } from "../../i18n/use-translation.js";
@@ -33,6 +34,7 @@ const PaginationContent = React.forwardRef(({ className, ...props }, ref) => {
33
34
  "ul",
34
35
  {
35
36
  ref: setListElement,
37
+ onFocusCapture: revealKeyboardFocus,
36
38
  className: cn("ui-pagination-list", className),
37
39
  role: "list",
38
40
  ...props
@@ -186,6 +188,7 @@ function Pagination({
186
188
  return /* @__PURE__ */ jsxs(
187
189
  "nav",
188
190
  {
191
+ onFocusCapture: revealKeyboardFocus,
189
192
  "aria-label": navLabel,
190
193
  "data-simple": "true",
191
194
  "data-size": size,
@@ -230,6 +233,7 @@ function Pagination({
230
233
  return /* @__PURE__ */ jsxs(
231
234
  "nav",
232
235
  {
236
+ onFocusCapture: revealKeyboardFocus,
233
237
  "aria-label": navLabel,
234
238
  "data-size": size,
235
239
  "data-align": align,
@@ -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.11.2",
3
+ "version": "33.12.1",
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,
@@ -1,5 +1,6 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { revealKeyboardFocus } from "../lib/focus-reveal.js";
3
4
  import * as React from "react";
4
5
  import { useTranslation } from "../i18n/use-translation.js";
5
6
  import { isDevelopment } from "../lib/dev.js";
@@ -264,7 +265,16 @@ function Anchor({
264
265
  } : void 0
265
266
  }
266
267
  ),
267
- /* @__PURE__ */ jsx("ul", { ref: listRef, "data-slot": "anchor-list", className: "ui-anchor-list", children: renderItems(items ?? [], false) })
268
+ /* @__PURE__ */ jsx(
269
+ "ul",
270
+ {
271
+ onFocusCapture: revealKeyboardFocus,
272
+ ref: listRef,
273
+ "data-slot": "anchor-list",
274
+ className: "ui-anchor-list",
275
+ children: renderItems(items ?? [], false)
276
+ }
277
+ )
268
278
  ] })
269
279
  }
270
280
  );
@@ -0,0 +1,5 @@
1
+ import type { FocusEvent } from "react";
2
+ /** Native Tab scrolling can leave a partially visible control clipped (Chromium).
3
+ * Reveal keyboard targets that fit inside a scroll viewport, without moving oversized
4
+ * rows, changing layout, or scrolling on pointer focus. */
5
+ export declare function revealKeyboardFocus(event: FocusEvent<HTMLElement>): void;