@grafloria/element 0.3.23 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/README.md +9 -7
  2. package/package.json +7 -9
  3. package/src/index.d.ts +16 -16
  4. package/src/index.js +58 -339
  5. package/src/lib/dashboard-kit/dashboard.d.ts +1 -1
  6. package/src/lib/dashboard-kit/dashboard.js +22 -26
  7. package/src/lib/dashboard-kit/grid-binder.d.ts +1 -1
  8. package/src/lib/dashboard-kit/grid-binder.js +62 -65
  9. package/src/lib/dashboard-kit/grid-mapping.js +14 -26
  10. package/src/lib/dashboard-kit/index.d.ts +5 -5
  11. package/src/lib/dashboard-kit/index.js +5 -30
  12. package/src/lib/dashboard-kit/styles.js +4 -8
  13. package/src/lib/dashboard-kit/widgets.d.ts +1 -1
  14. package/src/lib/dashboard-kit/widgets.js +16 -26
  15. package/src/lib/diagram-kit/card.d.ts +2 -2
  16. package/src/lib/diagram-kit/card.js +22 -32
  17. package/src/lib/diagram-kit/editing.js +15 -20
  18. package/src/lib/diagram-kit/er.d.ts +1 -1
  19. package/src/lib/diagram-kit/er.js +15 -18
  20. package/src/lib/diagram-kit/handles.d.ts +4 -4
  21. package/src/lib/diagram-kit/handles.js +16 -29
  22. package/src/lib/diagram-kit/index.d.ts +10 -10
  23. package/src/lib/diagram-kit/index.js +10 -50
  24. package/src/lib/diagram-kit/join-guidance.js +9 -18
  25. package/src/lib/diagram-kit/rows.js +1 -4
  26. package/src/lib/diagram-kit/styles.js +4 -8
  27. package/src/lib/diagram-kit/uml.js +10 -13
  28. package/src/lib/diagram-kit/update.d.ts +1 -1
  29. package/src/lib/diagram-kit/update.js +19 -26
  30. package/src/lib/grafloria-flow-element.js +19 -24
  31. package/src/lib/grafloria.d.ts +4 -4
  32. package/src/lib/grafloria.js +12 -17
  33. package/src/lib/load.d.ts +3 -3
  34. package/src/lib/load.js +19 -22
  35. package/src/lib/node-type-registry.js +6 -14
  36. package/src/lib/stencil-kit/builders.js +4 -10
  37. package/src/lib/stencil-kit/card-builders.js +14 -17
  38. package/src/lib/stencil-kit/index.d.ts +7 -7
  39. package/src/lib/stencil-kit/index.js +6 -16
  40. package/src/lib/stencil-kit/palette.js +14 -17
  41. package/src/lib/stencil-kit/shape-data.js +25 -28
  42. package/src/lib/stencil-kit/styles.js +1 -4
  43. package/esm/src/index.js +0 -295
  44. package/esm/src/lib/dashboard-kit/dashboard.js +0 -590
  45. package/esm/src/lib/dashboard-kit/grid-binder.js +0 -1633
  46. package/esm/src/lib/dashboard-kit/grid-mapping.js +0 -164
  47. package/esm/src/lib/dashboard-kit/index.js +0 -8
  48. package/esm/src/lib/dashboard-kit/styles.js +0 -205
  49. package/esm/src/lib/dashboard-kit/widgets.js +0 -379
  50. package/esm/src/lib/diagram-kit/card.js +0 -199
  51. package/esm/src/lib/diagram-kit/editing.js +0 -448
  52. package/esm/src/lib/diagram-kit/er.js +0 -160
  53. package/esm/src/lib/diagram-kit/handles.js +0 -312
  54. package/esm/src/lib/diagram-kit/index.js +0 -15
  55. package/esm/src/lib/diagram-kit/join-guidance.js +0 -322
  56. package/esm/src/lib/diagram-kit/rows.js +0 -144
  57. package/esm/src/lib/diagram-kit/styles.js +0 -118
  58. package/esm/src/lib/diagram-kit/uml.js +0 -135
  59. package/esm/src/lib/diagram-kit/update.js +0 -244
  60. package/esm/src/lib/grafloria-flow-element.js +0 -266
  61. package/esm/src/lib/grafloria.js +0 -69
  62. package/esm/src/lib/load.js +0 -252
  63. package/esm/src/lib/node-type-registry.js +0 -42
  64. package/esm/src/lib/stencil-kit/builders.js +0 -18
  65. package/esm/src/lib/stencil-kit/card-builders.js +0 -105
  66. package/esm/src/lib/stencil-kit/index.js +0 -9
  67. package/esm/src/lib/stencil-kit/palette.js +0 -427
  68. package/esm/src/lib/stencil-kit/shape-data.js +0 -537
  69. package/esm/src/lib/stencil-kit/styles.js +0 -126
