@egose/shadcn-theme-ng 0.6.0 → 0.6.2

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.
Files changed (50) hide show
  1. package/README.md +119 -1
  2. package/autocomplete/fesm2022/autocomplete.mjs +14 -4
  3. package/autocomplete/types/autocomplete.d.ts +4 -1
  4. package/checkbox/README.md +6 -2
  5. package/checkbox/fesm2022/checkbox.mjs +11 -7
  6. package/checkbox/types/checkbox.d.ts +6 -4
  7. package/combobox/fesm2022/combobox.mjs +13 -7
  8. package/combobox/types/combobox.d.ts +8 -2
  9. package/data-table/README.md +11 -1
  10. package/data-table/fesm2022/data-table.mjs +64 -18
  11. package/data-table/types/data-table.d.ts +25 -6
  12. package/date-picker/README.md +41 -9
  13. package/date-picker/fesm2022/date-picker.mjs +265 -59
  14. package/date-picker/types/date-picker.d.ts +58 -27
  15. package/form-autocomplete/README.md +4 -2
  16. package/form-autocomplete/fesm2022/form-autocomplete.mjs +19 -6
  17. package/form-autocomplete/types/form-autocomplete.d.ts +9 -3
  18. package/form-combobox/README.md +4 -2
  19. package/form-combobox/fesm2022/form-combobox.mjs +23 -6
  20. package/form-combobox/types/form-combobox.d.ts +10 -3
  21. package/form-input-otp/README.md +7 -5
  22. package/form-input-otp/fesm2022/form-input-otp.mjs +20 -8
  23. package/form-input-otp/types/form-input-otp.d.ts +9 -3
  24. package/form-searchable-multiselect/README.md +15 -1
  25. package/form-searchable-multiselect/fesm2022/form-searchable-multiselect.mjs +15 -3
  26. package/form-searchable-multiselect/types/form-searchable-multiselect.d.ts +9 -1
  27. package/form-slider/README.md +3 -1
  28. package/form-slider/fesm2022/form-slider.mjs +19 -6
  29. package/form-slider/types/form-slider.d.ts +9 -3
  30. package/form-toggle/README.md +22 -18
  31. package/form-toggle/fesm2022/form-toggle.mjs +22 -9
  32. package/form-toggle/types/form-toggle.d.ts +6 -2
  33. package/input-otp/README.md +33 -9
  34. package/input-otp/fesm2022/input-otp.mjs +127 -11
  35. package/input-otp/types/input-otp.d.ts +24 -4
  36. package/package.json +1 -1
  37. package/pagination/README.md +21 -12
  38. package/pagination/fesm2022/pagination.mjs +73 -65
  39. package/pagination/types/pagination.d.ts +21 -8
  40. package/phone-input/README.md +16 -0
  41. package/phone-input/fesm2022/phone-input.mjs +41 -3
  42. package/phone-input/types/phone-input.d.ts +5 -2
  43. package/searchable-multiselect/README.md +62 -40
  44. package/searchable-multiselect/fesm2022/searchable-multiselect.mjs +59 -32
  45. package/searchable-multiselect/types/searchable-multiselect.d.ts +23 -6
  46. package/slider/fesm2022/slider.mjs +9 -5
  47. package/slider/types/slider.d.ts +6 -2
  48. package/stepper/README.md +39 -19
  49. package/stepper/fesm2022/stepper.mjs +64 -12
  50. package/stepper/types/stepper.d.ts +12 -1
@@ -1,5 +1,5 @@
1
1
  import * as i0 from '@angular/core';
2
- import { InjectionToken, inject, input, booleanAttribute, computed, ChangeDetectionStrategy, Component, forwardRef, viewChild, contentChild, linkedSignal, signal, output, ElementRef, effect, Directive, numberAttribute, untracked, NgModule } from '@angular/core';
2
+ import { InjectionToken, inject, signal, Injectable, linkedSignal, Directive, input, booleanAttribute, computed, ChangeDetectionStrategy, Component, forwardRef, viewChild, contentChild, output, ElementRef, effect, numberAttribute, untracked, NgModule } from '@angular/core';
3
3
  import { NgIcon, provideIcons } from '@ng-icons/core';
4
4
  import { lucideX, lucideCalendar, lucideChevronDown } from '@ng-icons/lucide';
5
5
  import { BrnDateInput, provideBrnDatePickerTrigger, BrnDatePickerTriggerToken, provideBrnDatePicker, injectBrnDatePicker } from '@spartan-ng/brain/date-picker';
@@ -10,6 +10,8 @@ import { NG_VALUE_ACCESSOR } from '@angular/forms';
10
10
  import * as i1$1 from '@spartan-ng/brain/field';
11
11
  import { provideBrnLabelable, BrnFieldControl, BrnFieldControlDescribedBy } from '@spartan-ng/brain/field';
12
12
  import { BrnPopover } from '@spartan-ng/brain/popover';
13
+ import { injectDateAdapter } from '@spartan-ng/brain/date-time';
14
+ import { BrnCalendar, BrnCalendarMulti, BrnCalendarRange } from '@spartan-ng/brain/calendar';
13
15
  import * as i3 from '@egose/shadcn-theme-ng/calendar';
14
16
  import { HlmCalendar, HlmCalendarMulti, HlmCalendarRange, HlmCalendarImports } from '@egose/shadcn-theme-ng/calendar';
15
17
  import * as i2 from '@egose/shadcn-theme-ng/popover';
@@ -68,7 +70,100 @@ function injectHlmDatePickerMultiConfig() {
68
70
  return injectedConfig ? injectedConfig : getDefaultConfig$3();
69
71
  }
70
72
 
