@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,2 @@
1
+ export type Accessor<T> = T | string | ((obj: unknown) => T);
2
+ export default function accessorFn<T = unknown>(param: Accessor<T> | null | undefined): (obj: unknown) => T;
@@ -0,0 +1,16 @@
1
+ export declare class Bezier {
2
+ #private;
3
+ constructor(...pts: number[]);
4
+ /** Point on the curve at parameter `t` ∈ [0, 1]. Implementation
5
+ * uses the explicit cubic / quadratic forms (faster than
6
+ * de Casteljau for two/three subdivisions). */
7
+ get(t: number): {
8
+ x: number;
9
+ y: number;
10
+ };
11
+ /** Arc length via Gauss-Legendre quadrature would be cleaner, but
12
+ * this consumer only uses the length to position arrowheads and
13
+ * photons — a 40-step polyline approximation is plenty accurate.
14
+ * Allocation-free in the steady state (one number per step). */
15
+ length(): number;
16
+ }
@@ -0,0 +1,13 @@
1
+ export default class CanvasColorTracker<T = unknown> {
2
+ #private;
3
+ constructor(csBits?: number);
4
+ reset(): void;
5
+ /** Allocate a fresh colour for `obj` and return its hex string,
6
+ * or `null` once the registry is full. */
7
+ register(obj: T): string | null;
8
+ /** Reverse a hex string (as returned by `register`) or an [r,g,b]
9
+ * triple (as read from `ImageData.data`) back to its registered
10
+ * object. Returns `null` if the colour doesn't decode to a valid
11
+ * entry or fails the checksum. */
12
+ lookup(color: string | [number, number, number]): T | null;
13
+ }
@@ -0,0 +1,5 @@
1
+ export interface Debounced<F extends (...args: never[]) => unknown> {
2
+ (...args: Parameters<F>): void;
3
+ cancel: () => void;
4
+ }
5
+ export declare function debounce<F extends (...args: never[]) => unknown>(fn: F, wait: number): Debounced<F>;
@@ -0,0 +1,14 @@
1
+ export type TooltipContent = string | HTMLElement | false | null;
2
+ export interface TooltipOptions {
3
+ style?: Partial<CSSStyleDeclaration>;
4
+ }
5
+ export default class Tooltip {
6
+ #private;
7
+ constructor(host: HTMLElement, options?: TooltipOptions);
8
+ content(): TooltipContent;
9
+ content(value: TooltipContent): this;
10
+ offsetX(): number | string | null;
11
+ offsetX(value: number | string | null): this;
12
+ offsetY(): number | string | null;
13
+ offsetY(value: number | string | null): this;
14
+ }
@@ -0,0 +1,24 @@
1
+ type ChainableKapsule = {
2
+ _destructor?: () => void;
3
+ [k: string]: unknown;
4
+ };
5
+ type KapsuleConstructor = new () => ChainableKapsule;
6
+ interface KapsulePropConfig {
7
+ default: unknown;
8
+ onChange: (v: unknown, state: Record<string, ChainableKapsule>) => void;
9
+ triggerUpdate: false;
10
+ }
11
+ interface KapsuleLinker {
12
+ /** Build a kapsule prop config that forwards the prop to one or more
13
+ * named inner kapsules on every change. The `default` is read from a
14
+ * throwaway instance of the inner kapsule type so it stays in sync
15
+ * with upstream. */
16
+ linkProp: (prop: string) => KapsulePropConfig;
17
+ /** Build a method shim that calls `method(...args)` on every named
18
+ * inner kapsule and returns either the inner's return value (when
19
+ * it's not the kapsule itself, i.e. a getter) or `this` for chain
20
+ * continuity. */
21
+ linkMethod: (method: string) => (state: Record<string, ChainableKapsule>, ...args: unknown[]) => unknown;
22
+ }
23
+ export default function kapsuleLink(kapsulePropNames: string | string[], kapsuleType: KapsuleConstructor): KapsuleLinker;
24
+ export {};
@@ -0,0 +1,43 @@
1
+ export interface KapsuleState {
2
+ initialised: boolean;
3
+ _rerender: () => void;
4
+ [key: string]: unknown;
5
+ }
6
+ export interface PropConfig {
7
+ /** Default prop value, applied at construction time. */
8
+ default?: unknown;
9
+ /** Fire `update()` after this prop changes. Default true. */
10
+ triggerUpdate?: boolean;
11
+ /** Callback fired synchronously when the prop changes. */
12
+ onChange?: (newVal: unknown, state: KapsuleState, prevVal: unknown) => void;
13
+ }
14
+ export type KapsuleMethod = (state: KapsuleState, ...args: any[]) => unknown;
15
+ export interface KapsuleConfig {
16
+ props?: Record<string, PropConfig>;
17
+ methods?: Record<string, KapsuleMethod>;
18
+ /** Alias one prop/method name to another (for backwards compat). */
19
+ aliases?: Record<string, string>;
20
+ /** Initial state factory. Run once at construction. */
21
+ stateInit?: (options?: Record<string, unknown>) => Partial<KapsuleState>;
22
+ /** Constructor-time initialiser. Receives the host element (or
23
+ * whatever was passed to `new Kapsule(...)`). Typed `any` so
24
+ * consumers can declare a narrower DOM type without an extra cast. */
25
+ init?: (constructorItem: any, state: KapsuleState, options?: Record<string, unknown>) => void;
26
+ /** Trailing-edge digest. Fired after each batch of prop changes. */
27
+ update?: (state: KapsuleState, changedProps: Record<string, unknown>) => void;
28
+ }
29
+ /** A kapsule instance — every prop/method ends up as a chainable
30
+ * property on the result. We type it loosely (any-keyed) because
31
+ * the concrete shape depends on the caller's `props`/`methods`;
32
+ * consumers can narrow by declaring their own interface and casting
33
+ * the return through `unknown`. */
34
+ export interface KapsuleInstance {
35
+ (constructorItem?: unknown): KapsuleInstance;
36
+ resetProps: () => KapsuleInstance;
37
+ [propOrMethod: string]: (...args: unknown[]) => unknown;
38
+ }
39
+ export interface KapsuleClassCtor {
40
+ new (element?: unknown, options?: Record<string, unknown>): KapsuleInstance;
41
+ (element?: unknown, options?: Record<string, unknown>): KapsuleInstance;
42
+ }
43
+ export default function Kapsule(cfg?: KapsuleConfig): KapsuleClassCtor;
@@ -0,0 +1,6 @@
1
+ export interface Throttled<F extends (...args: never[]) => unknown> {
2
+ (...args: Parameters<F>): ReturnType<F> | undefined;
3
+ flush: () => ReturnType<F> | undefined;
4
+ cancel: () => void;
5
+ }
6
+ export declare function throttle<F extends (...args: never[]) => unknown>(fn: F, wait: number): Throttled<F>;
@@ -0,0 +1,31 @@
1
+ type Numbers = Record<string, number>;
2
+ type EasingFn = (k: number) => number;
3
+ export declare const Easing: {
4
+ readonly Linear: {
5
+ readonly None: (k: number) => number;
6
+ };
7
+ readonly Quadratic: {
8
+ readonly In: (k: number) => number;
9
+ readonly Out: (k: number) => number;
10
+ readonly InOut: (k: number) => number;
11
+ };
12
+ };
13
+ export declare class Group {
14
+ #private;
15
+ add<T extends Numbers>(t: Tween<T>): void;
16
+ remove<T extends Numbers>(t: Tween<T>): void;
17
+ /** Step every active tween. Should be called once per animation
18
+ * frame; finished tweens self-remove via their `onComplete`. */
19
+ update(time?: number): void;
20
+ }
21
+ export declare class Tween<T extends Numbers> {
22
+ #private;
23
+ constructor(initial: T);
24
+ to(target: Partial<T>, duration: number): this;
25
+ easing(fn: EasingFn): this;
26
+ onUpdate(cb: (obj: T) => void): this;
27
+ onComplete(cb: (this: Tween<T>, obj: T) => void): this;
28
+ start(time?: number): this;
29
+ update(time: number): boolean;
30
+ }
31
+ export {};
package/dist/style.css ADDED
@@ -0,0 +1 @@
1
+ .graph-info-msg{top:50%;width:100%;text-align:center;color:#e6e6fa;opacity:.7;font-size:22px;position:absolute;font-family:Sans-serif}.scene-container .clickable{cursor:pointer}.scene-container .grabbable{cursor:move;cursor:grab;cursor:-moz-grab;cursor:-webkit-grab}.scene-container .grabbable:active{cursor:grabbing;cursor:-moz-grabbing;cursor:-webkit-grabbing}.lora-graph-canvas{--lgc-bg: transparent;--lgc-fg: #1c1f23;--lgc-border: #d8dde3;--lgc-accent: #4f8ef7;--lgc-toolbar-bg: rgba(255, 255, 255, .92);--lgc-toolbar-fg: var(--lgc-fg);--lgc-toolbar-border: var(--lgc-border);--lgc-tool-active-bg: rgba(79, 142, 247, .18);--lgc-tool-hover-bg: rgba(0, 0, 0, .05);--lgc-tooltip-bg: rgba(28, 31, 35, .9);--lgc-tooltip-fg: #ffffff;--lgc-menu-bg: #ffffff;--lgc-menu-fg: var(--lgc-fg);--lgc-menu-hover-bg: rgba(0, 0, 0, .06);--lgc-font: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;--lgc-font-size: 13px;position:relative;overflow:hidden;background:var(--lgc-bg);color:var(--lgc-fg);font-family:var(--lgc-font);font-size:var(--lgc-font-size)}.lora-graph-canvas .lgc-engine-mount{position:absolute;inset:0}.lora-graph-canvas .lgc-engine-mount>canvas{display:block}.lora-graph-canvas .lgc-toolbar{position:absolute;display:flex;gap:2px;padding:4px;background:var(--lgc-toolbar-bg);color:var(--lgc-toolbar-fg);border:1px solid var(--lgc-toolbar-border);border-radius:8px;backdrop-filter:blur(8px);box-shadow:0 1px 2px #0000000f;z-index:2}.lora-graph-canvas .lgc-toolbar--top,.lora-graph-canvas .lgc-toolbar--top-right,.lora-graph-canvas .lgc-toolbar--top-left{top:12px}.lora-graph-canvas .lgc-toolbar--top{left:50%;transform:translate(-50%)}.lora-graph-canvas .lgc-toolbar--top-right{right:12px}.lora-graph-canvas .lgc-toolbar--top-left{left:12px}.lora-graph-canvas .lgc-toolbar--bottom{bottom:12px;left:50%;transform:translate(-50%)}.lora-graph-canvas .lgc-tool{appearance:none;display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;padding:0;border:0;border-radius:6px;background:transparent;color:inherit;cursor:pointer;transition:background-color 90ms ease,color 90ms ease}.lora-graph-canvas .lgc-tool:hover{background:var(--lgc-tool-hover-bg)}.lora-graph-canvas .lgc-tool--active{background:var(--lgc-tool-active-bg);color:var(--lgc-accent)}.lora-graph-canvas .lgc-tool--disabled,.lora-graph-canvas .lgc-tool:disabled{opacity:.35;cursor:not-allowed}.lora-graph-canvas .lgc-tool--disabled:hover,.lora-graph-canvas .lgc-tool:disabled:hover{background:transparent}.lora-graph-canvas .lgc-tool:focus-visible{outline:2px solid var(--lgc-accent);outline-offset:1px}.lora-graph-canvas .lgc-selpanel{position:absolute;top:12px;left:12px;display:flex;align-items:center;gap:4px;padding:4px 6px 4px 10px;background:var(--lgc-toolbar-bg);color:var(--lgc-toolbar-fg);border:1px solid var(--lgc-toolbar-border);border-radius:8px;backdrop-filter:blur(8px);box-shadow:0 1px 2px #0000000f;font-size:12px;z-index:2}.lora-graph-canvas .lgc-selpanel-label{font-weight:500;white-space:nowrap}.lora-graph-canvas .lgc-selpanel-divider{width:1px;height:16px;background:var(--lgc-toolbar-border);margin:0 2px}.lora-graph-canvas .lgc-selpanel-btn{appearance:none;display:inline-flex;align-items:center;justify-content:center;width:24px;height:24px;padding:0;border:0;border-radius:4px;background:transparent;color:inherit;cursor:pointer;transition:background-color 90ms ease}.lora-graph-canvas .lgc-selpanel-btn:hover{background:var(--lgc-tool-hover-bg)}.lora-graph-canvas .lgc-selpanel-btn:disabled{opacity:.35;cursor:not-allowed}.lora-graph-canvas .lgc-selpanel-btn:disabled:hover{background:transparent}.lora-graph-canvas .lgc-options-menu{position:absolute;bottom:52px;right:12px;z-index:2}.lora-graph-canvas .lgc-options-trigger{appearance:none;display:inline-flex;align-items:center;justify-content:center;width:30px;height:30px;padding:0;background:var(--lgc-toolbar-bg);color:var(--lgc-toolbar-fg);border:1px solid var(--lgc-toolbar-border);border-radius:8px;backdrop-filter:blur(8px);box-shadow:0 1px 2px #0000000f;cursor:pointer;transition:background-color 90ms ease}.lora-graph-canvas .lgc-options-trigger:hover{background:var(--lgc-tool-hover-bg)}.lora-graph-canvas .lgc-options-trigger:focus-visible{outline:2px solid var(--lgc-accent);outline-offset:1px}.lora-graph-canvas .lgc-options-panel{position:absolute;bottom:36px;right:0;min-width:220px;padding:6px;background:var(--lgc-menu-bg);color:var(--lgc-menu-fg);border:1px solid var(--lgc-toolbar-border);border-radius:8px;box-shadow:0 8px 24px #00000029;font-size:12px;line-height:1.3}.lora-graph-canvas .lgc-options-item{display:flex;align-items:flex-start;gap:8px;padding:6px 8px;border-radius:4px;cursor:pointer}.lora-graph-canvas .lgc-options-item:hover{background:var(--lgc-menu-hover-bg)}.lora-graph-canvas .lgc-options-item input[type=checkbox]{margin-top:2px}.lora-graph-canvas .lgc-options-item-text{display:flex;flex-direction:column;gap:2px}.lora-graph-canvas .lgc-options-item-label{font-weight:500}.lora-graph-canvas .lgc-options-item-hint{opacity:.65;font-size:11px}.lora-graph-canvas .lgc-options-select{margin-left:auto;font:inherit;font-size:11px;padding:2px 4px;background:var(--lgc-toolbar-bg);color:var(--lgc-toolbar-fg);border:1px solid var(--lgc-toolbar-border);border-radius:4px}.lora-graph-canvas .lgc-mode-toggle{position:absolute;bottom:12px;right:12px;display:inline-flex;align-items:center;gap:6px;padding:5px 10px;background:var(--lgc-toolbar-bg);color:var(--lgc-toolbar-fg);border:1px solid var(--lgc-toolbar-border);border-radius:8px;backdrop-filter:blur(8px);box-shadow:0 1px 2px #0000000f;font:inherit;font-size:11px;font-weight:600;letter-spacing:.04em;cursor:pointer;z-index:2;transition:background-color 90ms ease}.lora-graph-canvas .lgc-mode-toggle:hover{background:var(--lgc-tool-hover-bg)}.lora-graph-canvas .lgc-mode-toggle:focus-visible{outline:2px solid var(--lgc-accent);outline-offset:1px}.lora-graph-canvas .lgc-mode-toggle-label{font-variant-numeric:tabular-nums}.lora-graph-canvas .lgc-legend{position:absolute;bottom:12px;left:12px;display:flex;flex-direction:column;gap:2px;padding:6px;background:var(--lgc-toolbar-bg);color:var(--lgc-toolbar-fg);border:1px solid var(--lgc-toolbar-border);border-radius:8px;backdrop-filter:blur(8px);font-size:12px;z-index:2;max-height:40%;overflow-y:auto}.lora-graph-canvas .lgc-legend-item{appearance:none;display:flex;align-items:center;gap:8px;padding:2px 8px 2px 4px;border:0;border-radius:4px;background:transparent;color:inherit;cursor:pointer;text-align:left;font:inherit}.lora-graph-canvas .lgc-legend-item:hover{background:var(--lgc-tool-hover-bg)}.lora-graph-canvas .lgc-legend-item--hidden{opacity:.35;text-decoration:line-through}.lora-graph-canvas .lgc-legend-swatch{display:inline-block;width:12px;height:12px;border-radius:3px}.lora-graph-canvas .lgc-marquee{position:absolute;border:1px dashed var(--lgc-accent);background:color-mix(in srgb,var(--lgc-accent) 12%,transparent);border-radius:2px;z-index:2}.lora-graph-canvas .lgc-tooltip{position:absolute;max-width:280px;padding:4px 8px;background:var(--lgc-tooltip-bg);color:var(--lgc-tooltip-fg);border-radius:4px;font-size:12px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;z-index:4;box-shadow:0 2px 6px #0000001f}.lora-graph-canvas .lgc-menu{position:absolute;min-width:180px;padding:4px;background:var(--lgc-menu-bg);color:var(--lgc-menu-fg);border:1px solid var(--lgc-toolbar-border);border-radius:8px;box-shadow:0 8px 24px #00000029;z-index:3}.lora-graph-canvas .lgc-menu-item{display:flex;align-items:center;gap:8px;padding:6px 10px;border-radius:4px;cursor:pointer;user-select:none}.lora-graph-canvas .lgc-menu-item:hover{background:var(--lgc-menu-hover-bg)}.lora-graph-canvas .lgc-menu-separator{height:1px;margin:4px 0;background:var(--lgc-toolbar-border)}
@@ -0,0 +1,8 @@
1
+ import { LoraGraphTheme } from '../types';
2
+ /** Light theme — matches the package default. Useful as a reset value
3
+ * when toggling between presets. */
4
+ export declare const lightTheme: LoraGraphTheme;
5
+ /** Dark theme — dark surface, accent kept blue. The engine
6
+ * `backgroundColor` accessor is independent; pair this with a dark
7
+ * `backgroundColor` prop for the full effect. */
8
+ export declare const darkTheme: LoraGraphTheme;
@@ -0,0 +1,17 @@
1
+ import { ReactNode } from 'react';
2
+ export interface ContextMenuItem {
3
+ id: string;
4
+ label: ReactNode;
5
+ shortcut?: string;
6
+ disabled?: boolean;
7
+ onSelect(): void;
8
+ }
9
+ export interface ContextMenuProps {
10
+ x: number;
11
+ y: number;
12
+ items: Array<ContextMenuItem | {
13
+ separator: true;
14
+ }>;
15
+ onClose(): void;
16
+ }
17
+ export declare function ContextMenu({ x, y, items, onClose }: ContextMenuProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,18 @@
1
+ import { ToolId, ToolbarConfig } from '../types';
2
+ export interface GraphToolbarProps {
3
+ /** Tools the user configured. Falsy → none; `true` → defaults;
4
+ * array → that exact order; object → include/exclude + position. */
5
+ config: boolean | ToolId[] | ToolbarConfig;
6
+ /** Currently active *toggleable* tool (the cursor mode). */
7
+ activeTool: ToolId;
8
+ /** Engine paused state, so we can swap pause↔resume into one slot. */
9
+ paused: boolean;
10
+ /** Current view mode, so the toggle-mode button can show the right
11
+ * icon and the right tooltip. */
12
+ mode: "2d" | "3d";
13
+ /** Optional predicate to grey out individual tools (e.g. undo / redo
14
+ * when the corresponding stack is empty). */
15
+ isDisabled?: (id: ToolId) => boolean;
16
+ onSelect(id: ToolId): void;
17
+ }
18
+ export declare function GraphToolbar(props: GraphToolbarProps): import("react/jsx-runtime").JSX.Element | null;
@@ -0,0 +1,11 @@
1
+ import { NodeObject } from '../types';
2
+ export interface GroupLegendProps<N extends NodeObject = NodeObject> {
3
+ nodes: N[];
4
+ /** Same accessor as `nodeAutoColorBy` — string key or function. */
5
+ groupBy?: string | ((n: N) => string | number | null);
6
+ /** Hidden group keys (stringified). Toggled by clicking. */
7
+ hidden: Set<string>;
8
+ onToggle(group: string): void;
9
+ }
10
+ export declare function colorForGroup(group: string): string;
11
+ export declare function GroupLegend<N extends NodeObject = NodeObject>({ nodes, groupBy, hidden, onToggle, }: GroupLegendProps<N>): import("react/jsx-runtime").JSX.Element | null;
@@ -0,0 +1,15 @@
1
+ export interface HoverTooltipProps {
2
+ /** Content the tooltip should display. Pass `null` to hide. */
3
+ content: string | HTMLElement | null;
4
+ /** Host element to anchor against (positions are relative to this). */
5
+ hostRef: React.RefObject<HTMLElement | null>;
6
+ }
7
+ /** Floating tooltip that tracks the mouse position inside the host
8
+ * element. Hidden when `content` is null.
9
+ *
10
+ * Perf note: the mousemove listener (and the per-move
11
+ * `getBoundingClientRect`) is only installed while there is content
12
+ * to show. Otherwise we'd be doing a forced layout + React render at
13
+ * 60Hz whenever the user's mouse is over the canvas, even though
14
+ * nothing is being displayed. Themed via --lgc-tooltip-*. */
15
+ export declare function HoverTooltip({ content, hostRef }: HoverTooltipProps): import("react/jsx-runtime").JSX.Element | null;
@@ -0,0 +1,13 @@
1
+ export interface MarqueeRect {
2
+ x0: number;
3
+ y0: number;
4
+ x1: number;
5
+ y1: number;
6
+ }
7
+ export interface MarqueeOverlayProps {
8
+ rect: MarqueeRect | null;
9
+ }
10
+ /** Renders the dashed selection rectangle while the user is dragging a
11
+ * marquee. Positioned absolutely inside the host. Hidden when `rect`
12
+ * is null. */
13
+ export declare function MarqueeOverlay({ rect }: MarqueeOverlayProps): import("react/jsx-runtime").JSX.Element | null;
@@ -0,0 +1,10 @@
1
+ export interface ModeToggleProps {
2
+ mode: "2d" | "3d";
3
+ onToggle(): void;
4
+ }
5
+ /** Floating bottom-right pill that switches between 2D and 3D modes.
6
+ * Pulled out of the main toolbar so it stays out of the way and is
7
+ * always findable. Hosts who want the toggle inline can add
8
+ * `"toggle-mode"` to their `tools` array — the inline button keeps
9
+ * working alongside this one. */
10
+ export declare function ModeToggle({ mode, onToggle }: ModeToggleProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,28 @@
1
+ /** A single entry shown inside the menu. Either a boolean checkbox
2
+ * or a dropdown select. */
3
+ export type OptionItem = {
4
+ kind?: "toggle";
5
+ id: string;
6
+ label: string;
7
+ checked: boolean;
8
+ onChange(next: boolean): void;
9
+ hint?: string;
10
+ } | {
11
+ kind: "select";
12
+ id: string;
13
+ label: string;
14
+ value: string;
15
+ options: Array<{
16
+ value: string;
17
+ label?: string;
18
+ }>;
19
+ onChange(next: string): void;
20
+ hint?: string;
21
+ };
22
+ export interface OptionsMenuProps {
23
+ items: OptionItem[];
24
+ }
25
+ /** Floating bottom-right button + popover. The button toggles a small
26
+ * panel of checkbox-style options. Auto-closes on outside click /
27
+ * Escape. Renders nothing if `items` is empty. */
28
+ export declare function OptionsMenu({ items }: OptionsMenuProps): import("react/jsx-runtime").JSX.Element | null;
@@ -0,0 +1,18 @@
1
+ export interface SelectionPanelProps {
2
+ nodeCount: number;
3
+ linkCount: number;
4
+ hasClipboard: boolean;
5
+ enableClipboard: boolean;
6
+ onDelete(): void;
7
+ onDuplicate(): void;
8
+ onAddConnected(): void;
9
+ onCopy(): void;
10
+ onCut(): void;
11
+ onPaste(): void;
12
+ onClear(): void;
13
+ }
14
+ /** Inline summary that sits in the top-left of the canvas while
15
+ * anything is selected. Surfaces the count + the most common
16
+ * selection-scoped actions so the user doesn't have to reach for
17
+ * the right-side toolbar. Hidden when nothing is selected. */
18
+ export declare function SelectionPanel({ nodeCount, linkCount, hasClipboard, enableClipboard, onDelete, onDuplicate, onAddConnected, onCopy, onCut, onPaste, onClear, }: SelectionPanelProps): import("react/jsx-runtime").JSX.Element | null;
@@ -0,0 +1,23 @@
1
+ import { SVGProps } from 'react';
2
+ type IconProps = SVGProps<SVGSVGElement>;
3
+ export declare const IconSelect: (p: IconProps) => import("react/jsx-runtime").JSX.Element;
4
+ export declare const IconPan: (p: IconProps) => import("react/jsx-runtime").JSX.Element;
5
+ export declare const IconAddNode: (p: IconProps) => import("react/jsx-runtime").JSX.Element;
6
+ export declare const IconAddLink: (p: IconProps) => import("react/jsx-runtime").JSX.Element;
7
+ export declare const IconDelete: (p: IconProps) => import("react/jsx-runtime").JSX.Element;
8
+ export declare const IconFit: (p: IconProps) => import("react/jsx-runtime").JSX.Element;
9
+ export declare const IconZoomIn: (p: IconProps) => import("react/jsx-runtime").JSX.Element;
10
+ export declare const IconZoomOut: (p: IconProps) => import("react/jsx-runtime").JSX.Element;
11
+ export declare const IconPause: (p: IconProps) => import("react/jsx-runtime").JSX.Element;
12
+ export declare const IconResume: (p: IconProps) => import("react/jsx-runtime").JSX.Element;
13
+ export declare const IconScreenshot: (p: IconProps) => import("react/jsx-runtime").JSX.Element;
14
+ export declare const IconCube: (p: IconProps) => import("react/jsx-runtime").JSX.Element;
15
+ export declare const IconSquare: (p: IconProps) => import("react/jsx-runtime").JSX.Element;
16
+ export declare const IconDuplicate: (p: IconProps) => import("react/jsx-runtime").JSX.Element;
17
+ export declare const IconSelectAll: (p: IconProps) => import("react/jsx-runtime").JSX.Element;
18
+ export declare const IconExport: (p: IconProps) => import("react/jsx-runtime").JSX.Element;
19
+ export declare const IconImport: (p: IconProps) => import("react/jsx-runtime").JSX.Element;
20
+ /** Source node on the left, a "+" on the right, joined by an edge —
21
+ * visualises "create a new node connected to the selection". */
22
+ export declare const IconAddConnected: (p: IconProps) => import("react/jsx-runtime").JSX.Element;
23
+ export {};
@@ -0,0 +1,37 @@
1
+ import { ComponentType, SVGProps } from 'react';
2
+ import { ToolId } from '../types';
3
+ export interface ToolDescriptor {
4
+ id: ToolId;
5
+ label: string;
6
+ /** Aria label / tooltip. */
7
+ hint: string;
8
+ icon: ComponentType<SVGProps<SVGSVGElement>>;
9
+ /** Tools that toggle an active mode (select, pan, add-node, add-link).
10
+ * Others are one-shot actions. */
11
+ toggleable?: boolean;
12
+ /** Suggested keybinding. The toolbar surfaces this in tooltips; the
13
+ * actual binding is registered by the component. */
14
+ shortcut?: string;
15
+ }
16
+ export declare const TOOL_DESCRIPTORS: Record<ToolId, ToolDescriptor>;
17
+ /** Default toolbar order when `tools={true}`.
18
+ *
19
+ * Tools are grouped by purpose:
20
+ * 1. cursor modes (select, pan, add-node, add-link)
21
+ * 2. view (fit, zoom-in, zoom-out)
22
+ * 3. engine (pause, resume)
23
+ * 4. file ops (screenshot, export-json, import-json)
24
+ *
25
+ * Selection-scoped actions — delete, duplicate, copy, cut, paste,
26
+ * clear — live in the auto-appearing top-left SelectionPanel so the
27
+ * main toolbar doesn't restate the same controls. `select-all` lives
28
+ * with the cursor modes (Cmd+A keybinding) since it bootstraps a
29
+ * selection rather than acting on one. `toggle-mode` is its own
30
+ * bottom-right pill (`<ModeToggle>`). Hosts who want a different
31
+ * layout can pass their own `tools` array. */
32
+ export declare const DEFAULT_TOOL_ORDER: ToolId[];
33
+ /** Icon for the toggle-mode button, depending on the currently active
34
+ * mode (we render a cube when in 2D — "switch to 3D" — and a square
35
+ * when in 3D). The toolbar reads from this rather than the static
36
+ * descriptor for that one button. */
37
+ export declare function toggleModeIcon(currentMode: "2d" | "3d"): (p: SVGProps<SVGSVGElement>) => import("react/jsx-runtime").JSX.Element;