@egose/shadcn-theme-ng 0.5.3 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/README.md +131 -12
  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 +174 -0
  10. package/data-table/fesm2022/data-table.mjs +713 -0
  11. package/data-table/package.json +25 -0
  12. package/data-table/types/data-table.d.ts +495 -0
  13. package/date-picker/README.md +41 -9
  14. package/date-picker/fesm2022/date-picker.mjs +265 -59
  15. package/date-picker/types/date-picker.d.ts +58 -27
  16. package/dropdown-menu/README.md +2 -0
  17. package/form-autocomplete/README.md +4 -2
  18. package/form-autocomplete/fesm2022/form-autocomplete.mjs +19 -6
  19. package/form-autocomplete/types/form-autocomplete.d.ts +9 -3
  20. package/form-combobox/README.md +4 -2
  21. package/form-combobox/fesm2022/form-combobox.mjs +23 -6
  22. package/form-combobox/types/form-combobox.d.ts +10 -3
  23. package/form-input-otp/README.md +7 -5
  24. package/form-input-otp/fesm2022/form-input-otp.mjs +20 -8
  25. package/form-input-otp/types/form-input-otp.d.ts +9 -3
  26. package/form-searchable-multiselect/README.md +15 -1
  27. package/form-searchable-multiselect/fesm2022/form-searchable-multiselect.mjs +15 -3
  28. package/form-searchable-multiselect/types/form-searchable-multiselect.d.ts +9 -1
  29. package/form-slider/README.md +3 -1
  30. package/form-slider/fesm2022/form-slider.mjs +19 -6
  31. package/form-slider/types/form-slider.d.ts +9 -3
  32. package/form-text-input/fesm2022/form-text-input.mjs +1 -1
  33. package/form-textarea/fesm2022/form-textarea.mjs +1 -1
  34. package/form-toggle/README.md +22 -18
  35. package/form-toggle/fesm2022/form-toggle.mjs +22 -9
  36. package/form-toggle/types/form-toggle.d.ts +6 -2
  37. package/input/README.md +7 -12
  38. package/input/fesm2022/input.mjs +7 -4
  39. package/input/types/input.d.ts +3 -1
  40. package/input-otp/README.md +33 -9
  41. package/input-otp/fesm2022/input-otp.mjs +127 -11
  42. package/input-otp/types/input-otp.d.ts +24 -4
  43. package/layout-simple/fesm2022/layout-simple.mjs +1 -1
  44. package/package.json +6 -1
  45. package/pagination/README.md +21 -12
  46. package/pagination/fesm2022/pagination.mjs +73 -65
  47. package/pagination/types/pagination.d.ts +21 -8
  48. package/phone-input/README.md +16 -0
  49. package/phone-input/fesm2022/phone-input.mjs +42 -4
  50. package/phone-input/types/phone-input.d.ts +5 -2
  51. package/searchable-multiselect/README.md +62 -40
  52. package/searchable-multiselect/fesm2022/searchable-multiselect.mjs +59 -32
  53. package/searchable-multiselect/types/searchable-multiselect.d.ts +23 -6
  54. package/slider/fesm2022/slider.mjs +9 -5
  55. package/slider/types/slider.d.ts +6 -2
  56. package/stepper/README.md +39 -19
  57. package/stepper/fesm2022/stepper.mjs +64 -12
  58. package/stepper/types/stepper.d.ts +12 -1
  59. package/table/README.md +2 -0
  60. package/table/fesm2022/table.mjs +12 -8
  61. package/table/types/table.d.ts +7 -2
@@ -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;
@@ -136,6 +136,8 @@ Host exposes `data-state`, `data-side`, and animation classes derived from the t
136
136
 
137
137
  CDK bindings (via `hostDirectives`): `cdkMenuTriggerFor: hlmDropdownMenuTrigger` (the `ng-template`), `cdkMenuTriggerData: hlmDropdownMenuTriggerData`; outputs `cdkMenuOpened: hlmDropdownMenuOpened`, `cdkMenuClosed: hlmDropdownMenuClosed`.
138
138
 
139
+ > Do not nest the trigger inside another `hlmDropdownMenu` element. The outer element acts as a parent `CdkMenu`, turning the trigger into a submenu trigger that ignores menu-stack close events — the panel will no longer dismiss on outside clicks. Keep root triggers as siblings of their `ng-template` (see Anatomy above).
140
+
139
141
  ### `HlmDropdownMenuItem` — `[hlmDropdownMenuItem], hlm-dropdown-menu-item`
