@grafloria/element 0.4.31 → 0.4.32

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.
@@ -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
- (_a = forwardSlab.slabCancel) === null || _a === void 0 ? void 0 : _a.call(forwardSlab);
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 = ((_c = (_b = diagram.getNode(id)) === null || _b === void 0 ? void 0 : _b.state) === null || _c === void 0 ? void 0 : _c.locked) === true;
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
- (_d = options.onGesture) === null || _d === void 0 ? void 0 : _d.call(options, { type: 'cancel', kind: g.kind, nodeId: g.id, changed: false });
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
- (_a = hostOf(g.id)) === null || _a === void 0 ? void 0 : _a.classList.remove('axdb-out');
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
- (_b = hostOf(g.id)) === null || _b === void 0 ? void 0 : _b.classList.add('axdb-out');
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
- (_c = hostOf(g.id)) === null || _c === void 0 ? void 0 : _c.classList.remove('axdb-out');
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 = (_e = (_d = engine.getItem(g.id)) === null || _d === void 0 ? void 0 : _d.w) !== null && _e !== void 0 ? _e : g.spans.w;
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
- (_f = hostOf(g.id)) === null || _f === void 0 ? void 0 : _f.classList.add('axdb-out');
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 = (_h = (_g = parent.memberCell(group.id)) === null || _g === void 0 ? void 0 : _g.h) !== null && _h !== void 0 ? _h : maxRows;
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, (_j = g.node.size.depth) !== null && _j !== void 0 ? _j : 0);
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: (_a = geomBefore === null || geomBefore === void 0 ? void 0 : geomBefore.pos) !== null && _a !== void 0 ? _a : { x: fin.rect.x, y: fin.rect.y },
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: (_b = geomBefore === null || geomBefore === void 0 ? void 0 : geomBefore.size) !== null && _b !== void 0 ? _b : { width: fin.rect.width, height: fin.rect.height },
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
- execute('Move widget', crossing);
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
- (_c = options.onGesture) === null || _c === void 0 ? void 0 : _c.call(options, { type: 'commit', kind: g.kind, nodeId: g.id, changed: true });
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, (_d = snap.size.depth) !== null && _d !== void 0 ? _d : 0);
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 ((_e = options.onRemoveRequest) === null || _e === void 0 ? void 0 : _e.call(options, g.id, displaced));
2096
- (_f = options.onGesture) === null || _f === void 0 ? void 0 : _f.call(options, { type: 'remove', kind: g.kind, nodeId: g.id, changed: true });
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);
@@ -2388,6 +2540,56 @@ export function bindDashboardGrid(api, group, options = {}) {
2388
2540
  beginSlabResize(id, edges, ev);
2389
2541
  return (slabGesture === null || slabGesture === void 0 ? void 0 : slabGesture.id) === id;
2390
2542
  },
2543
+ beginSlabMove: (id, ev) => {
2544
+ if (isStatic)
2545
+ return false;
2546
+ beginSlabMove(id, ev);
2547
+ return (slabGesture === null || slabGesture === void 0 ? void 0 : slabGesture.id) === id;
2548
+ },
2549
+ dragMember: (id, ev) => {
2550
+ var _a;
2551
+ if (isStatic || disposed || !engine.getItem(id) || gesture || slabGesture)
2552
+ return false;
2553
+ const toTool = (e) => {
2554
+ var _a;
2555
+ const rect = api.container.getBoundingClientRect();
2556
+ 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 };
2557
+ return { world, screen: { x: e.clientX - rect.left, y: e.clientY - rect.top }, source: e };
2558
+ };
2559
+ beginSlabMove(id, toTool(ev));
2560
+ if (((_a = currentSlab()) === null || _a === void 0 ? void 0 : _a.id) !== id)
2561
+ return false;
2562
+ const detachAll = () => {
2563
+ window.removeEventListener('pointermove', onMove, true);
2564
+ window.removeEventListener('pointerup', onUp, true);
2565
+ window.removeEventListener('pointercancel', onCancel, true);
2566
+ window.removeEventListener('keydown', onKey, true);
2567
+ };
2568
+ const onMove = (e) => {
2569
+ var _a;
2570
+ if (disposed || ((_a = currentSlab()) === null || _a === void 0 ? void 0 : _a.id) !== id)
2571
+ return detachAll();
2572
+ slabMove(toTool(e));
2573
+ api.render();
2574
+ };
2575
+ const onUp = () => {
2576
+ detachAll();
2577
+ slabUp();
2578
+ };
2579
+ const onCancel = () => {
2580
+ detachAll();
2581
+ slabCancel();
2582
+ };
2583
+ const onKey = (e) => {
2584
+ if (e.key === 'Escape')
2585
+ onCancel();
2586
+ };
2587
+ window.addEventListener('pointermove', onMove, true);
2588
+ window.addEventListener('pointerup', onUp, true);
2589
+ window.addEventListener('pointercancel', onCancel, true);
2590
+ window.addEventListener('keydown', onKey, true);
2591
+ return true;
2592
+ },
2391
2593
  slabMove: (ev) => slabMove(ev),
