@neta-art/cohub 8.24.0 → 9.0.1

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 (159) hide show
  1. package/dist/board/animation.d.ts +57 -181
  2. package/dist/board/animation.js +374 -220
  3. package/dist/board/core/arrow-geometry.d.ts +33 -18
  4. package/dist/board/core/arrow-geometry.js +283 -52
  5. package/dist/board/core/draw-geometry.d.ts +22 -36
  6. package/dist/board/core/draw-geometry.js +148 -85
  7. package/dist/board/core/export-assets.d.ts +3 -6
  8. package/dist/board/core/export-assets.js +0 -2
  9. package/dist/board/core/export-plan.d.ts +12 -63
  10. package/dist/board/core/export-plan.js +55 -94
  11. package/dist/board/core/palette.d.ts +4 -21
  12. package/dist/board/core/palette.js +8 -13
  13. package/dist/board/core/scene.d.ts +33 -0
  14. package/dist/board/core/scene.js +302 -0
  15. package/dist/board/core/shape-definition.d.ts +12 -24
  16. package/dist/board/core/shape-definition.js +0 -19
  17. package/dist/board/core/shape-types.d.ts +6 -120
  18. package/dist/board/core/shape-types.js +5 -5
  19. package/dist/board/core/text-metrics.d.ts +8 -11
  20. package/dist/board/core/text-metrics.js +5 -34
  21. package/dist/board/core/tool-styles.d.ts +6 -16
  22. package/dist/board/core/tool-styles.js +17 -25
  23. package/dist/board/export/index.d.ts +10 -17
  24. package/dist/board/export/index.js +22 -21
  25. package/dist/board/export/scene.d.ts +10 -16
  26. package/dist/board/export/scene.js +17 -30
  27. package/dist/board/geometry.d.ts +27 -56
  28. package/dist/board/geometry.js +7 -67
  29. package/dist/board/headless/index.d.ts +6 -27
  30. package/dist/board/headless/index.js +4 -19
  31. package/dist/board/headless/sketch.d.ts +19 -0
  32. package/dist/board/headless/sketch.js +146 -0
  33. package/dist/board/image-key.d.ts +2 -12
  34. package/dist/board/image-key.js +3 -13
  35. package/dist/board/index.d.ts +18 -20
  36. package/dist/board/index.js +17 -17
  37. package/dist/board/media-playback.d.ts +3 -6
  38. package/dist/board/media-playback.js +6 -6
  39. package/dist/board/presets.d.ts +150 -0
  40. package/dist/board/presets.js +226 -0
  41. package/dist/board/render/board-background.d.ts +5 -3
  42. package/dist/board/render/board-background.js +8 -7
  43. package/dist/board/render/clip.d.ts +14 -0
  44. package/dist/board/render/clip.js +47 -0
  45. package/dist/board/render/index.d.ts +8 -5
  46. package/dist/board/render/index.js +7 -4
  47. package/dist/board/render/media-interaction.d.ts +4 -5
  48. package/dist/board/render/media-interaction.js +1 -2
  49. package/dist/board/render/palette.d.ts +6 -2
  50. package/dist/board/render/palette.js +7 -1
  51. package/dist/board/render/renderers/arrow-card-renderer.js +69 -71
  52. package/dist/board/render/renderers/audio-card-renderer.js +3 -3
  53. package/dist/board/render/renderers/base-card-renderer.d.ts +6 -17
  54. package/dist/board/render/renderers/base-card-renderer.js +1 -24
  55. package/dist/board/render/renderers/board-renderer-registry.d.ts +20 -50
  56. package/dist/board/render/renderers/board-renderer-registry.js +6 -7
  57. package/dist/board/render/renderers/draw-card-renderer.js +89 -56
  58. package/dist/board/render/renderers/effect-card-renderer.d.ts +6 -0
  59. package/dist/board/render/renderers/effect-card-renderer.js +197 -0
  60. package/dist/board/render/renderers/far-plate.d.ts +4 -21
  61. package/dist/board/render/renderers/far-plate.js +15 -34
  62. package/dist/board/render/renderers/file-card-renderer.d.ts +4 -25
  63. package/dist/board/render/renderers/file-card-renderer.js +8 -58
  64. package/dist/board/render/renderers/frame-card-renderer.js +28 -28
  65. package/dist/board/render/renderers/image-card-renderer.d.ts +4 -3
  66. package/dist/board/render/renderers/image-card-renderer.js +0 -9
  67. package/dist/board/render/renderers/shape-card-renderer.d.ts +9 -0
  68. package/dist/board/render/renderers/shape-card-renderer.js +240 -0
  69. package/dist/board/render/renderers/sketch-card-renderer.d.ts +5 -0
  70. package/dist/board/render/renderers/sketch-card-renderer.js +98 -0
  71. package/dist/board/render/renderers/task-card-renderer.d.ts +0 -1
  72. package/dist/board/render/renderers/task-card-renderer.js +14 -16
  73. package/dist/board/render/renderers/text-card-renderer.d.ts +1 -3
  74. package/dist/board/render/renderers/text-card-renderer.js +56 -46
  75. package/dist/board/render/renderers/unknown-card-renderer.js +4 -10
  76. package/dist/board/render/renderers/video-card-renderer.d.ts +4 -2
  77. package/dist/board/render/renderers/video-card-renderer.js +2 -1
  78. package/dist/board/render/sketch-host.d.ts +14 -0
  79. package/dist/board/render/sketch-host.js +216 -0
  80. package/dist/board/render/stroke.d.ts +12 -0
  81. package/dist/board/render/stroke.js +90 -0
  82. package/dist/board/render/text-measurement.d.ts +6 -26
  83. package/dist/board/render/text-measurement.js +11 -30
  84. package/dist/board/replay.d.ts +6 -31
  85. package/dist/board/replay.js +45 -203
  86. package/dist/board/runtime/board-player.d.ts +24 -0
  87. package/dist/board/runtime/board-player.js +161 -0
  88. package/dist/board/task.d.ts +2 -10
  89. package/dist/board/task.js +1 -10
  90. package/dist/chunks/environment.d.ts +2292 -3864
  91. package/dist/chunks/host-bridge.d.ts +253 -0
  92. package/dist/chunks/http.d.ts +233 -58
  93. package/dist/chunks/http.js +1046 -1028
  94. package/dist/chunks/transport.js +113 -59
  95. package/dist/chunks/websocket.d.ts +1 -1
  96. package/dist/chunks/websocket.js +1 -1
  97. package/dist/host-bridge.d.ts +54 -0
  98. package/dist/host-bridge.js +322 -0
  99. package/dist/http.d.ts +3 -3
  100. package/dist/index.d.ts +74 -191
  101. package/dist/index.js +2032 -1186
  102. package/dist/protocol/dist/board-constants.d.ts +1 -29
  103. package/dist/protocol/dist/board-constants.js +2 -115
  104. package/dist/protocol/dist/board-layout.d.ts +59 -0
  105. package/dist/protocol/dist/board-layout.js +482 -0
  106. package/dist/protocol/dist/board-model.d.ts +2052 -0
  107. package/dist/protocol/dist/board-model.js +592 -0
  108. package/dist/protocol/dist/board-patch.d.ts +86 -0
  109. package/dist/protocol/dist/board-patch.js +734 -0
  110. package/dist/protocol/dist/board-schema.d.ts +33 -0
  111. package/dist/protocol/dist/board-schema.js +59 -0
  112. package/dist/protocol/dist/board-upgrade.d.ts +1 -0
  113. package/dist/protocol/dist/board-url.d.ts +1 -3
  114. package/dist/protocol/dist/board-url.js +2 -2
  115. package/dist/protocol/dist/board.d.ts +37 -232
  116. package/dist/protocol/dist/board.js +62 -160
  117. package/dist/protocol/dist/display/actions.d.ts +1 -0
  118. package/dist/protocol/dist/display/index.d.ts +2 -0
  119. package/dist/protocol/dist/index.d.ts +11 -12
  120. package/dist/protocol/dist/model/metrics.d.ts +2 -0
  121. package/dist/protocol/dist/model/session.d.ts +2 -1
  122. package/dist/protocol/dist/realtime/types.d.ts +2 -2
  123. package/dist/protocol/dist/runtime/index.d.ts +1 -0
  124. package/dist/space-picker.d.ts +1 -0
  125. package/dist/space-picker.js +1 -0
  126. package/dist/types.d.ts +2 -5
  127. package/package.json +15 -7
  128. package/dist/board/core/connections.d.ts +0 -108
  129. package/dist/board/core/connections.js +0 -455
  130. package/dist/board/mutation.d.ts +0 -6
  131. package/dist/board/mutation.js +0 -14
  132. package/dist/board/render/connection-layer.d.ts +0 -53
  133. package/dist/board/render/connection-layer.js +0 -375
  134. package/dist/board/render/renderers/geo-card-renderer.d.ts +0 -5
  135. package/dist/board/render/renderers/geo-card-renderer.js +0 -127
  136. package/dist/board/semantic-document.d.ts +0 -37
  137. package/dist/board/semantic-document.js +0 -288
  138. package/dist/board/semantic-mutation.d.ts +0 -10
  139. package/dist/board/semantic-mutation.js +0 -209
  140. package/dist/protocol/dist/board-animation.js +0 -34
  141. package/dist/protocol/dist/board-authoring.d.ts +0 -1326
  142. package/dist/protocol/dist/board-authoring.js +0 -302
  143. package/dist/protocol/dist/board-capability-registry.d.ts +0 -2
  144. package/dist/protocol/dist/board-capability-registry.js +0 -123
  145. package/dist/protocol/dist/board-codec.d.ts +0 -3
  146. package/dist/protocol/dist/board-codec.js +0 -383
  147. package/dist/protocol/dist/board-composition.d.ts +0 -383
  148. package/dist/protocol/dist/board-composition.js +0 -310
  149. package/dist/protocol/dist/board-connection.d.ts +0 -317
  150. package/dist/protocol/dist/board-connection.js +0 -219
  151. package/dist/protocol/dist/board-document.d.ts +0 -1566
  152. package/dist/protocol/dist/board-document.js +0 -456
  153. package/dist/protocol/dist/board-effect.d.ts +0 -96
  154. package/dist/protocol/dist/board-effect.js +0 -54
  155. package/dist/protocol/dist/board-geometry.d.ts +0 -15
  156. package/dist/protocol/dist/board-geometry.js +0 -137
  157. package/dist/protocol/dist/board-node.d.ts +0 -8
  158. package/dist/protocol/dist/board-node.js +0 -252
  159. /package/dist/protocol/dist/{board-animation.d.ts → host-bridge.d.ts} +0 -0
