@neta-art/cohub 11.0.0 → 11.2.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 (115) hide show
  1. package/README.md +31 -5
  2. package/dist/board/editor/context.d.ts +1 -0
  3. package/dist/board/editor/context.js +1 -0
  4. package/dist/board/editor/creation.d.ts +2 -2
  5. package/dist/board/editor/creation.js +3 -3
  6. package/dist/board/editor/editor.d.ts +5 -4
  7. package/dist/board/editor/editor.js +8 -1
  8. package/dist/board/editor/index.d.ts +2 -2
  9. package/dist/board/editor/interaction.d.ts +12 -2
  10. package/dist/board/editor/interaction.js +50 -8
  11. package/dist/board/editor/selection-transform.d.ts +1 -1
  12. package/dist/board/editor/selection-transform.js +13 -2
  13. package/dist/board/editor/snapshots.d.ts +0 -2
  14. package/dist/board/editor/snapshots.js +4 -20
  15. package/dist/board/export/index.d.ts +2 -0
  16. package/dist/board/export/index.js +1 -0
  17. package/dist/board/export/scene.d.ts +2 -0
  18. package/dist/board/export/scene.js +8 -1
  19. package/dist/board/model/file-preview.d.ts +0 -1
  20. package/dist/board/model/file-preview.js +10 -1
  21. package/dist/board/model/image-key.js +1 -1
  22. package/dist/board/model/index.d.ts +8 -7
  23. package/dist/board/model/index.js +6 -5
  24. package/dist/board/model/items/create.d.ts +16 -3
  25. package/dist/board/model/items/create.js +125 -5
  26. package/dist/board/model/items/index.d.ts +2 -2
  27. package/dist/board/model/items/index.js +2 -2
  28. package/dist/board/model/media-playback.js +1 -1
  29. package/dist/board/model/overlay-placement.d.ts +25 -0
  30. package/dist/board/model/overlay-placement.js +35 -0
  31. package/dist/board/model/replay.d.ts +1 -1
  32. package/dist/board/model/replay.js +1 -0
  33. package/dist/board/model/script-hit.d.ts +20 -3
  34. package/dist/board/model/script-hit.js +81 -24
  35. package/dist/board/model/script-observers.d.ts +18 -0
  36. package/dist/board/model/script-observers.js +92 -0
  37. package/dist/board/model/script-scene.d.ts +1 -1
  38. package/dist/board/model/script-scene.js +35 -8
  39. package/dist/board/model/script.d.ts +51 -7
  40. package/dist/board/render/audio-waveform.d.ts +1 -1
  41. package/dist/board/render/audio-waveform.js +8 -5
  42. package/dist/board/render/board-background.js +5 -4
  43. package/dist/board/render/card-tag.d.ts +24 -0
  44. package/dist/board/render/card-tag.js +205 -0
  45. package/dist/board/render/index.d.ts +8 -5
  46. package/dist/board/render/index.js +8 -5
  47. package/dist/board/render/media-interaction.d.ts +2 -1
  48. package/dist/board/render/media-interaction.js +15 -7
  49. package/dist/board/render/palette.js +2 -2
  50. package/dist/board/render/renderers/arrow-card-renderer.js +20 -16
  51. package/dist/board/render/renderers/audio-card-renderer.d.ts +8 -1
  52. package/dist/board/render/renderers/audio-card-renderer.js +64 -60
  53. package/dist/board/render/renderers/base-card-renderer.d.ts +1 -26
  54. package/dist/board/render/renderers/base-card-renderer.js +6 -92
  55. package/dist/board/render/renderers/board-renderer-registry.d.ts +25 -2
  56. package/dist/board/render/renderers/board-renderer-registry.js +11 -4
  57. package/dist/board/render/renderers/custom-card-renderer.js +6 -8
  58. package/dist/board/render/renderers/effect-card-renderer.js +0 -5
  59. package/dist/board/render/renderers/file-card-renderer.d.ts +1 -11
  60. package/dist/board/render/renderers/file-card-renderer.js +43 -105
  61. package/dist/board/render/renderers/frame-card-renderer.js +14 -15
  62. package/dist/board/render/renderers/image-card-renderer.js +62 -67
  63. package/dist/board/render/renderers/media-key.d.ts +6 -0
  64. package/dist/board/render/renderers/media-key.js +39 -0
  65. package/dist/board/render/renderers/shape-card-renderer.js +6 -12
  66. package/dist/board/render/renderers/task-card-renderer.d.ts +1 -8
  67. package/dist/board/render/renderers/task-card-renderer.js +286 -111
  68. package/dist/board/render/renderers/unknown-card-renderer.js +17 -16
  69. package/dist/board/render/renderers/video-card-renderer.js +73 -80
  70. package/dist/board/render/script-host.d.ts +1 -1
  71. package/dist/board/render/script-host.js +53 -28
  72. package/dist/board/render/script-runtime.js +13 -3
  73. package/dist/board/render/script-texture.d.ts +11 -0
  74. package/dist/board/render/script-texture.js +33 -0
  75. package/dist/board/render/stroke.d.ts +1 -1
  76. package/dist/board/render/stroke.js +5 -4
  77. package/dist/board/render/style.d.ts +29 -0
  78. package/dist/board/render/style.js +54 -0
  79. package/dist/board/render/surface.d.ts +34 -0
  80. package/dist/board/render/surface.js +132 -0
  81. package/dist/board/render/text-fit.d.ts +8 -0
  82. package/dist/board/render/text-fit.js +60 -0
  83. package/dist/board/render/text-resolution.d.ts +2 -1
  84. package/dist/board/render/text-resolution.js +4 -1
  85. package/dist/board/stage/assets.js +1 -1
  86. package/dist/board/stage/index.d.ts +3 -2
  87. package/dist/board/stage/index.js +2 -1
  88. package/dist/board/stage/overlay.d.ts +41 -0
  89. package/dist/board/stage/overlay.js +182 -0
  90. package/dist/board/stage/scene.d.ts +4 -43
  91. package/dist/board/stage/scene.js +38 -130
  92. package/dist/board/stage/script-pointer.d.ts +1 -0
  93. package/dist/board/stage/script-pointer.js +8 -2
  94. package/dist/board/stage/stage.d.ts +13 -3
  95. package/dist/board/stage/stage.js +217 -53
  96. package/dist/chunks/environment.d.ts +1037 -8
  97. package/dist/chunks/environment.js +12 -4
  98. package/dist/chunks/http.d.ts +51 -2
  99. package/dist/chunks/http.js +10 -1
  100. package/dist/chunks/transport.js +1 -1
  101. package/dist/chunks/websocket.d.ts +1 -1
  102. package/dist/http.d.ts +3 -3
  103. package/dist/index.d.ts +3 -3
  104. package/dist/index.js +151 -139
  105. package/dist/protocol/dist/board-model.d.ts +36 -1
  106. package/dist/protocol/dist/board-model.js +14 -4
  107. package/dist/protocol/dist/board-task.d.ts +22 -0
  108. package/dist/{board/model/task.js → protocol/dist/board-task.js} +34 -7
  109. package/dist/protocol/dist/generation/blocks.d.ts +8 -0
  110. package/dist/{generation-blocks.js → protocol/dist/generation/blocks.js} +14 -15
  111. package/dist/protocol/dist/index.d.ts +5 -2
  112. package/dist/protocol/dist/task/index.d.ts +4 -0
  113. package/package.json +2 -1
  114. package/dist/board/model/task.d.ts +0 -11
  115. package/dist/types.d.ts +0 -36
