@griddle/svelte 0.1.1 → 0.1.3
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 +25 -0
- package/dist/GriddleGrid.svelte +4 -8
- package/dist/LoopGrid.svelte +3 -6
- package/dist/animation.d.ts +4 -0
- package/dist/animation.js +49 -0
- package/package.json +5 -4
package/README.md
CHANGED
|
@@ -38,6 +38,31 @@ shown above). `svelte` (^4 or ^5) is expected to already be in your app.
|
|
|
38
38
|
</GriddleGrid>
|
|
39
39
|
```
|
|
40
40
|
|
|
41
|
+
## Animation configuration
|
|
42
|
+
|
|
43
|
+
Tile repositioning and lift animations share `config.animation` with the other
|
|
44
|
+
Griddle adapters. Repositioning defaults to a smooth 320 ms ease-out and rapid
|
|
45
|
+
repacks continue from each tile's current visual position.
|
|
46
|
+
|
|
47
|
+
```js
|
|
48
|
+
const api = createGriddle({
|
|
49
|
+
config: {
|
|
50
|
+
cols: 12, rows: 12, unitWidth: 75, unitHeight: 75,
|
|
51
|
+
animation: {
|
|
52
|
+
repositionDurationMs: 320,
|
|
53
|
+
repositionEasing: 'cubic-bezier(0.22, 1, 0.36, 1)',
|
|
54
|
+
liftDurationMs: 160,
|
|
55
|
+
liftEasing: 'cubic-bezier(0.22, 1, 0.36, 1)',
|
|
56
|
+
respectReducedMotion: true,
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
tiles,
|
|
60
|
+
});
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Use `animation.enabled: false` to disable all adapter animations. A duration of
|
|
64
|
+
`0` disables only that transition.
|
|
65
|
+
|
|
41
66
|
## Loop mode (infinite gallery)
|
|
42
67
|
|
|
43
68
|
Enable `loop` in the config and the content repeats endlessly with drag-to-pan
|
package/dist/GriddleGrid.svelte
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script>import { onMount, onDestroy, tick, createEventDispatcher } from 'svelte';
|
|
2
2
|
import { visibleRange, visibleTiles, gridContentSize, DragController, computeTileLayout, resolveStickyStacking, isOutOfFlow, pixelsToPin, } from '@griddle/core';
|
|
3
3
|
import LoopGrid from './LoopGrid.svelte';
|
|
4
|
+
import { animateReposition, liftTransition } from './animation.js';
|
|
4
5
|
export let api;
|
|
5
6
|
export let height = '100%';
|
|
6
7
|
export let showGrid = true;
|
|
@@ -263,12 +264,7 @@ async function runFlip(_v) {
|
|
|
263
264
|
const dx = p.x - x;
|
|
264
265
|
const dy = p.y - y;
|
|
265
266
|
if (dx !== 0 || dy !== 0) {
|
|
266
|
-
node
|
|
267
|
-
node.style.transform = `translate(${dx}px, ${dy}px)`;
|
|
268
|
-
requestAnimationFrame(() => {
|
|
269
|
-
node.style.transition = 'transform 220ms cubic-bezier(.2,.7,.2,1)';
|
|
270
|
-
node.style.transform = 'translate(0,0)';
|
|
271
|
-
});
|
|
267
|
+
animateReposition(node, dx, dy, cfg.animation);
|
|
272
268
|
}
|
|
273
269
|
}
|
|
274
270
|
prevRects.set(t.id, { x, y });
|
|
@@ -408,6 +404,7 @@ $: indicatorRect = (() => {
|
|
|
408
404
|
style:width={layout.width + 'px'}
|
|
409
405
|
style:height={layout.height + 'px'}
|
|
410
406
|
style:transform={layout.transform ?? ''}
|
|
407
|
+
style:transition={isDragging(tile.id) ? liftTransition(cfg.animation) : ''}
|
|
411
408
|
style:z-index={layout.zIndex}
|
|
412
409
|
>
|
|
413
410
|
<slot name="tile" {tile} />
|
|
@@ -447,7 +444,7 @@ $: indicatorRect = (() => {
|
|
|
447
444
|
box-sizing: border-box;
|
|
448
445
|
cursor: grab;
|
|
449
446
|
user-select: none;
|
|
450
|
-
will-change: transform;
|
|
447
|
+
will-change: transform, translate;
|
|
451
448
|
z-index: 1;
|
|
452
449
|
}
|
|
453
450
|
.griddle-tile.griddle-resizing {
|
|
@@ -463,7 +460,6 @@ $: indicatorRect = (() => {
|
|
|
463
460
|
is set inline (cursor delta), so the scale is folded into a CSS filter
|
|
464
461
|
to avoid clobbering it. */
|
|
465
462
|
filter: drop-shadow(0 8px 16px rgba(0, 0, 0, 0.18));
|
|
466
|
-
transition: filter 120ms ease-out, opacity 120ms ease-out;
|
|
467
463
|
}
|
|
468
464
|
.griddle-drop-indicator {
|
|
469
465
|
position: absolute;
|
package/dist/LoopGrid.svelte
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
// NO native scrolling (wheel deltas feed the camera directly).
|
|
5
5
|
import { onMount, onDestroy, tick, createEventDispatcher } from 'svelte';
|
|
6
6
|
import { DragController, PanController, loopInstances, loopPeriod, resolveLoop, } from '@griddle/core';
|
|
7
|
+
import { animateReposition } from './animation.js';
|
|
7
8
|
export let api;
|
|
8
9
|
export let height = '100%';
|
|
9
10
|
export let showGrid = true;
|
|
@@ -329,12 +330,7 @@ async function runFlip(_v) {
|
|
|
329
330
|
if ((dx !== 0 || dy !== 0) &&
|
|
330
331
|
Math.abs(dx) < period.width / 2 &&
|
|
331
332
|
Math.abs(dy) < period.height / 2) {
|
|
332
|
-
node
|
|
333
|
-
node.style.transform = `translate(${dx}px, ${dy}px)`;
|
|
334
|
-
requestAnimationFrame(() => {
|
|
335
|
-
node.style.transition = 'transform 220ms cubic-bezier(.2,.7,.2,1)';
|
|
336
|
-
node.style.transform = 'translate(0,0)';
|
|
337
|
-
});
|
|
333
|
+
animateReposition(node, dx, dy, cfg.animation);
|
|
338
334
|
}
|
|
339
335
|
}
|
|
340
336
|
}
|
|
@@ -472,6 +468,7 @@ function instanceLayout(inst, r) {
|
|
|
472
468
|
position: absolute;
|
|
473
469
|
box-sizing: border-box;
|
|
474
470
|
user-select: none;
|
|
471
|
+
will-change: translate;
|
|
475
472
|
z-index: 1;
|
|
476
473
|
border-radius: var(--griddle-tile-radius, 4px);
|
|
477
474
|
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { type GridAnimationConfig } from '@griddle/core';
|
|
2
|
+
/** Continue rapid repacks from the tile's current visual position. */
|
|
3
|
+
export declare function animateReposition(node: HTMLElement, deltaX: number, deltaY: number, config?: GridAnimationConfig): void;
|
|
4
|
+
export declare function liftTransition(config?: GridAnimationConfig): string;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { resolveAnimationConfig, } from '@griddle/core';
|
|
2
|
+
const pendingFrames = new WeakMap();
|
|
3
|
+
function reducedMotionRequested(config) {
|
|
4
|
+
const animation = resolveAnimationConfig(config);
|
|
5
|
+
return animation.respectReducedMotion &&
|
|
6
|
+
typeof window !== 'undefined' &&
|
|
7
|
+
typeof window.matchMedia === 'function' &&
|
|
8
|
+
window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
9
|
+
}
|
|
10
|
+
function currentTranslation(node) {
|
|
11
|
+
if (typeof window === 'undefined')
|
|
12
|
+
return { x: 0, y: 0 };
|
|
13
|
+
const translate = window.getComputedStyle(node).translate;
|
|
14
|
+
if (!translate || translate === 'none')
|
|
15
|
+
return { x: 0, y: 0 };
|
|
16
|
+
const [x = '0', y = '0'] = translate.split(/\s+/);
|
|
17
|
+
return { x: Number.parseFloat(x) || 0, y: Number.parseFloat(y) || 0 };
|
|
18
|
+
}
|
|
19
|
+
/** Continue rapid repacks from the tile's current visual position. */
|
|
20
|
+
export function animateReposition(node, deltaX, deltaY, config) {
|
|
21
|
+
const pending = pendingFrames.get(node);
|
|
22
|
+
if (pending !== undefined)
|
|
23
|
+
cancelAnimationFrame(pending);
|
|
24
|
+
const animation = resolveAnimationConfig(config);
|
|
25
|
+
if (!animation.enabled || animation.repositionDurationMs === 0 || reducedMotionRequested(config)) {
|
|
26
|
+
node.style.transition = 'none';
|
|
27
|
+
node.style.translate = '0px 0px';
|
|
28
|
+
pendingFrames.delete(node);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const current = currentTranslation(node);
|
|
32
|
+
node.style.transition = 'none';
|
|
33
|
+
node.style.translate = `${deltaX + current.x}px ${deltaY + current.y}px`;
|
|
34
|
+
void node.offsetWidth;
|
|
35
|
+
const frame = requestAnimationFrame(() => {
|
|
36
|
+
pendingFrames.delete(node);
|
|
37
|
+
node.style.transition =
|
|
38
|
+
`translate ${animation.repositionDurationMs}ms ${animation.repositionEasing}`;
|
|
39
|
+
node.style.translate = '0px 0px';
|
|
40
|
+
});
|
|
41
|
+
pendingFrames.set(node, frame);
|
|
42
|
+
}
|
|
43
|
+
export function liftTransition(config) {
|
|
44
|
+
const animation = resolveAnimationConfig(config);
|
|
45
|
+
const duration = !animation.enabled || reducedMotionRequested(config)
|
|
46
|
+
? 0
|
|
47
|
+
: animation.liftDurationMs;
|
|
48
|
+
return `filter ${duration}ms ${animation.liftEasing}, opacity ${duration}ms ${animation.liftEasing}`;
|
|
49
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@griddle/svelte",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Svelte bindings for Griddle — a headless, zero-dependency grid/canvas engine.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"grid",
|
|
@@ -49,14 +49,15 @@
|
|
|
49
49
|
},
|
|
50
50
|
"scripts": {
|
|
51
51
|
"build": "svelte-package -i src -o dist",
|
|
52
|
-
"
|
|
52
|
+
"test": "node --test test/animation.test.mjs",
|
|
53
|
+
"prepublishOnly": "npm run build && npm test"
|
|
53
54
|
},
|
|
54
55
|
"peerDependencies": {
|
|
55
|
-
"@griddle/core": "^0.1.
|
|
56
|
+
"@griddle/core": "^0.1.3",
|
|
56
57
|
"svelte": "^4.0.0 || ^5.0.0"
|
|
57
58
|
},
|
|
58
59
|
"devDependencies": {
|
|
59
|
-
"@griddle/core": "^0.1.
|
|
60
|
+
"@griddle/core": "^0.1.3",
|
|
60
61
|
"@sveltejs/package": "^2.5.8",
|
|
61
62
|
"svelte": "^4.2.0",
|
|
62
63
|
"svelte-check": "^4.7.2",
|