@formisch/preact 0.2.0 → 0.3.1

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 CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  Formisch is a schema-based, headless form library for Preact. It manages form state and validation. It is type-safe, fast by default and its bundle size is small due to its modular design. Try it out in our [playground](https://stackblitz.com/edit/formisch-playground-preact)!
4
4
 
5
+ Formisch is also available for [Qwik][formisch-qwik], [SolidJS][formisch-solid], [Svelte][formisch-svelte] and [Vue][formisch-vue].
6
+
5
7
  ## Highlights
6
8
 
7
9
  - Small bundle size starting at 2.5 kB
@@ -55,6 +57,16 @@ export default function LoginPage() {
55
57
 
56
58
  In addition, Formisch offers several functions (we call them "methods") that can be used to read and manipulate the form state. These include `focus`, `getErrors`, `getAllErrors`, `getInput`, `insert`, `move`, `remove`, `replace`, `reset`, `setErrors`, `setInput`, `submit`, `swap` and `validate`. These methods allow you to control the form programmatically.
57
59
 
60
+ ## Comparison
61
+
62
+ What makes Formisch unique is its framework-agnostic core, which is fully native to the framework you are using. It works by inserting framework-specific reactivity blocks when the core package is built. The result is a small bundle size and native performance for any UI update. This feature, along with a few others, distinguishes Formisch from other form libraries. My vision for Formisch is to create a framework-agnostic platform similar to [Vite](https://vite.dev/), but for forms.
63
+
64
+ ## Partners
65
+
66
+ Thanks to our partners who support the development! [Join them](https://github.com/sponsors/fabian-hiller) and contribute to the sustainability of open source software!
67
+
68
+ ![Partners of Formisch](https://github.com/fabian-hiller/formisch/blob/main/partners.webp?raw=true)
69
+
58
70
  ## Feedback
59
71
 
60
72
  Find a bug or have an idea how to improve the library? Please fill out an [issue](https://github.com/fabian-hiller/formisch/issues/new). Together we can make forms even better!
@@ -62,3 +74,8 @@ Find a bug or have an idea how to improve the library? Please fill out an [issue
62
74
  ## License
63
75
 
64
76
  This project is available free of charge and licensed under the [MIT license](https://github.com/fabian-hiller/formisch/blob/main/LICENSE.md).
77
+
78
+ [formisch-qwik]: https://github.com/fabian-hiller/formisch/tree/main/frameworks/qwik
79
+ [formisch-solid]: https://github.com/fabian-hiller/formisch/tree/main/frameworks/solid
80
+ [formisch-svelte]: https://github.com/fabian-hiller/formisch/tree/main/frameworks/svelte
81
+ [formisch-vue]: https://github.com/fabian-hiller/formisch/tree/main/frameworks/vue
package/dist/index.d.ts CHANGED
@@ -93,7 +93,7 @@ interface InternalFormStore<TSchema extends Schema = Schema> extends InternalObj
93
93
  interface BaseFormStore<TSchema extends Schema = Schema> {
94
94
  [INTERNAL]: InternalFormStore<TSchema>;
95
95
  }
96
- type SubmitHandler<TSchema extends Schema> = (output: v.InferOutput<TSchema>, event: SubmitEvent) => MaybePromise<void>;
96
+ type SubmitHandler<TSchema extends Schema> = (output: v.InferOutput<TSchema>, event: SubmitEvent) => MaybePromise<unknown>;
97
97
  //#endregion
98
98
  //#region src/types/path.d.ts
99
99
  /**
@@ -123,16 +123,16 @@ type MergeUnion<T> = { [K in KeyOf<T>]: T extends Record<K, infer V> ? V : never
123
123
  /**
124
124
  * Lazily evaluate only the first valid path segment based on the given value.
125
125
  */
126
- 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<MergeUnion<TValue>[TFirstKey], TPathRest, readonly [...TValidPath, TFirstKey]> : IsNever<KeyOf<TValue>> extends false ? readonly [...TValidPath, KeyOf<TValue>] : TValidPath;
126
+ 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;
127
127
  /**
128
128
  * Returns the path if valid, otherwise the first possible valid path based on
129
129
  * the given value.
130
130
  */
131
- type ValidPath<TValue, TPath extends RequiredPath> = TPath extends LazyPath<TValue, TPath> ? TPath : LazyPath<TValue, TPath>;
131
+ type ValidPath<TValue, TPath extends RequiredPath> = TPath extends LazyPath<Required<TValue>, TPath> ? TPath : LazyPath<Required<TValue>, TPath>;
132
132
  /**
133
133
  * Extracts the value type at the given path.
134
134
  */
135
- type PathValue<TValue, TPath extends Path> = TPath extends readonly [infer TKey, ...infer TRest extends Path] ? TKey extends KeyOf<TValue> ? PathValue<MergeUnion<TValue>[TKey], TRest> : unknown : TValue;
135
+ 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;
136
136
  /**
137
137
  * Checks if a value is an array or contains one.
138
138
  */
@@ -140,16 +140,16 @@ type IsOrHasArray<TValue> = IsAny<TValue> extends true ? false : TValue extends
140
140
  /**
141
141
  * Extracts the exact keys of a tuple, array or object that contain arrays.
142
142
  */
143
- 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<TValue[TKey]> extends true ? TIndex : never : never }[number] : TValue extends Record<string, unknown> ? { [TKey in keyof TValue]: IsOrHasArray<TValue[TKey]> extends true ? TKey : never }[keyof TValue] & PathKey : never;
143
+ 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;
144
144
  /**
145
145
  * Lazily evaluate only the first valid array path segment based on the given value.
146
146
  */
147
- 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<MergeUnion<TValue>[TFirstKey], TPathRest, readonly [...TValidPath, TFirstKey]> : IsNever<KeyOfArrayPath<TValue>> extends false ? readonly [...TValidPath, KeyOfArrayPath<TValue>] : never;
147
+ 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;
148
148
  /**
149
149
  * Returns the path if valid, otherwise the first possible valid array path based on
150
150
  * the given value.
151
151
  */
152
- type ValidArrayPath<TValue, TPath extends RequiredPath> = TPath extends LazyArrayPath<TValue, TPath> ? TPath : LazyArrayPath<TValue, TPath>;
152
+ type ValidArrayPath<TValue, TPath extends RequiredPath> = TPath extends LazyArrayPath<Required<TValue>, TPath> ? TPath : LazyArrayPath<Required<TValue>, TPath>;
153
153
  //#endregion
154
154
  //#region src/array/copyItemState/copyItemState.d.ts
155
155
  /**
@@ -372,7 +372,7 @@ declare function FieldArray<TSchema extends Schema, TFieldArrayPath extends Requ
372
372
  }: FieldArrayProps<TSchema, TFieldArrayPath>): JSX.Element;
373
373
  //#endregion
374
374
  //#region src/components/Form/Form.d.ts
375
- type FormProps<TSchema extends Schema = Schema> = Omit<JSX.FormHTMLAttributes<HTMLFormElement>, "onSubmit"> & {
375
+ type FormProps<TSchema extends Schema = Schema> = Omit<JSX.FormHTMLAttributes<HTMLFormElement>, "onSubmit" | "novalidate" | "noValidate"> & {
376
376
  of: FormStore<TSchema>;
377
377
  onSubmit: SubmitHandler<TSchema>;
378
378
  };
package/dist/index.js CHANGED
@@ -37,7 +37,7 @@ function initializeFieldStore(internalFieldStore, schema, initialInput, path) {
37
37
  } else for (let index = 0; index < schema.items; index++) {
38
38
  internalFieldStore.children[index] = {};
39
39
  path.push(index);
40
- initializeFieldStore(internalFieldStore.children[index], schema.items[index], initialInput && initialInput[index], path);
40
+ initializeFieldStore(internalFieldStore.children[index], schema.items[index], initialInput?.[index], path);
41
41
  path.pop();
42
42
  }
43
43
  const initialItems = internalFieldStore.children.map(createId);
@@ -55,7 +55,7 @@ function initializeFieldStore(internalFieldStore, schema, initialInput, path) {
55
55
  for (const key in schema.entries) {
56
56
  internalFieldStore.children[key] = {};
57
57
  path.push(key);
58
- initializeFieldStore(internalFieldStore.children[key], schema.entries[key], initialInput && initialInput[key], path);
58
+ initializeFieldStore(internalFieldStore.children[key], schema.entries[key], initialInput?.[key], path);
59
59
  path.pop();
60
60
  }
61
61
  }
@@ -275,25 +275,27 @@ function setFieldBool(internalFieldStore, type, bool) {
275
275
  function setFieldInput(internalFieldStore, input) {
276
276
  batch(() => {
277
277
  if (internalFieldStore.kind === "array") {
278
+ const arrayInput = input ?? [];
278
279
  const items = untrack(() => internalFieldStore.items.value);
279
- if (input.length < items.length) internalFieldStore.items.value = items.slice(0, input.length);
280
- else if (input.length > items.length) {
281
- if (input.length > internalFieldStore.children.length) {
280
+ if (arrayInput.length < items.length) internalFieldStore.items.value = items.slice(0, arrayInput.length);
281
+ else if (arrayInput.length > items.length) {
282
+ if (arrayInput.length > internalFieldStore.children.length) {
282
283
  const path = JSON.parse(internalFieldStore.name);
283
- for (let index = internalFieldStore.children.length; index < input.length; index++) {
284
+ for (let index = internalFieldStore.children.length; index < arrayInput.length; index++) {
284
285
  internalFieldStore.children[index] = {};
285
286
  path.push(index);
286
- initializeFieldStore(internalFieldStore.children[index], internalFieldStore.schema.item, input[index], path);
287
+ initializeFieldStore(internalFieldStore.children[index], internalFieldStore.schema.item, arrayInput[index], path);
287
288
  path.pop();
288
289
  }
289
290
  }
290
- internalFieldStore.items.value = [...items, ...input.slice(items.length).map(createId)];
291
+ internalFieldStore.items.value = [...items, ...arrayInput.slice(items.length).map(createId)];
291
292
  }
292
- for (let index = 0; index < items.length; index++) setFieldInput(internalFieldStore.children[index], input[index]);
293
+ for (let index = 0; index < items.length; index++) setFieldInput(internalFieldStore.children[index], arrayInput[index]);
293
294
  internalFieldStore.isDirty.value = untrack(() => internalFieldStore.startItems.value).length !== items.length;
294
- } else if (internalFieldStore.kind === "object") for (const key in internalFieldStore.children) setFieldInput(internalFieldStore.children[key], input[key]);
295
+ } else if (internalFieldStore.kind === "object") for (const key in internalFieldStore.children) setFieldInput(internalFieldStore.children[key], input?.[key]);
295
296
  else {
296
297
  internalFieldStore.input.value = input;
298
+ internalFieldStore.isTouched.value = true;
297
299
  const startInput = untrack(() => internalFieldStore.startInput.value);
298
300
  internalFieldStore.isDirty.value = startInput !== input && (startInput !== void 0 || input !== "" && !Number.isNaN(input));
299
301
  }
@@ -302,17 +304,18 @@ function setFieldInput(internalFieldStore, input) {
302
304
  function setInitialFieldInput(internalFieldStore, initialInput) {
303
305
  batch(() => {
304
306
  if (internalFieldStore.kind === "array") {
305
- if (initialInput.length > internalFieldStore.children.length) {
307
+ const initialArrayInput = initialInput ?? [];
308
+ if (initialArrayInput.length > internalFieldStore.children.length) {
306
309
  const path = JSON.parse(internalFieldStore.name);
307
- for (let index = internalFieldStore.children.length; index < initialInput.length; index++) {
310
+ for (let index = internalFieldStore.children.length; index < initialArrayInput.length; index++) {
308
311
  internalFieldStore.children[index] = {};
309
312
  path.push(index);
310
- initializeFieldStore(internalFieldStore.children[index], internalFieldStore.schema.item, initialInput[index], path);
313
+ initializeFieldStore(internalFieldStore.children[index], internalFieldStore.schema.item, initialArrayInput[index], path);
311
314
  path.pop();
312
315
  }
313
316
  }
314
- internalFieldStore.initialItems.value = initialInput.map(createId);
315
- for (let index = 0; index < internalFieldStore.children.length; index++) setInitialFieldInput(internalFieldStore.children[index], initialInput?.[index]);
317
+ internalFieldStore.initialItems.value = initialArrayInput.map(createId);
318
+ for (let index = 0; index < internalFieldStore.children.length; index++) setInitialFieldInput(internalFieldStore.children[index], initialArrayInput[index]);
316
319
  } else if (internalFieldStore.kind === "object") for (const key in internalFieldStore.children) setInitialFieldInput(internalFieldStore.children[key], initialInput?.[key]);
317
320
  else internalFieldStore.initialInput.value = initialInput;
318
321
  });
@@ -519,7 +522,6 @@ function setInput(form, config) {
519
522
  batch(() => {
520
523
  const internalFormStore = form[INTERNAL];
521
524
  const internalFieldStore = config.path ? getFieldStore(internalFormStore, config.path) : internalFormStore;
522
- setFieldBool(internalFieldStore, "isTouched", true);
523
525
  setFieldInput(internalFieldStore, config.input);
524
526
  validateIfRequired(internalFormStore, internalFieldStore, "input");
525
527
  });
@@ -622,7 +624,7 @@ function useFieldArray(form, config) {
622
624
  //#region src/hooks/useForm/useForm.ts
623
625
  function useForm(config) {
624
626
  const form = useMemo(() => {
625
- const internalFormStore = createFormStore(config, async (input) => v.safeParseAsync(config.schema, input));
627
+ const internalFormStore = createFormStore(config, (input) => v.safeParseAsync(config.schema, input));
626
628
  return {
627
629
  [INTERNAL]: internalFormStore,
628
630
  isSubmitting: internalFormStore.isSubmitting,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@formisch/preact",
3
3
  "description": "The modular and type-safe form library for Preact",
4
- "version": "0.2.0",
4
+ "version": "0.3.1",
5
5
  "license": "MIT",
6
6
  "author": "Fabian Hiller",
7
7
  "homepage": "https://formisch.dev",
@@ -32,17 +32,8 @@
32
32
  "publishConfig": {
33
33
  "access": "public"
34
34
  },
35
- "scripts": {
36
- "build": "tsdown",
37
- "lint": "eslint \"src/**/*.ts*\" && tsc --noEmit",
38
- "lint.fix": "eslint \"src/**/*.ts*\" --fix",
39
- "format": "prettier --write ./src",
40
- "format.check": "prettier --check ./src"
41
- },
42
35
  "devDependencies": {
43
36
  "@eslint/js": "^9.31.0",
44
- "@formisch/core": "workspace:*",
45
- "@formisch/methods": "workspace:*",
46
37
  "@preact/preset-vite": "^2.9.3",
47
38
  "@preact/signals": "^2.2.1",
48
39
  "eslint": "^9.31.0",
@@ -51,7 +42,9 @@
51
42
  "tsdown": "^0.12.9",
52
43
  "typescript": "^5.8.3",
53
44
  "typescript-eslint": "^8.37.0",
54
- "vite": "^6.0.4"
45
+ "vite": "^6.0.4",
46
+ "@formisch/core": "0.3.1",
47
+ "@formisch/methods": "0.2.0"
55
48
  },
56
49
  "peerDependencies": {
57
50
  "@preact/signals": "^2.0.0",
@@ -63,5 +56,12 @@
63
56
  "typescript": {
64
57
  "optional": true
65
58
  }
59
+ },
60
+ "scripts": {
61
+ "build": "tsdown",
62
+ "lint": "eslint \"src/**/*.ts*\" && tsc --noEmit",
63
+ "lint.fix": "eslint \"src/**/*.ts*\" --fix",
64
+ "format": "prettier --write ./src",
65
+ "format.check": "prettier --check ./src"
66
66
  }
67
- }
67
+ }