notform 2.0.0-beta.1 → 2.0.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +21 -0
- package/dist/index.js +49 -23
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -260,6 +260,27 @@ type NotFieldProps = {
|
|
|
260
260
|
* ```
|
|
261
261
|
*/
|
|
262
262
|
validateOn?: Partial<Record<ValidationTrigger, boolean>>;
|
|
263
|
+
/**
|
|
264
|
+
* Debounce delay in milliseconds for input- and change-triggered validation.
|
|
265
|
+
*
|
|
266
|
+
* When set, validation is deferred until the user stops typing for the given
|
|
267
|
+
* duration. Only the final call within the window runs — earlier ones are
|
|
268
|
+
* cancelled. Useful for async validators (e.g. username availability checks)
|
|
269
|
+
* where firing on every keystroke would cause excessive requests.
|
|
270
|
+
*
|
|
271
|
+
* Blur- and submit-triggered validation always runs immediately, regardless
|
|
272
|
+
* of this setting, so the field never feels unresponsive when the user leaves.
|
|
273
|
+
*
|
|
274
|
+
* ```vue
|
|
275
|
+
* <!-- validate 400ms after the user stops typing -->
|
|
276
|
+
* <NotField path="username" :debounce="400" v-slot="{ events }">
|
|
277
|
+
* <input v-model="form.values.username" v-bind="events" />
|
|
278
|
+
* </NotField>
|
|
279
|
+
* ```
|
|
280
|
+
*
|
|
281
|
+
* Omit or set to `0` to disable debouncing (default behaviour).
|
|
282
|
+
*/
|
|
283
|
+
debounce?: number;
|
|
263
284
|
};
|
|
264
285
|
/**
|
|
265
286
|
* Everything available inside the `NotField` default slot.
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
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, watch, withCtx } from "vue";
|
|
1
|
+
import { computed, createBlock, createCommentVNode, createElementBlock, createTextVNode, defineComponent, guardReactiveProps, inject, markRaw, mergeProps, nextTick, normalizeProps, onMounted, onUnmounted, openBlock, provide, reactive, ref, renderSlot, resolveDynamicComponent, toDisplayString, toValue, unref, useAttrs, watch, 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";
|
|
@@ -310,6 +310,10 @@ var not_field_default = /* @__PURE__ */ defineComponent({
|
|
|
310
310
|
validateOn: {
|
|
311
311
|
type: Object,
|
|
312
312
|
required: false
|
|
313
|
+
},
|
|
314
|
+
debounce: {
|
|
315
|
+
type: Number,
|
|
316
|
+
required: false
|
|
313
317
|
}
|
|
314
318
|
},
|
|
315
319
|
setup(__props) {
|
|
@@ -320,6 +324,36 @@ var not_field_default = /* @__PURE__ */ defineComponent({
|
|
|
320
324
|
...props.validateOn
|
|
321
325
|
}));
|
|
322
326
|
const isValidating = ref(false);
|
|
327
|
+
/** Timer handle for the current pending debounced validation, if any. */
|
|
328
|
+
let debounceTimer;
|
|
329
|
+
/**
|
|
330
|
+
* Cancels any pending debounced validation without running it.
|
|
331
|
+
* Called on blur (so blur's own immediate validation takes over) and on unmount
|
|
332
|
+
* (to prevent a timer from firing after the component is gone).
|
|
333
|
+
*/
|
|
334
|
+
const clearDebounce = () => {
|
|
335
|
+
if (debounceTimer !== void 0) {
|
|
336
|
+
clearTimeout(debounceTimer);
|
|
337
|
+
debounceTimer = void 0;
|
|
338
|
+
}
|
|
339
|
+
};
|
|
340
|
+
/**
|
|
341
|
+
* Schedules a validation run, respecting the field's `debounce` prop.
|
|
342
|
+
*
|
|
343
|
+
* - If `debounce` is `0` or omitted, validation runs synchronously.
|
|
344
|
+
* - Otherwise, any pending timer is cancelled and a new one is started.
|
|
345
|
+
* Only the final call within the window actually validates — useful for
|
|
346
|
+
* async checks (availability lookups, server-side rules) where firing on
|
|
347
|
+
* every keystroke would be wasteful.
|
|
348
|
+
*/
|
|
349
|
+
const scheduleValidation = () => {
|
|
350
|
+
if (!props.debounce) {
|
|
351
|
+
validate();
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
clearDebounce();
|
|
355
|
+
debounceTimer = setTimeout(validate, props.debounce);
|
|
356
|
+
};
|
|
323
357
|
const value = computed(() => getProperty(form.values, props.path));
|
|
324
358
|
const errors = computed(() => form.getFieldErrors(props.path));
|
|
325
359
|
const isValid = computed(() => errors.value.length === 0);
|
|
@@ -343,21 +377,22 @@ var not_field_default = /* @__PURE__ */ defineComponent({
|
|
|
343
377
|
}
|
|
344
378
|
};
|
|
345
379
|
const onBlur = () => {
|
|
380
|
+
clearDebounce();
|
|
346
381
|
form.touchField(props.path);
|
|
347
382
|
if (validateOn.value.onBlur) validate();
|
|
348
383
|
};
|
|
349
384
|
const onInput = () => {
|
|
350
385
|
updateDirty();
|
|
351
386
|
if (!validateOn.value.onInput) return;
|
|
352
|
-
if (form.validationMode.eager &&
|
|
387
|
+
if (form.validationMode.eager && !isValid.value) scheduleValidation();
|
|
353
388
|
};
|
|
354
389
|
const onChange = () => {
|
|
355
390
|
updateDirty();
|
|
356
391
|
if (!validateOn.value.onChange) return;
|
|
357
|
-
if (form.validationMode.eager &&
|
|
392
|
+
if (form.validationMode.eager && !isValid.value) scheduleValidation();
|
|
358
393
|
};
|
|
359
394
|
const onFocus = () => {
|
|
360
|
-
if (validateOn.value.onFocus)
|
|
395
|
+
if (validateOn.value.onFocus) scheduleValidation();
|
|
361
396
|
};
|
|
362
397
|
const events = computed(() => ({
|
|
363
398
|
onBlur,
|
|
@@ -369,6 +404,7 @@ var not_field_default = /* @__PURE__ */ defineComponent({
|
|
|
369
404
|
await nextTick();
|
|
370
405
|
if (validateOn.value.onMount) validate();
|
|
371
406
|
});
|
|
407
|
+
onUnmounted(clearDebounce);
|
|
372
408
|
const slotProps = computed(() => ({
|
|
373
409
|
path: props.path,
|
|
374
410
|
value: value.value,
|
|
@@ -497,11 +533,7 @@ var not_array_field_default = /* @__PURE__ */ defineComponent({
|
|
|
497
533
|
isValidating.value = false;
|
|
498
534
|
}
|
|
499
535
|
};
|
|
500
|
-
/**
|
|
501
|
-
* Re-aligns itemKeys with the current array length.
|
|
502
|
-
* Called at the start of every mutation so keys stay consistent
|
|
503
|
-
* after external changes (e.g. form.reset()).
|
|
504
|
-
*/
|
|
536
|
+
/** Re-aligns itemKeys with the current array length. */
|
|
505
537
|
const syncKeys = () => {
|
|
506
538
|
const arrayLength = array.value.length;
|
|
507
539
|
if (itemKeys.value.length > arrayLength) itemKeys.value.length = arrayLength;
|
|
@@ -513,7 +545,6 @@ var not_array_field_default = /* @__PURE__ */ defineComponent({
|
|
|
513
545
|
* automatically via the computed above so no explicit dirty call is needed here.
|
|
514
546
|
*/
|
|
515
547
|
const mutate = (updater) => {
|
|
516
|
-
syncKeys();
|
|
517
548
|
const current = [...array.value];
|
|
518
549
|
updater(current);
|
|
519
550
|
setProperty(form.values, props.path, current);
|
|
@@ -562,21 +593,16 @@ var not_array_field_default = /* @__PURE__ */ defineComponent({
|
|
|
562
593
|
if (validateOn.value.onMount) validate();
|
|
563
594
|
});
|
|
564
595
|
/**
|
|
565
|
-
* Syncs `itemKeys`
|
|
566
|
-
*
|
|
567
|
-
*
|
|
568
|
-
* Without this, `itemKeys` would be stale until the next mutation:
|
|
569
|
-
* - If the reset shrinks the array, a subsequent `append` would call
|
|
570
|
-
* `syncKeys` and regenerate keys for the surviving items, causing
|
|
571
|
-
* Vue to remount them unnecessarily.
|
|
572
|
-
* - If the reset grows the array, items render with `fallback` keys
|
|
573
|
-
* until the next mutation, breaking key stability guarantees.
|
|
596
|
+
* Syncs `itemKeys` when the array length changes outside of this component's
|
|
597
|
+
* own mutation methods — most commonly after `form.reset()`.
|
|
574
598
|
*
|
|
575
|
-
*
|
|
576
|
-
*
|
|
577
|
-
*
|
|
599
|
+
* Watching `length` rather than the full array avoids unnecessary syncs on
|
|
600
|
+
* `update()` and `swap()`, which mutate values but never add or remove items.
|
|
601
|
+
* When our own mutations run, they manage keys explicitly before writing to
|
|
602
|
+
* the array, so by the time this watcher fires the lengths already match and
|
|
603
|
+
* `syncKeys` is a cheap no-op.
|
|
578
604
|
*/
|
|
579
|
-
watch(
|
|
605
|
+
watch(() => array.value.length, syncKeys);
|
|
580
606
|
const slotProps = computed(() => ({
|
|
581
607
|
path: props.path,
|
|
582
608
|
items: items.value,
|