@wordpress/components 25.15.0 → 25.16.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 (236) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/build/border-box-control/border-box-control/component.js.map +1 -1
  3. package/build/border-box-control/border-box-control/hook.js +3 -1
  4. package/build/border-box-control/border-box-control/hook.js.map +1 -1
  5. package/build/border-box-control/types.js.map +1 -1
  6. package/build/border-control/border-control/component.js +5 -1
  7. package/build/border-control/border-control/component.js.map +1 -1
  8. package/build/border-control/border-control/hook.js +18 -15
  9. package/build/border-control/border-control/hook.js.map +1 -1
  10. package/build/border-control/border-control-dropdown/component.js +2 -1
  11. package/build/border-control/border-control-dropdown/component.js.map +1 -1
  12. package/build/border-control/border-control-style-picker/component.js +21 -49
  13. package/build/border-control/border-control-style-picker/component.js.map +1 -1
  14. package/build/border-control/styles.js +15 -27
  15. package/build/border-control/styles.js.map +1 -1
  16. package/build/border-control/types.js.map +1 -1
  17. package/build/box-control/all-input-control.js +35 -29
  18. package/build/box-control/all-input-control.js.map +1 -1
  19. package/build/box-control/axial-input-controls.js +40 -54
  20. package/build/box-control/axial-input-controls.js.map +1 -1
  21. package/build/box-control/index.js +21 -24
  22. package/build/box-control/index.js.map +1 -1
  23. package/build/box-control/input-controls.js +45 -37
  24. package/build/box-control/input-controls.js.map +1 -1
  25. package/build/box-control/styles/box-control-styles.js +50 -112
  26. package/build/box-control/styles/box-control-styles.js.map +1 -1
  27. package/build/box-control/types.js.map +1 -1
  28. package/build/box-control/utils.js +123 -8
  29. package/build/box-control/utils.js.map +1 -1
  30. package/build/button/index.js +14 -16
  31. package/build/button/index.js.map +1 -1
  32. package/build/button/types.js.map +1 -1
  33. package/build/color-palette/index.native.js +11 -7
  34. package/build/color-palette/index.native.js.map +1 -1
  35. package/build/color-picker/hsl-input.js +55 -33
  36. package/build/color-picker/hsl-input.js.map +1 -1
  37. package/build/custom-select-control-v2/index.js +3 -2
  38. package/build/custom-select-control-v2/index.js.map +1 -1
  39. package/build/mobile/color-settings/palette.screen.native.js +8 -4
  40. package/build/mobile/color-settings/palette.screen.native.js.map +1 -1
  41. package/build/slot-fill/bubbles-virtually/use-slot-fills.js +1 -1
  42. package/build/slot-fill/bubbles-virtually/use-slot-fills.js.map +1 -1
  43. package/build/theme/styles.js +11 -6
  44. package/build/theme/styles.js.map +1 -1
  45. package/build/toggle-group-control/toggle-group-control/utils.js +7 -1
  46. package/build/toggle-group-control/toggle-group-control/utils.js.map +1 -1
  47. package/build/tooltip/index.js +35 -8
  48. package/build/tooltip/index.js.map +1 -1
  49. package/build/tooltip/types.js.map +1 -1
  50. package/build-module/border-box-control/border-box-control/component.js.map +1 -1
  51. package/build-module/border-box-control/border-box-control/hook.js +3 -1
  52. package/build-module/border-box-control/border-box-control/hook.js.map +1 -1
  53. package/build-module/border-box-control/types.js.map +1 -1
  54. package/build-module/border-control/border-control/component.js +5 -1
  55. package/build-module/border-control/border-control/component.js.map +1 -1
  56. package/build-module/border-control/border-control/hook.js +18 -15
  57. package/build-module/border-control/border-control/hook.js.map +1 -1
  58. package/build-module/border-control/border-control-dropdown/component.js +2 -1
  59. package/build-module/border-control/border-control-dropdown/component.js.map +1 -1
  60. package/build-module/border-control/border-control-style-picker/component.js +21 -48
  61. package/build-module/border-control/border-control-style-picker/component.js.map +1 -1
  62. package/build-module/border-control/styles.js +14 -24
  63. package/build-module/border-control/styles.js.map +1 -1
  64. package/build-module/border-control/types.js.map +1 -1
  65. package/build-module/box-control/all-input-control.js +38 -28
  66. package/build-module/box-control/all-input-control.js.map +1 -1
  67. package/build-module/box-control/axial-input-controls.js +42 -57
  68. package/build-module/box-control/axial-input-controls.js.map +1 -1
  69. package/build-module/box-control/index.js +22 -25
  70. package/build-module/box-control/index.js.map +1 -1
  71. package/build-module/box-control/input-controls.js +47 -40
  72. package/build-module/box-control/input-controls.js.map +1 -1
  73. package/build-module/box-control/styles/box-control-styles.js +45 -105
  74. package/build-module/box-control/styles/box-control-styles.js.map +1 -1
  75. package/build-module/box-control/types.js.map +1 -1
  76. package/build-module/box-control/utils.js +121 -7
  77. package/build-module/box-control/utils.js.map +1 -1
  78. package/build-module/button/index.js +14 -16
  79. package/build-module/button/index.js.map +1 -1
  80. package/build-module/button/types.js.map +1 -1
  81. package/build-module/color-palette/index.native.js +11 -7
  82. package/build-module/color-palette/index.native.js.map +1 -1
  83. package/build-module/color-picker/hsl-input.js +55 -33
  84. package/build-module/color-picker/hsl-input.js.map +1 -1
  85. package/build-module/custom-select-control-v2/index.js +3 -2
  86. package/build-module/custom-select-control-v2/index.js.map +1 -1
  87. package/build-module/mobile/color-settings/palette.screen.native.js +8 -4
  88. package/build-module/mobile/color-settings/palette.screen.native.js.map +1 -1
  89. package/build-module/slot-fill/bubbles-virtually/use-slot-fills.js +1 -1
  90. package/build-module/slot-fill/bubbles-virtually/use-slot-fills.js.map +1 -1
  91. package/build-module/theme/styles.js +11 -2
  92. package/build-module/theme/styles.js.map +1 -1
  93. package/build-module/toggle-group-control/toggle-group-control/utils.js +7 -1
  94. package/build-module/toggle-group-control/toggle-group-control/utils.js.map +1 -1
  95. package/build-module/tooltip/index.js +34 -9
  96. package/build-module/tooltip/index.js.map +1 -1
  97. package/build-module/tooltip/types.js.map +1 -1
  98. package/build-style/style-rtl.css +6 -4
  99. package/build-style/style.css +6 -4
  100. package/build-types/border-box-control/border-box-control/component.d.ts +1 -0
  101. package/build-types/border-box-control/border-box-control/component.d.ts.map +1 -1
  102. package/build-types/border-box-control/border-box-control/hook.d.ts.map +1 -1
  103. package/build-types/border-box-control/border-box-control-linked-button/hook.d.ts +3 -3
  104. package/build-types/border-box-control/stories/index.story.d.ts +2 -1
  105. package/build-types/border-box-control/stories/index.story.d.ts.map +1 -1
  106. package/build-types/border-box-control/types.d.ts +6 -0
  107. package/build-types/border-box-control/types.d.ts.map +1 -1
  108. package/build-types/border-control/border-control/component.d.ts +1 -0
  109. package/build-types/border-control/border-control/component.d.ts.map +1 -1
  110. package/build-types/border-control/border-control/hook.d.ts +2 -0
  111. package/build-types/border-control/border-control/hook.d.ts.map +1 -1
  112. package/build-types/border-control/border-control-dropdown/component.d.ts +1 -0
  113. package/build-types/border-control/border-control-dropdown/component.d.ts.map +1 -1
  114. package/build-types/border-control/border-control-dropdown/hook.d.ts +1 -0
  115. package/build-types/border-control/border-control-dropdown/hook.d.ts.map +1 -1
  116. package/build-types/border-control/border-control-style-picker/component.d.ts +3 -4
  117. package/build-types/border-control/border-control-style-picker/component.d.ts.map +1 -1
  118. package/build-types/border-control/stories/index.story.d.ts +12 -6
  119. package/build-types/border-control/stories/index.story.d.ts.map +1 -1
  120. package/build-types/border-control/styles.d.ts +0 -2
  121. package/build-types/border-control/styles.d.ts.map +1 -1
  122. package/build-types/border-control/types.d.ts +12 -1
  123. package/build-types/border-control/types.d.ts.map +1 -1
  124. package/build-types/box-control/all-input-control.d.ts +1 -1
  125. package/build-types/box-control/all-input-control.d.ts.map +1 -1
  126. package/build-types/box-control/axial-input-controls.d.ts +1 -1
  127. package/build-types/box-control/axial-input-controls.d.ts.map +1 -1
  128. package/build-types/box-control/index.d.ts +1 -1
  129. package/build-types/box-control/index.d.ts.map +1 -1
  130. package/build-types/box-control/input-controls.d.ts +1 -1
  131. package/build-types/box-control/input-controls.d.ts.map +1 -1
  132. package/build-types/box-control/stories/index.story.d.ts +24 -18
  133. package/build-types/box-control/stories/index.story.d.ts.map +1 -1
  134. package/build-types/box-control/styles/box-control-styles.d.ts +49 -23
  135. package/build-types/box-control/styles/box-control-styles.d.ts.map +1 -1
  136. package/build-types/box-control/types.d.ts +12 -12
  137. package/build-types/box-control/types.d.ts.map +1 -1
  138. package/build-types/box-control/utils.d.ts +2 -1
  139. package/build-types/box-control/utils.d.ts.map +1 -1
  140. package/build-types/button/deprecated.d.ts +1 -1
  141. package/build-types/button/index.d.ts.map +1 -1
  142. package/build-types/button/types.d.ts +7 -3
  143. package/build-types/button/types.d.ts.map +1 -1
  144. package/build-types/color-picker/hsl-input.d.ts.map +1 -1
  145. package/build-types/color-picker/styles.d.ts +1 -1
  146. package/build-types/custom-select-control-v2/index.d.ts.map +1 -1
  147. package/build-types/date-time/time/styles.d.ts +4 -4
  148. package/build-types/focal-point-picker/stories/index.story.d.ts +4 -4
  149. package/build-types/focal-point-picker/styles/focal-point-picker-style.d.ts +1 -1
  150. package/build-types/navigation/styles/navigation-styles.d.ts +1 -1
  151. package/build-types/navigator/navigator-back-button/hook.d.ts +2 -2
  152. package/build-types/navigator/navigator-button/hook.d.ts +2 -2
  153. package/build-types/number-control/index.d.ts +1 -1
  154. package/build-types/number-control/stories/index.story.d.ts +1 -1
  155. package/build-types/range-control/styles/range-control-styles.d.ts +1 -1
  156. package/build-types/search-control/index.d.ts +1 -1
  157. package/build-types/search-control/stories/index.story.d.ts +2 -2
  158. package/build-types/text-control/index.d.ts +1 -1
  159. package/build-types/textarea-control/index.d.ts +1 -1
  160. package/build-types/theme/styles.d.ts.map +1 -1
  161. package/build-types/toggle-group-control/toggle-group-control/as-button-group.d.ts +1 -1
  162. package/build-types/toggle-group-control/toggle-group-control/as-radio-group.d.ts +1 -1
  163. package/build-types/toggle-group-control/toggle-group-control/utils.d.ts.map +1 -1
  164. package/build-types/toggle-group-control/toggle-group-control-option/component.d.ts +1 -1
  165. package/build-types/toggle-group-control/toggle-group-control-option-icon/component.d.ts +1 -1
  166. package/build-types/toolbar/toolbar-button/index.d.ts +1 -1
  167. package/build-types/tooltip/index.d.ts +1 -1
  168. package/build-types/tooltip/index.d.ts.map +1 -1
  169. package/build-types/tooltip/stories/index.story.d.ts +10 -1
  170. package/build-types/tooltip/stories/index.story.d.ts.map +1 -1
  171. package/build-types/tooltip/types.d.ts +3 -0
  172. package/build-types/tooltip/types.d.ts.map +1 -1
  173. package/build-types/unit-control/index.d.ts +1 -1
  174. package/build-types/unit-control/styles/unit-control-styles.d.ts +1 -1
  175. package/package.json +19 -19
  176. package/src/border-box-control/border-box-control/component.tsx +0 -1
  177. package/src/border-box-control/border-box-control/hook.ts +5 -1
  178. package/src/border-box-control/types.ts +6 -0
  179. package/src/border-control/border-control/component.tsx +4 -0
  180. package/src/border-control/border-control/hook.ts +22 -16
  181. package/src/border-control/border-control-dropdown/component.tsx +2 -1
  182. package/src/border-control/border-control-style-picker/component.tsx +31 -66
  183. package/src/border-control/styles.ts +0 -15
  184. package/src/border-control/types.ts +15 -1
  185. package/src/box-control/all-input-control.tsx +57 -34
  186. package/src/box-control/axial-input-controls.tsx +79 -69
  187. package/src/box-control/index.tsx +47 -54
  188. package/src/box-control/input-controls.tsx +114 -83
  189. package/src/box-control/styles/box-control-styles.ts +21 -61
  190. package/src/box-control/test/index.tsx +126 -18
  191. package/src/box-control/types.ts +10 -21
  192. package/src/box-control/utils.ts +43 -8
  193. package/src/button/README.md +1 -1
  194. package/src/button/index.tsx +21 -33
  195. package/src/button/test/index.tsx +122 -0
  196. package/src/button/types.ts +7 -3
  197. package/src/circular-option-picker/test/index.tsx +10 -16
  198. package/src/color-palette/index.native.js +18 -7
  199. package/src/color-picker/hsl-input.tsx +56 -30
  200. package/src/color-picker/test/index.tsx +190 -16
  201. package/src/custom-select-control-v2/index.tsx +5 -2
  202. package/src/mobile/color-settings/palette.screen.native.js +7 -5
  203. package/src/palette-edit/test/index.tsx +326 -10
  204. package/src/slot-fill/bubbles-virtually/use-slot-fills.ts +1 -1
  205. package/src/tabs/test/index.tsx +3 -1
  206. package/src/theme/styles.ts +3 -1
  207. package/src/toggle-group-control/test/__snapshots__/index.tsx.snap +6 -6
  208. package/src/toggle-group-control/test/index.tsx +73 -36
  209. package/src/toggle-group-control/toggle-group-control/utils.ts +8 -3
  210. package/src/tooltip/README.md +4 -0
  211. package/src/tooltip/index.tsx +46 -8
  212. package/src/tooltip/stories/index.story.tsx +18 -1
  213. package/src/tooltip/test/index.tsx +77 -12
  214. package/src/tooltip/types.ts +4 -0
  215. package/tsconfig.tsbuildinfo +1 -1
  216. package/build/border-control/border-control-style-picker/hook.js +0 -41
  217. package/build/border-control/border-control-style-picker/hook.js.map +0 -1
  218. package/build/box-control/styles/box-control-visualizer-styles.js +0 -93
  219. package/build/box-control/styles/box-control-visualizer-styles.js.map +0 -1
  220. package/build/box-control/unit-control.js +0 -76
  221. package/build/box-control/unit-control.js.map +0 -1
  222. package/build-module/border-control/border-control-style-picker/hook.js +0 -32
  223. package/build-module/border-control/border-control-style-picker/hook.js.map +0 -1
  224. package/build-module/box-control/styles/box-control-visualizer-styles.js +0 -86
  225. package/build-module/box-control/styles/box-control-visualizer-styles.js.map +0 -1
  226. package/build-module/box-control/unit-control.js +0 -68
  227. package/build-module/box-control/unit-control.js.map +0 -1
  228. package/build-types/border-control/border-control-style-picker/hook.d.ts +0 -267
  229. package/build-types/border-control/border-control-style-picker/hook.d.ts.map +0 -1
  230. package/build-types/box-control/styles/box-control-visualizer-styles.d.ts +0 -46
  231. package/build-types/box-control/styles/box-control-visualizer-styles.d.ts.map +0 -1
  232. package/build-types/box-control/unit-control.d.ts +0 -4
  233. package/build-types/box-control/unit-control.d.ts.map +0 -1
  234. package/src/border-control/border-control-style-picker/hook.ts +0 -35
  235. package/src/box-control/styles/box-control-visualizer-styles.ts +0 -75
  236. package/src/box-control/unit-control.tsx +0 -74
