@instructure/ui-select 10.16.1 → 10.16.3
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 +19 -0
- package/es/Select/index.js +15 -3
- package/lib/Select/index.js +15 -3
- package/package.json +23 -23
- package/src/Select/README.md +183 -4
- package/src/Select/index.tsx +27 -3
- package/tsconfig.build.tsbuildinfo +1 -1
- package/types/Select/index.d.ts +4 -3
- package/types/Select/index.d.ts.map +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,25 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
## [10.16.3](https://github.com/instructure/instructure-ui/compare/v10.16.1...v10.16.3) (2025-04-30)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Bug Fixes
|
|
10
|
+
|
|
11
|
+
* **ui-time-select,ui-simple-select,ui-select:** make Select accessible for iOS VoiceOver ([b501a7b](https://github.com/instructure/instructure-ui/commit/b501a7b38bfa491298085a173a49a1baa0a19b29))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
## [10.16.2](https://github.com/instructure/instructure-ui/compare/v10.16.1...v10.16.2) (2025-04-22)
|
|
18
|
+
|
|
19
|
+
**Note:** Version bump only for package @instructure/ui-select
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
|
|
6
25
|
## [10.16.1](https://github.com/instructure/instructure-ui/compare/v10.16.0...v10.16.1) (2025-04-22)
|
|
7
26
|
|
|
8
27
|
|
package/es/Select/index.js
CHANGED
|
@@ -130,6 +130,9 @@ let Select = (_dec = withDeterministicId(), _dec2 = withStyle(generateStyle, gen
|
|
|
130
130
|
focus() {
|
|
131
131
|
this._input && this._input.focus();
|
|
132
132
|
}
|
|
133
|
+
blur() {
|
|
134
|
+
this._input && this._input.blur();
|
|
135
|
+
}
|
|
133
136
|
get childrenArray() {
|
|
134
137
|
return Children.toArray(this.props.children);
|
|
135
138
|
}
|
|
@@ -567,7 +570,12 @@ let Select = (_dec = withDeterministicId(), _dec2 = withStyle(generateStyle, gen
|
|
|
567
570
|
shouldNotWrap,
|
|
568
571
|
display: isInline ? 'inline-block' : 'block',
|
|
569
572
|
renderBeforeInput: this.handleRenderBeforeInput(),
|
|
570
|
-
|
|
573
|
+
// On iOS VoiceOver, if there is a custom element instead of the changing up and down arrow button
|
|
574
|
+
// the listbox closes on a swipe, so a DOM change is enforced by the key change
|
|
575
|
+
// that seems to inform VoiceOver to behave the correct way
|
|
576
|
+
renderAfterInput: utils.isAndroidOrIOS() && renderAfterInput !== void 0 ? _jsx("span", {
|
|
577
|
+
children: this.handleRenderAfterInput()
|
|
578
|
+
}, this.props.isShowingOptions ? 'open' : 'closed') : this.handleRenderAfterInput(),
|
|
571
579
|
// If `inputValue` is provided, we need to pass a default onChange handler,
|
|
572
580
|
// because TextInput `value` is a controlled prop,
|
|
573
581
|
// and onChange is not required for Select
|
|
@@ -621,8 +629,12 @@ let Select = (_dec = withDeterministicId(), _dec2 = withStyle(generateStyle, gen
|
|
|
621
629
|
children: assistiveText
|
|
622
630
|
}), _jsx(Popover, {
|
|
623
631
|
constrain: constrain,
|
|
624
|
-
placement: placement
|
|
625
|
-
|
|
632
|
+
placement: placement
|
|
633
|
+
// On iOS VoiceOver, the Popover is mounted right after the input
|
|
634
|
+
// in order to be able to navigate through the list items with a swipe.
|
|
635
|
+
// The swipe would result in closing the listbox if mounted elsewhere.
|
|
636
|
+
,
|
|
637
|
+
mountNode: mountNode !== void 0 ? mountNode : utils.isAndroidOrIOS() ? this.ref : void 0,
|
|
626
638
|
positionTarget: this._inputContainer,
|
|
627
639
|
isShowingContent: isShowingOptions,
|
|
628
640
|
shouldReturnFocus: false,
|
package/lib/Select/index.js
CHANGED
|
@@ -142,6 +142,9 @@ let Select = exports.Select = (_dec = (0, _withDeterministicId.withDeterministic
|
|
|
142
142
|
focus() {
|
|
143
143
|
this._input && this._input.focus();
|
|
144
144
|
}
|
|
145
|
+
blur() {
|
|
146
|
+
this._input && this._input.blur();
|
|
147
|
+
}
|
|
145
148
|
get childrenArray() {
|
|
146
149
|
return _react.Children.toArray(this.props.children);
|
|
147
150
|
}
|
|
@@ -579,7 +582,12 @@ let Select = exports.Select = (_dec = (0, _withDeterministicId.withDeterministic
|
|
|
579
582
|
shouldNotWrap,
|
|
580
583
|
display: isInline ? 'inline-block' : 'block',
|
|
581
584
|
renderBeforeInput: this.handleRenderBeforeInput(),
|
|
582
|
-
|
|
585
|
+
// On iOS VoiceOver, if there is a custom element instead of the changing up and down arrow button
|
|
586
|
+
// the listbox closes on a swipe, so a DOM change is enforced by the key change
|
|
587
|
+
// that seems to inform VoiceOver to behave the correct way
|
|
588
|
+
renderAfterInput: utils.isAndroidOrIOS() && renderAfterInput !== void 0 ? (0, _jsxRuntime.jsx)("span", {
|
|
589
|
+
children: this.handleRenderAfterInput()
|
|
590
|
+
}, this.props.isShowingOptions ? 'open' : 'closed') : this.handleRenderAfterInput(),
|
|
583
591
|
// If `inputValue` is provided, we need to pass a default onChange handler,
|
|
584
592
|
// because TextInput `value` is a controlled prop,
|
|
585
593
|
// and onChange is not required for Select
|
|
@@ -633,8 +641,12 @@ let Select = exports.Select = (_dec = (0, _withDeterministicId.withDeterministic
|
|
|
633
641
|
children: assistiveText
|
|
634
642
|
}), (0, _jsxRuntime.jsx)(_Popover.Popover, {
|
|
635
643
|
constrain: constrain,
|
|
636
|
-
placement: placement
|
|
637
|
-
|
|
644
|
+
placement: placement
|
|
645
|
+
// On iOS VoiceOver, the Popover is mounted right after the input
|
|
646
|
+
// in order to be able to navigate through the list items with a swipe.
|
|
647
|
+
// The swipe would result in closing the listbox if mounted elsewhere.
|
|
648
|
+
,
|
|
649
|
+
mountNode: mountNode !== void 0 ? mountNode : utils.isAndroidOrIOS() ? this.ref : void 0,
|
|
638
650
|
positionTarget: this._inputContainer,
|
|
639
651
|
isShowingContent: isShowingOptions,
|
|
640
652
|
shouldReturnFocus: false,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@instructure/ui-select",
|
|
3
|
-
"version": "10.16.
|
|
3
|
+
"version": "10.16.3",
|
|
4
4
|
"description": "A component for select and autocomplete behavior.",
|
|
5
5
|
"author": "Instructure, Inc. Engineering and Product Design",
|
|
6
6
|
"module": "./es/index.js",
|
|
@@ -23,12 +23,12 @@
|
|
|
23
23
|
},
|
|
24
24
|
"license": "MIT",
|
|
25
25
|
"devDependencies": {
|
|
26
|
-
"@instructure/ui-axe-check": "10.16.
|
|
27
|
-
"@instructure/ui-babel-preset": "10.16.
|
|
28
|
-
"@instructure/ui-color-utils": "10.16.
|
|
29
|
-
"@instructure/ui-scripts": "10.16.
|
|
30
|
-
"@instructure/ui-test-utils": "10.16.
|
|
31
|
-
"@instructure/ui-themes": "10.16.
|
|
26
|
+
"@instructure/ui-axe-check": "10.16.3",
|
|
27
|
+
"@instructure/ui-babel-preset": "10.16.3",
|
|
28
|
+
"@instructure/ui-color-utils": "10.16.3",
|
|
29
|
+
"@instructure/ui-scripts": "10.16.3",
|
|
30
|
+
"@instructure/ui-test-utils": "10.16.3",
|
|
31
|
+
"@instructure/ui-themes": "10.16.3",
|
|
32
32
|
"@testing-library/jest-dom": "^6.6.3",
|
|
33
33
|
"@testing-library/react": "^16.0.1",
|
|
34
34
|
"@testing-library/user-event": "^14.5.2",
|
|
@@ -36,22 +36,22 @@
|
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
38
|
"@babel/runtime": "^7.26.0",
|
|
39
|
-
"@instructure/emotion": "10.16.
|
|
40
|
-
"@instructure/shared-types": "10.16.
|
|
41
|
-
"@instructure/ui-dom-utils": "10.16.
|
|
42
|
-
"@instructure/ui-form-field": "10.16.
|
|
43
|
-
"@instructure/ui-icons": "10.16.
|
|
44
|
-
"@instructure/ui-options": "10.16.
|
|
45
|
-
"@instructure/ui-popover": "10.16.
|
|
46
|
-
"@instructure/ui-position": "10.16.
|
|
47
|
-
"@instructure/ui-prop-types": "10.16.
|
|
48
|
-
"@instructure/ui-react-utils": "10.16.
|
|
49
|
-
"@instructure/ui-selectable": "10.16.
|
|
50
|
-
"@instructure/ui-testable": "10.16.
|
|
51
|
-
"@instructure/ui-text-input": "10.16.
|
|
52
|
-
"@instructure/ui-utils": "10.16.
|
|
53
|
-
"@instructure/ui-view": "10.16.
|
|
54
|
-
"@instructure/uid": "10.16.
|
|
39
|
+
"@instructure/emotion": "10.16.3",
|
|
40
|
+
"@instructure/shared-types": "10.16.3",
|
|
41
|
+
"@instructure/ui-dom-utils": "10.16.3",
|
|
42
|
+
"@instructure/ui-form-field": "10.16.3",
|
|
43
|
+
"@instructure/ui-icons": "10.16.3",
|
|
44
|
+
"@instructure/ui-options": "10.16.3",
|
|
45
|
+
"@instructure/ui-popover": "10.16.3",
|
|
46
|
+
"@instructure/ui-position": "10.16.3",
|
|
47
|
+
"@instructure/ui-prop-types": "10.16.3",
|
|
48
|
+
"@instructure/ui-react-utils": "10.16.3",
|
|
49
|
+
"@instructure/ui-selectable": "10.16.3",
|
|
50
|
+
"@instructure/ui-testable": "10.16.3",
|
|
51
|
+
"@instructure/ui-text-input": "10.16.3",
|
|
52
|
+
"@instructure/ui-utils": "10.16.3",
|
|
53
|
+
"@instructure/ui-view": "10.16.3",
|
|
54
|
+
"@instructure/uid": "10.16.3",
|
|
55
55
|
"prop-types": "^15.8.1"
|
|
56
56
|
},
|
|
57
57
|
"peerDependencies": {
|
package/src/Select/README.md
CHANGED
|
@@ -26,6 +26,14 @@ describes: Select
|
|
|
26
26
|
selectedOptionId: this.props.options[0].id,
|
|
27
27
|
announcement: null
|
|
28
28
|
}
|
|
29
|
+
inputElement = null
|
|
30
|
+
|
|
31
|
+
focusInput = () => {
|
|
32
|
+
this.inputElement.blur()
|
|
33
|
+
if (this.inputElement) {
|
|
34
|
+
this.inputElement.focus()
|
|
35
|
+
}
|
|
36
|
+
}
|
|
29
37
|
|
|
30
38
|
getOptionById(queryId) {
|
|
31
39
|
return this.props.options.find(({ id }) => id === queryId)
|
|
@@ -69,6 +77,7 @@ describes: Select
|
|
|
69
77
|
}
|
|
70
78
|
|
|
71
79
|
handleSelectOption = (event, { id }) => {
|
|
80
|
+
this.focusInput()
|
|
72
81
|
const option = this.getOptionById(id).label
|
|
73
82
|
this.setState({
|
|
74
83
|
selectedOptionId: id,
|
|
@@ -99,6 +108,9 @@ describes: Select
|
|
|
99
108
|
onRequestHideOptions={this.handleHideOptions}
|
|
100
109
|
onRequestHighlightOption={this.handleHighlightOption}
|
|
101
110
|
onRequestSelectOption={this.handleSelectOption}
|
|
111
|
+
inputRef={(el) => {
|
|
112
|
+
this.inputElement = el
|
|
113
|
+
}}
|
|
102
114
|
>
|
|
103
115
|
{this.props.options.map((option) => {
|
|
104
116
|
return (
|
|
@@ -158,6 +170,14 @@ describes: Select
|
|
|
158
170
|
const [highlightedOptionId, setHighlightedOptionId] = useState(null)
|
|
159
171
|
const [selectedOptionId, setSelectedOptionId] = useState(options[0].id)
|
|
160
172
|
const [announcement, setAnnouncement] = useState(null)
|
|
173
|
+
const [inputElement, setInputElement] = useState(null)
|
|
174
|
+
|
|
175
|
+
const focusInput = () => {
|
|
176
|
+
inputElement.blur()
|
|
177
|
+
if (inputElement) {
|
|
178
|
+
inputElement.focus()
|
|
179
|
+
}
|
|
180
|
+
}
|
|
161
181
|
|
|
162
182
|
const getOptionById = (queryId) => {
|
|
163
183
|
return options.find(({ id }) => id === queryId)
|
|
@@ -193,6 +213,7 @@ describes: Select
|
|
|
193
213
|
|
|
194
214
|
const handleSelectOption = (event, { id }) => {
|
|
195
215
|
const option = getOptionById(id).label
|
|
216
|
+
focusInput()
|
|
196
217
|
setSelectedOptionId(id)
|
|
197
218
|
setInputValue(option)
|
|
198
219
|
setIsShowingOptions(false)
|
|
@@ -211,6 +232,9 @@ describes: Select
|
|
|
211
232
|
onRequestHideOptions={handleHideOptions}
|
|
212
233
|
onRequestHighlightOption={handleHighlightOption}
|
|
213
234
|
onRequestSelectOption={handleSelectOption}
|
|
235
|
+
inputRef={(el) => {
|
|
236
|
+
setInputElement(el)
|
|
237
|
+
}}
|
|
214
238
|
>
|
|
215
239
|
{options.map((option) => {
|
|
216
240
|
return (
|
|
@@ -277,6 +301,7 @@ It's best practice to always provide autocomplete functionality to help users ma
|
|
|
277
301
|
filteredOptions: this.props.options,
|
|
278
302
|
announcement: null
|
|
279
303
|
}
|
|
304
|
+
inputElement = null
|
|
280
305
|
|
|
281
306
|
getOptionById(queryId) {
|
|
282
307
|
return this.props.options.find(({ id }) => id === queryId)
|
|
@@ -304,6 +329,13 @@ It's best practice to always provide autocomplete functionality to help users ma
|
|
|
304
329
|
)
|
|
305
330
|
}
|
|
306
331
|
|
|
332
|
+
focusInput = () => {
|
|
333
|
+
this.inputElement.blur()
|
|
334
|
+
if (this.inputElement) {
|
|
335
|
+
this.inputElement.focus()
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
307
339
|
matchValue() {
|
|
308
340
|
const {
|
|
309
341
|
filteredOptions,
|
|
@@ -380,6 +412,7 @@ It's best practice to always provide autocomplete functionality to help users ma
|
|
|
380
412
|
handleSelectOption = (event, { id }) => {
|
|
381
413
|
const option = this.getOptionById(id)
|
|
382
414
|
if (!option) return // prevent selecting of empty option
|
|
415
|
+
this.focusInput()
|
|
383
416
|
this.setState({
|
|
384
417
|
selectedOptionId: id,
|
|
385
418
|
inputValue: option.label,
|
|
@@ -428,6 +461,9 @@ It's best practice to always provide autocomplete functionality to help users ma
|
|
|
428
461
|
onRequestSelectOption={this.handleSelectOption}
|
|
429
462
|
renderBeforeInput={<IconUserSolid inline={false} />}
|
|
430
463
|
renderAfterInput={<IconSearchLine inline={false} />}
|
|
464
|
+
inputRef={(el) => {
|
|
465
|
+
this.inputElement = el
|
|
466
|
+
}}
|
|
431
467
|
>
|
|
432
468
|
{filteredOptions.length > 0 ? (
|
|
433
469
|
filteredOptions.map((option) => {
|
|
@@ -499,6 +535,14 @@ It's best practice to always provide autocomplete functionality to help users ma
|
|
|
499
535
|
const [selectedOptionId, setSelectedOptionId] = useState(null)
|
|
500
536
|
const [filteredOptions, setFilteredOptions] = useState(options)
|
|
501
537
|
const [announcement, setAnnouncement] = useState(null)
|
|
538
|
+
const [inputElement, setInputElement] = useState(null)
|
|
539
|
+
|
|
540
|
+
const focusInput = () => {
|
|
541
|
+
inputElement.blur()
|
|
542
|
+
if (inputElement) {
|
|
543
|
+
inputElement.focus()
|
|
544
|
+
}
|
|
545
|
+
}
|
|
502
546
|
|
|
503
547
|
const getOptionById = (queryId) => {
|
|
504
548
|
return options.find(({ id }) => id === queryId)
|
|
@@ -586,6 +630,7 @@ It's best practice to always provide autocomplete functionality to help users ma
|
|
|
586
630
|
const handleSelectOption = (event, { id }) => {
|
|
587
631
|
const option = getOptionById(id)
|
|
588
632
|
if (!option) return // prevent selecting of empty option
|
|
633
|
+
focusInput()
|
|
589
634
|
setSelectedOptionId(id)
|
|
590
635
|
setInputValue(option.label)
|
|
591
636
|
setIsShowingOptions(false)
|
|
@@ -620,6 +665,9 @@ It's best practice to always provide autocomplete functionality to help users ma
|
|
|
620
665
|
onRequestSelectOption={handleSelectOption}
|
|
621
666
|
renderBeforeInput={<IconUserSolid inline={false} />}
|
|
622
667
|
renderAfterInput={<IconSearchLine inline={false} />}
|
|
668
|
+
inputRef={(el) => {
|
|
669
|
+
setInputElement(el)
|
|
670
|
+
}}
|
|
623
671
|
>
|
|
624
672
|
{filteredOptions.length > 0 ? (
|
|
625
673
|
filteredOptions.map((option) => {
|
|
@@ -697,6 +745,15 @@ To mark an option as "highlighted", use the option's `isHighlighted` prop. Note
|
|
|
697
745
|
announcement: null
|
|
698
746
|
}
|
|
699
747
|
|
|
748
|
+
inputRef = null
|
|
749
|
+
|
|
750
|
+
focusInput = () => {
|
|
751
|
+
this.inputRef.blur()
|
|
752
|
+
if (this.inputRef) {
|
|
753
|
+
this.inputRef.focus()
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
|
|
700
757
|
getOptionById(queryId) {
|
|
701
758
|
return this.props.options.find(({ id }) => id === queryId)
|
|
702
759
|
}
|
|
@@ -784,6 +841,7 @@ To mark an option as "highlighted", use the option's `isHighlighted` prop. Note
|
|
|
784
841
|
}
|
|
785
842
|
|
|
786
843
|
handleSelectOption = (event, { id }) => {
|
|
844
|
+
this.focusInput()
|
|
787
845
|
const option = this.getOptionById(id)
|
|
788
846
|
if (!option) return // prevent selecting of empty option
|
|
789
847
|
this.setState((state) => ({
|
|
@@ -956,7 +1014,14 @@ To mark an option as "highlighted", use the option's `isHighlighted` prop. Note
|
|
|
956
1014
|
const [selectedOptionId, setSelectedOptionId] = useState(['opt1', 'opt6'])
|
|
957
1015
|
const [filteredOptions, setFilteredOptions] = useState(options)
|
|
958
1016
|
const [announcement, setAnnouncement] = useState(null)
|
|
959
|
-
const
|
|
1017
|
+
const [inputElement, setInputElement] = useState(null)
|
|
1018
|
+
|
|
1019
|
+
const focusInput = () => {
|
|
1020
|
+
inputElement.blur()
|
|
1021
|
+
if (inputElement) {
|
|
1022
|
+
inputElement.focus()
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
960
1025
|
|
|
961
1026
|
const getOptionById = (queryId) => {
|
|
962
1027
|
return options.find(({ id }) => id === queryId)
|
|
@@ -1030,6 +1095,7 @@ To mark an option as "highlighted", use the option's `isHighlighted` prop. Note
|
|
|
1030
1095
|
const handleSelectOption = (event, { id }) => {
|
|
1031
1096
|
const option = getOptionById(id)
|
|
1032
1097
|
if (!option) return // prevent selecting of empty option
|
|
1098
|
+
focusInput()
|
|
1033
1099
|
setSelectedOptionId([...selectedOptionId, id])
|
|
1034
1100
|
setHighlightedOptionId(null)
|
|
1035
1101
|
setFilteredOptions(filterOptions(''))
|
|
@@ -1080,8 +1146,8 @@ To mark an option as "highlighted", use the option's `isHighlighted` prop. Note
|
|
|
1080
1146
|
dismissible
|
|
1081
1147
|
key={id}
|
|
1082
1148
|
text={
|
|
1083
|
-
<AccessibleContent alt={`Remove ${
|
|
1084
|
-
{
|
|
1149
|
+
<AccessibleContent alt={`Remove ${getOptionById(id).label}`}>
|
|
1150
|
+
{getOptionById(id).label}
|
|
1085
1151
|
</AccessibleContent>
|
|
1086
1152
|
}
|
|
1087
1153
|
margin={
|
|
@@ -1099,7 +1165,9 @@ To mark an option as "highlighted", use the option's `isHighlighted` prop. Note
|
|
|
1099
1165
|
assistiveText="Type or use arrow keys to navigate options. Multiple selections allowed."
|
|
1100
1166
|
inputValue={inputValue}
|
|
1101
1167
|
isShowingOptions={isShowingOptions}
|
|
1102
|
-
inputRef={(el) =>
|
|
1168
|
+
inputRef={(el) => {
|
|
1169
|
+
setInputElement(el)
|
|
1170
|
+
}}
|
|
1103
1171
|
onBlur={handleBlur}
|
|
1104
1172
|
onInputChange={handleInputChange}
|
|
1105
1173
|
onRequestShowOptions={handleShowOptions}
|
|
@@ -1180,6 +1248,15 @@ In addition to `<Select.Option />` Select also accepts `<Select.Group />` as chi
|
|
|
1180
1248
|
announcement: null
|
|
1181
1249
|
}
|
|
1182
1250
|
|
|
1251
|
+
inputElement = null
|
|
1252
|
+
|
|
1253
|
+
focusInput = () => {
|
|
1254
|
+
this.inputElement.blur()
|
|
1255
|
+
if (this.inputElement) {
|
|
1256
|
+
this.inputElement.focus()
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1183
1260
|
getOptionById(id) {
|
|
1184
1261
|
const { options } = this.props
|
|
1185
1262
|
let match = null
|
|
@@ -1240,6 +1317,7 @@ In addition to `<Select.Option />` Select also accepts `<Select.Group />` as chi
|
|
|
1240
1317
|
}
|
|
1241
1318
|
|
|
1242
1319
|
handleSelectOption = (event, { id }) => {
|
|
1320
|
+
this.focusInput()
|
|
1243
1321
|
this.setState({
|
|
1244
1322
|
selectedOptionId: id,
|
|
1245
1323
|
inputValue: this.getOptionById(id).label,
|
|
@@ -1322,6 +1400,9 @@ In addition to `<Select.Option />` Select also accepts `<Select.Group />` as chi
|
|
|
1322
1400
|
margin="0 0 xxx-small 0"
|
|
1323
1401
|
/>
|
|
1324
1402
|
}
|
|
1403
|
+
inputRef={(el) => {
|
|
1404
|
+
this.inputElement = el
|
|
1405
|
+
}}
|
|
1325
1406
|
>
|
|
1326
1407
|
{this.renderGroup()}
|
|
1327
1408
|
</Select>
|
|
@@ -1368,6 +1449,14 @@ In addition to `<Select.Option />` Select also accepts `<Select.Group />` as chi
|
|
|
1368
1449
|
options['Western'][0].id
|
|
1369
1450
|
)
|
|
1370
1451
|
const [announcement, setAnnouncement] = useState(null)
|
|
1452
|
+
const [inputElement, setInputElement] = useState(null)
|
|
1453
|
+
|
|
1454
|
+
const focusInput = () => {
|
|
1455
|
+
inputElement.blur()
|
|
1456
|
+
if (inputElement) {
|
|
1457
|
+
inputElement.focus()
|
|
1458
|
+
}
|
|
1459
|
+
}
|
|
1371
1460
|
|
|
1372
1461
|
const getOptionById = (id) => {
|
|
1373
1462
|
let match = null
|
|
@@ -1418,6 +1507,7 @@ In addition to `<Select.Option />` Select also accepts `<Select.Group />` as chi
|
|
|
1418
1507
|
}
|
|
1419
1508
|
|
|
1420
1509
|
const handleSelectOption = (event, { id }) => {
|
|
1510
|
+
focusInput()
|
|
1421
1511
|
setSelectedOptionId(id)
|
|
1422
1512
|
setInputValue(getOptionById(id).label)
|
|
1423
1513
|
setIsShowingOptions(false)
|
|
@@ -1485,6 +1575,9 @@ In addition to `<Select.Option />` Select also accepts `<Select.Group />` as chi
|
|
|
1485
1575
|
margin="0 0 xxx-small 0"
|
|
1486
1576
|
/>
|
|
1487
1577
|
}
|
|
1578
|
+
inputRef={(el) => {
|
|
1579
|
+
setInputElement(el)
|
|
1580
|
+
}}
|
|
1488
1581
|
>
|
|
1489
1582
|
{renderGroup()}
|
|
1490
1583
|
</Select>
|
|
@@ -1536,6 +1629,15 @@ Due to a WebKit bug if you are using `Select.Group` with autocomplete, the scree
|
|
|
1536
1629
|
announcement: null
|
|
1537
1630
|
}
|
|
1538
1631
|
|
|
1632
|
+
inputElement = null
|
|
1633
|
+
|
|
1634
|
+
focusInput = () => {
|
|
1635
|
+
this.inputElement.blur()
|
|
1636
|
+
if (this.inputElement) {
|
|
1637
|
+
this.inputElement.focus()
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1640
|
+
|
|
1539
1641
|
getOptionById(id) {
|
|
1540
1642
|
const options = this.props.options
|
|
1541
1643
|
return Object.values(options)
|
|
@@ -1593,6 +1695,7 @@ Due to a WebKit bug if you are using `Select.Group` with autocomplete, the scree
|
|
|
1593
1695
|
handleSelectOption = (event, { id }) => {
|
|
1594
1696
|
const option = this.getOptionById(id)
|
|
1595
1697
|
if (!option) return // prevent selecting of empty option
|
|
1698
|
+
this.focusInput()
|
|
1596
1699
|
this.setState({
|
|
1597
1700
|
selectedOptionId: id,
|
|
1598
1701
|
inputValue: option.label,
|
|
@@ -1672,6 +1775,9 @@ Due to a WebKit bug if you are using `Select.Group` with autocomplete, the scree
|
|
|
1672
1775
|
onRequestHideOptions={this.handleHideOptions}
|
|
1673
1776
|
onRequestHighlightOption={this.handleHighlightOption}
|
|
1674
1777
|
onRequestSelectOption={this.handleSelectOption}
|
|
1778
|
+
inputRef={(el) => {
|
|
1779
|
+
this.inputElement = el
|
|
1780
|
+
}}
|
|
1675
1781
|
>
|
|
1676
1782
|
{this.renderGroup()}
|
|
1677
1783
|
</Select>
|
|
@@ -1711,6 +1817,14 @@ Due to a WebKit bug if you are using `Select.Group` with autocomplete, the scree
|
|
|
1711
1817
|
const [selectedOptionId, setSelectedOptionId] = useState(null)
|
|
1712
1818
|
const [filteredOptions, setFilteredOptions] = useState(options)
|
|
1713
1819
|
const [announcement, setAnnouncement] = useState(null)
|
|
1820
|
+
const [inputElement, setInputElement] = useState(null)
|
|
1821
|
+
|
|
1822
|
+
const focusInput = () => {
|
|
1823
|
+
inputElement.blur()
|
|
1824
|
+
if (inputElement) {
|
|
1825
|
+
inputElement.focus()
|
|
1826
|
+
}
|
|
1827
|
+
}
|
|
1714
1828
|
|
|
1715
1829
|
const getOptionById = (id) => {
|
|
1716
1830
|
return Object.values(options)
|
|
@@ -1757,6 +1871,7 @@ Due to a WebKit bug if you are using `Select.Group` with autocomplete, the scree
|
|
|
1757
1871
|
const handleSelectOption = (event, { id }) => {
|
|
1758
1872
|
const option = getOptionById(id)
|
|
1759
1873
|
if (!option) return // prevent selecting of empty option
|
|
1874
|
+
focusInput()
|
|
1760
1875
|
setSelectedOptionId(id)
|
|
1761
1876
|
setInputValue(option.label)
|
|
1762
1877
|
setIsShowingOptions(false)
|
|
@@ -1819,6 +1934,9 @@ Due to a WebKit bug if you are using `Select.Group` with autocomplete, the scree
|
|
|
1819
1934
|
onRequestHideOptions={handleHideOptions}
|
|
1820
1935
|
onRequestHighlightOption={handleHighlightOption}
|
|
1821
1936
|
onRequestSelectOption={handleSelectOption}
|
|
1937
|
+
inputRef={(el) => {
|
|
1938
|
+
setInputElement(el)
|
|
1939
|
+
}}
|
|
1822
1940
|
>
|
|
1823
1941
|
{renderGroup()}
|
|
1824
1942
|
</Select>
|
|
@@ -1866,6 +1984,15 @@ If no results match the user's search, it's recommended to leave `isShowingOptio
|
|
|
1866
1984
|
announcement: null
|
|
1867
1985
|
}
|
|
1868
1986
|
|
|
1987
|
+
inputElement = null
|
|
1988
|
+
|
|
1989
|
+
focusInput = () => {
|
|
1990
|
+
this.inputElement.blur()
|
|
1991
|
+
if (this.inputElement) {
|
|
1992
|
+
this.inputElement.focus()
|
|
1993
|
+
}
|
|
1994
|
+
}
|
|
1995
|
+
|
|
1869
1996
|
timeoutId = null
|
|
1870
1997
|
|
|
1871
1998
|
getOptionById(queryId) {
|
|
@@ -1941,6 +2068,7 @@ If no results match the user's search, it's recommended to leave `isShowingOptio
|
|
|
1941
2068
|
handleSelectOption = (event, { id }) => {
|
|
1942
2069
|
const option = this.getOptionById(id)
|
|
1943
2070
|
if (!option) return // prevent selecting of empty option
|
|
2071
|
+
this.focusInput()
|
|
1944
2072
|
this.setState({
|
|
1945
2073
|
selectedOptionId: id,
|
|
1946
2074
|
selectedOptionLabel: option.label,
|
|
@@ -2009,6 +2137,9 @@ If no results match the user's search, it's recommended to leave `isShowingOptio
|
|
|
2009
2137
|
onRequestHideOptions={this.handleHideOptions}
|
|
2010
2138
|
onRequestHighlightOption={this.handleHighlightOption}
|
|
2011
2139
|
onRequestSelectOption={this.handleSelectOption}
|
|
2140
|
+
inputRef={(el) => {
|
|
2141
|
+
this.inputElement = el
|
|
2142
|
+
}}
|
|
2012
2143
|
>
|
|
2013
2144
|
{filteredOptions.length > 0 ? (
|
|
2014
2145
|
filteredOptions.map((option) => {
|
|
@@ -2086,6 +2217,14 @@ If no results match the user's search, it's recommended to leave `isShowingOptio
|
|
|
2086
2217
|
const [selectedOptionLabel, setSelectedOptionLabel] = useState('')
|
|
2087
2218
|
const [filteredOptions, setFilteredOptions] = useState([])
|
|
2088
2219
|
const [announcement, setAnnouncement] = useState(null)
|
|
2220
|
+
const [inputElement, setInputElement] = useState(null)
|
|
2221
|
+
|
|
2222
|
+
const focusInput = () => {
|
|
2223
|
+
inputElement.blur()
|
|
2224
|
+
if (inputElement) {
|
|
2225
|
+
inputElement.focus()
|
|
2226
|
+
}
|
|
2227
|
+
}
|
|
2089
2228
|
|
|
2090
2229
|
let timeoutId = null
|
|
2091
2230
|
|
|
@@ -2151,6 +2290,7 @@ If no results match the user's search, it's recommended to leave `isShowingOptio
|
|
|
2151
2290
|
const handleSelectOption = (event, { id }) => {
|
|
2152
2291
|
const option = getOptionById(id)
|
|
2153
2292
|
if (!option) return // prevent selecting of empty option
|
|
2293
|
+
focusInput()
|
|
2154
2294
|
setSelectedOptionId(id)
|
|
2155
2295
|
setSelectedOptionLabel(option.label)
|
|
2156
2296
|
setInputValue(option.label)
|
|
@@ -2200,6 +2340,9 @@ If no results match the user's search, it's recommended to leave `isShowingOptio
|
|
|
2200
2340
|
onRequestHideOptions={handleHideOptions}
|
|
2201
2341
|
onRequestHighlightOption={handleHighlightOption}
|
|
2202
2342
|
onRequestSelectOption={handleSelectOption}
|
|
2343
|
+
inputRef={(el) => {
|
|
2344
|
+
setInputElement(el)
|
|
2345
|
+
}}
|
|
2203
2346
|
>
|
|
2204
2347
|
{filteredOptions.length > 0 ? (
|
|
2205
2348
|
filteredOptions.map((option) => {
|
|
@@ -2279,6 +2422,14 @@ To display icons (or other elements) before or after an option, pass it via the
|
|
|
2279
2422
|
selectedOptionId: this.props.options[0].id,
|
|
2280
2423
|
announcement: null
|
|
2281
2424
|
}
|
|
2425
|
+
inputElement = null
|
|
2426
|
+
|
|
2427
|
+
focusInput = () => {
|
|
2428
|
+
this.inputElement.blur()
|
|
2429
|
+
if (this.inputElement) {
|
|
2430
|
+
this.inputElement.focus()
|
|
2431
|
+
}
|
|
2432
|
+
}
|
|
2282
2433
|
|
|
2283
2434
|
getOptionById(queryId) {
|
|
2284
2435
|
return this.props.options.find(({ id }) => id === queryId)
|
|
@@ -2323,6 +2474,7 @@ To display icons (or other elements) before or after an option, pass it via the
|
|
|
2323
2474
|
|
|
2324
2475
|
handleSelectOption = (event, { id }) => {
|
|
2325
2476
|
const option = this.getOptionById(id).label
|
|
2477
|
+
this.focusInput()
|
|
2326
2478
|
this.setState({
|
|
2327
2479
|
selectedOptionId: id,
|
|
2328
2480
|
inputValue: option,
|
|
@@ -2352,6 +2504,9 @@ To display icons (or other elements) before or after an option, pass it via the
|
|
|
2352
2504
|
onRequestHideOptions={this.handleHideOptions}
|
|
2353
2505
|
onRequestHighlightOption={this.handleHighlightOption}
|
|
2354
2506
|
onRequestSelectOption={this.handleSelectOption}
|
|
2507
|
+
inputRef={(el) => {
|
|
2508
|
+
this.inputElement = el
|
|
2509
|
+
}}
|
|
2355
2510
|
>
|
|
2356
2511
|
{this.props.options.map((option) => {
|
|
2357
2512
|
return (
|
|
@@ -2417,6 +2572,14 @@ To display icons (or other elements) before or after an option, pass it via the
|
|
|
2417
2572
|
const [highlightedOptionId, setHighlightedOptionId] = useState(null)
|
|
2418
2573
|
const [selectedOptionId, setSelectedOptionId] = useState(options[0].id)
|
|
2419
2574
|
const [announcement, setAnnouncement] = useState(null)
|
|
2575
|
+
const [inputElement, setInputElement] = useState(null)
|
|
2576
|
+
|
|
2577
|
+
const focusInput = () => {
|
|
2578
|
+
inputElement.blur()
|
|
2579
|
+
if (inputElement) {
|
|
2580
|
+
inputElement.focus()
|
|
2581
|
+
}
|
|
2582
|
+
}
|
|
2420
2583
|
|
|
2421
2584
|
const getOptionById = (queryId) => {
|
|
2422
2585
|
return options.find(({ id }) => id === queryId)
|
|
@@ -2452,6 +2615,7 @@ To display icons (or other elements) before or after an option, pass it via the
|
|
|
2452
2615
|
|
|
2453
2616
|
const handleSelectOption = (event, { id }) => {
|
|
2454
2617
|
const option = getOptionById(id).label
|
|
2618
|
+
focusInput()
|
|
2455
2619
|
setSelectedOptionId(id)
|
|
2456
2620
|
setInputValue(option)
|
|
2457
2621
|
setIsShowingOptions(false)
|
|
@@ -2470,6 +2634,9 @@ To display icons (or other elements) before or after an option, pass it via the
|
|
|
2470
2634
|
onRequestHideOptions={handleHideOptions}
|
|
2471
2635
|
onRequestHighlightOption={handleHighlightOption}
|
|
2472
2636
|
onRequestSelectOption={handleSelectOption}
|
|
2637
|
+
inputRef={(el) => {
|
|
2638
|
+
setInputElement(el)
|
|
2639
|
+
}}
|
|
2473
2640
|
>
|
|
2474
2641
|
{options.map((option) => {
|
|
2475
2642
|
return (
|
|
@@ -2532,3 +2699,15 @@ To display icons (or other elements) before or after an option, pass it via the
|
|
|
2532
2699
|
It's important to ensure screen reader users receive instruction and feedback while interacting with a `Select`, but screen reader support for the `combobox` role varies. The `assistiveText` prop should always be used to explain how a keyboard user can make a selection. Additionally, a live region should be updated with feedback as the component is interacted with, such as when options are filtered or highlighted. Using an [Alert](#Alert) with the `screenReaderOnly` prop is the easiest way to do this.
|
|
2533
2700
|
|
|
2534
2701
|
> Note: This component uses a native `input` field to render the selected value. When it's included in a native HTML `form`, the text value will be sent to the backend instead of anything specified in the `value` field of the `Select.Option`-s. We do not recommend to use this component this way, rather write your own code that collects information and sends it to the backend.
|
|
2702
|
+
|
|
2703
|
+
```js
|
|
2704
|
+
---
|
|
2705
|
+
type: embed
|
|
2706
|
+
---
|
|
2707
|
+
<Guidelines>
|
|
2708
|
+
<Figure recommendation="a11y" title="Accessibility">
|
|
2709
|
+
<Figure.Item>To ensure Select is accessible for iOS VoiceOver users, the input field’s focus must be blurred and then reapplied after selecting an option and closing the listbox. The examples above demonstrate this behavior.
|
|
2710
|
+
</Figure.Item>
|
|
2711
|
+
</Figure>
|
|
2712
|
+
</Guidelines>
|
|
2713
|
+
```
|