@formisch/qwik 0.11.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/dist/index.d.ts +18 -12
- package/dist/index.qwik.js +117 -74
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -74,6 +74,17 @@ interface InternalBaseStore {
|
|
|
74
74
|
*/
|
|
75
75
|
schema: NoSerialize<Schema>;
|
|
76
76
|
/**
|
|
77
|
+
* The initial elements of the field.
|
|
78
|
+
*
|
|
79
|
+
* Hint: This may look unused, but do not remove it. `copyItemState` and
|
|
80
|
+
* `swapItemState` move the `elements` reference between field stores when
|
|
81
|
+
* array items are inserted, moved, removed or swapped, and `reset` restores
|
|
82
|
+
* each field's original element via `elements = initialElements`. Without it,
|
|
83
|
+
* focus and file reset target the wrong element after a reorder followed by a
|
|
84
|
+
* reset.
|
|
85
|
+
*/
|
|
86
|
+
initialElements: FieldElement[];
|
|
87
|
+
/**
|
|
77
88
|
* The elements of the field.
|
|
78
89
|
*/
|
|
79
90
|
elements: FieldElement[];
|
|
@@ -202,14 +213,6 @@ interface InternalValueStore extends InternalBaseStore {
|
|
|
202
213
|
* The input of the value field.
|
|
203
214
|
*/
|
|
204
215
|
input: Signal<unknown>;
|
|
205
|
-
/**
|
|
206
|
-
* The touched state of the field.
|
|
207
|
-
*/
|
|
208
|
-
isTouched: Signal<boolean>;
|
|
209
|
-
/**
|
|
210
|
-
* The dirty state of the field.
|
|
211
|
-
*/
|
|
212
|
-
isDirty: Signal<boolean>;
|
|
213
216
|
}
|
|
214
217
|
/**
|
|
215
218
|
* Internal field store type.
|
|
@@ -402,7 +405,10 @@ type ExactRequired<TValue> = TValue extends Record<PropertyKey, unknown> ? IsExa
|
|
|
402
405
|
*/
|
|
403
406
|
type PathValue<TValue, TPath extends Path> = TPath extends readonly [infer TKey, ...infer TRest extends Path] ? TKey extends ExactKeysOf<ExactRequired<TValue>> ? PathValue<PropertiesOf<ExactRequired<TValue>>[TKey], TRest> : unknown : TValue;
|
|
404
407
|
/**
|
|
405
|
-
* Checks whether a value is
|
|
408
|
+
* Checks whether a value is a dynamic array or contains one anywhere in its
|
|
409
|
+
* shape. A fixed-length tuple is not itself a dynamic array, but it counts when
|
|
410
|
+
* it contains one, so paths can still navigate through tuples to reach nested
|
|
411
|
+
* arrays.
|
|
406
412
|
*
|
|
407
413
|
* Hint: The inner conditionals (`TValue extends readonly unknown[]` and
|
|
408
414
|
* `TValue extends Record<PropertyKey, unknown>`) distribute over union members,
|
|
@@ -413,7 +419,7 @@ type PathValue<TValue, TPath extends Path> = TPath extends readonly [infer TKey,
|
|
|
413
419
|
* `true extends ...`, which is `true` whenever at least one union member
|
|
414
420
|
* contributed `true`.
|
|
415
421
|
*/
|
|
416
|
-
type IsOrHasArray<TValue> = true extends (IsAny<TValue> extends true ? false : TValue extends readonly unknown[] ? true : TValue extends Record<PropertyKey, unknown> ? { [TKey in keyof TValue]: IsOrHasArray<TValue[TKey]> }[keyof TValue] : false) ? true : false;
|
|
422
|
+
type IsOrHasArray<TValue> = true extends (IsAny<TValue> extends true ? false : TValue extends readonly unknown[] ? number extends TValue["length"] ? true : IsOrHasArray<TValue[number]> : TValue extends Record<PropertyKey, unknown> ? { [TKey in keyof TValue]: IsOrHasArray<TValue[TKey]> }[keyof TValue] : false) ? true : false;
|
|
417
423
|
/**
|
|
418
424
|
* Extracts the exact keys of a tuple, array or object that contain arrays.
|
|
419
425
|
*/
|
|
@@ -428,7 +434,7 @@ type PropertiesOfArrayPath<TValue> = { [TKey in ExactKeysOfArrayPath<TValue>]: T
|
|
|
428
434
|
/**
|
|
429
435
|
* Lazily evaluates only the first valid array path segment based on the given value.
|
|
430
436
|
*/
|
|
431
|
-
type LazyArrayPath<TValue, TPathToCheck extends Path, TValidPath extends Path = readonly []> = TPathToCheck extends readonly [] ? TValue extends readonly unknown[] ? TValidPath : readonly [...TValidPath, ExactKeysOfArrayPath<TValue>] : TPathToCheck extends readonly [infer TFirstKey extends ExactKeysOfArrayPath<TValue>, ...infer TPathRest extends Path] ? LazyArrayPath<Required<PropertiesOfArrayPath<TValue>[TFirstKey]>, TPathRest, readonly [...TValidPath, TFirstKey]> : IsNever<ExactKeysOfArrayPath<TValue>> extends false ? readonly [...TValidPath, ExactKeysOfArrayPath<TValue>] : never;
|
|
437
|
+
type LazyArrayPath<TValue, TPathToCheck extends Path, TValidPath extends Path = readonly []> = TPathToCheck extends readonly [] ? TValue extends readonly unknown[] ? number extends TValue["length"] ? TValidPath : IsNever<ExactKeysOfArrayPath<TValue>> extends false ? readonly [...TValidPath, ExactKeysOfArrayPath<TValue>] : never : readonly [...TValidPath, ExactKeysOfArrayPath<TValue>] : TPathToCheck extends readonly [infer TFirstKey extends ExactKeysOfArrayPath<TValue>, ...infer TPathRest extends Path] ? LazyArrayPath<Required<PropertiesOfArrayPath<TValue>[TFirstKey]>, TPathRest, readonly [...TValidPath, TFirstKey]> : IsNever<ExactKeysOfArrayPath<TValue>> extends false ? readonly [...TValidPath, ExactKeysOfArrayPath<TValue>] : never;
|
|
432
438
|
/**
|
|
433
439
|
* Returns the path if valid, otherwise the first possible valid array path
|
|
434
440
|
* based on the given value.
|
|
@@ -481,7 +487,7 @@ interface FocusFieldConfig<TSchema extends FormSchema, TFieldPath extends Requir
|
|
|
481
487
|
readonly path: ValidPath<v.InferInput<TSchema>, TFieldPath>;
|
|
482
488
|
}
|
|
483
489
|
/**
|
|
484
|
-
* Focuses the first input element of a field. This is useful for
|
|
490
|
+
* Focuses the first focusable input element of a field. This is useful for
|
|
485
491
|
* programmatically setting focus to a specific field, such as after
|
|
486
492
|
* validation errors or user interactions.
|
|
487
493
|
*
|
package/dist/index.qwik.js
CHANGED
|
@@ -152,31 +152,45 @@ function copyItemState(fromInternalFieldStore, toInternalFieldStore) {
|
|
|
152
152
|
* form reset functionality.
|
|
153
153
|
*
|
|
154
154
|
* @param internalFieldStore The field store to reset.
|
|
155
|
-
* @param
|
|
155
|
+
* @param input The new input value (can be any type including array or object).
|
|
156
|
+
* @param keepStart Whether to keep `startInput` and `startItems` as the dirty
|
|
157
|
+
* baseline instead of resetting them to the new input. Used when a field store
|
|
158
|
+
* is reused for an in-place edit so its dirty state is detected correctly.
|
|
156
159
|
*/
|
|
157
|
-
function resetItemState(internalFieldStore,
|
|
160
|
+
function resetItemState(internalFieldStore, input, keepStart = false) {
|
|
158
161
|
batch(() => {
|
|
159
|
-
|
|
162
|
+
const elements = [];
|
|
163
|
+
if (internalFieldStore.elements === internalFieldStore.initialElements) internalFieldStore.initialElements = elements;
|
|
164
|
+
internalFieldStore.elements = elements;
|
|
160
165
|
internalFieldStore.errors.value = null;
|
|
161
166
|
internalFieldStore.isTouched.value = false;
|
|
162
167
|
internalFieldStore.isDirty.value = false;
|
|
163
168
|
if (internalFieldStore.kind === "array" || internalFieldStore.kind === "object") {
|
|
164
|
-
const objectInput =
|
|
165
|
-
internalFieldStore.startInput.value = objectInput;
|
|
169
|
+
const objectInput = input == null ? input : true;
|
|
170
|
+
if (!keepStart) internalFieldStore.startInput.value = objectInput;
|
|
166
171
|
internalFieldStore.input.value = objectInput;
|
|
167
|
-
if (internalFieldStore.kind === "array") if (
|
|
168
|
-
const
|
|
169
|
-
|
|
172
|
+
if (internalFieldStore.kind === "array") if (input) {
|
|
173
|
+
const length = internalFieldStore.schema.type === "array" ? input.length : internalFieldStore.children.length;
|
|
174
|
+
const newItems = Array.from({ length }, createId);
|
|
175
|
+
if (!keepStart) internalFieldStore.startItems.value = newItems;
|
|
170
176
|
internalFieldStore.items.value = newItems;
|
|
171
|
-
|
|
177
|
+
let path;
|
|
178
|
+
for (let index = 0; index < length; index++) if (internalFieldStore.children[index]) resetItemState(internalFieldStore.children[index], input[index], keepStart);
|
|
179
|
+
else {
|
|
180
|
+
path ??= JSON.parse(internalFieldStore.name);
|
|
181
|
+
internalFieldStore.children[index] = {};
|
|
182
|
+
path.push(index);
|
|
183
|
+
initializeFieldStore(internalFieldStore.children[index], internalFieldStore.schema.item, input[index], path);
|
|
184
|
+
path.pop();
|
|
185
|
+
}
|
|
172
186
|
} else {
|
|
173
|
-
internalFieldStore.startItems.value = [];
|
|
187
|
+
if (!keepStart) internalFieldStore.startItems.value = [];
|
|
174
188
|
internalFieldStore.items.value = [];
|
|
175
189
|
}
|
|
176
|
-
else for (const key in internalFieldStore.children) resetItemState(internalFieldStore.children[key],
|
|
190
|
+
else for (const key in internalFieldStore.children) resetItemState(internalFieldStore.children[key], input?.[key], keepStart);
|
|
177
191
|
} else {
|
|
178
|
-
internalFieldStore.startInput.value =
|
|
179
|
-
internalFieldStore.input.value =
|
|
192
|
+
if (!keepStart) internalFieldStore.startInput.value = input;
|
|
193
|
+
internalFieldStore.input.value = input;
|
|
180
194
|
}
|
|
181
195
|
});
|
|
182
196
|
}
|
|
@@ -243,6 +257,28 @@ function swapItemState(firstInternalFieldStore, secondInternalFieldStore) {
|
|
|
243
257
|
});
|
|
244
258
|
}
|
|
245
259
|
/**
|
|
260
|
+
* Focuses the first focusable element of a field store. The elements are tried
|
|
261
|
+
* in order and the first one that actually receives focus wins, so detached,
|
|
262
|
+
* disabled or hidden elements are skipped. The browser decides focusability,
|
|
263
|
+
* which is read back via the element's root `activeElement` so elements in a
|
|
264
|
+
* shadow root or another document are handled correctly.
|
|
265
|
+
*
|
|
266
|
+
* Hint: A `display: none` or `hidden` element is correctly skipped in real
|
|
267
|
+
* browsers, but jsdom has no layout and focuses it anyway, so that case cannot
|
|
268
|
+
* be covered by unit tests.
|
|
269
|
+
*
|
|
270
|
+
* @param internalFieldStore The field store to focus.
|
|
271
|
+
*
|
|
272
|
+
* @returns Whether an element was focused.
|
|
273
|
+
*/
|
|
274
|
+
function focusFieldElement(internalFieldStore) {
|
|
275
|
+
for (const element of internalFieldStore.elements) {
|
|
276
|
+
element.focus();
|
|
277
|
+
if (element.getRootNode().activeElement === element) return true;
|
|
278
|
+
}
|
|
279
|
+
return false;
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
246
282
|
* Returns whether the specified boolean property is true for the field store
|
|
247
283
|
* or any of its nested children. Recursively checks arrays and objects.
|
|
248
284
|
*
|
|
@@ -380,11 +416,9 @@ function getFieldStore(internalFormStore, path) {
|
|
|
380
416
|
*/
|
|
381
417
|
function setFieldBool(internalFieldStore, type, bool) {
|
|
382
418
|
batch(() => {
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
} else if (internalFieldStore.kind == "object") for (const key in internalFieldStore.children) setFieldBool(internalFieldStore.children[key], type, bool);
|
|
387
|
-
else internalFieldStore[type].value = bool;
|
|
419
|
+
internalFieldStore[type].value = bool;
|
|
420
|
+
if (internalFieldStore.kind === "array") for (let index = 0; index < untrack(() => internalFieldStore.items.value).length; index++) setFieldBool(internalFieldStore.children[index], type, bool);
|
|
421
|
+
else if (internalFieldStore.kind === "object") for (const key in internalFieldStore.children) setFieldBool(internalFieldStore.children[key], type, bool);
|
|
388
422
|
});
|
|
389
423
|
}
|
|
390
424
|
/**
|
|
@@ -399,20 +433,20 @@ function setNestedInput(internalFieldStore, input) {
|
|
|
399
433
|
if (internalFieldStore.kind === "array") {
|
|
400
434
|
const arrayInput = input ?? [];
|
|
401
435
|
const items = internalFieldStore.items.value;
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
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
|
+
const path = JSON.parse(internalFieldStore.name);
|
|
440
|
+
for (let index = items.length; index < length; index++) if (internalFieldStore.children[index]) resetItemState(internalFieldStore.children[index], arrayInput[index], true);
|
|
441
|
+
else {
|
|
442
|
+
internalFieldStore.children[index] = {};
|
|
443
|
+
path.push(index);
|
|
444
|
+
initializeFieldStore(internalFieldStore.children[index], internalFieldStore.schema.item, arrayInput[index], path);
|
|
445
|
+
path.pop();
|
|
412
446
|
}
|
|
413
|
-
internalFieldStore.items.value = [...items, ...
|
|
447
|
+
internalFieldStore.items.value = [...items, ...Array.from({ length: length - items.length }, createId)];
|
|
414
448
|
}
|
|
415
|
-
for (let index = 0; index <
|
|
449
|
+
for (let index = 0; index < length; index++) setNestedInput(internalFieldStore.children[index], arrayInput[index]);
|
|
416
450
|
internalFieldStore.input.value = input == null ? input : true;
|
|
417
451
|
internalFieldStore.isDirty.value = internalFieldStore.startInput.value !== internalFieldStore.input.value || internalFieldStore.startItems.value.length !== internalFieldStore.items.value.length;
|
|
418
452
|
} else if (internalFieldStore.kind === "object") {
|
|
@@ -456,21 +490,22 @@ function setFieldInput(internalFormStore, path, input) {
|
|
|
456
490
|
function setInitialFieldInput(internalFieldStore, initialInput) {
|
|
457
491
|
batch(() => {
|
|
458
492
|
if (internalFieldStore.kind === "array") {
|
|
459
|
-
internalFieldStore.
|
|
493
|
+
internalFieldStore.initialInput.value = initialInput == null ? initialInput : true;
|
|
460
494
|
const initialArrayInput = initialInput ?? [];
|
|
461
|
-
|
|
495
|
+
const length = internalFieldStore.schema.type === "array" ? initialArrayInput.length : internalFieldStore.children.length;
|
|
496
|
+
if (length > internalFieldStore.children.length) {
|
|
462
497
|
const path = JSON.parse(internalFieldStore.name);
|
|
463
|
-
for (let index = internalFieldStore.children.length; index <
|
|
498
|
+
for (let index = internalFieldStore.children.length; index < length; index++) {
|
|
464
499
|
internalFieldStore.children[index] = {};
|
|
465
500
|
path.push(index);
|
|
466
501
|
initializeFieldStore(internalFieldStore.children[index], internalFieldStore.schema.item, initialArrayInput[index], path);
|
|
467
502
|
path.pop();
|
|
468
503
|
}
|
|
469
504
|
}
|
|
470
|
-
internalFieldStore.initialItems.value =
|
|
505
|
+
internalFieldStore.initialItems.value = Array.from({ length }, createId);
|
|
471
506
|
for (let index = 0; index < internalFieldStore.children.length; index++) setInitialFieldInput(internalFieldStore.children[index], initialArrayInput[index]);
|
|
472
507
|
} else if (internalFieldStore.kind === "object") {
|
|
473
|
-
internalFieldStore.
|
|
508
|
+
internalFieldStore.initialInput.value = initialInput == null ? initialInput : true;
|
|
474
509
|
for (const key in internalFieldStore.children) setInitialFieldInput(internalFieldStore.children[key], initialInput?.[key]);
|
|
475
510
|
} else internalFieldStore.initialInput.value = initialInput;
|
|
476
511
|
});
|
|
@@ -522,44 +557,49 @@ function createFormStore(config, parse) {
|
|
|
522
557
|
async function validateFormInput(internalFormStore, config) {
|
|
523
558
|
internalFormStore.validators++;
|
|
524
559
|
internalFormStore.isValidating.value = true;
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
const path
|
|
532
|
-
|
|
533
|
-
const
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
const name = JSON.stringify(path);
|
|
540
|
-
const fieldErrors = nestedErrors[name];
|
|
541
|
-
if (fieldErrors) fieldErrors.push(issue.message);
|
|
542
|
-
else nestedErrors[name] = [issue.message];
|
|
543
|
-
} else if (rootErrors) rootErrors.push(issue.message);
|
|
544
|
-
else rootErrors = [issue.message];
|
|
545
|
-
}
|
|
546
|
-
let shouldFocus = config?.shouldFocus ?? false;
|
|
547
|
-
batch(() => {
|
|
548
|
-
walkFieldStore(internalFormStore, (internalFieldStore) => {
|
|
549
|
-
if (internalFieldStore.name === "[]") internalFieldStore.errors.value = rootErrors ?? null;
|
|
550
|
-
else {
|
|
551
|
-
const fieldErrors = nestedErrors?.[internalFieldStore.name] ?? null;
|
|
552
|
-
internalFieldStore.errors.value = fieldErrors;
|
|
553
|
-
if (shouldFocus && fieldErrors) {
|
|
554
|
-
internalFieldStore.elements[0]?.focus();
|
|
555
|
-
shouldFocus = false;
|
|
560
|
+
try {
|
|
561
|
+
const result = await internalFormStore.parse(untrack(() => /* @__PURE__ */ getFieldInput(internalFormStore)));
|
|
562
|
+
let rootErrors;
|
|
563
|
+
let nestedErrors;
|
|
564
|
+
if (result.issues) {
|
|
565
|
+
nestedErrors = {};
|
|
566
|
+
for (const issue of result.issues) if (issue.path) {
|
|
567
|
+
const path = [];
|
|
568
|
+
for (const pathItem of issue.path) {
|
|
569
|
+
const key = pathItem.key;
|
|
570
|
+
const keyType = typeof key;
|
|
571
|
+
const itemType = pathItem.type;
|
|
572
|
+
if (keyType !== "string" && keyType !== "number" || itemType === "map" || itemType === "set") break;
|
|
573
|
+
path.push(key);
|
|
556
574
|
}
|
|
557
|
-
|
|
575
|
+
const name = JSON.stringify(path);
|
|
576
|
+
const fieldErrors = nestedErrors[name];
|
|
577
|
+
if (fieldErrors) fieldErrors.push(issue.message);
|
|
578
|
+
else nestedErrors[name] = [issue.message];
|
|
579
|
+
} else if (rootErrors) rootErrors.push(issue.message);
|
|
580
|
+
else rootErrors = [issue.message];
|
|
581
|
+
}
|
|
582
|
+
let shouldFocus = config?.shouldFocus ?? false;
|
|
583
|
+
batch(() => {
|
|
584
|
+
walkFieldStore(internalFormStore, (internalFieldStore) => {
|
|
585
|
+
if (internalFieldStore.name === "[]") internalFieldStore.errors.value = rootErrors ?? null;
|
|
586
|
+
else {
|
|
587
|
+
const fieldErrors = nestedErrors?.[internalFieldStore.name] ?? null;
|
|
588
|
+
internalFieldStore.errors.value = fieldErrors;
|
|
589
|
+
if (shouldFocus && fieldErrors && focusFieldElement(internalFieldStore)) shouldFocus = false;
|
|
590
|
+
}
|
|
591
|
+
});
|
|
592
|
+
internalFormStore.validators--;
|
|
593
|
+
internalFormStore.isValidating.value = internalFormStore.validators > 0;
|
|
558
594
|
});
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
595
|
+
return result;
|
|
596
|
+
} catch (error) {
|
|
597
|
+
batch(() => {
|
|
598
|
+
internalFormStore.validators--;
|
|
599
|
+
internalFormStore.isValidating.value = internalFormStore.validators > 0;
|
|
600
|
+
});
|
|
601
|
+
throw error;
|
|
602
|
+
}
|
|
563
603
|
}
|
|
564
604
|
/**
|
|
565
605
|
* Validates the form input if required based on the validation mode and form
|
|
@@ -581,7 +621,7 @@ const INTERNAL = "~internal";
|
|
|
581
621
|
//#endregion
|
|
582
622
|
//#region ../../packages/methods/dist/index.qwik.js
|
|
583
623
|
/**
|
|
584
|
-
* Focuses the first input element of a field. This is useful for
|
|
624
|
+
* Focuses the first focusable input element of a field. This is useful for
|
|
585
625
|
* programmatically setting focus to a specific field, such as after
|
|
586
626
|
* validation errors or user interactions.
|
|
587
627
|
*
|
|
@@ -589,7 +629,7 @@ const INTERNAL = "~internal";
|
|
|
589
629
|
* @param config The focus field configuration.
|
|
590
630
|
*/
|
|
591
631
|
function focus(form, config) {
|
|
592
|
-
getFieldStore(form[INTERNAL], config.path)
|
|
632
|
+
focusFieldElement(getFieldStore(form[INTERNAL], config.path));
|
|
593
633
|
}
|
|
594
634
|
/**
|
|
595
635
|
* Retrieves all error messages from all fields in the form by walking through
|
|
@@ -917,7 +957,10 @@ function useField(form, config) {
|
|
|
917
957
|
useTask$(({ track, cleanup }) => {
|
|
918
958
|
track(internalFieldStore);
|
|
919
959
|
cleanup(() => {
|
|
920
|
-
|
|
960
|
+
const internalFieldStoreValue = internalFieldStore.value;
|
|
961
|
+
const elements = internalFieldStoreValue.elements.filter((element) => element.isConnected);
|
|
962
|
+
if (internalFieldStoreValue.elements === internalFieldStoreValue.initialElements) internalFieldStoreValue.initialElements = elements;
|
|
963
|
+
internalFieldStoreValue.elements = elements;
|
|
921
964
|
});
|
|
922
965
|
});
|
|
923
966
|
return useConstant(() => ({
|
package/package.json
CHANGED