140
142
 
141
143
  | Input | Type | Default | Description |
@@ -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/form-field';
6
6
  import { HlmLabel } from '@egose/shadcn-theme-ng/label';
7
7
  import { HlmAutocomplete, HlmAutocompleteContent, HlmAutocompleteEmpty, HlmAutocompleteGroup, HlmAutocompleteInput, HlmAutocompleteItem, HlmAutocompleteList, HlmAutocompleteLabel } from '@egose/shadcn-theme-ng/autocomplete';
8
8
  import { hlm } from '@egose/shadcn-theme-ng/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("w-full", this.userClass()), /* @ts-ignore */
109
122
  ...(ngDevMode ? [{ debugName: "$userClass" }] : /* istanbul ignore next */ []));
110
123
  $labelClass = computed(() => hlm("mb-1", this._shared.labelClass, this._config.labelClass, this.labelClass()), /* @ts-ignore */
@@ -118,7 +131,7 @@ class EgFormAutocomplete {
118
131
  $hintClass = computed(() => hlm("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: "\n @let lbl = label();\n @let cnm = controlName();\n @let hnt = hint();\n @let rqrd = required();\n\n <hlm-form-field>\n @if (lbl) {\n <label hlmLabel [for]=\"effectiveId()\" [class]=\"$labelClass()\"\n >{{ lbl }}\n @if (rqrd) {\n <span class=\"text-red-500\">*</span>\n }\n </label>\n }\n\n <div hlmAutocomplete [formControlName]=\"cnm\" [disabled]=\"effectiveDisabled()\" [class]=\"$controlClass()\">\n <hlm-autocomplete-input [inputId]=\"effectiveId()\" [placeholder]=\"placeholder()\" [class]=\"$inputClass()\" />\n <hlm-autocomplete-content>\n <div hlmAutocompleteList>\n <div hlmAutocompleteGroup>\n @for (option of options(); track option) {\n <hlm-autocomplete-item [value]=\"option\">{{ option }}</hlm-autocomplete-item>\n }\n </div>\n </div>\n <div hlmAutocompleteEmpty>{{ emptyText() }}</div>\n </hlm-autocomplete-content>\n </div>\n\n @if (showError()) {\n <hlm-error [id]=\"errorId()\" [class]=\"$errorClass()\">\n {{ resolvedError() }}\n </hlm-error>\n }\n\n @if (hnt) {\n <hlm-hint [id]=\"hintId()\" [class]=\"$hintClass()\">\n {{ hnt }}\n </hlm-hint>\n }\n </hlm-form-field>\n ", 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]" }] });
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: "\n @let lbl = label();\n @let cnm = controlName();\n @let hnt = hint();\n @let rqrd = required();\n\n <hlm-form-field>\n @if (lbl) {\n <label hlmLabel [for]=\"effectiveId()\" [class]=\"$labelClass()\"\n >{{ lbl }}\n @if (rqrd) {\n <span class=\"text-red-500\">*</span>\n }\n </label>\n }\n\n <div hlmAutocomplete [formControlName]=\"cnm\" [disabled]=\"effectiveDisabled()\" [class]=\"$controlClass()\">\n <hlm-autocomplete-input\n [inputId]=\"effectiveId()\"\n [placeholder]=\"placeholder()\"\n [aria-describedby]=\"describedBy()\"\n [required]=\"rqrd\"\n [class]=\"$inputClass()\"\n />\n <hlm-autocomplete-content>\n <div hlmAutocompleteList>\n <div hlmAutocompleteGroup>\n @for (option of options(); track option) {\n <hlm-autocomplete-item [value]=\"option\">{{ option }}</hlm-autocomplete-item>\n }\n </div>\n </div>\n <div hlmAutocompleteEmpty>{{ emptyText() }}</div>\n </hlm-autocomplete-content>\n </div>\n\n @if (showError()) {\n <hlm-error [id]=\"errorId()\" [class]=\"$errorClass()\">\n {{ resolvedError() }}\n </hlm-error>\n }\n\n @if (hnt) {\n <hlm-hint [id]=\"hintId()\" [class]=\"$hintClass()\">\n {{ hnt }}\n </hlm-hint>\n }\n </hlm-form-field>\n ", 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]" }] });
122
135
  }
123
136
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: EgFormAutocomplete, decorators: [{
124
137
  type: Component,
@@ -144,9 +157,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImpor
144
157
  HlmAutocompleteList,
145
158
  ],
146
159
  providers: [{ provide: ControlContainer, useExisting: FormGroupDirective }],
147
- template: "\n @let lbl = label();\n @let cnm = controlName();\n @let hnt = hint();\n @let rqrd = required();\n\n <hlm-form-field>\n @if (lbl) {\n <label hlmLabel [for]=\"effectiveId()\" [class]=\"$labelClass()\"\n >{{ lbl }}\n @if (rqrd) {\n <span class=\"text-red-500\">*</span>\n }\n </label>\n }\n\n <div hlmAutocomplete [formControlName]=\"cnm\" [disabled]=\"effectiveDisabled()\" [class]=\"$controlClass()\">\n <hlm-autocomplete-input [inputId]=\"effectiveId()\" [placeholder]=\"placeholder()\" [class]=\"$inputClass()\" />\n <hlm-autocomplete-content>\n <div hlmAutocompleteList>\n <div hlmAutocompleteGroup>\n @for (option of options(); track option) {\n <hlm-autocomplete-item [value]=\"option\">{{ option }}</hlm-autocomplete-item>\n }\n </div>\n </div>\n <div hlmAutocompleteEmpty>{{ emptyText() }}</div>\n </hlm-autocomplete-content>\n </div>\n\n @if (showError()) {\n <hlm-error [id]=\"errorId()\" [class]=\"$errorClass()\">\n {{ resolvedError() }}\n </hlm-error>\n }\n\n @if (hnt) {\n <hlm-hint [id]=\"hintId()\" [class]=\"$hintClass()\">\n {{ hnt }}\n </hlm-hint>\n }\n </hlm-form-field>\n ",
160
+ template: "\n @let lbl = label();\n @let cnm = controlName();\n @let hnt = hint();\n @let rqrd = required();\n\n <hlm-form-field>\n @if (lbl) {\n <label hlmLabel [for]=\"effectiveId()\" [class]=\"$labelClass()\"\n >{{ lbl }}\n @if (rqrd) {\n <span class=\"text-red-500\">*</span>\n }\n </label>\n }\n\n <div hlmAutocomplete [formControlName]=\"cnm\" [disabled]=\"effectiveDisabled()\" [class]=\"$controlClass()\">\n <hlm-autocomplete-input\n [inputId]=\"effectiveId()\"\n [placeholder]=\"placeholder()\"\n [aria-describedby]=\"describedBy()\"\n [required]=\"rqrd\"\n [class]=\"$inputClass()\"\n />\n <hlm-autocomplete-content>\n <div hlmAutocompleteList>\n <div hlmAutocompleteGroup>\n @for (option of options(); track option) {\n <hlm-autocomplete-item [value]=\"option\">{{ option }}</hlm-autocomplete-item>\n }\n </div>\n </div>\n <div hlmAutocompleteEmpty>{{ emptyText() }}</div>\n </hlm-autocomplete-content>\n </div>\n\n @if (showError()) {\n <hlm-error [id]=\"errorId()\" [class]=\"$errorClass()\">\n {{ resolvedError() }}\n </hlm-error>\n }\n\n @if (hnt) {\n <hlm-hint [id]=\"hintId()\" [class]=\"$hintClass()\">\n {{ hnt }}\n </hlm-hint>\n }\n </hlm-form-field>\n ",
148
161
  }]
149
- }], 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 }] }] } });
162
+ }], 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 }] }] } });
150
163
 
