@formisch/solid 0.10.0 → 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
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,13 @@ 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
+ }
195
213
  // @__NO_SIDE_EFFECTS__
196
214
  function getFieldBool(internalFieldStore, type) {
197
215
  if (internalFieldStore[type].value) return true;
@@ -275,11 +293,9 @@ function getFieldStore(internalFormStore, path) {
275
293
  }
276
294
  function setFieldBool(internalFieldStore, type, bool) {
277
295
  batch(() => {
278
- if (internalFieldStore.kind === "array") {
279
- internalFieldStore[type].value = bool;
280
- for (let index = 0; index < untrack(() => internalFieldStore.items.value).length; index++) setFieldBool(internalFieldStore.children[index], type, bool);
281
- } else if (internalFieldStore.kind == "object") for (const key in internalFieldStore.children) setFieldBool(internalFieldStore.children[key], type, bool);
282
- 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);
283
299
  });
284
300
  }
285
301
  function setNestedInput(internalFieldStore, input) {
@@ -287,20 +303,20 @@ function setNestedInput(internalFieldStore, input) {
287
303
  if (internalFieldStore.kind === "array") {
288
304
  const arrayInput = input ?? [];
289
305
  const items = internalFieldStore.items.value;
290
- if (arrayInput.length < items.length) internalFieldStore.items.value = items.slice(0, arrayInput.length);
291
- else if (arrayInput.length > items.length) {
292
- if (arrayInput.length > internalFieldStore.children.length) {
293
- const path = JSON.parse(internalFieldStore.name);
294
- for (let index = internalFieldStore.children.length; index < arrayInput.length; index++) {
295
- internalFieldStore.children[index] = {};
296
- path.push(index);
297
- initializeFieldStore(internalFieldStore.children[index], internalFieldStore.schema.item, arrayInput[index], path);
298
- path.pop();
299
- }
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();
300
316
  }
301
- internalFieldStore.items.value = [...items, ...arrayInput.slice(items.length).map(createUniqueId)];
317
+ internalFieldStore.items.value = [...items, ...Array.from({ length: length - items.length }, createUniqueId)];
302
318
  }
303
- 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]);
304
320
  internalFieldStore.input.value = input == null ? input : true;
305
321
  internalFieldStore.isDirty.value = internalFieldStore.startInput.value !== internalFieldStore.input.value || internalFieldStore.startItems.value.length !== internalFieldStore.items.value.length;
306
322
  } else if (internalFieldStore.kind === "object") {
@@ -328,21 +344,22 @@ function setFieldInput(internalFormStore, path, input) {
328
344
  function setInitialFieldInput(internalFieldStore, initialInput) {
329
345
  batch(() => {
330
346
  if (internalFieldStore.kind === "array") {
331
- internalFieldStore.input.value = initialInput == null ? initialInput : true;
347
+ internalFieldStore.initialInput.value = initialInput == null ? initialInput : true;
332
348
  const initialArrayInput = initialInput ?? [];
333
- if (initialArrayInput.length > internalFieldStore.children.length) {
349
+ const length = internalFieldStore.schema.type === "array" ? initialArrayInput.length : internalFieldStore.children.length;
350
+ if (length > internalFieldStore.children.length) {
334
351
  const path = JSON.parse(internalFieldStore.name);
335
- for (let index = internalFieldStore.children.length; index < initialArrayInput.length; index++) {
352
+ for (let index = internalFieldStore.children.length; index < length; index++) {
336
353
  internalFieldStore.children[index] = {};
337
354
  path.push(index);
338
355
  initializeFieldStore(internalFieldStore.children[index], internalFieldStore.schema.item, initialArrayInput[index], path);
339
356
  path.pop();
340
357
  }
341
358
  }
342
- internalFieldStore.initialItems.value = initialArrayInput.map(createUniqueId);
359
+ internalFieldStore.initialItems.value = Array.from({ length }, createUniqueId);
343
360
  for (let index = 0; index < internalFieldStore.children.length; index++) setInitialFieldInput(internalFieldStore.children[index], initialArrayInput[index]);
344
361
  } else if (internalFieldStore.kind === "object") {
345
- internalFieldStore.input.value = initialInput == null ? initialInput : true;
362
+ internalFieldStore.initialInput.value = initialInput == null ? initialInput : true;
346
363
  for (const key in internalFieldStore.children) setInitialFieldInput(internalFieldStore.children[key], initialInput?.[key]);
347
364
  } else internalFieldStore.initialInput.value = initialInput;
348
365
  });
@@ -367,44 +384,49 @@ function createFormStore(config, parse) {
367
384
  async function validateFormInput(internalFormStore, config) {
368
385
  internalFormStore.validators++;
369
386
  internalFormStore.isValidating.value = true;
370
- const result = await internalFormStore.parse(untrack(() => /* @__PURE__ */ getFieldInput(internalFormStore)));
371
- let rootErrors;
372
- let nestedErrors;
373
- if (result.issues) {
374
- nestedErrors = {};
375
- for (const issue of result.issues) if (issue.path) {
376
- const path = [];
377
- for (const pathItem of issue.path) {
378
- const key = pathItem.key;
379
- const keyType = typeof key;
380
- const itemType = pathItem.type;
381
- if (keyType !== "string" && keyType !== "number" || itemType === "map" || itemType === "set") break;
382
- path.push(key);
383
- }
384
- const name = JSON.stringify(path);
385
- const fieldErrors = nestedErrors[name];
386
- if (fieldErrors) fieldErrors.push(issue.message);
387
- else nestedErrors[name] = [issue.message];
388
- } else if (rootErrors) rootErrors.push(issue.message);
389
- else rootErrors = [issue.message];
390
- }
391
- let shouldFocus = config?.shouldFocus ?? false;
392
- batch(() => {
393
- walkFieldStore(internalFormStore, (internalFieldStore) => {
394
- if (internalFieldStore.name === "[]") internalFieldStore.errors.value = rootErrors ?? null;
395
- else {
396
- const fieldErrors = nestedErrors?.[internalFieldStore.name] ?? null;
397
- internalFieldStore.errors.value = fieldErrors;
398
- if (shouldFocus && fieldErrors) {
399
- internalFieldStore.elements[0]?.focus();
400
- 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);
401
401
  }
402
- }
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;
403
421
  });
404
- internalFormStore.validators--;
405
- internalFormStore.isValidating.value = internalFormStore.validators > 0;
406
- });
407
- 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
+ }
408
430
  }
409
431
  function validateIfRequired(internalFormStore, internalFieldStore, validationMode) {
410
432
  if (validationMode === (internalFormStore.validate === "initial" || (internalFormStore.validate === "submit" ? untrack(() => internalFormStore.isSubmitted.value) : untrack(() => /* @__PURE__ */ getFieldBool(internalFieldStore, "errors"))) ? internalFormStore.revalidate : internalFormStore.validate)) validateFormInput(internalFormStore);
@@ -413,7 +435,7 @@ var INTERNAL = "~internal";
413
435
 
414
436
  // ../../packages/methods/dist/index.solid.js
415
437
  function focus(form, config) {
416
- getFieldStore(form[INTERNAL], config.path).elements[0]?.focus();
438
+ focusFieldElement(getFieldStore(form[INTERNAL], config.path));
417
439
  }
418
440
  // @__NO_SIDE_EFFECTS__
419
441
  function getAllErrors(form) {
@@ -733,9 +755,13 @@ function useField(form, config) {
733
755
  const internalFieldStore = getInternalFieldStore();
734
756
  internalFieldStore.elements.push(element);
735
757
  onCleanup(() => {
736
- internalFieldStore.elements = internalFieldStore.elements.filter(
758
+ const elements = internalFieldStore.elements.filter(
737
759
  (el) => el !== element
738
760
  );
761
+ if (internalFieldStore.elements === internalFieldStore.initialElements) {
762
+ internalFieldStore.initialElements = elements;
763
+ }
764
+ internalFieldStore.elements = elements;
739
765
  });
740
766
  },
741
767
  onFocus() {
package/dist/dev.jsx CHANGED
@@ -111,29 +111,40 @@ function copyItemState(fromInternalFieldStore, toInternalFieldStore) {
111
111
  });
112
112
  });
113
113
  }
114
- function resetItemState(internalFieldStore, initialInput) {
114
+ function resetItemState(internalFieldStore, input, keepStart = false) {
115
115
  batch(() => {
116
- internalFieldStore.elements = [];
116
+ const elements = [];
117
+ if (internalFieldStore.elements === internalFieldStore.initialElements) internalFieldStore.initialElements = elements;
118
+ internalFieldStore.elements = elements;
117
119
  internalFieldStore.errors.value = null;
118
120
  internalFieldStore.isTouched.value = false;
119
121
  internalFieldStore.isDirty.value = false;
120
122
  if (internalFieldStore.kind === "array" || internalFieldStore.kind === "object") {
121
- const objectInput = initialInput == null ? initialInput : true;
122
- internalFieldStore.startInput.value = objectInput;
123
+ const objectInput = input == null ? input : true;
124
+ if (!keepStart) internalFieldStore.startInput.value = objectInput;
123
125
  internalFieldStore.input.value = objectInput;
124
- if (internalFieldStore.kind === "array") if (initialInput) {
125
- const newItems = initialInput.map(createId);
126
- internalFieldStore.startItems.value = newItems;
126
+ if (internalFieldStore.kind === "array") if (input) {
127
+ const length = internalFieldStore.schema.type === "array" ? input.length : internalFieldStore.children.length;
128
+ const newItems = Array.from({ length }, createId);
129
+ if (!keepStart) internalFieldStore.startItems.value = newItems;
127
130
  internalFieldStore.items.value = newItems;
128
- for (let index = 0; index < initialInput.length; index++) if (internalFieldStore.children[index]) resetItemState(internalFieldStore.children[index], initialInput[index]);
131
+ let path;
132
+ for (let index = 0; index < length; index++) if (internalFieldStore.children[index]) resetItemState(internalFieldStore.children[index], input[index], keepStart);
133
+ else {
134
+ path ??= JSON.parse(internalFieldStore.name);
135
+ internalFieldStore.children[index] = {};
136
+ path.push(index);
137
+ initializeFieldStore(internalFieldStore.children[index], internalFieldStore.schema.item, input[index], path);
138
+ path.pop();
139
+ }
129
140
  } else {
130
- internalFieldStore.startItems.value = [];
141
+ if (!keepStart) internalFieldStore.startItems.value = [];
131
142
  internalFieldStore.items.value = [];
132
143
  }
133
- else for (const key in internalFieldStore.children) resetItemState(internalFieldStore.children[key], initialInput?.[key]);
144
+ else for (const key in internalFieldStore.children) resetItemState(internalFieldStore.children[key], input?.[key], keepStart);
134
145
  } else {
135
- internalFieldStore.startInput.value = initialInput;
136
- internalFieldStore.input.value = initialInput;
146
+ if (!keepStart) internalFieldStore.startInput.value = input;
147
+ internalFieldStore.input.value = input;
137
148
  }
138
149
  });
139
150
  }
@@ -190,6 +201,13 @@ function swapItemState(firstInternalFieldStore, secondInternalFieldStore) {
190
201
  });
191
202
  });
192
203
  }
204
+ function focusFieldElement(internalFieldStore) {
205
+ for (const element of internalFieldStore.elements) {
206
+ element.focus();
207
+ if (element.getRootNode().activeElement === element) return true;
208
+ }
209
+ return false;
210
+ }
193
211
  // @__NO_SIDE_EFFECTS__
194
212
  function getFieldBool(internalFieldStore, type) {
195
213
  if (internalFieldStore[type].value) return true;
@@ -273,11 +291,9 @@ function getFieldStore(internalFormStore, path) {
273
291
  }
274
292
  function setFieldBool(internalFieldStore, type, bool) {
275
293
  batch(() => {
276
- if (internalFieldStore.kind === "array") {
277
- internalFieldStore[type].value = bool;
278
- for (let index = 0; index < untrack(() => internalFieldStore.items.value).length; index++) setFieldBool(internalFieldStore.children[index], type, bool);
279
- } else if (internalFieldStore.kind == "object") for (const key in internalFieldStore.children) setFieldBool(internalFieldStore.children[key], type, bool);
280
- else internalFieldStore[type].value = bool;
294
+ internalFieldStore[type].value = bool;
295
+ if (internalFieldStore.kind === "array") for (let index = 0; index < untrack(() => internalFieldStore.items.value).length; index++) setFieldBool(internalFieldStore.children[index], type, bool);
296
+ else if (internalFieldStore.kind === "object") for (const key in internalFieldStore.children) setFieldBool(internalFieldStore.children[key], type, bool);
281
297
  });
282
298
  }
283
299
  function setNestedInput(internalFieldStore, input) {
@@ -285,20 +301,20 @@ function setNestedInput(internalFieldStore, input) {
285
301
  if (internalFieldStore.kind === "array") {
286
302
  const arrayInput = input ?? [];
287
303
  const items = internalFieldStore.items.value;
288
- if (arrayInput.length < items.length) internalFieldStore.items.value = items.slice(0, arrayInput.length);
289
- else if (arrayInput.length > items.length) {
290
- if (arrayInput.length > internalFieldStore.children.length) {
291
- const path = JSON.parse(internalFieldStore.name);
292
- for (let index = internalFieldStore.children.length; index < arrayInput.length; index++) {
293
- internalFieldStore.children[index] = {};
294
- path.push(index);
295
- initializeFieldStore(internalFieldStore.children[index], internalFieldStore.schema.item, arrayInput[index], path);
296
- path.pop();
297
- }
304
+ const length = internalFieldStore.schema.type === "array" ? arrayInput.length : internalFieldStore.children.length;
305
+ if (length < items.length) internalFieldStore.items.value = items.slice(0, length);
306
+ else if (length > items.length) {
307
+ const path = JSON.parse(internalFieldStore.name);
308
+ for (let index = items.length; index < length; index++) if (internalFieldStore.children[index]) resetItemState(internalFieldStore.children[index], arrayInput[index], true);
309
+ else {
310
+ internalFieldStore.children[index] = {};
311
+ path.push(index);
312
+ initializeFieldStore(internalFieldStore.children[index], internalFieldStore.schema.item, arrayInput[index], path);
313
+ path.pop();
298
314
  }
299
- internalFieldStore.items.value = [...items, ...arrayInput.slice(items.length).map(createId)];
315
+ internalFieldStore.items.value = [...items, ...Array.from({ length: length - items.length }, createId)];
300
316
  }
301
- for (let index = 0; index < arrayInput.length; index++) setNestedInput(internalFieldStore.children[index], arrayInput[index]);
317
+ for (let index = 0; index < length; index++) setNestedInput(internalFieldStore.children[index], arrayInput[index]);
302
318
  internalFieldStore.input.value = input == null ? input : true;
303
319
  internalFieldStore.isDirty.value = internalFieldStore.startInput.value !== internalFieldStore.input.value || internalFieldStore.startItems.value.length !== internalFieldStore.items.value.length;
304
320
  } else if (internalFieldStore.kind === "object") {
@@ -326,21 +342,22 @@ function setFieldInput(internalFormStore, path, input) {
326
342
  function setInitialFieldInput(internalFieldStore, initialInput) {
327
343
  batch(() => {
328
344
  if (internalFieldStore.kind === "array") {
329
- internalFieldStore.input.value = initialInput == null ? initialInput : true;
345
+ internalFieldStore.initialInput.value = initialInput == null ? initialInput : true;
330
346
  const initialArrayInput = initialInput ?? [];
331
- if (initialArrayInput.length > internalFieldStore.children.length) {
347
+ const length = internalFieldStore.schema.type === "array" ? initialArrayInput.length : internalFieldStore.children.length;
348
+ if (length > internalFieldStore.children.length) {
332
349
  const path = JSON.parse(internalFieldStore.name);
333
- for (let index = internalFieldStore.children.length; index < initialArrayInput.length; index++) {
350
+ for (let index = internalFieldStore.children.length; index < length; index++) {
334
351
  internalFieldStore.children[index] = {};
335
352
  path.push(index);
336
353
  initializeFieldStore(internalFieldStore.children[index], internalFieldStore.schema.item, initialArrayInput[index], path);
337
354
  path.pop();
338
355
  }
339
356
  }
340
- internalFieldStore.initialItems.value = initialArrayInput.map(createId);
357
+ internalFieldStore.initialItems.value = Array.from({ length }, createId);
341
358
  for (let index = 0; index < internalFieldStore.children.length; index++) setInitialFieldInput(internalFieldStore.children[index], initialArrayInput[index]);
342
359
  } else if (internalFieldStore.kind === "object") {
343
- internalFieldStore.input.value = initialInput == null ? initialInput : true;
360
+ internalFieldStore.initialInput.value = initialInput == null ? initialInput : true;
344
361
  for (const key in internalFieldStore.children) setInitialFieldInput(internalFieldStore.children[key], initialInput?.[key]);
345
362
  } else internalFieldStore.initialInput.value = initialInput;
346
363
  });
@@ -365,44 +382,49 @@ function createFormStore(config, parse) {
365
382
  async function validateFormInput(internalFormStore, config) {
366
383
  internalFormStore.validators++;
367
384
  internalFormStore.isValidating.value = true;
368
- const result = await internalFormStore.parse(untrack(() => /* @__PURE__ */ getFieldInput(internalFormStore)));
369
- let rootErrors;
370
- let nestedErrors;
371
- if (result.issues) {
372
- nestedErrors = {};
373
- for (const issue of result.issues) if (issue.path) {
374
- const path = [];
375
- for (const pathItem of issue.path) {
376
- const key = pathItem.key;
377
- const keyType = typeof key;
378
- const itemType = pathItem.type;
379
- if (keyType !== "string" && keyType !== "number" || itemType === "map" || itemType === "set") break;
380
- path.push(key);
381
- }
382
- const name = JSON.stringify(path);
383
- const fieldErrors = nestedErrors[name];
384
- if (fieldErrors) fieldErrors.push(issue.message);
385
- else nestedErrors[name] = [issue.message];
386
- } else if (rootErrors) rootErrors.push(issue.message);
387
- else rootErrors = [issue.message];
388
- }
389
- let shouldFocus = config?.shouldFocus ?? false;
390
- batch(() => {
391
- walkFieldStore(internalFormStore, (internalFieldStore) => {
392
- if (internalFieldStore.name === "[]") internalFieldStore.errors.value = rootErrors ?? null;
393
- else {
394
- const fieldErrors = nestedErrors?.[internalFieldStore.name] ?? null;
395
- internalFieldStore.errors.value = fieldErrors;
396
- if (shouldFocus && fieldErrors) {
397
- internalFieldStore.elements[0]?.focus();
398
- shouldFocus = false;
385
+ try {
386
+ const result = await internalFormStore.parse(untrack(() => /* @__PURE__ */ getFieldInput(internalFormStore)));
387
+ let rootErrors;
388
+ let nestedErrors;
389
+ if (result.issues) {
390
+ nestedErrors = {};
391
+ for (const issue of result.issues) if (issue.path) {
392
+ const path = [];
393
+ for (const pathItem of issue.path) {
394
+ const key = pathItem.key;
395
+ const keyType = typeof key;
396
+ const itemType = pathItem.type;
397
+ if (keyType !== "string" && keyType !== "number" || itemType === "map" || itemType === "set") break;
398
+ path.push(key);
399
399
  }
400
- }
400
+ const name = JSON.stringify(path);
401
+ const fieldErrors = nestedErrors[name];
402
+ if (fieldErrors) fieldErrors.push(issue.message);
403
+ else nestedErrors[name] = [issue.message];
404
+ } else if (rootErrors) rootErrors.push(issue.message);
405
+ else rootErrors = [issue.message];
406
+ }
407
+ let shouldFocus = config?.shouldFocus ?? false;
408
+ batch(() => {
409
+ walkFieldStore(internalFormStore, (internalFieldStore) => {
410
+ if (internalFieldStore.name === "[]") internalFieldStore.errors.value = rootErrors ?? null;
411
+ else {
412
+ const fieldErrors = nestedErrors?.[internalFieldStore.name] ?? null;
413
+ internalFieldStore.errors.value = fieldErrors;
414
+ if (shouldFocus && fieldErrors && focusFieldElement(internalFieldStore)) shouldFocus = false;
415
+ }
416
+ });
417
+ internalFormStore.validators--;
418
+ internalFormStore.isValidating.value = internalFormStore.validators > 0;
401
419
  });
402
- internalFormStore.validators--;
403
- internalFormStore.isValidating.value = internalFormStore.validators > 0;
404
- });
405
- return result;
420
+ return result;
421
+ } catch (error) {
422
+ batch(() => {
423
+ internalFormStore.validators--;
424
+ internalFormStore.isValidating.value = internalFormStore.validators > 0;
425
+ });
426
+ throw error;
427
+ }
406
428
  }
407
429
  function validateIfRequired(internalFormStore, internalFieldStore, validationMode) {
408
430
  if (validationMode === (internalFormStore.validate === "initial" || (internalFormStore.validate === "submit" ? untrack(() => internalFormStore.isSubmitted.value) : untrack(() => /* @__PURE__ */ getFieldBool(internalFieldStore, "errors"))) ? internalFormStore.revalidate : internalFormStore.validate)) validateFormInput(internalFormStore);
@@ -411,7 +433,7 @@ var INTERNAL = "~internal";
411
433
 
412
434
  // ../../packages/methods/dist/index.solid.js
413
435
  function focus(form, config) {
414
- getFieldStore(form[INTERNAL], config.path).elements[0]?.focus();
436
+ focusFieldElement(getFieldStore(form[INTERNAL], config.path));
415
437
  }
416
438
  // @__NO_SIDE_EFFECTS__
417
439
  function getAllErrors(form) {
@@ -738,9 +760,13 @@ function useField(form, config) {
738
760
  const internalFieldStore = getInternalFieldStore();
739
761
  internalFieldStore.elements.push(element);
740
762
  onCleanup(() => {
741
- internalFieldStore.elements = internalFieldStore.elements.filter(
763
+ const elements = internalFieldStore.elements.filter(
742
764
  (el) => el !== element
743
765
  );
766
+ if (internalFieldStore.elements === internalFieldStore.initialElements) {
767
+ internalFieldStore.initialElements = elements;
768
+ }
769
+ internalFieldStore.elements = elements;
744
770
  });
745
771
  },
746
772
  onFocus() {
package/dist/index.d.ts CHANGED
@@ -72,6 +72,13 @@ interface InternalBaseStore {
72
72
  schema: Schema;
73
73
  /**
74
74
  * The initial elements of the field.
75
+ *
76
+ * Hint: This may look unused, but do not remove it. `copyItemState` and
77
+ * `swapItemState` move the `elements` reference between field stores when
78
+ * array items are inserted, moved, removed or swapped, and `reset` restores
79
+ * each field's original element via `elements = initialElements`. Without it,
80
+ * focus and file reset target the wrong element after a reorder followed by a
81
+ * reset.
75
82
  */
76
83
  initialElements: FieldElement[];
77
84
  /**
@@ -393,7 +400,10 @@ type ExactRequired<TValue> = TValue extends Record<PropertyKey, unknown> ? IsExa
393
400
  */
394
401
  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;
395
402
  /**
396
- * Checks whether a value is an array or contains one anywhere in its shape.
403
+ * Checks whether a value is a dynamic array or contains one anywhere in its
404
+ * shape. A fixed-length tuple is not itself a dynamic array, but it counts when
405
+ * it contains one, so paths can still navigate through tuples to reach nested
406
+ * arrays.
397
407
  *
398
408
  * Hint: The inner conditionals (`TValue extends readonly unknown[]` and
399
409
  * `TValue extends Record<PropertyKey, unknown>`) distribute over union members,
@@ -404,7 +414,7 @@ type PathValue<TValue, TPath extends Path> = TPath extends readonly [infer TKey,
404
414
  * `true extends ...`, which is `true` whenever at least one union member
405
415
  * contributed `true`.
406
416
  */
407
- 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;
417
+ type IsOrHasArray<TValue> = true extends (IsAny<TValue> extends true ? false : TValue extends readonly unknown[] ? number extends TValue["length"] ? true : IsOrHasArray<TValue[number]> : TValue extends Record<PropertyKey, unknown> ? { [TKey in keyof TValue]: IsOrHasArray<TValue[TKey]> }[keyof TValue] : false) ? true : false;
408
418
  /**
409
419
  * Extracts the exact keys of a tuple, array or object that contain arrays.
410
420
  */
@@ -419,7 +429,7 @@ type PropertiesOfArrayPath<TValue> = { [TKey in ExactKeysOfArrayPath<TValue>]: T
419
429
  /**
420
430
  * Lazily evaluates only the first valid array path segment based on the given value.
421
431
  */
422
- 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;
432
+ type LazyArrayPath<TValue, TPathToCheck extends Path, TValidPath extends Path = readonly []> = TPathToCheck extends readonly [] ? TValue extends readonly unknown[] ? number extends TValue["length"] ? TValidPath : IsNever<ExactKeysOfArrayPath<TValue>> extends false ? readonly [...TValidPath, ExactKeysOfArrayPath<TValue>] : never : 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;
423
433
  /**
424
434
  * Returns the path if valid, otherwise the first possible valid array path
425
435
  * based on the given value.
@@ -472,7 +482,7 @@ interface FocusFieldConfig<TSchema extends FormSchema, TFieldPath extends Requir
472
482
  readonly path: ValidPath<v.InferInput<TSchema>, TFieldPath>;
473
483
  }
474
484
  /**
475
- * Focuses the first input element of a field. This is useful for
485
+ * Focuses the first focusable input element of a field. This is useful for
476
486
  * programmatically setting focus to a specific field, such as after
477
487
  * validation errors or user interactions.
478
488
  *
package/dist/index.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,13 @@ 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
+ }
195
213
  // @__NO_SIDE_EFFECTS__
196
214
  function getFieldBool(internalFieldStore, type) {
197
215
  if (internalFieldStore[type].value) return true;
@@ -275,11 +293,9 @@ function getFieldStore(internalFormStore, path) {
275
293
  }
276
294
  function setFieldBool(internalFieldStore, type, bool) {
277
295
  batch(() => {
278
- if (internalFieldStore.kind === "array") {
279
- internalFieldStore[type].value = bool;
280
- for (let index = 0; index < untrack(() => internalFieldStore.items.value).length; index++) setFieldBool(internalFieldStore.children[index], type, bool);
281
- } else if (internalFieldStore.kind == "object") for (const key in internalFieldStore.children) setFieldBool(internalFieldStore.children[key], type, bool);
282
- 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);
283
299
  });
284
300
  }
285
301
  function setNestedInput(internalFieldStore, input) {
@@ -287,20 +303,20 @@ function setNestedInput(internalFieldStore, input) {
287
303
  if (internalFieldStore.kind === "array") {
288
304
  const arrayInput = input ?? [];
289
305
  const items = internalFieldStore.items.value;
290
- if (arrayInput.length < items.length) internalFieldStore.items.value = items.slice(0, arrayInput.length);
291
- else if (arrayInput.length > items.length) {
292
- if (arrayInput.length > internalFieldStore.children.length) {
293
- const path = JSON.parse(internalFieldStore.name);
294
- for (let index = internalFieldStore.children.length; index < arrayInput.length; index++) {
295
- internalFieldStore.children[index] = {};
296
- path.push(index);
297
- initializeFieldStore(internalFieldStore.children[index], internalFieldStore.schema.item, arrayInput[index], path);
298
- path.pop();
299
- }
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();
300
316
  }
301
- internalFieldStore.items.value = [...items, ...arrayInput.slice(items.length).map(createUniqueId)];
317
+ internalFieldStore.items.value = [...items, ...Array.from({ length: length - items.length }, createUniqueId)];
302
318
  }
303
- 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]);
304
320
  internalFieldStore.input.value = input == null ? input : true;
305
321
  internalFieldStore.isDirty.value = internalFieldStore.startInput.value !== internalFieldStore.input.value || internalFieldStore.startItems.value.length !== internalFieldStore.items.value.length;
306
322
  } else if (internalFieldStore.kind === "object") {
@@ -328,21 +344,22 @@ function setFieldInput(internalFormStore, path, input) {
328
344
  function setInitialFieldInput(internalFieldStore, initialInput) {
329
345
  batch(() => {
330
346
  if (internalFieldStore.kind === "array") {
331
- internalFieldStore.input.value = initialInput == null ? initialInput : true;
347
+ internalFieldStore.initialInput.value = initialInput == null ? initialInput : true;
332
348
  const initialArrayInput = initialInput ?? [];
333
- if (initialArrayInput.length > internalFieldStore.children.length) {
349
+ const length = internalFieldStore.schema.type === "array" ? initialArrayInput.length : internalFieldStore.children.length;
350
+ if (length > internalFieldStore.children.length) {
334
351
  const path = JSON.parse(internalFieldStore.name);
335
- for (let index = internalFieldStore.children.length; index < initialArrayInput.length; index++) {
352
+ for (let index = internalFieldStore.children.length; index < length; index++) {
336
353
  internalFieldStore.children[index] = {};
337
354
  path.push(index);
338
355
  initializeFieldStore(internalFieldStore.children[index], internalFieldStore.schema.item, initialArrayInput[index], path);
339
356
  path.pop();
340
357
  }
341
358
  }
342
- internalFieldStore.initialItems.value = initialArrayInput.map(createUniqueId);
359
+ internalFieldStore.initialItems.value = Array.from({ length }, createUniqueId);
343
360
  for (let index = 0; index < internalFieldStore.children.length; index++) setInitialFieldInput(internalFieldStore.children[index], initialArrayInput[index]);
344
361
  } else if (internalFieldStore.kind === "object") {
345
- internalFieldStore.input.value = initialInput == null ? initialInput : true;
362
+ internalFieldStore.initialInput.value = initialInput == null ? initialInput : true;
346
363
  for (const key in internalFieldStore.children) setInitialFieldInput(internalFieldStore.children[key], initialInput?.[key]);
347
364
  } else internalFieldStore.initialInput.value = initialInput;
348
365
  });
@@ -367,44 +384,49 @@ function createFormStore(config, parse) {
367
384
  async function validateFormInput(internalFormStore, config) {
368
385
  internalFormStore.validators++;
369
386
  internalFormStore.isValidating.value = true;
370
- const result = await internalFormStore.parse(untrack(() => /* @__PURE__ */ getFieldInput(internalFormStore)));
371
- let rootErrors;
372
- let nestedErrors;
373
- if (result.issues) {
374
- nestedErrors = {};
375
- for (const issue of result.issues) if (issue.path) {
376
- const path = [];
377
- for (const pathItem of issue.path) {
378
- const key = pathItem.key;
379
- const keyType = typeof key;
380
- const itemType = pathItem.type;
381
- if (keyType !== "string" && keyType !== "number" || itemType === "map" || itemType === "set") break;
382
- path.push(key);
383
- }
384
- const name = JSON.stringify(path);
385
- const fieldErrors = nestedErrors[name];
386
- if (fieldErrors) fieldErrors.push(issue.message);
387
- else nestedErrors[name] = [issue.message];
388
- } else if (rootErrors) rootErrors.push(issue.message);
389
- else rootErrors = [issue.message];
390
- }
391
- let shouldFocus = config?.shouldFocus ?? false;
392
- batch(() => {
393
- walkFieldStore(internalFormStore, (internalFieldStore) => {
394
- if (internalFieldStore.name === "[]") internalFieldStore.errors.value = rootErrors ?? null;
395
- else {
396
- const fieldErrors = nestedErrors?.[internalFieldStore.name] ?? null;
397
- internalFieldStore.errors.value = fieldErrors;
398
- if (shouldFocus && fieldErrors) {
399
- internalFieldStore.elements[0]?.focus();
400
- 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);
401
401
  }
402
- }
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;
403
421
  });
404
- internalFormStore.validators--;
405
- internalFormStore.isValidating.value = internalFormStore.validators > 0;
406
- });
407
- 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
+ }
408
430
  }
409
431
  function validateIfRequired(internalFormStore, internalFieldStore, validationMode) {
410
432
  if (validationMode === (internalFormStore.validate === "initial" || (internalFormStore.validate === "submit" ? untrack(() => internalFormStore.isSubmitted.value) : untrack(() => /* @__PURE__ */ getFieldBool(internalFieldStore, "errors"))) ? internalFormStore.revalidate : internalFormStore.validate)) validateFormInput(internalFormStore);
@@ -413,7 +435,7 @@ var INTERNAL = "~internal";
413
435
 
414
436
  // ../../packages/methods/dist/index.solid.js
415
437
  function focus(form, config) {
416
- getFieldStore(form[INTERNAL], config.path).elements[0]?.focus();
438
+ focusFieldElement(getFieldStore(form[INTERNAL], config.path));
417
439
  }
418
440
  // @__NO_SIDE_EFFECTS__
419
441
  function getAllErrors(form) {
@@ -733,9 +755,13 @@ function useField(form, config) {
733
755
  const internalFieldStore = getInternalFieldStore();
734
756
  internalFieldStore.elements.push(element);
735
757
  onCleanup(() => {
736
- internalFieldStore.elements = internalFieldStore.elements.filter(
758
+ const elements = internalFieldStore.elements.filter(
737
759
  (el) => el !== element
738
760
  );
761
+ if (internalFieldStore.elements === internalFieldStore.initialElements) {
762
+ internalFieldStore.initialElements = elements;
763
+ }
764
+ internalFieldStore.elements = elements;
739
765
  });
740
766
  },
741
767
  onFocus() {
package/dist/index.jsx CHANGED
@@ -111,29 +111,40 @@ function copyItemState(fromInternalFieldStore, toInternalFieldStore) {
111
111
  });
112
112
  });
113
113
  }
114
- function resetItemState(internalFieldStore, initialInput) {
114
+ function resetItemState(internalFieldStore, input, keepStart = false) {
115
115
  batch(() => {
116
- internalFieldStore.elements = [];
116
+ const elements = [];
117
+ if (internalFieldStore.elements === internalFieldStore.initialElements) internalFieldStore.initialElements = elements;
118
+ internalFieldStore.elements = elements;
117
119
  internalFieldStore.errors.value = null;
118
120
  internalFieldStore.isTouched.value = false;
119
121
  internalFieldStore.isDirty.value = false;
120
122
  if (internalFieldStore.kind === "array" || internalFieldStore.kind === "object") {
121
- const objectInput = initialInput == null ? initialInput : true;
122
- internalFieldStore.startInput.value = objectInput;
123
+ const objectInput = input == null ? input : true;
124
+ if (!keepStart) internalFieldStore.startInput.value = objectInput;
123
125
  internalFieldStore.input.value = objectInput;
124
- if (internalFieldStore.kind === "array") if (initialInput) {
125
- const newItems = initialInput.map(createId);
126
- internalFieldStore.startItems.value = newItems;
126
+ if (internalFieldStore.kind === "array") if (input) {
127
+ const length = internalFieldStore.schema.type === "array" ? input.length : internalFieldStore.children.length;
128
+ const newItems = Array.from({ length }, createId);
129
+ if (!keepStart) internalFieldStore.startItems.value = newItems;
127
130
  internalFieldStore.items.value = newItems;
128
- for (let index = 0; index < initialInput.length; index++) if (internalFieldStore.children[index]) resetItemState(internalFieldStore.children[index], initialInput[index]);
131
+ let path;
132
+ for (let index = 0; index < length; index++) if (internalFieldStore.children[index]) resetItemState(internalFieldStore.children[index], input[index], keepStart);
133
+ else {
134
+ path ??= JSON.parse(internalFieldStore.name);
135
+ internalFieldStore.children[index] = {};
136
+ path.push(index);
137
+ initializeFieldStore(internalFieldStore.children[index], internalFieldStore.schema.item, input[index], path);
138
+ path.pop();
139
+ }
129
140
  } else {
130
- internalFieldStore.startItems.value = [];
141
+ if (!keepStart) internalFieldStore.startItems.value = [];
131
142
  internalFieldStore.items.value = [];
132
143
  }
133
- else for (const key in internalFieldStore.children) resetItemState(internalFieldStore.children[key], initialInput?.[key]);
144
+ else for (const key in internalFieldStore.children) resetItemState(internalFieldStore.children[key], input?.[key], keepStart);
134
145
  } else {
135
- internalFieldStore.startInput.value = initialInput;
136
- internalFieldStore.input.value = initialInput;
146
+ if (!keepStart) internalFieldStore.startInput.value = input;
147
+ internalFieldStore.input.value = input;
137
148
  }
138
149
  });
139
150
  }
@@ -190,6 +201,13 @@ function swapItemState(firstInternalFieldStore, secondInternalFieldStore) {
190
201
  });
191
202
  });
192
203
  }
204
+ function focusFieldElement(internalFieldStore) {
205
+ for (const element of internalFieldStore.elements) {
206
+ element.focus();
207
+ if (element.getRootNode().activeElement === element) return true;
208
+ }
209
+ return false;
210
+ }
193
211
  // @__NO_SIDE_EFFECTS__
194
212
  function getFieldBool(internalFieldStore, type) {
195
213
  if (internalFieldStore[type].value) return true;
@@ -273,11 +291,9 @@ function getFieldStore(internalFormStore, path) {
273
291
  }
274
292
  function setFieldBool(internalFieldStore, type, bool) {
275
293
  batch(() => {
276
- if (internalFieldStore.kind === "array") {
277
- internalFieldStore[type].value = bool;
278
- for (let index = 0; index < untrack(() => internalFieldStore.items.value).length; index++) setFieldBool(internalFieldStore.children[index], type, bool);
279
- } else if (internalFieldStore.kind == "object") for (const key in internalFieldStore.children) setFieldBool(internalFieldStore.children[key], type, bool);
280
- else internalFieldStore[type].value = bool;
294
+ internalFieldStore[type].value = bool;
295
+ if (internalFieldStore.kind === "array") for (let index = 0; index < untrack(() => internalFieldStore.items.value).length; index++) setFieldBool(internalFieldStore.children[index], type, bool);
296
+ else if (internalFieldStore.kind === "object") for (const key in internalFieldStore.children) setFieldBool(internalFieldStore.children[key], type, bool);
281
297
  });
282
298
  }
283
299
  function setNestedInput(internalFieldStore, input) {
@@ -285,20 +301,20 @@ function setNestedInput(internalFieldStore, input) {
285
301
  if (internalFieldStore.kind === "array") {
286
302
  const arrayInput = input ?? [];
287
303
  const items = internalFieldStore.items.value;
288
- if (arrayInput.length < items.length) internalFieldStore.items.value = items.slice(0, arrayInput.length);
289
- else if (arrayInput.length > items.length) {
290
- if (arrayInput.length > internalFieldStore.children.length) {
291
- const path = JSON.parse(internalFieldStore.name);
292
- for (let index = internalFieldStore.children.length; index < arrayInput.length; index++) {
293
- internalFieldStore.children[index] = {};
294
- path.push(index);
295
- initializeFieldStore(internalFieldStore.children[index], internalFieldStore.schema.item, arrayInput[index], path);
296
- path.pop();
297
- }
304
+ const length = internalFieldStore.schema.type === "array" ? arrayInput.length : internalFieldStore.children.length;
305
+ if (length < items.length) internalFieldStore.items.value = items.slice(0, length);
306
+ else if (length > items.length) {
307
+ const path = JSON.parse(internalFieldStore.name);
308
+ for (let index = items.length; index < length; index++) if (internalFieldStore.children[index]) resetItemState(internalFieldStore.children[index], arrayInput[index], true);
309
+ else {
310
+ internalFieldStore.children[index] = {};
311
+ path.push(index);
312
+ initializeFieldStore(internalFieldStore.children[index], internalFieldStore.schema.item, arrayInput[index], path);
313
+ path.pop();
298
314
  }
299
- internalFieldStore.items.value = [...items, ...arrayInput.slice(items.length).map(createId)];
315
+ internalFieldStore.items.value = [...items, ...Array.from({ length: length - items.length }, createId)];
300
316
  }
301
- for (let index = 0; index < arrayInput.length; index++) setNestedInput(internalFieldStore.children[index], arrayInput[index]);
317
+ for (let index = 0; index < length; index++) setNestedInput(internalFieldStore.children[index], arrayInput[index]);
302
318
  internalFieldStore.input.value = input == null ? input : true;
303
319
  internalFieldStore.isDirty.value = internalFieldStore.startInput.value !== internalFieldStore.input.value || internalFieldStore.startItems.value.length !== internalFieldStore.items.value.length;
304
320
  } else if (internalFieldStore.kind === "object") {
@@ -326,21 +342,22 @@ function setFieldInput(internalFormStore, path, input) {
326
342
  function setInitialFieldInput(internalFieldStore, initialInput) {
327
343
  batch(() => {
328
344
  if (internalFieldStore.kind === "array") {
329
- internalFieldStore.input.value = initialInput == null ? initialInput : true;
345
+ internalFieldStore.initialInput.value = initialInput == null ? initialInput : true;
330
346
  const initialArrayInput = initialInput ?? [];
331
- if (initialArrayInput.length > internalFieldStore.children.length) {
347
+ const length = internalFieldStore.schema.type === "array" ? initialArrayInput.length : internalFieldStore.children.length;
348
+ if (length > internalFieldStore.children.length) {
332
349
  const path = JSON.parse(internalFieldStore.name);
333
- for (let index = internalFieldStore.children.length; index < initialArrayInput.length; index++) {
350
+ for (let index = internalFieldStore.children.length; index < length; index++) {
334
351
  internalFieldStore.children[index] = {};
335
352
  path.push(index);
336
353
  initializeFieldStore(internalFieldStore.children[index], internalFieldStore.schema.item, initialArrayInput[index], path);
337
354
  path.pop();
338
355
  }
339
356
  }
340
- internalFieldStore.initialItems.value = initialArrayInput.map(createId);
357
+ internalFieldStore.initialItems.value = Array.from({ length }, createId);
341
358
  for (let index = 0; index < internalFieldStore.children.length; index++) setInitialFieldInput(internalFieldStore.children[index], initialArrayInput[index]);
342
359
  } else if (internalFieldStore.kind === "object") {
343
- internalFieldStore.input.value = initialInput == null ? initialInput : true;
360
+ internalFieldStore.initialInput.value = initialInput == null ? initialInput : true;
344
361
  for (const key in internalFieldStore.children) setInitialFieldInput(internalFieldStore.children[key], initialInput?.[key]);
345
362
  } else internalFieldStore.initialInput.value = initialInput;
346
363
  });
@@ -365,44 +382,49 @@ function createFormStore(config, parse) {
365
382
  async function validateFormInput(internalFormStore, config) {
366
383
  internalFormStore.validators++;
367
384
  internalFormStore.isValidating.value = true;
368
- const result = await internalFormStore.parse(untrack(() => /* @__PURE__ */ getFieldInput(internalFormStore)));
369
- let rootErrors;
370
- let nestedErrors;
371
- if (result.issues) {
372
- nestedErrors = {};
373
- for (const issue of result.issues) if (issue.path) {
374
- const path = [];
375
- for (const pathItem of issue.path) {
376
- const key = pathItem.key;
377
- const keyType = typeof key;
378
- const itemType = pathItem.type;
379
- if (keyType !== "string" && keyType !== "number" || itemType === "map" || itemType === "set") break;
380
- path.push(key);
381
- }
382
- const name = JSON.stringify(path);
383
- const fieldErrors = nestedErrors[name];
384
- if (fieldErrors) fieldErrors.push(issue.message);
385
- else nestedErrors[name] = [issue.message];
386
- } else if (rootErrors) rootErrors.push(issue.message);
387
- else rootErrors = [issue.message];
388
- }
389
- let shouldFocus = config?.shouldFocus ?? false;
390
- batch(() => {
391
- walkFieldStore(internalFormStore, (internalFieldStore) => {
392
- if (internalFieldStore.name === "[]") internalFieldStore.errors.value = rootErrors ?? null;
393
- else {
394
- const fieldErrors = nestedErrors?.[internalFieldStore.name] ?? null;
395
- internalFieldStore.errors.value = fieldErrors;
396
- if (shouldFocus && fieldErrors) {
397
- internalFieldStore.elements[0]?.focus();
398
- shouldFocus = false;
385
+ try {
386
+ const result = await internalFormStore.parse(untrack(() => /* @__PURE__ */ getFieldInput(internalFormStore)));
387
+ let rootErrors;
388
+ let nestedErrors;
389
+ if (result.issues) {
390
+ nestedErrors = {};
391
+ for (const issue of result.issues) if (issue.path) {
392
+ const path = [];
393
+ for (const pathItem of issue.path) {
394
+ const key = pathItem.key;
395
+ const keyType = typeof key;
396
+ const itemType = pathItem.type;
397
+ if (keyType !== "string" && keyType !== "number" || itemType === "map" || itemType === "set") break;
398
+ path.push(key);
399
399
  }
400
- }
400
+ const name = JSON.stringify(path);
401
+ const fieldErrors = nestedErrors[name];
402
+ if (fieldErrors) fieldErrors.push(issue.message);
403
+ else nestedErrors[name] = [issue.message];
404
+ } else if (rootErrors) rootErrors.push(issue.message);
405
+ else rootErrors = [issue.message];
406
+ }
407
+ let shouldFocus = config?.shouldFocus ?? false;
408
+ batch(() => {
409
+ walkFieldStore(internalFormStore, (internalFieldStore) => {
410
+ if (internalFieldStore.name === "[]") internalFieldStore.errors.value = rootErrors ?? null;
411
+ else {
412
+ const fieldErrors = nestedErrors?.[internalFieldStore.name] ?? null;
413
+ internalFieldStore.errors.value = fieldErrors;
414
+ if (shouldFocus && fieldErrors && focusFieldElement(internalFieldStore)) shouldFocus = false;
415
+ }
416
+ });
417
+ internalFormStore.validators--;
418
+ internalFormStore.isValidating.value = internalFormStore.validators > 0;
401
419
  });
402
- internalFormStore.validators--;
403
- internalFormStore.isValidating.value = internalFormStore.validators > 0;
404
- });
405
- return result;
420
+ return result;
421
+ } catch (error) {
422
+ batch(() => {
423
+ internalFormStore.validators--;
424
+ internalFormStore.isValidating.value = internalFormStore.validators > 0;
425
+ });
426
+ throw error;
427
+ }
406
428
  }
407
429
  function validateIfRequired(internalFormStore, internalFieldStore, validationMode) {
408
430
  if (validationMode === (internalFormStore.validate === "initial" || (internalFormStore.validate === "submit" ? untrack(() => internalFormStore.isSubmitted.value) : untrack(() => /* @__PURE__ */ getFieldBool(internalFieldStore, "errors"))) ? internalFormStore.revalidate : internalFormStore.validate)) validateFormInput(internalFormStore);
@@ -411,7 +433,7 @@ var INTERNAL = "~internal";
411
433
 
412
434
  // ../../packages/methods/dist/index.solid.js
413
435
  function focus(form, config) {
414
- getFieldStore(form[INTERNAL], config.path).elements[0]?.focus();
436
+ focusFieldElement(getFieldStore(form[INTERNAL], config.path));
415
437
  }
416
438
  // @__NO_SIDE_EFFECTS__
417
439
  function getAllErrors(form) {
@@ -738,9 +760,13 @@ function useField(form, config) {
738
760
  const internalFieldStore = getInternalFieldStore();
739
761
  internalFieldStore.elements.push(element);
740
762
  onCleanup(() => {
741
- internalFieldStore.elements = internalFieldStore.elements.filter(
763
+ const elements = internalFieldStore.elements.filter(
742
764
  (el) => el !== element
743
765
  );
766
+ if (internalFieldStore.elements === internalFieldStore.initialElements) {
767
+ internalFieldStore.initialElements = elements;
768
+ }
769
+ internalFieldStore.elements = elements;
744
770
  });
745
771
  },
746
772
  onFocus() {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@formisch/solid",
3
3
  "description": "The lightweight, schema-first, and fully type-safe form library for SolidJS",
4
- "version": "0.10.0",
4
+ "version": "0.11.0",
5
5
  "license": "MIT",
6
6
  "author": "Fabian Hiller",
7
7
  "homepage": "https://formisch.dev",
@@ -57,7 +57,8 @@
57
57
  "@formisch/methods": "workspace:*",
58
58
  "@solidjs/testing-library": "^0.8.10",
59
59
  "@testing-library/jest-dom": "^6.6.0",
60
- "@vitest/coverage-v8": "^3.2.4",
60
+ "@types/node": "24.0.13",
61
+ "@vitest/coverage-v8": "^4.1.7",
61
62
  "eslint": "^9.31.0",
62
63
  "eslint-plugin-solid": "^0.14.5",
63
64
  "jsdom": "^26.1.0",
@@ -70,7 +71,7 @@
70
71
  "typescript": "^5.8.3",
71
72
  "valibot": "^1.4.1",
72
73
  "vite-plugin-solid": "^2.11.6",
73
- "vitest": "3.2.4"
74
+ "vitest": "4.1.7"
74
75
  },
75
76
  "peerDependencies": {
76
77
  "solid-js": "^1.6.0",