@neta-art/cohub 5.3.3 → 5.4.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 (43) hide show
  1. package/dist/board/codec.d.ts +2 -0
  2. package/dist/board/codec.js +18 -13
  3. package/dist/board/core/arrow-geometry.d.ts +23 -0
  4. package/dist/board/core/arrow-geometry.js +109 -0
  5. package/dist/board/core/connections.d.ts +91 -0
  6. package/dist/board/core/connections.js +414 -0
  7. package/dist/board/core/export-plan.d.ts +15 -4
  8. package/dist/board/core/export-plan.js +49 -16
  9. package/dist/board/core/shape-definition.js +1 -1
  10. package/dist/board/core/shape-types.d.ts +8 -2
  11. package/dist/board/core/shape-types.js +1 -1
  12. package/dist/board/core/tool-styles.d.ts +9 -3
  13. package/dist/board/core/tool-styles.js +10 -6
  14. package/dist/board/export/index.js +1 -0
  15. package/dist/board/export/scene.d.ts +3 -0
  16. package/dist/board/export/scene.js +18 -1
  17. package/dist/board/index.d.ts +8 -5
  18. package/dist/board/index.js +8 -5
  19. package/dist/board/render/connection-layer.d.ts +48 -0
  20. package/dist/board/render/connection-layer.js +223 -0
  21. package/dist/board/render/index.d.ts +2 -1
  22. package/dist/board/render/index.js +3 -2
  23. package/dist/board/render/renderers/arrow-card-renderer.js +35 -48
  24. package/dist/chunks/http.d.ts +53 -1
  25. package/dist/chunks/http.js +287 -1
  26. package/dist/chunks/websocket.d.ts +296 -57
  27. package/dist/http.d.ts +1 -1
  28. package/dist/index.d.ts +1 -1
  29. package/dist/index.js +17 -56
  30. package/dist/protocol/dist/board-connection.d.ts +310 -0
  31. package/dist/protocol/dist/board-connection.js +215 -0
  32. package/dist/protocol/dist/board-constants.d.ts +11 -1
  33. package/dist/protocol/dist/board-constants.js +15 -1
  34. package/dist/protocol/dist/board-document.d.ts +106 -60
  35. package/dist/protocol/dist/board-document.js +57 -17
  36. package/dist/protocol/dist/board.d.ts +1 -0
  37. package/dist/protocol/dist/board.js +3 -0
  38. package/dist/protocol/dist/index.d.ts +2 -1
  39. package/dist/protocol/dist/index.js +2 -1
  40. package/dist/protocol/dist/realtime/board-awareness.js +15 -15
  41. package/package.json +1 -1
  42. package/dist/board/core/bindings.d.ts +0 -45
  43. package/dist/board/core/bindings.js +0 -162
@@ -1,3 +1,4 @@
1
+ import { BoardConnectionRecord } from "../protocol/dist/board-connection.js";
1
2
  import { BoardManifest, BoardNodeInput, BoardNodeRecord, BoardRecord, InvalidBoardFileError, isBoardPath, parseBoardManifest, serializeBoardManifest } from "../protocol/dist/board.js";
2
3
  import "../protocol/dist/index.js";
3
4
  import { BoardAppearance, BoardDocument, BoardItem } from "../protocol/dist/board-document.js";
@@ -20,6 +21,7 @@ declare function boardNodeToItem(node: BoardNodeRecord): BoardItem;
20
21
  declare function boardBootstrapToDocument(input: {
21
22
  board: BoardRecord;
22
23
  nodes: BoardNodeRecord[];
24
+ connections?: BoardConnectionRecord[];
23
25
  }): BoardDocument;
24
26
  //#endregion
25
27
  export { BOARD_NODE_SOURCE, type BoardManifest, type BoardNodeRecord, type BoardRecord, DEFAULT_BOARD_APPEARANCE, ITEM_BASE_KEYS, InvalidBoardFileError, WireBackedBoardItem, boardBootstrapToDocument, boardNodeToItem, isBoardPath, isRecord, nodeInputFromRecord, parseBoardManifest, serializeBoardManifest, sourceForItem };
