@poodle64/ui 2026.10.16 → 2026.10.17
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/README.md +41 -0
- package/dist/components/ui/graph-canvas/graph-canvas.svelte +922 -0
- package/dist/components/ui/graph-canvas/graph-canvas.svelte.d.ts +73 -0
- package/dist/components/ui/graph-canvas/index.d.ts +3 -0
- package/dist/components/ui/graph-canvas/index.js +2 -0
- package/dist/components/ui/graph-canvas/scene.d.ts +107 -0
- package/dist/components/ui/graph-canvas/scene.js +562 -0
- package/dist/components/ui/graph-canvas/types.d.ts +33 -0
- package/dist/components/ui/graph-canvas/types.js +1 -0
- package/package.json +4 -2
- package/registry/component-map.json +17 -3
- package/registry/component-map.md +9 -1
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A live force-directed graph of entities and relations, drawn on a canvas
|
|
3
|
+
* and themed from the --ds-* tokens of wherever it is placed, a page or an
|
|
4
|
+
* element's shadow root alike. Every colour is resolved against this
|
|
5
|
+
* component's own element, never the document, so a shadow root takes its
|
|
6
|
+
* host page's palette; nothing portals out.
|
|
7
|
+
*/
|
|
8
|
+
import { type Snippet } from 'svelte';
|
|
9
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
10
|
+
import type { GraphEdge, GraphKind, GraphNeighbour, GraphNode } from './types.js';
|
|
11
|
+
declare function $$render<N extends GraphNode>(): {
|
|
12
|
+
props: Omit<HTMLAttributes<HTMLDivElement>, "class" | "onselect"> & {
|
|
13
|
+
nodes: N[];
|
|
14
|
+
edges: GraphEdge[];
|
|
15
|
+
/** Legend order, labels, colours and shapes. Kinds in the data but not here still draw. */
|
|
16
|
+
kinds?: GraphKind[];
|
|
17
|
+
/** The selected node's id. */
|
|
18
|
+
selected?: string | null;
|
|
19
|
+
/** Kind ids the legend has switched off. */
|
|
20
|
+
hiddenKinds?: string[];
|
|
21
|
+
/** Arrowheads at each edge's target. */
|
|
22
|
+
directed?: boolean;
|
|
23
|
+
/** The accessible name. */
|
|
24
|
+
label?: string;
|
|
25
|
+
/** The find field. */
|
|
26
|
+
search?: boolean;
|
|
27
|
+
activateLabel?: string;
|
|
28
|
+
/** A click on a node, a match or a neighbour; `null` when the selection clears. */
|
|
29
|
+
onselect?: (node: N | null) => void;
|
|
30
|
+
/** Double-click, Enter, or the card's action: open the thing the node stands for. */
|
|
31
|
+
onactivate?: (node: N) => void;
|
|
32
|
+
/** Extra content for the selected node's card, under its title. */
|
|
33
|
+
detail?: Snippet<[N, GraphNeighbour<N>[]]>;
|
|
34
|
+
class?: string;
|
|
35
|
+
};
|
|
36
|
+
exports: {
|
|
37
|
+
fit: () => void;
|
|
38
|
+
focus: (id: string) => Promise<void>;
|
|
39
|
+
locate: (id: string) => {
|
|
40
|
+
x: number;
|
|
41
|
+
y: number;
|
|
42
|
+
} | null;
|
|
43
|
+
relayout: () => void;
|
|
44
|
+
};
|
|
45
|
+
bindings: "selected" | "hiddenKinds";
|
|
46
|
+
slots: {};
|
|
47
|
+
events: {};
|
|
48
|
+
};
|
|
49
|
+
declare class __sveltets_Render<N extends GraphNode> {
|
|
50
|
+
props(): ReturnType<typeof $$render<N>>['props'];
|
|
51
|
+
events(): ReturnType<typeof $$render<N>>['events'];
|
|
52
|
+
slots(): ReturnType<typeof $$render<N>>['slots'];
|
|
53
|
+
bindings(): "selected" | "hiddenKinds";
|
|
54
|
+
exports(): {
|
|
55
|
+
fit: () => void;
|
|
56
|
+
focus: (id: string) => Promise<void>;
|
|
57
|
+
locate: (id: string) => {
|
|
58
|
+
x: number;
|
|
59
|
+
y: number;
|
|
60
|
+
} | null;
|
|
61
|
+
relayout: () => void;
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
interface $$IsomorphicComponent {
|
|
65
|
+
new <N extends GraphNode>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<N>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<N>['props']>, ReturnType<__sveltets_Render<N>['events']>, ReturnType<__sveltets_Render<N>['slots']>> & {
|
|
66
|
+
$$bindings?: ReturnType<__sveltets_Render<N>['bindings']>;
|
|
67
|
+
} & ReturnType<__sveltets_Render<N>['exports']>;
|
|
68
|
+
<N extends GraphNode>(internal: unknown, props: ReturnType<__sveltets_Render<N>['props']> & {}): ReturnType<__sveltets_Render<N>['exports']>;
|
|
69
|
+
z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
|
|
70
|
+
}
|
|
71
|
+
declare const GraphCanvas: $$IsomorphicComponent;
|
|
72
|
+
type GraphCanvas<N extends GraphNode> = InstanceType<typeof GraphCanvas<N>>;
|
|
73
|
+
export default GraphCanvas;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The graph's world: d3-force for the layout, a Canvas 2D painter, the camera
|
|
3
|
+
* and hit-testing. No Svelte in here; the component owns the loop, the
|
|
4
|
+
* pointer and the chrome, and calls in.
|
|
5
|
+
*
|
|
6
|
+
* Paint cost is bounded at any size: edges and nodes are culled to the
|
|
7
|
+
* viewport and stroked as a handful of batched paths, labels are placed
|
|
8
|
+
* greedily in screen space against a grid of what is already drawn, and the
|
|
9
|
+
* label pass stops at a fixed budget, so a few thousand nodes paint in a few
|
|
10
|
+
* milliseconds and the layout's own ticks are the cost that dominates.
|
|
11
|
+
*/
|
|
12
|
+
import { type Simulation, type SimulationLinkDatum, type SimulationNodeDatum } from 'd3-force';
|
|
13
|
+
import type { GraphEdge, GraphNode, GraphShape } from './types.js';
|
|
14
|
+
export interface SimNode<N extends GraphNode = GraphNode> extends SimulationNodeDatum {
|
|
15
|
+
id: string;
|
|
16
|
+
node: N;
|
|
17
|
+
x: number;
|
|
18
|
+
y: number;
|
|
19
|
+
r: number;
|
|
20
|
+
degree: number;
|
|
21
|
+
}
|
|
22
|
+
export interface SimEdge<N extends GraphNode = GraphNode> extends SimulationLinkDatum<SimNode<N>> {
|
|
23
|
+
source: SimNode<N>;
|
|
24
|
+
target: SimNode<N>;
|
|
25
|
+
edge: GraphEdge;
|
|
26
|
+
/** Signed curvature: parallel edges between one pair fan out. */
|
|
27
|
+
bend: number;
|
|
28
|
+
}
|
|
29
|
+
/** Every colour already resolved to a value canvas parses. */
|
|
30
|
+
export interface ScenePalette {
|
|
31
|
+
kinds: Map<string, string>;
|
|
32
|
+
fallback: string;
|
|
33
|
+
ground: string;
|
|
34
|
+
ink: string;
|
|
35
|
+
inkSoft: string;
|
|
36
|
+
edge: string;
|
|
37
|
+
font: string;
|
|
38
|
+
}
|
|
39
|
+
export interface Camera {
|
|
40
|
+
k: number;
|
|
41
|
+
x: number;
|
|
42
|
+
y: number;
|
|
43
|
+
}
|
|
44
|
+
export interface Inset {
|
|
45
|
+
top: number;
|
|
46
|
+
right: number;
|
|
47
|
+
bottom: number;
|
|
48
|
+
left: number;
|
|
49
|
+
}
|
|
50
|
+
export declare class GraphScene<N extends GraphNode = GraphNode> {
|
|
51
|
+
nodes: SimNode<N>[];
|
|
52
|
+
edges: SimEdge<N>[];
|
|
53
|
+
byId: Map<string, SimNode<N>>;
|
|
54
|
+
adj: Map<SimNode<N>, Set<SimNode<N>>>;
|
|
55
|
+
/** Label priority, fixed per data set: emphasis, then degree. */
|
|
56
|
+
ranked: SimNode<N>[];
|
|
57
|
+
cam: Camera;
|
|
58
|
+
width: number;
|
|
59
|
+
height: number;
|
|
60
|
+
dpr: number;
|
|
61
|
+
palette: ScenePalette | null;
|
|
62
|
+
shapes: Map<string, GraphShape>;
|
|
63
|
+
hidden: Set<string>;
|
|
64
|
+
directed: boolean;
|
|
65
|
+
selected: SimNode<N> | null;
|
|
66
|
+
hovered: SimNode<N> | null;
|
|
67
|
+
matches: Set<SimNode<N>>;
|
|
68
|
+
readonly sim: Simulation<SimNode<N>, SimEdge<N>>;
|
|
69
|
+
private link;
|
|
70
|
+
private charge;
|
|
71
|
+
private collide;
|
|
72
|
+
private ticks;
|
|
73
|
+
private gx;
|
|
74
|
+
private gy;
|
|
75
|
+
private widths;
|
|
76
|
+
private grid;
|
|
77
|
+
private boxes;
|
|
78
|
+
constructor();
|
|
79
|
+
/** Gravity follows the frame's shape, so a wide view lays out wide and a phone tall. */
|
|
80
|
+
setAspect(width: number, height: number): void;
|
|
81
|
+
/** Replace the data, keeping every surviving node where it is. */
|
|
82
|
+
setData(nodes: readonly N[], edges: readonly GraphEdge[]): number;
|
|
83
|
+
hot(): boolean;
|
|
84
|
+
heat(alpha: number): void;
|
|
85
|
+
/** Advance the layout for at most `budgetMs`, and at least one tick. */
|
|
86
|
+
advance(budgetMs: number): void;
|
|
87
|
+
visible(s: SimNode<N>): boolean;
|
|
88
|
+
neighbourhood(s: SimNode<N>): SimNode<N>[];
|
|
89
|
+
/** The camera that frames `subset` (default: every visible node) inside the inset box. */
|
|
90
|
+
fit(subset?: Iterable<SimNode<N>>, inset?: Inset, maxK?: number): Camera | null;
|
|
91
|
+
toWorld(sx: number, sy: number): [number, number];
|
|
92
|
+
/** Zoom by `factor` about the screen point (sx, sy). */
|
|
93
|
+
zoomAt(sx: number, sy: number, factor: number, min: number, max: number): void;
|
|
94
|
+
/** The node under a screen point, with `slop` px of grace for a finger. */
|
|
95
|
+
pick(sx: number, sy: number, slop: number): SimNode<N> | null;
|
|
96
|
+
clearTextCache(): void;
|
|
97
|
+
draw(ctx: CanvasRenderingContext2D): void;
|
|
98
|
+
colour(s: SimNode<N>): string;
|
|
99
|
+
private control;
|
|
100
|
+
private arrow;
|
|
101
|
+
private shape;
|
|
102
|
+
private measure;
|
|
103
|
+
/** Reserve a screen box unless it overlaps one already reserved. */
|
|
104
|
+
private claim;
|
|
105
|
+
private labels;
|
|
106
|
+
private text;
|
|
107
|
+
}
|