@grafloria/element 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/package.json +41 -0
  2. package/src/index.d.ts +87 -0
  3. package/src/index.js +547 -0
  4. package/src/index.js.map +1 -0
  5. package/src/lib/dashboard-kit/dashboard.d.ts +345 -0
  6. package/src/lib/dashboard-kit/dashboard.js +594 -0
  7. package/src/lib/dashboard-kit/dashboard.js.map +1 -0
  8. package/src/lib/dashboard-kit/grid-binder.d.ts +277 -0
  9. package/src/lib/dashboard-kit/grid-binder.js +1635 -0
  10. package/src/lib/dashboard-kit/grid-binder.js.map +1 -0
  11. package/src/lib/dashboard-kit/grid-mapping.d.ts +141 -0
  12. package/src/lib/dashboard-kit/grid-mapping.js +176 -0
  13. package/src/lib/dashboard-kit/grid-mapping.js.map +1 -0
  14. package/src/lib/dashboard-kit/index.d.ts +5 -0
  15. package/src/lib/dashboard-kit/index.js +33 -0
  16. package/src/lib/dashboard-kit/index.js.map +1 -0
  17. package/src/lib/dashboard-kit/styles.d.ts +25 -0
  18. package/src/lib/dashboard-kit/styles.js +203 -0
  19. package/src/lib/dashboard-kit/styles.js.map +1 -0
  20. package/src/lib/dashboard-kit/widgets.d.ts +112 -0
  21. package/src/lib/dashboard-kit/widgets.js +389 -0
  22. package/src/lib/dashboard-kit/widgets.js.map +1 -0
  23. package/src/lib/diagram-kit/card.d.ts +75 -0
  24. package/src/lib/diagram-kit/card.js +209 -0
  25. package/src/lib/diagram-kit/card.js.map +1 -0
  26. package/src/lib/diagram-kit/editing.d.ts +54 -0
  27. package/src/lib/diagram-kit/editing.js +289 -0
  28. package/src/lib/diagram-kit/editing.js.map +1 -0
  29. package/src/lib/diagram-kit/er.d.ts +73 -0
  30. package/src/lib/diagram-kit/er.js +163 -0
  31. package/src/lib/diagram-kit/er.js.map +1 -0
  32. package/src/lib/diagram-kit/handles.d.ts +181 -0
  33. package/src/lib/diagram-kit/handles.js +325 -0
  34. package/src/lib/diagram-kit/handles.js.map +1 -0
  35. package/src/lib/diagram-kit/index.d.ts +8 -0
  36. package/src/lib/diagram-kit/index.js +38 -0
  37. package/src/lib/diagram-kit/index.js.map +1 -0
  38. package/src/lib/diagram-kit/rows.d.ts +53 -0
  39. package/src/lib/diagram-kit/rows.js +147 -0
  40. package/src/lib/diagram-kit/rows.js.map +1 -0
  41. package/src/lib/diagram-kit/styles.d.ts +18 -0
  42. package/src/lib/diagram-kit/styles.js +114 -0
  43. package/src/lib/diagram-kit/styles.js.map +1 -0
  44. package/src/lib/diagram-kit/uml.d.ts +59 -0
  45. package/src/lib/diagram-kit/uml.js +138 -0
  46. package/src/lib/diagram-kit/uml.js.map +1 -0
  47. package/src/lib/diagram-kit/update.d.ts +133 -0
  48. package/src/lib/diagram-kit/update.js +251 -0
  49. package/src/lib/diagram-kit/update.js.map +1 -0
  50. package/src/lib/grafloria-flow-element.d.ts +48 -0
  51. package/src/lib/grafloria-flow-element.js +271 -0
  52. package/src/lib/grafloria-flow-element.js.map +1 -0
  53. package/src/lib/grafloria.d.ts +61 -0
  54. package/src/lib/grafloria.js +72 -0
  55. package/src/lib/grafloria.js.map +1 -0
  56. package/src/lib/load.d.ts +72 -0
  57. package/src/lib/load.js +255 -0
  58. package/src/lib/load.js.map +1 -0
  59. package/src/lib/node-type-registry.d.ts +39 -0
  60. package/src/lib/node-type-registry.js +50 -0
  61. package/src/lib/node-type-registry.js.map +1 -0
