@wordpress/components 23.9.0 → 24.0.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 (279) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/CONTRIBUTING.md +65 -1
  3. package/README.md +1 -3
  4. package/build/autocomplete/autocompleter-ui.js +0 -2
  5. package/build/autocomplete/autocompleter-ui.js.map +1 -1
  6. package/build/autocomplete/index.js +2 -0
  7. package/build/autocomplete/index.js.map +1 -1
  8. package/build/button/index.js +2 -0
  9. package/build/button/index.js.map +1 -1
  10. package/build/card/card-media/component.js +2 -1
  11. package/build/card/card-media/component.js.map +1 -1
  12. package/build/combobox-control/index.js +7 -5
  13. package/build/combobox-control/index.js.map +1 -1
  14. package/build/combobox-control/styles.js +3 -3
  15. package/build/combobox-control/styles.js.map +1 -1
  16. package/build/dimension-control/index.js +1 -1
  17. package/build/dimension-control/index.js.map +1 -1
  18. package/build/draggable/index.js +2 -7
  19. package/build/draggable/index.js.map +1 -1
  20. package/build/form-token-field/index.js +5 -3
  21. package/build/form-token-field/index.js.map +1 -1
  22. package/build/form-token-field/styles.js +3 -3
  23. package/build/form-token-field/styles.js.map +1 -1
  24. package/build/mobile/global-styles-context/index.native.js +13 -1
  25. package/build/mobile/global-styles-context/index.native.js.map +1 -1
  26. package/build/mobile/link-picker/link-picker-results.native.js +3 -1
  27. package/build/mobile/link-picker/link-picker-results.native.js.map +1 -1
  28. package/build/modal/index.js +2 -1
  29. package/build/modal/index.js.map +1 -1
  30. package/build/navigable-container/container.js +39 -19
  31. package/build/navigable-container/container.js.map +1 -1
  32. package/build/navigable-container/index.js.map +1 -1
  33. package/build/navigable-container/menu.js +37 -5
  34. package/build/navigable-container/menu.js.map +1 -1
  35. package/build/navigable-container/tabbable.js +45 -4
  36. package/build/navigable-container/tabbable.js.map +1 -1
  37. package/build/navigable-container/types.js +6 -0
  38. package/build/navigable-container/types.js.map +1 -0
  39. package/build/palette-edit/index.js +34 -12
  40. package/build/palette-edit/index.js.map +1 -1
  41. package/build/sandbox/index.native.js +6 -2
  42. package/build/sandbox/index.native.js.map +1 -1
  43. package/build/slot-fill/bubbles-virtually/fill.js +2 -1
  44. package/build/slot-fill/bubbles-virtually/fill.js.map +1 -1
  45. package/build/slot-fill/bubbles-virtually/slot-fill-provider.js +45 -35
  46. package/build/slot-fill/bubbles-virtually/slot-fill-provider.js.map +1 -1
  47. package/build/slot-fill/bubbles-virtually/use-slot.js +11 -26
  48. package/build/slot-fill/bubbles-virtually/use-slot.js.map +1 -1
  49. package/build/slot-fill/fill.js +7 -31
  50. package/build/slot-fill/fill.js.map +1 -1
  51. package/build/slot-fill/index.js.map +1 -1
  52. package/build/slot-fill/provider.js +0 -6
  53. package/build/slot-fill/provider.js.map +1 -1
  54. package/build/slot-fill/slot.js +0 -5
  55. package/build/slot-fill/slot.js.map +1 -1
  56. package/build/tab-panel/index.js.map +1 -1
  57. package/build/theme/color-algorithms.js +1 -1
  58. package/build/theme/color-algorithms.js.map +1 -1
  59. package/build/toolbar/toolbar-button/index.js +1 -2
  60. package/build/toolbar/toolbar-button/index.js.map +1 -1
  61. package/build/toolbar/toolbar-item/index.js +4 -2
  62. package/build/toolbar/toolbar-item/index.js.map +1 -1
  63. package/build/utils/colors-values.js +3 -3
  64. package/build/utils/colors-values.js.map +1 -1
  65. package/build/utils/use-deprecated-props.js +35 -0
  66. package/build/utils/use-deprecated-props.js.map +1 -0
  67. package/build-module/autocomplete/autocompleter-ui.js +1 -3
  68. package/build-module/autocomplete/autocompleter-ui.js.map +1 -1
  69. package/build-module/autocomplete/index.js +3 -3
  70. package/build-module/autocomplete/index.js.map +1 -1
  71. package/build-module/button/index.js +2 -0
  72. package/build-module/button/index.js.map +1 -1
  73. package/build-module/card/card-media/component.js +2 -1
  74. package/build-module/card/card-media/component.js.map +1 -1
  75. package/build-module/combobox-control/index.js +6 -5
  76. package/build-module/combobox-control/index.js.map +1 -1
  77. package/build-module/combobox-control/styles.js +3 -3
  78. package/build-module/combobox-control/styles.js.map +1 -1
  79. package/build-module/dimension-control/index.js +1 -1
  80. package/build-module/dimension-control/index.js.map +1 -1
  81. package/build-module/draggable/index.js +2 -7
  82. package/build-module/draggable/index.js.map +1 -1
  83. package/build-module/form-token-field/index.js +4 -3
  84. package/build-module/form-token-field/index.js.map +1 -1
  85. package/build-module/form-token-field/styles.js +3 -3
  86. package/build-module/form-token-field/styles.js.map +1 -1
  87. package/build-module/mobile/global-styles-context/index.native.js +13 -1
  88. package/build-module/mobile/global-styles-context/index.native.js.map +1 -1
  89. package/build-module/mobile/link-picker/link-picker-results.native.js +3 -1
  90. package/build-module/mobile/link-picker/link-picker-results.native.js.map +1 -1
  91. package/build-module/modal/index.js +2 -1
  92. package/build-module/modal/index.js.map +1 -1
  93. package/build-module/navigable-container/container.js +43 -19
  94. package/build-module/navigable-container/container.js.map +1 -1
  95. package/build-module/navigable-container/index.js +0 -2
  96. package/build-module/navigable-container/index.js.map +1 -1
  97. package/build-module/navigable-container/menu.js +36 -4
  98. package/build-module/navigable-container/menu.js.map +1 -1
  99. package/build-module/navigable-container/tabbable.js +44 -3
  100. package/build-module/navigable-container/tabbable.js.map +1 -1
  101. package/build-module/navigable-container/types.js +2 -0
  102. package/build-module/navigable-container/types.js.map +1 -0
  103. package/build-module/palette-edit/index.js +34 -13
  104. package/build-module/palette-edit/index.js.map +1 -1
  105. package/build-module/sandbox/index.native.js +6 -2
  106. package/build-module/sandbox/index.native.js.map +1 -1
  107. package/build-module/slot-fill/bubbles-virtually/fill.js +2 -1
  108. package/build-module/slot-fill/bubbles-virtually/fill.js.map +1 -1
  109. package/build-module/slot-fill/bubbles-virtually/slot-fill-provider.js +46 -36
  110. package/build-module/slot-fill/bubbles-virtually/slot-fill-provider.js.map +1 -1
  111. package/build-module/slot-fill/bubbles-virtually/use-slot.js +12 -27
  112. package/build-module/slot-fill/bubbles-virtually/use-slot.js.map +1 -1
  113. package/build-module/slot-fill/fill.js +7 -31
  114. package/build-module/slot-fill/fill.js.map +1 -1
  115. package/build-module/slot-fill/index.js +1 -2
  116. package/build-module/slot-fill/index.js.map +1 -1
  117. package/build-module/slot-fill/provider.js +0 -6
  118. package/build-module/slot-fill/provider.js.map +1 -1
  119. package/build-module/slot-fill/slot.js +0 -5
  120. package/build-module/slot-fill/slot.js.map +1 -1
  121. package/build-module/tab-panel/index.js.map +1 -1
  122. package/build-module/theme/color-algorithms.js +1 -1
  123. package/build-module/theme/color-algorithms.js.map +1 -1
  124. package/build-module/toolbar/toolbar-button/index.js +1 -2
  125. package/build-module/toolbar/toolbar-button/index.js.map +1 -1
  126. package/build-module/toolbar/toolbar-item/index.js +5 -2
  127. package/build-module/toolbar/toolbar-item/index.js.map +1 -1
  128. package/build-module/utils/colors-values.js +3 -3
  129. package/build-module/utils/colors-values.js.map +1 -1
  130. package/build-module/utils/use-deprecated-props.js +25 -0
  131. package/build-module/utils/use-deprecated-props.js.map +1 -0
  132. package/build-style/style-rtl.css +58 -55
  133. package/build-style/style.css +58 -55
  134. package/build-types/autocomplete/autocompleter-ui.d.ts.map +1 -1
  135. package/build-types/autocomplete/index.d.ts.map +1 -1
  136. package/build-types/autocomplete/types.d.ts +2 -18
  137. package/build-types/autocomplete/types.d.ts.map +1 -1
  138. package/build-types/button/deprecated.d.ts +6 -0
  139. package/build-types/button/deprecated.d.ts.map +1 -1
  140. package/build-types/button/index.d.ts.map +1 -1
  141. package/build-types/button/types.d.ts +7 -0
  142. package/build-types/button/types.d.ts.map +1 -1
  143. package/build-types/card/card-media/component.d.ts +2 -1
  144. package/build-types/card/card-media/component.d.ts.map +1 -1
  145. package/build-types/card/stories/index.d.ts +21 -1
  146. package/build-types/card/stories/index.d.ts.map +1 -1
  147. package/build-types/combobox-control/index.d.ts +1 -1
  148. package/build-types/combobox-control/index.d.ts.map +1 -1
  149. package/build-types/combobox-control/stories/index.d.ts.map +1 -1
  150. package/build-types/combobox-control/styles.d.ts +1 -1
  151. package/build-types/combobox-control/types.d.ts +8 -1
  152. package/build-types/combobox-control/types.d.ts.map +1 -1
  153. package/build-types/dimension-control/index.d.ts +1 -1
  154. package/build-types/draggable/index.d.ts.map +1 -1
  155. package/build-types/form-token-field/index.d.ts.map +1 -1
  156. package/build-types/form-token-field/styles.d.ts +1 -1
  157. package/build-types/form-token-field/types.d.ts +8 -1
  158. package/build-types/form-token-field/types.d.ts.map +1 -1
  159. package/build-types/modal/index.d.ts.map +1 -1
  160. package/build-types/navigable-container/container.d.ts +20 -1
  161. package/build-types/navigable-container/container.d.ts.map +1 -1
  162. package/build-types/navigable-container/index.d.ts +5 -2
  163. package/build-types/navigable-container/index.d.ts.map +1 -1
  164. package/build-types/navigable-container/menu.d.ts +45 -11
  165. package/build-types/navigable-container/menu.d.ts.map +1 -1
  166. package/build-types/navigable-container/stories/navigable-menu.d.ts +12 -0
  167. package/build-types/navigable-container/stories/navigable-menu.d.ts.map +1 -0
  168. package/build-types/navigable-container/stories/tabbable-container.d.ts +12 -0
  169. package/build-types/navigable-container/stories/tabbable-container.d.ts.map +1 -0
  170. package/build-types/navigable-container/tabbable.d.ts +52 -9
  171. package/build-types/navigable-container/tabbable.d.ts.map +1 -1
  172. package/build-types/navigable-container/test/navigable-menu.d.ts +2 -0
  173. package/build-types/navigable-container/test/navigable-menu.d.ts.map +1 -0
  174. package/build-types/navigable-container/test/tababble-container.d.ts +2 -0
  175. package/build-types/navigable-container/test/tababble-container.d.ts.map +1 -0
  176. package/build-types/navigable-container/types.d.ts +61 -0
  177. package/build-types/navigable-container/types.d.ts.map +1 -0
  178. package/build-types/navigator/navigator-back-button/component.d.ts +1 -0
  179. package/build-types/navigator/navigator-back-button/component.d.ts.map +1 -1
  180. package/build-types/navigator/navigator-back-button/hook.d.ts +1 -0
  181. package/build-types/navigator/navigator-back-button/hook.d.ts.map +1 -1
  182. package/build-types/navigator/navigator-button/component.d.ts +1 -0
  183. package/build-types/navigator/navigator-button/component.d.ts.map +1 -1
  184. package/build-types/navigator/navigator-button/hook.d.ts +1 -0
  185. package/build-types/navigator/navigator-button/hook.d.ts.map +1 -1
  186. package/build-types/navigator/navigator-to-parent-button/component.d.ts +1 -0
  187. package/build-types/navigator/navigator-to-parent-button/component.d.ts.map +1 -1
  188. package/build-types/palette-edit/index.d.ts +1 -1
  189. package/build-types/palette-edit/index.d.ts.map +1 -1
  190. package/build-types/palette-edit/stories/index.d.ts.map +1 -1
  191. package/build-types/palette-edit/types.d.ts +8 -0
  192. package/build-types/palette-edit/types.d.ts.map +1 -1
  193. package/build-types/slot-fill/bubbles-virtually/fill.d.ts.map +1 -1
  194. package/build-types/slot-fill/bubbles-virtually/slot-fill-provider.d.ts.map +1 -1
  195. package/build-types/slot-fill/bubbles-virtually/use-slot.d.ts.map +1 -1
  196. package/build-types/slot-fill/fill.d.ts +4 -2
  197. package/build-types/slot-fill/fill.d.ts.map +1 -1
  198. package/build-types/slot-fill/index.d.ts +1 -2
  199. package/build-types/slot-fill/index.d.ts.map +1 -1
  200. package/build-types/slot-fill/provider.d.ts +0 -2
  201. package/build-types/slot-fill/provider.d.ts.map +1 -1
  202. package/build-types/slot-fill/slot.d.ts.map +1 -1
  203. package/build-types/toolbar/stories/index.d.ts.map +1 -1
  204. package/build-types/toolbar/toolbar-button/index.d.ts +6 -0
  205. package/build-types/toolbar/toolbar-button/index.d.ts.map +1 -1
  206. package/build-types/toolbar/toolbar-item/index.d.ts +6 -4
  207. package/build-types/toolbar/toolbar-item/index.d.ts.map +1 -1
  208. package/build-types/tree-grid/types.d.ts +7 -0
  209. package/build-types/tree-grid/types.d.ts.map +1 -1
  210. package/build-types/utils/use-deprecated-props.d.ts +9 -0
  211. package/build-types/utils/use-deprecated-props.d.ts.map +1 -0
  212. package/package.json +19 -19
  213. package/src/autocomplete/README.md +78 -52
  214. package/src/autocomplete/autocompleter-ui.tsx +0 -2
  215. package/src/autocomplete/index.tsx +1 -2
  216. package/src/autocomplete/types.ts +3 -19
  217. package/src/button/index.tsx +2 -0
  218. package/src/button/style.scss +13 -6
  219. package/src/button/types.ts +7 -0
  220. package/src/card/card-media/README.md +1 -1
  221. package/src/card/card-media/component.tsx +2 -1
  222. package/src/card/stories/index.tsx +47 -26
  223. package/src/checkbox-control/style.scss +1 -4
  224. package/src/combobox-control/index.tsx +24 -18
  225. package/src/combobox-control/stories/index.tsx +0 -1
  226. package/src/combobox-control/styles.ts +4 -4
  227. package/src/combobox-control/types.ts +8 -1
  228. package/src/custom-gradient-picker/style.scss +2 -2
  229. package/src/dimension-control/index.tsx +1 -1
  230. package/src/draggable/index.tsx +1 -9
  231. package/src/form-toggle/style.scss +1 -5
  232. package/src/form-token-field/index.tsx +7 -3
  233. package/src/form-token-field/styles.ts +4 -4
  234. package/src/form-token-field/types.ts +8 -1
  235. package/src/mobile/global-styles-context/index.native.js +12 -1
  236. package/src/mobile/link-picker/link-picker-results.native.js +3 -0
  237. package/src/modal/index.tsx +6 -1
  238. package/src/modal/style.scss +1 -1
  239. package/src/navigable-container/README.md +24 -13
  240. package/src/navigable-container/{container.js → container.tsx} +57 -27
  241. package/src/navigable-container/{index.js → index.tsx} +0 -1
  242. package/src/navigable-container/menu.tsx +100 -0
  243. package/src/navigable-container/stories/{navigable-menu.js → navigable-menu.tsx} +15 -10
  244. package/src/navigable-container/stories/{tabbable-container.js → tabbable-container.tsx} +15 -6
  245. package/src/navigable-container/tabbable.tsx +92 -0
  246. package/src/navigable-container/test/{navigable-menu.js → navigable-menu.tsx} +3 -1
  247. package/src/navigable-container/test/{tababble-container.js → tababble-container.tsx} +53 -24
  248. package/src/navigable-container/types.ts +76 -0
  249. package/src/palette-edit/index.tsx +45 -7
  250. package/src/palette-edit/stories/index.tsx +4 -0
  251. package/src/palette-edit/types.ts +11 -0
  252. package/src/sandbox/index.native.js +4 -0
  253. package/src/slot-fill/bubbles-virtually/fill.js +2 -1
  254. package/src/slot-fill/bubbles-virtually/slot-fill-provider.js +46 -60
  255. package/src/slot-fill/bubbles-virtually/use-slot.js +14 -41
  256. package/src/slot-fill/fill.js +4 -26
  257. package/src/slot-fill/index.js +1 -3
  258. package/src/slot-fill/provider.js +0 -6
  259. package/src/slot-fill/slot.js +0 -5
  260. package/src/style.scss +6 -0
  261. package/src/tab-panel/index.tsx +1 -1
  262. package/src/theme/color-algorithms.ts +1 -1
  263. package/src/theme/stories/index.tsx +1 -1
  264. package/src/theme/test/color-algorithms.ts +2 -2
  265. package/src/toggle-group-control/test/__snapshots__/index.tsx.snap +4 -4
  266. package/src/toolbar/stories/index.tsx +26 -24
  267. package/src/toolbar/toolbar-button/index.tsx +10 -13
  268. package/src/toolbar/toolbar-item/{index.js → index.tsx} +12 -3
  269. package/src/tree-grid/README.md +18 -0
  270. package/src/tree-grid/types.ts +7 -0
  271. package/src/utils/colors-values.js +3 -3
  272. package/src/utils/theme-variables.scss +4 -4
  273. package/src/utils/use-deprecated-props.ts +29 -0
  274. package/tsconfig.json +1 -0
  275. package/tsconfig.tsbuildinfo +1 -1
  276. package/src/CONTRIBUTING.md +0 -78
  277. package/src/README.md +0 -20
  278. package/src/navigable-container/menu.js +0 -62
  279. package/src/navigable-container/tabbable.js +0 -46
