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 +319 -15
- package/dist/extensions/arrows/main.d.ts +7 -0
- package/dist/extensions/arrows/style.css +1 -0
- package/dist/extensions/arrows.js +25 -0
- package/dist/extensions/arrows.umd.cjs +2 -0
- package/dist/extensions/dots/main.d.ts +6 -0
- package/dist/extensions/dots/style.css +1 -0
- package/dist/extensions/dots.js +44 -0
- package/dist/extensions/dots.umd.cjs +2 -0
- package/dist/extensions/logger.d.ts +2 -0
- package/dist/extensions/logger.js +16 -0
- package/dist/extensions/logger.umd.cjs +2 -0
- package/dist/extensions/thumbnails/main.d.ts +6 -0
- package/dist/extensions/thumbnails/style.css +1 -0
- package/dist/extensions/thumbnails.js +42 -0
- package/dist/extensions/thumbnails.umd.cjs +2 -0
- package/dist/lib/main.d.ts +29 -7
- package/dist/lib/types.d.ts +16 -1
- package/dist/motionrail-Dq8joDtA.js +213 -0
- package/dist/motionrail.d.ts +1 -1
- package/dist/motionrail.js +1 -195
- package/dist/motionrail.umd.cjs +7 -6
- package/dist/style.css +1 -2
- package/package.json +32 -3
- package/dist/lib/utils.d.ts +0 -7
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
|
|
30
|
-
<div
|
|
31
|
-
<div
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
-
|
|
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
|
|
209
|
-
<div
|
|
210
|
-
<div
|
|
211
|
-
|
|
212
|
-
|
|
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
|
-
|
|
543
|
+
[data-motion-rail] {
|
|
249
544
|
height: 400px; /* Set carousel height */
|
|
250
545
|
}
|
|
251
546
|
|
|
252
|
-
|
|
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 @@
|
|
|
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 @@
|
|
|
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,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 @@
|
|
|
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});
|
package/dist/lib/main.d.ts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
}
|
package/dist/lib/types.d.ts
CHANGED
|
@@ -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
|
-
|
|
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 };
|
package/dist/motionrail.d.ts
CHANGED
package/dist/motionrail.js
CHANGED
|
@@ -1,196 +1,2 @@
|
|
|
1
|
-
|
|
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 };
|
package/dist/motionrail.umd.cjs
CHANGED
|
@@ -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){
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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
|
-
`}),
|
|
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
|
-
|
|
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
|
+
"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": "
|
|
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
|
},
|
package/dist/lib/utils.d.ts
DELETED
|
@@ -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;
|