@neta-art/cohub 11.5.1 → 12.1.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 (82) hide show
  1. package/dist/board/editor/animation.js +2 -1
  2. package/dist/board/editor/creation.js +1 -1
  3. package/dist/board/editor/document-edits.d.ts +1 -1
  4. package/dist/board/editor/document-edits.js +1 -19
  5. package/dist/board/editor/document.js +2 -1
  6. package/dist/board/editor/index.d.ts +2 -1
  7. package/dist/board/editor/index.js +2 -1
  8. package/dist/board/editor/snapshots.js +11 -13
  9. package/dist/board/export/scene.js +1 -0
  10. package/dist/board/model/client-geometry.d.ts +22 -0
  11. package/dist/board/model/client-geometry.js +94 -0
  12. package/dist/board/model/document-share.d.ts +5 -0
  13. package/dist/board/model/document-share.js +27 -0
  14. package/dist/board/model/index.d.ts +5 -4
  15. package/dist/board/model/index.js +4 -3
  16. package/dist/board/model/items/create.d.ts +5 -1
  17. package/dist/board/model/items/create.js +29 -58
  18. package/dist/board/model/items/index.d.ts +2 -2
  19. package/dist/board/model/items/index.js +2 -2
  20. package/dist/board/model/items/registry.js +2 -5
  21. package/dist/board/model/json.d.ts +4 -0
  22. package/dist/board/model/json.js +22 -0
  23. package/dist/board/model/media-playback.d.ts +6 -3
  24. package/dist/board/player/index.d.ts +6 -2
  25. package/dist/board/player/index.js +33 -15
  26. package/dist/board/render/card-tag.d.ts +0 -1
  27. package/dist/board/render/card-tag.js +15 -17
  28. package/dist/board/render/index.d.ts +3 -3
  29. package/dist/board/render/index.js +3 -3
  30. package/dist/board/render/renderers/arrow-card-renderer.js +8 -6
  31. package/dist/board/render/renderers/audio-card-renderer.js +10 -6
  32. package/dist/board/render/renderers/base-card-renderer.d.ts +1 -1
  33. package/dist/board/render/renderers/base-card-renderer.js +2 -3
  34. package/dist/board/render/renderers/board-renderer-registry.d.ts +6 -2
  35. package/dist/board/render/renderers/custom-card-renderer.js +23 -18
  36. package/dist/board/render/renderers/file-card-renderer.js +14 -9
  37. package/dist/board/render/renderers/frame-card-renderer.js +7 -4
  38. package/dist/board/render/renderers/image-card-renderer.js +13 -49
  39. package/dist/board/render/renderers/model-card-renderer.js +4 -1
  40. package/dist/board/render/renderers/shape-card-renderer.js +8 -6
  41. package/dist/board/render/renderers/task-card-renderer.js +13 -10
  42. package/dist/board/render/renderers/text-card-renderer.js +7 -5
  43. package/dist/board/render/renderers/unknown-card-renderer.js +8 -6
  44. package/dist/board/render/renderers/video-card-renderer.js +19 -18
  45. package/dist/board/render/script-host.d.ts +2 -0
  46. package/dist/board/render/script-host.js +44 -9
  47. package/dist/board/render/script-resolution.d.ts +4 -0
  48. package/dist/board/render/script-resolution.js +18 -0
  49. package/dist/board/render/surface.d.ts +9 -2
  50. package/dist/board/render/surface.js +15 -1
  51. package/dist/board/render/text-resolution.d.ts +4 -7
  52. package/dist/board/render/text-resolution.js +20 -22
  53. package/dist/board/replica/index.js +4 -1
  54. package/dist/board/stage/assets.d.ts +12 -8
  55. package/dist/board/stage/assets.js +173 -88
  56. package/dist/board/stage/canvas-size.d.ts +18 -0
  57. package/dist/board/stage/canvas-size.js +31 -0
  58. package/dist/board/stage/index.d.ts +3 -3
  59. package/dist/board/stage/index.js +3 -3
  60. package/dist/board/stage/lru.d.ts +3 -2
  61. package/dist/board/stage/lru.js +8 -4
  62. package/dist/board/stage/pointer.d.ts +1 -1
  63. package/dist/board/stage/pointer.js +4 -5
  64. package/dist/board/stage/scene.d.ts +8 -1
  65. package/dist/board/stage/scene.js +72 -10
  66. package/dist/board/stage/stage.d.ts +1 -0
  67. package/dist/board/stage/stage.js +151 -63
  68. package/dist/board/stage/text-resolution-tracker.d.ts +8 -0
  69. package/dist/board/stage/text-resolution-tracker.js +33 -0
  70. package/dist/board/stage/views.d.ts +7 -2
  71. package/dist/board/stage/views.js +50 -21
  72. package/dist/chunks/environment.d.ts +16 -2
  73. package/dist/index.js +65 -22
  74. package/dist/protocol/dist/board-constants.js +56 -1
  75. package/dist/protocol/dist/board-layout.d.ts +8 -1
  76. package/dist/protocol/dist/board-layout.js +16 -4
  77. package/dist/protocol/dist/board-model.js +13 -19
  78. package/dist/protocol/dist/board-schema.d.ts +52 -0
  79. package/dist/protocol/dist/board-schema.js +8 -0
  80. package/dist/protocol/dist/core/media.js +1 -0
  81. package/dist/protocol/dist/index.d.ts +2 -2
  82. package/package.json +1 -1
