@ni/nimble-components 21.4.0 → 21.5.0
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/dist/all-components-bundle.js +864 -73
- package/dist/all-components-bundle.js.map +1 -1
- package/dist/all-components-bundle.min.js +3598 -3450
- package/dist/all-components-bundle.min.js.map +1 -1
- package/dist/esm/anchored-region/styles.js +6 -1
- package/dist/esm/anchored-region/styles.js.map +1 -1
- package/dist/esm/label-provider/core/index.d.ts +6 -0
- package/dist/esm/label-provider/core/index.js +10 -2
- package/dist/esm/label-provider/core/index.js.map +1 -1
- package/dist/esm/label-provider/core/label-token-defaults.js +3 -1
- package/dist/esm/label-provider/core/label-token-defaults.js.map +1 -1
- package/dist/esm/label-provider/core/label-tokens.d.ts +2 -0
- package/dist/esm/label-provider/core/label-tokens.js +8 -0
- package/dist/esm/label-provider/core/label-tokens.js.map +1 -1
- package/dist/esm/patterns/dropdown/styles.js +0 -4
- package/dist/esm/patterns/dropdown/styles.js.map +1 -1
- package/dist/esm/select/index.d.ts +251 -7
- package/dist/esm/select/index.js +653 -19
- package/dist/esm/select/index.js.map +1 -1
- package/dist/esm/select/models/select-form-associated.d.ts +16 -0
- package/dist/esm/select/models/select-form-associated.js +19 -0
- package/dist/esm/select/models/select-form-associated.js.map +1 -0
- package/dist/esm/select/styles.js +89 -1
- package/dist/esm/select/styles.js.map +1 -1
- package/dist/esm/select/template.js +71 -37
- package/dist/esm/select/template.js.map +1 -1
- package/dist/esm/select/testing/select.pageobject.d.ts +32 -0
- package/dist/esm/select/testing/select.pageobject.js +128 -0
- package/dist/esm/select/testing/select.pageobject.js.map +1 -0
- package/dist/esm/select/types.d.ts +9 -0
- package/dist/esm/select/types.js +8 -0
- package/dist/esm/select/types.js.map +1 -1
- package/package.json +4 -1
package/dist/esm/select/index.js
CHANGED
|
@@ -1,75 +1,708 @@
|
|
|
1
1
|
import { __decorate } from "tslib";
|
|
2
|
-
|
|
3
|
-
import {
|
|
2
|
+
// Based on: https://github.com/microsoft/fast/blob/%40microsoft/fast-foundation_v2.49.5/packages/web-components/fast-foundation/src/select/select.ts
|
|
3
|
+
import { attr, html, observable, Observable, volatile } from '@microsoft/fast-element';
|
|
4
|
+
import { DesignSystem, Select as FoundationSelect, SelectPosition, applyMixins, StartEnd, DelegatesARIASelect, Listbox } from '@microsoft/fast-foundation';
|
|
5
|
+
import { keyArrowDown, keyArrowUp, keyEnd, keyEnter, keyEscape, keyHome, keySpace, keyTab, uniqueId } from '@microsoft/fast-web-utilities';
|
|
4
6
|
import { arrowExpanderDown16X16 } from '@ni/nimble-tokens/dist/icons/js';
|
|
5
7
|
import { styles } from './styles';
|
|
6
8
|
import { DropdownAppearance } from '../patterns/dropdown/types';
|
|
7
9
|
import { errorTextTemplate } from '../patterns/error/template';
|
|
8
10
|
import { iconExclamationMarkTag } from '../icons/exclamation-mark';
|
|
9
11
|
import { template } from './template';
|
|
12
|
+
import { FilterMode } from './types';
|
|
13
|
+
import { diacriticInsensitiveStringNormalizer } from '../utilities/models/string-normalizers';
|
|
14
|
+
import { FormAssociatedSelect } from './models/select-form-associated';
|
|
10
15
|
/**
|
|
11
|
-
* A nimble-styled HTML select
|
|
16
|
+
* A nimble-styled HTML select.
|
|
12
17
|
*/
|
|
13
|
-
export class Select extends
|
|
18
|
+
export class Select extends FormAssociatedSelect {
|
|
14
19
|
constructor() {
|
|
15
20
|
super(...arguments);
|
|
16
21
|
this.appearance = DropdownAppearance.underline;
|
|
17
22
|
this.errorVisible = false;
|
|
23
|
+
this.filterMode = FilterMode.none;
|
|
24
|
+
/**
|
|
25
|
+
* @internal
|
|
26
|
+
*/
|
|
27
|
+
this.open = false;
|
|
28
|
+
/**
|
|
29
|
+
* The unique id for the internal listbox element.
|
|
30
|
+
*
|
|
31
|
+
* @internal
|
|
32
|
+
*/
|
|
33
|
+
this.listboxId = uniqueId('listbox-');
|
|
18
34
|
/** @internal */
|
|
19
35
|
this.hasOverflow = false;
|
|
36
|
+
/**
|
|
37
|
+
* @internal
|
|
38
|
+
*/
|
|
39
|
+
this.filteredOptions = [];
|
|
40
|
+
/**
|
|
41
|
+
* @internal
|
|
42
|
+
*/
|
|
43
|
+
this.filter = '';
|
|
44
|
+
/**
|
|
45
|
+
* @internal
|
|
46
|
+
*/
|
|
47
|
+
this.committedSelectedOption = undefined;
|
|
48
|
+
/**
|
|
49
|
+
* The max height for the listbox when opened.
|
|
50
|
+
*
|
|
51
|
+
* @internal
|
|
52
|
+
*/
|
|
53
|
+
this.maxHeight = 0;
|
|
54
|
+
this._value = '';
|
|
55
|
+
this.forcedPosition = false;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* The component is collapsible when in single-selection mode with no size attribute.
|
|
59
|
+
*
|
|
60
|
+
* @internal
|
|
61
|
+
*/
|
|
62
|
+
get collapsible() {
|
|
63
|
+
return !(this.multiple || typeof this.size === 'number');
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* @internal
|
|
67
|
+
*/
|
|
68
|
+
connectedCallback() {
|
|
69
|
+
super.connectedCallback();
|
|
70
|
+
this.forcedPosition = !!this.positionAttribute;
|
|
71
|
+
this.initializeOpenState();
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* The list of options. This mirrors FAST's override implementation for this
|
|
75
|
+
* member for the Combobox to support a filtered list in the dropdown.
|
|
76
|
+
*
|
|
77
|
+
* @public
|
|
78
|
+
* @remarks
|
|
79
|
+
* Overrides `Listbox.options`.
|
|
80
|
+
*/
|
|
81
|
+
get options() {
|
|
82
|
+
Observable.track(this, 'options');
|
|
83
|
+
return this.filteredOptions?.length
|
|
84
|
+
? this.filteredOptions
|
|
85
|
+
: this._options;
|
|
86
|
+
}
|
|
87
|
+
set options(value) {
|
|
88
|
+
this._options = value;
|
|
89
|
+
Observable.notify(this, 'options');
|
|
90
|
+
}
|
|
91
|
+
get value() {
|
|
92
|
+
Observable.track(this, 'value');
|
|
93
|
+
return this._value;
|
|
94
|
+
}
|
|
95
|
+
set value(next) {
|
|
96
|
+
const prev = this._value;
|
|
97
|
+
let newValue = next;
|
|
98
|
+
// use 'options' here instead of '_options' as 'selectedIndex' may be relative
|
|
99
|
+
// to filtered set
|
|
100
|
+
if (this.options?.length) {
|
|
101
|
+
const newValueIndex = this.options.findIndex(el => el.value === newValue);
|
|
102
|
+
const prevSelectedValue = this.options[this.selectedIndex]?.value ?? null;
|
|
103
|
+
const nextSelectedValue = this.options[newValueIndex]?.value ?? null;
|
|
104
|
+
if (newValueIndex === -1
|
|
105
|
+
|| prevSelectedValue !== nextSelectedValue) {
|
|
106
|
+
newValue = '';
|
|
107
|
+
this.selectedIndex = newValueIndex;
|
|
108
|
+
}
|
|
109
|
+
newValue = this.firstSelectedOption?.value ?? newValue;
|
|
110
|
+
}
|
|
111
|
+
if (prev !== newValue && !(this.open && this.selectedIndex < 0)) {
|
|
112
|
+
this._value = newValue;
|
|
113
|
+
super.valueChanged(prev, newValue);
|
|
114
|
+
if (!this.open) {
|
|
115
|
+
this.committedSelectedOption = this._options.find(o => o.value === newValue);
|
|
116
|
+
}
|
|
117
|
+
Observable.notify(this, 'value');
|
|
118
|
+
if (this.collapsible) {
|
|
119
|
+
Observable.notify(this, 'displayValue');
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* @internal
|
|
125
|
+
*/
|
|
126
|
+
get displayValue() {
|
|
127
|
+
Observable.track(this, 'displayValue');
|
|
128
|
+
return this.committedSelectedOption?.text ?? '';
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* @internal
|
|
132
|
+
*/
|
|
133
|
+
anchoredRegionChanged(_prev, _next) {
|
|
134
|
+
if (this.anchoredRegion && this.control) {
|
|
135
|
+
this.anchoredRegion.anchorElement = this.control;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* @internal
|
|
140
|
+
*/
|
|
141
|
+
controlChanged(_prev, _next) {
|
|
142
|
+
if (this.anchoredRegion && this.control) {
|
|
143
|
+
this.anchoredRegion.anchorElement = this.control;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* @internal
|
|
148
|
+
*/
|
|
149
|
+
slottedOptionsChanged(prev, next) {
|
|
150
|
+
const value = this.value;
|
|
151
|
+
this._options.forEach(o => {
|
|
152
|
+
const notifier = Observable.getNotifier(o);
|
|
153
|
+
notifier.unsubscribe(this, 'value');
|
|
154
|
+
});
|
|
155
|
+
super.slottedOptionsChanged(prev, next);
|
|
156
|
+
this._options.forEach(o => {
|
|
157
|
+
const notifier = Observable.getNotifier(o);
|
|
158
|
+
notifier.subscribe(this, 'value');
|
|
159
|
+
});
|
|
160
|
+
this.setProxyOptions();
|
|
161
|
+
this.updateValue();
|
|
162
|
+
// We need to force an update to the filteredOptions observable
|
|
163
|
+
// (by calling 'filterOptions()) so that the template correctly updates.
|
|
164
|
+
this.filterOptions();
|
|
165
|
+
if (value) {
|
|
166
|
+
this.value = value;
|
|
167
|
+
}
|
|
168
|
+
this.committedSelectedOption = this.options[this.selectedIndex];
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* @internal
|
|
172
|
+
*/
|
|
173
|
+
clickHandler(e) {
|
|
174
|
+
// do nothing if the select is disabled
|
|
175
|
+
if (this.disabled) {
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
if (this.open) {
|
|
179
|
+
const captured = e.target.closest('option,[role=option]');
|
|
180
|
+
if (!captured?.disabled) {
|
|
181
|
+
this.updateSelectedIndexFromFilteredSet();
|
|
182
|
+
}
|
|
183
|
+
if (captured?.disabled) {
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
super.clickHandler(e);
|
|
188
|
+
this.open = this.collapsible && !this.open;
|
|
189
|
+
if (!this.open && this.indexWhenOpened !== this.selectedIndex) {
|
|
190
|
+
this.updateValue(true);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Updates the value when an option's value changes.
|
|
195
|
+
*
|
|
196
|
+
* @param source - the source object
|
|
197
|
+
* @param propertyName - the property to evaluate
|
|
198
|
+
*
|
|
199
|
+
* @internal
|
|
200
|
+
* @override
|
|
201
|
+
*/
|
|
202
|
+
handleChange(source, propertyName) {
|
|
203
|
+
super.handleChange(source, propertyName);
|
|
204
|
+
if (propertyName === 'value') {
|
|
205
|
+
this.updateValue();
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Prevents focus when size is set and a scrollbar is clicked.
|
|
210
|
+
*
|
|
211
|
+
* @param e - the mouse event object
|
|
212
|
+
*
|
|
213
|
+
* @override
|
|
214
|
+
* @internal
|
|
215
|
+
*/
|
|
216
|
+
mousedownHandler(e) {
|
|
217
|
+
if (e.offsetX >= 0 && e.offsetX <= this.listbox?.scrollWidth) {
|
|
218
|
+
return super.mousedownHandler(e);
|
|
219
|
+
}
|
|
220
|
+
return this.collapsible;
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* @internal
|
|
224
|
+
*/
|
|
225
|
+
regionLoadedHandler() {
|
|
226
|
+
this.focusAndScrollOptionIntoView();
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Sets the multiple property on the proxy element.
|
|
230
|
+
*
|
|
231
|
+
* @param prev - the previous multiple value
|
|
232
|
+
* @param next - the current multiple value
|
|
233
|
+
*/
|
|
234
|
+
multipleChanged(prev, next) {
|
|
235
|
+
super.multipleChanged(prev, next);
|
|
236
|
+
if (this.proxy) {
|
|
237
|
+
this.proxy.multiple = next;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* @internal
|
|
242
|
+
*/
|
|
243
|
+
inputClickHandler(e) {
|
|
244
|
+
e.stopPropagation(); // clicking in filter input shouldn't close dropdown
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* @internal
|
|
248
|
+
*/
|
|
249
|
+
changeValueHandler() {
|
|
250
|
+
this.committedSelectedOption = this.options.find(option => option.selected);
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* @internal
|
|
254
|
+
*/
|
|
255
|
+
updateDisplayValue() {
|
|
256
|
+
if (this.collapsible) {
|
|
257
|
+
Observable.notify(this, 'displayValue');
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Handle content changes on the control input.
|
|
262
|
+
*
|
|
263
|
+
* @param e - the input event
|
|
264
|
+
* @internal
|
|
265
|
+
*/
|
|
266
|
+
inputHandler(e) {
|
|
267
|
+
this.filter = this.filterInput?.value ?? '';
|
|
268
|
+
if (!this.committedSelectedOption) {
|
|
269
|
+
this.committedSelectedOption = this._options.find(option => option.selected);
|
|
270
|
+
}
|
|
271
|
+
this.clearSelection();
|
|
272
|
+
this.filterOptions();
|
|
273
|
+
if (this.filteredOptions.length > 0
|
|
274
|
+
&& this.committedSelectedOption
|
|
275
|
+
&& !this.filteredOptions.includes(this.committedSelectedOption)) {
|
|
276
|
+
const enabledOptions = this.filteredOptions.filter(o => !o.disabled);
|
|
277
|
+
if (enabledOptions.length > 0) {
|
|
278
|
+
enabledOptions[0].selected = true;
|
|
279
|
+
}
|
|
280
|
+
else {
|
|
281
|
+
// only filtered option is disabled
|
|
282
|
+
this.selectedOptions = [];
|
|
283
|
+
this.selectedIndex = -1;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
else if (this.committedSelectedOption) {
|
|
287
|
+
this.committedSelectedOption.selected = true;
|
|
288
|
+
}
|
|
289
|
+
if (e.inputType.includes('deleteContent') || !this.filter.length) {
|
|
290
|
+
return true;
|
|
291
|
+
}
|
|
292
|
+
e.stopPropagation();
|
|
293
|
+
return true;
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* @internal
|
|
297
|
+
*/
|
|
298
|
+
focusoutHandler(e) {
|
|
299
|
+
this.updateSelectedIndexFromFilteredSet();
|
|
300
|
+
super.focusoutHandler(e);
|
|
301
|
+
if (!this.open) {
|
|
302
|
+
return true;
|
|
303
|
+
}
|
|
304
|
+
const focusTarget = e.relatedTarget;
|
|
305
|
+
if (this.isSameNode(focusTarget)) {
|
|
306
|
+
this.focus();
|
|
307
|
+
return true;
|
|
308
|
+
}
|
|
309
|
+
if (!this.options?.includes(focusTarget)) {
|
|
310
|
+
this.open = false;
|
|
311
|
+
if (this.indexWhenOpened !== this.selectedIndex) {
|
|
312
|
+
this.updateValue(true);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
return true;
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* @internal
|
|
319
|
+
*/
|
|
320
|
+
keydownHandler(e) {
|
|
321
|
+
super.keydownHandler(e);
|
|
322
|
+
const key = e.key;
|
|
323
|
+
if (e.ctrlKey || e.shiftKey) {
|
|
324
|
+
return true;
|
|
325
|
+
}
|
|
326
|
+
switch (key) {
|
|
327
|
+
case keySpace: {
|
|
328
|
+
// when dropdown is open allow user to enter a space for filter text
|
|
329
|
+
if (this.open && this.filterMode !== FilterMode.none) {
|
|
330
|
+
break;
|
|
331
|
+
}
|
|
332
|
+
e.preventDefault();
|
|
333
|
+
if (this.collapsible && this.typeAheadExpired) {
|
|
334
|
+
this.open = !this.open;
|
|
335
|
+
}
|
|
336
|
+
if (!this.open) {
|
|
337
|
+
this.focus();
|
|
338
|
+
}
|
|
339
|
+
break;
|
|
340
|
+
}
|
|
341
|
+
case keyHome:
|
|
342
|
+
case keyEnd: {
|
|
343
|
+
e.preventDefault();
|
|
344
|
+
break;
|
|
345
|
+
}
|
|
346
|
+
case keyEnter: {
|
|
347
|
+
e.preventDefault();
|
|
348
|
+
if (this.filteredOptions.length === 0
|
|
349
|
+
|| this.filteredOptions.every(o => o.disabled)) {
|
|
350
|
+
return false;
|
|
351
|
+
}
|
|
352
|
+
this.updateSelectedIndexFromFilteredSet();
|
|
353
|
+
this.open = !this.open;
|
|
354
|
+
if (!this.open) {
|
|
355
|
+
this.focus();
|
|
356
|
+
}
|
|
357
|
+
break;
|
|
358
|
+
}
|
|
359
|
+
case keyEscape: {
|
|
360
|
+
// clear filter as update to "selectedIndex" will result in processing
|
|
361
|
+
// "options" and not "_options"
|
|
362
|
+
this.filter = '';
|
|
363
|
+
if (this.committedSelectedOption) {
|
|
364
|
+
this.clearSelection();
|
|
365
|
+
this.selectedIndex = this._options.indexOf(this.committedSelectedOption);
|
|
366
|
+
}
|
|
367
|
+
if (this.collapsible && this.open) {
|
|
368
|
+
e.preventDefault();
|
|
369
|
+
this.open = false;
|
|
370
|
+
}
|
|
371
|
+
// reset 'selected' state otherwise the selected state doesn't stick.
|
|
372
|
+
const selectedOption = this._options[this.selectedIndex];
|
|
373
|
+
if (selectedOption) {
|
|
374
|
+
selectedOption.selected = true;
|
|
375
|
+
}
|
|
376
|
+
this.focus();
|
|
377
|
+
break;
|
|
378
|
+
}
|
|
379
|
+
case keyTab: {
|
|
380
|
+
if (this.collapsible && this.open) {
|
|
381
|
+
e.preventDefault();
|
|
382
|
+
this.open = false;
|
|
383
|
+
}
|
|
384
|
+
return true;
|
|
385
|
+
}
|
|
386
|
+
default: {
|
|
387
|
+
break;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
if (!this.open && this.indexWhenOpened !== this.selectedIndex) {
|
|
391
|
+
this.updateValue(true);
|
|
392
|
+
this.indexWhenOpened = this.selectedIndex;
|
|
393
|
+
}
|
|
394
|
+
return !(key === keyArrowDown || key === keyArrowUp);
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* Updates the proxy value when the selected index changes.
|
|
398
|
+
*
|
|
399
|
+
* @param prev - the previous selected index
|
|
400
|
+
* @param next - the next selected index
|
|
401
|
+
*
|
|
402
|
+
* @internal
|
|
403
|
+
*/
|
|
404
|
+
selectedIndexChanged(prev, next) {
|
|
405
|
+
super.selectedIndexChanged(prev, next);
|
|
406
|
+
this.updateValue();
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* Synchronize the `aria-disabled` property when the `disabled` property changes.
|
|
410
|
+
*
|
|
411
|
+
* @param prev - The previous disabled value
|
|
412
|
+
* @param next - The next disabled value
|
|
413
|
+
*
|
|
414
|
+
* @internal
|
|
415
|
+
*/
|
|
416
|
+
disabledChanged(prev, next) {
|
|
417
|
+
if (super.disabledChanged) {
|
|
418
|
+
super.disabledChanged(prev, next);
|
|
419
|
+
}
|
|
420
|
+
this.ariaDisabled = this.disabled ? 'true' : 'false';
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* Reset the element to its first selectable option when its parent form is reset.
|
|
424
|
+
*
|
|
425
|
+
* @internal
|
|
426
|
+
*/
|
|
427
|
+
formResetCallback() {
|
|
428
|
+
this.setProxyOptions();
|
|
429
|
+
// Call the base class's implementation setDefaultSelectedOption instead of the select's
|
|
430
|
+
// override, in order to reset the selectedIndex without using the value property.
|
|
431
|
+
super.setDefaultSelectedOption();
|
|
432
|
+
if (this.selectedIndex === -1) {
|
|
433
|
+
this.selectedIndex = 0;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
// Prevents parent classes from resetting selectedIndex to a positive
|
|
437
|
+
// value while filtering, which can result in a disabled option being
|
|
438
|
+
// selected.
|
|
439
|
+
setSelectedOptions() {
|
|
440
|
+
if (this.open && this.selectedIndex === -1) {
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
super.setSelectedOptions();
|
|
444
|
+
}
|
|
445
|
+
focusAndScrollOptionIntoView() {
|
|
446
|
+
super.focusAndScrollOptionIntoView();
|
|
447
|
+
if (this.open) {
|
|
448
|
+
window.requestAnimationFrame(() => {
|
|
449
|
+
this.filterInput?.focus();
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
positionChanged(_, next) {
|
|
454
|
+
this.positionAttribute = next;
|
|
455
|
+
this.setPositioning();
|
|
456
|
+
}
|
|
457
|
+
/**
|
|
458
|
+
* Updates the proxy's size property when the size attribute changes.
|
|
459
|
+
*
|
|
460
|
+
* @param prev - the previous size
|
|
461
|
+
* @param next - the current size
|
|
462
|
+
*
|
|
463
|
+
* @override
|
|
464
|
+
* @internal
|
|
465
|
+
*/
|
|
466
|
+
sizeChanged(prev, next) {
|
|
467
|
+
super.sizeChanged(prev, next);
|
|
468
|
+
if (this.proxy) {
|
|
469
|
+
this.proxy.size = next;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
openChanged() {
|
|
473
|
+
if (!this.collapsible) {
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
if (this.open) {
|
|
477
|
+
this.initializeOpenState();
|
|
478
|
+
this.indexWhenOpened = this.selectedIndex;
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
this.filter = '';
|
|
482
|
+
if (this.filterInput) {
|
|
483
|
+
this.filterInput.value = '';
|
|
484
|
+
}
|
|
485
|
+
this.ariaControls = '';
|
|
486
|
+
this.ariaExpanded = 'false';
|
|
487
|
+
}
|
|
488
|
+
/**
|
|
489
|
+
* Updates the selectedness of each option when the list of selected options changes.
|
|
490
|
+
*
|
|
491
|
+
* @param prev - the previous list of selected options
|
|
492
|
+
* @param next - the current list of selected options
|
|
493
|
+
*
|
|
494
|
+
* @override
|
|
495
|
+
* @internal
|
|
496
|
+
*/
|
|
497
|
+
selectedOptionsChanged(prev, next) {
|
|
498
|
+
super.selectedOptionsChanged(prev, next);
|
|
499
|
+
this.options?.forEach((o, i) => {
|
|
500
|
+
const proxyOption = this.proxy?.options.item(i);
|
|
501
|
+
if (proxyOption) {
|
|
502
|
+
proxyOption.selected = o.selected;
|
|
503
|
+
}
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
/**
|
|
507
|
+
* Sets the selected index to match the first option with the selected attribute, or
|
|
508
|
+
* the first selectable option.
|
|
509
|
+
*
|
|
510
|
+
* @override
|
|
511
|
+
* @internal
|
|
512
|
+
*/
|
|
513
|
+
setDefaultSelectedOption() {
|
|
514
|
+
const options = this.options
|
|
515
|
+
?? Array.from(this.children).filter(o => Listbox.slottedOptionFilter(o));
|
|
516
|
+
const selectedIndex = options?.findIndex(el => el.hasAttribute('selected')
|
|
517
|
+
|| el.selected
|
|
518
|
+
|| el.value === this.value);
|
|
519
|
+
if (selectedIndex !== -1) {
|
|
520
|
+
this.selectedIndex = selectedIndex;
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
this.selectedIndex = 0;
|
|
20
524
|
}
|
|
21
|
-
// Workaround for https://github.com/microsoft/fast/issues/5123
|
|
22
525
|
setPositioning() {
|
|
23
526
|
if (!this.$fastController.isConnected) {
|
|
24
527
|
// Don't call setPositioning() until we're connected,
|
|
25
528
|
// since this.forcedPosition isn't initialized yet.
|
|
26
529
|
return;
|
|
27
530
|
}
|
|
28
|
-
|
|
531
|
+
const currentBox = this.getBoundingClientRect();
|
|
532
|
+
const viewportHeight = window.innerHeight;
|
|
533
|
+
const availableBottom = viewportHeight - currentBox.bottom;
|
|
534
|
+
if (this.forcedPosition) {
|
|
535
|
+
this.position = this.positionAttribute;
|
|
536
|
+
}
|
|
537
|
+
else if (currentBox.top > availableBottom) {
|
|
538
|
+
this.position = SelectPosition.above;
|
|
539
|
+
}
|
|
540
|
+
else {
|
|
541
|
+
this.position = SelectPosition.below;
|
|
542
|
+
}
|
|
543
|
+
this.positionAttribute = this.forcedPosition
|
|
544
|
+
? this.positionAttribute
|
|
545
|
+
: this.position;
|
|
546
|
+
this.maxHeight = this.position === SelectPosition.above
|
|
547
|
+
? Math.trunc(currentBox.top)
|
|
548
|
+
: Math.trunc(availableBottom);
|
|
29
549
|
this.updateListboxMaxHeightCssVariable();
|
|
30
550
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
551
|
+
/**
|
|
552
|
+
* Filter available options by text value.
|
|
553
|
+
*
|
|
554
|
+
* @public
|
|
555
|
+
*/
|
|
556
|
+
filterOptions() {
|
|
557
|
+
const filter = this.filter.toLowerCase();
|
|
558
|
+
if (filter) {
|
|
559
|
+
this.filteredOptions = this._options.filter(option => {
|
|
560
|
+
return diacriticInsensitiveStringNormalizer(option.text).includes(diacriticInsensitiveStringNormalizer(filter));
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
else {
|
|
564
|
+
this.filteredOptions = this._options;
|
|
37
565
|
}
|
|
566
|
+
this._options.forEach(o => {
|
|
567
|
+
o.hidden = !this.filteredOptions.includes(o);
|
|
568
|
+
});
|
|
38
569
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
570
|
+
/**
|
|
571
|
+
* Sets the value and display value to match the first selected option.
|
|
572
|
+
*
|
|
573
|
+
* @param shouldEmit - if true, the input and change events will be emitted
|
|
574
|
+
*
|
|
575
|
+
* @internal
|
|
576
|
+
*/
|
|
577
|
+
updateValue(shouldEmit) {
|
|
578
|
+
if (this.$fastController.isConnected) {
|
|
579
|
+
this.value = this.firstSelectedOption?.value ?? '';
|
|
580
|
+
}
|
|
581
|
+
if (shouldEmit) {
|
|
582
|
+
this.$emit('input');
|
|
583
|
+
this.$emit('change', this, {
|
|
584
|
+
bubbles: true,
|
|
585
|
+
composed: undefined
|
|
586
|
+
});
|
|
42
587
|
}
|
|
43
588
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
589
|
+
/**
|
|
590
|
+
* Resets and fills the proxy to match the component's options.
|
|
591
|
+
*
|
|
592
|
+
* @internal
|
|
593
|
+
*/
|
|
594
|
+
setProxyOptions() {
|
|
595
|
+
if (this.proxy instanceof HTMLSelectElement && this.options) {
|
|
596
|
+
this.proxy.options.length = 0;
|
|
597
|
+
this.options.forEach(option => {
|
|
598
|
+
const proxyOption = option.proxy
|
|
599
|
+
|| (option instanceof HTMLOptionElement
|
|
600
|
+
? option.cloneNode()
|
|
601
|
+
: null);
|
|
602
|
+
if (proxyOption) {
|
|
603
|
+
this.proxy.options.add(proxyOption);
|
|
604
|
+
}
|
|
605
|
+
});
|
|
47
606
|
}
|
|
48
607
|
}
|
|
608
|
+
clearSelection() {
|
|
609
|
+
this.options.forEach(option => {
|
|
610
|
+
option.selected = false;
|
|
611
|
+
});
|
|
612
|
+
}
|
|
613
|
+
filterChanged() {
|
|
614
|
+
this.filterOptions();
|
|
615
|
+
}
|
|
49
616
|
maxHeightChanged() {
|
|
50
617
|
this.updateListboxMaxHeightCssVariable();
|
|
51
618
|
}
|
|
619
|
+
initializeOpenState() {
|
|
620
|
+
if (!this.open) {
|
|
621
|
+
this.ariaExpanded = 'false';
|
|
622
|
+
this.ariaControls = '';
|
|
623
|
+
return;
|
|
624
|
+
}
|
|
625
|
+
this.committedSelectedOption = this._options[this.selectedIndex];
|
|
626
|
+
this.ariaControls = this.listboxId;
|
|
627
|
+
this.ariaExpanded = 'true';
|
|
628
|
+
this.setPositioning();
|
|
629
|
+
this.focusAndScrollOptionIntoView();
|
|
630
|
+
}
|
|
52
631
|
updateListboxMaxHeightCssVariable() {
|
|
53
632
|
if (this.listbox) {
|
|
54
633
|
this.listbox.style.setProperty('--ni-private-select-max-height', `${this.maxHeight}px`);
|
|
55
634
|
}
|
|
56
635
|
}
|
|
636
|
+
updateSelectedIndexFromFilteredSet() {
|
|
637
|
+
const selectedItem = this.filteredOptions.length > 0
|
|
638
|
+
? this.options[this.selectedIndex]
|
|
639
|
+
?? this.committedSelectedOption
|
|
640
|
+
: this.committedSelectedOption;
|
|
641
|
+
if (!selectedItem) {
|
|
642
|
+
return;
|
|
643
|
+
}
|
|
644
|
+
// Clear filter so any logic resolving against 'this.options' resolves against all options,
|
|
645
|
+
// since selectedIndex should be relative to entire set.
|
|
646
|
+
this.filter = '';
|
|
647
|
+
// translate selectedIndex for filtered list to selectedIndex for all items
|
|
648
|
+
this.selectedIndex = this._options.indexOf(selectedItem);
|
|
649
|
+
// force selected to true again if the selection hasn't actually changed
|
|
650
|
+
if (selectedItem === this.committedSelectedOption) {
|
|
651
|
+
selectedItem.selected = true;
|
|
652
|
+
}
|
|
653
|
+
}
|
|
57
654
|
}
|
|
58
655
|
__decorate([
|
|
59
656
|
attr
|
|
60
657
|
], Select.prototype, "appearance", void 0);
|
|
658
|
+
__decorate([
|
|
659
|
+
attr({ attribute: 'position' })
|
|
660
|
+
], Select.prototype, "positionAttribute", void 0);
|
|
61
661
|
__decorate([
|
|
62
662
|
attr({ attribute: 'error-text' })
|
|
63
663
|
], Select.prototype, "errorText", void 0);
|
|
64
664
|
__decorate([
|
|
65
665
|
attr({ attribute: 'error-visible', mode: 'boolean' })
|
|
66
666
|
], Select.prototype, "errorVisible", void 0);
|
|
667
|
+
__decorate([
|
|
668
|
+
attr({ attribute: 'filter-mode' })
|
|
669
|
+
], Select.prototype, "filterMode", void 0);
|
|
670
|
+
__decorate([
|
|
671
|
+
attr({ attribute: 'open', mode: 'boolean' })
|
|
672
|
+
], Select.prototype, "open", void 0);
|
|
673
|
+
__decorate([
|
|
674
|
+
observable
|
|
675
|
+
], Select.prototype, "position", void 0);
|
|
676
|
+
__decorate([
|
|
677
|
+
observable
|
|
678
|
+
], Select.prototype, "control", void 0);
|
|
679
|
+
__decorate([
|
|
680
|
+
observable
|
|
681
|
+
], Select.prototype, "scrollableRegion", void 0);
|
|
67
682
|
__decorate([
|
|
68
683
|
observable
|
|
69
|
-
], Select.prototype, "
|
|
684
|
+
], Select.prototype, "filterInput", void 0);
|
|
685
|
+
__decorate([
|
|
686
|
+
observable
|
|
687
|
+
], Select.prototype, "anchoredRegion", void 0);
|
|
70
688
|
__decorate([
|
|
71
689
|
observable
|
|
72
690
|
], Select.prototype, "hasOverflow", void 0);
|
|
691
|
+
__decorate([
|
|
692
|
+
observable
|
|
693
|
+
], Select.prototype, "filteredOptions", void 0);
|
|
694
|
+
__decorate([
|
|
695
|
+
observable
|
|
696
|
+
], Select.prototype, "filter", void 0);
|
|
697
|
+
__decorate([
|
|
698
|
+
observable
|
|
699
|
+
], Select.prototype, "committedSelectedOption", void 0);
|
|
700
|
+
__decorate([
|
|
701
|
+
observable
|
|
702
|
+
], Select.prototype, "maxHeight", void 0);
|
|
703
|
+
__decorate([
|
|
704
|
+
volatile
|
|
705
|
+
], Select.prototype, "collapsible", null);
|
|
73
706
|
const nimbleSelect = Select.compose({
|
|
74
707
|
baseName: 'select',
|
|
75
708
|
baseClass: FoundationSelect,
|
|
@@ -84,6 +717,7 @@ const nimbleSelect = Select.compose({
|
|
|
84
717
|
${errorTextTemplate}
|
|
85
718
|
`
|
|
86
719
|
});
|
|
720
|
+
applyMixins(Select, StartEnd, DelegatesARIASelect);
|
|
87
721
|
DesignSystem.getOrCreate().withPrefix('nimble').register(nimbleSelect());
|
|
88
722
|
export const selectTag = 'nimble-select';
|
|
89
723
|
//# sourceMappingURL=index.js.map
|