@nordwerk/scroll-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/AGENTS.md +114 -0
- package/CHANGELOG.md +15 -0
- package/LICENSE +21 -0
- package/README.md +226 -0
- package/dist/adapter.d.ts +55 -0
- package/dist/autoplay.d.ts +14 -0
- package/dist/autoplay.js +67 -0
- package/dist/carousel.css +258 -0
- package/dist/drag.d.ts +6 -0
- package/dist/drag.js +79 -0
- package/dist/index.d.ts +95 -0
- package/dist/index.js +9 -0
- package/dist/markup.d.ts +46 -0
- package/dist/markup.js +14 -0
- package/dist/math.d.ts +45 -0
- package/dist/preact.d.ts +9 -0
- package/dist/preact.js +24 -0
- package/dist/react.d.ts +9 -0
- package/dist/react.js +23 -0
- package/dist/shared/chunk-3THCBXDW.js +78 -0
- package/dist/shared/chunk-5Q4YPVNK.js +109 -0
- package/dist/shared/chunk-HPGJKTV5.js +377 -0
- package/dist/shared/chunk-I4ZMRKZV.js +60 -0
- package/docs/migration.md +84 -0
- package/package.json +106 -0
- package/src/astro/Carousel.astro +88 -0
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* scroll-carousel, prototype stylesheet.
|
|
3
|
+
*
|
|
4
|
+
* The layout is complete without JavaScript: the server-rendered markup is already the final
|
|
5
|
+
* layout, and the script only adds controls. Everything is configured with custom properties,
|
|
6
|
+
* which can be set anywhere above the track and changed in media or container queries. The root
|
|
7
|
+
* is the query container, so inside `@container sc (...)` set them on .sc-track.
|
|
8
|
+
*
|
|
9
|
+
* --sc-per-view slides per view; a fraction shows part of the next slide. Default 1.
|
|
10
|
+
* --sc-slide-size a fixed slide size, or auto for the content width. Wins over per-view.
|
|
11
|
+
* --sc-gap space between slides. Default 1rem.
|
|
12
|
+
* --sc-offset-before space before the first slide; snapped slides line up with it. Default 0.
|
|
13
|
+
* --sc-offset-after space after the last slide. Default 0.
|
|
14
|
+
* --sc-snap mandatory, proximity or none. Default mandatory.
|
|
15
|
+
* --sc-align start or center. Default start.
|
|
16
|
+
* --sc-centered 1 pads both ends so the first and last slide can reach the centre.
|
|
17
|
+
* Derived from --sc-per-view, so it does not combine with --sc-slide-size.
|
|
18
|
+
* --sc-count the number of slides, when known; per-view never exceeds it.
|
|
19
|
+
* --sc-group slides per step of next and previous: a number, or page. Read by the script.
|
|
20
|
+
* --sc-controls none hides the arrows, dots and play button, e.g. for free mode on phones.
|
|
21
|
+
*
|
|
22
|
+
* Theme, all optional: --sc-control-bg, --sc-control-fg, --sc-control-border,
|
|
23
|
+
* --sc-control-size, --sc-control-radius, --sc-control-shadow, --sc-nav-top, --sc-nav-inset,
|
|
24
|
+
* --sc-dot, --sc-dot-size, --sc-dot-height, --sc-dot-active-size, --sc-dot-radius, --sc-dot-idle,
|
|
25
|
+
* --sc-focus.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
.sc {
|
|
29
|
+
container: sc / inline-size;
|
|
30
|
+
position: relative;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.sc-track {
|
|
34
|
+
--_n: min(var(--sc-per-view, 1), var(--sc-count, 1000));
|
|
35
|
+
--_gap: var(--sc-gap, 1rem);
|
|
36
|
+
--_size: calc(
|
|
37
|
+
(100cqi - var(--sc-offset-before, 0px) - var(--sc-offset-after, 0px) - (var(--_n) - 1) * var(--_gap)) /
|
|
38
|
+
var(--_n)
|
|
39
|
+
);
|
|
40
|
+
--_edge: calc((100cqi - var(--_size)) / 2 * var(--sc-centered, 0));
|
|
41
|
+
/* Also the containing block for anything absolutely positioned inside the slides, which the
|
|
42
|
+
scroller then clips instead of letting it widen the page. */
|
|
43
|
+
position: relative;
|
|
44
|
+
display: flex;
|
|
45
|
+
gap: var(--_gap);
|
|
46
|
+
margin: 0;
|
|
47
|
+
padding-block: 0;
|
|
48
|
+
padding-inline: calc(var(--sc-offset-before, 0px) + var(--_edge)) calc(var(--sc-offset-after, 0px) + var(--_edge));
|
|
49
|
+
list-style: none;
|
|
50
|
+
overflow-x: auto;
|
|
51
|
+
overflow-y: hidden;
|
|
52
|
+
overscroll-behavior-x: contain;
|
|
53
|
+
/* `x none` is invalid, which makes the property fall back to its initial value, none. */
|
|
54
|
+
scroll-snap-type: x var(--sc-snap, mandatory);
|
|
55
|
+
scroll-padding-inline: var(--sc-offset-before, 0px) var(--sc-offset-after, 0px);
|
|
56
|
+
scrollbar-width: none;
|
|
57
|
+
transition: opacity 0.16s ease;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.sc-track::-webkit-scrollbar {
|
|
61
|
+
display: none;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/* Zero specificity, so a host can size its own slides without a fight. */
|
|
65
|
+
:where(.sc-track > *) {
|
|
66
|
+
flex: 0 0 var(--sc-slide-size, var(--_size));
|
|
67
|
+
min-width: 0;
|
|
68
|
+
scroll-snap-align: var(--sc-align, start);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
:where(.sc-track > [data-sc-snap-off]) {
|
|
72
|
+
scroll-snap-align: none;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/* A row too short to scroll sits in the middle; auto margins collapse to 0 once it overflows. */
|
|
76
|
+
.sc--center-few :where(.sc-track > :first-child) {
|
|
77
|
+
margin-inline-start: auto;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.sc--center-few :where(.sc-track > :last-child) {
|
|
81
|
+
margin-inline-end: auto;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.sc-track:focus-visible {
|
|
85
|
+
outline: 2px solid var(--sc-focus, Highlight);
|
|
86
|
+
outline-offset: -2px;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.sc[data-sc-drag][data-sc-overflow] .sc-track {
|
|
90
|
+
cursor: grab;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.sc[data-sc-drag] .sc-track :is(img, a) {
|
|
94
|
+
-webkit-user-drag: none;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.sc-track[data-sc-dragging] {
|
|
98
|
+
cursor: grabbing;
|
|
99
|
+
scroll-snap-type: none;
|
|
100
|
+
-webkit-user-select: none;
|
|
101
|
+
user-select: none;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.sc-track[data-sc-fading] {
|
|
105
|
+
opacity: 0;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/* Default controls. All optional: a host can bring its own and bind them to the API. */
|
|
109
|
+
|
|
110
|
+
.sc-nav,
|
|
111
|
+
.sc-play {
|
|
112
|
+
display: var(--sc-controls, grid);
|
|
113
|
+
place-items: center;
|
|
114
|
+
box-sizing: border-box;
|
|
115
|
+
inline-size: var(--sc-control-size, 2.75rem);
|
|
116
|
+
block-size: var(--sc-control-size, 2.75rem);
|
|
117
|
+
padding: 0;
|
|
118
|
+
border: 1px solid var(--sc-control-border, color-mix(in srgb, currentColor 22%, transparent));
|
|
119
|
+
border-radius: var(--sc-control-radius, 50%);
|
|
120
|
+
background: var(--sc-control-bg, Canvas);
|
|
121
|
+
box-shadow: var(--sc-control-shadow, none);
|
|
122
|
+
color: var(--sc-control-fg, CanvasText);
|
|
123
|
+
font: inherit;
|
|
124
|
+
cursor: pointer;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.sc-nav {
|
|
128
|
+
position: absolute;
|
|
129
|
+
z-index: 1;
|
|
130
|
+
inset-block-start: var(--sc-nav-top, 50%);
|
|
131
|
+
translate: 0 -50%;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
.sc-prev {
|
|
135
|
+
inset-inline-start: var(--sc-nav-inset, 0.5rem);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.sc-next {
|
|
139
|
+
inset-inline-end: var(--sc-nav-inset, 0.5rem);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.sc-nav svg,
|
|
143
|
+
.sc-play svg {
|
|
144
|
+
inline-size: 1.25rem;
|
|
145
|
+
block-size: 1.25rem;
|
|
146
|
+
fill: none;
|
|
147
|
+
stroke: currentColor;
|
|
148
|
+
stroke-width: 2;
|
|
149
|
+
stroke-linecap: round;
|
|
150
|
+
stroke-linejoin: round;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
.sc-nav:dir(rtl) svg {
|
|
154
|
+
scale: -1 1;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
.sc-nav[aria-disabled='true'] {
|
|
158
|
+
opacity: 0.35;
|
|
159
|
+
cursor: default;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
.sc-play .sc-icon-pause,
|
|
163
|
+
.sc[data-sc-playing] .sc-play .sc-icon-play {
|
|
164
|
+
display: none;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
.sc[data-sc-playing] .sc-play .sc-icon-pause {
|
|
168
|
+
display: block;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
.sc-dots {
|
|
172
|
+
display: var(--sc-controls, flex);
|
|
173
|
+
flex-wrap: wrap;
|
|
174
|
+
justify-content: center;
|
|
175
|
+
min-block-size: 1.5rem;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
.sc-dot {
|
|
179
|
+
display: grid;
|
|
180
|
+
place-items: center;
|
|
181
|
+
inline-size: 1.5rem;
|
|
182
|
+
block-size: 1.5rem;
|
|
183
|
+
padding: 0;
|
|
184
|
+
border: 0;
|
|
185
|
+
background: none;
|
|
186
|
+
color: var(--sc-dot, currentColor);
|
|
187
|
+
cursor: pointer;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
.sc-dot::before {
|
|
191
|
+
content: '';
|
|
192
|
+
inline-size: var(--sc-dot-size, 0.5rem);
|
|
193
|
+
block-size: var(--sc-dot-height, var(--sc-dot-size, 0.5rem));
|
|
194
|
+
border-radius: var(--sc-dot-radius, 1rem);
|
|
195
|
+
background: currentColor;
|
|
196
|
+
opacity: var(--sc-dot-idle, 0.35);
|
|
197
|
+
transition: inline-size 0.2s ease, opacity 0.2s ease;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
.sc-dot[aria-current]::before {
|
|
201
|
+
inline-size: var(--sc-dot-active-size, 1.25rem);
|
|
202
|
+
opacity: 1;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/* While autoplay runs, the current dot fills up until the next move. */
|
|
206
|
+
.sc[data-sc-playing] .sc-dot[aria-current]::before {
|
|
207
|
+
background:
|
|
208
|
+
linear-gradient(currentColor, currentColor) 0 0 / 0 100% no-repeat,
|
|
209
|
+
color-mix(in srgb, currentColor 35%, transparent);
|
|
210
|
+
animation: sc-progress var(--sc-autoplay-delay, 5s) linear forwards;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
.sc[data-sc-playing] .sc-dot[aria-current]:dir(rtl)::before {
|
|
214
|
+
background-position: 100% 0;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
.sc[data-sc-paused] .sc-dot::before {
|
|
218
|
+
animation-play-state: paused;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
@keyframes sc-progress {
|
|
222
|
+
to {
|
|
223
|
+
background-size: 100% 100%;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
:is(.sc-nav, .sc-play, .sc-dot):focus-visible {
|
|
228
|
+
outline: 2px solid var(--sc-focus, Highlight);
|
|
229
|
+
outline-offset: 2px;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/* Without the script, or without overflow, controls are absent: never focusable and dead.
|
|
233
|
+
Arrows float over the track and can simply go; dots and the play button keep their space,
|
|
234
|
+
so attaching the script cannot shift the layout around them. */
|
|
235
|
+
.sc:not([data-sc-ready][data-sc-overflow]) .sc-nav {
|
|
236
|
+
display: none;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
.sc:not([data-sc-ready][data-sc-overflow]) :is(.sc-dots, .sc-play) {
|
|
240
|
+
visibility: hidden;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
.sc-status,
|
|
244
|
+
.sc-skip:not(:focus) {
|
|
245
|
+
position: absolute;
|
|
246
|
+
inline-size: 1px;
|
|
247
|
+
block-size: 1px;
|
|
248
|
+
overflow: hidden;
|
|
249
|
+
clip-path: inset(50%);
|
|
250
|
+
white-space: nowrap;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
@media (prefers-reduced-motion: reduce) {
|
|
254
|
+
.sc-track,
|
|
255
|
+
.sc-dot::before {
|
|
256
|
+
transition: none;
|
|
257
|
+
}
|
|
258
|
+
}
|
package/dist/drag.d.ts
ADDED
package/dist/drag.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import {
|
|
2
|
+
clamp,
|
|
3
|
+
closest
|
|
4
|
+
} from "./shared/chunk-3THCBXDW.js";
|
|
5
|
+
|
|
6
|
+
// src/drag.ts
|
|
7
|
+
var drag = ({ threshold = 6 } = {}) => ({ root, track, listen, layout, toPage, scrollTo, grab, hold, on }) => {
|
|
8
|
+
let suppressUntil = 0;
|
|
9
|
+
let press = null;
|
|
10
|
+
const readPos = () => Math.abs(track.scrollLeft);
|
|
11
|
+
root.setAttribute("data-sc-drag", "");
|
|
12
|
+
listen(track, "dragstart", (event) => event.preventDefault());
|
|
13
|
+
listen(
|
|
14
|
+
track,
|
|
15
|
+
"click",
|
|
16
|
+
(event) => {
|
|
17
|
+
if (event.timeStamp > suppressUntil) return;
|
|
18
|
+
event.preventDefault();
|
|
19
|
+
event.stopPropagation();
|
|
20
|
+
},
|
|
21
|
+
{ capture: true }
|
|
22
|
+
);
|
|
23
|
+
on("settle", () => track.removeAttribute("data-sc-dragging"));
|
|
24
|
+
listen(track, "pointerdown", (event) => {
|
|
25
|
+
if (event.pointerType != "mouse" || event.button || !layout().state.overflow) return;
|
|
26
|
+
if (event.target.closest("input, textarea, select, label, [contenteditable], [data-sc-no-drag]")) return;
|
|
27
|
+
press = { id: event.pointerId, x: event.clientX, left: track.scrollLeft, from: readPos(), moved: false, trail: [[event.timeStamp, event.clientX]] };
|
|
28
|
+
});
|
|
29
|
+
listen(document, "pointermove", (event) => {
|
|
30
|
+
if (!press || event.pointerId != press.id) return;
|
|
31
|
+
if (!(event.buttons & 1)) return release(event);
|
|
32
|
+
const dx = event.clientX - press.x;
|
|
33
|
+
if (!press.moved) {
|
|
34
|
+
if (Math.abs(dx) < threshold) return;
|
|
35
|
+
press.moved = true;
|
|
36
|
+
grab();
|
|
37
|
+
hold(true);
|
|
38
|
+
track.setPointerCapture(press.id);
|
|
39
|
+
track.setAttribute("data-sc-dragging", "");
|
|
40
|
+
getSelection()?.removeAllRanges();
|
|
41
|
+
}
|
|
42
|
+
track.scrollLeft = press.left - dx;
|
|
43
|
+
press.trail.push([event.timeStamp, event.clientX]);
|
|
44
|
+
while (press.trail.length > 2 && event.timeStamp - press.trail[0][0] > 100) press.trail.shift();
|
|
45
|
+
});
|
|
46
|
+
const release = (event) => {
|
|
47
|
+
const done = press;
|
|
48
|
+
if (!done || event.pointerId != done.id) return;
|
|
49
|
+
press = null;
|
|
50
|
+
if (!done.moved) return;
|
|
51
|
+
hold(false);
|
|
52
|
+
suppressUntil = event.timeStamp + 100;
|
|
53
|
+
const { pages, max, rtl, snap } = layout();
|
|
54
|
+
const [t0, x0] = done.trail[0];
|
|
55
|
+
const velocity = event.timeStamp > t0 ? (event.clientX - x0) / (event.timeStamp - t0) : 0;
|
|
56
|
+
const here = readPos();
|
|
57
|
+
const forward = rtl ? velocity : -velocity;
|
|
58
|
+
if (snap == "none") return scrollTo(clamp(here + forward * 200, 0, max));
|
|
59
|
+
const positions = pages.map((page2) => page2.pos);
|
|
60
|
+
const start = closest(positions, done.from);
|
|
61
|
+
let page = closest(positions, here);
|
|
62
|
+
const moved = here - done.from;
|
|
63
|
+
if (page == start && (Math.abs(moved) > 40 || Math.abs(forward) > 0.3)) {
|
|
64
|
+
page = clamp(start + Math.sign(Math.abs(moved) > 40 ? moved : forward), 0, pages.length - 1);
|
|
65
|
+
}
|
|
66
|
+
toPage(page);
|
|
67
|
+
};
|
|
68
|
+
listen(document, "pointerup", release);
|
|
69
|
+
listen(document, "pointercancel", release);
|
|
70
|
+
return {
|
|
71
|
+
destroy() {
|
|
72
|
+
root.removeAttribute("data-sc-drag");
|
|
73
|
+
track.removeAttribute("data-sc-dragging");
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
};
|
|
77
|
+
export {
|
|
78
|
+
drag
|
|
79
|
+
};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { type Group, type Page } from './math.js';
|
|
2
|
+
export type { Align, Geometry, Group, Page } from './math.js';
|
|
3
|
+
export interface CarouselState {
|
|
4
|
+
/** The slide at the snap position, or the destination of a move in flight. */
|
|
5
|
+
index: number;
|
|
6
|
+
page: number;
|
|
7
|
+
pageCount: number;
|
|
8
|
+
isBeginning: boolean;
|
|
9
|
+
isEnd: boolean;
|
|
10
|
+
/** Whether the content overflows at all. Without overflow there are no controls. */
|
|
11
|
+
overflow: boolean;
|
|
12
|
+
}
|
|
13
|
+
export interface Labels {
|
|
14
|
+
/** Accessible name of a default dot. */
|
|
15
|
+
page: (n: number, count: number) => string;
|
|
16
|
+
/** Live-region text after a settled move, from the first and last visible slide (1-based). */
|
|
17
|
+
status: (first: number, last: number, count: number, slides: HTMLElement[]) => string;
|
|
18
|
+
}
|
|
19
|
+
export interface CarouselOptions {
|
|
20
|
+
/** Slides per step of next and previous: a number or 'page'. Default: --sc-group, then 1. */
|
|
21
|
+
group?: Group;
|
|
22
|
+
/** Initial slide. Default: the slide with [data-sc-initial], then 0. */
|
|
23
|
+
initial?: number;
|
|
24
|
+
/** Next at the end goes to the start and the reverse. true fades, 'scroll' scrolls back. */
|
|
25
|
+
rewind?: boolean | 'fade' | 'scroll';
|
|
26
|
+
labels?: Partial<Labels>;
|
|
27
|
+
/** Called after every settled move that changed the state, like the sc:change event. */
|
|
28
|
+
onChange?: (state: CarouselState) => void;
|
|
29
|
+
/** Default: [data-sc-prev], [data-sc-next], [data-sc-dots], [data-sc-status] inside the root. */
|
|
30
|
+
prev?: HTMLElement | null;
|
|
31
|
+
next?: HTMLElement | null;
|
|
32
|
+
dots?: HTMLElement | null;
|
|
33
|
+
status?: HTMLElement | null;
|
|
34
|
+
/** Opt-in behaviour, e.g. drag() and autoplay(). */
|
|
35
|
+
plugins?: Plugin[];
|
|
36
|
+
}
|
|
37
|
+
export interface MoveOptions {
|
|
38
|
+
/** Jump instead of scrolling smoothly. */
|
|
39
|
+
instant?: boolean;
|
|
40
|
+
}
|
|
41
|
+
export interface Carousel {
|
|
42
|
+
readonly root: HTMLElement;
|
|
43
|
+
readonly track: HTMLElement;
|
|
44
|
+
readonly slides: HTMLElement[];
|
|
45
|
+
readonly state: CarouselState;
|
|
46
|
+
readonly index: number;
|
|
47
|
+
readonly page: number;
|
|
48
|
+
readonly pageCount: number;
|
|
49
|
+
readonly isBeginning: boolean;
|
|
50
|
+
readonly isEnd: boolean;
|
|
51
|
+
next(): void;
|
|
52
|
+
prev(): void;
|
|
53
|
+
slideTo(index: number, options?: MoveOptions): void;
|
|
54
|
+
goToPage(page: number, options?: MoveOptions): void;
|
|
55
|
+
/** Measure again, e.g. after changing the direction or a custom property from script. */
|
|
56
|
+
update(): void;
|
|
57
|
+
destroy(): void;
|
|
58
|
+
/** Present when the autoplay plugin is attached. */
|
|
59
|
+
play?(): void;
|
|
60
|
+
pause?(): void;
|
|
61
|
+
}
|
|
62
|
+
export interface Layout {
|
|
63
|
+
pages: Page[];
|
|
64
|
+
max: number;
|
|
65
|
+
rtl: boolean;
|
|
66
|
+
snap: 'mandatory' | 'proximity' | 'none';
|
|
67
|
+
state: CarouselState;
|
|
68
|
+
}
|
|
69
|
+
/** What a plugin gets to work with. Listeners added through `listen` end with destroy(). */
|
|
70
|
+
export interface PluginContext {
|
|
71
|
+
root: HTMLElement;
|
|
72
|
+
track: HTMLElement;
|
|
73
|
+
listen(target: EventTarget | null | undefined, type: string, handler: (event: any) => void, options?: AddEventListenerOptions): void;
|
|
74
|
+
layout(): Layout;
|
|
75
|
+
toPage(page: number): void;
|
|
76
|
+
/** Scroll to a position, e.g. where a drag's momentum ends. */
|
|
77
|
+
scrollTo(pos: number): void;
|
|
78
|
+
/** Next or previous page; a quiet step is not announced in the live region. */
|
|
79
|
+
step(direction: 1 | -1, wrap: boolean, quiet?: boolean): void;
|
|
80
|
+
/** The user took hold of the track: a programmatic move in flight is abandoned. */
|
|
81
|
+
grab(): void;
|
|
82
|
+
/** While held, scrolling never counts as settled, e.g. during a drag. */
|
|
83
|
+
hold(on: boolean): void;
|
|
84
|
+
/** control: the user took over. settle: a move came to rest. measure: layout was read. */
|
|
85
|
+
on(event: 'control' | 'settle' | 'measure', handler: () => void): void;
|
|
86
|
+
}
|
|
87
|
+
export type Plugin = (context: PluginContext) => {
|
|
88
|
+
destroy?(): void;
|
|
89
|
+
play?(): void;
|
|
90
|
+
pause?(): void;
|
|
91
|
+
} | void;
|
|
92
|
+
/** The carousel attached to `root`, if any. */
|
|
93
|
+
export declare const getCarousel: (root: HTMLElement) => Carousel | undefined;
|
|
94
|
+
/** Attach to server-rendered markup. Attaching twice returns the same carousel. */
|
|
95
|
+
export declare function attach(root: HTMLElement, options?: CarouselOptions): Carousel;
|
package/dist/index.js
ADDED
package/dist/markup.d.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A value, or values by minimum container width in px, e.g. { 0: 2, 600: 3, 900: 4 }.
|
|
3
|
+
* Widths are container widths: the carousel root is the query container.
|
|
4
|
+
*/
|
|
5
|
+
export type Responsive<T> = T | Record<number, T>;
|
|
6
|
+
export interface LayoutProps {
|
|
7
|
+
/** Slides per view; fractions show part of the next slide. */
|
|
8
|
+
perView?: Responsive<number>;
|
|
9
|
+
/** A fixed slide size such as '18rem', or 'auto' for the content width. Wins over perView. */
|
|
10
|
+
slideSize?: Responsive<string>;
|
|
11
|
+
/** Numbers are px. */
|
|
12
|
+
gap?: Responsive<number | string>;
|
|
13
|
+
offsetBefore?: Responsive<number | string>;
|
|
14
|
+
offsetAfter?: Responsive<number | string>;
|
|
15
|
+
snap?: Responsive<'mandatory' | 'proximity' | 'none'>;
|
|
16
|
+
align?: Responsive<'start' | 'center'>;
|
|
17
|
+
/** Pad both ends so the first and last slide can reach the centre. Needs perView. */
|
|
18
|
+
centered?: boolean;
|
|
19
|
+
/** Slides per step of next and previous. */
|
|
20
|
+
group?: Responsive<number | 'page'>;
|
|
21
|
+
/** false hides arrows, dots and the play button, e.g. for free mode on small containers. */
|
|
22
|
+
controls?: Responsive<boolean>;
|
|
23
|
+
}
|
|
24
|
+
/** Custom properties for the root's style attribute: plain values and the 0 breakpoint. */
|
|
25
|
+
export declare function baseVars(props: LayoutProps): Record<string, string>;
|
|
26
|
+
/** The same as a CSS declaration string, for templating languages. */
|
|
27
|
+
export declare const baseStyle: (props: LayoutProps) => string;
|
|
28
|
+
/**
|
|
29
|
+
* Container-query rules for responsive values, scoped to [data-sc-id="id"]. Empty when nothing
|
|
30
|
+
* is responsive. Render it in a <style> element inside the root.
|
|
31
|
+
*/
|
|
32
|
+
export declare function responsiveCss(id: string, props: LayoutProps): string;
|
|
33
|
+
/**
|
|
34
|
+
* Inline script to render immediately after a carousel that opens on a later slide. It sets the
|
|
35
|
+
* start position before the first paint, so a deferred or module script finds it in place.
|
|
36
|
+
* Start alignment only. It expects the root as its previous sibling.
|
|
37
|
+
*/
|
|
38
|
+
export declare const PRE_POSITION: string;
|
|
39
|
+
/** Label templates such as 'Slide {n} of {count}' turned into label functions. */
|
|
40
|
+
export declare function templateLabels(templates?: {
|
|
41
|
+
page?: string;
|
|
42
|
+
status?: string;
|
|
43
|
+
}): {
|
|
44
|
+
page?: ((n: number, count: number) => string) | undefined;
|
|
45
|
+
status?: ((first: number, last: number, count: number) => string) | undefined;
|
|
46
|
+
};
|
package/dist/markup.js
ADDED
package/dist/math.d.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export type Align = 'start' | 'center' | 'end';
|
|
2
|
+
export type Group = number | 'page';
|
|
3
|
+
export interface Geometry {
|
|
4
|
+
/** Start of each slide in scroll-content coordinates, px. */
|
|
5
|
+
starts: number[];
|
|
6
|
+
/** Inline size of each slide, px. */
|
|
7
|
+
sizes: number[];
|
|
8
|
+
/** Width of the scrollport, px. */
|
|
9
|
+
port: number;
|
|
10
|
+
/** Scroll padding at the start and the end, px. */
|
|
11
|
+
padStart: number;
|
|
12
|
+
padEnd: number;
|
|
13
|
+
/** Largest scroll position, px. */
|
|
14
|
+
max: number;
|
|
15
|
+
align: Align;
|
|
16
|
+
}
|
|
17
|
+
export interface Page {
|
|
18
|
+
/** Scroll position of the page. */
|
|
19
|
+
pos: number;
|
|
20
|
+
/** Index of the first slide of the page. */
|
|
21
|
+
first: number;
|
|
22
|
+
}
|
|
23
|
+
/** Two positions closer than this, in px, are the same place. */
|
|
24
|
+
export declare const EPS = 2;
|
|
25
|
+
export declare const clamp: (value: number, min: number, max: number) => number;
|
|
26
|
+
/** Index of the value closest to `target`; the earliest one wins a tie. */
|
|
27
|
+
export declare const closest: (values: number[], target: number) => number;
|
|
28
|
+
/** The scroll position at which each slide is aligned, clamped to the scroll range. */
|
|
29
|
+
export declare const snapPositions: ({ starts, sizes, port, padStart, padEnd, max, align }: Geometry) => number[];
|
|
30
|
+
/**
|
|
31
|
+
* The first slide of every page. Pages are counted outwards from `anchor` (the initial slide),
|
|
32
|
+
* so the initial slide always starts a page and the first page may be shorter than the rest.
|
|
33
|
+
* `group` is a number of slides, or 'page' for as many whole slides as fit in the view.
|
|
34
|
+
*/
|
|
35
|
+
export declare const pageStarts: (geo: Geometry, snaps: number[], group: Group, anchor: number) => number[];
|
|
36
|
+
/**
|
|
37
|
+
* Pages at distinct positions, always ending at the end of the scroll range. A page's `first` is
|
|
38
|
+
* the earliest slide aligned at its position, which is also the index reported there: at the
|
|
39
|
+
* clamped end that is the first slide of the last full view, not the start of the last group.
|
|
40
|
+
*/
|
|
41
|
+
export declare const buildPages: (geo: Geometry, snaps: number[], group: Group, anchor: number) => Page[];
|
|
42
|
+
/** The page that contains slide `index`. */
|
|
43
|
+
export declare const pageOf: (pages: Page[], index: number) => number;
|
|
44
|
+
/** First and last slide that are at least half visible at `pos`, or [-1, -1]. */
|
|
45
|
+
export declare const visibleRange: (pos: number, { starts, sizes, port }: Geometry) => [number, number];
|
package/dist/preact.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export type { CarouselProps } from './adapter.js';
|
|
2
|
+
export type { Carousel as CarouselApi, CarouselOptions, CarouselState } from './index.js';
|
|
3
|
+
export declare const Carousel: (props: import("./adapter.ts").CarouselProps) => any, useCarousel: (options?: import("./index.ts").CarouselOptions) => {
|
|
4
|
+
ref: {
|
|
5
|
+
current: HTMLElement | null;
|
|
6
|
+
};
|
|
7
|
+
carousel: import("./index.ts").Carousel | null;
|
|
8
|
+
state: import("./index.ts").CarouselState | null;
|
|
9
|
+
};
|
package/dist/preact.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createAdapter
|
|
3
|
+
} from "./shared/chunk-5Q4YPVNK.js";
|
|
4
|
+
import "./shared/chunk-HPGJKTV5.js";
|
|
5
|
+
import "./shared/chunk-3THCBXDW.js";
|
|
6
|
+
import "./shared/chunk-I4ZMRKZV.js";
|
|
7
|
+
|
|
8
|
+
// src/preact.ts
|
|
9
|
+
import { Fragment, createElement, toChildArray } from "preact";
|
|
10
|
+
import { useEffect, useId, useLayoutEffect, useRef, useState } from "preact/hooks";
|
|
11
|
+
var { Carousel, useCarousel } = createAdapter({
|
|
12
|
+
createElement,
|
|
13
|
+
Fragment,
|
|
14
|
+
useState,
|
|
15
|
+
useRef,
|
|
16
|
+
useEffect,
|
|
17
|
+
useLayoutEffect,
|
|
18
|
+
useId,
|
|
19
|
+
toChildArray
|
|
20
|
+
});
|
|
21
|
+
export {
|
|
22
|
+
Carousel,
|
|
23
|
+
useCarousel
|
|
24
|
+
};
|
package/dist/react.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export type { CarouselProps } from './adapter.js';
|
|
2
|
+
export type { Carousel as CarouselApi, CarouselOptions, CarouselState } from './index.js';
|
|
3
|
+
export declare const Carousel: (props: import("./adapter.ts").CarouselProps) => any, useCarousel: (options?: import("./index.ts").CarouselOptions) => {
|
|
4
|
+
ref: {
|
|
5
|
+
current: HTMLElement | null;
|
|
6
|
+
};
|
|
7
|
+
carousel: import("./index.ts").Carousel | null;
|
|
8
|
+
state: import("./index.ts").CarouselState | null;
|
|
9
|
+
};
|
package/dist/react.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createAdapter
|
|
3
|
+
} from "./shared/chunk-5Q4YPVNK.js";
|
|
4
|
+
import "./shared/chunk-HPGJKTV5.js";
|
|
5
|
+
import "./shared/chunk-3THCBXDW.js";
|
|
6
|
+
import "./shared/chunk-I4ZMRKZV.js";
|
|
7
|
+
|
|
8
|
+
// src/react.ts
|
|
9
|
+
import { Children, Fragment, createElement, useEffect, useId, useLayoutEffect, useRef, useState } from "react";
|
|
10
|
+
var { Carousel, useCarousel } = createAdapter({
|
|
11
|
+
createElement,
|
|
12
|
+
Fragment,
|
|
13
|
+
useState,
|
|
14
|
+
useRef,
|
|
15
|
+
useEffect,
|
|
16
|
+
useLayoutEffect,
|
|
17
|
+
useId,
|
|
18
|
+
toChildArray: Children.toArray
|
|
19
|
+
});
|
|
20
|
+
export {
|
|
21
|
+
Carousel,
|
|
22
|
+
useCarousel
|
|
23
|
+
};
|