@instructure/ui-select 8.39.1-snapshot-1 → 8.40.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/CHANGELOG.md +1 -9
- package/package.json +22 -22
- package/src/Select/README.md +184 -0
- package/tsconfig.build.tsbuildinfo +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,22 +3,14 @@
|
|
|
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
|
-
|
|
6
|
+
# [8.40.0](https://github.com/instructure/instructure-ui/compare/v8.39.0...v8.40.0) (2023-08-17)
|
|
7
7
|
|
|
8
8
|
**Note:** Version bump only for package @instructure/ui-select
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
10
|
# [8.39.0](https://github.com/instructure/instructure-ui/compare/v8.38.1...v8.39.0) (2023-07-21)
|
|
15
11
|
|
|
16
12
|
**Note:** Version bump only for package @instructure/ui-select
|
|
17
13
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
14
|
## [8.38.1](https://github.com/instructure/instructure-ui/compare/v8.38.0...v8.38.1) (2023-06-13)
|
|
23
15
|
|
|
24
16
|
**Note:** Version bump only for package @instructure/ui-select
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@instructure/ui-select",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.40.0",
|
|
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,30 +23,30 @@
|
|
|
23
23
|
},
|
|
24
24
|
"license": "MIT",
|
|
25
25
|
"devDependencies": {
|
|
26
|
-
"@instructure/ui-babel-preset": "8.
|
|
27
|
-
"@instructure/ui-color-utils": "8.
|
|
28
|
-
"@instructure/ui-test-locator": "8.
|
|
29
|
-
"@instructure/ui-test-utils": "8.
|
|
30
|
-
"@instructure/ui-themes": "8.
|
|
26
|
+
"@instructure/ui-babel-preset": "8.40.0",
|
|
27
|
+
"@instructure/ui-color-utils": "8.40.0",
|
|
28
|
+
"@instructure/ui-test-locator": "8.40.0",
|
|
29
|
+
"@instructure/ui-test-utils": "8.40.0",
|
|
30
|
+
"@instructure/ui-themes": "8.40.0"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"@babel/runtime": "^7.22.6",
|
|
34
|
-
"@instructure/emotion": "8.
|
|
35
|
-
"@instructure/shared-types": "8.
|
|
36
|
-
"@instructure/ui-dom-utils": "8.
|
|
37
|
-
"@instructure/ui-form-field": "8.
|
|
38
|
-
"@instructure/ui-icons": "8.
|
|
39
|
-
"@instructure/ui-options": "8.
|
|
40
|
-
"@instructure/ui-popover": "8.
|
|
41
|
-
"@instructure/ui-position": "8.
|
|
42
|
-
"@instructure/ui-prop-types": "8.
|
|
43
|
-
"@instructure/ui-react-utils": "8.
|
|
44
|
-
"@instructure/ui-selectable": "8.
|
|
45
|
-
"@instructure/ui-testable": "8.
|
|
46
|
-
"@instructure/ui-text-input": "8.
|
|
47
|
-
"@instructure/ui-utils": "8.
|
|
48
|
-
"@instructure/ui-view": "8.
|
|
49
|
-
"@instructure/uid": "8.
|
|
34
|
+
"@instructure/emotion": "8.40.0",
|
|
35
|
+
"@instructure/shared-types": "8.40.0",
|
|
36
|
+
"@instructure/ui-dom-utils": "8.40.0",
|
|
37
|
+
"@instructure/ui-form-field": "8.40.0",
|
|
38
|
+
"@instructure/ui-icons": "8.40.0",
|
|
39
|
+
"@instructure/ui-options": "8.40.0",
|
|
40
|
+
"@instructure/ui-popover": "8.40.0",
|
|
41
|
+
"@instructure/ui-position": "8.40.0",
|
|
42
|
+
"@instructure/ui-prop-types": "8.40.0",
|
|
43
|
+
"@instructure/ui-react-utils": "8.40.0",
|
|
44
|
+
"@instructure/ui-selectable": "8.40.0",
|
|
45
|
+
"@instructure/ui-testable": "8.40.0",
|
|
46
|
+
"@instructure/ui-text-input": "8.40.0",
|
|
47
|
+
"@instructure/ui-utils": "8.40.0",
|
|
48
|
+
"@instructure/ui-view": "8.40.0",
|
|
49
|
+
"@instructure/uid": "8.40.0",
|
|
50
50
|
"prop-types": "^15.8.1"
|
|
51
51
|
},
|
|
52
52
|
"peerDependencies": {
|
package/src/Select/README.md
CHANGED
|
@@ -835,6 +835,190 @@ render(
|
|
|
835
835
|
)
|
|
836
836
|
```
|
|
837
837
|
|
|
838
|
+
##### Using groups with autocomplete on Safari
|
|
839
|
+
|
|
840
|
+
Due to a WebKit bug if you are using `Select.Group` with autocomplete, the screenreader won't announce highlight/selection changes. This only seems to be an issue in Safari. Here is an example how you can work around that:
|
|
841
|
+
|
|
842
|
+
```javascript
|
|
843
|
+
---
|
|
844
|
+
example: true
|
|
845
|
+
render: false
|
|
846
|
+
---
|
|
847
|
+
|
|
848
|
+
class GroupSelectAutocompleteExample extends React.Component {
|
|
849
|
+
state = {
|
|
850
|
+
inputValue: '',
|
|
851
|
+
isShowingOptions: false,
|
|
852
|
+
highlightedOptionId: null,
|
|
853
|
+
selectedOptionId: null,
|
|
854
|
+
filteredOptions: this.props.options,
|
|
855
|
+
announcement: null
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
getOptionById (id) {
|
|
859
|
+
const options = this.props.options
|
|
860
|
+
return Object.values(options)
|
|
861
|
+
.flat()
|
|
862
|
+
.find((o) => o?.id === id);
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
filterOptions (value, options) {
|
|
866
|
+
const filteredOptions = {};
|
|
867
|
+
Object.keys(options).forEach((key) => {
|
|
868
|
+
filteredOptions[key] = options[key]?.filter(
|
|
869
|
+
(option) =>
|
|
870
|
+
option.label.toLowerCase().includes(value.toLowerCase())
|
|
871
|
+
);
|
|
872
|
+
});
|
|
873
|
+
const optionsWithoutEmptyKeys = Object.keys(filteredOptions)
|
|
874
|
+
.filter((k) => filteredOptions[k].length > 0)
|
|
875
|
+
.reduce((a, k) => ({ ...a, [k]: filteredOptions[k] }), {});
|
|
876
|
+
return optionsWithoutEmptyKeys;
|
|
877
|
+
};
|
|
878
|
+
|
|
879
|
+
handleShowOptions = (event) => {
|
|
880
|
+
this.setState({
|
|
881
|
+
isShowingOptions: true,
|
|
882
|
+
highlightedOptionId: null
|
|
883
|
+
})
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
handleHideOptions = (event) => {
|
|
887
|
+
const { selectedOptionId } = this.state
|
|
888
|
+
this.setState({
|
|
889
|
+
isShowingOptions: false,
|
|
890
|
+
highlightedOptionId: null
|
|
891
|
+
})
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
handleBlur = (event) => {
|
|
895
|
+
this.setState({
|
|
896
|
+
highlightedOptionId: null
|
|
897
|
+
})
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
handleHighlightOption = (event, { id }) => {
|
|
901
|
+
event.persist()
|
|
902
|
+
const option = this.getOptionById(id)
|
|
903
|
+
setTimeout(() => {
|
|
904
|
+
this.setState((state) => ({
|
|
905
|
+
announcement: option.label
|
|
906
|
+
}))
|
|
907
|
+
}, 0)
|
|
908
|
+
this.setState((state) => ({
|
|
909
|
+
highlightedOptionId: id,
|
|
910
|
+
}))
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
handleSelectOption = (event, { id }) => {
|
|
914
|
+
const option = this.getOptionById(id)
|
|
915
|
+
if (!option) return // prevent selecting of empty option
|
|
916
|
+
this.setState({
|
|
917
|
+
selectedOptionId: id,
|
|
918
|
+
inputValue: option.label,
|
|
919
|
+
isShowingOptions: false,
|
|
920
|
+
filteredOptions: this.props.options,
|
|
921
|
+
announcement: option.label
|
|
922
|
+
})
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
handleInputChange = (event) => {
|
|
926
|
+
const value = event.target.value
|
|
927
|
+
const newOptions = this.filterOptions(value, this.props.options)
|
|
928
|
+
this.setState((state) => ({
|
|
929
|
+
inputValue: value,
|
|
930
|
+
filteredOptions: newOptions,
|
|
931
|
+
highlightedOptionId: newOptions.length > 0 ? newOptions[0].id : null,
|
|
932
|
+
isShowingOptions: true,
|
|
933
|
+
selectedOptionId: value === '' ? null : state.selectedOptionId,
|
|
934
|
+
}))
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
renderGroup () {
|
|
938
|
+
const filteredOptions = this.state.filteredOptions
|
|
939
|
+
const { highlightedOptionId, selectedOptionId } = this.state
|
|
940
|
+
|
|
941
|
+
return Object.keys(filteredOptions).map((key, index) => {
|
|
942
|
+
return (
|
|
943
|
+
<Select.Group key={index} renderLabel={key}>
|
|
944
|
+
{filteredOptions[key].map((option) => (
|
|
945
|
+
<Select.Option
|
|
946
|
+
key={option.id}
|
|
947
|
+
id={option.id}
|
|
948
|
+
isHighlighted={option.id === highlightedOptionId}
|
|
949
|
+
isSelected={option.id === selectedOptionId}
|
|
950
|
+
>
|
|
951
|
+
{ option.label }
|
|
952
|
+
</Select.Option>
|
|
953
|
+
))}
|
|
954
|
+
</Select.Group>
|
|
955
|
+
)
|
|
956
|
+
})
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
renderScreenReaderHelper () {
|
|
960
|
+
const announcement = this.state.announcement
|
|
961
|
+
return window.safari && (
|
|
962
|
+
<ScreenReaderContent>
|
|
963
|
+
<span role="alert" aria-live="assertive">{announcement}</span>
|
|
964
|
+
</ScreenReaderContent>
|
|
965
|
+
)
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
render () {
|
|
969
|
+
const {
|
|
970
|
+
inputValue,
|
|
971
|
+
isShowingOptions,
|
|
972
|
+
highlightedOptionId,
|
|
973
|
+
selectedOptionId,
|
|
974
|
+
filteredOptions
|
|
975
|
+
} = this.state
|
|
976
|
+
|
|
977
|
+
return (
|
|
978
|
+
<div>
|
|
979
|
+
<Select
|
|
980
|
+
placeholder="Start typing to search..."
|
|
981
|
+
renderLabel="Group Select with autocomplete"
|
|
982
|
+
assistiveText="Type or use arrow keys to navigate options."
|
|
983
|
+
inputValue={inputValue}
|
|
984
|
+
isShowingOptions={isShowingOptions}
|
|
985
|
+
onBlur={this.handleBlur}
|
|
986
|
+
onInputChange={this.handleInputChange}
|
|
987
|
+
onRequestShowOptions={this.handleShowOptions}
|
|
988
|
+
onRequestHideOptions={this.handleHideOptions}
|
|
989
|
+
onRequestHighlightOption={this.handleHighlightOption}
|
|
990
|
+
onRequestSelectOption={this.handleSelectOption}
|
|
991
|
+
>
|
|
992
|
+
{this.renderGroup()}
|
|
993
|
+
</Select>
|
|
994
|
+
{this.renderScreenReaderHelper()}
|
|
995
|
+
</div>
|
|
996
|
+
)
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
render(
|
|
1001
|
+
<View>
|
|
1002
|
+
<GroupSelectAutocompleteExample
|
|
1003
|
+
options={{
|
|
1004
|
+
'Western': [
|
|
1005
|
+
{ id: 'opt5', label: 'Alaska' },
|
|
1006
|
+
{ id: 'opt6', label: 'California' },
|
|
1007
|
+
{ id: 'opt7', label: 'Colorado' },
|
|
1008
|
+
{ id: 'opt8', label: 'Idaho' }
|
|
1009
|
+
],
|
|
1010
|
+
'Eastern': [
|
|
1011
|
+
{ id: 'opt1', label: 'Alabama' },
|
|
1012
|
+
{ id: 'opt2', label: 'Connecticut' },
|
|
1013
|
+
{ id: 'opt3', label: 'Delaware' },
|
|
1014
|
+
{ id: '4', label: 'Illinois' }
|
|
1015
|
+
]
|
|
1016
|
+
}}
|
|
1017
|
+
/>
|
|
1018
|
+
</View>
|
|
1019
|
+
)
|
|
1020
|
+
```
|
|
1021
|
+
|
|
838
1022
|
#### Asynchronous option loading
|
|
839
1023
|
|
|
840
1024
|
If no results match the user's search, it's recommended to leave `isShowingOptions` as `true` and to display an "empty option" as a way of communicating that there are no matches. Similarly, it's helpful to display a [Spinner](#Spinner) in an empty option while options load.
|