@godxjp/ui 22.0.0 → 23.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/README.md +4 -4
  2. package/dist/components/data-display/scroll-area.d.ts +24 -4
  3. package/dist/components/data-display/scroll-area.js +34 -72
  4. package/dist/components/data-entry/cascader.js +8 -6
  5. package/dist/components/data-entry/checkbox.js +2 -0
  6. package/dist/components/data-entry/choice-hit-target.d.ts +24 -0
  7. package/dist/components/data-entry/choice-hit-target.js +12 -0
  8. package/dist/components/data-entry/control-surface.d.ts +8 -0
  9. package/dist/components/data-entry/control-surface.js +10 -1
  10. package/dist/components/data-entry/form-field.js +3 -2
  11. package/dist/components/data-entry/radio.js +3 -2
  12. package/dist/components/data-entry/search-select.js +225 -95
  13. package/dist/components/data-entry/select.d.ts +76 -14
  14. package/dist/components/data-entry/select.js +495 -163
  15. package/dist/components/data-entry/slider.d.ts +3 -11
  16. package/dist/components/data-entry/slider.js +582 -93
  17. package/dist/components/data-entry/switch.js +2 -0
  18. package/dist/components/navigation/dropdown-menu.d.ts +11 -3
  19. package/dist/components/navigation/dropdown-menu.js +190 -15
  20. package/dist/components/navigation/index.d.ts +0 -3
  21. package/dist/components/navigation/index.js +0 -78
  22. package/dist/components/ui/index.d.ts +0 -3
  23. package/dist/components/ui/index.js +0 -3
  24. package/dist/components/ui/input-otp.d.ts +1 -1
  25. package/dist/components/ui/segmented.js +3 -1
  26. package/dist/components/ui/tag-input.js +3 -5
  27. package/dist/lib/select-options.d.ts +21 -0
  28. package/dist/lib/select-options.js +46 -0
  29. package/dist/props/components/data-display.prop.d.ts +9 -6
  30. package/dist/props/components/data-entry.prop.d.ts +323 -30
  31. package/dist/props/components/index.d.ts +1 -1
  32. package/dist/props/components/navigation.prop.d.ts +17 -0
  33. package/dist/props/registry.d.ts +63 -0
  34. package/dist/props/registry.js +69 -0
  35. package/dist/styles/control.css +205 -23
  36. package/dist/styles/data-display-layout.css +18 -10
  37. package/dist/styles/focus-ring.css +2 -2
  38. package/dist/styles/fonts.css +88 -4
  39. package/dist/styles/navigation-layout.css +11 -236
  40. package/dist/styles/shell-layout.css +144 -139
  41. package/dist/styles/table-layout.css +7 -3
  42. package/dist/tokens/components/control.css +4 -0
  43. package/dist/tokens/components/data-display.css +0 -5
  44. package/dist/tokens/components/navigation.css +3 -5
  45. package/dist/tokens/components/scroll-area.css +7 -0
  46. package/docs/CUSTOMER-THEMING.md +7 -1
  47. package/docs/FRAME-COVERAGE-REPORT.md +4 -7
  48. package/docs/data-display/data-table/index.tsx +27 -1
  49. package/docs/data-display/popover.tsx +4 -6
  50. package/docs/data-display/scroll-area.tsx +51 -45
  51. package/docs/data-entry/date-picker.tsx +2 -3
  52. package/docs/data-entry/input-otp.tsx +5 -2
  53. package/docs/data-entry/segmented.tsx +40 -1
  54. package/docs/data-entry/select-matrix.tsx +1 -2
  55. package/docs/data-entry/select.tsx +108 -3
  56. package/docs/data-entry/slider.tsx +125 -47
  57. package/docs/feedback/dialog.tsx +1 -2
  58. package/docs/feedback/sheet.tsx +1 -2
  59. package/docs/layout/app-shell-states.tsx +143 -0
  60. package/docs/navigation/dropdown-menu.tsx +75 -4
  61. package/docs/roadmap/parity-audit-data-entry.md +44 -33
  62. package/docs/roadmap/parity-audit-layout-navigation-general.md +304 -310
  63. package/docs/roadmap/parity-backlog.md +2 -3
  64. package/package.json +20 -44
  65. package/scripts/ui-audit.mjs +1 -1
  66. package/scripts/visual-audit-rules.mjs +0 -7
  67. package/scripts/visual-audit.mjs +6 -27
  68. package/dist/components/navigation/context-menu.d.ts +0 -21
  69. package/dist/components/navigation/context-menu.js +0 -149
  70. package/dist/components/navigation/menubar.d.ts +0 -28
  71. package/dist/components/navigation/menubar.js +0 -136
  72. package/dist/components/navigation/navigation-menu.d.ts +0 -10
  73. package/dist/components/navigation/navigation-menu.js +0 -91
  74. package/dist/components/ui/context-menu.d.ts +0 -1
  75. package/dist/components/ui/context-menu.js +0 -2
  76. package/dist/components/ui/menubar.d.ts +0 -1
  77. package/dist/components/ui/menubar.js +0 -2
  78. package/dist/components/ui/navigation-menu.d.ts +0 -1
  79. package/dist/components/ui/navigation-menu.js +0 -2
  80. package/docs/FRAME-A11Y-CI.md +0 -349
  81. package/docs/navigation/context-menu.tsx +0 -128
  82. package/docs/navigation/menubar.tsx +0 -141
  83. package/docs/navigation/navigation-menu.tsx +0 -158
