@datnguye/erd-flow 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +151 -0
- package/dist/erd-flow.css +1 -0
- package/dist/index.d.ts +193 -0
- package/dist/index.js +987 -0
- package/package.json +69 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Dat Nguyen
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# @datnguye/erd-flow
|
|
2
|
+
|
|
3
|
+
[](https://github.com/datnguye/erd-flow/actions/workflows/pr-ci.yml)
|
|
4
|
+
[](https://github.com/datnguye/erd-flow/actions/workflows/release.yml)
|
|
5
|
+
[](https://www.npmjs.com/package/@datnguye/erd-flow)
|
|
6
|
+
[](https://www.npmjs.com/package/@datnguye/erd-flow)
|
|
7
|
+
[](./LICENSE)
|
|
8
|
+
[](./tsconfig.json)
|
|
9
|
+
[](https://react.dev)
|
|
10
|
+
|
|
11
|
+
A [React Flow](https://reactflow.dev) entity-relationship diagram for
|
|
12
|
+
dbt projects: table nodes with per-column PK/FK badges, self-drawing foreign-key
|
|
13
|
+
edges that land on the exact joined rows, and pluggable layouts (hierarchical,
|
|
14
|
+
radial, force). It renders the [dbterd](https://github.com/datnguye/dbterd)
|
|
15
|
+
`json`-target shape directly, so a host just hands it data and a couple of
|
|
16
|
+
callbacks — the graph owns no transport and no theme of its own.
|
|
17
|
+
|
|
18
|
+
It powers the ERD in both [dbt-docs](https://github.com/datnguye/dbt-docs) (a
|
|
19
|
+
static docs SPA) and [dbterd-vscode](https://github.com/datnguye/dbterd-vscode)
|
|
20
|
+
(a VS Code webview). Turns out one diagram is enough for two very different
|
|
21
|
+
homes.
|
|
22
|
+
|
|
23
|
+
## Install
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm install @datnguye/erd-flow
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
React, React DOM, `@xyflow/react`, and `@dagrejs/dagre` are **peer
|
|
30
|
+
dependencies** — the package externalizes them so your app owns their versions.
|
|
31
|
+
|
|
32
|
+
## Usage
|
|
33
|
+
|
|
34
|
+
```tsx
|
|
35
|
+
import { ErdFlow } from "@datnguye/erd-flow";
|
|
36
|
+
import "@datnguye/erd-flow/styles.css";
|
|
37
|
+
|
|
38
|
+
export function Diagram({ payload }) {
|
|
39
|
+
return (
|
|
40
|
+
<div style={{ width: "100%", height: "100vh" }}>
|
|
41
|
+
<ErdFlow
|
|
42
|
+
data={payload}
|
|
43
|
+
layout="radial"
|
|
44
|
+
onOpenNode={(node) => open(node.model_path)}
|
|
45
|
+
onNodeActivate={(node) => setDetails(node)}
|
|
46
|
+
labelFor={(node) => node.name}
|
|
47
|
+
/>
|
|
48
|
+
</div>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
`ErdFlow` fills its parent, so give the parent an explicit size.
|
|
54
|
+
|
|
55
|
+
## Props
|
|
56
|
+
|
|
57
|
+
| Prop | Type | Notes |
|
|
58
|
+
|---|---|---|
|
|
59
|
+
| `data` | `ErdPayload` | The nodes/edges to render (dbterd-native shape). |
|
|
60
|
+
| `layout` / `defaultLayout` | `string` | Controlled or initial layout — a built-in (`"hierarchical" \| "radial" \| "force"`) or a name added via `registerLayout`. Unknown names fall back to the default. |
|
|
61
|
+
| `estimateSize` | `(node, visibleColumns, hasToggle) => { width, height }` | Override the layout-box estimate when your CSS changes the card geometry. |
|
|
62
|
+
| `filter` | `string` | Highlights matching tables, dims the rest — never removes them. |
|
|
63
|
+
| `hideUnconnected` | `boolean` | Drops zero-edge island tables. |
|
|
64
|
+
| `focus` / `focusDepth` | `string` / `number` | Render only a node's FK neighbourhood. |
|
|
65
|
+
| `compact` | `boolean` | Show only key (PK/FK) columns per table. |
|
|
66
|
+
| `collapseColumns` | `boolean` | Collapse wide tables to a few rows with a "N more" toggle (default `true`). `false` shows every column — for a focused view where join columns must stay visible. |
|
|
67
|
+
| `expandAll` | `boolean` | Controlled expand-all: forces every collapsible table open (`true`) or closed (`false`). Omit to let per-table toggles work independently. |
|
|
68
|
+
| `onExpandStateChange` | `({ allExpanded, canExpand }) => void` | Reports the aggregate expand state so a host can label/disable its own expand-all button. |
|
|
69
|
+
| `interactive` | `boolean` | Pan / zoom / drag (default `true`). `false` locks the canvas — gate it behind a "click to unlock" affordance. |
|
|
70
|
+
| `onNodeActivate` | `(node \| null) => void` | Header click activates; pane click clears. Render your own details pane. |
|
|
71
|
+
| `onOpenNode` | `(node) => void` | Table double-click — open the backing model. |
|
|
72
|
+
| `resourceMeta` | `Record<string, ResourceMeta>` | Colour/icon per `resource_type`. Merged over dbt defaults. |
|
|
73
|
+
| `labelFor` | `(node) => string` | Display label; defaults to `node.name`. |
|
|
74
|
+
| `theme` | `ErdTheme` | CSS-var overrides (see below). |
|
|
75
|
+
| `colorMode` | `"light" \| "dark" \| "system"` | React Flow colour mode (default `"dark"`). |
|
|
76
|
+
| `minimap` / `controls` / `background` | `boolean` | Toggle the built-in chrome. |
|
|
77
|
+
| `showSchema` | `boolean` | Render `schema_name` right-aligned in each table header (default `false`). |
|
|
78
|
+
| `compositeEdges` | `"bundle" \| "fan"` | Multi-column FK rendering: one bundled edge with per-column tails (default), or one independent edge per column pair. |
|
|
79
|
+
| `edgePath` | `"cubic" \| "smoothstep"` | Path shape for single-column edges (default `"cubic"`). |
|
|
80
|
+
| `animateEdge` | `(edge) => boolean` | Gate the marching-ants dash per edge; default animates every FK edge. |
|
|
81
|
+
| `dimOnSelect` | `boolean` | While an edge is selected, fade every other edge and non-endpoint node (default `false`). |
|
|
82
|
+
| `onlyRenderVisibleElements` | `boolean` | Mount only in-viewport nodes/edges — for very large uncapped ERDs (default `false`). |
|
|
83
|
+
| `fitViewOptions` | `FitViewOptions` | Padding / zoom clamps applied to the initial fit and every re-fit. |
|
|
84
|
+
| `refitKey` | `unknown` | Change it to request a re-fit (e.g. pass your fullscreen state). |
|
|
85
|
+
| `className` | `string` | Extra class name appended to the root element. |
|
|
86
|
+
|
|
87
|
+
## Custom layouts
|
|
88
|
+
|
|
89
|
+
Layouts are a name → engine registry. Register your own and select it by name —
|
|
90
|
+
the engine receives pre-sized nodes (see `estimateSize`) and only decides
|
|
91
|
+
placement:
|
|
92
|
+
|
|
93
|
+
```ts
|
|
94
|
+
import { registerLayout, type LayoutEngine } from "@datnguye/erd-flow";
|
|
95
|
+
|
|
96
|
+
const snowflake: LayoutEngine = (sized, edges, { centerId }) => {
|
|
97
|
+
// return [{ id, x, y, dimensions: { width, height } }, ...]
|
|
98
|
+
};
|
|
99
|
+
registerLayout("snowflake", snowflake);
|
|
100
|
+
|
|
101
|
+
<ErdFlow data={data} layout="snowflake" />
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Theming
|
|
105
|
+
|
|
106
|
+
Every colour is a CSS custom property with a built-in fallback, so the default
|
|
107
|
+
is a usable dark ERD. Map your own tokens either via the `theme` prop or by
|
|
108
|
+
setting the `--erd-*` variables in CSS:
|
|
109
|
+
|
|
110
|
+
```css
|
|
111
|
+
.my-erd {
|
|
112
|
+
--erd-node-bg: var(--vscode-editor-background);
|
|
113
|
+
--erd-border: var(--vscode-focusBorder);
|
|
114
|
+
--erd-accent: var(--vscode-charts-yellow);
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Tokens: `--erd-node-bg`, `--erd-node-fg`, `--erd-border`, `--erd-accent`,
|
|
119
|
+
`--erd-fk`, `--erd-header-bg`, `--erd-hover-bg`, `--erd-muted-fg`,
|
|
120
|
+
`--erd-link-fg`, `--erd-divider`, `--erd-icon`, `--erd-font-mono`, `--erd-edge`,
|
|
121
|
+
`--erd-edge-selected`, `--erd-edge-width`, `--erd-edge-selected-width`,
|
|
122
|
+
`--erd-minimap-bg`, `--erd-minimap-dim`, `--erd-minimap-mask`, `--erd-shadow`.
|
|
123
|
+
|
|
124
|
+
## Data shape
|
|
125
|
+
|
|
126
|
+
The payload uses dbterd's native `json`-target field names — an edge's `from_id`
|
|
127
|
+
is the FK/child side, `to_id` the referenced/parent side:
|
|
128
|
+
|
|
129
|
+
```ts
|
|
130
|
+
interface ErdPayload {
|
|
131
|
+
nodes: ErdNode[]; // { id, name, resource_type?, schema_name?, columns, ... }
|
|
132
|
+
edges: ErdEdge[]; // { id, from_id, to_id, from_columns?, to_columns?, ... }
|
|
133
|
+
metadata?: ErdMetadata;
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
`toFlowGraph`, `windowPayload`, `erdNeighborhood`, and `compactColumns` are
|
|
138
|
+
exported for hosts that want to pre-process the payload themselves.
|
|
139
|
+
|
|
140
|
+
## Development
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
npm install
|
|
144
|
+
npm run build # tsc -b && vite build → dist/ (ESM + .d.ts + css)
|
|
145
|
+
npm test # vitest
|
|
146
|
+
npm run typecheck
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## License
|
|
150
|
+
|
|
151
|
+
MIT © Dat Nguyen
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
.erd-table{background:var(--erd-node-bg, #1e1e1e);color:var(--erd-node-fg, #d4d4d4);border:1px solid var(--erd-border, #007acc);border-radius:6px;font-family:var(--erd-font-mono, monospace);font-size:12px;min-width:220px;box-shadow:0 1px 3px var(--erd-shadow, rgba(0, 0, 0, .25))}.erd-table[data-resource=source]{border-style:dashed}.erd-table[data-filter=match]{border-color:var(--erd-accent, #e5c07b);box-shadow:0 0 0 2px var(--erd-accent, #e5c07b),0 1px 4px var(--erd-shadow, rgba(0, 0, 0, .35))}.erd-table[data-filter=dim]{opacity:.32;filter:saturate(.6);transition:opacity .12s ease,filter .12s ease}.erd-table[data-filter=dim]:hover{opacity:.85;filter:none}.erd-table[data-active=true]{border-color:var(--erd-border, #007acc);box-shadow:0 0 0 2px var(--erd-border, #007acc),0 2px 8px var(--erd-shadow, rgba(0, 0, 0, .4))}.erd-table[data-active=true][data-filter=match]{box-shadow:0 0 0 2px var(--erd-border, #007acc),inset 0 0 0 1px var(--erd-accent, #e5c07b)}.erd-table-header{display:flex;align-items:center;gap:6px;box-sizing:border-box;height:32px;padding:6px 10px;background:var(--erd-header-bg, #252526);border-bottom:1px solid var(--erd-border, #007acc);cursor:pointer;-webkit-user-select:none;user-select:none}.erd-table-header:hover{background:var(--erd-hover-bg, #2a2d2e)}.erd-table-header:focus-visible{outline:2px solid var(--erd-border, #007acc);outline-offset:-2px}.erd-table-name{font-weight:600}.erd-table-schema{margin-left:auto;color:var(--erd-muted-fg, #808080);font-size:11px}.erd-table-columns{list-style:none;margin:0;padding:4px 0}.erd-column{display:grid;grid-template-columns:28px 1fr auto;align-items:center;gap:6px;box-sizing:border-box;height:22px;padding:0 10px;position:relative}.erd-column[data-highlighted=true]{background:color-mix(in srgb,var(--erd-accent, #e5c07b) 16%,transparent);box-shadow:inset 2px 0 0 var(--erd-accent, #e5c07b)}.erd-column[data-highlighted=true] .erd-column-name{color:var(--erd-accent, #e5c07b);font-weight:600}.erd-column-badge{font-size:10px;font-weight:700;text-align:center}.badge-pk{color:var(--erd-accent, #e5c07b)}.badge-fk{color:var(--erd-fk, #61afef)}.badge-none{color:transparent}.erd-column-type{color:var(--erd-muted-fg, #808080)}.erd-table-icon{display:inline-flex;align-items:center;color:var(--erd-icon, #FF694A)}.erd-table-more{box-sizing:border-box;height:22px;padding:0 10px;border-top:1px solid var(--erd-divider, #3c3c3c);display:flex;align-items:center;color:var(--erd-muted-fg, #808080);font-size:11px;font-style:italic}.erd-table[data-dimmed=true]{opacity:.25;transition:opacity .12s ease}.erd-table-expand{display:block;width:100%;box-sizing:border-box;height:24px;padding:0 10px;background:transparent;color:var(--erd-link-fg, #3794ff);border:none;border-top:1px dashed var(--erd-divider, #3c3c3c);text-align:center;cursor:pointer;font-size:11px;font-family:inherit}.erd-table-expand:hover{background:var(--erd-hover-bg, #2a2d2e)}.erd-table .react-flow__handle.erd-hidden-handle{width:1px;height:1px;min-width:0;min-height:0;border:none;background:transparent;opacity:0;pointer-events:none}.react-flow{direction:ltr;--xy-edge-stroke-default: #b1b1b7;--xy-edge-stroke-width-default: 1;--xy-edge-stroke-selected-default: #555;--xy-connectionline-stroke-default: #b1b1b7;--xy-connectionline-stroke-width-default: 1;--xy-attribution-background-color-default: rgba(255, 255, 255, .5);--xy-minimap-background-color-default: #fff;--xy-minimap-mask-background-color-default: rgba(240, 240, 240, .6);--xy-minimap-mask-stroke-color-default: transparent;--xy-minimap-mask-stroke-width-default: 1;--xy-minimap-node-background-color-default: #e2e2e2;--xy-minimap-node-stroke-color-default: transparent;--xy-minimap-node-stroke-width-default: 2;--xy-background-color-default: transparent;--xy-background-pattern-dots-color-default: #91919a;--xy-background-pattern-lines-color-default: #eee;--xy-background-pattern-cross-color-default: #e2e2e2;background-color:var(--xy-background-color, var(--xy-background-color-default));--xy-node-color-default: inherit;--xy-node-border-default: 1px solid #1a192b;--xy-node-background-color-default: #fff;--xy-node-group-background-color-default: rgba(240, 240, 240, .25);--xy-node-boxshadow-hover-default: 0 1px 4px 1px rgba(0, 0, 0, .08);--xy-node-boxshadow-selected-default: 0 0 0 .5px #1a192b;--xy-node-border-radius-default: 3px;--xy-handle-background-color-default: #1a192b;--xy-handle-border-color-default: #fff;--xy-selection-background-color-default: rgba(0, 89, 220, .08);--xy-selection-border-default: 1px dotted rgba(0, 89, 220, .8);--xy-controls-button-background-color-default: #fefefe;--xy-controls-button-background-color-hover-default: #f4f4f4;--xy-controls-button-color-default: inherit;--xy-controls-button-color-hover-default: inherit;--xy-controls-button-border-color-default: #eee;--xy-controls-box-shadow-default: 0 0 2px 1px rgba(0, 0, 0, .08);--xy-edge-label-background-color-default: #ffffff;--xy-edge-label-color-default: inherit;--xy-resize-background-color-default: #3367d9}.react-flow.dark{--xy-edge-stroke-default: #3e3e3e;--xy-edge-stroke-width-default: 1;--xy-edge-stroke-selected-default: #727272;--xy-connectionline-stroke-default: #b1b1b7;--xy-connectionline-stroke-width-default: 1;--xy-attribution-background-color-default: rgba(150, 150, 150, .25);--xy-minimap-background-color-default: #141414;--xy-minimap-mask-background-color-default: rgba(60, 60, 60, .6);--xy-minimap-mask-stroke-color-default: transparent;--xy-minimap-mask-stroke-width-default: 1;--xy-minimap-node-background-color-default: #2b2b2b;--xy-minimap-node-stroke-color-default: transparent;--xy-minimap-node-stroke-width-default: 2;--xy-background-color-default: #141414;--xy-background-pattern-dots-color-default: #777;--xy-background-pattern-lines-color-default: #777;--xy-background-pattern-cross-color-default: #777;--xy-node-color-default: #f8f8f8;--xy-node-border-default: 1px solid #3c3c3c;--xy-node-background-color-default: #1e1e1e;--xy-node-group-background-color-default: rgba(240, 240, 240, .25);--xy-node-boxshadow-hover-default: 0 1px 4px 1px rgba(255, 255, 255, .08);--xy-node-boxshadow-selected-default: 0 0 0 .5px #999;--xy-handle-background-color-default: #bebebe;--xy-handle-border-color-default: #1e1e1e;--xy-selection-background-color-default: rgba(200, 200, 220, .08);--xy-selection-border-default: 1px dotted rgba(200, 200, 220, .8);--xy-controls-button-background-color-default: #2b2b2b;--xy-controls-button-background-color-hover-default: #3e3e3e;--xy-controls-button-color-default: #f8f8f8;--xy-controls-button-color-hover-default: #fff;--xy-controls-button-border-color-default: #5b5b5b;--xy-controls-box-shadow-default: 0 0 2px 1px rgba(0, 0, 0, .08);--xy-edge-label-background-color-default: #141414;--xy-edge-label-color-default: #f8f8f8}.react-flow__background{background-color:var(--xy-background-color-props, var(--xy-background-color, var(--xy-background-color-default)));pointer-events:none;z-index:-1}.react-flow__container{position:absolute;width:100%;height:100%;top:0;left:0}.react-flow__pane{z-index:1;touch-action:none}.react-flow__pane.draggable{cursor:grab}.react-flow__pane.dragging{cursor:grabbing}.react-flow__pane.selection{cursor:pointer}.react-flow__viewport{transform-origin:0 0;z-index:2;pointer-events:none}.react-flow__renderer{z-index:4}.react-flow__selection{z-index:6}.react-flow__nodesselection-rect:focus,.react-flow__nodesselection-rect:focus-visible{outline:none}.react-flow__edge-path{stroke:var(--xy-edge-stroke, var(--xy-edge-stroke-default));stroke-width:var(--xy-edge-stroke-width, var(--xy-edge-stroke-width-default));fill:none}.react-flow__connection-path{stroke:var(--xy-connectionline-stroke, var(--xy-connectionline-stroke-default));stroke-width:var(--xy-connectionline-stroke-width, var(--xy-connectionline-stroke-width-default));fill:none}.react-flow .react-flow__edges{position:absolute}.react-flow .react-flow__edges svg{overflow:visible;position:absolute;pointer-events:none}.react-flow__edge{pointer-events:visibleStroke}.react-flow__edge.selectable{cursor:pointer}.react-flow__edge.animated path{stroke-dasharray:5;animation:dashdraw .5s linear infinite}.react-flow__edge.animated path.react-flow__edge-interaction{stroke-dasharray:none;animation:none}.react-flow__edge.inactive{pointer-events:none}.react-flow__edge.selected,.react-flow__edge:focus,.react-flow__edge:focus-visible{outline:none}.react-flow__edge.selected .react-flow__edge-path,.react-flow__edge.selectable:focus .react-flow__edge-path,.react-flow__edge.selectable:focus-visible .react-flow__edge-path{stroke:var(--xy-edge-stroke-selected, var(--xy-edge-stroke-selected-default))}.react-flow__edge-textwrapper{pointer-events:all}.react-flow__edge .react-flow__edge-text{pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none}.react-flow__arrowhead polyline{stroke:var(--xy-edge-stroke, var(--xy-edge-stroke-default))}.react-flow__arrowhead polyline.arrowclosed{fill:var(--xy-edge-stroke, var(--xy-edge-stroke-default))}.react-flow__connection{pointer-events:none}.react-flow__connection .animated{stroke-dasharray:5;animation:dashdraw .5s linear infinite}svg.react-flow__connectionline{z-index:1001;overflow:visible;position:absolute}.react-flow__nodes{pointer-events:none;transform-origin:0 0}.react-flow__node{position:absolute;-webkit-user-select:none;-moz-user-select:none;user-select:none;pointer-events:all;transform-origin:0 0;box-sizing:border-box;cursor:default}.react-flow__node.selectable{cursor:pointer}.react-flow__node.draggable{cursor:grab;pointer-events:all}.react-flow__node.draggable.dragging{cursor:grabbing}.react-flow__nodesselection{z-index:3;transform-origin:left top;pointer-events:none}.react-flow__nodesselection-rect{position:absolute;pointer-events:all;cursor:grab}.react-flow__handle{position:absolute;pointer-events:none;min-width:5px;min-height:5px;width:6px;height:6px;background-color:var(--xy-handle-background-color, var(--xy-handle-background-color-default));border:1px solid var(--xy-handle-border-color, var(--xy-handle-border-color-default));border-radius:100%}.react-flow__handle.connectingfrom{pointer-events:all}.react-flow__handle.connectionindicator{pointer-events:all;cursor:crosshair}.react-flow__handle-bottom{top:auto;left:50%;bottom:0;transform:translate(-50%,50%)}.react-flow__handle-top{top:0;left:50%;transform:translate(-50%,-50%)}.react-flow__handle-left{top:50%;left:0;transform:translate(-50%,-50%)}.react-flow__handle-right{top:50%;right:0;transform:translate(50%,-50%)}.react-flow__edgeupdater{cursor:move;pointer-events:all}.react-flow__pane.selection .react-flow__panel{pointer-events:none}.react-flow__panel{position:absolute;z-index:5;margin:15px}.react-flow__panel.top{top:0}.react-flow__panel.bottom{bottom:0}.react-flow__panel.top.center,.react-flow__panel.bottom.center{left:50%;transform:translate(-15px) translate(-50%)}.react-flow__panel.left{left:0}.react-flow__panel.right{right:0}.react-flow__panel.left.center,.react-flow__panel.right.center{top:50%;transform:translateY(-15px) translateY(-50%)}.react-flow__attribution{font-size:10px;background:var(--xy-attribution-background-color, var(--xy-attribution-background-color-default));padding:2px 3px;margin:0}.react-flow__attribution a{text-decoration:none;color:#999}@keyframes dashdraw{0%{stroke-dashoffset:10}}.react-flow__edgelabel-renderer{position:absolute;width:100%;height:100%;pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none;left:0;top:0}.react-flow__viewport-portal{position:absolute;width:100%;height:100%;left:0;top:0;-webkit-user-select:none;-moz-user-select:none;user-select:none}.react-flow__minimap{background:var( --xy-minimap-background-color-props, var(--xy-minimap-background-color, var(--xy-minimap-background-color-default)) )}.react-flow__minimap-svg{display:block}.react-flow__minimap-mask{fill:var( --xy-minimap-mask-background-color-props, var(--xy-minimap-mask-background-color, var(--xy-minimap-mask-background-color-default)) );stroke:var( --xy-minimap-mask-stroke-color-props, var(--xy-minimap-mask-stroke-color, var(--xy-minimap-mask-stroke-color-default)) );stroke-width:var( --xy-minimap-mask-stroke-width-props, var(--xy-minimap-mask-stroke-width, var(--xy-minimap-mask-stroke-width-default)) )}.react-flow__minimap-node{fill:var( --xy-minimap-node-background-color-props, var(--xy-minimap-node-background-color, var(--xy-minimap-node-background-color-default)) );stroke:var( --xy-minimap-node-stroke-color-props, var(--xy-minimap-node-stroke-color, var(--xy-minimap-node-stroke-color-default)) );stroke-width:var( --xy-minimap-node-stroke-width-props, var(--xy-minimap-node-stroke-width, var(--xy-minimap-node-stroke-width-default)) )}.react-flow__background-pattern.dots{fill:var( --xy-background-pattern-color-props, var(--xy-background-pattern-color, var(--xy-background-pattern-dots-color-default)) )}.react-flow__background-pattern.lines{stroke:var( --xy-background-pattern-color-props, var(--xy-background-pattern-color, var(--xy-background-pattern-lines-color-default)) )}.react-flow__background-pattern.cross{stroke:var( --xy-background-pattern-color-props, var(--xy-background-pattern-color, var(--xy-background-pattern-cross-color-default)) )}.react-flow__controls{display:flex;flex-direction:column;box-shadow:var(--xy-controls-box-shadow, var(--xy-controls-box-shadow-default))}.react-flow__controls.horizontal{flex-direction:row}.react-flow__controls-button{display:flex;justify-content:center;align-items:center;height:26px;width:26px;padding:4px;border:none;background:var(--xy-controls-button-background-color, var(--xy-controls-button-background-color-default));border-bottom:1px solid var( --xy-controls-button-border-color-props, var(--xy-controls-button-border-color, var(--xy-controls-button-border-color-default)) );color:var( --xy-controls-button-color-props, var(--xy-controls-button-color, var(--xy-controls-button-color-default)) );cursor:pointer;-webkit-user-select:none;-moz-user-select:none;user-select:none}.react-flow__controls-button svg{width:100%;max-width:12px;max-height:12px;fill:currentColor}.react-flow__edge.updating .react-flow__edge-path{stroke:#777}.react-flow__edge-text{font-size:10px}.react-flow__node.selectable:focus,.react-flow__node.selectable:focus-visible{outline:none}.react-flow__node-input,.react-flow__node-default,.react-flow__node-output,.react-flow__node-group{padding:10px;border-radius:var(--xy-node-border-radius, var(--xy-node-border-radius-default));width:150px;font-size:12px;color:var(--xy-node-color, var(--xy-node-color-default));text-align:center;border:var(--xy-node-border, var(--xy-node-border-default));background-color:var(--xy-node-background-color, var(--xy-node-background-color-default))}.react-flow__node-input.selectable:hover,.react-flow__node-default.selectable:hover,.react-flow__node-output.selectable:hover,.react-flow__node-group.selectable:hover{box-shadow:var(--xy-node-boxshadow-hover, var(--xy-node-boxshadow-hover-default))}.react-flow__node-input.selectable.selected,.react-flow__node-input.selectable:focus,.react-flow__node-input.selectable:focus-visible,.react-flow__node-default.selectable.selected,.react-flow__node-default.selectable:focus,.react-flow__node-default.selectable:focus-visible,.react-flow__node-output.selectable.selected,.react-flow__node-output.selectable:focus,.react-flow__node-output.selectable:focus-visible,.react-flow__node-group.selectable.selected,.react-flow__node-group.selectable:focus,.react-flow__node-group.selectable:focus-visible{box-shadow:var(--xy-node-boxshadow-selected, var(--xy-node-boxshadow-selected-default))}.react-flow__node-group{background-color:var(--xy-node-group-background-color, var(--xy-node-group-background-color-default))}.react-flow__nodesselection-rect,.react-flow__selection{background:var(--xy-selection-background-color, var(--xy-selection-background-color-default));border:var(--xy-selection-border, var(--xy-selection-border-default))}.react-flow__nodesselection-rect:focus,.react-flow__nodesselection-rect:focus-visible,.react-flow__selection:focus,.react-flow__selection:focus-visible{outline:none}.react-flow__controls-button:hover{background:var( --xy-controls-button-background-color-hover-props, var(--xy-controls-button-background-color-hover, var(--xy-controls-button-background-color-hover-default)) );color:var( --xy-controls-button-color-hover-props, var(--xy-controls-button-color-hover, var(--xy-controls-button-color-hover-default)) )}.react-flow__controls-button:disabled{pointer-events:none}.react-flow__controls-button:disabled svg{fill-opacity:.4}.react-flow__controls-button:last-child{border-bottom:none}.react-flow__controls.horizontal .react-flow__controls-button{border-bottom:none;border-right:1px solid var( --xy-controls-button-border-color-props, var(--xy-controls-button-border-color, var(--xy-controls-button-border-color-default)) )}.react-flow__controls.horizontal .react-flow__controls-button:last-child{border-right:none}.react-flow__resize-control{position:absolute}.react-flow__resize-control.left,.react-flow__resize-control.right{cursor:ew-resize}.react-flow__resize-control.top,.react-flow__resize-control.bottom{cursor:ns-resize}.react-flow__resize-control.top.left,.react-flow__resize-control.bottom.right{cursor:nwse-resize}.react-flow__resize-control.bottom.left,.react-flow__resize-control.top.right{cursor:nesw-resize}.react-flow__resize-control.handle{width:5px;height:5px;border:1px solid #fff;border-radius:1px;background-color:var(--xy-resize-background-color, var(--xy-resize-background-color-default));translate:-50% -50%}.react-flow__resize-control.handle.left{left:0;top:50%}.react-flow__resize-control.handle.right{left:100%;top:50%}.react-flow__resize-control.handle.top{left:50%;top:0}.react-flow__resize-control.handle.bottom{left:50%;top:100%}.react-flow__resize-control.handle.top.left,.react-flow__resize-control.handle.bottom.left{left:0}.react-flow__resize-control.handle.top.right,.react-flow__resize-control.handle.bottom.right{left:100%}.react-flow__resize-control.line{border-color:var(--xy-resize-background-color, var(--xy-resize-background-color-default));border-width:0;border-style:solid}.react-flow__resize-control.line.left,.react-flow__resize-control.line.right{width:1px;transform:translate(-50%);top:0;height:100%}.react-flow__resize-control.line.left{left:0;border-left-width:1px}.react-flow__resize-control.line.right{left:100%;border-right-width:1px}.react-flow__resize-control.line.top,.react-flow__resize-control.line.bottom{height:1px;transform:translateY(-50%);left:0;width:100%}.react-flow__resize-control.line.top{top:0;border-top-width:1px}.react-flow__resize-control.line.bottom{border-bottom-width:1px;top:100%}.react-flow__edge-textbg{fill:var(--xy-edge-label-background-color, var(--xy-edge-label-background-color-default))}.react-flow__edge-text{fill:var(--xy-edge-label-color, var(--xy-edge-label-color-default))}.erd-flow{width:100%;height:100%;min-height:0;position:relative}.erd-flow .react-flow{width:100%;height:100%}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { CSSProperties } from 'react';
|
|
2
|
+
import { Edge } from '@xyflow/react';
|
|
3
|
+
import { FitViewOptions } from '@xyflow/react';
|
|
4
|
+
import { Node as Node_2 } from '@xyflow/react';
|
|
5
|
+
import { ReactElement } from 'react';
|
|
6
|
+
|
|
7
|
+
export declare interface Column {
|
|
8
|
+
name: string;
|
|
9
|
+
data_type?: string | null;
|
|
10
|
+
description?: string | null;
|
|
11
|
+
is_primary_key?: boolean;
|
|
12
|
+
is_foreign_key?: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export declare function compactColumns(columns: Column[]): Column[];
|
|
16
|
+
|
|
17
|
+
export declare const DEFAULT_LAYOUT: LayoutStyle;
|
|
18
|
+
|
|
19
|
+
export declare const DEFAULT_RESOURCE_META: Record<string, ResourceMeta>;
|
|
20
|
+
|
|
21
|
+
export declare interface ErdEdge {
|
|
22
|
+
id: string;
|
|
23
|
+
from_id: string;
|
|
24
|
+
to_id: string;
|
|
25
|
+
from_column?: string | null;
|
|
26
|
+
to_column?: string | null;
|
|
27
|
+
from_columns?: string[];
|
|
28
|
+
to_columns?: string[];
|
|
29
|
+
relationship_type?: string;
|
|
30
|
+
cardinality?: string;
|
|
31
|
+
label?: string | null;
|
|
32
|
+
name?: string | null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export declare function ErdFlow(props: ErdFlowProps): ReactElement;
|
|
36
|
+
|
|
37
|
+
export declare type ErdFlowNode = Node_2<ErdNodeData, "erdTable">;
|
|
38
|
+
|
|
39
|
+
export declare interface ErdFlowProps {
|
|
40
|
+
data: ErdPayload;
|
|
41
|
+
layout?: string;
|
|
42
|
+
defaultLayout?: string;
|
|
43
|
+
estimateSize?: EstimateSize;
|
|
44
|
+
filter?: string;
|
|
45
|
+
hideUnconnected?: boolean;
|
|
46
|
+
focus?: string | null;
|
|
47
|
+
focusDepth?: number;
|
|
48
|
+
compact?: boolean;
|
|
49
|
+
collapseColumns?: boolean;
|
|
50
|
+
expandAll?: boolean;
|
|
51
|
+
onExpandStateChange?: (state: {
|
|
52
|
+
allExpanded: boolean;
|
|
53
|
+
canExpand: boolean;
|
|
54
|
+
}) => void;
|
|
55
|
+
interactive?: boolean;
|
|
56
|
+
onNodeActivate?: (node: ErdNode | null) => void;
|
|
57
|
+
onOpenNode?: (node: ErdNode) => void;
|
|
58
|
+
resourceMeta?: Record<string, ResourceMeta>;
|
|
59
|
+
labelFor?: (node: ErdNode) => string;
|
|
60
|
+
theme?: ErdTheme;
|
|
61
|
+
colorMode?: "light" | "dark" | "system";
|
|
62
|
+
minimap?: boolean;
|
|
63
|
+
controls?: boolean;
|
|
64
|
+
background?: boolean;
|
|
65
|
+
showSchema?: boolean;
|
|
66
|
+
compositeEdges?: "bundle" | "fan";
|
|
67
|
+
edgePath?: "cubic" | "smoothstep";
|
|
68
|
+
animateEdge?: (edge: ErdEdge) => boolean;
|
|
69
|
+
dimOnSelect?: boolean;
|
|
70
|
+
onlyRenderVisibleElements?: boolean;
|
|
71
|
+
fitViewOptions?: FitViewOptions;
|
|
72
|
+
refitKey?: unknown;
|
|
73
|
+
className?: string;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export declare interface ErdMetadata {
|
|
77
|
+
generated_at?: string;
|
|
78
|
+
dbt_project_name?: string;
|
|
79
|
+
[key: string]: unknown;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export declare function erdNeighborhood(payload: ErdPayload, focus: string, depth?: number): Set<string>;
|
|
83
|
+
|
|
84
|
+
export declare interface ErdNode {
|
|
85
|
+
id: string;
|
|
86
|
+
name: string;
|
|
87
|
+
label?: string | null;
|
|
88
|
+
description?: string | null;
|
|
89
|
+
resource_type?: string;
|
|
90
|
+
schema_name?: string | null;
|
|
91
|
+
database?: string | null;
|
|
92
|
+
columns: Column[];
|
|
93
|
+
hidden_column_count?: number;
|
|
94
|
+
compiled_sql?: string | null;
|
|
95
|
+
model_path?: string | null;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export declare type ErdNodeData = ErdNode & Record<string, unknown>;
|
|
99
|
+
|
|
100
|
+
export declare interface ErdPayload {
|
|
101
|
+
nodes: ErdNode[];
|
|
102
|
+
edges: ErdEdge[];
|
|
103
|
+
metadata?: ErdMetadata;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export declare interface ErdTheme {
|
|
107
|
+
nodeBg?: string;
|
|
108
|
+
nodeFg?: string;
|
|
109
|
+
border?: string;
|
|
110
|
+
accent?: string;
|
|
111
|
+
fk?: string;
|
|
112
|
+
headerBg?: string;
|
|
113
|
+
hoverBg?: string;
|
|
114
|
+
mutedFg?: string;
|
|
115
|
+
linkFg?: string;
|
|
116
|
+
divider?: string;
|
|
117
|
+
icon?: string;
|
|
118
|
+
fontMono?: string;
|
|
119
|
+
edge?: string;
|
|
120
|
+
edgeSelected?: string;
|
|
121
|
+
edgeWidth?: string;
|
|
122
|
+
edgeSelectedWidth?: string;
|
|
123
|
+
minimapBg?: string;
|
|
124
|
+
minimapDim?: string;
|
|
125
|
+
minimapMask?: string;
|
|
126
|
+
shadow?: string;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export declare type EstimateSize = (node: ErdNode, visibleColumns: number, hasToggle: boolean) => TableDimensions;
|
|
130
|
+
|
|
131
|
+
export declare interface FlowGraph {
|
|
132
|
+
nodes: ErdFlowNode[];
|
|
133
|
+
edges: Edge[];
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export declare function isLayoutStyle(value: unknown): value is LayoutStyle;
|
|
137
|
+
|
|
138
|
+
export declare interface LaidOutNode {
|
|
139
|
+
id: string;
|
|
140
|
+
x: number;
|
|
141
|
+
y: number;
|
|
142
|
+
dimensions: TableDimensions;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export declare const LAYOUT_STYLES: readonly ["hierarchical", "radial", "force"];
|
|
146
|
+
|
|
147
|
+
export declare type LayoutEngine = (sized: readonly SizedErdNode[], edges: readonly ErdEdge[], opts: LayoutEngineOptions) => LaidOutNode[];
|
|
148
|
+
|
|
149
|
+
export declare interface LayoutEngineOptions {
|
|
150
|
+
centerId?: string | null;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export declare type LayoutStyle = (typeof LAYOUT_STYLES)[number];
|
|
154
|
+
|
|
155
|
+
export declare function measureNodes(nodes: readonly ErdNode[], collapse?: boolean, estimateSize?: EstimateSize): SizedErdNode[];
|
|
156
|
+
|
|
157
|
+
export declare function registerLayout(name: string, engine: LayoutEngine): void;
|
|
158
|
+
|
|
159
|
+
export declare function resolveLayout(name: string): LayoutEngine;
|
|
160
|
+
|
|
161
|
+
export declare interface ResourceMeta {
|
|
162
|
+
color?: string;
|
|
163
|
+
icon?: "database" | "table";
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export declare type SizedErdNode = ErdNode & TableDimensions;
|
|
167
|
+
|
|
168
|
+
export declare interface TableDimensions {
|
|
169
|
+
width: number;
|
|
170
|
+
height: number;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export declare function themeStyle(theme?: ErdTheme): CSSProperties;
|
|
174
|
+
|
|
175
|
+
export declare function toFlowGraph(payload: ErdPayload, style?: string, collapse?: boolean, options?: ToFlowGraphOptions): FlowGraph;
|
|
176
|
+
|
|
177
|
+
export declare interface ToFlowGraphOptions {
|
|
178
|
+
centerId?: string | null;
|
|
179
|
+
estimateSize?: EstimateSize;
|
|
180
|
+
compositeEdges?: "bundle" | "fan";
|
|
181
|
+
edgePath?: "cubic" | "smoothstep";
|
|
182
|
+
animateEdge?: (edge: ErdEdge) => boolean;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export declare interface WindowOptions {
|
|
186
|
+
focus?: string | null;
|
|
187
|
+
focusDepth?: number;
|
|
188
|
+
compact?: boolean;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export declare function windowPayload(payload: ErdPayload, options: WindowOptions): ErdPayload;
|
|
192
|
+
|
|
193
|
+
export { }
|