@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,66 @@
1
+ import { BoardItem } from "../../protocol/dist/board-model.js";
2
+ import { BoardPlaybackSnapshot } from "../../protocol/dist/board.js";
3
+ import "../../protocol/dist/index.js";
4
+ import { BoardViewport, WorldPoint } from "../model/geometry.js";
5
+ import { BoardScene, BoardSceneItem } from "../model/scene.js";
6
+ import { BoardShapeColors } from "../model/palette.js";
7
+ import { BoardAssetSource } from "../model/media-playback.js";
8
+ import "../model/index.js";
9
+ import { BoardPointerEvent } from "../editor/interaction.js";
10
+ import { BoardEditor } from "../editor/editor.js";
11
+ import "../editor/index.js";
12
+ import { BoardCardRendererResolver, BoardRenderPalette } from "../render/renderers/board-renderer-registry.js";
13
+ import "../render/index.js";
14
+ import { BoardAssetManager } from "./assets.js";
15
+ import { BoardLiveStroke } from "./scene.js";
16
+ import { BoardItemView } from "./views.js";
17
+ import { Graphics, Renderer } from "pixi.js";
18
+ //#region src/board/stage/stage.d.ts
19
+ type BoardStageTheme = {
20
+ key: string;
21
+ palette: BoardRenderPalette;
22
+ colors: BoardShapeColors;
23
+ colorScheme: "dark" | "light";
24
+ imageBackground?: boolean;
25
+ };
26
+ type BoardStagePointerPhase = "down" | "move" | "up" | "leave";
27
+ type BoardStageOptions = {
28
+ editor: BoardEditor;
29
+ assets: BoardAssetManager;
30
+ source: BoardAssetSource;
31
+ renderers?: BoardCardRendererResolver;
32
+ views?: readonly BoardItemView[];
33
+ theme?: () => BoardStageTheme;
34
+ playback?: () => BoardPlaybackSnapshot | null;
35
+ fileState?: (path: string) => "ok" | "missing" | "unavailable";
36
+ previews?: () => ReadonlyMap<string, BoardItem>;
37
+ strokes?: () => readonly BoardLiveStroke[];
38
+ drawOverlay?: (overlay: Graphics, frame: {
39
+ zoom: number;
40
+ theme: BoardStageTheme;
41
+ scene: BoardScene;
42
+ }) => void;
43
+ onPointer?: (phase: BoardStagePointerPhase, event: PointerEvent, input: BoardPointerEvent) => boolean | undefined;
44
+ onDoubleClick?: (item: BoardSceneItem | null, point: WorldPoint) => boolean | undefined;
45
+ onSurfaceChange?: (size: {
46
+ width: number;
47
+ height: number;
48
+ }) => void;
49
+ onVisibleChange?: (ids: ReadonlySet<string> | null) => void;
50
+ };
51
+ type BoardStage = {
52
+ readonly ready: Promise<boolean>;
53
+ readonly renderer: Renderer | null;
54
+ readonly camera: BoardViewport;
55
+ readonly theme: BoardStageTheme;
56
+ setActive(active: boolean): void;
57
+ invalidate(options?: {
58
+ cards?: boolean;
59
+ }): void;
60
+ isMaterialized(id: string): boolean;
61
+ toWorld(clientX: number, clientY: number): WorldPoint;
62
+ destroy(): void;
63
+ };
64
+ declare function mountBoardStage(host: HTMLElement, options: BoardStageOptions): BoardStage;
65
+ //#endregion
66
+ export { BoardStage, BoardStageOptions, BoardStagePointerPhase, BoardStageTheme, mountBoardStage };
@@ -0,0 +1,568 @@
1
+ import { cameraForFocus, cameraForState, screenPoint, screenToWorld, stableCullRect, visibleWorldRect, worldPoint } from "../model/geometry.js";
2
+ import { resolveSceneArrow } from "../model/arrow-geometry.js";
3
+ import { buildFallbackShapeColors, pickBoardColor, resolveItemColor } from "../model/palette.js";
4
+ import { featuredTaskArtifact } from "../model/task.js";
5
+ import { isCreationBoardTool } from "../editor/tool.js";
6
+ import { resizeCursorForHandle } from "../editor/selection-transform.js";
7
+ import { parseBoardCssColor } from "../render/css-color.js";
8
+ import { createBoardBackground, updateBoardBackground } from "../render/board-background.js";
9
+ import { getBoardResolution, textZoomBucket } from "../render/text-resolution.js";
10
+ import { defaultBoardPalette } from "../render/palette.js";
11
+ import { getBoardCardRenderer } from "../render/renderers/board-renderer-registry.js";
12
+ import { createBoardSketchHost } from "../render/sketch-host.js";
13
+ import { createBoardPlayer } from "../player/index.js";
14
+ import { toBoardPointerEvent } from "./pointer.js";
15
+ import { createBoardScene } from "./scene.js";
16
+ import { createBoardItemViewLayer } from "./views.js";
17
+ import { Application, Container, Graphics, RendererType } from "pixi.js";
18
+ //#region src/board/stage/stage.ts
19
+ const LIVE_CHANNELS = [
20
+ "document",
21
+ "scene",
22
+ "selection",
23
+ "camera",
24
+ "tool",
25
+ "interaction",
26
+ "hover",
27
+ "editing",
28
+ "playback"
29
+ ];
30
+ function defaultTheme() {
31
+ return {
32
+ key: "dark",
33
+ palette: defaultBoardPalette("dark"),
34
+ colors: buildFallbackShapeColors("dark"),
35
+ colorScheme: "dark"
36
+ };
37
+ }
38
+ const ROTATE_CURSOR = `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath d='M21 12a9 9 0 1 1-2.64-6.36L21 8M21 3v5h-5' fill='none' stroke='%23fff' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M21 12a9 9 0 1 1-2.64-6.36L21 8M21 3v5h-5' fill='none' stroke='%231d1d1f' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") 12 12, crosshair`;
39
+ function mountBoardStage(host, options) {
40
+ const { editor, assets } = options;
41
+ const theme = options.theme ?? defaultTheme;
42
+ const resolveRenderer = options.renderers ?? getBoardCardRenderer;
43
+ let app = null;
44
+ let world = null;
45
+ let overlay = null;
46
+ let background = null;
47
+ let scene = null;
48
+ let sketches = null;
49
+ let active = true;
50
+ let disposed = false;
51
+ let renderFrame = 0;
52
+ let tickFrame = 0;
53
+ let resizeFrame = 0;
54
+ let cardEpoch = 0;
55
+ let assetVersion = 0;
56
+ let surface = {
57
+ width: 0,
58
+ height: 0
59
+ };
60
+ let playbackCamera = null;
61
+ let cursor = "";
62
+ const renderCamera = () => playbackCamera ?? editor.camera;
63
+ const views = createBoardItemViewLayer(host, editor, options.views ?? [], renderCamera);
64
+ const player = createBoardPlayer({ resolveColor: (color) => {
65
+ const current = theme();
66
+ return resolveItemColor(color, "brand", current.colors, current.colorScheme, parseBoardCssColor);
67
+ } });
68
+ let cull = {
69
+ rect: null,
70
+ ids: null,
71
+ scene: null
72
+ };
73
+ function visibleIds() {
74
+ if (surface.width === 0 || surface.height === 0) return null;
75
+ const rect = stableCullRect(visibleWorldRect(renderCamera(), surface.width, surface.height));
76
+ const same = cull.rect && cull.rect.x === rect.x && cull.rect.y === rect.y && cull.rect.width === rect.width && cull.rect.height === rect.height;
77
+ if (same && cull.scene === editor.scene) return cull.ids;
78
+ cull = {
79
+ rect: same ? cull.rect : rect,
80
+ ids: new Set(editor.idsInRect(rect)),
81
+ scene: editor.scene
82
+ };
83
+ options.onVisibleChange?.(cull.ids);
84
+ requestAssets = true;
85
+ return cull.ids;
86
+ }
87
+ let requestAssets = true;
88
+ function syncAssets(ids) {
89
+ if (!requestAssets || !ids) return;
90
+ requestAssets = false;
91
+ const sizes = [];
92
+ for (const id of ids) {
93
+ const item = editor.itemById(id);
94
+ if (!item) continue;
95
+ const key = assets.assetKey(item);
96
+ if (!key) continue;
97
+ assets.requestItem(item);
98
+ const artifact = item.type === "task" ? featuredTaskArtifact(item.props.snapshot.artifacts) : null;
99
+ const recorded = artifact?.type === "image" || artifact?.type === "video" ? artifact : item.type === "image" || item.type === "video" ? item.props.snapshot : null;
100
+ if (!recorded || recorded.naturalWidth && recorded.naturalHeight) continue;
101
+ const natural = assets.getNaturalSize(key);
102
+ if (natural?.width && natural.height) sizes.push({
103
+ id: item.id,
104
+ ...natural
105
+ });
106
+ }
107
+ if (sizes.length > 0) editor.adoptMediaNaturalSizes(sizes);
108
+ }
109
+ function renderContext(current, renderScene, time) {
110
+ const interaction = editor.interaction;
111
+ return {
112
+ settings: editor.settings,
113
+ scene: renderScene,
114
+ time,
115
+ ...sketches ? { sketches } : {},
116
+ selectedIds: new Set(editor.selection),
117
+ hoveredId: editor.hoverId,
118
+ resizingIds: interaction.type === "resizing" ? new Set(interaction.origin.keys()) : /* @__PURE__ */ new Set(),
119
+ palette: current.palette,
120
+ colors: current.colors,
121
+ colorScheme: current.colorScheme,
122
+ rendererType: app?.renderer.type === RendererType.CANVAS ? "canvas" : "gpu",
123
+ zoom: renderCamera().zoom,
124
+ assetKey: assets.assetKey,
125
+ getTexture: (key) => assets.getTexture(key),
126
+ hasError: (key) => assets.hasError(key),
127
+ fileState: (path) => options.fileState?.(path) ?? "ok",
128
+ acquireTexture: (key) => assets.acquire(key),
129
+ releaseTexture: (key) => assets.release(key)
130
+ };
131
+ }
132
+ function syncBackground(current) {
133
+ if (!app) return;
134
+ const context = {
135
+ app,
136
+ settings: editor.settings,
137
+ viewport: editor.camera,
138
+ colorScheme: current.colorScheme,
139
+ palette: current.palette,
140
+ hasImageBackground: current.imageBackground === true
141
+ };
142
+ if (background) updateBoardBackground(background, context);
143
+ else {
144
+ background = createBoardBackground(context);
145
+ app.stage.addChildAt(background, 0);
146
+ }
147
+ }
148
+ function localGestureItemIds() {
149
+ const interaction = editor.interaction;
150
+ switch (interaction.type) {
151
+ case "translating":
152
+ case "resizing":
153
+ case "rotating": return new Set(interaction.origin.keys());
154
+ case "draggingArrowHandle": return /* @__PURE__ */ new Set([interaction.arrowId]);
155
+ default: return /* @__PURE__ */ new Set();
156
+ }
157
+ }
158
+ function previewItems() {
159
+ const previews = /* @__PURE__ */ new Map();
160
+ const remote = options.previews?.();
161
+ if (!remote?.size) return previews;
162
+ const local = localGestureItemIds();
163
+ for (const [id, item] of remote) if (!local.has(id)) previews.set(id, item);
164
+ return previews;
165
+ }
166
+ function liveStrokes() {
167
+ const strokes = [...options.strokes?.() ?? []];
168
+ const interaction = editor.interaction;
169
+ if (interaction.type === "drawing") strokes.push({
170
+ id: interaction.id,
171
+ points: interaction.points,
172
+ color: interaction.color,
173
+ size: interaction.size
174
+ });
175
+ return strokes;
176
+ }
177
+ function followCamera(frame, renderScene) {
178
+ let camera = { ...editor.camera };
179
+ const focus = frame.camera.focus;
180
+ if (focus !== void 0 && surface.width > 0 && surface.height > 0) camera = cameraForFocus(focus, (id) => renderScene.get(id)?.frame, surface, { padding: 48 }) ?? camera;
181
+ if (frame.camera.zoom !== void 0) camera = {
182
+ ...camera,
183
+ zoom: frame.camera.zoom
184
+ };
185
+ if (frame.camera.center) camera = cameraForState({
186
+ centerX: frame.camera.center.x,
187
+ centerY: frame.camera.center.y,
188
+ zoom: camera.zoom
189
+ }, surface);
190
+ if (frame.camera.shake) {
191
+ const t = frame.time / 1e3;
192
+ camera = {
193
+ ...camera,
194
+ x: camera.x + Math.sin(t * 91.3) * frame.camera.shake,
195
+ y: camera.y + Math.cos(t * 77.9) * frame.camera.shake
196
+ };
197
+ }
198
+ return camera;
199
+ }
200
+ function arrowHead(target, from, to, color, width, inv, alpha) {
201
+ const angle = Math.atan2(to.y - from.y, to.x - from.x);
202
+ const head = Math.max(14, 16 * inv);
203
+ const spread = Math.PI / 6;
204
+ target.moveTo(to.x - head * Math.cos(angle - spread), to.y - head * Math.sin(angle - spread)).lineTo(to.x, to.y).lineTo(to.x - head * Math.cos(angle + spread), to.y - head * Math.sin(angle + spread)).stroke({
205
+ color,
206
+ width,
207
+ alpha,
208
+ cap: "round",
209
+ join: "round"
210
+ });
211
+ }
212
+ function drawTransient(target, current) {
213
+ const inv = 1 / Math.max(editor.camera.zoom, 1e-4);
214
+ const interaction = editor.interaction;
215
+ for (const guide of editor.snapGuides) {
216
+ const vertical = guide.axis === "x";
217
+ target.moveTo(vertical ? guide.at : guide.from, vertical ? guide.from : guide.at).lineTo(vertical ? guide.at : guide.to, vertical ? guide.to : guide.at).stroke({
218
+ color: current.palette.brand,
219
+ width: inv,
220
+ alpha: .9
221
+ });
222
+ }
223
+ if (interaction.type === "creatingArrow") {
224
+ const color = pickBoardColor(current.colors, interaction.color, current.colorScheme);
225
+ const target_ = interaction.targetItemId ? editor.itemById(interaction.targetItemId) : null;
226
+ const end = target_ ? worldPoint(target_.frame.x + target_.frame.width / 2, target_.frame.y + target_.frame.height / 2) : interaction.current;
227
+ const width = Math.max(interaction.size, 1.5 * inv);
228
+ target.moveTo(interaction.start.x, interaction.start.y).lineTo(end.x, end.y).stroke({
229
+ color: color.stroke,
230
+ width,
231
+ alpha: .9
232
+ });
233
+ arrowHead(target, interaction.start, end, color.stroke, width, inv, .95);
234
+ }
235
+ if (interaction.type === "creatingBox") {
236
+ const color = pickBoardColor(current.colors, interaction.color, current.colorScheme);
237
+ const { start, current: corner } = interaction;
238
+ const w = Math.abs(corner.x - start.x);
239
+ const h = Math.abs(corner.y - start.y);
240
+ if (w > 1 || h > 1) target.roundRect(Math.min(start.x, corner.x), Math.min(start.y, corner.y), Math.max(w, 1), Math.max(h, 1), 4).fill({
241
+ color: color.fill,
242
+ alpha: .04
243
+ }).stroke({
244
+ color: color.stroke,
245
+ width: 1.5 * inv,
246
+ alpha: .85
247
+ });
248
+ }
249
+ }
250
+ function cursorFor() {
251
+ const interaction = editor.interaction;
252
+ switch (interaction.type) {
253
+ case "panning":
254
+ case "translating": return "grabbing";
255
+ case "resizing": return resizeCursorForHandle(interaction.handle, interaction.single?.rotation ?? 0);
256
+ case "rotating": return ROTATE_CURSOR;
257
+ case "draggingArrowHandle":
258
+ case "brushing": return "crosshair";
259
+ }
260
+ if (editor.spaceHeld || editor.tool === "hand") return "grab";
261
+ const control = editor.hoveredTransformControl;
262
+ if (control?.kind === "resize") return resizeCursorForHandle(control.handle, editor.selectionTransform?.frame.rotation ?? 0);
263
+ if (control?.kind === "rotate") return ROTATE_CURSOR;
264
+ if (isCreationBoardTool(editor.tool)) return "crosshair";
265
+ const hovered = editor.hoverId ? editor.itemById(editor.hoverId) : null;
266
+ return hovered && !hovered.locked && editor.registry.capabilities(hovered).canMove ? "move" : "default";
267
+ }
268
+ function scheduleRender() {
269
+ if (renderFrame || !app || !active) return;
270
+ renderFrame = requestAnimationFrame(() => {
271
+ renderFrame = 0;
272
+ app?.render();
273
+ });
274
+ }
275
+ function scheduleTick() {
276
+ if (tickFrame || !active || disposed) return;
277
+ tickFrame = requestAnimationFrame(() => {
278
+ tickFrame = 0;
279
+ sync();
280
+ });
281
+ }
282
+ function sync() {
283
+ if (!app || !world || !scene || !overlay) return;
284
+ const current = theme();
285
+ syncBackground(current);
286
+ player.enter(editor.consumeRecentlyAdded(), editor.document);
287
+ const playback = options.playback?.() ?? null;
288
+ const frame = player.frame(editor.document, playback, performance.now(), Date.now(), editor.playhead?.animationId ?? null);
289
+ const previews = previewItems();
290
+ const moving = new Map(previews);
291
+ for (const [id, item] of frame.items) moving.set(id, item);
292
+ editor.setPlayedItems(moving);
293
+ const renderScene = editor.scene;
294
+ playbackCamera = editor.cameraPolicy === "follow" && Object.keys(frame.camera).length > 0 ? followCamera(frame, renderScene) : null;
295
+ const camera = renderCamera();
296
+ world.position.set(camera.x, camera.y);
297
+ world.scale.set(camera.zoom);
298
+ const ids = visibleIds();
299
+ syncAssets(ids);
300
+ const context = renderContext(current, renderScene, frame.time);
301
+ const pinnedIds = new Set(editor.selection);
302
+ for (const id of previews.keys()) pinnedIds.add(id);
303
+ if (editor.editingId) pinnedIds.add(editor.editingId);
304
+ for (const id of frame.items.keys()) {
305
+ pinnedIds.add(id);
306
+ for (const child of renderScene.descendants(id)) pinnedIds.add(child);
307
+ for (const binder of renderScene.binders(id)) pinnedIds.add(binder);
308
+ }
309
+ scene.sync({
310
+ items: renderScene.items,
311
+ strokes: liveStrokes(),
312
+ scene: renderScene,
313
+ context,
314
+ getItem: (id) => renderScene.get(id) ?? null,
315
+ visibleIds: ids,
316
+ pinnedIds,
317
+ globalSig: [
318
+ assetVersion,
319
+ cardEpoch,
320
+ current.key,
321
+ textZoomBucket(camera.zoom)
322
+ ].join("|"),
323
+ structureVersion: editor.structureVersion,
324
+ geometryVersion: editor.geometryVersion,
325
+ gestureActive: editor.gestureActive && editor.interaction.type !== "panning"
326
+ });
327
+ const single = editor.selection.length === 1 ? renderScene.get(editor.selection[0]) : null;
328
+ let arrowEndpoints;
329
+ if (single?.type === "arrow" && !single.locked) {
330
+ const resolved = resolveSceneArrow(single, renderScene);
331
+ arrowEndpoints = [
332
+ resolved.start.point,
333
+ resolved.mid,
334
+ resolved.end.point
335
+ ];
336
+ }
337
+ const interaction = editor.interaction;
338
+ scene.drawOverlay({
339
+ zoom: camera.zoom,
340
+ pointerType: editor.pointerType,
341
+ marquee: editor.marquee,
342
+ selection: editor.selection,
343
+ transform: editor.selectionTransform,
344
+ controls: editor.tool === "select",
345
+ hoveredControl: editor.hoveredTransformControl,
346
+ rotationPointer: interaction.type === "rotating" ? interaction.current : null,
347
+ arrowEndpoints,
348
+ ports: editor.connectionPorts,
349
+ hoveredPort: editor.hoveredConnectionPort,
350
+ arrowDraft: null,
351
+ bindTarget: editor.bindTargetFrame ?? editor.arrowDraft?.targetFrame ?? null
352
+ }, current.palette);
353
+ options.drawOverlay?.(overlay, {
354
+ zoom: camera.zoom,
355
+ theme: current,
356
+ scene: renderScene
357
+ });
358
+ drawTransient(overlay, current);
359
+ views.sync({
360
+ ids,
361
+ scene: renderScene,
362
+ camera,
363
+ surface,
364
+ selection: context.selectedIds
365
+ });
366
+ const nextCursor = cursorFor();
367
+ if (nextCursor !== cursor) host.style.cursor = cursor = nextCursor;
368
+ scheduleRender();
369
+ if (frame.running || scene.animated) scheduleTick();
370
+ }
371
+ function resize() {
372
+ cancelAnimationFrame(resizeFrame);
373
+ resizeFrame = requestAnimationFrame(() => {
374
+ if (!app) return;
375
+ app.resize();
376
+ surface = {
377
+ width: app.screen.width,
378
+ height: app.screen.height
379
+ };
380
+ editor.surfaceSize = surface;
381
+ options.onSurfaceChange?.(surface);
382
+ sync();
383
+ });
384
+ }
385
+ const rectOf = () => host.getBoundingClientRect();
386
+ const inViews = (event) => event.target instanceof Node && views.element.contains(event.target);
387
+ function toInput(event) {
388
+ return toBoardPointerEvent(event, rectOf(), renderCamera(), event.type === "pointermove" && editor.interaction.type === "drawing");
389
+ }
390
+ function handlePointerDown(event) {
391
+ if (inViews(event)) return;
392
+ editor.takeCameraControl();
393
+ const input = toInput(event);
394
+ if (options.onPointer?.("down", event, input) === false) return;
395
+ host.setPointerCapture(event.pointerId);
396
+ editor.pointerDown(input);
397
+ }
398
+ function handlePointerMove(event) {
399
+ if (inViews(event)) return;
400
+ const input = toInput(event);
401
+ if (options.onPointer?.("move", event, input) === false) return;
402
+ editor.pointerMove(input);
403
+ }
404
+ function handlePointerUp(event) {
405
+ if (inViews(event)) return;
406
+ const input = toInput(event);
407
+ if (options.onPointer?.("up", event, input) === false) return;
408
+ editor.pointerUp(input);
409
+ if (event.type === "pointercancel" || event.pointerType !== "mouse") editor.pointerLeave();
410
+ }
411
+ function handlePointerLeave(event) {
412
+ if (event.buttons !== 0) return;
413
+ if (options.onPointer?.("leave", event, toInput(event)) === false) return;
414
+ editor.pointerLeave();
415
+ }
416
+ function handleWheel(event) {
417
+ if (inViews(event)) return;
418
+ event.preventDefault();
419
+ editor.takeCameraControl();
420
+ const rect = rectOf();
421
+ const point = screenPoint(event.clientX - rect.left, event.clientY - rect.top);
422
+ editor.wheel(point, event.deltaX, event.deltaY, event.ctrlKey || event.metaKey, event.deltaMode);
423
+ }
424
+ function handleDoubleClick(event) {
425
+ if (inViews(event)) return;
426
+ const point = screenToWorld(event.clientX, event.clientY, rectOf(), renderCamera());
427
+ const item = editor.itemAt(point);
428
+ if (options.onDoubleClick?.(item, point) === true || editor.readonly) return;
429
+ if (item && !item.locked && editor.registry.capabilities(item).canEdit) {
430
+ editor.editingId = item.id;
431
+ return;
432
+ }
433
+ if (item && item.type !== "frame" && item.type !== "arrow") return;
434
+ const label = editor.labelItemAt(point);
435
+ if (label) editor.editingId = label.id;
436
+ else if (!item) editor.beginTextDraft(point);
437
+ }
438
+ const listeners = [
439
+ ["pointerdown", handlePointerDown],
440
+ ["pointermove", handlePointerMove],
441
+ ["pointerup", handlePointerUp],
442
+ ["pointercancel", handlePointerUp],
443
+ ["lostpointercapture", handlePointerUp],
444
+ ["pointerleave", handlePointerLeave],
445
+ [
446
+ "wheel",
447
+ handleWheel,
448
+ { passive: false }
449
+ ],
450
+ ["dblclick", handleDoubleClick]
451
+ ];
452
+ const stopEditor = editor.subscribe(LIVE_CHANNELS, scheduleTick);
453
+ const stopAssets = assets.subscribe(() => {
454
+ assetVersion += 1;
455
+ requestAssets = true;
456
+ scheduleTick();
457
+ });
458
+ const resizeObserver = new ResizeObserver(resize);
459
+ return {
460
+ ready: (async () => {
461
+ const instance = new Application();
462
+ try {
463
+ await instance.init({
464
+ antialias: true,
465
+ autoDensity: true,
466
+ backgroundAlpha: 0,
467
+ resizeTo: host,
468
+ resolution: getBoardResolution(),
469
+ autoStart: false
470
+ });
471
+ } catch (error) {
472
+ console.error("Board canvas failed to start", error);
473
+ instance.destroy({ removeView: true });
474
+ return false;
475
+ }
476
+ if (disposed) {
477
+ instance.destroy({ removeView: true });
478
+ return false;
479
+ }
480
+ app = instance;
481
+ instance.canvas.style.cssText = "position:relative;z-index:1;display:block";
482
+ host.insertBefore(instance.canvas, views.element);
483
+ world = new Container({
484
+ isRenderGroup: true,
485
+ label: "board-world"
486
+ });
487
+ const nodes = new Container({ label: "board-nodes" });
488
+ overlay = new Graphics({ label: "board-interaction-overlay" });
489
+ world.addChild(nodes, overlay);
490
+ instance.stage.addChild(world);
491
+ scene = createBoardScene({
492
+ world: nodes,
493
+ overlay,
494
+ getRenderer: resolveRenderer,
495
+ onFarLayerFrame: scheduleTick
496
+ });
497
+ sketches = createBoardSketchHost({
498
+ readModule: async (src) => {
499
+ const url = await options.source.resolveFileUrl(src);
500
+ if (!url) throw new Error(`${src} is not available.`);
501
+ const response = await fetch(url);
502
+ if (!response.ok) throw new Error(`${src} could not be read.`);
503
+ return response.text();
504
+ },
505
+ onFrame: () => sync()
506
+ });
507
+ for (const [type, listener, listenerOptions] of listeners) host.addEventListener(type, listener, listenerOptions);
508
+ resizeObserver.observe(host);
509
+ resize();
510
+ return true;
511
+ })(),
512
+ get renderer() {
513
+ return app ? app.renderer : null;
514
+ },
515
+ get camera() {
516
+ return renderCamera();
517
+ },
518
+ get theme() {
519
+ return theme();
520
+ },
521
+ setActive(next) {
522
+ if (active === next) return;
523
+ active = next;
524
+ if (!active) {
525
+ cancelAnimationFrame(renderFrame);
526
+ cancelAnimationFrame(tickFrame);
527
+ renderFrame = 0;
528
+ tickFrame = 0;
529
+ return;
530
+ }
531
+ player.reset();
532
+ resize();
533
+ },
534
+ invalidate(invalidation) {
535
+ if (invalidation?.cards) {
536
+ cardEpoch += 1;
537
+ requestAssets = true;
538
+ }
539
+ scheduleTick();
540
+ },
541
+ isMaterialized: (id) => Boolean(scene?.getNode(id)),
542
+ toWorld: (clientX, clientY) => screenToWorld(clientX, clientY, rectOf(), renderCamera()),
543
+ destroy() {
544
+ disposed = true;
545
+ stopEditor();
546
+ stopAssets();
547
+ resizeObserver.disconnect();
548
+ cancelAnimationFrame(resizeFrame);
549
+ cancelAnimationFrame(renderFrame);
550
+ cancelAnimationFrame(tickFrame);
551
+ for (const [type, listener] of listeners) host.removeEventListener(type, listener);
552
+ editor.setPlayedItems(/* @__PURE__ */ new Map());
553
+ scene?.destroy(renderContext(theme(), editor.scene, 0));
554
+ scene = null;
555
+ sketches?.destroy();
556
+ sketches = null;
557
+ background?.destroy({ children: true });
558
+ background = null;
559
+ views.destroy();
560
+ world = null;
561
+ overlay = null;
562
+ app?.destroy({ removeView: true });
563
+ app = null;
564
+ }
565
+ };
566
+ }
567
+ //#endregion
568
+ export { mountBoardStage };
@@ -0,0 +1,24 @@
1
+ import { Texture } from "pixi.js";
2
+ //#region src/board/stage/texture-pool.d.ts
3
+ type BoardImageTexturePool = {
4
+ acquire: (url: string) => Promise<Texture | null>;
5
+ release: (url: string) => Promise<void>;
6
+ };
7
+ /** A CDN variant that fails to process falls back to the media it came from. */
8
+ declare function loadBoardImageTexture(url: string): Promise<Texture>;
9
+ declare function unloadBoardImageTexture(url: string, texture: Texture | null): Promise<void>;
10
+ /**
11
+ * Share image textures by resolved URL without sharing Board ownership.
12
+ *
13
+ * Pixi's Assets cache is global, but its unload operation is not reference
14
+ * counted. The pool adds that missing application-level ownership layer:
15
+ * multiple Board managers share one load and texture, while the last release
16
+ * is the only one allowed to unload it.
17
+ */
18
+ declare function createBoardImageTexturePool(options?: {
19
+ loadTexture?: (url: string) => Promise<Texture | null>;
20
+ unloadTexture?: (url: string, texture: Texture | null) => Promise<void>;
21
+ }): BoardImageTexturePool;
22
+ declare const boardImageTexturePool: BoardImageTexturePool;
23
+ //#endregion
24
+ export { BoardImageTexturePool, boardImageTexturePool, createBoardImageTexturePool, loadBoardImageTexture, unloadBoardImageTexture };