coverflow-carousel 0.2.0-dev.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -77,6 +77,12 @@ initCoverflowCarousels({
77
77
  // - CSSStyleSheet (constructable stylesheet)
78
78
  // - string (e.g. imported via ?raw)
79
79
  // stylesheet: coverflowCarouselCssText,
80
+ //
81
+ // Optional overrides (applied on top of the base stylesheet):
82
+ // styleOverrides: `
83
+ // .cfc { --cfc-card-ar-num: 1.6; }
84
+ // .cfc__arrow { background: Black; }
85
+ // `,
80
86
  });
81
87
  ```
82
88
 
@@ -84,11 +90,13 @@ initCoverflowCarousels({
84
90
  ```javascript
85
91
  import { registerCoverflowCarouselElement, initCoverflowCarousels } from 'coverflow-carousel';
86
92
  import MyCfcCss from './assets/cfc.custom.css?raw';
93
+ import MyCfcOverrides from './assets/cfc.overrides.css?raw';
87
94
 
88
95
  registerCoverflowCarouselElement();
89
96
 
90
97
  initCoverflowCarousels({
91
98
  stylesheet: MyCfcCss,
99
+ styleOverrides: MyCfcOverrides,
92
100
  });
93
101
  ```
94
102
 
@@ -107,6 +115,13 @@ initCoverflowCarousels({
107
115
  });
108
116
  ```
109
117
 
118
+ <sub>CSS: override CSS variables (recommended for simple tweaks)</sub>
119
+ ```css
120
+ coverflow-carousel {
121
+ --cfc-card-ar-num: 1.6;
122
+ }
123
+ ```
124
+
110
125
  <sub>JS: manual control (prev/next/goTo/refresh)</sub>
