@godxjp/ui 33.11.2 → 33.12.2

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.
Files changed (42) hide show
  1. package/README.md +3 -3
  2. package/agent/START-HERE.md +7 -7
  3. package/agent/components/Anchor.json +1 -1
  4. package/agent/components/AppShell.json +1 -1
  5. package/agent/components/DataTable.json +1 -1
  6. package/agent/components/Pagination.json +1 -1
  7. package/agent/components/ResizablePanel.json +1 -1
  8. package/agent/components/ScrollArea.json +1 -1
  9. package/agent/components/Table.json +1 -1
  10. package/agent/components-index.json +7 -7
  11. package/agent/components.json +7 -7
  12. package/agent/index.json +6 -6
  13. package/agent/llms.txt +6 -6
  14. package/agent/tokens.json +20 -2
  15. package/dist/components/data-display/data-table.js +2 -0
  16. package/dist/components/data-display/table.js +3 -0
  17. package/dist/components/layout/app-shell.js +16 -6
  18. package/dist/components/layout/resizable.d.ts +1 -1
  19. package/dist/components/layout/resizable.js +7 -1
  20. package/dist/components/layout/topbar.d.ts +1 -1
  21. package/dist/components/layout/topbar.js +7 -0
  22. package/dist/components/navigation/pagination.js +4 -0
  23. package/dist/contracts/measurement.json +1 -1
  24. package/dist/lab/anchor.js +11 -1
  25. package/dist/lib/focus-reveal.d.ts +5 -0
  26. package/dist/lib/focus-reveal.js +28 -0
  27. package/dist/lib/hooks.js +5 -2
  28. package/dist/styles/card-layout.css +13 -0
  29. package/dist/styles/data-display-layout.css +2 -0
  30. package/dist/styles/data-entry-layout.css +2 -0
  31. package/dist/styles/focus-ring.css +77 -19
  32. package/dist/styles/layers.json +1 -1
  33. package/dist/styles/layout.css +10 -4
  34. package/dist/styles/navigation-layout.css +4 -0
  35. package/dist/styles/shell-layout.css +23 -0
  36. package/dist/styles/table-layout.css +14 -0
  37. package/dist/styles.css +1 -1
  38. package/dist/tokens/base.css +2 -0
  39. package/dist/tokens/components/resizable.css +4 -0
  40. package/dist/tokens/foundation.css +4 -1
  41. package/docs/TOKENS.md +41 -23
  42. package/package.json +2 -2
