@formisch/preact 0.5.0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +692 -24
- package/dist/index.js +262 -35
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -4,13 +4,29 @@ import { useLayoutEffect, useMemo } from "preact/hooks";
|
|
|
4
4
|
import { jsx } from "preact/jsx-runtime";
|
|
5
5
|
|
|
6
6
|
//#region ../../packages/core/dist/index.preact.js
|
|
7
|
+
/**
|
|
8
|
+
* The current framework being used.
|
|
9
|
+
*/
|
|
7
10
|
const framework = "preact";
|
|
11
|
+
/**
|
|
12
|
+
* Creates a unique identifier string.
|
|
13
|
+
*
|
|
14
|
+
* @returns The unique identifier.
|
|
15
|
+
*/
|
|
16
|
+
/* @__NO_SIDE_EFFECTS__ */
|
|
8
17
|
function createId() {
|
|
9
18
|
return Math.random().toString(36).slice(2);
|
|
10
19
|
}
|
|
11
20
|
/**
|
|
12
|
-
*
|
|
13
|
-
*
|
|
21
|
+
* Initializes a field store recursively based on the schema structure. Handles
|
|
22
|
+
* array, object, and value schemas, setting up all necessary signals and
|
|
23
|
+
* children. Supports wrapped schemas and schema options.
|
|
24
|
+
*
|
|
25
|
+
* @param internalFieldStore The partial field store to initialize.
|
|
26
|
+
* @param schema The Valibot schema defining the field structure.
|
|
27
|
+
* @param initialInput The initial input value.
|
|
28
|
+
* @param path The path to the field in the form.
|
|
29
|
+
* @param nullish Whether the schema is wrapped in a nullish schema.
|
|
14
30
|
*/
|
|
15
31
|
function initializeFieldStore(internalFieldStore, schema, initialInput, path, nullish = false) {
|
|
16
32
|
if (framework === "qwik" && schema.type === "lazy" || schema.type === "object_with_rest" || schema.type === "record" || schema.type === "tuple_with_rest" || schema.type === "promise") throw new Error(`"${schema.type}" schema is not supported`);
|
|
@@ -81,13 +97,13 @@ function initializeFieldStore(internalFieldStore, schema, initialInput, path, nu
|
|
|
81
97
|
}
|
|
82
98
|
}
|
|
83
99
|
/**
|
|
84
|
-
* Copies the deeply nested state (signal values) from one
|
|
85
|
-
* This includes the `
|
|
100
|
+
* Copies the deeply nested state (signal values) from one field store to
|
|
101
|
+
* another. This includes the `elements`, `errors`, `startInput`, `input`,
|
|
102
|
+
* `isTouched`, `isDirty`, and for arrays `startItems` and `items` properties.
|
|
86
103
|
* Recursively walks through the field stores and copies all signal values.
|
|
87
104
|
*
|
|
88
|
-
* @param
|
|
89
|
-
* @param
|
|
90
|
-
* @param toIndex - The destination index to copy to
|
|
105
|
+
* @param fromInternalFieldStore The source field store to copy from.
|
|
106
|
+
* @param toInternalFieldStore The destination field store to copy to.
|
|
91
107
|
*/
|
|
92
108
|
function copyItemState(fromInternalFieldStore, toInternalFieldStore) {
|
|
93
109
|
batch(() => {
|
|
@@ -118,13 +134,14 @@ function copyItemState(fromInternalFieldStore, toInternalFieldStore) {
|
|
|
118
134
|
});
|
|
119
135
|
}
|
|
120
136
|
/**
|
|
121
|
-
* Resets the state of
|
|
122
|
-
*
|
|
123
|
-
* `
|
|
124
|
-
* Keeps the `initialInput` and `initialItems` state unchanged for
|
|
137
|
+
* Resets the state of a field store (signal values) deeply nested. Sets
|
|
138
|
+
* `elements` to empty array, `errors` to `null`, `isTouched` and `isDirty` to
|
|
139
|
+
* `false`, and `startInput`, `input`, `startItems`, and `items` to the new
|
|
140
|
+
* input value. Keeps the `initialInput` and `initialItems` state unchanged for
|
|
141
|
+
* form reset functionality.
|
|
125
142
|
*
|
|
126
|
-
* @param internalFieldStore
|
|
127
|
-
* @param initialInput
|
|
143
|
+
* @param internalFieldStore The field store to reset.
|
|
144
|
+
* @param initialInput The new input value (can be any type including array or object).
|
|
128
145
|
*/
|
|
129
146
|
function resetItemState(internalFieldStore, initialInput) {
|
|
130
147
|
batch(() => {
|
|
@@ -153,12 +170,13 @@ function resetItemState(internalFieldStore, initialInput) {
|
|
|
153
170
|
});
|
|
154
171
|
}
|
|
155
172
|
/**
|
|
156
|
-
* Swaps the deeply nested state (signal values) between two field stores.
|
|
157
|
-
*
|
|
158
|
-
*
|
|
173
|
+
* Swaps the deeply nested state (signal values) between two field stores. This
|
|
174
|
+
* includes the `elements`, `errors`, `startInput`, `input`, `isTouched`,
|
|
175
|
+
* `isDirty`, and for arrays `startItems` and `items` properties. Recursively
|
|
176
|
+
* walks through the field stores and swaps all signal values.
|
|
159
177
|
*
|
|
160
|
-
* @param firstInternalFieldStore
|
|
161
|
-
* @param secondInternalFieldStore
|
|
178
|
+
* @param firstInternalFieldStore The first field store to swap.
|
|
179
|
+
* @param secondInternalFieldStore The second field store to swap.
|
|
162
180
|
*/
|
|
163
181
|
function swapItemState(firstInternalFieldStore, secondInternalFieldStore) {
|
|
164
182
|
batch(() => {
|
|
@@ -213,11 +231,21 @@ function swapItemState(firstInternalFieldStore, secondInternalFieldStore) {
|
|
|
213
231
|
});
|
|
214
232
|
});
|
|
215
233
|
}
|
|
234
|
+
/**
|
|
235
|
+
* Returns the current input of the field store. For arrays and objects,
|
|
236
|
+
* recursively collects input from all children. Returns `null` or `undefined`
|
|
237
|
+
* for nullish array/object inputs, or the primitive value for value fields.
|
|
238
|
+
*
|
|
239
|
+
* @param internalFieldStore The field store to get input from.
|
|
240
|
+
*
|
|
241
|
+
* @returns The field input.
|
|
242
|
+
*/
|
|
243
|
+
/* @__NO_SIDE_EFFECTS__ */
|
|
216
244
|
function getFieldInput(internalFieldStore) {
|
|
217
245
|
if (internalFieldStore.kind === "array") {
|
|
218
246
|
if (internalFieldStore.input.value) {
|
|
219
247
|
const value = [];
|
|
220
|
-
for (let index = 0; index < internalFieldStore.items.value.length; index++) value[index] = getFieldInput(internalFieldStore.children[index]);
|
|
248
|
+
for (let index = 0; index < internalFieldStore.items.value.length; index++) value[index] = /* @__PURE__ */ getFieldInput(internalFieldStore.children[index]);
|
|
221
249
|
return value;
|
|
222
250
|
}
|
|
223
251
|
return internalFieldStore.input.value;
|
|
@@ -225,7 +253,7 @@ function getFieldInput(internalFieldStore) {
|
|
|
225
253
|
if (internalFieldStore.kind === "object") {
|
|
226
254
|
if (internalFieldStore.input.value) {
|
|
227
255
|
const value = {};
|
|
228
|
-
for (const key in internalFieldStore.children) value[key] = getFieldInput(internalFieldStore.children[key]);
|
|
256
|
+
for (const key in internalFieldStore.children) value[key] = /* @__PURE__ */ getFieldInput(internalFieldStore.children[key]);
|
|
229
257
|
return value;
|
|
230
258
|
}
|
|
231
259
|
return internalFieldStore.input.value;
|
|
@@ -233,13 +261,15 @@ function getFieldInput(internalFieldStore) {
|
|
|
233
261
|
return internalFieldStore.input.value;
|
|
234
262
|
}
|
|
235
263
|
/**
|
|
236
|
-
* Returns the current input of the element.
|
|
264
|
+
* Returns the current input of the element. Handles special cases for select
|
|
265
|
+
* multiple, checkbox groups, radio groups, and file inputs.
|
|
237
266
|
*
|
|
238
267
|
* @param element The field element.
|
|
239
|
-
* @param
|
|
268
|
+
* @param internalFieldStore The internal field store.
|
|
240
269
|
*
|
|
241
270
|
* @returns The element input.
|
|
242
271
|
*/
|
|
272
|
+
/* @__NO_SIDE_EFFECTS__ */
|
|
243
273
|
function getElementInput(element, internalFieldStore) {
|
|
244
274
|
if (element.options && element.multiple) return [...element.options].filter((option) => option.selected && !option.disabled).map((option) => option.value);
|
|
245
275
|
if (element.type === "checkbox") {
|
|
@@ -248,7 +278,7 @@ function getElementInput(element, internalFieldStore) {
|
|
|
248
278
|
return element.checked;
|
|
249
279
|
}
|
|
250
280
|
if (element.type === "radio") {
|
|
251
|
-
const prevValue = untrack(() => getFieldInput(internalFieldStore));
|
|
281
|
+
const prevValue = untrack(() => /* @__PURE__ */ getFieldInput(internalFieldStore));
|
|
252
282
|
if (element.checked) return [...prevValue, element.value];
|
|
253
283
|
return prevValue.filter((value) => value !== element.value);
|
|
254
284
|
}
|
|
@@ -258,23 +288,51 @@ function getElementInput(element, internalFieldStore) {
|
|
|
258
288
|
}
|
|
259
289
|
return element.value;
|
|
260
290
|
}
|
|
291
|
+
/**
|
|
292
|
+
* Returns whether the specified boolean property is true for the field store
|
|
293
|
+
* or any of its nested children. Recursively checks arrays and objects.
|
|
294
|
+
*
|
|
295
|
+
* @param internalFieldStore The field store to check.
|
|
296
|
+
* @param type The boolean property type to check.
|
|
297
|
+
*
|
|
298
|
+
* @returns Whether the property is true.
|
|
299
|
+
*/
|
|
300
|
+
/* @__NO_SIDE_EFFECTS__ */
|
|
261
301
|
function getFieldBool(internalFieldStore, type) {
|
|
262
302
|
if (internalFieldStore[type].value) return true;
|
|
263
303
|
if (internalFieldStore.kind === "array") {
|
|
264
|
-
for (let index = 0; index < internalFieldStore.items.value.length; index++) if (getFieldBool(internalFieldStore.children[index], type)) return true;
|
|
304
|
+
for (let index = 0; index < internalFieldStore.items.value.length; index++) if (/* @__PURE__ */ getFieldBool(internalFieldStore.children[index], type)) return true;
|
|
265
305
|
return false;
|
|
266
306
|
}
|
|
267
307
|
if (internalFieldStore.kind == "object") {
|
|
268
|
-
for (const key in internalFieldStore.children) if (getFieldBool(internalFieldStore.children[key], type)) return true;
|
|
308
|
+
for (const key in internalFieldStore.children) if (/* @__PURE__ */ getFieldBool(internalFieldStore.children[key], type)) return true;
|
|
269
309
|
return false;
|
|
270
310
|
}
|
|
271
311
|
return false;
|
|
272
312
|
}
|
|
313
|
+
/**
|
|
314
|
+
* Returns the field store at the specified path by traversing the form store's
|
|
315
|
+
* children hierarchy.
|
|
316
|
+
*
|
|
317
|
+
* @param internalFormStore The form store to traverse.
|
|
318
|
+
* @param path The path to the field store.
|
|
319
|
+
*
|
|
320
|
+
* @returns The field store.
|
|
321
|
+
*/
|
|
322
|
+
/* @__NO_SIDE_EFFECTS__ */
|
|
273
323
|
function getFieldStore(internalFormStore, path) {
|
|
274
324
|
let internalFieldStore = internalFormStore;
|
|
275
325
|
for (const key of path) internalFieldStore = internalFieldStore.children[key];
|
|
276
326
|
return internalFieldStore;
|
|
277
327
|
}
|
|
328
|
+
/**
|
|
329
|
+
* Sets the specified boolean property for the field store and all nested
|
|
330
|
+
* children. Recursively updates arrays and objects.
|
|
331
|
+
*
|
|
332
|
+
* @param internalFieldStore The field store to update.
|
|
333
|
+
* @param type The boolean property type to set.
|
|
334
|
+
* @param bool The boolean value to set.
|
|
335
|
+
*/
|
|
278
336
|
function setFieldBool(internalFieldStore, type, bool) {
|
|
279
337
|
batch(() => {
|
|
280
338
|
if (internalFieldStore.kind === "array") {
|
|
@@ -284,6 +342,13 @@ function setFieldBool(internalFieldStore, type, bool) {
|
|
|
284
342
|
else internalFieldStore[type].value = bool;
|
|
285
343
|
});
|
|
286
344
|
}
|
|
345
|
+
/**
|
|
346
|
+
* Sets the input for a nested field store and all its children, updating
|
|
347
|
+
* touched and dirty states accordingly. Handles dynamic array resizing.
|
|
348
|
+
*
|
|
349
|
+
* @param internalFieldStore The field store to update.
|
|
350
|
+
* @param input The new input value.
|
|
351
|
+
*/
|
|
287
352
|
function setNestedInput(internalFieldStore, input) {
|
|
288
353
|
internalFieldStore.isTouched.value = true;
|
|
289
354
|
if (internalFieldStore.kind === "array") {
|
|
@@ -302,7 +367,7 @@ function setNestedInput(internalFieldStore, input) {
|
|
|
302
367
|
}
|
|
303
368
|
internalFieldStore.items.value = [...items, ...arrayInput.slice(items.length).map(createId)];
|
|
304
369
|
}
|
|
305
|
-
for (let index = 0; index <
|
|
370
|
+
for (let index = 0; index < arrayInput.length; index++) setNestedInput(internalFieldStore.children[index], arrayInput[index]);
|
|
306
371
|
internalFieldStore.input.value = input == null ? input : true;
|
|
307
372
|
internalFieldStore.isDirty.value = internalFieldStore.startInput.value !== internalFieldStore.input.value || internalFieldStore.startItems.value.length !== items.length;
|
|
308
373
|
} else if (internalFieldStore.kind === "object") {
|
|
@@ -315,6 +380,14 @@ function setNestedInput(internalFieldStore, input) {
|
|
|
315
380
|
internalFieldStore.isDirty.value = startInput !== input && (startInput !== void 0 || input !== "" && !Number.isNaN(input));
|
|
316
381
|
}
|
|
317
382
|
}
|
|
383
|
+
/**
|
|
384
|
+
* Sets the input for a field at the specified path in the form store,
|
|
385
|
+
* traversing the path and updating all parent fields along the way.
|
|
386
|
+
*
|
|
387
|
+
* @param internalFormStore The form store containing the field.
|
|
388
|
+
* @param path The path to the field.
|
|
389
|
+
* @param input The new input value.
|
|
390
|
+
*/
|
|
318
391
|
function setFieldInput(internalFormStore, path, input) {
|
|
319
392
|
batch(() => {
|
|
320
393
|
untrack(() => {
|
|
@@ -327,6 +400,14 @@ function setFieldInput(internalFormStore, path, input) {
|
|
|
327
400
|
});
|
|
328
401
|
});
|
|
329
402
|
}
|
|
403
|
+
/**
|
|
404
|
+
* Sets the initial input for a field store and all its children recursively.
|
|
405
|
+
* For arrays, initializes missing children if needed. Updates `initialInput`
|
|
406
|
+
* and `initialItems` properties.
|
|
407
|
+
*
|
|
408
|
+
* @param internalFieldStore The field store to update.
|
|
409
|
+
* @param initialInput The initial input value.
|
|
410
|
+
*/
|
|
330
411
|
function setInitialFieldInput(internalFieldStore, initialInput) {
|
|
331
412
|
batch(() => {
|
|
332
413
|
if (internalFieldStore.kind === "array") {
|
|
@@ -349,14 +430,32 @@ function setInitialFieldInput(internalFieldStore, initialInput) {
|
|
|
349
430
|
} else internalFieldStore.initialInput.value = initialInput;
|
|
350
431
|
});
|
|
351
432
|
}
|
|
433
|
+
/**
|
|
434
|
+
* Walks through the field store and all nested children, calling the callback
|
|
435
|
+
* for each field store in depth-first order.
|
|
436
|
+
*
|
|
437
|
+
* @param internalFieldStore The field store to walk.
|
|
438
|
+
* @param callback The callback to invoke for each field store.
|
|
439
|
+
*/
|
|
352
440
|
function walkFieldStore(internalFieldStore, callback) {
|
|
353
441
|
callback(internalFieldStore);
|
|
354
442
|
if (internalFieldStore.kind === "array") for (let index = 0; index < untrack(() => internalFieldStore.items.value).length; index++) walkFieldStore(internalFieldStore.children[index], callback);
|
|
355
443
|
else if (internalFieldStore.kind === "object") for (const key in internalFieldStore.children) walkFieldStore(internalFieldStore.children[key], callback);
|
|
356
444
|
}
|
|
445
|
+
/**
|
|
446
|
+
* Creates a new internal form store from the provided configuration.
|
|
447
|
+
* Initializes the field store hierarchy, sets validation modes, and
|
|
448
|
+
* creates form state signals.
|
|
449
|
+
*
|
|
450
|
+
* @param config The form configuration.
|
|
451
|
+
* @param parse The schema parse function.
|
|
452
|
+
*
|
|
453
|
+
* @returns The internal form store.
|
|
454
|
+
*/
|
|
357
455
|
function createFormStore(config, parse) {
|
|
358
456
|
const store = {};
|
|
359
457
|
initializeFieldStore(store, config.schema, config.initialInput, []);
|
|
458
|
+
store.validators = 0;
|
|
360
459
|
store.validate = config.validate ?? "submit";
|
|
361
460
|
store.revalidate = config.revalidate ?? "input";
|
|
362
461
|
store.parse = parse;
|
|
@@ -365,10 +464,20 @@ function createFormStore(config, parse) {
|
|
|
365
464
|
store.isValidating = createSignal(false);
|
|
366
465
|
return store;
|
|
367
466
|
}
|
|
467
|
+
/**
|
|
468
|
+
* Validates the form input using the configured Valibot schema. Parses the
|
|
469
|
+
* current form input, processes validation issues, assigns errors to fields,
|
|
470
|
+
* and optionally focuses the first field with an error.
|
|
471
|
+
*
|
|
472
|
+
* @param internalFormStore The form store to validate.
|
|
473
|
+
* @param config The validation configuration.
|
|
474
|
+
*
|
|
475
|
+
* @returns The Valibot validation result.
|
|
476
|
+
*/
|
|
368
477
|
async function validateFormInput(internalFormStore, config) {
|
|
369
478
|
internalFormStore.validators++;
|
|
370
479
|
internalFormStore.isValidating.value = true;
|
|
371
|
-
const result = await internalFormStore.parse(untrack(() => getFieldInput(internalFormStore)));
|
|
480
|
+
const result = await internalFormStore.parse(untrack(() => /* @__PURE__ */ getFieldInput(internalFormStore)));
|
|
372
481
|
let rootErrors;
|
|
373
482
|
let nestedErrors;
|
|
374
483
|
if (result.issues) {
|
|
@@ -407,16 +516,46 @@ async function validateFormInput(internalFormStore, config) {
|
|
|
407
516
|
});
|
|
408
517
|
return result;
|
|
409
518
|
}
|
|
519
|
+
/**
|
|
520
|
+
* Validates the form input if required based on the validation mode and form
|
|
521
|
+
* state. Determines whether to use initial validation mode, revalidation mode,
|
|
522
|
+
* or skip validation entirely.
|
|
523
|
+
*
|
|
524
|
+
* @param internalFormStore The form store to validate.
|
|
525
|
+
* @param internalFieldStore The field store that triggered validation.
|
|
526
|
+
* @param validationMode The validation mode that triggered this check.
|
|
527
|
+
*/
|
|
410
528
|
function validateIfRequired(internalFormStore, internalFieldStore, validationMode) {
|
|
411
|
-
if (validationMode === (internalFormStore.validate === "initial" || (internalFormStore.validate === "submit" ? untrack(() => internalFormStore.isSubmitted.value) : untrack(() => getFieldBool(internalFieldStore, "errors"))) ? internalFormStore.revalidate : internalFormStore.validate)) validateFormInput(internalFormStore);
|
|
529
|
+
if (validationMode === (internalFormStore.validate === "initial" || (internalFormStore.validate === "submit" ? untrack(() => internalFormStore.isSubmitted.value) : untrack(() => /* @__PURE__ */ getFieldBool(internalFieldStore, "errors"))) ? internalFormStore.revalidate : internalFormStore.validate)) validateFormInput(internalFormStore);
|
|
412
530
|
}
|
|
531
|
+
/**
|
|
532
|
+
* Internal symbol constant.
|
|
533
|
+
*/
|
|
413
534
|
const INTERNAL = "~internal";
|
|
414
535
|
|
|
415
536
|
//#endregion
|
|
416
537
|
//#region ../../packages/methods/dist/index.preact.js
|
|
417
|
-
|
|
418
|
-
|
|
538
|
+
/**
|
|
539
|
+
* Focuses the first input element of a field. This is useful for
|
|
540
|
+
* programmatically setting focus to a specific field, such as after
|
|
541
|
+
* validation errors or user interactions.
|
|
542
|
+
*
|
|
543
|
+
* @param form The form store containing the field.
|
|
544
|
+
* @param config The focus field configuration.
|
|
545
|
+
*/
|
|
546
|
+
function focus(form, config) {
|
|
547
|
+
getFieldStore(form[INTERNAL], config.path).elements[0]?.focus();
|
|
419
548
|
}
|
|
549
|
+
/**
|
|
550
|
+
* Retrieves all error messages from all fields in the form by walking through
|
|
551
|
+
* the entire field store tree. This is useful for displaying a summary of all
|
|
552
|
+
* validation errors across the form.
|
|
553
|
+
*
|
|
554
|
+
* @param form The form store to retrieve errors from.
|
|
555
|
+
*
|
|
556
|
+
* @returns A non-empty array of error messages, or null if no errors exist.
|
|
557
|
+
*/
|
|
558
|
+
/* @__NO_SIDE_EFFECTS__ */
|
|
420
559
|
function getAllErrors(form) {
|
|
421
560
|
let allErrors = null;
|
|
422
561
|
walkFieldStore(form[INTERNAL], (internalFieldStore) => {
|
|
@@ -426,12 +565,15 @@ function getAllErrors(form) {
|
|
|
426
565
|
});
|
|
427
566
|
return allErrors;
|
|
428
567
|
}
|
|
568
|
+
/* @__NO_SIDE_EFFECTS__ */
|
|
429
569
|
function getErrors(form, config) {
|
|
430
570
|
return (config?.path ? getFieldStore(form[INTERNAL], config.path) : form[INTERNAL]).errors.value;
|
|
431
571
|
}
|
|
572
|
+
/* @__NO_SIDE_EFFECTS__ */
|
|
432
573
|
function getInput(form, config) {
|
|
433
574
|
return getFieldInput(config?.path ? getFieldStore(form[INTERNAL], config.path) : form[INTERNAL]);
|
|
434
575
|
}
|
|
576
|
+
/* @__NO_SIDE_EFFECTS__ */
|
|
435
577
|
function handleSubmit(form, handler) {
|
|
436
578
|
return async (event) => {
|
|
437
579
|
event.preventDefault();
|
|
@@ -448,6 +590,13 @@ function handleSubmit(form, handler) {
|
|
|
448
590
|
}
|
|
449
591
|
};
|
|
450
592
|
}
|
|
593
|
+
/**
|
|
594
|
+
* Inserts a new item into a field array at the specified index. All items at
|
|
595
|
+
* or after the insertion point are shifted up by one index.
|
|
596
|
+
*
|
|
597
|
+
* @param form The form store containing the field array.
|
|
598
|
+
* @param config The insert configuration specifying the path, index, and initial value.
|
|
599
|
+
*/
|
|
451
600
|
function insert(form, config) {
|
|
452
601
|
const internalFormStore = form[INTERNAL];
|
|
453
602
|
let internalFieldStore = internalFormStore;
|
|
@@ -475,6 +624,13 @@ function insert(form, config) {
|
|
|
475
624
|
validateIfRequired(internalFormStore, internalArrayStore, "input");
|
|
476
625
|
});
|
|
477
626
|
}
|
|
627
|
+
/**
|
|
628
|
+
* Moves an item from one index to another within a field array. All items
|
|
629
|
+
* between the source and destination indices are shifted accordingly.
|
|
630
|
+
*
|
|
631
|
+
* @param form The form store containing the field array.
|
|
632
|
+
* @param config The move configuration specifying the path and source/destination indices.
|
|
633
|
+
*/
|
|
478
634
|
function move(form, config) {
|
|
479
635
|
const internalFormStore = form[INTERNAL];
|
|
480
636
|
const internalArrayStore = getFieldStore(internalFormStore, config.path);
|
|
@@ -485,15 +641,22 @@ function move(form, config) {
|
|
|
485
641
|
internalArrayStore.items.value = newItems;
|
|
486
642
|
const tempInternalFieldStore = {};
|
|
487
643
|
initializeFieldStore(tempInternalFieldStore, internalArrayStore.schema.item, void 0, []);
|
|
488
|
-
copyItemState(internalArrayStore.children[config.from
|
|
644
|
+
copyItemState(internalArrayStore.children[config.from], tempInternalFieldStore);
|
|
489
645
|
if (config.from < config.to) for (let index = config.from; index < config.to; index++) copyItemState(internalArrayStore.children[index + 1], internalArrayStore.children[index]);
|
|
490
646
|
else for (let index = config.from; index > config.to; index--) copyItemState(internalArrayStore.children[index - 1], internalArrayStore.children[index]);
|
|
491
|
-
copyItemState(tempInternalFieldStore, internalArrayStore.children[config.
|
|
647
|
+
copyItemState(tempInternalFieldStore, internalArrayStore.children[config.to]);
|
|
492
648
|
internalArrayStore.isTouched.value = true;
|
|
493
649
|
internalArrayStore.isDirty.value = internalArrayStore.startItems.value.join() !== newItems.join();
|
|
494
650
|
validateIfRequired(internalFormStore, internalArrayStore, "input");
|
|
495
651
|
});
|
|
496
652
|
}
|
|
653
|
+
/**
|
|
654
|
+
* Removes an item from a field array at the specified index. All items after
|
|
655
|
+
* the removed item are shifted down by one index.
|
|
656
|
+
*
|
|
657
|
+
* @param form The form store containing the field array.
|
|
658
|
+
* @param config The remove configuration specifying the path and index.
|
|
659
|
+
*/
|
|
497
660
|
function remove(form, config) {
|
|
498
661
|
const internalFormStore = form[INTERNAL];
|
|
499
662
|
const internalArrayStore = getFieldStore(internalFormStore, config.path);
|
|
@@ -508,6 +671,12 @@ function remove(form, config) {
|
|
|
508
671
|
validateIfRequired(internalFormStore, internalArrayStore, "input");
|
|
509
672
|
});
|
|
510
673
|
}
|
|
674
|
+
/**
|
|
675
|
+
* Replaces an item in a field array at the specified index with new initial input.
|
|
676
|
+
*
|
|
677
|
+
* @param form The form store containing the field array.
|
|
678
|
+
* @param config The replace configuration specifying the path, index, and initial input.
|
|
679
|
+
*/
|
|
511
680
|
function replace(form, config) {
|
|
512
681
|
const internalFormStore = form[INTERNAL];
|
|
513
682
|
const internalArrayStore = getFieldStore(internalFormStore, config.path);
|
|
@@ -563,9 +732,21 @@ function setInput(form, config) {
|
|
|
563
732
|
validateIfRequired(internalFormStore, config.path ? getFieldStore(internalFormStore, config.path) : internalFormStore, "input");
|
|
564
733
|
});
|
|
565
734
|
}
|
|
735
|
+
/**
|
|
736
|
+
* Programmatically requests form submission by calling the native
|
|
737
|
+
* `requestSubmit()` method on the underlying form element.
|
|
738
|
+
*
|
|
739
|
+
* @param form The form store to submit.
|
|
740
|
+
*/
|
|
566
741
|
function submit(form) {
|
|
567
742
|
form[INTERNAL].element?.requestSubmit();
|
|
568
743
|
}
|
|
744
|
+
/**
|
|
745
|
+
* Swaps two items in a field array by exchanging their positions.
|
|
746
|
+
*
|
|
747
|
+
* @param form The form store containing the field array.
|
|
748
|
+
* @param config The swap configuration specifying the path and indices to swap.
|
|
749
|
+
*/
|
|
569
750
|
function swap(form, config) {
|
|
570
751
|
const internalFormStore = form[INTERNAL];
|
|
571
752
|
const internalArrayStore = getFieldStore(internalFormStore, config.path);
|
|
@@ -582,25 +763,53 @@ function swap(form, config) {
|
|
|
582
763
|
validateIfRequired(internalFormStore, internalArrayStore, "input");
|
|
583
764
|
});
|
|
584
765
|
}
|
|
766
|
+
/**
|
|
767
|
+
* Validates the entire form input against its schema. Returns a safe parse result
|
|
768
|
+
* indicating success or failure with detailed issues. Optionally focuses the first
|
|
769
|
+
* field with validation errors.
|
|
770
|
+
*
|
|
771
|
+
* @param form The form store to validate.
|
|
772
|
+
* @param config The validate form configuration specifying focus behavior.
|
|
773
|
+
*
|
|
774
|
+
* @returns A promise resolving to the validation result.
|
|
775
|
+
*/
|
|
585
776
|
function validate(form, config) {
|
|
586
777
|
return validateFormInput(form[INTERNAL], config);
|
|
587
778
|
}
|
|
588
779
|
|
|
589
780
|
//#endregion
|
|
590
781
|
//#region src/hooks/usePathSignal/usePathSignal.ts
|
|
782
|
+
/**
|
|
783
|
+
* Compares two paths for equality.
|
|
784
|
+
*
|
|
785
|
+
* @param a The first path.
|
|
786
|
+
* @param b The second path.
|
|
787
|
+
*
|
|
788
|
+
* @returns Whether the paths are equal.
|
|
789
|
+
*/
|
|
790
|
+
/* @__NO_SIDE_EFFECTS__ */
|
|
591
791
|
function isEqual(a, b) {
|
|
592
792
|
if (a.length !== b.length) return false;
|
|
593
793
|
for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;
|
|
594
794
|
return true;
|
|
595
795
|
}
|
|
796
|
+
/**
|
|
797
|
+
* Creates a readonly signal for a path that updates when the path changes.
|
|
798
|
+
*
|
|
799
|
+
* @param path The path value.
|
|
800
|
+
*
|
|
801
|
+
* @returns A readonly signal containing the path.
|
|
802
|
+
*/
|
|
803
|
+
/* @__NO_SIDE_EFFECTS__ */
|
|
596
804
|
function usePathSignal(path) {
|
|
597
805
|
const signal = useSignal(path);
|
|
598
|
-
if (
|
|
806
|
+
if (!/* @__PURE__ */ isEqual(signal.value, path)) signal.value = path;
|
|
599
807
|
return signal;
|
|
600
808
|
}
|
|
601
809
|
|
|
602
810
|
//#endregion
|
|
603
811
|
//#region src/hooks/useField/useField.ts
|
|
812
|
+
/* @__NO_SIDE_EFFECTS__ */
|
|
604
813
|
function useField(form, config) {
|
|
605
814
|
const pathSignal = usePathSignal(config.path);
|
|
606
815
|
const internalFormStore = form[INTERNAL];
|
|
@@ -645,6 +854,7 @@ function useField(form, config) {
|
|
|
645
854
|
|
|
646
855
|
//#endregion
|
|
647
856
|
//#region src/hooks/useFieldArray/useFieldArray.ts
|
|
857
|
+
/* @__NO_SIDE_EFFECTS__ */
|
|
648
858
|
function useFieldArray(form, config) {
|
|
649
859
|
const pathSignal = usePathSignal(config.path);
|
|
650
860
|
const internalFieldStore = useComputed(() => getFieldStore(form[INTERNAL], pathSignal.value));
|
|
@@ -660,6 +870,7 @@ function useFieldArray(form, config) {
|
|
|
660
870
|
|
|
661
871
|
//#endregion
|
|
662
872
|
//#region src/hooks/useForm/useForm.ts
|
|
873
|
+
/* @__NO_SIDE_EFFECTS__ */
|
|
663
874
|
function useForm(config) {
|
|
664
875
|
const form = useMemo(() => {
|
|
665
876
|
const internalFormStore = createFormStore(config, (input) => v.safeParseAsync(config.schema, input));
|
|
@@ -683,8 +894,15 @@ function useForm(config) {
|
|
|
683
894
|
//#endregion
|
|
684
895
|
//#region src/components/Field/Field.tsx
|
|
685
896
|
/**
|
|
686
|
-
* Headless form field that provides reactive properties and state.
|
|
897
|
+
* Headless form field component that provides reactive properties and state.
|
|
898
|
+
* The field component takes a form store, path to field, and a render function
|
|
899
|
+
* that receives a field store to display field state and handle user interactions.
|
|
900
|
+
*
|
|
901
|
+
* @param props The field component props.
|
|
902
|
+
*
|
|
903
|
+
* @returns The UI of the field to be rendered.
|
|
687
904
|
*/
|
|
905
|
+
/* @__NO_SIDE_EFFECTS__ */
|
|
688
906
|
function Field({ of, path, children }) {
|
|
689
907
|
const field = useField(of, { path });
|
|
690
908
|
return children(field);
|
|
@@ -693,8 +911,16 @@ function Field({ of, path, children }) {
|
|
|
693
911
|
//#endregion
|
|
694
912
|
//#region src/components/FieldArray/FieldArray.tsx
|
|
695
913
|
/**
|
|
696
|
-
* Headless field array that provides reactive properties and state.
|
|
914
|
+
* Headless field array component that provides reactive properties and state.
|
|
915
|
+
* The field array component takes a form store, path to array field, and a render
|
|
916
|
+
* function that receives a field array store to manage array items and handle
|
|
917
|
+
* array operations.
|
|
918
|
+
*
|
|
919
|
+
* @param props The field array component props.
|
|
920
|
+
*
|
|
921
|
+
* @returns The UI of the field array to be rendered.
|
|
697
922
|
*/
|
|
923
|
+
/* @__NO_SIDE_EFFECTS__ */
|
|
698
924
|
function FieldArray({ of, path, children }) {
|
|
699
925
|
const field = useFieldArray(of, { path });
|
|
700
926
|
return children(field);
|
|
@@ -702,6 +928,7 @@ function FieldArray({ of, path, children }) {
|
|
|
702
928
|
|
|
703
929
|
//#endregion
|
|
704
930
|
//#region src/components/Form/Form.tsx
|
|
931
|
+
/* @__NO_SIDE_EFFECTS__ */
|
|
705
932
|
function Form({ of, onSubmit,...other }) {
|
|
706
933
|
return /* @__PURE__ */ jsx("form", {
|
|
707
934
|
...other,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@formisch/preact",
|
|
3
3
|
"description": "The modular and type-safe form library for Preact",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.6.1",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Fabian Hiller",
|
|
7
7
|
"homepage": "https://formisch.dev",
|
|
@@ -43,8 +43,8 @@
|
|
|
43
43
|
"typescript": "^5.8.3",
|
|
44
44
|
"typescript-eslint": "^8.37.0",
|
|
45
45
|
"vite": "^6.0.4",
|
|
46
|
-
"@formisch/core": "0.4.
|
|
47
|
-
"@formisch/methods": "0.
|
|
46
|
+
"@formisch/core": "0.4.1",
|
|
47
|
+
"@formisch/methods": "0.4.1"
|
|
48
48
|
},
|
|
49
49
|
"peerDependencies": {
|
|
50
50
|
"@preact/signals": "^2.0.0",
|