@neta-art/cohub 11.5.0 → 12.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 (88) hide show
  1. package/dist/board/editor/animation.js +2 -1
  2. package/dist/board/editor/clipboard.js +1 -1
  3. package/dist/board/editor/creation.js +2 -2
  4. package/dist/board/editor/document-edits.d.ts +1 -1
  5. package/dist/board/editor/document-edits.js +1 -19
  6. package/dist/board/editor/document.js +2 -1
  7. package/dist/board/editor/editor.js +2 -2
  8. package/dist/board/editor/index.d.ts +2 -1
  9. package/dist/board/editor/index.js +2 -1
  10. package/dist/board/editor/selection.js +1 -1
  11. package/dist/board/editor/snapshots.js +11 -13
  12. package/dist/board/export/index.js +2 -2
  13. package/dist/board/export/node/scripts.js +1 -1
  14. package/dist/board/export/scene.js +2 -2
  15. package/dist/board/model/client-geometry.d.ts +22 -0
  16. package/dist/board/model/client-geometry.js +94 -0
  17. package/dist/board/model/document-share.d.ts +5 -0
  18. package/dist/board/model/document-share.js +27 -0
  19. package/dist/board/model/index.d.ts +5 -4
  20. package/dist/board/model/index.js +19 -18
  21. package/dist/board/model/items/create.d.ts +5 -1
  22. package/dist/board/model/items/create.js +30 -59
  23. package/dist/board/model/items/index.d.ts +2 -2
  24. package/dist/board/model/items/index.js +2 -2
  25. package/dist/board/model/items/registry.js +2 -5
  26. package/dist/board/model/json.d.ts +4 -0
  27. package/dist/board/model/json.js +22 -0
  28. package/dist/board/model/media-playback.d.ts +6 -3
  29. package/dist/board/model/sampling.js +1 -1
  30. package/dist/board/player/index.d.ts +6 -2
  31. package/dist/board/player/index.js +33 -15
  32. package/dist/board/render/index.d.ts +2 -2
  33. package/dist/board/render/index.js +6 -6
  34. package/dist/board/render/media-interaction.js +1 -1
  35. package/dist/board/render/renderers/arrow-card-renderer.js +5 -1
  36. package/dist/board/render/renderers/audio-card-renderer.js +5 -2
  37. package/dist/board/render/renderers/base-card-renderer.js +2 -5
  38. package/dist/board/render/renderers/board-renderer-registry.d.ts +4 -2
  39. package/dist/board/render/renderers/custom-card-renderer.js +7 -3
  40. package/dist/board/render/renderers/draw-card-renderer.js +1 -1
  41. package/dist/board/render/renderers/file-card-renderer.js +6 -3
  42. package/dist/board/render/renderers/frame-card-renderer.js +5 -1
  43. package/dist/board/render/renderers/image-card-renderer.js +13 -49
  44. package/dist/board/render/renderers/model-card-renderer.js +7 -4
  45. package/dist/board/render/renderers/shape-card-renderer.js +5 -1
  46. package/dist/board/render/renderers/task-card-renderer.js +6 -3
  47. package/dist/board/render/renderers/text-card-renderer.js +6 -2
  48. package/dist/board/render/renderers/unknown-card-renderer.js +4 -0
  49. package/dist/board/render/renderers/video-card-renderer.js +20 -19
  50. package/dist/board/render/script-export.js +1 -1
  51. package/dist/board/render/script-host.d.ts +2 -0
  52. package/dist/board/render/script-host.js +45 -10
  53. package/dist/board/render/script-resolution.d.ts +4 -0
  54. package/dist/board/render/script-resolution.js +18 -0
  55. package/dist/board/render/surface.d.ts +9 -2
  56. package/dist/board/render/surface.js +15 -1
  57. package/dist/board/render/text-resolution.d.ts +4 -1
  58. package/dist/board/render/text-resolution.js +10 -5
  59. package/dist/board/replica/index.js +4 -1
  60. package/dist/board/stage/assets.d.ts +12 -8
  61. package/dist/board/stage/assets.js +174 -89
  62. package/dist/board/stage/display-density.d.ts +4 -0
  63. package/dist/board/stage/display-density.js +25 -0
  64. package/dist/board/stage/index.d.ts +3 -3
  65. package/dist/board/stage/index.js +3 -3
  66. package/dist/board/stage/lru.d.ts +3 -2
  67. package/dist/board/stage/lru.js +8 -4
  68. package/dist/board/stage/pointer.d.ts +1 -1
  69. package/dist/board/stage/pointer.js +4 -5
  70. package/dist/board/stage/scene.d.ts +3 -0
  71. package/dist/board/stage/scene.js +44 -8
  72. package/dist/board/stage/stage.d.ts +1 -0
  73. package/dist/board/stage/stage.js +152 -69
  74. package/dist/board/stage/views.d.ts +7 -2
  75. package/dist/board/stage/views.js +50 -21
  76. package/dist/chunks/environment.d.ts +16 -2
  77. package/dist/chunks/http.d.ts +6 -2
  78. package/dist/index.js +65 -22
  79. package/dist/protocol/dist/board-constants.js +56 -1
  80. package/dist/protocol/dist/board-layout.d.ts +8 -1
  81. package/dist/protocol/dist/board-layout.js +16 -4
  82. package/dist/protocol/dist/board-model.js +13 -19
  83. package/dist/protocol/dist/board-patch.js +1 -1
  84. package/dist/protocol/dist/board-schema.d.ts +52 -0
  85. package/dist/protocol/dist/board-schema.js +8 -0
  86. package/dist/protocol/dist/core/media.js +1 -0
  87. package/dist/protocol/dist/index.d.ts +2 -2
  88. package/package.json +1 -1
