@griddle/core 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.
Files changed (78) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +54 -0
  3. package/dist/compaction.d.ts +3 -0
  4. package/dist/compaction.d.ts.map +1 -0
  5. package/dist/compaction.js +85 -0
  6. package/dist/compaction.js.map +1 -0
  7. package/dist/drag.d.ts +52 -0
  8. package/dist/drag.d.ts.map +1 -0
  9. package/dist/drag.js +120 -0
  10. package/dist/drag.js.map +1 -0
  11. package/dist/events.d.ts +9 -0
  12. package/dist/events.d.ts.map +1 -0
  13. package/dist/events.js +22 -0
  14. package/dist/events.js.map +1 -0
  15. package/dist/geometry.d.ts +33 -0
  16. package/dist/geometry.d.ts.map +1 -0
  17. package/dist/geometry.js +164 -0
  18. package/dist/geometry.js.map +1 -0
  19. package/dist/grid.d.ts +100 -0
  20. package/dist/grid.d.ts.map +1 -0
  21. package/dist/grid.js +539 -0
  22. package/dist/grid.js.map +1 -0
  23. package/dist/group-drag.d.ts +41 -0
  24. package/dist/group-drag.d.ts.map +1 -0
  25. package/dist/group-drag.js +97 -0
  26. package/dist/group-drag.js.map +1 -0
  27. package/dist/index.d.ts +12 -0
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +11 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/loop.d.ts +99 -0
  32. package/dist/loop.d.ts.map +1 -0
  33. package/dist/loop.js +188 -0
  34. package/dist/loop.js.map +1 -0
  35. package/dist/movement.d.ts +17 -0
  36. package/dist/movement.d.ts.map +1 -0
  37. package/dist/movement.js +333 -0
  38. package/dist/movement.js.map +1 -0
  39. package/dist/packing.d.ts +16 -0
  40. package/dist/packing.d.ts.map +1 -0
  41. package/dist/packing.js +159 -0
  42. package/dist/packing.js.map +1 -0
  43. package/dist/pan.d.ts +58 -0
  44. package/dist/pan.d.ts.map +1 -0
  45. package/dist/pan.js +174 -0
  46. package/dist/pan.js.map +1 -0
  47. package/dist/positioning.d.ts +117 -0
  48. package/dist/positioning.d.ts.map +1 -0
  49. package/dist/positioning.js +251 -0
  50. package/dist/positioning.js.map +1 -0
  51. package/dist/repack.d.ts +4 -0
  52. package/dist/repack.d.ts.map +1 -0
  53. package/dist/repack.js +179 -0
  54. package/dist/repack.js.map +1 -0
  55. package/dist/types.d.ts +236 -0
  56. package/dist/types.d.ts.map +1 -0
  57. package/dist/types.js +4 -0
  58. package/dist/types.js.map +1 -0
  59. package/dist/virtualize.d.ts +27 -0
  60. package/dist/virtualize.d.ts.map +1 -0
  61. package/dist/virtualize.js +53 -0
  62. package/dist/virtualize.js.map +1 -0
  63. package/package.json +55 -0
  64. package/src/compaction.ts +80 -0
  65. package/src/drag.ts +146 -0
  66. package/src/events.ts +25 -0
  67. package/src/geometry.ts +199 -0
  68. package/src/grid.ts +578 -0
  69. package/src/group-drag.ts +120 -0
  70. package/src/index.ts +78 -0
  71. package/src/loop.ts +246 -0
  72. package/src/movement.ts +363 -0
  73. package/src/packing.ts +169 -0
  74. package/src/pan.ts +217 -0
  75. package/src/positioning.ts +292 -0
  76. package/src/repack.ts +212 -0
  77. package/src/types.ts +262 -0
  78. package/src/virtualize.ts +77 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Trustybits
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,54 @@
1
+ # @griddle/core
2
+
3
+ Headless grid/canvas engine — pure TypeScript, **zero runtime dependencies**.
4
+
5
+ `@griddle/core` is the logic half of [Griddle](https://github.com/Trustybits/griddle):
6
+ tile placement, movement, swap/push resolution, compaction (gravity),
7
+ virtualization, loop/infinite-canvas math, and serialization. It has no
8
+ rendering and no framework ties, so you can run it anywhere — in a browser, on a
9
+ server, or behind one of the framework adapters.
10
+
11
+ ## Install
12
+
13
+ ```sh
14
+ npm install @griddle/core
15
+ ```
16
+
17
+ ## Usage
18
+
19
+ ```ts
20
+ import { Grid } from '@griddle/core';
21
+
22
+ const grid = new Grid({ cols: 12, rows: 12, unitWidth: 75, unitHeight: 75 });
23
+
24
+ grid.addTile({ id: 't1', col: 0, row: 0, w: 2, h: 2 });
25
+ grid.moveTile('t1', { col: 4, row: 4 }); // resolves collisions via rules 1–6
26
+
27
+ const json = grid.toJSON(); // serialize
28
+ grid.loadJSON(json); // restore
29
+ ```
30
+
31
+ ## Concepts
32
+
33
+ - **Unit** — a fixed `unitWidth × unitHeight` cell.
34
+ - **Grid** — `cols × rows`; either axis can be `Infinity` for an infinite canvas.
35
+ - **Tile** — an object at `(col, row)` with a `w × h` footprint.
36
+ - **Repack** — after a move, tiles resolve collisions via a deterministic ruleset.
37
+ - **Compaction** — optional gravity that backfills gaps toward a chosen edge.
38
+ - **Loop** — optional infinite-gallery mode with drag-to-pan physics.
39
+
40
+ ## Framework adapters
41
+
42
+ Thin presentation layers built on this core:
43
+
44
+ - [`@griddle/react`](https://www.npmjs.com/package/@griddle/react)
45
+ - [`@griddle/vue`](https://www.npmjs.com/package/@griddle/vue)
46
+ - [`@griddle/svelte`](https://www.npmjs.com/package/@griddle/svelte)
47
+
48
+ See the full [movement ruleset](https://github.com/Trustybits/griddle/blob/master/docs/movement.md)
49
+ and [loop mode](https://github.com/Trustybits/griddle/blob/master/docs/loop.md)
50
+ docs, or the [main repository](https://github.com/Trustybits/griddle).
51
+
52
+ ## License
53
+
54
+ MIT © Trustybits
@@ -0,0 +1,3 @@
1
+ import type { Grid } from './grid.js';
2
+ export declare function compact(grid: Grid): string[];
3
+ //# sourceMappingURL=compaction.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compaction.d.ts","sourceRoot":"","sources":["../src/compaction.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAItC,wBAAgB,OAAO,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,EAAE,CAyC5C"}
@@ -0,0 +1,85 @@
1
+ // Compaction (gravity) — pull tiles toward a configured edge or anchor cell.
2
+ //
3
+ // Runs after moves/adds/removes when gravity is not 'none'. Iteratively moves
4
+ // each tile one step toward the gravity anchor until no tile can move any closer.
5
+ import { rectsOverlap, tileRect } from './geometry.js';
6
+ import { isInFlow } from './positioning.js';
7
+ export function compact(grid) {
8
+ const gravity = grid.config.gravity ?? 'none';
9
+ if (gravity === 'none')
10
+ return [];
11
+ const moved = new Set();
12
+ // Iteratively move tiles one cell toward anchor; stop when no tile moves.
13
+ for (let iter = 0; iter < 10000; iter++) {
14
+ let anyMoved = false;
15
+ // Order tiles so that those closest to the anchor move first — otherwise
16
+ // a further tile may try to move into a cell not yet vacated by the closer
17
+ // one. Out-of-flow tiles (absolute/fixed) are skipped entirely by gravity.
18
+ const ordered = [...grid.tiles]
19
+ .filter((t) => isInFlow(t))
20
+ .sort((a, b) => distanceToAnchor(a, gravity) - distanceToAnchor(b, gravity));
21
+ for (const t of ordered) {
22
+ const step = stepToward(t, gravity);
23
+ if (!step)
24
+ continue;
25
+ const newRect = {
26
+ col: t.col + step.dx,
27
+ row: t.row + step.dy,
28
+ w: t.w,
29
+ h: t.h,
30
+ };
31
+ if (!grid.rectInBounds(newRect))
32
+ continue;
33
+ const overlap = grid.tiles.filter((o) => o.id !== t.id && isInFlow(o) && rectsOverlap(tileRect(o), newRect));
34
+ if (overlap.length > 0)
35
+ continue;
36
+ grid._setTilePos(t.id, { col: newRect.col, row: newRect.row });
37
+ moved.add(t.id);
38
+ anyMoved = true;
39
+ }
40
+ if (!anyMoved)
41
+ break;
42
+ }
43
+ return Array.from(moved);
44
+ }
45
+ function stepToward(t, gravity) {
46
+ if (gravity === 'none')
47
+ return null;
48
+ if (gravity === 'top')
49
+ return t.row > 0 ? { dx: 0, dy: -1 } : null;
50
+ if (gravity === 'left')
51
+ return t.col > 0 ? { dx: -1, dy: 0 } : null;
52
+ if (gravity === 'bottom')
53
+ return { dx: 0, dy: 1 };
54
+ if (gravity === 'right')
55
+ return { dx: 1, dy: 0 };
56
+ // Anchor cell
57
+ const dx = Math.sign(gravity.col - t.col);
58
+ const dy = Math.sign(gravity.row - t.row);
59
+ // Move along the axis with the greater distance first so we take the shortest path.
60
+ const adx = Math.abs(gravity.col - t.col);
61
+ const ady = Math.abs(gravity.row - t.row);
62
+ if (adx === 0 && dy === 0)
63
+ return null;
64
+ if (adx >= ady && dx !== 0)
65
+ return { dx, dy: 0 };
66
+ if (dy !== 0)
67
+ return { dx: 0, dy };
68
+ if (dx !== 0)
69
+ return { dx, dy: 0 };
70
+ return null;
71
+ }
72
+ function distanceToAnchor(t, gravity) {
73
+ if (gravity === 'none')
74
+ return 0;
75
+ if (gravity === 'top')
76
+ return t.row;
77
+ if (gravity === 'bottom')
78
+ return -t.row;
79
+ if (gravity === 'left')
80
+ return t.col;
81
+ if (gravity === 'right')
82
+ return -t.col;
83
+ return Math.abs(gravity.col - t.col) + Math.abs(gravity.row - t.row);
84
+ }
85
+ //# sourceMappingURL=compaction.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compaction.js","sourceRoot":"","sources":["../src/compaction.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAC7E,EAAE;AACF,8EAA8E;AAC9E,kFAAkF;AAIlF,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACvD,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAE5C,MAAM,UAAU,OAAO,CAAC,IAAU;IAChC,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,OAAO,IAAI,MAAM,CAAC;IAC9C,IAAI,OAAO,KAAK,MAAM;QAAE,OAAO,EAAE,CAAC;IAElC,MAAM,KAAK,GAAG,IAAI,GAAG,EAAU,CAAC;IAChC,0EAA0E;IAC1E,KAAK,IAAI,IAAI,GAAG,CAAC,EAAE,IAAI,GAAG,KAAM,EAAE,IAAI,EAAE,EAAE,CAAC;QACzC,IAAI,QAAQ,GAAG,KAAK,CAAC;QAErB,yEAAyE;QACzE,2EAA2E;QAC3E,2EAA2E;QAC3E,MAAM,OAAO,GAAG,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC;aAC5B,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;aAC1B,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CACb,gBAAgB,CAAC,CAAC,EAAE,OAAO,CAAC,GAAG,gBAAgB,CAAC,CAAC,EAAE,OAAO,CAAC,CAC5D,CAAC;QAEJ,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;YACxB,MAAM,IAAI,GAAG,UAAU,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;YACpC,IAAI,CAAC,IAAI;gBAAE,SAAS;YACpB,MAAM,OAAO,GAAa;gBACxB,GAAG,EAAE,CAAC,CAAC,GAAG,GAAG,IAAI,CAAC,EAAE;gBACpB,GAAG,EAAE,CAAC,CAAC,GAAG,GAAG,IAAI,CAAC,EAAE;gBACpB,CAAC,EAAE,CAAC,CAAC,CAAC;gBACN,CAAC,EAAE,CAAC,CAAC,CAAC;aACP,CAAC;YACF,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC;gBAAE,SAAS;YAC1C,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAC/B,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,EAAE,IAAI,QAAQ,CAAC,CAAC,CAAC,IAAI,YAAY,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,CAC1E,CAAC;YACF,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC;gBAAE,SAAS;YACjC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;YAC/D,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;YAChB,QAAQ,GAAG,IAAI,CAAC;QAClB,CAAC;QAED,IAAI,CAAC,QAAQ;YAAE,MAAM;IACvB,CAAC;IAED,OAAO,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AAC3B,CAAC;AAED,SAAS,UAAU,CAAC,CAAO,EAAE,OAAgB;IAC3C,IAAI,OAAO,KAAK,MAAM;QAAE,OAAO,IAAI,CAAC;IACpC,IAAI,OAAO,KAAK,KAAK;QAAE,OAAO,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;IACnE,IAAI,OAAO,KAAK,MAAM;QAAE,OAAO,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;IACpE,IAAI,OAAO,KAAK,QAAQ;QAAE,OAAO,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC;IAClD,IAAI,OAAO,KAAK,OAAO;QAAE,OAAO,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC;IACjD,cAAc;IACd,MAAM,EAAE,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC;IAC1C,MAAM,EAAE,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC;IAC1C,oFAAoF;IACpF,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC;IAC1C,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC;IAC1C,IAAI,GAAG,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACvC,IAAI,GAAG,IAAI,GAAG,IAAI,EAAE,KAAK,CAAC;QAAE,OAAO,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC;IACjD,IAAI,EAAE,KAAK,CAAC;QAAE,OAAO,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC;IACnC,IAAI,EAAE,KAAK,CAAC;QAAE,OAAO,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC;IACnC,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,gBAAgB,CAAC,CAAO,EAAE,OAAgB;IACjD,IAAI,OAAO,KAAK,MAAM;QAAE,OAAO,CAAC,CAAC;IACjC,IAAI,OAAO,KAAK,KAAK;QAAE,OAAO,CAAC,CAAC,GAAG,CAAC;IACpC,IAAI,OAAO,KAAK,QAAQ;QAAE,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC;IACxC,IAAI,OAAO,KAAK,MAAM;QAAE,OAAO,CAAC,CAAC,GAAG,CAAC;IACrC,IAAI,OAAO,KAAK,OAAO;QAAE,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC;IACvC,OAAO,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC;AACvE,CAAC"}
package/dist/drag.d.ts ADDED
@@ -0,0 +1,52 @@
1
+ import type { CellPos } from './types.js';
2
+ import type { Grid } from './grid.js';
3
+ export interface DragUpdateResult {
4
+ /** Whether the most recent candidate cell was successfully committed. */
5
+ committed: boolean;
6
+ /** Cell where the drop indicator should render (null if no valid target). */
7
+ indicatorCell: CellPos | null;
8
+ /** Did the candidate cell change since the previous update? */
9
+ changed: boolean;
10
+ }
11
+ /**
12
+ * Holds the snapshot/preview state for a single drag gesture. One controller
13
+ * instance can be reused across drags — call `start()` to begin a new gesture
14
+ * and `end()` or `cancel()` to finish.
15
+ */
16
+ export declare class DragController {
17
+ private grid;
18
+ private tileId;
19
+ private snapshot;
20
+ private pickup;
21
+ private lastCandidate;
22
+ private lastCommitted;
23
+ constructor(grid: Grid);
24
+ /** True if a drag gesture is currently in progress. */
25
+ isActive(): boolean;
26
+ /** ID of the tile currently being dragged, or null. */
27
+ draggerId(): string | null;
28
+ /** Cell where the drag was picked up (the dragger's pre-drag position). */
29
+ pickupCell(): CellPos;
30
+ /**
31
+ * Begin a drag gesture for `tileId`. Snapshots the grid so we can rewind on
32
+ * each candidate change. Returns false if the tile doesn't exist.
33
+ */
34
+ start(tileId: string): boolean;
35
+ /**
36
+ * The cursor is now over `candidate`. If it's the same cell as last update,
37
+ * this is a no-op. If it changed, rewind any prior preview and attempt the
38
+ * new candidate. Returns the resulting render hints.
39
+ */
40
+ update(candidate: CellPos): DragUpdateResult;
41
+ /**
42
+ * End the drag. The current preview state stays committed if it was a valid
43
+ * move; otherwise the snapshot is restored. Returns the final state.
44
+ */
45
+ end(): {
46
+ committed: boolean;
47
+ finalCell: CellPos | null;
48
+ };
49
+ /** Abort the drag and restore the pre-drag grid state. */
50
+ cancel(): void;
51
+ }
52
+ //# sourceMappingURL=drag.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"drag.d.ts","sourceRoot":"","sources":["../src/drag.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,OAAO,EAAQ,MAAM,YAAY,CAAC;AAChD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAItC,MAAM,WAAW,gBAAgB;IAC/B,yEAAyE;IACzE,SAAS,EAAE,OAAO,CAAC;IACnB,6EAA6E;IAC7E,aAAa,EAAE,OAAO,GAAG,IAAI,CAAC;IAC9B,+DAA+D;IAC/D,OAAO,EAAE,OAAO,CAAC;CAClB;AAED;;;;GAIG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,IAAI,CAAO;IACnB,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,QAAQ,CAAyB;IACzC,OAAO,CAAC,MAAM,CAA+B;IAC7C,OAAO,CAAC,aAAa,CAAwB;IAC7C,OAAO,CAAC,aAAa,CAAwB;gBAEjC,IAAI,EAAE,IAAI;IAItB,uDAAuD;IACvD,QAAQ,IAAI,OAAO;IAInB,uDAAuD;IACvD,SAAS,IAAI,MAAM,GAAG,IAAI;IAI1B,2EAA2E;IAC3E,UAAU,IAAI,OAAO;IAIrB;;;OAGG;IACH,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO;IAW9B;;;;OAIG;IACH,MAAM,CAAC,SAAS,EAAE,OAAO,GAAG,gBAAgB;IAqC5C;;;OAGG;IACH,GAAG,IAAI;QAAE,SAAS,EAAE,OAAO,CAAC;QAAC,SAAS,EAAE,OAAO,GAAG,IAAI,CAAA;KAAE;IAgBxD,0DAA0D;IAC1D,MAAM,IAAI,IAAI;CAOf"}
package/dist/drag.js ADDED
@@ -0,0 +1,120 @@
1
+ // DragController — live-preview state machine for drag-to-rearrange.
2
+ //
3
+ // Models the storyboard semantics: as the user drags a tile, the engine
4
+ // continuously runs the candidate move against a snapshot of the grid taken
5
+ // at pickup. Whenever the cursor crosses a cell boundary, any prior preview
6
+ // is rewound and the new candidate is attempted. The dragged tile and any
7
+ // displaced victims animate into their new positions via the adapter's FLIP
8
+ // loop while the drag is still in progress; on release the latest preview
9
+ // is committed. If the user releases over an invalid cell (out of bounds or
10
+ // rejected by the move engine), the snapshot is restored.
11
+ //
12
+ // This module is pure cell-space — pixel-to-cell conversion is the adapter's
13
+ // responsibility. That keeps the core engine free of any DOM concepts.
14
+ /**
15
+ * Holds the snapshot/preview state for a single drag gesture. One controller
16
+ * instance can be reused across drags — call `start()` to begin a new gesture
17
+ * and `end()` or `cancel()` to finish.
18
+ */
19
+ export class DragController {
20
+ constructor(grid) {
21
+ this.tileId = null;
22
+ this.snapshot = null;
23
+ this.pickup = { col: 0, row: 0 };
24
+ this.lastCandidate = null;
25
+ this.lastCommitted = null;
26
+ this.grid = grid;
27
+ }
28
+ /** True if a drag gesture is currently in progress. */
29
+ isActive() {
30
+ return this.tileId !== null;
31
+ }
32
+ /** ID of the tile currently being dragged, or null. */
33
+ draggerId() {
34
+ return this.tileId;
35
+ }
36
+ /** Cell where the drag was picked up (the dragger's pre-drag position). */
37
+ pickupCell() {
38
+ return { col: this.pickup.col, row: this.pickup.row };
39
+ }
40
+ /**
41
+ * Begin a drag gesture for `tileId`. Snapshots the grid so we can rewind on
42
+ * each candidate change. Returns false if the tile doesn't exist.
43
+ */
44
+ start(tileId) {
45
+ const tile = this.grid.getTile(tileId);
46
+ if (!tile)
47
+ return false;
48
+ this.tileId = tileId;
49
+ this.snapshot = this.grid.snapshotTiles();
50
+ this.pickup = { col: tile.col, row: tile.row };
51
+ this.lastCandidate = { col: tile.col, row: tile.row };
52
+ this.lastCommitted = { col: tile.col, row: tile.row };
53
+ return true;
54
+ }
55
+ /**
56
+ * The cursor is now over `candidate`. If it's the same cell as last update,
57
+ * this is a no-op. If it changed, rewind any prior preview and attempt the
58
+ * new candidate. Returns the resulting render hints.
59
+ */
60
+ update(candidate) {
61
+ if (!this.tileId || !this.snapshot) {
62
+ return { committed: false, indicatorCell: null, changed: false };
63
+ }
64
+ const same = this.lastCandidate !== null &&
65
+ this.lastCandidate.col === candidate.col &&
66
+ this.lastCandidate.row === candidate.row;
67
+ if (same) {
68
+ return {
69
+ committed: this.lastCommitted !== null,
70
+ indicatorCell: this.lastCommitted,
71
+ changed: false,
72
+ };
73
+ }
74
+ this.lastCandidate = { col: candidate.col, row: candidate.row };
75
+ // Always rewind to pickup state before attempting the new candidate, so
76
+ // each preview is independent and we can't compound errors.
77
+ this.grid.restoreTiles(this.snapshot);
78
+ if (candidate.col === this.pickup.col && candidate.row === this.pickup.row) {
79
+ // Back at pickup — nothing to commit, indicator at pickup.
80
+ this.lastCommitted = { col: this.pickup.col, row: this.pickup.row };
81
+ return { committed: true, indicatorCell: this.lastCommitted, changed: true };
82
+ }
83
+ const ok = this.grid.moveTile(this.tileId, candidate);
84
+ this.lastCommitted = ok ? { col: candidate.col, row: candidate.row } : null;
85
+ return {
86
+ committed: ok,
87
+ indicatorCell: this.lastCommitted,
88
+ changed: true,
89
+ };
90
+ }
91
+ /**
92
+ * End the drag. The current preview state stays committed if it was a valid
93
+ * move; otherwise the snapshot is restored. Returns the final state.
94
+ */
95
+ end() {
96
+ const result = {
97
+ committed: this.lastCommitted !== null,
98
+ finalCell: this.lastCommitted,
99
+ };
100
+ // If the last attempt was rejected, ensure we're back at the original state.
101
+ if (!this.lastCommitted && this.snapshot) {
102
+ this.grid.restoreTiles(this.snapshot);
103
+ }
104
+ this.tileId = null;
105
+ this.snapshot = null;
106
+ this.lastCandidate = null;
107
+ this.lastCommitted = null;
108
+ return result;
109
+ }
110
+ /** Abort the drag and restore the pre-drag grid state. */
111
+ cancel() {
112
+ if (this.snapshot)
113
+ this.grid.restoreTiles(this.snapshot);
114
+ this.tileId = null;
115
+ this.snapshot = null;
116
+ this.lastCandidate = null;
117
+ this.lastCommitted = null;
118
+ }
119
+ }
120
+ //# sourceMappingURL=drag.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"drag.js","sourceRoot":"","sources":["../src/drag.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ export type Listener<T> = (payload: T) => void;
2
+ export declare class Emitter<T> {
3
+ private listeners;
4
+ on(fn: Listener<T>): () => void;
5
+ off(fn: Listener<T>): void;
6
+ emit(payload: T): void;
7
+ clear(): void;
8
+ }
9
+ //# sourceMappingURL=events.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,QAAQ,CAAC,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,KAAK,IAAI,CAAC;AAE/C,qBAAa,OAAO,CAAC,CAAC;IACpB,OAAO,CAAC,SAAS,CAA0B;IAE3C,EAAE,CAAC,EAAE,EAAE,QAAQ,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI;IAK/B,GAAG,CAAC,EAAE,EAAE,QAAQ,CAAC,CAAC,CAAC,GAAG,IAAI;IAI1B,IAAI,CAAC,OAAO,EAAE,CAAC,GAAG,IAAI;IAKtB,KAAK,IAAI,IAAI;CAGd"}
package/dist/events.js ADDED
@@ -0,0 +1,22 @@
1
+ // Minimal event emitter — no dependencies.
2
+ export class Emitter {
3
+ constructor() {
4
+ this.listeners = new Set();
5
+ }
6
+ on(fn) {
7
+ this.listeners.add(fn);
8
+ return () => this.listeners.delete(fn);
9
+ }
10
+ off(fn) {
11
+ this.listeners.delete(fn);
12
+ }
13
+ emit(payload) {
14
+ // Copy to array so listeners can safely unsubscribe during emit.
15
+ for (const l of Array.from(this.listeners))
16
+ l(payload);
17
+ }
18
+ clear() {
19
+ this.listeners.clear();
20
+ }
21
+ }
22
+ //# sourceMappingURL=events.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"events.js","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"AAAA,2CAA2C;AAI3C,MAAM,OAAO,OAAO;IAApB;QACU,cAAS,GAAG,IAAI,GAAG,EAAe,CAAC;IAmB7C,CAAC;IAjBC,EAAE,CAAC,EAAe;QAChB,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACvB,OAAO,GAAG,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IACzC,CAAC;IAED,GAAG,CAAC,EAAe;QACjB,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IAC5B,CAAC;IAED,IAAI,CAAC,OAAU;QACb,iEAAiE;QACjE,KAAK,MAAM,CAAC,IAAI,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC;YAAE,CAAC,CAAC,OAAO,CAAC,CAAC;IACzD,CAAC;IAED,KAAK;QACH,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,CAAC;IACzB,CAAC;CACF"}
@@ -0,0 +1,33 @@
1
+ import type { CellPos, CellRect, Direction8, Face, Footprint, Tile } from './types.js';
2
+ /** Does rect `a` overlap rect `b`? */
3
+ export declare function rectsOverlap(a: CellRect, b: CellRect): boolean;
4
+ /** Does rect `outer` fully contain rect `inner`? */
5
+ export declare function rectContains(outer: CellRect, inner: CellRect): boolean;
6
+ export declare function rectEquals(a: CellRect, b: CellRect): boolean;
7
+ /** Two tiles are "adjacent" if their rects share at least a corner touch (8-neighbor). */
8
+ export declare function rectsAdjacent(a: CellRect, b: CellRect): boolean;
9
+ export declare function tileRect(t: Tile): CellRect;
10
+ /** Face closest to origin from target's perspective. */
11
+ export declare function faceClosestToOrigin(origin: CellRect, target: CellRect): Face;
12
+ /** Opposite face. */
13
+ export declare function oppositeFace(f: Face): Face;
14
+ /** Classify origin -> target geometry. */
15
+ export type OriginKind = 'horizontal' | 'vertical' | 'corner';
16
+ export declare function classifyOrigin(origin: CellRect, target: CellRect): OriginKind;
17
+ /**
18
+ * Return the 8 displacement directions for a tile at `target` being pushed
19
+ * by a drag originating at `origin`, ordered by the priority spec.
20
+ */
21
+ export declare function priorityDirections(origin: CellRect, target: CellRect): Direction8[];
22
+ /** Translate a rect by a direction (one unit). */
23
+ export declare function translateRect(r: CellRect, dir: Direction8): CellRect;
24
+ /** Translate by arbitrary cell offset. */
25
+ export declare function offsetRect(r: CellRect, dcol: number, drow: number): CellRect;
26
+ /** Stepwise offset for a direction (dx, dy in cells, -1..1). */
27
+ export declare function directionStep(dir: Direction8): {
28
+ dx: number;
29
+ dy: number;
30
+ };
31
+ export declare function footprintEquals(a: Footprint, b: Footprint): boolean;
32
+ export declare function clonePos(p: CellPos): CellPos;
33
+ //# sourceMappingURL=geometry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"geometry.d.ts","sourceRoot":"","sources":["../src/geometry.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,OAAO,EACP,QAAQ,EACR,UAAU,EACV,IAAI,EAEJ,SAAS,EACT,IAAI,EACL,MAAM,YAAY,CAAC;AAEpB,sCAAsC;AACtC,wBAAgB,YAAY,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,QAAQ,GAAG,OAAO,CAO9D;AAED,oDAAoD;AACpD,wBAAgB,YAAY,CAAC,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,GAAG,OAAO,CAOtE;AAED,wBAAgB,UAAU,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,QAAQ,GAAG,OAAO,CAE5D;AAED,0FAA0F;AAC1F,wBAAgB,aAAa,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,QAAQ,GAAG,OAAO,CAc/D;AAED,wBAAgB,QAAQ,CAAC,CAAC,EAAE,IAAI,GAAG,QAAQ,CAE1C;AAWD,wDAAwD;AACxD,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,GAAG,IAAI,CAS5E;AAED,qBAAqB;AACrB,wBAAgB,YAAY,CAAC,CAAC,EAAE,IAAI,GAAG,IAAI,CAK1C;AAED,0CAA0C;AAC1C,MAAM,MAAM,UAAU,GAAG,YAAY,GAAG,UAAU,GAAG,QAAQ,CAAC;AAC9D,wBAAgB,cAAc,CAAC,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,GAAG,UAAU,CAO7E;AAwBD;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,GAAG,UAAU,EAAE,CAoCnF;AAOD,kDAAkD;AAClD,wBAAgB,aAAa,CAAC,CAAC,EAAE,QAAQ,EAAE,GAAG,EAAE,UAAU,GAAG,QAAQ,CAQpE;AAED,0CAA0C;AAC1C,wBAAgB,UAAU,CACxB,CAAC,EAAE,QAAQ,EACX,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,GACX,QAAQ,CAEV;AAED,gEAAgE;AAChE,wBAAgB,aAAa,CAAC,GAAG,EAAE,UAAU,GAAG;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CAGzE;AAED,wBAAgB,eAAe,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,SAAS,GAAG,OAAO,CAEnE;AAED,wBAAgB,QAAQ,CAAC,CAAC,EAAE,OAAO,GAAG,OAAO,CAE5C"}
@@ -0,0 +1,164 @@
1
+ // Rect / footprint / priority math. Pure functions only — no state.
2
+ /** Does rect `a` overlap rect `b`? */
3
+ export function rectsOverlap(a, b) {
4
+ return (a.col < b.col + b.w &&
5
+ a.col + a.w > b.col &&
6
+ a.row < b.row + b.h &&
7
+ a.row + a.h > b.row);
8
+ }
9
+ /** Does rect `outer` fully contain rect `inner`? */
10
+ export function rectContains(outer, inner) {
11
+ return (inner.col >= outer.col &&
12
+ inner.row >= outer.row &&
13
+ inner.col + inner.w <= outer.col + outer.w &&
14
+ inner.row + inner.h <= outer.row + outer.h);
15
+ }
16
+ export function rectEquals(a, b) {
17
+ return a.col === b.col && a.row === b.row && a.w === b.w && a.h === b.h;
18
+ }
19
+ /** Two tiles are "adjacent" if their rects share at least a corner touch (8-neighbor). */
20
+ export function rectsAdjacent(a, b) {
21
+ if (rectsOverlap(a, b))
22
+ return false;
23
+ const horizTouch = (a.col + a.w === b.col || b.col + b.w === a.col) &&
24
+ a.row < b.row + b.h &&
25
+ a.row + a.h > b.row;
26
+ const vertTouch = (a.row + a.h === b.row || b.row + b.h === a.row) &&
27
+ a.col < b.col + b.w &&
28
+ a.col + a.w > b.col;
29
+ const cornerTouch = (a.col + a.w === b.col || b.col + b.w === a.col) &&
30
+ (a.row + a.h === b.row || b.row + b.h === a.row);
31
+ return horizTouch || vertTouch || cornerTouch;
32
+ }
33
+ export function tileRect(t) {
34
+ return { col: t.col, row: t.row, w: t.w, h: t.h };
35
+ }
36
+ /** Delta from origin's center to target's center, both as cell centers. */
37
+ function centerDelta(origin, target) {
38
+ const ox = origin.col + origin.w / 2;
39
+ const oy = origin.row + origin.h / 2;
40
+ const tx = target.col + target.w / 2;
41
+ const ty = target.row + target.h / 2;
42
+ return { dx: tx - ox, dy: ty - oy };
43
+ }
44
+ /** Face closest to origin from target's perspective. */
45
+ export function faceClosestToOrigin(origin, target) {
46
+ const { dx, dy } = centerDelta(origin, target);
47
+ // target->origin: negate
48
+ const ox = -dx;
49
+ const oy = -dy;
50
+ if (Math.abs(ox) >= Math.abs(oy)) {
51
+ return ox >= 0 ? 'e' : 'w';
52
+ }
53
+ return oy >= 0 ? 's' : 'n';
54
+ }
55
+ /** Opposite face. */
56
+ export function oppositeFace(f) {
57
+ if (f === 'n')
58
+ return 's';
59
+ if (f === 's')
60
+ return 'n';
61
+ if (f === 'e')
62
+ return 'w';
63
+ return 'e';
64
+ }
65
+ export function classifyOrigin(origin, target) {
66
+ const { dx, dy } = centerDelta(origin, target);
67
+ const horiz = Math.abs(dx) > 1e-9;
68
+ const vert = Math.abs(dy) > 1e-9;
69
+ if (horiz && vert)
70
+ return 'corner';
71
+ if (horiz)
72
+ return 'horizontal';
73
+ return 'vertical';
74
+ }
75
+ /** Is corner `c` on the side of origin? */
76
+ function cornerDistToOrigin(origin, target, c) {
77
+ // Corner position as the center of the 1x1 slot diagonally adjacent.
78
+ const cx = c === 'nw' || c === 'sw'
79
+ ? target.col - 0.5
80
+ : target.col + target.w + 0.5;
81
+ const cy = c === 'nw' || c === 'ne'
82
+ ? target.row - 0.5
83
+ : target.row + target.h + 0.5;
84
+ const ox = origin.col + origin.w / 2;
85
+ const oy = origin.row + origin.h / 2;
86
+ const dx = cx - ox;
87
+ const dy = cy - oy;
88
+ return dx * dx + dy * dy;
89
+ }
90
+ /**
91
+ * Return the 8 displacement directions for a tile at `target` being pushed
92
+ * by a drag originating at `origin`, ordered by the priority spec.
93
+ */
94
+ export function priorityDirections(origin, target) {
95
+ const faceClosest = faceClosestToOrigin(origin, target);
96
+ const faceOpposite = oppositeFace(faceClosest);
97
+ const kind = classifyOrigin(origin, target);
98
+ // 3rd priority: upwards face if origin horizontal, right face otherwise
99
+ // but excluding already-chosen faces
100
+ const allFaces = ['n', 's', 'e', 'w'];
101
+ const used = new Set([faceClosest, faceOpposite]);
102
+ let third;
103
+ if (kind === 'horizontal') {
104
+ third = used.has('n') ? pickRemaining(allFaces, used) : 'n';
105
+ }
106
+ else {
107
+ third = used.has('e') ? pickRemaining(allFaces, used) : 'e';
108
+ }
109
+ used.add(third);
110
+ const fourth = pickRemaining(allFaces, used);
111
+ // Corners: rank closest-2 then furthest-2
112
+ const corners = ['nw', 'ne', 'sw', 'se'];
113
+ const cornerRanked = corners
114
+ .map((c) => ({ c, d: cornerDistToOrigin(origin, target, c) }))
115
+ .sort((a, b) => a.d - b.d);
116
+ const closeCorners = cornerRanked.slice(0, 2).map((x) => x.c);
117
+ const farCorners = cornerRanked.slice(2).map((x) => x.c);
118
+ return [
119
+ faceClosest,
120
+ faceOpposite,
121
+ third,
122
+ fourth,
123
+ closeCorners[0],
124
+ closeCorners[1],
125
+ farCorners[0],
126
+ farCorners[1],
127
+ ];
128
+ }
129
+ function pickRemaining(all, used) {
130
+ for (const f of all)
131
+ if (!used.has(f))
132
+ return f;
133
+ return all[0];
134
+ }
135
+ /** Translate a rect by a direction (one unit). */
136
+ export function translateRect(r, dir) {
137
+ let dcol = 0;
138
+ let drow = 0;
139
+ if (dir === 'n' || dir === 'nw' || dir === 'ne')
140
+ drow = -1;
141
+ if (dir === 's' || dir === 'sw' || dir === 'se')
142
+ drow = 1;
143
+ if (dir === 'w' || dir === 'nw' || dir === 'sw')
144
+ dcol = -1;
145
+ if (dir === 'e' || dir === 'ne' || dir === 'se')
146
+ dcol = 1;
147
+ return { col: r.col + dcol, row: r.row + drow, w: r.w, h: r.h };
148
+ }
149
+ /** Translate by arbitrary cell offset. */
150
+ export function offsetRect(r, dcol, drow) {
151
+ return { col: r.col + dcol, row: r.row + drow, w: r.w, h: r.h };
152
+ }
153
+ /** Stepwise offset for a direction (dx, dy in cells, -1..1). */
154
+ export function directionStep(dir) {
155
+ const r = translateRect({ col: 0, row: 0, w: 0, h: 0 }, dir);
156
+ return { dx: r.col, dy: r.row };
157
+ }
158
+ export function footprintEquals(a, b) {
159
+ return a.w === b.w && a.h === b.h;
160
+ }
161
+ export function clonePos(p) {
162
+ return { col: p.col, row: p.row };
163
+ }
164
+ //# sourceMappingURL=geometry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"geometry.js","sourceRoot":"","sources":["../src/geometry.ts"],"names":[],"mappings":"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"}