2392
2594
  slabUp: () => slabUp(),
2393
2595
  slabCancel: () => slabCancel(),
@@ -2494,7 +2696,7 @@ export function bindDashboardGrid(api, group, options = {}) {
2494
2696
  return insideMemberGroupFrame(ev.world.x, ev.world.y) || worldInsideBoard(ev.world.x, ev.world.y);
2495
2697
  },
2496
2698
  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;
2699
+ 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
2700
  if (gesture)
2499
2701
  return; // mid-palette
2500
2702
  const target = ((_b = (_a = ev.source) === null || _a === void 0 ? void 0 : _a.target) !== null && _b !== void 0 ? _b : null);
@@ -2532,6 +2734,10 @@ export function bindDashboardGrid(api, group, options = {}) {
2532
2734
  : slabEdgesNear(grp, ev.world.x, ev.world.y);
2533
2735
  if (anyEdge(edges))
2534
2736
  beginSlabResize(slabId, edges, ev);
2737
+ // The caption band is the section's handle: pressed and travelled,
2738
+ // it moves the whole section (its inner empty space only selects).
2739
+ else if (ownCaption)
2740
+ beginSlabMove(slabId, ev);
2535
2741
  return;
2536
2742
  }
2537
2743
  // OUR OWN empty band, and we are a section of a parent board: the
@@ -2548,12 +2754,14 @@ export function bindDashboardGrid(api, group, options = {}) {
2548
2754
  : slabEdgesNear(group, ev.world.x, ev.world.y);
2549
2755
  if (anyEdge(edges) && parent.beginSlabResize(group.id, edges, ev))
2550
2756
  forwardSlab = parent;
2757
+ else if (!anyEdge(edges) && captionId === group.id && ((_r = parent.beginSlabMove) === null || _r === void 0 ? void 0 : _r.call(parent, group.id, ev)))
2758
+ forwardSlab = parent;
2551
2759
  }
2552
2760
  return;
2553
2761
  }
2554
2762
  // The board's own empty area: a void click. Nothing to drag, and the
2555
2763
  // selection clears exactly as a click outside any board would.
2556
- (_s = (_r = diagram).clearSelection) === null || _s === void 0 ? void 0 : _s.call(_r);
2764
+ (_t = (_s = diagram).clearSelection) === null || _t === void 0 ? void 0 : _t.call(_s);
2557
2765
  selectWidget(undefined);
2558
2766
  api.render();
2559
2767
  return;
