coverflow-carousel 0.2.0-dev.2 → 0.3.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
@@ -49,7 +49,6 @@ registerCoverflowCarouselElement();
49
49
  import {
50
50
  registerCoverflowCarouselElement,
51
51
  initCoverflowCarousels,
52
- coverflowCarouselCssText,
53
52
  } from 'coverflow-carousel';
54
53
 
55
54
  registerCoverflowCarouselElement();
@@ -73,26 +72,38 @@ initCoverflowCarousels({
73
72
  else if (detail.index === 1) (el as any).goTo?.(2);
74
73
  else if (detail.index === 2) console.log('final');
75
74
  },
76
- // Optional styles:
77
- // - CSSStyleSheet (constructable stylesheet)
78
- // - string (e.g. imported via ?raw)
79
- // stylesheet: coverflowCarouselCssText,
80
75
  });
81
76
  ```
82
77
 
83
- <sub>JS: custom styles from `?raw` (CSS/SCSS)</sub>
78
+ <sub>Recommended: single overrides file (`?raw`) — variables + rules in one place</sub>
84
79
  ```javascript
85
80
  import { registerCoverflowCarouselElement, initCoverflowCarousels } from 'coverflow-carousel';
86
- import MyCfcCss from './assets/cfc.custom.css?raw';
81
+ import CfcOverrides from './assets/cfc.overrides.css?raw';
87
82
 
88
83
  registerCoverflowCarouselElement();
89
84
 
90
85
  initCoverflowCarousels({
91
- stylesheet: MyCfcCss,
86
+ styleOverrides: CfcOverrides,
92
87
  });
93
88
  ```
94
89
 
95
- <sub>JS: default styles shipped with the package</sub>
90
+ <sub>`cfc.overrides.css` example</sub>
91
+ ```css
92
+ :host {
93
+ /* CSS variables (override defaults) */
94
+ --cfc-card-ar-num: 1.6;
95
+ }
96
+
97
+ /* Example rule: only active slide is interactive */
98
+ .cfc__card slot::slotted(*) {
99
+ pointer-events: none;
100
+ }
101
+ .cfc__card[data-active="true"] slot::slotted(*) {
102
+ pointer-events: auto;
103
+ }
104
+ ```
105
+
106
+ <sub>Advanced: replace base styles completely</sub>
96
107
  ```javascript
97
108
  import {
98
109
  registerCoverflowCarouselElement,
@@ -126,36 +137,36 @@ el?.refresh();
126
137
  <br>
127
138
 
128
139
  # 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). |
140
+ | Option (attribute) | Type | Default | Description |
141
+ |:------------------:|:---------:|:-------:|:-----------------------------------------------------------------------------------------------------------------------------------------------|
142
+ | `start-index` | `number` | `0` | Start index if `index` is not set. |
143
+ | `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`. |
144
+ | `show-dots` | `boolean` | `false` | Shows pagination dots (decorative, non-interactive). |
145
+ | `show-arrows` | `boolean` | `false` | Shows Prev/Next arrows. |
136
146
 
137
147
  <br>
138
148
 
139
149
  # 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. |
150
+ | Event | Detail | Description |
151
+ |---------------------------------------|------------------------------------------------------|---------------------------------------------------------------------------------------------|
152
+ | `coverflow-carousel:ready` | `{ index: number, length: number }` | Fired after the first `refresh()` (when slides are built and layout/a11y is applied). |
153
+ | `coverflow-carousel:change` | `{ index: number, length: number }` | Fired on active slide change via `prev/next/goTo` (and when `index` is updated externally). |
154
+ | `coverflow-carousel:scratch-complete` | `{ index: number, length: number, percent: number }` | Fired when a descendant `<scratch-reveal>` dispatches `complete` for a slide. |
145
155
 
146
156
  <br>
147
157
 
148
158
  # 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. |
159
+ | Method | Description |
160
+ |-------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
161
+ | `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`). |
162
+ | `prev(): void` | Go to the previous slide (circular). |
163
+ | `next(): void` | Go to the next slide (circular). |
164
+ | `goTo(index: number): void` | Go to the given index (circular normalization). While animating, repeated transitions are ignored. |
165
+ | `refresh(): void` | Rebuilds cards from current `children` (useful after dynamic changes) and dispatches `coverflow-carousel:ready`. |
166
+ | `destroy(): void` | Removes event handlers and cancels animation-related timers. |
167
+ | `adoptStylesheet(sheet: CSSStyleSheet): void` | Applies a stylesheet via `adoptedStyleSheets` (when supported). |
168
+ | `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. |
169
+ | `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
170
 
160
171
  <br>
161
172
 
