-
Notifications
You must be signed in to change notification settings - Fork 4.4k
/
Copy pathuse-block-toolbar-popover-props.js
128 lines (111 loc) · 4.14 KB
/
use-block-toolbar-popover-props.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
/**
* WordPress dependencies
*/
import { useRefEffect } from '@wordpress/compose';
import { useSelect } from '@wordpress/data';
import { useCallback, useLayoutEffect, useState } from '@wordpress/element';
/**
* Internal dependencies
*/
import { store as blockEditorStore } from '../../store';
import { __unstableUseBlockElement as useBlockElement } from '../block-list/use-block-props/use-block-refs';
// By default the toolbar sets the `shift` prop. If the user scrolls the page
// down the toolbar will stay on screen by adopting a sticky position at the
// top of the viewport.
const DEFAULT_PROPS = {
resize: false,
flip: false,
__unstableShift: true,
};
// When there isn't enough height between the top of the block and the editor
// canvas, the `shift` prop is set to `false`, as it will cause the block to be
// obscured. The `flip` behavior is enabled, which positions the toolbar below
// the block.
const RESTRICTED_HEIGHT_PROPS = {
resize: false,
flip: true,
__unstableShift: false,
};
/**
* Get the popover props for the block toolbar, determined by the space at the top of the canvas and the toolbar height.
*
* @param {Element} contentElement The DOM element that represents the editor content or canvas.
* @param {Element} selectedBlockElement The outer DOM element of the first selected block.
* @param {number} toolbarHeight The height of the toolbar in pixels.
*
* @return {Object} The popover props used to determine the position of the toolbar.
*/
function getProps( contentElement, selectedBlockElement, toolbarHeight ) {
if ( ! contentElement || ! selectedBlockElement ) {
return DEFAULT_PROPS;
}
const blockRect = selectedBlockElement.getBoundingClientRect();
const contentRect = contentElement.getBoundingClientRect();
if ( blockRect.top - contentRect.top > toolbarHeight ) {
return DEFAULT_PROPS;
}
return RESTRICTED_HEIGHT_PROPS;
}
/**
* Determines the desired popover positioning behavior, returning a set of appropriate props.
*
* @param {Object} elements
* @param {Element} elements.contentElement The DOM element that represents the editor content or canvas.
* @param {string} elements.clientId The clientId of the first selected block.
*
* @return {Object} The popover props used to determine the position of the toolbar.
*/
export default function useBlockToolbarPopoverProps( {
contentElement,
clientId,
} ) {
const selectedBlockElement = useBlockElement( clientId );
const [ toolbarHeight, setToolbarHeight ] = useState( 0 );
const [ props, setProps ] = useState( () =>
getProps( contentElement, selectedBlockElement, toolbarHeight )
);
const blockIndex = useSelect(
( select ) => select( blockEditorStore ).getBlockIndex( clientId ),
[ clientId ]
);
const popoverRef = useRefEffect( ( popoverNode ) => {
setToolbarHeight( popoverNode.offsetHeight );
}, [] );
const updateProps = useCallback(
() =>
setProps(
getProps( contentElement, selectedBlockElement, toolbarHeight )
),
[ contentElement, selectedBlockElement, toolbarHeight ]
);
// Update props when the block is moved. This also ensures the props are
// correct on initial mount, and when the selected block or content element
// changes (since the callback ref will update).
useLayoutEffect( updateProps, [ blockIndex, updateProps ] );
// Update props when the viewport is resized or the block is resized.
useLayoutEffect( () => {
if ( ! contentElement || ! selectedBlockElement ) {
return;
}
// Update the toolbar props on viewport resize.
const contentView = contentElement?.ownerDocument?.defaultView;
contentView?.addEventHandler?.( 'resize', updateProps );
// Update the toolbar props on block resize.
let resizeObserver;
const blockView = selectedBlockElement?.ownerDocument?.defaultView;
if ( blockView.ResizeObserver ) {
resizeObserver = new blockView.ResizeObserver( updateProps );
resizeObserver.observe( selectedBlockElement );
}
return () => {
contentView?.removeEventHandler?.( 'resize', updateProps );
if ( resizeObserver ) {
resizeObserver.disconnect();
}
};
}, [ updateProps, contentElement, selectedBlockElement ] );
return {
...props,
ref: popoverRef,
};
}