@@ -0,0 +1,31 @@
1
+ import { getBoardResolution } from "../render/text-resolution.js";
2
+ //#region src/board/stage/canvas-size.ts
3
+ function measureBoardCanvasBox(entry, density) {
4
+ const css = entry.contentBoxSize?.[0];
5
+ const device = entry.devicePixelContentBoxSize?.[0];
6
+ return {
7
+ width: css?.inlineSize ?? entry.contentRect.width,
8
+ height: css?.blockSize ?? entry.contentRect.height,
9
+ density,
10
+ device: device ? {
11
+ width: device.inlineSize,
12
+ height: device.blockSize
13
+ } : null
14
+ };
15
+ }
16
+ function boardCanvasSize(box, density) {
17
+ const resolution = getBoardResolution(box, density);
18
+ const { device } = box;
19
+ if (device && box.density === density && resolution === density) return {
20
+ width: device.width / density,
21
+ height: device.height / density,
22
+ resolution
23
+ };
24
+ return {
25
+ width: box.width,
26
+ height: box.height,
27
+ resolution
28
+ };
29
+ }
30
+ //#endregion
31
+ export { boardCanvasSize, measureBoardCanvasBox };
@@ -1,10 +1,10 @@
1
- import { DEFAULT_LRU_BUDGET, LruBudget, LruEntry, selectLruEvictions } from "./lru.js";
1
+ import { COMPACT_LRU_BUDGET, DEFAULT_LRU_BUDGET, LruBudget, LruEntry, selectLruEvictions } from "./lru.js";
2
2
  import { BoardImageTexturePool, boardImageTexturePool, createBoardImageTexturePool, loadBoardImageTexture, unloadBoardImageTexture } from "./texture-pool.js";
3
- import { BoardAssetManager, BoardAssetManagerOptions, BoardModelPreviewRequest, BoardModelPreviews, boardAssetKey, createBoardAssetManager } from "./assets.js";
3
+ import { BoardAssetManager, BoardAssetManagerOptions, BoardModelPreviewRequest, BoardModelPreviews, boardAssetKey, boardTextureEdge, createBoardAssetManager } from "./assets.js";
4
4
  import { toBoardPointerEvent } from "./pointer.js";
5
5
  import { createBoardPointerGestures } from "./pointer-gestures.js";
