@wordpress/components 27.2.0 → 27.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/README.md +13 -0
  3. package/build/button/index.js +1 -1
  4. package/build/button/index.js.map +1 -1
  5. package/build/color-picker/component.js +2 -12
  6. package/build/color-picker/component.js.map +1 -1
  7. package/build/color-picker/picker.js +18 -77
  8. package/build/color-picker/picker.js.map +1 -1
  9. package/build/color-picker/types.js.map +1 -1
  10. package/build/custom-select-control-v2/default-component/index.js +4 -2
  11. package/build/custom-select-control-v2/default-component/index.js.map +1 -1
  12. package/build/custom-select-control-v2/index.js +1 -8
  13. package/build/custom-select-control-v2/index.js.map +1 -1
  14. package/build/custom-select-control-v2/{custom-select-item.js → item.js} +2 -1
  15. package/build/custom-select-control-v2/{custom-select-item.js.map → item.js.map} +1 -1
  16. package/build/custom-select-control-v2/legacy-component/index.js +5 -5
  17. package/build/custom-select-control-v2/legacy-component/index.js.map +1 -1
  18. package/build/input-control/input-field.js +2 -1
  19. package/build/input-control/input-field.js.map +1 -1
  20. package/build/mobile/color-settings/palette.screen.native.js +1 -0
  21. package/build/mobile/color-settings/palette.screen.native.js.map +1 -1
  22. package/build/navigable-container/container.js.map +1 -1
  23. package/build/navigator/navigator-provider/component.js +162 -120
  24. package/build/navigator/navigator-provider/component.js.map +1 -1
  25. package/build/navigator/navigator-screen/component.js +2 -2
  26. package/build/navigator/navigator-screen/component.js.map +1 -1
  27. package/build/popover/index.js +7 -34
  28. package/build/popover/index.js.map +1 -1
  29. package/build/range-control/styles/range-control-styles.js +29 -29
  30. package/build/range-control/styles/range-control-styles.js.map +1 -1
  31. package/build/toggle-group-control/toggle-group-control-option-base/component.js +1 -1
  32. package/build/toggle-group-control/toggle-group-control-option-base/component.js.map +1 -1
  33. package/build-module/button/index.js +1 -1
  34. package/build-module/button/index.js.map +1 -1
  35. package/build-module/color-picker/component.js +3 -13
  36. package/build-module/color-picker/component.js.map +1 -1
  37. package/build-module/color-picker/picker.js +19 -78
  38. package/build-module/color-picker/picker.js.map +1 -1
  39. package/build-module/color-picker/types.js.map +1 -1
  40. package/build-module/custom-select-control-v2/default-component/index.js +4 -2
  41. package/build-module/custom-select-control-v2/default-component/index.js.map +1 -1
  42. package/build-module/custom-select-control-v2/index.js +1 -2
  43. package/build-module/custom-select-control-v2/index.js.map +1 -1
  44. package/build-module/custom-select-control-v2/{custom-select-item.js → item.js} +2 -1
  45. package/build-module/custom-select-control-v2/{custom-select-item.js.map → item.js.map} +1 -1
  46. package/build-module/custom-select-control-v2/legacy-component/index.js +4 -4
  47. package/build-module/custom-select-control-v2/legacy-component/index.js.map +1 -1
  48. package/build-module/input-control/input-field.js +2 -1
  49. package/build-module/input-control/input-field.js.map +1 -1
  50. package/build-module/mobile/color-settings/palette.screen.native.js +1 -0
  51. package/build-module/mobile/color-settings/palette.screen.native.js.map +1 -1
  52. package/build-module/navigable-container/container.js.map +1 -1
  53. package/build-module/navigator/navigator-provider/component.js +163 -121
  54. package/build-module/navigator/navigator-provider/component.js.map +1 -1
  55. package/build-module/navigator/navigator-screen/component.js +2 -2
  56. package/build-module/navigator/navigator-screen/component.js.map +1 -1
  57. package/build-module/popover/index.js +9 -36
  58. package/build-module/popover/index.js.map +1 -1
  59. package/build-module/range-control/styles/range-control-styles.js +29 -29
  60. package/build-module/range-control/styles/range-control-styles.js.map +1 -1
  61. package/build-module/toggle-group-control/toggle-group-control-option-base/component.js +2 -2
  62. package/build-module/toggle-group-control/toggle-group-control-option-base/component.js.map +1 -1
  63. package/build-style/style-rtl.css +2 -8
  64. package/build-style/style.css +2 -8
  65. package/build-types/color-picker/component.d.ts.map +1 -1
  66. package/build-types/color-picker/picker.d.ts +1 -1
  67. package/build-types/color-picker/picker.d.ts.map +1 -1
  68. package/build-types/color-picker/types.d.ts +0 -3
  69. package/build-types/color-picker/types.d.ts.map +1 -1
  70. package/build-types/custom-select-control/stories/index.story.d.ts +35 -0
  71. package/build-types/custom-select-control/stories/index.story.d.ts.map +1 -0
  72. package/build-types/custom-select-control-v2/default-component/index.d.ts +5 -2
  73. package/build-types/custom-select-control-v2/default-component/index.d.ts.map +1 -1
  74. package/build-types/custom-select-control-v2/index.d.ts +1 -2
  75. package/build-types/custom-select-control-v2/index.d.ts.map +1 -1
  76. package/build-types/custom-select-control-v2/{custom-select-item.d.ts → item.d.ts} +4 -1
  77. package/build-types/custom-select-control-v2/item.d.ts.map +1 -0
  78. package/build-types/custom-select-control-v2/legacy-component/index.d.ts +2 -2
  79. package/build-types/custom-select-control-v2/legacy-component/index.d.ts.map +1 -1
  80. package/build-types/custom-select-control-v2/stories/default.story.d.ts +2 -2
  81. package/build-types/custom-select-control-v2/stories/default.story.d.ts.map +1 -1
  82. package/build-types/custom-select-control-v2/stories/legacy.story.d.ts +4 -2
  83. package/build-types/custom-select-control-v2/stories/legacy.story.d.ts.map +1 -1
  84. package/build-types/input-control/input-field.d.ts.map +1 -1
  85. package/build-types/navigator/navigator-provider/component.d.ts.map +1 -1
  86. package/build-types/navigator/navigator-screen/component.d.ts.map +1 -1
  87. package/build-types/popover/index.d.ts.map +1 -1
  88. package/package.json +21 -21
  89. package/src/button/index.tsx +1 -1
  90. package/src/color-picker/component.tsx +3 -25
  91. package/src/color-picker/picker.tsx +12 -96
  92. package/src/color-picker/types.ts +0 -3
  93. package/src/custom-select-control/stories/{index.story.js → index.story.tsx} +8 -3
  94. package/src/custom-select-control/test/index.js +24 -0
  95. package/src/custom-select-control-v2/README.md +27 -27
  96. package/src/custom-select-control-v2/default-component/index.tsx +5 -2
  97. package/src/custom-select-control-v2/index.tsx +1 -2
  98. package/src/custom-select-control-v2/{custom-select-item.tsx → item.tsx} +2 -0
  99. package/src/custom-select-control-v2/legacy-component/index.tsx +4 -6
  100. package/src/custom-select-control-v2/legacy-component/test/index.tsx +13 -10
  101. package/src/custom-select-control-v2/stories/default.story.tsx +16 -17
  102. package/src/custom-select-control-v2/stories/legacy.story.tsx +20 -35
  103. package/src/custom-select-control-v2/test/index.tsx +26 -16
  104. package/src/dropdown/style.scss +3 -3
  105. package/src/input-control/input-field.tsx +2 -1
  106. package/src/mobile/color-settings/palette.screen.native.js +5 -1
  107. package/src/navigable-container/container.tsx +1 -1
  108. package/src/navigator/navigator-provider/component.tsx +187 -188
  109. package/src/navigator/navigator-screen/component.tsx +2 -4
  110. package/src/popover/index.tsx +59 -99
  111. package/src/popover/style.scss +0 -9
  112. package/src/range-control/styles/range-control-styles.ts +1 -1
  113. package/src/text-control/style.scss +2 -0
  114. package/src/toggle-group-control/toggle-group-control-option-base/component.tsx +2 -2
  115. package/src/toolbar/toolbar/style.scss +1 -1
  116. package/tsconfig.tsbuildinfo +1 -1
  117. package/build-types/custom-select-control-v2/custom-select-item.d.ts.map +0 -1
