motionrail 0.0.3 → 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/README.md CHANGED
@@ -14,6 +14,7 @@ A lightweight, smooth carousel library with momentum-based scrolling, snap align
14
14
  - 🎨 **CSS Grid based** - Modern layout with customizable styling
15
15
  - 🪶 **Lightweight** - Zero dependencies, minimal bundle size
16
16
  - 🎮 **Full control API** - Programmatic navigation and playback control
17
+ - 🧩 **Extension system** - Modular architecture with built-in extensions (Arrows, Logger)
17
18
 
18
19
  ## Installation
19
20
 
@@ -21,21 +22,44 @@ A lightweight, smooth carousel library with momentum-based scrolling, snap align
21
22
  npm install motionrail
22
23
  ```
23
24
 
25
+ ## Quick Start
26
+
27
+ ```js
28
+ import { MotionRail } from 'motionrail';
29
+ import 'motionrail/style.css';
30
+
31
+ const carousel = new MotionRail(document.getElementById('carousel'), {
32
+ breakpoints: [
33
+ { columns: 1, gap: '16px' },
34
+ { width: 768, columns: 2, gap: '16px' },
35
+ { width: 1024, columns: 3, gap: '20px' }
36
+ ]
37
+ });
38
+ ```
39
+
24
40
  ## Usage
25
41
 
26
42
  ### HTML Structure
27
43
 
28
44
  ```html
29
- <div class="motion-rail" id="carousel">
30
- <div class="motion-rail-container">
31
- <div class="motion-rail-item">Item 1</div>
32
- <div class="motion-rail-item">Item 2</div>
33
- <div class="motion-rail-item">Item 3</div>
34
- <!-- Add more items -->
45
+ <div data-motion-rail id="carousel">
46
+ <div data-motion-rail-scrollable>
47
+ <div data-motion-rail-grid>
48
+ <div>Item 1</div>
49
+ <div>Item 2</div>
50
+ <div>Item 3</div>
51
+ <!-- Add more items -->
52
+ </div>
35
53
  </div>
36
54
  </div>
37
55
  ```
38
56
 
57
+ **Structure layers:**
58
+ - `[data-motion-rail]` - Wrapper element (receives the ID)
59
+ - `[data-motion-rail-scrollable]` - Scrollable container with overflow and snap behavior
60
+ - `[data-motion-rail-grid]` - Grid layout container
61
+ - Direct children - Carousel items (no specific class or attribute required)
62
+
39
63
  ### CSS Import
40
64
 
41
65
  ```js
@@ -46,6 +70,8 @@ import 'motionrail/style.css';
46
70
 
47
71
  ```js
48
72
  import { MotionRail } from 'motionrail';
73
+ // Import types if using TypeScript
74
+ import type { MotionRailOptions, MotionRailState, MotionRailBreakpoint } from 'motionrail';
49
75
 
50
76
  const element = document.getElementById('carousel');
51
77
 
@@ -70,7 +96,9 @@ const carousel = new MotionRail(element, {
70
96
  | `rtl` | `boolean` | `false` | Enable right-to-left layout |
71
97
  | `delay` | `number` | `3000` | Delay between auto-scrolls (milliseconds) |
72
98
  | `resumeDelay` | `number` | `4000` | Time to resume autoplay after user interaction (milliseconds) |
73
- | `breakpoints` | `MotionRailBreakpoint[]` | `[]` | Array of responsive breakpoint configurations |
99
+ | `breakpoints` | `MotionRailBreakpoint[]` | `[{ columns: 1, gap: "0px" }]` | Array of responsive breakpoint configurations (optional) |
100
+ | `onChange` | `(state: MotionRailState) => void` | `undefined` | Callback fired when carousel state changes (optional) |
101
+ | `extensions` | `MotionRailExtension[]` | `[]` | Array of extension instances (optional) |
74
102
 
75
103
  ### Breakpoint Configuration
76
104
 
@@ -93,6 +121,36 @@ breakpoints: [
93
121
  ]
94
122
  ```
95
123
 
124
+ **Note:** Breakpoints are optional. If omitted, a default single-column layout with no gap is used.
125
+
126
+ ### State Management
127
+
128
+ The `onChange` callback receives a `MotionRailState` object whenever the carousel state changes:
129
+
130
+ ```typescript
131
+ interface MotionRailState {
132
+ totalItems: number; // Total number of items in carousel
133
+ visibleItemIndexes: number[]; // Array of currently visible item indexes
134
+ isFirstItemVisible: boolean; // Whether the first item is visible
135
+ isLastItemVisible: boolean; // Whether the last item is visible
136
+ }
137
+ ```
138
+
139
+ **Example:**
140
+ ```js
141
+ const carousel = new MotionRail(element, {
142
+ breakpoints: [
143
+ { columns: 1, gap: '16px' },
144
+ { width: 768, columns: 2, gap: '16px' }
145
+ ],
146
+ onChange: (state) => {
147
+ console.log('Visible items:', state.visibleItemIndexes);
148
+ console.log('At start:', state.isFirstItemVisible);
149
+ console.log('At end:', state.isLastItemVisible);
150
+ }
151
+ });
152
+ ```
153
+
96
154
  ## API Methods
97
155
 
98
156
  ### `play()`
@@ -130,11 +188,200 @@ Scroll to a specific item by index. Pauses autoplay.
130
188
  carousel.scrollToIndex(2); // Scroll to the third item
131
189
  ```
132
190
 
191
+ ### `getState()`
192
+ Get the current carousel state.
193
+
194
+ ```js
195
+ const state = carousel.getState();
196
+ console.log(state.visibleItemIndexes); // [0, 1, 2]
197
+ ```
198
+
199
+ ### `getOptions()`
200
+ Get the current carousel configuration options. Returns a copy to prevent external modifications.
201
+
202
+ ```js
203
+ const options = carousel.getOptions();
204
+ console.log(options.autoplay); // false
205
+ console.log(options.breakpoints); // [{ columns: 1, gap: '16px' }, ...]
206
+ ```
207
+
208
+ ### `update()`
209
+ Refresh the carousel after dynamically adding or removing items from the DOM. This method:
210
+ - Recounts total items
211
+ - Recaches snap points
212
+ - Re-observes edge items with IntersectionObserver
213
+ - Reapplies breakpoints
214
+ - Triggers onChange callback with updated state
215
+
216
+ ```js
217
+ // Add items to the DOM
218
+ const grid = document.querySelector('[data-motion-rail-grid]');
219
+ const newItem = document.createElement('div');
220
+ newItem.textContent = 'New Item';
221
+ grid.appendChild(newItem);
222
+
223
+ // Update carousel to recognize new items
224
+ carousel.update();
225
+ ```
226
+
133
227
  ### `destroy()`
134
228
  Clean up event listeners and timers.
135
229
 
136
230
  ```js
137
- carousel.destroy();
231
+ car
232
+
233
+ ## Extensions
234
+
235
+ MotionRail supports a modular extension system that allows you to add functionality without bloating the core library.
236
+
237
+ ### Built-in Extensions
238
+
239
+ #### Arrow Navigation
240
+
241
+ Add previous/next navigation arrows to your carousel.
242
+
243
+ ```js
244
+ import { MotionRail } from 'motionrail';
245
+ import { Arrows } from 'motionrail/extensions/arrows';
246
+ import 'motionrail/style.css';
247
+ import 'motionrail/extensions/arrows/style.css';
248
+
249
+ const carousel = new MotionRail(element, {
250
+ breakpoints: [
251
+ { columns: 1, gap: '16px' },
252
+ { width: 768, columns: 2, gap: '16px' }
253
+ ],
254
+ extensions: [
255
+ Arrows({
256
+ loop: true, // Enable/disable arrows at edges (default: true)
257
+ leftIcon: '<svg>...</svg>', // Custom left arrow HTML (optional)
258
+ rightIcon: '<svg>...</svg>', // Custom right arrow HTML (optional)
259
+ log: false // Enable console logging (default: false)
260
+ })
261
+ ]
262
+ });
263
+ ```
264
+
265
+ **Arrow Options:**
266
+ - `loop` (boolean, default: `true`) - When `false`, arrows are disabled at carousel edges
267
+ - `leftIcon` (string, optional) - HTML string for left arrow icon
268
+ - `rightIcon` (string, optional) - HTML string for right arrow icon
269
+ - `log` (boolean, default: `false`) - Enable debug logging
270
+
271
+ **Features:**
272
+ - Automatically hides when all items are visible
273
+ - RTL-aware (swaps navigation direction)
274
+ - Customizable icons (SVG or text)
275
+ - Disabled state styling when `loop: false`
276
+
277
+ #### Logger Extension
278
+
279
+ Debug extension that logs lifecycle events to console.
280
+
281
+ ```js
282
+ import { MotionRail } from 'motionrail';
283
+ import { Logger } from 'motionrail/extensions/logger';
284
+
285
+ const carousel = new MotionRail(element, {
286
+ extensions: [Logger()]
287
+ });
288
+ ```
289
+
290
+ Logs:
291
+ - Initialization with initial state
292
+ - State updates on scroll/resize
293
+ - Destruction cleanup
294
+
295
+ ### Creating Custom Extensions
296
+
297
+ Extensions follow a simple lifecycle API:
298
+
299
+ ```typescript
300
+ interface MotionRailExtension {
301
+ name: string;
302
+ onInit?: (motionRail: MotionRail, state: MotionRailState) => void;
303
+ onUpdate?: (motionRail: MotionRail, state: MotionRailState) => void;
304
+ onDestroy?: (motionRail: MotionRail, state: MotionRailState) => void;
305
+ }
306
+ ```
307
+
308
+ **Example - Custom Page Indicator:**
309
+
310
+ ```js
311
+ function PageIndicator() {
312
+ let indicators;
313
+
314
+ return {
315
+ name: "PageIndicator",
316
+
317
+ onInit(motionRail, state) {
318
+ // Create indicator dots
319
+ indicators = document.createElement('div');
320
+ indicators.className = 'carousel-indicators';
321
+
322
+ for (let i = 0; i < state.totalItems; i++) {
323
+ const dot = document.createElement('button');
324
+ dot.addEventListener('click', () => motionRail.scrollToIndex(i));
325
+ indicators.appendChild(dot);
326
+ }
327
+
328
+ motionRail.element.appendChild(indicators);
329
+ },
330
+
331
+ onUpdate(motionRail, state) {
332
+ // Update active indicator
333
+ const dots = indicators.querySelectorAll('button');
334
+ dots.forEach((dot, i) => {
335
+ dot.classList.toggle('active', state.visibleItemIndexes.includes(i));
336
+ });
337
+ },
338
+
339
+ onDestroy() {
340
+ // Clean up
341
+ indicators.remove();
342
+ }
343
+ };
344
+ }
345
+
346
+ // Use it
347
+ const carousel = new MotionRail(element, {
348
+ extensions: [PageIndicator()]
349
+ });
350
+ ```
351
+
352
+ **Lifecycle Hooks:**
353
+ - `onInit(motionRail, state)` - Called once when carousel initializes
354
+ - `onUpdate(motionRail, state)` - Called whenever carousel state changes
355
+ - `onDestroy(motionRail, state)` - Called when carousel is destroyed
356
+
357
+ **Extension State Access:**
358
+ Both hooks receive:
359
+ - `motionRail` - Full API access (methods, element, getOptions())
360
+ - `state` - Current carousel state (totalItems, visibleItemIndexes, etc.)
361
+
362
+ ## UMD/CommonJS Usage
363
+
364
+ For non-module environments or CommonJS:
365
+
366
+ ```html
367
+ <script src="node_modules/motionrail/dist/motionrail.umd.cjs"></script>
368
+ <script src="node_modules/motionrail/dist/extensions/arrows.umd.cjs"></script>
369
+ <link rel="stylesheet" href="node_modules/motionrail/dist/style.css">
370
+ <link rel="stylesheet" href="node_modules/motionrail/dist/extensions/arrows/style.css">
371
+
372
+ <script>
373
+ const carousel = new MotionRail.MotionRail(element, {
374
+ extensions: [MotionRailArrows.Arrows()]
375
+ });
376
+ </script>
377
+ ```
378
+
379
+ Or with CommonJS:
380
+
381
+ ```js
382
+ const { MotionRail } = require('motionrail');
383
+ const { Arrows } = require('motionrail/extensions/arrows');
384
+ ```ousel.destroy();
138
385
  ```
139
386
 
140
387
  ## Examples
@@ -205,11 +452,13 @@ const carousel = new MotionRail(
205
452
  ### With Navigation Controls
206
453
 
207
454
  ```html
