@formisch/react 0.4.4 → 0.4.6
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 +66 -20
- package/dist/index.js +13 -3
- package/package.json +10 -3
package/dist/index.d.ts
CHANGED
|
@@ -3,13 +3,13 @@ import { ChangeEventHandler, FocusEventHandler, FormEvent, FormHTMLAttributes, R
|
|
|
3
3
|
|
|
4
4
|
//#region ../../packages/core/dist/index.react.d.ts
|
|
5
5
|
|
|
6
|
-
//#region src/types/schema.d.ts
|
|
6
|
+
//#region src/types/schema/schema.d.ts
|
|
7
7
|
/**
|
|
8
8
|
* Schema type.
|
|
9
9
|
*/
|
|
10
10
|
type Schema = v.GenericSchema | v.GenericSchemaAsync;
|
|
11
11
|
//#endregion
|
|
12
|
-
//#region src/types/signal.d.ts
|
|
12
|
+
//#region src/types/signal/signal.d.ts
|
|
13
13
|
/**
|
|
14
14
|
* Signal interface.
|
|
15
15
|
*/
|
|
@@ -24,7 +24,7 @@ interface Signal<T> {
|
|
|
24
24
|
*/
|
|
25
25
|
|
|
26
26
|
//#endregion
|
|
27
|
-
//#region src/types/field.d.ts
|
|
27
|
+
//#region src/types/field/field.d.ts
|
|
28
28
|
/**
|
|
29
29
|
* Field element type.
|
|
30
30
|
*/
|
|
@@ -184,7 +184,7 @@ interface InternalValueStore extends InternalBaseStore {
|
|
|
184
184
|
*/
|
|
185
185
|
type InternalFieldStore = InternalArrayStore | InternalObjectStore | InternalValueStore;
|
|
186
186
|
//#endregion
|
|
187
|
-
//#region src/types/utils.d.ts
|
|
187
|
+
//#region src/types/utils/utils.d.ts
|
|
188
188
|
/**
|
|
189
189
|
* Checks if a type is `any`.
|
|
190
190
|
*/
|
|
@@ -216,7 +216,7 @@ type PartialValues<TValue> = TValue extends readonly (infer TItem)[] ? number ex
|
|
|
216
216
|
*/
|
|
217
217
|
declare const INTERNAL: "~internal";
|
|
218
218
|
//#endregion
|
|
219
|
-
//#region src/types/form.d.ts
|
|
219
|
+
//#region src/types/form/form.d.ts
|
|
220
220
|
/**
|
|
221
221
|
* Validation mode type.
|
|
222
222
|
*/
|
|
@@ -291,7 +291,7 @@ interface BaseFormStore<TSchema extends Schema = Schema> {
|
|
|
291
291
|
readonly [INTERNAL]: InternalFormStore<TSchema>;
|
|
292
292
|
}
|
|
293
293
|
//#endregion
|
|
294
|
-
//#region src/types/form.react.d.ts
|
|
294
|
+
//#region src/types/form/form.react.d.ts
|
|
295
295
|
/**
|
|
296
296
|
* Submit handler type.
|
|
297
297
|
*/
|
|
@@ -301,7 +301,7 @@ type SubmitHandler<TSchema extends Schema> = (output: v.InferOutput<TSchema>) =>
|
|
|
301
301
|
*/
|
|
302
302
|
type SubmitEventHandler<TSchema extends Schema> = (output: v.InferOutput<TSchema>, event: FormEvent<HTMLFormElement>) => MaybePromise<unknown>;
|
|
303
303
|
//#endregion
|
|
304
|
-
//#region src/types/path.d.ts
|
|
304
|
+
//#region src/types/path/path.d.ts
|
|
305
305
|
/**
|
|
306
306
|
* Path key type.
|
|
307
307
|
*/
|
|
@@ -315,42 +315,88 @@ type Path = readonly PathKey[];
|
|
|
315
315
|
*/
|
|
316
316
|
type RequiredPath = readonly [PathKey, ...Path];
|
|
317
317
|
/**
|
|
318
|
-
* Extracts the exact keys of a tuple, array or object.
|
|
318
|
+
* Extracts the exact keys of a tuple, array or object. Tuples return their
|
|
319
|
+
* literal numeric indices, dynamic arrays return `number`, objects return
|
|
320
|
+
* their `keyof` keys, and any other input returns `never`.
|
|
319
321
|
*/
|
|
320
|
-
type
|
|
322
|
+
type ExactKeysOf<TValue> = IsAny<TValue> extends true ? never : TValue extends readonly unknown[] ? number extends TValue["length"] ? number : { [TKey in keyof TValue]: TKey extends `${infer TIndex extends number}` ? TIndex : never }[number] : TValue extends Record<PropertyKey, unknown> ? keyof TValue & PathKey : never;
|
|
321
323
|
/**
|
|
322
|
-
*
|
|
324
|
+
* Returns the flat object of all indexable properties of `TValue`. For object
|
|
325
|
+
* unions, properties from every member are merged so that any single property
|
|
326
|
+
* is accessible. For primitives and other non-indexable types, the result is
|
|
327
|
+
* `{}`.
|
|
323
328
|
*
|
|
324
|
-
* Hint: This is necessary to make
|
|
325
|
-
* properties that do not exist in all union options are not
|
|
326
|
-
* and result in "any" when accessed.
|
|
329
|
+
* Hint: This is necessary to make properties accessible across union members.
|
|
330
|
+
* By default, properties that do not exist in all union options are not
|
|
331
|
+
* accessible and result in "any" when accessed.
|
|
327
332
|
*/
|
|
328
|
-
type
|
|
333
|
+
type PropertiesOf<TValue> = { [TKey in ExactKeysOf<TValue>]: TValue extends Record<TKey, infer TItem> ? TItem : never };
|
|
329
334
|
/**
|
|
330
335
|
* Lazily evaluates only the first valid path segment based on the given value.
|
|
331
336
|
*/
|
|
332
|
-
type LazyPath<TValue, TPathToCheck extends Path, TValidPath extends Path = readonly []> = TPathToCheck extends readonly [] ? TValidPath : TPathToCheck extends readonly [infer TFirstKey extends
|
|
337
|
+
type LazyPath<TValue, TPathToCheck extends Path, TValidPath extends Path = readonly []> = TPathToCheck extends readonly [] ? TValidPath : TPathToCheck extends readonly [infer TFirstKey extends ExactKeysOf<TValue>, ...infer TPathRest extends Path] ? LazyPath<Required<PropertiesOf<TValue>[TFirstKey]>, TPathRest, readonly [...TValidPath, TFirstKey]> : IsNever<ExactKeysOf<TValue>> extends false ? readonly [...TValidPath, ExactKeysOf<TValue>] : TValidPath;
|
|
333
338
|
/**
|
|
334
339
|
* Returns the path if valid, otherwise the first possible valid path based on
|
|
335
340
|
* the given value.
|
|
336
341
|
*/
|
|
337
342
|
type ValidPath<TValue, TPath extends RequiredPath> = TPath extends LazyPath<Required<TValue>, TPath> ? TPath : LazyPath<Required<TValue>, TPath>;
|
|
338
343
|
/**
|
|
344
|
+
* Detects whether the consuming project is configured with
|
|
345
|
+
* `exactOptionalPropertyTypes: true`.
|
|
346
|
+
*
|
|
347
|
+
* Hint: If `false` the built-in `Required<T>` strips `| undefined` from
|
|
348
|
+
* optional properties, so `Required<{ key?: undefined }>['key']` collapses
|
|
349
|
+
* to `never` — under strict mode the same expression yields `undefined`.
|
|
350
|
+
*/
|
|
351
|
+
type IsExactOptionalProps = Required<{
|
|
352
|
+
key?: undefined;
|
|
353
|
+
}>["key"] extends never ? false : true;
|
|
354
|
+
/**
|
|
355
|
+
* Like the built-in `Required<T>`, but preserves `| undefined` in two
|
|
356
|
+
* places where `Required<T>` strips it:
|
|
357
|
+
*
|
|
358
|
+
* 1. Optional property values under `exactOptionalPropertyTypes: false`
|
|
359
|
+
* — without this, input typings for `v.optional`/`v.nullish` schemas
|
|
360
|
+
* narrow incorrectly (issue #15).
|
|
361
|
+
* 2. Array/tuple element types — e.g. `(string | undefined)[]` stays
|
|
362
|
+
* `(string | undefined)[]` instead of becoming `string[]`. Arrays
|
|
363
|
+
* fall through unchanged because they only have a numeric index
|
|
364
|
+
* signature and don't structurally extend `Record<PropertyKey,
|
|
365
|
+
* unknown>` (which requires string keys).
|
|
366
|
+
*/
|
|
367
|
+
type ExactRequired<TValue> = TValue extends Record<PropertyKey, unknown> ? IsExactOptionalProps extends true ? Required<TValue> : { [TKey in keyof Required<TValue>]: TValue[TKey] } : TValue;
|
|
368
|
+
/**
|
|
339
369
|
* Extracts the value type at the given path.
|
|
340
370
|
*/
|
|
341
|
-
type PathValue<TValue, TPath extends Path> = TPath extends readonly [infer TKey, ...infer TRest extends Path] ? TKey extends
|
|
371
|
+
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;
|
|
342
372
|
/**
|
|
343
|
-
* Checks
|
|
373
|
+
* Checks whether a value is an array or contains one anywhere in its shape.
|
|
374
|
+
*
|
|
375
|
+
* Hint: The inner conditionals (`TValue extends readonly unknown[]` and
|
|
376
|
+
* `TValue extends Record<PropertyKey, unknown>`) distribute over union members,
|
|
377
|
+
* so the inner expression returns the union of each member's result (e.g.
|
|
378
|
+
* `true | false` when some members contain arrays and others don't).
|
|
379
|
+
* Downstream code uses `IsOrHasArray<T> extends true`, but
|
|
380
|
+
* `boolean extends true` is `false` — so we collapse the result via
|
|
381
|
+
* `true extends ...`, which is `true` whenever at least one union member
|
|
382
|
+
* contributed `true`.
|
|
344
383
|
*/
|
|
345
|
-
type IsOrHasArray<TValue> = IsAny<TValue> extends true ? false : TValue extends readonly unknown[] ? true : TValue extends Record<
|
|
384
|
+
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;
|
|
346
385
|
/**
|
|
347
386
|
* Extracts the exact keys of a tuple, array or object that contain arrays.
|
|
348
387
|
*/
|
|
349
|
-
type
|
|
388
|
+
type ExactKeysOfArrayPath<TValue> = IsAny<TValue> extends true ? never : TValue extends readonly (infer TItem)[] ? number extends TValue["length"] ? IsOrHasArray<TItem> extends true ? number : never : { [TKey in keyof TValue]: TKey extends `${infer TIndex extends number}` ? IsOrHasArray<NonNullable<TValue[TKey]>> extends true ? TIndex : never : never }[number] : TValue extends Record<PropertyKey, unknown> ? { [TKey in keyof TValue]: IsOrHasArray<NonNullable<TValue[TKey]>> extends true ? TKey : never }[keyof TValue] & PathKey : never;
|
|
389
|
+
/**
|
|
390
|
+
* Returns the flat object of indexable properties of `TValue` whose values
|
|
391
|
+
* are or contain arrays. Mirrors `PropertiesOf` but keyed by
|
|
392
|
+
* `ExactKeysOfArrayPath` so the lookup is provably valid for array-path
|
|
393
|
+
* navigation in `LazyArrayPath`.
|
|
394
|
+
*/
|
|
395
|
+
type PropertiesOfArrayPath<TValue> = { [TKey in ExactKeysOfArrayPath<TValue>]: TValue extends Record<TKey, infer TItem> ? TItem : never };
|
|
350
396
|
/**
|
|
351
397
|
* Lazily evaluates only the first valid array path segment based on the given value.
|
|
352
398
|
*/
|
|
353
|
-
type LazyArrayPath<TValue, TPathToCheck extends Path, TValidPath extends Path = readonly []> = TPathToCheck extends readonly [] ? TValue extends readonly unknown[] ? TValidPath : readonly [...TValidPath,
|
|
399
|
+
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;
|
|
354
400
|
/**
|
|
355
401
|
* Returns the path if valid, otherwise the first possible valid array path
|
|
356
402
|
* based on the given value.
|
package/dist/index.js
CHANGED
|
@@ -162,7 +162,7 @@ function initializeFieldStore(internalFieldStore, schema, initialInput, path, nu
|
|
|
162
162
|
if (internalFieldStore.kind === "object") {
|
|
163
163
|
internalFieldStore.children ??= {};
|
|
164
164
|
for (const key in schema.entries) {
|
|
165
|
-
internalFieldStore.children[key]
|
|
165
|
+
internalFieldStore.children[key] ??= {};
|
|
166
166
|
path.push(key);
|
|
167
167
|
initializeFieldStore(internalFieldStore.children[key], schema.entries[key], initialInput?.[key], path);
|
|
168
168
|
path.pop();
|
|
@@ -173,6 +173,7 @@ function initializeFieldStore(internalFieldStore, schema, initialInput, path, nu
|
|
|
173
173
|
internalFieldStore.input = /* @__PURE__ */ createSignal(objectInput);
|
|
174
174
|
}
|
|
175
175
|
} else {
|
|
176
|
+
if (internalFieldStore.kind && internalFieldStore.kind !== "value") throw new Error(`Store initialized as "${internalFieldStore.kind}" cannot be reinitialized as "value"`);
|
|
176
177
|
internalFieldStore.kind = "value";
|
|
177
178
|
if (internalFieldStore.kind === "value") {
|
|
178
179
|
internalFieldStore.initialInput = /* @__PURE__ */ createSignal(initialInput);
|
|
@@ -644,6 +645,7 @@ function focus(form, config) {
|
|
|
644
645
|
function getAllErrors(form) {
|
|
645
646
|
let allErrors = null;
|
|
646
647
|
walkFieldStore(form[INTERNAL], (internalFieldStore) => {
|
|
648
|
+
if (internalFieldStore.kind === "array") internalFieldStore.items.value;
|
|
647
649
|
const errors = internalFieldStore.errors.value;
|
|
648
650
|
if (errors) if (allErrors) allErrors.push(...errors);
|
|
649
651
|
else allErrors = [...errors];
|
|
@@ -700,7 +702,15 @@ function insert(form, config) {
|
|
|
700
702
|
const newItems = [...items];
|
|
701
703
|
newItems.splice(insertIndex, 0, createId());
|
|
702
704
|
internalArrayStore.items.value = newItems;
|
|
703
|
-
for (let index = items.length; index > insertIndex; index--)
|
|
705
|
+
for (let index = items.length; index > insertIndex; index--) {
|
|
706
|
+
if (!internalArrayStore.children[index]) {
|
|
707
|
+
const path = JSON.parse(internalArrayStore.name);
|
|
708
|
+
internalArrayStore.children[index] = {};
|
|
709
|
+
path.push(index);
|
|
710
|
+
initializeFieldStore(internalArrayStore.children[index], internalArrayStore.schema.item, void 0, path);
|
|
711
|
+
}
|
|
712
|
+
copyItemState(internalArrayStore.children[index - 1], internalArrayStore.children[index]);
|
|
713
|
+
}
|
|
704
714
|
if (!internalArrayStore.children[insertIndex]) {
|
|
705
715
|
const path = JSON.parse(internalArrayStore.name);
|
|
706
716
|
internalArrayStore.children[insertIndex] = {};
|
|
@@ -785,7 +795,7 @@ function reset(form, config) {
|
|
|
785
795
|
untrack(() => {
|
|
786
796
|
const internalFormStore = form[INTERNAL];
|
|
787
797
|
const internalFieldStore = config?.path ? getFieldStore(internalFormStore, config.path) : internalFormStore;
|
|
788
|
-
if (config
|
|
798
|
+
if (config && "initialInput" in config) setInitialFieldInput(internalFieldStore, config.initialInput);
|
|
789
799
|
walkFieldStore(internalFieldStore, (internalFieldStore$1) => {
|
|
790
800
|
internalFieldStore$1.elements = internalFieldStore$1.initialElements;
|
|
791
801
|
if (!config?.keepErrors) internalFieldStore$1.errors.value = null;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@formisch/react",
|
|
3
|
-
"description": "The
|
|
4
|
-
"version": "0.4.
|
|
3
|
+
"description": "The lightweight, schema-first, and fully type-safe form library for React",
|
|
4
|
+
"version": "0.4.6",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Fabian Hiller",
|
|
7
7
|
"homepage": "https://formisch.dev",
|
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
},
|
|
35
35
|
"scripts": {
|
|
36
36
|
"build": "tsdown",
|
|
37
|
+
"test": "vitest run --typecheck",
|
|
37
38
|
"lint": "eslint \"src/**/*.ts*\" && tsc --noEmit",
|
|
38
39
|
"lint.fix": "eslint \"src/**/*.ts*\" --fix",
|
|
39
40
|
"format": "prettier --write ./src",
|
|
@@ -43,19 +44,25 @@
|
|
|
43
44
|
"@formisch/core": "workspace:*",
|
|
44
45
|
"@formisch/eslint-config": "workspace:*",
|
|
45
46
|
"@formisch/methods": "workspace:*",
|
|
47
|
+
"@testing-library/dom": "^10.4.0",
|
|
48
|
+
"@testing-library/jest-dom": "^6.6.0",
|
|
49
|
+
"@testing-library/react": "^16.3.0",
|
|
46
50
|
"@types/node": "^24.10.1",
|
|
47
51
|
"@types/react": "^19.2.5",
|
|
48
52
|
"@types/react-dom": "^19.2.3",
|
|
49
53
|
"@vitejs/plugin-react": "^5.1.1",
|
|
54
|
+
"@vitest/coverage-v8": "^3.2.4",
|
|
50
55
|
"eslint": "^9.39.1",
|
|
51
56
|
"eslint-plugin-react-hooks": "^7.0.1",
|
|
52
57
|
"eslint-plugin-react-refresh": "^0.4.24",
|
|
53
58
|
"globals": "^16.5.0",
|
|
59
|
+
"jsdom": "^26.1.0",
|
|
54
60
|
"react": "^19.2.1",
|
|
55
61
|
"react-dom": "^19.2.1",
|
|
56
62
|
"tsdown": "^0.16.8",
|
|
57
63
|
"typescript": "~5.9.3",
|
|
58
|
-
"vite": "^7.2.4"
|
|
64
|
+
"vite": "^7.2.4",
|
|
65
|
+
"vitest": "^3.2.4"
|
|
59
66
|
},
|
|
60
67
|
"peerDependencies": {
|
|
61
68
|
"react": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|