notform 2.0.0-alpha.4 → 2.0.0-alpha.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -23
- package/dist/index.d.ts +183 -20
- package/dist/index.js +200 -8
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,29 +1,16 @@
|
|
|
1
|
-
#
|
|
1
|
+
# notform
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/notform)
|
|
4
|
+
[](https://www.npmjs.com/package/notform)
|
|
5
|
+
[](https://github.com/favorodera/notform/blob/main/LICENSE)
|
|
6
|
+
[](https://bundlephobia.com/package/notform)
|
|
4
7
|
|
|
5
|
-
|
|
8
|
+
**The core engine for "Vue Forms Without the Friction."**
|
|
6
9
|
|
|
7
|
-
|
|
10
|
+
`notform` is the backbone of the NotForm ecosystem, providing the core composables and state management logic for building robust forms in Vue 3.
|
|
8
11
|
|
|
9
|
-
|
|
10
|
-
npm install
|
|
11
|
-
```
|
|
12
|
+
## Documentation
|
|
12
13
|
|
|
13
|
-
|
|
14
|
+
For detailed guides, API reference, and examples, visit:
|
|
15
|
+
**[notform-docs.vercel.app](https://notform-docs.vercel.app/)**
|
|
14
16
|
|
|
15
|
-
```bash
|
|
16
|
-
npm run playground
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
- Run the unit tests:
|
|
20
|
-
|
|
21
|
-
```bash
|
|
22
|
-
npm run test
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
- Build the library:
|
|
26
|
-
|
|
27
|
-
```bash
|
|
28
|
-
npm run build
|
|
29
|
-
```
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as _$vue from "vue";
|
|
2
|
-
import { ComputedRef, MaybeRefOrGetter, Raw, Ref } from "vue";
|
|
2
|
+
import { ComputedRef, MaybeRefOrGetter, Raw, Ref, useAttrs } from "vue";
|
|
3
3
|
import { StandardSchemaV1 } from "@standard-schema/spec";
|
|
4
4
|
import { Get, PartialDeep, Paths as Paths$1 } from "type-fest";
|
|
5
5
|
|
|
@@ -45,17 +45,6 @@ type ObjectSchema = StandardSchemaV1 & {
|
|
|
45
45
|
};
|
|
46
46
|
};
|
|
47
47
|
};
|
|
48
|
-
/**
|
|
49
|
-
* Represents a validation schema for array-based data structures.
|
|
50
|
-
* Complies with the Standard Schema specification.
|
|
51
|
-
*/
|
|
52
|
-
type ArraySchema = StandardSchemaV1 & {
|
|
53
|
-
'~standard': StandardSchemaV1['~standard'] & {
|
|
54
|
-
types?: {
|
|
55
|
-
input: Array<any>;
|
|
56
|
-
};
|
|
57
|
-
};
|
|
58
|
-
};
|
|
59
48
|
//#endregion
|
|
60
49
|
//#region src/types/use-not-form.d.ts
|
|
61
50
|
/**
|
|
@@ -217,11 +206,11 @@ type NotFormSlots = {
|
|
|
217
206
|
declare function useNotForm<TSchema extends ObjectSchema>(config: UseNotFormConfig<TSchema>): NotFormInstance<TSchema>;
|
|
218
207
|
//#endregion
|
|
219
208
|
//#region src/components/not-form.vue.d.ts
|
|
220
|
-
type __VLS_Slots = NotFormSlots;
|
|
221
|
-
declare const __VLS_base: _$vue.DefineComponent<NotFormProps, {}, {}, {}, {}, _$vue.ComponentOptionsMixin, _$vue.ComponentOptionsMixin, {}, string, _$vue.PublicProps, Readonly<NotFormProps> & Readonly<{}>, {}, {}, {}, {}, string, _$vue.ComponentProvideOptions, false, {}, any>;
|
|
222
|
-
declare const __VLS_export$
|
|
223
|
-
declare const _default$
|
|
224
|
-
type __VLS_WithSlots<T, S> = T & {
|
|
209
|
+
type __VLS_Slots$1 = NotFormSlots;
|
|
210
|
+
declare const __VLS_base$1: _$vue.DefineComponent<NotFormProps, {}, {}, {}, {}, _$vue.ComponentOptionsMixin, _$vue.ComponentOptionsMixin, {}, string, _$vue.PublicProps, Readonly<NotFormProps> & Readonly<{}>, {}, {}, {}, {}, string, _$vue.ComponentProvideOptions, false, {}, any>;
|
|
211
|
+
declare const __VLS_export$3: __VLS_WithSlots$1<typeof __VLS_base$1, __VLS_Slots$1>;
|
|
212
|
+
declare const _default$2: typeof __VLS_export$3;
|
|
213
|
+
type __VLS_WithSlots$1<T, S> = T & {
|
|
225
214
|
new (): {
|
|
226
215
|
$slots: S;
|
|
227
216
|
};
|
|
@@ -316,8 +305,8 @@ type NotFieldSlots<TSchema extends ObjectSchema> = {
|
|
|
316
305
|
};
|
|
317
306
|
//#endregion
|
|
318
307
|
//#region src/components/not-field.vue.d.ts
|
|
319
|
-
declare const __VLS_export: <TSchema extends ObjectSchema>(__VLS_props: NonNullable<Awaited<typeof __VLS_setup>>["props"], __VLS_ctx?: __VLS_PrettifyLocal<Pick<NonNullable<Awaited<typeof __VLS_setup>>, "attrs" | "emit" | "slots">>, __VLS_exposed?: NonNullable<Awaited<typeof __VLS_setup>>["expose"], __VLS_setup?: Promise<{
|
|
320
|
-
props: _$vue.PublicProps & __VLS_PrettifyLocal<NotFieldProps> & (typeof globalThis extends {
|
|
308
|
+
declare const __VLS_export$2: <TSchema extends ObjectSchema>(__VLS_props: NonNullable<Awaited<typeof __VLS_setup>>["props"], __VLS_ctx?: __VLS_PrettifyLocal$1<Pick<NonNullable<Awaited<typeof __VLS_setup>>, "attrs" | "emit" | "slots">>, __VLS_exposed?: NonNullable<Awaited<typeof __VLS_setup>>["expose"], __VLS_setup?: Promise<{
|
|
309
|
+
props: _$vue.PublicProps & __VLS_PrettifyLocal$1<NotFieldProps> & (typeof globalThis extends {
|
|
321
310
|
__VLS_PROPS_FALLBACK: infer P;
|
|
322
311
|
} ? P : {});
|
|
323
312
|
expose: (exposed: {}) => void;
|
|
@@ -327,7 +316,181 @@ declare const __VLS_export: <TSchema extends ObjectSchema>(__VLS_props: NonNulla
|
|
|
327
316
|
}>) => _$vue.VNode & {
|
|
328
317
|
__ctx?: Awaited<typeof __VLS_setup>;
|
|
329
318
|
};
|
|
319
|
+
declare const _default$1: typeof __VLS_export$2;
|
|
320
|
+
type __VLS_PrettifyLocal$1<T> = (T extends any ? { [K in keyof T]: T[K] } : { [K in keyof T as K]: T[K] }) & {};
|
|
321
|
+
//#endregion
|
|
322
|
+
//#region src/types/not-message.d.ts
|
|
323
|
+
/** Props for the `NotMessage` component. */
|
|
324
|
+
type NotMessageProps = {
|
|
325
|
+
/** The name/path of the field whose error message should be displayed */path: string; /** HTML Tag `NotMessage` should render as - default is `span`. */
|
|
326
|
+
as?: string;
|
|
327
|
+
/**
|
|
328
|
+
* Explicit form instance override.
|
|
329
|
+
* Takes priority over the instance provided by a `NotForm` ancestor.
|
|
330
|
+
* Required when using `NotMessage` outside of a `NotForm` (singleton fields).
|
|
331
|
+
*
|
|
332
|
+
* ```vue
|
|
333
|
+
* <NotMessage :form="form" path="email" />
|
|
334
|
+
* ```
|
|
335
|
+
*/
|
|
336
|
+
form?: NotFormInstance<any>;
|
|
337
|
+
};
|
|
338
|
+
/** Everything available inside the `NotMessage` default slot. */
|
|
339
|
+
type NotMessageSlotProps = {
|
|
340
|
+
/** The first active validation error message for the specified field */message?: string; /** Attributes passed to the `NotMessage` component */
|
|
341
|
+
attributes?: ReturnType<typeof useAttrs>;
|
|
342
|
+
};
|
|
343
|
+
/** Slots for the `NotMessage` component. */
|
|
344
|
+
type NotMessageSlots = {
|
|
345
|
+
/** The default slot receives the error message context for custom rendering */default: (props: NotMessageSlotProps) => [];
|
|
346
|
+
};
|
|
347
|
+
//#endregion
|
|
348
|
+
//#region src/components/not-message.vue.d.ts
|
|
349
|
+
type __VLS_Slots = NotMessageSlots;
|
|
350
|
+
declare const __VLS_base: _$vue.DefineComponent<NotMessageProps, {}, {}, {}, {}, _$vue.ComponentOptionsMixin, _$vue.ComponentOptionsMixin, {}, string, _$vue.PublicProps, Readonly<NotMessageProps> & Readonly<{}>, {
|
|
351
|
+
as: string;
|
|
352
|
+
}, {}, {}, {}, string, _$vue.ComponentProvideOptions, false, {}, any>;
|
|
353
|
+
declare const __VLS_export$1: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
354
|
+
declare const _default$3: typeof __VLS_export$1;
|
|
355
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
356
|
+
new (): {
|
|
357
|
+
$slots: S;
|
|
358
|
+
};
|
|
359
|
+
};
|
|
360
|
+
//#endregion
|
|
361
|
+
//#region src/types/not-array-field.d.ts
|
|
362
|
+
/** Props for the `NotArrayField` component. */
|
|
363
|
+
type NotArrayFieldProps<TItemSchema extends StandardSchemaV1 = StandardSchemaV1> = {
|
|
364
|
+
/** Dot-separated path to the array within the form values. */path: string;
|
|
365
|
+
/**
|
|
366
|
+
* Schema for a single array item — used purely for type inference.
|
|
367
|
+
* Enables typed `append`, `prepend`, `insert`, and `update` methods in the slot.
|
|
368
|
+
*
|
|
369
|
+
* ```vue
|
|
370
|
+
* <NotArrayField path="tags" :item-schema="z.string()">
|
|
371
|
+
* <!-- append now expects a string -->
|
|
372
|
+
* </NotArrayField>
|
|
373
|
+
* ```
|
|
374
|
+
*/
|
|
375
|
+
itemSchema?: TItemSchema;
|
|
376
|
+
/**
|
|
377
|
+
* Explicit form instance override.
|
|
378
|
+
* Takes priority over the instance provided by a `NotForm` ancestor.
|
|
379
|
+
* Required when using `NotArrayField` outside of a `NotForm`.
|
|
380
|
+
*/
|
|
381
|
+
form?: NotFormInstance<any>;
|
|
382
|
+
/**
|
|
383
|
+
* Per-field validation trigger overrides applied to the array as a whole.
|
|
384
|
+
* Merged over the form-wide `validateOn` — only the keys you specify are overridden.
|
|
385
|
+
*/
|
|
386
|
+
validateOn?: Partial<Record<Extract<ValidationTrigger, 'onMount' | 'onChange'>, boolean>>;
|
|
387
|
+
};
|
|
388
|
+
/**
|
|
389
|
+
* Represents a single item in the array field.
|
|
390
|
+
* Use `key` for `v-for` tracking and `path` to pass to a nested `NotField`.
|
|
391
|
+
*
|
|
392
|
+
* ```vue
|
|
393
|
+
* <NotArrayField path="tags" v-slot="{ items }">
|
|
394
|
+
* <NotField v-for="item in items" :key="item.key" :path="item.path" v-slot="{ events }">
|
|
395
|
+
* <input v-model="form.values.tags[item.index]" v-bind="events" />
|
|
396
|
+
* </NotField>
|
|
397
|
+
* </NotArrayField>
|
|
398
|
+
* ```
|
|
399
|
+
*/
|
|
400
|
+
type NotArrayFieldItem = {
|
|
401
|
+
/** Stable key for `v-for` — does not change when items are reordered. */key: string; /** Current index of this item in the array. */
|
|
402
|
+
index: number; /** Full dot-separated path to this item — pass directly to `NotField`. */
|
|
403
|
+
path: string;
|
|
404
|
+
};
|
|
405
|
+
/**
|
|
406
|
+
* Everything available inside the `NotArrayField` default slot.
|
|
407
|
+
* @template TSchema The form schema type.
|
|
408
|
+
* @template TItem The inferred type of a single array item.
|
|
409
|
+
*/
|
|
410
|
+
type NotArrayFieldSlotProps<TSchema extends ObjectSchema, TItem = any> = {
|
|
411
|
+
/** The dot-separated path to this array field. */path: string; /** The array items with stable keys and paths for use with `v-for`. */
|
|
412
|
+
items: NotArrayFieldItem[]; /** The current length of the array. */
|
|
413
|
+
length: number; /** All validation issues for this array field from the last validation run. */
|
|
414
|
+
errors: StandardSchemaV1.Issue[]; /** Whether this array field currently has no validation errors. */
|
|
415
|
+
isValid: boolean;
|
|
416
|
+
/**
|
|
417
|
+
* Whether any item in this array has been touched.
|
|
418
|
+
* Derived from the form's `touchedFields` set.
|
|
419
|
+
*/
|
|
420
|
+
isTouched: boolean;
|
|
421
|
+
/**
|
|
422
|
+
* Whether any item in this array differs from its initial value.
|
|
423
|
+
* Derived from the form's `dirtyFields` set.
|
|
424
|
+
*/
|
|
425
|
+
isDirty: boolean; /** Whether validation is currently running for this array field. */
|
|
426
|
+
isValidating: boolean;
|
|
427
|
+
/**
|
|
428
|
+
* Manually triggers validation for this array field.
|
|
429
|
+
* Useful when mutations are performed programmatically outside of the normal flow.
|
|
430
|
+
*/
|
|
431
|
+
validate: () => ReturnType<NotFormInstance<TSchema>['validateField']>;
|
|
432
|
+
/**
|
|
433
|
+
* Appends a new item to the end of the array.
|
|
434
|
+
* @param value The value to append.
|
|
435
|
+
*/
|
|
436
|
+
append: (value: TItem) => void;
|
|
437
|
+
/**
|
|
438
|
+
* Prepends a new item to the beginning of the array.
|
|
439
|
+
* @param value The value to prepend.
|
|
440
|
+
*/
|
|
441
|
+
prepend: (value: TItem) => void;
|
|
442
|
+
/**
|
|
443
|
+
* Removes the item at the given index.
|
|
444
|
+
* @param index The index of the item to remove.
|
|
445
|
+
*/
|
|
446
|
+
remove: (index: number) => void;
|
|
447
|
+
/**
|
|
448
|
+
* Inserts a new item at the given index, shifting subsequent items forward.
|
|
449
|
+
* @param index The index to insert the item at.
|
|
450
|
+
* @param value The value to insert.
|
|
451
|
+
*/
|
|
452
|
+
insert: (index: number, value: TItem) => void;
|
|
453
|
+
/**
|
|
454
|
+
* Replaces the value at the given index.
|
|
455
|
+
* @param index The index of the item to replace.
|
|
456
|
+
* @param value The value to replace with.
|
|
457
|
+
*/
|
|
458
|
+
update: (index: number, value: TItem) => void;
|
|
459
|
+
/**
|
|
460
|
+
* Swaps the positions of two items in the array.
|
|
461
|
+
* @param indexA The index of the first item.
|
|
462
|
+
* @param indexB The index of the second item.
|
|
463
|
+
*/
|
|
464
|
+
swap: (indexA: number, indexB: number) => void;
|
|
465
|
+
/**
|
|
466
|
+
* Moves an item from one index to another, shifting items between them.
|
|
467
|
+
* @param from The current index of the item.
|
|
468
|
+
* @param to The target index.
|
|
469
|
+
*/
|
|
470
|
+
move: (from: number, to: number) => void;
|
|
471
|
+
};
|
|
472
|
+
/**
|
|
473
|
+
* Slots for the `NotArrayField` component.
|
|
474
|
+
* @template TSchema The form schema type.
|
|
475
|
+
* @template TItem The inferred type of a single array item.
|
|
476
|
+
*/
|
|
477
|
+
type NotArrayFieldSlots<TSchema extends ObjectSchema, TItem = any> = {
|
|
478
|
+
/** The default slot receives the full array state and manipulation methods. */default: (props: NotArrayFieldSlotProps<TSchema, TItem>) => [];
|
|
479
|
+
};
|
|
480
|
+
//#endregion
|
|
481
|
+
//#region src/components/not-array-field.vue.d.ts
|
|
482
|
+
declare const __VLS_export: <TSchema extends ObjectSchema, TItemSchema extends StandardSchemaV1 = StandardSchemaV1, TItem = StandardSchemaV1.InferInput<TItemSchema>>(__VLS_props: NonNullable<Awaited<typeof __VLS_setup>>["props"], __VLS_ctx?: __VLS_PrettifyLocal<Pick<NonNullable<Awaited<typeof __VLS_setup>>, "attrs" | "emit" | "slots">>, __VLS_exposed?: NonNullable<Awaited<typeof __VLS_setup>>["expose"], __VLS_setup?: Promise<{
|
|
483
|
+
props: _$vue.PublicProps & __VLS_PrettifyLocal<NotArrayFieldProps<TItemSchema>> & (typeof globalThis extends {
|
|
484
|
+
__VLS_PROPS_FALLBACK: infer P;
|
|
485
|
+
} ? P : {});
|
|
486
|
+
expose: (exposed: {}) => void;
|
|
487
|
+
attrs: any;
|
|
488
|
+
slots: NotArrayFieldSlots<TSchema, TItem>;
|
|
489
|
+
emit: {};
|
|
490
|
+
}>) => _$vue.VNode & {
|
|
491
|
+
__ctx?: Awaited<typeof __VLS_setup>;
|
|
492
|
+
};
|
|
330
493
|
declare const _default: typeof __VLS_export;
|
|
331
494
|
type __VLS_PrettifyLocal<T> = (T extends any ? { [K in keyof T]: T[K] } : { [K in keyof T as K]: T[K] }) & {};
|
|
332
495
|
//#endregion
|
|
333
|
-
export {
|
|
496
|
+
export { DeepPartial, _default as NotArrayField, NotArrayFieldItem, NotArrayFieldProps, NotArrayFieldSlotProps, NotArrayFieldSlots, _default$1 as NotField, NotFieldEvents, NotFieldProps, NotFieldSlotProps, NotFieldSlots, _default$2 as NotForm, NotFormInstance, NotFormProps, NotFormSlots, _default$3 as NotMessage, NotMessageProps, NotMessageSlotProps, NotMessageSlots, ObjectSchema, Paths, UseNotFormConfig, ValidationMode, ValidationTrigger, useNotForm };
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { computed, createElementBlock, defineComponent, guardReactiveProps, inject, markRaw, nextTick, normalizeProps, onMounted, openBlock, provide, reactive, ref, renderSlot, toValue, unref, useAttrs } from "vue";
|
|
1
|
+
import { computed, createBlock, createCommentVNode, createElementBlock, createTextVNode, defineComponent, guardReactiveProps, inject, markRaw, mergeProps, nextTick, normalizeProps, onMounted, openBlock, provide, reactive, ref, renderSlot, resolveDynamicComponent, toDisplayString, toValue, unref, useAttrs, withCtx } from "vue";
|
|
2
2
|
import { klona } from "klona/full";
|
|
3
3
|
import { dequal } from "dequal";
|
|
4
4
|
import { deepKeys, deleteProperty, getProperty, hasProperty, parsePath, setProperty } from "dot-prop";
|
|
@@ -189,12 +189,9 @@ function useNotForm(config) {
|
|
|
189
189
|
if (newValues) initialValues = klona(newValues);
|
|
190
190
|
if (newErrors) initialErrors = klona(newErrors);
|
|
191
191
|
const freshValues = klona(initialValues);
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
deepKeys(freshValues).forEach((path) => {
|
|
196
|
-
setProperty(values, path, getProperty(freshValues, path));
|
|
197
|
-
});
|
|
192
|
+
const current = values;
|
|
193
|
+
for (const key of Object.keys(current)) if (!hasProperty(freshValues, key)) deleteProperty(values, key);
|
|
194
|
+
for (const key of Object.keys(freshValues)) setProperty(values, key, getProperty(freshValues, key));
|
|
198
195
|
errors.splice(0, errors.length, ...klona(initialErrors));
|
|
199
196
|
touchedFields.clear();
|
|
200
197
|
dirtyFields.clear();
|
|
@@ -365,4 +362,199 @@ var not_field_default = /* @__PURE__ */ defineComponent({
|
|
|
365
362
|
}
|
|
366
363
|
});
|
|
367
364
|
//#endregion
|
|
368
|
-
|
|
365
|
+
//#region src/components/not-message.vue
|
|
366
|
+
var not_message_default = /* @__PURE__ */ defineComponent({
|
|
367
|
+
inheritAttrs: false,
|
|
368
|
+
__name: "not-message",
|
|
369
|
+
props: {
|
|
370
|
+
path: {
|
|
371
|
+
type: String,
|
|
372
|
+
required: true
|
|
373
|
+
},
|
|
374
|
+
as: {
|
|
375
|
+
type: String,
|
|
376
|
+
required: false,
|
|
377
|
+
default: "span"
|
|
378
|
+
},
|
|
379
|
+
form: {
|
|
380
|
+
type: null,
|
|
381
|
+
required: false
|
|
382
|
+
}
|
|
383
|
+
},
|
|
384
|
+
setup(__props) {
|
|
385
|
+
const attributes = useAttrs();
|
|
386
|
+
const props = __props;
|
|
387
|
+
const form = useNotFormInstance(props.form);
|
|
388
|
+
const message = computed(() => form.errorsMap.value[props.path]);
|
|
389
|
+
const slotProps = computed(() => ({
|
|
390
|
+
message: message.value,
|
|
391
|
+
attributes
|
|
392
|
+
}));
|
|
393
|
+
return (_ctx, _cache) => {
|
|
394
|
+
return renderSlot(_ctx.$slots, "default", normalizeProps(guardReactiveProps(slotProps.value)), () => [message.value ? (openBlock(), createBlock(resolveDynamicComponent(props.as), normalizeProps(mergeProps({ key: 0 }, unref(attributes))), {
|
|
395
|
+
default: withCtx(() => [createTextVNode(toDisplayString(message.value), 1)]),
|
|
396
|
+
_: 1
|
|
397
|
+
}, 16)) : createCommentVNode("v-if", true)]);
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
});
|
|
401
|
+
//#endregion
|
|
402
|
+
//#region src/components/not-array-field.vue
|
|
403
|
+
var not_array_field_default = /* @__PURE__ */ defineComponent({
|
|
404
|
+
inheritAttrs: false,
|
|
405
|
+
__name: "not-array-field",
|
|
406
|
+
props: {
|
|
407
|
+
path: {
|
|
408
|
+
type: String,
|
|
409
|
+
required: true
|
|
410
|
+
},
|
|
411
|
+
itemSchema: {
|
|
412
|
+
type: null,
|
|
413
|
+
required: false
|
|
414
|
+
},
|
|
415
|
+
form: {
|
|
416
|
+
type: null,
|
|
417
|
+
required: false
|
|
418
|
+
},
|
|
419
|
+
validateOn: {
|
|
420
|
+
type: Object,
|
|
421
|
+
required: false
|
|
422
|
+
}
|
|
423
|
+
},
|
|
424
|
+
setup(__props) {
|
|
425
|
+
const props = __props;
|
|
426
|
+
const form = useNotFormInstance(props.form);
|
|
427
|
+
const validateOn = computed(() => ({
|
|
428
|
+
onMount: props.validateOn?.onMount ?? form.validateOn.onMount,
|
|
429
|
+
onChange: props.validateOn?.onChange ?? form.validateOn.onChange
|
|
430
|
+
}));
|
|
431
|
+
const isValidating = ref(false);
|
|
432
|
+
/**
|
|
433
|
+
* Stable keys per item — survive reorders, removals, and inserts.
|
|
434
|
+
* Seeded from the length of the initial array so existing items have keys from the start.
|
|
435
|
+
*/
|
|
436
|
+
const itemKeys = ref((() => {
|
|
437
|
+
const initial = getProperty(form.values, props.path);
|
|
438
|
+
const length = Array.isArray(initial) ? initial.length : 0;
|
|
439
|
+
return Array.from({ length }, (_, index) => `${props.path}-${index}-${Date.now()}`);
|
|
440
|
+
})());
|
|
441
|
+
let keyCounter = itemKeys.value.length;
|
|
442
|
+
const generateKey = () => `${props.path}-${keyCounter++}-${Date.now()}`;
|
|
443
|
+
const array = computed(() => {
|
|
444
|
+
const value = getProperty(form.values, props.path);
|
|
445
|
+
return Array.isArray(value) ? value : [];
|
|
446
|
+
});
|
|
447
|
+
const items = computed(() => array.value.map((_, index) => ({
|
|
448
|
+
key: itemKeys.value[index] ?? `${props.path}-fallback-${index}`,
|
|
449
|
+
index,
|
|
450
|
+
path: `${props.path}.${index}`
|
|
451
|
+
})));
|
|
452
|
+
const errors = computed(() => form.getFieldErrors(props.path));
|
|
453
|
+
const isValid = computed(() => errors.value.length === 0);
|
|
454
|
+
/**
|
|
455
|
+
* Derived from the form's touchedFields set.
|
|
456
|
+
* True if the array path itself or any of its item paths have been touched.
|
|
457
|
+
*/
|
|
458
|
+
const isTouched = computed(() => form.touchedFields.has(props.path) || [...form.touchedFields].some((path) => path.startsWith(`${props.path}.`)));
|
|
459
|
+
/**
|
|
460
|
+
* Derived from the form's dirtyFields set.
|
|
461
|
+
* True if the array path itself or any of its item paths are dirty.
|
|
462
|
+
*/
|
|
463
|
+
const isDirty = computed(() => form.dirtyFields.has(props.path) || [...form.dirtyFields].some((path) => path.startsWith(`${props.path}.`)));
|
|
464
|
+
const validate = async () => {
|
|
465
|
+
isValidating.value = true;
|
|
466
|
+
try {
|
|
467
|
+
return await form.validateField(props.path);
|
|
468
|
+
} finally {
|
|
469
|
+
isValidating.value = false;
|
|
470
|
+
}
|
|
471
|
+
};
|
|
472
|
+
/**
|
|
473
|
+
* Re-aligns itemKeys with the current array length.
|
|
474
|
+
* Called at the start of every mutation so keys stay consistent
|
|
475
|
+
* after external changes (e.g. form.reset()).
|
|
476
|
+
*/
|
|
477
|
+
const syncKeys = () => {
|
|
478
|
+
const arrayLength = array.value.length;
|
|
479
|
+
if (itemKeys.value.length > arrayLength) itemKeys.value.length = arrayLength;
|
|
480
|
+
else while (itemKeys.value.length < arrayLength) itemKeys.value.push(generateKey());
|
|
481
|
+
};
|
|
482
|
+
/**
|
|
483
|
+
* Applies an update to a copy of the current array, writes it back to the form values,
|
|
484
|
+
* and marks the array path as touched. Dirty state is derived from the form's Sets
|
|
485
|
+
* automatically via the computed above so no explicit dirty call is needed here.
|
|
486
|
+
*/
|
|
487
|
+
const mutate = (updater) => {
|
|
488
|
+
syncKeys();
|
|
489
|
+
const current = [...array.value];
|
|
490
|
+
updater(current);
|
|
491
|
+
setProperty(form.values, props.path, current);
|
|
492
|
+
form.touchField(props.path);
|
|
493
|
+
if (dequal(current, getProperty(form.initialValues, props.path))) form.dirtyFields.delete(props.path);
|
|
494
|
+
else form.dirtyField(props.path);
|
|
495
|
+
if (validateOn.value.onChange) validate();
|
|
496
|
+
};
|
|
497
|
+
const append = (value) => {
|
|
498
|
+
itemKeys.value.push(generateKey());
|
|
499
|
+
mutate((current) => current.push(value));
|
|
500
|
+
};
|
|
501
|
+
const prepend = (value) => {
|
|
502
|
+
itemKeys.value.unshift(generateKey());
|
|
503
|
+
mutate((current) => current.unshift(value));
|
|
504
|
+
};
|
|
505
|
+
const remove = (index) => {
|
|
506
|
+
mutate((current) => current.splice(index, 1));
|
|
507
|
+
itemKeys.value.splice(index, 1);
|
|
508
|
+
};
|
|
509
|
+
const insert = (index, value) => {
|
|
510
|
+
itemKeys.value.splice(index, 0, generateKey());
|
|
511
|
+
mutate((current) => current.splice(index, 0, value));
|
|
512
|
+
};
|
|
513
|
+
const update = (index, value) => {
|
|
514
|
+
mutate((current) => {
|
|
515
|
+
current[index] = value;
|
|
516
|
+
});
|
|
517
|
+
};
|
|
518
|
+
const swap = (indexA, indexB) => {
|
|
519
|
+
mutate((current) => {
|
|
520
|
+
[current[indexA], current[indexB]] = [current[indexB], current[indexA]];
|
|
521
|
+
});
|
|
522
|
+
[itemKeys.value[indexA], itemKeys.value[indexB]] = [itemKeys.value[indexB], itemKeys.value[indexA]];
|
|
523
|
+
};
|
|
524
|
+
const move = (from, to) => {
|
|
525
|
+
mutate((current) => {
|
|
526
|
+
const [item] = current.splice(from, 1);
|
|
527
|
+
current.splice(to, 0, item);
|
|
528
|
+
});
|
|
529
|
+
const [key] = itemKeys.value.splice(from, 1);
|
|
530
|
+
itemKeys.value.splice(to, 0, key);
|
|
531
|
+
};
|
|
532
|
+
onMounted(async () => {
|
|
533
|
+
await nextTick();
|
|
534
|
+
if (validateOn.value.onMount) validate();
|
|
535
|
+
});
|
|
536
|
+
const slotProps = computed(() => ({
|
|
537
|
+
path: props.path,
|
|
538
|
+
items: items.value,
|
|
539
|
+
length: array.value.length,
|
|
540
|
+
errors: errors.value,
|
|
541
|
+
isValid: isValid.value,
|
|
542
|
+
isTouched: isTouched.value,
|
|
543
|
+
isDirty: isDirty.value,
|
|
544
|
+
isValidating: isValidating.value,
|
|
545
|
+
validate,
|
|
546
|
+
append,
|
|
547
|
+
prepend,
|
|
548
|
+
remove,
|
|
549
|
+
insert,
|
|
550
|
+
update,
|
|
551
|
+
swap,
|
|
552
|
+
move
|
|
553
|
+
}));
|
|
554
|
+
return (_ctx, _cache) => {
|
|
555
|
+
return renderSlot(_ctx.$slots, "default", normalizeProps(guardReactiveProps(slotProps.value)));
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
});
|
|
559
|
+
//#endregion
|
|
560
|
+
export { not_array_field_default as NotArrayField, not_field_default as NotField, not_form_default as NotForm, not_message_default as NotMessage, useNotForm };
|