@fluentui/web-components 3.0.0-beta.77 → 3.0.0-beta.79
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/CHANGELOG.md +21 -2
- package/dist/dts/dropdown/define.d.ts +1 -0
- package/dist/dts/dropdown/dropdown.d.ts +492 -0
- package/dist/dts/dropdown/dropdown.definition.d.ts +9 -0
- package/dist/dts/dropdown/dropdown.options.d.ts +53 -0
- package/dist/dts/dropdown/dropdown.styles.d.ts +6 -0
- package/dist/dts/dropdown/dropdown.template.d.ts +38 -0
- package/dist/dts/dropdown/index.d.ts +5 -0
- package/dist/dts/index-rollup.d.ts +3 -0
- package/dist/dts/index.d.ts +3 -0
- package/dist/dts/listbox/define.d.ts +1 -0
- package/dist/dts/listbox/index.d.ts +5 -0
- package/dist/dts/listbox/listbox.d.ts +116 -0
- package/dist/dts/listbox/listbox.definition.d.ts +9 -0
- package/dist/dts/listbox/listbox.options.d.ts +10 -0
- package/dist/dts/listbox/listbox.styles.d.ts +6 -0
- package/dist/dts/listbox/listbox.template.d.ts +17 -0
- package/dist/dts/menu-item/menu-item.d.ts +1 -6
- package/dist/dts/menu-item/menu-item.template.d.ts +1 -1
- package/dist/dts/option/define.d.ts +1 -0
- package/dist/dts/option/index.d.ts +5 -0
- package/dist/dts/option/option.d.ts +260 -0
- package/dist/dts/option/option.definition.d.ts +9 -0
- package/dist/dts/option/option.options.d.ts +20 -0
- package/dist/dts/option/option.styles.d.ts +6 -0
- package/dist/dts/option/option.template.d.ts +16 -0
- package/dist/dts/patterns/start-end.d.ts +16 -2
- package/dist/dts/styles/states/index.d.ts +34 -0
- package/dist/dts/utils/element-internals.d.ts +3 -6
- package/dist/dts/utils/index.d.ts +1 -0
- package/dist/dts/utils/language.d.ts +9 -0
- package/dist/dts/utils/support.d.ts +15 -0
- package/dist/dts/utils/unique-id.d.ts +9 -0
- package/dist/esm/accordion/accordion.js +2 -3
- package/dist/esm/accordion/accordion.js.map +1 -1
- package/dist/esm/anchor-button/anchor-button.js +2 -4
- package/dist/esm/anchor-button/anchor-button.js.map +1 -1
- package/dist/esm/avatar/avatar.js +11 -12
- package/dist/esm/avatar/avatar.js.map +1 -1
- package/dist/esm/button/button.js +19 -23
- package/dist/esm/button/button.js.map +1 -1
- package/dist/esm/button/button.template.js +1 -1
- package/dist/esm/button/button.template.js.map +1 -1
- package/dist/esm/checkbox/checkbox.js +10 -11
- package/dist/esm/checkbox/checkbox.js.map +1 -1
- package/dist/esm/compound-button/compound-button.template.js +1 -1
- package/dist/esm/compound-button/compound-button.template.js.map +1 -1
- package/dist/esm/counter-badge/counter-badge.js +1 -2
- package/dist/esm/counter-badge/counter-badge.js.map +1 -1
- package/dist/esm/dialog-body/dialog-body.template.js +2 -2
- package/dist/esm/dialog-body/dialog-body.template.js.map +1 -1
- package/dist/esm/divider/divider.js +4 -5
- package/dist/esm/divider/divider.js.map +1 -1
- package/dist/esm/dropdown/define.js +4 -0
- package/dist/esm/dropdown/define.js.map +1 -0
- package/dist/esm/dropdown/dropdown.definition.js +20 -0
- package/dist/esm/dropdown/dropdown.definition.js.map +1 -0
- package/dist/esm/dropdown/dropdown.js +779 -0
- package/dist/esm/dropdown/dropdown.js.map +1 -0
- package/dist/esm/dropdown/dropdown.options.js +43 -0
- package/dist/esm/dropdown/dropdown.options.js.map +1 -0
- package/dist/esm/dropdown/dropdown.styles.js +213 -0
- package/dist/esm/dropdown/dropdown.styles.js.map +1 -0
- package/dist/esm/dropdown/dropdown.template.js +92 -0
- package/dist/esm/dropdown/dropdown.template.js.map +1 -0
- package/dist/esm/dropdown/index.js +6 -0
- package/dist/esm/dropdown/index.js.map +1 -0
- package/dist/esm/field/field.js +3 -4
- package/dist/esm/field/field.js.map +1 -1
- package/dist/esm/form-associated/form-associated.js +3 -5
- package/dist/esm/form-associated/form-associated.js.map +1 -1
- package/dist/esm/index-rollup.js +3 -0
- package/dist/esm/index-rollup.js.map +1 -1
- package/dist/esm/index.js +3 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/listbox/define.js +4 -0
- package/dist/esm/listbox/define.js.map +1 -0
- package/dist/esm/listbox/index.js +6 -0
- package/dist/esm/listbox/index.js.map +1 -0
- package/dist/esm/listbox/listbox.definition.js +17 -0
- package/dist/esm/listbox/listbox.definition.js.map +1 -0
- package/dist/esm/listbox/listbox.js +175 -0
- package/dist/esm/listbox/listbox.js.map +1 -0
- package/dist/esm/listbox/listbox.options.js +15 -0
- package/dist/esm/listbox/listbox.options.js.map +1 -0
- package/dist/esm/listbox/listbox.styles.js +26 -0
- package/dist/esm/listbox/listbox.styles.js.map +1 -0
- package/dist/esm/listbox/listbox.template.js +33 -0
- package/dist/esm/listbox/listbox.template.js.map +1 -0
- package/dist/esm/menu/menu.js +23 -32
- package/dist/esm/menu/menu.js.map +1 -1
- package/dist/esm/menu-item/menu-item.js +9 -19
- package/dist/esm/menu-item/menu-item.js.map +1 -1
- package/dist/esm/menu-item/menu-item.template.js +1 -2
- package/dist/esm/menu-item/menu-item.template.js.map +1 -1
- package/dist/esm/menu-list/menu-list.js +4 -5
- package/dist/esm/menu-list/menu-list.js.map +1 -1
- package/dist/esm/option/define.js +4 -0
- package/dist/esm/option/define.js.map +1 -0
- package/dist/esm/option/index.js +6 -0
- package/dist/esm/option/index.js.map +1 -0
- package/dist/esm/option/option.definition.js +17 -0
- package/dist/esm/option/option.definition.js.map +1 -0
- package/dist/esm/option/option.js +296 -0
- package/dist/esm/option/option.js.map +1 -0
- package/dist/esm/option/option.options.js +15 -0
- package/dist/esm/option/option.options.js.map +1 -0
- package/dist/esm/option/option.styles.js +127 -0
- package/dist/esm/option/option.styles.js.map +1 -0
- package/dist/esm/option/option.template.js +42 -0
- package/dist/esm/option/option.template.js.map +1 -0
- package/dist/esm/patterns/start-end.js +12 -0
- package/dist/esm/patterns/start-end.js.map +1 -1
- package/dist/esm/progress-bar/progress-bar.js +3 -4
- package/dist/esm/progress-bar/progress-bar.js.map +1 -1
- package/dist/esm/radio-group/radio-group.js +27 -38
- package/dist/esm/radio-group/radio-group.js.map +1 -1
- package/dist/esm/rating-display/rating-display.js +7 -13
- package/dist/esm/rating-display/rating-display.js.map +1 -1
- package/dist/esm/slider/slider.js +13 -16
- package/dist/esm/slider/slider.js.map +1 -1
- package/dist/esm/styles/states/index.js +34 -0
- package/dist/esm/styles/states/index.js.map +1 -1
- package/dist/esm/tablist/tablist.js +5 -7
- package/dist/esm/tablist/tablist.js.map +1 -1
- package/dist/esm/tabs/tabs.js +5 -8
- package/dist/esm/tabs/tabs.js.map +1 -1
- package/dist/esm/text-input/text-input.js +13 -15
- package/dist/esm/text-input/text-input.js.map +1 -1
- package/dist/esm/textarea/textarea.js +20 -29
- package/dist/esm/textarea/textarea.js.map +1 -1
- package/dist/esm/theme/set-theme.js +1 -2
- package/dist/esm/theme/set-theme.js.map +1 -1
- package/dist/esm/tooltip/tooltip.js +13 -18
- package/dist/esm/tooltip/tooltip.js.map +1 -1
- package/dist/esm/utils/direction.js +1 -2
- package/dist/esm/utils/direction.js.map +1 -1
- package/dist/esm/utils/element-internals.js +8 -11
- package/dist/esm/utils/element-internals.js.map +1 -1
- package/dist/esm/utils/get-initials.js +2 -2
- package/dist/esm/utils/get-initials.js.map +1 -1
- package/dist/esm/utils/index.js +1 -0
- package/dist/esm/utils/index.js.map +1 -1
- package/dist/esm/utils/language.js +12 -0
- package/dist/esm/utils/language.js.map +1 -0
- package/dist/esm/utils/support.js +16 -0
- package/dist/esm/utils/support.js.map +1 -0
- package/dist/esm/utils/unique-id.js +14 -0
- package/dist/esm/utils/unique-id.js.map +1 -0
- package/dist/esm/utils/whitespace-filter.js +1 -1
- package/dist/esm/utils/whitespace-filter.js.map +1 -1
- package/dist/web-components.d.ts +1928 -862
- package/dist/web-components.js +1666 -448
- package/dist/web-components.min.js +344 -322
- package/package.json +1 -1
|
@@ -0,0 +1,779 @@
|
|
|
1
|
+
import { __decorate } from "tslib";
|
|
2
|
+
import { attr, FASTElement, Observable, observable, Updates, volatile } from '@microsoft/fast-element';
|
|
3
|
+
import { isListbox } from '../listbox/listbox.options.js';
|
|
4
|
+
import { isDropdownOption } from '../option/option.options.js';
|
|
5
|
+
import { swapStates, toggleState } from '../utils/element-internals.js';
|
|
6
|
+
import { getLanguage } from '../utils/language.js';
|
|
7
|
+
import { uniqueId } from '../utils/unique-id.js';
|
|
8
|
+
import { DropdownAppearance, DropdownSize, DropdownType } from './dropdown.options.js';
|
|
9
|
+
import { dropdownButtonTemplate, dropdownInputTemplate } from './dropdown.template.js';
|
|
10
|
+
/**
|
|
11
|
+
* A Dropdown Custom HTML Element.
|
|
12
|
+
* Implements the {@link https://w3c.github.io/aria/#combobox | ARIA combobox } role.
|
|
13
|
+
*
|
|
14
|
+
* @remarks
|
|
15
|
+
* The Dropdown element does not provide a form association value. Instead, the slotted Option elements handle form
|
|
16
|
+
* association the same way as {@link (Checkbox:class)} elements. See the {@link (DropdownOption:class)} element for
|
|
17
|
+
* more details.
|
|
18
|
+
*
|
|
19
|
+
* @slot - The default slot. Accepts a {@link (Listbox:class)} element.
|
|
20
|
+
* @slot indicator - The indicator slot.
|
|
21
|
+
* @slot control - The control slot. This slot is automatically populated and should not be manually manipulated.
|
|
22
|
+
*
|
|
23
|
+
* @public
|
|
24
|
+
*/
|
|
25
|
+
export class BaseDropdown extends FASTElement {
|
|
26
|
+
/**
|
|
27
|
+
* The ID of the current active descendant.
|
|
28
|
+
*
|
|
29
|
+
* @public
|
|
30
|
+
*/
|
|
31
|
+
get activeDescendant() {
|
|
32
|
+
if (this.open) {
|
|
33
|
+
return this.enabledOptions[this.activeIndex]?.id;
|
|
34
|
+
}
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Sets the active index when the active index property changes.
|
|
39
|
+
*
|
|
40
|
+
* @param prev - the previous active index
|
|
41
|
+
* @param next - the current active index
|
|
42
|
+
* @internal
|
|
43
|
+
*/
|
|
44
|
+
activeIndexChanged(prev, next) {
|
|
45
|
+
if (typeof next === 'number') {
|
|
46
|
+
const optionIndex = this.matches(':has(:focus-visible)') ? next : -1;
|
|
47
|
+
this.enabledOptions.forEach((option, index) => {
|
|
48
|
+
option.active = index === optionIndex;
|
|
49
|
+
});
|
|
50
|
+
if (this.open) {
|
|
51
|
+
this.enabledOptions[optionIndex]?.scrollIntoView({ block: 'nearest' });
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Updates properties on the control element when the control slot changes.
|
|
57
|
+
*
|
|
58
|
+
* @param prev - the previous control element
|
|
59
|
+
* @param next - the current control element
|
|
60
|
+
* @internal
|
|
61
|
+
* @remarks
|
|
62
|
+
* The control element is assigned to the dropdown via the control slot with manual slot assignment.
|
|
63
|
+
*/
|
|
64
|
+
controlChanged(prev, next) {
|
|
65
|
+
if (next) {
|
|
66
|
+
next.id = next.id || uniqueId('input-');
|
|
67
|
+
this.controlSlot?.assign(next);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Updates the disabled state of the options when the disabled property changes.
|
|
72
|
+
*
|
|
73
|
+
* @param prev - the previous disabled state
|
|
74
|
+
* @param next - the current disabled state
|
|
75
|
+
*/
|
|
76
|
+
disabledChanged(prev, next) {
|
|
77
|
+
Updates.enqueue(() => {
|
|
78
|
+
this.options.forEach(option => {
|
|
79
|
+
option.disabled = option.disabledAttribute || this.disabled;
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* The display value for the control.
|
|
85
|
+
*
|
|
86
|
+
* @public
|
|
87
|
+
*/
|
|
88
|
+
get displayValue() {
|
|
89
|
+
if (!this.$fastController.isConnected || !this.control || (this.isCombobox && this.multiple)) {
|
|
90
|
+
toggleState(this.elementInternals, 'placeholder-shown', false);
|
|
91
|
+
return '';
|
|
92
|
+
}
|
|
93
|
+
this.listFormatter =
|
|
94
|
+
this.listFormatter ??
|
|
95
|
+
new Intl.ListFormat(getLanguage(this), {
|
|
96
|
+
type: 'conjunction',
|
|
97
|
+
style: 'narrow',
|
|
98
|
+
});
|
|
99
|
+
const displayValue = this.listFormatter.format(this.selectedOptions.map(x => x.text));
|
|
100
|
+
toggleState(this.elementInternals, 'placeholder-shown', !displayValue);
|
|
101
|
+
if (this.isCombobox) {
|
|
102
|
+
// comboboxes use an input element for the control, which provides the placeholder behavior
|
|
103
|
+
return displayValue;
|
|
104
|
+
}
|
|
105
|
+
return displayValue || this.placeholder;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Updates properties on the listbox element when the listbox reference changes.
|
|
109
|
+
*
|
|
110
|
+
* @param prev - the previous listbox element
|
|
111
|
+
* @param next - the current listbox element
|
|
112
|
+
* @internal
|
|
113
|
+
*
|
|
114
|
+
* @remarks
|
|
115
|
+
* The listbox element is assigned to the dropdown via the default slot with manual slot assignment.
|
|
116
|
+
*/
|
|
117
|
+
listboxChanged(prev, next) {
|
|
118
|
+
if (prev) {
|
|
119
|
+
Observable.getNotifier(this).unsubscribe(prev);
|
|
120
|
+
}
|
|
121
|
+
if (next) {
|
|
122
|
+
next.dropdown = this;
|
|
123
|
+
next.popover = 'manual';
|
|
124
|
+
this.listboxSlot.assign(next);
|
|
125
|
+
const notifier = Observable.getNotifier(this);
|
|
126
|
+
notifier.subscribe(next);
|
|
127
|
+
for (const key of ['disabled', 'multiple']) {
|
|
128
|
+
notifier.notify(key);
|
|
129
|
+
}
|
|
130
|
+
Updates.enqueue(() => {
|
|
131
|
+
this.enabledOptions
|
|
132
|
+
.filter(x => x.defaultSelected)
|
|
133
|
+
.forEach((x, i) => {
|
|
134
|
+
x.selected = this.multiple || i === 0;
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Toggles between single and multiple selection modes when the `multiple` property changes.
|
|
141
|
+
*
|
|
142
|
+
* @param prev - the previous multiple state
|
|
143
|
+
* @param next - the next multiple state
|
|
144
|
+
* @internal
|
|
145
|
+
*/
|
|
146
|
+
multipleChanged(prev, next) {
|
|
147
|
+
this.elementInternals.ariaMultiSelectable = next ? 'true' : 'false';
|
|
148
|
+
toggleState(this.elementInternals, 'multiple', next);
|
|
149
|
+
this.value = null;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Updates the name of the options when the name property changes.
|
|
153
|
+
*
|
|
154
|
+
* @param prev - the previous name
|
|
155
|
+
* @param next - the current name
|
|
156
|
+
*/
|
|
157
|
+
nameChanged(prev, next) {
|
|
158
|
+
Updates.enqueue(() => {
|
|
159
|
+
this.options.forEach(option => {
|
|
160
|
+
option.name = next;
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Handles the open state of the dropdown.
|
|
166
|
+
*
|
|
167
|
+
* @param prev - the previous open state
|
|
168
|
+
* @param next - the current open state
|
|
169
|
+
*
|
|
170
|
+
* @internal
|
|
171
|
+
*/
|
|
172
|
+
openChanged(prev, next) {
|
|
173
|
+
toggleState(this.elementInternals, 'open', next);
|
|
174
|
+
this.elementInternals.ariaExpanded = next ? 'true' : 'false';
|
|
175
|
+
this.activeIndex = this.selectedIndex ?? -1;
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Changes the slotted control element based on the dropdown type.
|
|
179
|
+
*
|
|
180
|
+
* @param prev - the previous dropdown type
|
|
181
|
+
* @param next - the current dropdown type
|
|
182
|
+
* @internal
|
|
183
|
+
*/
|
|
184
|
+
typeChanged(prev, next) {
|
|
185
|
+
if (this.$fastController.isConnected) {
|
|
186
|
+
this.insertControl();
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* The collection of enabled options.
|
|
191
|
+
* @public
|
|
192
|
+
*/
|
|
193
|
+
get enabledOptions() {
|
|
194
|
+
return this.listbox?.enabledOptions ?? [];
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* The form-associated flag.
|
|
198
|
+
* @see {@link https://html.spec.whatwg.org/multipage/custom-elements.html#custom-elements-face-example | Form-associated custom elements}
|
|
199
|
+
*
|
|
200
|
+
* @public
|
|
201
|
+
*/
|
|
202
|
+
static { this.formAssociated = true; }
|
|
203
|
+
/**
|
|
204
|
+
* Resets the form value to its initial value when the form is reset.
|
|
205
|
+
*
|
|
206
|
+
* @internal
|
|
207
|
+
*/
|
|
208
|
+
formResetCallback() {
|
|
209
|
+
this.enabledOptions.forEach((x, i) => {
|
|
210
|
+
if (this.multiple) {
|
|
211
|
+
x.selected = !!x.defaultSelected;
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
if (!x.defaultSelected) {
|
|
215
|
+
x.selected = false;
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
this.selectOption(i);
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* A reference to the first freeform option, if present.
|
|
223
|
+
*
|
|
224
|
+
* @internal
|
|
225
|
+
*/
|
|
226
|
+
get freeformOption() {
|
|
227
|
+
return this.enabledOptions.find(x => x.freeform);
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Indicates whether the dropdown is a combobox.
|
|
231
|
+
*
|
|
232
|
+
* @internal
|
|
233
|
+
*/
|
|
234
|
+
get isCombobox() {
|
|
235
|
+
return this.type === DropdownType.combobox;
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* The collection of all child options.
|
|
239
|
+
*
|
|
240
|
+
* @public
|
|
241
|
+
*/
|
|
242
|
+
get options() {
|
|
243
|
+
return this.listbox?.options ?? [];
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* The index of the first selected option, scoped to the enabled options.
|
|
247
|
+
*
|
|
248
|
+
* @internal
|
|
249
|
+
* @remarks
|
|
250
|
+
* This property is a reflection of {@link Listbox.selectedIndex}.
|
|
251
|
+
*/
|
|
252
|
+
get selectedIndex() {
|
|
253
|
+
return this.enabledOptions.findIndex(x => x.selected) ?? -1;
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* The collection of selected options.
|
|
257
|
+
*
|
|
258
|
+
* @public
|
|
259
|
+
* @remarks
|
|
260
|
+
* This property is a reflection of {@link Listbox.selectedOptions}.
|
|
261
|
+
*/
|
|
262
|
+
get selectedOptions() {
|
|
263
|
+
return this.listbox?.selectedOptions ?? [];
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* The validation message. Uses the browser's default validation message for native checkboxes if not otherwise
|
|
267
|
+
* specified (e.g., via `setCustomValidity`).
|
|
268
|
+
*
|
|
269
|
+
* @internal
|
|
270
|
+
*/
|
|
271
|
+
get validationMessage() {
|
|
272
|
+
if (this.elementInternals.validationMessage) {
|
|
273
|
+
return this.elementInternals.validationMessage;
|
|
274
|
+
}
|
|
275
|
+
if (!this.validationFallbackMessage) {
|
|
276
|
+
const validationMessageFallbackControl = document.createElement('input');
|
|
277
|
+
validationMessageFallbackControl.type = 'radio';
|
|
278
|
+
validationMessageFallbackControl.required = true;
|
|
279
|
+
validationMessageFallbackControl.checked = false;
|
|
280
|
+
this.validationFallbackMessage = validationMessageFallbackControl.validationMessage;
|
|
281
|
+
}
|
|
282
|
+
return this.validationFallbackMessage;
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* The current value of the selected option.
|
|
286
|
+
*
|
|
287
|
+
* @public
|
|
288
|
+
*/
|
|
289
|
+
get value() {
|
|
290
|
+
Observable.notify(this, 'value');
|
|
291
|
+
return this.enabledOptions.find(x => x.selected)?.value ?? null;
|
|
292
|
+
}
|
|
293
|
+
set value(next) {
|
|
294
|
+
if (this.multiple) {
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
this.selectOption(this.enabledOptions.findIndex(x => x.value === next));
|
|
298
|
+
Observable.track(this, 'value');
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Handles the change events for the dropdown.
|
|
302
|
+
*
|
|
303
|
+
* @param e - the event object
|
|
304
|
+
*
|
|
305
|
+
* @public
|
|
306
|
+
*/
|
|
307
|
+
changeHandler(e) {
|
|
308
|
+
if (this === e.target) {
|
|
309
|
+
return true;
|
|
310
|
+
}
|
|
311
|
+
const optionIndex = this.isCombobox
|
|
312
|
+
? this.enabledOptions.findIndex(x => x.text === this.control.value)
|
|
313
|
+
: this.enabledOptions.indexOf(e.target);
|
|
314
|
+
this.selectOption(optionIndex);
|
|
315
|
+
return true;
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Handles the click events for the dropdown.
|
|
319
|
+
*
|
|
320
|
+
* @param e - the event object
|
|
321
|
+
*
|
|
322
|
+
* @public
|
|
323
|
+
*/
|
|
324
|
+
clickHandler(e) {
|
|
325
|
+
if (this.disabled) {
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
const target = e.target;
|
|
329
|
+
this.focus();
|
|
330
|
+
if (target === this.control && !this.isCombobox) {
|
|
331
|
+
this.listbox.togglePopover();
|
|
332
|
+
return true;
|
|
333
|
+
}
|
|
334
|
+
if (!this.open) {
|
|
335
|
+
this.listbox.showPopover();
|
|
336
|
+
return true;
|
|
337
|
+
}
|
|
338
|
+
if (isDropdownOption(target) && !this.multiple) {
|
|
339
|
+
if (target.disabled) {
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
if (this.isCombobox) {
|
|
343
|
+
this.control.value = target.text;
|
|
344
|
+
this.updateFreeformOption();
|
|
345
|
+
}
|
|
346
|
+
this.listbox.hidePopover();
|
|
347
|
+
}
|
|
348
|
+
return true;
|
|
349
|
+
}
|
|
350
|
+
constructor() {
|
|
351
|
+
super();
|
|
352
|
+
/**
|
|
353
|
+
* The index of the currently active option.
|
|
354
|
+
*
|
|
355
|
+
* @internal
|
|
356
|
+
*/
|
|
357
|
+
this.activeIndex = 0;
|
|
358
|
+
/**
|
|
359
|
+
* Sets the listbox ID to a unique value if one is not provided.
|
|
360
|
+
*
|
|
361
|
+
* @override
|
|
362
|
+
* @public
|
|
363
|
+
* @remarks
|
|
364
|
+
* HTML Attribute: `id`
|
|
365
|
+
*/
|
|
366
|
+
this.id = uniqueId('dropdown-');
|
|
367
|
+
/**
|
|
368
|
+
* The required state of the dropdown.
|
|
369
|
+
*
|
|
370
|
+
* @public
|
|
371
|
+
* @remarks
|
|
372
|
+
* HTML Attribute: `required`
|
|
373
|
+
*/
|
|
374
|
+
this.required = false;
|
|
375
|
+
/**
|
|
376
|
+
* The dropdown type.
|
|
377
|
+
*
|
|
378
|
+
* @public
|
|
379
|
+
* @remarks
|
|
380
|
+
* HTML Attribute: `type`
|
|
381
|
+
*/
|
|
382
|
+
this.type = DropdownType.dropdown;
|
|
383
|
+
/**
|
|
384
|
+
* The initial value of the control. When the control is a combobox, this value is used to set the value of the
|
|
385
|
+
* control when the dropdown is initialized.
|
|
386
|
+
*
|
|
387
|
+
* @public
|
|
388
|
+
* @remarks
|
|
389
|
+
* HTML Attribute: `value`
|
|
390
|
+
*/
|
|
391
|
+
this.valueAttribute = '';
|
|
392
|
+
/**
|
|
393
|
+
* The internal {@link https://developer.mozilla.org/docs/Web/API/ElementInternals | `ElementInternals`} instance for the component.
|
|
394
|
+
*
|
|
395
|
+
* @internal
|
|
396
|
+
*/
|
|
397
|
+
this.elementInternals = this.attachInternals();
|
|
398
|
+
this.elementInternals.role = 'presentation';
|
|
399
|
+
Updates.enqueue(() => {
|
|
400
|
+
this.insertControl();
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* Filters the options based on the input value.
|
|
405
|
+
*
|
|
406
|
+
* @param value - the input value to filter the options by
|
|
407
|
+
* @param collection - the collection of options to filter
|
|
408
|
+
* @returns the filtered options
|
|
409
|
+
* @internal
|
|
410
|
+
*/
|
|
411
|
+
filterOptions(value, collection = this.enabledOptions) {
|
|
412
|
+
if (!this.listCollator) {
|
|
413
|
+
this.listCollator = new Intl.Collator(getLanguage(this), { usage: 'search', sensitivity: 'base' });
|
|
414
|
+
}
|
|
415
|
+
return collection.filter(x => {
|
|
416
|
+
return this.listCollator.compare(x.text.substring(0, Math.min(x.text.length, value.length)), value) === 0;
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
/**
|
|
420
|
+
* Focuses the control when the dropdown receives focus.
|
|
421
|
+
*
|
|
422
|
+
* @internal
|
|
423
|
+
*/
|
|
424
|
+
focus(options) {
|
|
425
|
+
if (this.disabled) {
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
this.control.focus(options);
|
|
429
|
+
}
|
|
430
|
+
/**
|
|
431
|
+
* Toggles the listbox when the control element loses focus.
|
|
432
|
+
*
|
|
433
|
+
* @param e - the focus event
|
|
434
|
+
* @internal
|
|
435
|
+
*/
|
|
436
|
+
focusoutHandler(e) {
|
|
437
|
+
const relatedTarget = e.relatedTarget;
|
|
438
|
+
if (this.open && !this.contains(relatedTarget)) {
|
|
439
|
+
this.listbox.togglePopover();
|
|
440
|
+
}
|
|
441
|
+
return true;
|
|
442
|
+
}
|
|
443
|
+
/**
|
|
444
|
+
* Ensures the active index is within bounds of the enabled options. Out-of-bounds indices are wrapped to the opposite
|
|
445
|
+
* end of the range.
|
|
446
|
+
*
|
|
447
|
+
* @param index - the desired index
|
|
448
|
+
* @param upperBound - the upper bound of the range
|
|
449
|
+
* @returns the index in bounds
|
|
450
|
+
* @internal
|
|
451
|
+
*/
|
|
452
|
+
getEnabledIndexInBounds(index, upperBound = this.enabledOptions.length || 0) {
|
|
453
|
+
if (upperBound === 0) {
|
|
454
|
+
return -1;
|
|
455
|
+
}
|
|
456
|
+
return (index + upperBound) % upperBound;
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* Handles the input events for the dropdown from the control element.
|
|
460
|
+
*
|
|
461
|
+
* @param e - the input event
|
|
462
|
+
* @public
|
|
463
|
+
*/
|
|
464
|
+
inputHandler(e) {
|
|
465
|
+
if (!this.open) {
|
|
466
|
+
this.listbox.showPopover();
|
|
467
|
+
}
|
|
468
|
+
this.updateFreeformOption();
|
|
469
|
+
const controlValue = this.control.value;
|
|
470
|
+
const index = this.enabledOptions.indexOf(this.filterOptions(controlValue)[0] ?? null);
|
|
471
|
+
this.activeIndex = index;
|
|
472
|
+
return true;
|
|
473
|
+
}
|
|
474
|
+
/**
|
|
475
|
+
* Inserts the control element based on the dropdown type.
|
|
476
|
+
*
|
|
477
|
+
* @public
|
|
478
|
+
* @remarks
|
|
479
|
+
* This method can be overridden in derived classes to provide custom control elements, though this is not recommended.
|
|
480
|
+
*/
|
|
481
|
+
insertControl() {
|
|
482
|
+
this.controlSlot?.assignedNodes().forEach(x => this.removeChild(x));
|
|
483
|
+
if (this.type === DropdownType.combobox) {
|
|
484
|
+
dropdownInputTemplate.render(this, this);
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
dropdownButtonTemplate.render(this, this);
|
|
488
|
+
}
|
|
489
|
+
/**
|
|
490
|
+
* Handles the keydown events for the dropdown.
|
|
491
|
+
*
|
|
492
|
+
* @param e - the keyboard event
|
|
493
|
+
* @public
|
|
494
|
+
*/
|
|
495
|
+
keydownHandler(e) {
|
|
496
|
+
let increment = 0;
|
|
497
|
+
switch (e.key) {
|
|
498
|
+
case 'ArrowUp': {
|
|
499
|
+
e.preventDefault();
|
|
500
|
+
increment = -1;
|
|
501
|
+
break;
|
|
502
|
+
}
|
|
503
|
+
case 'ArrowDown': {
|
|
504
|
+
e.preventDefault();
|
|
505
|
+
increment = 1;
|
|
506
|
+
break;
|
|
507
|
+
}
|
|
508
|
+
case ' ': {
|
|
509
|
+
if (this.isCombobox) {
|
|
510
|
+
break;
|
|
511
|
+
}
|
|
512
|
+
e.preventDefault();
|
|
513
|
+
}
|
|
514
|
+
case 'Enter':
|
|
515
|
+
case 'Tab': {
|
|
516
|
+
if (this.open) {
|
|
517
|
+
this.selectOption(this.activeIndex);
|
|
518
|
+
if (this.multiple) {
|
|
519
|
+
break;
|
|
520
|
+
}
|
|
521
|
+
this.listbox.hidePopover();
|
|
522
|
+
return e.key === 'Tab';
|
|
523
|
+
}
|
|
524
|
+
this.listbox.showPopover();
|
|
525
|
+
break;
|
|
526
|
+
}
|
|
527
|
+
case 'Escape': {
|
|
528
|
+
this.activeIndex = this.multiple ? 0 : this.selectedIndex;
|
|
529
|
+
this.listbox.hidePopover();
|
|
530
|
+
break;
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
if (!increment) {
|
|
534
|
+
return true;
|
|
535
|
+
}
|
|
536
|
+
if (!this.open) {
|
|
537
|
+
this.listbox.showPopover();
|
|
538
|
+
return;
|
|
539
|
+
}
|
|
540
|
+
let nextIndex = this.activeIndex;
|
|
541
|
+
nextIndex += increment;
|
|
542
|
+
let indexInBounds = this.getEnabledIndexInBounds(nextIndex);
|
|
543
|
+
if (indexInBounds === 0 && this.freeformOption?.hidden) {
|
|
544
|
+
indexInBounds = this.getEnabledIndexInBounds(nextIndex + increment);
|
|
545
|
+
}
|
|
546
|
+
this.activeIndex = indexInBounds;
|
|
547
|
+
return true;
|
|
548
|
+
}
|
|
549
|
+
/**
|
|
550
|
+
* Prevents the default behavior of the mousedown event. This is necessary to prevent the input from losing focus
|
|
551
|
+
* when the dropdown is open.
|
|
552
|
+
*
|
|
553
|
+
* @param e - the mouse event
|
|
554
|
+
*
|
|
555
|
+
* @internal
|
|
556
|
+
*/
|
|
557
|
+
mousedownHandler(e) {
|
|
558
|
+
if (this.disabled || (e.target === this.control && !this.isCombobox)) {
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
561
|
+
return !isDropdownOption(e.target);
|
|
562
|
+
}
|
|
563
|
+
/**
|
|
564
|
+
* Selects an option by index.
|
|
565
|
+
*
|
|
566
|
+
* @param index - The index of the option to select.
|
|
567
|
+
* @public
|
|
568
|
+
*/
|
|
569
|
+
selectOption(index = this.selectedIndex) {
|
|
570
|
+
this.listbox.selectOption(index);
|
|
571
|
+
this.control.value = this.displayValue;
|
|
572
|
+
this.setValidity();
|
|
573
|
+
this.updateFreeformOption();
|
|
574
|
+
}
|
|
575
|
+
/**
|
|
576
|
+
* Sets the validity of the element.
|
|
577
|
+
*
|
|
578
|
+
* @param flags - Validity flags to set.
|
|
579
|
+
* @param message - Optional message to supply. If not provided, the element's `validationMessage` will be used.
|
|
580
|
+
* @param anchor - Optional anchor to use for the validation message.
|
|
581
|
+
*
|
|
582
|
+
* @internal
|
|
583
|
+
*/
|
|
584
|
+
setValidity(flags, message, anchor) {
|
|
585
|
+
if (this.$fastController.isConnected) {
|
|
586
|
+
if (this.disabled || !this.required) {
|
|
587
|
+
this.elementInternals.setValidity({});
|
|
588
|
+
return;
|
|
589
|
+
}
|
|
590
|
+
const valueMissing = this.required && this.listbox.selectedOptions.length === 0;
|
|
591
|
+
this.elementInternals.setValidity({ valueMissing, ...flags }, message ?? this.validationMessage, anchor ?? this.listbox.enabledOptions[0]);
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
/**
|
|
595
|
+
* Updates the freeform option with the provided value.
|
|
596
|
+
*
|
|
597
|
+
* @param value - the value to update the freeform option with
|
|
598
|
+
* @internal
|
|
599
|
+
*/
|
|
600
|
+
updateFreeformOption(value = this.control.value) {
|
|
601
|
+
if (!this.freeformOption) {
|
|
602
|
+
return;
|
|
603
|
+
}
|
|
604
|
+
if (value === '' ||
|
|
605
|
+
this.filterOptions(value, this.enabledOptions.filter(x => !x.freeform)).length) {
|
|
606
|
+
this.freeformOption.value = '';
|
|
607
|
+
this.freeformOption.selected = false;
|
|
608
|
+
this.freeformOption.hidden = true;
|
|
609
|
+
return;
|
|
610
|
+
}
|
|
611
|
+
this.freeformOption.value = value;
|
|
612
|
+
this.freeformOption.hidden = false;
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
__decorate([
|
|
616
|
+
volatile
|
|
617
|
+
], BaseDropdown.prototype, "activeDescendant", null);
|
|
618
|
+
__decorate([
|
|
619
|
+
observable
|
|
620
|
+
], BaseDropdown.prototype, "activeIndex", void 0);
|
|
621
|
+
__decorate([
|
|
622
|
+
attr({ attribute: 'aria-labelledby', mode: 'fromView' })
|
|
623
|
+
], BaseDropdown.prototype, "ariaLabelledBy", void 0);
|
|
624
|
+
__decorate([
|
|
625
|
+
observable
|
|
626
|
+
], BaseDropdown.prototype, "control", void 0);
|
|
627
|
+
__decorate([
|
|
628
|
+
attr({ mode: 'boolean' })
|
|
629
|
+
], BaseDropdown.prototype, "disabled", void 0);
|
|
630
|
+
__decorate([
|
|
631
|
+
volatile
|
|
632
|
+
], BaseDropdown.prototype, "displayValue", null);
|
|
633
|
+
__decorate([
|
|
634
|
+
attr({ attribute: 'id' })
|
|
635
|
+
], BaseDropdown.prototype, "id", void 0);
|
|
636
|
+
__decorate([
|
|
637
|
+
observable
|
|
638
|
+
], BaseDropdown.prototype, "indicator", void 0);
|
|
639
|
+
__decorate([
|
|
640
|
+
observable
|
|
641
|
+
], BaseDropdown.prototype, "indicatorSlot", void 0);
|
|
642
|
+
__decorate([
|
|
643
|
+
attr({ attribute: 'value', mode: 'fromView' })
|
|
644
|
+
], BaseDropdown.prototype, "initialValue", void 0);
|
|
645
|
+
__decorate([
|
|
646
|
+
observable
|
|
647
|
+
], BaseDropdown.prototype, "listbox", void 0);
|
|
648
|
+
__decorate([
|
|
649
|
+
observable
|
|
650
|
+
], BaseDropdown.prototype, "listboxSlot", void 0);
|
|
651
|
+
__decorate([
|
|
652
|
+
attr({ mode: 'boolean' })
|
|
653
|
+
], BaseDropdown.prototype, "multiple", void 0);
|
|
654
|
+
__decorate([
|
|
655
|
+
attr
|
|
656
|
+
], BaseDropdown.prototype, "name", void 0);
|
|
657
|
+
__decorate([
|
|
658
|
+
observable
|
|
659
|
+
], BaseDropdown.prototype, "open", void 0);
|
|
660
|
+
__decorate([
|
|
661
|
+
attr
|
|
662
|
+
], BaseDropdown.prototype, "placeholder", void 0);
|
|
663
|
+
__decorate([
|
|
664
|
+
attr({ mode: 'boolean' })
|
|
665
|
+
], BaseDropdown.prototype, "required", void 0);
|
|
666
|
+
__decorate([
|
|
667
|
+
attr
|
|
668
|
+
], BaseDropdown.prototype, "type", void 0);
|
|
669
|
+
__decorate([
|
|
670
|
+
attr({ attribute: 'value' })
|
|
671
|
+
], BaseDropdown.prototype, "valueAttribute", void 0);
|
|
672
|
+
/**
|
|
673
|
+
* The Fluent Dropdown Element. Implements {@link @microsoft/fast-foundation#BaseDropdown}.
|
|
674
|
+
*
|
|
675
|
+
* @slot - The default slot. Accepts a {@link (Listbox:class)} element.
|
|
676
|
+
* @slot indicator - The indicator slot.
|
|
677
|
+
* @slot control - The control slot. This slot is automatically populated and should not be manually manipulated.
|
|
678
|
+
*
|
|
679
|
+
* @public
|
|
680
|
+
*/
|
|
681
|
+
export class Dropdown extends BaseDropdown {
|
|
682
|
+
/**
|
|
683
|
+
* Swaps appearance states when the appearance property changes.
|
|
684
|
+
*
|
|
685
|
+
* @param prev - the previous appearance state
|
|
686
|
+
* @param next - the current appearance state
|
|
687
|
+
* @internal
|
|
688
|
+
*/
|
|
689
|
+
appearanceChanged(prev, next) {
|
|
690
|
+
swapStates(this.elementInternals, prev, next, DropdownAppearance);
|
|
691
|
+
}
|
|
692
|
+
/**
|
|
693
|
+
* Swaps size states when the size property changes.
|
|
694
|
+
*
|
|
695
|
+
* @param prev - the previous size state
|
|
696
|
+
* @param next - the current size state
|
|
697
|
+
* @internal
|
|
698
|
+
*/
|
|
699
|
+
sizeChanged(prev, next) {
|
|
700
|
+
swapStates(this.elementInternals, prev, next, DropdownSize);
|
|
701
|
+
}
|
|
702
|
+
connectedCallback() {
|
|
703
|
+
super.connectedCallback();
|
|
704
|
+
this.anchorPositionFallback();
|
|
705
|
+
}
|
|
706
|
+
constructor() {
|
|
707
|
+
super();
|
|
708
|
+
/**
|
|
709
|
+
* The appearance of the dropdown.
|
|
710
|
+
*
|
|
711
|
+
* @public
|
|
712
|
+
* @remarks
|
|
713
|
+
* HTML Attribute: `appearance`
|
|
714
|
+
*/
|
|
715
|
+
this.appearance = DropdownAppearance.outline;
|
|
716
|
+
this.addEventListener('connected', this.listboxConnectedHandler);
|
|
717
|
+
}
|
|
718
|
+
disconnectedCallback() {
|
|
719
|
+
Dropdown.AnchorPositionFallbackObserver?.unobserve(this.listbox);
|
|
720
|
+
super.disconnectedCallback();
|
|
721
|
+
}
|
|
722
|
+
/**
|
|
723
|
+
* Handles the connected event for the listbox.
|
|
724
|
+
*
|
|
725
|
+
* @param e - the event object
|
|
726
|
+
* @internal
|
|
727
|
+
*/
|
|
728
|
+
listboxConnectedHandler(e) {
|
|
729
|
+
const target = e.target;
|
|
730
|
+
if (isListbox(target)) {
|
|
731
|
+
this.listbox = target;
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
/**
|
|
735
|
+
* Adds or removes the window event listener based on the open state.
|
|
736
|
+
*
|
|
737
|
+
* @param prev - the previous open state
|
|
738
|
+
* @param next - the current open state
|
|
739
|
+
* @internal
|
|
740
|
+
*/
|
|
741
|
+
openChanged(prev, next) {
|
|
742
|
+
super.openChanged(prev, next);
|
|
743
|
+
if (next) {
|
|
744
|
+
Dropdown.AnchorPositionFallbackObserver?.observe(this.listbox);
|
|
745
|
+
return;
|
|
746
|
+
}
|
|
747
|
+
Dropdown.AnchorPositionFallbackObserver?.unobserve(this.listbox);
|
|
748
|
+
}
|
|
749
|
+
/**
|
|
750
|
+
* When anchor positioning isn't supported, an intersection observer is used to flip the listbox when it hits the
|
|
751
|
+
* viewport bounds. One static observer is used for all dropdowns.
|
|
752
|
+
*
|
|
753
|
+
* @internal
|
|
754
|
+
*/
|
|
755
|
+
anchorPositionFallback() {
|
|
756
|
+
Dropdown.AnchorPositionFallbackObserver =
|
|
757
|
+
Dropdown.AnchorPositionFallbackObserver ??
|
|
758
|
+
new IntersectionObserver((entries) => {
|
|
759
|
+
entries.forEach(({ boundingClientRect, isIntersecting, target }) => {
|
|
760
|
+
if (isListbox(target) && !isIntersecting) {
|
|
761
|
+
if (boundingClientRect.bottom > window.innerHeight) {
|
|
762
|
+
toggleState(target.dropdown.elementInternals, 'flip-block', true);
|
|
763
|
+
return;
|
|
764
|
+
}
|
|
765
|
+
if (boundingClientRect.top < 0) {
|
|
766
|
+
toggleState(target.dropdown.elementInternals, 'flip-block', false);
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
});
|
|
770
|
+
}, { threshold: 1 });
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
__decorate([
|
|
774
|
+
attr
|
|
775
|
+
], Dropdown.prototype, "appearance", void 0);
|
|
776
|
+
__decorate([
|
|
777
|
+
attr
|
|
778
|
+
], Dropdown.prototype, "size", void 0);
|
|
779
|
+
//# sourceMappingURL=dropdown.js.map
|