@msdshsk/react-er-canvas 0.0.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.
Files changed (41) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE.md +48 -0
  3. package/README.ja.md +216 -0
  4. package/README.md +216 -0
  5. package/dist/components/JoinEdge.d.ts +10 -0
  6. package/dist/components/JoinEdge.d.ts.map +1 -0
  7. package/dist/components/MermaidER.d.ts +46 -0
  8. package/dist/components/MermaidER.d.ts.map +1 -0
  9. package/dist/components/TableNode.d.ts +30 -0
  10. package/dist/components/TableNode.d.ts.map +1 -0
  11. package/dist/core/index.d.ts +5 -0
  12. package/dist/core/index.d.ts.map +1 -0
  13. package/dist/core/layout.d.ts +58 -0
  14. package/dist/core/layout.d.ts.map +1 -0
  15. package/dist/core/layout.test.d.ts +2 -0
  16. package/dist/core/layout.test.d.ts.map +1 -0
  17. package/dist/core/model.d.ts +55 -0
  18. package/dist/core/model.d.ts.map +1 -0
  19. package/dist/core/parser/grammar.d.ts +22 -0
  20. package/dist/core/parser/grammar.d.ts.map +1 -0
  21. package/dist/core/parser/index.d.ts +15 -0
  22. package/dist/core/parser/index.d.ts.map +1 -0
  23. package/dist/core/parser/lexer.d.ts +18 -0
  24. package/dist/core/parser/lexer.d.ts.map +1 -0
  25. package/dist/core/parser/parser.test.d.ts +2 -0
  26. package/dist/core/parser/parser.test.d.ts.map +1 -0
  27. package/dist/core/parser/resolve.test.d.ts +2 -0
  28. package/dist/core/parser/resolve.test.d.ts.map +1 -0
  29. package/dist/core-BtdV83x9.cjs +2 -0
  30. package/dist/core-BtdV83x9.cjs.map +1 -0
  31. package/dist/core-DZ30VgUT.js +345 -0
  32. package/dist/core-DZ30VgUT.js.map +1 -0
  33. package/dist/core.cjs +1 -0
  34. package/dist/core.js +2 -0
  35. package/dist/index.cjs +2 -0
  36. package/dist/index.cjs.map +1 -0
  37. package/dist/index.d.ts +6 -0
  38. package/dist/index.d.ts.map +1 -0
  39. package/dist/index.js +592 -0
  40. package/dist/index.js.map +1 -0
  41. package/package.json +92 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 msdsh
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/NOTICE.md ADDED
@@ -0,0 +1,48 @@
1
+ # NOTICE
2
+
3
+ `@msdshsk/react-er-canvas` is distributed under the **MIT License** (see [LICENSE](./LICENSE)).
4
+
5
+ It uses the following third-party libraries, each governed by its own license. Full license texts are distributed with each dependency's npm package.
6
+
7
+ ## Direct dependencies
8
+
9
+ These libraries are installed alongside this package when consumers run `npm install @msdshsk/react-er-canvas`. They are referenced as `external` modules in the bundled output (`dist/`), so no portion of their source code is embedded in this project's distributed artifacts.
10
+
11
+ | Library | Version | License | Copyright |
12
+ |---|---|---|---|
13
+ | [`chevrotain`](https://github.com/Chevrotain/chevrotain) | `^12.0.0` | [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0) | Copyright (c) Shahar Soel and contributors |
14
+ | [`elkjs`](https://github.com/kieler/elkjs) | `^0.11.1` | [EPL-2.0](https://www.eclipse.org/legal/epl-2.0/) | Copyright (c) Eclipse Layout Kernel contributors |
15
+
16
+ ### chevrotain — Apache License 2.0
17
+
18
+ Used as an unmodified runtime dependency for ER-syntax parsing. Apache-2.0 permits incorporation into MIT-licensed works provided that the original license and any NOTICE files are preserved with redistribution. The chevrotain npm package ships its full Apache-2.0 license text alongside its source, satisfying this requirement under standard npm distribution.
19
+
20
+ ### elkjs — Eclipse Public License 2.0
21
+
22
+ Used as an unmodified runtime dependency for diagram layout computation. Under EPL-2.0:
23
+
24
+ - This project does **not** modify, fork, or otherwise alter `elkjs` source code.
25
+ - This project does **not** bundle `elkjs` into its compiled output. The Vite library configuration explicitly excludes `elkjs` (and any subpaths matching `/^elkjs(\/.*)?$/`) from the build via `rollupOptions.external`.
26
+ - Consumers receive `elkjs` through the standard npm dependency mechanism, with the EPL-2.0 license text preserved in the upstream package.
27
+
28
+ If you fork this project and modify `elkjs` or bundle its source into a redistributable artifact (e.g., a single-file CDN build or an Electron `asar` package), additional EPL-2.0 obligations apply — including making the modified `elkjs` source available under EPL-2.0.
29
+
30
+ ## Peer dependencies
31
+
32
+ Consumers must provide these themselves; they are not installed by this package.
33
+
34
+ | Library | Range | License |
35
+ |---|---|---|
36
+ | [`@xyflow/react`](https://github.com/xyflow/xyflow) | `>=12 <13` | MIT |
37
+ | [`react`](https://github.com/facebook/react) | `^18.2.0 \|\| ^19.0.0` | MIT |
38
+ | [`react-dom`](https://github.com/facebook/react) | `^18.2.0 \|\| ^19.0.0` | MIT |
39
+
40
+ All peer dependencies are MIT-licensed and impose no additional attribution requirements on this project.
41
+
42
+ ## Compliance summary
43
+
44
+ - All dependency licenses (MIT, Apache-2.0, EPL-2.0) are compatible with MIT redistribution under this project's standard usage pattern (unmodified, externalized, npm-distributed).
45
+ - No third-party source code is embedded in `dist/`.
46
+ - License texts and copyright notices for each dependency travel with their respective npm packages.
47
+
48
+ If you redistribute this library in a form that bundles its dependencies — for example, a browser CDN build, a single-file bundle, or an Electron application packaged via `asar` — you should include the relevant license texts directly. For unmodified npm-installed usage, the npm distribution mechanism satisfies attribution requirements.
package/README.ja.md ADDED
@@ -0,0 +1,216 @@
1
+ # @msdshsk/react-er-canvas
2
+
3
+ [English README](./README.md)
4
+
5
+ React / Electron 上で Mermaid 形式の ER 図を描画する Reactコンポーネントライブラリ。Mermaid 標準のレンダラには無い以下の視覚的拡張機能を備えています:
6
+
7
+ - **カラム単位の FK 接続線** (`users.id` から `orders.user_id` へ、テーブル間ではなく該当カラム行同士を直接結ぶ)
8
+ - **テーブルカードのドラッグ移動** (`positions` / `onPositionsChange` で位置の永続化が可能)
9
+ - **ホバー時の FK 参照ハイライト**
10
+ - **PK / FK / UK / 型 / コメント** をカラム単位で描画
11
+ - **スキーマグループ** (`%% @group` ディレクティブ ─ 標準 Mermaid と互換性のあるコメント形式)
12
+ - **手動 JOIN 作成** (カラム同士のドラッグ&ドロップ。クエリビルダ UI 用途)
13
+ - **カラム選択** (チェックボックス。クエリビルダ UI 用途)
14
+
15
+ [`@xyflow/react`](https://reactflow.dev/) と [`elkjs`](https://github.com/kieler/elkjs)、自前実装の [`chevrotain`](https://chevrotain.io/) パーサで構成されています。
16
+
17
+ ## インストール
18
+
19
+ ```sh
20
+ npm install @msdshsk/react-er-canvas @xyflow/react react react-dom
21
+ ```
22
+
23
+ `@xyflow/react`、`react`、`react-dom` は **peer dependency** なので、利用側のアプリで明示的にインストールしてください。
24
+
25
+ 加えて、React Flow のスタイルシートを利用側アプリで **一度** import する必要があります:
26
+
27
+ ```ts
28
+ import '@xyflow/react/dist/style.css';
29
+ ```
30
+
31
+ ## 使い方 ─ Mermaid ソースから
32
+
33
+ ```tsx
34
+ import { MermaidER } from '@msdshsk/react-er-canvas';
35
+
36
+ const source = `
37
+ erDiagram
38
+ CUSTOMER ||--o{ ORDER : places
39
+ CUSTOMER {
40
+ int id PK "Customer ID"
41
+ string name
42
+ string email UK
43
+ }
44
+ ORDER {
45
+ int id PK
46
+ int customer_id FK
47
+ decimal amount
48
+ }
49
+ `;
50
+
51
+ export function Schema() {
52
+ return <div style={{ height: '100vh' }}><MermaidER source={source} /></div>;
53
+ }
54
+ ```
55
+
56
+ ## 使い方 ─ 構築済みモデルを直接渡す(パース処理を省略)
57
+
58
+ 利用側のアプリが既に構造化スキーマデータを持っている場合(例: `INFORMATION_SCHEMA` から取得した情報)、Mermaid 文字列を経由せずに `model` prop で直接渡せます:
59
+
60
+ ```tsx
61
+ import { MermaidER, type ERModel } from '@msdshsk/react-er-canvas';
62
+
63
+ const model: ERModel = {
64
+ tables: [
65
+ {
66
+ name: 'users',
67
+ columns: [
68
+ { name: 'id', type: 'bigint', keys: { pk: true } },
69
+ { name: 'email', type: 'varchar(255)', keys: { uk: true } },
70
+ ],
71
+ },
72
+ {
73
+ name: 'orders',
74
+ columns: [
75
+ { name: 'id', type: 'bigint', keys: { pk: true } },
76
+ { name: 'user_id', type: 'bigint', keys: { fk: true } },
77
+ ],
78
+ },
79
+ ],
80
+ relations: [
81
+ {
82
+ id: 'users-orders',
83
+ from: 'users',
84
+ to: 'orders',
85
+ fromCardinality: 'one',
86
+ toCardinality: 'zero-or-many',
87
+ identifying: true,
88
+ fromColumn: 'id',
89
+ toColumn: 'user_id',
90
+ },
91
+ ],
92
+ groups: [],
93
+ };
94
+
95
+ <MermaidER model={model} />;
96
+ ```
97
+
98
+ `source` と `model` は排他です。両方渡された場合は `model` が優先されます。
99
+
100
+ ## 使い方 ─ Query Builder モード
101
+
102
+ カラムチェックボックスと手動 JOIN を組み合わせて、ビジュアルなクエリ構築 UI を作れます:
103
+
104
+ ```tsx
105
+ import { useState } from 'react';
106
+ import {
107
+ MermaidER,
108
+ type ColumnRef,
109
+ type Join,
110
+ type PartialColumnRef,
111
+ } from '@msdshsk/react-er-canvas';
112
+
113
+ function QueryBuilder({ source }: { source: string }) {
114
+ const [selected, setSelected] = useState<ColumnRef[]>([]);
115
+ const [joins, setJoins] = useState<Join[]>([]);
116
+
117
+ return (
118
+ <MermaidER
119
+ source={source}
120
+ showColumnCheckboxes
121
+ selectedColumns={selected}
122
+ onColumnSelectionChange={setSelected}
123
+ enableManualJoins
124
+ joins={joins}
125
+ onJoinConnect={(s, t) => {
126
+ // ダイアログを開いて JOIN タイプを訊いた上で:
127
+ // setJoins([...joins, { id: ..., source: s as ColumnRef, target: t as ColumnRef, type: 'INNER' }]);
128
+ }}
129
+ onJoinDelete={(id) => setJoins(joins.filter((j) => j.id !== id))}
130
+ />
131
+ );
132
+ }
133
+ ```
134
+
135
+ リポジトリ内 `examples/web/` に完全なデモが入っています(サンプル切替・SQL 生成・位置永続化・レイアウトアルゴリズム選択・テーブル削除)。
136
+
137
+ ## Mermaid 互換の拡張ディレクティブ
138
+
139
+ 以下のディレクティブはコメント形式で書かれているため、**標準 Mermaid レンダラに食わせても無害**です:
140
+
141
+ ```mermaid
142
+ %% @group public
143
+ CUSTOMER { int id PK }
144
+ ORDER { int id PK }
145
+ %% @endgroup
146
+
147
+ %% @ref CUSTOMER.id -> ORDER.customer_id
148
+ ```
149
+
150
+ - **`%% @group <名前>`** ... **`%% @endgroup`** ─ 内部のテーブルをスキーマ/名前空間としてグルーピング。ヘッダーに色付きバッジが表示されます。
151
+ - **`%% @ref <Table>.<col> -> <Table>.<col>`** ─ FK カラムを明示指定するオーバーライド。直前のリレーションに適用されます。自動推論で曖昧になる場合(例: `users` → `comments` の FK が複数本ある)に使用してください。
152
+
153
+ ## FK カラムの自動推論
154
+
155
+ `A ||--o{ B` の関係が宣言されると、本ライブラリは `A` (PK 側)のどのカラムが `B` (FK 側)のどのカラムに対応するかを以下の優先順位で推論します:
156
+
157
+ 1. **`%% @ref` ディレクティブ** (常に最優先)
158
+ 2. **Laravel 形式のラベル** ─ リレーションのラベルが `<fkテーブル>_<カラム>_foreign` パターンなら FK カラムを特定
159
+ 3. **ゆるいラベルマッチ** ─ `<fkテーブル>_<カラム>` パターン
160
+ 4. **命名規則** ─ FK 側に `<pkテーブル>_<pkカラム>` または `<pkテーブル>_id` という名前のカラム
161
+ 5. **単一 FK フォールバック** ─ FK テーブル内に FK カラムが 1 つしか無ければそれ
162
+ 6. **先頭 FK フォールバック** ─ 最終手段として最初の FK カラム
163
+
164
+ ## レイアウト
165
+
166
+ [elkjs](https://github.com/kieler/elkjs) で実装。以下の prop で調整可能です:
167
+
168
+ ```tsx
169
+ <MermaidER
170
+ algorithm="layered" // 'layered' | 'stress' | 'force' | 'mrtree' | 'rectpacking' | 'radial'
171
+ direction="DOWN" // 'DOWN' | 'RIGHT' | 'LEFT' | 'UP' (layered/mrtree のみ有効)
172
+ aspectRatio={16/9} // 目標アスペクト比 ─ 層を折り返してフィットさせる
173
+ />
174
+ ```
175
+
176
+ デフォルトは `layered` + `DOWN`。
177
+
178
+ インタラクティブな編集(textarea で source を編集など)を扱う場合、**利用側で source 更新をデバウンスしてください**。本ライブラリは `source` が変わるたびに同期的に再パース・再レイアウトするためです。
179
+
180
+ ## 位置の永続化
181
+
182
+ ```tsx
183
+ const [positions, setPositions] = useState<NodePositions>(loadFromStorage());
184
+
185
+ <MermaidER
186
+ source={source}
187
+ positions={positions}
188
+ onPositionsChange={(next) => {
189
+ setPositions(next);
190
+ saveToStorage(next);
191
+ }}
192
+ />
193
+ ```
194
+
195
+ `positions` に存在しないテーブルは自動レイアウト結果が使われます。複数選択ドラッグ(Shift+drag で範囲選択 → ドラッグ)も対応済みで、`onPositionsChange` には移動した全テーブルの新位置が渡されます。
196
+
197
+ ## 公開 API
198
+
199
+ ```ts
200
+ import {
201
+ MermaidER,
202
+ parseMermaidER,
203
+ layoutER,
204
+ MermaidERParseError,
205
+ } from '@msdshsk/react-er-canvas';
206
+ ```
207
+
208
+ 完全な型は package ルートからエクスポートされます: `MermaidERProps`、`ERModel`、`Table`、`Column`、`Relation`、`Group`、`ColumnRef`、`PartialColumnRef`、`Join`、`JoinType`、`LayoutOptions`、`LayoutAlgorithm`、`LayoutDirection`、`NodePositions`、`NodePosition`、`LayoutResult`、`PositionedNode`、`PositionedEdge`、`EdgePoint`、`Cardinality`、`ColumnKey`。
209
+
210
+ React 非依存のヘッドレス用途には `@msdshsk/react-er-canvas/core` サブパスが用意されています(パース + レイアウトのみ、レンダリング無し)。
211
+
212
+ ## ライセンス
213
+
214
+ MIT ─ [LICENSE](./LICENSE) を参照。
215
+
216
+ 依存ライブラリのライセンス(Apache-2.0 / EPL-2.0 / MIT)と帰属表示については [NOTICE](./NOTICE.md) を参照してください。
package/README.md ADDED
@@ -0,0 +1,216 @@
1
+ # @msdshsk/react-er-canvas
2
+
3
+ [日本語 README](./README.ja.md)
4
+
5
+ Render Mermaid-format ER diagrams in React / Electron, with extended visual features that the stock Mermaid renderer lacks:
6
+
7
+ - **Column-level FK connections** (lines from `users.id` to `orders.user_id`, not just table-to-table)
8
+ - **Drag-to-position** with optional persistence via `positions` / `onPositionsChange`
9
+ - **Hover-driven FK reference highlighting**
10
+ - **PK / FK / UK / type / comment** rendered per column
11
+ - **Schema groups** (via `%% @group` directive that stays Mermaid-compatible)
12
+ - **Manual JOIN authoring** (column-to-column drag) for query builder UIs
13
+ - **Column selection** (checkboxes) for query builder UIs
14
+
15
+ Built on [`@xyflow/react`](https://reactflow.dev/), [`elkjs`](https://github.com/kieler/elkjs), and a handwritten [`chevrotain`](https://chevrotain.io/) parser.
16
+
17
+ ## Install
18
+
19
+ ```sh
20
+ npm install @msdshsk/react-er-canvas @xyflow/react react react-dom
21
+ ```
22
+
23
+ `@xyflow/react`, `react`, and `react-dom` are peer dependencies — make sure they're installed in your app.
24
+
25
+ You also need to import React Flow's stylesheet **once** in your app:
26
+
27
+ ```ts
28
+ import '@xyflow/react/dist/style.css';
29
+ ```
30
+
31
+ ## Usage — Mermaid source
32
+
33
+ ```tsx
34
+ import { MermaidER } from '@msdshsk/react-er-canvas';
35
+
36
+ const source = `
37
+ erDiagram
38
+ CUSTOMER ||--o{ ORDER : places
39
+ CUSTOMER {
40
+ int id PK "Customer ID"
41
+ string name
42
+ string email UK
43
+ }
44
+ ORDER {
45
+ int id PK
46
+ int customer_id FK
47
+ decimal amount
48
+ }
49
+ `;
50
+
51
+ export function Schema() {
52
+ return <div style={{ height: '100vh' }}><MermaidER source={source} /></div>;
53
+ }
54
+ ```
55
+
56
+ ## Usage — pre-built model (skips parsing)
57
+
58
+ If your app already has structured schema data (e.g., from `INFORMATION_SCHEMA`), pass it directly via `model`:
59
+
60
+ ```tsx
61
+ import { MermaidER, type ERModel } from '@msdshsk/react-er-canvas';
62
+
63
+ const model: ERModel = {
64
+ tables: [
65
+ {
66
+ name: 'users',
67
+ columns: [
68
+ { name: 'id', type: 'bigint', keys: { pk: true } },
69
+ { name: 'email', type: 'varchar(255)', keys: { uk: true } },
70
+ ],
71
+ },
72
+ {
73
+ name: 'orders',
74
+ columns: [
75
+ { name: 'id', type: 'bigint', keys: { pk: true } },
76
+ { name: 'user_id', type: 'bigint', keys: { fk: true } },
77
+ ],
78
+ },
79
+ ],
80
+ relations: [
81
+ {
82
+ id: 'users-orders',
83
+ from: 'users',
84
+ to: 'orders',
85
+ fromCardinality: 'one',
86
+ toCardinality: 'zero-or-many',
87
+ identifying: true,
88
+ fromColumn: 'id',
89
+ toColumn: 'user_id',
90
+ },
91
+ ],
92
+ groups: [],
93
+ };
94
+
95
+ <MermaidER model={model} />;
96
+ ```
97
+
98
+ `source` and `model` are mutually exclusive; if both are passed, `model` wins.
99
+
100
+ ## Usage — Query Builder mode
101
+
102
+ Combine column checkboxes and manual JOINs to build a visual query composer:
103
+
104
+ ```tsx
105
+ import { useState } from 'react';
106
+ import {
107
+ MermaidER,
108
+ type ColumnRef,
109
+ type Join,
110
+ type PartialColumnRef,
111
+ } from '@msdshsk/react-er-canvas';
112
+
113
+ function QueryBuilder({ source }: { source: string }) {
114
+ const [selected, setSelected] = useState<ColumnRef[]>([]);
115
+ const [joins, setJoins] = useState<Join[]>([]);
116
+
117
+ return (
118
+ <MermaidER
119
+ source={source}
120
+ showColumnCheckboxes
121
+ selectedColumns={selected}
122
+ onColumnSelectionChange={setSelected}
123
+ enableManualJoins
124
+ joins={joins}
125
+ onJoinConnect={(s, t) => {
126
+ // open a dialog to ask for JOIN type, then:
127
+ // setJoins([...joins, { id: ..., source: s as ColumnRef, target: t as ColumnRef, type: 'INNER' }]);
128
+ }}
129
+ onJoinDelete={(id) => setJoins(joins.filter((j) => j.id !== id))}
130
+ />
131
+ );
132
+ }
133
+ ```
134
+
135
+ See `examples/web/` in the repo for a full demo (sample switcher, SQL generation, position persistence, layout algorithm controls, table removal).
136
+
137
+ ## Mermaid-compatible extensions
138
+
139
+ These directives are written as comments so they survive a round-trip through stock Mermaid renderers:
140
+
141
+ ```mermaid
142
+ %% @group public
143
+ CUSTOMER { int id PK }
144
+ ORDER { int id PK }
145
+ %% @endgroup
146
+
147
+ %% @ref CUSTOMER.id -> ORDER.customer_id
148
+ ```
149
+
150
+ - **`%% @group <name>`** ... **`%% @endgroup`** — visually mark tables as belonging to a schema/namespace; they get a colored header badge.
151
+ - **`%% @ref <Table>.<col> -> <Table>.<col>`** — explicit FK column override, applied to the most recent relation. Use this when automatic inference can't disambiguate (e.g., multiple FKs from `users` to `comments`).
152
+
153
+ ## Automatic FK column inference
154
+
155
+ When the relation `A ||--o{ B` is declared, the library tries to determine which column on `A` (PK side) connects to which column on `B` (FK side), in this priority:
156
+
157
+ 1. **`%% @ref` directive** (always wins)
158
+ 2. **Laravel-style label** — if the relation label matches `<fkTable>_<col>_foreign`, that pinpoints the FK column
159
+ 3. **Loose label match** — `<fkTable>_<col>`
160
+ 4. **Naming conventions** — `<pkTable>_<pkCol>` or `<pkTable>_id` on the FK side
161
+ 5. **Single-FK fallback** — if only one FK column exists on the FK table, use it
162
+ 6. **First-FK fallback** — last resort
163
+
164
+ ## Layout
165
+
166
+ Powered by [elkjs](https://github.com/kieler/elkjs). Configurable via:
167
+
168
+ ```tsx
169
+ <MermaidER
170
+ algorithm="layered" // 'layered' | 'stress' | 'force' | 'mrtree' | 'rectpacking' | 'radial'
171
+ direction="DOWN" // 'DOWN' | 'RIGHT' | 'LEFT' | 'UP' (only used by layered/mrtree)
172
+ aspectRatio={16/9} // hint to wrap layers to fit a target aspect
173
+ />
174
+ ```
175
+
176
+ Defaults to `layered` + `DOWN`.
177
+
178
+ For interactive editing, **debounce parse on the consumer side** if your source updates on every keystroke — the library re-parses synchronously on each `source` change.
179
+
180
+ ## Position persistence
181
+
182
+ ```tsx
183
+ const [positions, setPositions] = useState<NodePositions>(loadFromStorage());
184
+
185
+ <MermaidER
186
+ source={source}
187
+ positions={positions}
188
+ onPositionsChange={(next) => {
189
+ setPositions(next);
190
+ saveToStorage(next);
191
+ }}
192
+ />
193
+ ```
194
+
195
+ Tables not present in `positions` use the auto-layout result. Multi-select drag (Shift+drag for box select, then drag) is preserved.
196
+
197
+ ## Public API
198
+
199
+ ```ts
200
+ import {
201
+ MermaidER,
202
+ parseMermaidER,
203
+ layoutER,
204
+ MermaidERParseError,
205
+ } from '@msdshsk/react-er-canvas';
206
+ ```
207
+
208
+ The full type surface is exported from the package root: `MermaidERProps`, `ERModel`, `Table`, `Column`, `Relation`, `Group`, `ColumnRef`, `PartialColumnRef`, `Join`, `JoinType`, `LayoutOptions`, `LayoutAlgorithm`, `LayoutDirection`, `NodePositions`, `NodePosition`, `LayoutResult`, `PositionedNode`, `PositionedEdge`, `EdgePoint`, `Cardinality`, `ColumnKey`.
209
+
210
+ A headless subpath `@msdshsk/react-er-canvas/core` is also available for environments without React (just parsing + layout, no rendering).
211
+
212
+ ## License
213
+
214
+ MIT — see [LICENSE](./LICENSE).
215
+
216
+ For third-party dependency licenses (Apache-2.0 / EPL-2.0 / MIT) and attribution, see [NOTICE](./NOTICE.md).
@@ -0,0 +1,10 @@
1
+ import { EdgeProps } from '@xyflow/react';
2
+ import { JoinType } from '../core/model';
3
+ export interface JoinEdgeData extends Record<string, unknown> {
4
+ type: JoinType;
5
+ color: string;
6
+ hovered?: boolean;
7
+ onDelete?: () => void;
8
+ }
9
+ export declare const JoinEdge: import('react').MemoExoticComponent<({ id, sourceX, sourceY, targetX, targetY, sourcePosition, targetPosition, data, selected, }: EdgeProps) => import("react/jsx-runtime").JSX.Element>;
10
+ //# sourceMappingURL=JoinEdge.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"JoinEdge.d.ts","sourceRoot":"","sources":["../../src/components/JoinEdge.tsx"],"names":[],"mappings":"AACA,OAAO,EAIL,KAAK,SAAS,EACf,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAE9C,MAAM,WAAW,YAAa,SAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAC3D,IAAI,EAAE,QAAQ,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;CACvB;AAED,eAAO,MAAM,QAAQ,oIAUlB,SAAS,6CAkFV,CAAC"}
@@ -0,0 +1,46 @@
1
+ import { CSSProperties } from 'react';
2
+ import { LayoutAlgorithm, LayoutDirection } from '../core/layout';
3
+ import { ColumnRef, ERModel, Join, PartialColumnRef } from '../core/model';
4
+ export interface NodePosition {
5
+ x: number;
6
+ y: number;
7
+ }
8
+ export type NodePositions = Record<string, NodePosition>;
9
+ export interface MermaidERProps {
10
+ /** Mermaid ER source. Mutually exclusive with `model`. */
11
+ source?: string;
12
+ /** Pre-built ER model. Takes precedence over `source`. */
13
+ model?: ERModel;
14
+ layout?: 'elk';
15
+ algorithm?: LayoutAlgorithm;
16
+ direction?: LayoutDirection;
17
+ aspectRatio?: number;
18
+ positions?: NodePositions;
19
+ onPositionsChange?: (positions: NodePositions) => void;
20
+ showColumnCheckboxes?: boolean;
21
+ selectedColumns?: ColumnRef[];
22
+ onColumnSelectionChange?: (selectedColumns: ColumnRef[]) => void;
23
+ /** Enable column-to-column / card-to-card drag for manual JOINs. */
24
+ enableManualJoins?: boolean;
25
+ /** Existing manual joins to render alongside FK relations. */
26
+ joins?: Join[];
27
+ /**
28
+ * Fired when the user finishes a connect drag. The consumer typically opens
29
+ * a dialog to ask for join type, then appends a complete `Join` to its state.
30
+ * `column` may be undefined when the drag landed on a default (table-center) handle.
31
+ */
32
+ onJoinConnect?: (source: PartialColumnRef, target: PartialColumnRef) => void;
33
+ /** Fired when the user removes a manual join via Delete or the trash icon. */
34
+ onJoinDelete?: (joinId: string) => void;
35
+ /** When provided, a small × appears on each table header to remove it from the canvas. */
36
+ onTableRemove?: (table: string) => void;
37
+ highlightReferencesOnHover?: boolean;
38
+ onColumnClick?: (table: string, column: string) => void;
39
+ onTableClick?: (table: string) => void;
40
+ /** Override the default delete-key code(s). Default is 'Delete' (Backspace ignored to prevent accidents). */
41
+ deleteKeyCode?: string | string[] | null;
42
+ className?: string;
43
+ style?: CSSProperties;
44
+ }
45
+ export declare function MermaidER(props: MermaidERProps): import("react/jsx-runtime").JSX.Element;
46
+ //# sourceMappingURL=MermaidER.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MermaidER.d.ts","sourceRoot":"","sources":["../../src/components/MermaidER.tsx"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,aAAa,EACnB,MAAM,OAAO,CAAC;AAkBf,OAAO,EAEL,KAAK,eAAe,EACpB,KAAK,eAAe,EAErB,MAAM,gBAAgB,CAAC;AACxB,OAAO,KAAK,EACV,SAAS,EACT,OAAO,EACP,IAAI,EACJ,gBAAgB,EAEjB,MAAM,eAAe,CAAC;AAqBvB,MAAM,WAAW,YAAY;IAC3B,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;CACX;AAED,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;AAEzD,MAAM,WAAW,cAAc;IAC7B,0DAA0D;IAC1D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,0DAA0D;IAC1D,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,KAAK,CAAC;IACf,SAAS,CAAC,EAAE,eAAe,CAAC;IAC5B,SAAS,CAAC,EAAE,eAAe,CAAC;IAC5B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,aAAa,CAAC;IAC1B,iBAAiB,CAAC,EAAE,CAAC,SAAS,EAAE,aAAa,KAAK,IAAI,CAAC;IACvD,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,eAAe,CAAC,EAAE,SAAS,EAAE,CAAC;IAC9B,uBAAuB,CAAC,EAAE,CAAC,eAAe,EAAE,SAAS,EAAE,KAAK,IAAI,CAAC;IACjE,oEAAoE;IACpE,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,8DAA8D;IAC9D,KAAK,CAAC,EAAE,IAAI,EAAE,CAAC;IACf;;;;OAIG;IACH,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE,gBAAgB,EAAE,MAAM,EAAE,gBAAgB,KAAK,IAAI,CAAC;IAC7E,8EAA8E;IAC9E,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,0FAA0F;IAC1F,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,0BAA0B,CAAC,EAAE,OAAO,CAAC;IACrC,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACxD,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,6GAA6G;IAC7G,aAAa,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,IAAI,CAAC;IACzC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,aAAa,CAAC;CACvB;AA2CD,wBAAgB,SAAS,CAAC,KAAK,EAAE,cAAc,2CAmU9C"}
@@ -0,0 +1,30 @@
1
+ import { NodeProps } from '@xyflow/react';
2
+ import { Table } from '../core/model';
3
+ export interface TableNodeData extends Record<string, unknown> {
4
+ table: Table;
5
+ }
6
+ /**
7
+ * Map from table name -> set of highlighted column names.
8
+ * Provided by MermaidER, consumed by TableNode for hover-driven highlighting
9
+ * without forcing the whole node array to recompute on every hover change.
10
+ */
11
+ export declare const HighlightContext: import('react').Context<ReadonlyMap<string, ReadonlySet<string>>>;
12
+ export interface ColumnSelectionContextValue {
13
+ enabled: boolean;
14
+ /** "table.column" keys for fast lookup. */
15
+ selected: ReadonlySet<string>;
16
+ onToggle: (table: string, column: string, checked: boolean) => void;
17
+ }
18
+ export declare const ColumnSelectionContext: import('react').Context<ColumnSelectionContextValue>;
19
+ /** When true, column handles become visible/connectable for manual JOIN drawing. */
20
+ export declare const ConnectModeContext: import('react').Context<boolean>;
21
+ export interface TableActionsContextValue {
22
+ /** When provided, a delete affordance is shown on the table header. */
23
+ onTableRemove?: (table: string) => void;
24
+ /** Per-column click handler. Provided via context (not node data) so its
25
+ * identity can change without invalidating the React Flow node array. */
26
+ onColumnClick?: (table: string, column: string) => void;
27
+ }
28
+ export declare const TableActionsContext: import('react').Context<TableActionsContextValue>;
29
+ export declare const TableNode: import('react').MemoExoticComponent<({ data }: NodeProps) => import("react/jsx-runtime").JSX.Element>;
30
+ //# sourceMappingURL=TableNode.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TableNode.d.ts","sourceRoot":"","sources":["../../src/components/TableNode.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,KAAK,EAAU,KAAK,EAAE,MAAM,eAAe,CAAC;AAGnD,MAAM,WAAW,aAAc,SAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAC5D,KAAK,EAAE,KAAK,CAAC;CACd;AAED;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,mEAAqE,CAAC;AAEnG,MAAM,WAAW,2BAA2B;IAC1C,OAAO,EAAE,OAAO,CAAC;IACjB,2CAA2C;IAC3C,QAAQ,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAC9B,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;CACrE;AAED,eAAO,MAAM,sBAAsB,sDAIjC,CAAC;AAEH,oFAAoF;AACpF,eAAO,MAAM,kBAAkB,kCAAgC,CAAC;AAEhE,MAAM,WAAW,wBAAwB;IACvC,uEAAuE;IACvE,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC;8EAC0E;IAC1E,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;CACzD;AAED,eAAO,MAAM,mBAAmB,mDAA8C,CAAC;AA+G/E,eAAO,MAAM,SAAS,iDAAqC,SAAS,6CAwJlE,CAAC"}
@@ -0,0 +1,5 @@
1
+ export type { Cardinality, ColumnKey, Column, Table, Relation, Group, ERModel, ColumnRef, PartialColumnRef, JoinType, Join, } from './model';
2
+ export { parseMermaidER, MermaidERParseError } from './parser';
3
+ export { layoutER, estimateNodeHeight, NODE_WIDTH, HEADER_HEIGHT, ROW_HEIGHT, VERTICAL_PADDING, } from './layout';
4
+ export type { LayoutOptions, LayoutResult, LayoutDirection, LayoutAlgorithm, PositionedNode, PositionedEdge, EdgePoint, } from './layout';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,WAAW,EACX,SAAS,EACT,MAAM,EACN,KAAK,EACL,QAAQ,EACR,KAAK,EACL,OAAO,EACP,SAAS,EACT,gBAAgB,EAChB,QAAQ,EACR,IAAI,GACL,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC;AAC/D,OAAO,EACL,QAAQ,EACR,kBAAkB,EAClB,UAAU,EACV,aAAa,EACb,UAAU,EACV,gBAAgB,GACjB,MAAM,UAAU,CAAC;AAClB,YAAY,EACV,aAAa,EACb,YAAY,EACZ,eAAe,EACf,eAAe,EACf,cAAc,EACd,cAAc,EACd,SAAS,GACV,MAAM,UAAU,CAAC"}
@@ -0,0 +1,58 @@
1
+ import { ERModel } from './model';
2
+ export type LayoutDirection = 'RIGHT' | 'DOWN' | 'LEFT' | 'UP';
3
+ export type LayoutAlgorithm = 'layered' | 'stress' | 'force' | 'mrtree' | 'rectpacking' | 'radial';
4
+ export interface LayoutOptions {
5
+ engine?: 'elk';
6
+ algorithm?: LayoutAlgorithm;
7
+ direction?: LayoutDirection;
8
+ /**
9
+ * Target aspect ratio (width / height). Combined with the layered algorithm,
10
+ * enables wrapping so the layout fits common screen sizes.
11
+ * Example: `16/9`, `Math.SQRT2` for A4 landscape, `1` for square.
12
+ */
13
+ aspectRatio?: number;
14
+ nodeSpacing?: number;
15
+ rankSpacing?: number;
16
+ /** Raw ELK layoutOptions, merged after the defaults. */
17
+ raw?: Record<string, string>;
18
+ }
19
+ export interface PositionedNode {
20
+ id: string;
21
+ x: number;
22
+ y: number;
23
+ width: number;
24
+ height: number;
25
+ }
26
+ export interface EdgePoint {
27
+ x: number;
28
+ y: number;
29
+ }
30
+ export interface PositionedEdge {
31
+ id: string;
32
+ source: string;
33
+ target: string;
34
+ startPoint?: EdgePoint;
35
+ endPoint?: EdgePoint;
36
+ bendPoints?: EdgePoint[];
37
+ }
38
+ export interface LayoutResult {
39
+ nodes: PositionedNode[];
40
+ edges: PositionedEdge[];
41
+ width: number;
42
+ height: number;
43
+ }
44
+ export declare const NODE_WIDTH = 240;
45
+ export declare const HEADER_HEIGHT = 32;
46
+ export declare const ROW_HEIGHT = 22;
47
+ export declare const VERTICAL_PADDING = 8;
48
+ export declare function estimateNodeHeight(columnCount: number): number;
49
+ export declare function buildElkLayoutOptions(opts: {
50
+ algorithm: LayoutAlgorithm;
51
+ direction: LayoutDirection;
52
+ aspectRatio: number | undefined;
53
+ nodeSpacing: number;
54
+ rankSpacing: number;
55
+ raw: Record<string, string>;
56
+ }): Record<string, string>;
57
+ export declare function layoutER(model: ERModel, options?: LayoutOptions): Promise<LayoutResult>;
58
+ //# sourceMappingURL=layout.d.ts.map