ngx-vest-forms 2.5.1 → 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.5.1",
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
  */
@@ -1251,6 +1291,48 @@ declare class FormDirective<T extends Record<string, unknown>> {
1251
1291
  * ```
1252
1292
  */
1253
1293
  markAllAsTouched(): void;
1294
+ /**
1295
+ * Clears the current submit cycle without resetting control values or metadata.
1296
+ *
1297
+ * Unlike {@link resetForm}, this only flips the submitted gate back to `false`.
1298
+ * Touched/dirty/pristine state is preserved so consumers can end `'on-submit'`
1299
+ * error visibility without a full form reset.
1300
+ *
1301
+ * **When to use:**
1302
+ * - You use submit-gated error visibility such as `'on-submit'`
1303
+ * - A submit attempt already happened
1304
+ * - The user resolved the current submit-time errors
1305
+ * - You want future untouched fields to wait for the next submit before showing errors
1306
+ *
1307
+ * **Why this exists:**
1308
+ * `resetForm()` would also clear touched/dirty/pristine metadata, which is often
1309
+ * too disruptive for long-form, multi-form, or mixed error-display flows.
1310
+ *
1311
+ * **What it does NOT do:**
1312
+ * - Does not change field values
1313
+ * - Does not mark controls pristine or untouched
1314
+ * - Does not re-run validation
1315
+ *
1316
+ * @example
1317
+ * ```typescript
1318
+ * submitAll(): void {
1319
+ * for (const form of this.submitForms()) {
1320
+ * form.ngForm.onSubmit(new Event('submit'));
1321
+ * }
1322
+ *
1323
+ * if (this.submitForms().every((form) => form.formState().valid)) {
1324
+ * for (const form of this.submitForms()) {
1325
+ * form.clearSubmittedState();
1326
+ * }
1327
+ * }
1328
+ * }
1329
+ * ```
1330
+ *
1331
+ * @see {@link resetForm} to fully reset values and control metadata
1332
+ * @see {@link markAllAsTouched} to manually show all errors
1333
+ * @see {@link triggerFormValidation} to re-run validation after structure changes
1334
+ */
1335
+ clearSubmittedState(): void;
1254
1336
  /**
1255
1337
  * Finds the first invalid element in this form, scrolls it into view, and focuses it.
1256
1338
  *
@@ -1272,7 +1354,7 @@ declare class FormDirective<T extends Record<string, unknown>> {
1272
1354
  * Host handler: called whenever any descendant field loses focus.
1273
1355
  * Used to make touched-path tracking react immediately on blur/tab.
1274
1356
  */
1275
- onFormFocusOut(): void;
1357
+ onFormFocusOut(event: FocusEvent): void;
1276
1358
  /**
1277
1359
  * Resets the form to a pristine, untouched state with optional new values.
1278
1360
  *
@@ -1338,7 +1420,7 @@ declare class FormDirective<T extends Record<string, unknown>> {
1338
1420
  */
1339
1421
  createAsyncValidator(field: string, validationOptions: ValidationOptions): AsyncValidatorFn;
1340
1422
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<FormDirective<any>, never>;
1341
- 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>;
1342
1424
  }
1343
1425
 
1344
1426
  /**
@@ -1431,12 +1513,16 @@ declare class ValidateRootFormDirective<T> implements AsyncValidator, AfterViewI
1431
1513
  readonly ngxValidateRootForm: _angular_core.InputSignalWithTransform<boolean, unknown>;
1432
1514
  /**
1433
1515
  * Validation mode:
1434
- * - 'submit' (default): Only validates after form submission
1435
- * - 'live': Validates on every value change
1436
- * 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.
1437
1523
  */
1438
- readonly validateRootFormMode: _angular_core.InputSignal<"submit" | "live">;
1439
- 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>;
1440
1526
  constructor();
1441
1527
  /**
1442
1528
  * Subscribe to form submit event using NgForm.ngSubmit EventEmitter
@@ -2260,15 +2346,6 @@ declare function getFormGroupField(rootForm: FormGroup, control: AbstractControl
2260
2346
  * @param form
2261
2347
  */
2262
2348
  declare function mergeValuesAndRawValues<T>(form: FormGroup): T;
2263
- /**
2264
- * Performs a deep-clone of an object
2265
- * @param obj
2266
- *
2267
- * @deprecated Use official ES {@link https://developer.mozilla.org/en-US/docs/Web/API/Window/structuredClone structuredClone} instead
2268
- *
2269
- * Browser Support: structuredClone is available in all modern browsers (Chrome 98+, Firefox 94+, Safari 15.4+, Edge 98+)
2270
- * and Node.js 17+. A polyfill is provided in test-setup.ts for Jest test environments.
2271
- */
2272
2349
  declare function cloneDeep<T>(object: T): T;
2273
2350
  /**
2274
2351
  * Sets a value in an object at the provided field path.
@@ -2318,6 +2395,12 @@ type DebouncedPendingStateOptions = {
2318
2395
  */
2319
2396
  minimumDisplay?: number;
2320
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>;
2321
2404
  /**
2322
2405
  * Result of createDebouncedPendingState containing the debounced signal
2323
2406
  * and cleanup function.
@@ -2367,7 +2450,7 @@ type DebouncedPendingStateResult = {
2367
2450
  * @param options - Configuration options for debouncing behavior
2368
2451
  * @returns Object containing the debounced showPendingMessage signal and cleanup function
2369
2452
  */
2370
- declare function createDebouncedPendingState(isPending: Signal<boolean>, options?: DebouncedPendingStateOptions): DebouncedPendingStateResult;
2453
+ declare function createDebouncedPendingState(isPending: Signal<boolean>, options?: DebouncedPendingStateOptionsInput): DebouncedPendingStateResult;
2371
2454
 
2372
2455
  /**
2373
2456
  * Validates a form value against a shape to catch typos in `name` or `ngModelGroup` attributes.
@@ -2458,11 +2541,13 @@ declare function shallowEqual(obj1: unknown, obj2: unknown): boolean;
2458
2541
  * - Plain objects (with recursive deep comparison)
2459
2542
  * - Date objects (by timestamp comparison)
2460
2543
  * - RegExp objects (by source and flags comparison)
2461
- * - Set objects (by size and value membership)
2462
- * - 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)
2463
2548
  *
2464
2549
  * **Safety Features:**
2465
- * - **Circular reference protection**: MaxDepth parameter prevents infinite recursion
2550
+ * - **Circular reference handling**: Tracks visited object pairs with `WeakMap<object, WeakSet<object>>`
2466
2551
  * - **Type coercion prevention**: Strict type checking before comparison
2467
2552
  * - **Null safety**: Proper handling of null and undefined values
2468
2553
  *
@@ -2474,7 +2559,8 @@ declare function shallowEqual(obj1: unknown, obj2: unknown): boolean;
2474
2559
  * ///
2475
2560
  * /// Memory usage:
2476
2561
  * /// JSON.stringify: Creates temporary strings (high GC pressure)
2477
- * /// 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.
2478
2564
  * ```
2479
2565
  *
2480
2566
  * **Typical Usage in Forms:**
@@ -2490,10 +2576,15 @@ declare function shallowEqual(obj1: unknown, obj2: unknown): boolean;
2490
2576
  *
2491
2577
  * @param obj1 - First object to compare
2492
2578
  * @param obj2 - Second object to compare
2493
- * @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
+ *
2494
2585
  * @returns true if objects are deeply equal by value
2495
2586
  */
2496
- declare function fastDeepEqual(obj1: unknown, obj2: unknown, maxDepth?: number): boolean;
2587
+ declare function fastDeepEqual(obj1: unknown, obj2: unknown): boolean;
2497
2588
 
2498
2589
  /**
2499
2590
  * @deprecated Use NGX_ERROR_DISPLAY_MODE_TOKEN instead
@@ -2565,5 +2656,46 @@ declare const NGX_WARNING_DISPLAY_MODE_TOKEN: InjectionToken<NgxWarningDisplayMo
2565
2656
  */
2566
2657
  declare const NGX_VALIDATION_CONFIG_DEBOUNCE_TOKEN: InjectionToken<number>;
2567
2658
 
2568
- 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 };
2569
- 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 };