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

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.
package/README.md CHANGED
@@ -79,8 +79,16 @@ Editable text-input combobox (CDK overlay): type-to-filter, keyboard nav, option
79
79
  ### `mj-dropdown` — MJDropdownComponent
80
80
  Non-editable select (CDK overlay) with optional in-panel filter (`[Filterable]`, default false) and `DefaultItem` clear option. Same `Data`/`TextField`/`ValueField`/`ValuePrimitive` contract as combobox. Custom item template via `<ng-template #mjDropdownItem>`.
81
81
 
82
+ **Give every dropdown an accessible name.** The trigger is a `div[role=combobox]`, so `<label for>` neither names nor focuses it — an unnamed dropdown announces as "combobox, collapsed" (WCAG 2.1 4.1.2). Use `AriaLabelledBy` when a visible label already exists, `AriaLabel` when none does; `AriaDescribedBy` carries hint/error text, and `InputId` puts an id on the trigger for other markup to reference. All are applied to the popup listbox as well as the trigger, and a filterable panel's filter box takes its name from the same source.
83
+
82
84
  ```html
83
- <mj-dropdown [Data]="items" TextField="name" ValueField="id"
85
+ <!-- A visible label already on screen (preferred) -->
86
+ <span id="persona-label">Interview persona</span>
87
+ <mj-dropdown AriaLabelledBy="persona-label" [Data]="items" TextField="name" ValueField="id"
88
+ [(ngModel)]="selectedId" [ValuePrimitive]="true" />
89
+
90
+ <!-- No visible label -->
91
+ <mj-dropdown AriaLabel="Interview persona" [Data]="items" TextField="name" ValueField="id"
84
92
  [(ngModel)]="selectedId" [ValuePrimitive]="true" />
85
93
  ```
86
94
 
@@ -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,6 +39,30 @@ 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;
@@ -40,6 +82,29 @@ export declare class MJDropdownComponent implements ControlValueAccessor, OnDest
40
82
  private cdr;
41
83
  private static nextId;
42
84
  DropdownId: number;
85
+ /**
86
+ * Id of the popup listbox, so the trigger can point `aria-controls` at it while open — the half of
87
+ * the combobox pattern that makes "collapsed/expanded" refer to something a screen reader can
88
+ * find. Generated per instance from the same `static nextId` counter `dialog`, `window` and
89
+ * `accordion` use in this package.
90
+ */
91
+ get ListboxId(): string;
92
+ /** Id of the hidden "Filter" word, composed into the filter box's name via an id list. */
93
+ get FilterWordId(): string;
94
+ /**
95
+ * `aria-labelledby` for the filter box when the dropdown is named by a VISIBLE label: "Filter"
96
+ * plus that label's own text, composed by the accessibility tree. Empty when there is no such
97
+ * label, in which case {@link FilterLabel} supplies a string instead.
98
+ */
99
+ get FilterLabelledBy(): string;
100
+ /**
101
+ * `aria-label` for the filter box in the no-visible-label case.
102
+ *
103
+ * The "filter" guard is not cosmetic: this repo's house habit is `AriaLabel="Filter roles"`, and
104
+ * an unconditional prefix announces that box as "Filter Filter roles". A name that already begins
105
+ * with the word is used as-is.
106
+ */
107
+ get FilterLabel(): string;
43
108
  IsOpen: boolean;
44
109
  IsDisabled: boolean;
45
110
  HighlightedIndex: number;
@@ -70,6 +135,6 @@ export declare class MJDropdownComponent implements ControlValueAccessor, OnDest
70
135
  ngOnDestroy(): void;
71
136
  private getSelectedIndex;
72
137
  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>;
138
+ 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
139
  }
75
140
  //# 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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"}
@@ -10,17 +10,23 @@ 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
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)); });
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(_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_3_listener($event) { i0.ɵɵrestoreView(_r2); const ctx_r2 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r2.OnKeyDown($event)); });
15
18
  i0.ɵɵelementEnd()();
16
19
  } if (rf & 2) {
17
20
  const ctx_r2 = i0.ɵɵnextContext(2);
18
21
  i0.ɵɵadvance();
22
+ i0.ɵɵattribute("id", ctx_r2.FilterWordId);
23
+ i0.ɵɵadvance(2);
19
24
  i0.ɵɵproperty("value", ctx_r2.filterText);
25
+ i0.ɵɵattribute("aria-labelledby", ctx_r2.FilterLabelledBy)("aria-label", ctx_r2.FilterLabelledBy ? null : ctx_r2.FilterLabel);
20
26
  } }
21
27
  function MJDropdownComponent_ng_template_6_Conditional_2_Template(rf, ctx) { if (rf & 1) {
22
28
  const _r4 = i0.ɵɵgetCurrentView();
23
- i0.ɵɵelementStart(0, "div", 12);
29
+ i0.ɵɵelementStart(0, "div", 13);
24
30
  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)); });
25
31
  i0.ɵɵtext(1);
26
32
  i0.ɵɵelementEnd();
@@ -35,7 +41,7 @@ function MJDropdownComponent_ng_template_6_For_4_Conditional_1_ng_container_0_Te
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
46
  const item_r6 = i0.ɵɵnextContext().$implicit;
41
47
  const ctx_r2 = i0.ɵɵnextContext(2);
@@ -50,7 +56,7 @@ function MJDropdownComponent_ng_template_6_For_4_Conditional_2_Template(rf, ctx)
50
56
  } }