@@ -12,7 +12,7 @@ import { useState } from '@wordpress/element';
12
12
  /**
13
13
  * Internal dependencies
14
14
  */
15
- import { CustomSelect as UncontrolledCustomSelect, CustomSelectItem } from '..';
15
+ import UncontrolledCustomSelectControlV2 from '..';
16
16
  import type { CustomSelectProps } from '../types';
17
17
 
18
18
  const items = [
@@ -41,14 +41,14 @@ const items = [
41
41
  const defaultProps = {
42
42
  label: 'label!',
43
43
  children: items.map( ( { value, key } ) => (
44
- <CustomSelectItem value={ value } key={ key } />
44
+ <UncontrolledCustomSelectControlV2.Item value={ value } key={ key } />
45
45
  ) ),
46
46
  };
47
47
 
48
- const ControlledCustomSelect = ( props: CustomSelectProps ) => {
48
+ const ControlledCustomSelectControl = ( props: CustomSelectProps ) => {
49
49
  const [ value, setValue ] = useState< string | string[] >();
50
50
  return (
51
- <UncontrolledCustomSelect
51
+ <UncontrolledCustomSelectControlV2
52
52
  { ...props }
53
53
  onChange={ ( nextValue: string | string[] ) => {
54
54
  setValue( nextValue );
@@ -60,8 +60,8 @@ const ControlledCustomSelect = ( props: CustomSelectProps ) => {
60
60
  };
61
61
 
62
62
  describe.each( [
63
- [ 'Uncontrolled', UncontrolledCustomSelect ],
64
- [ 'Controlled', ControlledCustomSelect ],
63
+ [ 'Uncontrolled', UncontrolledCustomSelectControlV2 ],
64
+ [ 'Controlled', ControlledCustomSelectControl ],
65
65
  ] )( 'CustomSelectControlV2 (%s)', ( ...modeAndComponent ) => {
66
66
  const [ , Component ] = modeAndComponent;
67
67
 
@@ -175,8 +175,10 @@ describe.each( [
175
175
  await sleep();
176
176
  await press.Tab();
177
177
  expect( currentSelectedItem ).toHaveFocus();
178
+ expect( currentSelectedItem ).toHaveTextContent( 'violets' );
178
179
 
179
- await type( 'aq' );
180
+ // Ideally we would test a multi-character typeahead, but anything more than a single character is flaky
181
+ await type( 'a' );
180
182
 
181
183
  expect(
182
184
  screen.queryByRole( 'listbox', {
@@ -185,8 +187,10 @@ describe.each( [
185
187
  } )
186
188
  ).not.toBeInTheDocument();
187
189
 
190
+ // This Enter is a workaround for flakiness, and shouldn't be necessary in an actual browser
188
191
  await press.Enter();
189
- expect( currentSelectedItem ).toHaveTextContent( 'aquamarine' );
192
+
193
+ expect( currentSelectedItem ).toHaveTextContent( 'amber' );
190
194
  } );
191
195
 
192
196
  it( 'Should have correct aria-selected value for selections', async () => {
@@ -253,9 +257,12 @@ describe.each( [
253
257
  'rose blush',
254
258
  'ultraviolet morning light',
255
259
  ].map( ( item ) => (
256
- <CustomSelectItem key={ item } value={ item }>
260
+ <UncontrolledCustomSelectControlV2.Item
261
+ key={ item }
262
+ value={ item }
263
+ >
257
264
  { item }
258
- </CustomSelectItem>
265
+ </UncontrolledCustomSelectControlV2.Item>
259
266
  ) ) }
260
267
  </Component>
261
268
  );
@@ -322,9 +329,12 @@ describe.each( [
322
329
  render(
323
330
  <Component defaultValue={ defaultValues } label="Multi-select">
324
331
  { defaultValues.map( ( item ) => (
325
- <CustomSelectItem key={ item } value={ item }>
332
+ <UncontrolledCustomSelectControlV2.Item
333
+ key={ item }
334
+ value={ item }
335
+ >
326
336
  { item }
327
- </CustomSelectItem>
337
+ </UncontrolledCustomSelectControlV2.Item>
328
338
  ) ) }
329
339
  </Component>
330
340
  );
@@ -374,12 +384,12 @@ describe.each( [
374
384
 
375
385
  render(
376
386
  <Component label="Rendered" renderSelectedValue={ renderValue }>
377
- <CustomSelectItem value="april-29">
387
+ <UncontrolledCustomSelectControlV2.Item value="april-29">
378
388
  { renderValue( 'april-29' ) }
379
- </CustomSelectItem>
380
- <CustomSelectItem value="july-9">
389
+ </UncontrolledCustomSelectControlV2.Item>
390
+ <UncontrolledCustomSelectControlV2.Item value="july-9">
381
391
  { renderValue( 'july-9' ) }
382
- </CustomSelectItem>
392
+ </UncontrolledCustomSelectControlV2.Item>
383
393
  </Component>
384
394
  );
385
395
 
@@ -31,9 +31,9 @@
31
31
  margin-top: 0;
32
32
  border-top: $border-width solid $gray-400;
33
33
  padding: $grid-unit-10;
34
+ }
34
35
 
35
- .is-alternate & {
36
- border-color: $gray-900;
37
- }
36
+ &.is-alternate .components-menu-group + .components-menu-group {
37
+ border-color: $gray-900;
38
38
  }
39
39
  }
@@ -24,6 +24,7 @@ import { useDragCursor } from './utils';
24
24
  import { Input } from './styles/input-control-styles';
25
25
  import { useInputControlStateReducer } from './reducer/reducer';
26
26
  import type { InputFieldProps } from './types';
27
+ import { withIgnoreIMEEvents } from '../utils/with-ignore-ime-events';
27
28
 
28
29
  const noop = () => {};
29
30
 
@@ -222,7 +223,7 @@ function InputField(
222
223
  onBlur={ handleOnBlur }
223
224
  onChange={ handleOnChange }
224
225
  onFocus={ handleOnFocus }
225
- onKeyDown={ handleOnKeyDown }
226
+ onKeyDown={ withIgnoreIMEEvents( handleOnKeyDown ) }
226
227
  onMouseDown={ handleOnMouseDown }
227
228
  ref={ ref }
228
229
  inputSize={ size }
@@ -112,7 +112,11 @@ const PaletteScreen = () => {
112
112
 
113
113
  function getClearButton() {
114
114
  return (
115
- <TouchableWithoutFeedback onPress={ onClear } hitSlop={ HIT_SLOP }>
115
+ <TouchableWithoutFeedback
116
+ accessibilityLabel={ __( 'Clear selected color' ) }
117
+ onPress={ onClear }
118
+ hitSlop={ HIT_SLOP }
119
+ >
116
120
  <View style={ styles.clearButtonContainer }>
117
121
  <Text style={ clearButtonStyle }>{ __( 'Reset' ) }</Text>
118
122
  </View>
@@ -79,7 +79,7 @@ class NavigableContainer extends Component< NavigableContainerProps > {
79
79
 
80
80
  const { onlyBrowserTabstops } = this.props;
81
81
  const finder = onlyBrowserTabstops ? focus.tabbable : focus.focusable;
82
- const focusables = finder.find( this.container ) as HTMLElement[];
82
+ const focusables = finder.find( this.container );
83
83
 
84
84
  const index = this.getFocusableIndex( focusables, target );
85
85
  if ( index > -1 && target ) {
@@ -6,14 +6,7 @@ import type { ForwardedRef } from 'react';
6
6
  /**
7
7
  * WordPress dependencies
8
8
  */
9
- import {
10
- useMemo,
11
- useState,
12
- useCallback,
13
- useReducer,
14
- useRef,
15
- useEffect,
16
- } from '@wordpress/element';
9
+ import { useMemo, useReducer } from '@wordpress/element';
17
10
  import isShallowEqual from '@wordpress/is-shallow-equal';
18
11
 
19
12
  /**
@@ -30,26 +23,178 @@ import type {
30
23
  NavigatorProviderProps,
31
24
  NavigatorLocation,
32
25
  NavigatorContext as NavigatorContextType,
26
+ NavigateOptions,
33
27
  Screen,
28
+ NavigateToParentOptions,
34
29
  } from '../types';
35
30
 
36
31
  type MatchedPath = ReturnType< typeof patternMatch >;
37
- type ScreenAction = { type: string; screen: Screen };
32
+
33
+ type RouterAction =
34
+ | { type: 'add' | 'remove'; screen: Screen }
35
+ | { type: 'goback' }
36
+ | { type: 'goto'; path: string; options?: NavigateOptions }
37
+ | { type: 'gotoparent'; options?: NavigateToParentOptions };
38
+
39
+ type RouterState = {
40
+ screens: Screen[];
41
+ locationHistory: NavigatorLocation[];
42
+ matchedPath: MatchedPath;
43
+ };
38
44
 
39
45
  const MAX_HISTORY_LENGTH = 50;
40
46
 
41
- function screensReducer(
42
- state: Screen[] = [],
43
- action: ScreenAction
44
- ): Screen[] {
47
+ function addScreen( { screens }: RouterState, screen: Screen ) {
48
+ return [ ...screens, screen ];
49
+ }
50
+
51
+ function removeScreen( { screens }: RouterState, screen: Screen ) {
52
+ return screens.filter( ( s ) => s.id !== screen.id );
53
+ }
54
+
55
+ function goBack( { locationHistory }: RouterState ) {
56
+ if ( locationHistory.length <= 1 ) {
57
+ return locationHistory;
58
+ }
59
+ return [
60
+ ...locationHistory.slice( 0, -2 ),
61
+ {
62
+ ...locationHistory[ locationHistory.length - 2 ],
63
+ isBack: true,
64
+ hasRestoredFocus: false,
65
+ },
66
+ ];
67
+ }
68
+
69
+ function goTo(
70
+ state: RouterState,
71
+ path: string,
72
+ options: NavigateOptions = {}
73
+ ) {
74
+ const { locationHistory } = state;
75
+ const {
76
+ focusTargetSelector,
77
+ isBack = false,
78
+ skipFocus = false,
79
+ replace = false,
80
+ ...restOptions
81
+ } = options;
82
+
83
+ const isNavigatingToPreviousPath =
84
+ isBack &&
85
+ locationHistory.length > 1 &&
86
+ locationHistory[ locationHistory.length - 2 ].path === path;
87
+
88
+ if ( isNavigatingToPreviousPath ) {
89
+ return goBack( state );
90
+ }
91
+
92
+ const newLocation = {
93
+ ...restOptions,
94
+ path,
95
+ isBack,
96
+ hasRestoredFocus: false,
97
+ skipFocus,
98
+ };
99
+
100
+ if ( locationHistory.length === 0 ) {
101
+ return replace ? [] : [ newLocation ];
102
+ }
103
+
104
+ const newLocationHistory = locationHistory.slice(
105
+ locationHistory.length > MAX_HISTORY_LENGTH - 1 ? 1 : 0,
106
+ -1
107
+ );
108
+
109
+ if ( ! replace ) {
110
+ newLocationHistory.push(
111
+ // Assign `focusTargetSelector` to the previous location in history
112
+ // (the one we just navigated from).
113
+ {
114
+ ...locationHistory[ locationHistory.length - 1 ],
115
+ focusTargetSelector,
116
+ }
117
+ );
118
+ }
119
+
120
+ newLocationHistory.push( newLocation );
121
+
122
+ return newLocationHistory;
123
+ }
124
+
125
+ function goToParent(
126
+ state: RouterState,
127
+ options: NavigateToParentOptions = {}
128
+ ) {
129
+ const { locationHistory, screens } = state;
130
+ const currentPath = locationHistory[ locationHistory.length - 1 ].path;
131
+ if ( currentPath === undefined ) {
132
+ return locationHistory;
133
+ }
134
+ const parentPath = findParent( currentPath, screens );
135
+ if ( parentPath === undefined ) {
136
+ return locationHistory;
137
+ }
138
+ return goTo( state, parentPath, {
139
+ ...options,
140
+ isBack: true,
141
+ } );
142
+ }
143
+
144
+ function routerReducer(
145
+ state: RouterState,
146
+ action: RouterAction
147
+ ): RouterState {
148
+ let { screens, locationHistory, matchedPath } = state;
149
+
45
150
  switch ( action.type ) {
46
151
  case 'add':
47
- return [ ...state, action.screen ];
152
+ screens = addScreen( state, action.screen );
153
+ break;
48
154
  case 'remove':
49
- return state.filter( ( s: Screen ) => s.id !== action.screen.id );
155
+ screens = removeScreen( state, action.screen );
156
+ break;
157
+ case 'goback':
158
+ locationHistory = goBack( state );
159
+ break;
160
+ case 'goto':
161
+ locationHistory = goTo( state, action.path, action.options );
162
+ break;
163
+ case 'gotoparent':
164
+ locationHistory = goToParent( state, action.options );
165
+ break;
166
+ }
167
+
168
+ // Return early in case there is no change
169
+ if (
170
+ screens === state.screens &&
171
+ locationHistory === state.locationHistory
172
+ ) {
173
+ return state;
50
174
  }
51
175
 
52
- return state;
176
+ // Compute the matchedPath
177
+ const currentPath =
178
+ locationHistory.length > 0
179
+ ? locationHistory[ locationHistory.length - 1 ].path
180
+ : undefined;
181
+ matchedPath =
182
+ currentPath !== undefined
183
+ ? patternMatch( currentPath, screens )
184
+ : undefined;
185
+
186
+ // If the new match is the same as the previous match,
187
+ // return the previous one to keep immutability.
188
+ if (
189
+ matchedPath &&
190
+ state.matchedPath &&
191
+ matchedPath.id === state.matchedPath.id &&
192
+ isShallowEqual( matchedPath.params, state.matchedPath.params )
193
+ ) {
194
+ matchedPath = state.matchedPath;
195
+ }
196
+
197
+ return { screens, locationHistory, matchedPath };
53
198
  }
54
199
 
55
200
  function UnconnectedNavigatorProvider(
@@ -59,167 +204,33 @@ function UnconnectedNavigatorProvider(
59
204
  const { initialPath, children, className, ...otherProps } =
60
205
  useContextSystem( props, 'NavigatorProvider' );
61
206
 
62
- const [ locationHistory, setLocationHistory ] = useState<
63
- NavigatorLocation[]
64
- >( [
65
- {
66
- path: initialPath,
67
- },
68
- ] );
69
- const currentLocationHistory = useRef< NavigatorLocation[] >( [] );
70
- const [ screens, dispatch ] = useReducer( screensReducer, [] );
71
- const currentScreens = useRef< Screen[] >( [] );
72
- useEffect( () => {
73
- currentScreens.current = screens;
74
- }, [ screens ] );
75
- useEffect( () => {
76
- currentLocationHistory.current = locationHistory;
77
- }, [ locationHistory ] );
78
- const currentMatch = useRef< MatchedPath >();
79
- const matchedPath = useMemo( () => {
80
- let currentPath: string | undefined;
81
- if (
82
- locationHistory.length === 0 ||
83
- ( currentPath =
84
- locationHistory[ locationHistory.length - 1 ].path ) ===
85
- undefined
86
- ) {
87
- currentMatch.current = undefined;
88
- return undefined;
89
- }
90
-
91
- const resolvePath = ( path: string ) => {
92
- const newMatch = patternMatch( path, screens );
93
-
94
- // If the new match is the same as the current match,
95
- // return the previous one for performance reasons.
96
- if (
97
- currentMatch.current &&
98
- newMatch &&
99
- isShallowEqual(
100
- newMatch.params,
101
- currentMatch.current.params
102
- ) &&
103
- newMatch.id === currentMatch.current.id
104
- ) {
105
- return currentMatch.current;
106
- }
107
-
108
- return newMatch;
109
- };
110
-
111
- const newMatch = resolvePath( currentPath );
112
- currentMatch.current = newMatch;
113
- return newMatch;
114
- }, [ screens, locationHistory ] );
115
-
116
- const addScreen = useCallback(
117
- ( screen: Screen ) => dispatch( { type: 'add', screen } ),
118
- []
207
+ const [ routerState, dispatch ] = useReducer(
208
+ routerReducer,
209
+ initialPath,
210
+ ( path ) => ( {
211
+ screens: [],
212
+ locationHistory: [ { path } ],
213
+ matchedPath: undefined,
214
+ } )
119
215
  );
120
216
 
121
- const removeScreen = useCallback(
122
- ( screen: Screen ) => dispatch( { type: 'remove', screen } ),
217
+ // The methods are constant forever, create stable references to them.
218
+ const methods = useMemo(
219
+ () => ( {
220
+ goBack: () => dispatch( { type: 'goback' } ),
221
+ goTo: ( path: string, options?: NavigateOptions ) =>
222
+ dispatch( { type: 'goto', path, options } ),
223
+ goToParent: ( options: NavigateToParentOptions | undefined ) =>
224
+ dispatch( { type: 'gotoparent', options } ),
225
+ addScreen: ( screen: Screen ) =>
226
+ dispatch( { type: 'add', screen } ),
227
+ removeScreen: ( screen: Screen ) =>
228
+ dispatch( { type: 'remove', screen } ),
229
+ } ),
123
230
  []
124
231
  );
125
232
 
126
- const goBack: NavigatorContextType[ 'goBack' ] = useCallback( () => {
127
- setLocationHistory( ( prevLocationHistory ) => {
128
- if ( prevLocationHistory.length <= 1 ) {
129
- return prevLocationHistory;
130
- }
131
- return [
132
- ...prevLocationHistory.slice( 0, -2 ),
133
- {
134
- ...prevLocationHistory[ prevLocationHistory.length - 2 ],
135
- isBack: true,
136
- hasRestoredFocus: false,
137
- },
138
- ];
139
- } );
140
- }, [] );
141
-
142
- const goTo: NavigatorContextType[ 'goTo' ] = useCallback(
143
- ( path, options = {} ) => {
144
- const {
145
- focusTargetSelector,
146
- isBack = false,
147
- skipFocus = false,
148
- replace = false,
149
- ...restOptions
150
- } = options;
151
-
152
- const isNavigatingToPreviousPath =
153
- isBack &&
154
- currentLocationHistory.current.length > 1 &&
155
- currentLocationHistory.current[
156
- currentLocationHistory.current.length - 2
157
- ].path === path;
158
-
159
- if ( isNavigatingToPreviousPath ) {
160
- goBack();
161
- return;
162
- }
163
-
164
- setLocationHistory( ( prevLocationHistory ) => {
165
- const newLocation = {
166
- ...restOptions,
167
- path,
168
- isBack,
169
- hasRestoredFocus: false,
170
- skipFocus,
171
- };
172
-
173
- if ( prevLocationHistory.length === 0 ) {
174
- return replace ? [] : [ newLocation ];
175
- }
176
-
177
- const newLocationHistory = prevLocationHistory.slice(
178
- prevLocationHistory.length > MAX_HISTORY_LENGTH - 1 ? 1 : 0,
179
- -1
180
- );
181
-
182
- if ( ! replace ) {
183
- newLocationHistory.push(
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
- );
193
- }
194
-
195
- newLocationHistory.push( newLocation );
196
-
197
- return newLocationHistory;
198
- } );
199
- },
200
- [ goBack ]
201
- );
202
-
203
- const goToParent: NavigatorContextType[ 'goToParent' ] = useCallback(
204
- ( options = {} ) => {
205
- const currentPath =
206
- currentLocationHistory.current[
207
- currentLocationHistory.current.length - 1
208
- ].path;
209
- if ( currentPath === undefined ) {
210
- return;
211
- }
212
- const parentPath = findParent(
213
- currentPath,
214
- currentScreens.current
215
- );
216
- if ( parentPath === undefined ) {
217
- return;
218
- }
219
- goTo( parentPath, { ...options, isBack: true } );
220
- },
221
- [ goTo ]
222
- );
233
+ const { locationHistory, matchedPath } = routerState;
223
234
 
224
235
  const navigatorContextValue: NavigatorContextType = useMemo(
225
236
  () => ( {
@@ -227,23 +238,11 @@ function UnconnectedNavigatorProvider(
227
238
  ...locationHistory[ locationHistory.length - 1 ],
228
239
  isInitial: locationHistory.length === 1,
229
240
  },
230
- params: matchedPath ? matchedPath.params : {},
231
- match: matchedPath ? matchedPath.id : undefined,
232
- goTo,
233
- goBack,
234
- goToParent,
235
- addScreen,
236
- removeScreen,
241
+ params: matchedPath?.params ?? {},
242
+ match: matchedPath?.id,
243
+ ...methods,
237
244
  } ),
238
- [
239
- locationHistory,
240
- matchedPath,
241
- goTo,
242
- goBack,
243
- goToParent,
244
- addScreen,
245
- removeScreen,
246
- ]
245
+ [ locationHistory, matchedPath, methods ]
247
246
  );
248
247
 
249
248
  const cx = useCx();
@@ -106,7 +106,7 @@ function UnconnectedNavigatorScreen(
106
106
 
107
107
  // When navigating back, if a selector is provided, use it to look for the
108
108
  // target element (assumed to be a node inside the current NavigatorScreen)
109
- if ( location.isBack && location?.focusTargetSelector ) {
109
+ if ( location.isBack && location.focusTargetSelector ) {
110
110
  elementToFocus = wrapperRef.current.querySelector(
111
111
  location.focusTargetSelector
112
112
  );
@@ -115,9 +115,7 @@ function UnconnectedNavigatorScreen(
115
115
  // If the previous query didn't run or find any element to focus, fallback
116
116
  // to the first tabbable element in the screen (or the screen itself).
117
117
  if ( ! elementToFocus ) {
118
- const firstTabbable = (
119
- focus.tabbable.find( wrapperRef.current ) as HTMLElement[]
120
- )[ 0 ];
118
+ const [ firstTabbable ] = focus.tabbable.find( wrapperRef.current );
121
119
  elementToFocus = firstTabbable ?? wrapperRef.current;
122
120
  }
123
121