@erd-studio/renderer 0.1.4 → 0.4.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/CHANGELOG.md +70 -0
- package/README.md +13 -9
- package/dist/{chunk-DUQ2TTQH.js → chunk-64BVOHOR.js} +4 -3
- package/dist/chunk-7VFJNN4P.js +10461 -0
- package/dist/{chunk-WOTSQZCQ.js → chunk-YLXBIRPE.js} +132 -2
- package/dist/editor.js +51 -11
- package/dist/index.js +16 -5
- package/dist/sizing.js +2 -2
- package/dist/store.js +1 -1
- package/dist/styles.css +901 -292
- package/dist/types/ErdCanvas.d.ts +33 -5
- package/dist/types/canvas/CanvasBackdrop.d.ts +3 -2
- package/dist/types/canvas/EdgeRouting.d.ts +56 -0
- package/dist/types/canvas/useCanvasGraph.d.ts +11 -5
- package/dist/types/canvas/useHoverFocus.d.ts +64 -0
- package/dist/types/components/DetailPanel/AliasEditor.d.ts +16 -2
- package/dist/types/components/DetailPanel/ColumnEditor.d.ts +20 -4
- package/dist/types/components/DetailPanel/DescriptionEditor.d.ts +1 -1
- package/dist/types/components/DetailPanel/DetailPanel.d.ts +31 -2
- package/dist/types/components/DetailPanel/FieldsSection.d.ts +68 -0
- package/dist/types/components/Graph/ColumnTooltip.d.ts +1 -1
- package/dist/types/components/Graph/EdgeFlow.d.ts +55 -0
- package/dist/types/components/Graph/EdgeLabels.d.ts +18 -0
- package/dist/types/components/Graph/FkEdge.d.ts +8 -1
- package/dist/types/components/Graph/ModelNode.d.ts +4 -4
- package/dist/types/components/Legend/Legend.d.ts +35 -4
- package/dist/types/components/StatusBar/StatusBar.d.ts +45 -0
- package/dist/types/components/common/ColumnRowEditor.d.ts +20 -4
- package/dist/types/components/common/KeyBadgeGroup.d.ts +5 -1
- package/dist/types/editor.d.ts +10 -4
- package/dist/types/hooks/useColumnReorder.d.ts +1 -1
- package/dist/types/hooks/usePrefersReducedMotion.d.ts +13 -0
- package/dist/types/index.d.ts +5 -4
- package/dist/types/lib/badgeLabels.d.ts +7 -1
- package/dist/types/lib/canvasContext.d.ts +20 -0
- package/dist/types/lib/canvasFocus.d.ts +66 -0
- package/dist/types/lib/dragHandleSides.d.ts +2 -2
- package/dist/types/lib/edgeFlow.d.ts +84 -0
- package/dist/types/lib/edgeRouting/index.d.ts +146 -0
- package/dist/types/lib/edgeRouting/input.d.ts +35 -0
- package/dist/types/lib/edgeRouting/lineStyle.d.ts +16 -0
- package/dist/types/lib/edgeRouting/render/corners.d.ts +52 -0
- package/dist/types/lib/edgeRouting/render/crossings.d.ts +27 -0
- package/dist/types/lib/edgeRouting/render/curveCrossings.d.ts +35 -0
- package/dist/types/lib/edgeRouting/render/pathRender.d.ts +74 -0
- package/dist/types/lib/edgeRouting/render/tunnels.d.ts +49 -0
- package/dist/types/lib/edgeRouting/routeAll.d.ts +20 -0
- package/dist/types/lib/edgeRouting/routers/orthogonal/geom.d.ts +58 -0
- package/dist/types/lib/edgeRouting/routers/orthogonal/grid.d.ts +77 -0
- package/dist/types/lib/edgeRouting/routers/orthogonal/nudge.d.ts +62 -0
- package/dist/types/lib/edgeRouting/routers/orthogonal/params.d.ts +152 -0
- package/dist/types/lib/edgeRouting/routers/orthogonal/router.d.ts +51 -0
- package/dist/types/lib/edgeRouting/routers/orthogonal/search.d.ts +81 -0
- package/dist/types/lib/edgeRouting/routers/orthogonalAvoid.d.ts +11 -0
- package/dist/types/lib/edgeRouting/types.d.ts +113 -0
- package/dist/types/lib/edgeRouting/worker/client.d.ts +23 -0
- package/dist/types/lib/edgeRouting/worker/entry.d.ts +1 -0
- package/dist/types/lib/edgeRouting/worker/protocol.d.ts +23 -0
- package/dist/types/lib/graphTransformer.d.ts +7 -7
- package/dist/types/lib/hoverIntent.d.ts +44 -0
- package/dist/types/lib/layerCues.d.ts +26 -0
- package/dist/types/lib/modelLabels.d.ts +3 -3
- package/dist/types/lib/nodeOverlays.d.ts +10 -4
- package/dist/types/lib/nodeSizing.d.ts +6 -6
- package/dist/types/lib/pathGeometry.d.ts +45 -0
- package/dist/types/lib/reuseUnchanged.d.ts +28 -0
- package/dist/types/lib/roleOptions.d.ts +13 -0
- package/dist/types/store/editorStore.d.ts +65 -8
- package/dist/types/types/graph.d.ts +38 -14
- package/package.json +3 -2
- package/dist/chunk-IAFK7ZZV.js +0 -5576
- package/dist/types/components/DetailPanel/GrainEditor.d.ts +0 -16
- package/dist/types/components/DetailPanel/MetaEditor.d.ts +0 -21
- package/dist/types/components/DetailPanel/ModelRationale.d.ts +0 -27
- package/dist/types/components/DetailPanel/RationaleField.d.ts +0 -28
- package/dist/types/components/DetailPanel/RoleEditor.d.ts +0 -13
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Route-around router — A* over the routing grid.
|
|
3
|
+
*
|
|
4
|
+
* A state is (grid node, heading): bends cost BEND and a line may never
|
|
5
|
+
* double back on itself, so the search needs to know which way it is going.
|
|
6
|
+
* Many sources and many targets: every allowed anchor slot on every free
|
|
7
|
+
* side of the two cards is a seed / a goal (`Port`), each carrying its own
|
|
8
|
+
* cost (how far the anchor sits off the side's centre, plus the straight leg
|
|
9
|
+
* to the grid). The search stops at the cheapest complete route: a goal
|
|
10
|
+
* reached travelling along its leg towards the card, or turning onto it (one
|
|
11
|
+
* more bend).
|
|
12
|
+
*
|
|
13
|
+
* Costs: length, BEND per bend, CROSS per line routed earlier that this one
|
|
14
|
+
* crosses, SHARE per px of grid edge that earlier lines already use, and
|
|
15
|
+
* ZONE_COST per px run inside another card side's anchor zone. The
|
|
16
|
+
* heuristic is scaled by HEURISTIC_WEIGHT (bounded-suboptimal A*).
|
|
17
|
+
*
|
|
18
|
+
* The heuristic is the classic orthogonal-routing lower bound (as libavoid
|
|
19
|
+
* uses): for each side of the target, the Manhattan distance to its port
|
|
20
|
+
* line plus BEND × the fewest bends that can still arrive on it square-on
|
|
21
|
+
* from the current heading. Without the bend count a Z-shaped route (two
|
|
22
|
+
* bends) leaves every one-bend state between the cards cheaper than the
|
|
23
|
+
* answer, and A* floods the whole rectangle between them.
|
|
24
|
+
*
|
|
25
|
+
* Typed arrays are reused across searches with a stamp, so a search costs
|
|
26
|
+
* only what it touches.
|
|
27
|
+
*/
|
|
28
|
+
import type { Point, Side } from "../../types.js";
|
|
29
|
+
import type { Grid } from "./grid.js";
|
|
30
|
+
import type { Orient } from "./geom.js";
|
|
31
|
+
export interface Port {
|
|
32
|
+
side: Side;
|
|
33
|
+
/** The anchor's coordinate along the side. */
|
|
34
|
+
c: number;
|
|
35
|
+
/** The stub point (PATH_GAP off the card), where the drawn line starts / ends. */
|
|
36
|
+
point: Point;
|
|
37
|
+
/** The grid node at the far end of the straight leg (≥ MIN_LEG from the stub). */
|
|
38
|
+
node: number;
|
|
39
|
+
/** Orientation of the leg. */
|
|
40
|
+
orient: Orient;
|
|
41
|
+
/** Direction of travel along the leg: away from the card for a seed, towards it for a goal. */
|
|
42
|
+
dir: 1 | -1;
|
|
43
|
+
/** Anchor penalty + leg length (+ crossings of lines already routed across the leg). */
|
|
44
|
+
cost: number;
|
|
45
|
+
/** The slot's grid line (row for a horizontal leg, column for a vertical one). */
|
|
46
|
+
lane: number;
|
|
47
|
+
/** Elementary grid edges the leg covers along its lane: [legLo, legHi]. */
|
|
48
|
+
legLo: number;
|
|
49
|
+
legHi: number;
|
|
50
|
+
}
|
|
51
|
+
export interface Found {
|
|
52
|
+
/** Grid nodes from the seed's node to the goal's node. */
|
|
53
|
+
nodes: number[];
|
|
54
|
+
src: Port;
|
|
55
|
+
tgt: Port;
|
|
56
|
+
cost: number;
|
|
57
|
+
}
|
|
58
|
+
export declare class Searcher {
|
|
59
|
+
private cap;
|
|
60
|
+
private gScore;
|
|
61
|
+
private stamp;
|
|
62
|
+
private parent;
|
|
63
|
+
private seedOf;
|
|
64
|
+
private goalStamp;
|
|
65
|
+
private goalOf;
|
|
66
|
+
private id;
|
|
67
|
+
private readonly heap;
|
|
68
|
+
/** Expansions in the last search (for perf tests). */
|
|
69
|
+
lastPops: number;
|
|
70
|
+
/** Target sides for the heuristic: [ux, sgn, up, lo, hi] × up to 4. */
|
|
71
|
+
private readonly targetBuf;
|
|
72
|
+
private zeroBuf;
|
|
73
|
+
private zeros;
|
|
74
|
+
private ensure;
|
|
75
|
+
/**
|
|
76
|
+
* `zones`: keep out of the anchor zones in front of card sides other lines
|
|
77
|
+
* attach to (off for a last-ditch retry).
|
|
78
|
+
*/
|
|
79
|
+
search(grid: Grid, seeds: readonly Port[], goals: readonly Port[], maxPops: number, zones?: boolean, weight?: number, cross?: number): Found | null;
|
|
80
|
+
private build;
|
|
81
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Edge routing — the router: orthogonal routing around every table card, with
|
|
3
|
+
* parallel lines in a shared corridor nudged apart.
|
|
4
|
+
*
|
|
5
|
+
* Every line is routed on a sparse orthogonal grid around the padded cards
|
|
6
|
+
* (A*, length + bends + crossings), may leave or enter whichever side of its
|
|
7
|
+
* card is best, and is then nudged so lines that share a channel or a card
|
|
8
|
+
* side read as separate wires and never cross right at a card. See
|
|
9
|
+
* `orthogonal/router.ts` for the pass and `orthogonal/params.ts` for the knobs.
|
|
10
|
+
*/
|
|
11
|
+
export { createAvoidRouter, type AvoidRouter, type AvoidResult } from './orthogonal/router.js';
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Edge routing — shared types.
|
|
3
|
+
*
|
|
4
|
+
* Every FK line on the canvas is routed in one pass over the whole graph
|
|
5
|
+
* (`routeAll`), so the router can see every table (obstacles) and every other
|
|
6
|
+
* line (crossings, shared channels). An `FkEdge` reads its routed path from
|
|
7
|
+
* the canvas store and falls back to the smooth-step path when there is none.
|
|
8
|
+
*
|
|
9
|
+
* Coordinates are React Flow flow coordinates (the same space as node
|
|
10
|
+
* `positionAbsolute`).
|
|
11
|
+
*/
|
|
12
|
+
export type Side = 'top' | 'right' | 'bottom' | 'left';
|
|
13
|
+
export interface Point {
|
|
14
|
+
x: number;
|
|
15
|
+
y: number;
|
|
16
|
+
}
|
|
17
|
+
/** A node's box on the canvas. Tables and sticky notes are both obstacles. */
|
|
18
|
+
export interface Rect {
|
|
19
|
+
id: string;
|
|
20
|
+
x: number;
|
|
21
|
+
y: number;
|
|
22
|
+
width: number;
|
|
23
|
+
height: number;
|
|
24
|
+
/** 'model' for a table card, 'annotation' for a sticky note. */
|
|
25
|
+
kind: 'model' | 'annotation';
|
|
26
|
+
}
|
|
27
|
+
/** Where a line meets a table: the point on the card's border and which side. */
|
|
28
|
+
export interface Anchor extends Point {
|
|
29
|
+
side: Side;
|
|
30
|
+
}
|
|
31
|
+
/** How far (px) the drawn line stops short of the card, leaving room for the `*` / `1`. */
|
|
32
|
+
export declare const PATH_GAP = 20;
|
|
33
|
+
/** How far (px) from the card border the `*` / `1` label sits. */
|
|
34
|
+
export declare const LABEL_OFFSET = 8;
|
|
35
|
+
/** One FK line to route, as the graph transformer + edge distribution placed it. */
|
|
36
|
+
export interface EdgeSpec {
|
|
37
|
+
id: string;
|
|
38
|
+
source: string;
|
|
39
|
+
target: string;
|
|
40
|
+
/** The current (smooth-step) anchors: handle side + distributed slot. */
|
|
41
|
+
sourceAnchor: Anchor;
|
|
42
|
+
targetAnchor: Anchor;
|
|
43
|
+
isSelfLoop: boolean;
|
|
44
|
+
loopIndex: number;
|
|
45
|
+
/** 'many-to-one' | 'one-to-one' | 'one-to-many' | 'many-to-many'. */
|
|
46
|
+
cardinality: string;
|
|
47
|
+
}
|
|
48
|
+
export interface RoutingInput {
|
|
49
|
+
obstacles: Rect[];
|
|
50
|
+
edges: EdgeSpec[];
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* A router's answer for one line. `points` is an orthogonal-or-not polyline
|
|
54
|
+
* from the source stub to the target stub (each stub is the anchor pushed
|
|
55
|
+
* out by PATH_GAP along its side); the first and last segments should leave
|
|
56
|
+
* / enter perpendicular to the side. A router may move the anchors (choose
|
|
57
|
+
* another side or slot); the `*` / `1` labels follow the anchors.
|
|
58
|
+
*/
|
|
59
|
+
export interface RoutedLine {
|
|
60
|
+
sourceAnchor: Anchor;
|
|
61
|
+
targetAnchor: Anchor;
|
|
62
|
+
points: Point[];
|
|
63
|
+
}
|
|
64
|
+
/** What the canvas draws for one line, after crossings and tunnels are worked out. */
|
|
65
|
+
export interface RoutedEdge extends RoutedLine {
|
|
66
|
+
/** The SVG path. */
|
|
67
|
+
d: string;
|
|
68
|
+
/** Midpoint along the drawn path (role label, swap button, badges). */
|
|
69
|
+
mid: Point;
|
|
70
|
+
/** Draw a background-coloured casing under the line (crossings = 'halo'). */
|
|
71
|
+
halo?: boolean;
|
|
72
|
+
/**
|
|
73
|
+
* crossings = 'halo', on a line that passes under another: `d` with the
|
|
74
|
+
* breaks cut out. FkEdge draws the visible stroke and its casing with it;
|
|
75
|
+
* `d` stays whole for the moving arrows and the hit area.
|
|
76
|
+
*/
|
|
77
|
+
strokeD?: string;
|
|
78
|
+
/**
|
|
79
|
+
* Stretches of the line that pass under a table card (not its own two
|
|
80
|
+
* ends): polylines, drawn above the cards as a faint "x-ray" dashed line.
|
|
81
|
+
*/
|
|
82
|
+
tunnels?: Point[][];
|
|
83
|
+
}
|
|
84
|
+
export type CrossingMode = 'none'
|
|
85
|
+
/** A small semicircle bridge where one line hops over another. */
|
|
86
|
+
| 'hops'
|
|
87
|
+
/** Every line has a canvas-coloured casing, and the line underneath is cut where it passes under another. */
|
|
88
|
+
| 'halo';
|
|
89
|
+
export type UnderTablesMode =
|
|
90
|
+
/** The card hides the line. */
|
|
91
|
+
'hidden'
|
|
92
|
+
/** The hidden stretch is drawn faintly over the card, with portal rings where it enters and leaves. */
|
|
93
|
+
| 'xray';
|
|
94
|
+
export type CornerMode = 'rounded' | 'curved' | 'sharp';
|
|
95
|
+
/** How the router's polylines are drawn: one line style's settings. */
|
|
96
|
+
export interface LineStyleOptions {
|
|
97
|
+
crossings: CrossingMode;
|
|
98
|
+
underTables: UnderTablesMode;
|
|
99
|
+
corners: CornerMode;
|
|
100
|
+
}
|
|
101
|
+
/** What `routeAll` needs of a router (the route-around router is the one the canvas uses). */
|
|
102
|
+
export interface LineRouter {
|
|
103
|
+
route(input: RoutingInput): {
|
|
104
|
+
lines: Map<string, RoutedLine>;
|
|
105
|
+
};
|
|
106
|
+
/** Drop anything kept from earlier passes. */
|
|
107
|
+
reset(): void;
|
|
108
|
+
}
|
|
109
|
+
export interface RouteResult {
|
|
110
|
+
edges: Map<string, RoutedEdge>;
|
|
111
|
+
/** How long the pass took (ms), for the performance guard. */
|
|
112
|
+
millis: number;
|
|
113
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Starts a canvas's line-routing worker, or says it cannot (returns null) so
|
|
3
|
+
* the canvas routes on the main thread with a lower cap instead.
|
|
4
|
+
*
|
|
5
|
+
* The worker's code is inlined at build time as `__ERD_EDGE_ROUTING_WORKER__`
|
|
6
|
+
* (the extension's `esbuild.js` and the package's `build.mjs` both define
|
|
7
|
+
* it). Where it is not defined (unit tests, a consumer bundling the source
|
|
8
|
+
* itself), where `Worker` or blob URLs are missing, or where the page's CSP
|
|
9
|
+
* refuses a blob worker, there is no worker.
|
|
10
|
+
*/
|
|
11
|
+
import type { WorkerRouteRequest, WorkerRouteResult } from './protocol.js';
|
|
12
|
+
export interface RoutingWorkerEvents {
|
|
13
|
+
onResult: (result: WorkerRouteResult) => void;
|
|
14
|
+
/** The worker failed to start or crashed: it is terminated and never answers again. */
|
|
15
|
+
onFailure: (err: unknown) => void;
|
|
16
|
+
}
|
|
17
|
+
export interface RoutingWorkerHandle {
|
|
18
|
+
post: (request: WorkerRouteRequest) => void;
|
|
19
|
+
terminate: () => void;
|
|
20
|
+
}
|
|
21
|
+
/** Starts a worker for one canvas. */
|
|
22
|
+
export type RoutingWorkerFactory = (events: RoutingWorkerEvents) => RoutingWorkerHandle | null;
|
|
23
|
+
export declare const createRoutingWorker: RoutingWorkerFactory;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Messages between a canvas and its line-routing worker (`worker/entry.ts`).
|
|
3
|
+
* Everything here is structured-clone safe: plain objects, arrays and Maps.
|
|
4
|
+
*/
|
|
5
|
+
import type { LineStyle } from '../lineStyle.js';
|
|
6
|
+
import type { RoutedEdge, RoutingInput } from '../types.js';
|
|
7
|
+
/** Route `input` in `style`. `seq` comes back on the answer, so a canvas can drop a stale one. */
|
|
8
|
+
export interface WorkerRouteRequest {
|
|
9
|
+
type: 'route';
|
|
10
|
+
seq: number;
|
|
11
|
+
input: RoutingInput;
|
|
12
|
+
style: LineStyle;
|
|
13
|
+
zoom: number;
|
|
14
|
+
}
|
|
15
|
+
/** The worker's answer to one request. */
|
|
16
|
+
export interface WorkerRouteResult {
|
|
17
|
+
seq: number;
|
|
18
|
+
edges: Map<string, RoutedEdge>;
|
|
19
|
+
/** How long the pass took in the worker. */
|
|
20
|
+
millis: number;
|
|
21
|
+
/** A router or renderer error in this pass (the routes then leave those lines out). */
|
|
22
|
+
error?: string;
|
|
23
|
+
}
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Graph transformer — converts a
|
|
2
|
+
* Graph transformer — converts a DisplaySubjectArea into React Flow nodes and edges.
|
|
3
3
|
*
|
|
4
4
|
* This is a pure function with no side effects, making it easy to unit test.
|
|
5
5
|
* It handles:
|
|
6
|
-
* 1. Mapping each
|
|
6
|
+
* 1. Mapping each DisplayTable → TableFlowNode (with position + columns)
|
|
7
7
|
* 2. Mapping each DisplayRelationship → FkFlowEdge (with handle side selection)
|
|
8
8
|
* 3. Selecting optimal handle sides based on relative node positions
|
|
9
9
|
* 4. Injecting discrepancy data and ghost nodes/edges when a comparison report is active
|
|
10
10
|
*/
|
|
11
|
-
import type {
|
|
11
|
+
import type { DisplaySubjectArea } from '@erd-studio/core';
|
|
12
12
|
import type { DiscrepancyReport } from '@erd-studio/core';
|
|
13
|
-
import type {
|
|
13
|
+
import type { TableFlowNode, FkFlowEdge, AnnotationFlowNode, AnnotationFlowEdge } from '../types/graph.js';
|
|
14
14
|
export interface TransformResult {
|
|
15
|
-
nodes: (
|
|
15
|
+
nodes: (TableFlowNode | AnnotationFlowNode)[];
|
|
16
16
|
edges: (FkFlowEdge | AnnotationFlowEdge)[];
|
|
17
17
|
}
|
|
18
18
|
/** Width/height of a rendered node, in canvas pixels. */
|
|
@@ -55,11 +55,11 @@ export declare function pickHandleSides(source: NodeRect, target: NodeRect): {
|
|
|
55
55
|
targetSide: Side;
|
|
56
56
|
};
|
|
57
57
|
/**
|
|
58
|
-
* Convert a
|
|
58
|
+
* Convert a DisplaySubjectArea into React Flow nodes and edges.
|
|
59
59
|
*
|
|
60
60
|
* @param domain — the display domain from extension host
|
|
61
61
|
* @param options — optional discrepancy report and ghost positions
|
|
62
62
|
* @returns nodes and edges ready for React Flow's `<ReactFlow>` component
|
|
63
63
|
*/
|
|
64
|
-
export declare function
|
|
64
|
+
export declare function transformSubjectArea(domain: DisplaySubjectArea, options?: TransformOptions): TransformResult;
|
|
65
65
|
export {};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hover intent — decides when a pointer resting on a table becomes a hover
|
|
3
|
+
* focus, so skimming across the canvas does not flash every table it crosses.
|
|
4
|
+
*
|
|
5
|
+
* - From nothing to a table: wait `HOVER_ENTER_DELAY_MS`.
|
|
6
|
+
* - From table A to table B: wait `HOVER_SWITCH_DELAY_MS`, then commit B
|
|
7
|
+
* directly (never null in between, so nothing un-dims and re-dims).
|
|
8
|
+
* - Leaving: wait `HOVER_LEAVE_GRACE_MS` before clearing; entering again,
|
|
9
|
+
* or `hold()` (the pointer is on one of the focus's bright lines), cancels
|
|
10
|
+
* the clear.
|
|
11
|
+
*
|
|
12
|
+
* `cancelEnter()` drops only a pending enter; a pending clear still runs, so
|
|
13
|
+
* a pointer that has left a table never leaves its focus behind.
|
|
14
|
+
*
|
|
15
|
+
* An enter is ignored while a mouse button is held (`buttons !== 0`), while
|
|
16
|
+
* `isSuppressed()` says so (it is asked again when the timer fires), or when
|
|
17
|
+
* the table is already the focus. At most two timers exist at a time.
|
|
18
|
+
*
|
|
19
|
+
* Framework-free; timers are injected so tests can use fake ones.
|
|
20
|
+
*/
|
|
21
|
+
export declare const HOVER_ENTER_DELAY_MS = 120;
|
|
22
|
+
export declare const HOVER_SWITCH_DELAY_MS = 60;
|
|
23
|
+
export declare const HOVER_LEAVE_GRACE_MS = 150;
|
|
24
|
+
type TimerId = ReturnType<typeof setTimeout>;
|
|
25
|
+
export interface HoverIntentOptions {
|
|
26
|
+
/** Make `id` the focus, or clear it with null. */
|
|
27
|
+
commit: (id: string | null) => void;
|
|
28
|
+
/** The id currently in focus (read at event time). */
|
|
29
|
+
current: () => string | null;
|
|
30
|
+
/** True while hover must not start a focus (drags, menus, pans). */
|
|
31
|
+
isSuppressed: () => boolean;
|
|
32
|
+
setTimeout?: (fn: () => void, ms: number) => TimerId;
|
|
33
|
+
clearTimeout?: (id: TimerId) => void;
|
|
34
|
+
}
|
|
35
|
+
export interface HoverIntent {
|
|
36
|
+
enter(id: string, buttons: number): void;
|
|
37
|
+
leave(): void;
|
|
38
|
+
hold(): void;
|
|
39
|
+
cancelEnter(): void;
|
|
40
|
+
cancelPending(): void;
|
|
41
|
+
dispose(): void;
|
|
42
|
+
}
|
|
43
|
+
export declare function createHoverIntent(options: HoverIntentOptions): HoverIntent;
|
|
44
|
+
export {};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Layer cues: how the canvas names the layer a table lives in.
|
|
3
|
+
*
|
|
4
|
+
* A table's header badge has one meaning, its layer, and the layer is named
|
|
5
|
+
* the same way wherever the canvas names one (the node badge, the toolbar
|
|
6
|
+
* chip, the status-bar key): the layer's abbreviation, then its label.
|
|
7
|
+
*
|
|
8
|
+
* A project with a single layer has nothing to tell apart, so the per-table
|
|
9
|
+
* badge, its colour strip, the status-bar key and the toolbar chip are left
|
|
10
|
+
* off there ({@link isSingleLayerProject}).
|
|
11
|
+
*/
|
|
12
|
+
import type { DisplaySubjectArea, LayerConfig } from '@erd-studio/core';
|
|
13
|
+
/** Abbreviations for the default layers, for when a layer has no config. */
|
|
14
|
+
export declare const LAYER_BADGE_FALLBACK: Readonly<Record<string, string>>;
|
|
15
|
+
/**
|
|
16
|
+
* A layer's badge text: its configured abbreviation, else the default
|
|
17
|
+
* layers' abbreviation, else the first three letters of its id.
|
|
18
|
+
*/
|
|
19
|
+
export declare function layerAbbreviation(id: string, config?: Pick<LayerConfig, 'abbreviation'>): string;
|
|
20
|
+
/**
|
|
21
|
+
* Whether the project has one layer at most, with every table on the diagram
|
|
22
|
+
* in the diagram's layer: the layer badge, strip, key and chip are then left
|
|
23
|
+
* off. Needs `layerConfigs`: a domain from an older core (or a host that
|
|
24
|
+
* sends no layer list) keeps every layer cue, as it did before.
|
|
25
|
+
*/
|
|
26
|
+
export declare function isSingleLayerProject(domain: Pick<DisplaySubjectArea, 'layer' | 'layerConfigs' | 'models'>): boolean;
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* same are told apart by schema (`silver.date`, `gold.date`).
|
|
9
9
|
*/
|
|
10
10
|
/** The fields a label is computed from. */
|
|
11
|
-
export interface
|
|
11
|
+
export interface LabelledTable {
|
|
12
12
|
name: string;
|
|
13
13
|
schema?: string;
|
|
14
14
|
alias?: string;
|
|
@@ -18,9 +18,9 @@ export interface LabelledModel {
|
|
|
18
18
|
* name. A model absent from the map is labelled with its name, exactly as
|
|
19
19
|
* before aliases existed.
|
|
20
20
|
*/
|
|
21
|
-
export declare function
|
|
21
|
+
export declare function computeTableLabels(models: readonly LabelledTable[]): Map<string, string>;
|
|
22
22
|
/** Whether a search query (already lower-cased) matches a model by name or label. */
|
|
23
|
-
export declare function
|
|
23
|
+
export declare function matchesTableSearch(data: {
|
|
24
24
|
modelName: string;
|
|
25
25
|
label?: string;
|
|
26
26
|
}, query: string): boolean;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Node/edge overlays — ephemeral per-node presentation state layered on top
|
|
3
|
-
* of the graph produced by `
|
|
3
|
+
* of the graph produced by `transformSubjectArea`:
|
|
4
4
|
*
|
|
5
5
|
* - `dimmed`: search-miss or not-connected-to-selection dimming (F402)
|
|
6
6
|
* - `isExpanded` / `onToggleExpansion`: column expansion (F405)
|
|
@@ -8,15 +8,21 @@
|
|
|
8
8
|
* `applyNodeOverlays` is a pure function that preserves object identity for
|
|
9
9
|
* every node/edge whose overlay values did not change, so React Flow's
|
|
10
10
|
* `adoptUserNodes` short-circuits on unchanged nodes and memoised
|
|
11
|
-
* `
|
|
11
|
+
* `TableNode` / `FkEdge` components skip re-rendering. When nothing changed
|
|
12
12
|
* at all, the *same arrays* are returned so callers can skip `setNodes`.
|
|
13
13
|
*
|
|
14
14
|
* This lets selection / search / expansion changes avoid re-running the full
|
|
15
15
|
* domain transform (which would also drop `measured` and force React Flow to
|
|
16
16
|
* re-measure every node).
|
|
17
|
+
*
|
|
18
|
+
* What counts as "connected" is `computeCanvasFocus` (`canvasFocus.ts`), the
|
|
19
|
+
* same rule the hover preview uses. Hover itself never comes through here:
|
|
20
|
+
* every `setEdges` re-renders every `FkEdge`, which is fine once per click but
|
|
21
|
+
* not on every pointer move, so hover dims with a root CSS class instead
|
|
22
|
+
* (`useHoverFocus`).
|
|
17
23
|
*/
|
|
18
|
-
import type {
|
|
19
|
-
export type GraphNode =
|
|
24
|
+
import type { TableFlowNode, FkFlowEdge, AnnotationFlowNode, AnnotationFlowEdge } from '../types/graph.js';
|
|
25
|
+
export type GraphNode = TableFlowNode | AnnotationFlowNode;
|
|
20
26
|
export type GraphEdge = FkFlowEdge | AnnotationFlowEdge;
|
|
21
27
|
export interface OverlayState {
|
|
22
28
|
/** Currently selected model (drives connected-node dimming). */
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Model node size estimation (matches
|
|
2
|
+
* Model node size estimation (matches TableNode CSS).
|
|
3
3
|
*
|
|
4
4
|
* The width and height a model node will render at, estimated from its
|
|
5
5
|
* content, for code that needs node sizes before React Flow has measured the
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* Kept free of elkjs and React Flow runtime imports so it can be loaded on its
|
|
10
10
|
* own.
|
|
11
11
|
*/
|
|
12
|
-
import type {
|
|
12
|
+
import type { TableFlowNode, TableNodeData } from '../types/graph.js';
|
|
13
13
|
/** Default width — used as fallback by edgeDistribution.ts. */
|
|
14
14
|
export declare const NODE_WIDTH = 280;
|
|
15
15
|
/**
|
|
@@ -18,7 +18,7 @@ export declare const NODE_WIDTH = 280;
|
|
|
18
18
|
*/
|
|
19
19
|
export declare function estimateNodeHeight(rowCount: number, hasGrain: boolean): number;
|
|
20
20
|
/**
|
|
21
|
-
* Number of rows
|
|
21
|
+
* Number of rows TableNode actually renders in its columns section.
|
|
22
22
|
*
|
|
23
23
|
* Mirrors the rendering rules in `ModelNode.tsx`:
|
|
24
24
|
* - stub models show only PK/NK columns
|
|
@@ -29,12 +29,12 @@ export declare function estimateNodeHeight(rowCount: number, hasGrain: boolean):
|
|
|
29
29
|
* Using the full column count (the old behaviour) over-reserved
|
|
30
30
|
* `(columns - 5) * rowHeight` per node on auto-collapsed domains (≥30 models).
|
|
31
31
|
*/
|
|
32
|
-
export declare function countVisibleColumnRows(data: Pick<
|
|
32
|
+
export declare function countVisibleColumnRows(data: Pick<TableNodeData, 'columns' | 'isStub' | 'isExpanded'>): number;
|
|
33
33
|
/**
|
|
34
34
|
* Best-known size for a model node: React Flow's measured DOM size when
|
|
35
35
|
* available, otherwise an estimate from the rows that will be rendered.
|
|
36
36
|
*/
|
|
37
|
-
export declare function resolveNodeDimensions(node: Pick<
|
|
37
|
+
export declare function resolveNodeDimensions(node: Pick<TableFlowNode, 'data' | 'measured'>): {
|
|
38
38
|
width: number;
|
|
39
39
|
height: number;
|
|
40
40
|
};
|
|
@@ -47,4 +47,4 @@ export declare function resolveNodeDimensions(node: Pick<ModelFlowNode, 'data' |
|
|
|
47
47
|
* slightly more space than the minimum estimate, preventing overlap
|
|
48
48
|
* when font rendering or badge widths differ from the approximation.
|
|
49
49
|
*/
|
|
50
|
-
export declare function estimateNodeWidth(data: Pick<
|
|
50
|
+
export declare function estimateNodeWidth(data: Pick<TableNodeData, 'modelName' | 'label' | 'columns' | 'provenance'> & Partial<Pick<TableNodeData, 'tableLayer' | 'shownSchema' | 'alias' | 'singleLayer'>>): number;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Path geometry for the relationship flow glyphs: how long a drawn edge path
|
|
3
|
+
* is, where its straight runs are, and the point and heading at a fraction
|
|
4
|
+
* of its length.
|
|
5
|
+
*
|
|
6
|
+
* Reads the absolute commands the canvas draws: `M`, `L`, `H`, `V` (xyflow's
|
|
7
|
+
* smooth-step path and the Legend samples), `Q` (smooth-step bends) and `C`
|
|
8
|
+
* (self-loops). Curves are flattened into a few chords, which is accurate to
|
|
9
|
+
* well under a pixel for the radii used here. Anything else ends the parse;
|
|
10
|
+
* what was read so far is kept.
|
|
11
|
+
*
|
|
12
|
+
* Pure: no React, no DOM (`SVGPathElement.getTotalLength` would need a
|
|
13
|
+
* mounted element and a layout).
|
|
14
|
+
*/
|
|
15
|
+
export interface PathPoint {
|
|
16
|
+
x: number;
|
|
17
|
+
y: number;
|
|
18
|
+
}
|
|
19
|
+
export interface PathPose extends PathPoint {
|
|
20
|
+
/** Heading in degrees, along increasing path length (SVG `rotate="auto"`). */
|
|
21
|
+
angle: number;
|
|
22
|
+
}
|
|
23
|
+
export interface PathGeometry {
|
|
24
|
+
/** The flattened path. */
|
|
25
|
+
readonly points: readonly PathPoint[];
|
|
26
|
+
/** Cumulative length at each point (`cumulative[0] === 0`). */
|
|
27
|
+
readonly cumulative: readonly number[];
|
|
28
|
+
readonly length: number;
|
|
29
|
+
/** Straight runs (`L` / `H` / `V`) as fractions of the length, in path order. */
|
|
30
|
+
readonly straights: readonly {
|
|
31
|
+
start: number;
|
|
32
|
+
end: number;
|
|
33
|
+
}[];
|
|
34
|
+
}
|
|
35
|
+
/** Flatten and measure a path. An unreadable path has length 0. */
|
|
36
|
+
export declare function measurePath(d: string): PathGeometry;
|
|
37
|
+
/** The point and heading at `fraction` (0..1) of the path's length. */
|
|
38
|
+
export declare function poseAt(geom: PathGeometry, fraction: number): PathPose;
|
|
39
|
+
/**
|
|
40
|
+
* Where two still glyphs sit so that neither lands on a bend: the middle of
|
|
41
|
+
* the first and of the last usable straight run, or a third and two thirds
|
|
42
|
+
* of the only one. Null when the path has no usable straight run (a
|
|
43
|
+
* self-loop's curve): the planner's defaults then apply.
|
|
44
|
+
*/
|
|
45
|
+
export declare function stillPositions(geom: PathGeometry): readonly [number, number] | null;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structural sharing for a rebuilt graph: every rebuilt node or edge that is
|
|
3
|
+
* structurally equal to the one already in the store is replaced by that
|
|
4
|
+
* same object.
|
|
5
|
+
*
|
|
6
|
+
* The canvas rebuilds every node and edge whenever the domain changes —
|
|
7
|
+
* including the optimistic update after a drag, where only the moved
|
|
8
|
+
* tables' positions (and the sides of their edges) differ. React Flow reuses
|
|
9
|
+
* its internal node for a user node that is the same object as last time,
|
|
10
|
+
* and memoised node and edge components skip rendering when their props keep
|
|
11
|
+
* their identity, so handing back the old objects for everything that did
|
|
12
|
+
* not change turns a 500-table re-render into a handful.
|
|
13
|
+
*
|
|
14
|
+
* Correct by construction: the result is equal, field for field, to what the
|
|
15
|
+
* rebuild produced; only identities change. Transient React Flow fields that
|
|
16
|
+
* the rebuild never sets (`dragging`, `resizing`) are ignored, and an absent
|
|
17
|
+
* `selected` equals `false`.
|
|
18
|
+
*/
|
|
19
|
+
/** Structural equality of plain data: functions, Maps, Sets and class instances compare by identity. */
|
|
20
|
+
export declare function sameData(a: unknown, b: unknown): boolean;
|
|
21
|
+
/**
|
|
22
|
+
* `next` with each element replaced by the element of `prev` with the same
|
|
23
|
+
* id when the two are structurally equal. Returns `prev` itself when every
|
|
24
|
+
* element was reused in the same order (nothing to set at all).
|
|
25
|
+
*/
|
|
26
|
+
export declare function reuseUnchanged<T extends {
|
|
27
|
+
id: string;
|
|
28
|
+
}>(prev: readonly T[], next: T[]): T[];
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The words the canvas shows for each table role. The stored keys
|
|
3
|
+
* (`gold-fact`, …) never change; only the labels are for people. The order is
|
|
4
|
+
* core's `TABLE_ROLE_VALUES` (the role field's choices).
|
|
5
|
+
*/
|
|
6
|
+
import type { TableRole } from '@erd-studio/core';
|
|
7
|
+
/** Human-readable labels for each role (the stored key stays as it is). */
|
|
8
|
+
export declare const ROLE_OPTIONS: ReadonlyArray<{
|
|
9
|
+
value: TableRole;
|
|
10
|
+
label: string;
|
|
11
|
+
}>;
|
|
12
|
+
/** The label for a role key, or the key itself for a role this renderer does not know. */
|
|
13
|
+
export declare function roleLabel(role: string): string;
|