51
57
  function MJDropdownComponent_ng_template_6_For_4_Template(rf, ctx) { if (rf & 1) {
52
58
  const _r5 = i0.ɵɵgetCurrentView();
53
- i0.ɵɵelementStart(0, "div", 13);
59
+ i0.ɵɵelementStart(0, "div", 14);
54
60
  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)); });
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();
@@ -70,13 +76,14 @@ 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
85
  const ctx_r2 = i0.ɵɵnextContext();
86
+ i0.ɵɵattribute("id", ctx_r2.ListboxId)("aria-label", ctx_r2.AriaLabel || null)("aria-labelledby", ctx_r2.AriaLabelledBy || null);
80
87
  i0.ɵɵadvance();
81
88
  i0.ɵɵconditional(ctx_r2.Filterable ? 1 : -1);
82
89
  i0.ɵɵadvance();
@@ -91,9 +98,27 @@ function MJDropdownComponent_ng_template_6_Template(rf, ctx) { if (rf & 1) {
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)
95
106
  * ```html
107
+ * <span id="persona-label">Interview persona</span>
96
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
119
+ * ```html
120
+ * <mj-dropdown
121
+ * AriaLabel="Interview persona"
97
122
  * [Data]="items"
98
123
  * TextField="name"
99
124
  * ValueField="id"
@@ -105,6 +130,30 @@ 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 = '';
@@ -122,6 +171,34 @@ export class MJDropdownComponent {
122
171
  cdr = inject(ChangeDetectorRef);
123
172
  static nextId = 0;
124
173
  DropdownId = MJDropdownComponent.nextId++;
174
+ /**
175
+ * Id of the popup listbox, so the trigger can point `aria-controls` at it while open — the half of
176
+ * the combobox pattern that makes "collapsed/expanded" refer to something a screen reader can
177
+ * find. Generated per instance from the same `static nextId` counter `dialog`, `window` and
178
+ * `accordion` use in this package.
179
+ */
180
+ get ListboxId() { return `mj-dropdown-listbox-${this.DropdownId}`; }
181
+ /** Id of the hidden "Filter" word, composed into the filter box's name via an id list. */
182
+ get FilterWordId() { return `mj-dropdown-filter-word-${this.DropdownId}`; }
183
+ /**
184
+ * `aria-labelledby` for the filter box when the dropdown is named by a VISIBLE label: "Filter"
185
+ * plus that label's own text, composed by the accessibility tree. Empty when there is no such
186
+ * label, in which case {@link FilterLabel} supplies a string instead.
187
+ */
188
+ get FilterLabelledBy() { return this.AriaLabelledBy ? `${this.FilterWordId} ${this.AriaLabelledBy}` : ''; }
189
+ /**
190
+ * `aria-label` for the filter box in the no-visible-label case.
191
+ *
192
+ * The "filter" guard is not cosmetic: this repo's house habit is `AriaLabel="Filter roles"`, and
193
+ * an unconditional prefix announces that box as "Filter Filter roles". A name that already begins
194
+ * with the word is used as-is.
195
+ */
196
+ get FilterLabel() {
197
+ const name = this.AriaLabel.trim();
198
+ if (!name)
199
+ return 'Filter options';
200
+ return /^filter\b/i.test(name) ? name : `Filter ${name}`;
201
+ }
125
202
  IsOpen = false;
126
203
  IsDisabled = false;
127
204
  HighlightedIndex = -1;
@@ -293,11 +370,11 @@ export class MJDropdownComponent {
293
370
  i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.filterInputEl = _t.first);
294
371
  } }, hostVars: 2, hostBindings: function MJDropdownComponent_HostBindings(rf, ctx) { if (rf & 2) {
295
372
  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([{
373
+ } }, 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
374
  provide: NG_VALUE_ACCESSOR,
298
375
  useExisting: forwardRef(() => MJDropdownComponent),
299
376
  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) {
377
+ }])], 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) {
301
378
  const _r1 = i0.ɵɵgetCurrentView();
302
379
  i0.ɵɵelementStart(0, "div", 2, 0);
303
380
  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()); });
@@ -306,12 +383,12 @@ export class MJDropdownComponent {
306
383
  i0.ɵɵelementEnd();
307
384
  i0.ɵɵelement(5, "i", 4);
308
385
  i0.ɵɵelementEnd();
309
- i0.ɵɵtemplate(6, MJDropdownComponent_ng_template_6_Template, 6, 3, "ng-template", 5);
386
+ i0.ɵɵtemplate(6, MJDropdownComponent_ng_template_6_Template, 6, 6, "ng-template", 5);
310
387
  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()); });
