@wordpress/components 25.13.0 → 25.14.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.
Files changed (224) hide show
  1. package/CHANGELOG.md +38 -1
  2. package/build/border-control/border-control-dropdown/component.js +4 -2
  3. package/build/border-control/border-control-dropdown/component.js.map +1 -1
  4. package/build/border-control/border-control-dropdown/hook.js +3 -2
  5. package/build/border-control/border-control-dropdown/hook.js.map +1 -1
  6. package/build/border-control/styles.js +17 -17
  7. package/build/border-control/styles.js.map +1 -1
  8. package/build/checkbox-control/index.js +1 -1
  9. package/build/checkbox-control/index.js.map +1 -1
  10. package/build/checkbox-control/types.js.map +1 -1
  11. package/build/date-time/time/timezone.js +11 -2
  12. package/build/date-time/time/timezone.js.map +1 -1
  13. package/build/dimension-control/index.js +2 -0
  14. package/build/dimension-control/index.js.map +1 -1
  15. package/build/dimension-control/types.js.map +1 -1
  16. package/build/dropdown-menu-v2-ariakit/styles.js +14 -14
  17. package/build/dropdown-menu-v2-ariakit/styles.js.map +1 -1
  18. package/build/focal-point-picker/controls.js +5 -1
  19. package/build/focal-point-picker/controls.js.map +1 -1
  20. package/build/focal-point-picker/index.js +2 -0
  21. package/build/focal-point-picker/index.js.map +1 -1
  22. package/build/focal-point-picker/styles/focal-point-picker-style.js +15 -15
  23. package/build/focal-point-picker/styles/focal-point-picker-style.js.map +1 -1
  24. package/build/focal-point-picker/types.js.map +1 -1
  25. package/build/font-size-picker/font-size-picker-select.js +2 -0
  26. package/build/font-size-picker/font-size-picker-select.js.map +1 -1
  27. package/build/font-size-picker/font-size-picker-toggle-group.js +2 -0
  28. package/build/font-size-picker/font-size-picker-toggle-group.js.map +1 -1
  29. package/build/font-size-picker/index.js +6 -1
  30. package/build/font-size-picker/index.js.map +1 -1
  31. package/build/font-size-picker/types.js.map +1 -1
  32. package/build/index.native.js +0 -16
  33. package/build/index.native.js.map +1 -1
  34. package/build/mobile/global-styles-context/utils.native.js +13 -0
  35. package/build/mobile/global-styles-context/utils.native.js.map +1 -1
  36. package/build/palette-edit/index.js +21 -1
  37. package/build/palette-edit/index.js.map +1 -1
  38. package/build/query-controls/author-select.js +3 -1
  39. package/build/query-controls/author-select.js.map +1 -1
  40. package/build/query-controls/category-select.js +3 -1
  41. package/build/query-controls/category-select.js.map +1 -1
  42. package/build/query-controls/index.js +6 -1
  43. package/build/query-controls/index.js.map +1 -1
  44. package/build/query-controls/types.js.map +1 -1
  45. package/build/tabs/index.js +18 -1
  46. package/build/tabs/index.js.map +1 -1
  47. package/build/tabs/tab.js +2 -2
  48. package/build/tabs/tab.js.map +1 -1
  49. package/build/tabs/tabpanel.js +3 -2
  50. package/build/tabs/tabpanel.js.map +1 -1
  51. package/build/tabs/types.js.map +1 -1
  52. package/build/toggle-group-control/toggle-group-control/utils.js +17 -17
  53. package/build/toggle-group-control/toggle-group-control/utils.js.map +1 -1
  54. package/build/tools-panel/tools-panel-item/hook.js +11 -11
  55. package/build/tools-panel/tools-panel-item/hook.js.map +1 -1
  56. package/build-module/border-control/border-control-dropdown/component.js +4 -2
  57. package/build-module/border-control/border-control-dropdown/component.js.map +1 -1
  58. package/build-module/border-control/border-control-dropdown/hook.js +3 -2
  59. package/build-module/border-control/border-control-dropdown/hook.js.map +1 -1
  60. package/build-module/border-control/styles.js +17 -17
  61. package/build-module/border-control/styles.js.map +1 -1
  62. package/build-module/checkbox-control/index.js +1 -1
  63. package/build-module/checkbox-control/index.js.map +1 -1
  64. package/build-module/checkbox-control/types.js.map +1 -1
  65. package/build-module/date-time/time/timezone.js +11 -2
  66. package/build-module/date-time/time/timezone.js.map +1 -1
  67. package/build-module/dimension-control/index.js +2 -0
  68. package/build-module/dimension-control/index.js.map +1 -1
  69. package/build-module/dimension-control/types.js.map +1 -1
  70. package/build-module/dropdown-menu-v2-ariakit/styles.js +14 -14
  71. package/build-module/dropdown-menu-v2-ariakit/styles.js.map +1 -1
  72. package/build-module/focal-point-picker/controls.js +5 -1
  73. package/build-module/focal-point-picker/controls.js.map +1 -1
  74. package/build-module/focal-point-picker/index.js +2 -0
  75. package/build-module/focal-point-picker/index.js.map +1 -1
  76. package/build-module/focal-point-picker/styles/focal-point-picker-style.js +15 -15
  77. package/build-module/focal-point-picker/styles/focal-point-picker-style.js.map +1 -1
  78. package/build-module/focal-point-picker/types.js.map +1 -1
  79. package/build-module/font-size-picker/font-size-picker-select.js +2 -0
  80. package/build-module/font-size-picker/font-size-picker-select.js.map +1 -1
  81. package/build-module/font-size-picker/font-size-picker-toggle-group.js +2 -0
  82. package/build-module/font-size-picker/font-size-picker-toggle-group.js.map +1 -1
  83. package/build-module/font-size-picker/index.js +6 -1
  84. package/build-module/font-size-picker/index.js.map +1 -1
  85. package/build-module/font-size-picker/types.js.map +1 -1
  86. package/build-module/index.native.js +0 -2
  87. package/build-module/index.native.js.map +1 -1
  88. package/build-module/mobile/global-styles-context/utils.native.js +13 -0
  89. package/build-module/mobile/global-styles-context/utils.native.js.map +1 -1
  90. package/build-module/palette-edit/index.js +20 -3
  91. package/build-module/palette-edit/index.js.map +1 -1
  92. package/build-module/query-controls/author-select.js +3 -1
  93. package/build-module/query-controls/author-select.js.map +1 -1
  94. package/build-module/query-controls/category-select.js +3 -1
  95. package/build-module/query-controls/category-select.js.map +1 -1
  96. package/build-module/query-controls/index.js +6 -1
  97. package/build-module/query-controls/index.js.map +1 -1
  98. package/build-module/query-controls/types.js.map +1 -1
  99. package/build-module/tabs/index.js +18 -1
  100. package/build-module/tabs/index.js.map +1 -1
  101. package/build-module/tabs/tab.js +2 -2
  102. package/build-module/tabs/tab.js.map +1 -1
  103. package/build-module/tabs/tabpanel.js +3 -2
  104. package/build-module/tabs/tabpanel.js.map +1 -1
  105. package/build-module/tabs/types.js.map +1 -1
  106. package/build-module/toggle-group-control/toggle-group-control/utils.js +17 -17
  107. package/build-module/toggle-group-control/toggle-group-control/utils.js.map +1 -1
  108. package/build-module/tools-panel/tools-panel-item/hook.js +11 -11
  109. package/build-module/tools-panel/tools-panel-item/hook.js.map +1 -1
  110. package/build-style/style-rtl.css +1 -1
  111. package/build-style/style.css +1 -1
  112. package/build-types/border-control/border-control-dropdown/component.d.ts.map +1 -1
  113. package/build-types/border-control/border-control-dropdown/hook.d.ts +1 -0
  114. package/build-types/border-control/border-control-dropdown/hook.d.ts.map +1 -1
  115. package/build-types/border-control/styles.d.ts +1 -1
  116. package/build-types/border-control/styles.d.ts.map +1 -1
  117. package/build-types/checkbox-control/index.d.ts.map +1 -1
  118. package/build-types/checkbox-control/types.d.ts +3 -2
  119. package/build-types/checkbox-control/types.d.ts.map +1 -1
  120. package/build-types/date-time/time/timezone.d.ts.map +1 -1
  121. package/build-types/dimension-control/index.d.ts.map +1 -1
  122. package/build-types/dimension-control/types.d.ts +6 -0
  123. package/build-types/dimension-control/types.d.ts.map +1 -1
  124. package/build-types/dropdown-menu-v2-ariakit/styles.d.ts.map +1 -1
  125. package/build-types/focal-point-picker/controls.d.ts +1 -1
  126. package/build-types/focal-point-picker/controls.d.ts.map +1 -1
  127. package/build-types/focal-point-picker/index.d.ts +1 -1
  128. package/build-types/focal-point-picker/index.d.ts.map +1 -1
  129. package/build-types/focal-point-picker/stories/index.story.d.ts +8 -4
  130. package/build-types/focal-point-picker/stories/index.story.d.ts.map +1 -1
  131. package/build-types/focal-point-picker/types.d.ts +7 -0
  132. package/build-types/focal-point-picker/types.d.ts.map +1 -1
  133. package/build-types/font-size-picker/font-size-picker-select.d.ts.map +1 -1
  134. package/build-types/font-size-picker/font-size-picker-toggle-group.d.ts.map +1 -1
  135. package/build-types/font-size-picker/index.d.ts.map +1 -1
  136. package/build-types/font-size-picker/types.d.ts +8 -1
  137. package/build-types/font-size-picker/types.d.ts.map +1 -1
  138. package/build-types/palette-edit/index.d.ts +6 -1
  139. package/build-types/palette-edit/index.d.ts.map +1 -1
  140. package/build-types/query-controls/author-select.d.ts +1 -1
  141. package/build-types/query-controls/author-select.d.ts.map +1 -1
  142. package/build-types/query-controls/category-select.d.ts +1 -1
  143. package/build-types/query-controls/category-select.d.ts.map +1 -1
  144. package/build-types/query-controls/index.d.ts +1 -1
  145. package/build-types/query-controls/index.d.ts.map +1 -1
  146. package/build-types/query-controls/types.d.ts +9 -0
  147. package/build-types/query-controls/types.d.ts.map +1 -1
  148. package/build-types/tabs/index.d.ts +2 -2
  149. package/build-types/tabs/index.d.ts.map +1 -1
  150. package/build-types/tabs/tab.d.ts +2 -1
  151. package/build-types/tabs/tab.d.ts.map +1 -1
  152. package/build-types/tabs/tabpanel.d.ts +2 -1
  153. package/build-types/tabs/tabpanel.d.ts.map +1 -1
  154. package/build-types/tabs/types.d.ts +8 -3
  155. package/build-types/tabs/types.d.ts.map +1 -1
  156. package/build-types/toggle-group-control/toggle-group-control/utils.d.ts.map +1 -1
  157. package/build-types/tools-panel/tools-panel-item/hook.d.ts.map +1 -1
  158. package/package.json +19 -19
  159. package/src/border-control/border-control-dropdown/component.tsx +3 -1
  160. package/src/border-control/border-control-dropdown/hook.ts +3 -2
  161. package/src/border-control/styles.ts +2 -9
  162. package/src/checkbox-control/README.md +2 -1
  163. package/src/checkbox-control/index.tsx +8 -6
  164. package/src/checkbox-control/test/__snapshots__/index.tsx.snap +3 -8
  165. package/src/checkbox-control/test/index.tsx +7 -0
  166. package/src/checkbox-control/types.ts +3 -2
  167. package/src/custom-select-control/test/index.js +367 -35
  168. package/src/date-time/time/timezone.tsx +15 -3
  169. package/src/dimension-control/index.tsx +2 -0
  170. package/src/dimension-control/test/__snapshots__/index.test.js.snap +2 -2
  171. package/src/dimension-control/types.ts +6 -0
  172. package/src/dropdown-menu-v2-ariakit/styles.ts +12 -0
  173. package/src/focal-point-picker/controls.tsx +4 -0
  174. package/src/focal-point-picker/index.tsx +2 -0
  175. package/src/focal-point-picker/styles/focal-point-picker-style.ts +1 -1
  176. package/src/focal-point-picker/types.ts +7 -0
  177. package/src/font-size-picker/font-size-picker-select.tsx +2 -0
  178. package/src/font-size-picker/font-size-picker-toggle-group.tsx +9 -1
  179. package/src/font-size-picker/index.tsx +11 -3
  180. package/src/font-size-picker/types.ts +8 -1
  181. package/src/form-toggle/style.scss +4 -2
  182. package/src/index.native.js +0 -2
  183. package/src/mobile/global-styles-context/test/utils.native.js +22 -0
  184. package/src/mobile/global-styles-context/utils.native.js +14 -0
  185. package/src/mobile/link-settings/style.native.scss +0 -17
  186. package/src/palette-edit/index.tsx +22 -8
  187. package/src/palette-edit/style.scss +2 -2
  188. package/src/palette-edit/test/index.tsx +75 -1
  189. package/src/query-controls/author-select.tsx +2 -0
  190. package/src/query-controls/category-select.tsx +2 -0
  191. package/src/query-controls/index.tsx +6 -1
  192. package/src/query-controls/types.ts +9 -0
  193. package/src/search-control/README.md +2 -0
  194. package/src/spinner/README.md +2 -0
  195. package/src/tabs/README.md +4 -4
  196. package/src/tabs/index.tsx +22 -1
  197. package/src/tabs/stories/index.story.tsx +48 -48
  198. package/src/tabs/tab.tsx +3 -3
  199. package/src/tabs/tabpanel.tsx +7 -3
  200. package/src/tabs/test/index.tsx +180 -106
  201. package/src/tabs/types.ts +8 -3
  202. package/src/toggle-group-control/test/index.tsx +54 -1
  203. package/src/toggle-group-control/toggle-group-control/utils.ts +15 -20
  204. package/src/tools-panel/tools-panel-item/hook.ts +10 -21
  205. package/tsconfig.tsbuildinfo +1 -1
  206. package/build/mobile/inserter-button/index.native.js +0 -98
  207. package/build/mobile/inserter-button/index.native.js.map +0 -1
  208. package/build/mobile/inserter-button/sparkles.js +0 -23
  209. package/build/mobile/inserter-button/sparkles.js.map +0 -1
  210. package/build/mobile/link-settings/image-link-destinations-screen.native.js +0 -119
  211. package/build/mobile/link-settings/image-link-destinations-screen.native.js.map +0 -1
  212. package/build-module/mobile/inserter-button/index.native.js +0 -89
  213. package/build-module/mobile/inserter-button/index.native.js.map +0 -1
  214. package/build-module/mobile/inserter-button/sparkles.js +0 -15
  215. package/build-module/mobile/inserter-button/sparkles.js.map +0 -1
  216. package/build-module/mobile/link-settings/image-link-destinations-screen.native.js +0 -110
  217. package/build-module/mobile/link-settings/image-link-destinations-screen.native.js.map +0 -1
  218. package/build-types/mobile/inserter-button/sparkles.d.ts +0 -3
  219. package/build-types/mobile/inserter-button/sparkles.d.ts.map +0 -1
  220. package/src/mobile/inserter-button/README.md +0 -62
  221. package/src/mobile/inserter-button/index.native.js +0 -116
  222. package/src/mobile/inserter-button/sparkles.js +0 -15
  223. package/src/mobile/inserter-button/style.native.scss +0 -72
  224. package/src/mobile/link-settings/image-link-destinations-screen.native.js +0 -152
