@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
@@ -0,0 +1,45 @@
1
+ /**
2
+ * StatusBar — the diagram's counts at the bottom-left of the canvas, and a key
3
+ * to its layers: "5 tables | 4 relationships | [GLD] Gold 4 [SLV] Silver 1".
4
+ *
5
+ * Relationships are counted as the canvas draws them: a composite foreign key
6
+ * is one line, so one relationship, however many column pairs it has, and a
7
+ * relationship whose other end is not on the diagram is not drawn, so not
8
+ * counted.
9
+ *
10
+ * The key appears once the tables' own layers are known (DBML storage), so a
11
+ * diagram that borrows tables from other layers says which, with the badge
12
+ * each table's header carries. A single-layer project has no key.
13
+ *
14
+ * Shared by the extension's editor and the viewer (`ErdCanvas`): it only reads
15
+ * the store's domain, posts nothing and offers nothing to click. Render it as
16
+ * a child of `<ReactFlow>` (it is a React Flow `Panel`, beside the legend's
17
+ * `?` button). In the viewer it stays in sight while the legend is open, to
18
+ * the legend's right (the counts on one row, the layer key under them), and
19
+ * is hidden while the legend is open on a canvas too narrow for both.
20
+ */
21
+ import type { DisplaySubjectArea } from '@erd-studio/core';
22
+ import './StatusBar.css';
23
+ /** One entry of the layer key. */
24
+ export interface StatusBarLayer {
25
+ id: string;
26
+ count: number;
27
+ abbreviation: string;
28
+ label: string;
29
+ color?: string;
30
+ }
31
+ /** What the status bar shows for a domain. */
32
+ export interface StatusBarStats {
33
+ tables: number;
34
+ /** One per line on the canvas: a composite's column pairs fold into one. */
35
+ relationships: number;
36
+ /** Empty in a single-layer project. */
37
+ layers: StatusBarLayer[];
38
+ }
39
+ /**
40
+ * The status bar's counts for a domain: its tables, its relationships as the
41
+ * canvas draws them, and each layer its tables belong to with its count (the
42
+ * diagram's own layer first, then `layerConfigs` order, then by name).
43
+ */
44
+ export declare function statusBarStats(domain: Pick<DisplaySubjectArea, 'models' | 'relationships' | 'layer' | 'layerConfigs'>): StatusBarStats;
45
+ export declare function StatusBar(): import("react/jsx-runtime").JSX.Element | null;
@@ -7,7 +7,7 @@
7
7
  * Uses useFocusWithinRow hook for robust focus tracking that eliminates
8
8
  * flicker when switching between fields within the same row.
9
9
  */
10
- import type { ModelRole } from '@erd-studio/core';
10
+ import type { TableRole } from '@erd-studio/core';
11
11
  import type { UpdateColumnPayloadColumn } from '@erd-studio/core';
12
12
  import './ColumnRowEditor.css';
