@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.
Files changed (76) hide show
  1. package/CHANGELOG.md +70 -0
  2. package/README.md +13 -9
  3. package/dist/{chunk-DUQ2TTQH.js → chunk-64BVOHOR.js} +4 -3
  4. package/dist/chunk-7VFJNN4P.js +10461 -0
  5. package/dist/{chunk-WOTSQZCQ.js → chunk-YLXBIRPE.js} +132 -2
  6. package/dist/editor.js +51 -11
  7. package/dist/index.js +16 -5
  8. package/dist/sizing.js +2 -2
  9. package/dist/store.js +1 -1
  10. package/dist/styles.css +901 -292
  11. package/dist/types/ErdCanvas.d.ts +33 -5
  12. package/dist/types/canvas/CanvasBackdrop.d.ts +3 -2
  13. package/dist/types/canvas/EdgeRouting.d.ts +56 -0
  14. package/dist/types/canvas/useCanvasGraph.d.ts +11 -5
  15. package/dist/types/canvas/useHoverFocus.d.ts +64 -0
  16. package/dist/types/components/DetailPanel/AliasEditor.d.ts +16 -2
  17. package/dist/types/components/DetailPanel/ColumnEditor.d.ts +20 -4
  18. package/dist/types/components/DetailPanel/DescriptionEditor.d.ts +1 -1
  19. package/dist/types/components/DetailPanel/DetailPanel.d.ts +31 -2
  20. package/dist/types/components/DetailPanel/FieldsSection.d.ts +68 -0
  21. package/dist/types/components/Graph/ColumnTooltip.d.ts +1 -1
  22. package/dist/types/components/Graph/EdgeFlow.d.ts +55 -0
  23. package/dist/types/components/Graph/EdgeLabels.d.ts +18 -0
  24. package/dist/types/components/Graph/FkEdge.d.ts +8 -1
  25. package/dist/types/components/Graph/ModelNode.d.ts +4 -4
  26. package/dist/types/components/Legend/Legend.d.ts +35 -4
  27. package/dist/types/components/StatusBar/StatusBar.d.ts +45 -0
  28. package/dist/types/components/common/ColumnRowEditor.d.ts +20 -4
  29. package/dist/types/components/common/KeyBadgeGroup.d.ts +5 -1
  30. package/dist/types/editor.d.ts +10 -4
  31. package/dist/types/hooks/useColumnReorder.d.ts +1 -1
  32. package/dist/types/hooks/usePrefersReducedMotion.d.ts +13 -0
  33. package/dist/types/index.d.ts +5 -4
  34. package/dist/types/lib/badgeLabels.d.ts +7 -1
  35. package/dist/types/lib/canvasContext.d.ts +20 -0
  36. package/dist/types/lib/canvasFocus.d.ts +66 -0
  37. package/dist/types/lib/dragHandleSides.d.ts +2 -2
  38. package/dist/types/lib/edgeFlow.d.ts +84 -0
  39. package/dist/types/lib/edgeRouting/index.d.ts +146 -0
  40. package/dist/types/lib/edgeRouting/input.d.ts +35 -0
  41. package/dist/types/lib/edgeRouting/lineStyle.d.ts +16 -0
  42. package/dist/types/lib/edgeRouting/render/corners.d.ts +52 -0
  43. package/dist/types/lib/edgeRouting/render/crossings.d.ts +27 -0
  44. package/dist/types/lib/edgeRouting/render/curveCrossings.d.ts +35 -0
  45. package/dist/types/lib/edgeRouting/render/pathRender.d.ts +74 -0
  46. package/dist/types/lib/edgeRouting/render/tunnels.d.ts +49 -0
  47. package/dist/types/lib/edgeRouting/routeAll.d.ts +20 -0
  48. package/dist/types/lib/edgeRouting/routers/orthogonal/geom.d.ts +58 -0
  49. package/dist/types/lib/edgeRouting/routers/orthogonal/grid.d.ts +77 -0
  50. package/dist/types/lib/edgeRouting/routers/orthogonal/nudge.d.ts +62 -0
  51. package/dist/types/lib/edgeRouting/routers/orthogonal/params.d.ts +152 -0
  52. package/dist/types/lib/edgeRouting/routers/orthogonal/router.d.ts +51 -0
  53. package/dist/types/lib/edgeRouting/routers/orthogonal/search.d.ts +81 -0
  54. package/dist/types/lib/edgeRouting/routers/orthogonalAvoid.d.ts +11 -0
  55. package/dist/types/lib/edgeRouting/types.d.ts +113 -0
  56. package/dist/types/lib/edgeRouting/worker/client.d.ts +23 -0
  57. package/dist/types/lib/edgeRouting/worker/entry.d.ts +1 -0
  58. package/dist/types/lib/edgeRouting/worker/protocol.d.ts +23 -0
  59. package/dist/types/lib/graphTransformer.d.ts +7 -7
  60. package/dist/types/lib/hoverIntent.d.ts +44 -0
  61. package/dist/types/lib/layerCues.d.ts +26 -0
  62. package/dist/types/lib/modelLabels.d.ts +3 -3
  63. package/dist/types/lib/nodeOverlays.d.ts +10 -4
  64. package/dist/types/lib/nodeSizing.d.ts +6 -6
  65. package/dist/types/lib/pathGeometry.d.ts +45 -0
  66. package/dist/types/lib/reuseUnchanged.d.ts +28 -0
  67. package/dist/types/lib/roleOptions.d.ts +13 -0
  68. package/dist/types/store/editorStore.d.ts +65 -8
  69. package/dist/types/types/graph.d.ts +38 -14
  70. package/package.json +3 -2
  71. package/dist/chunk-IAFK7ZZV.js +0 -5576
  72. package/dist/types/components/DetailPanel/GrainEditor.d.ts +0 -16
  73. package/dist/types/components/DetailPanel/MetaEditor.d.ts +0 -21
  74. package/dist/types/components/DetailPanel/ModelRationale.d.ts +0 -27
  75. package/dist/types/components/DetailPanel/RationaleField.d.ts +0 -28
  76. package/dist/types/components/DetailPanel/RoleEditor.d.ts +0 -13
