@memberjunction/ng-ui-components 6.1.0-edge.4 → 6.1.0-edge.6

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 (52) hide show
  1. package/README.md +9 -1
  2. package/dist/lib/alert/alert.component.d.ts.map +1 -1
  3. package/dist/lib/alert/alert.component.js +1 -1
  4. package/dist/lib/applied-filters/applied-filters.component.js +1 -1
  5. package/dist/lib/bottom-sheet/bottom-sheet.component.js +1 -1
  6. package/dist/lib/combobox/combobox.component.d.ts +32 -1
  7. package/dist/lib/combobox/combobox.component.d.ts.map +1 -1
  8. package/dist/lib/combobox/combobox.component.js +73 -28
  9. package/dist/lib/combobox/combobox.component.js.map +1 -1
  10. package/dist/lib/confirm-dialog/confirm-dialog.component.d.ts.map +1 -1
  11. package/dist/lib/datepicker/datepicker.component.d.ts +25 -1
  12. package/dist/lib/datepicker/datepicker.component.d.ts.map +1 -1
  13. package/dist/lib/datepicker/datepicker.component.js +59 -28
  14. package/dist/lib/datepicker/datepicker.component.js.map +1 -1
  15. package/dist/lib/dialog/dialog.component.js +1 -1
  16. package/dist/lib/dialog/dialog.component.js.map +1 -1
  17. package/dist/lib/dialog/dialog.service.js +15 -16
  18. package/dist/lib/dialog/dialog.service.js.map +1 -1
  19. package/dist/lib/dropdown/dropdown.component.d.ts +103 -3
  20. package/dist/lib/dropdown/dropdown.component.d.ts.map +1 -1
  21. package/dist/lib/dropdown/dropdown.component.js +208 -52
  22. package/dist/lib/dropdown/dropdown.component.js.map +1 -1
  23. package/dist/lib/dropdown/dropdown.scss +16 -0
  24. package/dist/lib/empty-state/empty-state.component.js +1 -1
  25. package/dist/lib/filter-popover/filter-popover.component.js +27 -28
  26. package/dist/lib/filter-popover/filter-popover.component.js.map +1 -1
  27. package/dist/lib/left-nav/left-nav-content.component.js +1 -1
  28. package/dist/lib/left-nav/left-nav.component.js +72 -73
  29. package/dist/lib/left-nav/left-nav.component.js.map +1 -1
  30. package/dist/lib/numeric-input/numeric-input.component.d.ts +22 -1
  31. package/dist/lib/numeric-input/numeric-input.component.d.ts.map +1 -1
  32. package/dist/lib/numeric-input/numeric-input.component.js +30 -3
  33. package/dist/lib/numeric-input/numeric-input.component.js.map +1 -1
  34. package/dist/lib/page-body/page-body.component.js +1 -1
  35. package/dist/lib/page-body-interior/page-body-interior.component.js +1 -1
  36. package/dist/lib/page-header/page-header.component.js +1 -1
  37. package/dist/lib/page-header-interior/page-header-interior.component.js +1 -1
  38. package/dist/lib/page-search/page-search.component.js +1 -1
  39. package/dist/lib/refresh-button/refresh-button.component.js +1 -1
  40. package/dist/lib/slide-panel/slide-panel.component.js +1 -1
  41. package/dist/lib/stat-badge/stat-badge.component.js +1 -1
  42. package/dist/lib/switch/switch.component.d.ts +22 -1
  43. package/dist/lib/switch/switch.component.d.ts.map +1 -1
  44. package/dist/lib/switch/switch.component.js +30 -3
  45. package/dist/lib/switch/switch.component.js.map +1 -1
  46. package/dist/lib/tab-nav/tab-nav.component.js +1 -1
  47. package/dist/lib/view-toggle/view-toggle.component.js +1 -1
  48. package/dist/lib/window/window.component.js +1 -1
  49. package/dist/lib/window/window.component.js.map +1 -1
  50. package/dist/lib/workspace-tabs/workspace-card.component.js +1 -1
  51. package/dist/lib/workspace-tabs/workspace-tab-strip.component.js +1 -1
  52. package/package.json +8 -8
@@ -7,9 +7,27 @@ import * as i0 from "@angular/core";
7
7
  *
8
8
  * Replaces `<kendo-dropdownlist>`.
9
9
  *
10
- * @example
10
+ * Every dropdown needs an ACCESSIBLE NAME, or it announces as "combobox, collapsed" with no hint of
11
+ * what it selects (WCAG 2.1 4.1.2). Use {@link AriaLabelledBy} when a visible label already exists —
12
+ * `<label for>` cannot name a `div[role=combobox]` — and {@link AriaLabel} when none does.
13
+ *
14
+ * @example With a visible label (preferred)
15
+ * ```html
16
+ * <span id="persona-label">Interview persona</span>
17
+ * <mj-dropdown
18
+ * AriaLabelledBy="persona-label"
19
+ * [Data]="items"
20
+ * TextField="name"
21
+ * ValueField="id"
22
+ * [(ngModel)]="selectedId"
23
+ * [ValuePrimitive]="true">
24
+ * </mj-dropdown>
25
+ * ```
26
+ *
27
+ * @example With no visible label
11
28
  * ```html
12
29
  * <mj-dropdown
30
+ * AriaLabel="Interview persona"
13
31
  * [Data]="items"
14
32
  * TextField="name"
15
33
  * ValueField="id"
@@ -21,12 +39,43 @@ import * as i0 from "@angular/core";
21
39
  * ```
22
40
  */
