/**
* Palmetto Carousel — progressive enhancement for any `.palmetto-carousel`
* wrapper. The plain markup is `.wp-block-columns` with snap-scrolling
* (CSS-only, works without JS). This script adds:
*
* - Prev / Next arrow buttons (scroll by N cards based on
* --palmetto-carousel-per-view)
* - Dots indicator (one per card group)
* - Active-dot tracking on scroll
* - Edge-aware arrow disabling
*
* Markup contract:
*
*
* Idempotent — re-running init() on the same wrapper is a no-op (guard
* checks `data-palmetto-carousel-enhanced`).
*/
( function () {
'use strict';
function ready( fn ) {
if ( document.readyState !== 'loading' ) { fn(); }
else { document.addEventListener( 'DOMContentLoaded', fn ); }
}
function perView( track ) {
var raw = getComputedStyle( track.parentElement )
.getPropertyValue( '--palmetto-carousel-per-view' )
.trim();
var n = parseInt( raw, 10 );
return ( !isNaN( n ) && n > 0 ) ? n : 1;
}
function init( wrap ) {
if ( wrap.dataset.palmettoCarouselEnhanced === '1' ) { return; }
wrap.dataset.palmettoCarouselEnhanced = '1';
var track = wrap.querySelector( '.wp-block-columns' );
if ( !track ) { return; }
var cards = track.querySelectorAll( '.wp-block-column' );
if ( cards.length < 2 ) { return; }
// --- Arrow buttons ---
var prev = document.createElement( 'button' );
var next = document.createElement( 'button' );
prev.type = 'button';
next.type = 'button';
prev.className = 'palmetto-carousel__arrow palmetto-carousel__arrow--prev';
next.className = 'palmetto-carousel__arrow palmetto-carousel__arrow--next';
prev.setAttribute( 'aria-label', 'Previous' );
next.setAttribute( 'aria-label', 'Next' );
prev.innerHTML = '‹'; // ‹
next.innerHTML = '›'; // ›
wrap.appendChild( prev );
wrap.appendChild( next );
function scrollByCards( direction ) {
var first = cards[0];
if ( !first ) { return; }
var gap = parseFloat( getComputedStyle( track ).columnGap ) || 0;
var step = first.getBoundingClientRect().width + gap;
track.scrollBy( { left: direction * step * perView( track ), behavior: 'smooth' } );
}
prev.addEventListener( 'click', function () { scrollByCards( -1 ); } );
next.addEventListener( 'click', function () { scrollByCards( 1 ); } );
// --- Dots ---
var dots = document.createElement( 'div' );
dots.className = 'palmetto-carousel__dots';
dots.setAttribute( 'role', 'tablist' );
var dotButtons = [];
Array.prototype.forEach.call( cards, function ( card, i ) {
var d = document.createElement( 'button' );
d.type = 'button';
d.className = 'palmetto-carousel__dot';
d.setAttribute( 'role', 'tab' );
d.setAttribute( 'aria-label', 'Go to slide ' + ( i + 1 ) );
d.addEventListener( 'click', function () {
card.scrollIntoView( { behavior: 'smooth', inline: 'start', block: 'nearest' } );
} );
dots.appendChild( d );
dotButtons.push( d );
} );
wrap.appendChild( dots );
// --- Active state on scroll / edge-aware arrow disable ---
function update() {
var first = cards[0];
if ( !first ) { return; }
var cardWidth = first.getBoundingClientRect().width;
var gap = parseFloat( getComputedStyle( track ).columnGap ) || 0;
var index = Math.round( track.scrollLeft / ( cardWidth + gap ) );
dotButtons.forEach( function ( d, i ) {
d.classList.toggle( 'is-active', i === index );
d.setAttribute( 'aria-selected', i === index ? 'true' : 'false' );
} );
var atStart = track.scrollLeft <= 2;
var atEnd = track.scrollLeft + track.clientWidth >= track.scrollWidth - 2;
prev.disabled = atStart;
next.disabled = atEnd;
prev.setAttribute( 'aria-disabled', atStart ? 'true' : 'false' );
next.setAttribute( 'aria-disabled', atEnd ? 'true' : 'false' );
}
track.addEventListener( 'scroll', update, { passive: true } );
window.addEventListener( 'resize', update );
update();
}
function initAll() {
document.querySelectorAll( '.palmetto-carousel' ).forEach( init );
}
ready( initAll );
// Also expose for blocks added dynamically (editor preview, AJAX loads).
window.PalmettoCarousel = { init: init, initAll: initAll };
} )();