6
6
  import { BoardLiveStroke, BoardScene, BoardSceneNode, SceneSyncInput, createBoardScene } from "./scene.js";
7
7
  import { BoardItemView, BoardItemViewContext, BoardItemViewInstance, BoardItemViewLayer, createBoardItemViewLayer } from "./views.js";
8
8
  import { BoardStage, BoardStageOptions, BoardStagePointerPhase, BoardStageTheme, mountBoardStage } from "./stage.js";
9
9
  import { BoardOverlayInput, drawBoardOverlay, expandFrame } from "./overlay.js";
10
- export { BoardAssetManager, BoardAssetManagerOptions, BoardImageTexturePool, BoardItemView, BoardItemViewContext, BoardItemViewInstance, BoardItemViewLayer, BoardLiveStroke, BoardModelPreviewRequest, BoardModelPreviews, BoardOverlayInput, BoardScene, BoardSceneNode, BoardStage, BoardStageOptions, BoardStagePointerPhase, BoardStageTheme, DEFAULT_LRU_BUDGET, LruBudget, LruEntry, SceneSyncInput, boardAssetKey, boardImageTexturePool, createBoardAssetManager, createBoardImageTexturePool, createBoardItemViewLayer, createBoardPointerGestures, createBoardScene, drawBoardOverlay, expandFrame, loadBoardImageTexture, mountBoardStage, selectLruEvictions, toBoardPointerEvent, unloadBoardImageTexture };
10
+ export { BoardAssetManager, BoardAssetManagerOptions, BoardImageTexturePool, BoardItemView, BoardItemViewContext, BoardItemViewInstance, BoardItemViewLayer, BoardLiveStroke, BoardModelPreviewRequest, BoardModelPreviews, BoardOverlayInput, BoardScene, BoardSceneNode, BoardStage, BoardStageOptions, BoardStagePointerPhase, BoardStageTheme, COMPACT_LRU_BUDGET, DEFAULT_LRU_BUDGET, LruBudget, LruEntry, SceneSyncInput, boardAssetKey, boardImageTexturePool, boardTextureEdge, createBoardAssetManager, createBoardImageTexturePool, createBoardItemViewLayer, createBoardPointerGestures, createBoardScene, drawBoardOverlay, expandFrame, loadBoardImageTexture, mountBoardStage, selectLruEvictions, toBoardPointerEvent, unloadBoardImageTexture };
@@ -1,10 +1,10 @@
1
- import { DEFAULT_LRU_BUDGET, selectLruEvictions } from "./lru.js";
1
+ import { COMPACT_LRU_BUDGET, DEFAULT_LRU_BUDGET, selectLruEvictions } from "./lru.js";
2
2
  import { boardImageTexturePool, createBoardImageTexturePool, loadBoardImageTexture, unloadBoardImageTexture } from "./texture-pool.js";
3
- import { boardAssetKey, createBoardAssetManager } from "./assets.js";
3
+ import { boardAssetKey, boardTextureEdge, createBoardAssetManager } from "./assets.js";
4
4
  import { toBoardPointerEvent } from "./pointer.js";
5
5
  import { createBoardPointerGestures } from "./pointer-gestures.js";
6
6
  import { createBoardScene } from "./scene.js";
7
7
  import { drawBoardOverlay, expandFrame } from "./overlay.js";
8
8
  import { createBoardItemViewLayer } from "./views.js";
9
9
  import { mountBoardStage } from "./stage.js";
