@godxjp/ui 33.10.1 → 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.
Files changed (59) hide show
  1. package/DESIGN.md +1 -0
  2. package/README.md +3 -3
  3. package/agent/DESIGN.md +1 -0
  4. package/agent/START-HERE.md +7 -7
  5. package/agent/components/Anchor.json +1 -1
  6. package/agent/components/AppShell.json +1 -1
  7. package/agent/components/Cascader.json +1 -0
  8. package/agent/components/DataTable.json +1 -1
  9. package/agent/components/DatePicker.json +1 -0
  10. package/agent/components/Input.json +1 -0
  11. package/agent/components/NumberInput.json +1 -0
  12. package/agent/components/Pagination.json +1 -1
  13. package/agent/components/PasswordInput.json +1 -0
  14. package/agent/components/ResizablePanel.json +1 -1
  15. package/agent/components/ScrollArea.json +1 -1
  16. package/agent/components/SearchInput.json +1 -0
  17. package/agent/components/Select.json +1 -0
  18. package/agent/components/Table.json +1 -1
  19. package/agent/components/TimePicker.json +1 -0
  20. package/agent/components/Topbar.json +8 -2
  21. package/agent/components/TopbarItem.json +7 -1
  22. package/agent/components/TreeSelect.json +1 -0
  23. package/agent/components-index.json +7 -7
  24. package/agent/components.json +31 -10
  25. package/agent/index.json +6 -6
  26. package/agent/llms.txt +6 -6
  27. package/agent/tokens.json +39 -3
  28. package/dist/components/data-display/data-table.js +2 -0
  29. package/dist/components/data-display/table.js +3 -0
  30. package/dist/components/data-entry/input.js +16 -1
  31. package/dist/components/data-entry/search-input.js +1 -1
  32. package/dist/components/layout/app-shell.js +16 -6
  33. package/dist/components/layout/topbar-item.d.ts +1 -0
  34. package/dist/components/layout/topbar-item.js +9 -4
  35. package/dist/components/layout/topbar.d.ts +1 -1
  36. package/dist/components/layout/topbar.js +32 -3
  37. package/dist/components/navigation/pagination.js +4 -0
  38. package/dist/contracts/measurement.json +1 -1
  39. package/dist/lab/anchor.js +11 -1
  40. package/dist/lib/focus-reveal.d.ts +5 -0
  41. package/dist/lib/focus-reveal.js +19 -0
  42. package/dist/lib/hooks.js +5 -2
  43. package/dist/props/components/layout.prop.d.ts +13 -0
  44. package/dist/props/registry.d.ts +9 -1
  45. package/dist/props/registry.js +10 -0
  46. package/dist/styles/control.css +131 -3
  47. package/dist/styles/focus-ring.css +79 -19
  48. package/dist/styles/layers.json +1 -1
  49. package/dist/styles/layout.css +4 -4
  50. package/dist/styles/shell-layout.css +48 -0
  51. package/dist/styles.css +1 -1
  52. package/dist/tokens/base.css +2 -0
  53. package/dist/tokens/components/control.css +4 -1
  54. package/dist/tokens/components/resizable.css +4 -0
  55. package/dist/tokens/components/shell.css +3 -0
  56. package/dist/tokens/foundation.css +4 -1
  57. package/docs/TOKENS.md +52 -23
  58. package/package.json +2 -2
  59. package/scripts/visual-audit.mjs +33 -3