311
388
  } if (rf & 2) {
312
389
  const overlayOrigin_r8 = i0.ɵɵreference(2);
313
390
  i0.ɵɵclassProp("mj-dropdown--open", ctx.IsOpen)("mj-dropdown--disabled", ctx.IsDisabled);
314
- i0.ɵɵattribute("aria-expanded", ctx.IsOpen);
391
+ 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
392
  i0.ɵɵadvance(3);
316
393
  i0.ɵɵclassProp("mj-dropdown-placeholder", !ctx.HasValue);
317
394
  i0.ɵɵadvance();
@@ -335,9 +412,15 @@ export class MJDropdownComponent {
335
412
  [class.mj-dropdown--open]="IsOpen"
336
413
  [class.mj-dropdown--disabled]="IsDisabled"
337
414
  role="combobox"
415
+ [attr.id]="InputId || null"
416
+ [attr.aria-label]="AriaLabel || null"
417
+ [attr.aria-labelledby]="AriaLabelledBy || null"
418
+ [attr.aria-describedby]="AriaDescribedBy || null"
338
419
  [attr.aria-expanded]="IsOpen"
339
420
  aria-haspopup="listbox"
340
- tabindex="0"
421
+ [attr.aria-controls]="IsOpen ? ListboxId : null"
422
+ [attr.aria-disabled]="IsDisabled ? 'true' : null"
423
+ [attr.tabindex]="IsDisabled ? -1 : 0"
341
424
  (click)="Toggle()"
342
425
  (keydown)="OnKeyDown($event)"
343
426
  (blur)="OnBlur()">
@@ -357,14 +440,28 @@ export class MJDropdownComponent {
357
440
  cdkConnectedOverlayBackdropClass="mj-dropdown-backdrop"
358
441
  (backdropClick)="Close()"
359
442
  (detach)="Close()">
360
- <div class="mj-dropdown-panel" role="listbox">
443
+ <div class="mj-dropdown-panel" role="listbox"
444
+ [attr.id]="ListboxId"
445
+ [attr.aria-label]="AriaLabel || null"
446
+ [attr.aria-labelledby]="AriaLabelledBy || null">
361
447
  @if (Filterable) {
362
448
  <div class="mj-dropdown-filter-wrap">
449
+ <!--
450
+ The word "Filter" lives in a hidden span rather than in a concatenated string so the
451
+ AriaLabelledBy path can NAME this box from the same visible label that names the
452
+ dropdown: aria-labelledby takes an ID LIST, so "Filter" plus the label's own text is
453
+ composed by the accessibility tree without this component ever seeing that text.
454
+ Without it, every filterable dropdown on a form named this way announces as an
455
+ identical "Filter options".
456
+ -->
457
+ <span class="mj-dropdown-sr-only" [attr.id]="FilterWordId">Filter</span>
363
458
  <input
364
459
  #filterInput
365
460
  class="mj-input mj-dropdown-filter"
366
461
  type="text"
367
- placeholder="Search..."
462
+ placeholder="Filter..."
463
+ [attr.aria-labelledby]="FilterLabelledBy"
464
+ [attr.aria-label]="FilterLabelledBy ? null : FilterLabel"
368
465
  [value]="filterText"
369
466
  (input)="OnFilterInput($event)"
370
467
  (keydown)="OnKeyDown($event)" />
@@ -408,7 +505,15 @@ export class MJDropdownComponent {
408
505
  multi: true
409
506
  }]
410
507
  }]
411
- }], null, { Data: [{
508
+ }], null, { AriaLabel: [{
509
+ type: Input
510
+ }], AriaLabelledBy: [{
511
+ type: Input
512
+ }], InputId: [{
513
+ type: Input
514
+ }], AriaDescribedBy: [{
515
+ type: Input
516
+ }], Data: [{
412
517
  type: Input
413
518
  }], TextField: [{
414
519
  type: Input
@@ -441,5 +546,5 @@ export class MJDropdownComponent {
441
546
  type: HostBinding,
442
547
  args: ['class.mj-dropdown-host']
443
548
  }] }); })();
