PluginProbe
Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor / 2.0.14
Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor v2.0.14
2.0.14 2.0.13 2.0.12 2.0.11 2.0.10 2.0.9 trunk 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7 2.0.8
blockenberg / assets / js / template-carousel-runtime.js

template-carousel-runtime.js in Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor 2.0.14, at assets/js/template-carousel-runtime.js

235 lines 21.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
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