@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
@@ -240,7 +240,7 @@ exports[`ToggleGroupControl controlled should render correctly with icons 1`] =
240
240
  class="components-toggle-group-control emotion-8 emotion-9"
241
241
  data-wp-c16t="true"
242
242
  data-wp-component="ToggleGroupControl"
243
- id="toggle-group-control-as-radio-group-8"
243
+ id="toggle-group-control-as-radio-group-10"
244
244
  role="radiogroup"
245
245
  >
246
246
  <div
@@ -254,7 +254,7 @@ exports[`ToggleGroupControl controlled should render correctly with icons 1`] =
254
254
  data-value="uppercase"
255
255
  data-wp-c16t="true"
256
256
  data-wp-component="ToggleGroupControlOptionBase"
257
- id="toggle-group-control-as-radio-group-8-20"
257
+ id="toggle-group-control-as-radio-group-10-24"
258
258
  role="radio"
259
259
  type="button"
260
260
  value="uppercase"
@@ -292,7 +292,7 @@ exports[`ToggleGroupControl controlled should render correctly with icons 1`] =
292
292
  data-value="lowercase"
293
293
  data-wp-c16t="true"
294
294
  data-wp-component="ToggleGroupControlOptionBase"
295
- id="toggle-group-control-as-radio-group-8-21"
295
+ id="toggle-group-control-as-radio-group-10-25"
296
296
  role="radio"
297
297
  type="button"
298
298
  value="lowercase"