package/README.md CHANGED
@@ -55,7 +55,7 @@ A value is defined **once** as a CSS var (`--primary`), mapped to a utility in t
55
55
  | **Data Display** | `@godxjp/ui/data-display` | `Table`, `DataTable`, `Card`, `StatCard`, `Badge`, `Avatar`, `Descriptions`, `Timeline`, `EmptyState`, `Progress`, `QrCode`, `CredentialReveal`, `Accordion`, `HoverCard`, `Carousel`, `Popover`, `Collapsible` |
56
56
  | **Feedback** | `@godxjp/ui/feedback` | `Dialog`, `AlertDialog`, `Sheet` (side), `Drawer` (bottom-sheet), `Toast`, `Skeleton`, `Alert`, `Tooltip` |
57
57
  | **Query** | `@godxjp/ui/query` | `DataState`, `InfiniteQueryState`, `PrefetchLink` (adapter subpath — pulls TanStack Query) |
58
- | **Navigation** | `@godxjp/ui/navigation` | `Tabs`, `Toolbar`, `DropdownMenu`, `ContextMenu`, `Menubar`, `NavigationMenu`, `Steps`, `Pagination`, `Breadcrumb`, `AppSettingPicker` |
58
+ | **Navigation** | `@godxjp/ui/navigation` | `Tabs`, `Toolbar`, `DropdownMenu`, `Steps`, `Pagination`, `Breadcrumb`, `AppSettingPicker` |
59
59
  | **App** | `@godxjp/ui/app` | `AppProvider`, `useDateTime` (adapter — i18n/datetime singleton) |
60
60
  | **Datetime** | `@godxjp/ui/datetime` | `formatDate` (mandatory for display) |
61
61
  | **Form** | `@godxjp/ui/form` | `useZodForm`, `FormRoot` (adapter subpath — pulls react-hook-form) |