@@ -1,91 +1,94 @@
1
- import { BOARD_FONT_STACK } from "../../../protocol/dist/board-constants.js";
2
1
  import { getMediaResourceTitle } from "../../model/media.js";
3
- import { syncTextResolution, syncTextWrapWidth, textResolutionForZoom } from "../text-resolution.js";
2
+ import { boardCardDetailed } from "../text-resolution.js";
3
+ import { createCardTag, formatBoardDuration, syncCardTag, zoomCardTag } from "../card-tag.js";
4
+ import { cardElevation, containRect, drawCardSurface, traceCardOutline } from "../surface.js";
4
5
  import { positionShell } from "./base-card-renderer.js";
5
6
  import { drawFarPlate } from "./far-plate.js";
6
- import { Container, Graphics, Sprite, Text, Texture } from "pixi.js";
7
+ import { drawMediaPlayKey } from "./media-key.js";
8
+ import { Container, Graphics, Sprite, Texture } from "pixi.js";
7
9
  //#region src/board/render/renderers/video-card-renderer.ts
8
10
  const partsByContainer = /* @__PURE__ */ new WeakMap();
9
- const RADIUS = 4;
10
- function layoutContain(sprite, width, height) {
11
- const texture = sprite.texture;
12
- if (!texture.width || !texture.height) return;
13
- const scale = Math.min(width / texture.width, height / texture.height);
14
- sprite.width = texture.width * scale;
15
- sprite.height = texture.height * scale;
16
- sprite.x = (width - sprite.width) / 2;
17
- sprite.y = (height - sprite.height) / 2;
11
+ const FILM_RAIL = 10;
12
+ const FILM_HOLE = 4;
13
+ function videoTitle(item) {
14
+ return item.props.snapshot?.title ?? getMediaResourceTitle(item.props.src);
18
15
  }
19
16
  function sync(container, item, context) {
20
17
  const parts = partsByContainer.get(container);
21
18
  if (!parts) return;
22
19
  positionShell(parts.root, item);
23
20
  const { width, height } = item.frame;
24
- const selected = context.selectedIds.has(item.id);
25
- const hovered = context.hoveredId === item.id;
26
- const resizing = context.resizingIds.has(item.id);
27
- const title = item.props.snapshot?.title ?? getMediaResourceTitle(item.props.src);
21
+ const title = videoTitle(item);
22
+ const durationMs = item.props.snapshot?.durationMs;
23
+ const duration = durationMs ? formatBoardDuration(durationMs) : "";
24
+ syncCardTag(parts.tag, {
25
+ glyph: "video",
26
+ text: duration ? `${title} · ${duration}` : title,
27
+ width
28
+ }, context);
28
29
  const key = context.assetKey(item);
29
30
  const texture = key ? context.getTexture(key) : null;
30
- const failed = Boolean(key && !texture && context.hasError(key));
31
- syncTextResolution(parts.label, parts, context.zoom);
32
- if (parts.label.text !== title) parts.label.text = title;
33
- syncTextWrapWidth(parts.label, parts, Math.max(1, width - 16), resizing);
34
- parts.label.position.set(8, Math.max(8, height - parts.label.height - 8));
31
+ const elevation = cardElevation(context, item.id);
35
32
  const sig = [
36
33
  key ?? "",
37
34
  texture ? `${texture.width}x${texture.height}` : "none",
38
35
  width,
39
36
  height,
40
- selected,
41
- hovered,
42
- failed,
43
- title,
44
- item.props.time ?? 0,
45
- parts.label.height,
46
- context.palette.surface,
47
- context.palette.brand,
48
- context.palette.muted,
49
- context.palette.border,
50
- context.palette.hover,
51
- context.palette.text
37
+ elevation,
38
+ boardCardDetailed(context.zoom),
39
+ context.themeKey
52
40
  ].join("|");
53
41
  if (sig === parts.sig) return;
54
42
  parts.sig = sig;
43
+ const framed = context.style.media === "framed";
44
+ const rect = {
45
+ x: 0,
46
+ y: 0,
47
+ width,
48
+ height
49
+ };
50
+ const rail = framed ? Math.min(FILM_RAIL, height / 6) : 0;
51
+ const screen = {
52
+ x: framed ? rail * .6 : 0,
53
+ y: rail,
54
+ width: width - (framed ? rail * 1.2 : 0),
55
+ height: height - rail * 2
56
+ };
57
+ drawCardSurface(parts.plate.clear(), rect, context.style, {
58
+ fill: framed ? context.palette.text : context.palette.surface,
59
+ border: framed ? null : context.palette.border,
60
+ borderAlpha: .85,
61
+ elevation
62
+ });
63
+ if (framed) drawFilmRails(parts.plate, width, height, rail, context.palette.bg);
64
+ traceCardOutline(parts.mask.clear(), framed ? screen : rect, framed ? {
65
+ corner: "round",
66
+ radius: 1
67
+ } : context.style).fill({ color: 16777215 });
55
68
  if (texture && parts.sprite.texture !== texture) parts.sprite.texture = texture;
56
69
  parts.sprite.visible = Boolean(texture);
57
- if (texture) layoutContain(parts.sprite, width, height);
58
- parts.mask.clear().roundRect(0, 0, width, height, RADIUS).fill({ color: 16777215 });
59
- parts.plate.clear().roundRect(0, 0, width, height, RADIUS).fill({
60
- color: context.palette.surface,
61
- alpha: .96
62
- });
63
- parts.chrome.clear();
64
70
  if (texture) {
65
- const bandHeight = Math.min(height, parts.label.height + 16);
66
- parts.chrome.rect(0, height - bandHeight, width, bandHeight).fill({
67
- color: context.palette.surface,
68
- alpha: .78
69
- });
71
+ const fitted = containRect(screen.width, screen.height, texture.width, texture.height);
72
+ parts.sprite.position.set(screen.x + fitted.x, screen.y + fitted.y);
73
+ parts.sprite.width = fitted.width;
74
+ parts.sprite.height = fitted.height;
70
75
  }
71
- const cx = width / 2;
72
- const cy = height / 2;
73
- const radius = Math.min(22, Math.min(width, height) * .18);
74
- parts.chrome.circle(cx, cy, radius).fill({
75
- color: selected ? context.palette.brand : context.palette.surface,
76
- alpha: texture ? .88 : .96
77
- });
78
- const triangle = radius * .55;
79
- parts.chrome.moveTo(cx - triangle * .35, cy - triangle * .6).lineTo(cx - triangle * .35, cy + triangle * .6).lineTo(cx + triangle * .7, cy).closePath().fill({
80
- color: context.palette.text,
81
- alpha: .94
82
- });
83
- parts.chrome.roundRect(0, 0, width, height, RADIUS).stroke({
84
- color: selected ? context.palette.brand : hovered ? context.palette.muted : context.palette.border,
85
- width: selected ? 2 : 1,
86
- alpha: selected ? .95 : .85
76
+ parts.chrome.clear();
77
+ if (boardCardDetailed(context.zoom)) drawMediaPlayKey(parts.chrome, screen.x + screen.width / 2, screen.y + screen.height / 2, Math.min(18, Math.min(screen.width, screen.height) * .16), context);
78
+ }
79
+ function drawFilmRails(graphics, width, height, rail, hole) {
80
+ const size = Math.min(FILM_HOLE, rail * .45);
81
+ const step = size * 2.5;
82
+ const count = Math.max(1, Math.floor((width - step) / step));
83
+ const start = (width - (count - 1) * step - size) / 2;
84
+ for (let index = 0; index < count; index += 1) {
85
+ const x = start + index * step;
86
+ graphics.rect(x, (rail - size) / 2, size, size).rect(x, height - rail + (rail - size) / 2, size, size);
87
+ }
88
+ graphics.fill({
89
+ color: hole,
90
+ alpha: .9
87
91
  });
88
- parts.label.style.fill = texture ? context.palette.text : context.palette.muted;
89
92
  }
90
93
  const videoCardRenderer = {
91
94
  id: "video-card",
@@ -96,20 +99,8 @@ const videoCardRenderer = {
96
99
  const sprite = new Sprite(Texture.EMPTY);
97
100
  const mask = new Graphics();
98
101
  const chrome = new Graphics();
99
- const resolution = textResolutionForZoom(context.zoom);
100
- const label = new Text({
101
- text: "",
102
- style: {
103
- fill: context.palette.muted,
104
- fontFamily: BOARD_FONT_STACK,
105
- fontSize: 12,
106
- fontWeight: "500",
107
- wordWrap: true
108
- },
109
- resolution,
110
- roundPixels: true
111
- });
112
- root.addChild(plate, sprite, mask, chrome, label);
102
+ const tag = createCardTag();
103
+ root.addChild(plate, sprite, mask, chrome, tag.root);
113
104
  sprite.mask = mask;
114
105
  partsByContainer.set(root, {
115
106
  root,
@@ -117,10 +108,8 @@ const videoCardRenderer = {
117
108
  sprite,
118
109
  mask,
119
110
  chrome,
120
- label,
121
- sig: "",
122
- wrapWidth: 0,
123
- resolution
111
+ tag,
112
+ sig: ""
124
113
  });
125
114
  if (item.type === "video") sync(root, item, context);
126
115
  return root;
@@ -128,6 +117,10 @@ const videoCardRenderer = {
128
117
  update: (container, item, context) => {
129
118
  if (item.type === "video") sync(container, item, context);
130
119
  },
120
+ zoom: (container, zoom) => {
121
+ const tag = partsByContainer.get(container)?.tag;
122
+ if (tag) zoomCardTag(tag, zoom);
123
+ },
131
124
  renderFar: (graphics, item, context) => {
132
125
  drawFarPlate(graphics, item.frame, {
133
126
  fill: context.palette.surface,
@@ -1,7 +1,7 @@
1
1
  import { BoardDocument } from "../../protocol/dist/board-model.js";
2
2
  import "../../protocol/dist/index.js";
3
- import { BoardScriptEvent, BoardScriptOutput, BoardScriptSnapshot } from "../model/script.js";
4
3
  import { BoardViewport } from "../model/geometry.js";
4
+ import { BoardScriptEvent, BoardScriptOutput, BoardScriptSnapshot } from "../model/script.js";
5
5
  import { BoardScene } from "../model/scene.js";
6
6
  import { BoardScriptHost } from "./renderers/board-renderer-registry.js";
7
7
  //#region src/board/render/script-host.d.ts
@@ -1,8 +1,9 @@
1
1
  import { boardScriptItems } from "../model/script-index.js";
2
2
  import { boardScriptScope } from "../model/script-scene.js";
3
+ import { boardScriptHitVisible, resolveBoardScriptHit } from "../model/script-hit.js";
4
+ import { BoardScriptTexture } from "./script-texture.js";
3
5
  import { createBoardScriptModuleReader, loadBoardScriptSources } from "../../protocol/dist/board-script-modules.js";
4
6
  import { boardScriptFrameSource } from "./script-sandbox.js";
5
- import { Texture } from "pixi.js";
6
7
  //#region src/board/render/script-host.ts
7
8
  function createBoardScriptHost(options) {
8
9
  const revisions = /* @__PURE__ */ new Map();
@@ -25,11 +26,27 @@ function createBoardScriptHost(options) {
25
26
  return value;
26
27
  }
27
28
  const focusTargets = /* @__PURE__ */ new Map();
29
+ const focusStyles = /* @__PURE__ */ new WeakMap();
28
30
  const layer = document.createElement("div");
31
+ let interactive = false;
29
32
  layer.style.cssText = "position:absolute;inset:0;pointer-events:none;overflow:hidden;z-index:2";
30
33
  layer.dataset.boardScripts = "";
31
34
  options.host.appendChild(layer);
32
35
  let disposed = false;
36
+ const retiredTextures = /* @__PURE__ */ new Set();
37
+ let retirementFrame = 0;
38
+ function retireTexture(texture) {
39
+ if (!texture.current) return;
40
+ retiredTextures.add(texture);
41
+ if (retirementFrame) return;
42
+ retirementFrame = requestAnimationFrame(() => {
43
+ retirementFrame = requestAnimationFrame(() => {
44
+ retirementFrame = 0;
45
+ for (const retired of retiredTextures) retired.destroy();
46
+ retiredTextures.clear();
47
+ });
48
+ });
49
+ }
33
50
  function ensureBroker() {
34
51
  if (broker) return;
35
52
  broker = document.createElement("iframe");
@@ -58,7 +75,7 @@ function createBoardScriptHost(options) {
58
75
  post(entry, { kind: "dispose" });
59
76
  entry.timer = setTimeout(() => removeFrame(entry), 100);
60
77
  } else removeFrame(entry);
61
- entry.texture?.destroy(true);
78
+ retireTexture(entry.texture);
62
79
  entries.delete(entry.id);
63
80
  revisions.set(entry.id, (revisions.get(entry.id) ?? 0) + 1);
64
81
  options.onOutput(entry.id, null);
@@ -99,7 +116,10 @@ function createBoardScriptHost(options) {
99
116
  removeFrame(entry);
100
117
  return;
101
118
  }
102
- if (entries.get(entry.id) !== entry) return;
119
+ if (entries.get(entry.id) !== entry) {
120
+ data.bitmap?.close?.();
121
+ return;
122
+ }
103
123
  if (data.kind === "ready") {
104
124
  entry.ready = true;
105
125
  options.onFrame();
@@ -149,11 +169,7 @@ function createBoardScriptHost(options) {
149
169
  clearTimeout(entry.timer);
150
170
  entry.pending = false;
151
171
  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
- }
172
+ if (data.bitmap) entry.texture.update(data.bitmap);
157
173
  try {
158
174
  const output = data.output;
159
175
  if (output) {
@@ -170,7 +186,7 @@ function createBoardScriptHost(options) {
170
186
  return {
171
187
  revision: (id) => revisions.get(id) ?? 0,
172
188
  ready: (id) => entries.get(id)?.output !== null && entries.get(id)?.output !== void 0,
173
- frame: (item) => entries.get(item.id)?.texture ?? null,
189
+ frame: (item) => entries.get(item.id)?.texture.current ?? null,
174
190
  error: (id) => entries.get(id)?.error ?? null,
175
191
  release: () => {},
176
192
  fault(id, message) {
@@ -215,10 +231,12 @@ function createBoardScriptHost(options) {
215
231
  get animated() {
216
232
  return [...entries.values()].some((entry) => !entry.error && entry.output?.animated && (entry.visible || entry.output.updating) && !entry.pending);
217
233
  },
218
- sync(board, scene, camera, time, interactive, visibleIds) {
234
+ sync(board, scene, camera, time, inputEnabled, visibleIds) {
219
235
  if (disposed) return;
236
+ interactive = inputEnabled;
220
237
  const alive = /* @__PURE__ */ new Set();
221
238
  const liveFocusTargets = /* @__PURE__ */ new Set();
239
+ let focusSurface;
222
240
  for (const [id, item] of boardScriptItems(board)) {
223
241
  if (!item.script?.enabled) continue;
224
242
  alive.add(id);
@@ -255,7 +273,7 @@ function createBoardScriptHost(options) {
255
273
  inputKey: "",
256
274
  inputDocument: null,
257
275
  startedAt: (options.export ? 0 : time) - (input.snapshot?.time ?? 0),
258
- texture: null,
276
+ texture: new BoardScriptTexture(),
259
277
  error: null,
260
278
  output: null,
261
279
  events: [],
@@ -306,14 +324,15 @@ function createBoardScriptHost(options) {
306
324
  }
307
325
  if (entry.output && !options.export) {
308
326
  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
327
  for (const [target, hit] of hits) {
328
+ const resolved = resolveBoardScriptHit(scene, id, hit);
329
+ if (!resolved) continue;
330
+ const { rect } = resolved;
331
+ if (interactive) focusSurface ??= {
332
+ width: options.host.clientWidth,
333
+ height: options.host.clientHeight
334
+ };
335
+ const focusable = Boolean(focusSurface && interactive && boardScriptHitVisible(scene, resolved, camera, focusSurface));
317
336
  const key = `${id}/${target}`;
318
337
  liveFocusTargets.add(key);
319
338
  let button = focusTargets.get(key);
@@ -322,15 +341,11 @@ function createBoardScriptHost(options) {
322
341
  button.type = "button";
323
342
  const emit = (type, key) => {
324
343
  const current = entries.get(id);
325
- if (!current || current.error) return;
344
+ if (!interactive || !current || current.error || !current.output?.listens.includes(type)) return;
326
345
  current.events.push({
327
346
  type,
328
347
  target,
329
- key,
330
- point: {
331
- x: hit.x + hit.width / 2,
332
- y: hit.y + hit.height / 2
333
- }
348
+ key
334
349
  });
335
350
  current.dirty = true;
336
351
  options.onFrame();
@@ -343,10 +358,16 @@ function createBoardScriptHost(options) {
343
358
  focusTargets.set(key, button);
344
359
  layer.appendChild(button);
345
360
  }
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})`;
361
+ const label = hit.label ?? target;
362
+ if (button.getAttribute("aria-label") !== label) button.setAttribute("aria-label", label);
363
+ const tabIndex = focusable ? 0 : -1;
364
+ if (button.tabIndex !== tabIndex) button.tabIndex = tabIndex;
365
+ const [a, b, c, d, x, y] = scene.layout.matrix(resolved.id);
366
+ const style = `position:absolute;pointer-events:none;background:transparent;border:0;padding:0;transform-origin:0 0;width:${rect.width}px;height:${rect.height}px;transform:matrix(${a * camera.zoom},${b * camera.zoom},${c * camera.zoom},${d * camera.zoom},${(x + a * rect.x + c * rect.y) * camera.zoom + camera.x},${(y + b * rect.x + d * rect.y) * camera.zoom + camera.y})`;
367
+ if (focusStyles.get(button) !== style) {
368
+ button.style.cssText = style;
369
+ focusStyles.set(button, style);
370
+ }
350
371
  }
351
372
  }
352
373
  if (!entry.ready || !entry.sources || entry.pending || !entry.dirty && (!entry.output?.animated || !visible && !entry.output.updating || entry.time === time)) continue;
@@ -378,6 +399,10 @@ function createBoardScriptHost(options) {
378
399
  event(id, event) {
379
400
  const entry = entries.get(id);
380
401
  if (!entry || entry.error || entry.output && !entry.output.listens.includes(event.type)) return;
402
+ if (event.type === "viewchange" || event.type === "selectionchange") {
403
+ const queued = entry.events.findIndex((pending) => pending.type === event.type);
404
+ if (queued >= 0) entry.events.splice(queued, 1);
405
+ }
381
406
  if (event.type === "pointermove" && entry.events.at(-1)?.type === "pointermove") entry.events.pop();
382
407
  if (entry.events.length >= 128) return;
383
408
  entry.events.push(event);
@@ -33,6 +33,8 @@ function createScriptRuntime(module, initial, host) {
33
33
  let patches = effects?.patches ?? {};
34
34
  let items = effects?.items ?? {};
35
35
  let space = effects?.space ?? "world";
36
+ let anchor = effects?.anchor;
37
+ let view = null;
36
38
  let hits = effects?.hits ?? {};
37
39
  const invalidate = () => {
38
40
  if (disposed) return;
@@ -69,6 +71,9 @@ function createScriptRuntime(module, initial, host) {
69
71
  get frame() {
70
72
  return frame;
71
73
  },
74
+ get view() {
75
+ return view;
76
+ },
72
77
  random() {
73
78
  randomState = Math.imul(randomState, 1664525) + 1013904223 >>> 0;
74
79
  return randomState / 4294967296;
@@ -79,10 +84,11 @@ function createScriptRuntime(module, initial, host) {
79
84
  listeners = /* @__PURE__ */ new Set();
80
85
  handlers.set(type, listeners);
81
86
  }
82
- listeners.add(handler);
87
+ const listener = (event) => handler(event);
88
+ listeners.add(listener);
83
89
  invalidate();
84
90
  return () => {
85
- listeners.delete(handler);
91
+ listeners.delete(listener);
86
92
  invalidate();
87
93
  };
88
94
  },
@@ -101,6 +107,7 @@ function createScriptRuntime(module, initial, host) {
101
107
  scene(next, options) {
102
108
  items = structuredClone(next);
103
109
  space = options?.space ?? "world";
110
+ anchor = options?.anchor ? structuredClone(options.anchor) : void 0;
104
111
  invalidate();
105
112
  },
106
113
  hitRegions(next) {
@@ -162,6 +169,7 @@ function createScriptRuntime(module, initial, host) {
162
169
  },
163
170
  event(event) {
164
171
  if (disposed) return;
172
+ if (event.type === "viewchange") view = event.data;
165
173
  const listeners = handlers.get(event.type);
166
174
  if (!listeners?.size) return;
167
175
  for (const handler of listeners) handler(event);
@@ -175,6 +183,7 @@ function createScriptRuntime(module, initial, host) {
175
183
  return {
176
184
  parameters: module.parameters,
177
185
  space,
186
+ anchor,
178
187
  patches,
179
188
  items,
180
189
  hits,
@@ -193,7 +202,8 @@ function createScriptRuntime(module, initial, host) {
193
202
  patches,
194
203
  items,
195
204
  hits,
196
- space
205
+ space,
206
+ anchor
197
207
  } : void 0
198
208
  };
199
209
  return clone ? structuredClone(snapshot) : snapshot;
@@ -0,0 +1,11 @@
1
+ import { Texture } from "pixi.js";
2
+ //#region src/board/render/script-texture.d.ts
3
+ declare class BoardScriptTexture {
4
+ private texture;
5
+ private bitmap;
6
+ get current(): Texture | null;
7
+ update(bitmap: ImageBitmap): void;
8
+ destroy(): void;
9
+ }
10
+ //#endregion
11
+ export { BoardScriptTexture };
@@ -0,0 +1,33 @@
1
+ import { ImageSource, Texture } from "pixi.js";
2
+ //#region src/board/render/script-texture.ts
3
+ var BoardScriptTexture = class {
4
+ texture = null;
5
+ bitmap = null;
6
+ get current() {
7
+ return this.texture;
8
+ }
9
+ update(bitmap) {
10
+ const previous = this.bitmap;
11
+ this.bitmap = bitmap;
12
+ if (!this.texture) {
13
+ this.texture = new Texture({
14
+ source: new ImageSource({ resource: bitmap }),
15
+ dynamic: true
16
+ });
17
+ return;
18
+ }
19
+ const source = this.texture.source;
20
+ source.resource = bitmap;
21
+ source.update();
22
+ if (previous !== bitmap) previous?.close();
23
+ }
24
+ destroy() {
25
+ if (!this.texture) return;
26
+ this.texture.destroy(true);
27
+ this.bitmap?.close();
28
+ this.bitmap = null;
29
+ this.texture = null;
30
+ }
31
+ };
32
+ //#endregion
33
+ export { BoardScriptTexture };
@@ -5,7 +5,7 @@ import { Graphics } from "pixi.js";
5
5
  //#region src/board/render/stroke.d.ts
6
6
  declare function dashPattern(dash: BoardDash | undefined, width: number): [number, number] | null;
7
7
  declare function trimPolyline(path: readonly WorldPoint[], trim: number | undefined): readonly WorldPoint[];
8
- declare function tracePolyline(graphics: Graphics, path: readonly WorldPoint[], pattern: [number, number] | null, closed?: boolean): void;
8
+ declare function tracePolyline(graphics: Graphics, path: readonly WorldPoint[], pattern: [number, number] | null, closed?: boolean, offset?: number): void;
9
9
  declare function traceArrowhead(graphics: Graphics, tip: WorldPoint, angle: number, size: number): void;
10
10
  declare function endAngle(path: readonly WorldPoint[], atStart: boolean): number | null;
11
11
  declare function roundRectPoints(width: number, height: number, radius: number): WorldPoint[];
@@ -31,7 +31,7 @@ function trimPolyline(path, trim) {
31
31
  }
32
32
  return out;
33
33
  }
34
- function tracePolyline(graphics, path, pattern, closed = false) {
34
+ function tracePolyline(graphics, path, pattern, closed = false, offset = 0) {
35
35
  const points = closed && path.length > 2 ? [...path, path[0]] : path;
36
36
  if (points.length < 2) return;
37
37
  if (!pattern) {
@@ -45,9 +45,10 @@ function tracePolyline(graphics, path, pattern, closed = false) {
45
45
  return;
46
46
  }
47
47
  const [dash, gap] = pattern;
48
- let penDown = true;
49
- let fresh = true;
50
- let remaining = dash;
48
+ const phase = Math.max(0, offset) % (dash + gap);
49
+ let penDown = phase < dash;
50
+ let fresh = penDown;
51
+ let remaining = penDown ? dash - phase : dash + gap - phase;
51
52
  for (let index = 1; index < points.length; index += 1) {
52
53
  const a = points[index - 1];
53
54
  const b = points[index];
@@ -0,0 +1,29 @@
1
+ //#region src/board/render/style.d.ts
2
+ type BoardRenderStyle = {
3
+ corner: "round" | "pixel";
4
+ radius: number;
5
+ shadow: BoardShadowStyle | null;
6
+ selection: BoardSelectionStyle;
7
+ media: "plain" | "framed";
8
+ progress: "shimmer" | "mosaic";
9
+ icons: "line" | "pixel";
10
+ motion: "smooth" | "stepped";
11
+ font: string;
12
+ };
13
+ type BoardShadowStyle = {
14
+ color: number;
15
+ alpha: number;
16
+ blur: number;
17
+ y: number;
18
+ };
19
+ type BoardSelectionStyle = {
20
+ width: number;
21
+ outset: number;
22
+ dash: number;
23
+ handle: "circle" | "square";
24
+ handleSize: number;
25
+ };
26
+ declare function defaultBoardRenderStyle(mode: "dark" | "light"): BoardRenderStyle;
27
+ declare function resolveBoardRenderStyle(mode: "dark" | "light", style?: Partial<BoardRenderStyle>): BoardRenderStyle;
28
+ //#endregion
29
+ export { BoardRenderStyle, BoardSelectionStyle, BoardShadowStyle, defaultBoardRenderStyle, resolveBoardRenderStyle };
@@ -0,0 +1,54 @@
1
+ import { BOARD_FONT_STACK } from "../../protocol/dist/board-constants.js";
2
+ //#region src/board/render/style.ts
3
+ const BASE = {
4
+ corner: "round",
5
+ radius: 6,
6
+ selection: {
7
+ width: 1.5,
8
+ outset: 3,
9
+ dash: 0,
10
+ handle: "square",
11
+ handleSize: 8
12
+ },
13
+ media: "plain",
14
+ progress: "shimmer",
15
+ icons: "line",
16
+ motion: "smooth",
17
+ font: BOARD_FONT_STACK
18
+ };
19
+ const SHADOW = {
20
+ light: {
21
+ color: 1447704,
22
+ alpha: .06,
23
+ blur: 12,
24
+ y: 2
25
+ },
26
+ dark: {
27
+ color: 0,
28
+ alpha: .24,
29
+ blur: 12,
30
+ y: 2
31
+ }
32
+ };
33
+ function defaultBoardRenderStyle(mode) {
34
+ return {
35
+ ...BASE,
36
+ selection: { ...BASE.selection },
37
+ shadow: { ...SHADOW[mode] }
38
+ };
39
+ }
40
+ function resolveBoardRenderStyle(mode, style) {
41
+ const base = defaultBoardRenderStyle(mode);
42
+ if (!style) return base;
43
+ return {
44
+ ...base,
45
+ ...style,
46
+ selection: {
47
+ ...base.selection,
48
+ ...style.selection
49
+ },
50
+ shadow: style.shadow === void 0 ? base.shadow : style.shadow
51
+ };
52
+ }
53
+ //#endregion
54
+ export { defaultBoardRenderStyle, resolveBoardRenderStyle };
@@ -0,0 +1,34 @@
1
+ import { WorldPoint } from "../model/geometry.js";
2
+ import { BoardRenderStyle, BoardShadowStyle } from "./style.js";
3
+ import { Graphics } from "pixi.js";
4
+ //#region src/board/render/surface.d.ts
5
+ declare const PIXEL_STEP = 2;
6
+ type CardRect = {
7
+ x: number;
8
+ y: number;
9
+ width: number;
10
+ height: number;
11
+ };
12
+ declare function traceCardOutline(graphics: Graphics, rect: CardRect, style: Pick<BoardRenderStyle, "corner" | "radius">, grow?: number): Graphics;
13
+ declare function tracePixelRect(graphics: Graphics, x: number, y: number, width: number, height: number, step: number): Graphics;
14
+ declare function cardOutlinePoints(rect: CardRect, style: Pick<BoardRenderStyle, "corner" | "radius">, grow?: number): WorldPoint[];
15
+ declare function drawCardShadow(graphics: Graphics, rect: CardRect, style: Pick<BoardRenderStyle, "corner" | "radius">, shadow: BoardShadowStyle | null): void;
16
+ type CardSurface = {
17
+ fill: number;
18
+ fillAlpha?: number;
19
+ border?: number | null;
20
+ borderAlpha?: number;
21
+ elevation?: CardElevation;
22
+ };
23
+ type CardElevation = 0 | 1 | 2;
24
+ declare function cardShadow(style: BoardRenderStyle, elevation: CardElevation): BoardShadowStyle | null;
25
+ declare function drawCardSurface(graphics: Graphics, rect: CardRect, style: BoardRenderStyle, surface: CardSurface): Graphics;
26
+ declare function containRect(width: number, height: number, imageWidth: number, imageHeight: number): CardRect;
27
+ declare function cardElevation(context: {
28
+ liftedIds: ReadonlySet<string>;
29
+ }, id: string): CardElevation;
30
+ declare function isBoardCard(item: {
31
+ type: string;
32
+ }): boolean;
33
+ //#endregion
34
+ export { CardElevation, CardRect, CardSurface, PIXEL_STEP, cardElevation, cardOutlinePoints, cardShadow, containRect, drawCardShadow, drawCardSurface, isBoardCard, traceCardOutline, tracePixelRect };