@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.
- package/CHANGELOG.md +24 -0
- package/README.md +13 -0
- package/build/button/index.js +1 -1
- package/build/button/index.js.map +1 -1
- package/build/color-picker/component.js +2 -12
- package/build/color-picker/component.js.map +1 -1
- package/build/color-picker/picker.js +18 -77
- package/build/color-picker/picker.js.map +1 -1
- package/build/color-picker/types.js.map +1 -1
- package/build/custom-select-control-v2/default-component/index.js +4 -2
- package/build/custom-select-control-v2/default-component/index.js.map +1 -1
- package/build/custom-select-control-v2/index.js +1 -8
- package/build/custom-select-control-v2/index.js.map +1 -1
- package/build/custom-select-control-v2/{custom-select-item.js → item.js} +2 -1
- package/build/custom-select-control-v2/{custom-select-item.js.map → item.js.map} +1 -1
- package/build/custom-select-control-v2/legacy-component/index.js +5 -5
- package/build/custom-select-control-v2/legacy-component/index.js.map +1 -1
- package/build/input-control/input-field.js +2 -1
- package/build/input-control/input-field.js.map +1 -1
- package/build/mobile/color-settings/palette.screen.native.js +1 -0
- package/build/mobile/color-settings/palette.screen.native.js.map +1 -1
- package/build/navigable-container/container.js.map +1 -1
- package/build/navigator/navigator-provider/component.js +162 -120
- package/build/navigator/navigator-provider/component.js.map +1 -1
- package/build/navigator/navigator-screen/component.js +2 -2
- package/build/navigator/navigator-screen/component.js.map +1 -1
- package/build/popover/index.js +7 -34
- package/build/popover/index.js.map +1 -1
- package/build/range-control/styles/range-control-styles.js +29 -29
- package/build/range-control/styles/range-control-styles.js.map +1 -1
- package/build/toggle-group-control/toggle-group-control-option-base/component.js +1 -1
- package/build/toggle-group-control/toggle-group-control-option-base/component.js.map +1 -1
- package/build-module/button/index.js +1 -1
- package/build-module/button/index.js.map +1 -1
- package/build-module/color-picker/component.js +3 -13
- package/build-module/color-picker/component.js.map +1 -1
- package/build-module/color-picker/picker.js +19 -78
- package/build-module/color-picker/picker.js.map +1 -1
- package/build-module/color-picker/types.js.map +1 -1
- package/build-module/custom-select-control-v2/default-component/index.js +4 -2
- package/build-module/custom-select-control-v2/default-component/index.js.map +1 -1
- package/build-module/custom-select-control-v2/index.js +1 -2
- package/build-module/custom-select-control-v2/index.js.map +1 -1
- package/build-module/custom-select-control-v2/{custom-select-item.js → item.js} +2 -1
- package/build-module/custom-select-control-v2/{custom-select-item.js.map → item.js.map} +1 -1
- package/build-module/custom-select-control-v2/legacy-component/index.js +4 -4
- package/build-module/custom-select-control-v2/legacy-component/index.js.map +1 -1
- package/build-module/input-control/input-field.js +2 -1
- package/build-module/input-control/input-field.js.map +1 -1
- package/build-module/mobile/color-settings/palette.screen.native.js +1 -0
- package/build-module/mobile/color-settings/palette.screen.native.js.map +1 -1
- package/build-module/navigable-container/container.js.map +1 -1
- package/build-module/navigator/navigator-provider/component.js +163 -121
- package/build-module/navigator/navigator-provider/component.js.map +1 -1
- package/build-module/navigator/navigator-screen/component.js +2 -2
- package/build-module/navigator/navigator-screen/component.js.map +1 -1
- package/build-module/popover/index.js +9 -36
- package/build-module/popover/index.js.map +1 -1
- package/build-module/range-control/styles/range-control-styles.js +29 -29
- package/build-module/range-control/styles/range-control-styles.js.map +1 -1
- package/build-module/toggle-group-control/toggle-group-control-option-base/component.js +2 -2
- package/build-module/toggle-group-control/toggle-group-control-option-base/component.js.map +1 -1
- package/build-style/style-rtl.css +2 -8
- package/build-style/style.css +2 -8
- package/build-types/color-picker/component.d.ts.map +1 -1
- package/build-types/color-picker/picker.d.ts +1 -1
- package/build-types/color-picker/picker.d.ts.map +1 -1
- package/build-types/color-picker/types.d.ts +0 -3
- package/build-types/color-picker/types.d.ts.map +1 -1
- package/build-types/custom-select-control/stories/index.story.d.ts +35 -0
- package/build-types/custom-select-control/stories/index.story.d.ts.map +1 -0
- package/build-types/custom-select-control-v2/default-component/index.d.ts +5 -2
- package/build-types/custom-select-control-v2/default-component/index.d.ts.map +1 -1
- package/build-types/custom-select-control-v2/index.d.ts +1 -2
- package/build-types/custom-select-control-v2/index.d.ts.map +1 -1
- package/build-types/custom-select-control-v2/{custom-select-item.d.ts → item.d.ts} +4 -1
- package/build-types/custom-select-control-v2/item.d.ts.map +1 -0
- package/build-types/custom-select-control-v2/legacy-component/index.d.ts +2 -2
- package/build-types/custom-select-control-v2/legacy-component/index.d.ts.map +1 -1
- package/build-types/custom-select-control-v2/stories/default.story.d.ts +2 -2
- package/build-types/custom-select-control-v2/stories/default.story.d.ts.map +1 -1
- package/build-types/custom-select-control-v2/stories/legacy.story.d.ts +4 -2
- package/build-types/custom-select-control-v2/stories/legacy.story.d.ts.map +1 -1
- package/build-types/input-control/input-field.d.ts.map +1 -1
- package/build-types/navigator/navigator-provider/component.d.ts.map +1 -1
- package/build-types/navigator/navigator-screen/component.d.ts.map +1 -1
- package/build-types/popover/index.d.ts.map +1 -1
- package/package.json +21 -21
- package/src/button/index.tsx +1 -1
- package/src/color-picker/component.tsx +3 -25
- package/src/color-picker/picker.tsx +12 -96
- package/src/color-picker/types.ts +0 -3
- package/src/custom-select-control/stories/{index.story.js → index.story.tsx} +8 -3
- package/src/custom-select-control/test/index.js +24 -0
- package/src/custom-select-control-v2/README.md +27 -27
- package/src/custom-select-control-v2/default-component/index.tsx +5 -2
- package/src/custom-select-control-v2/index.tsx +1 -2
- package/src/custom-select-control-v2/{custom-select-item.tsx → item.tsx} +2 -0
- package/src/custom-select-control-v2/legacy-component/index.tsx +4 -6
- package/src/custom-select-control-v2/legacy-component/test/index.tsx +13 -10
- package/src/custom-select-control-v2/stories/default.story.tsx +16 -17
- package/src/custom-select-control-v2/stories/legacy.story.tsx +20 -35
- package/src/custom-select-control-v2/test/index.tsx +26 -16
- package/src/dropdown/style.scss +3 -3
- package/src/input-control/input-field.tsx +2 -1
- package/src/mobile/color-settings/palette.screen.native.js +5 -1
- package/src/navigable-container/container.tsx +1 -1
- package/src/navigator/navigator-provider/component.tsx +187 -188
- package/src/navigator/navigator-screen/component.tsx +2 -4
- package/src/popover/index.tsx +59 -99
- package/src/popover/style.scss +0 -9
- package/src/range-control/styles/range-control-styles.ts +1 -1
- package/src/text-control/style.scss +2 -0
- package/src/toggle-group-control/toggle-group-control-option-base/component.tsx +2 -2
- package/src/toolbar/toolbar/style.scss +1 -1
- package/tsconfig.tsbuildinfo +1 -1
- 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
|
|
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
|
-
<
|
|
44
|
+
<UncontrolledCustomSelectControlV2.Item value={ value } key={ key } />
|
|
45
45
|
) ),
|
|
46
46
|
};
|
|
47
47
|
|
|
48
|
-
const
|
|
48
|
+
const ControlledCustomSelectControl = ( props: CustomSelectProps ) => {
|
|
49
49
|
const [ value, setValue ] = useState< string | string[] >();
|
|
50
50
|
return (
|
|
51
|
-
<
|
|
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',
|
|
64
|
-
[ 'Controlled',
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
<
|
|
260
|
+
<UncontrolledCustomSelectControlV2.Item
|
|
261
|
+
key={ item }
|
|
262
|
+
value={ item }
|
|
263
|
+
>
|
|
257
264
|
{ item }
|
|
258
|
-
</
|
|
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
|
-
<
|
|
332
|
+
<UncontrolledCustomSelectControlV2.Item
|
|
333
|
+
key={ item }
|
|
334
|
+
value={ item }
|
|
335
|
+
>
|
|
326
336
|
{ item }
|
|
327
|
-
</
|
|
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
|
-
<
|
|
387
|
+
<UncontrolledCustomSelectControlV2.Item value="april-29">
|
|
378
388
|
{ renderValue( 'april-29' ) }
|
|
379
|
-
</
|
|
380
|
-
<
|
|
389
|
+
</UncontrolledCustomSelectControlV2.Item>
|
|
390
|
+
<UncontrolledCustomSelectControlV2.Item value="july-9">
|
|
381
391
|
{ renderValue( 'july-9' ) }
|
|
382
|
-
</
|
|
392
|
+
</UncontrolledCustomSelectControlV2.Item>
|
|
383
393
|
</Component>
|
|
384
394
|
);
|
|
385
395
|
|
package/src/dropdown/style.scss
CHANGED
|
@@ -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
|
-
|
|
36
|
-
|
|
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
|
|
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 )
|
|
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
|
-
|
|
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
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
-
|
|
152
|
+
screens = addScreen( state, action.screen );
|
|
153
|
+
break;
|
|
48
154
|
case 'remove':
|
|
49
|
-
|
|
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
|
-
|
|
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 [
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
{
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
-
|
|
122
|
-
|
|
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
|
|
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
|
|
231
|
-
match: matchedPath
|
|
232
|
-
|
|
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
|
|
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
|
|