@formisch/preact 0.10.0 → 0.12.0
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 +2 -1
- package/dist/index.d.ts +484 -157
- package/dist/index.js +220 -163
- package/package.json +4 -3
package/dist/index.js
CHANGED
|
@@ -37,11 +37,13 @@ function initializeFieldStore(internalFieldStore, schema, initialInput, path, nu
|
|
|
37
37
|
else {
|
|
38
38
|
internalFieldStore.schema = schema;
|
|
39
39
|
internalFieldStore.name = JSON.stringify(path);
|
|
40
|
+
internalFieldStore.path = path;
|
|
40
41
|
const initialElements = [];
|
|
41
42
|
internalFieldStore.initialElements = initialElements;
|
|
42
43
|
internalFieldStore.elements = initialElements;
|
|
43
44
|
internalFieldStore.errors = createSignal(null);
|
|
44
45
|
internalFieldStore.isTouched = createSignal(false);
|
|
46
|
+
internalFieldStore.isEdited = createSignal(false);
|
|
45
47
|
internalFieldStore.isDirty = createSignal(false);
|
|
46
48
|
if (schema.type === "array" || schema.type === "loose_tuple" || schema.type === "strict_tuple" || schema.type === "tuple") {
|
|
47
49
|
if (internalFieldStore.kind && internalFieldStore.kind !== "array") throw new Error(`Store initialized as "${internalFieldStore.kind}" cannot be reinitialized as "array"`);
|
|
@@ -51,16 +53,13 @@ function initializeFieldStore(internalFieldStore, schema, initialInput, path, nu
|
|
|
51
53
|
if (schema.type === "array") {
|
|
52
54
|
if (initialInput) for (let index = 0; index < initialInput.length; index++) {
|
|
53
55
|
internalFieldStore.children[index] = {};
|
|
54
|
-
|
|
55
|
-
initializeFieldStore(internalFieldStore.children[index], schema.item, initialInput[index], path);
|
|
56
|
-
path.pop();
|
|
56
|
+
initializeFieldStore(internalFieldStore.children[index], schema.item, initialInput[index], [...path, index]);
|
|
57
57
|
}
|
|
58
58
|
} else for (let index = 0; index < schema.items.length; index++) {
|
|
59
59
|
internalFieldStore.children[index] = {};
|
|
60
|
-
|
|
61
|
-
initializeFieldStore(internalFieldStore.children[index], schema.items[index], initialInput?.[index], path);
|
|
62
|
-
path.pop();
|
|
60
|
+
initializeFieldStore(internalFieldStore.children[index], schema.items[index], initialInput?.[index], [...path, index]);
|
|
63
61
|
}
|
|
62
|
+
internalFieldStore.isNullish = nullish;
|
|
64
63
|
const arrayInput = nullish && initialInput == null ? initialInput : true;
|
|
65
64
|
internalFieldStore.initialInput = createSignal(arrayInput);
|
|
66
65
|
internalFieldStore.startInput = createSignal(arrayInput);
|
|
@@ -77,10 +76,9 @@ function initializeFieldStore(internalFieldStore, schema, initialInput, path, nu
|
|
|
77
76
|
internalFieldStore.children ??= {};
|
|
78
77
|
for (const key in schema.entries) {
|
|
79
78
|
internalFieldStore.children[key] ??= {};
|
|
80
|
-
|
|
81
|
-
initializeFieldStore(internalFieldStore.children[key], schema.entries[key], initialInput?.[key], path);
|
|
82
|
-
path.pop();
|
|
79
|
+
initializeFieldStore(internalFieldStore.children[key], schema.entries[key], initialInput?.[key], [...path, key]);
|
|
83
80
|
}
|
|
81
|
+
internalFieldStore.isNullish = nullish;
|
|
84
82
|
const objectInput = nullish && initialInput == null ? initialInput : true;
|
|
85
83
|
internalFieldStore.initialInput = createSignal(objectInput);
|
|
86
84
|
internalFieldStore.startInput = createSignal(objectInput);
|
|
@@ -100,8 +98,9 @@ function initializeFieldStore(internalFieldStore, schema, initialInput, path, nu
|
|
|
100
98
|
/**
|
|
101
99
|
* Copies the deeply nested state (signal values) from one field store to
|
|
102
100
|
* another. This includes the `elements`, `errors`, `startInput`, `input`,
|
|
103
|
-
* `isTouched`, `isDirty`, and for arrays `startItems` and `items`
|
|
104
|
-
* Recursively walks through the field stores and copies all signal
|
|
101
|
+
* `isTouched`, `isEdited`, `isDirty`, and for arrays `startItems` and `items`
|
|
102
|
+
* properties. Recursively walks through the field stores and copies all signal
|
|
103
|
+
* values.
|
|
105
104
|
*
|
|
106
105
|
* @param fromInternalFieldStore The source field store to copy from.
|
|
107
106
|
* @param toInternalFieldStore The destination field store to copy to.
|
|
@@ -114,19 +113,16 @@ function copyItemState(fromInternalFieldStore, toInternalFieldStore) {
|
|
|
114
113
|
toInternalFieldStore.startInput.value = fromInternalFieldStore.startInput.value;
|
|
115
114
|
toInternalFieldStore.input.value = fromInternalFieldStore.input.value;
|
|
116
115
|
toInternalFieldStore.isTouched.value = fromInternalFieldStore.isTouched.value;
|
|
116
|
+
toInternalFieldStore.isEdited.value = fromInternalFieldStore.isEdited.value;
|
|
117
117
|
toInternalFieldStore.isDirty.value = fromInternalFieldStore.isDirty.value;
|
|
118
118
|
if (fromInternalFieldStore.kind === "array" && toInternalFieldStore.kind === "array") {
|
|
119
119
|
const fromItems = fromInternalFieldStore.items.value;
|
|
120
120
|
toInternalFieldStore.startItems.value = fromInternalFieldStore.startItems.value;
|
|
121
121
|
toInternalFieldStore.items.value = fromItems;
|
|
122
|
-
let path;
|
|
123
122
|
for (let index = 0; index < fromItems.length; index++) {
|
|
124
123
|
if (!toInternalFieldStore.children[index]) {
|
|
125
|
-
path ??= JSON.parse(toInternalFieldStore.name);
|
|
126
124
|
toInternalFieldStore.children[index] = {};
|
|
127
|
-
|
|
128
|
-
initializeFieldStore(toInternalFieldStore.children[index], toInternalFieldStore.schema.item, void 0, path);
|
|
129
|
-
path.pop();
|
|
125
|
+
initializeFieldStore(toInternalFieldStore.children[index], toInternalFieldStore.schema.item, void 0, [...toInternalFieldStore.path, index]);
|
|
130
126
|
}
|
|
131
127
|
copyItemState(fromInternalFieldStore.children[index], toInternalFieldStore.children[index]);
|
|
132
128
|
}
|
|
@@ -136,45 +132,61 @@ function copyItemState(fromInternalFieldStore, toInternalFieldStore) {
|
|
|
136
132
|
}
|
|
137
133
|
/**
|
|
138
134
|
* Resets the state of a field store (signal values) deeply nested. Sets
|
|
139
|
-
* `elements` to empty array, `errors` to `null`, `isTouched
|
|
140
|
-
* `false`, and `startInput`, `input`, `startItems`, and `items` to
|
|
141
|
-
* input value. Keeps the `initialInput` and `initialItems` state
|
|
142
|
-
* form reset functionality.
|
|
135
|
+
* `elements` to empty array, `errors` to `null`, `isTouched`, `isEdited` and
|
|
136
|
+
* `isDirty` to `false`, and `startInput`, `input`, `startItems`, and `items` to
|
|
137
|
+
* the new input value. Keeps the `initialInput` and `initialItems` state
|
|
138
|
+
* unchanged for form reset functionality.
|
|
143
139
|
*
|
|
144
140
|
* @param internalFieldStore The field store to reset.
|
|
145
|
-
* @param
|
|
141
|
+
* @param input The new input value (can be any type including array or object).
|
|
142
|
+
* @param keepStart Whether to keep `startInput` and `startItems` as the dirty
|
|
143
|
+
* baseline instead of resetting them to the new input. Used when a field store
|
|
144
|
+
* is reused for an in-place edit so its dirty state is detected correctly.
|
|
146
145
|
*/
|
|
147
|
-
function resetItemState(internalFieldStore,
|
|
146
|
+
function resetItemState(internalFieldStore, input, keepStart = false) {
|
|
148
147
|
batch(() => {
|
|
149
|
-
|
|
148
|
+
const elements = [];
|
|
149
|
+
if (internalFieldStore.elements === internalFieldStore.initialElements) internalFieldStore.initialElements = elements;
|
|
150
|
+
internalFieldStore.elements = elements;
|
|
150
151
|
internalFieldStore.errors.value = null;
|
|
151
152
|
internalFieldStore.isTouched.value = false;
|
|
153
|
+
internalFieldStore.isEdited.value = false;
|
|
152
154
|
internalFieldStore.isDirty.value = false;
|
|
153
155
|
if (internalFieldStore.kind === "array" || internalFieldStore.kind === "object") {
|
|
154
|
-
const objectInput =
|
|
155
|
-
internalFieldStore.startInput.value = objectInput;
|
|
156
|
+
const objectInput = internalFieldStore.isNullish && input == null ? input : true;
|
|
157
|
+
if (!keepStart) internalFieldStore.startInput.value = objectInput;
|
|
156
158
|
internalFieldStore.input.value = objectInput;
|
|
157
|
-
if (internalFieldStore.kind === "array")
|
|
158
|
-
const
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
159
|
+
if (internalFieldStore.kind === "array") {
|
|
160
|
+
const isTuple = internalFieldStore.schema.type !== "array";
|
|
161
|
+
if (input || isTuple) {
|
|
162
|
+
const length = isTuple ? internalFieldStore.children.length : input.length;
|
|
163
|
+
const newItems = Array.from({ length }, createId);
|
|
164
|
+
if (!keepStart) internalFieldStore.startItems.value = newItems;
|
|
165
|
+
internalFieldStore.items.value = newItems;
|
|
166
|
+
for (let index = 0; index < length; index++) {
|
|
167
|
+
const itemInput = input?.[index];
|
|
168
|
+
if (internalFieldStore.children[index]) resetItemState(internalFieldStore.children[index], itemInput, keepStart);
|
|
169
|
+
else {
|
|
170
|
+
internalFieldStore.children[index] = {};
|
|
171
|
+
initializeFieldStore(internalFieldStore.children[index], internalFieldStore.schema.item, itemInput, [...internalFieldStore.path, index]);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
} else {
|
|
175
|
+
if (!keepStart) internalFieldStore.startItems.value = [];
|
|
176
|
+
internalFieldStore.items.value = [];
|
|
177
|
+
}
|
|
178
|
+
} else for (const key in internalFieldStore.children) resetItemState(internalFieldStore.children[key], input?.[key], keepStart);
|
|
167
179
|
} else {
|
|
168
|
-
internalFieldStore.startInput.value =
|
|
169
|
-
internalFieldStore.input.value =
|
|
180
|
+
if (!keepStart) internalFieldStore.startInput.value = input;
|
|
181
|
+
internalFieldStore.input.value = input;
|
|
170
182
|
}
|
|
171
183
|
});
|
|
172
184
|
}
|
|
173
185
|
/**
|
|
174
186
|
* Swaps the deeply nested state (signal values) between two field stores. This
|
|
175
187
|
* includes the `elements`, `errors`, `startInput`, `input`, `isTouched`,
|
|
176
|
-
* `isDirty`, and for arrays `startItems` and `items` properties.
|
|
177
|
-
* walks through the field stores and swaps all signal values.
|
|
188
|
+
* `isEdited`, `isDirty`, and for arrays `startItems` and `items` properties.
|
|
189
|
+
* Recursively walks through the field stores and swaps all signal values.
|
|
178
190
|
*
|
|
179
191
|
* @param firstInternalFieldStore The first field store to swap.
|
|
180
192
|
* @param secondInternalFieldStore The second field store to swap.
|
|
@@ -197,6 +209,9 @@ function swapItemState(firstInternalFieldStore, secondInternalFieldStore) {
|
|
|
197
209
|
const tempIsTouched = firstInternalFieldStore.isTouched.value;
|
|
198
210
|
firstInternalFieldStore.isTouched.value = secondInternalFieldStore.isTouched.value;
|
|
199
211
|
secondInternalFieldStore.isTouched.value = tempIsTouched;
|
|
212
|
+
const tempIsEdited = firstInternalFieldStore.isEdited.value;
|
|
213
|
+
firstInternalFieldStore.isEdited.value = secondInternalFieldStore.isEdited.value;
|
|
214
|
+
secondInternalFieldStore.isEdited.value = tempIsEdited;
|
|
200
215
|
const tempIsDirty = firstInternalFieldStore.isDirty.value;
|
|
201
216
|
firstInternalFieldStore.isDirty.value = secondInternalFieldStore.isDirty.value;
|
|
202
217
|
secondInternalFieldStore.isDirty.value = tempIsDirty;
|
|
@@ -209,22 +224,14 @@ function swapItemState(firstInternalFieldStore, secondInternalFieldStore) {
|
|
|
209
224
|
firstInternalFieldStore.items.value = secondItems;
|
|
210
225
|
secondInternalFieldStore.items.value = firstItems;
|
|
211
226
|
const maxLength = Math.max(firstItems.length, secondItems.length);
|
|
212
|
-
let firstPath;
|
|
213
|
-
let secondPath;
|
|
214
227
|
for (let index = 0; index < maxLength; index++) {
|
|
215
228
|
if (!firstInternalFieldStore.children[index]) {
|
|
216
|
-
firstPath ??= JSON.parse(firstInternalFieldStore.name);
|
|
217
229
|
firstInternalFieldStore.children[index] = {};
|
|
218
|
-
|
|
219
|
-
initializeFieldStore(firstInternalFieldStore.children[index], firstInternalFieldStore.schema.item, void 0, firstPath);
|
|
220
|
-
firstPath.pop();
|
|
230
|
+
initializeFieldStore(firstInternalFieldStore.children[index], firstInternalFieldStore.schema.item, void 0, [...firstInternalFieldStore.path, index]);
|
|
221
231
|
}
|
|
222
232
|
if (!secondInternalFieldStore.children[index]) {
|
|
223
|
-
secondPath ??= JSON.parse(secondInternalFieldStore.name);
|
|
224
233
|
secondInternalFieldStore.children[index] = {};
|
|
225
|
-
|
|
226
|
-
initializeFieldStore(secondInternalFieldStore.children[index], secondInternalFieldStore.schema.item, void 0, secondPath);
|
|
227
|
-
secondPath.pop();
|
|
234
|
+
initializeFieldStore(secondInternalFieldStore.children[index], secondInternalFieldStore.schema.item, void 0, [...secondInternalFieldStore.path, index]);
|
|
228
235
|
}
|
|
229
236
|
swapItemState(firstInternalFieldStore.children[index], secondInternalFieldStore.children[index]);
|
|
230
237
|
}
|
|
@@ -233,6 +240,51 @@ function swapItemState(firstInternalFieldStore, secondInternalFieldStore) {
|
|
|
233
240
|
});
|
|
234
241
|
}
|
|
235
242
|
/**
|
|
243
|
+
* Focuses the first focusable element of a field store. The elements are tried
|
|
244
|
+
* in order and the first one that actually receives focus wins, so detached,
|
|
245
|
+
* disabled or hidden elements are skipped. The browser decides focusability,
|
|
246
|
+
* which is read back via the element's root `activeElement` so elements in a
|
|
247
|
+
* shadow root or another document are handled correctly.
|
|
248
|
+
*
|
|
249
|
+
* Hint: A `display: none` or `hidden` element is correctly skipped in real
|
|
250
|
+
* browsers, but jsdom has no layout and focuses it anyway, so that case cannot
|
|
251
|
+
* be covered by unit tests.
|
|
252
|
+
*
|
|
253
|
+
* @param internalFieldStore The field store to focus.
|
|
254
|
+
*
|
|
255
|
+
* @returns Whether an element was focused.
|
|
256
|
+
*/
|
|
257
|
+
function focusFieldElement(internalFieldStore) {
|
|
258
|
+
for (const element of internalFieldStore.elements) {
|
|
259
|
+
element.focus();
|
|
260
|
+
if (element.getRootNode().activeElement === element) return true;
|
|
261
|
+
}
|
|
262
|
+
return false;
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Walks through the field store and all nested children, calling the callback
|
|
266
|
+
* for each field store in depth-first order. The callback may return `true` to
|
|
267
|
+
* stop the walk early, in which case `walkFieldStore` returns `true` as well.
|
|
268
|
+
*
|
|
269
|
+
* The walk reads array `items` reactively, so a reactive caller subscribes to
|
|
270
|
+
* structural changes naturally. Imperative callers that must not subscribe
|
|
271
|
+
* (e.g. when invoked inside an effect) should wrap the call in `untrack`.
|
|
272
|
+
*
|
|
273
|
+
* @param internalFieldStore The field store to walk.
|
|
274
|
+
* @param callback The callback to invoke for each field store. Return `true` to stop the walk early.
|
|
275
|
+
*
|
|
276
|
+
* @returns Whether the walk was stopped early by the callback.
|
|
277
|
+
*/
|
|
278
|
+
function walkFieldStore(internalFieldStore, callback) {
|
|
279
|
+
if (callback(internalFieldStore)) return true;
|
|
280
|
+
if (internalFieldStore.kind === "array") {
|
|
281
|
+
for (let index = 0; index < internalFieldStore.items.value.length; index++) if (walkFieldStore(internalFieldStore.children[index], callback)) return true;
|
|
282
|
+
} else if (internalFieldStore.kind === "object") {
|
|
283
|
+
for (const key in internalFieldStore.children) if (walkFieldStore(internalFieldStore.children[key], callback)) return true;
|
|
284
|
+
}
|
|
285
|
+
return false;
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
236
288
|
* Returns whether the specified boolean property is true for the field store
|
|
237
289
|
* or any of its nested children. Recursively checks arrays and objects.
|
|
238
290
|
*
|
|
@@ -243,16 +295,7 @@ function swapItemState(firstInternalFieldStore, secondInternalFieldStore) {
|
|
|
243
295
|
*/
|
|
244
296
|
/* @__NO_SIDE_EFFECTS__ */
|
|
245
297
|
function getFieldBool(internalFieldStore, type) {
|
|
246
|
-
|
|
247
|
-
if (internalFieldStore.kind === "array") {
|
|
248
|
-
for (let index = 0; index < internalFieldStore.items.value.length; index++) if (/* @__PURE__ */ getFieldBool(internalFieldStore.children[index], type)) return true;
|
|
249
|
-
return false;
|
|
250
|
-
}
|
|
251
|
-
if (internalFieldStore.kind == "object") {
|
|
252
|
-
for (const key in internalFieldStore.children) if (/* @__PURE__ */ getFieldBool(internalFieldStore.children[key], type)) return true;
|
|
253
|
-
return false;
|
|
254
|
-
}
|
|
255
|
-
return false;
|
|
298
|
+
return walkFieldStore(internalFieldStore, (internalFieldStore$1) => Boolean(internalFieldStore$1[type].value));
|
|
256
299
|
}
|
|
257
300
|
/**
|
|
258
301
|
* Returns only the dirty input of the field store. Arrays are treated as
|
|
@@ -370,11 +413,11 @@ function getFieldStore(internalFormStore, path) {
|
|
|
370
413
|
*/
|
|
371
414
|
function setFieldBool(internalFieldStore, type, bool) {
|
|
372
415
|
batch(() => {
|
|
373
|
-
|
|
374
|
-
internalFieldStore
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
416
|
+
untrack(() => {
|
|
417
|
+
walkFieldStore(internalFieldStore, (internalFieldStore$1) => {
|
|
418
|
+
internalFieldStore$1[type].value = bool;
|
|
419
|
+
});
|
|
420
|
+
});
|
|
378
421
|
});
|
|
379
422
|
}
|
|
380
423
|
/**
|
|
@@ -386,23 +429,21 @@ function setFieldBool(internalFieldStore, type, bool) {
|
|
|
386
429
|
*/
|
|
387
430
|
function setNestedInput(internalFieldStore, input) {
|
|
388
431
|
internalFieldStore.isTouched.value = true;
|
|
432
|
+
internalFieldStore.isEdited.value = true;
|
|
389
433
|
if (internalFieldStore.kind === "array") {
|
|
390
434
|
const arrayInput = input ?? [];
|
|
391
435
|
const items = internalFieldStore.items.value;
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
initializeFieldStore(internalFieldStore.children[index], internalFieldStore.schema.item, arrayInput[index], path);
|
|
400
|
-
path.pop();
|
|
401
|
-
}
|
|
436
|
+
const length = internalFieldStore.schema.type === "array" ? arrayInput.length : internalFieldStore.children.length;
|
|
437
|
+
if (length < items.length) internalFieldStore.items.value = items.slice(0, length);
|
|
438
|
+
else if (length > items.length) {
|
|
439
|
+
for (let index = items.length; index < length; index++) if (internalFieldStore.children[index]) resetItemState(internalFieldStore.children[index], arrayInput[index], true);
|
|
440
|
+
else {
|
|
441
|
+
internalFieldStore.children[index] = {};
|
|
442
|
+
initializeFieldStore(internalFieldStore.children[index], internalFieldStore.schema.item, arrayInput[index], [...internalFieldStore.path, index]);
|
|
402
443
|
}
|
|
403
|
-
internalFieldStore.items.value = [...items, ...
|
|
444
|
+
internalFieldStore.items.value = [...items, ...Array.from({ length: length - items.length }, createId)];
|
|
404
445
|
}
|
|
405
|
-
for (let index = 0; index <
|
|
446
|
+
for (let index = 0; index < length; index++) setNestedInput(internalFieldStore.children[index], arrayInput[index]);
|
|
406
447
|
internalFieldStore.input.value = input == null ? input : true;
|
|
407
448
|
internalFieldStore.isDirty.value = internalFieldStore.startInput.value !== internalFieldStore.input.value || internalFieldStore.startItems.value.length !== internalFieldStore.items.value.length;
|
|
408
449
|
} else if (internalFieldStore.kind === "object") {
|
|
@@ -446,38 +487,22 @@ function setFieldInput(internalFormStore, path, input) {
|
|
|
446
487
|
function setInitialFieldInput(internalFieldStore, initialInput) {
|
|
447
488
|
batch(() => {
|
|
448
489
|
if (internalFieldStore.kind === "array") {
|
|
449
|
-
internalFieldStore.
|
|
490
|
+
internalFieldStore.initialInput.value = initialInput == null ? initialInput : true;
|
|
450
491
|
const initialArrayInput = initialInput ?? [];
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
path.push(index);
|
|
456
|
-
initializeFieldStore(internalFieldStore.children[index], internalFieldStore.schema.item, initialArrayInput[index], path);
|
|
457
|
-
path.pop();
|
|
458
|
-
}
|
|
492
|
+
const length = internalFieldStore.schema.type === "array" ? initialArrayInput.length : internalFieldStore.children.length;
|
|
493
|
+
if (length > internalFieldStore.children.length) for (let index = internalFieldStore.children.length; index < length; index++) {
|
|
494
|
+
internalFieldStore.children[index] = {};
|
|
495
|
+
initializeFieldStore(internalFieldStore.children[index], internalFieldStore.schema.item, initialArrayInput[index], [...internalFieldStore.path, index]);
|
|
459
496
|
}
|
|
460
|
-
internalFieldStore.initialItems.value =
|
|
497
|
+
internalFieldStore.initialItems.value = Array.from({ length }, createId);
|
|
461
498
|
for (let index = 0; index < internalFieldStore.children.length; index++) setInitialFieldInput(internalFieldStore.children[index], initialArrayInput[index]);
|
|
462
499
|
} else if (internalFieldStore.kind === "object") {
|
|
463
|
-
internalFieldStore.
|
|
500
|
+
internalFieldStore.initialInput.value = initialInput == null ? initialInput : true;
|
|
464
501
|
for (const key in internalFieldStore.children) setInitialFieldInput(internalFieldStore.children[key], initialInput?.[key]);
|
|
465
502
|
} else internalFieldStore.initialInput.value = initialInput;
|
|
466
503
|
});
|
|
467
504
|
}
|
|
468
505
|
/**
|
|
469
|
-
* Walks through the field store and all nested children, calling the callback
|
|
470
|
-
* for each field store in depth-first order.
|
|
471
|
-
*
|
|
472
|
-
* @param internalFieldStore The field store to walk.
|
|
473
|
-
* @param callback The callback to invoke for each field store.
|
|
474
|
-
*/
|
|
475
|
-
function walkFieldStore(internalFieldStore, callback) {
|
|
476
|
-
callback(internalFieldStore);
|
|
477
|
-
if (internalFieldStore.kind === "array") for (let index = 0; index < untrack(() => internalFieldStore.items.value).length; index++) walkFieldStore(internalFieldStore.children[index], callback);
|
|
478
|
-
else if (internalFieldStore.kind === "object") for (const key in internalFieldStore.children) walkFieldStore(internalFieldStore.children[key], callback);
|
|
479
|
-
}
|
|
480
|
-
/**
|
|
481
506
|
* Creates a new internal form store from the provided configuration.
|
|
482
507
|
* Initializes the field store hierarchy, sets validation modes, and
|
|
483
508
|
* creates form state signals.
|
|
@@ -512,44 +537,51 @@ function createFormStore(config, parse) {
|
|
|
512
537
|
async function validateFormInput(internalFormStore, config) {
|
|
513
538
|
internalFormStore.validators++;
|
|
514
539
|
internalFormStore.isValidating.value = true;
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
const path
|
|
522
|
-
|
|
523
|
-
const
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
const name = JSON.stringify(path);
|
|
530
|
-
const fieldErrors = nestedErrors[name];
|
|
531
|
-
if (fieldErrors) fieldErrors.push(issue.message);
|
|
532
|
-
else nestedErrors[name] = [issue.message];
|
|
533
|
-
} else if (rootErrors) rootErrors.push(issue.message);
|
|
534
|
-
else rootErrors = [issue.message];
|
|
535
|
-
}
|
|
536
|
-
let shouldFocus = config?.shouldFocus ?? false;
|
|
537
|
-
batch(() => {
|
|
538
|
-
walkFieldStore(internalFormStore, (internalFieldStore) => {
|
|
539
|
-
if (internalFieldStore.name === "[]") internalFieldStore.errors.value = rootErrors ?? null;
|
|
540
|
-
else {
|
|
541
|
-
const fieldErrors = nestedErrors?.[internalFieldStore.name] ?? null;
|
|
542
|
-
internalFieldStore.errors.value = fieldErrors;
|
|
543
|
-
if (shouldFocus && fieldErrors) {
|
|
544
|
-
internalFieldStore.elements[0]?.focus();
|
|
545
|
-
shouldFocus = false;
|
|
540
|
+
try {
|
|
541
|
+
const result = await internalFormStore.parse(untrack(() => /* @__PURE__ */ getFieldInput(internalFormStore)));
|
|
542
|
+
let rootErrors;
|
|
543
|
+
let nestedErrors;
|
|
544
|
+
if (result.issues) {
|
|
545
|
+
nestedErrors = {};
|
|
546
|
+
for (const issue of result.issues) if (issue.path) {
|
|
547
|
+
const path = [];
|
|
548
|
+
for (const pathItem of issue.path) {
|
|
549
|
+
const key = pathItem.key;
|
|
550
|
+
const keyType = typeof key;
|
|
551
|
+
const itemType = pathItem.type;
|
|
552
|
+
if (keyType !== "string" && keyType !== "number" || itemType === "map" || itemType === "set") break;
|
|
553
|
+
path.push(key);
|
|
546
554
|
}
|
|
547
|
-
|
|
555
|
+
const name = JSON.stringify(path);
|
|
556
|
+
const fieldErrors = nestedErrors[name];
|
|
557
|
+
if (fieldErrors) fieldErrors.push(issue.message);
|
|
558
|
+
else nestedErrors[name] = [issue.message];
|
|
559
|
+
} else if (rootErrors) rootErrors.push(issue.message);
|
|
560
|
+
else rootErrors = [issue.message];
|
|
561
|
+
}
|
|
562
|
+
let shouldFocus = config?.shouldFocus ?? false;
|
|
563
|
+
batch(() => {
|
|
564
|
+
untrack(() => {
|
|
565
|
+
walkFieldStore(internalFormStore, (internalFieldStore) => {
|
|
566
|
+
if (internalFieldStore.path.length === 0) internalFieldStore.errors.value = rootErrors ?? null;
|
|
567
|
+
else {
|
|
568
|
+
const fieldErrors = nestedErrors?.[internalFieldStore.name] ?? null;
|
|
569
|
+
internalFieldStore.errors.value = fieldErrors;
|
|
570
|
+
if (shouldFocus && fieldErrors && focusFieldElement(internalFieldStore)) shouldFocus = false;
|
|
571
|
+
}
|
|
572
|
+
});
|
|
573
|
+
});
|
|
574
|
+
internalFormStore.validators--;
|
|
575
|
+
internalFormStore.isValidating.value = internalFormStore.validators > 0;
|
|
548
576
|
});
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
577
|
+
return result;
|
|
578
|
+
} catch (error) {
|
|
579
|
+
batch(() => {
|
|
580
|
+
internalFormStore.validators--;
|
|
581
|
+
internalFormStore.isValidating.value = internalFormStore.validators > 0;
|
|
582
|
+
});
|
|
583
|
+
throw error;
|
|
584
|
+
}
|
|
553
585
|
}
|
|
554
586
|
/**
|
|
555
587
|
* Validates the form input if required based on the validation mode and form
|
|
@@ -571,7 +603,7 @@ const INTERNAL = "~internal";
|
|
|
571
603
|
//#endregion
|
|
572
604
|
//#region ../../packages/methods/dist/index.preact.js
|
|
573
605
|
/**
|
|
574
|
-
* Focuses the first input element of a field. This is useful for
|
|
606
|
+
* Focuses the first focusable input element of a field. This is useful for
|
|
575
607
|
* programmatically setting focus to a specific field, such as after
|
|
576
608
|
* validation errors or user interactions.
|
|
577
609
|
*
|
|
@@ -579,27 +611,29 @@ const INTERNAL = "~internal";
|
|
|
579
611
|
* @param config The focus field configuration.
|
|
580
612
|
*/
|
|
581
613
|
function focus(form, config) {
|
|
582
|
-
getFieldStore(form[INTERNAL], config.path)
|
|
614
|
+
focusFieldElement(getFieldStore(form[INTERNAL], config.path));
|
|
615
|
+
}
|
|
616
|
+
/* @__NO_SIDE_EFFECTS__ */
|
|
617
|
+
function getDeepErrorEntries(form, config) {
|
|
618
|
+
const entries = [];
|
|
619
|
+
walkFieldStore(config?.path ? getFieldStore(form[INTERNAL], config.path) : form[INTERNAL], (internalFieldStore) => {
|
|
620
|
+
const errors = internalFieldStore.errors.value;
|
|
621
|
+
if (errors) entries.push({
|
|
622
|
+
path: internalFieldStore.path,
|
|
623
|
+
errors
|
|
624
|
+
});
|
|
625
|
+
});
|
|
626
|
+
return entries;
|
|
583
627
|
}
|
|
584
|
-
/**
|
|
585
|
-
* Retrieves all error messages from all fields in the form by walking through
|
|
586
|
-
* the entire field store tree. This is useful for displaying a summary of all
|
|
587
|
-
* validation errors across the form.
|
|
588
|
-
*
|
|
589
|
-
* @param form The form store to retrieve errors from.
|
|
590
|
-
*
|
|
591
|
-
* @returns A non-empty array of error messages, or null if no errors exist.
|
|
592
|
-
*/
|
|
593
628
|
/* @__NO_SIDE_EFFECTS__ */
|
|
594
|
-
function
|
|
595
|
-
let
|
|
596
|
-
walkFieldStore(form[INTERNAL], (internalFieldStore) => {
|
|
597
|
-
if (internalFieldStore.kind === "array") internalFieldStore.items.value;
|
|
629
|
+
function getDeepErrors(form, config) {
|
|
630
|
+
let deepErrors = null;
|
|
631
|
+
walkFieldStore(config?.path ? getFieldStore(form[INTERNAL], config.path) : form[INTERNAL], (internalFieldStore) => {
|
|
598
632
|
const errors = internalFieldStore.errors.value;
|
|
599
|
-
if (errors) if (
|
|
600
|
-
else
|
|
633
|
+
if (errors) if (deepErrors) deepErrors.push(...errors);
|
|
634
|
+
else deepErrors = [...errors];
|
|
601
635
|
});
|
|
602
|
-
return
|
|
636
|
+
return deepErrors;
|
|
603
637
|
}
|
|
604
638
|
/* @__NO_SIDE_EFFECTS__ */
|
|
605
639
|
function getDirtyInput(form, config) {
|
|
@@ -607,9 +641,8 @@ function getDirtyInput(form, config) {
|
|
|
607
641
|
}
|
|
608
642
|
/* @__NO_SIDE_EFFECTS__ */
|
|
609
643
|
function getDirtyPaths(form, config) {
|
|
610
|
-
config?.path ? getFieldStore(form[INTERNAL], config.path) : form[INTERNAL];
|
|
611
644
|
const paths = [];
|
|
612
|
-
config?.path
|
|
645
|
+
config?.path ? getFieldStore(form[INTERNAL], config.path) : form[INTERNAL];
|
|
613
646
|
return paths;
|
|
614
647
|
}
|
|
615
648
|
/* @__NO_SIDE_EFFECTS__ */
|
|
@@ -660,25 +693,38 @@ function insert(form, config) {
|
|
|
660
693
|
internalArrayStore.items.value = newItems;
|
|
661
694
|
for (let index = items.length; index > insertIndex; index--) {
|
|
662
695
|
if (!internalArrayStore.children[index]) {
|
|
663
|
-
const path = JSON.parse(internalArrayStore.name);
|
|
664
696
|
internalArrayStore.children[index] = {};
|
|
665
|
-
|
|
666
|
-
initializeFieldStore(internalArrayStore.children[index], internalArrayStore.schema.item, void 0, path);
|
|
697
|
+
initializeFieldStore(internalArrayStore.children[index], internalArrayStore.schema.item, void 0, [...internalArrayStore.path, index]);
|
|
667
698
|
}
|
|
668
699
|
copyItemState(internalArrayStore.children[index - 1], internalArrayStore.children[index]);
|
|
669
700
|
}
|
|
670
701
|
if (!internalArrayStore.children[insertIndex]) {
|
|
671
|
-
const path = JSON.parse(internalArrayStore.name);
|
|
672
702
|
internalArrayStore.children[insertIndex] = {};
|
|
673
|
-
|
|
674
|
-
initializeFieldStore(internalArrayStore.children[insertIndex], internalArrayStore.schema.item, config.initialInput, path);
|
|
703
|
+
initializeFieldStore(internalArrayStore.children[insertIndex], internalArrayStore.schema.item, config.initialInput, [...internalArrayStore.path, insertIndex]);
|
|
675
704
|
} else resetItemState(internalArrayStore.children[insertIndex], config.initialInput);
|
|
676
705
|
internalArrayStore.input.value = true;
|
|
677
706
|
internalArrayStore.isTouched.value = true;
|
|
707
|
+
internalArrayStore.isEdited.value = true;
|
|
678
708
|
internalArrayStore.isDirty.value = true;
|
|
679
709
|
validateIfRequired(internalFormStore, internalArrayStore, "input");
|
|
680
710
|
});
|
|
681
711
|
}
|
|
712
|
+
/* @__NO_SIDE_EFFECTS__ */
|
|
713
|
+
function isDirty(form, config) {
|
|
714
|
+
return getFieldBool(config?.path ? getFieldStore(form[INTERNAL], config.path) : form[INTERNAL], "isDirty");
|
|
715
|
+
}
|
|
716
|
+
/* @__NO_SIDE_EFFECTS__ */
|
|
717
|
+
function isEdited(form, config) {
|
|
718
|
+
return getFieldBool(config?.path ? getFieldStore(form[INTERNAL], config.path) : form[INTERNAL], "isEdited");
|
|
719
|
+
}
|
|
720
|
+
/* @__NO_SIDE_EFFECTS__ */
|
|
721
|
+
function isTouched(form, config) {
|
|
722
|
+
return getFieldBool(config?.path ? getFieldStore(form[INTERNAL], config.path) : form[INTERNAL], "isTouched");
|
|
723
|
+
}
|
|
724
|
+
/* @__NO_SIDE_EFFECTS__ */
|
|
725
|
+
function isValid(form, config) {
|
|
726
|
+
return !getFieldBool(config?.path ? getFieldStore(form[INTERNAL], config.path) : form[INTERNAL], "errors");
|
|
727
|
+
}
|
|
682
728
|
/**
|
|
683
729
|
* Moves an item from one index to another within a field array. All items
|
|
684
730
|
* between the source and destination indices are shifted accordingly.
|
|
@@ -701,6 +747,7 @@ function move(form, config) {
|
|
|
701
747
|
else for (let index = config.from; index > config.to; index--) copyItemState(internalArrayStore.children[index - 1], internalArrayStore.children[index]);
|
|
702
748
|
copyItemState(tempInternalFieldStore, internalArrayStore.children[config.to]);
|
|
703
749
|
internalArrayStore.isTouched.value = true;
|
|
750
|
+
internalArrayStore.isEdited.value = true;
|
|
704
751
|
internalArrayStore.isDirty.value = internalArrayStore.startItems.value.join() !== newItems.join();
|
|
705
752
|
validateIfRequired(internalFormStore, internalArrayStore, "input");
|
|
706
753
|
});
|
|
@@ -764,6 +811,7 @@ function remove(form, config) {
|
|
|
764
811
|
internalArrayStore.items.value = newItems;
|
|
765
812
|
for (let index = config.at; index < items.length - 1; index++) copyItemState(internalArrayStore.children[index + 1], internalArrayStore.children[index]);
|
|
766
813
|
internalArrayStore.isTouched.value = true;
|
|
814
|
+
internalArrayStore.isEdited.value = true;
|
|
767
815
|
internalArrayStore.isDirty.value = internalArrayStore.startItems.value.join() !== newItems.join();
|
|
768
816
|
validateIfRequired(internalFormStore, internalArrayStore, "input");
|
|
769
817
|
});
|
|
@@ -784,6 +832,7 @@ function replace(form, config) {
|
|
|
784
832
|
internalArrayStore.items.value = newItems;
|
|
785
833
|
resetItemState(internalArrayStore.children[config.at], config.initialInput);
|
|
786
834
|
internalArrayStore.isTouched.value = true;
|
|
835
|
+
internalArrayStore.isEdited.value = true;
|
|
787
836
|
internalArrayStore.isDirty.value = true;
|
|
788
837
|
validateIfRequired(internalFormStore, internalArrayStore, "input");
|
|
789
838
|
});
|
|
@@ -798,6 +847,7 @@ function reset(form, config) {
|
|
|
798
847
|
internalFieldStore$1.elements = internalFieldStore$1.initialElements;
|
|
799
848
|
if (!config?.keepErrors) internalFieldStore$1.errors.value = null;
|
|
800
849
|
if (!config?.keepTouched) internalFieldStore$1.isTouched.value = false;
|
|
850
|
+
if (!config?.keepEdited) internalFieldStore$1.isEdited.value = false;
|
|
801
851
|
internalFieldStore$1.startInput.value = internalFieldStore$1.initialInput.value;
|
|
802
852
|
if (!config?.keepInput) internalFieldStore$1.input.value = internalFieldStore$1.initialInput.value;
|
|
803
853
|
if (internalFieldStore$1.kind === "array") {
|
|
@@ -856,6 +906,7 @@ function swap(form, config) {
|
|
|
856
906
|
internalArrayStore.items.value = newItems;
|
|
857
907
|
swapItemState(internalArrayStore.children[config.at], internalArrayStore.children[config.and]);
|
|
858
908
|
internalArrayStore.isTouched.value = true;
|
|
909
|
+
internalArrayStore.isEdited.value = true;
|
|
859
910
|
internalArrayStore.isDirty.value = internalArrayStore.startItems.value.join() !== newItems.join();
|
|
860
911
|
validateIfRequired(internalFormStore, internalArrayStore, "input");
|
|
861
912
|
});
|
|
@@ -913,7 +964,10 @@ function useField(form, config) {
|
|
|
913
964
|
const internalFieldStore = useComputed(() => getFieldStore(internalFormStore, pathSignal.value));
|
|
914
965
|
useSignalEffect(() => {
|
|
915
966
|
return () => {
|
|
916
|
-
|
|
967
|
+
const internalFieldStoreValue = internalFieldStore.value;
|
|
968
|
+
const elements = internalFieldStoreValue.elements.filter((element) => element.isConnected);
|
|
969
|
+
if (internalFieldStoreValue.elements === internalFieldStoreValue.initialElements) internalFieldStoreValue.initialElements = elements;
|
|
970
|
+
internalFieldStoreValue.elements = elements;
|
|
917
971
|
};
|
|
918
972
|
});
|
|
919
973
|
return useMemo(() => ({
|
|
@@ -921,6 +975,7 @@ function useField(form, config) {
|
|
|
921
975
|
input: computed(() => getFieldInput(internalFieldStore.value)),
|
|
922
976
|
errors: computed(() => internalFieldStore.value.errors.value),
|
|
923
977
|
isTouched: computed(() => getFieldBool(internalFieldStore.value, "isTouched")),
|
|
978
|
+
isEdited: computed(() => getFieldBool(internalFieldStore.value, "isEdited")),
|
|
924
979
|
isDirty: computed(() => getFieldBool(internalFieldStore.value, "isDirty")),
|
|
925
980
|
isValid: computed(() => !getFieldBool(internalFieldStore.value, "errors")),
|
|
926
981
|
onInput(value) {
|
|
@@ -964,6 +1019,7 @@ function useFieldArray(form, config) {
|
|
|
964
1019
|
items: computed(() => internalFieldStore.value.items.value),
|
|
965
1020
|
errors: computed(() => internalFieldStore.value.errors.value),
|
|
966
1021
|
isTouched: computed(() => getFieldBool(internalFieldStore.value, "isTouched")),
|
|
1022
|
+
isEdited: computed(() => getFieldBool(internalFieldStore.value, "isEdited")),
|
|
967
1023
|
isDirty: computed(() => getFieldBool(internalFieldStore.value, "isDirty")),
|
|
968
1024
|
isValid: computed(() => !getFieldBool(internalFieldStore.value, "errors"))
|
|
969
1025
|
}), []);
|
|
@@ -981,6 +1037,7 @@ function useForm(config) {
|
|
|
981
1037
|
isSubmitted: internalFormStore.isSubmitted,
|
|
982
1038
|
isValidating: internalFormStore.isValidating,
|
|
983
1039
|
isTouched: computed(() => getFieldBool(internalFormStore, "isTouched")),
|
|
1040
|
+
isEdited: computed(() => getFieldBool(internalFormStore, "isEdited")),
|
|
984
1041
|
isDirty: computed(() => getFieldBool(internalFormStore, "isDirty")),
|
|
985
1042
|
isValid: computed(() => !getFieldBool(internalFormStore, "errors")),
|
|
986
1043
|
errors: internalFormStore.errors
|
|
@@ -1040,4 +1097,4 @@ function Form({ of, onSubmit, ...other }) {
|
|
|
1040
1097
|
}
|
|
1041
1098
|
|
|
1042
1099
|
//#endregion
|
|
1043
|
-
export { Field, FieldArray, Form, focus,
|
|
1100
|
+
export { Field, FieldArray, Form, focus, getDeepErrorEntries, getDeepErrors, getDirtyInput, getDirtyPaths, getErrors, getInput, handleSubmit, insert, isDirty, isEdited, isTouched, isValid, move, pickDirty, remove, replace, reset, setErrors, setInput, submit, swap, useField, useFieldArray, useForm, validate };
|