@formisch/solid 0.9.6 → 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 CHANGED
@@ -9,6 +9,7 @@ Formisch is also available for [Preact][formisch-preact], [Qwik][formisch-qwik],
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
@@ -59,7 +60,13 @@ In addition, Formisch offers several functions (we call them "methods") that can
59
60
 
60
61
  ## Comparison
61
62
 
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
+ 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, giving you native performance for any UI update. A modular methods API keeps bundles starting at just ~2.5 kB by only including the methods you import, and end-to-end type safety covers deeply nested paths and field arrays with TypeScript inference that stays fast even as forms grow.
64
+
65
+ For a side-by-side look at how Formisch compares to Felte and TanStack Form, see the [comparison guide](https://formisch.dev/solid/guides/comparison/).
66
+
67
+ ## Vision
68
+
69
+ My vision for Formisch is to create a framework-agnostic platform similar to [Vite](https://vite.dev/), but for forms — a shared core that lets the same mental model and codebase work natively across every modern UI framework.
63
70
 
64
71
  ## Partners
65
72
 
package/dist/dev.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import * as v from 'valibot';
2
2
  import { untrack, batch, createUniqueId, createMemo, onCleanup, splitProps, createSignal as createSignal$1 } from 'solid-js';
3
- import { memo, template, use, spread, mergeProps } from 'solid-js/web';
3
+ import { memo, use, spread, mergeProps, template } from 'solid-js/web';
4
4
 
5
5
  // ../../packages/core/dist/index.solid.js
6
6
  var framework = "solid";
@@ -113,29 +113,40 @@ function copyItemState(fromInternalFieldStore, toInternalFieldStore) {
113
113
  });
114
114
  });
115
115
  }
116
- function resetItemState(internalFieldStore, initialInput) {
116
+ function resetItemState(internalFieldStore, input, keepStart = false) {
117
117
  batch(() => {
118
- internalFieldStore.elements = [];
118
+ const elements = [];
119
+ if (internalFieldStore.elements === internalFieldStore.initialElements) internalFieldStore.initialElements = elements;
120
+ internalFieldStore.elements = elements;
119
121
  internalFieldStore.errors.value = null;
120
122
  internalFieldStore.isTouched.value = false;
121
123
  internalFieldStore.isDirty.value = false;
122
124
  if (internalFieldStore.kind === "array" || internalFieldStore.kind === "object") {
123
- const objectInput = initialInput == null ? initialInput : true;
124
- internalFieldStore.startInput.value = objectInput;
125
+ const objectInput = input == null ? input : true;
126
+ if (!keepStart) internalFieldStore.startInput.value = objectInput;
125
127
  internalFieldStore.input.value = objectInput;
126
- if (internalFieldStore.kind === "array") if (initialInput) {
127
- const newItems = initialInput.map(createUniqueId);
128
- internalFieldStore.startItems.value = newItems;
128
+ if (internalFieldStore.kind === "array") if (input) {
129
+ const length = internalFieldStore.schema.type === "array" ? input.length : internalFieldStore.children.length;
130
+ const newItems = Array.from({ length }, createUniqueId);
131
+ if (!keepStart) internalFieldStore.startItems.value = newItems;
129
132
  internalFieldStore.items.value = newItems;
130
- for (let index = 0; index < initialInput.length; index++) if (internalFieldStore.children[index]) resetItemState(internalFieldStore.children[index], initialInput[index]);
133
+ let path;
134
+ for (let index = 0; index < length; index++) if (internalFieldStore.children[index]) resetItemState(internalFieldStore.children[index], input[index], keepStart);
135
+ else {
136
+ path ??= JSON.parse(internalFieldStore.name);
137
+ internalFieldStore.children[index] = {};
138
+ path.push(index);
139
+ initializeFieldStore(internalFieldStore.children[index], internalFieldStore.schema.item, input[index], path);
140
+ path.pop();
141
+ }
131
142
  } else {
132
- internalFieldStore.startItems.value = [];
143
+ if (!keepStart) internalFieldStore.startItems.value = [];
133
144
  internalFieldStore.items.value = [];
134
145
  }
135
- else for (const key in internalFieldStore.children) resetItemState(internalFieldStore.children[key], initialInput?.[key]);
146
+ else for (const key in internalFieldStore.children) resetItemState(internalFieldStore.children[key], input?.[key], keepStart);
136
147
  } else {
137
- internalFieldStore.startInput.value = initialInput;
138
- internalFieldStore.input.value = initialInput;
148
+ if (!keepStart) internalFieldStore.startInput.value = input;
149
+ internalFieldStore.input.value = input;
139
150
  }
140
151
  });
141
152
  }
@@ -192,6 +203,50 @@ function swapItemState(firstInternalFieldStore, secondInternalFieldStore) {
192
203
  });
193
204
  });
