@dhis2-ui/select 10.9.2 → 10.10.0-alpha.2

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.
Files changed (150) hide show
  1. package/build/cjs/index.js +18 -4
  2. package/build/cjs/locales/en/translations.json +7 -5
  3. package/build/cjs/simple-single-select/__stories__/DefaultPosition.js +38 -0
  4. package/build/cjs/simple-single-select/__stories__/Dense.js +22 -0
  5. package/build/cjs/simple-single-select/__stories__/Empty.js +20 -0
  6. package/build/cjs/simple-single-select/__stories__/EmptyWithEmptyNode.js +28 -0
  7. package/build/cjs/simple-single-select/__stories__/EmptyWithEmptyText.js +21 -0
  8. package/build/cjs/simple-single-select/__stories__/FlippedPosition.js +37 -0
  9. package/build/cjs/simple-single-select/__stories__/InfiniteLoading.js +61 -0
  10. package/build/cjs/simple-single-select/__stories__/Loading.js +20 -0
  11. package/build/cjs/simple-single-select/__stories__/ShiftedIntoView.js +28 -0
  12. package/build/cjs/simple-single-select/__stories__/WithClearButton.js +25 -0
  13. package/build/cjs/simple-single-select/__stories__/WithCustomLowMaxHeight.js +22 -0
  14. package/build/cjs/simple-single-select/__stories__/WithCustomOptions.js +48 -0
  15. package/build/cjs/simple-single-select/__stories__/WithDisabledOption.js +28 -0
  16. package/build/cjs/simple-single-select/__stories__/WithFilterField.js +36 -0
  17. package/build/cjs/simple-single-select/__stories__/WithInitialFocus.js +38 -0
  18. package/build/cjs/simple-single-select/__stories__/WithManyOptions.js +24 -0
  19. package/build/cjs/simple-single-select/__stories__/WithNoMatchText.js +30 -0
  20. package/build/cjs/simple-single-select/__stories__/WithOnBlur.js +23 -0
  21. package/build/cjs/simple-single-select/__stories__/WithOnFocus.js +23 -0
  22. package/build/cjs/simple-single-select/__stories__/WithOptionsAndDisabled.js +22 -0
  23. package/build/cjs/simple-single-select/__stories__/WithOptionsAndLoading.js +34 -0
  24. package/build/cjs/simple-single-select/__stories__/WithOptionsAndLoadingText.js +35 -0
  25. package/build/cjs/simple-single-select/__stories__/WithPlaceholder.js +23 -0
  26. package/build/cjs/simple-single-select/__stories__/WithPlaceholderAndSelection.js +25 -0
  27. package/build/cjs/simple-single-select/__stories__/WithPrefix.js +22 -0
  28. package/build/cjs/simple-single-select/__stories__/WithPrefixAndSelection.js +25 -0
  29. package/build/cjs/simple-single-select/__stories__/WithRTL.js +34 -0
  30. package/build/cjs/simple-single-select/__stories__/WithScrollingParent.js +36 -0
  31. package/build/cjs/simple-single-select/__stories__/WithSelection.js +24 -0
  32. package/build/cjs/simple-single-select/__stories__/WithSelectionAndDisabled.js +25 -0
  33. package/build/cjs/simple-single-select/__stories__/WithoutOptionsAndLoading.js +21 -0
  34. package/build/cjs/simple-single-select/__stories__/WithoutSelection.js +22 -0
  35. package/build/cjs/simple-single-select/__stories__/options.js +77 -0
  36. package/build/cjs/simple-single-select/features/keyboard-interactions.test.e2e.js +441 -0
  37. package/build/cjs/simple-single-select/features/menu-positioning.test.e2e.js +152 -0
  38. package/build/cjs/simple-single-select/index.js +19 -0
  39. package/build/cjs/simple-single-select/is-option-hidden.js +22 -0
  40. package/build/cjs/simple-single-select/menu/empty.js +26 -0
  41. package/build/cjs/simple-single-select/menu/filter.js +60 -0
  42. package/build/cjs/simple-single-select/menu/index.js +12 -0
  43. package/build/cjs/simple-single-select/menu/loading.js +30 -0
  44. package/build/cjs/simple-single-select/menu/menu.js +158 -0
  45. package/build/cjs/simple-single-select/menu/no-match.js +28 -0
  46. package/build/cjs/simple-single-select/menu/option.js +120 -0
  47. package/build/cjs/simple-single-select/menu/options-list.js +97 -0
  48. package/build/cjs/simple-single-select/selected-value/clear-button.js +74 -0
  49. package/build/cjs/simple-single-select/selected-value/container.js +100 -0
  50. package/build/cjs/simple-single-select/selected-value/index.js +12 -0
  51. package/build/cjs/simple-single-select/selected-value/placeholder.js +30 -0
  52. package/build/cjs/simple-single-select/selected-value/prefix.js +30 -0
  53. package/build/cjs/simple-single-select/selected-value/selected-value.js +119 -0
  54. package/build/cjs/simple-single-select/shared-prop-types.js +14 -0
  55. package/build/cjs/simple-single-select/simple-single-select.e2e.stories.js +156 -0
  56. package/build/cjs/simple-single-select/simple-single-select.js +319 -0
  57. package/build/cjs/simple-single-select/simple-single-select.prod.stories.js +235 -0
  58. package/build/cjs/simple-single-select/simple-single-select.test.js +1106 -0
  59. package/build/cjs/simple-single-select/use-handle-key-press/index.js +19 -0
  60. package/build/cjs/simple-single-select/use-handle-key-press/use-focus-option.js +24 -0
  61. package/build/cjs/simple-single-select/use-handle-key-press/use-handle-key-press-on-combobox.js +183 -0
  62. package/build/cjs/simple-single-select/use-handle-key-press/use-handle-key-press-on-filter-input.js +106 -0
  63. package/build/cjs/simple-single-select/use-handle-key-press/use-handle-typing.js +81 -0
  64. package/build/cjs/simple-single-select/use-handle-key-press/use-highlight-first-option-on-previous-page.js +50 -0
  65. package/build/cjs/simple-single-select/use-handle-key-press/use-highlight-first-visible-option.js +30 -0
  66. package/build/cjs/simple-single-select/use-handle-key-press/use-highlight-last-option-on-next-page.js +50 -0
  67. package/build/cjs/simple-single-select/use-handle-key-press/use-highlight-last-visible-option.js +42 -0
  68. package/build/cjs/simple-single-select/use-handle-key-press/use-page-down.js +45 -0
  69. package/build/cjs/simple-single-select/use-handle-key-press/use-page-up.js +44 -0
  70. package/build/cjs/simple-single-select/use-handle-key-press/utils.js +46 -0
  71. package/build/cjs/simple-single-select-field/index.js +12 -0
  72. package/build/cjs/simple-single-select-field/simple-single-select-field.js +138 -0
  73. package/build/cjs/simple-single-select-field/simple-single-select-field.prod.stories.js +188 -0
  74. package/build/cjs/simple-single-select-field/simple-single-select-field.test.js +133 -0
  75. package/build/es/index.js +2 -0
  76. package/build/es/locales/en/translations.json +7 -5
  77. package/build/es/simple-single-select/__stories__/DefaultPosition.js +29 -0
  78. package/build/es/simple-single-select/__stories__/Dense.js +13 -0
  79. package/build/es/simple-single-select/__stories__/Empty.js +11 -0
  80. package/build/es/simple-single-select/__stories__/EmptyWithEmptyNode.js +19 -0
  81. package/build/es/simple-single-select/__stories__/EmptyWithEmptyText.js +12 -0
  82. package/build/es/simple-single-select/__stories__/FlippedPosition.js +28 -0
  83. package/build/es/simple-single-select/__stories__/InfiniteLoading.js +53 -0
  84. package/build/es/simple-single-select/__stories__/Loading.js +12 -0
  85. package/build/es/simple-single-select/__stories__/ShiftedIntoView.js +18 -0
  86. package/build/es/simple-single-select/__stories__/WithClearButton.js +16 -0
  87. package/build/es/simple-single-select/__stories__/WithCustomLowMaxHeight.js +13 -0
  88. package/build/es/simple-single-select/__stories__/WithCustomOptions.js +38 -0
  89. package/build/es/simple-single-select/__stories__/WithDisabledOption.js +19 -0
  90. package/build/es/simple-single-select/__stories__/WithFilterField.js +27 -0
  91. package/build/es/simple-single-select/__stories__/WithInitialFocus.js +29 -0
  92. package/build/es/simple-single-select/__stories__/WithManyOptions.js +15 -0
  93. package/build/es/simple-single-select/__stories__/WithNoMatchText.js +21 -0
  94. package/build/es/simple-single-select/__stories__/WithOnBlur.js +14 -0
  95. package/build/es/simple-single-select/__stories__/WithOnFocus.js +14 -0
  96. package/build/es/simple-single-select/__stories__/WithOptionsAndDisabled.js +13 -0
  97. package/build/es/simple-single-select/__stories__/WithOptionsAndLoading.js +25 -0
  98. package/build/es/simple-single-select/__stories__/WithOptionsAndLoadingText.js +26 -0
  99. package/build/es/simple-single-select/__stories__/WithPlaceholder.js +14 -0
  100. package/build/es/simple-single-select/__stories__/WithPlaceholderAndSelection.js +16 -0
  101. package/build/es/simple-single-select/__stories__/WithPrefix.js +13 -0
  102. package/build/es/simple-single-select/__stories__/WithPrefixAndSelection.js +16 -0
  103. package/build/es/simple-single-select/__stories__/WithRTL.js +25 -0
  104. package/build/es/simple-single-select/__stories__/WithScrollingParent.js +27 -0
  105. package/build/es/simple-single-select/__stories__/WithSelection.js +15 -0
  106. package/build/es/simple-single-select/__stories__/WithSelectionAndDisabled.js +16 -0
  107. package/build/es/simple-single-select/__stories__/WithoutOptionsAndLoading.js +12 -0
  108. package/build/es/simple-single-select/__stories__/WithoutSelection.js +13 -0
  109. package/build/es/simple-single-select/__stories__/options.js +71 -0
  110. package/build/es/simple-single-select/features/keyboard-interactions.test.e2e.js +439 -0
  111. package/build/es/simple-single-select/features/menu-positioning.test.e2e.js +150 -0
  112. package/build/es/simple-single-select/index.js +2 -0
  113. package/build/es/simple-single-select/is-option-hidden.js +16 -0
  114. package/build/es/simple-single-select/menu/empty.js +19 -0
  115. package/build/es/simple-single-select/menu/filter.js +53 -0
  116. package/build/es/simple-single-select/menu/index.js +1 -0
  117. package/build/es/simple-single-select/menu/loading.js +23 -0
  118. package/build/es/simple-single-select/menu/menu.js +149 -0
  119. package/build/es/simple-single-select/menu/no-match.js +21 -0
  120. package/build/es/simple-single-select/menu/option.js +111 -0
  121. package/build/es/simple-single-select/menu/options-list.js +88 -0
  122. package/build/es/simple-single-select/selected-value/clear-button.js +68 -0
  123. package/build/es/simple-single-select/selected-value/container.js +91 -0
  124. package/build/es/simple-single-select/selected-value/index.js +1 -0
  125. package/build/es/simple-single-select/selected-value/placeholder.js +23 -0
  126. package/build/es/simple-single-select/selected-value/prefix.js +23 -0
  127. package/build/es/simple-single-select/selected-value/selected-value.js +112 -0
  128. package/build/es/simple-single-select/shared-prop-types.js +7 -0
  129. package/build/es/simple-single-select/simple-single-select.e2e.stories.js +142 -0
  130. package/build/es/simple-single-select/simple-single-select.js +310 -0
  131. package/build/es/simple-single-select/simple-single-select.prod.stories.js +37 -0
  132. package/build/es/simple-single-select/simple-single-select.test.js +1103 -0
  133. package/build/es/simple-single-select/use-handle-key-press/index.js +2 -0
  134. package/build/es/simple-single-select/use-handle-key-press/use-focus-option.js +18 -0
  135. package/build/es/simple-single-select/use-handle-key-press/use-handle-key-press-on-combobox.js +177 -0
  136. package/build/es/simple-single-select/use-handle-key-press/use-handle-key-press-on-filter-input.js +100 -0
  137. package/build/es/simple-single-select/use-handle-key-press/use-handle-typing.js +75 -0
  138. package/build/es/simple-single-select/use-handle-key-press/use-highlight-first-option-on-previous-page.js +44 -0
  139. package/build/es/simple-single-select/use-handle-key-press/use-highlight-first-visible-option.js +24 -0
  140. package/build/es/simple-single-select/use-handle-key-press/use-highlight-last-option-on-next-page.js +44 -0
  141. package/build/es/simple-single-select/use-handle-key-press/use-highlight-last-visible-option.js +36 -0
  142. package/build/es/simple-single-select/use-handle-key-press/use-page-down.js +39 -0
  143. package/build/es/simple-single-select/use-handle-key-press/use-page-up.js +38 -0
  144. package/build/es/simple-single-select/use-handle-key-press/utils.js +36 -0
  145. package/build/es/simple-single-select-field/index.js +1 -0
  146. package/build/es/simple-single-select-field/simple-single-select-field.js +131 -0
  147. package/build/es/simple-single-select-field/simple-single-select-field.prod.stories.js +172 -0
  148. package/build/es/simple-single-select-field/simple-single-select-field.test.js +130 -0
  149. package/package.json +15 -15
  150. package/types/index.d.ts +184 -1
