@formisch/preact 0.10.0 → 0.11.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 +1 -0
- package/dist/index.d.ts +14 -4
- package/dist/index.js +117 -74
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -9,6 +9,7 @@ Formisch is also available for [Qwik][formisch-qwik], [React][formisch-react], [
|
|
|
9
9
|
- Small bundle size starting at 2.5 kB
|
|
10
10
|
- Schema-based validation with Valibot
|
|
11
11
|
- Type safety with autocompletion in editor
|
|
12
|
+
- Open source and fully tested with 100 % coverage
|
|
12
13
|
- It's fast – DOM updates are fine-grained
|
|
13
14
|
- Minimal, readable and well thought out API
|
|
14
15
|
- Supports all native HTML form fields
|
package/dist/index.d.ts
CHANGED
|
@@ -64,6 +64,13 @@ interface InternalBaseStore {
|
|
|
64
64
|
schema: Schema;
|
|
65
65
|
/**
|
|
66
66
|
* The initial elements of the field.
|
|
67
|
+
*
|
|
68
|
+
* Hint: This may look unused, but do not remove it. `copyItemState` and
|
|
69
|
+
* `swapItemState` move the `elements` reference between field stores when
|
|
70
|
+
* array items are inserted, moved, removed or swapped, and `reset` restores
|
|
71
|
+
* each field's original element via `elements = initialElements`. Without it,
|
|
72
|
+
* focus and file reset target the wrong element after a reorder followed by a
|
|
73
|
+
* reset.
|
|
67
74
|
*/
|
|
68
75
|
initialElements: FieldElement[];
|
|
69
76
|
/**
|
|
@@ -385,7 +392,10 @@ type ExactRequired<TValue> = TValue extends Record<PropertyKey, unknown> ? IsExa
|
|
|
385
392
|
*/
|
|
386
393
|
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;
|
|
387
394
|
/**
|
|
388
|
-
* Checks whether a value is
|
|
395
|
+
* Checks whether a value is a dynamic array or contains one anywhere in its
|
|
396
|
+
* shape. A fixed-length tuple is not itself a dynamic array, but it counts when
|
|
397
|
+
* it contains one, so paths can still navigate through tuples to reach nested
|
|
398
|
+
* arrays.
|
|
389
399
|
*
|
|
390
400
|
* Hint: The inner conditionals (`TValue extends readonly unknown[]` and
|
|
391
401
|
* `TValue extends Record<PropertyKey, unknown>`) distribute over union members,
|
|
@@ -396,7 +406,7 @@ type PathValue<TValue, TPath extends Path> = TPath extends readonly [infer TKey,
|
|
|
396
406
|
* `true extends ...`, which is `true` whenever at least one union member
|
|
397
407
|
* contributed `true`.
|
|
398
408
|
*/
|
|
399
|
-
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;
|
|
409
|
+
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;
|
|
400
410
|
/**
|
|
401
411
|
* Extracts the exact keys of a tuple, array or object that contain arrays.
|
|
402
412
|
*/
|
|
@@ -411,7 +421,7 @@ type PropertiesOfArrayPath<TValue> = { [TKey in ExactKeysOfArrayPath<TValue>]: T
|
|
|
411
421
|
/**
|
|
412
422
|
* Lazily evaluates only the first valid array path segment based on the given value.
|
|
413
423
|
*/
|
|
414
|
-
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;
|
|
424
|
+
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;
|
|
415
425
|
/**
|
|
416
426
|
* Returns the path if valid, otherwise the first possible valid array path
|
|
417
427
|
* based on the given value.
|
|
@@ -464,7 +474,7 @@ interface FocusFieldConfig<TSchema extends FormSchema, TFieldPath extends Requir
|
|
|
464
474
|
readonly path: ValidPath<v.InferInput<TSchema>, TFieldPath>;
|
|
465
475
|
}
|
|
466
476
|
/**
|
|
467
|
-
* Focuses the first input element of a field. This is useful for
|
|
477
|
+
* Focuses the first focusable input element of a field. This is useful for
|
|
468
478
|
* programmatically setting focus to a specific field, such as after
|
|
469
479
|
* validation errors or user interactions.
|
|
470
480
|
*
|
package/dist/index.js
CHANGED
|
@@ -142,31 +142,45 @@ function copyItemState(fromInternalFieldStore, toInternalFieldStore) {
|
|
|
142
142
|
* form reset functionality.
|
|
143
143
|
*
|
|
144
144
|
* @param internalFieldStore The field store to reset.
|
|
145
|
-
* @param
|
|
145
|
+
* @param input The new input value (can be any type including array or object).
|
|
146
|
+
* @param keepStart Whether to keep `startInput` and `startItems` as the dirty
|
|
147
|
+
* baseline instead of resetting them to the new input. Used when a field store
|
|
148
|
+
* is reused for an in-place edit so its dirty state is detected correctly.
|
|
146
149
|
*/
|
|
147
|
-
function resetItemState(internalFieldStore,
|
|
150
|
+
function resetItemState(internalFieldStore, input, keepStart = false) {
|
|
148
151
|
batch(() => {
|
|
149
|
-
|
|
152
|
+
const elements = [];
|
|
153
|
+
if (internalFieldStore.elements === internalFieldStore.initialElements) internalFieldStore.initialElements = elements;
|
|
154
|
+
internalFieldStore.elements = elements;
|
|
150
155
|
internalFieldStore.errors.value = null;
|
|
151
156
|
internalFieldStore.isTouched.value = false;
|
|
152
157
|
internalFieldStore.isDirty.value = false;
|
|
153
158
|
if (internalFieldStore.kind === "array" || internalFieldStore.kind === "object") {
|
|
154
|
-
const objectInput =
|
|
155
|
-
internalFieldStore.startInput.value = objectInput;
|
|
159
|
+
const objectInput = input == null ? input : true;
|
|
160
|
+
if (!keepStart) internalFieldStore.startInput.value = objectInput;
|
|
156
161
|
internalFieldStore.input.value = objectInput;
|
|
157
|
-
if (internalFieldStore.kind === "array") if (
|
|
158
|
-
const
|
|
159
|
-
|
|
162
|
+
if (internalFieldStore.kind === "array") if (input) {
|
|
163
|
+
const length = internalFieldStore.schema.type === "array" ? input.length : internalFieldStore.children.length;
|
|
164
|
+
const newItems = Array.from({ length }, createId);
|
|
165
|
+
if (!keepStart) internalFieldStore.startItems.value = newItems;
|
|
160
166
|
internalFieldStore.items.value = newItems;
|
|
161
|
-
|
|
167
|
+
let path;
|
|
168
|
+
for (let index = 0; index < length; index++) if (internalFieldStore.children[index]) resetItemState(internalFieldStore.children[index], input[index], keepStart);
|
|
169
|
+
else {
|
|
170
|
+
path ??= JSON.parse(internalFieldStore.name);
|
|
171
|
+
internalFieldStore.children[index] = {};
|
|
172
|
+
path.push(index);
|
|
173
|
+
initializeFieldStore(internalFieldStore.children[index], internalFieldStore.schema.item, input[index], path);
|
|
174
|
+
path.pop();
|
|
175
|
+
}
|
|
162
176
|
} else {
|
|
163
|
-
internalFieldStore.startItems.value = [];
|
|
177
|
+
if (!keepStart) internalFieldStore.startItems.value = [];
|
|
164
178
|
internalFieldStore.items.value = [];
|
|
165
179
|
}
|
|
166
|
-
else for (const key in internalFieldStore.children) resetItemState(internalFieldStore.children[key],
|
|
180
|
+
else for (const key in internalFieldStore.children) resetItemState(internalFieldStore.children[key], input?.[key], keepStart);
|
|
167
181
|
} else {
|
|
168
|
-
internalFieldStore.startInput.value =
|
|
169
|
-
internalFieldStore.input.value =
|
|
182
|
+
if (!keepStart) internalFieldStore.startInput.value = input;
|
|
183
|
+
internalFieldStore.input.value = input;
|
|
170
184
|
}
|
|
171
185
|
});
|
|
172
186
|
}
|
|
@@ -233,6 +247,28 @@ function swapItemState(firstInternalFieldStore, secondInternalFieldStore) {
|
|
|
233
247
|
});
|
|
234
248
|
}
|
|
235
249
|
/**
|
|
250
|
+
* Focuses the first focusable element of a field store. The elements are tried
|
|
251
|
+
* in order and the first one that actually receives focus wins, so detached,
|
|
252
|
+
* disabled or hidden elements are skipped. The browser decides focusability,
|
|
253
|
+
* which is read back via the element's root `activeElement` so elements in a
|
|
254
|
+
* shadow root or another document are handled correctly.
|
|
255
|
+
*
|
|
256
|
+
* Hint: A `display: none` or `hidden` element is correctly skipped in real
|
|
257
|
+
* browsers, but jsdom has no layout and focuses it anyway, so that case cannot
|
|
258
|
+
* be covered by unit tests.
|
|
259
|
+
*
|
|
260
|
+
* @param internalFieldStore The field store to focus.
|
|
261
|
+
*
|
|
262
|
+
* @returns Whether an element was focused.
|
|
263
|
+
*/
|
|
264
|
+
function focusFieldElement(internalFieldStore) {
|
|
265
|
+
for (const element of internalFieldStore.elements) {
|
|
266
|
+
element.focus();
|
|
267
|
+
if (element.getRootNode().activeElement === element) return true;
|
|
268
|
+
}
|
|
269
|
+
return false;
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
236
272
|
* Returns whether the specified boolean property is true for the field store
|
|
237
273
|
* or any of its nested children. Recursively checks arrays and objects.
|
|
238
274
|
*
|
|
@@ -370,11 +406,9 @@ function getFieldStore(internalFormStore, path) {
|
|
|
370
406
|
*/
|
|
371
407
|
function setFieldBool(internalFieldStore, type, bool) {
|
|
372
408
|
batch(() => {
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
} else if (internalFieldStore.kind == "object") for (const key in internalFieldStore.children) setFieldBool(internalFieldStore.children[key], type, bool);
|
|
377
|
-
else internalFieldStore[type].value = bool;
|
|
409
|
+
internalFieldStore[type].value = bool;
|
|
410
|
+
if (internalFieldStore.kind === "array") for (let index = 0; index < untrack(() => internalFieldStore.items.value).length; index++) setFieldBool(internalFieldStore.children[index], type, bool);
|
|
411
|
+
else if (internalFieldStore.kind === "object") for (const key in internalFieldStore.children) setFieldBool(internalFieldStore.children[key], type, bool);
|
|
378
412
|
});
|
|
379
413
|
}
|
|
380
414
|
/**
|
|
@@ -389,20 +423,20 @@ function setNestedInput(internalFieldStore, input) {
|
|
|
389
423
|
if (internalFieldStore.kind === "array") {
|
|
390
424
|
const arrayInput = input ?? [];
|
|
391
425
|
const items = internalFieldStore.items.value;
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
426
|
+
const length = internalFieldStore.schema.type === "array" ? arrayInput.length : internalFieldStore.children.length;
|
|
427
|
+
if (length < items.length) internalFieldStore.items.value = items.slice(0, length);
|
|
428
|
+
else if (length > items.length) {
|
|
429
|
+
const path = JSON.parse(internalFieldStore.name);
|
|
430
|
+
for (let index = items.length; index < length; index++) if (internalFieldStore.children[index]) resetItemState(internalFieldStore.children[index], arrayInput[index], true);
|
|
431
|
+
else {
|
|
432
|
+
internalFieldStore.children[index] = {};
|
|
433
|
+
path.push(index);
|
|
434
|
+
initializeFieldStore(internalFieldStore.children[index], internalFieldStore.schema.item, arrayInput[index], path);
|
|
435
|
+
path.pop();
|
|
402
436
|
}
|
|
403
|
-
internalFieldStore.items.value = [...items, ...
|
|
437
|
+
internalFieldStore.items.value = [...items, ...Array.from({ length: length - items.length }, createId)];
|
|
404
438
|
}
|
|
405
|
-
for (let index = 0; index <
|
|
439
|
+
for (let index = 0; index < length; index++) setNestedInput(internalFieldStore.children[index], arrayInput[index]);
|
|
406
440
|
internalFieldStore.input.value = input == null ? input : true;
|
|
407
441
|
internalFieldStore.isDirty.value = internalFieldStore.startInput.value !== internalFieldStore.input.value || internalFieldStore.startItems.value.length !== internalFieldStore.items.value.length;
|
|
408
442
|
} else if (internalFieldStore.kind === "object") {
|
|
@@ -446,21 +480,22 @@ function setFieldInput(internalFormStore, path, input) {
|
|
|
446
480
|
function setInitialFieldInput(internalFieldStore, initialInput) {
|
|
447
481
|
batch(() => {
|
|
448
482
|
if (internalFieldStore.kind === "array") {
|
|
449
|
-
internalFieldStore.
|
|
483
|
+
internalFieldStore.initialInput.value = initialInput == null ? initialInput : true;
|
|
450
484
|
const initialArrayInput = initialInput ?? [];
|
|
451
|
-
|
|
485
|
+
const length = internalFieldStore.schema.type === "array" ? initialArrayInput.length : internalFieldStore.children.length;
|
|
486
|
+
if (length > internalFieldStore.children.length) {
|
|
452
487
|
const path = JSON.parse(internalFieldStore.name);
|
|
453
|
-
for (let index = internalFieldStore.children.length; index <
|
|
488
|
+
for (let index = internalFieldStore.children.length; index < length; index++) {
|
|
454
489
|
internalFieldStore.children[index] = {};
|
|
455
490
|
path.push(index);
|
|
456
491
|
initializeFieldStore(internalFieldStore.children[index], internalFieldStore.schema.item, initialArrayInput[index], path);
|
|
457
492
|
path.pop();
|
|
458
493
|
}
|
|
459
494
|
}
|
|
460
|
-
internalFieldStore.initialItems.value =
|
|
495
|
+
internalFieldStore.initialItems.value = Array.from({ length }, createId);
|
|
461
496
|
for (let index = 0; index < internalFieldStore.children.length; index++) setInitialFieldInput(internalFieldStore.children[index], initialArrayInput[index]);
|
|
462
497
|
} else if (internalFieldStore.kind === "object") {
|
|
463
|
-
internalFieldStore.
|
|
498
|
+
internalFieldStore.initialInput.value = initialInput == null ? initialInput : true;
|
|
464
499
|
for (const key in internalFieldStore.children) setInitialFieldInput(internalFieldStore.children[key], initialInput?.[key]);
|
|
465
500
|
} else internalFieldStore.initialInput.value = initialInput;
|
|
466
501
|
});
|
|
@@ -512,44 +547,49 @@ function createFormStore(config, parse) {
|
|
|
512
547
|
async function validateFormInput(internalFormStore, config) {
|
|
513
548
|
internalFormStore.validators++;
|
|
514
549
|
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;
|
|
550
|
+
try {
|
|
551
|
+
const result = await internalFormStore.parse(untrack(() => /* @__PURE__ */ getFieldInput(internalFormStore)));
|
|
552
|
+
let rootErrors;
|
|
553
|
+
let nestedErrors;
|
|
554
|
+
if (result.issues) {
|
|
555
|
+
nestedErrors = {};
|
|
556
|
+
for (const issue of result.issues) if (issue.path) {
|
|
557
|
+
const path = [];
|
|
558
|
+
for (const pathItem of issue.path) {
|
|
559
|
+
const key = pathItem.key;
|
|
560
|
+
const keyType = typeof key;
|
|
561
|
+
const itemType = pathItem.type;
|
|
562
|
+
if (keyType !== "string" && keyType !== "number" || itemType === "map" || itemType === "set") break;
|
|
563
|
+
path.push(key);
|
|
546
564
|
}
|
|
547
|
-
|
|
565
|
+
const name = JSON.stringify(path);
|
|
566
|
+
const fieldErrors = nestedErrors[name];
|
|
567
|
+
if (fieldErrors) fieldErrors.push(issue.message);
|
|
568
|
+
else nestedErrors[name] = [issue.message];
|
|
569
|
+
} else if (rootErrors) rootErrors.push(issue.message);
|
|
570
|
+
else rootErrors = [issue.message];
|
|
571
|
+
}
|
|
572
|
+
let shouldFocus = config?.shouldFocus ?? false;
|
|
573
|
+
batch(() => {
|
|
574
|
+
walkFieldStore(internalFormStore, (internalFieldStore) => {
|
|
575
|
+
if (internalFieldStore.name === "[]") internalFieldStore.errors.value = rootErrors ?? null;
|
|
576
|
+
else {
|
|
577
|
+
const fieldErrors = nestedErrors?.[internalFieldStore.name] ?? null;
|
|
578
|
+
internalFieldStore.errors.value = fieldErrors;
|
|
579
|
+
if (shouldFocus && fieldErrors && focusFieldElement(internalFieldStore)) shouldFocus = false;
|
|
580
|
+
}
|
|
581
|
+
});
|
|
582
|
+
internalFormStore.validators--;
|
|
583
|
+
internalFormStore.isValidating.value = internalFormStore.validators > 0;
|
|
548
584
|
});
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
585
|
+
return result;
|
|
586
|
+
} catch (error) {
|
|
587
|
+
batch(() => {
|
|
588
|
+
internalFormStore.validators--;
|
|
589
|
+
internalFormStore.isValidating.value = internalFormStore.validators > 0;
|
|
590
|
+
});
|
|
591
|
+
throw error;
|
|
592
|
+
}
|
|
553
593
|
}
|
|
554
594
|
/**
|
|
555
595
|
* Validates the form input if required based on the validation mode and form
|
|
@@ -571,7 +611,7 @@ const INTERNAL = "~internal";
|
|
|
571
611
|
//#endregion
|
|
572
612
|
//#region ../../packages/methods/dist/index.preact.js
|
|
573
613
|
/**
|
|
574
|
-
* Focuses the first input element of a field. This is useful for
|
|
614
|
+
* Focuses the first focusable input element of a field. This is useful for
|
|
575
615
|
* programmatically setting focus to a specific field, such as after
|
|
576
616
|
* validation errors or user interactions.
|
|
577
617
|
*
|
|
@@ -579,7 +619,7 @@ const INTERNAL = "~internal";
|
|
|
579
619
|
* @param config The focus field configuration.
|
|
580
620
|
*/
|
|
581
621
|
function focus(form, config) {
|
|
582
|
-
getFieldStore(form[INTERNAL], config.path)
|
|
622
|
+
focusFieldElement(getFieldStore(form[INTERNAL], config.path));
|
|
583
623
|
}
|
|
584
624
|
/**
|
|
585
625
|
* Retrieves all error messages from all fields in the form by walking through
|
|
@@ -913,7 +953,10 @@ function useField(form, config) {
|
|
|
913
953
|
const internalFieldStore = useComputed(() => getFieldStore(internalFormStore, pathSignal.value));
|
|
914
954
|
useSignalEffect(() => {
|
|
915
955
|
return () => {
|
|
916
|
-
|
|
956
|
+
const internalFieldStoreValue = internalFieldStore.value;
|
|
957
|
+
const elements = internalFieldStoreValue.elements.filter((element) => element.isConnected);
|
|
958
|
+
if (internalFieldStoreValue.elements === internalFieldStoreValue.initialElements) internalFieldStoreValue.initialElements = elements;
|
|
959
|
+
internalFieldStoreValue.elements = elements;
|
|
917
960
|
};
|
|
918
961
|
});
|
|
919
962
|
return useMemo(() => ({
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@formisch/preact",
|
|
3
3
|
"description": "The lightweight, schema-first, and fully type-safe form library for Preact",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.11.0",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Fabian Hiller",
|
|
7
7
|
"homepage": "https://formisch.dev",
|
|
@@ -48,7 +48,8 @@
|
|
|
48
48
|
"@preact/signals": "^2.9.0",
|
|
49
49
|
"@testing-library/jest-dom": "^6.6.0",
|
|
50
50
|
"@testing-library/preact": "^3.2.4",
|
|
51
|
-
"@
|
|
51
|
+
"@types/node": "24.0.13",
|
|
52
|
+
"@vitest/coverage-v8": "^4.1.7",
|
|
52
53
|
"eslint": "^9.31.0",
|
|
53
54
|
"eslint-config-preact": "^2.0.0",
|
|
54
55
|
"jsdom": "^26.1.0",
|
|
@@ -57,7 +58,7 @@
|
|
|
57
58
|
"typescript": "^5.8.3",
|
|
58
59
|
"valibot": "^1.4.1",
|
|
59
60
|
"vite": "^6.0.4",
|
|
60
|
-
"vitest": "^
|
|
61
|
+
"vitest": "^4.1.7"
|
|
61
62
|
},
|
|
62
63
|
"peerDependencies": {
|
|
63
64
|
"@preact/signals": "^2.0.0",
|