23
41
  export declare class MJDropdownComponent implements ControlValueAccessor, OnDestroy {
42
+ /**
43
+ * Accessible name for the combobox (#3860). Without one — and with no visible label wired via
44
+ * {@link AriaLabelledBy} — the control announces as an UNNAMED combobox, which fails WCAG 2.1
45
+ * 4.1.2 (Name, Role, Value): "combobox, collapsed" with no hint of what it selects. Applied as
46
+ * `aria-label` on the trigger AND on the popup listbox, so both halves announce the same name.
47
+ */
48
+ AriaLabel: string;
49
+ /**
50
+ * The id of a VISIBLE label element that names this control — the preferred wiring when a label
51
+ * already exists on screen (an `aria-label` would duplicate its text and drift on rename). This,
52
+ * not `<label for>`, is the visible-label path: the trigger is a `div[role=combobox]`, and the
53
+ * label-for association only names labelable form elements. `aria-labelledby` beats `AriaLabel`
54
+ * in the accessible-name computation where both are present. Applied to trigger AND listbox.
55
+ */
56
+ AriaLabelledBy: string;
57
+ /**
58
+ * `id` for the combobox trigger, so other markup can REFERENCE it — `aria-controls`, hint text,
59
+ * test hooks. It is deliberately not documented as a `<label for>` target: the trigger is a div,
60
+ * which `label[for]` neither names nor focuses. To name the control from a visible label, put an
61
+ * id on the LABEL and pass it as {@link AriaLabelledBy}.
62
+ */
63
+ InputId: string;
64
+ /** `aria-describedby` passthrough for hint/error text — same shape of gap as the name. */
65
+ AriaDescribedBy: string;
24
66
  Data: Record<string, unknown>[] | string[] | readonly unknown[] | null;
25
67
  TextField: string;
26
68
  ValueField: string;
27
69
  Filterable: boolean;
28
70
  ValuePrimitive: boolean;
29
- Disabled: boolean;
71
+ /**
72
+ * Host-driven disable. Composed with Angular Forms' `setDisabledState()` into `IsDisabled`
73
+ * (the actual gate) — see `syncDisabled`. A setter, not a bare field, because this input is
74
+ * routinely bound to an expression that changes over the control's lifetime
75
+ * (`[Disabled]="!draft.CompanyID"`), and the gate has to follow it every time.
76
+ */
77
+ set Disabled(value: boolean);
78
+ get Disabled(): boolean;
30
79
  Placeholder: string;
31
80
  DefaultItem: Record<string, unknown> | string | null;
32
81
  FilterChange: EventEmitter<string>;
@@ -40,7 +89,35 @@ export declare class MJDropdownComponent implements ControlValueAccessor, OnDest
40
89
  private cdr;
41
90
  private static nextId;
42
91
  DropdownId: number;
92
+ /**
93
+ * Id of the popup listbox, so the trigger can point `aria-controls` at it while open — the half of
94
+ * the combobox pattern that makes "collapsed/expanded" refer to something a screen reader can
95
+ * find. Generated per instance from the same `static nextId` counter `dialog`, `window` and
96
+ * `accordion` use in this package.
97
+ */
98
+ get ListboxId(): string;
99
+ /** Id of the hidden "Filter" word, composed into the filter box's name via an id list. */
100
+ get FilterWordId(): string;
101
+ /**
102
+ * `aria-labelledby` for the filter box when the dropdown is named by a VISIBLE label: "Filter"
103
+ * plus that label's own text, composed by the accessibility tree. Empty when there is no such
104
+ * label, in which case {@link FilterLabel} supplies a string instead.
105
+ */
106
+ get FilterLabelledBy(): string;
107
+ /**
108
+ * `aria-label` for the filter box in the no-visible-label case.
109
+ *
110
+ * The "filter" guard is not cosmetic: this repo's house habit is `AriaLabel="Filter roles"`, and
111
+ * an unconditional prefix announces that box as "Filter Filter roles". A name that already begins
112
+ * with the word is used as-is.
113
+ */
114
+ get FilterLabel(): string;
43
115
  IsOpen: boolean;
116
+ /**
117
+ * The single gate on `Toggle()` / `Open()` — true when EITHER the `Disabled` input or Angular
118
+ * Forms says so. Never assign it directly; go through `syncDisabled()` so both sources are
119
+ * always composed and a lock closes an open panel.
120
+ */
44
121
  IsDisabled: boolean;
45
122
  HighlightedIndex: number;
46
123
  SelectedValue: unknown;
@@ -49,12 +126,35 @@ export declare class MJDropdownComponent implements ControlValueAccessor, OnDest
49
126
  Positions: ConnectedPosition[];
50
127
  private onChange;
51
128
  private onTouched;
129
+ /** Backing field for the `Disabled` input. */
130
+ private disabledInput;
131
+ /** The forms-driven disabled state, kept SEPARATE so neither source can stomp the other. */
132
+ private formDisabled;
133
+ /**
134
+ * Recompute the gate from both of its sources. Called whenever either changes.
135
+ *
136
+ * `IsDisabled` is derived state, and the only thing that assigned it was `setDisabledState()`.
137
+ * The forms-driven half was in fact fine — `setUpControl` also wires `registerOnDisabledChange`,
138
+ * so that hook fires on every `control.disable()`/`enable()`, not just at registration. What had
139
+ * no recompute path at all was the `Disabled` @Input: a plain field, so the gate froze at
140
+ * whatever the first compose produced and every later change to the input was dropped.
141
+ *
142
+ * That is how a `[Disabled]="!draft.CompanyID"` picker stayed dead for the life of the
143
+ * component after the company was finally chosen.
144
+ */
145
+ private syncDisabled;
52
146
  get FilteredItems(): unknown[];
53
147
  get HasValue(): boolean;
54
148
  get DisplayText(): string;
55
149
  Toggle(): void;
56
150
  Open(): void;
57
151
  Close(): void;
152
+ /**
153
+ * Panel state only — no change detection. Split out of `Close()` because `syncDisabled()` can
154
+ * run from an @Input setter, i.e. DURING the parent's change-detection pass, where a nested
155
+ * `detectChanges()` re-enters CD and trips NG0100 on the parent's own bindings.
156
+ */
157
+ private resetPanelState;
58
158
  SelectItem(item: unknown | null, event?: Event): void;
59
159
  IsItemSelected(item: unknown): boolean;
60
160
  OnFilterInput(event: Event): void;
@@ -70,6 +170,6 @@ export declare class MJDropdownComponent implements ControlValueAccessor, OnDest
70
170
  ngOnDestroy(): void;
71
171
  private getSelectedIndex;
72
172
  static ɵfac: i0.ɵɵFactoryDeclaration<MJDropdownComponent, never>;
73
- static ɵcmp: i0.ɵɵComponentDeclaration<MJDropdownComponent, "mj-dropdown", never, { "Data": { "alias": "Data"; "required": false; }; "TextField": { "alias": "TextField"; "required": false; }; "ValueField": { "alias": "ValueField"; "required": false; }; "Filterable": { "alias": "Filterable"; "required": false; }; "ValuePrimitive": { "alias": "ValuePrimitive"; "required": false; }; "Disabled": { "alias": "Disabled"; "required": false; }; "Placeholder": { "alias": "Placeholder"; "required": false; }; "DefaultItem": { "alias": "DefaultItem"; "required": false; }; }, { "FilterChange": "FilterChange"; "ValueChange": "ValueChange"; }, ["itemTemplate"], never, true, never>;
173
+ static ɵcmp: i0.ɵɵComponentDeclaration<MJDropdownComponent, "mj-dropdown", never, { "AriaLabel": { "alias": "AriaLabel"; "required": false; }; "AriaLabelledBy": { "alias": "AriaLabelledBy"; "required": false; }; "InputId": { "alias": "InputId"; "required": false; }; "AriaDescribedBy": { "alias": "AriaDescribedBy"; "required": false; }; "Data": { "alias": "Data"; "required": false; }; "TextField": { "alias": "TextField"; "required": false; }; "ValueField": { "alias": "ValueField"; "required": false; }; "Filterable": { "alias": "Filterable"; "required": false; }; "ValuePrimitive": { "alias": "ValuePrimitive"; "required": false; }; "Disabled": { "alias": "Disabled"; "required": false; }; "Placeholder": { "alias": "Placeholder"; "required": false; }; "DefaultItem": { "alias": "DefaultItem"; "required": false; }; }, { "FilterChange": "FilterChange"; "ValueChange": "ValueChange"; }, ["itemTemplate"], never, true, never>;
74
174
  }
