@wordpress/components 23.3.0 → 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 +20 -18
  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
@@ -4,9 +4,9 @@
4
4
  import { render, fireEvent, screen } from '@testing-library/react';
5
5
 
6
6
  /**
7
- * WordPress dependencies
7
+ * Internal dependencies
8
8
  */
9
- import { CustomSelectControl } from '@wordpress/components';
9
+ import CustomSelectControl from '..';
10
10
 
11
11
  describe( 'CustomSelectControl', () => {
12
12
  it( 'Captures the keypress event and does not let it propagate', () => {
@@ -116,7 +116,6 @@ exports[`DimensionControl rendering renders with custom sizes 1`] = `
116
116
  width: 100%;
117
117
  max-width: none;
118
118
  cursor: pointer;
119
- overflow: hidden;
120
119
  white-space: nowrap;
121
120
  text-overflow: ellipsis;
122
121
  font-size: 16px;
@@ -126,6 +125,7 @@ exports[`DimensionControl rendering renders with custom sizes 1`] = `
126
125
  padding-bottom: 0;
127
126
  padding-left: 8px;
128
127
  padding-right: 26px;
128
+ overflow: hidden;
129
129
  }
130
130
 
131
131
  @media ( min-width: 600px ) {
@@ -387,7 +387,6 @@ exports[`DimensionControl rendering renders with defaults 1`] = `
387
387
  width: 100%;
388
388
  max-width: none;
389
389
  cursor: pointer;
390
- overflow: hidden;
391
390
  white-space: nowrap;
392
391
  text-overflow: ellipsis;
393
392
  font-size: 16px;
@@ -397,6 +396,7 @@ exports[`DimensionControl rendering renders with defaults 1`] = `
397
396
  padding-bottom: 0;
398
397
  padding-left: 8px;
399
398
  padding-right: 26px;
399
+ overflow: hidden;
400
400
  }
401
401
 
402
402
  @media ( min-width: 600px ) {
@@ -668,7 +668,6 @@ exports[`DimensionControl rendering renders with icon and custom icon label 1`]
668
668
  width: 100%;
669
669
  max-width: none;
670
670
  cursor: pointer;
671
- overflow: hidden;
672
671
  white-space: nowrap;
673
672
  text-overflow: ellipsis;
674
673
  font-size: 16px;
@@ -678,6 +677,7 @@ exports[`DimensionControl rendering renders with icon and custom icon label 1`]
678
677
  padding-bottom: 0;
679
678
  padding-left: 8px;
680
679
  padding-right: 26px;
680
+ overflow: hidden;
681
681
  }
682
682
 
683
683
  @media ( min-width: 600px ) {
@@ -961,7 +961,6 @@ exports[`DimensionControl rendering renders with icon and default icon label 1`]
961
961
  width: 100%;
962
962
  max-width: none;
963
963
  cursor: pointer;
964
- overflow: hidden;
965
964
  white-space: nowrap;
966
965
  text-overflow: ellipsis;
967
966
  font-size: 16px;
@@ -971,6 +970,7 @@ exports[`DimensionControl rendering renders with icon and default icon label 1`]
971
970
  padding-bottom: 0;
972
971
  padding-left: 8px;
973
972
  padding-right: 26px;
973
+ overflow: hidden;
974
974
  }
975
975
 
976
976
  @media ( min-width: 600px ) {
package/src/index.js CHANGED
@@ -59,7 +59,7 @@ export {
59
59
  useCompositeState as __unstableUseCompositeState,
60
60
  } from './composite';
61
61
  export { ConfirmDialog as __experimentalConfirmDialog } from './confirm-dialog';
62
- export { default as CustomSelectControl } from './custom-select-control';
62
+ export { StableCustomSelectControl as CustomSelectControl } from './custom-select-control';
63
63
  export { default as Dashicon } from './dashicon';
64
64
  export { default as DateTimePicker, DatePicker, TimePicker } from './date-time';
65
65
  export { default as __experimentalDimensionControl } from './dimension-control';
@@ -115,6 +115,7 @@ export {
115
115
  NavigatorScreen as __experimentalNavigatorScreen,
116
116
  NavigatorButton as __experimentalNavigatorButton,
117
117
  NavigatorBackButton as __experimentalNavigatorBackButton,
118
+ NavigatorToParentButton as __experimentalNavigatorToParentButton,
118
119
  useNavigator as __experimentalUseNavigator,
119
120
  } from './navigator';
120
121
  export { default as Notice } from './notice';
@@ -126,7 +127,6 @@ export { default as PanelHeader } from './panel/header';
126
127
  export { default as PanelRow } from './panel/row';
127
128
  export { default as Placeholder } from './placeholder';
128
129
  export { default as Popover } from './popover';
129
- export { positionToPlacement as __experimentalPopoverPositionToPlacement } from './popover/utils';
130
130
  export { default as QueryControls } from './query-controls';
131
131
  export { default as __experimentalRadio } from './radio-group/radio';
132
132
  export { default as __experimentalRadioGroup } from './radio-group';
@@ -212,3 +212,6 @@ export {
212
212
  } from './higher-order/with-focus-return';
213
213
  export { default as withNotices } from './higher-order/with-notices';
214
214
  export { default as withSpokenMessages } from './higher-order/with-spoken-messages';
215
+
216
+ // Private APIs.
217
+ export { privateApis } from './private-apis';
@@ -12,5 +12,9 @@ const initialContextValue: NavigatorContextType = {
12
12
  location: {},
13
13
  goTo: () => {},
14
14
  goBack: () => {},
15
+ goToParent: () => {},
16
+ addScreen: () => {},
17
+ removeScreen: () => {},
18
+ params: {},
15
19
  };
16
20
  export const NavigatorContext = createContext( initialContextValue );
@@ -2,4 +2,5 @@ export { NavigatorProvider } from './navigator-provider';
2
2
  export { NavigatorScreen } from './navigator-screen';
3
3
  export { NavigatorButton } from './navigator-button';
4
4
  export { NavigatorBackButton } from './navigator-back-button';
5
+ export { NavigatorToParentButton } from './navigator-to-parent-button';
5
6
  export { default as useNavigator } from './use-navigator';
@@ -10,22 +10,6 @@ The `NavigatorBackButton` component can be used to navigate to a screen and shou
10
10
 
11
11
  Refer to [the `NavigatorProvider` component](/packages/components/src/navigator/navigator-provider/README.md#usage) for a usage example.
12
12
 
13
- ## Props
14
-
15
- The component accepts the following props:
16
-
17
- ### `onClick`: `React.MouseEventHandler< HTMLElement >`
18
-
19
- The callback called in response to a `click` event.
20
-
21
- - Required: No
22
-
23
- ### `path`: `string`
24
-
25
- The path of the screen to navigate to.
26
-
27
- - Required: Yes
28
-
29
13
  ### Inherited props
30
14
 
31
- `NavigatorBackButton` also inherits all of the [`Button` props](/packages/components/src/button/README.md#props), except for `href`.
15
+ `NavigatorBackButton` also inherits all of the [`Button` props](/packages/components/src/button/README.md#props), except for `href` and `target`.
@@ -9,26 +9,31 @@ import { useCallback } from '@wordpress/element';
9
9
  import { useContextSystem, WordPressComponentProps } from '../../ui/context';
10
10
  import Button from '../../button';
11
11
  import useNavigator from '../use-navigator';
12
- import type { NavigatorBackButtonProps } from '../types';
12
+ import type { NavigatorBackButtonHookProps } from '../types';
13
13
 
14
14
  export function useNavigatorBackButton(
15
- props: WordPressComponentProps< NavigatorBackButtonProps, 'button' >
15
+ props: WordPressComponentProps< NavigatorBackButtonHookProps, 'button' >
16
16
  ) {
17
17
  const {
18
18
  onClick,
19
19
  as = Button,
20
+ goToParent: goToParentProp = false,
20
21
  ...otherProps
21
22
  } = useContextSystem( props, 'NavigatorBackButton' );
22
23
 
23
- const { goBack } = useNavigator();
24
+ const { goBack, goToParent } = useNavigator();
24
25
  const handleClick: React.MouseEventHandler< HTMLButtonElement > =
25
26
  useCallback(
26
27
  ( e ) => {
27
28
  e.preventDefault();
28
- goBack();
29
+ if ( goToParentProp ) {
30
+ goToParent();
31
+ } else {
32
+ goBack();
33
+ }
29
34
  onClick?.( e );
30
35
  },
31
- [ goBack, onClick ]
36
+ [ goToParentProp, goToParent, goBack, onClick ]
32
37
  );
33
38
 
34
39
  return {
@@ -35,4 +35,4 @@ The path of the screen to navigate to. The value of this prop needs to be [a val
35
35
 
36
36
  ### Inherited props
37
37
 
38
- `NavigatorButton` also inherits all of the [`Button` props](/packages/components/src/button/README.md#props), except for `href`.
38
+ `NavigatorButton` also inherits all of the [`Button` props](/packages/components/src/button/README.md#props), except for `href` and `target`.
@@ -4,7 +4,7 @@
4
4
  This feature is still experimental. “Experimental” means this is an early implementation subject to drastic and breaking changes.
5
5
  </div>
6
6
 
7
- The `NavigatorProvider` component allows rendering nested views/panels/menus (via the [`NavigatorScreen` component](/packages/components/src/navigator/navigator-screen/README.md)) and navigate between these different states (via the [`NavigatorButton`](/packages/components/src/navigator/navigator-button/README.md) and [`NavigatorBackButton`](/packages/components/src/navigator/navigator-back-button/README.md) components or the `useNavigator` hook). The Global Styles sidebar is an example of this.
7
+ The `NavigatorProvider` component allows rendering nested views/panels/menus (via the [`NavigatorScreen` component](/packages/components/src/navigator/navigator-screen/README.md)) and navigate between these different states (via the [`NavigatorButton`](/packages/components/src/navigator/navigator-button/README.md), [`NavigatorToParentButton`](/packages/components/src/navigator/navigator-to-parent-button/README.md) and [`NavigatorBackButton`](/packages/components/src/navigator/navigator-back-button/README.md) components or the `useNavigator` hook). The Global Styles sidebar is an example of this.
8
8
 
9
9
  ## Usage
10
10
 
@@ -13,7 +13,7 @@ import {
13
13
  __experimentalNavigatorProvider as NavigatorProvider,
14
14
  __experimentalNavigatorScreen as NavigatorScreen,
15
15
  __experimentalNavigatorButton as NavigatorButton,
16
- __experimentalNavigatorBackButton as NavigatorBackButton,
16
+ __experimentalNavigatorToParentButton as NavigatorToParentButton,
17
17
  } from '@wordpress/components';
18
18
 
19
19
  const MyNavigation = () => (
@@ -27,13 +27,21 @@ const MyNavigation = () => (
27
27
 
28
28
  <NavigatorScreen path="/child">
29
29
  <p>This is the child screen.</p>
30
- <NavigatorBackButton>
30
+ <NavigatorToParentButton>
31
31
  Go back
32
- </NavigatorBackButton>
32
+ </NavigatorToParentButton>
33
33
  </NavigatorScreen>
34
34
  </NavigatorProvider>
35
35
  );
36
36
  ```
37
+ **Important note**
38
+
39
+ Parent/child navigation only works if the path you define are hierarchical, following a URL-like scheme where each path segment is separated by the `/` character.
40
+ For example:
41
+ - `/` is the root of all paths. There should always be a screen with `path="/"`.
42
+ - `/parent/child` is a child of `/parent`.
43
+ - `/parent/child/grand-child` is a child of `/parent/child`.
44
+ - `/parent/:param` is a child of `/parent` as well.
37
45
 
38
46
  ## Props
39
47
 
@@ -58,6 +66,15 @@ The `goTo` function allows navigating to a given path. The second argument can a
58
66
  The available options are:
59
67
 
60
68
  - `focusTargetSelector`: `string`. An optional property used to specify the CSS selector used to restore focus on the matching element when navigating back.
69
+ - `isBack`: `boolean`. An optional property used to specify whether the navigation should be considered as backwards (thus enabling focus restoration when possible, and causing the animation to be backwards too)
70
+
71
+ ### `goToParent`: `() => void;`
72
+
73
+ The `goToParent` function allows navigating to the parent screen.
74
+
75
+ Parent/child navigation only works if the path you define are hierarchical (see note above).
76
+
77
+ When a match is not found, the function will try to recursively navigate the path hierarchy until a matching screen (or the root `/`) are found.
61
78
 
62
79
  ### `goBack`: `() => void`
63
80
 
@@ -70,3 +87,7 @@ The `location` object represent the current location, and has a few properties:
70
87
  - `path`: `string`. The path associated to the location.
71
88
  - `isBack`: `boolean`. A flag that is `true` when the current location was reached by navigating backwards in the location stack.
72
89
  - `isInitial`: `boolean`. A flag that is `true` only for the first (root) location in the location stack.
90
+
91
+ ### `params`: `Record< string, string | string[] >`
92
+
93
+ The parsed record of parameters from the current location. For example if the current screen path is `/product/:productId` and the location is `/product/123`, then `params` will be `{ productId: '123' }`.
@@ -7,7 +7,15 @@ import { css } from '@emotion/react';
7
7
  /**
8
8
  * WordPress dependencies
9
9
  */
10
- import { useMemo, useState, useCallback } from '@wordpress/element';
10
+ import {
11
+ useMemo,
12
+ useState,
13
+ useCallback,
14
+ useReducer,
15
+ useRef,
16
+ useEffect,
17
+ } from '@wordpress/element';
18
+ import isShallowEqual from '@wordpress/is-shallow-equal';
11
19
 
12
20
  /**
13
21
  * Internal dependencies
@@ -24,7 +32,28 @@ import type {
24
32
  NavigatorProviderProps,
25
33
  NavigatorLocation,
26
34
  NavigatorContext as NavigatorContextType,
35
+ Screen,
27
36
  } from '../types';
37
+ import { patternMatch, findParent } from '../utils/router';
38
+
39
+ type MatchedPath = ReturnType< typeof patternMatch >;
40
+ type ScreenAction = { type: string; screen: Screen };
41
+
42
+ const MAX_HISTORY_LENGTH = 50;
43
+
44
+ function screensReducer(
45
+ state: Screen[] = [],
46
+ action: ScreenAction
47
+ ): Screen[] {
48
+ switch ( action.type ) {
49
+ case 'add':
50
+ return [ ...state, action.screen ];
51
+ case 'remove':
52
+ return state.filter( ( s: Screen ) => s.id !== action.screen.id );
53
+ }
54
+
55
+ return state;
56
+ }
28
57
 
29
58
  function UnconnectedNavigatorProvider(
30
59
  props: WordPressComponentProps< NavigatorProviderProps, 'div' >,
@@ -40,19 +69,60 @@ function UnconnectedNavigatorProvider(
40
69
  path: initialPath,
41
70
  },
42
71
  ] );
72
+ const currentLocationHistory = useRef< NavigatorLocation[] >( [] );
73
+ const [ screens, dispatch ] = useReducer( screensReducer, [] );
74
+ const currentScreens = useRef< Screen[] >( [] );
75
+ useEffect( () => {
76
+ currentScreens.current = screens;
77
+ }, [ screens ] );
78
+ useEffect( () => {
79
+ currentLocationHistory.current = locationHistory;
80
+ }, [ locationHistory ] );
81
+ const currentMatch = useRef< MatchedPath >();
82
+ const matchedPath = useMemo( () => {
83
+ let currentPath: string | undefined;
84
+ if (
85
+ locationHistory.length === 0 ||
86
+ ( currentPath =
87
+ locationHistory[ locationHistory.length - 1 ].path ) ===
88
+ undefined
89
+ ) {
90
+ currentMatch.current = undefined;
91
+ return undefined;
92
+ }
43
93
 
44
- const goTo: NavigatorContextType[ 'goTo' ] = useCallback(
45
- ( path, options = {} ) => {
46
- setLocationHistory( ( prevLocationHistory ) => [
47
- ...prevLocationHistory,
48
- {
49
- ...options,
50
- path,
51
- isBack: false,
52
- hasRestoredFocus: false,
53
- },
54
- ] );
55
- },
94
+ const resolvePath = ( path: string ) => {
95
+ const newMatch = patternMatch( path, screens );
96
+
97
+ // If the new match is the same as the current match,
98
+ // return the previous one for performance reasons.
99
+ if (
100
+ currentMatch.current &&
101
+ newMatch &&
102
+ isShallowEqual(
103
+ newMatch.params,
104
+ currentMatch.current.params
105
+ ) &&
106
+ newMatch.id === currentMatch.current.id
107
+ ) {
108
+ return currentMatch.current;
109
+ }
110
+
111
+ return newMatch;
112
+ };
113
+
114
+ const newMatch = resolvePath( currentPath );
115
+ currentMatch.current = newMatch;
116
+ return newMatch;
117
+ }, [ screens, locationHistory ] );
118
+
119
+ const addScreen = useCallback(
120
+ ( screen: Screen ) => dispatch( { type: 'add', screen } ),
121
+ []
122
+ );
123
+
124
+ const removeScreen = useCallback(
125
+ ( screen: Screen ) => dispatch( { type: 'remove', screen } ),
56
126
  []
57
127
  );
58
128
 
@@ -72,16 +142,102 @@ function UnconnectedNavigatorProvider(
72
142
  } );
73
143
  }, [] );
74
144
 
145
+ const goTo: NavigatorContextType[ 'goTo' ] = useCallback(
146
+ ( path, options = {} ) => {
147
+ const {
148
+ focusTargetSelector,
149
+ isBack = false,
150
+ ...restOptions
151
+ } = options;
152
+
153
+ const isNavigatingToPreviousPath =
154
+ isBack &&
155
+ currentLocationHistory.current.length > 1 &&
156
+ currentLocationHistory.current[
157
+ currentLocationHistory.current.length - 2
158
+ ].path === path;
159
+
160
+ if ( isNavigatingToPreviousPath ) {
161
+ goBack();
162
+ return;
163
+ }
164
+
165
+ setLocationHistory( ( prevLocationHistory ) => {
166
+ const newLocation = {
167
+ ...restOptions,
168
+ path,
169
+ isBack,
170
+ hasRestoredFocus: false,
171
+ };
172
+
173
+ if ( prevLocationHistory.length < 1 ) {
174
+ return [ newLocation ];
175
+ }
176
+
177
+ return [
178
+ ...prevLocationHistory.slice(
179
+ prevLocationHistory.length > MAX_HISTORY_LENGTH - 1
180
+ ? 1
181
+ : 0,
182
+ -1
183
+ ),
184
+ // Assign `focusTargetSelector` to the previous location in history
185
+ // (the one we just navigated from).
186
+ {
187
+ ...prevLocationHistory[
188
+ prevLocationHistory.length - 1
189
+ ],
190
+ focusTargetSelector,
191
+ },
192
+ newLocation,
193
+ ];
194
+ } );
195
+ },
196
+ [ goBack ]
197
+ );
198
+
199
+ const goToParent: NavigatorContextType[ 'goToParent' ] =
200
+ useCallback( () => {
201
+ const currentPath =
202
+ currentLocationHistory.current[
203
+ currentLocationHistory.current.length - 1
204
+ ].path;
205
+ if ( currentPath === undefined ) {
206
+ return;
207
+ }
208
+ const parentPath = findParent(
209
+ currentPath,
210
+ currentScreens.current
211
+ );
212
+ if ( parentPath === undefined ) {
213
+ return;
214
+ }
215
+ goTo( parentPath, { isBack: true } );
216
+ }, [ goTo ] );
217
+
75
218
  const navigatorContextValue: NavigatorContextType = useMemo(
76
219
  () => ( {
77
220
  location: {
78
221
  ...locationHistory[ locationHistory.length - 1 ],
79
222
  isInitial: locationHistory.length === 1,
80
223
  },
224
+ params: matchedPath ? matchedPath.params : {},
225
+ match: matchedPath ? matchedPath.id : undefined,
81
226
  goTo,
82
227
  goBack,
228
+ goToParent,
229
+ addScreen,
230
+ removeScreen,
83
231
  } ),
84
- [ locationHistory, goTo, goBack ]
232
+ [
233
+ locationHistory,
234
+ matchedPath,
235
+ goTo,
236
+ goBack,
237
+ goToParent,
238
+ addScreen,
239
+ removeScreen,
240
+ ]
85
241
  );
86
242
 
87
243
  const cx = useCx();
@@ -10,12 +10,14 @@ import { css } from '@emotion/react';
10
10
  * WordPress dependencies
11
11
  */
12
12
  import { focus } from '@wordpress/dom';
13
- import { useContext, useEffect, useMemo, useRef } from '@wordpress/element';
14
13
  import {
15
- useReducedMotion,
16
- useMergeRefs,
17
- usePrevious,
18
- } from '@wordpress/compose';
14
+ useContext,
15
+ useEffect,
16
+ useMemo,
17
+ useRef,
18
+ useId,
19
+ } from '@wordpress/element';
20
+ import { useReducedMotion, useMergeRefs } from '@wordpress/compose';
19
21
  import { isRTL } from '@wordpress/i18n';
20
22
  import { escapeAttribute } from '@wordpress/escape-html';
21
23
 
@@ -48,17 +50,26 @@ function UnconnectedNavigatorScreen(
48
50
  props: Props,
49
51
  forwardedRef: ForwardedRef< any >
50
52
  ) {
53
+ const screenId = useId();
51
54
  const { children, className, path, ...otherProps } = useContextSystem(
52
55
  props,
53
56
  'NavigatorScreen'
54
57
  );
55
58
 
56
59
  const prefersReducedMotion = useReducedMotion();
57
- const { location } = useContext( NavigatorContext );
58
- const isMatch = location.path === escapeAttribute( path );
60
+ const { location, match, addScreen, removeScreen } =
61
+ useContext( NavigatorContext );
62
+ const isMatch = match === screenId;
59
63
  const wrapperRef = useRef< HTMLDivElement >( null );
60
64
 
61
- const previousLocation = usePrevious( location );
65
+ useEffect( () => {
66
+ const screen = {
67
+ id: screenId,
68
+ path: escapeAttribute( path ),
69
+ };
70
+ addScreen( screen );
71
+ return () => removeScreen( screen );
72
+ }, [ screenId, path, addScreen, removeScreen ] );
62
73
 
63
74
  const cx = useCx();
64
75
  const classes = useMemo(
@@ -110,9 +121,9 @@ function UnconnectedNavigatorScreen(
110
121
 
111
122
  // When navigating back, if a selector is provided, use it to look for the
112
123
  // target element (assumed to be a node inside the current NavigatorScreen)
113
- if ( location.isBack && previousLocation?.focusTargetSelector ) {
124
+ if ( location.isBack && location?.focusTargetSelector ) {
114
125
  elementToFocus = wrapperRef.current.querySelector(
115
- previousLocation.focusTargetSelector
126
+ location.focusTargetSelector
116
127
  );
117
128
  }
118
129
 
@@ -131,7 +142,7 @@ function UnconnectedNavigatorScreen(
131
142
  isInitialLocation,
132
143
  isMatch,
133
144
  location.isBack,
134
- previousLocation?.focusTargetSelector,
145
+ location.focusTargetSelector,
135
146
  ] );
136
147
 
137
148
  const mergedWrapperRef = useMergeRefs( [ forwardedRef, wrapperRef ] );
@@ -0,0 +1,15 @@
1
+ # `NavigatorToParentButton`
2
+
3
+ <div class="callout callout-alert">
4
+ This feature is still experimental. “Experimental” means this is an early implementation subject to drastic and breaking changes.
5
+ </div>
6
+
7
+ The `NavigatorToParentButton` component can be used to navigate to a screen and should be used in combination with the [`NavigatorProvider`](/packages/components/src/navigator/navigator-provider/README.md), the [`NavigatorScreen`](/packages/components/src/navigator/navigator-screen/README.md) and the [`NavigatorButton`](/packages/components/src/navigator/navigator-button/README.md) components (or the `useNavigator` hook).
8
+
9
+ ## Usage
10
+
11
+ Refer to [the `NavigatorProvider` component](/packages/components/src/navigator/navigator-provider/README.md#usage) for a usage example.
12
+
13
+ ### Inherited props
14
+
15
+ `NavigatorToParentButton` also inherits all of the [`Button` props](/packages/components/src/button/README.md#props), except for `href` and `target`.
@@ -0,0 +1,65 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import type { ForwardedRef } from 'react';
5
+
6
+ /**
7
+ * Internal dependencies
8
+ */
9
+ import { contextConnect, WordPressComponentProps } from '../../ui/context';
10
+ import { View } from '../../view';
11
+ import { useNavigatorBackButton } from '../navigator-back-button/hook';
12
+ import type { NavigatorToParentButtonProps } from '../types';
13
+
14
+ function UnconnectedNavigatorToParentButton(
15
+ props: WordPressComponentProps< NavigatorToParentButtonProps, 'button' >,
16
+ forwardedRef: ForwardedRef< any >
17
+ ) {
18
+ const navigatorToParentButtonProps = useNavigatorBackButton( {
19
+ ...props,
20
+ goToParent: true,
21
+ } );
22
+
23
+ return <View ref={ forwardedRef } { ...navigatorToParentButtonProps } />;
24
+ }
25
+
26
+ /*
27
+ * The `NavigatorToParentButton` component can be used to navigate to a screen and
28
+ * should be used in combination with the `NavigatorProvider`, the
29
+ * `NavigatorScreen` and the `NavigatorButton` components (or the `useNavigator`
30
+ * hook).
31
+ *
32
+ * @example
33
+ * ```jsx
34
+ * import {
35
+ * __experimentalNavigatorProvider as NavigatorProvider,
36
+ * __experimentalNavigatorScreen as NavigatorScreen,
37
+ * __experimentalNavigatorButton as NavigatorButton,
38
+ * __experimentalNavigatorToParentButton as NavigatorToParentButton,
39
+ * } from '@wordpress/components';
40
+ *
41
+ * const MyNavigation = () => (
42
+ * <NavigatorProvider initialPath="/">
43
+ * <NavigatorScreen path="/">
44
+ * <p>This is the home screen.</p>
45
+ * <NavigatorButton path="/child">
46
+ * Navigate to child screen.
47
+ * </NavigatorButton>
48
+ * </NavigatorScreen>
49
+ *
50
+ * <NavigatorScreen path="/child">
51
+ * <p>This is the child screen.</p>
52
+ * <NavigatorToParentButton>
53
+ * Go to parent
54
+ * </NavigatorToParentButton>
55
+ * </NavigatorScreen>
56
+ * </NavigatorProvider>
57
+ * );
58
+ * ```
59
+ */
60
+ export const NavigatorToParentButton = contextConnect(
61
+ UnconnectedNavigatorToParentButton,
62
+ 'NavigatorToParentButton'
63
+ );
64
+
65
+ export default NavigatorToParentButton;
@@ -0,0 +1 @@
1
+ export { default as NavigatorToParentButton } from './component';