@formisch/vue 0.7.4 → 0.7.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 CHANGED
@@ -1,23 +1,23 @@
1
1
  import * as v from "valibot";
2
- import * as vue1 from "vue";
2
+ import * as vue3 from "vue";
3
3
  import { ComponentPublicInstance, MaybeRefOrGetter, ShallowRef as Signal } from "vue";
4
4
 
5
5
  //#region ../../packages/core/dist/index.vue.d.ts
6
6
 
7
- //#region src/types/schema.d.ts
7
+ //#region src/types/schema/schema.d.ts
8
8
  /**
9
9
  * Schema type.
10
10
  */
11
11
  type Schema = v.GenericSchema | v.GenericSchemaAsync;
12
12
  //#endregion
13
- //#region src/types/signal.d.ts
13
+ //#region src/types/signal/signal.d.ts
14
14
 
15
15
  /**
16
16
  * Batch interface.
17
17
  */
18
18
 
19
19
  //#endregion
20
- //#region src/types/field.d.ts
20
+ //#region src/types/field/field.d.ts
21
21
  /**
22
22
  * Field element type.
23
23
  */
@@ -183,7 +183,7 @@ type InternalFieldStore = InternalArrayStore | InternalObjectStore | InternalVal
183
183
  */
184
184
  declare const INTERNAL: "~internal";
185
185
  //#endregion
186
- //#region src/types/utils.d.ts
186
+ //#region src/types/utils/utils.d.ts
187
187
  /**
188
188
  * Checks if a type is `any`.
189
189
  */
@@ -209,7 +209,7 @@ type DeepPartial<TValue> = TValue extends Record<PropertyKey, unknown> | readonl
209
209
  */
210
210
  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;
211
211
  //#endregion
212
- //#region src/types/form.d.ts
212
+ //#region src/types/form/form.d.ts
213
213
  /**
214
214
  * Validation mode type.
215
215
  */
@@ -292,7 +292,7 @@ type SubmitHandler<TSchema extends Schema> = (output: v.InferOutput<TSchema>) =>
292
292
  */
293
293
  type SubmitEventHandler<TSchema extends Schema> = (output: v.InferOutput<TSchema>, event: SubmitEvent) => MaybePromise<unknown>;
294
294
  //#endregion
295
- //#region src/types/path.d.ts
295
+ //#region src/types/path/path.d.ts
296
296
  /**
297
297
  * Path key type.
298
298
  */
@@ -306,42 +306,88 @@ type Path = readonly PathKey[];
306
306
  */
307
307
  type RequiredPath = readonly [PathKey, ...Path];
308
308
  /**
309
- * Extracts the exact keys of a tuple, array or object.
309
+ * Extracts the exact keys of a tuple, array or object. Tuples return their
310
+ * literal numeric indices, dynamic arrays return `number`, objects return
311
+ * their `keyof` keys, and any other input returns `never`.
310
312
  */
311
- 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;
313
+ 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;
312
314
  /**
313
- * Merges array and object unions into a single object.
315
+ * Returns the flat object of all indexable properties of `TValue`. For object
316
+ * unions, properties from every member are merged so that any single property
317
+ * is accessible. For primitives and other non-indexable types, the result is
318
+ * `{}`.
314
319
  *
315
- * Hint: This is necessary to make any property accessible. By default,
316
- * properties that do not exist in all union options are not accessible
317
- * and result in "any" when accessed.
320
+ * Hint: This is necessary to make properties accessible across union members.
321
+ * By default, properties that do not exist in all union options are not
322
+ * accessible and result in "any" when accessed.
318
323
  */
319
- type MergeUnion<TValue> = { [TKey in KeyOf<TValue>]: TValue extends Record<TKey, infer TItem> ? TItem : never };
324
+ type PropertiesOf<TValue> = { [TKey in ExactKeysOf<TValue>]: TValue extends Record<TKey, infer TItem> ? TItem : never };
320
325
  /**
321
326
  * Lazily evaluates only the first valid path segment based on the given value.
322
327
  */
323
- 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;
328
+ 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;
324
329
  /**
325
330
  * Returns the path if valid, otherwise the first possible valid path based on
326
331
  * the given value.
327
332
  */
