shopify-drag-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 (4) hide show
  1. package/README.md +110 -0
  2. package/index.js +231 -0
  3. package/package.json +34 -0
  4. package/style.css +19 -0
package/README.md ADDED
@@ -0,0 +1,110 @@
1
+ # shopify-drag-carousel
2
+
3
+ **Add drag-scroll to any horizontal container with one class.**
4
+
5
+ A tiny, zero-dependency behavior layer for smooth mouse and touch drag scrolling. It is **not** a UI kit: it does not dictate layout, spacing, or markup—only scroll behavior.
6
+
7
+ ## Features
8
+
9
+ - **Auto-init** — Elements with class `drag-carousel` are ready on `DOMContentLoaded`; no duplicate setup per element
10
+ - **Mouse + touch** — Desktop drag and mobile touch drag using native `scrollLeft`
11
+ - **Configurable speed** — Default multiplier `1.5`
12
+ - **Optional prev/next** — Wire buttons with CSS selectors; each click scrolls by one viewport width with smooth behavior
13
+ - **Minimal inline styles** — `overflow-x: auto`, `cursor: grab`, `scroll-behavior: smooth` (plus optional `style.css` for `grabbing`)
14
+
15
+ ## Install
16
+
17
+ ```bash
18
+ npm install shopify-drag-carousel
19
+ ```
20
+
21
+ ## CDN (no build step)
22
+
23
+ Script (global `DragCarousel` on `window`):
24
+
25
+ ```html
26
+ <script src="https://cdn.jsdelivr.net/npm/shopify-drag-carousel@1/index.js"></script>
27
+ ```
28
+
29
+ Optional CSS for the dragging cursor:
30
+
31
+ ```html
32
+ <link
33
+ rel="stylesheet"
34
+ href="https://cdn.jsdelivr.net/npm/shopify-drag-carousel@1/style.css"
35
+ />
36
+ ```
37
+
38
+ Pin a major version (`@1`) or an exact version in production.
39
+
40
+ ## Shopify (Liquid)
41
+
42
+ Drop the script (and optional stylesheet) in your theme—**theme.liquid** asset tags, or a section’s `{% schema %}` and `{% javascript %}` / `{% stylesheet %}` as you prefer.
43
+
44
+ ```liquid
45
+ <div class="drag-carousel">
46
+ {% for product in collections.frontpage.products %}
47
+ <div>{{ product.title }}</div>
48
+ {% endfor %}
49
+ </div>
50
+ ```
51
+
52
+ Give children whatever layout you use (flex row, grid, inline blocks, etc.). The library only enables horizontal scrolling and drag behavior.
53
+
54
+ After **dynamic** updates (e.g. section AJAX), call:
55
+
56
+ ```js
57
+ DragCarousel.initAll();
58
+ ```
59
+
60
+ ## Vanilla JavaScript
61
+
62
+ **Automatic:** Add class `drag-carousel` to your container; initialization runs when the DOM is ready.
63
+
64
+ **Manual:**
65
+
66
+ ```js
67
+ const carousel = new DragCarousel('#my-row', {
68
+ speed: 1.5,
69
+ buttons: {
70
+ prev: '.prev-btn',
71
+ next: '.next-btn',
72
+ },
73
+ });
74
+ ```
75
+
76
+ **Node / CommonJS:**
77
+
78
+ ```js
79
+ const DragCarousel = require('shopify-drag-carousel');
80
+ new DragCarousel('.drag-carousel');
81
+ ```
82
+
83
+ ## API
84
+
85
+ ### `new DragCarousel(selectorOrElement, options?)`
86
+
87
+ | Option | Type | Default | Description |
88
+ | --------- | -------- | ------- | ------------------------------------------------ |
89
+ | `speed` | `number` | `1.5` | Multiplier applied to pointer movement vs scroll |
90
+ | `buttons` | `object` | `null` | `{ prev: string, next: string }` CSS selectors |
91
+
92
+ String selectors use `document.querySelector` (first match). For multiple carousels, create one instance per element or rely on **auto-init** + class `drag-carousel`.
93
+
94
+ ### `DragCarousel.initAll()`
95
+
96
+ Finds all `.drag-carousel` elements that are not yet initialized and attaches behavior. Use after injecting new HTML.
97
+
98
+ ### `DragCarousel.isInitialized(element)`
99
+
100
+ Returns whether the given element already has carousel behavior.
101
+
102
+ ## Constraints
103
+
104
+ - No React or other UI framework required
105
+ - No bundler required for browser usage
106
+ - Intended to run directly in the browser via script tag or small bundles
107
+
108
+ ## License
109
+
110
+ MIT
package/index.js ADDED
@@ -0,0 +1,231 @@
1
+ /**
2
+ * shopify-drag-carousel — lightweight drag-scroll behavior for horizontal containers.
3
+ * Zero dependencies. Works in Liquid, vanilla JS, or any stack without a bundler.
4
+ */
5
+
6
+ const initialized = new WeakSet();
7
+
8
+ class DragCarousel {
9
+ /**
10
+ * @param {string | Element | null} selectorOrElement - CSS selector or DOM element
11
+ * @param {{ speed?: number, buttons?: { prev?: string, next?: string } | null }} [options]
12
+ */
13
+ constructor(selectorOrElement, options = {}) {
14
+ this.element =
15
+ typeof selectorOrElement === 'string'
16
+ ? document.querySelector(selectorOrElement)
17
+ : selectorOrElement;
18
+
19
+ this.options = {
20
+ speed: 1.5,
21
+ buttons: null,
22
+ ...options,
23
+ };
24
+
25
+ /** @private */
26
+ this._dragging = false;
27
+ /** @private */
28
+ this._startX = 0;
29
+ /** @private */
30
+ this._startScroll = 0;
31
+ /** @private */
32
+ this._onMouseDown = null;
33
+ /** @private */
34
+ this._onTouchStart = null;
35
+ /** @private */
36
+ this._boundMove = null;
37
+ /** @private */
38
+ this._boundStop = null;
39
+ /** @private */
40
+ this._boundTouchMove = null;
41
+ /** @private */
42
+ this._boundTouchEnd = null;
43
+
44
+ if (!this.element || initialized.has(this.element)) {
45
+ return;
46
+ }
47
+
48
+ this.init();
49
+ }
50
+
51
+ init() {
52
+ if (!this.element || initialized.has(this.element)) {
53
+ return;
54
+ }
55
+ initialized.add(this.element);
56
+ this.applyStyles();
57
+ this.bindEvents();
58
+ if (this.options.buttons) {
59
+ this.bindButtons();
60
+ }
61
+ }
62
+
63
+ applyStyles() {
64
+ const el = this.element;
65
+ el.style.overflowX = 'auto';
66
+ el.style.cursor = 'grab';
67
+ el.style.scrollBehavior = DragCarousel.scrollBehaviorOption();
68
+ }
69
+
70
+ /**
71
+ * Use smooth scrolling unless the user prefers reduced motion.
72
+ * @returns {'smooth' | 'auto'}
73
+ */
74
+ static scrollBehaviorOption() {
75
+ if (typeof matchMedia === 'undefined') return 'smooth';
76
+ return matchMedia('(prefers-reduced-motion: reduce)').matches ? 'auto' : 'smooth';
77
+ }
78
+
79
+ bindEvents() {
80
+ this._onMouseDown = (e) => {
81
+ if (e.button !== 0) return;
82
+ e.preventDefault();
83
+ this.stopMouse();
84
+ this.start(e.clientX);
85
+ this._boundMove = (ev) => this.move(ev.clientX);
86
+ this._boundStop = () => this.stopMouse();
87
+ window.addEventListener('mousemove', this._boundMove, { passive: true });
88
+ window.addEventListener('mouseup', this._boundStop, { passive: true });
89
+ };
90
+
91
+ this._onTouchStart = (e) => {
92
+ if (!e.touches.length) return;
93
+ this.stopTouch();
94
+ this.start(e.touches[0].clientX);
95
+ this._boundTouchMove = (ev) => {
96
+ if (!this._dragging) return;
97
+ if (!ev.touches.length) return;
98
+ ev.preventDefault();
99
+ this.move(ev.touches[0].clientX);
100
+ };
101
+ this._boundTouchEnd = () => this.stopTouch();
102
+ window.addEventListener('touchmove', this._boundTouchMove, { passive: false });
103
+ window.addEventListener('touchend', this._boundTouchEnd, { passive: true });
104
+ window.addEventListener('touchcancel', this._boundTouchEnd, { passive: true });
105
+ };
106
+
107
+ this.element.addEventListener('mousedown', this._onMouseDown);
108
+ this.element.addEventListener('touchstart', this._onTouchStart, { passive: true });
109
+ }
110
+
111
+ /**
112
+ * @param {number} clientX
113
+ */
114
+ start(clientX) {
115
+ this._dragging = true;
116
+ this.element.classList.add('dragging');
117
+ // Pointer-driven drag must be instant; CSS smooth would fight 1:1 tracking.
118
+ this.element.style.scrollBehavior = 'auto';
119
+ this._startX = clientX;
120
+ this._startScroll = this.element.scrollLeft;
121
+ }
122
+
123
+ /**
124
+ * @param {number} clientX
125
+ */
126
+ move(clientX) {
127
+ if (!this._dragging) return;
128
+ const delta = clientX - this._startX;
129
+ const left = this._startScroll - delta * this.options.speed;
130
+ this.element.scrollTo({ left, behavior: 'auto' });
131
+ }
132
+
133
+ stop() {
134
+ this._dragging = false;
135
+ this.element.classList.remove('dragging');
136
+ this.element.style.scrollBehavior = DragCarousel.scrollBehaviorOption();
137
+ }
138
+
139
+ stopMouse() {
140
+ if (this._boundMove) {
141
+ window.removeEventListener('mousemove', this._boundMove);
142
+ }
143
+ if (this._boundStop) {
144
+ window.removeEventListener('mouseup', this._boundStop);
145
+ }
146
+ this._boundMove = null;
147
+ this._boundStop = null;
148
+ this.stop();
149
+ }
150
+
151
+ stopTouch() {
152
+ if (this._boundTouchMove) {
153
+ window.removeEventListener('touchmove', this._boundTouchMove);
154
+ }
155
+ if (this._boundTouchEnd) {
156
+ window.removeEventListener('touchend', this._boundTouchEnd);
157
+ window.removeEventListener('touchcancel', this._boundTouchEnd);
158
+ }
159
+ this._boundTouchMove = null;
160
+ this._boundTouchEnd = null;
161
+ this.stop();
162
+ }
163
+
164
+ bindButtons() {
165
+ const cfg = this.options.buttons;
166
+ if (!cfg) return;
167
+
168
+ const el = this.element;
169
+ const step = () => el.clientWidth;
170
+
171
+ const behavior = DragCarousel.scrollBehaviorOption();
172
+
173
+ if (cfg.prev) {
174
+ const prevEl = document.querySelector(cfg.prev);
175
+ if (prevEl) {
176
+ prevEl.addEventListener('click', () => {
177
+ el.scrollBy({ left: -step(), behavior });
178
+ });
179
+ }
180
+ }
181
+ if (cfg.next) {
182
+ const nextEl = document.querySelector(cfg.next);
183
+ if (nextEl) {
184
+ nextEl.addEventListener('click', () => {
185
+ el.scrollBy({ left: step(), behavior });
186
+ });
187
+ }
188
+ }
189
+ }
190
+
191
+ /**
192
+ * Whether this element was already enhanced.
193
+ * @param {Element} el
194
+ * @returns {boolean}
195
+ */
196
+ static isInitialized(el) {
197
+ return initialized.has(el);
198
+ }
199
+
200
+ /**
201
+ * Scan the document for `.drag-carousel` and initialize any not yet handled.
202
+ * Useful after dynamic content (e.g. Shopify section reloads).
203
+ */
204
+ static initAll() {
205
+ if (typeof document === 'undefined') return;
206
+ document.querySelectorAll('.drag-carousel').forEach((el) => {
207
+ if (!initialized.has(el)) {
208
+ void new DragCarousel(el);
209
+ }
210
+ });
211
+ }
212
+ }
213
+
214
+ function autoInit() {
215
+ DragCarousel.initAll();
216
+ }
217
+
218
+ if (typeof document !== 'undefined') {
219
+ if (document.readyState === 'loading') {
220
+ document.addEventListener('DOMContentLoaded', autoInit, { once: true });
221
+ } else {
222
+ autoInit();
223
+ }
224
+ }
225
+
226
+ if (typeof module !== 'undefined' && module.exports) {
227
+ module.exports = DragCarousel;
228
+ }
229
+ if (typeof window !== 'undefined') {
230
+ window.DragCarousel = DragCarousel;
231
+ }
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "shopify-drag-carousel",
3
+ "version": "1.0.0",
4
+ "description": "Lightweight drag-scroll for horizontal containers—one class, zero dependencies. Shopify & vanilla JS friendly.",
5
+ "main": "index.js",
6
+ "files": [
7
+ "index.js",
8
+ "style.css",
9
+ "README.md"
10
+ ],
11
+ "scripts": {
12
+ "test": "echo \"Error: no test specified\" && exit 1"
13
+ },
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "git+https://github.com/Sahilattar8786/shopify-drag-carousel.git"
17
+ },
18
+ "keywords": [
19
+ "shopify",
20
+ "carousel",
21
+ "drag-scroll",
22
+ "slider",
23
+ "horizontal-scroll",
24
+ "touch",
25
+ "vanilla-js"
26
+ ],
27
+ "author": "",
28
+ "license": "MIT",
29
+ "type": "commonjs",
30
+ "bugs": {
31
+ "url": "https://github.com/Sahilattar8786/shopify-drag-carousel/issues"
32
+ },
33
+ "homepage": "https://github.com/Sahilattar8786/shopify-drag-carousel#readme"
34
+ }
package/style.css ADDED
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Optional stylesheet for shopify-drag-carousel.
3
+ * Behavior also injects minimal inline styles; import this for grabbing cursor while dragging.
4
+ */
5
+
6
+ .drag-carousel {
7
+ scroll-behavior: smooth;
8
+ }
9
+
10
+ @media (prefers-reduced-motion: reduce) {
11
+ .drag-carousel {
12
+ scroll-behavior: auto;
13
+ }
14
+ }
15
+
16
+ .drag-carousel.dragging {
17
+ cursor: grabbing;
18
+ user-select: none;
19
+ }