@wordpress/components 19.3.0 → 19.4.1

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 (318) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/CONTRIBUTING.md +1 -1
  3. package/README.md +8 -4
  4. package/build/base-field/hook.js +1 -1
  5. package/build/base-field/hook.js.map +1 -1
  6. package/build/button/deprecated.js +2 -1
  7. package/build/button/deprecated.js.map +1 -1
  8. package/build/button/index.js +2 -1
  9. package/build/button/index.js.map +1 -1
  10. package/build/card/card/component.js +1 -1
  11. package/build/card/card/component.js.map +1 -1
  12. package/build/card/card/hook.js +1 -1
  13. package/build/card/card/hook.js.map +1 -1
  14. package/build/card/card-body/hook.js +1 -1
  15. package/build/card/card-body/hook.js.map +1 -1
  16. package/build/card/card-divider/hook.js +1 -1
  17. package/build/card/card-divider/hook.js.map +1 -1
  18. package/build/card/card-footer/hook.js +1 -1
  19. package/build/card/card-footer/hook.js.map +1 -1
  20. package/build/card/card-header/hook.js +1 -1
  21. package/build/card/card-header/hook.js.map +1 -1
  22. package/build/card/card-media/hook.js +1 -1
  23. package/build/card/card-media/hook.js.map +1 -1
  24. package/build/circular-option-picker/index.js +2 -0
  25. package/build/circular-option-picker/index.js.map +1 -1
  26. package/build/color-indicator/index.js +2 -0
  27. package/build/color-indicator/index.js.map +1 -1
  28. package/build/color-palette/index.js +2 -0
  29. package/build/color-palette/index.js.map +1 -1
  30. package/build/color-picker/color-display.js.map +1 -1
  31. package/build/color-picker/color-input.js.map +1 -1
  32. package/build/color-picker/component.js +1 -1
  33. package/build/color-picker/component.js.map +1 -1
  34. package/build/color-picker/use-deprecated-props.js +2 -0
  35. package/build/color-picker/use-deprecated-props.js.map +1 -1
  36. package/build/date-time/time.js +1 -1
  37. package/build/date-time/time.js.map +1 -1
  38. package/build/dropdown/index.js +3 -3
  39. package/build/dropdown/index.js.map +1 -1
  40. package/build/elevation/hook.js +5 -5
  41. package/build/elevation/hook.js.map +1 -1
  42. package/build/flex/flex/hook.js +4 -4
  43. package/build/flex/flex/hook.js.map +1 -1
  44. package/build/grid/hook.js +2 -2
  45. package/build/grid/hook.js.map +1 -1
  46. package/build/item-group/item/hook.js +1 -1
  47. package/build/item-group/item/hook.js.map +1 -1
  48. package/build/mobile/inserter-button/index.native.js +3 -3
  49. package/build/mobile/inserter-button/index.native.js.map +1 -1
  50. package/build/mobile/inserter-button/sparkles.js +25 -0
  51. package/build/mobile/inserter-button/sparkles.js.map +1 -0
  52. package/build/modal/index.js +1 -12
  53. package/build/modal/index.js.map +1 -1
  54. package/build/navigator/context.js +2 -2
  55. package/build/navigator/context.js.map +1 -1
  56. package/build/navigator/navigator-provider/component.js +18 -25
  57. package/build/navigator/navigator-provider/component.js.map +1 -1
  58. package/build/navigator/navigator-screen/component.js +39 -13
  59. package/build/navigator/navigator-screen/component.js.map +1 -1
  60. package/build/navigator/use-navigator.js +4 -4
  61. package/build/navigator/use-navigator.js.map +1 -1
  62. package/build/placeholder/index.js +4 -4
  63. package/build/placeholder/index.js.map +1 -1
  64. package/build/scrollable/hook.js +1 -1
  65. package/build/scrollable/hook.js.map +1 -1
  66. package/build/slot-fill/bubbles-virtually/fill.js +11 -2
  67. package/build/slot-fill/bubbles-virtually/fill.js.map +1 -1
  68. package/build/spinner/index.js +44 -5
  69. package/build/spinner/index.js.map +1 -1
  70. package/build/spinner/styles.js +56 -0
  71. package/build/spinner/styles.js.map +1 -0
  72. package/build/surface/hook.js +1 -1
  73. package/build/surface/hook.js.map +1 -1
  74. package/build/text/hook.js +5 -5
  75. package/build/text/hook.js.map +1 -1
  76. package/build/tip/index.js +4 -8
  77. package/build/tip/index.js.map +1 -1
  78. package/build/toggle-group-control/toggle-group-control/component.js +1 -1
  79. package/build/toggle-group-control/toggle-group-control/component.js.map +1 -1
  80. package/build/tools-panel/tools-panel/hook.js +7 -7
  81. package/build/tools-panel/tools-panel/hook.js.map +1 -1
  82. package/build/tools-panel/tools-panel-header/hook.js +3 -3
  83. package/build/tools-panel/tools-panel-header/hook.js.map +1 -1
  84. package/build/tools-panel/tools-panel-item/hook.js +1 -1
  85. package/build/tools-panel/tools-panel-item/hook.js.map +1 -1
  86. package/build/tree-grid/index.js +13 -6
  87. package/build/tree-grid/index.js.map +1 -1
  88. package/build/truncate/hook.js +2 -2
  89. package/build/truncate/hook.js.map +1 -1
  90. package/build/utils/config-values.js +1 -1
  91. package/build/utils/config-values.js.map +1 -1
  92. package/build-module/base-field/hook.js +1 -1
  93. package/build-module/base-field/hook.js.map +1 -1
  94. package/build-module/button/deprecated.js +2 -1
  95. package/build-module/button/deprecated.js.map +1 -1
  96. package/build-module/button/index.js +2 -1
  97. package/build-module/button/index.js.map +1 -1
  98. package/build-module/card/card/component.js +1 -1
  99. package/build-module/card/card/component.js.map +1 -1
  100. package/build-module/card/card/hook.js +1 -1
  101. package/build-module/card/card/hook.js.map +1 -1
  102. package/build-module/card/card-body/hook.js +1 -1
  103. package/build-module/card/card-body/hook.js.map +1 -1
  104. package/build-module/card/card-divider/hook.js +1 -1
  105. package/build-module/card/card-divider/hook.js.map +1 -1
  106. package/build-module/card/card-footer/hook.js +1 -1
  107. package/build-module/card/card-footer/hook.js.map +1 -1
  108. package/build-module/card/card-header/hook.js +1 -1
  109. package/build-module/card/card-header/hook.js.map +1 -1
  110. package/build-module/card/card-media/hook.js +1 -1
  111. package/build-module/card/card-media/hook.js.map +1 -1
  112. package/build-module/circular-option-picker/index.js +1 -0
  113. package/build-module/circular-option-picker/index.js.map +1 -1
  114. package/build-module/color-indicator/index.js +1 -0
  115. package/build-module/color-indicator/index.js.map +1 -1
  116. package/build-module/color-palette/index.js +1 -0
  117. package/build-module/color-palette/index.js.map +1 -1
  118. package/build-module/color-picker/color-display.js.map +1 -1
  119. package/build-module/color-picker/color-input.js.map +1 -1
  120. package/build-module/color-picker/component.js +1 -1
  121. package/build-module/color-picker/component.js.map +1 -1
  122. package/build-module/color-picker/use-deprecated-props.js +2 -0
  123. package/build-module/color-picker/use-deprecated-props.js.map +1 -1
  124. package/build-module/date-time/time.js +1 -1
  125. package/build-module/date-time/time.js.map +1 -1
  126. package/build-module/dropdown/index.js +3 -3
  127. package/build-module/dropdown/index.js.map +1 -1
  128. package/build-module/elevation/hook.js +5 -5
  129. package/build-module/elevation/hook.js.map +1 -1
  130. package/build-module/flex/flex/hook.js +4 -4
  131. package/build-module/flex/flex/hook.js.map +1 -1
  132. package/build-module/grid/hook.js +2 -2
  133. package/build-module/grid/hook.js.map +1 -1
  134. package/build-module/item-group/item/hook.js +1 -1
  135. package/build-module/item-group/item/hook.js.map +1 -1
  136. package/build-module/mobile/inserter-button/index.native.js +1 -1
  137. package/build-module/mobile/inserter-button/index.native.js.map +1 -1
  138. package/build-module/mobile/inserter-button/sparkles.js +16 -0
  139. package/build-module/mobile/inserter-button/sparkles.js.map +1 -0
  140. package/build-module/modal/index.js +1 -11
  141. package/build-module/modal/index.js.map +1 -1
  142. package/build-module/navigator/context.js +2 -2
  143. package/build-module/navigator/context.js.map +1 -1
  144. package/build-module/navigator/navigator-provider/component.js +18 -25
  145. package/build-module/navigator/navigator-provider/component.js.map +1 -1
  146. package/build-module/navigator/navigator-screen/component.js +39 -15
  147. package/build-module/navigator/navigator-screen/component.js.map +1 -1
  148. package/build-module/navigator/use-navigator.js +4 -4
  149. package/build-module/navigator/use-navigator.js.map +1 -1
  150. package/build-module/placeholder/index.js +4 -4
  151. package/build-module/placeholder/index.js.map +1 -1
  152. package/build-module/scrollable/hook.js +1 -1
  153. package/build-module/scrollable/hook.js.map +1 -1
  154. package/build-module/slot-fill/bubbles-virtually/fill.js +11 -2
  155. package/build-module/slot-fill/bubbles-virtually/fill.js.map +1 -1
  156. package/build-module/spinner/index.js +40 -5
  157. package/build-module/spinner/index.js.map +1 -1
  158. package/build-module/spinner/styles.js +49 -0
  159. package/build-module/spinner/styles.js.map +1 -0
  160. package/build-module/surface/hook.js +1 -1
  161. package/build-module/surface/hook.js.map +1 -1
  162. package/build-module/text/hook.js +5 -5
  163. package/build-module/text/hook.js.map +1 -1
  164. package/build-module/tip/index.js +4 -8
  165. package/build-module/tip/index.js.map +1 -1
  166. package/build-module/toggle-group-control/toggle-group-control/component.js +1 -1
  167. package/build-module/toggle-group-control/toggle-group-control/component.js.map +1 -1
  168. package/build-module/tools-panel/tools-panel/hook.js +7 -7
  169. package/build-module/tools-panel/tools-panel/hook.js.map +1 -1
  170. package/build-module/tools-panel/tools-panel-header/hook.js +3 -3
  171. package/build-module/tools-panel/tools-panel-header/hook.js.map +1 -1
  172. package/build-module/tools-panel/tools-panel-item/hook.js +1 -1
  173. package/build-module/tools-panel/tools-panel-item/hook.js.map +1 -1
  174. package/build-module/tree-grid/index.js +12 -6
  175. package/build-module/tree-grid/index.js.map +1 -1
  176. package/build-module/truncate/hook.js +2 -2
  177. package/build-module/truncate/hook.js.map +1 -1
  178. package/build-module/utils/config-values.js +1 -1
  179. package/build-module/utils/config-values.js.map +1 -1
  180. package/build-style/style-rtl.css +12 -2
  181. package/build-style/style.css +12 -2
  182. package/build-types/button/index.d.ts.map +1 -1
  183. package/build-types/circular-option-picker/index.d.ts +31 -0
  184. package/build-types/circular-option-picker/index.d.ts.map +1 -0
  185. package/build-types/color-palette/index.d.ts +16 -0
  186. package/build-types/color-palette/index.d.ts.map +1 -0
  187. package/build-types/color-palette/styles.d.ts +8 -0
  188. package/build-types/color-palette/styles.d.ts.map +1 -0
  189. package/build-types/color-picker/color-display.d.ts +14 -0
  190. package/build-types/color-picker/color-display.d.ts.map +1 -0
  191. package/build-types/color-picker/color-input.d.ts +14 -0
  192. package/build-types/color-picker/color-input.d.ts.map +1 -0
  193. package/build-types/color-picker/component.d.ts +11 -0
  194. package/build-types/color-picker/component.d.ts.map +1 -0
  195. package/build-types/color-picker/hex-input.d.ts +13 -0
  196. package/build-types/color-picker/hex-input.d.ts.map +1 -0
  197. package/build-types/color-picker/hsl-input.d.ts +13 -0
  198. package/build-types/color-picker/hsl-input.d.ts.map +1 -0
  199. package/build-types/color-picker/index.d.ts +5 -0
  200. package/build-types/color-picker/index.d.ts.map +1 -0
  201. package/build-types/color-picker/input-with-slider.d.ts +12 -0
  202. package/build-types/color-picker/input-with-slider.d.ts.map +1 -0
  203. package/build-types/color-picker/legacy-adapter.d.ts +6 -0
  204. package/build-types/color-picker/legacy-adapter.d.ts.map +1 -0
  205. package/build-types/color-picker/picker.d.ts +10 -0
  206. package/build-types/color-picker/picker.d.ts.map +1 -0
  207. package/build-types/color-picker/rgb-input.d.ts +13 -0
  208. package/build-types/color-picker/rgb-input.d.ts.map +1 -0
  209. package/build-types/color-picker/styles.d.ts +76 -0
  210. package/build-types/color-picker/styles.d.ts.map +1 -0
  211. package/build-types/color-picker/types.d.ts +2 -0
  212. package/build-types/color-picker/types.d.ts.map +1 -0
  213. package/build-types/color-picker/use-deprecated-props.d.ts +49 -0
  214. package/build-types/color-picker/use-deprecated-props.d.ts.map +1 -0
  215. package/build-types/dropdown/index.d.ts +1 -13
  216. package/build-types/dropdown/index.d.ts.map +1 -1
  217. package/build-types/elevation/hook.d.ts.map +1 -1
  218. package/build-types/flex/flex/hook.d.ts.map +1 -1
  219. package/build-types/grid/hook.d.ts.map +1 -1
  220. package/build-types/navigator/navigator-provider/component.d.ts +4 -4
  221. package/build-types/navigator/navigator-provider/component.d.ts.map +1 -1
  222. package/build-types/navigator/navigator-screen/component.d.ts +4 -4
  223. package/build-types/navigator/navigator-screen/component.d.ts.map +1 -1
  224. package/build-types/navigator/types.d.ts +5 -3
  225. package/build-types/navigator/types.d.ts.map +1 -1
  226. package/build-types/resizable-box/index.d.ts +1 -1
  227. package/build-types/resizable-box/resize-tooltip/index.d.ts +1 -1
  228. package/build-types/slot-fill/bubbles-virtually/fill.d.ts.map +1 -1
  229. package/build-types/spinner/index.d.ts +18 -1
  230. package/build-types/spinner/index.d.ts.map +1 -1
  231. package/build-types/spinner/styles.d.ts +13 -0
  232. package/build-types/spinner/styles.d.ts.map +1 -0
  233. package/build-types/surface/hook.d.ts.map +1 -1
  234. package/build-types/text/hook.d.ts.map +1 -1
  235. package/build-types/tip/index.d.ts.map +1 -1
  236. package/build-types/tools-panel/tools-panel/hook.d.ts.map +1 -1
  237. package/build-types/tools-panel/tools-panel-item/hook.d.ts.map +1 -1
  238. package/build-types/unit-control/index.d.ts +3 -2
  239. package/build-types/unit-control/index.d.ts.map +1 -1
  240. package/build-types/unit-control/types.d.ts +2 -1
  241. package/build-types/unit-control/types.d.ts.map +1 -1
  242. package/build-types/utils/config-values.d.ts +1 -1
  243. package/package.json +16 -17
  244. package/src/base-field/hook.js +1 -1
  245. package/src/button/deprecated.js +1 -0
  246. package/src/button/index.js +1 -0
  247. package/src/card/card/component.js +1 -1
  248. package/src/card/card/hook.js +1 -1
  249. package/src/card/card-body/hook.js +1 -1
  250. package/src/card/card-divider/hook.js +1 -1
  251. package/src/card/card-footer/hook.js +1 -1
  252. package/src/card/card-header/hook.js +1 -1
  253. package/src/card/card-media/hook.js +1 -1
  254. package/src/circular-option-picker/index.js +1 -0
  255. package/src/color-indicator/index.js +2 -0
  256. package/src/color-palette/index.js +1 -0
  257. package/src/color-palette/test/__snapshots__/index.js.snap +0 -1
  258. package/src/color-picker/color-display.tsx +1 -1
  259. package/src/color-picker/color-input.tsx +1 -1
  260. package/src/color-picker/component.tsx +1 -1
  261. package/src/color-picker/use-deprecated-props.ts +2 -0
  262. package/src/combobox-control/stories/index.js +6 -2
  263. package/src/combobox-control/style.scss +2 -2
  264. package/src/date-time/README.md +4 -4
  265. package/src/date-time/test/time.js +2 -2
  266. package/src/date-time/time.js +2 -2
  267. package/src/dropdown/index.js +14 -13
  268. package/src/elevation/hook.js +1 -0
  269. package/src/flex/flex/hook.js +1 -0
  270. package/src/grid/hook.js +1 -0
  271. package/src/item-group/item/hook.ts +1 -1
  272. package/src/item-group/stories/index.js +2 -2
  273. package/src/mobile/inserter-button/index.native.js +1 -2
  274. package/src/mobile/inserter-button/sparkles.js +15 -0
  275. package/src/mobile/link-settings/test/edit.native.js +5 -5
  276. package/src/modal/index.js +1 -10
  277. package/src/navigator/context.ts +2 -2
  278. package/src/navigator/navigator-provider/README.md +11 -9
  279. package/src/navigator/navigator-provider/component.tsx +16 -25
  280. package/src/navigator/navigator-screen/component.tsx +55 -15
  281. package/src/navigator/stories/index.js +19 -5
  282. package/src/navigator/test/index.js +77 -25
  283. package/src/navigator/types.ts +5 -3
  284. package/src/navigator/use-navigator.ts +3 -3
  285. package/src/placeholder/index.js +8 -6
  286. package/src/placeholder/style.scss +12 -0
  287. package/src/placeholder/test/index.js +18 -1
  288. package/src/scrollable/hook.js +1 -1
  289. package/src/slot-fill/bubbles-virtually/fill.js +12 -1
  290. package/src/spinner/README.md +10 -10
  291. package/src/spinner/index.js +42 -3
  292. package/src/spinner/stories/index.js +36 -3
  293. package/src/spinner/styles.js +47 -0
  294. package/src/surface/hook.js +1 -0
  295. package/src/text/hook.js +1 -0
  296. package/src/tip/index.js +2 -4
  297. package/src/toggle-group-control/toggle-group-control/component.tsx +1 -1
  298. package/src/tools-panel/stories/index.js +20 -1
  299. package/src/tools-panel/test/__snapshots__/index.js.snap +0 -1
  300. package/src/tools-panel/test/index.js +31 -1
  301. package/src/tools-panel/tools-panel/hook.ts +14 -9
  302. package/src/tools-panel/tools-panel-header/hook.ts +3 -3
  303. package/src/tools-panel/tools-panel-item/hook.ts +1 -0
  304. package/src/tree-grid/index.js +19 -5
  305. package/src/truncate/hook.js +1 -1
  306. package/src/unit-control/types.ts +2 -1
  307. package/src/utils/config-values.js +1 -1
  308. package/src/utils/hooks/stories/use-cx.js +121 -44
  309. package/tsconfig.json +3 -0
  310. package/tsconfig.tsbuildinfo +1 -1
  311. package/build/spinner/styles/spinner-styles.js +0 -40
  312. package/build/spinner/styles/spinner-styles.js.map +0 -1
  313. package/build-module/spinner/styles/spinner-styles.js +0 -28
  314. package/build-module/spinner/styles/spinner-styles.js.map +0 -1
  315. package/build-types/spinner/styles/spinner-styles.d.ts +0 -5
  316. package/build-types/spinner/styles/spinner-styles.d.ts.map +0 -1
  317. package/src/spinner/styles/spinner-styles.js +0 -47
  318. package/src/ui/visually-hidden/README.md +0 -21
