@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,439 @@
1
+ describe('<SimpleSingleSelect/>', () => {
2
+ it('should highlight the first option on the currently displayed page', () => {
3
+ cy.log('**Given a select with 100 options is displayed**');
4
+ cy.visitStory('Simple Single Select', 'Hundret Options');
5
+ cy.log('**And the menu is visible**');
6
+ cy.findByRole('combobox').click();
7
+ cy.findByRole('option', {
8
+ selected: true
9
+ }).should('be.visible');
10
+ cy.log('**And the 74th option is being highlighted**');
11
+ cy.findByRole('combobox').focus().type(`Select option 73`);
12
+ cy.log('**And the 70th option is the first option on the current page**');
13
+ let optionOffset;
14
+ cy.findAllByRole('option').eq(70).then(option => {
15
+ const {
16
+ offsetTop
17
+ } = option.get(0);
18
+ optionOffset = offsetTop;
19
+ });
20
+ cy.findByRole('option', {
21
+ selected: true
22
+ }).invoke('parent') // listbox
23
+ .invoke('parent') // scrollable div
24
+ .then(listBoxParent => {
25
+ listBoxParent.get(0).scrollTop = optionOffset;
26
+ });
27
+ cy.findAllByRole('option').eq(70).should('be.visible');
28
+ cy.log('**When the PageUp key is pressed**');
29
+ cy.findByRole('combobox').trigger('keydown', {
30
+ key: 'PageUp',
31
+ force: true
32
+ });
33
+ cy.log('**Then the first option on the currently displayed page is highlighted**');
34
+ // For some reason, it takes a little time to change the highlighted option
35
+ cy.wait(1);
36
+ cy.findByRole('option', {
37
+ selected: true
38
+ }).invoke('attr', 'aria-label').should('equal', 'Select option 70');
39
+ });
40
+ it('should highlight the first enabled option before the first (disabled) option on the currently displayed page', () => {
41
+ cy.log('**Given a select with 100 options is displayed and option #17 is disabled**');
42
+ cy.visitStory('Simple Single Select', 'Hundret Options With Disabled');
43
+ cy.log('**And the menu is visible**');
44
+ cy.findByRole('combobox').click();
45
+ cy.findByRole('option', {
46
+ selected: true
47
+ }).should('be.visible');
48
+ cy.log('**And the 26th option is being highlighted**');
49
+ cy.findByRole('combobox').focus().type(`Select option 25`);
50
+ cy.log('**And the 17th option is the first option on the current page**');
51
+ let optionOffset;
52
+ cy.findAllByRole('option').eq(17).then(option => {
53
+ const {
54
+ offsetTop
55
+ } = option.get(0);
56
+ optionOffset = offsetTop;
57
+ });
58
+ cy.findByRole('option', {
59
+ selected: true
60
+ }).invoke('parent') // listbox
61
+ .invoke('parent') // scrollable div
62
+ .then(listBoxParent => {
63
+ listBoxParent.get(0).scrollTop = optionOffset;
64
+ });
65
+ cy.findAllByRole('option').eq(17).should('be.visible');
66
+ cy.log('**When the PageUp key is pressed**');
67
+ cy.findByRole('combobox').trigger('keydown', {
68
+ key: 'PageUp',
69
+ force: true
70
+ });
71
+ cy.log('**Then the first option on the currently displayed page is highlighted**');
72
+ // For some reason, it takes a little time to change the highlighted option
73
+ cy.wait(1);
74
+ cy.findByRole('option', {
75
+ selected: true
76
+ }).invoke('attr', 'aria-label').should('equal', 'Select option 16');
77
+ });
78
+ it('should highlight the first option on the previous page', () => {
79
+ cy.log('**Given a select with 100 options is displayed**');
80
+ cy.visitStory('Simple Single Select', 'Hundret Options');
81
+ cy.log('**And the menu is visible**');
82
+ cy.findByRole('combobox').click();
83
+ cy.findByRole('option', {
84
+ selected: true
85
+ }).should('be.visible');
86
+ cy.log('**And the 70th option is being highlighted**');
87
+ // Will automatically scroll there and make it the first option on the page
88
+ cy.findByRole('combobox').focus().type(`Select option 70`);
89
+ cy.log('**When the PageUp key is pressed**');
90
+ cy.findByRole('combobox').trigger('keydown', {
91
+ key: 'PageUp',
92
+ force: true
93
+ });
94
+ cy.log('**Then the first option on the previous page is highlighted**');
95
+ // For some reason, it takes a little time to change the highlighted option
96
+ cy.wait(1);
97
+ cy.findByRole('option', {
98
+ selected: true
99
+ }).invoke('attr', 'aria-label').should('equal', 'Select option 61');
100
+
101
+ // That option is truly the first option
102
+ cy.get('[role="option"]:visible').first().invoke('attr', 'aria-label').should('equal', 'Select option 61');
103
+ cy.log('**And the previously highlighted option is not visible**');
104
+ cy.findAllByRole('option').eq(70).should('not.be.visible');
105
+ });
106
+ it('should highlight the first enabled option before the first (disabled) option on the previous page', () => {
107
+ cy.log('**Given a select with 100 options is displayed and option #17 is disabled**');
108
+ cy.visitStory('Simple Single Select', 'Hundret Options With Disabled');
109
+ cy.log('**And the menu is visible**');
110
+ cy.findByRole('combobox').click();
111
+ cy.findByRole('option', {
112
+ selected: true
113
+ }).should('be.visible');
114
+ cy.log('**And the 27th option is being highlighted**');
115
+ // Will automatically scroll there and make it the first option on the page
116
+ cy.findByRole('combobox').focus().type(`Select option 27`);
117
+ cy.log('**When the PageUp key is pressed**');
118
+ cy.findByRole('combobox').trigger('keydown', {
119
+ key: 'PageUp',
120
+ force: true
121
+ });
122
+ cy.log('**Then the first option on the previous page is highlighted**');
123
+ // For some reason, it takes a little time to change the highlighted option
124
+ cy.wait(1);
125
+ cy.findByRole('option', {
126
+ selected: true
127
+ }).invoke('attr', 'aria-label').should('equal', 'Select option 16');
128
+ cy.log('**And that option is truly the first option**');
129
+ cy.get('[role="option"]:visible').first().invoke('attr', 'aria-label').should('equal', 'Select option 16');
130
+ cy.log('**And the previously highlighted option is not visible**');
131
+ cy.findAllByRole('option').eq(27).should('not.be.visible');
132
+ });
133
+ it('should highlight the first option', () => {
134
+ cy.log('**Given a select with 100 options is displayed**');
135
+ cy.visitStory('Simple Single Select', 'Hundret Options');
136
+ cy.log('**And the menu is visible**');
137
+ cy.findByRole('combobox').click();
138
+ cy.findByRole('option', {
139
+ selected: true
140
+ }).should('be.visible');
141
+ cy.log('**And the 2nd option is being highlighted**');
142
+ cy.findByRole('combobox').focus().type(`Select option 1`);
143
+ cy.log('**And the 2nd option is the first option on the current page**');
144
+ let optionOffset;
145
+ cy.findAllByRole('option').eq(1).then(option => {
146
+ const {
147
+ offsetTop
148
+ } = option.get(0);
149
+ optionOffset = offsetTop;
150
+ });
151
+ cy.findByRole('option', {
152
+ selected: true
153
+ }).invoke('parent') // listbox
154
+ .invoke('parent') // scrollable div
155
+ .then(listBoxParent => {
156
+ listBoxParent.get(0).scrollTop = optionOffset;
157
+ });
158
+ cy.log('**When the PageUp key is pressed**');
159
+ cy.findByRole('combobox').trigger('keydown', {
160
+ key: 'PageUp',
161
+ force: true
162
+ });
163
+ cy.log('**Then the first option is being highlighted**');
164
+ // For some reason, it takes a little time to change the highlighted option
165
+ cy.wait(1);
166
+ cy.all(() => cy.findAllByRole('option').invoke('get', 0), () => cy.findByRole('option', {
167
+ selected: true
168
+ }).invoke('get', 0)).then(_ref => {
169
+ let [firstOption, highlightedOption] = _ref;
170
+ expect(highlightedOption).to.equal(firstOption);
171
+ });
172
+ });
173
+ it('should highlight the second option when the first option is disabled', () => {
174
+ cy.log('**Given a select with 100 options is displayed and option #2 is disabled**');
175
+ cy.visitStory('Simple Single Select', 'Hundret Options With Disabled');
176
+ cy.log('**And the menu is visible**');
177
+ cy.findByRole('combobox').click();
178
+ cy.findByRole('option', {
179
+ selected: true
180
+ }).should('be.visible');
181
+ cy.log('**And the 3rd option is being highlighted**');
182
+ cy.findByRole('combobox').focus().type(`Select option 2`);
183
+ cy.log('**And the 3rd option is the first option on the current page**');
184
+ let optionOffset;
185
+ cy.findAllByRole('option').eq(2).then(option => {
186
+ const {
187
+ offsetTop
188
+ } = option.get(0);
189
+ optionOffset = offsetTop;
190
+ });
191
+ cy.findByRole('option', {
192
+ selected: true
193
+ }).invoke('parent') // listbox
194
+ .invoke('parent') // scrollable div
195
+ .then(listBoxParent => {
196
+ listBoxParent.get(0).scrollTop = optionOffset;
197
+ });
198
+ cy.log('**When the PageUp key is pressed**');
199
+ cy.findByRole('combobox').trigger('keydown', {
200
+ key: 'PageUp',
201
+ force: true
202
+ });
203
+ cy.log('**Then the second option is being highlighted**');
204
+ // For some reason, it takes a little time to change the highlighted option
205
+ cy.wait(1);
206
+ cy.all(() => cy.findAllByRole('option').invoke('get', 1), () => cy.findByRole('option', {
207
+ selected: true
208
+ }).invoke('get', 0)).then(_ref2 => {
209
+ let [firstOption, highlightedOption] = _ref2;
210
+ expect(highlightedOption).to.equal(firstOption);
211
+ });
212
+ });
213
+ it('should highlight the last option on the currently displayed page', () => {
214
+ cy.log('**Given a select with 100 options is displayed**');
215
+ cy.visitStory('Simple Single Select', 'Hundret Options');
216
+ cy.log('**And the menu is visible**');
217
+ // first option will be highlighted automatically
218
+ cy.findByRole('combobox').click();
219
+ cy.findByRole('option', {
220
+ selected: true
221
+ }).should('be.visible');
222
+ cy.log('**When the PageDown key is pressed**');
223
+ cy.findByRole('combobox').trigger('keydown', {
224
+ key: 'PageDown',
225
+ force: true
226
+ });
227
+ cy.log('**Then the last option on the currently displayed page is highlighted**');
228
+ // For some reason, it takes a little time to change the highlighted option
229
+ cy.wait(1);
230
+ cy.all(() => cy.get('[role="option"]:visible').last().invoke('get', 0), () => cy.findByRole('option', {
231
+ selected: true
232
+ }).invoke('get', 0)).then(_ref3 => {
233
+ let [lastVisibleOption, highlightedOption] = _ref3;
234
+ expect(highlightedOption).to.equal(lastVisibleOption);
235
+ });
236
+ });
237
+ it('should highlight the first enabled option after the last (disabled) option on the currently displayed page', () => {
238
+ cy.log('**Given a select with 100 options is displayed and option #17 is disabled**');
239
+ cy.visitStory('Simple Single Select', 'Hundret Options With Disabled');
240
+ cy.log('**And the 9th option is being selected (will cause it to be the first option on the page when opening the menu)**');
241
+ cy.findByRole('combobox').focus().type(`Select option 9`);
242
+ cy.log('**And the menu is visible**');
243
+ // first option will be highlighted automatically
244
+ cy.findByRole('combobox').click();
245
+ cy.findByRole('option', {
246
+ selected: true
247
+ }).should('be.visible');
248
+
249
+ // That option is truly the first option
250
+ cy.get('[role="option"]:visible').first().invoke('attr', 'aria-label').should('equal', 'Select option 9');
251
+ cy.log('**When the PageDown key is pressed**');
252
+ cy.findByRole('combobox').trigger('keydown', {
253
+ key: 'PageDown',
254
+ force: true
255
+ });
256
+
257
+ // For some reason, it takes a little time to change the highlighted option
258
+ cy.wait(1);
259
+ cy.log('**Then the first enabled option after last option on the currently displayed page is highlighted**');
260
+ cy.all(() => cy.findAllByRole('option').invoke('get', 19), () => cy.findByRole('option', {
261
+ selected: true
262
+ }).invoke('get', 0)).then(_ref4 => {
263
+ let [eighteensOptions, highlightedOption] = _ref4;
264
+ expect(highlightedOption).to.equal(eighteensOptions);
265
+ });
266
+ cy.log('**And the first enabled option after last option on the currently displayed page is the first visible option**');
267
+ cy.all(() => cy.get('[role="option"]:visible').invoke('get', 0), () => cy.findByRole('option', {
268
+ selected: true
269
+ }).invoke('get', 0)).then(_ref5 => {
270
+ let [lastVisibleOption, highlightedOption] = _ref5;
271
+ expect(highlightedOption).to.equal(lastVisibleOption);
272
+ });
273
+ });
274
+ it('should highlight the last option on the next page',
275
+ // We don't want the options to scroll when we check whether they're
276
+ // visible or not (as that'd make them visible)
277
+ {
278
+ scrollBehavior: false
279
+ }, () => {
280
+ const LAST_VISIBLE_OPTION_INDEX = 8;
281
+ cy.log('**Given a select with 100 options is displayed**');
282
+ cy.visitStory('Simple Single Select', 'Hundret Options');
283
+ cy.log('**And the last option on the first page is selected**');
284
+ cy.findByRole('combobox').focus().type(`Select option 8`);
285
+ cy.log('**And the menu is visible**');
286
+ cy.findByRole('combobox').click();
287
+ cy.findByRole('option', {
288
+ selected: true
289
+ }).should('be.visible');
290
+ cy.get('[role="option"]').eq(LAST_VISIBLE_OPTION_INDEX).invoke('attr', 'aria-selected').should('equal', 'true');
291
+ cy.log('**When the PageDown key is pressed**');
292
+ cy.findByRole('combobox').trigger('keydown', {
293
+ key: 'PageDown',
294
+ force: true
295
+ });
296
+ cy.log('**Then the next page is shown**');
297
+ cy.get('[role="option"]').eq(LAST_VISIBLE_OPTION_INDEX).should('not.be.visible');
298
+ cy.get('[role="option"]').eq(LAST_VISIBLE_OPTION_INDEX + 1).should('be.visible');
299
+ cy.log('**And the last option on the next page is highlighted**');
300
+ cy.get('[role="option"]:visible').last().invoke('attr', 'aria-selected').should('equal', 'true');
301
+ cy.log('**And the previously highlighted option is not visible**');
302
+ cy.get('[role="option"]').eq(LAST_VISIBLE_OPTION_INDEX + 1).invoke('attr', 'aria-selected').should('equal', 'false');
303
+ });
304
+ it('should highlight the first enabled option after the last (disabled) option on the next page',
305
+ // We don't want the options to scroll when we check whether they're
306
+ // visible or not (as that'd make them visible)
307
+ {
308
+ scrollBehavior: false
309
+ }, () => {
310
+ cy.log('**Given a select with 100 options is displayed and option #17 is disabled**');
311
+ cy.visitStory('Simple Single Select', 'Hundret Options With Disabled');
312
+ cy.log('**And the 2nd option is being selected**');
313
+ cy.findByRole('combobox').focus().type(`Select option 9`);
314
+ cy.log('**And the menu is visible**');
315
+ cy.findByRole('combobox').click();
316
+ cy.findByRole('option', {
317
+ selected: true
318
+ }).should('be.visible');
319
+ cy.log('**And the 2nd option is the first option on the current page**');
320
+ cy.all(() => cy.findAllByRole('option').eq(1),
321
+ // scrollable div
322
+ () => cy.findByRole('option', {
323
+ selected: true
324
+ }).invoke('parent').invoke('parent')).then(_ref6 => {
325
+ let [nextTopOption, listBoxParent] = _ref6;
326
+ const {
327
+ offsetTop
328
+ } = nextTopOption.get(0);
329
+ listBoxParent.get(0).scrollTop = offsetTop;
330
+ });
331
+ cy.wrap(9).as('lastVisibleOptionIndex');
332
+ cy.get('@lastVisibleOptionIndex').then(lastVisibleOptionIndex => {
333
+ cy.get('[role="option"]').eq(lastVisibleOptionIndex).invoke('attr', 'aria-selected').should('equal', 'true');
334
+ });
335
+ cy.log('**When the PageDown key is pressed**');
336
+ cy.findByRole('combobox').trigger('keydown', {
337
+ key: 'PageDown',
338
+ force: true
339
+ });
340
+ cy.log('**Then the next page is shown,**');
341
+ // but one option is skipped due to the disabled option at the end of the next page
342
+ cy.get('@lastVisibleOptionIndex').then(lastVisibleOptionIndex => {
343
+ cy.get('[role="option"]').eq(lastVisibleOptionIndex + 1).should('not.be.visible');
344
+ cy.get('[role="option"]').eq(lastVisibleOptionIndex + 2).should('be.visible');
345
+ });
346
+ cy.log('**And the last option on the next page is highlighted**');
347
+ cy.get('[role="option"]:visible').last().invoke('attr', 'aria-selected').should('equal', 'true');
348
+ cy.get('[role="option"]:visible').last().invoke('attr', 'aria-label').should('equal', 'Select option 19');
349
+ });
350
+ it('should highlight the last option',
351
+ // We don't want the options to scroll when we check whether they're
352
+ // visible or not (as that'd make them visible)
353
+ {
354
+ scrollBehavior: false
355
+ }, () => {
356
+ cy.log('**Given a select with 100 options is displayed**');
357
+ cy.visitStory('Simple Single Select', 'Hundret Options');
358
+ cy.log('**And the menu is visible**');
359
+ cy.findByRole('combobox').click();
360
+ cy.findByRole('option', {
361
+ selected: true
362
+ }).should('be.visible');
363
+ cy.log('**And the 2nd-last option is being highlighted and visible**');
364
+ for (let i = 0; i < 98;
365
+ // This will bring us to the second last option exactly
366
+ ++i) {
367
+ cy.findByRole('combobox').trigger('keydown', {
368
+ key: 'ArrowDown',
369
+ force: true
370
+ });
371
+ cy.wait(1);
372
+ }
373
+ cy.findAllByRole('option').eq(98).should('be.visible');
374
+ cy.log('**And the last option is not visible**');
375
+ cy.findAllByRole('option').last().should('not.be.visible');
376
+ cy.log('**When the PageDown key is pressed**');
377
+ cy.findByRole('combobox').trigger('keydown', {
378
+ key: 'PageDown',
379
+ force: true
380
+ });
381
+ cy.log('**Then the last option is highlighted**');
382
+ // For some reason, it takes a little time to change the highlighted option
383
+ cy.wait(1);
384
+ cy.all(() => cy.findAllByRole('option').last().invoke('get', 0), () => cy.findByRole('option', {
385
+ selected: true
386
+ }).invoke('get', 0)).then(_ref7 => {
387
+ let [lastOption, highlightedOption] = _ref7;
388
+ expect(highlightedOption).to.equal(lastOption);
389
+ });
390
+ cy.log('**And the last option is visible**');
391
+ cy.findAllByRole('option').last().should('be.visible');
392
+ });
393
+ it('should highlight the last enabled option',
394
+ // We don't want the options to scroll when we check whether they're
395
+ // visible or not (as that'd make them visible)
396
+ {
397
+ scrollBehavior: false
398
+ }, () => {
399
+ cy.log('**Given a select with 100 options is displayed and option #99 is disabled**');
400
+ cy.visitStory('Simple Single Select', 'Hundret Options With Disabled');
401
+ cy.log('**And the 3rd-last option is being selected**');
402
+ cy.findByRole('combobox').focus().type(`Select option 97`);
403
+ cy.log('**And the menu is visible**');
404
+ cy.findByRole('combobox').click();
405
+ cy.findByRole('option', {
406
+ selected: true
407
+ }).should('be.visible');
408
+ cy.log('**And the 3rd-last option is the last option on the current page**');
409
+ cy.all(() => cy.findAllByRole('option').eq(89), () => cy.findByRole('listbox').invoke('parent') // scrollable div
410
+ ).then(_ref8 => {
411
+ let [nextTopOption, listBoxParent] = _ref8;
412
+ const {
413
+ offsetTop
414
+ } = nextTopOption.get(0);
415
+ listBoxParent.get(0).scrollTop = offsetTop;
416
+ });
417
+ cy.findAllByRole('option').eq(97).should('be.visible');
418
+ cy.findAllByRole('option').eq(98).should('not.be.visible');
419
+ cy.findAllByRole('option').last().should('not.be.visible');
420
+ cy.log('**When the PageDown key is pressed**');
421
+ cy.findByRole('combobox').trigger('keydown', {
422
+ key: 'PageDown',
423
+ force: true
424
+ });
425
+ cy.log('**Then the last option is highlighted**');
426
+ // For some reason, it takes a little time to change the highlighted option
427
+ cy.wait(1);
428
+ cy.all(() => cy.findAllByRole('option').invoke('get', 98), () => cy.findByRole('option', {
429
+ selected: true
430
+ }).invoke('get', 0)).then(_ref9 => {
431
+ let [secondLastOption, highlightedOption] = _ref9;
432
+ expect(highlightedOption).to.equal(secondLastOption);
433
+ });
434
+ cy.log('**And the second last option is visible**');
435
+ cy.findAllByRole('option').eq(98).should('be.visible');
436
+ cy.log('**And the last option is not visible**');
437
+ cy.findAllByRole('option').eq(99).should('not.be.visible');
438
+ });
439
+ });
@@ -0,0 +1,150 @@
1
+ describe('SimpleSingleSelect: Menu positioning', () => {
2
+ it('should open in the default position', () => {
3
+ // Given there is enough space below the anchor to fit the SingleSelect menu
4
+ cy.visitStory('Simple Single Select', 'Default position');
5
+
6
+ // When the SingleSelect is clicked
7
+ cy.findByRole('combobox').click();
8
+
9
+ // Then the top of the menu is aligned with the bottom of the input
10
+ cy.all(() => cy.findByRole('combobox'), () => cy.findByRole('listbox')).should(_ref => {
11
+ let [selectedValue, menu] = _ref;
12
+ expect(selectedValue.length).to.equal(1);
13
+ expect(menu.length).to.equal(1);
14
+ const $selectedValue = selectedValue[0];
15
+ const $menu = menu[0];
16
+ const selectedValueRect = $selectedValue.getBoundingClientRect();
17
+ const menuRect = $menu.getBoundingClientRect();
18
+
19
+ // The listbox is inside a container with a border,
20
+ // hence the increased delta
21
+ expect(menuRect.top).to.be.closeTo(selectedValueRect.bottom, 2);
22
+ });
23
+
24
+ // And the left of the SingleSelect is aligned with the left of the anchor
25
+ cy.all(() => cy.findByRole('combobox'), () => cy.findByRole('listbox')).should(_ref2 => {
26
+ let [selectedValue, menu] = _ref2;
27
+ expect(selectedValue.length).to.equal(1);
28
+ expect(menu.length).to.equal(1);
29
+ const $selectedValue = selectedValue[0];
30
+ const $menu = menu[0];
31
+ const selectedValueRect = $selectedValue.getBoundingClientRect();
32
+ const menuRect = $menu.getBoundingClientRect();
33
+
34
+ // The listbox is inside a container with a border,
35
+ // hence the increased delta
36
+ expect(selectedValueRect.left).to.be.closeTo(menuRect.left, 2);
37
+ });
38
+ });
39
+ it('should flipp the position when insufficient space below', () => {
40
+ // Given there is not enough space below the anchor to fit the SingleSelect menu
41
+ cy.visitStory('Simple Single Select', 'Flipped position');
42
+
43
+ // When the SingleSelect is clicked
44
+ cy.findByRole('combobox').click();
45
+
46
+ // Then the bottom of the menu is aligned with the top of the input
47
+ cy.all(() => cy.findByRole('combobox'),
48
+ // We need to get the parent as the menu itself
49
+ // extends withing the div container
50
+ () => cy.findByRole('listbox').invoke('parent')).should(_ref3 => {
51
+ let [selectedValue, menu] = _ref3;
52
+ expect(selectedValue.length).to.equal(1);
53
+ expect(menu.length).to.equal(1);
54
+ const $selectedValue = selectedValue[0];
55
+ const $menu = menu[0];
56
+ const selectedValueRect = $selectedValue.getBoundingClientRect();
57
+ const menuRect = $menu.getBoundingClientRect();
58
+ expect(selectedValueRect.top).to.be.closeTo(menuRect.bottom, 2);
59
+ });
60
+
61
+ // And the left of the SingleSelect is aligned with the left of the anchor
62
+ cy.all(() => cy.findByRole('combobox'), () => cy.findByRole('listbox')).should(_ref4 => {
63
+ let [selectedValue, menu] = _ref4;
64
+ expect(selectedValue.length).to.equal(1);
65
+ expect(menu.length).to.equal(1);
66
+ const $selectedValue = selectedValue[0];
67
+ const $menu = menu[0];
68
+ const selectedValueRect = $selectedValue.getBoundingClientRect();
69
+ const menuRect = $menu.getBoundingClientRect();
70
+
71
+ // The listbox is inside a container with a border,
72
+ // hence the increased delta
73
+ expect(selectedValueRect.left).to.be.closeTo(menuRect.left, 2);
74
+ });
75
+ });
76
+ it('should shift the menu into view when insufficient space below and above', () => {
77
+ // Given there is not enough space above or below the anchor to fit the SingleSelect menu
78
+ cy.visitStory('Simple Single Select', 'Shifted into view');
79
+
80
+ // When the SingleSelect is clicked
81
+ cy.findByRole('combobox').click();
82
+
83
+ // Then it is rendered on top of the SingleSelect
84
+ cy.all(() => cy.findByRole('combobox'), () => cy.findByRole('listbox')).should(_ref5 => {
85
+ let [selectedValue, menu] = _ref5;
86
+ expect(selectedValue.length).to.equal(1);
87
+ expect(menu.length).to.equal(1);
88
+ const $selectedValue = selectedValue[0];
89
+ const $menu = menu[0];
90
+ const selectedValueRect = $selectedValue.getBoundingClientRect();
91
+ const menuRect = $menu.getBoundingClientRect();
92
+ expect(selectedValueRect.top).to.be.greaterThan(menuRect.top);
93
+ expect(menuRect.bottom).to.be.greaterThan(selectedValueRect.bottom);
94
+ });
95
+
96
+ // And the left of the SingleSelect is aligned with the left of the anchor
97
+ cy.all(() => cy.findByRole('combobox'), () => cy.findByRole('listbox')).should(_ref6 => {
98
+ let [selectedValue, menu] = _ref6;
99
+ expect(selectedValue.length).to.equal(1);
100
+ expect(menu.length).to.equal(1);
101
+ const $selectedValue = selectedValue[0];
102
+ const $menu = menu[0];
103
+ const selectedValueRect = $selectedValue.getBoundingClientRect();
104
+ const menuRect = $menu.getBoundingClientRect();
105
+ expect(selectedValueRect.top).to.be.greaterThan(menuRect.top);
106
+ expect(menuRect.bottom).to.be.greaterThan(selectedValueRect.bottom);
107
+ });
108
+ });
109
+ it('should keep the menu in position while the window is scrolled', () => {
110
+ // Given there is enough space below the anchor to fit the SingleSelect menu
111
+ cy.visitStory('Simple Single Select', 'Default position');
112
+
113
+ // When the SingleSelect is clicked
114
+ cy.findByRole('combobox').click();
115
+
116
+ // And the window is scrolled down
117
+ cy.get('body').then($body => $body.height('5000px')); // Ensure the body can scroll first
118
+ cy.scrollTo(0, 800);
119
+
120
+ // Then the top of the menu is aligned with the bottom of the input
121
+ cy.all(() => cy.findByRole('combobox'), () => cy.findByRole('listbox')).should(_ref7 => {
122
+ let [selectedValue, menu] = _ref7;
123
+ expect(selectedValue.length).to.equal(1);
124
+ expect(menu.length).to.equal(1);
125
+ const $selectedValue = selectedValue[0];
126
+ const $menu = menu[0];
127
+ const selectedValueRect = $selectedValue.getBoundingClientRect();
128
+ const menuRect = $menu.getBoundingClientRect();
129
+
130
+ // The listbox is inside a container with a border,
131
+ // hence the increased delta
132
+ expect(menuRect.top).to.be.closeTo(selectedValueRect.bottom, 2);
133
+ });
134
+
135
+ // And the left of the SingleSelect is aligned with the left of the anchor
136
+ cy.all(() => cy.findByRole('combobox'), () => cy.findByRole('listbox')).should(_ref8 => {
137
+ let [selectedValue, menu] = _ref8;
138
+ expect(selectedValue.length).to.equal(1);
139
+ expect(menu.length).to.equal(1);
140
+ const $selectedValue = selectedValue[0];
141
+ const $menu = menu[0];
142
+ const selectedValueRect = $selectedValue.getBoundingClientRect();
143
+ const menuRect = $menu.getBoundingClientRect();
144
+
145
+ // The listbox is inside a container with a border,
146
+ // hence the increased delta
147
+ expect(selectedValueRect.left).to.be.closeTo(menuRect.left, 2);
148
+ });
149
+ });
150
+ });
@@ -0,0 +1,2 @@
1
+ export { optionProp } from './shared-prop-types.js';
2
+ export { SimpleSingleSelect } from './simple-single-select.js';
@@ -0,0 +1,16 @@
1
+ // If this many pixels are invisible, we still consider the option visible
2
+ // The reason we're doing this is because of JS' number issues,
3
+ // e.g:
4
+ // optionOffsetBottom is 345.6000061035156
5
+ // containerOffsetBottom is 345.5999984741211
6
+ // -> Without tolerance, option is invisible
7
+ const TOLERANCE = 2;
8
+ export function isOptionHidden(option, scrollContainer) {
9
+ const optionOffsetTop = option.getBoundingClientRect().top;
10
+ const optionHeight = option.offsetHeight;
11
+ const optionOffsetBottom = optionOffsetTop + optionHeight;
12
+ const containerOffsetTop = scrollContainer.getBoundingClientRect().top;
13
+ const containerHeight = scrollContainer.offsetHeight;
14
+ const containerOffsetBottom = containerOffsetTop + containerHeight;
15
+ return optionOffsetBottom > containerOffsetBottom + TOLERANCE || optionOffsetTop < containerOffsetTop - TOLERANCE;
16
+ }
@@ -0,0 +1,19 @@
1
+ import _JSXStyle from "styled-jsx/style";
2
+ import { colors, spacers, theme } from '@dhis2/ui-constants';
3
+ import PropTypes from 'prop-types';
4
+ import React from 'react';
5
+ const Empty = _ref => {
6
+ let {
7
+ children
8
+ } = _ref;
9
+ return /*#__PURE__*/React.createElement("div", {
10
+ className: _JSXStyle.dynamic([["3788156646", [colors.grey700, theme.fonts, spacers.dp8, spacers.dp24]]])
11
+ }, children, /*#__PURE__*/React.createElement(_JSXStyle, {
12
+ id: "3788156646",
13
+ dynamic: [colors.grey700, theme.fonts, spacers.dp8, spacers.dp24]
14
+ }, [`div.__jsx-style-dynamic-selector{color:${colors.grey700};font-family:${theme.fonts};font-size:13px;line-height:16px;padding-block:${spacers.dp8};padding-inline:${spacers.dp24};text-align:center;}`]));
15
+ };
16
+ Empty.propTypes = {
17
+ children: PropTypes.string.isRequired
18
+ };
19
+ export { Empty };