@@ -1,9 +1,13 @@
1
1
  /**
2
2
  * ErdCanvas — the diagram as a self-contained, read-only React component.
3
3
  *
4
- * Renders a `DisplayDomain` the way the VS Code extension's canvas shows a
4
+ * Renders a `DisplaySubjectArea` the way the VS Code extension's canvas shows a
5
5
  * read-only domain: model nodes, FK edges, annotations, the minimap, the
6
- * detail panel a click on a model opens, and the legend. There are no editing
6
+ * detail panel a click on a model opens, the legend (only the entries the
7
+ * stage shown can draw) and, beside its button, the table and relationship
8
+ * counts. Closing the detail panel keeps the table selected, its
9
+ * relationships lit; a click on the empty canvas, or Escape, clears the
10
+ * selection. There are no editing
7
11
  * affordances (the canvas runs in viewer mode) and nothing is ever sent
8
12
  * anywhere.
9
13
  *
@@ -18,14 +22,23 @@
18
22
  * `onLayoutModifiedChange` reports whether the layout differs from the
19
23
  * domain's, and `resetLayout()` (through a ref) puts everything back.
20
24
  *
25
+ * Resting the pointer on a table previews what it connects to (the rest of
26
+ * the diagram fades) and the relationship lines of the hovered or clicked
27
+ * table carry moving direction arrows (`useHoverFocus`). Both are local,
28
+ * ephemeral presentation: nothing is posted and no prop is needed.
29
+ *
30
+ * Relationship lines are routed around the tables in one of two styles,
31
+ * `lineStyle` `curved` (the default) or `hops`; the reader cannot change it.
32
+ *
21
33
  * Derived from the extension webview's `EditorCanvas` (`webview/App.tsx`),
22
34
  * keeping its React Flow configuration.
23
35
  */
24
36
  import { type CSSProperties } from 'react';
25
- import type { DisplayDomain } from '@erd-studio/core';
37
+ import type { DisplaySubjectArea } from '@erd-studio/core';
38
+ import { type LineStyle } from './lib/edgeRouting/lineStyle.js';
26
39
  export interface ErdCanvasProps {
27
40
  /** The domain to render. It is shown read-only whatever its `readOnly` flag says. */
28
- domain: DisplayDomain;
41
+ domain: DisplaySubjectArea;
29
42
  /** Let readers drag model and annotation nodes. Moves are never saved. Default false. */
30
43
  nodesDraggable?: boolean;
31
44
  /**
@@ -36,6 +49,21 @@ export interface ErdCanvasProps {
36
49
  onLayoutModifiedChange?: (modified: boolean) => void;
37
50
  /** Called once, when the first nodes have been measured and fitted into view. */
38
51
  onReady?: () => void;
52
+ /**
53
+ * Draw the diagram of a project with no dbt project: no stage colours on
54
+ * tables, lines or the selection ring (the layer badge carries the colour)
55
+ * and no Physical, ghost or comparison entries in the legend. Default
56
+ * false: the stage colours, as before.
57
+ */
58
+ logicalOnly?: boolean;
59
+ /**
60
+ * How relationship lines are drawn. Both route the lines around the tables:
61
+ * `curved` (the default) with soft curves, a halo where two lines cross and
62
+ * a faint dashed "x-ray" where a line has to pass under a table; `hops`
63
+ * with square corners and a small hop where two lines cross. Anything else
64
+ * reads as `curved`.
65
+ */
66
+ lineStyle?: LineStyle;
39
67
  className?: string;
40
68
  style?: CSSProperties;
41
69
  }
@@ -50,5 +78,5 @@ export interface ErdCanvasHandle {
50
78
  * model's columns, each column's data type). Objects with nothing missing are
51
79
  * reused as they are.
52
80
  */
53
- export declare function toRenderableDomain(domain: DisplayDomain): DisplayDomain;
81
+ export declare function toRenderableSubjectArea(domain: DisplaySubjectArea): DisplaySubjectArea;
54
82
  export declare const ErdCanvas: import("react").ForwardRefExoticComponent<ErdCanvasProps & import("react").RefAttributes<ErdCanvasHandle>>;
@@ -1,6 +1,7 @@
1
1
  /**
2
- * The canvas backdrop: the dotted background and the minimap. Render it as a
3
- * child of `<ReactFlow>`.
2
+ * The canvas backdrop: the dotted background, the minimap and the line
3
+ * routing (`EdgeRouter`, the x-ray `EdgeOverlay`, `LayerLineColour`). Render
4
+ * it as a child of `<ReactFlow>`.
4
5
  */
5
6
  /** Minimap fill for annotation nodes, keyed by annotation colour. */
