| 1 |
/** |
| 2 |
* External dependencies |
| 3 |
*/ |
| 4 |
import { isObject, isString } from "lodash"; |
| 5 |
|
| 6 |
/** |
| 7 |
* WordPress dependencies |
| 8 |
*/ |
| 9 |
import { __ } from "@wordpress/i18n"; |
| 10 |
|
| 11 |
/** |
| 12 |
* Internal dependencies |
| 13 |
*/ |
| 14 |
import { isValidSettingObject } from "sdk/utils"; |
| 15 |
import { toType } from "../../utils/dom"; |
| 16 |
|
| 17 |
/** |
| 18 |
* Breakpoint values in px |
| 19 |
*/ |
| 20 |
export const breakpointValues = { |
| 21 |
sm: 576, |
| 22 |
md: 768, |
| 23 |
lg: 1024, |
| 24 |
}; |
| 25 |
|
| 26 |
/** |
| 27 |
* Define the list of effect options |
| 28 |
*/ |
| 29 |
export const effectOptions = [ |
| 30 |
{ value: "slide", label: __("Slide", "content-blocks-builder") }, |
| 31 |
{ value: "fade", label: __("Fade", "content-blocks-builder") }, |
| 32 |
{ value: "flip", label: __("Flip", "content-blocks-builder") }, |
| 33 |
{ value: "cube", label: __("Cube", "content-blocks-builder") }, |
| 34 |
{ value: "cards", label: __("Cards", "content-blocks-builder") }, |
| 35 |
]; |
| 36 |
|
| 37 |
/** |
| 38 |
* If the current effect support multiple slides |
| 39 |
* |
| 40 |
* @param {String} effect |
| 41 |
* @returns {Boolean} |
| 42 |
*/ |
| 43 |
export const isSingleSlideEffect = (effect) => |
| 44 |
["fade", "flip", "cube", "cards", "creative"].includes(effect); |
| 45 |
|
| 46 |
/** |
| 47 |
* Default pagination |
| 48 |
*/ |
| 49 |
export const paginationDefault = { |
| 50 |
enable: true, |
| 51 |
bulletWidth: "12px", |
| 52 |
bulletHeight: "12px", |
| 53 |
spacing: "10px", |
| 54 |
color: "", |
| 55 |
inactiveColor: "", |
| 56 |
opacity: 0.3, |
| 57 |
dynamicBullets: true, |
| 58 |
horizontalGap: "5px", |
| 59 |
}; |
| 60 |
|
| 61 |
/** |
| 62 |
* Default navigation |
| 63 |
*/ |
| 64 |
export const navigationDefault = { |
| 65 |
enable: false, |
| 66 |
size: "44px", |
| 67 |
spacing: "10px", |
| 68 |
color: "", |
| 69 |
}; |
| 70 |
|
| 71 |
/** |
| 72 |
* Default scrollbar |
| 73 |
*/ |
| 74 |
export const scrollbarDefault = { |
| 75 |
enable: false, |
| 76 |
trackSize: "5px", |
| 77 |
draggable: true, |
| 78 |
hide: false, |
| 79 |
snapOnRelease: true, |
| 80 |
trackColor: "", |
| 81 |
sliderColor: "", |
| 82 |
}; |
| 83 |
|
| 84 |
/** |
| 85 |
* Default carousel settings |
| 86 |
*/ |
| 87 |
export const carouselSettingsDefault = { |
| 88 |
speed: 500, |
| 89 |
effect: "slide", |
| 90 |
effectSettings: {}, |
| 91 |
loopType: "infinite", // rewind, none |
| 92 |
autoplay: { enable: true, delay: 3000 }, |
| 93 |
slidesPerView: 1, |
| 94 |
spaceBetween: 0, |
| 95 |
enableResponsive: false, |
| 96 |
breakpoints: { |
| 97 |
lg: { |
| 98 |
value: { |
| 99 |
slidesPerView: 4, // 'auto' |
| 100 |
spaceBetween: 0, |
| 101 |
}, |
| 102 |
inherit: null, |
| 103 |
}, |
| 104 |
md: { inherit: "lg" }, |
| 105 |
sm: { inherit: "lg" }, |
| 106 |
}, |
| 107 |
pagination: paginationDefault, |
| 108 |
navigation: navigationDefault, |
| 109 |
}; |
| 110 |
|
| 111 |
/** |
| 112 |
* Build the carousel settings. |
| 113 |
* |
| 114 |
* @param {Object} carousel |
| 115 |
* @param {Boolean} isDeprecatedAutoplay |
| 116 |
*/ |
| 117 |
export function buildCarouselSettings( |
| 118 |
carousel, |
| 119 |
{ isDeprecatedAutoplay = false, isBlockEdit }, |
| 120 |
) { |
| 121 |
// Extract sub-values. |
| 122 |
const { |
| 123 |
speed, |
| 124 |
loopType, |
| 125 |
autoplay, |
| 126 |
playPause = {}, |
| 127 |
direction = "horizontal", |
| 128 |
effect: effectRaw, |
| 129 |
effectSettings, |
| 130 |
slidesPerView: slidesPerViewRaw, |
| 131 |
slidesPerGroup, |
| 132 |
spaceBetween, |
| 133 |
centeredSlides, |
| 134 |
centeredSlidesSettings, |
| 135 |
enableResponsive, |
| 136 |
breakpoints, |
| 137 |
oneSlidePerViewInMobile = false, |
| 138 |
pagination = paginationDefault, |
| 139 |
navigation = navigationDefault, |
| 140 |
scrollbar = scrollbarDefault, |
| 141 |
equalHeight = false, |
| 142 |
thumbs, |
| 143 |
} = carousel ?? {}; |
| 144 |
|
| 145 |
let dataset = {}; |
| 146 |
|
| 147 |
// Speed |
| 148 |
if (speed) { |
| 149 |
dataset.speed = speed; |
| 150 |
} |
| 151 |
|
| 152 |
// Loop and rewind |
| 153 |
dataset.loop = false; |
| 154 |
dataset.rewind = false; |
| 155 |
if (loopType === "infinite") { |
| 156 |
dataset.loop = true; |
| 157 |
} else if (loopType === "rewind") { |
| 158 |
dataset.rewind = true; |
| 159 |
} |
| 160 |
|
| 161 |
// Autoplay |
| 162 |
dataset.autoplay = false; |
| 163 |
let enableAutoplay; |
| 164 |
if (isDeprecatedAutoplay) { |
| 165 |
enableAutoplay = autoplay && isObject(autoplay); |
| 166 |
} else { |
| 167 |
enableAutoplay = autoplay && isObject(autoplay) && autoplay?.enable; |
| 168 |
} |
| 169 |
if (enableAutoplay) { |
| 170 |
if (isDeprecatedAutoplay) { |
| 171 |
const { delay } = autoplay; |
| 172 |
if (delay) { |
| 173 |
dataset.autoplay = { |
| 174 |
delay, |
| 175 |
}; |
| 176 |
} |
| 177 |
} else { |
| 178 |
const { enable, ...restAutoplay } = autoplay; |
| 179 |
dataset.autoplay = restAutoplay; |
| 180 |
} |
| 181 |
|
| 182 |
// Only in edit mode |
| 183 |
if (isBlockEdit) { |
| 184 |
if (!dataset.loop && !dataset.rewind) { |
| 185 |
dataset.autoplay = { ...dataset.autoplay, stopOnLastSlide: true }; |
| 186 |
} |
| 187 |
} |
| 188 |
} |
| 189 |
|
| 190 |
// Direction |
| 191 |
if (direction) { |
| 192 |
dataset.direction = direction; |
| 193 |
} |
| 194 |
|
| 195 |
// Effect |
| 196 |
const effect = |
| 197 |
toType(effectRaw) === "object" ? effectRaw?.name ?? "slide" : effectRaw; |
| 198 |
if (effect) { |
| 199 |
const { [`${effect}Effect`]: settings } = effectSettings ?? {}; |
| 200 |
let refinedEffect = effectRaw; |
| 201 |
// Deprecated issue for very old version |
| 202 |
if ( |
| 203 |
toType(effectRaw) === "object" && |
| 204 |
toType(effectRaw?.settings) === "array" && |
| 205 |
effectRaw.settings.length === 0 |
| 206 |
) { |
| 207 |
refinedEffect = { ...effectRaw, settings: {} }; |
| 208 |
} |
| 209 |
dataset.effect = refinedEffect; |
| 210 |
|
| 211 |
// Effect settings |
| 212 |
if (effect === "fade") { |
| 213 |
dataset.fadeEffect = { crossFade: true }; |
| 214 |
} else if (effect === "coverflow") { |
| 215 |
if (isObject(settings)) { |
| 216 |
dataset.coverflowEffect = settings; |
| 217 |
} else { |
| 218 |
dataset.coverflowEffect = { |
| 219 |
slideShadows: false, |
| 220 |
}; |
| 221 |
} |
| 222 |
} else if (effect === "creative") { |
| 223 |
if (isObject(settings)) { |
| 224 |
dataset.creativeEffect = settings; |
| 225 |
} else { |
| 226 |
dataset.creativeEffect = { |
| 227 |
prev: { |
| 228 |
translate: ["-20%", 0, -1], |
| 229 |
}, |
| 230 |
next: { |
| 231 |
translate: ["100%", 0, 0], |
| 232 |
}, |
| 233 |
}; |
| 234 |
} |
| 235 |
|
| 236 |
dataset.direction = "horizontal"; |
| 237 |
} |
| 238 |
} |
| 239 |
|
| 240 |
// slidesPerView |
| 241 |
if (isSingleSlideEffect(effect)) { |
| 242 |
dataset.slidesPerView = 1; |
| 243 |
} else { |
| 244 |
let slidesPerView = slidesPerViewRaw; |
| 245 |
if (toType(slidesPerViewRaw) === "object") { |
| 246 |
slidesPerView = slidesPerViewRaw?.auto |
| 247 |
? "auto" |
| 248 |
: slidesPerViewRaw?.value ?? 1; |
| 249 |
} |
| 250 |
|
| 251 |
if (slidesPerViewRaw) { |
| 252 |
// Only in edit mode |
| 253 |
if (isBlockEdit) { |
| 254 |
dataset.slidesPerView = slidesPerView; |
| 255 |
} else { |
| 256 |
dataset.slidesPerView = slidesPerViewRaw; |
| 257 |
} |
| 258 |
|
| 259 |
if (slidesPerView > 1 && slidesPerGroup) { |
| 260 |
dataset.slidesPerGroup = slidesPerGroup; |
| 261 |
} |
| 262 |
} |
| 263 |
|
| 264 |
// Breakpoints |
| 265 |
if (enableResponsive && isObject(breakpoints)) { |
| 266 |
let breakpointAtts = {}; |
| 267 |
Object.keys(breakpoints).forEach((breakpoint) => { |
| 268 |
const { |
| 269 |
[breakpoint]: { value, inherit }, |
| 270 |
} = breakpoints; |
| 271 |
|
| 272 |
let valueByBreakpoint; |
| 273 |
if (value && isObject(value)) { |
| 274 |
valueByBreakpoint = value; |
| 275 |
} |
| 276 |
|
| 277 |
if (!valueByBreakpoint && isString(inherit)) { |
| 278 |
const { value: inheritValue } = breakpoints[inherit] ?? {}; |
| 279 |
if (inheritValue && isObject(inheritValue)) { |
| 280 |
valueByBreakpoint = inheritValue; |
| 281 |
} |
| 282 |
} |
| 283 |
|
| 284 |
if (isObject(valueByBreakpoint)) { |
| 285 |
// Only in edit mode |
| 286 |
if (isBlockEdit) { |
| 287 |
valueByBreakpoint.slidesPerView = refineSlidesPerView( |
| 288 |
valueByBreakpoint?.slidesPerView ?? 1, |
| 289 |
); |
| 290 |
} |
| 291 |
breakpointAtts[breakpointValues[breakpoint]] = valueByBreakpoint; |
| 292 |
} |
| 293 |
}); |
| 294 |
|
| 295 |
dataset.breakpoints = breakpointAtts; |
| 296 |
|
| 297 |
// Only in edit mode |
| 298 |
if (isBlockEdit) { |
| 299 |
const { slidesPerView, slidesPerGroup, spaceBetween } = |
| 300 |
Object.values(breakpointAtts)[0] ?? {}; |
| 301 |
if (slidesPerView) { |
| 302 |
dataset.slidesPerView = slidesPerView; |
| 303 |
if (slidesPerView > 1 && slidesPerGroup) { |
| 304 |
dataset.slidesPerGroup = slidesPerGroup; |
| 305 |
} |
| 306 |
if (spaceBetween) { |
| 307 |
dataset.spaceBetween = spaceBetween; |
| 308 |
} |
| 309 |
} |
| 310 |
} |
| 311 |
} else { |
| 312 |
if (oneSlidePerViewInMobile) { |
| 313 |
let breakpointAtts = { |
| 314 |
[breakpointValues.sm]: { |
| 315 |
slidesPerView: 1, |
| 316 |
spaceBetween: 0, |
| 317 |
}, |
| 318 |
[breakpointValues.md]: { |
| 319 |
// Only in edit mode |
| 320 |
slidesPerView: isBlockEdit ? slidesPerView : slidesPerViewRaw, |
| 321 |
spaceBetween, |
| 322 |
}, |
| 323 |
}; |
| 324 |
|
| 325 |
dataset.breakpoints = breakpointAtts; |
| 326 |
} |
| 327 |
} |
| 328 |
} |
| 329 |
|
| 330 |
// centeredSlides |
| 331 |
dataset.centeredSlides = centeredSlides; |
| 332 |
if ( |
| 333 |
centeredSlides && |
| 334 |
effect === "slide" && |
| 335 |
centeredSlidesSettings && |
| 336 |
centeredSlidesSettings?.enable |
| 337 |
) { |
| 338 |
dataset.centeredSlidesSettings = centeredSlidesSettings; |
| 339 |
} |
| 340 |
|
| 341 |
// Remove the centeredSlides |
| 342 |
if (isBlockEdit) { |
| 343 |
if ( |
| 344 |
isSingleSlideEffect(effect) || |
| 345 |
(dataset.slidesPerView <= 1 && !enableResponsive) |
| 346 |
) { |
| 347 |
delete dataset.centeredSlides; |
| 348 |
delete dataset.centeredSlidesSettings; |
| 349 |
} |
| 350 |
} |
| 351 |
|
| 352 |
// spaceBetween |
| 353 |
if (spaceBetween) { |
| 354 |
dataset.spaceBetween = spaceBetween; |
| 355 |
} |
| 356 |
|
| 357 |
// Pagination |
| 358 |
dataset.pagination = !!pagination?.enable; |
| 359 |
dataset.paginationSettings = pagination; |
| 360 |
|
| 361 |
// Navigation |
| 362 |
dataset.navigation = !!navigation?.enable; |
| 363 |
dataset.navigationSettings = navigation; |
| 364 |
|
| 365 |
// Play/pause since 2.8.2 |
| 366 |
if (playPause?.enable) { |
| 367 |
dataset.playPause = playPause; |
| 368 |
} |
| 369 |
|
| 370 |
// Scrollbar |
| 371 |
dataset.scrollbar = !!scrollbar?.enable; |
| 372 |
dataset.scrollbarSettings = scrollbar; |
| 373 |
|
| 374 |
// Equal height? |
| 375 |
if (equalHeight) { |
| 376 |
// Only add the property when it is true to prevent from invalid content |
| 377 |
dataset.equalHeight = equalHeight; |
| 378 |
} |
| 379 |
|
| 380 |
if (thumbs && thumbs?.enable) { |
| 381 |
dataset.thumbsSettings = thumbs; |
| 382 |
} |
| 383 |
|
| 384 |
return dataset; |
| 385 |
} |
| 386 |
|
| 387 |
export const buildCarouselEditStyle = ({ |
| 388 |
selector, |
| 389 |
wraperSelector = ".carousel__inner", |
| 390 |
itemSelector = ".is-carousel-item", |
| 391 |
cssVariables, |
| 392 |
}) => { |
| 393 |
let styles = ` |
| 394 |
${selector} {${cssVariables}} |
| 395 |
/* Appender */ |
| 396 |
${selector}.cbb-carousel-edit > ${wraperSelector} > .block-list-appender .block-list-appender__toggle { |
| 397 |
display: flex; |
| 398 |
} |
| 399 |
${selector}.cbb-carousel-edit > ${wraperSelector} > .block-list-appender { |
| 400 |
bottom: 17px; |
| 401 |
left: calc(var(--bb--carousel--inner-width, 100%) - 24px); |
| 402 |
} |
| 403 |
/* Edit mode */ |
| 404 |
${selector}.cbb-carousel-edit { |
| 405 |
overflow-x: auto; |
| 406 |
overflow-y: hidden; |
| 407 |
} |
| 408 |
${selector}.cbb-carousel-edit > ${wraperSelector} { |
| 409 |
position: relative; |
| 410 |
display: flex; |
| 411 |
gap: var(--bb--carousel--spacebetween, 0); |
| 412 |
max-width: none; |
| 413 |
scroll-snap-type: x mandatory; |
| 414 |
} |
| 415 |
${selector}.cbb-carousel-edit > ${wraperSelector} > ${itemSelector} { |
| 416 |
flex-shrink: 0; |
| 417 |
scroll-snap-align: start; |
| 418 |
} |
| 419 |
${selector}.cbb-carousel-edit > ${wraperSelector} > ${itemSelector} { |
| 420 |
width: var(--bb--carousel--item-width, 100%) !important; |
| 421 |
} |
| 422 |
`; |
| 423 |
|
| 424 |
return styles; |
| 425 |
}; |
| 426 |
|
| 427 |
/** |
| 428 |
* Build style for EDIT mode |
| 429 |
* |
| 430 |
* @param {Object} carousel |
| 431 |
* @param {Integer} slideCount |
| 432 |
* @param {String} devicePrefix |
| 433 |
* @returns {String} |
| 434 |
*/ |
| 435 |
export const buildEditLayoutStyle = (carousel, slideCount, devicePrefix) => { |
| 436 |
const { |
| 437 |
effect, |
| 438 |
slidesPerView, |
| 439 |
spaceBetween, |
| 440 |
enableResponsive, |
| 441 |
breakpoints = {}, |
| 442 |
oneSlidePerViewInMobile = false, |
| 443 |
} = carousel; |
| 444 |
|
| 445 |
const styles = []; |
| 446 |
let slidesPerViewValue = refineSlidesPerView(slidesPerView); |
| 447 |
let spaceBetweenValue = spaceBetween; |
| 448 |
|
| 449 |
if (isSingleSlideEffect(effect)) { |
| 450 |
slidesPerViewValue = 1; |
| 451 |
} else { |
| 452 |
if (enableResponsive && isValidSettingObject(breakpoints)) { |
| 453 |
let { value, inherit } = breakpoints[devicePrefix] ?? {}; |
| 454 |
if (!isValidSettingObject(value) && inherit) { |
| 455 |
value = (breakpoints[inherit] ?? {})["value"] ?? {}; |
| 456 |
} |
| 457 |
|
| 458 |
if (isValidSettingObject(value)) { |
| 459 |
if (value?.slidesPerView) { |
| 460 |
slidesPerViewValue = refineSlidesPerView(value.slidesPerView); |
| 461 |
} |
| 462 |
if (value?.spaceBetween) { |
| 463 |
spaceBetweenValue = value.spaceBetween; |
| 464 |
} |
| 465 |
} |
| 466 |
} else { |
| 467 |
if (oneSlidePerViewInMobile && devicePrefix === "sm") { |
| 468 |
slidesPerViewValue = 1; |
| 469 |
} |
| 470 |
} |
| 471 |
} |
| 472 |
|
| 473 |
if (slidesPerViewValue && slidesPerViewValue !== "auto") { |
| 474 |
styles.push(`--bb--carousel--slidesperview: ${slidesPerViewValue};`); |
| 475 |
slidesPerViewValue = parseFloat(slidesPerViewValue); |
| 476 |
slidesPerViewValue = isNaN(slidesPerViewValue) ? 1 : slidesPerViewValue; |
| 477 |
styles.push( |
| 478 |
`--bb--carousel--inner-width: calc(${ |
| 479 |
(1 / slidesPerViewValue) * slideCount * 100 |
| 480 |
}%);`, |
| 481 |
); |
| 482 |
|
| 483 |
styles.push( |
| 484 |
`--bb--carousel--item-width: calc(${100 / slidesPerViewValue}% - ${ |
| 485 |
((slidesPerViewValue - 1) * spaceBetweenValue) / slidesPerViewValue |
| 486 |
}px);`, |
| 487 |
); |
| 488 |
} |
| 489 |
|
| 490 |
if (spaceBetweenValue) { |
| 491 |
styles.push(`--bb--carousel--spacebetween: ${spaceBetweenValue}px;`); |
| 492 |
} |
| 493 |
|
| 494 |
return styles.join(""); |
| 495 |
}; |
| 496 |
|
| 497 |
/** |
| 498 |
* Get the valid value for slidesPerView |
| 499 |
* |
| 500 |
* @param {mixed} rawValue |
| 501 |
* @returns {String} |
| 502 |
*/ |
| 503 |
const refineSlidesPerView = (rawValue) => { |
| 504 |
const value = |
| 505 |
toType(rawValue) === "object" |
| 506 |
? rawValue?.auto |
| 507 |
? "auto" |
| 508 |
: rawValue?.value |
| 509 |
: rawValue; |
| 510 |
|
| 511 |
if (value === "auto") return value; |
| 512 |
|
| 513 |
const numericValue = parseFloat(value); |
| 514 |
return isNaN(numericValue) ? 1 : numericValue; |
| 515 |
}; |
| 516 |
|
| 517 |
export const minimumSlidesNeeded = ( |
| 518 |
spv, |
| 519 |
spg = 1, |
| 520 |
centeredSlides = false, |
| 521 |
breakpoints = false, |
| 522 |
) => { |
| 523 |
if (breakpoints) { |
| 524 |
spv = breakpoints?.slidesPerView; |
| 525 |
spg = breakpoints?.slidesPerGroup ?? 1; |
| 526 |
} |
| 527 |
|
| 528 |
if (!spv || spv === "auto") { |
| 529 |
return 0; |
| 530 |
} |
| 531 |
|
| 532 |
spv = Math.ceil(parseFloat(spv)); |
| 533 |
if (centeredSlides && spv % 2 === 0) spv += 1; |
| 534 |
|
| 535 |
let loopedSlides = centeredSlides ? Math.max(spg, Math.ceil(spv / 2)) : spg; |
| 536 |
|
| 537 |
if (loopedSlides % spg !== 0) { |
| 538 |
loopedSlides += spg - (loopedSlides % spg); |
| 539 |
} |
| 540 |
|
| 541 |
return spv + loopedSlides; |
| 542 |
}; |
| 543 |
|