@neta-art/cohub 11.1.0 → 11.3.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 (78) hide show
  1. package/dist/board/editor/context.d.ts +1 -0
  2. package/dist/board/editor/context.js +1 -0
  3. package/dist/board/editor/editor.d.ts +1 -0
  4. package/dist/board/editor/editor.js +7 -0
  5. package/dist/board/editor/interaction.d.ts +4 -0
  6. package/dist/board/editor/interaction.js +16 -7
  7. package/dist/board/editor/selection-transform.d.ts +1 -1
  8. package/dist/board/editor/selection-transform.js +13 -2
  9. package/dist/board/export/index.d.ts +2 -0
  10. package/dist/board/export/index.js +1 -0
  11. package/dist/board/export/scene.d.ts +2 -0
  12. package/dist/board/export/scene.js +4 -0
  13. package/dist/board/model/file-preview.d.ts +0 -1
  14. package/dist/board/model/file-preview.js +10 -1
  15. package/dist/board/model/index.d.ts +2 -1
  16. package/dist/board/model/index.js +2 -1
  17. package/dist/board/model/script-api.d.ts +69 -0
  18. package/dist/board/model/script-api.js +77 -0
  19. package/dist/board/render/audio-waveform.d.ts +1 -1
  20. package/dist/board/render/audio-waveform.js +8 -5
  21. package/dist/board/render/card-tag.d.ts +24 -0
  22. package/dist/board/render/card-tag.js +205 -0
  23. package/dist/board/render/index.d.ts +7 -4
  24. package/dist/board/render/index.js +7 -4
  25. package/dist/board/render/media-interaction.d.ts +2 -1
  26. package/dist/board/render/media-interaction.js +14 -6
  27. package/dist/board/render/palette.js +2 -2
  28. package/dist/board/render/renderers/arrow-card-renderer.js +20 -16
  29. package/dist/board/render/renderers/audio-card-renderer.d.ts +8 -1
  30. package/dist/board/render/renderers/audio-card-renderer.js +64 -60
  31. package/dist/board/render/renderers/base-card-renderer.d.ts +1 -26
  32. package/dist/board/render/renderers/base-card-renderer.js +6 -92
  33. package/dist/board/render/renderers/board-renderer-registry.d.ts +8 -0
  34. package/dist/board/render/renderers/board-renderer-registry.js +3 -3
  35. package/dist/board/render/renderers/custom-card-renderer.js +4 -7
  36. package/dist/board/render/renderers/effect-card-renderer.js +0 -5
  37. package/dist/board/render/renderers/file-card-renderer.d.ts +1 -11
  38. package/dist/board/render/renderers/file-card-renderer.js +43 -105
  39. package/dist/board/render/renderers/frame-card-renderer.js +14 -15
  40. package/dist/board/render/renderers/image-card-renderer.js +62 -67
  41. package/dist/board/render/renderers/media-key.d.ts +6 -0
  42. package/dist/board/render/renderers/media-key.js +39 -0
  43. package/dist/board/render/renderers/shape-card-renderer.js +6 -12
  44. package/dist/board/render/renderers/task-card-renderer.d.ts +1 -9
  45. package/dist/board/render/renderers/task-card-renderer.js +82 -56
  46. package/dist/board/render/renderers/unknown-card-renderer.js +17 -16
  47. package/dist/board/render/renderers/video-card-renderer.js +73 -80
  48. package/dist/board/render/stroke.d.ts +1 -1
  49. package/dist/board/render/stroke.js +5 -4
  50. package/dist/board/render/style.d.ts +29 -0
  51. package/dist/board/render/style.js +54 -0
  52. package/dist/board/render/surface.d.ts +34 -0
  53. package/dist/board/render/surface.js +132 -0
  54. package/dist/board/render/text-fit.d.ts +8 -0
  55. package/dist/board/render/text-fit.js +60 -0
  56. package/dist/board/render/text-resolution.d.ts +2 -1
  57. package/dist/board/render/text-resolution.js +4 -1
  58. package/dist/board/stage/index.d.ts +3 -2
  59. package/dist/board/stage/index.js +2 -1
  60. package/dist/board/stage/overlay.d.ts +41 -0
  61. package/dist/board/stage/overlay.js +182 -0
  62. package/dist/board/stage/scene.d.ts +2 -43
  63. package/dist/board/stage/scene.js +23 -131
  64. package/dist/board/stage/stage.d.ts +2 -0
  65. package/dist/board/stage/stage.js +99 -32
  66. package/dist/chunks/app-realtime.js +1697 -0
  67. package/dist/chunks/environment.d.ts +997 -3
  68. package/dist/chunks/http.d.ts +393 -3
  69. package/dist/chunks/http.js +40 -1
  70. package/dist/chunks/websocket.d.ts +4 -0
  71. package/dist/chunks/websocket.js +11 -2
  72. package/dist/http.d.ts +2 -2
  73. package/dist/index.d.ts +4 -323
  74. package/dist/index.js +58 -1660
  75. package/dist/protocol/dist/board-model.js +51 -51
  76. package/dist/protocol/dist/board-schema.d.ts +1 -2
  77. package/dist/protocol/dist/board-schema.js +7 -22
  78. package/package.json +2 -1
