@wordpress/components 25.15.1-next.79a6196f.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 (259) hide show
  1. package/CHANGELOG.md +27 -2
  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-picker/hsl-input.js +55 -33
  34. package/build/color-picker/hsl-input.js.map +1 -1
  35. package/build/custom-select-control-v2/index.js +3 -2
  36. package/build/custom-select-control-v2/index.js.map +1 -1
  37. package/build/slot-fill/bubbles-virtually/use-slot-fills.js +1 -1
  38. package/build/slot-fill/bubbles-virtually/use-slot-fills.js.map +1 -1
  39. package/build/theme/styles.js +11 -6
  40. package/build/theme/styles.js.map +1 -1
  41. package/build/toggle-group-control/toggle-group-control/utils.js +7 -1
  42. package/build/toggle-group-control/toggle-group-control/utils.js.map +1 -1
  43. package/build/tooltip/index.js +15 -12
  44. package/build/tooltip/index.js.map +1 -1
  45. package/build/tooltip/types.js.map +1 -1
  46. package/build-module/border-box-control/border-box-control/component.js.map +1 -1
  47. package/build-module/border-box-control/border-box-control/hook.js +3 -1
  48. package/build-module/border-box-control/border-box-control/hook.js.map +1 -1
  49. package/build-module/border-box-control/types.js.map +1 -1
  50. package/build-module/border-control/border-control/component.js +5 -1
  51. package/build-module/border-control/border-control/component.js.map +1 -1
  52. package/build-module/border-control/border-control/hook.js +18 -15
  53. package/build-module/border-control/border-control/hook.js.map +1 -1
  54. package/build-module/border-control/border-control-dropdown/component.js +2 -1
  55. package/build-module/border-control/border-control-dropdown/component.js.map +1 -1
  56. package/build-module/border-control/border-control-style-picker/component.js +21 -48
  57. package/build-module/border-control/border-control-style-picker/component.js.map +1 -1
  58. package/build-module/border-control/styles.js +14 -24
  59. package/build-module/border-control/styles.js.map +1 -1
  60. package/build-module/border-control/types.js.map +1 -1
  61. package/build-module/box-control/all-input-control.js +38 -28
  62. package/build-module/box-control/all-input-control.js.map +1 -1
  63. package/build-module/box-control/axial-input-controls.js +42 -57
  64. package/build-module/box-control/axial-input-controls.js.map +1 -1
  65. package/build-module/box-control/index.js +22 -25
  66. package/build-module/box-control/index.js.map +1 -1
  67. package/build-module/box-control/input-controls.js +47 -40
  68. package/build-module/box-control/input-controls.js.map +1 -1
  69. package/build-module/box-control/styles/box-control-styles.js +45 -105
  70. package/build-module/box-control/styles/box-control-styles.js.map +1 -1
  71. package/build-module/box-control/types.js.map +1 -1
  72. package/build-module/box-control/utils.js +121 -7
  73. package/build-module/box-control/utils.js.map +1 -1
  74. package/build-module/button/index.js +14 -16
  75. package/build-module/button/index.js.map +1 -1
  76. package/build-module/button/types.js.map +1 -1
  77. package/build-module/color-picker/hsl-input.js +55 -33
  78. package/build-module/color-picker/hsl-input.js.map +1 -1
  79. package/build-module/custom-select-control-v2/index.js +3 -2
  80. package/build-module/custom-select-control-v2/index.js.map +1 -1
  81. package/build-module/slot-fill/bubbles-virtually/use-slot-fills.js +1 -1
  82. package/build-module/slot-fill/bubbles-virtually/use-slot-fills.js.map +1 -1
  83. package/build-module/theme/styles.js +11 -2
  84. package/build-module/theme/styles.js.map +1 -1
  85. package/build-module/toggle-group-control/toggle-group-control/utils.js +7 -1
  86. package/build-module/toggle-group-control/toggle-group-control/utils.js.map +1 -1
  87. package/build-module/tooltip/index.js +16 -13
  88. package/build-module/tooltip/index.js.map +1 -1
  89. package/build-module/tooltip/types.js.map +1 -1
  90. package/build-style/style-rtl.css +6 -4
  91. package/build-style/style.css +6 -4
  92. package/build-types/border-box-control/border-box-control/component.d.ts +1 -0
  93. package/build-types/border-box-control/border-box-control/component.d.ts.map +1 -1
  94. package/build-types/border-box-control/border-box-control/hook.d.ts +4 -4
  95. package/build-types/border-box-control/border-box-control/hook.d.ts.map +1 -1
  96. package/build-types/border-box-control/border-box-control-linked-button/hook.d.ts +5 -5
  97. package/build-types/border-box-control/border-box-control-split-controls/hook.d.ts +4 -4
  98. package/build-types/border-box-control/border-box-control-visualizer/hook.d.ts +4 -4
  99. package/build-types/border-box-control/stories/index.story.d.ts +2 -1
  100. package/build-types/border-box-control/stories/index.story.d.ts.map +1 -1
  101. package/build-types/border-box-control/types.d.ts +6 -0
  102. package/build-types/border-box-control/types.d.ts.map +1 -1
  103. package/build-types/border-control/border-control/component.d.ts +1 -0
  104. package/build-types/border-control/border-control/component.d.ts.map +1 -1
  105. package/build-types/border-control/border-control/hook.d.ts +6 -4
  106. package/build-types/border-control/border-control/hook.d.ts.map +1 -1
  107. package/build-types/border-control/border-control-dropdown/component.d.ts +1 -0
  108. package/build-types/border-control/border-control-dropdown/component.d.ts.map +1 -1
  109. package/build-types/border-control/border-control-dropdown/hook.d.ts +5 -4
  110. package/build-types/border-control/border-control-dropdown/hook.d.ts.map +1 -1
  111. package/build-types/border-control/border-control-style-picker/component.d.ts +3 -4
  112. package/build-types/border-control/border-control-style-picker/component.d.ts.map +1 -1
  113. package/build-types/border-control/stories/index.story.d.ts +12 -6
  114. package/build-types/border-control/stories/index.story.d.ts.map +1 -1
  115. package/build-types/border-control/styles.d.ts +0 -2
  116. package/build-types/border-control/styles.d.ts.map +1 -1
  117. package/build-types/border-control/types.d.ts +12 -1
  118. package/build-types/border-control/types.d.ts.map +1 -1
  119. package/build-types/box-control/all-input-control.d.ts +1 -1
  120. package/build-types/box-control/all-input-control.d.ts.map +1 -1
  121. package/build-types/box-control/axial-input-controls.d.ts +1 -1
  122. package/build-types/box-control/axial-input-controls.d.ts.map +1 -1
  123. package/build-types/box-control/index.d.ts +1 -1
  124. package/build-types/box-control/index.d.ts.map +1 -1
  125. package/build-types/box-control/input-controls.d.ts +1 -1
  126. package/build-types/box-control/input-controls.d.ts.map +1 -1
  127. package/build-types/box-control/stories/index.story.d.ts +42 -36
  128. package/build-types/box-control/stories/index.story.d.ts.map +1 -1
  129. package/build-types/box-control/styles/box-control-styles.d.ts +49 -23
  130. package/build-types/box-control/styles/box-control-styles.d.ts.map +1 -1
  131. package/build-types/box-control/types.d.ts +12 -12
  132. package/build-types/box-control/types.d.ts.map +1 -1
  133. package/build-types/box-control/utils.d.ts +2 -1
  134. package/build-types/box-control/utils.d.ts.map +1 -1
  135. package/build-types/button/deprecated.d.ts +3 -3
  136. package/build-types/button/index.d.ts.map +1 -1
  137. package/build-types/button/types.d.ts +7 -3
  138. package/build-types/button/types.d.ts.map +1 -1
  139. package/build-types/card/card/hook.d.ts +4 -4
  140. package/build-types/card/card-body/hook.d.ts +4 -4
  141. package/build-types/card/card-divider/hook.d.ts +4 -4
  142. package/build-types/card/card-footer/hook.d.ts +4 -4
  143. package/build-types/card/card-header/hook.d.ts +4 -4
  144. package/build-types/card/card-media/hook.d.ts +4 -4
  145. package/build-types/color-palette/styles.d.ts +2 -2
  146. package/build-types/color-picker/component.d.ts +1 -1
  147. package/build-types/color-picker/hsl-input.d.ts.map +1 -1
  148. package/build-types/color-picker/stories/index.story.d.ts +1 -1
  149. package/build-types/color-picker/styles.d.ts +3 -3
  150. package/build-types/custom-select-control-v2/index.d.ts.map +1 -1
  151. package/build-types/date-time/date/styles.d.ts +3 -3
  152. package/build-types/date-time/date-time/styles.d.ts +1 -1
  153. package/build-types/date-time/time/styles.d.ts +4 -4
  154. package/build-types/elevation/hook.d.ts +4 -4
  155. package/build-types/flex/flex/hook.d.ts +4 -4
  156. package/build-types/flex/flex-block/hook.d.ts +4 -4
  157. package/build-types/flex/flex-item/hook.d.ts +4 -4
  158. package/build-types/focal-point-picker/styles/focal-point-picker-style.d.ts +1 -1
  159. package/build-types/font-size-picker/styles.d.ts +1 -1
  160. package/build-types/grid/hook.d.ts +4 -4
  161. package/build-types/h-stack/hook.d.ts +4 -4
  162. package/build-types/heading/component.d.ts +1 -1
  163. package/build-types/heading/hook.d.ts +4 -4
  164. package/build-types/item-group/item/hook.d.ts +4 -4
  165. package/build-types/item-group/item-group/hook.d.ts +4 -4
  166. package/build-types/menu-item/index.d.ts +1 -1
  167. package/build-types/menu-item/stories/index.story.d.ts +4 -4
  168. package/build-types/navigation/styles/navigation-styles.d.ts +2 -2
  169. package/build-types/navigator/navigator-back-button/hook.d.ts +4 -4
  170. package/build-types/navigator/navigator-button/hook.d.ts +4 -4
  171. package/build-types/number-control/index.d.ts +1 -1
  172. package/build-types/number-control/stories/index.story.d.ts +1 -1
  173. package/build-types/palette-edit/styles.d.ts +3 -3
  174. package/build-types/range-control/index.d.ts +1 -1
  175. package/build-types/range-control/styles/range-control-styles.d.ts +1 -1
  176. package/build-types/resizable-box/index.d.ts +1 -1
  177. package/build-types/resizable-box/resize-tooltip/index.d.ts +1 -1
  178. package/build-types/resizable-box/stories/index.story.d.ts +2 -2
  179. package/build-types/scrollable/hook.d.ts +4 -4
  180. package/build-types/spacer/hook.d.ts +4 -4
  181. package/build-types/surface/hook.d.ts +4 -4
  182. package/build-types/text/hook.d.ts +4 -4
  183. package/build-types/theme/styles.d.ts.map +1 -1
  184. package/build-types/toggle-control/stories/index.story.d.ts +2 -2
  185. package/build-types/toggle-group-control/toggle-group-control/utils.d.ts.map +1 -1
  186. package/build-types/toggle-group-control/toggle-group-control-option-icon/component.d.ts +1 -1
  187. package/build-types/toolbar/toolbar-button/index.d.ts +3 -3
  188. package/build-types/tools-panel/tools-panel/hook.d.ts +4 -4
  189. package/build-types/tools-panel/tools-panel-header/hook.d.ts +4 -4
  190. package/build-types/tools-panel/tools-panel-item/hook.d.ts +4 -4
  191. package/build-types/tooltip/index.d.ts +1 -1
  192. package/build-types/tooltip/index.d.ts.map +1 -1
  193. package/build-types/tooltip/stories/index.story.d.ts +1 -1
  194. package/build-types/tooltip/stories/index.story.d.ts.map +1 -1
  195. package/build-types/tooltip/types.d.ts +1 -1
  196. package/build-types/tooltip/types.d.ts.map +1 -1
  197. package/build-types/truncate/hook.d.ts +4 -4
  198. package/build-types/unit-control/index.d.ts +1 -1
  199. package/build-types/unit-control/styles/unit-control-styles.d.ts +1 -1
  200. package/build-types/v-stack/hook.d.ts +4 -4
  201. package/build-types/v-stack/stories/index.story.d.ts +1 -1
  202. package/package.json +19 -19
  203. package/src/border-box-control/border-box-control/component.tsx +0 -1
  204. package/src/border-box-control/border-box-control/hook.ts +5 -1
  205. package/src/border-box-control/types.ts +6 -0
  206. package/src/border-control/border-control/component.tsx +4 -0
  207. package/src/border-control/border-control/hook.ts +22 -16
  208. package/src/border-control/border-control-dropdown/component.tsx +2 -1
  209. package/src/border-control/border-control-style-picker/component.tsx +31 -66
  210. package/src/border-control/styles.ts +0 -15
  211. package/src/border-control/types.ts +15 -1
  212. package/src/box-control/all-input-control.tsx +57 -34
  213. package/src/box-control/axial-input-controls.tsx +79 -69
  214. package/src/box-control/index.tsx +47 -54
  215. package/src/box-control/input-controls.tsx +114 -83
  216. package/src/box-control/styles/box-control-styles.ts +21 -61
  217. package/src/box-control/test/index.tsx +126 -18
  218. package/src/box-control/types.ts +10 -21
  219. package/src/box-control/utils.ts +43 -8
  220. package/src/button/README.md +1 -1
  221. package/src/button/index.tsx +21 -33
  222. package/src/button/test/index.tsx +122 -0
  223. package/src/button/types.ts +7 -3
  224. package/src/circular-option-picker/test/index.tsx +10 -16
  225. package/src/color-picker/hsl-input.tsx +56 -30
  226. package/src/color-picker/test/index.tsx +190 -16
  227. package/src/custom-select-control-v2/index.tsx +5 -2
  228. package/src/palette-edit/test/index.tsx +326 -10
  229. package/src/slot-fill/bubbles-virtually/use-slot-fills.ts +1 -1
  230. package/src/tabs/test/index.tsx +3 -1
  231. package/src/theme/styles.ts +3 -1
  232. package/src/toggle-group-control/test/__snapshots__/index.tsx.snap +6 -6
  233. package/src/toggle-group-control/test/index.tsx +73 -36
  234. package/src/toggle-group-control/toggle-group-control/utils.ts +8 -3
  235. package/src/tooltip/index.tsx +29 -29
  236. package/src/tooltip/test/index.tsx +32 -13
  237. package/src/tooltip/types.ts +1 -1
  238. package/tsconfig.tsbuildinfo +1 -1
  239. package/build/border-control/border-control-style-picker/hook.js +0 -41
  240. package/build/border-control/border-control-style-picker/hook.js.map +0 -1
  241. package/build/box-control/styles/box-control-visualizer-styles.js +0 -93
  242. package/build/box-control/styles/box-control-visualizer-styles.js.map +0 -1
  243. package/build/box-control/unit-control.js +0 -76
  244. package/build/box-control/unit-control.js.map +0 -1
  245. package/build-module/border-control/border-control-style-picker/hook.js +0 -32
  246. package/build-module/border-control/border-control-style-picker/hook.js.map +0 -1
  247. package/build-module/box-control/styles/box-control-visualizer-styles.js +0 -86
  248. package/build-module/box-control/styles/box-control-visualizer-styles.js.map +0 -1
  249. package/build-module/box-control/unit-control.js +0 -68
  250. package/build-module/box-control/unit-control.js.map +0 -1
  251. package/build-types/border-control/border-control-style-picker/hook.d.ts +0 -267
  252. package/build-types/border-control/border-control-style-picker/hook.d.ts.map +0 -1
  253. package/build-types/box-control/styles/box-control-visualizer-styles.d.ts +0 -46
  254. package/build-types/box-control/styles/box-control-visualizer-styles.d.ts.map +0 -1
  255. package/build-types/box-control/unit-control.d.ts +0 -4
  256. package/build-types/box-control/unit-control.d.ts.map +0 -1
  257. package/src/border-control/border-control-style-picker/hook.ts +0 -35
  258. package/src/box-control/styles/box-control-visualizer-styles.ts +0 -75
  259. package/src/box-control/unit-control.tsx +0 -74
