@egose/shadcn-theme-ng-tw 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 +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 +174 -0
  10. package/data-table/fesm2022/data-table.mjs +1485 -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 +33 -8
  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 +65 -14
  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 +28 -14
  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 +23 -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 +21 -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 +25 -8
  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 +83 -75
  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 +97 -36
  53. package/searchable-multiselect/types/searchable-multiselect.d.ts +23 -6
  54. package/slider/fesm2022/slider.mjs +15 -5
  55. package/slider/types/slider.d.ts +6 -2
  56. package/stepper/README.md +39 -19
  57. package/stepper/fesm2022/stepper.mjs +71 -17
  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
@@ -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-tw/form-field';
6
6
  import { HlmLabel } from '@egose/shadcn-theme-ng-tw/label';
7
7
  import { HlmCombobox, HlmComboboxChip, HlmComboboxChipInput, HlmComboboxChips, HlmComboboxContent, HlmComboboxEmpty, HlmComboboxInput, HlmComboboxItem, HlmComboboxList, HlmComboboxMultiple, HlmComboboxPortal, HlmComboboxTrigger } from '@egose/shadcn-theme-ng-tw/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-tw/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('tw:w-full', this.userClass()), /* @ts-ignore */
127
143
  ...(ngDevMode ? [{ debugName: "$userClass" }] : /* istanbul ignore next */ []));