10
- export { DEFAULT_LRU_BUDGET, boardAssetKey, boardImageTexturePool, createBoardAssetManager, createBoardImageTexturePool, createBoardItemViewLayer, createBoardPointerGestures, createBoardScene, drawBoardOverlay, expandFrame, loadBoardImageTexture, mountBoardStage, selectLruEvictions, toBoardPointerEvent, unloadBoardImageTexture };
10
+ export { COMPACT_LRU_BUDGET, DEFAULT_LRU_BUDGET, boardAssetKey, boardImageTexturePool, boardTextureEdge, createBoardAssetManager, createBoardImageTexturePool, createBoardItemViewLayer, createBoardPointerGestures, createBoardScene, drawBoardOverlay, expandFrame, loadBoardImageTexture, mountBoardStage, selectLruEvictions, toBoardPointerEvent, unloadBoardImageTexture };
@@ -20,8 +20,9 @@ type LruBudget = {
20
20
  maxCount: number;
21
21
  maxBytes: number;
22
22
  };
23
- /** Default cooling-pool ceiling: 64 images or ~128 MB of GPU memory. */
23
+ /** Default cooling-pool ceiling: 512 images or ~192 MB of GPU memory. */
24
24
  declare const DEFAULT_LRU_BUDGET: LruBudget;
25
+ declare const COMPACT_LRU_BUDGET: LruBudget;
25
26
  /**
26
27
  * Given the unreferenced (cooling) entries and a budget, return the keys to
27
28
  * evict — oldest first — so the remaining pool fits within both limits. Returns
@@ -29,4 +30,4 @@ declare const DEFAULT_LRU_BUDGET: LruBudget;
29
30
  */
30
31
  declare function selectLruEvictions(entries: LruEntry[], budget: LruBudget): string[];
31
32
  //#endregion
32
- export { DEFAULT_LRU_BUDGET, LruBudget, LruEntry, selectLruEvictions };
33
+ export { COMPACT_LRU_BUDGET, DEFAULT_LRU_BUDGET, LruBudget, LruEntry, selectLruEvictions };
@@ -1,8 +1,12 @@
1
1
  //#region src/board/stage/lru.ts
2
- /** Default cooling-pool ceiling: 64 images or ~128 MB of GPU memory. */
2
+ /** Default cooling-pool ceiling: 512 images or ~192 MB of GPU memory. */
3
3
  const DEFAULT_LRU_BUDGET = {
4
- maxCount: 64,
5
- maxBytes: 134217728
4
+ maxCount: 512,
5
+ maxBytes: 201326592
6
+ };
7
+ const COMPACT_LRU_BUDGET = {
8
+ maxCount: 256,
9
+ maxBytes: 100663296
6
10
  };