@@ -1,5 +1,5 @@
1
1
  import { BOARD_DOCUMENT_KIND, InvalidBoardFileError, isBoardPath, parseBoardManifest, serializeBoardManifest } from "../protocol/dist/board.js";
2
- import { BoardAppearanceSchema, BoardDocumentSchema, BoardFileSnapshotSchema, UNKNOWN_BOARD_ITEM_TYPE } from "../protocol/dist/board-document.js";
2
+ import { BoardAppearanceSchema, BoardFileSnapshotSchema, UNKNOWN_BOARD_ITEM_TYPE, parseBoardDocument } from "../protocol/dist/board-document.js";
3
3
  import { TEXT_FONT_SIZE, clampBoardTextFontSize } from "./core/text-metrics.js";
4
4
  import "../protocol/dist/index.js";
5
5
  //#region src/board/codec.ts
@@ -72,6 +72,17 @@ function spaceFilePathFromNode(node) {
72
72
  if (typeof node.refPath === "string" && node.refPath) return node.refPath;
73
73
  return null;
74
74
  }
75
+ /** Read a stored world point, defaulting to the origin when absent. */
76
+ function pointFromData(value) {
77
+ if (isRecord(value) && typeof value.x === "number" && typeof value.y === "number") return {
78
+ x: value.x,
79
+ y: value.y
80
+ };
81
+ return {
82
+ x: 0,
83
+ y: 0
84
+ };
85
+ }
75
86
  function boardNodeToItemValue(node) {
76
87
  const frame = frameFromNode(node);
77
88
  const style = styleFromNode(node);
@@ -112,16 +123,8 @@ function boardNodeToItemValue(node) {
112
123
  case "arrow": return {
113
124
  id: node.nodeId,
114
125
  type: "arrow",
115
- start: data.start ?? {
116
- kind: "point",
117
- x: 0,
118
- y: 0
119
- },
120
- end: data.end ?? {
121
- kind: "point",
122
- x: 0,
123
- y: 0
124
- },
126
+ start: pointFromData(data.start),
127
+ end: pointFromData(data.end),
125
128
  bend: typeof data.bend === "number" ? data.bend : 0,
126
129
  color: typeof data.color === "string" ? data.color : "brand",
127
130
  size: typeof data.size === "number" ? data.size : 2.5,
@@ -219,7 +222,8 @@ function boardNodeToItem(node) {
219
222
  }
220
223
  function boardBootstrapToDocument(input) {
221
224
  const appearance = BoardAppearanceSchema.safeParse(input.board.metadata.appearance);
222
- const document = BoardDocumentSchema.parse({
225
+ const connections = (input.connections ?? []).map(({ boardId: _boardId, revision: _revision, createdAt: _createdAt, updatedAt: _updatedAt, ...connection }) => connection);
226
+ const document = parseBoardDocument({
223
227
  kind: BOARD_DOCUMENT_KIND,
224
228
  version: 1,
225
229
  appearance: appearance.success ? appearance.data : DEFAULT_BOARD_APPEARANCE,
@@ -228,7 +232,8 @@ function boardBootstrapToDocument(input) {
228
232
  y: 0,
229
233
  zoom: 1
230
234
  },
231
- items: input.nodes.map(boardNodeToItem)
235
+ items: input.nodes.map(boardNodeToItem),
236
+ connections
232
237
  });
233
238
  const sources = new Map(input.nodes.map((node) => [node.nodeId, nodeInputFromRecord(node)]));
234
239
  return {
@@ -0,0 +1,23 @@
1
+ import { BoardArrowItem, BoardFrame } from "../../protocol/dist/board-document.js";
2
+ import { Rect, WorldPoint } from "../geometry.js";
3
+ //#region src/board/core/arrow-geometry.d.ts
4
+ type ResolvedArrow = {
5
+ start: WorldPoint;
6
+ end: WorldPoint;
7
+ /** Quadratic control point (already bent); the midpoint when bend is 0. */
8
+ control: WorldPoint;
9
+ };
10
+ /** Resolve an arrow's endpoints and its bent control point. */
11
+ declare function resolveArrow(item: BoardArrowItem): ResolvedArrow;
12
+ /** Sample an arrow's quadratic curve into world points. */
13
+ declare function sampleArrow(resolved: ResolvedArrow, segments: number): WorldPoint[];
14
+ /** World bounds of an arrow, padded for its stroke. */
15
+ declare function arrowBounds(item: BoardArrowItem): Rect;
16
+ /** The frame an arrow should carry, derived from its endpoints. */
17
+ declare function arrowFrame(item: BoardArrowItem): BoardFrame;
18
+ /** Distance from a world point to an arrow's curve (hit testing). */
19
+ declare function distanceToArrow(item: BoardArrowItem, point: WorldPoint): number;
20
+ /** Translate an arrow by a delta, keeping its frame consistent. */
21
+ declare function translateArrow(item: BoardArrowItem, dx: number, dy: number): BoardArrowItem;
22
+ //#endregion
23
+ export { ResolvedArrow, arrowBounds, arrowFrame, distanceToArrow, resolveArrow, sampleArrow, translateArrow };
@@ -0,0 +1,109 @@
1
+ import { worldPoint } from "../geometry.js";
2
+ //#region src/board/core/arrow-geometry.ts
3
+ /**
4
+ * Free-arrow geometry.
5
+ *
6
+ * An arrow is a standalone annotation stroke between two world points — it does
7
+ * not relate nodes. Node relations are `BoardConnection`s, which resolve their
8
+ * geometry from the nodes they join (see ./connections). Keeping the two apart is
9
+ * what makes each one simple: an arrow owns absolute coordinates and needs no
10
+ * lookup, while a connection owns no coordinates at all.
11
+ */
12
+ /** Resolve an arrow's endpoints and its bent control point. */
13
+ function resolveArrow(item) {
14
+ const start = worldPoint(item.start.x, item.start.y);
15
+ const end = worldPoint(item.end.x, item.end.y);
16
+ const mid = worldPoint((start.x + end.x) / 2, (start.y + end.y) / 2);
17
+ if (!item.bend) return {
18
+ start,
19
+ end,
20
+ control: mid
21
+ };
22
+ const dx = end.x - start.x;
23
+ const dy = end.y - start.y;
24
+ const length = Math.hypot(dx, dy) || 1;
25
+ const offset = item.bend * length;
26
+ return {
27
+ start,
28
+ end,
29
+ control: worldPoint(mid.x + -dy / length * offset, mid.y + dx / length * offset)
30
+ };
31
+ }
32
+ /** Sample an arrow's quadratic curve into world points. */
33
+ function sampleArrow(resolved, segments) {
34
+ const { start, control, end } = resolved;
35
+ const out = [];
36
+ for (let index = 0; index <= segments; index += 1) {
37
+ const t = index / segments;
38
+ const mt = 1 - t;
39
+ out.push(worldPoint(mt * mt * start.x + 2 * mt * t * control.x + t * t * end.x, mt * mt * start.y + 2 * mt * t * control.y + t * t * end.y));
40
+ }
41
+ return out;
42
+ }
43
+ /** World bounds of an arrow, padded for its stroke. */
44
+ function arrowBounds(item) {
45
+ const samples = sampleArrow(resolveArrow(item), 12);
46
+ let minX = Number.POSITIVE_INFINITY;
47
+ let minY = Number.POSITIVE_INFINITY;
48
+ let maxX = Number.NEGATIVE_INFINITY;
49
+ let maxY = Number.NEGATIVE_INFINITY;
50
+ for (const point of samples) {
51
+ minX = Math.min(minX, point.x);
52
+ minY = Math.min(minY, point.y);
53
+ maxX = Math.max(maxX, point.x);
54
+ maxY = Math.max(maxY, point.y);
55
+ }
56
+ const pad = Math.max(8, item.size * 2);
57
+ return {
58
+ x: minX - pad,
59
+ y: minY - pad,
60
+ width: Math.max(1, maxX - minX + pad * 2),
61
+ height: Math.max(1, maxY - minY + pad * 2)
62
+ };
63
+ }
64
+ /** The frame an arrow should carry, derived from its endpoints. */
65
+ function arrowFrame(item) {
66
+ return {
67
+ ...arrowBounds(item),
68
+ rotation: 0
69
+ };
70
+ }
71
+ /** Distance from a world point to an arrow's curve (hit testing). */
72
+ function distanceToArrow(item, point) {
73
+ const samples = sampleArrow(resolveArrow(item), 16);
74
+ let min = Number.POSITIVE_INFINITY;
75
+ for (let index = 0; index < samples.length - 1; index += 1) {
76
+ const from = samples[index];
77
+ const to = samples[index + 1];
78
+ if (!from || !to) continue;
79
+ const distance = segmentDistance(point, from, to);
80
+ if (distance < min) min = distance;
81
+ }
82
+ return min;
83
+ }
84
+ function segmentDistance(p, a, b) {
85
+ const dx = b.x - a.x;
86
+ const dy = b.y - a.y;
87
+ const lengthSq = dx * dx + dy * dy;
88
+ if (lengthSq === 0) return Math.hypot(p.x - a.x, p.y - a.y);
89
+ const t = Math.min(1, Math.max(0, ((p.x - a.x) * dx + (p.y - a.y) * dy) / lengthSq));
90
+ return Math.hypot(p.x - (a.x + t * dx), p.y - (a.y + t * dy));
91
+ }
92
+ /** Translate an arrow by a delta, keeping its frame consistent. */
93
+ function translateArrow(item, dx, dy) {
94
+ const move = (point) => ({
95
+ x: point.x + dx,
96
+ y: point.y + dy
97
+ });
98
+ const next = {
99
+ ...item,
100
+ start: move(item.start),
101
+ end: move(item.end)
102
+ };
103
+ return {
104
+ ...next,
105
+ frame: arrowFrame(next)
106
+ };
107
+ }
108
+ //#endregion
109
+ export { arrowBounds, arrowFrame, distanceToArrow, resolveArrow, sampleArrow, translateArrow };
@@ -0,0 +1,91 @@
1
+ import { BoardConnection, BoardConnectionAnchor, BoardConnectionSide } from "../../protocol/dist/board-connection.js";
2
+ import { BoardFrame } from "../../protocol/dist/board-document.js";
3
+ import { Rect, WorldPoint } from "../geometry.js";
4
+ //#region src/board/core/connections.d.ts
5
+ type FrameLookup = (id: string) => BoardFrame | undefined;
6
+ /**
7
+ * Gap between a node's edge and the line's endpoint, in world units.
8
+ *
9
+ * A connection that touches the border reads as part of the node; a small gap
10
+ * makes the relation legible as its own object and keeps an arrowhead from being
11
+ * swallowed by the card it points at.
12
+ */
13
+ declare const CONNECTION_ENDPOINT_GAP = 4;
14
+ type ResolvedConnectionEndpoint = {
15
+ point: WorldPoint;
16
+ /** Outward normal at the attachment, used to aim arrowheads and elbows. */
17
+ normal: WorldPoint;
18
+ side: BoardConnectionSide;
19
+ };
20
+ type ResolvedConnection = {
21
+ source: ResolvedConnectionEndpoint;
22
+ target: ResolvedConnectionEndpoint;
23
+ /** Sampled world-space path, always at least two points. */
24
+ path: WorldPoint[];
25
+ /** Midpoint of the path, for labels and the drag handle. */
26
+ mid: WorldPoint;
27
+ };
28
+ /** Denormalize a (nx, ny) anchor to a world point on a possibly rotated frame. */
29
+ declare function anchorToWorld(frame: BoardFrame, nx: number, ny: number): WorldPoint;
30
+ /** Normalize a world point into a (nx, ny) anchor on a frame, clamped to it. */
31
+ declare function worldToAnchor(frame: BoardFrame, point: WorldPoint): {
32
+ nx: number;
33
+ ny: number;
34
+ };
35
+ /**
36
+ * Choose the side an `auto` anchor should use.
37
+ *
38
+ * The dominant axis of the center-to-center vector wins, weighted by the frame's
39
+ * own proportions so a wide card prefers its long edges. Comparing raw dx/dy
40
+ * instead would make a wide node attach to its short side for most angles, which
41
+ * is the classic "line leaves from the corner" artifact.
42
+ */
43
+ declare function autoConnectionSide(from: BoardFrame, to: BoardFrame): BoardConnectionSide;
44
+ /**
45
+ * World point of an anchor on a frame, ignoring the gap and the facing node.
46
+ *
47
+ * For a live drag the other end is the pointer, not a node, so `auto` has no
48
+ * frame to face: it resolves to the node's centre, which is the only honest
49
+ * answer while the relation has nowhere to point yet.
50
+ */
51
+ declare function anchorPointOnFrame(anchor: BoardConnectionAnchor, frame: BoardFrame): WorldPoint;
52
+ /**
53
+ * Resolve a connection into world-space geometry, or null if either node is gone.
54
+ *
55
+ * A null result means "not drawable right now", never "delete this": the caller
56
+ * may be looking at a viewport-culled read where an endpoint simply was not
57
+ * fetched.
58
+ */
59
+ declare function resolveConnection(connection: BoardConnection, getFrame: FrameLookup, options?: {
60
+ gap?: number;
61
+ }): ResolvedConnection | null;
62
+ /** Midpoint by arc length, so a label sits visually centered on the line. */
63
+ declare function pathMidpoint(path: WorldPoint[]): WorldPoint;
64
+ /** World bounds of a resolved connection, padded for its stroke. */
65
+ declare function connectionBounds(resolved: ResolvedConnection, strokeSize: number): Rect;
66
+ /** Distance from a world point to a resolved connection's path. */
67
+ declare function distanceToConnection(resolved: ResolvedConnection, point: WorldPoint): number;
68
+ /** Hit test a connection with a zoom-independent screen-space tolerance. */
69
+ declare function connectionHitTest(resolved: ResolvedConnection, point: WorldPoint, strokeSize: number): boolean;
70
+ /** Whether the connection draws an arrowhead at its source / target. */
71
+ declare function connectionArrowheads(connection: BoardConnection): {
72
+ atSource: boolean;
73
+ atTarget: boolean;
74
+ };
75
+ /**
76
+ * Index of connections by the nodes they touch.
77
+ *
78
+ * Built once per document revision and consulted per gesture frame: moving a node
79
+ * must cost its own degree, not a scan of every connection on the board. Without
80
+ * this, dragging one node in a densely connected board is O(connections) per
81
+ * frame, which is exactly the cost that shows up as drag lag.
82
+ */
83
+ type ConnectionIndex = {
84
+ /** Connection ids touching a node (either endpoint). */
85
+ byNode: (nodeId: string) => readonly string[];
86
+ get: (connectionId: string) => BoardConnection | undefined;
87
+ readonly all: readonly BoardConnection[];
88
+ };
89
+ declare function createConnectionIndex(connections: readonly BoardConnection[]): ConnectionIndex;
90
+ //#endregion
91
+ export { CONNECTION_ENDPOINT_GAP, ConnectionIndex, FrameLookup, ResolvedConnection, ResolvedConnectionEndpoint, anchorPointOnFrame, anchorToWorld, autoConnectionSide, connectionArrowheads, connectionBounds, connectionHitTest, createConnectionIndex, distanceToConnection, pathMidpoint, resolveConnection, worldToAnchor };