128
144
  $labelClass = computed(() => hlm('tw:mb-1', this._shared.labelClass, this._config.labelClass, this.labelClass()), /* @ts-ignore */
@@ -136,7 +152,7 @@ class EgFormCombobox {
136
152
  $hintClass = computed(() => hlm('tw: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: `
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: `
140
156
  @let lbl = label();
141
157
  @let cnm = controlName();
142
158
  @let hnt = hint();
@@ -144,7 +160,7 @@ class EgFormCombobox {
144
160
 
145
161
  <hlm-form-field>
146
162
  @if (lbl) {
147
- <label hlmLabel [for]="effectiveId()" [class]="$labelClass()"
163
+ <label hlmLabel [id]="labelId()" [for]="effectiveId()" [class]="$labelClass()"
148
164
  >{{ lbl }}
149
165
  @if (rqrd) {
150
166
  <span class="tw:text-red-500">*</span>
@@ -154,11 +170,21 @@ class EgFormCombobox {
154
170
 
155
171
  @if (mode() === 'single') {
156
172
  <hlm-combobox [formControlName]="cnm" [disabled]="effectiveDisabled()">
157
- <hlm-combobox-trigger [buttonId]="effectiveId()" [class]="$controlClass()">
173
+ <hlm-combobox-trigger
174
+ [buttonId]="effectiveId()"
175
+ [aria-describedby]="describedBy()"
176
+ [required]="rqrd"
177
+ [class]="$controlClass()"
178
+ >
158
179
  {{ singleDisplay() ?? placeholder() }}
159
180
  </hlm-combobox-trigger>
160
181
  <hlm-combobox-content *hlmComboboxPortal>
161
- <hlm-combobox-input [inputId]="effectiveId()" [placeholder]="placeholder()" />
182
+ <hlm-combobox-input
183
+ [inputId]="effectiveId() + '-search'"
184
+ [aria-labelledby]="lbl ? labelId() : null"
185
+ [aria-describedby]="describedBy()"
186
+ [placeholder]="placeholder()"
187
+ />
162
188
  <div hlmComboboxList>
163
189
  @for (option of options(); track option) {
164
190
  <hlm-combobox-item [value]="option">{{ option }}</hlm-combobox-item>
@@ -173,7 +199,14 @@ class EgFormCombobox {
173
199
  @for (v of selectedValues(); track v) {
174
200
  <hlm-combobox-chip [value]="v">{{ v }}</hlm-combobox-chip>
175
201
  }
176
- <input hlmComboboxChipInput [id]="effectiveId()" [placeholder]="placeholder()" />
202
+ <input
203
+ hlmComboboxChipInput
204
+ [id]="effectiveId()"
205
+ [placeholder]="placeholder()"
206
+ brnFieldControlDescribedBy
207
+ [aria-describedby]="describedBy()"
208
+ [attr.aria-required]="rqrd || null"
209
+ />
177
210
  </hlm-combobox-chips>
178
211
  <hlm-combobox-content *hlmComboboxPortal>
179
212
  <div hlmComboboxList>
@@ -198,7 +231,7 @@ class EgFormCombobox {
198
231
  </hlm-hint>
199
232
  }
200
233
  </hlm-form-field>
201
- `, 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"] }] });
234
+ `, 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"] }] });
202
235
  }
203
236
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: EgFormCombobox, decorators: [{
204
237
  type: Component,
@@ -226,6 +259,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImpor
226
259
  HlmComboboxMultiple,
227
260
  HlmComboboxPortal,
228
261
  HlmComboboxTrigger,
262
+ BrnFieldControlDescribedBy,
229
263
  ],
230
264
  providers: [{ provide: ControlContainer, useExisting: FormGroupDirective }],
231
265
  template: `
@@ -236,7 +270,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImpor
236
270
 
237
271
  <hlm-form-field>
238
272
  @if (lbl) {
239
- <label hlmLabel [for]="effectiveId()" [class]="$labelClass()"
273
+ <label hlmLabel [id]="labelId()" [for]="effectiveId()" [class]="$labelClass()"
240
274
  >{{ lbl }}
241
275
  @if (rqrd) {
242
276
  <span class="tw:text-red-500">*</span>
@@ -246,11 +280,21 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImpor
246
280
 
247
281
  @if (mode() === 'single') {
248
282
  <hlm-combobox [formControlName]="cnm" [disabled]="effectiveDisabled()">
249
- <hlm-combobox-trigger [buttonId]="effectiveId()" [class]="$controlClass()">
283
+ <hlm-combobox-trigger
284
+ [buttonId]="effectiveId()"
285
+ [aria-describedby]="describedBy()"
286
+ [required]="rqrd"
287
+ [class]="$controlClass()"
288
+ >
250
289
  {{ singleDisplay() ?? placeholder() }}
251
290
  </hlm-combobox-trigger>
252
291
  <hlm-combobox-content *hlmComboboxPortal>
253
- <hlm-combobox-input [inputId]="effectiveId()" [placeholder]="placeholder()" />
292
+ <hlm-combobox-input
293
+ [inputId]="effectiveId() + '-search'"
294
+ [aria-labelledby]="lbl ? labelId() : null"
295
+ [aria-describedby]="describedBy()"
296
+ [placeholder]="placeholder()"
297
+ />
254
298
  <div hlmComboboxList>
255
299
  @for (option of options(); track option) {
256
300
  <hlm-combobox-item [value]="option">{{ option }}</hlm-combobox-item>
@@ -265,7 +309,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImpor
265
309
  @for (v of selectedValues(); track v) {
266
310
  <hlm-combobox-chip [value]="v">{{ v }}</hlm-combobox-chip>
267
311
  }
268
- <input hlmComboboxChipInput [id]="effectiveId()" [placeholder]="placeholder()" />
312
+ <input
313
+ hlmComboboxChipInput
314
+ [id]="effectiveId()"
315
+ [placeholder]="placeholder()"
316
+ brnFieldControlDescribedBy
317
+ [aria-describedby]="describedBy()"
318
+ [attr.aria-required]="rqrd || null"
319
+ />
269
320
  </hlm-combobox-chips>
270
321
  <hlm-combobox-content *hlmComboboxPortal>
271
322
  <div hlmComboboxList>
@@ -292,7 +343,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImpor
292
343
  </hlm-form-field>
293
344
  `,
294
345
  }]
295
- }], 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 }] }] } });
346
+ }], 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 }] }] } });
296
347
 
297
348
  /*
298
349
  * Public API Surface of form-combobox
@@ -1,8 +1,8 @@
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 EgFormCombobox {
5
+ declare class EgFormCombobox implements OnInit {
6
6
  private readonly formGroupDirective;
7
7
  private readonly _errorMessages;
8
8
  private readonly generatedId;
@@ -10,6 +10,9 @@ declare class EgFormCombobox {
10
10
  private readonly _shared;
11
11
  private readonly _picker;
12
12
  private readonly _single;
13
+ private readonly _cdr;
14
+ private readonly _destroyRef;
15
+ ngOnInit(): void;
13
16
  label: _angular_core.InputSignal<string | undefined>;
14
17
  controlId: _angular_core.InputSignal<string | undefined>;
15
18
  controlName: _angular_core.InputSignal<string>;
@@ -21,6 +24,8 @@ declare class EgFormCombobox {
21
24
  */
22
25
  autoError: _angular_core.InputSignal<boolean>;
23
26
  hint: _angular_core.InputSignal<string | undefined>;
27
+ /** Consumer-owned description IDs, merged with the current error and hint IDs in both modes. */
28
+ ariaDescribedBy: _angular_core.InputSignal<string | null>;
24
29
  id: _angular_core.InputSignal<string | undefined>;
25
30
  placeholder: _angular_core.InputSignal<string>;
26
31
  emptyText: _angular_core.InputSignal<string>;
@@ -34,6 +39,7 @@ declare class EgFormCombobox {
34
39
  mode: _angular_core.InputSignal<"single" | "multiple">;
35
40
  options: _angular_core.InputSignal<string[]>;
36
41
  readonly effectiveId: _angular_core.Signal<string>;
42
+ readonly labelId: _angular_core.Signal<string>;
37
43
  readonly errorId: _angular_core.Signal<string>;
38
44
  readonly hintId: _angular_core.Signal<string>;
39
45
  /**
@@ -59,6 +65,7 @@ declare class EgFormCombobox {
59
65
  * wrapper-toggle + `control.disable()` cannot clobber CVA state.
60
66
  */
61
67
  effectiveDisabled(): boolean;
68
+ protected describedBy(): string | null;
62
69
  userClass: _angular_core.InputSignal<ClassValue>;
63
70
  labelClass: _angular_core.InputSignal<string>;
64
71
  controlClass: _angular_core.InputSignal<string>;
@@ -72,7 +79,7 @@ declare class EgFormCombobox {
72
79
  $errorClass: _angular_core.Signal<string>;
73
80
  $hintClass: _angular_core.Signal<string>;
74
81
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<EgFormCombobox, never>;
75
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<EgFormCombobox, "eg-form-combobox", 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; }; "mode": { "alias": "mode"; "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; }; "chipsClass": { "alias": "chipsClass"; "required": false; "isSignal": true; }; "errorClass": { "alias": "errorClass"; "required": false; "isSignal": true; }; "hintClass": { "alias": "hintClass"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
82
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<EgFormCombobox, "eg-form-combobox", 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; }; "mode": { "alias": "mode"; "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; }; "chipsClass": { "alias": "chipsClass"; "required": false; "isSignal": true; }; "errorClass": { "alias": "errorClass"; "required": false; "isSignal": true; }; "hintClass": { "alias": "hintClass"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
76
83
  }
77
84
 
78
85
  interface EgFormComboboxConfig {
@@ -1,8 +1,8 @@
1
1
  # Form Input OTP (`@egose/shadcn-theme-ng/form-input-otp`)
2
2
 
3
- A ready-made reactive-form one-time-code field: label + `brn-input-otp` slots + validation error/hint display in one tag. The model is a plain `string`.
3
+ A ready-made reactive-form one-time-code field: label + `hlm-input-otp` slots + validation error/hint display in one tag. The model is a plain `string`.
4
4
 
5
- The Angular implementation is a standalone wrapper: it renders `HlmFormField` / `HlmError` / `HlmHint` from `@egose/shadcn-theme-ng/form-field`, `HlmLabel`, and `BrnInputOtp` + `HlmInputOtp*` parts from `@egose/shadcn-theme-ng/input-otp` and `@spartan-ng/brain/input-otp`. The OTP input is bound with `[formControlName]="controlName()"` and renders one `hlm-input-otp-slot` per `length` unit in a single group. It must live inside a `FormGroupDirective` (`[formGroup]` parent); ids come from `HlmFormIdGenerator` unless overridden.
5
+ The Angular implementation is a standalone wrapper: it renders `HlmFormField` / `HlmError` / `HlmHint` from `@egose/shadcn-theme-ng/form-field`, `HlmLabel`, and `HlmInputOtpControl` + `HlmInputOtp*` parts from `@egose/shadcn-theme-ng/input-otp`. The adapter inherits `BrnInputOtp` behavior. The OTP input is bound with `[formControlName]="controlName()"` and renders one `hlm-input-otp-slot` per `length` unit in a single group. It must live inside a `FormGroupDirective` (`[formGroup]` parent); ids come from `HlmFormIdGenerator` unless overridden.
6
6
 
7
7
  > **Ships as:** `@egose/shadcn-theme-ng/form-input-otp` and `@egose/shadcn-theme-ng-tw/form-input-otp`
8
8
  > (the `tw:`-prefixed Tailwind variant). Both expose the identical TypeScript surface; only the
@@ -53,7 +53,7 @@ NgModule-based consumer: add `EgFormInputOtp` (and `ReactiveFormsModule`) to the
53
53
  `imports` — it is a standalone component, not a module.
54
54
 
55
55
  `ControlValueAccessor` behavior: `eg-form-input-otp` itself is not a `ControlValueAccessor`; the
56
- inner `brn-input-otp` is (bound via `formControlName`), so `formControlName`/`formGroup` handling,
56
+ inner `hlm-input-otp` is (bound via `formControlName`), so `formControlName`/`formGroup` handling,
57
57
  `Validators`, and `disabled` state from the control all flow through the parent form.
58
58
 
59
59
  ## Anatomy / Structure
@@ -169,8 +169,10 @@ export class PinComponent {
169
169
  ## Accessibility notes
170
170
 
171
171
  - Label `for` ↔ OTP `inputId` wiring is automatic via `effectiveId` — always pass a `label`.
172
- - `BrnInputOtp` exposes no `aria-describedby`, so error/hint ids render without an input-level link.
173
- - The required `*` is visual; pair with `Validators.required` (plus `minLength`) so assistive tech and validation agree.
172
+ - The wrapper uses `HlmInputOtpControl` (`hlm-input-otp`), a declarative adapter retaining `BrnInputOtp`'s CVA/editing behavior. Its native input references the displayed error (invalid + touched/dirty/submitted), otherwise the hint; correction/reset restores the hint.
173
+ - Optional `[aria-describedby]` (`string | null`, default `null`) adds consumer-owned IDs before the current message ID, normalized and deduplicated. Keep external elements mounted and reserve `<effectiveId>-error` / `-hint` for the wrapper. Bind the input rather than `[attr.aria-describedby]` on the wrapper host.
174
+ - `required` supplies the visual `*` and native `aria-required`; configure `Validators.required` (plus `minLength`) independently. Native `aria-invalid` follows the form control.
175
+ - `HlmError` is not a live region. Rendered DOM/focus regressions are automated coverage, not screen-reader testing.
174
176
 
175
177
  ## Theming / CSS variables
176
178
 
@@ -1,13 +1,13 @@
1
1
  import * as i0 from '@angular/core';
2
- import { InjectionToken, inject, input, numberAttribute, computed, Component } from '@angular/core';
2
+ import { InjectionToken, inject, ChangeDetectorRef, DestroyRef, input, numberAttribute, 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
- import { BrnInputOtp } from '@spartan-ng/brain/input-otp';
8
7
  import * as i2 from '@egose/shadcn-theme-ng-tw/input-otp';
9
8
  import { HlmInputOtpImports } from '@egose/shadcn-theme-ng-tw/input-otp';
10
9
  import { hlm } from '@egose/shadcn-theme-ng-tw/utils';
10
+ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
11
11
 
12
12
  const EgFormInputOtpConfigToken = new InjectionToken('EgFormInputOtpConfig');
13
13
  function provideEgFormInputOtpConfig(config) {
@@ -23,6 +23,14 @@ class EgFormInputOtp {
23
23
  generatedId = inject(HlmFormIdGenerator).generate('eg-form-input-otp');
24
24
  _config = injectEgFormInputOtpConfig();
25
25
  _shared = injectEgFormSharedConfig();
26
+ _cdr = inject(ChangeDetectorRef);
27
+ _destroyRef = inject(DestroyRef);
28
+ ngOnInit() {
29
+ // Submit/reset may change message visibility without changing the field's value or validity.
30
+ this.formGroupDirective.form.events
31
+ .pipe(takeUntilDestroyed(this._destroyRef))
32
+ .subscribe(() => this._cdr.markForCheck());
33
+ }
26
34
  label = input(undefined, /* @ts-ignore */
27
35
  ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
28
36
  controlId = input(undefined, /* @ts-ignore */
@@ -40,6 +48,8 @@ class EgFormInputOtp {
40
48
  ...(ngDevMode ? [{ debugName: "autoError" }] : /* istanbul ignore next */ []));
41
49
  hint = input(undefined, /* @ts-ignore */
42
50
  ...(ngDevMode ? [{ debugName: "hint" }] : /* istanbul ignore next */ []));
51
+ /** Consumer-owned description IDs, merged with the current error and hint IDs. */
52
+ ariaDescribedBy = input(null, { ...(ngDevMode ? { debugName: "ariaDescribedBy" } : /* istanbul ignore next */ {}), alias: 'aria-describedby' });
43
53
  id = input(undefined, /* @ts-ignore */
44
54
  ...(ngDevMode ? [{ debugName: "id" }] : /* istanbul ignore next */ []));
45
55
  length = input(6, { ...(ngDevMode ? { debugName: "length" } : /* istanbul ignore next */ {}), transform: numberAttribute });
@@ -89,8 +99,10 @@ class EgFormInputOtp {
89
99
  effectiveDisabled() {
90
100
  return this.disabled() || !!this.formGroupDirective.form.get(this.controlName())?.disabled;
91
101
  }
92
- // NOTE: BrnInputOtp exposes inputId but no aria-describedby, so error/hint
93
- // ids render for sighted users without an input-level describedby link.
102
+ describedBy() {
103
+ const ids = [this.ariaDescribedBy(), this.showError() ? this.errorId() : this.hint() ? this.hintId() : null];
104
+ return [...new Set(ids.filter(Boolean).join(' ').split(/\s+/).filter(Boolean))].join(' ') || null;
105
+ }
94
106
  // Styling
95
107
  userClass = input('', { ...(ngDevMode ? { debugName: "userClass" } : /* istanbul ignore next */ {}), alias: 'class' });
96
108
  labelClass = input('', /* @ts-ignore */
@@ -113,7 +125,7 @@ class EgFormInputOtp {
113
125
  $hintClass = computed(() => hlm('tw:mt-0', this._shared.hintClass, this._config.hintClass, this.hintClass()), /* @ts-ignore */
114
126
  ...(ngDevMode ? [{ debugName: "$hintClass" }] : /* istanbul ignore next */ []));
115
127
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: EgFormInputOtp, deps: [], target: i0.ɵɵFactoryTarget.Component });
116
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.3", type: EgFormInputOtp, isStandalone: true, selector: "eg-form-input-otp", 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 }, length: { classPropertyName: "length", publicName: "length", 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 }, userClass: { classPropertyName: "userClass", publicName: "class", isSignal: true, isRequired: false, transformFunction: null }, labelClass: { classPropertyName: "labelClass", publicName: "labelClass", isSignal: true, isRequired: false, transformFunction: null }, otpClass: { classPropertyName: "otpClass", publicName: "otpClass", 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: `
128
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.3", type: EgFormInputOtp, isStandalone: true, selector: "eg-form-input-otp", 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 }, length: { classPropertyName: "length", publicName: "length", 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 }, userClass: { classPropertyName: "userClass", publicName: "class", isSignal: true, isRequired: false, transformFunction: null }, labelClass: { classPropertyName: "labelClass", publicName: "labelClass", isSignal: true, isRequired: false, transformFunction: null }, otpClass: { classPropertyName: "otpClass", publicName: "otpClass", 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: `
117
129
  @let lbl = label();
118
130
  @let cnm = controlName();
119
131
  @let hnt = hint();
@@ -129,11 +141,12 @@ class EgFormInputOtp {
129
141
  </label>
130
142
  }
131
143
 
132
- <brn-input-otp
133
- hlmInputOtp
144
+ <hlm-input-otp
134
145
  [length]="length()"
135
146
  [formControlName]="cnm"
136
147
  [inputId]="effectiveId()"
148
+ [aria-describedby]="describedBy()"
149
+ [required]="rqrd"
137
150
  [disabled]="effectiveDisabled()"
138
151
  [class]="$otpClass()"
139
152
  >
@@ -142,7 +155,7 @@ class EgFormInputOtp {
142
155
  <hlm-input-otp-slot [index]="i" />
143
156
  }
144
157
  </div>
145
- </brn-input-otp>
158
+ </hlm-input-otp>
146
159
 
147
160
  @if (showError()) {
148
161
  <hlm-error [id]="errorId()" [class]="$errorClass()">
@@ -156,7 +169,7 @@ class EgFormInputOtp {
156
169
  </hlm-hint>
157
170
  }
158
171
  </hlm-form-field>
159
- `, 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: "component", type: BrnInputOtp, selector: "brn-input-otp", inputs: ["hostStyles", "inputId", "inputAutocomplete", "inputStyles", "containerStyles", "disabled", "length", "inputMode", "inputClass", "autofocus", "transformPaste", "value"], outputs: ["valueChange", "completed"] }, { kind: "directive", type: i2.HlmInputOtp, selector: "brn-input-otp[hlmInputOtp], brn-input-otp[hlm]" }, { kind: "directive", type: i2.HlmInputOtpGroup, selector: "[hlmInputOtpGroup],hlm-input-otp-group" }, { kind: "component", type: i2.HlmInputOtpSlot, selector: "hlm-input-otp-slot", inputs: ["index"] }] });
172
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { 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: "component", type: i2.HlmInputOtpControl, selector: "hlm-input-otp", inputs: ["aria-describedby", "required"] }, { kind: "directive", type: i2.HlmInputOtpGroup, selector: "[hlmInputOtpGroup],hlm-input-otp-group" }, { kind: "component", type: i2.HlmInputOtpSlot, selector: "hlm-input-otp-slot", inputs: ["index"] }] });
160
173
  }
161
174
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: EgFormInputOtp, decorators: [{
162
175
  type: Component,
@@ -166,7 +179,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImpor
166
179
  host: {
167
180
  '[class]': '$userClass()',
168
181
  },
169
- imports: [ReactiveFormsModule, HlmFormField, HlmError, HlmHint, HlmLabel, BrnInputOtp, HlmInputOtpImports],
182
+ imports: [ReactiveFormsModule, HlmFormField, HlmError, HlmHint, HlmLabel, HlmInputOtpImports],
170
183
  providers: [{ provide: ControlContainer, useExisting: FormGroupDirective }],
171
184
  template: `
172
185
  @let lbl = label();
@@ -184,11 +197,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImpor
184
197
  </label>
185
198
  }
186
199
 
187
- <brn-input-otp
188
- hlmInputOtp
200
+ <hlm-input-otp
189
201
  [length]="length()"
190
202
  [formControlName]="cnm"
191
203
  [inputId]="effectiveId()"
204
+ [aria-describedby]="describedBy()"
205
+ [required]="rqrd"
192
206
  [disabled]="effectiveDisabled()"
193
207
  [class]="$otpClass()"
194
208
  >
@@ -197,7 +211,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImpor
197
211
  <hlm-input-otp-slot [index]="i" />
198
212
  }
199
213
  </div>
200
- </brn-input-otp>
214
+ </hlm-input-otp>
201
215
 
202
216
  @if (showError()) {
203
217
  <hlm-error [id]="errorId()" [class]="$errorClass()">
@@ -213,7 +227,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImpor
213
227
  </hlm-form-field>
214
228
  `,
215
229
  }]
216
- }], 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 }] }], length: [{ type: i0.Input, args: [{ isSignal: true, alias: "length", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], userClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }], labelClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "labelClass", required: false }] }], otpClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "otpClass", required: false }] }], errorClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorClass", required: false }] }], hintClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "hintClass", required: false }] }] } });
230
+ }], 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 }] }], length: [{ type: i0.Input, args: [{ isSignal: true, alias: "length", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], userClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }], labelClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "labelClass", required: false }] }], otpClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "otpClass", required: false }] }], errorClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorClass", required: false }] }], hintClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "hintClass", required: false }] }] } });
217
231
 
218
232
  /*
219
233
  * Public API Surface of form-input-otp
@@ -1,14 +1,17 @@
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
  import { NumberInput } from '@angular/cdk/coercion';
5
5
 
6
- declare class EgFormInputOtp {
6
+ declare class EgFormInputOtp implements OnInit {
7
7
  private readonly formGroupDirective;
8
8
  private readonly _errorMessages;
9
9
  private readonly generatedId;
10
10
  private readonly _config;
11
11
  private readonly _shared;
12
+ private readonly _cdr;
13
+ private readonly _destroyRef;
14
+ ngOnInit(): void;
12
15
  label: _angular_core.InputSignal<string | undefined>;
13
16
  controlId: _angular_core.InputSignal<string | undefined>;
14
17
  controlName: _angular_core.InputSignal<string>;
@@ -20,6 +23,8 @@ declare class EgFormInputOtp {
20
23
  */
21
24
  autoError: _angular_core.InputSignal<boolean>;
22
25
  hint: _angular_core.InputSignal<string | undefined>;
26
+ /** Consumer-owned description IDs, merged with the current error and hint IDs. */
27
+ ariaDescribedBy: _angular_core.InputSignal<string | null>;
23
28
  id: _angular_core.InputSignal<string | undefined>;
24
29
  length: _angular_core.InputSignalWithTransform<number, NumberInput>;
25
30
  disabled: _angular_core.InputSignal<boolean>;
@@ -47,6 +52,7 @@ declare class EgFormInputOtp {
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
  otpClass: _angular_core.InputSignal<string>;
@@ -58,7 +64,7 @@ declare class EgFormInputOtp {
58
64
  $errorClass: _angular_core.Signal<string>;
59
65
  $hintClass: _angular_core.Signal<string>;
60
66
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<EgFormInputOtp, never>;
61
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<EgFormInputOtp, "eg-form-input-otp", 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; }; "length": { "alias": "length"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "required": { "alias": "required"; "required": false; "isSignal": true; }; "userClass": { "alias": "class"; "required": false; "isSignal": true; }; "labelClass": { "alias": "labelClass"; "required": false; "isSignal": true; }; "otpClass": { "alias": "otpClass"; "required": false; "isSignal": true; }; "errorClass": { "alias": "errorClass"; "required": false; "isSignal": true; }; "hintClass": { "alias": "hintClass"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
67
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<EgFormInputOtp, "eg-form-input-otp", 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; }; "length": { "alias": "length"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "required": { "alias": "required"; "required": false; "isSignal": true; }; "userClass": { "alias": "class"; "required": false; "isSignal": true; }; "labelClass": { "alias": "labelClass"; "required": false; "isSignal": true; }; "otpClass": { "alias": "otpClass"; "required": false; "isSignal": true; }; "errorClass": { "alias": "errorClass"; "required": false; "isSignal": true; }; "hintClass": { "alias": "hintClass"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
62
68
  }
63
69
 
64
70
  interface EgFormInputOtpConfig {
@@ -100,6 +100,19 @@ No outputs. Readonly computeds/methods (usable via template ref): `effectiveId()
100
100
 
101
101
  `SelectOption` is `export interface SelectOption { label: string; value: string }` from `@egose/shadcn-theme-ng/searchable-multiselect`.
102
102
 
103
+ ### Local search and form values
104
+
105
+ Additional copy inputs:
106
+
107
+ - `searchLabel: string` (default `'Search options'`): visible native search label.
108
+ - `searchPlaceholder: string` (default `'Type to filter…'`): hint inside the search input.
109
+ - `emptyMessage: string` (default `'No matching options'`): polite status message for no matches or no supplied options.
110
+ - `removeLabel: (option: SelectOption) => string` (default ``option => `Remove ${option.label}` ``): pure accessible chip-remove name formatter; unresolved labels are raw IDs.
111
+
112
+ Pass the full options list. The inner control trims the query and matches label substrings case-insensitively in source order. Filtering retains hidden selections and their labels, including unresolved IDs written before asynchronous options arrive. Searching/clearing the query never changes or touches the form value. The query persists across closing/reopening and form resets; clearing its text restores all options. Wrapper and reactive-form disabled states disable the search even if the popover is already open.
113
+
114
+ The search has an associated visible label, no-match feedback uses a polite live region, and each chip remove button has an accessible name. All four copy inputs above are forwarded unchanged. For example, on the wrapper in the complete reactive-form example below, add `searchLabel="Find tags" searchPlaceholder="Type a tag name…" emptyMessage="No tags match"`. Import `SelectOption` from the multiselect subpath to type a localized `[removeLabel]` callback. See the [multiselect usage](../searchable-multiselect/README.md#local-search) for keyboard and async ownership details. This is local label search, with no remote request or virtualization behavior.
115
+
103
116
  ## Examples
104
117
 
105
118
  ### 1. Basic reactive form
@@ -237,7 +250,8 @@ import { EgFormSearchableMultiselect } from '@egose/shadcn-theme-ng/form-searcha
237
250
  controlId="framework-picker"
238
251
  label="Frameworks"
239
252
  placeholder="Search frameworks…"
240
- hint="Type to filter, Enter to add"
253
+ searchLabel="Find frameworks"
254
+ hint="Type to filter, then Tab to a checkbox and Space to select"
241
255
  [options]="frameworks"
242
256
  class="tw:max-w-md"
243
257
  labelClass="tw:font-semibold"