@@ -488,7 +488,7 @@ exports[`ToggleGroupControl controlled should render correctly with text options
488
488
  class="components-toggle-group-control emotion-8 emotion-9"
489
489
  data-wp-c16t="true"
490
490
  data-wp-component="ToggleGroupControl"
491
- id="toggle-group-control-as-radio-group-7"
491
+ id="toggle-group-control-as-radio-group-9"
492
492
  role="radiogroup"
493
493
  >
494
494
  <div
@@ -501,7 +501,7 @@ exports[`ToggleGroupControl controlled should render correctly with text options
501
501
  data-value="rigas"
502
502
  data-wp-c16t="true"
503
503
  data-wp-component="ToggleGroupControlOptionBase"
504
- id="toggle-group-control-as-radio-group-7-18"
504
+ id="toggle-group-control-as-radio-group-9-22"
505
505
  role="radio"
506
506
  type="button"
507
507
  value="rigas"
@@ -523,7 +523,7 @@ exports[`ToggleGroupControl controlled should render correctly with text options
523
523
  data-value="jack"
524
524
  data-wp-c16t="true"
525
525
  data-wp-component="ToggleGroupControlOptionBase"
526
- id="toggle-group-control-as-radio-group-7-19"
526
+ id="toggle-group-control-as-radio-group-9-23"
527
527
  role="radio"
528
528
  type="button"
529
529
  value="jack"
@@ -117,6 +117,22 @@ describe.each( [
117
117
  expect( container ).toMatchSnapshot();
118
118
  } );
119
119
  } );
120
+ it( 'should render with the correct option initially selected when `value` is defined', () => {
121
+ render(
122
+ <Component value="jack" label="Test Toggle Group Control">
123
+ { options }
124
+ </Component>
125
+ );
126
+ expect( screen.getByRole( 'radio', { name: 'R' } ) ).not.toBeChecked();
127
+ expect( screen.getByRole( 'radio', { name: 'J' } ) ).toBeChecked();
128
+ } );
129
+ it( 'should render without a selected option when `value` is `undefined`', () => {
130
+ render(
131
+ <Component label="Test Toggle Group Control">{ options }</Component>
132
+ );
133
+ expect( screen.getByRole( 'radio', { name: 'R' } ) ).not.toBeChecked();
134
+ expect( screen.getByRole( 'radio', { name: 'J' } ) ).not.toBeChecked();
135
+ } );
120
136
  it( 'should call onChange with proper value', async () => {
121
137
  const mockOnChange = jest.fn();
122
138
 
@@ -193,7 +209,7 @@ describe.each( [
193
209
  } );
194
210
 
195
211
  if ( mode === 'controlled' ) {
196
- it( 'should reset values correctly', async () => {
212
+ it( 'should reset values correctly when default value is undefined', async () => {
197
213
  render(
198
214
  <Component label="Test Toggle Group Control">
199
215
  { options }
@@ -208,56 +224,77 @@ describe.each( [
208
224
  expect( jackOption ).not.toBeChecked();
209
225
  expect( rigasOption ).toBeChecked();
210
226
 
211
- await press.ArrowRight();
212
-
213
- expect( rigasOption ).not.toBeChecked();
214
- expect( jackOption ).toBeChecked();
215
-
216
227
  await click( screen.getByRole( 'button', { name: 'Reset' } ) );
217
228
 
218
229
  expect( rigasOption ).not.toBeChecked();
219
230
  expect( jackOption ).not.toBeChecked();
220
231
  } );
221
232
 
222
- it( 'should update correctly when triggered by external updates', async () => {
233
+ it( 'should reset values correctly when default value is defined', async () => {
223
234
  render(
224
- <Component
225
- value="rigas"
226
- label="Test Toggle Group Control"
227
- extraButtonOptions={ [
228
- { name: 'Rigas', value: 'rigas' },
229
- { name: 'Jack', value: 'jack' },
230
- ] }
231
- >
235
+ <Component label="Test Toggle Group Control" value="rigas">
232
236
  { options }
233
237
  </Component>
234
238
  );
235
239
 
236
- expect( screen.getByRole( 'radio', { name: 'R' } ) ).toBeChecked();
237
- expect(
238
- screen.getByRole( 'radio', { name: 'J' } )
239
- ).not.toBeChecked();
240
-
241
- await click( screen.getByRole( 'button', { name: 'Jack' } ) );
242
- expect( screen.getByRole( 'radio', { name: 'J' } ) ).toBeChecked();
243
- expect(
244
- screen.getByRole( 'radio', { name: 'R' } )
245
- ).not.toBeChecked();
240
+ const rigasOption = screen.getByRole( 'radio', {
241
+ name: 'R',
242
+ } );
243
+ const jackOption = screen.getByRole( 'radio', {
244
+ name: 'J',
245
+ } );
246
246
 
247
- await click( screen.getByRole( 'button', { name: 'Rigas' } ) );
248
- expect( screen.getByRole( 'radio', { name: 'R' } ) ).toBeChecked();
249
- expect(
250
- screen.getByRole( 'radio', { name: 'J' } )
251
- ).not.toBeChecked();
247
+ expect( rigasOption ).toBeChecked();
248
+ expect( jackOption ).not.toBeChecked();
252
249
 
253
250
  await click( screen.getByRole( 'button', { name: 'Reset' } ) );
254
- expect(
255
- screen.getByRole( 'radio', { name: 'R' } )
256
- ).not.toBeChecked();
257
- expect(
258
- screen.getByRole( 'radio', { name: 'J' } )
259
- ).not.toBeChecked();
251
+
252
+ expect( rigasOption ).not.toBeChecked();
253
+ expect( jackOption ).not.toBeChecked();
260
254
  } );
255
+
256
+ describe.each( [
257
+ [ 'undefined', undefined ],
258
+ [ 'defined', 'rigas' ],
259
+ ] )(
260
+ 'should update correctly when triggered by external updates',
261
+ ( defaultValueType, defaultValue ) => {
262
+ it( `when default value is ${ defaultValueType }`, async () => {
263
+ render(
264
+ <Component
265
+ value={ defaultValue }
266
+ label="Test Toggle Group Control"
267
+ extraButtonOptions={ [
268
+ { name: 'Rigas', value: 'rigas' },
269
+ { name: 'Jack', value: 'jack' },
270
+ ] }
271
+ >
272
+ { options }
273
+ </Component>
274
+ );
275
+
276
+ await click(
277
+ screen.getByRole( 'button', { name: 'Jack' } )
278
+ );
279
+ expect(
280
+ screen.getByRole( 'radio', { name: 'J' } )
281
+ ).toBeChecked();
282
+ expect(
283
+ screen.getByRole( 'radio', { name: 'R' } )
284
+ ).not.toBeChecked();
285
+
286
+ await click(
287
+ screen.getByRole( 'button', { name: 'Rigas' } )
288
+ );
289
+ expect(
290
+ screen.getByRole( 'radio', { name: 'R' } )
291
+ ).toBeChecked();
292
+ expect(
293
+ screen.getByRole( 'radio', { name: 'J' } )
294
+ ).not.toBeChecked();
295
+ } );
296
+ }
297
+ );
261
298
  }
262
299
 
263
300
  describe( 'isDeselectable', () => {
@@ -21,16 +21,21 @@ type ValueProp = ToggleGroupControlProps[ 'value' ];
21
21
  export function useComputeControlledOrUncontrolledValue(
22
22
  valueProp: ValueProp
23
23
  ): { value: ValueProp; defaultValue: ValueProp } {
24
+ const isInitialRender = useRef( true );
24
25
  const prevValueProp = usePrevious( valueProp );
25
26
  const prevIsControlled = useRef( false );
26
27
 
28
+ useEffect( () => {
29
+ if ( isInitialRender.current ) {
30
+ isInitialRender.current = false;
31
+ }
32
+ }, [] );
33
+
27
34
  // Assume the component is being used in controlled mode on the first re-render
28
35
  // that has a different `valueProp` from the previous render.
29
36
  const isControlled =
30
37
  prevIsControlled.current ||
31
- ( prevValueProp !== undefined &&
32
- valueProp !== undefined &&
33
- prevValueProp !== valueProp );
38
+ ( ! isInitialRender.current && prevValueProp !== valueProp );
34
39
  useEffect( () => {
35
40
  prevIsControlled.current = isControlled;
36
41
  }, [ isControlled ] );
@@ -16,6 +16,10 @@ const MyTooltip = () => (
16
16
  );
17
17
  ```
18
18
 
19
+ ### Nested tooltips
20
+
21
+ In case one or more `Tooltip` components are rendered inside another `Tooltip` component, only the tooltip associated to the outermost `Tooltip` component will be rendered in the browser and shown to the user appropriately. The rest of the nested `Tooltip` components will simply no-op and pass-through their anchor.
22
+
19
23
  ## Props
20
24
 
21
25
  The component accepts the following props:
@@ -8,22 +8,41 @@ import * as Ariakit from '@ariakit/react';
8
8
  * WordPress dependencies
9
9
  */
10
10
  import { useInstanceId } from '@wordpress/compose';
11
- import { Children } from '@wordpress/element';
11
+ import {
12
+ Children,
13
+ useContext,
14
+ createContext,
15
+ forwardRef,
16
+ } from '@wordpress/element';
12
17
  import deprecated from '@wordpress/deprecated';
13
18
 
14
19
  /**
15
20
  * Internal dependencies
16
21
  */
17
- import type { TooltipProps } from './types';
22
+ import type {
23
+ TooltipProps,
24
+ TooltipInternalContext as TooltipInternalContextType,
25
+ } from './types';
18
26
  import Shortcut from '../shortcut';
19
27
  import { positionToPlacement } from '../popover/utils';
20
28
 
29
+ const TooltipInternalContext = createContext< TooltipInternalContextType >( {
30
+ isNestedInTooltip: false,
31
+ } );
32
+
21
33
  /**
22
34
  * Time over anchor to wait before showing tooltip
23
35
  */
24
36
  export const TOOLTIP_DELAY = 700;
25
37
 
26
- function Tooltip( props: TooltipProps ) {
38
+ const CONTEXT_VALUE = {
39
+ isNestedInTooltip: true,
40
+ };
41
+
42
+ function UnforwardedTooltip(
43
+ props: TooltipProps,
44
+ ref: React.ForwardedRef< any >
45
+ ) {
27
46
  const {
28
47
  children,
29
48
  delay = TOOLTIP_DELAY,
@@ -32,8 +51,12 @@ function Tooltip( props: TooltipProps ) {
32
51
  position,
33
52
  shortcut,
34
53
  text,
54
+
55
+ ...restProps
35
56
  } = props;
36
57
 
58
+ const { isNestedInTooltip } = useContext( TooltipInternalContext );
59
+
37
60
  const baseId = useInstanceId( Tooltip, 'tooltip' );
38
61
  const describedById = text || shortcut ? baseId : undefined;
39
62
 
@@ -43,7 +66,7 @@ function Tooltip( props: TooltipProps ) {
43
66
  if ( 'development' === process.env.NODE_ENV ) {
44
67
  // eslint-disable-next-line no-console
45
68
  console.error(
46
- 'Tooltip should be called with only a single child element.'
69
+ 'wp-components.Tooltip should be called with only a single child element.'
47
70
  );
48
71
  }
49
72
  }
@@ -64,24 +87,37 @@ function Tooltip( props: TooltipProps ) {
64
87
  }
65
88
  computedPlacement = computedPlacement || 'bottom';
66
89
 
67
- const tooltipStore = Ariakit.useTooltipStore( {
90
+ // Removing the `Ariakit` namespace from the hook name allows ESLint to
91
+ // properly identify the hook, and apply the correct linting rules.
92
+ const useAriakitTooltipStore = Ariakit.useTooltipStore;
93
+ const tooltipStore = useAriakitTooltipStore( {
68
94
  placement: computedPlacement,
69
95
  showTimeout: delay,
70
96
  } );
71
97
 
98
+ if ( isNestedInTooltip ) {
99
+ return isOnlyChild ? (
100
+ <Ariakit.Role { ...restProps } render={ children } />
101
+ ) : (
102
+ children
103
+ );
104
+ }
105
+
72
106
  return (
73
- <>
107
+ <TooltipInternalContext.Provider value={ CONTEXT_VALUE }>
74
108
  <Ariakit.TooltipAnchor
75
109
  onClick={ hideOnClick ? tooltipStore.hide : undefined }
76
110
  store={ tooltipStore }
77
111
  render={ isOnlyChild ? children : undefined }
112
+ ref={ ref }
78
113
  >
79
114
  { isOnlyChild ? undefined : children }
80
115
  </Ariakit.TooltipAnchor>
81
116
  { isOnlyChild && ( text || shortcut ) && (
82
117
  <Ariakit.Tooltip
83
- unmountOnHide
118
+ { ...restProps }
84
119
  className="components-tooltip"
120
+ unmountOnHide
85
121
  gutter={ 4 }
86
122
  id={ describedById }
87
123
  overflowPadding={ 0.5 }
@@ -98,8 +134,10 @@ function Tooltip( props: TooltipProps ) {
98
134
  ) }
99
135
  </Ariakit.Tooltip>
100
136
  ) }
101
- </>
137
+ </TooltipInternalContext.Provider>
102
138
  );
103
139
  }
104
140
 
141
+ export const Tooltip = forwardRef( UnforwardedTooltip );
142
+
105
143
  export default Tooltip;
@@ -30,7 +30,7 @@ const meta: Meta< typeof Tooltip > = {
30
30
  'bottom right',
31
31
  ],
32
32
  },
33
- shortcut: { control: { type: 'text' } },
33
+ shortcut: { control: { type: 'object' } },
34
34
  },
35
35
  parameters: {
36
36
  controls: { expanded: true },
@@ -57,3 +57,20 @@ KeyboardShortcut.args = {
57
57
  ariaLabel: shortcutAriaLabel.primaryShift( ',' ),
58
58
  },
59
59
  };
60
+
61
+ /**
62
+ * In case one or more `Tooltip` components are rendered inside another
63
+ * `Tooltip` component, only the tooltip associated to the outermost `Tooltip`
64
+ * component will be rendered in the browser and shown to the user
65
+ * appropriately. The rest of the nested `Tooltip` components will simply no-op
66
+ * and pass-through their anchor.
67
+ */
68
+ export const Nested: StoryFn< typeof Tooltip > = Template.bind( {} );
69
+ Nested.args = {
70
+ children: (
71
+ <Tooltip text="Nested tooltip text (that will never show)">
72
+ <Button variant="primary">Tooltip Anchor</Button>
73
+ </Tooltip>
74
+ ),
75
+ text: 'Outer tooltip text',
76
+ };
@@ -12,12 +12,11 @@ import { shortcutAriaLabel } from '@wordpress/keycodes';
12
12
  /**
13
13
  * Internal dependencies
14
14
  */
15
- import Button from '../../button';
16
15
  import Modal from '../../modal';
17
16
  import Tooltip, { TOOLTIP_DELAY } from '..';
18
17
 
19
18
  const props = {
20
- children: <Button>Tooltip anchor</Button>,
19
+ children: <button>Tooltip anchor</button>,
21
20
  text: 'tooltip text',
22
21
  };
23
22
 
@@ -56,8 +55,8 @@ describe( 'Tooltip', () => {
56
55
  render(
57
56
  // @ts-expect-error Tooltip cannot have more than one child element
58
57
  <Tooltip { ...props }>
59
- <Button>First button</Button>
60
- <Button>Second button</Button>
58
+ <button>First button</button>
59
+ <button>Second button</button>
61
60
  </Tooltip>
62
61
  );
63
62
 
@@ -102,6 +101,17 @@ describe( 'Tooltip', () => {
102
101
  screen.queryByRole( 'button', { description: 'tooltip text' } )
103
102
  ).not.toBeInTheDocument();
104
103
  } );
104
+
105
+ it( 'should not leak Tooltip props to the tooltip anchor', () => {
106
+ render(
107
+ <Tooltip data-foo>
108
+ <button>Anchor</button>
109
+ </Tooltip>
110
+ );
111
+ expect(
112
+ screen.getByRole( 'button', { name: 'Anchor' } )
113
+ ).not.toHaveAttribute( 'data-foo' );
114
+ } );
105
115
  } );
106
116
 
107
117
  describe( 'keyboard focus', () => {
@@ -142,9 +152,7 @@ describe( 'Tooltip', () => {
142
152
  render(
143
153
  <>
144
154
  <Tooltip { ...props }>
145
- <Button disabled __experimentalIsFocusable>
146
- Tooltip anchor
147
- </Button>
155
+ <button aria-disabled="true">Tooltip anchor</button>
148
156
  </Tooltip>
149
157
  <button>Focus me</button>
150
158
  </>
@@ -196,9 +204,7 @@ describe( 'Tooltip', () => {
196
204
  render(
197
205
  <>
198
206
  <Tooltip { ...props }>
199
- <Button disabled __experimentalIsFocusable>
200
- Tooltip anchor
201
- </Button>
207
+ <button aria-disabled="true">Tooltip anchor</button>
202
208
  </Tooltip>
203
209
  <button>Focus me</button>
204
210
  </>
@@ -310,12 +316,12 @@ describe( 'Tooltip', () => {
310
316
 
311
317
  render(
312
318
  <Tooltip { ...props }>
313
- <Button
319
+ <button
314
320
  onMouseEnter={ onMouseEnterMock }
315
321
  onMouseLeave={ onMouseLeaveMock }
316
322
  >
317
323
  Tooltip anchor
318
- </Button>
324
+ </button>
319
325
  </Tooltip>
320
326
  );
321
327
 
@@ -436,4 +442,63 @@ describe( 'Tooltip', () => {
436
442
  await waitExpectTooltipToHide();
437
443
  } );
438
444
  } );
445
+
446
+ describe( 'nested', () => {
447
+ it( 'should render the outer tooltip and ignore nested tooltips', async () => {
448
+ render(
449
+ <Tooltip text="Outer tooltip">
450
+ <Tooltip text="Middle tooltip">
451
+ <Tooltip text="Inner tooltip">
452
+ <button>Tooltip anchor</button>
453
+ </Tooltip>
454
+ </Tooltip>
455
+ </Tooltip>
456
+ );
457
+
458
+ // Hover the anchor. Only the outer tooltip should show.
459
+ await hover(
460
+ screen.getByRole( 'button', {
461
+ name: 'Tooltip anchor',
462
+ } )
463
+ );
464
+
465
+ await waitFor( () =>
466
+ expect(
467
+ screen.getByRole( 'tooltip', { name: 'Outer tooltip' } )
468
+ ).toBeVisible()
469
+ );
470
+ expect(
471
+ screen.queryByRole( 'tooltip', { name: 'Middle tooltip' } )
472
+ ).not.toBeInTheDocument();
473
+ expect(
474
+ screen.queryByRole( 'tooltip', { name: 'Inner tooltip' } )
475
+ ).not.toBeInTheDocument();
476
+ expect(
477
+ screen.getByRole( 'button', {
478
+ description: 'Outer tooltip',
479
+ } )
480
+ ).toBeVisible();
481
+
482
+ // Hover outside of the anchor, tooltip should hide
483
+ await hoverOutside();
484
+ await waitFor( () =>
485
+ expect(
486
+ screen.queryByRole( 'tooltip', { name: 'Outer tooltip' } )
487
+ ).not.toBeInTheDocument()
488
+ );
489
+ } );
490
+
491
+ it( 'should not leak Tooltip component classname to the anchor element', () => {
492
+ render(
493
+ <Tooltip>
494
+ <Tooltip>
495
+ <button>Anchor</button>
496
+ </Tooltip>
497
+ </Tooltip>
498
+ );
499
+ expect(
500
+ screen.getByRole( 'button', { name: 'Anchor' } )
501
+ ).not.toHaveClass( 'components-tooltip' );
502
+ } );
503
+ } );
439
504
  } );
@@ -59,3 +59,7 @@ export type TooltipProps = {
59
59
  */
60
60
  text?: string;
61
61
  };
62
+
63
+ export type TooltipInternalContext = {
64
+ isNestedInTooltip: boolean;
65
+ };