208
- <div class="motion-rail" id="carousel">
209
- <div class="motion-rail-container">
210
- <div class="motion-rail-item">Item 1</div>
211
- <div class="motion-rail-item">Item 2</div>
212
- <div class="motion-rail-item">Item 3</div>
455
+ <div data-motion-rail id="carousel">
456
+ <div data-motion-rail-scrollable>
457
+ <div data-motion-rail-grid>
458
+ <div>Item 1</div>
459
+ <div>Item 2</div>
460
+ <div>Item 3</div>
461
+ </div>
213
462
  </div>
214
463
  </div>
215
464
 
@@ -240,23 +489,76 @@ document.getElementById('play').addEventListener('click', () => carousel.play())
240
489
  document.getElementById('pause').addEventListener('click', () => carousel.pause());
241
490
  ```
242
491
 
492
+ ### Dynamic Content
493
+
494
+ ```html
495
+ <div data-motion-rail id="carousel">
496
+ <div data-motion-rail-scrollable>
497
+ <div data-motion-rail-grid id="carousel-grid">
498
+ <div>Item 1</div>
499
+ <div>Item 2</div>
500
+ <div>Item 3</div>
501
+ </div>
502
+ </div>
503
+ </div>
504
+
505
+ <button id="add-item">Add Item</button>
506
+ <button id="remove-item">Remove Item</button>
507
+ ```
508
+
509
+ ```js
510
+ const carousel = new MotionRail(
511
+ document.getElementById('carousel'),
512
+ {
513
+ breakpoints: [
514
+ { columns: 1, gap: '16px' },
515
+ { width: 768, columns: 2, gap: '16px' }
516
+ ]
517
+ }
518
+ );
519
+
520
+ let itemCounter = 4;
521
+ document.getElementById('add-item').addEventListener('click', () => {
522
+ const grid = document.getElementById('carousel-grid');
523
+ const newItem = document.createElement('div');
524
+ newItem.textContent = `Item ${itemCounter++}`;
525
+ grid.appendChild(newItem);
526
+ carousel.update(); // Refresh carousel after adding items
527
+ });
528
+
529
+ document.getElementById('remove-item').addEventListener('click', () => {
530
+ const grid = document.getElementById('carousel-grid');
531
+ if (grid.children.length > 0) {
532
+ grid.removeChild(grid.lastChild);
533
+ carousel.update(); // Refresh carousel after removing items
534
+ }
535
+ });
536
+ ```
537
+
243
538
  ## Styling
244
539
 
245
540
  The library includes base styles via `motionrail/style.css`. You can customize the appearance of items with your own CSS:
246
541
 
247
542
  ```css