71
- class HlmDateMultiInput extends BrnDateInput {
73
+ /** Internal synchronization for explicit writes, including writes of the same value. */
74
+ class HlmDatePickerCommitState {
75
+ revision = signal(0, /* @ts-ignore */
76
+ ...(ngDevMode ? [{ debugName: "revision" }] : /* istanbul ignore next */ []));
77
+ changed() {
78
+ this.revision.update((revision) => revision + 1);
79
+ }
80
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: HlmDatePickerCommitState, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
81
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: HlmDatePickerCommitState });
82
+ }
83
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: HlmDatePickerCommitState, decorators: [{
84
+ type: Injectable
85
+ }] });
86
+ /** Match the calendar's inclusive whole-day bounds with the configured adapter. */
87
+ function isSelectableDate(adapter, value, min, max) {
88
+ return (value != null &&
89
+ Number.isFinite(adapter.getTime(value)) &&
90
+ (min == null || !adapter.isBefore(value, adapter.startOfDay(min))) &&
91
+ (max == null || !adapter.isAfter(value, adapter.endOfDay(max))));
92
+ }
93
+
94
+ /** Internal input lifecycle shared by the single, range and multi pickers. */
95
+ class HlmConstrainedDateInput extends BrnDateInput {
96
+ _commitState = inject(HlmDatePickerCommitState);
97
+ _inputValue = linkedSignal({ ...(ngDevMode ? { debugName: "_inputValue" } : /* istanbul ignore next */ {}), source: () => ({
98
+ revision: this._commitState.revision(),
99
+ formatted: this._datePicker.formattedDate(),
100
+ value: this._datePicker.value?.(),
101
+ inputValue: this.inputValue(),
102
+ }),
103
+ computation: (source, previous) => {
104
+ if (!previous)
105
+ return source.formatted ?? source.inputValue;
106
+ if (source.revision !== previous.source.revision ||
107
+ source.value !== previous.source.value ||
108
+ source.formatted !== previous.source.formatted)
109
+ return source.formatted ?? '';
110
+ if (source.inputValue !== previous.source.inputValue)
111
+ return source.inputValue;
112
+ return previous.value;
113
+ } });
114
+ /** Rejected text stays invalid until a successful commit, clear, or external value write. */
115
+ inputInvalid = linkedSignal(() => {
116
+ this._commitState.revision();
117
+ this._datePicker.value?.();
118
+ this.inputValue();
119
+ return false;
120
+ }, /* @ts-ignore */
121
+ ...(ngDevMode ? [{ debugName: "inputInvalid" }] : /* istanbul ignore next */ []));
122
+ _commitDate() {
123
+ if (this._disabled() || this.readonly())
124
+ return;
125
+ const text = this._inputValue();
126
+ const parsed = text ? this.parseValue(text) : null;
127
+ const picker = this._datePicker;
128
+ const accepted = (!text || parsed != null) && picker.updateDate(parsed);
129
+ this.inputInvalid.set(!accepted);
130
+ this._datePicker.touched?.();
131
+ }
132
+ _handleBlur() {
133
+ if (this._disabled() || this.readonly())
134
+ return;
135
+ this._commitDate();
136
+ if (!this.inputInvalid())
137
+ this._inputValue.set(this._datePicker.formattedDate() ?? '');
138
+ }
139
+ _handleEnter(event) {
140
+ event.preventDefault();
141
+ if (this._disabled() || this.readonly())
142
+ return;
143
+ this._commitDate();
144
+ if (!this.inputInvalid()) {
145
+ this._popover().close();
146
+ this._handleFocus();
147
+ }
148
+ }
149
+ _handleFocus() {
150
+ if (!this.inputInvalid())
151
+ super._handleFocus();
152
+ }
153
+ _clear() {
154
+ if (this._disabled() || this.readonly())
155
+ return;
156
+ this._inputValue.set('');
157
+ this._commitDate();
158
+ }
159
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: HlmConstrainedDateInput, deps: null, target: i0.ɵɵFactoryTarget.Directive });
160
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.1.3", type: HlmConstrainedDateInput, isStandalone: true, usesInheritance: true, ngImport: i0 });
161
+ }
162
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: HlmConstrainedDateInput, decorators: [{
163
+ type: Directive
164
+ }] });
165
+
166
+ class HlmDateMultiInput extends HlmConstrainedDateInput {
72
167
  _config = injectHlmDatePickerMultiConfig();
73
168
  /** Native `name` attribute forwarded to the inner `<input>`. */
74
169
  name = input(undefined, /* @ts-ignore */
@@ -91,9 +186,9 @@ class HlmDateMultiInput extends BrnDateInput {
91
186
  classes(() => [this.userClass()]);
92
187
  }
93
188
  /**
94
- * Parses input text into dates. Return `null` for invalid input - the
95
- * picker's dates are cleared while the text is preserved so the user can
96
- * fix it.
189
+ * Parses input text into dates. Return `null` for invalid input. Rejected
190
+ * text stays editable and aria-invalid; the committed value is preserved.
191
+ * Empty text explicitly clears on blur/Enter.
97
192
  *
98
193
  * Defaults to `parseDate` from `HlmDatePickerMultiConfig`.
99
194
  */
@@ -126,7 +221,8 @@ class HlmDateMultiInput extends BrnDateInput {
126
221
  [placeholder]="placeholder()"
127
222
  [readonly]="readonly()"
128
223
  [disabled]="_disabled()"
129
- [forceInvalid]="forceInvalid()"
224
+ [attr.aria-invalid]="forceInvalid() || inputInvalid() ? 'true' : null"
225
+ [attr.data-matches-spartan-invalid]="forceInvalid() || inputInvalid() ? 'true' : null"
130
226
  [class]="$inputClass()"
131
227
  (click)="_handleClick()"
132
228
  (keydown.arrowDown)="_open()"
@@ -180,7 +276,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImpor
180
276
  [placeholder]="placeholder()"
181
277
  [readonly]="readonly()"
182
278
  [disabled]="_disabled()"
183
- [forceInvalid]="forceInvalid()"
279
+ [attr.aria-invalid]="forceInvalid() || inputInvalid() ? 'true' : null"
280
+ [attr.data-matches-spartan-invalid]="forceInvalid() || inputInvalid() ? 'true' : null"
184
281
  [class]="$inputClass()"
185
282
  (click)="_handleClick()"
186
283
  (keydown.arrowDown)="_open()"
@@ -244,6 +341,11 @@ const HLM_DATE_PICKER_VALUE_ACCESSOR = {
244
341
  };
245
342
  class HlmDatePicker {
246
343
  _config = injectHlmDatePickerConfig();
344
+ _dateAdapter = injectDateAdapter();
345
+ _commitState = inject(HlmDatePickerCommitState);
346
+ _calendar = viewChild((BrnCalendar), /* @ts-ignore */
347
+ ...(ngDevMode ? [{ debugName: "_calendar" }] : /* istanbul ignore next */ []));
348
+ _restoringCalendar = false;
247
349
  popover = viewChild.required(BrnPopover, /* @ts-ignore */
248
350
  ...(ngDevMode ? [{ debugName: "popover" }] : /* istanbul ignore next */ []));
249
351
  _trigger = contentChild(BrnDatePickerTriggerToken, /* @ts-ignore */
@@ -305,30 +407,46 @@ class HlmDatePicker {
305
407
  this._onTouched?.();
306
408
  }
307
409
  _handleChange(value) {
308
- if (this.disabledState())
410
+ if (this._restoringCalendar)
309
411
  return;
310
- this.updateDate(value ?? null);
412
+ if (!this.updateDate(value ?? null)) {
413
+ // Brain model signals have already changed before their output fires.
414
+ this._restoringCalendar = true;
415
+ try {
416
+ this._calendar()?.date.set(this._mutableDate());
417
+ }
418
+ finally {
419
+ this._restoringCalendar = false;
420
+ }
421
+ return;
422
+ }
311
423
  if (this.autoCloseOnSelect()) {
312
424
  this._popoverState.set('closed');
313
425
  }
314
426
  }
315
427
  /**
316
- * Commit a date to the picker. Updates the internal model, notifies form
317
- * controls, and emits `dateChange`. Unlike `_handleChange`, this does not
318
- * close the popover - it's intended to be called from a text input that
319
- * is parsing user-entered values while typing.
428
+ * Commit a user value (null clears). Returns false without changing/emitting
429
+ * the model when disabled or when the raw/transformed date is invalid or
430
+ * outside the adapter's inclusive whole-day min/max bounds.
320
431
  */
321
432
  updateDate(value) {
322
433
  if (this.disabledState())
323
- return;
434
+ return false;
435
+ if (value != null && !isSelectableDate(this._dateAdapter, value, this.min(), this.max()))
436
+ return false;
324
437
  const transformedDate = value != null ? this.transformDate()(value) : undefined;
438
+ if (value != null && !isSelectableDate(this._dateAdapter, transformedDate, this.min(), this.max()))
439
+ return false;
325
440
  this._mutableDate.set(transformedDate);
441
+ this._commitState.changed();
326
442
  this._onChange?.(transformedDate ?? null);
327
443
  this.dateChange.emit(transformedDate ?? null);
444
+ return true;
328
445
  }
329
- /** CONTROL VALUE ACCESSOR */
446
+ /** Programmatic CVA write: transforms without enforcing user constraints or emitting. Clears rejected text. */
330
447
  writeValue(value) {
331
- this._mutableDate.set(value ? this.transformDate()(value) : undefined);
448
+ this._mutableDate.set(value != null ? this.transformDate()(value) : undefined);
449
+ this._commitState.changed();
332
450
  }
333
451
  registerOnChange(fn) {
334
452
  this._onChange = fn;
@@ -350,24 +468,35 @@ class HlmDatePicker {
350
468
  }
351
469
  reset() {
352
470
  this._mutableDate.set(undefined);
471
+ this._commitState.changed();
353
472
  this._onChange?.(null);
354
473
  this.dateChange.emit(null);
355
474
  }
356
475
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: HlmDatePicker, deps: [], target: i0.ɵɵFactoryTarget.Component });
357
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "22.1.3", type: HlmDatePicker, isStandalone: true, selector: "hlm-date-picker", inputs: { captionLayout: { classPropertyName: "captionLayout", publicName: "captionLayout", isSignal: true, isRequired: false, transformFunction: null }, min: { classPropertyName: "min", publicName: "min", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, wrapperDisabled: { classPropertyName: "wrapperDisabled", publicName: "wrapperDisabled", isSignal: true, isRequired: false, transformFunction: null }, date: { classPropertyName: "date", publicName: "date", isSignal: true, isRequired: false, transformFunction: null }, defaultFocusedDate: { classPropertyName: "defaultFocusedDate", publicName: "defaultFocusedDate", isSignal: true, isRequired: false, transformFunction: null }, autoCloseOnSelect: { classPropertyName: "autoCloseOnSelect", publicName: "autoCloseOnSelect", isSignal: true, isRequired: false, transformFunction: null }, formatDate: { classPropertyName: "formatDate", publicName: "formatDate", isSignal: true, isRequired: false, transformFunction: null }, transformDate: { classPropertyName: "transformDate", publicName: "transformDate", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { dateChange: "dateChange" }, host: { classAttribute: "block w-full min-w-0 max-w-full" }, providers: [HLM_DATE_PICKER_VALUE_ACCESSOR, provideBrnDatePicker(HlmDatePicker), provideBrnLabelable(HlmDatePicker)], queries: [{ propertyName: "_trigger", first: true, predicate: BrnDatePickerTriggerToken, descendants: true, isSignal: true }], viewQueries: [{ propertyName: "popover", first: true, predicate: BrnPopover, descendants: true, isSignal: true }], hostDirectives: [{ directive: i1$1.BrnFieldControl }], ngImport: i0, template: "\n <hlm-popover sideOffset=\"5\" [state]=\"_popoverState()\" (stateChanged)=\"_onStateChange($event)\">\n <ng-content />\n\n <hlm-popover-content class=\"w-fit p-0\" *hlmPopoverPortal=\"let ctx\">\n <ng-content select=\"[hlmDatePickerHeader]\" />\n <hlm-calendar\n class=\"rounded-none border-0\"\n [captionLayout]=\"captionLayout()\"\n [date]=\"_mutableDate()\"\n [defaultFocusedDate]=\"_mutableDate() ?? defaultFocusedDate()\"\n [min]=\"min()\"\n [max]=\"max()\"\n [disabled]=\"disabledState()\"\n (dateChange)=\"_handleChange($event)\"\n />\n <ng-content select=\"[hlmDatePickerFooter]\" />\n </hlm-popover-content>\n </hlm-popover>\n ", isInline: true, dependencies: [{ kind: "directive", type: i2.HlmPopover, selector: "[hlmPopover],hlm-popover" }, { kind: "directive", type: i2.HlmPopoverContent, selector: "[hlmPopoverContent],hlm-popover-content" }, { kind: "directive", type: i2.HlmPopoverPortal, selector: "[hlmPopoverPortal]" }, { kind: "component", type: HlmCalendar, selector: "hlm-calendar", inputs: ["captionLayout"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
476
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "22.1.3", type: HlmDatePicker, isStandalone: true, selector: "hlm-date-picker", inputs: { captionLayout: { classPropertyName: "captionLayout", publicName: "captionLayout", isSignal: true, isRequired: false, transformFunction: null }, min: { classPropertyName: "min", publicName: "min", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, wrapperDisabled: { classPropertyName: "wrapperDisabled", publicName: "wrapperDisabled", isSignal: true, isRequired: false, transformFunction: null }, date: { classPropertyName: "date", publicName: "date", isSignal: true, isRequired: false, transformFunction: null }, defaultFocusedDate: { classPropertyName: "defaultFocusedDate", publicName: "defaultFocusedDate", isSignal: true, isRequired: false, transformFunction: null }, autoCloseOnSelect: { classPropertyName: "autoCloseOnSelect", publicName: "autoCloseOnSelect", isSignal: true, isRequired: false, transformFunction: null }, formatDate: { classPropertyName: "formatDate", publicName: "formatDate", isSignal: true, isRequired: false, transformFunction: null }, transformDate: { classPropertyName: "transformDate", publicName: "transformDate", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { dateChange: "dateChange" }, host: { classAttribute: "block w-full min-w-0 max-w-full" }, providers: [
477
+ HlmDatePickerCommitState,
478
+ HLM_DATE_PICKER_VALUE_ACCESSOR,
479
+ provideBrnDatePicker(HlmDatePicker),
480
+ provideBrnLabelable(HlmDatePicker),
481
+ ], queries: [{ propertyName: "_trigger", first: true, predicate: BrnDatePickerTriggerToken, descendants: true, isSignal: true }], viewQueries: [{ propertyName: "_calendar", first: true, predicate: (BrnCalendar), descendants: true, isSignal: true }, { propertyName: "popover", first: true, predicate: BrnPopover, descendants: true, isSignal: true }], hostDirectives: [{ directive: i1$1.BrnFieldControl }], ngImport: i0, template: "\n <hlm-popover sideOffset=\"5\" [state]=\"_popoverState()\" (stateChanged)=\"_onStateChange($event)\">\n <ng-content />\n\n <hlm-popover-content class=\"w-fit p-0\" *hlmPopoverPortal=\"let ctx\">\n <ng-content select=\"[hlmDatePickerHeader]\" />\n <hlm-calendar\n class=\"rounded-none border-0\"\n [captionLayout]=\"captionLayout()\"\n [date]=\"_mutableDate()\"\n [defaultFocusedDate]=\"_mutableDate() ?? defaultFocusedDate()\"\n [min]=\"min()\"\n [max]=\"max()\"\n [disabled]=\"disabledState()\"\n (dateChange)=\"_handleChange($event)\"\n />\n <ng-content select=\"[hlmDatePickerFooter]\" />\n </hlm-popover-content>\n </hlm-popover>\n ", isInline: true, dependencies: [{ kind: "directive", type: i2.HlmPopover, selector: "[hlmPopover],hlm-popover" }, { kind: "directive", type: i2.HlmPopoverContent, selector: "[hlmPopoverContent],hlm-popover-content" }, { kind: "directive", type: i2.HlmPopoverPortal, selector: "[hlmPopoverPortal]" }, { kind: "component", type: HlmCalendar, selector: "hlm-calendar", inputs: ["captionLayout"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
358
482
  }
359
483
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: HlmDatePicker, decorators: [{
360
484
  type: Component,
361
485
  args: [{
362
486
  selector: 'hlm-date-picker',
363
487
  imports: [HlmPopoverImports, HlmCalendar],
364
- providers: [HLM_DATE_PICKER_VALUE_ACCESSOR, provideBrnDatePicker(HlmDatePicker), provideBrnLabelable(HlmDatePicker)],
488
+ providers: [
489
+ HlmDatePickerCommitState,
490
+ HLM_DATE_PICKER_VALUE_ACCESSOR,
491
+ provideBrnDatePicker(HlmDatePicker),
492
+ provideBrnLabelable(HlmDatePicker),
493
+ ],
365
494
  changeDetection: ChangeDetectionStrategy.OnPush,
366
495
  hostDirectives: [BrnFieldControl],
367
496
  host: { class: "block w-full min-w-0 max-w-full" },
368
497
  template: "\n <hlm-popover sideOffset=\"5\" [state]=\"_popoverState()\" (stateChanged)=\"_onStateChange($event)\">\n <ng-content />\n\n <hlm-popover-content class=\"w-fit p-0\" *hlmPopoverPortal=\"let ctx\">\n <ng-content select=\"[hlmDatePickerHeader]\" />\n <hlm-calendar\n class=\"rounded-none border-0\"\n [captionLayout]=\"captionLayout()\"\n [date]=\"_mutableDate()\"\n [defaultFocusedDate]=\"_mutableDate() ?? defaultFocusedDate()\"\n [min]=\"min()\"\n [max]=\"max()\"\n [disabled]=\"disabledState()\"\n (dateChange)=\"_handleChange($event)\"\n />\n <ng-content select=\"[hlmDatePickerFooter]\" />\n </hlm-popover-content>\n </hlm-popover>\n ",
369
498
  }]
370
- }], propDecorators: { popover: [{ type: i0.ViewChild, args: [i0.forwardRef(() => BrnPopover), { isSignal: true }] }], _trigger: [{ type: i0.ContentChild, args: [i0.forwardRef(() => BrnDatePickerTriggerToken), { isSignal: true }] }], captionLayout: [{ type: i0.Input, args: [{ isSignal: true, alias: "captionLayout", required: false }] }], min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], wrapperDisabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "wrapperDisabled", required: false }] }], date: [{ type: i0.Input, args: [{ isSignal: true, alias: "date", required: false }] }], defaultFocusedDate: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultFocusedDate", required: false }] }], autoCloseOnSelect: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoCloseOnSelect", required: false }] }], formatDate: [{ type: i0.Input, args: [{ isSignal: true, alias: "formatDate", required: false }] }], transformDate: [{ type: i0.Input, args: [{ isSignal: true, alias: "transformDate", required: false }] }], dateChange: [{ type: i0.Output, args: ["dateChange"] }] } });
499
+ }], propDecorators: { _calendar: [{ type: i0.ViewChild, args: [i0.forwardRef(() => BrnCalendar), { isSignal: true }] }], popover: [{ type: i0.ViewChild, args: [i0.forwardRef(() => BrnPopover), { isSignal: true }] }], _trigger: [{ type: i0.ContentChild, args: [i0.forwardRef(() => BrnDatePickerTriggerToken), { isSignal: true }] }], captionLayout: [{ type: i0.Input, args: [{ isSignal: true, alias: "captionLayout", required: false }] }], min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], wrapperDisabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "wrapperDisabled", required: false }] }], date: [{ type: i0.Input, args: [{ isSignal: true, alias: "date", required: false }] }], defaultFocusedDate: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultFocusedDate", required: false }] }], autoCloseOnSelect: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoCloseOnSelect", required: false }] }], formatDate: [{ type: i0.Input, args: [{ isSignal: true, alias: "formatDate", required: false }] }], transformDate: [{ type: i0.Input, args: [{ isSignal: true, alias: "transformDate", required: false }] }], dateChange: [{ type: i0.Output, args: ["dateChange"] }] } });
371
500
 
