@egose/shadcn-theme-ng-tw 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 +15 -9
  6. package/checkbox/types/checkbox.d.ts +6 -4
  7. package/combobox/fesm2022/combobox.mjs +22 -6
  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 +76 -26
  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 +33 -8
  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 +65 -14
  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 +28 -14
  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 +23 -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 +21 -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 +25 -8
  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 +83 -75
  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 +97 -36
  45. package/searchable-multiselect/types/searchable-multiselect.d.ts +23 -6
  46. package/slider/fesm2022/slider.mjs +15 -5
  47. package/slider/types/slider.d.ts +6 -2
  48. package/stepper/README.md +39 -19
  49. package/stepper/fesm2022/stepper.mjs +71 -17
  50. package/stepper/types/stepper.d.ts +12 -1
@@ -11,7 +11,23 @@ import * as _spartan_ng_brain_popover from '@spartan-ng/brain/popover';
11
11
  import { BrnPopover } from '@spartan-ng/brain/popover';
12
12
  import * as i1$1 from '@spartan-ng/brain/field';
13
13
 
14
- declare class HlmDateMultiInput<T> extends BrnDateInput<T[]> implements BrnDatePickerTriggerBase {
14
+ /** Internal input lifecycle shared by the single, range and multi pickers. */
15
+ declare abstract class HlmConstrainedDateInput<V> extends BrnDateInput<V> {
16
+ private readonly _commitState;
17
+ protected abstract readonly: () => boolean;
18
+ protected readonly _inputValue: _angular_core.WritableSignal<string>;
19
+ /** Rejected text stays invalid until a successful commit, clear, or external value write. */
20
+ readonly inputInvalid: _angular_core.WritableSignal<boolean>;
21
+ protected _commitDate(): void;
22
+ protected _handleBlur(): void;
23
+ protected _handleEnter(event: Event): void;
24
+ protected _handleFocus(): void;
25
+ protected _clear(): void;
26
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<HlmConstrainedDateInput<any>, never>;
27
+ static ɵdir: _angular_core.ɵɵDirectiveDeclaration<HlmConstrainedDateInput<any>, never, never, {}, {}, never, never, true, never>;
28
+ }
29
+
30
+ declare class HlmDateMultiInput<T> extends HlmConstrainedDateInput<T[]> implements BrnDatePickerTriggerBase {
15
31
  private readonly _config;
16
32
  /** Native `name` attribute forwarded to the inner `<input>`. */
17
33
  readonly name: _angular_core.InputSignal<string | undefined>;
@@ -26,9 +42,9 @@ declare class HlmDateMultiInput<T> extends BrnDateInput<T[]> implements BrnDateP
26
42
  protected readonly $inputClass: _angular_core.Signal<string>;
27
43
  constructor();
28
44
  /**
29
- * Parses input text into dates. Return `null` for invalid input - the
30
- * picker's dates are cleared while the text is preserved so the user can
31
- * fix it.
45
+ * Parses input text into dates. Return `null` for invalid input. Rejected
46
+ * text stays editable and aria-invalid; the committed value is preserved.
47
+ * Empty text explicitly clears on blur/Enter.
32
48
  *
33
49
  * Defaults to `parseDate` from `HlmDatePickerMultiConfig`.
34
50
  */
@@ -53,6 +69,10 @@ declare const HLM_DATE_PICKER_VALUE_ACCESSOR: {
53
69
  };
54
70
  declare class HlmDatePicker<T> implements BrnDatePickerBase<T>, ControlValueAccessor {
55
71
  private readonly _config;
72
+ private readonly _dateAdapter;
73
+ private readonly _commitState;
74
+ private readonly _calendar;
75
+ private _restoringCalendar;
56
76
  readonly popover: _angular_core.Signal<BrnPopover>;
57
77
  private readonly _trigger;
58
78
  /** Show dropdowns to navigate between months or years. */
@@ -91,13 +111,12 @@ declare class HlmDatePicker<T> implements BrnDatePickerBase<T>, ControlValueAcce
91
111
  protected _onStateChange(state: BrnOverlayState): void;
92
112
  protected _handleChange(value: T | undefined): void;
93
113
  /**
94
- * Commit a date to the picker. Updates the internal model, notifies form
95
- * controls, and emits `dateChange`. Unlike `_handleChange`, this does not
96
- * close the popover - it's intended to be called from a text input that
97
- * is parsing user-entered values while typing.
114
+ * Commit a user value (null clears). Returns false without changing/emitting
115
+ * the model when disabled or when the raw/transformed date is invalid or
116
+ * outside the adapter's inclusive whole-day min/max bounds.
98
117
  */
99
- updateDate(value: T | null): void;
100
- /** CONTROL VALUE ACCESSOR */
118
+ updateDate(value: T | null): boolean;
119
+ /** Programmatic CVA write: transforms without enforcing user constraints or emitting. Clears rejected text. */
101
120
  writeValue(value: T | null): void;
102
121
  registerOnChange(fn: ChangeFn<T | null>): void;
103
122
  registerOnTouched(fn: TouchFn): void;
@@ -120,7 +139,7 @@ declare class HlmDatePickerAnchor {
120
139
  static ɵdir: _angular_core.ɵɵDirectiveDeclaration<HlmDatePickerAnchor, "[hlmDatePickerAnchor]", never, { "hlmDatePickerAnchorForInput": { "alias": "hlmDatePickerAnchorFor"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
121
140
  }
122
141
 
123
- declare class HlmDatePickerInput<T> extends BrnDateInput<T> implements BrnDatePickerTriggerBase {
142
+ declare class HlmDatePickerInput<T> extends HlmConstrainedDateInput<T> implements BrnDatePickerTriggerBase {
124
143
  private readonly _config;
125
144
  readonly ariaLabel: _angular_core.InputSignal<string | undefined>;
126
145
  readonly ariaDescribedby: _angular_core.InputSignal<string | null>;
@@ -136,8 +155,8 @@ declare class HlmDatePickerInput<T> extends BrnDateInput<T> implements BrnDatePi
136
155
  constructor();
137
156
  /**
138
157
  * Parses input text into a date value. Return `null` for invalid
139
- * input - the picker's date is cleared while the text is preserved so
140
- * the user can fix it.
158
+ * input. Rejected text stays editable and aria-invalid; the committed value
159
+ * is preserved. Empty text explicitly clears on blur/Enter.
141
160
  *
142
161
  * Defaults to `parseDate` from `HlmDatePickerConfig`.
143
162
  */
@@ -162,6 +181,10 @@ declare const HLM_DATE_PICKER_MUTLI_VALUE_ACCESSOR: {
162
181
  };
163
182
  declare class HlmDatePickerMulti<T> implements BrnDatePickerBase<T[]>, ControlValueAccessor {
164
183
  private readonly _config;
184
+ private readonly _dateAdapter;
185
+ private readonly _commitState;
186
+ private readonly _calendar;
187
+ private _restoringCalendar;
165
188
  readonly popover: _angular_core.Signal<BrnPopover>;
166
189
  private readonly _trigger;
167
190
  /** Show dropdowns to navigate between months or years. */
@@ -170,7 +193,7 @@ declare class HlmDatePickerMulti<T> implements BrnDatePickerBase<T[]>, ControlVa
170
193
  readonly min: _angular_core.InputSignal<T | undefined>;
171
194
  /** The maximum date that can be selected. */
172
195
  readonly max: _angular_core.InputSignal<T | undefined>;
173
- /** The minimum selectable dates. */
196
+ /** Deselection floor; selection can grow from empty below this count. Explicit clear is allowed. */
174
197
  readonly minSelection: _angular_core.InputSignalWithTransform<number | undefined, NumberInput>;
175
198
  /** The maximum selectable dates. */
176
199
  readonly maxSelection: _angular_core.InputSignalWithTransform<number | undefined, NumberInput>;
@@ -201,13 +224,15 @@ declare class HlmDatePickerMulti<T> implements BrnDatePickerBase<T[]>, ControlVa
201
224
  protected _onStateChange(state: BrnOverlayState): void;
202
225
  protected _handleChange(value: T[] | undefined): void;
203
226
  /**
204
- * Commit dates to the picker. Updates the internal model, notifies form
205
- * controls, and emits `dateChange`. Intended to be called from a text input
206
- * that parses user-entered values. Pass `null` to clear the selection.
227
+ * Commit user dates, validating raw and transformed dates/counts. Returns
228
+ * false without changing/emitting on rejection. Min selection prevents
229
+ * reductions below the floor, but permits growth from empty. Null explicitly
230
+ * clears (emits []), bypassing the selection floor and transform.
207
231
  */
208
- updateDate(value: T[] | null): void;
232
+ updateDate(value: T[] | null): boolean;
233
+ private _isSelectionAllowed;
209
234
  touched(): void;
210
- /** CONTROL VALUE ACCESSOR */
235
+ /** Programmatic CVA write: transforms without enforcing user constraints or emitting. Clears rejected text. */
211
236
  writeValue(value: T[] | null): void;
212
237
  registerOnChange(fn: ChangeFn<T[]>): void;
213
238
  registerOnTouched(fn: TouchFn): void;
@@ -246,7 +271,7 @@ declare class HlmDatePickerTrigger implements BrnDatePickerTriggerBase {
246
271
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<HlmDatePickerTrigger, "hlm-date-picker-trigger", never, { "userClass": { "alias": "class"; "required": false; "isSignal": true; }; "buttonId": { "alias": "buttonId"; "required": false; "isSignal": true; }; "forceInvalid": { "alias": "forceInvalid"; "required": false; "isSignal": true; }; "variant": { "alias": "variant"; "required": false; "isSignal": true; }; "showTrigger": { "alias": "showTrigger"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
247
272
  }
248
273
 
249
- declare class HlmDateRangeInput<T> extends BrnDateInput<[T, T]> implements BrnDatePickerTriggerBase {
274
+ declare class HlmDateRangeInput<T> extends HlmConstrainedDateInput<[T, T]> implements BrnDatePickerTriggerBase {
250
275
  private readonly _config;
251
276
  /** Native `name` attribute forwarded to the inner `<input>`. */
252
277
  readonly name: _angular_core.InputSignal<string | undefined>;
@@ -262,8 +287,8 @@ declare class HlmDateRangeInput<T> extends BrnDateInput<[T, T]> implements BrnDa
262
287
  constructor();
263
288
  /**
264
289
  * Parses input text into a date range. Return `null` for invalid
265
- * input - the picker's range is cleared while the text is preserved so
266
- * the user can fix it.
290
+ * input. Rejected text stays editable and aria-invalid; the committed value
291
+ * is preserved. Empty text explicitly clears on blur/Enter.
267
292
  *
268
293
  * Defaults to `parseDate` from `HlmDateRangePickerConfig`.
269
294
  */
@@ -288,6 +313,10 @@ declare const HLM_DATE_RANGE_PICKER_VALUE_ACCESSOR: {
288
313
  };
289
314
  declare class HlmDateRangePicker<T> implements BrnDatePickerBase<[T, T]>, ControlValueAccessor {
290
315
  private readonly _config;
316
+ private readonly _dateAdapter;
317
+ private readonly _commitState;
318
+ private readonly _calendar;
319
+ private _restoringCalendar;
291
320
  readonly popover: _angular_core.Signal<BrnPopover>;
292
321
  private readonly _trigger;
293
322
  /** Show dropdowns to navigate between months or years. */
@@ -325,14 +354,16 @@ declare class HlmDateRangePicker<T> implements BrnDatePickerBase<[T, T]>, Contro
325
354
  protected _onStateChange(state: BrnOverlayState): void;
326
355
  protected _handleStartDayChange(value: T | undefined): void;
327
356
  protected _handleEndDateChange(value: T | undefined): void;
357
+ private _restoreCalendar;
328
358
  /**
329
- * Commit a range to the picker. Updates the internal model, notifies form
330
- * controls, and emits `dateChange`. Intended to be called from a text input
331
- * that parses user-entered values. Pass `null` to clear the range.
359
+ * Commit a user range (null clears). Both raw/transformed endpoints must
360
+ * satisfy adapter whole-day bounds; the transformed range must be ordered.
361
+ * Returns false without changing/emitting the model on rejection or disabled.
332
362
  */
333
- updateDate(value: [T, T] | null): void;
363
+ updateDate(value: [T, T] | null): boolean;
364
+ private _isRangeAllowed;
334
365
  touched(): void;
335
- /** CONTROL VALUE ACCESSOR */
366
+ /** Programmatic CVA write: transforms without enforcing user constraints or emitting. Clears rejected text. */
336
367
  writeValue(value: [T, T] | null): void;
337
368
  registerOnChange(fn: ChangeFn<[T, T] | null>): void;
338
369
  registerOnTouched(fn: TouchFn): void;
@@ -151,8 +151,10 @@ export class CityComponent {
151
151
  ## Accessibility notes
152
152
 
153
153
  - Label `for` ↔ input `inputId` wiring is automatic via `effectiveId` — always pass a `label`.
154
- - The autocomplete input exposes no `aria-describedby`, so error/hint ids render without an input-level link.
155
- - The required `*` is visual; pair with `Validators.required` so assistive tech and validation agree.
154
+ - The native input's `aria-describedby` references the displayed error when invalid and touched/dirty/submitted, otherwise the hint. Correction/reset restores the hint; absent messages leave no wrapper-owned reference.
155
+ - Optional `[aria-describedby]` (`string | null`, default `null`) adds consumer-owned IDs before the current message ID, with whitespace normalization and deduplication. Keep those external elements mounted; reserve `<effectiveId>-error` and `<effectiveId>-hint` for the wrapper. Use the input binding, not `[attr.aria-describedby]` on the wrapper host.
156
+ - `required` supplies the visual `*` and native `aria-required`; configure `Validators.required` independently. Native invalid state continues to follow the form control.
157
+ - Verification covers rendered DOM relationships and focus, not screen-reader announcement behavior. `HlmError` itself is not a live region.
156
158
 
157
159
  ## Theming / CSS variables
158
160
 
@@ -1,11 +1,12 @@
1
1
  import * as i0 from '@angular/core';
2
- import { InjectionToken, inject, input, computed, Component } from '@angular/core';
2
+ import { InjectionToken, inject, ChangeDetectorRef, DestroyRef, input, computed, Component } from '@angular/core';
3
3
  import * as i1 from '@angular/forms';
4
4
  import { FormGroupDirective, ReactiveFormsModule, ControlContainer } from '@angular/forms';
5
5
  import { injectEgFormErrorMessages, HlmFormIdGenerator, injectEgFormSharedConfig, resolveEgFormError, HlmFormField, HlmError, HlmHint } from '@egose/shadcn-theme-ng-tw/form-field';
6
6
  import { HlmLabel } from '@egose/shadcn-theme-ng-tw/label';
7
7
  import { HlmAutocomplete, HlmAutocompleteContent, HlmAutocompleteEmpty, HlmAutocompleteGroup, HlmAutocompleteInput, HlmAutocompleteItem, HlmAutocompleteList, HlmAutocompleteLabel } from '@egose/shadcn-theme-ng-tw/autocomplete';
8
8
  import { hlm } from '@egose/shadcn-theme-ng-tw/utils';
9
+ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
9
10
 
10
11
  const EgFormAutocompleteConfigToken = new InjectionToken('EgFormAutocompleteConfig');
11
12
  function provideEgFormAutocompleteConfig(config) {
@@ -21,6 +22,14 @@ class EgFormAutocomplete {
21
22
  generatedId = inject(HlmFormIdGenerator).generate('eg-form-autocomplete');
22
23
  _config = injectEgFormAutocompleteConfig();
23
24
  _shared = injectEgFormSharedConfig();
25
+ _cdr = inject(ChangeDetectorRef);
26
+ _destroyRef = inject(DestroyRef);
27
+ ngOnInit() {
28
+ // Submit/reset may change message visibility without changing the field's value or validity.
29
+ this.formGroupDirective.form.events
30
+ .pipe(takeUntilDestroyed(this._destroyRef))
31
+ .subscribe(() => this._cdr.markForCheck());
32
+ }
24
33
  label = input(undefined, /* @ts-ignore */
25
34
  ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
26
35
  controlId = input(undefined, /* @ts-ignore */
@@ -38,6 +47,8 @@ class EgFormAutocomplete {
38
47
  ...(ngDevMode ? [{ debugName: "autoError" }] : /* istanbul ignore next */ []));
39
48
  hint = input(undefined, /* @ts-ignore */
40
49
  ...(ngDevMode ? [{ debugName: "hint" }] : /* istanbul ignore next */ []));
50
+ /** Consumer-owned description IDs, merged with the current error and hint IDs. */
51
+ ariaDescribedBy = input(null, { ...(ngDevMode ? { debugName: "ariaDescribedBy" } : /* istanbul ignore next */ {}), alias: 'aria-describedby' });
41
52
  id = input(undefined, /* @ts-ignore */
42
53
  ...(ngDevMode ? [{ debugName: "id" }] : /* istanbul ignore next */ []));
43
54
  placeholder = input('Type to search…', /* @ts-ignore */
@@ -90,6 +101,10 @@ class EgFormAutocomplete {
90
101
  effectiveDisabled() {
91
102
  return this.disabled() || !!this.formGroupDirective.form.get(this.controlName())?.disabled;
92
103
  }
104
+ describedBy() {
105
+ const ids = [this.ariaDescribedBy(), this.showError() ? this.errorId() : this.hint() ? this.hintId() : null];
106
+ return [...new Set(ids.filter(Boolean).join(' ').split(/\s+/).filter(Boolean))].join(' ') || null;
107
+ }
93
108
  // Styling
94
109
  userClass = input('', { ...(ngDevMode ? { debugName: "userClass" } : /* istanbul ignore next */ {}), alias: 'class' });
95
110
  labelClass = input('', /* @ts-ignore */
@@ -103,8 +118,6 @@ class EgFormAutocomplete {
103
118
  hintClass = input('', /* @ts-ignore */
104
119
  ...(ngDevMode ? [{ debugName: "hintClass" }] : /* istanbul ignore next */ []));
105
120
  // Computed classes (library base < global config < per-instance).
106
- // NOTE: the autocomplete input exposes no aria-describedby, so error/hint
107
- // ids render without an input-level describedby link.
108
121
  $userClass = computed(() => hlm('tw:w-full', this.userClass()), /* @ts-ignore */
109
122
  ...(ngDevMode ? [{ debugName: "$userClass" }] : /* istanbul ignore next */ []));
110
123
  $labelClass = computed(() => hlm('tw:mb-1', this._shared.labelClass, this._config.labelClass, this.labelClass()), /* @ts-ignore */
@@ -118,7 +131,7 @@ class EgFormAutocomplete {
118
131
  $hintClass = computed(() => hlm('tw:mt-0', this._shared.hintClass, this._config.hintClass, this.hintClass()), /* @ts-ignore */
119
132
  ...(ngDevMode ? [{ debugName: "$hintClass" }] : /* istanbul ignore next */ []));
120
133
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: EgFormAutocomplete, deps: [], target: i0.ɵɵFactoryTarget.Component });
121
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.3", type: EgFormAutocomplete, isStandalone: true, selector: "eg-form-autocomplete", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, controlId: { classPropertyName: "controlId", publicName: "controlId", isSignal: true, isRequired: false, transformFunction: null }, controlName: { classPropertyName: "controlName", publicName: "controlName", isSignal: true, isRequired: false, transformFunction: null }, error: { classPropertyName: "error", publicName: "error", isSignal: true, isRequired: false, transformFunction: null }, autoError: { classPropertyName: "autoError", publicName: "autoError", isSignal: true, isRequired: false, transformFunction: null }, hint: { classPropertyName: "hint", publicName: "hint", isSignal: true, isRequired: false, transformFunction: null }, id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, emptyText: { classPropertyName: "emptyText", publicName: "emptyText", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, userClass: { classPropertyName: "userClass", publicName: "class", isSignal: true, isRequired: false, transformFunction: null }, labelClass: { classPropertyName: "labelClass", publicName: "labelClass", isSignal: true, isRequired: false, transformFunction: null }, controlClass: { classPropertyName: "controlClass", publicName: "controlClass", isSignal: true, isRequired: false, transformFunction: null }, inputClass: { classPropertyName: "inputClass", publicName: "inputClass", isSignal: true, isRequired: false, transformFunction: null }, errorClass: { classPropertyName: "errorClass", publicName: "errorClass", isSignal: true, isRequired: false, transformFunction: null }, hintClass: { classPropertyName: "hintClass", publicName: "hintClass", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "$userClass()" } }, providers: [{ provide: ControlContainer, useExisting: FormGroupDirective }], ngImport: i0, template: `
134
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.3", type: EgFormAutocomplete, isStandalone: true, selector: "eg-form-autocomplete", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, controlId: { classPropertyName: "controlId", publicName: "controlId", isSignal: true, isRequired: false, transformFunction: null }, controlName: { classPropertyName: "controlName", publicName: "controlName", isSignal: true, isRequired: false, transformFunction: null }, error: { classPropertyName: "error", publicName: "error", isSignal: true, isRequired: false, transformFunction: null }, autoError: { classPropertyName: "autoError", publicName: "autoError", isSignal: true, isRequired: false, transformFunction: null }, hint: { classPropertyName: "hint", publicName: "hint", isSignal: true, isRequired: false, transformFunction: null }, ariaDescribedBy: { classPropertyName: "ariaDescribedBy", publicName: "aria-describedby", isSignal: true, isRequired: false, transformFunction: null }, id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, emptyText: { classPropertyName: "emptyText", publicName: "emptyText", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, userClass: { classPropertyName: "userClass", publicName: "class", isSignal: true, isRequired: false, transformFunction: null }, labelClass: { classPropertyName: "labelClass", publicName: "labelClass", isSignal: true, isRequired: false, transformFunction: null }, controlClass: { classPropertyName: "controlClass", publicName: "controlClass", isSignal: true, isRequired: false, transformFunction: null }, inputClass: { classPropertyName: "inputClass", publicName: "inputClass", isSignal: true, isRequired: false, transformFunction: null }, errorClass: { classPropertyName: "errorClass", publicName: "errorClass", isSignal: true, isRequired: false, transformFunction: null }, hintClass: { classPropertyName: "hintClass", publicName: "hintClass", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "$userClass()" } }, providers: [{ provide: ControlContainer, useExisting: FormGroupDirective }], ngImport: i0, template: `
122
135
  @let lbl = label();
123
136
  @let cnm = controlName();
124
137
  @let hnt = hint();
@@ -135,7 +148,13 @@ class EgFormAutocomplete {
135
148
  }
136
149
 
137
150
  <div hlmAutocomplete [formControlName]="cnm" [disabled]="effectiveDisabled()" [class]="$controlClass()">
138
- <hlm-autocomplete-input [inputId]="effectiveId()" [placeholder]="placeholder()" [class]="$inputClass()" />
151
+ <hlm-autocomplete-input
152
+ [inputId]="effectiveId()"
153
+ [placeholder]="placeholder()"
154
+ [aria-describedby]="describedBy()"
155
+ [required]="rqrd"
156
+ [class]="$inputClass()"
157
+ />
139
158
  <hlm-autocomplete-content>
140
159
  <div hlmAutocompleteList>
141
160
  <div hlmAutocompleteGroup>
@@ -160,7 +179,7 @@ class EgFormAutocomplete {
160
179
  </hlm-hint>
161
180
  }
162
181
  </hlm-form-field>
163
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "component", type: HlmFormField, selector: "hlm-form-field", inputs: ["class"] }, { kind: "directive", type: HlmError, selector: "hlm-error", inputs: ["class"] }, { kind: "directive", type: HlmHint, selector: "hlm-hint", inputs: ["class"] }, { kind: "directive", type: HlmLabel, selector: "[hlmLabel]", inputs: ["class"] }, { kind: "directive", type: HlmAutocomplete, selector: "[hlmAutocomplete],hlm-autocomplete" }, { kind: "directive", type: HlmAutocompleteContent, selector: "[hlmAutocompleteContent],hlm-autocomplete-content" }, { kind: "directive", type: HlmAutocompleteEmpty, selector: "[hlmAutocompleteEmpty],hlm-autocomplete-empty" }, { kind: "directive", type: HlmAutocompleteGroup, selector: "[hlmAutocompleteGroup]" }, { kind: "component", type: HlmAutocompleteInput, selector: "hlm-autocomplete-input", inputs: ["inputId", "placeholder", "showSearch", "showClear", "forceInvalid", "aria-invalid"] }, { kind: "component", type: HlmAutocompleteItem, selector: "hlm-autocomplete-item" }, { kind: "directive", type: HlmAutocompleteList, selector: "[hlmAutocompleteList]" }] });
182
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "component", type: HlmFormField, selector: "hlm-form-field", inputs: ["class"] }, { kind: "directive", type: HlmError, selector: "hlm-error", inputs: ["class"] }, { kind: "directive", type: HlmHint, selector: "hlm-hint", inputs: ["class"] }, { kind: "directive", type: HlmLabel, selector: "[hlmLabel]", inputs: ["class"] }, { kind: "directive", type: HlmAutocomplete, selector: "[hlmAutocomplete],hlm-autocomplete" }, { kind: "directive", type: HlmAutocompleteContent, selector: "[hlmAutocompleteContent],hlm-autocomplete-content" }, { kind: "directive", type: HlmAutocompleteEmpty, selector: "[hlmAutocompleteEmpty],hlm-autocomplete-empty" }, { kind: "directive", type: HlmAutocompleteGroup, selector: "[hlmAutocompleteGroup]" }, { kind: "component", type: HlmAutocompleteInput, selector: "hlm-autocomplete-input", inputs: ["inputId", "placeholder", "aria-describedby", "required", "showSearch", "showClear", "forceInvalid", "aria-invalid"] }, { kind: "component", type: HlmAutocompleteItem, selector: "hlm-autocomplete-item" }, { kind: "directive", type: HlmAutocompleteList, selector: "[hlmAutocompleteList]" }] });
164
183
  }
165
184
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: EgFormAutocomplete, decorators: [{
166
185
  type: Component,
@@ -203,7 +222,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImpor
203
222
  }
204
223
 
205
224
  <div hlmAutocomplete [formControlName]="cnm" [disabled]="effectiveDisabled()" [class]="$controlClass()">
206
- <hlm-autocomplete-input [inputId]="effectiveId()" [placeholder]="placeholder()" [class]="$inputClass()" />
225
+ <hlm-autocomplete-input
226
+ [inputId]="effectiveId()"
227
+ [placeholder]="placeholder()"
228
+ [aria-describedby]="describedBy()"
229
+ [required]="rqrd"
230
+ [class]="$inputClass()"
231
+ />
207
232
  <hlm-autocomplete-content>
208
233
  <div hlmAutocompleteList>
209
234
  <div hlmAutocompleteGroup>
@@ -230,7 +255,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImpor
230
255
  </hlm-form-field>
231
256
  `,
232
257
  }]
233
- }], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], controlId: [{ type: i0.Input, args: [{ isSignal: true, alias: "controlId", required: false }] }], controlName: [{ type: i0.Input, args: [{ isSignal: true, alias: "controlName", required: false }] }], error: [{ type: i0.Input, args: [{ isSignal: true, alias: "error", required: false }] }], autoError: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoError", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }], id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], emptyText: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyText", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], userClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }], labelClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "labelClass", required: false }] }], controlClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "controlClass", required: false }] }], inputClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "inputClass", required: false }] }], errorClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorClass", required: false }] }], hintClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "hintClass", required: false }] }] } });
258
+ }], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], controlId: [{ type: i0.Input, args: [{ isSignal: true, alias: "controlId", required: false }] }], controlName: [{ type: i0.Input, args: [{ isSignal: true, alias: "controlName", required: false }] }], error: [{ type: i0.Input, args: [{ isSignal: true, alias: "error", required: false }] }], autoError: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoError", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }], ariaDescribedBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "aria-describedby", required: false }] }], id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], emptyText: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyText", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], userClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }], labelClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "labelClass", required: false }] }], controlClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "controlClass", required: false }] }], inputClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "inputClass", required: false }] }], errorClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorClass", required: false }] }], hintClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "hintClass", required: false }] }] } });
234
259
 
235
260
  /*
236
261
  * Public API Surface of form-autocomplete
@@ -1,13 +1,16 @@
1
1
  import * as _angular_core from '@angular/core';
2
- import { ValueProvider } from '@angular/core';
2
+ import { OnInit, ValueProvider } from '@angular/core';
3
3
  import { ClassValue } from 'clsx';
4
4
 
5
- declare class EgFormAutocomplete {
5
+ declare class EgFormAutocomplete implements OnInit {
6
6
  private readonly formGroupDirective;
7
7
  private readonly _errorMessages;
8
8
  private readonly generatedId;
9
9
  private readonly _config;
10
10
  private readonly _shared;
11
+ private readonly _cdr;
12
+ private readonly _destroyRef;
13
+ ngOnInit(): void;
11
14
  label: _angular_core.InputSignal<string | undefined>;
12
15
  controlId: _angular_core.InputSignal<string | undefined>;
13
16
  controlName: _angular_core.InputSignal<string>;
@@ -19,6 +22,8 @@ declare class EgFormAutocomplete {
19
22
  */
20
23
  autoError: _angular_core.InputSignal<boolean>;
21
24
  hint: _angular_core.InputSignal<string | undefined>;
25
+ /** Consumer-owned description IDs, merged with the current error and hint IDs. */
26
+ ariaDescribedBy: _angular_core.InputSignal<string | null>;
22
27
  id: _angular_core.InputSignal<string | undefined>;
23
28
  placeholder: _angular_core.InputSignal<string>;
24
29
  emptyText: _angular_core.InputSignal<string>;
@@ -47,6 +52,7 @@ declare class EgFormAutocomplete {
47
52
  * wrapper-toggle + `control.disable()` cannot clobber CVA state.
48
53
  */
49
54
  effectiveDisabled(): boolean;
55
+ protected describedBy(): string | null;
50
56
  userClass: _angular_core.InputSignal<ClassValue>;
51
57
  labelClass: _angular_core.InputSignal<string>;
52
58
  controlClass: _angular_core.InputSignal<string>;
@@ -60,7 +66,7 @@ declare class EgFormAutocomplete {
60
66
  $errorClass: _angular_core.Signal<string>;
61
67
  $hintClass: _angular_core.Signal<string>;
62
68
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<EgFormAutocomplete, never>;
63
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<EgFormAutocomplete, "eg-form-autocomplete", never, { "label": { "alias": "label"; "required": false; "isSignal": true; }; "controlId": { "alias": "controlId"; "required": false; "isSignal": true; }; "controlName": { "alias": "controlName"; "required": false; "isSignal": true; }; "error": { "alias": "error"; "required": false; "isSignal": true; }; "autoError": { "alias": "autoError"; "required": false; "isSignal": true; }; "hint": { "alias": "hint"; "required": false; "isSignal": true; }; "id": { "alias": "id"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "emptyText": { "alias": "emptyText"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "required": { "alias": "required"; "required": false; "isSignal": true; }; "options": { "alias": "options"; "required": false; "isSignal": true; }; "userClass": { "alias": "class"; "required": false; "isSignal": true; }; "labelClass": { "alias": "labelClass"; "required": false; "isSignal": true; }; "controlClass": { "alias": "controlClass"; "required": false; "isSignal": true; }; "inputClass": { "alias": "inputClass"; "required": false; "isSignal": true; }; "errorClass": { "alias": "errorClass"; "required": false; "isSignal": true; }; "hintClass": { "alias": "hintClass"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
69
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<EgFormAutocomplete, "eg-form-autocomplete", never, { "label": { "alias": "label"; "required": false; "isSignal": true; }; "controlId": { "alias": "controlId"; "required": false; "isSignal": true; }; "controlName": { "alias": "controlName"; "required": false; "isSignal": true; }; "error": { "alias": "error"; "required": false; "isSignal": true; }; "autoError": { "alias": "autoError"; "required": false; "isSignal": true; }; "hint": { "alias": "hint"; "required": false; "isSignal": true; }; "ariaDescribedBy": { "alias": "aria-describedby"; "required": false; "isSignal": true; }; "id": { "alias": "id"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "emptyText": { "alias": "emptyText"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "required": { "alias": "required"; "required": false; "isSignal": true; }; "options": { "alias": "options"; "required": false; "isSignal": true; }; "userClass": { "alias": "class"; "required": false; "isSignal": true; }; "labelClass": { "alias": "labelClass"; "required": false; "isSignal": true; }; "controlClass": { "alias": "controlClass"; "required": false; "isSignal": true; }; "inputClass": { "alias": "inputClass"; "required": false; "isSignal": true; }; "errorClass": { "alias": "errorClass"; "required": false; "isSignal": true; }; "hintClass": { "alias": "hintClass"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
64
70
  }
65
71
 
66
72
  interface EgFormAutocompleteConfig {
@@ -176,8 +176,10 @@ export class FruitSingleComponent {
176
176
  ## Accessibility notes
177
177
 
178
178
  - Label `for` ↔ chip-search-input `id` wiring is automatic via `effectiveId` — always pass a `label`.
179
- - The chip search input exposes no `aria-describedby`, so error/hint ids render without an input-level link.
180
- - The required `*` is visual; pair with `Validators.required` so assistive tech and validation agree.
179
+ - Multiple mode connects the native chip input to the displayed error (invalid + touched/dirty/submitted), otherwise the hint. Single mode connects both the native trigger button and portaled search input. Correction/reset restores hint descriptions and obsolete message IDs disappear.
180
+ - Optional `[aria-describedby]` (`string | null`, default `null`) adds consumer-owned IDs before the current message ID, with whitespace normalization and deduplication. Keep external elements mounted and reserve `<effectiveId>-error` / `-hint` for the wrapper. Use the input binding rather than `[attr.aria-describedby]` on the wrapper host.
181
+ - Single mode uses `<effectiveId>` for the labeled trigger and `<effectiveId>-search` for its search input, labeled by `<effectiveId>-label`; this avoids duplicate IDs while open. The search filters options, so required/invalid state belongs to the selected-value trigger. Multiple mode exposes that state on its chip input.
182
+ - `required` supplies the visual `*` and `aria-required` on the selected-value control; configure `Validators.required` independently. `HlmError` is not a live region; rendered DOM/focus tests do not establish screen-reader announcement behavior.
181
183
 
182
184
  ## Theming / CSS variables
183
185