| 1 |
/** Shared template carousel runtime: frontend and Gutenberg use the same layout, pagination and transitions. */ |
| 2 |
(function () { |
| 3 |
'use strict'; |
| 4 |
const instances = new WeakMap(); |
| 5 |
const selector = '[data-bkbg-component]'; |
| 6 |
const number = (v, fallback) => v !== '' && v != null && Number.isFinite(Number(v)) ? Number(v) : fallback; |
| 7 |
const clamp = (v, min, max) => Math.max(min, Math.min(max, v)); |
| 8 |
const bool = (v, fallback) => v == null ? fallback : v === true || v === 'true' || v === 'yes' || v === 1 || v === '1'; |
| 9 |
const all = (root, query) => Array.from(root.querySelectorAll(query)).filter(el => el.closest(selector) === root); |
| 10 |
const one = (root, query) => all(root, query)[0]; |
| 11 |
function carousel(root, input, environment = {}) { |
| 12 |
instances.get(root)?.destroy(); |
| 13 |
const document = root.ownerDocument, window = document.defaultView; |
| 14 |
const getComputedStyle = window.getComputedStyle.bind(window); |
| 15 |
const requestAnimationFrame = window.requestAnimationFrame.bind(window); |
| 16 |
const motion = window.matchMedia('(prefers-reduced-motion: reduce)'); |
| 17 |
const controllers = [], disposers = []; |
| 18 |
let alive = true; |
| 19 |
function listen(target, type, handler, options) { |
| 20 |
target.addEventListener(type, handler, options); |
| 21 |
disposers.push(() => target.removeEventListener(type, handler, options)); |
| 22 |
} |
| 23 |
function button(el, label, activate) { |
| 24 |
if (!el.matches('button,input,a[href],summary')) { el.setAttribute('role', 'button'); el.tabIndex = 0; } |
| 25 |
if (el.matches('button')) el.type = 'button'; |
| 26 |
if (label && !el.hasAttribute('aria-label')) el.setAttribute('aria-label', label); |
| 27 |
listen(el, 'click', e => { e.preventDefault(); activate(e); }); |
| 28 |
if (!el.matches('button,input,summary')) listen(el, 'keydown', e => { |
| 29 |
if (e.target === el && (e.key === 'Enter' || e.key === ' ')) { e.preventDefault(); activate(e); } |
| 30 |
}); |
| 31 |
} |
| 32 |
all(root, '.swiper-wrapper').forEach((track, trackIndex) => { |
| 33 |
const viewport = track.parentElement; |
| 34 |
const initialIndex = environment.initialIndices?.[trackIndex] || 0; |
| 35 |
const opts = Object.assign({}, input), source = opts.sourceSettings || {}; |
| 36 |
const aliases = {autoplay:['autoplay'], delay:['autoplay_speed','autoplay_delay'], loop:['infinite','loop'], speed:['speed','transition_speed'], carouselSlides:['slides_to_show','slides_per_view'], carouselSlidesTablet:['slides_to_show_tablet','slides_per_view_tablet'], carouselSlidesMobile:['slides_to_show_mobile','slides_per_view_mobile']}; |
| 37 |
Object.keys(aliases).forEach(key => {if(opts[key]==null){const name=aliases[key].find(k=>source[k]!=null&&source[k]!=='');if(name)opts[key]=source[name];}}); |
| 38 |
const restored = []; |
| 39 |
// React owns original slides. Restore only attributes this runtime changes. |
| 40 |
if (environment.editor) { |
| 41 |
[viewport, track, ...root.querySelectorAll('.swiper-slide,.swiper-pagination,.bkbg-catalog-swiper-pagination,.swiper-button-prev,.swiper-button-next,.bkbg-tpl-swiper-button-prev,.bkbg-tpl-swiper-button-next,[class*="-nav-prev"],[class*="-nav-next"],.bkbg-catalog-button-prev,.bkbg-catalog-button-next')].forEach(node => { |
| 42 |
const names=['style','class','tabindex','role','aria-label','aria-roledescription','aria-hidden','inert','aria-current','aria-disabled','data-bkbg-carousel-index','data-bkbg-carousel-pages','data-bkbg-carousel-config']; |
| 43 |
const values=names.map(name=>[name,node.getAttribute(name)]); |
| 44 |
restored.push(()=>values.forEach(([name,value])=>{if(value===null)node.removeAttribute(name);else node.setAttribute(name,value);})); |
| 45 |
}); |
| 46 |
} |
| 47 |
const sourceKind = root.dataset.bkbgKind || ''; |
| 48 |
// Older imported blocks retain the original settings, even when their |
| 49 |
// first conversion omitted zero delay or used the wrong hover default. |
| 50 |
if (opts.carouselRuntimeVersion !== 2) { |
| 51 |
if (sourceKind === 'image-carousel') { |
| 52 |
opts.delay = number(source.autoplay_speed, number(opts.delay, 5000)); |
| 53 |
opts.pauseOnHover = bool(source.pause_on_hover, true); |
| 54 |
opts.disableOnInteraction = bool(source.pause_on_interaction, true); |
| 55 |
} else if (sourceKind.startsWith('king-addons-') && !viewport.dataset.slidestoshow) { |
| 56 |
opts.pauseOnHover = Object.entries(source).some(([k,v]) => /pause.*(?:hover|mouseenter)/i.test(k) && bool(v, false)); |
| 57 |
opts.disableOnInteraction = false; |
| 58 |
opts.reverseDirection = Object.entries(source).some(([k,v]) => /autoplay_reversedirection_switcher$/.test(k) && bool(v, false)); |
| 59 |
} |
| 60 |
} |
| 61 |
if (viewport.dataset.slidestoshow) { |
| 62 |
const fallback={carouselSlides:number(viewport.dataset.slidestoshow,3),carouselSlidesTablet:2,carouselSlidesMobile:1,carouselGap:number(viewport.dataset.swiperSpaceBetween,0),speed:number(viewport.dataset.swiperSpeed,500),delay:number(viewport.dataset.swiperDelay,5000),autoplay:bool(viewport.dataset.autoplay,false),loop:bool(viewport.dataset.loop,false),pauseOnHover:bool(viewport.dataset.swiperPoh,false)}; |
| 63 |
fallback.carouselGapTablet=fallback.carouselGapMobile=fallback.carouselGap; |
| 64 |
Object.keys(fallback).forEach(key=>{if(opts[key]==null)opts[key]=fallback[key];}); |
| 65 |
} |
| 66 |
const outer = viewport.closest('.bkbg-catalog-timeline-outer-container'); if (outer) outer.style.opacity='1'; |
| 67 |
const slides = Array.from(track.children).filter(s => s.classList.contains('swiper-slide') && !s.classList.contains('swiper-slide-duplicate') && !s.classList.contains('bkbg-carousel-repeat')); |
| 68 |
if (!slides.length) return; |
| 69 |
Array.from(track.children).filter(s => s.classList.contains('swiper-slide-duplicate')).forEach(s => {if(environment.editor)s.style.display='none';else s.remove();}); |
| 70 |
const loop = bool(opts.loop, false), delay = clamp(number(opts.delay,5000),0,3600000), speed=clamp(number(opts.speed,400),0,60000); |
| 71 |
const autoplay = bool(opts.autoplay,false), group=Math.max(1,Math.floor(number(opts.carouselGroup,1))); |
| 72 |
const easing = getComputedStyle(track).transitionTimingFunction === 'linear' || Object.entries(source).some(([k,v]) => /autoplay_like_ticker_switcher$/.test(k)&&bool(v,false)) || delay<=10 ? 'linear' : 'ease'; |
| 73 |
let perView=1,gap=0,slideWidth=0,snaps=[0],index=0,physical=0,prefix=0,clones=[]; |
| 74 |
let timer,finishTimer,transitioning=false,hover=false,focused=false,interacted=false,startX=null,suppressClick=false,revision=0; |
| 75 |
const prev = all(root,'.swiper-button-prev,.bkbg-tpl-swiper-button-prev,[class*="-nav-prev"],.bkbg-catalog-button-prev'); |
| 76 |
const next = all(root,'.swiper-button-next,.bkbg-tpl-swiper-button-next,[class*="-nav-next"],.bkbg-catalog-button-next'); |
| 77 |
const pagination = one(root,'.swiper-pagination,.bkbg-catalog-swiper-pagination'); |
| 78 |
const paginationType = opts.paginationType || (viewport.dataset.slidestoshow || Object.entries(source).some(([k,v])=>k.endsWith('_pag_type')&&v==='progressbar') ? 'progressbar' : 'bullets'); |
| 79 |
let progress=null,dots=[]; |
| 80 |
const originalPagination = environment.editor && pagination ? Array.from(pagination.childNodes) : []; |
| 81 |
let observer; |
| 82 |
const controller = {go: value => go(value,true), next:()=>go(index+1,true), previous:()=>go(index-1,true), refresh:()=>resizeCarousel(), reveal:slideIndex=>{ |
| 83 |
const page=snaps.reduce((best,start,i)=>start<=slideIndex?i:best,0); |
| 84 |
go(page,true,true);return slides[slideIndex]; |
| 85 |
}, state:()=>({index,perView,gap,speed,delay,loop,autoplay,pages:snaps.length,count:slides.length,paginationType})}; |
| 86 |
controllers.push(controller); |
| 87 |
viewport.classList.add('swiper-initialized','swiper-horizontal'); |
| 88 |
viewport.style.overflow='hidden';viewport.style.touchAction='pan-y'; |
| 89 |
if (!viewport.hasAttribute('tabindex')) viewport.tabIndex=0; |
| 90 |
viewport.setAttribute('aria-roledescription','carousel'); |
| 91 |
track.style.display='flex';track.style.flexWrap='nowrap';track.style.width='100%';track.style.gap='0px'; |
| 92 |
function sizing() { |
| 93 |
const width=window.innerWidth; |
| 94 |
const count=width<=767?number(opts.carouselSlidesMobile,1):width<=1024?number(opts.carouselSlidesTablet,number(opts.carouselSlides,1)):number(opts.carouselSlides,1); |
| 95 |
perView=clamp(count,1,100); |
| 96 |
gap=Math.max(0,number(width<=767?opts.carouselGapMobile:width<=1024?opts.carouselGapTablet:opts.carouselGap,number(opts.carouselGap,0))); |
| 97 |
const st=getComputedStyle(viewport),w=viewport.clientWidth-(parseFloat(st.paddingLeft)||0)-(parseFloat(st.paddingRight)||0); |
| 98 |
slideWidth=Math.max(1,(w-(perView-1)*gap)/perView); |
| 99 |
const end=loop?slides.length-1:Math.max(0,slides.length-perView); |
| 100 |
snaps=[];for(let i=0;i<=end+.00001;i+=group)snaps.push(i); |
| 101 |
if(!loop&&Math.abs(snaps[snaps.length-1]-end)>.00001)snaps.push(end); |
| 102 |
} |
| 103 |
function sizeSlide(s) {s.style.flex='0 0 '+slideWidth+'px';s.style.width=slideWidth+'px';s.style.marginRight=gap+'px';s.style.boxSizing='border-box';} |
| 104 |
function cloneSlide(s,n) { |
| 105 |
const clone=s.cloneNode(true);clone.classList.remove('swiper-slide-active','swiper-slide-next','swiper-slide-prev');clone.classList.add('bkbg-carousel-repeat');clone.setAttribute('aria-hidden','true');clone.inert=true; |
| 106 |
if(environment.editor){ |
| 107 |
clone.dataset.bkbgEditorOriginal=String(slides.indexOf(s)); |
| 108 |
clone.querySelectorAll('[contenteditable]').forEach(node=>{node.setAttribute('contenteditable','false');node.setAttribute('aria-readonly','true');}); |
| 109 |
clone.querySelectorAll('input,textarea,select,button,a').forEach(node=>node.tabIndex=-1); |
| 110 |
} |
| 111 |
const descendants=[clone,...clone.querySelectorAll('*')],ids=new Map(); |
| 112 |
descendants.forEach(e=>{if(e.id){const old=e.id;e.id=old+'-repeat-'+n;ids.set(old,e.id);}e.removeAttribute('data-bkbg-id');e.removeAttribute('data-bkbg-component');}); |
| 113 |
descendants.forEach(e=>Array.from(e.attributes).forEach(a=>{let value=a.value;ids.forEach((id,old)=>{value=value.replaceAll('url(#'+old+')','url(#'+id+')');if(value==='#'+old)value='#'+id;if(['for','aria-controls','aria-labelledby','aria-describedby'].includes(a.name))value=value.split(/\s+/).map(v=>v===old?id:v).join(' ');});if(value!==a.value)e.setAttribute(a.name,value);})); |
| 114 |
sizeSlide(clone);return clone; |
| 115 |
} |
| 116 |
function repeatSlides() { |
| 117 |
clones.forEach(s=>s.remove());clones=[];prefix=0; |
| 118 |
if(!loop)return; |
| 119 |
prefix=Math.ceil(perView)+group; |
| 120 |
const before=document.createDocumentFragment(),after=document.createDocumentFragment(); |
| 121 |
for(let i=0;i<prefix;i++){ |
| 122 |
const left=cloneSlide(slides[((slides.length-prefix+i)%slides.length+slides.length)%slides.length],'before-'+i),right=cloneSlide(slides[i%slides.length],'after-'+i); |
| 123 |
before.append(left);after.append(right);clones.push(left,right); |
| 124 |
} |
| 125 |
track.prepend(before);track.append(after); |
| 126 |
} |
| 127 |
function buildPagination() { |
| 128 |
if(!pagination)return; |
| 129 |
pagination.replaceChildren();dots=[];progress=null; |
| 130 |
pagination.classList.remove('swiper-pagination-bullets','swiper-pagination-progressbar'); |
| 131 |
pagination.classList.add('swiper-pagination-horizontal',paginationType==='progressbar'?'swiper-pagination-progressbar':'swiper-pagination-bullets'); |
| 132 |
if(paginationType==='progressbar'){progress=document.createElement('span');progress.className='swiper-pagination-progressbar-fill';pagination.append(progress);} |
| 133 |
else snaps.forEach((_,i)=>{const dot=document.createElement('button');dot.type='button';dot.className='swiper-pagination-bullet';dot.setAttribute('aria-label','Go to slide '+(i*group+1));dot.addEventListener('click',()=>go(i,true));pagination.append(dot);dots.push(dot);}); |
| 134 |
} |
| 135 |
function paint() { |
| 136 |
const position=snaps[index],first=Math.floor(position); |
| 137 |
environment.onChange?.(trackIndex,index); |
| 138 |
slides.forEach((s,i)=>{const visible=prefix+i+1>prefix+physical+.00001&&prefix+i<prefix+physical+perView-.00001;s.classList.toggle('swiper-slide-active',i===first);s.classList.toggle('swiper-slide-prev',i===(first-1+slides.length)%slides.length);s.classList.toggle('swiper-slide-next',i===(first+1)%slides.length);s.inert=!visible;s.setAttribute('aria-hidden',String(!visible));}); |
| 139 |
clones.forEach(s=>{const i=Array.prototype.indexOf.call(track.children,s),visible=i+1>prefix+physical+.00001&&i<prefix+physical+perView-.00001;s.inert=!visible;s.setAttribute('aria-hidden',String(!visible));}); |
| 140 |
if(progress){progress.style.transition=motion.matches?'none':'transform '+speed+'ms '+easing;progress.style.transform='scaleX('+((index+1)/snaps.length)+')';} |
| 141 |
dots.forEach((d,i)=>{d.classList.toggle('swiper-pagination-bullet-active',i===index);d.setAttribute('aria-current',i===index?'true':'false');}); |
| 142 |
prev.forEach(b=>{b.setAttribute('aria-disabled',String(!loop&&index===0));b.classList.toggle('swiper-button-disabled',!loop&&index===0);}); |
| 143 |
next.forEach(b=>{b.setAttribute('aria-disabled',String(!loop&&index===snaps.length-1));b.classList.toggle('swiper-button-disabled',!loop&&index===snaps.length-1);}); |
| 144 |
viewport.dataset.bkbgCarouselIndex=String(index);viewport.dataset.bkbgCarouselPages=String(snaps.length); |
| 145 |
viewport.dataset.bkbgCarouselConfig=JSON.stringify({perView,gap,speed,delay,loop,autoplay,pauseOnHover:bool(opts.pauseOnHover,true),disableOnInteraction:bool(opts.disableOnInteraction,false),reverseDirection:bool(opts.reverseDirection,false),paginationType:pagination?paginationType:'none',stopOnLastSlide:bool(opts.stopOnLastSlide,false),easing}); |
| 146 |
} |
| 147 |
function translate(pos,animate) { |
| 148 |
const rtl=getComputedStyle(viewport).direction==='rtl'; |
| 149 |
track.style.transition=animate&&!motion.matches?'transform '+speed+'ms '+easing:'none'; |
| 150 |
track.style.transform='translateX('+((rtl?1:-1)*(prefix+pos)*(slideWidth+gap))+'px)'; |
| 151 |
} |
| 152 |
function schedule() { |
| 153 |
clearTimeout(timer); |
| 154 |
if(!alive||environment.paused?.()||!root.isConnected||!autoplay||motion.matches||hover||focused||interacted||document.hidden||snaps.length<2||transitioning||(!loop&&bool(opts.stopOnLastSlide,false)&&(bool(opts.reverseDirection,false)?index===0:index===snaps.length-1)))return; |
| 155 |
timer=setTimeout(()=>{let target=index+(bool(opts.reverseDirection,false)?-1:1);if(!loop&&(target<0||target>=snaps.length))target=target<0?snaps.length-1:0;go(target,false);},delay); |
| 156 |
} |
| 157 |
function finishTransition() { |
| 158 |
if(!transitioning)return;transitioning=false;clearTimeout(finishTimer); |
| 159 |
if(physical!==snaps[index]){physical=snaps[index];translate(physical,false);paint(); |
| 160 |
// Commit the equivalent loop position before a zero-delay |
| 161 |
// ticker starts its next transition. Otherwise CSS can |
| 162 |
// merge both transforms into a long backward jump. |
| 163 |
void track.offsetWidth; |
| 164 |
} |
| 165 |
schedule(); |
| 166 |
} |
| 167 |
function go(value,manual,immediate=false) { |
| 168 |
clearTimeout(timer);clearTimeout(finishTimer); |
| 169 |
if(manual&&bool(opts.disableOnInteraction,false))interacted=true; |
| 170 |
const old=index,newIndex=loop?((value%snaps.length)+snaps.length)%snaps.length:clamp(value,0,snaps.length-1); |
| 171 |
const changed=old!==newIndex; |
| 172 |
index=newIndex;physical=snaps[index]; |
| 173 |
if(loop&&changed){if(value>=snaps.length)physical+=slides.length;else if(value<0)physical-=slides.length;} |
| 174 |
transitioning=!immediate&&changed&&speed>0&&!motion.matches; |
| 175 |
if(!transitioning)physical=snaps[index]; |
| 176 |
paint();translate(physical,transitioning); |
| 177 |
if(transitioning)finishTimer=setTimeout(finishTransition,speed+34);else schedule(); |
| 178 |
} |
| 179 |
listen(track,'transitionend',e=>{if(e.target===track&&e.propertyName==='transform')finishTransition();}); |
| 180 |
prev.forEach(b=>button(b,'Previous slide',()=>go(index-1,true)));next.forEach(b=>button(b,'Next slide',()=>go(index+1,true))); |
| 181 |
listen(viewport,'keydown',e=>{ |
| 182 |
if(e.target.matches('input,textarea,select')||e.target.isContentEditable||e.target.closest('[contenteditable="true"]'))return; |
| 183 |
const rtl=getComputedStyle(viewport).direction==='rtl'; |
| 184 |
if(e.key==='Home'||e.key==='End'){e.preventDefault();go(e.key==='Home'?0:snaps.length-1,true);return;} |
| 185 |
if(e.key==='ArrowRight'||e.key==='ArrowLeft'){e.preventDefault();go(index+((e.key==='ArrowRight')!==rtl?1:-1),true);} |
| 186 |
}); |
| 187 |
listen(viewport,'pointerdown',e=>{if(!e.isPrimary||e.button!==0||e.target.closest('button,input,textarea,select,a,[role="button"],[role="textbox"],[contenteditable="true"]')||(environment.editor&&e.target.closest('img')))return;suppressClick=false;startX=e.clientX;if(!environment.editor)viewport.setPointerCapture(e.pointerId);}); |
| 188 |
listen(viewport,'pointerup',e=>{if(startX===null)return;const d=e.clientX-startX;startX=null;if(Math.abs(d)>40){suppressClick=true;go(index+((d<0)!==(getComputedStyle(viewport).direction==='rtl')?1:-1),true);setTimeout(()=>{suppressClick=false;},0);}}); |
| 189 |
listen(viewport,'pointercancel',()=>{startX=null;}); |
| 190 |
listen(viewport,'click',e=>{if(suppressClick){e.preventDefault();e.stopPropagation();}},{capture:true}); |
| 191 |
listen(root,'pointerenter',()=>{hover=bool(opts.pauseOnHover,true);schedule();});listen(root,'pointerleave',()=>{hover=false;schedule();}); |
| 192 |
listen(root,'focusin',()=>{focused=true;schedule();});listen(root,'focusout',e=>{focused=root.contains(e.relatedTarget);schedule();}); |
| 193 |
listen(document,'visibilitychange',schedule);listen(motion,'change',()=>{clearTimeout(finishTimer);transitioning=false;physical=snaps[index];paint();translate(physical,false);schedule();}); |
| 194 |
function resizeCarousel() { |
| 195 |
const current=snaps[index]||0,token=++revision; |
| 196 |
clearTimeout(timer);clearTimeout(finishTimer);transitioning=false; |
| 197 |
sizing();slides.forEach(sizeSlide);repeatSlides();buildPagination(); |
| 198 |
index=snaps.reduce((best,n,i)=>Math.abs(n-current)<Math.abs(snaps[best]-current)?i:best,0);physical=snaps[index];paint();translate(physical,false); |
| 199 |
// A first CSS transition needs a committed non-animated position. |
| 200 |
requestAnimationFrame(()=>requestAnimationFrame(()=>{if(token===revision)schedule();})); |
| 201 |
} |
| 202 |
slides.forEach((s,i)=>{s.setAttribute('role','group');s.setAttribute('aria-label',(i+1)+' of '+slides.length);}); |
| 203 |
listen(window,'resize',resizeCarousel); |
| 204 |
if(window.ResizeObserver){let observedWidth=viewport.clientWidth;observer=new window.ResizeObserver(()=>{const width=viewport.clientWidth;if(Math.abs(width-observedWidth)>.1){observedWidth=width;resizeCarousel();}});observer.observe(viewport);} |
| 205 |
if(environment.editor) listen(root,'click',event=>{ |
| 206 |
const repeat=event.target.closest('.bkbg-carousel-repeat'); |
| 207 |
if(!repeat||repeat.parentElement!==track)return; |
| 208 |
event.preventDefault();event.stopPropagation(); |
| 209 |
const field=(event.target.closest('[data-bkbg-editor-field]')||event.target.querySelector('[data-bkbg-editor-field]'))?.dataset.bkbgEditorField; |
| 210 |
const original=controller.reveal(Number(repeat.dataset.bkbgEditorOriginal)); |
| 211 |
const editable=field?Array.from(original.querySelectorAll('[data-bkbg-editor-field]')).find(n=>n.dataset.bkbgEditorField===field&&n.isContentEditable):null; |
| 212 |
if(editable?.isContentEditable)editable.focus();else original.closest('[data-block]')?.focus(); |
| 213 |
},{capture:true}); |
| 214 |
document.fonts?.ready.then(()=>{if(alive)resizeCarousel();}); |
| 215 |
disposers.push(()=>{ |
| 216 |
clearTimeout(timer);clearTimeout(finishTimer);revision++; |
| 217 |
observer?.disconnect();clones.forEach(s=>s.remove()); |
| 218 |
if(environment.editor&&pagination)pagination.replaceChildren(...originalPagination); |
| 219 |
restored.reverse().forEach(restore=>restore()); |
| 220 |
}); |
| 221 |
resizeCarousel(); |
| 222 |
if(initialIndex)go(clamp(initialIndex,0,snaps.length-1),false,true); |
| 223 |
}); |
| 224 |
|
| 225 |
const instance = {controllers, destroy(){ |
| 226 |
if(!alive)return;alive=false; |
| 227 |
disposers.reverse().forEach(dispose=>dispose()); |
| 228 |
if(instances.get(root)===instance)instances.delete(root); |
| 229 |
}}; |
| 230 |
instances.set(root,instance); |
| 231 |
return instance; |
| 232 |
} |
| 233 |
window.bkbgTemplateCarousel = carousel; |
| 234 |
})(); |
| 235 |
|