@@ -0,0 +1,2 @@
1
+ export { useHandleKeyPressOnCombobox } from './use-handle-key-press-on-combobox.js';
2
+ export { useHandleKeyPressOnFilterInput } from './use-handle-key-press-on-filter-input.js';
@@ -0,0 +1,18 @@
1
+ import { useCallback } from 'react';
2
+ export function useFocusOption(findIndexCallback, _ref) {
3
+ let {
4
+ options,
5
+ focussedOptionIndex,
6
+ setFocussedOptionIndex
7
+ } = _ref;
8
+ return useCallback(() => {
9
+ const nextFocussedIndex = findIndexCallback({
10
+ options,
11
+ activeIndex: focussedOptionIndex
12
+ });
13
+ if (nextFocussedIndex === -1) {
14
+ return;
15
+ }
16
+ setFocussedOptionIndex(nextFocussedIndex);
17
+ }, [findIndexCallback, options, focussedOptionIndex, setFocussedOptionIndex]);
18
+ }
@@ -0,0 +1,177 @@
1
+ import { useCallback } from 'react';
2
+ import { useFocusOption } from './use-focus-option.js';
3
+ import { useHandleTyping } from './use-handle-typing.js';
4
+ import { usePageDown } from './use-page-down.js';
5
+ import { usePageUp } from './use-page-up.js';
6
+ import { findNextOptionIndex, findPrevOptionIndex, findFirstOptionIndex, findLastOptionIndex } from './utils.js';
7
+ function useSelectOption(findIndexCallback, _ref) {
8
+ let {
9
+ options,
10
+ onChange,
11
+ value
12
+ } = _ref;
13
+ return useCallback(() => {
14
+ const currentOptionIndex = options.findIndex(option => option.value === value);
15
+ const nextSelectedOptionIndex = findIndexCallback({
16
+ options,
17
+ activeIndex: currentOptionIndex
18
+ });
19
+ if (nextSelectedOptionIndex === -1) {
20
+ return;
21
+ }
22
+ const nextOption = options[nextSelectedOptionIndex];
23
+ onChange({
24
+ label: nextOption.label,
25
+ value: nextOption.value
26
+ });
27
+ }, [findIndexCallback, options, onChange, value]);
28
+ }
29
+ export function useHandleKeyPressOnCombobox(_ref2) {
30
+ let {
31
+ value,
32
+ disabled,
33
+ expanded,
34
+ loading,
35
+ options,
36
+ openMenu,
37
+ closeMenu,
38
+ listBoxRef,
39
+ focussedOptionIndex,
40
+ setFocussedOptionIndex,
41
+ selectFocussedOption,
42
+ onChange
43
+ } = _ref2;
44
+ const {
45
+ onTyping,
46
+ typing
47
+ } = useHandleTyping({
48
+ expanded,
49
+ options,
50
+ setFocussedOptionIndex,
51
+ onChange
52
+ });
53
+ const pageDown = usePageDown({
54
+ options,
55
+ focussedOptionIndex,
56
+ setFocussedOptionIndex,
57
+ listBoxRef
58
+ });
59
+ const pageUp = usePageUp({
60
+ options,
61
+ listBoxRef,
62
+ focussedOptionIndex,
63
+ setFocussedOptionIndex
64
+ });
65
+ const selectNextOption = useSelectOption(findNextOptionIndex, {
66
+ options,
67
+ onChange,
68
+ value
69
+ });
70
+ const selectPrevOption = useSelectOption(findPrevOptionIndex, {
71
+ options,
72
+ onChange,
73
+ value
74
+ });
75
+ const focusNextOption = useFocusOption(findNextOptionIndex, {
76
+ options,
77
+ focussedOptionIndex,
78
+ setFocussedOptionIndex
79
+ });
80
+ const focusPrevOption = useFocusOption(findPrevOptionIndex, {
81
+ options,
82
+ focussedOptionIndex,
83
+ setFocussedOptionIndex
84
+ });
85
+ const focusFirstOption = useFocusOption(findFirstOptionIndex, {
86
+ options,
87
+ focussedOptionIndex,
88
+ setFocussedOptionIndex
89
+ });
90
+ const focusLastOption = useFocusOption(findLastOptionIndex, {
91
+ options,
92
+ focussedOptionIndex,
93
+ setFocussedOptionIndex
94
+ });
95
+ const handleKeyPress = useCallback(event => {
96
+ const {
97
+ key,
98
+ altKey,
99
+ ctrlKey,
100
+ metaKey
101
+ } = event;
102
+ if (disabled || loading) {
103
+ return;
104
+ }
105
+
106
+ /*
107
+ * The menu won't be an ancestor / sibling of the combobox,
108
+ * so when pressing tab, it's highly unlikely that any element
109
+ * in the menu would receive focus (happens only in storybook,
110
+ * where there's no other focussable element).
111
+ *
112
+ * Assistive technology will know about the menu as the combobox
113
+ * has the appropriate aria attributes that will point to the
114
+ * correct HTML elements.
115
+ */
116
+ if (expanded && key === 'Tab') {
117
+ closeMenu();
118
+ return;
119
+ }
120
+
121
+ // ! This is necessary for cases where the parent window is scrollable
122
+ // ! otherwise the ArrowDown/Up would change the combo but also scroll the window
123
+ if (key === 'ArrowDown' || key === 'ArrowUp' || key === ' ') {
124
+ event.preventDefault();
125
+ }
126
+ if (expanded && (key === 'Escape' || key === 'Enter' || key === ' ' && !typing || key === 'ArrowUp' && altKey || key === 'ArrowDown' && altKey)) {
127
+ if (value !== options[focussedOptionIndex].value) {
128
+ selectFocussedOption();
129
+ }
130
+ closeMenu();
131
+ return;
132
+ }
133
+ if (!expanded && (key === ' ' && !typing || key === 'Enter' || key === 'ArrowUp' && altKey || key === 'ArrowDown' && altKey)) {
134
+ openMenu();
135
+ return;
136
+ }
137
+ if (key === 'ArrowDown' && !expanded) {
138
+ selectNextOption();
139
+ return;
140
+ }
141
+ if (key === 'ArrowDown') {
142
+ focusNextOption();
143
+ return;
144
+ }
145
+ if (key === 'ArrowUp' && !expanded) {
146
+ selectPrevOption();
147
+ return;
148
+ }
149
+ if (key === 'ArrowUp') {
150
+ focusPrevOption();
151
+ return;
152
+ }
153
+ if (key === 'Home') {
154
+ focusFirstOption();
155
+ return;
156
+ }
157
+ if (key === 'End') {
158
+ focusLastOption();
159
+ return;
160
+ }
161
+ if (key === 'Backspace' || key === 'Clear' || key.length === 1 && key !== ' ' && !altKey && !ctrlKey && !metaKey || key === ' ' && typing) {
162
+ onTyping(key);
163
+ return;
164
+ }
165
+ if (expanded && key === 'PageUp') {
166
+ pageUp();
167
+ return;
168
+ }
169
+ if (expanded && key === 'PageDown') {
170
+ pageDown();
171
+ return;
172
+ }
173
+
174
+ // Do nothing
175
+ }, [closeMenu, disabled, expanded, loading, openMenu, options, value, typing, focussedOptionIndex, selectFocussedOption, selectNextOption, selectPrevOption, focusNextOption, focusPrevOption, focusFirstOption, focusLastOption, pageDown, pageUp, onTyping]);
176
+ return handleKeyPress;
177
+ }
@@ -0,0 +1,100 @@
1
+ import { useCallback } from 'react';
2
+ import { useFocusOption } from './use-focus-option.js';
3
+ import { usePageDown } from './use-page-down.js';
4
+ import { usePageUp } from './use-page-up.js';
5
+ import { findNextOptionIndex, findPrevOptionIndex, findFirstOptionIndex, findLastOptionIndex } from './utils.js';
6
+ export function useHandleKeyPressOnFilterInput(_ref) {
7
+ let {
8
+ value,
9
+ options,
10
+ loading,
11
+ closeMenu,
12
+ listBoxRef,
13
+ focusComboBox,
14
+ focussedOptionIndex,
15
+ setFocussedOptionIndex,
16
+ selectFocussedOption
17
+ } = _ref;
18
+ const pageDown = usePageDown({
19
+ options,
20
+ focussedOptionIndex,
21
+ setFocussedOptionIndex,
22
+ listBoxRef
23
+ });
24
+ const pageUp = usePageUp({
25
+ options,
26
+ listBoxRef,
27
+ focussedOptionIndex,
28
+ setFocussedOptionIndex
29
+ });
30
+ const focusNextOption = useFocusOption(findNextOptionIndex, {
31
+ options,
32
+ focussedOptionIndex,
33
+ setFocussedOptionIndex
34
+ });
35
+ const focusPrevOption = useFocusOption(findPrevOptionIndex, {
36
+ options,
37
+ focussedOptionIndex,
38
+ setFocussedOptionIndex
39
+ });
40
+ const focusFirstOption = useFocusOption(findFirstOptionIndex, {
41
+ options,
42
+ focussedOptionIndex,
43
+ setFocussedOptionIndex
44
+ });
45
+ const focusLastOption = useFocusOption(findLastOptionIndex, {
46
+ options,
47
+ focussedOptionIndex,
48
+ setFocussedOptionIndex
49
+ });
50
+ const handleKeyPress = useCallback(e => {
51
+ if (loading) {
52
+ return;
53
+ }
54
+ const {
55
+ key,
56
+ altKey
57
+ } = e;
58
+ if (key === 'Escape' || key === 'Enter' || key === 'ArrowUp' && altKey || key === 'ArrowDown' && altKey) {
59
+ if (value !== options[focussedOptionIndex].value) {
60
+ selectFocussedOption();
61
+ }
62
+ closeMenu();
63
+
64
+ // As the filter is being focused, we want to move focus back to the combobox
65
+ focusComboBox();
66
+ return;
67
+ }
68
+ if (key === 'ArrowDown') {
69
+ e.preventDefault(); // Disable moving cursor to end
70
+ focusNextOption();
71
+ return;
72
+ }
73
+ if (key === 'ArrowUp') {
74
+ e.preventDefault(); // Disable moving cursor to start
75
+ focusPrevOption();
76
+ return;
77
+ }
78
+ if (key === 'Home') {
79
+ e.preventDefault(); // Disable moving cursor to start
80
+ focusFirstOption();
81
+ return;
82
+ }
83
+ if (key === 'End') {
84
+ e.preventDefault(); // Disable moving cursor to end
85
+ focusLastOption();
86
+ return;
87
+ }
88
+ if (key === 'PageUp') {
89
+ pageUp();
90
+ return;
91
+ }
92
+ if (key === 'PageDown') {
93
+ pageDown();
94
+ return;
95
+ }
96
+
97
+ // Do nothing
98
+ }, [closeMenu, options, value, loading, focussedOptionIndex, selectFocussedOption, focusComboBox, focusNextOption, focusPrevOption, focusFirstOption, focusLastOption, pageDown, pageUp]);
99
+ return handleKeyPress;
100
+ }
@@ -0,0 +1,75 @@
1
+ import { useCallback, useEffect, useRef, useState } from 'react';
2
+ const TYPING_DEBOUNCE_TIME = 300; // ms
3
+
4
+ export function useHandleTyping(_ref) {
5
+ let {
6
+ expanded,
7
+ options,
8
+ setFocussedOptionIndex,
9
+ onChange
10
+ } = _ref;
11
+ const [value, setValue] = useState('');
12
+ const [typing, setTyping] = useState(false);
13
+
14
+ // This will reset the typed value after a given time
15
+ const timeoutRef = useRef();
16
+ useEffect(() => {
17
+ if (timeoutRef.current) {
18
+ clearTimeout(timeoutRef.current);
19
+ timeoutRef.current = null;
20
+ }
21
+ if (value) {
22
+ timeoutRef.current = setTimeout(() => {
23
+ setValue('');
24
+ setTyping(false);
25
+ }, TYPING_DEBOUNCE_TIME);
26
+ } else {
27
+ setTyping(false);
28
+ }
29
+ }, [
30
+ // adding value to the dependencies array so that the hooks runs every time the value changes
31
+ value]);
32
+
33
+ // This will focus the first option with a label starting with the typed sequence
34
+ const prevValueRef = useRef();
35
+ useEffect(() => {
36
+ if (value && value !== prevValueRef.current) {
37
+ // We only want to do this when the value changed
38
+ prevValueRef.current = value;
39
+ const optionIndex = options.findIndex(_ref2 => {
40
+ let {
41
+ disabled,
42
+ label
43
+ } = _ref2;
44
+ return !disabled && label.toLowerCase().startsWith(value.toLowerCase());
45
+ });
46
+ if (optionIndex !== -1) {
47
+ if (expanded) {
48
+ setFocussedOptionIndex(optionIndex);
49
+ } else {
50
+ const nextSelectedOption = options[optionIndex];
51
+ onChange({
52
+ label: nextSelectedOption.label,
53
+ value: nextSelectedOption.value
54
+ });
55
+ }
56
+ }
57
+ }
58
+ }, [value, options, setFocussedOptionIndex, expanded, onChange]);
59
+ const onTyping = useCallback(key => {
60
+ setTyping(true);
61
+ if (key === 'Backspace') {
62
+ setValue(prevValue => prevValue.slice(0, -1));
63
+ return;
64
+ }
65
+ if (key === 'Clear') {
66
+ setValue('');
67
+ return;
68
+ }
69
+ setValue(prevValue => `${prevValue}${key}`);
70
+ }, []);
71
+ return {
72
+ onTyping,
73
+ typing
74
+ };
75
+ }
@@ -0,0 +1,44 @@
1
+ import { useCallback } from 'react';
2
+ export function useHighlightFirstOptionOnPreviousPage(_ref) {
3
+ let {
4
+ options,
5
+ focussedOptionIndex,
6
+ setFocussedOptionIndex,
7
+ listBoxRef
8
+ } = _ref;
9
+ return useCallback(() => {
10
+ const listBoxParent = listBoxRef.current.parentNode;
11
+ const optionElements = Array.from(listBoxRef.current.childNodes);
12
+ const visibleOptionsAmount = Math.floor(listBoxParent.offsetHeight / optionElements[0].offsetHeight);
13
+ const nextTopOptionIndex = Math.max(0, focussedOptionIndex - visibleOptionsAmount);
14
+
15
+ // If there's no next option and we already have the last option in the list highlighted
16
+ if (!options[nextTopOptionIndex]) {
17
+ return;
18
+ }
19
+ if (!options[nextTopOptionIndex].disabled) {
20
+ const nextTopOption = optionElements[nextTopOptionIndex];
21
+ const scrollPosition = nextTopOption.offsetTop;
22
+ listBoxParent.scrollTop = scrollPosition;
23
+ setFocussedOptionIndex(nextTopOptionIndex);
24
+ return;
25
+ }
26
+ const lowerEnabledOptionIndex = options.slice(0, nextTopOptionIndex).findLastIndex(option => !option.disabled);
27
+ if (lowerEnabledOptionIndex !== -1) {
28
+ const lowerEnabledOption = optionElements[lowerEnabledOptionIndex];
29
+ const lowerScrollPosition = lowerEnabledOption.offsetTop;
30
+ listBoxParent.scrollTop = lowerScrollPosition;
31
+ setFocussedOptionIndex(lowerEnabledOptionIndex);
32
+ return;
33
+ }
34
+ const inbetweenEnabledOptionIndex = nextTopOptionIndex + options.slice(nextTopOptionIndex, focussedOptionIndex).findIndex(option => !option.disabled);
35
+ if (inbetweenEnabledOptionIndex === -1) {
36
+ // We're already on the first enabled option
37
+ return;
38
+ }
39
+ const inbetweenTopOption = optionElements[inbetweenEnabledOptionIndex];
40
+ const scrollPosition = inbetweenTopOption.offsetTop;
41
+ listBoxParent.scrollTop = scrollPosition;
42
+ setFocussedOptionIndex(inbetweenEnabledOptionIndex);
43
+ }, [options, focussedOptionIndex, setFocussedOptionIndex, listBoxRef]);
44
+ }
@@ -0,0 +1,24 @@
1
+ import { useCallback } from 'react';
2
+ export function useHighlightFirstVisibleOption(_ref) {
3
+ let {
4
+ options,
5
+ focussedOptionIndex,
6
+ setFocussedOptionIndex
7
+ } = _ref;
8
+ return useCallback(lowestVisibleIndex => {
9
+ if (!options[lowestVisibleIndex].disabled) {
10
+ setFocussedOptionIndex(lowestVisibleIndex);
11
+ return;
12
+ }
13
+ const previousEnabledOptionIndex = options.slice(0, lowestVisibleIndex).findLastIndex(option => !option.disabled);
14
+ if (previousEnabledOptionIndex !== -1) {
15
+ setFocussedOptionIndex(previousEnabledOptionIndex);
16
+ return;
17
+ }
18
+ const nextEnabledOptionIndex = lowestVisibleIndex + options.slice(lowestVisibleIndex, focussedOptionIndex).findIndex(option => !option.disabled);
19
+ if (nextEnabledOptionIndex !== -1) {
20
+ return;
21
+ }
22
+ setFocussedOptionIndex(nextEnabledOptionIndex);
23
+ }, [options, focussedOptionIndex, setFocussedOptionIndex]);
24
+ }
@@ -0,0 +1,44 @@
1
+ import { useCallback } from 'react';
2
+ export function useHighlightLastOptionOnNextPage(_ref) {
3
+ let {
4
+ options,
5
+ focussedOptionIndex,
6
+ setFocussedOptionIndex,
7
+ listBoxRef
8
+ } = _ref;
9
+ return useCallback(() => {
10
+ const listBoxParent = listBoxRef.current.parentNode;
11
+ const optionElements = Array.from(listBoxRef.current.childNodes);
12
+ const visibleOptionsAmount = Math.floor(listBoxParent.offsetHeight / optionElements[0].offsetHeight);
13
+ const nextHighlightedOptionIndex = Math.min(options.length - 1, focussedOptionIndex + visibleOptionsAmount);
14
+
15
+ // If there's no next option and we already have the last option in the list highlighted
16
+ if (!options[nextHighlightedOptionIndex]) {
17
+ return;
18
+ }
19
+ if (!options[nextHighlightedOptionIndex].disabled) {
20
+ // This will be the first option in the list
21
+ const {
22
+ offsetTop: scrollPosition
23
+ } = optionElements[nextHighlightedOptionIndex - visibleOptionsAmount + 1];
24
+ listBoxParent.scrollTop = scrollPosition;
25
+ setFocussedOptionIndex(nextHighlightedOptionIndex);
26
+ return;
27
+ }
28
+ const followingEnabledOptionIndex = nextHighlightedOptionIndex + options.slice(nextHighlightedOptionIndex).findIndex(option => !option.disabled);
29
+
30
+ // There is no enabled option after the disabled option that's at the end of the next page
31
+ // So we stay where we are
32
+ if (followingEnabledOptionIndex === -1) {
33
+ return;
34
+ }
35
+
36
+ // There is an enabled option after the disabled option that's at the end of the next page
37
+ // So that'll be the new highlighted option and the bottom of the next displayed page
38
+ const {
39
+ offsetTop: adjustedScrollPosition
40
+ } = optionElements[followingEnabledOptionIndex - visibleOptionsAmount + 1];
41
+ listBoxParent.scrollTop = adjustedScrollPosition;
42
+ setFocussedOptionIndex(followingEnabledOptionIndex);
43
+ }, [options, focussedOptionIndex, setFocussedOptionIndex, listBoxRef]);
44
+ }
@@ -0,0 +1,36 @@
1
+ import { useCallback } from 'react';
2
+ export function useHighlightLastVisibleOption(_ref) {
3
+ let {
4
+ options,
5
+ focussedOptionIndex,
6
+ setFocussedOptionIndex
7
+ } = _ref;
8
+ return useCallback(highestVisibleIndex => {
9
+ if (!options[highestVisibleIndex].disabled) {
10
+ setFocussedOptionIndex(highestVisibleIndex);
11
+ return;
12
+ }
13
+
14
+ // Last option on page is disabled, find next option that's not disabled
15
+ const followingEnabledOptionIndex = options.slice(highestVisibleIndex).findIndex(option => !option.disabled);
16
+ if (followingEnabledOptionIndex >= 0) {
17
+ // We need to add the highest visible index because the index is lower due to slicing the array
18
+ setFocussedOptionIndex(followingEnabledOptionIndex + highestVisibleIndex);
19
+ return;
20
+ }
21
+
22
+ // No following enabled option, trying to find the closest previous sibling of the last option on the current page
23
+ const closestToEndOfPageEnabledOptionIndex = options.slice(
24
+ // We don't include the currently highlighted option
25
+ focussedOptionIndex + 1, highestVisibleIndex).findLastIndex(option => !option.disabled);
26
+ if (closestToEndOfPageEnabledOptionIndex >= 0) {
27
+ setFocussedOptionIndex(closestToEndOfPageEnabledOptionIndex +
28
+ // We need to add the focused index and 1 because the index is lower due to slicing the array
29
+ focussedOptionIndex + 1);
30
+ return;
31
+ }
32
+
33
+ // The currently highlighted option is the last enabled option
34
+ return;
35
+ }, [options, focussedOptionIndex, setFocussedOptionIndex]);
36
+ }
@@ -0,0 +1,39 @@
1
+ import { useCallback } from 'react';
2
+ import { isOptionHidden } from '../is-option-hidden.js';
3
+ import { useHighlightLastOptionOnNextPage } from './use-highlight-last-option-on-next-page.js';
4
+ import { useHighlightLastVisibleOption } from './use-highlight-last-visible-option.js';
5
+ export function usePageDown(_ref) {
6
+ let {
7
+ options,
8
+ focussedOptionIndex,
9
+ setFocussedOptionIndex,
10
+ listBoxRef
11
+ } = _ref;
12
+ const highlightLastVisibleOption = useHighlightLastVisibleOption({
13
+ options,
14
+ focussedOptionIndex,
15
+ setFocussedOptionIndex
16
+ });
17
+ const highlightLastOptionOnNextPage = useHighlightLastOptionOnNextPage({
18
+ options,
19
+ focussedOptionIndex,
20
+ setFocussedOptionIndex,
21
+ listBoxRef
22
+ });
23
+ return useCallback(() => {
24
+ const listBoxParent = listBoxRef.current.parentNode;
25
+ const options = Array.from(listBoxRef.current.childNodes);
26
+ const highestVisibleIndex = options.findLastIndex(option => !isOptionHidden(option, listBoxParent));
27
+ if (highestVisibleIndex > focussedOptionIndex) {
28
+ highlightLastVisibleOption(highestVisibleIndex);
29
+ return;
30
+ }
31
+ if (highestVisibleIndex > -1) {
32
+ highlightLastOptionOnNextPage();
33
+ return;
34
+ }
35
+
36
+ // No visible option (e.g. when menu is empty)
37
+ return;
38
+ }, [focussedOptionIndex, listBoxRef, highlightLastVisibleOption, highlightLastOptionOnNextPage]);
39
+ }
@@ -0,0 +1,38 @@
1
+ import { useCallback } from 'react';
2
+ import { isOptionHidden } from '../is-option-hidden.js';
3
+ import { useHighlightFirstOptionOnPreviousPage } from './use-highlight-first-option-on-previous-page.js';
4
+ import { useHighlightFirstVisibleOption } from './use-highlight-first-visible-option.js';
5
+ export function usePageUp(_ref) {
6
+ let {
7
+ options,
8
+ listBoxRef,
9
+ focussedOptionIndex,
10
+ setFocussedOptionIndex
11
+ } = _ref;
12
+ const highlightFirstVisibleOption = useHighlightFirstVisibleOption({
13
+ options,
14
+ focussedOptionIndex,
15
+ setFocussedOptionIndex
16
+ });
17
+ const highlightFirstOptionOnPreviousPage = useHighlightFirstOptionOnPreviousPage({
18
+ options,
19
+ focussedOptionIndex,
20
+ setFocussedOptionIndex,
21
+ listBoxRef
22
+ });
23
+ return useCallback(() => {
24
+ const listBoxParent = listBoxRef.current.parentNode;
25
+ const optionElements = Array.from(listBoxRef.current.childNodes);
26
+ const lowestVisibleIndex = optionElements.findIndex(optionElement => !isOptionHidden(optionElement, listBoxParent));
27
+
28
+ // No visible option (e.g. when menu is empty)
29
+ if (lowestVisibleIndex === -1) {
30
+ return;
31
+ }
32
+ if (lowestVisibleIndex < focussedOptionIndex) {
33
+ highlightFirstVisibleOption(lowestVisibleIndex);
34
+ return;
35
+ }
36
+ highlightFirstOptionOnPreviousPage();
37
+ }, [focussedOptionIndex, listBoxRef, highlightFirstOptionOnPreviousPage, highlightFirstVisibleOption]);
38
+ }
@@ -0,0 +1,36 @@
1
+ export function isEnabled(_ref) {
2
+ let {
3
+ disabled
4
+ } = _ref;
5
+ return !disabled;
6
+ }
7
+ export function findNextOptionIndex(_ref2) {
8
+ let {
9
+ options,
10
+ activeIndex
11
+ } = _ref2;
12
+ const startIndex = activeIndex + 1;
13
+ const optionsToSearch = options.slice(startIndex);
14
+
15
+ // Need to add back the count we removed by slicing the options array
16
+ return startIndex + optionsToSearch.findIndex(isEnabled);
17
+ }
18
+ export function findPrevOptionIndex(_ref3) {
19
+ let {
20
+ options,
21
+ activeIndex
22
+ } = _ref3;
23
+ return options.slice(0, activeIndex).findLastIndex(isEnabled);
24
+ }
25
+ export function findFirstOptionIndex(_ref4) {
26
+ let {
27
+ options
28
+ } = _ref4;
29
+ return options.findIndex(isEnabled);
30
+ }
31
+ export function findLastOptionIndex(_ref5) {
32
+ let {
33
+ options
34
+ } = _ref5;
35
+ return options.findLastIndex(isEnabled);
36
+ }
@@ -0,0 +1 @@
1
+ export { SimpleSingleSelectField } from './simple-single-select-field.js';