@@ -0,0 +1,100 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import type { ForwardedRef } from 'react';
5
+
6
+ /**
7
+ * WordPress dependencies
8
+ */
9
+ import { forwardRef } from '@wordpress/element';
10
+
11
+ /**
12
+ * Internal dependencies
13
+ */
14
+ import NavigableContainer from './container';
15
+ import type { NavigableMenuProps } from './types';
16
+
17
+ export function UnforwardedNavigableMenu(
18
+ { role = 'menu', orientation = 'vertical', ...rest }: NavigableMenuProps,
19
+ ref: ForwardedRef< any >
20
+ ) {
21
+ const eventToOffset = ( evt: KeyboardEvent ) => {
22
+ const { code } = evt;
23
+
24
+ let next = [ 'ArrowDown' ];
25
+ let previous = [ 'ArrowUp' ];
26
+
27
+ if ( orientation === 'horizontal' ) {
28
+ next = [ 'ArrowRight' ];
29
+ previous = [ 'ArrowLeft' ];
30
+ }
31
+
32
+ if ( orientation === 'both' ) {
33
+ next = [ 'ArrowRight', 'ArrowDown' ];
34
+ previous = [ 'ArrowLeft', 'ArrowUp' ];
35
+ }
36
+
37
+ if ( next.includes( code ) ) {
38
+ return 1;
39
+ } else if ( previous.includes( code ) ) {
40
+ return -1;
41
+ } else if (
42
+ [ 'ArrowDown', 'ArrowUp', 'ArrowLeft', 'ArrowRight' ].includes(
43
+ code
44
+ )
45
+ ) {
46
+ // Key press should be handled, e.g. have event propagation and
47
+ // default behavior handled by NavigableContainer but not result
48
+ // in an offset.
49
+ return 0;
50
+ }
51
+
52
+ return undefined;
53
+ };
54
+
55
+ return (
56
+ <NavigableContainer
57
+ ref={ ref }
58
+ stopNavigationEvents
59
+ onlyBrowserTabstops={ false }
60
+ role={ role }
61
+ aria-orientation={
62
+ role !== 'presentation' &&
63
+ ( orientation === 'vertical' || orientation === 'horizontal' )
64
+ ? orientation
65
+ : undefined
66
+ }
67
+ eventToOffset={ eventToOffset }
68
+ { ...rest }
69
+ />
70
+ );
71
+ }
72
+
73
+ /**
74
+ * A container for a navigable menu.
75
+ *
76
+ * ```jsx
77
+ * import {
78
+ * NavigableMenu,
79
+ * Button,
80
+ * } from '@wordpress/components';
81
+ *
82
+ * function onNavigate( index, target ) {
83
+ * console.log( `Navigates to ${ index }`, target );
84
+ * }
85
+ *
86
+ * const MyNavigableContainer = () => (
87
+ * <div>
88
+ * <span>Navigable Menu:</span>
89
+ * <NavigableMenu onNavigate={ onNavigate } orientation="horizontal">
90
+ * <Button variant="secondary">Item 1</Button>
91
+ * <Button variant="secondary">Item 2</Button>
92
+ * <Button variant="secondary">Item 3</Button>
93
+ * </NavigableMenu>
94
+ * </div>
95
+ * );
96
+ * ```
97
+ */
98
+ export const NavigableMenu = forwardRef( UnforwardedNavigableMenu );
99
+
100
+ export default NavigableMenu;
@@ -1,25 +1,30 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import type { ComponentMeta, ComponentStory } from '@storybook/react';
5
+
1
6
  /**
2
7
  * Internal dependencies
3
8
  */