@@ -1,5 +1,6 @@
1
1
  import { parseBoardItem } from "../../protocol/dist/board-model.js";
2
2
  import { drawPointsBounds } from "../../protocol/dist/board-layout.js";
3
+ import { getBoardResolution, textZoomBucket } from "../render/text-resolution.js";
3
4
  import { clippingAncestor, isClippingFrame, syncClipGroup } from "../render/clip.js";
4
5
  import { Graphics } from "pixi.js";
5
6
  //#region src/board/stage/scene.ts
@@ -12,6 +13,7 @@ const Z_LIFTED = 1e9;
12
13
  const Z_LIVE_STROKE = Number.MAX_SAFE_INTEGER - 1;
13
14
  const Z_OVERLAY = Number.MAX_SAFE_INTEGER;
14
15
  const POOL_LIMIT_PER_RENDERER = 48;
16
+ const EMPTY_IDS = /* @__PURE__ */ new Set();
15
17
  function idSetSignature(ids) {
16
18
  let hash = ids.size;
17
19
  for (const id of ids) {
@@ -31,20 +33,33 @@ function createBoardScene(options) {
31
33
  const tickingIds = /* @__PURE__ */ new Set();
32
34
  const pools = /* @__PURE__ */ new Map();
33
35
  const heldKeys = /* @__PURE__ */ new Map();
36
+ const holders = /* @__PURE__ */ new Map();
34
37
  let orderById = /* @__PURE__ */ new Map();
35
38
  let farSig = null;
36
39
  let farActive = false;
37
40
  let lastStructureVersion = -1;
38
41
  let lastZoom = NaN;
42
+ let lastResolution = "";
43
+ let liveVersion = 0;
39
44
  let lifted = /* @__PURE__ */ new Set();
40
45
  const liveStrokes = /* @__PURE__ */ new Map();
41
- function setHeldKey(context, cardId, desiredKey) {
46
+ function setHeldKey(context, cardId, item) {
47
+ const desiredKey = item ? context.assetKey(item) : null;
42
48
  const heldKey = heldKeys.get(cardId) ?? null;
43
49
  if (desiredKey === heldKey) return;
44
- if (heldKey) context.releaseTexture(heldKey);
45
- if (desiredKey) context.acquireTexture(desiredKey);
46
- if (desiredKey) heldKeys.set(cardId, desiredKey);
47
- else heldKeys.delete(cardId);
50
+ if (heldKey) {
51
+ context.releaseTexture(heldKey);
52
+ const ids = holders.get(heldKey);
53
+ ids?.delete(cardId);
54
+ if (ids?.size === 0) holders.delete(heldKey);
55
+ }
56
+ if (desiredKey && item) {
57
+ heldKeys.set(cardId, desiredKey);
58
+ const ids = holders.get(desiredKey);
59
+ if (ids) ids.add(cardId);
60
+ else holders.set(desiredKey, /* @__PURE__ */ new Set([cardId]));
61
+ context.acquireTexture(desiredKey, item);
62
+ } else heldKeys.delete(cardId);
48
63
  }
49
64
  function takeFromPool(rendererId) {
50
65
  const pool = pools.get(rendererId);
@@ -119,7 +134,7 @@ function createBoardScene(options) {
119
134
  scriptRevision: context.scripts?.revision?.(item.id) ?? 0
120
135
  };
121
136
  cards.set(item.id, entry);
122
- setHeldKey(context, item.id, context.assetKey(item));
137
+ setHeldKey(context, item.id, item);
123
138
  renderer.zoom?.(container, context.zoom);
124
139
  return entry;
125
140
  }
@@ -277,6 +292,9 @@ function createBoardScene(options) {
277
292
  }
278
293
  function sync(input) {
279
294
  const { items, scene, context, getItem, visibleIds, pinnedIds, globalSig, structureVersion, geometryVersion, gestureActive } = input;
295
+ const resolution = `${textZoomBucket(context.zoom)}:${getBoardResolution()}`;
296
+ const resolutionChanged = resolution !== lastResolution;
297
+ lastResolution = resolution;
280
298
  const structureChanged = structureVersion !== lastStructureVersion;
281
299
  if (structureChanged) {
282
300
  lastStructureVersion = structureVersion;
@@ -304,7 +322,14 @@ function createBoardScene(options) {
304
322
  if (farActive) {
305
323
  const { orderedIds, unbatched } = visibleFacts(input);
306
324
  for (const id of unbatched) wanted.add(id);
307
- let budget = FAR_LIVE_LIMIT;
325
+ let retained = 0;
326
+ if (!farPainted) {
327
+ for (const id of cards.keys()) if ((!visibleIds || visibleIds.has(id)) && !farCovered.has(id) && !wanted.has(id)) {
328
+ wanted.add(id);
329
+ retained++;
330
+ }
331
+ }
332
+ let budget = Math.max(0, FAR_LIVE_LIMIT - retained);
308
333
  for (const id of orderedIds) {
309
334
  if (budget === 0) break;
310
335
  if (farCovered.has(id) || wanted.has(id)) continue;
@@ -346,13 +371,17 @@ function createBoardScene(options) {
346
371
  entry.hovered = hovered;
347
372
  entry.resizing = resizing;
348
373
  entry.globalSig = globalSig;
349
- setHeldKey(context, id, context.assetKey(item));
374
+ setHeldKey(context, id, item);
350
375
  if (changed) {
351
376
  entry.renderer.update(entry.container, item, context);
352
377
  entry.container.alpha = scene.opacity(id);
353
378
  if (entry.renderer.tick) tickingIds.add(id);
379
+ } else if (resolutionChanged && entry.renderer.resolution) {
380
+ entry.renderer.resolution(entry.container, item, context);
381
+ if (entry.renderer.tick) tickingIds.add(id);
354
382
  }
355
383
  }
384
+ if (liveSetChanged) liveVersion += 1;
356
385
  if (structureChanged || liveSetChanged) syncClips(scene);
357
386
  if (context.zoom !== lastZoom) {
358
387
  lastZoom = context.zoom;
@@ -391,6 +420,7 @@ function createBoardScene(options) {
391
420
  pools.clear();
392
421
  for (const key of heldKeys.values()) context.releaseTexture(key);
393
422
  heldKeys.clear();
423
+ holders.clear();
394
424
  orderById = /* @__PURE__ */ new Map();
395
425
  for (const entry of clips.values()) entry.group.destroy({ children: true });
396
426
  clips.clear();
@@ -402,6 +432,7 @@ function createBoardScene(options) {
402
432
  visibleMemo = null;
403
433
  lastStructureVersion = -1;
404
434
  lastZoom = NaN;
435
+ lastResolution = "";
405
436
  }
406
437
  return {
407
438
  sync,
@@ -412,6 +443,11 @@ function createBoardScene(options) {
412
443
  container: entry.container
413
444
  } : null;
414
445
  },
446
+ holders: (key) => holders.get(key) ?? EMPTY_IDS,
447
+ get liveVersion() {
448
+ return liveVersion;
449
+ },
450
+ liveIds: () => new Set(cards.keys()),
415
451
  get animated() {
416
452
  return animatedIds.size > 0;
417
453
  },
@@ -56,6 +56,7 @@ type BoardStageOptions = {
56
56
  height: number;
57
57
  }) => void;
58
58
  onVisibleChange?: (ids: ReadonlySet<string> | null) => void;
59
+ onLiveChange?: (ids: ReadonlySet<string>) => void;
59
60
  safeRect?: () => Rect;
60
61
  scriptCapabilities?: Record<string, (id: string, input: unknown) => Promise<unknown>>;
61
62
  };
@@ -1,22 +1,25 @@
1
1
  import { applyMatrix, invertMatrix } from "../../protocol/dist/board-layout.js";
2
2
  import { featuredTaskArtifact } from "../../protocol/dist/board-task.js";
3
- import { screenPoint, screenToWorld, stableCullRect, visibleWorldRect, worldPoint } from "../model/geometry.js";
3
+ import { pointToWorld, stableCullRect, visibleWorldRect, worldPoint } from "../model/geometry.js";
4
+ import { boardScriptScope, composeBoardScriptItems, prepareBoardScriptOutput, validateBoardScriptCommit } from "../model/script-scene.js";
5
+ import { boardScriptTarget } from "../model/script-hit.js";
4
6
  import { createBoardAnimationSampler, resolveBoardAnimationCamera } from "../model/sampling.js";
7
+ import { resolveSceneArrow } from "../model/arrow-geometry.js";
5
8
  import { buildFallbackShapeColors, pickBoardColor, resolveItemColor } from "../model/palette.js";
9
+ import { elementClientToLocal } from "../model/client-geometry.js";
10
+ import { isCreationBoardTool } from "../editor/tool.js";
11
+ import { resizeCursorForHandle } from "../editor/selection-transform.js";
12
+ import { createBoardPlayer } from "../player/index.js";
13
+ import { createBoardScriptObservers } from "../model/script-observers.js";
6
14
  import { parseBoardCssColor } from "../render/css-color.js";
7
- import { defaultBoardPalette, itemAlpha } from "../render/palette.js";
8
- import { resolveSceneArrow } from "../model/arrow-geometry.js";
9
15
  import { createBoardBackground, updateBoardBackground } from "../render/board-background.js";
10
- import { getBoardResolution, textZoomBucket } from "../render/text-resolution.js";
16
+ import { getBoardResolution } from "../render/text-resolution.js";
17
+ import { defaultBoardPalette, itemAlpha } from "../render/palette.js";
11
18
  import { DEFAULT_BOARD_RENDER_LABELS, getBoardCardRenderer } from "../render/renderers/board-renderer-registry.js";
12
- import { boardScriptScope, composeBoardScriptItems, prepareBoardScriptOutput, validateBoardScriptCommit } from "../model/script-scene.js";
13
- import { boardScriptTarget } from "../model/script-hit.js";
14
19
  import { createBoardScriptHost } from "../render/script-host.js";
15
20
  import { defaultBoardRenderStyle } from "../render/style.js";
16
- import { isCreationBoardTool } from "../editor/tool.js";
17
- import { resizeCursorForHandle } from "../editor/selection-transform.js";
18
- import { createBoardScriptObservers } from "../model/script-observers.js";
19
- import { createBoardPlayer } from "../player/index.js";
21
+ import { boardTextureEdge } from "./assets.js";
22
+ import { observeDisplayDensity } from "./display-density.js";
20
23
  import { toBoardPointerEvent } from "./pointer.js";
21
24
  import { createBoardPointerGestures } from "./pointer-gestures.js";
22
25
  import { createBoardScene } from "./scene.js";
@@ -48,6 +51,7 @@ function defaultTheme() {
48
51
  const NO_IDS = /* @__PURE__ */ new Set();
49
52
  const MARCH_STEP_MS = 160;
50
53
  const MARCH_SMOOTH_MS = 40;
54
+ const TEXTURE_SETTLE_MS = 150;
51
55
  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`;
52
56
  const defaultStyles = /* @__PURE__ */ new Map();
53
57
  function styleOf(theme) {
@@ -96,17 +100,18 @@ function mountBoardStage(host, options) {
96
100
  let scriptHover = null;
97
101
  const scriptPointers = createBoardScriptPointerRouter();
98
102
  let stopMaintenance = null;
103
+ let stopDensity = null;
99
104
  let active = true;
100
105
  let disposed = false;
101
106
  let renderFrame = 0;
102
107
  let tickFrame = 0;
108
+ let scriptTickFrame = 0;
103
109
  let cardTickFrame = 0;
104
110
  let cardTickTimer = null;
105
111
  let cardTickDue = 0;
106
112
  const staleIds = /* @__PURE__ */ new Set();
107
113
  let resizeFrame = 0;
108
114
  let cardEpoch = 0;
109
- let assetVersion = 0;
110
115
  let surface = {
111
116
  width: 0,
112
117
  height: 0
@@ -114,7 +119,7 @@ function mountBoardStage(host, options) {
114
119
  let playbackCamera = null;
115
120
  let cursor = "";
116
121
  const renderCamera = () => playbackCamera ?? editor.camera;
117
- const views = createBoardItemViewLayer(host, editor, options.views ?? [], renderCamera);
122
+ const views = createBoardItemViewLayer(host, editor, options.views ?? [], renderCamera, scheduleTick);
118
123
  const player = createBoardPlayer({ resolveColor: (color) => {
119
124
  const current = theme();
120
125
  return {
@@ -138,29 +143,70 @@ function mountBoardStage(host, options) {
138
143
  scene: editor.scene
139
144
  };
140
145
  options.onVisibleChange?.(cull.ids);
141
- requestAssets = true;
142
146
  return cull.ids;
143
147
  }
144
- let requestAssets = true;
145
- function syncAssets(ids) {
146
- if (!requestAssets || !ids) return;
147
- requestAssets = false;
148
+ const textureScale = () => renderCamera().zoom * (app?.renderer.resolution ?? 1);
149
+ let refined = {
150
+ scale: 0,
151
+ geometry: 0
152
+ };
153
+ let pendingRefine = "";
154
+ let refineTimer = null;
155
+ function stopRefine() {
156
+ if (refineTimer !== null) clearTimeout(refineTimer);
157
+ refineTimer = null;
158
+ refined = {
159
+ scale: 0,
160
+ geometry: 0
161
+ };
162
+ pendingRefine = "";
163
+ }
164
+ function scheduleRefine() {
165
+ const scale = textureScale();
166
+ const geometry = editor.geometryVersion;
167
+ const key = `${scale}:${geometry}`;
168
+ if (key === pendingRefine) return;
169
+ pendingRefine = key;
170
+ if (refineTimer !== null) clearTimeout(refineTimer);
171
+ refineTimer = null;
172
+ if (refined.scale === 0 || scale <= refined.scale && geometry === refined.geometry) {
173
+ refined = {
174
+ scale,
175
+ geometry
176
+ };
177
+ return;
178
+ }
179
+ refineTimer = setTimeout(() => {
180
+ refineTimer = null;
181
+ refined = {
182
+ scale,
183
+ geometry
184
+ };
185
+ for (const id of scene?.liveIds() ?? []) {
186
+ const item = editor.scene.get(id);
187
+ if (item) assets.requestItem(item, boardTextureEdge(item, scale));
188
+ }
189
+ }, TEXTURE_SETTLE_MS);
190
+ }
191
+ const naturalIds = /* @__PURE__ */ new Set();
192
+ let liveVersion = -1;
193
+ function adoptNaturalSizes() {
194
+ if (naturalIds.size === 0) return;
148
195
  const sizes = [];
149
- for (const id of ids) {
196
+ for (const id of naturalIds) {
150
197
  const item = editor.itemById(id);
151
- if (!item) continue;
152
- const key = assets.assetKey(item);
153
- if (!key) continue;
154
- assets.requestItem(item);
198
+ const key = item && assets.assetKey(item);
199
+ if (!item || !key) continue;
155
200
  const artifact = item.type === "task" ? featuredTaskArtifact(item.props.snapshot.artifacts) : null;
156
201
  const recorded = artifact?.type === "image" || artifact?.type === "video" ? artifact : item.type === "image" || item.type === "video" ? item.props.snapshot : null;
157
202
  if (!recorded || recorded.naturalWidth && recorded.naturalHeight) continue;
158
203
  const natural = assets.getNaturalSize(key);
159
204
  if (natural?.width && natural.height) sizes.push({
160
- id: item.id,
205
+ id,
161
206
  ...natural
162
207
  });
163
208
  }
209
+ naturalIds.clear();
164
210
  if (sizes.length > 0) editor.adoptMediaNaturalSizes(sizes);
165
211
  }
166
212
  let lifted = null;
@@ -201,7 +247,11 @@ function mountBoardStage(host, options) {
201
247
  getTexture: (key) => assets.getTexture(key),
202
248
  hasError: (key) => assets.hasError(key),
203
249
  fileState: (path) => options.fileState?.(path) ?? "ok",
204
- acquireTexture: (key) => assets.acquire(key),
250
+ acquireTexture: (key, item) => {
251
+ assets.acquire(key);
252
+ assets.requestItem(item, boardTextureEdge(item, textureScale()));
253
+ if (assets.getTexture(key)) naturalIds.add(item.id);
254
+ },
205
255
  releaseTexture: (key) => assets.release(key)
206
256
  };
207
257
  }
@@ -440,9 +490,37 @@ function mountBoardStage(host, options) {
440
490
  }, owner, retained);
441
491
  if (!retained || scriptHover && scriptHover.owner === owner && !Object.hasOwn(retained, scriptHover.target)) scriptHover = null;
442
492
  }
493
+ function syncScripts() {
494
+ if (!scripts) return;
495
+ const now = performance.now();
496
+ if (scriptMode !== "paused") scriptTime += Math.min(250, Math.max(0, now - scriptLastTick));
497
+ scriptLastTick = now;
498
+ const camera = renderCamera();
499
+ if (scriptMode !== "paused" && scriptObservers.size) scriptObservers.sync(editor.document, editor.selection, () => ({
500
+ camera: { ...camera },
501
+ ...surface,
502
+ safeRect: options.safeRect?.() ?? {
503
+ x: 0,
504
+ y: 0,
505
+ ...surface
506
+ }
507
+ }));
508
+ scripts.sync(editor.document, editor.scene, camera, scriptTime, scriptMode === "interact", visibleIds());
509
+ }
510
+ function scheduleScriptTick() {
511
+ if (scriptTickFrame || !active || disposed) return;
512
+ scriptTickFrame = requestAnimationFrame(() => {
513
+ scriptTickFrame = 0;
514
+ if (tickFrame) return;
515
+ syncScripts();
516
+ if (scriptMode !== "paused" && scripts?.animated) scheduleScriptTick();
517
+ });
518
+ }
443
519
  function createScripts() {
444
520
  return createBoardScriptHost({
445
521
  host,
522
+ isMaterialized: (id) => Boolean(scene?.getNode(id)),
523
+ onSchedule: scheduleScriptTick,
446
524
  capabilities: {
447
525
  ...options.scriptCapabilities,
448
526
  "board.emit": async (id, value) => {
@@ -550,25 +628,9 @@ function mountBoardStage(host, options) {
550
628
  }
551
629
  }
552
630
  const renderScene = editor.scene;
553
- if (scripts) {
554
- const now = performance.now();
555
- if (scriptMode !== "paused") scriptTime += Math.min(250, Math.max(0, now - scriptLastTick));
556
- scriptLastTick = now;
557
- if (scriptMode !== "paused" && scriptObservers.size) scriptObservers.sync(editor.document, editor.selection, () => ({
558
- camera: { ...camera },
559
- ...surface,
560
- safeRect: options.safeRect?.() ?? {
561
- x: 0,
562
- y: 0,
563
- ...surface
564
- }
565
- }));
566
- scripts.sync(editor.document, renderScene, camera, scriptTime, scriptMode === "interact", visibleIds());
567
- }
568
631
  world.position.set(camera.x, camera.y);
569
632
  world.scale.set(camera.zoom);
570
633
  const ids = visibleIds();
571
- syncAssets(ids);
572
634
  const context = renderContext(current, renderScene, frame.time);
573
635
  if (animationId && effectSampler) {
574
636
  context.time = editor.playhead?.time ?? frame.time;
@@ -591,18 +653,20 @@ function mountBoardStage(host, options) {
591
653
  getItem: (id) => renderScene.get(id) ?? null,
592
654
  visibleIds: ids,
593
655
  pinnedIds,
594
- globalSig: [
595
- assetVersion,
596
- cardEpoch,
597
- current.key,
598
- textZoomBucket(camera.zoom)
599
- ].join("|"),
656
+ globalSig: [cardEpoch, current.key].join("|"),
600
657
  staleIds,
601
658
  structureVersion: editor.structureVersion,
602
659
  geometryVersion: editor.geometryVersion,
603
660
  gestureActive: editor.gestureActive && editor.interaction.type !== "panning"
604
661
  });
605
662
  staleIds.clear();
663
+ if (scene.liveVersion !== liveVersion) {
664
+ liveVersion = scene.liveVersion;
665
+ options.onLiveChange?.(scene.liveIds());
666
+ }
667
+ adoptNaturalSizes();
668
+ scheduleRefine();
669
+ syncScripts();
606
670
  drawInteraction();
607
671
  views.sync({
608
672
  ids,
@@ -614,28 +678,35 @@ function mountBoardStage(host, options) {
614
678
  const nextCursor = cursorFor();
615
679
  if (nextCursor !== cursor) host.style.cursor = cursor = nextCursor;
616
680
  scheduleRender();
617
- if (frame.running || scene.animated || scriptMode !== "paused" && scripts?.animated) scheduleTick();
681
+ if (frame.running || scene.animated) scheduleTick();
682
+ if (scriptMode !== "paused" && scripts?.animated) scheduleScriptTick();
618
683
  const cardWait = scene.tick(performance.now());
619
684
  if (cardWait !== null) scheduleCardTick(cardWait);
620
685
  }
621
686
  function resize() {
622
687
  cancelAnimationFrame(resizeFrame);
623
688
  resizeFrame = requestAnimationFrame(() => {
624
- if (!app) return;
625
- app.resize();
626
- surface = {
627
- width: app.screen.width,
628
- height: app.screen.height
689
+ if (!app || !active) return;
690
+ const size = {
691
+ width: host.clientWidth,
692
+ height: host.clientHeight
629
693
  };
630
- editor.surfaceSize = surface;
631
- options.onSurfaceChange?.(surface);
694
+ if (size.width <= 0 || size.height <= 0) return;
695
+ const resolution = getBoardResolution(size);
696
+ const sizeChanged = size.width !== surface.width || size.height !== surface.height;
697
+ if (sizeChanged || app.renderer.resolution !== resolution) app.renderer.resize(size.width, size.height, resolution);
698
+ if (sizeChanged) {
699
+ surface = size;
700
+ editor.surfaceSize = surface;
701
+ options.onSurfaceChange?.(surface);
702
+ }
632
703
  sync();
633
704
  });
634
705
  }
635
- const rectOf = () => host.getBoundingClientRect();
706
+ const localPoint = (x, y) => elementClientToLocal(host)(x, y);
636
707
  const inViews = (event) => event.target instanceof Node && views.element.contains(event.target);
637
708
  function toInput(event) {
638
- return toBoardPointerEvent(event, rectOf(), renderCamera(), event.type === "pointermove" && editor.interaction.type === "drawing");
709
+ return toBoardPointerEvent(event, host, renderCamera(), event.type === "pointermove" && editor.interaction.type === "drawing");
639
710
  }
640
711
  function scriptTarget(input) {
641
712
  if (!scriptOutputs.size) return null;
@@ -742,13 +813,12 @@ function mountBoardStage(host, options) {
742
813
  if (inViews(event)) return;
743
814
  event.preventDefault();
744
815
  editor.takeCameraControl();
745
- const rect = rectOf();
746
- const point = screenPoint(event.clientX - rect.left, event.clientY - rect.top);
816
+ const point = localPoint(event.clientX, event.clientY);
747
817
  editor.wheel(point, event.deltaX, event.deltaY, event.ctrlKey || event.metaKey, event.deltaMode);
748
818
  }
749
819
  function handleDoubleClick(event) {
750
820
  if (inViews(event)) return;
751
- const point = screenToWorld(event.clientX, event.clientY, rectOf(), renderCamera());
821
+ const point = pointToWorld(localPoint(event.clientX, event.clientY), renderCamera());
752
822
  const item = editor.itemAt(point);
753
823
  if (options.onDoubleClick?.(item, point) === true || editor.readonly) return;
754
824
  if (item && !item.locked && editor.registry.capabilities(item).canEdit) {
@@ -774,9 +844,13 @@ function mountBoardStage(host, options) {
774
844
  ["dblclick", handleDoubleClick]
775
845
  ];
776
846
  const stopEditor = editor.subscribe(LIVE_CHANNELS, scheduleTick);
777
- const stopAssets = assets.subscribe(() => {
778
- assetVersion += 1;
779
- requestAssets = true;
847
+ const stopAssets = assets.subscribe((key) => {
848
+ const ids = scene?.holders(key);
849
+ if (!ids?.size) return;
850
+ for (const id of ids) {
851
+ staleIds.add(id);
852
+ naturalIds.add(id);
853
+ }
780
854
  scheduleTick();
781
855
  });
782
856
  const resizeObserver = new ResizeObserver(resize);
@@ -788,8 +862,12 @@ function mountBoardStage(host, options) {
788
862
  antialias: true,
789
863
  autoDensity: true,
790
864
  backgroundAlpha: 0,
791
- resizeTo: host,
792
- resolution: getBoardResolution(),
865
+ width: host.clientWidth,
866
+ height: host.clientHeight,
867
+ resolution: getBoardResolution({
868
+ width: host.clientWidth,
869
+ height: host.clientHeight
870
+ }),
793
871
  autoStart: false
794
872
  });
795
873
  } catch (error) {
@@ -829,6 +907,8 @@ function mountBoardStage(host, options) {
829
907
  });
830
908
  for (const [type, listener, listenerOptions] of listeners) host.addEventListener(type, listener, listenerOptions);
831
909
  resizeObserver.observe(host);
910
+ const window = host.ownerDocument.defaultView;
911
+ if (window) stopDensity = observeDisplayDensity(window, resize);
832
912
  resize();
833
913
  return true;
834
914
  })(),
@@ -880,7 +960,10 @@ function mountBoardStage(host, options) {
880
960
  if (!active) {
881
961
  pointers.cancel();
882
962
  stopMarch();
963
+ stopRefine();
883
964
  cancelScriptPointers();
965
+ cancelAnimationFrame(scriptTickFrame);
966
+ scriptTickFrame = 0;
884
967
  cancelAnimationFrame(renderFrame);
885
968
  cancelAnimationFrame(tickFrame);
886
969
  cancelAnimationFrame(cardTickFrame);
@@ -896,27 +979,27 @@ function mountBoardStage(host, options) {
896
979
  resize();
897
980
  },
898
981
  invalidate(invalidation) {
899
- if (invalidation?.cards) {
900
- cardEpoch += 1;
901
- requestAssets = true;
902
- }
982
+ if (invalidation?.cards) cardEpoch += 1;
903
983
  for (const id of invalidation?.items ?? []) staleIds.add(id);
904
984
  scheduleTick();
905
985
  },
906
986
  isMaterialized: (id) => Boolean(scene?.getNode(id)),
907
- toWorld: (clientX, clientY) => screenToWorld(clientX, clientY, rectOf(), renderCamera()),
987
+ toWorld: (clientX, clientY) => pointToWorld(localPoint(clientX, clientY), renderCamera()),
908
988
  destroy() {
909
989
  disposed = true;
910
990
  pointers.cancel();
911
991
  stopMarch();
992
+ stopRefine();
912
993
  scriptPointers.clear();
913
994
  stopTransforms?.();
914
995
  scriptObservers.clear();
915
996
  scriptListeners.clear();
916
997
  stopMaintenance?.();
998
+ stopDensity?.();
917
999
  stopEditor();
918
1000
  stopAssets();
919
1001
  resizeObserver.disconnect();
1002
+ cancelAnimationFrame(scriptTickFrame);
920
1003
  cancelAnimationFrame(resizeFrame);
921
1004
  cancelAnimationFrame(renderFrame);
922
1005
  cancelAnimationFrame(tickFrame);
@@ -7,7 +7,7 @@ import "../editor/index.js";
7
7
  //#region src/board/stage/views.d.ts
8
8
  type BoardItemViewContext = {
9
9
  editor: BoardEditor;
10
- zoom: number;
10
+ detailed: boolean;
11
11
  selected: boolean;
12
12
  toPointerEvent(event: PointerEvent): BoardPointerEvent;
13
13
  };
@@ -17,6 +17,11 @@ type BoardItemViewInstance = {
17
17
  };
18
18
  type BoardItemView = {
19
19
  type: string | ((item: BoardSceneItem) => boolean);
20
+ /** Optional screen-pixel threshold for expensive content, with built-in hysteresis. */
21
+ contentSize?: {
22
+ width: number;
23
+ height: number;
24
+ };
20
25
  mount(element: HTMLElement, item: BoardSceneItem, context: BoardItemViewContext): BoardItemViewInstance;
21
26
  };
22
27
  type BoardItemViewLayer = {
@@ -33,6 +38,6 @@ type BoardItemViewLayer = {
33
38
  }): void;
34
39
  destroy(): void;
35
40
  };
36
- declare function createBoardItemViewLayer(host: HTMLElement, editor: BoardEditor, views: readonly BoardItemView[], camera: () => BoardViewport): BoardItemViewLayer;
41
+ declare function createBoardItemViewLayer(host: HTMLElement, editor: BoardEditor, views: readonly BoardItemView[], camera: () => BoardViewport, onInvalidate?: () => void): BoardItemViewLayer;
37
42
  //#endregion
38
43
  export { BoardItemView, BoardItemViewContext, BoardItemViewInstance, BoardItemViewLayer, createBoardItemViewLayer };