@erd-studio/renderer 0.3.0 → 0.4.1
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 +49 -0
- package/README.md +8 -3
- package/dist/{chunk-DUQ2TTQH.js → chunk-64BVOHOR.js} +4 -3
- package/dist/chunk-JDL3BFJZ.js +10479 -0
- package/dist/{chunk-WOTSQZCQ.js → chunk-YLXBIRPE.js} +132 -2
- package/dist/edge-routing-worker.js +1 -0
- package/dist/editor.js +43 -3
- package/dist/index.js +14 -3
- package/dist/sizing.js +2 -2
- package/dist/store.js +1 -1
- package/dist/styles.css +912 -359
- package/dist/types/ErdCanvas.d.ts +38 -1
- 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 +7 -1
- package/dist/types/canvas/useHoverFocus.d.ts +64 -0
- package/dist/types/components/DetailPanel/AliasEditor.d.ts +15 -1
- package/dist/types/components/DetailPanel/ColumnEditor.d.ts +18 -2
- 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/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 +1 -1
- package/dist/types/components/Legend/Legend.d.ts +34 -3
- package/dist/types/components/StatusBar/StatusBar.d.ts +45 -0
- package/dist/types/components/common/ColumnRowEditor.d.ts +18 -2
- package/dist/types/components/common/KeyBadgeGroup.d.ts +5 -1
- package/dist/types/editor.d.ts +8 -2
- package/dist/types/hooks/usePrefersReducedMotion.d.ts +13 -0
- package/dist/types/index.d.ts +2 -1
- package/dist/types/lib/badgeLabels.d.ts +6 -0
- package/dist/types/lib/canvasContext.d.ts +20 -0
- package/dist/types/lib/canvasFocus.d.ts +66 -0
- 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 +38 -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/hoverIntent.d.ts +44 -0
- package/dist/types/lib/layerCues.d.ts +26 -0
- package/dist/types/lib/nodeOverlays.d.ts +6 -0
- package/dist/types/lib/nodeSizing.d.ts +1 -1
- 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 +66 -1
- package/dist/types/types/graph.d.ts +20 -1
- package/package.json +3 -2
- package/dist/chunk-HWA32XG5.js +0 -5652
- 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
|
@@ -14,7 +14,11 @@ export interface KeyBadgeGroupProps {
|
|
|
14
14
|
onTogglePK?: () => void;
|
|
15
15
|
onToggleFK?: () => void;
|
|
16
16
|
onToggleNK?: () => void;
|
|
17
|
-
/**
|
|
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;
|
package/dist/types/editor.d.ts
CHANGED
|
@@ -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
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
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';
|
|
@@ -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;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export { ErdCanvas, type ErdCanvasProps, type ErdCanvasHandle } from './ErdCanvas.js';
|
|
2
|
+
export { LINE_STYLES, DEFAULT_LINE_STYLE, isLineStyle, type LineStyle } from './lib/edgeRouting/lineStyle.js';
|
|
2
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
5
|
export type { DisplaySubjectArea, DisplayTable, DisplayColumn, DisplayRelationship, PhysicalColumnSource, PhysicalProvenance, Annotation, AnnotationColor, Cardinality, Layer, TableRole, 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 { toMermaid, toDbml, toDbmlRich, DBML_RICH_EXPORT_HEADER, type DbmlRichOptions, exportDiagram, diagramExportFileName, DIAGRAM_EXPORT_FORMATS, DIAGRAM_EXPORT_FILE_EXTENSIONS, type DiagramExportFormat, } from '@erd-studio/core';
|
|
6
7
|
export type { TableFlowNode, TableNodeData, FkFlowEdge, FkEdgeData, AnnotationFlowNode, AnnotationFlowEdge, ColumnDisplay, } from './types/graph.js';
|
|
@@ -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;
|
|
@@ -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, routingWorkerFactory, 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;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Edge routing — how much of each segment a corner eats.
|
|
3
|
+
*
|
|
4
|
+
* Every interior vertex of a simplified polyline becomes a corner that
|
|
5
|
+
* starts `rIn` px before the vertex (along the incoming segment) and ends
|
|
6
|
+
* `rOut` px after it (along the outgoing one). Between corners, what is left
|
|
7
|
+
* of a segment is drawn straight; hops and cuts may only sit there.
|
|
8
|
+
*
|
|
9
|
+
* sharp 0
|
|
10
|
+
* rounded up to 8 px, a quadratic (today's smooth-step look)
|
|
11
|
+
* curved up to CURVE_RADIUS px each way, an elliptical quarter (cubic).
|
|
12
|
+
* An interior segment is shared by its two corners (half each);
|
|
13
|
+
* the first and last segments (from the card stubs) go wholly to
|
|
14
|
+
* their one corner, so a short step becomes one soft S. The
|
|
15
|
+
* curve stays inside the corner's triangle, so it only ever cuts
|
|
16
|
+
* the corner, never swings out; given the cards, a corner that
|
|
17
|
+
* would cut into one is shrunk until it clears.
|
|
18
|
+
*/
|
|
19
|
+
import type { CornerMode, Point, Rect } from '../types.js';
|
|
20
|
+
export declare const ROUNDED_RADIUS = 8;
|
|
21
|
+
/** Largest radius of a curved corner. */
|
|
22
|
+
export declare const CURVE_RADIUS = 40;
|
|
23
|
+
/** Cubic control-point factor for a quarter circle (and ellipse). */
|
|
24
|
+
export declare const KAPPA = 0.5523;
|
|
25
|
+
export interface CornerRadii {
|
|
26
|
+
/** Length of each segment `i` (pts[i] → pts[i+1]). */
|
|
27
|
+
segLen: Float64Array;
|
|
28
|
+
/** Per vertex: px trimmed from the incoming segment (0 at the two ends). */
|
|
29
|
+
rIn: Float64Array;
|
|
30
|
+
/** Per vertex: px trimmed from the outgoing segment (0 at the two ends). */
|
|
31
|
+
rOut: Float64Array;
|
|
32
|
+
}
|
|
33
|
+
export declare function cornerRadii(pts: readonly Point[], mode: CornerMode, obstacles?: readonly Rect[]): CornerRadii;
|
|
34
|
+
/** The corner's start / end points and cubic control points. */
|
|
35
|
+
export declare function cornerCurve(prev: Point, cur: Point, next: Point, rIn: number, rOut: number): {
|
|
36
|
+
a: {
|
|
37
|
+
x: number;
|
|
38
|
+
y: number;
|
|
39
|
+
};
|
|
40
|
+
c1: {
|
|
41
|
+
x: number;
|
|
42
|
+
y: number;
|
|
43
|
+
};
|
|
44
|
+
c2: {
|
|
45
|
+
x: number;
|
|
46
|
+
y: number;
|
|
47
|
+
};
|
|
48
|
+
b: {
|
|
49
|
+
x: number;
|
|
50
|
+
y: number;
|
|
51
|
+
};
|
|
52
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Edge routing — where routed lines cross each other.
|
|
3
|
+
*
|
|
4
|
+
* Works on the simplified polylines (one segment per straight run). A
|
|
5
|
+
* crossing is *proper*: the two segments cut through each other strictly
|
|
6
|
+
* inside both of them. Touching ends, a corner sitting on another line,
|
|
7
|
+
* and collinear overlaps (a shared channel) are not crossings.
|
|
8
|
+
*
|
|
9
|
+
* A sweep over segments sorted by left edge keeps this well under a
|
|
10
|
+
* millisecond for a few hundred segments.
|
|
11
|
+
*/
|
|
12
|
+
import type { Point } from '../types.js';
|
|
13
|
+
/** One end of a crossing: which line, which segment, how far along it (px). */
|
|
14
|
+
export interface CrossingEnd {
|
|
15
|
+
line: number;
|
|
16
|
+
seg: number;
|
|
17
|
+
/** Distance from the segment's first point. */
|
|
18
|
+
d: number;
|
|
19
|
+
}
|
|
20
|
+
export interface Crossing {
|
|
21
|
+
x: number;
|
|
22
|
+
y: number;
|
|
23
|
+
a: CrossingEnd;
|
|
24
|
+
b: CrossingEnd;
|
|
25
|
+
}
|
|
26
|
+
/** Every proper crossing between segments of different lines. */
|
|
27
|
+
export declare function findCrossings(polys: readonly Point[][]): Crossing[];
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Edge routing — where a drawn corner curve crosses another line's
|
|
3
|
+
* straight stretch.
|
|
4
|
+
*
|
|
5
|
+
* `findCrossings` works on the routed polylines, which is exact only where
|
|
6
|
+
* both lines are drawn straight. A rounded or curved corner cuts inside its
|
|
7
|
+
* vertex, so where a corner is involved the polyline crossing is in the wrong
|
|
8
|
+
* place (or the drawn lines do not cross at all, or cross where the
|
|
9
|
+
* polylines do not). With curved corners that is most crossings. This finds
|
|
10
|
+
* them on the drawn geometry: each corner curve flattened into chords,
|
|
11
|
+
* against every other line's straight stretches (the part of each segment no
|
|
12
|
+
* corner eats).
|
|
13
|
+
*
|
|
14
|
+
* Curve against curve is left out: there is no straight line to break.
|
|
15
|
+
*/
|
|
16
|
+
import type { CornerMode, Point } from '../types.js';
|
|
17
|
+
import { type CornerRadii } from './corners.js';
|
|
18
|
+
import type { CrossingEnd } from './crossings.js';
|
|
19
|
+
export interface CurveCrossing {
|
|
20
|
+
x: number;
|
|
21
|
+
y: number;
|
|
22
|
+
/** The straight line's end, as `findCrossings` reports it (distance from the segment's first point). */
|
|
23
|
+
straight: CrossingEnd;
|
|
24
|
+
/** The curving line, its corner vertex, and how far through the corner (0..1). */
|
|
25
|
+
curve: {
|
|
26
|
+
line: number;
|
|
27
|
+
vertex: number;
|
|
28
|
+
t: number;
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Every crossing between one line's corner curve and another line's straight
|
|
33
|
+
* stretch. `radii[i]` must be the radii the paths are drawn with.
|
|
34
|
+
*/
|
|
35
|
+
export declare function findCurveCrossings(polys: readonly Point[][], radii: readonly CornerRadii[], mode: CornerMode): CurveCrossing[];
|