@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 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
- ## [8.39.1-snapshot-1](https://github.com/instructure/instructure-ui/compare/v8.39.0...v8.39.1-snapshot-1) (2023-08-07)
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.39.1-snapshot-1",
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.39.1-snapshot-1",
27
- "@instructure/ui-color-utils": "8.39.1-snapshot-1",
28
- "@instructure/ui-test-locator": "8.39.1-snapshot-1",
29
- "@instructure/ui-test-utils": "8.39.1-snapshot-1",
30
- "@instructure/ui-themes": "8.39.1-snapshot-1"
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.39.1-snapshot-1",
35
- "@instructure/shared-types": "8.39.1-snapshot-1",
36
- "@instructure/ui-dom-utils": "8.39.1-snapshot-1",
37
- "@instructure/ui-form-field": "8.39.1-snapshot-1",
38
- "@instructure/ui-icons": "8.39.1-snapshot-1",
39
- "@instructure/ui-options": "8.39.1-snapshot-1",
40
- "@instructure/ui-popover": "8.39.1-snapshot-1",
41
- "@instructure/ui-position": "8.39.1-snapshot-1",
42
- "@instructure/ui-prop-types": "8.39.1-snapshot-1",
43
- "@instructure/ui-react-utils": "8.39.1-snapshot-1",
44
- "@instructure/ui-selectable": "8.39.1-snapshot-1",
45
- "@instructure/ui-testable": "8.39.1-snapshot-1",
46
- "@instructure/ui-text-input": "8.39.1-snapshot-1",
47
- "@instructure/ui-utils": "8.39.1-snapshot-1",
48
- "@instructure/ui-view": "8.39.1-snapshot-1",
49
- "@instructure/uid": "8.39.1-snapshot-1",
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": {
@@ -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.