@zeno-lib/forms 0.10.0 → 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.
- package/dist/create-zeno-form.d.mts +1 -2
- package/dist/create-zeno-form.mjs +1 -2
- package/dist/form-element.d.mts +0 -2
- package/dist/form-element.mjs +1 -2
- package/dist/lib/action-result.d.mts +0 -2
- package/dist/lib/action-result.mjs +1 -2
- package/dist/lib/apply-validation-error.d.mts +0 -2
- package/dist/lib/apply-validation-error.mjs +1 -2
- package/dist/lib/aria.d.mts +1 -3
- package/dist/lib/aria.mjs +1 -2
- package/dist/lib/contexts.d.mts +0 -2
- package/dist/lib/contexts.mjs +1 -2
- package/dist/lib/date-value.d.mts +0 -2
- package/dist/lib/date-value.mjs +2 -3
- package/dist/lib/default-props.d.mts +1 -3
- package/dist/lib/default-props.mjs +1 -2
- package/dist/lib/empty-value.d.mts +0 -2
- package/dist/lib/empty-value.mjs +3 -4
- package/dist/lib/field-hints.d.mts +0 -2
- package/dist/lib/field-hints.mjs +1 -2
- package/dist/lib/focus-first-invalid.d.mts +0 -2
- package/dist/lib/focus-first-invalid.mjs +1 -2
- package/dist/lib/form-disabled.d.mts +0 -2
- package/dist/lib/form-disabled.mjs +1 -2
- package/dist/lib/formatted-number.d.mts +0 -2
- package/dist/lib/formatted-number.mjs +10 -11
- package/dist/lib/item-options.d.mts +0 -2
- package/dist/lib/item-options.mjs +1 -2
- package/dist/lib/schema-defaults.d.mts +0 -2
- package/dist/lib/schema-defaults.mjs +1 -2
- package/dist/lib/schema-required.d.mts +0 -2
- package/dist/lib/schema-required.mjs +3 -4
- package/dist/lib/submit-action.d.mts +0 -2
- package/dist/lib/submit-action.mjs +1 -2
- package/dist/lib/submit-error.d.mts +0 -2
- package/dist/lib/submit-error.mjs +1 -2
- package/dist/lib/use-field-control.d.mts +0 -2
- package/dist/lib/use-field-control.mjs +1 -2
- package/dist/lib/use-form-dialog.d.mts +0 -2
- package/dist/lib/use-form-dialog.mjs +2 -3
- package/dist/lib/use-formatted-number.d.mts +0 -2
- package/dist/lib/use-formatted-number.mjs +1 -2
- package/dist/lib/use-is-invalid.d.mts +0 -2
- package/dist/lib/use-is-invalid.mjs +1 -2
- package/dist/lib/use-rebased-default-values.d.mts +0 -2
- package/dist/lib/use-rebased-default-values.mjs +1 -2
- package/dist/lib/use-unsaved-changes-warning.d.mts +0 -2
- package/dist/lib/use-unsaved-changes-warning.mjs +1 -2
- package/dist/lib/validation-error.d.mts +0 -2
- package/dist/lib/validation-error.mjs +1 -2
- package/dist/lib/validation-logic.d.mts +0 -2
- package/dist/lib/validation-logic.mjs +1 -2
- package/dist/lib/validation-modes.d.mts +0 -2
- package/dist/lib/validation-modes.mjs +1 -2
- package/package.json +8 -8
- package/src/composition.test.tsx +3 -3
- package/src/control-field.test.tsx +1 -1
- package/src/date-picker-field.test.tsx +3 -3
- package/src/form-dialog.test.tsx +3 -3
- package/src/form-error.test.tsx +1 -1
- package/src/formatted-number-fields.test.tsx +3 -3
- package/src/lib/date-value.ts +2 -1
- package/src/lib/empty-value.ts +2 -2
- package/src/lib/formatted-number.ts +9 -9
- package/src/lib/schema-required.ts +2 -2
- package/src/lib/use-form-dialog.ts +1 -1
- package/src/multi-value-fields.test.tsx +2 -2
- package/src/submit-action.test.tsx +4 -4
- package/src/use-app-fields.test.tsx +10 -10
- package/src/use-form.cascade.test.tsx +2 -2
- package/src/use-form.reset-defaults.test.tsx +2 -2
- package/src/use-form.test.tsx +3 -3
- package/src/use-form.unsaved-warning.test.tsx +1 -1
- package/src/use-form.validation-modes.test.tsx +2 -2
- 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
|
-
|
|
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
|
-
|
|
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 };
|
package/dist/form-element.d.mts
CHANGED
|
@@ -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 };
|
package/dist/form-element.mjs
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 };
|
package/dist/lib/aria.d.mts
CHANGED
package/dist/lib/aria.mjs
CHANGED
package/dist/lib/contexts.d.mts
CHANGED
|
@@ -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 };
|
package/dist/lib/contexts.mjs
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { createFormHookContexts } from "@tanstack/react-form";
|
|
3
|
-
|
|
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 };
|
package/dist/lib/date-value.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
|
|
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
|
-
|
|
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,5 @@
|
|
|
1
1
|
import { StandardSchemaLike } from "./schema-required.mjs";
|
|
2
2
|
import { AnyFieldApi } from "@tanstack/react-form";
|
|
3
|
-
//#region src/lib/empty-value.d.ts
|
|
4
3
|
declare function toPathKeys(name: string): PropertyKey[];
|
|
5
4
|
declare function emptyValueFor(schema: StandardSchemaLike | undefined, values: unknown, name: string, fallback: unknown): unknown;
|
|
6
5
|
declare function withEmptyStartValues(schema: StandardSchemaLike, defaults: Record<string, unknown>): Record<string, unknown>;
|
|
@@ -16,5 +15,4 @@ declare function getEmptyValue<T>(field: AnyFieldApi, fallback: T): T;
|
|
|
16
15
|
* so the other fields' values never change it.
|
|
17
16
|
*/
|
|
18
17
|
declare function getNoTextValue(field: AnyFieldApi): string | null;
|
|
19
|
-
//#endregion
|
|
20
18
|
export { emptyValueFor, getEmptyValue, getNoTextValue, toPathKeys, withEmptyStartValues };
|
package/dist/lib/empty-value.mjs
CHANGED
|
@@ -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
|
-
|
|
6
|
-
const NAME_SEGMENT = /[^.[\]]+/
|
|
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
|
}
|
|
@@ -99,5 +99,4 @@ function getNoTextValue(field) {
|
|
|
99
99
|
const start = schema ? extractZodDefaultAt(schema, toPathKeys(field.name)) : { ok: false };
|
|
100
100
|
return start.ok && start.value === null ? null : "";
|
|
101
101
|
}
|
|
102
|
-
//#endregion
|
|
103
102
|
export { emptyValueFor, getEmptyValue, getNoTextValue, toPathKeys, withEmptyStartValues };
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { AnyFieldApi } from "@tanstack/react-form";
|
|
2
|
-
//#region src/lib/field-hints.d.ts
|
|
3
2
|
/**
|
|
4
3
|
* What a field reads from the column its schema comes from, when the column's
|
|
5
4
|
* hint names it. The `percentage()` and `year()` columns of
|
|
@@ -14,5 +13,4 @@ type FieldHints = {
|
|
|
14
13
|
readonly scale?: "fraction" | "percent" | undefined;
|
|
15
14
|
};
|
|
16
15
|
declare function getFieldHints(field: AnyFieldApi, kind: string): FieldHints;
|
|
17
|
-
//#endregion
|
|
18
16
|
export { type FieldHints, getFieldHints };
|
package/dist/lib/field-hints.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { findZodSchemaAt } from "./schema-defaults.mjs";
|
|
2
2
|
import { getFormSchema } from "./validation-modes.mjs";
|
|
3
3
|
import { toPathKeys } from "./empty-value.mjs";
|
|
4
|
-
|
|
4
|
+
// src/lib/field-hints.ts
|
|
5
5
|
const FIELD_HINTS_KEY = Symbol.for("@zeno-lib/field-hints");
|
|
6
6
|
const readHint = (node) => {
|
|
7
7
|
const hint = globalThis[FIELD_HINTS_KEY]?.get(node);
|
|
@@ -20,5 +20,4 @@ function getFieldHints(field, kind) {
|
|
|
20
20
|
scale: scale === "fraction" || scale === "percent" ? scale : void 0
|
|
21
21
|
};
|
|
22
22
|
}
|
|
23
|
-
//#endregion
|
|
24
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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/
|
|
6
|
-
const LEADING_ZEROS = /^0+(?=\d)
|
|
7
|
-
const DIGIT = /\d
|
|
8
|
-
const DIGITS = /\d/
|
|
9
|
-
const PARENTHESIZED = /^\s*\(.*\)\s
|
|
10
|
-
const NOT_NUMERIC = /[^\d.,]/
|
|
11
|
-
const UNICODE_MINUS = /\u2212/
|
|
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
|
-
|
|
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;
|
|
@@ -28,5 +27,4 @@ declare function findZodSchemaAt<T>(schema: SchemaNode, keys: readonly PropertyK
|
|
|
28
27
|
} | undefined;
|
|
29
28
|
declare function pickSchemaShape<T>(schema: SchemaNode, value: T): T;
|
|
30
29
|
declare function deepMergeDefaults(schemaDefaults: Record<string, unknown>, userDefaults: Record<string, unknown> | undefined): Record<string, unknown>;
|
|
31
|
-
//#endregion
|
|
32
30
|
export { deepMergeDefaults, extractZodDefaultAt, extractZodDefaults, findZodSchemaAt, pickSchemaShape };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
// src/lib/schema-defaults.ts
|
|
2
2
|
const MAX_WRAPPER_DEPTH = 16;
|
|
3
3
|
function getDef(node) {
|
|
4
4
|
return node?._zod?.def;
|
|
@@ -178,5 +178,4 @@ function deepMergeDefaults(schemaDefaults, userDefaults) {
|
|
|
178
178
|
}
|
|
179
179
|
return result;
|
|
180
180
|
}
|
|
181
|
-
//#endregion
|
|
182
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
|
-
|
|
1
|
+
// src/lib/schema-required.ts
|
|
2
2
|
const MAX_PROBE_DEPTH = 16;
|
|
3
|
-
const INDEX_SEGMENT = /^\d
|
|
4
|
-
const INDEX_IN_NAME = /\[\d+\]
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { useCallback, useRef, useState } from "react";
|
|
3
|
-
|
|
3
|
+
// src/lib/use-form-dialog.ts
|
|
4
4
|
/**
|
|
5
5
|
* Open/close state for a dialog-hosted form, one "session" per `open()`.
|
|
6
6
|
*
|
|
@@ -91,7 +91,7 @@ function useLeaveGuard({ hasUnsavedChanges, onSave }) {
|
|
|
91
91
|
}
|
|
92
92
|
const FOCUSABLE = "input:not([type=\"hidden\"]):not([disabled]), textarea:not([disabled]), select:not([disabled]), button:not([disabled]), [tabindex]:not([tabindex=\"-1\"])";
|
|
93
93
|
function attributeSelector(attribute, value) {
|
|
94
|
-
return `[${attribute}="${value.replace(/["\\]/
|
|
94
|
+
return `[${attribute}="${value.replace(/["\\]/gu, "\\$&")}"]`;
|
|
95
95
|
}
|
|
96
96
|
/**
|
|
97
97
|
* Find the element to focus for field `name` inside `root`: the control with
|
|
@@ -108,5 +108,4 @@ function findFieldElement(root, name) {
|
|
|
108
108
|
const labelFor = fieldRoot.querySelector("label[for]")?.getAttribute("for");
|
|
109
109
|
return (labelFor ? fieldRoot.querySelector(attributeSelector("id", labelFor)) : null) ?? fieldRoot.querySelector(FOCUSABLE);
|
|
110
110
|
}
|
|
111
|
-
//#endregion
|
|
112
111
|
export { findFieldElement, useFormDialog, useLeaveGuard };
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { ChangeEvent, ClipboardEvent } from "react";
|
|
2
|
-
//#region src/lib/use-formatted-number.d.ts
|
|
3
2
|
type UseFormattedNumberOptions = {
|
|
4
3
|
/** The stored value. `null`/`undefined` renders an empty input. */
|
|
5
4
|
value: number | null | undefined;
|
|
@@ -57,5 +56,4 @@ type FormattedNumberInputProps = {
|
|
|
57
56
|
* can use it.
|
|
58
57
|
*/
|
|
59
58
|
declare function useFormattedNumber({ value, onValueChange, onBlur, locale, maximumFractionDigits, padFraction, useGrouping, allowNegative, maxIntegerDigits, min, max, toDisplay, fromDisplay }: UseFormattedNumberOptions): FormattedNumberInputProps;
|
|
60
|
-
//#endregion
|
|
61
59
|
export { type FormattedNumberInputProps, type UseFormattedNumberOptions, useFormattedNumber };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { formatNumber, mapCaret, parseLocaleNumber, parseTypedNumber, roundTo } from "./formatted-number.mjs";
|
|
3
3
|
import { useLayoutEffect, useReducer, useRef, useState } from "react";
|
|
4
|
-
|
|
4
|
+
// src/lib/use-formatted-number.ts
|
|
5
5
|
const identity = (value) => value;
|
|
6
6
|
function clamp(value, min, max) {
|
|
7
7
|
let next = value;
|
|
@@ -100,5 +100,4 @@ function useFormattedNumber({ value, onValueChange, onBlur, locale, maximumFract
|
|
|
100
100
|
value: text
|
|
101
101
|
};
|
|
102
102
|
}
|
|
103
|
-
//#endregion
|
|
104
103
|
export { useFormattedNumber };
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { AnyFieldApi } from "@tanstack/react-form";
|
|
2
|
-
//#region src/lib/use-is-invalid.d.ts
|
|
3
2
|
declare function useIsInvalid(field: AnyFieldApi): boolean;
|
|
4
3
|
declare function useHideFieldErrors(field: AnyFieldApi): boolean;
|
|
5
4
|
declare function useIsFieldRequired(field: AnyFieldApi): boolean;
|
|
6
5
|
declare function useIsFieldRequiredBySchema(field: AnyFieldApi): boolean;
|
|
7
|
-
//#endregion
|
|
8
6
|
export { useHideFieldErrors, useIsFieldRequired, useIsFieldRequiredBySchema, useIsInvalid };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { getFormHideFieldErrors, getFormValidationMode, isFieldRequired, isFieldRequiredBySchema } from "./validation-modes.mjs";
|
|
3
3
|
import { useSelector } from "@tanstack/react-form";
|
|
4
|
-
|
|
4
|
+
// src/lib/use-is-invalid.ts
|
|
5
5
|
function hasFieldLevelError(errorMap, errorSourceMap, cause) {
|
|
6
6
|
if (!(errorMap && errorSourceMap)) return false;
|
|
7
7
|
return errorSourceMap[cause] === "field" && errorMap[cause] !== void 0;
|
|
@@ -30,5 +30,4 @@ function useIsFieldRequired(field) {
|
|
|
30
30
|
function useIsFieldRequiredBySchema(field) {
|
|
31
31
|
return isFieldRequiredBySchema(field.form, field.name);
|
|
32
32
|
}
|
|
33
|
-
//#endregion
|
|
34
33
|
export { useHideFieldErrors, useIsFieldRequired, useIsFieldRequiredBySchema, useIsInvalid };
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import { AnyFormApi } from "@tanstack/react-form";
|
|
2
|
-
//#region src/lib/use-rebased-default-values.d.ts
|
|
3
2
|
declare function useRebasedDefaultValues<T>(callerDefaults: T): {
|
|
4
3
|
defaultValues: T;
|
|
5
4
|
onMount: (props: {
|
|
6
5
|
formApi: AnyFormApi;
|
|
7
6
|
}) => void;
|
|
8
7
|
};
|
|
9
|
-
//#endregion
|
|
10
8
|
export { useRebasedDefaultValues };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { evaluate } from "@tanstack/react-form";
|
|
3
3
|
import { useCallback, useLayoutEffect, useRef } from "react";
|
|
4
|
-
|
|
4
|
+
// src/lib/use-rebased-default-values.ts
|
|
5
5
|
function useRebasedDefaultValues(callerDefaults) {
|
|
6
6
|
const liveForm = useRef(null);
|
|
7
7
|
const lastCallerDefaults = useRef(callerDefaults);
|
|
@@ -18,5 +18,4 @@ function useRebasedDefaultValues(callerDefaults) {
|
|
|
18
18
|
onMount
|
|
19
19
|
};
|
|
20
20
|
}
|
|
21
|
-
//#endregion
|
|
22
21
|
export { useRebasedDefaultValues };
|
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
import { AnyFormApi } from "@tanstack/react-form";
|
|
2
|
-
//#region src/lib/use-unsaved-changes-warning.d.ts
|
|
3
2
|
type UnsavedChangesMode = "if-changed" | "if-touched";
|
|
4
3
|
declare function useUnsavedChangesWarning(form: AnyFormApi, enabled: boolean | UnsavedChangesMode): void;
|
|
5
|
-
//#endregion
|
|
6
4
|
export { type UnsavedChangesMode, useUnsavedChangesWarning };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { useSelector } from "@tanstack/react-form";
|
|
3
3
|
import { useEffect } from "react";
|
|
4
|
-
|
|
4
|
+
// src/lib/use-unsaved-changes-warning.ts
|
|
5
5
|
function useUnsavedChangesWarning(form, enabled) {
|
|
6
6
|
let mode;
|
|
7
7
|
if (enabled === false) mode = null;
|
|
@@ -20,5 +20,4 @@ function useUnsavedChangesWarning(form, enabled) {
|
|
|
20
20
|
return () => window.removeEventListener("beforeunload", handler);
|
|
21
21
|
}, [active]);
|
|
22
22
|
}
|
|
23
|
-
//#endregion
|
|
24
23
|
export { useUnsavedChangesWarning };
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
//#region src/lib/validation-error.d.ts
|
|
2
1
|
type FieldMessage = string | readonly string[];
|
|
3
2
|
declare class ValidationError extends Error {
|
|
4
3
|
readonly fields: Readonly<Record<string, FieldMessage>>;
|
|
@@ -8,5 +7,4 @@ declare class ValidationError extends Error {
|
|
|
8
7
|
message?: string;
|
|
9
8
|
});
|
|
10
9
|
}
|
|
11
|
-
//#endregion
|
|
12
10
|
export { type FieldMessage, ValidationError };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
// src/lib/validation-error.ts
|
|
2
2
|
var ValidationError = class extends Error {
|
|
3
3
|
constructor(fields, options = {}) {
|
|
4
4
|
super(options.message ?? "Validation failed");
|
|
@@ -7,5 +7,4 @@ var ValidationError = class extends Error {
|
|
|
7
7
|
this.formError = options.formError;
|
|
8
8
|
}
|
|
9
9
|
};
|
|
10
|
-
//#endregion
|
|
11
10
|
export { ValidationError };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
|
|
2
|
+
// src/lib/validation-logic.ts
|
|
3
3
|
function pickLiveValidator(validators, isAsync) {
|
|
4
4
|
const fn = isAsync ? validators?.onChangeAsync ?? validators?.onBlurAsync : validators?.onChange ?? validators?.onBlur;
|
|
5
5
|
return fn ? {
|
|
@@ -110,5 +110,4 @@ function blurThenChangeLogicImpl(props) {
|
|
|
110
110
|
});
|
|
111
111
|
}
|
|
112
112
|
const blurThenChangeLogic = blurThenChangeLogicImpl;
|
|
113
|
-
//#endregion
|
|
114
113
|
export { blurThenChangeLogic };
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { StandardSchemaLike } from "./schema-required.mjs";
|
|
2
2
|
import { AnyFormApi } from "@tanstack/react-form";
|
|
3
|
-
//#region src/lib/validation-modes.d.ts
|
|
4
3
|
type ValidationMode = "change" | "blur" | "submit" | "blur-then-change";
|
|
5
4
|
declare const DEFAULT_VALIDATION_MODE: ValidationMode;
|
|
6
5
|
type ZenoFormState = {
|
|
@@ -16,5 +15,4 @@ declare function getFormHideFieldErrors(form: AnyFormApi): boolean;
|
|
|
16
15
|
declare function getFormSchema(form: AnyFormApi): StandardSchemaLike | undefined;
|
|
17
16
|
declare function isFieldRequiredBySchema(form: AnyFormApi, name: string): boolean;
|
|
18
17
|
declare function isFieldRequired(form: AnyFormApi, name: string): boolean;
|
|
19
|
-
//#endregion
|
|
20
18
|
export { DEFAULT_VALIDATION_MODE, type ValidationMode, getFormHideFieldErrors, getFormSchema, getFormValidationMode, isFieldRequired, isFieldRequiredBySchema, setFormZenoState };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { toRequiredPathKey } from "./schema-required.mjs";
|
|
3
|
-
|
|
3
|
+
// src/lib/validation-modes.ts
|
|
4
4
|
const DEFAULT_VALIDATION_MODE = "blur-then-change";
|
|
5
5
|
const DEFAULT_FORM_STATE = {
|
|
6
6
|
hideFieldErrors: false,
|
|
@@ -39,5 +39,4 @@ function isFieldRequiredBySchema(form, name) {
|
|
|
39
39
|
function isFieldRequired(form, name) {
|
|
40
40
|
return getFormZenoState(form).requiredIndicator && isFieldRequiredBySchema(form, name);
|
|
41
41
|
}
|
|
42
|
-
//#endregion
|
|
43
42
|
export { DEFAULT_VALIDATION_MODE, getFormHideFieldErrors, getFormSchema, getFormValidationMode, isFieldRequired, isFieldRequiredBySchema, setFormZenoState };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zeno-lib/forms",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.1",
|
|
4
4
|
"repository": {
|
|
5
5
|
"url": "https://github.com/zeno-lib/zeno.git",
|
|
6
6
|
"directory": "packages/forms"
|
|
@@ -33,18 +33,18 @@
|
|
|
33
33
|
"@tanstack/react-form": "1.33.5"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
|
-
"@types/node": "26.6.
|
|
36
|
+
"@types/node": "26.6.5",
|
|
37
37
|
"@types/react": "19.3.0",
|
|
38
38
|
"@types/react-dom": "19.3.0",
|
|
39
|
-
"@zeno-lib/test": "^0.1.
|
|
39
|
+
"@zeno-lib/test": "^0.1.2",
|
|
40
40
|
"@zeno-lib/typescript": "^1.2.0",
|
|
41
41
|
"@zeno-lib/ui": "^0.0.1",
|
|
42
|
-
"lucide-react": "^1.
|
|
43
|
-
"tsdown": "^0.23.
|
|
42
|
+
"lucide-react": "^1.62.0",
|
|
43
|
+
"tsdown": "^0.23.1",
|
|
44
44
|
"typescript": "7.0.2",
|
|
45
|
-
"vite": "8.3.
|
|
46
|
-
"vitest": "5.0.
|
|
47
|
-
"zod": "^4.6.
|
|
45
|
+
"vite": "8.3.5",
|
|
46
|
+
"vitest": "5.0.5",
|
|
47
|
+
"zod": "^4.6.7"
|
|
48
48
|
},
|
|
49
49
|
"peerDependencies": {
|
|
50
50
|
"@tanstack/react-devtools": ">=0.10",
|
package/src/composition.test.tsx
CHANGED
|
@@ -11,9 +11,9 @@ import {
|
|
|
11
11
|
withForm,
|
|
12
12
|
} from "./create-form"
|
|
13
13
|
|
|
14
|
-
const STREET_LABEL = /Street/
|
|
15
|
-
const CITY_LABEL = /City/
|
|
16
|
-
const BILLING_STREET_LABEL = /Billing street/
|
|
14
|
+
const STREET_LABEL = /Street/u
|
|
15
|
+
const CITY_LABEL = /City/u
|
|
16
|
+
const BILLING_STREET_LABEL = /Billing street/u
|
|
17
17
|
|
|
18
18
|
afterEach(() => {
|
|
19
19
|
cleanup()
|
|
@@ -9,9 +9,9 @@ import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"
|
|
|
9
9
|
import { Form, FormProvider, useForm } from "./create-form"
|
|
10
10
|
import type { DateValueType } from "./lib/date-value"
|
|
11
11
|
|
|
12
|
-
const DAY_LABEL = /Day/
|
|
13
|
-
const OCTOBER_9_SELECTED = /October 9th, 2026, selected/
|
|
14
|
-
const OCTOBER_10 = /October 10th, 2026/
|
|
12
|
+
const DAY_LABEL = /Day/u
|
|
13
|
+
const OCTOBER_9_SELECTED = /October 9th, 2026, selected/u
|
|
14
|
+
const OCTOBER_10 = /October 10th, 2026/u
|
|
15
15
|
|
|
16
16
|
afterEach(() => {
|
|
17
17
|
cleanup()
|
package/src/form-dialog.test.tsx
CHANGED
|
@@ -20,9 +20,9 @@ import type { ActionResult } from "./lib/action-result"
|
|
|
20
20
|
import { submitAction } from "./lib/submit-action"
|
|
21
21
|
import { type FormDialogController, useFormDialog } from "./lib/use-form-dialog"
|
|
22
22
|
|
|
23
|
-
const NAME = /Name/
|
|
24
|
-
const EMAIL = /Email/
|
|
25
|
-
const INNER_NAME = /Inner name/
|
|
23
|
+
const NAME = /Name/u
|
|
24
|
+
const EMAIL = /Email/u
|
|
25
|
+
const INNER_NAME = /Inner name/u
|
|
26
26
|
|
|
27
27
|
const animatable = HTMLElement.prototype as {
|
|
28
28
|
getAnimations?: () => unknown[]
|
package/src/form-error.test.tsx
CHANGED
|
@@ -11,9 +11,9 @@ import { z } from "zod"
|
|
|
11
11
|
import { Form, FormProvider, useForm } from "./create-form"
|
|
12
12
|
import { getNumberSeparators } from "./lib/formatted-number"
|
|
13
13
|
|
|
14
|
-
const AMOUNT = /Amount/
|
|
15
|
-
const RATE = /Rate/
|
|
16
|
-
const YEAR = /Year/
|
|
14
|
+
const AMOUNT = /Amount/u
|
|
15
|
+
const RATE = /Rate/u
|
|
16
|
+
const YEAR = /Year/u
|
|
17
17
|
const CH = getNumberSeparators("de-CH").group
|
|
18
18
|
|
|
19
19
|
afterEach(() => {
|
package/src/lib/date-value.ts
CHANGED
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
*/
|
|
10
10
|
type DateValueType = "date" | "iso-date" | "iso-datetime"
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
// biome-ignore lint/style/useNamedCaptureGroup: named groups need ES2018, past the ES6 target
|
|
13
|
+
const ISO_DATE = /^(\d{4})-(\d{2})-(\d{2})$/u
|
|
13
14
|
|
|
14
15
|
function pad(value: number, length: number): string {
|
|
15
16
|
return String(value).padStart(length, "0")
|
package/src/lib/empty-value.ts
CHANGED
|
@@ -12,8 +12,8 @@ import { getFormSchema } from "./validation-modes"
|
|
|
12
12
|
// accepts none of them is required. A cleared field holds its empty value,
|
|
13
13
|
// and so does an untouched one whose schema rejects `""` or `[]`.
|
|
14
14
|
|
|
15
|
-
const NAME_SEGMENT = /[^.[\]]+/
|
|
16
|
-
const INDEX = /^\d+$/
|
|
15
|
+
const NAME_SEGMENT = /[^.[\]]+/gu
|
|
16
|
+
const INDEX = /^\d+$/u
|
|
17
17
|
|
|
18
18
|
// `members[2].name` (or `members.2.name`) → `["members", 2, "name"]`.
|
|
19
19
|
function toPathKeys(name: string): PropertyKey[] {
|
|
@@ -43,15 +43,15 @@ const separatorCache = new Map<string, NumberSeparators>()
|
|
|
43
43
|
|
|
44
44
|
// Minus signs a user may type or paste: ASCII hyphen-minus, U+2212 (what Intl
|
|
45
45
|
// emits for some locales), and the en dash.
|
|
46
|
-
const MINUS_SIGNS = /[-−–]/
|
|
47
|
-
const LEADING_MINUS = /^[^\d]*[-−–]/
|
|
48
|
-
const NON_DIGIT = /\D/
|
|
49
|
-
const LEADING_ZEROS = /^0+(?=\d)/
|
|
50
|
-
const DIGIT = /\d/
|
|
51
|
-
const DIGITS = /\d/
|
|
52
|
-
const PARENTHESIZED = /^\s*\(.*\)\s*$/
|
|
53
|
-
const NOT_NUMERIC = /[^\d.,]/
|
|
54
|
-
const UNICODE_MINUS = /\u2212/
|
|
46
|
+
const MINUS_SIGNS = /[-−–]/u
|
|
47
|
+
const LEADING_MINUS = /^[^\d]*[-−–]/u
|
|
48
|
+
const NON_DIGIT = /\D/gu
|
|
49
|
+
const LEADING_ZEROS = /^0+(?=\d)/u
|
|
50
|
+
const DIGIT = /\d/u
|
|
51
|
+
const DIGITS = /\d/gu
|
|
52
|
+
const PARENTHESIZED = /^\s*\(.*\)\s*$/u
|
|
53
|
+
const NOT_NUMERIC = /[^\d.,]/gu
|
|
54
|
+
const UNICODE_MINUS = /\u2212/gu
|
|
55
55
|
|
|
56
56
|
function getNumberSeparators(locale?: string): NumberSeparators {
|
|
57
57
|
const key = locale ?? ""
|
|
@@ -53,10 +53,10 @@ type StandardSchemaLike = {
|
|
|
53
53
|
// Each level can take two passes: the empty value, then the descent.
|
|
54
54
|
const MAX_PROBE_DEPTH = 16
|
|
55
55
|
|
|
56
|
-
const INDEX_SEGMENT = /^\d+$/
|
|
56
|
+
const INDEX_SEGMENT = /^\d+$/u
|
|
57
57
|
|
|
58
58
|
// Matches `[3]` and `.3` index segments in a field name.
|
|
59
|
-
const INDEX_IN_NAME = /\[\d+\]
|
|
59
|
+
const INDEX_IN_NAME = /\[\d+\]|\.\d+(?=\.|\[|$)/gu
|
|
60
60
|
|
|
61
61
|
function pathKey(part: PathPart): PropertyKey {
|
|
62
62
|
// biome-ignore lint/suspicious/noUnnecessaryConditions: issue paths come from third-party validators at runtime, and a non-conforming `null` segment would make `in` throw.
|
|
@@ -199,7 +199,7 @@ const FOCUSABLE =
|
|
|
199
199
|
'input:not([type="hidden"]):not([disabled]), textarea:not([disabled]), select:not([disabled]), button:not([disabled]), [tabindex]:not([tabindex="-1"])'
|
|
200
200
|
|
|
201
201
|
function attributeSelector(attribute: string, value: string): string {
|
|
202
|
-
return `[${attribute}="${value.replace(/["\\]/
|
|
202
|
+
return `[${attribute}="${value.replace(/["\\]/gu, "\\$&")}"]`
|
|
203
203
|
}
|
|
204
204
|
|
|
205
205
|
/**
|
|
@@ -3,8 +3,8 @@ import userEvent from "@zeno-lib/test/user-event"
|
|
|
3
3
|
import { afterEach, describe, expect, test, vi } from "vitest"
|
|
4
4
|
import { Form, FormProvider, useForm } from "./create-form"
|
|
5
5
|
|
|
6
|
-
const TAGS = /Tags/
|
|
7
|
-
const CLEAR = /Clear/
|
|
6
|
+
const TAGS = /Tags/u
|
|
7
|
+
const CLEAR = /Clear/u
|
|
8
8
|
|
|
9
9
|
afterEach(() => {
|
|
10
10
|
cleanup()
|
|
@@ -14,10 +14,10 @@ afterEach(() => {
|
|
|
14
14
|
})
|
|
15
15
|
|
|
16
16
|
const SUBMIT = { name: "Submit" }
|
|
17
|
-
const EMAIL_LABEL = /Email/
|
|
18
|
-
const NAME_LABEL = /Name/
|
|
19
|
-
const OWNER_1_SHARE_LABEL = /^Owner 1 share/
|
|
20
|
-
const OWNER_2_SHARE_LABEL = /^Owner 2 share/
|
|
17
|
+
const EMAIL_LABEL = /Email/u
|
|
18
|
+
const NAME_LABEL = /Name/u
|
|
19
|
+
const OWNER_1_SHARE_LABEL = /^Owner 1 share/u
|
|
20
|
+
const OWNER_2_SHARE_LABEL = /^Owner 2 share/u
|
|
21
21
|
|
|
22
22
|
function failure(
|
|
23
23
|
fieldErrors: Record<string, string[]>,
|
|
@@ -12,16 +12,16 @@ import { z } from "zod"
|
|
|
12
12
|
import { SelectItem } from "@/components/ui/select"
|
|
13
13
|
import { Form, FormProvider, RadioGroupFieldItem, useForm } from "./create-form"
|
|
14
14
|
|
|
15
|
-
const CONTACT_EMAIL_LABEL = /Contact email/
|
|
16
|
-
const PASSWORD_LABEL = /Password/
|
|
17
|
-
const NAME_LABEL = /Name/
|
|
18
|
-
const ACCEPT_NAME = /Accept/
|
|
19
|
-
const TITLE_LABEL = /Title/
|
|
20
|
-
const NOTE_LABEL = /Note/
|
|
21
|
-
const DAY_LABEL = /Day/
|
|
22
|
-
const PICKED_DAY = /October 9/
|
|
23
|
-
const ROLE_LABEL = /Role/
|
|
24
|
-
const OWNER_LABEL = /Owner/
|
|
15
|
+
const CONTACT_EMAIL_LABEL = /Contact email/u
|
|
16
|
+
const PASSWORD_LABEL = /Password/u
|
|
17
|
+
const NAME_LABEL = /Name/u
|
|
18
|
+
const ACCEPT_NAME = /Accept/u
|
|
19
|
+
const TITLE_LABEL = /Title/u
|
|
20
|
+
const NOTE_LABEL = /Note/u
|
|
21
|
+
const DAY_LABEL = /Day/u
|
|
22
|
+
const PICKED_DAY = /October 9/u
|
|
23
|
+
const ROLE_LABEL = /Role/u
|
|
24
|
+
const OWNER_LABEL = /Owner/u
|
|
25
25
|
const PICK_A_ROLE = "Pick a role"
|
|
26
26
|
const PICK_A_DAY = "Pick a day"
|
|
27
27
|
|
|
@@ -4,8 +4,8 @@ import { afterEach, describe, expect, test, vi } from "vitest"
|
|
|
4
4
|
import { z } from "zod"
|
|
5
5
|
import { Form, FormProvider, useForm } from "./create-form"
|
|
6
6
|
|
|
7
|
-
const DIET_LABEL = /Diet/
|
|
8
|
-
const SAUSAGE_LABEL = /Sausage/
|
|
7
|
+
const DIET_LABEL = /Diet/u
|
|
8
|
+
const SAUSAGE_LABEL = /Sausage/u
|
|
9
9
|
|
|
10
10
|
afterEach(() => {
|
|
11
11
|
cleanup()
|
package/src/use-form.test.tsx
CHANGED
|
@@ -6,9 +6,9 @@ import { z } from "zod"
|
|
|
6
6
|
import { Form, FormProvider, useForm } from "./create-form"
|
|
7
7
|
import { ValidationError } from "./lib/validation-error"
|
|
8
8
|
|
|
9
|
-
const EMAIL_LABEL = /Email/
|
|
10
|
-
const VALID_EMAIL_MSG = /Enter a valid email/
|
|
11
|
-
const SUBMIT_NAME = /Submit/
|
|
9
|
+
const EMAIL_LABEL = /Email/u
|
|
10
|
+
const VALID_EMAIL_MSG = /Enter a valid email/iu
|
|
11
|
+
const SUBMIT_NAME = /Submit/u
|
|
12
12
|
|
|
13
13
|
// @testing-library auto-cleanup only runs with `globals: true`. Our vitest
|
|
14
14
|
// config keeps globals off (we import test helpers explicitly), so add the
|
|
@@ -4,7 +4,7 @@ import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"
|
|
|
4
4
|
import { z } from "zod"
|
|
5
5
|
import { Form, FormProvider, useForm } from "./create-form"
|
|
6
6
|
|
|
7
|
-
const NAME_LABEL = /Name/
|
|
7
|
+
const NAME_LABEL = /Name/u
|
|
8
8
|
|
|
9
9
|
let addSpy: ReturnType<typeof vi.spyOn>
|
|
10
10
|
let removeSpy: ReturnType<typeof vi.spyOn>
|
|
@@ -30,8 +30,8 @@ function Harness({ mode }: { mode?: ValidationMode }) {
|
|
|
30
30
|
)
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
const ERROR = /Enter a valid email/
|
|
34
|
-
const EMAIL_LABEL = /Email/
|
|
33
|
+
const ERROR = /Enter a valid email/iu
|
|
34
|
+
const EMAIL_LABEL = /Email/u
|
|
35
35
|
|
|
36
36
|
function getInput() {
|
|
37
37
|
return screen.getByLabelText(EMAIL_LABEL) as HTMLInputElement
|
package/src/with-form.test.tsx
CHANGED