328
333
  type ValidPath<TValue, TPath extends RequiredPath> = TPath extends LazyPath<Required<TValue>, TPath> ? TPath : LazyPath<Required<TValue>, TPath>;
329
334
  /**
335
+ * Detects whether the consuming project is configured with
336
+ * `exactOptionalPropertyTypes: true`.
337
+ *
338
+ * Hint: If `false` the built-in `Required<T>` strips `| undefined` from
339
+ * optional properties, so `Required<{ key?: undefined }>['key']` collapses
340
+ * to `never` — under strict mode the same expression yields `undefined`.
341
+ */
342
+ type IsExactOptionalProps = Required<{
343
+ key?: undefined;
344
+ }>["key"] extends never ? false : true;
345
+ /**
346
+ * Like the built-in `Required<T>`, but preserves `| undefined` in two
347
+ * places where `Required<T>` strips it:
348
+ *
349
+ * 1. Optional property values under `exactOptionalPropertyTypes: false`
350
+ * — without this, input typings for `v.optional`/`v.nullish` schemas
351
+ * narrow incorrectly (issue #15).
352
+ * 2. Array/tuple element types — e.g. `(string | undefined)[]` stays
353
+ * `(string | undefined)[]` instead of becoming `string[]`. Arrays
354
+ * fall through unchanged because they only have a numeric index
355
+ * signature and don't structurally extend `Record<PropertyKey,
356
+ * unknown>` (which requires string keys).
357
+ */
358
+ type ExactRequired<TValue> = TValue extends Record<PropertyKey, unknown> ? IsExactOptionalProps extends true ? Required<TValue> : { [TKey in keyof Required<TValue>]: TValue[TKey] } : TValue;
359
+ /**
330
360
  * Extracts the value type at the given path.
331
361
  */
332
- 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;
362
+ 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;
333
363
  /**
334
- * Checks if a value is an array or contains one.
364
+ * Checks whether a value is an array or contains one anywhere in its shape.
365
+ *
366
+ * Hint: The inner conditionals (`TValue extends readonly unknown[]` and
367
+ * `TValue extends Record<PropertyKey, unknown>`) distribute over union members,
368
+ * so the inner expression returns the union of each member's result (e.g.
369
+ * `true | false` when some members contain arrays and others don't).
370
+ * Downstream code uses `IsOrHasArray<T> extends true`, but
371
+ * `boolean extends true` is `false` — so we collapse the result via
372
+ * `true extends ...`, which is `true` whenever at least one union member
373
+ * contributed `true`.
335
374
  */
336
- 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;
375
+ 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;
337
376
  /**
338
377
  * Extracts the exact keys of a tuple, array or object that contain arrays.
339
378
  */
340
- 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;
379
+ 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;
380
+ /**
381
+ * Returns the flat object of indexable properties of `TValue` whose values
382
+ * are or contain arrays. Mirrors `PropertiesOf` but keyed by
383
+ * `ExactKeysOfArrayPath` so the lookup is provably valid for array-path
384
+ * navigation in `LazyArrayPath`.
385
+ */
386
+ type PropertiesOfArrayPath<TValue> = { [TKey in ExactKeysOfArrayPath<TValue>]: TValue extends Record<TKey, infer TItem> ? TItem : never };
341
387
  /**
342
388
  * Lazily evaluates only the first valid array path segment based on the given value.
343
389
  */