@@ -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>, viewport?: EventTarget & HTMLElement): void;
@@ -0,0 +1,28 @@
1
+ function revealKeyboardFocus(event, viewport = event.currentTarget) {
2
+ const target = event.target;
3
+ if (!(target instanceof HTMLElement) || target === viewport || !viewport.contains(target) || !target.matches(":focus-visible"))
4
+ return;
5
+ const carrier = target.closest(".ui-tag-input,.ui-control-composite-field") ?? target;
6
+ const rect = carrier.getBoundingClientRect();
7
+ const focus = getComputedStyle(carrier);
8
+ const extent = focus.outlineStyle === "none" ? 0 : Math.max(0, parseFloat(focus.outlineWidth) + parseFloat(focus.outlineOffset));
9
+ const box = {
10
+ left: rect.left - extent,
11
+ right: rect.right + extent,
12
+ top: rect.top - extent,
13
+ bottom: rect.bottom + extent,
14
+ width: rect.width + 2 * extent,
15
+ height: rect.height + 2 * extent
16
+ };
17
+ const frame = viewport.getBoundingClientRect();
18
+ const left = frame.left + viewport.clientLeft;
19
+ const top = frame.top + viewport.clientTop;
20
+ const style = getComputedStyle(viewport);
21
+ const outsideX = /auto|scroll/.test(style.overflowX) && box.width <= viewport.clientWidth && (box.left < left || box.right > left + viewport.clientWidth);
22
+ const outsideY = /auto|scroll/.test(style.overflowY) && box.height <= viewport.clientHeight && (box.top < top || box.bottom > top + viewport.clientHeight);
23
+ if (outsideX || outsideY)
24
+ carrier.scrollIntoView?.({ block: "nearest", inline: "nearest", behavior: "instant" });
25
+ }
26
+ export {
27
+ revealKeyboardFocus
28
+ };
package/dist/lib/hooks.js CHANGED
@@ -77,9 +77,12 @@ function useScrollableRegionTabIndex(element) {
77
77
  const sync = () => {
78
78
  const scrolls = element.scrollHeight > element.clientHeight || element.scrollWidth > element.clientWidth;
79
79
  const hasFocusableContent = element.querySelector(SCROLLABLE_REGION_FOCUSABLE_SELECTOR) !== null;
80
- if (scrolls && !hasFocusableContent) {
80
+ const hasVisibleBox = element.clientWidth > 0 && element.clientHeight > 0;
81
+ if (!hasVisibleBox) {
82
+ if (element.getAttribute("tabindex") !== "-1") element.setAttribute("tabindex", "-1");
83
+ } else if (scrolls && !hasFocusableContent) {
81
84
  if (element.getAttribute("tabindex") !== "0") element.setAttribute("tabindex", "0");
82
- } else if (element.getAttribute("tabindex") === "0") {
85
+ } else if (["0", "-1"].includes(element.getAttribute("tabindex") ?? "")) {
83
86
  element.removeAttribute("tabindex");
84
87
  }
85
88
  };
@@ -28,6 +28,19 @@
28
28
  overflow: hidden;
29
29
  }
30
30
 
31
+ [data-slot="card"]:focus-within,
32
+ [data-slot="card"]:has([data-slot="card-header"][data-banded]):focus-within {
33
+ overflow: clip;
34
+ overflow-clip-margin: var(--focus-ring-clip-margin);
35
+ }
36
+
37
+ @supports not (overflow-clip-margin: 1px) {
38
+ [data-slot="card"]:focus-within,
39
+ [data-slot="card"]:has([data-slot="card-header"][data-banded]):focus-within {
40
+ overflow: visible;
41
+ }
42
+ }
43
+
31
44
  button[data-slot="card"] {
32
45
  text-align: inherit;
33
46
  }
@@ -1108,6 +1108,8 @@
1108
1108
  @layer components {
1109
1109
 
1110
1110
  .ui-scroll-area {
1111
+ padding: calc(var(--focus-ring-weight) + var(--focus-outline-offset));
1112
+ scroll-padding: calc(var(--focus-ring-weight) + var(--focus-outline-offset));
1111
1113
  position: relative;
1112
1114
  scrollbar-width: var(--scroll-area-scrollbar-width);
1113
1115
  scrollbar-color: var(--scroll-area-thumb-color, hsl(var(--border)))
@@ -700,6 +700,8 @@
700
700
  }
701
701
 
702
702
  .ui-branch-scope-picker-list {
703
+ padding: calc(var(--focus-ring-weight) + var(--focus-outline-offset));
704
+ scroll-padding: calc(var(--focus-ring-weight) + var(--focus-outline-offset));
703
705
  max-block-size: var(--branch-scope-picker-list-max-height);
704
706
  overflow-y: auto;
705
707
  }
@@ -4,6 +4,34 @@
4
4
  :is(
5
5
  .ui-focus-ring,
6
6
  .ui-focus-ring-outline,
7
+ .ui-flex,
8
+ .ui-table-scroll,
9
+ .ui-pagination-list,
10
+ .react-aria-Menu,
11
+ .ui-chart [tabindex],
12
+ .ui-prose input,
13
+ .ui-auth-legal-footer a[href],
14
+ .ui-auth-shell a[href],
15
+ .app-main a[href],
16
+ .ui-breadcrumb a[href],
17
+ .ui-breadcrumb-link,
18
+ .ui-password-input-toggle,
19
+ .ui-search-select-value-remove,
20
+ .ui-tabs-overflow,
21
+ .ui-resizable-handle,
22
+ .ui-scroll-area,
23
+ .ui-data-table-scroll,
24
+ .ui-resizable-panel,
25
+ .ui-master-detail-master,
26
+ .ui-branch-scope-picker-list,
27
+ .ui-tag-input-chip,
28
+ .ui-attachments-placeholder,
29
+ .ui-block-editor-content,
30
+ .ui-range-timeline,
31
+ .sb-product,
32
+ .ui-logo,
33
+ .ui-logo-lockup,
34
+ [data-slot="anchor-link"],
7
35
  [data-interactive] > [data-slot="table-cell"] a[href],
8
36
  [data-interactive] > [data-slot="table-cell"] button,
9
37
  .ui-data-table-ellipsis[data-truncated],
@@ -51,6 +79,8 @@
51
79
  .tb-icon-btn
52
80
  ):focus-visible,
53
81
 
82
+ .ui-command-input-wrapper:has(.ui-command-input:focus-visible),
83
+ .ui-attachments:has(.ui-attachments-input:focus-visible),
54
84
  .ui-calendar-dropdown-root:has(.ui-calendar-dropdown:focus-visible),
55
85
  .ui-checkbox[data-focus-visible],
56
86
  .ui-radio[data-focus-visible],
@@ -70,17 +100,21 @@
70
100
  var(--focus-outline-color, var(--focus-ring-color, var(--ring))) /
71
101
  var(--focus-ring-opacity, 1)
72
102
  );
73
- outline-offset: var(--focus-ring-offset);
103
+ outline-offset: var(--focus-ring-contained-offset, var(--focus-ring-offset));
104
+ scroll-margin: calc(var(--focus-ring-width) + var(--focus-outline-offset));
74
105
 
75
106
  transition:
76
107
  outline-offset 0s,
77
108
  outline 0s;
78
109
  }
