@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.
@@ -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