75
175
  //# sourceMappingURL=dropdown.component.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"dropdown.component.d.ts","sourceRoot":"","sources":["../../../src/lib/dropdown/dropdown.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,YAAY,EAMZ,WAAW,EACX,SAAS,EAGV,MAAM,eAAe,CAAC;AAEvB,OAAO,EAAqB,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AACzE,OAAO,EAAiB,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;;AAExE;;;;;;;;;;;;;;;;;GAiBG;AACH,qBAsFa,mBAAoB,YAAW,oBAAoB,EAAE,SAAS;IAChE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,MAAM,EAAE,GAAG,SAAS,OAAO,EAAE,GAAG,IAAI,CAAM;IAC5E,SAAS,SAAM;IACf,UAAU,SAAM;IAChB,UAAU,UAAS;IACnB,cAAc,UAAS;IACvB,QAAQ,UAAS;IACjB,WAAW,SAAe;IAC1B,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,GAAG,IAAI,CAAQ;IAE3D,YAAY,uBAA8B;IAC1C,WAAW,wBAA+B;IAEpB,YAAY,EAAE,WAAW,CAAC;QAAE,SAAS,EAAE,OAAO,CAAA;KAAE,CAAC,GAAG,IAAI,CAAQ;IAE1E,OAAO,CAAC,SAAS,CAA2B;IACxC,OAAO,CAAC,aAAa,CAA2C;IAEnD,QAAQ,CAAC,SAAS,QAAQ;IAEjE,OAAO,CAAC,GAAG,CAA6B;IACxC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAK;IAE1B,UAAU,SAAgC;IAC1C,MAAM,UAAS;IACf,UAAU,UAAS;IACnB,gBAAgB,SAAM;IACtB,aAAa,EAAE,OAAO,CAAQ;IAC9B,YAAY,SAAK;IAEjB,UAAU,SAAM;IAEhB,SAAS,EAAE,iBAAiB,EAAE,CAG5B;IAEF,OAAO,CAAC,QAAQ,CAAsC;IACtD,OAAO,CAAC,SAAS,CAAwB;IAEzC,IAAI,aAAa,IAAI,OAAO,EAAE,CAK7B;IAED,IAAI,QAAQ,IAAI,OAAO,CAAuC;IAE9D,IAAI,WAAW,IAAI,MAAM,CAUxB;IAED,MAAM,IAAI,IAAI;IAKd,IAAI,IAAI,IAAI;IASZ,KAAK,IAAI,IAAI;IAQb,UAAU,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,EAAE,KAAK,CAAC,EAAE,KAAK,GAAG,IAAI;IAmBrD,cAAc,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO;IAQtC,aAAa,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI;IAMjC,SAAS,CAAC,KAAK,EAAE,aAAa,GAAG,IAAI;IAwBrC,MAAM,IAAI,IAAI;IAEd,WAAW,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM;IAOlC,YAAY,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO;IAMpC,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM;IAEnC,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI;IAChC,gBAAgB,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,GAAG,IAAI;IACpD,iBAAiB,CAAC,EAAE,EAAE,MAAM,IAAI,GAAG,IAAI;IACvC,gBAAgB,CAAC,UAAU,EAAE,OAAO,GAAG,IAAI;IAC3C,WAAW,IAAI,IAAI;IAEnB,OAAO,CAAC,gBAAgB;yCAnKb,mBAAmB;2CAAnB,mBAAmB;CAuK/B"}
1
+ {"version":3,"file":"dropdown.component.d.ts","sourceRoot":"","sources":["../../../src/lib/dropdown/dropdown.component.ts"],"names":[],"mappings":"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"}
@@ -9,59 +9,65 @@ const _c1 = ["trigger"];
9
9
  const _c2 = ["filterInput"];
10
10
  const _c3 = a0 => ({ $implicit: a0 });
11
11
  function MJDropdownComponent_ng_template_6_Conditional_1_Template(rf, ctx) { if (rf & 1) {
12
- const _r2 = i0.ɵɵgetCurrentView();
13
- i0.ɵɵelementStart(0, "div", 7)(1, "input", 11, 1);
14
- i0.ɵɵlistener("input", function MJDropdownComponent_ng_template_6_Conditional_1_Template_input_input_1_listener($event) { i0.ɵɵrestoreView(_r2); const ctx_r2 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r2.OnFilterInput($event)); })("keydown", function MJDropdownComponent_ng_template_6_Conditional_1_Template_input_keydown_1_listener($event) { i0.ɵɵrestoreView(_r2); const ctx_r2 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r2.OnKeyDown($event)); });
12
+ const _r1 = i0.ɵɵgetCurrentView();
13
+ i0.ɵɵelementStart(0, "div", 7)(1, "span", 11);
14
+ i0.ɵɵtext(2, "Filter");
15
+ i0.ɵɵelementEnd();
16
+ i0.ɵɵelementStart(3, "input", 12, 1);
17
+ i0.ɵɵlistener("input", function MJDropdownComponent_ng_template_6_Conditional_1_Template_input_input_3_listener($event) { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.OnFilterInput($event)); })("keydown", function MJDropdownComponent_ng_template_6_Conditional_1_Template_input_keydown_3_listener($event) { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.OnKeyDown($event)); });
15
18
  i0.ɵɵelementEnd()();