@@ -1,10 +1,4 @@
1
1
  //#region src/board/core/palette.ts
2
- /**
3
- * Label color fallbacks mirror `--board-color-*-label`, which every theme maps
4
- * to `--text-primary`: labels sit on a *translucent* fill over the page, not on
5
- * a saturated swatch, so a tinted label would be dark-on-dark. Keeping these in
6
- * step with `theme.css` is what makes a headless export match the screen.
7
- */
8
2
  const LABEL_DARK = 16053492;
9
3
  const LABEL_LIGHT = 1579035;
10
4
  const BOARD_COLORS = [
@@ -143,24 +137,25 @@ function isBoardColorId(value) {
143
137
  function boardColorCssVar(id, part) {
144
138
  return `--board-color-${id}-${part}`;
145
139
  }
146
- /** Resolve a color id to concrete values for a color mode. Unknown → brand. */
147
140
  function resolveBoardColor(id, mode) {
148
141
  return ((isBoardColorId(id) ? COLOR_INDEX.get(id) : void 0) ?? COLOR_INDEX.get("brand"))[mode];
149
142
  }
150
- /** Build a full shape-color table from hard-coded fallbacks (export / SSR). */
151
143
  function buildFallbackShapeColors(mode) {
152
144
  const out = {};
153
145
  for (const entry of BOARD_COLORS) out[entry.id] = entry[mode];
154
146
  return out;
155
147
  }
156
- /**
157
- * Pick a concrete color from a live shape-color table, falling back to the
158
- * default brand entry when the id is unknown.
159
- */
160
148
  function pickBoardColor(colors, id, mode = "dark") {
161
149
  if (colors && isBoardColorId(id) && colors[id]) return colors[id];
162
150
  if (colors) return colors[DEFAULT_BOARD_COLOR];
163
151
  return resolveBoardColor(id, mode);
164
152
  }
153
+ function resolveItemColor(color, fallback, colors, mode, parseCss, part = "stroke") {
154
+ const value = typeof color === "object" && color ? color[mode] : color;
155
+ if (value === void 0) return pickBoardColor(colors, fallback, mode)[part];
156
+ const token = value.trim().toLowerCase();
157
+ if (isBoardColorId(token)) return pickBoardColor(colors, token, mode)[part];
158
+ return parseCss(value) ?? pickBoardColor(colors, fallback, mode)[part];
159
+ }
165
160
  //#endregion
166
- export { BOARD_COLORS, DEFAULT_BOARD_COLOR, boardColorCssVar, buildFallbackShapeColors, isBoardColorId, pickBoardColor, resolveBoardColor };
161
+ export { BOARD_COLORS, DEFAULT_BOARD_COLOR, boardColorCssVar, buildFallbackShapeColors, isBoardColorId, pickBoardColor, resolveBoardColor, resolveItemColor };
@@ -0,0 +1,33 @@
1
+ import { BoardDocument, BoardItem } from "../../protocol/dist/board-model.js";
2
+ import { BoardFrame, BoardItemLookup, BoardLayout, BoardMatrix, BoardRect } from "../../protocol/dist/board-layout.js";
3
+ import "../../protocol/dist/index.js";
4
+ //#region src/board/core/scene.d.ts
5
+ type SceneItem<T extends BoardItem = BoardItem> = T & {
6
+ readonly id: string;
7
+ readonly frame: BoardFrame;
8
+ };
9
+ type BoardSceneItem = SceneItem;
10
+ type BoardScene = {
11
+ readonly items: readonly BoardSceneItem[];
12
+ get(id: string): BoardSceneItem | undefined;
13
+ indexOf(id: string): number;
14
+ readonly layout: BoardLayout;
15
+ children(id: string | undefined): readonly string[];
16
+ descendants(id: string): string[];
17
+ binders(id: string): readonly string[];
18
+ opacity(id: string): number;
19
+ };
20
+ declare function orderBoardItems(items: Readonly<Record<string, BoardItem>>): string[];
21
+ declare function buildBoardScene(document: BoardDocument, previous?: BoardScene | null): BoardScene;
22
+ declare function refreshBoardScene(previous: BoardScene, lookup: BoardItemLookup, ids: Iterable<string>): BoardScene;
23
+ declare function toSceneItem(id: string, item: BoardItem, layout: BoardLayout): BoardSceneItem;
24
+ declare function sceneItemToItem(scene: BoardSceneItem | (BoardItem & {
25
+ id: string;
26
+ frame?: BoardFrame;
27
+ }), layout: BoardLayout): BoardItem;
28
+ declare function placeItem(item: BoardItem, frame: BoardFrame, parent: BoardMatrix, box: BoardRect): BoardItem;
29
+ declare function sceneItemsToRecord(items: readonly (BoardSceneItem | (BoardItem & {
30
+ id: string;
31
+ }))[], layout: BoardLayout): Record<string, BoardItem>;
32
+ //#endregion
33
+ export { BoardScene, BoardSceneItem, SceneItem, buildBoardScene, orderBoardItems, placeItem, refreshBoardScene, sceneItemToItem, sceneItemsToRecord, toSceneItem };
@@ -0,0 +1,302 @@
1
+ import { IDENTITY_MATRIX, applyMatrix, arrowBindings, clampBoardFontSize, createBoardLayout, drawPointsBounds, invertMatrix, layoutBoardText, scaleVector } from "../../protocol/dist/board-layout.js";
2
+ //#region src/board/core/scene.ts
3
+ const EPSILON = 1e-6;
4
+ function stripScene(item) {
5
+ if (!("frame" in item) && !("id" in item)) return item;
6
+ const { id: _id, frame: _frame, ...rest } = item;
7
+ return rest;
8
+ }
9
+ function orderBoardItems(items) {
10
+ const children = /* @__PURE__ */ new Map();
11
+ for (const [id, item] of Object.entries(items)) {
12
+ const parent = item.parent && items[item.parent] && item.parent !== id ? item.parent : "";
13
+ const list = children.get(parent) ?? [];
14
+ list.push(id);
15
+ children.set(parent, list);
16
+ }
17
+ const compare = (a, b) => (items[a]?.z ?? 0) - (items[b]?.z ?? 0) || (a < b ? -1 : a > b ? 1 : 0);
18
+ for (const list of children.values()) list.sort(compare);
19
+ const order = [];
20
+ const seen = /* @__PURE__ */ new Set();
21
+ const visit = (id) => {
22
+ if (seen.has(id)) return;
23
+ seen.add(id);
24
+ order.push(id);
25
+ for (const child of children.get(id) ?? []) visit(child);
26
+ };
27
+ for (const id of children.get("") ?? []) visit(id);
28
+ for (const id of Object.keys(items)) visit(id);
29
+ return order;
30
+ }
31
+ function buildBoardScene(document, previous) {
32
+ const items = document.items;
33
+ const children = /* @__PURE__ */ new Map();
34
+ const binders = /* @__PURE__ */ new Map();
35
+ for (const [id, item] of Object.entries(items)) {
36
+ const parent = item.parent && items[item.parent] ? item.parent : "";
37
+ const list = children.get(parent) ?? [];
38
+ list.push(id);
39
+ children.set(parent, list);
40
+ for (const bound of arrowBindings(item)) binders.set(bound, [...binders.get(bound) ?? [], id]);
41
+ }
42
+ const layout = createBoardLayout((id) => items[id], (id) => [...children.get(id) ?? [], ...binders.get(id) ?? []]);
43
+ const stable = /* @__PURE__ */ new Map();
44
+ const isStable = (id, depth = 0) => {
45
+ const known = stable.get(id);
46
+ if (known !== void 0) return known;
47
+ const item = items[id];
48
+ const before = previous?.get(id);
49
+ let result = Boolean(item && before && stripSceneSource(before) === item) && depth < 64;
50
+ if (result && item?.parent && items[item.parent]) result = isStable(item.parent, depth + 1);
51
+ if (result && item) {
52
+ for (const bound of arrowBindings(item)) if (items[bound] && !isStable(bound, depth + 1)) result = false;
53
+ }
54
+ stable.set(id, result);
55
+ return result;
56
+ };
57
+ const ordered = orderBoardItems(items);
58
+ const sceneItems = [];
59
+ const byId = /* @__PURE__ */ new Map();
60
+ const indexById = /* @__PURE__ */ new Map();
61
+ for (const id of ordered) {
62
+ const item = items[id];
63
+ const scene = (isStable(id) ? previous?.get(id) : void 0) ?? toSceneItem(id, item, layout);
64
+ indexById.set(id, sceneItems.length);
65
+ sceneItems.push(scene);
66
+ byId.set(id, scene);
67
+ }
68
+ return {
69
+ items: sceneItems,
70
+ get: (id) => byId.get(id),
71
+ indexOf: (id) => indexById.get(id) ?? -1,
72
+ layout,
73
+ children: (id) => children.get(id ?? "") ?? [],
74
+ descendants: (id) => {
75
+ const result = [];
76
+ const seen = /* @__PURE__ */ new Set([id]);
77
+ const stack = [...children.get(id) ?? []];
78
+ while (stack.length) {
79
+ const next = stack.pop();
80
+ if (seen.has(next)) continue;
81
+ seen.add(next);
82
+ result.push(next);
83
+ stack.push(...children.get(next) ?? []);
84
+ }
85
+ return result;
86
+ },
87
+ binders: (id) => binders.get(id) ?? [],
88
+ opacity: (id) => cumulativeOpacity((key) => items[key], id)
89
+ };
90
+ }
91
+ function cumulativeOpacity(lookup, id) {
92
+ let value = 1;
93
+ let cursor = id;
94
+ for (let depth = 0; cursor && depth < 64; depth += 1) {
95
+ const item = lookup(cursor);
96
+ if (!item) break;
97
+ value *= item.opacity;
98
+ cursor = item.parent;
99
+ }
100
+ return value;
101
+ }
102
+ function refreshBoardScene(previous, lookup, ids) {
103
+ const changedIds = [...ids];
104
+ if (changedIds.length === 0) return previous;
105
+ const dependents = (id) => [...previous.children(id), ...previous.binders(id)];
106
+ const layout = previous.layout;
107
+ layout.setLookup(lookup);
108
+ layout.invalidate(changedIds);
109
+ const affected = /* @__PURE__ */ new Set();
110
+ const stack = [...changedIds];
111
+ while (stack.length) {
112
+ const id = stack.pop();
113
+ if (affected.has(id) || !lookup(id)) continue;
114
+ affected.add(id);
115
+ stack.push(...dependents(id));
116
+ }
117
+ if (affected.size === 0) return previous;
118
+ const sceneItems = previous.items.slice();
119
+ const chain = refreshed.get(previous);
120
+ const base = chain?.base ?? previous;
121
+ const overrides = new Map(chain?.overrides);
122
+ for (const id of affected) {
123
+ const index = previous.indexOf(id);
124
+ const scene = toSceneItem(id, lookup(id), layout);
125
+ overrides.set(id, scene);
126
+ if (index >= 0) sceneItems[index] = scene;
127
+ }
128
+ const scene = {
129
+ ...base,
130
+ items: sceneItems,
131
+ get: (id) => overrides.get(id) ?? base.get(id),
132
+ layout,
133
+ opacity: (id) => cumulativeOpacity(lookup, id)
134
+ };
135
+ refreshed.set(scene, {
136
+ base,
137
+ overrides
138
+ });
139
+ return scene;
140
+ }
141
+ const refreshed = /* @__PURE__ */ new WeakMap();
142
+ const sources = /* @__PURE__ */ new WeakMap();
143
+ function stripSceneSource(scene) {
144
+ return sources.get(scene);
145
+ }
146
+ function toSceneItem(id, item, layout) {
147
+ const scene = {
148
+ ...item,
149
+ id,
150
+ frame: layout.frame(id)
151
+ };
152
+ sources.set(scene, item);
153
+ return scene;
154
+ }
155
+ function sameFrame(a, b) {
156
+ return Math.abs(a.x - b.x) < EPSILON && Math.abs(a.y - b.y) < EPSILON && Math.abs(a.width - b.width) < EPSILON && Math.abs(a.height - b.height) < EPSILON && Math.abs(a.rotation - b.rotation) < EPSILON;
157
+ }
158
+ function sceneItemToItem(scene, layout) {
159
+ const source = sources.get(scene);
160
+ if (source) return source;
161
+ const item = stripScene(scene);
162
+ const frame = scene.frame;
163
+ if (!frame) return item;
164
+ if (layout.has(scene.id) && sameFrame(frame, layout.frame(scene.id))) return item;
165
+ return placeItem(item, frame, item.parent && layout.has(item.parent) ? layout.matrix(item.parent) : IDENTITY_MATRIX, itemBox(item, layout, scene.id));
166
+ }
167
+ function itemBox(item, layout, id) {
168
+ if ("size" in item && item.size) return {
169
+ x: 0,
170
+ y: 0,
171
+ width: item.size.width,
172
+ height: item.size.height
173
+ };
174
+ if (item.type === "text") {
175
+ const text = layoutBoardText(item.props);
176
+ return {
177
+ x: 0,
178
+ y: 0,
179
+ width: text.width,
180
+ height: text.height
181
+ };
182
+ }
183
+ if (item.type === "draw") return drawPointsBounds(item.props.points, item.style.strokeWidth ?? 4);
184
+ return layout.box(id);
185
+ }
186
+ function placeItem(item, frame, parent, box) {
187
+ const [a, b] = parent;
188
+ const parentScale = Math.hypot(a, b) || 1;
189
+ const parentRotation = Math.atan2(b, a) * 180 / Math.PI;
190
+ const center = applyMatrix(invertMatrix(parent), {
191
+ x: frame.x + frame.width / 2,
192
+ y: frame.y + frame.height / 2
193
+ });
194
+ const scale = scaleVector(item.scale);
195
+ const width = frame.width / parentScale / (Math.abs(scale.x) || 1);
196
+ const height = frame.height / parentScale / (Math.abs(scale.y) || 1);
197
+ if (item.type === "arrow") {
198
+ const currentCenter = {
199
+ x: item.position.x + box.x + box.width / 2,
200
+ y: item.position.y + box.y + box.height / 2
201
+ };
202
+ const dx = center.x - currentCenter.x;
203
+ const dy = center.y - currentCenter.y;
204
+ if (Math.abs(dx) < EPSILON && Math.abs(dy) < EPSILON) return item;
205
+ return {
206
+ ...item,
207
+ position: {
208
+ x: item.position.x + dx,
209
+ y: item.position.y + dy
210
+ }
211
+ };
212
+ }
213
+ let next = item;
214
+ let nextBox = box;
215
+ if (Math.abs(width - box.width) > EPSILON || Math.abs(height - box.height) > EPSILON) {
216
+ if (item.type === "text") {
217
+ const factor = Math.abs(width - box.width) > EPSILON ? width / box.width : height / box.height;
218
+ const props = {
219
+ ...item.props,
220
+ fontSize: clampBoardFontSize(item.props.fontSize * factor),
221
+ ...item.props.width ? { width: item.props.width * factor } : {}
222
+ };
223
+ next = {
224
+ ...item,
225
+ props
226
+ };
227
+ const text = layoutBoardText(props);
228
+ nextBox = {
229
+ x: 0,
230
+ y: 0,
231
+ width: text.width,
232
+ height: text.height
233
+ };
234
+ } else if (item.type === "draw") {
235
+ const sx = width / box.width;
236
+ const sy = height / box.height;
237
+ const points = item.props.points.map((point) => ({
238
+ ...point,
239
+ x: box.x + (point.x - box.x) * sx,
240
+ y: box.y + (point.y - box.y) * sy
241
+ }));
242
+ next = {
243
+ ...item,
244
+ props: {
245
+ ...item.props,
246
+ points
247
+ }
248
+ };
249
+ nextBox = drawPointsBounds(points, item.style.strokeWidth ?? 4);
250
+ } else if ("size" in item && item.size) {
251
+ next = {
252
+ ...item,
253
+ size: {
254
+ width,
255
+ height
256
+ }
257
+ };
258
+ nextBox = {
259
+ x: 0,
260
+ y: 0,
261
+ width,
262
+ height
263
+ };
264
+ }
265
+ }
266
+ const rotation = frame.rotation - parentRotation;
267
+ const radians = rotation * Math.PI / 180;
268
+ const origin = {
269
+ x: nextBox.x + item.origin.x * nextBox.width,
270
+ y: nextBox.y + item.origin.y * nextBox.height
271
+ };
272
+ const boxCenter = {
273
+ x: nextBox.x + nextBox.width / 2,
274
+ y: nextBox.y + nextBox.height / 2
275
+ };
276
+ const dx = (boxCenter.x - origin.x) * scale.x;
277
+ const dy = (boxCenter.y - origin.y) * scale.y;
278
+ const rotated = {
279
+ x: dx * Math.cos(radians) - dy * Math.sin(radians),
280
+ y: dx * Math.sin(radians) + dy * Math.cos(radians)
281
+ };
282
+ const position = {
283
+ x: center.x - origin.x - rotated.x,
284
+ y: center.y - origin.y - rotated.y
285
+ };
286
+ return {
287
+ ...next,
288
+ position,
289
+ rotation: normalizeDegrees(rotation)
290
+ };
291
+ }
292
+ function normalizeDegrees(value) {
293
+ const normalized = (value % 360 + 540) % 360 - 180;
294
+ return Math.abs(normalized) < EPSILON ? 0 : Number(normalized.toFixed(6));
295
+ }
296
+ function sceneItemsToRecord(items, layout) {
297
+ const record = {};
298
+ for (const scene of items) record[scene.id] = sceneItemToItem(scene, layout);
299
+ return record;
300
+ }
301
+ //#endregion
302
+ export { buildBoardScene, orderBoardItems, placeItem, refreshBoardScene, sceneItemToItem, sceneItemsToRecord, toSceneItem };
@@ -1,35 +1,23 @@
1
- import { BoardFrame, BoardItem } from "../../protocol/dist/board-document.js";
2
1
  import { Rect, WorldPoint } from "../geometry.js";
2
+ import { BoardSceneItem } from "./scene.js";
3
3
  import { ShapeCapabilities, ShapeGeometry, ShapeHandle, ShapeResizeMode } from "./shape-types.js";
4
4
  //#region src/board/core/shape-definition.d.ts
5
5
  type ShapeDefinition = {
6
- /** The item type this definition handles. */
7
6
  type: string;
8
7
  capabilities: ShapeCapabilities;
9
- /** Rotation-aware world bounds (culling, marquee). Defaults to itemBounds. */
10
- getBounds?: (item: BoardItem) => Rect;
11
- /** Exact world-space containment (hit testing). Defaults to rotated rect. */
12
- hitTest?: (item: BoardItem, point: WorldPoint) => boolean;
13
- /** Interaction handles in the shape's local space. Defaults to none. */
14
- getHandles?: (item: BoardItem) => ShapeHandle[];
15
- /**
16
- * Local-space geometry outline, used for precise hit tests and snapping.
17
- * Optional; box shapes rely on the frame directly.
18
- */
19
- getGeometry?: (item: BoardItem) => ShapeGeometry;
8
+ getBounds?: (item: BoardSceneItem) => Rect;
9
+ hitTest?: (item: BoardSceneItem, point: WorldPoint) => boolean;
10
+ getHandles?: (item: BoardSceneItem) => ShapeHandle[];
11
+ getGeometry?: (item: BoardSceneItem) => ShapeGeometry;
20
12
  };
21
13
  declare function registerShapeDefinition(definition: ShapeDefinition): void;
22
14
  declare function getShapeDefinition(type: string): ShapeDefinition | undefined;
23
- /**
24
- * The generic fallback for unknown shape types: a plain movable/resizable box.
25
- * This guarantees a shape authored by a newer client is still interactive here.
26
- */
27
15
  declare const unknownShapeDefinition: ShapeDefinition;
28
- declare function definitionForItem(item: BoardItem): ShapeDefinition;
29
- declare function shapeBounds(item: BoardItem): Rect;
30
- declare function shapeHitTest(item: BoardItem, point: WorldPoint): boolean;
31
- declare function shapeHandles(item: BoardItem): ShapeHandle[];
32
- declare function shapeCapabilities(item: BoardItem): ShapeCapabilities;
33
- declare function shapeResizeMode(item: BoardItem): ShapeResizeMode;
16
+ declare function definitionForItem(item: BoardSceneItem): ShapeDefinition;
17
+ declare function shapeBounds(item: BoardSceneItem): Rect;
18
+ declare function shapeHitTest(item: BoardSceneItem, point: WorldPoint): boolean;
19
+ declare function shapeHandles(item: BoardSceneItem): ShapeHandle[];
20
+ declare function shapeCapabilities(item: BoardSceneItem): ShapeCapabilities;
21
+ declare function shapeResizeMode(item: BoardSceneItem): ShapeResizeMode;
34
22
  //#endregion
35
- export { type BoardFrame, ShapeDefinition, definitionForItem, getShapeDefinition, registerShapeDefinition, shapeBounds, shapeCapabilities, shapeHandles, shapeHitTest, shapeResizeMode, unknownShapeDefinition };
23
+ export { ShapeDefinition, definitionForItem, getShapeDefinition, registerShapeDefinition, shapeBounds, shapeCapabilities, shapeHandles, shapeHitTest, shapeResizeMode, unknownShapeDefinition };
@@ -1,20 +1,6 @@
1
- import { isUnknownItem } from "../../protocol/dist/board-document.js";
2
1
  import { frameContainsPoint, itemBounds } from "../geometry.js";
3
2
  import { resizeModeForCapabilities } from "./shape-types.js";
4
3
  //#region src/board/core/shape-definition.ts
5
- /**
6
- * ShapeDefinition protocol + registry.
7
- *
8
- * A ShapeDefinition describes everything the editor needs to *behave* correctly
9
- * around a shape — bounds, hit testing, handles, capabilities, snap targets —
10
- * with no knowledge of how the shape is drawn. The Pixi renderers are a separate
11
- * concern (board-renderer-registry). This separation is what lets us add a shape
12
- * by writing one definition + one renderer, and keeps geometry fully unit-testable
13
- * without a GPU.
14
- *
15
- * Definitions are looked up by `item.type`. Unknown types fall back to a generic
16
- * box definition so unrecognised shapes still select, move and resize.
17
- */
18
4
  const definitions = /* @__PURE__ */ new Map();
19
5
  function registerShapeDefinition(definition) {
20
6
  definitions.set(definition.type, definition);
@@ -22,10 +8,6 @@ function registerShapeDefinition(definition) {
22
8
  function getShapeDefinition(type) {
23
9
  return definitions.get(type);
24
10
  }
25
- /**
26
- * The generic fallback for unknown shape types: a plain movable/resizable box.
27
- * This guarantees a shape authored by a newer client is still interactive here.
28
- */
29
11
  const unknownShapeDefinition = {
30
12
  type: "__unknown__",
31
13
  capabilities: {
@@ -40,7 +22,6 @@ const unknownShapeDefinition = {
40
22
  }
41
23
  };
42
24
  function definitionForItem(item) {
43
- if (isUnknownItem(item)) return unknownShapeDefinition;
44
25
  return definitions.get(item.type) ?? unknownShapeDefinition;
45
26
  }
46
27
  function shapeBounds(item) {
@@ -1,152 +1,38 @@
1
- import { BoardGeoKind } from "../../protocol/dist/board-node.js";
2
- import { BoardFrame } from "../../protocol/dist/board-document.js";
1
+ import { BoardShapeGeometry } from "../../protocol/dist/board-model.js";
2
+ import { BoardFrame } from "../../protocol/dist/board-layout.js";
3
3
  import "../../protocol/dist/index.js";
4
4
  import { Rect, WorldPoint } from "../geometry.js";
5
5
  //#region src/board/core/shape-types.d.ts
6
- /**
7
- * A shape's geometry, expressed in the shape's local space (origin at the
8
- * frame's top-left, unrotated). Hit testing and handles are derived from this,
9
- * so a shape only declares its outline once.
10
- */
11
6
  type ShapeGeometry = {
12
- /** Axis-aligned bounds in local space (usually the full frame). */
13
7
  bounds: Rect;
14
- /**
15
- * Exact point containment test in *world* space. Receives the shape frame so
16
- * it can account for rotation. Defaults to the rotated-rect test when absent.
17
- */
18
8
  containsWorldPoint?: (frame: BoardFrame, point: WorldPoint) => boolean;
19
9
  };
20
10
  type ShapeHandleId = string;
21
11
  type ShapeHandle = {
22
12
  id: ShapeHandleId;
23
- /** Position in the shape's local (unrotated, frame-origin) space. */
24
13
  x: number;
25
14
  y: number;
26
- /** Visual + hit radius hint in screen px; the stage scales by zoom. */
27
15
  radius?: number;
28
16
  };
29
- /** Result of dragging a handle: a patched frame and/or shape props. */
30
17
  type HandleDragResult = {
31
18
  frame?: BoardFrame;
32
19
  props?: Record<string, unknown>;
33
20
  };
34
- /**
35
- * What a shape supports. The editor, selection toolbar and tools read these so
36
- * behaviour is data-driven: adding a shape never requires editing the editor.
37
- */
38
21
  type ShapeCapabilities = {
39
- /** Can be moved by dragging. */
40
22
  canMove: boolean;
41
- /** Shows resize handles. */
42
23
  canResize: boolean;
43
- /**
44
- * Resize always preserves the shape's aspect ratio, regardless of Shift.
45
- * True for shapes whose content has a single intrinsic scale (text font size,
46
- * media pixels, stroke geometry) — distorting the frame would either letterbox
47
- * the content or have no representation in the data model.
48
- */
49
24
  aspectLocked: boolean;
50
- /** Shows the rotation handle. */
51
25
  canRotate: boolean;
52
- /** Supports double-click inline editing. */
53
26
  canEdit: boolean;
54
- /**
55
- * Can be an endpoint of a connection.
56
- *
57
- * False for shapes that are themselves annotation strokes (draw, arrow): a
58
- * relation between two scribbles has no meaning the model could express, and
59
- * allowing it would produce edges no reader could interpret.
60
- */
61
27
  canConnect: boolean;
62
- /** Participates in snapping as a target. */
63
28
  canSnap: boolean;
64
- /** Can be locked against accidental edits. */
65
29
  canLock: boolean;
66
30
  };
67
- /**
68
- * Effective resize behaviour derived from the backwards-compatible capability
69
- * flags. Consumers should use this mode instead of repeating the flag matrix.
70
- */
71
31
  type ShapeResizeMode = "none" | "uniform" | "free";
72
32
  declare function resizeModeForCapabilities(capabilities: ShapeCapabilities): ShapeResizeMode;
73
33
  declare const FULL_CAPABILITIES: ShapeCapabilities;
74
- /**
75
- * A single raw input sample of a freehand stroke. We persist the *raw* samples
76
- * (not just a simplified path) so strokes can be re-smoothed, re-simplified or
77
- * re-rendered at any LOD later without losing information — data first.
78
- */
79
- type DrawPoint = {
80
- x: number;
81
- y: number;
82
- /** Pen pressure 0..1; 0.5 when unavailable (mouse). */
83
- p: number;
84
- };
85
- type DrawShapeProps = {
86
- /** Raw samples in the shape's local space. */
87
- points: DrawPoint[];
88
- color: string;
89
- /** Stroke width in world units. */
90
- size: number;
91
- };
92
- type GeoKind = BoardGeoKind;
93
- declare const GEO_KINDS: readonly GeoKind[];
94
- declare function isGeoKind(value: unknown): value is GeoKind;
95
- type GeoShapeProps = {
96
- geo: GeoKind;
97
- /** Optional label rendered centered inside the shape. */
98
- text: string;
99
- color: string;
100
- /** Fill opacity 0..1 (0 = outline only). */
101
- fillOpacity: number;
102
- };
103
- /**
104
- * An arrow endpoint: either a free point (local space) or a binding to another
105
- * shape. Bindings use a normalized anchor (0..1 of the target's frame) so the
106
- * arrow tracks the target through move/resize without storing absolute coords.
107
- */
108
- type ArrowEndpoint = {
109
- kind: "point";
110
- x: number;
111
- y: number;
112
- } | {
113
- kind: "binding";
114
- /** Target shape id. */
115
- target: string;
116
- /** Normalized anchor on the target frame (0..1). */
117
- nx: number;
118
- ny: number;
119
- /** Whether the binding snaps to the nearest edge/center. */
120
- precise: boolean;
121
- };
122
- type ArrowShapeProps = {
123
- start: ArrowEndpoint;
124
- end: ArrowEndpoint;
125
- /** Bend of a curved arrow, as a fraction of the length (-0.5..0.5). */
126
- bend: number;
127
- color: string;
128
- size: number;
129
- /** Arrowhead placement. */
130
- arrowStart: boolean;
131
- arrowEnd: boolean;
132
- label: string;
133
- };
134
- type TextShapeProps = {
135
- text: string;
136
- color: string;
137
- fontSize: number;
138
- };
139
- type ImageShapeProps = {
140
- path: string;
141
- mimeType?: string;
142
- };
143
- type VideoShapeProps = {
144
- path: string;
145
- mimeType?: string;
146
- };
147
- type AudioShapeProps = {
148
- path: string;
149
- mimeType?: string;
150
- };
34
+ type ShapeKind = Exclude<BoardShapeGeometry, "path">;
35
+ declare const SHAPE_KINDS: ShapeKind[];
36
+ declare function isShapeKind(value: unknown): value is ShapeKind;
151
37
  //#endregion
152
- export { ArrowEndpoint, ArrowShapeProps, AudioShapeProps, DrawPoint, DrawShapeProps, FULL_CAPABILITIES, GEO_KINDS, GeoKind, GeoShapeProps, HandleDragResult, ImageShapeProps, ShapeCapabilities, ShapeGeometry, ShapeHandle, ShapeHandleId, ShapeResizeMode, TextShapeProps, VideoShapeProps, isGeoKind, resizeModeForCapabilities };
38
+ export { FULL_CAPABILITIES, HandleDragResult, SHAPE_KINDS, ShapeCapabilities, ShapeGeometry, ShapeHandle, ShapeHandleId, ShapeKind, ShapeResizeMode, isShapeKind, resizeModeForCapabilities };
@@ -1,4 +1,4 @@
1
- import { BOARD_GEO_KINDS } from "../../protocol/dist/board-node.js";
1
+ import { BOARD_SHAPE_GEOMETRIES } from "../../protocol/dist/board-model.js";
2
2
  //#region src/board/core/shape-types.ts
3
3
  function resizeModeForCapabilities(capabilities) {
4
4
  if (!capabilities.canResize) return "none";
@@ -14,9 +14,9 @@ const FULL_CAPABILITIES = {
14
14
  canSnap: true,
15
15
  canLock: true
16
16
  };
17
- const GEO_KINDS = BOARD_GEO_KINDS;
18
- function isGeoKind(value) {
19
- return typeof value === "string" && GEO_KINDS.includes(value);
17
+ const SHAPE_KINDS = BOARD_SHAPE_GEOMETRIES.filter((kind) => kind !== "path");
18
+ function isShapeKind(value) {
19
+ return typeof value === "string" && SHAPE_KINDS.includes(value);
20
20
  }
21
21
  //#endregion
22
- export { FULL_CAPABILITIES, GEO_KINDS, isGeoKind, resizeModeForCapabilities };
22
+ export { FULL_CAPABILITIES, SHAPE_KINDS, isShapeKind, resizeModeForCapabilities };
@@ -1,18 +1,15 @@
1
+ import { clampBoardFontSize } from "../../protocol/dist/board-layout.js";
2
+ import "../../protocol/dist/index.js";
1
3
  //#region src/board/core/text-metrics.d.ts
2
4
  declare const TEXT_FONT_FAMILY = "Geist";
3
5
  declare const TEXT_FONT_SIZE = 24;
4
6
  declare const TEXT_LINE_HEIGHT = 32;
5
7
  declare const TEXT_MIN_FONT_SIZE = 2;
6
8
  declare const TEXT_MAX_FONT_SIZE = 512;
7
- type BoardTextMeasurer = (text: string, fontSize: number) => number | null;
8
- /** Install the renderer's text measurer, or clear it for environment-free fallback metrics. */
9
- declare function setBoardTextMeasurer(measurer?: BoardTextMeasurer): void;
10
- declare function clampBoardTextFontSize(fontSize: number): number;
11
- declare function boardTextLineHeight(fontSize: number): number;
12
- /** Measure unwrapped plain text into scalable world-space bounds. */
13
- declare function measureBoardText(text: string, fontSize?: number): {
14
- width: number;
15
- height: number;
16
- };
9
+ declare function boardFontFamily(font: string, stacks: {
10
+ sans: string;
11
+ serif: string;
12
+ mono: string;
13
+ }): string;
17
14
  //#endregion
18
- export { TEXT_FONT_FAMILY, TEXT_FONT_SIZE, TEXT_LINE_HEIGHT, TEXT_MAX_FONT_SIZE, TEXT_MIN_FONT_SIZE, boardTextLineHeight, clampBoardTextFontSize, measureBoardText, setBoardTextMeasurer };
15
+ export { TEXT_FONT_FAMILY, TEXT_FONT_SIZE, TEXT_LINE_HEIGHT, TEXT_MAX_FONT_SIZE, TEXT_MIN_FONT_SIZE, boardFontFamily, clampBoardFontSize as clampBoardTextFontSize };