13
13
  export interface ColumnRowEditorColumn {
@@ -22,6 +22,17 @@ export interface ColumnRowEditorColumn {
22
22
  /** Additive type for fact measure columns. */
23
23
  additiveType?: 'additive' | 'semi-additive' | 'non-additive';
24
24
  }
25
+ /**
26
+ * Which of a column's typed fields the row shows, from the project's field
27
+ * list (`ColumnEditor` works it out with core's resolver). Each is shown when
28
+ * true. The natural key's badge always shows whatever `naturalKey` says: it
29
+ * is key information, not only a field.
30
+ */
31
+ export interface ColumnFieldVisibility {
32
+ scdType?: boolean;
33
+ additiveType?: boolean;
34
+ naturalKey?: boolean;
35
+ }
25
36
  export interface ColumnRowEditorProps {
26
37
  /** The column data to display/edit. */
27
38
  column: ColumnRowEditorColumn;
@@ -51,8 +62,13 @@ export interface ColumnRowEditorProps {
51
62
  onToggleNK?: () => void;
52
63
  /** Show warning if multiple PKs exist in model. */
53
64
  showMultiplePKWarning?: boolean;
54
- /** Parent model's role — determines whether SCD/additive dropdowns are shown. */
55
- modelRole?: ModelRole;
65
+ /** Parent model's role — determines whether SCD/additive dropdowns are shown (without `fieldVisibility`). */
66
+ modelRole?: TableRole;
67
+ /**
68
+ * Which typed fields to show, from the field list. When absent, the SCD
69
+ * dropdown shows for a dimension role and the additive one for a fact role.
70
+ */
71
+ fieldVisibility?: ColumnFieldVisibility;
56
72
  /** Whether the description area is expanded. */
57
73
  expanded?: boolean;
58
74
  /** Callback when the expand/collapse chevron is clicked. */
@@ -74,4 +90,4 @@ export interface ColumnRowEditorProps {
74
90
  /** Rendered at the foot of the expanded area — the column's metadata editor. */
75
91
  metaSlot?: React.ReactNode;
76
92
  }
77
- export declare function ColumnRowEditor({ column, mode, existingColumnNames, onUpdate, onDelete, onCancel, showIndicators, showDelete, onTogglePK, onToggleFK, onToggleNK, showMultiplePKWarning, modelRole, expanded, onToggleExpand, dragHandleProps, isDragOver, isBeingDragged, isSelected, onSelect, isEditingActive, metaSlot, }: ColumnRowEditorProps): import("react/jsx-runtime").JSX.Element;
93
+ export declare function ColumnRowEditor({ column, mode, existingColumnNames, onUpdate, onDelete, onCancel, showIndicators, showDelete, onTogglePK, onToggleFK, onToggleNK, showMultiplePKWarning, modelRole, fieldVisibility, expanded, onToggleExpand, dragHandleProps, isDragOver, isBeingDragged, isSelected, onSelect, isEditingActive, metaSlot, }: ColumnRowEditorProps): import("react/jsx-runtime").JSX.Element;
@@ -14,7 +14,11 @@ export interface KeyBadgeGroupProps {
14
14
  onTogglePK?: () => void;
15
15
  onToggleFK?: () => void;
16
16
  onToggleNK?: () => void;
17
- /** Show warning icon if multiple PKs exist in the model. */
17
+ /**
18
+ * Show a warning icon on a PK badge. Deprecated: several primary-key
19
+ * columns are one composite key (`indexes { (a, b) [pk] }`), never a fault,
20
+ * so the detail panel no longer sets it; kept for callers that pass it.
21
+ */
18
22
  showMultiplePKWarning?: boolean;
19
23
  }
20
24
  export declare function KeyBadgeGroup({ isPrimaryKey, isForeignKey, isNaturalKey, mode, status, onTogglePK, onToggleFK, onToggleNK, showMultiplePKWarning, }: KeyBadgeGroupProps): import("react/jsx-runtime").JSX.Element;
@@ -3,27 +3,33 @@ export * from './store.js';
3
3
  export * from './sizing.js';
4
4
  export { NOOP_CANVAS_HOST, CanvasEnvironmentProvider, useCanvasHost, useSend, useIsViewer, type CanvasHost, type CanvasEnvironment, } from './host/canvasEnvironment.js';
5
5
  export { useCanvasGraph } from './canvas/useCanvasGraph.js';
6
+ export { useHoverFocus, type UseHoverFocusResult, type HoverFocusHandlers } from './canvas/useHoverFocus.js';
6
7
  export { CanvasBackdrop, ANNOTATION_MINIMAP_COLORS } from './canvas/CanvasBackdrop.js';
7
8
  export { canvasNodeTypes, canvasEdgeTypes } from './canvas/flowTypes.js';
8
- export { ModelNode } from './components/Graph/ModelNode.js';
9
+ export { TableNode } from './components/Graph/ModelNode.js';
9
10
  export { FkEdge } from './components/Graph/FkEdge.js';
11
+ export { EdgeFlow, EdgeFlowGlyphs, type EdgeFlowProps } from './components/Graph/EdgeFlow.js';
10
12
  export { AnnotationNode } from './components/Graph/AnnotationNode.js';
11
13
  export { AnnotationEdge } from './components/Graph/AnnotationEdge.js';
12
14
  export { DetailPanel } from './components/DetailPanel/DetailPanel.js';
13
- export { Legend } from './components/Legend/Legend.js';
15
+ export { Legend, legendEntries, type LegendEntries } from './components/Legend/Legend.js';
16
+ export { StatusBar, statusBarStats, type StatusBarStats, type StatusBarLayer } from './components/StatusBar/StatusBar.js';
14
17
  export { KeyBadge } from './components/common/KeyBadge.js';
15
18
  export { KeyBadgeGroup } from './components/common/KeyBadgeGroup.js';
16
19
  export { DataTypeSelect } from './components/common/DataTypeSelect.js';
17
- export { ColumnRowEditor } from './components/common/ColumnRowEditor.js';
20
+ export { ColumnRowEditor, type ColumnFieldVisibility } from './components/common/ColumnRowEditor.js';
18
21
  export * from './hooks/useColumnExpansion.js';
19
22
  export * from './hooks/useColumnReorder.js';
20
23
  export * from './hooks/useFocusWithinRow.js';
21
24
  export * from './hooks/useLongPressDrag.js';
25
+ export { usePrefersReducedMotion } from './hooks/usePrefersReducedMotion.js';
22
26
  export { applyNodeOverlays, type GraphNode, type GraphEdge } from './lib/nodeOverlays.js';
27
+ export { computeCanvasFocus, sameFocus, type CanvasFocus } from './lib/canvasFocus.js';
23
28
  export * from './lib/stageColors.js';
24
29
  export * from './lib/dataTypeColors.js';
25
30
  export * from './lib/badgeLabels.js';
26
31
  export { swapCardinality } from './lib/cardinalityUtils.js';
27
32
  export { columnPairs, foldComposites, relationshipDisplayKey, relationshipEdgeId } from './lib/relationshipDisplayKey.js';
28
- export { computeModelLabels, matchesModelSearch, type LabelledModel } from './lib/modelLabels.js';
33
+ export { computeTableLabels, matchesTableSearch, type LabelledTable } from './lib/modelLabels.js';
34
+ export { LAYER_BADGE_FALLBACK, layerAbbreviation, isSingleLayerProject } from './lib/layerCues.js';
29
35
  export { ANNOTATION_COLORS } from './lib/annotationColors.js';
@@ -4,7 +4,7 @@
4
4
  * Uses imperative mouse events (matching the existing useLongPressDrag pattern).
5
5
  * Returns drag state, handle props for each row, and the optimistic display order.
6
6
  *
7
- * Works in both DetailPanel (ColumnRowEditor rows) and ModelNode (ColumnRow rows)
7
+ * Works in both DetailPanel (ColumnRowEditor rows) and TableNode (ColumnRow rows)
8
8
  * by accepting configurable CSS selectors for the container and row elements.
9
9
  */
10
10
  interface Column {
@@ -0,0 +1,13 @@
1
+ /**
2
+ * usePrefersReducedMotion — true while the operating system's reduce-motion
3
+ * setting is on (`prefers-reduced-motion: reduce`).
4
+ *
5
+ * The relationship arrows are SMIL animations, which CSS media queries cannot
6
+ * stop, so `EdgeFlow` asks this hook and draws a still frame instead. One
7
+ * module-level media query list serves every caller; false where
8
+ * `window.matchMedia` does not exist (tests, server rendering).
9
+ *
10
+ * VS Code's own `workbench.reduceMotion` setting does not reach webviews, so
11
+ * only the OS setting counts.
12
+ */
13
+ export declare function usePrefersReducedMotion(): boolean;
@@ -1,6 +1,7 @@
1
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';
2
+ export { LINE_STYLES, DEFAULT_LINE_STYLE, isLineStyle, type LineStyle } from './lib/edgeRouting/lineStyle.js';
3
+ export { transformSubjectArea, pickHandleSides, type TransformResult, type TransformOptions, type NodeRect, type NodeDimensions, } from './lib/graphTransformer.js';
3
4
  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 { toMermaid, toDbml, exportDiagram, diagramExportFileName, DIAGRAM_EXPORT_FORMATS, DIAGRAM_EXPORT_FILE_EXTENSIONS, type DiagramExportFormat, } from '@erd-studio/core';
6
- export type { ModelFlowNode, ModelNodeData, FkFlowEdge, FkEdgeData, AnnotationFlowNode, AnnotationFlowEdge, ColumnDisplay, } from './types/graph.js';
5
+ export type { DisplaySubjectArea, DisplayTable, DisplayColumn, DisplayRelationship, PhysicalColumnSource, PhysicalProvenance, Annotation, AnnotationColor, Cardinality, Layer, TableRole, NodePosition, Rationale, Stage, ViewConfig, LayerConfig, } from '@erd-studio/core';
6
+ export { toMermaid, toDbml, toDbmlRich, DBML_RICH_EXPORT_HEADER, type DbmlRichOptions, exportDiagram, diagramExportFileName, DIAGRAM_EXPORT_FORMATS, DIAGRAM_EXPORT_FILE_EXTENSIONS, type DiagramExportFormat, } from '@erd-studio/core';
7
+ export type { TableFlowNode, TableNodeData, FkFlowEdge, FkEdgeData, AnnotationFlowNode, AnnotationFlowEdge, ColumnDisplay, } from './types/graph.js';
@@ -2,7 +2,7 @@
2
2
  * badgeLabels — the abbreviations and symbols the canvas stamps on nodes and
3
3
  * columns, and the prose that decodes them.
4
4
  *
5
- * These live here rather than in ModelNode because the Legend explains exactly
5
+ * These live here rather than in TableNode because the Legend explains exactly
6
6
  * this vocabulary. Two copies of "WH means the warehouse catalog" is two things
7
7
  * to keep in step, and the legend is precisely the copy nobody edits when the
8
8
  * node changes. Both ends import these maps, so a new source or a renamed chip
@@ -27,6 +27,12 @@ export declare const SOURCE_PHRASE: Record<PhysicalColumnSource, string>;
27
27
  export declare const SOURCE_ORDER: PhysicalColumnSource[];
28
28
  /** One line per source for the legend, saying what having that chip means. */
29
29
  export declare const SOURCE_LEGEND_DESC: Record<PhysicalColumnSource, string>;
30
+ /** Viewer twin of `SOURCE_LABEL`: the manifest chip does not say DBT. */
31
+ export declare const SOURCE_LABEL_NEUTRAL: Record<PhysicalColumnSource, string>;
32
+ /** Viewer twin of `SOURCE_PHRASE`. */
33
+ export declare const SOURCE_PHRASE_NEUTRAL: Record<PhysicalColumnSource, string>;
34
+ /** Viewer twin of `SOURCE_LEGEND_DESC`. */
35
+ export declare const SOURCE_LEGEND_DESC_NEUTRAL: Record<PhysicalColumnSource, string>;
30
36
  /** Unicode circled numbers for SCD type badges. */
31
37
  export declare const SCD_BADGE: Record<number, string>;
32
38
  /**
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Reading the editing host's `CanvasContext` (the detail panel's File line
3
+ * and "Not on this diagram" list, the node's "+n" chip). A viewer never has
4
+ * one, so every helper answers "nothing" for a null context.
5
+ */
6
+ import type { CanvasContext, DisplaySubjectArea, OffDiagramRelationship } from '@erd-studio/core';
7
+ /** `record[key]` when it is the record's own key: a table named `constructor` is not `Object.prototype.constructor`. */
8
+ export declare function ownValue<T>(record: Readonly<Record<string, T>> | undefined, key: string): T | undefined;
9
+ /**
10
+ * The table's relationships to tables not on the diagram, as the host sent
11
+ * them, less any whose other table is on the diagram now (added since the
12
+ * context was worked out). Logical stage only.
13
+ */
14
+ export declare function offDiagramRows(context: CanvasContext | null, domain: DisplaySubjectArea | null, modelName: string): readonly OffDiagramRelationship[];
15
+ /** The distinct tables of `rows`, in their order. */
16
+ export declare function offDiagramTables(rows: readonly OffDiagramRelationship[]): string[];
17
+ /** `many-to-one` as a reader writes it: `Many-to-one`. */
18
+ export declare function cardinalityLabel(cardinality: string): string;
19
+ /** The "+n" chip's tip. */
20
+ export declare function offDiagramChipTip(count: number): string;
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Canvas focus — which tables and relationship lines stay bright, and which
3
+ * lines carry the moving direction arrows, for a focused table or line.
4
+ *
5
+ * One connectivity rule shared by click dimming (`applyNodeOverlays`) and the
6
+ * hover preview (`useHoverFocus`), so the two can never disagree about what
7
+ * "related" means:
8
+ *
9
+ * - A table: the table plus every table one FK line away. A line stays bright
10
+ * when both its ends are bright (so a line between two neighbours does too).
11
+ * The hover preview passes `ownLinesOnly`: it answers "what does this
12
+ * table connect to", so only the table's own lines stay bright there.
13
+ * - A line: its two end tables, and only that line.
14
+ *
15
+ * `flowEdges` are the lines that animate: the focused table's own lines (or
16
+ * the focused line), minus any drawn as `missing` in a comparison, because
17
+ * motion would claim a relationship that does not exist here.
18
+ *
19
+ * Pure: no React, no DOM. The result is immutable by convention, and
20
+ * `sameFocus` compares two by value so store writes can be skipped when a
21
+ * recompute produced the same answer.
22
+ */
23
+ import type { GraphEdge } from './nodeOverlays.js';
24
+ /** Above this many flowing lines, arrows stand still (a static direction cue). */
25
+ export declare const FLOW_ANIMATION_LIMIT = 40;
26
+ export interface CanvasFocus {
27
+ /** Table driving the focus (null for a line focus). */
28
+ readonly node: string | null;
29
+ /** FK line driving the focus (null for a table focus). */
30
+ readonly edge: string | null;
31
+ /** Bright tables: the table and its FK neighbours, or the line's two ends. */
32
+ readonly nodes: ReadonlySet<string>;
33
+ /** Bright FK lines: both ends in `nodes` (or, `ownLinesOnly`, touching `node`), or just the focused line. */
34
+ readonly edges: ReadonlySet<string>;
35
+ /** Lines that carry direction arrows: those touching `node`, or the focused line; never `missing` ones. */
36
+ readonly flowEdges: ReadonlySet<string>;
37
+ /** Whether the arrows move (`flowEdges.size <= FLOW_ANIMATION_LIMIT`). */
38
+ readonly animate: boolean;
39
+ }
40
+ export interface CanvasFocusTarget {
41
+ node?: string | null;
42
+ edge?: string | null;
43
+ }
44
+ export interface CanvasFocusOptions {
45
+ /** A table focus keeps only the table's own lines bright, not lines between its neighbours. */
46
+ ownLinesOnly?: boolean;
47
+ }
48
+ /**
49
+ * The focus for a table (`target.node`) or an FK line (`target.edge`); a table
50
+ * wins when both are given. Returns null when there is no target, the line is
51
+ * unknown or an annotation link, or (when `nodeIds` is given) the table is not
52
+ * one of them.
53
+ */
54
+ export declare function computeCanvasFocus(edges: readonly GraphEdge[], target: CanvasFocusTarget, nodeIds?: ReadonlySet<string>, options?: CanvasFocusOptions): CanvasFocus | null;
55
+ /** Value equality: driver, `animate`, then each set's size and members. */
56
+ export declare function sameFocus(a: CanvasFocus | null, b: CanvasFocus | null): boolean;
57
+ /** A table's part in a focus: the table driving it, one related to it, or neither. */
58
+ export declare function tableFocusRole(focus: CanvasFocus | null | undefined, id: string): 'origin' | 'related' | null;
59
+ /**
60
+ * An FK line's part in the current focus:
61
+ * - `'flow'`: carries moving arrows (`flowFocus`).
62
+ * - `'flow-static'`: carries arrows that stand still (too many lines flowing).
63
+ * - `'lit'`: kept bright by the hover preview, no arrows.
64
+ * - null: none of these.
65
+ */
66
+ export declare function edgeFocusRole(flow: CanvasFocus | null | undefined, hover: CanvasFocus | null | undefined, id: string): 'flow' | 'flow-static' | 'lit' | null;
@@ -1,14 +1,14 @@
1
1
  /**
2
2
  * Handle sides for edges after nodes are moved on the canvas.
3
3
  *
4
- * `transformDomain` chooses the handle side at each end of an edge from the
4
+ * `transformSubjectArea` chooses the handle side at each end of an edge from the
5
5
  * saved positions: `pickHandleSides` compares the two node rectangles, and a
6
6
  * self-referencing relationship always uses the top (source) and right
7
7
  * (target) handles. A canvas that lets nodes be dragged without rebuilding the
8
8
  * domain has to make that choice again for the edges on the dragged nodes, or
9
9
  * those edges stay attached to the side that faced the old position.
10
10
  *
11
- * The rectangles follow `transformDomain` too: a node's size is its measured
11
+ * The rectangles follow `transformSubjectArea` too: a node's size is its measured
12
12
  * size when React Flow has one, otherwise the model-node estimate from
13
13
  * `resolveNodeDimensions`, or an annotation's own (or default) size.
14
14
  */
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Relationship flow — the moving arrows on the lines of a focused table.
3
+ *
4
+ * The rule: glyphs travel from the `1` end to the `*` end, the way one
5
+ * customer fans out to many orders (Power BI's filter arrow, matching the
6
+ * `*` / `1` labels the canvas already draws). The edge path runs from the
7
+ * source (`fromModel`) to the target (`toModel`), so:
8
+ *
9
+ * | cardinality | mode | glyphs |
10
+ * |----------------|-----------|-----------------------------------------|
11
+ * | many-to-one | `reverse` | chevrons, target → source |
12
+ * | one-to-many | `forward` | chevrons, source → target |
13
+ * | many-to-many | `both` | two lanes of chevrons, opposite ways |
14
+ * | one-to-one | `meet` | two dots that meet in the middle |
15
+ * | anything else | `reverse` | as the default labels (`*` at source) |
16
+ * | `missing` | none | motion would claim the link exists here |
17
+ *
18
+ * `flowModeFor` is the one place to flip the convention.
19
+ *
20
+ * `planEdgeFlow` turns a mode and a path length into glyph recipes
21
+ * (`GlyphSpec`). Pure: no React, no DOM. A moving glyph is an SVG
22
+ * `<animateMotion>`; durations are bucketed so a few pixels of layout jitter
23
+ * do not restart the animation, and the glyph count is capped. With motion
24
+ * off (too many flowing lines, a click selection that has settled, or the
25
+ * user's reduce-motion setting) every glyph is `still`: a plain transform at
26
+ * a fixed point on the line, still pointing the right way. Still glyphs are
27
+ * deliberately not a frozen SMIL frame: once a `fill="freeze"` interval ends,
28
+ * Chrome stops applying later `path` / `rotate` / `keyPoints` changes, so a
29
+ * swapped or re-routed line would keep a stale arrow.
30
+ */
31
+ import type { FkEdgeData } from '../types/graph.js';
32
+ export declare const FLOW_SPEED_PX_S = 56;
33
+ export declare const FLOW_SPACING_PX = 90;
34
+ /** A one-way line always has at least two arrows, so one is always on screen. */
35
+ export declare const FLOW_MIN_GLYPHS = 2;
36
+ export declare const FLOW_MAX_GLYPHS = 4;
37
+ export declare const FLOW_MIN_DUR_S = 1.6;
38
+ export declare const FLOW_MAX_DUR_S = 7;
39
+ export declare const FLOW_DUR_BUCKET_S = 0.5;
40
+ export declare const MANY_MANY_SPACING_PX = 140;
41
+ export declare const MANY_MANY_MAX_PER_LANE = 3;
42
+ export declare const ONE_ONE_CYCLE_S = 1.8;
43
+ /** Below this zoom the glyphs would be sub-pixel noise, so none are drawn. */
44
+ export declare const MIN_FLOW_ZOOM = 0.35;
45
+ /** Glyphs grow as the canvas zooms out, up to this factor, so they stay legible. */
46
+ export declare const MAX_GLYPH_SCALE = 2.5;
47
+ /** Radius of a one-to-one dot, in flow px at scale 1. */
48
+ export declare const FLOW_DOT_RADIUS = 3.25;
49
+ export type FlowMode = 'forward' | 'reverse' | 'both' | 'meet';
50
+ export interface GlyphSpec {
51
+ kind: 'chevron' | 'dot';
52
+ /** 0 or 1; lanes only differ for `both`. */
53
+ lane: 0 | 1;
54
+ /** `<animateMotion keyPoints>`; for a still glyph, `p;p` (its fixed point). */
55
+ keyPoints: string;
56
+ rotate: 'auto' | 'auto-reverse';
57
+ keyTimes: string;
58
+ dur: string;
59
+ begin: string;
60
+ /** True: drawn at `at` with a plain transform, no animation. */
61
+ still: boolean;
62
+ /** A still glyph's position, as a fraction of the path length. */
63
+ at?: number;
64
+ opacityValues?: string;
65
+ opacityKeyTimes?: string;
66
+ }
67
+ export interface FlowPlanOptions {
68
+ animate: boolean;
69
+ reducedMotion: boolean;
70
+ /**
71
+ * Where a one-way or many-to-many line's two still glyphs sit (fractions of
72
+ * its length), chosen off the bends (`stillPositions`). Defaults to
73
+ * 0.25 / 0.75, or 0.2 / 0.8 for many-to-many.
74
+ */
75
+ stillAt?: readonly [number, number] | null;
76
+ /** How much the glyphs are scaled up (zoomed-out canvas); spaces the one-to-one dots. */
77
+ glyphScale?: number;
78
+ }
79
+ /** The flow mode for a line, or null when it must not move (`missing`). */
80
+ export declare function flowModeFor(cardinality: string | undefined, status?: FkEdgeData['discrepancyStatus']): FlowMode | null;
81
+ /** Seconds for one pass along a line of this length, bucketed and clamped. */
82
+ export declare function flowDuration(length: number): number;
83
+ /** The glyph recipes for one line. */
84
+ export declare function planEdgeFlow(mode: FlowMode, length: number, opts: FlowPlanOptions): GlyphSpec[];
@@ -0,0 +1,146 @@
1
+ /**
2
+ * Edge routing — route every FK line in one pass.
3
+ *
4
+ * input (boxes + anchors) → router (polylines) → renderer (corners, crossings)
5
+ * → tunnels (x-ray under cards)
6
+ *
7
+ * Two line styles, chosen by the reader (`LineStyle`): `curved` (soft curves
8
+ * around the tables, a halo where lines cross, an x-ray of a line that has
9
+ * to pass under a card) and `hops` (square corners rounded a little, a small
10
+ * hop where two lines cross). Both use the same router.
11
+ *
12
+ * A line the router leaves out (a self-loop, a line on an unmeasured card)
13
+ * keeps `FkEdge`'s smooth-step fallback, and so does every line when routing
14
+ * fails or is too slow for the diagram (`createEdgeRoutingEngine`).
15
+ */
16
+ import type { RoutedEdge, RoutingInput } from './types.js';
17
+ import { type LineStyle } from './lineStyle.js';
18
+ import type { RoutingWorkerFactory } from './worker/client.js';
19
+ export * from './types.js';
20
+ export { buildRoutingInput, routingInputKey, draggingNodesKey } from './input.js';
21
+ export { createAvoidRouter, type AvoidRouter } from './routers/orthogonalAvoid.js';
22
+ export * from './lineStyle.js';
23
+ export { routeAll, type RouteAllOptions } from './routeAll.js';
24
+ export { createRoutingWorker, type RoutingWorkerFactory, type RoutingWorkerHandle, type RoutingWorkerEvents } from './worker/client.js';
25
+ export type { WorkerRouteRequest, WorkerRouteResult } from './worker/protocol.js';
26
+ /**
27
+ * A pass during a drag slower than this (ms) is too slow to run on every
28
+ * frame: for the rest of that drag the routes are held (see
29
+ * `createEdgeRoutingEngine`), and everything is routed again once, when the
30
+ * drag ends.
31
+ */
32
+ export declare const DRAG_REROUTE_BUDGET_MS = 12;
33
+ /**
34
+ * A pass outside a drag slower than this (ms) holds the routes from a drag's
35
+ * first frame: even routing only the moved card's lines would not fit a frame.
36
+ * With a worker, it also moves that diagram's routing into the worker.
37
+ */
38
+ export declare const DRAG_HOLD_AFTER_MS = 50;
39
+ /** A main-thread pass slower than this (ms) turns routing off for diagrams of that size: every line draws the fallback. */
40
+ export declare const ROUTING_HARD_LIMIT_MS = 150;
41
+ /**
42
+ * A worker pass slower than this (ms) turns routing off for diagrams of that
43
+ * size. A safety net only: a 2,000-card diagram takes ~1.5 s.
44
+ */
45
+ export declare const WORKER_HARD_LIMIT_MS = 20000;
46
+ /**
47
+ * Diagrams with up to this many lines route on the main thread (a pass is
48
+ * ~20 ms or less), so a drag can re-route as the card moves. Larger ones
49
+ * route in the canvas's worker, when it has one.
50
+ */
51
+ export declare const MAIN_THREAD_MAX_LINES = 100;
52
+ /** With a worker: no limit on the number of lines (the worker never pauses the canvas). */
53
+ export declare const MAX_ROUTED_LINES: number;
54
+ /** Without a worker: diagrams with more lines than this are never routed (the pass would pause the canvas over ~40 ms). */
55
+ export declare const MAX_ROUTED_LINES_WITHOUT_WORKER = 150;
56
+ /** What the engine did on one update. */
57
+ export type RoutingPass =
58
+ /** Every line routed. */
59
+ 'routed'
60
+ /** A drag on a slow diagram: the last routes kept, minus the lines of the dragged cards. */
61
+ | 'held'
62
+ /**
63
+ * Sent to the worker: the last routes kept, minus the lines of cards that
64
+ * moved or changed size since, until the worker answers (`onUpdate`).
65
+ */
66
+ | 'pending'
67
+ /** No routes: every line draws the smooth-step fallback. */
68
+ | 'off';
69
+ export interface RoutingUpdate {
70
+ /** The routes to draw, by edge id; null when every line draws the fallback. */
71
+ routes: Map<string, RoutedEdge> | null;
72
+ pass: RoutingPass;
73
+ /** How long a routed pass took (0 when nothing was routed). */
74
+ millis: number;
75
+ }
76
+ export interface RoutingRequest {
77
+ /** Builds the input: called only for a pass that routes. */
78
+ input: () => RoutingInput;
79
+ /** The number of FK lines (for the caps, without building the input). */
80
+ lineCount: number;
81
+ style: LineStyle;
82
+ /** The canvas zoom (hops are sized for it). */
83
+ zoom?: number;
84
+ /** Ids of the cards being dragged (empty when no drag is in progress). */
85
+ dragging?: ReadonlySet<string>;
86
+ }
87
+ export interface EdgeRoutingEngine {
88
+ update(request: RoutingRequest): RoutingUpdate;
89
+ /** Stops the worker. The engine answers nothing afterwards. */
90
+ dispose(): void;
91
+ }
92
+ export interface EdgeRoutingEngineOptions {
93
+ /** Clock for pass timings (default `performance.now`). */
94
+ now?: () => number;
95
+ /**
96
+ * Starts this canvas's routing worker (`createRoutingWorker`), called the
97
+ * first time a diagram is large enough to need it. Omitted, or returning
98
+ * null: every pass runs on the main thread, under the lower cap.
99
+ */
100
+ worker?: RoutingWorkerFactory;
101
+ /** Receives the routes a worker pass produced, after `update` returned 'pending'. */
102
+ onUpdate?: (update: RoutingUpdate) => void;
103
+ }
104
+ /**
105
+ * A worker pass in which at least this share of the cards moved or changed
106
+ * size since the worker's last pass routes (nearly) every line again: when a
107
+ * newer request supersedes it, the worker is restarted rather than waited for.
108
+ */
109
+ export declare const COLD_PASS_SHARE = 0.5;
110
+ /**
111
+ * One canvas's routing: its own router (so two canvases on a page never share
112
+ * the router's memory of the last pass), its own worker, and the performance
113
+ * guard.
114
+ *
115
+ * - Up to `MAIN_THREAD_MAX_LINES` lines, a pass runs on the main thread, and
116
+ * a drag re-routes only what the move touches, which is usually far cheaper
117
+ * than a full pass (40 tables / 60 lines: about 16 ms cold, 6 ms a drag
118
+ * frame). When one drag frame takes more than `DRAG_REROUTE_BUDGET_MS`, or
119
+ * the last pass outside a drag took more than `DRAG_HOLD_AFTER_MS`, the
120
+ * drag stops routing: the routes stay as they were, minus the lines
121
+ * touching a dragged card (they draw the fallback, which follows the card),
122
+ * and the first update after the drag routes everything again.
123
+ * - Larger diagrams (and a smaller one whose pass took more than
124
+ * `DRAG_HOLD_AFTER_MS`) route in the worker, so the canvas never pauses:
125
+ * `update` returns 'pending' with the last routes still right (lines on a
126
+ * moved card fall back), and `onUpdate` delivers the new ones. A drag holds
127
+ * the routes; an answer to a request that has since been superseded is
128
+ * dropped, and only the latest waiting request is sent next. When the pass
129
+ * in flight is a cold one (the worker's first, sent before the cards were
130
+ * measured, say, or one after most cards moved) and a newer request
131
+ * arrives, the worker is stopped and a fresh one routes the newer request
132
+ * at once: waiting would cost a whole pass, and a cold router has no memory
133
+ * worth keeping. A warm pass (a few cards moved) is waited for, so the
134
+ * router keeps its memory and the next pass stays incremental.
135
+ * - With the worker, every diagram is routed, whatever its size. Without it
136
+ * (it cannot start, or failed), more than `MAX_ROUTED_LINES_WITHOUT_WORKER`
137
+ * lines are not. A main-thread pass over `ROUTING_HARD_LIMIT_MS`, or a
138
+ * worker pass over `WORKER_HARD_LIMIT_MS`, turns routing off for this
139
+ * canvas while the diagram has at least that many lines.
140
+ * - A worker that fails is stopped for good; the canvas carries on on the
141
+ * main thread under the lower cap.
142
+ * - A router, renderer or worker error is reported once (`console.warn`),
143
+ * never per frame, and leaves those lines on the fallback. Nothing else is
144
+ * logged.
145
+ */
146
+ export declare function createEdgeRoutingEngine(options?: EdgeRoutingEngineOptions | (() => number)): EdgeRoutingEngine;
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Edge routing — build the routing input from React Flow state.
3
+ *
4
+ * Mirrors what `FkEdge` does today: a line meets the side its handle names
5
+ * (`node-{side}-{src|tgt}`, picked by the graph transformer), at the centre
6
+ * of that side plus the evenly distributed slot from `edgeDistribution`.
7
+ * Kept pure (no React) so routers and tests can call it.
8
+ */
9
+ import type { Edge, InternalNode, Node } from '@xyflow/react';
10
+ import { DEFAULT_NODE_HEIGHT, DEFAULT_NODE_WIDTH } from '../edgeDistribution.js';
11
+ import type { Rect, RoutingInput, Side, Point } from './types.js';
12
+ /** The box of a node in flow coordinates. */
13
+ export declare function rectOfNode(node: InternalNode<Node>): Rect;
14
+ /** Centre of a rect's side. */
15
+ export declare function sideCentre(rect: Rect, side: Side): Point;
16
+ /** A point pushed `distance` px outward from a side. */
17
+ export declare function pushOut(p: Point, side: Side, distance: number): Point;
18
+ /**
19
+ * Every FK line's spec plus every node box, from React Flow's node lookup and
20
+ * edge list. Edges whose nodes are not measured yet are skipped (they keep the
21
+ * fallback path until the next pass).
22
+ */
23
+ export declare function buildRoutingInput(nodeLookup: Map<string, InternalNode<Node>>, edges: Edge[]): RoutingInput;
24
+ /**
25
+ * A value key that changes exactly when routing input changes: node boxes and
26
+ * edge endpoints/handles. Subscribe to this rather than the lookup Map, which
27
+ * React Flow mutates in place.
28
+ */
29
+ export declare function routingInputKey(nodeLookup: Map<string, InternalNode<Node>>, edges: Edge[]): string;
30
+ /**
31
+ * The ids of the cards being dragged, joined (empty when no drag is in
32
+ * progress): the performance guard holds the routes while it is non-empty.
33
+ */
34
+ export declare function draggingNodesKey(nodeLookup: Map<string, InternalNode<Node>>): string;
35
+ export { DEFAULT_NODE_WIDTH, DEFAULT_NODE_HEIGHT };
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Edge routing — the two line styles a reader chooses between. Type imports
3
+ * only, so the store entry (`@erd-studio/renderer/store`) can name them
4
+ * without loading the router.
5
+ */
6
+ import type { LineStyleOptions } from './types.js';
7
+ /** The reader's choice of line style (the editor's toolbar, `ErdCanvas`'s `lineStyle` prop). */
8
+ export type LineStyle = 'curved' | 'hops';
9
+ /** Every line style, in toolbar order. */
10
+ export declare const LINE_STYLES: readonly LineStyle[];
11
+ export declare const DEFAULT_LINE_STYLE: LineStyle;
12
+ export declare function isLineStyle(value: unknown): value is LineStyle;
13
+ /** What each line style draws. Internal: readers pick a style, never these knobs. */
14
+ export declare const LINE_STYLE_OPTIONS: Readonly<Record<LineStyle, LineStyleOptions>>;
15
+ /** The options of a style; anything that is not a style reads as the default. */
16
+ export declare function lineStyleOptions(style: unknown): LineStyleOptions;