79
110
 
80
- :root[data-focus-outline="on"]
111
+ :root:not([data-focus-outline="off"])
81
112
  :is(
82
113
  .ui-focus-ring,
83
114
  .ui-focus-ring-outline,
115
+ .ui-logo,
116
+ .ui-logo-lockup,
117
+ [data-slot="anchor-link"],
84
118
  .ui-input,
85
119
  .ui-control,
86
120
  .ui-control-multiline,
@@ -123,21 +157,22 @@
123
157
  .tb-icon-btn
124
158
  ):focus-visible,
125
159
 
126
- :root[data-focus-outline="on"] .ui-checkbox[data-focus-visible],
127
- :root[data-focus-outline="on"] .ui-radio[data-focus-visible],
128
- :root[data-focus-outline="on"] .ui-switch[data-focus-visible],
129
- :root[data-focus-outline="on"] .ui-slider-thumb[data-focus-visible],
130
- :root[data-focus-outline="on"] .ui-focus-ring[data-focus-visible],
131
- :root[data-focus-outline="on"] .ui-control-trigger[data-state="open"],
132
- :root[data-focus-outline="on"] .ui-tag-input:focus-within,
133
- :root[data-focus-outline="on"] .ui-control-composite-field:focus-within,
134
- :root[data-focus-outline="on"] .ui-control-composite-field[data-state="open"],
135
- :root[data-focus-outline="on"] .ui-otp-slot[data-active="true"],
136
- :root[data-focus-outline="on"]
160
+ :root:not([data-focus-outline="off"]) .ui-checkbox[data-focus-visible],
161
+ :root:not([data-focus-outline="off"]) .ui-radio[data-focus-visible],
162
+ :root:not([data-focus-outline="off"]) .ui-switch[data-focus-visible],
163
+ :root:not([data-focus-outline="off"]) .ui-slider-thumb[data-focus-visible],
164
+ :root:not([data-focus-outline="off"]) .ui-focus-ring[data-focus-visible],
165
+ :root:not([data-focus-outline="off"]) .ui-control-trigger[data-state="open"],
166
+ :root:not([data-focus-outline="off"]) .ui-tag-input:focus-within,
167
+ :root:not([data-focus-outline="off"]) .ui-control-composite-field:focus-within,
168
+ :root:not([data-focus-outline="off"]) .ui-control-composite-field[data-state="open"],
169
+ :root:not([data-focus-outline="off"]) .ui-otp-slot[data-active="true"],
170
+ :root:not([data-focus-outline="off"])
137
171
  .ui-otp-group[data-appearance="grouped"]:has(.ui-otp-slot[data-active="true"]) {
138
172
 
139
173
  --tw-ring-shadow: var(
140
174
  --focus-field-shadow,
175
+ inset 0 0 0 calc(-1 * var(--focus-ring-contained-offset, 0px)) hsl(var(--background)),
141
176
  0 0 0 var(--focus-ring-glow-width)
142
177
  hsl(
143
178
  var(
@@ -149,6 +184,7 @@
149
184
  );
150
185
  box-shadow: var(
151
186
  --focus-field-shadow,
187
+ inset 0 0 0 calc(-1 * var(--focus-ring-contained-offset, 0px)) hsl(var(--background)),
152
188
  0 0 0 var(--focus-ring-glow-width)
153
189
  hsl(
154
190
  var(
@@ -160,7 +196,7 @@
160
196
  );
161
197
  }
162
198
 
163
- :root[data-focus-outline="on"]
199
+ :root:not([data-focus-outline="off"])
164
200
  :is(
165
201
  .ui-input,
166
202
  .ui-control,
@@ -168,10 +204,10 @@
168
204
  .ui-control-trigger,
169
205
  .ui-tag-input
170
206
  ):focus-visible,
171
- :root[data-focus-outline="on"] .ui-control-trigger[data-state="open"],
172
- :root[data-focus-outline="on"] .ui-tag-input:focus-within,
173
- :root[data-focus-outline="on"] .ui-control-composite-field:focus-within,
174
- :root[data-focus-outline="on"] .ui-control-composite-field[data-state="open"] {
207
+ :root:not([data-focus-outline="off"]) .ui-control-trigger[data-state="open"],
208
+ :root:not([data-focus-outline="off"]) .ui-tag-input:focus-within,
209
+ :root:not([data-focus-outline="off"]) .ui-control-composite-field:focus-within,
210
+ :root:not([data-focus-outline="off"]) .ui-control-composite-field[data-state="open"] {
175
211
  --input: var(--focus-ring-color, var(--ring));
176
212
  }
177
213
 
@@ -191,10 +227,32 @@
191
227
 
192
228
  a.ui-list-row,
193
229
  tr.ui-focus-ring,
194
- .sb-nav-item {
230
+ .sb-nav-item,
231
+ .ui-app-setting-picker-icon[data-appearance="bar"],
232
+ .ui-legal-document-toc-link {
195
233
  --focus-ring-offset: calc(-1 * var(--focus-ring-width));
196
234
  }
197
235
 
236
+ :is(
237
+ .ui-mobile-shell-tabbar .ui-button--ghost,
238
+ [data-slot="tabs-list"] [role="tab"],
239
+ [data-slot="tabs-list"] .ui-tabs-add,
240
+ .ui-app-setting-picker-icon[data-appearance="bar"],
241
+ .ui-legal-document-rail .ui-legal-document-toc-link,
242
+ .ui-table-scroll [data-slot="table-cell"] .ui-button--ghost,
243
+ .ui-data-table-scroll [data-slot="table-cell"] .ui-button--ghost,
244
+ .ui-search-select-value-remove,
245
+ .ui-table-scroll tr.ui-focus-ring,
246
+ .ui-data-table-scroll tr.ui-focus-ring,
247
+ .ui-resizable-handle,
248
+ .ui-scroll-area,
249
+ .ui-table-scroll,
250
+ .ui-data-table-scroll,
251
+ .ui-resizable-panel
252
+ ):focus-visible {
253
+ --focus-ring-contained-offset: var(--focus-ring-inset-offset);
254
+ }
255
+
198
256
  .ui-accordion-trigger {
199
257
  --focus-ring-offset: var(--accordion-focus-ring-offset);
200
258
  }
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "33.11.2",
2
+ "version": "33.12.2",
3
3
  "generatedBy": "scripts/gen-style-layers.mjs — do not edit by hand; run `pnpm gen:style-layers`",
4
4
  "base": "base.css",
5
5
  "fonts": "fonts.css",
@@ -642,12 +642,14 @@
642
642
  }
643
643
 
644
644
  .ui-resizable-panel {
645
+ padding: calc(var(--focus-ring-weight) + var(--focus-outline-offset));
646
+ scroll-padding: calc(var(--focus-ring-weight) + var(--focus-outline-offset));
645
647
  min-height: 0;
646
648
  min-width: 0;
647
649
  }
648
650
 
649
651
  .ui-resizable-handle {
650
- width: var(--control-border-width, var(--stroke-hairline));
652
+ inline-size: var(--resizable-handle-size);
651
653
  background: hsl(var(--border));
652
654
  position: relative;
653
655
  flex-shrink: 0;
@@ -657,9 +659,9 @@
657
659
  color: hsl(var(--muted-foreground));
658
660
  }
659
661
 
660
- .ui-resizable-handle[aria-orientation="vertical"] {
661
- width: auto;
662
- height: var(--control-border-width, var(--stroke-hairline));
662
+ .ui-resizable-handle[aria-orientation="horizontal"] {
663
+ inline-size: auto;
664
+ block-size: var(--resizable-handle-size);
663
665
  }
664
666
 
665
667
  .ui-resizable-handle:hover,
@@ -1385,6 +1387,8 @@
1385
1387
  }
1386
1388
 
1387
1389
  .ui-pagination-list {
1390
+ padding: calc(var(--focus-ring-weight) + var(--focus-outline-offset));
1391
+ scroll-padding: calc(var(--focus-ring-weight) + var(--focus-outline-offset));
1388
1392
  display: flex;
1389
1393
  min-width: 0;
1390
1394
  align-items: center;
@@ -1701,6 +1705,8 @@
1701
1705
  }
1702
1706
 
1703
1707
  .ui-legal-document-rail {
1708
+ padding: calc(var(--focus-ring-weight) + var(--focus-outline-offset));
1709
+ scroll-padding: calc(var(--focus-ring-weight) + var(--focus-outline-offset));
1704
1710
  display: flex;
1705
1711
  flex-direction: column;
1706
1712
  gap: var(--legal-document-nav-gap);
@@ -493,6 +493,8 @@
493
493
  }
494
494
 
495
495
  .ui-pagination {
496
+ padding: calc(var(--focus-ring-weight) + var(--focus-outline-offset));
497
+ scroll-padding: calc(var(--focus-ring-weight) + var(--focus-outline-offset));
496
498
  max-inline-size: 100%;
497
499
  overflow-x: auto;
498
500
  scrollbar-width: thin;
@@ -1402,6 +1404,8 @@
1402
1404
  }
1403
1405
 
1404
1406
  .ui-anchor[data-direction="horizontal"] .ui-anchor-list {
1407
+ padding: calc(var(--focus-ring-weight) + var(--focus-outline-offset));
1408
+ scroll-padding: calc(var(--focus-ring-weight) + var(--focus-outline-offset));
1405
1409
  display: flex;
1406
1410
  gap: var(--anchor-gap);
1407
1411
 
@@ -277,6 +277,9 @@
277
277
  }
278
278
 
279
279
  .app-main {
280
+
281
+ padding: calc(var(--focus-ring-weight) + var(--focus-outline-offset));
282
+ scroll-padding: calc(var(--focus-ring-weight) + var(--focus-outline-offset));
280
283
  grid-area: main;
281
284
  min-width: 0;
282
285
  overflow: auto;
@@ -753,6 +756,11 @@
753
756
  gap: var(--space-3);
754
757
  }
755
758
 
759
+ .app-topbar-rail:has(> .app-topbar-right) {
760
+ overflow-x: auto;
761
+ scrollbar-width: none;
762
+ }
763
+
756
764
  .app-topbar-logo,
757
765
  .app-topbar-logo > a {
758
766
  display: flex;
@@ -2095,6 +2103,11 @@
2095
2103
  justify-content: center;
2096
2104
  }
2097
2105
 
2106
+ .ui-topbar[data-overflow="scroll"]
2107
+ > .ui-topbar-center:has(> :is(button, a, [role="button"]):not(.ui-topbar-search, .tb-search)) {
2108
+ min-inline-size: min-content;
2109
+ }
2110
+
2098
2111
  .ui-topbar-search,
2099
2112
  .ui-topbar-search-panel {
2100
2113
  display: flex;
@@ -2228,11 +2241,21 @@
2228
2241
  }
2229
2242
 
2230
2243
  .ui-topbar[data-overflow="scroll"] {
2244
+
2245
+ margin-inline: calc(-1 * (var(--focus-ring-weight) + var(--focus-outline-offset)));
2246
+ max-width: calc(100% + 2 * (var(--focus-ring-weight) + var(--focus-outline-offset)));
2247
+ padding-inline: calc(
2248
+ var(--topbar-inset) + var(--focus-ring-weight) + var(--focus-outline-offset)
2249
+ );
2231
2250
  overflow-x: auto;
2232
2251
  overscroll-behavior-inline: contain;
2233
2252
  scrollbar-width: none;
2234
2253
  }
2235
2254
 
2255
+ .ui-topbar[data-overflow="scroll"]:not([data-height]):not(.app-topbar .ui-topbar) {
2256
+ padding-block: calc(var(--focus-ring-weight) + var(--focus-outline-offset));
2257
+ }
2258
+
2236
2259
  .ui-topbar[data-overflow="scroll"]::-webkit-scrollbar {
2237
2260
  display: none;
2238
2261
  }
@@ -358,6 +358,17 @@
358
358
  saturate(var(--surface-backdrop-saturate, 160%));
359
359
  }
360
360
 
361
+ .ui-data-table-surface:focus-within {
362
+ overflow: clip;
363
+ overflow-clip-margin: var(--focus-ring-clip-margin);
364
+ }
365
+
366
+ @supports not (overflow-clip-margin: 1px) {
367
+ .ui-data-table-surface:focus-within {
368
+ overflow: visible;
369
+ }
370
+ }
371
+
361
372
  .ui-data-table-surface[data-preset="action-collection"] {
362
373
  min-inline-size: 0;
363
374
  }
@@ -460,6 +471,9 @@
460
471
  overflow-x: auto;
461
472
  margin-inline: calc(-1 * var(--space-page-active-x));
462
473
  padding-inline: var(--space-page-active-x);
474
+
475
+ padding-block: calc(var(--focus-ring-weight) + var(--focus-outline-offset));
476
+ scroll-padding-block: calc(var(--focus-ring-weight) + var(--focus-outline-offset));
463
477
  }
464
478
 
465
479
  .ui-data-table-scroll::after {