@neta-art/cohub 9.0.1 → 11.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 (240) hide show
  1. package/README.md +108 -102
  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 +17 -0
  5. package/dist/board/editor/animation.js +187 -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 +129 -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 +121 -0
  16. package/dist/board/editor/creation.d.ts +51 -0
  17. package/dist/board/editor/creation.js +225 -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 +41 -0
  23. package/dist/board/editor/document.js +302 -0
  24. package/dist/board/editor/editor.d.ts +225 -0
  25. package/dist/board/editor/editor.js +439 -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 +203 -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 +197 -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 +28 -6
  51. package/dist/board/export/index.js +47 -13
  52. package/dist/board/export/node/index.d.ts +56 -0
  53. package/dist/board/{headless → export/node}/index.js +26 -21
  54. package/dist/board/export/node/scripts.d.ts +16 -0
  55. package/dist/board/export/node/scripts.js +258 -0
  56. package/dist/board/export/scene.d.ts +8 -5
  57. package/dist/board/export/scene.js +24 -9
  58. package/dist/board/{animation.d.ts → model/animation.d.ts} +8 -5
  59. package/dist/board/{animation.js → model/animation.js} +37 -10
  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/model/authoring.d.ts +20 -0
  63. package/dist/board/model/authoring.js +185 -0
  64. package/dist/board/{core → model}/draw-geometry.d.ts +2 -2
  65. package/dist/board/{core → model}/draw-geometry.js +1 -1
  66. package/dist/board/model/draw-input.d.ts +14 -0
  67. package/dist/board/model/draw-input.js +28 -0
  68. package/dist/board/{core → model}/export-assets.d.ts +1 -1
  69. package/dist/board/{core → model}/export-assets.js +1 -1
  70. package/dist/board/{core → model}/export-plan.d.ts +2 -2
  71. package/dist/board/{core → model}/export-plan.js +3 -2
  72. package/dist/board/{core → model}/file-preview.d.ts +1 -1
  73. package/dist/board/{core → model}/file-preview.js +2 -2
  74. package/dist/board/{core → model}/file-snapshot.d.ts +1 -1
  75. package/dist/board/{core → model}/file-snapshot.js +4 -3
  76. package/dist/board/{geometry.d.ts → model/geometry.d.ts} +3 -3
  77. package/dist/board/{geometry.js → model/geometry.js} +1 -1
  78. package/dist/board/{image-key.d.ts → model/image-key.d.ts} +2 -2
  79. package/dist/board/{image-key.js → model/image-key.js} +7 -3
  80. package/dist/board/model/index.d.ts +42 -0
  81. package/dist/board/model/index.js +39 -0
  82. package/dist/board/model/items/builtin.d.ts +5 -0
  83. package/dist/board/model/items/builtin.js +119 -0
  84. package/dist/board/model/items/create.d.ts +61 -0
  85. package/dist/board/model/items/create.js +272 -0
  86. package/dist/board/model/items/definition.d.ts +35 -0
  87. package/dist/board/model/items/definition.js +18 -0
  88. package/dist/board/model/items/id.d.ts +5 -0
  89. package/dist/board/model/items/id.js +11 -0
  90. package/dist/board/model/items/index.d.ts +6 -0
  91. package/dist/board/model/items/index.js +6 -0
  92. package/dist/board/model/items/registry.d.ts +35 -0
  93. package/dist/board/model/items/registry.js +84 -0
  94. package/dist/board/{media-playback.d.ts → model/media-playback.d.ts} +4 -3
  95. package/dist/board/{media-playback.js → model/media-playback.js} +15 -5
  96. package/dist/board/{media.d.ts → model/media.d.ts} +1 -1
  97. package/dist/board/{media.js → model/media.js} +8 -2
  98. package/dist/board/{core → model}/palette.d.ts +1 -1
  99. package/dist/board/{core → model}/palette.js +1 -1
  100. package/dist/board/{presets.d.ts → model/presets.d.ts} +3 -3
  101. package/dist/board/{presets.js → model/presets.js} +1 -1
  102. package/dist/board/{replay.d.ts → model/replay.d.ts} +5 -5
  103. package/dist/board/{replay.js → model/replay.js} +2 -2
  104. package/dist/board/model/sampling.d.ts +19 -0
  105. package/dist/board/model/sampling.js +96 -0
  106. package/dist/board/{core → model}/scene.d.ts +1 -1
  107. package/dist/board/{core → model}/scene.js +1 -1
  108. package/dist/board/model/script-example.d.ts +4 -0
  109. package/dist/board/model/script-example.js +21 -0
  110. package/dist/board/model/script-hit.d.ts +10 -0
  111. package/dist/board/model/script-hit.js +52 -0
  112. package/dist/board/model/script-index.d.ts +7 -0
  113. package/dist/board/model/script-index.js +15 -0
  114. package/dist/board/model/script-scene.d.ts +24 -0
  115. package/dist/board/model/script-scene.js +267 -0
  116. package/dist/board/model/script.d.ts +105 -0
  117. package/dist/board/model/script.js +1 -0
  118. package/dist/board/model/shape-types.d.ts +8 -0
  119. package/dist/board/model/shape-types.js +8 -0
  120. package/dist/board/{task.d.ts → model/task.d.ts} +4 -4
  121. package/dist/board/{task.js → model/task.js} +3 -3
  122. package/dist/board/{core → model}/text-metrics.d.ts +1 -1
  123. package/dist/board/{core → model}/text-metrics.js +1 -1
  124. package/dist/board/{core → model}/tool-styles.d.ts +1 -1
  125. package/dist/board/{core → model}/tool-styles.js +1 -1
  126. package/dist/board/{runtime/board-player.d.ts → player/index.d.ts} +2 -2
  127. package/dist/board/{runtime/board-player.js → player/index.js} +5 -4
  128. package/dist/board/render/board-background.d.ts +1 -1
  129. package/dist/board/render/clip.d.ts +1 -1
  130. package/dist/board/render/css-color.d.ts +6 -1
  131. package/dist/board/render/css-color.js +34 -20
  132. package/dist/board/render/diagnostics.d.ts +20 -0
  133. package/dist/board/render/diagnostics.js +117 -0
  134. package/dist/board/render/index.d.ts +9 -6
  135. package/dist/board/render/index.js +10 -7
  136. package/dist/board/render/media-interaction.d.ts +2 -2
  137. package/dist/board/render/media-interaction.js +1 -1
  138. package/dist/board/render/palette.d.ts +3 -2
  139. package/dist/board/render/palette.js +7 -3
  140. package/dist/board/render/renderers/arrow-card-renderer.js +4 -3
  141. package/dist/board/render/renderers/audio-card-renderer.js +2 -1
  142. package/dist/board/render/renderers/board-renderer-registry.d.ts +20 -10
  143. package/dist/board/render/renderers/board-renderer-registry.js +17 -17
  144. package/dist/board/render/renderers/custom-card-renderer.d.ts +5 -0
  145. package/dist/board/render/renderers/{sketch-card-renderer.js → custom-card-renderer.js} +16 -15
  146. package/dist/board/render/renderers/draw-card-renderer.js +4 -3
  147. package/dist/board/render/renderers/effect-card-renderer.js +22 -5
  148. package/dist/board/render/renderers/far-plate.js +1 -1
  149. package/dist/board/render/renderers/file-card-renderer.d.ts +1 -1
  150. package/dist/board/render/renderers/file-card-renderer.js +2 -2
  151. package/dist/board/render/renderers/frame-card-renderer.js +30 -14
  152. package/dist/board/render/renderers/image-card-renderer.d.ts +1 -1
  153. package/dist/board/render/renderers/image-card-renderer.js +43 -4
  154. package/dist/board/render/renderers/shape-card-renderer.js +11 -28
  155. package/dist/board/render/renderers/task-card-renderer.js +1 -1
  156. package/dist/board/render/renderers/text-card-renderer.js +6 -4
  157. package/dist/board/render/renderers/unknown-card-renderer.js +1 -0
  158. package/dist/board/render/renderers/video-card-renderer.d.ts +1 -1
  159. package/dist/board/render/renderers/video-card-renderer.js +2 -1
  160. package/dist/board/render/sampling.d.ts +22 -0
  161. package/dist/board/render/sampling.js +21 -0
  162. package/dist/board/render/script-export.d.ts +11 -0
  163. package/dist/board/render/script-export.js +86 -0
  164. package/dist/board/render/script-host.d.ts +31 -0
  165. package/dist/board/render/script-host.js +421 -0
  166. package/dist/board/render/script-modules.d.ts +2 -0
  167. package/dist/board/render/script-modules.js +2 -0
  168. package/dist/board/render/script-runtime.d.ts +29 -0
  169. package/dist/board/render/script-runtime.js +213 -0
  170. package/dist/board/render/script-sandbox.d.ts +4 -0
  171. package/dist/board/render/script-sandbox.js +173 -0
  172. package/dist/board/render/stroke.d.ts +3 -2
  173. package/dist/board/render/stroke.js +23 -2
  174. package/dist/board/render/text-measurement.js +2 -2
  175. package/dist/board/replica/index.d.ts +70 -0
  176. package/dist/board/replica/index.js +276 -0
  177. package/dist/board/stage/assets.d.ts +41 -0
  178. package/dist/board/stage/assets.js +355 -0
  179. package/dist/board/stage/index.d.ts +8 -0
  180. package/dist/board/stage/index.js +8 -0
  181. package/dist/board/stage/lru.d.ts +32 -0
  182. package/dist/board/stage/lru.js +29 -0
  183. package/dist/board/stage/maintenance.d.ts +5 -0
  184. package/dist/board/stage/maintenance.js +11 -0
  185. package/dist/board/stage/pointer.d.ts +8 -0
  186. package/dist/board/stage/pointer.js +33 -0
  187. package/dist/board/stage/scene.d.ts +85 -0
  188. package/dist/board/stage/scene.js +515 -0
  189. package/dist/board/stage/script-pointer.d.ts +19 -0
  190. package/dist/board/stage/script-pointer.js +35 -0
  191. package/dist/board/stage/stage.d.ts +76 -0
  192. package/dist/board/stage/stage.js +774 -0
  193. package/dist/board/stage/texture-pool.d.ts +24 -0
  194. package/dist/board/stage/texture-pool.js +153 -0
  195. package/dist/board/stage/views.d.ts +38 -0
  196. package/dist/board/stage/views.js +93 -0
  197. package/dist/chunks/environment.d.ts +289 -23
  198. package/dist/chunks/host-bridge.d.ts +2 -2
  199. package/dist/chunks/http.d.ts +185 -71
  200. package/dist/chunks/http.js +368 -269
  201. package/dist/chunks/transport.js +3 -1
  202. package/dist/chunks/websocket.d.ts +9 -1
  203. package/dist/chunks/websocket.js +16 -2
  204. package/dist/host-bridge.js +2 -1
  205. package/dist/http.d.ts +2 -2
  206. package/dist/index.d.ts +182 -4
  207. package/dist/index.js +1393 -129
  208. package/dist/media.d.ts +1 -10
  209. package/dist/media.js +16 -27
  210. package/dist/protocol/dist/board-assets.d.ts +1 -0
  211. package/dist/protocol/dist/board-constants.js +2 -2
  212. package/dist/protocol/dist/board-model.d.ts +201 -11
  213. package/dist/protocol/dist/board-model.js +33 -14
  214. package/dist/protocol/dist/board-patch.js +3 -2
  215. package/dist/protocol/dist/board-schema.d.ts +4 -2
  216. package/dist/protocol/dist/board-schema.js +18 -4
  217. package/dist/protocol/dist/board-script-modules.d.ts +25 -0
  218. package/dist/protocol/dist/board-script-modules.js +127 -0
  219. package/dist/protocol/dist/board.d.ts +28 -2
  220. package/dist/protocol/dist/board.js +3 -1
  221. package/dist/protocol/dist/core/media.d.ts +5 -0
  222. package/dist/protocol/dist/core/media.js +39 -0
  223. package/dist/protocol/dist/display/index.d.ts +1 -0
  224. package/dist/protocol/dist/index.d.ts +7 -5
  225. package/dist/protocol/dist/realtime/types.d.ts +1 -0
  226. package/dist/protocol/dist/rtc/index.d.ts +1 -0
  227. package/dist/types.d.ts +1 -1
  228. package/package.json +21 -8
  229. package/dist/board/core/shape-definition.d.ts +0 -23
  230. package/dist/board/core/shape-definition.js +0 -45
  231. package/dist/board/core/shape-types.d.ts +0 -38
  232. package/dist/board/core/shape-types.js +0 -22
  233. package/dist/board/headless/index.d.ts +0 -55
  234. package/dist/board/headless/sketch.d.ts +0 -19
  235. package/dist/board/headless/sketch.js +0 -146
  236. package/dist/board/index.d.ts +0 -30
  237. package/dist/board/index.js +0 -29
  238. package/dist/board/render/renderers/sketch-card-renderer.d.ts +0 -5
  239. package/dist/board/render/sketch-host.d.ts +0 -14
  240. package/dist/board/render/sketch-host.js +0 -216
