@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.
Files changed (67) hide show
  1. package/.mcp.json +8 -0
  2. package/CLAUDE.md +177 -0
  3. package/LICENSE +21 -0
  4. package/README.md +76 -0
  5. package/biome.json +174 -0
  6. package/package.json +33 -0
  7. package/packages/alpine/index.html +50 -0
  8. package/packages/alpine/package.json +70 -0
  9. package/packages/alpine/src/index.ts +46 -0
  10. package/packages/alpine/style.css +76 -0
  11. package/packages/alpine/tsconfig.json +28 -0
  12. package/packages/alpine/vite.config.ts +28 -0
  13. package/packages/core/README.md +69 -0
  14. package/packages/core/package.json +45 -0
  15. package/packages/core/src/carousel.ts +842 -0
  16. package/packages/core/src/index.ts +2 -0
  17. package/packages/core/src/style.css +46 -0
  18. package/packages/core/vite.config.js +14 -0
  19. package/packages/react/.vite/deps/_metadata.json +25 -0
  20. package/packages/react/.vite/deps/chunk-7CQ73C3Q.js +1908 -0
  21. package/packages/react/.vite/deps/chunk-7CQ73C3Q.js.map +7 -0
  22. package/packages/react/.vite/deps/package.json +3 -0
  23. package/packages/react/.vite/deps/react-dom_client.js +21712 -0
  24. package/packages/react/.vite/deps/react-dom_client.js.map +7 -0
  25. package/packages/react/.vite/deps/react.js +4 -0
  26. package/packages/react/.vite/deps/react.js.map +7 -0
  27. package/packages/react/README.md +75 -0
  28. package/packages/react/index.html +12 -0
  29. package/packages/react/package.json +74 -0
  30. package/packages/react/src/App.tsx +38 -0
  31. package/packages/react/src/BlossomCarousel.tsx +77 -0
  32. package/packages/react/src/index.ts +2 -0
  33. package/packages/react/src/main.jsx +6 -0
  34. package/packages/react/src/style.css +50 -0
  35. package/packages/react/tsconfig.json +17 -0
  36. package/packages/react/vite.config.js +27 -0
  37. package/packages/svelte/README.md +58 -0
  38. package/packages/svelte/index.html +12 -0
  39. package/packages/svelte/package.json +71 -0
  40. package/packages/svelte/src/App.svelte +12 -0
  41. package/packages/svelte/src/BlossomCarousel.svelte +39 -0
  42. package/packages/svelte/src/BlossomCarousel.svelte.d.ts +8 -0
  43. package/packages/svelte/src/index.ts +2 -0
  44. package/packages/svelte/src/main.js +10 -0
  45. package/packages/svelte/src/style.css +52 -0
  46. package/packages/svelte/tsconfig.json +16 -0
  47. package/packages/svelte/vite.config.js +45 -0
  48. package/packages/vue/README.md +70 -0
  49. package/packages/vue/index.html +12 -0
  50. package/packages/vue/package.json +69 -0
  51. package/packages/vue/src/App.vue +82 -0
  52. package/packages/vue/src/BlossomCarousel.vue +52 -0
  53. package/packages/vue/src/index.ts +2 -0
  54. package/packages/vue/src/main.js +5 -0
  55. package/packages/vue/src/style.css +18 -0
  56. package/packages/vue/vite.config.js +25 -0
  57. package/packages/web/README.md +44 -0
  58. package/packages/web/index.html +29 -0
  59. package/packages/web/package.json +65 -0
  60. package/packages/web/src/index.ts +27 -0
  61. package/packages/web/src/style.css +53 -0
  62. package/packages/web/style.css +54 -0
  63. package/packages/web/tsconfig.json +18 -0
  64. package/packages/web/vite.config.ts +23 -0
  65. package/pnpm-workspace.yaml +8 -0
  66. package/public/cover.jpg +0 -0
  67. package/turbo.json +12 -0
package/.mcp.json ADDED
@@ -0,0 +1,8 @@
1
+ {
2
+ "mcpServers": {
3
+ "chrome-devtools": {
4
+ "command": "bunx",
5
+ "args": ["chrome-devtools-mcp@latest"]
6
+ }
7
+ }
8
+ }
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
+ ![Blossom Carousel](./public/cover.jpg)
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
+ }