@@ -155,9 +155,9 @@ pnpm release --ui <patch|minor|major> --mcp <…|skip> # publish lib + MCP in
155
155
  | `check:mcp-orphans` | every public component HAS a catalog entry (catalog can't rot) |
156
156
  | `check:core-isolation` | the root export pulls no foreign runtime (adapters stay on subpaths) |
157
157
 
158
- ### Runtime visual audit (Playwright + axe-core)
158
+ ### Runtime visual audit (Playwright)
159
159
 
160
- `scripts/visual-audit.mjs` drives a **real browser** over a running app and runs axe-core plus computed-style heuristics (target size, OKLCH accent chroma, rendered emoji, mis-laid-out alerts) — catching what the static `pnpm audit` (source regexes) can't see. Playwright + `@axe-core/playwright` are **optional peers**, installed only by apps that run the audit.
160
+ `scripts/visual-audit.mjs` drives a **real browser** over a running app and runs computed-style heuristics (target size, OKLCH accent chroma, rendered emoji, mis-laid-out alerts) — catching what the static `pnpm audit` (source regexes) can't see. Playwright + `@axe-core/playwright` are **optional peers**, installed only by apps that run the audit.
161
161
 
162
162
  ```bash
163
163
  # from a consumer, against its running dev/preview server:
@@ -166,7 +166,7 @@ node node_modules/@godxjp/ui/scripts/visual-audit.mjs http://localhost:5173 --fo
166
166
  node node_modules/@godxjp/ui/scripts/visual-audit.mjs --strict http://localhost:5173 # CI gate
167
167
  ```
168
168
 
169
- **Tested peer range** (pin one of these): `playwright >=1.55 <2` (tested 1.61.1) · `@axe-core/playwright >=4.10 <5` (tested 4.12.1) · `axe-core >=4.10 <5` (tested 4.12.1). Playwright 1.55+ is required for the `browser.newContext()` → `context.newPage()` flow axe expects; older `browser.newPage()` throws _"Please use browser.newContext()"_.
169
+ **Tested peer range** (pin one of these): `playwright >=1.55 <2` (tested 1.61.1)
170
170
 
171
171
  `--format json` **always** emits valid JSON — even on bootstrap failure — with a `status` (`ok` · `partial` · `error`) that separates **infrastructure errors** (missing peers, page won't load, axe won't inject → `errors[]`, `summary: null`/flagged) from **product findings** (`findings[]`). A tool failure can therefore never be misread as "zero violations". `pnpm check:visual-audit` is the CI smoke test: it serves a fixture page tripping all five rule families and asserts each one fires.
172
172
 
@@ -1,6 +1,26 @@
1
1
  import * as React from "react";
2
- import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area";
3
2
  import type { ScrollAreaProp } from "../../props/components/data-display.prop.js";
4
- export type ScrollAreaProps = ScrollAreaProp & Omit<React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>, keyof ScrollAreaProp>;
5
- export declare const ScrollArea: React.ForwardRefExoticComponent<ScrollAreaProp & Omit<Omit<ScrollAreaPrimitive.ScrollAreaProps & React.RefAttributes<HTMLDivElement>, "ref">, keyof ScrollAreaProp> & React.RefAttributes<HTMLDivElement>>;
6
- export declare const ScrollBar: React.ForwardRefExoticComponent<Omit<ScrollAreaPrimitive.ScrollAreaScrollbarProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
3
+ export type ScrollAreaProps = ScrollAreaProp & Omit<React.ComponentPropsWithoutRef<"div">, keyof ScrollAreaProp>;
4
+ export declare const ScrollArea: React.ForwardRefExoticComponent<ScrollAreaProp & Omit<Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref">, keyof ScrollAreaProp> & React.RefAttributes<HTMLDivElement>>;
5
+ /**
6
+ * Deliberately NOT exported: a deprecated no-op must not grow the package's public type surface.
7
+ * Nothing was exported for it before either — it used to be typed straight off Radix.
8
+ */
9
+ type ScrollBarProps = {
10
+ orientation?: "vertical" | "horizontal";
11
+ className?: string;
12
+ };
13
+ /**
14
+ * @deprecated Renders nothing. Native scrolling has no separate bar element to mount: the browser
15
+ * draws the scrollbar for whichever axis overflows, and WHICH axes may overflow is
16
+ * `<ScrollArea orientation="vertical | horizontal | both">`.
17
+ *
18
+ * Under Radix, mounting a `ScrollBar` was what ENABLED its axis (Radix read `scrollbarXEnabled` /
19
+ * `scrollbarYEnabled` from these children and wrote the viewport's inline `overflowX`/`overflowY`
20
+ * from them), so `<ScrollBar orientation="horizontal" />` was load-bearing rather than decorative.
21
+ * It is kept as an inert export so existing call sites keep compiling; move the intent to
22
+ * `orientation` — `<ScrollArea orientation="both">` is the replacement for a vertical area that
23
+ * also mounted a horizontal bar.
24
+ */
25
+ export declare function ScrollBar(_props: ScrollBarProps): null;
26
+ export {};
@@ -1,8 +1,6 @@
1
1
  "use client";
2
- import { jsx, jsxs } from "react/jsx-runtime";
3
- import { useLayoutEffect } from "@react-aria/utils";
2
+ import { jsx } from "react/jsx-runtime";
4
3
  import * as React from "react";
5
- import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area";
6
4
  import { cn } from "../../lib/utils.js";
7
5
  const ANCHOR_OFFSET_TOKEN = "--scroll-area-anchor-offset";
8
6
  const ANCHOR_OFFSET_FALLBACK_PX = 48;
@@ -113,18 +111,6 @@ function useBottomAnchor(viewport, enabled, offset, onAnchoredChange) {
113
111
  };
114
112
  }, [viewport, enabled, offset]);