@@ -0,0 +1,421 @@
1
+ import { boardScriptItems } from "../model/script-index.js";
2
+ import { boardScriptScope } from "../model/script-scene.js";
3
+ import { createBoardScriptModuleReader, loadBoardScriptSources } from "../../protocol/dist/board-script-modules.js";
4
+ import { boardScriptFrameSource } from "./script-sandbox.js";
5
+ import { Texture } from "pixi.js";
6
+ //#region src/board/render/script-host.ts
7
+ function createBoardScriptHost(options) {
8
+ const revisions = /* @__PURE__ */ new Map();
9
+ const entries = /* @__PURE__ */ new Map();
10
+ const restored = new Map(Object.entries(options.snapshots ?? {}));
11
+ const sources = /* @__PURE__ */ new Map();
12
+ const dependencies = /* @__PURE__ */ new Map();
13
+ const modules = createBoardScriptModuleReader(options.readModule);
14
+ const tokens = /* @__PURE__ */ new Map();
15
+ let nextToken = 0;
16
+ let brokerReady = false;
17
+ let broker = null;
18
+ const signatures = /* @__PURE__ */ new WeakMap();
19
+ function entrySignature(item) {
20
+ let value = signatures.get(item);
21
+ if (value === void 0) {
22
+ value = JSON.stringify(item.script);
23
+ signatures.set(item, value);
24
+ }
25
+ return value;
26
+ }
27
+ const focusTargets = /* @__PURE__ */ new Map();
28
+ const layer = document.createElement("div");
29
+ layer.style.cssText = "position:absolute;inset:0;pointer-events:none;overflow:hidden;z-index:2";
30
+ layer.dataset.boardScripts = "";
31
+ options.host.appendChild(layer);
32
+ let disposed = false;
33
+ function ensureBroker() {
34
+ if (broker) return;
35
+ broker = document.createElement("iframe");
36
+ broker.setAttribute("sandbox", "allow-scripts");
37
+ broker.setAttribute("aria-hidden", "true");
38
+ broker.tabIndex = -1;
39
+ broker.style.cssText = "position:absolute;visibility:hidden;width:0;height:0;border:0";
40
+ broker.srcdoc = boardScriptFrameSource();
41
+ layer.appendChild(broker);
42
+ }
43
+ function removeFrame(entry) {
44
+ if (entry.capture) {
45
+ clearTimeout(entry.capture.timer);
46
+ entry.capture.reject(/* @__PURE__ */ new Error("Script stopped before its snapshot completed."));
47
+ entry.capture = void 0;
48
+ }
49
+ tokens.delete(entry.token);
50
+ broker?.contentWindow?.postMessage({
51
+ token: entry.token,
52
+ data: { kind: "terminate" }
53
+ }, "*");
54
+ }
55
+ function release(entry) {
56
+ clearTimeout(entry.timer);
57
+ if (entry.ready && !entry.error) {
58
+ post(entry, { kind: "dispose" });
59
+ entry.timer = setTimeout(() => removeFrame(entry), 100);
60
+ } else removeFrame(entry);
61
+ entry.texture?.destroy(true);
62
+ entries.delete(entry.id);
63
+ revisions.set(entry.id, (revisions.get(entry.id) ?? 0) + 1);
64
+ options.onOutput(entry.id, null);
65
+ }
66
+ function fail(entry, error) {
67
+ clearTimeout(entry.timer);
68
+ entry.error = error;
69
+ revisions.set(entry.id, (revisions.get(entry.id) ?? 0) + 1);
70
+ entry.pending = false;
71
+ removeFrame(entry);
72
+ options.onOutput(entry.id, null);
73
+ options.onFrame();
74
+ }
75
+ function post(entry, data) {
76
+ broker?.contentWindow?.postMessage({
77
+ token: entry.token,
78
+ data
79
+ }, "*");
80
+ }
81
+ const onMessage = (event) => {
82
+ const data = event.data;
83
+ if (!data || typeof data !== "object") return;
84
+ const fromBroker = broker !== null && event.source === broker.contentWindow;
85
+ if (fromBroker && data.kind === "ready" && data.token === void 0) {
86
+ brokerReady = true;
87
+ for (const entry of tokens.values()) entry.ready = true;
88
+ options.onFrame();
89
+ return;
90
+ }
91
+ if (!fromBroker) return;
92
+ const entry = tokens.get(data.token);
93
+ if (!entry || entry.error) {
94
+ if (fromBroker || entry) data.bitmap?.close?.();
95
+ return;
96
+ }
97
+ if (data.kind === "disposed") {
98
+ clearTimeout(entry.timer);
99
+ removeFrame(entry);
100
+ return;
101
+ }
102
+ if (entries.get(entry.id) !== entry) return;
103
+ if (data.kind === "ready") {
104
+ entry.ready = true;
105
+ options.onFrame();
106
+ return;
107
+ }
108
+ if (data.kind === "dirty") {
109
+ entry.dirty = true;
110
+ options.onFrame();
111
+ return;
112
+ }
113
+ if (data.kind === "call") {
114
+ const capability = typeof data.name === "string" && options.capabilities && Object.hasOwn(options.capabilities, data.name) ? options.capabilities[data.name] : void 0;
115
+ Promise.resolve().then(() => {
116
+ if (!capability) throw new Error(`Capability unavailable: ${data.name}`);
117
+ return capability(entry.id, data.input);
118
+ }).then((value) => post(entry, {
119
+ kind: "result",
120
+ call: data.call,
121
+ value
122
+ }), (error) => post(entry, {
123
+ kind: "result",
124
+ call: data.call,
125
+ error: error instanceof Error ? error.message : String(error)
126
+ }));
127
+ return;
128
+ }
129
+ if (data.kind === "snapshot") {
130
+ const capture = entry.capture;
131
+ if (!capture) return;
132
+ clearTimeout(capture.timer);
133
+ entry.capture = void 0;
134
+ if (data.error) capture.reject(new Error(String(data.error)));
135
+ else capture.resolve(data.snapshot ? {
136
+ ...data.snapshot,
137
+ configuration: entry.signature
138
+ } : null);
139
+ return;
140
+ }
141
+ if (data.kind === "error") {
142
+ fail(entry, String(data.error));
143
+ return;
144
+ }
145
+ if (data.kind !== "frame" || data.request !== entry.request) {
146
+ data.bitmap?.close?.();
147
+ return;
148
+ }
149
+ clearTimeout(entry.timer);
150
+ entry.pending = false;
151
+ revisions.set(entry.id, (revisions.get(entry.id) ?? 0) + 1);
152
+ if (data.bitmap) {
153
+ const previous = entry.texture;
154
+ entry.texture = Texture.from(data.bitmap);
155
+ previous?.destroy(true);
156
+ }
157
+ try {
158
+ const output = data.output;
159
+ if (output) {
160
+ options.onOutput(entry.id, output);
161
+ entry.output = output;
162
+ }
163
+ } catch (error) {
164
+ fail(entry, error instanceof Error ? error.message : String(error));
165
+ return;
166
+ }
167
+ options.onFrame();
168
+ };
169
+ window.addEventListener("message", onMessage);
170
+ return {
171
+ revision: (id) => revisions.get(id) ?? 0,
172
+ ready: (id) => entries.get(id)?.output !== null && entries.get(id)?.output !== void 0,
173
+ frame: (item) => entries.get(item.id)?.texture ?? null,
174
+ error: (id) => entries.get(id)?.error ?? null,
175
+ release: () => {},
176
+ fault(id, message) {
177
+ const entry = entries.get(id);
178
+ if (entry) fail(entry, message);
179
+ },
180
+ snapshot(id) {
181
+ const entry = entries.get(id);
182
+ if (!entry?.output || entry.error) return Promise.resolve(null);
183
+ if (entry.capture) return entry.capture.promise;
184
+ let resolve = () => {};
185
+ let reject = () => {};
186
+ const promise = new Promise((yes, no) => {
187
+ resolve = yes;
188
+ reject = no;
189
+ });
190
+ const timer = setTimeout(() => {
191
+ entry.capture = void 0;
192
+ reject(/* @__PURE__ */ new Error("Script snapshot timed out."));
193
+ }, options.timeoutMs ?? 2e3);
194
+ entry.capture = {
195
+ promise,
196
+ resolve,
197
+ reject,
198
+ timer
199
+ };
200
+ post(entry, {
201
+ kind: "snapshot",
202
+ events: entry.events.splice(0)
203
+ });
204
+ return promise;
205
+ },
206
+ restore(id, snapshot) {
207
+ const entry = entries.get(id);
208
+ if (entry && snapshot.configuration && snapshot.configuration !== entry.signature) throw new Error("Snapshot configuration does not match the script.");
209
+ if (!Number.isFinite(snapshot.time) || snapshot.time < 0 || !Number.isInteger(snapshot.random)) throw new Error("Invalid script snapshot.");
210
+ restored.set(id, structuredClone(snapshot));
211
+ if (entry) release(entry);
212
+ options.onFrame();
213
+ },
214
+ output: (id) => entries.get(id)?.output ?? null,
215
+ get animated() {
216
+ return [...entries.values()].some((entry) => !entry.error && entry.output?.animated && (entry.visible || entry.output.updating) && !entry.pending);
217
+ },
218
+ sync(board, scene, camera, time, interactive, visibleIds) {
219
+ if (disposed) return;
220
+ const alive = /* @__PURE__ */ new Set();
221
+ const liveFocusTargets = /* @__PURE__ */ new Set();
222
+ for (const [id, item] of boardScriptItems(board)) {
223
+ if (!item.script?.enabled) continue;
224
+ alive.add(id);
225
+ const signature = entrySignature(item);
226
+ let entry = entries.get(id);
227
+ if (entry && entry.signature !== signature) {
228
+ release(entry);
229
+ entry = void 0;
230
+ }
231
+ const box = scene.layout.box(id);
232
+ const scoped = entry?.inputDocument === board ? entry.input.items : { [id]: item };
233
+ if (entry?.inputDocument !== board) for (const child of boardScriptScope(board, id)) {
234
+ const value = board.items[child];
235
+ if (value) scoped[child] = value;
236
+ }
237
+ const input = entry?.inputDocument === board && entry.input.width === box.width && entry.input.height === box.height ? entry.input : {
238
+ id,
239
+ item,
240
+ items: scoped,
241
+ width: box.width,
242
+ height: box.height,
243
+ capabilities: Object.keys(options.capabilities ?? {}),
244
+ snapshot: restored.get(id)
245
+ };
246
+ if (!entry) {
247
+ ensureBroker();
248
+ entry = {
249
+ id,
250
+ signature,
251
+ token: ++nextToken,
252
+ ready: brokerReady,
253
+ sources: null,
254
+ input,
255
+ inputKey: "",
256
+ inputDocument: null,
257
+ startedAt: (options.export ? 0 : time) - (input.snapshot?.time ?? 0),
258
+ texture: null,
259
+ error: null,
260
+ output: null,
261
+ events: [],
262
+ dirty: true,
263
+ request: 0,
264
+ pending: false,
265
+ time: -1,
266
+ sentSources: false,
267
+ visible: true
268
+ };
269
+ entries.set(id, entry);
270
+ restored.delete(id);
271
+ tokens.set(entry.token, entry);
272
+ const current = entry;
273
+ let loading = sources.get(item.script.src);
274
+ if (!loading) {
275
+ const paths = /* @__PURE__ */ new Set([item.script.src]);
276
+ dependencies.set(item.script.src, paths);
277
+ loading = loadBoardScriptSources(item.script.src, (path) => {
278
+ paths.add(path);
279
+ return modules.read(path);
280
+ });
281
+ sources.set(item.script.src, loading);
282
+ }
283
+ current.timer = setTimeout(() => fail(current, "Script loading timed out."), 15e3);
284
+ loading.then((value) => {
285
+ if (disposed || entries.get(id) !== current) return;
286
+ current.sources = value;
287
+ options.onFrame();
288
+ }, (error) => {
289
+ if (!disposed && entries.get(id) === current) fail(current, String(error));
290
+ });
291
+ }
292
+ if (entry.error) continue;
293
+ const visible = !visibleIds || visibleIds.has(id);
294
+ if (entry.visible !== visible) {
295
+ entry.visible = visible;
296
+ if (visible) entry.dirty = true;
297
+ }
298
+ if (entry.input !== input || entry.inputDocument !== board) {
299
+ const inputKey = JSON.stringify(input);
300
+ if (entry.inputKey !== inputKey) {
301
+ entry.inputKey = inputKey;
302
+ entry.input = input;
303
+ entry.dirty = true;
304
+ }
305
+ entry.inputDocument = board;
306
+ }
307
+ if (entry.output && !options.export) {
308
+ const hits = Object.entries(entry.output.hits).filter(([, hit]) => hit.label);
309
+ if (!hits.length && entry.output.listens.length) hits.push([id, {
310
+ x: 0,
311
+ y: 0,
312
+ width: box.width,
313
+ height: box.height,
314
+ label: "text" in item.props && typeof item.props.text === "string" ? item.props.text : item.script.src
315
+ }]);
316
+ for (const [target, hit] of hits) {
317
+ const key = `${id}/${target}`;
318
+ liveFocusTargets.add(key);
319
+ let button = focusTargets.get(key);
320
+ if (!button) {
321
+ button = document.createElement("button");
322
+ button.type = "button";
323
+ const emit = (type, key) => {
324
+ const current = entries.get(id);
325
+ if (!current || current.error) return;
326
+ current.events.push({
327
+ type,
328
+ target,
329
+ key,
330
+ point: {
331
+ x: hit.x + hit.width / 2,
332
+ y: hit.y + hit.height / 2
333
+ }
334
+ });
335
+ current.dirty = true;
336
+ options.onFrame();
337
+ };
338
+ button.onfocus = () => emit("focus");
339
+ button.onblur = () => emit("blur");
340
+ button.onclick = () => emit("click");
341
+ button.onkeydown = (event) => emit("keydown", event.key);
342
+ button.onkeyup = (event) => emit("keyup", event.key);
343
+ focusTargets.set(key, button);
344
+ layer.appendChild(button);
345
+ }
346
+ button.setAttribute("aria-label", hit.label ?? target);
347
+ button.tabIndex = interactive && visible ? 0 : -1;
348
+ const [a, b, c, d, x, y] = scene.layout.matrix(id);
349
+ button.style.cssText = `position:absolute;pointer-events:none;background:transparent;border:0;padding:0;transform-origin:0 0;width:${hit.width}px;height:${hit.height}px;transform:matrix(${a * camera.zoom},${b * camera.zoom},${c * camera.zoom},${d * camera.zoom},${(x + a * hit.x + c * hit.y) * camera.zoom + camera.x},${(y + b * hit.x + d * hit.y) * camera.zoom + camera.y})`;
350
+ }
351
+ }
352
+ if (!entry.ready || !entry.sources || entry.pending || !entry.dirty && (!entry.output?.animated || !visible && !entry.output.updating || entry.time === time)) continue;
353
+ entry.pending = true;
354
+ entry.dirty = false;
355
+ entry.time = time;
356
+ entry.request++;
357
+ const current = entry;
358
+ clearTimeout(entry.timer);
359
+ entry.timer = setTimeout(() => fail(current, "Script execution timed out."), options.timeoutMs ?? 2e3);
360
+ post(entry, {
361
+ request: entry.request,
362
+ input: entry.sentInput === entry.input ? void 0 : entry.input,
363
+ sources: entry.sentSources ? void 0 : entry.sources,
364
+ draw: visible,
365
+ t: time - entry.startedAt,
366
+ scale: Math.min(2, 2048 / Math.max(1, box.width, box.height)),
367
+ events: entry.events.splice(0)
368
+ });
369
+ entry.sentInput = entry.input;
370
+ entry.sentSources = true;
371
+ }
372
+ for (const [key, button] of focusTargets) if (!liveFocusTargets.has(key)) {
373
+ button.remove();
374
+ focusTargets.delete(key);
375
+ }
376
+ for (const entry of entries.values()) if (!alive.has(entry.id)) release(entry);
377
+ },
378
+ event(id, event) {
379
+ const entry = entries.get(id);
380
+ if (!entry || entry.error || entry.output && !entry.output.listens.includes(event.type)) return;
381
+ if (event.type === "pointermove" && entry.events.at(-1)?.type === "pointermove") entry.events.pop();
382
+ if (entry.events.length >= 128) return;
383
+ entry.events.push(event);
384
+ entry.dirty = true;
385
+ options.onFrame();
386
+ },
387
+ invalidate(path) {
388
+ modules.invalidate(path);
389
+ const affected = new Set([...sources.keys()].filter((src) => !path || src === path || dependencies.get(src)?.has(path)));
390
+ for (const src of affected) {
391
+ sources.delete(src);
392
+ dependencies.delete(src);
393
+ }
394
+ let changed = false;
395
+ for (const entry of entries.values()) {
396
+ if (path && !affected.has(entry.input.item.script?.src ?? "")) continue;
397
+ restored.delete(entry.id);
398
+ release(entry);
399
+ changed = true;
400
+ }
401
+ if (!path) restored.clear();
402
+ if (changed) options.onFrame();
403
+ },
404
+ destroy() {
405
+ disposed = true;
406
+ window.removeEventListener("message", onMessage);
407
+ for (const entry of entries.values()) release(entry);
408
+ layer.remove();
409
+ sources.clear();
410
+ dependencies.clear();
411
+ modules.destroy();
412
+ focusTargets.clear();
413
+ restored.clear();
414
+ tokens.clear();
415
+ revisions.clear();
416
+ broker = null;
417
+ }
418
+ };
419
+ }
420
+ //#endregion
421
+ export { createBoardScriptHost };
@@ -0,0 +1,2 @@
1
+ import { BOARD_SCRIPT_MAX_BYTES, BoardScriptSource, BoardScriptSources, createBoardScriptModuleReader, loadBoardScriptSources, resolveScriptImport, scriptModuleUrls } from "../../protocol/dist/board-script-modules.js";
2
+ export { BOARD_SCRIPT_MAX_BYTES, BoardScriptSource, BoardScriptSources, createBoardScriptModuleReader, loadBoardScriptSources, resolveScriptImport, scriptModuleUrls };
@@ -0,0 +1,2 @@
1
+ import { BOARD_SCRIPT_MAX_BYTES, createBoardScriptModuleReader, loadBoardScriptSources, resolveScriptImport, scriptModuleUrls } from "../../protocol/dist/board-script-modules.js";
2
+ export { BOARD_SCRIPT_MAX_BYTES, createBoardScriptModuleReader, loadBoardScriptSources, resolveScriptImport, scriptModuleUrls };
@@ -0,0 +1,29 @@
1
+ import { BoardItem } from "../../protocol/dist/board-model.js";
2
+ import "../../protocol/dist/index.js";
3
+ import { BoardScriptEvent, BoardScriptModule, BoardScriptOutput, BoardScriptSnapshot } from "../model/script.js";
4
+ //#region src/board/render/script-runtime.d.ts
5
+ type ScriptRuntimeInput = {
6
+ id: string;
7
+ item: BoardItem;
8
+ items: Record<string, BoardItem>;
9
+ width: number;
10
+ height: number;
11
+ capabilities?: string[];
12
+ snapshot?: BoardScriptSnapshot;
13
+ };
14
+ /** Self-contained so browser workers and Node execute exactly the same kernel. */
15
+ declare function createScriptRuntime(module: BoardScriptModule, initial: ScriptRuntimeInput, host: {
16
+ call(name: string, input: unknown): Promise<unknown>;
17
+ invalidate(): void;
18
+ }): {
19
+ readonly animated: boolean;
20
+ update(next: ScriptRuntimeInput): void;
21
+ step(t: number): void;
22
+ event(event: BoardScriptEvent): void;
23
+ draw(canvas: CanvasRenderingContext2D): void;
24
+ output(): BoardScriptOutput;
25
+ snapshot(includeEffects?: boolean, clone?: boolean): BoardScriptSnapshot;
26
+ destroy(): void;
27
+ };
28
+ //#endregion
29
+ export { ScriptRuntimeInput, createScriptRuntime };
@@ -0,0 +1,213 @@
1
+ //#region src/board/render/script-runtime.ts
2
+ /** Self-contained so browser workers and Node execute exactly the same kernel. */
3
+ function createScriptRuntime(module, initial, host) {
4
+ if ("mount" in module) throw new Error("Board scripts do not support DOM mount(). Use draw() or native scene items.");
5
+ let input = initial;
6
+ let disposed = false;
7
+ let revision = 0;
8
+ let randomState = 2166136261;
9
+ for (const char of initial.item.script?.seed ?? initial.id) randomState = Math.imul(randomState ^ char.charCodeAt(0), 16777619) >>> 0;
10
+ const parameterDefaults = Object.fromEntries(Object.entries(module.parameters ?? {}).filter(([, spec]) => spec.default !== void 0).map(([key, spec]) => [key, spec.default]));
11
+ let params = {
12
+ ...parameterDefaults,
13
+ ...initial.item.script?.params
14
+ };
15
+ const state = structuredClone(initial.snapshot?.state ?? {});
16
+ if (initial.snapshot) randomState = initial.snapshot.random;
17
+ let time = initial.snapshot?.time ?? 0;
18
+ let started = Boolean(initial.snapshot);
19
+ const stepMs = 1e3 / 60;
20
+ let frame = {
21
+ t: time,
22
+ dt: 0,
23
+ width: initial.width,
24
+ height: initial.height,
25
+ seed: initial.item.script?.seed ?? initial.id,
26
+ params,
27
+ state
28
+ };
29
+ const handlers = /* @__PURE__ */ new Map();
30
+ const frames = /* @__PURE__ */ new Set();
31
+ const cleanups = [];
32
+ const effects = structuredClone(initial.snapshot?.effects);
33
+ let patches = effects?.patches ?? {};
34
+ let items = effects?.items ?? {};
35
+ let space = effects?.space ?? "world";
36
+ let hits = effects?.hits ?? {};
37
+ const invalidate = () => {
38
+ if (disposed) return;
39
+ revision++;
40
+ host.invalidate();
41
+ };
42
+ const present = (id, patch) => {
43
+ if (patch === null) delete patches[id];
44
+ else patches[id] = structuredClone(patch);
45
+ invalidate();
46
+ };
47
+ const context = {
48
+ self: {
49
+ id: initial.id,
50
+ get base() {
51
+ return input.item;
52
+ },
53
+ present: (patch) => present(initial.id, patch)
54
+ },
55
+ get params() {
56
+ return params;
57
+ },
58
+ get targets() {
59
+ return input.item.script?.targets ?? {};
60
+ },
61
+ async emit(target, type, data) {
62
+ await host.call("board.emit", {
63
+ target,
64
+ type,
65
+ data
66
+ });
67
+ },
68
+ state,
69
+ get frame() {
70
+ return frame;
71
+ },
72
+ random() {
73
+ randomState = Math.imul(randomState, 1664525) + 1013904223 >>> 0;
74
+ return randomState / 4294967296;
75
+ },
76
+ on(type, handler) {
77
+ let listeners = handlers.get(type);
78
+ if (!listeners) {
79
+ listeners = /* @__PURE__ */ new Set();
80
+ handlers.set(type, listeners);
81
+ }
82
+ listeners.add(handler);
83
+ invalidate();
84
+ return () => {
85
+ listeners.delete(handler);
86
+ invalidate();
87
+ };
88
+ },
89
+ onFrame(handler) {
90
+ frames.add(handler);
91
+ invalidate();
92
+ return () => {
93
+ frames.delete(handler);
94
+ invalidate();
95
+ };
96
+ },
97
+ onDispose: (handler) => cleanups.push(handler),
98
+ invalidate,
99
+ get: (id) => input.items[id],
100
+ present,
101
+ scene(next, options) {
102
+ items = structuredClone(next);
103
+ space = options?.space ?? "world";
104
+ invalidate();
105
+ },
106
+ hitRegions(next) {
107
+ hits = structuredClone(next);
108
+ invalidate();
109
+ },
110
+ async commit(patch) {
111
+ await host.call("board.commit", patch);
112
+ },
113
+ capabilities: {
114
+ has: (name) => (input.capabilities ?? []).includes(name),
115
+ call: (name, value) => host.call(name, value)
116
+ }
117
+ };
118
+ const setup = module.default?.(context);
119
+ if (typeof setup === "function") cleanups.push(setup);
120
+ return {
121
+ get animated() {
122
+ return frames.size > 0 || module.animated === true;
123
+ },
124
+ update(next) {
125
+ input = next;
126
+ params = {
127
+ ...parameterDefaults,
128
+ ...next.item.script?.params
129
+ };
130
+ },
131
+ step(t) {
132
+ if (disposed) return;
133
+ if (!Number.isFinite(t) || t < time - .001) throw new Error("Reset the instance before seeking backwards.");
134
+ const update = (at, dt) => {
135
+ frame = {
136
+ ...frame,
137
+ t: at,
138
+ dt,
139
+ width: input.width,
140
+ height: input.height,
141
+ params: context.params
142
+ };
143
+ for (const handler of frames) handler(frame);
144
+ };
145
+ if (!started) {
146
+ started = true;
147
+ update(time, 0);
148
+ }
149
+ if (frames.size) while (time + stepMs <= t + 1e-6) {
150
+ time += stepMs;
151
+ update(time, stepMs);
152
+ }
153
+ else time = t;
154
+ frame = {
155
+ ...frame,
156
+ t,
157
+ dt: 0,
158
+ width: input.width,
159
+ height: input.height,
160
+ params: context.params
161
+ };
162
+ },
163
+ event(event) {
164
+ if (disposed) return;
165
+ const listeners = handlers.get(event.type);
166
+ if (!listeners?.size) return;
167
+ for (const handler of listeners) handler(event);
168
+ invalidate();
169
+ },
170
+ draw(canvas) {
171
+ const result = module.draw?.(canvas, frame);
172
+ if (result && typeof result.then === "function") throw new Error("draw() must be synchronous.");
173
+ },
174
+ output() {
175
+ return {
176
+ parameters: module.parameters,
177
+ space,
178
+ patches,
179
+ items,
180
+ hits,
181
+ listens: [...handlers].filter(([, callbacks]) => callbacks.size).map(([type]) => type),
182
+ animated: this.animated,
183
+ updating: frames.size > 0,
184
+ version: revision
185
+ };
186
+ },
187
+ snapshot(includeEffects = true, clone = true) {
188
+ const snapshot = {
189
+ state,
190
+ time,
191
+ random: randomState,
192
+ effects: includeEffects ? {
193
+ patches,
194
+ items,
195
+ hits,
196
+ space
197
+ } : void 0
198
+ };
199
+ return clone ? structuredClone(snapshot) : snapshot;
200
+ },
201
+ destroy() {
202
+ disposed = true;
203
+ for (const cleanup of cleanups.reverse()) cleanup();
204
+ handlers.clear();
205
+ frames.clear();
206
+ patches = {};
207
+ items = {};
208
+ hits = {};
209
+ }
210
+ };
211
+ }
212
+ //#endregion
213
+ export { createScriptRuntime };
@@ -0,0 +1,4 @@
1
+ //#region src/board/render/script-sandbox.d.ts
2
+ declare function boardScriptFrameSource(): string;
3
+ //#endregion
4
+ export { boardScriptFrameSource };