@arsedizioni/ars-utils 22.0.46 → 22.0.48

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.
@@ -2168,6 +2168,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
2168
2168
  * The host control is re-validated whenever the OTHER control's value changes:
2169
2169
  * without this, typing a new password AFTER the confirmation field was filled
2170
2170
  * left the form incorrectly valid (the classic password/confirm bug).
2171
+ *
2172
+ * IMPORTANT (reciprocal-binding safety): the re-validation triggered from the
2173
+ * other control's `valueChanges` runs with `{ emitEvent: false }`. Calling
2174
+ * Angular's `onValidatorChange` instead would re-emit valueChanges, so a
2175
+ * reciprocal setup (A [equals]=B and B [equals]=A) would overflow the stack.
2171
2176
  */
2172
2177
  class EqualsValidatorDirective {
2173
2178
  constructor() {
@@ -2175,21 +2180,18 @@ class EqualsValidatorDirective {
2175
2180
  this.equals = input(undefined, /* @ts-ignore */
2176
2181
  ...(ngDevMode ? [{ debugName: "equals" }] : /* istanbul ignore next */ []));
2177
2182
  // Re-subscribe whenever the [equals] binding points to a different control,
2178
- // and trigger host re-validation on every change of the other control.
2183
+ // and re-validate the host on every change of the other control.
2179
2184
  effect(() => {
2180
2185
  const other = this.equals();
2181
2186
  this.subscription?.unsubscribe();
2182
- this.subscription = other?.valueChanges.subscribe(() => this.onValidatorChange?.());
2187
+ this.subscription = other?.valueChanges.subscribe(() =>
2188
+ // emitEvent:false breaks the reciprocal valueChanges loop (see class doc).
2189
+ this.hostControl?.updateValueAndValidity({ emitEvent: false }));
2190
+ // Re-validate the host when the bound control itself is swapped.
2191
+ this.hostControl?.updateValueAndValidity({ emitEvent: false });
2183
2192
  });
2184
2193
  inject(DestroyRef).onDestroy(() => this.subscription?.unsubscribe());
2185
2194
  }
2186
- /**
2187
- * Registers the callback Angular invokes to re-run this validator.
2188
- * @param fn - The revalidation callback provided by the forms API.
2189
- */
2190
- registerOnValidatorChange(fn) {
2191
- this.onValidatorChange = fn;
2192
- }
2193
2195
  /**
2194
2196
  * Validates that the host control value equals the bound control's value.
2195
2197
  * Returns `null` (valid) when no control is bound.
@@ -2197,6 +2199,7 @@ class EqualsValidatorDirective {
2197
2199
  * @returns `null` when valid, `{ equals: ... }` otherwise.
2198
2200
  */
2199
2201
  validate(control) {
2202
+ this.hostControl = control;
2200
2203
  const eq = this.equals();
2201
2204
  if (!eq)
2202
2205
  return null;
@@ -2411,6 +2414,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
2411
2414
  *
2412
2415
  * The host control is re-validated whenever the OTHER control's value changes,
2413
2416
  * so editing either field keeps both error states consistent.
2417
+ *
2418
+ * IMPORTANT (reciprocal-binding safety): the re-validation triggered from the
2419
+ * other control's `valueChanges` is run with `{ emitEvent: false }`. Using
2420
+ * Angular's `onValidatorChange` here instead would call `host.updateValueAndValidity()`
2421
+ * WITH events, so a reciprocal setup (A [notEqual]=B and B [notEqual]=A) would
2422
+ * ping-pong valueChanges between the two controls and overflow the stack.
2414
2423
  */
2415
2424
  class NotEqualValidatorDirective {
2416
2425
  constructor() {
@@ -2420,17 +2429,15 @@ class NotEqualValidatorDirective {
2420
2429
  effect(() => {
2421
2430
  const other = this.notEqual();
2422
2431
  this.subscription?.unsubscribe();
2423
- this.subscription = other?.valueChanges.subscribe(() => this.onValidatorChange?.());
2432
+ this.subscription = other?.valueChanges.subscribe(() =>
2433
+ // Re-validate the host WITHOUT emitting valueChanges, otherwise a
2434
+ // reciprocal binding would loop forever (see class doc).
2435
+ this.hostControl?.updateValueAndValidity({ emitEvent: false }));
2436
+ // Re-validate the host when the bound control itself is swapped.
2437
+ this.hostControl?.updateValueAndValidity({ emitEvent: false });
2424
2438
  });
2425
2439
  inject(DestroyRef).onDestroy(() => this.subscription?.unsubscribe());
2426
2440
  }
2427
- /**
2428
- * Registers the callback Angular invokes to re-run this validator.
2429
- * @param fn - The revalidation callback provided by the forms API.
2430
- */
2431
- registerOnValidatorChange(fn) {
2432
- this.onValidatorChange = fn;
2433
- }
2434
2441
  /**
2435
2442
  * Validates that the host control value is not equal to the bound control's value.
2436
2443
  * Also clears the `notequal` error on the other control when the host becomes valid.
@@ -2439,6 +2446,7 @@ class NotEqualValidatorDirective {
2439
2446
  * @returns `null` when valid, `{ notequal: true }` otherwise.
2440
2447
  */
2441
2448
  validate(control) {
2449
+ this.hostControl = control;
2442
2450
  const notEqual = this.notEqual();
2443
2451
  if (!notEqual)
2444
2452
  return null;
@@ -2449,7 +2457,9 @@ class NotEqualValidatorDirective {
2449
2457
  }
2450
2458
  else if (notEqual.hasError('notequal')) {
2451
2459
  const { notequal: _removed, ...rest } = notEqual.errors ?? {};
2452
- notEqual.setErrors(Object.keys(rest).length > 0 ? rest : null);
2460
+ // emitEvent:false on both: clearing the partner's error must not feed
2461
+ // back through valueChanges/statusChanges into this validator.
2462
+ notEqual.setErrors(Object.keys(rest).length > 0 ? rest : null, { emitEvent: false });
2453
2463
  notEqual.updateValueAndValidity({ onlySelf: true, emitEvent: false });
2454
2464
  notEqual.markAsTouched();
2455
2465
  notEqual.markAsDirty();