@grafloria/element 0.3.23 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -7
- package/package.json +7 -9
- package/src/index.d.ts +16 -16
- package/src/index.js +58 -339
- package/src/lib/dashboard-kit/dashboard.d.ts +1 -1
- package/src/lib/dashboard-kit/dashboard.js +22 -26
- package/src/lib/dashboard-kit/grid-binder.d.ts +1 -1
- package/src/lib/dashboard-kit/grid-binder.js +62 -65
- package/src/lib/dashboard-kit/grid-mapping.js +14 -26
- package/src/lib/dashboard-kit/index.d.ts +5 -5
- package/src/lib/dashboard-kit/index.js +5 -30
- package/src/lib/dashboard-kit/styles.js +4 -8
- package/src/lib/dashboard-kit/widgets.d.ts +1 -1
- package/src/lib/dashboard-kit/widgets.js +16 -26
- package/src/lib/diagram-kit/card.d.ts +2 -2
- package/src/lib/diagram-kit/card.js +22 -32
- package/src/lib/diagram-kit/editing.js +15 -20
- package/src/lib/diagram-kit/er.d.ts +1 -1
- package/src/lib/diagram-kit/er.js +15 -18
- package/src/lib/diagram-kit/handles.d.ts +4 -4
- package/src/lib/diagram-kit/handles.js +16 -29
- package/src/lib/diagram-kit/index.d.ts +10 -10
- package/src/lib/diagram-kit/index.js +10 -50
- package/src/lib/diagram-kit/join-guidance.js +9 -18
- package/src/lib/diagram-kit/rows.js +1 -4
- package/src/lib/diagram-kit/styles.js +4 -8
- package/src/lib/diagram-kit/uml.js +10 -13
- package/src/lib/diagram-kit/update.d.ts +1 -1
- package/src/lib/diagram-kit/update.js +19 -26
- package/src/lib/grafloria-flow-element.js +19 -24
- package/src/lib/grafloria.d.ts +4 -4
- package/src/lib/grafloria.js +12 -17
- package/src/lib/load.d.ts +3 -3
- package/src/lib/load.js +19 -22
- package/src/lib/node-type-registry.js +6 -14
- package/src/lib/stencil-kit/builders.js +4 -10
- package/src/lib/stencil-kit/card-builders.js +14 -17
- package/src/lib/stencil-kit/index.d.ts +7 -7
- package/src/lib/stencil-kit/index.js +6 -16
- package/src/lib/stencil-kit/palette.js +14 -17
- package/src/lib/stencil-kit/shape-data.js +25 -28
- package/src/lib/stencil-kit/styles.js +1 -4
- package/esm/src/index.js +0 -295
- package/esm/src/lib/dashboard-kit/dashboard.js +0 -590
- package/esm/src/lib/dashboard-kit/grid-binder.js +0 -1633
- package/esm/src/lib/dashboard-kit/grid-mapping.js +0 -164
- package/esm/src/lib/dashboard-kit/index.js +0 -8
- package/esm/src/lib/dashboard-kit/styles.js +0 -205
- package/esm/src/lib/dashboard-kit/widgets.js +0 -379
- package/esm/src/lib/diagram-kit/card.js +0 -199
- package/esm/src/lib/diagram-kit/editing.js +0 -448
- package/esm/src/lib/diagram-kit/er.js +0 -160
- package/esm/src/lib/diagram-kit/handles.js +0 -312
- package/esm/src/lib/diagram-kit/index.js +0 -15
- package/esm/src/lib/diagram-kit/join-guidance.js +0 -322
- package/esm/src/lib/diagram-kit/rows.js +0 -144
- package/esm/src/lib/diagram-kit/styles.js +0 -118
- package/esm/src/lib/diagram-kit/uml.js +0 -135
- package/esm/src/lib/diagram-kit/update.js +0 -244
- package/esm/src/lib/grafloria-flow-element.js +0 -266
- package/esm/src/lib/grafloria.js +0 -69
- package/esm/src/lib/load.js +0 -252
- package/esm/src/lib/node-type-registry.js +0 -42
- package/esm/src/lib/stencil-kit/builders.js +0 -18
- package/esm/src/lib/stencil-kit/card-builders.js +0 -105
- package/esm/src/lib/stencil-kit/index.js +0 -9
- package/esm/src/lib/stencil-kit/palette.js +0 -427
- package/esm/src/lib/stencil-kit/shape-data.js +0 -537
- package/esm/src/lib/stencil-kit/styles.js +0 -126
|
@@ -1,266 +0,0 @@
|
|
|
1
|
-
import { createDiagram, DARK_THEME, LIGHT_THEME } from '@grafloria/renderer';
|
|
2
|
-
import { getNodeType, renderFromTemplate } from './node-type-registry';
|
|
3
|
-
/**
|
|
4
|
-
* `<grafloria-flow>` — the universal embed.
|
|
5
|
-
*
|
|
6
|
-
* React Flow serves React. ngx-vflow serves Angular. Each is a wall around one
|
|
7
|
-
* framework. A custom element has no wall: Vue, Svelte, Solid, Lit, Alpine,
|
|
8
|
-
* plain HTML, a CMS block, a Jupyter/Observable cell and a static site all speak
|
|
9
|
-
* "HTML element with attributes and events". This is the piece that lets Grafloria
|
|
10
|
-
* reach the long tail those libraries cannot.
|
|
11
|
-
*
|
|
12
|
-
* ```html
|
|
13
|
-
* <grafloria-flow theme="dark" fit-view
|
|
14
|
-
* nodes='[{"id":"a","position":{"x":0,"y":0},"label":"A"}]'
|
|
15
|
-
* edges='[{"source":"a","target":"b"}]'>
|
|
16
|
-
* <template data-node-type="card">
|
|
17
|
-
* <div class="card"><h4 data-field="title"></h4></div>
|
|
18
|
-
* </template>
|
|
19
|
-
* </grafloria-flow>
|
|
20
|
-
*
|
|
21
|
-
* <script>
|
|
22
|
-
* document.querySelector('grafloria-flow')
|
|
23
|
-
* .addEventListener('grafloria-connect', e => console.log(e.detail.link));
|
|
24
|
-
* </script>
|
|
25
|
-
* ```
|
|
26
|
-
*
|
|
27
|
-
* Rich data goes in as PROPERTIES (`el.nodes = [...]`) and simple data as
|
|
28
|
-
* ATTRIBUTES (JSON strings) — the standard custom-element contract that every
|
|
29
|
-
* framework's template binding already targets:
|
|
30
|
-
* `:nodes` in Vue, `nodes={...}` in Svelte/Solid, `[attr.nodes]` in Angular.
|
|
31
|
-
*
|
|
32
|
-
* Everything it does is delegation. There is no diagram logic in this file.
|
|
33
|
-
*/
|
|
34
|
-
const THEMES = {
|
|
35
|
-
light: LIGHT_THEME,
|
|
36
|
-
dark: DARK_THEME,
|
|
37
|
-
};
|
|
38
|
-
/** Events emitted on the element. All bubble and cross shadow boundaries. */
|
|
39
|
-
export const GRAFLORIA_EVENTS = {
|
|
40
|
-
ready: 'grafloria-ready',
|
|
41
|
-
nodesChange: 'grafloria-nodes-change',
|
|
42
|
-
edgesChange: 'grafloria-edges-change',
|
|
43
|
-
selectionChange: 'grafloria-selection-change',
|
|
44
|
-
connect: 'grafloria-connect',
|
|
45
|
-
nodeClick: 'grafloria-node-click',
|
|
46
|
-
edgeClick: 'grafloria-edge-click',
|
|
47
|
-
viewportChange: 'grafloria-viewport-change',
|
|
48
|
-
};
|
|
49
|
-
// wave11/gallery BUG FIX — worker-safe base class.
|
|
50
|
-
//
|
|
51
|
-
// `class GrafloriaFlowElement extends HTMLElement` evaluates `HTMLElement` at MODULE
|
|
52
|
-
// LOAD, so merely IMPORTING this package threw `HTMLElement is not defined` in any
|
|
53
|
-
// context without a DOM — a Web Worker, or Node. The package doc already promised
|
|
54
|
-
// the import is "a no-op on the server", and `defineGrafloriaFlow()` guards
|
|
55
|
-
// `customElements`; but the class declaration itself did not, so the promise was
|
|
56
|
-
// only half true. It matters concretely: the off-thread-layout demo runs the
|
|
57
|
-
// engine's `serveLayout` INSIDE a real Worker by importing this very bundle, which
|
|
58
|
-
// is impossible if the top-level class reference throws. The element only ever
|
|
59
|
-
// EXTENDS the real HTMLElement in a browser (a worker never instantiates it), so a
|
|
60
|
-
// guarded base is behaviourally identical on the client and merely importable off it.
|
|
61
|
-
const HTMLElementBase = typeof HTMLElement !== 'undefined'
|
|
62
|
-
? HTMLElement
|
|
63
|
-
: class {
|
|
64
|
-
};
|
|
65
|
-
export class GrafloriaFlowElement extends HTMLElementBase {
|
|
66
|
-
constructor() {
|
|
67
|
-
super(...arguments);
|
|
68
|
-
this.instance = null;
|
|
69
|
-
this.canvas = null;
|
|
70
|
-
this._nodes = [];
|
|
71
|
-
this._edges = [];
|
|
72
|
-
this.connected = false;
|
|
73
|
-
}
|
|
74
|
-
static get observedAttributes() {
|
|
75
|
-
return [
|
|
76
|
-
'nodes',
|
|
77
|
-
'edges',
|
|
78
|
-
'theme',
|
|
79
|
-
'fit-view',
|
|
80
|
-
'readonly',
|
|
81
|
-
'zoom',
|
|
82
|
-
'min-zoom',
|
|
83
|
-
'max-zoom',
|
|
84
|
-
'pan',
|
|
85
|
-
'wheel-zoom',
|
|
86
|
-
];
|
|
87
|
-
}
|
|
88
|
-
// -- properties (the rich path) ---------------------------------------------
|
|
89
|
-
get nodes() {
|
|
90
|
-
return this._nodes;
|
|
91
|
-
}
|
|
92
|
-
set nodes(value) {
|
|
93
|
-
var _a;
|
|
94
|
-
this._nodes = value !== null && value !== void 0 ? value : [];
|
|
95
|
-
(_a = this.instance) === null || _a === void 0 ? void 0 : _a.setNodes(this._nodes);
|
|
96
|
-
}
|
|
97
|
-
get edges() {
|
|
98
|
-
return this._edges;
|
|
99
|
-
}
|
|
100
|
-
set edges(value) {
|
|
101
|
-
var _a;
|
|
102
|
-
this._edges = value !== null && value !== void 0 ? value : [];
|
|
103
|
-
(_a = this.instance) === null || _a === void 0 ? void 0 : _a.setEdges(this._edges);
|
|
104
|
-
}
|
|
105
|
-
/** The headless instance — the escape hatch to everything else. */
|
|
106
|
-
get diagram() {
|
|
107
|
-
return this.instance;
|
|
108
|
-
}
|
|
109
|
-
// -- lifecycle ---------------------------------------------------------------
|
|
110
|
-
connectedCallback() {
|
|
111
|
-
if (this.connected)
|
|
112
|
-
return;
|
|
113
|
-
this.connected = true;
|
|
114
|
-
// Attributes may have been parsed before we upgraded.
|
|
115
|
-
this.readAttributeModel();
|
|
116
|
-
// Light DOM, not shadow DOM: the diagram's stylesheet is injected into
|
|
117
|
-
// <head> and its CSS variables cascade — a shadow root would cut both off,
|
|
118
|
-
// and hosts routinely want to style nodes from their own stylesheet.
|
|
119
|
-
this.canvas = document.createElement('div');
|
|
120
|
-
this.canvas.className = 'grafloria-flow-canvas';
|
|
121
|
-
this.canvas.setAttribute('style', 'position:relative;width:100%;height:100%');
|
|
122
|
-
this.appendChild(this.canvas);
|
|
123
|
-
if (!this.style.display)
|
|
124
|
-
this.style.display = 'block';
|
|
125
|
-
this.instance = createDiagram(this.canvas, this.buildOptions());
|
|
126
|
-
this.wireEvents(this.instance);
|
|
127
|
-
}
|
|
128
|
-
disconnectedCallback() {
|
|
129
|
-
var _a, _b;
|
|
130
|
-
this.connected = false;
|
|
131
|
-
(_a = this.instance) === null || _a === void 0 ? void 0 : _a.dispose();
|
|
132
|
-
this.instance = null;
|
|
133
|
-
(_b = this.canvas) === null || _b === void 0 ? void 0 : _b.remove();
|
|
134
|
-
this.canvas = null;
|
|
135
|
-
}
|
|
136
|
-
attributeChangedCallback(name, previous, next) {
|
|
137
|
-
var _a, _b;
|
|
138
|
-
if (previous === next)
|
|
139
|
-
return;
|
|
140
|
-
switch (name) {
|
|
141
|
-
case 'nodes':
|
|
142
|
-
this._nodes = parseJsonAttribute(next, []);
|
|
143
|
-
(_a = this.instance) === null || _a === void 0 ? void 0 : _a.setNodes(this._nodes);
|
|
144
|
-
return;
|
|
145
|
-
case 'edges':
|
|
146
|
-
this._edges = parseJsonAttribute(next, []);
|
|
147
|
-
(_b = this.instance) === null || _b === void 0 ? void 0 : _b.setEdges(this._edges);
|
|
148
|
-
return;
|
|
149
|
-
case 'theme':
|
|
150
|
-
if (this.instance)
|
|
151
|
-
this.instance.setTheme(this.resolveTheme());
|
|
152
|
-
return;
|
|
153
|
-
case 'fit-view':
|
|
154
|
-
if (this.instance && next !== null)
|
|
155
|
-
this.instance.fitView();
|
|
156
|
-
return;
|
|
157
|
-
case 'zoom':
|
|
158
|
-
if (this.instance && next !== null)
|
|
159
|
-
this.instance.viewport.setZoom(Number(next));
|
|
160
|
-
return;
|
|
161
|
-
default:
|
|
162
|
-
// pan / wheel-zoom / readonly / min-zoom / max-zoom are read at mount:
|
|
163
|
-
// they configure the event binder, which is created once. Changing them
|
|
164
|
-
// afterwards is rare enough that we do not tear the instance down.
|
|
165
|
-
return;
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
// -- API ---------------------------------------------------------------------
|
|
169
|
-
fitView(padding) {
|
|
170
|
-
var _a;
|
|
171
|
-
(_a = this.instance) === null || _a === void 0 ? void 0 : _a.fitView(padding);
|
|
172
|
-
}
|
|
173
|
-
// -- internals ---------------------------------------------------------------
|
|
174
|
-
buildOptions() {
|
|
175
|
-
return {
|
|
176
|
-
nodes: this._nodes,
|
|
177
|
-
edges: this._edges,
|
|
178
|
-
theme: this.resolveTheme(),
|
|
179
|
-
fitView: this.hasAttribute('fit-view'),
|
|
180
|
-
readonly: this.hasAttribute('readonly'),
|
|
181
|
-
enablePan: this.getAttribute('pan') !== 'false',
|
|
182
|
-
enableZoom: this.getAttribute('wheel-zoom') !== 'false',
|
|
183
|
-
zoom: this.hasAttribute('zoom') ? Number(this.getAttribute('zoom')) : undefined,
|
|
184
|
-
minZoom: this.hasAttribute('min-zoom')
|
|
185
|
-
? Number(this.getAttribute('min-zoom'))
|
|
186
|
-
: undefined,
|
|
187
|
-
maxZoom: this.hasAttribute('max-zoom')
|
|
188
|
-
? Number(this.getAttribute('max-zoom'))
|
|
189
|
-
: undefined,
|
|
190
|
-
renderCustomNode: (node, element) => this.renderCustomNode(node, element),
|
|
191
|
-
};
|
|
192
|
-
}
|
|
193
|
-
/**
|
|
194
|
-
* Custom nodes, two ways and no framework: a registered renderer
|
|
195
|
-
* (`Grafloria.registerNodeType`), or a slotted `<template data-node-type="…">`.
|
|
196
|
-
*/
|
|
197
|
-
renderCustomNode(node, element) {
|
|
198
|
-
const renderer = getNodeType(node.type);
|
|
199
|
-
if (renderer) {
|
|
200
|
-
renderer(node, element);
|
|
201
|
-
return;
|
|
202
|
-
}
|
|
203
|
-
const template = this.querySelector(`template[data-node-type="${cssEscape(node.type)}"]`);
|
|
204
|
-
if (template) {
|
|
205
|
-
renderFromTemplate(template, node, element);
|
|
206
|
-
return;
|
|
207
|
-
}
|
|
208
|
-
// Nothing registered: leave the host empty rather than throwing inside a
|
|
209
|
-
// render. The node still exists, is selectable and is draggable.
|
|
210
|
-
}
|
|
211
|
-
resolveTheme() {
|
|
212
|
-
var _a, _b;
|
|
213
|
-
return (_b = THEMES[(_a = this.getAttribute('theme')) !== null && _a !== void 0 ? _a : 'light']) !== null && _b !== void 0 ? _b : LIGHT_THEME;
|
|
214
|
-
}
|
|
215
|
-
readAttributeModel() {
|
|
216
|
-
if (this._nodes.length === 0 && this.hasAttribute('nodes')) {
|
|
217
|
-
this._nodes = parseJsonAttribute(this.getAttribute('nodes'), []);
|
|
218
|
-
}
|
|
219
|
-
if (this._edges.length === 0 && this.hasAttribute('edges')) {
|
|
220
|
-
this._edges = parseJsonAttribute(this.getAttribute('edges'), []);
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
/** Instance events → DOM CustomEvents. `composed` so they escape a shadow root. */
|
|
224
|
-
wireEvents(instance) {
|
|
225
|
-
const forward = (type, detail) => {
|
|
226
|
-
this.dispatchEvent(new CustomEvent(type, { detail, bubbles: true, composed: true }));
|
|
227
|
-
};
|
|
228
|
-
instance.on('ready', () => forward(GRAFLORIA_EVENTS.ready, { diagram: instance }));
|
|
229
|
-
instance.on('nodes:change', (payload) => forward(GRAFLORIA_EVENTS.nodesChange, payload));
|
|
230
|
-
instance.on('edges:change', (payload) => forward(GRAFLORIA_EVENTS.edgesChange, payload));
|
|
231
|
-
instance.on('selection:change', (payload) => forward(GRAFLORIA_EVENTS.selectionChange, payload));
|
|
232
|
-
instance.on('connect', (payload) => forward(GRAFLORIA_EVENTS.connect, payload));
|
|
233
|
-
instance.on('node:click', (payload) => forward(GRAFLORIA_EVENTS.nodeClick, payload));
|
|
234
|
-
instance.on('edge:click', (payload) => forward(GRAFLORIA_EVENTS.edgeClick, payload));
|
|
235
|
-
instance.on('viewport:change', (payload) => forward(GRAFLORIA_EVENTS.viewportChange, payload));
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
/** JSON attribute → value. A malformed attribute must not take the page down. */
|
|
239
|
-
function parseJsonAttribute(raw, fallback) {
|
|
240
|
-
if (!raw)
|
|
241
|
-
return fallback;
|
|
242
|
-
try {
|
|
243
|
-
return JSON.parse(raw);
|
|
244
|
-
}
|
|
245
|
-
catch (_a) {
|
|
246
|
-
console.warn('[grafloria-flow] ignoring malformed JSON attribute:', raw);
|
|
247
|
-
return fallback;
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
/** Minimal CSS.escape for the attribute selector (jsdom/older browsers lack it). */
|
|
251
|
-
function cssEscape(value) {
|
|
252
|
-
return value.replace(/["\\]/g, '\\$&');
|
|
253
|
-
}
|
|
254
|
-
/**
|
|
255
|
-
* Register the element. Idempotent, and safe to call on the server (where
|
|
256
|
-
* `customElements` does not exist) — which is what lets a bundle be imported
|
|
257
|
-
* from an SSR entry point without a `typeof window` dance at every call site.
|
|
258
|
-
*/
|
|
259
|
-
export function defineGrafloriaFlow(tagName = 'grafloria-flow') {
|
|
260
|
-
if (typeof customElements === 'undefined')
|
|
261
|
-
return;
|
|
262
|
-
if (customElements.get(tagName))
|
|
263
|
-
return;
|
|
264
|
-
customElements.define(tagName, GrafloriaFlowElement);
|
|
265
|
-
}
|
|
266
|
-
//# sourceMappingURL=grafloria-flow-element.js.map
|
package/esm/src/lib/grafloria.js
DELETED
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
import { createDiagram, renderToStaticSVG } from '@grafloria/renderer';
|
|
2
|
-
import { defineGrafloriaFlow } from './grafloria-flow-element';
|
|
3
|
-
import { registerNodeType, registeredNodeTypes, getNodeType } from './node-type-registry';
|
|
4
|
-
/**
|
|
5
|
-
* Mount `spec` into `target` and return the live instance.
|
|
6
|
-
*
|
|
7
|
-
* `target` may be an element or a CSS selector. Custom nodes (`custom: true`)
|
|
8
|
-
* are rendered by the types registered with {@link registerNodeType}.
|
|
9
|
-
*
|
|
10
|
-
* SCOPE, stated plainly: `spec` is data (an object or its JSON), not a Mermaid-
|
|
11
|
-
* style text DSL. The engine does have a DSL, but wiring it in is a separate
|
|
12
|
-
* card — `render()` is the embedding surface, not a parser.
|
|
13
|
-
*/
|
|
14
|
-
export function render(spec, target, options = {}) {
|
|
15
|
-
var _a, _b, _c, _d;
|
|
16
|
-
const element = typeof target === 'string'
|
|
17
|
-
? document.querySelector(target)
|
|
18
|
-
: target;
|
|
19
|
-
if (!element) {
|
|
20
|
-
throw new Error(`Grafloria.render: no element matched ${JSON.stringify(target)}`);
|
|
21
|
-
}
|
|
22
|
-
// Kit specs (dashboard()) carry looser node typing than DiagramSpec — both
|
|
23
|
-
// flow into createDiagram's NodeInput[] the same way.
|
|
24
|
-
const parsed = (typeof spec === 'string' ? parseSpec(spec) : spec);
|
|
25
|
-
const instance = createDiagram(element, Object.assign(Object.assign({}, options), { nodes: (_a = parsed.nodes) !== null && _a !== void 0 ? _a : [], edges: (_b = parsed.edges) !== null && _b !== void 0 ? _b : [],
|
|
26
|
-
// Wire the global registry in, so `registerNodeType` works for the tiny API
|
|
27
|
-
// exactly as it does for `<grafloria-flow>` — unless the caller supplies their
|
|
28
|
-
// own. A KIT SPEC may also carry its own painter (dashboard() does: every
|
|
29
|
-
// widget is a custom HTML node), and it must be honoured — otherwise the
|
|
30
|
-
// documented one-liner `render(dashboard({…}), host)` mounts a board whose
|
|
31
|
-
// widgets never paint. Precedence: explicit option > spec > registry.
|
|
32
|
-
renderCustomNode: (_d = (_c = options.renderCustomNode) !== null && _c !== void 0 ? _c : parsed.renderCustomNode) !== null && _d !== void 0 ? _d : ((node, host) => { var _a; return (_a = getNodeType(node.type)) === null || _a === void 0 ? void 0 : _a(node, host); }) }));
|
|
33
|
-
// Diagram-kit specs (erDiagram/umlDiagram) carry a `finalize(api)` for the
|
|
34
|
-
// post-render wiring that needs the LIVE instance — row interactions,
|
|
35
|
-
// multiplicity chips. Auto-run it so a kit diagram is fully wired from one
|
|
36
|
-
// render() call; finalize is idempotent, so a caller may still call it too.
|
|
37
|
-
const maybeFinalize = spec.finalize;
|
|
38
|
-
if (typeof maybeFinalize === 'function') {
|
|
39
|
-
try {
|
|
40
|
-
maybeFinalize(instance);
|
|
41
|
-
}
|
|
42
|
-
catch (_e) {
|
|
43
|
-
/* a kit's finalize must never break the mount */
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
return instance;
|
|
47
|
-
}
|
|
48
|
-
/** Server-side render (Card 6). Re-exported so the tiny API is self-contained. */
|
|
49
|
-
export function renderStatic(options = {}) {
|
|
50
|
-
return renderToStaticSVG(options);
|
|
51
|
-
}
|
|
52
|
-
function parseSpec(spec) {
|
|
53
|
-
try {
|
|
54
|
-
return JSON.parse(spec);
|
|
55
|
-
}
|
|
56
|
-
catch (error) {
|
|
57
|
-
throw new Error(`Grafloria.render: spec must be an object or a JSON string (${error.message})`);
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
/** The namespace object, for `import { Grafloria }` and for `<script>` globals. */
|
|
61
|
-
export const Grafloria = {
|
|
62
|
-
render,
|
|
63
|
-
renderStatic,
|
|
64
|
-
registerNodeType,
|
|
65
|
-
registeredNodeTypes,
|
|
66
|
-
/** Register `<grafloria-flow>` (called for you when you import this package). */
|
|
67
|
-
define: defineGrafloriaFlow,
|
|
68
|
-
};
|
|
69
|
-
//# sourceMappingURL=grafloria.js.map
|
package/esm/src/lib/load.js
DELETED
|
@@ -1,252 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `fromDocument()` — the LOAD front door.
|
|
3
|
-
*
|
|
4
|
-
* ---------------------------------------------------------------------------
|
|
5
|
-
* THE GAP THIS CLOSES
|
|
6
|
-
* ---------------------------------------------------------------------------
|
|
7
|
-
* Saving was never the problem. `DiagramSerializer.serialize()` already emits a
|
|
8
|
-
* document that round-trips byte-identically, carrying nodes, links, ports,
|
|
9
|
-
* groups and every scrap of kit metadata. LOADING was the problem, in three
|
|
10
|
-
* layers:
|
|
11
|
-
*
|
|
12
|
-
* 1. THERE WAS NO FRONT DOOR. `CreateDiagramOptions` takes `nodes`/`edges`;
|
|
13
|
-
* nothing took a saved document. `deserialize()` handed back a
|
|
14
|
-
* `DiagramModel` that no entry point would render.
|
|
15
|
-
* 2. THE KITS' POST-RENDER WIRING NEVER RAN. `erDiagram()`/`umlDiagram()` do
|
|
16
|
-
* their interaction wiring in `finalize(api)` — row selection, in-canvas
|
|
17
|
-
* editing, the suppressed resize handles. A document has no `finalize`, so
|
|
18
|
-
* a "successfully loaded" ERD was a PICTURE of an ERD: it looked right and
|
|
19
|
-
* did nothing.
|
|
20
|
-
* 3. CUSTOM NODES HAD NO PAINTER. `renderCustomNode` is a function; functions
|
|
21
|
-
* do not serialize. A loaded dashboard mounted 14 widget hosts and painted
|
|
22
|
-
* into none of them.
|
|
23
|
-
*
|
|
24
|
-
* ---------------------------------------------------------------------------
|
|
25
|
-
* WHY THE NODES GO BACK IN AS LIVE MODELS
|
|
26
|
-
* ---------------------------------------------------------------------------
|
|
27
|
-
* The obvious implementation projects every loaded `NodeModel` back down into a
|
|
28
|
-
* `NodeSpec` and lets `buildNode()` rebuild it. That is a LOSSY detour, and it
|
|
29
|
-
* loses exactly the things a saved diagram is made of: `toNodeSpec()` carries
|
|
30
|
-
* id/position/size/data/label/shape and nothing else — no ports, no metadata,
|
|
31
|
-
* no behavior. An ER card would come back with four default side ports instead
|
|
32
|
-
* of its field ports, and every FK→PK edge would silently reroute.
|
|
33
|
-
*
|
|
34
|
-
* It is also unnecessary. `applyNodes()`/`applyEdges()` have always accepted a
|
|
35
|
-
* live `NodeModel`/`LinkModel` and pass it through untouched — the documented
|
|
36
|
-
* "mix data with your own model" seam. So the loaded models ARE the input, and
|
|
37
|
-
* nothing is projected, converted or lost.
|
|
38
|
-
*
|
|
39
|
-
* ---------------------------------------------------------------------------
|
|
40
|
-
* HOW PAINTERS COME BACK
|
|
41
|
-
* ---------------------------------------------------------------------------
|
|
42
|
-
* ER and UML need NOTHING: their card is `metadata.html`, a structured tree the
|
|
43
|
-
* renderer paints itself, and metadata round-trips. Measured, not assumed — a
|
|
44
|
-
* loaded ER document paints byte-identical cards with no painter in sight.
|
|
45
|
-
*
|
|
46
|
-
* Dashboard widgets are custom HTML nodes and do need one. The kit REBUILDS the
|
|
47
|
-
* widget spec from the node's own metadata (`widgetKind` / `widgetSpec` /
|
|
48
|
-
* `widgetTitle`) and hands it to the same `defaultWidgetRenderer` the authoring
|
|
49
|
-
* path uses, so a loaded board is drawn by the identical code.
|
|
50
|
-
*
|
|
51
|
-
* Precedence, and why:
|
|
52
|
-
*
|
|
53
|
-
* caller's `renderCustomNode` — an explicit override outranks everything.
|
|
54
|
-
* caller's `renderWidget` — the app's own chart painter; a board that was
|
|
55
|
-
* authored with one must be reloaded with it.
|
|
56
|
-
* the kit's own painter — but ONLY for nodes the kit actually stamped.
|
|
57
|
-
* {@link getNodeType} registry — everything else, exactly as `render()` does.
|
|
58
|
-
*
|
|
59
|
-
* The kit claims a node by its METADATA, never by `type === 'widget'` alone.
|
|
60
|
-
* Hijacking a type name would mean any unrelated diagram with a node called
|
|
61
|
-
* "widget" got dashboard chrome painted over it; requiring the stamp the kit
|
|
62
|
-
* itself wrote keeps the registry the general seam it is meant to be.
|
|
63
|
-
*/
|
|
64
|
-
import { DiagramSerializer } from '@grafloria/engine';
|
|
65
|
-
import { getNodeType } from './node-type-registry';
|
|
66
|
-
import { bindRowInteractions } from './diagram-kit/rows';
|
|
67
|
-
import { bindCardEditing } from './diagram-kit/editing';
|
|
68
|
-
import { ensureDiagramKitStyles } from './diagram-kit/styles';
|
|
69
|
-
import { bindDashboardGrid } from './dashboard-kit/grid-binder';
|
|
70
|
-
import { ensureDashboardKitStyles } from './dashboard-kit/styles';
|
|
71
|
-
import { defaultWidgetRenderer } from './dashboard-kit/widgets';
|
|
72
|
-
import { createDashboardHandle, } from './dashboard-kit/dashboard';
|
|
73
|
-
/** True when the node is an ER entity card or a UML class card. */
|
|
74
|
-
function isDiagramKitCard(node) {
|
|
75
|
-
return node.getMetadata('kitEntity') !== undefined || node.getMetadata('kitClass') !== undefined;
|
|
76
|
-
}
|
|
77
|
-
/**
|
|
78
|
-
* Rebuild the widget spec the kit's renderers eat, from the node's own metadata.
|
|
79
|
-
* Returns null for a node the dashboard kit did not stamp — see the header on
|
|
80
|
-
* why the type name alone is not enough.
|
|
81
|
-
*/
|
|
82
|
-
function widgetSpecOf(node) {
|
|
83
|
-
var _a;
|
|
84
|
-
const kind = node.getMetadata('widgetKind');
|
|
85
|
-
if (kind === undefined)
|
|
86
|
-
return null;
|
|
87
|
-
const title = node.getMetadata('widgetTitle');
|
|
88
|
-
return Object.assign(Object.assign({ id: node.id, kind: kind }, (typeof title === 'string' ? { title } : {})), { data: ((_a = node.getMetadata('widgetSpec')) !== null && _a !== void 0 ? _a : {}), span: node.getMetadata('columnSpan'), rows: node.getMetadata('rowSpan') });
|
|
89
|
-
}
|
|
90
|
-
/**
|
|
91
|
-
* Turn a saved document back into something `render()` can mount.
|
|
92
|
-
*
|
|
93
|
-
* ```ts
|
|
94
|
-
* const json = JSON.stringify(new DiagramSerializer().serialize(api.getModel()));
|
|
95
|
-
* // …later, in a fresh page:
|
|
96
|
-
* render(fromDocument(json), host);
|
|
97
|
-
* ```
|
|
98
|
-
*
|
|
99
|
-
* Accepts the flat serializer form, the portable envelope, or the JSON string
|
|
100
|
-
* of either.
|
|
101
|
-
*/
|
|
102
|
-
export function fromDocument(document, options = {}) {
|
|
103
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s;
|
|
104
|
-
const parsed = typeof document === 'string' ? parseDocument(document) : document;
|
|
105
|
-
const model = new DiagramSerializer().deserialize(parsed);
|
|
106
|
-
const nodes = model.getNodes();
|
|
107
|
-
const groups = model.getGroups();
|
|
108
|
-
const boards = new Map();
|
|
109
|
-
// The kits inject their stylesheet from their builder; a load never calls one,
|
|
110
|
-
// so an un-styled card would come back as unstyled divs. Only for documents
|
|
111
|
-
// that actually contain that kit's nodes.
|
|
112
|
-
if (nodes.some(isDiagramKitCard))
|
|
113
|
-
ensureDiagramKitStyles();
|
|
114
|
-
if (nodes.some((n) => widgetSpecOf(n) !== null))
|
|
115
|
-
ensureDashboardKitStyles();
|
|
116
|
-
const paintWidget = (_a = options.renderWidget) !== null && _a !== void 0 ? _a : defaultWidgetRenderer;
|
|
117
|
-
// -- reconstruct the dashboard handle's context from the loaded model -------
|
|
118
|
-
// Every board group carries `dashboardBoard` geometry; its widget members
|
|
119
|
-
// carry `widgetSpec`/title/span/rows — the canonical, lossless source for
|
|
120
|
-
// widgets (the load.ts principle that non-widget nodes go back as LIVE models
|
|
121
|
-
// is untouched; only widgets are rebuilt from metadata, exactly as the
|
|
122
|
-
// renderCustomNode painter already does). That is enough to build the SAME
|
|
123
|
-
// handle dashboard() does, through the SAME builder — no drifting twin.
|
|
124
|
-
const dashGroups = groups.filter((g) => g.getMetadata('dashboardBoard') !== undefined);
|
|
125
|
-
const specById = new Map();
|
|
126
|
-
const viewOfWidget = new Map();
|
|
127
|
-
const ctxViews = dashGroups.map((g) => {
|
|
128
|
-
var _a, _b, _c;
|
|
129
|
-
const board = g.getMetadata('dashboardBoard');
|
|
130
|
-
const widgets = [];
|
|
131
|
-
for (const memberId of (_a = g.members) !== null && _a !== void 0 ? _a : []) {
|
|
132
|
-
const node = model.getNode(memberId);
|
|
133
|
-
const ws = node ? widgetSpecOf(node) : null;
|
|
134
|
-
if (!ws)
|
|
135
|
-
continue; // non-widget members (e.g. a nested slab group) are not widgets
|
|
136
|
-
specById.set(ws.id, ws);
|
|
137
|
-
viewOfWidget.set(ws.id, g.id);
|
|
138
|
-
widgets.push(ws);
|
|
139
|
-
}
|
|
140
|
-
return { id: g.id, name: g.name, widgets, columns: board.columns, width: (_b = g.size) === null || _b === void 0 ? void 0 : _b.width, height: (_c = g.size) === null || _c === void 0 ? void 0 : _c.height };
|
|
141
|
-
});
|
|
142
|
-
const firstBoard = (_b = dashGroups[0]) === null || _b === void 0 ? void 0 : _b.getMetadata('dashboardBoard');
|
|
143
|
-
// The active view is the one the save left ON camera (x≈0); the others were
|
|
144
|
-
// parked far off-screen by showView. Falls back to the first board when
|
|
145
|
-
// positions are ambiguous (e.g. a single view, or positions not restored).
|
|
146
|
-
const activeGroup = (_c = dashGroups.find((g) => g.position.x > -1000)) !== null && _c !== void 0 ? _c : dashGroups[0];
|
|
147
|
-
const ctx = {
|
|
148
|
-
views: ctxViews,
|
|
149
|
-
groups: new Map(dashGroups.map((g) => [g.id, g])),
|
|
150
|
-
// The SAME map the LoadedDiagramSpec exposes as `boards` — derived, not a copy.
|
|
151
|
-
binders: boards,
|
|
152
|
-
specById,
|
|
153
|
-
viewOfWidget,
|
|
154
|
-
hosts: new Map(),
|
|
155
|
-
renderWidget: paintWidget,
|
|
156
|
-
columns: (_d = firstBoard === null || firstBoard === void 0 ? void 0 : firstBoard.columns) !== null && _d !== void 0 ? _d : 12,
|
|
157
|
-
gap: (_e = firstBoard === null || firstBoard === void 0 ? void 0 : firstBoard.gap) !== null && _e !== void 0 ? _e : 8,
|
|
158
|
-
rowHeight: (_f = firstBoard === null || firstBoard === void 0 ? void 0 : firstBoard.baseRowHeight) !== null && _f !== void 0 ? _f : 130,
|
|
159
|
-
boardW: (_j = (_h = (_g = dashGroups[0]) === null || _g === void 0 ? void 0 : _g.size) === null || _h === void 0 ? void 0 : _h.width) !== null && _j !== void 0 ? _j : 1180,
|
|
160
|
-
boardH: (_m = (_l = (_k = dashGroups[0]) === null || _k === void 0 ? void 0 : _k.size) === null || _l === void 0 ? void 0 : _l.height) !== null && _m !== void 0 ? _m : 660,
|
|
161
|
-
// responsive is NOT in the document (a runtime seam), so it is deliberately
|
|
162
|
-
// absent from the round-trip; width/height/columns/gap/sizing/float/rtl are.
|
|
163
|
-
optionsBase: firstBoard
|
|
164
|
-
? {
|
|
165
|
-
columns: firstBoard.columns,
|
|
166
|
-
gap: firstBoard.gap,
|
|
167
|
-
rowHeight: firstBoard.baseRowHeight,
|
|
168
|
-
sizing: firstBoard.sizing,
|
|
169
|
-
float: firstBoard.float,
|
|
170
|
-
rtl: firstBoard.rtl,
|
|
171
|
-
width: (_p = (_o = dashGroups[0]) === null || _o === void 0 ? void 0 : _o.size) === null || _p === void 0 ? void 0 : _p.width,
|
|
172
|
-
height: (_r = (_q = dashGroups[0]) === null || _q === void 0 ? void 0 : _q.size) === null || _r === void 0 ? void 0 : _r.height,
|
|
173
|
-
}
|
|
174
|
-
: {},
|
|
175
|
-
active: (_s = activeGroup === null || activeGroup === void 0 ? void 0 : activeGroup.id) !== null && _s !== void 0 ? _s : 'main',
|
|
176
|
-
apiRef: null,
|
|
177
|
-
};
|
|
178
|
-
const handle = createDashboardHandle(ctx);
|
|
179
|
-
const renderCustomNode = (node, host) => {
|
|
180
|
-
var _a, _b;
|
|
181
|
-
if (options.renderCustomNode)
|
|
182
|
-
return options.renderCustomNode(node, host);
|
|
183
|
-
// Prefer the ctx spec object so a later handle.update()/repaint() mutates the
|
|
184
|
-
// SAME object this initial paint used; fall back to a fresh rebuild for a
|
|
185
|
-
// loose widget node that belongs to no reconstructed board.
|
|
186
|
-
const widget = (_a = specById.get(node.id)) !== null && _a !== void 0 ? _a : widgetSpecOf(node);
|
|
187
|
-
if (widget) {
|
|
188
|
-
ctx.hosts.set(node.id, host); // captured so update()/repaint() can find the host
|
|
189
|
-
return paintWidget(widget, host);
|
|
190
|
-
}
|
|
191
|
-
(_b = getNodeType(node.type)) === null || _b === void 0 ? void 0 : _b(node, host);
|
|
192
|
-
};
|
|
193
|
-
const finalize = (api) => {
|
|
194
|
-
var _a, _b, _c, _d;
|
|
195
|
-
const a = api;
|
|
196
|
-
if (!a)
|
|
197
|
-
return;
|
|
198
|
-
// -- groups ---------------------------------------------------------------
|
|
199
|
-
// Groups are not part of `nodes`/`edges`, so nothing else would carry them
|
|
200
|
-
// across. A dashboard without its view group is a set of loose widgets: no
|
|
201
|
-
// board frame, and nothing for the grid binder to bind to.
|
|
202
|
-
const live = (_a = a.getModel) === null || _a === void 0 ? void 0 : _a.call(a);
|
|
203
|
-
if (live) {
|
|
204
|
-
for (const group of groups) {
|
|
205
|
-
if (!((_b = live.getGroup) === null || _b === void 0 ? void 0 : _b.call(live, group.id)))
|
|
206
|
-
(_c = live.addGroup) === null || _c === void 0 ? void 0 : _c.call(live, group);
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
if (options.interactive === false)
|
|
210
|
-
return;
|
|
211
|
-
// -- ER / UML -------------------------------------------------------------
|
|
212
|
-
const cards = nodes.filter(isDiagramKitCard);
|
|
213
|
-
if (cards.length > 0 && a.container) {
|
|
214
|
-
for (const node of cards) {
|
|
215
|
-
// The card draws its own selection ring; the node's resize handles would
|
|
216
|
-
// frame it a second time. `erDiagram().finalize` does this on the live
|
|
217
|
-
// model for the same reason — and behaviour is NOT in the document.
|
|
218
|
-
(_d = node.setBehavior) === null || _d === void 0 ? void 0 : _d.call(node, { resizable: false });
|
|
219
|
-
}
|
|
220
|
-
// `rowSelection: false` is recorded only when it is the non-default, so an
|
|
221
|
-
// ordinary document stays byte-identical to what it always was.
|
|
222
|
-
if (!cards.some((n) => n.getMetadata('kitRowSelection') === false)) {
|
|
223
|
-
bindRowInteractions(a);
|
|
224
|
-
}
|
|
225
|
-
if (cards.some((n) => n.getMetadata('kitEditable') === true)) {
|
|
226
|
-
bindCardEditing(a);
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
// -- dashboard boards -----------------------------------------------------
|
|
230
|
-
// Wire the render API into the handle's boxed cell, then bind each board.
|
|
231
|
-
// `bindDashboardGrid` sync()s on construction, so the handle's cellOf/toJSON
|
|
232
|
-
// work immediately — no camera move, no showView, so the paint is byte-for-
|
|
233
|
-
// byte what a boards-only load produced.
|
|
234
|
-
ctx.apiRef = a;
|
|
235
|
-
for (const group of groups) {
|
|
236
|
-
const board = group.getMetadata('dashboardBoard');
|
|
237
|
-
if (!board)
|
|
238
|
-
continue;
|
|
239
|
-
boards.set(group.id, bindDashboardGrid(a, group, Object.assign({}, board)));
|
|
240
|
-
}
|
|
241
|
-
};
|
|
242
|
-
return { nodes, edges: model.getLinks(), renderCustomNode, finalize, model, boards, handle };
|
|
243
|
-
}
|
|
244
|
-
function parseDocument(json) {
|
|
245
|
-
try {
|
|
246
|
-
return JSON.parse(json);
|
|
247
|
-
}
|
|
248
|
-
catch (error) {
|
|
249
|
-
throw new Error(`fromDocument: not a saved diagram (${error.message})`);
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
//# sourceMappingURL=load.js.map
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
const registry = new Map();
|
|
2
|
-
/** Register (or replace) a node type globally. */
|
|
3
|
-
export function registerNodeType(type, renderer) {
|
|
4
|
-
registry.set(type, renderer);
|
|
5
|
-
}
|
|
6
|
-
export function getNodeType(type) {
|
|
7
|
-
return registry.get(type);
|
|
8
|
-
}
|
|
9
|
-
export function hasNodeType(type) {
|
|
10
|
-
return registry.has(type);
|
|
11
|
-
}
|
|
12
|
-
/** Drop a registration (mostly for tests). */
|
|
13
|
-
export function unregisterNodeType(type) {
|
|
14
|
-
registry.delete(type);
|
|
15
|
-
}
|
|
16
|
-
/** Every registered type name. */
|
|
17
|
-
export function registeredNodeTypes() {
|
|
18
|
-
return [...registry.keys()];
|
|
19
|
-
}
|
|
20
|
-
/**
|
|
21
|
-
* Render a node from a slotted `<template data-node-type="...">`.
|
|
22
|
-
*
|
|
23
|
-
* Clones the template's content into `element` and substitutes `node.data` into
|
|
24
|
-
* every `[data-field="key"]` descendant's text. Values are written with
|
|
25
|
-
* `textContent`, never `innerHTML`: a diagram's `data` is frequently
|
|
26
|
-
* user-supplied, and a template engine that injected raw HTML here would be an
|
|
27
|
-
* XSS vector in every host that embeds us.
|
|
28
|
-
*/
|
|
29
|
-
export function renderFromTemplate(template, node, element) {
|
|
30
|
-
const fragment = template.content.cloneNode(true);
|
|
31
|
-
const fields = fragment.querySelectorAll('[data-field]');
|
|
32
|
-
for (const field of Array.from(fields)) {
|
|
33
|
-
const key = field.getAttribute('data-field');
|
|
34
|
-
if (!key)
|
|
35
|
-
continue;
|
|
36
|
-
const value = key === 'id' ? node.id : node.data[key];
|
|
37
|
-
field.textContent = value === undefined || value === null ? '' : String(value);
|
|
38
|
-
}
|
|
39
|
-
element.textContent = '';
|
|
40
|
-
element.appendChild(fragment);
|
|
41
|
-
}
|
|
42
|
-
//# sourceMappingURL=node-type-registry.js.map
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
const builders = new Map();
|
|
2
|
-
/** Register the builder for a master id. Last registration wins. */
|
|
3
|
-
export function registerStencilBuilder(masterId, builder) {
|
|
4
|
-
builders.set(masterId, builder);
|
|
5
|
-
}
|
|
6
|
-
/** The builder for a master id, or undefined for the default template path. */
|
|
7
|
-
export function getStencilBuilder(masterId) {
|
|
8
|
-
return builders.get(masterId);
|
|
9
|
-
}
|
|
10
|
-
/** Drop a registration (tests, or a host replacing a built-in). */
|
|
11
|
-
export function unregisterStencilBuilder(masterId) {
|
|
12
|
-
return builders.delete(masterId);
|
|
13
|
-
}
|
|
14
|
-
/** Every master id that currently resolves to a card builder. */
|
|
15
|
-
export function registeredStencilBuilders() {
|
|
16
|
-
return [...builders.keys()].sort();
|
|
17
|
-
}
|
|
18
|
-
//# sourceMappingURL=builders.js.map
|