@@ -0,0 +1,182 @@
1
+ import { CORNER_RESIZE_HANDLES, frameCorners, frameHandlePosition, frameRayIntersection, rotationHandleAnchor, rotationHandlePosition } from "../model/geometry.js";
2
+ import { resolveSceneArrow } from "../model/arrow-geometry.js";
3
+ import { tracePolyline } from "../render/stroke.js";
4
+ import { cardOutlinePoints, isBoardCard, traceCardOutline } from "../render/surface.js";
5
+ //#region src/board/stage/overlay.ts
6
+ const HOVER_ALPHA = .55;
7
+ const PEER_ALPHA = .6;
8
+ function expandFrame(frame, by) {
9
+ return {
10
+ ...frame,
11
+ x: frame.x - by,
12
+ y: frame.y - by,
13
+ width: frame.width + by * 2,
14
+ height: frame.height + by * 2
15
+ };
16
+ }
17
+ function traceFrame(graphics, frame) {
18
+ const corners = frameCorners(frame);
19
+ const first = corners[0];
20
+ if (!first) return graphics;
21
+ graphics.moveTo(first.x, first.y);
22
+ for (const point of corners.slice(1)) graphics.lineTo(point.x, point.y);
23
+ return graphics.closePath();
24
+ }
25
+ function edgeOf(frame, handle) {
26
+ const at = (corner) => frameHandlePosition(frame, corner);
27
+ switch (handle) {
28
+ case "n": return [at("nw"), at("ne")];
29
+ case "e": return [at("ne"), at("se")];
30
+ case "s": return [at("sw"), at("se")];
31
+ case "w": return [at("nw"), at("sw")];
32
+ default: return null;
33
+ }
34
+ }
35
+ function strokeOutline(graphics, frame, card, style, line, inv, dash, march) {
36
+ const shape = card ? style : {
37
+ corner: "round",
38
+ radius: 0
39
+ };
40
+ const rect = {
41
+ x: -frame.width / 2,
42
+ y: -frame.height / 2,
43
+ width: frame.width,
44
+ height: frame.height
45
+ };
46
+ const angle = (frame.rotation || 0) * Math.PI / 180;
47
+ graphics.save();
48
+ graphics.setTransform(Math.cos(angle), Math.sin(angle), -Math.sin(angle), Math.cos(angle), frame.x + frame.width / 2, frame.y + frame.height / 2);
49
+ if (dash > 0) tracePolyline(graphics, cardOutlinePoints(rect, shape), [dash * inv, dash * inv], false, march * inv);
50
+ else traceCardOutline(graphics, rect, shape);
51
+ graphics.stroke({
52
+ ...line,
53
+ cap: "butt",
54
+ join: "miter"
55
+ });
56
+ graphics.restore();
57
+ }
58
+ function drawHandle(graphics, point, style, palette, inv, active) {
59
+ const size = (active ? style.selection.handleSize + 2 : style.selection.handleSize) * inv;
60
+ if (style.selection.handle === "square") graphics.rect(point.x - size / 2, point.y - size / 2, size, size);
61
+ else graphics.circle(point.x, point.y, size / 2);
62
+ graphics.fill({
63
+ color: active ? palette.brand : palette.surface,
64
+ alpha: 1
65
+ }).stroke({
66
+ color: palette.brand,
67
+ width: style.selection.width * inv,
68
+ alignment: 1
69
+ });
70
+ }
71
+ function strokeArrow(graphics, item, scene, color, inv, alpha) {
72
+ const arrow = item;
73
+ const path = resolveSceneArrow(arrow, scene).path;
74
+ const first = path[0];
75
+ if (!first) return;
76
+ graphics.moveTo(first.x, first.y);
77
+ for (const point of path.slice(1)) graphics.lineTo(point.x, point.y);
78
+ graphics.stroke({
79
+ color,
80
+ width: (arrow.style.strokeWidth ?? 2) + 6 * inv,
81
+ alpha,
82
+ cap: "round",
83
+ join: "round"
84
+ });
85
+ }
86
+ function drawBoardOverlay(graphics, input) {
87
+ const { zoom, palette, style, scene, selected, transform, controls, hoveredControl } = input;
88
+ const inv = 1 / Math.max(zoom, 1e-4);
89
+ const brand = palette.brand;
90
+ const outset = style.selection.outset * inv;
91
+ if (input.marquee) {
92
+ const { x, y, width, height } = input.marquee;
93
+ graphics.rect(x, y, width, height).fill({
94
+ color: brand,
95
+ alpha: .08
96
+ });
97
+ graphics.rect(x, y, width, height).stroke({
98
+ color: brand,
99
+ width: inv,
100
+ alpha: .7
101
+ });
102
+ }
103
+ if (input.bindTarget) traceFrame(graphics, input.bindTarget).stroke({
104
+ color: brand,
105
+ width: 2 * inv,
106
+ alpha: .9
107
+ });
108
+ for (const port of input.ports ?? []) {
109
+ const hovered = input.hoveredPort && Math.abs(input.hoveredPort.x - port.x) < .001 && Math.abs(input.hoveredPort.y - port.y) < .001;
110
+ const radius = (hovered ? port.radius * 1.5 : port.radius) * inv;
111
+ if (style.corner === "pixel") graphics.rect(port.x - radius, port.y - radius, radius * 2, radius * 2);
112
+ else graphics.circle(port.x, port.y, radius);
113
+ graphics.fill({
114
+ color: palette.surface,
115
+ alpha: 1
116
+ }).stroke({
117
+ color: brand,
118
+ width: 1.5 * inv,
119
+ alpha: hovered ? 1 : .8
120
+ });
121
+ }
122
+ const hovered = input.hovered;
123
+ if (hovered && hovered.type === "arrow") strokeArrow(graphics, hovered, scene, palette.muted, inv, .2);
124
+ else if (hovered) strokeOutline(graphics, expandFrame(hovered.frame, outset), isBoardCard(hovered), style, {
125
+ color: palette.muted,
126
+ width: inv * style.selection.width,
127
+ alpha: HOVER_ALPHA
128
+ }, inv, 0, 0);
129
+ if (selected.length === 0) return;
130
+ const single = selected.length === 1 ? selected[0] : void 0;
131
+ const dash = style.selection.dash;
132
+ const line = {
133
+ color: brand,
134
+ width: style.selection.width * inv,
135
+ alpha: 1
136
+ };
137
+ for (const item of selected) if (item.type === "arrow") strokeArrow(graphics, item, scene, brand, inv, .3);
138
+ else if (!single) strokeOutline(graphics, expandFrame(item.frame, outset), isBoardCard(item), style, {
139
+ ...line,
140
+ alpha: PEER_ALPHA
141
+ }, inv, 0, 0);
142
+ if (!transform) return;
143
+ if (single?.type !== "arrow") {
144
+ const card = single ? isBoardCard(single) : false;
145
+ strokeOutline(graphics, expandFrame(transform.frame, outset), card, style, line, inv, dash, input.march);
146
+ }
147
+ if (!controls) return;
148
+ if (input.arrowEndpoints?.length) {
149
+ for (const point of input.arrowEndpoints) drawHandle(graphics, point, style, palette, inv, false);
150
+ return;
151
+ }
152
+ const frame = expandFrame(transform.frame, outset);
153
+ if (hoveredControl?.kind === "resize" && transform.resizeMode === "free") {
154
+ const edge = edgeOf(frame, hoveredControl.handle);
155
+ if (edge) graphics.moveTo(edge[0].x, edge[0].y).lineTo(edge[1].x, edge[1].y).stroke({
156
+ color: brand,
157
+ width: 2.5 * inv,
158
+ alpha: 1
159
+ });
160
+ }
161
+ if (transform.resizeMode !== "none") for (const handle of CORNER_RESIZE_HANDLES) {
162
+ const active = hoveredControl?.kind === "resize" && hoveredControl.handle === handle;
163
+ drawHandle(graphics, frameHandlePosition(frame, handle), style, palette, inv, active);
164
+ }
165
+ if (transform.canRotate && (input.pointerType === "touch" || transform.resizeMode === "none")) {
166
+ const pointer = input.rotationPointer;
167
+ const rotation = pointer ?? rotationHandlePosition(frame, zoom);
168
+ const anchor = pointer ? frameRayIntersection(frame, pointer) : rotationHandleAnchor(frame);
169
+ const active = pointer !== null || hoveredControl?.kind === "rotate";
170
+ graphics.moveTo(anchor.x, anchor.y).lineTo(rotation.x, rotation.y).stroke({
171
+ color: brand,
172
+ width: inv,
173
+ alpha: .8
174
+ });
175
+ graphics.circle(rotation.x, rotation.y, (active ? 6 : 5) * inv).fill({ color: active ? brand : palette.surface }).stroke({
176
+ color: brand,
177
+ width: 1.5 * inv
178
+ });
179
+ }
180
+ }
181
+ //#endregion
182
+ export { drawBoardOverlay, expandFrame };
@@ -1,11 +1,7 @@
1
1
  import { BoardDrawPoint } from "../../protocol/dist/board-model.js";
