PluginProbe
Block Animations, Motion & Scroll Effects – Ghost Kit / 3.3.2
Block Animations, Motion & Scroll Effects – Ghost Kit v3.3.2
3.7.2 3.7.1 3.7.0 3.6.1 trunk 1.6.3 2.25.0 3.3.0 3.3.1 3.3.2 3.3.3 3.4.0 3.4.1 3.4.2 3.4.3 3.4.4 3.4.5 3.4.6 3.5.0 3.5.1 3.6.0
ghostkit / gutenberg / extend / styles / index.js

index.js in Block Animations, Motion & Scroll Effects – Ghost Kit 3.3.2, at gutenberg/extend/styles/index.js

371 lines 8.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import deepEqual from 'deep-equal';
2 import { cloneDeep } from 'lodash';
3 import shorthash from 'shorthash';
4 import { throttle } from 'throttle-debounce';
5
6 import { getBlockSupport, getBlockType } from '@wordpress/blocks';
7 import { createHigherOrderComponent } from '@wordpress/compose';
8 import { useSelect } from '@wordpress/data';
9 import { useCallback, useEffect, useRef } from '@wordpress/element';
10 import { addFilter, applyFilters } from '@wordpress/hooks';
11
12 import EditorStyles from '../../components/editor-styles';
13 import { hasClass, replaceClass } from '../../utils/classes-replacer';
14 import { maybeDecode, maybeEncode } from '../../utils/encode-decode';
15 // We can't use lodash merge, because it skip the specified undefined value
16 // which we use to reset styles.
17 import merge from '../../utils/merge';
18 import getStyles from './get-styles';
19
20 function cleanBlockCustomStyles(styles) {
21 const newStyles = {};
22
23 Object.keys(styles).forEach((key) => {
24 if (typeof styles[key] !== 'undefined') {
25 if (
26 typeof styles[key] === 'object' &&
27 !Array.isArray(styles[key]) &&
28 styles[key] !== null
29 ) {
30 const innerStyles = cleanBlockCustomStyles(styles[key]);
31
32 if (innerStyles && Object.keys(innerStyles).length) {
33 newStyles[key] = innerStyles;
34 }
35 } else {
36 newStyles[key] = styles[key];
37 }
38 }
39 });
40
41 return newStyles;
42 }
43
44 /**
45 * Get recursive all blocks in list.
46 *
47 * @param {boolean} blocks - block list
48 *
49 * @return {Array} block list
50 */
51 function getAllBlocks(blocks = false) {
52 let result = [];
53
54 if (!blocks) {
55 return result;
56 }
57
58 blocks.forEach((data) => {
59 result.push(data);
60
61 if (data.innerBlocks && data.innerBlocks.length) {
62 result = [...result, ...getAllBlocks(data.innerBlocks)];
63 }
64 });
65
66 return result;
67 }
68
69 /**
70 * Custom Styles Component.
71 * @param props
72 */
73 function CustomStylesComponent(props) {
74 const { setAttributes, attributes, clientId, name } = props;
75
76 const { ghostkit, className } = attributes;
77
78 const customSelector = getBlockSupport(name, [
79 'ghostkit',
80 'styles',
81 'customSelector',
82 ]);
83
84 const { allBlocks, blockSettings } = useSelect(
85 (select) => {
86 const { getBlocks } = select('core/block-editor');
87
88 return {
89 allBlocks: getBlocks(),
90 blockSettings: getBlockType(name),
91 };
92 },
93 [name]
94 );
95
96 const getGhostKitID = useCallback(
97 (checkDuplicates) => {
98 let id = ghostkit?.id;
99
100 // create block ID.
101 if (!id || checkDuplicates) {
102 const usedIds = {};
103
104 // prevent unique ID duplication after block duplicated.
105 if (checkDuplicates) {
106 const blocks = getAllBlocks(allBlocks);
107 blocks.forEach((data) => {
108 if (data.clientId && data?.attributes?.ghostkit?.id) {
109 usedIds[data.attributes.ghostkit.id] =
110 data.clientId;
111
112 if (
113 data.clientId !== clientId &&
114 data.attributes.ghostkit.id === id
115 ) {
116 id = '';
117 }
118 }
119 });
120 }
121
122 // prepare new block id.
123 if (clientId && !id) {
124 let newID = id || '';
125
126 // check if id already exist.
127 let tryCount = 10;
128 while (
129 !newID ||
130 (typeof usedIds[newID] !== 'undefined' &&
131 usedIds[newID] !== clientId &&
132 tryCount > 0)
133 ) {
134 newID = shorthash.unique(clientId);
135 tryCount -= 1;
136 }
137
138 if (newID && typeof usedIds[newID] === 'undefined') {
139 usedIds[newID] = clientId;
140 }
141
142 if (newID !== id) {
143 id = newID;
144 }
145 }
146 }
147
148 return id || false;
149 },
150 [ghostkit, allBlocks, clientId]
151 );
152
153 const onUpdate = useCallback(
154 (checkDuplicates) => {
155 const newAttrs = {};
156
157 // prepare custom block styles.
158 const blockCustomStyles = applyFilters(
159 'ghostkit.blocks.customStyles',
160 blockSettings.ghostkit &&
161 blockSettings.ghostkit.customStylesCallback
162 ? blockSettings.ghostkit.customStylesCallback(
163 attributes,
164 props
165 )
166 : {},
167 props
168 );
169
170 const withBlockCustomStyles =
171 blockCustomStyles && Object.keys(blockCustomStyles).length;
172 const withExtensionCustomStyles =
173 ghostkit?.styles && Object.keys(ghostkit.styles).length;
174
175 let reset = !withBlockCustomStyles && !withExtensionCustomStyles;
176
177 if (withBlockCustomStyles || withExtensionCustomStyles) {
178 let newStyles = cloneDeep(ghostkit?.styles || {});
179
180 if (withBlockCustomStyles) {
181 newStyles = merge(
182 newStyles,
183 maybeEncode(blockCustomStyles)
184 );
185 }
186
187 // Clean undefined and empty statements from custom styles list.
188 if (newStyles) {
189 newStyles = cleanBlockCustomStyles(newStyles);
190 }
191
192 const hasCustomStyles = Object.keys(newStyles).length;
193 const ghostkitID =
194 hasCustomStyles && getGhostKitID(checkDuplicates);
195
196 if (!ghostkitID) {
197 reset = true;
198 } else {
199 if (ghostkitID !== ghostkit?.id) {
200 if (!newAttrs.ghostkit) {
201 newAttrs.ghostkit = cloneDeep(ghostkit || {});
202 }
203
204 newAttrs.ghostkit.id = ghostkitID;
205 }
206
207 // Regenerate custom classname if it was removed or changed.
208 // We have to check if class name contains ghostkit-custom-<id> because
209 // it can be changed while your edit the custom block class.
210 if (!hasClass(className, `ghostkit-custom-${ghostkitID}`)) {
211 const newClassName = replaceClass(
212 className,
213 'ghostkit-custom',
214 ghostkitID
215 );
216 if (newClassName !== className) {
217 newAttrs.className = newClassName;
218 }
219 }
220
221 // Check if styles changes and update it.
222 if (!deepEqual(ghostkit?.styles, newStyles)) {
223 if (!newAttrs.ghostkit) {
224 newAttrs.ghostkit = cloneDeep(ghostkit || {});
225 }
226
227 newAttrs.ghostkit.styles = newStyles;
228 }
229 }
230 }
231
232 // Reset unused styles and ID.
233 if (reset) {
234 const newClassName = replaceClass(
235 className,
236 'ghostkit-custom',
237 ''
238 );
239
240 if (newClassName !== className) {
241 newAttrs.className = !newClassName
242 ? undefined
243 : newClassName;
244 }
245
246 if (ghostkit?.styles || ghostkit?.id) {
247 if (!newAttrs.ghostkit) {
248 newAttrs.ghostkit = cloneDeep(ghostkit || {});
249 }
250
251 if (newAttrs?.ghostkit?.styles) {
252 delete newAttrs.ghostkit.styles;
253 }
254
255 if (newAttrs?.ghostkit?.id) {
256 delete newAttrs.ghostkit.id;
257 }
258 }
259
260 // Reset ghostkit attribute if empty.
261 if (
262 newAttrs?.ghostkit &&
263 !Object.keys(newAttrs.ghostkit).length
264 ) {
265 newAttrs.ghostkit = undefined;
266 }
267 }
268
269 // Update attributes.
270 if (Object.keys(newAttrs).length) {
271 setAttributes(newAttrs);
272 }
273 },
274 // eslint-disable-next-line react-hooks/exhaustive-deps
275 [blockSettings, attributes, ghostkit, props, className, setAttributes]
276 );
277
278 const onUpdateThrottle = throttle(60, onUpdate);
279
280 const didMountRef = useRef(false);
281
282 useEffect(() => {
283 // Did update.
284 if (didMountRef.current) {
285 onUpdateThrottle();
286
287 // Did mount.
288 } else {
289 didMountRef.current = true;
290
291 onUpdate(true);
292 }
293 // eslint-disable-next-line react-hooks/exhaustive-deps
294 }, [attributes]);
295
296 let styles = '';
297
298 // generate custom styles.
299 if (ghostkit?.id) {
300 // New custom styles.
301 if (ghostkit?.styles && Object.keys(ghostkit?.styles).length) {
302 let selector = `.ghostkit-custom-${ghostkit?.id}`;
303
304 if (customSelector) {
305 selector = customSelector.replace('&', selector);
306 }
307
308 styles +=
309 (styles ? ' ' : '') +
310 getStyles(
311 maybeDecode({
312 [selector]: ghostkit?.styles,
313 }),
314 '',
315 false
316 );
317 }
318
319 if (
320 styles &&
321 blockSettings &&
322 blockSettings.ghostkit &&
323 blockSettings.ghostkit.customStylesFilter
324 ) {
325 styles = blockSettings.ghostkit.customStylesFilter(
326 styles,
327 maybeDecode(attributes?.ghostkit?.styles),
328 true,
329 attributes
330 );
331 }
332 }
333
334 // filter custom styles.
335 styles = applyFilters('ghostkit.editor.customStylesOutput', styles, props);
336
337 if (!styles) {
338 return null;
339 }
340
341 return <EditorStyles styles={window.GHOSTKIT.replaceVars(styles)} />;
342 }
343
344 /**
345 * Override the default edit UI to include a new block inspector control for
346 * assigning the custom styles if needed.
347 *
348 * @param {Function | Component} BlockEdit Original component.
349 *
350 * @return {string} Wrapped component.
351 */
352 const withNewAttrs = createHigherOrderComponent(
353 (BlockEdit) =>
354 function (props) {
355 return (
356 <>
357 <BlockEdit {...props} />
358 <CustomStylesComponent {...props} />
359 </>
360 );
361 },
362 'withNewAttrs'
363 );
364
365 // Init filters.
366 addFilter(
367 'editor.BlockEdit',
368 'ghostkit/styles/additional-attributes',
369 withNewAttrs
370 );
371