6
7
  export declare const ANNOTATION_MINIMAP_COLORS: Record<string, string>;
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Line routing on the canvas. Render all three as children of `<ReactFlow>`
3
+ * (CanvasBackdrop does), in the editor and in the viewer alike: none of them
4
+ * posts to a host or shows an editing affordance.
5
+ *
6
+ * - `EdgeRouter` routes every FK line in one pass whenever a card moves, is
7
+ * measured, or the line style changes, and puts the result in the canvas
8
+ * store (`edgeRoutes`). `FkEdge` reads its own route from there. Each
9
+ * canvas has its own routing engine (router memory and performance guard).
10
+ * - `EdgeOverlay` draws what has to sit above the cards: the x-ray stretches
11
+ * of a line that passes under a table (the `curved` style).
12
+ * - `LayerLineColour` hands the diagram's layer colour to the CSS, which a
13
+ * logical-only canvas lights the focused lines with (EdgeRouting.css).
14
+ */
15
+ import './EdgeRouting.css';
16
+ /**
17
+ * The diagram's own layer colour, as `--diagram-layer-color` on the React
18
+ * Flow root, so a logical-only canvas can light its focused lines with it
19
+ * (EdgeRouting.css). Cleared when the diagram has no layer colour: the lines
20
+ * then stay neutral grey.
21
+ */
22
+ export declare function LayerLineColour(): null;
23
+ /**
24
+ * Routes every FK line once per frame in which a card moved, was measured or
25
+ * the line style changed (and, for hops, when the zoom moves a quarter of a
26
+ * doubling: hops are sized for it). The engine is this canvas's own, and
27
+ * decides whether and where to route (see `createEdgeRoutingEngine`): a small
28
+ * diagram routes here, on the main thread; a larger one in the canvas's
29
+ * worker, whose answer arrives later through `onUpdate`; a slow diagram holds
30
+ * its routes while a card is dragged and routes again when the drag ends
31
+ * (`dragKey` changes back to empty).
32
+ */
33
+ export declare function EdgeRouter(): null;
34
+ /**
35
+ * The x-ray: every routed line that passes under a card is drawn again ABOVE
36
+ * the cards, as a fine dashed ghost in the line's own colour, with a portal
37
+ * ring where it dives under a border and where it comes back out. Only the
38
+ * `curved` line style computes tunnels; with `hops` this draws nothing.
39
+ *
40
+ * The ghost is the line's own `d` (so it sits exactly on the hidden line
41
+ * whatever the corner or crossing style) under two clips: the card boxes, and
42
+ * the boxes of the line's tunnels (`windows`). The second clip keeps out what
43
+ * `computeTunnels` deliberately dropped: a line's hook inside its own two
44
+ * nearly touching cards, or a rounded corner grazing a card.
45
+ *
46
+ * A line whose end stub sits inside another card (the two cards closer than
47
+ * 20 px) would leave its ghost dangling mid-card; a short `lead` carries it on
48
+ * to the anchor on its own card, so the reader sees where it plugs in.
49
+ *
50
+ * Calm by default, loud on demand: at rest the ghost is faint and the portals
51
+ * carry the message ("this line goes under here"); the lines of the hovered
52
+ * or selected table, or the selected line (`flowFocus` / `hoverFocus`), get a
53
+ * bright ghost; lines faded by a selection or by the hover preview fade their
54
+ * ghost almost away. Pointer-events none, no host posts: viewer-safe.
55
+ */
56
+ export declare function EdgeOverlay(): import("react/jsx-runtime").JSX.Element | null;
@@ -4,17 +4,23 @@
4
4
  *
5
5
  * - The transform effect rebuilds every node and edge when the domain (or
6
6
  * the discrepancy overlay) changes, preserving measured sizes, the
7
- * current selection and valid edge selections.
7
+ * current selection and valid edge selections, and keeps the existing
8
+ * object for every node and edge that came out the same
9
+ * (`reuseUnchanged`), so a drag re-renders only what moved.
8
10
  * - The overlay effect updates only the `dimmed` / `isExpanded` flags when
9
11
  * selection, search or column expansion changes.
10
12
  * - The handlers open the detail panel on a model click, select annotations,
11
13
  * highlight an FK edge's columns and clear selection on a pane click.
12
14
  *
15
+ * Hover preview and the moving relationship arrows are `useHoverFocus`, a
16
+ * separate hook: it needs React Flow's store, and this one must keep working
17
+ * without a `ReactFlowProvider`. Hover never comes through the overlay pass.
18
+ *
13
19
  * Must run inside a `CanvasStoreProvider`.
14
20
  */
15
21
  import type { OnSelectionChangeFunc } from '@xyflow/react';
16
22
  import type { DiscrepancyReport } from '@erd-studio/core';