@@ -1,13 +1,19 @@
1
1
  /**
2
2
  * External dependencies
3
3
  */
4
- import { screen, render } from '@testing-library/react';
4
+ import { fireEvent, screen, render, waitFor } 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 { ColorPicker } from '..';
16
+ import { click } from '@ariakit/test';
11
17
 
12
18
  const hslaMatcher = expect.objectContaining( {
13
19
  h: expect.any( Number ),
@@ -133,20 +139,39 @@ describe( 'ColorPicker', () => {
133
139
  } );
134
140
  } );
135
141
 
136
- describe.each( [
137
- [ 'hue', 'Hue', '#aad52a' ],
138
- [ 'saturation', 'Saturation', '#20dfdf' ],
139
- [ 'lightness', 'Lightness', '#95eaea' ],
140
- ] )( 'HSL inputs', ( colorInput, inputLabel, expected ) => {
141
- it( `should fire onChange with the correct value when the ${ colorInput } value is updated`, async () => {
142
+ describe( 'HSL inputs', () => {
143
+ it( 'sliders should use accurate H and S values based on user interaction when possible', async () => {
142
144
  const user = userEvent.setup();
143
145
  const onChange = jest.fn();
144
- const color = '#2ad5d5';
146
+
147
+ const ControlledColorPicker = ( {
148
+ onChange: onChangeProp,
149
+ ...restProps
150
+ }: React.ComponentProps< typeof ColorPicker > ) => {
151
+ const [ colorState, setColorState ] = useState( '#000000' );
152
+
153
+ const internalOnChange: typeof onChangeProp = ( newColor ) => {
154
+ onChangeProp?.( newColor );
155
+ setColorState( newColor );
156
+ };
157
+
158
+ return (
159
+ <>
160
+ <ColorPicker
161
+ { ...restProps }
162
+ onChange={ internalOnChange }
163
+ color={ colorState }
164
+ />
165
+ <button onClick={ () => setColorState( '#4d87ba' ) }>
166
+ Set color to #4d87ba
167
+ </button>
168
+ </>
169
+ );
170
+ };
145
171
 
146
172
  render(
147
- <ColorPicker
173
+ <ControlledColorPicker
148
174
  onChange={ onChange }
149
- color={ color }
150
175
  enableAlpha={ false }
151
176
  />
152
177
  );
@@ -156,16 +181,165 @@ describe( 'ColorPicker', () => {
156
181
 
157
182
  await user.selectOptions( formatSelector, 'hsl' );
158
183
 
159
- const inputElement = screen.getByRole( 'spinbutton', {
160
- name: inputLabel,
184
+ const hueSliders = screen.getAllByRole( 'slider', {
185
+ name: 'Hue',
161
186
  } );
162
- expect( inputElement ).toBeVisible();
187
+ expect( hueSliders ).toHaveLength( 2 );
163
188
 
164
- await user.clear( inputElement );
165
- await user.type( inputElement, '75' );
189
+ // Reason for the `!` post-fix expression operator: if the check above
190
+ // doesn't fail, we're guaranteed that `hueSlider` is not undefined.
191
+ const hueSlider = hueSliders.at( -1 )!;
192
+ const saturationSlider = screen.getByRole( 'slider', {
193
+ name: 'Saturation',
194
+ } );
195
+ const lightnessSlider = screen.getByRole( 'slider', {
196
+ name: 'Lightness',
197
+ } );
198
+ const hueNumberInput = screen.getByRole( 'spinbutton', {
199
+ name: 'Hue',
200
+ } );
201
+ const saturationNumberInput = screen.getByRole( 'spinbutton', {
202
+ name: 'Saturation',
203
+ } );
204
+ const lightnessNumberInput = screen.getByRole( 'spinbutton', {
205
+ name: 'Lightness',
206
+ } );
207
+
208
+ // All initial inputs should have a value of `0` since the color is black.
209
+ expect( hueSlider ).toHaveValue( '0' );
210
+ expect( saturationSlider ).toHaveValue( '0' );
211
+ expect( lightnessSlider ).toHaveValue( '0' );
212
+ expect( hueNumberInput ).toHaveValue( 0 );
213
+ expect( saturationNumberInput ).toHaveValue( 0 );
214
+ expect( lightnessNumberInput ).toHaveValue( 0 );
215
+
216
+ // Interact with the Hue slider, it should change its value (and the
217
+ // value of the associated number input), but it shouldn't cause the
218
+ // `onChange` callback to fire, since the resulting color is still black.
219
+ fireEvent.change( hueSlider, { target: { value: 80 } } );
220
+
221
+ expect( hueSlider ).toHaveValue( '80' );
222
+ expect( hueNumberInput ).toHaveValue( 80 );
223
+ expect( onChange ).not.toHaveBeenCalled();
224
+
225
+ // Interact with the Saturation slider, it should change its value (and the
226
+ // value of the associated number input), but it shouldn't cause the
227
+ // `onChange` callback to fire, since the resulting color is still black.
228
+ fireEvent.change( saturationSlider, { target: { value: 50 } } );
229
+
230
+ expect( saturationSlider ).toHaveValue( '50' );
231
+ expect( saturationNumberInput ).toHaveValue( 50 );
232
+ expect( onChange ).not.toHaveBeenCalled();
233
+
234
+ // Interact with the Lightness slider, it should change its value (and the
235
+ // value of the associated number input). It should also cause the
236
+ // `onChange` callback to fire, since changing the lightness actually
237
+ // causes the color to change.
238
+ fireEvent.change( lightnessSlider, { target: { value: 30 } } );
166
239
 
240
+ await waitFor( () =>
241
+ expect( lightnessSlider ).toHaveValue( '30' )
242
+ );
243
+ expect( lightnessNumberInput ).toHaveValue( 30 );
244
+ expect( onChange ).toHaveBeenCalledTimes( 1 );
245
+ expect( onChange ).toHaveBeenLastCalledWith( '#597326' );
246
+
247
+ // Interact with the Lightness slider, setting to 100 (ie. white).
248
+ // It should also cause the `onChange` callback to fire, and reset the
249
+ // hue and saturation inputs to `0`.
250
+ fireEvent.change( lightnessSlider, { target: { value: 100 } } );
251
+
252
+ await waitFor( () =>
253
+ expect( lightnessSlider ).toHaveValue( '100' )
254
+ );
255
+ expect( lightnessNumberInput ).toHaveValue( 100 );
256
+ expect( hueSlider ).toHaveValue( '0' );
257
+ expect( saturationSlider ).toHaveValue( '0' );
258
+ expect( hueNumberInput ).toHaveValue( 0 );
259
+ expect( saturationNumberInput ).toHaveValue( 0 );
260
+ expect( onChange ).toHaveBeenCalledTimes( 2 );
261
+ expect( onChange ).toHaveBeenLastCalledWith( '#ffffff' );
262
+
263
+ // Interact with the Hue slider, it should change its value (and the
264
+ // value of the associated number input), but it shouldn't cause the
265
+ // `onChange` callback to fire, since the resulting color is still white.
266
+ fireEvent.change( hueSlider, { target: { value: 147 } } );
267
+
268
+ expect( hueSlider ).toHaveValue( '147' );
269
+ expect( hueNumberInput ).toHaveValue( 147 );
270
+ expect( onChange ).toHaveBeenCalledTimes( 2 );
271
+
272
+ // Interact with the Saturation slider, it should change its value (and the
273
+ // value of the associated number input), but it shouldn't cause the
274
+ // `onChange` callback to fire, since the resulting color is still white.
275
+ fireEvent.change( saturationSlider, { target: { value: 82 } } );
276
+
277
+ expect( saturationSlider ).toHaveValue( '82' );
278
+ expect( saturationNumberInput ).toHaveValue( 82 );
279
+ expect( onChange ).toHaveBeenCalledTimes( 2 );
280
+
281
+ // Interact with the Lightness slider, it should change its value (and the
282
+ // value of the associated number input). It should also cause the
283
+ // `onChange` callback to fire, since changing the lightness actually
284
+ // causes the color to change.
285
+ fireEvent.change( lightnessSlider, { target: { value: 14 } } );
286
+
287
+ await waitFor( () =>
288
+ expect( lightnessSlider ).toHaveValue( '14' )
289
+ );
290
+ expect( lightnessNumberInput ).toHaveValue( 14 );
167
291
  expect( onChange ).toHaveBeenCalledTimes( 3 );
168
- expect( onChange ).toHaveBeenLastCalledWith( expected );
292
+ expect( onChange ).toHaveBeenLastCalledWith( '#064121' );
293
+
294
+ // Set the color externally. All inputs should update to match the H/S/L
295
+ // value of the new color.
296
+ const setColorButton = screen.getByRole( 'button', {
297
+ name: /set color/i,
298
+ } );
299
+ await click( setColorButton );
300
+
301
+ expect( hueSlider ).toHaveValue( '208' );
302
+ expect( hueNumberInput ).toHaveValue( 208 );
303
+ expect( saturationSlider ).toHaveValue( '44' );
304
+ expect( saturationNumberInput ).toHaveValue( 44 );
305
+ expect( lightnessSlider ).toHaveValue( '52' );
306
+ expect( lightnessNumberInput ).toHaveValue( 52 );
307
+ } );
308
+
309
+ describe.each( [
310
+ [ 'hue', 'Hue', '#aad52a' ],
311
+ [ 'saturation', 'Saturation', '#20dfdf' ],
312
+ [ 'lightness', 'Lightness', '#95eaea' ],
313
+ ] )( 'HSL inputs', ( colorInput, inputLabel, expected ) => {
314
+ it( `should fire onChange with the correct value when the ${ colorInput } value is updated`, async () => {
315
+ const user = userEvent.setup();
316
+ const onChange = jest.fn();
317
+ const color = '#2ad5d5';
318
+
319
+ render(
320
+ <ColorPicker
321
+ onChange={ onChange }
322
+ color={ color }
323
+ enableAlpha={ false }
324
+ />
325
+ );
326
+
327
+ const formatSelector = screen.getByRole( 'combobox' );
328
+ expect( formatSelector ).toBeVisible();
329
+
330
+ await user.selectOptions( formatSelector, 'hsl' );
331
+
332
+ const inputElement = screen.getByRole( 'spinbutton', {
333
+ name: inputLabel,
334
+ } );
335
+ expect( inputElement ).toBeVisible();
336
+
337
+ await user.clear( inputElement );
338
+ await user.type( inputElement, '75' );
339
+
340
+ expect( onChange ).toHaveBeenCalledTimes( 3 );
341
+ expect( onChange ).toHaveBeenLastCalledWith( expected );
342
+ } );
169
343
  } );
170
344
  } );
171
345
  } );
@@ -49,7 +49,7 @@ export function CustomSelect( {
49
49
  onChange,
50
50
  size = 'default',
51
51
  value,
52
- renderSelectedValue = defaultRenderSelectedValue,
52
+ renderSelectedValue,
53
53
  ...props
54
54
  }: WordPressComponentProps< CustomSelectProps, 'button', false > ) {
55
55
  const store = Ariakit.useSelectStore( {
@@ -60,6 +60,9 @@ export function CustomSelect( {
60
60
 
61
61
  const { value: currentValue } = store.useState();
62
62
 
63
+ const computedRenderSelectedValue =
64
+ renderSelectedValue ?? defaultRenderSelectedValue;
65
+
63
66
  return (
64
67
  <>
65
68
  <Styled.CustomSelectLabel store={ store }>
@@ -71,7 +74,7 @@ export function CustomSelect( {
71
74
  hasCustomRenderProp={ !! renderSelectedValue }
72
75
  store={ store }
73
76
  >
74
- { renderSelectedValue( currentValue ) }
77
+ { computedRenderSelectedValue( currentValue ) }
75
78
  <Ariakit.SelectArrow />
76
79
  </Styled.CustomSelectButton>
77
80
  <Styled.CustomSelectPopover gutter={ 12 } store={ store } sameWidth>
@@ -29,7 +29,6 @@ import { colorsUtils } from './utils';
29
29
  import styles from './style.scss';
30
30
 
31
31
  const HIT_SLOP = { top: 8, bottom: 8, left: 8, right: 8 };
32
- const THEME_PALETTE_NAME = 'Theme';
33
32
 
34
33
  const PaletteScreen = () => {
35
34
  const route = useRoute();
@@ -48,7 +47,6 @@ const PaletteScreen = () => {
48
47
  const [ currentValue, setCurrentValue ] = useState( colorValue );
49
48
  const isGradientColor = isGradient( currentValue );
50
49
  const selectedSegmentIndex = isGradientColor ? 1 : 0;
51
- const allAvailableColors = useMobileGlobalStylesColors();
52
50
 
53
51
  const [ currentSegment, setCurrentSegment ] = useState(
54
52
  segments[ selectedSegmentIndex ]
@@ -57,6 +55,10 @@ const PaletteScreen = () => {
57
55
  const currentSegmentColors = ! isGradientSegment
58
56
  ? defaultSettings.colors
59
57
  : defaultSettings.gradients;
58
+ const allAvailableColors = useMobileGlobalStylesColors();
59
+ const allAvailableGradients = currentSegmentColors
60
+ .flatMap( ( { gradients } ) => gradients )
61
+ .filter( Boolean );
60
62
 
61
63
  const horizontalSeparatorStyle = usePreferredColorSchemeStyle(
62
64
  styles.horizontalSeparator,
@@ -184,10 +186,10 @@ const PaletteScreen = () => {
184
186
  colors: palette.colors,
185
187
  gradients: palette.gradients,
186
188
  allColors: allAvailableColors,
189
+ allGradients: allAvailableGradients,
187
190
  };
188
- const enableCustomColor =
189
- ! isGradientSegment &&
190
- palette.name === THEME_PALETTE_NAME;
191
+ // Limit to show the custom indicator to the first available palette
192
+ const enableCustomColor = paletteKey === 0;
191
193
 
192
194
  return (
193
195
  <ColorPalette
@@ -1,7 +1,8 @@
1
1
  /**
2
2
  * External dependencies
3
3
  */
4
- import { render, fireEvent, screen } from '@testing-library/react';
4
+ import { render, screen, waitFor } from '@testing-library/react';
5
+ import { click, type, press } from '@ariakit/test';
5
6
 
6
7
  /**
7
8
  * Internal dependencies
@@ -9,6 +10,17 @@ import { render, fireEvent, screen } from '@testing-library/react';
9
10
  import PaletteEdit, { getNameForPosition } from '..';
10
11
  import type { PaletteElement } from '../types';
11
12
 
13
+ const noop = () => {};
14
+
15
+ async function clearInput( input: HTMLInputElement ) {
16
+ await click( input );
17
+
18
+ // Press backspace as many times as the input's current value
19
+ for ( const _ of Array( input.value.length ) ) {
20
+ await press.Backspace();
21
+ }
22
+ }
23
+
12
24
  describe( 'getNameForPosition', () => {
13
25
  test( 'should return 1 by default', () => {
14
26
  const slugPrefix = 'test-';
@@ -82,18 +94,322 @@ describe( 'getNameForPosition', () => {
82
94
 
83
95
  describe( 'PaletteEdit', () => {
84
96
  const defaultProps = {
85
- colors: [ { color: '#ffffff', name: 'Base', slug: 'base' } ],
86
- onChange: jest.fn(),
87
97
  paletteLabel: 'Test label',
88
- emptyMessage: 'Test empty message',
89
- canOnlyChangeValues: true,
90
- canReset: true,
91
98
  slugPrefix: '',
99
+ onChange: noop,
92
100
  };
93
101
 
94
- it( 'opens color selector for color palettes', () => {
95
- render( <PaletteEdit { ...defaultProps } /> );
96
- fireEvent.click( screen.getByLabelText( 'Color: Base' ) );
97
- expect( screen.getByLabelText( 'Hex color' ) ).toBeInTheDocument();
102
+ const colors = [
103
+ { color: '#1a4548', name: 'Primary', slug: 'primary' },
104
+ { color: '#0000ff', name: 'Secondary', slug: 'secondary' },
105
+ ];
106
+ const gradients = [
107
+ {
108
+ gradient:
109
+ 'linear-gradient(135deg,rgb(255,245,203) 0%,rgb(182,227,212) 50%,rgb(51,167,181) 100%)',
110
+ name: 'Pale ocean',
111
+ slug: 'pale-ocean',
112
+ },
113
+ {
114
+ gradient:
115
+ 'linear-gradient(135deg,rgb(2,3,129) 0%,rgb(40,116,252) 100%)',
116
+ name: 'Midnight',
117
+ slug: 'midnight',
118
+ },
119
+ ];
120
+
121
+ it( 'shows heading label', () => {
122
+ render( <PaletteEdit { ...defaultProps } colors={ colors } /> );
123
+
124
+ const paletteLabel = screen.getByRole( 'heading', {
125
+ level: 2,
126
+ name: 'Test label',
127
+ } );
128
+
129
+ expect( paletteLabel ).toBeVisible();
130
+ } );
131
+
132
+ it( 'shows heading label with custom heading level', () => {
133
+ render(
134
+ <PaletteEdit
135
+ { ...defaultProps }
136
+ colors={ colors }
137
+ paletteLabelHeadingLevel={ 5 }
138
+ />
139
+ );
140
+
141
+ expect(
142
+ screen.getByRole( 'heading', {
143
+ level: 5,
144
+ name: 'Test label',
145
+ } )
146
+ ).toBeVisible();
147
+ } );
148
+
149
+ it( 'shows empty message', () => {
150
+ render(
151
+ <PaletteEdit
152
+ { ...defaultProps }
153
+ emptyMessage={ 'Test empty message' }
154
+ />
155
+ );
156
+
157
+ expect( screen.getByText( 'Test empty message' ) ).toBeVisible();
158
+ } );
159
+
160
+ it( 'shows an option to remove all colors', async () => {
161
+ render( <PaletteEdit { ...defaultProps } colors={ colors } /> );
162
+
163
+ await click(
164
+ screen.getByRole( 'button', {
165
+ name: 'Color options',
166
+ } )
167
+ );
168
+
169
+ expect(
170
+ screen.getByRole( 'button', {
171
+ name: 'Remove all colors',
172
+ } )
173
+ ).toBeVisible();
174
+ } );
175
+
176
+ it( 'shows a reset option when the `canReset` prop is enabled', async () => {
177
+ render(
178
+ <PaletteEdit { ...defaultProps } colors={ colors } canReset />
179
+ );
180
+
181
+ await click(
182
+ screen.getByRole( 'button', {
183
+ name: 'Color options',
184
+ } )
185
+ );
186
+ expect(
187
+ screen.getByRole( 'button', {
188
+ name: 'Reset colors',
189
+ } )
190
+ ).toBeVisible();
191
+ } );
192
+
193
+ it( 'does not show a reset colors option when `canReset` is disabled', async () => {
194
+ render( <PaletteEdit { ...defaultProps } colors={ colors } /> );
195
+
196
+ await click(
197
+ screen.getByRole( 'button', {
198
+ name: 'Color options',
199
+ } )
200
+ );
201
+ expect(
202
+ screen.queryByRole( 'button', {
203
+ name: 'Reset colors',
204
+ } )
205
+ ).not.toBeInTheDocument();
206
+ } );
207
+
208
+ it( 'calls the `onChange` with the new color appended', async () => {
209
+ const onChange = jest.fn();
210
+
211
+ render(
212
+ <PaletteEdit
213
+ { ...defaultProps }
214
+ colors={ colors }
215
+ onChange={ onChange }
216
+ />
217
+ );
218
+
219
+ await click(
220
+ screen.getByRole( 'button', {
221
+ name: 'Add color',
222
+ } )
223
+ );
224
+
225
+ await waitFor( () => {
226
+ expect( onChange ).toHaveBeenCalledWith( [
227
+ ...colors,
228
+ {
229
+ color: '#000',
230
+ name: 'Color 1',
231
+ slug: 'color-1',
232
+ },
233
+ ] );
234
+ } );
235
+ } );
236
+
237
+ it( 'calls the `onChange` with the new gradient appended', async () => {
238
+ const onChange = jest.fn();
239
+
240
+ render(
241
+ <PaletteEdit
242
+ { ...defaultProps }
243
+ gradients={ gradients }
244
+ onChange={ onChange }
245
+ />
246
+ );
247
+
248
+ await click(
249
+ screen.getByRole( 'button', {
250
+ name: 'Add gradient',
251
+ } )
252
+ );
253
+
254
+ await waitFor( () => {
255
+ expect( onChange ).toHaveBeenCalledWith( [
256
+ ...gradients,
257
+ {
258
+ gradient:
259
+ 'linear-gradient(135deg, rgba(6, 147, 227, 1) 0%, rgb(155, 81, 224) 100%)',
260
+ name: 'Color 1',
261
+ slug: 'color-1',
262
+ },
263
+ ] );
264
+ } );
265
+ } );
266
+
267
+ it( 'can not add new colors when `canOnlyChangeValues` is enabled', () => {
268
+ render( <PaletteEdit { ...defaultProps } canOnlyChangeValues /> );
269
+
270
+ expect(
271
+ screen.queryByRole( 'button', {
272
+ name: 'Add color',
273
+ } )
274
+ ).not.toBeInTheDocument();
275
+ } );
276
+
277
+ it( 'can remove a color', async () => {
278
+ const onChange = jest.fn();
279
+
280
+ render(
281
+ <PaletteEdit
282
+ { ...defaultProps }
283
+ colors={ colors }
284
+ onChange={ onChange }
285
+ />
286
+ );
287
+
288
+ await click(
289
+ screen.getByRole( 'button', {
290
+ name: 'Color options',
291
+ } )
292
+ );
293
+ await click(
294
+ screen.getByRole( 'button', {
295
+ name: 'Show details',
296
+ } )
297
+ );
298
+ await click( screen.getByText( 'Primary' ) );
299
+ await click(
300
+ screen.getByRole( 'button', {
301
+ name: 'Remove color',
302
+ } )
303
+ );
304
+
305
+ await waitFor( () => {
306
+ expect( onChange ).toHaveBeenCalledWith( [ colors[ 1 ] ] );
307
+ } );
308
+ } );
309
+
310
+ it( 'can update palette name', async () => {
311
+ const onChange = jest.fn();
312
+
313
+ render(
314
+ <PaletteEdit
315
+ { ...defaultProps }
316
+ colors={ colors }
317
+ onChange={ onChange }
318
+ />
319
+ );
320
+
321
+ await click(
322
+ screen.getByRole( 'button', {
323
+ name: 'Color options',
324
+ } )
325
+ );
326
+ await click(
327
+ screen.getByRole( 'button', {
328
+ name: 'Show details',
329
+ } )
330
+ );
331
+ await click( screen.getByText( 'Primary' ) );
332
+ const nameInput = screen.getByRole( 'textbox', {
333
+ name: 'Color name',
334
+ } );
335
+
336
+ await clearInput( nameInput as HTMLInputElement );
337
+
338
+ await type( 'Primary Updated' );
339
+
340
+ await waitFor( () => {
341
+ expect( onChange ).toHaveBeenCalledWith( [
342
+ {
343
+ ...colors[ 0 ],
344
+ name: 'Primary Updated',
345
+ slug: 'primary-updated',
346
+ },
347
+ colors[ 1 ],
348
+ ] );
349
+ } );
350
+ } );
351
+
352
+ it( 'can update color palette value', async () => {
353
+ const onChange = jest.fn();
354
+
355
+ render(
356
+ <PaletteEdit
357
+ { ...defaultProps }
358
+ colors={ colors }
359
+ onChange={ onChange }
360
+ />
361
+ );
362
+
363
+ await click( screen.getByLabelText( 'Color: Primary' ) );
364
+ const hexInput = screen.getByRole( 'textbox', {
365
+ name: 'Hex color',
366
+ } );
367
+
368
+ await clearInput( hexInput as HTMLInputElement );
369
+
370
+ await type( '000000' );
371
+
372
+ await waitFor( () => {
373
+ expect( onChange ).toHaveBeenCalledWith( [
374
+ {
375
+ ...colors[ 0 ],
376
+ color: '#000000',
377
+ },
378
+ colors[ 1 ],
379
+ ] );
380
+ } );
381
+ } );
382
+
383
+ it( 'can update gradient palette value', async () => {
384
+ const onChange = jest.fn();
385
+
386
+ render(
387
+ <PaletteEdit
388
+ { ...defaultProps }
389
+ gradients={ gradients }
390
+ onChange={ onChange }
391
+ />
392
+ );
393
+
394
+ await click( screen.getByLabelText( 'Gradient: Pale ocean' ) );
395
+
396
+ // Select radial gradient option
397
+ await click(
398
+ screen.getByRole( 'combobox', {
399
+ name: 'Type',
400
+ } )
401
+ );
402
+ await click( screen.getByRole( 'option', { name: 'Radial' } ) );
403
+
404
+ await waitFor( () => {
405
+ expect( onChange ).toHaveBeenCalledWith( [
406
+ {
407
+ ...gradients[ 0 ],
408
+ gradient:
409
+ 'radial-gradient(rgb(255,245,203) 0%,rgb(182,227,212) 50%,rgb(51,167,181) 100%)',
410
+ },
411
+ gradients[ 1 ],
412
+ ] );
413
+ } );
98
414
  } );
99
415
  } );
@@ -19,6 +19,6 @@ export default function useSlotFills( name: SlotKey ) {
19
19
  const fills = useSnapshot( registry.fills, { sync: true } );
20
20
  // The important bit here is that this call ensures that the hook
21
21
  // only causes a re-render if the "fills" of a given slot name
22
- // change change, not any fills.
22
+ // change, not any fills.
23
23
  return fills.get( name );
24
24
  }
@@ -1260,7 +1260,9 @@ describe( 'Tabs', () => {
1260
1260
 
1261
1261
  // Tab key should focus the currently selected tab, which is Beta.
1262
1262
  await press.Tab();
1263
- expect( await getSelectedTab() ).toHaveFocus();
1263
+ await waitFor( async () =>
1264
+ expect( await getSelectedTab() ).toHaveFocus()
1265
+ );
1264
1266
 
1265
1267
  rerender(
1266
1268
  <ControlledTabs
@@ -30,4 +30,6 @@ export const colorVariables = ( { colors }: ThemeOutputValues ) => {
30
30
  ];
31
31
  };
32
32
 
33
- export const Wrapper = styled.div``;
33
+ export const Wrapper = styled.div`
34
+ color: var( --wp-components-color-foreground, currentColor );
35
+ `;