2
- import { BoardFrame } from "../../protocol/dist/board-layout.js";
3
- import { Rect, WorldPoint } from "../model/geometry.js";
4
2
  import { BoardScene as BoardScene$1, BoardSceneItem } from "../model/scene.js";
5
3
  import "../model/index.js";
6
- import { BoardSelectionTransform, BoardTransformControl } from "../editor/selection-transform.js";
7
- import "../editor/index.js";
8
- import { BoardCardRendererResolver, BoardRenderContext, BoardRenderPalette } from "../render/renderers/board-renderer-registry.js";
4
+ import { BoardCardRendererResolver, BoardRenderContext } from "../render/renderers/board-renderer-registry.js";
9
5
  import "../render/index.js";
10
6
  import { Container, Graphics } from "pixi.js";
11
7
  //#region src/board/stage/scene.d.ts
@@ -29,42 +25,6 @@ type SceneSyncInput = {
29
25
  geometryVersion: number;
30
26
  gestureActive: boolean;
31
27
  };
32
- type SceneOverlayInput = {
33
- zoom: number;
34
- pointerType: string;
35
- marquee: Rect | null;
36
- selection: string[];
37
- transform: BoardSelectionTransform | null;
38
- controls: boolean;
39
- hoveredControl: BoardTransformControl | null;
40
- rotationPointer: WorldPoint | null;
41
- arrowEndpoints?: Array<{
42
- x: number;
43
- y: number;
44
- }>;
45
- ports?: ReadonlyArray<{
46
- x: number;
47
- y: number;
48
- radius: number;
49
- }>;
50
- hoveredPort?: {
51
- x: number;
52
- y: number;
53
- } | null;
54
- arrowDraft?: {
55
- from: {
56
- x: number;
57
- y: number;
58
- };
59
- to: {
60
- x: number;
61
- y: number;
62
- };
63
- size: number;
64
- targetFrame: BoardFrame | null;
65
- } | null;
66
- bindTarget?: BoardFrame | null;
67
- };
68
28
  type BoardSceneNode = {
69
29
  item: BoardSceneItem;
70
30
  container: Container;
@@ -74,7 +34,6 @@ type BoardScene = {
74
34
  getNode: (itemId: string) => BoardSceneNode | null;
75
35
  readonly animated: boolean;
76
36
  tick: (clock: number) => number | null;
77
- drawOverlay: (input: SceneOverlayInput, palette: BoardRenderPalette) => void;
78
37
  destroy: (context: BoardRenderContext) => void;
79
38
  };
80
39
  declare function createBoardScene(options: {
@@ -84,4 +43,4 @@ declare function createBoardScene(options: {
84
43
  onFarLayerFrame?: () => void;
85
44
  }): BoardScene;
86
45
  //#endregion
87
- export { BoardLiveStroke, BoardScene, BoardSceneNode, SceneOverlayInput, SceneSyncInput, createBoardScene };
46
+ export { BoardLiveStroke, BoardScene, BoardSceneNode, SceneSyncInput, createBoardScene };
@@ -1,6 +1,5 @@
1
1
  import { parseBoardItem } from "../../protocol/dist/board-model.js";
2
2
  import { drawPointsBounds } from "../../protocol/dist/board-layout.js";
3
- import { CORNER_RESIZE_HANDLES, frameCorners, frameHandlePosition, frameRayIntersection, rotationHandleAnchor, rotationHandlePosition } from "../model/geometry.js";
4
3
  import { clippingAncestor, isClippingFrame, syncClipGroup } from "../render/clip.js";
5
4
  import { Graphics } from "pixi.js";
6
5
  //#region src/board/stage/scene.ts
@@ -9,6 +8,7 @@ const FAR_LAYER_EXIT = 350;
9
8
  const FAR_PASS_BATCH = 240;
10
9
  const FAR_LIVE_LIMIT = 64;
11
10
  const Z_FAR_LAYER = -1;
11
+ const Z_LIFTED = 1e9;
12
12
  const Z_LIVE_STROKE = Number.MAX_SAFE_INTEGER - 1;
13
13
  const Z_OVERLAY = Number.MAX_SAFE_INTEGER;
14
14
  const POOL_LIMIT_PER_RENDERER = 48;
@@ -21,26 +21,6 @@ function idSetSignature(ids) {
21
21
  }
22
22
  return hash;
23
23
  }
24
- function traceFrame(graphics, frame) {
25
- const corners = frameCorners(frame);
26
- const first = corners[0];
27
- if (!first) return graphics;
28
- graphics.moveTo(first.x, first.y);
29
- for (let index = 1; index < corners.length; index += 1) {
30
- const point = corners[index];
31
- if (point) graphics.lineTo(point.x, point.y);
32
- }
33
- return graphics.closePath();
34
- }
35
- function frameEdgePoints(frame, handle) {
36
- switch (handle) {
37
- case "n": return [frameHandlePosition(frame, "nw"), frameHandlePosition(frame, "ne")];
38
- case "e": return [frameHandlePosition(frame, "ne"), frameHandlePosition(frame, "se")];
39
- case "s": return [frameHandlePosition(frame, "sw"), frameHandlePosition(frame, "se")];
40
- case "w": return [frameHandlePosition(frame, "nw"), frameHandlePosition(frame, "sw")];
41
- default: return null;
42
- }
43
- }
44
24
  function createBoardScene(options) {
45
25
  const { world, overlay, getRenderer } = options;
46
26
  world.sortableChildren = true;
@@ -55,6 +35,8 @@ function createBoardScene(options) {
55
35
  let farSig = null;
56
36
  let farActive = false;
57
37
  let lastStructureVersion = -1;
38
+ let lastZoom = NaN;
39
+ let lifted = /* @__PURE__ */ new Set();
58
40
  const liveStrokes = /* @__PURE__ */ new Map();
59
41
  function setHeldKey(context, cardId, desiredKey) {
60
42
  const heldKey = heldKeys.get(cardId) ?? null;
@@ -138,6 +120,7 @@ function createBoardScene(options) {
138
120
  };
139
121
  cards.set(item.id, entry);
140
122
  setHeldKey(context, item.id, context.assetKey(item));
123
+ renderer.zoom?.(container, context.zoom);
141
124
  return entry;
142
125
  }
143
126
  let farFront = createFarLayer();
@@ -355,7 +338,8 @@ function createBoardScene(options) {
355
338
  const hovered = context.hoveredId === id;
356
339
  const resizing = context.resizingIds.has(id);
357
340
  const scriptRevision = context.scripts?.revision?.(id) ?? 0;
358
- const changed = input.staleIds?.has(id) || scriptRevision !== entry.scriptRevision || item !== entry.item || entry.renderer.animated || selected !== entry.selected || hovered !== entry.hovered || resizing !== entry.resizing || globalSig !== entry.globalSig;
341
+ const observed = entry.renderer.observesSelection === true && (selected !== entry.selected || hovered !== entry.hovered);
342
+ const changed = input.staleIds?.has(id) || scriptRevision !== entry.scriptRevision || item !== entry.item || entry.renderer.animated || observed || resizing !== entry.resizing || globalSig !== entry.globalSig;
359
343
  entry.scriptRevision = scriptRevision;
360
344
  entry.item = item;
361
345
  entry.selected = selected;
@@ -370,7 +354,13 @@ function createBoardScene(options) {
370
354
  }
371
355
  }
372
356
  if (structureChanged || liveSetChanged) syncClips(scene);
373
- if (structureChanged || farModeChanged || liveSetChanged) applyChildOrder();
357
+ if (context.zoom !== lastZoom) {
358
+ lastZoom = context.zoom;
359
+ for (const entry of cards.values()) entry.renderer.zoom?.(entry.container, lastZoom);
360
+ }
361
+ const liftChanged = context.liftedIds !== lifted;
362
+ lifted = context.liftedIds;
363
+ if (structureChanged || farModeChanged || liveSetChanged || liftChanged) applyChildOrder(scene);
374
364
  syncLiveStrokes(input);
375
365
  }
376
366
  function tick(clock) {
@@ -383,114 +373,16 @@ function createBoardScene(options) {
383
373
  }
384
374
  return next;
385
375
  }
386
- function applyChildOrder() {
387
- for (const [id, entry] of cards) entry.container.zIndex = orderById.get(id) ?? 0;
388
- world.sortableChildren = true;
389
- world.sortChildren();
390
- }
391
- function drawOverlay(input, palette) {
392
- overlay.clear();
393
- const { zoom, pointerType, marquee, selection, transform, controls, hoveredControl, rotationPointer, arrowEndpoints, ports, hoveredPort, arrowDraft, bindTarget } = input;
394
- const inv = 1 / zoom;
395
- const brand = palette.brand;
396
- if (marquee) {
397
- overlay.rect(marquee.x, marquee.y, marquee.width, marquee.height).fill({
398
- color: brand,
399
- alpha: .08
400
- });
401
- overlay.rect(marquee.x, marquee.y, marquee.width, marquee.height).stroke({
402
- color: brand,
403
- width: inv,
404
- alpha: .7
405
- });
406
- }
407
- if (bindTarget) traceFrame(overlay, bindTarget).stroke({
408
- color: brand,
409
- width: 2 * inv,
410
- alpha: .9
411
- });
412
- if (arrowDraft) {
413
- const { from, to, size, targetFrame } = arrowDraft;
414
- if (targetFrame) traceFrame(overlay, targetFrame).stroke({
415
- color: brand,
416
- width: 2 * inv,
417
- alpha: .9
418
- });
419
- overlay.moveTo(from.x, from.y).lineTo(to.x, to.y).stroke({
420
- color: brand,
421
- width: Math.max(size, 1.5) * inv,
422
- alpha: .85,
423
- cap: "round"
424
- });
425
- overlay.circle(to.x, to.y, 3.5 * inv).fill({
426
- color: brand,
427
- alpha: .95
428
- });
429
- }
430
- if (ports && ports.length > 0) for (const port of ports) {
431
- const hovered = hoveredPort && Math.abs(hoveredPort.x - port.x) < .001 && Math.abs(hoveredPort.y - port.y) < .001;
432
- const r = (hovered ? port.radius * 1.5 : port.radius) * inv;
433
- overlay.circle(port.x, port.y, r).fill({
434
- color: palette.surface,
435
- alpha: 1
436
- }).stroke({
437
- color: brand,
438
- width: 1.5 * inv,
439
- alpha: hovered ? 1 : .8
440
- });
441
- }
442
- if (!transform || selection.length === 0) return;
443
- const source = transform.frame;
444
- traceFrame(overlay, source).stroke({
445
- color: brand,
446
- width: 1.5 * inv,
447
- alpha: .95
448
- });
449
- if (!controls) return;
450
- if (arrowEndpoints && arrowEndpoints.length > 0) {
451
- const r = 5 * inv;
452
- for (const point of arrowEndpoints) overlay.circle(point.x, point.y, r).fill({
453
- color: palette.surface,
454
- alpha: 1
455
- }).stroke({
456
- color: brand,
457
- width: 1.5 * inv
458
- });
459
- return;
460
- }
461
- if (hoveredControl?.kind === "resize" && transform.resizeMode === "free") {
462
- const edge = frameEdgePoints(source, hoveredControl.handle);
463
- if (edge) overlay.moveTo(edge[0].x, edge[0].y).lineTo(edge[1].x, edge[1].y).stroke({
464
- color: brand,
465
- width: 2.5 * inv,
466
- alpha: 1
467
- });
468
- }
469
- if (transform.resizeMode !== "none") for (const handle of CORNER_RESIZE_HANDLES) {
470
- const position = frameHandlePosition(source, handle);
471
- const hovered = hoveredControl?.kind === "resize" && hoveredControl.handle === handle;
472
- overlay.circle(position.x, position.y, (hovered ? 5 : 4) * inv).fill({
473
- color: hovered ? brand : palette.surface,
474
- alpha: 1
475
- }).stroke({
476
- color: brand,
477
- width: 1.5 * inv
478
- });
479
- }
480
- if (transform.canRotate && (pointerType === "touch" || transform.resizeMode === "none")) {
481
- const rotation = rotationPointer ?? rotationHandlePosition(source, zoom);
482
- const anchor = rotationPointer ? frameRayIntersection(source, rotationPointer) : rotationHandleAnchor(source);
483
- const hovered = rotationPointer !== null || hoveredControl?.kind === "rotate";
484
- overlay.moveTo(anchor.x, anchor.y).lineTo(rotation.x, rotation.y).stroke({
485
- color: brand,
486
- width: inv,
487
- alpha: .8
488
- });
489
- overlay.circle(rotation.x, rotation.y, (hovered ? 6 : 5) * inv).fill({ color: hovered ? brand : palette.surface }).stroke({
490
- color: brand,
491
- width: 1.5 * inv
492
- });
376
+ function applyChildOrder(scene) {
377
+ const raised = new Set(lifted);
378
+ for (const id of lifted) if (scene.get(id)?.type === "frame") for (const child of scene.descendants(id)) raised.add(child);
379
+ const order = (id) => (orderById.get(id) ?? 0) + (raised.has(id) ? Z_LIFTED : 0);
380
+ for (const [id, entry] of cards) entry.container.zIndex = order(id);
381
+ for (const [id, entry] of clips) {
382
+ entry.group.zIndex = order(id) + .5;
383
+ entry.group.sortChildren();
493
384
  }
385
+ world.sortChildren();
494
386
  }
495
387
  function destroy(context) {
496
388
  for (const entry of cards.values()) entry.renderer.destroy?.(entry.container, context);
@@ -509,6 +401,7 @@ function createBoardScene(options) {
509
401
  farActive = false;
510
402
  visibleMemo = null;
511
403
  lastStructureVersion = -1;
404
+ lastZoom = NaN;
512
405
  }
513
406
  return {
514
407
  sync,
@@ -523,7 +416,6 @@ function createBoardScene(options) {
523
416
  return animatedIds.size > 0;
524
417
  },
525
418
  tick,
526
- drawOverlay,
527
419
  destroy
528
420
  };
529
421
  }
@@ -10,6 +10,7 @@ import "../model/index.js";
10
10
  import { BoardPointerEvent } from "../editor/interaction.js";
11
11
  import { BoardEditor } from "../editor/editor.js";
12
12
  import "../editor/index.js";
13
+ import { BoardRenderStyle } from "../render/style.js";
13
14
  import { BoardCardRendererResolver, BoardRenderLabels, BoardRenderPalette } from "../render/renderers/board-renderer-registry.js";
14
15
  import "../render/index.js";
15
16
  import { BoardAssetManager } from "./assets.js";
@@ -22,6 +23,7 @@ type BoardStageTheme = {
22
23
  palette: BoardRenderPalette;
23
24
  colors: BoardShapeColors;
24
25
  colorScheme: "dark" | "light";
26
+ style?: BoardRenderStyle;
25
27
  imageBackground?: boolean;
26
28
  };
27
29
  type BoardStagePointerPhase = "down" | "move" | "up" | "leave";
@@ -14,12 +14,14 @@ import { createBoardBackground, updateBoardBackground } from "../render/board-ba
14
14
  import { getBoardResolution, textZoomBucket } from "../render/text-resolution.js";
15
15
  import { DEFAULT_BOARD_RENDER_LABELS, getBoardCardRenderer } from "../render/renderers/board-renderer-registry.js";
16
16
  import { createBoardScriptHost } from "../render/script-host.js";
17
+ import { defaultBoardRenderStyle } from "../render/style.js";
17
18
  import { createBoardPlayer } from "../player/index.js";
18
19
  import { createBoardScriptObservers } from "../model/script-observers.js";
19
20
  import { toBoardPointerEvent } from "./pointer.js";
20
21
  import { createBoardScene } from "./scene.js";
21
22
  import { scheduleBoardMaintenance } from "./maintenance.js";
22
23
  import { createBoardScriptPointerRouter } from "./script-pointer.js";
24
+ import { drawBoardOverlay } from "./overlay.js";
23
25
  import { createBoardItemViewLayer } from "./views.js";
24
26
  import { Application, Container, Graphics, RendererType } from "pixi.js";
25
27
  //#region src/board/stage/stage.ts
@@ -42,7 +44,19 @@ function defaultTheme() {
42
44
  colorScheme: "dark"
43
45
  };
44
46
  }
47
+ const NO_IDS = /* @__PURE__ */ new Set();
48
+ const MARCH_STEP_MS = 160;
49
+ const MARCH_SMOOTH_MS = 40;
45
50
  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`;
51
+ const defaultStyles = /* @__PURE__ */ new Map();
52
+ function styleOf(theme) {
53
+ if (theme.style) return theme.style;
54
+ const cached = defaultStyles.get(theme.colorScheme);
55
+ if (cached) return cached;
56
+ const style = defaultBoardRenderStyle(theme.colorScheme);
57
+ defaultStyles.set(theme.colorScheme, style);
58
+ return style;
59
+ }
46
60
  function mountBoardStage(host, options) {
47
61
  const { editor, assets } = options;
48
62
  const theme = options.theme ?? defaultTheme;
@@ -148,6 +162,19 @@ function mountBoardStage(host, options) {
148
162
  }
149
163
  if (sizes.length > 0) editor.adoptMediaNaturalSizes(sizes);
150
164
  }
165
+ let lifted = null;
166
+ function liftedIds() {
167
+ const interaction = editor.interaction;
168
+ if (interaction.type !== "translating" || !interaction.moved) {
169
+ lifted = null;
170
+ return NO_IDS;
171
+ }
172
+ if (lifted?.origin !== interaction.origin) lifted = {
173
+ origin: interaction.origin,
174
+ ids: new Set(interaction.origin.keys())
175
+ };
176
+ return lifted.ids;
177
+ }
151
178
  function renderContext(current, renderScene, time) {
152
179
  const interaction = editor.interaction;
153
180
  return {
@@ -158,9 +185,12 @@ function mountBoardStage(host, options) {
158
185
  selectedIds: new Set(editor.selection),
159
186
  hoveredId: editor.hoverId,
160
187
  resizingIds: interaction.type === "resizing" ? new Set(interaction.origin.keys()) : /* @__PURE__ */ new Set(),
188
+ liftedIds: liftedIds(),
161
189
  palette: current.palette,
162
190
  colors: current.colors,
163
191
  colorScheme: current.colorScheme,
192
+ style: styleOf(current),
193
+ themeKey: current.key,
164
194
  rendererType: app?.renderer.type === RendererType.CANVAS ? "canvas" : "gpu",
165
195
  zoom: renderCamera().zoom,
166
196
  reducedMotion: options.reducedMotion?.() ?? false,
@@ -275,6 +305,72 @@ function mountBoardStage(host, options) {
275
305
  });
276
306
  }
277
307
  }
308
+ let march = 0;
309
+ let marchTimer = null;
310
+ function stopMarch() {
311
+ if (marchTimer !== null) clearTimeout(marchTimer);
312
+ marchTimer = null;
313
+ }
314
+ function scheduleMarch(style) {
315
+ const stepped = style.motion === "stepped";
316
+ if (marchTimer !== null || !active || disposed) return;
317
+ marchTimer = setTimeout(() => {
318
+ marchTimer = null;
319
+ march += stepped ? 2 : 1;
320
+ drawInteraction();
321
+ scheduleRender();
322
+ }, stepped ? MARCH_STEP_MS : MARCH_SMOOTH_MS);
323
+ }
324
+ function drawInteraction() {
325
+ if (!overlay) return;
326
+ const current = theme();
327
+ const style = styleOf(current);
328
+ const renderScene = editor.scene;
329
+ const camera = renderCamera();
330
+ const interaction = editor.interaction;
331
+ const selected = editor.selection.flatMap((id) => renderScene.get(id) ?? []);
332
+ const single = selected.length === 1 ? selected[0] : void 0;
333
+ let arrowEndpoints;
334
+ if (single?.type === "arrow" && !single.locked) {
335
+ const resolved = resolveSceneArrow(single, renderScene);
336
+ arrowEndpoints = [
337
+ resolved.start.point,
338
+ resolved.mid,
339
+ resolved.end.point
340
+ ];
341
+ }
342
+ const hoverId = interaction.type === "idle" ? editor.hoverId : null;
343
+ const hovered = hoverId && !editor.selection.includes(hoverId) ? renderScene.get(hoverId) ?? null : null;
344
+ overlay.clear();
345
+ drawBoardOverlay(overlay, {
346
+ zoom: camera.zoom,
347
+ pointerType: editor.pointerType,
348
+ palette: current.palette,
349
+ style,
350
+ scene: renderScene,
351
+ hovered,
352
+ selected,
353
+ march,
354
+ marquee: editor.marquee,
355
+ transform: editor.selectionTransform,
356
+ controls: editor.tool === "select",
357
+ hoveredControl: editor.hoveredTransformControl,
358
+ rotationPointer: interaction.type === "rotating" ? interaction.current : null,
359
+ arrowEndpoints,
360
+ ports: editor.connectionPorts,
361
+ hoveredPort: editor.hoveredConnectionPort,
362
+ bindTarget: editor.bindTargetFrame ?? editor.arrowDraft?.targetFrame ?? null
363
+ });
364
+ options.drawOverlay?.(overlay, {
365
+ zoom: camera.zoom,
366
+ theme: current,
367
+ scene: renderScene
368
+ });
369
+ drawTransient(overlay, current);
370
+ editor.controlOutset = style.selection.outset;
371
+ if (style.selection.dash > 0 && selected.length > 0 && !(options.reducedMotion?.() ?? false)) scheduleMarch(style);
372
+ else stopMarch();
373
+ }
278
374
  function cursorFor() {
279
375
  if (scriptMode === "interact" && scriptHover) {
280
376
  const output = scriptOutputs.get(scriptHover.owner);
@@ -506,38 +602,7 @@ function mountBoardStage(host, options) {
506
602
  gestureActive: editor.gestureActive && editor.interaction.type !== "panning"
507
603
  });
508
604
  staleIds.clear();
509
- const single = editor.selection.length === 1 ? renderScene.get(editor.selection[0]) : null;
510
- let arrowEndpoints;
511
- if (single?.type === "arrow" && !single.locked) {
512
- const resolved = resolveSceneArrow(single, renderScene);
513
- arrowEndpoints = [
514
- resolved.start.point,
515
- resolved.mid,
516
- resolved.end.point
517
- ];
518
- }
519
- const interaction = editor.interaction;
520
- scene.drawOverlay({
521
- zoom: camera.zoom,
522
- pointerType: editor.pointerType,
523
- marquee: editor.marquee,
524
- selection: editor.selection,
525
- transform: editor.selectionTransform,
526
- controls: editor.tool === "select",
527
- hoveredControl: editor.hoveredTransformControl,
528
- rotationPointer: interaction.type === "rotating" ? interaction.current : null,
529
- arrowEndpoints,
530
- ports: editor.connectionPorts,
531
- hoveredPort: editor.hoveredConnectionPort,
532
- arrowDraft: null,
533
- bindTarget: editor.bindTargetFrame ?? editor.arrowDraft?.targetFrame ?? null
534
- }, current.palette);
535
- options.drawOverlay?.(overlay, {
536
- zoom: camera.zoom,
537
- theme: current,
538
- scene: renderScene
539
- });
540
- drawTransient(overlay, current);
605
+ drawInteraction();
541
606
  views.sync({
542
607
  ids,
543
608
  scene: renderScene,
@@ -809,6 +874,7 @@ function mountBoardStage(host, options) {
809
874
  if (active === next) return;
810
875
  active = next;
811
876
  if (!active) {
877
+ stopMarch();
812
878
  cancelScriptPointers();
813
879
  cancelAnimationFrame(renderFrame);
814
880
  cancelAnimationFrame(tickFrame);
@@ -836,6 +902,7 @@ function mountBoardStage(host, options) {
836
902
  toWorld: (clientX, clientY) => screenToWorld(clientX, clientY, rectOf(), renderCamera()),
837
903
  destroy() {
838
904
  disposed = true;
905
+ stopMarch();
839
906
  scriptPointers.clear();
840
907
  stopTransforms?.();
841
908
  scriptObservers.clear();