@@ -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)}
@@ -9,8 +9,10 @@ export declare class CoverflowCarouselElement extends HTMLElement {
9
9
  private dotsEl;
10
10
  private prevBtn;
11
11
  private nextBtn;
12
- private liveRegion;
13
- private styleEl;
12
+ private baseStyles;
13
+ private overrideStyles;
14
+ private baseStyleEl;
15
+ private overrideStyleEl;
14
16
  private cards;
15
17
  private dots;
16
18
  private currentIndex;
@@ -36,6 +38,7 @@ export declare class CoverflowCarouselElement extends HTMLElement {
36
38
  destroy(): void;
37
39
  adoptStylesheet(sheet: CSSStyleSheet): void;
38
40
  adoptStyles(styles: StylesInput): void;
41
+ adoptStyleOverrides(styles: StylesInput): void;
39
42
  private readAttributes;
40
43
  private render;
41
44
  private bindEvents;
@@ -52,7 +55,9 @@ export declare class CoverflowCarouselElement extends HTMLElement {
52
55
  private unlockAnimation;
53
56
  private emitChange;
54
57
  private dispatchReady;
55
- private announce;
56
58
  private reflectIndexAttr;
57
- private applyStyles;
59
+ private setBaseStyles;
60
+ private setOverrideStyles;
61
+ private applyAllStyles;
62
+ private applyAllStylesFallback;
58
63
  }
package/dist/index.cjs.js CHANGED
@@ -1,12 +1,10 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const x=`:host {
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const S=`: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%;
@@ -151,17 +151,4 @@
151
151
  background: Crimson;
152
152
  transform: scale(1.2);
153
153
  }
154
-
155
- /* SR-only */
156
- .cfc__sr {
157
- position: absolute;
158
- width: 1px;
159
- height: 1px;
160
- padding: 0;
161
- margin: -1px;
162
- overflow: hidden;
163
- clip: rect(0, 0, 0, 0);
164
- white-space: nowrap;
165
- border: 0;
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 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=(m=>{const h=n.indexOf(m);if(h>=0)return h;const E=n.indexOf("all");return E>=0?E:-1})(t);if(r<0)return 0;const c=s[Math.min(r,s.length-1)]??"0s",o=e[Math.min(r,e.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,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 f(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 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=[];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=f(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=f(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=f(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.applyStyles(t)}adoptStyles(t){this.applyStyles(t)}readAttributes(t){this.rootEl||this.render();const n=u(this,"show-arrows",!1),s=u(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.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(),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 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",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"),c=`${this.instanceId}-slot-${e}`;t[e]||(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),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(p>=s){for(let e=0;e<t;e++)n.add(e);return n}for(let e=-p;e<=p;e++)n.add(f(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(!u(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<=p,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,c=t.dataset.cfcAbs;r!==e&&(t.style.setProperty("--cfc-delta",e),t.dataset.cfcDelta=e),c!==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&&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 n=this.dots[t],s=t===this.currentIndex;n.dataset.active=s?"true":"false"}}lockUntilTransitionEnd(){this.pendingAnimToken++;const t=this.pendingAnimToken;if(k()){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),c=Math.max(e,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 n=S(t);if(n){this.shadow.adoptedStyleSheets=[n],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 M(a={}){const{selector:t="coverflow-carousel",onReady:n,onChange:s,onScratchComplete:e,stylesheet:i}=a,r=Array.from(document.querySelectorAll(t));return(n||s||e)&&r.forEach(c=>{n&&c.addEventListener("coverflow-carousel:ready",o=>{n(c,o.detail)}),s&&c.addEventListener("coverflow-carousel:change",o=>{s(c,o.detail)}),e&&c.addEventListener("coverflow-carousel:scratch-complete",o=>{e(c,o.detail)})}),i&&r.forEach(c=>{const o=c;typeof i=="string"?o.adoptStyles(i):o.adoptStylesheet(i)}),r}function N(a="coverflow-carousel"){typeof window>"u"||!("customElements"in window)||customElements.get(a)||customElements.define(a,d)}exports.CoverflowCarouselElement=d;exports.coverflowCarouselCssText=A;exports.initCoverflowCarousels=M;exports.registerCoverflowCarouselElement=N;
154
+ `,A=S;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(S),k=S;function v(a){return a.split(",").map(t=>t.trim()).filter(Boolean)}function g(a,t){const s=a.trim();if(!s)return t;if(s.endsWith("ms")){const e=Number(s.slice(0,-2).trim());return Number.isFinite(e)?e:t}if(s.endsWith("s")){const e=Number(s.slice(0,-1).trim());return Number.isFinite(e)?e*1e3:t}const n=Number(s);return Number.isFinite(n)?n:t}function C(a,t){const s=v(a.transitionProperty),n=v(a.transitionDuration),e=v(a.transitionDelay);if(!s.length)return 0;const r=(m=>{const h=s.indexOf(m);if(h>=0)return h;const x=s.indexOf("all");return x>=0?x:-1})(t);if(r<0)return 0;const o=n[Math.min(r,n.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 b(a,t,s){if(!a.hasAttribute(t))return s;const n=a.getAttribute(t);return n==null||n===""?!0:n!=="false"}function L(a,t,s){const n=a.getAttribute(t);if(n==null)return s;const e=Number(n.trim());return Number.isFinite(e)?Math.trunc(e):s}function w(a,t){const s=a.getAttribute(t);if(s==null)return null;const n=s.trim();return n||null}function f(a,t){return t<=0?0:(a%t+t)%t}function T(a,t,s){const n=t-a,e=s/2;return n>e?n-s:n<-e?n+s:n}const p=1;let D=0;class u extends HTMLElement{static defaultStylesheet=I;static observedAttributes=["start-index","index","show-dots","show-arrows"];shadow=this.attachShadow({mode:"open"});instanceId=`cfc-${++D}`;rootEl;trackEl;dotsEl;prevBtn;nextBtn;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,s,n){if(!this.isConnected||this.reflectGuard)return;this.readAttributes({isInit:!1});const e=w(this,"index");if(e!=null){const i=f(Number(e),this.cards.length);if(Number.isFinite(i)&&i!==this.currentIndex){this.goTo(i);return}}this.applyLayoutAndA11y({emitChange:!1})}next(){this.goTo(this.currentIndex+1)}prev(){this.goTo(this.currentIndex-1)}goTo(t){if(this.isAnimating)return;const s=f(t,this.cards.length);s!==this.currentIndex&&(this.isAnimating=!0,this.currentIndex=s,this.reflectIndexAttr(),this.applyLayoutAndA11y({emitChange:!0}),this.lockUntilTransitionEnd())}refresh(){const t=this.lightDomObserver;t&&(this.suppressMutations=!0,t.disconnect(),t.takeRecords()),this.rebuildCardsFromLightDom();const s=L(this,"start-index",0),n=w(this,"index"),e=n!=null?Number(n):s,i=Number.isFinite(e)?e:0;this.currentIndex=f(i,this.cards.length),this.reflectIndexAttr(),this.buildDots(),this.hasAppliedInitialLayout=!1,this.lastLayoutIndex=null,this.lastVisibleSet=new Set,this.applyLayoutAndA11y({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 s=b(this,"show-arrows",!1),n=b(this,"show-dots",!1);this.prevBtn.style.display=s?"":"none",this.nextBtn.style.display=s?"":"none",this.dotsEl.style.display=n?"":"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.rootEl.append(this.trackEl,this.prevBtn,this.nextBtn,this.dotsEl),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(),s=()=>this.next();this.prevBtn.addEventListener("click",t),this.nextBtn.addEventListener("click",s),this.cleanup.push(()=>this.prevBtn.removeEventListener("click",t)),this.cleanup.push(()=>this.nextBtn.removeEventListener("click",s));const n=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",n),this.cleanup.push(()=>this.rootEl.removeEventListener("transitionend",n));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 m=i.detail?.percent??100;this.dispatchEvent(new CustomEvent("coverflow-carousel:scratch-complete",{detail:{index:c,length:this.cards.length,percent:m},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(n=>!(n.type==="attributes"&&n.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")),s=[],n=Array.from(this.children).filter(e=>e instanceof HTMLElement);for(let e=0;e<n.length;e++){const i=n[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),s.push(r)}for(let e=n.length;e<t.length;e++)t[e]?.remove();this.cards=s,this.hasAppliedInitialLayout=!1,this.lastLayoutIndex=null,this.lastVisibleSet=new Set}getVisibleSet(){const t=this.cards.length,s=new Set;if(t<=0)return s;const n=Math.floor(t/2);if(p>=n){for(let e=0;e<t;e++)s.add(e);return s}for(let e=-p;e<=p;e++)s.add(f(this.currentIndex+e,t));return s}buildDots(){const t=Array.from(this.dotsEl.children).filter(i=>i instanceof HTMLElement&&i.classList.contains("cfc__dot"));if(!b(this,"show-dots",!1)){t.forEach(i=>{i.remove()}),this.dots=[];return}const n=[],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"),n.push(r)}for(let i=e;i<t.length;i++)t[i]?.remove();this.dots=n,this.updateDotsVisualState()}computeCardState(t){const s=this.cards.length,n=T(this.currentIndex,t,s),e=Math.abs(n);return{index:t,delta:n,abs:e,isVisible:e<=p,isActive:t===this.currentIndex}}applyCardState(t,s){t.setAttribute("aria-hidden",s.isVisible?"false":"true"),t.dataset.active=s.isActive?"true":"false",t.dataset.cfcIndex=String(s.index),t.setAttribute("role","group"),t.setAttribute("aria-roledescription","slide"),t.setAttribute("aria-setsize",String(this.cards.length)),t.setAttribute("aria-posinset",String(s.index+1));const n=`${this.instanceId}-slide-${s.index}`;t.id=n;const e=String(s.delta),i=String(s.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 s=this.getVisibleSet(),n=new Set;this.lastVisibleSet.forEach(e=>{n.add(e)}),s.forEach(e=>{n.add(e)}),this.lastLayoutIndex!=null&&n.add(this.lastLayoutIndex),n.add(this.currentIndex),n.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=s}else{for(let s=0;s<this.cards.length;s++){const n=this.cards[s];if(!n)continue;const e=this.computeCardState(s);this.applyCardState(n,e)}this.hasAppliedInitialLayout=!0,this.lastLayoutIndex=this.currentIndex,this.lastVisibleSet=this.getVisibleSet()}this.updateDotsVisualState(),t.emitChange&&this.emitChange()}}updateDotsVisualState(){if(this.dots.length)for(let t=0;t<this.dots.length;t++){const s=this.dots[t],n=t===this.currentIndex;s.dataset.active=n?"true":"false"}}lockUntilTransitionEnd(){this.pendingAnimToken++;const t=this.pendingAnimToken;if(_()){this.unlockAnimation();return}const s=this.cards[this.currentIndex];if(!s){this.unlockAnimation();return}const n=getComputedStyle(s),e=C(n,"transform");if(e<=0){this.unlockAnimation();return}const i=g(n.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}}))}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,s=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 s=="string"){const i=y(s);if(!i){this.applyAllStylesFallback();return}e.push(i)}else s&&e.push(s);this.shadow.adoptedStyleSheets=e,this.baseStyleEl?.remove(),this.overrideStyleEl?.remove(),this.baseStyleEl=null,this.overrideStyleEl=null;return}this.applyAllStylesFallback()}applyAllStylesFallback(){const t=this.baseStyles,s=this.overrideStyles;E(this.shadow)&&(this.shadow.adoptedStyleSheets=[]);const n=typeof t=="string"?t:k,e=typeof s=="string"?s:"";this.baseStyleEl||(this.baseStyleEl=document.createElement("style")),this.baseStyleEl.textContent=n,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:s,onChange:n,onScratchComplete:e,stylesheet:i,styleOverrides:r}=a,o=Array.from(document.querySelectorAll(t));return(s||n||e)&&o.forEach(l=>{s&&l.addEventListener("coverflow-carousel:ready",c=>{s(l,c.detail)}),n&&l.addEventListener("coverflow-carousel:change",c=>{n(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%;
@@ -151,24 +151,11 @@ const x = `:host {
151
151
  background: Crimson;
152
152
  transform: scale(1.2);
153
153
  }
154
-
155
- /* SR-only */
156
- .cfc__sr {
157
- position: absolute;
158
- width: 1px;
159
- height: 1px;
160
- padding: 0;
161
- margin: -1px;
162
- overflow: hidden;
163
- clip: rect(0, 0, 0, 0);
164
- white-space: nowrap;
165
- border: 0;
166
- }
167
154
  `, D = x;
168
- function v(a) {
155
+ function E(a) {
169
156
  return "adoptedStyleSheets" in a;
170
157
  }
171
- function S(a) {
158
+ function y(a) {
172
159
  try {
173
160
  const t = new CSSStyleSheet();
174
161
  return t.replaceSync(a), t;
@@ -176,75 +163,69 @@ function S(a) {
176
163
  return null;
177
164
  }
178
165
  }
179
- const A = S(x), I = x;
180
- function b(a) {
166
+ const w = y(x), I = x;
167
+ function v(a) {
181
168
  return a.split(",").map((t) => t.trim()).filter(Boolean);
182
169
  }
183
170
  function g(a, 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());
171
+ const s = a.trim();
172
+ if (!s) return t;
173
+ if (s.endsWith("ms")) {
174
+ const e = Number(s.slice(0, -2).trim());
188
175
  return Number.isFinite(e) ? e : t;
189
176
  }
190
- if (n.endsWith("s")) {
191
- const e = Number(n.slice(0, -1).trim());
177
+ if (s.endsWith("s")) {
178
+ const e = Number(s.slice(0, -1).trim());
192
179
  return Number.isFinite(e) ? e * 1e3 : t;
193
180
  }
194
- const s = Number(n);
195
- return Number.isFinite(s) ? s : t;
181
+ const n = Number(s);
182
+ return Number.isFinite(n) ? n : t;
196
183
  }
197
- function _(a, t) {
198
- const n = b(a.transitionProperty), s = b(a.transitionDuration), e = b(a.transitionDelay);
199
- if (!n.length) return 0;
184
+ function k(a, t) {
185
+ const s = v(a.transitionProperty), n = v(a.transitionDuration), e = v(a.transitionDelay);
186
+ if (!s.length) return 0;
200
187
  const r = ((m) => {
201
- const h = n.indexOf(m);
188
+ const h = s.indexOf(m);
202
189
  if (h >= 0) return h;
203
- const E = n.indexOf("all");
204
- return E >= 0 ? E : -1;
190
+ const S = s.indexOf("all");
191
+ return S >= 0 ? S : -1;
205
192
  })(t);
206
193
  if (r < 0) return 0;
207
- const c = s[Math.min(r, s.length - 1)] ?? "0s", o = e[Math.min(r, e.length - 1)] ?? "0s", l = g(c, 0), y = g(o, 0);
208
- return Math.max(0, l + y);
194
+ const o = n[Math.min(r, n.length - 1)] ?? "0s", l = e[Math.min(r, e.length - 1)] ?? "0s", c = g(o, 0), d = g(l, 0);
195
+ return Math.max(0, c + d);
209
196
  }
210
- function k() {
197
+ function _() {
211
198
  return window.matchMedia?.("(prefers-reduced-motion: reduce)")?.matches ?? !1;
212
199
  }
213
- function d(a, t, n) {
214
- if (!a.hasAttribute(t)) return n;
215
- const s = a.getAttribute(t);
216
- return s == null || s === "" ? !0 : s !== "false";
217
- }
218
- function C(a, t, n) {
219
- const s = a.getAttribute(t);
220
- if (s == null) return n;
221
- const e = Number(s.trim());
222
- return Number.isFinite(e) ? Math.trunc(e) : n;
200
+ function b(a, t, s) {
201
+ if (!a.hasAttribute(t)) return s;
202
+ const n = a.getAttribute(t);
203
+ return n == null || n === "" ? !0 : n !== "false";
223
204
  }
224
- function w(a, t) {
205
+ function C(a, t, s) {
225
206
  const n = a.getAttribute(t);
226
- if (n == null) return null;
227
- const s = n.trim();
228
- return s || null;
207
+ if (n == null) return s;
208
+ const e = Number(n.trim());
209
+ return Number.isFinite(e) ? Math.trunc(e) : s;
210
+ }
211
+ function A(a, t) {
212
+ const s = a.getAttribute(t);
213
+ if (s == null) return null;
214
+ const n = s.trim();
215
+ return n || null;
229
216
  }
230
217
  function u(a, t) {
231
218
  return t <= 0 ? 0 : (a % t + t) % t;
232
219
  }
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;
220
+ function L(a, t, s) {
221
+ const n = t - a, e = s / 2;
222
+ return n > e ? n - s : n < -e ? n + s : n;
236
223
  }
237
224
  const f = 1;
238
225
  let T = 0;
239
226
  class p extends HTMLElement {
240
- static defaultStylesheet = A;
241
- static observedAttributes = [
242
- "start-index",
243
- "index",
244
- "show-dots",
245
- "show-arrows",
246
- "announce-changes"
247
- ];
227
+ static defaultStylesheet = w;
228
+ static observedAttributes = ["start-index", "index", "show-dots", "show-arrows"];
248
229
  shadow = this.attachShadow({ mode: "open" });
249
230
  instanceId = `cfc-${++T}`;
250
231
  rootEl;
@@ -252,8 +233,10 @@ class p extends HTMLElement {
252
233
  dotsEl;
253
234
  prevBtn;
254
235
  nextBtn;
255
- liveRegion;
256
- styleEl = null;
236
+ baseStyles = null;
237
+ overrideStyles = null;
238
+ baseStyleEl = null;
239
+ overrideStyleEl = null;
257
240
  cards = [];
258
241
  dots = [];
259
242
  currentIndex = 0;
@@ -279,10 +262,10 @@ class p extends HTMLElement {
279
262
  disconnectedCallback() {
280
263
  this.destroy();
281
264
  }
282
- attributeChangedCallback(t, n, s) {
265
+ attributeChangedCallback(t, s, n) {
283
266
  if (!this.isConnected || this.reflectGuard) return;
284
267
  this.readAttributes({ isInit: !1 });
285
- const e = w(this, "index");
268
+ const e = A(this, "index");
286
269
  if (e != null) {
287
270
  const i = u(Number(e), this.cards.length);
288
271
  if (Number.isFinite(i) && i !== this.currentIndex) {
@@ -290,7 +273,7 @@ class p extends HTMLElement {
290
273
  return;
291
274
  }
292
275
  }
293
- this.applyLayoutAndA11y({ announce: !0, emitChange: !1 });
276
+ this.applyLayoutAndA11y({ emitChange: !1 });
294
277
  }
295
278
  next() {
296
279
  this.goTo(this.currentIndex + 1);
@@ -300,14 +283,14 @@ class p extends HTMLElement {
300
283
  }
301
284
  goTo(t) {
302
285
  if (this.isAnimating) return;
303
- const n = u(t, this.cards.length);
304
- n !== this.currentIndex && (this.isAnimating = !0, this.currentIndex = n, this.reflectIndexAttr(), this.applyLayoutAndA11y({ announce: !0, emitChange: !0 }), this.lockUntilTransitionEnd());
286
+ const s = u(t, this.cards.length);
287
+ s !== this.currentIndex && (this.isAnimating = !0, this.currentIndex = s, this.reflectIndexAttr(), this.applyLayoutAndA11y({ emitChange: !0 }), this.lockUntilTransitionEnd());
305
288
  }
306
289
  refresh() {
307
290
  const t = this.lightDomObserver;
308
291
  t && (this.suppressMutations = !0, t.disconnect(), t.takeRecords()), this.rebuildCardsFromLightDom();
309
- const n = C(this, "start-index", 0), s = w(this, "index"), e = s != null ? Number(s) : n, i = Number.isFinite(e) ? e : 0;
310
- this.currentIndex = u(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);
292
+ const s = C(this, "start-index", 0), n = A(this, "index"), e = n != null ? Number(n) : s, i = Number.isFinite(e) ? e : 0;
293
+ this.currentIndex = u(i, this.cards.length), this.reflectIndexAttr(), this.buildDots(), this.hasAppliedInitialLayout = !1, this.lastLayoutIndex = null, this.lastVisibleSet = /* @__PURE__ */ new Set(), this.applyLayoutAndA11y({ emitChange: !1 }), this.dispatchReady(), t && (t.observe(this, this.lightDomObserverOptions), t.takeRecords(), this.suppressMutations = !1);
311
294
  }
312
295
  destroy() {
313
296
  this.animFallbackTimerId !== null && (window.clearTimeout(this.animFallbackTimerId), this.animFallbackTimerId = null), this.cleanup.forEach((t) => {
@@ -315,48 +298,51 @@ class p extends HTMLElement {
315
298
  }), this.cleanup = [], this.isAnimating = !1;
316
299
  }
317
300
  adoptStylesheet(t) {
318
- this.applyStyles(t);
301
+ this.setBaseStyles(t);
319
302
  }
320
303
  adoptStyles(t) {
321
- this.applyStyles(t);
304
+ this.setBaseStyles(t);
305
+ }
306
+ adoptStyleOverrides(t) {
307
+ this.setOverrideStyles(t);
322
308
  }
323
309
  readAttributes(t) {
324
310
  this.rootEl || this.render();
325
- const n = d(this, "show-arrows", !1), s = d(this, "show-dots", !1);
326
- this.prevBtn.style.display = n ? "" : "none", this.nextBtn.style.display = n ? "" : "none", this.dotsEl.style.display = s ? "" : "none";
311
+ const s = b(this, "show-arrows", !1), n = b(this, "show-dots", !1);
312
+ this.prevBtn.style.display = s ? "" : "none", this.nextBtn.style.display = s ? "" : "none", this.dotsEl.style.display = n ? "" : "none";
327
313
  const e = this.getAttribute("aria-label");
328
314
  e && this.rootEl.setAttribute("aria-label", e), t.isInit && this.setAttribute("aria-roledescription", "carousel");
329
315
  }
330
316
  render() {
331
- 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();
317
+ 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.rootEl.append(this.trackEl, this.prevBtn, this.nextBtn, this.dotsEl), this.shadow.innerHTML = "", this.baseStyleEl && this.shadow.append(this.baseStyleEl), this.overrideStyleEl && this.shadow.append(this.overrideStyleEl), this.shadow.append(this.rootEl), this.bindEvents();
332
318
  }
333
319
  bindEvents() {
334
320
  this.cleanup.forEach((i) => {
335
321
  i();
336
322
  }), this.cleanup = [];
337
- const t = () => this.prev(), n = () => this.next();
338
- 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));
339
- const s = (i) => {
323
+ const t = () => this.prev(), s = () => this.next();
324
+ this.prevBtn.addEventListener("click", t), this.nextBtn.addEventListener("click", s), this.cleanup.push(() => this.prevBtn.removeEventListener("click", t)), this.cleanup.push(() => this.nextBtn.removeEventListener("click", s));
325
+ const n = (i) => {
340
326
  if (!this.isAnimating || i.propertyName !== "transform" || !(i.target instanceof HTMLElement)) return;
341
327
  const r = this.cards[this.currentIndex];
342
328
  r && i.target === r && this.unlockAnimation();
343
329
  };
344
- this.rootEl.addEventListener("transitionend", s), this.cleanup.push(() => this.rootEl.removeEventListener("transitionend", s));
330
+ this.rootEl.addEventListener("transitionend", n), this.cleanup.push(() => this.rootEl.removeEventListener("transitionend", n));
345
331
  const e = (i) => {
346
332
  const r = i.target;
347
333
  if (!(r instanceof HTMLElement) || r.tagName !== "SCRATCH-REVEAL") return;
348
- const c = i.composedPath?.();
349
- if (!c?.length) return;
350
- const o = c.find(
334
+ const o = i.composedPath?.();
335
+ if (!o?.length) return;
336
+ const l = o.find(
351
337
  (h) => h instanceof HTMLElement && h.classList.contains("cfc__card") && h.dataset.cfcIndex != null
352
338
  );
353
- if (!o) return;
354
- const l = Number(o.dataset.cfcIndex);
355
- if (!Number.isFinite(l)) return;
339
+ if (!l) return;
340
+ const c = Number(l.dataset.cfcIndex);
341
+ if (!Number.isFinite(c)) return;
356
342
  const m = i.detail?.percent ?? 100;
357
343
  this.dispatchEvent(
358
344
  new CustomEvent("coverflow-carousel:scratch-complete", {
359
- detail: { index: l, length: this.cards.length, percent: m },
345
+ detail: { index: c, length: this.cards.length, percent: m },
360
346
  bubbles: !0,
361
347
  composed: !0
362
348
  })
@@ -368,7 +354,7 @@ class p extends HTMLElement {
368
354
  }
369
355
  setupLightDomObserver() {
370
356
  this.lightDomObserver || (this.lightDomObserver = new MutationObserver((t) => {
371
- this.suppressMutations || !t.some((s) => !(s.type === "attributes" && s.attributeName === "slot")) || this.scheduleRefreshFromMutations();
357
+ this.suppressMutations || !t.some((n) => !(n.type === "attributes" && n.attributeName === "slot")) || this.scheduleRefreshFromMutations();
372
358
  }), this.lightDomObserver.observe(this, {
373
359
  ...this.lightDomObserverOptions
374
360
  }), this.cleanup.push(() => {
@@ -383,122 +369,122 @@ class p extends HTMLElement {
383
369
  rebuildCardsFromLightDom() {
384
370
  const t = Array.from(this.trackEl.children).filter(
385
371
  (e) => e instanceof HTMLElement && e.classList.contains("cfc__card")
386
- ), n = [], s = Array.from(this.children).filter(
372
+ ), s = [], n = Array.from(this.children).filter(
387
373
  (e) => e instanceof HTMLElement
388
374
  );
389
- for (let e = 0; e < s.length; e++) {
390
- const i = s[e], r = t[e] ?? document.createElement("div"), c = `${this.instanceId}-slot-${e}`;
391
- t[e] || (r.className = "cfc__card", this.trackEl.append(r)), i.getAttribute("slot") !== c && i.setAttribute("slot", c);
392
- const o = r.querySelector("slot");
393
- let l;
394
- o instanceof HTMLSlotElement ? l = o : (l = document.createElement("slot"), r.replaceChildren(l)), l.name !== c && (l.name = c), n.push(r);
375
+ for (let e = 0; e < n.length; e++) {
376
+ const i = n[e], r = t[e] ?? document.createElement("div"), o = `${this.instanceId}-slot-${e}`;
377
+ t[e] || (r.className = "cfc__card", this.trackEl.append(r)), i.getAttribute("slot") !== o && i.setAttribute("slot", o);
378
+ const l = r.querySelector("slot");
379
+ let c;
380
+ l instanceof HTMLSlotElement ? c = l : (c = document.createElement("slot"), r.replaceChildren(c)), c.name !== o && (c.name = o), s.push(r);
395
381
  }
396
- for (let e = s.length; e < t.length; e++)
382
+ for (let e = n.length; e < t.length; e++)
397
383
  t[e]?.remove();
398
- this.cards = n, this.hasAppliedInitialLayout = !1, this.lastLayoutIndex = null, this.lastVisibleSet = /* @__PURE__ */ new Set();
384
+ this.cards = s, this.hasAppliedInitialLayout = !1, this.lastLayoutIndex = null, this.lastVisibleSet = /* @__PURE__ */ new Set();
399
385
  }
400
386
  getVisibleSet() {
401
- const t = this.cards.length, n = /* @__PURE__ */ new Set();
402
- if (t <= 0) return n;
403
- const s = Math.floor(t / 2);
404
- if (f >= s) {
405
- for (let e = 0; e < t; e++) n.add(e);
406
- return n;
387
+ const t = this.cards.length, s = /* @__PURE__ */ new Set();
388
+ if (t <= 0) return s;
389
+ const n = Math.floor(t / 2);
390
+ if (f >= n) {
391
+ for (let e = 0; e < t; e++) s.add(e);
392
+ return s;
407
393
  }
408
394
  for (let e = -f; e <= f; e++)
409
- n.add(u(this.currentIndex + e, t));
410
- return n;
395
+ s.add(u(this.currentIndex + e, t));
396
+ return s;
411
397
  }
412
398
  buildDots() {
413
399
  const t = Array.from(this.dotsEl.children).filter(
414
400
  (i) => i instanceof HTMLElement && i.classList.contains("cfc__dot")
415
401
  );
416
- if (!d(this, "show-dots", !1)) {
402
+ if (!b(this, "show-dots", !1)) {
417
403
  t.forEach((i) => {
418
404
  i.remove();
419
405
  }), this.dots = [];
420
406
  return;
421
407
  }
422
- const s = [], e = this.cards.length;
408
+ const n = [], e = this.cards.length;
423
409
  for (let i = 0; i < e; i++) {
424
410
  const r = t[i] ?? document.createElement("span");
425
- t[i] || this.dotsEl.append(r), r.className = "cfc__dot", r.setAttribute("aria-hidden", "true"), s.push(r);
411
+ t[i] || this.dotsEl.append(r), r.className = "cfc__dot", r.setAttribute("aria-hidden", "true"), n.push(r);
426
412
  }
427
413
  for (let i = e; i < t.length; i++)
428
414
  t[i]?.remove();
429
- this.dots = s, this.updateDotsVisualState();
415
+ this.dots = n, this.updateDotsVisualState();
430
416
  }
431
417
  computeCardState(t) {
432
- const n = this.cards.length, s = L(this.currentIndex, t, n), e = Math.abs(s);
418
+ const s = this.cards.length, n = L(this.currentIndex, t, s), e = Math.abs(n);
433
419
  return {
434
420
  index: t,
435
- delta: s,
421
+ delta: n,
436
422
  abs: e,
437
423
  isVisible: e <= f,
438
424
  isActive: t === this.currentIndex
439
425
  };
440
426
  }
441
- applyCardState(t, n) {
442
- 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));
443
- const s = `${this.instanceId}-slide-${n.index}`;
444
- t.id = s;
445
- const e = String(n.delta), i = String(n.abs), r = t.dataset.cfcDelta, c = t.dataset.cfcAbs;
446
- r !== e && (t.style.setProperty("--cfc-delta", e), t.dataset.cfcDelta = e), c !== i && (t.style.setProperty("--cfc-abs", i), t.dataset.cfcAbs = i);
427
+ applyCardState(t, s) {
428
+ t.setAttribute("aria-hidden", s.isVisible ? "false" : "true"), t.dataset.active = s.isActive ? "true" : "false", t.dataset.cfcIndex = String(s.index), t.setAttribute("role", "group"), t.setAttribute("aria-roledescription", "slide"), t.setAttribute("aria-setsize", String(this.cards.length)), t.setAttribute("aria-posinset", String(s.index + 1));
429
+ const n = `${this.instanceId}-slide-${s.index}`;
430
+ t.id = n;
431
+ const e = String(s.delta), i = String(s.abs), r = t.dataset.cfcDelta, o = t.dataset.cfcAbs;
432
+ r !== e && (t.style.setProperty("--cfc-delta", e), t.dataset.cfcDelta = e), o !== i && (t.style.setProperty("--cfc-abs", i), t.dataset.cfcAbs = i);
447
433
  }
448
434
  applyLayoutAndA11y(t) {
449
435
  if (this.cards.length) {
450
436
  if (this.hasAppliedInitialLayout) {
451
- const n = this.getVisibleSet(), s = /* @__PURE__ */ new Set();
437
+ const s = this.getVisibleSet(), n = /* @__PURE__ */ new Set();
452
438
  this.lastVisibleSet.forEach((e) => {
453
- s.add(e);
454
- }), n.forEach((e) => {
455
- s.add(e);
456
- }), this.lastLayoutIndex != null && s.add(this.lastLayoutIndex), s.add(this.currentIndex), s.forEach((e) => {
439
+ n.add(e);
440
+ }), s.forEach((e) => {
441
+ n.add(e);
442
+ }), this.lastLayoutIndex != null && n.add(this.lastLayoutIndex), n.add(this.currentIndex), n.forEach((e) => {
457
443
  const i = this.cards[e];
458
444
  if (!i) return;
459
445
  const r = this.computeCardState(e);
460
446
  this.applyCardState(i, r);
461
- }), this.lastLayoutIndex = this.currentIndex, this.lastVisibleSet = n;
447
+ }), this.lastLayoutIndex = this.currentIndex, this.lastVisibleSet = s;
462
448
  } else {
463
- for (let n = 0; n < this.cards.length; n++) {
464
- const s = this.cards[n];
465
- if (!s) continue;
466
- const e = this.computeCardState(n);
467
- this.applyCardState(s, e);
449
+ for (let s = 0; s < this.cards.length; s++) {
450
+ const n = this.cards[s];
451
+ if (!n) continue;
452
+ const e = this.computeCardState(s);
453
+ this.applyCardState(n, e);
468
454
  }
469
455
  this.hasAppliedInitialLayout = !0, this.lastLayoutIndex = this.currentIndex, this.lastVisibleSet = this.getVisibleSet();
470
456
  }
471
- this.updateDotsVisualState(), t.announce && d(this, "announce-changes", !0) && this.announce(`Slide ${this.currentIndex + 1} of ${this.cards.length}`), t.emitChange && this.emitChange();
457
+ this.updateDotsVisualState(), t.emitChange && this.emitChange();
472
458
  }
473
459
  }
474
460
  updateDotsVisualState() {
475
461
  if (this.dots.length)
476
462
  for (let t = 0; t < this.dots.length; t++) {
477
- const n = this.dots[t], s = t === this.currentIndex;
478
- n.dataset.active = s ? "true" : "false";
463
+ const s = this.dots[t], n = t === this.currentIndex;
464
+ s.dataset.active = n ? "true" : "false";
479
465
  }
480
466
  }
481
467
  lockUntilTransitionEnd() {
482
468
  this.pendingAnimToken++;
483
469
  const t = this.pendingAnimToken;
484
- if (k()) {
470
+ if (_()) {
485
471
  this.unlockAnimation();
486
472
  return;
487
473
  }
488
- const n = this.cards[this.currentIndex];
489
- if (!n) {
474
+ const s = this.cards[this.currentIndex];
475
+ if (!s) {
490
476
  this.unlockAnimation();
491
477
  return;
492
478
  }
493
- const s = getComputedStyle(n), e = _(s, "transform");
479
+ const n = getComputedStyle(s), e = k(n, "transform");
494
480
  if (e <= 0) {
495
481
  this.unlockAnimation();
496
482
  return;
497
483
  }
498
- const i = g(s.getPropertyValue("--cfc-transition-ms").trim(), 400), c = Math.max(e, i) + 60;
484
+ const i = g(n.getPropertyValue("--cfc-transition-ms").trim(), 400), o = Math.max(e, i) + 60;
499
485
  this.animFallbackTimerId !== null && window.clearTimeout(this.animFallbackTimerId), this.animFallbackTimerId = window.setTimeout(() => {
500
486
  this.pendingAnimToken === t && this.unlockAnimation();
501
- }, c);
487
+ }, o);
502
488
  }
503
489
  unlockAnimation() {
504
490
  this.isAnimating = !1, this.animFallbackTimerId !== null && (window.clearTimeout(this.animFallbackTimerId), this.animFallbackTimerId = null);
@@ -517,11 +503,6 @@ class p extends HTMLElement {
517
503
  })
518
504
  );
519
505
  }
520
- announce(t) {
521
- this.liveRegion.textContent = "", queueMicrotask(() => {
522
- this.liveRegion.textContent = t;
523
- });
524
- }
525
506
  reflectIndexAttr() {
526
507
  this.reflectGuard = !0;
527
508
  try {
@@ -530,56 +511,81 @@ class p extends HTMLElement {
530
511
  this.reflectGuard = !1;
531
512
  }
532
513
  }
533
- applyStyles(t) {
534
- if (typeof t == "string") {
535
- if (v(this.shadow)) {
536
- const n = S(t);
537
- if (n) {
538
- this.shadow.adoptedStyleSheets = [n], this.styleEl = null;
514
+ setBaseStyles(t) {
515
+ this.baseStyles = t ?? null, this.applyAllStyles();
516
+ }
517
+ setOverrideStyles(t) {
518
+ this.overrideStyles = t ?? null, this.applyAllStyles();
519
+ }
520
+ applyAllStyles() {
521
+ const t = this.baseStyles, s = this.overrideStyles;
522
+ if (E(this.shadow)) {
523
+ const e = [];
524
+ if (typeof t == "string") {
525
+ const i = y(t);
526
+ if (!i) {
527
+ this.applyAllStylesFallback();
539
528
  return;
540
529
  }
530
+ e.push(i);
531
+ } else if (t)
532
+ e.push(t);
533
+ else if (p.defaultStylesheet)
534
+ e.push(p.defaultStylesheet);
535
+ else {
536
+ this.applyAllStylesFallback();
537
+ return;
541
538
  }
542
- this.styleEl || (this.styleEl = document.createElement("style")), this.styleEl.textContent = t;
543
- return;
544
- }
545
- if (t && v(this.shadow)) {
546
- this.shadow.adoptedStyleSheets = [t], this.styleEl = null;
547
- return;
548
- }
549
- if (p.defaultStylesheet && v(this.shadow)) {
550
- this.shadow.adoptedStyleSheets = [p.defaultStylesheet], this.styleEl = null;
539
+ if (typeof s == "string") {
540
+ const i = y(s);
541
+ if (!i) {
542
+ this.applyAllStylesFallback();
543
+ return;
544
+ }
545
+ e.push(i);
546
+ } else s && e.push(s);
547
+ this.shadow.adoptedStyleSheets = e, this.baseStyleEl?.remove(), this.overrideStyleEl?.remove(), this.baseStyleEl = null, this.overrideStyleEl = null;
551
548
  return;
552
549
  }
553
- this.styleEl || (this.styleEl = document.createElement("style")), this.styleEl.textContent = I;
550
+ this.applyAllStylesFallback();
551
+ }
552
+ applyAllStylesFallback() {
553
+ const t = this.baseStyles, s = this.overrideStyles;
554
+ E(this.shadow) && (this.shadow.adoptedStyleSheets = []);
555
+ const n = typeof t == "string" ? t : I, e = typeof s == "string" ? s : "";
556
+ this.baseStyleEl || (this.baseStyleEl = document.createElement("style")), this.baseStyleEl.textContent = n, e ? (this.overrideStyleEl || (this.overrideStyleEl = document.createElement("style")), this.overrideStyleEl.textContent = e) : (this.overrideStyleEl?.remove(), this.overrideStyleEl = null);
554
557
  }
555
558
  }
556
- function M(a = {}) {
559
+ function F(a = {}) {
557
560
  const {
558
561
  selector: t = "coverflow-carousel",
559
- onReady: n,
560
- onChange: s,
562
+ onReady: s,
563
+ onChange: n,
561
564
  onScratchComplete: e,
562
- stylesheet: i
563
- } = a, r = Array.from(document.querySelectorAll(t));
564
- return (n || s || e) && r.forEach((c) => {
565
- n && c.addEventListener("coverflow-carousel:ready", (o) => {
566
- n(c, o.detail);
567
- }), s && c.addEventListener("coverflow-carousel:change", (o) => {
568
- s(c, o.detail);
569
- }), e && c.addEventListener("coverflow-carousel:scratch-complete", (o) => {
570
- e(c, o.detail);
565
+ stylesheet: i,
566
+ styleOverrides: r
567
+ } = a, o = Array.from(document.querySelectorAll(t));
568
+ return (s || n || e) && o.forEach((l) => {
569
+ s && l.addEventListener("coverflow-carousel:ready", (c) => {
570
+ s(l, c.detail);
571
+ }), n && l.addEventListener("coverflow-carousel:change", (c) => {
572
+ n(l, c.detail);
573
+ }), e && l.addEventListener("coverflow-carousel:scratch-complete", (c) => {
574
+ e(l, c.detail);
571
575
  });
572
- }), i && r.forEach((c) => {
573
- const o = c;
574
- typeof i == "string" ? o.adoptStyles(i) : o.adoptStylesheet(i);
575
- }), r;
576
+ }), i && o.forEach((l) => {
577
+ const c = l;
578
+ typeof i == "string" ? c.adoptStyles(i) : c.adoptStylesheet(i);
579
+ }), r && o.forEach((l) => {
580
+ l.adoptStyleOverrides(r);
581
+ }), o;
576
582
  }
577
- function N(a = "coverflow-carousel") {
583
+ function M(a = "coverflow-carousel") {
578
584
  typeof window > "u" || !("customElements" in window) || customElements.get(a) || customElements.define(a, p);
579
585
  }
580
586
  export {
581
587
  p as CoverflowCarouselElement,
582
588
  D as coverflowCarouselCssText,
583
- M as initCoverflowCarousels,
584
- N as registerCoverflowCarouselElement
589
+ F as initCoverflowCarousels,
590
+ M as registerCoverflowCarouselElement
585
591
  };
package/dist/index.umd.js CHANGED
@@ -1,12 +1,10 @@
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 {
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 {
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%;
@@ -151,17 +151,4 @@
151
151
  background: Crimson;
152
152
  transform: scale(1.2);
153
153
  }
154
-
155
- /* SR-only */
156
- .cfc__sr {
157
- position: absolute;
158
- width: 1px;
159
- height: 1px;
160
- padding: 0;
161
- margin: -1px;
162
- overflow: hidden;
163
- clip: rect(0, 0, 0, 0);
164
- white-space: nowrap;
165
- border: 0;
166
- }
167
- `,I=d;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(d),_=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 k(a,t){const n=g(a.transitionProperty),s=g(a.transitionDuration),e=g(a.transitionDelay);if(!n.length)return 0;const r=(y=>{const u=n.indexOf(y);if(u>=0)return u;const A=n.indexOf("all");return A>=0?A:-1})(t);if(r<0)return 0;const c=s[Math.min(r,s.length-1)]??"0s",o=e[Math.min(r,e.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,n){if(!a.hasAttribute(t))return n;const s=a.getAttribute(t);return s==null||s===""?!0:s!=="false"}function T(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 m(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 v=1;let M=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-${++M}`;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=[];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=m(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=m(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=T(this,"start-index",0),s=w(this,"index"),e=s!=null?Number(s):n,i=Number.isFinite(e)?e:0;this.currentIndex=m(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.applyStyles(t)}adoptStyles(t){this.applyStyles(t)}readAttributes(t){this.rootEl||this.render();const n=p(this,"show-arrows",!1),s=p(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.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(),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 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",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"),c=`${this.instanceId}-slot-${e}`;t[e]||(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),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(v>=s){for(let e=0;e<t;e++)n.add(e);return n}for(let e=-v;e<=v;e++)n.add(m(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(!p(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<=v,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,c=t.dataset.cfcAbs;r!==e&&(t.style.setProperty("--cfc-delta",e),t.dataset.cfcDelta=e),c!==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&&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 n=this.dots[t],s=t===this.currentIndex;n.dataset.active=s?"true":"false"}}lockUntilTransitionEnd(){this.pendingAnimToken++;const t=this.pendingAnimToken;if(L()){this.unlockAnimation();return}const n=this.cards[this.currentIndex];if(!n){this.unlockAnimation();return}const s=getComputedStyle(n),e=k(s,"transform");if(e<=0){this.unlockAnimation();return}const i=x(s.getPropertyValue("--cfc-transition-ms").trim(),400),c=Math.max(e,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 n=E(t);if(n){this.shadow.adoptedStyleSheets=[n],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 N(a={}){const{selector:t="coverflow-carousel",onReady:n,onChange:s,onScratchComplete:e,stylesheet:i}=a,r=Array.from(document.querySelectorAll(t));return(n||s||e)&&r.forEach(c=>{n&&c.addEventListener("coverflow-carousel:ready",o=>{n(c,o.detail)}),s&&c.addEventListener("coverflow-carousel:change",o=>{s(c,o.detail)}),e&&c.addEventListener("coverflow-carousel:scratch-complete",o=>{e(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,f)}h.CoverflowCarouselElement=f,h.coverflowCarouselCssText=I,h.initCoverflowCarousels=N,h.registerCoverflowCarouselElement=F,Object.defineProperty(h,Symbol.toStringTag,{value:"Module"})}));
154
+ `,I=h;function E(a){return"adoptedStyleSheets"in a}function b(a){try{const t=new CSSStyleSheet;return t.replaceSync(a),t}catch{return null}}const C=b(h),k=h;function y(a){return a.split(",").map(t=>t.trim()).filter(Boolean)}function g(a,t){const s=a.trim();if(!s)return t;if(s.endsWith("ms")){const e=Number(s.slice(0,-2).trim());return Number.isFinite(e)?e:t}if(s.endsWith("s")){const e=Number(s.slice(0,-1).trim());return Number.isFinite(e)?e*1e3:t}const n=Number(s);return Number.isFinite(n)?n:t}function _(a,t){const s=y(a.transitionProperty),n=y(a.transitionDuration),e=y(a.transitionDelay);if(!s.length)return 0;const r=(x=>{const u=s.indexOf(x);if(u>=0)return u;const A=s.indexOf("all");return A>=0?A:-1})(t);if(r<0)return 0;const o=n[Math.min(r,n.length-1)]??"0s",l=e[Math.min(r,e.length-1)]??"0s",c=g(o,0),p=g(l,0);return Math.max(0,c+p)}function T(){return window.matchMedia?.("(prefers-reduced-motion: reduce)")?.matches??!1}function S(a,t,s){if(!a.hasAttribute(t))return s;const n=a.getAttribute(t);return n==null||n===""?!0:n!=="false"}function L(a,t,s){const n=a.getAttribute(t);if(n==null)return s;const e=Number(n.trim());return Number.isFinite(e)?Math.trunc(e):s}function w(a,t){const s=a.getAttribute(t);if(s==null)return null;const n=s.trim();return n||null}function m(a,t){return t<=0?0:(a%t+t)%t}function D(a,t,s){const n=t-a,e=s/2;return n>e?n-s:n<-e?n+s:n}const v=1;let F=0;class f extends HTMLElement{static defaultStylesheet=C;static observedAttributes=["start-index","index","show-dots","show-arrows"];shadow=this.attachShadow({mode:"open"});instanceId=`cfc-${++F}`;rootEl;trackEl;dotsEl;prevBtn;nextBtn;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,s,n){if(!this.isConnected||this.reflectGuard)return;this.readAttributes({isInit:!1});const e=w(this,"index");if(e!=null){const i=m(Number(e),this.cards.length);if(Number.isFinite(i)&&i!==this.currentIndex){this.goTo(i);return}}this.applyLayoutAndA11y({emitChange:!1})}next(){this.goTo(this.currentIndex+1)}prev(){this.goTo(this.currentIndex-1)}goTo(t){if(this.isAnimating)return;const s=m(t,this.cards.length);s!==this.currentIndex&&(this.isAnimating=!0,this.currentIndex=s,this.reflectIndexAttr(),this.applyLayoutAndA11y({emitChange:!0}),this.lockUntilTransitionEnd())}refresh(){const t=this.lightDomObserver;t&&(this.suppressMutations=!0,t.disconnect(),t.takeRecords()),this.rebuildCardsFromLightDom();const s=L(this,"start-index",0),n=w(this,"index"),e=n!=null?Number(n):s,i=Number.isFinite(e)?e:0;this.currentIndex=m(i,this.cards.length),this.reflectIndexAttr(),this.buildDots(),this.hasAppliedInitialLayout=!1,this.lastLayoutIndex=null,this.lastVisibleSet=new Set,this.applyLayoutAndA11y({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 s=S(this,"show-arrows",!1),n=S(this,"show-dots",!1);this.prevBtn.style.display=s?"":"none",this.nextBtn.style.display=s?"":"none",this.dotsEl.style.display=n?"":"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.rootEl.append(this.trackEl,this.prevBtn,this.nextBtn,this.dotsEl),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(),s=()=>this.next();this.prevBtn.addEventListener("click",t),this.nextBtn.addEventListener("click",s),this.cleanup.push(()=>this.prevBtn.removeEventListener("click",t)),this.cleanup.push(()=>this.nextBtn.removeEventListener("click",s));const n=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",n),this.cleanup.push(()=>this.rootEl.removeEventListener("transitionend",n));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 x=i.detail?.percent??100;this.dispatchEvent(new CustomEvent("coverflow-carousel:scratch-complete",{detail:{index:c,length:this.cards.length,percent:x},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(n=>!(n.type==="attributes"&&n.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")),s=[],n=Array.from(this.children).filter(e=>e instanceof HTMLElement);for(let e=0;e<n.length;e++){const i=n[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),s.push(r)}for(let e=n.length;e<t.length;e++)t[e]?.remove();this.cards=s,this.hasAppliedInitialLayout=!1,this.lastLayoutIndex=null,this.lastVisibleSet=new Set}getVisibleSet(){const t=this.cards.length,s=new Set;if(t<=0)return s;const n=Math.floor(t/2);if(v>=n){for(let e=0;e<t;e++)s.add(e);return s}for(let e=-v;e<=v;e++)s.add(m(this.currentIndex+e,t));return s}buildDots(){const t=Array.from(this.dotsEl.children).filter(i=>i instanceof HTMLElement&&i.classList.contains("cfc__dot"));if(!S(this,"show-dots",!1)){t.forEach(i=>{i.remove()}),this.dots=[];return}const n=[],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"),n.push(r)}for(let i=e;i<t.length;i++)t[i]?.remove();this.dots=n,this.updateDotsVisualState()}computeCardState(t){const s=this.cards.length,n=D(this.currentIndex,t,s),e=Math.abs(n);return{index:t,delta:n,abs:e,isVisible:e<=v,isActive:t===this.currentIndex}}applyCardState(t,s){t.setAttribute("aria-hidden",s.isVisible?"false":"true"),t.dataset.active=s.isActive?"true":"false",t.dataset.cfcIndex=String(s.index),t.setAttribute("role","group"),t.setAttribute("aria-roledescription","slide"),t.setAttribute("aria-setsize",String(this.cards.length)),t.setAttribute("aria-posinset",String(s.index+1));const n=`${this.instanceId}-slide-${s.index}`;t.id=n;const e=String(s.delta),i=String(s.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 s=this.getVisibleSet(),n=new Set;this.lastVisibleSet.forEach(e=>{n.add(e)}),s.forEach(e=>{n.add(e)}),this.lastLayoutIndex!=null&&n.add(this.lastLayoutIndex),n.add(this.currentIndex),n.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=s}else{for(let s=0;s<this.cards.length;s++){const n=this.cards[s];if(!n)continue;const e=this.computeCardState(s);this.applyCardState(n,e)}this.hasAppliedInitialLayout=!0,this.lastLayoutIndex=this.currentIndex,this.lastVisibleSet=this.getVisibleSet()}this.updateDotsVisualState(),t.emitChange&&this.emitChange()}}updateDotsVisualState(){if(this.dots.length)for(let t=0;t<this.dots.length;t++){const s=this.dots[t],n=t===this.currentIndex;s.dataset.active=n?"true":"false"}}lockUntilTransitionEnd(){this.pendingAnimToken++;const t=this.pendingAnimToken;if(T()){this.unlockAnimation();return}const s=this.cards[this.currentIndex];if(!s){this.unlockAnimation();return}const n=getComputedStyle(s),e=_(n,"transform");if(e<=0){this.unlockAnimation();return}const i=g(n.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}}))}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,s=this.overrideStyles;if(E(this.shadow)){const e=[];if(typeof t=="string"){const i=b(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 s=="string"){const i=b(s);if(!i){this.applyAllStylesFallback();return}e.push(i)}else s&&e.push(s);this.shadow.adoptedStyleSheets=e,this.baseStyleEl?.remove(),this.overrideStyleEl?.remove(),this.baseStyleEl=null,this.overrideStyleEl=null;return}this.applyAllStylesFallback()}applyAllStylesFallback(){const t=this.baseStyles,s=this.overrideStyles;E(this.shadow)&&(this.shadow.adoptedStyleSheets=[]);const n=typeof t=="string"?t:k,e=typeof s=="string"?s:"";this.baseStyleEl||(this.baseStyleEl=document.createElement("style")),this.baseStyleEl.textContent=n,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:s,onChange:n,onScratchComplete:e,stylesheet:i,styleOverrides:r}=a,o=Array.from(document.querySelectorAll(t));return(s||n||e)&&o.forEach(l=>{s&&l.addEventListener("coverflow-carousel:ready",c=>{s(l,c.detail)}),n&&l.addEventListener("coverflow-carousel:change",c=>{n(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)}d.CoverflowCarouselElement=f,d.coverflowCarouselCssText=I,d.initCoverflowCarousels=M,d.registerCoverflowCarouselElement=N,Object.defineProperty(d,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.2",
3
+ "version": "0.3.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",