@neta-art/cohub 9.0.0 → 10.0.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 (178) hide show
  1. package/README.md +100 -103
  2. package/dist/board/editor/align.d.ts +11 -0
  3. package/dist/board/editor/align.js +72 -0
  4. package/dist/board/editor/animation.d.ts +14 -0
  5. package/dist/board/editor/animation.js +172 -0
  6. package/dist/board/editor/camera.d.ts +32 -0
  7. package/dist/board/editor/camera.js +137 -0
  8. package/dist/board/editor/clipboard.d.ts +41 -0
  9. package/dist/board/editor/clipboard.js +122 -0
  10. package/dist/board/editor/connection-ports.d.ts +19 -0
  11. package/dist/board/editor/connection-ports.js +85 -0
  12. package/dist/board/editor/context.d.ts +111 -0
  13. package/dist/board/editor/context.js +30 -0
  14. package/dist/board/editor/copy.d.ts +17 -0
  15. package/dist/board/editor/copy.js +116 -0
  16. package/dist/board/editor/creation.d.ts +47 -0
  17. package/dist/board/editor/creation.js +224 -0
  18. package/dist/board/editor/deletion.d.ts +9 -0
  19. package/dist/board/editor/deletion.js +90 -0
  20. package/dist/board/editor/document-edits.d.ts +22 -0
  21. package/dist/board/editor/document-edits.js +193 -0
  22. package/dist/board/editor/document.d.ts +35 -0
  23. package/dist/board/editor/document.js +255 -0
  24. package/dist/board/editor/editor.d.ts +210 -0
  25. package/dist/board/editor/editor.js +429 -0
  26. package/dist/board/editor/index.d.ts +18 -0
  27. package/dist/board/editor/index.js +12 -0
  28. package/dist/board/editor/interaction.d.ts +115 -0
  29. package/dist/board/editor/interaction.js +584 -0
  30. package/dist/board/editor/query.d.ts +27 -0
  31. package/dist/board/editor/query.js +201 -0
  32. package/dist/board/editor/remote.d.ts +11 -0
  33. package/dist/board/editor/remote.js +55 -0
  34. package/dist/board/editor/selection-transform.d.ts +24 -0
  35. package/dist/board/editor/selection-transform.js +81 -0
  36. package/dist/board/editor/selection.d.ts +33 -0
  37. package/dist/board/editor/selection.js +196 -0
  38. package/dist/board/editor/snapping.d.ts +33 -0
  39. package/dist/board/editor/snapping.js +75 -0
  40. package/dist/board/editor/snapshots.d.ts +27 -0
  41. package/dist/board/editor/snapshots.js +132 -0
  42. package/dist/board/editor/spatial.d.ts +41 -0
  43. package/dist/board/editor/spatial.js +191 -0
  44. package/dist/board/editor/state.d.ts +12 -0
  45. package/dist/board/editor/state.js +49 -0
  46. package/dist/board/editor/tool.d.ts +10 -0
  47. package/dist/board/editor/tool.js +19 -0
  48. package/dist/board/editor/wheel.d.ts +5 -0
  49. package/dist/board/editor/wheel.js +17 -0
  50. package/dist/board/export/index.d.ts +5 -4
  51. package/dist/board/export/index.js +4 -3
  52. package/dist/board/export/node/index.d.ts +55 -0
  53. package/dist/board/{headless → export/node}/index.js +12 -12
  54. package/dist/board/export/node/sketch.d.ts +19 -0
  55. package/dist/board/{headless → export/node}/sketch.js +6 -6
  56. package/dist/board/export/scene.d.ts +6 -4
  57. package/dist/board/export/scene.js +4 -3
  58. package/dist/board/{animation.d.ts → model/animation.d.ts} +4 -4
  59. package/dist/board/{animation.js → model/animation.js} +3 -3
  60. package/dist/board/{core → model}/arrow-geometry.d.ts +2 -2
  61. package/dist/board/{core → model}/arrow-geometry.js +2 -2
  62. package/dist/board/{core → model}/draw-geometry.d.ts +2 -2
  63. package/dist/board/{core → model}/draw-geometry.js +1 -1
  64. package/dist/board/model/draw-input.d.ts +14 -0
  65. package/dist/board/model/draw-input.js +28 -0
  66. package/dist/board/{core → model}/export-assets.d.ts +1 -1
  67. package/dist/board/{core → model}/export-assets.js +1 -1
  68. package/dist/board/{core → model}/export-plan.d.ts +2 -2
  69. package/dist/board/{core → model}/export-plan.js +2 -2
  70. package/dist/board/{core → model}/file-preview.d.ts +1 -1
  71. package/dist/board/{core → model}/file-preview.js +1 -1
  72. package/dist/board/{core → model}/file-snapshot.d.ts +1 -1
  73. package/dist/board/{core → model}/file-snapshot.js +1 -1
  74. package/dist/board/{geometry.d.ts → model/geometry.d.ts} +3 -3
  75. package/dist/board/{geometry.js → model/geometry.js} +1 -1
  76. package/dist/board/{image-key.d.ts → model/image-key.d.ts} +2 -2
  77. package/dist/board/{image-key.js → model/image-key.js} +1 -1
  78. package/dist/board/model/index.d.ts +35 -0
  79. package/dist/board/{index.js → model/index.js} +26 -22
  80. package/dist/board/model/items/builtin.d.ts +5 -0
  81. package/dist/board/model/items/builtin.js +119 -0
  82. package/dist/board/model/items/create.d.ts +59 -0
  83. package/dist/board/model/items/create.js +271 -0
  84. package/dist/board/model/items/definition.d.ts +35 -0
  85. package/dist/board/model/items/definition.js +18 -0
  86. package/dist/board/model/items/id.d.ts +5 -0
  87. package/dist/board/model/items/id.js +11 -0
  88. package/dist/board/model/items/index.d.ts +6 -0
  89. package/dist/board/model/items/index.js +6 -0
  90. package/dist/board/model/items/registry.d.ts +35 -0
  91. package/dist/board/model/items/registry.js +84 -0
  92. package/dist/board/{media-playback.d.ts → model/media-playback.d.ts} +2 -2
  93. package/dist/board/{media-playback.js → model/media-playback.js} +1 -1
  94. package/dist/board/{media.d.ts → model/media.d.ts} +1 -1
  95. package/dist/board/{media.js → model/media.js} +1 -1
  96. package/dist/board/{core → model}/palette.d.ts +1 -1
  97. package/dist/board/{core → model}/palette.js +1 -1
  98. package/dist/board/{presets.d.ts → model/presets.d.ts} +3 -3
  99. package/dist/board/{presets.js → model/presets.js} +1 -1
  100. package/dist/board/{replay.d.ts → model/replay.d.ts} +5 -5
  101. package/dist/board/{replay.js → model/replay.js} +2 -2
  102. package/dist/board/{core → model}/scene.d.ts +1 -1
  103. package/dist/board/{core → model}/scene.js +1 -1
  104. package/dist/board/model/shape-types.d.ts +8 -0
  105. package/dist/board/model/shape-types.js +8 -0
  106. package/dist/board/{task.d.ts → model/task.d.ts} +4 -4
  107. package/dist/board/{task.js → model/task.js} +3 -3
  108. package/dist/board/{core → model}/text-metrics.d.ts +1 -1
  109. package/dist/board/{core → model}/text-metrics.js +1 -1
  110. package/dist/board/{core → model}/tool-styles.d.ts +1 -1
  111. package/dist/board/{core → model}/tool-styles.js +1 -1
  112. package/dist/board/{runtime/board-player.d.ts → player/index.d.ts} +2 -2
  113. package/dist/board/{runtime/board-player.js → player/index.js} +5 -4
  114. package/dist/board/render/board-background.d.ts +1 -1
  115. package/dist/board/render/clip.d.ts +1 -1
  116. package/dist/board/render/index.d.ts +2 -2
  117. package/dist/board/render/index.js +2 -2
  118. package/dist/board/render/media-interaction.d.ts +2 -2
  119. package/dist/board/render/media-interaction.js +1 -1
  120. package/dist/board/render/palette.d.ts +1 -1
  121. package/dist/board/render/palette.js +1 -1
  122. package/dist/board/render/renderers/arrow-card-renderer.js +1 -1
  123. package/dist/board/render/renderers/board-renderer-registry.d.ts +11 -6
  124. package/dist/board/render/renderers/board-renderer-registry.js +15 -15
  125. package/dist/board/render/renderers/draw-card-renderer.js +1 -1
  126. package/dist/board/render/renderers/far-plate.js +1 -1
  127. package/dist/board/render/renderers/file-card-renderer.d.ts +1 -1
  128. package/dist/board/render/renderers/file-card-renderer.js +2 -2
  129. package/dist/board/render/renderers/image-card-renderer.d.ts +1 -1
  130. package/dist/board/render/renderers/shape-card-renderer.js +1 -1
  131. package/dist/board/render/renderers/task-card-renderer.js +1 -1
  132. package/dist/board/render/renderers/text-card-renderer.js +1 -1
  133. package/dist/board/render/renderers/video-card-renderer.d.ts +1 -1
  134. package/dist/board/render/stroke.d.ts +1 -1
  135. package/dist/board/render/stroke.js +1 -1
  136. package/dist/board/render/text-measurement.js +1 -1
  137. package/dist/board/replica/index.d.ts +70 -0
  138. package/dist/board/replica/index.js +276 -0
  139. package/dist/board/stage/assets.d.ts +41 -0
  140. package/dist/board/stage/assets.js +350 -0
  141. package/dist/board/stage/index.d.ts +8 -0
  142. package/dist/board/stage/index.js +8 -0
  143. package/dist/board/stage/lru.d.ts +32 -0
  144. package/dist/board/stage/lru.js +29 -0
  145. package/dist/board/stage/pointer.d.ts +8 -0
  146. package/dist/board/stage/pointer.js +33 -0
  147. package/dist/board/stage/scene.d.ts +85 -0
  148. package/dist/board/stage/scene.js +512 -0
  149. package/dist/board/stage/stage.d.ts +66 -0
  150. package/dist/board/stage/stage.js +568 -0
  151. package/dist/board/stage/texture-pool.d.ts +24 -0
  152. package/dist/board/stage/texture-pool.js +154 -0
  153. package/dist/board/stage/views.d.ts +38 -0
  154. package/dist/board/stage/views.js +93 -0
  155. package/dist/chunks/environment.d.ts +25 -1
  156. package/dist/chunks/http.d.ts +99 -66
  157. package/dist/chunks/http.js +214 -207
  158. package/dist/chunks/websocket.d.ts +1 -1
  159. package/dist/http.d.ts +2 -2
  160. package/dist/index.d.ts +3 -3
  161. package/dist/index.js +20 -4
  162. package/dist/media.d.ts +1 -10
  163. package/dist/media.js +16 -27
  164. package/dist/protocol/dist/board-patch.js +1 -1
  165. package/dist/protocol/dist/board.d.ts +28 -2
  166. package/dist/protocol/dist/board.js +3 -1
  167. package/dist/protocol/dist/core/media.d.ts +5 -0
  168. package/dist/protocol/dist/core/media.js +39 -0
  169. package/dist/protocol/dist/index.d.ts +5 -5
  170. package/dist/types.d.ts +1 -1
  171. package/package.json +26 -14
  172. package/dist/board/core/shape-definition.d.ts +0 -23
  173. package/dist/board/core/shape-definition.js +0 -45
  174. package/dist/board/core/shape-types.d.ts +0 -38
  175. package/dist/board/core/shape-types.js +0 -22
  176. package/dist/board/headless/index.d.ts +0 -55
  177. package/dist/board/headless/sketch.d.ts +0 -19
  178. package/dist/board/index.d.ts +0 -30
