-
Notifications
You must be signed in to change notification settings - Fork 4.3k
/
Copy pathindex.js
263 lines (246 loc) · 8.74 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
/**
* WordPress dependencies
*/
import { useSelect, useDispatch } from '@wordpress/data';
import { isTextField } from '@wordpress/dom';
import { Popover } from '@wordpress/components';
import { __unstableUseShortcutEventMatch as useShortcutEventMatch } from '@wordpress/keyboard-shortcuts';
import { useRef } from '@wordpress/element';
import { switchToBlockType, store as blocksStore } from '@wordpress/blocks';
import { speak } from '@wordpress/a11y';
import { __ } from '@wordpress/i18n';
/**
* Internal dependencies
*/
import EmptyBlockInserter from './empty-block-inserter';
import {
InsertionPointOpenRef,
default as InsertionPoint,
} from './insertion-point';
import BlockToolbarPopover from './block-toolbar-popover';
import BlockToolbarBreadcrumb from './block-toolbar-breadcrumb';
import ZoomOutPopover from './zoom-out-popover';
import { store as blockEditorStore } from '../../store';
import usePopoverScroll from '../block-popover/use-popover-scroll';
import ZoomOutModeInserters from './zoom-out-mode-inserters';
import { useShowBlockTools } from './use-show-block-tools';
import { unlock } from '../../lock-unlock';
import getEditorRegion from '../../utils/get-editor-region';
function selector( select ) {
const {
getSelectedBlockClientId,
getFirstMultiSelectedBlockClientId,
getSettings,
__unstableGetEditorMode,
isTyping,
} = select( blockEditorStore );
const clientId =
getSelectedBlockClientId() || getFirstMultiSelectedBlockClientId();
const editorMode = __unstableGetEditorMode();
return {
clientId,
hasFixedToolbar: getSettings().hasFixedToolbar,
isTyping: isTyping(),
isZoomOutMode: editorMode === 'zoom-out',
};
}
/**
* Renders block tools (the block toolbar, select/navigation mode toolbar, the
* insertion point and a slot for the inline rich text toolbar). Must be wrapped
* around the block content and editor styles wrapper or iframe.
*
* @param {Object} $0 Props.
* @param {Object} $0.children The block content and style container.
* @param {Object} $0.__unstableContentRef Ref holding the content scroll container.
*/
export default function BlockTools( {
children,
__unstableContentRef,
...props
} ) {
const { clientId, hasFixedToolbar, isTyping, isZoomOutMode } = useSelect(
selector,
[]
);
const isMatch = useShortcutEventMatch();
const {
getBlocksByClientId,
getSelectedBlockClientIds,
getBlockRootClientId,
isGroupable,
} = useSelect( blockEditorStore );
const { getGroupingBlockName } = useSelect( blocksStore );
const {
showEmptyBlockSideInserter,
showBreadcrumb,
showBlockToolbarPopover,
showZoomOutToolbar,
} = useShowBlockTools();
const {
clearSelectedBlock,
duplicateBlocks,
removeBlocks,
replaceBlocks,
insertAfterBlock,
insertBeforeBlock,
selectBlock,
moveBlocksUp,
moveBlocksDown,
expandBlock,
} = unlock( useDispatch( blockEditorStore ) );
const blockSelectionButtonRef = useRef();
function onKeyDown( event ) {
if ( event.defaultPrevented ) {
return;
}
if ( isMatch( 'core/block-editor/move-up', event ) ) {
const clientIds = getSelectedBlockClientIds();
if ( clientIds.length ) {
event.preventDefault();
const rootClientId = getBlockRootClientId( clientIds[ 0 ] );
moveBlocksUp( clientIds, rootClientId );
}
} else if ( isMatch( 'core/block-editor/move-down', event ) ) {
const clientIds = getSelectedBlockClientIds();
if ( clientIds.length ) {
event.preventDefault();
const rootClientId = getBlockRootClientId( clientIds[ 0 ] );
moveBlocksDown( clientIds, rootClientId );
}
} else if ( isMatch( 'core/block-editor/duplicate', event ) ) {
const clientIds = getSelectedBlockClientIds();
if ( clientIds.length ) {
event.preventDefault();
duplicateBlocks( clientIds );
}
} else if ( isMatch( 'core/block-editor/remove', event ) ) {
const clientIds = getSelectedBlockClientIds();
if ( clientIds.length ) {
event.preventDefault();
removeBlocks( clientIds );
}
} else if ( isMatch( 'core/block-editor/insert-after', event ) ) {
const clientIds = getSelectedBlockClientIds();
if ( clientIds.length ) {
event.preventDefault();
insertAfterBlock( clientIds[ clientIds.length - 1 ] );
}
} else if ( isMatch( 'core/block-editor/insert-before', event ) ) {
const clientIds = getSelectedBlockClientIds();
if ( clientIds.length ) {
event.preventDefault();
insertBeforeBlock( clientIds[ 0 ] );
}
} else if ( isMatch( 'core/block-editor/unselect', event ) ) {
if ( event.target.closest( '[role=toolbar]' ) ) {
// This shouldn't be necessary, but we have a combination of a few things all combining to create a situation where:
// - Because the block toolbar uses createPortal to populate the block toolbar fills, we can't rely on the React event bubbling to hit the onKeyDown listener for the block toolbar
// - Since we can't use the React tree, we use the DOM tree which _should_ handle the event bubbling correctly from a `createPortal` element.
// - This bubbles via the React tree, which hits this `unselect` escape keypress before the block toolbar DOM event listener has access to it.
// An alternative would be to remove the addEventListener on the navigableToolbar and use this event to handle it directly right here. That feels hacky too though.
return;
}
const clientIds = getSelectedBlockClientIds();
if ( clientIds.length > 1 ) {
event.preventDefault();
// If there is more than one block selected, select the first
// block so that focus is directed back to the beginning of the selection.
// In effect, to the user this feels like deselecting the multi-selection.
selectBlock( clientIds[ 0 ] );
} else if (
clientIds.length === 1 &&
event.target === blockSelectionButtonRef?.current
) {
event.preventDefault();
clearSelectedBlock();
getEditorRegion( __unstableContentRef.current )?.focus();
}
} else if ( isMatch( 'core/block-editor/collapse-list-view', event ) ) {
// If focus is currently within a text field, such as a rich text block or other editable field,
// skip collapsing the list view, and allow the keyboard shortcut to be handled by the text field.
// This condition checks for both the active element and the active element within an iframed editor.
if (
isTextField( event.target ) ||
isTextField(
event.target?.contentWindow?.document?.activeElement
)
) {
return;
}
event.preventDefault();
expandBlock( clientId );
} else if ( isMatch( 'core/block-editor/group', event ) ) {
const clientIds = getSelectedBlockClientIds();
if ( clientIds.length > 1 && isGroupable( clientIds ) ) {
event.preventDefault();
const blocks = getBlocksByClientId( clientIds );
const groupingBlockName = getGroupingBlockName();
const newBlocks = switchToBlockType(
blocks,
groupingBlockName
);
replaceBlocks( clientIds, newBlocks );
speak( __( 'Selected blocks are grouped.' ) );
}
}
}
const blockToolbarRef = usePopoverScroll( __unstableContentRef );
const blockToolbarAfterRef = usePopoverScroll( __unstableContentRef );
return (
// eslint-disable-next-line jsx-a11y/no-static-element-interactions
<div { ...props } onKeyDown={ onKeyDown }>
<InsertionPointOpenRef.Provider value={ useRef( false ) }>
{ ! isTyping && (
<InsertionPoint
__unstableContentRef={ __unstableContentRef }
/>
) }
{ showEmptyBlockSideInserter && (
<EmptyBlockInserter
__unstableContentRef={ __unstableContentRef }
clientId={ clientId }
/>
) }
{ showBlockToolbarPopover && (
<BlockToolbarPopover
__unstableContentRef={ __unstableContentRef }
clientId={ clientId }
isTyping={ isTyping }
/>
) }
{ showBreadcrumb && (
<BlockToolbarBreadcrumb
ref={ blockSelectionButtonRef }
__unstableContentRef={ __unstableContentRef }
clientId={ clientId }
/>
) }
{ showZoomOutToolbar && (
<ZoomOutPopover
__unstableContentRef={ __unstableContentRef }
clientId={ clientId }
/>
) }
{ /* Used for the inline rich text toolbar. Until this toolbar is combined into BlockToolbar, someone implementing their own BlockToolbar will also need to use this to see the image caption toolbar. */ }
{ ! isZoomOutMode && ! hasFixedToolbar && (
<Popover.Slot
name="block-toolbar"
ref={ blockToolbarRef }
/>
) }
{ children }
{ /* Used for inline rich text popovers. */ }
<Popover.Slot
name="__unstable-block-tools-after"
ref={ blockToolbarAfterRef }
/>
{ window.__experimentalEnableZoomedOutPatternsTab &&
isZoomOutMode && (
<ZoomOutModeInserters
__unstableContentRef={ __unstableContentRef }
/>
) }
</InsertionPointOpenRef.Provider>
</div>
);
}