@cnvx/nodal 0.3.2 → 0.4.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/BaseEdge.svelte +2 -0
- package/dist/Surface.svelte +52 -20
- package/dist/Surface.svelte.d.ts +10 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/layout-utils.d.ts +31 -0
- package/dist/layout-utils.js +44 -0
- package/dist/svelte-actions.d.ts +21 -2
- package/dist/svelte-actions.js +66 -38
- package/package.json +1 -1
package/dist/BaseEdge.svelte
CHANGED
package/dist/Surface.svelte
CHANGED
|
@@ -12,6 +12,11 @@
|
|
|
12
12
|
} from "./diagram-lib.js";
|
|
13
13
|
import type { HTMLAttributes } from "svelte/elements";
|
|
14
14
|
import type { Writable } from "svelte/store";
|
|
15
|
+
import {
|
|
16
|
+
getNodeAnchorFast,
|
|
17
|
+
getNodeAnchorWithBoundingClientRect,
|
|
18
|
+
} from "./layout-utils.js";
|
|
19
|
+
import { fade } from "svelte/transition";
|
|
15
20
|
|
|
16
21
|
type PathGenParams =
|
|
17
22
|
| {
|
|
@@ -32,25 +37,44 @@
|
|
|
32
37
|
svgPathAttributes?: HTMLAttributes<SVGPathElement>;
|
|
33
38
|
} & PathGenParams;
|
|
34
39
|
|
|
40
|
+
// export type SurfaceEdge = {
|
|
41
|
+
|
|
42
|
+
// } & SurfaceEdgeParams;
|
|
43
|
+
|
|
44
|
+
export interface PrecalculatedEdge {
|
|
45
|
+
x1: number;
|
|
46
|
+
y1: number;
|
|
47
|
+
x2: number;
|
|
48
|
+
y2: number;
|
|
49
|
+
}
|
|
50
|
+
|
|
35
51
|
export type SurfaceEdge = {
|
|
36
52
|
source: string | HTMLElement | string[] | HTMLElement[];
|
|
37
53
|
target: string | HTMLElement | string[] | HTMLElement[];
|
|
54
|
+
|
|
55
|
+
precalculated?: PrecalculatedEdge;
|
|
38
56
|
} & SurfaceEdgeParams;
|
|
39
57
|
</script>
|
|
40
58
|
|
|
41
59
|
<script lang="ts">
|
|
60
|
+
let svg: SVGSVGElement | undefined = $state();
|
|
61
|
+
|
|
42
62
|
const {
|
|
43
63
|
hostElement,
|
|
44
64
|
edges: _edges,
|
|
45
65
|
svgAttributes,
|
|
46
66
|
width: _width,
|
|
47
67
|
height: _height,
|
|
68
|
+
fadeInDuration = 80,
|
|
69
|
+
getNodeAnchor = getNodeAnchorWithBoundingClientRect,
|
|
48
70
|
}: {
|
|
49
71
|
hostElement: Element;
|
|
50
72
|
edges: SvelteMap<string, SurfaceEdge>;
|
|
51
73
|
width?: Writable<number>;
|
|
52
74
|
height?: Writable<number>;
|
|
53
75
|
svgAttributes?: HTMLAttributes<SVGElement>;
|
|
76
|
+
getNodeAnchor?: typeof getNodeAnchorFast;
|
|
77
|
+
fadeInDuration?: number;
|
|
54
78
|
} = $props();
|
|
55
79
|
|
|
56
80
|
export const width = _width;
|
|
@@ -158,6 +182,18 @@
|
|
|
158
182
|
}
|
|
159
183
|
|
|
160
184
|
function generateEdgePath(edge: SurfaceEdge) {
|
|
185
|
+
if (!svg) return [];
|
|
186
|
+
|
|
187
|
+
if (edge.precalculated) {
|
|
188
|
+
return generateCurvePath(
|
|
189
|
+
edge.precalculated.x1,
|
|
190
|
+
edge.precalculated.y1,
|
|
191
|
+
edge.precalculated.x2,
|
|
192
|
+
edge.precalculated.y2,
|
|
193
|
+
edge,
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
|
|
161
197
|
const sourceNodes = getNodes(edge.source);
|
|
162
198
|
const targetNodes = getNodes(edge.target);
|
|
163
199
|
|
|
@@ -175,10 +211,12 @@
|
|
|
175
211
|
const sourceAnchor = getNodeAnchor(
|
|
176
212
|
source,
|
|
177
213
|
edge.sourceAnchor ?? Anchor.CENTER_CENTER,
|
|
214
|
+
svg!,
|
|
178
215
|
);
|
|
179
216
|
const targetAnchor = getNodeAnchor(
|
|
180
217
|
target,
|
|
181
218
|
edge.targetAnchor ?? Anchor.CENTER_CENTER,
|
|
219
|
+
svg!,
|
|
182
220
|
);
|
|
183
221
|
paths.push(
|
|
184
222
|
generateCurvePath(
|
|
@@ -203,29 +241,23 @@
|
|
|
203
241
|
|
|
204
242
|
return paths;
|
|
205
243
|
}
|
|
206
|
-
|
|
207
|
-
function getNodeAnchor(node: HTMLElement, anchor: Vector2) {
|
|
208
|
-
return {
|
|
209
|
-
left: node.offsetLeft + anchor.x * node.clientWidth,
|
|
210
|
-
top: node.offsetTop + anchor.y * node.clientHeight,
|
|
211
|
-
};
|
|
212
|
-
}
|
|
213
244
|
</script>
|
|
214
245
|
|
|
246
|
+
<!-- style="position:absolute;top:0;right:0;bottom:0;left:0;height:100%;width:100%;overflow:visible;z-index:0;" -->
|
|
215
247
|
<svg
|
|
216
|
-
|
|
217
|
-
|
|
248
|
+
bind:this={svg}
|
|
249
|
+
preserveAspectRatio="none"
|
|
250
|
+
style="position: absolute; inset: 0px; height: 1px; width: 1px; overflow: visible; z-index: 0;"
|
|
218
251
|
{...svgAttributes}
|
|
219
252
|
>
|
|
220
|
-
{#
|
|
221
|
-
{
|
|
222
|
-
{
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
{/each}
|
|
253
|
+
{#if svg}
|
|
254
|
+
<g in:fade={{ duration: fadeInDuration }}>
|
|
255
|
+
{#each edges.values() as edge}
|
|
256
|
+
{#each generateEdgePath(edge) as path}
|
|
257
|
+
{@const EdgeComponent = edge.component ?? BaseEdge}
|
|
258
|
+
<EdgeComponent {path} {edge} />
|
|
259
|
+
{/each}
|
|
260
|
+
{/each}
|
|
261
|
+
</g>
|
|
262
|
+
{/if}
|
|
231
263
|
</svg>
|
package/dist/Surface.svelte.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { SvelteMap } from "svelte/reactivity";
|
|
|
3
3
|
import { type Vector2 } from "./diagram-lib.js";
|
|
4
4
|
import type { HTMLAttributes } from "svelte/elements";
|
|
5
5
|
import type { Writable } from "svelte/store";
|
|
6
|
+
import { getNodeAnchorFast } from "./layout-utils.js";
|
|
6
7
|
type PathGenParams = {
|
|
7
8
|
pathGen?: "bezier";
|
|
8
9
|
curvature?: number;
|
|
@@ -18,9 +19,16 @@ export type SurfaceEdgeParams = {
|
|
|
18
19
|
targetAnchor?: Vector2;
|
|
19
20
|
svgPathAttributes?: HTMLAttributes<SVGPathElement>;
|
|
20
21
|
} & PathGenParams;
|
|
22
|
+
export interface PrecalculatedEdge {
|
|
23
|
+
x1: number;
|
|
24
|
+
y1: number;
|
|
25
|
+
x2: number;
|
|
26
|
+
y2: number;
|
|
27
|
+
}
|
|
21
28
|
export type SurfaceEdge = {
|
|
22
29
|
source: string | HTMLElement | string[] | HTMLElement[];
|
|
23
30
|
target: string | HTMLElement | string[] | HTMLElement[];
|
|
31
|
+
precalculated?: PrecalculatedEdge;
|
|
24
32
|
} & SurfaceEdgeParams;
|
|
25
33
|
type $$ComponentProps = {
|
|
26
34
|
hostElement: Element;
|
|
@@ -28,6 +36,8 @@ type $$ComponentProps = {
|
|
|
28
36
|
width?: Writable<number>;
|
|
29
37
|
height?: Writable<number>;
|
|
30
38
|
svgAttributes?: HTMLAttributes<SVGElement>;
|
|
39
|
+
getNodeAnchor?: typeof getNodeAnchorFast;
|
|
40
|
+
fadeInDuration?: number;
|
|
31
41
|
};
|
|
32
42
|
declare const Surface: import("svelte").Component<$$ComponentProps, {
|
|
33
43
|
width: Writable<number> | undefined;
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { Vector2 } from "./diagram-lib.js";
|
|
2
|
+
export declare function getElementQuad(el: Element): DOMQuad | {
|
|
3
|
+
p1: {
|
|
4
|
+
x: number;
|
|
5
|
+
y: number;
|
|
6
|
+
};
|
|
7
|
+
p2: {
|
|
8
|
+
x: number;
|
|
9
|
+
y: number;
|
|
10
|
+
};
|
|
11
|
+
p3: {
|
|
12
|
+
x: number;
|
|
13
|
+
y: number;
|
|
14
|
+
};
|
|
15
|
+
p4: {
|
|
16
|
+
x: number;
|
|
17
|
+
y: number;
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
export declare function toSvgPoint(svg: SVGGraphicsElement, x: number, y: number): {
|
|
21
|
+
left: number;
|
|
22
|
+
top: number;
|
|
23
|
+
};
|
|
24
|
+
export declare function getNodeAnchorWithBoundingClientRect(el: Element, anchor: Vector2, svg: SVGGraphicsElement): {
|
|
25
|
+
left: number;
|
|
26
|
+
top: number;
|
|
27
|
+
};
|
|
28
|
+
export declare function getNodeAnchorFast(node: HTMLElement, anchor: Vector2, svgElement: SVGGraphicsElement): {
|
|
29
|
+
left: number;
|
|
30
|
+
top: number;
|
|
31
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export function getElementQuad(el) {
|
|
2
|
+
if (el.getBoxQuads) {
|
|
3
|
+
const quads = el.getBoxQuads();
|
|
4
|
+
if (quads && quads.length)
|
|
5
|
+
return quads[0];
|
|
6
|
+
}
|
|
7
|
+
// Fallback to rect -> quad
|
|
8
|
+
const r = el.getBoundingClientRect();
|
|
9
|
+
return {
|
|
10
|
+
p1: { x: r.left, y: r.top },
|
|
11
|
+
p2: { x: r.right, y: r.top },
|
|
12
|
+
p3: { x: r.right, y: r.bottom },
|
|
13
|
+
p4: { x: r.left, y: r.bottom },
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export function toSvgPoint(svg, x, y) {
|
|
17
|
+
const ctm = svg.getScreenCTM();
|
|
18
|
+
if (!ctm)
|
|
19
|
+
return { left: x, top: y };
|
|
20
|
+
const inv = ctm.inverse();
|
|
21
|
+
const p = new DOMPoint(x, y).matrixTransform(inv);
|
|
22
|
+
return { left: p.x, top: p.y };
|
|
23
|
+
}
|
|
24
|
+
export function getNodeAnchorWithBoundingClientRect(el, anchor, svg) {
|
|
25
|
+
const q = getElementQuad(el);
|
|
26
|
+
// Clamp to [0,1] to avoid surprises.
|
|
27
|
+
const ax = Math.max(0, Math.min(1, anchor.x));
|
|
28
|
+
const ay = Math.max(0, Math.min(1, anchor.y));
|
|
29
|
+
// Bilinear interpolation across the quad:
|
|
30
|
+
// p(ax,ay) = (1-ax)(1-ay) * p1 + ax(1-ay) * p2 + ax*ay * p3 + (1-ax)ay * p4
|
|
31
|
+
const w1 = (1 - ax) * (1 - ay);
|
|
32
|
+
const w2 = ax * (1 - ay);
|
|
33
|
+
const w3 = ax * ay;
|
|
34
|
+
const w4 = (1 - ax) * ay;
|
|
35
|
+
const sx = w1 * q.p1.x + w2 * q.p2.x + w3 * q.p3.x + w4 * q.p4.x;
|
|
36
|
+
const sy = w1 * q.p1.y + w2 * q.p2.y + w3 * q.p3.y + w4 * q.p4.y;
|
|
37
|
+
return toSvgPoint(svg, sx, sy);
|
|
38
|
+
}
|
|
39
|
+
export function getNodeAnchorFast(node, anchor, svgElement) {
|
|
40
|
+
return {
|
|
41
|
+
left: node.offsetLeft + anchor.x * node.clientWidth,
|
|
42
|
+
top: node.offsetTop + anchor.y * node.clientHeight,
|
|
43
|
+
};
|
|
44
|
+
}
|
package/dist/svelte-actions.d.ts
CHANGED
|
@@ -1,13 +1,32 @@
|
|
|
1
1
|
import type { Attachment } from "svelte/attachments";
|
|
2
2
|
import type { HTMLAttributes } from "svelte/elements";
|
|
3
|
-
import
|
|
3
|
+
import { SvelteMap } from "svelte/reactivity";
|
|
4
|
+
import { type Writable } from "svelte/store";
|
|
5
|
+
import type { SurfaceEdge, SurfaceEdgeParams } from "./Surface.svelte";
|
|
6
|
+
import type { Vector2 } from "./diagram-lib.js";
|
|
4
7
|
type IndividualConnectActionParam = string | (SurfaceEdgeParams & {
|
|
5
8
|
target: string | string[];
|
|
6
9
|
}) | (SurfaceEdgeParams & {
|
|
7
10
|
source: string | string[];
|
|
8
11
|
});
|
|
9
|
-
|
|
12
|
+
interface NodalSurfaceElement extends HTMLElement {
|
|
13
|
+
_nodalSurface: {
|
|
14
|
+
internals: {
|
|
15
|
+
width: Writable<number> | undefined;
|
|
16
|
+
height: Writable<number> | undefined;
|
|
17
|
+
edges: SvelteMap<string, SurfaceEdge>;
|
|
18
|
+
};
|
|
19
|
+
schedule: () => void;
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
export declare function createNodal({ svgAttributes, getNodeAnchor, fadeInDuration, onMount, }?: Partial<{
|
|
10
23
|
svgAttributes: HTMLAttributes<SVGElement>;
|
|
24
|
+
onMount: (engine: NodalSurfaceElement["_nodalSurface"]) => void;
|
|
25
|
+
getNodeAnchor: (node: HTMLElement, anchor: Vector2, svgElement: SVGElement) => {
|
|
26
|
+
top: number;
|
|
27
|
+
left: number;
|
|
28
|
+
};
|
|
29
|
+
fadeInDuration: number;
|
|
11
30
|
}>): Attachment;
|
|
12
31
|
export declare function connectTo(...edges: IndividualConnectActionParam[]): Attachment;
|
|
13
32
|
export {};
|
package/dist/svelte-actions.js
CHANGED
|
@@ -3,38 +3,36 @@ import { SvelteMap } from "svelte/reactivity";
|
|
|
3
3
|
import { writable } from "svelte/store";
|
|
4
4
|
import Surface from "./Surface.svelte";
|
|
5
5
|
function drawCall(surface) {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
6
|
+
// console.debug("Drawing nodal surface edges...");
|
|
7
|
+
const connections = surface.querySelectorAll("[data-nodal-connected='true']");
|
|
8
|
+
connections.forEach((conn) => {
|
|
9
|
+
// console.debug("Processing connection for:", conn);
|
|
10
|
+
const host = conn;
|
|
11
|
+
if (!conn._nodalEdgeConnections ||
|
|
12
|
+
conn._nodalEdgeConnections.length === 0) {
|
|
13
|
+
// console.debug("No edges found for host:", host);
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
conn._nodalEdgeConnections.forEach((edge) => {
|
|
17
|
+
let edgeDef;
|
|
18
|
+
if (typeof edge == "string") {
|
|
19
|
+
edgeDef = {
|
|
20
|
+
source: host,
|
|
21
|
+
target: edge,
|
|
22
|
+
};
|
|
16
23
|
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
else {
|
|
26
|
-
edgeDef = {
|
|
27
|
-
source: "source" in edge ? edge.source : host,
|
|
28
|
-
target: "target" in edge ? edge.target : host,
|
|
29
|
-
...edge,
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
surface._nodalSurface.edges.set(`${edgeDef.source}->${edgeDef.target}`, edgeDef);
|
|
33
|
-
});
|
|
24
|
+
else {
|
|
25
|
+
edgeDef = {
|
|
26
|
+
source: "source" in edge ? edge.source : host,
|
|
27
|
+
target: "target" in edge ? edge.target : host,
|
|
28
|
+
...edge,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
surface._nodalSurface.internals.edges.set(`${edgeDef.source}->${edgeDef.target}`, edgeDef);
|
|
34
32
|
});
|
|
35
33
|
});
|
|
36
34
|
}
|
|
37
|
-
export function createNodal({ svgAttributes = {}, } = {}) {
|
|
35
|
+
export function createNodal({ svgAttributes = {}, getNodeAnchor, fadeInDuration, onMount, } = {}) {
|
|
38
36
|
return (element) => {
|
|
39
37
|
console.debug("Creating nodal sruface..");
|
|
40
38
|
if (!Object.hasOwn(element, "_nodalSurface")) {
|
|
@@ -47,23 +45,53 @@ export function createNodal({ svgAttributes = {}, } = {}) {
|
|
|
47
45
|
width: writable(element.clientWidth),
|
|
48
46
|
height: writable(element.clientHeight),
|
|
49
47
|
svgAttributes,
|
|
48
|
+
getNodeAnchor,
|
|
49
|
+
fadeInDuration,
|
|
50
50
|
},
|
|
51
51
|
});
|
|
52
|
+
const engine = {
|
|
53
|
+
_isScheduled: false,
|
|
54
|
+
draw: () => drawCall(element),
|
|
55
|
+
schedule() {
|
|
56
|
+
if (engine._isScheduled)
|
|
57
|
+
return;
|
|
58
|
+
engine._isScheduled = true;
|
|
59
|
+
requestAnimationFrame(() => {
|
|
60
|
+
this._isScheduled = false;
|
|
61
|
+
drawCall(element);
|
|
62
|
+
});
|
|
63
|
+
},
|
|
64
|
+
internals: exports
|
|
65
|
+
};
|
|
52
66
|
Object.defineProperty(element, "_nodalSurface", {
|
|
53
|
-
value:
|
|
67
|
+
value: engine,
|
|
54
68
|
});
|
|
55
69
|
const resizeObserver = new ResizeObserver((entries) => {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
70
|
+
requestAnimationFrame(() => {
|
|
71
|
+
for (let entry of entries) {
|
|
72
|
+
const { width, height } = entry.contentRect;
|
|
73
|
+
// console.debug(
|
|
74
|
+
// "Resizing nodal surface to:",
|
|
75
|
+
// width,
|
|
76
|
+
// height,
|
|
77
|
+
// );
|
|
78
|
+
exports.width?.set(width);
|
|
79
|
+
exports.height?.set(height);
|
|
80
|
+
// redraw edges
|
|
81
|
+
// drawCall(element as NodalSurfaceElement);
|
|
82
|
+
engine.schedule();
|
|
83
|
+
}
|
|
84
|
+
});
|
|
64
85
|
});
|
|
86
|
+
try {
|
|
87
|
+
onMount?.(engine);
|
|
88
|
+
}
|
|
89
|
+
catch (e) {
|
|
90
|
+
console.error("Error in onMount callback for nodal surface:", e);
|
|
91
|
+
}
|
|
65
92
|
resizeObserver.observe(element);
|
|
66
|
-
|
|
93
|
+
engine.schedule();
|
|
94
|
+
// drawCall(element as NodalSurfaceElement);
|
|
67
95
|
}
|
|
68
96
|
};
|
|
69
97
|
}
|