@grafloria/element 0.4.31 → 0.4.33
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/package.json +1 -1
- package/src/lib/dashboard-kit/dashboard.d.ts +16 -2
- package/src/lib/dashboard-kit/dashboard.js +428 -114
- package/src/lib/dashboard-kit/grid-binder.d.ts +82 -2
- package/src/lib/dashboard-kit/grid-binder.js +708 -109
- package/src/lib/dashboard-kit/split-binder.js +2 -0
- package/src/lib/dashboard-kit/tabs.d.ts +1 -1
- package/src/lib/dashboard-kit/tabs.js +1 -1
- package/src/lib/load.js +9 -1
|
@@ -156,6 +156,38 @@ export function pressOnDragHandle(sel, target, hostEl, clientX, clientY) {
|
|
|
156
156
|
export const CAPTION_BAND = 28;
|
|
157
157
|
/** A torn-out page never arrives shorter than this: a strip with no room under it is not a group. */
|
|
158
158
|
export const TEAR_OUT_MIN_ROWS = 2;
|
|
159
|
+
/**
|
|
160
|
+
* Steps that depend on each other's RESULT — create a group, then move a page
|
|
161
|
+
* into it, then place the group on the board; or remove a member and then the
|
|
162
|
+
* group it emptied — run as one history step. A batch checks every member's
|
|
163
|
+
* `canExecute` before running any of them and every member's `canUndo` before
|
|
164
|
+
* undoing any, and a membership command's precondition (its group exists) is
|
|
165
|
+
* exactly what a neighbouring step creates or removes. This runs the chain in
|
|
166
|
+
* order and reverses it on undo, judging nothing up front.
|
|
167
|
+
*/
|
|
168
|
+
export class SequenceCommand extends Command {
|
|
169
|
+
constructor(name, steps) {
|
|
170
|
+
super(name);
|
|
171
|
+
this.steps = steps;
|
|
172
|
+
}
|
|
173
|
+
execute(context) {
|
|
174
|
+
for (const c of this.steps)
|
|
175
|
+
c.execute(context);
|
|
176
|
+
}
|
|
177
|
+
undo(context) {
|
|
178
|
+
for (let i = this.steps.length - 1; i >= 0; i--)
|
|
179
|
+
this.steps[i].undo(context);
|
|
180
|
+
}
|
|
181
|
+
canExecute() {
|
|
182
|
+
return true;
|
|
183
|
+
}
|
|
184
|
+
canUndo() {
|
|
185
|
+
return true;
|
|
186
|
+
}
|
|
187
|
+
serialize() {
|
|
188
|
+
return { id: this.id, name: this.name, timestamp: this.timestamp, data: { steps: this.steps.map((c) => c.serialize()) } };
|
|
189
|
+
}
|
|
190
|
+
}
|
|
159
191
|
/**
|
|
160
192
|
* Binders on the same canvas find each other here. A WeakMap: a canvas that
|
|
161
193
|
* is gone takes its (empty) peer set with it — the strong Map it replaced kept
|
|
@@ -1257,6 +1289,8 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1257
1289
|
return false;
|
|
1258
1290
|
};
|
|
1259
1291
|
let slabGesture = null;
|
|
1292
|
+
/** The slab gesture as it is NOW — read through a call so a guard's narrowing does not stick. */
|
|
1293
|
+
const currentSlab = () => slabGesture;
|
|
1260
1294
|
/** The PARENT running a resize of OUR section from a press this tool claimed. */
|
|
1261
1295
|
let forwardSlab = null;
|
|
1262
1296
|
const frameOfGroup = (grp) => ({ x: grp.position.x, y: grp.position.y, width: sizeOf(grp).width, height: sizeOf(grp).height });
|
|
@@ -1281,10 +1315,48 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1281
1315
|
dx: edges.e ? grp.position.x + sizeOf(grp).width - ev.world.x : edges.w ? grp.position.x - ev.world.x : 0,
|
|
1282
1316
|
dy: edges.s ? grp.position.y + sizeOf(grp).height - ev.world.y : edges.n ? grp.position.y - ev.world.y : 0,
|
|
1283
1317
|
},
|
|
1318
|
+
move: false,
|
|
1284
1319
|
};
|
|
1285
1320
|
capturePointer(slabGesture.pointerId);
|
|
1286
1321
|
api.container.style.cursor = cursorFor(edges);
|
|
1287
1322
|
};
|
|
1323
|
+
/**
|
|
1324
|
+
* MOVE a section by its caption band or its strip's empty space. A section
|
|
1325
|
+
* is a LOCKED tile so that nothing pushes it; for the gesture's duration the
|
|
1326
|
+
* tile is unlocked (it is the one moving), other sections stay locked and
|
|
1327
|
+
* still refuse it (E4b), and every unlocked tile gets pushed the way a
|
|
1328
|
+
* widget pushes it. Its children ride along: the frame moves, the nested
|
|
1329
|
+
* board re-projects.
|
|
1330
|
+
*/
|
|
1331
|
+
const beginSlabMove = (id, ev) => {
|
|
1332
|
+
const grp = diagram.getGroup(id);
|
|
1333
|
+
const it = engine.getItem(id);
|
|
1334
|
+
if (!grp || !it || gesture || slabGesture || isStatic)
|
|
1335
|
+
return;
|
|
1336
|
+
engine.beginGesture();
|
|
1337
|
+
const snap = snapshotAll();
|
|
1338
|
+
it.locked = false;
|
|
1339
|
+
slabGesture = {
|
|
1340
|
+
id,
|
|
1341
|
+
edges: NO_EDGES,
|
|
1342
|
+
pointerId: typeof PointerEvent !== 'undefined' && ev.source instanceof PointerEvent ? ev.source.pointerId : null,
|
|
1343
|
+
started: false,
|
|
1344
|
+
downScreen: { x: ev.screen.x, y: ev.screen.y },
|
|
1345
|
+
startCells: snap.cells,
|
|
1346
|
+
startGeom: snap.geoms,
|
|
1347
|
+
cellBefore: { x: it.x, y: it.y, w: it.w, h: it.h },
|
|
1348
|
+
frameBefore: frameOfGroup(grp),
|
|
1349
|
+
grab: { dx: grp.position.x - ev.world.x, dy: grp.position.y - ev.world.y },
|
|
1350
|
+
move: true,
|
|
1351
|
+
};
|
|
1352
|
+
capturePointer(slabGesture.pointerId);
|
|
1353
|
+
api.container.style.cursor = 'grabbing';
|
|
1354
|
+
};
|
|
1355
|
+
const relockSlab = (id) => {
|
|
1356
|
+
const it = engine.getItem(id);
|
|
1357
|
+
if (it)
|
|
1358
|
+
it.locked = true;
|
|
1359
|
+
};
|
|
1288
1360
|
const slabMove = (ev) => {
|
|
1289
1361
|
const g = slabGesture;
|
|
1290
1362
|
if (!g)
|
|
@@ -1300,6 +1372,14 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1300
1372
|
return;
|
|
1301
1373
|
const f = frame();
|
|
1302
1374
|
const gg = geom();
|
|
1375
|
+
if (g.move) {
|
|
1376
|
+
// The section's top-left follows the pointer by the offset it was grabbed at.
|
|
1377
|
+
const cell = pointToCell(ev.world.x + g.grab.dx, ev.world.y + g.grab.dy, f, gg, rows(), it.w);
|
|
1378
|
+
if ((cell.x !== it.x || cell.y !== it.y) && engine.moveCheck(g.id, cell.x, cell.y, { gate: false }).changed)
|
|
1379
|
+
project();
|
|
1380
|
+
syncPlaceholder();
|
|
1381
|
+
return;
|
|
1382
|
+
}
|
|
1303
1383
|
const cu = columnUnitFor(gg, f.width);
|
|
1304
1384
|
const rh = rowHeightFor(gg, rows());
|
|
1305
1385
|
// The grid line nearest the pointer, in cells (mirrored on RTL).
|
|
@@ -1346,6 +1426,8 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1346
1426
|
slabGesture = null;
|
|
1347
1427
|
releasePointer(g.pointerId);
|
|
1348
1428
|
api.container.style.cursor = '';
|
|
1429
|
+
if (g.move)
|
|
1430
|
+
relockSlab(g.id);
|
|
1349
1431
|
if (!g.started) {
|
|
1350
1432
|
engine.endGesture();
|
|
1351
1433
|
return;
|
|
@@ -1359,11 +1441,11 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1359
1441
|
if (it && grp && (b.x !== it.x || b.y !== it.y || b.w !== it.w || b.h !== it.h)) {
|
|
1360
1442
|
commands.push(new SetGroupCellCommand(g.id, b, { x: it.x, y: it.y, w: it.w, h: it.h }, g.frameBefore, frameOfGroup(grp)));
|
|
1361
1443
|
}
|
|
1362
|
-
const changed = execute('Resize section', commands);
|
|
1444
|
+
const changed = execute(g.move ? 'Move section' : 'Resize section', commands);
|
|
1363
1445
|
disarmGlideSoon();
|
|
1364
1446
|
syncHandles();
|
|
1365
1447
|
api.renderNow();
|
|
1366
|
-
(_a = options.onGesture) === null || _a === void 0 ? void 0 : _a.call(options, { type: 'commit', kind: 'resize', nodeId: g.id, changed });
|
|
1448
|
+
(_a = options.onGesture) === null || _a === void 0 ? void 0 : _a.call(options, { type: 'commit', kind: g.move ? 'move' : 'resize', nodeId: g.id, changed });
|
|
1367
1449
|
};
|
|
1368
1450
|
const slabCancel = () => {
|
|
1369
1451
|
var _a;
|
|
@@ -1373,6 +1455,8 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1373
1455
|
slabGesture = null;
|
|
1374
1456
|
releasePointer(g.pointerId);
|
|
1375
1457
|
api.container.style.cursor = '';
|
|
1458
|
+
if (g.move)
|
|
1459
|
+
relockSlab(g.id);
|
|
1376
1460
|
if (g.started)
|
|
1377
1461
|
engine.cancelGesture();
|
|
1378
1462
|
else
|
|
@@ -1487,9 +1571,13 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1487
1571
|
(_a = options.onGesture) === null || _a === void 0 ? void 0 : _a.call(options, { type: 'commit', kind: g.kind, nodeId: g.id, changed });
|
|
1488
1572
|
};
|
|
1489
1573
|
const cancelActiveGesture = (notify = true) => {
|
|
1490
|
-
var _a, _b, _c, _d;
|
|
1574
|
+
var _a, _b, _c, _d, _e;
|
|
1575
|
+
if (gesture === null || gesture === void 0 ? void 0 : gesture.strip) {
|
|
1576
|
+
(_a = options.tabDrop) === null || _a === void 0 ? void 0 : _a.markDrop(null, null);
|
|
1577
|
+
gesture.strip = null;
|
|
1578
|
+
}
|
|
1491
1579
|
if (forwardSlab) {
|
|
1492
|
-
(
|
|
1580
|
+
(_b = forwardSlab.slabCancel) === null || _b === void 0 ? void 0 : _b.call(forwardSlab);
|
|
1493
1581
|
forwardSlab = null;
|
|
1494
1582
|
}
|
|
1495
1583
|
if (slabGesture)
|
|
@@ -1514,7 +1602,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1514
1602
|
engine.endGesture();
|
|
1515
1603
|
const items = [];
|
|
1516
1604
|
for (const [id, c] of g.startCells) {
|
|
1517
|
-
const lockedNode = ((
|
|
1605
|
+
const lockedNode = ((_d = (_c = diagram.getNode(id)) === null || _c === void 0 ? void 0 : _c.state) === null || _d === void 0 ? void 0 : _d.locked) === true;
|
|
1518
1606
|
items.push(Object.assign(Object.assign({ id }, c), { locked: lockedNode || isGroupMember(id) }));
|
|
1519
1607
|
}
|
|
1520
1608
|
engine = engineFrom(items);
|
|
@@ -1562,7 +1650,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1562
1650
|
enforceBoardHeight();
|
|
1563
1651
|
api.renderNow();
|
|
1564
1652
|
if (notify) {
|
|
1565
|
-
(
|
|
1653
|
+
(_e = options.onGesture) === null || _e === void 0 ? void 0 : _e.call(options, { type: 'cancel', kind: g.kind, nodeId: g.id, changed: false });
|
|
1566
1654
|
}
|
|
1567
1655
|
};
|
|
1568
1656
|
/** Centre a w×h-span tile's top-left under the cursor, in world px. */
|
|
@@ -1577,7 +1665,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1577
1665
|
};
|
|
1578
1666
|
};
|
|
1579
1667
|
const onToolMove = (ev) => {
|
|
1580
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
1668
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
|
|
1581
1669
|
const g = gesture;
|
|
1582
1670
|
if (!g || g.kind === 'palette')
|
|
1583
1671
|
return;
|
|
@@ -1594,6 +1682,36 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1594
1682
|
ghostStyleFastPath(g, desired);
|
|
1595
1683
|
g.lastWorld = { x: ev.world.x, y: ev.world.y };
|
|
1596
1684
|
g.lastScreen = { x: ev.screen.x, y: ev.screen.y };
|
|
1685
|
+
// A TAB STRIP under the pointer wins over every board: the widget will
|
|
1686
|
+
// become a new tab there, so it leaves this board (survivors settle
|
|
1687
|
+
// home) and the strip marks the slot.
|
|
1688
|
+
if (options.tabDrop && !isStatic) {
|
|
1689
|
+
const crect = api.container.getBoundingClientRect();
|
|
1690
|
+
const hitStrip = options.tabDrop.stripAt(crect.left + ev.screen.x, crect.top + ev.screen.y);
|
|
1691
|
+
if (hitStrip) {
|
|
1692
|
+
if (g.leg) {
|
|
1693
|
+
g.leg.adopted.abort();
|
|
1694
|
+
g.leg = null;
|
|
1695
|
+
}
|
|
1696
|
+
if (!g.removedFromBoard) {
|
|
1697
|
+
g.removedFromBoard = true;
|
|
1698
|
+
engine.remove(g.id);
|
|
1699
|
+
project();
|
|
1700
|
+
}
|
|
1701
|
+
(_a = hostOf(g.id)) === null || _a === void 0 ? void 0 : _a.classList.remove('axdb-out');
|
|
1702
|
+
if (!g.strip || g.strip.containerId !== hitStrip.containerId || g.strip.index !== hitStrip.index) {
|
|
1703
|
+
options.tabDrop.markDrop(hitStrip.containerId, hitStrip.index);
|
|
1704
|
+
}
|
|
1705
|
+
g.strip = hitStrip;
|
|
1706
|
+
syncPlaceholder();
|
|
1707
|
+
api.render();
|
|
1708
|
+
return;
|
|
1709
|
+
}
|
|
1710
|
+
if (g.strip) {
|
|
1711
|
+
options.tabDrop.markDrop(null, null);
|
|
1712
|
+
g.strip = null;
|
|
1713
|
+
}
|
|
1714
|
+
}
|
|
1597
1715
|
// Deepest board under the pointer wins: the nested KPI strip beats the
|
|
1598
1716
|
// tab that contains it; a foreign board beats "outside". Strict frames
|
|
1599
1717
|
// first; the one-row grace band below each board (gridstack's extra
|
|
@@ -1633,13 +1751,13 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1633
1751
|
height: g.node.size.height,
|
|
1634
1752
|
});
|
|
1635
1753
|
if (adopted) {
|
|
1636
|
-
(
|
|
1754
|
+
(_b = hostOf(g.id)) === null || _b === void 0 ? void 0 : _b.classList.remove('axdb-out');
|
|
1637
1755
|
g.leg = { peer, adopted };
|
|
1638
1756
|
g.leg.adopted.move(ev.world);
|
|
1639
1757
|
}
|
|
1640
1758
|
else {
|
|
1641
1759
|
// Bounded/full board refused the adoption: dim = will snap home.
|
|
1642
|
-
(
|
|
1760
|
+
(_c = hostOf(g.id)) === null || _c === void 0 ? void 0 : _c.classList.add('axdb-out');
|
|
1643
1761
|
}
|
|
1644
1762
|
}
|
|
1645
1763
|
}
|
|
@@ -1651,7 +1769,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1651
1769
|
}
|
|
1652
1770
|
if (g.removedFromBoard) {
|
|
1653
1771
|
g.removedFromBoard = false;
|
|
1654
|
-
(
|
|
1772
|
+
(_d = hostOf(g.id)) === null || _d === void 0 ? void 0 : _d.classList.remove('axdb-out');
|
|
1655
1773
|
const cell = pointToCell(desired.x, desired.y, frame(), geom(), rows(), g.spans.w);
|
|
1656
1774
|
// Re-enter at the bottom edge (collision-free), then take the cursor
|
|
1657
1775
|
// cell GATELESSLY — a first placement skips the anti-jitter gate.
|
|
@@ -1660,7 +1778,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1660
1778
|
project();
|
|
1661
1779
|
}
|
|
1662
1780
|
else {
|
|
1663
|
-
const spanW = (
|
|
1781
|
+
const spanW = (_f = (_e = engine.getItem(g.id)) === null || _e === void 0 ? void 0 : _e.w) !== null && _f !== void 0 ? _f : g.spans.w;
|
|
1664
1782
|
const cell = pointToCell(desired.x, desired.y, frame(), geom(), rows(), spanW);
|
|
1665
1783
|
if (engine.moveCheck(g.id, cell.x, cell.y).changed)
|
|
1666
1784
|
project();
|
|
@@ -1675,7 +1793,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1675
1793
|
if (!g.removedFromBoard) {
|
|
1676
1794
|
g.removedFromBoard = true;
|
|
1677
1795
|
engine.remove(g.id); // survivors settle home; cells minted nowhere
|
|
1678
|
-
(
|
|
1796
|
+
(_g = hostOf(g.id)) === null || _g === void 0 ? void 0 : _g.classList.add('axdb-out');
|
|
1679
1797
|
project();
|
|
1680
1798
|
}
|
|
1681
1799
|
}
|
|
@@ -1785,7 +1903,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1785
1903
|
maxRows = n;
|
|
1786
1904
|
engine.maxRows = n;
|
|
1787
1905
|
};
|
|
1788
|
-
const slabRows = (
|
|
1906
|
+
const slabRows = (_j = (_h = parent.memberCell(group.id)) === null || _h === void 0 ? void 0 : _h.h) !== null && _j !== void 0 ? _j : maxRows;
|
|
1789
1907
|
const inner = maxRows;
|
|
1790
1908
|
const rhNow = rowHeightFor(geom(), rows());
|
|
1791
1909
|
const rowPx = rhNow + gap;
|
|
@@ -1949,7 +2067,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1949
2067
|
const anchorBottom = liveRect ? liveRect.y + liveRect.height : bottom;
|
|
1950
2068
|
const px = E.w ? anchorRight - w : anchorLeft;
|
|
1951
2069
|
const py = E.n ? anchorBottom - h : anchorTop;
|
|
1952
|
-
g.node.setSize(w, h, (
|
|
2070
|
+
g.node.setSize(w, h, (_k = g.node.size.depth) !== null && _k !== void 0 ? _k : 0);
|
|
1953
2071
|
g.node.setPosition(px, py);
|
|
1954
2072
|
ghostStyleFastPath(g, { x: px, y: py, width: w, height: h });
|
|
1955
2073
|
const spanF = bound() !== undefined ? frame() : f;
|
|
@@ -1990,7 +2108,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1990
2108
|
syncPlaceholder();
|
|
1991
2109
|
};
|
|
1992
2110
|
const onToolUp = () => {
|
|
1993
|
-
var _a, _b, _c, _d, _e, _f;
|
|
2111
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
|
|
1994
2112
|
const g = gesture;
|
|
1995
2113
|
if (!g || g.kind === 'palette')
|
|
1996
2114
|
return;
|
|
@@ -1998,6 +2116,36 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1998
2116
|
gesture = null; // a plain click — the page's own click-to-focus handles it
|
|
1999
2117
|
return;
|
|
2000
2118
|
}
|
|
2119
|
+
if (g.strip && options.tabDrop) {
|
|
2120
|
+
// -- INTO A STRIP: the widget becomes a new tab of that container ------
|
|
2121
|
+
const target = g.strip;
|
|
2122
|
+
g.strip = null;
|
|
2123
|
+
options.tabDrop.markDrop(null, null);
|
|
2124
|
+
if (g.leg) {
|
|
2125
|
+
g.leg.adopted.abort();
|
|
2126
|
+
g.leg = null;
|
|
2127
|
+
}
|
|
2128
|
+
const displaced = buildCommitCommands(deltasSince(g.startCells, g.startGeom, g.id));
|
|
2129
|
+
const snap = g.startGeom.get(g.id);
|
|
2130
|
+
if (snap) {
|
|
2131
|
+
g.node.setPosition(snap.pos.x, snap.pos.y);
|
|
2132
|
+
g.node.setSize(snap.size.width, snap.size.height, (_a = snap.size.depth) !== null && _a !== void 0 ? _a : 0);
|
|
2133
|
+
}
|
|
2134
|
+
const cmds = options.tabDrop.dropIntoStrip(g.id, target.containerId, target.index, group.id, displaced);
|
|
2135
|
+
if (cmds.length === 0) {
|
|
2136
|
+
cancelActiveGesture();
|
|
2137
|
+
return;
|
|
2138
|
+
}
|
|
2139
|
+
engine.endGesture();
|
|
2140
|
+
cleanupGestureVisuals(g);
|
|
2141
|
+
gesture = null;
|
|
2142
|
+
execute('Move widget into a new tab', cmds);
|
|
2143
|
+
enforceBoardHeight();
|
|
2144
|
+
persistLayouts();
|
|
2145
|
+
api.renderNow();
|
|
2146
|
+
(_b = options.onGesture) === null || _b === void 0 ? void 0 : _b.call(options, { type: 'commit', kind: g.kind, nodeId: g.id, changed: true });
|
|
2147
|
+
return;
|
|
2148
|
+
}
|
|
2001
2149
|
if (g.leg) {
|
|
2002
2150
|
// -- CROSS-CONTAINER COMMIT: one batch across both boards -----------
|
|
2003
2151
|
const fin = g.leg.adopted.finalize();
|
|
@@ -2040,21 +2188,25 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2040
2188
|
isGroup: false,
|
|
2041
2189
|
cellBefore: before !== null && before !== void 0 ? before : fin.cell,
|
|
2042
2190
|
cellAfter: fin.cell,
|
|
2043
|
-
posBefore: (
|
|
2191
|
+
posBefore: (_c = geomBefore === null || geomBefore === void 0 ? void 0 : geomBefore.pos) !== null && _c !== void 0 ? _c : { x: fin.rect.x, y: fin.rect.y },
|
|
2044
2192
|
posAfter: { x: fin.rect.x, y: fin.rect.y },
|
|
2045
|
-
sizeBefore: (
|
|
2193
|
+
sizeBefore: (_d = geomBefore === null || geomBefore === void 0 ? void 0 : geomBefore.size) !== null && _d !== void 0 ? _d : { width: fin.rect.width, height: fin.rect.height },
|
|
2046
2194
|
sizeAfter: { width: fin.rect.width, height: fin.rect.height },
|
|
2047
2195
|
},
|
|
2048
2196
|
]),
|
|
2049
2197
|
];
|
|
2050
|
-
|
|
2198
|
+
// …and whatever follows a member out of this board: an emptied page
|
|
2199
|
+
// closes — in which case the membership commands ride INSIDE one
|
|
2200
|
+
// sequence with it, or the batch could never undo (its group is gone).
|
|
2201
|
+
const leaving = (_f = (_e = options.onMemberLeaving) === null || _e === void 0 ? void 0 : _e.call(options, g.id)) !== null && _f !== void 0 ? _f : [];
|
|
2202
|
+
execute('Move widget', leaving.length > 0 ? [new SequenceCommand('Move widget', [...crossing, ...leaving])] : crossing);
|
|
2051
2203
|
engine.endGesture();
|
|
2052
2204
|
cleanupGestureVisuals(g);
|
|
2053
2205
|
gesture = null;
|
|
2054
2206
|
enforceBoardHeight();
|
|
2055
2207
|
persistLayouts();
|
|
2056
2208
|
api.renderNow();
|
|
2057
|
-
(
|
|
2209
|
+
(_g = options.onGesture) === null || _g === void 0 ? void 0 : _g.call(options, { type: 'commit', kind: g.kind, nodeId: g.id, changed: true });
|
|
2058
2210
|
return;
|
|
2059
2211
|
}
|
|
2060
2212
|
if (g.removedFromBoard && dragOut === 'cancel') {
|
|
@@ -2085,15 +2237,15 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2085
2237
|
// Park the node on its start rect so the page's RemoveNodeCommand
|
|
2086
2238
|
// captures sane geometry for undo.
|
|
2087
2239
|
g.node.setPosition(snap.pos.x, snap.pos.y);
|
|
2088
|
-
g.node.setSize(snap.size.width, snap.size.height, (
|
|
2240
|
+
g.node.setSize(snap.size.width, snap.size.height, (_h = snap.size.depth) !== null && _h !== void 0 ? _h : 0);
|
|
2089
2241
|
}
|
|
2090
2242
|
engine.endGesture();
|
|
2091
2243
|
cleanupGestureVisuals(g);
|
|
2092
2244
|
gesture = null;
|
|
2093
2245
|
enforceBoardHeight();
|
|
2094
2246
|
api.renderNow();
|
|
2095
|
-
void ((
|
|
2096
|
-
(
|
|
2247
|
+
void ((_j = options.onRemoveRequest) === null || _j === void 0 ? void 0 : _j.call(options, g.id, displaced));
|
|
2248
|
+
(_k = options.onGesture) === null || _k === void 0 ? void 0 : _k.call(options, { type: 'remove', kind: g.kind, nodeId: g.id, changed: true });
|
|
2097
2249
|
return;
|
|
2098
2250
|
}
|
|
2099
2251
|
commitGesture(g);
|
|
@@ -2301,6 +2453,34 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2301
2453
|
syncPlaceholder();
|
|
2302
2454
|
return {
|
|
2303
2455
|
groupId: group.id,
|
|
2456
|
+
baseline: () => startCells,
|
|
2457
|
+
place: (cell) => {
|
|
2458
|
+
if (!engine.getItem(node.id) && !engine.add({ id: node.id, x: 0, y: engine.rows(), w: cell.w, h: cell.h }))
|
|
2459
|
+
return false;
|
|
2460
|
+
const it = engine.getItem(node.id);
|
|
2461
|
+
if (!it)
|
|
2462
|
+
return false;
|
|
2463
|
+
// Shrink, move, then grow. A resize is clamped at the board's edge
|
|
2464
|
+
// where the ghost SITS, so a cell wider than fits there (a full-width
|
|
2465
|
+
// dock from a ghost parked mid-board) is reached by moving first —
|
|
2466
|
+
// and a cell narrower than the ghost by shrinking first, so the move
|
|
2467
|
+
// itself fits.
|
|
2468
|
+
const w0 = Math.min(it.w, cell.w);
|
|
2469
|
+
const h0 = Math.min(it.h, cell.h);
|
|
2470
|
+
if (w0 !== it.w || h0 !== it.h)
|
|
2471
|
+
engine.resizeCheck(node.id, w0, h0);
|
|
2472
|
+
const moved = engine.getItem(node.id);
|
|
2473
|
+
if (moved && (moved.x !== cell.x || moved.y !== cell.y))
|
|
2474
|
+
engine.moveCheck(node.id, cell.x, cell.y, { gate: false });
|
|
2475
|
+
const now = engine.getItem(node.id);
|
|
2476
|
+
if (now && (now.w !== cell.w || now.h !== cell.h))
|
|
2477
|
+
engine.resizeCheck(node.id, cell.w, cell.h);
|
|
2478
|
+
lastWant = null;
|
|
2479
|
+
project();
|
|
2480
|
+
syncPlaceholder();
|
|
2481
|
+
const at = engine.getItem(node.id);
|
|
2482
|
+
return !!at && at.x === cell.x && at.y === cell.y && at.w === cell.w && at.h === cell.h;
|
|
2483
|
+
},
|
|
2304
2484
|
move: (w) => {
|
|
2305
2485
|
const item = engine.getItem(node.id);
|
|
2306
2486
|
if (!item)
|
|
@@ -2354,11 +2534,29 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2354
2534
|
const cell = { x: item.x, y: item.y, w: item.w, h: item.h };
|
|
2355
2535
|
const rect = cellToRect(item, frame(), geom(), rows());
|
|
2356
2536
|
const commands = buildCommitCommands(deltasSince(startCells, startGeom, node.id));
|
|
2537
|
+
const groups = [];
|
|
2538
|
+
for (const [id, before] of startCells) {
|
|
2539
|
+
if (!isGroupMember(id))
|
|
2540
|
+
continue;
|
|
2541
|
+
const it = engine.getItem(id);
|
|
2542
|
+
const g0 = startGeom.get(id);
|
|
2543
|
+
if (!it || !g0)
|
|
2544
|
+
continue;
|
|
2545
|
+
if (it.x === before.x && it.y === before.y && it.w === before.w && it.h === before.h)
|
|
2546
|
+
continue;
|
|
2547
|
+
groups.push({
|
|
2548
|
+
id,
|
|
2549
|
+
cellBefore: before,
|
|
2550
|
+
cellAfter: { x: it.x, y: it.y, w: it.w, h: it.h },
|
|
2551
|
+
frameBefore: { x: g0.pos.x, y: g0.pos.y, width: g0.size.width, height: g0.size.height },
|
|
2552
|
+
frameAfter: cellToRect(it, frame(), geom(), rows()),
|
|
2553
|
+
});
|
|
2554
|
+
}
|
|
2357
2555
|
engine.endGesture();
|
|
2358
2556
|
adoptedGhostId = null;
|
|
2359
2557
|
disarmGlideSoon();
|
|
2360
2558
|
syncPlaceholder();
|
|
2361
|
-
return { commands, cell, rect };
|
|
2559
|
+
return { commands, cell, rect, groups };
|
|
2362
2560
|
},
|
|
2363
2561
|
};
|
|
2364
2562
|
};
|
|
@@ -2388,6 +2586,56 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2388
2586
|
beginSlabResize(id, edges, ev);
|
|
2389
2587
|
return (slabGesture === null || slabGesture === void 0 ? void 0 : slabGesture.id) === id;
|
|
2390
2588
|
},
|
|
2589
|
+
beginSlabMove: (id, ev) => {
|
|
2590
|
+
if (isStatic)
|
|
2591
|
+
return false;
|
|
2592
|
+
beginSlabMove(id, ev);
|
|
2593
|
+
return (slabGesture === null || slabGesture === void 0 ? void 0 : slabGesture.id) === id;
|
|
2594
|
+
},
|
|
2595
|
+
dragMember: (id, ev) => {
|
|
2596
|
+
var _a;
|
|
2597
|
+
if (isStatic || disposed || !engine.getItem(id) || gesture || slabGesture)
|
|
2598
|
+
return false;
|
|
2599
|
+
const toTool = (e) => {
|
|
2600
|
+
var _a;
|
|
2601
|
+
const rect = api.container.getBoundingClientRect();
|
|
2602
|
+
const world = ((_a = api.viewport) === null || _a === void 0 ? void 0 : _a.clientToWorld) ? api.viewport.clientToWorld(e.clientX, e.clientY, rect) : { x: e.clientX - rect.left, y: e.clientY - rect.top };
|
|
2603
|
+
return { world, screen: { x: e.clientX - rect.left, y: e.clientY - rect.top }, source: e };
|
|
2604
|
+
};
|
|
2605
|
+
beginSlabMove(id, toTool(ev));
|
|
2606
|
+
if (((_a = currentSlab()) === null || _a === void 0 ? void 0 : _a.id) !== id)
|
|
2607
|
+
return false;
|
|
2608
|
+
const detachAll = () => {
|
|
2609
|
+
window.removeEventListener('pointermove', onMove, true);
|
|
2610
|
+
window.removeEventListener('pointerup', onUp, true);
|
|
2611
|
+
window.removeEventListener('pointercancel', onCancel, true);
|
|
2612
|
+
window.removeEventListener('keydown', onKey, true);
|
|
2613
|
+
};
|
|
2614
|
+
const onMove = (e) => {
|
|
2615
|
+
var _a;
|
|
2616
|
+
if (disposed || ((_a = currentSlab()) === null || _a === void 0 ? void 0 : _a.id) !== id)
|
|
2617
|
+
return detachAll();
|
|
2618
|
+
slabMove(toTool(e));
|
|
2619
|
+
api.render();
|
|
2620
|
+
};
|
|
2621
|
+
const onUp = () => {
|
|
2622
|
+
detachAll();
|
|
2623
|
+
slabUp();
|
|
2624
|
+
};
|
|
2625
|
+
const onCancel = () => {
|
|
2626
|
+
detachAll();
|
|
2627
|
+
slabCancel();
|
|
2628
|
+
};
|
|
2629
|
+
const onKey = (e) => {
|
|
2630
|
+
if (e.key === 'Escape')
|
|
2631
|
+
onCancel();
|
|
2632
|
+
};
|
|
2633
|
+
window.addEventListener('pointermove', onMove, true);
|
|
2634
|
+
window.addEventListener('pointerup', onUp, true);
|
|
2635
|
+
window.addEventListener('pointercancel', onCancel, true);
|
|
2636
|
+
window.addEventListener('keydown', onKey, true);
|
|
2637
|
+
return true;
|
|
2638
|
+
},
|
|
2391
2639
|
slabMove: (ev) => slabMove(ev),
|
|
2392
2640
|
slabUp: () => slabUp(),
|
|
2393
2641
|
slabCancel: () => slabCancel(),
|
|
@@ -2494,7 +2742,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2494
2742
|
return insideMemberGroupFrame(ev.world.x, ev.world.y) || worldInsideBoard(ev.world.x, ev.world.y);
|
|
2495
2743
|
},
|
|
2496
2744
|
onPointerDown(ev, hit) {
|
|
2497
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y;
|
|
2745
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z;
|
|
2498
2746
|
if (gesture)
|
|
2499
2747
|
return; // mid-palette
|
|
2500
2748
|
const target = ((_b = (_a = ev.source) === null || _a === void 0 ? void 0 : _a.target) !== null && _b !== void 0 ? _b : null);
|
|
@@ -2532,6 +2780,10 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2532
2780
|
: slabEdgesNear(grp, ev.world.x, ev.world.y);
|
|
2533
2781
|
if (anyEdge(edges))
|
|
2534
2782
|
beginSlabResize(slabId, edges, ev);
|
|
2783
|
+
// The caption band is the section's handle: pressed and travelled,
|
|
2784
|
+
// it moves the whole section (its inner empty space only selects).
|
|
2785
|
+
else if (ownCaption)
|
|
2786
|
+
beginSlabMove(slabId, ev);
|
|
2535
2787
|
return;
|
|
2536
2788
|
}
|
|
2537
2789
|
// OUR OWN empty band, and we are a section of a parent board: the
|
|
@@ -2548,12 +2800,14 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2548
2800
|
: slabEdgesNear(group, ev.world.x, ev.world.y);
|
|
2549
2801
|
if (anyEdge(edges) && parent.beginSlabResize(group.id, edges, ev))
|
|
2550
2802
|
forwardSlab = parent;
|
|
2803
|
+
else if (!anyEdge(edges) && captionId === group.id && ((_r = parent.beginSlabMove) === null || _r === void 0 ? void 0 : _r.call(parent, group.id, ev)))
|
|
2804
|
+
forwardSlab = parent;
|
|
2551
2805
|
}
|
|
2552
2806
|
return;
|
|
2553
2807
|
}
|
|
2554
2808
|
// The board's own empty area: a void click. Nothing to drag, and the
|
|
2555
2809
|
// selection clears exactly as a click outside any board would.
|
|
2556
|
-
(
|
|
2810
|
+
(_t = (_s = diagram).clearSelection) === null || _t === void 0 ? void 0 : _t.call(_s);
|
|
2557
2811
|
selectWidget(undefined);
|
|
2558
2812
|
api.render();
|
|
2559
2813
|
return;
|
|
@@ -2562,17 +2816,17 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2562
2816
|
if (!node)
|
|
2563
2817
|
return;
|
|
2564
2818
|
selectWidget(node.id); // a press selects, whether or not it starts a gesture
|
|
2565
|
-
if (((
|
|
2819
|
+
if (((_u = node.state) === null || _u === void 0 ? void 0 : _u.locked) === true)
|
|
2566
2820
|
return; // pinned: refuse; click still focuses
|
|
2567
2821
|
if (isStatic)
|
|
2568
2822
|
return; // a static board: claimed and deadened, click still focuses
|
|
2569
2823
|
// Which edges did the press take? The corner handle names its own (s+e,
|
|
2570
2824
|
// or s+w on RTL); a bare press within EDGE_GRIP of the tile's border
|
|
2571
2825
|
// takes that border; anywhere else is a move. A grip press is a move.
|
|
2572
|
-
const onHandle = !!((
|
|
2826
|
+
const onHandle = !!((_v = target === null || target === void 0 ? void 0 : target.closest) === null || _v === void 0 ? void 0 : _v.call(target, '.axdb-rs'));
|
|
2573
2827
|
const hostEl = hostOf(node.id);
|
|
2574
|
-
const resizable = ((
|
|
2575
|
-
const movable = ((
|
|
2828
|
+
const resizable = ((_w = node.getMetadata) === null || _w === void 0 ? void 0 : _w.call(node, 'widgetResizable')) !== false;
|
|
2829
|
+
const movable = ((_x = node.getMetadata) === null || _x === void 0 ? void 0 : _x.call(node, 'widgetMovable')) !== false;
|
|
2576
2830
|
// `ev.screen` is ELEMENT-LOCAL px; host rects are in client px. Compare
|
|
2577
2831
|
// like with like — the source event's clientX/Y when there is one, else
|
|
2578
2832
|
// the container's origin plus the local offset.
|
|
@@ -2620,9 +2874,10 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2620
2874
|
startSize: { width: node.size.width, height: node.size.height },
|
|
2621
2875
|
startPos: { x: node.position.x, y: node.position.y },
|
|
2622
2876
|
edges,
|
|
2623
|
-
spans: { w: (
|
|
2877
|
+
spans: { w: (_y = it === null || it === void 0 ? void 0 : it.w) !== null && _y !== void 0 ? _y : 1, h: (_z = it === null || it === void 0 ? void 0 : it.h) !== null && _z !== void 0 ? _z : 1 },
|
|
2624
2878
|
removedFromBoard: false,
|
|
2625
2879
|
leg: null,
|
|
2880
|
+
strip: null,
|
|
2626
2881
|
lastWorld: null,
|
|
2627
2882
|
lastScreen: null,
|
|
2628
2883
|
hostEl: null,
|
|
@@ -2927,31 +3182,258 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2927
3182
|
const rect = api.container.getBoundingClientRect();
|
|
2928
3183
|
return ((_a = api.viewport) === null || _a === void 0 ? void 0 : _a.clientToWorld) ? api.viewport.clientToWorld(cx, cy, rect) : { x: cx - rect.left, y: cy - rect.top };
|
|
2929
3184
|
};
|
|
2930
|
-
const first = toWorld(ev.clientX, ev.clientY);
|
|
2931
3185
|
// The board holds the NEW group, not the bare page: it is adopted under the
|
|
2932
3186
|
// group's id, sized for the page plus its strip, taking the room under the
|
|
2933
3187
|
// pointer with its top edge at the pointer — the tab is what the pointer
|
|
2934
|
-
// holds, the page hangs below it.
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
3188
|
+
// holds, the page hangs below it. NOT at the press: a ghost in the engine
|
|
3189
|
+
// costs rows, and on a fit board every tile squeezes the moment it enters,
|
|
3190
|
+
// so the ghost enters only when the pointer is over free board space, and
|
|
3191
|
+
// leaves whenever it is over a strip, a group or an edge. A full board that
|
|
3192
|
+
// refuses the ghost still lets the page join, reorder or split.
|
|
3193
|
+
let leg = null;
|
|
3194
|
+
const ensureLeg = (world) => {
|
|
3195
|
+
if (!leg)
|
|
3196
|
+
leg = adopt({ id: plan.arrivingId }, world, plan.size, { fit: 'shrink', anchor: 'top' });
|
|
3197
|
+
return leg;
|
|
3198
|
+
};
|
|
3199
|
+
const naturalSpan = (() => {
|
|
3200
|
+
const sp = sizeToSpan(plan.size.width, plan.size.height, frame(), geom(), rows());
|
|
3201
|
+
return { w: Math.max(1, Math.min(columns, sp.w)), h: Math.max(TEAR_OUT_MIN_ROWS, sp.h) };
|
|
3202
|
+
})();
|
|
3203
|
+
// A DOCKED group takes at most HALF the board — VS Code's edge drop splits
|
|
3204
|
+
// the area in two. A page as tall as the container it left would
|
|
3205
|
+
// otherwise shove the whole dashboard out of view. Half of the board as
|
|
3206
|
+
// it stood at the press, and of what the canvas shows, whichever is less.
|
|
3207
|
+
const dockSpan = (() => {
|
|
3208
|
+
const rows0 = rows();
|
|
3209
|
+
const rect = api.container.getBoundingClientRect();
|
|
3210
|
+
const rh = rowHeightFor(geom(), rows0) + gap;
|
|
3211
|
+
const visible = rect.height > 0 && rh > 0 ? Math.max(1, Math.floor((rect.height + gap) / rh)) : rows0;
|
|
3212
|
+
const halfRows = Math.max(TEAR_OUT_MIN_ROWS, Math.ceil(Math.min(rows0, visible) / 2));
|
|
3213
|
+
const halfCols = Math.max(1, Math.ceil(columns / 2));
|
|
3214
|
+
return { w: Math.min(naturalSpan.w, halfCols), h: Math.min(naturalSpan.h, halfRows) };
|
|
3215
|
+
})();
|
|
2938
3216
|
tearing = pageId;
|
|
2939
3217
|
const doc = (_a = api.container.ownerDocument) !== null && _a !== void 0 ? _a : document;
|
|
2940
3218
|
const chip = doc.createElement('div');
|
|
2941
3219
|
chip.className = 'axdb-drag-chip axdb-tab-chip';
|
|
2942
3220
|
chip.textContent = plan.label;
|
|
2943
3221
|
doc.body.appendChild(chip);
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
3222
|
+
const moveChip = (cx, cy) => {
|
|
3223
|
+
chip.style.left = `${cx + 6}px`;
|
|
3224
|
+
chip.style.top = `${cy + 6}px`;
|
|
3225
|
+
};
|
|
3226
|
+
moveChip(ev.clientX, ev.clientY);
|
|
3227
|
+
armGlide();
|
|
3228
|
+
api.render();
|
|
2947
3229
|
const layer = htmlLayer();
|
|
3230
|
+
const area = (g) => {
|
|
3231
|
+
const sz = sizeOf(g);
|
|
3232
|
+
return sz.width * sz.height;
|
|
3233
|
+
};
|
|
2948
3234
|
const targets = plan.joinTargets
|
|
2949
3235
|
.map((id) => diagram.getGroup(id))
|
|
2950
|
-
.filter((g) => !!g && g.id !== fromGroupId)
|
|
2951
|
-
|
|
2952
|
-
|
|
3236
|
+
.filter((g) => !!g && g.id !== fromGroupId)
|
|
3237
|
+
.sort((a, b) => area(a) - area(b));
|
|
3238
|
+
// GEOMETRY MUST NOT MOVE WITH THE GHOST. On a fit board the rows squeeze
|
|
3239
|
+
// while the ghost occupies some and relax when it leaves, so a target's
|
|
3240
|
+
// frame changes with the very decision being made about it: a join makes
|
|
3241
|
+
// the ghost leave, the frame grows, and the same pointer now reads as a
|
|
3242
|
+
// split. The target and its frame are therefore anchored when the pointer
|
|
3243
|
+
// enters it and kept until it leaves; the board frame is the one at press.
|
|
3244
|
+
let anchor = null;
|
|
3245
|
+
const inRect = (r, wx, wy) => wx >= r.x && wx <= r.x + r.width && wy >= r.y && wy <= r.y + r.height;
|
|
3246
|
+
const targetAt = (wx, wy) => {
|
|
3247
|
+
var _a;
|
|
3248
|
+
if (anchor && inRect(anchor.frame, wx, wy))
|
|
3249
|
+
return anchor.g;
|
|
3250
|
+
const t = (_a = targets.find((x) => worldInsideGroup(x, wx, wy))) !== null && _a !== void 0 ? _a : null;
|
|
3251
|
+
if (t)
|
|
3252
|
+
leg === null || leg === void 0 ? void 0 : leg.leave(); // the ghost leaves first, so the frame anchored is the relaxed one
|
|
3253
|
+
anchor = t ? { g: t, frame: frameOfGroup(t), stripH: plan.stripHeight(t.id) } : null;
|
|
3254
|
+
return t;
|
|
3255
|
+
};
|
|
3256
|
+
// THE BANDS ARE ON THE SCREEN. Dockview measures its container edges on
|
|
3257
|
+
// the element, and so must we: the camera moves during the drag (the
|
|
3258
|
+
// board glides, the ghost adds rows a bounded scroll answers to), so a
|
|
3259
|
+
// band fixed in world space at the press drifts away from the edge the
|
|
3260
|
+
// user sees. The frame is mapped to client space through the live
|
|
3261
|
+
// camera and clipped to the canvas, so a scrolled board's bands sit at
|
|
3262
|
+
// its VISIBLE edges.
|
|
3263
|
+
const ROOT_TOP = 20;
|
|
3264
|
+
const ROOT_BOTTOM = 20;
|
|
3265
|
+
const ROOT_SIDE = 40;
|
|
3266
|
+
const visibleFrame = () => {
|
|
3267
|
+
const rect = api.container.getBoundingClientRect();
|
|
3268
|
+
const o = toWorld(rect.left, rect.top);
|
|
3269
|
+
const u = toWorld(rect.left + 100, rect.top + 100);
|
|
3270
|
+
const sx = 100 / (u.x - o.x || 100);
|
|
3271
|
+
const sy = 100 / (u.y - o.y || 100);
|
|
3272
|
+
const f = frame();
|
|
3273
|
+
const left = rect.left + (f.x - o.x) * sx;
|
|
3274
|
+
const top = rect.top + (f.y - o.y) * sy;
|
|
3275
|
+
const right = left + f.width * sx;
|
|
3276
|
+
const bottom = top + f.height * sy;
|
|
3277
|
+
if (rect.width <= 0 || rect.height <= 0)
|
|
3278
|
+
return { left, top, right, bottom }; // unlaid-out: nothing to clip to
|
|
3279
|
+
return { left: Math.max(left, rect.left), top: Math.max(top, rect.top), right: Math.min(right, rect.right), bottom: Math.min(bottom, rect.bottom) };
|
|
3280
|
+
};
|
|
3281
|
+
const rootAt = (cx, cy) => {
|
|
3282
|
+
// The bands reach a little OUTSIDE the frame too: a pointer that
|
|
3283
|
+
// overshoots the board's top edge by a few pixels means "the top".
|
|
3284
|
+
const v = visibleFrame();
|
|
3285
|
+
if (cx < v.left - ROOT_SIDE || cx > v.right + ROOT_SIDE || cy < v.top - ROOT_TOP || cy > v.bottom + ROOT_BOTTOM)
|
|
3286
|
+
return null;
|
|
3287
|
+
const rows = Math.max(TEAR_OUT_MIN_ROWS, rowsWithout(plan.arrivingId));
|
|
3288
|
+
const w = dockSpan.w;
|
|
3289
|
+
const h = dockSpan.h;
|
|
3290
|
+
if (cy - v.top <= ROOT_TOP)
|
|
3291
|
+
return { kind: 'root', side: 'top', cell: { x: 0, y: 0, w: columns, h } };
|
|
3292
|
+
if (cx - v.left <= ROOT_SIDE)
|
|
3293
|
+
return { kind: 'root', side: 'left', cell: { x: 0, y: 0, w, h: rows } };
|
|
3294
|
+
if (v.right - cx <= ROOT_SIDE)
|
|
3295
|
+
return { kind: 'root', side: 'right', cell: { x: Math.max(0, columns - w), y: 0, w, h: rows } };
|
|
3296
|
+
if (v.bottom - cy <= ROOT_BOTTOM)
|
|
3297
|
+
return { kind: 'root', side: 'bottom', cell: { x: 0, y: rows, w: columns, h } };
|
|
3298
|
+
return null;
|
|
3299
|
+
};
|
|
3300
|
+
const rowsWithout = (id) => {
|
|
3301
|
+
let r = 0;
|
|
3302
|
+
for (const it of engine.getItems())
|
|
3303
|
+
if (it.id !== id)
|
|
3304
|
+
r = Math.max(r, it.y + it.h);
|
|
3305
|
+
return r;
|
|
3306
|
+
};
|
|
3307
|
+
/** A split being previewed: the target shrunk to its half, to be restored when the pointer leaves. */
|
|
3308
|
+
let split = null;
|
|
3309
|
+
const halves = (target, side) => {
|
|
3310
|
+
// The cell to halve is the target's own — not the half it is already
|
|
3311
|
+
// shrunk to while a split is being previewed.
|
|
3312
|
+
const live = engine.getItem(target.id);
|
|
3313
|
+
const it = split && split.id === target.id ? split.before : live;
|
|
3314
|
+
if (!it || !live)
|
|
3315
|
+
return null; // a group on another board: no cell of ours to halve
|
|
3316
|
+
if (side === 'left' || side === 'right') {
|
|
3317
|
+
if (it.w < 2)
|
|
3318
|
+
return null;
|
|
3319
|
+
const a = Math.ceil(it.w / 2);
|
|
3320
|
+
const b = it.w - a;
|
|
3321
|
+
return side === 'right'
|
|
3322
|
+
? { keep: { x: it.x, y: it.y, w: a, h: it.h }, born: { x: it.x + a, y: it.y, w: b, h: it.h } }
|
|
3323
|
+
: { keep: { x: it.x + b, y: it.y, w: a, h: it.h }, born: { x: it.x, y: it.y, w: b, h: it.h } };
|
|
3324
|
+
}
|
|
3325
|
+
if (it.h < 2 * TEAR_OUT_MIN_ROWS)
|
|
3326
|
+
return null;
|
|
3327
|
+
const a = Math.ceil(it.h / 2);
|
|
3328
|
+
const b = it.h - a;
|
|
3329
|
+
return side === 'bottom'
|
|
3330
|
+
? { keep: { x: it.x, y: it.y, w: it.w, h: a }, born: { x: it.x, y: it.y + a, w: it.w, h: b } }
|
|
3331
|
+
: { keep: { x: it.x, y: it.y + b, w: it.w, h: a }, born: { x: it.x, y: it.y, w: it.w, h: b } };
|
|
3332
|
+
};
|
|
3333
|
+
const zoneAt = (cx, cy, world) => {
|
|
3334
|
+
const target = targetAt(world.x, world.y);
|
|
3335
|
+
const home = !target && worldInsideGroup(from, world.x, world.y);
|
|
3336
|
+
// A strip is the most precise target there is: anyone's wins outright.
|
|
3337
|
+
if (target) {
|
|
3338
|
+
const idx = plan.stripIndex(target.id, cx, cy);
|
|
3339
|
+
if (idx !== null)
|
|
3340
|
+
return { kind: 'strip', target, index: idx };
|
|
3341
|
+
}
|
|
3342
|
+
else if (home) {
|
|
3343
|
+
const idx = plan.stripIndex(fromGroupId, cx, cy);
|
|
3344
|
+
if (idx !== null)
|
|
3345
|
+
return { kind: 'reorder', index: idx };
|
|
3346
|
+
}
|
|
3347
|
+
// The board's own edges beat whatever sits against them — Dockview's
|
|
3348
|
+
// container edges over its groups — so a group at the top of the board
|
|
3349
|
+
// still leaves the top band to the board.
|
|
3350
|
+
const root = rootAt(cx, cy);
|
|
3351
|
+
if (root)
|
|
3352
|
+
return root;
|
|
3353
|
+
if (target) {
|
|
3354
|
+
const f = anchor && anchor.g === target ? anchor.frame : frameOfGroup(target);
|
|
3355
|
+
const stripH = anchor && anchor.g === target ? anchor.stripH : plan.stripHeight(target.id);
|
|
3356
|
+
const bodyH = Math.max(1, f.height - stripH);
|
|
3357
|
+
const rx = Math.min(1, Math.max(0, (world.x - f.x) / Math.max(1, f.width)));
|
|
3358
|
+
const ry = Math.min(1, Math.max(0, (world.y - f.y - stripH) / bodyH));
|
|
3359
|
+
if (rx >= 1 / 3 && rx <= 2 / 3 && ry >= 1 / 3 && ry <= 2 / 3)
|
|
3360
|
+
return { kind: 'join', target };
|
|
3361
|
+
const d = [['left', rx], ['right', 1 - rx], ['top', ry], ['bottom', 1 - ry]];
|
|
3362
|
+
d.sort((p, q) => p[1] - q[1]);
|
|
3363
|
+
const h = halves(target, d[0][0]);
|
|
3364
|
+
return h ? Object.assign({ kind: 'split', target, side: d[0][0] }, h) : { kind: 'join', target };
|
|
3365
|
+
}
|
|
3366
|
+
if (home)
|
|
3367
|
+
return { kind: 'home' };
|
|
3368
|
+
return { kind: 'board' };
|
|
3369
|
+
};
|
|
3370
|
+
const zoneKey = (z) => JSON.stringify(z, (k, v) => (k === 'target' ? v.id : v));
|
|
3371
|
+
// DOCKING PUSHES SECTIONS. A section is a locked tile so that a widget
|
|
3372
|
+
// never pushes it; a group docked against the board's edge is the one
|
|
3373
|
+
// gesture that must — everything below the top band moves down. For the
|
|
3374
|
+
// preview every member group is unlocked; they are relocked when the
|
|
3375
|
+
// pointer leaves the band, and their moved cells are committed with the
|
|
3376
|
+
// dock.
|
|
3377
|
+
let unlockedGroups = [];
|
|
3378
|
+
const unlockGroups = () => {
|
|
3379
|
+
if (unlockedGroups.length > 0)
|
|
3380
|
+
return;
|
|
3381
|
+
for (const it of engine.getItems()) {
|
|
3382
|
+
if (it.id !== plan.arrivingId && it.locked && isGroupMember(it.id)) {
|
|
3383
|
+
it.locked = false;
|
|
3384
|
+
unlockedGroups.push(it.id);
|
|
3385
|
+
}
|
|
3386
|
+
}
|
|
3387
|
+
};
|
|
3388
|
+
const relockGroups = () => {
|
|
3389
|
+
for (const id of unlockedGroups) {
|
|
3390
|
+
const it = engine.getItem(id);
|
|
3391
|
+
if (it)
|
|
3392
|
+
it.locked = true;
|
|
3393
|
+
}
|
|
3394
|
+
unlockedGroups = [];
|
|
3395
|
+
};
|
|
3396
|
+
const groupCommands = (fin, except) => fin.groups.filter((g) => g.id !== except).map((g) => new SetGroupCellCommand(g.id, g.cellBefore, g.cellAfter, g.frameBefore, g.frameAfter));
|
|
3397
|
+
// A TOP DOCK INSERTS ROWS. The engine's push cascade resolves the band's
|
|
3398
|
+
// collisions one tile at a time and scrambles what stood beneath it (the
|
|
3399
|
+
// demo's KPI row came apart, two of its tiles under the chart). VS Code
|
|
3400
|
+
// shoves the whole area down intact, so after the ghost takes the band
|
|
3401
|
+
// every tile that stood at or below it is put back at its own column,
|
|
3402
|
+
// exactly the band's height lower — a translation of a layout without
|
|
3403
|
+
// overlaps has none — and comes back the same way when the pointer leaves.
|
|
3404
|
+
// The layout translated is the one from BEFORE the ghost entered (the
|
|
3405
|
+
// leg's baseline), not the engine's mid-gesture state: the adoption
|
|
3406
|
+
// itself pushes tiles about, and a snapshot taken after it would carry
|
|
3407
|
+
// that cascade into the insert.
|
|
3408
|
+
let inserted = null;
|
|
3409
|
+
const insertRows = (cell, l) => {
|
|
3410
|
+
if (cell.w < columns)
|
|
3411
|
+
return; // a side dock has no whole rows to insert
|
|
3412
|
+
inserted = l.baseline();
|
|
3413
|
+
for (const [id, c] of inserted) {
|
|
3414
|
+
const it = engine.getItem(id);
|
|
3415
|
+
if (!it)
|
|
3416
|
+
continue;
|
|
3417
|
+
it.x = c.x;
|
|
3418
|
+
it.y = c.y >= cell.y ? c.y + cell.h : c.y;
|
|
3419
|
+
}
|
|
3420
|
+
};
|
|
3421
|
+
const undoInsertRows = () => {
|
|
3422
|
+
if (!inserted)
|
|
3423
|
+
return;
|
|
3424
|
+
leg === null || leg === void 0 ? void 0 : leg.leave(); // the ghost frees its rows first
|
|
3425
|
+
for (const [id, c] of inserted) {
|
|
3426
|
+
const it = engine.getItem(id);
|
|
3427
|
+
if (it) {
|
|
3428
|
+
it.x = c.x;
|
|
3429
|
+
it.y = c.y;
|
|
3430
|
+
}
|
|
3431
|
+
}
|
|
3432
|
+
inserted = null;
|
|
3433
|
+
project();
|
|
3434
|
+
};
|
|
2953
3435
|
let joinEl = null;
|
|
2954
|
-
const
|
|
3436
|
+
const showOverlay = (r) => {
|
|
2955
3437
|
if (!layer)
|
|
2956
3438
|
return;
|
|
2957
3439
|
if (!joinEl) {
|
|
@@ -2959,37 +3441,115 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2959
3441
|
joinEl.className = 'axdb-join';
|
|
2960
3442
|
layer.prepend(joinEl);
|
|
2961
3443
|
}
|
|
2962
|
-
|
|
2963
|
-
joinEl.style.
|
|
2964
|
-
joinEl.style.
|
|
2965
|
-
joinEl.style.
|
|
2966
|
-
joinEl.style.height = `${sz.height}px`;
|
|
3444
|
+
joinEl.style.left = `${r.x}px`;
|
|
3445
|
+
joinEl.style.top = `${r.y}px`;
|
|
3446
|
+
joinEl.style.width = `${r.width}px`;
|
|
3447
|
+
joinEl.style.height = `${r.height}px`;
|
|
2967
3448
|
};
|
|
2968
|
-
const
|
|
3449
|
+
const hideOverlay = () => {
|
|
2969
3450
|
joinEl === null || joinEl === void 0 ? void 0 : joinEl.remove();
|
|
2970
3451
|
joinEl = null;
|
|
2971
|
-
plan.markDrop(null, null);
|
|
2972
3452
|
};
|
|
2973
|
-
const
|
|
2974
|
-
if (
|
|
3453
|
+
const undoSplitPreview = () => {
|
|
3454
|
+
if (!split)
|
|
2975
3455
|
return;
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
3456
|
+
const it = engine.getItem(split.id);
|
|
3457
|
+
leg === null || leg === void 0 ? void 0 : leg.leave(); // the born half must be free before the target grows back into it
|
|
3458
|
+
if (it) {
|
|
3459
|
+
if (it.x !== split.before.x || it.y !== split.before.y)
|
|
3460
|
+
engine.moveCheck(split.id, split.before.x, split.before.y, { gate: false });
|
|
3461
|
+
if (it.w !== split.before.w || it.h !== split.before.h)
|
|
3462
|
+
engine.resizeCheck(split.id, split.before.w, split.before.h);
|
|
3463
|
+
it.locked = true;
|
|
2984
3464
|
}
|
|
3465
|
+
split = null;
|
|
3466
|
+
project();
|
|
2985
3467
|
};
|
|
2986
|
-
const
|
|
2987
|
-
|
|
2988
|
-
|
|
3468
|
+
const previewSplit = (z, world) => {
|
|
3469
|
+
const it = engine.getItem(z.target.id);
|
|
3470
|
+
const l = ensureLeg(world);
|
|
3471
|
+
if (!it || !l)
|
|
3472
|
+
return false;
|
|
3473
|
+
split = { id: z.target.id, before: { x: it.x, y: it.y, w: it.w, h: it.h }, frameBefore: frameOfGroup(z.target), keep: z.keep };
|
|
3474
|
+
it.locked = false; // its own gesture for the moment: it may shrink and shift
|
|
3475
|
+
if (it.w !== z.keep.w || it.h !== z.keep.h)
|
|
3476
|
+
engine.resizeCheck(z.target.id, z.keep.w, z.keep.h);
|
|
3477
|
+
const now = engine.getItem(z.target.id);
|
|
3478
|
+
if (now && (now.x !== z.keep.x || now.y !== z.keep.y))
|
|
3479
|
+
engine.moveCheck(z.target.id, z.keep.x, z.keep.y, { gate: false });
|
|
3480
|
+
const ok = l.place(z.born);
|
|
3481
|
+
project();
|
|
3482
|
+
placeholder === null || placeholder === void 0 ? void 0 : placeholder.remove(); // the accent overlay says which half; the grey placeholder under it is noise
|
|
3483
|
+
placeholder = null;
|
|
3484
|
+
return ok;
|
|
3485
|
+
};
|
|
3486
|
+
let zone = { kind: 'board' };
|
|
3487
|
+
let key = zoneKey(zone);
|
|
3488
|
+
const applyZone = (z, world) => {
|
|
3489
|
+
var _a;
|
|
3490
|
+
const k = zoneKey(z);
|
|
3491
|
+
const same = k === key;
|
|
3492
|
+
key = k;
|
|
3493
|
+
zone = z;
|
|
3494
|
+
if (same) {
|
|
3495
|
+
if (z.kind === 'board')
|
|
3496
|
+
(_a = ensureLeg(world)) === null || _a === void 0 ? void 0 : _a.move(world);
|
|
3497
|
+
return;
|
|
3498
|
+
}
|
|
3499
|
+
undoSplitPreview();
|
|
3500
|
+
undoInsertRows();
|
|
3501
|
+
if (z.kind !== 'root')
|
|
3502
|
+
relockGroups();
|
|
3503
|
+
plan.markDrop(null, null);
|
|
3504
|
+
hideOverlay();
|
|
3505
|
+
switch (z.kind) {
|
|
3506
|
+
case 'strip':
|
|
3507
|
+
leg === null || leg === void 0 ? void 0 : leg.leave();
|
|
3508
|
+
showOverlay(frameOfGroup(z.target));
|
|
3509
|
+
plan.markDrop(z.target.id, z.index);
|
|
3510
|
+
break;
|
|
3511
|
+
case 'join':
|
|
3512
|
+
leg === null || leg === void 0 ? void 0 : leg.leave();
|
|
3513
|
+
showOverlay(frameOfGroup(z.target));
|
|
3514
|
+
break;
|
|
3515
|
+
case 'split': {
|
|
3516
|
+
if (!previewSplit(z, world)) {
|
|
3517
|
+
zone = { kind: 'join', target: z.target };
|
|
3518
|
+
key = zoneKey(zone);
|
|
3519
|
+
leg === null || leg === void 0 ? void 0 : leg.leave();
|
|
3520
|
+
showOverlay(frameOfGroup(z.target));
|
|
3521
|
+
break;
|
|
3522
|
+
}
|
|
3523
|
+
showOverlay(cellToRect(z.born, frame(), geom(), rows()));
|
|
3524
|
+
break;
|
|
3525
|
+
}
|
|
3526
|
+
case 'root': {
|
|
3527
|
+
const l = ensureLeg(world);
|
|
3528
|
+
if (l) {
|
|
3529
|
+
unlockGroups();
|
|
3530
|
+
l.place(z.cell);
|
|
3531
|
+
insertRows(z.cell, l);
|
|
3532
|
+
project();
|
|
3533
|
+
placeholder === null || placeholder === void 0 ? void 0 : placeholder.remove();
|
|
3534
|
+
placeholder = null;
|
|
3535
|
+
showOverlay(cellToRect(z.cell, frame(), geom(), rows()));
|
|
3536
|
+
}
|
|
3537
|
+
break;
|
|
3538
|
+
}
|
|
3539
|
+
case 'reorder':
|
|
3540
|
+
leg === null || leg === void 0 ? void 0 : leg.leave();
|
|
3541
|
+
plan.markDrop(fromGroupId, z.index);
|
|
3542
|
+
break;
|
|
3543
|
+
case 'home':
|
|
3544
|
+
leg === null || leg === void 0 ? void 0 : leg.leave();
|
|
3545
|
+
break;
|
|
3546
|
+
case 'board': {
|
|
3547
|
+
const l = ensureLeg(world);
|
|
3548
|
+
l === null || l === void 0 ? void 0 : l.enter(world);
|
|
3549
|
+
break;
|
|
3550
|
+
}
|
|
3551
|
+
}
|
|
2989
3552
|
};
|
|
2990
|
-
moveChip(ev.clientX, ev.clientY);
|
|
2991
|
-
armGlide();
|
|
2992
|
-
api.render();
|
|
2993
3553
|
let last = { x: ev.clientX, y: ev.clientY };
|
|
2994
3554
|
const detach = () => {
|
|
2995
3555
|
window.removeEventListener('pointermove', onMove, true);
|
|
@@ -3003,68 +3563,98 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
3003
3563
|
last = { x: e.clientX, y: e.clientY };
|
|
3004
3564
|
moveChip(e.clientX, e.clientY);
|
|
3005
3565
|
const world = toWorld(e.clientX, e.clientY);
|
|
3006
|
-
const
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
//
|
|
3010
|
-
|
|
3011
|
-
chip.classList.toggle('axdb-out', home || (!target && !worldInsideBoardGrace(world.x, world.y)));
|
|
3012
|
-
if (target)
|
|
3013
|
-
plan.markDrop(target.id, plan.dropIndex(target.id, e.clientX, e.clientY));
|
|
3014
|
-
else if (!home)
|
|
3015
|
-
leg.move(world);
|
|
3566
|
+
const z = zoneAt(e.clientX, e.clientY, world);
|
|
3567
|
+
applyZone(z, world);
|
|
3568
|
+
// Dimmed = a release here does nothing: home, off the board, or a board
|
|
3569
|
+
// that refused the ghost (bounded and full).
|
|
3570
|
+
chip.classList.toggle('axdb-out', zone.kind === 'home' || (zone.kind === 'root' && !leg) || (zone.kind === 'board' && (!leg || !worldInsideBoardGrace(world.x, world.y))));
|
|
3016
3571
|
api.render();
|
|
3017
3572
|
};
|
|
3573
|
+
const done = (changed, kind) => {
|
|
3574
|
+
var _a;
|
|
3575
|
+
disarmGlideSoon();
|
|
3576
|
+
enforceBoardHeight();
|
|
3577
|
+
persistLayouts();
|
|
3578
|
+
api.renderNow();
|
|
3579
|
+
(_a = options.onGesture) === null || _a === void 0 ? void 0 : _a.call(options, { type: kind, kind: 'move', nodeId: pageId, changed });
|
|
3580
|
+
};
|
|
3018
3581
|
const finish = (commit) => {
|
|
3019
|
-
var _a, _b
|
|
3582
|
+
var _a, _b;
|
|
3020
3583
|
detach();
|
|
3021
3584
|
chip.remove();
|
|
3022
|
-
|
|
3585
|
+
hideOverlay();
|
|
3586
|
+
plan.markDrop(null, null);
|
|
3023
3587
|
tearing = null;
|
|
3024
3588
|
if (disposed)
|
|
3025
3589
|
return;
|
|
3026
3590
|
const world = toWorld(last.x, last.y);
|
|
3027
|
-
const
|
|
3028
|
-
|
|
3029
|
-
|
|
3591
|
+
const z = commit ? zoneAt(last.x, last.y, world) : { kind: 'home' };
|
|
3592
|
+
if (z.kind !== 'root')
|
|
3593
|
+
undoInsertRows();
|
|
3594
|
+
if (!commit || z.kind === 'home' || (z.kind === 'root' && !ensureLeg(world)) || (z.kind === 'board' && (!ensureLeg(world) || !worldInsideBoardGrace(world.x, world.y)))) {
|
|
3595
|
+
undoSplitPreview();
|
|
3596
|
+
leg === null || leg === void 0 ? void 0 : leg.abort();
|
|
3597
|
+
relockGroups();
|
|
3598
|
+
done(false, 'cancel');
|
|
3599
|
+
return;
|
|
3600
|
+
}
|
|
3601
|
+
if (z.kind === 'reorder') {
|
|
3602
|
+
undoSplitPreview();
|
|
3603
|
+
leg === null || leg === void 0 ? void 0 : leg.abort();
|
|
3604
|
+
const cmds = plan.reorder(z.index);
|
|
3605
|
+
const changed = cmds.length > 0 ? execute('Reorder tab', cmds) : false;
|
|
3606
|
+
done(changed, changed ? 'commit' : 'cancel');
|
|
3607
|
+
return;
|
|
3608
|
+
}
|
|
3609
|
+
if (z.kind === 'strip' || z.kind === 'join') {
|
|
3030
3610
|
// JOIN: no cell on this board — the leg is abandoned (its displaced
|
|
3031
3611
|
// tiles are already home) and the page becomes the target's tab.
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
const
|
|
3036
|
-
|
|
3037
|
-
disarmGlideSoon();
|
|
3038
|
-
enforceBoardHeight();
|
|
3039
|
-
persistLayouts();
|
|
3040
|
-
api.renderNow();
|
|
3041
|
-
(_a = options.onGesture) === null || _a === void 0 ? void 0 : _a.call(options, { type: 'commit', kind: 'move', nodeId: pageId, changed });
|
|
3612
|
+
undoSplitPreview();
|
|
3613
|
+
const index = z.kind === 'strip' ? z.index : plan.dropIndex(z.target.id, last.x, last.y);
|
|
3614
|
+
leg === null || leg === void 0 ? void 0 : leg.abort();
|
|
3615
|
+
const planned = plan.join(z.target.id, index, group.id);
|
|
3616
|
+
done(execute('Move tab', [...planned.move, ...planned.collapse]), 'commit');
|
|
3042
3617
|
return;
|
|
3043
3618
|
}
|
|
3044
|
-
if (
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3619
|
+
if (z.kind === 'split') {
|
|
3620
|
+
// SPLIT: the target keeps one half of its cell, the page's new group the
|
|
3621
|
+
// other. The preview already holds both in the engine; commit them.
|
|
3622
|
+
if (!split || split.id !== z.target.id || zoneKey(zone) !== zoneKey(z))
|
|
3623
|
+
applyZone(z, world);
|
|
3624
|
+
const fin = (_a = leg === null || leg === void 0 ? void 0 : leg.finalize()) !== null && _a !== void 0 ? _a : null;
|
|
3625
|
+
const it = engine.getItem(z.target.id);
|
|
3626
|
+
const before = split;
|
|
3627
|
+
if (it)
|
|
3628
|
+
it.locked = true;
|
|
3629
|
+
split = null;
|
|
3630
|
+
if (!fin || !before || !it) {
|
|
3631
|
+
leg === null || leg === void 0 ? void 0 : leg.abort();
|
|
3632
|
+
done(false, 'cancel');
|
|
3633
|
+
return;
|
|
3634
|
+
}
|
|
3635
|
+
const keep = { x: it.x, y: it.y, w: it.w, h: it.h };
|
|
3636
|
+
const planned = plan.commands(fin.cell, fin.rect, group.id);
|
|
3637
|
+
const changed = execute('Split group', [
|
|
3638
|
+
...fin.commands,
|
|
3639
|
+
...groupCommands(fin, z.target.id),
|
|
3640
|
+
new SetGroupCellCommand(z.target.id, before.before, keep, before.frameBefore, cellToRect(keep, frame(), geom(), rows())),
|
|
3641
|
+
...planned.move,
|
|
3642
|
+
...planned.collapse,
|
|
3643
|
+
]);
|
|
3644
|
+
done(changed, 'commit');
|
|
3049
3645
|
return;
|
|
3050
3646
|
}
|
|
3051
|
-
|
|
3647
|
+
// ROOT DOCK or a free cell on the board: the leg sits where it will land.
|
|
3648
|
+
if (zoneKey(zone) !== zoneKey(z))
|
|
3649
|
+
applyZone(z, world);
|
|
3650
|
+
const fin = (_b = leg === null || leg === void 0 ? void 0 : leg.finalize()) !== null && _b !== void 0 ? _b : null;
|
|
3651
|
+
relockGroups();
|
|
3052
3652
|
if (!fin) {
|
|
3053
|
-
|
|
3054
|
-
api.renderNow();
|
|
3653
|
+
done(false, 'cancel');
|
|
3055
3654
|
return;
|
|
3056
3655
|
}
|
|
3057
3656
|
const planned = plan.commands(fin.cell, fin.rect, group.id);
|
|
3058
|
-
|
|
3059
|
-
// goes too, and the tiles around its slab settle the way a removal
|
|
3060
|
-
// settles them (VS Code closes a group whose last editor leaves).
|
|
3061
|
-
const collapse = planned.collapse.length > 0 ? [...planRemovalOf(fromGroupId), ...planned.collapse] : [];
|
|
3062
|
-
const changed = execute('Move tab out', [...fin.commands, ...planned.move, ...collapse]);
|
|
3063
|
-
disarmGlideSoon();
|
|
3064
|
-
enforceBoardHeight();
|
|
3065
|
-
persistLayouts();
|
|
3066
|
-
api.renderNow();
|
|
3067
|
-
(_c = options.onGesture) === null || _c === void 0 ? void 0 : _c.call(options, { type: 'commit', kind: 'move', nodeId: pageId, changed });
|
|
3657
|
+
done(execute(z.kind === 'root' ? 'Dock tab' : 'Move tab out', [...fin.commands, ...groupCommands(fin), ...planned.move, ...planned.collapse]), 'commit');
|
|
3068
3658
|
};
|
|
3069
3659
|
const onUp = () => finish(true);
|
|
3070
3660
|
const onCancel = () => finish(false);
|
|
@@ -3107,6 +3697,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
3107
3697
|
spans: { w: Math.max(1, spec.w), h: Math.max(1, spec.h) },
|
|
3108
3698
|
removedFromBoard: true,
|
|
3109
3699
|
leg: null,
|
|
3700
|
+
strip: null,
|
|
3110
3701
|
lastWorld: null,
|
|
3111
3702
|
lastScreen: null,
|
|
3112
3703
|
hostEl: null,
|
|
@@ -3220,7 +3811,15 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
3220
3811
|
return false;
|
|
3221
3812
|
engine.beginGesture();
|
|
3222
3813
|
const snap = snapshotAll();
|
|
3223
|
-
|
|
3814
|
+
// A section is a LOCKED tile; for ITS OWN move it is the one moving.
|
|
3815
|
+
const self = engine.getItem(id);
|
|
3816
|
+
const wasLocked = !!(self === null || self === void 0 ? void 0 : self.locked);
|
|
3817
|
+
if (self && isGroupMember(id))
|
|
3818
|
+
self.locked = false;
|
|
3819
|
+
const ok = op();
|
|
3820
|
+
if (self && isGroupMember(id))
|
|
3821
|
+
self.locked = wasLocked;
|
|
3822
|
+
if (!ok) {
|
|
3224
3823
|
engine.endGesture();
|
|
3225
3824
|
return false;
|
|
3226
3825
|
}
|