@zeno-lib/forms 0.2.0 → 0.2.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 (41) hide show
  1. package/dist/create-zeno-form.d.mts +118 -0
  2. package/dist/create-zeno-form.mjs +121 -0
  3. package/dist/form-element.d.mts +13 -0
  4. package/dist/form-element.mjs +31 -0
  5. package/dist/index.d.mts +11 -0
  6. package/dist/index.mjs +10 -0
  7. package/dist/lib/action-result.d.mts +22 -0
  8. package/dist/lib/action-result.mjs +32 -0
  9. package/dist/lib/apply-validation-error.d.mts +6 -0
  10. package/dist/lib/apply-validation-error.mjs +41 -0
  11. package/dist/lib/aria.d.mts +4 -0
  12. package/dist/lib/aria.mjs +7 -0
  13. package/dist/lib/contexts.d.mts +5 -0
  14. package/dist/lib/contexts.mjs +7 -0
  15. package/dist/lib/formatted-number.d.mts +56 -0
  16. package/dist/lib/formatted-number.mjs +138 -0
  17. package/dist/lib/schema-defaults.d.mts +17 -0
  18. package/dist/lib/schema-defaults.mjs +75 -0
  19. package/dist/lib/schema-required.d.mts +25 -0
  20. package/dist/lib/schema-required.mjs +72 -0
  21. package/dist/lib/submit-action.d.mts +64 -0
  22. package/dist/lib/submit-action.mjs +43 -0
  23. package/dist/lib/use-form-dialog.d.mts +73 -0
  24. package/dist/lib/use-form-dialog.mjs +90 -0
  25. package/dist/lib/use-formatted-number.d.mts +61 -0
  26. package/dist/lib/use-formatted-number.mjs +104 -0
  27. package/dist/lib/use-is-invalid.d.mts +7 -0
  28. package/dist/lib/use-is-invalid.mjs +31 -0
  29. package/dist/lib/use-rebased-default-values.d.mts +10 -0
  30. package/dist/lib/use-rebased-default-values.mjs +21 -0
  31. package/dist/lib/use-unsaved-changes-warning.d.mts +6 -0
  32. package/dist/lib/use-unsaved-changes-warning.mjs +24 -0
  33. package/dist/lib/validation-error.d.mts +12 -0
  34. package/dist/lib/validation-error.mjs +11 -0
  35. package/dist/lib/validation-logic.d.mts +5 -0
  36. package/dist/lib/validation-logic.mjs +114 -0
  37. package/dist/lib/validation-modes.d.mts +16 -0
  38. package/dist/lib/validation-modes.mjs +38 -0
  39. package/dist/tanstack.d.mts +1 -0
  40. package/dist/tanstack.mjs +2 -0
  41. package/package.json +23 -7
