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 / border / index.js

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

629 lines 18.9 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 { isString, isUndefined, isEmpty } from "lodash";
5
6 /**
7 * WordPress dependencies
8 */
9 import { __ } from "@wordpress/i18n";
10 import { addFilter } from "@wordpress/hooks";
11 import { createHigherOrderComponent } from "@wordpress/compose";
12 import {
13 ToggleControl,
14 BaseControl,
15 Fill,
16 __experimentalBorderBoxControl as BorderBoxControl,
17 __experimentalToolsPanelItem as ToolsPanelItem,
18 __experimentalVStack as VStack,
19 } from "@wordpress/components";
20 import { useState, useMemo } from "@wordpress/element";
21 import { hasBlockSupport } from "@wordpress/blocks";
22
23 /**
24 * Internal dependencies
25 */
26 import { LabelControl, BorderRadiusControl } from "sdk/components";
27 import {
28 getBreakpointType,
29 getAllBreakpoints,
30 handleChangeResponsiveSettingField,
31 getResponsiveSettingFieldValue,
32 getSettingFieldValue,
33 handleChangeSettingField,
34 handleChangeMultipleSettingField,
35 isValidSettingObject,
36 buildBorderStyle,
37 getCSSBoxValue,
38 getCSSBoxValueDeprecatedV1,
39 getColorObject,
40 useMultipleOriginColors,
41 toType,
42 addEditProps,
43 usePropsStyle,
44 useBlockFeature,
45 } from "sdk/utils";
46 import { boldblocksHasSupport } from "../../utils";
47 import { HelpLink } from "../../components/help-link";
48
49 /**
50 * Import styles
51 */
52 // import "./index.style.scss";
53
54 /**
55 * Debugging
56 */
57 // import { useDebugInformation } from "../../debug";
58
59 /**
60 * Define feature name
61 */
62 const featureName = "border";
63
64 /**
65 * Get css variable object for border
66 *
67 * @param {Object} border
68 * @param {Function} getCSSBoxValue
69 * @returns {Object}
70 */
71 function getBorderStyle({ border = {} }, getCSSBoxValue) {
72 let style = {};
73 if (isValidSettingObject(border)) {
74 Object.keys(border).forEach((breakpoint) => {
75 const { value, inherit } = border[breakpoint];
76 if (isUndefined(value)) {
77 if (inherit && isString(inherit)) {
78 const { value: inheritValue } = border[inherit] ?? {};
79 style = {
80 ...style,
81 ...getCSSBoxValue(
82 inheritValue,
83 "--bb--border",
84 breakpoint,
85 inherit,
86 buildBorderStyle,
87 ),
88 };
89 }
90 } else {
91 style = {
92 ...style,
93 ...getCSSBoxValue(
94 value,
95 "--bb--border",
96 breakpoint,
97 false,
98 buildBorderStyle,
99 ),
100 };
101 }
102 });
103 }
104
105 return style;
106 }
107
108 /**
109 * Build border radius style
110 *
111 * @param {Object} value
112 * @returns {String}
113 */
114 function buildBorderRadiusStyle(value, enableEllipticalRadius) {
115 let cssValue = "";
116 if (isValidSettingObject(value)) {
117 const {
118 "top-left": topLeft,
119 "top-right": topRight,
120 "bottom-right": bottomRight,
121 "bottom-left": bottomLeft,
122 } = value;
123 if (topLeft || topRight || bottomRight || bottomLeft) {
124 cssValue = `${topLeft ? topLeft : 0} ${topRight ? topRight : 0} ${
125 bottomRight ? bottomRight : 0
126 } ${bottomLeft ? bottomLeft : 0}`;
127 }
128
129 if (enableEllipticalRadius) {
130 const {
131 "top-left-vertical": topLeftVertical,
132 "top-right-vertical": topRightVertical,
133 "bottom-right-vertical": bottomRightVertical,
134 "bottom-left-vertical": bottomLeftVertical,
135 } = value;
136
137 if (
138 topLeftVertical ||
139 topRightVertical ||
140 bottomRightVertical ||
141 bottomLeftVertical
142 ) {
143 const borderRadiusVerticalValue = `${
144 topLeftVertical ? topLeftVertical : 0
145 } ${topRightVertical ? topRightVertical : 0} ${
146 bottomRightVertical ? bottomRightVertical : 0
147 } ${bottomLeftVertical ? bottomLeftVertical : 0}`;
148
149 cssValue = `${cssValue} / ${borderRadiusVerticalValue}`;
150 }
151 }
152 }
153
154 return cssValue;
155 }
156
157 /**
158 * Get css variable object for border radius
159 *
160 * @param {Object} attributes
161 * @returns {Object}
162 */
163 function getBorderRadiusStyle({
164 enableEllipticalRadius = false,
165 borderRadius = {},
166 }) {
167 let style = {};
168 if (isValidSettingObject(borderRadius)) {
169 Object.keys(borderRadius).forEach((breakpoint) => {
170 const { value, inherit } = borderRadius[breakpoint];
171 if (isUndefined(value)) {
172 if (inherit && isString(inherit)) {
173 const { value: inheritValue } = borderRadius[inherit] ?? {};
174 if (buildBorderRadiusStyle(inheritValue, enableEllipticalRadius)) {
175 style = {
176 ...style,
177 [`--bb--border-radius--${breakpoint}`]: `var(--bb--border-radius--${inherit})`,
178 };
179 }
180 }
181 } else {
182 const styleByBreakpoint = buildBorderRadiusStyle(
183 value,
184 enableEllipticalRadius,
185 );
186
187 if (styleByBreakpoint) {
188 style = {
189 ...style,
190 [`--bb--border-radius--${breakpoint}`]: styleByBreakpoint,
191 };
192 }
193 }
194 });
195 }
196
197 return style;
198 }
199
200 /**
201 * Get styles for the feature.
202 *
203 * @param {Object} attributes
204 * @param {Boolean} hasDeprecatedV1
205 */
206 function getFeatureStyle(attributes, hasDeprecatedV1) {
207 if (hasDeprecatedV1) {
208 return {
209 ...getBorderStyle(attributes, getCSSBoxValueDeprecatedV1),
210 ...getBorderRadiusStyle(attributes),
211 };
212 } else {
213 return {
214 ...getBorderStyle(attributes, getCSSBoxValue),
215 ...getBorderRadiusStyle(attributes),
216 };
217 }
218 }
219
220 /*
221 {
222 border: {
223 lg: {
224 value: {},
225 inherit: null,
226 },
227 md: { inherit: "lg" },
228 sm: { inherit: "lg" },
229 },
230 enableEllipticalRadius: false,
231 borderRadius: {
232 lg: {
233 value: {},
234 inherit: null,
235 },
236 md: { inherit: "lg" },
237 sm: { inherit: "lg" },
238 }
239 }
240 */
241
242 /**
243 * Override the default edit UI to include new inspector controls for custom settings.
244 *
245 * @param {Function} BlockEdit Block edit component.
246 * @return {Function} BlockEdit Modified block edit component.
247 */
248 const withInspectorControls = createHigherOrderComponent((BlockEdit) => {
249 return (props) => {
250 if (!boldblocksHasSupport(props.name, featureName)) {
251 return <BlockEdit {...props} />;
252 }
253
254 const { attributes, setAttributes } = props;
255
256 const { shouldDisplayBlockControls, blocks, updateBlockAttributes } =
257 useBlockFeature(props);
258
259 // Get all breakpoints
260 const allBreakpoints = getAllBreakpoints();
261
262 // Get current breakpoint
263 const breakpoint = getBreakpointType();
264
265 const { colors, allColors } = useMultipleOriginColors();
266
267 const onResponsiveFeatureChange = (fieldName) => (newValue) => {
268 handleChangeResponsiveSettingField({
269 fieldName,
270 setAttributes,
271 attributes,
272 breakpoint,
273 allBreakpoints,
274 blocks,
275 updateBlockAttributes,
276 })(newValue);
277 };
278
279 // Border
280 const borderByDevice = useMemo(
281 () =>
282 getResponsiveSettingFieldValue({
283 fieldName: "border",
284 attributes,
285 breakpoint,
286 }),
287 [attributes, breakpoint],
288 );
289
290 const migrateBorder =
291 (toDefaultControl = true) =>
292 (border) => {
293 let formatedBorder = border;
294 if (border && "object" === toType(border)) {
295 if (border?.top || border?.right || border?.bottom || border?.left) {
296 const sides = ["top", "right", "bottom", "left"];
297 formatedBorder = Object.keys(border).reduce((prev, side) => {
298 if (sides.includes(side) && (border[side] ?? false)) {
299 let { color } = border[side] ?? {};
300 const colorDataType = toType(color);
301 if (toDefaultControl) {
302 if (colorDataType === "object") {
303 color = color?.value;
304 }
305 } else {
306 if (colorDataType === "string") {
307 color = getColorObject(color, allColors);
308 }
309 }
310
311 return { ...prev, [side]: { ...border[side], color } };
312 }
313
314 return prev;
315 }, {});
316 } else if (border?.color) {
317 let color = border.color;
318 const colorDataType = toType(color);
319 if (toDefaultControl) {
320 if (colorDataType === "object") {
321 color = color?.value;
322 }
323 } else {
324 if (colorDataType === "string") {
325 color = getColorObject(color, allColors);
326 }
327 }
328
329 formatedBorder = { ...border, color };
330 }
331 }
332
333 return formatedBorder;
334 };
335
336 const borderNewByDevice = migrateBorder(true)(borderByDevice);
337
338 const onBorderChange = onResponsiveFeatureChange("border");
339
340 // Border radius
341 const borderRadiusByDevice = useMemo(
342 () =>
343 getResponsiveSettingFieldValue({
344 fieldName: "borderRadius",
345 attributes,
346 breakpoint,
347 }),
348 [attributes, breakpoint],
349 );
350
351 const onBorderRadiusChange = onResponsiveFeatureChange("borderRadius");
352
353 // Enable elliptical radius or not
354 const enableEllipticalRadius = getSettingFieldValue({
355 fieldName: "enableEllipticalRadius",
356 defaultValue: false,
357 attributes,
358 });
359
360 const onFeatureChange = (fieldName) => (newValue) => {
361 handleChangeSettingField({
362 fieldName,
363 setAttributes,
364 attributes,
365 blocks,
366 updateBlockAttributes,
367 })(newValue);
368 };
369
370 // A little hacked to update border radius onPaste
371 const [pastedBorderRadius, setPastedBorderRadius] = useState(false);
372
373 const borderLabel = __("Border", "content-blocks-builder");
374 const radiusLabel = __("Radius", "content-blocks-builder");
375
376 // Debugging
377 // useDebugInformation("border", attributes);
378
379 const rawBorder = attributes?.boldblocks?.border ?? {};
380 const rawBorderRadius = attributes?.boldblocks?.borderRadius ?? {};
381
382 const isEmptyValue = ({ lg = {}, md = {}, sm = {} }) =>
383 md?.inherit === "lg" && sm?.inherit === "lg" && isEmpty(lg?.value);
384
385 return (
386 <>
387 <BlockEdit {...props} />
388 {shouldDisplayBlockControls && (
389 <>
390 <Fill name="cbb-panel-border">
391 <ToolsPanelItem
392 label={borderLabel}
393 panelId="cbb-panel-border"
394 hasValue={() => !isEmpty(rawBorder) && !isEmptyValue(rawBorder)}
395 onDeselect={() => onFeatureChange("border")({})}
396 >
397 <LabelControl label={borderLabel} isResponsive={true} />
398 <BorderBoxControl
399 colors={colors}
400 onChange={(value) => {
401 if (isUndefined(borderNewByDevice)) {
402 if (value?.width && !value?.style) {
403 value = { ...value, style: "solid" };
404 }
405 }
406 onBorderChange(migrateBorder(false)(value));
407 }}
408 value={borderNewByDevice}
409 enableAlpha={true}
410 popoverOffset={40}
411 popoverPlacement="left-start"
412 __experimentalIsRenderedInSidebar={true}
413 size={"__unstable-large"}
414 />
415 </ToolsPanelItem>
416 </Fill>
417 <Fill name="cbb-panel-border">
418 <ToolsPanelItem
419 label={radiusLabel}
420 panelId="cbb-panel-border"
421 hasValue={() =>
422 !isEmpty(rawBorderRadius) && !isEmptyValue(rawBorderRadius)
423 }
424 onDeselect={() => {
425 handleChangeMultipleSettingField({
426 setAttributes,
427 attributes,
428 blocks,
429 updateBlockAttributes,
430 })({
431 borderRadius: {},
432 enableEllipticalRadius: false,
433 });
434 }}
435 >
436 <VStack spacing={3}>
437 <BorderRadiusControl
438 fields={[
439 { name: "top-left", placeholder: "top left" },
440 { name: "top-right", placeholder: "top right" },
441 { name: "bottom-right", placeholder: "bottom right" },
442 { name: "bottom-left", placeholder: "bottom left" },
443 ]}
444 label={radiusLabel}
445 values={borderRadiusByDevice}
446 onChange={onBorderRadiusChange}
447 />
448 <ToggleControl
449 label={__(
450 "Enable elliptical radius",
451 "content-blocks-builder",
452 )}
453 checked={enableEllipticalRadius}
454 onChange={onFeatureChange("enableEllipticalRadius")}
455 />
456 {enableEllipticalRadius && (
457 <>
458 <BorderRadiusControl
459 fields={[
460 {
461 name: "top-left-vertical",
462 placeholder: __(
463 "top left",
464 "content-blocks-builder",
465 ),
466 },
467 {
468 name: "top-right-vertical",
469 placeholder: __(
470 "top right",
471 "content-blocks-builder",
472 ),
473 },
474 {
475 name: "bottom-right-vertical",
476 placeholder: __(
477 "bottom right",
478 "content-blocks-builder",
479 ),
480 },
481 {
482 name: "bottom-left-vertical",
483 placeholder: __(
484 "bottom left",
485 "content-blocks-builder",
486 ),
487 },
488 ]}
489 label={__("Vertical radius", "content-blocks-builder")}
490 values={borderRadiusByDevice}
491 onChange={(value) => {
492 if (pastedBorderRadius) {
493 onBorderRadiusChange(pastedBorderRadius);
494 setPastedBorderRadius(false);
495 } else {
496 onBorderRadiusChange(value);
497 }
498 }}
499 onPaste={(event) => {
500 const found = event.clipboardData
501 .getData("text/plain")
502 .match(
503 /(\d+%)\s(\d+%)\s(\d+%)\s(\d+%)\s\/\s(\d+%)\s(\d+%)\s(\d+%)\s(\d+%)/,
504 );
505 if (found) {
506 event.preventDefault();
507 event.stopPropagation();
508 const pastedBorderRadius = {
509 "top-left": found[1],
510 "top-right": found[2],
511 "bottom-right": found[3],
512 "bottom-left": found[4],
513 "top-left-vertical": found[5],
514 "top-right-vertical": found[6],
515 "bottom-right-vertical": found[7],
516 "bottom-left-vertical": found[8],
517 };
518
519 setPastedBorderRadius(pastedBorderRadius);
520 onBorderRadiusChange(pastedBorderRadius);
521 }
522 }}
523 />
524 <BaseControl
525 help={
526 <>
527 {__(
528 "Use the following tool to learn more about elliptical radius and take advantage of it by copying and pasting the value here.",
529 "content-blocks-builder",
530 )}{" "}
531 <HelpLink
532 href="https://9elements.github.io/fancy-border-radius/"
533 label="Fancy Border Radius"
534 />
535 </>
536 }
537 />
538 </>
539 )}
540 </VStack>
541 </ToolsPanelItem>
542 </Fill>
543 </>
544 )}
545 </>
546 );
547 };
548 }, "withInspectorControls");
549 addFilter(
550 "editor.BlockEdit",
551 `boldblocks/${featureName}/withInspectorControls`,
552 withInspectorControls,
553 );
554
555 /**
556 * Override props assigned to save component to inject the CSS variables definition.
557 *
558 * @param {Object} props Additional props applied to save element.
559 * @param {Object} blockType Block type.
560 * @param {Object} attributes Block attributes.
561 *
562 * @return {Object} Filtered props applied to save element.
563 */
564 export function addSaveProps(props, blockType, attributes) {
565 if (!boldblocksHasSupport(blockType, featureName)) {
566 return props;
567 }
568
569 const border = (({
570 boldblocks: { border, borderRadius, enableEllipticalRadius } = {},
571 }) => ({
572 border,
573 borderRadius,
574 enableEllipticalRadius,
575 }))(attributes);
576
577 const hasDeprecatedV1 = hasBlockSupport(
578 blockType,
579 "CBBDeprecatedStyleUnsetValueV1",
580 );
581
582 const isDeprecatedDynamicStyle = hasBlockSupport(
583 blockType,
584 "CBBDeprecatedDynamicStyleV1",
585 );
586
587 let featureStyles = {};
588 if (attributes?.isBlockEdit) {
589 featureStyles = usePropsStyle({
590 value: border,
591 getStyle: (value) => () => getFeatureStyle(value, hasDeprecatedV1),
592 });
593 } else {
594 if (isDeprecatedDynamicStyle) {
595 featureStyles = getFeatureStyle(border, hasDeprecatedV1);
596 }
597 }
598
599 props.style = {
600 ...props.style,
601 ...featureStyles,
602 };
603
604 return props;
605 }
606 addFilter(
607 "blocks.getSaveContent.extraProps",
608 `boldblocks/${featureName}/addSaveProps`,
609 addSaveProps,
610 );
611
612 /**
613 * Filters registered block settings to extend the block edit wrapper
614 * to apply the desired styles and className properly.
615 *
616 * @param {Object} settings Original block settings.
617 *
618 * @return {Object}.Filtered block settings.
619 */
620 addFilter(
621 "blocks.registerBlockType",
622 `boldblocks/${featureName}/addEditProps`,
623 addEditProps(
624 addSaveProps,
625 (settings, { featureName }) => boldblocksHasSupport(settings, featureName),
626 { featureName },
627 ),
628 );
629