pdyform 2.1.0 → 2.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.
@@ -20,13 +20,10 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/formState.ts
21
21
  var formState_exports = {};
22
22
  __export(formState_exports, {
23
- applyFieldBlur: () => applyFieldBlur,
24
- applyFieldChange: () => applyFieldChange,
25
- createFormRuntimeState: () => createFormRuntimeState,
26
- runSubmitValidation: () => runSubmitValidation,
27
- setSubmitting: () => setSubmitting
23
+ createFormStore: () => createFormStore
28
24
  });
29
25
  module.exports = __toCommonJS(formState_exports);
26
+ var import_vanilla = require("zustand/vanilla");
30
27
 
31
28
  // src/utils.ts
32
29
  function parseNumberish(value) {
@@ -35,85 +32,140 @@ function parseNumberish(value) {
35
32
  const parsed = Number(value);
36
33
  return Number.isNaN(parsed) ? null : parsed;
37
34
  }
35
+ var defaultErrorMessages = {
36
+ required: "{label} is required",
37
+ min: "{label} must be at least {value}",
38
+ max: "{label} must be at most {value}",
39
+ email: "Invalid email address",
40
+ pattern: "Invalid format",
41
+ custom: "Invalid value"
42
+ };
43
+ function formatMessage(template, field, rule) {
44
+ return template.replace("{label}", field.label).replace("{value}", String(rule.value || ""));
45
+ }
46
+ function get(obj, path, defaultValue) {
47
+ if (!path) return defaultValue;
48
+ const keys = path.split(/[.[\]]/).filter(Boolean);
49
+ let result = obj;
50
+ for (const key of keys) {
51
+ if (result === null || result === void 0) return defaultValue;
52
+ result = result[key];
53
+ }
54
+ return result === void 0 ? defaultValue : result;
55
+ }
56
+ function set(obj, path, value) {
57
+ if (Object(obj) !== obj) return obj;
58
+ const keys = path.split(/[.[\]]/).filter(Boolean);
59
+ const newObj = { ...obj };
60
+ let current = newObj;
61
+ for (let i = 0; i < keys.length - 1; i++) {
62
+ const key = keys[i];
63
+ const nextKey = keys[i + 1];
64
+ const isNextKeyIndex = /^\d+$/.test(nextKey);
65
+ if (!(key in current) || current[key] === null || typeof current[key] !== "object") {
66
+ current[key] = isNextKeyIndex ? [] : {};
67
+ } else {
68
+ current[key] = Array.isArray(current[key]) ? [...current[key]] : { ...current[key] };
69
+ }
70
+ current = current[key];
71
+ }
72
+ current[keys[keys.length - 1]] = value;
73
+ return newObj;
74
+ }
38
75
  function normalizeFieldValue(field, value) {
39
76
  if (field.type !== "number") return value;
40
77
  if (value === "" || value === void 0 || value === null) return "";
41
78
  const numericValue = parseNumberish(value);
42
79
  return numericValue === null ? value : numericValue;
43
80
  }
44
- function validateField(value, field) {
81
+ async function validateField(value, field, customMessages) {
45
82
  if (!field.validations) return null;
83
+ const messages = { ...defaultErrorMessages, ...customMessages };
46
84
  for (const rule of field.validations) {
47
85
  switch (rule.type) {
48
86
  case "required":
49
87
  if (value === void 0 || value === null || value === "" || Array.isArray(value) && value.length === 0) {
50
- return rule.message || `${field.label} is required`;
88
+ return rule.message || formatMessage(messages.required, field, rule);
51
89
  }
52
90
  break;
53
91
  case "min":
54
92
  if (field.type === "number") {
55
93
  const numericValue = parseNumberish(value);
56
94
  if (numericValue !== null && numericValue < rule.value) {
57
- return rule.message || `${field.label} must be at least ${rule.value}`;
95
+ const template = field.type === "number" ? messages.min : typeof value === "string" ? "{label} must be at least {value} characters" : messages.min;
96
+ return rule.message || formatMessage(template, field, rule);
58
97
  }
59
98
  break;
60
99
  }
61
100
  if (typeof value === "number" && value < rule.value) {
62
- return rule.message || `${field.label} must be at least ${rule.value}`;
101
+ return rule.message || formatMessage(messages.min, field, rule);
63
102
  }
64
103
  if (typeof value === "string" && value.length < rule.value) {
65
- return rule.message || `${field.label} must be at least ${rule.value} characters`;
104
+ const template = "{label} must be at least {value} characters";
105
+ return rule.message || formatMessage(template, field, rule);
66
106
  }
67
107
  break;
68
108
  case "max":
69
109
  if (field.type === "number") {
70
110
  const numericValue = parseNumberish(value);
71
111
  if (numericValue !== null && numericValue > rule.value) {
72
- return rule.message || `${field.label} must be at most ${rule.value}`;
112
+ return rule.message || formatMessage(messages.max, field, rule);
73
113
  }
74
114
  break;
75
115
  }
76
116
  if (typeof value === "number" && value > rule.value) {
77
- return rule.message || `${field.label} must be at most ${rule.value}`;
117
+ return rule.message || formatMessage(messages.max, field, rule);
78
118
  }
79
119
  if (typeof value === "string" && value.length > rule.value) {
80
- return rule.message || `${field.label} must be at most ${rule.value} characters`;
120
+ const template = "{label} must be at most {value} characters";
121
+ return rule.message || formatMessage(template, field, rule);
81
122
  }
82
123
  break;
83
124
  case "email": {
84
125
  const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
85
126
  if (value && !emailRegex.test(value)) {
86
- return rule.message || "Invalid email address";
127
+ return rule.message || formatMessage(messages.email, field, rule);
87
128
  }
88
129
  break;
89
130
  }
90
131
  case "pattern":
91
132
  if (value && rule.value && !new RegExp(rule.value).test(value)) {
92
- return rule.message || "Invalid format";
133
+ return rule.message || formatMessage(messages.pattern, field, rule);
93
134
  }
94
135
  break;
95
136
  case "custom":
96
137
  if (rule.validator) {
97
- const result = rule.validator(value);
138
+ const result = await rule.validator(value);
98
139
  if (typeof result === "string") return result;
99
- if (!result) return rule.message || "Invalid value";
140
+ if (result === false) return rule.message || formatMessage(messages.custom, field, rule);
100
141
  }
101
142
  break;
102
143
  }
103
144
  }
104
145
  return null;
105
146
  }
106
- function validateFieldByName(fields, name, value) {
147
+ async function validateFieldByName(fields, name, value, resolver, allValues, customMessages) {
148
+ if (resolver && allValues) {
149
+ const resolverErrors = await resolver(allValues);
150
+ if (resolverErrors[name]) return resolverErrors[name];
151
+ }
107
152
  const field = fields.find((f) => f.name === name);
108
153
  if (!field) return null;
109
- return validateField(value, field);
154
+ return await validateField(value, field, customMessages);
110
155
  }
111
- function validateForm(fields, values) {
112
- const errors = {};
113
- for (const field of fields) {
114
- const error = validateField(values[field.name], field);
115
- if (error) errors[field.name] = error;
156
+ async function validateForm(fields, values, resolver, customMessages) {
157
+ let errors = {};
158
+ if (resolver) {
159
+ errors = await resolver(values);
116
160
  }
161
+ const validationPromises = fields.map(async (field) => {
162
+ if (errors[field.name]) return;
163
+ const isHidden = typeof field.hidden === "function" ? field.hidden(values) : field.hidden;
164
+ if (isHidden) return;
165
+ const error = await validateField(get(values, field.name), field, customMessages);
166
+ if (error) errors[field.name] = error;
167
+ });
168
+ await Promise.all(validationPromises);
117
169
  return errors;
118
170
  }
119
171
  function getDefaultValues(fields) {
@@ -124,64 +176,74 @@ function getDefaultValues(fields) {
124
176
  }
125
177
 
126
178
  // src/formState.ts
127
- function createFormRuntimeState(fields) {
128
- return {
179
+ function createFormStore(fields, resolver, errorMessages) {
180
+ return (0, import_vanilla.createStore)()((set2, getStore) => ({
129
181
  values: getDefaultValues(fields),
130
182
  errors: {},
131
- isSubmitting: false
132
- };
133
- }
134
- function setSubmitting(state, isSubmitting) {
135
- return {
136
- ...state,
137
- isSubmitting
138
- };
139
- }
140
- function applyFieldChange(fields, state, name, rawValue) {
141
- const field = fields.find((f) => f.name === name);
142
- const normalizedValue = field ? normalizeFieldValue(field, rawValue) : rawValue;
143
- const values = {
144
- ...state.values,
145
- [name]: normalizedValue
146
- };
147
- const error = validateFieldByName(fields, name, normalizedValue);
148
- const errors = {
149
- ...state.errors,
150
- [name]: error || ""
151
- };
152
- return {
153
- ...state,
154
- values,
155
- errors
156
- };
157
- }
158
- function applyFieldBlur(fields, state, name) {
159
- const error = validateFieldByName(fields, name, state.values[name]);
160
- return {
161
- ...state,
162
- errors: {
163
- ...state.errors,
164
- [name]: error || ""
165
- }
166
- };
167
- }
168
- function runSubmitValidation(fields, state) {
169
- const errors = validateForm(fields, state.values);
170
- const hasError = Object.keys(errors).length > 0;
171
- return {
172
- state: {
173
- ...state,
174
- errors,
175
- isSubmitting: false
183
+ validatingFields: [],
184
+ isSubmitting: false,
185
+ setFieldValue: async (name, rawValue) => {
186
+ const field = fields.find((f) => f.name === name);
187
+ const normalizedValue = field ? normalizeFieldValue(field, rawValue) : rawValue;
188
+ set2((state) => ({
189
+ values: set(state.values, name, normalizedValue)
190
+ }));
191
+ const hasExistingError = !!getStore().errors[name];
192
+ const shouldValidateImmediately = field && ["select", "checkbox", "radio", "switch", "date"].includes(field.type);
193
+ if (shouldValidateImmediately || hasExistingError) {
194
+ set2((state) => ({
195
+ validatingFields: [...state.validatingFields, name]
196
+ }));
197
+ try {
198
+ const currentValues = getStore().values;
199
+ const error = await validateFieldByName(fields, name, normalizedValue, resolver, currentValues, errorMessages);
200
+ set2((state) => ({
201
+ errors: { ...state.errors, [name]: error || "" },
202
+ validatingFields: state.validatingFields.filter((f) => f !== name)
203
+ }));
204
+ } catch (err) {
205
+ set2((state) => ({
206
+ validatingFields: state.validatingFields.filter((f) => f !== name)
207
+ }));
208
+ }
209
+ }
176
210
  },
177
- hasError
178
- };
211
+ setFieldBlur: async (name) => {
212
+ set2((state) => ({
213
+ validatingFields: [...state.validatingFields, name]
214
+ }));
215
+ try {
216
+ const currentValues = getStore().values;
217
+ const value = get(currentValues, name);
218
+ const error = await validateFieldByName(fields, name, value, resolver, currentValues, errorMessages);
219
+ set2((state) => ({
220
+ errors: { ...state.errors, [name]: error || "" },
221
+ validatingFields: state.validatingFields.filter((f) => f !== name)
222
+ }));
223
+ } catch (err) {
224
+ set2((state) => ({
225
+ validatingFields: state.validatingFields.filter((f) => f !== name)
226
+ }));
227
+ }
228
+ },
229
+ setSubmitting: (isSubmitting) => set2({ isSubmitting }),
230
+ runSubmitValidation: async () => {
231
+ set2({ isSubmitting: true });
232
+ const state = getStore();
233
+ const errors = await validateForm(fields, state.values, resolver, errorMessages);
234
+ const hasError = Object.keys(errors).length > 0;
235
+ set2({
236
+ errors,
237
+ isSubmitting: false
238
+ });
239
+ return {
240
+ state: getStore(),
241
+ hasError
242
+ };
243
+ }
244
+ }));
179
245
  }
180
246
  // Annotate the CommonJS export names for ESM import in node:
181
247
  0 && (module.exports = {
182
- applyFieldBlur,
183
- applyFieldChange,
184
- createFormRuntimeState,
185
- runSubmitValidation,
186
- setSubmitting
248
+ createFormStore
187
249
  });
@@ -1,17 +1,21 @@
1
- import { FormField } from './types.cjs';
1
+ import * as zustand_vanilla from 'zustand/vanilla';
2
+ import { FormField, FormResolver, ErrorMessageTemplates } from './types.cjs';
2
3
 
3
4
  interface FormRuntimeState {
4
5
  values: Record<string, any>;
5
6
  errors: Record<string, string>;
7
+ validatingFields: string[];
6
8
  isSubmitting: boolean;
7
9
  }
8
- declare function createFormRuntimeState(fields: FormField[]): FormRuntimeState;
9
- declare function setSubmitting(state: FormRuntimeState, isSubmitting: boolean): FormRuntimeState;
10
- declare function applyFieldChange(fields: FormField[], state: FormRuntimeState, name: string, rawValue: unknown): FormRuntimeState;
11
- declare function applyFieldBlur(fields: FormField[], state: FormRuntimeState, name: string): FormRuntimeState;
12
- declare function runSubmitValidation(fields: FormField[], state: FormRuntimeState): {
13
- state: FormRuntimeState;
14
- hasError: boolean;
15
- };
10
+ interface FormStore extends FormRuntimeState {
11
+ setFieldValue: (name: string, rawValue: unknown) => Promise<void>;
12
+ setFieldBlur: (name: string) => Promise<void>;
13
+ setSubmitting: (isSubmitting: boolean) => void;
14
+ runSubmitValidation: () => Promise<{
15
+ state: FormRuntimeState;
16
+ hasError: boolean;
17
+ }>;
18
+ }
19
+ declare function createFormStore(fields: FormField[], resolver?: FormResolver, errorMessages?: ErrorMessageTemplates): zustand_vanilla.StoreApi<FormStore>;
16
20
 
17
- export { type FormRuntimeState, applyFieldBlur, applyFieldChange, createFormRuntimeState, runSubmitValidation, setSubmitting };
21
+ export { type FormRuntimeState, type FormStore, createFormStore };
@@ -1,17 +1,21 @@
1
- import { FormField } from './types.js';
1
+ import * as zustand_vanilla from 'zustand/vanilla';
2
+ import { FormField, FormResolver, ErrorMessageTemplates } from './types.js';
2
3
 
3
4
  interface FormRuntimeState {
4
5
  values: Record<string, any>;
5
6
  errors: Record<string, string>;
7
+ validatingFields: string[];
6
8
  isSubmitting: boolean;
7
9
  }
8
- declare function createFormRuntimeState(fields: FormField[]): FormRuntimeState;
9
- declare function setSubmitting(state: FormRuntimeState, isSubmitting: boolean): FormRuntimeState;
10
- declare function applyFieldChange(fields: FormField[], state: FormRuntimeState, name: string, rawValue: unknown): FormRuntimeState;
11
- declare function applyFieldBlur(fields: FormField[], state: FormRuntimeState, name: string): FormRuntimeState;
12
- declare function runSubmitValidation(fields: FormField[], state: FormRuntimeState): {
13
- state: FormRuntimeState;
14
- hasError: boolean;
15
- };
10
+ interface FormStore extends FormRuntimeState {
11
+ setFieldValue: (name: string, rawValue: unknown) => Promise<void>;
12
+ setFieldBlur: (name: string) => Promise<void>;
13
+ setSubmitting: (isSubmitting: boolean) => void;
14
+ runSubmitValidation: () => Promise<{
15
+ state: FormRuntimeState;
16
+ hasError: boolean;
17
+ }>;
18
+ }
19
+ declare function createFormStore(fields: FormField[], resolver?: FormResolver, errorMessages?: ErrorMessageTemplates): zustand_vanilla.StoreApi<FormStore>;
16
20
 
17
- export { type FormRuntimeState, applyFieldBlur, applyFieldChange, createFormRuntimeState, runSubmitValidation, setSubmitting };
21
+ export { type FormRuntimeState, type FormStore, createFormStore };
@@ -1,15 +1,7 @@
1
1
  import {
2
- applyFieldBlur,
3
- applyFieldChange,
4
- createFormRuntimeState,
5
- runSubmitValidation,
6
- setSubmitting
7
- } from "./chunk-TP3IHKWV.js";
8
- import "./chunk-WEDHXOHH.js";
2
+ createFormStore
3
+ } from "./chunk-J6ESJZ4U.js";
4
+ import "./chunk-B7OMM2UC.js";
9
5
  export {
10
- applyFieldBlur,
11
- applyFieldChange,
12
- createFormRuntimeState,
13
- runSubmitValidation,
14
- setSubmitting
6
+ createFormStore
15
7
  };