@fluixi-ui/carousel 0.1.0-alpha.1 → 0.1.0-alpha.10
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/dist/carousel.d.ts +42 -0
- package/dist/carousel.d.ts.map +1 -1
- package/dist/carousel.js +456 -374
- package/dist/index.js +2 -10
- package/dist/tsconfig.lib.tsbuildinfo +1 -1
- package/package.json +6 -5
package/dist/carousel.js
CHANGED
|
@@ -1,388 +1,470 @@
|
|
|
1
|
-
import { delegateEvents,
|
|
2
|
-
import { createEffect, createSignal, onCleanup
|
|
1
|
+
import { createComponent, createNativeElement, delegateEvents, handleRef, holeContent, holeEnd, holeScope, insert, mergeProps, splitProps, spread, templateNode } from "@fluixi/dom";
|
|
2
|
+
import { createEffect, createMemo, createSignal, onCleanup } from "@fluixi/reactive/signal";
|
|
3
|
+
import { fxProps } from "@fluixi-ui/fx";
|
|
3
4
|
import { For } from "@fluixi/core";
|
|
4
|
-
import {
|
|
5
|
+
import { composeEventHandlers, composeId, createControllableSignal, createId, createSafeContext, dataAttr, mergeDefaultProps } from "@fluixi-ui/kit";
|
|
5
6
|
import { createCollection } from "@fluixi-ui/primitives";
|
|
6
|
-
|
|
7
|
-
|
|
7
|
+
//#region src/carousel.tsx
|
|
8
|
+
var _fxTmpl$0 = "<div data-carousel-status=\"\" aria-live=\"polite\" aria-atomic=\"true\" style=\"position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0\"><!--fx--><!--fx/--></div>";
|
|
9
|
+
delegateEvents(["click", "keydown"]);
|
|
10
|
+
var [CarouselProvider, useCarouselContext] = createSafeContext("Carousel");
|
|
11
|
+
/**
|
|
12
|
+
* Root of a carousel. Owns the active slide index (controlled `index` or
|
|
13
|
+
* uncontrolled `defaultIndex`), exposes navigation to its parts, and drives
|
|
14
|
+
* optional autoplay. Arrow keys move between slides when the carousel is focused.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```tsx
|
|
18
|
+
* import {
|
|
19
|
+
* Carousel, CarouselViewport, CarouselSlide,
|
|
20
|
+
* CarouselPrevious, CarouselNext, CarouselDots,
|
|
21
|
+
* } from '@fluixi-ui/carousel';
|
|
22
|
+
*
|
|
23
|
+
* <Carousel loop autoplay>
|
|
24
|
+
* <CarouselViewport>
|
|
25
|
+
* <CarouselSlide><img src="/1.jpg" alt="" /></CarouselSlide>
|
|
26
|
+
* <CarouselSlide><img src="/2.jpg" alt="" /></CarouselSlide>
|
|
27
|
+
* </CarouselViewport>
|
|
28
|
+
* <CarouselPrevious /><CarouselNext />
|
|
29
|
+
* <CarouselDots />
|
|
30
|
+
* </Carousel>
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
8
33
|
function Carousel(props) {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
34
|
+
const merged = mergeDefaultProps({
|
|
35
|
+
orientation: "horizontal",
|
|
36
|
+
loop: false,
|
|
37
|
+
pauseOnHover: true,
|
|
38
|
+
draggable: true
|
|
39
|
+
}, props);
|
|
40
|
+
const [local, others] = splitProps(merged, [
|
|
41
|
+
"index",
|
|
42
|
+
"defaultIndex",
|
|
43
|
+
"onIndexChange",
|
|
44
|
+
"orientation",
|
|
45
|
+
"loop",
|
|
46
|
+
"autoplay",
|
|
47
|
+
"autoplayInterval",
|
|
48
|
+
"pauseOnHover",
|
|
49
|
+
"draggable",
|
|
50
|
+
"overlay",
|
|
51
|
+
"ref",
|
|
52
|
+
"children",
|
|
53
|
+
"onKeyDown",
|
|
54
|
+
"fx",
|
|
55
|
+
"style"
|
|
56
|
+
]);
|
|
57
|
+
const baseId = createId("carousel");
|
|
58
|
+
const collection = createCollection();
|
|
59
|
+
const count = () => collection.items().length;
|
|
60
|
+
const loop = () => local.loop ?? false;
|
|
61
|
+
const orientation = () => local.orientation ?? "horizontal";
|
|
62
|
+
const [rawIndex, setIndex] = createControllableSignal({
|
|
63
|
+
value: () => local.index,
|
|
64
|
+
defaultValue: () => local.defaultIndex ?? 0,
|
|
65
|
+
onChange: (next) => local.onIndexChange?.(next)
|
|
66
|
+
});
|
|
67
|
+
const index = () => rawIndex() ?? 0;
|
|
68
|
+
const clamp = (i) => {
|
|
69
|
+
const c = count();
|
|
70
|
+
if (c <= 0) return 0;
|
|
71
|
+
return loop() ? (i % c + c) % c : Math.min(Math.max(i, 0), c - 1);
|
|
72
|
+
};
|
|
73
|
+
const goTo = (i) => setIndex(clamp(i));
|
|
74
|
+
const next = () => goTo(index() + 1);
|
|
75
|
+
const previous = () => goTo(index() - 1);
|
|
76
|
+
const canPrevious = () => loop() || index() > 0;
|
|
77
|
+
const canNext = () => loop() || index() < count() - 1;
|
|
78
|
+
createEffect(() => {
|
|
79
|
+
const c = count();
|
|
80
|
+
if (c > 0 && index() > c - 1) setIndex(c - 1);
|
|
81
|
+
});
|
|
82
|
+
const [paused, setPaused] = createSignal(false);
|
|
83
|
+
const [dragging, setDragging] = createSignal(false);
|
|
84
|
+
const [reducedMotion, setReducedMotion] = createSignal(false);
|
|
85
|
+
const mq = typeof window !== "undefined" && typeof window.matchMedia === "function" ? window.matchMedia("(prefers-reduced-motion: reduce)") : null;
|
|
86
|
+
if (mq) {
|
|
87
|
+
setReducedMotion(mq.matches);
|
|
88
|
+
const onChange = () => setReducedMotion(mq.matches);
|
|
89
|
+
mq.addEventListener?.("change", onChange);
|
|
90
|
+
onCleanup(() => mq.removeEventListener?.("change", onChange));
|
|
91
|
+
}
|
|
92
|
+
const autoplayMs = () => {
|
|
93
|
+
const a = local.autoplay;
|
|
94
|
+
if (a === true) return local.autoplayInterval ?? 4e3;
|
|
95
|
+
if (typeof a === "number") return a;
|
|
96
|
+
return 0;
|
|
97
|
+
};
|
|
98
|
+
createEffect(() => {
|
|
99
|
+
const ms = autoplayMs();
|
|
100
|
+
if (!ms || paused() || dragging() || reducedMotion() || count() <= 1) return;
|
|
101
|
+
const id = setInterval(() => next(), ms);
|
|
102
|
+
onCleanup(() => clearInterval(id));
|
|
103
|
+
});
|
|
104
|
+
const onRootKeyDown = (event) => {
|
|
105
|
+
const horizontal = orientation() === "horizontal";
|
|
106
|
+
switch (event.key) {
|
|
107
|
+
case horizontal ? "ArrowLeft" : "ArrowUp":
|
|
108
|
+
event.preventDefault();
|
|
109
|
+
previous();
|
|
110
|
+
break;
|
|
111
|
+
case horizontal ? "ArrowRight" : "ArrowDown":
|
|
112
|
+
event.preventDefault();
|
|
113
|
+
next();
|
|
114
|
+
break;
|
|
115
|
+
case "Home":
|
|
116
|
+
event.preventDefault();
|
|
117
|
+
goTo(0);
|
|
118
|
+
break;
|
|
119
|
+
case "End":
|
|
120
|
+
event.preventDefault();
|
|
121
|
+
goTo(count() - 1);
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
const setRootRef = (element) => {
|
|
125
|
+
const pause = () => local.pauseOnHover && setPaused(true);
|
|
126
|
+
const resume = () => local.pauseOnHover && setPaused(false);
|
|
127
|
+
element.addEventListener("pointerenter", pause);
|
|
128
|
+
element.addEventListener("pointerleave", resume);
|
|
129
|
+
element.addEventListener("focusin", pause);
|
|
130
|
+
element.addEventListener("focusout", resume);
|
|
131
|
+
onCleanup(() => {
|
|
132
|
+
element.removeEventListener("pointerenter", pause);
|
|
133
|
+
element.removeEventListener("pointerleave", resume);
|
|
134
|
+
element.removeEventListener("focusin", pause);
|
|
135
|
+
element.removeEventListener("focusout", resume);
|
|
136
|
+
});
|
|
137
|
+
handleRef(local.ref, element);
|
|
138
|
+
};
|
|
139
|
+
const context = {
|
|
140
|
+
index,
|
|
141
|
+
count,
|
|
142
|
+
orientation,
|
|
143
|
+
loop,
|
|
144
|
+
goTo,
|
|
145
|
+
next,
|
|
146
|
+
previous,
|
|
147
|
+
canPrevious,
|
|
148
|
+
canNext,
|
|
149
|
+
collection,
|
|
150
|
+
draggable: () => local.draggable ?? true,
|
|
151
|
+
isDragging: dragging,
|
|
152
|
+
setDragging,
|
|
153
|
+
ids: { root: composeId(baseId, "root") }
|
|
154
|
+
};
|
|
155
|
+
return createMemo(() => createComponent(CarouselProvider, {
|
|
156
|
+
get value() {
|
|
157
|
+
return context;
|
|
158
|
+
},
|
|
159
|
+
get children() {
|
|
160
|
+
return (() => {
|
|
161
|
+
const _el$ = createNativeElement("div");
|
|
162
|
+
spread({
|
|
163
|
+
element: _el$,
|
|
164
|
+
props: mergeProps(fxProps(() => local.fx, () => local.style), others, {
|
|
165
|
+
ref: setRootRef,
|
|
166
|
+
id: () => context.ids.root,
|
|
167
|
+
"data-carousel": "",
|
|
168
|
+
"data-overlay": () => local.overlay ? "" : void 0,
|
|
169
|
+
role: "region",
|
|
170
|
+
"aria-roledescription": "carousel",
|
|
171
|
+
"data-orientation": () => orientation(),
|
|
172
|
+
tabindex: 0,
|
|
173
|
+
onKeyDown: composeEventHandlers([local.onKeyDown, onRootKeyDown])
|
|
174
|
+
})
|
|
175
|
+
});
|
|
176
|
+
insert(_el$, local.children, null);
|
|
177
|
+
insert(_el$, (() => {
|
|
178
|
+
const _el$ = templateNode(_fxTmpl$0, "div", true);
|
|
179
|
+
const _n$0 = _el$.firstChild;
|
|
180
|
+
const _n$1 = holeEnd(_n$0);
|
|
181
|
+
insert(_el$, holeScope(_n$0, () => () => `Slide ${index() + 1} of ${count()}`), _n$1, holeContent(_n$0, _n$1));
|
|
182
|
+
return _el$;
|
|
183
|
+
})());
|
|
184
|
+
return _el$;
|
|
185
|
+
})();
|
|
186
|
+
}
|
|
187
|
+
}));
|
|
156
188
|
}
|
|
189
|
+
/** Clips the slides and translates the track to the active slide. */
|
|
157
190
|
function CarouselViewport(props) {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
191
|
+
const ctx = useCarouselContext();
|
|
192
|
+
const [local, others] = splitProps(props, [
|
|
193
|
+
"ref",
|
|
194
|
+
"children",
|
|
195
|
+
"style",
|
|
196
|
+
"fx",
|
|
197
|
+
"style"
|
|
198
|
+
]);
|
|
199
|
+
const [dragDelta, setDragDelta] = createSignal(0);
|
|
200
|
+
const trackTransform = () => {
|
|
201
|
+
const base = ctx.index() * -100;
|
|
202
|
+
const delta = dragDelta();
|
|
203
|
+
const axis = ctx.orientation() === "vertical" ? "Y" : "X";
|
|
204
|
+
return delta === 0 ? `transform:translate${axis}(${base}%)` : `transform:translate${axis}(calc(${base}% + ${delta}px))`;
|
|
205
|
+
};
|
|
206
|
+
const setRef = (element) => {
|
|
207
|
+
const vertical = () => ctx.orientation() === "vertical";
|
|
208
|
+
let active = false;
|
|
209
|
+
let pointerId = -1;
|
|
210
|
+
let start = 0;
|
|
211
|
+
let size = 1;
|
|
212
|
+
let moved = false;
|
|
213
|
+
const onDown = (event) => {
|
|
214
|
+
if (!ctx.draggable() || event.button !== 0) return;
|
|
215
|
+
active = true;
|
|
216
|
+
moved = false;
|
|
217
|
+
pointerId = event.pointerId;
|
|
218
|
+
start = vertical() ? event.clientY : event.clientX;
|
|
219
|
+
size = (vertical() ? element.clientHeight : element.clientWidth) || 1;
|
|
220
|
+
element.setPointerCapture?.(event.pointerId);
|
|
221
|
+
ctx.setDragging(true);
|
|
222
|
+
};
|
|
223
|
+
const onMove = (event) => {
|
|
224
|
+
if (!active || event.pointerId !== pointerId) return;
|
|
225
|
+
const delta = (vertical() ? event.clientY : event.clientX) - start;
|
|
226
|
+
if (Math.abs(delta) > 3) moved = true;
|
|
227
|
+
setDragDelta(delta);
|
|
228
|
+
event.preventDefault();
|
|
229
|
+
};
|
|
230
|
+
const end = (event) => {
|
|
231
|
+
if (!active || event.pointerId !== pointerId) return;
|
|
232
|
+
active = false;
|
|
233
|
+
const delta = dragDelta();
|
|
234
|
+
setDragDelta(0);
|
|
235
|
+
ctx.setDragging(false);
|
|
236
|
+
element.releasePointerCapture?.(event.pointerId);
|
|
237
|
+
const threshold = Math.max(size * .2, 40);
|
|
238
|
+
if (delta <= -threshold) ctx.next();
|
|
239
|
+
else if (delta >= threshold) ctx.previous();
|
|
240
|
+
};
|
|
241
|
+
const onClick = (event) => {
|
|
242
|
+
if (moved) {
|
|
243
|
+
event.preventDefault();
|
|
244
|
+
event.stopPropagation();
|
|
245
|
+
moved = false;
|
|
246
|
+
}
|
|
247
|
+
};
|
|
248
|
+
element.addEventListener("pointerdown", onDown);
|
|
249
|
+
element.addEventListener("pointermove", onMove);
|
|
250
|
+
element.addEventListener("pointerup", end);
|
|
251
|
+
element.addEventListener("pointercancel", end);
|
|
252
|
+
element.addEventListener("click", onClick, true);
|
|
253
|
+
onCleanup(() => {
|
|
254
|
+
element.removeEventListener("pointerdown", onDown);
|
|
255
|
+
element.removeEventListener("pointermove", onMove);
|
|
256
|
+
element.removeEventListener("pointerup", end);
|
|
257
|
+
element.removeEventListener("pointercancel", end);
|
|
258
|
+
element.removeEventListener("click", onClick, true);
|
|
259
|
+
});
|
|
260
|
+
handleRef(local.ref, element);
|
|
261
|
+
};
|
|
262
|
+
return (() => {
|
|
263
|
+
const _el$ = createNativeElement("div");
|
|
264
|
+
spread({
|
|
265
|
+
element: _el$,
|
|
266
|
+
props: mergeProps(fxProps(() => local.fx, () => local.style), others, {
|
|
267
|
+
ref: setRef,
|
|
268
|
+
"data-carousel-viewport": "",
|
|
269
|
+
"data-orientation": () => ctx.orientation()
|
|
270
|
+
})
|
|
271
|
+
});
|
|
272
|
+
insert(_el$, (() => {
|
|
273
|
+
const _el$ = createNativeElement("div");
|
|
274
|
+
spread({
|
|
275
|
+
element: _el$,
|
|
276
|
+
props: {
|
|
277
|
+
"data-carousel-track": "",
|
|
278
|
+
"data-orientation": () => ctx.orientation(),
|
|
279
|
+
"data-dragging": () => dataAttr(ctx.isDragging()),
|
|
280
|
+
style: () => trackTransform()
|
|
281
|
+
}
|
|
282
|
+
});
|
|
283
|
+
insert(_el$, local.children, null);
|
|
284
|
+
return _el$;
|
|
285
|
+
})());
|
|
286
|
+
return _el$;
|
|
287
|
+
})();
|
|
250
288
|
}
|
|
289
|
+
/** A single slide; registers with the carousel so counts and dots stay in sync. */
|
|
251
290
|
function CarouselSlide(props) {
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
291
|
+
const ctx = useCarouselContext();
|
|
292
|
+
const [local, others] = splitProps(props, [
|
|
293
|
+
"ref",
|
|
294
|
+
"children",
|
|
295
|
+
"fx",
|
|
296
|
+
"style"
|
|
297
|
+
]);
|
|
298
|
+
const [node, setNode] = createSignal(void 0);
|
|
299
|
+
const [idx, setIdx] = createSignal(-1);
|
|
300
|
+
const setRef = (element) => {
|
|
301
|
+
setNode(element);
|
|
302
|
+
const remove = ctx.collection.register({ ref: element });
|
|
303
|
+
onCleanup(remove);
|
|
304
|
+
handleRef(local.ref, element);
|
|
305
|
+
};
|
|
306
|
+
createEffect(() => {
|
|
307
|
+
ctx.count();
|
|
308
|
+
const element = node();
|
|
309
|
+
const parent = element?.parentElement;
|
|
310
|
+
if (!element || !parent) {
|
|
311
|
+
setIdx(-1);
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
setIdx([...parent.querySelectorAll(":scope > [data-carousel-slide]")].indexOf(element));
|
|
315
|
+
});
|
|
316
|
+
const isActive = () => idx() === ctx.index();
|
|
317
|
+
return (() => {
|
|
318
|
+
const _el$ = createNativeElement("div");
|
|
319
|
+
spread({
|
|
320
|
+
element: _el$,
|
|
321
|
+
props: mergeProps(fxProps(() => local.fx, () => local.style), others, {
|
|
322
|
+
ref: setRef,
|
|
323
|
+
"data-carousel-slide": "",
|
|
324
|
+
role: "group",
|
|
325
|
+
"aria-roledescription": "slide",
|
|
326
|
+
"aria-label": () => {
|
|
327
|
+
const i = idx();
|
|
328
|
+
return i >= 0 ? `${i + 1} of ${ctx.count()}` : void 0;
|
|
329
|
+
},
|
|
330
|
+
"aria-hidden": () => isActive() ? void 0 : "true",
|
|
331
|
+
"data-active": () => dataAttr(isActive())
|
|
332
|
+
})
|
|
333
|
+
});
|
|
334
|
+
insert(_el$, local.children, null);
|
|
335
|
+
return _el$;
|
|
336
|
+
})();
|
|
295
337
|
}
|
|
338
|
+
/**
|
|
339
|
+
* Lays out the carousel's controls: previous, dots, next.
|
|
340
|
+
*
|
|
341
|
+
* Without it every consumer hand-rolls a flex row, and they tend to end up flush
|
|
342
|
+
* against the carousel's edges. In `overlay` mode this becomes a transparent
|
|
343
|
+
* layer over the slides — it stops reserving height, and only the controls
|
|
344
|
+
* themselves take pointer events, so a drag still starts on the slide beneath.
|
|
345
|
+
*
|
|
346
|
+
* @example
|
|
347
|
+
* ```tsx
|
|
348
|
+
* <Carousel overlay>
|
|
349
|
+
* <CarouselViewport>…</CarouselViewport>
|
|
350
|
+
* <CarouselControls>
|
|
351
|
+
* <CarouselPrevious />
|
|
352
|
+
* <CarouselDots />
|
|
353
|
+
* <CarouselNext />
|
|
354
|
+
* </CarouselControls>
|
|
355
|
+
* </Carousel>
|
|
356
|
+
* ```
|
|
357
|
+
*/
|
|
358
|
+
function CarouselControls(props) {
|
|
359
|
+
const [local, others] = splitProps(props, [
|
|
360
|
+
"children",
|
|
361
|
+
"fx",
|
|
362
|
+
"style"
|
|
363
|
+
]);
|
|
364
|
+
return (() => {
|
|
365
|
+
const _el$ = createNativeElement("div");
|
|
366
|
+
spread({
|
|
367
|
+
element: _el$,
|
|
368
|
+
props: mergeProps(fxProps(() => local.fx, () => local.style), others, { "data-carousel-controls": "" })
|
|
369
|
+
});
|
|
370
|
+
insert(_el$, local.children, null);
|
|
371
|
+
return _el$;
|
|
372
|
+
})();
|
|
373
|
+
}
|
|
374
|
+
/** Moves to the previous slide; disabled at the start unless the carousel loops. */
|
|
296
375
|
function CarouselPrevious(props) {
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
376
|
+
const ctx = useCarouselContext();
|
|
377
|
+
const merged = mergeDefaultProps({
|
|
378
|
+
type: "button",
|
|
379
|
+
"aria-label": "Previous slide"
|
|
380
|
+
}, props);
|
|
381
|
+
const [local, others] = splitProps(merged, [
|
|
382
|
+
"onClick",
|
|
383
|
+
"children",
|
|
384
|
+
"fx",
|
|
385
|
+
"style"
|
|
386
|
+
]);
|
|
387
|
+
return (() => {
|
|
388
|
+
const _el$ = createNativeElement("button");
|
|
389
|
+
spread({
|
|
390
|
+
element: _el$,
|
|
391
|
+
props: mergeProps(fxProps(() => local.fx, () => local.style), others, {
|
|
392
|
+
"data-carousel-prev": "",
|
|
393
|
+
"aria-controls": () => ctx.ids.root,
|
|
394
|
+
disabled: () => !ctx.canPrevious(),
|
|
395
|
+
onClick: composeEventHandlers([local.onClick, () => ctx.previous()])
|
|
396
|
+
})
|
|
397
|
+
});
|
|
398
|
+
insert(_el$, local.children ?? "‹", null);
|
|
399
|
+
return _el$;
|
|
400
|
+
})();
|
|
317
401
|
}
|
|
402
|
+
/** Moves to the next slide; disabled at the end unless the carousel loops. */
|
|
318
403
|
function CarouselNext(props) {
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
404
|
+
const ctx = useCarouselContext();
|
|
405
|
+
const merged = mergeDefaultProps({
|
|
406
|
+
type: "button",
|
|
407
|
+
"aria-label": "Next slide"
|
|
408
|
+
}, props);
|
|
409
|
+
const [local, others] = splitProps(merged, [
|
|
410
|
+
"onClick",
|
|
411
|
+
"children",
|
|
412
|
+
"fx",
|
|
413
|
+
"style"
|
|
414
|
+
]);
|
|
415
|
+
return (() => {
|
|
416
|
+
const _el$ = createNativeElement("button");
|
|
417
|
+
spread({
|
|
418
|
+
element: _el$,
|
|
419
|
+
props: mergeProps(fxProps(() => local.fx, () => local.style), others, {
|
|
420
|
+
"data-carousel-next": "",
|
|
421
|
+
"aria-controls": () => ctx.ids.root,
|
|
422
|
+
disabled: () => !ctx.canNext(),
|
|
423
|
+
onClick: composeEventHandlers([local.onClick, () => ctx.next()])
|
|
424
|
+
})
|
|
425
|
+
});
|
|
426
|
+
insert(_el$, local.children ?? "›", null);
|
|
427
|
+
return _el$;
|
|
428
|
+
})();
|
|
339
429
|
}
|
|
430
|
+
/** One clickable dot per slide, reflecting and setting the active index. */
|
|
340
431
|
function CarouselDots(props) {
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
return _el$;
|
|
378
|
-
})();
|
|
432
|
+
const ctx = useCarouselContext();
|
|
433
|
+
const dots = () => Array.from({ length: ctx.count() }, (_, i) => i);
|
|
434
|
+
return (() => {
|
|
435
|
+
const _el$ = createNativeElement("div");
|
|
436
|
+
spread({
|
|
437
|
+
element: _el$,
|
|
438
|
+
props: mergeProps(props, {
|
|
439
|
+
"data-carousel-dots": "",
|
|
440
|
+
role: "group",
|
|
441
|
+
"aria-label": "Choose slide"
|
|
442
|
+
})
|
|
443
|
+
});
|
|
444
|
+
insert(_el$, createMemo(() => createComponent(For, {
|
|
445
|
+
get each() {
|
|
446
|
+
return dots;
|
|
447
|
+
},
|
|
448
|
+
get children() {
|
|
449
|
+
return (i) => (() => {
|
|
450
|
+
const _el$ = createNativeElement("button");
|
|
451
|
+
spread({
|
|
452
|
+
element: _el$,
|
|
453
|
+
props: {
|
|
454
|
+
type: "button",
|
|
455
|
+
"data-carousel-dot": "",
|
|
456
|
+
"data-active": () => dataAttr(i === ctx.index()),
|
|
457
|
+
"aria-label": `Go to slide ${i + 1}`,
|
|
458
|
+
"aria-current": () => i === ctx.index() ? "true" : void 0,
|
|
459
|
+
onClick: () => ctx.goTo(i)
|
|
460
|
+
}
|
|
461
|
+
});
|
|
462
|
+
return _el$;
|
|
463
|
+
})();
|
|
464
|
+
}
|
|
465
|
+
})), null);
|
|
466
|
+
return _el$;
|
|
467
|
+
})();
|
|
379
468
|
}
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
CarouselDots,
|
|
383
|
-
CarouselNext,
|
|
384
|
-
CarouselPrevious,
|
|
385
|
-
CarouselSlide,
|
|
386
|
-
CarouselViewport,
|
|
387
|
-
useCarouselContext
|
|
388
|
-
};
|
|
469
|
+
//#endregion
|
|
470
|
+
export { Carousel, CarouselControls, CarouselDots, CarouselNext, CarouselPrevious, CarouselSlide, CarouselViewport, useCarouselContext };
|