16
19
  } if (rf & 2) {
17
- const ctx_r2 = i0.ɵɵnextContext(2);
20
+ const ctx_r1 = i0.ɵɵnextContext(2);
18
21
  i0.ɵɵadvance();
19
- i0.ɵɵproperty("value", ctx_r2.filterText);
22
+ i0.ɵɵattribute("id", ctx_r1.FilterWordId);
23
+ i0.ɵɵadvance(2);
24
+ i0.ɵɵproperty("value", ctx_r1.filterText);
25
+ i0.ɵɵattribute("aria-labelledby", ctx_r1.FilterLabelledBy)("aria-label", ctx_r1.FilterLabelledBy ? null : ctx_r1.FilterLabel);
20
26
  } }
21
27
  function MJDropdownComponent_ng_template_6_Conditional_2_Template(rf, ctx) { if (rf & 1) {
22
- const _r4 = i0.ɵɵgetCurrentView();
23
- i0.ɵɵelementStart(0, "div", 12);
24
- i0.ɵɵlistener("click", function MJDropdownComponent_ng_template_6_Conditional_2_Template_div_click_0_listener($event) { i0.ɵɵrestoreView(_r4); const ctx_r2 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r2.SelectItem(null, $event)); });
28
+ const _r3 = i0.ɵɵgetCurrentView();
29
+ i0.ɵɵelementStart(0, "div", 13);
30
+ i0.ɵɵlistener("click", function MJDropdownComponent_ng_template_6_Conditional_2_Template_div_click_0_listener($event) { i0.ɵɵrestoreView(_r3); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.SelectItem(null, $event)); });
25
31
  i0.ɵɵtext(1);
26
32
  i0.ɵɵelementEnd();
27
33
  } if (rf & 2) {
28
- const ctx_r2 = i0.ɵɵnextContext(2);
29
- i0.ɵɵclassProp("mj-dropdown-option--selected", ctx_r2.SelectedValue == null)("mj-dropdown-option--highlighted", ctx_r2.HighlightedIndex === -1);
30
- i0.ɵɵattribute("aria-selected", ctx_r2.SelectedValue == null);
34
+ const ctx_r1 = i0.ɵɵnextContext(2);
35
+ i0.ɵɵclassProp("mj-dropdown-option--selected", ctx_r1.SelectedValue == null)("mj-dropdown-option--highlighted", ctx_r1.HighlightedIndex === -1);
36
+ i0.ɵɵattribute("aria-selected", ctx_r1.SelectedValue == null);
31
37
  i0.ɵɵadvance();
32
- i0.ɵɵtextInterpolate1(" ", ctx_r2.GetItemText(ctx_r2.DefaultItem), " ");
38
+ i0.ɵɵtextInterpolate1(" ", ctx_r1.GetItemText(ctx_r1.DefaultItem), " ");
33
39
  } }
34
40
  function MJDropdownComponent_ng_template_6_For_4_Conditional_1_ng_container_0_Template(rf, ctx) { if (rf & 1) {
35
41
  i0.ɵɵelementContainer(0);
36
42
  } }
37
43
  function MJDropdownComponent_ng_template_6_For_4_Conditional_1_Template(rf, ctx) { if (rf & 1) {
38
- i0.ɵɵtemplate(0, MJDropdownComponent_ng_template_6_For_4_Conditional_1_ng_container_0_Template, 1, 0, "ng-container", 14);
44
+ i0.ɵɵtemplate(0, MJDropdownComponent_ng_template_6_For_4_Conditional_1_ng_container_0_Template, 1, 0, "ng-container", 15);
39
45
  } if (rf & 2) {
40
- const item_r6 = i0.ɵɵnextContext().$implicit;
41
- const ctx_r2 = i0.ɵɵnextContext(2);
42
- i0.ɵɵproperty("ngTemplateOutlet", ctx_r2.itemTemplate)("ngTemplateOutletContext", i0.ɵɵpureFunction1(2, _c3, item_r6));
46
+ const item_r5 = i0.ɵɵnextContext().$implicit;
47
+ const ctx_r1 = i0.ɵɵnextContext(2);
48
+ i0.ɵɵproperty("ngTemplateOutlet", ctx_r1.itemTemplate)("ngTemplateOutletContext", i0.ɵɵpureFunction1(2, _c3, item_r5));
43
49
  } }
44
50
  function MJDropdownComponent_ng_template_6_For_4_Conditional_2_Template(rf, ctx) { if (rf & 1) {
45
51
  i0.ɵɵtext(0);
46
52
  } if (rf & 2) {
47
- const item_r6 = i0.ɵɵnextContext().$implicit;
48
- const ctx_r2 = i0.ɵɵnextContext(2);
49
- i0.ɵɵtextInterpolate1(" ", ctx_r2.GetItemText(item_r6), " ");
53
+ const item_r5 = i0.ɵɵnextContext().$implicit;
54
+ const ctx_r1 = i0.ɵɵnextContext(2);
55
+ i0.ɵɵtextInterpolate1(" ", ctx_r1.GetItemText(item_r5), " ");
50
56
  } }
51
57
  function MJDropdownComponent_ng_template_6_For_4_Template(rf, ctx) { if (rf & 1) {
52
- const _r5 = i0.ɵɵgetCurrentView();
53
- i0.ɵɵelementStart(0, "div", 13);
54
- i0.ɵɵlistener("click", function MJDropdownComponent_ng_template_6_For_4_Template_div_click_0_listener($event) { const item_r6 = i0.ɵɵrestoreView(_r5).$implicit; const ctx_r2 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r2.SelectItem(item_r6, $event)); });
58
+ const _r4 = i0.ɵɵgetCurrentView();
59
+ i0.ɵɵelementStart(0, "div", 14);
60
+ i0.ɵɵlistener("click", function MJDropdownComponent_ng_template_6_For_4_Template_div_click_0_listener($event) { const item_r5 = i0.ɵɵrestoreView(_r4).$implicit; const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.SelectItem(item_r5, $event)); });
55
61
  i0.ɵɵconditionalCreate(1, MJDropdownComponent_ng_template_6_For_4_Conditional_1_Template, 1, 4, "ng-container")(2, MJDropdownComponent_ng_template_6_For_4_Conditional_2_Template, 1, 1);
56
62
  i0.ɵɵelementEnd();