7
11
  /**
8
12
  * Given the unreferenced (cooling) entries and a budget, return the keys to
@@ -26,4 +30,4 @@ function selectLruEvictions(entries, budget) {
26
30
  return evict;
27
31
  }
28
32
  //#endregion
29
- export { DEFAULT_LRU_BUDGET, selectLruEvictions };
33
+ export { COMPACT_LRU_BUDGET, DEFAULT_LRU_BUDGET, selectLruEvictions };
@@ -3,6 +3,6 @@ import "../model/index.js";
3
3
  import { BoardPointerEvent } from "../editor/interaction.js";
4
4
  import "../editor/index.js";
5
5
  //#region src/board/stage/pointer.d.ts
6
- declare function toBoardPointerEvent(event: PointerEvent, rect: DOMRect, camera: BoardViewport, coalesce?: boolean): BoardPointerEvent;
6
+ declare function toBoardPointerEvent(event: PointerEvent, rect: DOMRect | HTMLElement, camera: BoardViewport, coalesce?: boolean): BoardPointerEvent;
7
7
  //#endregion
8
8
  export { toBoardPointerEvent };
@@ -1,16 +1,15 @@
1
1
  import { pointToWorld, screenPoint } from "../model/geometry.js";
2
+ import { elementClientToLocal } from "../model/client-geometry.js";
2
3
  //#region src/board/stage/pointer.ts
3
4
  function pressureOf(event) {
4
5
  return event.pointerType === "pen" && event.pressure > 0 ? event.pressure : .5;
5
6
  }
6
- function screenOf(event, rect) {
7
- return screenPoint(event.clientX - rect.left, event.clientY - rect.top);
8
- }
9
7
  function toBoardPointerEvent(event, rect, camera, coalesce = false) {
10
- const screen = screenOf(event, rect);
8
+ const local = "getBoundingClientRect" in rect ? elementClientToLocal(rect) : (x, y) => screenPoint(x - rect.left, y - rect.top);
9
+ const screen = local(event.clientX, event.clientY);
11
10
  const coalesced = coalesce ? event.getCoalescedEvents?.() : void 0;
12
11
  const samples = coalesced && coalesced.length > 1 ? coalesced.map((sample) => ({
13
- world: pointToWorld(screenOf(sample, rect), camera),
12
+ world: pointToWorld(local(sample.clientX, sample.clientY), camera),
14
13
  pressure: pressureOf(sample)
15
14
  })) : void 0;
16
15
  return {
@@ -24,6 +24,10 @@ type SceneSyncInput = {
24
24
  structureVersion: number;
25
25
  geometryVersion: number;
26
26
  gestureActive: boolean;
27
+ focus?: {
28
+ x: number;
29
+ y: number;
30
+ };
27
31
  };
28
32
  type BoardSceneNode = {
29
33
  item: BoardSceneItem;
@@ -32,6 +36,9 @@ type BoardSceneNode = {
32
36
  type BoardScene = {
33
37
  sync: (input: SceneSyncInput) => void;
34
38
  getNode: (itemId: string) => BoardSceneNode | null;
39
+ holders: (key: string) => ReadonlySet<string>;
40
+ readonly liveVersion: number;
41
+ liveIds: () => ReadonlySet<string>;
35
42
  readonly animated: boolean;
36
43
  tick: (clock: number) => number | null;
37
44
  destroy: (context: BoardRenderContext) => void;
@@ -40,7 +47,7 @@ declare function createBoardScene(options: {
40
47
  world: Container;
41
48
  overlay: Graphics;
42
49
  getRenderer: BoardCardRendererResolver;
43
- onFarLayerFrame?: () => void;
50
+ requestFrame?: () => void;
44
51
  }): BoardScene;
45
52
  //#endregion
46
53
  export { BoardLiveStroke, BoardScene, BoardSceneNode, SceneSyncInput, createBoardScene };
@@ -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 { 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
@@ -7,11 +8,13 @@ const FAR_LAYER_ENTER = 450;
7
8
  const FAR_LAYER_EXIT = 350;
8
9
  const FAR_PASS_BATCH = 240;
9
10
  const FAR_LIVE_LIMIT = 64;
11
+ const RESOLUTION_BATCH = 32;
10
12
  const Z_FAR_LAYER = -1;
11
13
  const Z_LIFTED = 1e9;
12
14
  const Z_LIVE_STROKE = Number.MAX_SAFE_INTEGER - 1;
13
15
  const Z_OVERLAY = Number.MAX_SAFE_INTEGER;
14
16
  const POOL_LIMIT_PER_RENDERER = 48;
17
+ const EMPTY_IDS = /* @__PURE__ */ new Set();
15
18
  function idSetSignature(ids) {
16
19
  let hash = ids.size;
17
20
  for (const id of ids) {
@@ -31,20 +34,34 @@ function createBoardScene(options) {
31
34
  const tickingIds = /* @__PURE__ */ new Set();
32
35
  const pools = /* @__PURE__ */ new Map();
33
36
  const heldKeys = /* @__PURE__ */ new Map();
37
+ const holders = /* @__PURE__ */ new Map();
34
38
  let orderById = /* @__PURE__ */ new Map();
35
39
  let farSig = null;
36
40
  let farActive = false;
37
41
  let lastStructureVersion = -1;
38
42
  let lastZoom = NaN;
43
+ let lastResolution = "";
44
+ const pendingResolution = /* @__PURE__ */ new Set();
45
+ let liveVersion = 0;
39
46
  let lifted = /* @__PURE__ */ new Set();
40
47
  const liveStrokes = /* @__PURE__ */ new Map();
41
- function setHeldKey(context, cardId, desiredKey) {
48
+ function setHeldKey(context, cardId, item) {
49
+ const desiredKey = item ? context.assetKey(item) : null;
42
50
  const heldKey = heldKeys.get(cardId) ?? null;
43
51
  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);
52
+ if (heldKey) {
53
+ context.releaseTexture(heldKey);
54
+ const ids = holders.get(heldKey);
55
+ ids?.delete(cardId);
56
+ if (ids?.size === 0) holders.delete(heldKey);
57
+ }
58
+ if (desiredKey && item) {
59
+ heldKeys.set(cardId, desiredKey);
60
+ const ids = holders.get(desiredKey);
61
+ if (ids) ids.add(cardId);
62
+ else holders.set(desiredKey, /* @__PURE__ */ new Set([cardId]));
63
+ context.acquireTexture(desiredKey, item);
64
+ } else heldKeys.delete(cardId);
48
65
  }
49
66
  function takeFromPool(rendererId) {
50
67
  const pool = pools.get(rendererId);
@@ -54,6 +71,7 @@ function createBoardScene(options) {
54
71
  function recycle(id, entry, context, destroyed) {
55
72
  setHeldKey(context, id, null);
56
73
  cards.delete(id);
74
+ pendingResolution.delete(id);
57
75
  animatedIds.delete(id);
58
76
  tickingIds.delete(id);
59
77
  if (destroyed) return;
@@ -119,7 +137,7 @@ function createBoardScene(options) {
119
137
  scriptRevision: context.scripts?.revision?.(item.id) ?? 0
120
138
  };
121
139
  cards.set(item.id, entry);
122
- setHeldKey(context, item.id, context.assetKey(item));
140
+ setHeldKey(context, item.id, item);
123
141
  renderer.zoom?.(container, context.zoom);
124
142
  return entry;
125
143
  }
@@ -167,7 +185,7 @@ function createBoardScene(options) {
167
185
  }
168
186
  if (index < ids.length) {
169
187
  farFrame = requestAnimationFrame(step);
170
- if (target === farFront) options.onFarLayerFrame?.();
188
+ if (target === farFront) options.requestFrame?.();
171
189
  return;
172
190
  }
173
191
  if (target === farBack) {
@@ -180,7 +198,7 @@ function createBoardScene(options) {
180
198
  farFront.visible = farActive;
181
199
  farPainted = true;
182
200
  farRunning = false;
183
- options.onFarLayerFrame?.();
201
+ options.requestFrame?.();
184
202
  if (farQueued) runFarPass();
185
203
  };
186
204
  step();
@@ -277,6 +295,11 @@ function createBoardScene(options) {
277
295
  }
278
296
  function sync(input) {
279
297
  const { items, scene, context, getItem, visibleIds, pinnedIds, globalSig, structureVersion, geometryVersion, gestureActive } = input;
298
+ const resolution = `${textZoomBucket(context.zoom)}:${context.textResolution}`;
299
+ if (resolution !== lastResolution) {
300
+ lastResolution = resolution;
301
+ queueResolution(input.focus);
302
+ }
280
303
  const structureChanged = structureVersion !== lastStructureVersion;
281
304
  if (structureChanged) {
282
305
  lastStructureVersion = structureVersion;
@@ -304,7 +327,14 @@ function createBoardScene(options) {
304
327
  if (farActive) {
305
328
  const { orderedIds, unbatched } = visibleFacts(input);
306
329
  for (const id of unbatched) wanted.add(id);
307
- let budget = FAR_LIVE_LIMIT;
330
+ let retained = 0;
331
+ if (!farPainted) {
332
+ for (const id of cards.keys()) if ((!visibleIds || visibleIds.has(id)) && !farCovered.has(id) && !wanted.has(id)) {
333
+ wanted.add(id);
334
+ retained++;
335
+ }
336
+ }
337
+ let budget = Math.max(0, FAR_LIVE_LIMIT - retained);
308
338
  for (const id of orderedIds) {
309
339
  if (budget === 0) break;
310
340
  if (farCovered.has(id) || wanted.has(id)) continue;
@@ -346,13 +376,16 @@ function createBoardScene(options) {
346
376
  entry.hovered = hovered;
347
377
  entry.resizing = resizing;
348
378
  entry.globalSig = globalSig;
349
- setHeldKey(context, id, context.assetKey(item));
379
+ setHeldKey(context, id, item);
350
380
  if (changed) {
351
381
  entry.renderer.update(entry.container, item, context);
352
382
  entry.container.alpha = scene.opacity(id);
353
383
  if (entry.renderer.tick) tickingIds.add(id);
384
+ pendingResolution.delete(id);
354
385
  }
355
386
  }
387
+ refreshResolution(context);
388
+ if (liveSetChanged) liveVersion += 1;
356
389
  if (structureChanged || liveSetChanged) syncClips(scene);
357
390
  if (context.zoom !== lastZoom) {
358
391
  lastZoom = context.zoom;
@@ -363,6 +396,27 @@ function createBoardScene(options) {
363
396
  if (structureChanged || farModeChanged || liveSetChanged || liftChanged) applyChildOrder(scene);
364
397
  syncLiveStrokes(input);
365
398
  }
399
+ function queueResolution(focus) {
400
+ const ids = [...cards].filter(([, entry]) => entry.renderer.resolution);
401
+ if (focus) {
402
+ const distance = ({ item: { frame } }) => (frame.x + frame.width / 2 - focus.x) ** 2 + (frame.y + frame.height / 2 - focus.y) ** 2;
403
+ ids.sort(([, a], [, b]) => distance(a) - distance(b));
404
+ }
405
+ pendingResolution.clear();
406
+ for (const [id] of ids) pendingResolution.add(id);
407
+ }
408
+ function refreshResolution(context) {
409
+ let budget = RESOLUTION_BATCH;
410
+ for (const id of pendingResolution) {
411
+ if (budget-- === 0) break;
412
+ pendingResolution.delete(id);
413
+ const entry = cards.get(id);
414
+ if (!entry) continue;
415
+ entry.renderer.resolution?.(entry.container, entry.item, context);
416
+ if (entry.renderer.tick) tickingIds.add(id);
417
+ }
418
+ if (pendingResolution.size) options.requestFrame?.();
419
+ }
366
420
  function tick(clock) {
367
421
  let next = null;
368
422
  for (const id of tickingIds) {
@@ -391,6 +445,7 @@ function createBoardScene(options) {
391
445
  pools.clear();
392
446
  for (const key of heldKeys.values()) context.releaseTexture(key);
393
447
  heldKeys.clear();
448
+ holders.clear();
394
449
  orderById = /* @__PURE__ */ new Map();
395
450
  for (const entry of clips.values()) entry.group.destroy({ children: true });
396
451
  clips.clear();
@@ -402,6 +457,8 @@ function createBoardScene(options) {
402
457
  visibleMemo = null;
403
458
  lastStructureVersion = -1;
404
459
  lastZoom = NaN;
460
+ lastResolution = "";
461
+ pendingResolution.clear();
405
462
  }
406
463
  return {
407
464
  sync,
@@ -412,6 +469,11 @@ function createBoardScene(options) {
412
469
  container: entry.container
413
470
  } : null;
414
471
  },
472
+ holders: (key) => holders.get(key) ?? EMPTY_IDS,
473
+ get liveVersion() {
474
+ return liveVersion;
475
+ },
476
+ liveIds: () => new Set(cards.keys()),
415
477
  get animated() {
416
478
  return animatedIds.size > 0;
417
479
  },
@@ -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
  };