372
501
  class HlmDatePickerAnchor {
373
502
  _host = inject(ElementRef, { host: true });
@@ -389,7 +518,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImpor
389
518
  args: [{ selector: '[hlmDatePickerAnchor]' }]
390
519
  }], ctorParameters: () => [], propDecorators: { hlmDatePickerAnchorForInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "hlmDatePickerAnchorFor", required: false }] }] } });
391
520
 
392
- class HlmDatePickerInput extends BrnDateInput {
521
+ class HlmDatePickerInput extends HlmConstrainedDateInput {
393
522
  _config = injectHlmDatePickerConfig();
394
523
  ariaLabel = input(undefined, /* @ts-ignore */
395
524
  ...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
@@ -413,8 +542,8 @@ class HlmDatePickerInput extends BrnDateInput {
413
542
  }
414
543
  /**
415
544
  * Parses input text into a date value. Return `null` for invalid
416
- * input - the picker's date is cleared while the text is preserved so
417
- * the user can fix it.
545
+ * input. Rejected text stays editable and aria-invalid; the committed value
546
+ * is preserved. Empty text explicitly clears on blur/Enter.
418
547
  *
419
548
  * Defaults to `parseDate` from `HlmDatePickerConfig`.
420
549
  */
@@ -447,7 +576,8 @@ class HlmDatePickerInput extends BrnDateInput {
447
576
  [placeholder]="placeholder()"
448
577
  [readonly]="readonly()"
449
578
  [disabled]="_disabled()"
450
- [forceInvalid]="forceInvalid()"
579
+ [attr.aria-invalid]="forceInvalid() || inputInvalid() ? 'true' : null"
580
+ [attr.data-matches-spartan-invalid]="forceInvalid() || inputInvalid() ? 'true' : null"
451
581
  [class]="$inputClass()"
452
582
  (click)="_handleClick()"
453
583
  (keydown.arrowDown)="_open()"
@@ -501,7 +631,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImpor
501
631
  [placeholder]="placeholder()"
502
632
  [readonly]="readonly()"
503
633
  [disabled]="_disabled()"
504
- [forceInvalid]="forceInvalid()"
634
+ [attr.aria-invalid]="forceInvalid() || inputInvalid() ? 'true' : null"
635
+ [attr.data-matches-spartan-invalid]="forceInvalid() || inputInvalid() ? 'true' : null"
505
636
  [class]="$inputClass()"
506
637
  (click)="_handleClick()"
507
638
  (keydown.arrowDown)="_open()"
@@ -544,6 +675,11 @@ const HLM_DATE_PICKER_MUTLI_VALUE_ACCESSOR = {
544
675
  };
545
676
  class HlmDatePickerMulti {
546
677
  _config = injectHlmDatePickerMultiConfig();
678
+ _dateAdapter = injectDateAdapter();
679
+ _commitState = inject(HlmDatePickerCommitState);
680
+ _calendar = viewChild((BrnCalendarMulti), /* @ts-ignore */
681
+ ...(ngDevMode ? [{ debugName: "_calendar" }] : /* istanbul ignore next */ []));
682
+ _restoringCalendar = false;
547
683
  popover = viewChild.required(BrnPopover, /* @ts-ignore */
548
684
  ...(ngDevMode ? [{ debugName: "popover" }] : /* istanbul ignore next */ []));
549
685
  _trigger = contentChild(BrnDatePickerTriggerToken, /* @ts-ignore */
@@ -557,7 +693,7 @@ class HlmDatePickerMulti {
557
693
  /** The maximum date that can be selected. */
558
694
  max = input(/* @ts-ignore */
559
695
  ...(ngDevMode ? [undefined, { debugName: "max" }] : /* istanbul ignore next */ []));
560
- /** The minimum selectable dates. */
696
+ /** Deselection floor; selection can grow from empty below this count. Explicit clear is allowed. */
561
697
  minSelection = input(undefined, { ...(ngDevMode ? { debugName: "minSelection" } : /* istanbul ignore next */ {}), transform: numberAttribute });
562
698
  /** The maximum selectable dates. */
563
699
  maxSelection = input(undefined, { ...(ngDevMode ? { debugName: "maxSelection" } : /* istanbul ignore next */ {}), transform: numberAttribute });
@@ -605,37 +741,58 @@ class HlmDatePickerMulti {
605
741
  this._onTouched?.();
606
742
  }
607
743
  _handleChange(value) {
608
- if (value === undefined)
744
+ if (this._restoringCalendar || value === undefined)
609
745
  return;
610
- if (this._disabled())
746
+ if (!this.updateDate(value)) {
747
+ // Restore the calendar's already-mutated model without a second user commit.
748
+ this._restoringCalendar = true;
749
+ try {
750
+ this._calendar()?.date.set(this._mutableDate());
751
+ }
752
+ finally {
753
+ this._restoringCalendar = false;
754
+ }
611
755
  return;
612
- const transformedDate = value !== undefined ? this.transformDates()(value) : value;
613
- this._mutableDate.set(transformedDate);
614
- this._onChange?.(transformedDate);
615
- this.dateChange.emit(transformedDate);
756
+ }
616
757
  if (this.autoCloseOnMaxSelection() && this._mutableDate()?.length === this.maxSelection()) {
617
758
  this._popoverState.set('closed');
618
759
  }
619
760
  }
620
761
  /**
621
- * Commit dates to the picker. Updates the internal model, notifies form
622
- * controls, and emits `dateChange`. Intended to be called from a text input
623
- * that parses user-entered values. Pass `null` to clear the selection.
762
+ * Commit user dates, validating raw and transformed dates/counts. Returns
763
+ * false without changing/emitting on rejection. Min selection prevents
764
+ * reductions below the floor, but permits growth from empty. Null explicitly
765
+ * clears (emits []), bypassing the selection floor and transform.
624
766
  */
625
767
  updateDate(value) {
626
768
  if (this._disabled())
627
- return;
769
+ return false;
770
+ if (value && !this._isSelectionAllowed(value))
771
+ return false;
628
772
  const transformedDate = value ? this.transformDates()(value) : undefined;
773
+ if (value && (!transformedDate || !this._isSelectionAllowed(transformedDate)))
774
+ return false;
629
775
  this._mutableDate.set(transformedDate);
776
+ this._commitState.changed();
630
777
  this._onChange?.(transformedDate ?? []);
631
778
  this.dateChange.emit(transformedDate ?? []);
779
+ return true;
780
+ }
781
+ _isSelectionAllowed(dates) {
782
+ const min = this.minSelection();
783
+ const max = this.maxSelection();
784
+ const currentCount = this._mutableDate()?.length ?? 0;
785
+ return (!(max != null && dates.length > max) &&
786
+ !(min != null && dates.length < min && dates.length < currentCount) &&
787
+ dates.every((date) => isSelectableDate(this._dateAdapter, date, this.min(), this.max())));
632
788
  }
633
789
  touched() {
634
790
  this._onTouched?.();
635
791
  }
636
- /** CONTROL VALUE ACCESSOR */
792
+ /** Programmatic CVA write: transforms without enforcing user constraints or emitting. Clears rejected text. */
637
793
  writeValue(value) {
638
794
  this._mutableDate.set(value ? this.transformDates()(value) : undefined);
795
+ this._commitState.changed();
639
796
  }
640
797
  registerOnChange(fn) {
641
798
  this._onChange = fn;
@@ -654,15 +811,17 @@ class HlmDatePickerMulti {
654
811
  }
655
812
  reset() {
656
813
  this._mutableDate.set(undefined);
814
+ this._commitState.changed();
657
815
  this._onChange?.([]);
658
816
  this.dateChange.emit([]);
659
817
  }
660
818
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: HlmDatePickerMulti, deps: [], target: i0.ɵɵFactoryTarget.Component });
661
819
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "22.1.3", type: HlmDatePickerMulti, isStandalone: true, selector: "hlm-date-picker-multi", inputs: { captionLayout: { classPropertyName: "captionLayout", publicName: "captionLayout", isSignal: true, isRequired: false, transformFunction: null }, min: { classPropertyName: "min", publicName: "min", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, minSelection: { classPropertyName: "minSelection", publicName: "minSelection", isSignal: true, isRequired: false, transformFunction: null }, maxSelection: { classPropertyName: "maxSelection", publicName: "maxSelection", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, date: { classPropertyName: "date", publicName: "date", isSignal: true, isRequired: false, transformFunction: null }, autoCloseOnMaxSelection: { classPropertyName: "autoCloseOnMaxSelection", publicName: "autoCloseOnMaxSelection", isSignal: true, isRequired: false, transformFunction: null }, formatDates: { classPropertyName: "formatDates", publicName: "formatDates", isSignal: true, isRequired: false, transformFunction: null }, transformDates: { classPropertyName: "transformDates", publicName: "transformDates", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { dateChange: "dateChange" }, host: { classAttribute: "block" }, providers: [
820
+ HlmDatePickerCommitState,
662
821
  HLM_DATE_PICKER_MUTLI_VALUE_ACCESSOR,
663
822
  provideBrnDatePicker(HlmDatePickerMulti),
664
823
  provideBrnLabelable(HlmDatePickerMulti),
665
- ], queries: [{ propertyName: "_trigger", first: true, predicate: BrnDatePickerTriggerToken, descendants: true, isSignal: true }], viewQueries: [{ propertyName: "popover", first: true, predicate: BrnPopover, descendants: true, isSignal: true }], hostDirectives: [{ directive: i1$1.BrnFieldControl }], ngImport: i0, template: "\n <hlm-popover sideOffset=\"5\" [state]=\"_popoverState()\" (stateChanged)=\"_onStateChange($event)\">\n <ng-content />\n\n <hlm-popover-content class=\"w-fit p-0\" *hlmPopoverPortal=\"let ctx\">\n <ng-content select=\"[hlmDatePickerHeader]\" />\n <hlm-calendar-multi\n class=\"rounded-none border-0\"\n [date]=\"_mutableDate()\"\n [captionLayout]=\"captionLayout()\"\n [min]=\"min()\"\n [max]=\"max()\"\n [minSelection]=\"minSelection()\"\n [maxSelection]=\"maxSelection()\"\n [disabled]=\"_disabled()\"\n (dateChange)=\"_handleChange($event)\"\n />\n <ng-content select=\"[hlmDatePickerFooter]\" />\n </hlm-popover-content>\n </hlm-popover>\n ", isInline: true, dependencies: [{ kind: "directive", type: i2.HlmPopover, selector: "[hlmPopover],hlm-popover" }, { kind: "directive", type: i2.HlmPopoverContent, selector: "[hlmPopoverContent],hlm-popover-content" }, { kind: "directive", type: i2.HlmPopoverPortal, selector: "[hlmPopoverPortal]" }, { kind: "component", type: HlmCalendarMulti, selector: "hlm-calendar-multi", inputs: ["captionLayout"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
824
+ ], queries: [{ propertyName: "_trigger", first: true, predicate: BrnDatePickerTriggerToken, descendants: true, isSignal: true }], viewQueries: [{ propertyName: "_calendar", first: true, predicate: (BrnCalendarMulti), descendants: true, isSignal: true }, { propertyName: "popover", first: true, predicate: BrnPopover, descendants: true, isSignal: true }], hostDirectives: [{ directive: i1$1.BrnFieldControl }], ngImport: i0, template: "\n <hlm-popover sideOffset=\"5\" [state]=\"_popoverState()\" (stateChanged)=\"_onStateChange($event)\">\n <ng-content />\n\n <hlm-popover-content class=\"w-fit p-0\" *hlmPopoverPortal=\"let ctx\">\n <ng-content select=\"[hlmDatePickerHeader]\" />\n <hlm-calendar-multi\n class=\"rounded-none border-0\"\n [date]=\"_mutableDate()\"\n [captionLayout]=\"captionLayout()\"\n [min]=\"min()\"\n [max]=\"max()\"\n [minSelection]=\"minSelection()\"\n [maxSelection]=\"maxSelection()\"\n [disabled]=\"_disabled()\"\n (dateChange)=\"_handleChange($event)\"\n />\n <ng-content select=\"[hlmDatePickerFooter]\" />\n </hlm-popover-content>\n </hlm-popover>\n ", isInline: true, dependencies: [{ kind: "directive", type: i2.HlmPopover, selector: "[hlmPopover],hlm-popover" }, { kind: "directive", type: i2.HlmPopoverContent, selector: "[hlmPopoverContent],hlm-popover-content" }, { kind: "directive", type: i2.HlmPopoverPortal, selector: "[hlmPopoverPortal]" }, { kind: "component", type: HlmCalendarMulti, selector: "hlm-calendar-multi", inputs: ["captionLayout"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
666
825
  }
667
826
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: HlmDatePickerMulti, decorators: [{
668
827
  type: Component,
@@ -670,6 +829,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImpor
670
829
  selector: 'hlm-date-picker-multi',
671
830
  imports: [HlmPopoverImports, HlmCalendarMulti],
672
831
  providers: [
832
+ HlmDatePickerCommitState,
673
833
  HLM_DATE_PICKER_MUTLI_VALUE_ACCESSOR,
674
834
  provideBrnDatePicker(HlmDatePickerMulti),
675
835
  provideBrnLabelable(HlmDatePickerMulti),
@@ -679,7 +839,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImpor
679
839
  host: { class: 'block' },
680
840
  template: "\n <hlm-popover sideOffset=\"5\" [state]=\"_popoverState()\" (stateChanged)=\"_onStateChange($event)\">\n <ng-content />\n\n <hlm-popover-content class=\"w-fit p-0\" *hlmPopoverPortal=\"let ctx\">\n <ng-content select=\"[hlmDatePickerHeader]\" />\n <hlm-calendar-multi\n class=\"rounded-none border-0\"\n [date]=\"_mutableDate()\"\n [captionLayout]=\"captionLayout()\"\n [min]=\"min()\"\n [max]=\"max()\"\n [minSelection]=\"minSelection()\"\n [maxSelection]=\"maxSelection()\"\n [disabled]=\"_disabled()\"\n (dateChange)=\"_handleChange($event)\"\n />\n <ng-content select=\"[hlmDatePickerFooter]\" />\n </hlm-popover-content>\n </hlm-popover>\n ",
681
841
  }]
682
- }], propDecorators: { popover: [{ type: i0.ViewChild, args: [i0.forwardRef(() => BrnPopover), { isSignal: true }] }], _trigger: [{ type: i0.ContentChild, args: [i0.forwardRef(() => BrnDatePickerTriggerToken), { isSignal: true }] }], captionLayout: [{ type: i0.Input, args: [{ isSignal: true, alias: "captionLayout", required: false }] }], min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], minSelection: [{ type: i0.Input, args: [{ isSignal: true, alias: "minSelection", required: false }] }], maxSelection: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxSelection", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], date: [{ type: i0.Input, args: [{ isSignal: true, alias: "date", required: false }] }], autoCloseOnMaxSelection: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoCloseOnMaxSelection", required: false }] }], formatDates: [{ type: i0.Input, args: [{ isSignal: true, alias: "formatDates", required: false }] }], transformDates: [{ type: i0.Input, args: [{ isSignal: true, alias: "transformDates", required: false }] }], dateChange: [{ type: i0.Output, args: ["dateChange"] }] } });
842
+ }], propDecorators: { _calendar: [{ type: i0.ViewChild, args: [i0.forwardRef(() => BrnCalendarMulti), { isSignal: true }] }], popover: [{ type: i0.ViewChild, args: [i0.forwardRef(() => BrnPopover), { isSignal: true }] }], _trigger: [{ type: i0.ContentChild, args: [i0.forwardRef(() => BrnDatePickerTriggerToken), { isSignal: true }] }], captionLayout: [{ type: i0.Input, args: [{ isSignal: true, alias: "captionLayout", required: false }] }], min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], minSelection: [{ type: i0.Input, args: [{ isSignal: true, alias: "minSelection", required: false }] }], maxSelection: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxSelection", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], date: [{ type: i0.Input, args: [{ isSignal: true, alias: "date", required: false }] }], autoCloseOnMaxSelection: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoCloseOnMaxSelection", required: false }] }], formatDates: [{ type: i0.Input, args: [{ isSignal: true, alias: "formatDates", required: false }] }], transformDates: [{ type: i0.Input, args: [{ isSignal: true, alias: "transformDates", required: false }] }], dateChange: [{ type: i0.Output, args: ["dateChange"] }] } });
683
843
 
