@erd-studio/renderer 0.1.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 +167 -0
- package/README.md +132 -0
- package/dist/chunk-3PJCC7BX.js +5049 -0
- package/dist/chunk-6XQXDZ4R.js +0 -0
- package/dist/chunk-HOQPSLHQ.js +90 -0
- package/dist/chunk-QZGU5NZH.js +0 -0
- package/dist/chunk-WOTSQZCQ.js +151 -0
- package/dist/editor.js +124 -0
- package/dist/index.js +16 -0
- package/dist/sizing.js +16 -0
- package/dist/store.js +15 -0
- package/dist/styles.css +2963 -0
- package/dist/types/ErdCanvas.d.ts +54 -0
- package/dist/types/canvas/CanvasBackdrop.d.ts +7 -0
- package/dist/types/canvas/flowTypes.d.ts +10 -0
- package/dist/types/canvas/useCanvasGraph.d.ts +37 -0
- package/dist/types/components/DetailPanel/BulkColumnActions.d.ts +15 -0
- package/dist/types/components/DetailPanel/ColumnEditor.d.ts +19 -0
- package/dist/types/components/DetailPanel/DescriptionEditor.d.ts +16 -0
- package/dist/types/components/DetailPanel/DetailPanel.d.ts +10 -0
- package/dist/types/components/DetailPanel/GrainEditor.d.ts +16 -0
- package/dist/types/components/DetailPanel/ModelRationale.d.ts +27 -0
- package/dist/types/components/DetailPanel/RationaleField.d.ts +28 -0
- package/dist/types/components/DetailPanel/RoleEditor.d.ts +13 -0
- package/dist/types/components/Graph/AnnotationEdge.d.ts +11 -0
- package/dist/types/components/Graph/AnnotationNode.d.ts +18 -0
- package/dist/types/components/Graph/ColumnTooltip.d.ts +25 -0
- package/dist/types/components/Graph/FkEdge.d.ts +21 -0
- package/dist/types/components/Graph/HoverTip.d.ts +47 -0
- package/dist/types/components/Graph/ModelNode.d.ts +21 -0
- package/dist/types/components/Legend/Legend.d.ts +11 -0
- package/dist/types/components/common/ColumnRowEditor.d.ts +75 -0
- package/dist/types/components/common/DataTypeSelect.d.ts +20 -0
- package/dist/types/components/common/KeyBadge.d.ts +16 -0
- package/dist/types/components/common/KeyBadgeGroup.d.ts +20 -0
- package/dist/types/editor.d.ts +27 -0
- package/dist/types/hooks/useColumnExpansion.d.ts +36 -0
- package/dist/types/hooks/useColumnReorder.d.ts +36 -0
- package/dist/types/hooks/useFocusWithinRow.d.ts +23 -0
- package/dist/types/hooks/useLongPressDrag.d.ts +31 -0
- package/dist/types/host/canvasEnvironment.d.ts +36 -0
- package/dist/types/index.d.ts +5 -0
- package/dist/types/lib/annotationColors.d.ts +13 -0
- package/dist/types/lib/badgeLabels.d.ts +63 -0
- package/dist/types/lib/cardinalityUtils.d.ts +14 -0
- package/dist/types/lib/dataTypeColors.d.ts +15 -0
- package/dist/types/lib/dragHandleSides.d.ts +27 -0
- package/dist/types/lib/edgeDistribution.d.ts +165 -0
- package/dist/types/lib/graphTransformer.d.ts +65 -0
- package/dist/types/lib/nodeOverlays.d.ts +41 -0
- package/dist/types/lib/nodeSizing.d.ts +50 -0
- package/dist/types/lib/stageColors.d.ts +16 -0
- package/dist/types/sizing.d.ts +1 -0
- package/dist/types/store/editorStore.d.ts +185 -0
- package/dist/types/store.d.ts +2 -0
- package/dist/types/types/graph.d.ts +145 -0
- package/package.json +72 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useColumnReorder — drag-to-reorder state machine for column lists.
|
|
3
|
+
*
|
|
4
|
+
* Uses imperative mouse events (matching the existing useLongPressDrag pattern).
|
|
5
|
+
* Returns drag state, handle props for each row, and the optimistic display order.
|
|
6
|
+
*
|
|
7
|
+
* Works in both DetailPanel (ColumnRowEditor rows) and ModelNode (ColumnRow rows)
|
|
8
|
+
* by accepting configurable CSS selectors for the container and row elements.
|
|
9
|
+
*/
|
|
10
|
+
interface Column {
|
|
11
|
+
name: string;
|
|
12
|
+
}
|
|
13
|
+
interface UseColumnReorderOptions<T extends Column> {
|
|
14
|
+
/** Source-of-truth columns from the extension. */
|
|
15
|
+
columns: T[];
|
|
16
|
+
/** Called on drop with the final ordered column names. */
|
|
17
|
+
onReorder: (orderedNames: string[]) => void;
|
|
18
|
+
/** CSS selector for the scrollable list container (walked up from the handle). */
|
|
19
|
+
containerSelector?: string;
|
|
20
|
+
/** CSS selector for individual row elements within the container. */
|
|
21
|
+
rowSelector?: string;
|
|
22
|
+
}
|
|
23
|
+
interface UseColumnReorderReturn<T extends Column> {
|
|
24
|
+
/** Columns in display order (same as input — reorder applied on drop). */
|
|
25
|
+
orderedColumns: T[];
|
|
26
|
+
/** Index of the row currently being dragged, or null. */
|
|
27
|
+
dragIndex: number | null;
|
|
28
|
+
/** Current insertion point index, or null. */
|
|
29
|
+
dropIndex: number | null;
|
|
30
|
+
/** Props to spread onto the drag handle element for a given row index. */
|
|
31
|
+
getDragHandleProps: (index: number) => {
|
|
32
|
+
onMouseDown: (e: React.MouseEvent) => void;
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
export declare function useColumnReorder<T extends Column>({ columns, onReorder, containerSelector, rowSelector, }: UseColumnReorderOptions<T>): UseColumnReorderReturn<T>;
|
|
36
|
+
export {};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useFocusWithinRow — hook for tracking focus within a container element.
|
|
3
|
+
*
|
|
4
|
+
* Uses requestAnimationFrame instead of setTimeout to reliably detect
|
|
5
|
+
* when focus leaves a row container. This eliminates flicker issues
|
|
6
|
+
* when switching between fields (e.g., name input → data type dropdown).
|
|
7
|
+
*/
|
|
8
|
+
export interface UseFocusWithinRowResult {
|
|
9
|
+
/** Whether focus is currently within the row container. */
|
|
10
|
+
isFocusWithin: boolean;
|
|
11
|
+
/** Props to spread on the row container element. */
|
|
12
|
+
rowProps: {
|
|
13
|
+
onFocus: (e: React.FocusEvent) => void;
|
|
14
|
+
onBlur: (e: React.FocusEvent) => void;
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Hook for tracking focus within a row container.
|
|
19
|
+
*
|
|
20
|
+
* @param onFocusLeave - Callback fired when focus leaves the row entirely.
|
|
21
|
+
* Use this to trigger auto-save or validation.
|
|
22
|
+
*/
|
|
23
|
+
export declare function useFocusWithinRow(onFocusLeave?: () => void): UseFocusWithinRowResult;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom hook for detecting long-press (press and hold) gestures.
|
|
3
|
+
*
|
|
4
|
+
* Used for initiating drag-to-create-relationship interactions. After the
|
|
5
|
+
* specified delay, `onLongPressStart` fires to indicate a drag has begun.
|
|
6
|
+
* The consumer is responsible for tracking mouse position and handling the
|
|
7
|
+
* drop via global event listeners.
|
|
8
|
+
*/
|
|
9
|
+
export interface UseLongPressDragOptions {
|
|
10
|
+
/** Delay in ms before long-press is triggered. Default: 220ms */
|
|
11
|
+
delay?: number;
|
|
12
|
+
/** Called when long-press threshold is reached */
|
|
13
|
+
onLongPressStart?: () => void;
|
|
14
|
+
/** Called when press is cancelled (mouseup before delay, or mouse leaves) */
|
|
15
|
+
onCancel?: () => void;
|
|
16
|
+
}
|
|
17
|
+
export interface UseLongPressDragReturn {
|
|
18
|
+
/** Whether the press timer is active (for visual feedback like pulsing) */
|
|
19
|
+
isPressing: boolean;
|
|
20
|
+
/** Whether long-press completed and drag is active */
|
|
21
|
+
isDragging: boolean;
|
|
22
|
+
/** Call this to end the drag state (e.g., after drop) */
|
|
23
|
+
endDrag: () => void;
|
|
24
|
+
/** Event handlers to spread onto the pressable element */
|
|
25
|
+
handlers: {
|
|
26
|
+
onMouseDown: (e: React.MouseEvent) => void;
|
|
27
|
+
onMouseUp: () => void;
|
|
28
|
+
onMouseLeave: () => void;
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
export declare function useLongPressDrag(options?: UseLongPressDragOptions): UseLongPressDragReturn;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The canvas's link to whatever page embeds it.
|
|
3
|
+
*
|
|
4
|
+
* Canvas components never talk to their host directly. Edits made on the
|
|
5
|
+
* canvas (renaming a column, moving an annotation, …) are posted as
|
|
6
|
+
* `CanvasEditMessage`s to the `CanvasHost` supplied by the nearest
|
|
7
|
+
* `CanvasEnvironmentProvider`. The VS Code extension supplies a host that
|
|
8
|
+
* forwards them to the extension; without a provider, messages go nowhere.
|
|
9
|
+
*
|
|
10
|
+
* The environment also carries the `viewer` flag, which read-only embeddings
|
|
11
|
+
* set to present the canvas without its editing affordances.
|
|
12
|
+
*/
|
|
13
|
+
import { type ReactNode } from 'react';
|
|
14
|
+
import type { CanvasEditMessage } from '@erd-studio/core';
|
|
15
|
+
/** Receives the edits made on the canvas. */
|
|
16
|
+
export interface CanvasHost {
|
|
17
|
+
postMessage(message: CanvasEditMessage): void;
|
|
18
|
+
}
|
|
19
|
+
export interface CanvasEnvironment {
|
|
20
|
+
host: CanvasHost;
|
|
21
|
+
/** Read-only viewer presentation (no editing affordances). */
|
|
22
|
+
viewer: boolean;
|
|
23
|
+
}
|
|
24
|
+
/** A host that drops every message. */
|
|
25
|
+
export declare const NOOP_CANVAS_HOST: CanvasHost;
|
|
26
|
+
export declare function CanvasEnvironmentProvider({ host, viewer, children, }: {
|
|
27
|
+
host?: CanvasHost;
|
|
28
|
+
viewer?: boolean;
|
|
29
|
+
children: ReactNode;
|
|
30
|
+
}): JSX.Element;
|
|
31
|
+
/** The host edits are posted to (a no-op host outside any provider). */
|
|
32
|
+
export declare function useCanvasHost(): CanvasHost;
|
|
33
|
+
/** A stable function that posts an edit to the host. */
|
|
34
|
+
export declare function useSend(): (message: CanvasEditMessage) => void;
|
|
35
|
+
/** Whether the canvas is in read-only viewer mode (false outside any provider). */
|
|
36
|
+
export declare function useIsViewer(): boolean;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { ErdCanvas, type ErdCanvasProps, type ErdCanvasHandle } from './ErdCanvas.js';
|
|
2
|
+
export { transformDomain, pickHandleSides, type TransformResult, type TransformOptions, type NodeRect, type NodeDimensions, } from './lib/graphTransformer.js';
|
|
3
|
+
export { repickHandleSides, nodeRect } from './lib/dragHandleSides.js';
|
|
4
|
+
export type { DisplayDomain, DisplayModel, DisplayColumn, DisplayRelationship, PhysicalColumnSource, PhysicalProvenance, Annotation, AnnotationColor, Cardinality, Layer, ModelRole, NodePosition, Rationale, Stage, ViewConfig, LayerConfig, } from '@erd-studio/core';
|
|
5
|
+
export type { ModelFlowNode, ModelNodeData, FkFlowEdge, FkEdgeData, AnnotationFlowNode, AnnotationFlowEdge, ColumnDisplay, } from './types/graph.js';
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared annotation colour constants.
|
|
3
|
+
*
|
|
4
|
+
* Used by AnnotationNode (inline toolbar) and ContextMenu (right-click menu)
|
|
5
|
+
* to render identical colour swatch rows from a single source of truth.
|
|
6
|
+
*/
|
|
7
|
+
import type { AnnotationColor } from '@erd-studio/core';
|
|
8
|
+
export interface AnnotationColorOption {
|
|
9
|
+
value: AnnotationColor;
|
|
10
|
+
label: string;
|
|
11
|
+
swatch: string;
|
|
12
|
+
}
|
|
13
|
+
export declare const ANNOTATION_COLORS: AnnotationColorOption[];
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* badgeLabels — the abbreviations and symbols the canvas stamps on nodes and
|
|
3
|
+
* columns, and the prose that decodes them.
|
|
4
|
+
*
|
|
5
|
+
* These live here rather than in ModelNode because the Legend explains exactly
|
|
6
|
+
* this vocabulary. Two copies of "WH means the warehouse catalog" is two things
|
|
7
|
+
* to keep in step, and the legend is precisely the copy nobody edits when the
|
|
8
|
+
* node changes. Both ends import these maps, so a new source or a renamed chip
|
|
9
|
+
* shows up in the legend for free.
|
|
10
|
+
*
|
|
11
|
+
* Pure data — no vscode, no DOM.
|
|
12
|
+
*/
|
|
13
|
+
import type { PhysicalColumnSource } from '@erd-studio/core';
|
|
14
|
+
/**
|
|
15
|
+
* Header chip text for each physical column source. Three letters, because the
|
|
16
|
+
* chip shares a fixed-height header row with the name and the schema badge —
|
|
17
|
+
* the sentence-length explanation lives in the chip's title and in the
|
|
18
|
+
* DetailPanel's "Columns from" row.
|
|
19
|
+
*/
|
|
20
|
+
export declare const SOURCE_LABEL: Record<PhysicalColumnSource, string>;
|
|
21
|
+
/** How each source is named in prose (chip tooltip). */
|
|
22
|
+
export declare const SOURCE_PHRASE: Record<PhysicalColumnSource, string>;
|
|
23
|
+
/**
|
|
24
|
+
* The order the legend lists the sources in: most authoritative first, which is
|
|
25
|
+
* also the order `provenance.columns` arrives in.
|
|
26
|
+
*/
|
|
27
|
+
export declare const SOURCE_ORDER: PhysicalColumnSource[];
|
|
28
|
+
/** One line per source for the legend, saying what having that chip means. */
|
|
29
|
+
export declare const SOURCE_LEGEND_DESC: Record<PhysicalColumnSource, string>;
|
|
30
|
+
/** Unicode circled numbers for SCD type badges. */
|
|
31
|
+
export declare const SCD_BADGE: Record<number, string>;
|
|
32
|
+
/**
|
|
33
|
+
* What each SCD badge is claiming, for the badge's title. A bare "SCD Type 2"
|
|
34
|
+
* only re-reads the number back to someone who is hovering precisely because
|
|
35
|
+
* the number meant nothing to them.
|
|
36
|
+
*/
|
|
37
|
+
export declare const SCD_TITLE: Record<number, string>;
|
|
38
|
+
/** Symbols for additive type badges. */
|
|
39
|
+
export declare const ADDITIVE_BADGE: Record<string, string>;
|
|
40
|
+
/** The same courtesy for the additive symbols. */
|
|
41
|
+
export declare const ADDITIVE_TITLE: Record<string, string>;
|
|
42
|
+
/**
|
|
43
|
+
* What a coloured or dashed edge is claiming during a comparison. The colours
|
|
44
|
+
* alone carry this today, which is unreadable to anyone who has not just read
|
|
45
|
+
* the legend — and invisible to anyone who cannot tell amber from red.
|
|
46
|
+
*/
|
|
47
|
+
export declare const DISCREPANCY_PHRASE: Record<'extra' | 'missing' | 'cardinality-mismatch', string>;
|
|
48
|
+
/** The fields of an edge that its hover text is built from. */
|
|
49
|
+
export interface EdgeHoverInput {
|
|
50
|
+
fromModel: string;
|
|
51
|
+
fromColumn: string;
|
|
52
|
+
toModel: string;
|
|
53
|
+
toColumn: string;
|
|
54
|
+
cardinality: string;
|
|
55
|
+
discrepancyStatus?: 'extra' | 'missing' | 'cardinality-mismatch';
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Hover text for a relationship line: which columns it joins, its cardinality,
|
|
59
|
+
* and — during a comparison — what its colour is saying. The `*` / `1` glyphs
|
|
60
|
+
* are `pointer-events: none` so the line itself is the only part of an edge
|
|
61
|
+
* that can carry this.
|
|
62
|
+
*/
|
|
63
|
+
export declare function edgeHoverText(edge: EdgeHoverInput): string;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cardinality utility functions.
|
|
3
|
+
*
|
|
4
|
+
* Pure helpers for working with FK relationship cardinality values.
|
|
5
|
+
*/
|
|
6
|
+
import type { Cardinality } from '@erd-studio/core';
|
|
7
|
+
/**
|
|
8
|
+
* Returns the inverse cardinality (source/target sides swapped).
|
|
9
|
+
*
|
|
10
|
+
* many-to-one ↔ one-to-many
|
|
11
|
+
* one-to-one → one-to-one (symmetric, no-op)
|
|
12
|
+
* many-to-many → many-to-many (symmetric, no-op)
|
|
13
|
+
*/
|
|
14
|
+
export declare function swapCardinality(c: Cardinality): Cardinality;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Data-type colour mapping for column type text.
|
|
3
|
+
*
|
|
4
|
+
* Hand-picked colours for the 8 preset SQL types; deterministic
|
|
5
|
+
* hash-derived colours for any custom / unknown type.
|
|
6
|
+
*
|
|
7
|
+
* Colours are HSL strings with moderate saturation and lightness tuned
|
|
8
|
+
* for readability on both dark and light VS Code themes.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Returns an HSL colour string for a given SQL data type.
|
|
12
|
+
* Preset types get a hand-picked colour; everything else gets a
|
|
13
|
+
* deterministic hash-derived colour.
|
|
14
|
+
*/
|
|
15
|
+
export declare function getDataTypeColor(dataType: string): string;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Handle sides for edges after nodes are moved on the canvas.
|
|
3
|
+
*
|
|
4
|
+
* `transformDomain` chooses the handle side at each end of an edge from the
|
|
5
|
+
* saved positions: `pickHandleSides` compares the two node rectangles, and a
|
|
6
|
+
* self-referencing relationship always uses the top (source) and right
|
|
7
|
+
* (target) handles. A canvas that lets nodes be dragged without rebuilding the
|
|
8
|
+
* domain has to make that choice again for the edges on the dragged nodes, or
|
|
9
|
+
* those edges stay attached to the side that faced the old position.
|
|
10
|
+
*
|
|
11
|
+
* The rectangles follow `transformDomain` too: a node's size is its measured
|
|
12
|
+
* size when React Flow has one, otherwise the model-node estimate from
|
|
13
|
+
* `resolveNodeDimensions`, or an annotation's own (or default) size.
|
|
14
|
+
*/
|
|
15
|
+
import { type NodeRect } from './graphTransformer.js';
|
|
16
|
+
import type { GraphNode, GraphEdge } from './nodeOverlays.js';
|
|
17
|
+
/** The rectangle `pickHandleSides` compares for `node`: its position and size. */
|
|
18
|
+
export declare function nodeRect(node: GraphNode): NodeRect;
|
|
19
|
+
/**
|
|
20
|
+
* Recompute the handles of the edges attached to any node in `movedIds`.
|
|
21
|
+
*
|
|
22
|
+
* Only edges whose handles actually change are replaced; every other entry is
|
|
23
|
+
* the original edge. If none change, `edges` is returned as is, which lets a
|
|
24
|
+
* caller compare by reference and skip a store update. Edges with an endpoint
|
|
25
|
+
* missing from `nodes` are left as they are.
|
|
26
|
+
*/
|
|
27
|
+
export declare function repickHandleSides(nodes: ReadonlyArray<GraphNode>, edges: GraphEdge[], movedIds: ReadonlySet<string>): GraphEdge[];
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Edge distribution — calculates evenly-spaced connection points along node sides.
|
|
3
|
+
*
|
|
4
|
+
* When multiple edges connect to the same side of a node, they would normally
|
|
5
|
+
* all connect at the center point, causing visual overlap. This module provides
|
|
6
|
+
* functions to distribute those connection points evenly along the side.
|
|
7
|
+
*
|
|
8
|
+
* The algorithm:
|
|
9
|
+
* 1. Group edges by (nodeId, side) — ALL edges on a side, regardless of
|
|
10
|
+
* direction (incoming/outgoing). This prevents overlap between source
|
|
11
|
+
* and target edges that would otherwise be centered independently.
|
|
12
|
+
* 2. For each edge, find its index in the sorted group (consistent ordering)
|
|
13
|
+
* 3. Calculate offset from center: evenly divide the side length
|
|
14
|
+
* 4. Apply offset perpendicular to the edge direction
|
|
15
|
+
*/
|
|
16
|
+
import type { Edge } from '@xyflow/react';
|
|
17
|
+
/** Default node width when measured dimensions not available. */
|
|
18
|
+
export declare const DEFAULT_NODE_WIDTH = 280;
|
|
19
|
+
/** Default node height when measured dimensions not available. */
|
|
20
|
+
export declare const DEFAULT_NODE_HEIGHT = 200;
|
|
21
|
+
export type Side = 'top' | 'right' | 'bottom' | 'left';
|
|
22
|
+
export interface EdgeOffset {
|
|
23
|
+
x: number;
|
|
24
|
+
y: number;
|
|
25
|
+
}
|
|
26
|
+
/** Map of node IDs to their positions. */
|
|
27
|
+
export type NodePositionMap = Map<string, {
|
|
28
|
+
x: number;
|
|
29
|
+
y: number;
|
|
30
|
+
}>;
|
|
31
|
+
/**
|
|
32
|
+
* Get all edge IDs that connect to a specific node side in a specific direction.
|
|
33
|
+
*
|
|
34
|
+
* @param edges — all edges in the graph
|
|
35
|
+
* @param nodeId — the node to check connections for
|
|
36
|
+
* @param side — which side of the node (top/right/bottom/left)
|
|
37
|
+
* @param isSource — true to find edges where this node is the source,
|
|
38
|
+
* false for edges where this node is the target
|
|
39
|
+
* @returns Sorted array of edge IDs for consistent ordering
|
|
40
|
+
*/
|
|
41
|
+
export declare function getEdgesForSide(edges: Edge[], nodeId: string, side: Side, isSource: boolean): string[];
|
|
42
|
+
/**
|
|
43
|
+
* Get all edge IDs that connect to a specific node side, regardless of direction.
|
|
44
|
+
*
|
|
45
|
+
* This includes both edges where the node is the source (outgoing) AND edges
|
|
46
|
+
* where it's the target (incoming). Used for distribution to prevent overlap
|
|
47
|
+
* between incoming and outgoing edges on the same side.
|
|
48
|
+
*
|
|
49
|
+
* When nodePositions is provided, edges are sorted by the position of their
|
|
50
|
+
* connected node (the node at the OTHER end of the edge):
|
|
51
|
+
* - For left/right sides: sort by Y position (top to bottom)
|
|
52
|
+
* - For top/bottom sides: sort by X position (left to right)
|
|
53
|
+
*
|
|
54
|
+
* This sorting minimizes edge crossings by aligning connection points with
|
|
55
|
+
* the visual flow of the connected nodes.
|
|
56
|
+
*
|
|
57
|
+
* @param edges — all edges in the graph
|
|
58
|
+
* @param nodeId — the node to check connections for
|
|
59
|
+
* @param side — which side of the node (top/right/bottom/left)
|
|
60
|
+
* @param nodePositions — optional map of node positions for spatial sorting
|
|
61
|
+
* @returns Sorted array of edge IDs for consistent ordering
|
|
62
|
+
*/
|
|
63
|
+
export declare function getAllEdgesForSide(edges: Edge[], nodeId: string, side: Side, nodePositions?: NodePositionMap): string[];
|
|
64
|
+
/**
|
|
65
|
+
* All edges that connect to `nodeId` on `side`, in either direction.
|
|
66
|
+
*
|
|
67
|
+
* Returns a cached array (do not mutate) — identity is stable for a given
|
|
68
|
+
* `edges` array, so it is safe to use as a memo dependency.
|
|
69
|
+
*/
|
|
70
|
+
export declare function getEdgesOnSide(edges: Edge[], nodeId: string, side: Side): readonly Edge[];
|
|
71
|
+
/**
|
|
72
|
+
* Sort a side group into its final connection order and return edge IDs.
|
|
73
|
+
*
|
|
74
|
+
* When `nodePositions` is provided, edges are ordered by the position of the
|
|
75
|
+
* node at the OTHER end (Y for left/right sides, X for top/bottom) so
|
|
76
|
+
* connection points follow the visual flow and edges do not cross.
|
|
77
|
+
* Falls back to alphabetical ID order when positions are unavailable.
|
|
78
|
+
*/
|
|
79
|
+
export declare function sortEdgesForSide(edgesForSide: readonly Edge[], nodeId: string, side: Side, nodePositions?: NodePositionMap): string[];
|
|
80
|
+
/**
|
|
81
|
+
* Parse the side from a handle ID.
|
|
82
|
+
*
|
|
83
|
+
* Handle IDs follow the format "node-{side}-{src|tgt}".
|
|
84
|
+
* Returns undefined if the handle ID doesn't match the expected format.
|
|
85
|
+
*/
|
|
86
|
+
export declare function parseSideFromHandle(handleId: string | null | undefined): Side | undefined;
|
|
87
|
+
/** Node dimensions from React Flow's internal node measurement. */
|
|
88
|
+
interface MeasuredDimensions {
|
|
89
|
+
width?: number;
|
|
90
|
+
height?: number;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Get the length of a node side in pixels.
|
|
94
|
+
*
|
|
95
|
+
* For horizontal sides (top/bottom), returns the node width.
|
|
96
|
+
* For vertical sides (left/right), returns the node height.
|
|
97
|
+
* Falls back to default dimensions if the node hasn't been measured yet.
|
|
98
|
+
*
|
|
99
|
+
* @param side — which side of the node
|
|
100
|
+
* @param measured — the node's measured dimensions (may be undefined)
|
|
101
|
+
* @returns Side length in pixels
|
|
102
|
+
*/
|
|
103
|
+
export declare function getSideLength(side: Side, measured: MeasuredDimensions | undefined): number;
|
|
104
|
+
/**
|
|
105
|
+
* Calculate the offset for a connection point along a node side.
|
|
106
|
+
*
|
|
107
|
+
* Distributes connection points evenly along the side. For example, with
|
|
108
|
+
* 3 edges connecting to a 280px wide side:
|
|
109
|
+
* - spacing = 280 / 4 = 70px
|
|
110
|
+
* - positions: 70px, 140px, 210px (from left edge)
|
|
111
|
+
* - offsets from center (140px): -70px, 0px, +70px
|
|
112
|
+
*
|
|
113
|
+
* @param edgeIndex — this edge's position in the group (0-based)
|
|
114
|
+
* @param groupSize — total number of edges connecting to this side
|
|
115
|
+
* @param sideLength — width or height of the side in pixels
|
|
116
|
+
* @param side — which side (determines offset axis)
|
|
117
|
+
* @returns Offset in pixels from the center of the side
|
|
118
|
+
*/
|
|
119
|
+
export declare function calculateDistributionOffset(edgeIndex: number, groupSize: number, sideLength: number, side: Side): EdgeOffset;
|
|
120
|
+
/**
|
|
121
|
+
* Calculate the full offset for an edge's connection point.
|
|
122
|
+
*
|
|
123
|
+
* This is the main entry point — combines grouping and offset calculation.
|
|
124
|
+
* Groups ALL edges on a side together (both incoming and outgoing) to prevent
|
|
125
|
+
* overlap between source and target edges.
|
|
126
|
+
*
|
|
127
|
+
* When nodePositions is provided, edges are sorted by their connected node's
|
|
128
|
+
* position to minimize visual crossings.
|
|
129
|
+
*
|
|
130
|
+
* @param edgeId — the edge to calculate offset for
|
|
131
|
+
* @param nodeId — the node this edge connects to
|
|
132
|
+
* @param side — which side of the node
|
|
133
|
+
* @param isSource — true if calculating source offset, false for target (kept for API compatibility)
|
|
134
|
+
* @param allEdges — all edges in the graph
|
|
135
|
+
* @param sideLength — width (for top/bottom) or height (for left/right) of the node
|
|
136
|
+
* @param nodePositions — optional map of node positions for spatial sorting
|
|
137
|
+
* @returns Offset in pixels from the center of the side
|
|
138
|
+
*/
|
|
139
|
+
export declare function calculateEdgeOffset(edgeId: string, nodeId: string, side: Side, _isSource: boolean, allEdges: Edge[], sideLength: number, nodePositions?: NodePositionMap): EdgeOffset;
|
|
140
|
+
/**
|
|
141
|
+
* Same as `calculateEdgeOffset` but for a pre-grouped side (see
|
|
142
|
+
* `getEdgesOnSide`), avoiding a scan of the full edge array per call.
|
|
143
|
+
*/
|
|
144
|
+
export declare function calculateEdgeOffsetInGroup(edgeId: string, nodeId: string, side: Side, edgesOnSide: readonly Edge[], sideLength: number, nodePositions?: NodePositionMap): EdgeOffset;
|
|
145
|
+
/**
|
|
146
|
+
* Build a compact, value-comparable key of the given nodes' positions.
|
|
147
|
+
*
|
|
148
|
+
* React Flow mutates its `nodeLookup` Map in place (`adoptUserNodes` clears
|
|
149
|
+
* and refills it), so the Map identity never changes and cannot be used as a
|
|
150
|
+
* memo dependency. Subscribing to this string instead re-renders exactly when
|
|
151
|
+
* one of the listed nodes moves.
|
|
152
|
+
*/
|
|
153
|
+
export declare function buildPositionsKey(nodeLookup: ReadonlyMap<string, {
|
|
154
|
+
position?: {
|
|
155
|
+
x: number;
|
|
156
|
+
y: number;
|
|
157
|
+
};
|
|
158
|
+
internals?: {
|
|
159
|
+
positionAbsolute?: {
|
|
160
|
+
x: number;
|
|
161
|
+
y: number;
|
|
162
|
+
};
|
|
163
|
+
};
|
|
164
|
+
}>, nodeIds: readonly string[]): string;
|
|
165
|
+
export {};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Graph transformer — converts a DisplayDomain into React Flow nodes and edges.
|
|
3
|
+
*
|
|
4
|
+
* This is a pure function with no side effects, making it easy to unit test.
|
|
5
|
+
* It handles:
|
|
6
|
+
* 1. Mapping each DisplayModel → ModelFlowNode (with position + columns)
|
|
7
|
+
* 2. Mapping each DisplayRelationship → FkFlowEdge (with handle side selection)
|
|
8
|
+
* 3. Selecting optimal handle sides based on relative node positions
|
|
9
|
+
* 4. Injecting discrepancy data and ghost nodes/edges when a comparison report is active
|
|
10
|
+
*/
|
|
11
|
+
import type { DisplayDomain } from '@erd-studio/core';
|
|
12
|
+
import type { DiscrepancyReport } from '@erd-studio/core';
|
|
13
|
+
import type { ModelFlowNode, FkFlowEdge, AnnotationFlowNode, AnnotationFlowEdge } from '../types/graph.js';
|
|
14
|
+
export interface TransformResult {
|
|
15
|
+
nodes: (ModelFlowNode | AnnotationFlowNode)[];
|
|
16
|
+
edges: (FkFlowEdge | AnnotationFlowEdge)[];
|
|
17
|
+
}
|
|
18
|
+
/** Width/height of a rendered node, in canvas pixels. */
|
|
19
|
+
export interface NodeDimensions {
|
|
20
|
+
width: number;
|
|
21
|
+
height: number;
|
|
22
|
+
}
|
|
23
|
+
/** Optional parameters for discrepancy overlay rendering. */
|
|
24
|
+
export interface TransformOptions {
|
|
25
|
+
/** Active cross-stage discrepancy report (e.g., physical vs logical). */
|
|
26
|
+
discrepancyReport?: DiscrepancyReport;
|
|
27
|
+
/**
|
|
28
|
+
* Measured node sizes (keyed by node id) from the current React Flow state.
|
|
29
|
+
* Used to pick edge handle sides from node centres rather than top-left
|
|
30
|
+
* corners. Nodes without an entry fall back to an estimate.
|
|
31
|
+
*/
|
|
32
|
+
nodeDimensions?: ReadonlyMap<string, NodeDimensions>;
|
|
33
|
+
/** Column expansion state per model — sharpens the height estimate fallback. */
|
|
34
|
+
isExpanded?: (modelName: string) => boolean;
|
|
35
|
+
}
|
|
36
|
+
type Side = 'top' | 'right' | 'bottom' | 'left';
|
|
37
|
+
/** A node's top-left position plus its rendered size. */
|
|
38
|
+
export interface NodeRect {
|
|
39
|
+
x: number;
|
|
40
|
+
y: number;
|
|
41
|
+
width: number;
|
|
42
|
+
height: number;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Choose which side of each node to connect, minimising visual bends.
|
|
46
|
+
*
|
|
47
|
+
* Compares the relative position of the source and target node *centres*
|
|
48
|
+
* and picks the axis (horizontal or vertical) with the greater distance.
|
|
49
|
+
* On that axis, the source connects on the side facing the target and vice
|
|
50
|
+
* versa. Centres (not top-left corners) matter because a tall node next to a
|
|
51
|
+
* short one would otherwise route its edge out of the wrong side.
|
|
52
|
+
*/
|
|
53
|
+
export declare function pickHandleSides(source: NodeRect, target: NodeRect): {
|
|
54
|
+
sourceSide: Side;
|
|
55
|
+
targetSide: Side;
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* Convert a DisplayDomain into React Flow nodes and edges.
|
|
59
|
+
*
|
|
60
|
+
* @param domain — the display domain from extension host
|
|
61
|
+
* @param options — optional discrepancy report and ghost positions
|
|
62
|
+
* @returns nodes and edges ready for React Flow's `<ReactFlow>` component
|
|
63
|
+
*/
|
|
64
|
+
export declare function transformDomain(domain: DisplayDomain, options?: TransformOptions): TransformResult;
|
|
65
|
+
export {};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Node/edge overlays — ephemeral per-node presentation state layered on top
|
|
3
|
+
* of the graph produced by `transformDomain`:
|
|
4
|
+
*
|
|
5
|
+
* - `dimmed`: search-miss or not-connected-to-selection dimming (F402)
|
|
6
|
+
* - `isExpanded` / `onToggleExpansion`: column expansion (F405)
|
|
7
|
+
*
|
|
8
|
+
* `applyNodeOverlays` is a pure function that preserves object identity for
|
|
9
|
+
* every node/edge whose overlay values did not change, so React Flow's
|
|
10
|
+
* `adoptUserNodes` short-circuits on unchanged nodes and memoised
|
|
11
|
+
* `ModelNode` / `FkEdge` components skip re-rendering. When nothing changed
|
|
12
|
+
* at all, the *same arrays* are returned so callers can skip `setNodes`.
|
|
13
|
+
*
|
|
14
|
+
* This lets selection / search / expansion changes avoid re-running the full
|
|
15
|
+
* domain transform (which would also drop `measured` and force React Flow to
|
|
16
|
+
* re-measure every node).
|
|
17
|
+
*/
|
|
18
|
+
import type { ModelFlowNode, FkFlowEdge, AnnotationFlowNode, AnnotationFlowEdge } from '../types/graph.js';
|
|
19
|
+
export type GraphNode = ModelFlowNode | AnnotationFlowNode;
|
|
20
|
+
export type GraphEdge = FkFlowEdge | AnnotationFlowEdge;
|
|
21
|
+
export interface OverlayState {
|
|
22
|
+
/** Currently selected model (drives connected-node dimming). */
|
|
23
|
+
selectedNode: string | null;
|
|
24
|
+
/** Currently selected FK edge id (drives endpoint dimming). */
|
|
25
|
+
selectedEdge: string | null;
|
|
26
|
+
/** Raw search query; blank means no search dimming. */
|
|
27
|
+
searchQuery: string;
|
|
28
|
+
/** Column expansion lookup per model. */
|
|
29
|
+
isExpanded: (modelName: string) => boolean;
|
|
30
|
+
/** Stable toggle callback injected into node data. */
|
|
31
|
+
toggleExpansion: (modelName: string) => void;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Apply dimming + expansion overlays to nodes and edges.
|
|
35
|
+
*
|
|
36
|
+
* Returns the input arrays untouched (same reference) when no element changed.
|
|
37
|
+
*/
|
|
38
|
+
export declare function applyNodeOverlays(nodes: GraphNode[], edges: GraphEdge[], state: OverlayState): {
|
|
39
|
+
nodes: GraphNode[];
|
|
40
|
+
edges: GraphEdge[];
|
|
41
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Model node size estimation (matches ModelNode CSS).
|
|
3
|
+
*
|
|
4
|
+
* The width and height a model node will render at, estimated from its
|
|
5
|
+
* content, for code that needs node sizes before React Flow has measured the
|
|
6
|
+
* DOM: edge handle placement in the graph transformer and the extension's ELK
|
|
7
|
+
* auto-layout, which imports these from `@erd-studio/renderer/sizing`.
|
|
8
|
+
*
|
|
9
|
+
* Kept free of elkjs and React Flow runtime imports so it can be loaded on its
|
|
10
|
+
* own.
|
|
11
|
+
*/
|
|
12
|
+
import type { ModelFlowNode, ModelNodeData } from '../types/graph.js';
|
|
13
|
+
/** Default width — used as fallback by edgeDistribution.ts. */
|
|
14
|
+
export declare const NODE_WIDTH = 280;
|
|
15
|
+
/**
|
|
16
|
+
* Estimate a node's rendered height from the number of column rows actually
|
|
17
|
+
* drawn (see `countVisibleColumnRows`) and whether a grain subtitle is shown.
|
|
18
|
+
*/
|
|
19
|
+
export declare function estimateNodeHeight(rowCount: number, hasGrain: boolean): number;
|
|
20
|
+
/**
|
|
21
|
+
* Number of rows ModelNode actually renders in its columns section.
|
|
22
|
+
*
|
|
23
|
+
* Mirrors the rendering rules in `ModelNode.tsx`:
|
|
24
|
+
* - stub models show only PK/NK columns
|
|
25
|
+
* - collapsed nodes show at most `COLLAPSED_COLUMN_LIMIT` columns plus a
|
|
26
|
+
* "...and N more" button row
|
|
27
|
+
* - expanded nodes with more than the limit also render a "Show less" row
|
|
28
|
+
*
|
|
29
|
+
* Using the full column count (the old behaviour) over-reserved
|
|
30
|
+
* `(columns - 5) * rowHeight` per node on auto-collapsed domains (≥30 models).
|
|
31
|
+
*/
|
|
32
|
+
export declare function countVisibleColumnRows(data: Pick<ModelNodeData, 'columns' | 'isStub' | 'isExpanded'>): number;
|
|
33
|
+
/**
|
|
34
|
+
* Best-known size for a model node: React Flow's measured DOM size when
|
|
35
|
+
* available, otherwise an estimate from the rows that will be rendered.
|
|
36
|
+
*/
|
|
37
|
+
export declare function resolveNodeDimensions(node: Pick<ModelFlowNode, 'data' | 'measured'>): {
|
|
38
|
+
width: number;
|
|
39
|
+
height: number;
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* Estimate the pixel width a node needs to display its content without
|
|
43
|
+
* truncation. Examines the header and each column row, returning the
|
|
44
|
+
* widest value clamped to [MIN_NODE_WIDTH, MAX_NODE_WIDTH].
|
|
45
|
+
*
|
|
46
|
+
* A safety margin is added before clamping so ELK always reserves
|
|
47
|
+
* slightly more space than the minimum estimate, preventing overlap
|
|
48
|
+
* when font rendering or badge widths differ from the approximation.
|
|
49
|
+
*/
|
|
50
|
+
export declare function estimateNodeWidth(data: Pick<ModelNodeData, 'modelName' | 'columns' | 'provenance'>): number;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stage colour constants.
|
|
3
|
+
*
|
|
4
|
+
* Fixed colour scheme for the two design stages.
|
|
5
|
+
*
|
|
6
|
+
* CSS classes use `var(--stage-*)` custom properties defined in theme.css.
|
|
7
|
+
* This module provides raw hex values for contexts that cannot use CSS
|
|
8
|
+
* variables (e.g., React Flow MiniMap `nodeColor` callback).
|
|
9
|
+
*/
|
|
10
|
+
/** Hex border colours keyed by stage name (or 'ghost' for missing nodes). */
|
|
11
|
+
export declare const STAGE_HEX: Record<string, string>;
|
|
12
|
+
/**
|
|
13
|
+
* Return the hex border colour for a node given its stage and ghost state.
|
|
14
|
+
* Used by MiniMap's `nodeColor` callback which needs raw colour values.
|
|
15
|
+
*/
|
|
16
|
+
export declare function stageNodeColor(stage: string, isGhost?: boolean): string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { NODE_WIDTH, estimateNodeHeight, countVisibleColumnRows, resolveNodeDimensions, estimateNodeWidth, } from './lib/nodeSizing.js';
|