diff --git a/.babelrc b/.babelrc deleted file mode 100644 index 1320b9a..0000000 --- a/.babelrc +++ /dev/null @@ -1,3 +0,0 @@ -{ - "presets": ["@babel/preset-env"] -} diff --git a/.eslintrc.cjs b/.eslintrc.cjs new file mode 100644 index 0000000..e178e9d --- /dev/null +++ b/.eslintrc.cjs @@ -0,0 +1,23 @@ +module.exports = { + root: true, + parser: '@typescript-eslint/parser', + plugins: ['@typescript-eslint'], + extends: [ + 'eslint:recommended', + 'plugin:@typescript-eslint/recommended', + ], + parserOptions: { + ecmaVersion: 2020, + sourceType: 'module', + }, + env: { + browser: true, + es2020: true, + node: true, + }, + rules: { + '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }], + '@typescript-eslint/no-explicit-any': 'warn', + }, + ignorePatterns: ['dist/', 'dist-demo/', 'node_modules/', 'coverage/'], +}; diff --git a/.github/workflows/deploy-pages.yml b/.github/workflows/deploy-pages.yml new file mode 100644 index 0000000..919100f --- /dev/null +++ b/.github/workflows/deploy-pages.yml @@ -0,0 +1,39 @@ +name: Deploy to GitHub Pages + +on: + push: + branches: [master] + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: pages + cancel-in-progress: true + +jobs: + build-and-deploy: + runs-on: ubuntu-latest + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + + - run: npm ci + - run: npm run build:demo + + - uses: actions/configure-pages@v5 + - uses: actions/upload-pages-artifact@v3 + with: + path: dist-demo + + - id: deployment + uses: actions/deploy-pages@v4 diff --git a/.gitignore b/.gitignore index a56a7ef..1ecdec6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,40 @@ -node_modules +# dependencies +/node_modules +/.pnp +.pnp.js +# build output — only CDN bundles are tracked +/dist/* +!/dist/js-cloudimage-carousel.min.js +!/dist/js-cloudimage-carousel.min.js.map +/dist-demo + +# testing +/coverage + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# local env files +.env*.local + +# typescript +*.tsbuildinfo + +# IDE +.idea +/.idea +.vscode/ + +# husky +.husky/ + +# claude +.claude +CLAUDE.md diff --git a/.npmignore b/.npmignore deleted file mode 100644 index 90bba8f..0000000 --- a/.npmignore +++ /dev/null @@ -1,7 +0,0 @@ -# .npmignore -src -examples -build -dist -.babelrc -.gitignore \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..a903283 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,83 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +## [1.0.2] - 2026-03-20 + +### Fixed + +- **Type declarations path** — `package.json` types field now points to correct `dist/index.d.ts` (was pointing to non-existent `dist/types/`) +- **UMD global** — `window.CloudImageCarousel` is now the class directly, not a namespace object (CDN ` + ``` -The carousel is exposed as a UMD module, so it works with: +## Quick Start -- Browser globals (`window.CloudImageCarousel`) -- AMD modules -- CommonJS/Node.js modules +### JavaScript API -## Basic Usage +```js +import { CloudImageCarousel } from 'js-cloudimage-carousel' -```javascript -const carousel = new CloudImageCarousel('#carousel', { - // your options here - images: ['path/to/image1.jpg', 'path/to/image2.jpg'], +const carousel = new CloudImageCarousel('#my-carousel', { + images: ['photo1.jpg', 'photo2.jpg', 'photo3.jpg'], + theme: 'light', + showBullets: true, + transitionEffect: 'slide', }) -// initialize carousel carousel.init() ``` -## Available Options +### HTML Data-Attributes -### `autoplay` (boolean) - -- **Default**: `false` -- **Description**: Enables automatic slideshow of images -- **Example**: - -```javascript -{ - autoplay: true -} -``` - -### `autoplayInterval` (number) - -- **Default**: `3000` (3 seconds) -- **Description**: Sets the time interval (in milliseconds) between slides when autoplay is enabled -- **Example**: - -```javascript -{ - autoplayInterval: 5000 // Changes slide every 5 seconds -} -``` - -### `cycle` (boolean) - -- **Default**: `true` -- **Description**: Determines if the carousel should loop back to the first image after reaching the last one -- **Example**: - -```javascript -{ - cycle: false // Stops at the last image -} +```html +
+ + ``` -### `showFilenames` (boolean) +## API Reference -- **Default**: `false` -- **Description**: Shows/hides the filename caption below each image. Filenames are automatically extracted from the image URL -- **Example**: +### Constructor -```javascript -{ - showFilenames: true // Displays image filenames -} +```ts +new CloudImageCarousel(element: HTMLElement | string, config?: Partial) ``` -### `showThumbnails` (boolean) - -- **Default**: `true` -- **Description**: Shows/hides the thumbnail navigation bar at the bottom of the carousel -- **Example**: - -```javascript -{ - showThumbnails: false // Hides thumbnail navigation -} +### Config + +| Option | Type | Default | Description | +| ------------------ | --------------------------------------- | ---------- | ----------------------------------------------- | +| `images` | `ImageSource[]` | `[]` | Array of image URLs or `{ src, alt }` objects | +| `autoplay` | `boolean` | `false` | Enable automatic slide advancement | +| `autoplayInterval` | `number` | `3000` | Autoplay interval in ms (min 100) | +| `cycle` | `boolean` | `true` | Loop from last slide back to first | +| `showFilenames` | `boolean` | `false` | Show filename overlay on each slide | +| `showThumbnails` | `boolean` | `true` | Show thumbnail strip below the carousel | +| `showBullets` | `boolean` | `false` | Show bullet indicators | +| `showControls` | `boolean` | `true` | Show navigation controls (prev/next/fullscreen) | +| `controlsPosition` | `'center' \| 'bottom'` | `'center'` | Position of navigation arrows | +| `theme` | `'light' \| 'dark'` | `'light'` | Color theme | +| `transitionEffect` | `'slide' \| 'fade' \| 'zoom' \| 'flip'` | `'fade'` | Slide transition effect | +| `zoomMin` | `number` | `1` | Minimum zoom level | +| `zoomMax` | `number` | `4` | Maximum zoom level | +| `zoomStep` | `number` | `0.3` | Zoom step increment | +| `onSlideChange` | `(index: number) => void` | — | Callback fired after a slide change | +| `onError` | `(src: string, index: number) => void` | — | Callback fired when an image fails to load | +| `cloudimage` | `CloudimageConfig` | — | Cloudimage CDN config | + +### CloudimageConfig + +| Field | Type | Default | Description | +| ------------- | -------- | --------------- | ---------------------------------------------- | +| `token` | `string` | — | Cloudimage customer token (required) | +| `apiVersion` | `string` | `'v7'` | API version | +| `domain` | `string` | `'cloudimg.io'` | Custom domain | +| `limitFactor` | `number` | `100` | Round widths to nearest N pixels | +| `params` | `string` | — | Custom URL params (e.g. `'q=80&org_if_sml=1'`) | + +### Instance Methods + +```ts +carousel.init(): void +carousel.next(): void +carousel.prev(): void +carousel.goToSlide(index: number): void +carousel.zoomIn(): void +carousel.zoomOut(): void +carousel.resetZoom(): void +carousel.toggleFullscreen(): void +carousel.startAutoplay(): void +carousel.stopAutoplay(): void +carousel.pauseAutoplay(): void +carousel.resumeAutoplay(): void +carousel.setTheme(theme: 'light' | 'dark'): void +carousel.loadImages(sources: ImageSource[]): void +carousel.destroy(): void ``` -### `showControls` (boolean) +### Static Methods -- **Default**: `true` -- **Description**: Shows/hides the control buttons (previous/next navigation, zoom controls, fullscreen toggle) -- **Example**: - -```javascript -{ - showControls: false // Hides all control buttons -} +```ts +CloudImageCarousel.autoInit(root?: HTMLElement | Document): CloudImageCarousel[] ``` -### `showBullets` (boolean) - -- **Default**: `false` -- **Description**: Shows/hides bullet navigation indicators below the carousel -- **Example**: - -```javascript -{ - showBullets: true // Shows bullet navigation +## React Usage + +```tsx +import { CloudImageCarouselViewer, useCloudImageCarousel } from 'js-cloudimage-carousel/react' + +// Component +function Gallery() { + return ( + console.log('Slide:', index)} + /> + ) } -``` - -### `transitionEffect` (string) - -- **Default**: `'fade'` -- **Options**: `'fade'`, `'slide'` -- **Description**: Sets the transition effect when changing slides -- **Example**: -```javascript -{ - transitionEffect: 'fade' // Uses fade transition between slides +// Hook +function Gallery() { + const { containerRef, instance } = useCloudImageCarousel({ + images: ['photo1.jpg', 'photo2.jpg'], + controlsPosition: 'bottom', + }) + + return ( + <> +
+ + + ) } -``` - -### `thumbnailFitMode` (string) - -- **Default**: `'crop-fit'` -- **Options**: `'crop-fit'`, `'fit'` -- **Description**: Controls how thumbnail images are fitted within their containers -- **Example**: -```javascript -{ - thumbnailFitMode: 'fit' // Makes thumbnails fit within their container without cropping +// Ref API +function Gallery() { + const ref = useRef(null) + + return ( + <> + + + + + + ) } ``` -### `thumbnailAlignment` (string) +## Theming -- **Default**: `'space-evenly'` -- **Options**: `'left'`, `'center'`, `'right'`, `'space-evenly'`, `'space-between'` -- **Description**: Controls the horizontal alignment of thumbnails in the thumbnail container -- **Example**: +All visuals are customizable via CSS variables: -```javascript -{ - thumbnailAlignment: 'space-between' // Distributes thumbnails with equal space between them +```css +.my-carousel .ci-carousel { + --ci-carousel-bg: #0f172a; + --ci-carousel-btn-bg: rgba(59, 130, 246, 0.85); + --ci-carousel-btn-color: #ffffff; + --ci-carousel-btn-hover-bg: rgba(59, 130, 246, 1); + --ci-carousel-bullet-active-bg: #3b82f6; + --ci-carousel-thumbnail-active-border: 0 0 0 2px #3b82f6; + --ci-carousel-thumbnails-bg: #0f172a; } ``` -## Complete Example - -Here's an example showing all options with their default values: - -```html - -``` - -```javascript -const carousel = new CloudImageCarousel('#my-carousel', { - autoplay: false, - autoplayInterval: 3000, - cycle: true, - showFilenames: false, - showThumbnails: true, - showControls: true, - showBullets: false, - transitionEffect: 'fade', - thumbnailFitMode: 'crop-fit', - thumbnailAlignment: 'space-evenly', - images: ['path/to/image1.jpg', 'path/to/image2.jpg', 'path/to/image3.jpg'], +Set `theme: 'dark'` for the built-in dark theme. + +## Accessibility + +- All interactive elements are ` + + + ); +} +``` + +### 7.4 Ref API + +The `` component forwards a ref exposing instance methods: + +```tsx +import { useRef } from 'react'; +import { CloudImageCarouselViewer, CloudImageCarouselViewerRef } from 'js-cloudimage-carousel/react'; + +function Gallery() { + const ref = useRef(null); + + return ( + <> + + + + + + ); +} +``` + +### 7.5 SSR Safety + +The React wrapper is SSR-safe: + +- The vanilla core is instantiated inside `useEffect` (client-only) +- No `window`, `document`, or `navigator` access during server rendering +- The component renders an empty container `
` on the server; hydration attaches the carousel + +--- + +## 8. Accessibility + +### 8.1 WCAG 2.1 AA Compliance + +The library targets WCAG 2.1 Level AA conformance across all interactive elements. + +### 8.2 Keyboard Navigation + +| Key | Action | +|---|---| +| `ArrowLeft` | Navigate to previous slide | +| `ArrowRight` | Navigate to next slide | +| `+` / `=` | Zoom in | +| `-` | Zoom out | +| `0` | Reset zoom | +| `F` | Toggle fullscreen | +| `Escape` | Reset zoom; exit fullscreen | +| `Tab` | Navigate between interactive elements | +| `Shift + Tab` | Navigate backwards between interactive elements | +| `Enter` / `Space` | Activate focused button (thumbnail, bullet, control) | + +### 8.3 ARIA Attributes + +**Carousel container:** + +```html +