@wordpress/components 23.3.1 → 23.3.2

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 (220) hide show
  1. package/CHANGELOG.md +22 -2
  2. package/build/color-palette/index.js +7 -4
  3. package/build/color-palette/index.js.map +1 -1
  4. package/build/color-palette/utils.js +12 -4
  5. package/build/color-palette/utils.js.map +1 -1
  6. package/build/custom-select-control/index.js +7 -0
  7. package/build/custom-select-control/index.js.map +1 -1
  8. package/build/index.js +16 -10
  9. package/build/index.js.map +1 -1
  10. package/build/navigator/context.js +5 -1
  11. package/build/navigator/context.js.map +1 -1
  12. package/build/navigator/index.js +8 -0
  13. package/build/navigator/index.js.map +1 -1
  14. package/build/navigator/navigator-back-button/hook.js +11 -3
  15. package/build/navigator/navigator-back-button/hook.js.map +1 -1
  16. package/build/navigator/navigator-provider/component.js +119 -11
  17. package/build/navigator/navigator-provider/component.js.map +1 -1
  18. package/build/navigator/navigator-screen/component.js +18 -7
  19. package/build/navigator/navigator-screen/component.js.map +1 -1
  20. package/build/navigator/navigator-to-parent-button/component.js +75 -0
  21. package/build/navigator/navigator-to-parent-button/component.js.map +1 -0
  22. package/build/navigator/navigator-to-parent-button/index.js +16 -0
  23. package/build/navigator/navigator-to-parent-button/index.js.map +1 -0
  24. package/build/navigator/use-navigator.js +6 -2
  25. package/build/navigator/use-navigator.js.map +1 -1
  26. package/build/navigator/utils/router.js +57 -0
  27. package/build/navigator/utils/router.js.map +1 -0
  28. package/build/private-apis.js +35 -0
  29. package/build/private-apis.js.map +1 -0
  30. package/build/select-control/index.js +1 -1
  31. package/build/select-control/index.js.map +1 -1
  32. package/build/select-control/styles/select-control-styles.js +38 -25
  33. package/build/select-control/styles/select-control-styles.js.map +1 -1
  34. package/build/tools-panel/tools-panel/hook.js +4 -4
  35. package/build/tools-panel/tools-panel/hook.js.map +1 -1
  36. package/build/tools-panel/tools-panel-item/hook.js +20 -11
  37. package/build/tools-panel/tools-panel-item/hook.js.map +1 -1
  38. package/build-module/color-palette/index.js +8 -5
  39. package/build-module/color-palette/index.js.map +1 -1
  40. package/build-module/color-palette/utils.js +12 -4
  41. package/build-module/color-palette/utils.js.map +1 -1
  42. package/build-module/custom-select-control/index.js +5 -0
  43. package/build-module/custom-select-control/index.js.map +1 -1
  44. package/build-module/index.js +5 -4
  45. package/build-module/index.js.map +1 -1
  46. package/build-module/navigator/context.js +5 -1
  47. package/build-module/navigator/context.js.map +1 -1
  48. package/build-module/navigator/index.js +1 -0
  49. package/build-module/navigator/index.js.map +1 -1
  50. package/build-module/navigator/navigator-back-button/hook.js +11 -3
  51. package/build-module/navigator/navigator-back-button/hook.js.map +1 -1
  52. package/build-module/navigator/navigator-provider/component.js +117 -12
  53. package/build-module/navigator/navigator-provider/component.js.map +1 -1
  54. package/build-module/navigator/navigator-screen/component.js +20 -9
  55. package/build-module/navigator/navigator-screen/component.js.map +1 -1
  56. package/build-module/navigator/navigator-to-parent-button/component.js +61 -0
  57. package/build-module/navigator/navigator-to-parent-button/component.js.map +1 -0
  58. package/build-module/navigator/navigator-to-parent-button/index.js +2 -0
  59. package/build-module/navigator/navigator-to-parent-button/index.js.map +1 -0
  60. package/build-module/navigator/use-navigator.js +6 -2
  61. package/build-module/navigator/use-navigator.js.map +1 -1
  62. package/build-module/navigator/utils/router.js +51 -0
  63. package/build-module/navigator/utils/router.js.map +1 -0
  64. package/build-module/private-apis.js +20 -0
  65. package/build-module/private-apis.js.map +1 -0
  66. package/build-module/select-control/index.js +1 -1
  67. package/build-module/select-control/index.js.map +1 -1
  68. package/build-module/select-control/styles/select-control-styles.js +38 -25
  69. package/build-module/select-control/styles/select-control-styles.js.map +1 -1
  70. package/build-module/tools-panel/tools-panel/hook.js +4 -4
  71. package/build-module/tools-panel/tools-panel/hook.js.map +1 -1
  72. package/build-module/tools-panel/tools-panel-item/hook.js +20 -11
  73. package/build-module/tools-panel/tools-panel-item/hook.js.map +1 -1
  74. package/build-style/style-rtl.css +0 -11
  75. package/build-style/style.css +0 -11
  76. package/build-types/base-control/hooks.d.ts +1 -1
  77. package/build-types/base-field/hook.d.ts +2 -2
  78. package/build-types/border-box-control/border-box-control/hook.d.ts +2 -2
  79. package/build-types/border-box-control/border-box-control-linked-button/hook.d.ts +2 -2
  80. package/build-types/border-box-control/border-box-control-split-controls/hook.d.ts +2 -2
  81. package/build-types/border-box-control/border-box-control-visualizer/hook.d.ts +2 -2
  82. package/build-types/border-control/border-control/hook.d.ts +2 -2
  83. package/build-types/border-control/border-control-dropdown/hook.d.ts +2 -2
  84. package/build-types/border-control/border-control-style-picker/hook.d.ts +2 -2
  85. package/build-types/button/deprecated.d.ts +2 -2
  86. package/build-types/button/types.d.ts +3 -1
  87. package/build-types/button/types.d.ts.map +1 -1
  88. package/build-types/card/card/hook.d.ts +2 -2
  89. package/build-types/card/card-body/hook.d.ts +2 -2
  90. package/build-types/card/card-divider/hook.d.ts +2 -2
  91. package/build-types/card/card-footer/hook.d.ts +2 -2
  92. package/build-types/card/card-header/hook.d.ts +2 -2
  93. package/build-types/card/card-media/hook.d.ts +2 -2
  94. package/build-types/color-palette/index.d.ts.map +1 -1
  95. package/build-types/color-palette/styles.d.ts +1 -1
  96. package/build-types/color-palette/utils.d.ts +8 -5
  97. package/build-types/color-palette/utils.d.ts.map +1 -1
  98. package/build-types/color-picker/styles.d.ts +7 -7
  99. package/build-types/custom-select-control/index.d.ts +1 -0
  100. package/build-types/custom-select-control/index.d.ts.map +1 -1
  101. package/build-types/date-time/date/styles.d.ts +3 -3
  102. package/build-types/date-time/date-time/styles.d.ts +3 -3
  103. package/build-types/date-time/time/styles.d.ts +8 -8
  104. package/build-types/elevation/hook.d.ts +2 -2
  105. package/build-types/external-link/styles/external-link-styles.d.ts +1 -1
  106. package/build-types/flex/flex/hook.d.ts +2 -2
  107. package/build-types/flex/flex-block/hook.d.ts +2 -2
  108. package/build-types/flex/flex-item/hook.d.ts +2 -2
  109. package/build-types/focal-point-picker/styles/focal-point-picker-style.d.ts +2 -2
  110. package/build-types/form-token-field/styles.d.ts +1 -1
  111. package/build-types/grid/hook.d.ts +2 -2
  112. package/build-types/h-stack/component.d.ts +1 -1
  113. package/build-types/h-stack/hook.d.ts +2 -2
  114. package/build-types/heading/hook.d.ts +2 -2
  115. package/build-types/input-control/styles/input-control-styles.d.ts +2 -2
  116. package/build-types/item-group/item/hook.d.ts +2 -2
  117. package/build-types/item-group/item-group/hook.d.ts +2 -2
  118. package/build-types/navigator/context.d.ts.map +1 -1
  119. package/build-types/navigator/index.d.ts +1 -0
  120. package/build-types/navigator/index.d.ts.map +1 -1
  121. package/build-types/navigator/navigator-back-button/component.d.ts +22 -3
  122. package/build-types/navigator/navigator-back-button/component.d.ts.map +1 -1
  123. package/build-types/navigator/navigator-back-button/hook.d.ts +24 -6
  124. package/build-types/navigator/navigator-back-button/hook.d.ts.map +1 -1
  125. package/build-types/navigator/navigator-button/component.d.ts +22 -3
  126. package/build-types/navigator/navigator-button/component.d.ts.map +1 -1
  127. package/build-types/navigator/navigator-button/hook.d.ts +22 -4
  128. package/build-types/navigator/navigator-button/hook.d.ts.map +1 -1
  129. package/build-types/navigator/navigator-provider/component.d.ts.map +1 -1
  130. package/build-types/navigator/navigator-screen/component.d.ts.map +1 -1
  131. package/build-types/navigator/navigator-to-parent-button/component.d.ts +27 -0
  132. package/build-types/navigator/navigator-to-parent-button/component.d.ts.map +1 -0
  133. package/build-types/navigator/navigator-to-parent-button/index.d.ts +2 -0
  134. package/build-types/navigator/navigator-to-parent-button/index.d.ts.map +1 -0
  135. package/build-types/navigator/stories/index.d.ts +1 -0
  136. package/build-types/navigator/stories/index.d.ts.map +1 -1
  137. package/build-types/navigator/test/router.d.ts +2 -0
  138. package/build-types/navigator/test/router.d.ts.map +1 -0
  139. package/build-types/navigator/types.d.ts +25 -9
  140. package/build-types/navigator/types.d.ts.map +1 -1
  141. package/build-types/navigator/use-navigator.d.ts.map +1 -1
  142. package/build-types/navigator/utils/router.d.ts +10 -0
  143. package/build-types/navigator/utils/router.d.ts.map +1 -0
  144. package/build-types/number-control/index.d.ts +2 -2
  145. package/build-types/number-control/stories/index.d.ts +2 -2
  146. package/build-types/popover/index.d.ts +1 -1
  147. package/build-types/popover/stories/e2e/index.d.ts +1 -1
  148. package/build-types/private-apis.d.ts +4 -0
  149. package/build-types/private-apis.d.ts.map +1 -0
  150. package/build-types/range-control/index.d.ts +2 -2
  151. package/build-types/range-control/styles/range-control-styles.d.ts +2 -2
  152. package/build-types/resizable-box/index.d.ts +1 -1
  153. package/build-types/resizable-box/resize-tooltip/index.d.ts +1 -1
  154. package/build-types/resizable-box/stories/index.d.ts +2 -2
  155. package/build-types/scrollable/hook.d.ts +2 -2
  156. package/build-types/search-control/index.d.ts +1 -1
  157. package/build-types/search-control/stories/index.d.ts +2 -2
  158. package/build-types/select-control/index.d.ts.map +1 -1
  159. package/build-types/select-control/styles/select-control-styles.d.ts +1 -1
  160. package/build-types/select-control/styles/select-control-styles.d.ts.map +1 -1
  161. package/build-types/select-control/types.d.ts +3 -1
  162. package/build-types/select-control/types.d.ts.map +1 -1
  163. package/build-types/spacer/hook.d.ts +2 -2
  164. package/build-types/spinner/index.d.ts +1 -1
  165. package/build-types/surface/hook.d.ts +2 -2
  166. package/build-types/text/hook.d.ts +2 -2
  167. package/build-types/text-control/index.d.ts +3 -3
  168. package/build-types/tools-panel/tools-panel/hook.d.ts +2 -2
  169. package/build-types/tools-panel/tools-panel/hook.d.ts.map +1 -1
  170. package/build-types/tools-panel/tools-panel-header/hook.d.ts +2 -2
  171. package/build-types/tools-panel/tools-panel-item/hook.d.ts +2 -2
  172. package/build-types/tools-panel/tools-panel-item/hook.d.ts.map +1 -1
  173. package/build-types/truncate/hook.d.ts +2 -2
  174. package/build-types/ui/control-group/hook.d.ts +2 -2
  175. package/build-types/ui/control-label/hook.d.ts +2 -2
  176. package/build-types/ui/form-group/form-group.d.ts +2 -2
  177. package/build-types/ui/form-group/use-form-group.d.ts +2 -2
  178. package/build-types/unit-control/index.d.ts +1 -1
  179. package/build-types/unit-control/styles/unit-control-styles.d.ts +2 -2
  180. package/build-types/v-stack/component.d.ts +2 -2
  181. package/build-types/v-stack/hook.d.ts +2 -2
  182. package/build-types/v-stack/stories/index.d.ts +2 -2
  183. package/package.json +8 -6
  184. package/src/button/types.ts +5 -2
  185. package/src/color-palette/index.tsx +13 -5
  186. package/src/color-palette/test/utils.ts +17 -1
  187. package/src/color-palette/utils.ts +12 -7
  188. package/src/custom-select-control/index.js +9 -0
  189. package/src/custom-select-control/stories/index.js +1 -1
  190. package/src/custom-select-control/test/index.js +2 -2
  191. package/src/dimension-control/test/__snapshots__/index.test.js.snap +4 -4
  192. package/src/index.js +5 -2
  193. package/src/navigator/context.ts +4 -0
  194. package/src/navigator/index.ts +1 -0
  195. package/src/navigator/navigator-back-button/README.md +1 -17
  196. package/src/navigator/navigator-back-button/hook.ts +10 -5
  197. package/src/navigator/navigator-button/README.md +1 -1
  198. package/src/navigator/navigator-provider/README.md +25 -4
  199. package/src/navigator/navigator-provider/component.tsx +170 -14
  200. package/src/navigator/navigator-screen/component.tsx +22 -11
  201. package/src/navigator/navigator-to-parent-button/README.md +15 -0
  202. package/src/navigator/navigator-to-parent-button/component.tsx +65 -0
  203. package/src/navigator/navigator-to-parent-button/index.ts +1 -0
  204. package/src/navigator/stories/index.tsx +93 -3
  205. package/src/navigator/test/index.tsx +267 -23
  206. package/src/navigator/test/router.ts +122 -0
  207. package/src/navigator/types.ts +31 -12
  208. package/src/navigator/use-navigator.ts +4 -1
  209. package/src/navigator/utils/router.ts +49 -0
  210. package/src/private-apis.js +22 -0
  211. package/src/select-control/README.md +3 -1
  212. package/src/select-control/index.tsx +3 -1
  213. package/src/select-control/style.scss +0 -10
  214. package/src/select-control/styles/select-control-styles.ts +36 -22
  215. package/src/select-control/types.ts +3 -1
  216. package/src/tools-panel/test/index.js +65 -0
  217. package/src/tools-panel/tools-panel/hook.ts +4 -5
  218. package/src/tools-panel/tools-panel-item/hook.ts +24 -14
  219. package/tsconfig.json +5 -1
  220. package/tsconfig.tsbuildinfo +1 -1
