@slidy/solid
Version:
Simple, configurable & reusable carousel component built with SolidJS
77 lines (74 loc) • 15.2 kB
JSX
import{Show as O,For as Xe,mergeProps as Kt,createSignal as Zt,untrack as Ye}from"solid-js";import{mergeProps as ot}from"solid-js";var it={direction:1,step:1,index:0,vertical:!1},st=r=>{let e=ot(it,r),{classNames:t,i18n:o}=P(),s=()=>e.direction<0?e.index===0&&!e.loop:e.index===e.items-1&&!e.loop,g=()=>e.direction>0?o.next:o.prev;return<button aria-label={g()}aria-orientation={e.vertical?"vertical":"horizontal"}classList={{[t.arrow]:!0,prev:e.direction<1}}data-step={e.direction*e.step}disabled={s()}title={g()}>
{e.children}
</button>},Pe=st;import{mergeProps as lt,splitProps as dt,createEffect as ct,onCleanup as pt,onMount as ut}from"solid-js";import{Dynamic as Pt}from"solid-js/web";var{assign:Se,entries:Ve}=Object,{abs:j,exp:qe,floor:Be,min:nt,max:Ee,round:oe,sign:V}=Math;function z(r,e,t){return nt(t,Ee(r,e))}function Ce(r,e=50,t=!0){let o=0;return t?s=>{let g=performance.now();g-o>=e&&(r(s),o=g)}:s=>r(s)}function M(r,e){for(let t=0;t<r.length;t++)e(r[t],t,r);return r}var Ie=(r,e)=>j(r.deltaX)>=j(r.deltaY)&&e.axis!=="y";function We(r,e=0){return new Promise((t,o)=>{let s=setInterval(()=>{e++,e>=69?(clearInterval(s),o("few slides")):r.childElementCount&&(clearInterval(s),t(at(r)))},16)})}function at(r){return M(r.children,(e,t)=>e.index=t)}function ie(r,e,t){let o=r.children.length;return e.loop?(t+o)%o:z(0,t,o-1)}function Y(r,e){if(r.type==="wheel")return Ie(r,e)?r.deltaX:r.shiftKey||e.axis==="y"?r.deltaY:0;{let t=r.touches&&r.touches[0]||r;return e.axis==="y"?t.pageY:t.pageX}}function A(r,e,t){r.dispatchEvent(new CustomEvent(e,{detail:t}))}function _(r,e,t=!0){let o=t?"addEventListener":"removeEventListener";M(e,s=>r[o](...s))}function Ke(r,e){let t=[...r.children],o=t.length,s=o-1,g=Be(o/2),f=o>1?t[1].offsetTop-t[0].offsetTop>=t[0].offsetHeight:!1,a=f?"offsetTop":"offsetLeft",d=f?"offsetHeight":"offsetWidth",u=V(t[s][a]),v=o>1?t[s][a]*u-t[s-1][a]*u-t[s-Ee(u,0)][d]:0,x=t.reduce((n,c)=>n+=c[d]+v,0)>r.offsetWidth,Z=e.snap==="deck";Se(e,{reverse:u,scrollable:x,vertical:f,edged:T()});function T(n){let c=b(u<0?s:0,"start"),m=b(u<0?0:s,"end"),w=b(n),I=e.direction,L=oe(e.position),F=N=>I<=0&&N<=c||I>=0&&N>=m;return e.loop?!1:F(n>=0?w:L)}function b(n,c=e.snap){let m=E=>t.find(k=>k.index===E)||t[0],w=E=>r[d]-m(E)[d],I=N(u<0?s:0,"start"),L=N(u<0?0:s,"end"),F=N(n,c);return e.loop||c==="deck"?F:z(I,F,L);function N(E,k){k=Z?"deck":k;let R=m(E)[d]+v*2<r[d]?e.indent??1:w(E)/2/v,ee=k==="start"?0:k==="end"?1:.5,C=k==="start"?-R:k==="end"?R:0;return m(E)[a]-w(E)*ee+v*C}}return{edges:T,distance:b,index(n){let c=({index:m})=>j(b(m)-n);return t.reduce((m,w)=>c(w)<c(m)?w:m).index},position(n){let c=e.index;if(n){let m=t.slice(c-g).concat(t.slice(0,c-g));r.replaceChildren(...m)}return b(c)},swap(n){let c=o%n?V(-n):n,m=c>0?0:s;return x&&(m?r.prepend(t[m]):r.append(t[m])),(t[m][d]+v)*(c*u)},sense(n,c,m){return n.shiftKey||e.axis==="y"&&n.type!=="touchmove"||j(c)>=m},animate(){M(t,(n,c)=>{n.i=c,n.active=e.loop?g:e.index,n.size=n[d]+v,n.dist=b(n.index),n.track=e.position-n.dist,n.turn=z(-1,n.track/n.size,1),n.exp=z(0,(n.size-j(n.track))/n.size,1);let m=Z?n.dist:e.position,w=f?`translateY(${-m}px)`:`translateX(${-m}px)`,I={node:r,child:n,options:e,translate:w},L=e.animation?.(I)||{transform:w};Se(n.style,x?L:{transform:""})})}}}function Ne(r,e){let t={...e},o,s=0,g=0,f=0,a=0,d=0,u,v,y=s=t.index??=0,x=t.position??=0,Z=t.direction??=0,T=(t.duration??=450)/2,b=t.sensity??=2.5,n=t.gravity??=1.2,c=t.clamp??=0,m=[["touchmove",Oe,{passive:!1}],["mousemove",Oe],["touchend",Fe],["mouseup",Fe],["scroll",()=>{C(y),n=2}]],w=[["wheel",$e,{passive:!1,capture:!0}]],I=[["touchstart",De,{passive:!1}],["mousedown",De],["keydown",Je],["contextmenu",()=>C(y)],["dragstart",i=>i.preventDefault()]],L=new ResizeObserver(i=>{x=t.position=o().position(),C(y),A(r,"resize",{ROE:i,options:t})}),F=new MutationObserver(i=>{M(i,l=>{[...l.addedNodes,...l.removedNodes].every(h=>"index"in h)||_e().then(Q)}),A(r,"mutate",{ML:i,options:t})}),N=requestAnimationFrame,E="outline:0;overflow:hidden;user-select:none;-webkit-user-select:none;",k={init:Q,update:Qe,destroy:_e,to:C};Q(),M(t.plugins||[],(i,l,p)=>{p[l]=i({node:r,options:t,instance:k})});function Q(){We(r).then(()=>{o=()=>Ke(r,t),r.style.cssText+=E,r.onwheel=Ce(je,T,c),x=t.position=o().position(t.loop),L.observe(r),F.observe(r,{childList:!0}),_(r,I),_(window,w),A(r,"mount",{options:t})})}function R(i,l){Z=t.direction=V(i),x=t.position+=p(i),y=t.index=o().index(x),n=o().edges()?1.8:t.gravity,b=0,o().animate(),A(r,"move",{index:y,position:x});function p(h){return y-s&&(h-=t.loop?o().swap(y-s):0,s=y,A(r,"index",{index:l})),h}}function ee(i,l){let h=t.snap||o().edges(i)?o().distance(i):x+l,U=T*z(1,i-s,2),te=h-x;f=N(Re);let re=0,G=0,X=0;function Re(He){re||=He,G=X;let et=re-He,Ue=qe(et/U),tt=t.easing?.(Ue)||Ue;X=te*tt;let rt=G%X?(G-X)%te:0;R(rt,i),oe(X)?f=N(Re):(b=t.sensity,H())}}function C(i=0,l=0){i=ie(r,t,i),H(),ee(i,l||o().distance(i)-x)}function De(i){H(),b=t.sensity,g=Y(i,t),a=i.timeStamp,d=0,_(window,m),!o().edges()&&i.stopPropagation()}function Oe(i){let l=(g-Y(i,t))*(2-n),p=i.timeStamp-a,h=1e3*l/(n+p);a=i.timeStamp,g=Y(i,t),d=(2-n)*h+(n-1)*d,o().sense(i,l,b)&&(R(l,y),i.preventDefault())}function Fe(){H();let i=d*(2-n),l=o().index(x+i);ee(p(l,t),i);function p(h,U){return h=c&&h-s?y+c*Z:h,ie(r,U,h)}}function je(i){H();let l=Y(i,t)*(2-n),p=y+V(l)*(c||1),h=t.snap||u||o().edges(),U=o().sense(i,l,b),te=o().edges()?l/5:l,re=u?p:y,G=u?0:T/2;!u&&U&&R(te,y),v=h&&U?setTimeout(C,G,re):void 0,!o().edges()&&i.stopPropagation()}function $e(i){if(i.composedPath().includes(r)){let l=t.axis==="y"&&!o().edges();(Ie(i,t)||l||i.shiftKey)&&i.preventDefault();let p=c||t.axis==="y"&&!t.vertical||i.shiftKey;u!==p&&(r.onwheel=Ce(je,T,p),u=p)}}function Je(i){let l=["ArrowLeft","ArrowRight","ArrowUp","ArrowDown"],p=(l.indexOf(i.key)%2-1||1)*(c||1);l.indexOf(i.key)>=0&&(C(y+p),i.preventDefault()),A(r,"keys",i.key)}function H(){clearTimeout(v),cancelAnimationFrame(f),_(window,m,!1)}function Qe(i){M(Ve(i),([l,p])=>{if(p!==t[l]){switch(l){case"index":C(y=t[l]=ie(r,t,p));break;case"position":C(y,p);break;case"gravity":n=t[l]=z(0,p,2);break;case"duration":t[l]=p,T=p/2;break;case"sensity":b=t[l]=p;break;case"clamp":c=t[l]=p;break;default:t[l]=p;break}A(r,"update",i)}})}async function _e(){H(),L.disconnect(),F.disconnect(),_(r,I,!1),_(window,w,!1),A(r,"destroy",r)}return k}var q=r=>typeof r=="function",B=(r,...e)=>{for(let t of e)r=r.replace("%s",t.toString());return r},S=(...r)=>e=>r.forEach(t=>t?.(e)),Ze=()=>{};var St={axis:"x",clamp:0,duration:450,easing:r=>r,gravity:1.2,indent:2,index:0,loop:!1,position:0,sensity:5,tag:"ol"},Et=["animation","axis","clamp","duration","easing","gravity","indent","loop","sensity","snap","index","plugins"],Ct=r=>{let e=lt(St,r),t=dt(e,Et)[0],o=s=>{ut(()=>{let{update:f,destroy:a}=Ne(s,{...t});ct(()=>{f(t)}),pt(a)})};return<Pt component={e.tag}class={e.className}aria-live="polite"tabindex="0"ref={o}on:destroy={S(e.onDestroy)}on:index={S(e.onIndex)}on:keys={S(e.onKeys)}on:mount={S(e.onMount)}on:move={S(e.onMove)}on:resize={S(e.onResize)}on:update={S(e.onUpdate)}on:mutate={S(e.onMutate)}>
{e.children}
</Pt>},W=Ct;import{splitProps as It,mergeProps as Nt}from"solid-js";var zt={decoding:"auto",lazy:!1},Mt=r=>{let e=Nt(zt,r),[t,o]=It(e,["lazy","id"]),{classNames:s}=P();return<img{...o}class={s.img}id={t.id}loading={t.lazy?"lazy":void 0}/>},$=Mt;import{mergeProps as At,For as Tt}from"solid-js";var se=(r,e)=>{let t=e-r+1;return[...Array(t).keys()].map(o=>o+r)},Ge=({current:r,start:e=0,end:t,limit:o,siblings:s})=>{if(Math.max(5+s*2,t-e+1)<=o)return se(e,t);let f=Math.max(r-s,e),a=Math.min(r+s,t),d=f>2,u=a<t-1;if(!d&&u)return[...se(e,3+2*s),-1,t];if(d&&!u){let v=3+2*s,y=se(t-v+1,t);return[e,-1,...y]}if(d&&u){let v=se(f,a);return[e,-1,...v,-1,t]}return[]};var D={viewBox:"0 0 32 32",path:"M19.56,24a.89.89,0,0,1-.63-.26L11.8,16.65a.92.92,0,0,1,0-1.27h0l7.13-7.16A.9.9,0,0,1,20.2,9.48L13.69,16l6.51,6.5a.91.91,0,0,1,0,1.26h0A.9.9,0,0,1,19.56,24Z"};var Lt={ordinal:!1,vertical:!1,limit:7,siblings:1},Dt=r=>{let e=At(Lt,r),{i18n:t,classNames:o}=P(),s=a=>a===e.start?t.first:a===e.end?t.last:B(t.slideN,a),g=()=>e.end-e.start+1>e.limit&&!0,f=()=>Ge({current:e.current,start:e.start,end:e.end,limit:e.limit,siblings:e.siblings});return<nav class={o?.nav}aria-orientation={e.vertical?"vertical":"horizontal"}aria-label="pagination">
<button aria-label={t.first}class={o["nav-item"]+" arrow"}data-step={-1}disabled={e.current<=1}title={t.prev}>
<svg viewBox={D.viewBox}>
<path d={D.path}/>
</svg>
</button>
<Tt each={f()}>
{a=>{let d=()=>e.current===a,u=()=>a<0?"\u2026":a,v=()=>a<0,y=s(a);return<button aria-current={d()?"true":void 0}aria-label={y}classList={{[o["nav-item"]]:!0,active:d(),ellipsis:v(),ordinal:g()}}data-index={v()?void 0:a-1}disabled={v()}title={y}>
{g()?u():""}
</button>}}
</Tt>
<button aria-label={t.first}class={o["nav-item"]+" arrow"}data-step={1}disabled={e.current>=e.end}title={t.next}>
<svg viewBox={D.viewBox}>
<path d={D.path}/>
</svg>
</button>
</nav>},Me=Dt;import{mergeProps as Ot}from"solid-js";var Ft={value:0,max:1,vertical:!1,onInput:Ze},jt=r=>{let e=Ot(Ft,r),{classNames:t}=P(),o=()=>Math.ceil(e.value*100/e.max),s=()=>Math.ceil(100/e.max);return<div class={t.progress}aria-orientation={e.vertical?"vertical":"horizontal"}style={{"--_slidy-progress-size":s()+"%","--_slidy-progress":o()+"%"}}>
<input class="slidy-progress-input"type="range"value={e.value}min={1}max={e.max}name="slidy-progress"onInput={e.onInput}/>
<span class={t["progress-handle"]}/>
</div>},Ae=jt;import{mergeProps as _t,For as Rt,Show as Ht}from"solid-js";var Ut={active:0,animation:void 0,axis:"x",background:!1,clamp:0,duration:250,easing:r=>r,getImgSrc:r=>r.src??"",gravity:.75,indent:0,index:0,loop:!1,sensity:5,slides:[],snap:void 0},Vt=r=>{let e=_t(Ut,r),{classNames:t,i18n:o}=P();return<W{...e}tag="nav"className={t?.thumbnails}>
<Rt each={e.slides}>
{(s,g)=>{let f=()=>e.active===g(),a=()=>B(o.slideN,g()+1);return<button type="button"aria-current={f()?"true":void 0}aria-label={a()}title={a()}aria-roledescription="slide"classList={{[t.thumbnail]:!0,active:f(),bg:e.background}}style={{"--_slidy-slide-bg":e.background?`url(${e.getImgSrc?.(s)})`:""}}onClick={()=>e.onSelect?.(g())}>
<Ht when={!e.background}>
<${...s}src={e.getImgSrc?.(s)}/>
</Ht>
</button>}}
</Rt>
</W>},Te=Vt;import{createContext as qt,useContext as Bt}from"solid-js";var K={carousel:"carousel",counter:"%s of %s",first:"Go to the first slide",last:"Go to the last slide",next:"Go to the next slide",play:"Start autoplay",prev:"Return back to previous slide",slide:"slide",slideN:"Go to the slide %s",stop:"Stop autoplay"};var J={arrow:"slidy-arrow",autoplay:"slidy-autoplay",counter:"slidy-counter",img:"slidy-img",nav:"slidy-nav","nav-item":"slidy-nav-item",overlay:"slidy-overlay",progress:"slidy-progress","progress-handle":"slidy-progress-handle",root:"slidy",slide:"slidy-slide",slides:"slidy-slides",thumbnail:"slidy-thumbnail",thumbnails:"slidy-thumbnails"};var Wt={i18n:K,classNames:J},Le=qt(Wt),P=()=>Bt(Le);var Gt={arrows:!0,vertical:!1,background:!1,counter:!0,getImgSrc:r=>r.src??"",getThumbSrc:r=>r.src??"",navigation:!1,loop:!1,groups:0,progress:!1,slides:[],thumbnail:!1,index:()=>0,classNames:J,i18n:K},Xt=r=>{let e=Kt(Gt,r),[t,o]=q(e.setIndex)?[e.index,e.setIndex]:Zt(Ye(e.index)),s=()=>e.slides.length,g=a=>{let d=a.target;if(d.nodeName==="BUTTON"){if(d.dataset.index){o(parseInt(d.dataset.index));return}if(d.dataset.step){o(parseInt(d.dataset.step)+Ye(t));return}}},f=a=>{Promise.resolve(a.detail.index).then(o)};return<Le.Provider value={{classNames:e.classNames,i18n:e.i18n}}>
<section aria-roledescription={e.i18n.carousel}aria-orientation={e.vertical?"vertical":"horizontal"}classList={{[e.classNames&&e.classNames.root]:!0,groups:e.groups>1}}style={{"--slidy-group-items":e.groups}}id={e.id}onClick={g}>
<O when={e.counter||e.overlay}>
<div class={e.classNames?.overlay}>
<O when={e.counter}>
<output class={e.classNames?.counter}>
{t()+1} / {s()}
</output>
</O>
{q(e.overlay)&&e.overlay()}
</div>
</O>
<W animation={e.animation}axis={e.axis}clamp={e.clamp}className={e.classNames?.slides}duration={e.duration}easing={e.easing}gravity={e.gravity}indent={e.indent}index={t()}loop={e.loop}sensity={e.sensity}snap={e.snap}plugins={e.plugins}onResize={e.onResize}onMount={e.onMount}onMove={e.onMove}onIndex={S(f,e.onIndex)}onKeys={e.onKeys}onUpdate={e.onUpdate}onDestroy={e.onDestroy}onMutate={e.onMutate}>
<Xe each={e.slides}>
{(a,d)=>{let u=()=>t()===d();return e.children?e.children(a):<li aria-current={u()?"true":void 0}aria-label={B(e.i18n.counter,d()+1,s())}aria-roledescription={e.i18n.slide}classList={{[e.classNames&&e.classNames.slide]:!0,active:u(),bg:e.background}}role="group"style={{"--_slidy-slide-bg":e.background?`url("${e.getImgSrc(a)}")`:void 0}}>
<O when={!e.background}>
<${...a}src={e.getImgSrc(a)}/>
</O>
</li>}}
</Xe>
</W>
<O when={e.arrows===!0}fallback={q(e.arrows)&&e.arrows()}>
<Xe each={[-1,1]}>
{a=><Pe direction={a}index={t()}items={s()}loop={e.loop}step={e.clamp>0?e.clamp:1}vertical={e.vertical}>
<O when={!e.arrow}fallback={q(e.arrow)&&e.arrow()}>
<svg class="slidy-arrow-icon"viewBox={D.viewBox}>
<path d={D.path}/>
</svg>
</O>
</Pe>}
</Xe>
</O>
<O when={e.progress}>
<Ae value={t()+1}max={s()}vertical={e.vertical}onInput={a=>{o(a.currentTarget.valueAsNumber-1)}}/>
</O>
<O when={e.thumbnail===!0}fallback={q(e.thumbnail)&&e.thumbnail()}>
<Te active={t()}background={e.background}duration={e.duration}easing={e.easing}getImgSrc={e.getThumbSrc}indent={e.indent}index={t()}loop={e.loop}sensity={e.sensity}slides={e.slides}onSelect={o}/>
</O>
<O when={e.navigation}>
<Me current={t()+1}start={1}end={s()}vertical={e.vertical}/>
</O>
</section>
</Le.Provider>};var Yt=Xt;export{W as Core,Yt as Slidy,J as classNames,K as i18nDefaults};