@canvas-harness/core 0.0.4 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -106,7 +106,7 @@ type EdgeStyle = Style & {
106
106
  * Built-in node types — see ARCHITECTURE.md §3.5.
107
107
  * Custom node types are arbitrary strings registered via defineNode.
108
108
  */
109
- type BuiltInNodeType = 'rect' | 'ellipse' | 'diamond' | 'tag' | 'capsule' | 'thought-cloud' | 'layered-rect' | 'layered-ellipse' | 'layered-diamond' | 'text' | 'image' | 'icon' | 'frame';
109
+ type BuiltInNodeType = 'rect' | 'ellipse' | 'diamond' | 'tag' | 'capsule' | 'thought-cloud' | 'layered-rect' | 'layered-ellipse' | 'layered-diamond' | 'soft-diamond' | 'text' | 'image' | 'icon' | 'frame';
110
110
  type NodeType = BuiltInNodeType | (string & {
111
111
  readonly __nodeType?: never;
112
112
  });
@@ -1495,14 +1495,21 @@ interface CanvasStore {
1495
1495
  * Adds a node. Returns its id. If `node.style.autoFit !== false` and
1496
1496
  * `node.content` is set, height is grown to fit.
1497
1497
  *
1498
+ * `z` is optional — omit to land on top of the current stack (uses
1499
+ * the internal `topZ` counter). Pass a literal value (incl. `0` or
1500
+ * negative) to place the node at that exact z.
1501
+ *
1498
1502
  * @example
1503
+ * // Omit z → goes on top of the current stack.
1499
1504
  * const id = store.addNode({
1500
1505
  * id: asNodeId(store.generateId()),
1501
1506
  * type: 'rect', x: 0, y: 0, w: 200, h: 100,
1502
- * angle: 0, z: 0, groups: [],
1507
+ * angle: 0, groups: [],
1503
1508
  * })
1504
1509
  */
1505
- addNode(node: Node): NodeId;
1510
+ addNode(node: Omit<Node, 'z'> & {
1511
+ z?: number;
1512
+ }): NodeId;
1506
1513
  /**
1507
1514
  * Patches fields on an existing node. Captures the previous slice on
1508
1515
  * the op so undo is free. Autofit re-runs when `content` or font
@@ -1566,8 +1573,13 @@ interface CanvasStore {
1566
1573
  alt?: string;
1567
1574
  style?: Style;
1568
1575
  }): Promise<NodeId>;
1569
- /** Adds an edge. Returns its id. */
1570
- addEdge(edge: Edge): EdgeId;
1576
+ /**
1577
+ * Adds an edge. Returns its id. `z` is optional — same auto-top
1578
+ * semantics as {@link CanvasStore.addNode}.
1579
+ */
1580
+ addEdge(edge: Omit<Edge, 'z'> & {
1581
+ z?: number;
1582
+ }): EdgeId;
1571
1583
  /** Patches fields on an existing edge. */
1572
1584
  updateEdge(id: EdgeId, patch: Partial<Edge>): void;
1573
1585
  /** Removes an edge. */
@@ -2034,7 +2046,7 @@ type AtomicPrimitive = 'rect' | 'ellipse' | 'diamond' | 'tag' | 'thought-cloud';
2034
2046
  * circle and the rect body reads as two stacked hand-drawn shapes
2035
2047
  * (medicine-pill aesthetic), which we want to keep.
2036
2048
  */
2037
- type CompositePrimitive = 'capsule' | 'layered-rect' | 'layered-ellipse' | 'layered-diamond';
2049
+ type CompositePrimitive = 'capsule' | 'layered-rect' | 'layered-ellipse' | 'layered-diamond' | 'soft-diamond';
2038
2050
  type PrimitiveType = AtomicPrimitive | CompositePrimitive;
2039
2051
  /** Returns true if `node.type` is one of the built-ins drawShape can render. */
2040
2052
  declare const isDrawablePrimitive: (type: string) => type is PrimitiveType;
package/dist/index.d.ts CHANGED
@@ -106,7 +106,7 @@ type EdgeStyle = Style & {
106
106
  * Built-in node types — see ARCHITECTURE.md §3.5.
107
107
  * Custom node types are arbitrary strings registered via defineNode.
108
108
  */
109
- type BuiltInNodeType = 'rect' | 'ellipse' | 'diamond' | 'tag' | 'capsule' | 'thought-cloud' | 'layered-rect' | 'layered-ellipse' | 'layered-diamond' | 'text' | 'image' | 'icon' | 'frame';
109
+ type BuiltInNodeType = 'rect' | 'ellipse' | 'diamond' | 'tag' | 'capsule' | 'thought-cloud' | 'layered-rect' | 'layered-ellipse' | 'layered-diamond' | 'soft-diamond' | 'text' | 'image' | 'icon' | 'frame';
110
110
  type NodeType = BuiltInNodeType | (string & {
111
111
  readonly __nodeType?: never;
112
112
  });
@@ -1495,14 +1495,21 @@ interface CanvasStore {
1495
1495
  * Adds a node. Returns its id. If `node.style.autoFit !== false` and
1496
1496
  * `node.content` is set, height is grown to fit.
1497
1497
  *
1498
+ * `z` is optional — omit to land on top of the current stack (uses
1499
+ * the internal `topZ` counter). Pass a literal value (incl. `0` or
1500
+ * negative) to place the node at that exact z.
1501
+ *
1498
1502
  * @example
1503
+ * // Omit z → goes on top of the current stack.
1499
1504
  * const id = store.addNode({
1500
1505
  * id: asNodeId(store.generateId()),
1501
1506
  * type: 'rect', x: 0, y: 0, w: 200, h: 100,
1502
- * angle: 0, z: 0, groups: [],
1507
+ * angle: 0, groups: [],
1503
1508
  * })
1504
1509
  */
1505
- addNode(node: Node): NodeId;
1510
+ addNode(node: Omit<Node, 'z'> & {
1511
+ z?: number;
1512
+ }): NodeId;
1506
1513
  /**
1507
1514
  * Patches fields on an existing node. Captures the previous slice on
1508
1515
  * the op so undo is free. Autofit re-runs when `content` or font
@@ -1566,8 +1573,13 @@ interface CanvasStore {
1566
1573
  alt?: string;
1567
1574
  style?: Style;
1568
1575
  }): Promise<NodeId>;
1569
- /** Adds an edge. Returns its id. */
1570
- addEdge(edge: Edge): EdgeId;
1576
+ /**
1577
+ * Adds an edge. Returns its id. `z` is optional — same auto-top
1578
+ * semantics as {@link CanvasStore.addNode}.
1579
+ */
1580
+ addEdge(edge: Omit<Edge, 'z'> & {
1581
+ z?: number;
1582
+ }): EdgeId;
1571
1583
  /** Patches fields on an existing edge. */
1572
1584
  updateEdge(id: EdgeId, patch: Partial<Edge>): void;
1573
1585
  /** Removes an edge. */
@@ -2034,7 +2046,7 @@ type AtomicPrimitive = 'rect' | 'ellipse' | 'diamond' | 'tag' | 'thought-cloud';
2034
2046
  * circle and the rect body reads as two stacked hand-drawn shapes
2035
2047
  * (medicine-pill aesthetic), which we want to keep.
2036
2048
  */
2037
- type CompositePrimitive = 'capsule' | 'layered-rect' | 'layered-ellipse' | 'layered-diamond';
2049
+ type CompositePrimitive = 'capsule' | 'layered-rect' | 'layered-ellipse' | 'layered-diamond' | 'soft-diamond';
2038
2050
  type PrimitiveType = AtomicPrimitive | CompositePrimitive;
2039
2051
  /** Returns true if `node.type` is one of the built-ins drawShape can render. */
2040
2052
  declare const isDrawablePrimitive: (type: string) => type is PrimitiveType;
package/dist/index.js CHANGED
@@ -1053,7 +1053,8 @@ var COMPOSITE = /* @__PURE__ */ new Set([
1053
1053
  "capsule",
1054
1054
  "layered-rect",
1055
1055
  "layered-ellipse",
1056
- "layered-diamond"
1056
+ "layered-diamond",
1057
+ "soft-diamond"
1057
1058
  ]);
1058
1059
  var isCompositePrimitive = (type) => COMPOSITE.has(type);
1059
1060
  var isDrawablePrimitive = (type) => ATOMIC.has(type) || COMPOSITE.has(type);
@@ -1159,6 +1160,30 @@ var compositeLayout = (node) => {
1159
1160
  const front = { atomic, x: 0, y: 0, w, h };
1160
1161
  return [back, front];
1161
1162
  }
1163
+ case "soft-diamond": {
1164
+ const backScale = 1.08;
1165
+ const frontScale = 0.96;
1166
+ const bw = w * backScale;
1167
+ const bh = h * backScale;
1168
+ const fw = w * frontScale;
1169
+ const fh = h * frontScale;
1170
+ const back = {
1171
+ atomic: "diamond",
1172
+ x: (w - bw) / 2,
1173
+ y: (h - bh) / 2,
1174
+ w: bw,
1175
+ h: bh,
1176
+ style: darkenedStyle(node.style)
1177
+ };
1178
+ const front = {
1179
+ atomic: "diamond",
1180
+ x: (w - fw) / 2,
1181
+ y: (h - fh) / 2,
1182
+ w: fw,
1183
+ h: fh
1184
+ };
1185
+ return [back, front];
1186
+ }
1162
1187
  }
1163
1188
  return [];
1164
1189
  };
@@ -3217,8 +3242,15 @@ var createCanvasStore = (opts = {}) => {
3217
3242
  };
3218
3243
  const incidentEdges = /* @__PURE__ */ new Map();
3219
3244
  let topZ = 0;
3220
- for (const n of Object.values(initial.nodes)) if (n.z > topZ) topZ = n.z;
3221
- for (const e of Object.values(initial.edges)) if (e.z > topZ) topZ = e.z;
3245
+ let bottomZ = 0;
3246
+ for (const n of Object.values(initial.nodes)) {
3247
+ if (n.z > topZ) topZ = n.z;
3248
+ if (n.z < bottomZ) bottomZ = n.z;
3249
+ }
3250
+ for (const e of Object.values(initial.edges)) {
3251
+ if (e.z > topZ) topZ = e.z;
3252
+ if (e.z < bottomZ) bottomZ = e.z;
3253
+ }
3222
3254
  const getNodeForGeo = (id) => nodeAtoms.get(id)?.value;
3223
3255
  let currentBatchOps = null;
3224
3256
  let batchDepth = 0;
@@ -3307,6 +3339,8 @@ var createCanvasStore = (opts = {}) => {
3307
3339
  nodeAtoms.set(op.node.id, a);
3308
3340
  nodeIdsAtom.update((ids) => [...ids, op.node.id]);
3309
3341
  reindexNode(op.node);
3342
+ if (op.node.z > topZ) topZ = op.node.z;
3343
+ if (op.node.z < bottomZ) bottomZ = op.node.z;
3310
3344
  if (op.node.type === "frame") {
3311
3345
  frameOrderAtom.update((ids) => ids.includes(op.node.id) ? ids : [...ids, op.node.id]);
3312
3346
  }
@@ -3318,6 +3352,10 @@ var createCanvasStore = (opts = {}) => {
3318
3352
  const next = { ...a.value, ...op.patch };
3319
3353
  a.set(next);
3320
3354
  reindexNode(next);
3355
+ if (op.patch.z !== void 0) {
3356
+ if (op.patch.z > topZ) topZ = op.patch.z;
3357
+ if (op.patch.z < bottomZ) bottomZ = op.patch.z;
3358
+ }
3321
3359
  const incident = incidentEdges.get(op.id);
3322
3360
  if (incident) {
3323
3361
  for (const eid of incident) {
@@ -3346,6 +3384,8 @@ var createCanvasStore = (opts = {}) => {
3346
3384
  trackIncidence(op.edge);
3347
3385
  bumpEdgeVersion(op.edge.id);
3348
3386
  reindexEdge(op.edge);
3387
+ if (op.edge.z > topZ) topZ = op.edge.z;
3388
+ if (op.edge.z < bottomZ) bottomZ = op.edge.z;
3349
3389
  break;
3350
3390
  }
3351
3391
  case "edge.update": {
@@ -3358,6 +3398,10 @@ var createCanvasStore = (opts = {}) => {
3358
3398
  a.set(next);
3359
3399
  bumpEdgeVersion(op.id);
3360
3400
  reindexEdge(next);
3401
+ if (op.patch.z !== void 0) {
3402
+ if (op.patch.z > topZ) topZ = op.patch.z;
3403
+ if (op.patch.z < bottomZ) bottomZ = op.patch.z;
3404
+ }
3361
3405
  break;
3362
3406
  }
3363
3407
  case "edge.remove": {
@@ -3445,9 +3489,8 @@ var createCanvasStore = (opts = {}) => {
3445
3489
  clientId,
3446
3490
  generateId: () => idGenerator(),
3447
3491
  addNode(node) {
3448
- const withZ = node.z === 0 ? { ...node, z: ++topZ } : node;
3449
- if (withZ.z > topZ) topZ = withZ.z;
3450
- const fitted = withAutoFitHeight(withZ);
3492
+ const z = node.z ?? ++topZ;
3493
+ const fitted = withAutoFitHeight({ ...node, z });
3451
3494
  enqueueOp({ type: "node.add", node: fitted });
3452
3495
  return fitted.id;
3453
3496
  },
@@ -3510,7 +3553,6 @@ var createCanvasStore = (opts = {}) => {
3510
3553
  w,
3511
3554
  h,
3512
3555
  angle: 0,
3513
- z: 0,
3514
3556
  groups: [],
3515
3557
  style: opts2.style,
3516
3558
  data: { src, naturalW, naturalH, alt: opts2.alt }
@@ -3533,7 +3575,6 @@ var createCanvasStore = (opts = {}) => {
3533
3575
  w,
3534
3576
  h,
3535
3577
  angle: 0,
3536
- z: 0,
3537
3578
  groups: [],
3538
3579
  ...mergedStyle ? { style: mergedStyle } : {},
3539
3580
  data: { src: sanitized, alt: opts2.alt }
@@ -3541,8 +3582,8 @@ var createCanvasStore = (opts = {}) => {
3541
3582
  return id;
3542
3583
  },
3543
3584
  addEdge(edge) {
3544
- const withZ = edge.z === 0 ? { ...edge, z: ++topZ } : edge;
3545
- if (withZ.z > topZ) topZ = withZ.z;
3585
+ const z = edge.z ?? ++topZ;
3586
+ const withZ = { ...edge, z };
3546
3587
  enqueueOp({ type: "edge.add", edge: withZ });
3547
3588
  return withZ.id;
3548
3589
  },
@@ -3565,29 +3606,10 @@ var createCanvasStore = (opts = {}) => {
3565
3606
  });
3566
3607
  },
3567
3608
  sendToBack(ids) {
3568
- const targets = new Set(ids);
3569
- let minZ = 0;
3570
- let initialized = false;
3571
- for (const a of nodeAtoms.values()) {
3572
- if (targets.has(a.value.id)) continue;
3573
- if (!initialized || a.value.z < minZ) {
3574
- minZ = a.value.z;
3575
- initialized = true;
3576
- }
3577
- }
3578
- for (const a of edgeAtoms.values()) {
3579
- if (targets.has(a.value.id)) continue;
3580
- if (!initialized || a.value.z < minZ) {
3581
- minZ = a.value.z;
3582
- initialized = true;
3583
- }
3584
- }
3585
3609
  this.batch(() => {
3586
- let next = (initialized ? minZ : 0) - 1;
3587
3610
  for (const id of ids) {
3588
- if (nodeAtoms.has(id)) this.updateNode(id, { z: next });
3589
- else if (edgeAtoms.has(id)) this.updateEdge(id, { z: next });
3590
- next -= 1;
3611
+ if (nodeAtoms.has(id)) this.updateNode(id, { z: --bottomZ });
3612
+ else if (edgeAtoms.has(id)) this.updateEdge(id, { z: --bottomZ });
3591
3613
  }
3592
3614
  });
3593
3615
  },
@@ -3605,7 +3627,6 @@ var createCanvasStore = (opts = {}) => {
3605
3627
  if (currentZ === void 0) continue;
3606
3628
  const idx = binaryFirstGreater(allZ, currentZ);
3607
3629
  const nextZ = idx >= 0 ? allZ[idx] + 1 : currentZ + 1;
3608
- if (nextZ > topZ) topZ = nextZ;
3609
3630
  if (node) this.updateNode(id, { z: nextZ });
3610
3631
  else this.updateEdge(id, { z: nextZ });
3611
3632
  }
@@ -4266,7 +4287,7 @@ var paintBackground = (ctx, opts) => {
4266
4287
  }
4267
4288
  };
4268
4289
  var paintDots = (ctx, minX, minY, maxX, maxY, gap, color, zoom) => {
4269
- const sizeWorld = Math.max(1, 2.4 / zoom);
4290
+ const sizeWorld = Math.max(1, 1.6 / zoom);
4270
4291
  const half = sizeWorld / 2;
4271
4292
  ctx.save();
4272
4293
  ctx.fillStyle = color;
@@ -4547,7 +4568,8 @@ var contentBounds = (node) => {
4547
4568
  return { x: rectX, y: 0, w: Math.max(0, w - rectX), h };
4548
4569
  }
4549
4570
  case "diamond":
4550
- case "layered-diamond": {
4571
+ case "layered-diamond":
4572
+ case "soft-diamond": {
4551
4573
  const cw = w * SQRT2_INV;
4552
4574
  const ch = h * SQRT2_INV;
4553
4575
  return { x: (w - cw) / 2, y: (h - ch) / 2, w: cw, h: ch };