@canvas-harness/core 0.0.2 → 0.0.4
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.cjs +125 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +39 -0
- package/dist/index.d.ts +39 -0
- package/dist/index.js +125 -3
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -3133,6 +3133,8 @@ var inverseOp = (op) => {
|
|
|
3133
3133
|
return { type: "group.remove", group: op.group };
|
|
3134
3134
|
case "group.remove":
|
|
3135
3135
|
return { type: "group.upsert", group: op.group };
|
|
3136
|
+
case "frame.reorder":
|
|
3137
|
+
return { type: "frame.reorder", ids: op.prev, prev: op.ids };
|
|
3136
3138
|
}
|
|
3137
3139
|
};
|
|
3138
3140
|
var inverseBatch = (batch) => {
|
|
@@ -3200,6 +3202,7 @@ var createCanvasStore = (opts = {}) => {
|
|
|
3200
3202
|
const groupIdsAtom = signia.atom("groupIds", []);
|
|
3201
3203
|
const cameraAtom = signia.atom("camera", initial.camera);
|
|
3202
3204
|
const selectionAtom = signia.atom("selection", initial.selection);
|
|
3205
|
+
const frameOrderAtom = signia.atom("frameOrder", initial.frameOrder ?? []);
|
|
3203
3206
|
const interactionAtom = signia.atom("interaction", idleInteractionState());
|
|
3204
3207
|
const localPresenceAtom = signia.atom("presence", emptyPresenceState(clientId));
|
|
3205
3208
|
const remotePresence = /* @__PURE__ */ new Map();
|
|
@@ -3306,6 +3309,9 @@ var createCanvasStore = (opts = {}) => {
|
|
|
3306
3309
|
nodeAtoms.set(op.node.id, a);
|
|
3307
3310
|
nodeIdsAtom.update((ids) => [...ids, op.node.id]);
|
|
3308
3311
|
reindexNode(op.node);
|
|
3312
|
+
if (op.node.type === "frame") {
|
|
3313
|
+
frameOrderAtom.update((ids) => ids.includes(op.node.id) ? ids : [...ids, op.node.id]);
|
|
3314
|
+
}
|
|
3309
3315
|
break;
|
|
3310
3316
|
}
|
|
3311
3317
|
case "node.update": {
|
|
@@ -3330,6 +3336,9 @@ var createCanvasStore = (opts = {}) => {
|
|
|
3330
3336
|
nodeIdsAtom.update((ids) => ids.filter((x) => x !== id));
|
|
3331
3337
|
unindexNode(id);
|
|
3332
3338
|
incidentEdges.delete(id);
|
|
3339
|
+
if (op.node.type === "frame") {
|
|
3340
|
+
frameOrderAtom.update((ids) => ids.filter((x) => x !== id));
|
|
3341
|
+
}
|
|
3333
3342
|
break;
|
|
3334
3343
|
}
|
|
3335
3344
|
case "edge.add": {
|
|
@@ -3379,6 +3388,10 @@ var createCanvasStore = (opts = {}) => {
|
|
|
3379
3388
|
groupIdsAtom.update((ids) => ids.filter((x) => x !== id));
|
|
3380
3389
|
break;
|
|
3381
3390
|
}
|
|
3391
|
+
case "frame.reorder": {
|
|
3392
|
+
frameOrderAtom.set([...op.ids]);
|
|
3393
|
+
break;
|
|
3394
|
+
}
|
|
3382
3395
|
}
|
|
3383
3396
|
};
|
|
3384
3397
|
const enqueueOp = (op) => {
|
|
@@ -3401,6 +3414,7 @@ var createCanvasStore = (opts = {}) => {
|
|
|
3401
3414
|
return prev;
|
|
3402
3415
|
};
|
|
3403
3416
|
const populateInitial = (scene) => {
|
|
3417
|
+
const seededFrameOrder = [];
|
|
3404
3418
|
for (const id of Object.keys(scene.nodes)) {
|
|
3405
3419
|
const node = scene.nodes[id];
|
|
3406
3420
|
if (!node) continue;
|
|
@@ -3408,7 +3422,9 @@ var createCanvasStore = (opts = {}) => {
|
|
|
3408
3422
|
nodeAtoms.set(node.id, a);
|
|
3409
3423
|
nodeIdsAtom.update((ids) => [...ids, node.id]);
|
|
3410
3424
|
reindexNode(node);
|
|
3425
|
+
if (node.type === "frame") seededFrameOrder.push(node.id);
|
|
3411
3426
|
}
|
|
3427
|
+
if (!scene.frameOrder) frameOrderAtom.set(seededFrameOrder);
|
|
3412
3428
|
for (const id of Object.keys(scene.edges)) {
|
|
3413
3429
|
const edge = scene.edges[id];
|
|
3414
3430
|
if (!edge) continue;
|
|
@@ -3710,6 +3726,53 @@ var createCanvasStore = (opts = {}) => {
|
|
|
3710
3726
|
getNodeCount: () => nodeIdsAtom.value.length,
|
|
3711
3727
|
getEdgeCount: () => edgeIdsAtom.value.length,
|
|
3712
3728
|
getGroupCount: () => groupIdsAtom.value.length,
|
|
3729
|
+
getFrames: () => {
|
|
3730
|
+
const out = [];
|
|
3731
|
+
for (const id of frameOrderAtom.value) {
|
|
3732
|
+
const n = nodeAtoms.get(id)?.value;
|
|
3733
|
+
if (n && n.type === "frame") out.push(n);
|
|
3734
|
+
}
|
|
3735
|
+
return out;
|
|
3736
|
+
},
|
|
3737
|
+
setFrameOrder(ids) {
|
|
3738
|
+
const valid = /* @__PURE__ */ new Set();
|
|
3739
|
+
for (const a of nodeAtoms.values()) {
|
|
3740
|
+
if (a.value.type === "frame") valid.add(a.value.id);
|
|
3741
|
+
}
|
|
3742
|
+
const filtered = [];
|
|
3743
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3744
|
+
for (const id of ids) {
|
|
3745
|
+
if (valid.has(id) && !seen.has(id)) {
|
|
3746
|
+
filtered.push(id);
|
|
3747
|
+
seen.add(id);
|
|
3748
|
+
}
|
|
3749
|
+
}
|
|
3750
|
+
for (const id of valid) {
|
|
3751
|
+
if (!seen.has(id)) filtered.push(id);
|
|
3752
|
+
}
|
|
3753
|
+
const prev = [...frameOrderAtom.value];
|
|
3754
|
+
if (filtered.length === prev.length && filtered.every((id, i) => id === prev[i])) {
|
|
3755
|
+
return;
|
|
3756
|
+
}
|
|
3757
|
+
enqueueOp({ type: "frame.reorder", ids: filtered, prev });
|
|
3758
|
+
},
|
|
3759
|
+
getNodesInFrame(id) {
|
|
3760
|
+
const frame = nodeAtoms.get(id)?.value;
|
|
3761
|
+
if (!frame || frame.type !== "frame") return [];
|
|
3762
|
+
const frameAabb = nodeAABB(frame);
|
|
3763
|
+
const candidates = nodeIndex.queryRect(frameAabb);
|
|
3764
|
+
const out = [];
|
|
3765
|
+
for (const cid of candidates) {
|
|
3766
|
+
if (cid === id) continue;
|
|
3767
|
+
const node = nodeAtoms.get(cid)?.value;
|
|
3768
|
+
if (!node || node.type === "frame") continue;
|
|
3769
|
+
const a = nodeAABB(node);
|
|
3770
|
+
if (a.x >= frameAabb.x && a.y >= frameAabb.y && a.x + a.w <= frameAabb.x + frameAabb.w && a.y + a.h <= frameAabb.y + frameAabb.h) {
|
|
3771
|
+
out.push(node);
|
|
3772
|
+
}
|
|
3773
|
+
}
|
|
3774
|
+
return out;
|
|
3775
|
+
},
|
|
3713
3776
|
getEdgeGeometry(id) {
|
|
3714
3777
|
const edge = edgeAtoms.get(id)?.value;
|
|
3715
3778
|
if (!edge) return void 0;
|
|
@@ -3871,7 +3934,8 @@ var toSerialized = (scene) => ({
|
|
|
3871
3934
|
edges: Object.values(scene.edges),
|
|
3872
3935
|
groups: Object.values(scene.groups),
|
|
3873
3936
|
camera: scene.camera,
|
|
3874
|
-
selection: scene.selection
|
|
3937
|
+
selection: scene.selection,
|
|
3938
|
+
...scene.frameOrder && scene.frameOrder.length > 0 ? { frameOrder: scene.frameOrder } : {}
|
|
3875
3939
|
});
|
|
3876
3940
|
var fromSerialized = (raw) => {
|
|
3877
3941
|
let working = raw;
|
|
@@ -3893,7 +3957,8 @@ var fromSerialized = (raw) => {
|
|
|
3893
3957
|
edges: Object.fromEntries(ser.edges.map((e) => [asEdgeId(e.id), e])),
|
|
3894
3958
|
groups: Object.fromEntries(ser.groups.map((g) => [asGroupId(g.id), g])),
|
|
3895
3959
|
camera: ser.camera,
|
|
3896
|
-
selection: ser.selection
|
|
3960
|
+
selection: ser.selection,
|
|
3961
|
+
...ser.frameOrder ? { frameOrder: ser.frameOrder } : {}
|
|
3897
3962
|
};
|
|
3898
3963
|
};
|
|
3899
3964
|
var storeToJSON = (store) => ({
|
|
@@ -4438,6 +4503,40 @@ var drawMarquee = (ctx, rect, scale, color) => {
|
|
|
4438
4503
|
ctx.restore();
|
|
4439
4504
|
};
|
|
4440
4505
|
|
|
4506
|
+
// src/render/paint-frame.ts
|
|
4507
|
+
var FRAME_BORDER_PX = 1.5;
|
|
4508
|
+
var FRAME_BORDER_COLOR_DEFAULT = "#94a3b8";
|
|
4509
|
+
var FRAME_FILL_DEFAULT = "rgba(148, 163, 184, 0.06)";
|
|
4510
|
+
var FRAME_LABEL_FONT_PX = 12;
|
|
4511
|
+
var FRAME_LABEL_GAP_PX = 6;
|
|
4512
|
+
var FRAME_LABEL_COLOR = "#64748b";
|
|
4513
|
+
var paintFrameNode = (ctx, node, scale, theme) => {
|
|
4514
|
+
if (node.w <= 0 || node.h <= 0) return;
|
|
4515
|
+
const opacity = resolveOpacity(node.style, theme);
|
|
4516
|
+
const needsScope = opacity !== 1;
|
|
4517
|
+
if (needsScope) {
|
|
4518
|
+
ctx.save();
|
|
4519
|
+
ctx.globalAlpha = opacity;
|
|
4520
|
+
}
|
|
4521
|
+
const fill = node.style?.backgroundColor ?? (theme ? theme("frame.background") : void 0) ?? FRAME_FILL_DEFAULT;
|
|
4522
|
+
ctx.fillStyle = fill;
|
|
4523
|
+
ctx.fillRect(0, 0, node.w, node.h);
|
|
4524
|
+
const stroke = resolveColor(node.style, "strokeColor", FRAME_BORDER_COLOR_DEFAULT, theme);
|
|
4525
|
+
ctx.strokeStyle = stroke;
|
|
4526
|
+
ctx.lineWidth = FRAME_BORDER_PX / scale;
|
|
4527
|
+
ctx.setLineDash([]);
|
|
4528
|
+
ctx.strokeRect(0, 0, node.w, node.h);
|
|
4529
|
+
const labelPx = FRAME_LABEL_FONT_PX / scale;
|
|
4530
|
+
const gapPx = FRAME_LABEL_GAP_PX / scale;
|
|
4531
|
+
const label = node.content?.trim() || "Frame";
|
|
4532
|
+
ctx.fillStyle = FRAME_LABEL_COLOR;
|
|
4533
|
+
ctx.textBaseline = "bottom";
|
|
4534
|
+
ctx.textAlign = "left";
|
|
4535
|
+
ctx.font = `500 ${labelPx}px system-ui, -apple-system, sans-serif`;
|
|
4536
|
+
ctx.fillText(label, 0, -gapPx);
|
|
4537
|
+
if (needsScope) ctx.restore();
|
|
4538
|
+
};
|
|
4539
|
+
|
|
4441
4540
|
// src/render/shapes/content-bounds.ts
|
|
4442
4541
|
var SQRT2_INV = 1 / Math.SQRT2;
|
|
4443
4542
|
var contentBounds = (node) => {
|
|
@@ -4511,6 +4610,7 @@ var createRenderer = (opts) => {
|
|
|
4511
4610
|
const interactiveSurface = setupSurface(opts.interactiveCanvas);
|
|
4512
4611
|
let background = opts.background;
|
|
4513
4612
|
let selectionColor = opts.selectionColor ?? DEFAULT_SELECTION_COLOR;
|
|
4613
|
+
let hideFrames = false;
|
|
4514
4614
|
sizeSurface(staticSurface, opts.width, opts.height);
|
|
4515
4615
|
sizeSurface(interactiveSurface, opts.width, opts.height);
|
|
4516
4616
|
let staticDirty = true;
|
|
@@ -4560,7 +4660,18 @@ var createRenderer = (opts) => {
|
|
|
4560
4660
|
const cameraIsMoving = interaction.mode === "panning" || interaction.mode === "zooming";
|
|
4561
4661
|
const movingNodeCount = excludedNodes?.size ?? 0;
|
|
4562
4662
|
const roughEnabled = !cameraIsMoving && movingNodeCount <= ROUGH_MAX_MOVING_NODES && camera.z >= ROUGH_MIN_ZOOM && visible.length <= ROUGH_MAX_NODES;
|
|
4663
|
+
if (!hideFrames) {
|
|
4664
|
+
for (const node of visible) {
|
|
4665
|
+
if (node.type !== "frame") continue;
|
|
4666
|
+
if (excludedNodes?.has(node.id)) continue;
|
|
4667
|
+
drawWithNodeTransform(staticSurface.ctx, node, () => {
|
|
4668
|
+
paintFrameNode(staticSurface.ctx, node, scale, theme);
|
|
4669
|
+
});
|
|
4670
|
+
drawn++;
|
|
4671
|
+
}
|
|
4672
|
+
}
|
|
4563
4673
|
for (const node of visible) {
|
|
4674
|
+
if (node.type === "frame") continue;
|
|
4564
4675
|
if (excludedNodes?.has(node.id)) continue;
|
|
4565
4676
|
const isEditingThis = editingNodeId === node.id;
|
|
4566
4677
|
if (isDrawablePrimitive(node.type)) {
|
|
@@ -4763,9 +4874,13 @@ var createRenderer = (opts) => {
|
|
|
4763
4874
|
isMoving: true};
|
|
4764
4875
|
const dragRoughEnabled = inDragMap.size <= ROUGH_MAX_MOVING_NODES && camera.z >= ROUGH_MIN_ZOOM;
|
|
4765
4876
|
for (const node of inDragMap.values()) {
|
|
4766
|
-
if (!isDrawablePrimitive(node.type) && node.type !== "text" && node.type !== "image" && node.type !== "icon")
|
|
4877
|
+
if (!isDrawablePrimitive(node.type) && node.type !== "text" && node.type !== "image" && node.type !== "icon" && node.type !== "frame")
|
|
4767
4878
|
continue;
|
|
4768
4879
|
drawWithNodeTransform(ctx, node, () => {
|
|
4880
|
+
if (node.type === "frame") {
|
|
4881
|
+
paintFrameNode(ctx, node, scale, theme);
|
|
4882
|
+
return;
|
|
4883
|
+
}
|
|
4769
4884
|
if (node.type === "image") {
|
|
4770
4885
|
paintImageNode(ctx, node, assetCache, theme);
|
|
4771
4886
|
return;
|
|
@@ -4967,6 +5082,11 @@ var createRenderer = (opts) => {
|
|
|
4967
5082
|
interactiveDirty = true;
|
|
4968
5083
|
loop.requestFrame();
|
|
4969
5084
|
},
|
|
5085
|
+
setHideFrames(hidden) {
|
|
5086
|
+
hideFrames = hidden;
|
|
5087
|
+
staticDirty = true;
|
|
5088
|
+
loop.requestFrame();
|
|
5089
|
+
},
|
|
4970
5090
|
stats: () => loop.stats(),
|
|
4971
5091
|
lastDrawCount: () => lastDrawn,
|
|
4972
5092
|
getOverlaySet: () => [...overlaySet],
|
|
@@ -4997,6 +5117,7 @@ var sceneBounds = (store) => {
|
|
|
4997
5117
|
let maxY = Number.NEGATIVE_INFINITY;
|
|
4998
5118
|
for (const n of nodes) {
|
|
4999
5119
|
if (n.hidden) continue;
|
|
5120
|
+
if (n.type === "frame") continue;
|
|
5000
5121
|
const r = nodeAABB(n);
|
|
5001
5122
|
if (r.x < minX) minX = r.x;
|
|
5002
5123
|
if (r.y < minY) minY = r.y;
|
|
@@ -5027,6 +5148,7 @@ var renderMinimapContent = (ctx, store, mapWidth, mapHeight, opts = {}) => {
|
|
|
5027
5148
|
const defaultColor = opts.defaultNodeColor ?? "#94a3b8";
|
|
5028
5149
|
for (const node of store.getAllNodes()) {
|
|
5029
5150
|
if (node.hidden) continue;
|
|
5151
|
+
if (node.type === "frame") continue;
|
|
5030
5152
|
const r = nodeAABB(node);
|
|
5031
5153
|
const x = offX + (r.x - bx) * scale;
|
|
5032
5154
|
const y = offY + (r.y - by) * scale;
|