444
- (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(MJDropdownComponent, { className: "MJDropdownComponent", filePath: "lib/dropdown/dropdown.component.ts", lineNumber: 124 }); })();
549
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(MJDropdownComponent, { className: "MJDropdownComponent", filePath: "lib/dropdown/dropdown.component.ts", lineNumber: 162 }); })();
445
550
  //# sourceMappingURL=dropdown.component.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"dropdown.component.js","sourceRoot":"","sources":["../../../src/lib/dropdown/dropdown.component.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n Component,\n Input,\n Output,\n EventEmitter,\n forwardRef,\n HostBinding,\n ElementRef,\n ViewChild,\n ContentChild,\n TemplateRef,\n OnDestroy,\n inject,\n ChangeDetectorRef\n} from '@angular/core';\nimport { NgTemplateOutlet } from '@angular/common';\nimport { NG_VALUE_ACCESSOR, ControlValueAccessor } from '@angular/forms';\nimport { OverlayModule, ConnectedPosition } from '@angular/cdk/overlay';\n\n/**\n * mj-dropdown — Dropdown select component using CDK Overlay.\n *\n * Replaces `<kendo-dropdownlist>`.\n *\n * @example\n * ```html\n * <mj-dropdown\n * [Data]=\"items\"\n * TextField=\"name\"\n * ValueField=\"id\"\n * [(ngModel)]=\"selectedId\"\n * [ValuePrimitive]=\"true\"\n * [Filterable]=\"true\"\n * (FilterChange)=\"onFilter($event)\">\n * </mj-dropdown>\n * ```\n */\n@Component({\n selector: 'mj-dropdown',\n standalone: true,\n imports: [NgTemplateOutlet, OverlayModule],\n template: `\n <div\n class=\"mj-dropdown\"\n #trigger\n cdkOverlayOrigin\n #overlayOrigin=\"cdkOverlayOrigin\"\n [class.mj-dropdown--open]=\"IsOpen\"\n [class.mj-dropdown--disabled]=\"IsDisabled\"\n role=\"combobox\"\n [attr.aria-expanded]=\"IsOpen\"\n aria-haspopup=\"listbox\"\n tabindex=\"0\"\n (click)=\"Toggle()\"\n (keydown)=\"OnKeyDown($event)\"\n (blur)=\"OnBlur()\">\n <span class=\"mj-dropdown-value\" [class.mj-dropdown-placeholder]=\"!HasValue\">\n {{ DisplayText }}\n </span>\n <i class=\"fa-solid fa-chevron-down mj-dropdown-arrow\"></i>\n </div>\n\n <ng-template\n cdkConnectedOverlay\n [cdkConnectedOverlayOrigin]=\"overlayOrigin\"\n [cdkConnectedOverlayOpen]=\"IsOpen\"\n [cdkConnectedOverlayPositions]=\"Positions\"\n [cdkConnectedOverlayWidth]=\"TriggerWidth\"\n [cdkConnectedOverlayHasBackdrop]=\"true\"\n cdkConnectedOverlayBackdropClass=\"mj-dropdown-backdrop\"\n (backdropClick)=\"Close()\"\n (detach)=\"Close()\">\n <div class=\"mj-dropdown-panel\" role=\"listbox\">\n @if (Filterable) {\n <div class=\"mj-dropdown-filter-wrap\">\n <input\n #filterInput\n class=\"mj-input mj-dropdown-filter\"\n type=\"text\"\n placeholder=\"Search...\"\n [value]=\"filterText\"\n (input)=\"OnFilterInput($event)\"\n (keydown)=\"OnKeyDown($event)\" />\n </div>\n }\n @if (DefaultItem != null) {\n <div\n class=\"mj-dropdown-option mj-dropdown-option--default\"\n [class.mj-dropdown-option--selected]=\"SelectedValue == null\"\n [class.mj-dropdown-option--highlighted]=\"HighlightedIndex === -1\"\n role=\"option\"\n [attr.aria-selected]=\"SelectedValue == null\"\n (click)=\"SelectItem(null, $event)\">\n {{ GetItemText(DefaultItem) }}\n </div>\n }\n @for (item of FilteredItems; track TrackByIndex($index)) {\n <div\n class=\"mj-dropdown-option\"\n [class.mj-dropdown-option--selected]=\"IsItemSelected(item)\"\n [class.mj-dropdown-option--highlighted]=\"HighlightedIndex === $index\"\n role=\"option\"\n [attr.aria-selected]=\"IsItemSelected(item)\"\n (click)=\"SelectItem(item, $event)\">\n @if (itemTemplate) {\n <ng-container *ngTemplateOutlet=\"itemTemplate; context: { $implicit: item }\"></ng-container>\n } @else {\n {{ GetItemText(item) }}\n }\n </div>\n }\n @if (FilteredItems.length === 0) {\n <div class=\"mj-dropdown-no-data\">No data found</div>\n }\n </div>\n </ng-template>\n `,\n providers: [{\n provide: NG_VALUE_ACCESSOR,\n useExisting: forwardRef(() => MJDropdownComponent),\n multi: true\n }]\n})\nexport class MJDropdownComponent implements ControlValueAccessor, OnDestroy {\n @Input() Data: Record<string, unknown>[] | string[] | readonly unknown[] | null = [];\n @Input() TextField = '';\n @Input() ValueField = '';\n @Input() Filterable = false;\n @Input() ValuePrimitive = false;\n @Input() Disabled = false;\n @Input() Placeholder = 'Select...';\n @Input() DefaultItem: Record<string, unknown> | string | null = null;\n\n @Output() FilterChange = new EventEmitter<string>();\n @Output() ValueChange = new EventEmitter<unknown>();\n\n @ContentChild('mjDropdownItem') itemTemplate: TemplateRef<{ $implicit: unknown }> | null = null;\n\n @ViewChild('trigger') private triggerEl!: ElementRef<HTMLElement>;\n @ViewChild('filterInput') private filterInputEl: ElementRef<HTMLInputElement> | undefined;\n\n @HostBinding('class.mj-dropdown-host') readonly hostClass = true;\n\n private cdr = inject(ChangeDetectorRef);\n private static nextId = 0;\n\n DropdownId = MJDropdownComponent.nextId++;\n IsOpen = false;\n IsDisabled = false;\n HighlightedIndex = -1;\n SelectedValue: unknown = null;\n TriggerWidth = 0;\n\n filterText = '';\n\n Positions: ConnectedPosition[] = [\n { originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top' },\n { originX: 'start', originY: 'top', overlayX: 'start', overlayY: 'bottom' }\n ];\n\n private onChange: (value: unknown) => void = () => {};\n private onTouched: () => void = () => {};\n\n get FilteredItems(): unknown[] {\n const data = (this.Data ?? []) as unknown[];\n if (!this.Filterable || !this.filterText) return data;\n const search = this.filterText.toLowerCase();\n return data.filter(item => this.GetItemText(item).toLowerCase().includes(search));\n }\n\n get HasValue(): boolean { return this.SelectedValue != null; }\n\n get DisplayText(): string {\n if (this.SelectedValue == null) {\n if (this.DefaultItem != null) return this.GetItemText(this.DefaultItem);\n return this.Placeholder;\n }\n if (this.ValuePrimitive && this.ValueField) {\n const found = ((this.Data ?? []) as unknown[]).find(item => this.GetItemValue(item) === this.SelectedValue);\n return found ? this.GetItemText(found) : String(this.SelectedValue);\n }\n return this.GetItemText(this.SelectedValue as Record<string, unknown> | string);\n }\n\n Toggle(): void {\n if (this.IsDisabled) return;\n this.IsOpen ? this.Close() : this.Open();\n }\n\n Open(): void {\n if (this.IsDisabled || this.IsOpen) return;\n this.TriggerWidth = this.triggerEl?.nativeElement.offsetWidth ?? 200;\n this.IsOpen = true;\n this.HighlightedIndex = this.getSelectedIndex();\n this.cdr.detectChanges();\n if (this.Filterable) setTimeout(() => this.filterInputEl?.nativeElement.focus(), 0);\n }\n\n Close(): void {\n if (!this.IsOpen) return;\n this.IsOpen = false;\n this.filterText = '';\n this.HighlightedIndex = -1;\n this.cdr.detectChanges();\n }\n\n SelectItem(item: unknown | null, event?: Event): void {\n event?.stopPropagation();\n if (item == null) {\n this.SelectedValue = null;\n this.onChange(null);\n this.ValueChange.emit(null);\n } else if (this.ValuePrimitive && this.ValueField) {\n const value = this.GetItemValue(item);\n this.SelectedValue = value;\n this.onChange(value);\n this.ValueChange.emit(value);\n } else {\n this.SelectedValue = item;\n this.onChange(item);\n this.ValueChange.emit(item);\n }\n this.Close();\n }\n\n IsItemSelected(item: unknown): boolean {\n if (this.SelectedValue == null) return false;\n if (this.ValuePrimitive && this.ValueField) return this.GetItemValue(item) === this.SelectedValue;\n if (this.ValueField && typeof item === 'object' && typeof this.SelectedValue === 'object')\n return this.GetItemValue(item) === this.GetItemValue(this.SelectedValue);\n return item === this.SelectedValue;\n }\n\n OnFilterInput(event: Event): void {\n this.filterText = (event.target as HTMLInputElement).value;\n this.HighlightedIndex = 0;\n this.FilterChange.emit(this.filterText);\n }\n\n OnKeyDown(event: KeyboardEvent): void {\n const items = this.FilteredItems;\n switch (event.key) {\n case 'ArrowDown':\n event.preventDefault();\n if (!this.IsOpen) this.Open();\n else this.HighlightedIndex = Math.min(this.HighlightedIndex + 1, items.length - 1);\n break;\n case 'ArrowUp':\n event.preventDefault();\n if (this.IsOpen) this.HighlightedIndex = Math.max(this.HighlightedIndex - 1, 0);\n break;\n case 'Enter':\n event.preventDefault();\n if (this.IsOpen && this.HighlightedIndex >= 0 && this.HighlightedIndex < items.length)\n this.SelectItem(items[this.HighlightedIndex]);\n else if (!this.IsOpen) this.Open();\n break;\n case 'Escape': event.preventDefault(); this.Close(); break;\n case 'Home': if (this.IsOpen) { event.preventDefault(); this.HighlightedIndex = 0; } break;\n case 'End': if (this.IsOpen) { event.preventDefault(); this.HighlightedIndex = items.length - 1; } break;\n }\n }\n\n OnBlur(): void { this.onTouched(); }\n\n GetItemText(item: unknown): string {\n if (item == null) return '';\n if (typeof item === 'string') return item;\n if (this.TextField && typeof item === 'object') return String((item as Record<string, unknown>)[this.TextField] ?? '');\n return String(item);\n }\n\n GetItemValue(item: unknown): unknown {\n if (typeof item === 'string') return item;\n if (this.ValueField && typeof item === 'object' && item != null) return (item as Record<string, unknown>)[this.ValueField];\n return item;\n }\n\n TrackByIndex(index: number): number { return index; }\n\n writeValue(value: unknown): void { this.SelectedValue = value; }\n registerOnChange(fn: (value: unknown) => void): void { this.onChange = fn; }\n registerOnTouched(fn: () => void): void { this.onTouched = fn; }\n setDisabledState(isDisabled: boolean): void { this.IsDisabled = isDisabled || this.Disabled; }\n ngOnDestroy(): void { this.Close(); }\n\n private getSelectedIndex(): number {\n if (this.SelectedValue == null) return -1;\n return this.FilteredItems.findIndex(item => this.IsItemSelected(item));\n }\n}\n"]}