@@ -8,13 +8,15 @@ import type { ComponentMeta, ComponentStory } from '@storybook/react';
8
8
  */
9
9
  import Button from '../../button';
10
10
  import { Card, CardBody, CardFooter, CardHeader } from '../../card';
11
- import { HStack } from '../../h-stack';
11
+ import { VStack } from '../../v-stack';
12
12
  import Dropdown from '../../dropdown';
13
13
  import {
14
14
  NavigatorProvider,
15
15
  NavigatorScreen,
16
16
  NavigatorButton,
17
17
  NavigatorBackButton,
18
+ NavigatorToParentButton,
19
+ useNavigator,
18
20
  } from '..';
19
21
 
20
22
  const meta: ComponentMeta< typeof NavigatorProvider > = {
@@ -46,7 +48,7 @@ const Template: ComponentStory< typeof NavigatorProvider > = ( {
46
48
  <CardBody>
47
49
  <p>This is the home screen.</p>
48
50
 
49
- <HStack justify="flex-start" wrap>
51
+ <VStack alignment="left">
50
52
  <NavigatorButton variant="secondary" path="/child">
51
53
  Navigate to child screen.
52
54
  </NavigatorButton>
@@ -62,6 +64,10 @@ const Template: ComponentStory< typeof NavigatorProvider > = ( {
62
64
  Navigate to screen with sticky content.
63
65
  </NavigatorButton>
64
66
 
67
+ <NavigatorButton variant="secondary" path="/product/1">
68
+ Navigate to product screen with id 1.
69
+ </NavigatorButton>
70
+
65
71
  <Dropdown
66
72
  renderToggle={ ( {
67
73
  isOpen,
@@ -86,7 +92,7 @@ const Template: ComponentStory< typeof NavigatorProvider > = ( {
86
92
  </Card>
87
93
  ) }
88
94
  />
89
- </HStack>
95
+ </VStack>
90
96
  </CardBody>
91
97
  </Card>
92
98
  </NavigatorScreen>
@@ -166,6 +172,10 @@ const Template: ComponentStory< typeof NavigatorProvider > = ( {
166
172
  </CardFooter>
167
173
  </Card>
168
174
  </NavigatorScreen>
175
+
176
+ <NavigatorScreen path="/product/:id">
177
+ <ProductDetails />
178
+ </NavigatorScreen>
169
179
  </NavigatorProvider>
170
180
  );
171
181
 
@@ -208,3 +218,83 @@ function MetaphorIpsum( { quantity }: { quantity: number } ) {
208
218
  </>
209
219
  );
210
220
  }
221
+
222
+ function ProductDetails() {
223
+ const { params } = useNavigator();
224
+
225
+ return (
226
+ <Card>
227
+ <CardBody>
228
+ <NavigatorBackButton variant="secondary">
229
+ Go back
230
+ </NavigatorBackButton>
231
+ <p>This is the screen for the product with id: { params.id }</p>
232
+ </CardBody>
233
+ </Card>
234
+ );
235
+ }
236
+
237
+ const NestedNavigatorTemplate: ComponentStory< typeof NavigatorProvider > = ( {
238
+ style,
239
+ ...props
240
+ } ) => (
241
+ <NavigatorProvider
242
+ style={ { ...style, height: '100vh', maxHeight: '450px' } }
243
+ { ...props }
244
+ >
245
+ <NavigatorScreen path="/">
246
+ <Card>
247
+ <CardBody>
248
+ <NavigatorButton variant="secondary" path="/child1">
249
+ Go to first child.
250
+ </NavigatorButton>
251
+ <NavigatorButton variant="secondary" path="/child2">
252
+ Go to second child.
253
+ </NavigatorButton>
254
+ </CardBody>
255
+ </Card>
256
+ </NavigatorScreen>
257
+ <NavigatorScreen path="/child1">
258
+ <Card>
259
+ <CardBody>
260
+ This is the first child
261
+ <NavigatorToParentButton variant="secondary">
262
+ Go back to parent
263
+ </NavigatorToParentButton>
264
+ </CardBody>
265
+ </Card>
266
+ </NavigatorScreen>
267
+ <NavigatorScreen path="/child2">
268
+ <Card>
269
+ <CardBody>
270
+ This is the second child
271
+ <NavigatorToParentButton variant="secondary">
272
+ Go back to parent
273
+ </NavigatorToParentButton>
274
+ <NavigatorButton
275
+ variant="secondary"
276
+ path="/child2/grandchild"
277
+ >
278
+ Go to grand child.
279
+ </NavigatorButton>
280
+ </CardBody>
281
+ </Card>
282
+ </NavigatorScreen>
283
+ <NavigatorScreen path="/child2/grandchild">
284
+ <Card>
285
+ <CardBody>
286
+ This is the grand child
287
+ <NavigatorToParentButton variant="secondary">
288
+ Go back to parent
289
+ </NavigatorToParentButton>
290
+ </CardBody>
291
+ </Card>
292
+ </NavigatorScreen>
293
+ </NavigatorProvider>
294
+ );
295
+
296
+ export const NestedNavigator: ComponentStory< typeof NavigatorProvider > =
297
+ NestedNavigatorTemplate.bind( {} );
298
+ NestedNavigator.args = {
299
+ initialPath: '/child2/grandchild',
300
+ };
@@ -13,11 +13,14 @@ import { useState } from '@wordpress/element';
13
13
  /**
14
14
  * Internal dependencies
15
15
  */
16
+ import Button from '../../button';
16
17
  import {
17
18
  NavigatorProvider,
18
19
  NavigatorScreen,
19
20
  NavigatorButton,
20
21
  NavigatorBackButton,
22
+ NavigatorToParentButton,
23
+ useNavigator,
21
24
  } from '..';
22
25
 
23
26
  jest.mock( 'framer-motion', () => {
@@ -50,6 +53,9 @@ const PATHS = {
50
53
  HOME: '/',
51
54
  CHILD: '/child',
52
55
  NESTED: '/child/nested',
56
+ PRODUCT_PATTERN: '/product/:productId',
57
+ PRODUCT_1: '/product/1',
58
+ PRODUCT_2: '/product/2',
53
59
  INVALID_HTML_ATTRIBUTE: INVALID_HTML_ATTRIBUTE.raw,
54
60
  NOT_FOUND: '/not-found',
55
61
  };
@@ -58,6 +64,7 @@ const SCREEN_TEXT = {
58
64
  home: 'This is the home screen.',
59
65
  child: 'This is the child screen.',
60
66
  nested: 'This is the nested screen.',
67
+ product: 'This is the product screen.',
61
68
  invalidHtmlPath: 'This is the screen with an invalid HTML value as a path.',
62
69
  };
63
70
 
@@ -65,9 +72,12 @@ const BUTTON_TEXT = {
65
72
  toNonExistingScreen: 'Navigate to non-existing screen.',
66
73
  toChildScreen: 'Navigate to child screen.',
67
74
  toNestedScreen: 'Navigate to nested screen.',
75
+ toProductScreen1: 'Navigate to product 1 screen.',
76
+ toProductScreen2: 'Navigate to product 2 screen.',
68
77
  toInvalidHtmlPathScreen:
69
78
  'Navigate to screen with an invalid HTML value as a path.',
70
79
  back: 'Go back',
80
+ backUsingGoTo: 'Go back using goTo',
71
81
  };
72
82
 
73
83
  type CustomTestOnClickHandler = (
@@ -77,6 +87,7 @@ type CustomTestOnClickHandler = (
77
87
  path: string;
78
88
  }
79
89
  | { type: 'goBack' }
90
+ | { type: 'goToParent' }
80
91
  ) => void;
81
92
 
82
93
  function CustomNavigatorButton( {
@@ -98,20 +109,21 @@ function CustomNavigatorButton( {
98
109
  );
99
110
  }
100
111
 
101
- function CustomNavigatorButtonWithFocusRestoration( {
112
+ function CustomNavigatorGoToBackButton( {
102
113
  path,
103
114
  onClick,
104
115
  ...props
105
116
  }: Omit< ComponentPropsWithoutRef< typeof NavigatorButton >, 'onClick' > & {
106
117
  onClick?: CustomTestOnClickHandler;
107
118
  } ) {
119
+ const { goTo } = useNavigator();
108
120
  return (
109
- <NavigatorButton
121
+ <Button
110
122
  onClick={ () => {
123
+ goTo( path, { isBack: true } );
111
124
  // Used to spy on the values passed to `navigator.goTo`.
112
125
  onClick?.( { type: 'goTo', path } );
113
126
  } }
114
- path={ path }
115
127
  { ...props }
116
128
  />
117
129
  );
@@ -134,6 +146,41 @@ function CustomNavigatorBackButton( {
134
146
  );
135
147
  }
136
148
 
149
+ function CustomNavigatorToParentButton( {
150
+ onClick,
151
+ ...props
152
+ }: Omit< ComponentPropsWithoutRef< typeof NavigatorBackButton >, 'onClick' > & {
153
+ onClick?: CustomTestOnClickHandler;
154
+ } ) {
155
+ return (
156
+ <NavigatorToParentButton
157
+ onClick={ () => {
158
+ // Used to spy on the values passed to `navigator.goBack`.
159
+ onClick?.( { type: 'goToParent' } );
160
+ } }
161
+ { ...props }
162
+ />
163
+ );
164
+ }
165
+
166
+ const ProductScreen = ( {
167
+ onBackButtonClick,
168
+ }: {
169
+ onBackButtonClick?: CustomTestOnClickHandler;
170
+ } ) => {
171
+ const { params } = useNavigator();
172
+
173
+ return (
174
+ <NavigatorScreen path={ PATHS.PRODUCT_PATTERN }>
175
+ <p>{ SCREEN_TEXT.product }</p>
176
+ <p>Product ID is { params.productId }</p>
177
+ <CustomNavigatorBackButton onClick={ onBackButtonClick }>
178
+ { BUTTON_TEXT.back }
179
+ </CustomNavigatorBackButton>
180
+ </NavigatorScreen>
181
+ );
182
+ };
183
+
137
184
  const MyNavigation = ( {
138
185
  initialPath = PATHS.HOME,
139
186
  onNavigatorButtonClick,
@@ -148,6 +195,12 @@ const MyNavigation = ( {
148
195
  <NavigatorProvider initialPath={ initialPath }>
149
196
  <NavigatorScreen path={ PATHS.HOME }>
150
197
  <p>{ SCREEN_TEXT.home }</p>
198
+ { /*
199
+ * A button useful to test focus restoration. This button is the first
200
+ * tabbable item in the screen, but should not receive focus when
201
+ * navigating to screen as a result of a backwards navigation.
202
+ */ }
203
+ <button>First tabbable home screen button</button>
151
204
  <CustomNavigatorButton
152
205
  path={ PATHS.NOT_FOUND }
153
206
  onClick={ onNavigatorButtonClick }
@@ -160,6 +213,18 @@ const MyNavigation = ( {
160
213
  >
161
214
  { BUTTON_TEXT.toChildScreen }
162
215
  </CustomNavigatorButton>
216
+ <CustomNavigatorButton
217
+ path={ PATHS.PRODUCT_1 }
218
+ onClick={ onNavigatorButtonClick }
219
+ >
220
+ { BUTTON_TEXT.toProductScreen1 }
221
+ </CustomNavigatorButton>
222
+ <CustomNavigatorButton
223
+ path={ PATHS.PRODUCT_2 }
224
+ onClick={ onNavigatorButtonClick }
225
+ >
226
+ { BUTTON_TEXT.toProductScreen2 }
227
+ </CustomNavigatorButton>
163
228
  <CustomNavigatorButton
164
229
  path={ PATHS.INVALID_HTML_ATTRIBUTE }
165
230
  onClick={ onNavigatorButtonClick }
@@ -170,12 +235,18 @@ const MyNavigation = ( {
170
235
 
171
236
  <NavigatorScreen path={ PATHS.CHILD }>
172
237
  <p>{ SCREEN_TEXT.child }</p>
173
- <CustomNavigatorButtonWithFocusRestoration
238
+ { /*
239
+ * A button useful to test focus restoration. This button is the first
240
+ * tabbable item in the screen, but should not receive focus when
241
+ * navigating to screen as a result of a backwards navigation.
242
+ */ }
243
+ <button>First tabbable child screen button</button>
244
+ <CustomNavigatorButton
174
245
  path={ PATHS.NESTED }
175
246
  onClick={ onNavigatorButtonClick }
176
247
  >
177
248
  { BUTTON_TEXT.toNestedScreen }
178
- </CustomNavigatorButtonWithFocusRestoration>
249
+ </CustomNavigatorButton>
179
250
  <CustomNavigatorBackButton
180
251
  onClick={ onNavigatorButtonClick }
181
252
  >
@@ -203,6 +274,8 @@ const MyNavigation = ( {
203
274
  </CustomNavigatorBackButton>
204
275
  </NavigatorScreen>
205
276
 
277
+ <ProductScreen onBackButtonClick={ onNavigatorButtonClick } />
278
+
206
279
  <NavigatorScreen path={ PATHS.INVALID_HTML_ATTRIBUTE }>
207
280
  <p>{ SCREEN_TEXT.invalidHtmlPath }</p>
208
281
  <CustomNavigatorBackButton
@@ -229,6 +302,72 @@ const MyNavigation = ( {
229
302
  );
230
303
  };
231
304
 
305
+ const MyHierarchicalNavigation = ( {
306
+ initialPath = PATHS.HOME,
307
+ onNavigatorButtonClick,
308
+ }: {
309
+ initialPath?: string;
310
+ onNavigatorButtonClick?: CustomTestOnClickHandler;
311
+ } ) => {
312
+ return (
313
+ <>
314
+ <NavigatorProvider initialPath={ initialPath }>
315
+ <NavigatorScreen path={ PATHS.HOME }>
316
+ <p>{ SCREEN_TEXT.home }</p>
317
+ { /*
318
+ * A button useful to test focus restoration. This button is the first
319
+ * tabbable item in the screen, but should not receive focus when
320
+ * navigating to screen as a result of a backwards navigation.
321
+ */ }
322
+ <button>First tabbable home screen button</button>
323
+ <CustomNavigatorButton
324
+ path={ PATHS.CHILD }
325
+ onClick={ onNavigatorButtonClick }
326
+ >
327
+ { BUTTON_TEXT.toChildScreen }
328
+ </CustomNavigatorButton>
329
+ </NavigatorScreen>
330
+
331
+ <NavigatorScreen path={ PATHS.CHILD }>
332
+ <p>{ SCREEN_TEXT.child }</p>
333
+ { /*
334
+ * A button useful to test focus restoration. This button is the first
335
+ * tabbable item in the screen, but should not receive focus when
336
+ * navigating to screen as a result of a backwards navigation.
337
+ */ }
338
+ <button>First tabbable child screen button</button>
339
+ <CustomNavigatorButton
340
+ path={ PATHS.NESTED }
341
+ onClick={ onNavigatorButtonClick }
342
+ >
343
+ { BUTTON_TEXT.toNestedScreen }
344
+ </CustomNavigatorButton>
345
+ <CustomNavigatorToParentButton
346
+ onClick={ onNavigatorButtonClick }
347
+ >
348
+ { BUTTON_TEXT.back }
349
+ </CustomNavigatorToParentButton>
350
+ </NavigatorScreen>
351
+
352
+ <NavigatorScreen path={ PATHS.NESTED }>
353
+ <p>{ SCREEN_TEXT.nested }</p>
354
+ <CustomNavigatorToParentButton
355
+ onClick={ onNavigatorButtonClick }
356
+ >
357
+ { BUTTON_TEXT.back }
358
+ </CustomNavigatorToParentButton>
359
+ <CustomNavigatorGoToBackButton
360
+ path={ PATHS.CHILD }
361
+ onClick={ onNavigatorButtonClick }
362
+ >
363
+ { BUTTON_TEXT.backUsingGoTo }
364
+ </CustomNavigatorGoToBackButton>
365
+ </NavigatorScreen>
366
+ </NavigatorProvider>
367
+ </>
368
+ );
369
+ };
370
+
232
371
  const getScreen = ( screenKey: keyof typeof SCREEN_TEXT ) =>
233
372
  screen.getByText( SCREEN_TEXT[ screenKey ] );
234
373
  const queryScreen = ( screenKey: keyof typeof SCREEN_TEXT ) =>
@@ -369,7 +508,7 @@ describe( 'Navigator', () => {
369
508
  } );
370
509
  } );
371
510
 
372
- it( 'should not rended anything if the path does not match any available screen', async () => {
511
+ it( 'should not render anything if the path does not match any available screen', async () => {
373
512
  const spy = jest.fn();
374
513
 
375
514
  const user = userEvent.setup();
@@ -396,8 +535,6 @@ describe( 'Navigator', () => {
396
535
  } );
397
536
 
398
537
  it( 'should escape the value of the `path` prop', async () => {
399
- const user = userEvent.setup();
400
-
401
538
  render( <MyNavigation /> );
402
539
 
403
540
  expect( getScreen( 'home' ) ).toBeInTheDocument();
@@ -407,24 +544,42 @@ describe( 'Navigator', () => {
407
544
 
408
545
  // The following line tests the implementation details, but it's necessary
409
546
  // as this would be otherwise transparent to the user.
547
+ // A potential way would be to check if an invalid HTML attribute could
548
+ // be detected in the tests (by JSDom or any other plugin). We could then
549
+ // make sure that an invalid path would not error because it's escaped
550
+ // correctly.
410
551
  expect(
411
552
  getNavigationButton( 'toInvalidHtmlPathScreen' )
412
553
  ).toHaveAttribute( 'id', INVALID_HTML_ATTRIBUTE.escaped );
554
+ } );
413
555
 
414
- // Navigate to screen with an invalid HTML value for its `path`.
415
- await user.click( getNavigationButton( 'toInvalidHtmlPathScreen' ) );
556
+ it( 'should match correctly paths with named arguments', async () => {
557
+ const user = userEvent.setup();
416
558
 
417
- expect( getScreen( 'invalidHtmlPath' ) ).toBeInTheDocument();
418
- expect( getNavigationButton( 'back' ) ).toBeInTheDocument();
559
+ render( <MyNavigation /> );
560
+
561
+ expect( getScreen( 'home' ) ).toBeInTheDocument();
419
562
 
420
- // Navigate back to home screen, check that the focus restoration selector
421
- // worked correctly despite the escaping.
563
+ // Navigate to Product 1 screen
564
+ await user.click( getNavigationButton( 'toProductScreen1' ) );
565
+
566
+ expect( getScreen( 'product' ) ).toBeInTheDocument();
567
+
568
+ // Check that named parameter is extracted correctly
569
+ expect( screen.getByText( 'Product ID is 1' ) ).toBeInTheDocument();
570
+
571
+ // Navigate back to home screen
422
572
  await user.click( getNavigationButton( 'back' ) );
423
573
 
424
574
  expect( getScreen( 'home' ) ).toBeInTheDocument();
425
- expect(
426
- getNavigationButton( 'toInvalidHtmlPathScreen' )
427
- ).toHaveFocus();
575
+
576
+ // Navigate to Product 2 screen
577
+ await user.click( getNavigationButton( 'toProductScreen2' ) );
578
+
579
+ expect( getScreen( 'product' ) ).toBeInTheDocument();
580
+
581
+ // Check that named parameter is extracted correctly
582
+ expect( screen.getByText( 'Product ID is 2' ) ).toBeInTheDocument();
428
583
  } );
429
584
 
430
585
  describe( 'focus management', () => {
@@ -437,7 +592,11 @@ describe( 'Navigator', () => {
437
592
  await user.click( getNavigationButton( 'toChildScreen' ) );
438
593
 
439
594
  // The first tabbable element receives focus.
440
- expect( getNavigationButton( 'toNestedScreen' ) ).toHaveFocus();
595
+ expect(
596
+ screen.getByRole( 'button', {
597
+ name: 'First tabbable child screen button',
598
+ } )
599
+ ).toHaveFocus();
441
600
 
442
601
  // Navigate to nested screen.
443
602
  await user.click( getNavigationButton( 'toNestedScreen' ) );
@@ -448,14 +607,29 @@ describe( 'Navigator', () => {
448
607
  // Navigate back to child screen.
449
608
  await user.click( getNavigationButton( 'back' ) );
450
609
 
451
- // The first tabbable element receives focus.
610
+ // Focus is restored on the last element that had focus when the
611
+ // navigation away from the screen occurred.
452
612
  expect( getNavigationButton( 'toNestedScreen' ) ).toHaveFocus();
453
613
 
454
- // Navigate back to home screen, check that focus was correctly restored.
614
+ // Navigate back to home screen.
455
615
  await user.click( getNavigationButton( 'back' ) );
456
616
 
457
- // The first tabbable element receives focus.
617
+ // Focus is restored on the last element that had focus when the
618
+ // navigation away from the screen occurred.
458
619
  expect( getNavigationButton( 'toChildScreen' ) ).toHaveFocus();
620
+
621
+ // Navigate to product screen for product 2
622
+ await user.click( getNavigationButton( 'toProductScreen2' ) );
623
+
624
+ // The first tabbable element receives focus.
625
+ expect( getNavigationButton( 'back' ) ).toHaveFocus();
626
+
627
+ // Navigate back to home screen.
628
+ await user.click( getNavigationButton( 'back' ) );
629
+
630
+ // Focus is restored on the last element that had focus when the
631
+ // navigation away from the screen occurred.
632
+ expect( getNavigationButton( 'toProductScreen2' ) ).toHaveFocus();
459
633
  } );
460
634
 
461
635
  it( 'should keep focus on an active element inside navigator, while re-rendering', async () => {
@@ -467,7 +641,11 @@ describe( 'Navigator', () => {
467
641
  await user.click( getNavigationButton( 'toChildScreen' ) );
468
642
 
469
643
  // The first tabbable element receives focus.
470
- expect( getNavigationButton( 'toNestedScreen' ) ).toHaveFocus();
644
+ expect(
645
+ screen.getByRole( 'button', {
646
+ name: 'First tabbable child screen button',
647
+ } )
648
+ ).toHaveFocus();
471
649
 
472
650
  // Interact with the inner input.
473
651
  // The focus should stay on the input element.
@@ -485,7 +663,11 @@ describe( 'Navigator', () => {
485
663
  await user.click( getNavigationButton( 'toChildScreen' ) );
486
664
 
487
665
  // The first tabbable element receives focus.
488
- expect( getNavigationButton( 'toNestedScreen' ) ).toHaveFocus();
666
+ expect(
667
+ screen.getByRole( 'button', {
668
+ name: 'First tabbable child screen button',
669
+ } )
670
+ ).toHaveFocus();
489
671
 
490
672
  // Interact with the outer input.
491
673
  // The focus should stay on the input element.
@@ -493,5 +675,67 @@ describe( 'Navigator', () => {
493
675
  await user.type( outerInput, 'd' );
494
676
  expect( outerInput ).toHaveFocus();
495
677
  } );
678
+
679
+ it( 'should restore focus correctly even when the `path` needs to be escaped', async () => {
680
+ const user = userEvent.setup();
681
+
682
+ render( <MyNavigation /> );
683
+
684
+ expect( getScreen( 'home' ) ).toBeInTheDocument();
685
+
686
+ // Navigate to screen with an invalid HTML value for its `path`.
687
+ await user.click(
688
+ getNavigationButton( 'toInvalidHtmlPathScreen' )
689
+ );
690
+
691
+ expect( getScreen( 'invalidHtmlPath' ) ).toBeInTheDocument();
692
+
693
+ // Navigate back to home screen, check that the focus restoration selector
694
+ // worked correctly despite the escaping.
695
+ await user.click( getNavigationButton( 'back' ) );
696
+
697
+ expect( getScreen( 'home' ) ).toBeInTheDocument();
698
+ expect(
699
+ getNavigationButton( 'toInvalidHtmlPathScreen' )
700
+ ).toHaveFocus();
701
+ } );
702
+
703
+ it( 'should restore focus while using goTo and goToParent', async () => {
704
+ const user = userEvent.setup();
705
+
706
+ render( <MyHierarchicalNavigation /> );
707
+
708
+ expect( getScreen( 'home' ) ).toBeInTheDocument();
709
+
710
+ // Navigate to child screen.
711
+ await user.click( getNavigationButton( 'toChildScreen' ) );
712
+ expect( getScreen( 'child' ) ).toBeInTheDocument();
713
+
714
+ // Navigate to nested screen.
715
+ await user.click( getNavigationButton( 'toNestedScreen' ) );
716
+ expect( getScreen( 'nested' ) ).toBeInTheDocument();
717
+ expect( getNavigationButton( 'back' ) ).toBeInTheDocument();
718
+
719
+ // Navigate back to child screen using the back button.
720
+ await user.click( getNavigationButton( 'back' ) );
721
+ expect( getScreen( 'child' ) ).toBeInTheDocument();
722
+ expect( getNavigationButton( 'toNestedScreen' ) ).toHaveFocus();
723
+
724
+ // Re navigate to nested screen.
725
+ await user.click( getNavigationButton( 'toNestedScreen' ) );
726
+ expect( getScreen( 'nested' ) ).toBeInTheDocument();
727
+ expect(
728
+ getNavigationButton( 'backUsingGoTo' )
729
+ ).toBeInTheDocument();
730
+
731
+ // Navigate back to child screen using the go to button.
732
+ await user.click( getNavigationButton( 'backUsingGoTo' ) );
733
+ expect( getScreen( 'child' ) ).toBeInTheDocument();
734
+ expect( getNavigationButton( 'toNestedScreen' ) ).toHaveFocus();
735
+
736
+ // Navigate back to home screen.
737
+ await user.click( getNavigationButton( 'back' ) );
738
+ expect( getNavigationButton( 'toChildScreen' ) ).toHaveFocus();
739
+ } );
496
740
  } );
497
741
  } );