194
205
  }
206
+ function focusFieldElement(internalFieldStore) {
207
+ for (const element of internalFieldStore.elements) {
208
+ element.focus();
209
+ if (element.getRootNode().activeElement === element) return true;
210
+ }
211
+ return false;
212
+ }
213
+ // @__NO_SIDE_EFFECTS__
214
+ function getFieldBool(internalFieldStore, type) {
215
+ if (internalFieldStore[type].value) return true;
216
+ if (internalFieldStore.kind === "array") {
217
+ for (let index = 0; index < internalFieldStore.items.value.length; index++) if (/* @__PURE__ */ getFieldBool(internalFieldStore.children[index], type)) return true;
218
+ return false;
219
+ }
220
+ if (internalFieldStore.kind == "object") {
221
+ for (const key in internalFieldStore.children) if (/* @__PURE__ */ getFieldBool(internalFieldStore.children[key], type)) return true;
222
+ return false;
223
+ }
224
+ return false;
225
+ }
226
+ // @__NO_SIDE_EFFECTS__
227
+ function getDirtyFieldInput(internalFieldStore, dirtyOnly = true) {
228
+ if (dirtyOnly && !/* @__PURE__ */ getFieldBool(internalFieldStore, "isDirty")) return;
229
+ if (internalFieldStore.kind === "array") {
230
+ if (internalFieldStore.input.value) {
231
+ const value = [];
232
+ for (let index = 0; index < internalFieldStore.items.value.length; index++) value[index] = /* @__PURE__ */ getDirtyFieldInput(internalFieldStore.children[index], false);
233
+ return value;
234
+ }
235
+ return internalFieldStore.input.value;
236
+ }
237
+ if (internalFieldStore.kind === "object") {
238
+ if (internalFieldStore.input.value) {
239
+ const value = {};
240
+ for (const key in internalFieldStore.children) {
241
+ const child = internalFieldStore.children[key];
242
+ if (!dirtyOnly || /* @__PURE__ */ getFieldBool(child, "isDirty")) value[key] = /* @__PURE__ */ getDirtyFieldInput(child, dirtyOnly);
243
+ }
244
+ return value;
245
+ }
246
+ return internalFieldStore.input.value;
247
+ }
248
+ return internalFieldStore.input.value;
249
+ }
195
250
  // @__NO_SIDE_EFFECTS__
