@quadrel-enterprise-ui/framework 20.21.0 → 20.21.1

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.
@@ -8468,6 +8468,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
8468
8468
  // @ts-strict-ignore
8469
8469
  class QdFormControl extends UntypedFormControl {
8470
8470
  maxLength;
8471
+ revalidationPartners = [];
8471
8472
  validatorList;
8472
8473
  constructor(formState, validatorOrOpts, asyncValidator) {
8473
8474
  super(formState, validatorOrOpts, asyncValidator);
@@ -8482,12 +8483,15 @@ class QdFormControl extends UntypedFormControl {
8482
8483
  }
8483
8484
  setPropsFromValidatorList() {
8484
8485
  let maxLength;
8486
+ const revalidationPartners = [];
8485
8487
  this.validatorList.forEach((validatorFn) => {
8486
- if (validatorFn.maxLength) {
8488
+ if (validatorFn.maxLength)
8487
8489
  maxLength = validatorFn.maxLength;
8488
- }
8490
+ if (validatorFn.qdRevalidates)
8491
+ revalidationPartners.push(validatorFn.qdRevalidates);
8489
8492
  });
8490
8493
  this.maxLength = maxLength;
8494
+ this.revalidationPartners = revalidationPartners;
8491
8495
  }
8492
8496
  setValidators(validators) {
8493
8497
  this.validatorList = this.getValidatorList(validators);
@@ -8520,6 +8524,9 @@ class QdFormControl extends UntypedFormControl {
8520
8524
  getMaxLengthOrUndefined() {
8521
8525
  return this.maxLength;
8522
8526
  }
8527
+ getRevalidationPartners() {
8528
+ return this.revalidationPartners;
8529
+ }
8523
8530
  }
8524
8531
  function isOptionsObj$1(validatorOrOpts) {
8525
8532
  return validatorOrOpts != null && !Array.isArray(validatorOrOpts) && typeof validatorOrOpts === 'object';
@@ -12387,6 +12394,8 @@ class QdDatepickerComponent {
12387
12394
  const current = this.config?.disabledDates;
12388
12395
  if (current === this._boundDisabledDates)
12389
12396
  return;
12397
+ if (!this.control)
12398
+ return;
12390
12399
  this._boundDisabledDates = current;
12391
12400
  this.validateDisabledDates();
12392
12401
  }
@@ -12394,6 +12403,8 @@ class QdDatepickerComponent {
12394
12403
  const current = this.config?.timePicker?.disabledTimes;
12395
12404
  if (current === this._boundDisabledTimes)
12396
12405
  return;
12406
+ if (!this.control)
12407
+ return;
12397
12408
  this._boundDisabledTimes = current;
12398
12409
  this.validateDisabledTimes();
12399
12410
  }
@@ -13524,14 +13535,40 @@ const validatorsDroppedWithOptsWarning = 'Quadrel Framework | QdForms - QdFormCo
13524
13535
  function hasUnit(control) {
13525
13536
  return control.value?.value != null && control.value?.unit != null;
13526
13537
  }
13538
+ /**
13539
+ * Ready-to-use validators for Quadrel Reactive Forms.
13540
+ *
13541
+ * Use them like Angular's built-in `Validators`, but every validator returns a **translatable
13542
+ * error key** instead of a fixed message, and most accept a custom `errorKey` as the last
13543
+ * argument.
13544
+ *
13545
+ * Empty values pass for most validators — combine with `required()` when a value is mandatory.
13546
+ * The amount-aware validators (`min`, `max`, `minLength`, `maxLength`, `pattern`) also accept
13547
+ * `{ value, unit }` objects and validate the inner `value`.
13548
+ *
13549
+ * #### **Usage**
13550
+ * ```ts
13551
+ * form = new QdFormGroup({
13552
+ * email: new QdFormControl(null, [QdValidators.required(), QdValidators.email()]),
13553
+ * age: new QdFormControl(null, [QdValidators.required(), QdValidators.min(18)])
13554
+ * });
13555
+ * ```
13556
+ */
13527
13557
  class QdValidators {
13528
13558
  /**
13529
- * Validates that the value of the form field is at least the specified minimum.
13559
+ * Validates that a numeric value is greater than or equal to a minimum.
13560
+ *
13561
+ * Empty values (`null`/`undefined`) pass — combine with `required` when a value is mandatory.
13562
+ * Also accepts amount objects of the shape `{ value, unit }`; in that case the inner `value`
13563
+ * is validated.
13564
+ *
13565
+ * @param min - The smallest allowed value (inclusive).
13566
+ * @param errorKey - Translation key returned when the value is below the minimum.
13530
13567
  *
13531
13568
  * #### **Usage**
13532
13569
  * ```ts
13533
- * new QdFormControl('', QdValidators.min(3));
13534
- * new QdFormControl('', QdValidators.min(3, 'custom-error'));
13570
+ * new QdFormControl(null, QdValidators.min(3));
13571
+ * new QdFormControl(null, QdValidators.min(3, 'custom-error'));
13535
13572
  * ```
13536
13573
  */
13537
13574
  static min(min, errorKey = 'i18n.qd.form.error.min') {
@@ -13543,12 +13580,19 @@ class QdValidators {
13543
13580
  return validFn;
13544
13581
  }
13545
13582
  /**
13546
- * Validates that the value of the form field does not exceed the specified maximum.
13583
+ * Validates that a numeric value is less than or equal to a maximum.
13584
+ *
13585
+ * Empty values (`null`/`undefined`) pass — combine with `required` when a value is mandatory.
13586
+ * Also accepts amount objects of the shape `{ value, unit }`; in that case the inner `value`
13587
+ * is validated.
13588
+ *
13589
+ * @param max - The largest allowed value (inclusive).
13590
+ * @param errorKey - Translation key returned when the value exceeds the maximum.
13547
13591
  *
13548
13592
  * #### **Usage**
13549
13593
  * ```ts
13550
- * new QdFormControl('', QdValidators.max(10));
13551
- * new QdFormControl('', QdValidators.max(10, 'custom-error'));
13594
+ * new QdFormControl(null, QdValidators.max(10));
13595
+ * new QdFormControl(null, QdValidators.max(10, 'custom-error'));
13552
13596
  * ```
13553
13597
  */
13554
13598
  static max(max, errorKey = 'i18n.qd.form.error.max') {
@@ -13560,7 +13604,16 @@ class QdValidators {
13560
13604
  return validFn;
13561
13605
  }
13562
13606
  /**
13563
- * Ensures that the form field is not empty.
13607
+ * Validates that the field holds a non-empty value.
13608
+ *
13609
+ * Quadrel form components additionally render a required asterisk (`*`) on the field label
13610
+ * whenever this validator is present, so users see at a glance that the field is mandatory.
13611
+ *
13612
+ * Empty means: `null`, `undefined`, an empty string, an empty array, or an object whose
13613
+ * leaf values are all empty (checked recursively — useful for amount objects such as
13614
+ * `{ value, unit }`).
13615
+ *
13616
+ * @param errorKey - Translation key returned when the field is empty.
13564
13617
  *
13565
13618
  * #### **Usage**
13566
13619
  * ```ts
@@ -13583,7 +13636,12 @@ class QdValidators {
13583
13636
  return value == null || (typeof value === 'string' && value.length === 0);
13584
13637
  }
13585
13638
  /**
13586
- * Validates that the form field contains a valid email address.
13639
+ * Validates that the field contains a single, well-formed email address.
13640
+ *
13641
+ * Empty values pass — combine with `required` when an address is mandatory. The whole value
13642
+ * must be the address; surrounding text makes it invalid.
13643
+ *
13644
+ * @param errorKey - Translation key returned when the address is malformed.
13587
13645
  *
13588
13646
  * #### **Usage**
13589
13647
  * ```ts
@@ -13598,7 +13656,14 @@ class QdValidators {
13598
13656
  return validFn;
13599
13657
  }
13600
13658
  /**
13601
- * Ensures that the form field contains at least the specified number of characters.
13659
+ * Validates that a text value is at least a given number of characters long.
13660
+ *
13661
+ * Empty values (`null`/`undefined`/`''`) pass — combine with `required` when input is
13662
+ * mandatory. Also accepts amount objects of the shape `{ value, unit }`; in that case the
13663
+ * inner `value` is measured.
13664
+ *
13665
+ * @param minLength - The minimum number of characters (inclusive).
13666
+ * @param errorKey - Translation key returned when the value is too short.
13602
13667
  *
13603
13668
  * #### **Usage**
13604
13669
  * ```ts
@@ -13615,7 +13680,15 @@ class QdValidators {
13615
13680
  return validFn;
13616
13681
  }
13617
13682
  /**
13618
- * Ensures that the form field contains no more than the specified number of characters.
13683
+ * Validates that a text value is no longer than a given number of characters.
13684
+ *
13685
+ * Empty values pass — combine with `required` when input is mandatory. Also accepts amount
13686
+ * objects of the shape `{ value, unit }`; in that case the inner `value` is measured. The
13687
+ * configured limit is exposed as `.maxLength` on the returned validator, which `qd-textarea`
13688
+ * reads to show a live character counter (`current / max`).
13689
+ *
13690
+ * @param maxLength - The maximum number of characters (inclusive).
13691
+ * @param errorKey - Translation key returned when the value is too long.
13619
13692
  *
13620
13693
  * #### **Usage**
13621
13694
  * ```ts
@@ -13633,7 +13706,13 @@ class QdValidators {
13633
13706
  return validFn;
13634
13707
  }
13635
13708
  /**
13636
- * Ensures that the form field matches a given regular expression pattern.
13709
+ * Validates that a text value matches a regular expression.
13710
+ *
13711
+ * Empty values pass — combine with `required` when input is mandatory. Also accepts amount
13712
+ * objects of the shape `{ value, unit }`; in that case the inner `value` is matched.
13713
+ *
13714
+ * @param pattern - The pattern to match, as a `RegExp` or a string regular expression.
13715
+ * @param errorKey - Translation key returned when the value does not match.
13637
13716
  *
13638
13717
  * #### **Usage**
13639
13718
  * ```ts
@@ -13651,11 +13730,17 @@ class QdValidators {
13651
13730
  return validFn;
13652
13731
  }
13653
13732
  /**
13654
- * Ensures that the form field contains a valid date.
13733
+ * Validates that the value is a real, parseable `Date` object.
13734
+ *
13735
+ * Empty values pass — combine with `required` when a date is mandatory. Strings (including
13736
+ * `'Invalid Date'`), numbers and plain objects are rejected. The error carries the locale
13737
+ * date format (`DD.MM.YYYY`) as a `params.format` hint for the message.
13738
+ *
13739
+ * @param errorKey - Translation key returned when the value is not a valid date.
13655
13740
  *
13656
13741
  * #### **Usage**
13657
13742
  * ```ts
13658
- * new QdFormControl('', QdValidators.date());
13743
+ * new QdFormControl(null, QdValidators.date());
13659
13744
  * new QdFormControl(new Date(), QdValidators.date());
13660
13745
  * new QdFormControl(new Date(), QdValidators.date('date-error'));
13661
13746
  * ```
@@ -13666,24 +13751,29 @@ class QdValidators {
13666
13751
  return null;
13667
13752
  const localizedFormat = moment().format('DD.MM.YYYY');
13668
13753
  const formattedErrorKey = { date: { key: errorKey, params: { format: localizedFormat } } };
13669
- if (value === 'Invalid Date')
13670
- return formattedErrorKey;
13671
- if (!(value instanceof Date) || isNaN(value.getTime()))
13672
- return formattedErrorKey;
13673
- return null;
13754
+ return this.isValidDate(value) ? null : formattedErrorKey;
13674
13755
  };
13675
13756
  }
13676
13757
  /**
13677
- * Ensures that the selected date is not within a range of disabled dates.
13758
+ * Validates that a date does not fall inside any of the given disabled ranges.
13759
+ *
13760
+ * Each range may set `from`, `to`, or both; an open end reaches into the far past/future.
13761
+ * Boundaries are **exclusive** — a date exactly on `from` or `to` is still allowed; only
13762
+ * dates strictly between them are rejected. Comparison is day-granular. Empty or invalid
13763
+ * values pass.
13764
+ *
13765
+ * @param disabledDates - Ranges to block, e.g. `[{ from, to }]`.
13766
+ * @param errorKey - Translation key returned when the date lies inside a range.
13678
13767
  *
13679
13768
  * #### **Usage**
13680
13769
  * ```ts
13681
- * new QdFormControl('', QdValidators.disabledDates([{ from: new Date('2025-01-01') }]));
13770
+ * new QdFormControl(null, QdValidators.disabledDates([{ from: new Date('2025-01-01') }]));
13771
+ * new QdFormControl(null, QdValidators.disabledDates([{ from: new Date('2025-01-01'), to: new Date('2025-01-31') }]));
13682
13772
  * ```
13683
13773
  */
13684
13774
  static disabledDates(disabledDates, errorKey = 'i18n.qd.form.error.disabledDates') {
13685
13775
  const validFn = (control) => {
13686
- if (!control.value || isNaN(control.value?.getTime?.()))
13776
+ if (!this.isValidDate(control.value))
13687
13777
  return null;
13688
13778
  const date = moment(control.value.getTime());
13689
13779
  return this.isDateDisabled(disabledDates, date) ? { disabledDates: errorKey } : null;
@@ -13702,16 +13792,74 @@ class QdValidators {
13702
13792
  });
13703
13793
  }
13704
13794
  /**
13705
- * Ensures that the selected time is not within a range of disabled times.
13795
+ * Validates that a date stays on the correct side of a sibling "range partner" control.
13796
+ *
13797
+ * Put it on both ends of a from/to pair, each pointing at the other. A `'start'` control is
13798
+ * invalid when its date is after the partner; an `'end'` control is invalid when its date is
13799
+ * before the partner. Comparison is day-granular, so equal days are valid.
13800
+ *
13801
+ * The partner value is read live on every run, so the limit is never cached — it stays
13802
+ * correct across form rebuilds, saves and config changes. Empty or invalid own/partner
13803
+ * values, and a missing partner control, all pass.
13804
+ *
13805
+ * Inside a `QdFormGroup` the partner is re-validated automatically when this control changes
13806
+ * (and vice versa), so a stale error never lingers — no manual `updateValueAndValidity` needed.
13807
+ * The validator declares this dependency via `qdRevalidates`, which the group reads.
13808
+ *
13809
+ * The auto-revalidation only applies when both controls are `QdFormControl`s inside a
13810
+ * `QdFormGroup`, and only for validators present at construction or set via the group's
13811
+ * `add`/`set`/`removeControl`. On a native `FormGroup`/`FormControl`, or when the validator is
13812
+ * added later via `control.addValidators(...)`, re-validate the partner manually with
13813
+ * `partner.updateValueAndValidity({ emitEvent: false })` whenever this control changes.
13814
+ *
13815
+ * @param partnerControlName - Name of the sibling control holding the other end of the range.
13816
+ * @param position - `'start'` for the from-control, `'end'` for the to-control.
13817
+ * @param errorKey - Translation key returned when the order is violated.
13818
+ *
13819
+ * #### **Usage**
13820
+ * ```ts
13821
+ * new QdFormGroup({
13822
+ * from: new QdFormControl(null, QdValidators.dateRange('to', 'start')),
13823
+ * to: new QdFormControl(null, QdValidators.dateRange('from', 'end'))
13824
+ * });
13825
+ * ```
13826
+ */
13827
+ static dateRange(partnerControlName, position, errorKey = 'i18n.qd.form.error.dateRange') {
13828
+ const validFn = (control) => {
13829
+ const ownValue = control.value;
13830
+ const partnerValue = control.parent?.get(partnerControlName)?.value;
13831
+ if (!this.isValidDate(ownValue) || !this.isValidDate(partnerValue))
13832
+ return null;
13833
+ const own = moment(ownValue);
13834
+ const partner = moment(partnerValue);
13835
+ const isInvalid = position === 'start' ? own.isAfter(partner, 'day') : own.isBefore(partner, 'day');
13836
+ return isInvalid ? { dateRange: errorKey } : null;
13837
+ };
13838
+ validFn.qdRevalidates = partnerControlName;
13839
+ return validFn;
13840
+ }
13841
+ static isValidDate(value) {
13842
+ return value instanceof Date && !isNaN(value.getTime());
13843
+ }
13844
+ /**
13845
+ * Validates that the time of a date value does not fall inside any disabled time range.
13846
+ *
13847
+ * Each range may set `from`, `to`, or both, as `HH:mm` strings. Boundaries are **exclusive** —
13848
+ * a time exactly on `from` or `to` is allowed; only times strictly between them are rejected.
13849
+ * Only the time-of-day is considered, not the date. Empty or invalid values pass.
13850
+ *
13851
+ * @param disabledTimes - Ranges to block, e.g. `[{ from: '12:00', to: '13:00' }]`.
13852
+ * @param errorKey - Translation key returned when the time lies inside a range.
13706
13853
  *
13707
13854
  * #### **Usage**
13708
13855
  * ```ts
13709
- * new QdFormControl('', QdValidators.disabledTimes([{ from: '15:00' }]));
13856
+ * new QdFormControl(null, QdValidators.disabledTimes([{ from: '15:00' }]));
13857
+ * new QdFormControl(null, QdValidators.disabledTimes([{ from: '12:00', to: '13:00' }]));
13710
13858
  * ```
13711
13859
  */
13712
13860
  static disabledTimes(disabledTimes, errorKey = 'i18n.qd.form.error.disabledTimes') {
13713
13861
  const validFn = (control) => {
13714
- if (!control.value || isNaN(control.value?.getTime?.()))
13862
+ if (!this.isValidDate(control.value))
13715
13863
  return null;
13716
13864
  const time = moment(control.value).format('HH:mm');
13717
13865
  return TimePickerService.isTimeDisabled(time, disabledTimes) ? { disabledTimes: errorKey } : null;
@@ -13719,7 +13867,14 @@ class QdValidators {
13719
13867
  return validFn;
13720
13868
  }
13721
13869
  /**
13722
- * Ensures that the selected file does not exceed the given maximum size in megabytes.
13870
+ * Validates that a selected file does not exceed a maximum size.
13871
+ *
13872
+ * Operates on a `File` value (e.g. from a file input). Non-file values — including `null` —
13873
+ * pass, so combine with `required` when a file is mandatory. The error carries `params.maxMb`
13874
+ * for the message.
13875
+ *
13876
+ * @param maxMb - The maximum allowed size in megabytes.
13877
+ * @param errorKey - Translation key returned when the file is too large.
13723
13878
  *
13724
13879
  * #### **Usage**
13725
13880
  * ```ts
@@ -13737,7 +13892,15 @@ class QdValidators {
13737
13892
  };
13738
13893
  }
13739
13894
  /**
13740
- * Validates that the file type or file extension matches one of the accepted types.
13895
+ * Validates that a selected file matches one of the accepted types.
13896
+ *
13897
+ * Each accepted entry is either a MIME type (`'application/pdf'`), a MIME wildcard
13898
+ * (`'image/*'`), or a file extension (`'.pdf'`); matching is case-insensitive. Non-file
13899
+ * values — including `null` — pass, so combine with `required` when a file is mandatory. The
13900
+ * error carries `params.accepted` for the message.
13901
+ *
13902
+ * @param accepted - Allowed MIME types, MIME wildcards or extensions.
13903
+ * @param errorKey - Translation key returned when the file type is not accepted.
13741
13904
  *
13742
13905
  * #### **Usage**
13743
13906
  * ```ts
@@ -15176,7 +15339,59 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
15176
15339
  args: ['class.qd-radio-buttons-error']
15177
15340
  }] } });
15178
15341
 
15342
+ /**
15343
+ * `UntypedFormGroup` that wires cross-field re-validation automatically.
15344
+ *
15345
+ * A validator can declare a sibling dependency via `qdRevalidates` (see `QdValidators.dateRange`).
15346
+ * The group then subscribes to that partner's `valueChanges` and re-validates the dependent control,
15347
+ * so a stale error never lingers when the partner value changes — without the consumer calling
15348
+ * `updateValueAndValidity`. The subscriptions are group-internal (sibling to sibling) and are torn
15349
+ * down and rebuilt whenever a control is added, replaced or removed.
15350
+ *
15351
+ * Re-wiring is triggered by control mutations (`add`/`set`/`removeControl`), not by validator
15352
+ * mutations on an existing control: calling `control.addValidators(...)` after construction updates
15353
+ * the control's partners but does not re-wire the group until the next control mutation.
15354
+ */
15179
15355
  class QdFormGroup extends UntypedFormGroup {
15356
+ revalidationSubscription = new Subscription();
15357
+ constructor(controls, validatorOrOpts, asyncValidator) {
15358
+ super(controls, validatorOrOpts, asyncValidator);
15359
+ this.wireCrossFieldRevalidation();
15360
+ }
15361
+ addControl(name, control, options) {
15362
+ super.addControl(name, control, options);
15363
+ this.rewireCrossFieldRevalidation();
15364
+ }
15365
+ setControl(name, control, options) {
15366
+ super.setControl(name, control, options);
15367
+ this.rewireCrossFieldRevalidation();
15368
+ }
15369
+ removeControl(name, options) {
15370
+ super.removeControl(name, options);
15371
+ this.rewireCrossFieldRevalidation();
15372
+ }
15373
+ rewireCrossFieldRevalidation() {
15374
+ this.revalidationSubscription.unsubscribe();
15375
+ this.revalidationSubscription = new Subscription();
15376
+ this.wireCrossFieldRevalidation();
15377
+ }
15378
+ wireCrossFieldRevalidation() {
15379
+ Object.keys(this.controls).forEach(name => {
15380
+ const control = this.controls[name];
15381
+ if (!(control instanceof QdFormControl))
15382
+ return;
15383
+ const partnerNames = control.getRevalidationPartners();
15384
+ if (!partnerNames.length)
15385
+ return;
15386
+ partnerNames.forEach(partnerName => {
15387
+ const partner = this.get(partnerName);
15388
+ if (!partner)
15389
+ return;
15390
+ this.revalidationSubscription.add(partner.valueChanges.subscribe(() => control.updateValueAndValidity({ emitEvent: false })));
15391
+ });
15392
+ control.updateValueAndValidity({ emitEvent: false });
15393
+ });
15394
+ }
15180
15395
  }
15181
15396
 
15182
15397
  class QdFormBuilder extends UntypedFormBuilder {