@canvas-harness/core 0.1.9 → 0.1.11
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 +42 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +37 -7
- package/dist/index.d.ts +37 -7
- package/dist/index.js +42 -4
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -2529,15 +2529,36 @@ type SerializedClipboard = {
|
|
|
2529
2529
|
*/
|
|
2530
2530
|
declare const serializeSelection: (store: CanvasStore) => SerializedClipboard;
|
|
2531
2531
|
type DeserializeOptions = {
|
|
2532
|
-
/**
|
|
2532
|
+
/**
|
|
2533
|
+
* Relative world-space offset added to every pasted node's `x/y`
|
|
2534
|
+
* (and to free-floating edge endpoints). Takes precedence over
|
|
2535
|
+
* `at` when both are passed. Default `(20, 20)` when neither is
|
|
2536
|
+
* given.
|
|
2537
|
+
*/
|
|
2533
2538
|
offset?: Vec2;
|
|
2539
|
+
/**
|
|
2540
|
+
* Absolute world-space target — the *center* of the pasted bbox
|
|
2541
|
+
* lands here. Used by `paste()` to place the paste at the cursor;
|
|
2542
|
+
* pass directly for programmatic absolute placement. Ignored if
|
|
2543
|
+
* `offset` is also set.
|
|
2544
|
+
*/
|
|
2545
|
+
at?: Vec2;
|
|
2534
2546
|
/** Override the selection on the store after applying. Default true. */
|
|
2535
2547
|
select?: boolean;
|
|
2536
2548
|
};
|
|
2537
2549
|
/**
|
|
2538
2550
|
* Applies a clipboard payload to the store. New ids are minted; edge
|
|
2539
|
-
* endpoints are rewired;
|
|
2540
|
-
*
|
|
2551
|
+
* endpoints are rewired; the resulting nodes + edges become the new
|
|
2552
|
+
* selection by default. Positioning precedence:
|
|
2553
|
+
*
|
|
2554
|
+
* 1. `opts.offset` (relative) — used as-is.
|
|
2555
|
+
* 2. `opts.at` (absolute) — offset computed so the clip's bbox
|
|
2556
|
+
* center lands on this point.
|
|
2557
|
+
* 3. Default — relative `(20, 20)` offset.
|
|
2558
|
+
*
|
|
2559
|
+
* Free-floating edge endpoints (`{ worldPoint }`) also receive the
|
|
2560
|
+
* offset so an edge with an unattached end stays connected to the
|
|
2561
|
+
* surrounding nodes after the paste.
|
|
2541
2562
|
*
|
|
2542
2563
|
* One `store.batch` — one undo step.
|
|
2543
2564
|
*
|
|
@@ -2577,8 +2598,17 @@ declare const cut: (store: CanvasStore) => Promise<SerializedClipboard>;
|
|
|
2577
2598
|
/**
|
|
2578
2599
|
* Paste from the system clipboard (or a supplied payload). Every node
|
|
2579
2600
|
* + edge gets a fresh id; edge endpoints rewire to the new ids; the
|
|
2580
|
-
*
|
|
2581
|
-
*
|
|
2601
|
+
* resulting nodes + edges become the new selection. Wrapped in one
|
|
2602
|
+
* undoable batch.
|
|
2603
|
+
*
|
|
2604
|
+
* Positioning, in precedence order:
|
|
2605
|
+
* 1. `opts.offset` — relative offset, used as-is.
|
|
2606
|
+
* 2. `opts.at` — absolute target; the paste's bbox center lands here.
|
|
2607
|
+
* 3. The store's current cursor (`interactionState.pointer`) — the
|
|
2608
|
+
* paste lands centered under the cursor. This is the default
|
|
2609
|
+
* `paste(store)` behavior on a Cmd+V keybind.
|
|
2610
|
+
* 4. Fallback `(20, 20)` relative offset when nothing else is known
|
|
2611
|
+
* (e.g. fresh session with no pointermove yet).
|
|
2582
2612
|
*
|
|
2583
2613
|
* Returns the new node ids on success, or `null` if the clipboard
|
|
2584
2614
|
* didn't contain a canvas-harness payload.
|
|
@@ -2587,8 +2617,8 @@ declare const cut: (store: CanvasStore) => Promise<SerializedClipboard>;
|
|
|
2587
2617
|
* <button onClick={() => paste(store)}>Paste</button>
|
|
2588
2618
|
*
|
|
2589
2619
|
* @example
|
|
2590
|
-
* // Programmatic paste
|
|
2591
|
-
* paste(store, savedClip, {
|
|
2620
|
+
* // Programmatic paste at a specific world point:
|
|
2621
|
+
* paste(store, savedClip, { at: { x: 300, y: 200 }, select: false })
|
|
2592
2622
|
*/
|
|
2593
2623
|
declare const paste: (store: CanvasStore, payload?: SerializedClipboard, opts?: DeserializeOptions) => Promise<(NodeId | EdgeId)[] | null>;
|
|
2594
2624
|
|
package/dist/index.d.ts
CHANGED
|
@@ -2529,15 +2529,36 @@ type SerializedClipboard = {
|
|
|
2529
2529
|
*/
|
|
2530
2530
|
declare const serializeSelection: (store: CanvasStore) => SerializedClipboard;
|
|
2531
2531
|
type DeserializeOptions = {
|
|
2532
|
-
/**
|
|
2532
|
+
/**
|
|
2533
|
+
* Relative world-space offset added to every pasted node's `x/y`
|
|
2534
|
+
* (and to free-floating edge endpoints). Takes precedence over
|
|
2535
|
+
* `at` when both are passed. Default `(20, 20)` when neither is
|
|
2536
|
+
* given.
|
|
2537
|
+
*/
|
|
2533
2538
|
offset?: Vec2;
|
|
2539
|
+
/**
|
|
2540
|
+
* Absolute world-space target — the *center* of the pasted bbox
|
|
2541
|
+
* lands here. Used by `paste()` to place the paste at the cursor;
|
|
2542
|
+
* pass directly for programmatic absolute placement. Ignored if
|
|
2543
|
+
* `offset` is also set.
|
|
2544
|
+
*/
|
|
2545
|
+
at?: Vec2;
|
|
2534
2546
|
/** Override the selection on the store after applying. Default true. */
|
|
2535
2547
|
select?: boolean;
|
|
2536
2548
|
};
|
|
2537
2549
|
/**
|
|
2538
2550
|
* Applies a clipboard payload to the store. New ids are minted; edge
|
|
2539
|
-
* endpoints are rewired;
|
|
2540
|
-
*
|
|
2551
|
+
* endpoints are rewired; the resulting nodes + edges become the new
|
|
2552
|
+
* selection by default. Positioning precedence:
|
|
2553
|
+
*
|
|
2554
|
+
* 1. `opts.offset` (relative) — used as-is.
|
|
2555
|
+
* 2. `opts.at` (absolute) — offset computed so the clip's bbox
|
|
2556
|
+
* center lands on this point.
|
|
2557
|
+
* 3. Default — relative `(20, 20)` offset.
|
|
2558
|
+
*
|
|
2559
|
+
* Free-floating edge endpoints (`{ worldPoint }`) also receive the
|
|
2560
|
+
* offset so an edge with an unattached end stays connected to the
|
|
2561
|
+
* surrounding nodes after the paste.
|
|
2541
2562
|
*
|
|
2542
2563
|
* One `store.batch` — one undo step.
|
|
2543
2564
|
*
|
|
@@ -2577,8 +2598,17 @@ declare const cut: (store: CanvasStore) => Promise<SerializedClipboard>;
|
|
|
2577
2598
|
/**
|
|
2578
2599
|
* Paste from the system clipboard (or a supplied payload). Every node
|
|
2579
2600
|
* + edge gets a fresh id; edge endpoints rewire to the new ids; the
|
|
2580
|
-
*
|
|
2581
|
-
*
|
|
2601
|
+
* resulting nodes + edges become the new selection. Wrapped in one
|
|
2602
|
+
* undoable batch.
|
|
2603
|
+
*
|
|
2604
|
+
* Positioning, in precedence order:
|
|
2605
|
+
* 1. `opts.offset` — relative offset, used as-is.
|
|
2606
|
+
* 2. `opts.at` — absolute target; the paste's bbox center lands here.
|
|
2607
|
+
* 3. The store's current cursor (`interactionState.pointer`) — the
|
|
2608
|
+
* paste lands centered under the cursor. This is the default
|
|
2609
|
+
* `paste(store)` behavior on a Cmd+V keybind.
|
|
2610
|
+
* 4. Fallback `(20, 20)` relative offset when nothing else is known
|
|
2611
|
+
* (e.g. fresh session with no pointermove yet).
|
|
2582
2612
|
*
|
|
2583
2613
|
* Returns the new node ids on success, or `null` if the clipboard
|
|
2584
2614
|
* didn't contain a canvas-harness payload.
|
|
@@ -2587,8 +2617,8 @@ declare const cut: (store: CanvasStore) => Promise<SerializedClipboard>;
|
|
|
2587
2617
|
* <button onClick={() => paste(store)}>Paste</button>
|
|
2588
2618
|
*
|
|
2589
2619
|
* @example
|
|
2590
|
-
* // Programmatic paste
|
|
2591
|
-
* paste(store, savedClip, {
|
|
2620
|
+
* // Programmatic paste at a specific world point:
|
|
2621
|
+
* paste(store, savedClip, { at: { x: 300, y: 200 }, select: false })
|
|
2592
2622
|
*/
|
|
2593
2623
|
declare const paste: (store: CanvasStore, payload?: SerializedClipboard, opts?: DeserializeOptions) => Promise<(NodeId | EdgeId)[] | null>;
|
|
2594
2624
|
|
package/dist/index.js
CHANGED
|
@@ -2336,6 +2336,7 @@ var createDefaultTextareaEditor = ({
|
|
|
2336
2336
|
wrap.style.borderRadius = "4px";
|
|
2337
2337
|
wrap.style.background = style.backgroundColor ?? "#ffffff";
|
|
2338
2338
|
wrap.style.zIndex = "20";
|
|
2339
|
+
wrap.style.pointerEvents = "auto";
|
|
2339
2340
|
const ta = document.createElement("textarea");
|
|
2340
2341
|
ta.value = node.content ?? "";
|
|
2341
2342
|
ta.spellcheck = false;
|
|
@@ -4784,7 +4785,13 @@ var paintAtomicRough = (rc, ctx, type, w, h, style, scale, theme, seed) => {
|
|
|
4784
4785
|
strokeWidth,
|
|
4785
4786
|
roughness,
|
|
4786
4787
|
seed,
|
|
4787
|
-
|
|
4788
|
+
// Always pass an explicit array (empty = solid) so rough.js calls
|
|
4789
|
+
// ctx.setLineDash() to a known state. Passing `undefined` makes
|
|
4790
|
+
// rough skip that call, and the canvas inherits whatever the
|
|
4791
|
+
// previous draw left behind — a transparent-stroke node's
|
|
4792
|
+
// fill-derived outline would pick up the dash from an earlier
|
|
4793
|
+
// dashed node in the same paint pass.
|
|
4794
|
+
strokeLineDash: dash,
|
|
4788
4795
|
curveStepCount: detail.curveStepCount,
|
|
4789
4796
|
maxRandomnessOffset: detail.maxRandomnessOffset
|
|
4790
4797
|
});
|
|
@@ -5957,8 +5964,30 @@ var endInside = (end, ids) => {
|
|
|
5957
5964
|
if (!isAttached(end)) return true;
|
|
5958
5965
|
return ids.has(end.nodeId);
|
|
5959
5966
|
};
|
|
5967
|
+
var clipBboxCenter = (nodes) => {
|
|
5968
|
+
if (nodes.length === 0) return { x: 0, y: 0 };
|
|
5969
|
+
let minX = Number.POSITIVE_INFINITY;
|
|
5970
|
+
let minY = Number.POSITIVE_INFINITY;
|
|
5971
|
+
let maxX = Number.NEGATIVE_INFINITY;
|
|
5972
|
+
let maxY = Number.NEGATIVE_INFINITY;
|
|
5973
|
+
for (const n of nodes) {
|
|
5974
|
+
if (n.x < minX) minX = n.x;
|
|
5975
|
+
if (n.y < minY) minY = n.y;
|
|
5976
|
+
if (n.x + n.w > maxX) maxX = n.x + n.w;
|
|
5977
|
+
if (n.y + n.h > maxY) maxY = n.y + n.h;
|
|
5978
|
+
}
|
|
5979
|
+
return { x: (minX + maxX) / 2, y: (minY + maxY) / 2 };
|
|
5980
|
+
};
|
|
5960
5981
|
var deserializeClipboard = (store, clip, opts = {}) => {
|
|
5961
|
-
|
|
5982
|
+
let offset;
|
|
5983
|
+
if (opts.offset) {
|
|
5984
|
+
offset = opts.offset;
|
|
5985
|
+
} else if (opts.at && clip.nodes.length > 0) {
|
|
5986
|
+
const center = clipBboxCenter(clip.nodes);
|
|
5987
|
+
offset = { x: opts.at.x - center.x, y: opts.at.y - center.y };
|
|
5988
|
+
} else {
|
|
5989
|
+
offset = { x: 20, y: 20 };
|
|
5990
|
+
}
|
|
5962
5991
|
const select = opts.select ?? true;
|
|
5963
5992
|
const nodeMap = /* @__PURE__ */ new Map();
|
|
5964
5993
|
const edgeMap = /* @__PURE__ */ new Map();
|
|
@@ -5971,7 +6000,9 @@ var deserializeClipboard = (store, clip, opts = {}) => {
|
|
|
5971
6000
|
y: n.y + offset.y
|
|
5972
6001
|
}));
|
|
5973
6002
|
const remapEnd = (end) => {
|
|
5974
|
-
if (!isAttached(end))
|
|
6003
|
+
if (!isAttached(end)) {
|
|
6004
|
+
return { worldPoint: { x: end.worldPoint.x + offset.x, y: end.worldPoint.y + offset.y } };
|
|
6005
|
+
}
|
|
5975
6006
|
const newId = nodeMap.get(end.nodeId);
|
|
5976
6007
|
return newId ? { nodeId: newId, localOffset: end.localOffset } : end;
|
|
5977
6008
|
};
|
|
@@ -6014,7 +6045,14 @@ var cut = async (store) => {
|
|
|
6014
6045
|
var paste = async (store, payload, opts) => {
|
|
6015
6046
|
const clip = payload ?? await readClipboard();
|
|
6016
6047
|
if (!clip) return null;
|
|
6017
|
-
|
|
6048
|
+
let effective = opts;
|
|
6049
|
+
if (!opts?.offset && !opts?.at) {
|
|
6050
|
+
const pointer = store.getInteractionState().pointer;
|
|
6051
|
+
if (pointer) {
|
|
6052
|
+
effective = { ...opts, at: { x: pointer.worldX, y: pointer.worldY } };
|
|
6053
|
+
}
|
|
6054
|
+
}
|
|
6055
|
+
const ids = deserializeClipboard(store, clip, effective);
|
|
6018
6056
|
return ids;
|
|
6019
6057
|
};
|
|
6020
6058
|
var writeClipboard = async (clip) => {
|