115
113
  }
116
- function useAmbientDirection(root, provided) {
117
- const [ambient, setAmbient] = React.useState(void 0);
118
- useLayoutEffect(() => {
119
- if (provided !== void 0) return;
120
- const parent = root?.parentElement;
121
- if (!parent || typeof window === "undefined" || typeof window.getComputedStyle !== "function") {
122
- return;
123
- }
124
- setAmbient(window.getComputedStyle(parent).direction === "rtl" ? "rtl" : "ltr");
125
- }, [root, provided]);
126
- return provided ?? ambient;
127
- }
128
114
  const ScrollArea = React.forwardRef(
129
115
  ({
130
116
  className,
@@ -134,75 +120,51 @@ const ScrollArea = React.forwardRef(
134
120
  anchorOffset,
135
121
  onAnchoredChange,
136
122
  orientation = "vertical",
137
- dir,
138
123
  ...props
139
124
  }, ref) => {
140
125
  const [viewport, setViewport] = React.useState(null);
141
- const [root, setRoot] = React.useState(null);
142
- const attachRoot = React.useCallback(
143
- (node) => {
144
- setRoot(node);
145
- if (typeof ref === "function") ref(node);
146
- else if (ref) ref.current = node;
147
- },
148
- [ref]
149
- );
150
- const direction = useAmbientDirection(root, dir);
151
126
  const attachViewport = React.useCallback(
152
127
  (node) => {
153
128
  setViewport(node);
154
- if (typeof viewportRef === "function") viewportRef(node);
155
- else if (viewportRef)
156
- viewportRef.current = node;
129
+ for (const target of [ref, viewportRef]) {
130
+ if (typeof target === "function") target(node);
131
+ else if (target) target.current = node;
132
+ }
157
133
  },
158
- [viewportRef]
134
+ [ref, viewportRef]
159
135
  );
160
136
  useBottomAnchor(viewport, anchor === "bottom", anchorOffset, onAnchoredChange);
161
- return /* @__PURE__ */ jsxs(
162
- ScrollAreaPrimitive.Root,
163
- {
164
- ref: attachRoot,
165
- dir: direction,
166
- className: cn("relative overflow-hidden", className),
167
- ...props,
168
- children: [
169
- /* @__PURE__ */ jsx(
170
- ScrollAreaPrimitive.Viewport,
171
- {
172
- ref: attachViewport,
173
- tabIndex: 0,
174
- "data-slot": "scroll-area-viewport",
175
- "data-anchor": anchor,
176
- "data-orientation": orientation,
177
- className: "size-full rounded-[inherit]",
178
- children
179
- }
180
- ),
181
- orientation !== "horizontal" && /* @__PURE__ */ jsx(ScrollBar, { orientation: "vertical" }),
182
- orientation !== "vertical" && /* @__PURE__ */ jsx(ScrollBar, { orientation: "horizontal" }),
183
- /* @__PURE__ */ jsx(ScrollAreaPrimitive.Corner, {})
184
- ]
185
- }
137
+ return (
138
+ // `tabIndex={0}` keeps the scroll viewport keyboard-reachable so overflowing content can be
139
+ // scrolled without a pointer (WCAG 2.1.1 / axe scrollable-region-focusable).
140
+ //
141
+ // NO `dir` IS STAMPED HERE, deliberately. Radix's Root called `useDirection(dir)`, which
142
+ // falls back to the literal "ltr" and writes it onto the element — and a `dir` attribute is
143
+ // not advisory: it resets the inline axis for the whole subtree, so every `margin-inline-*`,
144
+ // `text-align: start` and `align-items: flex-end` inside resolved LTR on an RTL page
145
+ // (measured in Chromium: a chat feed inside `dir="rtl"` kept its own bubbles on the wrong
146
+ // side). A plain element inherits the page's direction, which is the correct answer and
147
+ // needs no code. A caller may still pass `dir` explicitly; it rides through with the rest.
148
+ /* @__PURE__ */ jsx(
149
+ "div",
150
+ {
151
+ ref: attachViewport,
152
+ tabIndex: 0,
153
+ "data-slot": "scroll-area-viewport",
154
+ "data-anchor": anchor,
155
+ "data-orientation": orientation,
156
+ className: cn("ui-scroll-area", className),
157
+ ...props,
158
+ children: /* @__PURE__ */ jsx("div", { "data-slot": "scroll-area-content", className: "ui-scroll-area-content", children })
159
+ }
160
+ )
186
161
  );
187
162
  }
188
163
  );
189
- ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName;
190
- const ScrollBar = React.forwardRef(({ className, orientation = "vertical", ...props }, ref) => /* @__PURE__ */ jsx(
191
- ScrollAreaPrimitive.ScrollAreaScrollbar,
192
- {
193
- ref,
194
- orientation,
195
- className: cn(
196
- "ui-scroll-area-bar flex touch-none transition-colors select-none",
197
- orientation === "vertical" && "ui-scroll-area-bar--vertical h-full border-s border-s-transparent",
198
- orientation === "horizontal" && "ui-scroll-area-bar--horizontal flex-col border-t border-t-transparent",
199
- className
200
- ),
201
- ...props,
202
- children: /* @__PURE__ */ jsx(ScrollAreaPrimitive.ScrollAreaThumb, { className: "ui-scroll-area-thumb bg-border relative flex-1" })
203
- }
204
- ));
205
- ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName;
164
+ ScrollArea.displayName = "ScrollArea";
165
+ function ScrollBar(_props) {
166
+ return null;
167
+ }
206
168
  export {
207
169
  ScrollArea,
208
170
  ScrollBar
@@ -13,7 +13,7 @@ import {
13
13
  resolveAriaInvalid
14
14
  } from "./control-surface.js";
15
15
  import { Popover, PopoverContent, PopoverTrigger } from "../data-display/popover.js";
16
- import { ScrollArea, ScrollBar } from "../data-display/scroll-area.js";
16
+ import { ScrollArea } from "../data-display/scroll-area.js";
17
17
  import { Command, CommandInput } from "./command.js";
18
18
  import {
19
19
  filterTreeOptions,
@@ -311,8 +311,11 @@ function Cascader({
311
311
  if (multiple) commitMulti([]);
312
312
  else commitSingle([]);
313
313
  };
314
- const renderCascadeColumns = () => /* @__PURE__ */ jsxs(ScrollArea, { className: "w-full", children: [
315
- /* @__PURE__ */ jsx("div", { className: "ui-cascader-columns", children: columns.map((col, colIndex) => /* @__PURE__ */ jsx(
314
+ const renderCascadeColumns = () => (
315
+ // `both`: the columns strip overflows sideways, and a tall column scrolls down. Under Radix
316
+ // this was a vertical area plus a mounted `<ScrollBar orientation="horizontal" />`, which was
317
+ // how Radix was told to open the second axis at all.
318
+ /* @__PURE__ */ jsx(ScrollArea, { className: "w-full", orientation: "both", children: /* @__PURE__ */ jsx("div", { className: "ui-cascader-columns", children: columns.map((col, colIndex) => /* @__PURE__ */ jsx(
316
319
  "ul",
317
320
  {
318
321
  role: "listbox",
@@ -371,9 +374,8 @@ function Cascader({
371
374
  })
372
375
  },
373
376
  colIndex
374
- )) }),
375
- /* @__PURE__ */ jsx(ScrollBar, { orientation: "horizontal" })
376
- ] });
377
+ )) }) })
378
+ );
377
379
  const clearControl = resolveAllowClear(allowClear, true, t("dataEntry.cascader.clear"));
378
380
  const showClear = clearControl.enabled && hasValue && !disabled && !readOnly && !loading;
379
381
  const surface = controlSurfaceAttrs({ variant, status, size });
@@ -6,6 +6,7 @@ import { Check, Minus } from "lucide-react";
6
6
  import { cn } from "../../lib/utils.js";
7
7
  import { useFieldIdentity } from "../../lib/field-a11y.js";
8
8
  import { CheckboxGroup } from "./checkbox-group.js";
9
+ import { withOwnHitTarget } from "./choice-hit-target.js";
9
10
  function CheckboxGlyph({ state }) {
10
11
  return state === "indeterminate" ? /* @__PURE__ */ jsx(Minus, { className: "ui-checkbox-icon", "aria-hidden": "true" }) : /* @__PURE__ */ jsx(Check, { className: "ui-checkbox-icon", "aria-hidden": "true" });
11
12
  }
@@ -96,6 +97,7 @@ const CheckboxRoot = React.forwardRef((props, ref) => {
96
97
  "peer ui-checkbox data-[invalid]:border-destructive data-[state=checked]:border-primary data-[state=checked]:text-primary-foreground inline-flex shrink-0 items-center justify-center shadow-xs transition-shadow outline-none",
97
98
  className
98
99
  ),
100
+ render: (domProps) => /* @__PURE__ */ jsx("label", { ...domProps, children: withOwnHitTarget(domProps.children) }),
99
101
  onChange: (next) => {
100
102
  setUncontrolled(next);
101
103
  onCheckedChange?.(next);
@@ -0,0 +1,24 @@
1
+ import * as React from "react";
2
+ /**
3
+ * THE PAINTED BOX OF A CHOICE CONTROL IS ITS OWN HIT TARGET (gh#476).
4
+ *
5
+ * react-aria-components renders the real `<input>` inside a `VisuallyHidden` span, and that span
6
+ * carries its geometry as an INLINE style: `position:absolute; width:1px; height:1px;
7
+ * clip:rect(0 0 0 0); clip-path:inset(50%)`, pinned at the label's top-left. The thing a user
8
+ * sees and aims at is the `<label>` wrapped around it, so every pointer landing on the control
9
+ * hits the LABEL, and the input's own hit area is a 13x13 box at a different origin than the
10
+ * 16x16 one that is painted.
11
+ *
12
+ * Measured on `data-entry-checkbox` before this helper existed: of 49 points scanned across the
13
+ * box, **0** reached the input, 46 reached the label; Playwright `check()` / `uncheck()` without
14
+ * `force` timed out on `<label data-slot="checkbox"> intercepts pointer events`, the same check a
15
+ * browser applies to a real pointer. `force: true` passed, which is the tell: the control is
16
+ * functional, it is simply covered.
17
+ *
18
+ * An inline style can only be beaten with `!important`, so the WRAPPER is replaced instead of
19
+ * fought. The `render` prop hands over the DOM children react-aria built; the VisuallyHidden
20
+ * element among them becomes a `.ui-choice-input` span that control.css sizes to the painted box.
21
+ * The `<input>` itself — its props, its ref, its keyboard handling, its place in the a11y tree —
22
+ * is passed through untouched, so this is a geometry change and nothing else.
23
+ */
24
+ export declare function withOwnHitTarget(children: React.ReactNode): React.ReactNode;
@@ -0,0 +1,12 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import * as React from "react";
3
+ import { VisuallyHidden } from "react-aria-components";
4
+ function withOwnHitTarget(children) {
5
+ return React.Children.map(
6
+ children,
7
+ (child) => React.isValidElement(child) && child.type === VisuallyHidden ? /* @__PURE__ */ jsx("span", { "data-slot": "choice-input", className: "ui-choice-input", children: child.props.children }) : child
8
+ );
9
+ }
10
+ export {
11
+ withOwnHitTarget
12
+ };
@@ -59,3 +59,11 @@ export declare function applyMaxTagCount<T extends {
59
59
  omitted: T[];
60
60
  overflow: React.ReactNode | undefined;
61
61
  };
62
+ /**
63
+ * Split a run of text on antd's `tokenSeparators` — the contract behind "paste a column out of a
64
+ * spreadsheet and get one value per line".
65
+ *
66
+ * Shared by `TagInput` and the tags/multiple `Select`: they are the two controls that turn typing
67
+ * into several values, and two copies of this would be two answers to "what is a separator".
68
+ */
69
+ export declare function splitByTokenSeparators(text: string, separators: string[]): string[];
@@ -31,9 +31,18 @@ function applyMaxTagCount(items, maxTagCount, maxTagPlaceholder) {
31
31
  const overflow = typeof maxTagPlaceholder === "function" ? maxTagPlaceholder(omitted.map(({ value, label }) => ({ value, label }))) : maxTagPlaceholder;
32
32
  return { visible, omitted, overflow };
33
33
  }
34
+ function escapeForCharClass(character) {
35
+ return character.replace(/[\\\]^-]/g, "\\$&");
36
+ }
37
+ function splitByTokenSeparators(text, separators) {
38
+ if (!separators.length) return [text];
39
+ const pattern = new RegExp(`[${separators.map(escapeForCharClass).join("")}]`);
40
+ return text.split(pattern);
41
+ }
34
42
  export {
35
43
  applyMaxTagCount,
36
44
  controlSurfaceAttrs,
37
45
  resolveAllowClear,
38
- resolveAriaInvalid
46
+ resolveAriaInvalid,
47
+ splitByTokenSeparators
39
48
  };
@@ -69,6 +69,7 @@ function FormField({
69
69
  [fieldKey, emitFieldNames]
70
70
  );
71
71
  const childProps = React.isValidElement(children) ? children.props : void 0;
72
+ const controlId = childProps?.id ?? resolvedId;
72
73
  const mergeIds = mergeAriaIds;
73
74
  const childWithA11y = isStatic ? (
74
75
  // read-only FormField row and a Descriptions value are indistinguishable when mixed. That
@@ -78,7 +79,7 @@ function FormField({
78
79
  // The label is associated via aria-labelledby (not <label for>): composite
79
80
  // controls (Radio.Group, checkbox lists, range pairs) have no labelable root,
80
81
  // and a dangling `for` triggers Chrome's "Incorrect use of <label>" issue.
81
- id: childProps?.id ?? resolvedId,
82
+ id: controlId,
82
83
  ...form?.disabled && childProps?.disabled !== false ? { disabled: true } : {},
83
84
  ...validationStatus === "validating" ? { "aria-busy": true } : {},
84
85
  // Read the child's own value first in BOTH cases: cloneElement
@@ -133,7 +134,7 @@ function FormField({
133
134
  "span",
134
135
  {
135
136
  onClick: () => {
136
- const el = document.getElementById(resolvedId);
137
+ const el = document.getElementById(controlId);
137
138
  if (!(el instanceof HTMLElement)) return;
138
139
  const focusable = el.matches(FOCUSABLE_SELECTOR) ? el : el.querySelector(FOCUSABLE_SELECTOR);
139
140
  (focusable ?? el).focus();
@@ -7,6 +7,7 @@ import { cn } from "../../lib/utils.js";
7
7
  import { pickFieldA11y, useFieldIdentity, useMirroredInputAttributes } from "../../lib/field-a11y.js";
8
8
  import { Field } from "./field.js";
9
9
  import { choiceGroupClassName } from "./choice-option.js";
10
+ import { withOwnHitTarget } from "./choice-hit-target.js";
10
11
  function checkedState(isSelected) {
11
12
  return isSelected ? "checked" : "unchecked";
12
13
  }
@@ -65,7 +66,7 @@ const RadioItem = React.forwardRef(
65
66
  className
66
67
  ),
67
68
  ...props,
68
- render: (domProps, state) => /* @__PURE__ */ jsx("label", { ...domProps, "data-state": checkedState(state.isSelected) }),
69
+ render: (domProps, state) => /* @__PURE__ */ jsx("label", { ...domProps, "data-state": checkedState(state.isSelected), children: withOwnHitTarget(domProps.children) }),
69
70
  children: /* @__PURE__ */ jsx("span", { "data-slot": "radio-group-indicator", className: "ui-choice-indicator", children: /* @__PURE__ */ jsx(Circle, { className: "ui-radio-icon", "aria-hidden": "true" }) })
70
71
  }
71
72
  );
@@ -87,7 +88,7 @@ function RadioBarButton({
87
88
  isDisabled: option.disabled,
88
89
  "data-slot": "radio-button",
89
90
  className: "ui-radio-button ui-focus-ring",
90
- render: (domProps, state) => /* @__PURE__ */ jsx("label", { ...domProps, "data-state": checkedState(state.isSelected) }),
91
+ render: (domProps, state) => /* @__PURE__ */ jsx("label", { ...domProps, "data-state": checkedState(state.isSelected), children: withOwnHitTarget(domProps.children) }),
91
92
  children: /* @__PURE__ */ jsx("span", { className: "ui-radio-button-label", children: option.label })
92
93
  }
93
94
  );