151
164
  /*
152
165
  * 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
 
@@ -1,12 +1,14 @@
1
1
  import * as i0 from '@angular/core';
2
- import { InjectionToken, inject, viewChild, input, computed, Component } from '@angular/core';
2
+ import { InjectionToken, inject, viewChild, 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/form-field';
6
6
  import { HlmLabel } from '@egose/shadcn-theme-ng/label';
7
7
  import { HlmCombobox, HlmComboboxChip, HlmComboboxChipInput, HlmComboboxChips, HlmComboboxContent, HlmComboboxEmpty, HlmComboboxInput, HlmComboboxItem, HlmComboboxList, HlmComboboxMultiple, HlmComboboxPortal, HlmComboboxTrigger } from '@egose/shadcn-theme-ng/combobox';
8
8
  import { BrnComboboxMultiple, BrnCombobox } from '@spartan-ng/brain/combobox';
9
+ import { BrnFieldControlDescribedBy } from '@spartan-ng/brain/field';
9
10
  import { hlm } from '@egose/shadcn-theme-ng/utils';
11
+ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
10
12
 
11
13
  const EgFormComboboxConfigToken = new InjectionToken('EgFormComboboxConfig');
12
14
  function provideEgFormComboboxConfig(config) {
@@ -26,6 +28,14 @@ class EgFormCombobox {
26
28
  ...(ngDevMode ? [{ debugName: "_picker" }] : /* istanbul ignore next */ []));