57
63
  } if (rf & 2) {
58
- const item_r6 = ctx.$implicit;
59
- const $index_r7 = ctx.$index;
60
- const ctx_r2 = i0.ɵɵnextContext(2);
61
- i0.ɵɵclassProp("mj-dropdown-option--selected", ctx_r2.IsItemSelected(item_r6))("mj-dropdown-option--highlighted", ctx_r2.HighlightedIndex === $index_r7);
62
- i0.ɵɵattribute("aria-selected", ctx_r2.IsItemSelected(item_r6));
64
+ const item_r5 = ctx.$implicit;
65
+ const $index_r6 = ctx.$index;
66
+ const ctx_r1 = i0.ɵɵnextContext(2);
67
+ i0.ɵɵclassProp("mj-dropdown-option--selected", ctx_r1.IsItemSelected(item_r5))("mj-dropdown-option--highlighted", ctx_r1.HighlightedIndex === $index_r6);
68
+ i0.ɵɵattribute("aria-selected", ctx_r1.IsItemSelected(item_r5));
63
69
  i0.ɵɵadvance();
64
- i0.ɵɵconditional(ctx_r2.itemTemplate ? 1 : 2);
70
+ i0.ɵɵconditional(ctx_r1.itemTemplate ? 1 : 2);
65
71
  } }