1
+ {"version":3,"file":"dropdown.component.js","sourceRoot":"","sources":["../../../src/lib/dropdown/dropdown.component.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n Component,\n Input,\n Output,\n EventEmitter,\n forwardRef,\n HostBinding,\n ElementRef,\n ViewChild,\n ContentChild,\n TemplateRef,\n OnDestroy,\n inject,\n ChangeDetectorRef\n} from '@angular/core';\nimport { NgTemplateOutlet } from '@angular/common';\nimport { NG_VALUE_ACCESSOR, ControlValueAccessor } from '@angular/forms';\nimport { OverlayModule, ConnectedPosition } from '@angular/cdk/overlay';\n\n/**\n * mj-dropdown — Dropdown select component using CDK Overlay.\n *\n * Replaces `<kendo-dropdownlist>`.\n *\n * Every dropdown needs an ACCESSIBLE NAME, or it announces as \"combobox, collapsed\" with no hint of\n * what it selects (WCAG 2.1 4.1.2). Use {@link AriaLabelledBy} when a visible label already exists —\n * `<label for>` cannot name a `div[role=combobox]` — and {@link AriaLabel} when none does.\n *\n * @example With a visible label (preferred)\n * ```html\n * <span id=\"persona-label\">Interview persona</span>\n * <mj-dropdown\n * AriaLabelledBy=\"persona-label\"\n * [Data]=\"items\"\n * TextField=\"name\"\n * ValueField=\"id\"\n * [(ngModel)]=\"selectedId\"\n * [ValuePrimitive]=\"true\">\n * </mj-dropdown>\n * ```\n *\n * @example With no visible label\n * ```html\n * <mj-dropdown\n * AriaLabel=\"Interview persona\"\n * [Data]=\"items\"\n * TextField=\"name\"\n * ValueField=\"id\"\n * [(ngModel)]=\"selectedId\"\n * [ValuePrimitive]=\"true\"\n * [Filterable]=\"true\"\n * (FilterChange)=\"onFilter($event)\">\n * </mj-dropdown>\n * ```\n */\n@Component({\n selector: 'mj-dropdown',\n standalone: true,\n imports: [NgTemplateOutlet, OverlayModule],\n template: `\n <div\n class=\"mj-dropdown\"\n #trigger\n cdkOverlayOrigin\n #overlayOrigin=\"cdkOverlayOrigin\"\n [class.mj-dropdown--open]=\"IsOpen\"\n [class.mj-dropdown--disabled]=\"IsDisabled\"\n role=\"combobox\"\n [attr.id]=\"InputId || null\"\n [attr.aria-label]=\"AriaLabel || null\"\n [attr.aria-labelledby]=\"AriaLabelledBy || null\"\n [attr.aria-describedby]=\"AriaDescribedBy || null\"\n [attr.aria-expanded]=\"IsOpen\"\n aria-haspopup=\"listbox\"\n [attr.aria-controls]=\"IsOpen ? ListboxId : null\"\n [attr.aria-disabled]=\"IsDisabled ? 'true' : null\"\n [attr.tabindex]=\"IsDisabled ? -1 : 0\"\n (click)=\"Toggle()\"\n (keydown)=\"OnKeyDown($event)\"\n (blur)=\"OnBlur()\">\n <span class=\"mj-dropdown-value\" [class.mj-dropdown-placeholder]=\"!HasValue\">\n {{ DisplayText }}\n </span>\n <i class=\"fa-solid fa-chevron-down mj-dropdown-arrow\"></i>\n </div>\n\n <ng-template\n cdkConnectedOverlay\n [cdkConnectedOverlayOrigin]=\"overlayOrigin\"\n [cdkConnectedOverlayOpen]=\"IsOpen\"\n [cdkConnectedOverlayPositions]=\"Positions\"\n [cdkConnectedOverlayWidth]=\"TriggerWidth\"\n [cdkConnectedOverlayHasBackdrop]=\"true\"\n cdkConnectedOverlayBackdropClass=\"mj-dropdown-backdrop\"\n (backdropClick)=\"Close()\"\n (detach)=\"Close()\">\n <div class=\"mj-dropdown-panel\" role=\"listbox\"\n [attr.id]=\"ListboxId\"\n [attr.aria-label]=\"AriaLabel || null\"\n [attr.aria-labelledby]=\"AriaLabelledBy || null\">\n @if (Filterable) {\n <div class=\"mj-dropdown-filter-wrap\">\n <!--\n The word \"Filter\" lives in a hidden span rather than in a concatenated string so the\n AriaLabelledBy path can NAME this box from the same visible label that names the\n dropdown: aria-labelledby takes an ID LIST, so \"Filter\" plus the label's own text is\n composed by the accessibility tree without this component ever seeing that text.\n Without it, every filterable dropdown on a form named this way announces as an\n identical \"Filter options\".\n -->\n <span class=\"mj-dropdown-sr-only\" [attr.id]=\"FilterWordId\">Filter</span>\n <input\n #filterInput\n class=\"mj-input mj-dropdown-filter\"\n type=\"text\"\n placeholder=\"Filter...\"\n [attr.aria-labelledby]=\"FilterLabelledBy\"\n [attr.aria-label]=\"FilterLabelledBy ? null : FilterLabel\"\n [value]=\"filterText\"\n (input)=\"OnFilterInput($event)\"\n (keydown)=\"OnKeyDown($event)\" />\n </div>\n }\n @if (DefaultItem != null) {\n <div\n class=\"mj-dropdown-option mj-dropdown-option--default\"\n [class.mj-dropdown-option--selected]=\"SelectedValue == null\"\n [class.mj-dropdown-option--highlighted]=\"HighlightedIndex === -1\"\n role=\"option\"\n [attr.aria-selected]=\"SelectedValue == null\"\n (click)=\"SelectItem(null, $event)\">\n {{ GetItemText(DefaultItem) }}\n </div>\n }\n @for (item of FilteredItems; track TrackByIndex($index)) {\n <div\n class=\"mj-dropdown-option\"\n [class.mj-dropdown-option--selected]=\"IsItemSelected(item)\"\n [class.mj-dropdown-option--highlighted]=\"HighlightedIndex === $index\"\n role=\"option\"\n [attr.aria-selected]=\"IsItemSelected(item)\"\n (click)=\"SelectItem(item, $event)\">\n @if (itemTemplate) {\n <ng-container *ngTemplateOutlet=\"itemTemplate; context: { $implicit: item }\"></ng-container>\n } @else {\n {{ GetItemText(item) }}\n }\n </div>\n }\n @if (FilteredItems.length === 0) {\n <div class=\"mj-dropdown-no-data\">No data found</div>\n }\n </div>\n </ng-template>\n `,\n providers: [{\n provide: NG_VALUE_ACCESSOR,\n useExisting: forwardRef(() => MJDropdownComponent),\n multi: true\n }]\n})\nexport class MJDropdownComponent implements ControlValueAccessor, OnDestroy {\n /**\n * Accessible name for the combobox (#3860). Without one — and with no visible label wired via\n * {@link AriaLabelledBy} — the control announces as an UNNAMED combobox, which fails WCAG 2.1\n * 4.1.2 (Name, Role, Value): \"combobox, collapsed\" with no hint of what it selects. Applied as\n * `aria-label` on the trigger AND on the popup listbox, so both halves announce the same name.\n */\n @Input() AriaLabel = '';\n\n /**\n * The id of a VISIBLE label element that names this control — the preferred wiring when a label\n * already exists on screen (an `aria-label` would duplicate its text and drift on rename). This,\n * not `<label for>`, is the visible-label path: the trigger is a `div[role=combobox]`, and the\n * label-for association only names labelable form elements. `aria-labelledby` beats `AriaLabel`\n * in the accessible-name computation where both are present. Applied to trigger AND listbox.\n */\n @Input() AriaLabelledBy = '';\n\n /**\n * `id` for the combobox trigger, so other markup can REFERENCE it — `aria-controls`, hint text,\n * test hooks. It is deliberately not documented as a `<label for>` target: the trigger is a div,\n * which `label[for]` neither names nor focuses. To name the control from a visible label, put an\n * id on the LABEL and pass it as {@link AriaLabelledBy}.\n */\n @Input() InputId = '';\n\n /** `aria-describedby` passthrough for hint/error text — same shape of gap as the name. */\n @Input() AriaDescribedBy = '';\n\n @Input() Data: Record<string, unknown>[] | string[] | readonly unknown[] | null = [];\n @Input() TextField = '';\n @Input() ValueField = '';\n @Input() Filterable = false;\n @Input() ValuePrimitive = false;\n @Input() Disabled = false;\n @Input() Placeholder = 'Select...';\n @Input() DefaultItem: Record<string, unknown> | string | null = null;\n\n @Output() FilterChange = new EventEmitter<string>();\n @Output() ValueChange = new EventEmitter<unknown>();\n\n @ContentChild('mjDropdownItem') itemTemplate: TemplateRef<{ $implicit: unknown }> | null = null;\n\n @ViewChild('trigger') private triggerEl!: ElementRef<HTMLElement>;\n @ViewChild('filterInput') private filterInputEl: ElementRef<HTMLInputElement> | undefined;\n\n @HostBinding('class.mj-dropdown-host') readonly hostClass = true;\n\n private cdr = inject(ChangeDetectorRef);\n private static nextId = 0;\n\n DropdownId = MJDropdownComponent.nextId++;\n\n /**\n * Id of the popup listbox, so the trigger can point `aria-controls` at it while open — the half of\n * the combobox pattern that makes \"collapsed/expanded\" refer to something a screen reader can\n * find. Generated per instance from the same `static nextId` counter `dialog`, `window` and\n * `accordion` use in this package.\n */\n get ListboxId(): string { return `mj-dropdown-listbox-${this.DropdownId}`; }\n\n /** Id of the hidden \"Filter\" word, composed into the filter box's name via an id list. */\n get FilterWordId(): string { return `mj-dropdown-filter-word-${this.DropdownId}`; }\n\n /**\n * `aria-labelledby` for the filter box when the dropdown is named by a VISIBLE label: \"Filter\"\n * plus that label's own text, composed by the accessibility tree. Empty when there is no such\n * label, in which case {@link FilterLabel} supplies a string instead.\n */\n get FilterLabelledBy(): string { return this.AriaLabelledBy ? `${this.FilterWordId} ${this.AriaLabelledBy}` : ''; }\n\n /**\n * `aria-label` for the filter box in the no-visible-label case.\n *\n * The \"filter\" guard is not cosmetic: this repo's house habit is `AriaLabel=\"Filter roles\"`, and\n * an unconditional prefix announces that box as \"Filter Filter roles\". A name that already begins\n * with the word is used as-is.\n */\n get FilterLabel(): string {\n const name = this.AriaLabel.trim();\n if (!name) return 'Filter options';\n return /^filter\\b/i.test(name) ? name : `Filter ${name}`;\n }\n IsOpen = false;\n IsDisabled = false;\n HighlightedIndex = -1;\n SelectedValue: unknown = null;\n TriggerWidth = 0;\n\n filterText = '';\n\n Positions: ConnectedPosition[] = [\n { originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top' },\n { originX: 'start', originY: 'top', overlayX: 'start', overlayY: 'bottom' }\n ];\n\n private onChange: (value: unknown) => void = () => {};\n private onTouched: () => void = () => {};\n\n get FilteredItems(): unknown[] {\n const data = (this.Data ?? []) as unknown[];\n if (!this.Filterable || !this.filterText) return data;\n const search = this.filterText.toLowerCase();\n return data.filter(item => this.GetItemText(item).toLowerCase().includes(search));\n }\n\n get HasValue(): boolean { return this.SelectedValue != null; }\n\n get DisplayText(): string {\n if (this.SelectedValue == null) {\n if (this.DefaultItem != null) return this.GetItemText(this.DefaultItem);\n return this.Placeholder;\n }\n if (this.ValuePrimitive && this.ValueField) {\n const found = ((this.Data ?? []) as unknown[]).find(item => this.GetItemValue(item) === this.SelectedValue);\n return found ? this.GetItemText(found) : String(this.SelectedValue);\n }\n return this.GetItemText(this.SelectedValue as Record<string, unknown> | string);\n }\n\n Toggle(): void {\n if (this.IsDisabled) return;\n this.IsOpen ? this.Close() : this.Open();\n }\n\n Open(): void {\n if (this.IsDisabled || this.IsOpen) return;\n this.TriggerWidth = this.triggerEl?.nativeElement.offsetWidth ?? 200;\n this.IsOpen = true;\n this.HighlightedIndex = this.getSelectedIndex();\n this.cdr.detectChanges();\n if (this.Filterable) setTimeout(() => this.filterInputEl?.nativeElement.focus(), 0);\n }\n\n Close(): void {\n if (!this.IsOpen) return;\n this.IsOpen = false;\n this.filterText = '';\n this.HighlightedIndex = -1;\n this.cdr.detectChanges();\n }\n\n SelectItem(item: unknown | null, event?: Event): void {\n event?.stopPropagation();\n if (item == null) {\n this.SelectedValue = null;\n this.onChange(null);\n this.ValueChange.emit(null);\n } else if (this.ValuePrimitive && this.ValueField) {\n const value = this.GetItemValue(item);\n this.SelectedValue = value;\n this.onChange(value);\n this.ValueChange.emit(value);\n } else {\n this.SelectedValue = item;\n this.onChange(item);\n this.ValueChange.emit(item);\n }\n this.Close();\n }\n\n IsItemSelected(item: unknown): boolean {\n if (this.SelectedValue == null) return false;\n if (this.ValuePrimitive && this.ValueField) return this.GetItemValue(item) === this.SelectedValue;\n if (this.ValueField && typeof item === 'object' && typeof this.SelectedValue === 'object')\n return this.GetItemValue(item) === this.GetItemValue(this.SelectedValue);\n return item === this.SelectedValue;\n }\n\n OnFilterInput(event: Event): void {\n this.filterText = (event.target as HTMLInputElement).value;\n this.HighlightedIndex = 0;\n this.FilterChange.emit(this.filterText);\n }\n\n OnKeyDown(event: KeyboardEvent): void {\n const items = this.FilteredItems;\n switch (event.key) {\n case 'ArrowDown':\n event.preventDefault();\n if (!this.IsOpen) this.Open();\n else this.HighlightedIndex = Math.min(this.HighlightedIndex + 1, items.length - 1);\n break;\n case 'ArrowUp':\n event.preventDefault();\n if (this.IsOpen) this.HighlightedIndex = Math.max(this.HighlightedIndex - 1, 0);\n break;\n case 'Enter':\n event.preventDefault();\n if (this.IsOpen && this.HighlightedIndex >= 0 && this.HighlightedIndex < items.length)\n this.SelectItem(items[this.HighlightedIndex]);\n else if (!this.IsOpen) this.Open();\n break;\n case 'Escape': event.preventDefault(); this.Close(); break;\n case 'Home': if (this.IsOpen) { event.preventDefault(); this.HighlightedIndex = 0; } break;\n case 'End': if (this.IsOpen) { event.preventDefault(); this.HighlightedIndex = items.length - 1; } break;\n }\n }\n\n OnBlur(): void { this.onTouched(); }\n\n GetItemText(item: unknown): string {\n if (item == null) return '';\n if (typeof item === 'string') return item;\n if (this.TextField && typeof item === 'object') return String((item as Record<string, unknown>)[this.TextField] ?? '');\n return String(item);\n }\n\n GetItemValue(item: unknown): unknown {\n if (typeof item === 'string') return item;\n if (this.ValueField && typeof item === 'object' && item != null) return (item as Record<string, unknown>)[this.ValueField];\n return item;\n }\n\n TrackByIndex(index: number): number { return index; }\n\n writeValue(value: unknown): void { this.SelectedValue = value; }\n registerOnChange(fn: (value: unknown) => void): void { this.onChange = fn; }\n registerOnTouched(fn: () => void): void { this.onTouched = fn; }\n setDisabledState(isDisabled: boolean): void { this.IsDisabled = isDisabled || this.Disabled; }\n ngOnDestroy(): void { this.Close(); }\n\n private getSelectedIndex(): number {\n if (this.SelectedValue == null) return -1;\n return this.FilteredItems.findIndex(item => this.IsItemSelected(item));\n }\n}\n"]}
@@ -66,3 +66,19 @@
66
66
  .mj-dropdown-no-data { padding: 16px 12px; color: var(--mj-text-muted); text-align: center; font-size: var(--mj-text-sm); }
