@formisch/solid 0.9.5 → 0.9.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/dev.js CHANGED
@@ -64,7 +64,7 @@ function initializeFieldStore(internalFieldStore, schema, initialInput, path, nu
64
64
  if (internalFieldStore.kind === "object") {
65
65
  internalFieldStore.children ??= {};
66
66
  for (const key in schema.entries) {
67
- internalFieldStore.children[key] = {};
67
+ internalFieldStore.children[key] ??= {};
68
68
  path.push(key);
69
69
  initializeFieldStore(internalFieldStore.children[key], schema.entries[key], initialInput?.[key], path);
70
70
  path.pop();
@@ -75,6 +75,7 @@ function initializeFieldStore(internalFieldStore, schema, initialInput, path, nu
75
75
  internalFieldStore.input = /* @__PURE__ */ createSignal(objectInput);
76
76
  }
77
77
  } else {
78
+ if (internalFieldStore.kind && internalFieldStore.kind !== "value") throw new Error(`Store initialized as "${internalFieldStore.kind}" cannot be reinitialized as "value"`);
78
79
  internalFieldStore.kind = "value";
79
80
  if (internalFieldStore.kind === "value") {
80
81
  internalFieldStore.initialInput = /* @__PURE__ */ createSignal(initialInput);
@@ -513,7 +514,7 @@ function reset(form, config) {
513
514
  untrack(() => {
514
515
  const internalFormStore = form[INTERNAL];
515
516
  const internalFieldStore = config?.path ? getFieldStore(internalFormStore, config.path) : internalFormStore;
516
- if (config?.initialInput) setInitialFieldInput(internalFieldStore, config.initialInput);
517
+ if (config && "initialInput" in config) setInitialFieldInput(internalFieldStore, config.initialInput);
517
518
  walkFieldStore(internalFieldStore, (internalFieldStore$1) => {
518
519
  internalFieldStore$1.elements = internalFieldStore$1.initialElements;
519
520
  if (!config?.keepErrors) internalFieldStore$1.errors.value = null;
package/dist/dev.jsx CHANGED
@@ -62,7 +62,7 @@ function initializeFieldStore(internalFieldStore, schema, initialInput, path, nu
62
62
  if (internalFieldStore.kind === "object") {
63
63
  internalFieldStore.children ??= {};
64
64
  for (const key in schema.entries) {
65
- internalFieldStore.children[key] = {};
65
+ internalFieldStore.children[key] ??= {};
66
66
  path.push(key);
67
67
  initializeFieldStore(internalFieldStore.children[key], schema.entries[key], initialInput?.[key], path);
68
68
  path.pop();
@@ -73,6 +73,7 @@ function initializeFieldStore(internalFieldStore, schema, initialInput, path, nu
73
73
  internalFieldStore.input = /* @__PURE__ */ createSignal(objectInput);
74
74
  }
75
75
  } else {
76
+ if (internalFieldStore.kind && internalFieldStore.kind !== "value") throw new Error(`Store initialized as "${internalFieldStore.kind}" cannot be reinitialized as "value"`);
76
77
  internalFieldStore.kind = "value";
77
78
  if (internalFieldStore.kind === "value") {
78
79
  internalFieldStore.initialInput = /* @__PURE__ */ createSignal(initialInput);
@@ -511,7 +512,7 @@ function reset(form, config) {
511
512
  untrack(() => {
512
513
  const internalFormStore = form[INTERNAL];
513
514
  const internalFieldStore = config?.path ? getFieldStore(internalFormStore, config.path) : internalFormStore;
514
- if (config?.initialInput) setInitialFieldInput(internalFieldStore, config.initialInput);
515
+ if (config && "initialInput" in config) setInitialFieldInput(internalFieldStore, config.initialInput);
515
516
  walkFieldStore(internalFieldStore, (internalFieldStore$1) => {
516
517
  internalFieldStore$1.elements = internalFieldStore$1.initialElements;
517
518
  if (!config?.keepErrors) internalFieldStore$1.errors.value = null;
package/dist/index.d.ts CHANGED
@@ -3,13 +3,13 @@ import { JSX } from "solid-js";
3
3
 
4
4
  //#region ../../packages/core/dist/index.solid.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
  */
@@ -190,7 +190,7 @@ type InternalFieldStore = InternalArrayStore | InternalObjectStore | InternalVal
190
190
  */
191
191
  declare const INTERNAL: "~internal";
192
192
  //#endregion
193
- //#region src/types/utils.d.ts
193
+ //#region src/types/utils/utils.d.ts
194
194
  /**
195
195
  * Checks if a type is `any`.
196
196
  */
@@ -216,7 +216,7 @@ type DeepPartial<TValue> = TValue extends Record<PropertyKey, unknown> | readonl
216
216
  */
217
217
  type PartialValues<TValue> = TValue extends readonly (infer TItem)[] ? number extends TValue["length"] ? (TItem extends Record<PropertyKey, unknown> | readonly unknown[] ? { [TKey in keyof TItem]: PartialValues<TItem[TKey]> } : TItem)[] : { [TKey in keyof TValue]: PartialValues<TValue[TKey]> } : TValue extends Record<PropertyKey, unknown> ? { [TKey in keyof TValue]: PartialValues<TValue[TKey]> } : TValue | undefined;
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
  */
@@ -299,7 +299,7 @@ type SubmitHandler<TSchema extends Schema> = (output: v.InferOutput<TSchema>) =>
299
299
  */
300
300
  type SubmitEventHandler<TSchema extends Schema> = (output: v.InferOutput<TSchema>, event: SubmitEvent) => MaybePromise<unknown>;
301
301
  //#endregion
302
- //#region src/types/path.d.ts
302
+ //#region src/types/path/path.d.ts
303
303
  /**
304
304
  * Path key type.
305
305
  */
@@ -313,42 +313,88 @@ type Path = readonly PathKey[];
313
313
  */
314
314
  type RequiredPath = readonly [PathKey, ...Path];
315
315
  /**
316
- * Extracts the exact keys of a tuple, array or object.
316
+ * Extracts the exact keys of a tuple, array or object. Tuples return their
317
+ * literal numeric indices, dynamic arrays return `number`, objects return
318
+ * their `keyof` keys, and any other input returns `never`.
317
319
  */
318
- type KeyOf<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<string, unknown> ? keyof TValue & PathKey : never;
320
+ 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;
319
321
  /**
320
- * Merges array and object unions into a single object.
322
+ * Returns the flat object of all indexable properties of `TValue`. For object
323
+ * unions, properties from every member are merged so that any single property
324
+ * is accessible. For primitives and other non-indexable types, the result is
325
+ * `{}`.
321
326
  *
322
- * Hint: This is necessary to make any property accessible. By default,
323
- * properties that do not exist in all union options are not accessible
324
- * and result in "any" when accessed.
327
+ * Hint: This is necessary to make properties accessible across union members.
328
+ * By default, properties that do not exist in all union options are not
329
+ * accessible and result in "any" when accessed.
325
330
  */
326
- type MergeUnion<TValue> = { [TKey in KeyOf<TValue>]: TValue extends Record<TKey, infer TItem> ? TItem : never };
331
+ type PropertiesOf<TValue> = { [TKey in ExactKeysOf<TValue>]: TValue extends Record<TKey, infer TItem> ? TItem : never };
327
332
  /**
328
333
  * Lazily evaluates only the first valid path segment based on the given value.
329
334
  */
330
- type LazyPath<TValue, TPathToCheck extends Path, TValidPath extends Path = readonly []> = TPathToCheck extends readonly [] ? TValidPath : TPathToCheck extends readonly [infer TFirstKey extends KeyOf<TValue>, ...infer TPathRest extends Path] ? LazyPath<Required<MergeUnion<TValue>[TFirstKey]>, TPathRest, readonly [...TValidPath, TFirstKey]> : IsNever<KeyOf<TValue>> extends false ? readonly [...TValidPath, KeyOf<TValue>] : TValidPath;
335
+ 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;
331
336
  /**
332
337
  * Returns the path if valid, otherwise the first possible valid path based on
333
338
  * the given value.
334
339
  */
335
340
  type ValidPath<TValue, TPath extends RequiredPath> = TPath extends LazyPath<Required<TValue>, TPath> ? TPath : LazyPath<Required<TValue>, TPath>;
336
341
  /**
342
+ * Detects whether the consuming project is configured with
343
+ * `exactOptionalPropertyTypes: true`.
344
+ *
345
+ * Hint: If `false` the built-in `Required<T>` strips `| undefined` from
346
+ * optional properties, so `Required<{ key?: undefined }>['key']` collapses
347
+ * to `never` — under strict mode the same expression yields `undefined`.
348
+ */
349
+ type IsExactOptionalProps = Required<{
350
+ key?: undefined;
351
+ }>["key"] extends never ? false : true;
352
+ /**
353
+ * Like the built-in `Required<T>`, but preserves `| undefined` in two
354
+ * places where `Required<T>` strips it:
355
+ *
356
+ * 1. Optional property values under `exactOptionalPropertyTypes: false`
357
+ * — without this, input typings for `v.optional`/`v.nullish` schemas
358
+ * narrow incorrectly (issue #15).
359
+ * 2. Array/tuple element types — e.g. `(string | undefined)[]` stays
360
+ * `(string | undefined)[]` instead of becoming `string[]`. Arrays
361
+ * fall through unchanged because they only have a numeric index
362
+ * signature and don't structurally extend `Record<PropertyKey,
363
+ * unknown>` (which requires string keys).
364
+ */
365
+ type ExactRequired<TValue> = TValue extends Record<PropertyKey, unknown> ? IsExactOptionalProps extends true ? Required<TValue> : { [TKey in keyof Required<TValue>]: TValue[TKey] } : TValue;
366
+ /**
337
367
  * Extracts the value type at the given path.
338
368
  */
339
- type PathValue<TValue, TPath extends Path> = TPath extends readonly [infer TKey, ...infer TRest extends Path] ? TKey extends KeyOf<Required<TValue>> ? PathValue<MergeUnion<Required<TValue>>[TKey], TRest> : unknown : TValue;
369
+ 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;
340
370
  /**
341
- * Checks if a value is an array or contains one.
371
+ * Checks whether a value is an array or contains one anywhere in its shape.
372
+ *
373
+ * Hint: The inner conditionals (`TValue extends readonly unknown[]` and
374
+ * `TValue extends Record<PropertyKey, unknown>`) distribute over union members,
375
+ * so the inner expression returns the union of each member's result (e.g.
376
+ * `true | false` when some members contain arrays and others don't).
377
+ * Downstream code uses `IsOrHasArray<T> extends true`, but
378
+ * `boolean extends true` is `false` — so we collapse the result via
379
+ * `true extends ...`, which is `true` whenever at least one union member
380
+ * contributed `true`.
342
381
  */
343
- type IsOrHasArray<TValue> = IsAny<TValue> extends true ? false : TValue extends readonly unknown[] ? true : TValue extends Record<string, unknown> ? true extends { [TKey in keyof TValue]: IsOrHasArray<TValue[TKey]> }[keyof TValue] ? true : false : false;
382
+ 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;
344
383
  /**
345
384
  * Extracts the exact keys of a tuple, array or object that contain arrays.
346
385
  */
347
- type KeyOfArrayPath<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<string, unknown> ? { [TKey in keyof TValue]: IsOrHasArray<NonNullable<TValue[TKey]>> extends true ? TKey : never }[keyof TValue] & PathKey : never;
386
+ 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;
387
+ /**
388
+ * Returns the flat object of indexable properties of `TValue` whose values
389
+ * are or contain arrays. Mirrors `PropertiesOf` but keyed by
390
+ * `ExactKeysOfArrayPath` so the lookup is provably valid for array-path
391
+ * navigation in `LazyArrayPath`.
392
+ */
393
+ type PropertiesOfArrayPath<TValue> = { [TKey in ExactKeysOfArrayPath<TValue>]: TValue extends Record<TKey, infer TItem> ? TItem : never };
348
394
  /**
349
395
  * Lazily evaluates only the first valid array path segment based on the given value.
350
396
  */
351
- type LazyArrayPath<TValue, TPathToCheck extends Path, TValidPath extends Path = readonly []> = TPathToCheck extends readonly [] ? TValue extends readonly unknown[] ? TValidPath : readonly [...TValidPath, KeyOfArrayPath<TValue>] : TPathToCheck extends readonly [infer TFirstKey extends KeyOfArrayPath<TValue>, ...infer TPathRest extends Path] ? LazyArrayPath<Required<MergeUnion<TValue>[TFirstKey]>, TPathRest, readonly [...TValidPath, TFirstKey]> : IsNever<KeyOfArrayPath<TValue>> extends false ? readonly [...TValidPath, KeyOfArrayPath<TValue>] : never;
397
+ 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;
352
398
  /**
353
399
  * Returns the path if valid, otherwise the first possible valid array path
354
400
  * based on the given value.
package/dist/index.js CHANGED
@@ -64,7 +64,7 @@ function initializeFieldStore(internalFieldStore, schema, initialInput, path, nu
64
64
  if (internalFieldStore.kind === "object") {
65
65
  internalFieldStore.children ??= {};
66
66
  for (const key in schema.entries) {
67
- internalFieldStore.children[key] = {};
67
+ internalFieldStore.children[key] ??= {};
68
68
  path.push(key);
69
69
  initializeFieldStore(internalFieldStore.children[key], schema.entries[key], initialInput?.[key], path);
70
70
  path.pop();
@@ -75,6 +75,7 @@ function initializeFieldStore(internalFieldStore, schema, initialInput, path, nu
75
75
  internalFieldStore.input = /* @__PURE__ */ createSignal(objectInput);
76
76
  }
77
77
  } else {
78
+ if (internalFieldStore.kind && internalFieldStore.kind !== "value") throw new Error(`Store initialized as "${internalFieldStore.kind}" cannot be reinitialized as "value"`);
78
79
  internalFieldStore.kind = "value";
79
80
  if (internalFieldStore.kind === "value") {
80
81
  internalFieldStore.initialInput = /* @__PURE__ */ createSignal(initialInput);
@@ -513,7 +514,7 @@ function reset(form, config) {
513
514
  untrack(() => {
514
515
  const internalFormStore = form[INTERNAL];
515
516
  const internalFieldStore = config?.path ? getFieldStore(internalFormStore, config.path) : internalFormStore;
516
- if (config?.initialInput) setInitialFieldInput(internalFieldStore, config.initialInput);
517
+ if (config && "initialInput" in config) setInitialFieldInput(internalFieldStore, config.initialInput);
517
518
  walkFieldStore(internalFieldStore, (internalFieldStore$1) => {
518
519
  internalFieldStore$1.elements = internalFieldStore$1.initialElements;
519
520
  if (!config?.keepErrors) internalFieldStore$1.errors.value = null;
package/dist/index.jsx CHANGED
@@ -62,7 +62,7 @@ function initializeFieldStore(internalFieldStore, schema, initialInput, path, nu
62
62
  if (internalFieldStore.kind === "object") {
63
63
  internalFieldStore.children ??= {};
64
64
  for (const key in schema.entries) {
65
- internalFieldStore.children[key] = {};
65
+ internalFieldStore.children[key] ??= {};
66
66
  path.push(key);
67
67
  initializeFieldStore(internalFieldStore.children[key], schema.entries[key], initialInput?.[key], path);
68
68
  path.pop();
@@ -73,6 +73,7 @@ function initializeFieldStore(internalFieldStore, schema, initialInput, path, nu
73
73
  internalFieldStore.input = /* @__PURE__ */ createSignal(objectInput);
74
74
  }
75
75
  } else {
76
+ if (internalFieldStore.kind && internalFieldStore.kind !== "value") throw new Error(`Store initialized as "${internalFieldStore.kind}" cannot be reinitialized as "value"`);
76
77
  internalFieldStore.kind = "value";
77
78
  if (internalFieldStore.kind === "value") {
78
79
  internalFieldStore.initialInput = /* @__PURE__ */ createSignal(initialInput);
@@ -511,7 +512,7 @@ function reset(form, config) {
511
512
  untrack(() => {
512
513
  const internalFormStore = form[INTERNAL];
513
514
  const internalFieldStore = config?.path ? getFieldStore(internalFormStore, config.path) : internalFormStore;
514
- if (config?.initialInput) setInitialFieldInput(internalFieldStore, config.initialInput);
515
+ if (config && "initialInput" in config) setInitialFieldInput(internalFieldStore, config.initialInput);
515
516
  walkFieldStore(internalFieldStore, (internalFieldStore$1) => {
516
517
  internalFieldStore$1.elements = internalFieldStore$1.initialElements;
517
518
  if (!config?.keepErrors) internalFieldStore$1.errors.value = null;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@formisch/solid",
3
- "description": "The modular and type-safe form library for SolidJS",
4
- "version": "0.9.5",
3
+ "description": "The lightweight, schema-first, and fully type-safe form library for SolidJS",
4
+ "version": "0.9.6",
5
5
  "license": "MIT",
6
6
  "author": "Fabian Hiller",
7
7
  "homepage": "https://formisch.dev",
@@ -44,6 +44,7 @@
44
44
  "access": "public"
45
45
  },
46
46
  "scripts": {
47
+ "test": "vitest run --typecheck",
47
48
  "lint": "eslint \"src/**/*.ts*\" && tsc --noEmit",
48
49
  "lint.fix": "eslint \"src/**/*.ts*\" --fix",
49
50
  "format": "prettier --write ./src",
@@ -54,6 +55,8 @@
54
55
  "@formisch/core": "workspace:*",
55
56
  "@formisch/eslint-config": "workspace:*",
56
57
  "@formisch/methods": "workspace:*",
58
+ "@solidjs/testing-library": "^0.8.10",
59
+ "@testing-library/jest-dom": "^6.6.0",
57
60
  "@vitest/coverage-v8": "^3.2.4",
58
61
  "eslint": "^9.31.0",
59
62
  "eslint-plugin-solid": "^0.14.5",
@@ -66,6 +69,7 @@
66
69
  "tsup-preset-solid": "^2.2.0",
67
70
  "typescript": "^5.8.3",
68
71
  "valibot": "^1.2.0",
72
+ "vite-plugin-solid": "^2.11.6",
69
73
  "vitest": "3.2.4"
70
74
  },
71
75
  "peerDependencies": {