@griddle/svelte 0.1.0 → 0.1.2
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 +12 -11
- package/dist/LoopGrid.svelte +3 -6
- package/dist/animation.d.ts +4 -0
- package/dist/animation.js +49 -0
- package/package.json +12 -6
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;
|
|
@@ -16,6 +17,10 @@ $: tilesAll = $tilesStore;
|
|
|
16
17
|
$: ver = $versionStore;
|
|
17
18
|
// Loop mode delegates rendering to LoopGrid.
|
|
18
19
|
$: loopOn = cfg.loop?.enabled === true;
|
|
20
|
+
// 'none' scroll mode: the grid sizes to content and lets the host page own
|
|
21
|
+
// scrolling/panning — no internal scroll box, no touch-action lock.
|
|
22
|
+
$: contained = cfg.scroll !== 'none';
|
|
23
|
+
$: heightCss = typeof height === 'number' ? height + 'px' : height;
|
|
19
24
|
$: colSize = cfg.unitWidth + (cfg.gap ?? 0);
|
|
20
25
|
$: rowSize = cfg.unitHeight + (cfg.gap ?? 0);
|
|
21
26
|
$: halfGap = (cfg.gap ?? 0) / 2;
|
|
@@ -259,12 +264,7 @@ async function runFlip(_v) {
|
|
|
259
264
|
const dx = p.x - x;
|
|
260
265
|
const dy = p.y - y;
|
|
261
266
|
if (dx !== 0 || dy !== 0) {
|
|
262
|
-
node
|
|
263
|
-
node.style.transform = `translate(${dx}px, ${dy}px)`;
|
|
264
|
-
requestAnimationFrame(() => {
|
|
265
|
-
node.style.transition = 'transform 220ms cubic-bezier(.2,.7,.2,1)';
|
|
266
|
-
node.style.transform = 'translate(0,0)';
|
|
267
|
-
});
|
|
267
|
+
animateReposition(node, dx, dy, cfg.animation);
|
|
268
268
|
}
|
|
269
269
|
}
|
|
270
270
|
prevRects.set(t.id, { x, y });
|
|
@@ -363,7 +363,9 @@ $: indicatorRect = (() => {
|
|
|
363
363
|
<div
|
|
364
364
|
bind:this={scrollEl}
|
|
365
365
|
class="griddle-scroll"
|
|
366
|
-
style:
|
|
366
|
+
style:overflow={contained ? 'auto' : 'visible'}
|
|
367
|
+
style:touch-action={contained ? 'none' : 'auto'}
|
|
368
|
+
style:height={contained ? heightCss : 'auto'}
|
|
367
369
|
>
|
|
368
370
|
<div
|
|
369
371
|
class="griddle-content"
|
|
@@ -402,6 +404,7 @@ $: indicatorRect = (() => {
|
|
|
402
404
|
style:width={layout.width + 'px'}
|
|
403
405
|
style:height={layout.height + 'px'}
|
|
404
406
|
style:transform={layout.transform ?? ''}
|
|
407
|
+
style:transition={isDragging(tile.id) ? liftTransition(cfg.animation) : ''}
|
|
405
408
|
style:z-index={layout.zIndex}
|
|
406
409
|
>
|
|
407
410
|
<slot name="tile" {tile} />
|
|
@@ -422,9 +425,8 @@ $: indicatorRect = (() => {
|
|
|
422
425
|
|
|
423
426
|
<style>
|
|
424
427
|
.griddle-scroll {
|
|
428
|
+
/* overflow / touch-action / height are set inline, driven by cfg.scroll. */
|
|
425
429
|
position: relative;
|
|
426
|
-
overflow: auto;
|
|
427
|
-
touch-action: none;
|
|
428
430
|
}
|
|
429
431
|
.griddle-content {
|
|
430
432
|
position: relative;
|
|
@@ -442,7 +444,7 @@ $: indicatorRect = (() => {
|
|
|
442
444
|
box-sizing: border-box;
|
|
443
445
|
cursor: grab;
|
|
444
446
|
user-select: none;
|
|
445
|
-
will-change: transform;
|
|
447
|
+
will-change: transform, translate;
|
|
446
448
|
z-index: 1;
|
|
447
449
|
}
|
|
448
450
|
.griddle-tile.griddle-resizing {
|
|
@@ -458,7 +460,6 @@ $: indicatorRect = (() => {
|
|
|
458
460
|
is set inline (cursor delta), so the scale is folded into a CSS filter
|
|
459
461
|
to avoid clobbering it. */
|
|
460
462
|
filter: drop-shadow(0 8px 16px rgba(0, 0, 0, 0.18));
|
|
461
|
-
transition: filter 120ms ease-out, opacity 120ms ease-out;
|
|
462
463
|
}
|
|
463
464
|
.griddle-drop-indicator {
|
|
464
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.2",
|
|
4
4
|
"description": "Svelte bindings for Griddle — a headless, zero-dependency grid/canvas engine.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"grid",
|
|
@@ -26,7 +26,10 @@
|
|
|
26
26
|
"license": "MIT",
|
|
27
27
|
"author": "Trustybits",
|
|
28
28
|
"type": "module",
|
|
29
|
-
"sideEffects": [
|
|
29
|
+
"sideEffects": [
|
|
30
|
+
"**/*.svelte",
|
|
31
|
+
"**/*.css"
|
|
32
|
+
],
|
|
30
33
|
"svelte": "./dist/index.js",
|
|
31
34
|
"types": "./dist/index.d.ts",
|
|
32
35
|
"exports": {
|
|
@@ -35,7 +38,9 @@
|
|
|
35
38
|
"svelte": "./dist/index.js"
|
|
36
39
|
}
|
|
37
40
|
},
|
|
38
|
-
"files": [
|
|
41
|
+
"files": [
|
|
42
|
+
"dist"
|
|
43
|
+
],
|
|
39
44
|
"engines": {
|
|
40
45
|
"node": ">=18"
|
|
41
46
|
},
|
|
@@ -44,14 +49,15 @@
|
|
|
44
49
|
},
|
|
45
50
|
"scripts": {
|
|
46
51
|
"build": "svelte-package -i src -o dist",
|
|
47
|
-
"
|
|
52
|
+
"test": "node --test test/animation.test.mjs",
|
|
53
|
+
"prepublishOnly": "npm run build && npm test"
|
|
48
54
|
},
|
|
49
55
|
"peerDependencies": {
|
|
50
|
-
"@griddle/core": "^0.1.
|
|
56
|
+
"@griddle/core": "^0.1.2",
|
|
51
57
|
"svelte": "^4.0.0 || ^5.0.0"
|
|
52
58
|
},
|
|
53
59
|
"devDependencies": {
|
|
54
|
-
"@griddle/core": "^0.1.
|
|
60
|
+
"@griddle/core": "^0.1.2",
|
|
55
61
|
"@sveltejs/package": "^2.5.8",
|
|
56
62
|
"svelte": "^4.2.0",
|
|
57
63
|
"svelte-check": "^4.7.2",
|