@zeno-lib/forms 0.9.1 → 0.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/dist/create-zeno-form.d.mts +1 -2
  2. package/dist/create-zeno-form.mjs +1 -2
  3. package/dist/form-element.d.mts +0 -2
  4. package/dist/form-element.mjs +1 -2
  5. package/dist/lib/action-result.d.mts +0 -2
  6. package/dist/lib/action-result.mjs +1 -2
  7. package/dist/lib/apply-validation-error.d.mts +0 -2
  8. package/dist/lib/apply-validation-error.mjs +1 -2
  9. package/dist/lib/aria.d.mts +1 -3
  10. package/dist/lib/aria.mjs +1 -2
  11. package/dist/lib/contexts.d.mts +0 -2
  12. package/dist/lib/contexts.mjs +1 -2
  13. package/dist/lib/date-value.d.mts +0 -2
  14. package/dist/lib/date-value.mjs +2 -3
  15. package/dist/lib/default-props.d.mts +1 -3
  16. package/dist/lib/default-props.mjs +1 -2
  17. package/dist/lib/empty-value.d.mts +9 -3
  18. package/dist/lib/empty-value.mjs +15 -5
  19. package/dist/lib/field-hints.d.mts +16 -0
  20. package/dist/lib/field-hints.mjs +23 -0
  21. package/dist/lib/focus-first-invalid.d.mts +0 -2
  22. package/dist/lib/focus-first-invalid.mjs +1 -2
  23. package/dist/lib/form-disabled.d.mts +0 -2
  24. package/dist/lib/form-disabled.mjs +1 -2
  25. package/dist/lib/formatted-number.d.mts +0 -2
  26. package/dist/lib/formatted-number.mjs +10 -11
  27. package/dist/lib/item-options.d.mts +0 -2
  28. package/dist/lib/item-options.mjs +1 -2
  29. package/dist/lib/schema-defaults.d.mts +6 -3
  30. package/dist/lib/schema-defaults.mjs +37 -5
  31. package/dist/lib/schema-required.d.mts +0 -2
  32. package/dist/lib/schema-required.mjs +3 -4
  33. package/dist/lib/submit-action.d.mts +0 -2
  34. package/dist/lib/submit-action.mjs +1 -2
  35. package/dist/lib/submit-error.d.mts +0 -2
  36. package/dist/lib/submit-error.mjs +1 -2
  37. package/dist/lib/use-field-control.d.mts +0 -2
  38. package/dist/lib/use-field-control.mjs +1 -2
  39. package/dist/lib/use-form-dialog.d.mts +0 -2
  40. package/dist/lib/use-form-dialog.mjs +2 -3
  41. package/dist/lib/use-formatted-number.d.mts +0 -2
  42. package/dist/lib/use-formatted-number.mjs +1 -2
  43. package/dist/lib/use-is-invalid.d.mts +0 -2
  44. package/dist/lib/use-is-invalid.mjs +1 -2
  45. package/dist/lib/use-rebased-default-values.d.mts +0 -2
  46. package/dist/lib/use-rebased-default-values.mjs +1 -2
  47. package/dist/lib/use-unsaved-changes-warning.d.mts +0 -2
  48. package/dist/lib/use-unsaved-changes-warning.mjs +1 -2
  49. package/dist/lib/validation-error.d.mts +0 -2
  50. package/dist/lib/validation-error.mjs +1 -2
  51. package/dist/lib/validation-logic.d.mts +0 -2
  52. package/dist/lib/validation-logic.mjs +1 -2
  53. package/dist/lib/validation-modes.d.mts +0 -2
  54. package/dist/lib/validation-modes.mjs +1 -2
  55. package/package.json +8 -8
  56. package/src/composition.test.tsx +3 -3
  57. package/src/control-field.test.tsx +1 -1
  58. package/src/date-picker-field.test.tsx +3 -3
  59. package/src/field-hints.test.tsx +118 -0
  60. package/src/fields/input-field.tsx +6 -4
  61. package/src/fields/percentage-field.tsx +16 -8
  62. package/src/fields/textarea-field.tsx +6 -4
  63. package/src/fields/year-field.tsx +8 -3
  64. package/src/form-dialog.test.tsx +3 -3
  65. package/src/form-error.test.tsx +1 -1
  66. package/src/formatted-number-fields.test.tsx +3 -3
  67. package/src/lib/date-value.ts +2 -1
  68. package/src/lib/empty-value.ts +26 -3
  69. package/src/lib/field-hints.ts +64 -0
  70. package/src/lib/formatted-number.ts +9 -9
  71. package/src/lib/schema-defaults.test.ts +6 -3
  72. package/src/lib/schema-defaults.ts +60 -3
  73. package/src/lib/schema-required.ts +2 -2
  74. package/src/lib/use-form-dialog.ts +1 -1
  75. package/src/multi-value-fields.test.tsx +2 -2
  76. package/src/nullable-text.test.tsx +80 -0
  77. package/src/submit-action.test.tsx +4 -4
  78. package/src/use-app-fields.test.tsx +10 -10
  79. package/src/use-form.cascade.test.tsx +2 -2
  80. package/src/use-form.reset-defaults.test.tsx +2 -2
  81. package/src/use-form.test.tsx +3 -3
  82. package/src/use-form.unsaved-warning.test.tsx +1 -1
  83. package/src/use-form.validation-modes.test.tsx +2 -2
  84. package/src/with-form.test.tsx +1 -1