@@ -3,6 +3,11 @@
3
3
  */
4
4
  import { colord } from 'colord';
5
5
 
6
+ /**
7
+ * WordPress dependencies
8
+ */
9
+ import { useState, useEffect, useMemo } from '@wordpress/element';
10
+
6
11
  /**
7
12
  * Internal dependencies
8
13
  */
@@ -10,7 +15,49 @@ import { InputWithSlider } from './input-with-slider';
10
15
  import type { HslInputProps } from './types';
11
16
 
12
17
  export const HslInput = ( { color, onChange, enableAlpha }: HslInputProps ) => {
13
- const { h, s, l, a } = color.toHsl();
18
+ const colorPropHSLA = useMemo( () => color.toHsl(), [ color ] );
19
+
20
+ const [ internalHSLA, setInternalHSLA ] = useState( { ...colorPropHSLA } );
21
+
22
+ const isInternalColorSameAsReceivedColor = color.isEqual(
23
+ colord( internalHSLA )
24
+ );
25
+
26
+ useEffect( () => {
27
+ if ( ! isInternalColorSameAsReceivedColor ) {
28
+ // Keep internal HSLA color up to date with the received color prop
29
+ setInternalHSLA( colorPropHSLA );
30
+ }
31
+ }, [ colorPropHSLA, isInternalColorSameAsReceivedColor ] );
32
+
33
+ // If the internal color is equal to the received color prop, we can use the
34
+ // HSLA values from the local state which, compared to the received color prop,
35
+ // retain more details about the actual H and S values that the user selected,
36
+ // and thus allow for better UX when interacting with the H and S sliders.
37
+ const colorValue = isInternalColorSameAsReceivedColor
38
+ ? internalHSLA
39
+ : colorPropHSLA;
40
+
41
+ const updateHSLAValue = (
42
+ partialNewValue: Partial< typeof colorPropHSLA >
43
+ ) => {
44
+ const nextOnChangeValue = colord( {
45
+ ...colorValue,
46
+ ...partialNewValue,
47
+ } );
48
+
49
+ // Fire `onChange` only if the resulting color is different from the
50
+ // current one.
51
+ // Otherwise, update the internal HSLA color to cause a re-render.
52
+ if ( ! color.isEqual( nextOnChangeValue ) ) {
53
+ onChange( nextOnChangeValue );
54
+ } else {
55
+ setInternalHSLA( ( prevHSLA ) => ( {
56
+ ...prevHSLA,
57
+ ...partialNewValue,
58
+ } ) );
59
+ }
60
+ };
14
61
 
15
62
  return (
16
63
  <>
@@ -19,9 +66,9 @@ export const HslInput = ( { color, onChange, enableAlpha }: HslInputProps ) => {
19
66
  max={ 359 }
20
67
  label="Hue"
21
68
  abbreviation="H"
22
- value={ h }
69
+ value={ colorValue.h }
23
70
  onChange={ ( nextH: number ) => {
24
- onChange( colord( { h: nextH, s, l, a } ) );
71
+ updateHSLAValue( { h: nextH } );
25
72
  } }
26
73
  />
27
74
  <InputWithSlider
@@ -29,16 +76,9 @@ export const HslInput = ( { color, onChange, enableAlpha }: HslInputProps ) => {
29
76
  max={ 100 }
30
77
  label="Saturation"
31
78
  abbreviation="S"
32
- value={ s }
79
+ value={ colorValue.s }
33
80
  onChange={ ( nextS: number ) => {
34
- onChange(
35
- colord( {
36
- h,
37
- s: nextS,
38
- l,
39
- a,
40
- } )
41
- );
81
+ updateHSLAValue( { s: nextS } );
42
82
  } }
43
83
  />
44
84
  <InputWithSlider
@@ -46,16 +86,9 @@ export const HslInput = ( { color, onChange, enableAlpha }: HslInputProps ) => {
46
86
  max={ 100 }
47
87
  label="Lightness"
48
88
  abbreviation="L"
49
- value={ l }
89
+ value={ colorValue.l }
50
90
  onChange={ ( nextL: number ) => {
51
- onChange(
52
- colord( {
53
- h,
54
- s,
55
- l: nextL,
56
- a,
57
- } )
58
- );
91
+ updateHSLAValue( { l: nextL } );
59
92
  } }
60
93
  />
61
94
  { enableAlpha && (
@@ -64,16 +97,9 @@ export const HslInput = ( { color, onChange, enableAlpha }: HslInputProps ) => {
64
97
  max={ 100 }
65
98
  label="Alpha"
66
99
  abbreviation="A"
67
- value={ Math.trunc( 100 * a ) }
100
+ value={ Math.trunc( 100 * colorValue.a ) }
68
101
  onChange={ ( nextA: number ) => {
69
- onChange(
70
- colord( {
71
- h,
72
- s,
73
- l,
74
- a: nextA / 100,
75
- } )
76
- );
102
+ updateHSLAValue( { a: nextA / 100 } );
77
103
  } }
78
104
  />
79
105
  ) }
@@ -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>