27
29
  _single = viewChild((BrnCombobox), /* @ts-ignore */
28
30
  ...(ngDevMode ? [{ debugName: "_single" }] : /* istanbul ignore next */ []));
31
+ _cdr = inject(ChangeDetectorRef);
32
+ _destroyRef = inject(DestroyRef);
33
+ ngOnInit() {
34
+ // Submit/reset may change message visibility without changing the field's value or validity.
35
+ this.formGroupDirective.form.events
36
+ .pipe(takeUntilDestroyed(this._destroyRef))
37
+ .subscribe(() => this._cdr.markForCheck());
38
+ }
29
39
  label = input(undefined, /* @ts-ignore */
30
40
  ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
31
41
  controlId = input(undefined, /* @ts-ignore */
@@ -43,6 +53,8 @@ class EgFormCombobox {
43
53
  ...(ngDevMode ? [{ debugName: "autoError" }] : /* istanbul ignore next */ []));
44
54
  hint = input(undefined, /* @ts-ignore */
45
55
  ...(ngDevMode ? [{ debugName: "hint" }] : /* istanbul ignore next */ []));
56
+ /** Consumer-owned description IDs, merged with the current error and hint IDs in both modes. */
57
+ ariaDescribedBy = input(null, { ...(ngDevMode ? { debugName: "ariaDescribedBy" } : /* istanbul ignore next */ {}), alias: 'aria-describedby' });
46
58
  id = input(undefined, /* @ts-ignore */
47
59
  ...(ngDevMode ? [{ debugName: "id" }] : /* istanbul ignore next */ []));
48
60
  placeholder = input('Pick…', /* @ts-ignore */
@@ -64,6 +76,8 @@ class EgFormCombobox {
64
76
  ...(ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []));
65
77
  effectiveId = computed(() => this.controlId() || this.id() || this.generatedId, /* @ts-ignore */
66
78
  ...(ngDevMode ? [{ debugName: "effectiveId" }] : /* istanbul ignore next */ []));
79
+ labelId = computed(() => `${this.effectiveId()}-label`, /* @ts-ignore */
80
+ ...(ngDevMode ? [{ debugName: "labelId" }] : /* istanbul ignore next */ []));
67
81
  errorId = computed(() => `${this.effectiveId()}-error`, /* @ts-ignore */
68
82
  ...(ngDevMode ? [{ debugName: "errorId" }] : /* istanbul ignore next */ []));
69
83
  hintId = computed(() => `${this.effectiveId()}-hint`, /* @ts-ignore */
@@ -108,6 +122,10 @@ class EgFormCombobox {
108
122
  effectiveDisabled() {
109
123
  return this.disabled() || !!this.formGroupDirective.form.get(this.controlName())?.disabled;
110
124
  }
125
+ describedBy() {
126
+ const ids = [this.ariaDescribedBy(), this.showError() ? this.errorId() : this.hint() ? this.hintId() : null];
127
+ return [...new Set(ids.filter(Boolean).join(' ').split(/\s+/).filter(Boolean))].join(' ') || null;
128
+ }
111
129
  // Styling
112
130
  userClass = input('', { ...(ngDevMode ? { debugName: "userClass" } : /* istanbul ignore next */ {}), alias: 'class' });
113
131
  labelClass = input('', /* @ts-ignore */
@@ -121,8 +139,6 @@ class EgFormCombobox {
121
139
  hintClass = input('', /* @ts-ignore */
122
140
  ...(ngDevMode ? [{ debugName: "hintClass" }] : /* istanbul ignore next */ []));
123
141
  // Computed classes (library base < global config < per-instance).
124
- // NOTE: the chip search input exposes no aria-describedby, so error/hint
125
- // ids render without an input-level describedby link.
126
142
  $userClass = computed(() => hlm("w-full", this.userClass()), /* @ts-ignore */
127
143
  ...(ngDevMode ? [{ debugName: "$userClass" }] : /* istanbul ignore next */ []));
128
144
  $labelClass = computed(() => hlm("mb-1", this._shared.labelClass, this._config.labelClass, this.labelClass()), /* @ts-ignore */
@@ -136,7 +152,7 @@ class EgFormCombobox {
136
152
  $hintClass = computed(() => hlm("mt-0", this._shared.hintClass, this._config.hintClass, this.hintClass()), /* @ts-ignore */
137
153
  ...(ngDevMode ? [{ debugName: "$hintClass" }] : /* istanbul ignore next */ []));
138
154
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: EgFormCombobox, deps: [], target: i0.ɵɵFactoryTarget.Component });
139
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.3", type: EgFormCombobox, isStandalone: true, selector: "eg-form-combobox", 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 }, mode: { classPropertyName: "mode", publicName: "mode", 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 }, chipsClass: { classPropertyName: "chipsClass", publicName: "chipsClass", 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 }], viewQueries: [{ propertyName: "_picker", first: true, predicate: (BrnComboboxMultiple), descendants: true, isSignal: true }, { propertyName: "_single", first: true, predicate: (BrnCombobox), descendants: true, isSignal: true }], ngImport: i0, template: "\n @let lbl = label();\n @let cnm = controlName();\n @let hnt = hint();\n @let rqrd = required();\n\n <hlm-form-field>\n @if (lbl) {\n <label hlmLabel [for]=\"effectiveId()\" [class]=\"$labelClass()\"\n >{{ lbl }}\n @if (rqrd) {\n <span class=\"text-red-500\">*</span>\n }\n </label>\n }\n\n @if (mode() === 'single') {\n <hlm-combobox [formControlName]=\"cnm\" [disabled]=\"effectiveDisabled()\">\n <hlm-combobox-trigger [buttonId]=\"effectiveId()\" [class]=\"$controlClass()\">\n {{ singleDisplay() ?? placeholder() }}\n </hlm-combobox-trigger>\n <hlm-combobox-content *hlmComboboxPortal>\n <hlm-combobox-input [inputId]=\"effectiveId()\" [placeholder]=\"placeholder()\" />\n <div hlmComboboxList>\n @for (option of options(); track option) {\n <hlm-combobox-item [value]=\"option\">{{ option }}</hlm-combobox-item>\n }\n </div>\n <div hlmComboboxEmpty>{{ emptyText() }}</div>\n </hlm-combobox-content>\n </hlm-combobox>\n } @else {\n <hlm-combobox-multiple [formControlName]=\"cnm\" [disabled]=\"effectiveDisabled()\" [class]=\"$controlClass()\">\n <hlm-combobox-chips [class]=\"$chipsClass()\">\n @for (v of selectedValues(); track v) {\n <hlm-combobox-chip [value]=\"v\">{{ v }}</hlm-combobox-chip>\n }\n <input hlmComboboxChipInput [id]=\"effectiveId()\" [placeholder]=\"placeholder()\" />\n </hlm-combobox-chips>\n <hlm-combobox-content *hlmComboboxPortal>\n <div hlmComboboxList>\n @for (option of options(); track option) {\n <hlm-combobox-item [value]=\"option\">{{ option }}</hlm-combobox-item>\n }\n </div>\n <div hlmComboboxEmpty>{{ emptyText() }}</div>\n </hlm-combobox-content>\n </hlm-combobox-multiple>\n }\n\n @if (showError()) {\n <hlm-error [id]=\"errorId()\" [class]=\"$errorClass()\">\n {{ resolvedError() }}\n </hlm-error>\n }\n\n @if (hnt) {\n <hlm-hint [id]=\"hintId()\" [class]=\"$hintClass()\">\n {{ hnt }}\n </hlm-hint>\n }\n </hlm-form-field>\n ", 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: HlmCombobox, selector: "[hlmCombobox],hlm-combobox" }, { kind: "component", type: HlmComboboxChip, selector: "hlm-combobox-chip", inputs: ["showRemove"] }, { kind: "directive", type: HlmComboboxChipInput, selector: "input[hlmComboboxChipInput]" }, { kind: "directive", type: HlmComboboxChips, selector: "[hlmComboboxChips],hlm-combobox-chips", inputs: ["forceInvalid"] }, { kind: "directive", type: HlmComboboxContent, selector: "[hlmComboboxContent],hlm-combobox-content" }, { kind: "directive", type: HlmComboboxEmpty, selector: "[hlmComboboxEmpty],hlm-combobox-empty" }, { kind: "component", type: HlmComboboxInput, selector: "hlm-combobox-input", inputs: ["inputId", "placeholder", "showTrigger", "showClear", "forceInvalid", "aria-invalid"] }, { kind: "component", type: HlmComboboxItem, selector: "hlm-combobox-item" }, { kind: "directive", type: HlmComboboxList, selector: "[hlmComboboxList]" }, { kind: "directive", type: HlmComboboxMultiple, selector: "[hlmComboboxMultiple],hlm-combobox-multiple" }, { kind: "directive", type: HlmComboboxPortal, selector: "[hlmComboboxPortal]" }, { kind: "component", type: HlmComboboxTrigger, selector: "hlm-combobox-trigger", inputs: ["class", "buttonId", "variant", "forceInvalid"] }] });
155
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.3", type: EgFormCombobox, isStandalone: true, selector: "eg-form-combobox", 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 }, mode: { classPropertyName: "mode", publicName: "mode", 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 }, chipsClass: { classPropertyName: "chipsClass", publicName: "chipsClass", 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 }], viewQueries: [{ propertyName: "_picker", first: true, predicate: (BrnComboboxMultiple), descendants: true, isSignal: true }, { propertyName: "_single", first: true, predicate: (BrnCombobox), descendants: true, isSignal: true }], ngImport: i0, template: "\n @let lbl = label();\n @let cnm = controlName();\n @let hnt = hint();\n @let rqrd = required();\n\n <hlm-form-field>\n @if (lbl) {\n <label hlmLabel [id]=\"labelId()\" [for]=\"effectiveId()\" [class]=\"$labelClass()\"\n >{{ lbl }}\n @if (rqrd) {\n <span class=\"text-red-500\">*</span>\n }\n </label>\n }\n\n @if (mode() === 'single') {\n <hlm-combobox [formControlName]=\"cnm\" [disabled]=\"effectiveDisabled()\">\n <hlm-combobox-trigger\n [buttonId]=\"effectiveId()\"\n [aria-describedby]=\"describedBy()\"\n [required]=\"rqrd\"\n [class]=\"$controlClass()\"\n >\n {{ singleDisplay() ?? placeholder() }}\n </hlm-combobox-trigger>\n <hlm-combobox-content *hlmComboboxPortal>\n <hlm-combobox-input\n [inputId]=\"effectiveId() + '-search'\"\n [aria-labelledby]=\"lbl ? labelId() : null\"\n [aria-describedby]=\"describedBy()\"\n [placeholder]=\"placeholder()\"\n />\n <div hlmComboboxList>\n @for (option of options(); track option) {\n <hlm-combobox-item [value]=\"option\">{{ option }}</hlm-combobox-item>\n }\n </div>\n <div hlmComboboxEmpty>{{ emptyText() }}</div>\n </hlm-combobox-content>\n </hlm-combobox>\n } @else {\n <hlm-combobox-multiple [formControlName]=\"cnm\" [disabled]=\"effectiveDisabled()\" [class]=\"$controlClass()\">\n <hlm-combobox-chips [class]=\"$chipsClass()\">\n @for (v of selectedValues(); track v) {\n <hlm-combobox-chip [value]=\"v\">{{ v }}</hlm-combobox-chip>\n }\n <input\n hlmComboboxChipInput\n [id]=\"effectiveId()\"\n [placeholder]=\"placeholder()\"\n brnFieldControlDescribedBy\n [aria-describedby]=\"describedBy()\"\n [attr.aria-required]=\"rqrd || null\"\n />\n </hlm-combobox-chips>\n <hlm-combobox-content *hlmComboboxPortal>\n <div hlmComboboxList>\n @for (option of options(); track option) {\n <hlm-combobox-item [value]=\"option\">{{ option }}</hlm-combobox-item>\n }\n </div>\n <div hlmComboboxEmpty>{{ emptyText() }}</div>\n </hlm-combobox-content>\n </hlm-combobox-multiple>\n }\n\n @if (showError()) {\n <hlm-error [id]=\"errorId()\" [class]=\"$errorClass()\">\n {{ resolvedError() }}\n </hlm-error>\n }\n\n @if (hnt) {\n <hlm-hint [id]=\"hintId()\" [class]=\"$hintClass()\">\n {{ hnt }}\n </hlm-hint>\n }\n </hlm-form-field>\n ", 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: HlmCombobox, selector: "[hlmCombobox],hlm-combobox" }, { kind: "component", type: HlmComboboxChip, selector: "hlm-combobox-chip", inputs: ["showRemove"] }, { kind: "directive", type: HlmComboboxChipInput, selector: "input[hlmComboboxChipInput]" }, { kind: "directive", type: HlmComboboxChips, selector: "[hlmComboboxChips],hlm-combobox-chips", inputs: ["forceInvalid"] }, { kind: "directive", type: HlmComboboxContent, selector: "[hlmComboboxContent],hlm-combobox-content" }, { kind: "directive", type: HlmComboboxEmpty, selector: "[hlmComboboxEmpty],hlm-combobox-empty" }, { kind: "component", type: HlmComboboxInput, selector: "hlm-combobox-input", inputs: ["inputId", "placeholder", "aria-describedby", "aria-labelledby", "showTrigger", "showClear", "forceInvalid", "aria-invalid"] }, { kind: "component", type: HlmComboboxItem, selector: "hlm-combobox-item" }, { kind: "directive", type: HlmComboboxList, selector: "[hlmComboboxList]" }, { kind: "directive", type: HlmComboboxMultiple, selector: "[hlmComboboxMultiple],hlm-combobox-multiple" }, { kind: "directive", type: HlmComboboxPortal, selector: "[hlmComboboxPortal]" }, { kind: "component", type: HlmComboboxTrigger, selector: "hlm-combobox-trigger", inputs: ["class", "buttonId", "aria-describedby", "required", "variant", "forceInvalid"] }, { kind: "directive", type: BrnFieldControlDescribedBy, selector: "[brnFieldControlDescribedBy]", inputs: ["aria-describedby"] }] });
140
156
  }