@@ -2,7 +2,6 @@ import { FieldControlProps } from "./lib/use-field-control.mjs";
2
2
  import { ValidationMode } from "./lib/validation-modes.mjs";
3
3
  import { AppFieldExtendedReactFieldGroupApi, AppFieldExtendedReactFormApi, BaseFormOptions, DeepKeys, DeepValue, FieldsMap, FormAsyncValidateOrFn, FormOptions, FormValidateOrFn, WithFormProps } from "@tanstack/react-form";
4
4
  import { ComponentProps, PropsWithChildren, ReactNode } from "react";
5
- //#region src/create-zeno-form.d.ts
6
5
  type StandardSchema<T> = {
7
6
  readonly "~standard": {
8
7
  readonly validate: (value: unknown) => unknown;
@@ -144,4 +143,4 @@ export declare function createZenoForm<FC extends Record<string, AnyComponent>,
144
143
  form: AppFieldExtendedReactFormApi<[unknown] extends [TFormData] ? any : TFormData, [FormValidateOrFn<TFormData> | undefined] extends [TOnMount] ? [TOnMount] extends [TOnMount & (FormValidateOrFn<TFormData> | undefined)] ? any : TOnMount : TOnMount, [FormValidateOrFn<TFormData> | undefined] extends [TOnChange] ? [TOnChange] extends [TOnChange & (FormValidateOrFn<TFormData> | undefined)] ? any : TOnChange : TOnChange, [FormValidateOrFn<TFormData> | undefined] extends [TOnChangeAsync] ? [TOnChangeAsync] extends [TOnChangeAsync & (FormValidateOrFn<TFormData> | undefined)] ? any : TOnChangeAsync : TOnChangeAsync, [FormValidateOrFn<TFormData> | undefined] extends [TOnBlur] ? [TOnBlur] extends [TOnBlur & (FormValidateOrFn<TFormData> | undefined)] ? any : TOnBlur : TOnBlur, [FormValidateOrFn<TFormData> | undefined] extends [TOnBlurAsync] ? [TOnBlurAsync] extends [TOnBlurAsync & (FormValidateOrFn<TFormData> | undefined)] ? any : TOnBlurAsync : TOnBlurAsync, [FormValidateOrFn<TFormData> | undefined] extends [TOnSubmit] ? [TOnSubmit] extends [TOnSubmit & (FormValidateOrFn<TFormData> | undefined)] ? any : TOnSubmit : TOnSubmit, [FormValidateOrFn<TFormData> | undefined] extends [TOnSubmitAsync] ? [TOnSubmitAsync] extends [TOnSubmitAsync & (FormValidateOrFn<TFormData> | undefined)] ? any : TOnSubmitAsync : TOnSubmitAsync, [FormValidateOrFn<TFormData> | undefined] extends [TOnDynamic] ? [TOnDynamic] extends [TOnDynamic & (FormValidateOrFn<TFormData> | undefined)] ? any : TOnDynamic : TOnDynamic, [FormValidateOrFn<TFormData> | undefined] extends [TOnDynamicAsync] ? [TOnDynamicAsync] extends [TOnDynamicAsync & (FormValidateOrFn<TFormData> | undefined)] ? any : TOnDynamicAsync : TOnDynamicAsync, [FormValidateOrFn<TFormData> | undefined] extends [TOnServer] ? [TOnServer] extends [TOnServer & (FormValidateOrFn<TFormData> | undefined)] ? any : TOnServer : TOnServer, [unknown] extends [TSubmitMeta] ? any : TSubmitMeta, [unknown] extends [FC] ? any : FC, [unknown] extends [FMC] ? any : FMC>;
145
144
  }>>;
146
145
  };
147
- //#endregion
146
+ export {};
@@ -15,7 +15,7 @@ import { blurThenChangeLogic } from "./lib/validation-logic.mjs";
15
15
  import { createFormHook } from "@tanstack/react-form";
16
16
  import { useMemo } from "react";
17
17
  import { jsx } from "react/jsx-runtime";
