@oh-just-another/state 0.57.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 (269) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/LICENSE +21 -0
  3. package/README.md +60 -0
  4. package/dist/.tsbuildinfo +1 -0
  5. package/dist/actions/actionClipboard.d.ts +6 -0
  6. package/dist/actions/actionClipboard.d.ts.map +1 -0
  7. package/dist/actions/actionClipboard.js +26 -0
  8. package/dist/actions/actionClipboard.js.map +1 -0
  9. package/dist/actions/actionGrouping.d.ts +5 -0
  10. package/dist/actions/actionGrouping.d.ts.map +1 -0
  11. package/dist/actions/actionGrouping.js +26 -0
  12. package/dist/actions/actionGrouping.js.map +1 -0
  13. package/dist/actions/actionHistory.d.ts +5 -0
  14. package/dist/actions/actionHistory.d.ts.map +1 -0
  15. package/dist/actions/actionHistory.js +25 -0
  16. package/dist/actions/actionHistory.js.map +1 -0
  17. package/dist/actions/actionKeyboard.d.ts +3 -0
  18. package/dist/actions/actionKeyboard.d.ts.map +1 -0
  19. package/dist/actions/actionKeyboard.js +130 -0
  20. package/dist/actions/actionKeyboard.js.map +1 -0
  21. package/dist/actions/actionLayout.d.ts +3 -0
  22. package/dist/actions/actionLayout.d.ts.map +1 -0
  23. package/dist/actions/actionLayout.js +65 -0
  24. package/dist/actions/actionLayout.js.map +1 -0
  25. package/dist/actions/actionMode.d.ts +13 -0
  26. package/dist/actions/actionMode.d.ts.map +1 -0
  27. package/dist/actions/actionMode.js +116 -0
  28. package/dist/actions/actionMode.js.map +1 -0
  29. package/dist/actions/actionSelection.d.ts +9 -0
  30. package/dist/actions/actionSelection.d.ts.map +1 -0
  31. package/dist/actions/actionSelection.js +57 -0
  32. package/dist/actions/actionSelection.js.map +1 -0
  33. package/dist/actions/actionView.d.ts +7 -0
  34. package/dist/actions/actionView.d.ts.map +1 -0
  35. package/dist/actions/actionView.js +14 -0
  36. package/dist/actions/actionView.js.map +1 -0
  37. package/dist/actions/actionZOrder.d.ts +7 -0
  38. package/dist/actions/actionZOrder.d.ts.map +1 -0
  39. package/dist/actions/actionZOrder.js +42 -0
  40. package/dist/actions/actionZOrder.js.map +1 -0
  41. package/dist/actions/actionZoom.d.ts +8 -0
  42. package/dist/actions/actionZoom.d.ts.map +1 -0
  43. package/dist/actions/actionZoom.js +61 -0
  44. package/dist/actions/actionZoom.js.map +1 -0
  45. package/dist/actions/index.d.ts +12 -0
  46. package/dist/actions/index.d.ts.map +1 -0
  47. package/dist/actions/index.js +11 -0
  48. package/dist/actions/index.js.map +1 -0
  49. package/dist/actions/registry.d.ts +68 -0
  50. package/dist/actions/registry.d.ts.map +1 -0
  51. package/dist/actions/registry.js +300 -0
  52. package/dist/actions/registry.js.map +1 -0
  53. package/dist/actions/types.d.ts +103 -0
  54. package/dist/actions/types.d.ts.map +1 -0
  55. package/dist/actions/types.js +10 -0
  56. package/dist/actions/types.js.map +1 -0
  57. package/dist/animation-tick.d.ts +28 -0
  58. package/dist/animation-tick.d.ts.map +1 -0
  59. package/dist/animation-tick.js +50 -0
  60. package/dist/animation-tick.js.map +1 -0
  61. package/dist/auto-compact.d.ts +27 -0
  62. package/dist/auto-compact.d.ts.map +1 -0
  63. package/dist/auto-compact.js +46 -0
  64. package/dist/auto-compact.js.map +1 -0
  65. package/dist/auto-layout-scheduler.d.ts +50 -0
  66. package/dist/auto-layout-scheduler.d.ts.map +1 -0
  67. package/dist/auto-layout-scheduler.js +94 -0
  68. package/dist/auto-layout-scheduler.js.map +1 -0
  69. package/dist/built-in-handlers.d.ts +24 -0
  70. package/dist/built-in-handlers.d.ts.map +1 -0
  71. package/dist/built-in-handlers.js +210 -0
  72. package/dist/built-in-handlers.js.map +1 -0
  73. package/dist/clipboard.d.ts +51 -0
  74. package/dist/clipboard.d.ts.map +1 -0
  75. package/dist/clipboard.js +105 -0
  76. package/dist/clipboard.js.map +1 -0
  77. package/dist/constants.d.ts +544 -0
  78. package/dist/constants.d.ts.map +1 -0
  79. package/dist/constants.js +537 -0
  80. package/dist/constants.js.map +1 -0
  81. package/dist/dom-events.d.ts +13 -0
  82. package/dist/dom-events.d.ts.map +1 -0
  83. package/dist/dom-events.js +70 -0
  84. package/dist/dom-events.js.map +1 -0
  85. package/dist/editor/anchor-points.d.ts +22 -0
  86. package/dist/editor/anchor-points.d.ts.map +1 -0
  87. package/dist/editor/anchor-points.js +28 -0
  88. package/dist/editor/anchor-points.js.map +1 -0
  89. package/dist/editor/applies/create.d.ts +60 -0
  90. package/dist/editor/applies/create.d.ts.map +1 -0
  91. package/dist/editor/applies/create.js +107 -0
  92. package/dist/editor/applies/create.js.map +1 -0
  93. package/dist/editor/applies/edge.d.ts +32 -0
  94. package/dist/editor/applies/edge.d.ts.map +1 -0
  95. package/dist/editor/applies/edge.js +44 -0
  96. package/dist/editor/applies/edge.js.map +1 -0
  97. package/dist/editor/applies/link-move.d.ts +53 -0
  98. package/dist/editor/applies/link-move.d.ts.map +1 -0
  99. package/dist/editor/applies/link-move.js +160 -0
  100. package/dist/editor/applies/link-move.js.map +1 -0
  101. package/dist/editor/applies/move.d.ts +38 -0
  102. package/dist/editor/applies/move.d.ts.map +1 -0
  103. package/dist/editor/applies/move.js +83 -0
  104. package/dist/editor/applies/move.js.map +1 -0
  105. package/dist/editor/applies/resize.d.ts +68 -0
  106. package/dist/editor/applies/resize.d.ts.map +1 -0
  107. package/dist/editor/applies/resize.js +199 -0
  108. package/dist/editor/applies/resize.js.map +1 -0
  109. package/dist/editor/applies/selection.d.ts +34 -0
  110. package/dist/editor/applies/selection.d.ts.map +1 -0
  111. package/dist/editor/applies/selection.js +65 -0
  112. package/dist/editor/applies/selection.js.map +1 -0
  113. package/dist/editor/applies/snap-grid.d.ts +45 -0
  114. package/dist/editor/applies/snap-grid.d.ts.map +1 -0
  115. package/dist/editor/applies/snap-grid.js +90 -0
  116. package/dist/editor/applies/snap-grid.js.map +1 -0
  117. package/dist/editor/container-ops.d.ts +74 -0
  118. package/dist/editor/container-ops.d.ts.map +1 -0
  119. package/dist/editor/container-ops.js +262 -0
  120. package/dist/editor/container-ops.js.map +1 -0
  121. package/dist/editor/event-fanout.d.ts +48 -0
  122. package/dist/editor/event-fanout.d.ts.map +1 -0
  123. package/dist/editor/event-fanout.js +53 -0
  124. package/dist/editor/event-fanout.js.map +1 -0
  125. package/dist/editor/gesture-tx.d.ts +77 -0
  126. package/dist/editor/gesture-tx.d.ts.map +1 -0
  127. package/dist/editor/gesture-tx.js +93 -0
  128. package/dist/editor/gesture-tx.js.map +1 -0
  129. package/dist/editor/hit-test.d.ts +178 -0
  130. package/dist/editor/hit-test.d.ts.map +1 -0
  131. package/dist/editor/hit-test.js +460 -0
  132. package/dist/editor/hit-test.js.map +1 -0
  133. package/dist/editor/long-press.d.ts +34 -0
  134. package/dist/editor/long-press.d.ts.map +1 -0
  135. package/dist/editor/long-press.js +58 -0
  136. package/dist/editor/long-press.js.map +1 -0
  137. package/dist/editor/pinch.d.ts +37 -0
  138. package/dist/editor/pinch.d.ts.map +1 -0
  139. package/dist/editor/pinch.js +90 -0
  140. package/dist/editor/pinch.js.map +1 -0
  141. package/dist/editor/pointer-binding.d.ts +18 -0
  142. package/dist/editor/pointer-binding.d.ts.map +1 -0
  143. package/dist/editor/pointer-binding.js +1087 -0
  144. package/dist/editor/pointer-binding.js.map +1 -0
  145. package/dist/editor/public/annotations.d.ts +54 -0
  146. package/dist/editor/public/annotations.d.ts.map +1 -0
  147. package/dist/editor/public/annotations.js +106 -0
  148. package/dist/editor/public/annotations.js.map +1 -0
  149. package/dist/editor/public/arrange-group.d.ts +81 -0
  150. package/dist/editor/public/arrange-group.d.ts.map +1 -0
  151. package/dist/editor/public/arrange-group.js +193 -0
  152. package/dist/editor/public/arrange-group.js.map +1 -0
  153. package/dist/editor/public/brush.d.ts +33 -0
  154. package/dist/editor/public/brush.d.ts.map +1 -0
  155. package/dist/editor/public/brush.js +54 -0
  156. package/dist/editor/public/brush.js.map +1 -0
  157. package/dist/editor/public/clipboard.d.ts +31 -0
  158. package/dist/editor/public/clipboard.d.ts.map +1 -0
  159. package/dist/editor/public/clipboard.js +35 -0
  160. package/dist/editor/public/clipboard.js.map +1 -0
  161. package/dist/editor/public/image-insert.d.ts +30 -0
  162. package/dist/editor/public/image-insert.d.ts.map +1 -0
  163. package/dist/editor/public/image-insert.js +57 -0
  164. package/dist/editor/public/image-insert.js.map +1 -0
  165. package/dist/editor/public/layers.d.ts +56 -0
  166. package/dist/editor/public/layers.d.ts.map +1 -0
  167. package/dist/editor/public/layers.js +104 -0
  168. package/dist/editor/public/layers.js.map +1 -0
  169. package/dist/editor/public/link.d.ts +25 -0
  170. package/dist/editor/public/link.d.ts.map +1 -0
  171. package/dist/editor/public/link.js +74 -0
  172. package/dist/editor/public/link.js.map +1 -0
  173. package/dist/editor/public/placement.d.ts +60 -0
  174. package/dist/editor/public/placement.d.ts.map +1 -0
  175. package/dist/editor/public/placement.js +111 -0
  176. package/dist/editor/public/placement.js.map +1 -0
  177. package/dist/editor/public/selection-ops.d.ts +78 -0
  178. package/dist/editor/public/selection-ops.d.ts.map +1 -0
  179. package/dist/editor/public/selection-ops.js +226 -0
  180. package/dist/editor/public/selection-ops.js.map +1 -0
  181. package/dist/editor/public/text-edit.d.ts +17 -0
  182. package/dist/editor/public/text-edit.d.ts.map +1 -0
  183. package/dist/editor/public/text-edit.js +28 -0
  184. package/dist/editor/public/text-edit.js.map +1 -0
  185. package/dist/editor/public/z-order.d.ts +47 -0
  186. package/dist/editor/public/z-order.d.ts.map +1 -0
  187. package/dist/editor/public/z-order.js +151 -0
  188. package/dist/editor/public/z-order.js.map +1 -0
  189. package/dist/editor/public/zoom-pan.d.ts +44 -0
  190. package/dist/editor/public/zoom-pan.d.ts.map +1 -0
  191. package/dist/editor/public/zoom-pan.js +116 -0
  192. package/dist/editor/public/zoom-pan.js.map +1 -0
  193. package/dist/editor/render-orchestrator.d.ts +11 -0
  194. package/dist/editor/render-orchestrator.d.ts.map +1 -0
  195. package/dist/editor/render-orchestrator.js +473 -0
  196. package/dist/editor/render-orchestrator.js.map +1 -0
  197. package/dist/editor/resize-helpers.d.ts +30 -0
  198. package/dist/editor/resize-helpers.d.ts.map +1 -0
  199. package/dist/editor/resize-helpers.js +121 -0
  200. package/dist/editor/resize-helpers.js.map +1 -0
  201. package/dist/editor/shape-filters.d.ts +11 -0
  202. package/dist/editor/shape-filters.d.ts.map +1 -0
  203. package/dist/editor/shape-filters.js +19 -0
  204. package/dist/editor/shape-filters.js.map +1 -0
  205. package/dist/editor/shape-traits.d.ts +16 -0
  206. package/dist/editor/shape-traits.d.ts.map +1 -0
  207. package/dist/editor/shape-traits.js +29 -0
  208. package/dist/editor/shape-traits.js.map +1 -0
  209. package/dist/editor/viewport-helpers.d.ts +24 -0
  210. package/dist/editor/viewport-helpers.d.ts.map +1 -0
  211. package/dist/editor/viewport-helpers.js +63 -0
  212. package/dist/editor/viewport-helpers.js.map +1 -0
  213. package/dist/editor-events.d.ts +33 -0
  214. package/dist/editor-events.d.ts.map +1 -0
  215. package/dist/editor-events.js +2 -0
  216. package/dist/editor-events.js.map +1 -0
  217. package/dist/editor.d.ts +1841 -0
  218. package/dist/editor.d.ts.map +1 -0
  219. package/dist/editor.js +4803 -0
  220. package/dist/editor.js.map +1 -0
  221. package/dist/file-drop.d.ts +99 -0
  222. package/dist/file-drop.d.ts.map +1 -0
  223. package/dist/file-drop.js +198 -0
  224. package/dist/file-drop.js.map +1 -0
  225. package/dist/frame-helpers.d.ts +30 -0
  226. package/dist/frame-helpers.d.ts.map +1 -0
  227. package/dist/frame-helpers.js +105 -0
  228. package/dist/frame-helpers.js.map +1 -0
  229. package/dist/group-helpers.d.ts +39 -0
  230. package/dist/group-helpers.d.ts.map +1 -0
  231. package/dist/group-helpers.js +116 -0
  232. package/dist/group-helpers.js.map +1 -0
  233. package/dist/handle.d.ts +73 -0
  234. package/dist/handle.d.ts.map +1 -0
  235. package/dist/handle.js +184 -0
  236. package/dist/handle.js.map +1 -0
  237. package/dist/index.d.ts +26 -0
  238. package/dist/index.d.ts.map +1 -0
  239. package/dist/index.js +16 -0
  240. package/dist/index.js.map +1 -0
  241. package/dist/interactive.d.ts +21 -0
  242. package/dist/interactive.d.ts.map +1 -0
  243. package/dist/interactive.js +11 -0
  244. package/dist/interactive.js.map +1 -0
  245. package/dist/link-selection.d.ts +21 -0
  246. package/dist/link-selection.d.ts.map +1 -0
  247. package/dist/link-selection.js +37 -0
  248. package/dist/link-selection.js.map +1 -0
  249. package/dist/machine.d.ts +322 -0
  250. package/dist/machine.d.ts.map +1 -0
  251. package/dist/machine.js +568 -0
  252. package/dist/machine.js.map +1 -0
  253. package/dist/modes.d.ts +24 -0
  254. package/dist/modes.d.ts.map +1 -0
  255. package/dist/modes.js +2 -0
  256. package/dist/modes.js.map +1 -0
  257. package/dist/overlay.d.ts +262 -0
  258. package/dist/overlay.d.ts.map +1 -0
  259. package/dist/overlay.js +673 -0
  260. package/dist/overlay.js.map +1 -0
  261. package/dist/platform.d.ts +70 -0
  262. package/dist/platform.d.ts.map +1 -0
  263. package/dist/platform.js +115 -0
  264. package/dist/platform.js.map +1 -0
  265. package/dist/selection.d.ts +18 -0
  266. package/dist/selection.d.ts.map +1 -0
  267. package/dist/selection.js +31 -0
  268. package/dist/selection.js.map +1 -0
  269. package/package.json +59 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auto-layout-scheduler.d.ts","sourceRoot":"","sources":["../src/auto-layout-scheduler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,EACL,KAAK,EAML,KAAK,KAAK,EACV,KAAK,KAAK,EACX,MAAM,wBAAwB,CAAC;AAEhC;;;;;;;;;;GAUG;AACH,qBAAa,mBAAmB;IAIlB,OAAO,CAAC,QAAQ,CAAC,IAAI;IAHjC,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAgC;gBAE9B,IAAI,EAAE,0BAA0B;IAE7D,8DAA8D;IAC9D,QAAQ,IAAI,IAAI;IAShB;;;;OAIG;IACH,cAAc,CAAC,QAAQ,EAAE,SAAS,GAAG,IAAI;IAIzC,kEAAkE;IAClE,QAAQ,IAAI,IAAI;IAuBhB,OAAO,CAAC,YAAY;CA0BrB;AAED,MAAM,WAAW,0BAA0B;IACzC,QAAQ,CAAC,QAAQ,EAAE,MAAM,KAAK,CAAC;IAC/B;;;OAGG;IACH,QAAQ,CAAC,UAAU,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IAC5C,iEAAiE;IACjE,QAAQ,CAAC,aAAa,EAAE,CAAC,QAAQ,EAAE,SAAS,EAAE,OAAO,EAAE,SAAS,KAAK,IAAI,CAAC;IAC1E;;;;;OAKG;IACH,QAAQ,CAAC,SAAS,EAAE,MAAM,IAAI,CAAC;CAChC;AAED,YAAY,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;AAC7B,OAAO,EAAE,KAAK,EAAE,CAAC"}