141
157
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: EgFormCombobox, decorators: [{
142
158
  type: Component,
@@ -164,11 +180,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImpor
164
180
  HlmComboboxMultiple,
165
181
  HlmComboboxPortal,
166
182
  HlmComboboxTrigger,
183
+ BrnFieldControlDescribedBy,
167
184
  ],
168
185
  providers: [{ provide: ControlContainer, useExisting: FormGroupDirective }],
169
- template: "\n @let lbl = label();\n @let cnm = controlName();\n @let hnt = hint();\n @let rqrd = required();\n\n <hlm-form-field>\n @if (lbl) {\n <label hlmLabel [for]=\"effectiveId()\" [class]=\"$labelClass()\"\n >{{ lbl }}\n @if (rqrd) {\n <span class=\"text-red-500\">*</span>\n }\n </label>\n }\n\n @if (mode() === 'single') {\n <hlm-combobox [formControlName]=\"cnm\" [disabled]=\"effectiveDisabled()\">\n <hlm-combobox-trigger [buttonId]=\"effectiveId()\" [class]=\"$controlClass()\">\n {{ singleDisplay() ?? placeholder() }}\n </hlm-combobox-trigger>\n <hlm-combobox-content *hlmComboboxPortal>\n <hlm-combobox-input [inputId]=\"effectiveId()\" [placeholder]=\"placeholder()\" />\n <div hlmComboboxList>\n @for (option of options(); track option) {\n <hlm-combobox-item [value]=\"option\">{{ option }}</hlm-combobox-item>\n }\n </div>\n <div hlmComboboxEmpty>{{ emptyText() }}</div>\n </hlm-combobox-content>\n </hlm-combobox>\n } @else {\n <hlm-combobox-multiple [formControlName]=\"cnm\" [disabled]=\"effectiveDisabled()\" [class]=\"$controlClass()\">\n <hlm-combobox-chips [class]=\"$chipsClass()\">\n @for (v of selectedValues(); track v) {\n <hlm-combobox-chip [value]=\"v\">{{ v }}</hlm-combobox-chip>\n }\n <input hlmComboboxChipInput [id]=\"effectiveId()\" [placeholder]=\"placeholder()\" />\n </hlm-combobox-chips>\n <hlm-combobox-content *hlmComboboxPortal>\n <div hlmComboboxList>\n @for (option of options(); track option) {\n <hlm-combobox-item [value]=\"option\">{{ option }}</hlm-combobox-item>\n }\n </div>\n <div hlmComboboxEmpty>{{ emptyText() }}</div>\n </hlm-combobox-content>\n </hlm-combobox-multiple>\n }\n\n @if (showError()) {\n <hlm-error [id]=\"errorId()\" [class]=\"$errorClass()\">\n {{ resolvedError() }}\n </hlm-error>\n }\n\n @if (hnt) {\n <hlm-hint [id]=\"hintId()\" [class]=\"$hintClass()\">\n {{ hnt }}\n </hlm-hint>\n }\n </hlm-form-field>\n ",
186
+ template: "\n @let lbl = label();\n @let cnm = controlName();\n @let hnt = hint();\n @let rqrd = required();\n\n <hlm-form-field>\n @if (lbl) {\n <label hlmLabel [id]=\"labelId()\" [for]=\"effectiveId()\" [class]=\"$labelClass()\"\n >{{ lbl }}\n @if (rqrd) {\n <span class=\"text-red-500\">*</span>\n }\n </label>\n }\n\n @if (mode() === 'single') {\n <hlm-combobox [formControlName]=\"cnm\" [disabled]=\"effectiveDisabled()\">\n <hlm-combobox-trigger\n [buttonId]=\"effectiveId()\"\n [aria-describedby]=\"describedBy()\"\n [required]=\"rqrd\"\n [class]=\"$controlClass()\"\n >\n {{ singleDisplay() ?? placeholder() }}\n </hlm-combobox-trigger>\n <hlm-combobox-content *hlmComboboxPortal>\n <hlm-combobox-input\n [inputId]=\"effectiveId() + '-search'\"\n [aria-labelledby]=\"lbl ? labelId() : null\"\n [aria-describedby]=\"describedBy()\"\n [placeholder]=\"placeholder()\"\n />\n <div hlmComboboxList>\n @for (option of options(); track option) {\n <hlm-combobox-item [value]=\"option\">{{ option }}</hlm-combobox-item>\n }\n </div>\n <div hlmComboboxEmpty>{{ emptyText() }}</div>\n </hlm-combobox-content>\n </hlm-combobox>\n } @else {\n <hlm-combobox-multiple [formControlName]=\"cnm\" [disabled]=\"effectiveDisabled()\" [class]=\"$controlClass()\">\n <hlm-combobox-chips [class]=\"$chipsClass()\">\n @for (v of selectedValues(); track v) {\n <hlm-combobox-chip [value]=\"v\">{{ v }}</hlm-combobox-chip>\n }\n <input\n hlmComboboxChipInput\n [id]=\"effectiveId()\"\n [placeholder]=\"placeholder()\"\n brnFieldControlDescribedBy\n [aria-describedby]=\"describedBy()\"\n [attr.aria-required]=\"rqrd || null\"\n />\n </hlm-combobox-chips>\n <hlm-combobox-content *hlmComboboxPortal>\n <div hlmComboboxList>\n @for (option of options(); track option) {\n <hlm-combobox-item [value]=\"option\">{{ option }}</hlm-combobox-item>\n }\n </div>\n <div hlmComboboxEmpty>{{ emptyText() }}</div>\n </hlm-combobox-content>\n </hlm-combobox-multiple>\n }\n\n @if (showError()) {\n <hlm-error [id]=\"errorId()\" [class]=\"$errorClass()\">\n {{ resolvedError() }}\n </hlm-error>\n }\n\n @if (hnt) {\n <hlm-hint [id]=\"hintId()\" [class]=\"$hintClass()\">\n {{ hnt }}\n </hlm-hint>\n }\n </hlm-form-field>\n ",
170
187
  }]
171
- }], propDecorators: { _picker: [{ type: i0.ViewChild, args: [i0.forwardRef(() => BrnComboboxMultiple), { isSignal: true }] }], _single: [{ type: i0.ViewChild, args: [i0.forwardRef(() => BrnCombobox), { isSignal: true }] }], 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 }] }], mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", 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 }] }], chipsClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "chipsClass", required: false }] }], errorClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorClass", required: false }] }], hintClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "hintClass", required: false }] }] } });
188
+ }], propDecorators: { _picker: [{ type: i0.ViewChild, args: [i0.forwardRef(() => BrnComboboxMultiple), { isSignal: true }] }], _single: [{ type: i0.ViewChild, args: [i0.forwardRef(() => BrnCombobox), { isSignal: true }] }], 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 }] }], mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", 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 }] }], chipsClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "chipsClass", required: false }] }], errorClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorClass", required: false }] }], hintClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "hintClass", required: false }] }] } });
172
189
 
173
190
  /*
174
191
  * Public API Surface of form-combobox