@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.
Files changed (64) hide show
  1. package/LICENSE +87 -0
  2. package/README.md +191 -0
  3. package/THIRD_PARTY.md +40 -0
  4. package/dist/LoraGraphCanvas.d.ts +9 -0
  5. package/dist/LoraGraphCanvas.stories.d.ts +23 -0
  6. package/dist/engines/3d-force-graph/index.d.ts +1 -0
  7. package/dist/engines/3d-force-graph/kapsule.d.ts +12 -0
  8. package/dist/engines/createEngineUnified.d.ts +13 -0
  9. package/dist/engines/propBindings.d.ts +29 -0
  10. package/dist/engines/rafAnim.d.ts +13 -0
  11. package/dist/engines/types.d.ts +86 -0
  12. package/dist/hooks/useAccessorOverrides.d.ts +62 -0
  13. package/dist/hooks/useAutoIndexNeighbors.d.ts +6 -0
  14. package/dist/hooks/useClickToleranceShim.d.ts +46 -0
  15. package/dist/hooks/useGraphClipboard.d.ts +47 -0
  16. package/dist/hooks/useGraphData.d.ts +42 -0
  17. package/dist/hooks/useGraphEngine.d.ts +23 -0
  18. package/dist/hooks/useGraphForces.d.ts +12 -0
  19. package/dist/hooks/useGraphKeybindings.d.ts +27 -0
  20. package/dist/hooks/useGraphSelection.d.ts +14 -0
  21. package/dist/hooks/useHoverState.d.ts +67 -0
  22. package/dist/hooks/useImperativeGraphHandle.d.ts +22 -0
  23. package/dist/hooks/useLabelRenderer.d.ts +47 -0
  24. package/dist/hooks/useLinkLabelRenderer.d.ts +56 -0
  25. package/dist/hooks/useMarqueeAndCursor.d.ts +59 -0
  26. package/dist/hooks/usePerfTierDefaults.d.ts +13 -0
  27. package/dist/hooks/useResizeObserver.d.ts +7 -0
  28. package/dist/hooks/useShiftHeld.d.ts +5 -0
  29. package/dist/index.cjs +685 -0
  30. package/dist/index.cjs.map +1 -0
  31. package/dist/index.d.ts +5 -0
  32. package/dist/index.js +11309 -0
  33. package/dist/index.js.map +1 -0
  34. package/dist/internal/accessor-fn.d.ts +2 -0
  35. package/dist/internal/bezier.d.ts +16 -0
  36. package/dist/internal/canvas-color-tracker.d.ts +13 -0
  37. package/dist/internal/debounce.d.ts +5 -0
  38. package/dist/internal/float-tooltip.d.ts +14 -0
  39. package/dist/internal/kapsule-link.d.ts +24 -0
  40. package/dist/internal/kapsule.d.ts +43 -0
  41. package/dist/internal/throttle.d.ts +6 -0
  42. package/dist/internal/tween.d.ts +31 -0
  43. package/dist/style.css +1 -0
  44. package/dist/theme/presets.d.ts +8 -0
  45. package/dist/tools/ContextMenu.d.ts +17 -0
  46. package/dist/tools/GraphToolbar.d.ts +18 -0
  47. package/dist/tools/GroupLegend.d.ts +11 -0
  48. package/dist/tools/HoverTooltip.d.ts +15 -0
  49. package/dist/tools/MarqueeOverlay.d.ts +13 -0
  50. package/dist/tools/ModeToggle.d.ts +10 -0
  51. package/dist/tools/OptionsMenu.d.ts +28 -0
  52. package/dist/tools/SelectionPanel.d.ts +18 -0
  53. package/dist/tools/icons.d.ts +23 -0
  54. package/dist/tools/tools.d.ts +37 -0
  55. package/dist/types.d.ts +375 -0
  56. package/dist/utils/accessor.d.ts +36 -0
  57. package/dist/utils/download.d.ts +4 -0
  58. package/dist/utils/geometry.d.ts +8 -0
  59. package/dist/utils/grid.d.ts +9 -0
  60. package/dist/utils/ids.d.ts +3 -0
  61. package/dist/utils/perfTier.d.ts +29 -0
  62. package/dist/utils/spriteLabel.d.ts +61 -0
  63. package/dist/utils/themeStyle.d.ts +5 -0
  64. 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;