@@ -0,0 +1,94 @@
1
+ import { apply, getAutoLayoutSpec, getDropZoneWorld, getElement, getElementWorldBounds, runAutoLayout, } from "@oh-just-another/scene";
2
+ /**
3
+ * Microtask-coalesced re-runner for shapes carrying
4
+ * `metadata.autoLayout`. The editor calls `schedule()` from `notify()`;
5
+ * the check walks every auto-layout parent, compares its children-set
6
+ * fingerprint against the previous run, and re-runs the layout when the
7
+ * set changed.
8
+ *
9
+ * Pure position edits inside an existing children set are ignored
10
+ * (fingerprint is sorted-ids only), so a manual nudge of a child
11
+ * isn't snapped back. Add / remove / reparent does trigger.
12
+ */
13
+ export class AutoLayoutScheduler {
14
+ opts;
15
+ pending = false;
16
+ signatures = new Map();
17
+ constructor(opts) {
18
+ this.opts = opts;
19
+ }
20
+ /** Queue a check for the next microtask. Coalesces bursts. */
21
+ schedule() {
22
+ if (this.pending)
23
+ return;
24
+ this.pending = true;
25
+ queueMicrotask(() => {
26
+ this.pending = false;
27
+ this.runCheck();
28
+ });
29
+ }
30
+ /**
31
+ * Recompute and store the signature for a parent, used after the
32
+ * editor's public `runLayout(parentId)` to prevent the next
33
+ * scheduled check from firing a redundant second run.
34
+ */
35
+ resetSignature(parentId) {
36
+ this.signatures.set(parentId, this.signatureFor(parentId));
37
+ }
38
+ /** Run the actual check + relayout immediately. Used by tests. */
39
+ runCheck() {
40
+ const scene = this.opts.getScene();
41
+ let mutated = false;
42
+ for (const parent of scene.elements.values()) {
43
+ if (!getAutoLayoutSpec(parent))
44
+ continue;
45
+ const sig = this.signatureFor(parent.id);
46
+ if (this.signatures.get(parent.id) === sig)
47
+ continue;
48
+ this.signatures.set(parent.id, sig);
49
+ const patch = runAutoLayout(this.opts.getScene(), parent.id);
50
+ if (!patch)
51
+ continue;
52
+ this.opts.applyPatch(patch);
53
+ // Grow container to fit children after they've been re-laid out.
54
+ // Per-child idempotent: `growContainer` is a no-op when the child
55
+ // already fits the drop-zone.
56
+ const post = this.opts.getScene();
57
+ for (const s of post.elements.values()) {
58
+ if (s.parentId === parent.id)
59
+ this.opts.growContainer(parent.id, s.id);
60
+ }
61
+ mutated = true;
62
+ }
63
+ if (mutated)
64
+ this.opts.onMutated();
65
+ }
66
+ signatureFor(parentId) {
67
+ const scene = this.opts.getScene();
68
+ const parent = getElement(scene, parentId);
69
+ const children = [...scene.elements.values()].filter((s) => s.parentId === parentId);
70
+ // Wrap containers reflow on drop-zone geometry (origin + width) AND
71
+ // child-size changes (not just child add/remove): fold the drop-zone
72
+ // top-left + width + each child's size into the signature so resizing the
73
+ // container, MOVING it, or resizing a child re-anchors children at the
74
+ // (top-left-pinned) drop-zone origin. Other kinds keep the ids-only
75
+ // fingerprint (a manual position nudge isn't snapped back).
76
+ if (parent && getAutoLayoutSpec(parent)?.kind === "wrap") {
77
+ const dz = getDropZoneWorld(parent);
78
+ const origin = dz ? `${Math.round(dz.x)},${Math.round(dz.y)}` : "0,0";
79
+ const innerW = dz ? Math.round(dz.width) : 0;
80
+ const parts = children
81
+ .map((s) => {
82
+ const b = getElementWorldBounds(s);
83
+ return `${s.id}:${Math.round(b.width)}x${Math.round(b.height)}`;
84
+ })
85
+ .sort();
86
+ return `o${origin}|w${innerW}|${parts.join(",")}`;
87
+ }
88
+ const ids = children.map((s) => s.id);
89
+ ids.sort();
90
+ return ids.join(",");
91
+ }
92
+ }
93
+ export { apply };
94
+ //# sourceMappingURL=auto-layout-scheduler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auto-layout-scheduler.js","sourceRoot":"","sources":["../src/auto-layout-scheduler.ts"],"names":[],"mappings":"AACA,OAAO,EACL,KAAK,EACL,iBAAiB,EACjB,gBAAgB,EAChB,UAAU,EACV,qBAAqB,EACrB,aAAa,GAGd,MAAM,wBAAwB,CAAC;AAEhC;;;;;;;;;;GAUG;AACH,MAAM,OAAO,mBAAmB;IAID;IAHrB,OAAO,GAAG,KAAK,CAAC;IACP,UAAU,GAAG,IAAI,GAAG,EAAqB,CAAC;IAE3D,YAA6B,IAAgC;QAAhC,SAAI,GAAJ,IAAI,CAA4B;IAAG,CAAC;IAEjE,8DAA8D;IAC9D,QAAQ;QACN,IAAI,IAAI,CAAC,OAAO;YAAE,OAAO;QACzB,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;QACpB,cAAc,CAAC,GAAG,EAAE;YAClB,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC;YACrB,IAAI,CAAC,QAAQ,EAAE,CAAC;QAClB,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;;;OAIG;IACH,cAAc,CAAC,QAAmB;QAChC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,QAAQ,EAAE,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC,CAAC;IAC7D,CAAC;IAED,kEAAkE;IAClE,QAAQ;QACN,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;QACnC,IAAI,OAAO,GAAG,KAAK,CAAC;QACpB,KAAK,MAAM,MAAM,IAAI,KAAK,CAAC,QAAQ,CAAC,MAAM,EAAE,EAAE,CAAC;YAC7C,IAAI,CAAC,iBAAiB,CAAC,MAAM,CAAC;gBAAE,SAAS;YACzC,MAAM,GAAG,GAAG,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;YACzC,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,KAAK,GAAG;gBAAE,SAAS;YACrD,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC;YACpC,MAAM,KAAK,GAAG,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC;YAC7D,IAAI,CAAC,KAAK;gBAAE,SAAS;YACrB,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;YAC5B,iEAAiE;YACjE,kEAAkE;YAClE,8BAA8B;YAC9B,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;YAClC,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,EAAE,CAAC;gBACvC,IAAI,CAAC,CAAC,QAAQ,KAAK,MAAM,CAAC,EAAE;oBAAE,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;YACzE,CAAC;YACD,OAAO,GAAG,IAAI,CAAC;QACjB,CAAC;QACD,IAAI,OAAO;YAAE,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC;IACrC,CAAC;IAEO,YAAY,CAAC,QAAmB;QACtC,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;QACnC,MAAM,MAAM,GAAG,UAAU,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;QAC3C,MAAM,QAAQ,GAAG,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC;QACrF,oEAAoE;QACpE,qEAAqE;QACrE,0EAA0E;QAC1E,uEAAuE;QACvE,oEAAoE;QACpE,4DAA4D;QAC5D,IAAI,MAAM,IAAI,iBAAiB,CAAC,MAAM,CAAC,EAAE,IAAI,KAAK,MAAM,EAAE,CAAC;YACzD,MAAM,EAAE,GAAG,gBAAgB,CAAC,MAAM,CAAC,CAAC;YACpC,MAAM,MAAM,GAAG,EAAE,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC;YACtE,MAAM,MAAM,GAAG,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YAC7C,MAAM,KAAK,GAAG,QAAQ;iBACnB,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;gBACT,MAAM,CAAC,GAAG,qBAAqB,CAAC,CAAC,CAAC,CAAC;gBACnC,OAAO,GAAG,CAAC,CAAC,EAAE,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC;YAClE,CAAC,CAAC;iBACD,IAAI,EAAE,CAAC;YACV,OAAO,IAAI,MAAM,KAAK,MAAM,IAAI,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QACpD,CAAC;QACD,MAAM,GAAG,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QACtC,GAAG,CAAC,IAAI,EAAE,CAAC;QACX,OAAO,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACvB,CAAC;CACF;AAqBD,OAAO,EAAE,KAAK,EAAE,CAAC"}
@@ -0,0 +1,24 @@
1
+ import { type FileDropHandler } from "./file-drop.js";
2
+ /**
3
+ * Built-in handler: any file matching IMAGE_MIME_TYPES becomes an
4
+ * ImageElement at the drop point. Measured size is downscaled to
5
+ * `DEFAULT_IMAGE_MAX_EDGE_PX` on the longer axis so huge phone
6
+ * snapshots arrive at a reasonable on-canvas size.
7
+ *
8
+ * Registered automatically by the Editor constructor; hosts that
9
+ * want different sizing can `unregisterFileDropHandler("image")`
10
+ * and supply their own.
11
+ */
12
+ export declare const imageFileDropHandler: FileDropHandler;
13
+ /**
14
+ * Built-in video file-drop handler. Drops a `<video>` element into
15
+ * the hidden sink (autoplay + muted + loop so it advances frames
16
+ * without user-gesture limits), reads the natural dimensions, and
17
+ * inserts an image-shaped scene element pointing at the video
18
+ * element via `metadata.image`. The Canvas2D renderer accepts a
19
+ * video element through drawImage and reads its current frame.
20
+ * The editor's animation tick (`metadata.animated = true`) keeps
21
+ * the canvas in sync with the playing video.
22
+ */
23
+ export declare const videoFileDropHandler: FileDropHandler;
24
+ //# sourceMappingURL=built-in-handlers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"built-in-handlers.d.ts","sourceRoot":"","sources":["../src/built-in-handlers.ts"],"names":[],"mappings":"AAEA,OAAO,EAIL,KAAK,eAAe,EACrB,MAAM,gBAAgB,CAAC;AAmBxB;;;;;;;;;GASG;AACH,eAAO,MAAM,oBAAoB,EAAE,eA+FlC,CAAC;AAiCF;;;;;;;;;GASG;AACH,eAAO,MAAM,oBAAoB,EAAE,eAgDlC,CAAC"}
@@ -0,0 +1,210 @@
1
+ import { DEFAULT_IMAGE_MAX_EDGE_PX } from "./constants.js";
2
+ import { isImageFile, isVideoFile, readFileAsDataURL, } from "./file-drop.js";
3
+ /**
4
+ * Read an image data URL and return its natural pixel dimensions
5
+ * (loaded once via an off-screen `<img>` element).
6
+ */
7
+ const measureImage = (dataUrl) => new Promise((resolve, reject) => {
8
+ if (typeof Image === "undefined") {
9
+ reject(new Error("Image() is not available in this environment"));
10
+ return;
11
+ }
12
+ const img = new Image();
13
+ img.onload = () => { resolve({ width: img.naturalWidth, height: img.naturalHeight }); };
14
+ img.onerror = () => { reject(new Error("Failed to decode image data URL")); };
15
+ img.src = dataUrl;
16
+ });
17
+ /**
18
+ * Built-in handler: any file matching IMAGE_MIME_TYPES becomes an
19
+ * ImageElement at the drop point. Measured size is downscaled to
20
+ * `DEFAULT_IMAGE_MAX_EDGE_PX` on the longer axis so huge phone
21
+ * snapshots arrive at a reasonable on-canvas size.
22
+ *
23
+ * Registered automatically by the Editor constructor; hosts that
24
+ * want different sizing can `unregisterFileDropHandler("image")`
25
+ * and supply their own.
26
+ */
27
+ export const imageFileDropHandler = {
28
+ id: "image",
29
+ accept: (file) => isImageFile(file),
30
+ handle: async (file, { editor, worldPoint }) => {
31
+ // Register the blob in Scene.files first, then create an object-URL
32
+ // for renderer convenience. Scene.json ends up with just a small
33
+ // `fileId` reference instead of the full base64 dataURL, keeping the
34
+ // payload small even for large PNGs.
35
+ //
36
+ // The dataURL fallback path produces a usable `src` for the renderer
37
+ // where `URL.createObjectURL(blob)` is unavailable (headless / SSR):
38
+ // the file is read as a dataURL so the shape still has something to
39
+ // draw if a host renders it before the file blob lands.
40
+ const useObjectUrl = typeof URL !== "undefined" && typeof URL.createObjectURL === "function";
41
+ const fileId = await editor.addBinaryFile(file, file.name);
42
+ const src = useObjectUrl ? URL.createObjectURL(file) : await readFileAsDataURL(file);
43
+ const natural = await measureImage(src);
44
+ const max = DEFAULT_IMAGE_MAX_EDGE_PX;
45
+ const scale = Math.max(natural.width, natural.height) > max
46
+ ? max / Math.max(natural.width, natural.height)
47
+ : 1;
48
+ const width = Math.round(natural.width * scale);
49
+ const height = Math.round(natural.height * scale);
50
+ // Land the image centered on the drop point.
51
+ const topLeft = {
52
+ x: worldPoint.x - width / 2,
53
+ y: worldPoint.y - height / 2,
54
+ };
55
+ // Pre-decoded `<img>` instance so the Canvas2D renderer can call
56
+ // drawImage() directly (canvas can't drawImage a raw string). GIFs
57
+ // animate natively in the element — the animation tick re-draws every
58
+ // frame.
59
+ //
60
+ // For animation the `<img>` must be attached to the DOM (Safari won't
61
+ // advance frames on a detached element, and Chrome/Firefox can also
62
+ // pause off-DOM elements). It is attached to a hidden container so the
63
+ // user never sees the raw image element.
64
+ const isGif = file.type === "image/gif" || /\.gif$/i.test(file.name);
65
+ let img = null;
66
+ if (typeof Image !== "undefined") {
67
+ img = new Image();
68
+ img.src = src;
69
+ if (typeof document !== "undefined") {
70
+ const sink = ensureAnimatedImageSink();
71
+ // Keep the element inside the viewport — browsers pause GIF frame
72
+ // advancement for images scrolled out of view as a power-saving
73
+ // measure, so an off-screen element would freeze on its first
74
+ // frame. It is pinned to the top-left corner and hidden via
75
+ // near-zero opacity + 1px size + z-index behind everything.
76
+ // Throttling keys off viewport-intersection, not opacity, so a
77
+ // 0.01-alpha 1px element keeps decoding frames while staying
78
+ // imperceptible to the user.
79
+ img.style.position = "fixed";
80
+ img.style.left = "0";
81
+ img.style.top = "0";
82
+ img.style.width = "1px";
83
+ img.style.height = "1px";
84
+ img.style.opacity = "0.01";
85
+ img.style.zIndex = "-1";
86
+ img.style.pointerEvents = "none";
87
+ sink.appendChild(img);
88
+ }
89
+ // Wait for the image to decode before handing it to the renderer; an
90
+ // undecoded element draws nothing and the user sees a flash of empty
91
+ // rectangle on first paint.
92
+ const el = img;
93
+ if (!el.complete) {
94
+ await new Promise((resolve) => {
95
+ el.onload = () => { resolve(); };
96
+ el.onerror = () => { resolve(); };
97
+ });
98
+ }
99
+ }
100
+ // GIF animation goes through the adapter path: the raw bytes become
101
+ // `animationData`, and the renderer asks the registered "gif" adapter
102
+ // for the current frame (relying on the hidden `<img>` to advance
103
+ // frames is unreliable since browsers pause near-invisible elements).
104
+ // `animationData` is transient (an ArrayBuffer doesn't survive JSON) —
105
+ // on reload the editor rehydrates it from `Scene.files`.
106
+ const animationBytes = isGif ? await file.arrayBuffer() : undefined;
107
+ editor.insertImage({
108
+ src,
109
+ fileId,
110
+ width,
111
+ height,
112
+ position: topLeft,
113
+ ...(img ? { image: img } : {}),
114
+ animated: isGif,
115
+ ...(isGif ? { animationKind: "gif" } : {}),
116
+ ...(animationBytes ? { animationData: animationBytes } : {}),
117
+ });
118
+ },
119
+ };
120
+ /**
121
+ * Singleton hidden container that holds animated `<img>` elements so
122
+ * the browser keeps decoding their frames. One container per
123
+ * document; created lazily.
124
+ *
125
+ * Pinned to the viewport's top-left (`position:fixed; 0,0`) — not
126
+ * off-screen — because browsers pause GIF animation for elements
127
+ * outside the viewport. A 0×0 container with `overflow:visible`
128
+ * lets the 1px children sit at the corner; `pointer-events:none`
129
+ * and `z-index:-1` keep it from intercepting clicks or covering UI.
130
+ */
131
+ const SINK_ID = "oh-just-another-animated-image-sink";
132
+ const ensureAnimatedImageSink = () => {
133
+ const existing = document.getElementById(SINK_ID);
134
+ if (existing)
135
+ return existing;
136
+ const div = document.createElement("div");
137
+ div.id = SINK_ID;
138
+ div.setAttribute("aria-hidden", "true");
139
+ div.style.position = "fixed";
140
+ div.style.left = "0";
141
+ div.style.top = "0";
142
+ div.style.width = "0";
143
+ div.style.height = "0";
144
+ div.style.overflow = "visible";
145
+ div.style.pointerEvents = "none";
146
+ div.style.zIndex = "-1";
147
+ document.body.appendChild(div);
148
+ return div;
149
+ };
150
+ /**
151
+ * Built-in video file-drop handler. Drops a `<video>` element into
152
+ * the hidden sink (autoplay + muted + loop so it advances frames
153
+ * without user-gesture limits), reads the natural dimensions, and
154
+ * inserts an image-shaped scene element pointing at the video
155
+ * element via `metadata.image`. The Canvas2D renderer accepts a
156
+ * video element through drawImage and reads its current frame.
157
+ * The editor's animation tick (`metadata.animated = true`) keeps
158
+ * the canvas in sync with the playing video.
159
+ */
160
+ export const videoFileDropHandler = {
161
+ id: "video",
162
+ accept: (file) => isVideoFile(file),
163
+ handle: async (file, { editor, worldPoint }) => {
164
+ if (typeof document === "undefined")
165
+ return;
166
+ const url = URL.createObjectURL(file);
167
+ const video = document.createElement("video");
168
+ video.src = url;
169
+ video.muted = true;
170
+ video.loop = true;
171
+ video.playsInline = true;
172
+ video.autoplay = true;
173
+ const sink = ensureAnimatedImageSink();
174
+ video.style.position = "absolute";
175
+ video.style.left = "-99999px";
176
+ video.style.top = "-99999px";
177
+ video.style.width = "1px";
178
+ video.style.height = "1px";
179
+ sink.appendChild(video);
180
+ await new Promise((resolve) => {
181
+ const done = () => { resolve(); };
182
+ video.onloadedmetadata = done;
183
+ video.onerror = done;
184
+ });
185
+ const nW = video.videoWidth || 480;
186
+ const nH = video.videoHeight || 270;
187
+ const max = DEFAULT_IMAGE_MAX_EDGE_PX;
188
+ const scale = Math.max(nW, nH) > max ? max / Math.max(nW, nH) : 1;
189
+ const width = Math.round(nW * scale);
190
+ const height = Math.round(nH * scale);
191
+ const topLeft = {
192
+ x: worldPoint.x - width / 2,
193
+ y: worldPoint.y - height / 2,
194
+ };
195
+ // Best-effort play (some browsers require an interaction
196
+ // before allowing autoplay even when muted).
197
+ void video.play().catch(() => {
198
+ /* intentional no-op: autoplay rejection is expected before user interaction */
199
+ });
200
+ editor.insertImage({
201
+ src: url,
202
+ width,
203
+ height,
204
+ position: topLeft,
205
+ image: video,
206
+ animated: true,
207
+ });
208
+ },
209
+ };
210
+ //# sourceMappingURL=built-in-handlers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"built-in-handlers.js","sourceRoot":"","sources":["../src/built-in-handlers.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,51 @@
1
+ import type { ElementId, Vec2 } from "@oh-just-another/types";
2
+ import { type Scene, type Element } from "@oh-just-another/scene";
3
+ import type { HistoryProvider } from "@oh-just-another/history";
4
+ /**
5
+ * Clipboard helpers.
6
+ *
7
+ * The Editor's internal clipboard buffer is an array of deep-cloned
8
+ * shapes — it survives across calls within one editor session;
9
+ * cross-tab paste is a host concern (`navigator.clipboard`).
10
+ *
11
+ * • `copyElements` — collect selection into a fresh buffer.
12
+ * • `pasteElements` — paste a buffer into the scene at a target
13
+ * centroid, returning the new selection.
14
+ */
15
+ export interface PasteResult {
16
+ readonly scene: Scene;
17
+ readonly newIds: readonly ElementId[];
18
+ }
19
+ /**
20
+ * Clone a shape for the clipboard buffer / paste output. Deep-clones the
21
+ * plain data (so later edits don't bleed into the buffer) but carries
22
+ * live runtime handles by reference instead of cloning them:
23
+ *
24
+ * - `metadata.image` — a decoded `HTMLImageElement`; `structuredClone`
25
+ * throws `DataCloneError` on DOM nodes.
26
+ * - `animationData` — the raw GIF/video buffer; large and re-derivable
27
+ * from `fileId`, so sharing the reference avoids copying megabytes.
28
+ *
29
+ * The live `<img>` / buffer is shared, so the pasted shape draws
30
+ * immediately (same as the original) and still references its `fileId`.
31
+ */
32
+ export declare const cloneElementForClipboard: (shape: Element) => Element;
33
+ /**
34
+ * Snapshot every selected shape into a fresh array. Uses
35
+ * {@link cloneElementForClipboard} so live runtime handles (decoded
36
+ * image element, animation buffer) survive — a plain `structuredClone`
37
+ * throws on the DOM `<img>` an image shape carries in `metadata.image`.
38
+ */
39
+ export declare const copyElements: (scene: Scene, selection: Iterable<ElementId>) => Element[];
40
+ /**
41
+ * Paste the clipboard into `scene` so that its centroid lands at
42
+ * `target` (when supplied) or `+10` from each shape's stored
43
+ * position (so duplicates stay visible). Relative offsets between
44
+ * clipboard items are preserved.
45
+ *
46
+ * Allocates new shape ids via the `genId` callback (so the Editor
47
+ * keeps owning the counter) and pushes patches into `history`.
48
+ * Returns the new scene + ids the caller should select.
49
+ */
50
+ export declare const pasteElements: (scene: Scene, history: HistoryProvider, clipboard: readonly Element[], target: Vec2 | null, genId: () => ElementId) => PasteResult;
51
+ //# sourceMappingURL=clipboard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clipboard.d.ts","sourceRoot":"","sources":["../src/clipboard.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,EAML,KAAK,KAAK,EACV,KAAK,OAAO,EACb,MAAM,wBAAwB,CAAC;AAChC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAEhE;;;;;;;;;;GAUG;AAEH,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,SAAS,SAAS,EAAE,CAAC;CACvC;AAED;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,wBAAwB,GAAI,OAAO,OAAO,KAAG,OAmBzD,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,YAAY,GACvB,OAAO,KAAK,EACZ,WAAW,QAAQ,CAAC,SAAS,CAAC,KAC7B,OAAO,EAOT,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,aAAa,GACxB,OAAO,KAAK,EACZ,SAAS,eAAe,EACxB,WAAW,SAAS,OAAO,EAAE,EAC7B,QAAQ,IAAI,GAAG,IAAI,EACnB,OAAO,MAAM,SAAS,KACrB,WA8CF,CAAC"}
@@ -0,0 +1,105 @@
1
+ import { addElement, batch, getElement, orderForTop, } from "@oh-just-another/scene";
2
+ /**
3
+ * Clone a shape for the clipboard buffer / paste output. Deep-clones the
4
+ * plain data (so later edits don't bleed into the buffer) but carries
5
+ * live runtime handles by reference instead of cloning them:
6
+ *
7
+ * - `metadata.image` — a decoded `HTMLImageElement`; `structuredClone`
8
+ * throws `DataCloneError` on DOM nodes.
9
+ * - `animationData` — the raw GIF/video buffer; large and re-derivable
10
+ * from `fileId`, so sharing the reference avoids copying megabytes.
11
+ *
12
+ * The live `<img>` / buffer is shared, so the pasted shape draws
13
+ * immediately (same as the original) and still references its `fileId`.
14
+ */
15
+ export const cloneElementForClipboard = (shape) => {
16
+ const meta = shape.metadata;
17
+ const liveImage = meta && "image" in meta ? meta.image : undefined;
18
+ const liveAnim = shape.animationData;
19
+ // Strip the live handles, deep-clone the rest, then re-attach by ref.
20
+ const stripped = { ...shape };
21
+ if (liveAnim !== undefined)
22
+ delete stripped.animationData;
23
+ if (meta) {
24
+ const m = { ...meta };
25
+ delete m.image;
26
+ stripped.metadata = m;
27
+ }
28
+ const cloned = structuredClone(stripped);
29
+ if (liveImage !== undefined) {
30
+ cloned.metadata = { ...cloned.metadata, image: liveImage };
31
+ }
32
+ if (liveAnim !== undefined)
33
+ cloned.animationData = liveAnim;
34
+ return cloned;
35
+ };
36
+ /**
37
+ * Snapshot every selected shape into a fresh array. Uses
38
+ * {@link cloneElementForClipboard} so live runtime handles (decoded
39
+ * image element, animation buffer) survive — a plain `structuredClone`
40
+ * throws on the DOM `<img>` an image shape carries in `metadata.image`.
41
+ */
42
+ export const copyElements = (scene, selection) => {
43
+ const out = [];
44
+ for (const id of selection) {
45
+ const s = getElement(scene, id);
46
+ if (s)
47
+ out.push(cloneElementForClipboard(s));
48
+ }
49
+ return out;
50
+ };
51
+ /**
52
+ * Paste the clipboard into `scene` so that its centroid lands at
53
+ * `target` (when supplied) or `+10` from each shape's stored
54
+ * position (so duplicates stay visible). Relative offsets between
55
+ * clipboard items are preserved.
56
+ *
57
+ * Allocates new shape ids via the `genId` callback (so the Editor
58
+ * keeps owning the counter) and pushes patches into `history`.
59
+ * Returns the new scene + ids the caller should select.
60
+ */
61
+ export const pasteElements = (scene, history, clipboard, target, genId) => {
62
+ if (clipboard.length === 0)
63
+ return { scene, newIds: [] };
64
+ let cx = 0;
65
+ let cy = 0;
66
+ for (const s of clipboard) {
67
+ cx += s.position.x;
68
+ cy += s.position.y;
69
+ }
70
+ cx /= clipboard.length;
71
+ cy /= clipboard.length;
72
+ const delta = target
73
+ ? { x: target.x - cx, y: target.y - cy }
74
+ : { x: 10, y: 10 };
75
+ // Build the patch list first, push as one batch. `history.transaction()`
76
+ // is intentionally avoided: an Editor with an open gesture-transaction
77
+ // (drag/resize in flight) would make `transaction()` throw "A transaction
78
+ // is already open". The batch-push path doesn't nest, so paste is always
79
+ // safe to call.
80
+ const patches = [];
81
+ const newIds = [];
82
+ let next = scene;
83
+ for (const tmpl of clipboard) {
84
+ const newId = genId();
85
+ const order = orderForTop([...next.elements.values()]
86
+ .filter((s) => s.layerId === tmpl.layerId)
87
+ .map((s) => s.order));
88
+ const clone = {
89
+ ...cloneElementForClipboard(tmpl),
90
+ id: newId,
91
+ position: { x: tmpl.position.x + delta.x, y: tmpl.position.y + delta.y },
92
+ order,
93
+ };
94
+ const r = addElement(next, clone);
95
+ next = r.scene;
96
+ patches.push(r.patch);
97
+ newIds.push(newId);
98
+ }
99
+ const first = patches[0];
100
+ if (first !== undefined) {
101
+ history.push(patches.length === 1 ? first : batch(patches));
102
+ }
103
+ return { scene: next, newIds };
104
+ };
105
+ //# sourceMappingURL=clipboard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clipboard.js","sourceRoot":"","sources":["../src/clipboard.ts"],"names":[],"mappings":"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"}