111
126
  ```javascript
112
127
  import { registerCoverflowCarouselElement } from 'coverflow-carousel';
@@ -126,36 +141,37 @@ el?.refresh();
126
141
  <br>
127
142
 
128
143
  # Options
129
- | Option (attribute) | Type | Default | Description |
130
- |:--------------------:|:-----------------------:|:------------:|:---------------------------------------------------------------------------------------------------------------------------------------------------------|
131
- | `start-index` | `number` | `0` | Start index if `index` is not set. |
132
- | `index` | `number` | | Current index. If you update it externally, the carousel will animate to it. The component also reflects the current value back into `index`. |
133
- | `show-dots` | `boolean` | `false` | Shows pagination dots (decorative, non-interactive). |
134
- | `show-arrows` | `boolean` | `false` | Shows Prev/Next arrows. |
135
- | `announce-changes` | `boolean` | `true` | Enables slide-change announcements via live-region (a11y). |
144
+ | Option (attribute) | Type | Default | Description |
145
+ |:------------------:|:---------:|:-------:|:-----------------------------------------------------------------------------------------------------------------------------------------------|
146
+ | `start-index` | `number` | `0` | Start index if `index` is not set. |
147
+ | `index` | `number` | | Current index. If you update it externally, the carousel will animate to it. The component also reflects the current value back into `index`. |
148
+ | `show-dots` | `boolean` | `false` | Shows pagination dots (decorative, non-interactive). |
149
+ | `show-arrows` | `boolean` | `false` | Shows Prev/Next arrows. |
150
+ | `announce-changes` | `boolean` | `true` | Enables slide-change announcements via live-region (a11y). |
136
151
 
137
152
  <br>
138
153
 
139
154
  # Events
140
- | Event | Detail | Description |
141
- |-------------------|------------------------------|--------------------------------------------------------------------------------------------------|
142
- | `coverflow-carousel:ready` | `{ index: number, length: number }` | Fired after the first `refresh()` (when slides are built and layout/a11y is applied). |
143
- | `coverflow-carousel:change` | `{ index: number, length: number }` | Fired on active slide change via `prev/next/goTo` (and when `index` is updated externally). |
144
- | `coverflow-carousel:scratch-complete` | `{ index: number, length: number, percent: number }` | Fired when a descendant `<scratch-reveal>` dispatches `complete` for a slide. |
155
+ | Event | Detail | Description |
156
+ |---------------------------------------|------------------------------------------------------|---------------------------------------------------------------------------------------------|
157
+ | `coverflow-carousel:ready` | `{ index: number, length: number }` | Fired after the first `refresh()` (when slides are built and layout/a11y is applied). |
158
+ | `coverflow-carousel:change` | `{ index: number, length: number }` | Fired on active slide change via `prev/next/goTo` (and when `index` is updated externally). |
159
+ | `coverflow-carousel:scratch-complete` | `{ index: number, length: number, percent: number }` | Fired when a descendant `<scratch-reveal>` dispatches `complete` for a slide. |
145
160
 
146
161
  <br>
147
162
 
148
163
  # API Methods
149
- | Method | Description |
150
- |-------------------|--------------------------------------------------------------------------------------------------|
151
- | `initCoverflowCarousels({ selector?, onReady?, onChange?, onScratchComplete?, stylesheet? }): HTMLElement[]` | Finds elements by `selector` (default: `coverflow-carousel`), optionally subscribes to `ready/change/scratch-complete` events, and optionally applies styles to each element (`stylesheet?: CSSStyleSheet \| string \| null`). |
152
- | `prev(): void` | Go to the previous slide (circular). |
153
- | `next(): void` | Go to the next slide (circular). |
154
- | `goTo(index: number): void` | Go to the given index (circular normalization). While animating, repeated transitions are ignored. |
155
- | `refresh(): void` | Rebuilds cards from current `children` (useful after dynamic changes) and dispatches `coverflow-carousel:ready`. |
156
- | `destroy(): void` | Removes event handlers and cancels animation-related timers. |
157
- | `adoptStylesheet(sheet: CSSStyleSheet): void` | Applies a stylesheet via `adoptedStyleSheets` (when supported). |
158
- | `adoptStyles(styles: CSSStyleSheet \| string \| null): void` | Applies styles: string via `adoptedStyleSheets` (when possible) or `<style>` fallback in the shadow root; `null` restores the package default styles. |
164
+ | Method | Description |
165
+ |-------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
166
+ | `initCoverflowCarousels({ selector?, onReady?, onChange?, onScratchComplete?, stylesheet?, styleOverrides? }): HTMLElement[]` | Finds elements by `selector` (default: `coverflow-carousel`), optionally subscribes to `ready/change/scratch-complete` events, and optionally applies styles to each element (`stylesheet?: CSSStyleSheet \| string \| null`, `styleOverrides?: CSSStyleSheet \| string \| null`). |
167
+ | `prev(): void` | Go to the previous slide (circular). |
168
+ | `next(): void` | Go to the next slide (circular). |
169
+ | `goTo(index: number): void` | Go to the given index (circular normalization). While animating, repeated transitions are ignored. |
170
+ | `refresh(): void` | Rebuilds cards from current `children` (useful after dynamic changes) and dispatches `coverflow-carousel:ready`. |
171
+ | `destroy(): void` | Removes event handlers and cancels animation-related timers. |
172
+ | `adoptStylesheet(sheet: CSSStyleSheet): void` | Applies a stylesheet via `adoptedStyleSheets` (when supported). |
173
+ | `adoptStyles(styles: CSSStyleSheet \| string \| null): void` | Applies styles: string via `adoptedStyleSheets` (when possible) or `<style>` fallback in the shadow root; `null` restores the package default styles. |
174
+ | `adoptStyleOverrides(styles: CSSStyleSheet \| string \| null): void` | Applies an additional "overlay" stylesheet on top of the current base styles (useful for small tweaks like CSS variables). |
159
175
 
160
176
  <br>
161
177
 
@@ -1 +1 @@
1
- :host{width:100%;height:100%;display:block;overflow:hidden;container-type:inline-size}.cfc{--cfc-card-ar-num:1.25;--cfc-card-w:clamp(160px,100vw - 120px,380px);--cfc-card-ar:1/var(--cfc-card-ar-num);--cfc-step-x:min(22vw,250px);--cfc-step-z:calc(var(--cfc-card-w)*.5);--cfc-scale-step:.1;--cfc-transition-ms:.55s;--cfc-easing:cubic-bezier(.785,.135,.15,.86);--cfc-arrow-size:50px;--cfc-park-x:0px;--cfc-park-z:calc(var(--cfc-step-z)*-6);--cfc-park-scale:.1;perspective:1000px;width:min(900px,100%);height:100%;margin:auto;position:relative}.cfc__track{width:100%;height:calc(var(--cfc-card-w)*var(--cfc-card-ar-num));transform-style:preserve-3d;position:relative}.cfc__card{--cfc-delta:0;--cfc-abs:0;--cfc-scale:clamp(.1,calc(1 - (var(--cfc-abs)*var(--cfc-scale-step))),1);--cfc-x:calc(var(--cfc-step-x)*var(--cfc-delta));--cfc-z:calc(var(--cfc-step-z)*-1*var(--cfc-abs));--cfc-zIndex:calc(100 - var(--cfc-abs));--cfc-scale-effective:var(--cfc-scale);--cfc-x-effective:var(--cfc-x);--cfc-z-effective:var(--cfc-z);pointer-events:auto;width:var(--cfc-card-w);aspect-ratio:var(--cfc-card-ar);z-index:var(--cfc-zIndex);transform-style:preserve-3d;transform:translate(-50%,-50%)translate3d(var(--cfc-x-effective),0px,var(--cfc-z-effective))scale(var(--cfc-scale-effective));transition:transform var(--cfc-transition-ms)var(--cfc-easing);position:absolute;inset:50% 0 0 50%;overflow:hidden}.cfc__card[aria-hidden=false]{will-change:transform}.cfc__card[aria-hidden=true]{--cfc-x-effective:var(--cfc-park-x);--cfc-z-effective:var(--cfc-park-z);--cfc-scale-effective:var(--cfc-park-scale);pointer-events:none}.cfc__card[data-active=true]{--cfc-scale-effective:1}.cfc__card ::slotted(img),.cfc__card img{object-fit:cover;object-position:center;width:100%;height:100%;display:block}.cfc__arrow{top:calc(50% - var(--cfc-arrow-size));width:var(--cfc-arrow-size);height:var(--cfc-arrow-size);cursor:pointer;z-index:50;background:#dc143c;border:none;transition:background .25s;position:absolute}.cfc__arrow:hover{background:#d3d3d3}.cfc__arrow--left{left:20px}.cfc__arrow--right{right:20px}.cfc__dots{justify-content:center;align-items:center;gap:10px;height:50px;display:flex}.cfc__dot{cursor:default;background:#d3d3d3;border-radius:50%;width:10px;height:10px;transition:transform .25s,background .25s}.cfc__dot[data-active=true]{background:#dc143c;transform:scale(1.2)}.cfc__sr{clip:rect(0,0,0,0);white-space:nowrap;border:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}
1
+ :host{--cfc-card-ar-num:1.25;--cfc-card-w:clamp(160px,100vw - 120px,380px);--cfc-card-ar:1/var(--cfc-card-ar-num);--cfc-step-x:min(22vw,250px);--cfc-step-z:calc(var(--cfc-card-w)*.5);--cfc-scale-step:.1;--cfc-transition-ms:.55s;--cfc-easing:cubic-bezier(.785,.135,.15,.86);--cfc-arrow-size:50px;--cfc-park-x:0px;--cfc-park-z:calc(var(--cfc-step-z)*-6);--cfc-park-scale:.1;width:100%;height:100%;display:block;overflow:hidden;container-type:inline-size}.cfc{perspective:1000px;width:min(900px,100%);height:100%;margin:auto;position:relative}.cfc__track{width:100%;height:calc(var(--cfc-card-w)*var(--cfc-card-ar-num));transform-style:preserve-3d;position:relative}.cfc__card{--cfc-delta:0;--cfc-abs:0;--cfc-scale:clamp(.1,calc(1 - (var(--cfc-abs)*var(--cfc-scale-step))),1);--cfc-x:calc(var(--cfc-step-x)*var(--cfc-delta));--cfc-z:calc(var(--cfc-step-z)*-1*var(--cfc-abs));--cfc-zIndex:calc(100 - var(--cfc-abs));--cfc-scale-effective:var(--cfc-scale);--cfc-x-effective:var(--cfc-x);--cfc-z-effective:var(--cfc-z);pointer-events:auto;width:var(--cfc-card-w);aspect-ratio:var(--cfc-card-ar);z-index:var(--cfc-zIndex);transform-style:preserve-3d;transform:translate(-50%,-50%)translate3d(var(--cfc-x-effective),0px,var(--cfc-z-effective))scale(var(--cfc-scale-effective));transition:transform var(--cfc-transition-ms)var(--cfc-easing);position:absolute;inset:50% 0 0 50%;overflow:hidden}.cfc__card[aria-hidden=false]{will-change:transform}.cfc__card[aria-hidden=true]{--cfc-x-effective:var(--cfc-park-x);--cfc-z-effective:var(--cfc-park-z);--cfc-scale-effective:var(--cfc-park-scale);pointer-events:none}.cfc__card[data-active=true]{--cfc-scale-effective:1}.cfc__card ::slotted(img),.cfc__card img{object-fit:cover;object-position:center;width:100%;height:100%;display:block}.cfc__arrow{top:calc(50% - var(--cfc-arrow-size));width:var(--cfc-arrow-size);height:var(--cfc-arrow-size);cursor:pointer;z-index:50;background:#dc143c;border:none;transition:background .25s;position:absolute}.cfc__arrow:hover{background:#d3d3d3}.cfc__arrow--left{left:20px}.cfc__arrow--right{right:20px}.cfc__dots{justify-content:center;align-items:center;gap:10px;height:50px;display:flex}.cfc__dot{cursor:default;background:#d3d3d3;border-radius:50%;width:10px;height:10px;transition:transform .25s,background .25s}.cfc__dot[data-active=true]{background:#dc143c;transform:scale(1.2)}.cfc__sr{clip:rect(0,0,0,0);white-space:nowrap;border:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}
@@ -10,7 +10,10 @@ export declare class CoverflowCarouselElement extends HTMLElement {
10
10
  private prevBtn;
11
11
  private nextBtn;
12
12
  private liveRegion;
13
- private styleEl;
13
+ private baseStyles;
14
+ private overrideStyles;
15
+ private baseStyleEl;
16
+ private overrideStyleEl;
14
17
  private cards;
15
18
  private dots;
16
19
  private currentIndex;
@@ -21,8 +24,10 @@ export declare class CoverflowCarouselElement extends HTMLElement {
21
24
  private pendingAnimToken;
22
25
  private animFallbackTimerId;
23
26
  private cleanup;
27
+ private readonly lightDomObserverOptions;
24
28
  private lightDomObserver;
25
29
  private refreshScheduled;
30
+ private suppressMutations;
26
31
  private reflectGuard;
27
32
  connectedCallback(): void;
28
33
  disconnectedCallback(): void;
@@ -34,6 +39,7 @@ export declare class CoverflowCarouselElement extends HTMLElement {
34
39
  destroy(): void;
35
40
  adoptStylesheet(sheet: CSSStyleSheet): void;
36
41
  adoptStyles(styles: StylesInput): void;
42
+ adoptStyleOverrides(styles: StylesInput): void;
37
43
  private readAttributes;
38
44
  private render;
39
45
  private bindEvents;
@@ -52,5 +58,8 @@ export declare class CoverflowCarouselElement extends HTMLElement {
52
58
  private dispatchReady;
53
59
  private announce;
54
60
  private reflectIndexAttr;
55
- private applyStyles;
61
+ private setBaseStyles;
62
+ private setOverrideStyles;
63
+ private applyAllStyles;
64
+ private applyAllStylesFallback;
56
65
  }
package/dist/index.cjs.js CHANGED
@@ -4,9 +4,7 @@
4
4
  height: 100%;
5
5
  container-type: inline-size;
6
6
  overflow: hidden;
7
- }
8
7
 
9
- .cfc {
10
8
  /* Layout */
11
9
  --cfc-card-ar-num: 1.25;
12
10
  --cfc-card-w: clamp(160px, 100vw - 120px, 380px);
@@ -30,7 +28,9 @@
30
28
  --cfc-park-x: 0px;
31
29
  --cfc-park-z: calc(var(--cfc-step-z) * -6);
32
30
  --cfc-park-scale: 0.1;
31
+ }
33
32
 
33
+ .cfc {
34
34
  margin: auto;
35
35
  width: min(900px, 100%);
36
36
  height: 100%;
@@ -164,4 +164,4 @@
164
164
  white-space: nowrap;
165
165
  border: 0;
166
166
  }
167
- `,A=x;function v(a){return"adoptedStyleSheets"in a}function S(a){try{const t=new CSSStyleSheet;return t.replaceSync(a),t}catch{return null}}const I=S(x),_=x;function b(a){return a.split(",").map(t=>t.trim()).filter(Boolean)}function g(a,t){const e=a.trim();if(!e)return t;if(e.endsWith("ms")){const n=Number(e.slice(0,-2).trim());return Number.isFinite(n)?n:t}if(e.endsWith("s")){const n=Number(e.slice(0,-1).trim());return Number.isFinite(n)?n*1e3:t}const s=Number(e);return Number.isFinite(s)?s:t}function C(a,t){const e=b(a.transitionProperty),s=b(a.transitionDuration),n=b(a.transitionDelay);if(!e.length)return 0;const r=(m=>{const h=e.indexOf(m);if(h>=0)return h;const E=e.indexOf("all");return E>=0?E:-1})(t);if(r<0)return 0;const c=s[Math.min(r,s.length-1)]??"0s",o=n[Math.min(r,n.length-1)]??"0s",l=g(c,0),y=g(o,0);return Math.max(0,l+y)}function k(){return window.matchMedia?.("(prefers-reduced-motion: reduce)")?.matches??!1}function u(a,t,e){if(!a.hasAttribute(t))return e;const s=a.getAttribute(t);return s==null||s===""?!0:s!=="false"}function L(a,t,e){const s=a.getAttribute(t);if(s==null)return e;const n=Number(s.trim());return Number.isFinite(n)?Math.trunc(n):e}function w(a,t){const e=a.getAttribute(t);if(e==null)return null;const s=e.trim();return s||null}function f(a,t){return t<=0?0:(a%t+t)%t}function T(a,t,e){const s=t-a,n=e/2;return s>n?s-e:s<-n?s+e:s}const p=1;let D=0;class d extends HTMLElement{static defaultStylesheet=I;static observedAttributes=["start-index","index","show-dots","show-arrows","announce-changes"];shadow=this.attachShadow({mode:"open"});instanceId=`cfc-${++D}`;rootEl;trackEl;dotsEl;prevBtn;nextBtn;liveRegion;styleEl=null;cards=[];dots=[];currentIndex=0;isAnimating=!1;hasAppliedInitialLayout=!1;lastLayoutIndex=null;lastVisibleSet=new Set;pendingAnimToken=0;animFallbackTimerId=null;cleanup=[];lightDomObserver=null;refreshScheduled=!1;reflectGuard=!1;connectedCallback(){this.render(),this.readAttributes({isInit:!0}),this.refresh(),this.setupLightDomObserver()}disconnectedCallback(){this.destroy()}attributeChangedCallback(t,e,s){if(!this.isConnected||this.reflectGuard)return;this.readAttributes({isInit:!1});const n=w(this,"index");if(n!=null){const i=f(Number(n),this.cards.length);if(Number.isFinite(i)&&i!==this.currentIndex){this.goTo(i);return}}this.applyLayoutAndA11y({announce:!0,emitChange:!1})}next(){this.goTo(this.currentIndex+1)}prev(){this.goTo(this.currentIndex-1)}goTo(t){if(this.isAnimating)return;const e=f(t,this.cards.length);e!==this.currentIndex&&(this.isAnimating=!0,this.currentIndex=e,this.reflectIndexAttr(),this.applyLayoutAndA11y({announce:!0,emitChange:!0}),this.lockUntilTransitionEnd())}refresh(){this.rebuildCardsFromLightDom();const t=L(this,"start-index",0),e=w(this,"index"),s=e!=null?Number(e):t,n=Number.isFinite(s)?s:0;this.currentIndex=f(n,this.cards.length),this.reflectIndexAttr(),this.buildDots(),this.hasAppliedInitialLayout=!1,this.lastLayoutIndex=null,this.lastVisibleSet=new Set,this.applyLayoutAndA11y({announce:!0,emitChange:!1}),this.dispatchReady()}destroy(){this.animFallbackTimerId!==null&&(window.clearTimeout(this.animFallbackTimerId),this.animFallbackTimerId=null),this.cleanup.forEach(t=>{t()}),this.cleanup=[],this.isAnimating=!1}adoptStylesheet(t){this.applyStyles(t)}adoptStyles(t){this.applyStyles(t)}readAttributes(t){this.rootEl||this.render();const e=u(this,"show-arrows",!1),s=u(this,"show-dots",!1);this.prevBtn.style.display=e?"":"none",this.nextBtn.style.display=e?"":"none",this.dotsEl.style.display=s?"":"none";const n=this.getAttribute("aria-label");n&&this.rootEl.setAttribute("aria-label",n),t.isInit&&this.setAttribute("aria-roledescription","carousel")}render(){this.applyStyles(null),this.rootEl=document.createElement("div"),this.rootEl.className="cfc",this.trackEl=document.createElement("div"),this.trackEl.className="cfc__track",this.prevBtn=document.createElement("button"),this.prevBtn.type="button",this.prevBtn.className="cfc__arrow cfc__arrow--left",this.prevBtn.setAttribute("aria-label","Previous"),this.prevBtn.textContent="‹",this.nextBtn=document.createElement("button"),this.nextBtn.type="button",this.nextBtn.className="cfc__arrow cfc__arrow--right",this.nextBtn.setAttribute("aria-label","Next"),this.nextBtn.textContent="›",this.dotsEl=document.createElement("div"),this.dotsEl.className="cfc__dots",this.liveRegion=document.createElement("div"),this.liveRegion.className="cfc__sr",this.liveRegion.setAttribute("aria-live","polite"),this.liveRegion.setAttribute("aria-atomic","true"),this.rootEl.append(this.trackEl,this.prevBtn,this.nextBtn,this.dotsEl,this.liveRegion),this.shadow.innerHTML="",this.styleEl&&this.shadow.append(this.styleEl),this.shadow.append(this.rootEl),this.bindEvents()}bindEvents(){this.cleanup.forEach(i=>{i()}),this.cleanup=[];const t=()=>this.prev(),e=()=>this.next();this.prevBtn.addEventListener("click",t),this.nextBtn.addEventListener("click",e),this.cleanup.push(()=>this.prevBtn.removeEventListener("click",t)),this.cleanup.push(()=>this.nextBtn.removeEventListener("click",e));const s=i=>{if(!this.isAnimating||i.propertyName!=="transform"||!(i.target instanceof HTMLElement))return;const r=this.cards[this.currentIndex];r&&i.target===r&&this.unlockAnimation()};this.rootEl.addEventListener("transitionend",s),this.cleanup.push(()=>this.rootEl.removeEventListener("transitionend",s));const n=i=>{const r=i.target;if(!(r instanceof HTMLElement)||r.tagName!=="SCRATCH-REVEAL")return;const c=i.composedPath?.();if(!c?.length)return;const o=c.find(h=>h instanceof HTMLElement&&h.classList.contains("cfc__card")&&h.dataset.cfcIndex!=null);if(!o)return;const l=Number(o.dataset.cfcIndex);if(!Number.isFinite(l))return;const m=i.detail?.percent??100;this.dispatchEvent(new CustomEvent("coverflow-carousel:scratch-complete",{detail:{index:l,length:this.cards.length,percent:m},bubbles:!0,composed:!0}))};this.rootEl.addEventListener("complete",n),this.cleanup.push(()=>this.rootEl.removeEventListener("complete",n))}setupLightDomObserver(){this.lightDomObserver||(this.lightDomObserver=new MutationObserver(t=>{t.some(s=>!(s.type==="attributes"&&s.attributeName==="slot"))&&this.scheduleRefreshFromMutations()}),this.lightDomObserver.observe(this,{childList:!0,attributes:!0,subtree:!0}),this.cleanup.push(()=>{this.lightDomObserver?.disconnect(),this.lightDomObserver=null}))}scheduleRefreshFromMutations(){this.refreshScheduled||(this.refreshScheduled=!0,queueMicrotask(()=>{this.refreshScheduled=!1,this.isConnected&&this.refresh()}))}rebuildCardsFromLightDom(){const t=Array.from(this.trackEl.children).filter(n=>n instanceof HTMLElement&&n.classList.contains("cfc__card")),e=[],s=Array.from(this.children).filter(n=>n instanceof HTMLElement);for(let n=0;n<s.length;n++){const i=s[n],r=t[n]??document.createElement("div"),c=`${this.instanceId}-slot-${n}`;t[n]||(r.className="cfc__card",this.trackEl.append(r)),i.getAttribute("slot")!==c&&i.setAttribute("slot",c);const o=r.querySelector("slot");let l;o instanceof HTMLSlotElement?l=o:(l=document.createElement("slot"),r.replaceChildren(l)),l.name!==c&&(l.name=c),e.push(r)}for(let n=s.length;n<t.length;n++)t[n]?.remove();this.cards=e,this.hasAppliedInitialLayout=!1,this.lastLayoutIndex=null,this.lastVisibleSet=new Set}getVisibleSet(){const t=this.cards.length,e=new Set;if(t<=0)return e;const s=Math.floor(t/2);if(p>=s){for(let n=0;n<t;n++)e.add(n);return e}for(let n=-p;n<=p;n++)e.add(f(this.currentIndex+n,t));return e}buildDots(){const t=Array.from(this.dotsEl.children).filter(i=>i instanceof HTMLElement&&i.classList.contains("cfc__dot"));if(!u(this,"show-dots",!1)){t.forEach(i=>{i.remove()}),this.dots=[];return}const s=[],n=this.cards.length;for(let i=0;i<n;i++){const r=t[i]??document.createElement("span");t[i]||this.dotsEl.append(r),r.className="cfc__dot",r.setAttribute("aria-hidden","true"),s.push(r)}for(let i=n;i<t.length;i++)t[i]?.remove();this.dots=s,this.updateDotsVisualState()}computeCardState(t){const e=this.cards.length,s=T(this.currentIndex,t,e),n=Math.abs(s);return{index:t,delta:s,abs:n,isVisible:n<=p,isActive:t===this.currentIndex}}applyCardState(t,e){t.setAttribute("aria-hidden",e.isVisible?"false":"true"),t.dataset.active=e.isActive?"true":"false",t.dataset.cfcIndex=String(e.index),t.setAttribute("role","group"),t.setAttribute("aria-roledescription","slide"),t.setAttribute("aria-setsize",String(this.cards.length)),t.setAttribute("aria-posinset",String(e.index+1));const s=`${this.instanceId}-slide-${e.index}`;t.id=s;const n=String(e.delta),i=String(e.abs),r=t.dataset.cfcDelta,c=t.dataset.cfcAbs;r!==n&&(t.style.setProperty("--cfc-delta",n),t.dataset.cfcDelta=n),c!==i&&(t.style.setProperty("--cfc-abs",i),t.dataset.cfcAbs=i)}applyLayoutAndA11y(t){if(this.cards.length){if(this.hasAppliedInitialLayout){const e=this.getVisibleSet(),s=new Set;this.lastVisibleSet.forEach(n=>{s.add(n)}),e.forEach(n=>{s.add(n)}),this.lastLayoutIndex!=null&&s.add(this.lastLayoutIndex),s.add(this.currentIndex),s.forEach(n=>{const i=this.cards[n];if(!i)return;const r=this.computeCardState(n);this.applyCardState(i,r)}),this.lastLayoutIndex=this.currentIndex,this.lastVisibleSet=e}else{for(let e=0;e<this.cards.length;e++){const s=this.cards[e];if(!s)continue;const n=this.computeCardState(e);this.applyCardState(s,n)}this.hasAppliedInitialLayout=!0,this.lastLayoutIndex=this.currentIndex,this.lastVisibleSet=this.getVisibleSet()}this.updateDotsVisualState(),t.announce&&u(this,"announce-changes",!0)&&this.announce(`Slide ${this.currentIndex+1} of ${this.cards.length}`),t.emitChange&&this.emitChange()}}updateDotsVisualState(){if(this.dots.length)for(let t=0;t<this.dots.length;t++){const e=this.dots[t],s=t===this.currentIndex;e.dataset.active=s?"true":"false"}}lockUntilTransitionEnd(){this.pendingAnimToken++;const t=this.pendingAnimToken;if(k()){this.unlockAnimation();return}const e=this.cards[this.currentIndex];if(!e){this.unlockAnimation();return}const s=getComputedStyle(e),n=C(s,"transform");if(n<=0){this.unlockAnimation();return}const i=g(s.getPropertyValue("--cfc-transition-ms").trim(),400),c=Math.max(n,i)+60;this.animFallbackTimerId!==null&&window.clearTimeout(this.animFallbackTimerId),this.animFallbackTimerId=window.setTimeout(()=>{this.pendingAnimToken===t&&this.unlockAnimation()},c)}unlockAnimation(){this.isAnimating=!1,this.animFallbackTimerId!==null&&(window.clearTimeout(this.animFallbackTimerId),this.animFallbackTimerId=null)}emitChange(){this.dispatchEvent(new CustomEvent("coverflow-carousel:change",{detail:{index:this.currentIndex,length:this.cards.length}}))}dispatchReady(){this.dispatchEvent(new CustomEvent("coverflow-carousel:ready",{detail:{index:this.currentIndex,length:this.cards.length}}))}announce(t){this.liveRegion.textContent="",queueMicrotask(()=>{this.liveRegion.textContent=t})}reflectIndexAttr(){this.reflectGuard=!0;try{this.setAttribute("index",String(this.currentIndex))}finally{this.reflectGuard=!1}}applyStyles(t){if(typeof t=="string"){if(v(this.shadow)){const e=S(t);if(e){this.shadow.adoptedStyleSheets=[e],this.styleEl=null;return}}this.styleEl||(this.styleEl=document.createElement("style")),this.styleEl.textContent=t;return}if(t&&v(this.shadow)){this.shadow.adoptedStyleSheets=[t],this.styleEl=null;return}if(d.defaultStylesheet&&v(this.shadow)){this.shadow.adoptedStyleSheets=[d.defaultStylesheet],this.styleEl=null;return}this.styleEl||(this.styleEl=document.createElement("style")),this.styleEl.textContent=_}}function N(a={}){const{selector:t="coverflow-carousel",onReady:e,onChange:s,onScratchComplete:n,stylesheet:i}=a,r=Array.from(document.querySelectorAll(t));return(e||s||n)&&r.forEach(c=>{e&&c.addEventListener("coverflow-carousel:ready",o=>{e(c,o.detail)}),s&&c.addEventListener("coverflow-carousel:change",o=>{s(c,o.detail)}),n&&c.addEventListener("coverflow-carousel:scratch-complete",o=>{n(c,o.detail)})}),i&&r.forEach(c=>{const o=c;typeof i=="string"?o.adoptStyles(i):o.adoptStylesheet(i)}),r}function F(a="coverflow-carousel"){typeof window>"u"||!("customElements"in window)||customElements.get(a)||customElements.define(a,d)}exports.CoverflowCarouselElement=d;exports.coverflowCarouselCssText=A;exports.initCoverflowCarousels=N;exports.registerCoverflowCarouselElement=F;
167
+ `,A=x;function E(a){return"adoptedStyleSheets"in a}function y(a){try{const t=new CSSStyleSheet;return t.replaceSync(a),t}catch{return null}}const I=y(x),k=x;function b(a){return a.split(",").map(t=>t.trim()).filter(Boolean)}function g(a,t){const n=a.trim();if(!n)return t;if(n.endsWith("ms")){const e=Number(n.slice(0,-2).trim());return Number.isFinite(e)?e:t}if(n.endsWith("s")){const e=Number(n.slice(0,-1).trim());return Number.isFinite(e)?e*1e3:t}const s=Number(n);return Number.isFinite(s)?s:t}function C(a,t){const n=b(a.transitionProperty),s=b(a.transitionDuration),e=b(a.transitionDelay);if(!n.length)return 0;const r=(v=>{const h=n.indexOf(v);if(h>=0)return h;const S=n.indexOf("all");return S>=0?S:-1})(t);if(r<0)return 0;const o=s[Math.min(r,s.length-1)]??"0s",l=e[Math.min(r,e.length-1)]??"0s",c=g(o,0),d=g(l,0);return Math.max(0,c+d)}function _(){return window.matchMedia?.("(prefers-reduced-motion: reduce)")?.matches??!1}function f(a,t,n){if(!a.hasAttribute(t))return n;const s=a.getAttribute(t);return s==null||s===""?!0:s!=="false"}function L(a,t,n){const s=a.getAttribute(t);if(s==null)return n;const e=Number(s.trim());return Number.isFinite(e)?Math.trunc(e):n}function w(a,t){const n=a.getAttribute(t);if(n==null)return null;const s=n.trim();return s||null}function p(a,t){return t<=0?0:(a%t+t)%t}function T(a,t,n){const s=t-a,e=n/2;return s>e?s-n:s<-e?s+n:s}const m=1;let D=0;class u extends HTMLElement{static defaultStylesheet=I;static observedAttributes=["start-index","index","show-dots","show-arrows","announce-changes"];shadow=this.attachShadow({mode:"open"});instanceId=`cfc-${++D}`;rootEl;trackEl;dotsEl;prevBtn;nextBtn;liveRegion;baseStyles=null;overrideStyles=null;baseStyleEl=null;overrideStyleEl=null;cards=[];dots=[];currentIndex=0;isAnimating=!1;hasAppliedInitialLayout=!1;lastLayoutIndex=null;lastVisibleSet=new Set;pendingAnimToken=0;animFallbackTimerId=null;cleanup=[];lightDomObserverOptions={childList:!0,attributes:!0,subtree:!0};lightDomObserver=null;refreshScheduled=!1;suppressMutations=!1;reflectGuard=!1;connectedCallback(){this.render(),this.readAttributes({isInit:!0}),this.refresh(),this.setupLightDomObserver()}disconnectedCallback(){this.destroy()}attributeChangedCallback(t,n,s){if(!this.isConnected||this.reflectGuard)return;this.readAttributes({isInit:!1});const e=w(this,"index");if(e!=null){const i=p(Number(e),this.cards.length);if(Number.isFinite(i)&&i!==this.currentIndex){this.goTo(i);return}}this.applyLayoutAndA11y({announce:!0,emitChange:!1})}next(){this.goTo(this.currentIndex+1)}prev(){this.goTo(this.currentIndex-1)}goTo(t){if(this.isAnimating)return;const n=p(t,this.cards.length);n!==this.currentIndex&&(this.isAnimating=!0,this.currentIndex=n,this.reflectIndexAttr(),this.applyLayoutAndA11y({announce:!0,emitChange:!0}),this.lockUntilTransitionEnd())}refresh(){const t=this.lightDomObserver;t&&(this.suppressMutations=!0,t.disconnect(),t.takeRecords()),this.rebuildCardsFromLightDom();const n=L(this,"start-index",0),s=w(this,"index"),e=s!=null?Number(s):n,i=Number.isFinite(e)?e:0;this.currentIndex=p(i,this.cards.length),this.reflectIndexAttr(),this.buildDots(),this.hasAppliedInitialLayout=!1,this.lastLayoutIndex=null,this.lastVisibleSet=new Set,this.applyLayoutAndA11y({announce:!0,emitChange:!1}),this.dispatchReady(),t&&(t.observe(this,this.lightDomObserverOptions),t.takeRecords(),this.suppressMutations=!1)}destroy(){this.animFallbackTimerId!==null&&(window.clearTimeout(this.animFallbackTimerId),this.animFallbackTimerId=null),this.cleanup.forEach(t=>{t()}),this.cleanup=[],this.isAnimating=!1}adoptStylesheet(t){this.setBaseStyles(t)}adoptStyles(t){this.setBaseStyles(t)}adoptStyleOverrides(t){this.setOverrideStyles(t)}readAttributes(t){this.rootEl||this.render();const n=f(this,"show-arrows",!1),s=f(this,"show-dots",!1);this.prevBtn.style.display=n?"":"none",this.nextBtn.style.display=n?"":"none",this.dotsEl.style.display=s?"":"none";const e=this.getAttribute("aria-label");e&&this.rootEl.setAttribute("aria-label",e),t.isInit&&this.setAttribute("aria-roledescription","carousel")}render(){this.applyAllStyles(),this.rootEl=document.createElement("div"),this.rootEl.className="cfc",this.trackEl=document.createElement("div"),this.trackEl.className="cfc__track",this.prevBtn=document.createElement("button"),this.prevBtn.type="button",this.prevBtn.className="cfc__arrow cfc__arrow--left",this.prevBtn.setAttribute("aria-label","Previous"),this.prevBtn.textContent="‹",this.nextBtn=document.createElement("button"),this.nextBtn.type="button",this.nextBtn.className="cfc__arrow cfc__arrow--right",this.nextBtn.setAttribute("aria-label","Next"),this.nextBtn.textContent="›",this.dotsEl=document.createElement("div"),this.dotsEl.className="cfc__dots",this.liveRegion=document.createElement("div"),this.liveRegion.className="cfc__sr",this.liveRegion.setAttribute("aria-live","polite"),this.liveRegion.setAttribute("aria-atomic","true"),this.rootEl.append(this.trackEl,this.prevBtn,this.nextBtn,this.dotsEl,this.liveRegion),this.shadow.innerHTML="",this.baseStyleEl&&this.shadow.append(this.baseStyleEl),this.overrideStyleEl&&this.shadow.append(this.overrideStyleEl),this.shadow.append(this.rootEl),this.bindEvents()}bindEvents(){this.cleanup.forEach(i=>{i()}),this.cleanup=[];const t=()=>this.prev(),n=()=>this.next();this.prevBtn.addEventListener("click",t),this.nextBtn.addEventListener("click",n),this.cleanup.push(()=>this.prevBtn.removeEventListener("click",t)),this.cleanup.push(()=>this.nextBtn.removeEventListener("click",n));const s=i=>{if(!this.isAnimating||i.propertyName!=="transform"||!(i.target instanceof HTMLElement))return;const r=this.cards[this.currentIndex];r&&i.target===r&&this.unlockAnimation()};this.rootEl.addEventListener("transitionend",s),this.cleanup.push(()=>this.rootEl.removeEventListener("transitionend",s));const e=i=>{const r=i.target;if(!(r instanceof HTMLElement)||r.tagName!=="SCRATCH-REVEAL")return;const o=i.composedPath?.();if(!o?.length)return;const l=o.find(h=>h instanceof HTMLElement&&h.classList.contains("cfc__card")&&h.dataset.cfcIndex!=null);if(!l)return;const c=Number(l.dataset.cfcIndex);if(!Number.isFinite(c))return;const v=i.detail?.percent??100;this.dispatchEvent(new CustomEvent("coverflow-carousel:scratch-complete",{detail:{index:c,length:this.cards.length,percent:v},bubbles:!0,composed:!0}))};this.rootEl.addEventListener("complete",e),this.cleanup.push(()=>this.rootEl.removeEventListener("complete",e))}setupLightDomObserver(){this.lightDomObserver||(this.lightDomObserver=new MutationObserver(t=>{this.suppressMutations||!t.some(s=>!(s.type==="attributes"&&s.attributeName==="slot"))||this.scheduleRefreshFromMutations()}),this.lightDomObserver.observe(this,{...this.lightDomObserverOptions}),this.cleanup.push(()=>{this.lightDomObserver?.disconnect(),this.lightDomObserver=null}))}scheduleRefreshFromMutations(){this.refreshScheduled||(this.refreshScheduled=!0,queueMicrotask(()=>{this.refreshScheduled=!1,this.isConnected&&this.refresh()}))}rebuildCardsFromLightDom(){const t=Array.from(this.trackEl.children).filter(e=>e instanceof HTMLElement&&e.classList.contains("cfc__card")),n=[],s=Array.from(this.children).filter(e=>e instanceof HTMLElement);for(let e=0;e<s.length;e++){const i=s[e],r=t[e]??document.createElement("div"),o=`${this.instanceId}-slot-${e}`;t[e]||(r.className="cfc__card",this.trackEl.append(r)),i.getAttribute("slot")!==o&&i.setAttribute("slot",o);const l=r.querySelector("slot");let c;l instanceof HTMLSlotElement?c=l:(c=document.createElement("slot"),r.replaceChildren(c)),c.name!==o&&(c.name=o),n.push(r)}for(let e=s.length;e<t.length;e++)t[e]?.remove();this.cards=n,this.hasAppliedInitialLayout=!1,this.lastLayoutIndex=null,this.lastVisibleSet=new Set}getVisibleSet(){const t=this.cards.length,n=new Set;if(t<=0)return n;const s=Math.floor(t/2);if(m>=s){for(let e=0;e<t;e++)n.add(e);return n}for(let e=-m;e<=m;e++)n.add(p(this.currentIndex+e,t));return n}buildDots(){const t=Array.from(this.dotsEl.children).filter(i=>i instanceof HTMLElement&&i.classList.contains("cfc__dot"));if(!f(this,"show-dots",!1)){t.forEach(i=>{i.remove()}),this.dots=[];return}const s=[],e=this.cards.length;for(let i=0;i<e;i++){const r=t[i]??document.createElement("span");t[i]||this.dotsEl.append(r),r.className="cfc__dot",r.setAttribute("aria-hidden","true"),s.push(r)}for(let i=e;i<t.length;i++)t[i]?.remove();this.dots=s,this.updateDotsVisualState()}computeCardState(t){const n=this.cards.length,s=T(this.currentIndex,t,n),e=Math.abs(s);return{index:t,delta:s,abs:e,isVisible:e<=m,isActive:t===this.currentIndex}}applyCardState(t,n){t.setAttribute("aria-hidden",n.isVisible?"false":"true"),t.dataset.active=n.isActive?"true":"false",t.dataset.cfcIndex=String(n.index),t.setAttribute("role","group"),t.setAttribute("aria-roledescription","slide"),t.setAttribute("aria-setsize",String(this.cards.length)),t.setAttribute("aria-posinset",String(n.index+1));const s=`${this.instanceId}-slide-${n.index}`;t.id=s;const e=String(n.delta),i=String(n.abs),r=t.dataset.cfcDelta,o=t.dataset.cfcAbs;r!==e&&(t.style.setProperty("--cfc-delta",e),t.dataset.cfcDelta=e),o!==i&&(t.style.setProperty("--cfc-abs",i),t.dataset.cfcAbs=i)}applyLayoutAndA11y(t){if(this.cards.length){if(this.hasAppliedInitialLayout){const n=this.getVisibleSet(),s=new Set;this.lastVisibleSet.forEach(e=>{s.add(e)}),n.forEach(e=>{s.add(e)}),this.lastLayoutIndex!=null&&s.add(this.lastLayoutIndex),s.add(this.currentIndex),s.forEach(e=>{const i=this.cards[e];if(!i)return;const r=this.computeCardState(e);this.applyCardState(i,r)}),this.lastLayoutIndex=this.currentIndex,this.lastVisibleSet=n}else{for(let n=0;n<this.cards.length;n++){const s=this.cards[n];if(!s)continue;const e=this.computeCardState(n);this.applyCardState(s,e)}this.hasAppliedInitialLayout=!0,this.lastLayoutIndex=this.currentIndex,this.lastVisibleSet=this.getVisibleSet()}this.updateDotsVisualState(),t.announce&&f(this,"announce-changes",!0)&&this.announce(`Slide ${this.currentIndex+1} of ${this.cards.length}`),t.emitChange&&this.emitChange()}}updateDotsVisualState(){if(this.dots.length)for(let t=0;t<this.dots.length;t++){const n=this.dots[t],s=t===this.currentIndex;n.dataset.active=s?"true":"false"}}lockUntilTransitionEnd(){this.pendingAnimToken++;const t=this.pendingAnimToken;if(_()){this.unlockAnimation();return}const n=this.cards[this.currentIndex];if(!n){this.unlockAnimation();return}const s=getComputedStyle(n),e=C(s,"transform");if(e<=0){this.unlockAnimation();return}const i=g(s.getPropertyValue("--cfc-transition-ms").trim(),400),o=Math.max(e,i)+60;this.animFallbackTimerId!==null&&window.clearTimeout(this.animFallbackTimerId),this.animFallbackTimerId=window.setTimeout(()=>{this.pendingAnimToken===t&&this.unlockAnimation()},o)}unlockAnimation(){this.isAnimating=!1,this.animFallbackTimerId!==null&&(window.clearTimeout(this.animFallbackTimerId),this.animFallbackTimerId=null)}emitChange(){this.dispatchEvent(new CustomEvent("coverflow-carousel:change",{detail:{index:this.currentIndex,length:this.cards.length}}))}dispatchReady(){this.dispatchEvent(new CustomEvent("coverflow-carousel:ready",{detail:{index:this.currentIndex,length:this.cards.length}}))}announce(t){this.liveRegion.textContent="",queueMicrotask(()=>{this.liveRegion.textContent=t})}reflectIndexAttr(){this.reflectGuard=!0;try{this.setAttribute("index",String(this.currentIndex))}finally{this.reflectGuard=!1}}setBaseStyles(t){this.baseStyles=t??null,this.applyAllStyles()}setOverrideStyles(t){this.overrideStyles=t??null,this.applyAllStyles()}applyAllStyles(){const t=this.baseStyles,n=this.overrideStyles;if(E(this.shadow)){const e=[];if(typeof t=="string"){const i=y(t);if(!i){this.applyAllStylesFallback();return}e.push(i)}else if(t)e.push(t);else if(u.defaultStylesheet)e.push(u.defaultStylesheet);else{this.applyAllStylesFallback();return}if(typeof n=="string"){const i=y(n);if(!i){this.applyAllStylesFallback();return}e.push(i)}else n&&e.push(n);this.shadow.adoptedStyleSheets=e,this.baseStyleEl?.remove(),this.overrideStyleEl?.remove(),this.baseStyleEl=null,this.overrideStyleEl=null;return}this.applyAllStylesFallback()}applyAllStylesFallback(){const t=this.baseStyles,n=this.overrideStyles;E(this.shadow)&&(this.shadow.adoptedStyleSheets=[]);const s=typeof t=="string"?t:k,e=typeof n=="string"?n:"";this.baseStyleEl||(this.baseStyleEl=document.createElement("style")),this.baseStyleEl.textContent=s,e?(this.overrideStyleEl||(this.overrideStyleEl=document.createElement("style")),this.overrideStyleEl.textContent=e):(this.overrideStyleEl?.remove(),this.overrideStyleEl=null)}}function F(a={}){const{selector:t="coverflow-carousel",onReady:n,onChange:s,onScratchComplete:e,stylesheet:i,styleOverrides:r}=a,o=Array.from(document.querySelectorAll(t));return(n||s||e)&&o.forEach(l=>{n&&l.addEventListener("coverflow-carousel:ready",c=>{n(l,c.detail)}),s&&l.addEventListener("coverflow-carousel:change",c=>{s(l,c.detail)}),e&&l.addEventListener("coverflow-carousel:scratch-complete",c=>{e(l,c.detail)})}),i&&o.forEach(l=>{const c=l;typeof i=="string"?c.adoptStyles(i):c.adoptStylesheet(i)}),r&&o.forEach(l=>{l.adoptStyleOverrides(r)}),o}function M(a="coverflow-carousel"){typeof window>"u"||!("customElements"in window)||customElements.get(a)||customElements.define(a,u)}exports.CoverflowCarouselElement=u;exports.coverflowCarouselCssText=A;exports.initCoverflowCarousels=F;exports.registerCoverflowCarouselElement=M;
package/dist/index.es.js CHANGED
@@ -4,9 +4,7 @@ const x = `:host {
4
4
  height: 100%;
5
5
  container-type: inline-size;
6
6
  overflow: hidden;
7
- }
8
7
 