@@ -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";
@@ -11,17 +12,21 @@ function Topbar({
11
12
  start,
12
13
  center,
13
14
  end,
15
+ identity,
14
16
  className,
15
17
  children,
16
18
  height,
17
19
  overflow = "scroll",
18
20
  pad,
19
21
  style,
22
+ onFocusCapture,
20
23
  ...props
21
24
  }) {
22
25
  const barRef = React.useRef(null);
26
+ const controlsRef = React.useRef(null);
27
+ const hasIdentity = identity != null && children == null;
23
28
  const foldable = overflow === "menu" && children == null;
24
- const collapsed = useCollapsesToMenu(barRef, foldable);
29
+ const collapsed = useCollapsesToMenu(hasIdentity ? controlsRef : barRef, foldable);
25
30
  const clusters = /* @__PURE__ */ jsxs(Fragment, { children: [
26
31
  start != null ? /* @__PURE__ */ jsx("div", { "data-slot": "topbar-start", className: "ui-topbar-start", children: start }) : null,
27
32
  center != null ? /* @__PURE__ */ jsx("div", { "data-slot": "topbar-center", className: "ui-topbar-center", children: center }) : null,
@@ -31,14 +36,32 @@ function Topbar({
31
36
  "div",
32
37
  {
33
38
  ref: barRef,
39
+ onFocusCapture: (event) => {
40
+ onFocusCapture?.(event);
41
+ if (!event.defaultPrevented) revealKeyboardFocus(event);
42
+ },
34
43
  "data-height": height,
44
+ "data-identity": hasIdentity ? "true" : void 0,
35
45
  "data-overflow": overflow,
36
46
  "data-collapsed": foldable ? String(collapsed) : void 0,
37
47
  style: { ...style, ...padStyle(pad, void 0) },
38
48
  "data-slot": "topbar",
39
49
  className: cn("ui-topbar", className),
40
50
  ...props,
41
- children: children ?? (collapsed ? /* @__PURE__ */ jsx(TopbarOverflowMenu, { children: clusters }) : clusters)
51
+ children: children ?? (hasIdentity ? /* @__PURE__ */ jsxs(Fragment, { children: [
52
+ /* @__PURE__ */ jsx(
53
+ "div",
54
+ {
55
+ ref: controlsRef,
56
+ onFocusCapture: revealKeyboardFocus,
57
+ "data-slot": "topbar-controls",
58
+ "data-overflow": overflow,
59
+ className: "ui-topbar ui-topbar-controls",
60
+ children: collapsed ? /* @__PURE__ */ jsx(TopbarOverflowMenu, { children: clusters }) : clusters
61
+ }
62
+ ),
63
+ /* @__PURE__ */ jsx("div", { "data-slot": "topbar-identity", className: "ui-topbar-identity", children: identity })
64
+ ] }) : collapsed ? /* @__PURE__ */ jsx(TopbarOverflowMenu, { children: clusters }) : clusters)
42
65
  }
43
66
  );
44
67
  }
@@ -69,7 +92,13 @@ function useCollapsesToMenu(ref, enabled) {
69
92
  if (typeof ResizeObserver === "undefined") return void 0;
70
93
  const observer = new ResizeObserver(update);
71
94
  observer.observe(bar);
72
- if (!collapsed) for (const cluster of bar.children) observer.observe(cluster);
95
+ if (!collapsed)
96
+ for (const cluster of bar.children) {
97
+ observer.observe(cluster);
98
+ if (cluster.getAttribute("data-slot") === "topbar-start") {
99
+ for (const cell of cluster.children) observer.observe(cell);
100
+ }
101
+ }
73
102
  return () => observer.disconnect();
74
103
  }, [ref, enabled, collapsed]);
75
104
  return enabled && collapsed;
@@ -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.10.1",
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;
@@ -0,0 +1,19 @@
1
+ function revealKeyboardFocus(event) {
2
+ const viewport = event.currentTarget;
3
+ const target = event.target;
4
+ if (!(target instanceof HTMLElement) || target === viewport || !viewport.contains(target) || !target.matches(":focus-visible"))
5
+ return;
6
+ const carrier = target.closest(".ui-tag-input,.ui-control-composite-field") ?? target;
7
+ const box = carrier.getBoundingClientRect();
8
+ const frame = viewport.getBoundingClientRect();
9
+ const left = frame.left + viewport.clientLeft;
10
+ const top = frame.top + viewport.clientTop;
11
+ const style = getComputedStyle(viewport);
12
+ const outsideX = /auto|scroll/.test(style.overflowX) && box.width <= viewport.clientWidth && (box.left < left || box.right > left + viewport.clientWidth);
13
+ const outsideY = /auto|scroll/.test(style.overflowY) && box.height <= viewport.clientHeight && (box.top < top || box.bottom > top + viewport.clientHeight);
14
+ if (outsideX || outsideY)
15
+ carrier.scrollIntoView?.({ block: "nearest", inline: "nearest", behavior: "instant" });
16
+ }
17
+ export {
18
+ revealKeyboardFocus
19
+ };
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
  };
@@ -1545,6 +1545,14 @@ export type TopbarProp = Omit<React.HTMLAttributes<HTMLDivElement>, "children">
1545
1545
  center?: ReactNode;
1546
1546
  /** Inline-end cluster — settings pickers, notifications, the user menu. */
1547
1547
  end?: ReactNode;
1548
+ /**
1549
+ * Opt-in shared-device identity, pinned outside scrolling/overflow menus. Pass one
1550
+ * TopbarItem with `truncate` and an Avatar in `icon`; use a plain string name so its full
1551
+ * title is automatic. Never combine with hideBelow/labelHideBelow/iconHideFrom.
1552
+ * Capped by --topbar-identity-max-inline-size (40%); primary controls retain their floors.
1553
+ * At extreme widths only the other clusters scroll or move to the menu. Ignored with children.
1554
+ */
1555
+ identity?: ReactNode;
1548
1556
  /**
1549
1557
  * What a bar that does not fit does with the cells that do not fit (gh#728).
1550
1558
  *
@@ -1643,6 +1651,11 @@ export type TopbarItemProp = Omit<React.ButtonHTMLAttributes<HTMLButtonElement>,
1643
1651
  * accessible name. Under `asChild` the child element's own children are the label.
1644
1652
  */
1645
1653
  labelHideBelow?: BreakpointProp;
1654
+ /** Ellipse the label in the available width, keeping the icon and control minimum.
1655
+ * Same boolean vocabulary as Text.truncate. Overrides labelHideBelow; the complete text
1656
+ * remains the accessible name. String children automatically supply title (asChild too).
1657
+ */
1658
+ truncate?: boolean;
1646
1659
  /**
1647
1660
  * Unread count OVERLAID on the cell's glyph — the notification-bell affordance the cell's own use
1648
1661
  * cases name (gh#398). Pass the CONTENT ONLY, exactly like `SidebarItemProp.badge`: a number, a
@@ -1433,7 +1433,11 @@ export declare const COMPONENT_PROP_REGISTRY: {
1433
1433
  readonly TopbarProp: {
1434
1434
  readonly group: "layout";
1435
1435
  readonly file: "components/layout.prop.ts";
1436
- readonly vocabulary: readonly ["ChildrenProp", {
1436
+ readonly vocabulary: readonly [{
1437
+ readonly field: "identity";
1438
+ readonly local: true;
1439
+ readonly reason: "Reserved shared-device account slot; bounded and outside the overflow policy.";
1440
+ }, "ChildrenProp", {
1437
1441
  readonly field: "overflow";
1438
1442
  readonly local: true;
1439
1443
  readonly reason: "The bar's overflow contract — `scroll` (default) keeps every cell reachable, `clip` is the pre-gh#728 slicing.";
@@ -1443,6 +1447,10 @@ export declare const COMPONENT_PROP_REGISTRY: {
1443
1447
  readonly group: "layout";
1444
1448
  readonly file: "components/layout.prop.ts";
1445
1449
  readonly vocabulary: readonly ["ChildrenProp", "ClassNameProp", {
1450
+ readonly field: "truncate";
1451
+ readonly local: true;
1452
+ readonly reason: "Text.truncate boolean vocabulary applied to the bar cell label.";
1453
+ }, {
1446
1454
  readonly field: "asChild";
1447
1455
  readonly local: true;
1448
1456
  readonly reason: "Radix Slot passthrough — merges onto the child";
@@ -1551,6 +1551,11 @@ const COMPONENT_PROP_REGISTRY = {
1551
1551
  group: "layout",
1552
1552
  file: "components/layout.prop.ts",
1553
1553
  vocabulary: [
1554
+ {
1555
+ field: "identity",
1556
+ local: true,
1557
+ reason: "Reserved shared-device account slot; bounded and outside the overflow policy."
1558
+ },
1554
1559
  "ChildrenProp",
1555
1560
  {
1556
1561
  field: "overflow",
@@ -1565,6 +1570,11 @@ const COMPONENT_PROP_REGISTRY = {
1565
1570
  vocabulary: [
1566
1571
  "ChildrenProp",
1567
1572
  "ClassNameProp",
1573
+ {
1574
+ field: "truncate",
1575
+ local: true,
1576
+ reason: "Text.truncate boolean vocabulary applied to the bar cell label."
1577
+ },
1568
1578
  { field: "asChild", local: true, reason: "Radix Slot passthrough \u2014 merges onto the child" },
1569
1579
  "BreakpointProp",
1570
1580
  {
@@ -1232,7 +1232,10 @@
1232
1232
 
1233
1233
  .ui-search-input-control {
1234
1234
  padding-inline-start: var(--search-input-start-padding);
1235
- padding-inline-end: var(--search-input-end-padding);
1235
+ padding-inline-end: max(
1236
+ var(--search-input-end-padding),
1237
+ var(--search-input-affix-min-padding, 0px)
1238
+ );
1236
1239
  }
1237
1240
 
1238
1241
  .ui-search-input-clear {
@@ -1263,8 +1266,8 @@
1263
1266
  .ui-search-input-clear::after {
1264
1267
  content: "";
1265
1268
  position: absolute;
1266
- inset-block: min(0px, calc((100% - var(--control-height-default)) / 2));
1267
- inset-inline: min(0px, calc((100% - var(--control-height-default)) / 2));
1269
+ inset-block: min(0px, calc((100% - var(--control-affix-touch-target-size)) / 2));
1270
+ inset-inline: min(0px, calc((100% - var(--control-affix-touch-target-size)) / 2));
1268
1271
  }
1269
1272
  }
1270
1273
 
@@ -3767,3 +3770,128 @@
3767
3770
  min-inline-size: var(--control-height-touch);
3768
3771
  }
3769
3772
  }
3773
+
3774
+ @layer components {
3775
+ @media (pointer: coarse) {
3776
+ .ui-control-inline-affix-action:not(.ui-badge-remove),
3777
+ .ui-control-affix-action,
3778
+ .ui-tree-select-clear,
3779
+ .ui-password-input-toggle,
3780
+ .ui-search-input-clear,
3781
+ .ui-number-input-step {
3782
+ min-inline-size: var(--control-affix-touch-target-size);
3783
+ min-block-size: var(--control-affix-touch-target-size);
3784
+ flex-shrink: 0;
3785
+ }
3786
+
3787
+ .ui-control-inline-affix-action:not(.ui-badge-remove)::after {
3788
+ inset: 0;
3789
+ translate: none;
3790
+ }
3791
+
3792
+ .ui-input-affix-wrapper:has(.ui-control-inline-affix-action) > .ui-input {
3793
+ min-block-size: var(--control-affix-touch-target-size);
3794
+ }
3795
+
3796
+ .ui-input--trailing-affix {
3797
+ padding-inline-end: max(
3798
+ var(--control-inline-affix-space-inline-end),
3799
+ calc(
3800
+ var(--input-trailing-measured-size, var(--control-affix-touch-target-size)) +
3801
+ var(--control-inline-affix-inset-inline) + var(--control-inline-affix-space-gap)
3802
+ )
3803
+ );
3804
+ }
3805
+
3806
+ .ui-control-trigger-affixed {
3807
+ min-block-size: var(--control-affix-touch-target-size);
3808
+ padding-inline-end: max(
3809
+ var(--control-trigger-space-inline-end),
3810
+ calc(
3811
+ var(--control-affix-touch-target-size) + var(--control-affix-inset-inline-end) +
3812
+ var(--control-inline-affix-space-gap)
3813
+ )
3814
+ );
3815
+ }
3816
+
3817
+ .ui-tree-select-trigger-affixed {
3818
+ min-block-size: var(--control-affix-touch-target-size);
3819
+ padding-inline-end: calc(
3820
+ var(--control-affix-touch-target-size) + var(--space-3) +
3821
+ var(--control-inline-affix-space-gap)
3822
+ );
3823
+ }
3824
+
3825
+ .ui-password-input-field {
3826
+ min-block-size: var(--control-affix-touch-target-size);
3827
+ padding-inline-end: calc(
3828
+ var(--control-affix-touch-target-size) + var(--control-inline-affix-space-gap)
3829
+ );
3830
+ }
3831
+
3832
+ .ui-number-input-steppers {
3833
+ flex-direction: row;
3834
+ align-items: center;
3835
+ inset-block: var(--control-border-width, var(--stroke-hairline));
3836
+ }
3837
+
3838
+ .ui-number-input-step {
3839
+ border: 0;
3840
+ background: transparent;
3841
+ box-shadow: none;
3842
+ }
3843
+ .ui-number-input-step:hover {
3844
+ background: hsl(var(--accent));
3845
+ color: hsl(var(--accent-foreground));
3846
+ }
3847
+ .ui-number-input-step::after {
3848
+ inset-block: calc((100% - var(--control-affix-touch-target-size)) / 2) auto;
3849
+ inset-inline: calc((100% - var(--control-affix-touch-target-size)) / 2) auto;
3850
+ inline-size: var(--control-affix-touch-target-size);
3851
+ block-size: var(--control-affix-touch-target-size);
3852
+ translate: none;
3853
+ }
3854
+ .ui-search-input-control {
3855
+ min-block-size: var(--control-affix-touch-target-size);
3856
+
3857
+ --search-input-affix-min-padding: max(
3858
+ var(--control-affix-touch-target-size),
3859
+ calc(
3860
+ var(--search-input-edge-inset) + var(--touch-target-min) / 2 +
3861
+ var(--control-affix-touch-target-size) / 2
3862
+ )
3863
+ );
3864
+ }
3865
+ [data-slot="textarea-affix-wrapper"]:has(.ui-textarea-clear) > textarea {
3866
+ min-block-size: calc(
3867
+ var(--control-affix-touch-target-size) + 2 * var(--textarea-clear-inset-block-start)
3868
+ );
3869
+ }
3870
+ .ui-search-input-clear {
3871
+ inset-inline-end: max(
3872
+ 0px,
3873
+ calc(
3874
+ var(--search-input-edge-inset) + var(--touch-target-min) / 2 -
3875
+ var(--control-affix-touch-target-size) / 2
3876
+ )
3877
+ );
3878
+ }
3879
+ .ui-number-input-field {
3880
+ min-block-size: calc(
3881
+ var(--control-affix-touch-target-size) + 2 *
3882
+ var(--control-border-width, var(--stroke-hairline))
3883
+ );
3884
+ padding-inline-end: calc(2 * var(--control-affix-touch-target-size) + var(--space-2));
3885
+ }
3886
+ .ui-number-input:not([data-controls="off"]) [data-slot="number-input-suffix"] {
3887
+ inset-inline-end: calc(2 * var(--control-affix-touch-target-size) + var(--space-2));
3888
+ }
3889
+ .ui-number-input:not([data-controls="off"]):has([data-slot="number-input-suffix"])
3890
+ .ui-number-input-field {
3891
+ padding-inline-end: calc(
3892
+ 2 * var(--control-affix-touch-target-size) + var(--space-2) +
3893
+ var(--control-inline-affix-size)
3894
+ );
3895
+ }
3896
+ }
3897
+ }
@@ -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,20 @@
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));
74
104
 
75
105
  transition:
76
106
  outline-offset 0s,
77
107
  outline 0s;
78
108
  }
79
109
 
80
- :root[data-focus-outline="on"]
110
+ :root:not([data-focus-outline="off"])
81
111
  :is(
82
112
  .ui-focus-ring,
83
113
  .ui-focus-ring-outline,
114
+ .ui-logo,
115
+ .ui-logo-lockup,
116
+ [data-slot="anchor-link"],
84
117
  .ui-input,
85
118
  .ui-control,
86
119
  .ui-control-multiline,
@@ -123,21 +156,22 @@
123
156
  .tb-icon-btn
124
157
  ):focus-visible,
125
158
 
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"]
159
+ :root:not([data-focus-outline="off"]) .ui-checkbox[data-focus-visible],
160
+ :root:not([data-focus-outline="off"]) .ui-radio[data-focus-visible],
161
+ :root:not([data-focus-outline="off"]) .ui-switch[data-focus-visible],
162
+ :root:not([data-focus-outline="off"]) .ui-slider-thumb[data-focus-visible],
163
+ :root:not([data-focus-outline="off"]) .ui-focus-ring[data-focus-visible],
164
+ :root:not([data-focus-outline="off"]) .ui-control-trigger[data-state="open"],
165
+ :root:not([data-focus-outline="off"]) .ui-tag-input:focus-within,
166
+ :root:not([data-focus-outline="off"]) .ui-control-composite-field:focus-within,
167
+ :root:not([data-focus-outline="off"]) .ui-control-composite-field[data-state="open"],
168
+ :root:not([data-focus-outline="off"]) .ui-otp-slot[data-active="true"],
169
+ :root:not([data-focus-outline="off"])
137
170
  .ui-otp-group[data-appearance="grouped"]:has(.ui-otp-slot[data-active="true"]) {
138
171
 
139
172
  --tw-ring-shadow: var(
140
173
  --focus-field-shadow,
174
+ inset 0 0 0 calc(-1 * var(--focus-ring-contained-offset, 0px)) hsl(var(--background)),
141
175
  0 0 0 var(--focus-ring-glow-width)
142
176
  hsl(
143
177
  var(
@@ -149,6 +183,7 @@
149
183
  );
150
184
  box-shadow: var(
151
185
  --focus-field-shadow,
186
+ inset 0 0 0 calc(-1 * var(--focus-ring-contained-offset, 0px)) hsl(var(--background)),
152
187
  0 0 0 var(--focus-ring-glow-width)
153
188
  hsl(
154
189
  var(
@@ -160,7 +195,7 @@
160
195
  );
161
196
  }
162
197
 
163
- :root[data-focus-outline="on"]
198
+ :root:not([data-focus-outline="off"])
164
199
  :is(
165
200
  .ui-input,
166
201
  .ui-control,
@@ -168,10 +203,10 @@
168
203
  .ui-control-trigger,
169
204
  .ui-tag-input
170
205
  ):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"] {
206
+ :root:not([data-focus-outline="off"]) .ui-control-trigger[data-state="open"],
207
+ :root:not([data-focus-outline="off"]) .ui-tag-input:focus-within,
208
+ :root:not([data-focus-outline="off"]) .ui-control-composite-field:focus-within,
209
+ :root:not([data-focus-outline="off"]) .ui-control-composite-field[data-state="open"] {
175
210
  --input: var(--focus-ring-color, var(--ring));
176
211
  }
177
212
 
@@ -191,10 +226,35 @@
191
226
 
192
227
  a.ui-list-row,
193
228
  tr.ui-focus-ring,
194
- .sb-nav-item {
229
+ .sb-nav-item,
230
+ .ui-app-setting-picker-icon[data-appearance="bar"],
231
+ .ui-legal-document-toc-link,
232
+ [data-slot="anchor-link"] {
195
233
  --focus-ring-offset: calc(-1 * var(--focus-ring-width));
196
234
  }
197
235
 
236
+ :is(
237
+ .ui-pagination,
238
+ .ui-pagination-list,
239
+ [data-slot="card"],
240
+ [data-slot="tabs-list"],
241
+ .ui-topbar,
242
+ .app-topbar,
243
+ .ui-anchor-list,
244
+ .app-main,
245
+ .ui-data-table-scroll,
246
+ .ui-scroll-area,
247
+ .ui-table-scroll,
248
+ .ui-mobile-shell,
249
+ .ui-resizable-panel-group,
250
+ .ui-legal-document-rail,
251
+ .ui-command-input-wrapper,
252
+ .ui-attachments
253
+ ) {
254
+ --focus-ring-contained-offset: var(--focus-ring-inset-offset);
255
+
256
+ }
257
+
198
258
  .ui-accordion-trigger {
199
259
  --focus-ring-offset: var(--accordion-focus-ring-offset);
200
260
  }
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "33.10.1",
2
+ "version": "33.12.1",
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",
@@ -647,7 +647,7 @@
647
647
  }
648
648
 
649
649
  .ui-resizable-handle {
650
- width: var(--control-border-width, var(--stroke-hairline));
650
+ inline-size: var(--resizable-handle-size);
651
651
  background: hsl(var(--border));
652
652
  position: relative;
653
653
  flex-shrink: 0;
@@ -657,9 +657,9 @@
657
657
  color: hsl(var(--muted-foreground));
658
658
  }
659
659
 
660
- .ui-resizable-handle[aria-orientation="vertical"] {
661
- width: auto;
662
- height: var(--control-border-width, var(--stroke-hairline));
660
+ .ui-resizable-handle[aria-orientation="horizontal"] {
661
+ inline-size: auto;
662
+ block-size: var(--resizable-handle-size);
663
663
  }
664
664
 
665
665
  .ui-resizable-handle:hover,