344
- 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;
390
+ 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;
345
391
  /**
346
392
  * Returns the path if valid, otherwise the first possible valid array path
347
393
  * based on the given value.
@@ -959,14 +1005,14 @@ interface FieldProps<TSchema extends Schema = Schema, TFieldPath extends Require
959
1005
  readonly path: ValidPath<v.InferInput<TSchema>, TFieldPath>;
960
1006
  }
961
1007
  declare const __VLS_export$2: <TSchema extends Schema, TFieldPath extends RequiredPath>(__VLS_props: NonNullable<Awaited<typeof __VLS_setup>>["props"], __VLS_ctx?: __VLS_PrettifyLocal$2<Pick<NonNullable<Awaited<typeof __VLS_setup>>, "attrs" | "emit" | "slots">>, __VLS_expose?: NonNullable<Awaited<typeof __VLS_setup>>["expose"], __VLS_setup?: Promise<{
962
- props: __VLS_PrettifyLocal$2<FieldProps<TSchema, TFieldPath>> & vue1.PublicProps;
1008
+ props: __VLS_PrettifyLocal$2<FieldProps<TSchema, TFieldPath>> & vue3.PublicProps;
963
1009
  expose: (exposed: {}) => void;
964
1010
  attrs: any;
965
1011
  slots: {
966
1012
  default(props: FieldStore<TSchema, TFieldPath>): any;
967
1013
  };
968
1014
  emit: {};
969
- }>) => vue1.VNode & {
1015
+ }>) => vue3.VNode & {
970
1016
  __ctx?: Awaited<typeof __VLS_setup>;
971
1017
  };
972
1018
  declare const _default: typeof __VLS_export$2;
@@ -987,14 +1033,14 @@ interface FieldArrayProps<TSchema extends Schema = Schema, TFieldArrayPath exten
987
1033
  readonly path: ValidArrayPath<v.InferInput<TSchema>, TFieldArrayPath>;
988
1034
  }
989
1035
  declare const __VLS_export$1: <TSchema extends Schema, TFieldArrayPath extends RequiredPath>(__VLS_props: NonNullable<Awaited<typeof __VLS_setup>>["props"], __VLS_ctx?: __VLS_PrettifyLocal$1<Pick<NonNullable<Awaited<typeof __VLS_setup>>, "attrs" | "emit" | "slots">>, __VLS_expose?: NonNullable<Awaited<typeof __VLS_setup>>["expose"], __VLS_setup?: Promise<{
990
- props: __VLS_PrettifyLocal$1<FieldArrayProps<TSchema, TFieldArrayPath>> & vue1.PublicProps;
1036
+ props: __VLS_PrettifyLocal$1<FieldArrayProps<TSchema, TFieldArrayPath>> & vue3.PublicProps;
991
1037
  expose: (exposed: {}) => void;
992
1038
  attrs: any;
993
1039
  slots: {
994
1040
  default(props: FieldArrayStore<TSchema, TFieldArrayPath>): any;
995
1041
  };
996
1042
  emit: {};
997
- }>) => vue1.VNode & {
1043
+ }>) => vue3.VNode & {
998
1044
  __ctx?: Awaited<typeof __VLS_setup>;
999
1045
  };
1000
1046
  declare const _default$1: typeof __VLS_export$1;
@@ -1015,14 +1061,14 @@ interface FormProps<TSchema extends Schema = Schema> {
1015
1061
  onSubmit: SubmitEventHandler<TSchema>;
1016
1062
  }
1017
1063
  declare const __VLS_export: <TSchema extends Schema = Schema>(__VLS_props: NonNullable<Awaited<typeof __VLS_setup>>["props"], __VLS_ctx?: __VLS_PrettifyLocal<Pick<NonNullable<Awaited<typeof __VLS_setup>>, "attrs" | "emit" | "slots">>, __VLS_expose?: NonNullable<Awaited<typeof __VLS_setup>>["expose"], __VLS_setup?: Promise<{
1018
- props: __VLS_PrettifyLocal<FormProps<TSchema>> & vue1.PublicProps;
1064
+ props: __VLS_PrettifyLocal<FormProps<TSchema>> & vue3.PublicProps;
1019
1065
  expose: (exposed: {}) => void;
1020
1066
  attrs: any;
1021
1067
  slots: {
1022
1068
  default?: (props: {}) => any;
1023
1069
  };
1024
1070
  emit: {};
1025
- }>) => vue1.VNode & {
1071
+ }>) => vue3.VNode & {
1026
1072
  __ctx?: Awaited<typeof __VLS_setup>;
1027
1073
  };
1028
1074
  declare const _default$2: typeof __VLS_export;
package/dist/index.js CHANGED
@@ -96,7 +96,7 @@ function initializeFieldStore(internalFieldStore, schema, initialInput, path, nu
96
96
  if (internalFieldStore.kind === "object") {
97
97
  internalFieldStore.children ??= {};
98
98
  for (const key in schema.entries) {
99
- internalFieldStore.children[key] = {};
99
+ internalFieldStore.children[key] ??= {};
100
100
  path.push(key);
101
101
  initializeFieldStore(internalFieldStore.children[key], schema.entries[key], initialInput?.[key], path);
102
102
  path.pop();
@@ -107,6 +107,7 @@ function initializeFieldStore(internalFieldStore, schema, initialInput, path, nu
107
107
  internalFieldStore.input = createSignal(objectInput);
108
108
  }
109
109
  } else {
110
+ if (internalFieldStore.kind && internalFieldStore.kind !== "value") throw new Error(`Store initialized as "${internalFieldStore.kind}" cannot be reinitialized as "value"`);
110
111
  internalFieldStore.kind = "value";
111
112
  if (internalFieldStore.kind === "value") {
112
113
  internalFieldStore.initialInput = createSignal(initialInput);
@@ -551,6 +552,7 @@ function focus(form, config) {
551
552
  function getAllErrors(form) {
552
553
  let allErrors = null;
553
554
  walkFieldStore(form[INTERNAL], (internalFieldStore) => {
555
+ if (internalFieldStore.kind === "array") internalFieldStore.items.value;
554
556
  const errors = internalFieldStore.errors.value;
555
557
  if (errors) if (allErrors) allErrors.push(...errors);
556
558
  else allErrors = [...errors];
@@ -603,7 +605,15 @@ function insert(form, config) {
603
605
  const newItems = [...items];
604
606
  newItems.splice(insertIndex, 0, createId());
605
607
  internalArrayStore.items.value = newItems;
606
- for (let index = items.length; index > insertIndex; index--) copyItemState(internalArrayStore.children[index - 1], internalArrayStore.children[index]);
608
+ for (let index = items.length; index > insertIndex; index--) {
609
+ if (!internalArrayStore.children[index]) {
610
+ const path = JSON.parse(internalArrayStore.name);
611
+ internalArrayStore.children[index] = {};
612
+ path.push(index);
613
+ initializeFieldStore(internalArrayStore.children[index], internalArrayStore.schema.item, void 0, path);
614
+ }
615
+ copyItemState(internalArrayStore.children[index - 1], internalArrayStore.children[index]);
616
+ }
607
617
  if (!internalArrayStore.children[insertIndex]) {
608
618
  const path = JSON.parse(internalArrayStore.name);
609
619
  internalArrayStore.children[insertIndex] = {};
@@ -688,7 +698,7 @@ function reset(form, config) {
688
698
  untrack(() => {
689
699
  const internalFormStore = form[INTERNAL];
690
700
  const internalFieldStore = config?.path ? getFieldStore(internalFormStore, config.path) : internalFormStore;
691
- if (config?.initialInput) setInitialFieldInput(internalFieldStore, config.initialInput);
701
+ if (config && "initialInput" in config) setInitialFieldInput(internalFieldStore, config.initialInput);
692
702
  walkFieldStore(internalFieldStore, (internalFieldStore$1) => {
693
703
  internalFieldStore$1.elements = internalFieldStore$1.initialElements;
694
704
  if (!config?.keepErrors) internalFieldStore$1.errors.value = null;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@formisch/vue",
3
- "description": "The modular and type-safe form library for Vue",
4
- "version": "0.7.4",
3
+ "description": "The lightweight, schema-first, and fully type-safe form library for Vue",
4
+ "version": "0.7.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",
@@ -44,14 +45,19 @@
44
45
  "@formisch/core": "workspace:*",
45
46
  "@formisch/eslint-config": "workspace:*",
46
47
  "@formisch/methods": "workspace:*",
48
+ "@testing-library/jest-dom": "^6.6.0",
47
49
  "@types/node": "^24.1.0",
50
+ "@vitest/coverage-v8": "^3.2.4",
48
51
  "@vue/eslint-config-typescript": "^14.6.0",
52
+ "@vue/test-utils": "^2.4.6",
49
53
  "eslint": "^9.32.0",
50
54
  "eslint-plugin-vue": "~10.3.0",
55
+ "jsdom": "^26.1.0",
51
56
  "tsdown": "^0.16.8",
52
57
  "typescript": "~5.8.3",
53
58
  "unplugin-vue": "^7.0.0",
54
59
  "valibot": "^1.2.0",
60
+ "vitest": "^3.2.4",
55
61
  "vue": "^3.5.18",
56
62
  "vue-tsc": "^3.0.4"
57
63
  },