ngx-vest-forms 2.6.0 → 2.7.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ngx-vest-forms",
3
- "version": "2.6.0",
3
+ "version": "2.7.0",
4
4
  "license": "MIT",
5
5
  "author": "Brecht Billiet, Arjen Althoff",
6
6
  "description": "Opinionated template-driven forms library for Angular with Vest.js integration",
@@ -42,5 +42,6 @@
42
42
  "types": "./types/ngx-vest-forms.d.ts",
43
43
  "default": "./fesm2022/ngx-vest-forms.mjs"
44
44
  }
45
- }
45
+ },
46
+ "type": "module"
46
47
  }
@@ -85,7 +85,8 @@ declare class FormControlStateDirective {
85
85
  /**
86
86
  * Whether this control has been validated at least once.
87
87
  * True after the first validation completes, even if the user hasn't touched the field.
88
- * This enables showing errors for validationConfig-triggered validations.
88
+ * This is primarily used for warning display and other derived state that should react
89
+ * to validationConfig-triggered validation even before the user touches the field.
89
90
  */
90
91
  readonly hasBeenValidated: _angular_core.Signal<boolean>;
91
92
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<FormControlStateDirective, never>;
@@ -144,21 +145,26 @@ declare class FormErrorDisplayDirective {
144
145
  * This keeps programmatic `NgForm.onSubmit()` reactive in zoneless mode and
145
146
  * avoids depending on `NgForm.submitted`, whose getter intentionally reads an
146
147
  * internal signal with `untracked()`.
148
+ *
149
+ * Note: when this directive is used outside an `NgForm` (no parent form), no
150
+ * subscription is wired up and this signal stays `false` for the lifetime of
151
+ * the directive. Consumers relying on submitted state must host the field
152
+ * inside an `NgForm` (or `ngxVestForm`).
147
153
  */
148
154
  readonly formSubmitted: Signal<boolean>;
149
155
  constructor();
150
156
  /**
151
157
  * Determines if errors should be shown based on the specified display mode
152
- * and the control's state (touched/submitted/validated).
158
+ * and the control's state (touched/submitted/dirty).
153
159
  *
154
160
  * Note: We check both hasErrors (extracted error messages) AND isInvalid (Angular's validation state)
155
161
  * because in some cases (like conditional validations via validationConfig), the control is marked
156
162
  * as invalid by Angular before error messages are extracted from Vest. This ensures aria-invalid
157
163
  * is set correctly even during the validation propagation delay.
158
164
  *
159
- * For validationConfig-triggered validations: A field can be validated without being touched
160
- * (e.g., confirmPassword validated when password changes). We check hasBeenValidated to show
161
- * errors in these scenarios, providing better UX and proper ARIA attributes.
165
+ * For validationConfig-triggered validations, a field may become invalid before it has been
166
+ * touched. Error visibility still respects the field's own `errorDisplayMode`, so untouched
167
+ * dependent fields can remain visually quiet until blur or submit.
162
168
  */
163
169
  readonly shouldShowErrors: Signal<boolean>;
164
170
  /**
@@ -993,6 +999,34 @@ type NgxTypedVestSuite<T> = StaticSuite<string, string, NgxTypedSuiteCallback<T>
993
999
  * This ensures backward compatibility while supporting the new typed API.
994
1000
  */
995
1001
  type NgxValidationConfig<T = unknown> = Record<string, string[]> | ValidationConfigMap<T> | null;
1002
+ /**
1003
+ * Payload emitted when a named control inside the form loses focus.
1004
+ *
1005
+ * This is intentionally low-level so app code can build workflows such as
1006
+ * draft auto-save, analytics, or blur-driven side effects without the form
1007
+ * library taking ownership of persistence behavior.
1008
+ *
1009
+ * It is not intended as a blur-time workaround for dependent field validation;
1010
+ * for that pattern, prefer `validationConfig` plus each target field's own
1011
+ * `errorDisplayMode`.
1012
+ *
1013
+ * The emitted `field` is the full dotted control path (e.g.
1014
+ * `passwords.confirm`, `businessHours.values.0.from`) regardless of whether
1015
+ * the surrounding groups use static `ngModelGroup="key"` or dynamic
1016
+ * `[ngModelGroup]="expr"` bindings, because the path is read from the live
1017
+ * `NgModel` directive registered with the form.
1018
+ *
1019
+ * @publicApi
1020
+ */
1021
+ type NgxFieldBlurEvent<T = unknown> = {
1022
+ field: string;
1023
+ value: unknown;
1024
+ formValue: T | null;
1025
+ dirty: boolean;
1026
+ touched: boolean;
1027
+ valid: boolean;
1028
+ pending: boolean;
1029
+ };
996
1030
  /**
997
1031
  * Main form directive for ngx-vest-forms that bridges Angular template-driven forms with Vest.js validation.
998
1032
  *
@@ -1142,6 +1176,12 @@ declare class FormDirective<T extends Record<string, unknown>> {
1142
1176
  * Cleanup is handled automatically by the directive when it's destroyed.
1143
1177
  */
1144
1178
  readonly validChange: _angular_core.OutputRef<boolean>;
1179
+ /**
1180
+ * Emits when a named control inside the form loses focus.
1181
+ *
1182
+ * Useful for application-level workflows such as draft auto-save on blur.
1183
+ */
1184
+ readonly fieldBlur: _angular_core.OutputEmitterRef<NgxFieldBlurEvent<T>>;
1145
1185
  /**
1146
1186
  * Track validation in progress to prevent circular triggering (Issue #19)
1147
1187
  */
@@ -1314,7 +1354,7 @@ declare class FormDirective<T extends Record<string, unknown>> {
1314
1354
  * Host handler: called whenever any descendant field loses focus.
1315
1355
  * Used to make touched-path tracking react immediately on blur/tab.
1316
1356
  */
1317
- onFormFocusOut(): void;
1357
+ onFormFocusOut(event: FocusEvent): void;
1318
1358
  /**
1319
1359
  * Resets the form to a pristine, untouched state with optional new values.
1320
1360
  *
@@ -1380,7 +1420,7 @@ declare class FormDirective<T extends Record<string, unknown>> {
1380
1420
  */
1381
1421
  createAsyncValidator(field: string, validationOptions: ValidationOptions): AsyncValidatorFn;
1382
1422
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<FormDirective<any>, never>;
1383
- static ɵdir: _angular_core.ɵɵDirectiveDeclaration<FormDirective<any>, "form[scVestForm], form[ngxVestForm]", ["scVestForm", "ngxVestForm"], { "formValue": { "alias": "formValue"; "required": false; "isSignal": true; }; "suite": { "alias": "suite"; "required": false; "isSignal": true; }; "formShape": { "alias": "formShape"; "required": false; "isSignal": true; }; "validationConfig": { "alias": "validationConfig"; "required": false; "isSignal": true; }; }, { "formValueChange": "formValueChange"; "errorsChange": "errorsChange"; "dirtyChange": "dirtyChange"; "validChange": "validChange"; }, never, never, true, never>;
1423
+ static ɵdir: _angular_core.ɵɵDirectiveDeclaration<FormDirective<any>, "form[scVestForm], form[ngxVestForm]", ["scVestForm", "ngxVestForm"], { "formValue": { "alias": "formValue"; "required": false; "isSignal": true; }; "suite": { "alias": "suite"; "required": false; "isSignal": true; }; "formShape": { "alias": "formShape"; "required": false; "isSignal": true; }; "validationConfig": { "alias": "validationConfig"; "required": false; "isSignal": true; }; }, { "formValueChange": "formValueChange"; "errorsChange": "errorsChange"; "dirtyChange": "dirtyChange"; "validChange": "validChange"; "fieldBlur": "fieldBlur"; }, never, never, true, never>;
1384
1424
  }
1385
1425
 
1386
1426
  /**
@@ -1473,12 +1513,16 @@ declare class ValidateRootFormDirective<T> implements AsyncValidator, AfterViewI
1473
1513
  readonly ngxValidateRootForm: _angular_core.InputSignalWithTransform<boolean, unknown>;
1474
1514
  /**
1475
1515
  * Validation mode:
1476
- * - 'submit' (default): Only validates after form submission
1477
- * - 'live': Validates on every value change
1478
- * Accepts both validateRootFormMode and ngxValidateRootFormMode
1516
+ * - `'submit'` (effective default): Only validates after form submission.
1517
+ * - `'live'`: Validates on every value change.
1518
+ *
1519
+ * Both inputs default to `undefined` so we can detect whether the consumer
1520
+ * set them explicitly. Precedence is `ngx ?? legacy ?? 'submit'`, which
1521
+ * matches the documented behavior — observable only when both attributes
1522
+ * are set explicitly on the same form.
1479
1523
  */
1480
- readonly validateRootFormMode: _angular_core.InputSignal<"submit" | "live">;
1481
- readonly ngxValidateRootFormMode: _angular_core.InputSignal<"submit" | "live">;
1524
+ readonly validateRootFormMode: _angular_core.InputSignal<"submit" | "live" | undefined>;
1525
+ readonly ngxValidateRootFormMode: _angular_core.InputSignal<"submit" | "live" | undefined>;
1482
1526
  constructor();
1483
1527
  /**
1484
1528
  * Subscribe to form submit event using NgForm.ngSubmit EventEmitter
@@ -2302,15 +2346,6 @@ declare function getFormGroupField(rootForm: FormGroup, control: AbstractControl
2302
2346
  * @param form
2303
2347
  */
2304
2348
  declare function mergeValuesAndRawValues<T>(form: FormGroup): T;
2305
- /**
2306
- * Performs a deep-clone of an object
2307
- * @param obj
2308
- *
2309
- * @deprecated Use official ES {@link https://developer.mozilla.org/en-US/docs/Web/API/Window/structuredClone structuredClone} instead
2310
- *
2311
- * Browser Support: structuredClone is available in all modern browsers (Chrome 98+, Firefox 94+, Safari 15.4+, Edge 98+)
2312
- * and Node.js 17+. A polyfill is provided in test-setup.ts for Jest test environments.
2313
- */
2314
2349
  declare function cloneDeep<T>(object: T): T;
2315
2350
  /**
2316
2351
  * Sets a value in an object at the provided field path.
@@ -2360,6 +2395,12 @@ type DebouncedPendingStateOptions = {
2360
2395
  */
2361
2396
  minimumDisplay?: number;
2362
2397
  };
2398
+ /**
2399
+ * Accepts either a static options object or a reactive `Signal` (e.g. an
2400
+ * `input()` accessor) so consumers can update debounce timings at runtime
2401
+ * without recreating the pending-state machine.
2402
+ */
2403
+ type DebouncedPendingStateOptionsInput = DebouncedPendingStateOptions | Signal<DebouncedPendingStateOptions>;
2363
2404
  /**
2364
2405
  * Result of createDebouncedPendingState containing the debounced signal
2365
2406
  * and cleanup function.
@@ -2409,7 +2450,7 @@ type DebouncedPendingStateResult = {
2409
2450
  * @param options - Configuration options for debouncing behavior
2410
2451
  * @returns Object containing the debounced showPendingMessage signal and cleanup function
2411
2452
  */
2412
- declare function createDebouncedPendingState(isPending: Signal<boolean>, options?: DebouncedPendingStateOptions): DebouncedPendingStateResult;
2453
+ declare function createDebouncedPendingState(isPending: Signal<boolean>, options?: DebouncedPendingStateOptionsInput): DebouncedPendingStateResult;
2413
2454
 
2414
2455
  /**
2415
2456
  * Validates a form value against a shape to catch typos in `name` or `ngModelGroup` attributes.
@@ -2500,11 +2541,13 @@ declare function shallowEqual(obj1: unknown, obj2: unknown): boolean;
2500
2541
  * - Plain objects (with recursive deep comparison)
2501
2542
  * - Date objects (by timestamp comparison)
2502
2543
  * - RegExp objects (by source and flags comparison)
2503
- * - Set objects (by size and value membership)
2504
- * - Map objects (by size and key-value pairs)
2544
+ * - Set objects (reference equality only)
2545
+ * - Map objects (reference equality only)
2546
+ * - Functions (reference equality only — distinct function instances are never equal,
2547
+ * even if their source code is identical)
2505
2548
  *
2506
2549
  * **Safety Features:**
2507
- * - **Circular reference protection**: MaxDepth parameter prevents infinite recursion
2550
+ * - **Circular reference handling**: Tracks visited object pairs with `WeakMap<object, WeakSet<object>>`
2508
2551
  * - **Type coercion prevention**: Strict type checking before comparison
2509
2552
  * - **Null safety**: Proper handling of null and undefined values
2510
2553
  *
@@ -2516,7 +2559,8 @@ declare function shallowEqual(obj1: unknown, obj2: unknown): boolean;
2516
2559
  * ///
2517
2560
  * /// Memory usage:
2518
2561
  * /// JSON.stringify: Creates temporary strings (high GC pressure)
2519
- * /// fastDeepEqual: Zero allocations during comparison
2562
+ * /// fastDeepEqual: One small WeakMap of visited object pairs is allocated lazily on
2563
+ * /// first nested-container descent; primitive-only comparisons allocate nothing.
2520
2564
  * ```
2521
2565
  *
2522
2566
  * **Typical Usage in Forms:**
@@ -2532,10 +2576,15 @@ declare function shallowEqual(obj1: unknown, obj2: unknown): boolean;
2532
2576
  *
2533
2577
  * @param obj1 - First object to compare
2534
2578
  * @param obj2 - Second object to compare
2535
- * @param maxDepth - Maximum recursion depth to prevent infinite loops (default: 10)
2579
+ *
2580
+ * Cyclic arrays and plain objects are compared structurally by tracking visited object
2581
+ * pairs. Distinct cyclic graphs with the same structure compare equal. `Date` and
2582
+ * `RegExp` values compare structurally. `Map` and `Set` values compare by reference
2583
+ * only, so distinct instances are considered different even if their contents match.
2584
+ *
2536
2585
  * @returns true if objects are deeply equal by value
2537
2586
  */
2538
- declare function fastDeepEqual(obj1: unknown, obj2: unknown, maxDepth?: number): boolean;
2587
+ declare function fastDeepEqual(obj1: unknown, obj2: unknown): boolean;
2539
2588
 
2540
2589
  /**
2541
2590
  * @deprecated Use NGX_ERROR_DISPLAY_MODE_TOKEN instead
@@ -2607,5 +2656,46 @@ declare const NGX_WARNING_DISPLAY_MODE_TOKEN: InjectionToken<NgxWarningDisplayMo
2607
2656
  */
2608
2657
  declare const NGX_VALIDATION_CONFIG_DEBOUNCE_TOKEN: InjectionToken<number>;
2609
2658
 
2610
- export { ControlWrapperComponent, DEFAULT_FOCUS_SELECTOR, DEFAULT_INVALID_SELECTOR, FormControlStateDirective, FormDirective, FormErrorControlDirective, FormErrorDisplayDirective, FormGroupWrapperComponent, FormModelDirective, FormModelGroupDirective, NGX_ERROR_DISPLAY_MODE_TOKEN, NGX_VALIDATION_CONFIG_DEBOUNCE_TOKEN, NGX_WARNING_DISPLAY_MODE_TOKEN, NgxVestForms, ROOT_FORM, ROOT_FORM as ROOT_FORM_CONSTANT, SC_ERROR_DISPLAY_MODE_TOKEN, ValidateRootFormDirective, ValidationConfigBuilder, arrayToObject, clearFields, clearFieldsWhen, cloneDeep, createDebouncedPendingState, createEmptyFormState, createValidationConfig, deepArrayToObject, fastDeepEqual, getAllFormErrors, getFormControlField, getFormGroupField, keepFieldsWhen, mergeAriaDescribedBy, mergeValuesAndRawValues, objectToArray, parseAriaIdTokens, parseFieldPath, resolveAssociationTargets, set, setValueAtPath, shallowEqual, stringifyFieldPath, validateShape, vestForms, vestFormsViewProviders };
2611
- export type { AriaAssociationMode, DebouncedPendingStateOptions, DebouncedPendingStateResult, DeepPartial, DeepRequired, FieldPath, FieldPathValue, FormCompatibleDeepRequired, FormFieldName, LeafFieldPath, NgxDeepPartial, NgxDeepRequired, NgxFieldKey, NgxFirstInvalidOptions, NgxFormCompatibleDeepRequired, NgxFormState, NgxTypedVestSuite, NgxValidationConfig, NgxVestSuite, NgxWarningDisplayMode, ScErrorDisplayMode, ValidateFieldPath, ValidationConfigMap, ValidationOptions };
2659
+ /**
2660
+ * Signature of a deep-equality comparator. Must return `true` iff `a` and `b`
2661
+ * are considered equal for the purpose of change detection.
2662
+ */
2663
+ type NgxEqualityFn = (a: unknown, b: unknown) => boolean;
2664
+ /**
2665
+ * Injection token for the deep-equality function used internally by
2666
+ * {@link FormDirective} for change detection — `formValueChange`
2667
+ * `distinctUntilChanged`, the form↔model two-way sync effect, and the
2668
+ * `formState` signal's structural comparator.
2669
+ *
2670
+ * The default factory returns {@link fastDeepEqual}. Override this token to
2671
+ * plug in a smaller or differently-tuned comparator (e.g. `dequal/lite`,
2672
+ * `lodash.isEqual`) without forking the directive.
2673
+ *
2674
+ * @example Bring-your-own equality at the application level
2675
+ * ```ts
2676
+ * import { dequal } from 'dequal/lite';
2677
+ * import { NGX_EQUALITY_FN } from 'ngx-vest-forms';
2678
+ *
2679
+ * export const appConfig: ApplicationConfig = {
2680
+ * providers: [
2681
+ * { provide: NGX_EQUALITY_FN, useValue: dequal },
2682
+ * ],
2683
+ * };
2684
+ * ```
2685
+ *
2686
+ * @example Per-component override (e.g. for tests)
2687
+ * ```ts
2688
+ * @Component({
2689
+ * providers: [
2690
+ * { provide: NGX_EQUALITY_FN, useValue: (a, b) => a === b },
2691
+ * ],
2692
+ * })
2693
+ * export class TestFormComponent {}
2694
+ * ```
2695
+ *
2696
+ * @default {@link fastDeepEqual}
2697
+ */
2698
+ declare const NGX_EQUALITY_FN: InjectionToken<NgxEqualityFn>;
2699
+
2700
+ export { ControlWrapperComponent, DEFAULT_FOCUS_SELECTOR, DEFAULT_INVALID_SELECTOR, FormControlStateDirective, FormDirective, FormErrorControlDirective, FormErrorDisplayDirective, FormGroupWrapperComponent, FormModelDirective, FormModelGroupDirective, NGX_EQUALITY_FN, NGX_ERROR_DISPLAY_MODE_TOKEN, NGX_VALIDATION_CONFIG_DEBOUNCE_TOKEN, NGX_WARNING_DISPLAY_MODE_TOKEN, NgxVestForms, ROOT_FORM, ROOT_FORM as ROOT_FORM_CONSTANT, SC_ERROR_DISPLAY_MODE_TOKEN, ValidateRootFormDirective, ValidationConfigBuilder, arrayToObject, clearFields, clearFieldsWhen, cloneDeep, createDebouncedPendingState, createEmptyFormState, createValidationConfig, deepArrayToObject, fastDeepEqual, getAllFormErrors, getFormControlField, getFormGroupField, keepFieldsWhen, mergeAriaDescribedBy, mergeValuesAndRawValues, objectToArray, parseAriaIdTokens, parseFieldPath, resolveAssociationTargets, set, setValueAtPath, shallowEqual, stringifyFieldPath, validateShape, vestForms, vestFormsViewProviders };
2701
+ export type { AriaAssociationMode, DebouncedPendingStateOptions, DebouncedPendingStateOptionsInput, DebouncedPendingStateResult, DeepPartial, DeepRequired, FieldPath, FieldPathValue, FormCompatibleDeepRequired, FormFieldName, LeafFieldPath, NgxDeepPartial, NgxDeepRequired, NgxEqualityFn, NgxFieldBlurEvent, NgxFieldKey, NgxFirstInvalidOptions, NgxFormCompatibleDeepRequired, NgxFormState, NgxTypedVestSuite, NgxValidationConfig, NgxVestSuite, NgxWarningDisplayMode, ScErrorDisplayMode, ValidateFieldPath, ValidationConfigMap, ValidationOptions };