@@ -0,0 +1,345 @@
1
+ /**
2
+ * `dashboard({ views, widgets })` — the DATA-FIRST dashboard authoring API.
3
+ *
4
+ * The exact shape `erDiagram()` / `umlDiagram()` have for ER and UML: you
5
+ * describe WHAT the dashboard is, `render()` runs the returned `finalize(api)`
6
+ * automatically, and every interactive part (the pack grid, drag/resize with
7
+ * live push and a truthful placeholder, fit/grow, float, pin, undoable
8
+ * commands) wires itself.
9
+ *
10
+ * Before this, a developer got `bindDashboardGrid()` — a gesture BINDER one
11
+ * layer down — and had to hand-assemble everything above it: a GroupModel per
12
+ * view, a NodeModel per widget, `useHTMLLayer` / `widgetKind` metadata, grid
13
+ * cells, membership, then the bind. The demo page needed ~143 lines just to
14
+ * build its boards. That was the missing authoring layer; this is it:
15
+ *
16
+ * ```js
17
+ * const SPEC = dashboard({
18
+ * columns: 12,
19
+ * sizing: 'fit',
20
+ * views: [{
21
+ * id: 'overview', name: 'Overview',
22
+ * widgets: [
23
+ * { id: 'rev', kind: 'kpi', span: 3, rows: 1, data: {…} },
24
+ * { id: 'trend', kind: 'line', span: 8, rows: 2, data: {…} },
25
+ * { id: 'mix', kind: 'donut', span: 4, rows: 2, pinned: true },
26
+ * ],
27
+ * }],
28
+ * renderWidget: (widget, host) => { … }, // optional: your charts
29
+ * });
30
+ * render(SPEC, host);
31
+ * ```
32
+ *
33
+ * WHAT IT DELIBERATELY DOES NOT DO: pick a charting library. `renderWidget` is
34
+ * the seam — the kit hands you the widget and a raw HTML host (the renderer's
35
+ * custom-node path, which unlike `metadata.html` is not sanitised, so real
36
+ * `<svg>`/`<canvas>` is fine). Omit it and `defaultWidgetRenderer` (widgets.ts)
37
+ * draws the declared `kind` from your own `data` with hand-rolled inline SVG —
38
+ * kpi / line / bar / donut / funnel / table, no dependency, no sample dataset —
39
+ * falling back to a titled frame for kinds it does not know, so a layout is
40
+ * testable before any chart exists.
41
+ *
42
+ * Cells are the truth and live in the existing `GridItemConfig`, so save/load
43
+ * round-trips with no extra work — same as every other kit.
44
+ */
45
+ import { GroupModel, NodeModel, type GridColumnLayout } from '@grafloria/engine';
46
+ import { type DashboardGridHandle, type DashboardGridOptions, type DashboardResponsiveOptions } from './grid-binder';
47
+ /** A widget, declared as data. */
48
+ export interface DashboardWidgetSpec {
49
+ id: string;
50
+ /** Free-form kind string handed back to `renderWidget` (e.g. 'kpi', 'line'). */
51
+ kind?: string;
52
+ /** Column span (default 3) and row span (default 1). */
53
+ span?: number;
54
+ rows?: number;
55
+ /**
56
+ * Explicit cell. Omit and widgets flow in declaration order, wrapping at
57
+ * the column count — the common case needs no coordinates at all.
58
+ */
59
+ x?: number;
60
+ y?: number;
61
+ /** Pinned: never pushed, refuses the mover, survives every reflow. */
62
+ pinned?: boolean;
63
+ /** Your payload — passed straight back to `renderWidget`. */
64
+ data?: Record<string, unknown>;
65
+ /** Optional title used by the built-in fallback renderer. */
66
+ title?: string;
67
+ }
68
+ /** One board. Multiple views are the tab pattern: only one is on-camera. */
69
+ export interface DashboardViewSpec {
70
+ id: string;
71
+ name?: string;
72
+ widgets: DashboardWidgetSpec[];
73
+ /** Per-view overrides of the dashboard-level geometry. */
74
+ columns?: number;
75
+ width?: number;
76
+ height?: number;
77
+ }
78
+ export interface DashboardOptions {
79
+ /** Column count for every view (default 12). */
80
+ columns?: number;
81
+ /** Gap between widgets AND the board padding, px (default 8). */
82
+ gap?: number;
83
+ /** Sizing mode (default 'fit' — squeeze rows; 'grow' extends the board). */
84
+ sizing?: 'fit' | 'grow';
85
+ /** Row height in 'grow' mode, px (default 130). */
86
+ rowHeight?: number;
87
+ /** Board size, px (default 1180 × 660). */
88
+ width?: number;
89
+ height?: number;
90
+ /** Engine float mode (default false → gravity packs upward). */
91
+ float?: boolean;
92
+ /**
93
+ * RIGHT-TO-LEFT boards: column x=0 renders at the RIGHT edge and columns run
94
+ * leftwards. Cells are untouched — the same `widgets` array describes the
95
+ * same layout in both directions, and a layout saved in one renders mirrored
96
+ * in the other with identical cells.
97
+ */
98
+ rtl?: boolean;
99
+ /**
100
+ * RESPONSIVE COLUMN COUNT: derive the live count from each board's width.
101
+ * `{ columnWidth: 100 }` gives one column per ~100px (capped by `columns`);
102
+ * `{ breakpoints: [{ w: 480, c: 1 }, { w: 900, c: 6 }] }` names the steps.
103
+ * The count changes through the engine's per-column layout CACHE, so
104
+ * narrowing and widening again restores the wide layout exactly, and
105
+ * `toJSON()` keeps serialising the widest layout however narrow the board is.
106
+ */
107
+ responsive?: DashboardResponsiveOptions;
108
+ /** One view, or many (the tab pattern). Mutually exclusive with `widgets`. */
109
+ views?: DashboardViewSpec[];
110
+ /** Shorthand for a single unnamed view. */
111
+ widgets?: DashboardWidgetSpec[];
112
+ /**
113
+ * Paint a widget into its host element. Called once per widget when it
114
+ * mounts (the host is reused across re-renders, so this is not a per-frame
115
+ * hook). Omit for a titled placeholder frame.
116
+ */
117
+ renderWidget?: (widget: DashboardWidgetSpec, host: HTMLElement) => void;
118
+ /** Fires after any committed gesture, with the view whose layout changed. */
119
+ onLayoutChange?: (viewId: string, widgets: DashboardWidgetSpec[]) => void;
120
+ /** Extra binder options, merged last (escape hatch to the layer below). */
121
+ binder?: Partial<DashboardGridOptions>;
122
+ }
123
+ /** What `dashboard()` returns — a render spec plus the runtime handle. */
124
+ export interface DashboardSpec {
125
+ nodes: Array<Record<string, unknown>>;
126
+ edges: Array<Record<string, unknown>>;
127
+ renderCustomNode: (node: unknown, host: HTMLElement) => void;
128
+ finalize: (api: unknown) => void;
129
+ /** Live handle, populated by finalize(). */
130
+ readonly handle: DashboardHandle;
131
+ }
132
+ /**
133
+ * A whole board as plain data: every `DashboardOptions` field except the
134
+ * function seams. `dashboard({ ...snapshot, renderWidget })` rebuilds it.
135
+ *
136
+ * Typed as an Omit rather than a hand-written twin on purpose — a field added
137
+ * to `DashboardOptions` then joins the snapshot automatically instead of being
138
+ * silently dropped, which is the exact failure this type exists to end.
139
+ */
140
+ export type DashboardSnapshot = Omit<DashboardOptions, 'renderWidget' | 'onLayoutChange' | 'views'> & {
141
+ views: DashboardViewSpec[];
142
+ };
143
+ /** The typed façade — the `erTable`/`umlClass` equivalent for dashboards. */
144
+ export interface DashboardHandle {
145
+ /** The view ids, in declaration order. */
146
+ readonly views: string[];
147
+ /** Show a view (the others park off-camera) and frame it. */
148
+ showView(id: string): void;
149
+ /** The currently shown view id. */
150
+ readonly activeView: string;
151
+ /** A widget handle by id (undefined when unknown). */
152
+ widget(id: string): WidgetHandle | undefined;
153
+ /** Every widget handle of a view (default: the active one). */
154
+ widgetsOf(viewId?: string): WidgetHandle[];
155
+ /** Live sizing/float switches — the two prototype toggles. */
156
+ setSizing(mode: 'fit' | 'grow'): void;
157
+ getSizing(): 'fit' | 'grow';
158
+ setFloat(on: boolean): void;
159
+ getFloat(): boolean;
160
+ /**
161
+ * Set the COLUMN COUNT of every board (or one view), live. Goes through the
162
+ * engine's per-column layout cache, so shrinking then growing back restores
163
+ * the wide layout rather than re-deriving it. An explicit call PINS the
164
+ * count — the width-driven `responsive` evaluator stops overriding it.
165
+ */
166
+ setColumns(n: number, layout?: GridColumnLayout, viewId?: string): void;
167
+ /** The LIVE column count of a view (default: the active one). */
168
+ getColumns(viewId?: string): number;
169
+ /** RTL mirroring, live — pixels only, cells never change. */
170
+ setRtl(on: boolean): void;
171
+ getRtl(): boolean;
172
+ /**
173
+ * Add a widget to a view. CREATES the node (you do not pre-build one), wires
174
+ * its metadata, and commits node + membership as ONE undoable step.
175
+ * Auto-positions when the spec names no cell.
176
+ */
177
+ addWidget(spec: DashboardWidgetSpec, viewId?: string): WidgetHandle | undefined;
178
+ /**
179
+ * Re-read every board from the model — call after undo/redo, or any
180
+ * out-of-band mutation, so the grid and the projection agree again.
181
+ */
182
+ refresh(): void;
183
+ /** Re-frame the camera on a view (default: the active one). */
184
+ fit(viewId?: string): void;
185
+ /** Live geometry of a view's board (columns, gap, rows, rowHeight, frame…). */
186
+ metrics(viewId?: string): ReturnType<DashboardGridHandle['metrics']> | undefined;
187
+ /**
188
+ * The whole board as plain data — feed it straight back to `dashboard()`:
189
+ *
190
+ * ```ts
191
+ * dashboard({ ...handle.toJSON(), renderWidget }); // a true round trip
192
+ * ```
193
+ *
194
+ * Everything `DashboardOptions` takes EXCEPT the function seams
195
+ * (`renderWidget`, `onLayoutChange`), which cannot be written to a file and
196
+ * must be supplied again on the way back in.
197
+ *
198
+ * Values are read from the LIVE board, not from the authored literal, so a
199
+ * mode or column count the user changed after mount is what you get back.
200
+ *
201
+ * This used to return only `views`, which made the round-trip claim true of
202
+ * the layout and false of the board: a board authored `grow` at a 10-column,
203
+ * 6px-gap geometry reloaded as a 12-column `fit` one. It is also what
204
+ * `JSON.stringify(handle)` calls, so the partial answer was a permanent
205
+ * footgun in a save API rather than merely an omission.
206
+ */
207
+ toJSON(): DashboardSnapshot;
208
+ /**
209
+ * The node ids ONE view occupies — pass straight to `includeIds` to export
210
+ * just that board:
211
+ *
212
+ * ```ts
213
+ * api.export('pdf', { includeIds: handle.exportIds() });
214
+ * ```
215
+ *
216
+ * WHY THIS EXISTS. Tabs park the inactive views far off-camera, which is
217
+ * invisible on screen and ruinous on export: `export()` frames the whole
218
+ * MODEL, so a two-view board writes a ~21,000px document that is almost
219
+ * entirely empty — with no warning, because nothing is technically wrong.
220
+ * Scoping was always possible; knowing WHAT to scope to was not.
221
+ *
222
+ * The set includes the view's GROUP as well as its widgets. Rolling this by
223
+ * hand from `toJSON()` looks equivalent and is not — it drops the group, and
224
+ * the widgets export without the frame they sit in.
225
+ */
226
+ exportIds(viewId?: string): Set<string>;
227
+ /**
228
+ * THE DOCUMENTED ESCAPE HATCH: the view's own `bindDashboardGrid` handle
229
+ * (default: the active view). Reach for it only for what this façade does
230
+ * not cover yet — palette drag-in (`beginPaletteDrag`), board `metrics()`,
231
+ * `cellRectOf`, `planRemoval`, and re-`sync()` after an external undo. Every
232
+ * call site is a named gap in this API, not a normal way to drive a board.
233
+ */
234
+ binderOf(viewId?: string): DashboardGridHandle | undefined;
235
+ dispose(): void;
236
+ }
237
+ /** One widget's OO surface (mirrors ErTable/UmlClass). */
238
+ export interface WidgetHandle {
239
+ readonly id: string;
240
+ readonly viewId: string;
241
+ readonly node: NodeModel | undefined;
242
+ /** The DECLARED spec — read it back (title/kind/data) without a side map. */
243
+ readonly spec: DashboardWidgetSpec;
244
+ /** Current cell, as data. */
245
+ readonly cell: {
246
+ x: number;
247
+ y: number;
248
+ w: number;
249
+ h: number;
250
+ } | undefined;
251
+ /** The world rect the current cell projects to. */
252
+ readonly rect: {
253
+ x: number;
254
+ y: number;
255
+ width: number;
256
+ height: number;
257
+ } | undefined;
258
+ /** Resize in CELLS. Resolves TRUE when the board accepted it. */
259
+ resize(span: number, rows: number): Promise<boolean>;
260
+ /** Move to a cell. Resolves TRUE when the board accepted it. */
261
+ moveTo(x: number, y: number): Promise<boolean>;
262
+ /** Pin / unpin (a pinned widget refuses the mover and never gets pushed). */
263
+ pin(on?: boolean): void;
264
+ readonly pinned: boolean;
265
+ /** Raise / lower — one undoable step each (mirrors the toolbar commands). */
266
+ bringToFront(): void;
267
+ sendToBack(): void;
268
+ /**
269
+ * Remove it — ONE undoable step including the survivors' re-pack.
270
+ * `displaced` accepts the commands a drag-out gesture already computed;
271
+ * omit it and the handle plans them itself.
272
+ */
273
+ remove(displaced?: unknown[]): void;
274
+ /** Replace the widget's `data` (and optionally title) and repaint. */
275
+ update(patch: Partial<Pick<DashboardWidgetSpec, 'data' | 'title' | 'kind'>>): void;
276
+ /** Repaint through `renderWidget` (after your data changed). */
277
+ repaint(): void;
278
+ }
279
+ /**
280
+ * The API surface the handle drives — the slice of a DiagramInstance both
281
+ * `dashboard().finalize` and `fromDocument().finalize` hand in. Named (was an
282
+ * inline type on the old `apiRef` local) because two call sites now share it.
283
+ */
284
+ export interface DashboardApiRef {
285
+ getModel(): {
286
+ getNode(id: string): NodeModel | undefined;
287
+ addGroup(g: GroupModel): void;
288
+ getGroup(id: string): GroupModel | undefined;
289
+ removeGroup?(id: string): unknown;
290
+ };
291
+ getEngine?: () => {
292
+ commandManager: {
293
+ execute(c: unknown): unknown;
294
+ };
295
+ };
296
+ renderNow(): void;
297
+ viewport?: {
298
+ fitToBounds(r: unknown, pad: number, o?: unknown): void;
299
+ };
300
+ }
301
+ /**
302
+ * Everything one `DashboardHandle` closes over, gathered into ONE object so a
303
+ * single builder can serve both `dashboard()` (context built from the authored
304
+ * literal) and `fromDocument()` (context reconstructed from the loaded model).
305
+ *
306
+ * `active` and `apiRef` are the two MUTABLE cells: the handle READS them on
307
+ * every call and the builder WRITES them (showView reassigns `active`, the
308
+ * caller's finalize sets `apiRef`). They live here rather than as free `let`s
309
+ * precisely because there are now two call sites — a boxed cell one builder
310
+ * reads and writes is the whole reason a second handle implementation, which
311
+ * would silently drift, is not needed.
312
+ */
313
+ export interface DashboardHandleContext {
314
+ /** The views — MUTATED in place by addWidget (push) and remove (filter). */
315
+ views: DashboardViewSpec[];
316
+ groups: Map<string, GroupModel>;
317
+ binders: Map<string, DashboardGridHandle>;
318
+ specById: Map<string, DashboardWidgetSpec>;
319
+ viewOfWidget: Map<string, string>;
320
+ hosts: Map<string, HTMLElement>;
321
+ renderWidget: (widget: DashboardWidgetSpec, host: HTMLElement) => void;
322
+ columns: number;
323
+ gap: number;
324
+ rowHeight: number;
325
+ boardW: number;
326
+ boardH: number;
327
+ /**
328
+ * Spread verbatim into `toJSON()` output — carries width/height/responsive
329
+ * and any other authored option so a new `DashboardOptions` field round-trips
330
+ * for free (dashboard() passes the whole `options`; fromDocument() passes the
331
+ * geometry it can recover from the persisted board metadata).
332
+ */
333
+ optionsBase: Partial<DashboardOptions>;
334
+ /** MUTABLE — reassigned by showView(). */
335
+ active: string;
336
+ /** MUTABLE — set by the caller's finalize once the render API exists. */
337
+ apiRef: DashboardApiRef | null;
338
+ }
339
+ /**
340
+ * Build THE `DashboardHandle` — the one and only implementation, shared by
341
+ * `dashboard()` and `fromDocument()`. Reads/writes the mutable `ctx.active` /
342
+ * `ctx.apiRef` cells so the caller's finalize can wire the API in afterwards.
343
+ */
344
+ export declare function createDashboardHandle(ctx: DashboardHandleContext): DashboardHandle;
345
+ export declare function dashboard(options: DashboardOptions): DashboardSpec;