684
844
  class HlmDatePickerTrigger {
685
845
  static _nextId = 0;
@@ -747,7 +907,7 @@ function getDefaultConfig$1() {
747
907
  if (isNaN(start.getTime()))
748
908
  return null;
749
909
  const end = parts.length === 2 ? new Date(parts[1]) : start;
750
- return [start, isNaN(end.getTime()) ? start : end];
910
+ return isNaN(end.getTime()) ? null : [start, end];
751
911
  },
752
912
  };
753
913
  }
@@ -760,7 +920,7 @@ function injectHlmDateRangePickerConfig() {
760
920
  return injectedConfig ? injectedConfig : getDefaultConfig$1();
761
921
  }
762
922
 
763
- class HlmDateRangeInput extends BrnDateInput {
923
+ class HlmDateRangeInput extends HlmConstrainedDateInput {
764
924
  _config = injectHlmDateRangePickerConfig();
765
925
  /** Native `name` attribute forwarded to the inner `<input>`. */
766
926
  name = input(undefined, /* @ts-ignore */
@@ -784,8 +944,8 @@ class HlmDateRangeInput extends BrnDateInput {
784
944
  }
785
945
  /**
786
946
  * Parses input text into a date range. Return `null` for invalid
787
- * input - the picker's range is cleared while the text is preserved so
788
- * the user can fix it.
947
+ * input. Rejected text stays editable and aria-invalid; the committed value
948
+ * is preserved. Empty text explicitly clears on blur/Enter.
789
949
  *
790
950
  * Defaults to `parseDate` from `HlmDateRangePickerConfig`.
791
951
  */
@@ -818,7 +978,8 @@ class HlmDateRangeInput extends BrnDateInput {
818
978
  [placeholder]="placeholder()"
819
979
  [readonly]="readonly()"
820
980
  [disabled]="_disabled()"
821
- [forceInvalid]="forceInvalid()"
981
+ [attr.aria-invalid]="forceInvalid() || inputInvalid() ? 'true' : null"
982
+ [attr.data-matches-spartan-invalid]="forceInvalid() || inputInvalid() ? 'true' : null"
822
983
  [class]="$inputClass()"
823
984
  (click)="_handleClick()"
824
985
  (keydown.arrowDown)="_open()"
@@ -872,7 +1033,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImpor
872
1033
  [placeholder]="placeholder()"
873
1034
  [readonly]="readonly()"
874
1035
  [disabled]="_disabled()"
875
- [forceInvalid]="forceInvalid()"
1036
+ [attr.aria-invalid]="forceInvalid() || inputInvalid() ? 'true' : null"
1037
+ [attr.data-matches-spartan-invalid]="forceInvalid() || inputInvalid() ? 'true' : null"
876
1038
  [class]="$inputClass()"
877
1039
  (click)="_handleClick()"
878
1040
  (keydown.arrowDown)="_open()"
@@ -915,6 +1077,11 @@ const HLM_DATE_RANGE_PICKER_VALUE_ACCESSOR = {
915
1077
  };
916
1078
  class HlmDateRangePicker {
917
1079
  _config = injectHlmDateRangePickerConfig();
1080
+ _dateAdapter = injectDateAdapter();
1081
+ _commitState = inject(HlmDatePickerCommitState);
1082
+ _calendar = viewChild((BrnCalendarRange), /* @ts-ignore */
1083
+ ...(ngDevMode ? [{ debugName: "_calendar" }] : /* istanbul ignore next */ []));
1084
+ _restoringCalendar = false;
918
1085
  popover = viewChild.required(BrnPopover, /* @ts-ignore */
919
1086
  ...(ngDevMode ? [{ debugName: "popover" }] : /* istanbul ignore next */ []));
920
1087
  _trigger = contentChild(BrnDatePickerTriggerToken, /* @ts-ignore */
@@ -979,50 +1146,85 @@ class HlmDateRangePicker {
979
1146
  }
980
1147
  }
981
1148
  _handleStartDayChange(value) {
1149
+ if (this._restoringCalendar)
1150
+ return;
1151
+ if (this._disabled() || (value != null && !isSelectableDate(this._dateAdapter, value, this.min(), this.max()))) {
1152
+ this._restoreCalendar();
1153
+ return;
1154
+ }
982
1155
  this._start.set(value);
983
1156
  }
984
1157
  _handleEndDateChange(value) {
985
- this._end.set(value);
986
- if (this._disabled())
1158
+ if (this._restoringCalendar)
1159
+ return;
1160
+ if (this._disabled()) {
1161
+ this._restoreCalendar();
987
1162
  return;
1163
+ }
1164
+ if (value == null) {
1165
+ this._end.set(undefined);
1166
+ return;
1167
+ }
988
1168
  const start = this._start();
989
- if (start && value) {
990
- const transformedDates = this.transformDates()([start, value]);
991
- this._mutableDate.set(transformedDates);
992
- this.dateChange.emit(transformedDates);
993
- this._onChange?.(transformedDates);
1169
+ if (start != null) {
1170
+ if (!this.updateDate([start, value])) {
1171
+ this._restoreCalendar();
1172
+ return;
1173
+ }
994
1174
  if (this.autoCloseOnEndSelection()) {
995
1175
  this._popoverState.set('closed');
996
1176
  }
997
1177
  }
998
1178
  }
1179
+ _restoreCalendar() {
1180
+ this._start.set(this._mutableDate()?.[0]);
1181
+ this._end.set(this._mutableDate()?.[1]);
1182
+ this._restoringCalendar = true;
1183
+ try {
1184
+ this._calendar()?.startDate.set(this._start());
1185
+ this._calendar()?.endDate.set(this._end());
1186
+ }
1187
+ finally {
1188
+ this._restoringCalendar = false;
1189
+ }
1190
+ }
999
1191
  /**
1000
- * Commit a range to the picker. Updates the internal model, notifies form
1001
- * controls, and emits `dateChange`. Intended to be called from a text input
1002
- * that parses user-entered values. Pass `null` to clear the range.
1192
+ * Commit a user range (null clears). Both raw/transformed endpoints must
1193
+ * satisfy adapter whole-day bounds; the transformed range must be ordered.
1194
+ * Returns false without changing/emitting the model on rejection or disabled.
1003
1195
  */
1004
1196
  updateDate(value) {
1005
1197
  if (this._disabled())
1006
- return;
1198
+ return false;
1007
1199
  if (!value) {
1008
1200
  this._mutableDate.set(undefined);
1009
1201
  this._start.set(undefined);
1010
1202
  this._end.set(undefined);
1203
+ this._commitState.changed();
1011
1204
  this._onChange?.(null);
1012
1205
  this.dateChange.emit(null);
1013
- return;
1206
+ return true;
1014
1207
  }
1208
+ if (!this._isRangeAllowed(value))
1209
+ return false;
1015
1210
  const transformedDates = this.transformDates()(value);
1211
+ if (!this._isRangeAllowed(transformedDates) || this._dateAdapter.isAfter(transformedDates[0], transformedDates[1]))
1212
+ return false;
1016
1213
  this._mutableDate.set(transformedDates);
1017
1214
  this._start.set(transformedDates[0]);
1018
1215
  this._end.set(transformedDates[1]);
1216
+ this._commitState.changed();
1019
1217
  this._onChange?.(transformedDates);
1020
1218
  this.dateChange.emit(transformedDates);
1219
+ return true;
1220
+ }
1221
+ _isRangeAllowed(dates) {
1222
+ return (dates?.length === 2 && dates.every((date) => isSelectableDate(this._dateAdapter, date, this.min(), this.max())));
1021
1223
  }
1022
1224
  touched() {
1023
1225
  this._onTouched?.();
1024
1226
  }
1025
- /** CONTROL VALUE ACCESSOR */
1227
+ /** Programmatic CVA write: transforms without enforcing user constraints or emitting. Clears rejected text. */
1026
1228
  writeValue(value) {
1027
1229
  untracked(() => {
1028
1230
  if (!value) {
@@ -1031,6 +1233,7 @@ class HlmDateRangePicker {
1031
1233
  else {
1032
1234
  this._mutableDate.set(this.transformDates()(value));
1033
1235
  }
1236
+ this._commitState.changed();
1034
1237
  });
1035
1238
  }
1036
1239
  registerOnChange(fn) {
@@ -1052,6 +1255,7 @@ class HlmDateRangePicker {
1052
1255
  this._mutableDate.set(undefined);
1053
1256
  this._start.set(undefined);
1054
1257
  this._end.set(undefined);
1258
+ this._commitState.changed();
1055
1259
  this._onChange?.(null);
1056
1260
  this.dateChange.emit(null);
1057
1261
  }
@@ -1064,10 +1268,11 @@ class HlmDateRangePicker {
1064
1268
  }
1065
1269
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: HlmDateRangePicker, deps: [], target: i0.ɵɵFactoryTarget.Component });
1066
1270
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "22.1.3", type: HlmDateRangePicker, isStandalone: true, selector: "hlm-date-range-picker", inputs: { captionLayout: { classPropertyName: "captionLayout", publicName: "captionLayout", isSignal: true, isRequired: false, transformFunction: null }, min: { classPropertyName: "min", publicName: "min", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, date: { classPropertyName: "date", publicName: "date", isSignal: true, isRequired: false, transformFunction: null }, autoCloseOnEndSelection: { classPropertyName: "autoCloseOnEndSelection", publicName: "autoCloseOnEndSelection", isSignal: true, isRequired: false, transformFunction: null }, formatDates: { classPropertyName: "formatDates", publicName: "formatDates", isSignal: true, isRequired: false, transformFunction: null }, transformDates: { classPropertyName: "transformDates", publicName: "transformDates", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { dateChange: "dateChange" }, host: { classAttribute: "block" }, providers: [
1271
+ HlmDatePickerCommitState,
1067
1272
  HLM_DATE_RANGE_PICKER_VALUE_ACCESSOR,
1068
1273
  provideBrnDatePicker(HlmDateRangePicker),
1069
1274
  provideBrnLabelable(HlmDateRangePicker),
1070
- ], queries: [{ propertyName: "_trigger", first: true, predicate: BrnDatePickerTriggerToken, descendants: true, isSignal: true }], viewQueries: [{ propertyName: "popover", first: true, predicate: BrnPopover, descendants: true, isSignal: true }], hostDirectives: [{ directive: i1$1.BrnFieldControl }], ngImport: i0, template: "\n <hlm-popover sideOffset=\"5\" [state]=\"_popoverState()\" (stateChanged)=\"_onStateChange($event)\">\n <ng-content />\n\n <hlm-popover-content class=\"w-fit p-0\" *hlmPopoverPortal=\"let ctx\">\n <ng-content select=\"[hlmDatePickerHeader]\" />\n <hlm-calendar-range\n class=\"rounded-none border-0\"\n [startDate]=\"_start()\"\n [captionLayout]=\"captionLayout()\"\n [endDate]=\"_end()\"\n [min]=\"min()\"\n [max]=\"max()\"\n [disabled]=\"_disabled()\"\n (startDateChange)=\"_handleStartDayChange($event)\"\n (endDateChange)=\"_handleEndDateChange($event)\"\n />\n <ng-content select=\"[hlmDatePickerFooter]\" />\n </hlm-popover-content>\n </hlm-popover>\n ", isInline: true, dependencies: [{ kind: "directive", type: i2.HlmPopover, selector: "[hlmPopover],hlm-popover" }, { kind: "directive", type: i2.HlmPopoverContent, selector: "[hlmPopoverContent],hlm-popover-content" }, { kind: "directive", type: i2.HlmPopoverPortal, selector: "[hlmPopoverPortal]" }, { kind: "component", type: HlmCalendarRange, selector: "hlm-calendar-range", inputs: ["captionLayout"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1275
+ ], queries: [{ propertyName: "_trigger", first: true, predicate: BrnDatePickerTriggerToken, descendants: true, isSignal: true }], viewQueries: [{ propertyName: "_calendar", first: true, predicate: (BrnCalendarRange), descendants: true, isSignal: true }, { propertyName: "popover", first: true, predicate: BrnPopover, descendants: true, isSignal: true }], hostDirectives: [{ directive: i1$1.BrnFieldControl }], ngImport: i0, template: "\n <hlm-popover sideOffset=\"5\" [state]=\"_popoverState()\" (stateChanged)=\"_onStateChange($event)\">\n <ng-content />\n\n <hlm-popover-content class=\"w-fit p-0\" *hlmPopoverPortal=\"let ctx\">\n <ng-content select=\"[hlmDatePickerHeader]\" />\n <hlm-calendar-range\n class=\"rounded-none border-0\"\n [startDate]=\"_start()\"\n [captionLayout]=\"captionLayout()\"\n [endDate]=\"_end()\"\n [min]=\"min()\"\n [max]=\"max()\"\n [disabled]=\"_disabled()\"\n (startDateChange)=\"_handleStartDayChange($event)\"\n (endDateChange)=\"_handleEndDateChange($event)\"\n />\n <ng-content select=\"[hlmDatePickerFooter]\" />\n </hlm-popover-content>\n </hlm-popover>\n ", isInline: true, dependencies: [{ kind: "directive", type: i2.HlmPopover, selector: "[hlmPopover],hlm-popover" }, { kind: "directive", type: i2.HlmPopoverContent, selector: "[hlmPopoverContent],hlm-popover-content" }, { kind: "directive", type: i2.HlmPopoverPortal, selector: "[hlmPopoverPortal]" }, { kind: "component", type: HlmCalendarRange, selector: "hlm-calendar-range", inputs: ["captionLayout"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1071
1276
  }
1072
1277
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: HlmDateRangePicker, decorators: [{
1073
1278
  type: Component,
@@ -1075,6 +1280,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImpor
1075
1280
  selector: 'hlm-date-range-picker',
1076
1281
  imports: [HlmPopoverImports, HlmCalendarRange],
1077
1282
  providers: [
1283
+ HlmDatePickerCommitState,
1078
1284
  HLM_DATE_RANGE_PICKER_VALUE_ACCESSOR,
1079
1285
  provideBrnDatePicker(HlmDateRangePicker),
1080
1286
  provideBrnLabelable(HlmDateRangePicker),
@@ -1084,7 +1290,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImpor
1084
1290
  host: { class: 'block' },
1085
1291
  template: "\n <hlm-popover sideOffset=\"5\" [state]=\"_popoverState()\" (stateChanged)=\"_onStateChange($event)\">\n <ng-content />\n\n <hlm-popover-content class=\"w-fit p-0\" *hlmPopoverPortal=\"let ctx\">\n <ng-content select=\"[hlmDatePickerHeader]\" />\n <hlm-calendar-range\n class=\"rounded-none border-0\"\n [startDate]=\"_start()\"\n [captionLayout]=\"captionLayout()\"\n [endDate]=\"_end()\"\n [min]=\"min()\"\n [max]=\"max()\"\n [disabled]=\"_disabled()\"\n (startDateChange)=\"_handleStartDayChange($event)\"\n (endDateChange)=\"_handleEndDateChange($event)\"\n />\n <ng-content select=\"[hlmDatePickerFooter]\" />\n </hlm-popover-content>\n </hlm-popover>\n ",
1086
1292
  }]
1087
- }], propDecorators: { popover: [{ type: i0.ViewChild, args: [i0.forwardRef(() => BrnPopover), { isSignal: true }] }], _trigger: [{ type: i0.ContentChild, args: [i0.forwardRef(() => BrnDatePickerTriggerToken), { isSignal: true }] }], captionLayout: [{ type: i0.Input, args: [{ isSignal: true, alias: "captionLayout", required: false }] }], min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], date: [{ type: i0.Input, args: [{ isSignal: true, alias: "date", required: false }] }], autoCloseOnEndSelection: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoCloseOnEndSelection", required: false }] }], formatDates: [{ type: i0.Input, args: [{ isSignal: true, alias: "formatDates", required: false }] }], transformDates: [{ type: i0.Input, args: [{ isSignal: true, alias: "transformDates", required: false }] }], dateChange: [{ type: i0.Output, args: ["dateChange"] }] } });
1293
+ }], propDecorators: { _calendar: [{ type: i0.ViewChild, args: [i0.forwardRef(() => BrnCalendarRange), { isSignal: true }] }], popover: [{ type: i0.ViewChild, args: [i0.forwardRef(() => BrnPopover), { isSignal: true }] }], _trigger: [{ type: i0.ContentChild, args: [i0.forwardRef(() => BrnDatePickerTriggerToken), { isSignal: true }] }], captionLayout: [{ type: i0.Input, args: [{ isSignal: true, alias: "captionLayout", required: false }] }], min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], date: [{ type: i0.Input, args: [{ isSignal: true, alias: "date", required: false }] }], autoCloseOnEndSelection: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoCloseOnEndSelection", required: false }] }], formatDates: [{ type: i0.Input, args: [{ isSignal: true, alias: "formatDates", required: false }] }], transformDates: [{ type: i0.Input, args: [{ isSignal: true, alias: "transformDates", required: false }] }], dateChange: [{ type: i0.Output, args: ["dateChange"] }] } });
1088
1294
 
1089
1295
  const mmYYYY = (date) => {
1090
1296
  if (!(date instanceof Date))