248
- .motion-rail {
543
+ [data-motion-rail] {
249
544
  height: 400px; /* Set carousel height */
250
545
  }
251
546
 
252
- .motion-rail-item {
547
+ [data-motion-rail-grid] > * {
253
548
  /* Style your carousel items */
254
549
  background: #f0f0f0;
255
550
  border-radius: 8px;
256
551
  padding: 20px;
552
+ scroll-snap-align: start; /* Enable snap behavior */
257
553
  }
258
554
  ```
259
555
 
556
+ **Key selectors:**
557
+ - `[data-motion-rail]` - Wrapper element
558
+ - `[data-motion-rail-scrollable]` - Scrollable container (has overflow and snap type)
559
+ - `[data-motion-rail-grid]` - Grid layout container
560
+ - `[data-motion-rail-grid] > *` - Direct children (carousel items)
561
+
260
562
  ## Browser Support
261
563
 
262
564
  - Chrome/Edge (latest)
@@ -269,6 +571,8 @@ Requires support for:
269
571
  - Pointer Events API
270
572
  - Container Queries
271
573
  - Scroll Snap
574
+ - IntersectionObserver API
575
+ - ResizeObserver API
272
576
 
273
577
  ## License
274
578
 
@@ -0,0 +1,7 @@
1
+ import type { MotionRailExtension } from "../../motionrail";
2
+ export declare function Arrows(par?: {
3
+ leftIcon?: string;
4
+ rightIcon?: string;
5
+ loop?: boolean;
6
+ log?: boolean;
7
+ }): MotionRailExtension;
@@ -0,0 +1 @@
1
+ .motionrail-arrow[disabled]{opacity:.3;pointer-events:none}.motionrail-arrow-left,.motionrail-arrow-right{color:#fff;cursor:pointer;z-index:10;position-anchor:--motionrail-scrollable;width:40px;height:40px;top:anchor(center);background:#00000080;border:none;border-radius:50%;justify-content:center;align-items:center;font-size:20px;transition:all .2s;display:flex;position:absolute;translate:0 -50%}.motionrail-arrow-left{left:anchor(left);margin-left:10px}.motionrail-arrow-right{right:anchor(right);margin-right:10px}.motionrail-arrow-left:hover,.motionrail-arrow-right:hover{background:#000c;scale:1.1}.motionrail-arrow-left:active,.motionrail-arrow-right:active{scale:.95}
@@ -0,0 +1,25 @@
1
+ import "../motionrail-Dq8joDtA.js";
2
+ function Arrows(e) {
3
+ let t = document.createElement("button"), n = document.createElement("button"), { leftIcon: r = "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"lucide lucide-chevron-left-icon lucide-chevron-left\"><path d=\"m15 18-6-6 6-6\"/></svg>", rightIcon: i = "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"lucide lucide-chevron-right-icon lucide-chevron-right\"><path d=\"m9 18 6-6-6-6\"/></svg>", loop: a = !0, log: o = !1 } = e || {};
4
+ return {
5
+ name: "ArrowExtension",
6
+ onInit(e, a) {
7
+ let { totalItems: o } = a;
8
+ if (o === 0) return;
9
+ let { rtl: s } = e.getOptions();
10
+ t.innerHTML = r, t.className = "motionrail-arrow motionrail-arrow-left", t.addEventListener("click", () => {
11
+ s ? e.next() : e.prev();
12
+ }), n.innerHTML = i, n.className = "motionrail-arrow motionrail-arrow-right", n.addEventListener("click", () => {
13
+ s ? e.prev() : e.next();
14
+ }), e.element.appendChild(t), e.element.appendChild(n);
15
+ },
16
+ onUpdate(e, r) {
17
+ let { isFirstItemVisible: i, isLastItemVisible: s, totalItems: c, visibleItemIndexes: l } = r;
18
+ a || (t.disabled = i, n.disabled = s), l.length < c ? (t.style.removeProperty("display"), n.style.removeProperty("display")) : (t.style.display = "none", n.style.display = "none"), c || (t.style.display = "none", n.style.display = "none"), o && console.log("ArrowExtension updated", r);
19
+ },
20
+ onDestroy(e, r) {
21
+ t.remove(), n.remove();
22
+ }
23
+ };
24
+ }
25
+ export { Arrows };
@@ -0,0 +1,2 @@
1
+ (function(e,t){typeof exports==`object`&&typeof module<`u`?t(exports):typeof define==`function`&&define.amd?define([`exports`],t):(e=typeof globalThis<`u`?globalThis:e||self,t(e.MotionRailArrows={}))})(this,function(e){var t=document.createElement(`style`);t.textContent=`[data-motion-rail-scrollable]::-webkit-scrollbar{display:none}[data-motion-rail]{position:relative}[data-motion-rail] [data-motion-rail-scrollable]{anchor-name:--motionrail-scrollable;scroll-snap-type:x mandatory;scrollbar-width:none;-ms-overflow-style:none;cursor:grab;position:relative;overflow:scroll hidden;container-type:inline-size}[data-motion-rail] [data-motion-rail-scrollable]:active{cursor:grabbing}[data-motion-rail] [data-motion-rail-scrollable] [data-motion-rail-grid]{grid-auto-flow:column;height:100%;display:grid}[data-motion-rail] [data-motion-rail-scrollable] [data-motion-rail-grid]>*{pointer-events:none;scroll-snap-align:start}
2
+ /*$vite$:1*/`,document.head.appendChild(t);function n(e){let t=document.createElement(`button`),n=document.createElement(`button`),{leftIcon:r=`<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-left-icon lucide-chevron-left"><path d="m15 18-6-6 6-6"/></svg>`,rightIcon:i=`<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-right-icon lucide-chevron-right"><path d="m9 18 6-6-6-6"/></svg>`,loop:a=!0,log:o=!1}=e||{};return{name:`ArrowExtension`,onInit(e,a){let{totalItems:o}=a;if(o===0)return;let{rtl:s}=e.getOptions();t.innerHTML=r,t.className=`motionrail-arrow motionrail-arrow-left`,t.addEventListener(`click`,()=>{s?e.next():e.prev()}),n.innerHTML=i,n.className=`motionrail-arrow motionrail-arrow-right`,n.addEventListener(`click`,()=>{s?e.prev():e.next()}),e.element.appendChild(t),e.element.appendChild(n)},onUpdate(e,r){let{isFirstItemVisible:i,isLastItemVisible:s,totalItems:c,visibleItemIndexes:l}=r;a||(t.disabled=i,n.disabled=s),l.length<c?(t.style.removeProperty(`display`),n.style.removeProperty(`display`)):(t.style.display=`none`,n.style.display=`none`),c||(t.style.display=`none`,n.style.display=`none`),o&&console.log(`ArrowExtension updated`,r)},onDestroy(e,r){t.remove(),n.remove()}}}e.Arrows=n});
@@ -0,0 +1,6 @@
1
+ import type { MotionRailExtension } from "../../motionrail";
2
+ export declare function Dots(par?: {
3
+ showIndex?: boolean;
4
+ dotSize?: number;
5
+ log?: boolean;
6
+ }): MotionRailExtension;
@@ -0,0 +1 @@
1
+ .motionrail-dots{z-index:10;scrollbar-width:none;-ms-overflow-style:none;background:#0000004d;border-radius:24px;gap:8px;width:fit-content;max-width:calc(100% - 32px);margin:16px auto 0;padding:8px 12px;display:flex;position:relative;overflow-x:auto;-webkit-mask-image:linear-gradient(90deg,#0000 0,#000 12px calc(100% - 12px),#0000 100%);mask-image:linear-gradient(90deg,#0000 0,#000 12px calc(100% - 12px),#0000 100%)}.motionrail-dots::-webkit-scrollbar{display:none}.motionrail-dot{--dot-size:44px;min-width:var(--dot-size);min-height:var(--dot-size);width:var(--dot-size);height:var(--dot-size);color:#fff;cursor:pointer;background:#ffffff4d;border:2px solid #ffffff80;border-radius:50%;flex-shrink:0;justify-content:center;align-items:center;font-size:14px;font-weight:600;transition:all .3s;display:flex}.motionrail-dot:hover{background:#ffffff80;border-color:#fffc;scale:1.1}.motionrail-dot:active{scale:.95}.motionrail-dot-active{color:#000;background:#ffffffe6;border-color:#fff}.motionrail-dot-active:hover{background:#fff}
@@ -0,0 +1,44 @@
1
+ import "../motionrail-Dq8joDtA.js";
2
+ function Dots(e) {
3
+ let t = document.createElement("div"), n = [], { showIndex: r = !1, dotSize: i = 44, log: a = !1 } = e || {};
4
+ return {
5
+ name: "DotsExtension",
6
+ onInit(e, a) {
7
+ let { totalItems: o } = a;
8
+ if (o !== 0) {
9
+ t.className = "motionrail-dots", t.style.setProperty("--dot-size", `${i}px`);
10
+ for (let i = 0; i < o; i++) {
11
+ let a = document.createElement("button");
12
+ a.className = "motionrail-dot", a.setAttribute("aria-label", `Go to item ${i + 1}`), r && (a.textContent = (i + 1).toString()), a.addEventListener("click", () => {
13
+ e.scrollToIndex(i);
14
+ }), n.push(a), t.appendChild(a);
15
+ }
16
+ e.element.appendChild(t);
17
+ }
18
+ },
19
+ onUpdate(e, r) {
20
+ let { visibleItemIndexes: i, totalItems: o } = r;
21
+ if (!o) {
22
+ t.style.display = "none";
23
+ return;
24
+ }
25
+ if (t.style.removeProperty("display"), n.forEach((e, t) => {
26
+ i.includes(t) ? e.classList.add("motionrail-dot-active") : e.classList.remove("motionrail-dot-active");
27
+ }), i.length > 0) {
28
+ let e = n[i[0]];
29
+ if (e) {
30
+ let n = t.offsetWidth, r = e.offsetLeft, i = e.offsetWidth, a = r - n / 2 + i / 2;
31
+ t.scrollTo({
32
+ left: a,
33
+ behavior: "smooth"
34
+ });
35
+ }
36
+ }
37
+ a && console.log("DotsExtension updated", r);
38
+ },
39
+ onDestroy(e, r) {
40
+ t.remove(), n.length = 0;
41
+ }
42
+ };
43
+ }
44
+ export { Dots };
@@ -0,0 +1,2 @@
1
+ (function(e,t){typeof exports==`object`&&typeof module<`u`?t(exports):typeof define==`function`&&define.amd?define([`exports`],t):(e=typeof globalThis<`u`?globalThis:e||self,t(e.MotionRailDots={}))})(this,function(e){var t=document.createElement(`style`);t.textContent=`[data-motion-rail-scrollable]::-webkit-scrollbar{display:none}[data-motion-rail]{position:relative}[data-motion-rail] [data-motion-rail-scrollable]{anchor-name:--motionrail-scrollable;scroll-snap-type:x mandatory;scrollbar-width:none;-ms-overflow-style:none;cursor:grab;position:relative;overflow:scroll hidden;container-type:inline-size}[data-motion-rail] [data-motion-rail-scrollable]:active{cursor:grabbing}[data-motion-rail] [data-motion-rail-scrollable] [data-motion-rail-grid]{grid-auto-flow:column;height:100%;display:grid}[data-motion-rail] [data-motion-rail-scrollable] [data-motion-rail-grid]>*{pointer-events:none;scroll-snap-align:start}
2
+ /*$vite$:1*/`,document.head.appendChild(t);function n(e){let t=document.createElement(`div`),n=[],{showIndex:r=!1,dotSize:i=44,log:a=!1}=e||{};return{name:`DotsExtension`,onInit(e,a){let{totalItems:o}=a;if(o!==0){t.className=`motionrail-dots`,t.style.setProperty(`--dot-size`,`${i}px`);for(let i=0;i<o;i++){let a=document.createElement(`button`);a.className=`motionrail-dot`,a.setAttribute(`aria-label`,`Go to item ${i+1}`),r&&(a.textContent=(i+1).toString()),a.addEventListener(`click`,()=>{e.scrollToIndex(i)}),n.push(a),t.appendChild(a)}e.element.appendChild(t)}},onUpdate(e,r){let{visibleItemIndexes:i,totalItems:o}=r;if(!o){t.style.display=`none`;return}if(t.style.removeProperty(`display`),n.forEach((e,t)=>{i.includes(t)?e.classList.add(`motionrail-dot-active`):e.classList.remove(`motionrail-dot-active`)}),i.length>0){let e=n[i[0]];if(e){let n=t.offsetWidth,r=e.offsetLeft,i=e.offsetWidth,a=r-n/2+i/2;t.scrollTo({left:a,behavior:`smooth`})}}a&&console.log(`DotsExtension updated`,r)},onDestroy(e,r){t.remove(),n.length=0}}}e.Dots=n});
@@ -0,0 +1,2 @@
1
+ import type { MotionRailExtension } from "../motionrail";
2
+ export declare function Logger(): MotionRailExtension;
@@ -0,0 +1,16 @@
1
+ import "../motionrail-Dq8joDtA.js";
2
+ function Logger() {
3
+ return {
4
+ name: "LoggerExtension",
5
+ onInit(e, t) {
6
+ console.debug("[LoggerExtension] MotionRail initialized", t), console.debug(e);
7
+ },
8
+ onUpdate(e, t) {
9
+ console.debug("[LoggerExtension] MotionRail updated", t), console.debug(e);
10
+ },
11
+ onDestroy(e, t) {
12
+ console.debug("[LoggerExtension] MotionRail destroyed", t), console.debug(e);
13
+ }
14
+ };
15
+ }
16
+ export { Logger };
@@ -0,0 +1,2 @@
1
+ (function(e,t){typeof exports==`object`&&typeof module<`u`?t(exports):typeof define==`function`&&define.amd?define([`exports`],t):(e=typeof globalThis<`u`?globalThis:e||self,t(e.MotionRailLogger={}))})(this,function(e){var t=document.createElement(`style`);t.textContent=`[data-motion-rail-scrollable]::-webkit-scrollbar{display:none}[data-motion-rail]{position:relative}[data-motion-rail] [data-motion-rail-scrollable]{anchor-name:--motionrail-scrollable;scroll-snap-type:x mandatory;scrollbar-width:none;-ms-overflow-style:none;cursor:grab;position:relative;overflow:scroll hidden;container-type:inline-size}[data-motion-rail] [data-motion-rail-scrollable]:active{cursor:grabbing}[data-motion-rail] [data-motion-rail-scrollable] [data-motion-rail-grid]{grid-auto-flow:column;height:100%;display:grid}[data-motion-rail] [data-motion-rail-scrollable] [data-motion-rail-grid]>*{pointer-events:none;scroll-snap-align:start}
2
+ /*$vite$:1*/`,document.head.appendChild(t);function n(){return{name:`LoggerExtension`,onInit(e,t){console.debug(`[LoggerExtension] MotionRail initialized`,t),console.debug(e)},onUpdate(e,t){console.debug(`[LoggerExtension] MotionRail updated`,t),console.debug(e)},onDestroy(e,t){console.debug(`[LoggerExtension] MotionRail destroyed`,t),console.debug(e)}}}e.Logger=n});
@@ -0,0 +1,6 @@
1
+ import type { MotionRailExtension } from "../../motionrail";
2
+ export declare function Thumbnails(par?: {
3
+ thumbnailWidth?: number;
4
+ thumbnailHeight?: number;
5
+ log?: boolean;
6
+ }): MotionRailExtension;
@@ -0,0 +1 @@
1
+ .motionrail-thumbnails{--thumbnail-width:80px;--thumbnail-height:80px;z-index:10;scrollbar-width:none;-ms-overflow-style:none;background:#0000004d;border-radius:8px;gap:12px;width:fit-content;max-width:calc(100% - 32px);margin:16px auto 0;padding:12px;display:flex;position:relative;overflow-x:auto;-webkit-mask-image:linear-gradient(90deg,#0000 0,#000 12px calc(100% - 12px),#0000 100%);mask-image:linear-gradient(90deg,#0000 0,#000 12px calc(100% - 12px),#0000 100%)}.motionrail-thumbnails::-webkit-scrollbar{display:none}.motionrail-thumbnail{min-width:var(--thumbnail-width);min-height:var(--thumbnail-height);width:var(--thumbnail-width);height:var(--thumbnail-height);cursor:pointer;background:0 0;border:3px solid #ffffff4d;border-radius:4px;flex-shrink:0;justify-content:center;align-items:center;padding:0;transition:all .3s;display:flex;position:relative;overflow:hidden}.motionrail-thumbnail>*{border-radius:0!important;width:100%!important;height:100%!important}.motionrail-thumbnail:hover{border-color:#fff9;scale:1.05}.motionrail-thumbnail:active{scale:.98}.motionrail-thumbnail-active{border-width:3px;border-color:#fff;scale:1.1}.motionrail-thumbnail-active:hover{border-color:#fff}
@@ -0,0 +1,42 @@
1
+ import "../motionrail-Dq8joDtA.js";
2
+ function Thumbnails(e) {
3
+ let t = document.createElement("div"), n = [], { thumbnailWidth: r = 80, thumbnailHeight: i = 80, log: a = !1 } = e || {};
4
+ return {
5
+ name: "ThumbnailsExtension",
6
+ onInit(e, a) {
7
+ let { totalItems: o } = a;
8
+ o !== 0 && (t.className = "motionrail-thumbnails", t.style.setProperty("--thumbnail-width", `${r}px`), t.style.setProperty("--thumbnail-height", `${i}px`), e.element.querySelectorAll("[data-motion-rail-grid] > *").forEach((r, i) => {
9
+ let a = document.createElement("button");
10
+ a.className = "motionrail-thumbnail", a.setAttribute("aria-label", `Go to item ${i + 1}`);
11
+ let o = r.cloneNode(!0);
12
+ o.removeAttribute("data-motion-rail-item"), a.appendChild(o), a.addEventListener("click", () => {
13
+ e.scrollToIndex(i);
14
+ }), n.push(a), t.appendChild(a);
15
+ }), e.element.appendChild(t));
16
+ },
17
+ onUpdate(e, r) {
18
+ let { visibleItemIndexes: i, totalItems: o } = r;
19
+ if (!o) {
20
+ t.style.display = "none";
21
+ return;
22
+ }
23
+ if (t.style.removeProperty("display"), n.forEach((e, t) => {
24
+ i.includes(t) ? e.classList.add("motionrail-thumbnail-active") : e.classList.remove("motionrail-thumbnail-active");
25
+ }), i.length > 0) {
26
+ let e = n[i[0]];
27
+ if (e) {
28
+ let n = t.offsetWidth, r = e.offsetLeft, i = e.offsetWidth, a = r - n / 2 + i / 2;
29
+ t.scrollTo({
30
+ left: a,
31
+ behavior: "smooth"
32
+ });
33
+ }
34
+ }
35
+ a && console.log("ThumbnailsExtension updated", r);
36
+ },
37
+ onDestroy(e, r) {
38
+ t.remove(), n.length = 0;
39
+ }
40
+ };
41
+ }
42
+ export { Thumbnails };
@@ -0,0 +1,2 @@
1
+ (function(e,t){typeof exports==`object`&&typeof module<`u`?t(exports):typeof define==`function`&&define.amd?define([`exports`],t):(e=typeof globalThis<`u`?globalThis:e||self,t(e.MotionRailThumbnails={}))})(this,function(e){var t=document.createElement(`style`);t.textContent=`[data-motion-rail-scrollable]::-webkit-scrollbar{display:none}[data-motion-rail]{position:relative}[data-motion-rail] [data-motion-rail-scrollable]{anchor-name:--motionrail-scrollable;scroll-snap-type:x mandatory;scrollbar-width:none;-ms-overflow-style:none;cursor:grab;position:relative;overflow:scroll hidden;container-type:inline-size}[data-motion-rail] [data-motion-rail-scrollable]:active{cursor:grabbing}[data-motion-rail] [data-motion-rail-scrollable] [data-motion-rail-grid]{grid-auto-flow:column;height:100%;display:grid}[data-motion-rail] [data-motion-rail-scrollable] [data-motion-rail-grid]>*{pointer-events:none;scroll-snap-align:start}
2
+ /*$vite$:1*/`,document.head.appendChild(t);function n(e){let t=document.createElement(`div`),n=[],{thumbnailWidth:r=80,thumbnailHeight:i=80,log:a=!1}=e||{};return{name:`ThumbnailsExtension`,onInit(e,a){let{totalItems:o}=a;o!==0&&(t.className=`motionrail-thumbnails`,t.style.setProperty(`--thumbnail-width`,`${r}px`),t.style.setProperty(`--thumbnail-height`,`${i}px`),e.element.querySelectorAll(`[data-motion-rail-grid] > *`).forEach((r,i)=>{let a=document.createElement(`button`);a.className=`motionrail-thumbnail`,a.setAttribute(`aria-label`,`Go to item ${i+1}`);let o=r.cloneNode(!0);o.removeAttribute(`data-motion-rail-item`),a.appendChild(o),a.addEventListener(`click`,()=>{e.scrollToIndex(i)}),n.push(a),t.appendChild(a)}),e.element.appendChild(t))},onUpdate(e,r){let{visibleItemIndexes:i,totalItems:o}=r;if(!o){t.style.display=`none`;return}if(t.style.removeProperty(`display`),n.forEach((e,t)=>{i.includes(t)?e.classList.add(`motionrail-thumbnail-active`):e.classList.remove(`motionrail-thumbnail-active`)}),i.length>0){let e=n[i[0]];if(e){let n=t.offsetWidth,r=e.offsetLeft,i=e.offsetWidth,a=r-n/2+i/2;t.scrollTo({left:a,behavior:`smooth`})}}a&&console.log(`ThumbnailsExtension updated`,r)},onDestroy(e,r){t.remove(),n.length=0}}}e.Thumbnails=n});
@@ -1,15 +1,17 @@
1
1
  import type { MotionRailOptions } from "./types";
2
2
  export declare class MotionRail {
3
+ element: HTMLElement;
4
+ scrollable: HTMLElement;
3
5
  private rtl;
4
- private rtlScrollType;
5
6
  private autoplay;
6
- private breakpoints;
7
- private element;
8
7
  private delay;
9
8
  private resumeDelay;
9
+ private onChange?;
10
+ private handleStateChange;
11
+ private extensions;
12
+ private breakpoints;
10
13
  private autoPlayIntervalId;
11
14
  private autoPlayTimeoutId;
12
- private currentIndex;
13
15
  private isDragging;
14
16
  private startX;
15
17
  private startLogicalScroll;
@@ -20,15 +22,18 @@ export declare class MotionRail {
20
22
  private pointerId;
21
23
  private snapPoints;
22
24
  private resizeObserver;
25
+ private intersectionObserver;
26
+ private state;
23
27
  constructor(element: HTMLElement, options: MotionRailOptions);
24
- private detectRTLScrollType;
28
+ private randomContainerName;
29
+ private setBreakPoints;
25
30
  private getLogicalScroll;
26
31
  private setLogicalScroll;
27
32
  private scrollToLogical;
28
33
  private observeResize;
34
+ private observeEdgeItems;
29
35
  private cacheSnapPoints;
30
36
  private findNearestSnapPoint;
31
- private init;
32
37
  private attachPointerEvents;
33
38
  private handlePointerDown;
34
39
  private handlePointerMove;
@@ -40,6 +45,23 @@ export declare class MotionRail {
40
45
  prev(): void;
41
46
  pause(): void;
42
47
  scrollToIndex(index: number): void;
43
- getCurrentIndex(): number;
48
+ getState(): {
49
+ visibleItemIndexes: number[];
50
+ totalItems: number;
51
+ isFirstItemVisible: boolean;
52
+ isLastItemVisible: boolean;
53
+ };
54
+ getOptions(): {
55
+ autoplay: boolean;
56
+ rtl: boolean;
57
+ delay: number;
58
+ resumeDelay: number;
59
+ breakpoints: {
60
+ width?: number;
61
+ columns?: number;
62
+ gap?: string;
63
+ }[];
64
+ };
65
+ update(): void;
44
66
  destroy(): void;
45
67
  }
@@ -1,12 +1,27 @@
1
+ import type { MotionRail } from "./main";
1
2
  export type MotionRailBreakpoint = {
2
3
  width?: number;
3
4
  columns?: number;
4
5
  gap?: string;
5
6
  };
7
+ export type MotionRailState = {
8
+ totalItems: number;
9
+ visibleItemIndexes: number[];
10
+ isFirstItemVisible: boolean;
11
+ isLastItemVisible: boolean;
12
+ };
6
13
  export type MotionRailOptions = {
7
14
  autoplay?: boolean;
8
15
  resumeDelay?: number;
9
16
  delay?: number;
10
17
  rtl?: boolean;
11
- breakpoints: MotionRailBreakpoint[];
18
+ onChange?: (state: MotionRailState) => void;
19
+ breakpoints?: MotionRailBreakpoint[];
20
+ extensions?: MotionRailExtension[];
21
+ };
22
+ export type MotionRailExtension = {
23
+ name: string;
24
+ onInit?: (motionRail: MotionRail, state: MotionRailState) => void;
25
+ onUpdate?: (motionRail: MotionRail, state: MotionRailState) => void;
26
+ onDestroy?: (motionRail: MotionRail, state: MotionRailState) => void;
12
27
  };
@@ -0,0 +1,213 @@
1
+ var MotionRail = class {
2
+ element;
3
+ scrollable;
4
+ rtl = !1;
5
+ autoplay = !1;
6
+ delay = 3e3;
7
+ resumeDelay = 4e3;
8
+ onChange;
9
+ handleStateChange = () => {
10
+ let e = this.getState();
11
+ this.extensions.forEach((t) => {
12
+ t.onUpdate && t.onUpdate(this, e);
13
+ }), this.onChange && this.onChange(e);
14
+ };
15
+ extensions = [];
16
+ breakpoints = [];
17
+ autoPlayIntervalId = null;
18
+ autoPlayTimeoutId = null;
19
+ isDragging = !1;
20
+ startX = 0;
21
+ startLogicalScroll = 0;
22
+ cancelScroll = null;
23
+ lastPointerX = 0;
24
+ lastPointerTime = 0;
25
+ velocity = 0;
26
+ pointerId = null;
27
+ snapPoints = [];
28
+ resizeObserver = null;
29
+ intersectionObserver = null;
30
+ state = {
31
+ totalItems: 0,
32
+ visibleItemIndexes: [],
33
+ isFirstItemVisible: !1,
34
+ isLastItemVisible: !1
35
+ };
36
+ constructor(e, t) {
37
+ this.autoplay = t.autoplay || !1, this.rtl = t.rtl || !1, this.breakpoints = t.breakpoints || [{
38
+ columns: 1,
39
+ gap: "0px"
40
+ }], this.element = e, this.extensions = t.extensions || [];
41
+ let n = this.element.querySelector("[data-motion-rail-scrollable]");
42
+ if (!n) throw Error("MotionRail: [data-motion-rail-scrollable] element not found");
43
+ this.scrollable = n, this.delay = t.delay || 3e3, this.resumeDelay = t.resumeDelay || 4e3, this.onChange = t.onChange, this.state.totalItems = this.element.querySelectorAll("[data-motion-rail-grid] > *").length, this.setBreakPoints(), this.setLogicalScroll(0), this.attachPointerEvents(), this.cacheSnapPoints(), this.observeResize(), this.observeEdgeItems(), this.autoplay && this.play();
44
+ let r = this.getState();
45
+ this.extensions.forEach((e) => {
46
+ e.onInit && e.onInit(this, r);
47
+ });
48
+ }
49
+ randomContainerName() {
50
+ return `motion-rail-${Math.random().toString(36).substring(2, 11)}`;
51
+ }
52
+ setBreakPoints() {
53
+ let e = this.element.querySelector("[data-motion-rail-scrollable]");
54
+ if (!e) return;
55
+ let t = "";
56
+ e.style.containerName ? t = e.style.containerName : (t = this.randomContainerName(), e.style.containerName = t);
57
+ let n = document.createElement("style"), r = "", i = this.breakpoints.filter((e) => e.width), a = i.length > 0 ? Math.min(...i.map((e) => e.width)) : null;
58
+ return this.breakpoints.forEach((e) => {
59
+ let n = e.columns || 1, i = e.gap || "0px", o = `calc((100cqw - (${n - 1} * ${i})) / ${n})`, s = "";
60
+ s = e.width ? `(min-width: ${e.width}px)` : a ? `(max-width: ${a - 1}px)` : "(min-width: 0px)", r += `
61
+ @container ${t} ${s} {
62
+ [data-motion-rail-grid] {
63
+ grid-template-columns: repeat(${this.state.totalItems}, ${o});
64
+ gap: ${i};
65
+ }
66
+ }
67
+ `;
68
+ }), n.textContent = r, document.head.appendChild(n), n;
69
+ }
70
+ getLogicalScroll() {
71
+ return this.scrollable.scrollLeft;
72
+ }
73
+ setLogicalScroll(e) {
74
+ this.scrollable.scrollLeft = e;
75
+ }
76
+ scrollToLogical(e, t = "auto") {
77
+ this.scrollable.scrollTo({
78
+ left: e,
79
+ behavior: t
80
+ });
81
+ }
82
+ observeResize() {
83
+ typeof ResizeObserver > "u" || (this.resizeObserver = new ResizeObserver(() => {
84
+ this.cacheSnapPoints();
85
+ }), this.resizeObserver.observe(this.scrollable));
86
+ }
87
+ observeEdgeItems() {
88
+ if (typeof IntersectionObserver > "u") return;
89
+ let e = this.element.querySelectorAll("[data-motion-rail-grid] > *");
90
+ if (e.length === 0) return;
91
+ let t = e[0], n = e[e.length - 1];
92
+ this.intersectionObserver = new IntersectionObserver((r) => {
93
+ let i = !1;
94
+ r.forEach((r) => {
95
+ let a = Array.from(e).indexOf(r.target);
96
+ if (a !== -1) {
97
+ if (r.isIntersecting) this.state.visibleItemIndexes.includes(a) || (this.state.visibleItemIndexes.push(a), this.state.visibleItemIndexes.sort((e, t) => e - t), i = !0);
98
+ else {
99
+ let e = this.state.visibleItemIndexes.length;
100
+ this.state.visibleItemIndexes = this.state.visibleItemIndexes.filter((e) => e !== a), this.state.visibleItemIndexes.length !== e && (i = !0);
101
+ }
102
+ if (r.target === t) {
103
+ let e = r.isIntersecting;
104
+ this.state.isFirstItemVisible !== e && (this.state.isFirstItemVisible = e, i = !0);
105
+ } else if (r.target === n) {
106
+ let e = r.isIntersecting;
107
+ this.state.isLastItemVisible !== e && (this.state.isLastItemVisible = e, i = !0);
108
+ }
109
+ }
110
+ }), i && this.handleStateChange();
111
+ }, {
112
+ root: this.scrollable,
113
+ threshold: .5
114
+ }), e.forEach((e) => this.intersectionObserver.observe(e));
115
+ }
116
+ cacheSnapPoints() {
117
+ let e = this.element.querySelectorAll("[data-motion-rail-grid] > *"), t = this.scrollable.scrollWidth - this.scrollable.clientWidth;
118
+ this.snapPoints = Array.from(e).map((e) => Math.min(e.offsetLeft, t));
119
+ }
120
+ findNearestSnapPoint(e) {
121
+ let t = 0, n = Infinity;
122
+ for (let r of this.snapPoints) {
123
+ let i = Math.abs(r - e);
124
+ i < n && (n = i, t = r);
125
+ }
126
+ return t;
127
+ }
128
+ attachPointerEvents() {
129
+ window.matchMedia("(pointer: fine)").matches && (this.scrollable.addEventListener("pointerdown", this.handlePointerDown), this.scrollable.addEventListener("pointermove", this.handlePointerMove), this.scrollable.addEventListener("pointerup", this.handlePointerUp), this.scrollable.addEventListener("pointerleave", this.handlePointerUp));
130
+ }
131
+ handlePointerDown = (e) => {
132
+ this.pointerId === null && (this.pointerId = e.pointerId, this.scrollable.setPointerCapture(e.pointerId), this.isDragging = !0, this.startX = e.clientX, this.startLogicalScroll = this.getLogicalScroll(), this.lastPointerX = e.clientX, this.lastPointerTime = e.timeStamp, this.velocity = 0, this.scrollable.style.userSelect = "none", this.scrollable.style.scrollSnapType = "none", this.pause(), this.cancelScroll && this.cancelScroll(), this.autoPlayTimeoutId && clearTimeout(this.autoPlayTimeoutId));
133
+ };
134
+ handlePointerMove = (e) => {
135
+ if (!this.isDragging || e.pointerId !== this.pointerId) return;
136
+ e.preventDefault();
137
+ let t = e.clientX - this.startX, n = this.startLogicalScroll - t;
138
+ this.setLogicalScroll(n);
139
+ let r = e.timeStamp - this.lastPointerTime;
140
+ r > 0 && (this.velocity = (e.clientX - this.lastPointerX) / r, this.lastPointerX = e.clientX, this.lastPointerTime = e.timeStamp);
141
+ };
142
+ handlePointerUp = (e) => {
143
+ if (!this.isDragging || e.pointerId !== this.pointerId) return;
144
+ this.scrollable.releasePointerCapture(e.pointerId), this.pointerId = null, this.isDragging = !1, this.scrollable.style.userSelect = "";
145
+ let t = Math.abs(this.velocity), n = Math.min(100 + Math.sqrt(t) * 50, 200), r = -this.velocity * n, i = this.getLogicalScroll() + r;
146
+ this.cancelScroll && this.cancelScroll();
147
+ let a = this.findNearestSnapPoint(i);
148
+ this.cancelScroll = this.animateLogicalScroll(a, n, () => {
149
+ this.scrollable.style.scrollSnapType = "x mandatory", this.cancelScroll = null, this.autoplay && (this.autoPlayTimeoutId = window.setTimeout(() => {
150
+ this.play(), this.autoPlayTimeoutId = null;
151
+ }, this.resumeDelay));
152
+ }), this.velocity = 0;
153
+ };
154
+ animateLogicalScroll(e, t, n) {
155
+ let r = this.getLogicalScroll(), i = performance.now(), a = !1, o = (s) => {
156
+ if (a) return;
157
+ let c = s - i, l = Math.min(c / t, 1), u = 1 - (1 - l) ** 3, d = r + (e - r) * u;
158
+ this.setLogicalScroll(d), l < 1 ? requestAnimationFrame(o) : n();
159
+ };
160
+ return requestAnimationFrame(o), () => {
161
+ a = !0;
162
+ };
163
+ }
164
+ scrollByPage(e) {
165
+ if (this.state.visibleItemIndexes.length === 0) return;
166
+ let t, n = this.state.visibleItemIndexes[0], r = this.state.visibleItemIndexes[this.state.visibleItemIndexes.length - 1], i = this.state.visibleItemIndexes.length;
167
+ e === 1 ? (t = r + 1, this.rtl && (t = r + i), t >= this.snapPoints.length - 1 && this.state.isLastItemVisible ? t = 0 : t >= this.snapPoints.length - 1 && !this.state.isLastItemVisible && (t = this.snapPoints.length - 1)) : (t = n - i, this.rtl && (t = r - i), t <= 0 && this.state.isFirstItemVisible ? t = this.snapPoints.length - 1 : t <= 0 && !this.state.isFirstItemVisible && (t = 0)), this.scrollToLogical(this.snapPoints[t], "smooth");
168
+ }
169
+ play() {
170
+ this.autoPlayIntervalId = window.setInterval(() => {
171
+ this.scrollByPage(1);
172
+ }, this.delay);
173
+ }
174
+ next() {
175
+ this.pause(), this.scrollByPage(1);
176
+ }
177
+ prev() {
178
+ this.pause(), this.scrollByPage(-1);
179
+ }
180
+ pause() {
181
+ this.autoPlayIntervalId &&= (clearInterval(this.autoPlayIntervalId), null), this.autoPlayTimeoutId &&= (clearTimeout(this.autoPlayTimeoutId), null), !this.isDragging && this.autoplay && (this.autoPlayTimeoutId = window.setTimeout(() => {
182
+ this.play(), this.autoPlayTimeoutId = null;
183
+ }, this.resumeDelay));
184
+ }
185
+ scrollToIndex(e) {
186
+ this.pause(), e >= 0 && e < this.snapPoints.length && this.scrollToLogical(this.snapPoints[e], "smooth");
187
+ }
188
+ getState() {
189
+ return {
190
+ ...this.state,
191
+ visibleItemIndexes: [...this.state.visibleItemIndexes]
192
+ };
193
+ }
194
+ getOptions() {
195
+ return {
196
+ autoplay: this.autoplay,
197
+ rtl: this.rtl,
198
+ delay: this.delay,
199
+ resumeDelay: this.resumeDelay,
200
+ breakpoints: this.breakpoints.map((e) => ({ ...e }))
201
+ };
202
+ }
203
+ update() {
204
+ this.intersectionObserver &&= (this.intersectionObserver.disconnect(), null), this.state.totalItems = this.element.querySelectorAll("[data-motion-rail-grid] > *").length, this.state.visibleItemIndexes = [], this.state.isFirstItemVisible = !1, this.state.isLastItemVisible = !1, this.setBreakPoints(), this.cacheSnapPoints(), this.observeEdgeItems(), this.handleStateChange();
205
+ }
206
+ destroy() {
207
+ let e = this.getState();
208
+ this.extensions.forEach((t) => {
209
+ t.onDestroy && t.onDestroy(this, e);
210
+ }), this.autoPlayIntervalId &&= (clearInterval(this.autoPlayIntervalId), null), this.cancelScroll &&= (this.cancelScroll(), null), this.autoPlayTimeoutId &&= (clearTimeout(this.autoPlayTimeoutId), null), this.resizeObserver &&= (this.resizeObserver.disconnect(), null), this.intersectionObserver &&= (this.intersectionObserver.disconnect(), null), this.scrollable.removeEventListener("pointerdown", this.handlePointerDown), this.scrollable.removeEventListener("pointermove", this.handlePointerMove), this.scrollable.removeEventListener("pointerup", this.handlePointerUp), this.scrollable.removeEventListener("pointerleave", this.handlePointerUp);
211
+ }
212
+ };
213
+ export { MotionRail as t };
@@ -1,3 +1,3 @@
1
1
  export { MotionRail } from "./lib/main";
2
- export type { MotionRailBreakpoint, MotionRailOptions } from "./lib/types";
2
+ export type * from "./lib/types";
3
3
  import "./style.css";
@@ -1,196 +1,2 @@
1
- function randomContainerName() {
2
- return `motion-rail-${Math.random().toString(36).substring(2, 11)}`;
3
- }
4
- function setBreakPoints(t) {
5
- let { container: n, breakpoints: r, length: i } = t;
6
- if (!n.querySelector(".motion-rail-container")) return;
7
- let a = "";
8
- n.style.containerName ? a = n.style.containerName : (a = randomContainerName(), n.style.containerName = a);
9
- let o = document.createElement("style"), s = "", c = r.filter((e) => e.width), l = c.length > 0 ? Math.min(...c.map((e) => e.width)) : null;
10
- return r.forEach((e) => {
11
- let t = e.columns || 1, n = e.gap || "0px", r = `calc((100cqw - (${t - 1} * ${n})) / ${t})`, o = "";
12
- o = e.width ? `(min-width: ${e.width}px)` : l ? `(max-width: ${l - 1}px)` : "(min-width: 0px)", s += `
13
- @container ${a} ${o} {
14
- .motion-rail-container {
15
- grid-template-columns: repeat(${i}, ${r});
16
- gap: ${n};
17
- }
18
- }
19
- `;
20
- }), o.textContent = s, document.head.appendChild(o), o;
21
- }
22
- var MotionRail = class {
23
- rtl = !1;
24
- rtlScrollType = "default";
25
- autoplay = !1;
26
- breakpoints = [];
27
- element;
28
- delay = 3e3;
29
- resumeDelay = 4e3;
30
- autoPlayIntervalId = null;
31
- autoPlayTimeoutId = null;
32
- currentIndex = 0;
33
- isDragging = !1;
34
- startX = 0;
35
- startLogicalScroll = 0;
36
- cancelScroll = null;
37
- lastPointerX = 0;
38
- lastPointerTime = 0;
39
- velocity = 0;
40
- pointerId = null;
41
- snapPoints = [];
42
- resizeObserver = null;
43
- constructor(e, n) {
44
- this.autoplay = n.autoplay || !1, this.rtl = n.rtl || !1, this.breakpoints = n.breakpoints, this.element = e, this.delay = n.delay || 3e3, this.resumeDelay = n.resumeDelay || 4e3, this.rtl && (this.rtlScrollType = this.detectRTLScrollType()), setBreakPoints({
45
- container: this.element,
46
- breakpoints: this.breakpoints,
47
- length: this.element.querySelectorAll(".motion-rail-item").length
48
- }), this.init(), this.attachPointerEvents(), this.cacheSnapPoints(), this.observeResize(), this.autoplay && this.play();
49
- }
50
- detectRTLScrollType() {
51
- let e = document.createElement("div");
52
- e.dir = "rtl", e.style.width = "1px", e.style.height = "1px", e.style.position = "absolute", e.style.top = "-1000px", e.style.overflow = "scroll";
53
- let t = document.createElement("div");
54
- t.style.width = "2px", t.style.height = "1px", e.appendChild(t), document.body.appendChild(e);
55
- let n = "default";
56
- return e.scrollLeft > 0 ? n = "reverse" : (e.scrollLeft = 1, e.scrollLeft < 0 && (n = "negative")), document.body.removeChild(e), n;
57
- }
58
- getLogicalScroll() {
59
- if (!this.rtl) return this.element.scrollLeft;
60
- let e = this.element.scrollLeft, t = this.element.scrollWidth - this.element.clientWidth;
61
- switch (this.rtlScrollType) {
62
- case "negative": return -e;
63
- case "reverse": return t - e;
64
- default: return e;
65
- }
66
- }
67
- setLogicalScroll(e) {
68
- if (!this.rtl) {
69
- this.element.scrollLeft = e;
70
- return;
71
- }
72
- let t = this.element.scrollWidth - this.element.clientWidth;
73
- switch (this.rtlScrollType) {
74
- case "negative":
75
- this.element.scrollLeft = -e;
76
- break;
77
- case "reverse":
78
- this.element.scrollLeft = t - e;
79
- break;
80
- default:
81
- this.element.scrollLeft = e;
82
- break;
83
- }
84
- }
85
- scrollToLogical(e, t = "auto") {
86
- if (!this.rtl) {
87
- this.element.scrollTo({
88
- left: e,
89
- behavior: t
90
- });
91
- return;
92
- }
93
- let n = this.element.scrollWidth - this.element.clientWidth, r;
94
- switch (this.rtlScrollType) {
95
- case "negative":
96
- r = -e;
97
- break;
98
- case "reverse":
99
- r = n - e;
100
- break;
101
- default:
102
- r = e;
103
- break;
104
- }
105
- this.element.scrollTo({
106
- left: r,
107
- behavior: t
108
- });
109
- }
110
- observeResize() {
111
- typeof ResizeObserver > "u" || (this.resizeObserver = new ResizeObserver(() => {
112
- this.cacheSnapPoints();
113
- }), this.resizeObserver.observe(this.element));
114
- }
115
- cacheSnapPoints() {
116
- let e = this.element.querySelectorAll(".motion-rail-item"), t = this.element.scrollWidth - this.element.clientWidth;
117
- this.snapPoints = Array.from(e).map((e) => Math.min(e.offsetLeft, t));
118
- }
119
- findNearestSnapPoint(e) {
120
- let t = 0, n = Infinity;
121
- for (let r of this.snapPoints) {
122
- let i = Math.abs(r - e);
123
- i < n && (n = i, t = r);
124
- }
125
- return t;
126
- }
127
- init() {
128
- this.setLogicalScroll(0), this.currentIndex = 0, this.element.style.cursor = "grab";
129
- }
130
- attachPointerEvents() {
131
- this.element.addEventListener("pointerdown", this.handlePointerDown), this.element.addEventListener("pointermove", this.handlePointerMove), this.element.addEventListener("pointerup", this.handlePointerUp), this.element.addEventListener("pointerleave", this.handlePointerUp);
132
- }
133
- handlePointerDown = (e) => {
134
- this.pointerId === null && (this.pointerId = e.pointerId, this.element.setPointerCapture(e.pointerId), this.isDragging = !0, this.startX = e.clientX, this.startLogicalScroll = this.getLogicalScroll(), this.lastPointerX = e.clientX, this.lastPointerTime = e.timeStamp, this.velocity = 0, this.element.style.cursor = "grabbing", this.element.style.userSelect = "none", this.element.style.scrollSnapType = "none", this.pause(), this.cancelScroll && this.cancelScroll(), this.autoPlayTimeoutId && clearTimeout(this.autoPlayTimeoutId));
135
- };
136
- handlePointerMove = (e) => {
137
- if (!this.isDragging || e.pointerId !== this.pointerId) return;
138
- e.preventDefault();
139
- let t = e.clientX - this.startX, n = this.startLogicalScroll - t;
140
- this.setLogicalScroll(n);
141
- let r = e.timeStamp - this.lastPointerTime;
142
- r > 0 && (this.velocity = (e.clientX - this.lastPointerX) / r, this.lastPointerX = e.clientX, this.lastPointerTime = e.timeStamp);
143
- };
144
- handlePointerUp = (e) => {
145
- if (!this.isDragging || e.pointerId !== this.pointerId) return;
146
- this.element.releasePointerCapture(e.pointerId), this.pointerId = null, this.isDragging = !1, this.element.style.cursor = "grab", this.element.style.userSelect = "";
147
- let t = Math.abs(this.velocity), n = Math.min(100 + Math.sqrt(t) * 50, 200), r = -this.velocity * n, i = this.getLogicalScroll() + r;
148
- this.cancelScroll && this.cancelScroll();
149
- let a = this.findNearestSnapPoint(i);
150
- this.currentIndex = this.snapPoints.indexOf(a), this.cancelScroll = this.animateLogicalScroll(a, n, () => {
151
- this.element.style.scrollSnapType = "x mandatory", this.cancelScroll = null, this.autoplay && (this.autoPlayTimeoutId = window.setTimeout(() => {
152
- this.play(), this.autoPlayTimeoutId = null;
153
- }, this.resumeDelay));
154
- }), this.velocity = 0;
155
- };
156
- animateLogicalScroll(e, t, n) {
157
- let r = this.getLogicalScroll(), i = performance.now(), a = !1, o = (s) => {
158
- if (a) return;
159
- let c = s - i, l = Math.min(c / t, 1), u = 1 - (1 - l) ** 3, d = r + (e - r) * u;
160
- this.setLogicalScroll(d), l < 1 ? requestAnimationFrame(o) : n();
161
- };
162
- return requestAnimationFrame(o), () => {
163
- a = !0;
164
- };
165
- }
166
- scrollByPage(e) {
167
- let t = this.currentIndex + e;
168
- t >= this.snapPoints.length ? (this.currentIndex = 0, this.scrollToLogical(this.snapPoints[0], "smooth")) : t < 0 ? (this.currentIndex = this.snapPoints.length - 1, this.scrollToLogical(this.snapPoints[this.currentIndex], "smooth")) : (this.currentIndex = t, this.scrollToLogical(this.snapPoints[t], "smooth"));
169
- }
170
- play() {
171
- this.autoPlayIntervalId = window.setInterval(() => {
172
- this.scrollByPage(1);
173
- }, this.delay);
174
- }
175
- next() {
176
- this.pause(), this.scrollByPage(1);
177
- }
178
- prev() {
179
- this.pause(), this.scrollByPage(-1);
180
- }
181
- pause() {
182
- this.autoplay && (this.autoPlayIntervalId &&= (clearInterval(this.autoPlayIntervalId), null), this.autoPlayTimeoutId &&= (clearTimeout(this.autoPlayTimeoutId), null), !this.isDragging && (this.autoPlayTimeoutId = window.setTimeout(() => {
183
- this.play(), this.autoPlayTimeoutId = null;
184
- }, this.resumeDelay)));
185
- }
186
- scrollToIndex(e) {
187
- this.pause(), e >= 0 && e < this.snapPoints.length && (this.scrollToLogical(this.snapPoints[e], "smooth"), this.currentIndex = e);
188
- }
189
- getCurrentIndex() {
190
- return this.currentIndex;
191
- }
192
- destroy() {
193
- this.autoPlayIntervalId &&= (clearInterval(this.autoPlayIntervalId), null), this.cancelScroll &&= (this.cancelScroll(), null), this.autoPlayTimeoutId &&= (clearTimeout(this.autoPlayTimeoutId), null), this.resizeObserver &&= (this.resizeObserver.disconnect(), null), this.element.removeEventListener("pointerdown", this.handlePointerDown), this.element.removeEventListener("pointermove", this.handlePointerMove), this.element.removeEventListener("pointerup", this.handlePointerUp), this.element.removeEventListener("pointerleave", this.handlePointerUp);
194
- }
195
- };
1
+ import { t as MotionRail } from "./motionrail-Dq8joDtA.js";
196
2
  export { MotionRail };
@@ -1,8 +1,9 @@
1
- (function(e,t){typeof exports==`object`&&typeof module<`u`?t(exports):typeof define==`function`&&define.amd?define([`exports`],t):(e=typeof globalThis<`u`?globalThis:e||self,t(e.MotionRail={}))})(this,function(e){function t(){return`motion-rail-${Math.random().toString(36).substring(2,11)}`}function n(e){let{container:n,breakpoints:r,length:i}=e;if(!n.querySelector(`.motion-rail-container`))return;let a=``;n.style.containerName?a=n.style.containerName:(a=t(),n.style.containerName=a);let o=document.createElement(`style`),s=``,c=r.filter(e=>e.width),l=c.length>0?Math.min(...c.map(e=>e.width)):null;return r.forEach(e=>{let t=e.columns||1,n=e.gap||`0px`,r=`calc((100cqw - (${t-1} * ${n})) / ${t})`,o=``;o=e.width?`(min-width: ${e.width}px)`:l?`(max-width: ${l-1}px)`:`(min-width: 0px)`,s+=`
2
- @container ${a} ${o} {
3
- .motion-rail-container {
4
- grid-template-columns: repeat(${i}, ${r});
5
- gap: ${n};
1
+ (function(e,t){typeof exports==`object`&&typeof module<`u`?t(exports):typeof define==`function`&&define.amd?define([`exports`],t):(e=typeof globalThis<`u`?globalThis:e||self,t(e.MotionRail={}))})(this,function(e){var t=document.createElement(`style`);t.textContent=`[data-motion-rail-scrollable]::-webkit-scrollbar{display:none}[data-motion-rail]{position:relative}[data-motion-rail] [data-motion-rail-scrollable]{anchor-name:--motionrail-scrollable;scroll-snap-type:x mandatory;scrollbar-width:none;-ms-overflow-style:none;cursor:grab;position:relative;overflow:scroll hidden;container-type:inline-size}[data-motion-rail] [data-motion-rail-scrollable]:active{cursor:grabbing}[data-motion-rail] [data-motion-rail-scrollable] [data-motion-rail-grid]{grid-auto-flow:column;height:100%;display:grid}[data-motion-rail] [data-motion-rail-scrollable] [data-motion-rail-grid]>*{pointer-events:none;scroll-snap-align:start}
2
+ /*$vite$:1*/`,document.head.appendChild(t),e.MotionRail=class{element;scrollable;rtl=!1;autoplay=!1;delay=3e3;resumeDelay=4e3;onChange;handleStateChange=()=>{let e=this.getState();this.extensions.forEach(t=>{t.onUpdate&&t.onUpdate(this,e)}),this.onChange&&this.onChange(e)};extensions=[];breakpoints=[];autoPlayIntervalId=null;autoPlayTimeoutId=null;isDragging=!1;startX=0;startLogicalScroll=0;cancelScroll=null;lastPointerX=0;lastPointerTime=0;velocity=0;pointerId=null;snapPoints=[];resizeObserver=null;intersectionObserver=null;state={totalItems:0,visibleItemIndexes:[],isFirstItemVisible:!1,isLastItemVisible:!1};constructor(e,t){this.autoplay=t.autoplay||!1,this.rtl=t.rtl||!1,this.breakpoints=t.breakpoints||[{columns:1,gap:`0px`}],this.element=e,this.extensions=t.extensions||[];let n=this.element.querySelector(`[data-motion-rail-scrollable]`);if(!n)throw Error(`MotionRail: [data-motion-rail-scrollable] element not found`);this.scrollable=n,this.delay=t.delay||3e3,this.resumeDelay=t.resumeDelay||4e3,this.onChange=t.onChange,this.state.totalItems=this.element.querySelectorAll(`[data-motion-rail-grid] > *`).length,this.setBreakPoints(),this.setLogicalScroll(0),this.attachPointerEvents(),this.cacheSnapPoints(),this.observeResize(),this.observeEdgeItems(),this.autoplay&&this.play();let r=this.getState();this.extensions.forEach(e=>{e.onInit&&e.onInit(this,r)})}randomContainerName(){return`motion-rail-${Math.random().toString(36).substring(2,11)}`}setBreakPoints(){let e=this.element.querySelector(`[data-motion-rail-scrollable]`);if(!e)return;let t=``;e.style.containerName?t=e.style.containerName:(t=this.randomContainerName(),e.style.containerName=t);let n=document.createElement(`style`),r=``,i=this.breakpoints.filter(e=>e.width),a=i.length>0?Math.min(...i.map(e=>e.width)):null;return this.breakpoints.forEach(e=>{let n=e.columns||1,i=e.gap||`0px`,o=`calc((100cqw - (${n-1} * ${i})) / ${n})`,s=``;s=e.width?`(min-width: ${e.width}px)`:a?`(max-width: ${a-1}px)`:`(min-width: 0px)`,r+=`
3
+ @container ${t} ${s} {
4
+ [data-motion-rail-grid] {
5
+ grid-template-columns: repeat(${this.state.totalItems}, ${o});
6
+ gap: ${i};
6
7
  }
7
8
  }
8
- `}),o.textContent=s,document.head.appendChild(o),o}e.MotionRail=class{rtl=!1;rtlScrollType=`default`;autoplay=!1;breakpoints=[];element;delay=3e3;resumeDelay=4e3;autoPlayIntervalId=null;autoPlayTimeoutId=null;currentIndex=0;isDragging=!1;startX=0;startLogicalScroll=0;cancelScroll=null;lastPointerX=0;lastPointerTime=0;velocity=0;pointerId=null;snapPoints=[];resizeObserver=null;constructor(e,t){this.autoplay=t.autoplay||!1,this.rtl=t.rtl||!1,this.breakpoints=t.breakpoints,this.element=e,this.delay=t.delay||3e3,this.resumeDelay=t.resumeDelay||4e3,this.rtl&&(this.rtlScrollType=this.detectRTLScrollType()),n({container:this.element,breakpoints:this.breakpoints,length:this.element.querySelectorAll(`.motion-rail-item`).length}),this.init(),this.attachPointerEvents(),this.cacheSnapPoints(),this.observeResize(),this.autoplay&&this.play()}detectRTLScrollType(){let e=document.createElement(`div`);e.dir=`rtl`,e.style.width=`1px`,e.style.height=`1px`,e.style.position=`absolute`,e.style.top=`-1000px`,e.style.overflow=`scroll`;let t=document.createElement(`div`);t.style.width=`2px`,t.style.height=`1px`,e.appendChild(t),document.body.appendChild(e);let n=`default`;return e.scrollLeft>0?n=`reverse`:(e.scrollLeft=1,e.scrollLeft<0&&(n=`negative`)),document.body.removeChild(e),n}getLogicalScroll(){if(!this.rtl)return this.element.scrollLeft;let e=this.element.scrollLeft,t=this.element.scrollWidth-this.element.clientWidth;switch(this.rtlScrollType){case`negative`:return-e;case`reverse`:return t-e;default:return e}}setLogicalScroll(e){if(!this.rtl){this.element.scrollLeft=e;return}let t=this.element.scrollWidth-this.element.clientWidth;switch(this.rtlScrollType){case`negative`:this.element.scrollLeft=-e;break;case`reverse`:this.element.scrollLeft=t-e;break;default:this.element.scrollLeft=e;break}}scrollToLogical(e,t=`auto`){if(!this.rtl){this.element.scrollTo({left:e,behavior:t});return}let n=this.element.scrollWidth-this.element.clientWidth,r;switch(this.rtlScrollType){case`negative`:r=-e;break;case`reverse`:r=n-e;break;default:r=e;break}this.element.scrollTo({left:r,behavior:t})}observeResize(){typeof ResizeObserver>`u`||(this.resizeObserver=new ResizeObserver(()=>{this.cacheSnapPoints()}),this.resizeObserver.observe(this.element))}cacheSnapPoints(){let e=this.element.querySelectorAll(`.motion-rail-item`),t=this.element.scrollWidth-this.element.clientWidth;this.snapPoints=Array.from(e).map(e=>Math.min(e.offsetLeft,t))}findNearestSnapPoint(e){let t=0,n=1/0;for(let r of this.snapPoints){let i=Math.abs(r-e);i<n&&(n=i,t=r)}return t}init(){this.setLogicalScroll(0),this.currentIndex=0,this.element.style.cursor=`grab`}attachPointerEvents(){this.element.addEventListener(`pointerdown`,this.handlePointerDown),this.element.addEventListener(`pointermove`,this.handlePointerMove),this.element.addEventListener(`pointerup`,this.handlePointerUp),this.element.addEventListener(`pointerleave`,this.handlePointerUp)}handlePointerDown=e=>{this.pointerId===null&&(this.pointerId=e.pointerId,this.element.setPointerCapture(e.pointerId),this.isDragging=!0,this.startX=e.clientX,this.startLogicalScroll=this.getLogicalScroll(),this.lastPointerX=e.clientX,this.lastPointerTime=e.timeStamp,this.velocity=0,this.element.style.cursor=`grabbing`,this.element.style.userSelect=`none`,this.element.style.scrollSnapType=`none`,this.pause(),this.cancelScroll&&this.cancelScroll(),this.autoPlayTimeoutId&&clearTimeout(this.autoPlayTimeoutId))};handlePointerMove=e=>{if(!this.isDragging||e.pointerId!==this.pointerId)return;e.preventDefault();let t=e.clientX-this.startX,n=this.startLogicalScroll-t;this.setLogicalScroll(n);let r=e.timeStamp-this.lastPointerTime;r>0&&(this.velocity=(e.clientX-this.lastPointerX)/r,this.lastPointerX=e.clientX,this.lastPointerTime=e.timeStamp)};handlePointerUp=e=>{if(!this.isDragging||e.pointerId!==this.pointerId)return;this.element.releasePointerCapture(e.pointerId),this.pointerId=null,this.isDragging=!1,this.element.style.cursor=`grab`,this.element.style.userSelect=``;let t=Math.abs(this.velocity),n=Math.min(100+Math.sqrt(t)*50,200),r=-this.velocity*n,i=this.getLogicalScroll()+r;this.cancelScroll&&this.cancelScroll();let a=this.findNearestSnapPoint(i);this.currentIndex=this.snapPoints.indexOf(a),this.cancelScroll=this.animateLogicalScroll(a,n,()=>{this.element.style.scrollSnapType=`x mandatory`,this.cancelScroll=null,this.autoplay&&(this.autoPlayTimeoutId=window.setTimeout(()=>{this.play(),this.autoPlayTimeoutId=null},this.resumeDelay))}),this.velocity=0};animateLogicalScroll(e,t,n){let r=this.getLogicalScroll(),i=performance.now(),a=!1,o=s=>{if(a)return;let c=s-i,l=Math.min(c/t,1),u=1-(1-l)**3,d=r+(e-r)*u;this.setLogicalScroll(d),l<1?requestAnimationFrame(o):n()};return requestAnimationFrame(o),()=>{a=!0}}scrollByPage(e){let t=this.currentIndex+e;t>=this.snapPoints.length?(this.currentIndex=0,this.scrollToLogical(this.snapPoints[0],`smooth`)):t<0?(this.currentIndex=this.snapPoints.length-1,this.scrollToLogical(this.snapPoints[this.currentIndex],`smooth`)):(this.currentIndex=t,this.scrollToLogical(this.snapPoints[t],`smooth`))}play(){this.autoPlayIntervalId=window.setInterval(()=>{this.scrollByPage(1)},this.delay)}next(){this.pause(),this.scrollByPage(1)}prev(){this.pause(),this.scrollByPage(-1)}pause(){this.autoplay&&(this.autoPlayIntervalId&&=(clearInterval(this.autoPlayIntervalId),null),this.autoPlayTimeoutId&&=(clearTimeout(this.autoPlayTimeoutId),null),!this.isDragging&&(this.autoPlayTimeoutId=window.setTimeout(()=>{this.play(),this.autoPlayTimeoutId=null},this.resumeDelay)))}scrollToIndex(e){this.pause(),e>=0&&e<this.snapPoints.length&&(this.scrollToLogical(this.snapPoints[e],`smooth`),this.currentIndex=e)}getCurrentIndex(){return this.currentIndex}destroy(){this.autoPlayIntervalId&&=(clearInterval(this.autoPlayIntervalId),null),this.cancelScroll&&=(this.cancelScroll(),null),this.autoPlayTimeoutId&&=(clearTimeout(this.autoPlayTimeoutId),null),this.resizeObserver&&=(this.resizeObserver.disconnect(),null),this.element.removeEventListener(`pointerdown`,this.handlePointerDown),this.element.removeEventListener(`pointermove`,this.handlePointerMove),this.element.removeEventListener(`pointerup`,this.handlePointerUp),this.element.removeEventListener(`pointerleave`,this.handlePointerUp)}}});
9
+ `}),n.textContent=r,document.head.appendChild(n),n}getLogicalScroll(){return this.scrollable.scrollLeft}setLogicalScroll(e){this.scrollable.scrollLeft=e}scrollToLogical(e,t=`auto`){this.scrollable.scrollTo({left:e,behavior:t})}observeResize(){typeof ResizeObserver>`u`||(this.resizeObserver=new ResizeObserver(()=>{this.cacheSnapPoints()}),this.resizeObserver.observe(this.scrollable))}observeEdgeItems(){if(typeof IntersectionObserver>`u`)return;let e=this.element.querySelectorAll(`[data-motion-rail-grid] > *`);if(e.length===0)return;let t=e[0],n=e[e.length-1];this.intersectionObserver=new IntersectionObserver(r=>{let i=!1;r.forEach(r=>{let a=Array.from(e).indexOf(r.target);if(a!==-1){if(r.isIntersecting)this.state.visibleItemIndexes.includes(a)||(this.state.visibleItemIndexes.push(a),this.state.visibleItemIndexes.sort((e,t)=>e-t),i=!0);else{let e=this.state.visibleItemIndexes.length;this.state.visibleItemIndexes=this.state.visibleItemIndexes.filter(e=>e!==a),this.state.visibleItemIndexes.length!==e&&(i=!0)}if(r.target===t){let e=r.isIntersecting;this.state.isFirstItemVisible!==e&&(this.state.isFirstItemVisible=e,i=!0)}else if(r.target===n){let e=r.isIntersecting;this.state.isLastItemVisible!==e&&(this.state.isLastItemVisible=e,i=!0)}}}),i&&this.handleStateChange()},{root:this.scrollable,threshold:.5}),e.forEach(e=>this.intersectionObserver.observe(e))}cacheSnapPoints(){let e=this.element.querySelectorAll(`[data-motion-rail-grid] > *`),t=this.scrollable.scrollWidth-this.scrollable.clientWidth;this.snapPoints=Array.from(e).map(e=>Math.min(e.offsetLeft,t))}findNearestSnapPoint(e){let t=0,n=1/0;for(let r of this.snapPoints){let i=Math.abs(r-e);i<n&&(n=i,t=r)}return t}attachPointerEvents(){window.matchMedia(`(pointer: fine)`).matches&&(this.scrollable.addEventListener(`pointerdown`,this.handlePointerDown),this.scrollable.addEventListener(`pointermove`,this.handlePointerMove),this.scrollable.addEventListener(`pointerup`,this.handlePointerUp),this.scrollable.addEventListener(`pointerleave`,this.handlePointerUp))}handlePointerDown=e=>{this.pointerId===null&&(this.pointerId=e.pointerId,this.scrollable.setPointerCapture(e.pointerId),this.isDragging=!0,this.startX=e.clientX,this.startLogicalScroll=this.getLogicalScroll(),this.lastPointerX=e.clientX,this.lastPointerTime=e.timeStamp,this.velocity=0,this.scrollable.style.userSelect=`none`,this.scrollable.style.scrollSnapType=`none`,this.pause(),this.cancelScroll&&this.cancelScroll(),this.autoPlayTimeoutId&&clearTimeout(this.autoPlayTimeoutId))};handlePointerMove=e=>{if(!this.isDragging||e.pointerId!==this.pointerId)return;e.preventDefault();let t=e.clientX-this.startX,n=this.startLogicalScroll-t;this.setLogicalScroll(n);let r=e.timeStamp-this.lastPointerTime;r>0&&(this.velocity=(e.clientX-this.lastPointerX)/r,this.lastPointerX=e.clientX,this.lastPointerTime=e.timeStamp)};handlePointerUp=e=>{if(!this.isDragging||e.pointerId!==this.pointerId)return;this.scrollable.releasePointerCapture(e.pointerId),this.pointerId=null,this.isDragging=!1,this.scrollable.style.userSelect=``;let t=Math.abs(this.velocity),n=Math.min(100+Math.sqrt(t)*50,200),r=-this.velocity*n,i=this.getLogicalScroll()+r;this.cancelScroll&&this.cancelScroll();let a=this.findNearestSnapPoint(i);this.cancelScroll=this.animateLogicalScroll(a,n,()=>{this.scrollable.style.scrollSnapType=`x mandatory`,this.cancelScroll=null,this.autoplay&&(this.autoPlayTimeoutId=window.setTimeout(()=>{this.play(),this.autoPlayTimeoutId=null},this.resumeDelay))}),this.velocity=0};animateLogicalScroll(e,t,n){let r=this.getLogicalScroll(),i=performance.now(),a=!1,o=s=>{if(a)return;let c=s-i,l=Math.min(c/t,1),u=1-(1-l)**3,d=r+(e-r)*u;this.setLogicalScroll(d),l<1?requestAnimationFrame(o):n()};return requestAnimationFrame(o),()=>{a=!0}}scrollByPage(e){if(this.state.visibleItemIndexes.length===0)return;let t,n=this.state.visibleItemIndexes[0],r=this.state.visibleItemIndexes[this.state.visibleItemIndexes.length-1],i=this.state.visibleItemIndexes.length;e===1?(t=r+1,this.rtl&&(t=r+i),t>=this.snapPoints.length-1&&this.state.isLastItemVisible?t=0:t>=this.snapPoints.length-1&&!this.state.isLastItemVisible&&(t=this.snapPoints.length-1)):(t=n-i,this.rtl&&(t=r-i),t<=0&&this.state.isFirstItemVisible?t=this.snapPoints.length-1:t<=0&&!this.state.isFirstItemVisible&&(t=0)),this.scrollToLogical(this.snapPoints[t],`smooth`)}play(){this.autoPlayIntervalId=window.setInterval(()=>{this.scrollByPage(1)},this.delay)}next(){this.pause(),this.scrollByPage(1)}prev(){this.pause(),this.scrollByPage(-1)}pause(){this.autoPlayIntervalId&&=(clearInterval(this.autoPlayIntervalId),null),this.autoPlayTimeoutId&&=(clearTimeout(this.autoPlayTimeoutId),null),!this.isDragging&&this.autoplay&&(this.autoPlayTimeoutId=window.setTimeout(()=>{this.play(),this.autoPlayTimeoutId=null},this.resumeDelay))}scrollToIndex(e){this.pause(),e>=0&&e<this.snapPoints.length&&this.scrollToLogical(this.snapPoints[e],`smooth`)}getState(){return{...this.state,visibleItemIndexes:[...this.state.visibleItemIndexes]}}getOptions(){return{autoplay:this.autoplay,rtl:this.rtl,delay:this.delay,resumeDelay:this.resumeDelay,breakpoints:this.breakpoints.map(e=>({...e}))}}update(){this.intersectionObserver&&=(this.intersectionObserver.disconnect(),null),this.state.totalItems=this.element.querySelectorAll(`[data-motion-rail-grid] > *`).length,this.state.visibleItemIndexes=[],this.state.isFirstItemVisible=!1,this.state.isLastItemVisible=!1,this.setBreakPoints(),this.cacheSnapPoints(),this.observeEdgeItems(),this.handleStateChange()}destroy(){let e=this.getState();this.extensions.forEach(t=>{t.onDestroy&&t.onDestroy(this,e)}),this.autoPlayIntervalId&&=(clearInterval(this.autoPlayIntervalId),null),this.cancelScroll&&=(this.cancelScroll(),null),this.autoPlayTimeoutId&&=(clearTimeout(this.autoPlayTimeoutId),null),this.resizeObserver&&=(this.resizeObserver.disconnect(),null),this.intersectionObserver&&=(this.intersectionObserver.disconnect(),null),this.scrollable.removeEventListener(`pointerdown`,this.handlePointerDown),this.scrollable.removeEventListener(`pointermove`,this.handlePointerMove),this.scrollable.removeEventListener(`pointerup`,this.handlePointerUp),this.scrollable.removeEventListener(`pointerleave`,this.handlePointerUp)}}});
package/dist/style.css CHANGED
@@ -1,2 +1 @@
1
- .motion-rail::-webkit-scrollbar{display:none}.motion-rail{scroll-snap-type:x mandatory;scrollbar-width:none;-ms-overflow-style:none;position:relative;overflow:scroll hidden;container-type:inline-size}.motion-rail .motion-rail-container{grid-auto-flow:column;height:100%;display:grid}
2
- /*$vite$:1*/
1
+ [data-motion-rail-scrollable]::-webkit-scrollbar{display:none}[data-motion-rail]{position:relative}[data-motion-rail] [data-motion-rail-scrollable]{anchor-name:--motionrail-scrollable;scroll-snap-type:x mandatory;scrollbar-width:none;-ms-overflow-style:none;cursor:grab;position:relative;overflow:scroll hidden;container-type:inline-size}[data-motion-rail] [data-motion-rail-scrollable]:active{cursor:grabbing}[data-motion-rail] [data-motion-rail-scrollable] [data-motion-rail-grid]{grid-auto-flow:column;height:100%;display:grid}[data-motion-rail] [data-motion-rail-scrollable] [data-motion-rail-grid]>*{pointer-events:none;scroll-snap-align:start}
package/package.json CHANGED
@@ -1,8 +1,7 @@
1
1
  {
2
2
  "name": "motionrail",
3
- "version": "0.0.3",
3
+ "version": "0.1.0",
4
4
  "type": "module",
5
- "main": "./dist/motionrail.umd.cjs",
6
5
  "module": "./dist/motionrail.js",
7
6
  "types": "./dist/motionrail.d.ts",
8
7
  "exports": {
@@ -11,6 +10,29 @@
11
10
  "require": "./dist/motionrail.umd.cjs",
12
11
  "types": "./dist/motionrail.d.ts"
13
12
  },
13
+ "./extensions/arrows": {
14
+ "import": "./dist/extensions/arrows.js",
15
+ "require": "./dist/extensions/arrows.umd.cjs",
16
+ "types": "./dist/extensions/arrows/main.d.ts"
17
+ },
18
+ "./extensions/arrows/style.css": "./dist/extensions/arrows/style.css",
19
+ "./extensions/dots": {
20
+ "import": "./dist/extensions/dots.js",
21
+ "require": "./dist/extensions/dots.umd.cjs",
22
+ "types": "./dist/extensions/dots/main.d.ts"
23
+ },
24
+ "./extensions/dots/style.css": "./dist/extensions/dots/style.css",
25
+ "./extensions/thumbnails": {
26
+ "import": "./dist/extensions/thumbnails.js",
27
+ "require": "./dist/extensions/thumbnails.umd.cjs",
28
+ "types": "./dist/extensions/thumbnails.d.ts"
29
+ },
30
+ "./extensions/thumbnails/style.css": "./dist/extensions/thumbnails/style.css",
31
+ "./extensions/logger": {
32
+ "import": "./dist/extensions/logger.js",
33
+ "require": "./dist/extensions/logger.umd.cjs",
34
+ "types": "./dist/extensions/logger.d.ts"
35
+ },
14
36
  "./style.css": "./dist/style.css"
15
37
  },
16
38
  "files": [
@@ -22,7 +44,14 @@
22
44
  },
23
45
  "scripts": {
24
46
  "dev": "vite",
25
- "build": "vite build && tsc",
47
+ "build": "npm run build:es && npm run build:umd",
48
+ "build:es": "vite build && tsc",
49
+ "build:umd": "npm run build:umd:main && npm run build:umd:arrows && npm run build:umd:dots && npm run build:umd:thumbnails && npm run build:umd:logger",
50
+ "build:umd:main": "BUILD_UMD=true ENTRY_PATH=src/motionrail.ts LIB_NAME=MotionRail FILE_NAME=motionrail.umd vite build",
51
+ "build:umd:arrows": "BUILD_UMD=true ENTRY_PATH=src/extensions/arrows/main.ts LIB_NAME=MotionRailArrows FILE_NAME=extensions/arrows.umd vite build",
52
+ "build:umd:dots": "BUILD_UMD=true ENTRY_PATH=src/extensions/dots/main.ts LIB_NAME=MotionRailDots FILE_NAME=extensions/dots.umd vite build",
53
+ "build:umd:thumbnails": "BUILD_UMD=true ENTRY_PATH=src/extensions/thumbnails/main.ts LIB_NAME=MotionRailThumbnails FILE_NAME=extensions/thumbnails.umd vite build",
54
+ "build:umd:logger": "BUILD_UMD=true ENTRY_PATH=src/extensions/logger.ts LIB_NAME=MotionRailLogger FILE_NAME=extensions/logger.umd vite build",
26
55
  "preview": "vite preview",
27
56
  "prepare": "husky"
28
57
  },
@@ -1,7 +0,0 @@
1
- import type { MotionRailBreakpoint } from "./types";
2
- export declare function randomContainerName(): string;
3
- export declare function setBreakPoints(par: {
4
- container: HTMLElement;
5
- breakpoints: MotionRailBreakpoint[];
6
- length: number;
7
- }): HTMLStyleElement | undefined;