@haventmet/carousel 1.0.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/.mcp.json +8 -0
- package/CLAUDE.md +177 -0
- package/LICENSE +21 -0
- package/README.md +76 -0
- package/biome.json +174 -0
- package/package.json +33 -0
- package/packages/alpine/index.html +50 -0
- package/packages/alpine/package.json +70 -0
- package/packages/alpine/src/index.ts +46 -0
- package/packages/alpine/style.css +76 -0
- package/packages/alpine/tsconfig.json +28 -0
- package/packages/alpine/vite.config.ts +28 -0
- package/packages/core/README.md +69 -0
- package/packages/core/package.json +45 -0
- package/packages/core/src/carousel.ts +842 -0
- package/packages/core/src/index.ts +2 -0
- package/packages/core/src/style.css +46 -0
- package/packages/core/vite.config.js +14 -0
- package/packages/react/.vite/deps/_metadata.json +25 -0
- package/packages/react/.vite/deps/chunk-7CQ73C3Q.js +1908 -0
- package/packages/react/.vite/deps/chunk-7CQ73C3Q.js.map +7 -0
- package/packages/react/.vite/deps/package.json +3 -0
- package/packages/react/.vite/deps/react-dom_client.js +21712 -0
- package/packages/react/.vite/deps/react-dom_client.js.map +7 -0
- package/packages/react/.vite/deps/react.js +4 -0
- package/packages/react/.vite/deps/react.js.map +7 -0
- package/packages/react/README.md +75 -0
- package/packages/react/index.html +12 -0
- package/packages/react/package.json +74 -0
- package/packages/react/src/App.tsx +38 -0
- package/packages/react/src/BlossomCarousel.tsx +77 -0
- package/packages/react/src/index.ts +2 -0
- package/packages/react/src/main.jsx +6 -0
- package/packages/react/src/style.css +50 -0
- package/packages/react/tsconfig.json +17 -0
- package/packages/react/vite.config.js +27 -0
- package/packages/svelte/README.md +58 -0
- package/packages/svelte/index.html +12 -0
- package/packages/svelte/package.json +71 -0
- package/packages/svelte/src/App.svelte +12 -0
- package/packages/svelte/src/BlossomCarousel.svelte +39 -0
- package/packages/svelte/src/BlossomCarousel.svelte.d.ts +8 -0
- package/packages/svelte/src/index.ts +2 -0
- package/packages/svelte/src/main.js +10 -0
- package/packages/svelte/src/style.css +52 -0
- package/packages/svelte/tsconfig.json +16 -0
- package/packages/svelte/vite.config.js +45 -0
- package/packages/vue/README.md +70 -0
- package/packages/vue/index.html +12 -0
- package/packages/vue/package.json +69 -0
- package/packages/vue/src/App.vue +82 -0
- package/packages/vue/src/BlossomCarousel.vue +52 -0
- package/packages/vue/src/index.ts +2 -0
- package/packages/vue/src/main.js +5 -0
- package/packages/vue/src/style.css +18 -0
- package/packages/vue/vite.config.js +25 -0
- package/packages/web/README.md +44 -0
- package/packages/web/index.html +29 -0
- package/packages/web/package.json +65 -0
- package/packages/web/src/index.ts +27 -0
- package/packages/web/src/style.css +53 -0
- package/packages/web/style.css +54 -0
- package/packages/web/tsconfig.json +18 -0
- package/packages/web/vite.config.ts +23 -0
- package/pnpm-workspace.yaml +8 -0
- package/public/cover.jpg +0 -0
- package/turbo.json +12 -0
package/.mcp.json
ADDED
package/CLAUDE.md
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## Project Overview
|
|
6
|
+
|
|
7
|
+
Blossom Carousel is a native-scroll-first carousel component library with physics-based drag support. It provides a core implementation and framework-specific wrappers for React, Vue, Svelte, Alpine.js, and Web Components.
|
|
8
|
+
|
|
9
|
+
**Key characteristics:**
|
|
10
|
+
- Native scrolling with CSS scroll-snap support
|
|
11
|
+
- Custom drag physics for fine pointer devices (mouse/trackpad) only - 0kb on touch devices
|
|
12
|
+
- Conditional loading - physics engine only loads on devices with `(hover: hover) and (pointer: fine)`
|
|
13
|
+
- Physics-based momentum scrolling with configurable friction and damping
|
|
14
|
+
- Optional repeat/infinite scroll mode (disabled by default on touch devices)
|
|
15
|
+
- Rubber banding overscroll effects
|
|
16
|
+
- Programmatic navigation with `prev()` and `next()` methods
|
|
17
|
+
- Index tracking with custom change events
|
|
18
|
+
|
|
19
|
+
## Repository Structure
|
|
20
|
+
|
|
21
|
+
This is a **pnpm workspace monorepo** with the following packages:
|
|
22
|
+
|
|
23
|
+
- `packages/core` - Core TypeScript library with Vite build ([blossom-carousel.ts](packages/core/src/blossom-carousel.ts:1))
|
|
24
|
+
- `packages/react` - React 19 wrapper component
|
|
25
|
+
- `packages/vue` - Vue 3 wrapper component
|
|
26
|
+
- `packages/svelte` - Svelte wrapper component
|
|
27
|
+
- `packages/alpine` - Alpine.js plugin wrapper
|
|
28
|
+
- `packages/web` - Web Component implementation
|
|
29
|
+
- `packages/dev` - Development sandbox (Vue-based)
|
|
30
|
+
|
|
31
|
+
All framework wrappers depend on `@numbered/carousel/core` and provide thin component layers that initialize/destroy the core Blossom instance.
|
|
32
|
+
|
|
33
|
+
## Development Commands
|
|
34
|
+
|
|
35
|
+
Use pnpm for all package management:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
# Install dependencies
|
|
39
|
+
pnpm install
|
|
40
|
+
|
|
41
|
+
# Development servers (runs specific package dev server)
|
|
42
|
+
pnpm dev # Run dev sandbox (packages/dev)
|
|
43
|
+
pnpm dev:react # Run React package dev server
|
|
44
|
+
pnpm dev:vue # Run Vue package dev server
|
|
45
|
+
pnpm dev:svelte # Run Svelte package dev server
|
|
46
|
+
pnpm dev:alpine # Run Alpine.js package dev server
|
|
47
|
+
pnpm dev:web # Run Web Component package dev server
|
|
48
|
+
|
|
49
|
+
# Build individual packages
|
|
50
|
+
cd packages/core && pnpm build
|
|
51
|
+
cd packages/react && pnpm build
|
|
52
|
+
# etc...
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Core Architecture
|
|
56
|
+
|
|
57
|
+
### Main Entry Point
|
|
58
|
+
[packages/core/src/blossom-carousel.ts](packages/core/src/blossom-carousel.ts:15) exports the `Blossom` factory function which accepts:
|
|
59
|
+
- `scroller: HTMLElement` - The scrollable container element
|
|
60
|
+
- `options: CarouselOptions` - Configuration options:
|
|
61
|
+
- `repeat?: boolean` - Enable infinite scroll mode (automatically disabled on touch devices)
|
|
62
|
+
- `load?: 'always'` - Force physics loading on all devices including touch
|
|
63
|
+
|
|
64
|
+
Returns an object with:
|
|
65
|
+
- `init()` - Initialize the carousel
|
|
66
|
+
- `destroy()` - Clean up and remove event listeners
|
|
67
|
+
- `prev()` - Navigate to previous slide
|
|
68
|
+
- `next()` - Navigate to next slide
|
|
69
|
+
- `snap` - Boolean indicating if snap is enabled
|
|
70
|
+
- `hasOverflow` - Proxy object tracking overflow state
|
|
71
|
+
|
|
72
|
+
### Physics System
|
|
73
|
+
The carousel uses a custom physics ticker ([tick function](packages/core/src/blossom-carousel.ts:423)) with:
|
|
74
|
+
- **Friction** (`FRICTION = 0.72`) - Applied to velocity each frame
|
|
75
|
+
- **Damping** (`DAMPING = 0.12`) - Smooth interpolation using exponential decay via `damp()` function
|
|
76
|
+
- **Velocity projection** - Predicts final resting position for snap point selection
|
|
77
|
+
- **Rubber banding** - 0.2x resistance when dragging beyond edges
|
|
78
|
+
|
|
79
|
+
### Key Systems
|
|
80
|
+
1. **Conditional loading** - Physics engine only loads on devices with `(hover: hover) and (pointer: fine)` media query, touch devices use native CSS scroll-snap
|
|
81
|
+
2. **Pointer interaction** - Only activates on fine pointer devices (mouse/trackpad)
|
|
82
|
+
3. **Scroll interception** - Patches `scrollTo`, `scrollBy`, and `Element.prototype.scrollIntoView` to disable physics during programmatic scrolling
|
|
83
|
+
4. **Snap point calculation** - Traverses DOM to find elements with `scroll-snap-align`, computes their positions relative to scroll container
|
|
84
|
+
5. **Virtual snap points** - Improved slide positioning system for more precise alignment
|
|
85
|
+
6. **Repeat mode** - Translates slides to create infinite scroll illusion, resets scroll position when reaching edges (disabled on touch devices)
|
|
86
|
+
7. **Index tracking** - Dispatches custom `change` events with `detail.index` when the active slide changes
|
|
87
|
+
8. **Navigation methods** - `prev()` and `next()` methods for programmatic slide navigation
|
|
88
|
+
|
|
89
|
+
### Important State Variables
|
|
90
|
+
- `virtualScroll` - The smoothed scroll position updated by physics ticker
|
|
91
|
+
- `target` - The target scroll position (updated by drag or velocity)
|
|
92
|
+
- `velocity` - Current momentum vector
|
|
93
|
+
- `isDragging` - Pointer down state
|
|
94
|
+
- `isTicking` - Whether the animation frame loop is active
|
|
95
|
+
- `hasOverflow` - Proxy that adds/removes event listeners when overflow changes
|
|
96
|
+
|
|
97
|
+
## Framework Wrappers
|
|
98
|
+
|
|
99
|
+
All wrappers follow the same pattern:
|
|
100
|
+
1. Accept children/slots and forward props to rendered element
|
|
101
|
+
2. Create a ref to the DOM element
|
|
102
|
+
3. Call `Blossom(element, options)` on mount
|
|
103
|
+
4. Call `blossom.init()` to start
|
|
104
|
+
5. Call `blossom.destroy()` on unmount
|
|
105
|
+
|
|
106
|
+
Example: [packages/react/src/BlossomCarousel.tsx](packages/react/src/BlossomCarousel.tsx:32)
|
|
107
|
+
|
|
108
|
+
### React Wrapper
|
|
109
|
+
- React 19 compatible
|
|
110
|
+
- Uses custom `onChange` event pattern instead of traditional props
|
|
111
|
+
- Dispatches custom `change` events with index information
|
|
112
|
+
- Example: `<BlossomCarousel repeat={true} />`
|
|
113
|
+
|
|
114
|
+
### Alpine.js Wrapper
|
|
115
|
+
- Registered as an Alpine.js plugin via `Alpine.data('carousel', ...)`
|
|
116
|
+
- Provides reactive `currentIndex` property
|
|
117
|
+
- Exposes `prev()` and `next()` methods in the component scope
|
|
118
|
+
- Listens for custom `change` events to update reactive state
|
|
119
|
+
- Example: `<div x-data="carousel({ load: 'always' })">...</div>`
|
|
120
|
+
|
|
121
|
+
### Custom Events
|
|
122
|
+
All framework wrappers dispatch a standard `change` event on the carousel element:
|
|
123
|
+
```javascript
|
|
124
|
+
// Event structure
|
|
125
|
+
new CustomEvent('change', {
|
|
126
|
+
detail: { index: number }
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
// Listening for changes
|
|
130
|
+
carouselElement.addEventListener('change', (e) => {
|
|
131
|
+
console.log('Current index:', e.detail.index)
|
|
132
|
+
})
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Mobile & Performance Optimization
|
|
136
|
+
|
|
137
|
+
### Conditional Loading Strategy
|
|
138
|
+
Blossom Carousel uses a smart loading strategy to optimize for different device types:
|
|
139
|
+
|
|
140
|
+
**Desktop/Laptop (fine pointer devices)**
|
|
141
|
+
- Full physics engine loads (~5-10kb)
|
|
142
|
+
- Custom drag interactions with momentum
|
|
143
|
+
- Physics-based scrolling with friction and damping
|
|
144
|
+
- Rubber banding effects
|
|
145
|
+
|
|
146
|
+
**Mobile/Touch devices**
|
|
147
|
+
- Physics engine does NOT load (0kb overhead)
|
|
148
|
+
- Native CSS `scroll-snap-type` handles snapping
|
|
149
|
+
- Native touch scrolling for optimal performance
|
|
150
|
+
- No repeat mode (infinite scroll disabled)
|
|
151
|
+
|
|
152
|
+
**Detection**
|
|
153
|
+
```javascript
|
|
154
|
+
const hasMouse = window.matchMedia('(hover: hover) and (pointer: fine)').matches
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
**Force loading on all devices**
|
|
158
|
+
```javascript
|
|
159
|
+
Blossom(element, { load: 'always' })
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Performance Features
|
|
163
|
+
- **Passive event listeners** - Scroll performance optimization
|
|
164
|
+
- **ResizeObserver** - Efficient dimension updates
|
|
165
|
+
- **MutationObserver** - Automatic recalculation when slides change
|
|
166
|
+
- **RequestAnimationFrame** - Smooth 60fps animations
|
|
167
|
+
- **Scroll interception** - Prevents physics conflicts during programmatic scrolling
|
|
168
|
+
|
|
169
|
+
## Publishing
|
|
170
|
+
|
|
171
|
+
Packages use `publishConfig.access: "public"` and are built to `dist/` directories with:
|
|
172
|
+
- UMD build: `dist/blossom-carousel-{package}.umd.cjs`
|
|
173
|
+
- ESM build: `dist/blossom-carousel-{package}.js`
|
|
174
|
+
- Types: `dist/index.d.ts`
|
|
175
|
+
- Styles: `dist/blossom-carousel-{package}.css`
|
|
176
|
+
|
|
177
|
+
Vite is used for all builds with `vite-plugin-dts` for TypeScript declarations.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Jesper Vos
|
|
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,76 @@
|
|
|
1
|
+

|
|
2
|
+
|
|
3
|
+
# Blossom Carousel
|
|
4
|
+
|
|
5
|
+
<p>
|
|
6
|
+
<a href="https://www.npmjs.com/package/@numbered/carousel/core"><img src="https://img.shields.io/npm/v/@numbered/carousel/core.svg?color=%23c1a8e2"></a>
|
|
7
|
+
<a href="https://bundlephobia.com/package/@numbered/carousel/core"><img src="https://img.shields.io/bundlephobia/minzip/@numbered/carousel/core?color=%238ab4f8&label=gzip%20size"></a>
|
|
8
|
+
</p>
|
|
9
|
+
|
|
10
|
+
The native-first carousel enhanced with drag support.
|
|
11
|
+
|
|
12
|
+
➥ [www.blossom-carousel.com](http://www.blossom-carousel.com)
|
|
13
|
+
|
|
14
|
+
## Why Blossom?
|
|
15
|
+
|
|
16
|
+
<ul style="list-style: none; padding: 0;">
|
|
17
|
+
<li>🥇 <strong>Native scrolling</strong>: Full performance and accessibility.</li>
|
|
18
|
+
<li>🚀 <strong>Dragging</strong>: Custom physics-based dragging for all pointer types.</li>
|
|
19
|
+
<li>✨ <strong>No abstraction</strong>: use all native web API's.</li>
|
|
20
|
+
<li>💡 <strong>Configure with CSS</strong>: Works with native scroll-snap, position sticky and scroll-driven animations.</li>
|
|
21
|
+
<li>🪶 <strong>0kb on touch devices</strong>: Blossom only loads when a fine pointer device is detected.</li>
|
|
22
|
+
<li>🧱 <strong>Works with major frameworks</strong>: Components for React, Vue, Svelte and Web Components.</li>
|
|
23
|
+
</ul>
|
|
24
|
+
|
|
25
|
+
## Installation
|
|
26
|
+
|
|
27
|
+
### React
|
|
28
|
+
|
|
29
|
+
[React Docs](./packages/react)
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npm install @numbered/carousel/react
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Vue
|
|
36
|
+
|
|
37
|
+
[Vue Docs](./packages/vue)
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
npm install @numbered/carousel/vue
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Svelte
|
|
44
|
+
|
|
45
|
+
[Svelte Docs](./packages/svelte)
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
npm install @numbered/carousel/svelte
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Web Component
|
|
52
|
+
|
|
53
|
+
[Web Component Docs](./packages/web)
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
npm install @numbered/carousel/web
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Core
|
|
60
|
+
|
|
61
|
+
[Core Docs](./packages/core)
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
npm install @numbered/carousel/core
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Examples
|
|
68
|
+
|
|
69
|
+
- [Simple](https://www.blossom-carousel.com/docs/examples#simple)
|
|
70
|
+
- [Variable widths](https://www.blossom-carousel.com/docs/examples#variable-widths)
|
|
71
|
+
- [CSS Grid](https://www.blossom-carousel.com/docs/examples#css-grid)
|
|
72
|
+
- [Multiple rows](https://www.blossom-carousel.com/docs/examples#multiple-rows)
|
|
73
|
+
- [Snapping](https://www.blossom-carousel.com/docs/examples#snapping)
|
|
74
|
+
- [Grouping](https://www.blossom-carousel.com/docs/examples#grouping)
|
|
75
|
+
- [Sticky](https://www.blossom-carousel.com/docs/examples#sticky)
|
|
76
|
+
- [Coverflow](https://www.blossom-carousel.com/docs/examples#cover-flow)
|
package/biome.json
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://biomejs.dev/schemas/2.2.2/schema.json",
|
|
3
|
+
"vcs": {
|
|
4
|
+
"enabled": false,
|
|
5
|
+
"clientKind": "git",
|
|
6
|
+
"useIgnoreFile": false
|
|
7
|
+
},
|
|
8
|
+
"files": {
|
|
9
|
+
"ignoreUnknown": true,
|
|
10
|
+
"experimentalScannerIgnores": ["node_modules", ".next"],
|
|
11
|
+
"includes": [
|
|
12
|
+
"**/*.{js,jsx,ts,tsx,css,scss,json}",
|
|
13
|
+
"!**/node_modules/**/*",
|
|
14
|
+
"!**/dist"
|
|
15
|
+
]
|
|
16
|
+
},
|
|
17
|
+
"formatter": {
|
|
18
|
+
"enabled": true,
|
|
19
|
+
"indentStyle": "tab",
|
|
20
|
+
"indentWidth": 2,
|
|
21
|
+
"lineWidth": 150
|
|
22
|
+
},
|
|
23
|
+
"assist": { "actions": { "source": { "organizeImports": "on" } } },
|
|
24
|
+
"linter": {
|
|
25
|
+
"enabled": true,
|
|
26
|
+
"rules": {
|
|
27
|
+
"correctness": {
|
|
28
|
+
"useExhaustiveDependencies": "off",
|
|
29
|
+
"useHookAtTopLevel": "off",
|
|
30
|
+
"useUniqueElementIds": "off"
|
|
31
|
+
},
|
|
32
|
+
"complexity": {
|
|
33
|
+
"useOptionalChain": {
|
|
34
|
+
"level": "error",
|
|
35
|
+
"fix": "safe"
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"style": {
|
|
39
|
+
"noNonNullAssertion": "off",
|
|
40
|
+
"useTemplate": {
|
|
41
|
+
"level": "error",
|
|
42
|
+
"fix": "safe"
|
|
43
|
+
},
|
|
44
|
+
"noUnusedTemplateLiteral": {
|
|
45
|
+
"level": "error",
|
|
46
|
+
"fix": "safe"
|
|
47
|
+
},
|
|
48
|
+
"useNodejsImportProtocol": {
|
|
49
|
+
"level": "error",
|
|
50
|
+
"fix": "safe"
|
|
51
|
+
},
|
|
52
|
+
"noUselessElse": {
|
|
53
|
+
"level": "warn",
|
|
54
|
+
"fix": "safe"
|
|
55
|
+
},
|
|
56
|
+
"useAsConstAssertion": "error",
|
|
57
|
+
"useEnumInitializers": "error",
|
|
58
|
+
"useSelfClosingElements": "error",
|
|
59
|
+
"useSingleVarDeclarator": "error",
|
|
60
|
+
"useNumberNamespace": "error",
|
|
61
|
+
"noInferrableTypes": "error"
|
|
62
|
+
},
|
|
63
|
+
"suspicious": {
|
|
64
|
+
"noExplicitAny": "off",
|
|
65
|
+
"noImplicitAnyLet": "off",
|
|
66
|
+
"noArrayIndexKey": "off",
|
|
67
|
+
"noAssignInExpressions": "off"
|
|
68
|
+
},
|
|
69
|
+
"nursery": {
|
|
70
|
+
"useSortedClasses": {
|
|
71
|
+
"level": "error",
|
|
72
|
+
"fix": "safe",
|
|
73
|
+
"options": {}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
"includes": [
|
|
78
|
+
"**",
|
|
79
|
+
"!**/.turbo",
|
|
80
|
+
"!**/dist",
|
|
81
|
+
"!**/node_modules"
|
|
82
|
+
]
|
|
83
|
+
},
|
|
84
|
+
"javascript": {
|
|
85
|
+
"formatter": {
|
|
86
|
+
"quoteStyle": "single",
|
|
87
|
+
"semicolons": "asNeeded",
|
|
88
|
+
"bracketSameLine": true,
|
|
89
|
+
"jsxQuoteStyle": "single"
|
|
90
|
+
},
|
|
91
|
+
"globals": ["document", "navigator", "window"]
|
|
92
|
+
},
|
|
93
|
+
"json": {
|
|
94
|
+
"formatter": {
|
|
95
|
+
"trailingCommas": "none",
|
|
96
|
+
"lineWidth": 100
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
"overrides": [
|
|
100
|
+
{
|
|
101
|
+
"includes": ["**/*.json"],
|
|
102
|
+
"linter": {
|
|
103
|
+
"rules": {}
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
"includes": ["**/*.css"],
|
|
108
|
+
"linter": {
|
|
109
|
+
"rules": {
|
|
110
|
+
"suspicious": {
|
|
111
|
+
"noUnknownAtRules": "warn"
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
"includes": ["**/*.ts", "**/*.tsx", "**/*.mts", "**/*.cts"],
|
|
118
|
+
"linter": {
|
|
119
|
+
"rules": {
|
|
120
|
+
"performance": {
|
|
121
|
+
"noDelete": "off",
|
|
122
|
+
"noAccumulatingSpread": "off"
|
|
123
|
+
},
|
|
124
|
+
"a11y": {
|
|
125
|
+
"noAutofocus": "off",
|
|
126
|
+
"noSvgWithoutTitle": "off",
|
|
127
|
+
"useValidAriaRole": "info",
|
|
128
|
+
"useKeyWithClickEvents": "warn",
|
|
129
|
+
"useButtonType": "off",
|
|
130
|
+
"useSemanticElements": "info",
|
|
131
|
+
"noStaticElementInteractions": "info"
|
|
132
|
+
},
|
|
133
|
+
"complexity": {
|
|
134
|
+
"noForEach": "off",
|
|
135
|
+
"noArguments": "error"
|
|
136
|
+
},
|
|
137
|
+
"correctness": {
|
|
138
|
+
"noConstAssign": "off",
|
|
139
|
+
"noGlobalObjectCalls": "off",
|
|
140
|
+
"noInvalidBuiltinInstantiation": "off",
|
|
141
|
+
"noInvalidConstructorSuper": "off",
|
|
142
|
+
"noSetterReturn": "off",
|
|
143
|
+
"noUndeclaredVariables": "off",
|
|
144
|
+
"noUnreachable": "off",
|
|
145
|
+
"noUnusedImports": "error",
|
|
146
|
+
"noUnreachableSuper": "off"
|
|
147
|
+
},
|
|
148
|
+
"style": {
|
|
149
|
+
"noParameterAssign": "off",
|
|
150
|
+
"useConst": "error",
|
|
151
|
+
"useDefaultParameterLast": "off"
|
|
152
|
+
},
|
|
153
|
+
"security": {
|
|
154
|
+
"noDangerouslySetInnerHtml": "off"
|
|
155
|
+
},
|
|
156
|
+
"suspicious": {
|
|
157
|
+
"noMisleadingCharacterClass": "off",
|
|
158
|
+
"noShadowRestrictedNames": "off",
|
|
159
|
+
"noDuplicateClassMembers": "off",
|
|
160
|
+
"noDuplicateObjectKeys": "off",
|
|
161
|
+
"noDuplicateParameters": "off",
|
|
162
|
+
"noFunctionAssign": "off",
|
|
163
|
+
"noImportAssign": "off",
|
|
164
|
+
"noRedeclare": "off",
|
|
165
|
+
"noUnsafeNegation": "off",
|
|
166
|
+
"useGetterReturn": "off",
|
|
167
|
+
"noVar": "error",
|
|
168
|
+
"useIterableCallbackReturn": "warn"
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
]
|
|
174
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@haventmet/carousel",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"author": "Loris",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"description": "A native-scroll-first carousel component for the web.",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/loris-numbered/carousel.git"
|
|
11
|
+
},
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/loris-numbered/carousel/issues"
|
|
14
|
+
},
|
|
15
|
+
"main": "index.js",
|
|
16
|
+
"scripts": {
|
|
17
|
+
"test": "echo \"Error: no test specified\" && exit 1",
|
|
18
|
+
"dev": "pnpm --filter @haventmet/carousel-dev dev",
|
|
19
|
+
"dev:vue": "pnpm --filter @haventmet/carousel-vue dev",
|
|
20
|
+
"dev:react": "pnpm --filter @haventmet/carousel-react dev",
|
|
21
|
+
"dev:svelte": "pnpm --filter @haventmet/carousel-svelte dev",
|
|
22
|
+
"dev:web": "pnpm --filter @haventmet/carousel-web dev",
|
|
23
|
+
"dev:alpine": "pnpm --filter @haventmet/carousel-alpine dev"
|
|
24
|
+
},
|
|
25
|
+
"workspaces": [
|
|
26
|
+
"packages/*"
|
|
27
|
+
],
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"biome": "^0.3.3",
|
|
30
|
+
"turbo": "^2.5.0"
|
|
31
|
+
},
|
|
32
|
+
"packageManager": "pnpm@10.14.0+sha512.ad27a79641b49c3e481a16a805baa71817a04bbe06a38d17e60e2eaee83f6a146c6a688125f5792e48dd5ba30e7da52a5cda4c3992b9ccf333f9ce223af84748"
|
|
33
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
|
|
4
|
+
<head>
|
|
5
|
+
<meta charset="UTF-8" />
|
|
6
|
+
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
|
7
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
8
|
+
<title>Blossom Carousel - Alpine.js</title>
|
|
9
|
+
<link rel="stylesheet" href="./style.css" />
|
|
10
|
+
</head>
|
|
11
|
+
|
|
12
|
+
<body>
|
|
13
|
+
<div class="page">
|
|
14
|
+
<h1>Blossom Carousel - Alpine.js</h1>
|
|
15
|
+
|
|
16
|
+
<div x-data="carousel({ load: 'always' })" class="wrapper">
|
|
17
|
+
<p>Current slide: <span x-text="currentIndex + 1"></span></p>
|
|
18
|
+
|
|
19
|
+
<ul class="carousel" x-ref="carousel">
|
|
20
|
+
<li class="slide">1</li>
|
|
21
|
+
<li class="slide">2</li>
|
|
22
|
+
<li class="slide">3</li>
|
|
23
|
+
<li class="slide">4</li>
|
|
24
|
+
<li class="slide">5</li>
|
|
25
|
+
<li class="slide">6</li>
|
|
26
|
+
<li class="slide">7</li>
|
|
27
|
+
<li class="slide">8</li>
|
|
28
|
+
<li class="slide">9</li>
|
|
29
|
+
<li class="slide">10</li>
|
|
30
|
+
<li class="slide">11</li>
|
|
31
|
+
<li class="slide">12</li>
|
|
32
|
+
</ul>
|
|
33
|
+
|
|
34
|
+
<div class="controls">
|
|
35
|
+
<button @click="prev()">Previous</button>
|
|
36
|
+
<button @click="next()">Next</button>
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
|
|
41
|
+
<script type="module">
|
|
42
|
+
import Alpine from 'alpinejs'
|
|
43
|
+
import blossomCarousel from './src/index.ts'
|
|
44
|
+
|
|
45
|
+
Alpine.plugin(blossomCarousel)
|
|
46
|
+
Alpine.start()
|
|
47
|
+
</script>
|
|
48
|
+
</body>
|
|
49
|
+
|
|
50
|
+
</html>
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@haventmet/carousel-alpine",
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"author": "Loris",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"description": "A native-scroll-first carousel component for Alpine.js.",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/loris-numbered/carousel"
|
|
10
|
+
},
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://github.com/loris-numbered/carousel/issues"
|
|
13
|
+
},
|
|
14
|
+
"homepage": "https://www.blossom-carousel.com",
|
|
15
|
+
"keywords": [
|
|
16
|
+
"blossom",
|
|
17
|
+
"carousel",
|
|
18
|
+
"slider",
|
|
19
|
+
"slideshow",
|
|
20
|
+
"scroll",
|
|
21
|
+
"scroller",
|
|
22
|
+
"drag",
|
|
23
|
+
"dragging",
|
|
24
|
+
"js",
|
|
25
|
+
"javascript",
|
|
26
|
+
"typescript",
|
|
27
|
+
"web",
|
|
28
|
+
"html",
|
|
29
|
+
"css",
|
|
30
|
+
"alpine",
|
|
31
|
+
"alpinejs"
|
|
32
|
+
],
|
|
33
|
+
"type": "module",
|
|
34
|
+
"files": [
|
|
35
|
+
"dist"
|
|
36
|
+
],
|
|
37
|
+
"main": "dist/blossom-carousel-alpine.umd.cjs",
|
|
38
|
+
"module": "dist/blossom-carousel-alpine.js",
|
|
39
|
+
"types": "dist/index.d.ts",
|
|
40
|
+
"exports": {
|
|
41
|
+
".": {
|
|
42
|
+
"types": "./dist/index.d.ts",
|
|
43
|
+
"import": "./dist/blossom-carousel-alpine.js",
|
|
44
|
+
"require": "./dist/blossom-carousel-alpine.umd.cjs"
|
|
45
|
+
},
|
|
46
|
+
"./style.css": "./dist/blossom-carousel-alpine.css"
|
|
47
|
+
},
|
|
48
|
+
"publishConfig": {
|
|
49
|
+
"access": "public"
|
|
50
|
+
},
|
|
51
|
+
"scripts": {
|
|
52
|
+
"dev": "vite",
|
|
53
|
+
"build": "vite build",
|
|
54
|
+
"preview": "vite preview"
|
|
55
|
+
},
|
|
56
|
+
"peerDependencies": {
|
|
57
|
+
"alpinejs": "^3.0.0"
|
|
58
|
+
},
|
|
59
|
+
"devDependencies": {
|
|
60
|
+
"@types/alpinejs": "^3.13.11",
|
|
61
|
+
"@types/node": "^24.8.0",
|
|
62
|
+
"alpinejs": "^3.15.0",
|
|
63
|
+
"typescript": "^5.9.3",
|
|
64
|
+
"vite": "^7.1.10",
|
|
65
|
+
"vite-plugin-dts": "^4.5.4"
|
|
66
|
+
},
|
|
67
|
+
"dependencies": {
|
|
68
|
+
"@haventmet/carousel": "workspace:*"
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { Blossom, type CarouselOptions } from '@numbered/carousel'
|
|
2
|
+
import type { Alpine } from 'alpinejs'
|
|
3
|
+
|
|
4
|
+
export default function blossomCarousel(Alpine: Alpine) {
|
|
5
|
+
Alpine.data('carousel', (options: CarouselOptions & { load?: 'always' } = {}) => ({
|
|
6
|
+
blossom: null as ReturnType<typeof Blossom> | null,
|
|
7
|
+
currentIndex: 0,
|
|
8
|
+
|
|
9
|
+
init() {
|
|
10
|
+
// Find the carousel element (first child with class 'carousel')
|
|
11
|
+
const carouselEl = this.$refs.carousel || this.$el
|
|
12
|
+
|
|
13
|
+
// Set the blossom-carousel attribute
|
|
14
|
+
carouselEl.setAttribute('blossom-carousel', 'true')
|
|
15
|
+
|
|
16
|
+
// Check if should load based on media query
|
|
17
|
+
const hasMouse = window.matchMedia('(hover: hover) and (pointer: fine)').matches
|
|
18
|
+
|
|
19
|
+
// Don't load if the user has no mouse (unless load: 'always')
|
|
20
|
+
if (!hasMouse && options.load !== 'always') {
|
|
21
|
+
return
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
this.blossom = Blossom(carouselEl, options)
|
|
25
|
+
this.blossom.init()
|
|
26
|
+
|
|
27
|
+
// Listen for change events
|
|
28
|
+
carouselEl.addEventListener('change', (e: Event) => {
|
|
29
|
+
const customEvent = e as CustomEvent<{ index: number }>
|
|
30
|
+
this.currentIndex = customEvent.detail.index
|
|
31
|
+
})
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
destroy() {
|
|
35
|
+
this.blossom?.destroy()
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
next() {
|
|
39
|
+
this.blossom?.next()
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
prev() {
|
|
43
|
+
this.blossom?.prev()
|
|
44
|
+
},
|
|
45
|
+
}))
|
|
46
|
+
}
|