@@ -0,0 +1,584 @@
1
+ import { clampZoom, normalizeRotation, panBy, rectCenter, rectsIntersect, resizeFrame, rotateFrames, scaleFrames, screenPoint, selectionBounds, worldPoint, zoomAround } from "../model/geometry.js";
2
+ import { resolveSceneArrow } from "../model/arrow-geometry.js";
3
+ import { appendBoardDrawSamples } from "../model/draw-input.js";
4
+ import { createBoardItemId } from "../model/items/id.js";
5
+ import { connectionPortAt, connectionPorts } from "./connection-ports.js";
6
+ import { canTapSelectWithHand, isCreationBoardTool, isWithinHandTapSlop } from "./tool.js";
7
+ import { selectionTransformControlAt } from "./selection-transform.js";
8
+ import { computeSnap } from "./snapping.js";
9
+ import { memo } from "./state.js";
10
+ //#region src/board/editor/interaction.ts
11
+ const DRAG_THRESHOLD = 3;
12
+ const SNAP_THRESHOLD = 8;
13
+ const NO_IDS = /* @__PURE__ */ new Set();
14
+ function angleFrom(center, point) {
15
+ return Math.atan2(point.y - center.y, point.x - center.x) * 180 / Math.PI;
16
+ }
17
+ function createInteractionModule(ctx) {
18
+ const { state, options, registry } = ctx;
19
+ const activePointers = /* @__PURE__ */ new Map();
20
+ let pinch = null;
21
+ const marquee = memo(() => [state.interaction], (interaction) => {
22
+ if (interaction.type !== "brushing") return null;
23
+ const { start, current } = interaction;
24
+ return {
25
+ x: Math.min(start.x, current.x),
26
+ y: Math.min(start.y, current.y),
27
+ width: Math.abs(current.x - start.x),
28
+ height: Math.abs(current.y - start.y)
29
+ };
30
+ });
31
+ const hoveredControl = memo(() => [
32
+ state.interaction,
33
+ state.tool,
34
+ state.hoverPoint,
35
+ state.hoverPointerType,
36
+ state.camera.zoom,
37
+ ctx.selection.transform()
38
+ ], (interaction, tool, hoverPoint, pointerType, zoom, transform) => interaction.type === "idle" && !pinch && tool === "select" && hoverPoint ? selectionTransformControlAt(transform, hoverPoint, zoom, pointerType) : null);
39
+ function gridSnapSize() {
40
+ const grid = state.base.board.grid;
41
+ return grid?.visible === true ? grid.size ?? 0 : 0;
42
+ }
43
+ function snapThreshold() {
44
+ return SNAP_THRESHOLD / Math.max(state.camera.zoom, 1e-4);
45
+ }
46
+ function computeTranslationSnap(moved, skip) {
47
+ const movingBounds = selectionBounds([...moved.values()]);
48
+ if (!movingBounds) return {
49
+ dx: 0,
50
+ dy: 0,
51
+ guides: []
52
+ };
53
+ const view = ctx.camera.visibleRect();
54
+ const targets = [];
55
+ for (const id of view ? ctx.query.idsInRect(view) : state.scene.items.map((item) => item.id)) {
56
+ if (skip.has(id)) continue;
57
+ const item = state.scene.get(id);
58
+ if (item && registry.capabilities(item).canSnap) targets.push(registry.bounds(item));
59
+ }
60
+ return computeSnap(movingBounds, targets, {
61
+ threshold: snapThreshold(),
62
+ gridSize: gridSnapSize()
63
+ });
64
+ }
65
+ function reparentDropped(ids) {
66
+ const changes = /* @__PURE__ */ new Map();
67
+ const moving = ctx.query.withSubtrees(ids);
68
+ for (const id of ids) {
69
+ const item = state.scene.get(id);
70
+ if (!item || item.type === "arrow") continue;
71
+ const parent = ctx.query.frameAt(rectCenter(item.frame), moving)?.id;
72
+ if (parent === item.parent) continue;
73
+ const stored = ctx.document.displayItem(id);
74
+ if (!stored) continue;
75
+ changes.set(id, {
76
+ ...ctx.document.reparent(stored, item.frame, parent, state.scene.layout.box(id)),
77
+ z: ctx.document.topZ(parent) + changes.size
78
+ });
79
+ }
80
+ return changes;
81
+ }
82
+ function portItemId() {
83
+ const { tool, editingId, interaction, selection, hoverId, scene } = state;
84
+ if (options.readonly || tool !== "select" || editingId) return null;
85
+ if (interaction.type !== "idle" && interaction.type !== "creatingArrow") return null;
86
+ const hoverReveals = !canTapSelectWithHand(state.hoverPointerType);
87
+ const candidateId = selection.length === 1 ? selection[0] : hoverReveals ? hoverId : null;
88
+ if (!candidateId) return null;
89
+ const item = scene.get(candidateId);
90
+ if (!item || ctx.query.isLocked(item) || item.type === "arrow") return null;
91
+ return registry.capabilities(item).canConnect ? item.id : null;
92
+ }
93
+ function visiblePorts() {
94
+ const itemId = portItemId();
95
+ const item = itemId ? state.scene.get(itemId) : null;
96
+ if (!itemId || !item) return [];
97
+ return connectionPorts(item.frame, state.camera.zoom).map((port) => ({
98
+ ...port,
99
+ itemId
100
+ }));
101
+ }
102
+ function connectionPortAt$1(point, pointerType) {
103
+ const itemId = portItemId();
104
+ const item = itemId ? state.scene.get(itemId) : null;
105
+ if (!itemId || !item) return null;
106
+ const port = connectionPortAt(item.frame, point, state.camera.zoom, pointerType);
107
+ return port ? {
108
+ ...port,
109
+ itemId
110
+ } : null;
111
+ }
112
+ function beginPinch() {
113
+ const [a, b] = [...activePointers.values()];
114
+ if (!a || !b) return;
115
+ pinch = {
116
+ distance: Math.max(1, Math.hypot(a.x - b.x, a.y - b.y)),
117
+ midpoint: screenPoint((a.x + b.x) / 2, (a.y + b.y) / 2),
118
+ zoom: state.camera.zoom
119
+ };
120
+ state.interaction = { type: "idle" };
121
+ }
122
+ function brush(event, additive) {
123
+ state.interaction = {
124
+ type: "brushing",
125
+ start: event.world,
126
+ current: event.world,
127
+ additive,
128
+ baseSelection: state.selection
129
+ };
130
+ }
131
+ function arrowGesture(start, current, startItemId, startSide) {
132
+ const { arrow } = state.toolStyles;
133
+ return {
134
+ type: "creatingArrow",
135
+ id: createBoardItemId(),
136
+ start,
137
+ current,
138
+ startItemId,
139
+ startSide,
140
+ targetItemId: null,
141
+ color: arrow.color,
142
+ size: arrow.size
143
+ };
144
+ }
145
+ function pointerDown(event) {
146
+ ctx.camera.cancelAnimation();
147
+ state.hoverPoint = event.world;
148
+ state.hoverPointerType = event.pointerType;
149
+ const { interaction, tool, spaceHeld, toolStyles, camera } = state;
150
+ if (interaction.type === "drawing" && interaction.pointerId !== event.pointerId) return;
151
+ activePointers.set(event.pointerId, event.screen);
152
+ if (activePointers.size === 2) {
153
+ beginPinch();
154
+ return;
155
+ }
156
+ if (activePointers.size > 2) return;
157
+ const additive = event.shiftKey || event.metaKey || event.ctrlKey;
158
+ if (tool === "hand" || spaceHeld || event.button === 1) {
159
+ const tapSelection = tool === "hand" && !spaceHeld && event.button !== 1 && canTapSelectWithHand(event.pointerType) ? { targetId: ctx.query.itemAt(event.world)?.id ?? null } : null;
160
+ state.interaction = {
161
+ type: "panning",
162
+ start: event.screen,
163
+ origin: { ...camera },
164
+ moved: false,
165
+ tapSelection
166
+ };
167
+ return;
168
+ }
169
+ if (options.readonly) {
170
+ const hit = ctx.query.itemAt(event.world);
171
+ if (!hit) return brush(event, additive);
172
+ if (additive) ctx.selection.toggle(hit.id);
173
+ else state.selection = [hit.id];
174
+ state.interaction = { type: "idle" };
175
+ return;
176
+ }
177
+ switch (tool) {
178
+ case "draw": {
179
+ const points = [];
180
+ appendBoardDrawSamples(points, [event], camera.zoom);
181
+ state.interaction = {
182
+ type: "drawing",
183
+ id: createBoardItemId(),
184
+ pointerId: event.pointerId,
185
+ points,
186
+ color: toolStyles.draw.color,
187
+ size: toolStyles.draw.size
188
+ };
189
+ return;
190
+ }
191
+ case "arrow":
192
+ state.interaction = arrowGesture(event.world, event.world, ctx.query.connectTargetAt(event.world), null);
193
+ return;
194
+ case "shape":
195
+ case "frame":
196
+ state.interaction = {
197
+ type: "creatingBox",
198
+ id: createBoardItemId(),
199
+ kind: tool,
200
+ start: event.world,
201
+ current: event.world,
202
+ color: tool === "shape" ? toolStyles.shape.color : toolStyles.frame.color,
203
+ geometry: toolStyles.shape.geometry
204
+ };
205
+ return;
206
+ case "text":
207
+ ctx.creation.beginTextDraft(event.world);
208
+ return;
209
+ }
210
+ const port = connectionPortAt$1(event.world, event.pointerType);
211
+ if (port) {
212
+ state.interaction = arrowGesture(port.point, event.world, port.itemId, port.side);
213
+ return;
214
+ }
215
+ const arrowHandle = ctx.query.arrowHandleAt(event.world);
216
+ const arrow = ctx.selection.items()[0];
217
+ if (arrowHandle && arrow?.type === "arrow") {
218
+ const { id: _id, frame: _frame, ...origin } = arrow;
219
+ state.interaction = {
220
+ type: "draggingArrowHandle",
221
+ arrowId: arrow.id,
222
+ which: arrowHandle,
223
+ origin,
224
+ targetItemId: null,
225
+ moved: false
226
+ };
227
+ return;
228
+ }
229
+ const bounds = ctx.selection.bounds();
230
+ const control = selectionTransformControlAt(ctx.selection.transform(), event.world, camera.zoom, event.pointerType);
231
+ if (control?.kind === "rotate" && bounds) {
232
+ const pivot = rectCenter(bounds);
233
+ state.interaction = {
234
+ type: "rotating",
235
+ pivot,
236
+ startAngle: angleFrom(pivot, event.world),
237
+ current: event.world,
238
+ origin: ctx.selection.framesFor(ctx.selection.movable()),
239
+ moved: false
240
+ };
241
+ return;
242
+ }
243
+ if (control?.kind === "resize" && bounds) {
244
+ const origin = ctx.selection.framesFor(ctx.selection.movable());
245
+ const single = origin.size === 1 ? { ...[...origin.values()][0] } : null;
246
+ state.interaction = {
247
+ type: "resizing",
248
+ handle: control.handle,
249
+ single,
250
+ bounds,
251
+ origin,
252
+ moved: false
253
+ };
254
+ return;
255
+ }
256
+ const item = ctx.query.itemAt(event.world);
257
+ if (!item) return brush(event, additive);
258
+ if (additive) ctx.selection.toggle(item.id);
259
+ else if (!state.selection.includes(item.id)) state.selection = [item.id];
260
+ const movable = ctx.selection.movable();
261
+ state.interaction = movable.length === 0 ? { type: "idle" } : {
262
+ type: "translating",
263
+ start: event.world,
264
+ origin: ctx.selection.framesFor(movable),
265
+ moved: false,
266
+ duplicate: event.altKey
267
+ };
268
+ }
269
+ function pinchMove() {
270
+ const [a, b] = [...activePointers.values()];
271
+ if (!a || !b || !pinch) return;
272
+ const distance = Math.max(1, Math.hypot(a.x - b.x, a.y - b.y));
273
+ const midpoint = screenPoint((a.x + b.x) / 2, (a.y + b.y) / 2);
274
+ const zoomed = zoomAround(state.camera, midpoint, clampZoom(pinch.zoom * (distance / pinch.distance)));
275
+ ctx.camera.setCamera(panBy(zoomed, midpoint.x - pinch.midpoint.x, midpoint.y - pinch.midpoint.y));
276
+ pinch = {
277
+ ...pinch,
278
+ midpoint
279
+ };
280
+ }
281
+ function translate(gesture, event) {
282
+ let dx = event.world.x - gesture.start.x;
283
+ let dy = event.world.y - gesture.start.y;
284
+ let current = gesture;
285
+ if (!current.moved) {
286
+ if (Math.hypot(dx, dy) <= DRAG_THRESHOLD / state.camera.zoom) return;
287
+ current = {
288
+ ...current,
289
+ moved: true
290
+ };
291
+ if (gesture.duplicate) {
292
+ const clones = ctx.copy.duplicates([...gesture.origin.keys()], 0);
293
+ if (clones.size > 0) {
294
+ ctx.document.writeDraft(clones, true);
295
+ const roots = ctx.copy.rootsOfCopies(clones);
296
+ state.selection = roots;
297
+ current = {
298
+ ...current,
299
+ origin: ctx.selection.framesFor(roots),
300
+ duplicate: false
301
+ };
302
+ }
303
+ }
304
+ state.interaction = current;
305
+ }
306
+ if (event.shiftKey) {
307
+ if (Math.abs(dx) >= Math.abs(dy)) dy = 0;
308
+ else dx = 0;
309
+ }
310
+ const shift = (tx, ty) => {
311
+ const frames = /* @__PURE__ */ new Map();
312
+ for (const [id, frame] of current.origin) frames.set(id, {
313
+ ...frame,
314
+ x: frame.x + tx,
315
+ y: frame.y + ty
316
+ });
317
+ return frames;
318
+ };
319
+ let tx = dx;
320
+ let ty = dy;
321
+ if (!event.metaKey && !event.ctrlKey) {
322
+ const snap = computeTranslationSnap(shift(dx, dy), ctx.query.withSubtrees(current.origin.keys()));
323
+ tx += snap.dx;
324
+ ty += snap.dy;
325
+ state.snapGuides = snap.guides;
326
+ } else state.snapGuides = [];
327
+ ctx.document.writeDraft(ctx.selection.placed(shift(tx, ty)));
328
+ }
329
+ function resize(gesture, event) {
330
+ if (!gesture.moved) state.interaction = {
331
+ ...gesture,
332
+ moved: true
333
+ };
334
+ const frames = /* @__PURE__ */ new Map();
335
+ if (gesture.single) {
336
+ const id = [...gesture.origin.keys()][0];
337
+ const item = state.scene.get(id);
338
+ const keepAspect = item ? registry.capabilities(item).aspectLocked || event.shiftKey : event.shiftKey;
339
+ frames.set(id, resizeFrame(gesture.single, gesture.handle, event.world, void 0, keepAspect));
340
+ } else {
341
+ const scaled = scaleFrames([...gesture.origin.values()], gesture.bounds, gesture.handle, event.world);
342
+ [...gesture.origin.keys()].forEach((id, index) => {
343
+ const frame = scaled[index];
344
+ if (frame) frames.set(id, frame);
345
+ });
346
+ }
347
+ ctx.document.writeDraft(ctx.selection.placed(frames));
348
+ }
349
+ function rotate(gesture, event) {
350
+ state.interaction = {
351
+ ...gesture,
352
+ moved: true,
353
+ current: event.world
354
+ };
355
+ let delta = angleFrom(gesture.pivot, event.world) - gesture.startAngle;
356
+ if (event.shiftKey) delta = Math.round(delta / 15) * 15;
357
+ const rotated = rotateFrames([...gesture.origin.values()], gesture.pivot, delta);
358
+ const frames = /* @__PURE__ */ new Map();
359
+ [...gesture.origin.keys()].forEach((id, index) => {
360
+ const frame = rotated[index];
361
+ if (frame) frames.set(id, {
362
+ ...frame,
363
+ rotation: normalizeRotation(frame.rotation)
364
+ });
365
+ });
366
+ ctx.document.writeDraft(ctx.selection.placed(frames));
367
+ }
368
+ function dragArrowHandle(gesture, event) {
369
+ if (!gesture.moved) state.interaction = {
370
+ ...gesture,
371
+ moved: true
372
+ };
373
+ const { arrowId, which, origin } = gesture;
374
+ const { scene } = state;
375
+ const arrow = scene.get(arrowId);
376
+ if (!arrow) return;
377
+ if (which === "mid") {
378
+ const resolved = resolveSceneArrow({
379
+ ...arrow,
380
+ props: origin.props
381
+ }, scene);
382
+ const start = resolved.start.point;
383
+ const end = resolved.end.point;
384
+ const length = Math.hypot(end.x - start.x, end.y - start.y) || 1;
385
+ const mid = worldPoint((start.x + end.x) / 2, (start.y + end.y) / 2);
386
+ const bend = ((event.world.x - mid.x) * -(end.y - start.y) + (event.world.y - mid.y) * (end.x - start.x)) / (length * length);
387
+ const route = origin.props.route === "straight" ? "curve" : origin.props.route;
388
+ const props = {
389
+ ...origin.props,
390
+ route,
391
+ bend: Math.max(-.85, Math.min(.85, bend))
392
+ };
393
+ ctx.document.writeDraft(/* @__PURE__ */ new Map([[arrowId, {
394
+ ...origin,
395
+ props
396
+ }]]));
397
+ state.snapGuides = [];
398
+ return;
399
+ }
400
+ const targetItemId = ctx.query.connectTargetAt(event.world, /* @__PURE__ */ new Set([arrowId]));
401
+ let point = event.world;
402
+ if (!targetItemId && !event.metaKey && !event.ctrlKey) {
403
+ const snap = computeSnap({
404
+ x: point.x,
405
+ y: point.y,
406
+ width: 0,
407
+ height: 0
408
+ }, scene.items.filter((item) => item.id !== arrowId && registry.capabilities(item).canSnap).map((item) => registry.bounds(item)), {
409
+ threshold: snapThreshold(),
410
+ gridSize: gridSnapSize()
411
+ });
412
+ point = worldPoint(point.x + snap.dx, point.y + snap.dy);
413
+ state.snapGuides = snap.guides;
414
+ } else state.snapGuides = [];
415
+ state.interaction = {
416
+ ...state.interaction,
417
+ targetItemId
418
+ };
419
+ const end = ctx.creation.arrowEnd(point, targetItemId, scene.layout.matrix(arrowId));
420
+ ctx.document.writeDraft(/* @__PURE__ */ new Map([[arrowId, {
421
+ ...origin,
422
+ props: {
423
+ ...origin.props,
424
+ [which]: end
425
+ }
426
+ }]]), true);
427
+ }
428
+ function pointerMove(event) {
429
+ state.hoverPoint = event.world;
430
+ state.hoverPointerType = event.pointerType;
431
+ if (activePointers.has(event.pointerId)) activePointers.set(event.pointerId, event.screen);
432
+ if (pinch && activePointers.size >= 2) return pinchMove();
433
+ const interaction = state.interaction;
434
+ switch (interaction.type) {
435
+ case "idle":
436
+ state.hoverId = isCreationBoardTool(state.tool) || hoveredControl() ? null : ctx.query.itemAt(event.world)?.id ?? null;
437
+ return;
438
+ case "panning": {
439
+ const dx = event.screen.x - interaction.start.x;
440
+ const dy = event.screen.y - interaction.start.y;
441
+ if (interaction.tapSelection && !interaction.moved && isWithinHandTapSlop(dx, dy)) return;
442
+ if (!interaction.moved) state.interaction = {
443
+ ...interaction,
444
+ moved: true
445
+ };
446
+ ctx.camera.setCamera(panBy(interaction.origin, dx, dy));
447
+ return;
448
+ }
449
+ case "translating": return translate(interaction, event);
450
+ case "resizing": return resize(interaction, event);
451
+ case "rotating": return rotate(interaction, event);
452
+ case "draggingArrowHandle": return dragArrowHandle(interaction, event);
453
+ case "brushing": {
454
+ state.interaction = {
455
+ ...interaction,
456
+ current: event.world
457
+ };
458
+ const rect = marquee();
459
+ if (!rect) return;
460
+ const hits = ctx.query.idsInRect(rect).filter((id) => {
461
+ const item = state.scene.get(id);
462
+ return item && !item.parent && rectsIntersect(ctx.query.boundsOf(item), rect);
463
+ });
464
+ state.selection = interaction.additive ? [.../* @__PURE__ */ new Set([...interaction.baseSelection, ...hits])] : hits;
465
+ return;
466
+ }
467
+ case "drawing":
468
+ if (event.pointerId !== interaction.pointerId || (event.buttons & 1) === 0) return;
469
+ if (appendBoardDrawSamples(interaction.points, event.samples ?? [event], state.camera.zoom)) state.interaction = { ...interaction };
470
+ return;
471
+ case "creatingArrow": {
472
+ const exclude = interaction.startItemId ? /* @__PURE__ */ new Set([interaction.startItemId]) : NO_IDS;
473
+ state.interaction = {
474
+ ...interaction,
475
+ current: event.world,
476
+ targetItemId: ctx.query.connectTargetAt(event.world, exclude)
477
+ };
478
+ return;
479
+ }
480
+ case "creatingBox":
481
+ state.interaction = {
482
+ ...interaction,
483
+ current: event.world
484
+ };
485
+ return;
486
+ }
487
+ }
488
+ function pointerUp(event) {
489
+ state.hoverPoint = event.world;
490
+ state.hoverPointerType = event.pointerType;
491
+ activePointers.delete(event.pointerId);
492
+ if (activePointers.size < 2) pinch = null;
493
+ if (activePointers.size > 0) return;
494
+ const gesture = state.interaction;
495
+ if (gesture.type === "drawing" && gesture.pointerId !== event.pointerId) return;
496
+ state.snapGuides = [];
497
+ state.interaction = { type: "idle" };
498
+ switch (gesture.type) {
499
+ case "panning":
500
+ if (gesture.tapSelection && !gesture.moved && !event.cancelled) {
501
+ const targetId = gesture.tapSelection.targetId;
502
+ state.selection = targetId && state.scene.get(targetId) ? [targetId] : [];
503
+ }
504
+ break;
505
+ case "translating":
506
+ if (gesture.moved) {
507
+ for (const [id, item] of reparentDropped(ctx.query.rootsOf(state.selection))) ctx.document.setDraftItem(id, item);
508
+ ctx.document.commit();
509
+ }
510
+ break;
511
+ case "resizing":
512
+ case "rotating":
513
+ case "draggingArrowHandle":
514
+ if (gesture.moved) ctx.document.commit();
515
+ break;
516
+ case "brushing":
517
+ if (!gesture.additive && Math.hypot(gesture.current.x - gesture.start.x, gesture.current.y - gesture.start.y) <= 1 / state.camera.zoom) state.selection = [];
518
+ break;
519
+ case "drawing": {
520
+ const pressure = gesture.points.at(-1)?.p ?? event.pressure;
521
+ appendBoardDrawSamples(gesture.points, [{
522
+ world: event.world,
523
+ pressure
524
+ }], state.camera.zoom);
525
+ ctx.creation.commitDraw(gesture.id, gesture.points, gesture.color, gesture.size);
526
+ break;
527
+ }
528
+ case "creatingArrow":
529
+ ctx.creation.commitArrow(gesture, event.cancelled);
530
+ break;
531
+ case "creatingBox": ctx.creation.commitBox(gesture);
532
+ }
533
+ ctx.remote.flushPending();
534
+ }
535
+ function pointerLeave() {
536
+ if (state.interaction.type !== "idle") return;
537
+ state.hoverPoint = null;
538
+ state.hoverId = null;
539
+ }
540
+ function cancel() {
541
+ activePointers.clear();
542
+ pinch = null;
543
+ state.snapGuides = [];
544
+ state.interaction = { type: "idle" };
545
+ ctx.document.discardDraft();
546
+ ctx.remote.flushPending();
547
+ }
548
+ const ports = memo(() => [
549
+ state.scene,
550
+ state.selection,
551
+ state.hoverId,
552
+ state.hoverPointerType,
553
+ state.interaction,
554
+ state.tool,
555
+ state.editingId,
556
+ state.camera.zoom
557
+ ], (..._inputs) => visiblePorts().map((port) => ({
558
+ itemId: port.itemId,
559
+ x: port.point.x,
560
+ y: port.point.y,
561
+ radius: 4
562
+ })));
563
+ return {
564
+ marquee,
565
+ hoveredControl,
566
+ ports,
567
+ hoveredPort: memo(() => [ports(), state.hoverPoint], (_ports, hoverPoint) => {
568
+ if (!hoverPoint) return null;
569
+ const port = connectionPortAt$1(hoverPoint, state.hoverPointerType);
570
+ return port ? {
571
+ x: port.point.x,
572
+ y: port.point.y
573
+ } : null;
574
+ }),
575
+ pointerDown,
576
+ pointerMove,
577
+ pointerUp,
578
+ pointerLeave,
579
+ cancel,
580
+ destroy: () => activePointers.clear()
581
+ };
582
+ }
583
+ //#endregion
584
+ export { createInteractionModule };
@@ -0,0 +1,27 @@
1
+ import { Rect, WorldPoint } from "../model/geometry.js";
2
+ import { BoardSceneItem } from "../model/scene.js";
3
+ import "../model/index.js";
4
+ import { EditorContext } from "./context.js";
5
+ //#region src/board/editor/query.d.ts
6
+ declare function createQueryModule(ctx: EditorContext): {
7
+ invalidate: (ids?: readonly string[]) => void;
8
+ boundsOf: (item: BoardSceneItem) => Rect;
9
+ itemById: (id: string) => BoardSceneItem | null;
10
+ isLocked: (item: {
11
+ locked?: boolean;
12
+ }) => boolean;
13
+ unlockedIds: (ids: Iterable<string>) => string[];
14
+ rootsOf: (ids: Iterable<string>) => string[];
15
+ withSubtrees: (ids: Iterable<string>) => Set<string>;
16
+ mediaIdsForPath: (path: string) => ReadonlySet<string>;
17
+ contentBounds: (ids?: Iterable<string>) => Rect | null;
18
+ idsInRect: (rect: Rect) => string[];
19
+ frameAt: (point: WorldPoint, exclude?: ReadonlySet<string>) => BoardSceneItem | null;
20
+ itemAt: (point: WorldPoint, exclude?: ReadonlySet<string>) => BoardSceneItem | null;
21
+ labelItemAt: (point: WorldPoint) => BoardSceneItem | null;
22
+ arrowHandleAt: (point: WorldPoint) => "start" | "end" | "mid" | null;
23
+ connectTargetAt: (point: WorldPoint, exclude?: ReadonlySet<string>) => string | null;
24
+ };
25
+ type QueryModule = ReturnType<typeof createQueryModule>;
26
+ //#endregion
27
+ export { QueryModule, createQueryModule };