@@ -4,54 +4,205 @@
4
4
  import { render, screen } from '@testing-library/react';
5
5
  import userEvent from '@testing-library/user-event';
6
6
 
7
+ /**
8
+ * WordPress dependencies
9
+ */
10
+ import { useState } from '@wordpress/element';
11
+
7
12
  /**
8
13
  * Internal dependencies
9
14
  */
10
15
  import CustomSelectControl from '..';
11
16
 
12
- describe( 'CustomSelectControl', () => {
13
- it( 'Captures the keypress event and does not let it propagate', async () => {
14
- const user = userEvent.setup();
15
- const onKeyDown = jest.fn();
16
- const options = [
17
- {
18
- key: 'one',
19
- name: 'Option one',
20
- },
21
- {
22
- key: 'two',
23
- name: 'Option two',
24
- },
25
- {
26
- key: 'three',
27
- name: 'Option three',
17
+ const customClass = 'amber-skies';
18
+
19
+ const props = {
20
+ label: 'label!',
21
+ options: [
22
+ {
23
+ key: 'flower1',
24
+ name: 'violets',
25
+ },
26
+ {
27
+ key: 'flower2',
28
+ name: 'crimson clover',
29
+ className: customClass,
30
+ },
31
+ {
32
+ key: 'flower3',
33
+ name: 'poppy',
34
+ },
35
+ {
36
+ key: 'color1',
37
+ name: 'amber',
38
+ className: customClass,
39
+ },
40
+ {
41
+ key: 'color2',
42
+ name: 'aquamarine',
43
+ style: {
44
+ backgroundColor: 'rgb(127, 255, 212)',
45
+ rotate: '13deg',
28
46
  },
29
- ];
47
+ },
48
+ ],
49
+ __nextUnconstrainedWidth: true,
50
+ };
30
51
 
31
- render(
32
- <div
33
- // This role="none" is required to prevent an eslint warning about accessibility.
34
- role="none"
35
- onKeyDown={ onKeyDown }
36
- >
37
- <CustomSelectControl
38
- options={ options }
39
- __nextUnconstrainedWidth
40
- />
41
- </div>
52
+ const ControlledCustomSelectControl = ( { options } ) => {
53
+ const [ value, setValue ] = useState( options[ 0 ] );
54
+ return (
55
+ <CustomSelectControl
56
+ { ...props }
57
+ onChange={ ( { selectedItem } ) => setValue( selectedItem ) }
58
+ value={ options.find( ( option ) => option.key === value.key ) }
59
+ />
60
+ );
61
+ };
62
+
63
+ describe.each( [
64
+ [ 'uncontrolled', CustomSelectControl ],
65
+ [ 'controlled', ControlledCustomSelectControl ],
66
+ ] )( 'CustomSelectControl %s', ( ...modeAndComponent ) => {
67
+ const [ , Component ] = modeAndComponent;
68
+
69
+ it( 'Should replace the initial selection when a new item is selected', async () => {
70
+ const user = userEvent.setup();
71
+
72
+ render( <Component { ...props } /> );
73
+
74
+ const currentSelectedItem = screen.getByRole( 'button', {
75
+ expanded: false,
76
+ } );
77
+
78
+ await user.click( currentSelectedItem );
79
+
80
+ await user.click(
81
+ screen.getByRole( 'option', {
82
+ name: 'crimson clover',
83
+ } )
84
+ );
85
+
86
+ expect( currentSelectedItem ).toHaveTextContent( 'crimson clover' );
87
+
88
+ await user.click( currentSelectedItem );
89
+
90
+ await user.click(
91
+ screen.getByRole( 'option', {
92
+ name: 'poppy',
93
+ } )
94
+ );
95
+
96
+ expect( currentSelectedItem ).toHaveTextContent( 'poppy' );
97
+ } );
98
+
99
+ it( 'Should keep current selection if dropdown is closed without changing selection', async () => {
100
+ const user = userEvent.setup();
101
+
102
+ render( <CustomSelectControl { ...props } /> );
103
+
104
+ const currentSelectedItem = screen.getByRole( 'button', {
105
+ expanded: false,
106
+ } );
107
+
108
+ await user.tab();
109
+ await user.keyboard( '{enter}' );
110
+ expect(
111
+ screen.getByRole( 'listbox', {
112
+ name: 'label!',
113
+ } )
114
+ ).toBeVisible();
115
+
116
+ await user.keyboard( '{escape}' );
117
+ expect(
118
+ screen.queryByRole( 'listbox', {
119
+ name: 'label!',
120
+ } )
121
+ ).not.toBeInTheDocument();
122
+
123
+ expect( currentSelectedItem ).toHaveTextContent(
124
+ props.options[ 0 ].name
125
+ );
126
+ } );
127
+
128
+ it( 'Should apply class only to options that have a className defined', async () => {
129
+ const user = userEvent.setup();
130
+
131
+ render( <CustomSelectControl { ...props } /> );
132
+
133
+ await user.click(
134
+ screen.getByRole( 'button', {
135
+ expanded: false,
136
+ } )
137
+ );
138
+
139
+ // return an array of items _with_ a className added
140
+ const itemsWithClass = props.options.filter(
141
+ ( option ) => option.className !== undefined
142
+ );
143
+
144
+ // assert against filtered array
145
+ itemsWithClass.map( ( { name } ) =>
146
+ expect( screen.getByRole( 'option', { name } ) ).toHaveClass(
147
+ customClass
148
+ )
149
+ );
150
+
151
+ // return an array of items _without_ a className added
152
+ const itemsWithoutClass = props.options.filter(
153
+ ( option ) => option.className === undefined
154
+ );
155
+
156
+ // assert against filtered array
157
+ itemsWithoutClass.map( ( { name } ) =>
158
+ expect( screen.getByRole( 'option', { name } ) ).not.toHaveClass(
159
+ customClass
160
+ )
161
+ );
162
+ } );
163
+
164
+ it( 'Should apply styles only to options that have styles defined', async () => {
165
+ const user = userEvent.setup();
166
+ const customStyles =
167
+ 'background-color: rgb(127, 255, 212); rotate: 13deg;';
168
+
169
+ render( <CustomSelectControl { ...props } /> );
170
+
171
+ await user.click(
172
+ screen.getByRole( 'button', {
173
+ expanded: false,
174
+ } )
175
+ );
176
+
177
+ // return an array of items _with_ styles added
178
+ const styledItems = props.options.filter(
179
+ ( option ) => option.style !== undefined
42
180
  );
43
- const toggleButton = screen.getByRole( 'button' );
44
- await user.click( toggleButton );
45
181
 
46
- const customSelect = screen.getByRole( 'listbox' );
47
- await user.type( customSelect, '{enter}' );
182
+ // assert against filtered array
183
+ styledItems.map( ( { name } ) =>
184
+ expect( screen.getByRole( 'option', { name } ) ).toHaveStyle(
185
+ customStyles
186
+ )
187
+ );
188
+
189
+ // return an array of items _without_ styles added
190
+ const unstyledItems = props.options.filter(
191
+ ( option ) => option.style === undefined
192
+ );
48
193
 
49
- expect( onKeyDown ).toHaveBeenCalledTimes( 0 );
194
+ // assert against filtered array
195
+ unstyledItems.map( ( { name } ) =>
196
+ expect( screen.getByRole( 'option', { name } ) ).not.toHaveStyle(
197
+ customStyles
198
+ )
199
+ );
50
200
  } );
51
201
 
52
202
  it( 'does not show selected hint by default', () => {
53
203
  render(
54
204
  <CustomSelectControl
205
+ { ...props }
55
206
  label="Custom select"
56
207
  options={ [
57
208
  {
@@ -60,7 +211,6 @@ describe( 'CustomSelectControl', () => {
60
211
  __experimentalHint: 'Hint',
61
212
  },
62
213
  ] }
63
- __nextUnconstrainedWidth
64
214
  />
65
215
  );
66
216
  expect(
@@ -71,6 +221,7 @@ describe( 'CustomSelectControl', () => {
71
221
  it( 'shows selected hint when __experimentalShowSelectedHint is set', () => {
72
222
  render(
73
223
  <CustomSelectControl
224
+ { ...props }
74
225
  label="Custom select"
75
226
  options={ [
76
227
  {
@@ -80,11 +231,192 @@ describe( 'CustomSelectControl', () => {
80
231
  },
81
232
  ] }
82
233
  __experimentalShowSelectedHint
83
- __nextUnconstrainedWidth
84
234
  />
85
235
  );
86
236
  expect(
87
237
  screen.getByRole( 'button', { name: 'Custom select' } )
88
238
  ).toHaveTextContent( 'Hint' );
89
239
  } );
240
+
241
+ describe( 'Keyboard behavior and accessibility', () => {
242
+ it( 'Captures the keypress event and does not let it propagate', async () => {
243
+ const user = userEvent.setup();
244
+ const onKeyDown = jest.fn();
245
+
246
+ render(
247
+ <div
248
+ // This role="none" is required to prevent an eslint warning about accessibility.
249
+ role="none"
250
+ onKeyDown={ onKeyDown }
251
+ >
252
+ <CustomSelectControl { ...props } />
253
+ </div>
254
+ );
255
+ const currentSelectedItem = screen.getByRole( 'button', {
256
+ expanded: false,
257
+ } );
258
+ await user.click( currentSelectedItem );
259
+
260
+ const customSelect = screen.getByRole( 'listbox', {
261
+ name: 'label!',
262
+ } );
263
+ await user.type( customSelect, '{enter}' );
264
+
265
+ expect( onKeyDown ).toHaveBeenCalledTimes( 0 );
266
+ } );
267
+
268
+ it( 'Should be able to change selection using keyboard', async () => {
269
+ const user = userEvent.setup();
270
+
271
+ render( <CustomSelectControl { ...props } /> );
272
+
273
+ const currentSelectedItem = screen.getByRole( 'button', {
274
+ expanded: false,
275
+ } );
276
+
277
+ await user.tab();
278
+ expect( currentSelectedItem ).toHaveFocus();
279
+
280
+ await user.keyboard( '{enter}' );
281
+ expect(
282
+ screen.getByRole( 'listbox', {
283
+ name: 'label!',
284
+ } )
285
+ ).toHaveFocus();
286
+
287
+ await user.keyboard( '{arrowdown}' );
288
+ await user.keyboard( '{enter}' );
289
+
290
+ expect( currentSelectedItem ).toHaveTextContent( 'crimson clover' );
291
+ } );
292
+
293
+ it( 'Should be able to type characters to select matching options', async () => {
294
+ const user = userEvent.setup();
295
+
296
+ render( <CustomSelectControl { ...props } /> );
297
+
298
+ const currentSelectedItem = screen.getByRole( 'button', {
299
+ expanded: false,
300
+ } );
301
+
302
+ await user.tab();
303
+ await user.keyboard( '{enter}' );
304
+ expect(
305
+ screen.getByRole( 'listbox', {
306
+ name: 'label!',
307
+ } )
308
+ ).toHaveFocus();
309
+
310
+ await user.keyboard( '{a}' );
311
+ await user.keyboard( '{enter}' );
312
+ expect( currentSelectedItem ).toHaveTextContent( 'amber' );
313
+ } );
314
+
315
+ it( 'Can change selection with a focused input and closed dropdown if typed characters match an option', async () => {
316
+ const user = userEvent.setup();
317
+
318
+ render( <CustomSelectControl { ...props } /> );
319
+
320
+ const currentSelectedItem = screen.getByRole( 'button', {
321
+ expanded: false,
322
+ } );
323
+
324
+ await user.tab();
325
+ expect( currentSelectedItem ).toHaveFocus();
326
+
327
+ await user.keyboard( '{a}' );
328
+ await user.keyboard( '{q}' );
329
+
330
+ expect(
331
+ screen.queryByRole( 'listbox', {
332
+ name: 'label!',
333
+ hidden: true,
334
+ } )
335
+ ).not.toBeInTheDocument();
336
+
337
+ await user.keyboard( '{enter}' );
338
+ expect( currentSelectedItem ).toHaveTextContent( 'aquamarine' );
339
+ } );
340
+
341
+ it( 'Should have correct aria-selected value for selections', async () => {
342
+ const user = userEvent.setup();
343
+
344
+ render( <CustomSelectControl { ...props } /> );
345
+
346
+ const currentSelectedItem = screen.getByRole( 'button', {
347
+ expanded: false,
348
+ } );
349
+
350
+ await user.click( currentSelectedItem );
351
+
352
+ // get all items except for first option
353
+ const unselectedItems = props.options.filter(
354
+ ( { name } ) => name !== props.options[ 0 ].name
355
+ );
356
+
357
+ // assert that all other items have aria-selected="false"
358
+ unselectedItems.map( ( { name } ) =>
359
+ expect(
360
+ screen.getByRole( 'option', { name, selected: false } )
361
+ ).toBeVisible()
362
+ );
363
+
364
+ // assert that first item has aria-selected="true"
365
+ expect(
366
+ screen.getByRole( 'option', {
367
+ name: props.options[ 0 ].name,
368
+ selected: true,
369
+ } )
370
+ ).toBeVisible();
371
+
372
+ // change the current selection
373
+ await user.click( screen.getByRole( 'option', { name: 'poppy' } ) );
374
+
375
+ // click button to mount listbox with options again
376
+ await user.click( currentSelectedItem );
377
+
378
+ // check that first item is has aria-selected="false" after new selection
379
+ expect(
380
+ screen.getByRole( 'option', {
381
+ name: props.options[ 0 ].name,
382
+ selected: false,
383
+ } )
384
+ ).toBeVisible();
385
+
386
+ // check that new selected item now has aria-selected="true"
387
+ expect(
388
+ screen.getByRole( 'option', {
389
+ name: 'poppy',
390
+ selected: true,
391
+ } )
392
+ ).toBeVisible();
393
+ } );
394
+
395
+ it( 'Should call custom event handlers', async () => {
396
+ const user = userEvent.setup();
397
+ const onFocusMock = jest.fn();
398
+ const onBlurMock = jest.fn();
399
+
400
+ render(
401
+ <CustomSelectControl
402
+ { ...props }
403
+ onFocus={ onFocusMock }
404
+ onBlur={ onBlurMock }
405
+ />
406
+ );
407
+
408
+ const currentSelectedItem = screen.getByRole( 'button', {
409
+ expanded: false,
410
+ } );
411
+
412
+ await user.tab();
413
+
414
+ expect( currentSelectedItem ).toHaveFocus();
415
+ expect( onFocusMock ).toHaveBeenCalledTimes( 1 );
416
+
417
+ await user.tab();
418
+ expect( currentSelectedItem ).not.toHaveFocus();
419
+ expect( onBlurMock ).toHaveBeenCalledTimes( 1 );
420
+ } );
421
+ } );
90
422
  } );
@@ -32,12 +32,24 @@ const TimeZone = () => {
32
32
  ? timezone.abbr
33
33
  : `UTC${ offsetSymbol }${ timezone.offset }`;
34
34
 
35
+ // Replace underscore with space in strings like `America/Costa_Rica`.
36
+ const prettyTimezoneString = timezone.string.replace( '_', ' ' );
37
+
35
38
  const timezoneDetail =
36
39
  'UTC' === timezone.string
37
40
  ? __( 'Coordinated Universal Time' )
38
- : `(${ zoneAbbr }) ${ timezone.string.replace( '_', ' ' ) }`;
39
-
40
- return (
41
+ : `(${ zoneAbbr }) ${ prettyTimezoneString }`;
42
+
43
+ // When the prettyTimezoneString is empty, there is no additional timezone
44
+ // detail information to show in a Tooltip.
45
+ const hasNoAdditionalTimezoneDetail =
46
+ prettyTimezoneString.trim().length === 0;
47
+
48
+ return hasNoAdditionalTimezoneDetail ? (
49
+ <StyledComponent className="components-datetime__timezone">
50
+ { zoneAbbr }
51
+ </StyledComponent>
52
+ ) : (
41
53
  <Tooltip placement="top" text={ timezoneDetail }>
42
54
  <StyledComponent className="components-datetime__timezone">
43
55
  { zoneAbbr }
@@ -42,6 +42,7 @@ import type { SelectControlSingleSelectionProps } from '../select-control/types'
42
42
  */
43
43
  export function DimensionControl( props: DimensionControlProps ) {
44
44
  const {
45
+ __next40pxDefaultSize = false,
45
46
  label,
46
47
  value,
47
48
  sizes = sizesTable,
@@ -85,6 +86,7 @@ export function DimensionControl( props: DimensionControlProps ) {
85
86
 
86
87
  return (
87
88
  <SelectControl
89
+ __next40pxDefaultSize={ __next40pxDefaultSize }
88
90
  className={ classnames(
89
91
  className,
90
92
  'block-editor-dimension-control'
@@ -764,7 +764,7 @@ exports[`DimensionControl rendering renders with icon and custom icon label 1`]
764
764
  xmlns="http://www.w3.org/2000/svg"
765
765
  >
766
766
  <path
767
- d="M18 11.2h-5.2V6h-1.6v5.2H6v1.6h5.2V18h1.6v-5.2H18z"
767
+ d="M11 12.5V17.5H12.5V12.5H17.5V11H12.5V6H11V11H6V12.5H11Z"
768
768
  />
769
769
  </svg>
770
770
  Margin
@@ -1057,7 +1057,7 @@ exports[`DimensionControl rendering renders with icon and default icon label 1`]
1057
1057
  xmlns="http://www.w3.org/2000/svg"
1058
1058
  >
1059
1059
  <path
1060
- d="M18 11.2h-5.2V6h-1.6v5.2H6v1.6h5.2V18h1.6v-5.2H18z"
1060
+ d="M11 12.5V17.5H12.5V12.5H17.5V11H12.5V6H11V11H6V12.5H11Z"
1061
1061
  />
1062
1062
  </svg>
1063
1063
  Margin
@@ -45,4 +45,10 @@ export type DimensionControlProps = {
45
45
  * @default ''
46
46
  */
47
47
  className?: string;
48
+ /**
49
+ * Start opting into the larger default height that will become the default size in a future version.
50
+ *
51
+ * @default false
52
+ */
53
+ __next40pxDefaultSize?: boolean;
48
54
  };
@@ -212,6 +212,18 @@ export const ItemPrefixWrapper = styled.span`
212
212
  /* Always occupy the first column, even when auto-collapsing */
213
213
  grid-column: 1;
214
214
 
215
+ /*
216
+ * Even when the item is not checked, occupy the same screen space to avoid
217
+ * the space collapside when no items are checked.
218
+ */
219
+ ${ DropdownMenuCheckboxItem } > &,
220
+ ${ DropdownMenuRadioItem } > & {
221
+ /* Same width as the check icons */
222
+ min-width: ${ space( 6 ) };
223
+ }
224
+
225
+ ${ DropdownMenuCheckboxItem } > &,
226
+ ${ DropdownMenuRadioItem } > &,
215
227
  &:not( :empty ) {
216
228
  margin-inline-end: ${ space( 2 ) };
217
229
  }
@@ -23,6 +23,7 @@ const noop = () => {};
23
23
 
24
24
  export default function FocalPointPickerControls( {
25
25
  __nextHasNoMarginBottom,
26
+ __next40pxDefaultSize,
26
27
  hasHelpText,
27
28
  onChange = noop,
28
29
  point = {
@@ -51,8 +52,10 @@ export default function FocalPointPickerControls( {
51
52
  className="focal-point-picker__controls"
52
53
  __nextHasNoMarginBottom={ __nextHasNoMarginBottom }
53
54
  hasHelpText={ hasHelpText }
55
+ gap={ 4 }
54
56
  >
55
57
  <FocalPointUnitControl
58
+ __next40pxDefaultSize={ __next40pxDefaultSize }
56
59
  label={ __( 'Left' ) }
57
60
  aria-label={ __( 'Focal point left position' ) }
58
61
  value={ [ valueX, '%' ].join( '' ) }
@@ -66,6 +69,7 @@ export default function FocalPointPickerControls( {
66
69
  dragDirection="e"
67
70
  />
68
71
  <FocalPointUnitControl
72
+ __next40pxDefaultSize={ __next40pxDefaultSize }
69
73
  label={ __( 'Top' ) }
70
74
  aria-label={ __( 'Focal point top position' ) }
71
75
  value={ [ valueY, '%' ].join( '' ) }
@@ -84,6 +84,7 @@ const GRID_OVERLAY_TIMEOUT = 600;
84
84
  */
85
85
  export function FocalPointPicker( {
86
86
  __nextHasNoMarginBottom,
87
+ __next40pxDefaultSize = false,
87
88
  autoPlay = true,
88
89
  className,
89
90
  help,
@@ -273,6 +274,7 @@ export function FocalPointPicker( {
273
274
  </MediaWrapper>
274
275
  <Controls
275
276
  __nextHasNoMarginBottom={ __nextHasNoMarginBottom }
277
+ __next40pxDefaultSize={ __next40pxDefaultSize }
276
278
  hasHelpText={ !! help }
277
279
  point={ { x, y } }
278
280
  onChange={ ( value ) => {
@@ -53,7 +53,7 @@ export const MediaPlaceholder = styled.div`
53
53
  `;
54
54
 
55
55
  export const StyledUnitControl = styled( UnitControl )`
56
- width: 100px;
56
+ width: 100%;
57
57
  `;
58
58
 
59
59
  const deprecatedBottomMargin = ( {
@@ -26,6 +26,12 @@ export type FocalPointPickerProps = Pick<
26
26
  * @default false
27
27
  */
28
28
  __nextHasNoMarginBottom?: boolean;
29
+ /**
30
+ * Start opting into the larger default height that will become the default size in a future version.
31
+ *
32
+ * @default false
33
+ */
34
+ __next40pxDefaultSize?: boolean;
29
35
  /**
30
36
  * Autoplays HTML5 video. This only applies to video sources (`url`).
31
37
  *
@@ -62,6 +68,7 @@ export type FocalPointPickerProps = Pick<
62
68
 
63
69
  export type FocalPointPickerControlsProps = {
64
70
  __nextHasNoMarginBottom?: boolean;
71
+ __next40pxDefaultSize?: boolean;
65
72
  /**
66
73
  * A bit of extra bottom margin will be added if a `help` text
67
74
  * needs to be rendered under it.
@@ -27,6 +27,7 @@ const CUSTOM_OPTION: FontSizePickerSelectOption = {
27
27
 
28
28
  const FontSizePickerSelect = ( props: FontSizePickerSelectProps ) => {
29
29
  const {
30
+ __next40pxDefaultSize,
30
31
  fontSizes,
31
32
  value,
32
33
  disableCustomFontSizes,
@@ -67,6 +68,7 @@ const FontSizePickerSelect = ( props: FontSizePickerSelectProps ) => {
67
68
 
68
69
  return (
69
70
  <CustomSelectControl
71
+ __next40pxDefaultSize={ __next40pxDefaultSize }
70
72
  __nextUnconstrainedWidth
71
73
  className="components-font-size-picker__select"
72
74
  label={ __( 'Font size' ) }
@@ -14,10 +14,18 @@ import { T_SHIRT_ABBREVIATIONS, T_SHIRT_NAMES } from './constants';
14
14
  import type { FontSizePickerToggleGroupProps } from './types';
15
15
 
16
16
  const FontSizePickerToggleGroup = ( props: FontSizePickerToggleGroupProps ) => {
17
- const { fontSizes, value, __nextHasNoMarginBottom, size, onChange } = props;
17
+ const {
18
+ fontSizes,
19
+ value,
20
+ __nextHasNoMarginBottom,
21
+ __next40pxDefaultSize,
22
+ size,
23
+ onChange,
24
+ } = props;
18
25
  return (
19
26
  <ToggleGroupControl
20
27
  __nextHasNoMarginBottom={ __nextHasNoMarginBottom }
28
+ __next40pxDefaultSize={ __next40pxDefaultSize }
21
29
  label={ __( 'Font size' ) }
22
30
  hideLabelFromVision
23
31
  value={ value }