@axenstudio/axen-form 0.1.17 → 0.1.19
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/README.md +34 -3
- package/dist/adapters/yupAdapter.cjs +29 -10
- package/dist/adapters/yupAdapter.d.ts +1 -2
- package/dist/adapters/yupAdapter.js +29 -10
- package/dist/adapters/zodAdapter.cjs +29 -10
- package/dist/adapters/zodAdapter.d.ts +1 -2
- package/dist/adapters/zodAdapter.js +29 -10
- package/dist/core/dateUtils.d.ts +0 -1
- package/dist/core/formStore.cjs +214 -47
- package/dist/core/formStore.d.ts +5 -2
- package/dist/core/formStore.js +214 -47
- package/dist/core/numberUtils.d.ts +0 -1
- package/dist/core/pathUtils.d.ts +0 -1
- package/dist/core/payloadUtils.d.ts +0 -1
- package/dist/core/semanticValidation.cjs +82 -0
- package/dist/core/semanticValidation.d.ts +5 -0
- package/dist/core/semanticValidation.js +82 -0
- package/dist/core/validation.cjs +4 -3
- package/dist/core/validation.d.ts +0 -1
- package/dist/core/validation.js +4 -3
- package/dist/fields/AutocompleteField/AutocompleteField.d.ts +1 -2
- package/dist/fields/AutocompleteMultiField/AutocompleteMultiField.d.ts +1 -2
- package/dist/fields/AutocompletePredictField/AutocompletePredictField.d.ts +1 -2
- package/dist/fields/CheckboxField/CheckboxField.d.ts +1 -2
- package/dist/fields/ColorField/ColorField.d.ts +1 -2
- package/dist/fields/CurrencyField/CurrencyField.d.ts +1 -2
- package/dist/fields/DateField/DateField.d.ts +1 -2
- package/dist/fields/DateField/DatePicker.d.ts +1 -2
- package/dist/fields/DateTimeField/DateTimeField.d.ts +1 -2
- package/dist/fields/EmailField/EmailField.d.ts +1 -2
- package/dist/fields/NumberField/NumberField.d.ts +1 -2
- package/dist/fields/PasswordField/PasswordField.d.ts +1 -2
- package/dist/fields/PhoneField/PhoneField.d.ts +1 -2
- package/dist/fields/RadioField/RadioField.d.ts +1 -2
- package/dist/fields/SelectField/SelectField.d.ts +1 -2
- package/dist/fields/SliderField/SliderField.d.ts +1 -2
- package/dist/fields/SwitchField/SwitchField.d.ts +1 -2
- package/dist/fields/TextField/TextField.d.ts +1 -2
- package/dist/fields/TextareaField/TextareaField.d.ts +1 -2
- package/dist/fields/TimeField/TimeField.d.ts +1 -2
- package/dist/fields/TimeField/TimePicker.d.ts +1 -2
- package/dist/fields/defaultComponentMap.cjs +3 -0
- package/dist/fields/defaultComponentMap.d.ts +0 -1
- package/dist/fields/defaultComponentMap.js +3 -0
- package/dist/fields/defaultPlaceholders.d.ts +0 -1
- package/dist/fields/fieldBase.module.css.cjs +92 -70
- package/dist/fields/fieldBase.module.css.js +92 -70
- package/dist/fields/fieldUtils.d.ts +0 -1
- package/dist/fields/iconUtils.d.ts +0 -1
- package/dist/fields/icons.d.ts +3 -4
- package/dist/index.cjs +31 -22
- package/dist/index.d.ts +3 -2
- package/dist/index.js +32 -23
- package/dist/layout/Box/Box.d.ts +1 -2
- package/dist/layout/Divider/Divider.d.ts +1 -2
- package/dist/layout/Grid/Grid.d.ts +1 -2
- package/dist/layout/Spacer/Spacer.d.ts +1 -2
- package/dist/layout/Stack/Stack.d.ts +1 -2
- package/dist/layout/index.d.ts +0 -1
- package/dist/react/AxenArrayField.cjs +2 -0
- package/dist/react/AxenArrayField.d.ts +1 -2
- package/dist/react/AxenArrayField.js +2 -0
- package/dist/react/AxenField.cjs +2 -0
- package/dist/react/AxenField.d.ts +1 -2
- package/dist/react/AxenField.js +2 -0
- package/dist/react/AxenForm.cjs +29 -23
- package/dist/react/AxenForm.d.ts +0 -1
- package/dist/react/AxenForm.js +30 -24
- package/dist/react/FormContext.d.ts +0 -1
- package/dist/react/useAxenForm.cjs +17 -4
- package/dist/react/useAxenForm.d.ts +1 -2
- package/dist/react/useAxenForm.js +17 -4
- package/dist/react/useField.cjs +3 -0
- package/dist/react/useField.d.ts +0 -1
- package/dist/react/useField.js +3 -0
- package/dist/react/useFieldArray.cjs +19 -13
- package/dist/react/useFieldArray.d.ts +0 -1
- package/dist/react/useFieldArray.js +19 -13
- package/dist/react/useFormState.cjs +18 -0
- package/dist/react/useFormState.d.ts +5 -0
- package/dist/react/useFormState.js +18 -0
- package/dist/types.d.ts +28 -3
- package/package.json +5 -2
- package/dist/adapters/yupAdapter.d.ts.map +0 -1
- package/dist/adapters/zodAdapter.d.ts.map +0 -1
- package/dist/core/dateUtils.d.ts.map +0 -1
- package/dist/core/formStore.d.ts.map +0 -1
- package/dist/core/numberUtils.d.ts.map +0 -1
- package/dist/core/pathUtils.d.ts.map +0 -1
- package/dist/core/payloadUtils.d.ts.map +0 -1
- package/dist/core/validation.d.ts.map +0 -1
- package/dist/fields/AutocompleteField/AutocompleteField.d.ts.map +0 -1
- package/dist/fields/AutocompleteMultiField/AutocompleteMultiField.d.ts.map +0 -1
- package/dist/fields/AutocompletePredictField/AutocompletePredictField.d.ts.map +0 -1
- package/dist/fields/CheckboxField/CheckboxField.d.ts.map +0 -1
- package/dist/fields/ColorField/ColorField.d.ts.map +0 -1
- package/dist/fields/CurrencyField/CurrencyField.d.ts.map +0 -1
- package/dist/fields/DateField/DateField.d.ts.map +0 -1
- package/dist/fields/DateField/DatePicker.d.ts.map +0 -1
- package/dist/fields/DateTimeField/DateTimeField.d.ts.map +0 -1
- package/dist/fields/EmailField/EmailField.d.ts.map +0 -1
- package/dist/fields/NumberField/NumberField.d.ts.map +0 -1
- package/dist/fields/PasswordField/PasswordField.d.ts.map +0 -1
- package/dist/fields/PhoneField/PhoneField.d.ts.map +0 -1
- package/dist/fields/RadioField/RadioField.d.ts.map +0 -1
- package/dist/fields/SelectField/SelectField.d.ts.map +0 -1
- package/dist/fields/SliderField/SliderField.d.ts.map +0 -1
- package/dist/fields/SwitchField/SwitchField.d.ts.map +0 -1
- package/dist/fields/TextField/TextField.d.ts.map +0 -1
- package/dist/fields/TextareaField/TextareaField.d.ts.map +0 -1
- package/dist/fields/TimeField/TimeField.d.ts.map +0 -1
- package/dist/fields/TimeField/TimePicker.d.ts.map +0 -1
- package/dist/fields/defaultComponentMap.d.ts.map +0 -1
- package/dist/fields/defaultPlaceholders.d.ts.map +0 -1
- package/dist/fields/fieldUtils.d.ts.map +0 -1
- package/dist/fields/iconUtils.d.ts.map +0 -1
- package/dist/fields/icons.d.ts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/layout/Box/Box.d.ts.map +0 -1
- package/dist/layout/Divider/Divider.d.ts.map +0 -1
- package/dist/layout/Grid/Grid.d.ts.map +0 -1
- package/dist/layout/Spacer/Spacer.d.ts.map +0 -1
- package/dist/layout/Stack/Stack.d.ts.map +0 -1
- package/dist/layout/index.d.ts.map +0 -1
- package/dist/react/AxenArrayField.d.ts.map +0 -1
- package/dist/react/AxenField.d.ts.map +0 -1
- package/dist/react/AxenForm.d.ts.map +0 -1
- package/dist/react/FormContext.d.ts.map +0 -1
- package/dist/react/useAxenForm.d.ts.map +0 -1
- package/dist/react/useField.d.ts.map +0 -1
- package/dist/react/useFieldArray.d.ts.map +0 -1
- package/dist/types.d.ts.map +0 -1
package/dist/core/formStore.js
CHANGED
|
@@ -1,4 +1,15 @@
|
|
|
1
1
|
import { get, set } from "./pathUtils.js";
|
|
2
|
+
function valuesEqual(a, b) {
|
|
3
|
+
if (Object.is(a, b)) return true;
|
|
4
|
+
if (a instanceof Date || b instanceof Date) return a instanceof Date && b instanceof Date && a.getTime() === b.getTime();
|
|
5
|
+
if (a === null || b === null || typeof a !== "object" || typeof b !== "object") return false;
|
|
6
|
+
if (Array.isArray(a) !== Array.isArray(b)) return false;
|
|
7
|
+
const left = a;
|
|
8
|
+
const right = b;
|
|
9
|
+
const keys = Object.keys(left);
|
|
10
|
+
if (keys.length !== Object.keys(right).length) return false;
|
|
11
|
+
return keys.every((key) => Object.prototype.hasOwnProperty.call(right, key) && valuesEqual(left[key], right[key]));
|
|
12
|
+
}
|
|
2
13
|
function createFormStore(options) {
|
|
3
14
|
const {
|
|
4
15
|
initialValues,
|
|
@@ -10,6 +21,9 @@ function createFormStore(options) {
|
|
|
10
21
|
values: structuredClone(initialValues),
|
|
11
22
|
errors: {},
|
|
12
23
|
touched: {},
|
|
24
|
+
validationStatus: "unvalidated",
|
|
25
|
+
fieldValidationStatus: {},
|
|
26
|
+
isValidating: false,
|
|
13
27
|
dirty: false,
|
|
14
28
|
isSubmitting: false,
|
|
15
29
|
submitCount: 0,
|
|
@@ -18,6 +32,9 @@ function createFormStore(options) {
|
|
|
18
32
|
const subscribers = /* @__PURE__ */ new Set();
|
|
19
33
|
const fieldSubscribers = /* @__PURE__ */ new Map();
|
|
20
34
|
let validationSchema = options.validationSchema;
|
|
35
|
+
let valuesRevision = 0;
|
|
36
|
+
let formRequest = 0;
|
|
37
|
+
const fieldRequests = /* @__PURE__ */ new Map();
|
|
21
38
|
if (validationAdapter && options.fields && !validationSchema) {
|
|
22
39
|
validationSchema = validationAdapter.buildSchema(options.fields);
|
|
23
40
|
}
|
|
@@ -46,16 +63,53 @@ function createFormStore(options) {
|
|
|
46
63
|
var _a;
|
|
47
64
|
(_a = fieldSubscribers.get(name)) == null ? void 0 : _a.forEach((fn) => fn());
|
|
48
65
|
}
|
|
66
|
+
function notifyAllFields() {
|
|
67
|
+
for (const name of fieldSubscribers.keys()) notifyField(name);
|
|
68
|
+
notify();
|
|
69
|
+
}
|
|
70
|
+
function notifyValuePath(path) {
|
|
71
|
+
const related = (key) => key === path || key.startsWith(`${path}.`) || key.startsWith(`${path}[`) || path.startsWith(`${key}.`) || path.startsWith(`${key}[`);
|
|
72
|
+
for (const key of fieldSubscribers.keys()) if (related(key)) notifyField(key);
|
|
73
|
+
notify();
|
|
74
|
+
}
|
|
49
75
|
function setFieldValue(name, value) {
|
|
76
|
+
const nextValues = set(state.values, name, value);
|
|
77
|
+
const statuses = { ...state.fieldValidationStatus, [name]: "unvalidated" };
|
|
78
|
+
const dependents = (options.fields ?? []).filter((field) => {
|
|
79
|
+
var _a;
|
|
80
|
+
return field.name !== name && ((_a = field.rules) == null ? void 0 : _a.countryField) === name;
|
|
81
|
+
});
|
|
82
|
+
const errors = { ...state.errors };
|
|
83
|
+
for (const field of dependents) {
|
|
84
|
+
statuses[field.name] = "unvalidated";
|
|
85
|
+
delete errors[field.name];
|
|
86
|
+
}
|
|
87
|
+
valuesRevision += 1;
|
|
50
88
|
state = {
|
|
51
89
|
...state,
|
|
52
|
-
values:
|
|
53
|
-
|
|
90
|
+
values: nextValues,
|
|
91
|
+
errors,
|
|
92
|
+
dirty: !valuesEqual(nextValues, state.initialValues),
|
|
93
|
+
validationStatus: "unvalidated",
|
|
94
|
+
isValidating: false,
|
|
95
|
+
fieldValidationStatus: statuses
|
|
54
96
|
};
|
|
55
|
-
|
|
56
|
-
|
|
97
|
+
for (const field of options.fields ?? []) {
|
|
98
|
+
if (field.hidden && state.errors[field.name] && (typeof field.hidden === "function" ? field.hidden(nextValues) : field.hidden)) {
|
|
99
|
+
setFieldError(field.name, void 0);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
notifyValuePath(name);
|
|
103
|
+
for (const field of dependents) notifyField(field.name);
|
|
57
104
|
if (validateOnChange && validationAdapter && validationSchema) {
|
|
58
|
-
|
|
105
|
+
const revision = valuesRevision;
|
|
106
|
+
for (const fieldName of [name, ...dependents.map((field) => field.name)]) {
|
|
107
|
+
void runFieldValidation(fieldName).catch((error) => {
|
|
108
|
+
if (revision === valuesRevision) {
|
|
109
|
+
setFieldError(fieldName, error instanceof Error ? error.message : "Validation failed");
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
}
|
|
59
113
|
}
|
|
60
114
|
}
|
|
61
115
|
function setFieldTouched(name, touched = true) {
|
|
@@ -66,28 +120,84 @@ function createFormStore(options) {
|
|
|
66
120
|
notifyField(name);
|
|
67
121
|
notify();
|
|
68
122
|
if (validateOnBlur && touched && validationAdapter && validationSchema) {
|
|
69
|
-
|
|
123
|
+
const revision = valuesRevision;
|
|
124
|
+
void runFieldValidation(name).catch((error) => {
|
|
125
|
+
if (revision === valuesRevision) setFieldError(name, error instanceof Error ? error.message : "Validation failed");
|
|
126
|
+
});
|
|
70
127
|
}
|
|
71
128
|
}
|
|
72
129
|
function setFieldError(name, error) {
|
|
73
|
-
const
|
|
74
|
-
if (error)
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
130
|
+
const errors = { ...state.errors };
|
|
131
|
+
if (error) errors[name] = error;
|
|
132
|
+
else delete errors[name];
|
|
133
|
+
state = {
|
|
134
|
+
...state,
|
|
135
|
+
errors,
|
|
136
|
+
validationStatus: Object.keys(errors).length ? "invalid" : "unvalidated",
|
|
137
|
+
isValidating: false,
|
|
138
|
+
fieldValidationStatus: { ...state.fieldValidationStatus, [name]: error ? "invalid" : "unvalidated" }
|
|
139
|
+
};
|
|
80
140
|
notifyField(name);
|
|
81
141
|
notify();
|
|
82
142
|
}
|
|
83
143
|
function setValues(values) {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
144
|
+
valuesRevision += 1;
|
|
145
|
+
state = {
|
|
146
|
+
...state,
|
|
147
|
+
values,
|
|
148
|
+
dirty: !valuesEqual(values, state.initialValues),
|
|
149
|
+
validationStatus: "unvalidated",
|
|
150
|
+
isValidating: false,
|
|
151
|
+
fieldValidationStatus: {}
|
|
152
|
+
};
|
|
153
|
+
notifyAllFields();
|
|
87
154
|
}
|
|
88
155
|
function setErrors(errors) {
|
|
89
|
-
state = {
|
|
90
|
-
|
|
156
|
+
state = {
|
|
157
|
+
...state,
|
|
158
|
+
errors,
|
|
159
|
+
validationStatus: Object.keys(errors).length ? "invalid" : "unvalidated",
|
|
160
|
+
isValidating: false,
|
|
161
|
+
fieldValidationStatus: {}
|
|
162
|
+
};
|
|
163
|
+
notifyAllFields();
|
|
164
|
+
}
|
|
165
|
+
function setFieldArrayValue(name, items, origins) {
|
|
166
|
+
const remap = (entries) => {
|
|
167
|
+
const result = {};
|
|
168
|
+
for (const [path, value] of Object.entries(entries)) {
|
|
169
|
+
if (!path.startsWith(name)) {
|
|
170
|
+
result[path] = value;
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
const suffix = path.slice(name.length);
|
|
174
|
+
const match = /^(?:\.(\d+)|\[(\d+)\])(?=\.|\[|$)/.exec(suffix);
|
|
175
|
+
if (!match) {
|
|
176
|
+
result[path] = value;
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
const oldIndex = Number(match[1] ?? match[2]);
|
|
180
|
+
const tail = suffix.slice(match[0].length);
|
|
181
|
+
for (let i = 0; i < origins.length; i++) {
|
|
182
|
+
if (origins[i] !== oldIndex) continue;
|
|
183
|
+
result[`${name}${match[1] !== void 0 ? `.${i}` : `[${i}]`}${tail}`] = value;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return result;
|
|
187
|
+
};
|
|
188
|
+
const values = set(state.values, name, items);
|
|
189
|
+
valuesRevision += 1;
|
|
190
|
+
state = {
|
|
191
|
+
...state,
|
|
192
|
+
values,
|
|
193
|
+
dirty: !valuesEqual(values, state.initialValues),
|
|
194
|
+
errors: remap(state.errors),
|
|
195
|
+
touched: remap(state.touched),
|
|
196
|
+
validationStatus: "unvalidated",
|
|
197
|
+
isValidating: false,
|
|
198
|
+
fieldValidationStatus: remap(state.fieldValidationStatus)
|
|
199
|
+
};
|
|
200
|
+
notifyValuePath(name);
|
|
91
201
|
}
|
|
92
202
|
function resetForm(nextValues) {
|
|
93
203
|
const newValues = nextValues ? { ...state.initialValues, ...nextValues } : structuredClone(state.initialValues);
|
|
@@ -95,29 +205,94 @@ function createFormStore(options) {
|
|
|
95
205
|
values: newValues,
|
|
96
206
|
errors: {},
|
|
97
207
|
touched: {},
|
|
208
|
+
validationStatus: "unvalidated",
|
|
209
|
+
fieldValidationStatus: {},
|
|
210
|
+
isValidating: false,
|
|
98
211
|
dirty: false,
|
|
99
212
|
isSubmitting: false,
|
|
100
213
|
submitCount: state.submitCount,
|
|
101
|
-
initialValues: state.initialValues
|
|
214
|
+
initialValues: nextValues ? structuredClone(newValues) : state.initialValues
|
|
102
215
|
};
|
|
103
|
-
|
|
216
|
+
valuesRevision += 1;
|
|
217
|
+
notifyAllFields();
|
|
104
218
|
}
|
|
105
219
|
async function validateForm() {
|
|
106
220
|
if (!validationAdapter || !validationSchema) return {};
|
|
107
|
-
const
|
|
108
|
-
|
|
221
|
+
const revision = valuesRevision;
|
|
222
|
+
const request = ++formRequest;
|
|
223
|
+
state = { ...state, validationStatus: "validating", isValidating: true };
|
|
109
224
|
notify();
|
|
110
|
-
|
|
111
|
-
|
|
225
|
+
try {
|
|
226
|
+
const errors = await validationAdapter.validate(validationSchema, state.values);
|
|
227
|
+
if (revision !== valuesRevision || request !== formRequest) return state.errors;
|
|
228
|
+
const statuses = { ...state.fieldValidationStatus };
|
|
229
|
+
for (const field of options.fields ?? []) statuses[field.name] = errors[field.name] ? "invalid" : "valid";
|
|
230
|
+
state = {
|
|
231
|
+
...state,
|
|
232
|
+
errors,
|
|
233
|
+
validationStatus: Object.keys(errors).length ? "invalid" : "valid",
|
|
234
|
+
isValidating: false,
|
|
235
|
+
fieldValidationStatus: statuses
|
|
236
|
+
};
|
|
237
|
+
notify();
|
|
238
|
+
for (const name of fieldSubscribers.keys()) notifyField(name);
|
|
239
|
+
return errors;
|
|
240
|
+
} catch (error) {
|
|
241
|
+
if (revision === valuesRevision && request === formRequest) {
|
|
242
|
+
state = { ...state, validationStatus: "unvalidated", isValidating: false };
|
|
243
|
+
notify();
|
|
244
|
+
}
|
|
245
|
+
throw error;
|
|
112
246
|
}
|
|
113
|
-
return errors;
|
|
114
247
|
}
|
|
115
248
|
async function validateField(name) {
|
|
249
|
+
var _a;
|
|
116
250
|
if (!(validationAdapter == null ? void 0 : validationAdapter.validateField) || !validationSchema) return void 0;
|
|
251
|
+
const revision = valuesRevision;
|
|
252
|
+
const request = (fieldRequests.get(name) ?? 0) + 1;
|
|
253
|
+
fieldRequests.set(name, request);
|
|
254
|
+
const config = (_a = options.fields) == null ? void 0 : _a.find((field) => field.name === name);
|
|
255
|
+
const hidden = (config == null ? void 0 : config.hidden) && (typeof config.hidden === "function" ? config.hidden(state.values) : config.hidden);
|
|
256
|
+
if (hidden) {
|
|
257
|
+
setFieldError(name, void 0);
|
|
258
|
+
state = { ...state, fieldValidationStatus: { ...state.fieldValidationStatus, [name]: "valid" } };
|
|
259
|
+
notifyField(name);
|
|
260
|
+
notify();
|
|
261
|
+
return void 0;
|
|
262
|
+
}
|
|
263
|
+
state = {
|
|
264
|
+
...state,
|
|
265
|
+
fieldValidationStatus: { ...state.fieldValidationStatus, [name]: "validating" },
|
|
266
|
+
validationStatus: state.validationStatus === "validating" ? "validating" : "unvalidated"
|
|
267
|
+
};
|
|
268
|
+
notifyField(name);
|
|
269
|
+
notify();
|
|
117
270
|
const fieldValue = get(state.values, name);
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
271
|
+
try {
|
|
272
|
+
const error = await validationAdapter.validateField(validationSchema, name, fieldValue, state.values);
|
|
273
|
+
if (revision === valuesRevision && request === fieldRequests.get(name)) {
|
|
274
|
+
const errors = { ...state.errors };
|
|
275
|
+
if (error) errors[name] = error;
|
|
276
|
+
else delete errors[name];
|
|
277
|
+
state = {
|
|
278
|
+
...state,
|
|
279
|
+
errors,
|
|
280
|
+
validationStatus: state.validationStatus === "validating" ? "validating" : Object.keys(errors).length ? "invalid" : "unvalidated",
|
|
281
|
+
fieldValidationStatus: { ...state.fieldValidationStatus, [name]: error ? "invalid" : "valid" }
|
|
282
|
+
};
|
|
283
|
+
notifyField(name);
|
|
284
|
+
notify();
|
|
285
|
+
return error;
|
|
286
|
+
}
|
|
287
|
+
return state.errors[name];
|
|
288
|
+
} catch (error) {
|
|
289
|
+
if (revision === valuesRevision && request === fieldRequests.get(name)) {
|
|
290
|
+
state = { ...state, fieldValidationStatus: { ...state.fieldValidationStatus, [name]: "unvalidated" } };
|
|
291
|
+
notifyField(name);
|
|
292
|
+
notify();
|
|
293
|
+
}
|
|
294
|
+
throw error;
|
|
295
|
+
}
|
|
121
296
|
}
|
|
122
297
|
async function runFieldValidation(name) {
|
|
123
298
|
if (validationAdapter == null ? void 0 : validationAdapter.validateField) {
|
|
@@ -127,27 +302,18 @@ function createFormStore(options) {
|
|
|
127
302
|
}
|
|
128
303
|
}
|
|
129
304
|
async function submitForm(onSubmit) {
|
|
305
|
+
if (state.isSubmitting) return;
|
|
130
306
|
state = { ...state, isSubmitting: true, submitCount: state.submitCount + 1 };
|
|
131
307
|
notify();
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
}
|
|
142
|
-
const handler = onSubmit ?? options.onSubmit;
|
|
143
|
-
if (handler) {
|
|
144
|
-
try {
|
|
145
|
-
await handler(state.values);
|
|
146
|
-
} finally {
|
|
147
|
-
state = { ...state, isSubmitting: false };
|
|
148
|
-
notify();
|
|
149
|
-
}
|
|
150
|
-
} else {
|
|
308
|
+
try {
|
|
309
|
+
touchAllFields();
|
|
310
|
+
const revision = valuesRevision;
|
|
311
|
+
const errors = await validateForm();
|
|
312
|
+
for (const name of Object.keys(state.touched)) notifyField(name);
|
|
313
|
+
if (revision !== valuesRevision || Object.keys(errors).length > 0) return;
|
|
314
|
+
const handler = onSubmit ?? options.onSubmit;
|
|
315
|
+
if (handler) await handler(state.values);
|
|
316
|
+
} finally {
|
|
151
317
|
state = { ...state, isSubmitting: false };
|
|
152
318
|
notify();
|
|
153
319
|
}
|
|
@@ -186,6 +352,7 @@ function createFormStore(options) {
|
|
|
186
352
|
setFieldError,
|
|
187
353
|
setValues,
|
|
188
354
|
setErrors,
|
|
355
|
+
setFieldArrayValue,
|
|
189
356
|
resetForm,
|
|
190
357
|
validateForm,
|
|
191
358
|
validateField,
|
|
@@ -23,4 +23,3 @@ export declare function getGroupSeparator(locale?: string): string;
|
|
|
23
23
|
* then finds the same position in the formatted string.
|
|
24
24
|
*/
|
|
25
25
|
export declare function calculateCursorPosition(rawValue: string, formattedValue: string, cursorPos: number, locale?: string): number;
|
|
26
|
-
//# sourceMappingURL=numberUtils.d.ts.map
|
package/dist/core/pathUtils.d.ts
CHANGED
|
@@ -12,4 +12,3 @@ export declare function set<T extends Record<string, unknown>>(obj: T, path: str
|
|
|
12
12
|
export declare function toPathArray(path: string): string[];
|
|
13
13
|
/** Convert path segments back to dot-path string */
|
|
14
14
|
export declare function pathToString(segments: string[]): string;
|
|
15
|
-
//# sourceMappingURL=pathUtils.d.ts.map
|
|
@@ -19,4 +19,3 @@ export declare function pickFields<T extends Record<string, unknown>>(values: T,
|
|
|
19
19
|
* Useful for debugging / UI display.
|
|
20
20
|
*/
|
|
21
21
|
export declare function getStrippedFields<T extends Record<string, unknown>>(values: T, fieldMap: string[] | Record<string, string[]>, discriminatorKey?: string): string[];
|
|
22
|
-
//# sourceMappingURL=payloadUtils.d.ts.map
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const pathUtils = require("./pathUtils.cjs");
|
|
4
|
+
function isHidden(field, values) {
|
|
5
|
+
return typeof field.hidden === "function" ? Boolean(field.hidden(values)) : Boolean(field.hidden);
|
|
6
|
+
}
|
|
7
|
+
function validateSemanticValue(field, value, policy, values = {}) {
|
|
8
|
+
var _a, _b;
|
|
9
|
+
if (field.validation) return void 0;
|
|
10
|
+
const rules = field.rules;
|
|
11
|
+
const inferred = field.type === "tel" ? "phone" : field.type;
|
|
12
|
+
const format = (rules == null ? void 0 : rules.format) ?? ((policy == null ? void 0 : policy.mode) === "standard" && (inferred === "email" || inferred === "phone" || inferred === "url") ? inferred : void 0);
|
|
13
|
+
if (!format || value === void 0 || value === null || value === "") return void 0;
|
|
14
|
+
if (typeof value !== "string") return (rules == null ? void 0 : rules.message) ?? "Invalid value";
|
|
15
|
+
const input = value.trim();
|
|
16
|
+
if (!input) return void 0;
|
|
17
|
+
let valid = true;
|
|
18
|
+
if (format === "email") {
|
|
19
|
+
valid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/u.test(input) && input.length <= 254;
|
|
20
|
+
} else if (format === "url") {
|
|
21
|
+
try {
|
|
22
|
+
const parsed = new URL(input);
|
|
23
|
+
valid = (parsed.protocol === "http:" || parsed.protocol === "https:") && Boolean(parsed.hostname);
|
|
24
|
+
} catch {
|
|
25
|
+
valid = false;
|
|
26
|
+
}
|
|
27
|
+
} else if (format === "phone") {
|
|
28
|
+
const clean = input.replace(/[().\s-]/g, "");
|
|
29
|
+
const region = ((rules == null ? void 0 : rules.region) ?? "").toUpperCase();
|
|
30
|
+
valid = region === "ID" ? /^(?:\+62|62|0)8\d{7,11}$/u.test(clean) : /^\+[1-9]\d{7,14}$/u.test(clean);
|
|
31
|
+
} else if (format === "postalCode") {
|
|
32
|
+
const resolved = ((_a = rules == null ? void 0 : rules.countryResolver) == null ? void 0 : _a.call(rules, values)) ?? ((rules == null ? void 0 : rules.countryField) ? pathUtils.get(values, rules.countryField) : void 0) ?? (rules == null ? void 0 : rules.country);
|
|
33
|
+
const country = typeof resolved === "string" ? resolved.trim().toUpperCase() : "";
|
|
34
|
+
if (!country && ((rules == null ? void 0 : rules.countryField) || (rules == null ? void 0 : rules.countryResolver))) return void 0;
|
|
35
|
+
const patterns = {
|
|
36
|
+
ID: /^\d{5}$/u,
|
|
37
|
+
US: /^\d{5}(?:-\d{4})?$/u,
|
|
38
|
+
SG: /^\d{6}$/u,
|
|
39
|
+
JP: /^\d{3}-?\d{4}$/u,
|
|
40
|
+
GB: /^(?:GIR\s?0AA|[A-Z]{1,2}\d[A-Z\d]?\s?\d[A-Z]{2})$/iu,
|
|
41
|
+
UK: /^(?:GIR\s?0AA|[A-Z]{1,2}\d[A-Z\d]?\s?\d[A-Z]{2})$/iu
|
|
42
|
+
};
|
|
43
|
+
const validator = ((_b = policy == null ? void 0 : policy.postalCodePatterns) == null ? void 0 : _b[country]) ?? patterns[country];
|
|
44
|
+
if (!validator) valid = (policy == null ? void 0 : policy.unknownCountry) === "ignore";
|
|
45
|
+
else if (validator instanceof RegExp) {
|
|
46
|
+
validator.lastIndex = 0;
|
|
47
|
+
valid = validator.test(input);
|
|
48
|
+
} else valid = Boolean(validator(input, values));
|
|
49
|
+
}
|
|
50
|
+
return valid ? void 0 : (rules == null ? void 0 : rules.message) ?? `Invalid ${format}`;
|
|
51
|
+
}
|
|
52
|
+
function withValidationPolicy(adapter, policy) {
|
|
53
|
+
return {
|
|
54
|
+
buildSchema(fields) {
|
|
55
|
+
return { base: adapter.buildSchema(fields), fields };
|
|
56
|
+
},
|
|
57
|
+
async validate(schema, values) {
|
|
58
|
+
const { base, fields } = schema;
|
|
59
|
+
const errors = { ...await adapter.validate(base, values) };
|
|
60
|
+
for (const field of fields) {
|
|
61
|
+
if (isHidden(field, values)) {
|
|
62
|
+
delete errors[field.name];
|
|
63
|
+
} else if (!errors[field.name]) {
|
|
64
|
+
const error = validateSemanticValue(field, pathUtils.get(values, field.name), policy, values);
|
|
65
|
+
if (error) errors[field.name] = error;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return errors;
|
|
69
|
+
},
|
|
70
|
+
async validateField(schema, name, value, values) {
|
|
71
|
+
var _a;
|
|
72
|
+
const { base, fields } = schema;
|
|
73
|
+
const context = values ?? { [name]: value };
|
|
74
|
+
const field = fields.find((f) => f.name === name) ?? { name, type: "text" };
|
|
75
|
+
if (isHidden(field, context)) return void 0;
|
|
76
|
+
const error = await ((_a = adapter.validateField) == null ? void 0 : _a.call(adapter, base, name, value, context));
|
|
77
|
+
return error ?? validateSemanticValue(field, value, policy, context);
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
exports.validateSemanticValue = validateSemanticValue;
|
|
82
|
+
exports.withValidationPolicy = withValidationPolicy;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { FieldConfig, ValidationAdapter, ValidationPolicy } from '../types';
|
|
2
|
+
/** Optional semantics: custom Yup/Zod field schemas retain precedence. */
|
|
3
|
+
export declare function validateSemanticValue(field: FieldConfig, value: unknown, policy?: ValidationPolicy, values?: Record<string, unknown>): string | undefined;
|
|
4
|
+
/** Adapter-independent policy wrapper. No mandatory Yup, Zod, or UI dependencies. */
|
|
5
|
+
export declare function withValidationPolicy(adapter: ValidationAdapter, policy?: ValidationPolicy): ValidationAdapter;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { get } from "./pathUtils.js";
|
|
2
|
+
function isHidden(field, values) {
|
|
3
|
+
return typeof field.hidden === "function" ? Boolean(field.hidden(values)) : Boolean(field.hidden);
|
|
4
|
+
}
|
|
5
|
+
function validateSemanticValue(field, value, policy, values = {}) {
|
|
6
|
+
var _a, _b;
|
|
7
|
+
if (field.validation) return void 0;
|
|
8
|
+
const rules = field.rules;
|
|
9
|
+
const inferred = field.type === "tel" ? "phone" : field.type;
|
|
10
|
+
const format = (rules == null ? void 0 : rules.format) ?? ((policy == null ? void 0 : policy.mode) === "standard" && (inferred === "email" || inferred === "phone" || inferred === "url") ? inferred : void 0);
|
|
11
|
+
if (!format || value === void 0 || value === null || value === "") return void 0;
|
|
12
|
+
if (typeof value !== "string") return (rules == null ? void 0 : rules.message) ?? "Invalid value";
|
|
13
|
+
const input = value.trim();
|
|
14
|
+
if (!input) return void 0;
|
|
15
|
+
let valid = true;
|
|
16
|
+
if (format === "email") {
|
|
17
|
+
valid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/u.test(input) && input.length <= 254;
|
|
18
|
+
} else if (format === "url") {
|
|
19
|
+
try {
|
|
20
|
+
const parsed = new URL(input);
|
|
21
|
+
valid = (parsed.protocol === "http:" || parsed.protocol === "https:") && Boolean(parsed.hostname);
|
|
22
|
+
} catch {
|
|
23
|
+
valid = false;
|
|
24
|
+
}
|
|
25
|
+
} else if (format === "phone") {
|
|
26
|
+
const clean = input.replace(/[().\s-]/g, "");
|
|
27
|
+
const region = ((rules == null ? void 0 : rules.region) ?? "").toUpperCase();
|
|
28
|
+
valid = region === "ID" ? /^(?:\+62|62|0)8\d{7,11}$/u.test(clean) : /^\+[1-9]\d{7,14}$/u.test(clean);
|
|
29
|
+
} else if (format === "postalCode") {
|
|
30
|
+
const resolved = ((_a = rules == null ? void 0 : rules.countryResolver) == null ? void 0 : _a.call(rules, values)) ?? ((rules == null ? void 0 : rules.countryField) ? get(values, rules.countryField) : void 0) ?? (rules == null ? void 0 : rules.country);
|
|
31
|
+
const country = typeof resolved === "string" ? resolved.trim().toUpperCase() : "";
|
|
32
|
+
if (!country && ((rules == null ? void 0 : rules.countryField) || (rules == null ? void 0 : rules.countryResolver))) return void 0;
|
|
33
|
+
const patterns = {
|
|
34
|
+
ID: /^\d{5}$/u,
|
|
35
|
+
US: /^\d{5}(?:-\d{4})?$/u,
|
|
36
|
+
SG: /^\d{6}$/u,
|
|
37
|
+
JP: /^\d{3}-?\d{4}$/u,
|
|
38
|
+
GB: /^(?:GIR\s?0AA|[A-Z]{1,2}\d[A-Z\d]?\s?\d[A-Z]{2})$/iu,
|
|
39
|
+
UK: /^(?:GIR\s?0AA|[A-Z]{1,2}\d[A-Z\d]?\s?\d[A-Z]{2})$/iu
|
|
40
|
+
};
|
|
41
|
+
const validator = ((_b = policy == null ? void 0 : policy.postalCodePatterns) == null ? void 0 : _b[country]) ?? patterns[country];
|
|
42
|
+
if (!validator) valid = (policy == null ? void 0 : policy.unknownCountry) === "ignore";
|
|
43
|
+
else if (validator instanceof RegExp) {
|
|
44
|
+
validator.lastIndex = 0;
|
|
45
|
+
valid = validator.test(input);
|
|
46
|
+
} else valid = Boolean(validator(input, values));
|
|
47
|
+
}
|
|
48
|
+
return valid ? void 0 : (rules == null ? void 0 : rules.message) ?? `Invalid ${format}`;
|
|
49
|
+
}
|
|
50
|
+
function withValidationPolicy(adapter, policy) {
|
|
51
|
+
return {
|
|
52
|
+
buildSchema(fields) {
|
|
53
|
+
return { base: adapter.buildSchema(fields), fields };
|
|
54
|
+
},
|
|
55
|
+
async validate(schema, values) {
|
|
56
|
+
const { base, fields } = schema;
|
|
57
|
+
const errors = { ...await adapter.validate(base, values) };
|
|
58
|
+
for (const field of fields) {
|
|
59
|
+
if (isHidden(field, values)) {
|
|
60
|
+
delete errors[field.name];
|
|
61
|
+
} else if (!errors[field.name]) {
|
|
62
|
+
const error = validateSemanticValue(field, get(values, field.name), policy, values);
|
|
63
|
+
if (error) errors[field.name] = error;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return errors;
|
|
67
|
+
},
|
|
68
|
+
async validateField(schema, name, value, values) {
|
|
69
|
+
var _a;
|
|
70
|
+
const { base, fields } = schema;
|
|
71
|
+
const context = values ?? { [name]: value };
|
|
72
|
+
const field = fields.find((f) => f.name === name) ?? { name, type: "text" };
|
|
73
|
+
if (isHidden(field, context)) return void 0;
|
|
74
|
+
const error = await ((_a = adapter.validateField) == null ? void 0 : _a.call(adapter, base, name, value, context));
|
|
75
|
+
return error ?? validateSemanticValue(field, value, policy, context);
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
export {
|
|
80
|
+
validateSemanticValue,
|
|
81
|
+
withValidationPolicy
|
|
82
|
+
};
|
package/dist/core/validation.cjs
CHANGED
|
@@ -16,7 +16,7 @@ const simpleValidator = {
|
|
|
16
16
|
const isHidden = typeof rule.hidden === "function" ? rule.hidden(values) : rule.hidden;
|
|
17
17
|
if (isHidden) continue;
|
|
18
18
|
const value = getNestedValue(values, rule.name);
|
|
19
|
-
if (isEmpty(value)) {
|
|
19
|
+
if (isEmpty(value, rule.type)) {
|
|
20
20
|
errors[rule.name] = rule.message;
|
|
21
21
|
}
|
|
22
22
|
}
|
|
@@ -26,10 +26,11 @@ const simpleValidator = {
|
|
|
26
26
|
const rules = schema;
|
|
27
27
|
const rule = rules.find((r) => r.name === name);
|
|
28
28
|
if (!rule) return void 0;
|
|
29
|
-
return isEmpty(value) ? rule.message : void 0;
|
|
29
|
+
return isEmpty(value, rule.type) ? rule.message : void 0;
|
|
30
30
|
}
|
|
31
31
|
};
|
|
32
|
-
function isEmpty(value) {
|
|
32
|
+
function isEmpty(value, type) {
|
|
33
|
+
if (type === "checkbox" || type === "switch") return value !== true;
|
|
33
34
|
if (value == null) return true;
|
|
34
35
|
if (typeof value === "string") return value.trim() === "";
|
|
35
36
|
if (Array.isArray(value)) return value.length === 0;
|
package/dist/core/validation.js
CHANGED
|
@@ -14,7 +14,7 @@ const simpleValidator = {
|
|
|
14
14
|
const isHidden = typeof rule.hidden === "function" ? rule.hidden(values) : rule.hidden;
|
|
15
15
|
if (isHidden) continue;
|
|
16
16
|
const value = getNestedValue(values, rule.name);
|
|
17
|
-
if (isEmpty(value)) {
|
|
17
|
+
if (isEmpty(value, rule.type)) {
|
|
18
18
|
errors[rule.name] = rule.message;
|
|
19
19
|
}
|
|
20
20
|
}
|
|
@@ -24,10 +24,11 @@ const simpleValidator = {
|
|
|
24
24
|
const rules = schema;
|
|
25
25
|
const rule = rules.find((r) => r.name === name);
|
|
26
26
|
if (!rule) return void 0;
|
|
27
|
-
return isEmpty(value) ? rule.message : void 0;
|
|
27
|
+
return isEmpty(value, rule.type) ? rule.message : void 0;
|
|
28
28
|
}
|
|
29
29
|
};
|
|
30
|
-
function isEmpty(value) {
|
|
30
|
+
function isEmpty(value, type) {
|
|
31
|
+
if (type === "checkbox" || type === "switch") return value !== true;
|
|
31
32
|
if (value == null) return true;
|
|
32
33
|
if (typeof value === "string") return value.trim() === "";
|
|
33
34
|
if (Array.isArray(value)) return value.length === 0;
|
|
@@ -2,5 +2,4 @@ import { AutocompleteOption, FieldComponentProps } from '../../types';
|
|
|
2
2
|
export declare function AutocompleteField({ name, value, onChange: _onChange, onBlur, error, helperText, label, placeholder, disabled, readOnly, required, options: staticOptions, fetchOptions, optionLabel, icon, form, }: FieldComponentProps & {
|
|
3
3
|
fetchOptions?: (query: string, signal?: AbortSignal) => Promise<AutocompleteOption[]>;
|
|
4
4
|
optionLabel?: string;
|
|
5
|
-
}): import("react
|
|
6
|
-
//# sourceMappingURL=AutocompleteField.d.ts.map
|
|
5
|
+
}): import("react").JSX.Element;
|
|
@@ -2,5 +2,4 @@ import { AutocompleteOption, FieldComponentProps } from '../../types';
|
|
|
2
2
|
export declare function AutocompleteMultiField({ name, value, onChange: _onChange, onBlur, error, helperText, label, placeholder, disabled, readOnly, required, options: staticOptions, fetchOptions, optionLabel, icon, form, }: FieldComponentProps & {
|
|
3
3
|
fetchOptions?: (query: string, signal?: AbortSignal) => Promise<AutocompleteOption[]>;
|
|
4
4
|
optionLabel?: string;
|
|
5
|
-
}): import("react
|
|
6
|
-
//# sourceMappingURL=AutocompleteMultiField.d.ts.map
|
|
5
|
+
}): import("react").JSX.Element;
|
|
@@ -6,5 +6,4 @@ import { AutocompleteOption, FieldComponentProps } from '../../types';
|
|
|
6
6
|
export declare function AutocompletePredictField({ name, value, onChange: _onChange, onBlur, error, helperText, label, placeholder, disabled, readOnly, required, options: staticOptions, fetchOptions, optionLabel, icon, form, }: FieldComponentProps & {
|
|
7
7
|
fetchOptions?: (query: string, signal?: AbortSignal) => Promise<AutocompleteOption[]>;
|
|
8
8
|
optionLabel?: string;
|
|
9
|
-
}): import("react
|
|
10
|
-
//# sourceMappingURL=AutocompletePredictField.d.ts.map
|
|
9
|
+
}): import("react").JSX.Element;
|
|
@@ -1,3 +1,2 @@
|
|
|
1
1
|
import { FieldComponentProps } from '../../types';
|
|
2
|
-
export declare function CheckboxField({ name, value, onChange: _onChange, onBlur, error, helperText, label, disabled, readOnly, required, form, }: Readonly<FieldComponentProps>): import("react
|
|
3
|
-
//# sourceMappingURL=CheckboxField.d.ts.map
|
|
2
|
+
export declare function CheckboxField({ name, value, onChange: _onChange, onBlur, error, helperText, label, disabled, readOnly, required, form, }: Readonly<FieldComponentProps>): import("react").JSX.Element;
|
|
@@ -1,3 +1,2 @@
|
|
|
1
1
|
import { FieldComponentProps } from '../../types';
|
|
2
|
-
export declare function ColorField({ name, value, onChange: _onChange, onBlur, error, helperText, label, disabled, readOnly, required, form, }: Readonly<FieldComponentProps>): import("react
|
|
3
|
-
//# sourceMappingURL=ColorField.d.ts.map
|
|
2
|
+
export declare function ColorField({ name, value, onChange: _onChange, onBlur, error, helperText, label, disabled, readOnly, required, form, }: Readonly<FieldComponentProps>): import("react").JSX.Element;
|
|
@@ -6,6 +6,5 @@ interface DatePickerProps {
|
|
|
6
6
|
onApply: (isoDate: string) => void;
|
|
7
7
|
onClose: () => void;
|
|
8
8
|
}
|
|
9
|
-
export declare function DatePicker({ value, min, max, pickerId, onApply, onClose, }: Readonly<DatePickerProps>): import("react
|
|
9
|
+
export declare function DatePicker({ value, min, max, pickerId, onApply, onClose, }: Readonly<DatePickerProps>): import("react").JSX.Element;
|
|
10
10
|
export {};
|
|
11
|
-
//# sourceMappingURL=DatePicker.d.ts.map
|
|
@@ -5,5 +5,4 @@ export declare function DateTimeField({ name, value, onChange, onBlur, error, he
|
|
|
5
5
|
locale?: string;
|
|
6
6
|
ampm?: boolean;
|
|
7
7
|
showSeconds?: boolean;
|
|
8
|
-
}>): import("react
|
|
9
|
-
//# sourceMappingURL=DateTimeField.d.ts.map
|
|
8
|
+
}>): import("react").JSX.Element;
|
|
@@ -1,3 +1,2 @@
|
|
|
1
1
|
import { FieldComponentProps } from '../../types';
|
|
2
|
-
export declare function EmailField({ name, value, onChange, onBlur, error, helperText, label, placeholder, disabled, readOnly, required, icon, }: Readonly<FieldComponentProps>): import("react
|
|
3
|
-
//# sourceMappingURL=EmailField.d.ts.map
|
|
2
|
+
export declare function EmailField({ name, value, onChange, onBlur, error, helperText, label, placeholder, disabled, readOnly, required, icon, }: Readonly<FieldComponentProps>): import("react").JSX.Element;
|