@nordwerk/scroll-carousel 0.1.3 → 0.1.5

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 CHANGED
@@ -1,10 +1,10 @@
1
1
  # scroll-carousel: guide for coding agents
2
2
 
3
- Two parts: how to build a carousel with this package in a project, and how to work on the package
4
- itself. Human documentation is in README.md; live examples with code are at
5
- https://www.nordwerk.studio/oss/scroll-carousel.
3
+ How to build a carousel with this package in a project. Human documentation is in README.md; live
4
+ examples with code are at https://www.nordwerk.studio/oss/scroll-carousel. Working on the package
5
+ itself: see AGENTS.md at the root of https://github.com/studio-nordwerk/oss-ui.
6
6
 
7
- ## Part 1: using the package
7
+ ## Using the package
8
8
 
9
9
  ### The model in one paragraph
10
10
 
@@ -45,7 +45,7 @@ JavaScript, never move slides with transforms, never clone slides.
45
45
  `aria-disabled:opacity-30`). Copyable example: the `#tailwind` section of the docs site.
46
46
  12. Next.js App Router: import `<Carousel>` from `@nordwerk/scroll-carousel/react` in a Server
47
47
  Component as is; the entry carries `'use client'`.
48
- 13. shadcn/ui projects: `npx shadcn@latest add studio-nordwerk/scroll-carousel/scroll-carousel`, or a
48
+ 13. shadcn/ui projects: `npx shadcn@latest add studio-nordwerk/oss-ui/scroll-carousel`, or a
49
49
  block (`product-row`, `brand-teasers`, `hero-autoplay`, `image-gallery`, `logo-belt`). Compose
50
50
  `ScrollCarousel` > `ScrollCarouselContent` > `ScrollCarouselItem`, with `ScrollCarouselPrevious`,
51
51
  `ScrollCarouselNext`, `ScrollCarouselDots` and `ScrollCarouselPlay` inside `ScrollCarousel`;
@@ -77,64 +77,3 @@ JavaScript, never move slides with transforms, never clone slides.
77
77
  mark the initial slide with `data-sc-initial` yourself.
78
78
  - Anything else on the server: `baseStyle(props)`, `responsiveCss(id, props)` and `PRE_POSITION`
79
79
  from `@nordwerk/scroll-carousel/markup`.
