@rcarls/rc-carousel 0.1.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 +38 -0
- package/dist/custom-elements.json +937 -0
- package/dist/rc-carousel-CdDEH9fb.js +658 -0
- package/dist/rc-carousel-CdDEH9fb.js.map +1 -0
- package/dist/rc-carousel-define.js +8 -0
- package/dist/rc-carousel-define.js.map +1 -0
- package/dist/rc-carousel.js +6 -0
- package/dist/rc-carousel.js.map +1 -0
- package/dist/types/packages/rc-carousel/src/define.d.ts +1 -0
- package/dist/types/packages/rc-carousel/src/index.d.ts +2 -0
- package/dist/types/packages/rc-carousel/src/rc-carousel-item.d.ts +63 -0
- package/dist/types/packages/rc-carousel/src/rc-carousel-item.styles.d.ts +2 -0
- package/dist/types/packages/rc-carousel/src/rc-carousel.d.ts +270 -0
- package/dist/types/packages/rc-carousel/src/rc-carousel.styles.d.ts +2 -0
- package/package.json +65 -0
|
@@ -0,0 +1,658 @@
|
|
|
1
|
+
import { css as f, LitElement as m, html as h, nothing as d } from "lit";
|
|
2
|
+
import { property as l, query as b, state as x } from "lit/decorators.js";
|
|
3
|
+
import { DragGestureController as I, findNearestSnapIndex as k, findExtremeSnapIndex as g, findNextSnapIndex as A, keyNavigation as v } from "@rcarls/rc-common";
|
|
4
|
+
const C = f`
|
|
5
|
+
:host {
|
|
6
|
+
display: block;
|
|
7
|
+
box-sizing: border-box;
|
|
8
|
+
min-inline-size: 0;
|
|
9
|
+
min-block-size: 0;
|
|
10
|
+
scroll-snap-align: var(--rc-carousel-item-scroll-snap-align, start);
|
|
11
|
+
/*
|
|
12
|
+
* Without this, a normal-speed swipe carries enough fling momentum to
|
|
13
|
+
* sail past the very next item and settle two (or more) items over —
|
|
14
|
+
* \`mandatory\` scroll-snap-type on the track alone only guarantees
|
|
15
|
+
* landing on *a* snap point, not the nearest one. \`always\` forces the
|
|
16
|
+
* browser to stop at each snap point in turn.
|
|
17
|
+
*/
|
|
18
|
+
scroll-snap-stop: always;
|
|
19
|
+
overflow: var(--rc-carousel-item-overflow, hidden);
|
|
20
|
+
border-radius: var(--rc-carousel-item-border-radius, 0);
|
|
21
|
+
background: var(--rc-carousel-item-background, transparent);
|
|
22
|
+
color: var(--rc-carousel-item-color, CanvasText);
|
|
23
|
+
color-scheme: inherit;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
:host([hidden]) {
|
|
27
|
+
display: none;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/*
|
|
31
|
+
* Peeking/off-screen items are hidden from assistive technology (see
|
|
32
|
+
* rc-carousel-item.ts's IntersectionObserver) but must stay visually and
|
|
33
|
+
* interactively present — a peek is a legitimate visual affordance, not
|
|
34
|
+
* decoration to strip. This only removes it from the accessibility tree
|
|
35
|
+
* and, via inert, the tab sequence, matching the same technique proven
|
|
36
|
+
* carousels (e.g. Shoelace's sl-carousel) use for exactly this problem.
|
|
37
|
+
*/
|
|
38
|
+
:host([aria-hidden='true']) {
|
|
39
|
+
pointer-events: none;
|
|
40
|
+
}
|
|
41
|
+
`;
|
|
42
|
+
var S = Object.defineProperty, E = (c, t, e, s) => {
|
|
43
|
+
for (var i = void 0, n = c.length - 1, a; n >= 0; n--)
|
|
44
|
+
(a = c[n]) && (i = a(t, e, i) || i);
|
|
45
|
+
return i && S(t, e, i), i;
|
|
46
|
+
};
|
|
47
|
+
const p = class p extends m {
|
|
48
|
+
constructor() {
|
|
49
|
+
super(), this._intersectionObserver = null, this.position = "", this._internals = this.attachInternals(), this._internals.role = "group";
|
|
50
|
+
}
|
|
51
|
+
connectedCallback() {
|
|
52
|
+
super.connectedCallback(), this.hasAttribute("role") || this.setAttribute("role", "group"), this.hasAttribute("aria-roledescription") || this.setAttribute("aria-roledescription", "slide"), this._observeIntersection();
|
|
53
|
+
}
|
|
54
|
+
disconnectedCallback() {
|
|
55
|
+
super.disconnectedCallback(), this._intersectionObserver?.disconnect(), this._intersectionObserver = null;
|
|
56
|
+
}
|
|
57
|
+
updated(t) {
|
|
58
|
+
super.updated(t), t.has("position") && this._syncDefaultLabel();
|
|
59
|
+
}
|
|
60
|
+
_syncDefaultLabel() {
|
|
61
|
+
this.hasAttribute("aria-label") || this.hasAttribute("aria-labelledby") || this.position && this.setAttribute("aria-label", this.position);
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Observes intersection against the parent carousel's own scroll
|
|
65
|
+
* container (not the viewport — a slide can be viewport-visible yet
|
|
66
|
+
* still clipped/off-screen within the track on a page where the
|
|
67
|
+
* carousel isn't the whole viewport). Reads `trackElement` off the
|
|
68
|
+
* parent `rc-carousel`, an internal integration point rather than a
|
|
69
|
+
* public API — awaits the parent's own first render first, since a
|
|
70
|
+
* slotted item's `connectedCallback` isn't guaranteed to run after its
|
|
71
|
+
* host's, and `trackElement` only exists once the host has rendered.
|
|
72
|
+
*/
|
|
73
|
+
async _observeIntersection() {
|
|
74
|
+
if (this.hasAttribute("data-clone")) {
|
|
75
|
+
this.setAttribute("aria-hidden", "true"), this.setAttribute("inert", "");
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const t = this.closest("rc-carousel");
|
|
79
|
+
await t?.updateComplete;
|
|
80
|
+
const e = t?.trackElement ?? null;
|
|
81
|
+
if (this._intersectionObserver?.disconnect(), typeof IntersectionObserver != "function" || !e) {
|
|
82
|
+
this.removeAttribute("aria-hidden"), this.removeAttribute("inert");
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
this._intersectionObserver = new IntersectionObserver(
|
|
86
|
+
([s]) => {
|
|
87
|
+
const i = s !== void 0 && !s.isIntersecting;
|
|
88
|
+
i ? (this.contains(this.ownerDocument.activeElement) && t?.trackElement?.focus(), this.setAttribute("aria-hidden", "true")) : this.removeAttribute("aria-hidden"), this.toggleAttribute("inert", i);
|
|
89
|
+
},
|
|
90
|
+
// A low/zero threshold would count a peeking neighbor's own sliver
|
|
91
|
+
// of visible pixels as "intersecting", never hiding it — a peek is
|
|
92
|
+
// real but not readable/interactive, so it should still hide. 0.5
|
|
93
|
+
// reliably separates "the active slide" (near-fully visible) from
|
|
94
|
+
// "a peeking neighbor" (a fraction of it showing).
|
|
95
|
+
{ root: e, threshold: 0.5 }
|
|
96
|
+
), this._intersectionObserver.observe(this);
|
|
97
|
+
}
|
|
98
|
+
render() {
|
|
99
|
+
return h`<slot></slot>`;
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
p.styles = C;
|
|
103
|
+
let u = p;
|
|
104
|
+
E([
|
|
105
|
+
l({ type: String, attribute: !1 })
|
|
106
|
+
], u.prototype, "position");
|
|
107
|
+
const w = f`
|
|
108
|
+
:host {
|
|
109
|
+
display: block;
|
|
110
|
+
position: relative;
|
|
111
|
+
color: var(--rc-carousel-color, CanvasText);
|
|
112
|
+
color-scheme: inherit;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
#track {
|
|
116
|
+
display: grid;
|
|
117
|
+
grid-auto-flow: column;
|
|
118
|
+
grid-auto-columns: var(--rc-carousel-slide-size, calc(100% - 4rem));
|
|
119
|
+
column-gap: var(--rc-carousel-gap, 8px);
|
|
120
|
+
inline-size: 100%;
|
|
121
|
+
block-size: 100%;
|
|
122
|
+
overflow-x: auto;
|
|
123
|
+
overflow-y: hidden;
|
|
124
|
+
overscroll-behavior-x: contain;
|
|
125
|
+
scroll-snap-type: x mandatory;
|
|
126
|
+
/*
|
|
127
|
+
* Deliberately no \`scroll-behavior: smooth\` here — _scrollToIndex in
|
|
128
|
+
* rc-carousel.ts passes \`behavior: 'smooth'\` explicitly per
|
|
129
|
+
* programmatic call instead, which is unaffected either way (an
|
|
130
|
+
* explicit argument always overrides this CSS default). Setting it
|
|
131
|
+
* here as an ambient default would also apply to the browser's own
|
|
132
|
+
* native snap-settle correction after a swipe — a documented
|
|
133
|
+
* cross-browser conflict with scroll-snap-stop: always (each
|
|
134
|
+
* rc-carousel-item sets that; Firefox bugzilla 1643217, 1959811)
|
|
135
|
+
* that produces a jerk-then-snap-back artifact on an ordinary swipe.
|
|
136
|
+
*/
|
|
137
|
+
scrollbar-width: none;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
#track::-webkit-scrollbar {
|
|
141
|
+
display: none;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
:host([mouse-dragging]) #track {
|
|
145
|
+
cursor: grab;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
:host([mouse-dragging]) #track.dragging {
|
|
149
|
+
cursor: grabbing;
|
|
150
|
+
/*
|
|
151
|
+
* scroll-snap-type itself is toggled imperatively in rc-carousel.ts,
|
|
152
|
+
* not here — it must be off before the very first scrollLeft write of
|
|
153
|
+
* a drag, and this class only lands on the next reactive render, a
|
|
154
|
+
* render pass too late for that first write (the browser eagerly
|
|
155
|
+
* resnaps a plain scrollLeft assignment right back to the nearest
|
|
156
|
+
* snap point, same as any other programmatic scroll).
|
|
157
|
+
*/
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
#navigation {
|
|
161
|
+
display: contents;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
[part~='navigation-button'] {
|
|
165
|
+
position: absolute;
|
|
166
|
+
top: 50%;
|
|
167
|
+
translate: 0 -50%;
|
|
168
|
+
display: inline-flex;
|
|
169
|
+
align-items: center;
|
|
170
|
+
justify-content: center;
|
|
171
|
+
inline-size: var(--rc-carousel-navigation-button-size, 40px);
|
|
172
|
+
block-size: var(--rc-carousel-navigation-button-size, 40px);
|
|
173
|
+
border: none;
|
|
174
|
+
border-radius: 50%;
|
|
175
|
+
background: var(
|
|
176
|
+
--rc-carousel-navigation-button-background,
|
|
177
|
+
color-mix(in srgb, CanvasText 12%, transparent)
|
|
178
|
+
);
|
|
179
|
+
color: var(--rc-carousel-navigation-button-color, CanvasText);
|
|
180
|
+
cursor: pointer;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
[part~='navigation-button'][aria-disabled='true'] {
|
|
184
|
+
opacity: 0.38;
|
|
185
|
+
cursor: default;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
[part~='navigation-button-previous'] {
|
|
189
|
+
inset-inline-start: var(--rc-carousel-navigation-inset, 8px);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
[part~='navigation-button-next'] {
|
|
193
|
+
inset-inline-end: var(--rc-carousel-navigation-inset, 8px);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
#pagination {
|
|
197
|
+
position: absolute;
|
|
198
|
+
inset-block-end: var(--rc-carousel-navigation-inset, 8px);
|
|
199
|
+
inset-inline: 0;
|
|
200
|
+
display: flex;
|
|
201
|
+
justify-content: center;
|
|
202
|
+
gap: var(--rc-carousel-gap, 8px);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
[part~='pagination-item'] {
|
|
206
|
+
inline-size: var(--rc-carousel-pagination-item-size, 8px);
|
|
207
|
+
block-size: var(--rc-carousel-pagination-item-size, 8px);
|
|
208
|
+
padding: 0;
|
|
209
|
+
border: none;
|
|
210
|
+
border-radius: 50%;
|
|
211
|
+
background: var(
|
|
212
|
+
--rc-carousel-pagination-item-color,
|
|
213
|
+
color-mix(in srgb, CanvasText 40%, transparent)
|
|
214
|
+
);
|
|
215
|
+
cursor: pointer;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
[part~='pagination-item-active'] {
|
|
219
|
+
background: var(--rc-carousel-pagination-item-active-color, Highlight);
|
|
220
|
+
cursor: default;
|
|
221
|
+
}
|
|
222
|
+
`;
|
|
223
|
+
var T = Object.defineProperty, $ = Object.getOwnPropertyDescriptor, o = (c, t, e, s) => {
|
|
224
|
+
for (var i = s > 1 ? void 0 : s ? $(t, e) : t, n = c.length - 1, a; n >= 0; n--)
|
|
225
|
+
(a = c[n]) && (i = (s ? a(t, e, i) : a(i)) || i);
|
|
226
|
+
return s && i && T(t, e, i), i;
|
|
227
|
+
};
|
|
228
|
+
const z = 300, P = 120, _ = class _ extends m {
|
|
229
|
+
constructor() {
|
|
230
|
+
super(), this._items = [], this._cloneItems = [], this._mounted = !1, this._suppressSync = !1, this._pendingInstant = !1, this.loop = !1, this.navigation = !1, this.pagination = !1, this.mouseDragging = !1, this._dragging = !1, this._dragStartLeft = 0, this._suppressNextClick = !1, this._dragController = new I(this, {
|
|
231
|
+
target: () => this._trackEl ?? null,
|
|
232
|
+
axis: "x",
|
|
233
|
+
activation: "axis",
|
|
234
|
+
canStart: (t) => this.mouseDragging && t.pointerType === "mouse",
|
|
235
|
+
onStart: () => {
|
|
236
|
+
this._dragging = !0, this._dragStartLeft = this._trackEl?.scrollLeft ?? 0, this._suppressNextClick = !0, this._trackEl && (this._trackEl.style.scrollSnapType = "none");
|
|
237
|
+
},
|
|
238
|
+
onMove: (t) => {
|
|
239
|
+
this._trackEl && (this._trackEl.scrollLeft = this._dragStartLeft - t.deltaX);
|
|
240
|
+
},
|
|
241
|
+
onEnd: (t) => this._endDrag(t),
|
|
242
|
+
onCancel: (t) => this._endDrag(t)
|
|
243
|
+
}), this._busy = !1, this._defaultActiveIndex = 0, this._activeIndexInitialized = !1, this._onSlotChange = () => {
|
|
244
|
+
this._syncItems();
|
|
245
|
+
}, this._onScroll = () => {
|
|
246
|
+
this._settleTimer !== void 0 && clearTimeout(this._settleTimer), this._busy = !0, this._settleTimer = setTimeout(this._commitSettledIndex, P);
|
|
247
|
+
}, this._commitSettledIndex = () => {
|
|
248
|
+
if (this._busy = !1, !this._trackEl)
|
|
249
|
+
return;
|
|
250
|
+
const t = this._trackSlots(), e = k(
|
|
251
|
+
t.map((n) => n.point),
|
|
252
|
+
this._trackEl.scrollLeft
|
|
253
|
+
), s = e >= 0 ? t[e] : void 0;
|
|
254
|
+
if (!s)
|
|
255
|
+
return;
|
|
256
|
+
if (s.isClone) {
|
|
257
|
+
const n = t.find((a) => !a.isClone && a.index === s.index);
|
|
258
|
+
n && (this._trackEl.scrollLeft = n.point);
|
|
259
|
+
}
|
|
260
|
+
if (s.index === this.activeIndex)
|
|
261
|
+
return;
|
|
262
|
+
const i = this._activeIndex !== void 0;
|
|
263
|
+
i || (this._suppressSync = !0), this._setActiveIndex(s.index, "swipe"), i && this.activeIndex !== s.index && this._scrollToIndex(this.activeIndex, !0);
|
|
264
|
+
}, this._onNavigate = (t) => {
|
|
265
|
+
switch (t) {
|
|
266
|
+
case "next":
|
|
267
|
+
this._step(1, "keyboard");
|
|
268
|
+
break;
|
|
269
|
+
case "prev":
|
|
270
|
+
this._step(-1, "keyboard");
|
|
271
|
+
break;
|
|
272
|
+
case "start": {
|
|
273
|
+
const e = g(this._snapPoints(), -1);
|
|
274
|
+
e >= 0 && this._setActiveIndex(e, "keyboard");
|
|
275
|
+
break;
|
|
276
|
+
}
|
|
277
|
+
case "end": {
|
|
278
|
+
const e = g(this._snapPoints(), 1);
|
|
279
|
+
e >= 0 && this._setActiveIndex(e, "keyboard");
|
|
280
|
+
break;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}, this._onTrackClickCapture = (t) => {
|
|
284
|
+
this._suppressNextClick && (this._suppressNextClick = !1, t.stopPropagation(), t.preventDefault());
|
|
285
|
+
}, this._onPaginationFocus = (t) => {
|
|
286
|
+
const e = this._paginationButtons(), s = t.composedPath().find((i) => e.includes(i));
|
|
287
|
+
s && e.forEach((i) => i.setAttribute("tabindex", i === s ? "0" : "-1"));
|
|
288
|
+
}, this._onPaginationNavigate = (t) => {
|
|
289
|
+
const e = this._paginationButtons();
|
|
290
|
+
if (!e.length)
|
|
291
|
+
return;
|
|
292
|
+
const s = e.indexOf(this.shadowRoot?.activeElement), i = s < 0 ? 0 : s;
|
|
293
|
+
switch (t) {
|
|
294
|
+
case "next":
|
|
295
|
+
e[(i + 1) % e.length]?.focus();
|
|
296
|
+
break;
|
|
297
|
+
case "prev":
|
|
298
|
+
e[(i - 1 + e.length) % e.length]?.focus();
|
|
299
|
+
break;
|
|
300
|
+
case "start":
|
|
301
|
+
e[0]?.focus();
|
|
302
|
+
break;
|
|
303
|
+
case "end":
|
|
304
|
+
e[e.length - 1]?.focus();
|
|
305
|
+
break;
|
|
306
|
+
}
|
|
307
|
+
}, this._internals = this.attachInternals(), this._internals.role = "group";
|
|
308
|
+
}
|
|
309
|
+
get activeIndex() {
|
|
310
|
+
return this._clampIndex(
|
|
311
|
+
this._activeIndex ?? this._uncontrolledActiveIndex ?? this._defaultActiveIndex
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
set activeIndex(t) {
|
|
315
|
+
const e = this.activeIndex;
|
|
316
|
+
this._activeIndex = t, this._activeIndexInitialized = !0, this.requestUpdate("activeIndex", e);
|
|
317
|
+
}
|
|
318
|
+
get defaultActiveIndex() {
|
|
319
|
+
return this._defaultActiveIndex;
|
|
320
|
+
}
|
|
321
|
+
set defaultActiveIndex(t) {
|
|
322
|
+
const e = this._defaultActiveIndex;
|
|
323
|
+
this._defaultActiveIndex = t, !this._activeIndexInitialized && this._activeIndex === void 0 && this._uncontrolledActiveIndex === void 0 && this.requestUpdate("activeIndex", e), this.requestUpdate("defaultActiveIndex", e);
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* The scroll-snap track element. Internal integration point consumed by
|
|
327
|
+
* `rc-carousel-item`'s IntersectionObserver (its `root` must be this
|
|
328
|
+
* track, not the viewport, so peeking/clipped-but-viewport-visible
|
|
329
|
+
* slides are still correctly detected as off-screen) — not a public API.
|
|
330
|
+
*/
|
|
331
|
+
get trackElement() {
|
|
332
|
+
return this._trackEl ?? null;
|
|
333
|
+
}
|
|
334
|
+
connectedCallback() {
|
|
335
|
+
super.connectedCallback(), this._items = this._directItems(), this._setItemPositions(), this.hasAttribute("role") || this.setAttribute("role", "group"), this.hasAttribute("aria-roledescription") || this.setAttribute("aria-roledescription", "carousel");
|
|
336
|
+
}
|
|
337
|
+
disconnectedCallback() {
|
|
338
|
+
super.disconnectedCallback(), this._settleTimer !== void 0 && clearTimeout(this._settleTimer);
|
|
339
|
+
}
|
|
340
|
+
firstUpdated() {
|
|
341
|
+
this._scrollToIndex(this.activeIndex, !0), this._mounted = !0;
|
|
342
|
+
}
|
|
343
|
+
updated(t) {
|
|
344
|
+
if (super.updated(t), this._initPaginationTabIndex(), t.has("loop") && this._mounted && this._syncItems(), !t.has("activeIndex"))
|
|
345
|
+
return;
|
|
346
|
+
if (this._suppressSync) {
|
|
347
|
+
this._suppressSync = !1;
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
const e = !this._mounted || this._pendingInstant;
|
|
351
|
+
this._pendingInstant = !1, this._scrollToIndex(this.activeIndex, e);
|
|
352
|
+
}
|
|
353
|
+
/** Moves to the next slide. */
|
|
354
|
+
next() {
|
|
355
|
+
this._step(1, "api");
|
|
356
|
+
}
|
|
357
|
+
/** Moves to the previous slide. */
|
|
358
|
+
previous() {
|
|
359
|
+
this._step(-1, "api");
|
|
360
|
+
}
|
|
361
|
+
/** Moves directly to a slide index. */
|
|
362
|
+
goToIndex(t, e = !1) {
|
|
363
|
+
this._pendingInstant = e, this._setActiveIndex(t, "api");
|
|
364
|
+
}
|
|
365
|
+
_step(t, e) {
|
|
366
|
+
this._setActiveIndex(this.activeIndex + t, e);
|
|
367
|
+
}
|
|
368
|
+
/** Whether a previous/next button (or an equivalent keyboard action)
|
|
369
|
+
* currently has anywhere to go — always true when `loop` is set and
|
|
370
|
+
* there's more than one slide. */
|
|
371
|
+
_canStep(t) {
|
|
372
|
+
const e = this._items.length;
|
|
373
|
+
return e <= 1 ? !1 : this.loop ? !0 : t > 0 ? this.activeIndex < e - 1 : this.activeIndex > 0;
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
* Wraps when `loop` is set, otherwise clamps to the real slide range.
|
|
377
|
+
* Wrapping here covers button/keyboard navigation; a swipe wraps
|
|
378
|
+
* seamlessly through `_trackSlots`'s cloned lead/trail slides instead,
|
|
379
|
+
* since that needs the clones' own track positions, not just an index.
|
|
380
|
+
*/
|
|
381
|
+
_clampIndex(t) {
|
|
382
|
+
const e = this._items.length;
|
|
383
|
+
return e === 0 ? 0 : this.loop ? (t % e + e) % e : Math.min(e - 1, Math.max(0, t));
|
|
384
|
+
}
|
|
385
|
+
_snapPoints() {
|
|
386
|
+
const t = this._itemStep();
|
|
387
|
+
return this._items.map((e, s) => s * t);
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* Every scrollable track position in rendered order, including cloned
|
|
391
|
+
* lead/trail slides when `loop` is on — `[lastClone, item0, ..., itemN-1,
|
|
392
|
+
* firstClone]`. Each slot reports which real slide index it represents
|
|
393
|
+
* (a clone mirrors the real slide it duplicates), so both scrolling to
|
|
394
|
+
* an index and reading back a settled scroll position can stay in terms
|
|
395
|
+
* of real indices while the clones do the seamless-wrap work.
|
|
396
|
+
*/
|
|
397
|
+
_trackSlots() {
|
|
398
|
+
const t = this._itemStep(), e = this._items.length;
|
|
399
|
+
return !this.loop || e < 2 ? this._items.map((s, i) => ({ point: i * t, index: i, isClone: !1 })) : [
|
|
400
|
+
{ point: 0, index: e - 1, isClone: !0 },
|
|
401
|
+
...this._items.map((s, i) => ({ point: (i + 1) * t, index: i, isClone: !1 })),
|
|
402
|
+
{ point: (e + 1) * t, index: 0, isClone: !0 }
|
|
403
|
+
];
|
|
404
|
+
}
|
|
405
|
+
_itemStep() {
|
|
406
|
+
if (!this._trackEl)
|
|
407
|
+
return 1;
|
|
408
|
+
const t = this._items[0];
|
|
409
|
+
if (!(t instanceof HTMLElement))
|
|
410
|
+
return this._trackEl.clientWidth;
|
|
411
|
+
const e = Number.parseFloat(getComputedStyle(this._trackEl).columnGap || "0") || 0;
|
|
412
|
+
return t.offsetWidth + e;
|
|
413
|
+
}
|
|
414
|
+
_setActiveIndex(t, e) {
|
|
415
|
+
const s = this._clampIndex(t), i = this.activeIndex;
|
|
416
|
+
i !== s && (this._activeIndex === void 0 && (this._uncontrolledActiveIndex = s, this.requestUpdate("activeIndex", i)), this.dispatchEvent(
|
|
417
|
+
new CustomEvent("rc-carousel-change", {
|
|
418
|
+
bubbles: !0,
|
|
419
|
+
composed: !0,
|
|
420
|
+
detail: { index: s, trigger: e }
|
|
421
|
+
})
|
|
422
|
+
));
|
|
423
|
+
}
|
|
424
|
+
_scrollToIndex(t, e) {
|
|
425
|
+
if (!this._trackEl)
|
|
426
|
+
return;
|
|
427
|
+
const s = this._trackSlots().find((a) => !a.isClone && a.index === t), i = s ? s.point : t * this._itemStep(), n = this.ownerDocument.defaultView?.matchMedia("(prefers-reduced-motion: reduce)").matches ?? !1;
|
|
428
|
+
e || n ? this._trackEl.scrollLeft = i : this._trackEl.scrollTo({ left: i, behavior: "smooth" });
|
|
429
|
+
}
|
|
430
|
+
_directItems() {
|
|
431
|
+
return Array.from(this.children).filter(
|
|
432
|
+
(t) => t.tagName === "RC-CAROUSEL-ITEM" && !t.hasAttribute("data-clone")
|
|
433
|
+
);
|
|
434
|
+
}
|
|
435
|
+
_setItemPositions() {
|
|
436
|
+
this._items.forEach((t, e) => {
|
|
437
|
+
t.position = `${e + 1} of ${this._items.length}`;
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
/**
|
|
441
|
+
* Prepending/appending the loop clones below is itself a light-DOM
|
|
442
|
+
* mutation on this host, which re-fires `slotchange` (asynchronously) —
|
|
443
|
+
* `_onSlotChange` calls back into this method, so a naive unconditional
|
|
444
|
+
* remove-then-recreate would recreate a fresh pair of clones on every
|
|
445
|
+
* pass forever. Clones are always excluded from `nextItems`, so the
|
|
446
|
+
* real item list is byte-for-byte identical on that re-entrant pass;
|
|
447
|
+
* only rebuild clone DOM when either the real items or the desired
|
|
448
|
+
* clone presence has actually changed, so the re-entrant pass is a
|
|
449
|
+
* true no-op and the recursion terminates.
|
|
450
|
+
*/
|
|
451
|
+
_syncItems() {
|
|
452
|
+
const e = (this._slotEl?.assignedElements() ?? []).filter(
|
|
453
|
+
(a) => a.tagName === "RC-CAROUSEL-ITEM" && !a.hasAttribute("data-clone")
|
|
454
|
+
), s = e.length !== this._items.length || e.some((a, y) => a !== this._items[y]), i = this.loop && e.length > 1, n = this._cloneItems.length > 0;
|
|
455
|
+
this._items = e, this._setItemPositions(), s && this._mounted && this.requestUpdate(), !(!s && i === n) && (this._removeClones(), i && this._addClones());
|
|
456
|
+
}
|
|
457
|
+
/**
|
|
458
|
+
* Clones the first and last slide and prepends/appends them
|
|
459
|
+
* (`data-clone="<real index>"` marks them, excluded from `_items` and
|
|
460
|
+
* from position numbering) so the track has real scrollable content
|
|
461
|
+
* past both visual ends. `_trackSlots`/`_commitSettledIndex` detect
|
|
462
|
+
* when a clone settles and instantly re-anchor to the real slide it
|
|
463
|
+
* mirrors — Shoelace's proven technique for a seamless infinite swipe,
|
|
464
|
+
* rather than a discontinuous index-modulo jump back to the other end.
|
|
465
|
+
*/
|
|
466
|
+
_addClones() {
|
|
467
|
+
const t = this._items[0], e = this._items[this._items.length - 1];
|
|
468
|
+
if (!t || !e)
|
|
469
|
+
return;
|
|
470
|
+
const s = e.cloneNode(!0);
|
|
471
|
+
s.setAttribute("data-clone", String(this._items.length - 1)), this._sanitizeClone(s), this.prepend(s);
|
|
472
|
+
const i = t.cloneNode(!0);
|
|
473
|
+
i.setAttribute("data-clone", "0"), this._sanitizeClone(i), this.append(i), this._cloneItems = [s, i];
|
|
474
|
+
}
|
|
475
|
+
/** Keeps visual loop clones out of forms, focus order, and the accessibility tree. */
|
|
476
|
+
_sanitizeClone(t) {
|
|
477
|
+
t.setAttribute("aria-hidden", "true"), t.setAttribute("inert", "");
|
|
478
|
+
for (const e of [t, ...t.querySelectorAll("*")])
|
|
479
|
+
e.removeAttribute("id"), e.removeAttribute("name"), e.removeAttribute("form");
|
|
480
|
+
}
|
|
481
|
+
_removeClones() {
|
|
482
|
+
this._cloneItems.forEach((t) => t.remove()), this._cloneItems = [];
|
|
483
|
+
}
|
|
484
|
+
/**
|
|
485
|
+
* On release, a fast enough flick pre-nudges `scrollLeft` to the next
|
|
486
|
+
* snap point in the drag direction (a "decisive swipe" — mirroring
|
|
487
|
+
* `rc-bottom-sheet`'s own velocity-vs-nearest-point settle heuristic,
|
|
488
|
+
* adapted from "jump to the extreme end" for a 2-point sheet to "advance
|
|
489
|
+
* one further point" for a carousel that can have many). Either way,
|
|
490
|
+
* this hands off to the same debounced settle path a native swipe
|
|
491
|
+
* already goes through — our own `scrollLeft` writes during the drag
|
|
492
|
+
* already fired real `scroll` events, so `_onScroll` just needs to run
|
|
493
|
+
* its usual timer to pick up wherever things ended.
|
|
494
|
+
*/
|
|
495
|
+
_endDrag(t) {
|
|
496
|
+
if (this._dragging = !1, this._trackEl && (this._trackEl.style.scrollSnapType = ""), this._trackEl && Math.abs(t.velocityX) > z) {
|
|
497
|
+
const e = this._trackSlots(), s = t.velocityX < 0 ? 1 : -1, i = A(
|
|
498
|
+
e.map((a) => a.point),
|
|
499
|
+
this._trackEl.scrollLeft,
|
|
500
|
+
s
|
|
501
|
+
), n = e[i];
|
|
502
|
+
n && (this._trackEl.scrollLeft = n.point);
|
|
503
|
+
}
|
|
504
|
+
this._onScroll();
|
|
505
|
+
}
|
|
506
|
+
_paginationButtons() {
|
|
507
|
+
return Array.from(
|
|
508
|
+
this.shadowRoot?.querySelectorAll("[data-pagination-item]") ?? []
|
|
509
|
+
);
|
|
510
|
+
}
|
|
511
|
+
/**
|
|
512
|
+
* One-time tabindex seed for pagination buttons that have never
|
|
513
|
+
* received focus yet (matching `activeIndex`, the sane default) —
|
|
514
|
+
* `_onPaginationFocus` takes over from there. Safe to call on every
|
|
515
|
+
* render: a no-op once every button already has some tabindex value.
|
|
516
|
+
*/
|
|
517
|
+
_initPaginationTabIndex() {
|
|
518
|
+
this._paginationButtons().forEach((t, e) => {
|
|
519
|
+
t.hasAttribute("tabindex") || t.setAttribute("tabindex", e === this.activeIndex ? "0" : "-1");
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
render() {
|
|
523
|
+
const t = this._canStep(-1), e = this._canStep(1);
|
|
524
|
+
return h`
|
|
525
|
+
<div
|
|
526
|
+
id="track"
|
|
527
|
+
part="track"
|
|
528
|
+
tabindex="0"
|
|
529
|
+
class=${this._dragging ? "dragging" : d}
|
|
530
|
+
aria-busy=${this._busy ? "true" : "false"}
|
|
531
|
+
aria-atomic="true"
|
|
532
|
+
@scroll=${this._onScroll}
|
|
533
|
+
@click=${{ handleEvent: this._onTrackClickCapture, capture: !0 }}
|
|
534
|
+
${v(this._onNavigate, { navigationAxis: "horizontal" })}
|
|
535
|
+
>
|
|
536
|
+
<slot @slotchange=${this._onSlotChange}></slot>
|
|
537
|
+
</div>
|
|
538
|
+
|
|
539
|
+
${this.navigation ? h`
|
|
540
|
+
<div id="navigation" part="navigation">
|
|
541
|
+
<button
|
|
542
|
+
type="button"
|
|
543
|
+
part="navigation-button navigation-button-previous"
|
|
544
|
+
aria-label="Previous slide"
|
|
545
|
+
aria-disabled=${t ? "false" : "true"}
|
|
546
|
+
@click=${() => {
|
|
547
|
+
t && this._step(-1, "button");
|
|
548
|
+
}}
|
|
549
|
+
>
|
|
550
|
+
<span aria-hidden="true">
|
|
551
|
+
<slot name="previous-icon">
|
|
552
|
+
<svg
|
|
553
|
+
viewBox="0 0 6 10"
|
|
554
|
+
width="10"
|
|
555
|
+
height="16"
|
|
556
|
+
fill="none"
|
|
557
|
+
stroke="currentColor"
|
|
558
|
+
stroke-width="1.5"
|
|
559
|
+
stroke-linecap="round"
|
|
560
|
+
stroke-linejoin="round"
|
|
561
|
+
>
|
|
562
|
+
<polyline points="5,1 1,5 5,9" />
|
|
563
|
+
</svg>
|
|
564
|
+
</slot>
|
|
565
|
+
</span>
|
|
566
|
+
</button>
|
|
567
|
+
<button
|
|
568
|
+
type="button"
|
|
569
|
+
part="navigation-button navigation-button-next"
|
|
570
|
+
aria-label="Next slide"
|
|
571
|
+
aria-disabled=${e ? "false" : "true"}
|
|
572
|
+
@click=${() => {
|
|
573
|
+
e && this._step(1, "button");
|
|
574
|
+
}}
|
|
575
|
+
>
|
|
576
|
+
<span aria-hidden="true">
|
|
577
|
+
<slot name="next-icon">
|
|
578
|
+
<svg
|
|
579
|
+
viewBox="0 0 6 10"
|
|
580
|
+
width="10"
|
|
581
|
+
height="16"
|
|
582
|
+
fill="none"
|
|
583
|
+
stroke="currentColor"
|
|
584
|
+
stroke-width="1.5"
|
|
585
|
+
stroke-linecap="round"
|
|
586
|
+
stroke-linejoin="round"
|
|
587
|
+
>
|
|
588
|
+
<polyline points="1,1 5,5 1,9" />
|
|
589
|
+
</svg>
|
|
590
|
+
</slot>
|
|
591
|
+
</span>
|
|
592
|
+
</button>
|
|
593
|
+
</div>
|
|
594
|
+
` : d}
|
|
595
|
+
${this.pagination ? h`
|
|
596
|
+
<div
|
|
597
|
+
id="pagination"
|
|
598
|
+
part="pagination"
|
|
599
|
+
role="group"
|
|
600
|
+
aria-label="Choose slide to display"
|
|
601
|
+
@focusin=${this._onPaginationFocus}
|
|
602
|
+
${v(this._onPaginationNavigate, { navigationAxis: "horizontal" })}
|
|
603
|
+
>
|
|
604
|
+
${this._items.map(
|
|
605
|
+
(s, i) => h`
|
|
606
|
+
<button
|
|
607
|
+
type="button"
|
|
608
|
+
data-pagination-item
|
|
609
|
+
part="pagination-item${i === this.activeIndex ? " pagination-item-active" : ""}"
|
|
610
|
+
aria-label="Go to slide ${i + 1}"
|
|
611
|
+
aria-current=${i === this.activeIndex ? "true" : "false"}
|
|
612
|
+
aria-disabled=${i === this.activeIndex ? "true" : "false"}
|
|
613
|
+
@click=${() => this._setActiveIndex(i, "button")}
|
|
614
|
+
></button>
|
|
615
|
+
`
|
|
616
|
+
)}
|
|
617
|
+
</div>
|
|
618
|
+
` : d}
|
|
619
|
+
`;
|
|
620
|
+
}
|
|
621
|
+
};
|
|
622
|
+
_.styles = w;
|
|
623
|
+
let r = _;
|
|
624
|
+
o([
|
|
625
|
+
b("#track")
|
|
626
|
+
], r.prototype, "_trackEl", 2);
|
|
627
|
+
o([
|
|
628
|
+
b("slot:not([name])")
|
|
629
|
+
], r.prototype, "_slotEl", 2);
|
|
630
|
+
o([
|
|
631
|
+
l({ type: Boolean, reflect: !0 })
|
|
632
|
+
], r.prototype, "loop", 2);
|
|
633
|
+
o([
|
|
634
|
+
l({ type: Boolean, reflect: !0 })
|
|
635
|
+
], r.prototype, "navigation", 2);
|
|
636
|
+
o([
|
|
637
|
+
l({ type: Boolean, reflect: !0 })
|
|
638
|
+
], r.prototype, "pagination", 2);
|
|
639
|
+
o([
|
|
640
|
+
l({ type: Boolean, reflect: !0, attribute: "mouse-dragging" })
|
|
641
|
+
], r.prototype, "mouseDragging", 2);
|
|
642
|
+
o([
|
|
643
|
+
x()
|
|
644
|
+
], r.prototype, "_dragging", 2);
|
|
645
|
+
o([
|
|
646
|
+
x()
|
|
647
|
+
], r.prototype, "_busy", 2);
|
|
648
|
+
o([
|
|
649
|
+
l({ type: Number, attribute: "active-index" })
|
|
650
|
+
], r.prototype, "activeIndex", 1);
|
|
651
|
+
o([
|
|
652
|
+
l({ type: Number, attribute: "default-active-index" })
|
|
653
|
+
], r.prototype, "defaultActiveIndex", 1);
|
|
654
|
+
export {
|
|
655
|
+
r as R,
|
|
656
|
+
u as a
|
|
657
|
+
};
|
|
658
|
+
//# sourceMappingURL=rc-carousel-CdDEH9fb.js.map
|