@loradb/lora-graph-canvas 0.10.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/LICENSE +87 -0
- package/README.md +191 -0
- package/THIRD_PARTY.md +40 -0
- package/dist/LoraGraphCanvas.d.ts +9 -0
- package/dist/LoraGraphCanvas.stories.d.ts +23 -0
- package/dist/engines/3d-force-graph/index.d.ts +1 -0
- package/dist/engines/3d-force-graph/kapsule.d.ts +12 -0
- package/dist/engines/createEngineUnified.d.ts +13 -0
- package/dist/engines/propBindings.d.ts +29 -0
- package/dist/engines/rafAnim.d.ts +13 -0
- package/dist/engines/types.d.ts +86 -0
- package/dist/hooks/useAccessorOverrides.d.ts +62 -0
- package/dist/hooks/useAutoIndexNeighbors.d.ts +6 -0
- package/dist/hooks/useClickToleranceShim.d.ts +46 -0
- package/dist/hooks/useGraphClipboard.d.ts +47 -0
- package/dist/hooks/useGraphData.d.ts +42 -0
- package/dist/hooks/useGraphEngine.d.ts +23 -0
- package/dist/hooks/useGraphForces.d.ts +12 -0
- package/dist/hooks/useGraphKeybindings.d.ts +27 -0
- package/dist/hooks/useGraphSelection.d.ts +14 -0
- package/dist/hooks/useHoverState.d.ts +67 -0
- package/dist/hooks/useImperativeGraphHandle.d.ts +22 -0
- package/dist/hooks/useLabelRenderer.d.ts +47 -0
- package/dist/hooks/useLinkLabelRenderer.d.ts +56 -0
- package/dist/hooks/useMarqueeAndCursor.d.ts +59 -0
- package/dist/hooks/usePerfTierDefaults.d.ts +13 -0
- package/dist/hooks/useResizeObserver.d.ts +7 -0
- package/dist/hooks/useShiftHeld.d.ts +5 -0
- package/dist/index.cjs +685 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +11309 -0
- package/dist/index.js.map +1 -0
- package/dist/internal/accessor-fn.d.ts +2 -0
- package/dist/internal/bezier.d.ts +16 -0
- package/dist/internal/canvas-color-tracker.d.ts +13 -0
- package/dist/internal/debounce.d.ts +5 -0
- package/dist/internal/float-tooltip.d.ts +14 -0
- package/dist/internal/kapsule-link.d.ts +24 -0
- package/dist/internal/kapsule.d.ts +43 -0
- package/dist/internal/throttle.d.ts +6 -0
- package/dist/internal/tween.d.ts +31 -0
- package/dist/style.css +1 -0
- package/dist/theme/presets.d.ts +8 -0
- package/dist/tools/ContextMenu.d.ts +17 -0
- package/dist/tools/GraphToolbar.d.ts +18 -0
- package/dist/tools/GroupLegend.d.ts +11 -0
- package/dist/tools/HoverTooltip.d.ts +15 -0
- package/dist/tools/MarqueeOverlay.d.ts +13 -0
- package/dist/tools/ModeToggle.d.ts +10 -0
- package/dist/tools/OptionsMenu.d.ts +28 -0
- package/dist/tools/SelectionPanel.d.ts +18 -0
- package/dist/tools/icons.d.ts +23 -0
- package/dist/tools/tools.d.ts +37 -0
- package/dist/types.d.ts +375 -0
- package/dist/utils/accessor.d.ts +36 -0
- package/dist/utils/download.d.ts +4 -0
- package/dist/utils/geometry.d.ts +8 -0
- package/dist/utils/grid.d.ts +9 -0
- package/dist/utils/ids.d.ts +3 -0
- package/dist/utils/perfTier.d.ts +29 -0
- package/dist/utils/spriteLabel.d.ts +61 -0
- package/dist/utils/themeStyle.d.ts +5 -0
- package/package.json +105 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { MutableRefObject } from 'react';
|
|
2
|
+
import { GraphEngine } from '../engines/types';
|
|
3
|
+
import { LinkObject, NodeObject } from '../types';
|
|
4
|
+
import { GraphDataApi } from './useGraphData';
|
|
5
|
+
import { SelectionApi } from './useGraphSelection';
|
|
6
|
+
export interface UseGraphClipboardParams<N extends NodeObject, L extends LinkObject> {
|
|
7
|
+
enableClipboard: boolean;
|
|
8
|
+
dataApi: GraphDataApi<N, L>;
|
|
9
|
+
selection: SelectionApi;
|
|
10
|
+
setSelectedLinkIds: React.Dispatch<React.SetStateAction<Array<string | number>>>;
|
|
11
|
+
engineRef: MutableRefObject<GraphEngine<N, L> | null>;
|
|
12
|
+
lastCursorRef: MutableRefObject<{
|
|
13
|
+
x: number;
|
|
14
|
+
y: number;
|
|
15
|
+
} | null>;
|
|
16
|
+
onCopy?: (nodes: N[]) => void;
|
|
17
|
+
onCut?: (nodes: N[]) => void;
|
|
18
|
+
onPaste?: (nodes: N[]) => void;
|
|
19
|
+
}
|
|
20
|
+
export interface GraphClipboardApi<N extends NodeObject> {
|
|
21
|
+
/** Live "is clipboard non-empty" indicator — read once per render for
|
|
22
|
+
* the selection panel chrome. The ref itself is also exposed for
|
|
23
|
+
* callers that need to react on every keystroke. */
|
|
24
|
+
hasClipboard(): boolean;
|
|
25
|
+
copy(): N[];
|
|
26
|
+
cut(): N[];
|
|
27
|
+
paste(at?: {
|
|
28
|
+
x: number;
|
|
29
|
+
y: number;
|
|
30
|
+
z?: number;
|
|
31
|
+
}): N[];
|
|
32
|
+
duplicate(): N[];
|
|
33
|
+
addConnectedNode(opts?: {
|
|
34
|
+
at?: {
|
|
35
|
+
x: number;
|
|
36
|
+
y: number;
|
|
37
|
+
z?: number;
|
|
38
|
+
};
|
|
39
|
+
label?: string;
|
|
40
|
+
}): N | null;
|
|
41
|
+
togglePin(id: string | number): void;
|
|
42
|
+
}
|
|
43
|
+
/** Bundles the editing primitives that read from / write to a private
|
|
44
|
+
* per-instance clipboard. The clipboard lives in a ref so writes don't
|
|
45
|
+
* trigger re-renders, and the OS clipboard is intentionally not
|
|
46
|
+
* touched — copy/paste shouldn't disturb the user's other apps. */
|
|
47
|
+
export declare function useGraphClipboard<N extends NodeObject, L extends LinkObject>(params: UseGraphClipboardParams<N, L>): GraphClipboardApi<N>;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { GraphData, LinkObject, NodeObject } from '../types';
|
|
2
|
+
export interface UseGraphDataOptions<N extends NodeObject, L extends LinkObject> {
|
|
3
|
+
/** Controlled data — when present, props win. */
|
|
4
|
+
controlled?: GraphData<N, L>;
|
|
5
|
+
/** Uncontrolled seed — used only on mount. */
|
|
6
|
+
defaultData?: GraphData<N, L>;
|
|
7
|
+
/** Fires for every mutation regardless of controlled/uncontrolled. */
|
|
8
|
+
onChange?: (next: GraphData<N, L>) => void;
|
|
9
|
+
}
|
|
10
|
+
export interface GraphDataApi<N extends NodeObject, L extends LinkObject> {
|
|
11
|
+
data: GraphData<N, L>;
|
|
12
|
+
setData(next: GraphData<N, L>): void;
|
|
13
|
+
addNode(node?: Partial<N> & {
|
|
14
|
+
id?: string | number;
|
|
15
|
+
}, opts?: {
|
|
16
|
+
at?: {
|
|
17
|
+
x: number;
|
|
18
|
+
y: number;
|
|
19
|
+
z?: number;
|
|
20
|
+
};
|
|
21
|
+
}): N;
|
|
22
|
+
addNodes(nodes: Array<Partial<N> & {
|
|
23
|
+
id?: string | number;
|
|
24
|
+
}>): N[];
|
|
25
|
+
updateNode(id: string | number, patch: Partial<N>): void;
|
|
26
|
+
removeNode(id: string | number): void;
|
|
27
|
+
removeNodes(ids: Array<string | number>): void;
|
|
28
|
+
addLink(link: {
|
|
29
|
+
source: string | number;
|
|
30
|
+
target: string | number;
|
|
31
|
+
id?: string | number;
|
|
32
|
+
} & Partial<L>): L;
|
|
33
|
+
addLinks(links: Array<{
|
|
34
|
+
source: string | number;
|
|
35
|
+
target: string | number;
|
|
36
|
+
} & Partial<L>>): L[];
|
|
37
|
+
removeLink(predicate: (l: L) => boolean): void;
|
|
38
|
+
clear(): void;
|
|
39
|
+
}
|
|
40
|
+
/** Owns the canonical `GraphData` for a `LoraGraphCanvas`. Supports
|
|
41
|
+
* controlled and uncontrolled usage; mutators always notify `onChange`. */
|
|
42
|
+
export declare function useGraphData<N extends NodeObject, L extends LinkObject>(opts: UseGraphDataOptions<N, L>): GraphDataApi<N, L>;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { GraphEngine } from '../engines/types';
|
|
2
|
+
import { GraphData, GraphMode, LinkObject, LoraGraphCanvasProps, NodeObject } from '../types';
|
|
3
|
+
interface UseGraphEngineParams<N extends NodeObject, L extends LinkObject> {
|
|
4
|
+
mount: HTMLElement | null;
|
|
5
|
+
mode: GraphMode;
|
|
6
|
+
width: number;
|
|
7
|
+
height: number;
|
|
8
|
+
data: GraphData<N, L>;
|
|
9
|
+
props: LoraGraphCanvasProps<N, L>;
|
|
10
|
+
/** Whether the engine should be paused. */
|
|
11
|
+
paused: boolean;
|
|
12
|
+
/** Duration (ms) of the camera + node-z tween when switching modes.
|
|
13
|
+
* 0 disables the animation. */
|
|
14
|
+
modeTransitionMs?: number;
|
|
15
|
+
}
|
|
16
|
+
/** Owns the kapsule engine lifecycle. The engine is created once on
|
|
17
|
+
* mount and survives mode changes — `mode` flips flow through
|
|
18
|
+
* `engine.setMode()` so the nodes smoothly move between 2D (z pinned
|
|
19
|
+
* to 0, top-down camera) and 3D (z released, orbit camera). The
|
|
20
|
+
* engine is destroyed only when the host element changes or the
|
|
21
|
+
* component unmounts. */
|
|
22
|
+
export declare function useGraphEngine<N extends NodeObject, L extends LinkObject>(params: UseGraphEngineParams<N, L>): GraphEngine<N, L> | null;
|
|
23
|
+
export {};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { GraphEngine } from '../engines/types';
|
|
2
|
+
import { LinkObject, LoraGraphCanvasProps, NodeObject } from '../types';
|
|
3
|
+
export interface UseGraphForcesParams<N extends NodeObject, L extends LinkObject> {
|
|
4
|
+
engine: GraphEngine<N, L> | null;
|
|
5
|
+
collideNodes: NonNullable<LoraGraphCanvasProps<N, L>["collideNodes"]>;
|
|
6
|
+
beeswarm: NonNullable<LoraGraphCanvasProps<N, L>["beeswarm"]>;
|
|
7
|
+
nodeRelSize?: number;
|
|
8
|
+
}
|
|
9
|
+
/** Drive optional d3-force-3d forces (collide + beeswarm) onto the
|
|
10
|
+
* active engine. These are deliberately kept as separate effects in
|
|
11
|
+
* one hook so the simulation knobs can be toggled independently. */
|
|
12
|
+
export declare function useGraphForces<N extends NodeObject, L extends LinkObject>(params: UseGraphForcesParams<N, L>): void;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { GraphEngine } from '../engines/types';
|
|
2
|
+
import { GraphMode, LinkObject, NodeObject, ToolId } from '../types';
|
|
3
|
+
import { GraphDataApi } from './useGraphData';
|
|
4
|
+
import { SelectionApi } from './useGraphSelection';
|
|
5
|
+
export interface UseGraphKeybindingsParams<N extends NodeObject, L extends LinkObject> {
|
|
6
|
+
engine: GraphEngine<N, L> | null;
|
|
7
|
+
dataApi: GraphDataApi<N, L>;
|
|
8
|
+
selection: SelectionApi;
|
|
9
|
+
mode: GraphMode;
|
|
10
|
+
setMode: (next: GraphMode) => void;
|
|
11
|
+
selectedLinkIds: Array<string | number>;
|
|
12
|
+
setSelectedLinkIds: React.Dispatch<React.SetStateAction<Array<string | number>>>;
|
|
13
|
+
setLinkSourceId: React.Dispatch<React.SetStateAction<string | number | null>>;
|
|
14
|
+
setActiveTool: React.Dispatch<React.SetStateAction<ToolId>>;
|
|
15
|
+
enableClipboard: boolean;
|
|
16
|
+
copy: () => unknown;
|
|
17
|
+
cut: () => unknown;
|
|
18
|
+
paste: () => unknown;
|
|
19
|
+
duplicate: () => unknown;
|
|
20
|
+
addConnectedNode: () => unknown;
|
|
21
|
+
togglePin: (id: string | number) => void;
|
|
22
|
+
}
|
|
23
|
+
/** Global keyboard shortcuts for the canvas. The listener is bound once
|
|
24
|
+
* per mount; live state is read through a ref so we avoid the
|
|
25
|
+
* re-binding churn that would otherwise happen on every selection
|
|
26
|
+
* change. */
|
|
27
|
+
export declare function useGraphKeybindings<N extends NodeObject, L extends LinkObject>(params: UseGraphKeybindingsParams<N, L>): void;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface UseGraphSelectionOptions {
|
|
2
|
+
mode?: "none" | "single" | "multi";
|
|
3
|
+
onChange?: (selectedIds: Array<string | number>) => void;
|
|
4
|
+
}
|
|
5
|
+
export interface SelectionApi {
|
|
6
|
+
selected: Array<string | number>;
|
|
7
|
+
isSelected(id: string | number): boolean;
|
|
8
|
+
toggle(id: string | number, opts?: {
|
|
9
|
+
additive?: boolean;
|
|
10
|
+
}): void;
|
|
11
|
+
set(ids: Array<string | number>): void;
|
|
12
|
+
clear(): void;
|
|
13
|
+
}
|
|
14
|
+
export declare function useGraphSelection(opts: UseGraphSelectionOptions): SelectionApi;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { MutableRefObject } from 'react';
|
|
2
|
+
import { Accessor, LinkObject, NodeObject } from '../types';
|
|
3
|
+
export interface UseHoverStateParams<N extends NodeObject, L extends LinkObject> {
|
|
4
|
+
highlightNeighborsOnHover: boolean;
|
|
5
|
+
nodeLabel?: Accessor<string | HTMLElement, N>;
|
|
6
|
+
linkLabel?: Accessor<string | HTMLElement, L>;
|
|
7
|
+
onNodeHover?: (node: N | null, previousNode: N | null) => void;
|
|
8
|
+
onLinkHover?: (link: L | null, previousLink: L | null) => void;
|
|
9
|
+
}
|
|
10
|
+
export interface HoverState<N extends NodeObject, L extends LinkObject> {
|
|
11
|
+
/** Debounced hovered-node id — drives label visibility / highlight
|
|
12
|
+
* pipeline. Keeps a 250 ms grace period after mouseleave so labels
|
|
13
|
+
* stay readable while the cursor moves to them. */
|
|
14
|
+
hoverNodeId: string | number | null;
|
|
15
|
+
/** Debounced hovered-link id — companion to `hoverNodeId`. */
|
|
16
|
+
hoverLinkId: string | number | null;
|
|
17
|
+
/** Live (non-debounced) hovered-node id — updated synchronously from
|
|
18
|
+
* the kapsule's `onNodeHover` before the grace timer is even
|
|
19
|
+
* scheduled. Read this (not `hoverNodeId`) from interactions that
|
|
20
|
+
* need to know whether the cursor is *currently* over a node, e.g.
|
|
21
|
+
* the marquee hook's shift+mousedown guard. Reading the debounced
|
|
22
|
+
* value there would tell us "still hovering A" for 250 ms after the
|
|
23
|
+
* cursor moved off A, which is long enough that the next shift+drag
|
|
24
|
+
* would never start a marquee. */
|
|
25
|
+
liveHoverNodeIdRef: MutableRefObject<string | number | null>;
|
|
26
|
+
/** Live (non-debounced) hovered-link id — same contract as
|
|
27
|
+
* `liveHoverNodeIdRef`, used so neighbour-link hits don't get
|
|
28
|
+
* swallowed during the label-grace window. */
|
|
29
|
+
liveHoverLinkIdRef: MutableRefObject<string | number | null>;
|
|
30
|
+
/** Set of node ids that should currently render with neighbour-
|
|
31
|
+
* highlight styling (the hovered node plus its `_neighbors`). */
|
|
32
|
+
highlightedNodeIds: ReadonlySet<string | number>;
|
|
33
|
+
/** Set of link ids that should currently render with neighbour-
|
|
34
|
+
* highlight styling (links touching the hovered node). */
|
|
35
|
+
highlightedLinkIds: ReadonlySet<string | number>;
|
|
36
|
+
/** Set of node ids whose label should currently be drawn for the
|
|
37
|
+
* hover affordance (hovered node + neighbours when highlighting is
|
|
38
|
+
* on; just the hovered node otherwise; empty when nothing hovered).
|
|
39
|
+
* Memoised so referential equality bails the renderer's no-op
|
|
40
|
+
* frames out cheaply. */
|
|
41
|
+
hoveredNodeSet: ReadonlySet<string | number>;
|
|
42
|
+
/** Sibling of `hoveredNodeSet` for the link-label renderer. */
|
|
43
|
+
hoveredLinkSet: ReadonlySet<string | number>;
|
|
44
|
+
/** Current hover-tooltip content. `null` when no hover. */
|
|
45
|
+
tooltipContent: string | HTMLElement | null;
|
|
46
|
+
/** Callback to wire into `engineProps.onNodeHover`. */
|
|
47
|
+
handleNodeHover: (node: N | null, prev: N | null) => void;
|
|
48
|
+
/** Callback to wire into `engineProps.onLinkHover`. */
|
|
49
|
+
handleLinkHover: (link: L | null, prev: L | null) => void;
|
|
50
|
+
/** Pin the node-hover state to the given node for the duration of
|
|
51
|
+
* a drag. The kapsule fires `onNodeHover(null)` on mousedown of a
|
|
52
|
+
* drag gesture; without this pin, the grace timer would clear
|
|
53
|
+
* `highlightedNodeIds` + `highlightedLinkIds` mid-drag and the
|
|
54
|
+
* neighbour labels would vanish. Call `pinHover(node)` from the
|
|
55
|
+
* drag-start handler and `pinHover(null)` from drag-end. Calling
|
|
56
|
+
* with a node also forces the hover state to that node so a drag
|
|
57
|
+
* begun without a preceding hover lights up the right
|
|
58
|
+
* neighbours. */
|
|
59
|
+
pinHover: (node: N | null) => void;
|
|
60
|
+
}
|
|
61
|
+
/** Owns the hover-driven state that the canvas reacts to: the
|
|
62
|
+
* debounced ids for label visibility, the live refs for gesture
|
|
63
|
+
* guards, the neighbour-highlight sets, and the hover-tooltip
|
|
64
|
+
* content. Pulled out of `LoraGraphCanvas` so the live-vs-debounced
|
|
65
|
+
* split is a first-class contract instead of a tangle of useState +
|
|
66
|
+
* useRef calls in the canvas body. */
|
|
67
|
+
export declare function useHoverState<N extends NodeObject, L extends LinkObject>(params: UseHoverStateParams<N, L>): HoverState<N, L>;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { GraphEngine } from '../engines/types';
|
|
2
|
+
import { GraphMode, LinkObject, LoraGraphCanvasHandle, NodeObject } from '../types';
|
|
3
|
+
import { GraphDataApi } from './useGraphData';
|
|
4
|
+
import { SelectionApi } from './useGraphSelection';
|
|
5
|
+
import { GraphClipboardApi } from './useGraphClipboard';
|
|
6
|
+
export interface UseImperativeGraphHandleParams<N extends NodeObject, L extends LinkObject> {
|
|
7
|
+
ref: React.Ref<LoraGraphCanvasHandle<N, L>>;
|
|
8
|
+
dataApi: GraphDataApi<N, L>;
|
|
9
|
+
selection: SelectionApi;
|
|
10
|
+
engine: GraphEngine<N, L> | null;
|
|
11
|
+
mode: GraphMode;
|
|
12
|
+
setMode: (next: GraphMode) => void;
|
|
13
|
+
setPaused: React.Dispatch<React.SetStateAction<boolean>>;
|
|
14
|
+
clipboard: GraphClipboardApi<N>;
|
|
15
|
+
exportJSON: () => string;
|
|
16
|
+
importJSON: (json: string) => void;
|
|
17
|
+
downloadJSON: (filename?: string) => void;
|
|
18
|
+
}
|
|
19
|
+
/** Hooks `useImperativeHandle` to expose the canvas's full
|
|
20
|
+
* programmatic API surface to consumers via a forwardRef. Kept as a
|
|
21
|
+
* hook so the main component file stays focused on rendering. */
|
|
22
|
+
export declare function useImperativeGraphHandle<N extends NodeObject, L extends LinkObject>(params: UseImperativeGraphHandleParams<N, L>): void;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { Accessor, GraphMode, LoraGraphTheme, NodeObject } from '../types';
|
|
2
|
+
export interface UseLabelRendererParams<N extends NodeObject> {
|
|
3
|
+
mode: GraphMode;
|
|
4
|
+
showLabels: boolean;
|
|
5
|
+
selectedNodeSet: ReadonlySet<string | number>;
|
|
6
|
+
/** Nodes the cursor is currently over (plus optional neighbours). Drawn
|
|
7
|
+
* with the default caption styling so the hover affordance is visible
|
|
8
|
+
* without competing with the accent-coloured selection pill. */
|
|
9
|
+
hoveredNodeSet?: ReadonlySet<string | number>;
|
|
10
|
+
accentColor: string;
|
|
11
|
+
hostNodeCanvasObject?: (n: N, ctx: CanvasRenderingContext2D, globalScale: number) => void;
|
|
12
|
+
hostNodeThreeObject?: (n: N) => unknown;
|
|
13
|
+
nodeLabel?: Accessor<string | HTMLElement, N>;
|
|
14
|
+
nodeVal?: Accessor<number, N>;
|
|
15
|
+
nodeRelSize?: number;
|
|
16
|
+
theme?: Partial<LoraGraphTheme>;
|
|
17
|
+
/** Live nodes list. Required for the 2D post-render pass so labels
|
|
18
|
+
* can be drawn after all node circles have been painted. Stored in
|
|
19
|
+
* a ref internally, so identity changes here don't churn the
|
|
20
|
+
* renderer's outputs. */
|
|
21
|
+
nodes?: ReadonlyArray<N>;
|
|
22
|
+
}
|
|
23
|
+
export interface NodeLabelRenderer<N> {
|
|
24
|
+
/** 2D mode: kept undefined now — labels draw in `renderFramePost`
|
|
25
|
+
* so they sit above all node circles, not just the one they're
|
|
26
|
+
* attached to. The kapsule's `nodeCanvasObject` callback ran
|
|
27
|
+
* inline with each node's circle paint, so a label drawn early
|
|
28
|
+
* could be obscured by a circle drawn after. */
|
|
29
|
+
canvasObject: ((n: N, ctx: CanvasRenderingContext2D, scale: number) => void) | undefined;
|
|
30
|
+
/** 3D mode: returns an Object3D containing a billboarded text
|
|
31
|
+
* sprite, positioned below the kapsule's default sphere. Used as
|
|
32
|
+
* `nodeThreeObject` with `nodeThreeObjectExtend: true` so we
|
|
33
|
+
* augment rather than replace the default rendering. */
|
|
34
|
+
threeObject: ((n: N) => unknown) | undefined;
|
|
35
|
+
/** 2D mode: walks the node list at the end of every frame and
|
|
36
|
+
* draws each visible label on top of everything the kapsule has
|
|
37
|
+
* already painted. Wired into the kapsule's `onRenderFramePost`
|
|
38
|
+
* callback by `LoraGraphCanvas`. Receives the same world-space
|
|
39
|
+
* canvas transform as the inline accessor would have, so the
|
|
40
|
+
* drawing math is identical — just the layering changes. */
|
|
41
|
+
renderFramePost: ((ctx: CanvasRenderingContext2D, globalScale: number) => void) | undefined;
|
|
42
|
+
}
|
|
43
|
+
/** Build the optional `nodeCanvasObject` that draws a small themed pill
|
|
44
|
+
* with each node's label beneath it. Returns `undefined` when neither
|
|
45
|
+
* the global `showLabels` flag nor any selection demands labels — that
|
|
46
|
+
* way the engine prop bag skips the binding entirely on hot paths. */
|
|
47
|
+
export declare function useLabelRenderer<N extends NodeObject>(params: UseLabelRendererParams<N>): NodeLabelRenderer<N>;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { Accessor, GraphMode, LinkObject, LoraGraphTheme } from '../types';
|
|
2
|
+
export interface UseLinkLabelRendererParams<L extends LinkObject> {
|
|
3
|
+
mode: GraphMode;
|
|
4
|
+
showLabels: boolean;
|
|
5
|
+
selectedLinkSet: ReadonlySet<string | number>;
|
|
6
|
+
/** Links to render labels for on hover (e.g. the directly-hovered
|
|
7
|
+
* link, or neighbour links of a hovered node). Styled like the
|
|
8
|
+
* default caption so the selected pill still stands out. */
|
|
9
|
+
hoveredLinkSet?: ReadonlySet<string | number>;
|
|
10
|
+
accentColor: string;
|
|
11
|
+
hostLinkCanvasObject?: (l: L, ctx: CanvasRenderingContext2D, globalScale: number) => void;
|
|
12
|
+
hostLinkThreeObject?: (l: L) => unknown;
|
|
13
|
+
hostLinkPositionUpdate?: ((obj: unknown, coords: {
|
|
14
|
+
start: {
|
|
15
|
+
x: number;
|
|
16
|
+
y: number;
|
|
17
|
+
z: number;
|
|
18
|
+
};
|
|
19
|
+
end: {
|
|
20
|
+
x: number;
|
|
21
|
+
y: number;
|
|
22
|
+
z: number;
|
|
23
|
+
};
|
|
24
|
+
}, link: L) => void | boolean | null) | null;
|
|
25
|
+
linkLabel?: Accessor<string | HTMLElement, L>;
|
|
26
|
+
theme?: Partial<LoraGraphTheme>;
|
|
27
|
+
}
|
|
28
|
+
export interface LinkLabelRenderer<L> {
|
|
29
|
+
/** 2D: `linkCanvasObject` drawing the label along the link. */
|
|
30
|
+
canvasObject: ((l: L, ctx: CanvasRenderingContext2D, scale: number) => void) | undefined;
|
|
31
|
+
/** 3D: `linkThreeObject` returning a sprite for the label. */
|
|
32
|
+
threeObject: ((l: L) => unknown) | undefined;
|
|
33
|
+
/** 3D: `linkPositionUpdate` placing the sprite at the midpoint of
|
|
34
|
+
* the link each frame. Returns `true` so the kapsule's default
|
|
35
|
+
* link tube/line update still runs (we're additive, not
|
|
36
|
+
* replacing). */
|
|
37
|
+
positionUpdate: ((obj: unknown, coords: {
|
|
38
|
+
start: {
|
|
39
|
+
x: number;
|
|
40
|
+
y: number;
|
|
41
|
+
z: number;
|
|
42
|
+
};
|
|
43
|
+
end: {
|
|
44
|
+
x: number;
|
|
45
|
+
y: number;
|
|
46
|
+
z: number;
|
|
47
|
+
};
|
|
48
|
+
}, link: L) => boolean) | undefined;
|
|
49
|
+
}
|
|
50
|
+
/** Draw a label *along* each link (rotated to match the link's
|
|
51
|
+
* bearing, font auto-sized to fit between the endpoint margins).
|
|
52
|
+
* Mirrors the canonical force-graph link-label snippet, themed for
|
|
53
|
+
* selection. Returns `undefined` when nothing demands a label so the
|
|
54
|
+
* engine prop bag skips the binding entirely on hot paths. 2D only —
|
|
55
|
+
* the 3D variant lives in `useLinkLabelRenderer3D`. */
|
|
56
|
+
export declare function useLinkLabelRenderer<L extends LinkObject>(params: UseLinkLabelRendererParams<L>): LinkLabelRenderer<L>;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { MutableRefObject } from 'react';
|
|
2
|
+
import { GraphEngine } from '../engines/types';
|
|
3
|
+
import { LinkObject, NodeObject } from '../types';
|
|
4
|
+
import { SelectionApi } from './useGraphSelection';
|
|
5
|
+
export interface MarqueeRect {
|
|
6
|
+
x0: number;
|
|
7
|
+
y0: number;
|
|
8
|
+
x1: number;
|
|
9
|
+
y1: number;
|
|
10
|
+
additive: boolean;
|
|
11
|
+
}
|
|
12
|
+
export interface UseMarqueeAndCursorParams<N extends NodeObject, L extends LinkObject> {
|
|
13
|
+
mount: HTMLDivElement | null;
|
|
14
|
+
/** Trampoline to the live engine — read inside event handlers so we
|
|
15
|
+
* always pick up the current engine after a 2D↔3D remount. */
|
|
16
|
+
engineRef: MutableRefObject<GraphEngine<N, L> | null>;
|
|
17
|
+
selection: SelectionApi;
|
|
18
|
+
nodes: ReadonlyArray<N>;
|
|
19
|
+
links: ReadonlyArray<L>;
|
|
20
|
+
setSelectedLinkIds: React.Dispatch<React.SetStateAction<Array<string | number>>>;
|
|
21
|
+
/** Live (non-debounced) hover state from the kapsule's
|
|
22
|
+
* `onNodeHover`. Used at shift+mousedown time to decide between
|
|
23
|
+
* "shift+click to add to selection" (a node is under the cursor →
|
|
24
|
+
* let the click fall through to the kapsule) and "shift+drag to
|
|
25
|
+
* marquee" (background → start a rectangle).
|
|
26
|
+
*
|
|
27
|
+
* Must be a ref, not a state value: the host applies a grace-period
|
|
28
|
+
* debounce to `hoverNodeId` so labels can persist while the cursor
|
|
29
|
+
* briefly leaves a node. Reading a debounced value here would tell
|
|
30
|
+
* us "still hovering A" for 250ms after the cursor has actually
|
|
31
|
+
* moved away — long enough that the user's next shift+drag would
|
|
32
|
+
* be wrongly treated as a click-falling-through-to-A, and no
|
|
33
|
+
* marquee would start. */
|
|
34
|
+
liveHoverNodeIdRef: MutableRefObject<string | number | null>;
|
|
35
|
+
}
|
|
36
|
+
export interface UseMarqueeAndCursorResult {
|
|
37
|
+
marquee: MarqueeRect | null;
|
|
38
|
+
/** Last screen-space cursor position over the mount element. Used by
|
|
39
|
+
* paste-at-cursor. */
|
|
40
|
+
lastCursorRef: MutableRefObject<{
|
|
41
|
+
x: number;
|
|
42
|
+
y: number;
|
|
43
|
+
} | null>;
|
|
44
|
+
}
|
|
45
|
+
/** Wires up the canvas-level pointer behaviour that lives outside the
|
|
46
|
+
* kapsule:
|
|
47
|
+
*
|
|
48
|
+
* - Shift+drag draws a marquee rectangle. Nodes whose projected screen
|
|
49
|
+
* coordinates land inside the rectangle get selected on release.
|
|
50
|
+
* - The last cursor position over the mount is tracked into a ref so
|
|
51
|
+
* paste-at-cursor knows where to drop new nodes.
|
|
52
|
+
* - Any wheel / touch / mousedown on the canvas interrupts an in-flight
|
|
53
|
+
* focus animation.
|
|
54
|
+
*
|
|
55
|
+
* The effect mounts once per mount element. Live data is read through a
|
|
56
|
+
* ref so the listener identity stays stable across data mutations —
|
|
57
|
+
* re-binding ResizeObserver + window listeners on every change would
|
|
58
|
+
* otherwise glitch the marquee gesture mid-drag. */
|
|
59
|
+
export declare function useMarqueeAndCursor<N extends NodeObject, L extends LinkObject>(params: UseMarqueeAndCursorParams<N, L>): UseMarqueeAndCursorResult;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { GraphMode, LinkObject, LoraGraphCanvasProps, NodeObject } from '../types';
|
|
2
|
+
export interface UsePerfTierDefaultsParams {
|
|
3
|
+
profile: LoraGraphCanvasProps["performanceProfile"];
|
|
4
|
+
nodeCount: number;
|
|
5
|
+
linkCount: number;
|
|
6
|
+
mode: GraphMode;
|
|
7
|
+
}
|
|
8
|
+
/** Pick a perf tier from the live node + link count (or the host's
|
|
9
|
+
* explicit override) and turn it into a partial prop bag pre-filling
|
|
10
|
+
* perf knobs (cooldownTicks, ngraph layout in 3D, lower mesh
|
|
11
|
+
* resolutions, etc). The bag is spread *before* `props` so anything
|
|
12
|
+
* the host sets explicitly always wins. */
|
|
13
|
+
export declare function usePerfTierDefaults<N extends NodeObject, L extends LinkObject>(params: UsePerfTierDefaultsParams): Partial<LoraGraphCanvasProps<N, L>>;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { RefObject } from 'react';
|
|
2
|
+
/** Reports the content rect of `ref.current`. Returns null on the
|
|
3
|
+
* initial render. Re-fires whenever the observed element resizes. */
|
|
4
|
+
export declare function useResizeObserver(ref: RefObject<HTMLElement | null>): {
|
|
5
|
+
width: number;
|
|
6
|
+
height: number;
|
|
7
|
+
} | null;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** Track whether Shift is currently held. In 3D mode this lets us
|
|
2
|
+
* disable the Three.js navigation controls while the user is drawing a
|
|
3
|
+
* marquee — otherwise OrbitControls / TrackballControls would also
|
|
4
|
+
* process the same drag and pan the camera. */
|
|
5
|
+
export declare function useShiftHeld(): boolean;
|