@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 +9 -1
- package/dist/lib/dropdown/dropdown.component.d.ts +67 -2
- package/dist/lib/dropdown/dropdown.component.d.ts.map +1 -1
- package/dist/lib/dropdown/dropdown.component.js +121 -16
- package/dist/lib/dropdown/dropdown.component.js.map +1 -1
- package/dist/lib/dropdown/dropdown.scss +16 -0
- package/package.json +2 -2
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
|
-
|
|
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
|
-
*
|
|
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
|
|
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, "
|
|
14
|
-
i0.ɵɵ
|
|
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",
|
|
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",
|
|
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",
|
|
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,
|
|
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
|
-
*
|
|
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:
|
|
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,
|
|
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
|
-
|
|
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="
|
|
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, {
|
|
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:
|
|
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.
|
|
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.
|
|
19
|
+
"@memberjunction/ng-test-utils": "6.1.0-edge.5",
|
|
20
20
|
"vitest": "^4.0.18"
|
|
21
21
|
},
|
|
22
22
|
"peerDependencies": {
|