9
- .cfc {
10
8
  /* Layout */
11
9
  --cfc-card-ar-num: 1.25;
12
10
  --cfc-card-w: clamp(160px, 100vw - 120px, 380px);
@@ -30,7 +28,9 @@ const x = `:host {
30
28
  --cfc-park-x: 0px;
31
29
  --cfc-park-z: calc(var(--cfc-step-z) * -6);
32
30
  --cfc-park-scale: 0.1;
31
+ }
33
32
 
33
+ .cfc {
34
34
  margin: auto;
35
35
  width: min(900px, 100%);
36
36
  height: 100%;
@@ -165,10 +165,10 @@ const x = `:host {
165
165
  border: 0;
166
166
  }
167
167
  `, D = x;
168
- function v(a) {
168
+ function E(a) {
169
169
  return "adoptedStyleSheets" in a;
170
170
  }
171
- function S(a) {
171
+ function y(a) {
172
172
  try {
173
173
  const t = new CSSStyleSheet();
174
174
  return t.replaceSync(a), t;
@@ -176,67 +176,67 @@ function S(a) {
176
176
  return null;
177
177
  }
178
178
  }
179
- const A = S(x), I = x;
179
+ const A = y(x), I = x;
180
180
  function b(a) {
181
181
  return a.split(",").map((t) => t.trim()).filter(Boolean);
182
182
  }
183
183
  function g(a, t) {
184
- const e = a.trim();
185
- if (!e) return t;
186
- if (e.endsWith("ms")) {
187
- const n = Number(e.slice(0, -2).trim());
188
- return Number.isFinite(n) ? n : t;
184
+ const n = a.trim();
185
+ if (!n) return t;
186
+ if (n.endsWith("ms")) {
187
+ const e = Number(n.slice(0, -2).trim());
188
+ return Number.isFinite(e) ? e : t;
189
189
  }
190
- if (e.endsWith("s")) {
191
- const n = Number(e.slice(0, -1).trim());
192
- return Number.isFinite(n) ? n * 1e3 : t;
190
+ if (n.endsWith("s")) {
191
+ const e = Number(n.slice(0, -1).trim());
192
+ return Number.isFinite(e) ? e * 1e3 : t;
193
193
  }
194
- const s = Number(e);
194
+ const s = Number(n);
195
195
  return Number.isFinite(s) ? s : t;
196
196
  }
197
- function _(a, t) {
198
- const e = b(a.transitionProperty), s = b(a.transitionDuration), n = b(a.transitionDelay);
199
- if (!e.length) return 0;
200
- const r = ((m) => {
201
- const h = e.indexOf(m);
197
+ function k(a, t) {
198
+ const n = b(a.transitionProperty), s = b(a.transitionDuration), e = b(a.transitionDelay);
199
+ if (!n.length) return 0;
200
+ const r = ((v) => {
201
+ const h = n.indexOf(v);
202
202
  if (h >= 0) return h;
203
- const E = e.indexOf("all");
204
- return E >= 0 ? E : -1;
203
+ const S = n.indexOf("all");
204
+ return S >= 0 ? S : -1;
205
205
  })(t);
206
206
  if (r < 0) return 0;
207
- const c = s[Math.min(r, s.length - 1)] ?? "0s", o = n[Math.min(r, n.length - 1)] ?? "0s", l = g(c, 0), y = g(o, 0);
208
- return Math.max(0, l + y);
207
+ const o = s[Math.min(r, s.length - 1)] ?? "0s", l = e[Math.min(r, e.length - 1)] ?? "0s", c = g(o, 0), d = g(l, 0);
208
+ return Math.max(0, c + d);
209
209
  }
210
- function k() {
210
+ function _() {
211
211
  return window.matchMedia?.("(prefers-reduced-motion: reduce)")?.matches ?? !1;
212
212
  }
213
- function d(a, t, e) {
214
- if (!a.hasAttribute(t)) return e;
213
+ function u(a, t, n) {
214
+ if (!a.hasAttribute(t)) return n;
215
215
  const s = a.getAttribute(t);
216
216
  return s == null || s === "" ? !0 : s !== "false";
217
217
  }
218
- function C(a, t, e) {
218
+ function C(a, t, n) {
219
219
  const s = a.getAttribute(t);
220
- if (s == null) return e;
221
- const n = Number(s.trim());
222
- return Number.isFinite(n) ? Math.trunc(n) : e;
220
+ if (s == null) return n;
221
+ const e = Number(s.trim());
222
+ return Number.isFinite(e) ? Math.trunc(e) : n;
223
223
  }
224
224
  function w(a, t) {
225
- const e = a.getAttribute(t);
226
- if (e == null) return null;
227
- const s = e.trim();
225
+ const n = a.getAttribute(t);
226
+ if (n == null) return null;
227
+ const s = n.trim();
228
228
  return s || null;
229
229
  }
230
- function u(a, t) {
230
+ function f(a, t) {
231
231
  return t <= 0 ? 0 : (a % t + t) % t;
232
232
  }
233
- function L(a, t, e) {
234
- const s = t - a, n = e / 2;
235
- return s > n ? s - e : s < -n ? s + e : s;
233
+ function L(a, t, n) {
234
+ const s = t - a, e = n / 2;
235
+ return s > e ? s - n : s < -e ? s + n : s;
236
236
  }
237
- const f = 1;
237
+ const p = 1;
238
238
  let T = 0;
239
- class p extends HTMLElement {
239
+ class m extends HTMLElement {
240
240
  static defaultStylesheet = A;
241
241
  static observedAttributes = [
242
242
  "start-index",
@@ -253,7 +253,10 @@ class p extends HTMLElement {
253
253
  prevBtn;
254
254
  nextBtn;
255
255
  liveRegion;
256
- styleEl = null;
256
+ baseStyles = null;
257
+ overrideStyles = null;
258
+ baseStyleEl = null;
259
+ overrideStyleEl = null;
257
260
  cards = [];
258
261
  dots = [];
259
262
  currentIndex = 0;
@@ -264,8 +267,14 @@ class p extends HTMLElement {
264
267
  pendingAnimToken = 0;
265
268
  animFallbackTimerId = null;
266
269
  cleanup = [];
270
+ lightDomObserverOptions = {
271
+ childList: !0,
272
+ attributes: !0,
273
+ subtree: !0
274
+ };
267
275
  lightDomObserver = null;
268
276
  refreshScheduled = !1;
277
+ suppressMutations = !1;
269
278
  reflectGuard = !1;
270
279
  connectedCallback() {
271
280
  this.render(), this.readAttributes({ isInit: !0 }), this.refresh(), this.setupLightDomObserver();
@@ -273,12 +282,12 @@ class p extends HTMLElement {
273
282
  disconnectedCallback() {
274
283
  this.destroy();
275
284
  }
276
- attributeChangedCallback(t, e, s) {
285
+ attributeChangedCallback(t, n, s) {
277
286
  if (!this.isConnected || this.reflectGuard) return;
278
287
  this.readAttributes({ isInit: !1 });
279
- const n = w(this, "index");
280
- if (n != null) {
281
- const i = u(Number(n), this.cards.length);
288
+ const e = w(this, "index");
289
+ if (e != null) {
290
+ const i = f(Number(e), this.cards.length);
282
291
  if (Number.isFinite(i) && i !== this.currentIndex) {
283
292
  this.goTo(i);
284
293
  return;
@@ -294,13 +303,14 @@ class p extends HTMLElement {
294
303
  }
295
304
  goTo(t) {
296
305
  if (this.isAnimating) return;
297
- const e = u(t, this.cards.length);
298
- e !== this.currentIndex && (this.isAnimating = !0, this.currentIndex = e, this.reflectIndexAttr(), this.applyLayoutAndA11y({ announce: !0, emitChange: !0 }), this.lockUntilTransitionEnd());
306
+ const n = f(t, this.cards.length);
307
+ n !== this.currentIndex && (this.isAnimating = !0, this.currentIndex = n, this.reflectIndexAttr(), this.applyLayoutAndA11y({ announce: !0, emitChange: !0 }), this.lockUntilTransitionEnd());
299
308
  }
300
309
  refresh() {
301
- this.rebuildCardsFromLightDom();
302
- const t = C(this, "start-index", 0), e = w(this, "index"), s = e != null ? Number(e) : t, n = Number.isFinite(s) ? s : 0;
303
- this.currentIndex = u(n, this.cards.length), this.reflectIndexAttr(), this.buildDots(), this.hasAppliedInitialLayout = !1, this.lastLayoutIndex = null, this.lastVisibleSet = /* @__PURE__ */ new Set(), this.applyLayoutAndA11y({ announce: !0, emitChange: !1 }), this.dispatchReady();
310
+ const t = this.lightDomObserver;
311
+ t && (this.suppressMutations = !0, t.disconnect(), t.takeRecords()), this.rebuildCardsFromLightDom();
312
+ const n = C(this, "start-index", 0), s = w(this, "index"), e = s != null ? Number(s) : n, i = Number.isFinite(e) ? e : 0;
313
+ this.currentIndex = f(i, this.cards.length), this.reflectIndexAttr(), this.buildDots(), this.hasAppliedInitialLayout = !1, this.lastLayoutIndex = null, this.lastVisibleSet = /* @__PURE__ */ new Set(), this.applyLayoutAndA11y({ announce: !0, emitChange: !1 }), this.dispatchReady(), t && (t.observe(this, this.lightDomObserverOptions), t.takeRecords(), this.suppressMutations = !1);
304
314
  }
305
315
  destroy() {
306
316
  this.animFallbackTimerId !== null && (window.clearTimeout(this.animFallbackTimerId), this.animFallbackTimerId = null), this.cleanup.forEach((t) => {
@@ -308,64 +318,65 @@ class p extends HTMLElement {
308
318
  }), this.cleanup = [], this.isAnimating = !1;
309
319
  }
310
320
  adoptStylesheet(t) {
311
- this.applyStyles(t);
321
+ this.setBaseStyles(t);
312
322
  }
313
323
  adoptStyles(t) {
314
- this.applyStyles(t);
324
+ this.setBaseStyles(t);
325
+ }
326
+ adoptStyleOverrides(t) {
327
+ this.setOverrideStyles(t);
315
328
  }
316
329
  readAttributes(t) {
317
330
  this.rootEl || this.render();
318
- const e = d(this, "show-arrows", !1), s = d(this, "show-dots", !1);
319
- this.prevBtn.style.display = e ? "" : "none", this.nextBtn.style.display = e ? "" : "none", this.dotsEl.style.display = s ? "" : "none";
320
- const n = this.getAttribute("aria-label");
321
- n && this.rootEl.setAttribute("aria-label", n), t.isInit && this.setAttribute("aria-roledescription", "carousel");
331
+ const n = u(this, "show-arrows", !1), s = u(this, "show-dots", !1);
332
+ this.prevBtn.style.display = n ? "" : "none", this.nextBtn.style.display = n ? "" : "none", this.dotsEl.style.display = s ? "" : "none";
333
+ const e = this.getAttribute("aria-label");
334
+ e && this.rootEl.setAttribute("aria-label", e), t.isInit && this.setAttribute("aria-roledescription", "carousel");
322
335
  }
323
336
  render() {
324
- this.applyStyles(null), this.rootEl = document.createElement("div"), this.rootEl.className = "cfc", this.trackEl = document.createElement("div"), this.trackEl.className = "cfc__track", this.prevBtn = document.createElement("button"), this.prevBtn.type = "button", this.prevBtn.className = "cfc__arrow cfc__arrow--left", this.prevBtn.setAttribute("aria-label", "Previous"), this.prevBtn.textContent = "‹", this.nextBtn = document.createElement("button"), this.nextBtn.type = "button", this.nextBtn.className = "cfc__arrow cfc__arrow--right", this.nextBtn.setAttribute("aria-label", "Next"), this.nextBtn.textContent = "›", this.dotsEl = document.createElement("div"), this.dotsEl.className = "cfc__dots", this.liveRegion = document.createElement("div"), this.liveRegion.className = "cfc__sr", this.liveRegion.setAttribute("aria-live", "polite"), this.liveRegion.setAttribute("aria-atomic", "true"), this.rootEl.append(this.trackEl, this.prevBtn, this.nextBtn, this.dotsEl, this.liveRegion), this.shadow.innerHTML = "", this.styleEl && this.shadow.append(this.styleEl), this.shadow.append(this.rootEl), this.bindEvents();
337
+ this.applyAllStyles(), this.rootEl = document.createElement("div"), this.rootEl.className = "cfc", this.trackEl = document.createElement("div"), this.trackEl.className = "cfc__track", this.prevBtn = document.createElement("button"), this.prevBtn.type = "button", this.prevBtn.className = "cfc__arrow cfc__arrow--left", this.prevBtn.setAttribute("aria-label", "Previous"), this.prevBtn.textContent = "‹", this.nextBtn = document.createElement("button"), this.nextBtn.type = "button", this.nextBtn.className = "cfc__arrow cfc__arrow--right", this.nextBtn.setAttribute("aria-label", "Next"), this.nextBtn.textContent = "›", this.dotsEl = document.createElement("div"), this.dotsEl.className = "cfc__dots", this.liveRegion = document.createElement("div"), this.liveRegion.className = "cfc__sr", this.liveRegion.setAttribute("aria-live", "polite"), this.liveRegion.setAttribute("aria-atomic", "true"), this.rootEl.append(this.trackEl, this.prevBtn, this.nextBtn, this.dotsEl, this.liveRegion), this.shadow.innerHTML = "", this.baseStyleEl && this.shadow.append(this.baseStyleEl), this.overrideStyleEl && this.shadow.append(this.overrideStyleEl), this.shadow.append(this.rootEl), this.bindEvents();
325
338
  }
326
339
  bindEvents() {
327
340
  this.cleanup.forEach((i) => {
328
341
  i();
329
342
  }), this.cleanup = [];
330
- const t = () => this.prev(), e = () => this.next();
331
- this.prevBtn.addEventListener("click", t), this.nextBtn.addEventListener("click", e), this.cleanup.push(() => this.prevBtn.removeEventListener("click", t)), this.cleanup.push(() => this.nextBtn.removeEventListener("click", e));
343
+ const t = () => this.prev(), n = () => this.next();
344
+ this.prevBtn.addEventListener("click", t), this.nextBtn.addEventListener("click", n), this.cleanup.push(() => this.prevBtn.removeEventListener("click", t)), this.cleanup.push(() => this.nextBtn.removeEventListener("click", n));
332
345
  const s = (i) => {
333
346
  if (!this.isAnimating || i.propertyName !== "transform" || !(i.target instanceof HTMLElement)) return;
334
347
  const r = this.cards[this.currentIndex];
335
348
  r && i.target === r && this.unlockAnimation();
336
349
  };
337
350
  this.rootEl.addEventListener("transitionend", s), this.cleanup.push(() => this.rootEl.removeEventListener("transitionend", s));
338
- const n = (i) => {
351
+ const e = (i) => {
339
352
  const r = i.target;
340
353
  if (!(r instanceof HTMLElement) || r.tagName !== "SCRATCH-REVEAL") return;
341
- const c = i.composedPath?.();
342
- if (!c?.length) return;
343
- const o = c.find(
354
+ const o = i.composedPath?.();
355
+ if (!o?.length) return;
356
+ const l = o.find(
344
357
  (h) => h instanceof HTMLElement && h.classList.contains("cfc__card") && h.dataset.cfcIndex != null
345
358
  );
346
- if (!o) return;
347
- const l = Number(o.dataset.cfcIndex);
348
- if (!Number.isFinite(l)) return;
349
- const m = i.detail?.percent ?? 100;
359
+ if (!l) return;
360
+ const c = Number(l.dataset.cfcIndex);
361
+ if (!Number.isFinite(c)) return;
362
+ const v = i.detail?.percent ?? 100;
350
363
  this.dispatchEvent(
351
364
  new CustomEvent("coverflow-carousel:scratch-complete", {
352
- detail: { index: l, length: this.cards.length, percent: m },
365
+ detail: { index: c, length: this.cards.length, percent: v },
353
366
  bubbles: !0,
354
367
  composed: !0
355
368
  })
356
369
  );
357
370
  };
358
- this.rootEl.addEventListener("complete", n), this.cleanup.push(
359
- () => this.rootEl.removeEventListener("complete", n)
371
+ this.rootEl.addEventListener("complete", e), this.cleanup.push(
372
+ () => this.rootEl.removeEventListener("complete", e)
360
373
  );
361
374
  }
362
375
  setupLightDomObserver() {
363
376
  this.lightDomObserver || (this.lightDomObserver = new MutationObserver((t) => {
364
- t.some((s) => !(s.type === "attributes" && s.attributeName === "slot")) && this.scheduleRefreshFromMutations();
377
+ this.suppressMutations || !t.some((s) => !(s.type === "attributes" && s.attributeName === "slot")) || this.scheduleRefreshFromMutations();
365
378
  }), this.lightDomObserver.observe(this, {
366
- childList: !0,
367
- attributes: !0,
368
- subtree: !0
379
+ ...this.lightDomObserverOptions
369
380
  }), this.cleanup.push(() => {
370
381
  this.lightDomObserver?.disconnect(), this.lightDomObserver = null;
371
382
  }));
@@ -377,123 +388,123 @@ class p extends HTMLElement {
377
388
  }
378
389
  rebuildCardsFromLightDom() {
379
390
  const t = Array.from(this.trackEl.children).filter(
380
- (n) => n instanceof HTMLElement && n.classList.contains("cfc__card")
381
- ), e = [], s = Array.from(this.children).filter(
382
- (n) => n instanceof HTMLElement
391
+ (e) => e instanceof HTMLElement && e.classList.contains("cfc__card")
392
+ ), n = [], s = Array.from(this.children).filter(
393
+ (e) => e instanceof HTMLElement
383
394
  );
384
- for (let n = 0; n < s.length; n++) {
385
- const i = s[n], r = t[n] ?? document.createElement("div"), c = `${this.instanceId}-slot-${n}`;
386
- t[n] || (r.className = "cfc__card", this.trackEl.append(r)), i.getAttribute("slot") !== c && i.setAttribute("slot", c);
387
- const o = r.querySelector("slot");
388
- let l;
389
- o instanceof HTMLSlotElement ? l = o : (l = document.createElement("slot"), r.replaceChildren(l)), l.name !== c && (l.name = c), e.push(r);
395
+ for (let e = 0; e < s.length; e++) {
396
+ const i = s[e], r = t[e] ?? document.createElement("div"), o = `${this.instanceId}-slot-${e}`;
397
+ t[e] || (r.className = "cfc__card", this.trackEl.append(r)), i.getAttribute("slot") !== o && i.setAttribute("slot", o);
398
+ const l = r.querySelector("slot");
399
+ let c;
400
+ l instanceof HTMLSlotElement ? c = l : (c = document.createElement("slot"), r.replaceChildren(c)), c.name !== o && (c.name = o), n.push(r);
390
401
  }
391
- for (let n = s.length; n < t.length; n++)
392
- t[n]?.remove();
393
- this.cards = e, this.hasAppliedInitialLayout = !1, this.lastLayoutIndex = null, this.lastVisibleSet = /* @__PURE__ */ new Set();
402
+ for (let e = s.length; e < t.length; e++)
403
+ t[e]?.remove();
404
+ this.cards = n, this.hasAppliedInitialLayout = !1, this.lastLayoutIndex = null, this.lastVisibleSet = /* @__PURE__ */ new Set();
394
405
  }
395
406
  getVisibleSet() {
396
- const t = this.cards.length, e = /* @__PURE__ */ new Set();
397
- if (t <= 0) return e;
407
+ const t = this.cards.length, n = /* @__PURE__ */ new Set();
408
+ if (t <= 0) return n;
398
409
  const s = Math.floor(t / 2);
399
- if (f >= s) {
400
- for (let n = 0; n < t; n++) e.add(n);
401
- return e;
410
+ if (p >= s) {
411
+ for (let e = 0; e < t; e++) n.add(e);
412
+ return n;
402
413
  }
403
- for (let n = -f; n <= f; n++)
404
- e.add(u(this.currentIndex + n, t));
405
- return e;
414
+ for (let e = -p; e <= p; e++)
415
+ n.add(f(this.currentIndex + e, t));
416
+ return n;
406
417
  }
407
418
  buildDots() {
408
419
  const t = Array.from(this.dotsEl.children).filter(
409
420
  (i) => i instanceof HTMLElement && i.classList.contains("cfc__dot")
410
421
  );
411
- if (!d(this, "show-dots", !1)) {
422
+ if (!u(this, "show-dots", !1)) {
412
423
  t.forEach((i) => {
413
424
  i.remove();
414
425
  }), this.dots = [];
415
426
  return;
416
427
  }
417
- const s = [], n = this.cards.length;
418
- for (let i = 0; i < n; i++) {
428
+ const s = [], e = this.cards.length;
429
+ for (let i = 0; i < e; i++) {
419
430
  const r = t[i] ?? document.createElement("span");
420
431
  t[i] || this.dotsEl.append(r), r.className = "cfc__dot", r.setAttribute("aria-hidden", "true"), s.push(r);
421
432
  }
422
- for (let i = n; i < t.length; i++)
433
+ for (let i = e; i < t.length; i++)
423
434
  t[i]?.remove();
424
435
  this.dots = s, this.updateDotsVisualState();
425
436
  }
426
437
  computeCardState(t) {
427
- const e = this.cards.length, s = L(this.currentIndex, t, e), n = Math.abs(s);
438
+ const n = this.cards.length, s = L(this.currentIndex, t, n), e = Math.abs(s);
428
439
  return {
429
440
  index: t,
430
441
  delta: s,
431
- abs: n,
432
- isVisible: n <= f,
442
+ abs: e,
443
+ isVisible: e <= p,
433
444
  isActive: t === this.currentIndex
434
445
  };
435
446
  }
436
- applyCardState(t, e) {
437
- t.setAttribute("aria-hidden", e.isVisible ? "false" : "true"), t.dataset.active = e.isActive ? "true" : "false", t.dataset.cfcIndex = String(e.index), t.setAttribute("role", "group"), t.setAttribute("aria-roledescription", "slide"), t.setAttribute("aria-setsize", String(this.cards.length)), t.setAttribute("aria-posinset", String(e.index + 1));
438
- const s = `${this.instanceId}-slide-${e.index}`;
447
+ applyCardState(t, n) {
448
+ t.setAttribute("aria-hidden", n.isVisible ? "false" : "true"), t.dataset.active = n.isActive ? "true" : "false", t.dataset.cfcIndex = String(n.index), t.setAttribute("role", "group"), t.setAttribute("aria-roledescription", "slide"), t.setAttribute("aria-setsize", String(this.cards.length)), t.setAttribute("aria-posinset", String(n.index + 1));
449
+ const s = `${this.instanceId}-slide-${n.index}`;
439
450
  t.id = s;
440
- const n = String(e.delta), i = String(e.abs), r = t.dataset.cfcDelta, c = t.dataset.cfcAbs;
441
- r !== n && (t.style.setProperty("--cfc-delta", n), t.dataset.cfcDelta = n), c !== i && (t.style.setProperty("--cfc-abs", i), t.dataset.cfcAbs = i);
451
+ const e = String(n.delta), i = String(n.abs), r = t.dataset.cfcDelta, o = t.dataset.cfcAbs;
452
+ r !== e && (t.style.setProperty("--cfc-delta", e), t.dataset.cfcDelta = e), o !== i && (t.style.setProperty("--cfc-abs", i), t.dataset.cfcAbs = i);
442
453
  }
443
454
  applyLayoutAndA11y(t) {
444
455
  if (this.cards.length) {
445
456
  if (this.hasAppliedInitialLayout) {
446
- const e = this.getVisibleSet(), s = /* @__PURE__ */ new Set();
447
- this.lastVisibleSet.forEach((n) => {
448
- s.add(n);
449
- }), e.forEach((n) => {
450
- s.add(n);
451
- }), this.lastLayoutIndex != null && s.add(this.lastLayoutIndex), s.add(this.currentIndex), s.forEach((n) => {
452
- const i = this.cards[n];
457
+ const n = this.getVisibleSet(), s = /* @__PURE__ */ new Set();
458
+ this.lastVisibleSet.forEach((e) => {
459
+ s.add(e);
460
+ }), n.forEach((e) => {
461
+ s.add(e);
462
+ }), this.lastLayoutIndex != null && s.add(this.lastLayoutIndex), s.add(this.currentIndex), s.forEach((e) => {
463
+ const i = this.cards[e];
453
464
  if (!i) return;
454
- const r = this.computeCardState(n);
465
+ const r = this.computeCardState(e);
455
466
  this.applyCardState(i, r);
456
- }), this.lastLayoutIndex = this.currentIndex, this.lastVisibleSet = e;
467
+ }), this.lastLayoutIndex = this.currentIndex, this.lastVisibleSet = n;
457
468
  } else {
458
- for (let e = 0; e < this.cards.length; e++) {
459
- const s = this.cards[e];
469
+ for (let n = 0; n < this.cards.length; n++) {
470
+ const s = this.cards[n];
460
471
  if (!s) continue;
461
- const n = this.computeCardState(e);
462
- this.applyCardState(s, n);
472
+ const e = this.computeCardState(n);
473
+ this.applyCardState(s, e);
463
474
  }
464
475
  this.hasAppliedInitialLayout = !0, this.lastLayoutIndex = this.currentIndex, this.lastVisibleSet = this.getVisibleSet();
465
476
  }
466
- this.updateDotsVisualState(), t.announce && d(this, "announce-changes", !0) && this.announce(`Slide ${this.currentIndex + 1} of ${this.cards.length}`), t.emitChange && this.emitChange();
477
+ this.updateDotsVisualState(), t.announce && u(this, "announce-changes", !0) && this.announce(`Slide ${this.currentIndex + 1} of ${this.cards.length}`), t.emitChange && this.emitChange();
467
478
  }
468
479
  }
469
480
  updateDotsVisualState() {
470
481
  if (this.dots.length)
471
482
  for (let t = 0; t < this.dots.length; t++) {
472
- const e = this.dots[t], s = t === this.currentIndex;
473
- e.dataset.active = s ? "true" : "false";
483
+ const n = this.dots[t], s = t === this.currentIndex;
484
+ n.dataset.active = s ? "true" : "false";
474
485
  }
475
486
  }
476
487
  lockUntilTransitionEnd() {
477
488
  this.pendingAnimToken++;
478
489
  const t = this.pendingAnimToken;
479
- if (k()) {
490
+ if (_()) {
480
491
  this.unlockAnimation();
481
492
  return;
482
493
  }
483
- const e = this.cards[this.currentIndex];
484
- if (!e) {
494
+ const n = this.cards[this.currentIndex];
495
+ if (!n) {
485
496
  this.unlockAnimation();
486
497
  return;
487
498
  }
488
- const s = getComputedStyle(e), n = _(s, "transform");
489
- if (n <= 0) {
499
+ const s = getComputedStyle(n), e = k(s, "transform");
500
+ if (e <= 0) {
490
501
  this.unlockAnimation();
491
502
  return;
492
503
  }
493
- const i = g(s.getPropertyValue("--cfc-transition-ms").trim(), 400), c = Math.max(n, i) + 60;
504
+ const i = g(s.getPropertyValue("--cfc-transition-ms").trim(), 400), o = Math.max(e, i) + 60;
494
505
  this.animFallbackTimerId !== null && window.clearTimeout(this.animFallbackTimerId), this.animFallbackTimerId = window.setTimeout(() => {
495
506
  this.pendingAnimToken === t && this.unlockAnimation();
496
- }, c);
507
+ }, o);
497
508
  }
498
509
  unlockAnimation() {
499
510
  this.isAnimating = !1, this.animFallbackTimerId !== null && (window.clearTimeout(this.animFallbackTimerId), this.animFallbackTimerId = null);
@@ -525,56 +536,81 @@ class p extends HTMLElement {
525
536
  this.reflectGuard = !1;
526
537
  }
527
538
  }
528
- applyStyles(t) {
529
- if (typeof t == "string") {
530
- if (v(this.shadow)) {
531
- const e = S(t);
532
- if (e) {
533
- this.shadow.adoptedStyleSheets = [e], this.styleEl = null;
539
+ setBaseStyles(t) {
540
+ this.baseStyles = t ?? null, this.applyAllStyles();
541
+ }
542
+ setOverrideStyles(t) {
543
+ this.overrideStyles = t ?? null, this.applyAllStyles();
544
+ }
545
+ applyAllStyles() {
546
+ const t = this.baseStyles, n = this.overrideStyles;
547
+ if (E(this.shadow)) {
548
+ const e = [];
549
+ if (typeof t == "string") {
550
+ const i = y(t);
551
+ if (!i) {
552
+ this.applyAllStylesFallback();
534
553
  return;
535
554
  }
555
+ e.push(i);
556
+ } else if (t)
557
+ e.push(t);
558
+ else if (m.defaultStylesheet)
559
+ e.push(m.defaultStylesheet);
560
+ else {
561
+ this.applyAllStylesFallback();
562
+ return;
536
563
  }
537
- this.styleEl || (this.styleEl = document.createElement("style")), this.styleEl.textContent = t;
538
- return;
539
- }
540
- if (t && v(this.shadow)) {
541
- this.shadow.adoptedStyleSheets = [t], this.styleEl = null;
542
- return;
543
- }
544
- if (p.defaultStylesheet && v(this.shadow)) {
545
- this.shadow.adoptedStyleSheets = [p.defaultStylesheet], this.styleEl = null;
564
+ if (typeof n == "string") {
565
+ const i = y(n);
566
+ if (!i) {
567
+ this.applyAllStylesFallback();
568
+ return;
569
+ }
570
+ e.push(i);
571
+ } else n && e.push(n);
572
+ this.shadow.adoptedStyleSheets = e, this.baseStyleEl?.remove(), this.overrideStyleEl?.remove(), this.baseStyleEl = null, this.overrideStyleEl = null;
546
573
  return;
547
574
  }
548
- this.styleEl || (this.styleEl = document.createElement("style")), this.styleEl.textContent = I;
575
+ this.applyAllStylesFallback();
576
+ }
577
+ applyAllStylesFallback() {
578
+ const t = this.baseStyles, n = this.overrideStyles;
579
+ E(this.shadow) && (this.shadow.adoptedStyleSheets = []);
580
+ const s = typeof t == "string" ? t : I, e = typeof n == "string" ? n : "";
581
+ this.baseStyleEl || (this.baseStyleEl = document.createElement("style")), this.baseStyleEl.textContent = s, e ? (this.overrideStyleEl || (this.overrideStyleEl = document.createElement("style")), this.overrideStyleEl.textContent = e) : (this.overrideStyleEl?.remove(), this.overrideStyleEl = null);
549
582
  }
550
583
  }
551
- function N(a = {}) {
584
+ function F(a = {}) {
552
585
  const {
553
586
  selector: t = "coverflow-carousel",
554
- onReady: e,
587
+ onReady: n,
555
588
  onChange: s,
556
- onScratchComplete: n,
557
- stylesheet: i
558
- } = a, r = Array.from(document.querySelectorAll(t));
559
- return (e || s || n) && r.forEach((c) => {
560
- e && c.addEventListener("coverflow-carousel:ready", (o) => {
561
- e(c, o.detail);
562
- }), s && c.addEventListener("coverflow-carousel:change", (o) => {
563
- s(c, o.detail);
564
- }), n && c.addEventListener("coverflow-carousel:scratch-complete", (o) => {
565
- n(c, o.detail);
589
+ onScratchComplete: e,
590
+ stylesheet: i,
591
+ styleOverrides: r
592
+ } = a, o = Array.from(document.querySelectorAll(t));
593
+ return (n || s || e) && o.forEach((l) => {
594
+ n && l.addEventListener("coverflow-carousel:ready", (c) => {
595
+ n(l, c.detail);
596
+ }), s && l.addEventListener("coverflow-carousel:change", (c) => {
597
+ s(l, c.detail);
598
+ }), e && l.addEventListener("coverflow-carousel:scratch-complete", (c) => {
599
+ e(l, c.detail);
566
600
  });
567
- }), i && r.forEach((c) => {
568
- const o = c;
569
- typeof i == "string" ? o.adoptStyles(i) : o.adoptStylesheet(i);
570
- }), r;
601
+ }), i && o.forEach((l) => {
602
+ const c = l;
603
+ typeof i == "string" ? c.adoptStyles(i) : c.adoptStylesheet(i);
604
+ }), r && o.forEach((l) => {
605
+ l.adoptStyleOverrides(r);
606
+ }), o;
571
607
  }
572
- function F(a = "coverflow-carousel") {
573
- typeof window > "u" || !("customElements" in window) || customElements.get(a) || customElements.define(a, p);
608
+ function M(a = "coverflow-carousel") {
609
+ typeof window > "u" || !("customElements" in window) || customElements.get(a) || customElements.define(a, m);
574
610
  }
575
611
  export {
576
- p as CoverflowCarouselElement,
612
+ m as CoverflowCarouselElement,
577
613
  D as coverflowCarouselCssText,
578
- N as initCoverflowCarousels,
579
- F as registerCoverflowCarouselElement
614
+ F as initCoverflowCarousels,
615
+ M as registerCoverflowCarouselElement
580
616
  };
package/dist/index.umd.js CHANGED
@@ -1,12 +1,10 @@
1
- (function(d,h){typeof exports=="object"&&typeof module<"u"?h(exports):typeof define=="function"&&define.amd?define(["exports"],h):(d=typeof globalThis<"u"?globalThis:d||self,h(d.CoverflowCarousel={}))})(this,(function(d){"use strict";const h=`:host {
1
+ (function(h,d){typeof exports=="object"&&typeof module<"u"?d(exports):typeof define=="function"&&define.amd?define(["exports"],d):(h=typeof globalThis<"u"?globalThis:h||self,d(h.CoverflowCarousel={}))})(this,(function(h){"use strict";const d=`:host {
2
2
  display: block;
3
3
  width: 100%;
4
4
  height: 100%;
5
5
  container-type: inline-size;
6
6
  overflow: hidden;
7
- }
8
7
 
9
- .cfc {
10
8
  /* Layout */
11
9
  --cfc-card-ar-num: 1.25;
12
10
  --cfc-card-w: clamp(160px, 100vw - 120px, 380px);
@@ -30,7 +28,9 @@
30
28
  --cfc-park-x: 0px;
31
29
  --cfc-park-z: calc(var(--cfc-step-z) * -6);
32
30
  --cfc-park-scale: 0.1;
31
+ }
33
32
 
33
+ .cfc {
34
34
  margin: auto;
35
35
  width: min(900px, 100%);
36
36
  height: 100%;
@@ -164,4 +164,4 @@
164
164
  white-space: nowrap;
165
165
  border: 0;
166
166
  }
167
- `,I=h;function b(a){return"adoptedStyleSheets"in a}function E(a){try{const t=new CSSStyleSheet;return t.replaceSync(a),t}catch{return null}}const C=E(h),_=h;function g(a){return a.split(",").map(t=>t.trim()).filter(Boolean)}function x(a,t){const e=a.trim();if(!e)return t;if(e.endsWith("ms")){const n=Number(e.slice(0,-2).trim());return Number.isFinite(n)?n:t}if(e.endsWith("s")){const n=Number(e.slice(0,-1).trim());return Number.isFinite(n)?n*1e3:t}const s=Number(e);return Number.isFinite(s)?s:t}function k(a,t){const e=g(a.transitionProperty),s=g(a.transitionDuration),n=g(a.transitionDelay);if(!e.length)return 0;const r=(y=>{const u=e.indexOf(y);if(u>=0)return u;const A=e.indexOf("all");return A>=0?A:-1})(t);if(r<0)return 0;const c=s[Math.min(r,s.length-1)]??"0s",o=n[Math.min(r,n.length-1)]??"0s",l=x(c,0),S=x(o,0);return Math.max(0,l+S)}function L(){return window.matchMedia?.("(prefers-reduced-motion: reduce)")?.matches??!1}function p(a,t,e){if(!a.hasAttribute(t))return e;const s=a.getAttribute(t);return s==null||s===""?!0:s!=="false"}function T(a,t,e){const s=a.getAttribute(t);if(s==null)return e;const n=Number(s.trim());return Number.isFinite(n)?Math.trunc(n):e}function w(a,t){const e=a.getAttribute(t);if(e==null)return null;const s=e.trim();return s||null}function m(a,t){return t<=0?0:(a%t+t)%t}function D(a,t,e){const s=t-a,n=e/2;return s>n?s-e:s<-n?s+e:s}const v=1;let N=0;class f extends HTMLElement{static defaultStylesheet=C;static observedAttributes=["start-index","index","show-dots","show-arrows","announce-changes"];shadow=this.attachShadow({mode:"open"});instanceId=`cfc-${++N}`;rootEl;trackEl;dotsEl;prevBtn;nextBtn;liveRegion;styleEl=null;cards=[];dots=[];currentIndex=0;isAnimating=!1;hasAppliedInitialLayout=!1;lastLayoutIndex=null;lastVisibleSet=new Set;pendingAnimToken=0;animFallbackTimerId=null;cleanup=[];lightDomObserver=null;refreshScheduled=!1;reflectGuard=!1;connectedCallback(){this.render(),this.readAttributes({isInit:!0}),this.refresh(),this.setupLightDomObserver()}disconnectedCallback(){this.destroy()}attributeChangedCallback(t,e,s){if(!this.isConnected||this.reflectGuard)return;this.readAttributes({isInit:!1});const n=w(this,"index");if(n!=null){const i=m(Number(n),this.cards.length);if(Number.isFinite(i)&&i!==this.currentIndex){this.goTo(i);return}}this.applyLayoutAndA11y({announce:!0,emitChange:!1})}next(){this.goTo(this.currentIndex+1)}prev(){this.goTo(this.currentIndex-1)}goTo(t){if(this.isAnimating)return;const e=m(t,this.cards.length);e!==this.currentIndex&&(this.isAnimating=!0,this.currentIndex=e,this.reflectIndexAttr(),this.applyLayoutAndA11y({announce:!0,emitChange:!0}),this.lockUntilTransitionEnd())}refresh(){this.rebuildCardsFromLightDom();const t=T(this,"start-index",0),e=w(this,"index"),s=e!=null?Number(e):t,n=Number.isFinite(s)?s:0;this.currentIndex=m(n,this.cards.length),this.reflectIndexAttr(),this.buildDots(),this.hasAppliedInitialLayout=!1,this.lastLayoutIndex=null,this.lastVisibleSet=new Set,this.applyLayoutAndA11y({announce:!0,emitChange:!1}),this.dispatchReady()}destroy(){this.animFallbackTimerId!==null&&(window.clearTimeout(this.animFallbackTimerId),this.animFallbackTimerId=null),this.cleanup.forEach(t=>{t()}),this.cleanup=[],this.isAnimating=!1}adoptStylesheet(t){this.applyStyles(t)}adoptStyles(t){this.applyStyles(t)}readAttributes(t){this.rootEl||this.render();const e=p(this,"show-arrows",!1),s=p(this,"show-dots",!1);this.prevBtn.style.display=e?"":"none",this.nextBtn.style.display=e?"":"none",this.dotsEl.style.display=s?"":"none";const n=this.getAttribute("aria-label");n&&this.rootEl.setAttribute("aria-label",n),t.isInit&&this.setAttribute("aria-roledescription","carousel")}render(){this.applyStyles(null),this.rootEl=document.createElement("div"),this.rootEl.className="cfc",this.trackEl=document.createElement("div"),this.trackEl.className="cfc__track",this.prevBtn=document.createElement("button"),this.prevBtn.type="button",this.prevBtn.className="cfc__arrow cfc__arrow--left",this.prevBtn.setAttribute("aria-label","Previous"),this.prevBtn.textContent="‹",this.nextBtn=document.createElement("button"),this.nextBtn.type="button",this.nextBtn.className="cfc__arrow cfc__arrow--right",this.nextBtn.setAttribute("aria-label","Next"),this.nextBtn.textContent="›",this.dotsEl=document.createElement("div"),this.dotsEl.className="cfc__dots",this.liveRegion=document.createElement("div"),this.liveRegion.className="cfc__sr",this.liveRegion.setAttribute("aria-live","polite"),this.liveRegion.setAttribute("aria-atomic","true"),this.rootEl.append(this.trackEl,this.prevBtn,this.nextBtn,this.dotsEl,this.liveRegion),this.shadow.innerHTML="",this.styleEl&&this.shadow.append(this.styleEl),this.shadow.append(this.rootEl),this.bindEvents()}bindEvents(){this.cleanup.forEach(i=>{i()}),this.cleanup=[];const t=()=>this.prev(),e=()=>this.next();this.prevBtn.addEventListener("click",t),this.nextBtn.addEventListener("click",e),this.cleanup.push(()=>this.prevBtn.removeEventListener("click",t)),this.cleanup.push(()=>this.nextBtn.removeEventListener("click",e));const s=i=>{if(!this.isAnimating||i.propertyName!=="transform"||!(i.target instanceof HTMLElement))return;const r=this.cards[this.currentIndex];r&&i.target===r&&this.unlockAnimation()};this.rootEl.addEventListener("transitionend",s),this.cleanup.push(()=>this.rootEl.removeEventListener("transitionend",s));const n=i=>{const r=i.target;if(!(r instanceof HTMLElement)||r.tagName!=="SCRATCH-REVEAL")return;const c=i.composedPath?.();if(!c?.length)return;const o=c.find(u=>u instanceof HTMLElement&&u.classList.contains("cfc__card")&&u.dataset.cfcIndex!=null);if(!o)return;const l=Number(o.dataset.cfcIndex);if(!Number.isFinite(l))return;const y=i.detail?.percent??100;this.dispatchEvent(new CustomEvent("coverflow-carousel:scratch-complete",{detail:{index:l,length:this.cards.length,percent:y},bubbles:!0,composed:!0}))};this.rootEl.addEventListener("complete",n),this.cleanup.push(()=>this.rootEl.removeEventListener("complete",n))}setupLightDomObserver(){this.lightDomObserver||(this.lightDomObserver=new MutationObserver(t=>{t.some(s=>!(s.type==="attributes"&&s.attributeName==="slot"))&&this.scheduleRefreshFromMutations()}),this.lightDomObserver.observe(this,{childList:!0,attributes:!0,subtree:!0}),this.cleanup.push(()=>{this.lightDomObserver?.disconnect(),this.lightDomObserver=null}))}scheduleRefreshFromMutations(){this.refreshScheduled||(this.refreshScheduled=!0,queueMicrotask(()=>{this.refreshScheduled=!1,this.isConnected&&this.refresh()}))}rebuildCardsFromLightDom(){const t=Array.from(this.trackEl.children).filter(n=>n instanceof HTMLElement&&n.classList.contains("cfc__card")),e=[],s=Array.from(this.children).filter(n=>n instanceof HTMLElement);for(let n=0;n<s.length;n++){const i=s[n],r=t[n]??document.createElement("div"),c=`${this.instanceId}-slot-${n}`;t[n]||(r.className="cfc__card",this.trackEl.append(r)),i.getAttribute("slot")!==c&&i.setAttribute("slot",c);const o=r.querySelector("slot");let l;o instanceof HTMLSlotElement?l=o:(l=document.createElement("slot"),r.replaceChildren(l)),l.name!==c&&(l.name=c),e.push(r)}for(let n=s.length;n<t.length;n++)t[n]?.remove();this.cards=e,this.hasAppliedInitialLayout=!1,this.lastLayoutIndex=null,this.lastVisibleSet=new Set}getVisibleSet(){const t=this.cards.length,e=new Set;if(t<=0)return e;const s=Math.floor(t/2);if(v>=s){for(let n=0;n<t;n++)e.add(n);return e}for(let n=-v;n<=v;n++)e.add(m(this.currentIndex+n,t));return e}buildDots(){const t=Array.from(this.dotsEl.children).filter(i=>i instanceof HTMLElement&&i.classList.contains("cfc__dot"));if(!p(this,"show-dots",!1)){t.forEach(i=>{i.remove()}),this.dots=[];return}const s=[],n=this.cards.length;for(let i=0;i<n;i++){const r=t[i]??document.createElement("span");t[i]||this.dotsEl.append(r),r.className="cfc__dot",r.setAttribute("aria-hidden","true"),s.push(r)}for(let i=n;i<t.length;i++)t[i]?.remove();this.dots=s,this.updateDotsVisualState()}computeCardState(t){const e=this.cards.length,s=D(this.currentIndex,t,e),n=Math.abs(s);return{index:t,delta:s,abs:n,isVisible:n<=v,isActive:t===this.currentIndex}}applyCardState(t,e){t.setAttribute("aria-hidden",e.isVisible?"false":"true"),t.dataset.active=e.isActive?"true":"false",t.dataset.cfcIndex=String(e.index),t.setAttribute("role","group"),t.setAttribute("aria-roledescription","slide"),t.setAttribute("aria-setsize",String(this.cards.length)),t.setAttribute("aria-posinset",String(e.index+1));const s=`${this.instanceId}-slide-${e.index}`;t.id=s;const n=String(e.delta),i=String(e.abs),r=t.dataset.cfcDelta,c=t.dataset.cfcAbs;r!==n&&(t.style.setProperty("--cfc-delta",n),t.dataset.cfcDelta=n),c!==i&&(t.style.setProperty("--cfc-abs",i),t.dataset.cfcAbs=i)}applyLayoutAndA11y(t){if(this.cards.length){if(this.hasAppliedInitialLayout){const e=this.getVisibleSet(),s=new Set;this.lastVisibleSet.forEach(n=>{s.add(n)}),e.forEach(n=>{s.add(n)}),this.lastLayoutIndex!=null&&s.add(this.lastLayoutIndex),s.add(this.currentIndex),s.forEach(n=>{const i=this.cards[n];if(!i)return;const r=this.computeCardState(n);this.applyCardState(i,r)}),this.lastLayoutIndex=this.currentIndex,this.lastVisibleSet=e}else{for(let e=0;e<this.cards.length;e++){const s=this.cards[e];if(!s)continue;const n=this.computeCardState(e);this.applyCardState(s,n)}this.hasAppliedInitialLayout=!0,this.lastLayoutIndex=this.currentIndex,this.lastVisibleSet=this.getVisibleSet()}this.updateDotsVisualState(),t.announce&&p(this,"announce-changes",!0)&&this.announce(`Slide ${this.currentIndex+1} of ${this.cards.length}`),t.emitChange&&this.emitChange()}}updateDotsVisualState(){if(this.dots.length)for(let t=0;t<this.dots.length;t++){const e=this.dots[t],s=t===this.currentIndex;e.dataset.active=s?"true":"false"}}lockUntilTransitionEnd(){this.pendingAnimToken++;const t=this.pendingAnimToken;if(L()){this.unlockAnimation();return}const e=this.cards[this.currentIndex];if(!e){this.unlockAnimation();return}const s=getComputedStyle(e),n=k(s,"transform");if(n<=0){this.unlockAnimation();return}const i=x(s.getPropertyValue("--cfc-transition-ms").trim(),400),c=Math.max(n,i)+60;this.animFallbackTimerId!==null&&window.clearTimeout(this.animFallbackTimerId),this.animFallbackTimerId=window.setTimeout(()=>{this.pendingAnimToken===t&&this.unlockAnimation()},c)}unlockAnimation(){this.isAnimating=!1,this.animFallbackTimerId!==null&&(window.clearTimeout(this.animFallbackTimerId),this.animFallbackTimerId=null)}emitChange(){this.dispatchEvent(new CustomEvent("coverflow-carousel:change",{detail:{index:this.currentIndex,length:this.cards.length}}))}dispatchReady(){this.dispatchEvent(new CustomEvent("coverflow-carousel:ready",{detail:{index:this.currentIndex,length:this.cards.length}}))}announce(t){this.liveRegion.textContent="",queueMicrotask(()=>{this.liveRegion.textContent=t})}reflectIndexAttr(){this.reflectGuard=!0;try{this.setAttribute("index",String(this.currentIndex))}finally{this.reflectGuard=!1}}applyStyles(t){if(typeof t=="string"){if(b(this.shadow)){const e=E(t);if(e){this.shadow.adoptedStyleSheets=[e],this.styleEl=null;return}}this.styleEl||(this.styleEl=document.createElement("style")),this.styleEl.textContent=t;return}if(t&&b(this.shadow)){this.shadow.adoptedStyleSheets=[t],this.styleEl=null;return}if(f.defaultStylesheet&&b(this.shadow)){this.shadow.adoptedStyleSheets=[f.defaultStylesheet],this.styleEl=null;return}this.styleEl||(this.styleEl=document.createElement("style")),this.styleEl.textContent=_}}function F(a={}){const{selector:t="coverflow-carousel",onReady:e,onChange:s,onScratchComplete:n,stylesheet:i}=a,r=Array.from(document.querySelectorAll(t));return(e||s||n)&&r.forEach(c=>{e&&c.addEventListener("coverflow-carousel:ready",o=>{e(c,o.detail)}),s&&c.addEventListener("coverflow-carousel:change",o=>{s(c,o.detail)}),n&&c.addEventListener("coverflow-carousel:scratch-complete",o=>{n(c,o.detail)})}),i&&r.forEach(c=>{const o=c;typeof i=="string"?o.adoptStyles(i):o.adoptStylesheet(i)}),r}function M(a="coverflow-carousel"){typeof window>"u"||!("customElements"in window)||customElements.get(a)||customElements.define(a,f)}d.CoverflowCarouselElement=f,d.coverflowCarouselCssText=I,d.initCoverflowCarousels=F,d.registerCoverflowCarouselElement=M,Object.defineProperty(d,Symbol.toStringTag,{value:"Module"})}));
167
+ `,I=d;function E(a){return"adoptedStyleSheets"in a}function y(a){try{const t=new CSSStyleSheet;return t.replaceSync(a),t}catch{return null}}const C=y(d),k=d;function g(a){return a.split(",").map(t=>t.trim()).filter(Boolean)}function x(a,t){const n=a.trim();if(!n)return t;if(n.endsWith("ms")){const e=Number(n.slice(0,-2).trim());return Number.isFinite(e)?e:t}if(n.endsWith("s")){const e=Number(n.slice(0,-1).trim());return Number.isFinite(e)?e*1e3:t}const s=Number(n);return Number.isFinite(s)?s:t}function _(a,t){const n=g(a.transitionProperty),s=g(a.transitionDuration),e=g(a.transitionDelay);if(!n.length)return 0;const r=(S=>{const u=n.indexOf(S);if(u>=0)return u;const A=n.indexOf("all");return A>=0?A:-1})(t);if(r<0)return 0;const o=s[Math.min(r,s.length-1)]??"0s",l=e[Math.min(r,e.length-1)]??"0s",c=x(o,0),p=x(l,0);return Math.max(0,c+p)}function T(){return window.matchMedia?.("(prefers-reduced-motion: reduce)")?.matches??!1}function m(a,t,n){if(!a.hasAttribute(t))return n;const s=a.getAttribute(t);return s==null||s===""?!0:s!=="false"}function L(a,t,n){const s=a.getAttribute(t);if(s==null)return n;const e=Number(s.trim());return Number.isFinite(e)?Math.trunc(e):n}function w(a,t){const n=a.getAttribute(t);if(n==null)return null;const s=n.trim();return s||null}function v(a,t){return t<=0?0:(a%t+t)%t}function D(a,t,n){const s=t-a,e=n/2;return s>e?s-n:s<-e?s+n:s}const b=1;let F=0;class f extends HTMLElement{static defaultStylesheet=C;static observedAttributes=["start-index","index","show-dots","show-arrows","announce-changes"];shadow=this.attachShadow({mode:"open"});instanceId=`cfc-${++F}`;rootEl;trackEl;dotsEl;prevBtn;nextBtn;liveRegion;baseStyles=null;overrideStyles=null;baseStyleEl=null;overrideStyleEl=null;cards=[];dots=[];currentIndex=0;isAnimating=!1;hasAppliedInitialLayout=!1;lastLayoutIndex=null;lastVisibleSet=new Set;pendingAnimToken=0;animFallbackTimerId=null;cleanup=[];lightDomObserverOptions={childList:!0,attributes:!0,subtree:!0};lightDomObserver=null;refreshScheduled=!1;suppressMutations=!1;reflectGuard=!1;connectedCallback(){this.render(),this.readAttributes({isInit:!0}),this.refresh(),this.setupLightDomObserver()}disconnectedCallback(){this.destroy()}attributeChangedCallback(t,n,s){if(!this.isConnected||this.reflectGuard)return;this.readAttributes({isInit:!1});const e=w(this,"index");if(e!=null){const i=v(Number(e),this.cards.length);if(Number.isFinite(i)&&i!==this.currentIndex){this.goTo(i);return}}this.applyLayoutAndA11y({announce:!0,emitChange:!1})}next(){this.goTo(this.currentIndex+1)}prev(){this.goTo(this.currentIndex-1)}goTo(t){if(this.isAnimating)return;const n=v(t,this.cards.length);n!==this.currentIndex&&(this.isAnimating=!0,this.currentIndex=n,this.reflectIndexAttr(),this.applyLayoutAndA11y({announce:!0,emitChange:!0}),this.lockUntilTransitionEnd())}refresh(){const t=this.lightDomObserver;t&&(this.suppressMutations=!0,t.disconnect(),t.takeRecords()),this.rebuildCardsFromLightDom();const n=L(this,"start-index",0),s=w(this,"index"),e=s!=null?Number(s):n,i=Number.isFinite(e)?e:0;this.currentIndex=v(i,this.cards.length),this.reflectIndexAttr(),this.buildDots(),this.hasAppliedInitialLayout=!1,this.lastLayoutIndex=null,this.lastVisibleSet=new Set,this.applyLayoutAndA11y({announce:!0,emitChange:!1}),this.dispatchReady(),t&&(t.observe(this,this.lightDomObserverOptions),t.takeRecords(),this.suppressMutations=!1)}destroy(){this.animFallbackTimerId!==null&&(window.clearTimeout(this.animFallbackTimerId),this.animFallbackTimerId=null),this.cleanup.forEach(t=>{t()}),this.cleanup=[],this.isAnimating=!1}adoptStylesheet(t){this.setBaseStyles(t)}adoptStyles(t){this.setBaseStyles(t)}adoptStyleOverrides(t){this.setOverrideStyles(t)}readAttributes(t){this.rootEl||this.render();const n=m(this,"show-arrows",!1),s=m(this,"show-dots",!1);this.prevBtn.style.display=n?"":"none",this.nextBtn.style.display=n?"":"none",this.dotsEl.style.display=s?"":"none";const e=this.getAttribute("aria-label");e&&this.rootEl.setAttribute("aria-label",e),t.isInit&&this.setAttribute("aria-roledescription","carousel")}render(){this.applyAllStyles(),this.rootEl=document.createElement("div"),this.rootEl.className="cfc",this.trackEl=document.createElement("div"),this.trackEl.className="cfc__track",this.prevBtn=document.createElement("button"),this.prevBtn.type="button",this.prevBtn.className="cfc__arrow cfc__arrow--left",this.prevBtn.setAttribute("aria-label","Previous"),this.prevBtn.textContent="‹",this.nextBtn=document.createElement("button"),this.nextBtn.type="button",this.nextBtn.className="cfc__arrow cfc__arrow--right",this.nextBtn.setAttribute("aria-label","Next"),this.nextBtn.textContent="›",this.dotsEl=document.createElement("div"),this.dotsEl.className="cfc__dots",this.liveRegion=document.createElement("div"),this.liveRegion.className="cfc__sr",this.liveRegion.setAttribute("aria-live","polite"),this.liveRegion.setAttribute("aria-atomic","true"),this.rootEl.append(this.trackEl,this.prevBtn,this.nextBtn,this.dotsEl,this.liveRegion),this.shadow.innerHTML="",this.baseStyleEl&&this.shadow.append(this.baseStyleEl),this.overrideStyleEl&&this.shadow.append(this.overrideStyleEl),this.shadow.append(this.rootEl),this.bindEvents()}bindEvents(){this.cleanup.forEach(i=>{i()}),this.cleanup=[];const t=()=>this.prev(),n=()=>this.next();this.prevBtn.addEventListener("click",t),this.nextBtn.addEventListener("click",n),this.cleanup.push(()=>this.prevBtn.removeEventListener("click",t)),this.cleanup.push(()=>this.nextBtn.removeEventListener("click",n));const s=i=>{if(!this.isAnimating||i.propertyName!=="transform"||!(i.target instanceof HTMLElement))return;const r=this.cards[this.currentIndex];r&&i.target===r&&this.unlockAnimation()};this.rootEl.addEventListener("transitionend",s),this.cleanup.push(()=>this.rootEl.removeEventListener("transitionend",s));const e=i=>{const r=i.target;if(!(r instanceof HTMLElement)||r.tagName!=="SCRATCH-REVEAL")return;const o=i.composedPath?.();if(!o?.length)return;const l=o.find(u=>u instanceof HTMLElement&&u.classList.contains("cfc__card")&&u.dataset.cfcIndex!=null);if(!l)return;const c=Number(l.dataset.cfcIndex);if(!Number.isFinite(c))return;const S=i.detail?.percent??100;this.dispatchEvent(new CustomEvent("coverflow-carousel:scratch-complete",{detail:{index:c,length:this.cards.length,percent:S},bubbles:!0,composed:!0}))};this.rootEl.addEventListener("complete",e),this.cleanup.push(()=>this.rootEl.removeEventListener("complete",e))}setupLightDomObserver(){this.lightDomObserver||(this.lightDomObserver=new MutationObserver(t=>{this.suppressMutations||!t.some(s=>!(s.type==="attributes"&&s.attributeName==="slot"))||this.scheduleRefreshFromMutations()}),this.lightDomObserver.observe(this,{...this.lightDomObserverOptions}),this.cleanup.push(()=>{this.lightDomObserver?.disconnect(),this.lightDomObserver=null}))}scheduleRefreshFromMutations(){this.refreshScheduled||(this.refreshScheduled=!0,queueMicrotask(()=>{this.refreshScheduled=!1,this.isConnected&&this.refresh()}))}rebuildCardsFromLightDom(){const t=Array.from(this.trackEl.children).filter(e=>e instanceof HTMLElement&&e.classList.contains("cfc__card")),n=[],s=Array.from(this.children).filter(e=>e instanceof HTMLElement);for(let e=0;e<s.length;e++){const i=s[e],r=t[e]??document.createElement("div"),o=`${this.instanceId}-slot-${e}`;t[e]||(r.className="cfc__card",this.trackEl.append(r)),i.getAttribute("slot")!==o&&i.setAttribute("slot",o);const l=r.querySelector("slot");let c;l instanceof HTMLSlotElement?c=l:(c=document.createElement("slot"),r.replaceChildren(c)),c.name!==o&&(c.name=o),n.push(r)}for(let e=s.length;e<t.length;e++)t[e]?.remove();this.cards=n,this.hasAppliedInitialLayout=!1,this.lastLayoutIndex=null,this.lastVisibleSet=new Set}getVisibleSet(){const t=this.cards.length,n=new Set;if(t<=0)return n;const s=Math.floor(t/2);if(b>=s){for(let e=0;e<t;e++)n.add(e);return n}for(let e=-b;e<=b;e++)n.add(v(this.currentIndex+e,t));return n}buildDots(){const t=Array.from(this.dotsEl.children).filter(i=>i instanceof HTMLElement&&i.classList.contains("cfc__dot"));if(!m(this,"show-dots",!1)){t.forEach(i=>{i.remove()}),this.dots=[];return}const s=[],e=this.cards.length;for(let i=0;i<e;i++){const r=t[i]??document.createElement("span");t[i]||this.dotsEl.append(r),r.className="cfc__dot",r.setAttribute("aria-hidden","true"),s.push(r)}for(let i=e;i<t.length;i++)t[i]?.remove();this.dots=s,this.updateDotsVisualState()}computeCardState(t){const n=this.cards.length,s=D(this.currentIndex,t,n),e=Math.abs(s);return{index:t,delta:s,abs:e,isVisible:e<=b,isActive:t===this.currentIndex}}applyCardState(t,n){t.setAttribute("aria-hidden",n.isVisible?"false":"true"),t.dataset.active=n.isActive?"true":"false",t.dataset.cfcIndex=String(n.index),t.setAttribute("role","group"),t.setAttribute("aria-roledescription","slide"),t.setAttribute("aria-setsize",String(this.cards.length)),t.setAttribute("aria-posinset",String(n.index+1));const s=`${this.instanceId}-slide-${n.index}`;t.id=s;const e=String(n.delta),i=String(n.abs),r=t.dataset.cfcDelta,o=t.dataset.cfcAbs;r!==e&&(t.style.setProperty("--cfc-delta",e),t.dataset.cfcDelta=e),o!==i&&(t.style.setProperty("--cfc-abs",i),t.dataset.cfcAbs=i)}applyLayoutAndA11y(t){if(this.cards.length){if(this.hasAppliedInitialLayout){const n=this.getVisibleSet(),s=new Set;this.lastVisibleSet.forEach(e=>{s.add(e)}),n.forEach(e=>{s.add(e)}),this.lastLayoutIndex!=null&&s.add(this.lastLayoutIndex),s.add(this.currentIndex),s.forEach(e=>{const i=this.cards[e];if(!i)return;const r=this.computeCardState(e);this.applyCardState(i,r)}),this.lastLayoutIndex=this.currentIndex,this.lastVisibleSet=n}else{for(let n=0;n<this.cards.length;n++){const s=this.cards[n];if(!s)continue;const e=this.computeCardState(n);this.applyCardState(s,e)}this.hasAppliedInitialLayout=!0,this.lastLayoutIndex=this.currentIndex,this.lastVisibleSet=this.getVisibleSet()}this.updateDotsVisualState(),t.announce&&m(this,"announce-changes",!0)&&this.announce(`Slide ${this.currentIndex+1} of ${this.cards.length}`),t.emitChange&&this.emitChange()}}updateDotsVisualState(){if(this.dots.length)for(let t=0;t<this.dots.length;t++){const n=this.dots[t],s=t===this.currentIndex;n.dataset.active=s?"true":"false"}}lockUntilTransitionEnd(){this.pendingAnimToken++;const t=this.pendingAnimToken;if(T()){this.unlockAnimation();return}const n=this.cards[this.currentIndex];if(!n){this.unlockAnimation();return}const s=getComputedStyle(n),e=_(s,"transform");if(e<=0){this.unlockAnimation();return}const i=x(s.getPropertyValue("--cfc-transition-ms").trim(),400),o=Math.max(e,i)+60;this.animFallbackTimerId!==null&&window.clearTimeout(this.animFallbackTimerId),this.animFallbackTimerId=window.setTimeout(()=>{this.pendingAnimToken===t&&this.unlockAnimation()},o)}unlockAnimation(){this.isAnimating=!1,this.animFallbackTimerId!==null&&(window.clearTimeout(this.animFallbackTimerId),this.animFallbackTimerId=null)}emitChange(){this.dispatchEvent(new CustomEvent("coverflow-carousel:change",{detail:{index:this.currentIndex,length:this.cards.length}}))}dispatchReady(){this.dispatchEvent(new CustomEvent("coverflow-carousel:ready",{detail:{index:this.currentIndex,length:this.cards.length}}))}announce(t){this.liveRegion.textContent="",queueMicrotask(()=>{this.liveRegion.textContent=t})}reflectIndexAttr(){this.reflectGuard=!0;try{this.setAttribute("index",String(this.currentIndex))}finally{this.reflectGuard=!1}}setBaseStyles(t){this.baseStyles=t??null,this.applyAllStyles()}setOverrideStyles(t){this.overrideStyles=t??null,this.applyAllStyles()}applyAllStyles(){const t=this.baseStyles,n=this.overrideStyles;if(E(this.shadow)){const e=[];if(typeof t=="string"){const i=y(t);if(!i){this.applyAllStylesFallback();return}e.push(i)}else if(t)e.push(t);else if(f.defaultStylesheet)e.push(f.defaultStylesheet);else{this.applyAllStylesFallback();return}if(typeof n=="string"){const i=y(n);if(!i){this.applyAllStylesFallback();return}e.push(i)}else n&&e.push(n);this.shadow.adoptedStyleSheets=e,this.baseStyleEl?.remove(),this.overrideStyleEl?.remove(),this.baseStyleEl=null,this.overrideStyleEl=null;return}this.applyAllStylesFallback()}applyAllStylesFallback(){const t=this.baseStyles,n=this.overrideStyles;E(this.shadow)&&(this.shadow.adoptedStyleSheets=[]);const s=typeof t=="string"?t:k,e=typeof n=="string"?n:"";this.baseStyleEl||(this.baseStyleEl=document.createElement("style")),this.baseStyleEl.textContent=s,e?(this.overrideStyleEl||(this.overrideStyleEl=document.createElement("style")),this.overrideStyleEl.textContent=e):(this.overrideStyleEl?.remove(),this.overrideStyleEl=null)}}function M(a={}){const{selector:t="coverflow-carousel",onReady:n,onChange:s,onScratchComplete:e,stylesheet:i,styleOverrides:r}=a,o=Array.from(document.querySelectorAll(t));return(n||s||e)&&o.forEach(l=>{n&&l.addEventListener("coverflow-carousel:ready",c=>{n(l,c.detail)}),s&&l.addEventListener("coverflow-carousel:change",c=>{s(l,c.detail)}),e&&l.addEventListener("coverflow-carousel:scratch-complete",c=>{e(l,c.detail)})}),i&&o.forEach(l=>{const c=l;typeof i=="string"?c.adoptStyles(i):c.adoptStylesheet(i)}),r&&o.forEach(l=>{l.adoptStyleOverrides(r)}),o}function N(a="coverflow-carousel"){typeof window>"u"||!("customElements"in window)||customElements.get(a)||customElements.define(a,f)}h.CoverflowCarouselElement=f,h.coverflowCarouselCssText=I,h.initCoverflowCarousels=M,h.registerCoverflowCarouselElement=N,Object.defineProperty(h,Symbol.toStringTag,{value:"Module"})}));
package/dist/init.d.ts CHANGED
@@ -14,5 +14,6 @@ export type InitCoverflowCarouselsOptions = {
14
14
  percent: number;
15
15
  }) => void;
16
16
  stylesheet?: CSSStyleSheet | string | null;
17
+ styleOverrides?: CSSStyleSheet | string | null;
17
18
  };
18
19
  export declare function initCoverflowCarousels(options?: InitCoverflowCarouselsOptions): HTMLElement[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "coverflow-carousel",
3
- "version": "0.2.0-dev.1",
3
+ "version": "0.2.0",
4
4
  "description": "Tiny coverflow carousel Web Component. Exposes API via attributes + events (explicit element registration).",
5
5
  "author": "ux-ui.pro",
6
6
  "license": "MIT",