@@ -2562,17 +2770,17 @@ export function bindDashboardGrid(api, group, options = {}) {
2562
2770
  if (!node)
2563
2771
  return;
2564
2772
  selectWidget(node.id); // a press selects, whether or not it starts a gesture
2565
- if (((_t = node.state) === null || _t === void 0 ? void 0 : _t.locked) === true)
2773
+ if (((_u = node.state) === null || _u === void 0 ? void 0 : _u.locked) === true)
2566
2774
  return; // pinned: refuse; click still focuses
2567
2775
  if (isStatic)
2568
2776
  return; // a static board: claimed and deadened, click still focuses
2569
2777
  // Which edges did the press take? The corner handle names its own (s+e,
2570
2778
  // or s+w on RTL); a bare press within EDGE_GRIP of the tile's border
2571
2779
  // takes that border; anywhere else is a move. A grip press is a move.
2572
- const onHandle = !!((_u = target === null || target === void 0 ? void 0 : target.closest) === null || _u === void 0 ? void 0 : _u.call(target, '.axdb-rs'));
2780
+ const onHandle = !!((_v = target === null || target === void 0 ? void 0 : target.closest) === null || _v === void 0 ? void 0 : _v.call(target, '.axdb-rs'));
2573
2781
  const hostEl = hostOf(node.id);
2574
- const resizable = ((_v = node.getMetadata) === null || _v === void 0 ? void 0 : _v.call(node, 'widgetResizable')) !== false;
2575
- const movable = ((_w = node.getMetadata) === null || _w === void 0 ? void 0 : _w.call(node, 'widgetMovable')) !== false;
2782
+ const resizable = ((_w = node.getMetadata) === null || _w === void 0 ? void 0 : _w.call(node, 'widgetResizable')) !== false;
2783
+ const movable = ((_x = node.getMetadata) === null || _x === void 0 ? void 0 : _x.call(node, 'widgetMovable')) !== false;
2576
2784
  // `ev.screen` is ELEMENT-LOCAL px; host rects are in client px. Compare
2577
2785
  // like with like — the source event's clientX/Y when there is one, else
2578
2786
  // the container's origin plus the local offset.
@@ -2620,9 +2828,10 @@ export function bindDashboardGrid(api, group, options = {}) {
2620
2828
  startSize: { width: node.size.width, height: node.size.height },
2621
2829
  startPos: { x: node.position.x, y: node.position.y },
2622
2830
  edges,
2623
- spans: { w: (_x = it === null || it === void 0 ? void 0 : it.w) !== null && _x !== void 0 ? _x : 1, h: (_y = it === null || it === void 0 ? void 0 : it.h) !== null && _y !== void 0 ? _y : 1 },
2831
+ 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
2832
  removedFromBoard: false,
2625
2833
  leg: null,
2834
+ strip: null,
2626
2835
  lastWorld: null,
2627
2836
  lastScreen: null,
2628
2837
  hostEl: null,
@@ -2952,8 +3161,8 @@ export function bindDashboardGrid(api, group, options = {}) {
2952
3161
  let over = null;
2953
3162
  let joinEl = null;
2954
3163
  const showJoin = (g) => {
2955
- if (!layer)
2956
- return;
3164
+ if (!layer || g.id === fromGroupId)
3165
+ return; // reordering along its own strip: the mark is enough
2957
3166
  if (!joinEl) {
2958
3167
  joinEl = doc.createElement('div');
2959
3168
  joinEl.className = 'axdb-join';
@@ -2997,6 +3206,7 @@ export function bindDashboardGrid(api, group, options = {}) {
2997
3206
  window.removeEventListener('pointercancel', onCancel, true);
2998
3207
  window.removeEventListener('keydown', onKey, true);
2999
3208
  };
3209
+ let homeIndex = null;
3000
3210
  const onMove = (e) => {
3001
3211
  if (disposed)
3002
3212
  return detach();
@@ -3004,19 +3214,22 @@ export function bindDashboardGrid(api, group, options = {}) {
3004
3214
  moveChip(e.clientX, e.clientY);
3005
3215
  const world = toWorld(e.clientX, e.clientY);
3006
3216
  const home = worldInsideGroup(from, world.x, world.y);
3217
+ // Over its OWN strip the tab reorders: the strip marks the slot and the
3218
+ // tile leaves the board. Over its own body it goes home (a cancel).
3219
+ homeIndex = home ? plan.stripIndex(fromGroupId, e.clientX, e.clientY) : null;
3007
3220
  const target = home ? null : targetAt(world.x, world.y);
3008
- setOver(target, world);
3009
- // Over its own container it goes home: a tab dragged around its own
3010
- // strip must not tear itself out.
3011
- chip.classList.toggle('axdb-out', home || (!target && !worldInsideBoardGrace(world.x, world.y)));
3221
+ setOver(target !== null && target !== void 0 ? target : (homeIndex !== null ? from : null), world);
3222
+ chip.classList.toggle('axdb-out', (home && homeIndex === null) || (!target && !home && !worldInsideBoardGrace(world.x, world.y)));
3012
3223
  if (target)
3013
3224
  plan.markDrop(target.id, plan.dropIndex(target.id, e.clientX, e.clientY));
3225
+ else if (homeIndex !== null)
3226
+ plan.markDrop(fromGroupId, homeIndex);
3014
3227
  else if (!home)
3015
3228
  leg.move(world);
3016
3229
  api.render();
3017
3230
  };
3018
3231
  const finish = (commit) => {
3019
- var _a, _b, _c;
3232
+ var _a, _b, _c, _d;
3020
3233
  detach();
3021
3234
  chip.remove();
3022
3235
  hideJoin();
@@ -3025,6 +3238,16 @@ export function bindDashboardGrid(api, group, options = {}) {
3025
3238
  return;
3026
3239
  const world = toWorld(last.x, last.y);
3027
3240
  const home = worldInsideGroup(from, world.x, world.y);
3241
+ if (commit && home && homeIndex !== null) {
3242
+ // REORDER along its own strip.
3243
+ leg.abort();
3244
+ const cmds = plan.reorder(homeIndex);
3245
+ const changed = cmds.length > 0 ? execute('Reorder tab', cmds) : false;
3246
+ disarmGlideSoon();
3247
+ api.renderNow();
3248
+ (_a = options.onGesture) === null || _a === void 0 ? void 0 : _a.call(options, { type: changed ? 'commit' : 'cancel', kind: 'move', nodeId: pageId, changed });
3249
+ return;
3250
+ }
3028
3251
  const target = commit && !home ? targetAt(world.x, world.y) : null;
3029
3252
  if (target) {
3030
3253
  // JOIN: no cell on this board — the leg is abandoned (its displaced
@@ -3032,20 +3255,19 @@ export function bindDashboardGrid(api, group, options = {}) {
3032
3255
  const index = plan.dropIndex(target.id, last.x, last.y);
3033
3256
  leg.abort();
3034
3257
  const planned = plan.join(target.id, index, group.id);
3035
- const collapse = planned.collapse.length > 0 ? [...planRemovalOf(fromGroupId), ...planned.collapse] : [];
3036
- const changed = execute('Move tab', [...planned.move, ...collapse]);
3258
+ const changed = execute('Move tab', [...planned.move, ...planned.collapse]);
3037
3259
  disarmGlideSoon();
3038
3260
  enforceBoardHeight();
3039
3261
  persistLayouts();
3040
3262
  api.renderNow();
3041
- (_a = options.onGesture) === null || _a === void 0 ? void 0 : _a.call(options, { type: 'commit', kind: 'move', nodeId: pageId, changed });
3263
+ (_b = options.onGesture) === null || _b === void 0 ? void 0 : _b.call(options, { type: 'commit', kind: 'move', nodeId: pageId, changed });
3042
3264
  return;
3043
3265
  }
3044
3266
  if (!commit || home || !worldInsideBoardGrace(world.x, world.y)) {
3045
3267
  leg.abort();
3046
3268
  disarmGlideSoon();
3047
3269
  api.renderNow();
3048
- (_b = options.onGesture) === null || _b === void 0 ? void 0 : _b.call(options, { type: 'cancel', kind: 'move', nodeId: pageId, changed: false });
3270
+ (_c = options.onGesture) === null || _c === void 0 ? void 0 : _c.call(options, { type: 'cancel', kind: 'move', nodeId: pageId, changed: false });
3049
3271
  return;
3050
3272
  }
3051
3273
  const fin = leg.finalize();
@@ -3056,15 +3278,13 @@ export function bindDashboardGrid(api, group, options = {}) {
3056
3278
  }
3057
3279
  const planned = plan.commands(fin.cell, fin.rect, group.id);
3058
3280
  // The container the page left may have been holding its last page: it
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]);
3281
+ // goes too (the plan carries the removal's settle).
3282
+ const changed = execute('Move tab out', [...fin.commands, ...planned.move, ...planned.collapse]);
3063
3283
  disarmGlideSoon();
3064
3284
  enforceBoardHeight();
3065
3285
  persistLayouts();
3066
3286
  api.renderNow();
3067
- (_c = options.onGesture) === null || _c === void 0 ? void 0 : _c.call(options, { type: 'commit', kind: 'move', nodeId: pageId, changed });
3287
+ (_d = options.onGesture) === null || _d === void 0 ? void 0 : _d.call(options, { type: 'commit', kind: 'move', nodeId: pageId, changed });
3068
3288
  };
3069
3289
  const onUp = () => finish(true);
3070
3290
  const onCancel = () => finish(false);
@@ -3107,6 +3327,7 @@ export function bindDashboardGrid(api, group, options = {}) {
3107
3327
  spans: { w: Math.max(1, spec.w), h: Math.max(1, spec.h) },
3108
3328
  removedFromBoard: true,
3109
3329
  leg: null,
3330
+ strip: null,
3110
3331
  lastWorld: null,
3111
3332
  lastScreen: null,
3112
3333
  hostEl: null,
@@ -3220,7 +3441,15 @@ export function bindDashboardGrid(api, group, options = {}) {
3220
3441
  return false;
3221
3442
  engine.beginGesture();
3222
3443
  const snap = snapshotAll();
3223
- if (!op()) {
3444
+ // A section is a LOCKED tile; for ITS OWN move it is the one moving.
3445
+ const self = engine.getItem(id);
3446
+ const wasLocked = !!(self === null || self === void 0 ? void 0 : self.locked);
3447
+ if (self && isGroupMember(id))
3448
+ self.locked = false;
3449
+ const ok = op();
3450
+ if (self && isGroupMember(id))
3451
+ self.locked = wasLocked;
3452
+ if (!ok) {
3224
3453
  engine.endGesture();
3225
3454
  return false;
3226
3455
  }
@@ -1254,6 +1254,8 @@ export function bindDashboardSplit(api, group, options = {}) {
1254
1254
  // A split board has no cells to drop a torn-out page into: it refuses,
1255
1255
  // and the press stays a plain tab click.
1256
1256
  tearOutMember: (_pageId, _fromGroupId, _ev, _plan) => false,
1257
+ // …and it has no cells to move a section across: a strip press stays a selection.
1258
+ dragMember: () => false,
1257
1259
  containsWorld: (x, y) => worldInsideBoard(x, y),
1258
1260
  containsWorldExtended: (x, y) => worldInsideBoard(x, y),
1259
1261
  frameArea: () => {
@@ -39,7 +39,7 @@ export declare function tabStripReserve(o: TabsOptions | undefined, pageCount: n
39
39
  * rebuild — and so a `click` listener survives between switches.
40
40
  */
41
41
  export declare function tabStripKey(pages: TabPage[], activeId: string, o: TabsOptions | undefined, rtl: boolean): string;
42
- export declare function paintTabStrip(strip: HTMLElement, pages: TabPage[], activeId: string, o: TabsOptions | undefined, rtl: boolean, onPick: (id: string) => void, onSelectContainer?: () => void,
42
+ export declare function paintTabStrip(strip: HTMLElement, pages: TabPage[], activeId: string, o: TabsOptions | undefined, rtl: boolean, onPick: (id: string) => void, onSelectContainer?: (e: PointerEvent) => void,
43
43
  /**
44
44
  * The tab is the PAGE's drag handle, as it is in VS Code: press one and
45
45
  * travel, and the whole page leaves — not the widget under the pointer,