@wordpress/block-editor 8.1.2 → 8.2.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 +10 -0
- package/README.md +0 -24
- package/build/components/block-list/block.js +16 -2
- package/build/components/block-list/block.js.map +1 -1
- package/build/components/block-list/block.native.js +1 -1
- package/build/components/block-list/block.native.js.map +1 -1
- package/build/components/block-list/use-block-props/use-focus-first-element.js +3 -0
- package/build/components/block-list/use-block-props/use-focus-first-element.js.map +1 -1
- package/build/components/block-list/use-block-props/use-multi-selection.js +25 -27
- package/build/components/block-list/use-block-props/use-multi-selection.js.map +1 -1
- package/build/components/block-settings/container.native.js +1 -5
- package/build/components/block-settings/container.native.js.map +1 -1
- package/build/components/index.js +9 -0
- package/build/components/index.js.map +1 -1
- package/build/components/index.native.js +9 -0
- package/build/components/index.native.js.map +1 -1
- package/build/components/inserter/block-patterns-tab.js +3 -3
- package/build/components/inserter/block-patterns-tab.js.map +1 -1
- package/build/components/inserter/quick-inserter.js +19 -7
- package/build/components/inserter/quick-inserter.js.map +1 -1
- package/build/components/inserter/search-results.js +28 -11
- package/build/components/inserter/search-results.js.map +1 -1
- package/build/components/line-height-control/index.js +61 -43
- package/build/components/line-height-control/index.js.map +1 -1
- package/build/components/list-view/block-contents.js +8 -4
- package/build/components/list-view/block-contents.js.map +1 -1
- package/build/components/list-view/block-select-button.js +0 -1
- package/build/components/list-view/block-select-button.js.map +1 -1
- package/build/components/list-view/block.js +19 -8
- package/build/components/list-view/block.js.map +1 -1
- package/build/components/list-view/branch.js +2 -1
- package/build/components/list-view/branch.js.map +1 -1
- package/build/components/list-view/index.js +49 -41
- package/build/components/list-view/index.js.map +1 -1
- package/build/components/list-view/use-block-selection.js +139 -0
- package/build/components/list-view/use-block-selection.js.map +1 -0
- package/build/components/list-view/use-list-view-expand-selected-item.js +60 -0
- package/build/components/list-view/use-list-view-expand-selected-item.js.map +1 -0
- package/build/components/list-view/utils.js +29 -1
- package/build/components/list-view/utils.js.map +1 -1
- package/build/components/rich-text/index.js +7 -12
- package/build/components/rich-text/index.js.map +1 -1
- package/build/components/rich-text/use-paste-handler.js +0 -1
- package/build/components/rich-text/use-paste-handler.js.map +1 -1
- package/build/components/writing-flow/index.js +1 -0
- package/build/components/writing-flow/index.js.map +1 -1
- package/build/components/writing-flow/use-multi-selection.js +22 -24
- package/build/components/writing-flow/use-multi-selection.js.map +1 -1
- package/build/components/writing-flow/use-select-all.js +3 -2
- package/build/components/writing-flow/use-select-all.js.map +1 -1
- package/build/hooks/custom-class-name.js +40 -0
- package/build/hooks/custom-class-name.js.map +1 -1
- package/build/hooks/line-height.js +2 -0
- package/build/hooks/line-height.js.map +1 -1
- package/build/hooks/style.js +27 -11
- package/build/hooks/style.js.map +1 -1
- package/build/layouts/flow.js +7 -5
- package/build/layouts/flow.js.map +1 -1
- package/build/store/actions.js +51 -44
- package/build/store/actions.js.map +1 -1
- package/build/store/defaults.js +1 -0
- package/build/store/defaults.js.map +1 -1
- package/build/store/index.js +1 -2
- package/build/store/index.js.map +1 -1
- package/build/store/reducer.js +2 -2
- package/build/store/reducer.js.map +1 -1
- package/build/utils/index.js +0 -14
- package/build/utils/index.js.map +1 -1
- package/build-module/components/block-list/block.js +17 -3
- package/build-module/components/block-list/block.js.map +1 -1
- package/build-module/components/block-list/block.native.js +2 -2
- package/build-module/components/block-list/block.native.js.map +1 -1
- package/build-module/components/block-list/use-block-props/use-focus-first-element.js +2 -0
- package/build-module/components/block-list/use-block-props/use-focus-first-element.js.map +1 -1
- package/build-module/components/block-list/use-block-props/use-multi-selection.js +23 -28
- package/build-module/components/block-list/use-block-props/use-multi-selection.js.map +1 -1
- package/build-module/components/block-settings/container.native.js +2 -6
- package/build-module/components/block-settings/container.native.js.map +1 -1
- package/build-module/components/index.js +1 -0
- package/build-module/components/index.js.map +1 -1
- package/build-module/components/index.native.js +1 -0
- package/build-module/components/index.native.js.map +1 -1
- package/build-module/components/inserter/block-patterns-tab.js +3 -3
- package/build-module/components/inserter/block-patterns-tab.js.map +1 -1
- package/build-module/components/inserter/quick-inserter.js +19 -7
- package/build-module/components/inserter/quick-inserter.js.map +1 -1
- package/build-module/components/inserter/search-results.js +28 -11
- package/build-module/components/inserter/search-results.js.map +1 -1
- package/build-module/components/line-height-control/index.js +59 -43
- package/build-module/components/line-height-control/index.js.map +1 -1
- package/build-module/components/list-view/block-contents.js +8 -4
- package/build-module/components/list-view/block-contents.js.map +1 -1
- package/build-module/components/list-view/block-select-button.js +0 -1
- package/build-module/components/list-view/block-select-button.js.map +1 -1
- package/build-module/components/list-view/block.js +19 -8
- package/build-module/components/list-view/block.js.map +1 -1
- package/build-module/components/list-view/branch.js +2 -1
- package/build-module/components/list-view/branch.js.map +1 -1
- package/build-module/components/list-view/index.js +46 -42
- package/build-module/components/list-view/index.js.map +1 -1
- package/build-module/components/list-view/use-block-selection.js +123 -0
- package/build-module/components/list-view/use-block-selection.js.map +1 -0
- package/build-module/components/list-view/use-list-view-expand-selected-item.js +50 -0
- package/build-module/components/list-view/use-list-view-expand-selected-item.js.map +1 -0
- package/build-module/components/list-view/utils.js +25 -0
- package/build-module/components/list-view/utils.js.map +1 -1
- package/build-module/components/rich-text/index.js +7 -12
- package/build-module/components/rich-text/index.js.map +1 -1
- package/build-module/components/rich-text/use-paste-handler.js +0 -1
- package/build-module/components/rich-text/use-paste-handler.js.map +1 -1
- package/build-module/components/writing-flow/index.js +1 -0
- package/build-module/components/writing-flow/index.js.map +1 -1
- package/build-module/components/writing-flow/use-multi-selection.js +21 -21
- package/build-module/components/writing-flow/use-multi-selection.js.map +1 -1
- package/build-module/components/writing-flow/use-select-all.js +3 -2
- package/build-module/components/writing-flow/use-select-all.js.map +1 -1
- package/build-module/hooks/custom-class-name.js +38 -0
- package/build-module/hooks/custom-class-name.js.map +1 -1
- package/build-module/hooks/line-height.js +2 -0
- package/build-module/hooks/line-height.js.map +1 -1
- package/build-module/hooks/style.js +26 -11
- package/build-module/hooks/style.js.map +1 -1
- package/build-module/layouts/flow.js +7 -5
- package/build-module/layouts/flow.js.map +1 -1
- package/build-module/store/actions.js +48 -41
- package/build-module/store/actions.js.map +1 -1
- package/build-module/store/defaults.js +1 -0
- package/build-module/store/defaults.js.map +1 -1
- package/build-module/store/index.js +1 -2
- package/build-module/store/index.js.map +1 -1
- package/build-module/store/reducer.js +2 -2
- package/build-module/store/reducer.js.map +1 -1
- package/build-module/utils/index.js +0 -1
- package/build-module/utils/index.js.map +1 -1
- package/build-style/style-rtl.css +3 -12
- package/build-style/style.css +3 -12
- package/package.json +12 -11
- package/src/components/block-list/block.js +27 -3
- package/src/components/block-list/block.native.js +2 -1
- package/src/components/block-list/style.scss +3 -1
- package/src/components/block-list/use-block-props/use-focus-first-element.js +3 -0
- package/src/components/block-list/use-block-props/use-multi-selection.js +22 -30
- package/src/components/block-settings/container.native.js +5 -6
- package/src/components/index.js +1 -0
- package/src/components/index.native.js +1 -0
- package/src/components/inserter/block-patterns-tab.js +12 -16
- package/src/components/inserter/quick-inserter.js +31 -9
- package/src/components/inserter/search-results.js +54 -42
- package/src/components/line-height-control/README.md +13 -2
- package/src/components/line-height-control/index.js +63 -40
- package/src/components/line-height-control/stories/index.js +33 -0
- package/src/components/line-height-control/test/index.js +61 -0
- package/src/components/list-view/README.md +2 -2
- package/src/components/list-view/block-contents.js +10 -3
- package/src/components/list-view/block-select-button.js +0 -1
- package/src/components/list-view/block.js +29 -9
- package/src/components/list-view/branch.js +1 -0
- package/src/components/list-view/index.js +56 -30
- package/src/components/list-view/test/utils.js +50 -0
- package/src/components/list-view/use-block-selection.js +163 -0
- package/src/components/list-view/use-list-view-expand-selected-item.js +58 -0
- package/src/components/list-view/utils.js +31 -0
- package/src/components/rich-text/index.js +7 -14
- package/src/components/rich-text/use-paste-handler.js +0 -1
- package/src/components/writing-flow/index.js +1 -0
- package/src/components/writing-flow/use-multi-selection.js +17 -20
- package/src/components/writing-flow/use-select-all.js +6 -2
- package/src/hooks/custom-class-name.js +45 -0
- package/src/hooks/line-height.js +2 -0
- package/src/hooks/style.js +26 -11
- package/src/hooks/typography.scss +0 -4
- package/src/layouts/flow.js +10 -5
- package/src/store/actions.js +20 -10
- package/src/store/defaults.js +1 -0
- package/src/store/index.js +0 -1
- package/src/store/reducer.js +2 -1
- package/src/store/test/actions.js +1 -1
- package/src/store/test/reducer.js +9 -0
- package/src/style.scss +0 -1
- package/src/utils/index.js +0 -1
- package/build/utils/theme.js +0 -63
- package/build/utils/theme.js.map +0 -1
- package/build-module/utils/theme.js +0 -53
- package/build-module/utils/theme.js.map +0 -1
- package/src/components/line-height-control/style.scss +0 -8
- package/src/components/writing-flow/test/use-multi-selection.js +0 -36
- package/src/utils/theme.js +0 -48
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* External dependencies
|
|
3
|
+
*/
|
|
4
|
+
import { fireEvent, render, screen } from '@testing-library/react';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* WordPress dependencies
|
|
8
|
+
*/
|
|
9
|
+
import { useState } from '@wordpress/element';
|
|
10
|
+
import { UP, DOWN } from '@wordpress/keycodes';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Internal dependencies
|
|
14
|
+
*/
|
|
15
|
+
import LineHeightControl from '../';
|
|
16
|
+
import { BASE_DEFAULT_VALUE, STEP } from '../utils';
|
|
17
|
+
|
|
18
|
+
const ControlledLineHeightControl = () => {
|
|
19
|
+
const [ value, setValue ] = useState();
|
|
20
|
+
return (
|
|
21
|
+
<LineHeightControl
|
|
22
|
+
value={ value }
|
|
23
|
+
onChange={ setValue }
|
|
24
|
+
__nextHasNoMarginBottom={ true }
|
|
25
|
+
/>
|
|
26
|
+
);
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
describe( 'LineHeightControl', () => {
|
|
30
|
+
it( 'should immediately step up from the default value if up-arrowed from an unset state', () => {
|
|
31
|
+
render( <ControlledLineHeightControl /> );
|
|
32
|
+
const input = screen.getByRole( 'spinbutton' );
|
|
33
|
+
input.focus();
|
|
34
|
+
fireEvent.keyDown( input, { keyCode: UP } );
|
|
35
|
+
expect( input ).toHaveValue( BASE_DEFAULT_VALUE + STEP );
|
|
36
|
+
} );
|
|
37
|
+
|
|
38
|
+
it( 'should immediately step down from the default value if down-arrowed from an unset state', () => {
|
|
39
|
+
render( <ControlledLineHeightControl /> );
|
|
40
|
+
const input = screen.getByRole( 'spinbutton' );
|
|
41
|
+
input.focus();
|
|
42
|
+
fireEvent.keyDown( input, { keyCode: DOWN } );
|
|
43
|
+
expect( input ).toHaveValue( BASE_DEFAULT_VALUE - STEP );
|
|
44
|
+
} );
|
|
45
|
+
|
|
46
|
+
it( 'should immediately step up from the default value if spin button up was clicked from an unset state', () => {
|
|
47
|
+
render( <ControlledLineHeightControl /> );
|
|
48
|
+
const input = screen.getByRole( 'spinbutton' );
|
|
49
|
+
input.focus();
|
|
50
|
+
fireEvent.change( input, { target: { value: 0.1 } } ); // simulates click on spin button up
|
|
51
|
+
expect( input ).toHaveValue( BASE_DEFAULT_VALUE + STEP );
|
|
52
|
+
} );
|
|
53
|
+
|
|
54
|
+
it( 'should immediately step down from the default value if spin button down was clicked from an unset state', () => {
|
|
55
|
+
render( <ControlledLineHeightControl /> );
|
|
56
|
+
const input = screen.getByRole( 'spinbutton' );
|
|
57
|
+
input.focus();
|
|
58
|
+
fireEvent.change( input, { target: { value: 0 } } ); // simulates click on spin button down
|
|
59
|
+
expect( input ).toHaveValue( BASE_DEFAULT_VALUE - STEP );
|
|
60
|
+
} );
|
|
61
|
+
} );
|
|
@@ -4,7 +4,7 @@ The ListView component provides an overview of the hierarchical structure of all
|
|
|
4
4
|
|
|
5
5
|
Blocks that have child blocks (such as group or column blocks) are presented with the parent at the top and the nested children below.
|
|
6
6
|
|
|
7
|
-
In addition to presenting the structure of the blocks in the editor, the ListView component lets users navigate to each block by clicking on its line in the hierarchy tree.
|
|
7
|
+
In addition to presenting the structure of the blocks in the editor, the ListView component lets users navigate to each block by clicking on its line in the hierarchy tree. Multiple blocks at the same level of nesting can be selected by holding down the `SHIFT` key and clicking blocks within the list.
|
|
8
8
|
|
|
9
9
|

|
|
10
10
|

|
|
@@ -23,7 +23,7 @@ Renders a list view with default syles.
|
|
|
23
23
|
```jsx
|
|
24
24
|
import { ListView } from '@wordpress/block-editor';
|
|
25
25
|
|
|
26
|
-
const MyNavigation = () => <ListView
|
|
26
|
+
const MyNavigation = () => <ListView />;
|
|
27
27
|
```
|
|
28
28
|
|
|
29
29
|
## Related components
|
|
@@ -27,6 +27,7 @@ const ListViewBlockContents = forwardRef(
|
|
|
27
27
|
siblingBlockCount,
|
|
28
28
|
level,
|
|
29
29
|
isExpanded,
|
|
30
|
+
selectedClientIds,
|
|
30
31
|
...props
|
|
31
32
|
},
|
|
32
33
|
ref
|
|
@@ -36,12 +37,10 @@ const ListViewBlockContents = forwardRef(
|
|
|
36
37
|
const { blockMovingClientId, selectedBlockInBlockEditor } = useSelect(
|
|
37
38
|
( select ) => {
|
|
38
39
|
const {
|
|
39
|
-
getBlockRootClientId,
|
|
40
40
|
hasBlockMovingClientId,
|
|
41
41
|
getSelectedBlockClientId,
|
|
42
42
|
} = select( blockEditorStore );
|
|
43
43
|
return {
|
|
44
|
-
rootClientId: getBlockRootClientId( clientId ) || '',
|
|
45
44
|
blockMovingClientId: hasBlockMovingClientId(),
|
|
46
45
|
selectedBlockInBlockEditor: getSelectedBlockClientId(),
|
|
47
46
|
};
|
|
@@ -56,8 +55,16 @@ const ListViewBlockContents = forwardRef(
|
|
|
56
55
|
'is-dropping-before': isBlockMoveTarget,
|
|
57
56
|
} );
|
|
58
57
|
|
|
58
|
+
// Only include all selected blocks if the currently clicked on block
|
|
59
|
+
// is one of the selected blocks. This ensures that if a user attempts
|
|
60
|
+
// to drag a block that isn't part of the selection, they're still able
|
|
61
|
+
// to drag it and rearrange its position.
|
|
62
|
+
const draggableClientIds = selectedClientIds.includes( clientId )
|
|
63
|
+
? selectedClientIds
|
|
64
|
+
: [ clientId ];
|
|
65
|
+
|
|
59
66
|
return (
|
|
60
|
-
<BlockDraggable clientIds={
|
|
67
|
+
<BlockDraggable clientIds={ draggableClientIds }>
|
|
61
68
|
{ ( { draggable, onDragStart, onDragEnd } ) => (
|
|
62
69
|
<ListViewBlockSelectButton
|
|
63
70
|
ref={ ref }
|
|
@@ -48,6 +48,7 @@ function ListViewBlock( {
|
|
|
48
48
|
showBlockMovers,
|
|
49
49
|
path,
|
|
50
50
|
isExpanded,
|
|
51
|
+
selectedClientIds,
|
|
51
52
|
} ) {
|
|
52
53
|
const cellRef = useRef( null );
|
|
53
54
|
const [ isHovered, setIsHovered ] = useState( false );
|
|
@@ -104,14 +105,22 @@ function ListViewBlock( {
|
|
|
104
105
|
|
|
105
106
|
const selectEditorBlock = useCallback(
|
|
106
107
|
( event ) => {
|
|
107
|
-
event
|
|
108
|
-
selectBlock( clientId );
|
|
108
|
+
selectBlock( event, clientId );
|
|
109
109
|
},
|
|
110
110
|
[ clientId, selectBlock ]
|
|
111
111
|
);
|
|
112
112
|
|
|
113
|
+
const selectDuplicatedBlock = useCallback(
|
|
114
|
+
( newClientId ) => {
|
|
115
|
+
selectBlock( undefined, newClientId );
|
|
116
|
+
},
|
|
117
|
+
[ selectBlock ]
|
|
118
|
+
);
|
|
119
|
+
|
|
113
120
|
const toggleExpanded = useCallback(
|
|
114
121
|
( event ) => {
|
|
122
|
+
// Prevent shift+click from opening link in a new window when toggling.
|
|
123
|
+
event.preventDefault();
|
|
115
124
|
event.stopPropagation();
|
|
116
125
|
if ( isExpanded === true ) {
|
|
117
126
|
collapse( clientId );
|
|
@@ -146,11 +155,21 @@ function ListViewBlock( {
|
|
|
146
155
|
} );
|
|
147
156
|
|
|
148
157
|
const blockInformation = useBlockDisplayInformation( clientId );
|
|
149
|
-
const settingsAriaLabel =
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
158
|
+
const settingsAriaLabel = blockInformation
|
|
159
|
+
? sprintf(
|
|
160
|
+
// translators: %s: The title of the block.
|
|
161
|
+
__( 'Options for %s block' ),
|
|
162
|
+
blockInformation.title
|
|
163
|
+
)
|
|
164
|
+
: __( 'Options' );
|
|
165
|
+
|
|
166
|
+
// Only include all selected blocks if the currently clicked on block
|
|
167
|
+
// is one of the selected blocks. This ensures that if a user attempts
|
|
168
|
+
// to alter a block that isn't part of the selection, they're still able
|
|
169
|
+
// to do so.
|
|
170
|
+
const dropdownClientIds = selectedClientIds.includes( clientId )
|
|
171
|
+
? selectedClientIds
|
|
172
|
+
: [ clientId ];
|
|
154
173
|
|
|
155
174
|
return (
|
|
156
175
|
<ListViewLeaf
|
|
@@ -186,6 +205,7 @@ function ListViewBlock( {
|
|
|
186
205
|
tabIndex={ tabIndex }
|
|
187
206
|
onFocus={ onFocus }
|
|
188
207
|
isExpanded={ isExpanded }
|
|
208
|
+
selectedClientIds={ selectedClientIds }
|
|
189
209
|
/>
|
|
190
210
|
</div>
|
|
191
211
|
) }
|
|
@@ -226,7 +246,7 @@ function ListViewBlock( {
|
|
|
226
246
|
<TreeGridCell className={ listViewBlockSettingsClassName }>
|
|
227
247
|
{ ( { ref, tabIndex, onFocus } ) => (
|
|
228
248
|
<BlockSettingsDropdown
|
|
229
|
-
clientIds={
|
|
249
|
+
clientIds={ dropdownClientIds }
|
|
230
250
|
icon={ moreVertical }
|
|
231
251
|
label={ settingsAriaLabel }
|
|
232
252
|
toggleProps={ {
|
|
@@ -236,7 +256,7 @@ function ListViewBlock( {
|
|
|
236
256
|
onFocus,
|
|
237
257
|
} }
|
|
238
258
|
disableOpenOnArrowDown
|
|
239
|
-
__experimentalSelectBlock={
|
|
259
|
+
__experimentalSelectBlock={ selectDuplicatedBlock }
|
|
240
260
|
/>
|
|
241
261
|
) }
|
|
242
262
|
</TreeGridCell>
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
__experimentalUseFixedWindowList as useFixedWindowList,
|
|
7
7
|
} from '@wordpress/compose';
|
|
8
8
|
import { __experimentalTreeGrid as TreeGrid } from '@wordpress/components';
|
|
9
|
-
import { AsyncModeProvider,
|
|
9
|
+
import { AsyncModeProvider, useSelect } from '@wordpress/data';
|
|
10
10
|
import {
|
|
11
11
|
useCallback,
|
|
12
12
|
useEffect,
|
|
@@ -23,41 +23,47 @@ import { __ } from '@wordpress/i18n';
|
|
|
23
23
|
import ListViewBranch from './branch';
|
|
24
24
|
import { ListViewContext } from './context';
|
|
25
25
|
import ListViewDropIndicator from './drop-indicator';
|
|
26
|
+
import useBlockSelection from './use-block-selection';
|
|
26
27
|
import useListViewClientIds from './use-list-view-client-ids';
|
|
27
28
|
import useListViewDropZone from './use-list-view-drop-zone';
|
|
29
|
+
import useListViewExpandSelectedItem from './use-list-view-expand-selected-item';
|
|
28
30
|
import { store as blockEditorStore } from '../../store';
|
|
29
31
|
|
|
30
|
-
const noop = () => {};
|
|
31
32
|
const expanded = ( state, action ) => {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
33
|
+
if ( Array.isArray( action.clientIds ) ) {
|
|
34
|
+
return {
|
|
35
|
+
...state,
|
|
36
|
+
...action.clientIds.reduce(
|
|
37
|
+
( newState, id ) => ( {
|
|
38
|
+
...newState,
|
|
39
|
+
[ id ]: action.type === 'expand',
|
|
40
|
+
} ),
|
|
41
|
+
{}
|
|
42
|
+
),
|
|
43
|
+
};
|
|
39
44
|
}
|
|
45
|
+
return state;
|
|
40
46
|
};
|
|
41
47
|
|
|
48
|
+
export const BLOCK_LIST_ITEM_HEIGHT = 36;
|
|
49
|
+
|
|
42
50
|
/**
|
|
43
51
|
* Wrap `ListViewRows` with `TreeGrid`. ListViewRows is a
|
|
44
52
|
* recursive component (it renders itself), so this ensures TreeGrid is only
|
|
45
53
|
* present at the very top of the navigation grid.
|
|
46
54
|
*
|
|
47
|
-
* @param {Object}
|
|
48
|
-
* @param {Array}
|
|
49
|
-
* @param {
|
|
50
|
-
* @param {boolean}
|
|
51
|
-
* @param {boolean}
|
|
52
|
-
* @param {boolean}
|
|
53
|
-
* @param {boolean}
|
|
54
|
-
* @param {
|
|
55
|
-
* @param {Object} ref Forwarded ref
|
|
55
|
+
* @param {Object} props Components props.
|
|
56
|
+
* @param {Array} props.blocks Custom subset of block client IDs to be used instead of the default hierarchy.
|
|
57
|
+
* @param {boolean} props.showNestedBlocks Flag to enable displaying nested blocks.
|
|
58
|
+
* @param {boolean} props.showBlockMovers Flag to enable block movers
|
|
59
|
+
* @param {boolean} props.__experimentalFeatures Flag to enable experimental features.
|
|
60
|
+
* @param {boolean} props.__experimentalPersistentListViewFeatures Flag to enable features for the Persistent List View experiment.
|
|
61
|
+
* @param {boolean} props.__experimentalHideContainerBlockActions Flag to hide actions of top level blocks (like core/widget-area)
|
|
62
|
+
* @param {Object} ref Forwarded ref
|
|
56
63
|
*/
|
|
57
64
|
function ListView(
|
|
58
65
|
{
|
|
59
66
|
blocks,
|
|
60
|
-
onSelect = noop,
|
|
61
67
|
__experimentalFeatures,
|
|
62
68
|
__experimentalPersistentListViewFeatures,
|
|
63
69
|
__experimentalHideContainerBlockActions,
|
|
@@ -72,7 +78,7 @@ function ListView(
|
|
|
72
78
|
draggedClientIds,
|
|
73
79
|
selectedClientIds,
|
|
74
80
|
} = useListViewClientIds( blocks );
|
|
75
|
-
|
|
81
|
+
|
|
76
82
|
const { visibleBlockCount } = useSelect(
|
|
77
83
|
( select ) => {
|
|
78
84
|
const { getGlobalBlockCount, getClientIdsOfDescendants } = select(
|
|
@@ -88,13 +94,9 @@ function ListView(
|
|
|
88
94
|
},
|
|
89
95
|
[ draggedClientIds ]
|
|
90
96
|
);
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
onSelect( clientId );
|
|
95
|
-
},
|
|
96
|
-
[ selectBlock, onSelect ]
|
|
97
|
-
);
|
|
97
|
+
|
|
98
|
+
const { updateBlockSelection } = useBlockSelection();
|
|
99
|
+
|
|
98
100
|
const [ expandedState, setExpandedState ] = useReducer( expanded, {} );
|
|
99
101
|
|
|
100
102
|
const { ref: dropZoneRef, target: blockDropTarget } = useListViewDropZone();
|
|
@@ -102,6 +104,17 @@ function ListView(
|
|
|
102
104
|
const treeGridRef = useMergeRefs( [ elementRef, dropZoneRef, ref ] );
|
|
103
105
|
|
|
104
106
|
const isMounted = useRef( false );
|
|
107
|
+
const { setSelectedTreeId } = useListViewExpandSelectedItem( {
|
|
108
|
+
firstSelectedBlockClientId: selectedClientIds[ 0 ],
|
|
109
|
+
setExpandedState,
|
|
110
|
+
} );
|
|
111
|
+
const selectEditorBlock = useCallback(
|
|
112
|
+
( event, clientId ) => {
|
|
113
|
+
updateBlockSelection( event, clientId );
|
|
114
|
+
setSelectedTreeId( clientId );
|
|
115
|
+
},
|
|
116
|
+
[ setSelectedTreeId, updateBlockSelection ]
|
|
117
|
+
);
|
|
105
118
|
useEffect( () => {
|
|
106
119
|
isMounted.current = true;
|
|
107
120
|
}, [] );
|
|
@@ -111,7 +124,7 @@ function ListView(
|
|
|
111
124
|
// See: https://github.com/WordPress/gutenberg/pull/35230 for additional context.
|
|
112
125
|
const [ fixedListWindow ] = useFixedWindowList(
|
|
113
126
|
elementRef,
|
|
114
|
-
|
|
127
|
+
BLOCK_LIST_ITEM_HEIGHT,
|
|
115
128
|
visibleBlockCount,
|
|
116
129
|
{
|
|
117
130
|
useWindowing: __experimentalPersistentListViewFeatures,
|
|
@@ -124,7 +137,7 @@ function ListView(
|
|
|
124
137
|
if ( ! clientId ) {
|
|
125
138
|
return;
|
|
126
139
|
}
|
|
127
|
-
setExpandedState( { type: 'expand', clientId } );
|
|
140
|
+
setExpandedState( { type: 'expand', clientIds: [ clientId ] } );
|
|
128
141
|
},
|
|
129
142
|
[ setExpandedState ]
|
|
130
143
|
);
|
|
@@ -133,7 +146,7 @@ function ListView(
|
|
|
133
146
|
if ( ! clientId ) {
|
|
134
147
|
return;
|
|
135
148
|
}
|
|
136
|
-
setExpandedState( { type: 'collapse', clientId } );
|
|
149
|
+
setExpandedState( { type: 'collapse', clientIds: [ clientId ] } );
|
|
137
150
|
},
|
|
138
151
|
[ setExpandedState ]
|
|
139
152
|
);
|
|
@@ -149,6 +162,18 @@ function ListView(
|
|
|
149
162
|
},
|
|
150
163
|
[ collapse ]
|
|
151
164
|
);
|
|
165
|
+
const focusRow = useCallback(
|
|
166
|
+
( event, startRow, endRow ) => {
|
|
167
|
+
if ( event.shiftKey ) {
|
|
168
|
+
updateBlockSelection(
|
|
169
|
+
event,
|
|
170
|
+
startRow?.dataset?.block,
|
|
171
|
+
endRow?.dataset?.block
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
},
|
|
175
|
+
[ updateBlockSelection ]
|
|
176
|
+
);
|
|
152
177
|
|
|
153
178
|
const contextValue = useMemo(
|
|
154
179
|
() => ( {
|
|
@@ -185,6 +210,7 @@ function ListView(
|
|
|
185
210
|
ref={ treeGridRef }
|
|
186
211
|
onCollapseRow={ collapseRow }
|
|
187
212
|
onExpandRow={ expandRow }
|
|
213
|
+
onFocusRow={ focusRow }
|
|
188
214
|
>
|
|
189
215
|
<ListViewContext.Provider value={ contextValue }>
|
|
190
216
|
<ListViewBranch
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal dependencies
|
|
3
|
+
*/
|
|
4
|
+
import { getCommonDepthClientIds } from '../utils';
|
|
5
|
+
|
|
6
|
+
describe( 'getCommonDepthClientIds', () => {
|
|
7
|
+
it( 'should return start and end when no depth is provided', () => {
|
|
8
|
+
const result = getCommonDepthClientIds(
|
|
9
|
+
'start-id',
|
|
10
|
+
'clicked-id',
|
|
11
|
+
[],
|
|
12
|
+
[]
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
expect( result ).toEqual( { start: 'start-id', end: 'clicked-id' } );
|
|
16
|
+
} );
|
|
17
|
+
|
|
18
|
+
it( 'should return deepest start and end when depths match', () => {
|
|
19
|
+
const result = getCommonDepthClientIds(
|
|
20
|
+
'start-id',
|
|
21
|
+
'clicked-id',
|
|
22
|
+
[ 'start-1', 'start-2', 'start-3' ],
|
|
23
|
+
[ 'end-1', 'end-2', 'end-3' ]
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
expect( result ).toEqual( { start: 'start-id', end: 'clicked-id' } );
|
|
27
|
+
} );
|
|
28
|
+
|
|
29
|
+
it( 'should return shallower ids when start is shallower', () => {
|
|
30
|
+
const result = getCommonDepthClientIds(
|
|
31
|
+
'start-id',
|
|
32
|
+
'clicked-id',
|
|
33
|
+
[ 'start-1' ],
|
|
34
|
+
[ 'end-1', 'end-2', 'end-3' ]
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
expect( result ).toEqual( { start: 'start-id', end: 'end-2' } );
|
|
38
|
+
} );
|
|
39
|
+
|
|
40
|
+
it( 'should return shallower ids when end is shallower', () => {
|
|
41
|
+
const result = getCommonDepthClientIds(
|
|
42
|
+
'start-id',
|
|
43
|
+
'clicked-id',
|
|
44
|
+
[ 'start-1', 'start-2', 'start-3' ],
|
|
45
|
+
[ 'end-1', 'end-2' ]
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
expect( result ).toEqual( { start: 'start-3', end: 'clicked-id' } );
|
|
49
|
+
} );
|
|
50
|
+
} );
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* External dependencies
|
|
3
|
+
*/
|
|
4
|
+
import { difference } from 'lodash';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* WordPress dependencies
|
|
8
|
+
*/
|
|
9
|
+
import { speak } from '@wordpress/a11y';
|
|
10
|
+
import { __, sprintf } from '@wordpress/i18n';
|
|
11
|
+
import { useDispatch, useSelect } from '@wordpress/data';
|
|
12
|
+
import { useCallback } from '@wordpress/element';
|
|
13
|
+
import { UP, DOWN } from '@wordpress/keycodes';
|
|
14
|
+
import { store as blocksStore } from '@wordpress/blocks';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Internal dependencies
|
|
18
|
+
*/
|
|
19
|
+
import { store as blockEditorStore } from '../../store';
|
|
20
|
+
import { getCommonDepthClientIds } from './utils';
|
|
21
|
+
|
|
22
|
+
export default function useBlockSelection() {
|
|
23
|
+
const { clearSelectedBlock, multiSelect, selectBlock } = useDispatch(
|
|
24
|
+
blockEditorStore
|
|
25
|
+
);
|
|
26
|
+
const {
|
|
27
|
+
getBlockName,
|
|
28
|
+
getBlockParents,
|
|
29
|
+
getBlockSelectionStart,
|
|
30
|
+
getBlockSelectionEnd,
|
|
31
|
+
getSelectedBlockClientIds,
|
|
32
|
+
hasMultiSelection,
|
|
33
|
+
hasSelectedBlock,
|
|
34
|
+
} = useSelect( blockEditorStore );
|
|
35
|
+
|
|
36
|
+
const { getBlockType } = useSelect( blocksStore );
|
|
37
|
+
|
|
38
|
+
const updateBlockSelection = useCallback(
|
|
39
|
+
async ( event, clientId, destinationClientId ) => {
|
|
40
|
+
if ( ! event?.shiftKey ) {
|
|
41
|
+
await clearSelectedBlock();
|
|
42
|
+
selectBlock( clientId );
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// To handle multiple block selection via the `SHIFT` key, prevent
|
|
47
|
+
// the browser default behavior of opening the link in a new window.
|
|
48
|
+
event.preventDefault();
|
|
49
|
+
|
|
50
|
+
const isKeyPress =
|
|
51
|
+
event.type === 'keydown' &&
|
|
52
|
+
( event.keyCode === UP || event.keyCode === DOWN );
|
|
53
|
+
|
|
54
|
+
// Handle clicking on a block when no blocks are selected, and return early.
|
|
55
|
+
if (
|
|
56
|
+
! isKeyPress &&
|
|
57
|
+
! hasSelectedBlock() &&
|
|
58
|
+
! hasMultiSelection()
|
|
59
|
+
) {
|
|
60
|
+
selectBlock( clientId, null );
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const selectedBlocks = getSelectedBlockClientIds();
|
|
65
|
+
const clientIdWithParents = [
|
|
66
|
+
...getBlockParents( clientId ),
|
|
67
|
+
clientId,
|
|
68
|
+
];
|
|
69
|
+
|
|
70
|
+
if (
|
|
71
|
+
isKeyPress &&
|
|
72
|
+
! selectedBlocks.some( ( blockId ) =>
|
|
73
|
+
clientIdWithParents.includes( blockId )
|
|
74
|
+
)
|
|
75
|
+
) {
|
|
76
|
+
// Ensure that shift-selecting blocks via the keyboard only
|
|
77
|
+
// expands the current selection if focusing over already
|
|
78
|
+
// selected blocks. Otherwise, clear the selection so that
|
|
79
|
+
// a user can create a new selection entirely by keyboard.
|
|
80
|
+
await clearSelectedBlock();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
let startTarget = getBlockSelectionStart();
|
|
84
|
+
let endTarget = clientId;
|
|
85
|
+
|
|
86
|
+
// Handle keyboard behavior for selecting multiple blocks.
|
|
87
|
+
if ( isKeyPress ) {
|
|
88
|
+
if ( ! hasSelectedBlock() && ! hasMultiSelection() ) {
|
|
89
|
+
// Set the starting point of the selection to the currently
|
|
90
|
+
// focused block, if there are no blocks currently selected.
|
|
91
|
+
// This ensures that as the selection is expanded or contracted,
|
|
92
|
+
// the starting point of the selection is anchored to that block.
|
|
93
|
+
startTarget = clientId;
|
|
94
|
+
}
|
|
95
|
+
if ( destinationClientId ) {
|
|
96
|
+
// If the user presses UP or DOWN, we want to ensure that the block they're
|
|
97
|
+
// moving to is the target for selection, and not the currently focused one.
|
|
98
|
+
endTarget = destinationClientId;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const startParents = getBlockParents( startTarget );
|
|
103
|
+
const endParents = getBlockParents( endTarget );
|
|
104
|
+
|
|
105
|
+
const { start, end } = getCommonDepthClientIds(
|
|
106
|
+
startTarget,
|
|
107
|
+
endTarget,
|
|
108
|
+
startParents,
|
|
109
|
+
endParents
|
|
110
|
+
);
|
|
111
|
+
await multiSelect( start, end, null );
|
|
112
|
+
|
|
113
|
+
// Announce deselected block, or number of deselected blocks if
|
|
114
|
+
// the total number of blocks deselected is greater than one.
|
|
115
|
+
const updatedSelectedBlocks = getSelectedBlockClientIds();
|
|
116
|
+
|
|
117
|
+
const selectionDiff = difference(
|
|
118
|
+
selectedBlocks,
|
|
119
|
+
updatedSelectedBlocks
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
let label;
|
|
123
|
+
if ( selectionDiff.length === 1 ) {
|
|
124
|
+
const title = getBlockType( getBlockName( selectionDiff[ 0 ] ) )
|
|
125
|
+
?.title;
|
|
126
|
+
if ( title ) {
|
|
127
|
+
label = sprintf(
|
|
128
|
+
/* translators: %s: block name */
|
|
129
|
+
__( '%s deselected.' ),
|
|
130
|
+
title
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
} else if ( selectionDiff.length > 1 ) {
|
|
134
|
+
label = sprintf(
|
|
135
|
+
/* translators: %s: number of deselected blocks */
|
|
136
|
+
__( '%s blocks deselected.' ),
|
|
137
|
+
selectionDiff.length
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if ( label ) {
|
|
142
|
+
speak( label );
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
[
|
|
146
|
+
clearSelectedBlock,
|
|
147
|
+
getBlockName,
|
|
148
|
+
getBlockType,
|
|
149
|
+
getBlockParents,
|
|
150
|
+
getBlockSelectionStart,
|
|
151
|
+
getBlockSelectionEnd,
|
|
152
|
+
getSelectedBlockClientIds,
|
|
153
|
+
hasMultiSelection,
|
|
154
|
+
hasSelectedBlock,
|
|
155
|
+
multiSelect,
|
|
156
|
+
selectBlock,
|
|
157
|
+
]
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
return {
|
|
161
|
+
updateBlockSelection,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WordPress dependencies
|
|
3
|
+
*/
|
|
4
|
+
import { useEffect, useState } from '@wordpress/element';
|
|
5
|
+
import { useSelect } from '@wordpress/data';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Internal dependencies
|
|
9
|
+
*/
|
|
10
|
+
import { store as blockEditorStore } from '../../store';
|
|
11
|
+
|
|
12
|
+
export default function useListViewExpandSelectedItem( {
|
|
13
|
+
firstSelectedBlockClientId,
|
|
14
|
+
setExpandedState,
|
|
15
|
+
} ) {
|
|
16
|
+
const [ selectedTreeId, setSelectedTreeId ] = useState( null );
|
|
17
|
+
const { selectedBlockParentClientIds } = useSelect(
|
|
18
|
+
( select ) => {
|
|
19
|
+
const { getBlockParents } = select( blockEditorStore );
|
|
20
|
+
return {
|
|
21
|
+
selectedBlockParentClientIds: getBlockParents(
|
|
22
|
+
firstSelectedBlockClientId,
|
|
23
|
+
false
|
|
24
|
+
),
|
|
25
|
+
};
|
|
26
|
+
},
|
|
27
|
+
[ firstSelectedBlockClientId ]
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
const parentClientIds =
|
|
31
|
+
Array.isArray( selectedBlockParentClientIds ) &&
|
|
32
|
+
selectedBlockParentClientIds.length
|
|
33
|
+
? selectedBlockParentClientIds
|
|
34
|
+
: null;
|
|
35
|
+
|
|
36
|
+
// Expand tree when a block is selected.
|
|
37
|
+
useEffect( () => {
|
|
38
|
+
// If the selectedTreeId is the same as the selected block,
|
|
39
|
+
// it means that the block was selected using the block list tree.
|
|
40
|
+
if ( selectedTreeId === firstSelectedBlockClientId ) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// If the selected block has parents, get the top-level parent.
|
|
45
|
+
if ( parentClientIds ) {
|
|
46
|
+
// If the selected block has parents,
|
|
47
|
+
// expand the tree branch.
|
|
48
|
+
setExpandedState( {
|
|
49
|
+
type: 'expand',
|
|
50
|
+
clientIds: selectedBlockParentClientIds,
|
|
51
|
+
} );
|
|
52
|
+
}
|
|
53
|
+
}, [ firstSelectedBlockClientId ] );
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
setSelectedTreeId,
|
|
57
|
+
};
|
|
58
|
+
}
|