-
Notifications
You must be signed in to change notification settings - Fork 4.3k
/
use-inner-block-template-sync.js
100 lines (92 loc) · 4.1 KB
/
use-inner-block-template-sync.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
/**
* External dependencies
*/
import { isEqual } from 'lodash';
/**
* WordPress dependencies
*/
import { useRef, useLayoutEffect } from '@wordpress/element';
import { useSelect, useDispatch } from '@wordpress/data';
import { synchronizeBlocksWithTemplate } from '@wordpress/blocks';
/**
* Internal dependencies
*/
import { store as blockEditorStore } from '../../store';
/**
* This hook makes sure that a block's inner blocks stay in sync with the given
* block "template". The template is a block hierarchy to which inner blocks must
* conform. If the blocks get "out of sync" with the template and the template
* is meant to be locked (e.g. templateLock = "all" or templateLock = "contentOnly"),
* then we replace the inner blocks with the correct value after synchronizing it with the template.
*
* @param {string} clientId The block client ID.
* @param {Object} template The template to match.
* @param {string} templateLock The template lock state for the inner blocks. For
* example, if the template lock is set to "all",
* then the inner blocks will stay in sync with the
* template. If not defined or set to false, then
* the inner blocks will not be synchronized with
* the given template.
* @param {boolean} templateInsertUpdatesSelection Whether or not to update the
* block-editor selection state when inner blocks
* are replaced after template synchronization.
*/
export default function useInnerBlockTemplateSync(
clientId,
template,
templateLock,
templateInsertUpdatesSelection
) {
const { getSelectedBlocksInitialCaretPosition, isBlockSelected } =
useSelect( blockEditorStore );
const { replaceInnerBlocks } = useDispatch( blockEditorStore );
const innerBlocks = useSelect(
( select ) => select( blockEditorStore ).getBlocks( clientId ),
[ clientId ]
);
const { getBlocks } = useSelect( blockEditorStore );
// Maintain a reference to the previous value so we can do a deep equality check.
const existingTemplate = useRef( null );
useLayoutEffect( () => {
// There's an implicit dependency between useInnerBlockTemplateSync and useNestedSettingsUpdate
// The former needs to happen after the latter and since the latter is using microtasks to batch updates (performance optimization),
// we need to schedule this one in a microtask as well.
// Exemple: If you remove queueMicrotask here, ctrl + click to insert quote block won't close the inserter.
window.queueMicrotask( () => {
// Only synchronize innerBlocks with template if innerBlocks are empty
// or a locking "all" or "contentOnly" exists directly on the block.
const currentInnerBlocks = getBlocks( clientId );
const shouldApplyTemplate =
currentInnerBlocks.length === 0 ||
templateLock === 'all' ||
templateLock === 'contentOnly';
const hasTemplateChanged = ! isEqual(
template,
existingTemplate.current
);
if ( ! shouldApplyTemplate || ! hasTemplateChanged ) {
return;
}
existingTemplate.current = template;
const nextBlocks = synchronizeBlocksWithTemplate(
currentInnerBlocks,
template
);
if ( ! isEqual( nextBlocks, currentInnerBlocks ) ) {
replaceInnerBlocks(
clientId,
nextBlocks,
currentInnerBlocks.length === 0 &&
templateInsertUpdatesSelection &&
nextBlocks.length !== 0 &&
isBlockSelected( clientId ),
// This ensures the "initialPosition" doesn't change when applying the template
// If we're supposed to focus the block, we'll focus the first inner block
// otherwise, we won't apply any auto-focus.
// This ensures for instance that the focus stays in the inserter when inserting the "buttons" block.
getSelectedBlocksInitialCaretPosition()
);
}
} );
}, [ innerBlocks, template, templateLock, clientId ] );
}