@ascentgl/ads-ui 21.83.0 → 21.84.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.
@@ -5676,13 +5676,23 @@ class AdsDatetimepickerComponent extends AbstractInputComponent {
5676
5676
  /** @ignore */
5677
5677
  onChanged(event) {
5678
5678
  const value = event === null ? null : typeof event == 'string' ? event : event.value;
5679
+ const inputHasContent = !!this.input?.nativeElement.value;
5679
5680
  /**
5680
5681
  * sync valueControl errors with displayControl errors
5681
5682
  * NOTE: this is needed for "invalid format" errors that are calculated for displayControl
5682
5683
  * by MatDatetimepicker component
5683
5684
  */
5684
- this.valueControl.setErrors(this.displayControl.errors);
5685
+ if (inputHasContent && this.displayControl.errors) {
5686
+ // Input has content — propagate displayControl errors (including matDatepickerParse)
5687
+ this.valueControl.setErrors(this.displayControl.errors);
5688
+ }
5685
5689
  this.onDisplayedValueChange(value ? new Date(value) : null);
5690
+ // Re-validate valueControl so its own validators (e.g. required) run,
5691
+ // clearing any stale errors set via setErrors above.
5692
+ this.valueControl.updateValueAndValidity({ emitEvent: false });
5693
+ // Directly call syncState because statusChanges may not fire if
5694
+ // the status hasn't changed (e.g., was already INVALID).
5695
+ this.syncState(this.valueControl.status);
5686
5696
  }
5687
5697
  onViewChanged(view) {
5688
5698
  this.templateFormatter.run(view);
@@ -5776,8 +5786,24 @@ class AdsDatetimepickerComponent extends AbstractInputComponent {
5776
5786
  }
5777
5787
  syncState(status) {
5778
5788
  if (status === 'INVALID') {
5779
- this.displayControl.setErrors(this.valueControl.errors);
5780
- this.touchControls();
5789
+ const errors = { ...this.valueControl.errors };
5790
+ const inputHasContent = !!this.input?.nativeElement.value;
5791
+ // When the input has visible content but the control value is null,
5792
+ // the user typed a partial/invalid date. Show format error instead
5793
+ // of "required", since the field is not actually empty from the user's perspective.
5794
+ if (inputHasContent && !this.displayControl.value) {
5795
+ // Prefer existing parse error from displayControl, otherwise create one.
5796
+ const existingParseError = this.displayControl.errors?.matDatepickerParse;
5797
+ errors['matDatepickerParse'] = existingParseError ?? { text: this.input?.nativeElement.value };
5798
+ }
5799
+ // When the input is empty, make sure stale parse errors are not carried over
5800
+ // from valueControl (which may have received them via setErrors in onChanged).
5801
+ if (!inputHasContent) {
5802
+ delete errors['matDatepickerParse'];
5803
+ }
5804
+ // Angular treats an empty object as "has errors" (truthy), so normalize to null.
5805
+ const finalErrors = Object.keys(errors).length > 0 ? errors : null;
5806
+ this.displayControl.setErrors(finalErrors);
5781
5807
  }
5782
5808
  else {
5783
5809
  // Clear errors correctly (Angular expects null, not [])
@@ -5830,6 +5856,12 @@ class AdsDatetimepickerComponent extends AbstractInputComponent {
5830
5856
  syncEmptyValues() {
5831
5857
  if (this.valueControl.value)
5832
5858
  return false;
5859
+ // When the input has user-typed content that failed parsing (e.g., partial date '03/10/202'),
5860
+ // displayControl is already null (set by mat-datetimepicker's _cvaOnChange(null)).
5861
+ // Don't clear the display — the user's text should stay so the format error is visible.
5862
+ // But when displayControl still holds a value (e.g., external form reset), allow clearing.
5863
+ if (this.input?.nativeElement.value && !this.displayControl.value)
5864
+ return true;
5833
5865
  this.setControlValue(this.displayControl, this.valueControl.value, false);
5834
5866
  return true;
5835
5867
  }
@@ -6018,6 +6050,8 @@ class AdsDatepickerComponent extends AdsDatetimepickerComponent {
6018
6050
  };
6019
6051
  /** @ignore - Previous raw input value for tracking changes */
6020
6052
  this.previousRawValue = '';
6053
+ /** @ignore - Tracks whether the input was modified by the custom keydown handler */
6054
+ this.inputModifiedByKeyHandler = false;
6021
6055
  /** @ignore */
6022
6056
  this.elementRef = inject(ElementRef);
6023
6057
  }
@@ -6048,6 +6082,18 @@ class AdsDatepickerComponent extends AdsDatetimepickerComponent {
6048
6082
  });
6049
6083
  this.intersectionObserver.observe(this.elementRef.nativeElement);
6050
6084
  }
6085
+ /** @ignore - Override to dispatch change event on blur when input was modified programmatically */
6086
+ touchControls() {
6087
+ // Mark controls as touched BEFORE dispatching the change event.
6088
+ // onChanged → syncState → super.syncState() checks valueControl.untouched
6089
+ // and clears displayControl if it's touched+invalid — so valueControl must
6090
+ // be touched first to prevent the user's partial input from being wiped.
6091
+ super.touchControls();
6092
+ if (this.inputModifiedByKeyHandler && this.input) {
6093
+ this.inputModifiedByKeyHandler = false;
6094
+ this.input.nativeElement.dispatchEvent(new Event('change', { bubbles: true }));
6095
+ }
6096
+ }
6051
6097
  /** @ignore */
6052
6098
  applyCustomFormat() {
6053
6099
  this.customDisplayFormatOverride = {};
@@ -6180,6 +6226,8 @@ class AdsDatepickerComponent extends AdsDatetimepickerComponent {
6180
6226
  newCursorPos++;
6181
6227
  }
6182
6228
  input.setSelectionRange(newCursorPos, newCursorPos);
6229
+ // Mark that the input was modified programmatically
6230
+ this.inputModifiedByKeyHandler = true;
6183
6231
  // Trigger change detection
6184
6232
  input.dispatchEvent(new Event('input', { bubbles: true }));
6185
6233
  }
@@ -6198,6 +6246,8 @@ class AdsDatepickerComponent extends AdsDatetimepickerComponent {
6198
6246
  // Adjust cursor position
6199
6247
  const newCursorPos = Math.min(cursorPos, formatted.length);
6200
6248
  input.setSelectionRange(newCursorPos, newCursorPos);
6249
+ // Mark that the input was modified programmatically
6250
+ this.inputModifiedByKeyHandler = true;
6201
6251
  input.dispatchEvent(new Event('input', { bubbles: true }));
6202
6252
  }
6203
6253
  /** @ignore - Format digits with slashes (MM/DD/YYYY) */