@@ -42,37 +42,25 @@ function NavigatorProvider(
42
42
  >( [
43
43
  {
44
44
  path: initialPath,
45
- isBack: false,
46
- isInitial: true,
47
45
  },
48
46
  ] );
49
47
 
50
- const push: NavigatorContextType[ 'push' ] = useCallback(
51
- ( path, options ) => {
52
- // Force the `isBack` flag to `false` when navigating forward on both the
53
- // previous and the new location.
54
- // Also force the `isInitial` flag to `false` for the new location, to make
55
- // sure it doesn't get overridden by mistake.
48
+ const goTo: NavigatorContextType[ 'goTo' ] = useCallback(
49
+ ( path, options = {} ) => {
56
50
  setLocationHistory( [
57
- ...locationHistory.slice( 0, -1 ),
58
- {
59
- ...locationHistory[ locationHistory.length - 1 ],
60
- isBack: false,
61
- },
51
+ ...locationHistory,
62
52
  {
63
53
  ...options,
64
54
  path,
65
55
  isBack: false,
66
- isInitial: false,
67
56
  },
68
57
  ] );
69
58
  },
70
59
  [ locationHistory ]
71
60
  );
72
61
 
73
- const pop: NavigatorContextType[ 'pop' ] = useCallback( () => {
62
+ const goBack: NavigatorContextType[ 'goBack' ] = useCallback( () => {
74
63
  if ( locationHistory.length > 1 ) {
75
- // Force the `isBack` flag to `true` when navigating back.
76
64
  setLocationHistory( [
77
65
  ...locationHistory.slice( 0, -2 ),
78
66
  {
@@ -85,18 +73,21 @@ function NavigatorProvider(
85
73
 
86
74
  const navigatorContextValue: NavigatorContextType = useMemo(
87
75
  () => ( {
88
- location: locationHistory[ locationHistory.length - 1 ],
89
- push,
90
- pop,
76
+ location: {
77
+ ...locationHistory[ locationHistory.length - 1 ],
78
+ isInitial: locationHistory.length === 1,
79
+ },
80
+ goTo,
81
+ goBack,
91
82
  } ),
92
- [ locationHistory, push, pop ]
83
+ [ locationHistory, goTo, goBack ]
93
84
  );
94
85
 
95
86
  const cx = useCx();
96
87
  const classes = useMemo(
97
88
  // Prevents horizontal overflow while animating screen transitions
98
89
  () => cx( css( { overflowX: 'hidden' } ), className ),
99
- [ className ]
90
+ [ className, cx ]
100
91
  );
101
92
 
102
93
  return (
@@ -120,19 +111,19 @@ function NavigatorProvider(
120
111
  * } from '@wordpress/components';
121
112
  *
122
113
  * function NavigatorButton( { path, ...props } ) {
123
- * const { push } = useNavigator();
114
+ * const { goTo } = useNavigator();
124
115
  * return (
125
116
  * <Button
126
117
  * variant="primary"
127
- * onClick={ () => push( path ) }
118
+ * onClick={ () => goTo( path ) }
128
119
  * { ...props }
129
120
  * />
130
121
  * );
131
122
  * }
132
123
  *
133
124
  * function NavigatorBackButton( props ) {
134
- * const { pop } = useNavigator();
135
- * return <Button variant="secondary" onClick={ () => pop() } { ...props } />;
125
+ * const { goBack } = useNavigator();
126
+ * return <Button variant="secondary" onClick={ () => goBack() } { ...props } />;
136
127
  * }
137
128
  *
138
129
  * const MyNavigation = () => (
@@ -9,8 +9,13 @@ import { css } from '@emotion/react';
9
9
  /**
10
10
  * WordPress dependencies
11
11
  */
12
- import { useContext, useEffect, useState, useMemo } from '@wordpress/element';
13
- import { useReducedMotion, useFocusOnMount } from '@wordpress/compose';
12
+ import { focus } from '@wordpress/dom';
13
+ import { useContext, useEffect, useMemo, useRef } from '@wordpress/element';
14
+ import {
15
+ useReducedMotion,
16
+ useMergeRefs,
17
+ usePrevious,
18
+ } from '@wordpress/compose';
14
19
  import { isRTL } from '@wordpress/i18n';
15
20
 
16
21
  /**
@@ -47,7 +52,9 @@ function NavigatorScreen( props: Props, forwardedRef: Ref< any > ) {
47
52
  const prefersReducedMotion = useReducedMotion();
48
53
  const { location } = useContext( NavigatorContext );
49
54
  const isMatch = location.path === path;
50
- const ref = useFocusOnMount();
55
+ const wrapperRef = useRef< HTMLDivElement >( null );
56
+
57
+ const previousLocation = usePrevious( location );
51
58
 
52
59
  const cx = useCx();
53
60
  const classes = useMemo(
@@ -61,15 +68,44 @@ function NavigatorScreen( props: Props, forwardedRef: Ref< any > ) {
61
68
  } ),
62
69
  className
63
70
  ),
64
- [ className ]
71
+ [ className, cx ]
65
72
  );
66
73
 
67
- // This flag is used to only apply the focus on mount when the actual path changes.
68
- // It avoids the focus to happen on the first render.
69
- const [ hasPathChanged, setHasPathChanged ] = useState( false );
74
+ // Focus restoration
75
+ const isInitialLocation = location.isInitial && ! location.isBack;
70
76
  useEffect( () => {
71
- setHasPathChanged( true );
72
- }, [ path ] );
77
+ // Only attempt to restore focus:
78
+ // - if the current location is not the initial one (to avoid moving focus on page load)
79
+ // - when the screen becomes visible
80
+ // - if the wrapper ref has been assigned
81
+ if ( isInitialLocation || ! isMatch || ! wrapperRef.current ) {
82
+ return;
83
+ }
84
+
85
+ let elementToFocus: HTMLElement | null = null;
86
+
87
+ // When navigating back, if a selector is provided, use it to look for the
88
+ // target element (assumed to be a node inside the current NavigatorScreen)
89
+ if ( location.isBack && previousLocation?.focusTargetSelector ) {
90
+ elementToFocus = wrapperRef.current.querySelector(
91
+ previousLocation.focusTargetSelector
92
+ );
93
+ }
94
+
95
+ // If the previous query didn't run or find any element to focus, fallback
96
+ // to the first tabbable element in the screen (or the screen itself).
97
+ if ( ! elementToFocus ) {
98
+ const firstTabbable = ( focus.tabbable.find(
99
+ wrapperRef.current
100
+ ) as HTMLElement[] )[ 0 ];
101
+
102
+ elementToFocus = firstTabbable ?? wrapperRef.current;
103
+ }
104
+
105
+ elementToFocus.focus();
106
+ }, [ isInitialLocation, isMatch ] );
107
+
108
+ const mergedWrapperRef = useMergeRefs( [ forwardedRef, wrapperRef ] );
73
109
 
74
110
  if ( ! isMatch ) {
75
111
  return null;
@@ -77,7 +113,11 @@ function NavigatorScreen( props: Props, forwardedRef: Ref< any > ) {
77
113
 
78
114
  if ( prefersReducedMotion ) {
79
115
  return (
80
- <View ref={ forwardedRef } className={ classes } { ...otherProps }>
116
+ <View
117
+ ref={ mergedWrapperRef }
118
+ className={ classes }
119
+ { ...otherProps }
120
+ >
81
121
  { children }
82
122
  </View>
83
123
  );
@@ -120,7 +160,7 @@ function NavigatorScreen( props: Props, forwardedRef: Ref< any > ) {
120
160
 
121
161
  return (
122
162
  <motion.div
123
- ref={ hasPathChanged ? ref : undefined }
163
+ ref={ mergedWrapperRef }
124
164
  className={ classes }
125
165
  { ...otherProps }
126
166
  { ...animatedProps }
@@ -142,19 +182,19 @@ function NavigatorScreen( props: Props, forwardedRef: Ref< any > ) {
142
182
  * } from '@wordpress/components';
143
183
  *
144
184
  * function NavigatorButton( { path, ...props } ) {
145
- * const { push } = useNavigator();
185
+ * const { goTo } = useNavigator();
146
186
  * return (
147
187
  * <Button
148
188
  * variant="primary"
149
- * onClick={ () => push( path ) }
189
+ * onClick={ () => goTo( path ) }
150
190
  * { ...props }
151
191
  * />
152
192
  * );
153
193
  * }
154
194
  *
155
195
  * function NavigatorBackButton( props ) {
156
- * const { pop } = useNavigator();
157
- * return <Button variant="secondary" onClick={ () => pop() } { ...props } />;
196
+ * const { goBack } = useNavigator();
197
+ * return <Button variant="secondary" onClick={ () => goBack() } { ...props } />;
158
198
  * }
159
199
  *
160
200
  * const MyNavigation = () => (
@@ -19,19 +19,33 @@ export default {
19
19
  };
20
20
 
21
21
  function NavigatorButton( { path, ...props } ) {
22
- const { push } = useNavigator();
22
+ const { goTo } = useNavigator();
23
+ const dataAttrName = 'data-navigator-focusable-id';
24
+ const dataAttrValue = path;
25
+
26
+ const dataAttrCssSelector = `[${ dataAttrName }="${ dataAttrValue }"]`;
27
+
28
+ const buttonProps = {
29
+ ...props,
30
+ [ dataAttrName ]: dataAttrValue,
31
+ };
32
+
23
33
  return (
24
34
  <Button
25
35
  variant="secondary"
26
- onClick={ () => push( path ) }
27
- { ...props }
36
+ onClick={ () =>
37
+ goTo( path, { focusTargetSelector: dataAttrCssSelector } )
38
+ }
39
+ { ...buttonProps }
28
40
  />
29
41
  );
30
42
  }
31
43
 
32
44
  function NavigatorBackButton( props ) {
33
- const { pop } = useNavigator();
34
- return <Button variant="secondary" onClick={ () => pop() } { ...props } />;
45
+ const { goBack } = useNavigator();
46
+ return (
47
+ <Button variant="secondary" onClick={ () => goBack() } { ...props } />
48
+ );
35
49
  }
36
50
 
37
51
  const MyNavigation = () => {
@@ -31,27 +31,51 @@ const PATHS = {
31
31
  };
32
32
 
33
33
  function NavigatorButton( { path, onClick, ...props } ) {
34
- const { push } = useNavigator();
34
+ const { goTo } = useNavigator();
35
35
  return (
36
36
  <button
37
37
  onClick={ () => {
38
- push( path );
39
- // Used to spy on the values passed to `navigator.push`
40
- onClick?.( { type: 'push', path } );
38
+ goTo( path );
39
+ // Used to spy on the values passed to `navigator.goTo`
40
+ onClick?.( { type: 'goTo', path } );
41
41
  } }
42
42
  { ...props }
43
43
  />
44
44
  );
45
45
  }
46
46
 
47
+ function NavigatorButtonWithFocusRestoration( { path, onClick, ...props } ) {
48
+ const { goTo } = useNavigator();
49
+ const dataAttrName = 'data-navigator-focusable-id';
50
+ const dataAttrValue = path;
51
+
52
+ const dataAttrCssSelector = `[${ dataAttrName }="${ dataAttrValue }"]`;
53
+
54
+ const buttonProps = {
55
+ ...props,
56
+ [ dataAttrName ]: dataAttrValue,
57
+ };
58
+
59
+ return (
60
+ <button
61
+ onClick={ () => {
62
+ goTo( path, { focusTargetSelector: dataAttrCssSelector } );
63
+ // Used to spy on the values passed to `navigator.goTo`
64
+ onClick?.( { type: 'goTo', path } );
65
+ } }
66
+ { ...buttonProps }
67
+ />
68
+ );
69
+ }
70
+
47
71
  function NavigatorBackButton( { onClick, ...props } ) {
48
- const { pop } = useNavigator();
72
+ const { goBack } = useNavigator();
49
73
  return (
50
74
  <button
51
75
  onClick={ () => {
52
- pop();
53
- // Used to spy on the values passed to `navigator.push`
54
- onClick?.( { type: 'pop' } );
76
+ goBack();
77
+ // Used to spy on the values passed to `navigator.goBack`
78
+ onClick?.( { type: 'goBack' } );
55
79
  } }
56
80
  { ...props }
57
81
  />
@@ -65,28 +89,28 @@ const MyNavigation = ( {
65
89
  <NavigatorProvider initialPath={ initialPath }>
66
90
  <NavigatorScreen path={ PATHS.HOME }>
67
91
  <p>This is the home screen.</p>
68
- <NavigatorButton
69
- path={ PATHS.CHILD }
70
- onClick={ onNavigatorButtonClick }
71
- >
72
- Navigate to child screen.
73
- </NavigatorButton>
74
92
  <NavigatorButton
75
93
  path={ PATHS.NOT_FOUND }
76
94
  onClick={ onNavigatorButtonClick }
77
95
  >
78
96
  Navigate to non-existing screen.
79
97
  </NavigatorButton>
98
+ <NavigatorButtonWithFocusRestoration
99
+ path={ PATHS.CHILD }
100
+ onClick={ onNavigatorButtonClick }
101
+ >
102
+ Navigate to child screen.
103
+ </NavigatorButtonWithFocusRestoration>
80
104
  </NavigatorScreen>
81
105
 
82
106
  <NavigatorScreen path={ PATHS.CHILD }>
83
107
  <p>This is the child screen.</p>
84
- <NavigatorButton
108
+ <NavigatorButtonWithFocusRestoration
85
109
  path={ PATHS.NESTED }
86
110
  onClick={ onNavigatorButtonClick }
87
111
  >
88
112
  Navigate to nested screen.
89
- </NavigatorButton>
113
+ </NavigatorButtonWithFocusRestoration>
90
114
  <NavigatorBackButton onClick={ onNavigatorButtonClick }>
91
115
  Go back
92
116
  </NavigatorBackButton>
@@ -246,28 +270,28 @@ describe( 'Navigator', () => {
246
270
  expect( getHomeScreen() ).toBeInTheDocument();
247
271
  expect( getToChildScreenButton() ).toBeInTheDocument();
248
272
 
249
- // Check the values passed to `navigator.push()`
273
+ // Check the values passed to `navigator.goTo()`
250
274
  expect( spy ).toHaveBeenCalledTimes( 6 );
251
275
  expect( spy ).toHaveBeenNthCalledWith( 1, {
252
276
  path: PATHS.CHILD,
253
- type: 'push',
277
+ type: 'goTo',
254
278
  } );
255
279
  expect( spy ).toHaveBeenNthCalledWith( 2, {
256
- type: 'pop',
280
+ type: 'goBack',
257
281
  } );
258
282
  expect( spy ).toHaveBeenNthCalledWith( 3, {
259
283
  path: PATHS.CHILD,
260
- type: 'push',
284
+ type: 'goTo',
261
285
  } );
262
286
  expect( spy ).toHaveBeenNthCalledWith( 4, {
263
287
  path: PATHS.NESTED,
264
- type: 'push',
288
+ type: 'goTo',
265
289
  } );
266
290
  expect( spy ).toHaveBeenNthCalledWith( 5, {
267
- type: 'pop',
291
+ type: 'goBack',
268
292
  } );
269
293
  expect( spy ).toHaveBeenNthCalledWith( 6, {
270
- type: 'pop',
294
+ type: 'goBack',
271
295
  } );
272
296
  } );
273
297
 
@@ -291,11 +315,39 @@ describe( 'Navigator', () => {
291
315
  getNestedScreen( { throwIfNotFound: false } )
292
316
  ).not.toBeInTheDocument();
293
317
 
294
- // Check the values passed to `navigator.push()`
318
+ // Check the values passed to `navigator.goTo()`
295
319
  expect( spy ).toHaveBeenCalledTimes( 1 );
296
320
  expect( spy ).toHaveBeenNthCalledWith( 1, {
297
321
  path: PATHS.NOT_FOUND,
298
- type: 'push',
322
+ type: 'goTo',
299
323
  } );
300
324
  } );
325
+
326
+ it( 'should restore focus correctly', () => {
327
+ render( <MyNavigation /> );
328
+
329
+ expect( getHomeScreen() ).toBeInTheDocument();
330
+
331
+ // Navigate to child screen
332
+ fireEvent.click( getToChildScreenButton() );
333
+
334
+ expect( getChildScreen() ).toBeInTheDocument();
335
+
336
+ // Navigate to nested screen
337
+ fireEvent.click( getToNestedScreenButton() );
338
+
339
+ expect( getNestedScreen() ).toBeInTheDocument();
340
+
341
+ // Navigate back to child screen, check that focus was correctly restored
342
+ fireEvent.click( getBackButton() );
343
+
344
+ expect( getChildScreen() ).toBeInTheDocument();
345
+ expect( getToNestedScreenButton() ).toHaveFocus();
346
+
347
+ // Navigate back to home screen, check that focus was correctly restored
348
+ fireEvent.click( getBackButton() );
349
+
350
+ expect( getHomeScreen() ).toBeInTheDocument();
351
+ expect( getToChildScreenButton() ).toHaveFocus();
352
+ } );
301
353
  } );
@@ -3,7 +3,9 @@
3
3
  */
4
4
  import type { ReactNode } from 'react';
5
5
 
6
- type NavigateOptions = {};
6
+ type NavigateOptions = {
7
+ focusTargetSelector?: string;
8
+ };
7
9
 
8
10
  export type NavigatorLocation = NavigateOptions & {
9
11
  isInitial?: boolean;
@@ -13,8 +15,8 @@ export type NavigatorLocation = NavigateOptions & {
13
15
 
14
16
  export type NavigatorContext = {
15
17
  location: NavigatorLocation;
16
- push: ( path: string, options: NavigateOptions ) => void;
17
- pop: () => void;
18
+ goTo: ( path: string, options?: NavigateOptions ) => void;
19
+ goBack: () => void;
18
20
  };
19
21
 
20
22
  // Returned by the `useNavigator` hook
@@ -13,12 +13,12 @@ import type { Navigator } from './types';
13
13
  * Retrieves a `navigator` instance.
14
14
  */
15
15
  function useNavigator(): Navigator {
16
- const { location, push, pop } = useContext( NavigatorContext );
16
+ const { location, goTo, goBack } = useContext( NavigatorContext );
17
17
 
18
18
  return {
19
19
  location,
20
- push,
21
- pop,
20
+ goTo,
21
+ goBack,
22
22
  };
23
23
  }
24
24
 
@@ -73,12 +73,14 @@ function Placeholder( {
73
73
  <Icon icon={ icon } />
74
74
  { label }
75
75
  </div>
76
- { !! instructions && (
77
- <div className="components-placeholder__instructions">
78
- { instructions }
79
- </div>
80
- ) }
81
- <div className={ fieldsetClasses }>{ children }</div>
76
+ <fieldset className={ fieldsetClasses }>
77
+ { !! instructions && (
78
+ <legend className="components-placeholder__instructions">
79
+ { instructions }
80
+ </legend>
81
+ ) }
82
+ { children }
83
+ </fieldset>
82
84
  </div>
83
85
  );
84
86
  }
@@ -77,6 +77,18 @@
77
77
  }
78
78
  }
79
79
 
80
+ // Overrides for browser and editor fieldset styles.
81
+ .components-placeholder__fieldset.components-placeholder__fieldset {
82
+ border: none;
83
+ padding: 0;
84
+
85
+ .components-placeholder__instructions {
86
+ padding: 0;
87
+ font-weight: normal;
88
+ font-size: 1em;
89
+ }
90
+ }
91
+
80
92
  .components-placeholder__fieldset.is-column-layout,
81
93
  .components-placeholder__fieldset.is-column-layout form {
82
94
  flex-direction: column;
@@ -88,7 +88,7 @@ describe( 'Placeholder', () => {
88
88
  const element = <div>Fieldset</div>;
89
89
  const placeholder = shallow( <Placeholder children={ element } /> );
90
90
  const placeholderFieldset = placeholder.find(
91
- '.components-placeholder__fieldset'
91
+ 'fieldset.components-placeholder__fieldset'
92
92
  );
93
93
  const child = placeholderFieldset.childAt( 0 );
94
94
 
@@ -96,6 +96,23 @@ describe( 'Placeholder', () => {
96
96
  expect( child.matchesElement( element ) ).toBe( true );
97
97
  } );
98
98
 
99
+ it( 'should display a legend if instructions are passed', () => {
100
+ const element = <div>Fieldset</div>;
101
+ const instructions = 'Choose an option.';
102
+ const placeholder = shallow(
103
+ <Placeholder
104
+ children={ element }
105
+ instructions={ instructions }
106
+ />
107
+ );
108
+ const placeholderLegend = placeholder.find(
109
+ 'legend.components-placeholder__instructions'
110
+ );
111
+
112
+ expect( placeholderLegend.exists() ).toBe( true );
113
+ expect( placeholderLegend.text() ).toEqual( instructions );
114
+ } );
115
+
99
116
  it( 'should add an additional className to the top container', () => {
100
117
  const placeholder = shallow(
101
118
  <Placeholder className="wp-placeholder" />
@@ -36,7 +36,7 @@ export function useScrollable( props ) {
36
36
  scrollDirection === 'auto' && styles.scrollAuto,
37
37
  className
38
38
  ),
39
- [ className, scrollDirection, smoothScroll ]
39
+ [ className, cx, scrollDirection, smoothScroll ]
40
40
  );
41
41
 
42
42
  return { ...otherProps, className: classes };
@@ -8,6 +8,7 @@ import { useRef, useState, useEffect, createPortal } from '@wordpress/element';
8
8
  * Internal dependencies
9
9
  */
10
10
  import useSlot from './use-slot';
11
+ import StyleProvider from '../../style-provider';
11
12
 
12
13
  function useForceUpdate() {
13
14
  const [ , setState ] = useState( {} );
@@ -48,5 +49,15 @@ export default function Fill( { name, children } ) {
48
49
  children = children( slot.fillProps );
49
50
  }
50
51
 
51
- return createPortal( children, slot.ref.current );
52
+ // When using a `Fill`, the `children` will be rendered in the document of the
53
+ // `Slot`. This means that we need to wrap the `children` in a `StyleProvider`
54
+ // to make sure we're referencing the right document/iframe (instead of the
55
+ // context of the `Fill`'s parent).
56
+ const wrappedChildren = (
57
+ <StyleProvider document={ slot.ref.current.ownerDocument }>
58
+ { children }
59
+ </StyleProvider>
60
+ );
61
+
62
+ return createPortal( wrappedChildren, slot.ref.current );
52
63
  }
@@ -1,19 +1,19 @@
1
1
  # Spinner
2
2
 
3
- Spinners notify users that their action is being processed.
4
-
5
- ![Spinner component](https://wordpress.org/gutenberg/files/2019/07/spinner.png)
6
-
7
- ## Best practices
8
-
9
- The spinner component should:
10
-
11
- - Signal to users that the processing of their request is underway and will soon complete.
3
+ `Spinner` is a component used to notify users that their action is being processed.
12
4
 
13
5
  ## Usage
14
6
 
15
7
  ```jsx
16
8
  import { Spinner } from '@wordpress/components';
17
9
 
18
- const MySpinner = () => <Spinner />;
10
+ function Example() {
11
+ return <Spinner />;
12
+ }
19
13
  ```
14
+
15
+ ## Best practices
16
+
17
+ The spinner component should:
18
+
19
+ - Signal to users that the processing of their request is underway and will soon complete.
@@ -1,8 +1,47 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import classNames from 'classnames';
5
+
1
6
  /**
2
7
  * Internal dependencies
3
8
  */
4
- import { StyledSpinner } from './styles/spinner-styles';
9
+ import { StyledSpinner, SpinnerTrack, SpinnerIndicator } from './styles';
10
+
11
+ /**
12
+ * @typedef OwnProps
13
+ *
14
+ * @property {string} [className] Class name
15
+ */
16
+ /** @typedef {import('react').ComponentPropsWithoutRef<'svg'> & OwnProps} Props */
17
+
18
+ /**
19
+ * @param {Props} props
20
+ * @return {JSX.Element} Element
21
+ */
22
+ export default function Spinner( { className, ...props } ) {
23
+ return (
24
+ <StyledSpinner
25
+ className={ classNames( 'components-spinner', className ) }
26
+ viewBox="0 0 100 100"
27
+ xmlns="http://www.w3.org/2000/svg"
28
+ role="presentation"
29
+ focusable="false"
30
+ { ...props }
31
+ >
32
+ { /* Gray circular background */ }
33
+ <SpinnerTrack
34
+ cx="50"
35
+ cy="50"
36
+ r="50"
37
+ vectorEffect="non-scaling-stroke"
38
+ />
5
39
 
6
- export default function Spinner() {
7
- return <StyledSpinner className="components-spinner" />;
40
+ { /* Theme-colored arc */ }
41
+ <SpinnerIndicator
42
+ d="m 50 0 a 50 50 0 0 1 50 50"
43
+ vectorEffect="non-scaling-stroke"
44
+ />
45
+ </StyledSpinner>
46
+ );
8
47
  }