196
251
  function getFieldInput(internalFieldStore) {
197
252
  if (internalFieldStore.kind === "array") {
@@ -231,19 +286,6 @@ function getElementInput(element, internalFieldStore) {
231
286
  return element.value;
232
287
  }
233
288
  // @__NO_SIDE_EFFECTS__
234
- function getFieldBool(internalFieldStore, type) {
235
- if (internalFieldStore[type].value) return true;
236
- if (internalFieldStore.kind === "array") {
237
- for (let index = 0; index < internalFieldStore.items.value.length; index++) if (/* @__PURE__ */ getFieldBool(internalFieldStore.children[index], type)) return true;
238
- return false;
239
- }
240
- if (internalFieldStore.kind == "object") {
241
- for (const key in internalFieldStore.children) if (/* @__PURE__ */ getFieldBool(internalFieldStore.children[key], type)) return true;
242
- return false;
243
- }
244
- return false;
245
- }
246
- // @__NO_SIDE_EFFECTS__
247
289
  function getFieldStore(internalFormStore, path) {
248
290
  let internalFieldStore = internalFormStore;
249
291
  for (const key of path) internalFieldStore = internalFieldStore.children[key];
@@ -251,11 +293,9 @@ function getFieldStore(internalFormStore, path) {
251
293
  }
252
294
  function setFieldBool(internalFieldStore, type, bool) {
253
295
  batch(() => {
254
- if (internalFieldStore.kind === "array") {
255
- internalFieldStore[type].value = bool;
256
- for (let index = 0; index < untrack(() => internalFieldStore.items.value).length; index++) setFieldBool(internalFieldStore.children[index], type, bool);
257
- } else if (internalFieldStore.kind == "object") for (const key in internalFieldStore.children) setFieldBool(internalFieldStore.children[key], type, bool);
258
- else internalFieldStore[type].value = bool;
296
+ internalFieldStore[type].value = bool;
297
+ if (internalFieldStore.kind === "array") for (let index = 0; index < untrack(() => internalFieldStore.items.value).length; index++) setFieldBool(internalFieldStore.children[index], type, bool);
298
+ else if (internalFieldStore.kind === "object") for (const key in internalFieldStore.children) setFieldBool(internalFieldStore.children[key], type, bool);
259
299
  });
260
300
  }
261
301
  function setNestedInput(internalFieldStore, input) {
@@ -263,20 +303,20 @@ function setNestedInput(internalFieldStore, input) {
263
303
  if (internalFieldStore.kind === "array") {
264
304
  const arrayInput = input ?? [];
265
305
  const items = internalFieldStore.items.value;
266
- if (arrayInput.length < items.length) internalFieldStore.items.value = items.slice(0, arrayInput.length);
267
- else if (arrayInput.length > items.length) {
268
- if (arrayInput.length > internalFieldStore.children.length) {
269
- const path = JSON.parse(internalFieldStore.name);
270
- for (let index = internalFieldStore.children.length; index < arrayInput.length; index++) {
271
- internalFieldStore.children[index] = {};
272
- path.push(index);
273
- initializeFieldStore(internalFieldStore.children[index], internalFieldStore.schema.item, arrayInput[index], path);
274
- path.pop();
275
- }
306
+ const length = internalFieldStore.schema.type === "array" ? arrayInput.length : internalFieldStore.children.length;
307
+ if (length < items.length) internalFieldStore.items.value = items.slice(0, length);
308
+ else if (length > items.length) {
309
+ const path = JSON.parse(internalFieldStore.name);
310
+ for (let index = items.length; index < length; index++) if (internalFieldStore.children[index]) resetItemState(internalFieldStore.children[index], arrayInput[index], true);
311
+ else {
312
+ internalFieldStore.children[index] = {};
313
+ path.push(index);
314
+ initializeFieldStore(internalFieldStore.children[index], internalFieldStore.schema.item, arrayInput[index], path);
315
+ path.pop();
276
316
  }
277
- internalFieldStore.items.value = [...items, ...arrayInput.slice(items.length).map(createUniqueId)];
317
+ internalFieldStore.items.value = [...items, ...Array.from({ length: length - items.length }, createUniqueId)];
278
318
  }
279
- for (let index = 0; index < arrayInput.length; index++) setNestedInput(internalFieldStore.children[index], arrayInput[index]);
319
+ for (let index = 0; index < length; index++) setNestedInput(internalFieldStore.children[index], arrayInput[index]);
280
320
  internalFieldStore.input.value = input == null ? input : true;
281
321
  internalFieldStore.isDirty.value = internalFieldStore.startInput.value !== internalFieldStore.input.value || internalFieldStore.startItems.value.length !== internalFieldStore.items.value.length;
282
322
  } else if (internalFieldStore.kind === "object") {
@@ -304,21 +344,22 @@ function setFieldInput(internalFormStore, path, input) {
304
344
  function setInitialFieldInput(internalFieldStore, initialInput) {
305
345
  batch(() => {
306
346
  if (internalFieldStore.kind === "array") {
307
- internalFieldStore.input.value = initialInput == null ? initialInput : true;
347
+ internalFieldStore.initialInput.value = initialInput == null ? initialInput : true;
308
348
  const initialArrayInput = initialInput ?? [];
309
- if (initialArrayInput.length > internalFieldStore.children.length) {
349
+ const length = internalFieldStore.schema.type === "array" ? initialArrayInput.length : internalFieldStore.children.length;
350
+ if (length > internalFieldStore.children.length) {
310
351
  const path = JSON.parse(internalFieldStore.name);
311
- for (let index = internalFieldStore.children.length; index < initialArrayInput.length; index++) {
352
+ for (let index = internalFieldStore.children.length; index < length; index++) {
312
353
  internalFieldStore.children[index] = {};
313
354
  path.push(index);
314
355
  initializeFieldStore(internalFieldStore.children[index], internalFieldStore.schema.item, initialArrayInput[index], path);
315
356
  path.pop();
316
357
  }
317
358
  }
318
- internalFieldStore.initialItems.value = initialArrayInput.map(createUniqueId);
359
+ internalFieldStore.initialItems.value = Array.from({ length }, createUniqueId);
319
360
  for (let index = 0; index < internalFieldStore.children.length; index++) setInitialFieldInput(internalFieldStore.children[index], initialArrayInput[index]);
320
361
  } else if (internalFieldStore.kind === "object") {
321
- internalFieldStore.input.value = initialInput == null ? initialInput : true;
362
+ internalFieldStore.initialInput.value = initialInput == null ? initialInput : true;
322
363
  for (const key in internalFieldStore.children) setInitialFieldInput(internalFieldStore.children[key], initialInput?.[key]);
323
364
  } else internalFieldStore.initialInput.value = initialInput;
324
365
  });
@@ -343,44 +384,49 @@ function createFormStore(config, parse) {
343
384
  async function validateFormInput(internalFormStore, config) {
344
385
  internalFormStore.validators++;
345
386
  internalFormStore.isValidating.value = true;
346
- const result = await internalFormStore.parse(untrack(() => /* @__PURE__ */ getFieldInput(internalFormStore)));
347
- let rootErrors;
348
- let nestedErrors;
349
- if (result.issues) {
350
- nestedErrors = {};
351
- for (const issue of result.issues) if (issue.path) {
352
- const path = [];
353
- for (const pathItem of issue.path) {
354
- const key = pathItem.key;
355
- const keyType = typeof key;
356
- const itemType = pathItem.type;
357
- if (keyType !== "string" && keyType !== "number" || itemType === "map" || itemType === "set") break;
358
- path.push(key);
359
- }
360
- const name = JSON.stringify(path);
361
- const fieldErrors = nestedErrors[name];
362
- if (fieldErrors) fieldErrors.push(issue.message);
363
- else nestedErrors[name] = [issue.message];
364
- } else if (rootErrors) rootErrors.push(issue.message);
365
- else rootErrors = [issue.message];
366
- }
367
- let shouldFocus = config?.shouldFocus ?? false;
368
- batch(() => {
369
- walkFieldStore(internalFormStore, (internalFieldStore) => {
370
- if (internalFieldStore.name === "[]") internalFieldStore.errors.value = rootErrors ?? null;
371
- else {
372
- const fieldErrors = nestedErrors?.[internalFieldStore.name] ?? null;
373
- internalFieldStore.errors.value = fieldErrors;
374
- if (shouldFocus && fieldErrors) {
375
- internalFieldStore.elements[0]?.focus();
376
- shouldFocus = false;
387
+ try {
388
+ const result = await internalFormStore.parse(untrack(() => /* @__PURE__ */ getFieldInput(internalFormStore)));
389
+ let rootErrors;
390
+ let nestedErrors;
391
+ if (result.issues) {
392
+ nestedErrors = {};
393
+ for (const issue of result.issues) if (issue.path) {
394
+ const path = [];
395
+ for (const pathItem of issue.path) {
396
+ const key = pathItem.key;
397
+ const keyType = typeof key;
398
+ const itemType = pathItem.type;
399
+ if (keyType !== "string" && keyType !== "number" || itemType === "map" || itemType === "set") break;
400
+ path.push(key);
377
401
  }
378
- }
402
+ const name = JSON.stringify(path);
403
+ const fieldErrors = nestedErrors[name];
404
+ if (fieldErrors) fieldErrors.push(issue.message);
405
+ else nestedErrors[name] = [issue.message];
406
+ } else if (rootErrors) rootErrors.push(issue.message);
407
+ else rootErrors = [issue.message];
408
+ }
409
+ let shouldFocus = config?.shouldFocus ?? false;
410
+ batch(() => {
411
+ walkFieldStore(internalFormStore, (internalFieldStore) => {
412
+ if (internalFieldStore.name === "[]") internalFieldStore.errors.value = rootErrors ?? null;
413
+ else {
414
+ const fieldErrors = nestedErrors?.[internalFieldStore.name] ?? null;
415
+ internalFieldStore.errors.value = fieldErrors;
416
+ if (shouldFocus && fieldErrors && focusFieldElement(internalFieldStore)) shouldFocus = false;
417
+ }
418
+ });
419
+ internalFormStore.validators--;
420
+ internalFormStore.isValidating.value = internalFormStore.validators > 0;
379
421
  });
380
- internalFormStore.validators--;
381
- internalFormStore.isValidating.value = internalFormStore.validators > 0;
382
- });
383
- return result;
422
+ return result;
423
+ } catch (error) {
424
+ batch(() => {
425
+ internalFormStore.validators--;
426
+ internalFormStore.isValidating.value = internalFormStore.validators > 0;
427
+ });
428
+ throw error;
429
+ }
384
430
  }
385
431
  function validateIfRequired(internalFormStore, internalFieldStore, validationMode) {
386
432
  if (validationMode === (internalFormStore.validate === "initial" || (internalFormStore.validate === "submit" ? untrack(() => internalFormStore.isSubmitted.value) : untrack(() => /* @__PURE__ */ getFieldBool(internalFieldStore, "errors"))) ? internalFormStore.revalidate : internalFormStore.validate)) validateFormInput(internalFormStore);
@@ -389,7 +435,7 @@ var INTERNAL = "~internal";
389
435
 
390
436
  // ../../packages/methods/dist/index.solid.js
391
437
  function focus(form, config) {
392
- getFieldStore(form[INTERNAL], config.path).elements[0]?.focus();
438
+ focusFieldElement(getFieldStore(form[INTERNAL], config.path));
393
439
  }
394
440
  // @__NO_SIDE_EFFECTS__
395
441
  function getAllErrors(form) {
@@ -403,6 +449,17 @@ function getAllErrors(form) {
403
449
  return allErrors;
404
450
  }
405
451
  // @__NO_SIDE_EFFECTS__
452
+ function getDirtyInput(form, config) {
453
+ return getDirtyFieldInput(config?.path ? getFieldStore(form[INTERNAL], config.path) : form[INTERNAL]);
454
+ }
455
+ // @__NO_SIDE_EFFECTS__
456
+ function getDirtyPaths(form, config) {
457
+ config?.path ? getFieldStore(form[INTERNAL], config.path) : form[INTERNAL];
458
+ const paths = [];
459
+ config?.path && [...config.path];
460
+ return paths;
461
+ }
462
+ // @__NO_SIDE_EFFECTS__
406
463
  function getErrors(form, config) {
407
464
  return (config?.path ? getFieldStore(form[INTERNAL], config.path) : form[INTERNAL]).errors.value;
408
465
  }
@@ -481,6 +538,24 @@ function move(form, config) {
481
538
  validateIfRequired(internalFormStore, internalArrayStore, "input");
482
539
  });
483
540
  }
541
+ // @__NO_SIDE_EFFECTS__
542
+ function pickDirty(form, config) {
543
+ if (!getFieldBool(form[INTERNAL], "isDirty")) return;
544
+ const result = /* @__PURE__ */ pickFieldValue(form[INTERNAL], config.from);
545
+ return Object.keys(result).length ? result : void 0;
546
+ }
547
+ // @__NO_SIDE_EFFECTS__
548
+ function pickFieldValue(internalFieldStore, value) {
549
+ if (internalFieldStore.kind === "object" && internalFieldStore.input.value && value && typeof value === "object" && !Array.isArray(value)) {
550
+ const result = {};
551
+ for (const key in internalFieldStore.children) {
552
+ const child = internalFieldStore.children[key];
553
+ if (getFieldBool(child, "isDirty") && key in value) result[key] = /* @__PURE__ */ pickFieldValue(child, value[key]);
554
+ }
555
+ return result;
556
+ }
557
+ return value;
558
+ }
484
559
  function remove(form, config) {
485
560
  const internalFormStore = form[INTERNAL];
486
561
  const internalArrayStore = getFieldStore(internalFormStore, config.path);
@@ -680,9 +755,13 @@ function useField(form, config) {
680
755
  const internalFieldStore = getInternalFieldStore();
681
756
  internalFieldStore.elements.push(element);
682
757
  onCleanup(() => {
683
- internalFieldStore.elements = internalFieldStore.elements.filter(
758
+ const elements = internalFieldStore.elements.filter(
684
759
  (el) => el !== element
685
760
  );
761
+ if (internalFieldStore.elements === internalFieldStore.initialElements) {
762
+ internalFieldStore.initialElements = elements;
763
+ }
764
+ internalFieldStore.elements = elements;
686
765
  });
687
766
  },
688
767
  onFocus() {
@@ -790,4 +869,4 @@ function Form(props) {
790
869
  })();
791
870
  }
792
871
 
793
- export { Field, FieldArray, Form, createForm, focus, getAllErrors, getErrors, getInput, handleSubmit, insert, move, remove, replace, reset, setErrors, setInput, submit, swap, useField, useFieldArray, validate };
872
+ export { Field, FieldArray, Form, createForm, focus, getAllErrors, getDirtyInput, getDirtyPaths, getErrors, getInput, handleSubmit, insert, move, pickDirty, remove, replace, reset, setErrors, setInput, submit, swap, useField, useFieldArray, validate };