react-best-carroussel 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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Bruno Jimenez Postali
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,72 @@
1
+ # React Image Carousel
2
+
3
+ React image carousel library with a simple API, accessibility support and npm-ready packaging.
4
+
5
+ ## Features
6
+
7
+ - keyboard and screen-reader accessibility
8
+ - autoplay with pause on hover and focus
9
+ - optional loop mode
10
+ - arrows, indicators and thumbnail navigation
11
+ - swipe support for touch and pointer
12
+ - controlled and uncontrolled usage
13
+ - automated tests with coverage and CI
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ npm install react-best-carroussel react react-dom
19
+ ```
20
+
21
+ ## Usage
22
+
23
+ ```tsx
24
+ import { ImageCarousel } from "react-best-carroussel";
25
+
26
+ const images = [
27
+ { src: "/images/1.jpg", alt: "Landscape 1", caption: "First image" },
28
+ { src: "/images/2.jpg", alt: "Landscape 2", caption: "Second image" },
29
+ { src: "/images/3.jpg", alt: "Landscape 3", caption: "Third image" }
30
+ ];
31
+
32
+ export function Gallery() {
33
+ return <ImageCarousel images={images} autoplay showThumbnails aspectRatio="4 / 3" />;
34
+ }
35
+ ```
36
+
37
+ ## Main props
38
+
39
+ - `images`: `{ src, alt, caption?, thumbnailSrc? }[]`
40
+ - `initialIndex`, `index`, `onIndexChange`, `onSlideChange`
41
+ - `loop`, `autoplay`, `autoplayDelay`
42
+ - `pauseOnHover`, `pauseOnFocus`
43
+ - `showArrows`, `showIndicators`, `showThumbnails`
44
+ - `keyboardNavigation`, `swipeThreshold`
45
+ - `aspectRatio`, `objectFit`, `labels`
46
+
47
+ ## Quality
48
+
49
+ ```bash
50
+ npm install --include=dev
51
+ npm run check
52
+ ```
53
+
54
+ `npm run check` runs type checking, tests with coverage and the distribution build. The workflow in [`.github/workflows/ci.yml`](/d:/Study/react-best-carroussel/.github/workflows/ci.yml) runs the same validation on push and pull request.
55
+
56
+ ## Release Automation
57
+
58
+ The workflow in [`.github/workflows/release.yml`](/d:/Study/react-best-carroussel/.github/workflows/release.yml) publishes the package to npm with Trusted Publishing when you push a tag matching `v*.*.*`.
59
+
60
+ Configure Trusted Publishing in npm for this GitHub repository using these values:
61
+
62
+ - Source repository owner: `brujim`
63
+ - Source repository name: `react-best-carroussel`
64
+ - Workflow file: `.github/workflows/release.yml`
65
+ - Environment: leave empty unless you later decide to use a protected GitHub environment
66
+
67
+ After the trusted publisher is configured on npm, you can release with:
68
+
69
+ ```bash
70
+ git tag -a v0.1.1 -m "Release v0.1.1"
71
+ git push origin v0.1.1
72
+ ```
package/dist/index.cjs ADDED
@@ -0,0 +1,365 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ ImageCarousel: () => ImageCarousel
24
+ });
25
+ module.exports = __toCommonJS(index_exports);
26
+
27
+ // src/ImageCarousel.tsx
28
+ var import_react = require("react");
29
+
30
+ // src/injectStyles.ts
31
+ var STYLE_ID = "image-carousel-library-styles";
32
+ var styles = `
33
+ .icl-root{position:relative;width:100%;display:grid;gap:1rem;color:#101828;font-family:system-ui,sans-serif}
34
+ .icl-viewport{position:relative;overflow:hidden;border-radius:1rem;background:linear-gradient(180deg,#f8fafc 0%,#e2e8f0 100%)}
35
+ .icl-track{display:flex;touch-action:pan-y;will-change:transform}
36
+ .icl-slide{min-width:100%;position:relative;user-select:none}
37
+ .icl-figure{margin:0;position:relative;aspect-ratio:var(--icl-aspect-ratio,16/9);overflow:hidden}
38
+ .icl-image{width:100%;height:100%;display:block;object-fit:var(--icl-object-fit,cover);background:#cbd5e1}
39
+ .icl-caption{position:absolute;left:1rem;right:1rem;bottom:1rem;padding:.75rem 1rem;border-radius:.75rem;background:rgb(15 23 42 / .7);color:#f8fafc;font-size:.95rem;backdrop-filter:blur(8px)}
40
+ .icl-nav{position:absolute;top:50%;transform:translateY(-50%);display:inline-flex;align-items:center;justify-content:center;width:2.75rem;height:2.75rem;border:0;border-radius:999px;background:rgb(15 23 42 / .8);color:#fff;cursor:pointer;z-index:1}
41
+ .icl-nav:hover{background:rgb(15 23 42 / .92)}
42
+ .icl-nav:focus-visible,.icl-dot:focus-visible,.icl-thumb:focus-visible{outline:3px solid #38bdf8;outline-offset:2px}
43
+ .icl-nav[disabled]{opacity:.45;cursor:not-allowed}
44
+ .icl-nav-prev{left:.75rem}
45
+ .icl-nav-next{right:.75rem}
46
+ .icl-footer{display:grid;gap:.75rem}
47
+ .icl-dots{display:flex;justify-content:center;gap:.5rem;flex-wrap:wrap}
48
+ .icl-dot{width:.8rem;height:.8rem;border:0;border-radius:999px;background:#cbd5e1;cursor:pointer;padding:0}
49
+ .icl-dot[aria-current="true"]{background:#0f172a;transform:scale(1.15)}
50
+ .icl-thumbs{display:grid;grid-template-columns:repeat(auto-fit,minmax(4.5rem,1fr));gap:.5rem}
51
+ .icl-thumb{border:2px solid transparent;border-radius:.8rem;padding:0;overflow:hidden;cursor:pointer;background:#e2e8f0}
52
+ .icl-thumb[aria-current="true"]{border-color:#0f172a}
53
+ .icl-thumb img{display:block;width:100%;aspect-ratio:1/1;object-fit:cover}
54
+ .icl-sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}
55
+ @media (max-width: 640px){
56
+ .icl-caption{left:.75rem;right:.75rem;bottom:.75rem;font-size:.85rem}
57
+ .icl-nav{width:2.4rem;height:2.4rem}
58
+ }
59
+ `;
60
+ function ensureCarouselStyles() {
61
+ if (typeof document === "undefined") {
62
+ return;
63
+ }
64
+ if (document.getElementById(STYLE_ID)) {
65
+ return;
66
+ }
67
+ const style = document.createElement("style");
68
+ style.id = STYLE_ID;
69
+ style.textContent = styles;
70
+ document.head.appendChild(style);
71
+ }
72
+
73
+ // src/ImageCarousel.tsx
74
+ var import_jsx_runtime = require("react/jsx-runtime");
75
+ var defaultLabels = {
76
+ region: "Image carousel",
77
+ previous: "Previous slide",
78
+ next: "Next slide",
79
+ goToSlide: (index) => `Go to slide ${index + 1}`,
80
+ slide: (index, total) => `Slide ${index + 1} of ${total}`
81
+ };
82
+ function clampIndex(value, total) {
83
+ if (total === 0) {
84
+ return 0;
85
+ }
86
+ return Math.max(0, Math.min(value, total - 1));
87
+ }
88
+ function resolveIndex(value, total, loop) {
89
+ if (total === 0) {
90
+ return 0;
91
+ }
92
+ if (loop) {
93
+ return (value + total) % total;
94
+ }
95
+ return clampIndex(value, total);
96
+ }
97
+ function ImageCarousel({
98
+ images,
99
+ className,
100
+ style,
101
+ initialIndex = 0,
102
+ index,
103
+ loop = true,
104
+ autoplay = false,
105
+ autoplayDelay = 4e3,
106
+ pauseOnHover = true,
107
+ pauseOnFocus = true,
108
+ keyboardNavigation = true,
109
+ showArrows = true,
110
+ showIndicators = true,
111
+ showThumbnails = false,
112
+ swipeThreshold = 40,
113
+ aspectRatio = "16 / 9",
114
+ objectFit = "cover",
115
+ transitionDuration = 350,
116
+ onIndexChange,
117
+ onSlideChange,
118
+ labels
119
+ }) {
120
+ const total = images.length;
121
+ const mergedLabels = { ...defaultLabels, ...labels };
122
+ const rootId = (0, import_react.useId)();
123
+ const [internalIndex, setInternalIndex] = (0, import_react.useState)(() => clampIndex(initialIndex, total));
124
+ const [isHovered, setIsHovered] = (0, import_react.useState)(false);
125
+ const [isFocused, setIsFocused] = (0, import_react.useState)(false);
126
+ const [dragOffset, setDragOffset] = (0, import_react.useState)(0);
127
+ const dragStartX = (0, import_react.useRef)(null);
128
+ const dragOffsetRef = (0, import_react.useRef)(0);
129
+ const isControlled = index !== void 0;
130
+ const activeIndex = resolveIndex(isControlled ? index : internalIndex, total, false);
131
+ (0, import_react.useEffect)(() => {
132
+ ensureCarouselStyles();
133
+ }, []);
134
+ (0, import_react.useEffect)(() => {
135
+ setInternalIndex((current) => clampIndex(current, total));
136
+ }, [total]);
137
+ (0, import_react.useEffect)(() => {
138
+ if (total === 0) {
139
+ return;
140
+ }
141
+ onSlideChange?.(activeIndex);
142
+ }, [activeIndex, onSlideChange, total]);
143
+ const canGoPrevious = loop || activeIndex > 0;
144
+ const canGoNext = loop || activeIndex < total - 1;
145
+ const setIndex = (next) => {
146
+ const resolved = resolveIndex(next, total, loop);
147
+ onIndexChange?.(resolved);
148
+ if (!isControlled) {
149
+ setInternalIndex(resolved);
150
+ }
151
+ };
152
+ const goToPrevious = () => setIndex(activeIndex - 1);
153
+ const goToNext = () => setIndex(activeIndex + 1);
154
+ const goTo = (next) => setIndex(next);
155
+ (0, import_react.useEffect)(() => {
156
+ if (!autoplay || total <= 1) {
157
+ return;
158
+ }
159
+ if (pauseOnHover && isHovered || pauseOnFocus && isFocused) {
160
+ return;
161
+ }
162
+ const timer = window.setInterval(() => {
163
+ setIndex(activeIndex + 1);
164
+ }, autoplayDelay);
165
+ return () => window.clearInterval(timer);
166
+ }, [
167
+ activeIndex,
168
+ autoplay,
169
+ autoplayDelay,
170
+ isFocused,
171
+ isHovered,
172
+ pauseOnFocus,
173
+ pauseOnHover,
174
+ total
175
+ ]);
176
+ const handleKeyDown = (event) => {
177
+ if (!keyboardNavigation || total <= 1) {
178
+ return;
179
+ }
180
+ if (event.key === "ArrowLeft") {
181
+ event.preventDefault();
182
+ goToPrevious();
183
+ }
184
+ if (event.key === "ArrowRight") {
185
+ event.preventDefault();
186
+ goToNext();
187
+ }
188
+ if (event.key === "Home") {
189
+ event.preventDefault();
190
+ goTo(0);
191
+ }
192
+ if (event.key === "End") {
193
+ event.preventDefault();
194
+ goTo(total - 1);
195
+ }
196
+ };
197
+ const handlePointerDown = (event) => {
198
+ if (total <= 1) {
199
+ return;
200
+ }
201
+ dragStartX.current = event.clientX;
202
+ dragOffsetRef.current = 0;
203
+ setDragOffset(0);
204
+ };
205
+ const handlePointerMove = (event) => {
206
+ if (dragStartX.current === null) {
207
+ return;
208
+ }
209
+ const nextOffset = event.clientX - dragStartX.current;
210
+ dragOffsetRef.current = nextOffset;
211
+ setDragOffset(nextOffset);
212
+ };
213
+ const finishDrag = () => {
214
+ if (dragStartX.current === null) {
215
+ return;
216
+ }
217
+ if (dragOffsetRef.current >= swipeThreshold) {
218
+ goToPrevious();
219
+ } else if (dragOffsetRef.current <= -swipeThreshold) {
220
+ goToNext();
221
+ }
222
+ dragStartX.current = null;
223
+ dragOffsetRef.current = 0;
224
+ setDragOffset(0);
225
+ };
226
+ const handleTouchStart = (event) => {
227
+ if (total <= 1) {
228
+ return;
229
+ }
230
+ dragStartX.current = event.touches[0]?.clientX ?? null;
231
+ dragOffsetRef.current = 0;
232
+ setDragOffset(0);
233
+ };
234
+ const handleTouchMove = (event) => {
235
+ if (dragStartX.current === null) {
236
+ return;
237
+ }
238
+ const nextOffset = (event.touches[0]?.clientX ?? dragStartX.current) - dragStartX.current;
239
+ dragOffsetRef.current = nextOffset;
240
+ setDragOffset(nextOffset);
241
+ };
242
+ const viewportStyle = (0, import_react.useMemo)(
243
+ () => ({
244
+ ["--icl-aspect-ratio"]: aspectRatio,
245
+ ["--icl-object-fit"]: objectFit
246
+ }),
247
+ [aspectRatio, objectFit]
248
+ );
249
+ const trackStyle = {
250
+ transform: `translate3d(calc(${-activeIndex * 100}% + ${dragOffset}px), 0, 0)`,
251
+ transition: dragStartX.current === null ? `transform ${transitionDuration}ms ease` : "none"
252
+ };
253
+ if (total === 0) {
254
+ return null;
255
+ }
256
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
257
+ "section",
258
+ {
259
+ "aria-label": mergedLabels.region,
260
+ "aria-roledescription": "carousel",
261
+ className: ["icl-root", className].filter(Boolean).join(" "),
262
+ onBlur: (event) => {
263
+ if (!event.currentTarget.contains(event.relatedTarget)) {
264
+ setIsFocused(false);
265
+ }
266
+ },
267
+ onFocus: () => setIsFocused(true),
268
+ onKeyDown: handleKeyDown,
269
+ onMouseEnter: () => setIsHovered(true),
270
+ onMouseLeave: () => setIsHovered(false),
271
+ style,
272
+ tabIndex: 0,
273
+ children: [
274
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
275
+ "div",
276
+ {
277
+ className: "icl-viewport",
278
+ onPointerCancel: finishDrag,
279
+ onPointerDown: handlePointerDown,
280
+ onPointerMove: handlePointerMove,
281
+ onPointerUp: finishDrag,
282
+ onTouchEnd: finishDrag,
283
+ onTouchMove: handleTouchMove,
284
+ onTouchStart: handleTouchStart,
285
+ style: viewportStyle,
286
+ children: [
287
+ showArrows ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
288
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
289
+ "button",
290
+ {
291
+ "aria-label": mergedLabels.previous,
292
+ className: "icl-nav icl-nav-prev",
293
+ disabled: !canGoPrevious,
294
+ onClick: goToPrevious,
295
+ type: "button",
296
+ children: "<"
297
+ }
298
+ ),
299
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
300
+ "button",
301
+ {
302
+ "aria-label": mergedLabels.next,
303
+ className: "icl-nav icl-nav-next",
304
+ disabled: !canGoNext,
305
+ onClick: goToNext,
306
+ type: "button",
307
+ children: ">"
308
+ }
309
+ )
310
+ ] }) : null,
311
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "icl-track", style: trackStyle, children: images.map((image, imageIndex) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
312
+ "article",
313
+ {
314
+ "aria-hidden": activeIndex !== imageIndex,
315
+ "aria-label": mergedLabels.slide(imageIndex, total),
316
+ className: "icl-slide",
317
+ id: `${rootId}-slide-${imageIndex}`,
318
+ role: "group",
319
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("figure", { className: "icl-figure", children: [
320
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", { alt: image.alt, className: "icl-image", src: image.src }),
321
+ image.caption ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("figcaption", { className: "icl-caption", children: image.caption }) : null
322
+ ] })
323
+ },
324
+ `${image.src}-${imageIndex}`
325
+ )) })
326
+ ]
327
+ }
328
+ ),
329
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "icl-footer", children: [
330
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { "aria-live": "polite", className: "icl-sr-only", children: mergedLabels.slide(activeIndex, total) }),
331
+ showIndicators ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { "aria-label": "Slide indicators", className: "icl-dots", role: "tablist", children: images.map((image, imageIndex) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
332
+ "button",
333
+ {
334
+ "aria-controls": `${rootId}-slide-${imageIndex}`,
335
+ "aria-current": activeIndex === imageIndex,
336
+ "aria-label": mergedLabels.goToSlide(imageIndex),
337
+ className: "icl-dot",
338
+ onClick: () => goTo(imageIndex),
339
+ role: "tab",
340
+ type: "button"
341
+ },
342
+ `${image.src}-dot-${imageIndex}`
343
+ )) }) : null,
344
+ showThumbnails ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "icl-thumbs", children: images.map((image, imageIndex) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
345
+ "button",
346
+ {
347
+ "aria-current": activeIndex === imageIndex,
348
+ "aria-label": mergedLabels.goToSlide(imageIndex),
349
+ className: "icl-thumb",
350
+ onClick: () => goTo(imageIndex),
351
+ type: "button",
352
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", { alt: "", "aria-hidden": "true", src: image.thumbnailSrc ?? image.src })
353
+ },
354
+ `${image.src}-thumb-${imageIndex}`
355
+ )) }) : null
356
+ ] })
357
+ ]
358
+ }
359
+ );
360
+ }
361
+ // Annotate the CommonJS export names for ESM import in node:
362
+ 0 && (module.exports = {
363
+ ImageCarousel
364
+ });
365
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/ImageCarousel.tsx","../src/injectStyles.ts"],"sourcesContent":["export { ImageCarousel } from \"./ImageCarousel\";\nexport type { CarouselImage, ImageCarouselLabels, ImageCarouselProps } from \"./ImageCarousel\";\n","import React, {\n CSSProperties,\n KeyboardEvent,\n PointerEvent,\n TouchEvent,\n useEffect,\n useId,\n useMemo,\n useRef,\n useState\n} from \"react\";\nimport { ensureCarouselStyles } from \"./injectStyles\";\n\nexport type CarouselImage = {\n src: string;\n alt: string;\n caption?: React.ReactNode;\n thumbnailSrc?: string;\n};\n\nexport type ImageCarouselLabels = {\n region: string;\n previous: string;\n next: string;\n goToSlide: (index: number) => string;\n slide: (index: number, total: number) => string;\n};\n\nexport type ImageCarouselProps = {\n images: CarouselImage[];\n className?: string;\n style?: CSSProperties;\n initialIndex?: number;\n index?: number;\n loop?: boolean;\n autoplay?: boolean;\n autoplayDelay?: number;\n pauseOnHover?: boolean;\n pauseOnFocus?: boolean;\n keyboardNavigation?: boolean;\n showArrows?: boolean;\n showIndicators?: boolean;\n showThumbnails?: boolean;\n swipeThreshold?: number;\n aspectRatio?: CSSProperties[\"aspectRatio\"];\n objectFit?: CSSProperties[\"objectFit\"];\n transitionDuration?: number;\n onIndexChange?: (index: number) => void;\n onSlideChange?: (index: number) => void;\n labels?: Partial<ImageCarouselLabels>;\n};\n\nconst defaultLabels: ImageCarouselLabels = {\n region: \"Image carousel\",\n previous: \"Previous slide\",\n next: \"Next slide\",\n goToSlide: (index) => `Go to slide ${index + 1}`,\n slide: (index, total) => `Slide ${index + 1} of ${total}`\n};\n\nfunction clampIndex(value: number, total: number) {\n if (total === 0) {\n return 0;\n }\n\n return Math.max(0, Math.min(value, total - 1));\n}\n\nfunction resolveIndex(value: number, total: number, loop: boolean) {\n if (total === 0) {\n return 0;\n }\n\n if (loop) {\n return (value + total) % total;\n }\n\n return clampIndex(value, total);\n}\n\nexport function ImageCarousel({\n images,\n className,\n style,\n initialIndex = 0,\n index,\n loop = true,\n autoplay = false,\n autoplayDelay = 4000,\n pauseOnHover = true,\n pauseOnFocus = true,\n keyboardNavigation = true,\n showArrows = true,\n showIndicators = true,\n showThumbnails = false,\n swipeThreshold = 40,\n aspectRatio = \"16 / 9\",\n objectFit = \"cover\",\n transitionDuration = 350,\n onIndexChange,\n onSlideChange,\n labels\n}: ImageCarouselProps) {\n const total = images.length;\n const mergedLabels = { ...defaultLabels, ...labels };\n const rootId = useId();\n const [internalIndex, setInternalIndex] = useState(() => clampIndex(initialIndex, total));\n const [isHovered, setIsHovered] = useState(false);\n const [isFocused, setIsFocused] = useState(false);\n const [dragOffset, setDragOffset] = useState(0);\n const dragStartX = useRef<number | null>(null);\n const dragOffsetRef = useRef(0);\n const isControlled = index !== undefined;\n const activeIndex = resolveIndex(isControlled ? index : internalIndex, total, false);\n\n useEffect(() => {\n ensureCarouselStyles();\n }, []);\n\n useEffect(() => {\n setInternalIndex((current) => clampIndex(current, total));\n }, [total]);\n\n useEffect(() => {\n if (total === 0) {\n return;\n }\n\n onSlideChange?.(activeIndex);\n }, [activeIndex, onSlideChange, total]);\n\n const canGoPrevious = loop || activeIndex > 0;\n const canGoNext = loop || activeIndex < total - 1;\n\n const setIndex = (next: number) => {\n const resolved = resolveIndex(next, total, loop);\n onIndexChange?.(resolved);\n\n if (!isControlled) {\n setInternalIndex(resolved);\n }\n };\n\n const goToPrevious = () => setIndex(activeIndex - 1);\n const goToNext = () => setIndex(activeIndex + 1);\n const goTo = (next: number) => setIndex(next);\n\n useEffect(() => {\n if (!autoplay || total <= 1) {\n return;\n }\n\n if ((pauseOnHover && isHovered) || (pauseOnFocus && isFocused)) {\n return;\n }\n\n const timer = window.setInterval(() => {\n setIndex(activeIndex + 1);\n }, autoplayDelay);\n\n return () => window.clearInterval(timer);\n }, [\n activeIndex,\n autoplay,\n autoplayDelay,\n isFocused,\n isHovered,\n pauseOnFocus,\n pauseOnHover,\n total\n ]);\n\n const handleKeyDown = (event: KeyboardEvent<HTMLElement>) => {\n if (!keyboardNavigation || total <= 1) {\n return;\n }\n\n if (event.key === \"ArrowLeft\") {\n event.preventDefault();\n goToPrevious();\n }\n\n if (event.key === \"ArrowRight\") {\n event.preventDefault();\n goToNext();\n }\n\n if (event.key === \"Home\") {\n event.preventDefault();\n goTo(0);\n }\n\n if (event.key === \"End\") {\n event.preventDefault();\n goTo(total - 1);\n }\n };\n\n const handlePointerDown = (event: PointerEvent<HTMLDivElement>) => {\n if (total <= 1) {\n return;\n }\n\n dragStartX.current = event.clientX;\n dragOffsetRef.current = 0;\n setDragOffset(0);\n };\n\n const handlePointerMove = (event: PointerEvent<HTMLDivElement>) => {\n if (dragStartX.current === null) {\n return;\n }\n\n const nextOffset = event.clientX - dragStartX.current;\n dragOffsetRef.current = nextOffset;\n setDragOffset(nextOffset);\n };\n\n const finishDrag = () => {\n if (dragStartX.current === null) {\n return;\n }\n\n if (dragOffsetRef.current >= swipeThreshold) {\n goToPrevious();\n } else if (dragOffsetRef.current <= -swipeThreshold) {\n goToNext();\n }\n\n dragStartX.current = null;\n dragOffsetRef.current = 0;\n setDragOffset(0);\n };\n\n const handleTouchStart = (event: TouchEvent<HTMLDivElement>) => {\n if (total <= 1) {\n return;\n }\n\n dragStartX.current = event.touches[0]?.clientX ?? null;\n dragOffsetRef.current = 0;\n setDragOffset(0);\n };\n\n const handleTouchMove = (event: TouchEvent<HTMLDivElement>) => {\n if (dragStartX.current === null) {\n return;\n }\n\n const nextOffset = (event.touches[0]?.clientX ?? dragStartX.current) - dragStartX.current;\n dragOffsetRef.current = nextOffset;\n setDragOffset(nextOffset);\n };\n\n const viewportStyle = useMemo<CSSProperties>(\n () => ({\n [\"--icl-aspect-ratio\" as string]: aspectRatio,\n [\"--icl-object-fit\" as string]: objectFit\n }),\n [aspectRatio, objectFit]\n );\n\n const trackStyle: CSSProperties = {\n transform: `translate3d(calc(${-activeIndex * 100}% + ${dragOffset}px), 0, 0)`,\n transition: dragStartX.current === null ? `transform ${transitionDuration}ms ease` : \"none\"\n };\n\n if (total === 0) {\n return null;\n }\n\n return (\n <section\n aria-label={mergedLabels.region}\n aria-roledescription=\"carousel\"\n className={[\"icl-root\", className].filter(Boolean).join(\" \")}\n onBlur={(event) => {\n if (!event.currentTarget.contains(event.relatedTarget)) {\n setIsFocused(false);\n }\n }}\n onFocus={() => setIsFocused(true)}\n onKeyDown={handleKeyDown}\n onMouseEnter={() => setIsHovered(true)}\n onMouseLeave={() => setIsHovered(false)}\n style={style}\n tabIndex={0}\n >\n <div\n className=\"icl-viewport\"\n onPointerCancel={finishDrag}\n onPointerDown={handlePointerDown}\n onPointerMove={handlePointerMove}\n onPointerUp={finishDrag}\n onTouchEnd={finishDrag}\n onTouchMove={handleTouchMove}\n onTouchStart={handleTouchStart}\n style={viewportStyle}\n >\n {showArrows ? (\n <>\n <button\n aria-label={mergedLabels.previous}\n className=\"icl-nav icl-nav-prev\"\n disabled={!canGoPrevious}\n onClick={goToPrevious}\n type=\"button\"\n >\n {\"<\"}\n </button>\n <button\n aria-label={mergedLabels.next}\n className=\"icl-nav icl-nav-next\"\n disabled={!canGoNext}\n onClick={goToNext}\n type=\"button\"\n >\n {\">\"}\n </button>\n </>\n ) : null}\n\n <div className=\"icl-track\" style={trackStyle}>\n {images.map((image, imageIndex) => (\n <article\n aria-hidden={activeIndex !== imageIndex}\n aria-label={mergedLabels.slide(imageIndex, total)}\n className=\"icl-slide\"\n id={`${rootId}-slide-${imageIndex}`}\n key={`${image.src}-${imageIndex}`}\n role=\"group\"\n >\n <figure className=\"icl-figure\">\n <img alt={image.alt} className=\"icl-image\" src={image.src} />\n {image.caption ? <figcaption className=\"icl-caption\">{image.caption}</figcaption> : null}\n </figure>\n </article>\n ))}\n </div>\n </div>\n\n <div className=\"icl-footer\">\n <div aria-live=\"polite\" className=\"icl-sr-only\">\n {mergedLabels.slide(activeIndex, total)}\n </div>\n\n {showIndicators ? (\n <div aria-label=\"Slide indicators\" className=\"icl-dots\" role=\"tablist\">\n {images.map((image, imageIndex) => (\n <button\n aria-controls={`${rootId}-slide-${imageIndex}`}\n aria-current={activeIndex === imageIndex}\n aria-label={mergedLabels.goToSlide(imageIndex)}\n className=\"icl-dot\"\n key={`${image.src}-dot-${imageIndex}`}\n onClick={() => goTo(imageIndex)}\n role=\"tab\"\n type=\"button\"\n />\n ))}\n </div>\n ) : null}\n\n {showThumbnails ? (\n <div className=\"icl-thumbs\">\n {images.map((image, imageIndex) => (\n <button\n aria-current={activeIndex === imageIndex}\n aria-label={mergedLabels.goToSlide(imageIndex)}\n className=\"icl-thumb\"\n key={`${image.src}-thumb-${imageIndex}`}\n onClick={() => goTo(imageIndex)}\n type=\"button\"\n >\n <img alt=\"\" aria-hidden=\"true\" src={image.thumbnailSrc ?? image.src} />\n </button>\n ))}\n </div>\n ) : null}\n </div>\n </section>\n );\n}\n","const STYLE_ID = \"image-carousel-library-styles\";\n\nconst styles = `\n.icl-root{position:relative;width:100%;display:grid;gap:1rem;color:#101828;font-family:system-ui,sans-serif}\n.icl-viewport{position:relative;overflow:hidden;border-radius:1rem;background:linear-gradient(180deg,#f8fafc 0%,#e2e8f0 100%)}\n.icl-track{display:flex;touch-action:pan-y;will-change:transform}\n.icl-slide{min-width:100%;position:relative;user-select:none}\n.icl-figure{margin:0;position:relative;aspect-ratio:var(--icl-aspect-ratio,16/9);overflow:hidden}\n.icl-image{width:100%;height:100%;display:block;object-fit:var(--icl-object-fit,cover);background:#cbd5e1}\n.icl-caption{position:absolute;left:1rem;right:1rem;bottom:1rem;padding:.75rem 1rem;border-radius:.75rem;background:rgb(15 23 42 / .7);color:#f8fafc;font-size:.95rem;backdrop-filter:blur(8px)}\n.icl-nav{position:absolute;top:50%;transform:translateY(-50%);display:inline-flex;align-items:center;justify-content:center;width:2.75rem;height:2.75rem;border:0;border-radius:999px;background:rgb(15 23 42 / .8);color:#fff;cursor:pointer;z-index:1}\n.icl-nav:hover{background:rgb(15 23 42 / .92)}\n.icl-nav:focus-visible,.icl-dot:focus-visible,.icl-thumb:focus-visible{outline:3px solid #38bdf8;outline-offset:2px}\n.icl-nav[disabled]{opacity:.45;cursor:not-allowed}\n.icl-nav-prev{left:.75rem}\n.icl-nav-next{right:.75rem}\n.icl-footer{display:grid;gap:.75rem}\n.icl-dots{display:flex;justify-content:center;gap:.5rem;flex-wrap:wrap}\n.icl-dot{width:.8rem;height:.8rem;border:0;border-radius:999px;background:#cbd5e1;cursor:pointer;padding:0}\n.icl-dot[aria-current=\"true\"]{background:#0f172a;transform:scale(1.15)}\n.icl-thumbs{display:grid;grid-template-columns:repeat(auto-fit,minmax(4.5rem,1fr));gap:.5rem}\n.icl-thumb{border:2px solid transparent;border-radius:.8rem;padding:0;overflow:hidden;cursor:pointer;background:#e2e8f0}\n.icl-thumb[aria-current=\"true\"]{border-color:#0f172a}\n.icl-thumb img{display:block;width:100%;aspect-ratio:1/1;object-fit:cover}\n.icl-sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}\n@media (max-width: 640px){\n .icl-caption{left:.75rem;right:.75rem;bottom:.75rem;font-size:.85rem}\n .icl-nav{width:2.4rem;height:2.4rem}\n}\n`;\n\nexport function ensureCarouselStyles() {\n if (typeof document === \"undefined\") {\n return;\n }\n\n if (document.getElementById(STYLE_ID)) {\n return;\n }\n\n const style = document.createElement(\"style\");\n style.id = STYLE_ID;\n style.textContent = styles;\n document.head.appendChild(style);\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,42 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import React, { CSSProperties } from 'react';
3
+
4
+ type CarouselImage = {
5
+ src: string;
6
+ alt: string;
7
+ caption?: React.ReactNode;
8
+ thumbnailSrc?: string;
9
+ };
10
+ type ImageCarouselLabels = {
11
+ region: string;
12
+ previous: string;
13
+ next: string;
14
+ goToSlide: (index: number) => string;
15
+ slide: (index: number, total: number) => string;
16
+ };
17
+ type ImageCarouselProps = {
18
+ images: CarouselImage[];
19
+ className?: string;
20
+ style?: CSSProperties;
21
+ initialIndex?: number;
22
+ index?: number;
23
+ loop?: boolean;
24
+ autoplay?: boolean;
25
+ autoplayDelay?: number;
26
+ pauseOnHover?: boolean;
27
+ pauseOnFocus?: boolean;
28
+ keyboardNavigation?: boolean;
29
+ showArrows?: boolean;
30
+ showIndicators?: boolean;
31
+ showThumbnails?: boolean;
32
+ swipeThreshold?: number;
33
+ aspectRatio?: CSSProperties["aspectRatio"];
34
+ objectFit?: CSSProperties["objectFit"];
35
+ transitionDuration?: number;
36
+ onIndexChange?: (index: number) => void;
37
+ onSlideChange?: (index: number) => void;
38
+ labels?: Partial<ImageCarouselLabels>;
39
+ };
40
+ declare function ImageCarousel({ images, className, style, initialIndex, index, loop, autoplay, autoplayDelay, pauseOnHover, pauseOnFocus, keyboardNavigation, showArrows, showIndicators, showThumbnails, swipeThreshold, aspectRatio, objectFit, transitionDuration, onIndexChange, onSlideChange, labels }: ImageCarouselProps): react_jsx_runtime.JSX.Element | null;
41
+
42
+ export { type CarouselImage, ImageCarousel, type ImageCarouselLabels, type ImageCarouselProps };
@@ -0,0 +1,42 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import React, { CSSProperties } from 'react';
3
+
4
+ type CarouselImage = {
5
+ src: string;
6
+ alt: string;
7
+ caption?: React.ReactNode;
8
+ thumbnailSrc?: string;
9
+ };
10
+ type ImageCarouselLabels = {
11
+ region: string;
12
+ previous: string;
13
+ next: string;
14
+ goToSlide: (index: number) => string;
15
+ slide: (index: number, total: number) => string;
16
+ };
17
+ type ImageCarouselProps = {
18
+ images: CarouselImage[];
19
+ className?: string;
20
+ style?: CSSProperties;
21
+ initialIndex?: number;
22
+ index?: number;
23
+ loop?: boolean;
24
+ autoplay?: boolean;
25
+ autoplayDelay?: number;
26
+ pauseOnHover?: boolean;
27
+ pauseOnFocus?: boolean;
28
+ keyboardNavigation?: boolean;
29
+ showArrows?: boolean;
30
+ showIndicators?: boolean;
31
+ showThumbnails?: boolean;
32
+ swipeThreshold?: number;
33
+ aspectRatio?: CSSProperties["aspectRatio"];
34
+ objectFit?: CSSProperties["objectFit"];
35
+ transitionDuration?: number;
36
+ onIndexChange?: (index: number) => void;
37
+ onSlideChange?: (index: number) => void;
38
+ labels?: Partial<ImageCarouselLabels>;
39
+ };
40
+ declare function ImageCarousel({ images, className, style, initialIndex, index, loop, autoplay, autoplayDelay, pauseOnHover, pauseOnFocus, keyboardNavigation, showArrows, showIndicators, showThumbnails, swipeThreshold, aspectRatio, objectFit, transitionDuration, onIndexChange, onSlideChange, labels }: ImageCarouselProps): react_jsx_runtime.JSX.Element | null;
41
+
42
+ export { type CarouselImage, ImageCarousel, type ImageCarouselLabels, type ImageCarouselProps };
package/dist/index.js ADDED
@@ -0,0 +1,344 @@
1
+ // src/ImageCarousel.tsx
2
+ import {
3
+ useEffect,
4
+ useId,
5
+ useMemo,
6
+ useRef,
7
+ useState
8
+ } from "react";
9
+
10
+ // src/injectStyles.ts
11
+ var STYLE_ID = "image-carousel-library-styles";
12
+ var styles = `
13
+ .icl-root{position:relative;width:100%;display:grid;gap:1rem;color:#101828;font-family:system-ui,sans-serif}
14
+ .icl-viewport{position:relative;overflow:hidden;border-radius:1rem;background:linear-gradient(180deg,#f8fafc 0%,#e2e8f0 100%)}
15
+ .icl-track{display:flex;touch-action:pan-y;will-change:transform}
16
+ .icl-slide{min-width:100%;position:relative;user-select:none}
17
+ .icl-figure{margin:0;position:relative;aspect-ratio:var(--icl-aspect-ratio,16/9);overflow:hidden}
18
+ .icl-image{width:100%;height:100%;display:block;object-fit:var(--icl-object-fit,cover);background:#cbd5e1}
19
+ .icl-caption{position:absolute;left:1rem;right:1rem;bottom:1rem;padding:.75rem 1rem;border-radius:.75rem;background:rgb(15 23 42 / .7);color:#f8fafc;font-size:.95rem;backdrop-filter:blur(8px)}
20
+ .icl-nav{position:absolute;top:50%;transform:translateY(-50%);display:inline-flex;align-items:center;justify-content:center;width:2.75rem;height:2.75rem;border:0;border-radius:999px;background:rgb(15 23 42 / .8);color:#fff;cursor:pointer;z-index:1}
21
+ .icl-nav:hover{background:rgb(15 23 42 / .92)}
22
+ .icl-nav:focus-visible,.icl-dot:focus-visible,.icl-thumb:focus-visible{outline:3px solid #38bdf8;outline-offset:2px}
23
+ .icl-nav[disabled]{opacity:.45;cursor:not-allowed}
24
+ .icl-nav-prev{left:.75rem}
25
+ .icl-nav-next{right:.75rem}
26
+ .icl-footer{display:grid;gap:.75rem}
27
+ .icl-dots{display:flex;justify-content:center;gap:.5rem;flex-wrap:wrap}
28
+ .icl-dot{width:.8rem;height:.8rem;border:0;border-radius:999px;background:#cbd5e1;cursor:pointer;padding:0}
29
+ .icl-dot[aria-current="true"]{background:#0f172a;transform:scale(1.15)}
30
+ .icl-thumbs{display:grid;grid-template-columns:repeat(auto-fit,minmax(4.5rem,1fr));gap:.5rem}
31
+ .icl-thumb{border:2px solid transparent;border-radius:.8rem;padding:0;overflow:hidden;cursor:pointer;background:#e2e8f0}
32
+ .icl-thumb[aria-current="true"]{border-color:#0f172a}
33
+ .icl-thumb img{display:block;width:100%;aspect-ratio:1/1;object-fit:cover}
34
+ .icl-sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}
35
+ @media (max-width: 640px){
36
+ .icl-caption{left:.75rem;right:.75rem;bottom:.75rem;font-size:.85rem}
37
+ .icl-nav{width:2.4rem;height:2.4rem}
38
+ }
39
+ `;
40
+ function ensureCarouselStyles() {
41
+ if (typeof document === "undefined") {
42
+ return;
43
+ }
44
+ if (document.getElementById(STYLE_ID)) {
45
+ return;
46
+ }
47
+ const style = document.createElement("style");
48
+ style.id = STYLE_ID;
49
+ style.textContent = styles;
50
+ document.head.appendChild(style);
51
+ }
52
+
53
+ // src/ImageCarousel.tsx
54
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
55
+ var defaultLabels = {
56
+ region: "Image carousel",
57
+ previous: "Previous slide",
58
+ next: "Next slide",
59
+ goToSlide: (index) => `Go to slide ${index + 1}`,
60
+ slide: (index, total) => `Slide ${index + 1} of ${total}`
61
+ };
62
+ function clampIndex(value, total) {
63
+ if (total === 0) {
64
+ return 0;
65
+ }
66
+ return Math.max(0, Math.min(value, total - 1));
67
+ }
68
+ function resolveIndex(value, total, loop) {
69
+ if (total === 0) {
70
+ return 0;
71
+ }
72
+ if (loop) {
73
+ return (value + total) % total;
74
+ }
75
+ return clampIndex(value, total);
76
+ }
77
+ function ImageCarousel({
78
+ images,
79
+ className,
80
+ style,
81
+ initialIndex = 0,
82
+ index,
83
+ loop = true,
84
+ autoplay = false,
85
+ autoplayDelay = 4e3,
86
+ pauseOnHover = true,
87
+ pauseOnFocus = true,
88
+ keyboardNavigation = true,
89
+ showArrows = true,
90
+ showIndicators = true,
91
+ showThumbnails = false,
92
+ swipeThreshold = 40,
93
+ aspectRatio = "16 / 9",
94
+ objectFit = "cover",
95
+ transitionDuration = 350,
96
+ onIndexChange,
97
+ onSlideChange,
98
+ labels
99
+ }) {
100
+ const total = images.length;
101
+ const mergedLabels = { ...defaultLabels, ...labels };
102
+ const rootId = useId();
103
+ const [internalIndex, setInternalIndex] = useState(() => clampIndex(initialIndex, total));
104
+ const [isHovered, setIsHovered] = useState(false);
105
+ const [isFocused, setIsFocused] = useState(false);
106
+ const [dragOffset, setDragOffset] = useState(0);
107
+ const dragStartX = useRef(null);
108
+ const dragOffsetRef = useRef(0);
109
+ const isControlled = index !== void 0;
110
+ const activeIndex = resolveIndex(isControlled ? index : internalIndex, total, false);
111
+ useEffect(() => {
112
+ ensureCarouselStyles();
113
+ }, []);
114
+ useEffect(() => {
115
+ setInternalIndex((current) => clampIndex(current, total));
116
+ }, [total]);
117
+ useEffect(() => {
118
+ if (total === 0) {
119
+ return;
120
+ }
121
+ onSlideChange?.(activeIndex);
122
+ }, [activeIndex, onSlideChange, total]);
123
+ const canGoPrevious = loop || activeIndex > 0;
124
+ const canGoNext = loop || activeIndex < total - 1;
125
+ const setIndex = (next) => {
126
+ const resolved = resolveIndex(next, total, loop);
127
+ onIndexChange?.(resolved);
128
+ if (!isControlled) {
129
+ setInternalIndex(resolved);
130
+ }
131
+ };
132
+ const goToPrevious = () => setIndex(activeIndex - 1);
133
+ const goToNext = () => setIndex(activeIndex + 1);
134
+ const goTo = (next) => setIndex(next);
135
+ useEffect(() => {
136
+ if (!autoplay || total <= 1) {
137
+ return;
138
+ }
139
+ if (pauseOnHover && isHovered || pauseOnFocus && isFocused) {
140
+ return;
141
+ }
142
+ const timer = window.setInterval(() => {
143
+ setIndex(activeIndex + 1);
144
+ }, autoplayDelay);
145
+ return () => window.clearInterval(timer);
146
+ }, [
147
+ activeIndex,
148
+ autoplay,
149
+ autoplayDelay,
150
+ isFocused,
151
+ isHovered,
152
+ pauseOnFocus,
153
+ pauseOnHover,
154
+ total
155
+ ]);
156
+ const handleKeyDown = (event) => {
157
+ if (!keyboardNavigation || total <= 1) {
158
+ return;
159
+ }
160
+ if (event.key === "ArrowLeft") {
161
+ event.preventDefault();
162
+ goToPrevious();
163
+ }
164
+ if (event.key === "ArrowRight") {
165
+ event.preventDefault();
166
+ goToNext();
167
+ }
168
+ if (event.key === "Home") {
169
+ event.preventDefault();
170
+ goTo(0);
171
+ }
172
+ if (event.key === "End") {
173
+ event.preventDefault();
174
+ goTo(total - 1);
175
+ }
176
+ };
177
+ const handlePointerDown = (event) => {
178
+ if (total <= 1) {
179
+ return;
180
+ }
181
+ dragStartX.current = event.clientX;
182
+ dragOffsetRef.current = 0;
183
+ setDragOffset(0);
184
+ };
185
+ const handlePointerMove = (event) => {
186
+ if (dragStartX.current === null) {
187
+ return;
188
+ }
189
+ const nextOffset = event.clientX - dragStartX.current;
190
+ dragOffsetRef.current = nextOffset;
191
+ setDragOffset(nextOffset);
192
+ };
193
+ const finishDrag = () => {
194
+ if (dragStartX.current === null) {
195
+ return;
196
+ }
197
+ if (dragOffsetRef.current >= swipeThreshold) {
198
+ goToPrevious();
199
+ } else if (dragOffsetRef.current <= -swipeThreshold) {
200
+ goToNext();
201
+ }
202
+ dragStartX.current = null;
203
+ dragOffsetRef.current = 0;
204
+ setDragOffset(0);
205
+ };
206
+ const handleTouchStart = (event) => {
207
+ if (total <= 1) {
208
+ return;
209
+ }
210
+ dragStartX.current = event.touches[0]?.clientX ?? null;
211
+ dragOffsetRef.current = 0;
212
+ setDragOffset(0);
213
+ };
214
+ const handleTouchMove = (event) => {
215
+ if (dragStartX.current === null) {
216
+ return;
217
+ }
218
+ const nextOffset = (event.touches[0]?.clientX ?? dragStartX.current) - dragStartX.current;
219
+ dragOffsetRef.current = nextOffset;
220
+ setDragOffset(nextOffset);
221
+ };
222
+ const viewportStyle = useMemo(
223
+ () => ({
224
+ ["--icl-aspect-ratio"]: aspectRatio,
225
+ ["--icl-object-fit"]: objectFit
226
+ }),
227
+ [aspectRatio, objectFit]
228
+ );
229
+ const trackStyle = {
230
+ transform: `translate3d(calc(${-activeIndex * 100}% + ${dragOffset}px), 0, 0)`,
231
+ transition: dragStartX.current === null ? `transform ${transitionDuration}ms ease` : "none"
232
+ };
233
+ if (total === 0) {
234
+ return null;
235
+ }
236
+ return /* @__PURE__ */ jsxs(
237
+ "section",
238
+ {
239
+ "aria-label": mergedLabels.region,
240
+ "aria-roledescription": "carousel",
241
+ className: ["icl-root", className].filter(Boolean).join(" "),
242
+ onBlur: (event) => {
243
+ if (!event.currentTarget.contains(event.relatedTarget)) {
244
+ setIsFocused(false);
245
+ }
246
+ },
247
+ onFocus: () => setIsFocused(true),
248
+ onKeyDown: handleKeyDown,
249
+ onMouseEnter: () => setIsHovered(true),
250
+ onMouseLeave: () => setIsHovered(false),
251
+ style,
252
+ tabIndex: 0,
253
+ children: [
254
+ /* @__PURE__ */ jsxs(
255
+ "div",
256
+ {
257
+ className: "icl-viewport",
258
+ onPointerCancel: finishDrag,
259
+ onPointerDown: handlePointerDown,
260
+ onPointerMove: handlePointerMove,
261
+ onPointerUp: finishDrag,
262
+ onTouchEnd: finishDrag,
263
+ onTouchMove: handleTouchMove,
264
+ onTouchStart: handleTouchStart,
265
+ style: viewportStyle,
266
+ children: [
267
+ showArrows ? /* @__PURE__ */ jsxs(Fragment, { children: [
268
+ /* @__PURE__ */ jsx(
269
+ "button",
270
+ {
271
+ "aria-label": mergedLabels.previous,
272
+ className: "icl-nav icl-nav-prev",
273
+ disabled: !canGoPrevious,
274
+ onClick: goToPrevious,
275
+ type: "button",
276
+ children: "<"
277
+ }
278
+ ),
279
+ /* @__PURE__ */ jsx(
280
+ "button",
281
+ {
282
+ "aria-label": mergedLabels.next,
283
+ className: "icl-nav icl-nav-next",
284
+ disabled: !canGoNext,
285
+ onClick: goToNext,
286
+ type: "button",
287
+ children: ">"
288
+ }
289
+ )
290
+ ] }) : null,
291
+ /* @__PURE__ */ jsx("div", { className: "icl-track", style: trackStyle, children: images.map((image, imageIndex) => /* @__PURE__ */ jsx(
292
+ "article",
293
+ {
294
+ "aria-hidden": activeIndex !== imageIndex,
295
+ "aria-label": mergedLabels.slide(imageIndex, total),
296
+ className: "icl-slide",
297
+ id: `${rootId}-slide-${imageIndex}`,
298
+ role: "group",
299
+ children: /* @__PURE__ */ jsxs("figure", { className: "icl-figure", children: [
300
+ /* @__PURE__ */ jsx("img", { alt: image.alt, className: "icl-image", src: image.src }),
301
+ image.caption ? /* @__PURE__ */ jsx("figcaption", { className: "icl-caption", children: image.caption }) : null
302
+ ] })
303
+ },
304
+ `${image.src}-${imageIndex}`
305
+ )) })
306
+ ]
307
+ }
308
+ ),
309
+ /* @__PURE__ */ jsxs("div", { className: "icl-footer", children: [
310
+ /* @__PURE__ */ jsx("div", { "aria-live": "polite", className: "icl-sr-only", children: mergedLabels.slide(activeIndex, total) }),
311
+ showIndicators ? /* @__PURE__ */ jsx("div", { "aria-label": "Slide indicators", className: "icl-dots", role: "tablist", children: images.map((image, imageIndex) => /* @__PURE__ */ jsx(
312
+ "button",
313
+ {
314
+ "aria-controls": `${rootId}-slide-${imageIndex}`,
315
+ "aria-current": activeIndex === imageIndex,
316
+ "aria-label": mergedLabels.goToSlide(imageIndex),
317
+ className: "icl-dot",
318
+ onClick: () => goTo(imageIndex),
319
+ role: "tab",
320
+ type: "button"
321
+ },
322
+ `${image.src}-dot-${imageIndex}`
323
+ )) }) : null,
324
+ showThumbnails ? /* @__PURE__ */ jsx("div", { className: "icl-thumbs", children: images.map((image, imageIndex) => /* @__PURE__ */ jsx(
325
+ "button",
326
+ {
327
+ "aria-current": activeIndex === imageIndex,
328
+ "aria-label": mergedLabels.goToSlide(imageIndex),
329
+ className: "icl-thumb",
330
+ onClick: () => goTo(imageIndex),
331
+ type: "button",
332
+ children: /* @__PURE__ */ jsx("img", { alt: "", "aria-hidden": "true", src: image.thumbnailSrc ?? image.src })
333
+ },
334
+ `${image.src}-thumb-${imageIndex}`
335
+ )) }) : null
336
+ ] })
337
+ ]
338
+ }
339
+ );
340
+ }
341
+ export {
342
+ ImageCarousel
343
+ };
344
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/ImageCarousel.tsx","../src/injectStyles.ts"],"sourcesContent":["import React, {\n CSSProperties,\n KeyboardEvent,\n PointerEvent,\n TouchEvent,\n useEffect,\n useId,\n useMemo,\n useRef,\n useState\n} from \"react\";\nimport { ensureCarouselStyles } from \"./injectStyles\";\n\nexport type CarouselImage = {\n src: string;\n alt: string;\n caption?: React.ReactNode;\n thumbnailSrc?: string;\n};\n\nexport type ImageCarouselLabels = {\n region: string;\n previous: string;\n next: string;\n goToSlide: (index: number) => string;\n slide: (index: number, total: number) => string;\n};\n\nexport type ImageCarouselProps = {\n images: CarouselImage[];\n className?: string;\n style?: CSSProperties;\n initialIndex?: number;\n index?: number;\n loop?: boolean;\n autoplay?: boolean;\n autoplayDelay?: number;\n pauseOnHover?: boolean;\n pauseOnFocus?: boolean;\n keyboardNavigation?: boolean;\n showArrows?: boolean;\n showIndicators?: boolean;\n showThumbnails?: boolean;\n swipeThreshold?: number;\n aspectRatio?: CSSProperties[\"aspectRatio\"];\n objectFit?: CSSProperties[\"objectFit\"];\n transitionDuration?: number;\n onIndexChange?: (index: number) => void;\n onSlideChange?: (index: number) => void;\n labels?: Partial<ImageCarouselLabels>;\n};\n\nconst defaultLabels: ImageCarouselLabels = {\n region: \"Image carousel\",\n previous: \"Previous slide\",\n next: \"Next slide\",\n goToSlide: (index) => `Go to slide ${index + 1}`,\n slide: (index, total) => `Slide ${index + 1} of ${total}`\n};\n\nfunction clampIndex(value: number, total: number) {\n if (total === 0) {\n return 0;\n }\n\n return Math.max(0, Math.min(value, total - 1));\n}\n\nfunction resolveIndex(value: number, total: number, loop: boolean) {\n if (total === 0) {\n return 0;\n }\n\n if (loop) {\n return (value + total) % total;\n }\n\n return clampIndex(value, total);\n}\n\nexport function ImageCarousel({\n images,\n className,\n style,\n initialIndex = 0,\n index,\n loop = true,\n autoplay = false,\n autoplayDelay = 4000,\n pauseOnHover = true,\n pauseOnFocus = true,\n keyboardNavigation = true,\n showArrows = true,\n showIndicators = true,\n showThumbnails = false,\n swipeThreshold = 40,\n aspectRatio = \"16 / 9\",\n objectFit = \"cover\",\n transitionDuration = 350,\n onIndexChange,\n onSlideChange,\n labels\n}: ImageCarouselProps) {\n const total = images.length;\n const mergedLabels = { ...defaultLabels, ...labels };\n const rootId = useId();\n const [internalIndex, setInternalIndex] = useState(() => clampIndex(initialIndex, total));\n const [isHovered, setIsHovered] = useState(false);\n const [isFocused, setIsFocused] = useState(false);\n const [dragOffset, setDragOffset] = useState(0);\n const dragStartX = useRef<number | null>(null);\n const dragOffsetRef = useRef(0);\n const isControlled = index !== undefined;\n const activeIndex = resolveIndex(isControlled ? index : internalIndex, total, false);\n\n useEffect(() => {\n ensureCarouselStyles();\n }, []);\n\n useEffect(() => {\n setInternalIndex((current) => clampIndex(current, total));\n }, [total]);\n\n useEffect(() => {\n if (total === 0) {\n return;\n }\n\n onSlideChange?.(activeIndex);\n }, [activeIndex, onSlideChange, total]);\n\n const canGoPrevious = loop || activeIndex > 0;\n const canGoNext = loop || activeIndex < total - 1;\n\n const setIndex = (next: number) => {\n const resolved = resolveIndex(next, total, loop);\n onIndexChange?.(resolved);\n\n if (!isControlled) {\n setInternalIndex(resolved);\n }\n };\n\n const goToPrevious = () => setIndex(activeIndex - 1);\n const goToNext = () => setIndex(activeIndex + 1);\n const goTo = (next: number) => setIndex(next);\n\n useEffect(() => {\n if (!autoplay || total <= 1) {\n return;\n }\n\n if ((pauseOnHover && isHovered) || (pauseOnFocus && isFocused)) {\n return;\n }\n\n const timer = window.setInterval(() => {\n setIndex(activeIndex + 1);\n }, autoplayDelay);\n\n return () => window.clearInterval(timer);\n }, [\n activeIndex,\n autoplay,\n autoplayDelay,\n isFocused,\n isHovered,\n pauseOnFocus,\n pauseOnHover,\n total\n ]);\n\n const handleKeyDown = (event: KeyboardEvent<HTMLElement>) => {\n if (!keyboardNavigation || total <= 1) {\n return;\n }\n\n if (event.key === \"ArrowLeft\") {\n event.preventDefault();\n goToPrevious();\n }\n\n if (event.key === \"ArrowRight\") {\n event.preventDefault();\n goToNext();\n }\n\n if (event.key === \"Home\") {\n event.preventDefault();\n goTo(0);\n }\n\n if (event.key === \"End\") {\n event.preventDefault();\n goTo(total - 1);\n }\n };\n\n const handlePointerDown = (event: PointerEvent<HTMLDivElement>) => {\n if (total <= 1) {\n return;\n }\n\n dragStartX.current = event.clientX;\n dragOffsetRef.current = 0;\n setDragOffset(0);\n };\n\n const handlePointerMove = (event: PointerEvent<HTMLDivElement>) => {\n if (dragStartX.current === null) {\n return;\n }\n\n const nextOffset = event.clientX - dragStartX.current;\n dragOffsetRef.current = nextOffset;\n setDragOffset(nextOffset);\n };\n\n const finishDrag = () => {\n if (dragStartX.current === null) {\n return;\n }\n\n if (dragOffsetRef.current >= swipeThreshold) {\n goToPrevious();\n } else if (dragOffsetRef.current <= -swipeThreshold) {\n goToNext();\n }\n\n dragStartX.current = null;\n dragOffsetRef.current = 0;\n setDragOffset(0);\n };\n\n const handleTouchStart = (event: TouchEvent<HTMLDivElement>) => {\n if (total <= 1) {\n return;\n }\n\n dragStartX.current = event.touches[0]?.clientX ?? null;\n dragOffsetRef.current = 0;\n setDragOffset(0);\n };\n\n const handleTouchMove = (event: TouchEvent<HTMLDivElement>) => {\n if (dragStartX.current === null) {\n return;\n }\n\n const nextOffset = (event.touches[0]?.clientX ?? dragStartX.current) - dragStartX.current;\n dragOffsetRef.current = nextOffset;\n setDragOffset(nextOffset);\n };\n\n const viewportStyle = useMemo<CSSProperties>(\n () => ({\n [\"--icl-aspect-ratio\" as string]: aspectRatio,\n [\"--icl-object-fit\" as string]: objectFit\n }),\n [aspectRatio, objectFit]\n );\n\n const trackStyle: CSSProperties = {\n transform: `translate3d(calc(${-activeIndex * 100}% + ${dragOffset}px), 0, 0)`,\n transition: dragStartX.current === null ? `transform ${transitionDuration}ms ease` : \"none\"\n };\n\n if (total === 0) {\n return null;\n }\n\n return (\n <section\n aria-label={mergedLabels.region}\n aria-roledescription=\"carousel\"\n className={[\"icl-root\", className].filter(Boolean).join(\" \")}\n onBlur={(event) => {\n if (!event.currentTarget.contains(event.relatedTarget)) {\n setIsFocused(false);\n }\n }}\n onFocus={() => setIsFocused(true)}\n onKeyDown={handleKeyDown}\n onMouseEnter={() => setIsHovered(true)}\n onMouseLeave={() => setIsHovered(false)}\n style={style}\n tabIndex={0}\n >\n <div\n className=\"icl-viewport\"\n onPointerCancel={finishDrag}\n onPointerDown={handlePointerDown}\n onPointerMove={handlePointerMove}\n onPointerUp={finishDrag}\n onTouchEnd={finishDrag}\n onTouchMove={handleTouchMove}\n onTouchStart={handleTouchStart}\n style={viewportStyle}\n >\n {showArrows ? (\n <>\n <button\n aria-label={mergedLabels.previous}\n className=\"icl-nav icl-nav-prev\"\n disabled={!canGoPrevious}\n onClick={goToPrevious}\n type=\"button\"\n >\n {\"<\"}\n </button>\n <button\n aria-label={mergedLabels.next}\n className=\"icl-nav icl-nav-next\"\n disabled={!canGoNext}\n onClick={goToNext}\n type=\"button\"\n >\n {\">\"}\n </button>\n </>\n ) : null}\n\n <div className=\"icl-track\" style={trackStyle}>\n {images.map((image, imageIndex) => (\n <article\n aria-hidden={activeIndex !== imageIndex}\n aria-label={mergedLabels.slide(imageIndex, total)}\n className=\"icl-slide\"\n id={`${rootId}-slide-${imageIndex}`}\n key={`${image.src}-${imageIndex}`}\n role=\"group\"\n >\n <figure className=\"icl-figure\">\n <img alt={image.alt} className=\"icl-image\" src={image.src} />\n {image.caption ? <figcaption className=\"icl-caption\">{image.caption}</figcaption> : null}\n </figure>\n </article>\n ))}\n </div>\n </div>\n\n <div className=\"icl-footer\">\n <div aria-live=\"polite\" className=\"icl-sr-only\">\n {mergedLabels.slide(activeIndex, total)}\n </div>\n\n {showIndicators ? (\n <div aria-label=\"Slide indicators\" className=\"icl-dots\" role=\"tablist\">\n {images.map((image, imageIndex) => (\n <button\n aria-controls={`${rootId}-slide-${imageIndex}`}\n aria-current={activeIndex === imageIndex}\n aria-label={mergedLabels.goToSlide(imageIndex)}\n className=\"icl-dot\"\n key={`${image.src}-dot-${imageIndex}`}\n onClick={() => goTo(imageIndex)}\n role=\"tab\"\n type=\"button\"\n />\n ))}\n </div>\n ) : null}\n\n {showThumbnails ? (\n <div className=\"icl-thumbs\">\n {images.map((image, imageIndex) => (\n <button\n aria-current={activeIndex === imageIndex}\n aria-label={mergedLabels.goToSlide(imageIndex)}\n className=\"icl-thumb\"\n key={`${image.src}-thumb-${imageIndex}`}\n onClick={() => goTo(imageIndex)}\n type=\"button\"\n >\n <img alt=\"\" aria-hidden=\"true\" src={image.thumbnailSrc ?? image.src} />\n </button>\n ))}\n </div>\n ) : null}\n </div>\n </section>\n );\n}\n","const STYLE_ID = \"image-carousel-library-styles\";\n\nconst styles = `\n.icl-root{position:relative;width:100%;display:grid;gap:1rem;color:#101828;font-family:system-ui,sans-serif}\n.icl-viewport{position:relative;overflow:hidden;border-radius:1rem;background:linear-gradient(180deg,#f8fafc 0%,#e2e8f0 100%)}\n.icl-track{display:flex;touch-action:pan-y;will-change:transform}\n.icl-slide{min-width:100%;position:relative;user-select:none}\n.icl-figure{margin:0;position:relative;aspect-ratio:var(--icl-aspect-ratio,16/9);overflow:hidden}\n.icl-image{width:100%;height:100%;display:block;object-fit:var(--icl-object-fit,cover);background:#cbd5e1}\n.icl-caption{position:absolute;left:1rem;right:1rem;bottom:1rem;padding:.75rem 1rem;border-radius:.75rem;background:rgb(15 23 42 / .7);color:#f8fafc;font-size:.95rem;backdrop-filter:blur(8px)}\n.icl-nav{position:absolute;top:50%;transform:translateY(-50%);display:inline-flex;align-items:center;justify-content:center;width:2.75rem;height:2.75rem;border:0;border-radius:999px;background:rgb(15 23 42 / .8);color:#fff;cursor:pointer;z-index:1}\n.icl-nav:hover{background:rgb(15 23 42 / .92)}\n.icl-nav:focus-visible,.icl-dot:focus-visible,.icl-thumb:focus-visible{outline:3px solid #38bdf8;outline-offset:2px}\n.icl-nav[disabled]{opacity:.45;cursor:not-allowed}\n.icl-nav-prev{left:.75rem}\n.icl-nav-next{right:.75rem}\n.icl-footer{display:grid;gap:.75rem}\n.icl-dots{display:flex;justify-content:center;gap:.5rem;flex-wrap:wrap}\n.icl-dot{width:.8rem;height:.8rem;border:0;border-radius:999px;background:#cbd5e1;cursor:pointer;padding:0}\n.icl-dot[aria-current=\"true\"]{background:#0f172a;transform:scale(1.15)}\n.icl-thumbs{display:grid;grid-template-columns:repeat(auto-fit,minmax(4.5rem,1fr));gap:.5rem}\n.icl-thumb{border:2px solid transparent;border-radius:.8rem;padding:0;overflow:hidden;cursor:pointer;background:#e2e8f0}\n.icl-thumb[aria-current=\"true\"]{border-color:#0f172a}\n.icl-thumb img{display:block;width:100%;aspect-ratio:1/1;object-fit:cover}\n.icl-sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}\n@media (max-width: 640px){\n .icl-caption{left:.75rem;right:.75rem;bottom:.75rem;font-size:.85rem}\n .icl-nav{width:2.4rem;height:2.4rem}\n}\n`;\n\nexport function ensureCarouselStyles() {\n if (typeof document === \"undefined\") {\n return;\n }\n\n if (document.getElementById(STYLE_ID)) {\n return;\n }\n\n const style = document.createElement(\"style\");\n style.id = STYLE_ID;\n style.textContent = styles;\n document.head.appendChild(style);\n}\n"],"mappings":"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","names":[]}
package/package.json ADDED
@@ -0,0 +1,70 @@
1
+ {
2
+ "name": "react-best-carroussel",
3
+ "version": "0.1.0",
4
+ "description": "React image carousel library with simple API, accessibility and npm-ready packaging.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "./dist/index.cjs",
8
+ "module": "./dist/index.js",
9
+ "types": "./dist/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/index.js",
14
+ "require": "./dist/index.cjs"
15
+ }
16
+ },
17
+ "files": [
18
+ "dist"
19
+ ],
20
+ "sideEffects": false,
21
+ "scripts": {
22
+ "build": "tsup",
23
+ "dev": "tsup --watch",
24
+ "lint": "tsc --noEmit",
25
+ "test": "cross-env NODE_ENV=test vitest run --coverage",
26
+ "test:watch": "cross-env NODE_ENV=test vitest",
27
+ "check": "npm run lint && npm run test && npm run build",
28
+ "prepublishOnly": "npm run check"
29
+ },
30
+ "peerDependencies": {
31
+ "react": "^18.2.0 || ^19.0.0",
32
+ "react-dom": "^18.2.0 || ^19.0.0"
33
+ },
34
+ "devDependencies": {
35
+ "@testing-library/jest-dom": "^6.6.3",
36
+ "@testing-library/react": "^16.3.0",
37
+ "@testing-library/user-event": "^14.6.1",
38
+ "@types/react": "18.3.12",
39
+ "@types/react-dom": "18.3.1",
40
+ "@vitest/coverage-v8": "^3.2.4",
41
+ "cross-env": "^7.0.3",
42
+ "jsdom": "^26.1.0",
43
+ "react": "18.3.1",
44
+ "react-dom": "18.3.1",
45
+ "tsup": "^8.5.0",
46
+ "typescript": "^5.8.3",
47
+ "vitest": "^3.2.4"
48
+ },
49
+ "repository": {
50
+ "type": "git",
51
+ "url": "git+https://github.com/brujim/react-best-carroussel.git"
52
+ },
53
+ "homepage": "https://github.com/brujim/react-best-carroussel#readme",
54
+ "bugs": {
55
+ "url": "https://github.com/brujim/react-best-carroussel/issues"
56
+ },
57
+ "keywords": [
58
+ "react",
59
+ "carousel",
60
+ "carrousel",
61
+ "image-carousel",
62
+ "slider",
63
+ "gallery",
64
+ "typescript",
65
+ "accessible"
66
+ ],
67
+ "publishConfig": {
68
+ "access": "public"
69
+ }
70
+ }