@godxjp/ui 18.13.0 → 18.14.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/components/data-entry/form-errors.d.ts +45 -0
- package/dist/components/data-entry/form-errors.js +71 -0
- package/dist/components/data-entry/form-field.d.ts +1 -1
- package/dist/components/data-entry/form-field.js +12 -3
- package/dist/components/data-entry/form.d.ts +1 -0
- package/dist/components/data-entry/form.js +16 -11
- package/dist/components/data-entry/index.d.ts +2 -0
- package/dist/components/data-entry/index.js +3 -0
- package/dist/components/data-entry/input.js +9 -2
- package/dist/components/data-entry/search-select.js +11 -4
- package/dist/components/data-entry/select.js +24 -8
- package/dist/components/layout/flex.js +17 -1
- package/dist/form/form-field-control.js +2 -0
- package/dist/i18n/messages/en.json +3 -0
- package/dist/i18n/messages/ja.json +3 -0
- package/dist/i18n/messages/vi.json +3 -0
- package/dist/lib/field-a11y.d.ts +29 -1
- package/dist/lib/field-a11y.js +16 -1
- package/dist/props/components/data-entry.prop.d.ts +54 -1
- package/dist/props/registry.d.ts +17 -2
- package/dist/props/registry.js +17 -1
- package/dist/props/vocabulary/index.d.ts +1 -1
- package/dist/props/vocabulary/shared.prop.d.ts +5 -0
- package/package.json +2 -2
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import type { FormErrorsProp, FormErrorsProviderProp } from "../../props/components/data-entry.prop.js";
|
|
3
|
+
import type { ErrorBagProp } from "../../props/vocabulary/index.js";
|
|
4
|
+
export type { FormErrorsProp, FormErrorsProp as FormErrorsProps, FormErrorsProviderProp, FormErrorsProviderProp as FormErrorsProviderProps, } from "../../props/components/data-entry.prop.js";
|
|
5
|
+
/**
|
|
6
|
+
* Registry shared by a `Form` with its FormFields and `<FormErrors />`. Each mounted
|
|
7
|
+
* `FormField name="…"` CLAIMS its error-bag key (it is the visible owner of that message);
|
|
8
|
+
* `<FormErrors />` renders only the unclaimed remainder. Claims are reference-counted so
|
|
9
|
+
* duplicate names and unmount order stay correct.
|
|
10
|
+
*/
|
|
11
|
+
interface FormErrorsRegistryValue {
|
|
12
|
+
errors: ErrorBagProp;
|
|
13
|
+
claimed: ReadonlyMap<string, number>;
|
|
14
|
+
/** Claim `key`; returns the release function (stable identity — safe as an effect dep). */
|
|
15
|
+
claim: (key: string) => () => void;
|
|
16
|
+
}
|
|
17
|
+
/** Read the nearest Form's error registry (null when used outside a Form). */
|
|
18
|
+
export declare function useFormErrorsRegistry(): FormErrorsRegistryValue | null;
|
|
19
|
+
/** First message of a bag entry — Laravel arrays surface their first message on the field. */
|
|
20
|
+
export declare function firstBagMessage(entry: string | string[] | undefined): string | undefined;
|
|
21
|
+
/**
|
|
22
|
+
* FormErrorsProvider — one shared error registry (error bag + claim set) over a region.
|
|
23
|
+
* 兄弟 Form 群で 1 つのエラーバッグを共有するための公開プロバイダ。Two ways to get one:
|
|
24
|
+
*
|
|
25
|
+
* 1. `Form errors={…}` renders this provider itself (both `<form>` and `asChild` modes) —
|
|
26
|
+
* the single-form case needs nothing else.
|
|
27
|
+
* 2. An edit screen split into several sibling Card+Form sections (the standard exseli shape)
|
|
28
|
+
* wraps the REGION in `<FormErrorsProvider errors={…}>` instead: Forms **without** their own
|
|
29
|
+
* `errors` join the surrounding registry, every `FormField name="…"` inside claims into it,
|
|
30
|
+
* and one `<FormErrors />` anywhere in the region renders the unclaimed remainder.
|
|
31
|
+
*
|
|
32
|
+
* A nested Form WITH its own `errors` starts a new registry that shadows this one — its claims
|
|
33
|
+
* and messages stay inside it.
|
|
34
|
+
*/
|
|
35
|
+
export declare function FormErrorsProvider({ errors, children }: FormErrorsProviderProp): React.JSX.Element;
|
|
36
|
+
/** FormField calls this to claim its error-bag key while mounted (no-op outside a Form). */
|
|
37
|
+
export declare function useClaimErrorKey(name: string | undefined): void;
|
|
38
|
+
/**
|
|
39
|
+
* FormErrors — renders the error-bag entries no mounted `FormField name="…"` displays: server
|
|
40
|
+
* validation errors attached to hidden/derived fields (an `action_mode`, a `page`, a source-record
|
|
41
|
+
* id) that would otherwise fail silently. Place it inside a `Form errors={…}` — typically above
|
|
42
|
+
* the fields; it renders nothing while every entry is claimed or the bag is empty. Composed on
|
|
43
|
+
* `Alert tone="destructive"` (role="alert"), so appearing messages are announced assertively.
|
|
44
|
+
*/
|
|
45
|
+
export declare function FormErrors({ errors: errorsProp, title, className }: FormErrorsProp): React.JSX.Element | null;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { useTranslation } from "../../i18n/use-translation.js";
|
|
5
|
+
import { Alert, AlertContent, AlertDescription, AlertTitle } from "../feedback/alert.js";
|
|
6
|
+
const useIsomorphicLayoutEffect = typeof window === "undefined" ? React.useEffect : React.useLayoutEffect;
|
|
7
|
+
const FormErrorsRegistryContext = React.createContext(null);
|
|
8
|
+
function useFormErrorsRegistry() {
|
|
9
|
+
return React.useContext(FormErrorsRegistryContext);
|
|
10
|
+
}
|
|
11
|
+
function firstBagMessage(entry) {
|
|
12
|
+
const message = Array.isArray(entry) ? entry[0] : entry;
|
|
13
|
+
return message ? message : void 0;
|
|
14
|
+
}
|
|
15
|
+
function FormErrorsProvider({ errors, children }) {
|
|
16
|
+
const [claimed, setClaimed] = React.useState(/* @__PURE__ */ new Map());
|
|
17
|
+
const claim = React.useCallback((key) => {
|
|
18
|
+
setClaimed((prev) => {
|
|
19
|
+
const next = new Map(prev);
|
|
20
|
+
next.set(key, (next.get(key) ?? 0) + 1);
|
|
21
|
+
return next;
|
|
22
|
+
});
|
|
23
|
+
return () => {
|
|
24
|
+
setClaimed((prev) => {
|
|
25
|
+
const next = new Map(prev);
|
|
26
|
+
const count = (next.get(key) ?? 0) - 1;
|
|
27
|
+
if (count <= 0) next.delete(key);
|
|
28
|
+
else next.set(key, count);
|
|
29
|
+
return next;
|
|
30
|
+
});
|
|
31
|
+
};
|
|
32
|
+
}, []);
|
|
33
|
+
const value = React.useMemo(
|
|
34
|
+
() => ({ errors: errors ?? {}, claimed, claim }),
|
|
35
|
+
[errors, claimed, claim]
|
|
36
|
+
);
|
|
37
|
+
return /* @__PURE__ */ jsx(FormErrorsRegistryContext.Provider, { value, children });
|
|
38
|
+
}
|
|
39
|
+
function useClaimErrorKey(name) {
|
|
40
|
+
const registry = useFormErrorsRegistry();
|
|
41
|
+
const claim = registry?.claim;
|
|
42
|
+
useIsomorphicLayoutEffect(() => {
|
|
43
|
+
if (!name || !claim) return;
|
|
44
|
+
return claim(name);
|
|
45
|
+
}, [name, claim]);
|
|
46
|
+
}
|
|
47
|
+
function FormErrors({ errors: errorsProp, title, className }) {
|
|
48
|
+
const { t } = useTranslation();
|
|
49
|
+
const registry = useFormErrorsRegistry();
|
|
50
|
+
const errors = errorsProp ?? registry?.errors;
|
|
51
|
+
const claimed = registry?.claimed;
|
|
52
|
+
const messages = [];
|
|
53
|
+
for (const [key, entry] of Object.entries(errors ?? {})) {
|
|
54
|
+
if (entry == null || (claimed?.get(key) ?? 0) > 0) continue;
|
|
55
|
+
for (const message of Array.isArray(entry) ? entry : [entry]) {
|
|
56
|
+
if (message) messages.push({ key, message });
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (!messages.length) return null;
|
|
60
|
+
return /* @__PURE__ */ jsx(Alert, { tone: "destructive", className, children: /* @__PURE__ */ jsxs(AlertContent, { children: [
|
|
61
|
+
/* @__PURE__ */ jsx(AlertTitle, { children: title ?? t("dataEntry.formErrors.title") }),
|
|
62
|
+
messages.map(({ key, message }, index) => /* @__PURE__ */ jsx(AlertDescription, { children: message }, `${key}-${index}`))
|
|
63
|
+
] }) });
|
|
64
|
+
}
|
|
65
|
+
export {
|
|
66
|
+
FormErrors,
|
|
67
|
+
FormErrorsProvider,
|
|
68
|
+
firstBagMessage,
|
|
69
|
+
useClaimErrorKey,
|
|
70
|
+
useFormErrorsRegistry
|
|
71
|
+
};
|
|
@@ -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, label, required, helper, error, labelAddon, layout: layoutProp, labelWidth: labelWidthProp, controlWidth: controlWidthProp, colSpan, className, children, staticText, }: FormFieldProp): React.JSX.Element;
|
|
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;
|
|
@@ -3,16 +3,18 @@ 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 { mergeAriaIds } from "../../lib/field-a11y.js";
|
|
6
|
+
import { FieldNameContext, mergeAriaIds } from "../../lib/field-a11y.js";
|
|
7
7
|
import { useFormLayout } from "./form.js";
|
|
8
|
+
import { firstBagMessage, useClaimErrorKey, useFormErrorsRegistry } from "./form-errors.js";
|
|
8
9
|
const toCssLength = (v) => typeof v === "number" ? `${v}px` : v;
|
|
9
10
|
const FOCUSABLE_SELECTOR = 'input:not([type="hidden"]), select, textarea, button, [tabindex]:not([tabindex="-1"])';
|
|
10
11
|
function FormField({
|
|
11
12
|
id,
|
|
13
|
+
name,
|
|
12
14
|
label,
|
|
13
15
|
required,
|
|
14
16
|
helper,
|
|
15
|
-
error,
|
|
17
|
+
error: errorProp,
|
|
16
18
|
labelAddon,
|
|
17
19
|
layout: layoutProp,
|
|
18
20
|
labelWidth: labelWidthProp,
|
|
@@ -28,6 +30,9 @@ function FormField({
|
|
|
28
30
|
const controlWidth = controlWidthProp ?? form?.controlWidth;
|
|
29
31
|
const labelAlign = form?.labelAlign ?? "end";
|
|
30
32
|
const collapseBelow = form?.collapseBelow ?? "md";
|
|
33
|
+
const errorsRegistry = useFormErrorsRegistry();
|
|
34
|
+
useClaimErrorKey(name);
|
|
35
|
+
const error = errorProp ?? (name && errorsRegistry ? firstBagMessage(errorsRegistry.errors[name]) : void 0);
|
|
31
36
|
const autoId = React.useId();
|
|
32
37
|
const resolvedId = id ?? autoId;
|
|
33
38
|
const labelId = `${resolvedId}-label`;
|
|
@@ -39,6 +44,10 @@ function FormField({
|
|
|
39
44
|
"FormField expects a single React element child to receive aria-describedby/aria-errormessage; the helper text and error message will not be associated with the control. Pass plain text via `staticText` instead of `children` for a read-only value row."
|
|
40
45
|
);
|
|
41
46
|
}
|
|
47
|
+
const fieldNameContext = React.useMemo(
|
|
48
|
+
() => ({ labelId, label: typeof label === "string" ? label : void 0 }),
|
|
49
|
+
[labelId, label]
|
|
50
|
+
);
|
|
42
51
|
const childProps = React.isValidElement(children) ? children.props : void 0;
|
|
43
52
|
const mergeIds = mergeAriaIds;
|
|
44
53
|
const childWithA11y = isStatic ? (
|
|
@@ -112,7 +121,7 @@ function FormField({
|
|
|
112
121
|
labelAddon
|
|
113
122
|
] }),
|
|
114
123
|
/* @__PURE__ */ jsxs("div", { "data-slot": "form-field-control", className: "ui-form-field-control", children: [
|
|
115
|
-
childWithA11y,
|
|
124
|
+
isStatic ? childWithA11y : /* @__PURE__ */ jsx(FieldNameContext.Provider, { value: fieldNameContext, children: childWithA11y }),
|
|
116
125
|
helper ? /* @__PURE__ */ jsx("p", { id: helperId, className: "text-muted-foreground text-xs", children: helper }) : null,
|
|
117
126
|
error ? /* @__PURE__ */ jsx("p", { id: errorId, role: "alert", className: "text-destructive text-xs", children: error }) : null
|
|
118
127
|
] })
|
|
@@ -32,6 +32,7 @@ export declare const Form: React.ForwardRefExoticComponent<React.FormHTMLAttribu
|
|
|
32
32
|
collapseBelow?: BreakpointProp | false;
|
|
33
33
|
columns?: import("../../props/components/layout.prop.js").ResponsiveGridColumnsProp;
|
|
34
34
|
density?: import("../../props/index.js").DensityProp;
|
|
35
|
+
errors?: import("../../props/index.js").ErrorBagProp;
|
|
35
36
|
asChild?: boolean;
|
|
36
37
|
className?: import("../../props/index.js").ClassNameProp;
|
|
37
38
|
} & React.RefAttributes<HTMLFormElement>>;
|
|
@@ -3,6 +3,7 @@ import { jsx } from "react/jsx-runtime";
|
|
|
3
3
|
import { Slot } from "@radix-ui/react-slot";
|
|
4
4
|
import * as React from "react";
|
|
5
5
|
import { cn } from "../../lib/utils.js";
|
|
6
|
+
import { FormErrorsProvider } from "./form-errors.js";
|
|
6
7
|
import { ResponsiveGrid } from "../layout/responsive-grid.js";
|
|
7
8
|
const FormLayoutContext = React.createContext(null);
|
|
8
9
|
function useFormLayout() {
|
|
@@ -16,6 +17,7 @@ const Form = React.forwardRef(function Form2({
|
|
|
16
17
|
collapseBelow = "md",
|
|
17
18
|
columns,
|
|
18
19
|
density,
|
|
20
|
+
errors,
|
|
19
21
|
asChild = false,
|
|
20
22
|
className,
|
|
21
23
|
children,
|
|
@@ -26,17 +28,20 @@ const Form = React.forwardRef(function Form2({
|
|
|
26
28
|
[layout, labelWidth, controlWidth, labelAlign, collapseBelow]
|
|
27
29
|
);
|
|
28
30
|
const content = columns != null ? /* @__PURE__ */ jsx(ResponsiveGrid, { columns, children }) : children;
|
|
31
|
+
const withRegistry = (node) => errors !== void 0 ? /* @__PURE__ */ jsx(FormErrorsProvider, { errors, children: node }) : node;
|
|
29
32
|
if (asChild) {
|
|
30
|
-
return /* @__PURE__ */ jsx(FormLayoutContext.Provider, { value: ctx, children:
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
33
|
+
return /* @__PURE__ */ jsx(FormLayoutContext.Provider, { value: ctx, children: withRegistry(
|
|
34
|
+
/* @__PURE__ */ jsx(
|
|
35
|
+
Slot,
|
|
36
|
+
{
|
|
37
|
+
ref,
|
|
38
|
+
"data-slot": "form",
|
|
39
|
+
"data-layout": layout,
|
|
40
|
+
className: cn("ui-form", density && `ui-density-${density}`, className),
|
|
41
|
+
...props,
|
|
42
|
+
children
|
|
43
|
+
}
|
|
44
|
+
)
|
|
40
45
|
) });
|
|
41
46
|
}
|
|
42
47
|
return /* @__PURE__ */ jsx(
|
|
@@ -47,7 +52,7 @@ const Form = React.forwardRef(function Form2({
|
|
|
47
52
|
"data-layout": layout,
|
|
48
53
|
className: cn("ui-form", density && `ui-density-${density}`, className),
|
|
49
54
|
...props,
|
|
50
|
-
children: /* @__PURE__ */ jsx(FormLayoutContext.Provider, { value: ctx, children: content })
|
|
55
|
+
children: /* @__PURE__ */ jsx(FormLayoutContext.Provider, { value: ctx, children: withRegistry(content) })
|
|
51
56
|
}
|
|
52
57
|
);
|
|
53
58
|
});
|
|
@@ -13,6 +13,8 @@ export { Form, useFormLayout, type FormLayoutContextValue } from "./form.js";
|
|
|
13
13
|
export type { FormProp, FormProps } from "./form.js";
|
|
14
14
|
export { FormField } from "./form-field.js";
|
|
15
15
|
export type { FormFieldProp, FormFieldProps } from "./form-field.js";
|
|
16
|
+
export { FormErrors, FormErrorsProvider } from "./form-errors.js";
|
|
17
|
+
export type { FormErrorsProp, FormErrorsProps, FormErrorsProviderProp, FormErrorsProviderProps, } from "./form-errors.js";
|
|
16
18
|
export { Field } from "./field.js";
|
|
17
19
|
export type { FieldProps } from "./field.js";
|
|
18
20
|
export { SearchInput } from "./search-input.js";
|
|
@@ -19,6 +19,7 @@ import { Radio, RadioGroup, RadioItem, RadioGroupRoot } from "./radio.js";
|
|
|
19
19
|
import { Textarea } from "./textarea.js";
|
|
20
20
|
import { Form, useFormLayout } from "./form.js";
|
|
21
21
|
import { FormField } from "./form-field.js";
|
|
22
|
+
import { FormErrors, FormErrorsProvider } from "./form-errors.js";
|
|
22
23
|
import { Field } from "./field.js";
|
|
23
24
|
import { SearchInput } from "./search-input.js";
|
|
24
25
|
import { Switch } from "./switch.js";
|
|
@@ -69,6 +70,8 @@ export {
|
|
|
69
70
|
DateRangePicker,
|
|
70
71
|
Field,
|
|
71
72
|
Form,
|
|
73
|
+
FormErrors,
|
|
74
|
+
FormErrorsProvider,
|
|
72
75
|
FormField,
|
|
73
76
|
Input,
|
|
74
77
|
InputOTP,
|
|
@@ -3,6 +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
7
|
import { cn } from "../../lib/utils.js";
|
|
7
8
|
const inputBaseClass = [
|
|
8
9
|
"ui-control border-input bg-background w-full min-w-0 rounded-[var(--control-radius)] transition-[color,box-shadow] outline-none",
|
|
@@ -27,6 +28,10 @@ const Input = React.forwardRef(
|
|
|
27
28
|
...props
|
|
28
29
|
}, ref) => {
|
|
29
30
|
const { t } = useTranslation();
|
|
31
|
+
const nameFallback = useFieldNameFallback({
|
|
32
|
+
"aria-label": props["aria-label"],
|
|
33
|
+
"aria-labelledby": props["aria-labelledby"]
|
|
34
|
+
});
|
|
30
35
|
const innerRef = React.useRef(null);
|
|
31
36
|
const setRefs = React.useCallback(
|
|
32
37
|
(node) => {
|
|
@@ -71,7 +76,8 @@ const Input = React.forwardRef(
|
|
|
71
76
|
defaultValue,
|
|
72
77
|
onChange,
|
|
73
78
|
className: cn(inputBaseClass, className),
|
|
74
|
-
...props
|
|
79
|
+
...props,
|
|
80
|
+
...nameFallback
|
|
75
81
|
}
|
|
76
82
|
);
|
|
77
83
|
}
|
|
@@ -112,7 +118,8 @@ const Input = React.forwardRef(
|
|
|
112
118
|
(showClear || trailingIcon != null) && "pe-9",
|
|
113
119
|
className
|
|
114
120
|
),
|
|
115
|
-
...props
|
|
121
|
+
...props,
|
|
122
|
+
...nameFallback
|
|
116
123
|
}
|
|
117
124
|
),
|
|
118
125
|
trailing != null ? /* @__PURE__ */ jsx("span", { className: "absolute inset-y-0 end-2 inline-flex items-center", children: trailing }) : null
|
|
@@ -3,6 +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
7
|
import { cn } from "../../lib/utils.js";
|
|
7
8
|
import { controlOpenRingClass } from "../../lib/control-styles.js";
|
|
8
9
|
import { Button } from "../general/button.js";
|
|
@@ -49,6 +50,12 @@ function SearchSelect({
|
|
|
49
50
|
"aria-required": ariaRequired
|
|
50
51
|
}) {
|
|
51
52
|
const { t } = useTranslation();
|
|
53
|
+
const nameFallback = useFieldNameFallback({
|
|
54
|
+
"aria-label": ariaLabel,
|
|
55
|
+
"aria-labelledby": ariaLabelledby
|
|
56
|
+
});
|
|
57
|
+
const triggerAriaLabel = ariaLabel ?? nameFallback["aria-label"];
|
|
58
|
+
const triggerAriaLabelledby = ariaLabelledby ?? nameFallback["aria-labelledby"];
|
|
52
59
|
const reactId = React.useId();
|
|
53
60
|
const listId = `${reactId}-listbox`;
|
|
54
61
|
const optionDomId = (optionValue) => `${reactId}-opt-${optionValue}`;
|
|
@@ -238,8 +245,8 @@ function SearchSelect({
|
|
|
238
245
|
size,
|
|
239
246
|
"aria-expanded": open,
|
|
240
247
|
"aria-controls": open ? listId : void 0,
|
|
241
|
-
"aria-label":
|
|
242
|
-
"aria-labelledby":
|
|
248
|
+
"aria-label": triggerAriaLabel,
|
|
249
|
+
"aria-labelledby": triggerAriaLabelledby,
|
|
243
250
|
"aria-describedby": ariaDescribedby,
|
|
244
251
|
"aria-errormessage": ariaErrorMessage,
|
|
245
252
|
"aria-invalid": ariaInvalid,
|
|
@@ -272,8 +279,8 @@ function SearchSelect({
|
|
|
272
279
|
/* @__PURE__ */ jsx(
|
|
273
280
|
PopoverContent,
|
|
274
281
|
{
|
|
275
|
-
"aria-label":
|
|
276
|
-
"aria-labelledby":
|
|
282
|
+
"aria-label": triggerAriaLabelledby ? void 0 : triggerAriaLabel ?? resolvedPlaceholder,
|
|
283
|
+
"aria-labelledby": triggerAriaLabelledby,
|
|
277
284
|
align: "start",
|
|
278
285
|
sideOffset: 4,
|
|
279
286
|
collisionPadding: 12,
|
|
@@ -5,7 +5,12 @@ import * as SelectPrimitive from "@radix-ui/react-select";
|
|
|
5
5
|
import { ChevronDown, ChevronUp, X } from "lucide-react";
|
|
6
6
|
import { cn } from "../../lib/utils.js";
|
|
7
7
|
import { controlTriggerClass } from "../../lib/control-styles.js";
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
mergeAriaIds,
|
|
10
|
+
omitFieldA11y,
|
|
11
|
+
pickFieldA11y,
|
|
12
|
+
useFieldNameFallback
|
|
13
|
+
} from "../../lib/field-a11y.js";
|
|
9
14
|
import { SearchSelect } from "./search-select.js";
|
|
10
15
|
import { useTranslation } from "../../i18n/use-translation.js";
|
|
11
16
|
function isDataSelect(props) {
|
|
@@ -32,13 +37,24 @@ function SelectValue(props) {
|
|
|
32
37
|
const SelectTrigger = React.forwardRef(({ className, children, size = "md", showIndicator = true, ...props }, ref) => {
|
|
33
38
|
const field = React.useContext(SelectFieldA11yContext);
|
|
34
39
|
const ownsName = props["aria-label"] !== void 0 || props["aria-labelledby"] !== void 0;
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
"aria-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
const fieldOwnsName = field?.["aria-label"] !== void 0 || field?.["aria-labelledby"] !== void 0;
|
|
41
|
+
const nameFallback = useFieldNameFallback({
|
|
42
|
+
"aria-label": ownsName ? props["aria-label"] : field?.["aria-label"],
|
|
43
|
+
"aria-labelledby": ownsName ? props["aria-labelledby"] : field?.["aria-labelledby"]
|
|
44
|
+
});
|
|
45
|
+
const fieldA11y = field || nameFallback["aria-labelledby"] !== void 0 ? {
|
|
46
|
+
id: props.id ?? field?.id,
|
|
47
|
+
...ownsName ? {} : fieldOwnsName ? {
|
|
48
|
+
"aria-label": field?.["aria-label"],
|
|
49
|
+
"aria-labelledby": field?.["aria-labelledby"]
|
|
50
|
+
} : nameFallback,
|
|
51
|
+
"aria-describedby": mergeAriaIds(props["aria-describedby"], field?.["aria-describedby"]),
|
|
52
|
+
"aria-errormessage": mergeAriaIds(
|
|
53
|
+
props["aria-errormessage"],
|
|
54
|
+
field?.["aria-errormessage"]
|
|
55
|
+
),
|
|
56
|
+
"aria-required": props["aria-required"] ?? field?.["aria-required"],
|
|
57
|
+
"aria-invalid": props["aria-invalid"] ?? field?.["aria-invalid"]
|
|
42
58
|
} : void 0;
|
|
43
59
|
return /* @__PURE__ */ jsxs(
|
|
44
60
|
SelectPrimitive.Trigger,
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
"use client";
|
|
1
2
|
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
import { mergeAriaIds } from "../../lib/field-a11y.js";
|
|
2
4
|
import { cn } from "../../lib/utils.js";
|
|
3
5
|
import { flexGapClass } from "../../lib/variants.js";
|
|
4
6
|
function Flex({
|
|
@@ -13,6 +15,20 @@ function Flex({
|
|
|
13
15
|
children,
|
|
14
16
|
...props
|
|
15
17
|
}) {
|
|
18
|
+
let domProps = props;
|
|
19
|
+
if (props.role === void 0 && (props["aria-label"] !== void 0 || props["aria-labelledby"] !== void 0)) {
|
|
20
|
+
const {
|
|
21
|
+
"aria-required": _ariaRequired,
|
|
22
|
+
"aria-invalid": _ariaInvalid,
|
|
23
|
+
"aria-errormessage": ariaErrorMessage,
|
|
24
|
+
...allowed
|
|
25
|
+
} = props;
|
|
26
|
+
domProps = {
|
|
27
|
+
...allowed,
|
|
28
|
+
role: "group",
|
|
29
|
+
"aria-describedby": mergeAriaIds(props["aria-describedby"], ariaErrorMessage)
|
|
30
|
+
};
|
|
31
|
+
}
|
|
16
32
|
return /* @__PURE__ */ jsx(
|
|
17
33
|
"div",
|
|
18
34
|
{
|
|
@@ -23,7 +39,7 @@ function Flex({
|
|
|
23
39
|
"data-hide-below": hideBelow,
|
|
24
40
|
"data-hide-from": hideFrom,
|
|
25
41
|
className: cn("ui-flex", flexGapClass[gap], className),
|
|
26
|
-
...
|
|
42
|
+
...domProps,
|
|
27
43
|
children
|
|
28
44
|
}
|
|
29
45
|
);
|
|
@@ -21,6 +21,7 @@ function FormFieldControl({
|
|
|
21
21
|
FormField,
|
|
22
22
|
{
|
|
23
23
|
id: fieldName,
|
|
24
|
+
name: fieldName,
|
|
24
25
|
label,
|
|
25
26
|
required,
|
|
26
27
|
helper,
|
|
@@ -46,6 +47,7 @@ function FormFieldControl({
|
|
|
46
47
|
FormField,
|
|
47
48
|
{
|
|
48
49
|
id: String(name),
|
|
50
|
+
name: String(name),
|
|
49
51
|
label,
|
|
50
52
|
required,
|
|
51
53
|
helper,
|
|
@@ -128,6 +128,9 @@
|
|
|
128
128
|
"denied": "You do not have permission to view branches",
|
|
129
129
|
"loading": "Loading branches…",
|
|
130
130
|
"noMatches": "No branches match your search"
|
|
131
|
+
},
|
|
132
|
+
"formErrors": {
|
|
133
|
+
"title": "Please review the following errors"
|
|
131
134
|
}
|
|
132
135
|
},
|
|
133
136
|
"feedback": {
|
|
@@ -125,6 +125,9 @@
|
|
|
125
125
|
"denied": "Bạn không có quyền xem chi nhánh",
|
|
126
126
|
"loading": "Đang tải chi nhánh…",
|
|
127
127
|
"noMatches": "Không có chi nhánh khớp với tìm kiếm"
|
|
128
|
+
},
|
|
129
|
+
"formErrors": {
|
|
130
|
+
"title": "Dữ liệu nhập có lỗi, vui lòng kiểm tra"
|
|
128
131
|
}
|
|
129
132
|
},
|
|
130
133
|
"feedback": {
|
package/dist/lib/field-a11y.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as React from "react";
|
|
2
2
|
/**
|
|
3
3
|
* The accessible-name / description / validation contract that {@link FormField} injects onto its
|
|
4
4
|
* single control child (via `cloneElement`). EVERY form-capable `@godxjp/ui` component accepts
|
|
@@ -53,6 +53,34 @@ export declare function omitFieldA11y<T extends FieldA11yProps>(props: T): Omit<
|
|
|
53
53
|
* both — `aria-labelledby` takes precedence and the redundant `aria-label` only adds noise).
|
|
54
54
|
*/
|
|
55
55
|
export declare function resolveFieldA11y(props: FieldA11yProps, intrinsicAriaLabel?: string): FieldA11yProps;
|
|
56
|
+
/**
|
|
57
|
+
* The FormField label made reachable by NESTED controls (gh#303).
|
|
58
|
+
*
|
|
59
|
+
* `cloneElement` can only wire the field-a11y contract onto FormField's single direct child. When
|
|
60
|
+
* that child is a layout wrapper (a `Flex` holding a range from/to pair, a 年/月 input+select
|
|
61
|
+
* combo), the naming attributes stop on the wrapper `div` and every control inside is left with no
|
|
62
|
+
* accessible name at all (axe: `label` on the inputs, `button-name` on select/combobox triggers).
|
|
63
|
+
*
|
|
64
|
+
* FormField therefore also publishes its label through this context, and each control's semantic
|
|
65
|
+
* focus target picks it up as a LAST-RESORT name via {@link useFieldNameFallback}: a control that
|
|
66
|
+
* already has a name — its own `aria-label`/`aria-labelledby`, or the one FormField cloned onto it
|
|
67
|
+
* as the direct child — keeps it untouched. Multiple nested controls then all announce the field's
|
|
68
|
+
* label; a consumer wanting distinct names (e.g. "開始日" / "終了日") sets `aria-label` per
|
|
69
|
+
* control, which always wins.
|
|
70
|
+
*/
|
|
71
|
+
export interface FieldNameContextValue {
|
|
72
|
+
/** DOM id of FormField's visible label element (the `aria-labelledby` target). */
|
|
73
|
+
labelId: string;
|
|
74
|
+
/** The label's plain-text content, when it is a string (belt-and-suspenders `aria-label`). */
|
|
75
|
+
label?: string;
|
|
76
|
+
}
|
|
77
|
+
export declare const FieldNameContext: React.Context<FieldNameContextValue | null>;
|
|
78
|
+
/**
|
|
79
|
+
* Resolve the last-resort accessible name for a control's semantic focus target (see
|
|
80
|
+
* {@link FieldNameContext}). Returns `{}` — never clobbering anything — unless the control is
|
|
81
|
+
* inside a FormField AND still nameless after its own props and the cloned contract are applied.
|
|
82
|
+
*/
|
|
83
|
+
export declare function useFieldNameFallback(name: Pick<FieldA11yProps, "aria-label" | "aria-labelledby">): Pick<FieldA11yProps, "aria-label" | "aria-labelledby">;
|
|
56
84
|
/**
|
|
57
85
|
* The field-a11y attributes valid on a **group container** (`role="group"`), used by composite
|
|
58
86
|
* controls that have no single labelable focus target — CheckboxGroup, and the two-input range
|
package/dist/lib/field-a11y.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import * as React from "react";
|
|
1
3
|
const FIELD_A11Y_KEYS = [
|
|
2
4
|
"aria-label",
|
|
3
5
|
"aria-labelledby",
|
|
@@ -34,6 +36,17 @@ function resolveFieldA11y(props, intrinsicAriaLabel) {
|
|
|
34
36
|
}
|
|
35
37
|
return picked;
|
|
36
38
|
}
|
|
39
|
+
const FieldNameContext = React.createContext(null);
|
|
40
|
+
function useFieldNameFallback(name) {
|
|
41
|
+
const field = React.useContext(FieldNameContext);
|
|
42
|
+
if (!field || name["aria-label"] !== void 0 || name["aria-labelledby"] !== void 0) {
|
|
43
|
+
return {};
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
"aria-labelledby": field.labelId,
|
|
47
|
+
...field.label !== void 0 ? { "aria-label": field.label } : {}
|
|
48
|
+
};
|
|
49
|
+
}
|
|
37
50
|
function pickGroupFieldA11y(props) {
|
|
38
51
|
const describedBy = mergeAriaIds(props["aria-describedby"], props["aria-errormessage"]);
|
|
39
52
|
return {
|
|
@@ -42,9 +55,11 @@ function pickGroupFieldA11y(props) {
|
|
|
42
55
|
};
|
|
43
56
|
}
|
|
44
57
|
export {
|
|
58
|
+
FieldNameContext,
|
|
45
59
|
mergeAriaIds,
|
|
46
60
|
omitFieldA11y,
|
|
47
61
|
pickFieldA11y,
|
|
48
62
|
pickGroupFieldA11y,
|
|
49
|
-
resolveFieldA11y
|
|
63
|
+
resolveFieldA11y,
|
|
64
|
+
useFieldNameFallback
|
|
50
65
|
};
|
|
@@ -8,7 +8,7 @@ import type { DateRange } from "react-day-picker";
|
|
|
8
8
|
import type * as React from "react";
|
|
9
9
|
import type { UploadFileItem } from "../../components/data-entry/upload-types.js";
|
|
10
10
|
import type { FieldA11yProps } from "../../lib/field-a11y.js";
|
|
11
|
-
import type { ClassNameProp, DisabledProp, EmptyMessageProp, ErrorProp, HelperProp, IdProp, LabelProp, NameProp, OnChangeProp, OnValueChangeProp, OnSearchChangeProp, OpenProp, OnOpenChangeProp, PlaceholderProp, RequiredProp, ValueProp, DefaultValueProp, FormLayoutProp, WidthProp, BreakpointProp, DensityProp, SizeProp } from "../vocabulary/index.js";
|
|
11
|
+
import type { ClassNameProp, DisabledProp, EmptyMessageProp, ErrorBagProp, ErrorProp, HelperProp, IdProp, LabelProp, NameProp, OnChangeProp, OnValueChangeProp, OnSearchChangeProp, OpenProp, OnOpenChangeProp, PlaceholderProp, RequiredProp, ValueProp, DefaultValueProp, FormLayoutProp, WidthProp, BreakpointProp, DensityProp, SizeProp, TitleProp } from "../vocabulary/index.js";
|
|
12
12
|
import type { ResponsiveGridColumnsProp } from "./layout.prop.js";
|
|
13
13
|
/** One-outline-per-group appearance for the compound InputOTP control. */
|
|
14
14
|
export type InputOTPGroupAppearanceProp = "slots" | "grouped";
|
|
@@ -88,6 +88,15 @@ export type FormProp = React.FormHTMLAttributes<HTMLFormElement> & {
|
|
|
88
88
|
collapseBelow?: BreakpointProp | false;
|
|
89
89
|
columns?: ResponsiveGridColumnsProp;
|
|
90
90
|
density?: DensityProp;
|
|
91
|
+
/**
|
|
92
|
+
* Server validation error bag (e.g. Inertia's `form.errors`). Each `FormField name="…"` inside
|
|
93
|
+
* resolves its own message from the bag automatically and CLAIMS its key; `<FormErrors />`
|
|
94
|
+
* renders the remaining, unclaimed entries — errors attached to hidden/derived fields that no
|
|
95
|
+
* visible field displays. Works in both the `<form>` and `asChild` modes. A Form WITHOUT this
|
|
96
|
+
* prop joins a surrounding `FormErrorsProvider` instead (sibling Card+Form sections sharing one
|
|
97
|
+
* bag); a Form WITH it starts its own (shadowing) registry.
|
|
98
|
+
*/
|
|
99
|
+
errors?: ErrorBagProp;
|
|
91
100
|
/**
|
|
92
101
|
* Render the caller's own element instead of a `<form>`, keeping only the layout context.
|
|
93
102
|
* For routing libraries that own the form element (Inertia, TanStack Form) — two `<form>`
|
|
@@ -110,6 +119,13 @@ export type FormProp = React.FormHTMLAttributes<HTMLFormElement> & {
|
|
|
110
119
|
export type FormFieldProp = {
|
|
111
120
|
/** Optional — auto-generated and injected into the child control when omitted. */
|
|
112
121
|
id?: IdProp;
|
|
122
|
+
/**
|
|
123
|
+
* Error-bag key of this field. When the surrounding `Form` carries `errors`, the field
|
|
124
|
+
* resolves its message from `errors[name]` automatically (an explicit `error` prop wins)
|
|
125
|
+
* and CLAIMS the key so `<FormErrors />` does not repeat it. Not injected into the child —
|
|
126
|
+
* pass `name` on the control itself for native form submission.
|
|
127
|
+
*/
|
|
128
|
+
name?: NameProp;
|
|
113
129
|
label: LabelProp;
|
|
114
130
|
required?: RequiredProp;
|
|
115
131
|
helper?: HelperProp;
|
|
@@ -130,6 +146,12 @@ export type FormFieldProp = {
|
|
|
130
146
|
} | {
|
|
131
147
|
/** Optional — auto-generated and injected into the child control when omitted. */
|
|
132
148
|
id?: IdProp;
|
|
149
|
+
/**
|
|
150
|
+
* Error-bag key of this field. When the surrounding `Form` carries `errors`, the field
|
|
151
|
+
* resolves its message from `errors[name]` automatically (an explicit `error` prop wins)
|
|
152
|
+
* and CLAIMS the key so `<FormErrors />` does not repeat it.
|
|
153
|
+
*/
|
|
154
|
+
name?: NameProp;
|
|
133
155
|
label: LabelProp;
|
|
134
156
|
required?: RequiredProp;
|
|
135
157
|
helper?: HelperProp;
|
|
@@ -149,6 +171,37 @@ export type FormFieldProp = {
|
|
|
149
171
|
/** Read-only value — renders as `Descriptions.Item`-matched text instead of a control. */
|
|
150
172
|
staticText: React.ReactNode;
|
|
151
173
|
};
|
|
174
|
+
/**
|
|
175
|
+
* @see FormErrors — the "no field to stand on" error summary. Renders the entries of the
|
|
176
|
+
* surrounding `Form`'s error bag that no mounted `FormField name="…"` has claimed — validation
|
|
177
|
+
* errors attached to hidden/derived fields (`action_mode`, `page`, a source-record id…) that
|
|
178
|
+
* would otherwise fail silently. Renders nothing while every error is claimed or the bag is empty.
|
|
179
|
+
*/
|
|
180
|
+
export type FormErrorsProp = {
|
|
181
|
+
/**
|
|
182
|
+
* Explicit error bag — overrides the surrounding `Form errors`. Use it when the component sits
|
|
183
|
+
* outside a `Form` (e.g. inside `FormRoot`); field claiming still applies when a `Form` provides
|
|
184
|
+
* the registry.
|
|
185
|
+
*/
|
|
186
|
+
errors?: ErrorBagProp;
|
|
187
|
+
/** Heading above the messages. Defaults to the localized "please review your input" title. */
|
|
188
|
+
title?: TitleProp;
|
|
189
|
+
className?: ClassNameProp;
|
|
190
|
+
};
|
|
191
|
+
/**
|
|
192
|
+
* @see FormErrorsProvider — one shared error registry over a REGION of sibling Forms. An edit
|
|
193
|
+
* screen split into several Card+Form sections shares a single server bag: wrap the sections in
|
|
194
|
+
* this provider instead of passing `errors` to each Form, and every `FormField name="…"` inside
|
|
195
|
+
* (Forms without their own `errors` join the surrounding registry) claims into the same registry,
|
|
196
|
+
* so one `<FormErrors />` anywhere in the region renders exactly the unclaimed remainder.
|
|
197
|
+
* `Form errors={…}` renders this provider itself — a Form WITH its own `errors` starts a new
|
|
198
|
+
* (shadowing) registry.
|
|
199
|
+
*/
|
|
200
|
+
export type FormErrorsProviderProp = {
|
|
201
|
+
/** Server validation error bag shared by every Form/FormField in the region. */
|
|
202
|
+
errors?: ErrorBagProp;
|
|
203
|
+
children?: React.ReactNode;
|
|
204
|
+
};
|
|
152
205
|
/** @see SearchInput */
|
|
153
206
|
export type SearchInputProp = FieldA11yProps & {
|
|
154
207
|
id?: IdProp;
|
package/dist/props/registry.d.ts
CHANGED
|
@@ -69,6 +69,11 @@ export declare const VOCABULARY_REGISTRY: {
|
|
|
69
69
|
readonly category: "shared";
|
|
70
70
|
readonly description: "Validation error message";
|
|
71
71
|
};
|
|
72
|
+
readonly ErrorBagProp: {
|
|
73
|
+
readonly file: "vocabulary/shared.prop.ts";
|
|
74
|
+
readonly category: "shared";
|
|
75
|
+
readonly description: "Server validation error bag keyed by field name (Laravel/Inertia errors)";
|
|
76
|
+
};
|
|
72
77
|
readonly PlaceholderProp: {
|
|
73
78
|
readonly file: "vocabulary/shared.prop.ts";
|
|
74
79
|
readonly category: "shared";
|
|
@@ -863,12 +868,22 @@ export declare const COMPONENT_PROP_REGISTRY: {
|
|
|
863
868
|
readonly FormProp: {
|
|
864
869
|
readonly group: "data-entry";
|
|
865
870
|
readonly file: "components/data-entry.prop.ts";
|
|
866
|
-
readonly vocabulary: readonly ["FormLayoutProp", "WidthProp", "BreakpointProp", "DensityProp"];
|
|
871
|
+
readonly vocabulary: readonly ["FormLayoutProp", "WidthProp", "BreakpointProp", "DensityProp", "ErrorBagProp"];
|
|
867
872
|
};
|
|
868
873
|
readonly FormFieldProp: {
|
|
869
874
|
readonly group: "data-entry";
|
|
870
875
|
readonly file: "components/data-entry.prop.ts";
|
|
871
|
-
readonly vocabulary: readonly ["IdProp", "LabelProp", "RequiredProp", "HelperProp", "ErrorProp", "FormLayoutProp", "WidthProp"];
|
|
876
|
+
readonly vocabulary: readonly ["IdProp", "NameProp", "LabelProp", "RequiredProp", "HelperProp", "ErrorProp", "FormLayoutProp", "WidthProp"];
|
|
877
|
+
};
|
|
878
|
+
readonly FormErrorsProp: {
|
|
879
|
+
readonly group: "data-entry";
|
|
880
|
+
readonly file: "components/data-entry.prop.ts";
|
|
881
|
+
readonly vocabulary: readonly ["ErrorBagProp", "TitleProp", "ClassNameProp"];
|
|
882
|
+
};
|
|
883
|
+
readonly FormErrorsProviderProp: {
|
|
884
|
+
readonly group: "data-entry";
|
|
885
|
+
readonly file: "components/data-entry.prop.ts";
|
|
886
|
+
readonly vocabulary: readonly ["ErrorBagProp"];
|
|
872
887
|
};
|
|
873
888
|
readonly SearchInputProp: {
|
|
874
889
|
readonly group: "data-entry";
|
package/dist/props/registry.js
CHANGED
|
@@ -65,6 +65,11 @@ const VOCABULARY_REGISTRY = {
|
|
|
65
65
|
category: "shared",
|
|
66
66
|
description: "Validation error message"
|
|
67
67
|
},
|
|
68
|
+
ErrorBagProp: {
|
|
69
|
+
file: "vocabulary/shared.prop.ts",
|
|
70
|
+
category: "shared",
|
|
71
|
+
description: "Server validation error bag keyed by field name (Laravel/Inertia errors)"
|
|
72
|
+
},
|
|
68
73
|
PlaceholderProp: {
|
|
69
74
|
file: "vocabulary/shared.prop.ts",
|
|
70
75
|
category: "shared",
|
|
@@ -959,13 +964,14 @@ const COMPONENT_PROP_REGISTRY = {
|
|
|
959
964
|
FormProp: {
|
|
960
965
|
group: "data-entry",
|
|
961
966
|
file: "components/data-entry.prop.ts",
|
|
962
|
-
vocabulary: ["FormLayoutProp", "WidthProp", "BreakpointProp", "DensityProp"]
|
|
967
|
+
vocabulary: ["FormLayoutProp", "WidthProp", "BreakpointProp", "DensityProp", "ErrorBagProp"]
|
|
963
968
|
},
|
|
964
969
|
FormFieldProp: {
|
|
965
970
|
group: "data-entry",
|
|
966
971
|
file: "components/data-entry.prop.ts",
|
|
967
972
|
vocabulary: [
|
|
968
973
|
"IdProp",
|
|
974
|
+
"NameProp",
|
|
969
975
|
"LabelProp",
|
|
970
976
|
"RequiredProp",
|
|
971
977
|
"HelperProp",
|
|
@@ -974,6 +980,16 @@ const COMPONENT_PROP_REGISTRY = {
|
|
|
974
980
|
"WidthProp"
|
|
975
981
|
]
|
|
976
982
|
},
|
|
983
|
+
FormErrorsProp: {
|
|
984
|
+
group: "data-entry",
|
|
985
|
+
file: "components/data-entry.prop.ts",
|
|
986
|
+
vocabulary: ["ErrorBagProp", "TitleProp", "ClassNameProp"]
|
|
987
|
+
},
|
|
988
|
+
FormErrorsProviderProp: {
|
|
989
|
+
group: "data-entry",
|
|
990
|
+
file: "components/data-entry.prop.ts",
|
|
991
|
+
vocabulary: ["ErrorBagProp"]
|
|
992
|
+
},
|
|
977
993
|
SearchInputProp: {
|
|
978
994
|
group: "data-entry",
|
|
979
995
|
file: "components/data-entry.prop.ts",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Barrel — all vocabulary prop types. */
|
|
2
|
-
export type { ClassNameProp, ChildrenProp, IdProp, OpenProp, DefaultOpenProp, OnOpenChangeProp, HandlerProp, PendingProp, RequiredProp, DisabledProp, LabelProp, HelperProp, ErrorProp, PlaceholderProp, NameProp, ValueProp, DefaultValueProp, OnValueChangeProp, OnChangeProp, OnClickProp, AsChildProp, WidthProp, } from "./shared.prop.js";
|
|
2
|
+
export type { ClassNameProp, ChildrenProp, IdProp, OpenProp, DefaultOpenProp, OnOpenChangeProp, HandlerProp, PendingProp, RequiredProp, DisabledProp, LabelProp, HelperProp, ErrorProp, ErrorBagProp, PlaceholderProp, NameProp, ValueProp, DefaultValueProp, OnValueChangeProp, OnChangeProp, OnClickProp, AsChildProp, WidthProp, } from "./shared.prop.js";
|
|
3
3
|
export type { TitleProp, SubtitleProp, StatusProp, DescriptionProp, ExtraProp, FooterProp, ActionProp, IconProp, ConfirmLabelProp, CancelLabelProp, ActionsProp, EmptyMessageProp, } from "./content.prop.js";
|
|
4
4
|
export type { PageDensityProp, PageContainerVariantProp, CenteredShellWidthProp, CenteredShellAlignProp, CenteredShellPresetProp, ErrorSurfaceModeProp, ErrorSurfaceStatusProp, AuthShellPresetProp, TableDensityProp, DensityProp, GapProp, } from "./layout.prop.js";
|
|
5
5
|
export type { ButtonVariantProp, ButtonSizeProp, BadgeVariantProp, AppSettingPickerAppearanceProp, ShapeProp, AvatarShapeProp, TextSizeProp, TextToneProp, FontWeightProp, HeadingLevelProp, TextAlignProp, SizeProp, FormLayoutProp, BreakpointProp, ConfirmVariantProp, ToneProp, AlertVariantProp, SortDirectionProp, ColumnAlignProp, SortStateProp, RevealDelayProp, } from "./interaction.prop.js";
|
|
@@ -29,6 +29,11 @@ export type LabelProp = React.ReactNode;
|
|
|
29
29
|
export type HelperProp = React.ReactNode;
|
|
30
30
|
/** Validation error message. */
|
|
31
31
|
export type ErrorProp = React.ReactNode;
|
|
32
|
+
/**
|
|
33
|
+
* Server validation error bag, keyed by field name — the shape Laravel hands Inertia
|
|
34
|
+
* (`errors: { field: "message" }`) or a JSON API returns (`{ field: ["m1", "m2"] }`).
|
|
35
|
+
*/
|
|
36
|
+
export type ErrorBagProp = Partial<Record<string, string | string[]>>;
|
|
32
37
|
/** Placeholder text for inputs. */
|
|
33
38
|
export type PlaceholderProp = string;
|
|
34
39
|
/** HTML input `name` attribute. */
|