@@ -0,0 +1,21 @@
1
+ "use client";
2
+ import { evaluate } from "@tanstack/react-form";
3
+ import { useCallback, useRef } from "react";
4
+ //#region src/lib/use-rebased-default-values.ts
5
+ function useRebasedDefaultValues(callerDefaults) {
6
+ const liveForm = useRef(null);
7
+ const lastCallerDefaults = useRef(callerDefaults);
8
+ let defaultValues = callerDefaults;
9
+ if (evaluate(lastCallerDefaults.current, callerDefaults)) {
10
+ if (liveForm.current) defaultValues = liveForm.current.options.defaultValues;
11
+ } else lastCallerDefaults.current = callerDefaults;
12
+ const onMount = useCallback((props) => {
13
+ liveForm.current = props.formApi;
14
+ }, []);
15
+ return {
16
+ defaultValues,
17
+ onMount
18
+ };
19
+ }
20
+ //#endregion
21
+ export { useRebasedDefaultValues };
@@ -0,0 +1,6 @@
1
+ import { AnyFormApi } from "@tanstack/react-form";
2
+ //#region src/lib/use-unsaved-changes-warning.d.ts
3
+ type UnsavedChangesMode = "if-changed" | "if-touched";
4
+ declare function useUnsavedChangesWarning(form: AnyFormApi, enabled: boolean | UnsavedChangesMode): void;
5
+ //#endregion
6
+ export { type UnsavedChangesMode, useUnsavedChangesWarning };
@@ -0,0 +1,24 @@
1
+ "use client";
2
+ import { useSelector } from "@tanstack/react-form";
3
+ import { useEffect } from "react";
4
+ //#region src/lib/use-unsaved-changes-warning.ts
5
+ function useUnsavedChangesWarning(form, enabled) {
6
+ let mode;
7
+ if (enabled === false) mode = null;
8
+ else if (enabled === true) mode = "if-changed";
9
+ else mode = enabled;
10
+ const dirty = useSelector(form.store, (state) => mode === "if-touched" ? state.isDirty : !state.isDefaultValue);
11
+ const isSubmitting = useSelector(form.store, (state) => state.isSubmitting);
12
+ const active = mode !== null && dirty && !isSubmitting;
13
+ useEffect(() => {
14
+ if (!active) return;
15
+ const handler = (event) => {
16
+ event.preventDefault();
17
+ event.returnValue = "";
18
+ };
19
+ window.addEventListener("beforeunload", handler);
20
+ return () => window.removeEventListener("beforeunload", handler);
21
+ }, [active]);
22
+ }
23
+ //#endregion
24
+ export { useUnsavedChangesWarning };
@@ -0,0 +1,12 @@
1
+ //#region src/lib/validation-error.d.ts
2
+ type FieldMessage = string | readonly string[];
3
+ declare class ValidationError extends Error {
4
+ readonly fields: Readonly<Record<string, FieldMessage>>;
5
+ readonly formError?: string;
6
+ constructor(fields: Readonly<Record<string, FieldMessage>>, options?: {
7
+ formError?: string;
8
+ message?: string;
9
+ });
10
+ }
11
+ //#endregion
12
+ export { type FieldMessage, ValidationError };
@@ -0,0 +1,11 @@
1
+ //#region src/lib/validation-error.ts
2
+ var ValidationError = class extends Error {
3
+ constructor(fields, options = {}) {
4
+ super(options.message ?? "Validation failed");
5
+ this.name = "ValidationError";
6
+ this.fields = fields;
7
+ this.formError = options.formError;
8
+ }
9
+ };
10
+ //#endregion
11
+ export { ValidationError };
@@ -0,0 +1,5 @@
1
+ import { ValidationLogicFn } from "@tanstack/react-form";
2
+ //#region src/lib/validation-logic.d.ts
3
+ declare const blurThenChangeLogic: ValidationLogicFn;
4
+ //#endregion
5
+ export { blurThenChangeLogic };
@@ -0,0 +1,114 @@
1
+ "use client";
2
+ //#region src/lib/validation-logic.ts
3
+ function pickLiveValidator(validators, isAsync) {
4
+ const fn = isAsync ? validators?.onChangeAsync ?? validators?.onBlurAsync : validators?.onChange ?? validators?.onBlur;
5
+ return fn ? {
6
+ cause: "change",
7
+ fn
8
+ } : void 0;
9
+ }
10
+ function shouldRunFormChange(form) {
11
+ if (form.state.submissionAttempts > 0) return true;
12
+ return Object.values(form.state.fieldMeta).some((meta) => meta?.isBlurred === true);
13
+ }
14
+ function fieldLevelLogic(props) {
15
+ const { event, validators, form } = props;
16
+ const run = props.runValidation;
17
+ if (!validators) return run({
18
+ form,
19
+ validators: []
20
+ });
21
+ const isAsync = event.async;
22
+ if (event.type === "mount") return run({
23
+ form,
24
+ validators: isAsync ? [] : [{
25
+ cause: "mount",
26
+ fn: validators.onMount
27
+ }]
28
+ });
29
+ if (event.type === "change") return run({
30
+ form,
31
+ validators: [{
32
+ cause: "change",
33
+ fn: isAsync ? validators.onChangeAsync : validators.onChange
34
+ }]
35
+ });
36
+ if (event.type === "blur") return run({
37
+ form,
38
+ validators: [{
39
+ cause: "blur",
40
+ fn: isAsync ? validators.onBlurAsync : validators.onBlur
41
+ }]
42
+ });
43
+ if (event.type === "submit") return run({
44
+ form,
45
+ validators: [
46
+ {
47
+ cause: "change",
48
+ fn: isAsync ? validators.onChangeAsync : validators.onChange
49
+ },
50
+ {
51
+ cause: "blur",
52
+ fn: isAsync ? validators.onBlurAsync : validators.onBlur
53
+ },
54
+ {
55
+ cause: "submit",
56
+ fn: isAsync ? validators.onSubmitAsync : validators.onSubmit
57
+ }
58
+ ]
59
+ });
60
+ return run({
61
+ form,
62
+ validators: []
63
+ });
64
+ }
65
+ function buildSubmitValidators(validators, isAsync, liveValidator) {
66
+ return [
67
+ ...liveValidator ? [liveValidator] : [],
68
+ {
69
+ cause: "submit",
70
+ fn: isAsync ? validators.onSubmitAsync : validators.onSubmit
71
+ },
72
+ ...isAsync ? [] : [{
73
+ cause: "server",
74
+ fn: () => void 0
75
+ }]
76
+ ];
77
+ }
78
+ function blurThenChangeLogicImpl(props) {
79
+ if (props.event.fieldName !== void 0) return fieldLevelLogic(props);
80
+ const { event, validators, form } = props;
81
+ const run = props.runValidation;
82
+ if (!validators) return run({
83
+ form,
84
+ validators: []
85
+ });
86
+ const isAsync = event.async;
87
+ const liveValidator = pickLiveValidator(validators, isAsync);
88
+ if (event.type === "mount") return run({
89
+ form,
90
+ validators: isAsync ? [] : [{
91
+ cause: "mount",
92
+ fn: validators.onMount
93
+ }]
94
+ });
95
+ if (event.type === "blur") return run({
96
+ form,
97
+ validators: liveValidator ? [liveValidator] : []
98
+ });
99
+ if (event.type === "change") return run({
100
+ form,
101
+ validators: liveValidator && shouldRunFormChange(form) ? [liveValidator] : []
102
+ });
103
+ if (event.type === "submit") return run({
104
+ form,
105
+ validators: buildSubmitValidators(validators, isAsync, liveValidator)
106
+ });
107
+ return run({
108
+ form,
109
+ validators: []
110
+ });
111
+ }
112
+ const blurThenChangeLogic = blurThenChangeLogicImpl;
113
+ //#endregion
114
+ export { blurThenChangeLogic };
@@ -0,0 +1,16 @@
1
+ import { AnyFormApi } from "@tanstack/react-form";
2
+ //#region src/lib/validation-modes.d.ts
3
+ type ValidationMode = "change" | "blur" | "submit" | "blur-then-change";
4
+ declare const DEFAULT_VALIDATION_MODE: ValidationMode;
5
+ type ZenoFormState = {
6
+ validation: ValidationMode;
7
+ hideFieldErrors: boolean;
8
+ requiredIndicator: boolean;
9
+ requiredFields: ReadonlySet<string>;
10
+ };
11
+ declare function setFormZenoState(form: AnyFormApi, state: Partial<ZenoFormState>): void;
12
+ declare function getFormValidationMode(form: AnyFormApi): ValidationMode;
13
+ declare function getFormHideFieldErrors(form: AnyFormApi): boolean;
14
+ declare function isFieldRequired(form: AnyFormApi, name: string): boolean;
15
+ //#endregion
16
+ export { DEFAULT_VALIDATION_MODE, type ValidationMode, getFormHideFieldErrors, getFormValidationMode, isFieldRequired, setFormZenoState };
@@ -0,0 +1,38 @@
1
+ "use client";
2
+ import { toRequiredPathKey } from "./schema-required.mjs";
3
+ //#region src/lib/validation-modes.ts
4
+ const DEFAULT_VALIDATION_MODE = "blur-then-change";
5
+ const DEFAULT_FORM_STATE = {
6
+ hideFieldErrors: false,
7
+ requiredFields: /* @__PURE__ */ new Set(),
8
+ requiredIndicator: true,
9
+ validation: DEFAULT_VALIDATION_MODE
10
+ };
11
+ const formZenoState = /* @__PURE__ */ new WeakMap();
12
+ function stateKey(form) {
13
+ return form.store;
14
+ }
15
+ function setFormZenoState(form, state) {
16
+ const key = stateKey(form);
17
+ const current = formZenoState.get(key) ?? DEFAULT_FORM_STATE;
18
+ formZenoState.set(key, {
19
+ ...current,
20
+ ...state
21
+ });
22
+ }
23
+ function getFormZenoState(form) {
24
+ return formZenoState.get(stateKey(form)) ?? DEFAULT_FORM_STATE;
25
+ }
26
+ function getFormValidationMode(form) {
27
+ return getFormZenoState(form).validation;
28
+ }
29
+ function getFormHideFieldErrors(form) {
30
+ return getFormZenoState(form).hideFieldErrors;
31
+ }
32
+ function isFieldRequired(form, name) {
33
+ const state = getFormZenoState(form);
34
+ if (!state.requiredIndicator) return false;
35
+ return state.requiredFields.has(toRequiredPathKey(name));
36
+ }
37
+ //#endregion
38
+ export { DEFAULT_VALIDATION_MODE, getFormHideFieldErrors, getFormValidationMode, isFieldRequired, setFormZenoState };
@@ -0,0 +1 @@
1
+ export * from "@tanstack/react-form";
@@ -0,0 +1,2 @@
1
+ export * from "@tanstack/react-form";
2
+ export {};
package/package.json CHANGED
@@ -1,14 +1,28 @@
1
1
  {
2
2
  "name": "@zeno-lib/forms",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
+ "type": "module",
4
5
  "exports": {
5
- ".": "./src/index.ts",
6
+ ".": {
7
+ "types": "./dist/index.d.mts",
8
+ "import": "./dist/index.mjs",
9
+ "default": "./dist/index.mjs"
10
+ },
6
11
  "./create-form": "./src/create-form.tsx",
7
12
  "./form-dialog": "./src/form-dialog.tsx",
8
- "./lib/*": "./src/lib/*.ts",
9
- "./tanstack": "./src/tanstack.ts"
13
+ "./lib/*": {
14
+ "types": "./dist/lib/*.d.mts",
15
+ "import": "./dist/lib/*.mjs",
16
+ "default": "./dist/lib/*.mjs"
17
+ },
18
+ "./tanstack": {
19
+ "types": "./dist/tanstack.d.mts",
20
+ "import": "./dist/tanstack.mjs",
21
+ "default": "./dist/tanstack.mjs"
22
+ }
10
23
  },
11
24
  "files": [
25
+ "dist",
12
26
  "src"
13
27
  ],
14
28
  "publishConfig": {
@@ -43,15 +57,17 @@
43
57
  "@types/react": "19.2.14",
44
58
  "@types/react-dom": "19.2.3",
45
59
  "lucide-react": "^1.23.0",
60
+ "tsdown": "^0.22.14",
46
61
  "typescript": "7.0.2",
47
62
  "vite": "8.0.16",
48
- "vitest": "4.1.11",
63
+ "vitest": "5.0.2",
49
64
  "zod": "^4.3.6",
65
+ "@zeno-lib/test": "^0.1.0",
50
66
  "@zeno-lib/typescript": "^1.1.0",
51
- "@zeno-lib/ui": "^0.0.1",
52
- "@zeno-lib/test": "^0.1.0"
67
+ "@zeno-lib/ui": "^0.0.1"
53
68
  },
54
69
  "scripts": {
70
+ "build": "tsdown",
55
71
  "test": "vitest --run",
56
72
  "test:watch": "vitest watch",
57
73
  "types:check": "tsc --noEmit"