18
- //#region src/create-zeno-form.tsx
18
+ // src/create-zeno-form.tsx
19
19
  function buildValidatorsFromSchema(schema, mode) {
20
20
  switch (mode) {
21
21
  case "blur": return { onBlur: schema };
@@ -191,5 +191,4 @@ function createZenoForm(config) {
191
191
  withForm
192
192
  };
193
193
  }
194
- //#endregion
195
194
  export { createZenoForm };
@@ -1,5 +1,4 @@
1
1
  import { ComponentProps, ReactNode } from "react";
2
- //#region src/form-element.d.ts
3
2
  type FormProviderProps = {
4
3
  children: ReactNode;
5
4
  /**
@@ -22,5 +21,4 @@ type FormProps = Omit<ComponentProps<"form">, "onSubmit"> & {
22
21
  disabled?: boolean;
23
22
  };
24
23
  declare function Form({ children, className, disabled, ...props }: FormProps): import("react").JSX.Element;
25
- //#endregion
26
24
  export { Form, FormProvider };
@@ -5,7 +5,7 @@ import { reportSubmitError } from "./lib/submit-error.mjs";
5
5
  import { focusFirstInvalid, restoreFocus } from "./lib/focus-first-invalid.mjs";
6
6
  import { useMemo } from "react";
7
7
  import { jsx } from "react/jsx-runtime";
8
- //#region src/form-element.tsx
8
+ // src/form-element.tsx
9
9
  function FormProvider({ children, disabled = false, form }) {
10
10
  const { store } = form;
11
11
  const scope = useMemo(() => ({
@@ -49,5 +49,4 @@ function Form({ children, className, disabled = false, ...props }) {
49
49
  })
50
50
  });
51
51
  }
52
- //#endregion
53
52
  export { Form, FormProvider };
@@ -1,4 +1,3 @@
1
- //#region src/lib/action-result.d.ts
2
1
  type ActionError = {
3
2
  readonly fieldErrors: Readonly<Record<string, readonly string[]>>;
4
3
  readonly formErrors: readonly string[];
@@ -18,5 +17,4 @@ type ActionIssue = {
18
17
  };
19
18
  declare function toFieldName(path: ActionIssue["path"]): string;
20
19
  declare function toActionError(issues: readonly ActionIssue[]): ActionError;
21
- //#endregion
22
20
  export { type ActionError, type ActionIssue, type ActionResult, toActionError, toFieldName };
@@ -1,4 +1,4 @@
1
- //#region src/lib/action-result.ts
1
+ // src/lib/action-result.ts
2
2
  function toFieldName(path) {
3
3
  let name = "";
4
4
  for (const segment of path ?? []) {
@@ -28,5 +28,4 @@ function toActionError(issues) {
28
28
  formErrors
29
29
  };
30
30
  }
31
- //#endregion
32
31
  export { toActionError, toFieldName };
@@ -1,6 +1,4 @@
1
1
  import { ValidationError } from "./validation-error.mjs";
2
2
  import { AnyFormApi } from "@tanstack/react-form";
3
- //#region src/lib/apply-validation-error.d.ts
4
3
  declare function applyValidationError(formApi: AnyFormApi, error: ValidationError): void;
5
- //#endregion
6
4
  export { applyValidationError };
@@ -1,4 +1,4 @@
1
- //#region src/lib/apply-validation-error.ts
1
+ // src/lib/apply-validation-error.ts
2
2
  function clearWhenEdited(formApi, name, entry) {
3
3
  const rejected = formApi.getFieldValue(name);
4
4
  const subscription = formApi.store.subscribe(() => {
@@ -37,5 +37,4 @@ function applyValidationError(formApi, error) {
37
37
  form: error.formError
38
38
  } });
39
39
  }
40
- //#endregion
41
40
  export { applyValidationError };
@@ -1,3 +1 @@
1
- //#region src/lib/aria.d.ts
2
- export declare function describedBy(...entries: [unknown, string][]): string | undefined;
3
- //#endregion
1
+ export declare function describedBy(...entries: [unknown, string][]): string | undefined;
package/dist/lib/aria.mjs CHANGED
@@ -1,7 +1,6 @@
1
- //#region src/lib/aria.ts
1
+ // src/lib/aria.ts
2
2
  function describedBy(...entries) {
3
3
  const ids = entries.filter(([show]) => Boolean(show)).map(([, id]) => id);
4
4
  return ids.length > 0 ? ids.join(" ") : void 0;
5
5
  }
6
- //#endregion
7
6
  export { describedBy };
@@ -1,5 +1,3 @@
1
- //#region src/lib/contexts.d.ts
2
1
  declare const fieldContext: import("react").Context<import("@tanstack/react-form").AnyFieldApi>, formContext: import("react").Context<import("@tanstack/react-form").AnyFormApi>, useFieldContext: <TData>() => import("@tanstack/react-form").FieldApi<any, string, TData, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any, any>, useFormContext: () => import("@tanstack/react-form").ReactFormExtendedApi<Record<string, never>, any, any, any, any, any, any, any, any, any, any, any>;
3
2
  export declare const FormProvider: import("react").Provider<import("@tanstack/react-form").AnyFormApi>;
4
- //#endregion
5
3
  export { fieldContext, formContext, useFieldContext, useFormContext };
@@ -1,7 +1,6 @@
1
1
  "use client";
2
2
  import { createFormHookContexts } from "@tanstack/react-form";
3
- //#region src/lib/contexts.ts
3
+ // src/lib/contexts.ts
4
4
  const { fieldContext, formContext, useFieldContext, useFormContext } = createFormHookContexts();
5
5
  const FormProvider = formContext.Provider;
6
- //#endregion
7
6
  export { FormProvider, fieldContext, formContext, useFieldContext, useFormContext };
@@ -1,4 +1,3 @@
1
- //#region src/lib/date-value.d.ts
2
1
  /**
3
2
  * What a date field stores for a picked day:
4
3
  * - `"date"`: a `Date`, at local midnight.
@@ -20,5 +19,4 @@ declare function parseDateValue(value: unknown): Date | undefined;
20
19
  declare function formatIsoDate(date: Date): string;
21
20
  /** The value to store for `date`, a day picked in local time. */
22
21
  declare function toDateValue(date: Date, type: DateValueType): Date | string;
23
- //#endregion
24
22
  export { type DateValueType, formatIsoDate, parseDateValue, toDateValue };
@@ -1,5 +1,5 @@
1
- //#region src/lib/date-value.ts
2
- const ISO_DATE = /^(\d{4})-(\d{2})-(\d{2})$/;
1
+ // src/lib/date-value.ts
2
+ const ISO_DATE = /^(\d{4})-(\d{2})-(\d{2})$/u;
3
3
  function pad(value, length) {
4
4
  return String(value).padStart(length, "0");
5
5
  }
@@ -33,5 +33,4 @@ function toDateValue(date, type) {
33
33
  if (type === "iso-datetime") return date.toISOString();
34
34
  return date;
35
35
  }
36
- //#endregion
37
36
  export { formatIsoDate, parseDateValue, toDateValue };
@@ -1,7 +1,5 @@
1
- //#region src/lib/default-props.d.ts
2
1
  /**
3
2
  * The props a component renders with: `props` over `defaults`. A prop passed
4
3
  * as `undefined` keeps its default, as a destructuring default would.
5
4
  */
6
- export declare function withDefaultProps<P extends object>(defaults: Partial<P> | undefined, props: P): P;
7
- //#endregion
5
+ export declare function withDefaultProps<P extends object>(defaults: Partial<P> | undefined, props: P): P;
@@ -1,4 +1,4 @@
1
- //#region src/lib/default-props.ts
1
+ // src/lib/default-props.ts
2
2
  /**
3
3
  * The props a component renders with: `props` over `defaults`. A prop passed
4
4
  * as `undefined` keeps its default, as a destructuring default would.
@@ -9,5 +9,4 @@ function withDefaultProps(defaults, props) {
9
9
  for (const [key, value] of Object.entries(props)) if (value !== void 0) merged[key] = value;
10
10
  return merged;
11
11
  }
12
- //#endregion
13
12
  export { withDefaultProps };
@@ -1,6 +1,6 @@
1
1
  import { StandardSchemaLike } from "./schema-required.mjs";
2
2
  import { AnyFieldApi } from "@tanstack/react-form";
3
- //#region src/lib/empty-value.d.ts
3
+ declare function toPathKeys(name: string): PropertyKey[];
4
4
  declare function emptyValueFor(schema: StandardSchemaLike | undefined, values: unknown, name: string, fallback: unknown): unknown;
5
5
  declare function withEmptyStartValues(schema: StandardSchemaLike, defaults: Record<string, unknown>): Record<string, unknown>;
6
6
  /**
@@ -8,5 +8,11 @@ declare function withEmptyStartValues(schema: StandardSchemaLike, defaults: Reco
8
8
  * the field's value, since the same schema decides both.
9
9
  */
10
10
  declare function getEmptyValue<T>(field: AnyFieldApi, fallback: T): T;
11
- //#endregion
12
- export { emptyValueFor, getEmptyValue, withEmptyStartValues };
11
+ /**
12
+ * What a text control stores once its text is cleared: `null` where the field
13
+ * starts at `null` (a nullable string), so it saves as none, and `""`
14
+ * otherwise. Read from the schema's shape rather than by validating the form,
15
+ * so the other fields' values never change it.
16
+ */
17
+ declare function getNoTextValue(field: AnyFieldApi): string | null;
18
+ export { emptyValueFor, getEmptyValue, getNoTextValue, toPathKeys, withEmptyStartValues };
@@ -2,9 +2,9 @@ import { toFieldName } from "./action-result.mjs";
2
2
  import { extractZodDefaultAt } from "./schema-defaults.mjs";
3
3
  import { validateSync } from "./schema-required.mjs";
4
4
  import { getFormSchema } from "./validation-modes.mjs";
5
- //#region src/lib/empty-value.ts
6
- const NAME_SEGMENT = /[^.[\]]+/g;
7
- const INDEX = /^\d+$/;
5
+ // src/lib/empty-value.ts
6
+ const NAME_SEGMENT = /[^.[\]]+/gu;
7
+ const INDEX = /^\d+$/u;
8
8
  function toPathKeys(name) {
9
9
  return (name.match(NAME_SEGMENT) ?? []).map((part) => INDEX.test(part) ? Number(part) : part);
10
10
  }
@@ -88,5 +88,15 @@ function withEmptyStartValues(schema, defaults) {
88
88
  function getEmptyValue(field, fallback) {
89
89
  return emptyValueFor(getFormSchema(field.form), field.form.state.values, field.name, fallback);
90
90
  }
91
- //#endregion
92
- export { emptyValueFor, getEmptyValue, withEmptyStartValues };
91
+ /**
92
+ * What a text control stores once its text is cleared: `null` where the field
93
+ * starts at `null` (a nullable string), so it saves as none, and `""`
94
+ * otherwise. Read from the schema's shape rather than by validating the form,
95
+ * so the other fields' values never change it.
96
+ */
97
+ function getNoTextValue(field) {
98
+ const schema = getFormSchema(field.form);
99
+ const start = schema ? extractZodDefaultAt(schema, toPathKeys(field.name)) : { ok: false };
100
+ return start.ok && start.value === null ? null : "";
101
+ }
102
+ export { emptyValueFor, getEmptyValue, getNoTextValue, toPathKeys, withEmptyStartValues };
@@ -0,0 +1,16 @@
1
+ import { AnyFieldApi } from "@tanstack/react-form";
2
+ /**
3
+ * What a field reads from the column its schema comes from, when the column's
4
+ * hint names it. The `percentage()` and `year()` columns of
5
+ * `@zeno-lib/db/schema` give one, so `PercentageField` and `YearField` show
6
+ * and clamp what the column accepts with no props: the scale from the hint,
7
+ * the bounds from the schema's own `.min()` and `.max()`, a caller's
8
+ * narrowing included. A prop the field is given wins.
9
+ */
10
+ type FieldHints = {
11
+ readonly max?: number | undefined;
12
+ readonly min?: number | undefined;
13
+ readonly scale?: "fraction" | "percent" | undefined;
14
+ };
15
+ declare function getFieldHints(field: AnyFieldApi, kind: string): FieldHints;
16
+ export { type FieldHints, getFieldHints };
@@ -0,0 +1,23 @@
1
+ import { findZodSchemaAt } from "./schema-defaults.mjs";
2
+ import { getFormSchema } from "./validation-modes.mjs";
3
+ import { toPathKeys } from "./empty-value.mjs";
4
+ // src/lib/field-hints.ts
5
+ const FIELD_HINTS_KEY = Symbol.for("@zeno-lib/field-hints");
6
+ const readHint = (node) => {
7
+ const hint = globalThis[FIELD_HINTS_KEY]?.get(node);
8
+ return typeof hint?.field === "string" ? hint : void 0;
9
+ };
10
+ const toBound = (value) => typeof value === "number" && Number.isFinite(value) ? value : void 0;
11
+ function getFieldHints(field, kind) {
12
+ const schema = getFormSchema(field.form);
13
+ const found = schema ? findZodSchemaAt(schema, toPathKeys(field.name), readHint) : void 0;
14
+ if (found?.value.field !== kind) return {};
15
+ const { scale } = found.value;
16
+ const { maxValue, minValue } = found.node;
17
+ return {
18
+ max: toBound(maxValue),
19
+ min: toBound(minValue),
20
+ scale: scale === "fraction" || scale === "percent" ? scale : void 0
21
+ };
22
+ }
23
+ export { getFieldHints };
@@ -1,4 +1,3 @@
1
- //#region src/lib/focus-first-invalid.d.ts
2
1
  /**
3
2
  * Move focus to the first invalid control inside `root` (a `<form>`): the
4
3
  * first element with `aria-invalid="true"`, or, for a group root that can't
@@ -19,5 +18,4 @@ declare function focusFirstInvalid(root: ParentNode | null | undefined): HTMLEle
19
18
  * moved focus.
20
19
  */
21
20
  declare function restoreFocus(element: Element | null): boolean;
22
- //#endregion
23
21
  export { focusFirstInvalid, restoreFocus };
@@ -1,4 +1,4 @@
1
- //#region src/lib/focus-first-invalid.ts
1
+ // src/lib/focus-first-invalid.ts
2
2
  /**
3
3
  * Move focus to the first invalid control inside `root` (a `<form>`): the
4
4
  * first element with `aria-invalid="true"`, or, for a group root that can't
@@ -33,5 +33,4 @@ function restoreFocus(element) {
33
33
  element.focus({ preventScroll: true });
34
34
  return element.ownerDocument.activeElement === element;
35
35
  }
36
- //#endregion
37
36
  export { focusFirstInvalid, restoreFocus };
@@ -1,5 +1,4 @@
1
1
  import { AnyFormApi } from "@tanstack/react-form";
2
- //#region src/lib/form-disabled.d.ts
3
2
  /** Apply `useForm({ disabled })` to `form`. */
4
3
  declare function useDisabledOption(form: AnyFormApi, disabled: boolean): void;
5
4
  type FormDisabledScope = {
@@ -24,5 +23,4 @@ declare const FormDisabledContext: import("react").Context<FormDisabledScope | n
24
23
  * mounted.
25
24
  */
26
25
  declare function useIsFormDisabled(form?: AnyFormApi): boolean;
27
- //#endregion
28
26
  export { FormDisabledContext, useDisabledOption, useIsFormDisabled };
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import { formContext } from "./contexts.mjs";
3
3
  import { createContext, useCallback, useContext, useLayoutEffect, useSyncExternalStore } from "react";
4
- //#region src/lib/form-disabled.ts
4
+ // src/lib/form-disabled.ts
5
5
  const disabledOption = /* @__PURE__ */ new WeakMap();
6
6
  const listeners = /* @__PURE__ */ new WeakMap();
7
7
  function subscribe(key, listener) {
@@ -50,5 +50,4 @@ function useIsFormDisabled(form) {
50
50
  const byOption = useSyncExternalStore(subscribeToKey, getSnapshot, getSnapshot);
51
51
  return scope?.disabled === true && (key === void 0 || scope.key === key) || byOption;
52
52
  }
53
- //#endregion
54
53
  export { FormDisabledContext, useDisabledOption, useIsFormDisabled };
@@ -1,4 +1,3 @@
1
- //#region src/lib/formatted-number.d.ts
2
1
  type NumberSeparators = {
3
2
  /** Thousands separator, e.g. `’` for `de-CH`, `,` for `en-US`. */
4
3
  group: string;
@@ -52,5 +51,4 @@ declare function parseLocaleNumber(text: string, locale?: string): number | null
52
51
  declare function mapCaret(raw: string, caret: number, next: string, locale?: string): number;
53
52
  /** Round to `digits` decimals without the `1.005 → 1.00` float surprise. */
54
53
  declare function roundTo(value: number, digits: number): number;
55
- //#endregion
56
54
  export { type FormatNumberOptions, type NumberSeparators, type ParseTypedOptions, type TypedNumber, formatNumber, getNumberSeparators, mapCaret, parseLocaleNumber, parseTypedNumber, roundTo };
@@ -1,14 +1,14 @@
1
- //#region src/lib/formatted-number.ts
1
+ // src/lib/formatted-number.ts
2
2
  const separatorCache = /* @__PURE__ */ new Map();
3
- const MINUS_SIGNS = /[-−–]/;
4
- const LEADING_MINUS = /^[^\d]*[-−–]/;
5
- const NON_DIGIT = /\D/g;
6
- const LEADING_ZEROS = /^0+(?=\d)/;
7
- const DIGIT = /\d/;
8
- const DIGITS = /\d/g;
9
- const PARENTHESIZED = /^\s*\(.*\)\s*$/;
10
- const NOT_NUMERIC = /[^\d.,]/g;
11
- const UNICODE_MINUS = /\u2212/g;
3
+ const MINUS_SIGNS = /[-−–]/u;
4
+ const LEADING_MINUS = /^[^\d]*[-−–]/u;
5
+ const NON_DIGIT = /\D/gu;
6
+ const LEADING_ZEROS = /^0+(?=\d)/u;
7
+ const DIGIT = /\d/u;
8
+ const DIGITS = /\d/gu;
9
+ const PARENTHESIZED = /^\s*\(.*\)\s*$/u;
10
+ const NOT_NUMERIC = /[^\d.,]/gu;
11
+ const UNICODE_MINUS = /\u2212/gu;
12
12
  function getNumberSeparators(locale) {
13
13
  const key = locale ?? "";
14
14
  const cached = separatorCache.get(key);
@@ -134,5 +134,4 @@ function roundTo(value, digits) {
134
134
  const rounded = Math.round((Math.abs(value) + Number.EPSILON) * factor);
135
135
  return Math.sign(value) * rounded / factor;
136
136
  }
137
- //#endregion
138
137
  export { formatNumber, getNumberSeparators, mapCaret, parseLocaleNumber, parseTypedNumber, roundTo };
@@ -1,4 +1,3 @@
1
- //#region src/lib/item-options.d.ts
2
1
  /** An item whose value isn't its label. */
3
2
  type ItemObject<V> = {
4
3
  value: V;
@@ -41,5 +40,4 @@ declare function toItemList<T>(items: readonly T[]): ItemList<T>;
41
40
  declare function isSameValue(a: unknown, b: unknown, isItemEqualToValue: ValueComparator | undefined): boolean;
42
41
  /** The option whose value is `value`, if `items` has it. */
43
42
  declare function findOption<T>(list: ItemList<T>, value: unknown, isItemEqualToValue: ValueComparator | undefined): ItemOption<T> | undefined;
44
- //#endregion
45
43
  export { type ItemGroup, type ItemList, type ItemObject, type ItemOption, type ItemValue, type ValueComparator, findOption, isItemGroup, isItemObject, isSameValue, itemLabel, itemValue, toItemList, valueKey };
@@ -1,4 +1,4 @@
1
- //#region src/lib/item-options.ts
1
+ // src/lib/item-options.ts
2
2
  function isItemObject(item) {
3
3
  return typeof item === "object" && item !== null && "value" in item && "label" in item;
4
4
  }
@@ -66,5 +66,4 @@ function findOption(list, value, isItemEqualToValue) {
66
66
  if (hit !== void 0 && isSameValue(itemValue(hit), value, isItemEqualToValue)) return hit;
67
67
  return list.options.find((option) => isSameValue(itemValue(option), value, isItemEqualToValue));
68
68
  }
69
- //#endregion
70
69
  export { findOption, isItemGroup, isItemObject, isSameValue, itemLabel, itemValue, toItemList, valueKey };
@@ -1,4 +1,3 @@
1
- //#region src/lib/schema-defaults.d.ts
2
1
  type SchemaNode = {
3
2
  readonly _zod?: {
4
3
  readonly def?: SchemaDef;
@@ -10,6 +9,7 @@ type SchemaDef = {
10
9
  readonly shape?: Record<string, SchemaNode>;
11
10
  readonly catchall?: SchemaNode;
12
11
  readonly in?: SchemaNode;
12
+ readonly out?: SchemaNode;
13
13
  readonly element?: SchemaNode;
14
14
  readonly defaultValue?: unknown;
15
15
  };
@@ -21,7 +21,10 @@ type ExtractResult = {
21
21
  };
22
22
  declare function extractZodDefaults(schema: SchemaNode): Record<string, unknown>;
23
23
  declare function extractZodDefaultAt(schema: SchemaNode, keys: readonly PropertyKey[]): ExtractResult;
24
+ declare function findZodSchemaAt<T>(schema: SchemaNode, keys: readonly PropertyKey[], read: (node: SchemaNode) => T | undefined): {
25
+ node: SchemaNode;
26
+ value: T;
27
+ } | undefined;
24
28
  declare function pickSchemaShape<T>(schema: SchemaNode, value: T): T;
25
29
  declare function deepMergeDefaults(schemaDefaults: Record<string, unknown>, userDefaults: Record<string, unknown> | undefined): Record<string, unknown>;
26
- //#endregion
27
- export { deepMergeDefaults, extractZodDefaultAt, extractZodDefaults, pickSchemaShape };
30
+ export { deepMergeDefaults, extractZodDefaultAt, extractZodDefaults, findZodSchemaAt, pickSchemaShape };
@@ -1,10 +1,11 @@
1
- //#region src/lib/schema-defaults.ts
1
+ // src/lib/schema-defaults.ts
2
2
  const MAX_WRAPPER_DEPTH = 16;
3
3
  function getDef(node) {
4
4
  return node?._zod?.def;
5
5
  }
6
6
  function extractDefaultForField(node) {
7
7
  let current = node;
8
+ let isNullable = false;
8
9
  for (let depth = 0; depth < MAX_WRAPPER_DEPTH; depth += 1) {
9
10
  const def = getDef(current);
10
11
  if (!def?.type) return { ok: false };
@@ -14,8 +15,11 @@ function extractDefaultForField(node) {
14
15
  ok: true,
15
16
  value: def.defaultValue
16
17
  };
17
- case "optional":
18
18
  case "nullable":
19
+ isNullable = true;
20
+ current = def.innerType;
21
+ continue;
22
+ case "optional":
19
23
  case "nonoptional":
20
24
  case "readonly":
21
25
  current = def.innerType;
@@ -25,7 +29,7 @@ function extractDefaultForField(node) {
25
29
  continue;
26
30
  case "string": return {
27
31
  ok: true,
28
- value: ""
32
+ value: isNullable ? null : ""
29
33
  };
30
34
  case "array": return {
31
35
  ok: true,
@@ -89,6 +93,35 @@ function extractZodDefaultAt(schema, keys) {
89
93
  return { ok: false };
90
94
  }
91
95
  }
96
+ function unwrapped(node) {
97
+ const def = getDef(node);
98
+ switch (def?.type) {
99
+ case "catch":
100
+ case "default":
101
+ case "nonoptional":
102
+ case "nullable":
103
+ case "optional":
104
+ case "prefault":
105
+ case "readonly": return def.innerType;
106
+ case "pipe": return getDef(def.in)?.type === "transform" ? def.out : def.in;
107
+ default: return;
108
+ }
109
+ }
110
+ function findZodSchemaAt(schema, keys, read) {
111
+ try {
112
+ let node = schema;
113
+ for (const key of keys) node = node && childAt(node, key);
114
+ let current = node && keys.length > 0 ? node : void 0;
115
+ for (let depth = 0; current && depth < MAX_WRAPPER_DEPTH; depth += 1) {
116
+ const value = read(current);
117
+ if (value !== void 0) return {
118
+ node: current,
119
+ value
120
+ };
121
+ current = unwrapped(current);
122
+ }
123
+ } catch {}
124
+ }
92
125
  function isPlainObject(value) {
93
126
  return typeof value === "object" && value !== null && Object.getPrototypeOf(value) === Object.prototype;
94
127
  }
@@ -145,5 +178,4 @@ function deepMergeDefaults(schemaDefaults, userDefaults) {
145
178
  }
146
179
  return result;
147
180
  }
148
- //#endregion
149
- export { deepMergeDefaults, extractZodDefaultAt, extractZodDefaults, pickSchemaShape };
181
+ export { deepMergeDefaults, extractZodDefaultAt, extractZodDefaults, findZodSchemaAt, pickSchemaShape };
@@ -1,4 +1,3 @@
1
- //#region src/lib/schema-required.d.ts
2
1
  type PathPart = PropertyKey | {
3
2
  readonly key: PropertyKey;
4
3
  };
@@ -22,5 +21,4 @@ type StandardSchemaLike = {
22
21
  declare function toRequiredPathKey(name: string): string;
23
22
  declare function validateSync(schema: StandardSchemaLike, value: unknown): readonly StandardIssue[] | undefined;
24
23
  declare function getRequiredPaths(schema: StandardSchemaLike): Set<string>;
25
- //#endregion
26
24
  export { type StandardSchemaLike, getRequiredPaths, toRequiredPathKey, validateSync };
@@ -1,7 +1,7 @@
1
- //#region src/lib/schema-required.ts
1
+ // src/lib/schema-required.ts
2
2
  const MAX_PROBE_DEPTH = 16;
3
- const INDEX_SEGMENT = /^\d+$/;
4
- const INDEX_IN_NAME = /\[\d+\]|\.(\d+)(?=\.|\[|$)/g;
3
+ const INDEX_SEGMENT = /^\d+$/u;
4
+ const INDEX_IN_NAME = /\[\d+\]|\.\d+(?=\.|\[|$)/gu;
5
5
  function pathKey(part) {
6
6
  if (typeof part === "object" && part !== null && "key" in part) return part.key;
7
7
  return part;
@@ -134,5 +134,4 @@ function getRequiredPaths(schema) {
134
134
  }
135
135
  return required;
136
136
  }
137
- //#endregion
138
137
  export { getRequiredPaths, toRequiredPathKey, validateSync };
@@ -1,6 +1,5 @@
1
1
  import { ActionError, ActionIssue, ActionResult } from "./action-result.mjs";
2
2
  import { AnyFormApi } from "@tanstack/react-form";
3
- //#region src/lib/submit-action.d.ts
4
3
  declare function applyActionError(formApi: FormApiLike, error: ActionError): void;
5
4
  type SchemaResult<TOutput> = {
6
5
  readonly value: TOutput;
@@ -66,5 +65,4 @@ type SubmitActionSchemaOptions<TValues, TOutput, TData> = SubmitActionOptions<TV
66
65
  */
67
66
  declare function submitAction<TValues, TOutput, TData>(props: SubmitProps<TValues>, action: Action<TOutput, TData>, options: SubmitActionSchemaOptions<TValues, TOutput, TData>): Promise<ActionResult<TData>>;
68
67
  declare function submitAction<TValues, TData>(props: SubmitProps<TValues>, action: Action<NoInfer<TValues>, TData>, options?: SubmitActionOptions<TValues, TData>): Promise<ActionResult<TData>>;
69
- //#endregion
70
68
  export { type FormApiLike, type SubmitActionOptions, type SubmitActionSchema, applyActionError, submitAction };
@@ -3,7 +3,7 @@ import { toActionError } from "./action-result.mjs";
3
3
  import { pickSchemaShape } from "./schema-defaults.mjs";
4
4
  import { getFormSchema } from "./validation-modes.mjs";
5
5
  import { ValidationError } from "./validation-error.mjs";
6
- //#region src/lib/submit-action.ts
6
+ // src/lib/submit-action.ts
7
7
  function applyActionError(formApi, error) {
8
8
  const fields = {};
9
9
  const formErrors = [...error.formErrors];
@@ -46,5 +46,4 @@ function toFormValues(formApi, data) {
46
46
  const schema = getFormSchema(formApi);
47
47
  return schema ? pickSchemaShape(schema, data) : data;
48
48
  }
49
- //#endregion
50
49
  export { applyActionError, submitAction };
@@ -1,4 +1,3 @@
1
- //#region src/lib/submit-error.d.ts
2
1
  type SubmitErrorState = {
3
2
  readonly submissionAttempts: number;
4
3
  readonly values: unknown;
@@ -32,5 +31,4 @@ declare function setSubmitError(form: SubmitErrorForm, error: unknown): void;
32
31
  declare function reportSubmitError(error: unknown): void;
33
32
  /** The error `form`'s last submit threw, until it went stale. */
34
33
  declare function useSubmitError(form: SubmitErrorForm): SubmitError | undefined;
35
- //#endregion
36
34
  export { type SubmitError, type SubmitErrorForm, reportSubmitError, setSubmitError, useSubmitError };
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
  import { useCallback, useSyncExternalStore } from "react";
3
- //#region src/lib/submit-error.ts
3
+ // src/lib/submit-error.ts
4
4
  const entries = /* @__PURE__ */ new WeakMap();
5
5
  const listeners = /* @__PURE__ */ new WeakMap();
6
6
  function notify(key) {
@@ -62,5 +62,4 @@ function useSubmitError(form) {
62
62
  }, [key]);
63
63
  return useSyncExternalStore(subscribe, () => entries.get(key)?.submitError, () => void 0);
64
64
  }
65
- //#endregion
66
65
  export { reportSubmitError, setSubmitError, useSubmitError };
@@ -1,6 +1,5 @@
1
1
  import { AnyFieldMeta } from "@tanstack/react-form";
2
2
  import { ReactNode } from "react";
3
- //#region src/lib/use-field-control.d.ts
4
3
  type UseFieldControlOptions = {
5
4
  /**
6
5
  * The description the field renders, if any. The control is described by
@@ -79,5 +78,4 @@ type FieldControl<TValue = unknown> = {
79
78
  * state its label, description and error need.
80
79
  */
81
80
  declare function useFieldControl<TValue = unknown>({ description, disabled, label, required }?: UseFieldControlOptions): FieldControl<TValue>;
82
- //#endregion
83
81
  export { type FieldControl, type FieldControlProps, type UseFieldControlOptions, useFieldControl };
@@ -6,7 +6,7 @@ import { useIsFormDisabled } from "./form-disabled.mjs";
6
6
  import { describedBy } from "./aria.mjs";
7
7
  import { useHideFieldErrors, useIsFieldRequired, useIsFieldRequiredBySchema, useIsInvalid } from "./use-is-invalid.mjs";
8
8
  import { useCallback, useId } from "react";
9
- //#region src/lib/use-field-control.ts
9
+ // src/lib/use-field-control.ts
10
10
  function isEmpty(value) {
11
11
  return value === null || value === void 0 || value === "" || Number.isNaN(value) || Array.isArray(value) && value.length === 0;
12
12
  }
@@ -66,5 +66,4 @@ function useFieldControl({ description, disabled, label, required } = {}) {
66
66
  showRequiredIndicator: required ?? schemaRequired
67
67
  };
68
68
  }
69
- //#endregion
70
69
  export { useFieldControl };
@@ -1,4 +1,3 @@
1
- //#region src/lib/use-form-dialog.d.ts
2
1
  type FormDialogOpenOptions<TValues> = {
3
2
  /**
4
3
  * Values to edit in this session. Omit to start from the hook's
@@ -84,5 +83,4 @@ declare function useLeaveGuard({ hasUnsavedChanges, onSave }: UseLeaveGuardOptio
84
83
  * fields' ids come from `useId`), else that root's first focusable element.
85
84
  */
86
85
  declare function findFieldElement(root: ParentNode | null | undefined, name: string): HTMLElement | null;
87
- //#endregion
88
86
  export { type FormDialogController, type FormDialogOpenOptions, type LeaveGuard, type UseFormDialogOptions, type UseLeaveGuardOptions, findFieldElement, useFormDialog, useLeaveGuard };