17
- import type { ModelFlowNode, FkFlowEdge, AnnotationFlowNode, AnnotationFlowEdge } from '../types/graph.js';
23
+ import type { TableFlowNode, FkFlowEdge, AnnotationFlowNode, AnnotationFlowEdge } from '../types/graph.js';
18
24
  export interface UseCanvasGraphOptions {
19
25
  /** Whether the cross-stage discrepancy overlay is shown. */
20
26
  discrepancyVisible?: boolean;
@@ -24,12 +30,12 @@ export interface UseCanvasGraphOptions {
24
30
  * Called with the rebuilt nodes and edges each time the transform effect
25
31
  * runs, just before they are written to the store.
26
32
  */
27
- onTransformed?: (nodes: (ModelFlowNode | AnnotationFlowNode)[], edges: (FkFlowEdge | AnnotationFlowEdge)[]) => void;
33
+ onTransformed?: (nodes: (TableFlowNode | AnnotationFlowNode)[], edges: (FkFlowEdge | AnnotationFlowEdge)[]) => void;
28
34
  }
29
35
  export interface UseCanvasGraphResult {
30
- nodes: (ModelFlowNode | AnnotationFlowNode)[];
36
+ nodes: (TableFlowNode | AnnotationFlowNode)[];
31
37
  edges: (FkFlowEdge | AnnotationFlowEdge)[];
32
- onNodeClick: (event: React.MouseEvent, node: ModelFlowNode | AnnotationFlowNode) => void;
38
+ onNodeClick: (event: React.MouseEvent, node: TableFlowNode | AnnotationFlowNode) => void;
33
39
  onEdgeClick: (event: React.MouseEvent, edge: FkFlowEdge | AnnotationFlowEdge) => void;
34
40
  onPaneClick: () => void;
35
41
  onSelectionChange: OnSelectionChangeFunc;
@@ -0,0 +1,64 @@
1
+ /**
2
+ * useHoverFocus — hover a table to preview what it connects to, and keep the
3
+ * moving relationship arrows on the right lines.
4
+ *
5
+ * Rest the pointer on a table and, after a short hover-intent delay, it and
6
+ * its related tables stay bright while everything else fades, as a click
7
+ * does. Move away and the diagram, or whatever was clicked, comes back.
8
+ *
9
+ * Precedence:
10
+ * 1. Hover previews: while `hoverFocus` is set, the hovered neighbourhood is
11
+ * bright and everything else is faded, over click and search dimming.
12
+ * 2. Leaving restores: hover never touches `data.dimmed`, so the click and
13
+ * search state reappears exactly as it was.
14
+ * 3. The clicked table stays findable at half opacity while a hover fades it.
15
+ * 4. Arrows follow `flowFocus = hoverFocus ?? focus(clicked table | line)`.
16
+ * A click selection's arrows settle (stand still, still pointing the
17
+ * way) after `SELECTION_FLOW_SETTLE_MS`, so a table left selected does
18
+ * not keep the canvas animating; a hover's arrows move while it lasts.
19
+ * 5. The hover preview lights only the hovered table's own lines, not the
20
+ * lines between its neighbours (`ownLinesOnly`); a click keeps both.
21
+ *
22
+ * Render path: hover never rewrites the nodes or edges arrays (every
23
+ * `setEdges` re-renders every `FkEdge`). The store holds `CanvasFocus` sets,
24
+ * `flowClassName` on the React Flow root fades everything in CSS, and each
25
+ * table and line reads its own part with an O(1) selector that returns a
26
+ * primitive, so only the tables and lines whose part changed re-render.
27
+ *
28
+ * Ephemeral: `hoverFocus` / `flowFocus` are never persisted, posted to the
29
+ * host or written to a file, and there is no message type. The preview is not
30
+ * an editing affordance, so it works the same in viewer mode and on the
31
+ * read-only Physical tab (any future viewer gate: `viewer ? … : <upstream>`).
32
+ *
33
+ * Suppressed (checked at event time): any mouse button held, a pane drag or
34
+ * box selection, a viewport move (pan, wheel zoom, inertia), a column drag
35
+ * to create a relationship, an annotation link drag, an open context menu, a
36
+ * device without a fine hover pointer. The column and annotation drags and
37
+ * the context menu also clear a hover at once, so a drop target is never
38
+ * faded. A node drag freezes the focus until it stops; grabbing a table other
39
+ * than the hovered one moves the focus to it first, and dropping a table the
40
+ * focus is not on previews it (the pointer is still resting there).
41
+ *
42
+ * Lives apart from `useCanvasGraph` because it needs React Flow's store; both
43
+ * hosts call it inside their `ReactFlowProvider`. `useOnViewportChange` takes
44
+ * React Flow's single `onViewportChangeStart/End` slot: no host uses it.
45
+ */
46
+ import { type NodeMouseHandler, type EdgeMouseHandler, type OnNodeDrag } from '@xyflow/react';
47
+ import type { GraphNode, GraphEdge } from '../lib/nodeOverlays.js';
48
+ export interface HoverFocusHandlers {
49
+ onNodeMouseEnter: NodeMouseHandler<GraphNode>;
50
+ onNodeMouseLeave: NodeMouseHandler<GraphNode>;
51
+ onEdgeMouseEnter: EdgeMouseHandler<GraphEdge>;
52
+ onEdgeMouseLeave: EdgeMouseHandler<GraphEdge>;
53
+ onNodeDragStart: OnNodeDrag<GraphNode>;
54
+ onNodeDragStop: OnNodeDrag<GraphNode>;
55
+ }
56
+ export interface UseHoverFocusResult {
57
+ /** Spread onto `<ReactFlow>`. Stable across renders. */
58
+ hoverHandlers: HoverFocusHandlers;
59
+ /** `className` for `<ReactFlow>`: `erd-flow`, plus `erd-flow--hover-focus` while a hover preview is on. */
60
+ flowClassName: string;
61
+ }
62
+ /** A click selection's arrows move this long, then stand still. */
63
+ export declare const SELECTION_FLOW_SETTLE_MS = 6000;
64
+ export declare function useHoverFocus(): UseHoverFocusResult;
@@ -1,5 +1,5 @@
1
1
  /**
2
- * AliasEditor — the "Table name" row of the detail panel's metadata.
2
+ * AliasEditor — the "Warehouse name" row of the detail panel's metadata.
3
3
  *
4
4
  * A model's name is its identity; its alias is the table dbt builds it as.
5
5
  * Setting one is how two layers can each have a `date` table: the models are
@@ -8,12 +8,26 @@
8
8
  * Read mode shows the table name (the model name, dimmed, when there is no
9
9
  * alias). Edit mode is a single input: Enter saves, Escape cancels, and an
10
10
  * empty value clears the alias.
11
+ *
12
+ * Without a dbt project (`dbtWording` false) the tooltip and hint say the
13
+ * same thing without naming dbt. The viewer always passes false: a page
14
+ * outside VS Code cannot know whether the project uses dbt.
11
15
  */
12
16
  interface AliasEditorProps {
13
17
  modelName: string;
14
18
  alias?: string;
15
19
  /** Physical stage and the viewer: show the value, offer no edit. */
16
20
  readOnly: boolean;
21
+ /**
22
+ * Name dbt in the tooltip and hint (default). False in a folder with no
23
+ * dbt project, where the alias is just the table's name in the warehouse,
24
+ * and always in the viewer.
25
+ */
26
+ dbtWording?: boolean;
17
27
  }
18
- export declare function AliasEditor({ modelName, alias, readOnly }: AliasEditorProps): import("react/jsx-runtime").JSX.Element;
28
+ /** The edit-mode hint under the input. */
29
+ export declare function aliasHint(dbtWording: boolean): string;
30
+ /** The read-mode tooltip on the value. */
31
+ export declare function aliasTitle(modelName: string, alias: string | undefined, dbtWording: boolean): string;
32
+ export declare function AliasEditor({ modelName, alias, readOnly, dbtWording }: AliasEditorProps): import("react/jsx-runtime").JSX.Element;
19
33
  export {};
@@ -5,8 +5,10 @@
5
5
  * Supports adding new columns and inline editing.
6
6
  * Uses ColumnRowEditor for consistent column row rendering.
7
7
  */
8
- import type { DisplayColumn } from '@erd-studio/core';
9
- import type { ModelRole } from '@erd-studio/core';
8
+ import { type FieldsTable } from './FieldsSection.js';
9
+ import type { DisplayColumn, FieldList } from '@erd-studio/core';
10
+ import type { TableRole } from '@erd-studio/core';
11
+ import type { ColumnFieldVisibility } from '../common/ColumnRowEditor.js';
10
12
  import './ColumnEditor.css';
11
13
  export interface ColumnEditorProps {
12
14
  modelName: string;
@@ -14,6 +16,20 @@ export interface ColumnEditorProps {
14
16
  /** Whether this model/stage is read-only. */
15
17
  readOnly?: boolean;
16
18
  /** Parent model's role — passed to column rows for conditional SCD/additive dropdowns. */
17
- modelRole?: ModelRole;
19
+ modelRole?: TableRole;
20
+ /** The project's field list (`DisplaySubjectArea.fields`); the starting set when absent. */
21
+ fields?: FieldList;
22
+ /** No dbt project: the column Fields show no "from dbt" badge. */
23
+ logicalOnly?: boolean;
18
24
  }
19
- export declare function ColumnEditor({ modelName, columns, readOnly, modelRole }: ColumnEditorProps): import("react/jsx-runtime").JSX.Element;
25
+ /**
26
+ * Which of a column's typed fields its row shows: those the field list
27
+ * names (and that suit the table's role), and any with a value.
28
+ */
29
+ export declare function columnFieldVisibility(column: DisplayColumn, table: FieldsTable, list: FieldList): ColumnFieldVisibility;
30
+ /**
31
+ * A read-only row (the viewer, the physical stage) shows only the typed
32
+ * fields that have a value: an empty, disabled "Not set" dropdown says nothing.
33
+ */
34
+ export declare function valueOnlyFieldVisibility(column: DisplayColumn): ColumnFieldVisibility;
35
+ export declare function ColumnEditor({ modelName, columns, readOnly, modelRole, fields, logicalOnly }: ColumnEditorProps): import("react/jsx-runtime").JSX.Element;
@@ -6,7 +6,7 @@
6
6
  * 2. Read — shows description text with pencil edit button on hover
7
7
  * 3. Edit — textarea with Save/Cancel buttons
8
8
  *
9
- * Follows the same pattern as GrainEditor.tsx.
9
+ * Follows the same add / read / edit pattern as the text fields in FieldsSection.tsx.
10
10
  */
11
11
  interface DescriptionEditorProps {
12
12
  modelName: string;
@@ -2,9 +2,38 @@
2
2
  * DetailPanel — floating detail view for the selected model.
3
3
  *
4
4
  * Displays at top-right when a node is clicked. Shows:
5
- * - Model metadata (name, schema, description)
5
+ * - Model metadata (its file, when the editing host says, then name, schema, description)
6
6
  * - Editable column list
7
- * - Incoming and outgoing FK relationships
7
+ * - Incoming and outgoing FK relationships, then (editing host only) the
8
+ * relationships to tables not on this diagram, each with "+ Add"
9
+ * - "Modelling detail (optional)": the table's Fields (its context fields,
10
+ * from the project's field list), folded until one of them has a value
11
+ *
12
+ * The viewer (`useIsViewer`) never has the host's context and keeps the
13
+ * layout it always had, in the old order, with the Fields where the role,
14
+ * grain, meta and rationale sections were: a section folded to its heading
15
+ * and count until the reader opens it (it sits above Columns, so a few long
16
+ * rationale values would push the columns and relationships out of a short
17
+ * embedded canvas), and remembered from table to table. Its close button hides the
18
+ * panel but keeps the table selected (its relationships stay lit; a click on
19
+ * the table opens the panel again, and the empty canvas or Escape clears it),
20
+ * its relationship rows wrap instead of cutting names off, and it is never
21
+ * taller than the canvas it floats over.
8
22
  */
23
+ import { type FieldsTable } from './FieldsSection.js';
24
+ import type { DisplayRelationship, FieldList } from '@erd-studio/core';
9
25
  import './DetailPanel.css';
26
+ /**
27
+ * The detail panel's "Remove from Diagram" question. A table's relationships
28
+ * go with it only when the diagram file holds them itself (a YAML/JSON
29
+ * domain's own list); in the Table Library (every DBML diagram) they stay.
30
+ * `inDiagramFile` is absent from an older core: then every relationship the
31
+ * table draws is counted, the old warning.
32
+ */
33
+ export declare function removeConfirmText(tableName: string, relationshipsInDiagramFile: boolean | undefined, outgoing: readonly Pick<DisplayRelationship, 'inDiagramFile'>[], incoming: readonly Pick<DisplayRelationship, 'inDiagramFile'>[]): string;
34
+ /**
35
+ * Whether any of the table's fields has a value, so "Modelling detail" opens
36
+ * by itself. Import DBML's bookkeeping (`meta.dbml`) is not a field.
37
+ */
38
+ export declare function hasModellingDetail(model: FieldsTable, list?: FieldList): boolean;
10
39
  export declare function DetailPanel(): import("react/jsx-runtime").JSX.Element | null;
@@ -0,0 +1,68 @@
1
+ /**
2
+ * FieldsSection — a table's (or a column's) context fields in the Detail panel.
3
+ *
4
+ * The rows come from core's resolver (`resolveTableFields` /
5
+ * `resolveColumnFields`) over the project's field list
6
+ * (`DisplaySubjectArea.fields`, else core's `STARTING_FIELD_LIST`), so the
7
+ * canvas, the CLI and the assistant agree on what a table records. A field's
8
+ * value stays where it always lived (its *home*), and each edit is posted as
9
+ * the message that home has always used:
10
+ *
11
+ * - `rationale` → `updateModelRationale` with one key (`''` clears it)
12
+ * - `meta` → `updateMeta` (set one key; a clear removes the key)
13
+ * - `grain` → `updateModelGrain` (`''` clears it)
14
+ * - `role` → `updateModelRole` (`null` clears it)
15
+ *
16
+ * A column's section lists only the fields kept in the column's `meta`; its
17
+ * natural key, SCD type and additivity stay in `ColumnRowEditor`.
18
+ *
19
+ * Three presentations, gated `viewer ? … : (readOnly ? … : <editor>)`:
20
+ * the viewer and a read-only stage (physical) show the rows that have a
21
+ * value, labels and values only (two columns: muted label, wrapped value),
22
+ * and post nothing; in the viewer a table's section is a fold, its heading a
23
+ * button with the count, closed unless `open` says otherwise. The editor shows the list
24
+ * in order (empty fields with their guidance), retired fields with a badge,
25
+ * values the list does not name under "Not in your list", and "+ Add field".
26
+ */
27
+ import type { DisplayColumn, DisplayTable, FieldList, FieldRow, Meta, MetaValue } from '@erd-studio/core';
28
+ import './FieldsSection.css';
29
+ /** The table properties the resolver reads. */
30
+ export type FieldsTable = Pick<DisplayTable, 'modelRole' | 'grain' | 'meta' | 'rationale'>;
31
+ /** The column properties the resolver reads. */
32
+ export type FieldsColumn = Pick<DisplayColumn, 'name' | 'meta' | 'isNaturalKey' | 'scdType' | 'additiveType'>;
33
+ /** The label of Import DBML's bookkeeping row (`meta.dbml`). */
34
+ export declare const DBML_IMPORT_ROW_LABEL = "Kept from DBML import";
35
+ /** Whether a row holds a value a reader would see: anything except no value or blank text. */
36
+ export declare function rowHasValue(row: FieldRow): boolean;
37
+ /** The `meta.dbml` entry Import DBML keeps, or undefined. */
38
+ export declare function dbmlImportValue(owner: {
39
+ meta?: Meta;
40
+ } | undefined): MetaValue | undefined;
41
+ /** The rows a section draws: a column's section keeps only the fields held in its `meta`. */
42
+ export declare function fieldRows(table: FieldsTable, column: FieldsColumn | undefined, list: FieldList): FieldRow[];
43
+ /**
44
+ * Whether a table's read-only Fields section has anything to show: a field
45
+ * with a value, or the "Kept from DBML import" row.
46
+ */
47
+ export declare function tableFieldsShown(table: FieldsTable, list?: FieldList): boolean;
48
+ export interface FieldsSectionProps {
49
+ modelName: string;
50
+ /** The table (for a column's section: the column's table, whose role decides which fields suit it). */
51
+ table: FieldsTable;
52
+ /** Show this column's fields instead of the table's (a compact section). */
53
+ column?: FieldsColumn;
54
+ /** The project's field list (`DisplaySubjectArea.fields`); the starting set when absent. */
55
+ fields?: FieldList;
56
+ /** A read-only stage (physical): values only, as the viewer shows them. */
57
+ readOnly?: boolean;
58
+ /** No dbt project: no "from dbt" badge. */
59
+ logicalOnly?: boolean;
60
+ /**
61
+ * Viewer only, a table's section: whether its fold is open. Without it the
62
+ * section keeps its own state, starting closed.
63
+ */
64
+ open?: boolean;
65
+ /** Viewer only: called when the reader opens or closes the fold. */
66
+ onOpenChange?: (open: boolean) => void;
67
+ }
68
+ export declare function FieldsSection({ modelName, table, column, fields, readOnly, logicalOnly, open, onOpenChange }: FieldsSectionProps): import("react/jsx-runtime").JSX.Element;
@@ -2,7 +2,7 @@
2
2
  * ColumnTooltip — hover tooltip showing column details on the canvas.
3
3
  *
4
4
  * Renders via a React Portal to document.body so it is never clipped by
5
- * the ModelNode's `overflow: hidden`. Positioned with `position: fixed`
5
+ * the TableNode's `overflow: hidden`. Positioned with `position: fixed`
6
6
  * using the anchor element's bounding rect. Automatically flips below
7
7
  * the row if there isn't enough space above.
8
8
  *
@@ -0,0 +1,55 @@
1
+ /**
2
+ * EdgeFlow — the moving direction glyphs drawn along a relationship line.
3
+ *
4
+ * Chevrons run from the `1` end to the `*` end; a many-to-many has two lanes
5
+ * running opposite ways; a one-to-one has two dots meeting in the middle
6
+ * (`lib/edgeFlow.ts` has the rule and the recipes). Each glyph is an SVG
7
+ * `<animateMotion>` along the line's own path:
8
+ *
9
+ * - `path={d}`, never `<mpath href>`: edge ids repeat when two canvases
10
+ * share a page, so an id reference could follow the other canvas's line.
11
+ * - `calcMode="linear"`: the default (`paced`) ignores `keyPoints`.
12
+ * - Each glyph is keyed on its recipe, so a change of direction or a switch
13
+ * between moving and still mounts fresh SMIL elements.
14
+ * - With the OS reduce-motion setting on, too many lines flowing, or a
15
+ * settled click selection, every glyph is still: a plain
16
+ * `translate() rotate()` at a point off the bends (`stillPositions`),
17
+ * with no SMIL at all, so it follows a swapped or re-routed line and
18
+ * costs nothing per frame.
19
+ *
20
+ * `EdgeFlow` is the canvas version: it draws nothing below `MIN_FLOW_ZOOM`
21
+ * and scales the glyphs up as the canvas zooms out, so they stay legible.
22
+ * `EdgeFlowGlyphs` always draws (the Legend, which is not zoomed). Neither
23
+ * takes pointer events nor is read out by assistive technology: the line's
24
+ * own `<title>` says what the relationship is.
25
+ */
26
+ import { type FlowMode } from '../../lib/edgeFlow.js';
27
+ import './FkEdge.css';
28
+ /** A filled, notched arrowhead, 8 × 9, pointing along +x (the direction of travel). */
29
+ export declare const CHEVRON_D = "M -4,-4.5 L 4,0 L -4,4.5 L -1.75,0 Z";
30
+ export interface EdgeFlowProps {
31
+ /** The line's path data, exactly as drawn. */
32
+ d: string;
33
+ mode: FlowMode;
34
+ /** Path length in flow pixels when `d` cannot be measured; otherwise unused. */
35
+ length?: number;
36
+ /** False: stand still (too many lines flowing, or a settled selection). */
37
+ animate: boolean;
38
+ /** Colour key: a stage (`logical`, `physical`, `ghost`) or `discrepancy-<status>`. */
39
+ statusKey: string;
40
+ /** Glyph size factor (1 = 8 flow px). `EdgeFlow` sets it from the zoom. */
41
+ glyphScale?: number;
42
+ }
43
+ declare function EdgeFlowGlyphsComponent({ d, mode, length, animate, statusKey, glyphScale }: EdgeFlowProps): import("react/jsx-runtime").JSX.Element;
44
+ /** The glyphs at any zoom (for the Legend). */
45
+ export declare const EdgeFlowGlyphs: import("react").MemoExoticComponent<typeof EdgeFlowGlyphsComponent>;
46
+ /**
47
+ * The glyph scale for a zoom: 0 below `MIN_FLOW_ZOOM` (draw nothing), else
48
+ * 1 / zoom rounded up to a quarter and clamped to 1..`MAX_GLYPH_SCALE`, so a
49
+ * glyph stays about 8 screen px or more and the value changes rarely.
50
+ */
51
+ export declare function glyphScaleForZoom(zoom: number): number;
52
+ declare function EdgeFlowComponent(props: EdgeFlowProps): import("react/jsx-runtime").JSX.Element | null;
53
+ /** The glyphs on the canvas: nothing below `MIN_FLOW_ZOOM`, larger when zoomed out. */
54
+ export declare const EdgeFlow: import("react").MemoExoticComponent<typeof EdgeFlowComponent>;
55
+ export {};
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Where an edge's HTML labels go: React Flow's label layer
3
+ * (`.react-flow__edgelabel-renderer`), portalled into exactly as
4
+ * `<EdgeLabelRenderer>` does, but found once per canvas.
5
+ *
6
+ * `<EdgeLabelRenderer>` finds the layer with a store selector that runs
7
+ * `domNode.querySelector(...)`, so every edge re-searches the canvas DOM on
8
+ * every React Flow store update. On a 2,000-table diagram that was 2,743
9
+ * searches through ~100,000 elements several times a drag frame: about 45% of
10
+ * the time a drag took. Here the selector is a WeakMap lookup.
11
+ */
12
+ import type { ReactNode } from 'react';
13
+ /** The canvas's label layer, looked up once per canvas root (again only if it left the DOM). */
14
+ export declare function labelLayerOf(root: Element | null | undefined): HTMLElement | null;
15
+ /** Renders `children` in the canvas's label layer (nothing until the layer exists). */
16
+ export declare function EdgeLabels({ children }: {
17
+ children: ReactNode;
18
+ }): import("react").ReactPortal | null;
@@ -1,7 +1,9 @@
1
1
  /**
2
2
  * FkEdge — custom React Flow edge for displaying FK relationships.
3
3
  *
4
- * Renders a smooth-step path with Power BI-style cardinality labels:
4
+ * Draws its route from the one-pass line router (`EdgeRouter`, in the canvas
5
+ * store's `edgeRoutes`) when it has one, else a smooth-step path, with Power
6
+ * BI-style cardinality labels:
5
7
  * `*` for "many" and `1` for "one", positioned near each endpoint.
6
8
  * Built relationships are blue; design relationships are orange.
7
9
  * All edges use a solid line style.
@@ -12,6 +14,11 @@
12
14
  * Edges connect to node-level handles (top/right/bottom/left) rather
13
15
  * than column-specific handles, choosing the side that creates the
14
16
  * least bends.
17
+ *
18
+ * Edges of the hovered or selected table carry moving arrows from the 1 end
19
+ * to the * end; one-to-one shows two dots meeting (`EdgeFlow`). While a hover
20
+ * preview is active, the hovered table's lines are marked lit so the root
21
+ * `erd-flow--hover-focus` class can fade every other line (`useHoverFocus`).
15
22
  */
16
23
  import { type EdgeProps } from '@xyflow/react';
17
24
  import type { FkFlowEdge } from '../../types/graph.js';
@@ -1,5 +1,5 @@
1
1
  /**
2
- * ModelNode — custom React Flow node for displaying a semantic model card.
2
+ * TableNode — custom React Flow node for displaying a semantic model card.
3
3
  *
4
4
  * Shows model name with a layer badge, a list of columns with PK/FK
5
5
  * indicators and data types, and a footer with the total column count.
@@ -14,8 +14,8 @@
14
14
  * 3. Release to open the relationship dialog with prefilled data
15
15
  */
16
16
  import { type NodeProps } from '@xyflow/react';
17
- import type { ModelFlowNode } from '../../types/graph.js';
17
+ import type { TableFlowNode } from '../../types/graph.js';
18
18
  import './ModelNode.css';
19
- declare function ModelNodeComponent({ data, selected }: NodeProps<ModelFlowNode>): import("react/jsx-runtime").JSX.Element;
20
- export declare const ModelNode: import("react").MemoExoticComponent<typeof ModelNodeComponent>;
19
+ declare function TableNodeComponent({ id, data, selected }: NodeProps<TableFlowNode>): import("react/jsx-runtime").JSX.Element;
20
+ export declare const TableNode: import("react").MemoExoticComponent<typeof TableNodeComponent>;
21
21
  export {};
@@ -1,11 +1,42 @@
1
1
  /**
2
2
  * Legend — collapsible panel explaining the visual language of the graph.
3
3
  *
4
- * Shows stage colours, relationship types, cardinality notation, the node
5
- * header badges (schema abbreviation and physical provenance chips) and the
6
- * column badges. The badge vocabulary is imported from lib/badgeLabels so the
7
- * legend cannot fall out of step with what ModelNode actually renders. Positioned in the bottom-left corner, collapsible
4
+ * Shows stage colours, relationship types, cardinality notation (and the
5
+ * moving direction arrows a hovered or clicked table's lines carry), the node
6
+ * header badges (the layer badge, the schema prefix and physical provenance
7
+ * chips) and the column badges. Without a dbt project (`logicalOnly`) the
8
+ * Physical, ghost, provenance and comparison entries are left out, and in a
9
+ * single-layer project the layer badge, which the canvas does not draw. The
10
+ * viewer (`useIsViewer`) shows only what can appear on the one read-only stage
11
+ * it draws (`legendEntries`): a logical diagram has no Physical, ghost,
12
+ * provenance or line-colour entries, a physical one no Logical entry, and
13
+ * neither has the comparison entries (the viewer never compares stages). It
14
+ * never names dbt: a page outside VS Code cannot know whether the project
15
+ * uses it. The badge vocabulary is imported from lib/badgeLabels so the
16
+ * legend cannot fall out of step with what TableNode actually renders. Positioned in the bottom-left corner, collapsible
8
17
  * to a `?` icon button.
9
18
  */
10
19
  import './Legend.css';
20
+ /** Which optional groups of entries the legend shows. */
21
+ export interface LegendEntries {
22
+ /** The Logical stage card. */
23
+ logicalStage: boolean;
24
+ /** The Physical stage card, both ghosts and the provenance chips. */
25
+ physical: boolean;
26
+ /** "Line colour follows the stage": a logical and a physical line. */
27
+ lineColours: boolean;
28
+ /** "Comparing Stages": the discrepancy colours and lines. */
29
+ comparison: boolean;
30
+ }
31
+ /**
32
+ * The legend's optional entries. The editor shows every one (without a dbt
33
+ * project, `logicalOnly`, none of the physical or comparison ones). The viewer
34
+ * draws one stage, read-only, and never a comparison, so it shows only that
35
+ * stage's entries.
36
+ */
37
+ export declare function legendEntries(options: {
38
+ viewer: boolean;
39
+ logicalOnly: boolean;
40
+ stage: string | undefined;
41
+ }): LegendEntries;
11
42
  export declare function Legend(): import("react/jsx-runtime").JSX.Element;