67
67
 
68
68
  @media (max-width: 767px) { .mj-dropdown-panel { max-height: 50vh; } }
69
+
70
+ /* Visually hidden, still in the accessibility tree — carries the word "Filter" that composes the
71
+ filter box's name with the dropdown's own visible label via an aria-labelledby id list (#3860).
72
+ Not `display:none` or `visibility:hidden`: both remove the element from the accessibility tree,
73
+ which is the one thing this element exists to be in. */
74
+ .mj-dropdown-sr-only {
75
+ position: absolute;
76
+ width: 1px;
77
+ height: 1px;
78
+ padding: 0;
79
+ margin: -1px;
80
+ overflow: hidden;
81
+ clip: rect(0, 0, 0, 0);
82
+ white-space: nowrap;
83
+ border: 0;
84
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@memberjunction/ng-ui-components",
3
- "version": "6.1.0-edge.4",
3
+ "version": "6.1.0-edge.5",
4
4
  "description": "MemberJunction: Angular UI Components - reusable standalone components replacing Kendo UI dependencies.",
5
5
  "main": "./dist/public-api.js",
6
6
  "typings": "./dist/public-api.d.ts",
@@ -16,7 +16,7 @@
16
16
  "devDependencies": {
17
17
  "@angular/compiler": "21.1.3",
18
18
  "@angular/compiler-cli": "21.1.3",
19
- "@memberjunction/ng-test-utils": "6.1.0-edge.4",
19
+ "@memberjunction/ng-test-utils": "6.1.0-edge.5",
20
20
  "vitest": "^4.0.18"
21
21
  },
22
22
  "peerDependencies": {