@gntik-ai/ui 0.7.0 → 0.10.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/forms.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
 
2
- import { FormHTMLAttributes, ReactNode } from "react";
3
- import { Control, ControllerFieldState, ControllerRenderProps, FieldErrors, FieldPath, FieldValues, RegisterOptions, Resolver, SubmitErrorHandler, SubmitHandler, UseFormReturn } from "react-hook-form";
2
+ import { t as ComponentDoc } from "./doc-C6r06QOg.js";
3
+ import { FormHTMLAttributes, ReactNode, RefCallback } from "react";
4
+ import { Control, ControllerFieldState, ControllerRenderProps, FieldArray as FieldArray$1, FieldArrayPath, FieldErrors, FieldPath, FieldPathValue, FieldValues, Path, RegisterOptions, Resolver, SubmitErrorHandler, SubmitHandler, UseFieldArrayProps, UseFormReturn } from "react-hook-form";
4
5
  //#region src/forms/useFieldProps.d.ts
5
6
  interface UseFieldPropsOptions<TValues extends FieldValues, TName extends FieldPath<TValues>> {
6
7
  /** The form's `control`. Defaults to the nearest `<FormProvider>` / `<Form>`. */
@@ -37,34 +38,83 @@ interface UseFieldPropsResult<TValues extends FieldValues, TName extends FieldPa
37
38
  */
38
39
  export declare function useFieldProps<TValues extends FieldValues = FieldValues, TName extends FieldPath<TValues> = FieldPath<TValues>>(name: TName, { control, rules, disabled }?: UseFieldPropsOptions<TValues, TName>): UseFieldPropsResult<TValues, TName>;
39
40
  //#endregion
41
+ //#region src/forms/bindings.d.ts
42
+ /** The public control props returned by useFieldProps. */
43
+ interface BindingControlProps<TValue> {
44
+ name: string;
45
+ value: TValue;
46
+ onChange: ControllerRenderProps['onChange'];
47
+ onBlur: () => void;
48
+ ref: ControllerRenderProps['ref'];
49
+ disabled?: boolean;
50
+ readOnly?: boolean;
51
+ 'aria-invalid'?: true;
52
+ }
53
+ interface ValueBindingProps<TValue> {
54
+ name: string;
55
+ value: NonNullable<TValue> | null;
56
+ onValueChange: (value: TValue | null) => void;
57
+ ref: RefCallback<HTMLElement>;
58
+ disabled?: boolean;
59
+ readOnly?: boolean;
60
+ 'aria-invalid'?: true;
61
+ }
62
+ interface CheckedBindingProps extends Omit<ValueBindingProps<boolean>, 'value' | 'onValueChange'> {
63
+ checked: boolean;
64
+ onCheckedChange: (checked: boolean) => void;
65
+ }
66
+ /** Maps value/onChange controls to the kit's value/onValueChange API. Changes also touch the field. */
67
+ export declare function toValueProps<TValue>({ value, onChange, onBlur, ...props }: BindingControlProps<TValue>): ValueBindingProps<TValue>;
68
+ /** Maps value/onChange controls to the kit's checked/onCheckedChange API. */
69
+ export declare function toCheckedProps<TValue>({ value, onChange, onBlur, ...props }: BindingControlProps<TValue>): CheckedBindingProps;
70
+ //#endregion
40
71
  //#region src/forms/FormField.d.ts
41
- interface FormFieldProps<TValues extends FieldValues, TName extends FieldPath<TValues>> extends UseFieldPropsOptions<TValues, TName> {
72
+ interface EnhancedFieldProps {
73
+ disabled?: boolean;
74
+ readOnly?: boolean;
75
+ }
76
+ type EnhanceFieldProps = (field: {
77
+ name: string;
78
+ }) => EnhancedFieldProps | undefined;
79
+ type FormFieldWidth = 'xs' | 'sm' | 'md' | 'lg' | 'xl';
80
+ type FormFieldBinding = 'input' | 'value' | 'checked';
81
+ type BoundProps<TValues extends FieldValues, TName extends FieldPath<TValues>, TBind extends FormFieldBinding> = TBind extends 'value' ? ValueBindingProps<FieldPathValue<TValues, TName>> : TBind extends 'checked' ? CheckedBindingProps : UseFieldPropsResult<TValues, TName>['controlProps'];
82
+ type FormFieldProps<TValues extends FieldValues, TName extends FieldPath<TValues>, TBind extends FormFieldBinding = 'input'> = UseFieldPropsOptions<TValues, TName> & {
42
83
  name: TName;
43
84
  label?: ReactNode;
44
- /** Shows the required marker on the label (validation itself comes from `rules` or the schema). */
85
+ /** Shows the required marker (validation comes from rules or the schema). */
45
86
  required?: boolean;
46
87
  description?: ReactNode;
47
88
  className?: string;
48
- /** Renders the control: `{(field) => <Input {...field} />}`. Gets the field's state as the second argument. */
49
- children: (control: UseFieldPropsResult<TValues, TName>['controlProps'], state: UseFieldPropsResult<TValues, TName>) => ReactNode;
50
- }
89
+ /** Optional maximum width; unset fills the available width. */
90
+ width?: FormFieldWidth;
91
+ children: (control: BoundProps<TValues, TName, TBind>, state: UseFieldPropsResult<TValues, TName>) => ReactNode;
92
+ } & (TBind extends 'input' ? {
93
+ bind?: 'input';
94
+ } : {
95
+ bind: TBind;
96
+ });
51
97
  /**
52
98
  * A kit `Field` bound to a react-hook-form field: label, control, description and the field's
53
99
  * error message, with `aria-invalid` and `aria-describedby` wired by Base UI's Field.
54
100
  */
55
- export declare function FormField<TValues extends FieldValues = FieldValues, TName extends FieldPath<TValues> = FieldPath<TValues>>({ name, label, required, description, className, children, ...options }: FormFieldProps<TValues, TName>): import("react").JSX.Element;
101
+ export declare function FormField<TValues extends FieldValues = FieldValues, TName extends FieldPath<TValues> = FieldPath<TValues>>(props: FormFieldProps<TValues, TName, 'value'>): ReactNode;
102
+ export declare function FormField<TValues extends FieldValues = FieldValues, TName extends FieldPath<TValues> = FieldPath<TValues>>(props: FormFieldProps<TValues, TName, 'checked'>): ReactNode;
103
+ export declare function FormField<TValues extends FieldValues = FieldValues, TName extends FieldPath<TValues> = FieldPath<TValues>>(props: FormFieldProps<TValues, TName>): ReactNode;
56
104
  interface FormProps<TValues extends FieldValues, TTransformed = TValues> extends Omit<FormHTMLAttributes<HTMLFormElement>, 'onSubmit' | 'onInvalid'> {
57
105
  /** The object returned by `useForm()`. */
58
106
  form: UseFormReturn<TValues, unknown, TTransformed>;
59
107
  onSubmit: SubmitHandler<TTransformed>;
60
108
  onInvalid?: SubmitErrorHandler<TValues>;
109
+ /** Adds form-wide disabled/readOnly props to fields by name. Undefined leaves props unchanged. */
110
+ enhanceFieldProps?: EnhanceFieldProps;
61
111
  children?: ReactNode;
62
112
  }
63
113
  /**
64
114
  * `<form noValidate>` + `FormProvider`: submits through `handleSubmit`, so a failed submit shows
65
115
  * every message and focuses the first invalid field (react-hook-form's `shouldFocusError`).
66
116
  */
67
- export declare function Form<TValues extends FieldValues, TTransformed = TValues>({ form, onSubmit, onInvalid, children, ...props }: FormProps<TValues, TTransformed>): import("react").JSX.Element;
117
+ export declare function Form<TValues extends FieldValues, TTransformed = TValues>({ form, onSubmit, onInvalid, enhanceFieldProps, children, ...props }: FormProps<TValues, TTransformed>): import("react").JSX.Element;
68
118
  //#endregion
69
119
  //#region src/forms/schemaResolver.d.ts
70
120
  /** One validation issue, as zod (v3 and v4) and other Standard-Schema-style libraries report it. */
@@ -98,5 +148,94 @@ export declare function issuesToErrors<TValues extends FieldValues>(issues: read
98
148
  */
99
149
  export declare function schemaResolver<TValues extends FieldValues, TOutput = TValues>(schema: ParseableSchema<TOutput>): Resolver<TValues, unknown, TOutput>;
100
150
  //#endregion
101
- export type { FormFieldProps, FormFieldStateProps, FormProps, ParseableSchema, SchemaIssue, UseFieldPropsOptions, UseFieldPropsResult };
151
+ //#region src/forms/FieldArray.d.ts
152
+ interface FieldArrayRow<TValues extends FieldValues, TName extends FieldArrayPath<TValues>> {
153
+ index: number;
154
+ remove(): void;
155
+ move(delta: number): void;
156
+ duplicate(): void;
157
+ name<TField extends Path<FieldArray$1<TValues, TName>>>(field: TField): `${TName}.${number}.${TField}` & FieldPath<TValues>;
158
+ }
159
+ interface FieldArrayProps<TValues extends FieldValues, TName extends FieldArrayPath<TValues> = FieldArrayPath<TValues>> {
160
+ name: TName;
161
+ control?: Control<TValues>;
162
+ defaultItem: FieldArray$1<TValues, TName> | (() => FieldArray$1<TValues, TName>);
163
+ label?: ReactNode;
164
+ description?: ReactNode;
165
+ rules?: UseFieldArrayProps<TValues, TName>['rules'];
166
+ max?: number;
167
+ min?: number;
168
+ emptyState?: ReactNode;
169
+ reorderable?: boolean;
170
+ duplicable?: boolean;
171
+ layout?: 'inline' | 'card';
172
+ itemLabel?: (n: number) => string;
173
+ addLabel?: ReactNode;
174
+ className?: string;
175
+ children: (row: FieldArrayRow<TValues, TName>) => ReactNode;
176
+ }
177
+ /** Repeatable object fields in the optional react-hook-form adapter. */
178
+ export declare function FieldArray<TValues extends FieldValues, TName extends FieldArrayPath<TValues> = FieldArrayPath<TValues>>(props: FieldArrayProps<TValues, TName>): import("react").JSX.Element;
179
+ //#endregion
180
+ //#region src/forms/field-array.variants.d.ts
181
+ export declare const fieldArrayVariants: import("tailwind-variants").TVReturnType<{
182
+ layout: {
183
+ inline: {
184
+ row: string;
185
+ };
186
+ card: {
187
+ row: string;
188
+ };
189
+ };
190
+ }, {
191
+ root: string;
192
+ list: string;
193
+ row: string;
194
+ fields: string;
195
+ actions: string;
196
+ footer: string;
197
+ hint: string;
198
+ error: string;
199
+ }, undefined, {
200
+ layout: {
201
+ inline: {
202
+ row: string;
203
+ };
204
+ card: {
205
+ row: string;
206
+ };
207
+ };
208
+ }, {
209
+ root: string;
210
+ list: string;
211
+ row: string;
212
+ fields: string;
213
+ actions: string;
214
+ footer: string;
215
+ hint: string;
216
+ error: string;
217
+ }, import("tailwind-variants").TVReturnTypeLike<{
218
+ layout: {
219
+ inline: {
220
+ row: string;
221
+ };
222
+ card: {
223
+ row: string;
224
+ };
225
+ };
226
+ }, {
227
+ root: string;
228
+ list: string;
229
+ row: string;
230
+ fields: string;
231
+ actions: string;
232
+ footer: string;
233
+ hint: string;
234
+ error: string;
235
+ }>>;
236
+ //#endregion
237
+ //#region src/forms/FieldArray.doc.d.ts
238
+ declare const doc: ComponentDoc;
239
+ //#endregion
240
+ export { type BindingControlProps, type CheckedBindingProps, type EnhanceFieldProps, type EnhancedFieldProps, type FieldArrayProps, type FieldArrayRow, type FormFieldBinding, type FormFieldProps, type FormFieldStateProps, type FormFieldWidth, type FormProps, type ParseableSchema, type SchemaIssue, type UseFieldPropsOptions, type UseFieldPropsResult, type ValueBindingProps, doc as fieldArrayDoc };
102
241
  //# sourceMappingURL=forms.d.ts.map
package/dist/forms.js CHANGED
@@ -1,7 +1,9 @@
1
1
  'use client';
2
- import { a as FieldLabel, n as FieldDescription, r as FieldError, t as Field } from "./Field-neBukiNh.js";
3
- import { jsx, jsxs } from "react/jsx-runtime";
4
- import { FormProvider, useController, useFormContext } from "react-hook-form";
2
+ import { F as cn, P as tv, a as FieldLabel, c as FieldsetLegend, d as useAnnounce, f as useHasLiveAnnouncer, h as IconButton, m as Button, n as FieldDescription, o as Fieldset, r as FieldError, s as FieldsetDescription, t as Field, u as LiveAnnouncer, w as useI18n } from "./Field-knzMlyzO.js";
3
+ import { createContext, useContext, useId, useLayoutEffect, useRef } from "react";
4
+ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
5
+ import { ArrowDown, ArrowUp, Copy, Plus, Trash2 } from "lucide-react";
6
+ import { FormProvider, get, useController, useFieldArray, useFormContext, useFormState, useWatch } from "react-hook-form";
5
7
  //#region src/forms/useFieldProps.ts
6
8
  /**
7
9
  * Wires one react-hook-form field (through `useController`) into the kit's Field parts.
@@ -39,23 +41,74 @@ function errorMessage(error) {
39
41
  return typeof root?.message === "string" ? root.message : void 0;
40
42
  }
41
43
  //#endregion
44
+ //#region src/forms/bindings.ts
45
+ /** Maps value/onChange controls to the kit's value/onValueChange API. Changes also touch the field. */
46
+ function toValueProps({ value, onChange, onBlur, ...props }) {
47
+ return {
48
+ ...props,
49
+ ref: focusTargetRef(props.ref),
50
+ value: value ?? null,
51
+ onValueChange: (next) => {
52
+ onChange(next);
53
+ onBlur();
54
+ }
55
+ };
56
+ }
57
+ /** Maps value/onChange controls to the kit's checked/onCheckedChange API. */
58
+ function toCheckedProps({ value, onChange, onBlur, ...props }) {
59
+ return {
60
+ ...props,
61
+ ref: focusTargetRef(props.ref),
62
+ checked: !!value,
63
+ onCheckedChange: (next) => {
64
+ onChange(next);
65
+ onBlur();
66
+ }
67
+ };
68
+ }
69
+ /** Resolve at focus time: composite controls can mount or change their tabbable child later. */
70
+ function resolveFocusTarget(element) {
71
+ const enabled = ":not(:disabled):not([aria-disabled=\"true\"])";
72
+ if (element.matches(`input:not([type="hidden"])${enabled}, button${enabled}, textarea${enabled}, select${enabled}, a[href], [tabindex]${enabled}`)) return element;
73
+ return element.querySelector(`[tabindex="0"]${enabled}`) ?? element.querySelector(`input:not([type="hidden"])${enabled}, button${enabled}`);
74
+ }
75
+ function focusTargetRef(ref) {
76
+ return (element) => ref(element ? { focus: () => resolveFocusTarget(element)?.focus() } : null);
77
+ }
78
+ //#endregion
42
79
  //#region src/forms/FormField.tsx
43
- /**
44
- * A kit `Field` bound to a react-hook-form field: label, control, description and the field's
45
- * error message, with `aria-invalid` and `aria-describedby` wired by Base UI's Field.
46
- */
47
- function FormField({ name, label, required, description, className, children, ...options }) {
48
- const state = useFieldProps(name, options);
80
+ const FieldEnhancerContext = createContext(void 0);
81
+ const widths = {
82
+ xs: "max-w-[6.5rem]",
83
+ sm: "max-w-[13.5rem]",
84
+ md: "max-w-[20.5rem]",
85
+ lg: "max-w-[27.5rem]",
86
+ xl: "max-w-[34.5rem]"
87
+ };
88
+ function FormField(props) {
89
+ const { name, label, required, description, className, width, control, rules, disabled } = props;
90
+ const enhanced = useContext(FieldEnhancerContext)?.({ name });
91
+ const fieldDisabled = enhanced?.disabled ?? disabled;
92
+ const state = useFieldProps(name, {
93
+ control,
94
+ rules,
95
+ disabled: fieldDisabled
96
+ });
97
+ const controlProps = enhanced ? {
98
+ ...state.controlProps,
99
+ ...enhanced
100
+ } : state.controlProps;
101
+ const child = props.bind === "value" ? props.children(toValueProps(controlProps), state) : props.bind === "checked" ? props.children(toCheckedProps(controlProps), state) : props.children(controlProps, state);
49
102
  return /* @__PURE__ */ jsxs(Field, {
50
103
  ...state.fieldProps,
51
- disabled: options.disabled,
52
- className,
104
+ disabled: fieldDisabled,
105
+ className: width ? cn("w-full", widths[width], className) : className,
53
106
  children: [
54
107
  label && /* @__PURE__ */ jsx(FieldLabel, {
55
108
  required,
56
109
  children: label
57
110
  }),
58
- children(state.controlProps, state),
111
+ child,
59
112
  description && /* @__PURE__ */ jsx(FieldDescription, { children: description }),
60
113
  /* @__PURE__ */ jsx(FieldError, {
61
114
  match: !!state.error,
@@ -68,14 +121,17 @@ function FormField({ name, label, required, description, className, children, ..
68
121
  * `<form noValidate>` + `FormProvider`: submits through `handleSubmit`, so a failed submit shows
69
122
  * every message and focuses the first invalid field (react-hook-form's `shouldFocusError`).
70
123
  */
71
- function Form({ form, onSubmit, onInvalid, children, ...props }) {
124
+ function Form({ form, onSubmit, onInvalid, enhanceFieldProps, children, ...props }) {
72
125
  return /* @__PURE__ */ jsx(FormProvider, {
73
126
  ...form,
74
- children: /* @__PURE__ */ jsx("form", {
75
- noValidate: true,
76
- ...props,
77
- onSubmit: form.handleSubmit(onSubmit, onInvalid),
78
- children
127
+ children: /* @__PURE__ */ jsx(FieldEnhancerContext.Provider, {
128
+ value: enhanceFieldProps,
129
+ children: /* @__PURE__ */ jsx("form", {
130
+ noValidate: true,
131
+ ...props,
132
+ onSubmit: form.handleSubmit(onSubmit, onInvalid),
133
+ children
134
+ })
79
135
  })
80
136
  });
81
137
  }
@@ -125,6 +181,253 @@ function schemaResolver(schema) {
125
181
  };
126
182
  }
127
183
  //#endregion
128
- export { Form, FormField, issuesToErrors, schemaResolver, useFieldProps };
184
+ //#region src/forms/field-array.variants.ts
185
+ const fieldArrayVariants = tv({
186
+ slots: {
187
+ root: "min-w-0",
188
+ list: "flex flex-col gap-3",
189
+ row: "flex min-w-0 gap-3",
190
+ fields: "flex min-w-0 flex-1 flex-col gap-3",
191
+ actions: "flex shrink-0 flex-wrap items-start gap-1",
192
+ footer: "flex flex-wrap items-center gap-3",
193
+ hint: "text-sm text-muted-foreground",
194
+ error: "text-sm text-destructive-text"
195
+ },
196
+ variants: { layout: {
197
+ inline: { row: "flex-col sm:flex-row" },
198
+ card: { row: "flex-col rounded-lg border border-border bg-card p-4" }
199
+ } },
200
+ defaultVariants: { layout: "inline" }
201
+ });
202
+ //#endregion
203
+ //#region src/forms/FieldArray.tsx
204
+ /** Repeatable object fields in the optional react-hook-form adapter. */
205
+ function FieldArray(props) {
206
+ return useHasLiveAnnouncer() ? /* @__PURE__ */ jsx(FieldArrayContent, { ...props }) : /* @__PURE__ */ jsx(LiveAnnouncer, { children: /* @__PURE__ */ jsx(FieldArrayContent, { ...props }) });
207
+ }
208
+ const focusable = "input:not([disabled]):not([type=\"hidden\"]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), [tabindex]:not([tabindex=\"-1\"]):not([disabled])";
209
+ function RowFields({ children, row }) {
210
+ return children(row);
211
+ }
212
+ function FieldArrayContent({ name, control: controlProp, label, description, rules, children, defaultItem, max, min = 0, emptyState, reorderable = false, duplicable = false, layout = "inline", itemLabel, addLabel, className }) {
213
+ const context = useFormContext();
214
+ const control = controlProp ?? context?.control;
215
+ const minimum = typeof rules?.minLength === "object" ? rules.minLength.value : rules?.minLength;
216
+ const arrayRules = minimum && minimum > 0 ? {
217
+ ...rules,
218
+ required: rules?.required ?? {
219
+ value: true,
220
+ message: typeof rules?.minLength === "object" ? rules.minLength.message : ""
221
+ }
222
+ } : rules;
223
+ const { fields, append, insert, remove: removeItem, move: moveItem } = useFieldArray({
224
+ name,
225
+ control,
226
+ rules: arrayRules
227
+ });
228
+ const path = name;
229
+ const values = useWatch({
230
+ control,
231
+ name: path
232
+ });
233
+ const { errors } = useFormState({
234
+ control,
235
+ name: path
236
+ });
237
+ const arrayError = get(errors, name);
238
+ const message = arrayError?.root?.message ?? arrayError?.message;
239
+ const error = typeof message === "string" ? message : void 0;
240
+ const uid = useId();
241
+ const descriptionId = `${uid}-description`;
242
+ const errorId = `${uid}-error`;
243
+ const { t } = useI18n();
244
+ const labelFor = (n) => itemLabel?.(n) ?? t("fieldArray.item", { n });
245
+ const s = fieldArrayVariants({ layout });
246
+ const announce = useAnnounce();
247
+ const rows = useRef(/* @__PURE__ */ new Map());
248
+ const addButton = useRef(null);
249
+ const pendingFocus = useRef(null);
250
+ const full = max !== void 0 && fields.length >= max;
251
+ const announceInsertion = (message) => {
252
+ const limit = max !== void 0 && fields.length + 1 >= max ? t("fieldArray.limit", { max }) : "";
253
+ announce([message, limit].filter(Boolean).join(" "));
254
+ };
255
+ useLayoutEffect(() => {
256
+ const pending = pendingFocus.current;
257
+ if (!pending) return;
258
+ pendingFocus.current = null;
259
+ if ("add" in pending) {
260
+ addButton.current?.focus();
261
+ return;
262
+ }
263
+ const id = "previousIds" in pending ? fields.find((field) => !pending.previousIds.has(field.id))?.id : pending.id;
264
+ const element = "element" in pending ? pending.element : void 0;
265
+ (element?.isConnected ? element : id ? rows.current.get(id)?.querySelector(focusable) : null)?.focus();
266
+ });
267
+ const add = () => {
268
+ if (full) {
269
+ announce(t("fieldArray.limit", { max: max ?? 0 }));
270
+ return;
271
+ }
272
+ pendingFocus.current = { previousIds: new Set(fields.map((field) => field.id)) };
273
+ append(structuredClone(typeof defaultItem === "function" ? defaultItem() : defaultItem), { shouldFocus: false });
274
+ announceInsertion(t("fieldArray.added", { item: labelFor(fields.length + 1) }));
275
+ };
276
+ const remove = (index) => {
277
+ if (fields.length <= min) return;
278
+ const neighbour = fields[index + 1] ?? fields[index - 1];
279
+ pendingFocus.current = neighbour ? { id: neighbour.id } : { add: true };
280
+ removeItem(index);
281
+ announce(t("fieldArray.removed", { item: labelFor(index + 1) }));
282
+ };
283
+ const move = (index, delta) => {
284
+ const destination = index + delta;
285
+ const field = fields[index];
286
+ if (!reorderable || !field || !Number.isInteger(delta) || destination < 0 || destination >= fields.length || delta === 0) return;
287
+ const active = document.activeElement;
288
+ pendingFocus.current = {
289
+ id: field.id,
290
+ element: active instanceof HTMLElement && rows.current.get(field.id)?.contains(active) ? active : void 0
291
+ };
292
+ moveItem(index, destination);
293
+ announce(t("fieldArray.moved", {
294
+ n: destination + 1,
295
+ total: fields.length
296
+ }));
297
+ };
298
+ const duplicate = (index) => {
299
+ if (!duplicable || full) return;
300
+ const value = values?.[index];
301
+ if (!value) return;
302
+ pendingFocus.current = { previousIds: new Set(fields.map((field) => field.id)) };
303
+ insert(index + 1, structuredClone(value), { shouldFocus: false });
304
+ announceInsertion(t("fieldArray.duplicated", { item: labelFor(index + 1) }));
305
+ };
306
+ return /* @__PURE__ */ jsxs(Fieldset, {
307
+ "data-field-array": "",
308
+ className: s.root({ className }),
309
+ "aria-describedby": [description ? descriptionId : "", error ? errorId : ""].filter(Boolean).join(" ") || void 0,
310
+ "aria-invalid": !!error || void 0,
311
+ children: [
312
+ label && /* @__PURE__ */ jsx(FieldsetLegend, { children: label }),
313
+ description && /* @__PURE__ */ jsx(FieldsetDescription, {
314
+ id: descriptionId,
315
+ children: description
316
+ }),
317
+ /* @__PURE__ */ jsx("div", {
318
+ className: s.list(),
319
+ children: fields.map((field, index) => /* @__PURE__ */ jsxs("div", {
320
+ className: s.row(),
321
+ ref: (element) => {
322
+ if (element) rows.current.set(field.id, element);
323
+ else rows.current.delete(field.id);
324
+ },
325
+ role: "group",
326
+ "aria-label": labelFor(index + 1),
327
+ onKeyDown: (event) => {
328
+ if (event.target instanceof Element && event.target.closest("[data-field-array]") !== event.currentTarget.closest("[data-field-array]")) return;
329
+ if (reorderable && event.altKey && (event.key === "ArrowUp" || event.key === "ArrowDown")) {
330
+ event.preventDefault();
331
+ event.stopPropagation();
332
+ move(index, event.key === "ArrowUp" ? -1 : 1);
333
+ }
334
+ },
335
+ children: [/* @__PURE__ */ jsx("div", {
336
+ className: s.fields(),
337
+ children: /* @__PURE__ */ jsx(RowFields, {
338
+ row: {
339
+ index,
340
+ remove: () => remove(index),
341
+ move: (delta) => move(index, delta),
342
+ duplicate: () => duplicate(index),
343
+ name: (part) => `${name}.${index}.${part}`
344
+ },
345
+ children
346
+ })
347
+ }), /* @__PURE__ */ jsxs("div", {
348
+ className: s.actions(),
349
+ children: [
350
+ reorderable && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(IconButton, {
351
+ icon: ArrowUp,
352
+ label: t("fieldArray.moveUp", { item: labelFor(index + 1) }),
353
+ disabled: index === 0,
354
+ onClick: () => move(index, -1)
355
+ }), /* @__PURE__ */ jsx(IconButton, {
356
+ icon: ArrowDown,
357
+ label: t("fieldArray.moveDown", { item: labelFor(index + 1) }),
358
+ disabled: index === fields.length - 1,
359
+ onClick: () => move(index, 1)
360
+ })] }),
361
+ duplicable && /* @__PURE__ */ jsx(IconButton, {
362
+ icon: Copy,
363
+ label: t("fieldArray.duplicate", { item: labelFor(index + 1) }),
364
+ disabled: full,
365
+ onClick: () => duplicate(index)
366
+ }),
367
+ /* @__PURE__ */ jsx(IconButton, {
368
+ icon: Trash2,
369
+ label: t("fieldArray.remove", { item: labelFor(index + 1) }),
370
+ disabled: fields.length <= min,
371
+ onClick: () => remove(index)
372
+ })
373
+ ]
374
+ })]
375
+ }, field.id))
376
+ }),
377
+ !fields.length && (emptyState ?? /* @__PURE__ */ jsx("p", {
378
+ className: s.hint(),
379
+ children: t("fieldArray.empty")
380
+ })),
381
+ /* @__PURE__ */ jsxs("div", {
382
+ className: s.footer(),
383
+ children: [/* @__PURE__ */ jsx(Button, {
384
+ ref: addButton,
385
+ icon: Plus,
386
+ variant: "secondary",
387
+ disabled: full,
388
+ onClick: add,
389
+ children: addLabel ?? t("fieldArray.add")
390
+ }), full && /* @__PURE__ */ jsx("p", {
391
+ className: s.hint(),
392
+ children: t("fieldArray.limit", { max: max ?? 0 })
393
+ })]
394
+ }),
395
+ error && /* @__PURE__ */ jsx("p", {
396
+ id: errorId,
397
+ className: s.error(),
398
+ children: error
399
+ })
400
+ ]
401
+ });
402
+ }
403
+ //#endregion
404
+ //#region src/forms/FieldArray.doc.ts
405
+ const doc = {
406
+ name: "FieldArray",
407
+ group: "Forms",
408
+ status: "beta",
409
+ description: "Repeatable arbitrary fields bound to react-hook-form arrays. Import from @gntik-ai/ui/forms only; use within Form or pass control explicitly. Rows expose index, name(field), remove(), duplicate() and move(delta). Add accepts a default object or factory. Optional reorder and duplicate actions preserve row identity, and min/max gate removals and additions. Inline and card layouts support nested arrays, list-level validation, focus restoration and localized announcements. Use ListInput for standalone key/value pairs without a form library; use FieldArray for arbitrary object fields managed by react-hook-form.",
410
+ primitive: "@base-ui/react/fieldset, @base-ui/react/field, @base-ui/react/button",
411
+ pattern: "fieldset with named row groups",
412
+ keyboard: [
413
+ ["Tab / Shift+Tab", "Moves through row fields, row actions and Add in document order"],
414
+ ["Enter / Space on Add", "Appends the default item and focuses its first control"],
415
+ ["Enter / Space on Remove", "Removes the row and focuses the next row, previous row or Add"],
416
+ ["Alt+ArrowUp / Alt+ArrowDown", "With reorderable, moves the row and keeps focus on the same control; stops at either end"],
417
+ ["Enter / Space on Move up / Move down", "Moves the row by one position and preserves focus"],
418
+ ["Enter / Space on Duplicate", "With duplicable, inserts an independent deep copy below and focuses it"]
419
+ ],
420
+ tokens: [
421
+ "border",
422
+ "card",
423
+ "foreground",
424
+ "muted-foreground",
425
+ "destructive-text",
426
+ "secondary",
427
+ "focus-ring"
428
+ ]
429
+ };
430
+ //#endregion
431
+ export { FieldArray, Form, FormField, doc as fieldArrayDoc, fieldArrayVariants, issuesToErrors, schemaResolver, toCheckedProps, toValueProps, useFieldProps };
129
432
 
130
433
  //# sourceMappingURL=forms.js.map