Changeset 3423082
- Timestamp:
- 12/18/2025 04:17:50 PM (3 months ago)
- Location:
- native-blocks-carousel/trunk
- Files:
-
- 3 deleted
- 7 edited
-
.gitignore (deleted)
-
any-block-carousel-slider.php (modified) (2 diffs)
-
assets/css/carousel.css (modified) (29 diffs)
-
assets/js/carousel-editor.js (modified) (14 diffs)
-
assets/js/carousel-frontend-init.js (modified) (6 diffs)
-
includes/Renderer.php (modified) (2 diffs)
-
includes/ThemeStyles.php (modified) (3 diffs)
-
package.json (deleted)
-
playground-blueprint.json (deleted)
-
readme.txt (modified) (5 diffs)
Legend:
- Unmodified
- Added
- Removed
-
native-blocks-carousel/trunk/any-block-carousel-slider.php
r3421232 r3423082 5 5 * GitHub Plugin URI: https://github.com/WEBLAZER/native-blocks-carousel 6 6 * Description: Transform any WordPress block into a performant carousel with pure CSS. Zero JavaScript, works with Gallery, Grid, Post Template, and Group blocks. 7 * Version: 1.0. 3.67 * Version: 1.0.4 8 8 * Author: weblazer 9 9 * Author URI: https://profiles.wordpress.org/weblazer/ … … 29 29 } 30 30 31 define('ANY_BLOCK_CAROUSEL_SLIDER_VERSION', '1.0. 3.6');31 define('ANY_BLOCK_CAROUSEL_SLIDER_VERSION', '1.0.4'); 32 32 define('ANY_BLOCK_CAROUSEL_SLIDER_PLUGIN_FILE', __FILE__); 33 33 define('ANY_BLOCK_CAROUSEL_SLIDER_PLUGIN_URL', plugin_dir_url(__FILE__)); -
native-blocks-carousel/trunk/assets/css/carousel.css
r3421254 r3423082 7 7 * 8 8 * @package AnyBlockCarouselSlider 9 * @version 1.0. 29 * @version 1.0.4 10 10 * @author weblazer35 11 11 */ … … 35 35 --carousel-padding-right: 0px; 36 36 --carousel-padding-top: 0px; 37 /* Default carousel padding-top */38 37 --carousel-padding-bottom: 0px; 39 /* Default carousel padding-bottom */40 38 } 41 39 … … 46 44 /* Force every parent containing a carousel to use position: relative */ 47 45 /* Required so ::scroll-button and ::scroll-marker are positioned correctly */ 48 /* ::scroll-button tends to position relative to a distant ancestor, not the carousel itself */49 46 :has(.abcs) { 50 47 position: relative !important; 51 48 } 52 49 53 /* Also force the direct parent to be positioned relative */54 /* Important to correctly anchor buttons to the container */55 50 .abcs { 56 51 position: relative !important; 57 52 } 58 53 59 /* S'assurer que le parent direct du carousel est aussi en position relative */ 60 /* Double safety for cases where :has() is not supported everywhere */ 61 /* IMPORTANT: the parent must be the positioning context for the buttons */ 54 /* Ensure the direct parent is also positioned relative */ 62 55 *:has(> .abcs) { 63 56 position: relative !important; 64 /* Ensure the parent can contain positioned buttons */65 57 overflow: visible; 66 /* S'assurer que le parent contraint la largeur du carousel */67 58 max-width: 100%; 68 59 box-sizing: border-box; 69 60 } 70 61 62 /* Main carousel styles - grouped selectors for editor compatibility */ 71 63 .abcs, 72 64 .block-editor-block-list__layout .abcs, 73 65 .editor-styles-wrapper .abcs { 74 /* Layout */75 66 position: relative !important; 76 67 width: 100%; … … 81 72 align-items: stretch; 82 73 justify-content: flex-start; 83 /* Utiliser le gap pour l'espacement entre les slides */84 74 gap: var(--wp--style--block-gap, 1rem); 85 75 padding: 1rem 0px; 86 76 87 /* Scroll */88 77 overflow-x: scroll; 89 78 overflow-y: visible; … … 92 81 -webkit-overflow-scrolling: touch; 93 82 94 /* Scroll padding pour respecter le padding horizontal */95 /* Automatically set by PHP/JavaScript based on padding-left/right */96 83 scroll-padding-left: var(--carousel-scroll-padding-left, 0); 97 84 scroll-padding-right: var(--carousel-scroll-padding-right, 0); 98 85 99 /* Masquer les scrollbars */100 86 -ms-overflow-style: none; 101 87 scrollbar-width: none; 102 88 -webkit-scrollbar: none; 103 89 104 /* Scroll markers (future CSS feature) */105 90 scroll-marker-group: after; 106 91 107 /* Force the carousel to be the positioning context for its pseudo-elements */108 /* These three properties create a robust positioning context */109 92 isolation: isolate; 110 93 contain: layout !important; 111 94 transform: translateZ(0); 112 /* Forces a new positioning context + GPU acceleration */113 95 } 114 96 … … 120 102 .block-editor-block-list__layout .abcs>*, 121 103 .editor-styles-wrapper .abcs>* { 122 /* Layout */123 104 position: relative; 124 105 flex: 0 0 auto; 125 106 width: 100%; 126 107 height: auto; 127 128 /* Scroll snap */129 108 scroll-snap-align: start; 130 scroll-snap-stop: normal; 131 132 /* Spacing */ 109 scroll-snap-stop: always; 133 110 margin: 0px !important; 134 111 padding: 0px; … … 144 121 } 145 122 146 /* Backgrounds */147 123 .abcs .has-background, 148 124 .block-editor-block-list__layout .abcs .has-background, … … 155 131 ========================================================================== */ 156 132 157 /* Disable flex-wrap and the gallery column system */158 133 .wp-block-gallery.abcs, 159 134 .block-editor-block-list__layout .wp-block-gallery.abcs, … … 163 138 } 164 139 165 /* Chaque figure dans la galerie carousel */166 140 .wp-block-gallery.abcs>.wp-block-image, 167 141 .block-editor-block-list__layout .wp-block-gallery.abcs>.wp-block-image, 168 142 .editor-styles-wrapper .wp-block-gallery.abcs>.wp-block-image { 169 143 flex: 0 0 auto; 170 /* Default width: 6 images visible on wide desktop (> 1280px) */171 144 width: calc(16.666% - var(--wp--style--block-gap, 1rem) * 5 / 6); 172 145 min-width: calc(16.666% - var(--wp--style--block-gap, 1rem) * 5 / 6); … … 175 148 } 176 149 177 /* Images dans la galerie carousel */178 150 .wp-block-gallery.abcs>.wp-block-image img, 179 151 .block-editor-block-list__layout .wp-block-gallery.abcs>.wp-block-image img, … … 188 160 ========================================================================== */ 189 161 190 /* Convertir le CSS Grid en Flexbox pour le carousel */191 162 .is-layout-grid.abcs, 192 163 .block-editor-block-list__layout .is-layout-grid.abcs, … … 197 168 } 198 169 199 /* Child elements in a carousel grid */200 /* Grids in carousel mode display items with a fixed width */201 /* Use --carousel-grid-item-width to customise */202 170 .is-layout-grid.abcs>*, 203 171 .block-editor-block-list__layout .is-layout-grid.abcs>*, 204 172 .editor-styles-wrapper .is-layout-grid.abcs>* { 205 173 flex: 0 0 auto; 206 /* Default width sized for 3 visible columns */207 174 width: var(--carousel-grid-item-width, calc(33.333% - var(--wp--style--block-gap, 1rem) * 2 / 3)); 208 175 min-width: var(--carousel-grid-item-width, calc(33.333% - var(--wp--style--block-gap, 1rem) * 2 / 3)); … … 227 194 228 195 /* Helper classes to define the number of visible columns in the carousel grid */ 229 /* Add these classes to the Grid block to control item width */ 230 231 /* abcs-cols-1: 1 visible item (full width) */ 196 /* Simplified: use a generic rule with --carousel-grid-item-width variable */ 197 .is-layout-grid.abcs[class*="abcs-cols-"]>* { 198 width: var(--carousel-grid-item-width); 199 min-width: var(--carousel-grid-item-width); 200 } 201 202 /* Define --carousel-grid-item-width for each column count */ 232 203 .is-layout-grid.abcs.abcs-cols-1>* { 233 204 --carousel-grid-item-width: 100%; 234 width: 100%; 235 min-width: 100%; 236 } 237 238 /* abcs-cols-2: 2 visible items */ 205 } 206 239 207 .is-layout-grid.abcs.abcs-cols-2>* { 240 208 --carousel-grid-item-width: calc(50% - var(--wp--style--block-gap, 1rem) / 2); 241 width: calc(50% - var(--wp--style--block-gap, 1rem) / 2); 242 min-width: calc(50% - var(--wp--style--block-gap, 1rem) / 2); 243 } 244 245 /* abcs-cols-3: 3 visible items (default) */ 209 } 210 246 211 .is-layout-grid.abcs.abcs-cols-3>* { 247 212 --carousel-grid-item-width: calc(33.333% - var(--wp--style--block-gap, 1rem) * 2 / 3); 248 width: calc(33.333% - var(--wp--style--block-gap, 1rem) * 2 / 3); 249 min-width: calc(33.333% - var(--wp--style--block-gap, 1rem) * 2 / 3); 250 } 251 252 /* abcs-cols-4: 4 visible items */ 213 } 214 253 215 .is-layout-grid.abcs.abcs-cols-4>* { 254 216 --carousel-grid-item-width: calc(25% - var(--wp--style--block-gap, 1rem) * 3 / 4); 255 width: calc(25% - var(--wp--style--block-gap, 1rem) * 3 / 4); 256 min-width: calc(25% - var(--wp--style--block-gap, 1rem) * 3 / 4); 257 } 258 259 /* abcs-cols-5: 5 visible items */ 217 } 218 260 219 .is-layout-grid.abcs.abcs-cols-5>* { 261 220 --carousel-grid-item-width: calc(20% - var(--wp--style--block-gap, 1rem) * 4 / 5); 262 width: calc(20% - var(--wp--style--block-gap, 1rem) * 4 / 5); 263 min-width: calc(20% - var(--wp--style--block-gap, 1rem) * 4 / 5); 264 } 265 266 /* abcs-cols-6: 6 visible items */ 221 } 222 267 223 .is-layout-grid.abcs.abcs-cols-6>* { 268 224 --carousel-grid-item-width: calc(16.666% - var(--wp--style--block-gap, 1rem) * 5 / 6); 269 width: calc(16.666% - var(--wp--style--block-gap, 1rem) * 5 / 6); 270 min-width: calc(16.666% - var(--wp--style--block-gap, 1rem) * 5 / 6); 271 } 272 273 /* abcs-cols-7: 7 visible items */ 225 } 226 274 227 .is-layout-grid.abcs.abcs-cols-7>* { 275 228 --carousel-grid-item-width: calc(14.285% - var(--wp--style--block-gap, 1rem) * 6 / 7); 276 width: calc(14.285% - var(--wp--style--block-gap, 1rem) * 6 / 7); 277 min-width: calc(14.285% - var(--wp--style--block-gap, 1rem) * 6 / 7); 278 } 279 280 /* abcs-cols-8: 8 visible items */ 229 } 230 281 231 .is-layout-grid.abcs.abcs-cols-8>* { 282 232 --carousel-grid-item-width: calc(12.5% - var(--wp--style--block-gap, 1rem) * 7 / 8); 283 width: calc(12.5% - var(--wp--style--block-gap, 1rem) * 7 / 8); 284 min-width: calc(12.5% - var(--wp--style--block-gap, 1rem) * 7 / 8); 285 } 286 287 /* abcs-cols-9: 9 visible items */ 233 } 234 288 235 .is-layout-grid.abcs.abcs-cols-9>* { 289 236 --carousel-grid-item-width: calc(11.111% - var(--wp--style--block-gap, 1rem) * 8 / 9); 290 width: calc(11.111% - var(--wp--style--block-gap, 1rem) * 8 / 9); 291 min-width: calc(11.111% - var(--wp--style--block-gap, 1rem) * 8 / 9); 292 } 293 294 /* abcs-cols-10: 10 visible items */ 237 } 238 295 239 .is-layout-grid.abcs.abcs-cols-10>* { 296 240 --carousel-grid-item-width: calc(10% - var(--wp--style--block-gap, 1rem) * 9 / 10); 297 width: calc(10% - var(--wp--style--block-gap, 1rem) * 9 / 10); 298 min-width: calc(10% - var(--wp--style--block-gap, 1rem) * 9 / 10); 299 } 300 301 /* abcs-cols-11: 11 visible items */ 241 } 242 302 243 .is-layout-grid.abcs.abcs-cols-11>* { 303 244 --carousel-grid-item-width: calc(9.090% - var(--wp--style--block-gap, 1rem) * 10 / 11); 304 width: calc(9.090% - var(--wp--style--block-gap, 1rem) * 10 / 11); 305 min-width: calc(9.090% - var(--wp--style--block-gap, 1rem) * 10 / 11); 306 } 307 308 /* abcs-cols-12: 12 visible items */ 245 } 246 309 247 .is-layout-grid.abcs.abcs-cols-12>* { 310 248 --carousel-grid-item-width: calc(8.333% - var(--wp--style--block-gap, 1rem) * 11 / 12); 311 width: calc(8.333% - var(--wp--style--block-gap, 1rem) * 11 / 12); 312 min-width: calc(8.333% - var(--wp--style--block-gap, 1rem) * 11 / 12); 313 } 314 315 /* abcs-cols-13: 13 visible items */ 249 } 250 316 251 .is-layout-grid.abcs.abcs-cols-13>* { 317 252 --carousel-grid-item-width: calc(7.692% - var(--wp--style--block-gap, 1rem) * 12 / 13); 318 width: calc(7.692% - var(--wp--style--block-gap, 1rem) * 12 / 13); 319 min-width: calc(7.692% - var(--wp--style--block-gap, 1rem) * 12 / 13); 320 } 321 322 /* abcs-cols-14: 14 visible items */ 253 } 254 323 255 .is-layout-grid.abcs.abcs-cols-14>* { 324 256 --carousel-grid-item-width: calc(7.142% - var(--wp--style--block-gap, 1rem) * 13 / 14); 325 width: calc(7.142% - var(--wp--style--block-gap, 1rem) * 13 / 14); 326 min-width: calc(7.142% - var(--wp--style--block-gap, 1rem) * 13 / 14); 327 } 328 329 /* abcs-cols-15: 15 visible items */ 257 } 258 330 259 .is-layout-grid.abcs.abcs-cols-15>* { 331 260 --carousel-grid-item-width: calc(6.666% - var(--wp--style--block-gap, 1rem) * 14 / 15); 332 width: calc(6.666% - var(--wp--style--block-gap, 1rem) * 14 / 15); 333 min-width: calc(6.666% - var(--wp--style--block-gap, 1rem) * 14 / 15); 334 } 335 336 /* abcs-cols-16: 16 visible items */ 261 } 262 337 263 .is-layout-grid.abcs.abcs-cols-16>* { 338 264 --carousel-grid-item-width: calc(6.25% - var(--wp--style--block-gap, 1rem) * 15 / 16); 339 width: calc(6.25% - var(--wp--style--block-gap, 1rem) * 15 / 16);340 min-width: calc(6.25% - var(--wp--style--block-gap, 1rem) * 15 / 16);341 265 } 342 266 343 267 /* abcs-min-width: use the minimum width defined in the grid */ 344 /* WordPress generates: grid-template-columns: repeat(auto-fill, minmax(XXXpx, 1fr)) */345 /* On doit extraire cette valeur et l'utiliser comme largeur fixe en mode Flexbox */346 268 .is-layout-grid.abcs.abcs-min-width>*, 347 269 .block-editor-block-list__layout .is-layout-grid.abcs.abcs-min-width>*, 348 .editor-styles-wrapper .is-layout-grid.abcs.abcs-min-width>* { 349 /* Largeur fixe avec adaptation sur mobile */ 350 flex: 0 0 auto !important; 351 /* Use min() to respect the fixed width unless the viewport is smaller */ 352 width: min(var(--carousel-min-width, 200px), 100%) !important; 353 min-width: min(var(--carousel-min-width, 200px), 100%) !important; 354 max-width: 100% !important; 355 } 356 357 /* Specific rules for Post Template (children are <li class="wp-block-post">) */ 270 .editor-styles-wrapper .is-layout-grid.abcs.abcs-min-width>*, 358 271 .wp-block-post-template.abcs.abcs-min-width .wp-block-post, 359 272 .block-editor-block-list__layout .wp-block-post-template.abcs.abcs-min-width .wp-block-post, 360 .editor-styles-wrapper .wp-block-post-template.abcs.abcs-min-width .wp-block-post { 361 flex: 0 0 auto !important; 362 width: min(var(--carousel-min-width, 200px), 100%) !important; 363 min-width: min(var(--carousel-min-width, 200px), 100%) !important; 364 max-width: 100% !important; 365 } 366 367 /* Specific rules for Group blocks (children can be <div> or other elements) */ 273 .editor-styles-wrapper .wp-block-post-template.abcs.abcs-min-width .wp-block-post, 368 274 .wp-block-group.abcs.abcs-min-width>*, 369 275 .block-editor-block-list__layout .wp-block-group.abcs.abcs-min-width>*, … … 379 285 ========================================================================== */ 380 286 381 /* En mode Auto (abcs-min-width), le snap se fait sur le centre */382 287 .abcs.abcs-min-width>*, 383 288 .block-editor-block-list__layout .abcs.abcs-min-width>*, 384 .editor-styles-wrapper .abcs.abcs-min-width>* { 385 scroll-snap-align: center !important; 386 } 387 388 /* ========================================================================== 389 SCROLL SNAP POUR POST TEMPLATE EN MODE LIST VIEW 390 ========================================================================== */ 391 392 /* Post Template en mode list view (sans is-layout-grid) : snap sur le centre */ 289 .editor-styles-wrapper .abcs.abcs-min-width>*, 393 290 .wp-block-post-template.abcs:not(.is-layout-grid) .wp-block-post, 394 291 .block-editor-block-list__layout .wp-block-post-template.abcs:not(.is-layout-grid) .wp-block-post, 395 .editor-styles-wrapper .wp-block-post-template.abcs:not(.is-layout-grid) .wp-block-post { 396 scroll-snap-align: center !important; 397 } 398 399 /* Specific rules for Post Template in Auto mode */ 292 .editor-styles-wrapper .wp-block-post-template.abcs:not(.is-layout-grid) .wp-block-post, 400 293 .wp-block-post-template.abcs.abcs-min-width .wp-block-post, 401 294 .block-editor-block-list__layout .wp-block-post-template.abcs.abcs-min-width .wp-block-post, 402 .editor-styles-wrapper .wp-block-post-template.abcs.abcs-min-width .wp-block-post { 403 scroll-snap-align: center !important; 404 } 405 406 /* Specific rules for Group in Auto mode */ 295 .editor-styles-wrapper .wp-block-post-template.abcs.abcs-min-width .wp-block-post, 407 296 .wp-block-group.abcs.abcs-min-width>*, 408 297 .block-editor-block-list__layout .wp-block-group.abcs.abcs-min-width>*, 409 .editor-styles-wrapper .wp-block-group.abcs.abcs-min-width>* { 410 scroll-snap-align: center !important; 411 } 412 413 /* Specific rules for Gallery in Auto mode */ 298 .editor-styles-wrapper .wp-block-group.abcs.abcs-min-width>*, 414 299 .wp-block-gallery.abcs.abcs-min-width>.wp-block-image, 415 300 .block-editor-block-list__layout .wp-block-gallery.abcs.abcs-min-width>.wp-block-image, … … 423 308 424 309 /* Fallback : indicateur de pagination pour navigateurs non compatibles */ 425 /* Objectif : signaler visuellement le carrousel sans boutons cliquables */426 310 :has(> .abcs)::after { 427 311 content: ''; … … 456 340 } 457 341 458 459 460 342 /* Sur les navigateurs compatibles, masquer les fallbacks et afficher les vrais boutons */ 461 343 @supports selector(::scroll-button(*)) { 462 463 /* Masquer les fallbacks visuels */464 344 :has(> .abcs)::after { 465 345 display: none; … … 474 354 line-height: 1em; 475 355 border-radius: 100rem; 476 477 /* Apparence */478 356 background-color: var(--carousel-button-bg); 479 357 border: 2px solid var(--carousel-button-bg); … … 481 359 opacity: 1; 482 360 visibility: visible; 483 484 /* Interaction */485 361 cursor: pointer; 486 362 text-align: center; 487 363 display: inline-block; 488 489 /* Effets */490 364 box-shadow: var(--carousel-shadow); 491 365 transition: all var(--carousel-transition-duration) var(--carousel-transition-easing); 492 493 /* Prepare for SVG */494 366 font-size: 0px; 495 367 overflow: hidden; 496 497 /* Ensure left and right are not set by default */498 368 left: auto; 499 369 right: auto; … … 513 383 } 514 384 515 /* Boutons - Prendre en compte le padding pour TOUS les carrousels */ 516 /* Simple approach: rely on padding variables already defined by JavaScript */ 517 /* Le calcul se fait dans le CSS avec calc() */ 385 /* Keep scroll buttons enabled when loop is active */ 386 .abcs[data-abcs-loop="true"]::scroll-button(*):disabled { 387 opacity: 1; 388 visibility: visible; 389 pointer-events: auto; 390 cursor: pointer; 391 } 392 518 393 .abcs::scroll-button(left) { 519 394 left: calc(var(--carousel-padding-left, 0px) + var(--carousel-button-offset, 0px)); 520 395 content: ''; 521 396 transform: translate(-50%, -50%); 522 /* Use the dynamically generated CSS variable with a fallback to the white SVG */523 397 background-image: var(--carousel-button-arrow-left, url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 320 512'%3E%3Cpath fill='white' d='M41.4 233.4c-12.5 12.5-12.5 32.8 0 45.3l160 160c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L109.3 256 246.6 118.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0l-160 160z'/%3E%3C/svg%3E")); 524 398 background-repeat: no-repeat; … … 532 406 content: ''; 533 407 transform: translate(50%, -50%); 534 /* Use the dynamically generated CSS variable with a fallback to the white SVG */535 408 background-image: var(--carousel-button-arrow-right, url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 320 512'%3E%3Cpath fill='white' d='M278.6 233.4c12.5 12.5 12.5 32.8 0 45.3l-160 160c-12.5 12.5-32.8 12.5-45.3 0s-12.5-32.8 0-45.3L210.7 256 73.4 118.6c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0l160 160z'/%3E%3C/svg%3E")); 536 409 background-repeat: no-repeat; … … 555 428 556 429 /* ========================================================================== 557 MARQUEURS DE SCROLL (CSS natif futur) - NE PAS MODIFIER430 MARQUEURS DE SCROLL (CSS natif futur) 558 431 ========================================================================== */ 559 432 560 433 @supports selector(::scroll-marker-group) { 561 562 /* Masquer les fallbacks visuels */563 434 :has(> .abcs)::after { 564 435 display: none; … … 568 439 position: absolute; 569 440 z-index: var(--carousel-z-index); 570 /* Positionner par rapport au contenu, en ignorant le padding-bottom */571 /* bottom: offset + padding-bottom pour compenser le padding du carousel */572 /* The variable --carousel-padding-bottom is set dynamically by PHP/JavaScript */573 441 bottom: calc(var(--carousel-marker-bottom-offset) + var(--carousel-padding-bottom, 1rem)); 574 442 left: var(--carousel-padding-left, 0); … … 601 469 } 602 470 471 603 472 .abcs>*::scroll-marker:hover { 604 473 opacity: 0.75; … … 615 484 ========================================================================== */ 616 485 617 /* 618 Rules derived from analysing WordPress CSS: 486 /* Rules derived from analysing WordPress CSS: 619 487 - Primary breakpoint: 600px (mobile/tablet) 620 488 - Secondary breakpoint: 782px (tablet/desktop) … … 623 491 624 492 /* ========================================================================== 625 POST TEMPLATE - MATCHES WORDPRESS BEHAVIOUR626 ========================================================================== */ 627 628 /* Mobile (default) - 1 column */629 /* Exclude min-width mode which has its own rules */ 630 .wp-block- post-template.is-layout-grid.abcs:not(.abcs-min-width) .wp-block-post{493 POST TEMPLATE, GALLERY, GROUP - RESPONSIVE COLUMNS 494 ========================================================================== */ 495 496 /* Mobile (default) - 1 column for Post Template and Group */ 497 .wp-block-post-template.is-layout-grid.abcs:not(.abcs-min-width) .wp-block-post, 498 .wp-block-group.is-layout-grid.abcs:not(.abcs-min-width)>* { 631 499 width: 100% !important; 632 500 min-width: 100% !important; 633 501 } 634 502 503 /* Mobile (default) - 2 columns for Gallery */ 504 .wp-block-gallery.abcs .wp-block-image { 505 width: calc(50% - var(--wp--style--block-gap, 1rem) / 2) !important; 506 min-width: calc(50% - var(--wp--style--block-gap, 1rem) / 2) !important; 507 } 508 635 509 /* From 600px onwards - behaviour determined by column count */ 510 /* Generic rule for all block types with column classes */ 636 511 @media (min-width: 600px) { 637 512 638 /* 2columns */513 /* Post Template columns */ 639 514 .wp-block-post-template.is-layout-grid.abcs.abcs-cols-2 .wp-block-post { 640 width: calc(50% - var(--wp--style--block-gap, 1.25em) / 2) !important; 641 min-width: calc(50% - var(--wp--style--block-gap, 1.25em) / 2) !important; 642 } 643 644 /* 3 columns */ 515 --carousel-grid-item-width: calc(50% - var(--wp--style--block-gap, 1.25em) / 2); 516 } 517 645 518 .wp-block-post-template.is-layout-grid.abcs.abcs-cols-3 .wp-block-post { 646 width: calc(33.33333% - var(--wp--style--block-gap, 1.25em) * 2 / 3) !important; 647 min-width: calc(33.33333% - var(--wp--style--block-gap, 1.25em) * 2 / 3) !important; 648 } 649 650 /* 4 columns */ 519 --carousel-grid-item-width: calc(33.33333% - var(--wp--style--block-gap, 1.25em) * 2 / 3); 520 } 521 651 522 .wp-block-post-template.is-layout-grid.abcs.abcs-cols-4 .wp-block-post { 652 width: calc(25% - var(--wp--style--block-gap, 1.25em) * 3 / 4) !important; 653 min-width: calc(25% - var(--wp--style--block-gap, 1.25em) * 3 / 4) !important; 654 } 655 656 /* 5 columns */ 523 --carousel-grid-item-width: calc(25% - var(--wp--style--block-gap, 1.25em) * 3 / 4); 524 } 525 657 526 .wp-block-post-template.is-layout-grid.abcs.abcs-cols-5 .wp-block-post { 658 width: calc(20% - var(--wp--style--block-gap, 1.25em) * 4 / 5) !important; 659 min-width: calc(20% - var(--wp--style--block-gap, 1.25em) * 4 / 5) !important; 660 } 661 662 /* 6 columns */ 527 --carousel-grid-item-width: calc(20% - var(--wp--style--block-gap, 1.25em) * 4 / 5); 528 } 529 663 530 .wp-block-post-template.is-layout-grid.abcs.abcs-cols-6 .wp-block-post { 664 width: calc(16.66667% - var(--wp--style--block-gap, 1.25em) * 5 / 6) !important; 665 min-width: calc(16.66667% - var(--wp--style--block-gap, 1.25em) * 5 / 6) !important; 666 } 667 668 /* 7 columns */ 531 --carousel-grid-item-width: calc(16.66667% - var(--wp--style--block-gap, 1.25em) * 5 / 6); 532 } 533 669 534 .wp-block-post-template.is-layout-grid.abcs.abcs-cols-7 .wp-block-post { 670 width: calc(14.285% - var(--wp--style--block-gap, 1.25em) * 6 / 7) !important; 671 min-width: calc(14.285% - var(--wp--style--block-gap, 1.25em) * 6 / 7) !important; 672 } 673 674 /* 8 columns */ 535 --carousel-grid-item-width: calc(14.285% - var(--wp--style--block-gap, 1.25em) * 6 / 7); 536 } 537 675 538 .wp-block-post-template.is-layout-grid.abcs.abcs-cols-8 .wp-block-post { 676 width: calc(12.5% - var(--wp--style--block-gap, 1.25em) * 7 / 8) !important; 677 min-width: calc(12.5% - var(--wp--style--block-gap, 1.25em) * 7 / 8) !important; 678 } 679 680 /* 9 columns */ 539 --carousel-grid-item-width: calc(12.5% - var(--wp--style--block-gap, 1.25em) * 7 / 8); 540 } 541 681 542 .wp-block-post-template.is-layout-grid.abcs.abcs-cols-9 .wp-block-post { 682 width: calc(11.111% - var(--wp--style--block-gap, 1.25em) * 8 / 9) !important; 683 min-width: calc(11.111% - var(--wp--style--block-gap, 1.25em) * 8 / 9) !important; 684 } 685 686 /* 10 columns */ 543 --carousel-grid-item-width: calc(11.111% - var(--wp--style--block-gap, 1.25em) * 8 / 9); 544 } 545 687 546 .wp-block-post-template.is-layout-grid.abcs.abcs-cols-10 .wp-block-post { 688 width: calc(10% - var(--wp--style--block-gap, 1.25em) * 9 / 10) !important; 689 min-width: calc(10% - var(--wp--style--block-gap, 1.25em) * 9 / 10) !important; 690 } 691 692 /* 11 columns */ 547 --carousel-grid-item-width: calc(10% - var(--wp--style--block-gap, 1.25em) * 9 / 10); 548 } 549 693 550 .wp-block-post-template.is-layout-grid.abcs.abcs-cols-11 .wp-block-post { 694 width: calc(9.090% - var(--wp--style--block-gap, 1.25em) * 10 / 11) !important; 695 min-width: calc(9.090% - var(--wp--style--block-gap, 1.25em) * 10 / 11) !important; 696 } 697 698 /* 12 columns */ 551 --carousel-grid-item-width: calc(9.090% - var(--wp--style--block-gap, 1.25em) * 10 / 11); 552 } 553 699 554 .wp-block-post-template.is-layout-grid.abcs.abcs-cols-12 .wp-block-post { 700 width: calc(8.333% - var(--wp--style--block-gap, 1.25em) * 11 / 12) !important; 701 min-width: calc(8.333% - var(--wp--style--block-gap, 1.25em) * 11 / 12) !important; 702 } 703 704 /* 13 columns */ 555 --carousel-grid-item-width: calc(8.333% - var(--wp--style--block-gap, 1.25em) * 11 / 12); 556 } 557 705 558 .wp-block-post-template.is-layout-grid.abcs.abcs-cols-13 .wp-block-post { 706 width: calc(7.692% - var(--wp--style--block-gap, 1.25em) * 12 / 13) !important; 707 min-width: calc(7.692% - var(--wp--style--block-gap, 1.25em) * 12 / 13) !important; 708 } 709 710 /* 14 columns */ 559 --carousel-grid-item-width: calc(7.692% - var(--wp--style--block-gap, 1.25em) * 12 / 13); 560 } 561 711 562 .wp-block-post-template.is-layout-grid.abcs.abcs-cols-14 .wp-block-post { 712 width: calc(7.142% - var(--wp--style--block-gap, 1.25em) * 13 / 14) !important; 713 min-width: calc(7.142% - var(--wp--style--block-gap, 1.25em) * 13 / 14) !important; 714 } 715 716 /* 15 columns */ 563 --carousel-grid-item-width: calc(7.142% - var(--wp--style--block-gap, 1.25em) * 13 / 14); 564 } 565 717 566 .wp-block-post-template.is-layout-grid.abcs.abcs-cols-15 .wp-block-post { 718 width: calc(6.666% - var(--wp--style--block-gap, 1.25em) * 14 / 15) !important; 719 min-width: calc(6.666% - var(--wp--style--block-gap, 1.25em) * 14 / 15) !important; 720 } 721 722 /* 16 columns */ 567 --carousel-grid-item-width: calc(6.666% - var(--wp--style--block-gap, 1.25em) * 14 / 15); 568 } 569 723 570 .wp-block-post-template.is-layout-grid.abcs.abcs-cols-16 .wp-block-post { 724 width: calc(6.25% - var(--wp--style--block-gap, 1.25em) * 15 / 16) !important; 725 min-width: calc(6.25% - var(--wp--style--block-gap, 1.25em) * 15 / 16) !important; 726 } 727 } 728 729 /* Comportement responsive natif WordPress pour tablette */ 730 /* Between 600px and 1200px, Post Template always shows 2 columns */ 571 --carousel-grid-item-width: calc(6.25% - var(--wp--style--block-gap, 1.25em) * 15 / 16); 572 } 573 574 /* Apply width and min-width using the variable */ 575 .wp-block-post-template.is-layout-grid.abcs[class*="abcs-cols-"] .wp-block-post { 576 width: var(--carousel-grid-item-width) !important; 577 min-width: var(--carousel-grid-item-width) !important; 578 } 579 580 /* Gallery columns */ 581 .wp-block-gallery.abcs.abcs-cols-1 .wp-block-image { 582 --carousel-grid-item-width: 100%; 583 } 584 585 .wp-block-gallery.abcs.abcs-cols-2 .wp-block-image { 586 --carousel-grid-item-width: calc(50% - var(--wp--style--block-gap, 1rem) / 2); 587 } 588 589 .wp-block-gallery.abcs.abcs-cols-3 .wp-block-image { 590 --carousel-grid-item-width: calc(33.33333% - var(--wp--style--block-gap, 1rem) * 2 / 3); 591 } 592 593 .wp-block-gallery.abcs.abcs-cols-4 .wp-block-image { 594 --carousel-grid-item-width: calc(25% - var(--wp--style--block-gap, 1rem) * 3 / 4); 595 } 596 597 .wp-block-gallery.abcs.abcs-cols-5 .wp-block-image { 598 --carousel-grid-item-width: calc(20% - var(--wp--style--block-gap, 1rem) * 4 / 5); 599 } 600 601 .wp-block-gallery.abcs.abcs-cols-6 .wp-block-image { 602 --carousel-grid-item-width: calc(16.66667% - var(--wp--style--block-gap, 1rem) * 5 / 6); 603 } 604 605 .wp-block-gallery.abcs.abcs-cols-7 .wp-block-image { 606 --carousel-grid-item-width: calc(14.285% - var(--wp--style--block-gap, 1rem) * 6 / 7); 607 } 608 609 .wp-block-gallery.abcs.abcs-cols-8 .wp-block-image { 610 --carousel-grid-item-width: calc(12.5% - var(--wp--style--block-gap, 1rem) * 7 / 8); 611 } 612 613 .wp-block-gallery.abcs[class*="abcs-cols-"] .wp-block-image { 614 width: var(--carousel-grid-item-width) !important; 615 min-width: var(--carousel-grid-item-width) !important; 616 } 617 618 /* Group columns */ 619 .wp-block-group.is-layout-grid.abcs.abcs-cols-2>* { 620 --carousel-grid-item-width: calc(50% - var(--wp--style--block-gap, 1rem) / 2); 621 } 622 623 .wp-block-group.is-layout-grid.abcs.abcs-cols-3>* { 624 --carousel-grid-item-width: calc(33.33333% - var(--wp--style--block-gap, 1rem) * 2 / 3); 625 } 626 627 .wp-block-group.is-layout-grid.abcs.abcs-cols-4>* { 628 --carousel-grid-item-width: calc(25% - var(--wp--style--block-gap, 1rem) * 3 / 4); 629 } 630 631 .wp-block-group.is-layout-grid.abcs.abcs-cols-5>* { 632 --carousel-grid-item-width: calc(20% - var(--wp--style--block-gap, 1rem) * 4 / 5); 633 } 634 635 .wp-block-group.is-layout-grid.abcs.abcs-cols-6>* { 636 --carousel-grid-item-width: calc(16.66667% - var(--wp--style--block-gap, 1rem) * 5 / 6); 637 } 638 639 .wp-block-group.is-layout-grid.abcs.abcs-cols-7>* { 640 --carousel-grid-item-width: calc(14.285% - var(--wp--style--block-gap, 1rem) * 6 / 7); 641 } 642 643 .wp-block-group.is-layout-grid.abcs.abcs-cols-8>* { 644 --carousel-grid-item-width: calc(12.5% - var(--wp--style--block-gap, 1rem) * 7 / 8); 645 } 646 647 .wp-block-group.is-layout-grid.abcs.abcs-cols-9>* { 648 --carousel-grid-item-width: calc(11.111% - var(--wp--style--block-gap, 1rem) * 8 / 9); 649 } 650 651 .wp-block-group.is-layout-grid.abcs.abcs-cols-10>* { 652 --carousel-grid-item-width: calc(10% - var(--wp--style--block-gap, 1rem) * 9 / 10); 653 } 654 655 .wp-block-group.is-layout-grid.abcs.abcs-cols-11>* { 656 --carousel-grid-item-width: calc(9.090% - var(--wp--style--block-gap, 1rem) * 10 / 11); 657 } 658 659 .wp-block-group.is-layout-grid.abcs.abcs-cols-12>* { 660 --carousel-grid-item-width: calc(8.333% - var(--wp--style--block-gap, 1rem) * 11 / 12); 661 } 662 663 .wp-block-group.is-layout-grid.abcs.abcs-cols-13>* { 664 --carousel-grid-item-width: calc(7.692% - var(--wp--style--block-gap, 1rem) * 12 / 13); 665 } 666 667 .wp-block-group.is-layout-grid.abcs.abcs-cols-14>* { 668 --carousel-grid-item-width: calc(7.142% - var(--wp--style--block-gap, 1rem) * 13 / 14); 669 } 670 671 .wp-block-group.is-layout-grid.abcs.abcs-cols-15>* { 672 --carousel-grid-item-width: calc(6.666% - var(--wp--style--block-gap, 1rem) * 14 / 15); 673 } 674 675 .wp-block-group.is-layout-grid.abcs.abcs-cols-16>* { 676 --carousel-grid-item-width: calc(6.25% - var(--wp--style--block-gap, 1rem) * 15 / 16); 677 } 678 679 .wp-block-group.is-layout-grid.abcs[class*="abcs-cols-"]>* { 680 width: var(--carousel-grid-item-width) !important; 681 min-width: var(--carousel-grid-item-width) !important; 682 } 683 } 684 685 /* Tablet behavior: force 2 columns for Post Template and Group (3+ columns) */ 731 686 @media (min-width: 600px) and (max-width: 1200px) { 732 687 733 /* 3 columns and up: force 2 columns on tablets */734 688 .wp-block-post-template.is-layout-grid.abcs.abcs-cols-3 .wp-block-post, 735 689 .wp-block-post-template.is-layout-grid.abcs.abcs-cols-4 .wp-block-post, … … 749 703 min-width: calc(50% - var(--wp--style--block-gap, 1.25em) / 2) !important; 750 704 } 751 } 752 753 /* ========================================================================== 754 GALLERY - MATCHES WORDPRESS BEHAVIOUR 755 ========================================================================== */ 756 757 /* Mobile (default) - 2 columns for galleries */ 758 .wp-block-gallery.abcs .wp-block-image { 759 width: calc(50% - var(--wp--style--block-gap, 1rem) / 2) !important; 760 min-width: calc(50% - var(--wp--style--block-gap, 1rem) / 2) !important; 761 } 762 763 /* From 600px onwards - behaviour determined by column count */ 764 @media (min-width: 600px) { 765 766 /* 1 column */ 767 .wp-block-gallery.abcs.abcs-cols-1 .wp-block-image { 768 width: 100% !important; 769 min-width: 100% !important; 770 } 771 772 /* 2 columns */ 773 .wp-block-gallery.abcs.abcs-cols-2 .wp-block-image { 774 width: calc(50% - var(--wp--style--block-gap, 1rem) / 2) !important; 775 min-width: calc(50% - var(--wp--style--block-gap, 1rem) / 2) !important; 776 } 777 778 /* 3 columns */ 779 .wp-block-gallery.abcs.abcs-cols-3 .wp-block-image { 780 width: calc(33.33333% - var(--wp--style--block-gap, 1rem) * 2 / 3) !important; 781 min-width: calc(33.33333% - var(--wp--style--block-gap, 1rem) * 2 / 3) !important; 782 } 783 784 /* 4 columns */ 785 .wp-block-gallery.abcs.abcs-cols-4 .wp-block-image { 786 width: calc(25% - var(--wp--style--block-gap, 1rem) * 3 / 4) !important; 787 min-width: calc(25% - var(--wp--style--block-gap, 1rem) * 3 / 4) !important; 788 } 789 790 /* 5 columns */ 791 .wp-block-gallery.abcs.abcs-cols-5 .wp-block-image { 792 width: calc(20% - var(--wp--style--block-gap, 1rem) * 4 / 5) !important; 793 min-width: calc(20% - var(--wp--style--block-gap, 1rem) * 4 / 5) !important; 794 } 795 796 /* 6 columns */ 797 .wp-block-gallery.abcs.abcs-cols-6 .wp-block-image { 798 width: calc(16.66667% - var(--wp--style--block-gap, 1rem) * 5 / 6) !important; 799 min-width: calc(16.66667% - var(--wp--style--block-gap, 1rem) * 5 / 6) !important; 800 } 801 802 /* 7 columns */ 803 .wp-block-gallery.abcs.abcs-cols-7 .wp-block-image { 804 width: calc(14.285% - var(--wp--style--block-gap, 1rem) * 6 / 7) !important; 805 min-width: calc(14.285% - var(--wp--style--block-gap, 1rem) * 6 / 7) !important; 806 } 807 808 /* 8 columns */ 809 .wp-block-gallery.abcs.abcs-cols-8 .wp-block-image { 810 width: calc(12.5% - var(--wp--style--block-gap, 1rem) * 7 / 8) !important; 811 min-width: calc(12.5% - var(--wp--style--block-gap, 1rem) * 7 / 8) !important; 812 } 813 } 814 815 /* ========================================================================== 816 GROUP - MATCHES WORDPRESS BEHAVIOUR 817 ========================================================================== */ 818 819 /* Mobile (default) - 1 column */ 820 /* Exclude min-width mode which has its own rules */ 821 .wp-block-group.is-layout-grid.abcs:not(.abcs-min-width)>* { 822 width: 100% !important; 823 min-width: 100% !important; 824 } 825 826 /* From 600px onwards - behaviour determined by column count */ 827 @media (min-width: 600px) { 828 829 /* 2 columns */ 830 .wp-block-group.is-layout-grid.abcs.abcs-cols-2>* { 831 width: calc(50% - var(--wp--style--block-gap, 1rem) / 2) !important; 832 min-width: calc(50% - var(--wp--style--block-gap, 1rem) / 2) !important; 833 } 834 835 /* 3 columns */ 836 .wp-block-group.is-layout-grid.abcs.abcs-cols-3>* { 837 width: calc(33.33333% - var(--wp--style--block-gap, 1rem) * 2 / 3) !important; 838 min-width: calc(33.33333% - var(--wp--style--block-gap, 1rem) * 2 / 3) !important; 839 } 840 841 /* 4 columns */ 842 .wp-block-group.is-layout-grid.abcs.abcs-cols-4>* { 843 width: calc(25% - var(--wp--style--block-gap, 1rem) * 3 / 4) !important; 844 min-width: calc(25% - var(--wp--style--block-gap, 1rem) * 3 / 4) !important; 845 } 846 847 /* 5 columns */ 848 .wp-block-group.is-layout-grid.abcs.abcs-cols-5>* { 849 width: calc(20% - var(--wp--style--block-gap, 1rem) * 4 / 5) !important; 850 min-width: calc(20% - var(--wp--style--block-gap, 1rem) * 4 / 5) !important; 851 } 852 853 /* 6 columns */ 854 .wp-block-group.is-layout-grid.abcs.abcs-cols-6>* { 855 width: calc(16.66667% - var(--wp--style--block-gap, 1rem) * 5 / 6) !important; 856 min-width: calc(16.66667% - var(--wp--style--block-gap, 1rem) * 5 / 6) !important; 857 } 858 859 /* 7 columns */ 860 .wp-block-group.is-layout-grid.abcs.abcs-cols-7>* { 861 width: calc(14.285% - var(--wp--style--block-gap, 1rem) * 6 / 7) !important; 862 min-width: calc(14.285% - var(--wp--style--block-gap, 1rem) * 6 / 7) !important; 863 } 864 865 /* 8 columns */ 866 .wp-block-group.is-layout-grid.abcs.abcs-cols-8>* { 867 width: calc(12.5% - var(--wp--style--block-gap, 1rem) * 7 / 8) !important; 868 min-width: calc(12.5% - var(--wp--style--block-gap, 1rem) * 7 / 8) !important; 869 } 870 871 /* 9 columns */ 872 .wp-block-group.is-layout-grid.abcs.abcs-cols-9>* { 873 width: calc(11.111% - var(--wp--style--block-gap, 1rem) * 8 / 9) !important; 874 min-width: calc(11.111% - var(--wp--style--block-gap, 1rem) * 8 / 9) !important; 875 } 876 877 /* 10 columns */ 878 .wp-block-group.is-layout-grid.abcs.abcs-cols-10>* { 879 width: calc(10% - var(--wp--style--block-gap, 1rem) * 9 / 10) !important; 880 min-width: calc(10% - var(--wp--style--block-gap, 1rem) * 9 / 10) !important; 881 } 882 883 /* 11 columns */ 884 .wp-block-group.is-layout-grid.abcs.abcs-cols-11>* { 885 width: calc(9.090% - var(--wp--style--block-gap, 1rem) * 10 / 11) !important; 886 min-width: calc(9.090% - var(--wp--style--block-gap, 1rem) * 10 / 11) !important; 887 } 888 889 /* 12 columns */ 890 .wp-block-group.is-layout-grid.abcs.abcs-cols-12>* { 891 width: calc(8.333% - var(--wp--style--block-gap, 1rem) * 11 / 12) !important; 892 min-width: calc(8.333% - var(--wp--style--block-gap, 1rem) * 11 / 12) !important; 893 } 894 895 /* 13 columns */ 896 .wp-block-group.is-layout-grid.abcs.abcs-cols-13>* { 897 width: calc(7.692% - var(--wp--style--block-gap, 1rem) * 12 / 13) !important; 898 min-width: calc(7.692% - var(--wp--style--block-gap, 1rem) * 12 / 13) !important; 899 } 900 901 /* 14 columns */ 902 .wp-block-group.is-layout-grid.abcs.abcs-cols-14>* { 903 width: calc(7.142% - var(--wp--style--block-gap, 1rem) * 13 / 14) !important; 904 min-width: calc(7.142% - var(--wp--style--block-gap, 1rem) * 13 / 14) !important; 905 } 906 907 /* 15 columns */ 908 .wp-block-group.is-layout-grid.abcs.abcs-cols-15>* { 909 width: calc(6.666% - var(--wp--style--block-gap, 1rem) * 14 / 15) !important; 910 min-width: calc(6.666% - var(--wp--style--block-gap, 1rem) * 14 / 15) !important; 911 } 912 913 /* 16 columns */ 914 .wp-block-group.is-layout-grid.abcs.abcs-cols-16>* { 915 width: calc(6.25% - var(--wp--style--block-gap, 1rem) * 15 / 16) !important; 916 min-width: calc(6.25% - var(--wp--style--block-gap, 1rem) * 15 / 16) !important; 917 } 918 } 919 920 /* Comportement responsive natif WordPress pour tablette - GROUP */ 921 /* Between 600px and 1200px, Group grids always display 2 columns */ 922 @media (min-width: 600px) and (max-width: 1200px) { 923 924 /* 3 columns and up: force 2 columns on tablets */ 705 925 706 .wp-block-group.is-layout-grid.abcs.abcs-cols-3>*, 926 707 .wp-block-group.is-layout-grid.abcs.abcs-cols-4>*, … … 946 727 ========================================================================== */ 947 728 948 /* Button and marker adjustments based on screen size */949 729 @media (max-width: 782px) { 950 730 :root { … … 963 743 } 964 744 965 /* Reduce the gap on mobile */966 745 .abcs { 967 746 gap: var(--wp--style--block-gap, 0.5rem); 968 747 padding: 0.75rem 0px; 969 748 } 970 }971 972 @media (max-width: 480px) {973 :root {974 --carousel-button-size: 2rem;975 --carousel-button-offset: 0.25rem;976 --carousel-marker-size: 0.4rem;977 --carousel-marker-gap: 0.35rem;978 --carousel-marker-bottom-offset: -1.5rem;979 }980 981 /* Reduce the gap even further */982 .abcs {983 gap: var(--wp--style--block-gap, 0.25rem);984 padding: 1rem 0px;985 }986 }987 988 @media (max-width: 375px) {989 :root {990 --carousel-button-size: 1.75rem;991 --carousel-marker-size: 0.35rem;992 }993 }994 995 /* ==========================================================================996 GUTENBERG EDITOR STYLES997 ========================================================================== */998 999 /* Toggle Control */1000 .cfg-abcs-toggle-control {1001 margin-bottom: 1rem;1002 }1003 1004 .cfg-abcs-toggle-control .components-base-control__label {1005 font-weight: 600;1006 color: #1e1e1e;1007 }1008 1009 .cfg-abcs-toggle-control .components-base-control__help {1010 font-size: 12px;1011 color: #757575;1012 margin-top: 4px;1013 }1014 1015 .cfg-abcs-toggle-control .components-toggle-control__label[for*="carousel"] {1016 color: #007cba;1017 font-weight: 600;1018 }1019 1020 /* Panel Carousel */1021 .cfg-abcs-panel .components-panel__body-title {1022 font-weight: 600;1023 }1024 1025 .cfg-abcs-panel-first {1026 order: -1000 !important;1027 }1028 1029 .block-editor-block-inspector .cfg-abcs-panel-first {1030 order: -1000 !important;1031 margin-bottom: 16px;1032 }1033 1034 /* Fix editor click behaviour */1035 .block-editor-block-list__layout .abcs>*:not(:first-child),1036 .editor-styles-wrapper .abcs>*:not(:first-child) {1037 pointer-events: none;1038 }1039 1040 /* ==========================================================================1041 PERFORMANCE ET OPTIMISATIONS1042 ========================================================================== */1043 1044 /* Optimisation du rendu */1045 .abcs,1046 .block-editor-block-list__layout .abcs,1047 .editor-styles-wrapper .abcs {1048 will-change: scroll-position;1049 contain: layout style;1050 }1051 1052 /* Optimise rendered elements */1053 .abcs>*,1054 .block-editor-block-list__layout .abcs>*,1055 .editor-styles-wrapper .abcs>* {1056 will-change: transform;1057 }1058 1059 /* ==========================================================================1060 ACCESSIBILITY1061 ========================================================================== */1062 1063 /* Honour reduced motion preferences */1064 @media (prefers-reduced-motion: reduce) {1065 1066 .abcs,1067 .block-editor-block-list__layout .abcs,1068 .editor-styles-wrapper .abcs {1069 scroll-behavior: auto;1070 }1071 1072 .abcs::scroll-button(*),1073 *:has(>.abcs)::after {1074 transition: none;1075 }1076 1077 .abcs>*::scroll-marker {1078 transition: none;1079 }1080 }1081 1082 /* High contrast */1083 @media (prefers-contrast: high) {1084 1085 .abcs::scroll-button(*),1086 *:has(>.abcs)::after {1087 border-width: 3px;1088 }1089 1090 .abcs>*::scroll-marker {1091 border: 2px solid currentColor;1092 }1093 }1094 1095 @media (max-width: 600px) {1096 749 1097 750 .abcs.abcs-min-width>*, … … 1112 765 } 1113 766 } 767 768 @media (max-width: 480px) { 769 :root { 770 --carousel-button-size: 2rem; 771 --carousel-button-offset: 0.25rem; 772 --carousel-marker-size: 0.4rem; 773 --carousel-marker-gap: 0.35rem; 774 --carousel-marker-bottom-offset: -1.5rem; 775 } 776 777 .abcs { 778 gap: var(--wp--style--block-gap, 0.25rem); 779 padding: 1rem 0px; 780 } 781 } 782 783 @media (max-width: 375px) { 784 :root { 785 --carousel-button-size: 1.75rem; 786 --carousel-marker-size: 0.35rem; 787 } 788 } 789 790 /* ========================================================================== 791 GUTENBERG EDITOR STYLES 792 ========================================================================== */ 793 794 .cfg-abcs-toggle-control { 795 margin-bottom: 1rem; 796 } 797 798 .cfg-abcs-toggle-control .components-base-control__label { 799 font-weight: 600; 800 color: #1e1e1e; 801 } 802 803 .cfg-abcs-toggle-control .components-base-control__help { 804 font-size: 12px; 805 color: #757575; 806 margin-top: 4px; 807 } 808 809 .cfg-abcs-toggle-control .components-toggle-control__label[for*="carousel"] { 810 color: #007cba; 811 font-weight: 600; 812 } 813 814 .cfg-abcs-panel .components-panel__body-title { 815 font-weight: 600; 816 } 817 818 .cfg-abcs-panel-first { 819 order: -1000 !important; 820 } 821 822 .block-editor-block-inspector .cfg-abcs-panel-first { 823 order: -1000 !important; 824 margin-bottom: 16px; 825 } 826 827 .block-editor-block-list__layout .abcs>*:not(:first-child), 828 .editor-styles-wrapper .abcs>*:not(:first-child) { 829 pointer-events: none; 830 } 831 832 /* ========================================================================== 833 PERFORMANCE ET OPTIMISATIONS 834 ========================================================================== */ 835 836 .abcs, 837 .block-editor-block-list__layout .abcs, 838 .editor-styles-wrapper .abcs { 839 will-change: scroll-position; 840 contain: layout style; 841 } 842 843 .abcs>*, 844 .block-editor-block-list__layout .abcs>*, 845 .editor-styles-wrapper .abcs>* { 846 will-change: transform; 847 } 848 849 /* ========================================================================== 850 ACCESSIBILITY 851 ========================================================================== */ 852 853 @media (prefers-reduced-motion: reduce) { 854 855 .abcs, 856 .block-editor-block-list__layout .abcs, 857 .editor-styles-wrapper .abcs { 858 scroll-behavior: auto; 859 } 860 861 .abcs::scroll-button(*), 862 *:has(>.abcs)::after { 863 transition: none; 864 } 865 866 .abcs>*::scroll-marker { 867 transition: none; 868 } 869 } 870 871 @media (prefers-contrast: high) { 872 873 .abcs::scroll-button(*), 874 *:has(>.abcs)::after { 875 border-width: 3px; 876 } 877 878 .abcs>*::scroll-marker { 879 border: 2px solid currentColor; 880 } 881 } -
native-blocks-carousel/trunk/assets/js/carousel-editor.js
r3395721 r3423082 8 8 const { Fragment, useEffect, useMemo, createElement, RawHTML } = wp.element; 9 9 const { InspectorControls, BlockListBlock } = wp.blockEditor; 10 const { PanelBody, ToggleControl, Tooltip, __experimentalToggleGroupControl: ToggleGroupControl, __experimentalToggleGroupControlOption: ToggleGroupControlOption, __experimentalToggleGroupControlOptionIcon: ToggleGroupControlOptionIcon } = wp.components;10 const { PanelBody, ToggleControl, RangeControl, Tooltip, __experimentalToggleGroupControl: ToggleGroupControl, __experimentalToggleGroupControlOption: ToggleGroupControlOption, __experimentalToggleGroupControlOptionIcon: ToggleGroupControlOptionIcon } = wp.components; 11 11 const { __ } = wp.i18n; 12 12 … … 198 198 default: true, 199 199 }, 200 carouselLoop: { 201 type: 'boolean', 202 default: false, 203 }, 204 carouselAutoplay: { 205 type: 'boolean', 206 default: false, 207 }, 208 carouselAutoplayDelay: { 209 type: 'number', 210 default: 3000, 211 }, 200 212 }, 201 213 }; … … 219 231 carouselShowArrows = true, 220 232 carouselShowMarkers = true, 233 carouselLoop = false, 234 carouselAutoplay = false, 235 carouselAutoplayDelay = 3000, 221 236 } = attributes; 222 237 const normalizedArrowStyle = wp.element.useMemo( … … 738 753 }) 739 754 : null, 755 carouselEnabled 756 ? createElement(ToggleControl, { 757 label: __('Enable loop', 'native-blocks-carousel'), 758 checked: carouselLoop, 759 __nextHasNoMarginBottom: true, 760 onChange: (value) => { 761 setAttributes({ carouselLoop: value }); 762 }, 763 help: carouselLoop 764 ? __( 765 'The carousel will loop infinitely, returning to the first slide after the last.', 766 'native-blocks-carousel' 767 ) 768 : __( 769 'Loop is disabled. The carousel stops at the end.', 770 'native-blocks-carousel' 771 ), 772 }) 773 : null, 774 carouselEnabled 775 ? createElement(ToggleControl, { 776 label: __('Enable autoplay', 'native-blocks-carousel'), 777 checked: carouselAutoplay, 778 __nextHasNoMarginBottom: true, 779 onChange: (value) => { 780 setAttributes({ carouselAutoplay: value }); 781 }, 782 help: carouselAutoplay 783 ? __( 784 'The carousel will automatically scroll through slides.', 785 'native-blocks-carousel' 786 ) 787 : __( 788 'Autoplay is disabled. Users must navigate manually.', 789 'native-blocks-carousel' 790 ), 791 }) 792 : null, 793 carouselEnabled && carouselAutoplay 794 ? createElement(RangeControl, { 795 label: __('Autoplay delay (ms)', 'native-blocks-carousel'), 796 value: carouselAutoplayDelay, 797 onChange: (value) => { 798 setAttributes({ carouselAutoplayDelay: value || 3000 }); 799 }, 800 min: 1000, 801 max: 10000, 802 step: 100, 803 __nextHasNoMarginBottom: true, 804 help: __( 805 'Time in milliseconds between each slide transition.', 806 'native-blocks-carousel' 807 ), 808 }) 809 : null, 740 810 carouselEnabled && carouselShowArrows 741 811 ? createElement( … … 917 987 }, 918 988 'data-abcs-arrow-style': attributes.carouselArrowStyle || DEFAULT_ARROW_STYLE, 989 'data-abcs-loop': attributes.carouselLoop ? 'true' : 'false', 990 'data-abcs-autoplay': attributes.carouselAutoplay ? 'true' : 'false', 991 'data-abcs-autoplay-delay': attributes.carouselAutoplayDelay || 3000, 919 992 }, 920 993 }; … … 1041 1114 1042 1115 /** 1116 * Injects CSS variables into a <style> tag instead of inline style attribute. 1117 * This is a better practice than using element.style.setProperty() on documentElement. 1118 * 1119 * @param {Object} variables - Object with CSS variable names as keys and values as values 1120 * @param {Document|HTMLElement} docContext - Document context (for iframe support) 1121 */ 1122 function injectCssVariablesInStyleTag(variables, docContext) { 1123 const doc = docContext && docContext.ownerDocument ? docContext.ownerDocument : (docContext || document); 1124 const head = doc.head || doc.getElementsByTagName('head')[0]; 1125 1126 if (!head) return; 1127 1128 // Find or create the style tag for carousel variables 1129 let styleTag = doc.getElementById('carousel-dynamic-variables'); 1130 1131 if (!styleTag) { 1132 styleTag = doc.createElement('style'); 1133 styleTag.id = 'carousel-dynamic-variables'; 1134 styleTag.type = 'text/css'; 1135 head.appendChild(styleTag); 1136 } 1137 1138 // Build CSS with all variables 1139 let css = ':root {'; 1140 for (const [key, value] of Object.entries(variables)) { 1141 if (value !== null && value !== undefined && value !== '') { 1142 css += `\n ${key}: ${value};`; 1143 } 1144 } 1145 css += '\n}'; 1146 1147 styleTag.textContent = css; 1148 } 1149 1150 /** 1151 * Checks if a color value matches WordPress core default button colors. 1152 * This helps avoid applying core defaults when theme has no custom button styles. 1153 * 1154 * @param {string} color - Color value to check (can be rgb, rgba, hex, etc.) 1155 * @returns {boolean} True if the color matches core defaults 1156 */ 1157 function isWordPressCoreDefaultColor(color) { 1158 if (!color) return false; 1159 1160 // WordPress core default button colors 1161 // Background: rgb(50, 55, 60) = #32373c 1162 // Text: rgb(255, 255, 255) = #fff 1163 const coreDefaults = [ 1164 'rgb(50, 55, 60)', 1165 'rgba(50, 55, 60, 1)', 1166 '#32373c', 1167 'rgb(255, 255, 255)', 1168 'rgba(255, 255, 255, 1)', 1169 '#ffffff', 1170 '#fff' 1171 ]; 1172 1173 // Normalize the color for comparison 1174 const normalizedColor = color.toLowerCase().trim(); 1175 1176 return coreDefaults.some(defaultColor => { 1177 const normalizedDefault = defaultColor.toLowerCase().trim(); 1178 return normalizedColor === normalizedDefault || normalizedColor.includes(normalizedDefault); 1179 }); 1180 } 1181 1182 /** 1043 1183 * Dynamically updates button colors based on computed styles. 1044 1184 * Mirrors WordPress behaviour that reads computed styles directly. 1185 * Only applies colors if they come from theme customizations, not WordPress core defaults. 1045 1186 */ 1046 1187 function updateButtonColorsFromTheme() { 1047 1188 const root = document.documentElement; 1048 1189 let buttonBg = ''; 1049 let buttonColor = ' #fff';1190 let buttonColor = ''; 1050 1191 1051 1192 // Main method: read from a real WordPress button inside the editor … … 1074 1215 const computedColor = buttonComputedStyle.color; 1075 1216 1076 // Use computed colors when they are valid 1077 if (computedBg && computedBg !== 'rgba(0, 0, 0, 0)' && computedBg !== 'transparent' ) {1217 // Use computed colors when they are valid AND not WordPress core defaults 1218 if (computedBg && computedBg !== 'rgba(0, 0, 0, 0)' && computedBg !== 'transparent' && !isWordPressCoreDefaultColor(computedBg)) { 1078 1219 buttonBg = computedBg; 1079 1220 } 1080 1221 1081 if (computedColor && computedColor !== 'rgba(0, 0, 0, 0)' ) {1222 if (computedColor && computedColor !== 'rgba(0, 0, 0, 0)' && !isWordPressCoreDefaultColor(computedColor)) { 1082 1223 buttonColor = computedColor; 1083 1224 } … … 1095 1236 const computedColor = buttonComputedStyle.color; 1096 1237 1097 if (computedBg && computedBg !== 'rgba(0, 0, 0, 0)' && computedBg !== 'transparent') { 1238 // Use computed colors when they are valid AND not WordPress core defaults 1239 if (computedBg && computedBg !== 'rgba(0, 0, 0, 0)' && computedBg !== 'transparent' && !isWordPressCoreDefaultColor(computedBg)) { 1098 1240 buttonBg = computedBg; 1099 1241 } 1100 1242 1101 if (computedColor && computedColor !== 'rgba(0, 0, 0, 0)' ) {1243 if (computedColor && computedColor !== 'rgba(0, 0, 0, 0)' && !isWordPressCoreDefaultColor(computedColor)) { 1102 1244 buttonColor = computedColor; 1103 1245 } … … 1106 1248 1107 1249 // Apply the retrieved colors to the carousel CSS variables 1250 // Use style tag instead of inline style attribute (better practice) 1251 const docContext = root.ownerDocument || document; 1252 const variables = {}; 1253 1108 1254 if (buttonBg && buttonBg !== 'rgba(0, 0, 0, 0)' && buttonBg !== 'transparent' && buttonBg !== '') { 1109 root.style.setProperty('--carousel-button-bg', buttonBg); 1110 } 1111 1112 const docContext = root.ownerDocument || document; 1255 variables['--carousel-button-bg'] = buttonBg; 1256 } 1113 1257 1114 1258 if (buttonColor && buttonColor !== 'rgba(0, 0, 0, 0)' && buttonColor !== '') { 1115 root.style.setProperty('--carousel-button-color', buttonColor);1259 variables['--carousel-button-color'] = buttonColor; 1116 1260 1117 1261 // Generate arrow SVGs using the button text color … … 1120 1264 const defaultRightArrow = generateArrowSvg('right', arrowColor, DEFAULT_ARROW_STYLE); 1121 1265 1122 root.style.setProperty('--carousel-button-arrow-left', `url("${defaultLeftArrow}")`); 1123 root.style.setProperty('--carousel-button-arrow-right', `url("${defaultRightArrow}")`); 1266 variables['--carousel-button-arrow-left'] = `url("${defaultLeftArrow}")`; 1267 variables['--carousel-button-arrow-right'] = `url("${defaultRightArrow}")`; 1268 } 1269 1270 // Inject all variables at once in a style tag 1271 if (Object.keys(variables).length > 0) { 1272 injectCssVariablesInStyleTag(variables, docContext); 1124 1273 } 1125 1274 … … 1409 1558 1410 1559 // Check whether the colors changed 1560 // Also check that colors are not WordPress core defaults 1411 1561 const bgChanged = currentBg !== lastButtonBg && 1412 1562 currentBg !== 'rgba(0, 0, 0, 0)' && 1413 1563 currentBg !== 'transparent' && 1414 currentBg !== ''; 1564 currentBg !== '' && 1565 !isWordPressCoreDefaultColor(currentBg); 1415 1566 const colorChanged = currentColor !== lastButtonColor && 1416 1567 currentColor !== 'rgba(0, 0, 0, 0)' && 1417 currentColor !== ''; 1568 currentColor !== '' && 1569 !isWordPressCoreDefaultColor(currentColor); 1418 1570 1419 1571 // Update if a change is detected … … 1423 1575 1424 1576 // Apply colors inside the appropriate document (iframe or main page) 1425 if (currentBg && currentBg !== 'rgba(0, 0, 0, 0)' && currentBg !== 'transparent') { 1426 root.style.setProperty('--carousel-button-bg', currentBg); 1427 } 1428 1429 if (currentColor && currentColor !== 'rgba(0, 0, 0, 0)') { 1430 root.style.setProperty('--carousel-button-color', currentColor); 1577 // Use style tag instead of inline style attribute (better practice) 1578 const variables = {}; 1579 1580 if (currentBg && currentBg !== 'rgba(0, 0, 0, 0)' && currentBg !== 'transparent' && !isWordPressCoreDefaultColor(currentBg)) { 1581 variables['--carousel-button-bg'] = currentBg; 1582 } 1583 1584 if (currentColor && currentColor !== 'rgba(0, 0, 0, 0)' && !isWordPressCoreDefaultColor(currentColor)) { 1585 variables['--carousel-button-color'] = currentColor; 1431 1586 1432 1587 // Generate arrow SVGs using the button text color … … 1435 1590 const rightArrowSvg = generateArrowSvg('right', arrowColor, DEFAULT_ARROW_STYLE); 1436 1591 1437 root.style.setProperty('--carousel-button-arrow-left', `url("${leftArrowSvg}")`); 1438 root.style.setProperty('--carousel-button-arrow-right', `url("${rightArrowSvg}")`); 1592 variables['--carousel-button-arrow-left'] = `url("${leftArrowSvg}")`; 1593 variables['--carousel-button-arrow-right'] = `url("${rightArrowSvg}")`; 1594 } 1595 1596 // Inject all variables at once in a style tag 1597 if (Object.keys(variables).length > 0) { 1598 injectCssVariablesInStyleTag(variables, doc); 1439 1599 } 1440 1600 … … 1589 1749 const color = computedStyle.color; 1590 1750 1591 if (bg && bg !== 'rgba(0, 0, 0, 0)' && bg !== 'transparent') { 1592 document.documentElement.style.setProperty('--carousel-button-bg', bg); 1751 // Only apply colors if they are not WordPress core defaults 1752 // Use style tag instead of inline style attribute (better practice) 1753 const variables = {}; 1754 1755 if (bg && bg !== 'rgba(0, 0, 0, 0)' && bg !== 'transparent' && !isWordPressCoreDefaultColor(bg)) { 1756 variables['--carousel-button-bg'] = bg; 1593 1757 } 1594 if (color && color !== 'rgba(0, 0, 0, 0)') { 1595 document.documentElement.style.setProperty('--carousel-button-color', color); 1758 if (color && color !== 'rgba(0, 0, 0, 0)' && !isWordPressCoreDefaultColor(color)) { 1759 variables['--carousel-button-color'] = color; 1760 1761 // Generate arrow SVGs using the button text color 1762 const arrowColor = convertColorToHexForSvg(color, document); 1763 const leftArrowSvg = generateArrowSvg('left', arrowColor, DEFAULT_ARROW_STYLE); 1764 const rightArrowSvg = generateArrowSvg('right', arrowColor, DEFAULT_ARROW_STYLE); 1765 1766 variables['--carousel-button-arrow-left'] = `url("${leftArrowSvg}")`; 1767 variables['--carousel-button-arrow-right'] = `url("${rightArrowSvg}")`; 1596 1768 1597 1769 applyArrowIconsToCarousels(color, document); 1770 } 1771 1772 // Inject all variables at once in a style tag 1773 if (Object.keys(variables).length > 0) { 1774 injectCssVariablesInStyleTag(variables, document); 1598 1775 } 1599 1776 } -
native-blocks-carousel/trunk/assets/js/carousel-frontend-init.js
r3395126 r3423082 4 4 * 5 5 * @package AnyBlockCarouselSlider 6 * @version 1.0. 26 * @version 1.0.4 7 7 * @author weblazer35 8 8 */ … … 441 441 carousel.style.setProperty('--carousel-button-arrow-left', 'url("' + leftArrowSvg + '")'); 442 442 carousel.style.setProperty('--carousel-button-arrow-right', 'url("' + rightArrowSvg + '")'); 443 if (carousel.dataset) {444 carousel.dataset.abcsCarouselArrowStyle = styleKey;445 carousel.dataset.abcsArrowStyle = styleKey;446 }443 if (carousel.dataset) { 444 carousel.dataset.abcsCarouselArrowStyle = styleKey; 445 carousel.dataset.abcsArrowStyle = styleKey; 446 } 447 447 448 448 if (parent) { 449 if (parent.dataset) {450 parent.dataset.abcsCarouselArrowStyle = styleKey;451 parent.dataset.abcsArrowStyle = styleKey;452 }449 if (parent.dataset) { 450 parent.dataset.abcsCarouselArrowStyle = styleKey; 451 parent.dataset.abcsArrowStyle = styleKey; 452 } 453 453 parent.style.setProperty('--carousel-button-arrow-left', 'url("' + leftArrowSvg + '")'); 454 454 parent.style.setProperty('--carousel-button-arrow-right', 'url("' + rightArrowSvg + '")'); 455 455 } 456 456 }); 457 } 458 459 /** 460 * Injects CSS variables into a <style> tag instead of inline style attribute. 461 * This is a better practice than using element.style.setProperty() on documentElement. 462 * 463 * @param {Object} variables - Object with CSS variable names as keys and values as values 464 */ 465 function injectCssVariablesInStyleTag(variables) { 466 const head = document.head || document.getElementsByTagName('head')[0]; 467 468 if (!head) return; 469 470 // Find or create the style tag for carousel variables 471 let styleTag = document.getElementById('carousel-dynamic-variables'); 472 473 if (!styleTag) { 474 styleTag = document.createElement('style'); 475 styleTag.id = 'carousel-dynamic-variables'; 476 styleTag.type = 'text/css'; 477 head.appendChild(styleTag); 478 } 479 480 // Build CSS with all variables 481 let css = ':root {'; 482 for (const [key, value] of Object.entries(variables)) { 483 if (value !== null && value !== undefined && value !== '') { 484 css += '\n ' + key + ': ' + value + ';'; 485 } 486 } 487 css += '\n}'; 488 489 styleTag.textContent = css; 457 490 } 458 491 … … 488 521 const defaultRightArrow = generateArrowSvg('right', arrowColor, DEFAULT_ARROW_STYLE); 489 522 490 // Inject CSS variables on :root 491 root.style.setProperty('--carousel-button-arrow-left', 'url("' + defaultLeftArrow + '")'); 492 root.style.setProperty('--carousel-button-arrow-right', 'url("' + defaultRightArrow + '")'); 523 // Inject CSS variables in a style tag instead of inline style attribute 524 const variables = { 525 '--carousel-button-arrow-left': 'url("' + defaultLeftArrow + '")', 526 '--carousel-button-arrow-right': 'url("' + defaultRightArrow + '")' 527 }; 528 injectCssVariablesInStyleTag(variables); 493 529 } 494 530 495 531 // Do not force the style on each carousel here; they are normalised later 532 } 533 534 /** 535 * Initializes autoplay for carousels with autoplay enabled. 536 * Handles automatic scrolling, pause on hover/interaction, and stop at end. 537 */ 538 // Store intervals and state outside function to persist across calls 539 const autoplayIntervals = new WeakMap(); 540 const autoplayPaused = new WeakMap(); 541 const interactionTimeout = new WeakMap(); 542 const autoplayInitialized = new WeakSet(); 543 const loopResetSetup = new WeakSet(); 544 const isAutoScrollingMap = new WeakMap(); // Track autoplay scrolling state 545 546 /** 547 * Setup loop functionality: keep buttons visible and handle reset when clicking Next at the end 548 * Simple approach: listen for clicks and check if we're at the end, then reset 549 */ 550 function setupLoopReset(carousel) { 551 // Skip if already setup 552 if (loopResetSetup.has(carousel)) { 553 return; 554 } 555 556 const isLoopEnabled = carousel.getAttribute('data-abcs-loop') === 'true'; 557 if (!isLoopEnabled) { 558 return; 559 } 560 561 // Skip if carousel has no children 562 if (!carousel.firstElementChild) { 563 return; 564 } 565 566 let isResetting = false; // Flag to prevent multiple resets 567 568 // Get autoplay delay for this carousel (if autoplay is enabled) 569 const autoplayDelay = parseInt(carousel.getAttribute('data-abcs-autoplay-delay'), 10) || 3000; 570 571 // Function to check if we're at the end 572 function isAtEnd() { 573 const threshold = 5; 574 return carousel.scrollLeft + carousel.offsetWidth >= carousel.scrollWidth - threshold; 575 } 576 577 // Function to check if we're at the start 578 function isAtStart() { 579 const threshold = 5; 580 return carousel.scrollLeft <= threshold; 581 } 582 583 // Function to reset to start 584 function resetToStart() { 585 if (isResetting) { 586 return; 587 } 588 isResetting = true; 589 carousel.style.scrollBehavior = 'auto'; 590 carousel.scrollTo({ 591 left: 0, 592 behavior: 'auto' 593 }); 594 carousel.style.scrollBehavior = ''; 595 setTimeout(function () { 596 isResetting = false; 597 }, 100); 598 } 599 600 // Function to reset to end 601 function resetToEnd() { 602 if (isResetting) { 603 return; 604 } 605 isResetting = true; 606 carousel.style.scrollBehavior = 'auto'; 607 carousel.scrollTo({ 608 left: carousel.scrollWidth, 609 behavior: 'auto' 610 }); 611 carousel.style.scrollBehavior = ''; 612 setTimeout(function () { 613 isResetting = false; 614 }, 100); 615 } 616 617 // Track if we were already at boundaries BEFORE any interaction 618 // This distinguishes "arriving at the end" from "already at the end" 619 let wasAtEndBeforeInteraction = false; 620 let wasAtStartBeforeInteraction = false; 621 let clickTimeout = null; 622 let scrollTimeout = null; 623 let previousScrollLeft = carousel.scrollLeft; 624 625 // Update boundary flags on scroll (to track when we reach boundaries) 626 function updateBoundaryFlags() { 627 // Only update if we're not resetting 628 if (!isResetting) { 629 wasAtEndBeforeInteraction = isAtEnd(); 630 wasAtStartBeforeInteraction = isAtStart(); 631 } 632 } 633 634 // Listen for clicks on the carousel (this will catch clicks on scroll buttons) 635 function handleClick(e) { 636 if (isResetting) { 637 return; 638 } 639 640 // Clear any pending click timeout 641 if (clickTimeout) { 642 clearTimeout(clickTimeout); 643 } 644 645 // Store state BEFORE the click - this is crucial! 646 // We use the flag that was set BEFORE this click, not the current state 647 const wasAtEndBeforeClick = wasAtEndBeforeInteraction; 648 const wasAtStartBeforeClick = wasAtStartBeforeInteraction; 649 650 // Only reset if we were ALREADY at the end BEFORE the click 651 // This allows the first click to show the last slide, and the second click to reset 652 if (wasAtEndBeforeClick) { 653 clickTimeout = setTimeout(function () { 654 if (isResetting) { 655 return; 656 } 657 // If still at end after click, the button couldn't scroll - reset to start 658 if (isAtEnd()) { 659 resetToStart(); 660 } 661 }, 400); // Longer delay to let scroll-button try to scroll 662 } 663 // Only reset if we were ALREADY at the start BEFORE the click 664 else if (wasAtStartBeforeClick) { 665 clickTimeout = setTimeout(function () { 666 if (isResetting) { 667 return; 668 } 669 // If still at start after click, the button couldn't scroll - reset to end 670 if (isAtStart()) { 671 resetToEnd(); 672 } 673 }, 400); 674 } 675 676 // After the click, update flags for next time (but don't reset now) 677 // This ensures that if we just arrived at the end, the next click will trigger reset 678 setTimeout(function () { 679 updateBoundaryFlags(); 680 }, 500); // Wait for scroll to complete before updating flags 681 } 682 683 // Handle scroll events - only reset if we were ALREADY at the end before scrolling 684 function handleScroll() { 685 if (isResetting) { 686 previousScrollLeft = carousel.scrollLeft; 687 return; 688 } 689 690 const currentScrollLeft = carousel.scrollLeft; 691 const isScrollingForward = currentScrollLeft > previousScrollLeft; 692 const isScrollingBackward = currentScrollLeft < previousScrollLeft; 693 const scrollDelta = Math.abs(currentScrollLeft - previousScrollLeft); 694 695 // Update boundary flags as we scroll 696 updateBoundaryFlags(); 697 698 previousScrollLeft = currentScrollLeft; 699 700 // Clear any pending scroll timeout 701 if (scrollTimeout) { 702 clearTimeout(scrollTimeout); 703 } 704 705 // Check if this is autoplay scrolling 706 const isAutoplayActive = isAutoScrollingMap.get(carousel); 707 708 // Ignore tiny scrolls (scroll-snap adjustments) - they're less than 10px 709 // BUT allow autoplay scrolls even if tiny (autoplay is programmatic, not scroll-snap) 710 const isSignificantScroll = scrollDelta > 10 || isAutoplayActive; 711 712 // Only reset if we were ALREADY at the end BEFORE this scroll AND still at end 713 // AND it's a significant scroll (not just scroll-snap micro-adjustments) 714 // OR if it's autoplay (which can have small scrolls) 715 // This prevents reset when scrolling normally towards the end or from scroll-snap 716 if (wasAtEndBeforeInteraction && isAtEnd() && isScrollingForward && isSignificantScroll) { 717 scrollTimeout = setTimeout(function () { 718 if (isAtEnd() && !isResetting && wasAtEndBeforeInteraction) { 719 // We were already at the end and tried to scroll forward - reset to start 720 // If autoplay is active, add delay before reset equal to autoplay delay 721 if (isAutoplayActive) { 722 setTimeout(function () { 723 if (!isResetting) { 724 resetToStart(); 725 } 726 }, autoplayDelay); 727 } else { 728 resetToStart(); 729 } 730 } 731 }, 200); 732 } 733 // Only reset if we were ALREADY at the start BEFORE this scroll AND still at start 734 else if (wasAtStartBeforeInteraction && isAtStart() && isScrollingBackward && isSignificantScroll) { 735 scrollTimeout = setTimeout(function () { 736 if (isAtStart() && !isResetting && wasAtStartBeforeInteraction) { 737 // We were already at the start and tried to scroll backward - reset to end 738 resetToEnd(); 739 } 740 }, 200); 741 } 742 } 743 744 // Initialize boundary flags 745 updateBoundaryFlags(); 746 747 // Listen for clicks and scroll events 748 carousel.addEventListener('click', handleClick); 749 carousel.addEventListener('scroll', handleScroll, { passive: true }); 750 751 // Store handler references for cleanup 752 carousel._abcsLoopClickHandler = handleClick; 753 carousel._abcsLoopScrollHandler = handleScroll; 754 755 // Store cleanup function 756 carousel._abcsLoopCleanup = function () { 757 if (carousel._abcsLoopClickHandler) { 758 carousel.removeEventListener('click', carousel._abcsLoopClickHandler); 759 } 760 if (carousel._abcsLoopScrollHandler) { 761 carousel.removeEventListener('scroll', carousel._abcsLoopScrollHandler); 762 } 763 if (clickTimeout) { 764 clearTimeout(clickTimeout); 765 } 766 if (scrollTimeout) { 767 clearTimeout(scrollTimeout); 768 } 769 loopResetSetup.delete(carousel); 770 }; 771 772 loopResetSetup.add(carousel); 773 } 774 775 function initAutoplay() { 776 const carousels = document.querySelectorAll('.abcs[data-abcs-autoplay="true"]'); 777 778 carousels.forEach(function (carousel) { 779 // Skip if already initialized 780 if (autoplayInitialized.has(carousel)) { 781 return; 782 } 783 784 // Skip if carousel has no children 785 if (!carousel.firstElementChild) { 786 return; 787 } 788 789 const autoplayDelay = parseInt(carousel.getAttribute('data-abcs-autoplay-delay'), 10) || 3000; 790 const isLoopEnabled = carousel.getAttribute('data-abcs-loop') === 'true'; 791 792 // Setup loop reset if loop is enabled 793 if (isLoopEnabled) { 794 setupLoopReset(carousel); 795 } 796 let intervalId = null; 797 let isPaused = false; 798 let isHoverPaused = false; 799 let interactionTimeoutId = null; 800 let isAutoScrolling = false; // Flag to track if scroll is from autoplay 801 const RESUME_DELAY = 2000; // Resume autoplay after 2 seconds of no interaction 802 803 // Calculate slide width including gap 804 function getSlideWidth() { 805 const firstChild = carousel.firstElementChild; 806 if (!firstChild) { 807 return 0; 808 } 809 const computedStyle = window.getComputedStyle(carousel); 810 const gap = computedStyle.getPropertyValue('gap') || computedStyle.getPropertyValue('--wp--style--block-gap') || '1rem'; 811 // Convert gap to pixels if it has a unit 812 let gapValue = 0; 813 if (gap && gap !== 'normal') { 814 // Try to parse as number (for px values) 815 const gapNum = parseFloat(gap); 816 if (!isNaN(gapNum)) { 817 // If gap contains 'rem' or 'em', convert to pixels 818 if (gap.includes('rem')) { 819 const rootFontSize = parseFloat(window.getComputedStyle(document.documentElement).fontSize) || 16; 820 gapValue = gapNum * rootFontSize; 821 } else if (gap.includes('em')) { 822 const parentFontSize = parseFloat(computedStyle.fontSize) || 16; 823 gapValue = gapNum * parentFontSize; 824 } else { 825 // Assume px or unitless 826 gapValue = gapNum; 827 } 828 } 829 } 830 return firstChild.offsetWidth + gapValue; 831 } 832 833 // Check if carousel is at the end 834 function isAtEnd() { 835 // If loop is enabled, never consider it at the end 836 if (isLoopEnabled) { 837 return false; 838 } 839 const threshold = 5; // Small threshold for rounding errors 840 return carousel.scrollLeft + carousel.offsetWidth >= carousel.scrollWidth - threshold; 841 } 842 843 844 // Scroll to next slide 845 function scrollToNext() { 846 const slideWidth = getSlideWidth(); 847 if (slideWidth === 0) { 848 return; 849 } 850 851 const currentScroll = carousel.scrollLeft; 852 const nextScroll = currentScroll + slideWidth; 853 854 // If loop is disabled, check if we're at the end 855 if (!isLoopEnabled) { 856 const threshold = 5; 857 const isAtEndNow = currentScroll + carousel.offsetWidth >= carousel.scrollWidth - threshold; 858 if (isAtEndNow) { 859 // Stop autoplay if at the end and loop is disabled 860 if (intervalId) { 861 clearInterval(intervalId); 862 intervalId = null; 863 autoplayIntervals.delete(carousel); 864 } 865 return; 866 } 867 } 868 869 // With loop enabled, the reset handler will detect the end and jump to start 870 // We just scroll normally - the handler manages the reset 871 isAutoScrolling = true; 872 isAutoScrollingMap.set(carousel, true); 873 carousel.scrollTo({ 874 left: nextScroll, 875 behavior: 'smooth' 876 }); 877 878 // Keep the flag active longer to allow handler to detect end during smooth scroll 879 setTimeout(function () { 880 isAutoScrolling = false; 881 isAutoScrollingMap.set(carousel, false); 882 }, 700); // Slightly longer to ensure smooth scroll completes 883 } 884 885 // Start autoplay 886 function startAutoplay() { 887 // If loop is disabled, check if we're at the end 888 if (intervalId || (!isLoopEnabled && isAtEnd())) { 889 return; 890 } 891 892 // Start autoplay (loop reset is already set up if needed) 893 intervalId = setInterval(function () { 894 if (!isPaused) { 895 scrollToNext(); 896 } 897 }, autoplayDelay); 898 autoplayIntervals.set(carousel, intervalId); 899 } 900 901 // Pause autoplay 902 function pauseAutoplay() { 903 isPaused = true; 904 autoplayPaused.set(carousel, true); 905 } 906 907 // Resume autoplay 908 function resumeAutoplay() { 909 // If loop is disabled and we're at the end, don't resume 910 if (!isLoopEnabled && isAtEnd()) { 911 return; 912 } 913 isPaused = false; 914 autoplayPaused.delete(carousel); 915 } 916 917 // Handle interaction - pause and resume after delay 918 function handleInteraction() { 919 // Ignore scroll events from autoplay 920 if (isAutoScrolling || isAutoScrollingMap.get(carousel)) { 921 return; 922 } 923 924 pauseAutoplay(); 925 926 // Clear existing timeout 927 if (interactionTimeoutId) { 928 clearTimeout(interactionTimeoutId); 929 } 930 931 // Resume after delay (only if not hover paused) 932 interactionTimeoutId = setTimeout(function () { 933 if (!isHoverPaused) { 934 resumeAutoplay(); 935 } 936 interactionTimeoutId = null; 937 }, RESUME_DELAY); 938 939 interactionTimeout.set(carousel, interactionTimeoutId); 940 } 941 942 // Event listeners for pause on hover 943 const handleMouseEnter = function () { 944 isHoverPaused = true; 945 pauseAutoplay(); 946 }; 947 const handleMouseLeave = function () { 948 isHoverPaused = false; 949 // Resume if loop is enabled or if not at the end 950 if (isLoopEnabled || !isAtEnd()) { 951 resumeAutoplay(); 952 } 953 }; 954 carousel.addEventListener('mouseenter', handleMouseEnter); 955 carousel.addEventListener('mouseleave', handleMouseLeave); 956 957 // Event listeners for pause on interaction 958 carousel.addEventListener('scroll', handleInteraction, { passive: true }); 959 carousel.addEventListener('touchstart', handleInteraction, { passive: true }); 960 carousel.addEventListener('mousedown', handleInteraction); 961 962 // Pause when clicking on scroll buttons (handled via parent click events) 963 // Note: ::scroll-button are pseudo-elements, so we listen on the carousel itself 964 965 // Start autoplay 966 startAutoplay(); 967 968 // Mark as initialized 969 autoplayInitialized.add(carousel); 970 971 // Cleanup function (stored for potential future use) 972 carousel._abcsAutoplayCleanup = function () { 973 if (intervalId) { 974 clearInterval(intervalId); 975 intervalId = null; 976 autoplayIntervals.delete(carousel); 977 } 978 if (interactionTimeoutId) { 979 clearTimeout(interactionTimeoutId); 980 interactionTimeoutId = null; 981 interactionTimeout.delete(carousel); 982 } 983 carousel.removeEventListener('mouseenter', handleMouseEnter); 984 carousel.removeEventListener('mouseleave', handleMouseLeave); 985 carousel.removeEventListener('scroll', handleInteraction); 986 carousel.removeEventListener('touchstart', handleInteraction); 987 carousel.removeEventListener('mousedown', handleInteraction); 988 }; 989 }); 496 990 } 497 991 … … 504 998 injectArrowSvgs(); 505 999 applyArrowIconsToCarousels(null, document); 1000 1001 // Setup loop reset for all carousels with loop enabled 1002 const loopCarousels = document.querySelectorAll('.abcs[data-abcs-loop="true"]'); 1003 loopCarousels.forEach(function (carousel) { 1004 if (carousel.firstElementChild) { 1005 setupLoopReset(carousel); 1006 } 1007 }); 1008 1009 initAutoplay(); 506 1010 } 507 1011 … … 554 1058 injectArrowSvgs(); 555 1059 applyArrowIconsToCarousels(null, document); 1060 initAutoplay(); 556 1061 }); 557 1062 }); 558 1063 } 559 1064 }); 1065 1066 // Handle dynamically added carousels with MutationObserver 1067 function setupAutoplayObserver() { 1068 // Only set up observer if MutationObserver is available 1069 if (typeof MutationObserver === 'undefined') { 1070 return; 1071 } 1072 1073 const observer = new MutationObserver(function (mutations) { 1074 let shouldInitAutoplay = false; 1075 let shouldInitLoop = false; 1076 mutations.forEach(function (mutation) { 1077 mutation.addedNodes.forEach(function (node) { 1078 if (node.nodeType === 1) { // Element node 1079 // Check if the added node is a carousel 1080 if (node.classList && node.classList.contains('abcs')) { 1081 if (node.getAttribute('data-abcs-autoplay') === 'true') { 1082 shouldInitAutoplay = true; 1083 } 1084 if (node.getAttribute('data-abcs-loop') === 'true') { 1085 shouldInitLoop = true; 1086 } 1087 } 1088 // Check for carousels within the added node 1089 if (node.querySelectorAll) { 1090 const autoplayCarousels = node.querySelectorAll('.abcs[data-abcs-autoplay="true"]'); 1091 if (autoplayCarousels.length > 0) { 1092 shouldInitAutoplay = true; 1093 } 1094 const loopCarousels = node.querySelectorAll('.abcs[data-abcs-loop="true"]'); 1095 if (loopCarousels.length > 0) { 1096 shouldInitLoop = true; 1097 } 1098 } 1099 } 1100 }); 1101 }); 1102 1103 if (shouldInitAutoplay) { 1104 // Use requestAnimationFrame to ensure DOM is ready 1105 requestAnimationFrame(function () { 1106 initAutoplay(); 1107 }); 1108 } 1109 if (shouldInitLoop) { 1110 // Setup loop reset for new carousels 1111 requestAnimationFrame(function () { 1112 const loopCarousels = document.querySelectorAll('.abcs[data-abcs-loop="true"]'); 1113 loopCarousels.forEach(function (carousel) { 1114 if (carousel.firstElementChild && !loopResetSetup.has(carousel)) { 1115 setupLoopReset(carousel); 1116 } 1117 }); 1118 }); 1119 } 1120 }); 1121 1122 // Start observing the document body for added nodes 1123 observer.observe(document.body, { 1124 childList: true, 1125 subtree: true 1126 }); 1127 } 1128 1129 // Set up observer after initial load 1130 if (document.readyState === 'loading') { 1131 document.addEventListener('DOMContentLoaded', function () { 1132 setupAutoplayObserver(); 1133 }); 1134 } else { 1135 setupAutoplayObserver(); 1136 } 560 1137 561 1138 if (typeof window !== 'undefined') { … … 568 1145 applyArrowIconsToCarousels(color, context || document, normalizedConfig); 569 1146 }; 1147 window.abcsCarousel.initAutoplay = initAutoplay; 570 1148 } 571 1149 -
native-blocks-carousel/trunk/includes/Renderer.php
r3395126 r3423082 51 51 $this->maybeAddPaddingVariables($custom_styles, $block, $block_content); 52 52 53 if (empty($custom_styles)) { 53 // Get loop and autoplay attributes 54 $loop = $block['attrs']['carouselLoop'] ?? false; 55 $autoplay = $block['attrs']['carouselAutoplay'] ?? false; 56 $autoplay_delay = $block['attrs']['carouselAutoplayDelay'] ?? 3000; 57 58 $has_styles = !empty($custom_styles); 59 $has_loop_attrs = $loop; 60 $has_autoplay_attrs = $autoplay; 61 62 // If no styles and no loop/autoplay attributes, return early 63 if (!$has_styles && !$has_loop_attrs && !$has_autoplay_attrs) { 54 64 return $block_content; 55 65 } 56 66 57 $styles_string = $this->buildStylesString($custom_styles); 67 $styles_string = $has_styles ? $this->buildStylesString($custom_styles) : ''; 68 58 69 if (\class_exists('\\WP_HTML_Tag_Processor')) { 59 70 $processor = new \WP_HTML_Tag_Processor($block_content); 60 71 61 72 if ($processor->next_tag(['class_name' => 'abcs'])) { 62 $existing_style = $processor->get_attribute('style'); 63 $processor->set_attribute('style', $this->mergeStyleAttribute($existing_style, $styles_string)); 73 if ($has_styles) { 74 $existing_style = $processor->get_attribute('style'); 75 $processor->set_attribute('style', $this->mergeStyleAttribute($existing_style, $styles_string)); 76 } 77 78 // Add loop data attribute 79 if ($has_loop_attrs) { 80 $processor->set_attribute('data-abcs-loop', $loop ? 'true' : 'false'); 81 } 82 83 // Add autoplay data attributes 84 if ($has_autoplay_attrs) { 85 $processor->set_attribute('data-abcs-autoplay', $autoplay ? 'true' : 'false'); 86 $processor->set_attribute('data-abcs-autoplay-delay', (string) $autoplay_delay); 87 } 64 88 65 89 $modified_content = $processor->get_updated_html(); … … 71 95 } 72 96 97 // Fallback to regex if WP_HTML_Tag_Processor is not available 73 98 $pattern = '/(<(?:div|ul|figure)\s+[^>]*class="[^"]*\babcs\b[^"]*"[^>]*?)(?:\s+style="([^"]*)")?(\s*>)/i'; 74 99 75 $replacement = function (array $matches) use ($styles_string ) {100 $replacement = function (array $matches) use ($styles_string, $loop, $autoplay, $autoplay_delay, $has_styles, $has_loop_attrs, $has_autoplay_attrs) { 76 101 $tag_start = $matches[1]; 77 102 $existing_style = $matches[2] ?? ''; 78 103 $tag_end = $matches[3]; 79 104 80 $new_style = $this->mergeStyleAttribute($existing_style, $styles_string); 81 82 return $tag_start . ' style="' . $new_style . '"' . $tag_end; 105 $result = $tag_start; 106 107 // Add style attribute if needed 108 if ($has_styles) { 109 $existing_style_trimmed = '' !== $existing_style ? \trim($existing_style) : ''; 110 if ('' !== $existing_style_trimmed && ';' !== \substr($existing_style_trimmed, -1)) { 111 $existing_style_trimmed .= ';'; 112 } 113 $new_style = $existing_style_trimmed . $styles_string; 114 $result .= ' style="' . \esc_attr($new_style) . '"'; 115 } elseif ($existing_style) { 116 $result .= ' style="' . \esc_attr($existing_style) . '"'; 117 } 118 119 // Add loop data attribute 120 if ($has_loop_attrs) { 121 $result .= ' data-abcs-loop="' . \esc_attr($loop ? 'true' : 'false') . '"'; 122 } 123 124 // Add autoplay data attributes 125 if ($has_autoplay_attrs) { 126 $result .= ' data-abcs-autoplay="' . \esc_attr($autoplay ? 'true' : 'false') . '"'; 127 $result .= ' data-abcs-autoplay-delay="' . \esc_attr((string) $autoplay_delay) . '"'; 128 } 129 130 $result .= $tag_end; 131 132 return $result; 83 133 }; 84 134 -
native-blocks-carousel/trunk/includes/ThemeStyles.php
r3395126 r3423082 25 25 public function injectButtonColors(): void 26 26 { 27 $theme_json = \WP_Theme_JSON_Resolver::get_merged_data(); 28 $styles = $theme_json->get_stylesheet(); 29 $settings = $theme_json->get_data(); 30 27 // Step 1: Get merged data (includes core + theme + user styles) 28 // This is needed to get user styles (custom styles from site-editor) 29 $merged_data = \WP_Theme_JSON_Resolver::get_merged_data(); 30 $styles = $merged_data->get_stylesheet(); 31 $merged_settings = $merged_data->get_data(); 32 33 // Get theme data separately (to check if theme has custom styles) 34 $theme_data = \WP_Theme_JSON_Resolver::get_theme_data(); 35 $theme_settings = $theme_data->get_data(); 36 37 // Get user data separately (to check if user has custom styles) 38 $user_data = \WP_Theme_JSON_Resolver::get_user_data(); 39 $user_settings = $user_data->get_data(); 40 41 // Initialize variables to store detected colors 31 42 $button_bg = ''; 32 43 $button_color = ''; 33 44 34 if (isset($settings['styles']['elements']['button']['color']['background'])) { 35 $button_bg = $settings['styles']['elements']['button']['color']['background']; 36 } 37 38 if (isset($settings['styles']['elements']['button']['color']['text'])) { 39 $button_color = $settings['styles']['elements']['button']['color']['text']; 40 } 41 42 if (empty($button_bg) && \preg_match('/.wp-element-button[^{]*\{[^}]*background-color:\s*([^;]+)/s', $styles, $matches)) { 45 // Step 2: Priority 1 - Check user styles (custom styles from site-editor) 46 // User styles have the highest priority and should be used if they exist 47 // Path: styles.elements.button.color.background 48 if (isset($user_settings['styles']['elements']['button']['color']['background'])) { 49 $button_bg = $user_settings['styles']['elements']['button']['color']['background']; 50 } 51 52 if (isset($user_settings['styles']['elements']['button']['color']['text'])) { 53 $button_color = $user_settings['styles']['elements']['button']['color']['text']; 54 } 55 56 // Step 3: Priority 2 - Check theme styles (from theme.json) 57 // Only use theme styles if user styles are not defined 58 // Path: styles.elements.button.color.background 59 if (empty($button_bg) && isset($theme_settings['styles']['elements']['button']['color']['background'])) { 60 $button_bg = $theme_settings['styles']['elements']['button']['color']['background']; 61 } 62 63 if (empty($button_color) && isset($theme_settings['styles']['elements']['button']['color']['text'])) { 64 $button_color = $theme_settings['styles']['elements']['button']['color']['text']; 65 } 66 67 // Step 4: Priority 3 - Fallback method - extract colors from compiled CSS 68 // IMPORTANT: Only use this fallback if theme OR user has explicitly defined button styles 69 // We check if theme or user has button element styles to avoid using WordPress core defaults 70 // If neither theme nor user has button styles defined, we skip the fallback to avoid core defaults 71 $has_custom_button_styles = ( 72 (isset($theme_settings['styles']['elements']['button']) && !empty($theme_settings['styles']['elements']['button'])) 73 || (isset($user_settings['styles']['elements']['button']) && !empty($user_settings['styles']['elements']['button'])) 74 ); 75 76 // This regex searches for .wp-element-button class and extracts background-color value 77 // Pattern: .wp-element-button { ... background-color: VALUE; ... } 78 // Only extract if theme or user has defined button styles (to avoid core defaults) 79 if (empty($button_bg) && $has_custom_button_styles && \preg_match('/.wp-element-button[^{]*\{[^}]*background-color:\s*([^;]+)/s', $styles, $matches)) { 43 80 $button_bg = \trim($matches[1]); 44 81 } 45 82 46 if (empty($button_color) && \preg_match('/.wp-element-button[^{]*\{[^}]*color:\s*([^;]+)/s', $styles, $matches)) { 83 // Extract text color from compiled CSS if not found in theme.json or user styles 84 // Pattern: .wp-element-button { ... color: VALUE; ... } 85 // Only extract if theme or user has defined button styles 86 if (empty($button_color) && $has_custom_button_styles && \preg_match('/.wp-element-button[^{]*\{[^}]*color:\s*([^;]+)/s', $styles, $matches)) { 47 87 $button_color = \trim($matches[1]); 48 88 } 49 89 90 // Step 5: Resolve CSS variables to concrete values 91 // If the color is a CSS variable (e.g., var(--wp--preset--color--primary)), 92 // try to resolve it to its actual value (e.g., #007cba) 50 93 $button_bg = $this->resolveCssVariable($button_bg, $styles); 51 94 $button_color = $this->resolveCssVariable($button_color, $styles); 52 95 96 // Step 5.5: Filter out WordPress core default colors 97 // IMPORTANT: Only filter if colors don't come from user styles 98 // If user has explicitly set colors in site-editor, we should respect them 99 // even if they match core defaults (user might want to use core defaults intentionally) 100 $colors_from_user = !empty($user_settings['styles']['elements']['button'] ?? []); 101 102 if (!$colors_from_user) { 103 // Only filter core defaults if colors don't come from user styles 104 // This prevents using core defaults that might be stored in theme styles 105 $button_bg = $this->filterCoreDefaultColors($button_bg); 106 $button_color = $this->filterCoreDefaultColors($button_color); 107 } 108 109 // Step 6: Build CSS custom properties block 110 // Create :root selector to define global CSS variables 53 111 $custom_css = ':root {'; 54 112 113 // Add background color variable if found 114 // esc_attr() sanitizes the value for safe output 55 115 if (!empty($button_bg)) { 56 116 $custom_css .= '--carousel-button-bg: ' . \esc_attr($button_bg) . ';'; 57 117 } 58 118 119 // Add text color variable if found 59 120 if (!empty($button_color)) { 60 121 $custom_css .= '--carousel-button-color: ' . \esc_attr($button_color) . ';'; … … 63 124 $custom_css .= '}'; 64 125 126 // Step 7: Inject the CSS into WordPress stylesheet 127 // Only inject if at least one color was successfully detected 128 // wp_add_inline_style() appends CSS to the specified stylesheet handle 65 129 if (!empty($button_bg) || !empty($button_color)) { 66 130 \wp_add_inline_style('any-block-carousel-slider', $custom_css); … … 78 142 private function resolveCssVariable(string $value, string $styles): string 79 143 { 144 // If value is empty or not a CSS variable, return as-is 145 // CSS variables use the format: var(--variable-name) 80 146 if (empty($value) || false === \strpos($value, 'var(')) { 81 147 return $value; 82 148 } 83 149 150 // Extract the variable name from var(--variable-name) format 151 // Example: var(--wp--preset--color--primary) → --wp--preset--color--primary 84 152 if (\preg_match('/var\(([^)]+)\)/', $value, $var_match)) { 85 153 $var_name = \trim($var_match[1]); 154 155 // Search for the variable definition in the compiled stylesheet 156 // Pattern: --variable-name: VALUE; 157 // preg_quote() escapes special regex characters in the variable name 86 158 if (\preg_match('/' . \preg_quote($var_name, '/') . ':\s*([^;]+)/s', $styles, $color_match)) { 159 // Return the resolved concrete value (e.g., #007cba instead of var(--wp--preset--color--primary)) 87 160 return \trim($color_match[1]); 88 161 } 89 162 } 90 163 164 // If variable couldn't be resolved, return original value 165 // This allows CSS to handle the variable at runtime 91 166 return $value; 92 167 } 168 169 /** 170 * Filters out WordPress core default button colors. 171 * 172 * Even if colors are detected from theme or user styles, ignore them 173 * if they match WordPress core defaults. This prevents using core defaults 174 * that might be stored in user global styles. 175 * 176 * @param string $color Color value to check (can be rgb, rgba, hex, etc.) 177 * @return string Empty string if color matches core defaults, original value otherwise 178 */ 179 private function filterCoreDefaultColors(string $color): string 180 { 181 if (empty($color)) { 182 return $color; 183 } 184 185 // WordPress core default button colors 186 // Background: rgb(50, 55, 60) = #32373c 187 // Text: rgb(255, 255, 255) = #fff = #ffffff 188 $core_defaults = [ 189 'rgb(50, 55, 60)', 190 'rgba(50, 55, 60, 1)', 191 'rgba(50, 55, 60,1)', 192 '#32373c', 193 'rgb(255, 255, 255)', 194 'rgba(255, 255, 255, 1)', 195 'rgba(255, 255, 255,1)', 196 '#ffffff', 197 '#fff', 198 ]; 199 200 // Normalize the color for comparison 201 $normalized_color = \strtolower(\trim($color)); 202 203 // Check if color matches any core default 204 foreach ($core_defaults as $default) { 205 $normalized_default = \strtolower(\trim($default)); 206 if ($normalized_color === $normalized_default) { 207 // Return empty string to indicate this is a core default and should be ignored 208 return ''; 209 } 210 } 211 212 // Also check for rgb/rgba values that might have different formatting 213 // e.g., "rgb(50,55,60)" or "rgb( 50 , 55 , 60 )" 214 if (\preg_match('/rgba?\((\d+)\s*,\s*(\d+)\s*,\s*(\d+)/i', $color, $matches)) { 215 $r = (int) $matches[1]; 216 $g = (int) $matches[2]; 217 $b = (int) $matches[3]; 218 219 // Check if it matches core background default 220 if ($r === 50 && $g === 55 && $b === 60) { 221 return ''; 222 } 223 224 // Check if it matches core text default 225 if ($r === 255 && $g === 255 && $b === 255) { 226 return ''; 227 } 228 } 229 230 return $color; 231 } 93 232 } -
native-blocks-carousel/trunk/readme.txt
r3421234 r3423082 5 5 Tested up to: 6.9 6 6 Requires PHP: 7.4 7 Stable tag: 1.0. 3.67 Stable tag: 1.0.4 8 8 License: GPLv2 or later 9 9 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 45 45 46 46 * **100% CSS** – Smooth carousel slider with `scroll-snap`, `::scroll-button`, and `::scroll-marker`. No script to bundle. 47 * **Loop functionality** – Enable infinite scrolling with seamless reset to start/end when reaching boundaries. 48 * **Autoplay support** – Automatic slide progression with configurable delay and pause on hover/interaction. 47 49 * **Smart responsive** – Automatically handles visible columns, spacing, and control sizes according to WordPress breakpoints (1280, 1024, 782, 600, 480, 375). 48 50 * **Two width modes** – Manual mode (fixed column count) and Auto mode (fixed width like 320px) with automatic detection. … … 61 63 = Advanced customization = 62 64 65 * **Loop mode** – Enable infinite scrolling: when reaching the end, the carousel seamlessly resets to the beginning (and vice versa). Navigation buttons remain active at all times. 66 * **Autoplay** – Automatic slide progression with configurable delay (default: 3000ms). Autoplay pauses on hover and user interaction, and stops at the end when loop is disabled. 63 67 * **Manual mode (fixed columns)** – Ideal for article carousel sliders: 1 to 6 columns depending on screen sizes. 64 68 * **Auto mode (fixed width)** – Perfect for card-based sliders (posts, testimonials, product highlights) with pixel-perfect widths like 280px, 320px, or 360px. … … 178 182 * 🔗 Simplified WordPress Playground link in "Try it now" section. 179 183 * 📝 Updated readme.txt to refresh WordPress.org cache. 184 185 = 1.0.4 - 2025-01-XX = 186 * ✨ Added Loop functionality: infinite carousel scrolling with seamless reset to start/end. 187 * ✨ Added Autoplay functionality: automatic slide progression with configurable delay. 188 * 🎯 Loop keeps navigation buttons visible even at carousel boundaries. 189 * ⏱️ Autoplay respects configured delay before resetting when loop is enabled. 190 * 🎨 Improved scroll detection to ignore scroll-snap micro-adjustments. 191 * 🐛 Fixed premature reset triggers when scrolling towards carousel end. 192 * 🛠️ Enhanced boundary detection for better loop and autoplay behavior. 180 193 181 194 = 1.0.3.2 - 2025-11-13 = … … 221 234 == Upgrade Notice == 222 235 236 = 1.0.4 = 237 Recommended update: adds Loop and Autoplay features for enhanced carousel functionality. Loop enables infinite scrolling, and Autoplay provides automatic slide progression with configurable timing. 238 223 239 = 1.0.3 = 224 240 Recommended update: fixes dynamic arrow style updates in the editor and improves compatibility with Site Editor iframes.
Note: See TracChangeset
for help on using the changeset viewer.