@godxjp/ui 19.0.0 → 19.1.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/dist/app/app-provider.d.ts +1 -1
- package/dist/app/app-provider.js +3 -0
- package/dist/components/data-entry/cascader.js +3 -1
- package/dist/components/data-entry/checkbox-group.js +12 -5
- package/dist/components/data-entry/checkbox.js +30 -21
- package/dist/components/data-entry/date-picker.js +6 -2
- package/dist/components/data-entry/date-range-picker.js +9 -3
- package/dist/components/data-entry/form-field.d.ts +1 -1
- package/dist/components/data-entry/form-field.js +15 -2
- package/dist/components/data-entry/input.js +10 -3
- package/dist/components/data-entry/month-picker.js +4 -2
- package/dist/components/data-entry/month-range-picker.js +9 -3
- package/dist/components/data-entry/radio.js +19 -5
- package/dist/components/data-entry/search-select.d.ts +1 -1
- package/dist/components/data-entry/search-select.js +8 -2
- package/dist/components/data-entry/select.d.ts +8 -0
- package/dist/components/data-entry/select.js +53 -8
- package/dist/components/data-entry/switch.js +9 -1
- package/dist/components/data-entry/textarea.js +8 -1
- package/dist/components/data-entry/tree-select.js +3 -1
- package/dist/lib/field-a11y.d.ts +52 -0
- package/dist/lib/field-a11y.js +17 -2
- package/dist/props/components/app.prop.d.ts +15 -0
- package/dist/props/components/data-entry.prop.d.ts +18 -2
- package/package.json +5 -4
|
@@ -2,7 +2,7 @@ import * as React from "react";
|
|
|
2
2
|
import { type AppDateFormat, type AppLocale, type AppTimeFormat, type AppTimezone } from "./types.js";
|
|
3
3
|
import type { AppContextValue, AppProviderProp } from "../props/components/app.prop.js";
|
|
4
4
|
export type { AppProviderProp, AppContextValue } from "../props/components/app.prop.js";
|
|
5
|
-
export declare function AppProvider({ children, defaultLocale, fallbackLocale, defaultTimezone, systemTimezone, defaultTimeFormat, defaultDateFormat, timezoneOptions, storageKey, persist, theme: initialTheme, brand: initialBrand, density: initialDensity, fontSize: initialFontSize, scaling: initialScaling, onLocaleChange, onTimezoneChange, onTimeFormatChange, onDateFormatChange, onThemeChange, onBrandChange, onDensityChange, onFontSizeChange, onScalingChange, }: AppProviderProp): React.JSX.Element;
|
|
5
|
+
export declare function AppProvider({ children, defaultLocale, fallbackLocale, defaultTimezone, systemTimezone, defaultTimeFormat, defaultDateFormat, timezoneOptions, storageKey, persist, theme: initialTheme, brand: initialBrand, density: initialDensity, fontSize: initialFontSize, scaling: initialScaling, emitFieldNames, onLocaleChange, onTimezoneChange, onTimeFormatChange, onDateFormatChange, onThemeChange, onBrandChange, onDensityChange, onFontSizeChange, onScalingChange, }: AppProviderProp): React.JSX.Element;
|
|
6
6
|
export declare function useAppContext(): AppContextValue;
|
|
7
7
|
/** Returns null outside AppProvider — used by pickers for optional context. */
|
|
8
8
|
export declare function useOptionalAppContext(): AppContextValue | null;
|
package/dist/app/app-provider.js
CHANGED
|
@@ -65,6 +65,7 @@ function AppProvider({
|
|
|
65
65
|
density: initialDensity = "default",
|
|
66
66
|
fontSize: initialFontSize = "default",
|
|
67
67
|
scaling: initialScaling = null,
|
|
68
|
+
emitFieldNames = false,
|
|
68
69
|
onLocaleChange,
|
|
69
70
|
onTimezoneChange,
|
|
70
71
|
onTimeFormatChange,
|
|
@@ -299,6 +300,7 @@ function AppProvider({
|
|
|
299
300
|
density,
|
|
300
301
|
fontSize,
|
|
301
302
|
scaling,
|
|
303
|
+
emitFieldNames,
|
|
302
304
|
setLocale,
|
|
303
305
|
setTimezone,
|
|
304
306
|
setTimeFormat,
|
|
@@ -323,6 +325,7 @@ function AppProvider({
|
|
|
323
325
|
density,
|
|
324
326
|
fontSize,
|
|
325
327
|
scaling,
|
|
328
|
+
emitFieldNames,
|
|
326
329
|
setLocale,
|
|
327
330
|
setTimezone,
|
|
328
331
|
setTimeFormat,
|
|
@@ -4,7 +4,7 @@ import * as React from "react";
|
|
|
4
4
|
import { Check, ChevronRight, ChevronsUpDown, Minus, X } from "lucide-react";
|
|
5
5
|
import { useTranslation } from "../../i18n/use-translation.js";
|
|
6
6
|
import { cn } from "../../lib/utils.js";
|
|
7
|
-
import { pickFieldA11y } from "../../lib/field-a11y.js";
|
|
7
|
+
import { pickFieldA11y, useFieldIdentity } from "../../lib/field-a11y.js";
|
|
8
8
|
import { controlOpenRingClass } from "../../lib/control-styles.js";
|
|
9
9
|
import { Button } from "../general/button.js";
|
|
10
10
|
import { Popover, PopoverContent, PopoverTrigger } from "../data-display/popover.js";
|
|
@@ -86,6 +86,7 @@ function Cascader({
|
|
|
86
86
|
}) {
|
|
87
87
|
const { t } = useTranslation();
|
|
88
88
|
const fieldA11y = pickFieldA11y(ariaProps);
|
|
89
|
+
const identity = useFieldIdentity({ id, "data-field": fieldA11y["data-field"] });
|
|
89
90
|
const reactId = React.useId();
|
|
90
91
|
const panelId = `${id ?? reactId}-panel`;
|
|
91
92
|
const options = React.useMemo(
|
|
@@ -240,6 +241,7 @@ function Cascader({
|
|
|
240
241
|
Button,
|
|
241
242
|
{
|
|
242
243
|
id,
|
|
244
|
+
"data-field": fieldA11y["data-field"] ?? identity["data-field"],
|
|
243
245
|
type: "button",
|
|
244
246
|
variant: "outline",
|
|
245
247
|
role: "combobox",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { jsx } from "react/jsx-runtime";
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import { cn } from "../../lib/utils.js";
|
|
5
|
-
import { pickGroupFieldA11y } from "../../lib/field-a11y.js";
|
|
5
|
+
import { pickGroupFieldA11y, useFieldIdentity } from "../../lib/field-a11y.js";
|
|
6
6
|
import { Checkbox } from "./checkbox.js";
|
|
7
7
|
import { Field } from "./field.js";
|
|
8
8
|
import { choiceGroupClassName } from "./choice-option.js";
|
|
@@ -33,6 +33,10 @@ function CheckboxGroup({
|
|
|
33
33
|
const reactId = React.useId();
|
|
34
34
|
const [value, setValue] = useControllableArray(controlledValue, defaultValue);
|
|
35
35
|
const groupA11y = pickGroupFieldA11y(ariaProps);
|
|
36
|
+
const identity = useFieldIdentity({ id, name, "data-field": groupA11y["data-field"] });
|
|
37
|
+
const resolvedName = name ?? identity.name;
|
|
38
|
+
const resolvedField = groupA11y["data-field"] ?? identity["data-field"];
|
|
39
|
+
const optionDomId = (optionValue, index) => id ? `${id}-${optionValue}` : `${reactId}-${optionValue}-${index}`;
|
|
36
40
|
const toggle = (optionValue) => {
|
|
37
41
|
const next = value.includes(optionValue) ? value.filter((v) => v !== optionValue) : [...value, optionValue];
|
|
38
42
|
setValue(next);
|
|
@@ -45,17 +49,19 @@ function CheckboxGroup({
|
|
|
45
49
|
role: "group",
|
|
46
50
|
id,
|
|
47
51
|
...groupA11y,
|
|
52
|
+
"data-field": resolvedField,
|
|
48
53
|
"aria-disabled": disabled ? true : void 0,
|
|
49
54
|
"data-orientation": orientation,
|
|
50
55
|
className: choiceGroupClassName(orientation, className),
|
|
51
56
|
children: options.map((opt, index) => {
|
|
52
|
-
const
|
|
57
|
+
const optionId = optionDomId(opt.value, index);
|
|
53
58
|
const checked = value.includes(opt.value);
|
|
54
|
-
return /* @__PURE__ */ jsx(Field, { id:
|
|
59
|
+
return /* @__PURE__ */ jsx(Field, { id: optionId, label: opt.label, description: opt.description, children: /* @__PURE__ */ jsx(
|
|
55
60
|
Checkbox,
|
|
56
61
|
{
|
|
57
|
-
id:
|
|
58
|
-
name,
|
|
62
|
+
id: optionId,
|
|
63
|
+
name: resolvedName,
|
|
64
|
+
"data-field": resolvedField,
|
|
59
65
|
value: opt.value,
|
|
60
66
|
checked,
|
|
61
67
|
disabled: Boolean(disabled) || Boolean(opt.disabled),
|
|
@@ -74,6 +80,7 @@ function CheckboxGroup({
|
|
|
74
80
|
role: "group",
|
|
75
81
|
id,
|
|
76
82
|
...groupA11y,
|
|
83
|
+
"data-field": resolvedField,
|
|
77
84
|
"aria-disabled": disabled ? true : void 0,
|
|
78
85
|
"data-orientation": orientation,
|
|
79
86
|
className: cn(choiceGroupClassName(orientation), className),
|
|
@@ -4,28 +4,37 @@ import * as React from "react";
|
|
|
4
4
|
import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
|
|
5
5
|
import { Check } from "lucide-react";
|
|
6
6
|
import { cn } from "../../lib/utils.js";
|
|
7
|
+
import { useFieldIdentity } from "../../lib/field-a11y.js";
|
|
7
8
|
import { CheckboxGroup } from "./checkbox-group.js";
|
|
8
|
-
const CheckboxRoot = React.forwardRef(({ className, ...props }, ref) =>
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
"data-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
9
|
+
const CheckboxRoot = React.forwardRef(({ className, ...props }, ref) => {
|
|
10
|
+
const identity = useFieldIdentity({
|
|
11
|
+
id: props.id,
|
|
12
|
+
name: props.name,
|
|
13
|
+
"data-field": props["data-field"]
|
|
14
|
+
});
|
|
15
|
+
return /* @__PURE__ */ jsx(
|
|
16
|
+
CheckboxPrimitive.Root,
|
|
17
|
+
{
|
|
18
|
+
ref,
|
|
19
|
+
"data-slot": "checkbox",
|
|
20
|
+
className: cn(
|
|
21
|
+
// `disabled:cursor-not-allowed disabled:opacity-50` and the checked fill are DELETED, not
|
|
22
|
+
// moved (#319): `.ui-checkbox:disabled, .ui-checkbox[data-disabled]` and
|
|
23
|
+
// `.ui-checkbox[data-state="checked"]` in styles/control.css already declare both, reading
|
|
24
|
+
// --disabled-opacity and --checkbox-checked-background. Utilities are layered AFTER
|
|
25
|
+
// components in Tailwind v4, so these literals were silently OUTRANKING those knobs — a
|
|
26
|
+
// service overriding --checkbox-checked-background got no fill change at all. Radix sets
|
|
27
|
+
// `data-state`/`data-disabled` on the root, so the CSS rules match. Rendering is unchanged:
|
|
28
|
+
// both knobs default to exactly the values these utilities hard-coded.
|
|
29
|
+
"peer ui-checkbox aria-invalid:border-destructive aria-invalid:ring-destructive/20 data-[state=checked]:border-primary data-[state=checked]:text-primary-foreground shrink-0 shadow-xs transition-shadow outline-none",
|
|
30
|
+
className
|
|
31
|
+
),
|
|
32
|
+
...props,
|
|
33
|
+
...identity,
|
|
34
|
+
children: /* @__PURE__ */ jsx(CheckboxPrimitive.Indicator, { "data-slot": "checkbox-indicator", className: "ui-choice-indicator", children: /* @__PURE__ */ jsx(Check, { className: "ui-checkbox-icon", "aria-hidden": "true" }) })
|
|
35
|
+
}
|
|
36
|
+
);
|
|
37
|
+
});
|
|
29
38
|
CheckboxRoot.displayName = CheckboxPrimitive.Root.displayName;
|
|
30
39
|
const Checkbox = Object.assign(CheckboxRoot, {
|
|
31
40
|
Group: CheckboxGroup
|
|
@@ -5,7 +5,7 @@ import { CalendarIcon, X } from "lucide-react";
|
|
|
5
5
|
import { usePickerLocales, useTranslation } from "../../i18n/use-translation.js";
|
|
6
6
|
import { parseDateInput, toIsoDate } from "../../lib/datetime/parse.js";
|
|
7
7
|
import { useControlledLatch } from "../../lib/hooks.js";
|
|
8
|
-
import { pickFieldA11y } from "../../lib/field-a11y.js";
|
|
8
|
+
import { pickFieldA11y, useFieldIdentity } from "../../lib/field-a11y.js";
|
|
9
9
|
import { cn } from "../../lib/utils.js";
|
|
10
10
|
import { Input } from "./input.js";
|
|
11
11
|
import { Popover, PopoverAnchor, PopoverContent, PopoverTrigger } from "../data-display/popover.js";
|
|
@@ -30,6 +30,9 @@ function DatePicker({
|
|
|
30
30
|
const { dayPickerLocale } = usePickerLocales(localeProp);
|
|
31
31
|
const [open, setOpen] = React.useState(false);
|
|
32
32
|
const fieldA11y = pickFieldA11y(ariaProps);
|
|
33
|
+
const identity = useFieldIdentity({ id, name, "data-field": fieldA11y["data-field"] });
|
|
34
|
+
const resolvedName = name ?? identity.name;
|
|
35
|
+
const resolvedField = fieldA11y["data-field"] ?? identity["data-field"];
|
|
33
36
|
const reactId = React.useId();
|
|
34
37
|
const dialogId = `${id ?? reactId}-dialog`;
|
|
35
38
|
const isControlled = useControlledLatch(valueProp !== void 0);
|
|
@@ -66,7 +69,8 @@ function DatePicker({
|
|
|
66
69
|
Input,
|
|
67
70
|
{
|
|
68
71
|
id,
|
|
69
|
-
name,
|
|
72
|
+
name: resolvedName,
|
|
73
|
+
"data-field": resolvedField,
|
|
70
74
|
value: text,
|
|
71
75
|
disabled,
|
|
72
76
|
placeholder: resolvedPlaceholder,
|
|
@@ -5,7 +5,7 @@ import { ArrowRight, CalendarIcon, X } from "lucide-react";
|
|
|
5
5
|
import { usePickerLocales, useTranslation } from "../../i18n/use-translation.js";
|
|
6
6
|
import { parseDateInput, toIsoDate } from "../../lib/datetime/index.js";
|
|
7
7
|
import { useControlledLatch } from "../../lib/hooks.js";
|
|
8
|
-
import { pickGroupFieldA11y } from "../../lib/field-a11y.js";
|
|
8
|
+
import { pickGroupFieldA11y, useFieldIdentity } from "../../lib/field-a11y.js";
|
|
9
9
|
import { cn } from "../../lib/utils.js";
|
|
10
10
|
import { Popover, PopoverAnchor, PopoverContent, PopoverTrigger } from "../data-display/popover.js";
|
|
11
11
|
import { Calendar } from "./calendar.js";
|
|
@@ -33,6 +33,9 @@ function DateRangePicker({
|
|
|
33
33
|
const groupId = id ?? autoId;
|
|
34
34
|
const fromId = `${groupId}-from`;
|
|
35
35
|
const toId = `${groupId}-to`;
|
|
36
|
+
const identity = useFieldIdentity({ id: groupId, name, "data-field": groupA11y["data-field"] });
|
|
37
|
+
const rangeField = groupA11y["data-field"] ?? identity["data-field"];
|
|
38
|
+
const rangeName = name ?? identity.name;
|
|
36
39
|
const isControlled = useControlledLatch(valueProp !== void 0);
|
|
37
40
|
const [internalValue, setInternalValue] = React.useState(defaultValue);
|
|
38
41
|
const value = isControlled ? valueProp : internalValue;
|
|
@@ -77,6 +80,7 @@ function DateRangePicker({
|
|
|
77
80
|
role: "group",
|
|
78
81
|
id: groupId,
|
|
79
82
|
...groupA11y,
|
|
83
|
+
"data-field": rangeField,
|
|
80
84
|
"aria-disabled": disabled ? true : void 0,
|
|
81
85
|
className: cn(
|
|
82
86
|
// One input-styled shell for the whole range — the shared composite-field box, so
|
|
@@ -94,7 +98,8 @@ function DateRangePicker({
|
|
|
94
98
|
"input",
|
|
95
99
|
{
|
|
96
100
|
id: fromId,
|
|
97
|
-
|
|
101
|
+
"data-field": rangeField ? `${rangeField}_from` : void 0,
|
|
102
|
+
name: rangeName ? `${rangeName}_from` : void 0,
|
|
98
103
|
value: fromText,
|
|
99
104
|
disabled,
|
|
100
105
|
placeholder: resolvedPlaceholder,
|
|
@@ -118,7 +123,8 @@ function DateRangePicker({
|
|
|
118
123
|
"input",
|
|
119
124
|
{
|
|
120
125
|
id: toId,
|
|
121
|
-
|
|
126
|
+
"data-field": rangeField ? `${rangeField}_to` : void 0,
|
|
127
|
+
name: rangeName ? `${rangeName}_to` : void 0,
|
|
122
128
|
value: toText,
|
|
123
129
|
disabled,
|
|
124
130
|
placeholder: resolvedPlaceholder,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import type { FormFieldProp } from "../../props/components/data-entry.prop.js";
|
|
3
3
|
export type { FormFieldProp, FormFieldProp as FormFieldProps, } from "../../props/components/data-entry.prop.js";
|
|
4
|
-
export declare function FormField({ id, name, label, required, helper, error: errorProp, labelAddon, layout: layoutProp, labelWidth: labelWidthProp, controlWidth: controlWidthProp, colSpan, className, children, staticText, }: FormFieldProp): React.JSX.Element;
|
|
4
|
+
export declare function FormField({ id, name, field, label, required, helper, error: errorProp, labelAddon, layout: layoutProp, labelWidth: labelWidthProp, controlWidth: controlWidthProp, colSpan, className, children, staticText, }: FormFieldProp): React.JSX.Element;
|
|
@@ -3,7 +3,8 @@ import { jsx, jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import { Label } from "../data-entry/label.js";
|
|
5
5
|
import { cn } from "../../lib/utils.js";
|
|
6
|
-
import { FieldNameContext, mergeAriaIds } from "../../lib/field-a11y.js";
|
|
6
|
+
import { FieldIdentityContext, FieldNameContext, mergeAriaIds } from "../../lib/field-a11y.js";
|
|
7
|
+
import { useOptionalAppContext } from "../../app/app-provider.js";
|
|
7
8
|
import { useFormLayout } from "./form.js";
|
|
8
9
|
import { firstBagMessage, useClaimErrorKey, useFormErrorsRegistry } from "./form-errors.js";
|
|
9
10
|
const toCssLength = (v) => typeof v === "number" ? `${v}px` : v;
|
|
@@ -11,6 +12,7 @@ const FOCUSABLE_SELECTOR = 'input:not([type="hidden"]), select, textarea, button
|
|
|
11
12
|
function FormField({
|
|
12
13
|
id,
|
|
13
14
|
name,
|
|
15
|
+
field,
|
|
14
16
|
label,
|
|
15
17
|
required,
|
|
16
18
|
helper,
|
|
@@ -38,6 +40,8 @@ function FormField({
|
|
|
38
40
|
const labelId = `${resolvedId}-label`;
|
|
39
41
|
const helperId = helper ? `${resolvedId}-helper` : void 0;
|
|
40
42
|
const errorId = error ? `${resolvedId}-error` : void 0;
|
|
43
|
+
const fieldKey = field ?? name ?? id;
|
|
44
|
+
const emitFieldNames = useOptionalAppContext()?.emitFieldNames ?? false;
|
|
41
45
|
const isStatic = staticText !== void 0;
|
|
42
46
|
if (!isStatic && typeof process !== "undefined" && process.env?.NODE_ENV !== "production" && !React.isValidElement(children)) {
|
|
43
47
|
console.warn(
|
|
@@ -48,6 +52,10 @@ function FormField({
|
|
|
48
52
|
() => ({ labelId, label: typeof label === "string" ? label : void 0 }),
|
|
49
53
|
[labelId, label]
|
|
50
54
|
);
|
|
55
|
+
const fieldIdentityContext = React.useMemo(
|
|
56
|
+
() => fieldKey === void 0 ? null : { emitName: emitFieldNames },
|
|
57
|
+
[fieldKey, emitFieldNames]
|
|
58
|
+
);
|
|
51
59
|
const childProps = React.isValidElement(children) ? children.props : void 0;
|
|
52
60
|
const mergeIds = mergeAriaIds;
|
|
53
61
|
const childWithA11y = isStatic ? (
|
|
@@ -63,6 +71,11 @@ function FormField({
|
|
|
63
71
|
// controls (Radio.Group, checkbox lists, range pairs) have no labelable root,
|
|
64
72
|
// and a dangling `for` triggers Chrome's "Incorrect use of <label>" issue.
|
|
65
73
|
id: childProps?.id ?? resolvedId,
|
|
74
|
+
// gh#337 — the machine key. Read the child's own value first in BOTH cases: cloneElement
|
|
75
|
+
// overwrites every key present in the config bag, `undefined` included, so a bare
|
|
76
|
+
// `"data-field": fieldKey` would erase a value the control set for itself.
|
|
77
|
+
"data-field": childProps?.["data-field"] ?? fieldKey,
|
|
78
|
+
...emitFieldNames ? { name: childProps?.name ?? fieldKey } : void 0,
|
|
66
79
|
"aria-labelledby": childProps?.["aria-labelledby"] ?? labelId,
|
|
67
80
|
// Redundant `aria-label` fallback (belt-and-suspenders): the accessible name is the
|
|
68
81
|
// SAME string as the visible label, just reachable even if an aria-labelledby lookup
|
|
@@ -125,7 +138,7 @@ function FormField({
|
|
|
125
138
|
labelAddon
|
|
126
139
|
] }),
|
|
127
140
|
/* @__PURE__ */ jsxs("div", { "data-slot": "form-field-control", className: "ui-form-field-control", children: [
|
|
128
|
-
isStatic ? childWithA11y : /* @__PURE__ */ jsx(FieldNameContext.Provider, { value: fieldNameContext, children: childWithA11y }),
|
|
141
|
+
isStatic ? childWithA11y : /* @__PURE__ */ jsx(FieldNameContext.Provider, { value: fieldNameContext, children: /* @__PURE__ */ jsx(FieldIdentityContext.Provider, { value: fieldIdentityContext, children: childWithA11y }) }),
|
|
129
142
|
helper ? /* @__PURE__ */ jsx("p", { id: helperId, className: "text-muted-foreground text-xs", children: helper }) : null,
|
|
130
143
|
error ? /* @__PURE__ */ jsx("p", { id: errorId, role: "alert", className: "text-destructive text-xs", children: error }) : null
|
|
131
144
|
] })
|
|
@@ -3,7 +3,7 @@ import { jsx, jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import { X } from "lucide-react";
|
|
5
5
|
import { useTranslation } from "../../i18n/use-translation.js";
|
|
6
|
-
import { useFieldNameFallback } from "../../lib/field-a11y.js";
|
|
6
|
+
import { useFieldIdentity, useFieldNameFallback } from "../../lib/field-a11y.js";
|
|
7
7
|
import { cn } from "../../lib/utils.js";
|
|
8
8
|
const inputBaseClass = [
|
|
9
9
|
"ui-control ui-input border-input bg-background w-full rounded-[var(--control-radius)] transition-[color,box-shadow] outline-none",
|
|
@@ -29,6 +29,11 @@ const Input = React.forwardRef(
|
|
|
29
29
|
"aria-label": props["aria-label"],
|
|
30
30
|
"aria-labelledby": props["aria-labelledby"]
|
|
31
31
|
});
|
|
32
|
+
const identity = useFieldIdentity({
|
|
33
|
+
id: props.id,
|
|
34
|
+
name: props.name,
|
|
35
|
+
"data-field": props["data-field"]
|
|
36
|
+
});
|
|
32
37
|
const innerRef = React.useRef(null);
|
|
33
38
|
const setRefs = React.useCallback(
|
|
34
39
|
(node) => {
|
|
@@ -74,7 +79,8 @@ const Input = React.forwardRef(
|
|
|
74
79
|
onChange,
|
|
75
80
|
className: cn(inputBaseClass, className),
|
|
76
81
|
...props,
|
|
77
|
-
...nameFallback
|
|
82
|
+
...nameFallback,
|
|
83
|
+
...identity
|
|
78
84
|
}
|
|
79
85
|
);
|
|
80
86
|
}
|
|
@@ -108,7 +114,8 @@ const Input = React.forwardRef(
|
|
|
108
114
|
className
|
|
109
115
|
),
|
|
110
116
|
...props,
|
|
111
|
-
...nameFallback
|
|
117
|
+
...nameFallback,
|
|
118
|
+
...identity
|
|
112
119
|
}
|
|
113
120
|
),
|
|
114
121
|
trailing != null ? /* @__PURE__ */ jsx("span", { className: "ui-input-trailing", children: trailing }) : null
|
|
@@ -4,7 +4,7 @@ import * as React from "react";
|
|
|
4
4
|
import { CalendarIcon, ChevronLeft, ChevronRight, X } from "lucide-react";
|
|
5
5
|
import { usePickerLocales, useTranslation } from "../../i18n/use-translation.js";
|
|
6
6
|
import { useControlledLatch } from "../../lib/hooks.js";
|
|
7
|
-
import { pickFieldA11y } from "../../lib/field-a11y.js";
|
|
7
|
+
import { pickFieldA11y, useFieldIdentity } from "../../lib/field-a11y.js";
|
|
8
8
|
import { cn } from "../../lib/utils.js";
|
|
9
9
|
import { Button } from "../general/button.js";
|
|
10
10
|
import { Popover, PopoverAnchor, PopoverContent, PopoverTrigger } from "../data-display/popover.js";
|
|
@@ -38,6 +38,7 @@ function MonthPicker({
|
|
|
38
38
|
const inputId = id ?? autoId;
|
|
39
39
|
const dialogId = `${inputId}-dialog`;
|
|
40
40
|
const fieldA11y = pickFieldA11y(ariaProps);
|
|
41
|
+
const identity = useFieldIdentity({ id, name, "data-field": fieldA11y["data-field"] });
|
|
41
42
|
const isControlled = useControlledLatch(valueProp !== void 0);
|
|
42
43
|
const [internalValue, setInternalValue] = React.useState(defaultValue);
|
|
43
44
|
const value = isControlled ? valueProp : internalValue;
|
|
@@ -76,7 +77,8 @@ function MonthPicker({
|
|
|
76
77
|
"input",
|
|
77
78
|
{
|
|
78
79
|
id: inputId,
|
|
79
|
-
name,
|
|
80
|
+
name: name ?? identity.name,
|
|
81
|
+
"data-field": fieldA11y["data-field"] ?? identity["data-field"],
|
|
80
82
|
value: text,
|
|
81
83
|
disabled,
|
|
82
84
|
placeholder: placeholder ?? t("dataEntry.monthPicker.placeholder") ?? YM_HINT,
|
|
@@ -4,7 +4,7 @@ import * as React from "react";
|
|
|
4
4
|
import { ArrowRight, CalendarIcon, ChevronLeft, ChevronRight, X } from "lucide-react";
|
|
5
5
|
import { usePickerLocales, useTranslation } from "../../i18n/use-translation.js";
|
|
6
6
|
import { useControlledLatch } from "../../lib/hooks.js";
|
|
7
|
-
import { pickGroupFieldA11y } from "../../lib/field-a11y.js";
|
|
7
|
+
import { pickGroupFieldA11y, useFieldIdentity } from "../../lib/field-a11y.js";
|
|
8
8
|
import { cn } from "../../lib/utils.js";
|
|
9
9
|
import { Button } from "../general/button.js";
|
|
10
10
|
import { Popover, PopoverAnchor, PopoverContent, PopoverTrigger } from "../data-display/popover.js";
|
|
@@ -40,6 +40,9 @@ function MonthRangePicker({
|
|
|
40
40
|
const groupId = id ?? autoId;
|
|
41
41
|
const fromId = `${groupId}-from`;
|
|
42
42
|
const toId = `${groupId}-to`;
|
|
43
|
+
const identity = useFieldIdentity({ id: groupId, name, "data-field": groupA11y["data-field"] });
|
|
44
|
+
const rangeField = groupA11y["data-field"] ?? identity["data-field"];
|
|
45
|
+
const rangeName = name ?? identity.name;
|
|
43
46
|
const isControlled = useControlledLatch(valueProp !== void 0);
|
|
44
47
|
const [internalValue, setInternalValue] = React.useState(defaultValue);
|
|
45
48
|
const value = isControlled ? valueProp : internalValue;
|
|
@@ -107,6 +110,7 @@ function MonthRangePicker({
|
|
|
107
110
|
role: "group",
|
|
108
111
|
id: groupId,
|
|
109
112
|
...groupA11y,
|
|
113
|
+
"data-field": rangeField,
|
|
110
114
|
"aria-disabled": disabled ? true : void 0,
|
|
111
115
|
"data-disabled": disabled ? "" : void 0,
|
|
112
116
|
className: cn(
|
|
@@ -123,7 +127,8 @@ function MonthRangePicker({
|
|
|
123
127
|
"input",
|
|
124
128
|
{
|
|
125
129
|
id: fromId,
|
|
126
|
-
|
|
130
|
+
"data-field": rangeField ? `${rangeField}_from` : void 0,
|
|
131
|
+
name: rangeName ? `${rangeName}_from` : void 0,
|
|
127
132
|
value: fromText,
|
|
128
133
|
disabled,
|
|
129
134
|
placeholder: resolvedPlaceholder,
|
|
@@ -144,7 +149,8 @@ function MonthRangePicker({
|
|
|
144
149
|
"input",
|
|
145
150
|
{
|
|
146
151
|
id: toId,
|
|
147
|
-
|
|
152
|
+
"data-field": rangeField ? `${rangeField}_to` : void 0,
|
|
153
|
+
name: rangeName ? `${rangeName}_to` : void 0,
|
|
148
154
|
value: toText,
|
|
149
155
|
disabled,
|
|
150
156
|
placeholder: resolvedPlaceholder,
|
|
@@ -4,7 +4,7 @@ import * as React from "react";
|
|
|
4
4
|
import * as RadioGroupPrimitive from "@radix-ui/react-radio-group";
|
|
5
5
|
import { Circle } from "lucide-react";
|
|
6
6
|
import { cn } from "../../lib/utils.js";
|
|
7
|
-
import { pickFieldA11y } from "../../lib/field-a11y.js";
|
|
7
|
+
import { pickFieldA11y, useFieldIdentity } from "../../lib/field-a11y.js";
|
|
8
8
|
import { Field } from "./field.js";
|
|
9
9
|
import { choiceGroupClassName } from "./choice-option.js";
|
|
10
10
|
const RadioGroupRoot = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
|
|
@@ -58,6 +58,10 @@ function RadioGroupOptions({
|
|
|
58
58
|
}) {
|
|
59
59
|
const reactId = React.useId();
|
|
60
60
|
const groupA11y = pickFieldA11y(ariaProps);
|
|
61
|
+
const identity = useFieldIdentity({ id, name, "data-field": groupA11y["data-field"] });
|
|
62
|
+
const resolvedName = name ?? identity.name;
|
|
63
|
+
const resolvedField = groupA11y["data-field"] ?? identity["data-field"];
|
|
64
|
+
const optionDomId = (optionValue, index) => id ? `${id}-${optionValue}` : `${reactId}-${optionValue}-${index}`;
|
|
61
65
|
if (options && options.length > 0) {
|
|
62
66
|
return /* @__PURE__ */ jsx(
|
|
63
67
|
RadioGroupRoot,
|
|
@@ -66,14 +70,23 @@ function RadioGroupOptions({
|
|
|
66
70
|
defaultValue,
|
|
67
71
|
onValueChange,
|
|
68
72
|
disabled,
|
|
69
|
-
name,
|
|
73
|
+
name: resolvedName,
|
|
70
74
|
id,
|
|
71
75
|
...groupA11y,
|
|
76
|
+
"data-field": resolvedField,
|
|
72
77
|
"data-orientation": orientation,
|
|
73
78
|
className: choiceGroupClassName(orientation, className),
|
|
74
79
|
children: options.map((opt, index) => {
|
|
75
|
-
const
|
|
76
|
-
return /* @__PURE__ */ jsx(Field, { id:
|
|
80
|
+
const optionId = optionDomId(opt.value, index);
|
|
81
|
+
return /* @__PURE__ */ jsx(Field, { id: optionId, label: opt.label, description: opt.description, children: /* @__PURE__ */ jsx(
|
|
82
|
+
RadioItem,
|
|
83
|
+
{
|
|
84
|
+
id: optionId,
|
|
85
|
+
value: opt.value,
|
|
86
|
+
disabled: opt.disabled,
|
|
87
|
+
"data-field": resolvedField
|
|
88
|
+
}
|
|
89
|
+
) }, opt.value);
|
|
77
90
|
})
|
|
78
91
|
}
|
|
79
92
|
);
|
|
@@ -85,9 +98,10 @@ function RadioGroupOptions({
|
|
|
85
98
|
defaultValue,
|
|
86
99
|
onValueChange,
|
|
87
100
|
disabled,
|
|
88
|
-
name,
|
|
101
|
+
name: resolvedName,
|
|
89
102
|
id,
|
|
90
103
|
...groupA11y,
|
|
104
|
+
"data-field": resolvedField,
|
|
91
105
|
"data-orientation": orientation,
|
|
92
106
|
className: choiceGroupClassName(orientation, className),
|
|
93
107
|
children
|
|
@@ -9,4 +9,4 @@ export type { SearchSelectProp, SearchSelectProp as SearchSelectProps, SearchSel
|
|
|
9
9
|
* Custom per-option rendering via `renderOption` (Ant-Design style). Form-submittable via
|
|
10
10
|
* `name`; e2e-testable by the trigger's `data-testid` + each option's `${data-testid}-option-${value}`.
|
|
11
11
|
*/
|
|
12
|
-
export declare function SearchSelect({ value: valueProp, defaultValue, onValueChange, options: staticOptions, loadOptions, renderOption, labelRender, selectedLabel, selectedIcon, placeholder, searchPlaceholder, emptyMessage, loadingMessage, errorMessage, clearLabel, clearable, disabled, readOnly, size, open: openProp, onOpenChange, search: searchProp, onSearchChange, filterOption, renderError, renderLoadMore, name, id, className, "data-testid": dataTestId, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledby, "aria-describedby": ariaDescribedby, "aria-errormessage": ariaErrorMessage, "aria-invalid": ariaInvalid, "aria-required": ariaRequired, }: SearchSelectProp): React.JSX.Element;
|
|
12
|
+
export declare function SearchSelect({ value: valueProp, defaultValue, onValueChange, options: staticOptions, loadOptions, renderOption, labelRender, selectedLabel, selectedIcon, placeholder, searchPlaceholder, emptyMessage, loadingMessage, errorMessage, clearLabel, clearable, disabled, readOnly, size, open: openProp, onOpenChange, search: searchProp, onSearchChange, filterOption, renderError, renderLoadMore, name, id, className, "data-testid": dataTestId, "data-field": dataField, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledby, "aria-describedby": ariaDescribedby, "aria-errormessage": ariaErrorMessage, "aria-invalid": ariaInvalid, "aria-required": ariaRequired, }: SearchSelectProp): React.JSX.Element;
|
|
@@ -3,7 +3,7 @@ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import { ChevronsUpDown, Loader2, X } from "lucide-react";
|
|
5
5
|
import { useTranslation } from "../../i18n/use-translation.js";
|
|
6
|
-
import { useFieldNameFallback } from "../../lib/field-a11y.js";
|
|
6
|
+
import { useFieldIdentity, useFieldNameFallback } from "../../lib/field-a11y.js";
|
|
7
7
|
import { cn } from "../../lib/utils.js";
|
|
8
8
|
import { controlOpenRingClass } from "../../lib/control-styles.js";
|
|
9
9
|
import { Button } from "../general/button.js";
|
|
@@ -42,6 +42,7 @@ function SearchSelect({
|
|
|
42
42
|
id,
|
|
43
43
|
className,
|
|
44
44
|
"data-testid": dataTestId,
|
|
45
|
+
"data-field": dataField,
|
|
45
46
|
"aria-label": ariaLabel,
|
|
46
47
|
"aria-labelledby": ariaLabelledby,
|
|
47
48
|
"aria-describedby": ariaDescribedby,
|
|
@@ -54,6 +55,9 @@ function SearchSelect({
|
|
|
54
55
|
"aria-label": ariaLabel,
|
|
55
56
|
"aria-labelledby": ariaLabelledby
|
|
56
57
|
});
|
|
58
|
+
const identity = useFieldIdentity({ id, name, "data-field": dataField });
|
|
59
|
+
const resolvedName = name ?? identity.name;
|
|
60
|
+
const resolvedField = dataField ?? identity["data-field"];
|
|
57
61
|
const triggerAriaLabel = ariaLabel ?? nameFallback["aria-label"];
|
|
58
62
|
const triggerAriaLabelledby = ariaLabelledby ?? nameFallback["aria-labelledby"];
|
|
59
63
|
const reactId = React.useId();
|
|
@@ -254,6 +258,8 @@ function SearchSelect({
|
|
|
254
258
|
"aria-readonly": readOnly || void 0,
|
|
255
259
|
disabled,
|
|
256
260
|
"data-testid": dataTestId,
|
|
261
|
+
"data-field": resolvedField,
|
|
262
|
+
"data-value": value || void 0,
|
|
257
263
|
className: cn(
|
|
258
264
|
"w-full justify-start font-normal",
|
|
259
265
|
controlOpenRingClass,
|
|
@@ -275,7 +281,7 @@ function SearchSelect({
|
|
|
275
281
|
)
|
|
276
282
|
}
|
|
277
283
|
) }),
|
|
278
|
-
|
|
284
|
+
resolvedName ? /* @__PURE__ */ jsx("input", { type: "hidden", name: resolvedName, value, readOnly: true }) : null,
|
|
279
285
|
/* @__PURE__ */ jsx(
|
|
280
286
|
PopoverContent,
|
|
281
287
|
{
|
|
@@ -27,6 +27,14 @@ export declare const SelectTrigger: React.ForwardRefExoticComponent<Omit<SelectP
|
|
|
27
27
|
* descendant CSS is needed to remove it.
|
|
28
28
|
*/
|
|
29
29
|
showIndicator?: boolean;
|
|
30
|
+
/** Stable machine key (gh#337) — normally inherited from `FormField` through the Select. */
|
|
31
|
+
"data-field"?: string;
|
|
32
|
+
/**
|
|
33
|
+
* The selected CODE (gh#337). Normally supplied by `Select` itself — the trigger shows the
|
|
34
|
+
* option's LABEL, and this is the only place the underlying value is readable from the DOM
|
|
35
|
+
* without reaching into Radix's aria-hidden native `<select>`.
|
|
36
|
+
*/
|
|
37
|
+
"data-value"?: string;
|
|
30
38
|
} & React.RefAttributes<HTMLButtonElement>>;
|
|
31
39
|
export declare const SelectScrollUpButton: React.ForwardRefExoticComponent<Omit<SelectPrimitive.SelectScrollUpButtonProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
|
|
32
40
|
export declare const SelectScrollDownButton: React.ForwardRefExoticComponent<Omit<SelectPrimitive.SelectScrollDownButtonProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
|
|
@@ -7,6 +7,7 @@ import { cn } from "../../lib/utils.js";
|
|
|
7
7
|
import { controlTriggerClass } from "../../lib/control-styles.js";
|
|
8
8
|
import {
|
|
9
9
|
mergeAriaIds,
|
|
10
|
+
useFieldIdentity,
|
|
10
11
|
omitFieldA11y,
|
|
11
12
|
pickFieldA11y,
|
|
12
13
|
useFieldNameFallback
|
|
@@ -26,7 +27,36 @@ function Select(props) {
|
|
|
26
27
|
function CompoundSelect({ id, ...props }) {
|
|
27
28
|
const fieldA11y = pickFieldA11y(props);
|
|
28
29
|
const rootProps = omitFieldA11y(props);
|
|
29
|
-
|
|
30
|
+
const [uncontrolled, setUncontrolled] = React.useState(props.defaultValue);
|
|
31
|
+
const value = props.value ?? uncontrolled;
|
|
32
|
+
const identity = useFieldIdentity({
|
|
33
|
+
id,
|
|
34
|
+
name: props.name,
|
|
35
|
+
"data-field": fieldA11y["data-field"]
|
|
36
|
+
});
|
|
37
|
+
return /* @__PURE__ */ jsx(
|
|
38
|
+
SelectFieldA11yContext.Provider,
|
|
39
|
+
{
|
|
40
|
+
value: {
|
|
41
|
+
...fieldA11y,
|
|
42
|
+
"data-field": identity["data-field"] ?? fieldA11y["data-field"],
|
|
43
|
+
id,
|
|
44
|
+
value
|
|
45
|
+
},
|
|
46
|
+
children: /* @__PURE__ */ jsx(
|
|
47
|
+
SelectPrimitive.Root,
|
|
48
|
+
{
|
|
49
|
+
"data-slot": "select",
|
|
50
|
+
...rootProps,
|
|
51
|
+
name: props.name ?? identity.name,
|
|
52
|
+
onValueChange: (next) => {
|
|
53
|
+
setUncontrolled(next);
|
|
54
|
+
props.onValueChange?.(next);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
);
|
|
30
60
|
}
|
|
31
61
|
function SelectGroup(props) {
|
|
32
62
|
return /* @__PURE__ */ jsx(SelectPrimitive.Group, { "data-slot": "select-group", ...props });
|
|
@@ -54,8 +84,10 @@ const SelectTrigger = React.forwardRef(({ className, children, size = "md", show
|
|
|
54
84
|
field?.["aria-errormessage"]
|
|
55
85
|
),
|
|
56
86
|
"aria-required": props["aria-required"] ?? field?.["aria-required"],
|
|
57
|
-
"aria-invalid": props["aria-invalid"] ?? field?.["aria-invalid"]
|
|
87
|
+
"aria-invalid": props["aria-invalid"] ?? field?.["aria-invalid"],
|
|
88
|
+
"data-field": props["data-field"] ?? field?.["data-field"]
|
|
58
89
|
} : void 0;
|
|
90
|
+
const dataValue = (props["data-value"] ?? field?.value) || void 0;
|
|
59
91
|
return /* @__PURE__ */ jsxs(
|
|
60
92
|
SelectPrimitive.Trigger,
|
|
61
93
|
{
|
|
@@ -69,6 +101,7 @@ const SelectTrigger = React.forwardRef(({ className, children, size = "md", show
|
|
|
69
101
|
),
|
|
70
102
|
...props,
|
|
71
103
|
...fieldA11y,
|
|
104
|
+
"data-value": dataValue,
|
|
72
105
|
children: [
|
|
73
106
|
children,
|
|
74
107
|
showIndicator ? /* @__PURE__ */ jsx(SelectPrimitive.Icon, { asChild: true, children: /* @__PURE__ */ jsx(
|
|
@@ -212,12 +245,18 @@ function DataSelect({
|
|
|
212
245
|
id,
|
|
213
246
|
className,
|
|
214
247
|
"data-testid": dataTestId,
|
|
248
|
+
"data-field": dataField,
|
|
215
249
|
...rest
|
|
216
250
|
}) {
|
|
217
251
|
const ariaProps = Object.fromEntries(
|
|
218
252
|
Object.entries(rest).filter(([key]) => key.startsWith("aria-"))
|
|
219
253
|
);
|
|
220
254
|
const { t } = useTranslation();
|
|
255
|
+
const [uncontrolledValue, setUncontrolledValue] = React.useState(defaultValue);
|
|
256
|
+
const currentValue = value ?? uncontrolledValue;
|
|
257
|
+
const identity = useFieldIdentity({ id, name, "data-field": dataField });
|
|
258
|
+
const resolvedName = name ?? identity.name;
|
|
259
|
+
const resolvedField = dataField ?? identity["data-field"];
|
|
221
260
|
const searchable = showSearch ?? Boolean(loadOptions);
|
|
222
261
|
const hasOptions = options.length > 0;
|
|
223
262
|
if (searchable) {
|
|
@@ -250,10 +289,11 @@ function DataSelect({
|
|
|
250
289
|
filterOption,
|
|
251
290
|
renderError,
|
|
252
291
|
renderLoadMore,
|
|
253
|
-
name,
|
|
292
|
+
name: resolvedName,
|
|
254
293
|
id,
|
|
255
294
|
className,
|
|
256
295
|
"data-testid": dataTestId,
|
|
296
|
+
"data-field": resolvedField,
|
|
257
297
|
...ariaProps
|
|
258
298
|
}
|
|
259
299
|
);
|
|
@@ -278,18 +318,23 @@ function DataSelect({
|
|
|
278
318
|
"data-slot": "select",
|
|
279
319
|
value: isControlled ? value : void 0,
|
|
280
320
|
defaultValue: isControlled ? void 0 : defaultValue || void 0,
|
|
281
|
-
onValueChange: (next) =>
|
|
282
|
-
next
|
|
283
|
-
|
|
284
|
-
|
|
321
|
+
onValueChange: (next) => {
|
|
322
|
+
setUncontrolledValue(next);
|
|
323
|
+
onValueChange?.(
|
|
324
|
+
next,
|
|
325
|
+
options.find((option) => option.value === next)
|
|
326
|
+
);
|
|
327
|
+
},
|
|
285
328
|
disabled: disabled || !hasOptions,
|
|
286
|
-
name,
|
|
329
|
+
name: resolvedName,
|
|
287
330
|
children: [
|
|
288
331
|
/* @__PURE__ */ jsx(
|
|
289
332
|
SelectTrigger,
|
|
290
333
|
{
|
|
291
334
|
id,
|
|
292
335
|
"data-testid": dataTestId,
|
|
336
|
+
"data-field": resolvedField,
|
|
337
|
+
"data-value": currentValue || void 0,
|
|
293
338
|
className: cn(showClear && "ui-control-trigger-affixed", canClear ? void 0 : className),
|
|
294
339
|
showIndicator: !showClear,
|
|
295
340
|
...ariaProps,
|
|
@@ -3,8 +3,15 @@ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import * as SwitchPrimitive from "@radix-ui/react-switch";
|
|
5
5
|
import { cn } from "../../lib/utils.js";
|
|
6
|
+
import { useFieldIdentity } from "../../lib/field-a11y.js";
|
|
6
7
|
const Switch = React.forwardRef(
|
|
7
8
|
({ className, size = "md", name, checked, defaultChecked = false, onCheckedChange, ...props }, ref) => {
|
|
9
|
+
const identity = useFieldIdentity({
|
|
10
|
+
id: props.id,
|
|
11
|
+
name,
|
|
12
|
+
"data-field": props["data-field"]
|
|
13
|
+
});
|
|
14
|
+
const resolvedName = name ?? identity.name;
|
|
8
15
|
const [internalChecked, setInternalChecked] = React.useState(defaultChecked);
|
|
9
16
|
const isControlled = checked !== void 0;
|
|
10
17
|
const isChecked = isControlled ? checked : internalChecked;
|
|
@@ -15,7 +22,7 @@ const Switch = React.forwardRef(
|
|
|
15
22
|
onCheckedChange?.(next);
|
|
16
23
|
};
|
|
17
24
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
18
|
-
|
|
25
|
+
resolvedName ? /* @__PURE__ */ jsx("input", { type: "hidden", name: resolvedName, value: isChecked ? "1" : "0", readOnly: true }) : null,
|
|
19
26
|
/* @__PURE__ */ jsx(
|
|
20
27
|
SwitchPrimitive.Root,
|
|
21
28
|
{
|
|
@@ -35,6 +42,7 @@ const Switch = React.forwardRef(
|
|
|
35
42
|
className
|
|
36
43
|
),
|
|
37
44
|
...props,
|
|
45
|
+
"data-field": identity["data-field"] ?? props["data-field"],
|
|
38
46
|
children: /* @__PURE__ */ jsx(SwitchPrimitive.Thumb, { "data-slot": "switch-thumb", className: "ui-switch-thumb" })
|
|
39
47
|
}
|
|
40
48
|
)
|
|
@@ -4,6 +4,7 @@ import * as React from "react";
|
|
|
4
4
|
import { X } from "lucide-react";
|
|
5
5
|
import { useTranslation } from "../../i18n/use-translation.js";
|
|
6
6
|
import { cn } from "../../lib/utils.js";
|
|
7
|
+
import { useFieldIdentity } from "../../lib/field-a11y.js";
|
|
7
8
|
import { controlMultilineClass, controlMultilineGhostClass } from "../../lib/control-styles.js";
|
|
8
9
|
const UNBOUNDED_ROWS = "infinity";
|
|
9
10
|
const Textarea = React.forwardRef(
|
|
@@ -26,6 +27,11 @@ const Textarea = React.forwardRef(
|
|
|
26
27
|
}, ref) => {
|
|
27
28
|
const { t } = useTranslation();
|
|
28
29
|
const base = variant === "ghost" ? controlMultilineGhostClass : controlMultilineClass;
|
|
30
|
+
const identity = useFieldIdentity({
|
|
31
|
+
id: props.id,
|
|
32
|
+
name: props.name,
|
|
33
|
+
"data-field": props["data-field"]
|
|
34
|
+
});
|
|
29
35
|
const innerRef = React.useRef(null);
|
|
30
36
|
const setRefs = React.useCallback(
|
|
31
37
|
(node) => {
|
|
@@ -110,7 +116,8 @@ const Textarea = React.forwardRef(
|
|
|
110
116
|
onCompositionEnd: handleCompositionEnd,
|
|
111
117
|
style,
|
|
112
118
|
className: cn(base, showClear && "ui-input--trailing-affix", className),
|
|
113
|
-
...props
|
|
119
|
+
...props,
|
|
120
|
+
...identity
|
|
114
121
|
}
|
|
115
122
|
);
|
|
116
123
|
if (!needsWrapper) return field;
|
|
@@ -4,7 +4,7 @@ import * as React from "react";
|
|
|
4
4
|
import { ChevronDown, ChevronRight, ChevronsUpDown, X } from "lucide-react";
|
|
5
5
|
import { useTranslation } from "../../i18n/use-translation.js";
|
|
6
6
|
import { cn } from "../../lib/utils.js";
|
|
7
|
-
import { pickFieldA11y } from "../../lib/field-a11y.js";
|
|
7
|
+
import { pickFieldA11y, useFieldIdentity } from "../../lib/field-a11y.js";
|
|
8
8
|
import { controlOpenRingClass } from "../../lib/control-styles.js";
|
|
9
9
|
import { Button } from "../general/button.js";
|
|
10
10
|
import { Popover, PopoverContent, PopoverTrigger } from "../data-display/popover.js";
|
|
@@ -63,6 +63,7 @@ function TreeSelectRoot({
|
|
|
63
63
|
}) {
|
|
64
64
|
const { t } = useTranslation();
|
|
65
65
|
const fieldA11y = pickFieldA11y(ariaProps);
|
|
66
|
+
const identity = useFieldIdentity({ id, "data-field": fieldA11y["data-field"] });
|
|
66
67
|
const reactId = React.useId();
|
|
67
68
|
const treeId = `${id ?? reactId}-tree`;
|
|
68
69
|
const options = React.useMemo(
|
|
@@ -179,6 +180,7 @@ function TreeSelectRoot({
|
|
|
179
180
|
Button,
|
|
180
181
|
{
|
|
181
182
|
id,
|
|
183
|
+
"data-field": fieldA11y["data-field"] ?? identity["data-field"],
|
|
182
184
|
type: "button",
|
|
183
185
|
variant: "outline",
|
|
184
186
|
role: "combobox",
|
package/dist/lib/field-a11y.d.ts
CHANGED
|
@@ -13,6 +13,12 @@ import * as React from "react";
|
|
|
13
13
|
* - `aria-errormessage` + `aria-invalid` — the validation message, announced when invalid.
|
|
14
14
|
* - `aria-required` — required-field semantics.
|
|
15
15
|
* - `aria-label` — a name supplied directly (used when there is no visible label element).
|
|
16
|
+
*
|
|
17
|
+
* `data-field` travels the SAME route (gh#337) and is therefore part of this bag even though it is
|
|
18
|
+
* not an ARIA attribute. It is the field's stable machine key — the `data-testid` role, standardised
|
|
19
|
+
* on one attribute so a control never has to be found by a generated id or by its visible Japanese
|
|
20
|
+
* label. It is inert (read-only metadata) and must land on the same semantic focus target the aria
|
|
21
|
+
* relationships do; routing it through a second, parallel mechanism is how a control gets missed.
|
|
16
22
|
*/
|
|
17
23
|
export interface FieldA11yProps {
|
|
18
24
|
"aria-label"?: string;
|
|
@@ -21,6 +27,8 @@ export interface FieldA11yProps {
|
|
|
21
27
|
"aria-errormessage"?: string;
|
|
22
28
|
"aria-invalid"?: React.AriaAttributes["aria-invalid"];
|
|
23
29
|
"aria-required"?: React.AriaAttributes["aria-required"];
|
|
30
|
+
/** Stable machine key of the field (gh#337) — see the interface doc above. */
|
|
31
|
+
"data-field"?: string;
|
|
24
32
|
}
|
|
25
33
|
/**
|
|
26
34
|
* Merge one or more space-separated id-reference lists (`aria-describedby`, `aria-labelledby`,
|
|
@@ -81,6 +89,49 @@ export declare const FieldNameContext: React.Context<FieldNameContextValue | nul
|
|
|
81
89
|
* inside a FormField AND still nameless after its own props and the cloned contract are applied.
|
|
82
90
|
*/
|
|
83
91
|
export declare function useFieldNameFallback(name: Pick<FieldA11yProps, "aria-label" | "aria-labelledby">): Pick<FieldA11yProps, "aria-label" | "aria-labelledby">;
|
|
92
|
+
/**
|
|
93
|
+
* The enclosing `FormField`, made reachable by NESTED controls (gh#337).
|
|
94
|
+
*
|
|
95
|
+
* `cloneElement` reaches FormField's single DIRECT child only. In the real screens this library
|
|
96
|
+
* serves, 322 of 1,410 controls (23%) sit one level deeper — the direct child is a `Flex` holding a
|
|
97
|
+
* from/to pair, a 年/月 combo, or a value + 「不明」 checkbox — so the machine key stopped on the
|
|
98
|
+
* wrapper `div` and the controls inside stayed anonymous. The customer's acceptance condition is
|
|
99
|
+
* 「画面に見えている入力欄の100%に付与されていること」; 77% does not pass.
|
|
100
|
+
*
|
|
101
|
+
* The key for such a control is its OWN `id`, and that is a finding, not a convention invented
|
|
102
|
+
* here: of those 322, **250 already carry a static id** and the ambiguous case the wrapper creates
|
|
103
|
+
* — two controls under one field — is already distinguished at the call site
|
|
104
|
+
* (`search_billing_date_from` / `..._to`, `fax` / `fax_unknown`). So a nested control names itself
|
|
105
|
+
* and no two controls can ever end up sharing a key. The remaining 72 (48 computed ids, 24 with no
|
|
106
|
+
* id at all) get NOTHING: a fabricated key is worse than a missing one, because automation would
|
|
107
|
+
* bind to it and break silently on the next render.
|
|
108
|
+
*/
|
|
109
|
+
export interface FieldIdentityContextValue {
|
|
110
|
+
/** Whether the app opted into a native `name` (@see AppProviderProp.emitFieldNames). */
|
|
111
|
+
emitName: boolean;
|
|
112
|
+
}
|
|
113
|
+
export declare const FieldIdentityContext: React.Context<FieldIdentityContextValue | null>;
|
|
114
|
+
/**
|
|
115
|
+
* Resolve `data-field` / `name` for a control nested under a `FormField` (see
|
|
116
|
+
* {@link FieldIdentityContext}). Returns `{}` — adding nothing — in every case but the one it
|
|
117
|
+
* exists for, which is what keeps it safe to call from every control:
|
|
118
|
+
*
|
|
119
|
+
* - **outside a FormField** → `{}`. A control elsewhere on the page is untouched.
|
|
120
|
+
* - **`data-field` already present** → `{}`. Either `FormField` cloned it onto its direct child, or
|
|
121
|
+
* a composite (DatePicker, Select) already resolved this field and is passing it down. Whoever
|
|
122
|
+
* owns the field owns BOTH attributes, so `name` is not second-guessed here either — that is what
|
|
123
|
+
* keeps DatePicker's ISO mirror (`name` belongs to the hidden `yyyy-MM-dd` input, not the visible
|
|
124
|
+
* `yyyy/MM/dd` one) and Select's native `<select>` intact.
|
|
125
|
+
* - **no `id` of its own** → `{}`. Nothing to derive a stable key from; see the interface doc.
|
|
126
|
+
*/
|
|
127
|
+
export declare function useFieldIdentity(own: {
|
|
128
|
+
id?: string;
|
|
129
|
+
name?: string;
|
|
130
|
+
"data-field"?: string;
|
|
131
|
+
}): {
|
|
132
|
+
"data-field"?: string;
|
|
133
|
+
name?: string;
|
|
134
|
+
};
|
|
84
135
|
/**
|
|
85
136
|
* The field-a11y attributes valid on a **group container** (`role="group"`), used by composite
|
|
86
137
|
* controls that have no single labelable focus target — CheckboxGroup, and the two-input range
|
|
@@ -98,4 +149,5 @@ export declare function useFieldNameFallback(name: Pick<FieldA11yProps, "aria-la
|
|
|
98
149
|
export declare function pickGroupFieldA11y(props: FieldA11yProps): {
|
|
99
150
|
"aria-labelledby"?: string;
|
|
100
151
|
"aria-describedby"?: string;
|
|
152
|
+
"data-field"?: string;
|
|
101
153
|
};
|
package/dist/lib/field-a11y.js
CHANGED
|
@@ -6,7 +6,8 @@ const FIELD_A11Y_KEYS = [
|
|
|
6
6
|
"aria-describedby",
|
|
7
7
|
"aria-errormessage",
|
|
8
8
|
"aria-invalid",
|
|
9
|
-
"aria-required"
|
|
9
|
+
"aria-required",
|
|
10
|
+
"data-field"
|
|
10
11
|
];
|
|
11
12
|
function mergeAriaIds(...values) {
|
|
12
13
|
return Array.from(new Set(values.flatMap((value) => value?.split(/\s+/).filter(Boolean) ?? []))).join(
|
|
@@ -47,19 +48,33 @@ function useFieldNameFallback(name) {
|
|
|
47
48
|
...field.label !== void 0 ? { "aria-label": field.label } : {}
|
|
48
49
|
};
|
|
49
50
|
}
|
|
51
|
+
const FieldIdentityContext = React.createContext(null);
|
|
52
|
+
function useFieldIdentity(own) {
|
|
53
|
+
const ctx = React.useContext(FieldIdentityContext);
|
|
54
|
+
if (!ctx || own["data-field"] !== void 0 || own.id === void 0) return {};
|
|
55
|
+
return {
|
|
56
|
+
"data-field": own.id,
|
|
57
|
+
...ctx.emitName && own.name === void 0 ? { name: own.id } : {}
|
|
58
|
+
};
|
|
59
|
+
}
|
|
50
60
|
function pickGroupFieldA11y(props) {
|
|
51
61
|
const describedBy = mergeAriaIds(props["aria-describedby"], props["aria-errormessage"]);
|
|
52
62
|
return {
|
|
53
63
|
...props["aria-labelledby"] !== void 0 ? { "aria-labelledby": props["aria-labelledby"] } : {},
|
|
54
|
-
...describedBy !== void 0 ? { "aria-describedby": describedBy } : {}
|
|
64
|
+
...describedBy !== void 0 ? { "aria-describedby": describedBy } : {},
|
|
65
|
+
// Not an ARIA attribute and so not subject to the role="group" restriction above — a group
|
|
66
|
+
// still has to be findable by its field key (gh#337).
|
|
67
|
+
...props["data-field"] !== void 0 ? { "data-field": props["data-field"] } : {}
|
|
55
68
|
};
|
|
56
69
|
}
|
|
57
70
|
export {
|
|
71
|
+
FieldIdentityContext,
|
|
58
72
|
FieldNameContext,
|
|
59
73
|
mergeAriaIds,
|
|
60
74
|
omitFieldA11y,
|
|
61
75
|
pickFieldA11y,
|
|
62
76
|
pickGroupFieldA11y,
|
|
63
77
|
resolveFieldA11y,
|
|
78
|
+
useFieldIdentity,
|
|
64
79
|
useFieldNameFallback
|
|
65
80
|
};
|
|
@@ -49,6 +49,19 @@ export type AppProviderProp = {
|
|
|
49
49
|
* number (e.g. `0.95`) overrides it. Type is a separate axis — not scaled.
|
|
50
50
|
*/
|
|
51
51
|
scaling?: number | null;
|
|
52
|
+
/**
|
|
53
|
+
* Emit a native `name` on every control a `FormField` wraps, taken from the field's key
|
|
54
|
+
* (`field` → `name` → `id`). Default `false`, and deliberately opt-in (gh#337).
|
|
55
|
+
*
|
|
56
|
+
* `data-field` is inert metadata and is always emitted; `name` is NOT — it changes what a
|
|
57
|
+
* native `<form>` submit sends. An app whose controls have never carried a `name` would start
|
|
58
|
+
* posting extra keys to its backend the moment it upgraded the library. That is a behaviour
|
|
59
|
+
* change no shared package may make by default, so the app that WANTS it (a screen-automation /
|
|
60
|
+
* RPA contract, native form posts) turns it on here, once, and every FormField in it obeys.
|
|
61
|
+
*
|
|
62
|
+
* A `name` written on the control itself always wins over the injected one.
|
|
63
|
+
*/
|
|
64
|
+
emitFieldNames?: boolean;
|
|
52
65
|
onLocaleChange?: (locale: AppLocale) => void;
|
|
53
66
|
onTimezoneChange?: (timezone: AppTimezone) => void;
|
|
54
67
|
onTimeFormatChange?: (timeFormat: AppTimeFormat) => void;
|
|
@@ -117,6 +130,8 @@ export type AppContextValue = {
|
|
|
117
130
|
density: AppDensity;
|
|
118
131
|
fontSize: AppFontSize;
|
|
119
132
|
scaling: number | null;
|
|
133
|
+
/** Whether `FormField` injects a native `name` onto its control (gh#337). Default `false`. */
|
|
134
|
+
emitFieldNames: boolean;
|
|
120
135
|
setLocale: (locale: AppLocale) => void;
|
|
121
136
|
setTimezone: (timezone: AppTimezone) => void;
|
|
122
137
|
setTimeFormat: (timeFormat: AppTimeFormat) => void;
|
|
@@ -130,10 +130,22 @@ export type FormFieldProp = {
|
|
|
130
130
|
/**
|
|
131
131
|
* Error-bag key of this field. When the surrounding `Form` carries `errors`, the field
|
|
132
132
|
* resolves its message from `errors[name]` automatically (an explicit `error` prop wins)
|
|
133
|
-
* and CLAIMS the key so `<FormErrors />` does not repeat it.
|
|
134
|
-
*
|
|
133
|
+
* and CLAIMS the key so `<FormErrors />` does not repeat it. Also the default source of
|
|
134
|
+
* {@link FormFieldProp.field} (below), so a field that already names its error key does not
|
|
135
|
+
* repeat itself.
|
|
135
136
|
*/
|
|
136
137
|
name?: NameProp;
|
|
138
|
+
/**
|
|
139
|
+
* Stable MACHINE key of this field — the bare column/parameter name (`project_name`), not a
|
|
140
|
+
* bracketed legacy path (gh#337). Rendered on the control as `data-field`, and as a native
|
|
141
|
+
* `name` when the app opted in via `<AppProvider emitFieldNames>`.
|
|
142
|
+
*
|
|
143
|
+
* Defaults to `name`, then `id` — which is why an app whose fields already carry a
|
|
144
|
+
* column-named `id` gets the attribute on every control without editing a single screen.
|
|
145
|
+
* Set it explicitly only where `id` is a DOM-uniqueness artefact rather than the field key
|
|
146
|
+
* (`id="source_slip_field"` for `field="source_slip_id"`).
|
|
147
|
+
*/
|
|
148
|
+
field?: NameProp;
|
|
137
149
|
label: LabelProp;
|
|
138
150
|
required?: RequiredProp;
|
|
139
151
|
helper?: HelperProp;
|
|
@@ -160,6 +172,8 @@ export type FormFieldProp = {
|
|
|
160
172
|
* and CLAIMS the key so `<FormErrors />` does not repeat it.
|
|
161
173
|
*/
|
|
162
174
|
name?: NameProp;
|
|
175
|
+
/** Stable machine key — see the `children` variant above. Unused on a read-only row. */
|
|
176
|
+
field?: NameProp;
|
|
163
177
|
label: LabelProp;
|
|
164
178
|
required?: RequiredProp;
|
|
165
179
|
helper?: HelperProp;
|
|
@@ -490,6 +504,8 @@ export type SearchSelectProp = {
|
|
|
490
504
|
"aria-invalid"?: boolean | "true" | "false";
|
|
491
505
|
"aria-required"?: boolean | "true" | "false";
|
|
492
506
|
"data-testid"?: string;
|
|
507
|
+
/** Stable machine key, forwarded to the trigger (gh#337). Normally injected by `FormField`. */
|
|
508
|
+
"data-field"?: string;
|
|
493
509
|
};
|
|
494
510
|
/**
|
|
495
511
|
* Data-driven (Ant-style) form of {@link Select} — one component covering static `options` or
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@godxjp/ui",
|
|
3
|
-
"version": "19.
|
|
4
|
-
"godxUiMcp": "19.
|
|
3
|
+
"version": "19.1.0",
|
|
4
|
+
"godxUiMcp": "19.1.0",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -333,10 +333,10 @@
|
|
|
333
333
|
"check:final-touch-rtl": "node scripts/check-final-touch-rtl.mjs",
|
|
334
334
|
"check:frame-runtime": "pnpm check:data-entry-frame-runtime && pnpm check:data-entry-touch-aria && pnpm check:layout-nav-frames && pnpm check:provider-feedback-query-runtime && pnpm check:layout-nav-closure && pnpm check:final-touch-rtl && pnpm check:data-table-pagination-wrap && pnpm check:button-icon-xs",
|
|
335
335
|
"pretest": "pnpm check:frame-coverage",
|
|
336
|
-
"verify": "pnpm typecheck && pnpm lint && pnpm format && pnpm build && pnpm preview:build && pnpm check:example-imports && pnpm check:core-isolation && pnpm check:no-consumer-coupling && pnpm check:prop-vocabulary && pnpm check:token-tiers && pnpm check:token-scale-bypass && pnpm check:dist-tokens-resolve && pnpm check:no-hardcoded-geometry && pnpm check:no-hardcoded-css-values && pnpm check:no-inline-magic-numbers && pnpm check:no-tailwind-class-assertions && pnpm check:control-sizing && pnpm check:rtl && pnpm check:typography && pnpm check:mcp-token-sync && pnpm check:email-token-sync && pnpm check:mcp-lockstep && pnpm check:mcp-sync && pnpm check:doc-prop-existence && pnpm check:mcp-catalog-coverage && pnpm check:mcp-orphans && pnpm check:mcp-pattern-imports && pnpm check:audit-sync && pnpm check:frame-coverage && pnpm test",
|
|
336
|
+
"verify": "pnpm typecheck && pnpm lint && pnpm format && pnpm build && pnpm preview:build && pnpm check:example-imports && pnpm check:core-isolation && pnpm check:no-consumer-coupling && pnpm check:prop-vocabulary && pnpm check:token-tiers && pnpm check:no-external-assets && pnpm check:token-scale-bypass && pnpm check:dist-tokens-resolve && pnpm check:no-hardcoded-geometry && pnpm check:no-hardcoded-css-values && pnpm check:no-inline-magic-numbers && pnpm check:no-tailwind-class-assertions && pnpm check:control-sizing && pnpm check:rtl && pnpm check:typography && pnpm check:mcp-token-sync && pnpm check:email-token-sync && pnpm check:mcp-lockstep && pnpm check:mcp-sync && pnpm check:doc-prop-existence && pnpm check:mcp-catalog-coverage && pnpm check:mcp-orphans && pnpm check:mcp-pattern-imports && pnpm check:audit-sync && pnpm check:frame-coverage && pnpm test",
|
|
337
337
|
"verify:static": "pnpm build && pnpm check:packed-public-contract && pnpm typecheck && pnpm typecheck:docs && pnpm lint && pnpm preview:build && pnpm check:example-imports && pnpm check:core-isolation && pnpm check:no-consumer-coupling && pnpm check:use-client && pnpm check:prop-vocabulary && pnpm check:token-tiers && pnpm check:token-scale-bypass && pnpm check:dist-tokens-resolve && pnpm check:no-hardcoded-geometry && pnpm check:no-hardcoded-css-values && pnpm check:no-inline-magic-numbers && pnpm check:no-tailwind-class-assertions && pnpm check:control-sizing && pnpm check:rtl && pnpm check:typography && pnpm check:mcp-token-sync && pnpm check:email-token-sync && pnpm check:mcp-lockstep && pnpm check:mcp-sync && pnpm check:doc-prop-existence && pnpm check:mcp-catalog-coverage && pnpm check:mcp-orphans && pnpm check:mcp-pattern-imports && pnpm check:audit-sync && pnpm check:mcp-prop-sync && pnpm check:contrast && pnpm check:visual-audit && pnpm test",
|
|
338
338
|
"verify:ci": "pnpm build && pnpm check:packed-public-contract && pnpm typecheck && pnpm typecheck:docs && pnpm lint && pnpm preview:build && pnpm check:example-imports && pnpm check:core-isolation && pnpm check:no-consumer-coupling && pnpm check:use-client && pnpm check:prop-vocabulary && pnpm check:token-tiers && pnpm check:token-scale-bypass && pnpm check:dist-tokens-resolve && pnpm check:no-hardcoded-geometry && pnpm check:no-hardcoded-css-values && pnpm check:no-inline-magic-numbers && pnpm check:no-tailwind-class-assertions && pnpm check:control-sizing && pnpm check:rtl && pnpm check:typography && pnpm check:mcp-token-sync && pnpm check:email-token-sync && pnpm check:mcp-lockstep && pnpm check:mcp-sync && pnpm check:doc-prop-existence && pnpm check:mcp-catalog-coverage && pnpm check:mcp-orphans && pnpm check:mcp-pattern-imports && pnpm check:audit-sync && pnpm check:mcp-prop-sync && pnpm test",
|
|
339
|
-
"verify:ci:static": "pnpm build && pnpm check:packed-public-contract && pnpm typecheck && pnpm typecheck:docs && pnpm lint && pnpm preview:build && pnpm check:example-imports && pnpm check:core-isolation && pnpm check:no-consumer-coupling && pnpm check:use-client && pnpm check:prop-vocabulary && pnpm check:token-tiers && pnpm check:token-scale-bypass && pnpm check:dist-tokens-resolve && pnpm check:no-hardcoded-geometry && pnpm check:no-hardcoded-css-values && pnpm check:no-inline-magic-numbers && pnpm check:no-tailwind-class-assertions && pnpm check:control-sizing && pnpm check:rtl && pnpm check:typography && pnpm check:mcp-token-sync && pnpm check:email-token-sync && pnpm check:mcp-lockstep && pnpm check:mcp-sync && pnpm check:doc-prop-existence && pnpm check:mcp-catalog-coverage && pnpm check:mcp-orphans && pnpm check:mcp-pattern-imports && pnpm check:audit-sync && pnpm check:mcp-prop-sync",
|
|
339
|
+
"verify:ci:static": "pnpm build && pnpm check:packed-public-contract && pnpm typecheck && pnpm typecheck:docs && pnpm lint && pnpm preview:build && pnpm check:example-imports && pnpm check:core-isolation && pnpm check:no-consumer-coupling && pnpm check:use-client && pnpm check:prop-vocabulary && pnpm check:token-tiers && pnpm check:no-external-assets && pnpm check:token-scale-bypass && pnpm check:dist-tokens-resolve && pnpm check:no-hardcoded-geometry && pnpm check:no-hardcoded-css-values && pnpm check:no-inline-magic-numbers && pnpm check:no-tailwind-class-assertions && pnpm check:control-sizing && pnpm check:rtl && pnpm check:typography && pnpm check:mcp-token-sync && pnpm check:email-token-sync && pnpm check:mcp-lockstep && pnpm check:mcp-sync && pnpm check:doc-prop-existence && pnpm check:mcp-catalog-coverage && pnpm check:mcp-orphans && pnpm check:mcp-pattern-imports && pnpm check:audit-sync && pnpm check:mcp-prop-sync",
|
|
340
340
|
"verify:browser": "pnpm check:contrast && pnpm check:visual-audit",
|
|
341
341
|
"verify:release": "pnpm verify:static && pnpm check:frame-contracts && pnpm check:frame-coverage && pnpm check:frame-axe",
|
|
342
342
|
"verify:publish-tree": "pnpm build && pnpm check:packed-public-contract && pnpm check:use-client && pnpm check:dist-tokens-resolve && pnpm check:mcp-lockstep",
|
|
@@ -357,6 +357,7 @@
|
|
|
357
357
|
"check:token-scale-bypass": "node scripts/check-token-scale-bypass.mjs",
|
|
358
358
|
"check:dist-tokens-resolve": "node scripts/check-dist-tokens-resolve.mjs",
|
|
359
359
|
"check:no-hardcoded-geometry": "node scripts/check-no-hardcoded-geometry.mjs",
|
|
360
|
+
"check:no-external-assets": "node scripts/check-no-external-assets.mjs",
|
|
360
361
|
"check:no-hardcoded-css-values": "node scripts/check-no-hardcoded-css-values.mjs",
|
|
361
362
|
"check:no-inline-magic-numbers": "node scripts/check-no-inline-magic-numbers.mjs",
|
|
362
363
|
"check:no-tailwind-class-assertions": "node scripts/check-no-tailwind-class-assertions.mjs",
|