80
-
81
- ## Part 2: working on this repository
82
-
83
- ### Layout
84
-
85
- - `src/math.ts`: pure positioning maths (snap positions, pages, visible range). Unit tested.
86
- - `src/index.ts`: the controller, `attach()`. Keep it free of framework code.
87
- - `src/drag.ts`, `src/autoplay.ts`: plugins against the `PluginContext` in index.ts.
88
- - `src/markup.ts`: server helpers shared by the adapters.
89
- - `src/adapter.ts`: the React and Preact adapter, written once against a small `Framework`
90
- interface; `src/react.ts` and `src/preact.ts` only bind it.
91
- - `src/astro/Carousel.astro`: shipped as source.
92
- - `src/carousel.css`: all layout and the default controls. The build also writes
93
- `carousel.layer.css`, the same rules inside Tailwind's components layer.
94
- - `registry.json` and `registry/`: the shadcn registry, read by the shadcn CLI straight from this
95
- public repository (`studio-nordwerk/scroll-carousel/<item>`). `registry/ui` is the component,
96
- `registry/blocks` the blocks, `registry/shims` stand-ins that only exist for the type check.
97
- `node scripts/shadcn-smoke.mjs` installs everything into a fresh shadcn project, builds and
98
- renders it; CI runs it on every push.
99
- - `site/`: the documentation site (`generate.mjs` writes `_site/`), including the wireframe
100
- patterns in `wireframes.mjs`. Content there is fictional. CI publishes it to GitHub Pages;
101
- www.nordwerk.studio/oss/scroll-carousel serves the same files through a proxy that rewrites
102
- `<base href>`, so every link in the site must stay relative to `<base>`. The proxy also puts the
103
- studio site's own header and footer in place of `<!--nw:header-->` (first child of `.sheet`)
104
- and `<!--nw:footer-->` (right after `.sheet`); keep both, keep `<html lang="en">` and `<body>`
105
- free of other attributes, keep `data-hero` on the introduction, and never style the studio's
106
- class names (band, pill, brand, brand-mark, links, link, cta, foot, grid, brand-col, tag, label,
107
- base) or set `--wrap` in the site CSS. Locally and on GitHub Pages the site has no header or
108
- footer.
109
- - `test/unit/`: node's test runner on the TypeScript sources.
110
- - `test/e2e/`: Playwright against `_site/` and the adapter fixtures from `scripts/fixtures.mjs`.
111
-
112
- ### Commands
113
-
114
- ```sh
115
- pnpm install
116
- pnpm typecheck
117
- pnpm test # unit tests, no build needed
118
- pnpm build # dist/: esbuild modules plus tsc declarations
119
- pnpm size # fails when an entry exceeds its gzip budget
120
- pnpm site # build, then _site/
121
- node scripts/fixtures.mjs # React, Preact and Astro fixtures into _site/fixtures/
122
- pnpm test:e2e # Chromium, WebKit and Firefox
123
- pnpm check # all of the above in order
124
- ```
125
-
126
- Visual baselines exist per platform in `test/e2e/__screenshots__/<platform>`; update them with
127
- `UPDATE_VISUAL=1 pnpm test:e2e --project=chromium visual --update-snapshots=all`.
128
-
129
- ### Conventions
130
-
131
- - No runtime dependencies. Anything a consumer does not use must tree-shake away; new optional
132
- behaviour becomes a plugin.
133
- - Size budgets live in `scripts/size.mjs`. Raising one needs a reason in the commit message.
134
- - Layout stays in CSS. The script reads layout (custom properties, computed styles, geometry)
135
- and never writes it.
136
- - Every must-have behaviour has a browser test that passes in all three engines. Fix flaky tests
137
- instead of retrying them.
138
- - Descriptive names, English in code, comments and docs, no project code names.
139
- - Examples, fixtures and docs use fictional content. Never name or describe a client, its code,
140
- its paths or its numbers in this repository, including commit messages.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.5 (2026-09-22)
4
+
5
+ - The repository is now [studio-nordwerk/oss-ui](https://github.com/studio-nordwerk/oss-ui), with
6
+ the package in `packages/scroll-carousel`. shadcn items install from
7
+ `studio-nordwerk/oss-ui/<item>`.
8
+ - Built with Vite+ (`vp pack`, on tsdown) instead of esbuild and tsc; the output and its sizes
9
+ are unchanged.
10
+ - React and Preact adapters, and the shadcn component: the latest options are kept in an effect
11
+ instead of being written to a ref during render, as React's rules require.
12
+
13
+ ## 0.1.4 (2026-09-22)
14
+
15
+ - Types resolve in TypeScript projects still on `moduleResolution: "node"`, which ignores
16
+ `exports`: `main`, `types` and `typesVersions` point at the same files.
17
+ - No `engines` field any more: it asked consumers for Node 22 although the package runs in the
18
+ browser.
19
+ - Released from GitHub Actions through npm trusted publishing, with provenance.
20
+
3
21
  ## 0.1.3 (2026-09-22)
4
22
 
5
23
  - `carousel.layer.css`: the stylesheet inside Tailwind's components layer, with Tailwind's layer
package/README.md CHANGED
@@ -133,8 +133,8 @@ A component with the structure of shadcn's Carousel, on native scrolling, and fi
133
133
  repository:
134
134
 
135
135
  ```sh
136
- npx shadcn@latest add studio-nordwerk/scroll-carousel/scroll-carousel
137
- npx shadcn@latest add studio-nordwerk/scroll-carousel/product-row
136
+ npx shadcn@latest add studio-nordwerk/oss-ui/scroll-carousel
137
+ npx shadcn@latest add studio-nordwerk/oss-ui/product-row
138
138
  ```
139
139
 
140
140
  `ScrollCarousel`, `ScrollCarouselContent`, `ScrollCarouselItem`, `ScrollCarouselPrevious` and
@@ -259,14 +259,10 @@ not supported (true infinite loop, vertical, effects, virtual slides, synced thu
259
259
 
260
260
  ## Development
261
261
 
262
- ```sh
263
- pnpm install
264
- pnpm check # typecheck, unit tests, build, size budget, site, fixtures, browser tests
265
- pnpm serve # the site on http://localhost:4173 after pnpm site
266
- ```
267
-
268
- See [AGENTS.md](AGENTS.md) for the repository layout and conventions, and
269
- [docs/testing.md](docs/testing.md) for what is tested automatically and by hand.
262
+ The package lives in [studio-nordwerk/oss-ui](https://github.com/studio-nordwerk/oss-ui), together
263
+ with the other open-source packages of Studio Nordwerk. Commands, layout and conventions are in
264
+ the repository's README and AGENTS.md; [docs/testing.md](docs/testing.md) says what is tested
265
+ automatically and by hand.
270
266
 
271
267
  ## Licence
272
268
 
@@ -1,14 +1,16 @@
1
- import type { Plugin } from './index.js';
1
+ import { s as Plugin } from "./shared/index-D1Np3wzP.js";
2
+ //#region src/autoplay.d.ts
2
3
  export interface AutoplayOptions {
3
- /** Milliseconds per page. Default 5000. */
4
- delay?: number;
5
- /** Start running on attach. Default true, except with reduced motion. */
6
- start?: boolean;
7
- /** Default: [data-sc-play] inside the root. */
8
- button?: HTMLElement | null;
9
- labels?: {
10
- play?: string;
11
- pause?: string;
12
- };
4
+ /** Milliseconds per page. Default 5000. */
5
+ delay?: number;
6
+ /** Start running on attach. Default true, except with reduced motion. */
7
+ start?: boolean;
8
+ /** Default: [data-sc-play] inside the root. */
9
+ button?: HTMLElement | null;
10
+ labels?: {
11
+ play?: string;
12
+ pause?: string;
13
+ };
13
14
  }
14
15
  export declare const autoplay: ({ delay, start, button, labels }?: AutoplayOptions) => Plugin;
16
+ //#endregion
package/dist/autoplay.js CHANGED
@@ -1,67 +1,66 @@
1
- // src/autoplay.ts
2
- var autoplay = ({ delay = 5e3, start = true, button, labels = {} } = {}) => ({ root, track, listen, layout, step, on }) => {
3
- const play = button || root.querySelector("[data-sc-play]");
4
- const holds = /* @__PURE__ */ new Set();
5
- let playing = start && !matchMedia("(prefers-reduced-motion: reduce)").matches;
6
- let timer = 0;
7
- let left = delay;
8
- let since = 0;
9
- function sync() {
10
- const running = playing && !holds.size && layout().pages.length > 1;
11
- root.toggleAttribute("data-sc-playing", playing);
12
- root.toggleAttribute("data-sc-paused", playing && !running);
13
- play?.setAttribute("aria-label", playing ? labels.pause || "Stop automatic scrolling" : labels.play || "Start automatic scrolling");
14
- if (running && !timer) {
15
- since = performance.now();
16
- timer = setTimeout(() => {
17
- timer = 0;
18
- left = delay;
19
- step(1, true, true);
20
- sync();
21
- }, left);
22
- } else if (!running && timer) {
23
- clearTimeout(timer);
24
- timer = 0;
25
- left = Math.max(0, left - (performance.now() - since));
26
- }
27
- }
28
- function set(on2) {
29
- playing = on2;
30
- left = delay;
31
- clearTimeout(timer);
32
- timer = 0;
33
- sync();
34
- }
35
- const hold = (reason, on2) => {
36
- holds[on2 ? "add" : "delete"](reason);
37
- sync();
38
- };
39
- if (track.matches(":hover")) holds.add("hover");
40
- if (document.hidden) holds.add("hidden");
41
- if (root.contains(document.activeElement) && !play?.contains(document.activeElement)) playing = false;
42
- root.style.setProperty("--sc-autoplay-delay", `${delay}ms`);
43
- on("control", () => playing && set(false));
44
- on("measure", sync);
45
- listen(play, "click", () => set(!playing));
46
- listen(track, "pointerenter", (event) => event.pointerType == "mouse" && hold("hover", true));
47
- listen(track, "pointerleave", (event) => event.pointerType == "mouse" && hold("hover", false));
48
- listen(root, "focusin", (event) => !play?.contains(event.target) && playing && set(false));
49
- listen(document, "visibilitychange", () => hold("hidden", document.hidden));
50
- const visibility = new IntersectionObserver(([entry]) => hold("offscreen", !entry.isIntersecting));
51
- visibility.observe(root);
52
- sync();
53
- return {
54
- play: () => set(true),
55
- pause: () => set(false),
56
- destroy() {
57
- clearTimeout(timer);
58
- visibility.disconnect();
59
- root.removeAttribute("data-sc-playing");
60
- root.removeAttribute("data-sc-paused");
61
- root.style.removeProperty("--sc-autoplay-delay");
62
- }
63
- };
64
- };
65
- export {
66
- autoplay
1
+ //#region src/autoplay.ts
2
+ const autoplay = ({ delay = 5e3, start = true, button, labels = {} } = {}) => ({ root, track, listen, layout, step, on }) => {
3
+ const play = button || root.querySelector("[data-sc-play]");
4
+ const holds = /* @__PURE__ */ new Set();
5
+ let playing = start && !matchMedia("(prefers-reduced-motion: reduce)").matches;
6
+ let timer = 0;
7
+ let left = delay;
8
+ let since = 0;
9
+ function sync() {
10
+ const running = playing && !holds.size && layout().pages.length > 1;
11
+ root.toggleAttribute("data-sc-playing", playing);
12
+ root.toggleAttribute("data-sc-paused", playing && !running);
13
+ play?.setAttribute("aria-label", playing ? labels.pause || "Stop automatic scrolling" : labels.play || "Start automatic scrolling");
14
+ if (running && !timer) {
15
+ since = performance.now();
16
+ timer = setTimeout(() => {
17
+ timer = 0;
18
+ left = delay;
19
+ step(1, true, true);
20
+ sync();
21
+ }, left);
22
+ } else if (!running && timer) {
23
+ clearTimeout(timer);
24
+ timer = 0;
25
+ left = Math.max(0, left - (performance.now() - since));
26
+ }
27
+ }
28
+ function set(on) {
29
+ playing = on;
30
+ left = delay;
31
+ clearTimeout(timer);
32
+ timer = 0;
33
+ sync();
34
+ }
35
+ const hold = (reason, on) => {
36
+ holds[on ? "add" : "delete"](reason);
37
+ sync();
38
+ };
39
+ if (track.matches(":hover")) holds.add("hover");
40
+ if (document.hidden) holds.add("hidden");
41
+ if (root.contains(document.activeElement) && !play?.contains(document.activeElement)) playing = false;
42
+ root.style.setProperty("--sc-autoplay-delay", `${delay}ms`);
43
+ on("control", () => playing && set(false));
44
+ on("measure", sync);
45
+ listen(play, "click", () => set(!playing));
46
+ listen(track, "pointerenter", (event) => event.pointerType == "mouse" && hold("hover", true));
47
+ listen(track, "pointerleave", (event) => event.pointerType == "mouse" && hold("hover", false));
48
+ listen(root, "focusin", (event) => !play?.contains(event.target) && playing && set(false));
49
+ listen(document, "visibilitychange", () => hold("hidden", document.hidden));
50
+ const visibility = new IntersectionObserver(([entry]) => hold("offscreen", !entry.isIntersecting));
51
+ visibility.observe(root);
52
+ sync();
53
+ return {
54
+ play: () => set(true),
55
+ pause: () => set(false),
56
+ destroy() {
57
+ clearTimeout(timer);
58
+ visibility.disconnect();
59
+ root.removeAttribute("data-sc-playing");
60
+ root.removeAttribute("data-sc-paused");
61
+ root.style.removeProperty("--sc-autoplay-delay");
62
+ }
63
+ };
67
64
  };
65
+ //#endregion
66
+ export { autoplay };
package/dist/carousel.css CHANGED
@@ -34,8 +34,7 @@
34
34
  --_n: min(var(--sc-per-view, 1), var(--sc-count, 1000));
35
35
  --_gap: var(--sc-gap, 1rem);
36
36
  --_size: calc(
37
- (100cqi - var(--sc-offset-before, 0px) - var(--sc-offset-after, 0px) - (var(--_n) - 1) * var(--_gap)) /
38
- var(--_n)
37
+ (100cqi - var(--sc-offset-before, 0px) - var(--sc-offset-after, 0px) - (var(--_n) - 1) * var(--_gap)) / var(--_n)
39
38
  );
40
39
  --_edge: calc((100cqi - var(--_size)) / 2 * var(--sc-centered, 0));
41
40
  /* Also the containing block for anything absolutely positioned inside the slides, which the
@@ -194,7 +193,9 @@
194
193
  border-radius: var(--sc-dot-radius, 1rem);
195
194
  background: currentColor;
196
195
  opacity: var(--sc-dot-idle, 0.35);
197
- transition: inline-size 0.2s ease, opacity 0.2s ease;
196
+ transition:
197
+ inline-size 0.2s ease,
198
+ opacity 0.2s ease;
198
199
  }
199
200
 
200
201
  .sc-dot[aria-current]::before {
@@ -37,8 +37,7 @@
37
37
  --_n: min(var(--sc-per-view, 1), var(--sc-count, 1000));
38
38
  --_gap: var(--sc-gap, 1rem);
39
39
  --_size: calc(
40
- (100cqi - var(--sc-offset-before, 0px) - var(--sc-offset-after, 0px) - (var(--_n) - 1) * var(--_gap)) /
41
- var(--_n)
40
+ (100cqi - var(--sc-offset-before, 0px) - var(--sc-offset-after, 0px) - (var(--_n) - 1) * var(--_gap)) / var(--_n)
42
41
  );
43
42
  --_edge: calc((100cqi - var(--_size)) / 2 * var(--sc-centered, 0));
44
43
  /* Also the containing block for anything absolutely positioned inside the slides, which the
@@ -197,7 +196,9 @@
197
196
  border-radius: var(--sc-dot-radius, 1rem);
198
197
  background: currentColor;
199
198
  opacity: var(--sc-dot-idle, 0.35);
200
- transition: inline-size 0.2s ease, opacity 0.2s ease;
199
+ transition:
200
+ inline-size 0.2s ease,
201
+ opacity 0.2s ease;
201
202
  }
202
203
 
203
204
  .sc-dot[aria-current]::before {
package/dist/drag.d.ts CHANGED
@@ -1,6 +1,8 @@
1
- import type { Plugin } from './index.js';
1
+ import { s as Plugin } from "./shared/index-D1Np3wzP.js";
2
+ //#region src/drag.d.ts
2
3
  export interface DragOptions {
3
- /** Pixels the pointer must travel before a press becomes a drag. Default 6. */
4
- threshold?: number;
4
+ /** Pixels the pointer must travel before a press becomes a drag. Default 6. */
5
+ threshold?: number;
5
6
  }
6
7
  export declare const drag: ({ threshold }?: DragOptions) => Plugin;
8
+ //#endregion
package/dist/drag.js CHANGED
@@ -1,79 +1,72 @@
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
1
+ import { n as clamp, r as closest } from "./shared/math-DhvRXjtu.js";
2
+ //#region src/drag.ts
3
+ const drag = ({ threshold = 6 } = {}) => ({ root, track, listen, layout, toPage, scrollTo, grab, hold, on }) => {
4
+ let suppressUntil = 0;
5
+ let press = null;
6
+ const readPos = () => Math.abs(track.scrollLeft);
7
+ root.setAttribute("data-sc-drag", "");
8
+ listen(track, "dragstart", (event) => event.preventDefault());
9
+ listen(track, "click", (event) => {
10
+ if (event.timeStamp > suppressUntil) return;
11
+ event.preventDefault();
12
+ event.stopPropagation();
13
+ }, { capture: true });
14
+ on("settle", () => track.removeAttribute("data-sc-dragging"));
15
+ listen(track, "pointerdown", (event) => {
16
+ if (event.pointerType != "mouse" || event.button || !layout().state.overflow) return;
17
+ if (event.target.closest("input, textarea, select, label, [contenteditable], [data-sc-no-drag]")) return;
18
+ press = {
19
+ id: event.pointerId,
20
+ x: event.clientX,
21
+ left: track.scrollLeft,
22
+ from: readPos(),
23
+ moved: false,
24
+ trail: [[event.timeStamp, event.clientX]]
25
+ };
26
+ });
27
+ listen(document, "pointermove", (event) => {
28
+ if (!press || event.pointerId != press.id) return;
29
+ if (!(event.buttons & 1)) return release(event);
30
+ const dx = event.clientX - press.x;
31
+ if (!press.moved) {
32
+ if (Math.abs(dx) < threshold) return;
33
+ press.moved = true;
34
+ grab();
35
+ hold(true);
36
+ track.setPointerCapture(press.id);
37
+ track.setAttribute("data-sc-dragging", "");
38
+ getSelection()?.removeAllRanges();
39
+ }
40
+ track.scrollLeft = press.left - dx;
41
+ press.trail.push([event.timeStamp, event.clientX]);
42
+ while (press.trail.length > 2 && event.timeStamp - press.trail[0][0] > 100) press.trail.shift();
43
+ });
44
+ const release = (event) => {
45
+ const done = press;
46
+ if (!done || event.pointerId != done.id) return;
47
+ press = null;
48
+ if (!done.moved) return;
49
+ hold(false);
50
+ suppressUntil = event.timeStamp + 100;
51
+ const { pages, max, rtl, snap } = layout();
52
+ const [t0, x0] = done.trail[0];
53
+ const velocity = event.timeStamp > t0 ? (event.clientX - x0) / (event.timeStamp - t0) : 0;
54
+ const here = readPos();
55
+ const forward = rtl ? velocity : -velocity;
56
+ if (snap == "none") return scrollTo(clamp(here + forward * 200, 0, max));
57
+ const positions = pages.map((page) => page.pos);
58
+ const start = closest(positions, done.from);
59
+ let page = closest(positions, here);
60
+ const moved = here - done.from;
61
+ if (page == start && (Math.abs(moved) > 40 || Math.abs(forward) > .3)) page = clamp(start + Math.sign(Math.abs(moved) > 40 ? moved : forward), 0, pages.length - 1);
62
+ toPage(page);
63
+ };
64
+ listen(document, "pointerup", release);
65
+ listen(document, "pointercancel", release);
66
+ return { destroy() {
67
+ root.removeAttribute("data-sc-drag");
68
+ track.removeAttribute("data-sc-dragging");
69
+ } };
79
70
  };
71
+ //#endregion
72
+ export { drag };
package/dist/index.d.ts CHANGED
@@ -1,95 +1,2 @@
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;
1
+ import { a as Layout, c as PluginContext, d as Align, f as Geometry, i as Labels, l as attach, m as Page, n as CarouselOptions, o as MoveOptions, p as Group, r as CarouselState, s as Plugin, t as Carousel, u as getCarousel } from "./shared/index-D1Np3wzP.js";
2
+ export { type Align, Carousel, CarouselOptions, CarouselState, type Geometry, type Group, Labels, Layout, MoveOptions, type Page, Plugin, PluginContext, attach, getCarousel };