66
72
  function MJDropdownComponent_ng_template_6_Conditional_5_Template(rf, ctx) { if (rf & 1) {
67
73
  i0.ɵɵelementStart(0, "div", 10);
@@ -70,30 +76,49 @@ function MJDropdownComponent_ng_template_6_Conditional_5_Template(rf, ctx) { if
70
76
  } }
71
77
  function MJDropdownComponent_ng_template_6_Template(rf, ctx) { if (rf & 1) {
72
78
  i0.ɵɵelementStart(0, "div", 6);
73
- i0.ɵɵconditionalCreate(1, MJDropdownComponent_ng_template_6_Conditional_1_Template, 3, 1, "div", 7);
79
+ i0.ɵɵconditionalCreate(1, MJDropdownComponent_ng_template_6_Conditional_1_Template, 5, 4, "div", 7);
74
80
  i0.ɵɵconditionalCreate(2, MJDropdownComponent_ng_template_6_Conditional_2_Template, 2, 6, "div", 8);
75
81
  i0.ɵɵrepeaterCreate(3, MJDropdownComponent_ng_template_6_For_4_Template, 3, 6, "div", 9, i0.ɵɵcomponentInstance().TrackByIndex, true);
76
82
  i0.ɵɵconditionalCreate(5, MJDropdownComponent_ng_template_6_Conditional_5_Template, 2, 0, "div", 10);
77
83
  i0.ɵɵelementEnd();
78
84
  } if (rf & 2) {
79
- const ctx_r2 = i0.ɵɵnextContext();
85
+ const ctx_r1 = i0.ɵɵnextContext();
86
+ i0.ɵɵattribute("id", ctx_r1.ListboxId)("aria-label", ctx_r1.AriaLabel || null)("aria-labelledby", ctx_r1.AriaLabelledBy || null);
80
87
  i0.ɵɵadvance();
81
- i0.ɵɵconditional(ctx_r2.Filterable ? 1 : -1);
88
+ i0.ɵɵconditional(ctx_r1.Filterable ? 1 : -1);
82
89
  i0.ɵɵadvance();
83
- i0.ɵɵconditional(ctx_r2.DefaultItem != null ? 2 : -1);
90
+ i0.ɵɵconditional(ctx_r1.DefaultItem != null ? 2 : -1);
84
91
  i0.ɵɵadvance();
85
- i0.ɵɵrepeater(ctx_r2.FilteredItems);
92
+ i0.ɵɵrepeater(ctx_r1.FilteredItems);
86
93
  i0.ɵɵadvance(2);
87
- i0.ɵɵconditional(ctx_r2.FilteredItems.length === 0 ? 5 : -1);
94
+ i0.ɵɵconditional(ctx_r1.FilteredItems.length === 0 ? 5 : -1);
88
95
  } }
89
96
  /**
90
97
  * mj-dropdown — Dropdown select component using CDK Overlay.
91
98
  *
92
99
  * Replaces `<kendo-dropdownlist>`.
93
100
  *
94
- * @example
101
+ * Every dropdown needs an ACCESSIBLE NAME, or it announces as "combobox, collapsed" with no hint of
102
+ * what it selects (WCAG 2.1 4.1.2). Use {@link AriaLabelledBy} when a visible label already exists —
103
+ * `<label for>` cannot name a `div[role=combobox]` — and {@link AriaLabel} when none does.
104
+ *
105
+ * @example With a visible label (preferred)
106
+ * ```html
107
+ * <span id="persona-label">Interview persona</span>
108
+ * <mj-dropdown
109
+ * AriaLabelledBy="persona-label"
110
+ * [Data]="items"
111
+ * TextField="name"
112
+ * ValueField="id"
113
+ * [(ngModel)]="selectedId"
114
+ * [ValuePrimitive]="true">
115
+ * </mj-dropdown>
116
+ * ```
117
+ *
118
+ * @example With no visible label
95
119
  * ```html
96
120
  * <mj-dropdown
121
+ * AriaLabel="Interview persona"
97
122
  * [Data]="items"
98
123
  * TextField="name"
99
124
  * ValueField="id"
@@ -105,12 +130,48 @@ function MJDropdownComponent_ng_template_6_Template(rf, ctx) { if (rf & 1) {
105
130
  * ```
106
131
  */
107
132
  export class MJDropdownComponent {
133
+ /**
134
+ * Accessible name for the combobox (#3860). Without one — and with no visible label wired via
135
+ * {@link AriaLabelledBy} — the control announces as an UNNAMED combobox, which fails WCAG 2.1
136
+ * 4.1.2 (Name, Role, Value): "combobox, collapsed" with no hint of what it selects. Applied as
137
+ * `aria-label` on the trigger AND on the popup listbox, so both halves announce the same name.
138
+ */
139
+ AriaLabel = '';
140
+ /**
141
+ * The id of a VISIBLE label element that names this control — the preferred wiring when a label
142
+ * already exists on screen (an `aria-label` would duplicate its text and drift on rename). This,
143
+ * not `<label for>`, is the visible-label path: the trigger is a `div[role=combobox]`, and the
144
+ * label-for association only names labelable form elements. `aria-labelledby` beats `AriaLabel`
145
+ * in the accessible-name computation where both are present. Applied to trigger AND listbox.
146
+ */
147
+ AriaLabelledBy = '';
148
+ /**
149
+ * `id` for the combobox trigger, so other markup can REFERENCE it — `aria-controls`, hint text,
150
+ * test hooks. It is deliberately not documented as a `<label for>` target: the trigger is a div,
151
+ * which `label[for]` neither names nor focuses. To name the control from a visible label, put an
152
+ * id on the LABEL and pass it as {@link AriaLabelledBy}.
153
+ */
154
+ InputId = '';
155
+ /** `aria-describedby` passthrough for hint/error text — same shape of gap as the name. */
156
+ AriaDescribedBy = '';
108
157
  Data = [];
109
158
  TextField = '';
110
159
  ValueField = '';
111
160
  Filterable = false;
112
161
  ValuePrimitive = false;
113
- Disabled = false;
162
+ /**
163
+ * Host-driven disable. Composed with Angular Forms' `setDisabledState()` into `IsDisabled`
164
+ * (the actual gate) — see `syncDisabled`. A setter, not a bare field, because this input is
165
+ * routinely bound to an expression that changes over the control's lifetime
166
+ * (`[Disabled]="!draft.CompanyID"`), and the gate has to follow it every time.
167
+ */
168
+ set Disabled(value) {
169
+ this.disabledInput = value;
170
+ this.syncDisabled();
171
+ }
172
+ get Disabled() {
173
+ return this.disabledInput;
174
+ }
114
175
  Placeholder = 'Select...';
115
176
  DefaultItem = null;
116
177
  FilterChange = new EventEmitter();
@@ -122,7 +183,40 @@ export class MJDropdownComponent {
122
183
  cdr = inject(ChangeDetectorRef);
123
184
  static nextId = 0;
124
185
  DropdownId = MJDropdownComponent.nextId++;
186
+ /**
187
+ * Id of the popup listbox, so the trigger can point `aria-controls` at it while open — the half of
188
+ * the combobox pattern that makes "collapsed/expanded" refer to something a screen reader can
189
+ * find. Generated per instance from the same `static nextId` counter `dialog`, `window` and
190
+ * `accordion` use in this package.
191
+ */
192
+ get ListboxId() { return `mj-dropdown-listbox-${this.DropdownId}`; }
193
+ /** Id of the hidden "Filter" word, composed into the filter box's name via an id list. */
194
+ get FilterWordId() { return `mj-dropdown-filter-word-${this.DropdownId}`; }
195
+ /**
196
+ * `aria-labelledby` for the filter box when the dropdown is named by a VISIBLE label: "Filter"
197
+ * plus that label's own text, composed by the accessibility tree. Empty when there is no such
198
+ * label, in which case {@link FilterLabel} supplies a string instead.
199
+ */
200
+ get FilterLabelledBy() { return this.AriaLabelledBy ? `${this.FilterWordId} ${this.AriaLabelledBy}` : ''; }
201
+ /**
202
+ * `aria-label` for the filter box in the no-visible-label case.
203
+ *
204
+ * The "filter" guard is not cosmetic: this repo's house habit is `AriaLabel="Filter roles"`, and
205
+ * an unconditional prefix announces that box as "Filter Filter roles". A name that already begins
206
+ * with the word is used as-is.
207
+ */
208
+ get FilterLabel() {
209
+ const name = this.AriaLabel.trim();
210
+ if (!name)
211
+ return 'Filter options';
212
+ return /^filter\b/i.test(name) ? name : `Filter ${name}`;
213
+ }
125
214
  IsOpen = false;
215
+ /**
216
+ * The single gate on `Toggle()` / `Open()` — true when EITHER the `Disabled` input or Angular
217
+ * Forms says so. Never assign it directly; go through `syncDisabled()` so both sources are
218
+ * always composed and a lock closes an open panel.
219
+ */
126
220
  IsDisabled = false;
127
221
  HighlightedIndex = -1;
128
222
  SelectedValue = null;
@@ -134,6 +228,33 @@ export class MJDropdownComponent {
134
228
  ];
135
229
  onChange = () => { };
136
230
  onTouched = () => { };
231
+ /** Backing field for the `Disabled` input. */
232
+ disabledInput = false;
233
+ /** The forms-driven disabled state, kept SEPARATE so neither source can stomp the other. */
234
+ formDisabled = false;
235
+ /**
236
+ * Recompute the gate from both of its sources. Called whenever either changes.
237
+ *
238
+ * `IsDisabled` is derived state, and the only thing that assigned it was `setDisabledState()`.
239
+ * The forms-driven half was in fact fine — `setUpControl` also wires `registerOnDisabledChange`,
240
+ * so that hook fires on every `control.disable()`/`enable()`, not just at registration. What had
241
+ * no recompute path at all was the `Disabled` @Input: a plain field, so the gate froze at
242
+ * whatever the first compose produced and every later change to the input was dropped.
243
+ *
244
+ * That is how a `[Disabled]="!draft.CompanyID"` picker stayed dead for the life of the
245
+ * component after the company was finally chosen.
246
+ */
247
+ syncDisabled() {
248
+ const disabled = this.disabledInput || this.formDisabled;
249
+ if (disabled === this.IsDisabled)
250
+ return;
251
+ this.IsDisabled = disabled;
252
+ // Becoming disabled while the panel is open would otherwise leave an interactive list
253
+ // hanging off a control the user can no longer operate.
254
+ if (disabled)
255
+ this.resetPanelState();
256
+ this.cdr.markForCheck();
257
+ }
137
258
  get FilteredItems() {
138
259
  const data = (this.Data ?? []);
139
260
  if (!this.Filterable || !this.filterText)
@@ -172,10 +293,18 @@ export class MJDropdownComponent {
172
293
  Close() {
173
294
  if (!this.IsOpen)
174
295
  return;
296
+ this.resetPanelState();
297
+ this.cdr.detectChanges();
298
+ }
299
+ /**
300
+ * Panel state only — no change detection. Split out of `Close()` because `syncDisabled()` can
301
+ * run from an @Input setter, i.e. DURING the parent's change-detection pass, where a nested
302
+ * `detectChanges()` re-enters CD and trips NG0100 on the parent's own bindings.
303
+ */
304
+ resetPanelState() {
175
305
  this.IsOpen = false;
176
306
  this.filterText = '';
177
307
  this.HighlightedIndex = -1;
178
- this.cdr.detectChanges();
179
308
  }
180
309
  SelectItem(item, event) {
181
310
  event?.stopPropagation();
@@ -272,7 +401,7 @@ export class MJDropdownComponent {
272
401
  writeValue(value) { this.SelectedValue = value; }
273
402
  registerOnChange(fn) { this.onChange = fn; }
274
403
  registerOnTouched(fn) { this.onTouched = fn; }
275
- setDisabledState(isDisabled) { this.IsDisabled = isDisabled || this.Disabled; }
404
+ setDisabledState(isDisabled) { this.formDisabled = isDisabled; this.syncDisabled(); }
276
405
  ngOnDestroy() { this.Close(); }
277
406
  getSelectedIndex() {
278
407
  if (this.SelectedValue == null)
@@ -293,31 +422,30 @@ export class MJDropdownComponent {
293
422
  i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.filterInputEl = _t.first);
294
423
  } }, hostVars: 2, hostBindings: function MJDropdownComponent_HostBindings(rf, ctx) { if (rf & 2) {
295
424
  i0.ɵɵclassProp("mj-dropdown-host", ctx.hostClass);
296
- } }, inputs: { Data: "Data", TextField: "TextField", ValueField: "ValueField", Filterable: "Filterable", ValuePrimitive: "ValuePrimitive", Disabled: "Disabled", Placeholder: "Placeholder", DefaultItem: "DefaultItem" }, outputs: { FilterChange: "FilterChange", ValueChange: "ValueChange" }, features: [i0.ɵɵProvidersFeature([{
425
+ } }, inputs: { AriaLabel: "AriaLabel", AriaLabelledBy: "AriaLabelledBy", InputId: "InputId", AriaDescribedBy: "AriaDescribedBy", Data: "Data", TextField: "TextField", ValueField: "ValueField", Filterable: "Filterable", ValuePrimitive: "ValuePrimitive", Disabled: "Disabled", Placeholder: "Placeholder", DefaultItem: "DefaultItem" }, outputs: { FilterChange: "FilterChange", ValueChange: "ValueChange" }, features: [i0.ɵɵProvidersFeature([{
297
426
  provide: NG_VALUE_ACCESSOR,
298
427
  useExisting: forwardRef(() => MJDropdownComponent),
299
428
  multi: true
300
- }])], decls: 7, vars: 13, consts: [["trigger", "", "overlayOrigin", "cdkOverlayOrigin"], ["filterInput", ""], ["cdkOverlayOrigin", "", "role", "combobox", "aria-haspopup", "listbox", "tabindex", "0", 1, "mj-dropdown", 3, "click", "keydown", "blur"], [1, "mj-dropdown-value"], [1, "fa-solid", "fa-chevron-down", "mj-dropdown-arrow"], ["cdkConnectedOverlay", "", "cdkConnectedOverlayBackdropClass", "mj-dropdown-backdrop", 3, "backdropClick", "detach", "cdkConnectedOverlayOrigin", "cdkConnectedOverlayOpen", "cdkConnectedOverlayPositions", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHasBackdrop"], ["role", "listbox", 1, "mj-dropdown-panel"], [1, "mj-dropdown-filter-wrap"], ["role", "option", 1, "mj-dropdown-option", "mj-dropdown-option--default", 3, "mj-dropdown-option--selected", "mj-dropdown-option--highlighted"], ["role", "option", 1, "mj-dropdown-option", 3, "mj-dropdown-option--selected", "mj-dropdown-option--highlighted"], [1, "mj-dropdown-no-data"], ["type", "text", "placeholder", "Search...", 1, "mj-input", "mj-dropdown-filter", 3, "input", "keydown", "value"], ["role", "option", 1, "mj-dropdown-option", "mj-dropdown-option--default", 3, "click"], ["role", "option", 1, "mj-dropdown-option", 3, "click"], [4, "ngTemplateOutlet", "ngTemplateOutletContext"]], template: function MJDropdownComponent_Template(rf, ctx) { if (rf & 1) {
301
- const _r1 = i0.ɵɵgetCurrentView();
429
+ }])], decls: 7, vars: 20, consts: [["trigger", "", "overlayOrigin", "cdkOverlayOrigin"], ["filterInput", ""], ["cdkOverlayOrigin", "", "role", "combobox", "aria-haspopup", "listbox", 1, "mj-dropdown", 3, "click", "keydown", "blur"], [1, "mj-dropdown-value"], [1, "fa-solid", "fa-chevron-down", "mj-dropdown-arrow"], ["cdkConnectedOverlay", "", "cdkConnectedOverlayBackdropClass", "mj-dropdown-backdrop", 3, "backdropClick", "detach", "cdkConnectedOverlayOrigin", "cdkConnectedOverlayOpen", "cdkConnectedOverlayPositions", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHasBackdrop"], ["role", "listbox", 1, "mj-dropdown-panel"], [1, "mj-dropdown-filter-wrap"], ["role", "option", 1, "mj-dropdown-option", "mj-dropdown-option--default", 3, "mj-dropdown-option--selected", "mj-dropdown-option--highlighted"], ["role", "option", 1, "mj-dropdown-option", 3, "mj-dropdown-option--selected", "mj-dropdown-option--highlighted"], [1, "mj-dropdown-no-data"], [1, "mj-dropdown-sr-only"], ["type", "text", "placeholder", "Filter...", 1, "mj-input", "mj-dropdown-filter", 3, "input", "keydown", "value"], ["role", "option", 1, "mj-dropdown-option", "mj-dropdown-option--default", 3, "click"], ["role", "option", 1, "mj-dropdown-option", 3, "click"], [4, "ngTemplateOutlet", "ngTemplateOutletContext"]], template: function MJDropdownComponent_Template(rf, ctx) { if (rf & 1) {
302
430
  i0.ɵɵelementStart(0, "div", 2, 0);
303
- i0.ɵɵlistener("click", function MJDropdownComponent_Template_div_click_0_listener() { i0.ɵɵrestoreView(_r1); return i0.ɵɵresetView(ctx.Toggle()); })("keydown", function MJDropdownComponent_Template_div_keydown_0_listener($event) { i0.ɵɵrestoreView(_r1); return i0.ɵɵresetView(ctx.OnKeyDown($event)); })("blur", function MJDropdownComponent_Template_div_blur_0_listener() { i0.ɵɵrestoreView(_r1); return i0.ɵɵresetView(ctx.OnBlur()); });
431
+ i0.ɵɵlistener("click", function MJDropdownComponent_Template_div_click_0_listener() { return ctx.Toggle(); })("keydown", function MJDropdownComponent_Template_div_keydown_0_listener($event) { return ctx.OnKeyDown($event); })("blur", function MJDropdownComponent_Template_div_blur_0_listener() { return ctx.OnBlur(); });
304
432
  i0.ɵɵelementStart(3, "span", 3);
305
433
  i0.ɵɵtext(4);
306
434
  i0.ɵɵelementEnd();
307
435
  i0.ɵɵelement(5, "i", 4);
308
436
  i0.ɵɵelementEnd();
309
- i0.ɵɵtemplate(6, MJDropdownComponent_ng_template_6_Template, 6, 3, "ng-template", 5);
310
- i0.ɵɵlistener("backdropClick", function MJDropdownComponent_Template_ng_template_backdropClick_6_listener() { i0.ɵɵrestoreView(_r1); return i0.ɵɵresetView(ctx.Close()); })("detach", function MJDropdownComponent_Template_ng_template_detach_6_listener() { i0.ɵɵrestoreView(_r1); return i0.ɵɵresetView(ctx.Close()); });
437
+ i0.ɵɵtemplate(6, MJDropdownComponent_ng_template_6_Template, 6, 6, "ng-template", 5);
438
+ i0.ɵɵlistener("backdropClick", function MJDropdownComponent_Template_ng_template_backdropClick_6_listener() { return ctx.Close(); })("detach", function MJDropdownComponent_Template_ng_template_detach_6_listener() { return ctx.Close(); });
311
439
  } if (rf & 2) {
312
- const overlayOrigin_r8 = i0.ɵɵreference(2);
440
+ const overlayOrigin_r7 = i0.ɵɵreference(2);
313
441
  i0.ɵɵclassProp("mj-dropdown--open", ctx.IsOpen)("mj-dropdown--disabled", ctx.IsDisabled);
314
- i0.ɵɵattribute("aria-expanded", ctx.IsOpen);
442
+ i0.ɵɵattribute("id", ctx.InputId || null)("aria-label", ctx.AriaLabel || null)("aria-labelledby", ctx.AriaLabelledBy || null)("aria-describedby", ctx.AriaDescribedBy || null)("aria-expanded", ctx.IsOpen)("aria-controls", ctx.IsOpen ? ctx.ListboxId : null)("aria-disabled", ctx.IsDisabled ? "true" : null)("tabindex", ctx.IsDisabled ? -1 : 0);
315
443
  i0.ɵɵadvance(3);
316
444
  i0.ɵɵclassProp("mj-dropdown-placeholder", !ctx.HasValue);
317
445
  i0.ɵɵadvance();
318
446
  i0.ɵɵtextInterpolate1(" ", ctx.DisplayText, " ");
319
447
  i0.ɵɵadvance(2);
320
- i0.ɵɵproperty("cdkConnectedOverlayOrigin", overlayOrigin_r8)("cdkConnectedOverlayOpen", ctx.IsOpen)("cdkConnectedOverlayPositions", ctx.Positions)("cdkConnectedOverlayWidth", ctx.TriggerWidth)("cdkConnectedOverlayHasBackdrop", true);
448
+ i0.ɵɵproperty("cdkConnectedOverlayOrigin", overlayOrigin_r7)("cdkConnectedOverlayOpen", ctx.IsOpen)("cdkConnectedOverlayPositions", ctx.Positions)("cdkConnectedOverlayWidth", ctx.TriggerWidth)("cdkConnectedOverlayHasBackdrop", true);
321
449
  } }, dependencies: [NgTemplateOutlet, OverlayModule, i1.CdkConnectedOverlay, i1.CdkOverlayOrigin], encapsulation: 2 });
322
450
  }
323
451
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MJDropdownComponent, [{
@@ -335,9 +463,15 @@ export class MJDropdownComponent {
335
463
  [class.mj-dropdown--open]="IsOpen"
336
464
  [class.mj-dropdown--disabled]="IsDisabled"
337
465
  role="combobox"
466
+ [attr.id]="InputId || null"
467
+ [attr.aria-label]="AriaLabel || null"
468
+ [attr.aria-labelledby]="AriaLabelledBy || null"
469
+ [attr.aria-describedby]="AriaDescribedBy || null"
338
470
  [attr.aria-expanded]="IsOpen"
339
471
  aria-haspopup="listbox"
340
- tabindex="0"
472
+ [attr.aria-controls]="IsOpen ? ListboxId : null"
473
+ [attr.aria-disabled]="IsDisabled ? 'true' : null"
474
+ [attr.tabindex]="IsDisabled ? -1 : 0"
341
475
  (click)="Toggle()"
342
476
  (keydown)="OnKeyDown($event)"
343
477
  (blur)="OnBlur()">
@@ -357,14 +491,28 @@ export class MJDropdownComponent {
357
491
  cdkConnectedOverlayBackdropClass="mj-dropdown-backdrop"
358
492
  (backdropClick)="Close()"
359
493
  (detach)="Close()">
360
- <div class="mj-dropdown-panel" role="listbox">
494
+ <div class="mj-dropdown-panel" role="listbox"
495
+ [attr.id]="ListboxId"
496
+ [attr.aria-label]="AriaLabel || null"
497
+ [attr.aria-labelledby]="AriaLabelledBy || null">
361
498
  @if (Filterable) {
362
499
  <div class="mj-dropdown-filter-wrap">
500
+ <!--
501
+ The word "Filter" lives in a hidden span rather than in a concatenated string so the
502
+ AriaLabelledBy path can NAME this box from the same visible label that names the
503
+ dropdown: aria-labelledby takes an ID LIST, so "Filter" plus the label's own text is
504
+ composed by the accessibility tree without this component ever seeing that text.
505
+ Without it, every filterable dropdown on a form named this way announces as an
506
+ identical "Filter options".
507
+ -->
508
+ <span class="mj-dropdown-sr-only" [attr.id]="FilterWordId">Filter</span>
363
509
  <input
364
510
  #filterInput
365
511
  class="mj-input mj-dropdown-filter"
366
512
  type="text"
367
- placeholder="Search..."
513
+ placeholder="Filter..."
514
+ [attr.aria-labelledby]="FilterLabelledBy"
515
+ [attr.aria-label]="FilterLabelledBy ? null : FilterLabel"
368
516
  [value]="filterText"
369
517
  (input)="OnFilterInput($event)"
370
518
  (keydown)="OnKeyDown($event)" />
@@ -408,7 +556,15 @@ export class MJDropdownComponent {
408
556
  multi: true
409
557
  }]
410
558
  }]
411
- }], null, { Data: [{
559
+ }], null, { AriaLabel: [{
560
+ type: Input
561
+ }], AriaLabelledBy: [{
562
+ type: Input
563
+ }], InputId: [{
564
+ type: Input
565
+ }], AriaDescribedBy: [{
566
+ type: Input
567
+ }], Data: [{
412
568
  type: Input
413
569
  }], TextField: [{
414
570
  type: Input
@@ -441,5 +597,5 @@ export class MJDropdownComponent {
441
597
  type: HostBinding,
442
598
  args: ['class.mj-dropdown-host']
443
599
  }] }); })();
444
- (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(MJDropdownComponent, { className: "MJDropdownComponent", filePath: "lib/dropdown/dropdown.component.ts", lineNumber: 124 }); })();
600
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(MJDropdownComponent, { className: "MJDropdownComponent", filePath: "lib/dropdown/dropdown.component.ts", lineNumber: 162 }); })();
445
601
  //# sourceMappingURL=dropdown.component.js.map