@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.
- 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 +9 -3
- package/dist/lib/empty-value.mjs +15 -5
- package/dist/lib/field-hints.d.mts +16 -0
- package/dist/lib/field-hints.mjs +23 -0
- 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 +6 -3
- package/dist/lib/schema-defaults.mjs +37 -5
- 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/field-hints.test.tsx +118 -0
- package/src/fields/input-field.tsx +6 -4
- package/src/fields/percentage-field.tsx +16 -8
- package/src/fields/textarea-field.tsx +6 -4
- package/src/fields/year-field.tsx +8 -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 +26 -3
- package/src/lib/field-hints.ts +64 -0
- package/src/lib/formatted-number.ts +9 -9
- package/src/lib/schema-defaults.test.ts +6 -3
- package/src/lib/schema-defaults.ts +60 -3
- 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/nullable-text.test.tsx +80 -0
- 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,6 @@
|
|
|
1
1
|
import { StandardSchemaLike } from "./schema-required.mjs";
|
|
2
2
|
import { AnyFieldApi } from "@tanstack/react-form";
|
|
3
|
-
|
|
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
|
-
|
|
12
|
-
|
|
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 };
|
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
|
}
|
|
@@ -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
|
-
|
|
92
|
-
|
|
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
|
-
|
|
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;
|
|
@@ -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
|
-
|
|
27
|
-
export { deepMergeDefaults, extractZodDefaultAt, extractZodDefaults, pickSchemaShape };
|
|
30
|
+
export { deepMergeDefaults, extractZodDefaultAt, extractZodDefaults, findZodSchemaAt, pickSchemaShape };
|
|
@@ -1,10 +1,11 @@
|
|
|
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;
|
|
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
|
-
|
|
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
|
-
|
|
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 };
|