@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
@@ -135,7 +135,7 @@ An accessible description for the button.
135
135
 
136
136
  #### `disabled`: `boolean`
137
137
 
138
- Whether the button is disabled. If `true`, this will force a `button` element to be rendered.
138
+ Whether the button is disabled. If `true`, this will force a `button` element to be rendered, even when an `href` is given.
139
139
 
140
140
  - Required: No
141
141
 
@@ -195,9 +195,9 @@ export function UnforwardedButton(
195
195
  const shouldShowTooltip =
196
196
  ! trulyDisabled &&
197
197
  // An explicit tooltip is passed or...
198
- ( ( showTooltip && label ) ||
198
+ ( ( showTooltip && !! label ) ||
199
199
  // There's a shortcut or...
200
- shortcut ||
200
+ !! shortcut ||
201
201
  // There's a label and...
202
202
  ( !! label &&
203
203
  // The children are empty and...
@@ -249,40 +249,28 @@ export function UnforwardedButton(
249
249
  </button>
250
250
  );
251
251
 
252
- // Convert legacy `position` values to be used with the new `placement` prop
253
- let computedPlacement;
254
- // if `tooltipPosition` is defined, compute value to `placement`
255
- if ( tooltipPosition !== undefined ) {
256
- computedPlacement = positionToPlacement( tooltipPosition );
257
- }
258
-
259
- if ( ! shouldShowTooltip ) {
260
- return (
261
- <>
262
- { element }
263
- { describedBy && (
264
- <VisuallyHidden>
265
- <span id={ descriptionId }>{ describedBy }</span>
266
- </VisuallyHidden>
267
- ) }
268
- </>
269
- );
270
- }
271
-
272
- return (
273
- <>
274
- <Tooltip
275
- text={
252
+ // In order to avoid some React reconciliation issues, we are always rendering
253
+ // the `Tooltip` component even when `shouldShowTooltip` is `false`.
254
+ // In order to make sure that the tooltip doesn't show when it shouldn't,
255
+ // we don't pass the props to the `Tooltip` component.
256
+ const tooltipProps = shouldShowTooltip
257
+ ? {
258
+ text:
276
259
  ( children as string | ReactElement[] )?.length &&
277
260
  describedBy
278
261
  ? describedBy
279
- : label
280
- }
281
- shortcut={ shortcut }
282
- placement={ computedPlacement }
283
- >
284
- { element }
285
- </Tooltip>
262
+ : label,
263
+ shortcut,
264
+ placement:
265
+ tooltipPosition &&
266
+ // Convert legacy `position` values to be used with the new `placement` prop
267
+ positionToPlacement( tooltipPosition ),
268
+ }
269
+ : {};
270
+
271
+ return (
272
+ <>
273
+ <Tooltip { ...tooltipProps }>{ element }</Tooltip>
286
274
  { describedBy && (
287
275
  <VisuallyHidden>
288
276
  <span id={ descriptionId }>{ describedBy }</span>
@@ -112,6 +112,128 @@ describe( 'Button', () => {
112
112
  );
113
113
  } );
114
114
 
115
+ it( 'should render correctly as a tooltip anchor', async () => {
116
+ const user = userEvent.setup();
117
+
118
+ render(
119
+ <>
120
+ <Tooltip text="Tooltip text">
121
+ <Button icon={ plusCircle } label="Tooltip anchor" />
122
+ </Tooltip>
123
+ <Button>Focus me</Button>
124
+ </>
125
+ );
126
+
127
+ const anchor = screen.getByRole( 'button', {
128
+ name: 'Tooltip anchor',
129
+ } );
130
+
131
+ await user.tab();
132
+
133
+ expect( anchor ).toHaveFocus();
134
+
135
+ const tooltip = await screen.findByRole( 'tooltip', {
136
+ name: 'Tooltip text',
137
+ } );
138
+
139
+ expect( tooltip ).toBeVisible();
140
+
141
+ await user.tab();
142
+
143
+ expect(
144
+ screen.getByRole( 'button', { name: 'Focus me' } )
145
+ ).toHaveFocus();
146
+
147
+ expect(
148
+ screen.queryByRole( 'tooltip', {
149
+ name: 'Tooltip text',
150
+ } )
151
+ ).not.toBeInTheDocument();
152
+ } );
153
+
154
+ it( 'should render correctly as a tooltip anchor, ignoring its internal tooltip in favour of the external tooltip', async () => {
155
+ const user = userEvent.setup();
156
+
157
+ render(
158
+ <>
159
+ <Tooltip text="Tooltip text">
160
+ <Button icon={ plusCircle } label="Button label" />
161
+ </Tooltip>
162
+ <Button>Focus me</Button>
163
+ </>
164
+ );
165
+
166
+ const anchor = screen.getByRole( 'button', {
167
+ name: 'Button label',
168
+ } );
169
+
170
+ await user.tab();
171
+
172
+ expect( anchor ).toHaveFocus();
173
+
174
+ const tooltip = await screen.findByRole( 'tooltip', {
175
+ name: 'Tooltip text',
176
+ } );
177
+
178
+ expect( tooltip ).toBeVisible();
179
+ // Check that the tooltip that would be normally rendered internally by
180
+ // the `Button` component is ignored, because of an outer tooltip.
181
+ expect(
182
+ screen.queryByRole( 'tooltip', {
183
+ name: 'Button label',
184
+ } )
185
+ ).not.toBeInTheDocument();
186
+
187
+ await user.tab();
188
+
189
+ expect(
190
+ screen.getByRole( 'button', { name: 'Focus me' } )
191
+ ).toHaveFocus();
192
+
193
+ expect(
194
+ screen.queryByRole( 'tooltip', {
195
+ name: 'Tooltip text',
196
+ } )
197
+ ).not.toBeInTheDocument();
198
+ } );
199
+
200
+ it( 'should not trash the rendered HTML elements when toggling between showing and not showing a tooltip', async () => {
201
+ const user = userEvent.setup();
202
+
203
+ const { rerender } = render(
204
+ <Button label="Button label">Test button</Button>
205
+ );
206
+
207
+ const button = screen.getByRole( 'button', {
208
+ name: 'Button label',
209
+ } );
210
+
211
+ expect( button ).toBeVisible();
212
+
213
+ await user.tab();
214
+
215
+ expect( button ).toHaveFocus();
216
+
217
+ // Re-render the button, but this time change the settings so that it
218
+ // shows a tooltip.
219
+ rerender(
220
+ <Button label="Button label" showTooltip>
221
+ Test button
222
+ </Button>
223
+ );
224
+
225
+ // The same button element that we referenced before should still be
226
+ // in the document and have focus.
227
+ expect( button ).toHaveFocus();
228
+
229
+ // Re-render the button, but stop showing a tooltip.
230
+ rerender( <Button label="Button label">Test button</Button> );
231
+
232
+ // The same button element that we referenced before should still be
233
+ // in the document and have focus.
234
+ expect( button ).toHaveFocus();
235
+ } );
236
+
115
237
  it( 'should add a disabled prop to the button', () => {
116
238
  render( <Button disabled /> );
117
239
 
@@ -115,7 +115,9 @@ type BaseButtonProps = {
115
115
  */
116
116
  variant?: 'primary' | 'secondary' | 'tertiary' | 'link';
117
117
  /**
118
- * Whether this is focusable.
118
+ * Whether to keep the button focusable when disabled.
119
+ *
120
+ * @default false
119
121
  */
120
122
  __experimentalIsFocusable?: boolean;
121
123
  };
@@ -123,7 +125,8 @@ type BaseButtonProps = {
123
125
  type _ButtonProps = {
124
126
  /**
125
127
  * Whether the button is disabled.
126
- * If `true`, this will force a `button` element to be rendered.
128
+ *
129
+ * If `true`, this will force a `button` element to be rendered, even when an `href` is given.
127
130
  */
128
131
  disabled?: boolean;
129
132
  };
@@ -131,7 +134,8 @@ type _ButtonProps = {
131
134
  type AnchorProps = {
132
135
  /**
133
136
  * Whether the button is disabled.
134
- * If `true`, this will force a `button` element to be rendered.
137
+ *
138
+ * If `true`, this will force a `button` element to be rendered, even when an `href` is given.
135
139
  */
136
140
  disabled?: false;
137
141
  /**
@@ -2,7 +2,7 @@
2
2
  * External dependencies
3
3
  */
4
4
  import { render, screen } from '@testing-library/react';
5
- import userEvent from '@testing-library/user-event';
5
+ import { press } from '@ariakit/test';
6
6
 
7
7
  /**
8
8
  * Internal dependencies
@@ -71,8 +71,6 @@ describe( 'CircularOptionPicker', () => {
71
71
 
72
72
  describe( 'when `loop` is not set', () => {
73
73
  it( 'should loop', async () => {
74
- const user = userEvent.setup();
75
-
76
74
  render(
77
75
  <CircularOptionPicker
78
76
  { ...DEFAULT_PROPS }
@@ -80,19 +78,17 @@ describe( 'CircularOptionPicker', () => {
80
78
  />
81
79
  );
82
80
 
83
- await user.tab();
81
+ await press.Tab();
84
82
  expect( getOption( 'Option One' ) ).toHaveFocus();
85
- await user.keyboard( '[ArrowRight]' );
83
+ await press.ArrowRight();
86
84
  expect( getOption( 'Option Two' ) ).toHaveFocus();
87
- await user.keyboard( '[ArrowRight]' );
85
+ await press.ArrowRight();
88
86
  expect( getOption( 'Option One' ) ).toHaveFocus();
89
87
  } );
90
88
  } );
91
89
 
92
90
  describe( 'when `loop` is true', () => {
93
91
  it( 'should loop', async () => {
94
- const user = userEvent.setup();
95
-
96
92
  render(
97
93
  <CircularOptionPicker
98
94
  { ...DEFAULT_PROPS }
@@ -101,19 +97,17 @@ describe( 'CircularOptionPicker', () => {
101
97
  />
102
98
  );
103
99
 
104
- await user.tab();
100
+ await press.Tab();
105
101
  expect( getOption( 'Option One' ) ).toHaveFocus();
106
- await user.keyboard( '[ArrowRight]' );
102
+ await press.ArrowRight();
107
103
  expect( getOption( 'Option Two' ) ).toHaveFocus();
108
- await user.keyboard( '[ArrowRight]' );
104
+ await press.ArrowRight();
109
105
  expect( getOption( 'Option One' ) ).toHaveFocus();
110
106
  } );
111
107
  } );
112
108
 
113
109
  describe( 'when `loop` is false', () => {
114
110
  it( 'should not loop', async () => {
115
- const user = userEvent.setup();
116
-
117
111
  render(
118
112
  <CircularOptionPicker
119
113
  { ...DEFAULT_PROPS }
@@ -122,11 +116,11 @@ describe( 'CircularOptionPicker', () => {
122
116
  />
123
117
  );
124
118
 
125
- await user.tab();
119
+ await press.Tab();
126
120
  expect( getOption( 'Option One' ) ).toHaveFocus();
127
- await user.keyboard( '[ArrowRight]' );
121
+ await press.ArrowRight();
128
122
  expect( getOption( 'Option Two' ) ).toHaveFocus();
129
- await user.keyboard( '[ArrowRight]' );
123
+ await press.ArrowRight();
130
124
  expect( getOption( 'Option Two' ) ).toHaveFocus();
131
125
  } );
132
126
  } );
@@ -33,7 +33,7 @@ let scrollPosition = 0;
33
33
  let customIndicatorWidth = 0;
34
34
 
35
35
  function ColorPalette( {
36
- enableCustomColor = true,
36
+ enableCustomColor = false,
37
37
  setColor,
38
38
  activeColor,
39
39
  isGradientColor,
@@ -62,24 +62,35 @@ function ColorPalette( {
62
62
  const scale = useRef( new Animated.Value( 1 ) ).current;
63
63
  const opacity = useRef( new Animated.Value( 1 ) ).current;
64
64
 
65
- const defaultColors = [
65
+ const mergedColors = [
66
66
  ...new Set(
67
67
  ( defaultSettings.colors ?? [] ).map( ( { color } ) => color )
68
68
  ),
69
69
  ];
70
- const mergedColors = [
70
+ const mergedGradients = [
71
+ ...new Set(
72
+ ( defaultSettings.gradients ?? [] ).map(
73
+ ( { gradient } ) => gradient
74
+ )
75
+ ),
76
+ ];
77
+ const allAvailableColors = [
71
78
  ...new Set(
72
79
  ( defaultSettings.allColors ?? [] ).map( ( { color } ) => color )
73
80
  ),
74
81
  ];
75
- const defaultGradientColors = [
82
+ const allAvailableGradients = [
76
83
  ...new Set(
77
- ( defaultSettings.gradients ?? [] ).map(
84
+ ( defaultSettings.allGradients ?? [] ).map(
78
85
  ( { gradient } ) => gradient
79
86
  )
80
87
  ),
81
88
  ];
82
- const colors = isGradientSegment ? defaultGradientColors : defaultColors;
89
+
90
+ const colors = isGradientSegment ? mergedGradients : mergedColors;
91
+ const allColors = isGradientSegment
92
+ ? allAvailableGradients
93
+ : allAvailableColors;
83
94
 
84
95
  const customIndicatorColor = isGradientSegment
85
96
  ? activeColor
@@ -110,7 +121,7 @@ function ColorPalette( {
110
121
 
111
122
  function isSelectedCustom() {
112
123
  const isWithinColors =
113
- activeColor && mergedColors && mergedColors.includes( activeColor );
124
+ activeColor && allColors?.includes( activeColor );
114
125
  if ( enableCustomColor && activeColor ) {
115
126
  if ( isGradientSegment ) {
116
127
  return isGradientColor && ! isWithinColors;
@@ -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
  ) }