PluginProbe
Content Blocks Builder – Create blocks, repeater blocks with carousel, grid, popup layouts / trunk
Content Blocks Builder – Create blocks, repeater blocks with carousel, grid, popup layouts vtrunk
2.8.14 2.8.13 2.8.12 2.7.5 2.7.6 2.7.7 2.7.8 2.7.9 2.8.0 2.8.1 2.8.10 2.8.11 2.8.2 2.8.3 2.8.4 2.8.5 2.8.6 2.8.7 2.8.8 2.8.9 trunk 1.0.1 1.0.2 1.1.0 1.1.1 All 147 releases
content-blocks-builder / src / features / spacing / visualizer.js

visualizer.js in Content Blocks Builder – Create blocks, repeater blocks with carousel, grid, popup layouts trunk, at src/features/spacing/visualizer.js

182 lines 4.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * External dependencies
3 */
4 import clsx from "clsx";
5 import styled from "@emotion/styled";
6
7 /**
8 * WordPress dependencies
9 */
10 import isDeepEqual from "fast-deep-equal/es6";
11 import { Popover } from "@wordpress/components";
12 import {
13 useMemo,
14 useRef,
15 useState,
16 useEffect,
17 useReducer,
18 } from "@wordpress/element";
19 import { BlockPopover } from "@wordpress/block-editor";
20
21 /**
22 * Internal dependencies
23 */
24
25 /**
26 * Get a property value
27 *
28 * @param {DOMNode} element
29 * @param {String} property
30 * @returns {String}
31 */
32 function getComputedCSS(element, property) {
33 return element.ownerDocument.defaultView
34 .getComputedStyle(element)
35 .getPropertyValue(property);
36 }
37
38 /**
39 * Get visualizer style
40 *
41 * @param {DOMNode} blockElement
42 * @param {String} type
43 * @returns {Object}
44 */
45 function computeStyle(blockElement, type) {
46 const top = getComputedCSS(blockElement, `${type}-top`);
47 const right = getComputedCSS(blockElement, `${type}-right`);
48 const bottom = getComputedCSS(blockElement, `${type}-bottom`);
49 const left = getComputedCSS(blockElement, `${type}-left`);
50 return {
51 borderTopWidth: top,
52 borderRightWidth: right,
53 borderBottomWidth: bottom,
54 borderLeftWidth: left,
55 ...(type === "margin"
56 ? {
57 top: top ? `-${top}` : 0,
58 right: right ? `-${right}` : 0,
59 bottom: bottom ? `-${bottom}` : 0,
60 left: left ? `-${left}` : 0,
61 }
62 : {}),
63 };
64 }
65
66 /**
67 * Cover container
68 *
69 * @param {Object}
70 * @returns
71 */
72 function CoverContainer({ blockElement, children }) {
73 const [width, setWidth] = useState(blockElement.offsetWidth);
74 const [height, setHeight] = useState(blockElement.offsetHeight);
75
76 useEffect(() => {
77 const observer = new window.ResizeObserver(() => {
78 setWidth(blockElement.offsetWidth);
79 setHeight(blockElement.offsetHeight);
80 });
81 observer.observe(blockElement, { box: "border-box" });
82 return () => observer.disconnect();
83 }, [blockElement]);
84
85 const style = useMemo(() => {
86 return {
87 position: "absolute",
88 width,
89 height,
90 };
91 }, [width, height]);
92
93 return <div style={style}>{children}</div>;
94 }
95
96 /**
97 * Dimension visualizer.
98 */
99 export default function DimensionVisualizer({ clientId, visualizer, value }) {
100 const ref = useRef();
101 const [blockElement, setBlockElement] = useState(null);
102 useEffect(() => {
103 if (ref.current) {
104 setBlockElement(
105 ref.current?.ownerDocument?.querySelector(`#block-${clientId}`),
106 );
107 }
108 }, [clientId, ref.current]);
109
110 const [style, updateStyle] = useReducer(() =>
111 computeStyle(blockElement, visualizer),
112 );
113
114 const forceShow = !!visualizer;
115
116 // Force style computation when forceShow becomes true (e.g., when hovering control)
117 // to ensure visualizer displays correct dimensions on first render.
118 useEffect(() => {
119 if (blockElement && forceShow) {
120 updateStyle();
121 }
122 }, [blockElement, forceShow]);
123
124 // It's not sufficient to read the block's computed style when `value` changes because
125 // the effect would run before the block’s style has updated. Thus observing mutations
126 // to the block’s attributes is used to trigger updates to the visualizer’s styles.
127 useEffect(() => {
128 if (!blockElement) {
129 return;
130 }
131 const observer = new window.MutationObserver(updateStyle);
132 observer.observe(blockElement, {
133 attributes: true,
134 attributeFilter: ["style", "class"],
135 });
136 return () => {
137 observer.disconnect();
138 };
139 }, [blockElement]);
140
141 const previousValueRef = useRef(value);
142 const [isActive, setIsActive] = useState(false);
143
144 useEffect(() => {
145 if (isDeepEqual(value, previousValueRef.current) || forceShow) {
146 return;
147 }
148
149 setIsActive(true);
150 previousValueRef.current = value;
151
152 const timeout = setTimeout(() => {
153 setIsActive(false);
154 }, 400);
155
156 return () => {
157 setIsActive(false);
158 clearTimeout(timeout);
159 };
160 }, [value, forceShow]);
161
162 return (
163 <BlockPopover
164 clientId={clientId}
165 __unstablePopoverSlot="block-toolbar"
166 ref={ref}
167 shift={false}
168 className="cbb-visualizer"
169 >
170 {blockElement && visualizer && (
171 <CoverContainer
172 blockElement={blockElement}
173 value={value}
174 visualizer={visualizer}
175 >
176 <div className="cbb-spacing-visualizer" style={style}></div>
177 </CoverContainer>
178 )}
179 </BlockPopover>
180 );
181 }
182