4
9
  import { NavigableMenu } from '..';
5
10
 
6
- export default {
11
+ const meta: ComponentMeta< typeof NavigableMenu > = {
7
12
  title: 'Components/NavigableMenu',
8
13
  component: NavigableMenu,
9
14
  argTypes: {
10
- children: { type: null },
11
- cycle: {
12
- type: 'boolean',
13
- },
14
- onNavigate: { action: 'onNavigate' },
15
- orientation: {
16
- options: [ 'horizontal', 'vertical' ],
17
- control: { type: 'radio' },
15
+ children: { control: { type: null } },
16
+ },
17
+ parameters: {
18
+ actions: { argTypesRegex: '^on.*' },
19
+ controls: {
20
+ expanded: true,
18
21
  },
22
+ docs: { source: { state: 'open' } },
19
23
  },
20
24
  };
25
+ export default meta;
21
26
 
22
- export const Default = ( args ) => {
27
+ export const Default: ComponentStory< typeof NavigableMenu > = ( args ) => {
23
28
  return (
24
29
  <>
25
30
  <button>Before navigable menu</button>
@@ -1,21 +1,30 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import type { ComponentMeta, ComponentStory } from '@storybook/react';
5
+
1
6
  /**
2
7
  * Internal dependencies
3
8
  */
4
9
  import { TabbableContainer } from '..';
5
10
 
6
- export default {
11
+ const meta: ComponentMeta< typeof TabbableContainer > = {
7
12
  title: 'Components/TabbableContainer',
8
13
  component: TabbableContainer,
9
14
  argTypes: {
10
- children: { type: null },
11
- cycle: {
12
- type: 'boolean',
15
+ children: { control: { type: null } },
16
+ },
17
+ parameters: {
18
+ actions: { argTypesRegex: '^on.*' },
19
+ controls: {
20
+ expanded: true,
13
21
  },
14
- onNavigate: { action: 'onNavigate' },
22
+ docs: { source: { state: 'open' } },
15
23
  },
16
24
  };
25
+ export default meta;
17
26
 
18
- export const Default = ( args ) => {
27
+ export const Default: ComponentStory< typeof TabbableContainer > = ( args ) => {
19
28
  return (
20
29
  <>
21
30
  <button>Before tabbable container</button>
@@ -0,0 +1,92 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import type { ForwardedRef } from 'react';
5
+
6
+ /**
7
+ * WordPress dependencies
8
+ */
9
+ import { forwardRef } from '@wordpress/element';
10
+
11
+ /**
12
+ * Internal dependencies
13
+ */
14
+ import NavigableContainer from './container';
15
+ import type { TabbableContainerProps } from './types';
16
+
17
+ export function UnforwardedTabbableContainer(
18
+ { eventToOffset, ...props }: TabbableContainerProps,
19
+ ref: ForwardedRef< any >
20
+ ) {
21
+ const innerEventToOffset = ( evt: KeyboardEvent ) => {
22
+ const { code, shiftKey } = evt;
23
+ if ( 'Tab' === code ) {
24
+ return shiftKey ? -1 : 1;
25
+ }
26
+
27
+ // Allow custom handling of keys besides Tab.
28
+ //
29
+ // By default, TabbableContainer will move focus forward on Tab and
30
+ // backward on Shift+Tab. The handler below will be used for all other
31
+ // events. The semantics for `eventToOffset`'s return
32
+ // values are the following:
33
+ //
34
+ // - +1: move focus forward
35
+ // - -1: move focus backward
36
+ // - 0: don't move focus, but acknowledge event and thus stop it
37
+ // - undefined: do nothing, let the event propagate.
38
+ if ( eventToOffset ) {
39
+ return eventToOffset( evt );
40
+ }
41
+
42
+ return undefined;
43
+ };
44
+
45
+ return (
46
+ <NavigableContainer
47
+ ref={ ref }
48
+ stopNavigationEvents
49
+ onlyBrowserTabstops
50
+ eventToOffset={ innerEventToOffset }
51
+ { ...props }
52
+ />
53
+ );
54
+ }
55
+
56
+ /**
57
+ * A container for tabbable elements.
58
+ *
59
+ * ```jsx
60
+ * import {
61
+ * TabbableContainer,
62
+ * Button,
63
+ * } from '@wordpress/components';
64
+ *
65
+ * function onNavigate( index, target ) {
66
+ * console.log( `Navigates to ${ index }`, target );
67
+ * }
68
+ *
69
+ * const MyTabbableContainer = () => (
70
+ * <div>
71
+ * <span>Tabbable Container:</span>
72
+ * <TabbableContainer onNavigate={ onNavigate }>
73
+ * <Button variant="secondary" tabIndex="0">
74
+ * Section 1
75
+ * </Button>
76
+ * <Button variant="secondary" tabIndex="0">
77
+ * Section 2
78
+ * </Button>
79
+ * <Button variant="secondary" tabIndex="0">
80
+ * Section 3
81
+ * </Button>
82
+ * <Button variant="secondary" tabIndex="0">
83
+ * Section 4
84
+ * </Button>
85
+ * </TabbableContainer>
86
+ * </div>
87
+ * );
88
+ * ```
89
+ */
90
+ export const TabbableContainer = forwardRef( UnforwardedTabbableContainer );
91
+
92
+ export default TabbableContainer;
@@ -8,8 +8,9 @@ import userEvent from '@testing-library/user-event';
8
8
  * Internal dependencies
9
9
  */
10
10
  import { NavigableMenu } from '../menu';
11
+ import type { NavigableMenuProps } from '../types';
11
12
 
12
- const NavigableMenuTestCase = ( props ) => (
13
+ const NavigableMenuTestCase = ( props: NavigableMenuProps ) => (
13
14
  <NavigableMenu { ...props }>
14
15
  <button>Item 1</button>
15
16
  <span>
@@ -34,6 +35,7 @@ describe( 'NavigableMenu', () => {
34
35
  // Mocking `getClientRects()` is necessary to pass a check performed by
35
36
  // the `focus.tabbable.find()` and by the `focus.focusable.find()` functions
36
37
  // from the `@wordpress/dom` package.
38
+ // @ts-expect-error We're not trying to comply to the DOM spec, only mocking
37
39
  window.HTMLElement.prototype.getClientRects = function () {
38
40
  return [ 'trick-jsdom-into-having-size-for-element-rect' ];
39
41
  };
@@ -8,20 +8,25 @@ import userEvent from '@testing-library/user-event';
8
8
  * Internal dependencies
9
9
  */
10
10
  import { TabbableContainer } from '../tabbable';
11
-
12
- const TabbableContainerTestCase = ( props ) => (
13
- <TabbableContainer { ...props }>
14
- <button>Item 1</button>
15
- <span>
16
- <span tabIndex={ -1 }>Item 2 (not tabbable)</span>
17
- </span>
18
- <span>
19
- <span tabIndex={ 0 }>Item 3</span>
20
- </span>
21
- <p>I can not be tabbed</p>
22
- <input type="text" disabled name="disabled-input" />
23
- <a href="https://example.com">Item 4</a>
24
- </TabbableContainer>
11
+ import type { TabbableContainerProps } from '../types';
12
+
13
+ const TabbableContainerTestCase = ( props: TabbableContainerProps ) => (
14
+ <>
15
+ <button>Before container</button>
16
+ <TabbableContainer { ...props }>
17
+ <button>Item 1</button>
18
+ <span>
19
+ <span tabIndex={ -1 }>Item 2 (not tabbable)</span>
20
+ </span>
21
+ <span>
22
+ <span tabIndex={ 0 }>Item 3</span>
23
+ </span>
24
+ <p>I can not be tabbed</p>
25
+ <input type="text" disabled name="disabled-input" />
26
+ <a href="https://example.com">Item 4</a>
27
+ </TabbableContainer>
28
+ <button>After container</button>
29
+ </>
25
30
  );
26
31
 
27
32
  const getTabbableContainerTabbables = () => [
@@ -37,6 +42,7 @@ describe( 'TabbableContainer', () => {
37
42
  // Mocking `getClientRects()` is necessary to pass a check performed by
38
43
  // the `focus.tabbable.find()` and by the `focus.focusable.find()` functions
39
44
  // from the `@wordpress/dom` package.
45
+ // @ts-expect-error We're not trying to comply to the DOM spec, only mocking
40
46
  window.HTMLElement.prototype.getClientRects = function () {
41
47
  return [ 'trick-jsdom-into-having-size-for-element-rect' ];
42
48
  };
@@ -55,7 +61,11 @@ describe( 'TabbableContainer', () => {
55
61
 
56
62
  const tabbables = getTabbableContainerTabbables();
57
63
 
58
- // Move focus to first item.
64
+ await user.tab();
65
+ expect(
66
+ screen.getByRole( 'button', { name: 'Before container' } )
67
+ ).toHaveFocus();
68
+
59
69
  await user.tab();
60
70
  expect( tabbables[ 0 ] ).toHaveFocus();
61
71
 
@@ -89,7 +99,11 @@ describe( 'TabbableContainer', () => {
89
99
  const lastTabbableIndex = tabbables.length - 1;
90
100
  const lastTabbable = tabbables[ lastTabbableIndex ];
91
101
 
92
- // Move focus to first item.
102
+ await user.tab();
103
+ expect(
104
+ screen.getByRole( 'button', { name: 'Before container' } )
105
+ ).toHaveFocus();
106
+
93
107
  await user.tab();
94
108
  expect( firstTabbable ).toHaveFocus();
95
109
 
@@ -114,12 +128,17 @@ describe( 'TabbableContainer', () => {
114
128
  />
115
129
  );
116
130
 
117
- // With the `cycle` prop set to `false`, cycling is not allowed.
118
131
  // By default, cycling from first to last and from last to first is allowed.
132
+ // With the `cycle` prop set to `false`, cycling is not allowed.
133
+ // Therefore, focus will escape the `TabbableContainer` and continue its
134
+ // natural path in the page.
119
135
  await user.tab( { shift: true } );
120
- expect( firstTabbable ).toHaveFocus();
136
+ expect(
137
+ screen.getByRole( 'button', { name: 'Before container' } )
138
+ ).toHaveFocus();
121
139
  expect( onNavigateSpy ).toHaveBeenCalledTimes( 2 );
122
140
 
141
+ await user.tab();
123
142
  await user.tab();
124
143
  await user.tab();
125
144
  expect( lastTabbable ).toHaveFocus();
@@ -129,8 +148,12 @@ describe( 'TabbableContainer', () => {
129
148
  lastTabbable
130
149
  );
131
150
 
151
+ // Focus will move to the next natively focusable elements after
152
+ // `TabbableContainer`
132
153
  await user.tab();
133
- expect( lastTabbable ).toHaveFocus();
154
+ expect(
155
+ screen.getByRole( 'button', { name: 'After container' } )
156
+ ).toHaveFocus();
134
157
  expect( onNavigateSpy ).toHaveBeenCalledTimes( 4 );
135
158
  } );
136
159
 
@@ -149,21 +172,27 @@ describe( 'TabbableContainer', () => {
149
172
 
150
173
  const tabbables = getTabbableContainerTabbables();
151
174
 
152
- // Move focus to first item
175
+ await user.tab();
176
+ expect(
177
+ screen.getByRole( 'button', { name: 'Before container' } )
178
+ ).toHaveFocus();
179
+ expect( externalWrapperOnKeyDownSpy ).toHaveBeenCalledTimes( 0 );
180
+
153
181
  await user.tab();
154
182
  expect( tabbables[ 0 ] ).toHaveFocus();
183
+ expect( externalWrapperOnKeyDownSpy ).toHaveBeenCalledTimes( 1 );
155
184
 
156
185
  await user.keyboard( '[Space]' );
157
- expect( externalWrapperOnKeyDownSpy ).toHaveBeenCalledTimes( 1 );
186
+ expect( externalWrapperOnKeyDownSpy ).toHaveBeenCalledTimes( 2 );
158
187
 
159
188
  await user.tab();
160
- expect( externalWrapperOnKeyDownSpy ).toHaveBeenCalledTimes( 1 );
189
+ expect( externalWrapperOnKeyDownSpy ).toHaveBeenCalledTimes( 2 );
161
190
  await user.tab( { shift: true } );
162
191
  // This extra call is caused by the "shift" key being pressed
163
192
  // on its own before "tab"
164
- expect( externalWrapperOnKeyDownSpy ).toHaveBeenCalledTimes( 2 );
193
+ expect( externalWrapperOnKeyDownSpy ).toHaveBeenCalledTimes( 3 );
165
194
 
166
195
  await user.keyboard( '[Escape]' );
167
- expect( externalWrapperOnKeyDownSpy ).toHaveBeenCalledTimes( 3 );
196
+ expect( externalWrapperOnKeyDownSpy ).toHaveBeenCalledTimes( 4 );
168
197
  } );
169
198
  } );
@@ -0,0 +1,76 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import type { ForwardedRef, ReactNode } from 'react';
5
+
6
+ /**
7
+ * Internal dependencies
8
+ */
9
+ import type { WordPressComponentProps } from '../ui/context';
10
+
11
+ type BaseProps = {
12
+ /**
13
+ * The component children.
14
+ */
15
+ children?: ReactNode;
16
+ /**
17
+ * A boolean which tells the component whether or not to cycle from the end back to the beginning and vice versa.
18
+ *
19
+ * @default true
20
+ */
21
+ cycle?: boolean;
22
+ /**
23
+ * A callback invoked on the keydown event.
24
+ */
25
+ onKeyDown?: ( event: KeyboardEvent ) => void;
26
+ /**
27
+ * A callback invoked when the menu navigates to one of its children passing the index and child as an argument
28
+ */
29
+ onNavigate?: ( index: number, focusable: HTMLElement ) => void;
30
+ };
31
+
32
+ export type NavigableContainerProps = WordPressComponentProps<
33
+ BaseProps & {
34
+ /**
35
+ * Gets an offset, given an event.
36
+ */
37
+ eventToOffset: ( event: KeyboardEvent ) => -1 | 0 | 1 | undefined;
38
+ /**
39
+ * The forwarded ref.
40
+ */
41
+ forwardedRef?: ForwardedRef< any >;
42
+ /**
43
+ * Whether to only consider browser tab stops.
44
+ *
45
+ * @default false
46
+ */
47
+ onlyBrowserTabstops: boolean;
48
+ /**
49
+ * Whether to stop navigation events.
50
+ *
51
+ * @default false
52
+ */
53
+ stopNavigationEvents: boolean;
54
+ },
55
+ 'div',
56
+ false
57
+ >;
58
+
59
+ export type NavigableMenuProps = WordPressComponentProps<
60
+ BaseProps & {
61
+ /**
62
+ * The orientation of the menu.
63
+ *
64
+ * @default 'vertical'
65
+ */
66
+ orientation?: 'vertical' | 'horizontal' | 'both';
67
+ },
68
+ 'div',
69
+ false
70
+ >;
71
+
72
+ export type TabbableContainerProps = WordPressComponentProps<
73
+ BaseProps & Partial< Pick< NavigableContainerProps, 'eventToOffset' > >,
74
+ 'div',
75
+ false
76
+ >;
@@ -1,12 +1,19 @@
1
1
  /**
2
2
  * External dependencies
3
3
  */
4
+ import classnames from 'classnames';
4
5
  import { paramCase as kebabCase } from 'change-case';
5
6
 
6
7
  /**
7
8
  * WordPress dependencies
8
9
  */
9
- import { useState, useRef, useEffect, useCallback } from '@wordpress/element';
10
+ import {
11
+ useState,
12
+ useRef,
13
+ useEffect,
14
+ useCallback,
15
+ useMemo,
16
+ } from '@wordpress/element';
10
17
  import { __, sprintf } from '@wordpress/i18n';
11
18
  import { lineSolid, moreVertical, plus } from '@wordpress/icons';
12
19
  import {
@@ -106,15 +113,26 @@ function ColorPickerPopover< T extends Color | Gradient >( {
106
113
  isGradient,
107
114
  element,
108
115
  onChange,
116
+ popoverProps: receivedPopoverProps,
109
117
  onClose = () => {},
110
118
  }: ColorPickerPopoverProps< T > ) {
119
+ const popoverProps: ColorPickerPopoverProps< T >[ 'popoverProps' ] =
120
+ useMemo(
121
+ () => ( {
122
+ shift: true,
123
+ offset: 20,
124
+ placement: 'left-start',
125
+ ...receivedPopoverProps,
126
+ className: classnames(
127
+ 'components-palette-edit__popover',
128
+ receivedPopoverProps?.className
129
+ ),
130
+ } ),
131
+ [ receivedPopoverProps ]
132
+ );
133
+
111
134
  return (
112
- <Popover
113
- placement="left-start"
114
- offset={ 20 }
115
- className="components-palette-edit__popover"
116
- onClose={ onClose }
117
- >
135
+ <Popover { ...popoverProps } onClose={ onClose }>
118
136
  { ! isGradient && (
119
137
  <ColorPicker
120
138
  color={ element.color }
@@ -154,17 +172,31 @@ function Option< T extends Color | Gradient >( {
154
172
  onStartEditing,
155
173
  onRemove,
156
174
  onStopEditing,
175
+ popoverProps: receivedPopoverProps,
157
176
  slugPrefix,
158
177
  isGradient,
159
178
  }: OptionProps< T > ) {
160
179
  const focusOutsideProps = useFocusOutside( onStopEditing );
161
180
  const value = isGradient ? element.gradient : element.color;
162
181
 
182
+ // Use internal state instead of a ref to make sure that the component
183
+ // re-renders when the popover's anchor updates.
184
+ const [ popoverAnchor, setPopoverAnchor ] = useState( null );
185
+ const popoverProps = useMemo(
186
+ () => ( {
187
+ ...receivedPopoverProps,
188
+ // Use the custom palette color item as the popover anchor.
189
+ anchor: popoverAnchor,
190
+ } ),
191
+ [ popoverAnchor, receivedPopoverProps ]
192
+ );
193
+
163
194
  return (
164
195
  <PaletteItem
165
196
  className={ isEditing ? 'is-selected' : undefined }
166
197
  as="div"
167
198
  onClick={ onStartEditing }
199
+ ref={ setPopoverAnchor }
168
200
  { ...( isEditing
169
201
  ? { ...focusOutsideProps }
170
202
  : {
@@ -218,6 +250,7 @@ function Option< T extends Color | Gradient >( {
218
250
  isGradient={ isGradient }
219
251
  onChange={ onChange }
220
252
  element={ element }
253
+ popoverProps={ popoverProps }
221
254
  />
222
255
  ) }
223
256
  </PaletteItem>
@@ -244,6 +277,7 @@ function PaletteEditListView< T extends Color | Gradient >( {
244
277
  canOnlyChangeValues,
245
278
  slugPrefix,
246
279
  isGradient,
280
+ popoverProps,
247
281
  }: PaletteEditListViewProps< T > ) {
248
282
  // When unmounting the component if there are empty elements (the user did not complete the insertion) clean them.
249
283
  const elementsReference = useRef< typeof elements >();
@@ -317,6 +351,7 @@ function PaletteEditListView< T extends Color | Gradient >( {
317
351
  }
318
352
  } }
319
353
  slugPrefix={ slugPrefix }
354
+ popoverProps={ popoverProps }
320
355
  />
321
356
  ) ) }
322
357
  </ItemGroup>
@@ -356,6 +391,7 @@ export function PaletteEdit( {
356
391
  canOnlyChangeValues,
357
392
  canReset,
358
393
  slugPrefix = '',
394
+ popoverProps,
359
395
  }: PaletteEditProps ) {
360
396
  const isGradient = !! gradients;
361
397
  const elements = isGradient ? gradients : colors;
@@ -541,6 +577,7 @@ export function PaletteEdit( {
541
577
  setEditingElement={ setEditingElement }
542
578
  slugPrefix={ slugPrefix }
543
579
  isGradient={ isGradient }
580
+ popoverProps={ popoverProps }
544
581
  />
545
582
  ) }
546
583
  { ! isEditing && editingElement !== null && (
@@ -568,6 +605,7 @@ export function PaletteEdit( {
568
605
  );
569
606
  } }
570
607
  element={ elements[ editingElement ?? -1 ] }
608
+ popoverProps={ popoverProps }
571
609
  />
572
610
  ) }
573
611
  { ! isEditing &&
@@ -59,6 +59,10 @@ Default.args = {
59
59
  ],
60
60
  paletteLabel: 'Colors',
61
61
  emptyMessage: 'Colors are empty',
62
+ popoverProps: {
63
+ placement: 'bottom-start',
64
+ offset: 8,
65
+ },
62
66
  };
63
67
 
64
68
  export const Gradients = Template.bind( {} );
@@ -6,6 +6,7 @@ import type { Key, MouseEventHandler } from 'react';
6
6
  /**
7
7
  * Internal dependencies
8
8
  */
9
+ import type Popover from '../popover';
9
10
  import type { HeadingSize } from '../heading/types';
10
11
 
11
12
  export type Color = {
@@ -58,6 +59,13 @@ export type BasePaletteEdit = {
58
59
  * @default ''
59
60
  */
60
61
  slugPrefix?: string;
62
+ /**
63
+ * Props to pass through to the underlying Popover component.
64
+ */
65
+ popoverProps?: Omit<
66
+ React.ComponentPropsWithoutRef< typeof Popover >,
67
+ 'children'
68
+ >;
61
69
  };
62
70
 
63
71
  type PaletteEditColors = {
@@ -94,6 +102,7 @@ export type ColorPickerPopoverProps< T extends Color | Gradient > = {
94
102
  onChange: ( newElement: T ) => void;
95
103
  isGradient?: T extends Gradient ? true : false;
96
104
  onClose?: () => void;
105
+ popoverProps?: PaletteEditProps[ 'popoverProps' ];
97
106
  };
98
107
 
99
108
  export type NameInputProps = {
@@ -112,6 +121,7 @@ export type OptionProps< T extends Color | Gradient > = {
112
121
  onRemove: MouseEventHandler< HTMLButtonElement >;
113
122
  onStartEditing: () => void;
114
123
  onStopEditing: () => void;
124
+ popoverProps?: PaletteEditProps[ 'popoverProps' ];
115
125
  slugPrefix: string;
116
126
  };
117
127
 
@@ -121,6 +131,7 @@ export type PaletteEditListViewProps< T extends Color | Gradient > = {
121
131
  isGradient: T extends Gradient ? true : false;
122
132
  canOnlyChangeValues: PaletteEditProps[ 'canOnlyChangeValues' ];
123
133
  editingElement?: EditingElement;
134
+ popoverProps?: PaletteEditProps[ 'popoverProps' ];
124
135
  setEditingElement: ( newEditingElement?: EditingElement ) => void;
125
136
  slugPrefix: string;
126
137
  };