@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.
- package/README.md +4 -4
- package/dist/components/data-display/scroll-area.d.ts +24 -4
- package/dist/components/data-display/scroll-area.js +34 -72
- package/dist/components/data-entry/cascader.js +8 -6
- package/dist/components/data-entry/checkbox.js +2 -0
- package/dist/components/data-entry/choice-hit-target.d.ts +24 -0
- package/dist/components/data-entry/choice-hit-target.js +12 -0
- package/dist/components/data-entry/control-surface.d.ts +8 -0
- package/dist/components/data-entry/control-surface.js +10 -1
- package/dist/components/data-entry/form-field.js +3 -2
- package/dist/components/data-entry/radio.js +3 -2
- package/dist/components/data-entry/search-select.js +225 -95
- package/dist/components/data-entry/select.d.ts +76 -14
- package/dist/components/data-entry/select.js +495 -163
- package/dist/components/data-entry/slider.d.ts +3 -11
- package/dist/components/data-entry/slider.js +582 -93
- package/dist/components/data-entry/switch.js +2 -0
- package/dist/components/navigation/dropdown-menu.d.ts +11 -3
- package/dist/components/navigation/dropdown-menu.js +190 -15
- package/dist/components/navigation/index.d.ts +0 -3
- package/dist/components/navigation/index.js +0 -78
- package/dist/components/ui/index.d.ts +0 -3
- package/dist/components/ui/index.js +0 -3
- package/dist/components/ui/input-otp.d.ts +1 -1
- package/dist/components/ui/segmented.js +3 -1
- package/dist/components/ui/tag-input.js +3 -5
- package/dist/lib/select-options.d.ts +21 -0
- package/dist/lib/select-options.js +46 -0
- package/dist/props/components/data-display.prop.d.ts +9 -6
- package/dist/props/components/data-entry.prop.d.ts +323 -30
- package/dist/props/components/index.d.ts +1 -1
- package/dist/props/components/navigation.prop.d.ts +17 -0
- package/dist/props/registry.d.ts +63 -0
- package/dist/props/registry.js +69 -0
- package/dist/styles/control.css +205 -23
- package/dist/styles/data-display-layout.css +18 -10
- package/dist/styles/focus-ring.css +2 -2
- package/dist/styles/fonts.css +88 -4
- package/dist/styles/navigation-layout.css +11 -236
- package/dist/styles/shell-layout.css +144 -139
- package/dist/styles/table-layout.css +7 -3
- package/dist/tokens/components/control.css +4 -0
- package/dist/tokens/components/data-display.css +0 -5
- package/dist/tokens/components/navigation.css +3 -5
- package/dist/tokens/components/scroll-area.css +7 -0
- package/docs/CUSTOMER-THEMING.md +7 -1
- package/docs/FRAME-COVERAGE-REPORT.md +4 -7
- package/docs/data-display/data-table/index.tsx +27 -1
- package/docs/data-display/popover.tsx +4 -6
- package/docs/data-display/scroll-area.tsx +51 -45
- package/docs/data-entry/date-picker.tsx +2 -3
- package/docs/data-entry/input-otp.tsx +5 -2
- package/docs/data-entry/segmented.tsx +40 -1
- package/docs/data-entry/select-matrix.tsx +1 -2
- package/docs/data-entry/select.tsx +108 -3
- package/docs/data-entry/slider.tsx +125 -47
- package/docs/feedback/dialog.tsx +1 -2
- package/docs/feedback/sheet.tsx +1 -2
- package/docs/layout/app-shell-states.tsx +143 -0
- package/docs/navigation/dropdown-menu.tsx +75 -4
- package/docs/roadmap/parity-audit-data-entry.md +44 -33
- package/docs/roadmap/parity-audit-layout-navigation-general.md +304 -310
- package/docs/roadmap/parity-backlog.md +2 -3
- package/package.json +20 -44
- package/scripts/ui-audit.mjs +1 -1
- package/scripts/visual-audit-rules.mjs +0 -7
- package/scripts/visual-audit.mjs +6 -27
- package/dist/components/navigation/context-menu.d.ts +0 -21
- package/dist/components/navigation/context-menu.js +0 -149
- package/dist/components/navigation/menubar.d.ts +0 -28
- package/dist/components/navigation/menubar.js +0 -136
- package/dist/components/navigation/navigation-menu.d.ts +0 -10
- package/dist/components/navigation/navigation-menu.js +0 -91
- package/dist/components/ui/context-menu.d.ts +0 -1
- package/dist/components/ui/context-menu.js +0 -2
- package/dist/components/ui/menubar.d.ts +0 -1
- package/dist/components/ui/menubar.js +0 -2
- package/dist/components/ui/navigation-menu.d.ts +0 -1
- package/dist/components/ui/navigation-menu.js +0 -2
- package/docs/FRAME-A11Y-CI.md +0 -349
- package/docs/navigation/context-menu.tsx +0 -128
- package/docs/navigation/menubar.tsx +0 -141
- 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`, `
|
|
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
|
|
158
|
+
### Runtime visual audit (Playwright)
|
|
159
159
|
|
|
160
|
-
`scripts/visual-audit.mjs` drives a **real browser** over a running app and runs
|
|
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)
|
|
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<
|
|
5
|
-
export declare const ScrollArea: React.ForwardRefExoticComponent<ScrollAreaProp & Omit<Omit<
|
|
6
|
-
|
|
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
|
|
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
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
),
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
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 =
|
|
190
|
-
|
|
191
|
-
|
|
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
|
|
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 = () =>
|
|
315
|
-
|
|
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
|
-
|
|
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:
|
|
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(
|
|
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
|
);
|