@@ -1,1633 +0,0 @@
1
- /**
2
- * `bindDashboardGrid(api, group, options)` — the dashboard-grid gesture binder
3
- * (Phase 2 of the dashboard-grid plan; the plan page's Section-1 prototype is
4
- * the executable spec of the feel this reproduces).
5
- *
6
- * One binder owns ONE `GridPackEngine` mirroring the group's members — the
7
- * engine is the single source of truth for cell math (three swap shapes, the
8
- * >50% anti-jitter gate, locked refusal, push-down + skipDown, teleport-home
9
- * settle). This module only converts: pointer → cells in, cells → pixels out,
10
- * gesture → ONE undoable BatchCommand.
11
- *
12
- * HOW THE RENDERER'S OWN NODE-DRAG IS BYPASSED — a registered `CanvasTool`
13
- * (the renderer's wave-6 tool registry, `registerTool`). The tool makes a
14
- * POINT-SPECIFIC claim (priority 2, like the whiteboard ink tool): any
15
- * pointerdown whose hit node is a member of the bound group, or that lands
16
- * inside a member group's frame with no node under it (the KPI-slab dead
17
- * zone — otherwise the built-in group-drag would fight the pack layout
18
- * there). A claimed gesture bypasses the ENTIRE built-in ladder — node-drag,
19
- * selection, marquee, wave-12 resize handles — and the DomEventBinder
20
- * forwards move/up/cancel with world coordinates already computed. This beats
21
- * `behavior.draggable = false` (which still runs selection and shows the
22
- * built-in resize handles) and beats capture-phase DOM listeners (which fight
23
- * the binder's own preventDefault bookkeeping). `click` is NOT a
24
- * compatibility mouse event, so the page's click-to-focus keeps firing for
25
- * sub-threshold presses.
26
- *
27
- * DURING a gesture, positions are written through the MODEL, not commands:
28
- * - the held tile is a GHOST — transition-exempt, tracking the cursor in
29
- * WORLD coordinates (no `position: fixed` hack; the html layer carries the
30
- * camera transform, and the tool events arrive already converted);
31
- * - every cell crossing runs `engine.moveCheck` / `resizeCheck`; every item
32
- * the engine displaced is re-projected cells→pixels. Those writes are
33
- * DERIVED state, so they run inside `diagram.runSystemWrite` — which is
34
- * also what lets a PINNED tile's pixels follow a fit-mode row-height
35
- * change without violating the authoritative geometry lock (its CELLS
36
- * never change; the pin protects the document fact, not the projection);
37
- * - a dashed PLACEHOLDER (`.axdb-ph`) always shows the engine's current cell
38
- * for the held tile — the truthful drop preview.
39
- *
40
- * The gesture ends in ONE `BatchCommand`: `SetGridItemCommand` per tile whose
41
- * cells changed (cells are the truth) + `MoveNodeCommand`/`ResizeNodeCommand`
42
- * (merge-opted-OUT) per unlocked node whose geometry changed, so a bare undo
43
- * restores geometry AND cells with no binder help. Escape restores the
44
- * engine snapshot (`cancelGesture`) and every pixel — nothing is committed.
45
- *
46
- * MEMBER GROUPS (e.g. a KPI section) ride as LOCKED slab items — never
47
- * pushed, never packed, drags onto them refused (the prototype's pinned
48
- * full-width row). Their cells persist in group metadata `gridItem` (groups
49
- * carry no GridItemConfig); their INNER layout is their own concern — bind a
50
- * second `bindDashboardGrid` on the section for a nested pack grid.
51
- */
52
- import { __awaiter } from "tslib";
53
- import { AddToGroupCommand, BatchCommand, Command, GridPackEngine, RemoveFromGroupCommand, } from '@grafloria/engine';
54
- import { registerTool } from '@grafloria/renderer';
55
- import { buildCommitCommands, cellFromGridItem, cellToRect, columnUnitFor, gridItemFromCell, pointToCell, rowHeightFor, sizeToSpan, } from './grid-mapping';
56
- import { ensureDashboardKitStyles } from './styles';
57
- const BOARD_REGISTRY = new Map();
58
- /**
59
- * Undoable cell+frame write for a GROUP member (the strip's slab). The engine
60
- * has Move/Resize commands for nodes but none for a group's frame, and slab
61
- * cells live in group metadata — this closes nested height escalation into
62
- * the gesture's single BatchCommand so one undo restores the strip too.
63
- */
64
- class SetGroupCellCommand extends Command {
65
- constructor(groupId, cellBefore, cellAfter, frameBefore, frameAfter) {
66
- super('Resize section');
67
- this.groupId = groupId;
68
- this.cellBefore = cellBefore;
69
- this.cellAfter = cellAfter;
70
- this.frameBefore = frameBefore;
71
- this.frameAfter = frameAfter;
72
- }
73
- apply(context, cell, frame) {
74
- const diagram = context.diagram;
75
- const grp = diagram === null || diagram === void 0 ? void 0 : diagram.getGroup(this.groupId);
76
- if (!grp)
77
- return;
78
- grp.setMetadata('gridItem', gridItemFromCell(cell));
79
- grp.setFrame(Object.assign({}, frame));
80
- }
81
- execute(context) {
82
- this.apply(context, this.cellAfter, this.frameAfter);
83
- }
84
- undo(context) {
85
- this.apply(context, this.cellBefore, this.frameBefore);
86
- }
87
- serialize() {
88
- return {
89
- id: this.id,
90
- name: this.name,
91
- timestamp: this.timestamp,
92
- data: {
93
- groupId: this.groupId,
94
- cellBefore: this.cellBefore,
95
- cellAfter: this.cellAfter,
96
- frameBefore: this.frameBefore,
97
- frameAfter: this.frameAfter,
98
- },
99
- };
100
- }
101
- }
102
- const DRAG_THRESHOLD = 4;
103
- const GLIDE_OFF_DELAY = 400;
104
- let binderSeq = 0;
105
- export function bindDashboardGrid(api, group, options = {}) {
106
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
107
- ensureDashboardKitStyles();
108
- const diagram = api.getModel();
109
- /** The DECLARED count — the board's authored width, and the responsive cap. */
110
- const maxColumns = (_a = options.columns) !== null && _a !== void 0 ? _a : 12;
111
- /** The LIVE count. Responsive width evaluation and `setColumns` move it. */
112
- let columns = maxColumns;
113
- let rtl = options.rtl === true;
114
- const responsive = options.responsive;
115
- /** An explicit `setColumns` pins the count and stops width evaluation. */
116
- let responsivePinned = false;
117
- const gap = (_b = options.gap) !== null && _b !== void 0 ? _b : 12;
118
- const padding = (_c = options.padding) !== null && _c !== void 0 ? _c : gap;
119
- const baseRowHeight = (_d = options.baseRowHeight) !== null && _d !== void 0 ? _d : 110;
120
- const minRowHeight = (_e = options.minRowHeight) !== null && _e !== void 0 ? _e : 28;
121
- let float = (_f = options.float) !== null && _f !== void 0 ? _f : false;
122
- const maxRows = options.maxRows;
123
- const dragOut = (_g = options.dragOut) !== null && _g !== void 0 ? _g : 'cancel';
124
- const wantHandles = options.resizeHandles !== false;
125
- const designH = (_k = (_h = options.designHeight) !== null && _h !== void 0 ? _h : (_j = group.size) === null || _j === void 0 ? void 0 : _j.height) !== null && _k !== void 0 ? _k : 0;
126
- let sizing = (_l = options.sizing) !== null && _l !== void 0 ? _l : 'fit';
127
- let engine = new GridPackEngine([], { columns, float, maxRows });
128
- let gesture = null;
129
- let disposed = false;
130
- /** Reentrancy guard: our own derived frame writes must not re-project. */
131
- let writing = false;
132
- let placeholder = null;
133
- /** Foreign tile currently adopted from another binder's gesture. */
134
- let adoptedGhostId = null;
135
- let glideTimer = null;
136
- let ghostTimer = null;
137
- const frame = () => {
138
- var _a, _b, _c, _d;
139
- return ({
140
- x: group.position.x,
141
- y: group.position.y,
142
- width: (_b = (_a = group.size) === null || _a === void 0 ? void 0 : _a.width) !== null && _b !== void 0 ? _b : 0,
143
- height: (_d = (_c = group.size) === null || _c === void 0 ? void 0 : _c.height) !== null && _d !== void 0 ? _d : 0,
144
- });
145
- };
146
- /** Entity size with GroupModel's optionality flattened away. */
147
- const sizeOf = (e) => { var _a; return (_a = e.size) !== null && _a !== void 0 ? _a : { width: 0, height: 0 }; };
148
- /** Mapping geometry. 'fit' derives row height from the LIVE frame height
149
- * (which `enforceBoardHeight` pins to the design height), so an externally
150
- * resized board still fits itself. */
151
- const geom = () => ({
152
- columns,
153
- gap,
154
- padding,
155
- sizing,
156
- baseRowHeight,
157
- minRowHeight,
158
- designHeight: sizing === 'fit' ? frame().height : designH,
159
- rtl,
160
- });
161
- const rows = () => Math.max(1, engine.rows());
162
- const htmlLayer = () => api.container.querySelector('.grafloria-html-layer');
163
- const hostOf = (id) => {
164
- const esc = typeof CSS !== 'undefined' && CSS.escape ? CSS.escape(id) : id.replace(/"/g, '\\"');
165
- return api.container.querySelector(`.grafloria-node-host[data-node-id="${esc}"]`);
166
- };
167
- const memberEntity = (id) => { var _a; return (_a = diagram.getNode(id)) !== null && _a !== void 0 ? _a : diagram.getGroup(id); };
168
- const isGroupMember = (id) => !diagram.getNode(id) && !!diagram.getGroup(id);
169
- // -- cells <-> members -----------------------------------------------------
170
- /** The engine item a member should enter as (cells from GridItemConfig /
171
- * group metadata; spans falling back to metadata columnSpan/rowSpan; last
172
- * resort: adopt from the current pixel geometry). */
173
- const itemFor = (id) => {
174
- var _a, _b, _c, _d, _e;
175
- const node = diagram.getNode(id);
176
- if (node) {
177
- const spanMeta = Number((_a = node.getMetadata) === null || _a === void 0 ? void 0 : _a.call(node, 'columnSpan')) || 0;
178
- const rowsMeta = Number((_b = node.getMetadata) === null || _b === void 0 ? void 0 : _b.call(node, 'rowSpan')) || 0;
179
- const locked = ((_c = node.state) === null || _c === void 0 ? void 0 : _c.locked) === true;
180
- const cell = cellFromGridItem((_d = node.getGridItem) === null || _d === void 0 ? void 0 : _d.call(node), {
181
- w: spanMeta || 1,
182
- h: rowsMeta || 1,
183
- });
184
- if (cell)
185
- return Object.assign(Object.assign({ id }, cell), { locked });
186
- const f = frame();
187
- const g = geom();
188
- if (node.position && (node.position.x !== 0 || node.position.y !== 0)) {
189
- // First adoption from pixels: where the tile already sits. The SPAN is
190
- // resolved first because the mirrored (RTL) mapping needs it to turn a
191
- // left edge into a cell.
192
- const s = sizeToSpan(node.size.width, node.size.height, f, g, rows());
193
- const p = pointToCell(node.position.x, node.position.y, f, g, rows(), spanMeta || s.w);
194
- return {
195
- id,
196
- x: Math.max(0, p.x),
197
- y: Math.max(0, p.y),
198
- w: spanMeta || s.w,
199
- h: rowsMeta || s.h,
200
- locked,
201
- };
202
- }
203
- return { id, x: 0, y: 0, w: spanMeta || 1, h: rowsMeta || 1, locked, autoPosition: true };
204
- }
205
- const grp = diagram.getGroup(id);
206
- const cell = grp
207
- ? cellFromGridItem((_e = grp.getMetadata) === null || _e === void 0 ? void 0 : _e.call(grp, 'gridItem'))
208
- : null;
209
- // Member groups are LOCKED slabs (see the module doc).
210
- if (cell)
211
- return Object.assign(Object.assign({ id }, cell), { locked: true });
212
- return { id, x: 0, y: 0, w: columns, h: 1, locked: true, autoPosition: true };
213
- };
214
- /** Persist adopted cells so save/undo round-trips them. */
215
- const persistAdoptedCell = (id, item) => {
216
- var _a, _b;
217
- const cell = { x: item.x, y: item.y, w: item.w, h: item.h };
218
- const node = diagram.getNode(id);
219
- if (node) {
220
- if (!cellFromGridItem((_a = node.getGridItem) === null || _a === void 0 ? void 0 : _a.call(node)))
221
- node.setGridItem(gridItemFromCell(cell));
222
- return;
223
- }
224
- const grp = diagram.getGroup(id);
225
- if (grp && !cellFromGridItem((_b = grp.getMetadata) === null || _b === void 0 ? void 0 : _b.call(grp, 'gridItem'))) {
226
- grp.setMetadata('gridItem', gridItemFromCell(cell));
227
- }
228
- };
229
- // -- projection: cells -> pixels -------------------------------------------
230
- /** Write one member's projected rect (derived state → system write). */
231
- const writeRect = (id, r) => {
232
- const node = diagram.getNode(id);
233
- if (node) {
234
- if (Math.abs(node.position.x - r.x) > 0.25 ||
235
- Math.abs(node.position.y - r.y) > 0.25 ||
236
- Math.abs(node.size.width - r.width) > 0.25 ||
237
- Math.abs(node.size.height - r.height) > 0.25) {
238
- diagram.runSystemWrite(() => {
239
- var _a;
240
- node.setPosition(r.x, r.y);
241
- node.setSize(r.width, r.height, (_a = node.size.depth) !== null && _a !== void 0 ? _a : 0);
242
- });
243
- }
244
- return;
245
- }
246
- const grp = diagram.getGroup(id);
247
- if (grp) {
248
- const p = grp.position;
249
- const s = sizeOf(grp);
250
- if (Math.abs(p.x - r.x) > 0.25 ||
251
- Math.abs(p.y - r.y) > 0.25 ||
252
- Math.abs(s.width - r.width) > 0.25 ||
253
- Math.abs(s.height - r.height) > 0.25) {
254
- diagram.runSystemWrite(() => grp.setFrame(Object.assign({}, r)));
255
- }
256
- }
257
- };
258
- /** Enforce the board-frame height the sizing mode implies. */
259
- const enforceBoardHeight = () => {
260
- if (designH <= 0)
261
- return;
262
- const r = rows();
263
- const target = sizing === 'fit'
264
- ? designH
265
- : Math.max(designH, 2 * padding + r * baseRowHeight + (r - 1) * gap);
266
- const f = frame();
267
- if (Math.abs(f.height - target) > 0.5) {
268
- writing = true;
269
- try {
270
- diagram.runSystemWrite(() => group.setFrame({ x: f.x, y: f.y, width: f.width, height: target }));
271
- }
272
- finally {
273
- writing = false;
274
- }
275
- }
276
- };
277
- /** Project every member from its engine cells (the ghost is exempt). */
278
- const project = () => {
279
- enforceBoardHeight();
280
- writing = true;
281
- try {
282
- const f = frame();
283
- const g = geom();
284
- const r = rows();
285
- for (const item of engine.getItems()) {
286
- if ((gesture === null || gesture === void 0 ? void 0 : gesture.started) && item.id === gesture.id)
287
- continue; // the ghost
288
- if (item.id === adoptedGhostId)
289
- continue; // a ghost adopted from another binder
290
- writeRect(item.id, cellToRect(item, f, g, r));
291
- }
292
- }
293
- finally {
294
- writing = false;
295
- }
296
- syncPlaceholder();
297
- };
298
- // -- placeholder / ghost chrome --------------------------------------------
299
- /** The placeholder exists ONLY while a gesture is live — so at any moment
300
- * the DOM holds at most one `.axdb-ph` per active gesture, not one idle
301
- * div per bound board. */
302
- const syncPlaceholder = () => {
303
- const ghostId = adoptedGhostId !== null && adoptedGhostId !== void 0 ? adoptedGhostId : ((gesture === null || gesture === void 0 ? void 0 : gesture.started) && !gesture.removedFromBoard ? gesture.id : null);
304
- const item = ghostId ? engine.getItem(ghostId) : undefined;
305
- const live = !!item;
306
- if (!live || !item) {
307
- placeholder === null || placeholder === void 0 ? void 0 : placeholder.remove();
308
- placeholder = null;
309
- return;
310
- }
311
- const layer = htmlLayer();
312
- if (!layer)
313
- return;
314
- if (!placeholder || placeholder.parentElement !== layer) {
315
- placeholder === null || placeholder === void 0 ? void 0 : placeholder.remove();
316
- placeholder = document.createElement('div');
317
- placeholder.className = 'axdb-ph';
318
- layer.prepend(placeholder);
319
- }
320
- const r = cellToRect(item, frame(), geom(), rows());
321
- placeholder.style.display = 'block';
322
- placeholder.style.left = `${r.x}px`;
323
- placeholder.style.top = `${r.y}px`;
324
- placeholder.style.width = `${r.width}px`;
325
- placeholder.style.height = `${r.height}px`;
326
- };
327
- const armGlide = () => {
328
- var _a;
329
- (_a = htmlLayer()) === null || _a === void 0 ? void 0 : _a.classList.add('axdb-glide');
330
- if (glideTimer)
331
- clearTimeout(glideTimer);
332
- };
333
- const disarmGlideSoon = () => {
334
- if (glideTimer)
335
- clearTimeout(glideTimer);
336
- glideTimer = setTimeout(() => { var _a; return (_a = htmlLayer()) === null || _a === void 0 ? void 0 : _a.classList.remove('axdb-glide'); }, GLIDE_OFF_DELAY);
337
- };
338
- const setGhost = (id, on) => {
339
- const host = hostOf(id);
340
- if (!host)
341
- return;
342
- if (on) {
343
- if (ghostTimer)
344
- clearTimeout(ghostTimer);
345
- host.classList.add('axdb-ghost');
346
- host.classList.remove('axdb-out');
347
- }
348
- else {
349
- host.classList.remove('axdb-out');
350
- // Keep transition-exemption through the drop write so the snap into the
351
- // placeholder is INSTANT (gridstack-style), then let glides resume.
352
- if (ghostTimer)
353
- clearTimeout(ghostTimer);
354
- ghostTimer = setTimeout(() => host.classList.remove('axdb-ghost'), 60);
355
- }
356
- };
357
- // -- resize handles ---------------------------------------------------------
358
- const syncHandles = () => {
359
- var _a, _b;
360
- if (!wantHandles || disposed)
361
- return;
362
- for (const id of (_a = group.members) !== null && _a !== void 0 ? _a : []) {
363
- const node = diagram.getNode(id);
364
- if (!node)
365
- continue;
366
- const host = hostOf(id);
367
- if (!host)
368
- continue;
369
- const existing = host.querySelector(':scope > .axdb-rs');
370
- if (((_b = node.state) === null || _b === void 0 ? void 0 : _b.locked) === true) {
371
- existing === null || existing === void 0 ? void 0 : existing.remove();
372
- continue;
373
- }
374
- const rs = existing !== null && existing !== void 0 ? existing : document.createElement('div');
375
- if (!existing) {
376
- rs.className = 'axdb-rs';
377
- rs.setAttribute('title', 'Resize');
378
- host.appendChild(rs);
379
- }
380
- // The grab corner mirrors with the board: bottom-right LTR, bottom-left
381
- // RTL — the same corner the tile actually grows from in each direction.
382
- rs.classList.toggle('axdb-rs--rtl', rtl);
383
- }
384
- };
385
- const hostObserver = new MutationObserver(() => syncHandles());
386
- // -- gesture snapshot / commit ---------------------------------------------
387
- const snapshotAll = () => {
388
- const cells = new Map();
389
- const geoms = new Map();
390
- for (const item of engine.getItems()) {
391
- cells.set(item.id, { x: item.x, y: item.y, w: item.w, h: item.h });
392
- const e = memberEntity(item.id);
393
- if (e) {
394
- const es = sizeOf(e);
395
- geoms.set(item.id, {
396
- pos: { x: e.position.x, y: e.position.y },
397
- size: { width: es.width, height: es.height, depth: es.depth },
398
- });
399
- }
400
- }
401
- return { cells, geoms };
402
- };
403
- const deltasSince = (startCells, startGeom, excludeId) => {
404
- const out = [];
405
- for (const item of engine.getItems()) {
406
- if (item.id === excludeId)
407
- continue;
408
- const before = startCells.get(item.id);
409
- const geomBefore = startGeom.get(item.id);
410
- const e = memberEntity(item.id);
411
- if (!before || !geomBefore || !e)
412
- continue; // items added mid-gesture commit separately
413
- out.push({
414
- id: item.id,
415
- locked: !!item.locked,
416
- isGroup: isGroupMember(item.id),
417
- cellBefore: before,
418
- cellAfter: { x: item.x, y: item.y, w: item.w, h: item.h },
419
- posBefore: geomBefore.pos,
420
- posAfter: { x: e.position.x, y: e.position.y },
421
- sizeBefore: geomBefore.size,
422
- sizeAfter: (({ width, height, depth }) => ({ width, height, depth }))(sizeOf(e)),
423
- });
424
- }
425
- return out;
426
- };
427
- const execute = (name, commands) => {
428
- if (commands.length === 0)
429
- return false;
430
- void api.getEngine().commandManager.execute(new BatchCommand(name, commands));
431
- return true;
432
- };
433
- // -- membership + bounds sync ----------------------------------------------
434
- const onMemberAdded = (id) => {
435
- if (disposed)
436
- return;
437
- if (!engine.getItem(id)) {
438
- const item = itemFor(id);
439
- const placed = engine.add(item);
440
- if (placed)
441
- persistAdoptedCell(id, placed);
442
- }
443
- project();
444
- syncHandles();
445
- api.render();
446
- };
447
- const onMemberRemoved = (id) => {
448
- if (disposed)
449
- return;
450
- if (gesture && gesture.id === id)
451
- cancelActiveGesture(false);
452
- if (!engine.getItem(id))
453
- return;
454
- engine.remove(id);
455
- project();
456
- api.render();
457
- };
458
- const onBoundsChanged = () => {
459
- if (disposed || writing)
460
- return;
461
- if (evaluateResponsive())
462
- return; // a column change already re-projected
463
- project();
464
- api.render();
465
- };
466
- // -- responsive column count -----------------------------------------------
467
- /**
468
- * The column count this board's CURRENT WIDTH asks for. Breakpoints win when
469
- * given (first step, ascending, whose `w` is at least the board width);
470
- * otherwise `columnWidth` divides the width. Both clamp to `[1, columnMax]`.
471
- */
472
- const columnsForWidth = (width) => {
473
- var _a, _b, _c;
474
- if (!responsive)
475
- return null;
476
- const max = Math.max(1, (_a = responsive.columnMax) !== null && _a !== void 0 ? _a : maxColumns);
477
- if ((_b = responsive.breakpoints) === null || _b === void 0 ? void 0 : _b.length) {
478
- const steps = [...responsive.breakpoints].sort((a, b) => a.w - b.w);
479
- const hit = steps.find((s) => width <= s.w);
480
- const c = hit ? hit.c : max;
481
- return { c: Math.max(1, Math.min(max, c)), layout: (_c = hit === null || hit === void 0 ? void 0 : hit.layout) !== null && _c !== void 0 ? _c : responsive.layout };
482
- }
483
- if (responsive.columnWidth && responsive.columnWidth > 0) {
484
- const c = Math.round(width / responsive.columnWidth);
485
- return { c: Math.max(1, Math.min(max, c)), layout: responsive.layout };
486
- }
487
- return null;
488
- };
489
- /**
490
- * Re-derive the column count from the board width and apply it. Returns true
491
- * when the count actually changed (the caller then skips its own project(),
492
- * because applyColumns already re-projected everything).
493
- */
494
- const evaluateResponsive = () => {
495
- var _a, _b;
496
- if (!responsive || responsivePinned || disposed || gesture)
497
- return false;
498
- const want = columnsForWidth(frame().width);
499
- if (!want || want.c === columns)
500
- return false;
501
- return applyColumns(want.c, (_b = (_a = want.layout) !== null && _a !== void 0 ? _a : responsive.layout) !== null && _b !== void 0 ? _b : 'moveScale');
502
- };
503
- /**
504
- * Cells written by a column change are DERIVED STATE, not an edit: they go
505
- * through `runSystemWrite` exactly like the pixel projection does, never
506
- * through the command stack. A browser resize must not be undoable, and the
507
- * authored layout is safe regardless — the engine's cache still holds it,
508
- * and `saveLayout()` serialises from the widest cached count.
509
- */
510
- const persistLiveCells = () => {
511
- writing = true;
512
- try {
513
- diagram.runSystemWrite(() => {
514
- var _a;
515
- for (const item of engine.getItems()) {
516
- const cell = { x: item.x, y: item.y, w: item.w, h: item.h };
517
- const node = diagram.getNode(item.id);
518
- if (node) {
519
- node.setGridItem(gridItemFromCell(cell));
520
- continue;
521
- }
522
- (_a = diagram.getGroup(item.id)) === null || _a === void 0 ? void 0 : _a.setMetadata('gridItem', gridItemFromCell(cell));
523
- }
524
- });
525
- }
526
- finally {
527
- writing = false;
528
- }
529
- };
530
- const applyColumns = (n, layout) => {
531
- var _a;
532
- const prev = columns;
533
- if (!engine.setColumns(n, layout))
534
- return false;
535
- columns = engine.columns;
536
- persistLiveCells();
537
- armGlide();
538
- project();
539
- syncPlaceholder();
540
- disarmGlideSoon();
541
- api.renderNow();
542
- (_a = options.onColumnsChange) === null || _a === void 0 ? void 0 : _a.call(options, columns, prev);
543
- return true;
544
- };
545
- /**
546
- * The canvas container resizing is the OTHER trigger for re-evaluation: a
547
- * page that sizes its boards from the viewport changes the frame in the same
548
- * turn, and a page that does not still wants the check to run. The binder
549
- * owns this — pages should never have to wire a ResizeObserver for it.
550
- */
551
- const containerObserver = responsive && typeof ResizeObserver !== 'undefined'
552
- ? new ResizeObserver(() => {
553
- if (disposed)
554
- return;
555
- evaluateResponsive();
556
- })
557
- : null;
558
- // -- board hit-testing ------------------------------------------------------
559
- const boardVisualHeight = () => {
560
- const f = frame();
561
- const g = geom();
562
- return Math.max(f.height, 2 * padding + rows() * (rowHeightFor(g, rows()) + gap) - gap);
563
- };
564
- const worldInsideBoard = (x, y) => {
565
- const f = frame();
566
- return x >= f.x && x <= f.x + f.width && y >= f.y && y <= f.y + boardVisualHeight();
567
- };
568
- /** Small overshoots CLAMP onto the board instead of counting as off-board —
569
- * the plan prototype cannot leave its board at all (cells clamp at the
570
- * edges), so a 60px slip past the frame must not dim or delete. */
571
- const EDGE_GRACE = 60;
572
- const worldInsideBoardGrace = (x, y) => {
573
- if (worldInsideBoardExtended(x, y))
574
- return true;
575
- const f = frame();
576
- const band = rowHeightFor(geom(), rows()) + gap;
577
- return (x >= f.x - EDGE_GRACE &&
578
- x <= f.x + f.width + EDGE_GRACE &&
579
- y >= f.y - EDGE_GRACE &&
580
- y <= f.y + boardVisualHeight() + band + EDGE_GRACE);
581
- };
582
- /** One extra row of grace below the frame (gridstack's extra drag row). */
583
- const worldInsideBoardExtended = (x, y) => {
584
- if (worldInsideBoard(x, y))
585
- return true;
586
- const f = frame();
587
- const band = rowHeightFor(geom(), rows()) + gap;
588
- return x >= f.x && x <= f.x + f.width && y >= f.y && y <= f.y + boardVisualHeight() + band;
589
- };
590
- const insideMemberGroupFrame = (x, y) => {
591
- var _a;
592
- for (const id of (_a = group.members) !== null && _a !== void 0 ? _a : []) {
593
- const grp = diagram.getGroup(id);
594
- if (!grp)
595
- continue;
596
- const p = grp.position;
597
- const s = sizeOf(grp);
598
- if (x >= p.x && x <= p.x + s.width && y >= p.y && y <= p.y + s.height)
599
- return true;
600
- }
601
- return false;
602
- };
603
- const capturePointer = (pointerId) => {
604
- var _a, _b;
605
- if (pointerId === null)
606
- return;
607
- try {
608
- (_b = (_a = api.container).setPointerCapture) === null || _b === void 0 ? void 0 : _b.call(_a, pointerId);
609
- }
610
- catch (_c) {
611
- /* capture is best-effort */
612
- }
613
- };
614
- const releasePointer = (pointerId) => {
615
- var _a, _b;
616
- if (pointerId === null)
617
- return;
618
- try {
619
- (_b = (_a = api.container).releasePointerCapture) === null || _b === void 0 ? void 0 : _b.call(_a, pointerId);
620
- }
621
- catch (_c) {
622
- /* nothing to release */
623
- }
624
- };
625
- // -- the gesture machine ----------------------------------------------------
626
- const beginGestureVisuals = (g) => {
627
- engine.beginGesture();
628
- const snap = snapshotAll();
629
- g.startCells = snap.cells;
630
- g.startGeom = snap.geoms;
631
- g.started = true;
632
- armGlide();
633
- if (g.kind !== 'palette') {
634
- setGhost(g.id, true);
635
- g.hostEl = hostOf(g.id);
636
- capturePointer(g.pointerId);
637
- api.container.style.cursor = g.kind === 'resize' ? 'nwse-resize' : 'grabbing';
638
- }
639
- syncPlaceholder();
640
- };
641
- /**
642
- * SAME-EVENT ghost fast-path — the vanilla-parity fix. The prototype writes
643
- * the tile's style inside the pointermove handler, so the ghost sits at 0px
644
- * behind the cursor; writing only the MODEL leaves the host to the next
645
- * render frame (~7.5px measured trailing at hand speed). Hosts live in the
646
- * HTML layer whose transform carries the camera, so world px == style px;
647
- * the renderer's next pass writes the identical values from the model — no
648
- * fight, just no wait.
649
- */
650
- const ghostStyleFastPath = (g, rect) => {
651
- const el = g.hostEl;
652
- if (!el)
653
- return;
654
- if (rect.x !== undefined)
655
- el.style.left = `${rect.x}px`;
656
- if (rect.y !== undefined)
657
- el.style.top = `${rect.y}px`;
658
- if (rect.width !== undefined)
659
- el.style.width = `${rect.width}px`;
660
- if (rect.height !== undefined)
661
- el.style.height = `${rect.height}px`;
662
- };
663
- const cleanupGestureVisuals = (g) => {
664
- var _a;
665
- if (g.kind !== 'palette')
666
- setGhost(g.id, false);
667
- disarmGlideSoon();
668
- releasePointer(g.pointerId);
669
- api.container.style.cursor = '';
670
- (_a = g.chip) === null || _a === void 0 ? void 0 : _a.remove();
671
- placeholder === null || placeholder === void 0 ? void 0 : placeholder.remove();
672
- placeholder = null;
673
- };
674
- const commitGesture = (g) => {
675
- var _a;
676
- // SETTLE FIRST, then measure. Mid-drag the board can transiently hold more
677
- // rows than the drop keeps (hover above a locked slab pushes everything
678
- // down), and fit-mode row height shrinks with it — every displaced tile is
679
- // re-projected at that TRANSIENT height. The old order computed the commit
680
- // deltas from those pixels and, when the engine settled back with no cell
681
- // changes, nothing ever wrote the pixels back: a refused drop left the
682
- // whole board at the mid-drag height, KPI value lines clipped to nothing
683
- // (live audit repro: drag the trend chart above the KPI row → every widget
684
- // 122px→85px with identical cells). endGesture() keeps the settled cells;
685
- // project() then derives every member's pixels — the ghost included — from
686
- // that truth, so the deltas below record settled geometry and undo/redo
687
- // both replay it faithfully.
688
- engine.endGesture();
689
- cleanupGestureVisuals(g);
690
- gesture = null;
691
- project();
692
- const deltas = deltasSince(g.startCells, g.startGeom);
693
- const commands = buildCommitCommands(deltas);
694
- if (g.esc && g.esc.rowsAdded !== 0) {
695
- commands.push(new SetGroupCellCommand(group.id, g.esc.cellBefore, g.esc.cellAfter, g.esc.frameBefore, g.esc.frameAfter));
696
- }
697
- const changed = execute(g.kind === 'resize' ? 'Resize widget' : 'Move widget', commands);
698
- api.renderNow();
699
- (_a = options.onGesture) === null || _a === void 0 ? void 0 : _a.call(options, { type: 'commit', kind: g.kind, nodeId: g.id, changed });
700
- };
701
- const cancelActiveGesture = (notify = true) => {
702
- var _a, _b, _c;
703
- const g = gesture;
704
- if (!g)
705
- return;
706
- gesture = null;
707
- if (g.kind !== 'palette' && g.leg) {
708
- g.leg.adopted.abort(); // target board back to its pre-entry layout
709
- g.leg = null;
710
- }
711
- if (g.kind !== 'palette' && g.esc && g.esc.rowsAdded !== 0) {
712
- g.esc.peer.resizeMemberBy(group.id, -g.esc.rowsAdded); // slab back down
713
- g.esc = null;
714
- }
715
- if (g.started) {
716
- if (g.removedFromBoard || g.kind === 'palette') {
717
- // The engine cannot resurrect a removed item — rebuild from the
718
- // gesture-start snapshot (cells are pure data; the constructor
719
- // honours legal layouts verbatim).
720
- engine.endGesture();
721
- const items = [];
722
- for (const [id, c] of g.startCells) {
723
- const lockedNode = ((_b = (_a = diagram.getNode(id)) === null || _a === void 0 ? void 0 : _a.state) === null || _b === void 0 ? void 0 : _b.locked) === true;
724
- items.push(Object.assign(Object.assign({ id }, c), { locked: lockedNode || isGroupMember(id) }));
725
- }
726
- engine = new GridPackEngine(items, { columns, float, maxRows });
727
- }
728
- else {
729
- engine.cancelGesture();
730
- }
731
- // Restore every pixel to its gesture-start state.
732
- writing = true;
733
- try {
734
- for (const [id, snap] of g.startGeom) {
735
- const e = memberEntity(id);
736
- if (!e)
737
- continue;
738
- const node = diagram.getNode(id);
739
- diagram.runSystemWrite(() => {
740
- var _a;
741
- if (node) {
742
- node.setPosition(snap.pos.x, snap.pos.y);
743
- node.setSize(snap.size.width, snap.size.height, (_a = snap.size.depth) !== null && _a !== void 0 ? _a : 0);
744
- }
745
- else {
746
- e.setFrame({
747
- x: snap.pos.x,
748
- y: snap.pos.y,
749
- width: snap.size.width,
750
- height: snap.size.height,
751
- });
752
- }
753
- });
754
- }
755
- }
756
- finally {
757
- writing = false;
758
- }
759
- }
760
- else {
761
- engine.endGesture();
762
- }
763
- cleanupGestureVisuals(g);
764
- enforceBoardHeight();
765
- api.renderNow();
766
- if (notify) {
767
- (_c = options.onGesture) === null || _c === void 0 ? void 0 : _c.call(options, { type: 'cancel', kind: g.kind, nodeId: g.id, changed: false });
768
- }
769
- };
770
- /** Centre a w×h-span tile's top-left under the cursor, in world px. */
771
- const centredTopLeft = (worldX, worldY, spans) => {
772
- const f = frame();
773
- const g = geom();
774
- const cu = columnUnitFor(g, f.width);
775
- const rh = rowHeightFor(g, rows());
776
- return {
777
- x: worldX - (spans.w * (cu + gap) - gap) / 2,
778
- y: worldY - (spans.h * (rh + gap) - gap) / 2,
779
- };
780
- };
781
- const onToolMove = (ev) => {
782
- var _a, _b, _c, _d, _e, _f, _g, _h, _j;
783
- const g = gesture;
784
- if (!g || g.kind === 'palette')
785
- return;
786
- if (!g.started) {
787
- const dx = ev.screen.x - g.downClient.x;
788
- const dy = ev.screen.y - g.downClient.y;
789
- if (Math.abs(dx) + Math.abs(dy) < DRAG_THRESHOLD)
790
- return;
791
- beginGestureVisuals(g);
792
- }
793
- if (g.kind === 'move') {
794
- const desired = { x: ev.world.x - g.grab.dx, y: ev.world.y - g.grab.dy };
795
- g.node.setPosition(desired.x, desired.y);
796
- ghostStyleFastPath(g, desired);
797
- g.lastWorld = { x: ev.world.x, y: ev.world.y };
798
- g.lastScreen = { x: ev.screen.x, y: ev.screen.y };
799
- // Deepest board under the pointer wins: the nested KPI strip beats the
800
- // tab that contains it; a foreign board beats "outside". Strict frames
801
- // first; the one-row grace band below each board (gridstack's extra
802
- // drag row) is consulted only when NO strict frame matched — so "under
803
- // the last row" appends instead of reading as off-board.
804
- const strictSelf = worldInsideBoard(ev.world.x, ev.world.y);
805
- let peer = peerAt(ev.world.x, ev.world.y);
806
- let inside = strictSelf;
807
- if (!strictSelf && !peer) {
808
- if (worldInsideBoardExtended(ev.world.x, ev.world.y))
809
- inside = true;
810
- else
811
- peer = peerAt(ev.world.x, ev.world.y, true);
812
- // Last resort: the grace band — a small slip past the edge stays ON
813
- // this board (the engine clamps the cell; prototype parity).
814
- if (!inside && !peer && worldInsideBoardGrace(ev.world.x, ev.world.y))
815
- inside = true;
816
- }
817
- const selfWins = inside && (!peer || boardArea() <= peer.frameArea());
818
- if (peer && !selfWins) {
819
- // -- HANDOFF: the pointer is over another board -------------------
820
- if (g.leg && g.leg.peer === peer) {
821
- g.leg.adopted.move(ev.world);
822
- }
823
- else {
824
- if (g.leg) {
825
- g.leg.adopted.abort();
826
- g.leg = null;
827
- }
828
- if (!g.removedFromBoard) {
829
- g.removedFromBoard = true;
830
- engine.remove(g.id); // survivors settle home (gesture memory intact)
831
- project();
832
- }
833
- const adopted = peer.adopt(g.node, ev.world, {
834
- width: g.node.size.width,
835
- height: g.node.size.height,
836
- });
837
- if (adopted) {
838
- (_a = hostOf(g.id)) === null || _a === void 0 ? void 0 : _a.classList.remove('axdb-out');
839
- g.leg = { peer, adopted };
840
- g.leg.adopted.move(ev.world);
841
- }
842
- else {
843
- // Bounded/full board refused the adoption: dim = will snap home.
844
- (_b = hostOf(g.id)) === null || _b === void 0 ? void 0 : _b.classList.add('axdb-out');
845
- }
846
- }
847
- }
848
- else if (inside) {
849
- // -- back on (or still on) our own board --------------------------
850
- if (g.leg) {
851
- g.leg.adopted.abort();
852
- g.leg = null;
853
- }
854
- if (g.removedFromBoard) {
855
- g.removedFromBoard = false;
856
- (_c = hostOf(g.id)) === null || _c === void 0 ? void 0 : _c.classList.remove('axdb-out');
857
- const cell = pointToCell(desired.x, desired.y, frame(), geom(), rows(), g.spans.w);
858
- // Re-enter at the bottom edge (collision-free), then take the cursor
859
- // cell GATELESSLY — a first placement skips the anti-jitter gate.
860
- engine.add({ id: g.id, x: 0, y: engine.rows(), w: g.spans.w, h: g.spans.h });
861
- engine.moveCheck(g.id, cell.x, cell.y, { gate: false });
862
- project();
863
- }
864
- else {
865
- const spanW = (_e = (_d = engine.getItem(g.id)) === null || _d === void 0 ? void 0 : _d.w) !== null && _e !== void 0 ? _e : g.spans.w;
866
- const cell = pointToCell(desired.x, desired.y, frame(), geom(), rows(), spanW);
867
- if (engine.moveCheck(g.id, cell.x, cell.y).changed)
868
- project();
869
- }
870
- }
871
- else {
872
- // -- outside every board ------------------------------------------
873
- if (g.leg) {
874
- g.leg.adopted.abort();
875
- g.leg = null;
876
- }
877
- if (!g.removedFromBoard) {
878
- g.removedFromBoard = true;
879
- engine.remove(g.id); // survivors settle home; cells minted nowhere
880
- (_f = hostOf(g.id)) === null || _f === void 0 ? void 0 : _f.classList.add('axdb-out');
881
- project();
882
- }
883
- }
884
- syncPlaceholder();
885
- return;
886
- }
887
- // resize: fluid pixel preview on the ghost, cell-stepped live push.
888
- //
889
- // RTL: the corner handle is on the BOTTOM-LEFT and the tile grows
890
- // leftwards, so the horizontal delta inverts and the tile's RIGHT edge is
891
- // what stays pinned. Everything below this line — the span rounding, the
892
- // engine's resizeCheck, the maxRows escalation — is in CELLS and needs no
893
- // mirroring at all.
894
- const dw = rtl ? g.downWorld.x - ev.world.x : ev.world.x - g.downWorld.x;
895
- const dh = ev.world.y - g.downWorld.y;
896
- const f = frame();
897
- const gg = geom();
898
- const minW = Math.max(8, columnUnitFor(gg, f.width));
899
- let w = Math.max(minW, g.startSize.width + dw);
900
- // The height min-clamp is applied AFTER the escalation logic below: on a
901
- // grown strip "one current row" IS the whole strip, and clamping the
902
- // fluid pull to it made the de-escalation threshold unreachable — the
903
- // ratchet's second disguise (grow committed; a fresh shrink gesture could
904
- // never pull low enough to ask the parent for a row back).
905
- let h = g.startSize.height + dh;
906
- // NESTED HEIGHT ESCALATION (live report: "i cant increase height"). A
907
- // bounded strip cannot grow a tile taller than itself — so pulling
908
- // clearly past its bottom GROWS THE STRIP: the slab gains a row in the
909
- // parent board (all tiles inside get taller together); pulling clearly
910
- // back up REMOVES a row again. SYMMETRIC AND STATELESS ACROSS GESTURES:
911
- // the first version only de-escalated inside the ledger of the gesture
912
- // that grew ("rowsAdded > 0"), so grow → release → try to shrink was a
913
- // RATCHET — the strip could only ever get taller (live report: "Total
914
- // Revenue widget size can only increase"). Direction is now decided from
915
- // the strip's CURRENT slab rows, whatever gesture created them; the
916
- // ledger just accumulates this gesture's net change for the one-batch
917
- // commit and for Escape.
918
- if (maxRows !== undefined && g.kind === 'resize') {
919
- const parent = parentPeer();
920
- if (parent) {
921
- const visual = boardVisualHeight();
922
- const slabRows = (_h = (_g = parent.memberCell(group.id)) === null || _g === void 0 ? void 0 : _g.h) !== null && _h !== void 0 ? _h : 1;
923
- const rowPx = visual / Math.max(1, slabRows);
924
- const record = (res, d) => {
925
- if (!res.changed || !res.cellBefore || !res.cellAfter || !res.frameBefore || !res.frameAfter)
926
- return;
927
- if (!g.esc) {
928
- g.esc = {
929
- peer: parent,
930
- rowsAdded: 0,
931
- cellBefore: res.cellBefore,
932
- frameBefore: res.frameBefore,
933
- cellAfter: res.cellAfter,
934
- frameAfter: res.frameAfter,
935
- };
936
- }
937
- g.esc.rowsAdded += d;
938
- g.esc.cellAfter = res.cellAfter;
939
- g.esc.frameAfter = res.frameAfter;
940
- project();
941
- };
942
- if (h > visual + 24) {
943
- record(parent.resizeMemberBy(group.id, +1), +1);
944
- }
945
- else if (slabRows > 1 && h < visual - rowPx * 0.7) {
946
- record(parent.resizeMemberBy(group.id, -1), -1);
947
- }
948
- }
949
- }
950
- // The fluid preview must not outrun what the board can accept: on a
951
- // bounded strip an unclamped ghost ballooned to 273px while the engine
952
- // (rightly) refused every cell — visually indistinguishable from the
953
- // squeeze bug it replaced. Clamp to the tile's maximum legal rect.
954
- // (Escalation above may have just grown OR shrunk the board — re-read.)
955
- const fNow = frame();
956
- const ggNow = geom();
957
- h = Math.max(Math.max(8, rowHeightFor(ggNow, rows())), h);
958
- const itemNow = engine.getItem(g.id);
959
- if (itemNow) {
960
- const cuNow = columnUnitFor(ggNow, fNow.width);
961
- const rhNow = rowHeightFor(ggNow, rows());
962
- w = Math.min(w, (columns - itemNow.x) * (cuNow + gap) - gap);
963
- if (maxRows !== undefined) {
964
- h = Math.min(h, Math.max(1, maxRows - itemNow.y) * (rhNow + gap) - gap);
965
- }
966
- }
967
- g.node.setSize(w, h, (_j = g.node.size.depth) !== null && _j !== void 0 ? _j : 0);
968
- if (rtl) {
969
- // Keep the RIGHT edge pinned: mirrored, that is the tile's origin. The
970
- // start position is recoverable from the grab offset, so this needs no
971
- // extra gesture state.
972
- const startLeft = g.downWorld.x - g.grab.dx;
973
- const right = startLeft + g.startSize.width;
974
- g.node.setPosition(right - w, g.node.position.y);
975
- ghostStyleFastPath(g, { x: right - w, width: w, height: h });
976
- }
977
- else {
978
- ghostStyleFastPath(g, { width: w, height: h });
979
- }
980
- const spanF = maxRows !== undefined ? frame() : f;
981
- const spanG = maxRows !== undefined ? geom() : gg;
982
- const span = sizeToSpan(w, h, spanF, spanG, rows());
983
- if (engine.resizeCheck(g.id, span.w, span.h).changed)
984
- project();
985
- syncPlaceholder();
986
- };
987
- const onToolUp = () => {
988
- var _a, _b, _c, _d, _e, _f;
989
- const g = gesture;
990
- if (!g || g.kind === 'palette')
991
- return;
992
- if (!g.started) {
993
- gesture = null; // a plain click — the page's own click-to-focus handles it
994
- return;
995
- }
996
- if (g.leg) {
997
- // -- CROSS-CONTAINER COMMIT: one batch across both boards -----------
998
- const fin = g.leg.adopted.finalize();
999
- if (!fin) {
1000
- cancelActiveGesture();
1001
- return;
1002
- }
1003
- const targetGroupId = g.leg.adopted.groupId;
1004
- // Land the ghost on its target rect before the geometry deltas read it.
1005
- writing = true;
1006
- try {
1007
- diagram.runSystemWrite(() => {
1008
- var _a;
1009
- g.node.setPosition(fin.rect.x, fin.rect.y);
1010
- g.node.setSize(fin.rect.width, fin.rect.height, (_a = g.node.size.depth) !== null && _a !== void 0 ? _a : 0);
1011
- });
1012
- }
1013
- finally {
1014
- writing = false;
1015
- }
1016
- const sourceDisplaced = buildCommitCommands(deltasSince(g.startCells, g.startGeom, g.id));
1017
- const before = g.startCells.get(g.id);
1018
- const geomBefore = g.startGeom.get(g.id);
1019
- const crossing = [
1020
- ...sourceDisplaced,
1021
- new RemoveFromGroupCommand(group.id, g.id),
1022
- new AddToGroupCommand(targetGroupId, g.id),
1023
- ...buildCommitCommands([
1024
- {
1025
- id: g.id,
1026
- locked: false,
1027
- isGroup: false,
1028
- cellBefore: before !== null && before !== void 0 ? before : fin.cell,
1029
- cellAfter: fin.cell,
1030
- posBefore: (_a = geomBefore === null || geomBefore === void 0 ? void 0 : geomBefore.pos) !== null && _a !== void 0 ? _a : { x: fin.rect.x, y: fin.rect.y },
1031
- posAfter: { x: fin.rect.x, y: fin.rect.y },
1032
- sizeBefore: (_b = geomBefore === null || geomBefore === void 0 ? void 0 : geomBefore.size) !== null && _b !== void 0 ? _b : { width: fin.rect.width, height: fin.rect.height },
1033
- sizeAfter: { width: fin.rect.width, height: fin.rect.height },
1034
- },
1035
- ]),
1036
- ];
1037
- execute('Move widget', crossing);
1038
- engine.endGesture();
1039
- cleanupGestureVisuals(g);
1040
- gesture = null;
1041
- enforceBoardHeight();
1042
- api.renderNow();
1043
- (_c = options.onGesture) === null || _c === void 0 ? void 0 : _c.call(options, { type: 'commit', kind: g.kind, nodeId: g.id, changed: true });
1044
- return;
1045
- }
1046
- if (g.removedFromBoard && dragOut === 'cancel') {
1047
- // Released outside every board on a snap-home board: full restore,
1048
- // nothing committed (the parked-outside release).
1049
- cancelActiveGesture();
1050
- return;
1051
- }
1052
- // 'remove' fires ONLY for a release genuinely outside every board. A
1053
- // refused adoption (full strip) leaves removedFromBoard=true while the
1054
- // pointer is still over a board — releasing there must snap home, not
1055
- // delete the tile (the battery's S5 caught exactly that deletion).
1056
- const releasedOutsideAll = !g.lastWorld ||
1057
- (!worldInsideBoardGrace(g.lastWorld.x, g.lastWorld.y) &&
1058
- !peerAt(g.lastWorld.x, g.lastWorld.y, true));
1059
- const inRemoveZone = !options.removeZone ||
1060
- (g.lastScreen && g.lastWorld && options.removeZone(g.lastScreen, g.lastWorld));
1061
- if (g.removedFromBoard && dragOut === 'remove' && (!releasedOutsideAll || !inRemoveZone)) {
1062
- // Outside-but-not-over-the-trash (or a mere overshoot): snap home.
1063
- cancelActiveGesture();
1064
- return;
1065
- }
1066
- if (g.removedFromBoard && dragOut === 'remove') {
1067
- // Release OUTSIDE the board → remove via the page's atomic command path.
1068
- const displaced = buildCommitCommands(deltasSince(g.startCells, g.startGeom, g.id));
1069
- const snap = g.startGeom.get(g.id);
1070
- if (snap) {
1071
- // Park the node on its start rect so the page's RemoveNodeCommand
1072
- // captures sane geometry for undo.
1073
- g.node.setPosition(snap.pos.x, snap.pos.y);
1074
- g.node.setSize(snap.size.width, snap.size.height, (_d = snap.size.depth) !== null && _d !== void 0 ? _d : 0);
1075
- }
1076
- engine.endGesture();
1077
- cleanupGestureVisuals(g);
1078
- gesture = null;
1079
- enforceBoardHeight();
1080
- api.renderNow();
1081
- void ((_e = options.onRemoveRequest) === null || _e === void 0 ? void 0 : _e.call(options, g.id, displaced));
1082
- (_f = options.onGesture) === null || _f === void 0 ? void 0 : _f.call(options, { type: 'remove', kind: g.kind, nodeId: g.id, changed: true });
1083
- return;
1084
- }
1085
- commitGesture(g);
1086
- };
1087
- // -- cross-container peers -------------------------------------------------
1088
- const boardArea = () => {
1089
- const f = frame();
1090
- return f.width * boardVisualHeight();
1091
- };
1092
- const peersOnCanvas = () => {
1093
- let set = BOARD_REGISTRY.get(api.container);
1094
- if (!set) {
1095
- set = new Set();
1096
- BOARD_REGISTRY.set(api.container, set);
1097
- }
1098
- return set;
1099
- };
1100
- /** The board whose engine holds OUR group as an item (nesting parent). */
1101
- const parentPeer = () => {
1102
- for (const p of peersOnCanvas()) {
1103
- if (p !== selfPeer && p.hasItem(group.id))
1104
- return p;
1105
- }
1106
- return null;
1107
- };
1108
- /** Deepest OTHER registered board containing the world point. */
1109
- const peerAt = (x, y, extended = false) => {
1110
- let best = null;
1111
- for (const p of peersOnCanvas()) {
1112
- if (p === selfPeer)
1113
- continue;
1114
- if (!(extended ? p.containsWorldExtended(x, y) : p.containsWorld(x, y)))
1115
- continue;
1116
- if (!best || p.frameArea() < best.frameArea())
1117
- best = p;
1118
- }
1119
- return best;
1120
- };
1121
- /** This binder's side of an adoption: enter gateless, then live-push. */
1122
- const adopt = (node, world, pxSize) => {
1123
- if (disposed)
1124
- return null;
1125
- const f = frame();
1126
- const gg = geom();
1127
- const span = sizeToSpan(pxSize.width, pxSize.height, f, gg, rows());
1128
- // Clamp to the TARGET board's shape: a tall tile entering a one-row strip
1129
- // arrives as a strip-height tile, not a refusal.
1130
- span.w = Math.max(1, Math.min(columns, span.w));
1131
- if (maxRows !== undefined)
1132
- span.h = Math.max(1, Math.min(maxRows, span.h));
1133
- engine.beginGesture(); // pre-entry snapshot — abort() restores it
1134
- const entered = engine.add({ id: node.id, x: 0, y: engine.rows(), w: span.w, h: span.h });
1135
- if (!entered) {
1136
- engine.endGesture();
1137
- return null; // a bounded, full board refuses the adoption
1138
- }
1139
- // Pre-entry baselines for THIS board's displaced-tile commit.
1140
- const startCells = new Map();
1141
- const startGeom = new Map();
1142
- for (const item of engine.getItems()) {
1143
- if (item.id === node.id)
1144
- continue;
1145
- startCells.set(item.id, { x: item.x, y: item.y, w: item.w, h: item.h });
1146
- const e = memberEntity(item.id);
1147
- if (e) {
1148
- const sz = sizeOf(e);
1149
- startGeom.set(item.id, {
1150
- pos: { x: e.position.x, y: e.position.y },
1151
- size: { width: sz.width, height: sz.height, depth: sz.depth },
1152
- });
1153
- }
1154
- }
1155
- adoptedGhostId = node.id;
1156
- const tl = centredTopLeft(world.x, world.y, span);
1157
- const cell0 = pointToCell(tl.x, tl.y, f, gg, rows(), span.w);
1158
- engine.moveCheck(node.id, cell0.x, cell0.y, { gate: false });
1159
- armGlide();
1160
- project();
1161
- syncPlaceholder();
1162
- return {
1163
- groupId: group.id,
1164
- move: (w) => {
1165
- const item = engine.getItem(node.id);
1166
- if (!item)
1167
- return;
1168
- const tlm = centredTopLeft(w.x, w.y, { w: item.w, h: item.h });
1169
- const cell = pointToCell(tlm.x, tlm.y, frame(), geom(), rows(), item.w);
1170
- if (engine.moveCheck(node.id, cell.x, cell.y).changed)
1171
- project();
1172
- syncPlaceholder();
1173
- },
1174
- abort: () => {
1175
- engine.remove(node.id);
1176
- engine.cancelGesture(); // pre-entry layout, memory cleared
1177
- adoptedGhostId = null;
1178
- disarmGlideSoon();
1179
- project();
1180
- syncPlaceholder();
1181
- },
1182
- finalize: () => {
1183
- const item = engine.getItem(node.id);
1184
- if (!item) {
1185
- engine.endGesture();
1186
- adoptedGhostId = null;
1187
- syncPlaceholder();
1188
- return null;
1189
- }
1190
- const cell = { x: item.x, y: item.y, w: item.w, h: item.h };
1191
- const rect = cellToRect(item, frame(), geom(), rows());
1192
- const commands = buildCommitCommands(deltasSince(startCells, startGeom, node.id));
1193
- engine.endGesture();
1194
- adoptedGhostId = null;
1195
- disarmGlideSoon();
1196
- syncPlaceholder();
1197
- return { commands, cell, rect };
1198
- },
1199
- };
1200
- };
1201
- const selfPeer = {
1202
- group,
1203
- hasItem: (id) => !!engine.getItem(id),
1204
- memberCell: (id) => {
1205
- const it = engine.getItem(id);
1206
- return it ? { x: it.x, y: it.y, w: it.w, h: it.h } : undefined;
1207
- },
1208
- resizeMemberBy: (id, dRows) => {
1209
- const item = engine.getItem(id);
1210
- if (!item || disposed)
1211
- return { changed: false };
1212
- const grp = diagram.getGroup(id);
1213
- const cellBefore = { x: item.x, y: item.y, w: item.w, h: item.h };
1214
- const fb = grp
1215
- ? { x: grp.position.x, y: grp.position.y, width: sizeOf(grp).width, height: sizeOf(grp).height }
1216
- : undefined;
1217
- const r = engine.resizeCheck(id, item.w, item.h + dRows);
1218
- if (!r.changed)
1219
- return { changed: false };
1220
- project();
1221
- const after = engine.getItem(id);
1222
- const fa = grp
1223
- ? { x: grp.position.x, y: grp.position.y, width: sizeOf(grp).width, height: sizeOf(grp).height }
1224
- : undefined;
1225
- return {
1226
- changed: true,
1227
- cellBefore,
1228
- cellAfter: { x: after.x, y: after.y, w: after.w, h: after.h },
1229
- frameBefore: fb,
1230
- frameAfter: fa,
1231
- };
1232
- },
1233
- containsWorld: worldInsideBoard,
1234
- containsWorldExtended: worldInsideBoardExtended,
1235
- frameArea: boardArea,
1236
- adopt,
1237
- };
1238
- peersOnCanvas().add(selfPeer);
1239
- const tool = {
1240
- id: `dashboard-grid:${group.id}:${++binderSeq}`,
1241
- priority: 2, // point-specific claim — outranks mode-style tools (see ext/tools.ts)
1242
- hitTest(ev, hit) {
1243
- var _a;
1244
- if (disposed)
1245
- return false;
1246
- if (gesture)
1247
- return true; // own the rest of an in-flight gesture
1248
- if (hit.node)
1249
- return ((_a = group.members) !== null && _a !== void 0 ? _a : new Set()).has(hit.node.id);
1250
- // Claim (and deaden) empty presses inside a member group's frame so the
1251
- // built-in group-drag cannot fight the pack layout for the KPI slab.
1252
- return insideMemberGroupFrame(ev.world.x, ev.world.y);
1253
- },
1254
- onPointerDown(ev, hit) {
1255
- var _a, _b, _c, _d, _e, _f;
1256
- if (gesture || !hit.node)
1257
- return; // dead zone (slab area), or mid-palette
1258
- const node = diagram.getNode(hit.node.id);
1259
- if (!node || ((_a = node.state) === null || _a === void 0 ? void 0 : _a.locked) === true)
1260
- return; // pinned: refuse; click still focuses
1261
- // Arm the glide class NOW, a full task before any displacement can
1262
- // happen: a transition defined in the same style recalc as the first
1263
- // left/top write does not run (CSS transitions fire only when the
1264
- // property changes while the transition exists in the BEFORE-change
1265
- // style) — the first pushed neighbour of every gesture TELEPORTED
1266
- // (measured 109px in one frame) while later ones glided.
1267
- armGlide();
1268
- const target = ((_c = (_b = ev.source) === null || _b === void 0 ? void 0 : _b.target) !== null && _c !== void 0 ? _c : null);
1269
- const isResize = !!((_d = target === null || target === void 0 ? void 0 : target.closest) === null || _d === void 0 ? void 0 : _d.call(target, '.axdb-rs'));
1270
- const it = engine.getItem(node.id);
1271
- gesture = {
1272
- kind: isResize ? 'resize' : 'move',
1273
- id: node.id,
1274
- node,
1275
- pointerId: typeof PointerEvent !== 'undefined' && ev.source instanceof PointerEvent
1276
- ? ev.source.pointerId
1277
- : null,
1278
- started: false,
1279
- downClient: { x: ev.screen.x, y: ev.screen.y },
1280
- downWorld: { x: ev.world.x, y: ev.world.y },
1281
- grab: { dx: ev.world.x - node.position.x, dy: ev.world.y - node.position.y },
1282
- startCells: new Map(),
1283
- startGeom: new Map(),
1284
- startSize: { width: node.size.width, height: node.size.height },
1285
- spans: { w: (_e = it === null || it === void 0 ? void 0 : it.w) !== null && _e !== void 0 ? _e : 1, h: (_f = it === null || it === void 0 ? void 0 : it.h) !== null && _f !== void 0 ? _f : 1 },
1286
- removedFromBoard: false,
1287
- leg: null,
1288
- lastWorld: null,
1289
- lastScreen: null,
1290
- hostEl: null,
1291
- esc: null,
1292
- chip: null,
1293
- };
1294
- },
1295
- onPointerMove(ev) {
1296
- onToolMove(ev);
1297
- },
1298
- onPointerUp() {
1299
- onToolUp();
1300
- },
1301
- onCancel() {
1302
- cancelActiveGesture();
1303
- },
1304
- };
1305
- const unregisterTool = registerTool(tool);
1306
- // -- palette drag-in --------------------------------------------------------
1307
- const beginPaletteDrag = (node, spec, event) => {
1308
- var _a, _b;
1309
- if (disposed || gesture)
1310
- return;
1311
- const chip = (_a = spec.chip) !== null && _a !== void 0 ? _a : null;
1312
- if (chip) {
1313
- chip.classList.add('axdb-drag-chip');
1314
- document.body.appendChild(chip);
1315
- chip.style.left = `${event.clientX + 6}px`;
1316
- chip.style.top = `${event.clientY + 6}px`;
1317
- }
1318
- const g = {
1319
- kind: 'palette',
1320
- id: node.id,
1321
- node,
1322
- pointerId: (_b = event.pointerId) !== null && _b !== void 0 ? _b : null,
1323
- started: false,
1324
- downClient: { x: event.clientX, y: event.clientY },
1325
- downWorld: { x: 0, y: 0 },
1326
- grab: { dx: 0, dy: 0 },
1327
- startCells: new Map(),
1328
- startGeom: new Map(),
1329
- startSize: { width: 0, height: 0 },
1330
- spans: { w: Math.max(1, spec.w), h: Math.max(1, spec.h) },
1331
- removedFromBoard: true,
1332
- leg: null,
1333
- lastWorld: null,
1334
- lastScreen: null,
1335
- hostEl: null,
1336
- esc: null,
1337
- chip,
1338
- };
1339
- gesture = g;
1340
- const toWorld = (cx, cy) => {
1341
- var _a;
1342
- const rect = api.container.getBoundingClientRect();
1343
- return ((_a = api.viewport) === null || _a === void 0 ? void 0 : _a.clientToWorld)
1344
- ? api.viewport.clientToWorld(cx, cy, rect)
1345
- : { x: cx - rect.left, y: cy - rect.top };
1346
- };
1347
- const detach = () => {
1348
- window.removeEventListener('pointermove', onMove, true);
1349
- window.removeEventListener('pointerup', onUp, true);
1350
- window.removeEventListener('keydown', onKey, true);
1351
- };
1352
- const onMove = (e) => {
1353
- if (gesture !== g)
1354
- return detach();
1355
- if (!g.started) {
1356
- if (Math.abs(e.clientX - g.downClient.x) + Math.abs(e.clientY - g.downClient.y) <
1357
- DRAG_THRESHOLD) {
1358
- return;
1359
- }
1360
- beginGestureVisuals(g);
1361
- }
1362
- if (chip) {
1363
- chip.style.left = `${e.clientX + 6}px`;
1364
- chip.style.top = `${e.clientY + 6}px`;
1365
- }
1366
- const world = toWorld(e.clientX, e.clientY);
1367
- const inside = worldInsideBoard(world.x, world.y);
1368
- if (inside) {
1369
- const tl = centredTopLeft(world.x, world.y, g.spans);
1370
- const cell = pointToCell(tl.x, tl.y, frame(), geom(), rows(), g.spans.w);
1371
- if (g.removedFromBoard) {
1372
- g.removedFromBoard = false;
1373
- // Enter at the bottom edge (collision-free), then take the cursor
1374
- // cell GATELESSLY — gridstack's drag-in skips the gate on entry.
1375
- engine.add({ id: g.id, x: 0, y: engine.rows(), w: g.spans.w, h: g.spans.h });
1376
- engine.moveCheck(g.id, cell.x, cell.y, { gate: false });
1377
- project();
1378
- }
1379
- else if (engine.moveCheck(g.id, cell.x, cell.y).changed) {
1380
- project();
1381
- }
1382
- }
1383
- else if (!g.removedFromBoard) {
1384
- g.removedFromBoard = true;
1385
- engine.remove(g.id); // displaced tiles come home (gesture memory)
1386
- project();
1387
- }
1388
- syncPlaceholder();
1389
- api.render();
1390
- };
1391
- const finish = (commit) => {
1392
- var _a, _b;
1393
- detach();
1394
- if (gesture !== g)
1395
- return;
1396
- if (!g.started) {
1397
- // Never crossed the threshold: a plain palette CLICK — the page's
1398
- // click-to-add handler owns it.
1399
- gesture = null;
1400
- chip === null || chip === void 0 ? void 0 : chip.remove();
1401
- return;
1402
- }
1403
- if (commit && !g.removedFromBoard && engine.getItem(g.id)) {
1404
- const item = engine.getItem(g.id);
1405
- const cell = { x: item.x, y: item.y, w: item.w, h: item.h };
1406
- node.setGridItem(gridItemFromCell(cell));
1407
- const displaced = buildCommitCommands(deltasSince(g.startCells, g.startGeom, g.id));
1408
- engine.endGesture();
1409
- cleanupGestureVisuals(g);
1410
- gesture = null;
1411
- void ((_a = options.onDropIn) === null || _a === void 0 ? void 0 : _a.call(options, node, cell, displaced));
1412
- (_b = options.onGesture) === null || _b === void 0 ? void 0 : _b.call(options, { type: 'drop-in', kind: 'palette', nodeId: g.id, changed: true });
1413
- api.renderNow();
1414
- return;
1415
- }
1416
- // Abort (released outside, or Escape): restore the board.
1417
- cancelActiveGesture();
1418
- };
1419
- const onUp = () => finish(true);
1420
- const onKey = (e) => {
1421
- if (e.key === 'Escape')
1422
- finish(false);
1423
- };
1424
- window.addEventListener('pointermove', onMove, true);
1425
- window.addEventListener('pointerup', onUp, true);
1426
- window.addEventListener('keydown', onKey, true);
1427
- };
1428
- // -- handle -----------------------------------------------------------------
1429
- const subs = [
1430
- group.on('member:added', ((id) => onMemberAdded(id))),
1431
- group.on('member:removed', ((id) => onMemberRemoved(id))),
1432
- group.on('bounds:changed', (() => onBoundsChanged())),
1433
- ];
1434
- /** Run one programmatic gesture through the same snapshot→op→commit pipeline. */
1435
- const programmatic = (name, id, op) => __awaiter(this, void 0, void 0, function* () {
1436
- if (disposed || gesture || !engine.getItem(id))
1437
- return false;
1438
- engine.beginGesture();
1439
- const snap = snapshotAll();
1440
- if (!op()) {
1441
- engine.endGesture();
1442
- return false;
1443
- }
1444
- armGlide();
1445
- project();
1446
- writing = true;
1447
- try {
1448
- const it = engine.getItem(id);
1449
- if (it)
1450
- writeRect(id, cellToRect(it, frame(), geom(), rows()));
1451
- }
1452
- finally {
1453
- writing = false;
1454
- }
1455
- const commands = buildCommitCommands(deltasSince(snap.cells, snap.geoms));
1456
- engine.endGesture();
1457
- disarmGlideSoon();
1458
- enforceBoardHeight();
1459
- if (commands.length > 0) {
1460
- yield api.getEngine().commandManager.execute(new BatchCommand(name, commands));
1461
- }
1462
- api.renderNow();
1463
- return true;
1464
- });
1465
- const handle = {
1466
- sync() {
1467
- var _a;
1468
- if (disposed)
1469
- return;
1470
- if (gesture)
1471
- cancelActiveGesture(false);
1472
- const items = [];
1473
- for (const id of (_a = group.members) !== null && _a !== void 0 ? _a : []) {
1474
- if (!memberEntity(id))
1475
- continue;
1476
- items.push(itemFor(id));
1477
- }
1478
- // CARRY THE PER-COLUMN CACHE ACROSS THE REBUILD. sync() runs on every
1479
- // undo, member add and refresh; without this handoff a responsive board
1480
- // would silently lose its wide layouts the first time anything else
1481
- // happened, and growing back would re-derive instead of restoring.
1482
- const carried = engine.getLayouts();
1483
- engine = new GridPackEngine(items, { columns, float, maxRows });
1484
- engine.setLayouts(carried);
1485
- for (const item of engine.getItems())
1486
- persistAdoptedCell(item.id, item);
1487
- project();
1488
- syncHandles();
1489
- api.renderNow();
1490
- evaluateResponsive();
1491
- },
1492
- setColumns(n, layout, opts) {
1493
- var _a;
1494
- if (disposed)
1495
- return false;
1496
- if (!(opts === null || opts === void 0 ? void 0 : opts.responsive))
1497
- responsivePinned = true;
1498
- return applyColumns(n, (_a = layout !== null && layout !== void 0 ? layout : responsive === null || responsive === void 0 ? void 0 : responsive.layout) !== null && _a !== void 0 ? _a : 'moveScale');
1499
- },
1500
- getColumns: () => columns,
1501
- setRtl(on) {
1502
- if (on === rtl)
1503
- return;
1504
- rtl = on;
1505
- // Pixels only — the cells are already correct in both directions.
1506
- project();
1507
- syncHandles();
1508
- api.renderNow();
1509
- },
1510
- getRtl: () => rtl,
1511
- saveLayout() {
1512
- const saved = engine.saveLayout();
1513
- return {
1514
- columns: saved.columns,
1515
- cells: new Map(saved.items.map((i) => [i.id, { x: i.x, y: i.y, w: i.w, h: i.h }])),
1516
- };
1517
- },
1518
- setSizing(mode) {
1519
- if (mode === sizing)
1520
- return;
1521
- sizing = mode;
1522
- project();
1523
- api.renderNow();
1524
- },
1525
- getSizing: () => sizing,
1526
- setFloat(on) {
1527
- if (on === float)
1528
- return;
1529
- float = on;
1530
- // Rebuild from persisted cells under the new mode: sync() constructs a
1531
- // fresh engine with the captured `float`, and gravity (when turning
1532
- // OFF) packs immediately through its settle.
1533
- this.sync();
1534
- api.renderNow();
1535
- },
1536
- getFloat: () => float,
1537
- metrics() {
1538
- const f = frame();
1539
- const g = geom();
1540
- const r = rows();
1541
- return {
1542
- columns,
1543
- maxColumns,
1544
- rtl,
1545
- responsive: !!responsive && !responsivePinned,
1546
- gap,
1547
- padding,
1548
- sizing,
1549
- rows: r,
1550
- rowHeight: rowHeightFor(g, r),
1551
- columnUnit: columnUnitFor(g, f.width),
1552
- boardHeight: f.height,
1553
- frame: f,
1554
- };
1555
- },
1556
- cellOf(id) {
1557
- const it = engine.getItem(id);
1558
- return it ? { x: it.x, y: it.y, w: it.w, h: it.h } : undefined;
1559
- },
1560
- cellRectOf(id) {
1561
- const it = engine.getItem(id);
1562
- return it ? cellToRect(it, frame(), geom(), rows()) : undefined;
1563
- },
1564
- planRemoval(id) {
1565
- const it = engine.getItem(id);
1566
- if (!it)
1567
- return [];
1568
- const clone = new GridPackEngine(engine.getItems().map((i) => (Object.assign({}, i))), { columns, float });
1569
- clone.remove(id);
1570
- const f = frame();
1571
- const g = geom();
1572
- const rAfter = Math.max(1, clone.rows());
1573
- const deltas = [];
1574
- for (const item of clone.getItems()) {
1575
- const before = engine.getItem(item.id);
1576
- const e = memberEntity(item.id);
1577
- if (!before || !e)
1578
- continue;
1579
- const target = cellToRect(item, f, g, rAfter);
1580
- deltas.push({
1581
- id: item.id,
1582
- locked: !!item.locked,
1583
- isGroup: isGroupMember(item.id),
1584
- cellBefore: { x: before.x, y: before.y, w: before.w, h: before.h },
1585
- cellAfter: { x: item.x, y: item.y, w: item.w, h: item.h },
1586
- posBefore: { x: e.position.x, y: e.position.y },
1587
- posAfter: { x: target.x, y: target.y },
1588
- sizeBefore: (({ width, height, depth }) => ({ width, height, depth }))(sizeOf(e)),
1589
- sizeAfter: { width: target.width, height: target.height },
1590
- });
1591
- }
1592
- return buildCommitCommands(deltas);
1593
- },
1594
- moveTo(id, x, y) {
1595
- return programmatic('Move widget', id, () => engine.moveCheck(id, x, y).changed);
1596
- },
1597
- resizeTo(id, w, h) {
1598
- return programmatic('Resize widget', id, () => engine.resizeCheck(id, w, h).changed);
1599
- },
1600
- beginPaletteDrag,
1601
- dispose() {
1602
- var _a;
1603
- if (disposed)
1604
- return;
1605
- cancelActiveGesture(false);
1606
- disposed = true;
1607
- peersOnCanvas().delete(selfPeer);
1608
- unregisterTool();
1609
- hostObserver.disconnect();
1610
- containerObserver === null || containerObserver === void 0 ? void 0 : containerObserver.disconnect();
1611
- for (const off of subs)
1612
- off();
1613
- placeholder === null || placeholder === void 0 ? void 0 : placeholder.remove();
1614
- placeholder = null;
1615
- if (glideTimer)
1616
- clearTimeout(glideTimer);
1617
- if (ghostTimer)
1618
- clearTimeout(ghostTimer);
1619
- (_a = htmlLayer()) === null || _a === void 0 ? void 0 : _a.classList.remove('axdb-glide');
1620
- api.container.style.cursor = '';
1621
- },
1622
- };
1623
- // Boot: adopt the current members, observe host churn for handle re-injection,
1624
- // and let a responsive board settle on the count its width asks for before the
1625
- // first frame (a 400px board declared at 12 columns must not flash at 12).
1626
- handle.sync();
1627
- const layer = htmlLayer();
1628
- if (layer)
1629
- hostObserver.observe(layer, { childList: true, subtree: true });
1630
- containerObserver === null || containerObserver === void 0 ? void 0 : containerObserver.observe(api.container);
1631
- return handle;
1632
- }
1633
- //# sourceMappingURL=grid-binder.js.map