@grafloria/element 0.4.32 → 0.4.34
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
CHANGED
|
@@ -1099,6 +1099,7 @@ export function createDashboardHandle(ctx) {
|
|
|
1099
1099
|
return { move, collapse: remaining.length > 0 ? [] : collapseOf(boardId) };
|
|
1100
1100
|
},
|
|
1101
1101
|
joinTargets: [...ctx.layoutOf.keys()].filter((id) => { var _a, _b; return id !== containerId && id !== pageId && ((_a = ctx.layoutOf.get(id)) !== null && _a !== void 0 ? _a : (_b = specById.get(id)) === null || _b === void 0 ? void 0 : _b.layout) === 'tabs' && !!model.getGroup(id) && !insidePage(id); }),
|
|
1102
|
+
stripHeight: (targetId) => tabStripReserve(ctx.tabsOf.get(targetId), Math.max(1, liveCount(targetId))),
|
|
1102
1103
|
stripIndex: (targetId, cx, cy) => {
|
|
1103
1104
|
const strip = ctx.tabStrips.get(targetId);
|
|
1104
1105
|
if (!strip)
|
|
@@ -1322,7 +1323,7 @@ export function createDashboardHandle(ctx) {
|
|
|
1322
1323
|
},
|
|
1323
1324
|
focusWidget(id) {
|
|
1324
1325
|
var _a;
|
|
1325
|
-
const b = binders.get((_a =
|
|
1326
|
+
const b = binders.get((_a = boardOfWidget(id)) !== null && _a !== void 0 ? _a : '');
|
|
1326
1327
|
if (!b)
|
|
1327
1328
|
return false;
|
|
1328
1329
|
if (b.focusWidget(id))
|
|
@@ -1343,7 +1344,7 @@ export function createDashboardHandle(ctx) {
|
|
|
1343
1344
|
b.selectWidget(undefined);
|
|
1344
1345
|
return true;
|
|
1345
1346
|
}
|
|
1346
|
-
const b = binders.get((_a =
|
|
1347
|
+
const b = binders.get((_a = boardOfWidget(id)) !== null && _a !== void 0 ? _a : '');
|
|
1347
1348
|
return !!b && b.selectWidget(id);
|
|
1348
1349
|
},
|
|
1349
1350
|
getSelectedWidget() {
|
|
@@ -1627,16 +1628,39 @@ export function createDashboardHandle(ctx) {
|
|
|
1627
1628
|
ctx.hosts.clear();
|
|
1628
1629
|
},
|
|
1629
1630
|
};
|
|
1631
|
+
/**
|
|
1632
|
+
* The board that HOLDS a widget NOW — live membership, the way toJSON reads
|
|
1633
|
+
* it. A cross-board drag moves membership through the binder's own
|
|
1634
|
+
* commands, which the mount-time map never sees; a handle that kept asking
|
|
1635
|
+
* the widget's FIRST board answered `cell: null` for a tile that had just
|
|
1636
|
+
* been dropped into a page. The map is the fallback for a widget that is
|
|
1637
|
+
* not a member yet (an add still committing).
|
|
1638
|
+
*/
|
|
1639
|
+
const boardOfWidget = (id) => {
|
|
1640
|
+
var _a, _b, _c, _d;
|
|
1641
|
+
for (const [cid, g] of ctx.boardGroups)
|
|
1642
|
+
if ((_a = g.members) === null || _a === void 0 ? void 0 : _a.has(id))
|
|
1643
|
+
return cid;
|
|
1644
|
+
const model = (_b = ctx.apiRef) === null || _b === void 0 ? void 0 : _b.getModel();
|
|
1645
|
+
if (model)
|
|
1646
|
+
for (const vid of binders.keys())
|
|
1647
|
+
if ((_d = (_c = model.getGroup(vid)) === null || _c === void 0 ? void 0 : _c.members) === null || _d === void 0 ? void 0 : _d.has(id))
|
|
1648
|
+
return vid;
|
|
1649
|
+
return viewOfWidget.get(id);
|
|
1650
|
+
};
|
|
1630
1651
|
function makeWidgetHandle(id) {
|
|
1631
1652
|
const spec = specById.get(id);
|
|
1632
|
-
|
|
1633
|
-
if (!spec || !viewId)
|
|
1653
|
+
if (!spec || !boardOfWidget(id))
|
|
1634
1654
|
return undefined;
|
|
1635
|
-
const
|
|
1655
|
+
const board = () => { var _a; return (_a = boardOfWidget(id)) !== null && _a !== void 0 ? _a : ''; };
|
|
1656
|
+
const binder = () => binders.get(board());
|
|
1636
1657
|
const node = () => { var _a; return (_a = ctx.apiRef) === null || _a === void 0 ? void 0 : _a.getModel().getNode(id); };
|
|
1637
1658
|
return {
|
|
1638
1659
|
id,
|
|
1639
|
-
viewId
|
|
1660
|
+
get viewId() {
|
|
1661
|
+
var _a;
|
|
1662
|
+
return (_a = boardOfWidget(id)) !== null && _a !== void 0 ? _a : '';
|
|
1663
|
+
},
|
|
1640
1664
|
get node() {
|
|
1641
1665
|
return node();
|
|
1642
1666
|
},
|
|
@@ -1715,8 +1739,8 @@ export function createDashboardHandle(ctx) {
|
|
|
1715
1739
|
// the parent board and the parent's re-pack, as ONE undoable batch.
|
|
1716
1740
|
// Undo restores the groups as fresh GroupModels, so the history
|
|
1717
1741
|
// handler re-binds the container's grid (ctx.rebindContainer).
|
|
1718
|
-
const parentGroup = ctx.boardGroups.get(
|
|
1719
|
-
const parentBinder = binders.get(
|
|
1742
|
+
const parentGroup = ctx.boardGroups.get(board());
|
|
1743
|
+
const parentBinder = binders.get(board());
|
|
1720
1744
|
const model = (_a = ctx.apiRef) === null || _a === void 0 ? void 0 : _a.getModel();
|
|
1721
1745
|
if (!parentGroup || !parentBinder || !model)
|
|
1722
1746
|
return;
|
|
@@ -1750,7 +1774,7 @@ export function createDashboardHandle(ctx) {
|
|
|
1750
1774
|
}
|
|
1751
1775
|
};
|
|
1752
1776
|
removeSubtree(id);
|
|
1753
|
-
const registry = registryOf(id,
|
|
1777
|
+
const registry = registryOf(id, board(), spec);
|
|
1754
1778
|
cmds.push(...nodeRemovals, ...unregisters, new RegisterWidgetCommand(registry, 'unregister'));
|
|
1755
1779
|
(_c = binders.get(id)) === null || _c === void 0 ? void 0 : _c.dispose();
|
|
1756
1780
|
binders.delete(id);
|
|
@@ -1761,7 +1785,7 @@ export function createDashboardHandle(ctx) {
|
|
|
1761
1785
|
return;
|
|
1762
1786
|
}
|
|
1763
1787
|
const n = node();
|
|
1764
|
-
const group = ctx.boardGroups.get(
|
|
1788
|
+
const group = ctx.boardGroups.get(board());
|
|
1765
1789
|
const b = binder();
|
|
1766
1790
|
if (!n || !group || !b)
|
|
1767
1791
|
return;
|
|
@@ -1774,11 +1798,11 @@ export function createDashboardHandle(ctx) {
|
|
|
1774
1798
|
// The un-registration is the LAST command so that undo — which runs
|
|
1775
1799
|
// the batch in reverse — re-registers the spec BEFORE the node and its
|
|
1776
1800
|
// membership come back and the painter is asked to paint it (D2).
|
|
1777
|
-
const registry = registryOf(id,
|
|
1801
|
+
const registry = registryOf(id, board(), spec);
|
|
1778
1802
|
// …and the page it emptied, if it was one, closes with it — in which
|
|
1779
1803
|
// case everything rides INSIDE one sequence, or the batch could never
|
|
1780
1804
|
// undo (a membership command cannot undo once its group is gone).
|
|
1781
|
-
const closing = (_g = (_f = ctx.closePageIfEmptied) === null || _f === void 0 ? void 0 : _f.call(ctx,
|
|
1805
|
+
const closing = (_g = (_f = ctx.closePageIfEmptied) === null || _f === void 0 ? void 0 : _f.call(ctx, board(), id)) !== null && _g !== void 0 ? _g : [];
|
|
1782
1806
|
const cmds = [
|
|
1783
1807
|
...survivors,
|
|
1784
1808
|
new RemoveFromGroupCommand(group.id, id),
|
|
@@ -567,6 +567,8 @@ export interface TearOutPlan {
|
|
|
567
567
|
markDrop(targetId: string | null, index: number | null): void;
|
|
568
568
|
/** The tab index a CLIENT point means along `targetId`'s strip — null when the point is not over that strip. */
|
|
569
569
|
stripIndex(targetId: string, clientX: number, clientY: number): number | null;
|
|
570
|
+
/** The strip's height on `targetId`, px — the band above its body. */
|
|
571
|
+
stripHeight(targetId: string): number;
|
|
570
572
|
/** The commands that REORDER the page to `index` along its own strip. Empty when nothing changes. */
|
|
571
573
|
reorder(index: number): Command[];
|
|
572
574
|
/**
|
|
@@ -650,17 +652,34 @@ interface AdoptedLeg {
|
|
|
650
652
|
x: number;
|
|
651
653
|
y: number;
|
|
652
654
|
}): void;
|
|
655
|
+
/** Put the tile at a PRESCRIBED cell (a split's half, a docking band): sized to it, unlocked tiles pushed. Answers whether it sits there. */
|
|
656
|
+
place(cell: {
|
|
657
|
+
x: number;
|
|
658
|
+
y: number;
|
|
659
|
+
w: number;
|
|
660
|
+
h: number;
|
|
661
|
+
}): boolean;
|
|
662
|
+
/** Every other tile's cell before the ghost entered — the layout a row insert translates. */
|
|
663
|
+
baseline(): Map<string, CellRect>;
|
|
653
664
|
/** Undo the adoption: target board back to its pre-entry layout. */
|
|
654
665
|
abort(): void;
|
|
655
666
|
/**
|
|
656
667
|
* Close the leg for commit: returns the target-side displaced commands, the
|
|
657
|
-
* tile's final cell and its projected rect
|
|
658
|
-
*
|
|
668
|
+
* tile's final cell and its projected rect — and the member GROUPS the
|
|
669
|
+
* adoption moved (the node commands skip groups; a dock that pushed sections
|
|
670
|
+
* commits them through these). Null when the tile is somehow gone.
|
|
659
671
|
*/
|
|
660
672
|
finalize(): {
|
|
661
673
|
commands: Command[];
|
|
662
674
|
cell: CellRect;
|
|
663
675
|
rect: WorldRect;
|
|
676
|
+
groups: Array<{
|
|
677
|
+
id: string;
|
|
678
|
+
cellBefore: CellRect;
|
|
679
|
+
cellAfter: CellRect;
|
|
680
|
+
frameBefore: WorldRect;
|
|
681
|
+
frameAfter: WorldRect;
|
|
682
|
+
}>;
|
|
664
683
|
} | null;
|
|
665
684
|
}
|
|
666
685
|
/**
|
|
@@ -394,7 +394,38 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
394
394
|
return e;
|
|
395
395
|
};
|
|
396
396
|
/** The effective row bound a gesture must respect: the strip's, else the fit capacity. */
|
|
397
|
-
const bound = () =>
|
|
397
|
+
const bound = () => { var _a; return (_a = squeezeRoom !== null && squeezeRoom !== void 0 ? squeezeRoom : maxRows) !== null && _a !== void 0 ? _a : capacity; };
|
|
398
|
+
/** Move the LIVE bound (the engine's too), the way escalation does. */
|
|
399
|
+
const setLiveBound = (n) => {
|
|
400
|
+
maxRows = n;
|
|
401
|
+
engine.maxRows = n;
|
|
402
|
+
};
|
|
403
|
+
/**
|
|
404
|
+
* Rows a SQUEEZE may use while a tile arrives by hand — the engine's bound
|
|
405
|
+
* only. The binder's `maxRows` also sets the GEOMETRY (`rows()`), and
|
|
406
|
+
* raising it to the room made every tile shrink to a 29-row grid the
|
|
407
|
+
* moment the ghost entered; the rows must shrink only as far as the
|
|
408
|
+
* content actually reaches.
|
|
409
|
+
*/
|
|
410
|
+
let squeezeRoom;
|
|
411
|
+
const setSqueeze = (n) => {
|
|
412
|
+
squeezeRoom = n;
|
|
413
|
+
engine.maxRows = n !== null && n !== void 0 ? n : maxRows;
|
|
414
|
+
};
|
|
415
|
+
/**
|
|
416
|
+
* The rows a NESTED board's live frame holds at the row floor. A page's
|
|
417
|
+
* height is its container's and a section's is its slab's, so a full one
|
|
418
|
+
* cannot ask for room the way the main fit board does (`fitCapacity` reads
|
|
419
|
+
* the design height, which a nested board hands to its parent as 0).
|
|
420
|
+
*/
|
|
421
|
+
const elasticRows = () => {
|
|
422
|
+
if (designRows === undefined)
|
|
423
|
+
return undefined;
|
|
424
|
+
const fh = frame().height;
|
|
425
|
+
if (fh <= 0)
|
|
426
|
+
return undefined;
|
|
427
|
+
return Math.max(1, Math.floor((fh - 2 * padding + gap) / (minRowHeight + gap)));
|
|
428
|
+
};
|
|
398
429
|
/**
|
|
399
430
|
* The rows the design height can hold at the row floor — what 'bounded' fit
|
|
400
431
|
* enforces. A board is never bounded BELOW what it already holds: a document
|
|
@@ -2404,6 +2435,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2404
2435
|
const adopt = (
|
|
2405
2436
|
// Only the id is used: a tab page arriving here is a GROUP, not a node.
|
|
2406
2437
|
node, world, pxSize, opts = {}) => {
|
|
2438
|
+
var _a;
|
|
2407
2439
|
if (disposed)
|
|
2408
2440
|
return null;
|
|
2409
2441
|
const f = frame();
|
|
@@ -2416,8 +2448,29 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2416
2448
|
if (b !== undefined)
|
|
2417
2449
|
span.h = Math.max(1, Math.min(b, span.h));
|
|
2418
2450
|
engine.beginGesture(); // pre-entry snapshot — abort() restores it
|
|
2419
|
-
|
|
2451
|
+
// A FULL nested board SQUEEZES for a tile arriving by hand, the way a fit
|
|
2452
|
+
// board squeezes for its own. Its bound is the design the escalation path
|
|
2453
|
+
// grows through the parent, and a page has no parent tile to grow (its
|
|
2454
|
+
// height is its container's): so the rows shrink toward the floor instead
|
|
2455
|
+
// of refusing the drop with no sign of why — the fluid demo's Filters page
|
|
2456
|
+
// took a KPI dragged onto it and showed nothing at all. abort() puts the
|
|
2457
|
+
// bound back; a commit keeps what the board then holds.
|
|
2458
|
+
const squeezeBefore = squeezeRoom;
|
|
2459
|
+
let entered = engine.add({ id: node.id, x: 0, y: engine.rows(), w: span.w, h: span.h });
|
|
2460
|
+
// Only a board with NO parent tile to grow through squeezes — a page. A
|
|
2461
|
+
// full SECTION keeps refusing (grid-options s05: refused, then joined once
|
|
2462
|
+
// a strip was removed); growing its slab for a dropped tile is the
|
|
2463
|
+
// escalation path's, not a squeeze's, and is not built yet.
|
|
2464
|
+
if (!entered && !parentPeer()) {
|
|
2465
|
+
const room = elasticRows();
|
|
2466
|
+
if (room !== undefined && room > ((_a = bound()) !== null && _a !== void 0 ? _a : 0)) {
|
|
2467
|
+
setSqueeze(room);
|
|
2468
|
+
span.h = Math.max(1, Math.min(room, span.h));
|
|
2469
|
+
entered = engine.add({ id: node.id, x: 0, y: engine.rows(), w: span.w, h: span.h });
|
|
2470
|
+
}
|
|
2471
|
+
}
|
|
2420
2472
|
if (!entered) {
|
|
2473
|
+
setSqueeze(squeezeBefore);
|
|
2421
2474
|
engine.endGesture();
|
|
2422
2475
|
return null; // a bounded, full board refuses the adoption
|
|
2423
2476
|
}
|
|
@@ -2453,6 +2506,34 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2453
2506
|
syncPlaceholder();
|
|
2454
2507
|
return {
|
|
2455
2508
|
groupId: group.id,
|
|
2509
|
+
baseline: () => startCells,
|
|
2510
|
+
place: (cell) => {
|
|
2511
|
+
if (!engine.getItem(node.id) && !engine.add({ id: node.id, x: 0, y: engine.rows(), w: cell.w, h: cell.h }))
|
|
2512
|
+
return false;
|
|
2513
|
+
const it = engine.getItem(node.id);
|
|
2514
|
+
if (!it)
|
|
2515
|
+
return false;
|
|
2516
|
+
// Shrink, move, then grow. A resize is clamped at the board's edge
|
|
2517
|
+
// where the ghost SITS, so a cell wider than fits there (a full-width
|
|
2518
|
+
// dock from a ghost parked mid-board) is reached by moving first —
|
|
2519
|
+
// and a cell narrower than the ghost by shrinking first, so the move
|
|
2520
|
+
// itself fits.
|
|
2521
|
+
const w0 = Math.min(it.w, cell.w);
|
|
2522
|
+
const h0 = Math.min(it.h, cell.h);
|
|
2523
|
+
if (w0 !== it.w || h0 !== it.h)
|
|
2524
|
+
engine.resizeCheck(node.id, w0, h0);
|
|
2525
|
+
const moved = engine.getItem(node.id);
|
|
2526
|
+
if (moved && (moved.x !== cell.x || moved.y !== cell.y))
|
|
2527
|
+
engine.moveCheck(node.id, cell.x, cell.y, { gate: false });
|
|
2528
|
+
const now = engine.getItem(node.id);
|
|
2529
|
+
if (now && (now.w !== cell.w || now.h !== cell.h))
|
|
2530
|
+
engine.resizeCheck(node.id, cell.w, cell.h);
|
|
2531
|
+
lastWant = null;
|
|
2532
|
+
project();
|
|
2533
|
+
syncPlaceholder();
|
|
2534
|
+
const at = engine.getItem(node.id);
|
|
2535
|
+
return !!at && at.x === cell.x && at.y === cell.y && at.w === cell.w && at.h === cell.h;
|
|
2536
|
+
},
|
|
2456
2537
|
move: (w) => {
|
|
2457
2538
|
const item = engine.getItem(node.id);
|
|
2458
2539
|
if (!item)
|
|
@@ -2490,6 +2571,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2490
2571
|
if (engine.getItem(node.id))
|
|
2491
2572
|
engine.remove(node.id);
|
|
2492
2573
|
engine.cancelGesture(); // pre-entry layout, memory cleared
|
|
2574
|
+
setSqueeze(squeezeBefore);
|
|
2493
2575
|
adoptedGhostId = null;
|
|
2494
2576
|
disarmGlideSoon();
|
|
2495
2577
|
project();
|
|
@@ -2506,11 +2588,34 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2506
2588
|
const cell = { x: item.x, y: item.y, w: item.w, h: item.h };
|
|
2507
2589
|
const rect = cellToRect(item, frame(), geom(), rows());
|
|
2508
2590
|
const commands = buildCommitCommands(deltasSince(startCells, startGeom, node.id));
|
|
2591
|
+
const groups = [];
|
|
2592
|
+
for (const [id, before] of startCells) {
|
|
2593
|
+
if (!isGroupMember(id))
|
|
2594
|
+
continue;
|
|
2595
|
+
const it = engine.getItem(id);
|
|
2596
|
+
const g0 = startGeom.get(id);
|
|
2597
|
+
if (!it || !g0)
|
|
2598
|
+
continue;
|
|
2599
|
+
if (it.x === before.x && it.y === before.y && it.w === before.w && it.h === before.h)
|
|
2600
|
+
continue;
|
|
2601
|
+
groups.push({
|
|
2602
|
+
id,
|
|
2603
|
+
cellBefore: before,
|
|
2604
|
+
cellAfter: { x: it.x, y: it.y, w: it.w, h: it.h },
|
|
2605
|
+
frameBefore: { x: g0.pos.x, y: g0.pos.y, width: g0.size.width, height: g0.size.height },
|
|
2606
|
+
frameAfter: cellToRect(it, frame(), geom(), rows()),
|
|
2607
|
+
});
|
|
2608
|
+
}
|
|
2509
2609
|
engine.endGesture();
|
|
2610
|
+
if (squeezeRoom !== undefined) {
|
|
2611
|
+
// What the board now HOLDS is its bound, not the squeeze's room.
|
|
2612
|
+
squeezeRoom = undefined;
|
|
2613
|
+
setLiveBound(liveBound(engine.getItems()));
|
|
2614
|
+
}
|
|
2510
2615
|
adoptedGhostId = null;
|
|
2511
2616
|
disarmGlideSoon();
|
|
2512
2617
|
syncPlaceholder();
|
|
2513
|
-
return { commands, cell, rect };
|
|
2618
|
+
return { commands, cell, rect, groups };
|
|
2514
2619
|
},
|
|
2515
2620
|
};
|
|
2516
2621
|
};
|
|
@@ -3136,69 +3241,374 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
3136
3241
|
const rect = api.container.getBoundingClientRect();
|
|
3137
3242
|
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 };
|
|
3138
3243
|
};
|
|
3139
|
-
const first = toWorld(ev.clientX, ev.clientY);
|
|
3140
3244
|
// The board holds the NEW group, not the bare page: it is adopted under the
|
|
3141
3245
|
// group's id, sized for the page plus its strip, taking the room under the
|
|
3142
3246
|
// pointer with its top edge at the pointer — the tab is what the pointer
|
|
3143
|
-
// holds, the page hangs below it.
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3247
|
+
// holds, the page hangs below it. NOT at the press: a ghost in the engine
|
|
3248
|
+
// costs rows, and on a fit board every tile squeezes the moment it enters,
|
|
3249
|
+
// so the ghost enters only when the pointer is over free board space, and
|
|
3250
|
+
// leaves whenever it is over a strip, a group or an edge. A full board that
|
|
3251
|
+
// refuses the ghost still lets the page join, reorder or split.
|
|
3252
|
+
let leg = null;
|
|
3253
|
+
const ensureLeg = (world) => {
|
|
3254
|
+
if (!leg)
|
|
3255
|
+
leg = adopt({ id: plan.arrivingId }, world, plan.size, { fit: 'shrink', anchor: 'top' });
|
|
3256
|
+
return leg;
|
|
3257
|
+
};
|
|
3258
|
+
const naturalSpan = (() => {
|
|
3259
|
+
const sp = sizeToSpan(plan.size.width, plan.size.height, frame(), geom(), rows());
|
|
3260
|
+
return { w: Math.max(1, Math.min(columns, sp.w)), h: Math.max(TEAR_OUT_MIN_ROWS, sp.h) };
|
|
3261
|
+
})();
|
|
3262
|
+
// A DOCKED group takes at most HALF the board — VS Code's edge drop splits
|
|
3263
|
+
// the area in two. A page as tall as the container it left would
|
|
3264
|
+
// otherwise shove the whole dashboard out of view. Half of the board as
|
|
3265
|
+
// it stood at the press, and of what the canvas shows, whichever is less.
|
|
3266
|
+
const dockSpan = (() => {
|
|
3267
|
+
const rows0 = rows();
|
|
3268
|
+
const rect = api.container.getBoundingClientRect();
|
|
3269
|
+
const rh = rowHeightFor(geom(), rows0) + gap;
|
|
3270
|
+
const visible = rect.height > 0 && rh > 0 ? Math.max(1, Math.floor((rect.height + gap) / rh)) : rows0;
|
|
3271
|
+
const halfRows = Math.max(TEAR_OUT_MIN_ROWS, Math.ceil(Math.min(rows0, visible) / 2));
|
|
3272
|
+
const halfCols = Math.max(1, Math.ceil(columns / 2));
|
|
3273
|
+
return { w: Math.min(naturalSpan.w, halfCols), h: Math.min(naturalSpan.h, halfRows) };
|
|
3274
|
+
})();
|
|
3147
3275
|
tearing = pageId;
|
|
3148
3276
|
const doc = (_a = api.container.ownerDocument) !== null && _a !== void 0 ? _a : document;
|
|
3149
3277
|
const chip = doc.createElement('div');
|
|
3150
3278
|
chip.className = 'axdb-drag-chip axdb-tab-chip';
|
|
3151
3279
|
chip.textContent = plan.label;
|
|
3152
3280
|
doc.body.appendChild(chip);
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
3281
|
+
const moveChip = (cx, cy) => {
|
|
3282
|
+
chip.style.left = `${cx + 6}px`;
|
|
3283
|
+
chip.style.top = `${cy + 6}px`;
|
|
3284
|
+
};
|
|
3285
|
+
moveChip(ev.clientX, ev.clientY);
|
|
3286
|
+
armGlide();
|
|
3287
|
+
api.render();
|
|
3156
3288
|
const layer = htmlLayer();
|
|
3289
|
+
const area = (g) => {
|
|
3290
|
+
const sz = sizeOf(g);
|
|
3291
|
+
return sz.width * sz.height;
|
|
3292
|
+
};
|
|
3157
3293
|
const targets = plan.joinTargets
|
|
3158
3294
|
.map((id) => diagram.getGroup(id))
|
|
3159
|
-
.filter((g) => !!g && g.id !== fromGroupId)
|
|
3160
|
-
|
|
3161
|
-
|
|
3295
|
+
.filter((g) => !!g && g.id !== fromGroupId)
|
|
3296
|
+
.sort((a, b) => area(a) - area(b));
|
|
3297
|
+
// GEOMETRY MUST NOT MOVE WITH THE GHOST. On a fit board the rows squeeze
|
|
3298
|
+
// while the ghost occupies some and relax when it leaves, so a target's
|
|
3299
|
+
// frame changes with the very decision being made about it: a join makes
|
|
3300
|
+
// the ghost leave, the frame grows, and the same pointer now reads as a
|
|
3301
|
+
// split. The target and its frame are therefore anchored when the pointer
|
|
3302
|
+
// enters it and kept until it leaves; the board frame is the one at press.
|
|
3303
|
+
let anchor = null;
|
|
3304
|
+
const inRect = (r, wx, wy) => wx >= r.x && wx <= r.x + r.width && wy >= r.y && wy <= r.y + r.height;
|
|
3305
|
+
const targetAt = (wx, wy) => {
|
|
3306
|
+
var _a;
|
|
3307
|
+
if (anchor && inRect(anchor.frame, wx, wy))
|
|
3308
|
+
return anchor.g;
|
|
3309
|
+
const t = (_a = targets.find((x) => worldInsideGroup(x, wx, wy))) !== null && _a !== void 0 ? _a : null;
|
|
3310
|
+
if (t)
|
|
3311
|
+
leg === null || leg === void 0 ? void 0 : leg.leave(); // the ghost leaves first, so the frame anchored is the relaxed one
|
|
3312
|
+
anchor = t ? { g: t, frame: frameOfGroup(t), stripH: plan.stripHeight(t.id) } : null;
|
|
3313
|
+
return t;
|
|
3314
|
+
};
|
|
3315
|
+
// THE BANDS ARE ON THE SCREEN. Dockview measures its container edges on
|
|
3316
|
+
// the element, and so must we: the camera moves during the drag (the
|
|
3317
|
+
// board glides, the ghost adds rows a bounded scroll answers to), so a
|
|
3318
|
+
// band fixed in world space at the press drifts away from the edge the
|
|
3319
|
+
// user sees. The frame is mapped to client space through the live
|
|
3320
|
+
// camera and clipped to the canvas, so a scrolled board's bands sit at
|
|
3321
|
+
// its VISIBLE edges.
|
|
3322
|
+
const ROOT_TOP = 20;
|
|
3323
|
+
const ROOT_BOTTOM = 20;
|
|
3324
|
+
const ROOT_SIDE = 40;
|
|
3325
|
+
const visibleFrame = () => {
|
|
3326
|
+
const rect = api.container.getBoundingClientRect();
|
|
3327
|
+
const o = toWorld(rect.left, rect.top);
|
|
3328
|
+
const u = toWorld(rect.left + 100, rect.top + 100);
|
|
3329
|
+
const sx = 100 / (u.x - o.x || 100);
|
|
3330
|
+
const sy = 100 / (u.y - o.y || 100);
|
|
3331
|
+
const f = frame();
|
|
3332
|
+
const left = rect.left + (f.x - o.x) * sx;
|
|
3333
|
+
const top = rect.top + (f.y - o.y) * sy;
|
|
3334
|
+
const right = left + f.width * sx;
|
|
3335
|
+
const bottom = top + f.height * sy;
|
|
3336
|
+
if (rect.width <= 0 || rect.height <= 0)
|
|
3337
|
+
return { left, top, right, bottom }; // unlaid-out: nothing to clip to
|
|
3338
|
+
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) };
|
|
3339
|
+
};
|
|
3340
|
+
const rootAt = (cx, cy) => {
|
|
3341
|
+
// The bands reach a little OUTSIDE the frame too: a pointer that
|
|
3342
|
+
// overshoots the board's top edge by a few pixels means "the top".
|
|
3343
|
+
const v = visibleFrame();
|
|
3344
|
+
if (cx < v.left - ROOT_SIDE || cx > v.right + ROOT_SIDE || cy < v.top - ROOT_TOP || cy > v.bottom + ROOT_BOTTOM)
|
|
3345
|
+
return null;
|
|
3346
|
+
const rows = Math.max(TEAR_OUT_MIN_ROWS, rowsWithout(plan.arrivingId));
|
|
3347
|
+
const w = dockSpan.w;
|
|
3348
|
+
const h = dockSpan.h;
|
|
3349
|
+
if (cy - v.top <= ROOT_TOP)
|
|
3350
|
+
return { kind: 'root', side: 'top', cell: { x: 0, y: 0, w: columns, h } };
|
|
3351
|
+
if (cx - v.left <= ROOT_SIDE)
|
|
3352
|
+
return { kind: 'root', side: 'left', cell: { x: 0, y: 0, w, h: rows } };
|
|
3353
|
+
if (v.right - cx <= ROOT_SIDE)
|
|
3354
|
+
return { kind: 'root', side: 'right', cell: { x: Math.max(0, columns - w), y: 0, w, h: rows } };
|
|
3355
|
+
if (v.bottom - cy <= ROOT_BOTTOM)
|
|
3356
|
+
return { kind: 'root', side: 'bottom', cell: { x: 0, y: rows, w: columns, h } };
|
|
3357
|
+
return null;
|
|
3358
|
+
};
|
|
3359
|
+
const rowsWithout = (id) => {
|
|
3360
|
+
let r = 0;
|
|
3361
|
+
for (const it of engine.getItems())
|
|
3362
|
+
if (it.id !== id)
|
|
3363
|
+
r = Math.max(r, it.y + it.h);
|
|
3364
|
+
return r;
|
|
3365
|
+
};
|
|
3366
|
+
/** A split being previewed: the target shrunk to its half, to be restored when the pointer leaves. */
|
|
3367
|
+
let split = null;
|
|
3368
|
+
const halves = (target, side) => {
|
|
3369
|
+
// The cell to halve is the target's own — not the half it is already
|
|
3370
|
+
// shrunk to while a split is being previewed.
|
|
3371
|
+
const live = engine.getItem(target.id);
|
|
3372
|
+
const it = split && split.id === target.id ? split.before : live;
|
|
3373
|
+
if (!it || !live)
|
|
3374
|
+
return null; // a group on another board: no cell of ours to halve
|
|
3375
|
+
if (side === 'left' || side === 'right') {
|
|
3376
|
+
if (it.w < 2)
|
|
3377
|
+
return null;
|
|
3378
|
+
const a = Math.ceil(it.w / 2);
|
|
3379
|
+
const b = it.w - a;
|
|
3380
|
+
return side === 'right'
|
|
3381
|
+
? { 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 } }
|
|
3382
|
+
: { 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 } };
|
|
3383
|
+
}
|
|
3384
|
+
if (it.h < 2 * TEAR_OUT_MIN_ROWS)
|
|
3385
|
+
return null;
|
|
3386
|
+
const a = Math.ceil(it.h / 2);
|
|
3387
|
+
const b = it.h - a;
|
|
3388
|
+
return side === 'bottom'
|
|
3389
|
+
? { 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 } }
|
|
3390
|
+
: { 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 } };
|
|
3391
|
+
};
|
|
3392
|
+
const zoneAt = (cx, cy, world) => {
|
|
3393
|
+
const target = targetAt(world.x, world.y);
|
|
3394
|
+
const home = !target && worldInsideGroup(from, world.x, world.y);
|
|
3395
|
+
// A strip is the most precise target there is: anyone's wins outright.
|
|
3396
|
+
if (target) {
|
|
3397
|
+
const idx = plan.stripIndex(target.id, cx, cy);
|
|
3398
|
+
if (idx !== null)
|
|
3399
|
+
return { kind: 'strip', target, index: idx };
|
|
3400
|
+
}
|
|
3401
|
+
else if (home) {
|
|
3402
|
+
const idx = plan.stripIndex(fromGroupId, cx, cy);
|
|
3403
|
+
if (idx !== null)
|
|
3404
|
+
return { kind: 'reorder', index: idx };
|
|
3405
|
+
}
|
|
3406
|
+
// The board's own edges beat whatever sits against them — Dockview's
|
|
3407
|
+
// container edges over its groups — so a group at the top of the board
|
|
3408
|
+
// still leaves the top band to the board.
|
|
3409
|
+
const root = rootAt(cx, cy);
|
|
3410
|
+
if (root)
|
|
3411
|
+
return root;
|
|
3412
|
+
if (target) {
|
|
3413
|
+
const f = anchor && anchor.g === target ? anchor.frame : frameOfGroup(target);
|
|
3414
|
+
const stripH = anchor && anchor.g === target ? anchor.stripH : plan.stripHeight(target.id);
|
|
3415
|
+
const bodyH = Math.max(1, f.height - stripH);
|
|
3416
|
+
const rx = Math.min(1, Math.max(0, (world.x - f.x) / Math.max(1, f.width)));
|
|
3417
|
+
const ry = Math.min(1, Math.max(0, (world.y - f.y - stripH) / bodyH));
|
|
3418
|
+
if (rx >= 1 / 3 && rx <= 2 / 3 && ry >= 1 / 3 && ry <= 2 / 3)
|
|
3419
|
+
return { kind: 'join', target };
|
|
3420
|
+
const d = [['left', rx], ['right', 1 - rx], ['top', ry], ['bottom', 1 - ry]];
|
|
3421
|
+
d.sort((p, q) => p[1] - q[1]);
|
|
3422
|
+
const h = halves(target, d[0][0]);
|
|
3423
|
+
return h ? Object.assign({ kind: 'split', target, side: d[0][0] }, h) : { kind: 'join', target };
|
|
3424
|
+
}
|
|
3425
|
+
if (home)
|
|
3426
|
+
return { kind: 'home' };
|
|
3427
|
+
return { kind: 'board' };
|
|
3428
|
+
};
|
|
3429
|
+
const zoneKey = (z) => JSON.stringify(z, (k, v) => (k === 'target' ? v.id : v));
|
|
3430
|
+
// DOCKING PUSHES SECTIONS. A section is a locked tile so that a widget
|
|
3431
|
+
// never pushes it; a group docked against the board's edge is the one
|
|
3432
|
+
// gesture that must — everything below the top band moves down. For the
|
|
3433
|
+
// preview every member group is unlocked; they are relocked when the
|
|
3434
|
+
// pointer leaves the band, and their moved cells are committed with the
|
|
3435
|
+
// dock.
|
|
3436
|
+
let unlockedGroups = [];
|
|
3437
|
+
const unlockGroups = () => {
|
|
3438
|
+
if (unlockedGroups.length > 0)
|
|
3439
|
+
return;
|
|
3440
|
+
for (const it of engine.getItems()) {
|
|
3441
|
+
if (it.id !== plan.arrivingId && it.locked && isGroupMember(it.id)) {
|
|
3442
|
+
it.locked = false;
|
|
3443
|
+
unlockedGroups.push(it.id);
|
|
3444
|
+
}
|
|
3445
|
+
}
|
|
3446
|
+
};
|
|
3447
|
+
const relockGroups = () => {
|
|
3448
|
+
for (const id of unlockedGroups) {
|
|
3449
|
+
const it = engine.getItem(id);
|
|
3450
|
+
if (it)
|
|
3451
|
+
it.locked = true;
|
|
3452
|
+
}
|
|
3453
|
+
unlockedGroups = [];
|
|
3454
|
+
};
|
|
3455
|
+
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));
|
|
3456
|
+
// A TOP DOCK INSERTS ROWS. The engine's push cascade resolves the band's
|
|
3457
|
+
// collisions one tile at a time and scrambles what stood beneath it (the
|
|
3458
|
+
// demo's KPI row came apart, two of its tiles under the chart). VS Code
|
|
3459
|
+
// shoves the whole area down intact, so after the ghost takes the band
|
|
3460
|
+
// every tile that stood at or below it is put back at its own column,
|
|
3461
|
+
// exactly the band's height lower — a translation of a layout without
|
|
3462
|
+
// overlaps has none — and comes back the same way when the pointer leaves.
|
|
3463
|
+
// The layout translated is the one from BEFORE the ghost entered (the
|
|
3464
|
+
// leg's baseline), not the engine's mid-gesture state: the adoption
|
|
3465
|
+
// itself pushes tiles about, and a snapshot taken after it would carry
|
|
3466
|
+
// that cascade into the insert.
|
|
3467
|
+
let inserted = null;
|
|
3468
|
+
const insertRows = (cell, l) => {
|
|
3469
|
+
if (cell.w < columns)
|
|
3470
|
+
return; // a side dock has no whole rows to insert
|
|
3471
|
+
inserted = l.baseline();
|
|
3472
|
+
for (const [id, c] of inserted) {
|
|
3473
|
+
const it = engine.getItem(id);
|
|
3474
|
+
if (!it)
|
|
3475
|
+
continue;
|
|
3476
|
+
it.x = c.x;
|
|
3477
|
+
it.y = c.y >= cell.y ? c.y + cell.h : c.y;
|
|
3478
|
+
}
|
|
3479
|
+
};
|
|
3480
|
+
const undoInsertRows = () => {
|
|
3481
|
+
if (!inserted)
|
|
3482
|
+
return;
|
|
3483
|
+
leg === null || leg === void 0 ? void 0 : leg.leave(); // the ghost frees its rows first
|
|
3484
|
+
for (const [id, c] of inserted) {
|
|
3485
|
+
const it = engine.getItem(id);
|
|
3486
|
+
if (it) {
|
|
3487
|
+
it.x = c.x;
|
|
3488
|
+
it.y = c.y;
|
|
3489
|
+
}
|
|
3490
|
+
}
|
|
3491
|
+
inserted = null;
|
|
3492
|
+
project();
|
|
3493
|
+
};
|
|
3162
3494
|
let joinEl = null;
|
|
3163
|
-
const
|
|
3164
|
-
if (!layer
|
|
3165
|
-
return;
|
|
3495
|
+
const showOverlay = (r) => {
|
|
3496
|
+
if (!layer)
|
|
3497
|
+
return;
|
|
3166
3498
|
if (!joinEl) {
|
|
3167
3499
|
joinEl = doc.createElement('div');
|
|
3168
3500
|
joinEl.className = 'axdb-join';
|
|
3169
3501
|
layer.prepend(joinEl);
|
|
3170
3502
|
}
|
|
3171
|
-
|
|
3172
|
-
joinEl.style.
|
|
3173
|
-
joinEl.style.
|
|
3174
|
-
joinEl.style.
|
|
3175
|
-
joinEl.style.height = `${sz.height}px`;
|
|
3503
|
+
joinEl.style.left = `${r.x}px`;
|
|
3504
|
+
joinEl.style.top = `${r.y}px`;
|
|
3505
|
+
joinEl.style.width = `${r.width}px`;
|
|
3506
|
+
joinEl.style.height = `${r.height}px`;
|
|
3176
3507
|
};
|
|
3177
|
-
const
|
|
3508
|
+
const hideOverlay = () => {
|
|
3178
3509
|
joinEl === null || joinEl === void 0 ? void 0 : joinEl.remove();
|
|
3179
3510
|
joinEl = null;
|
|
3180
|
-
plan.markDrop(null, null);
|
|
3181
3511
|
};
|
|
3182
|
-
const
|
|
3183
|
-
if (
|
|
3512
|
+
const undoSplitPreview = () => {
|
|
3513
|
+
if (!split)
|
|
3184
3514
|
return;
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
|
|
3515
|
+
const it = engine.getItem(split.id);
|
|
3516
|
+
leg === null || leg === void 0 ? void 0 : leg.leave(); // the born half must be free before the target grows back into it
|
|
3517
|
+
if (it) {
|
|
3518
|
+
if (it.x !== split.before.x || it.y !== split.before.y)
|
|
3519
|
+
engine.moveCheck(split.id, split.before.x, split.before.y, { gate: false });
|
|
3520
|
+
if (it.w !== split.before.w || it.h !== split.before.h)
|
|
3521
|
+
engine.resizeCheck(split.id, split.before.w, split.before.h);
|
|
3522
|
+
it.locked = true;
|
|
3193
3523
|
}
|
|
3524
|
+
split = null;
|
|
3525
|
+
project();
|
|
3194
3526
|
};
|
|
3195
|
-
const
|
|
3196
|
-
|
|
3197
|
-
|
|
3527
|
+
const previewSplit = (z, world) => {
|
|
3528
|
+
const it = engine.getItem(z.target.id);
|
|
3529
|
+
const l = ensureLeg(world);
|
|
3530
|
+
if (!it || !l)
|
|
3531
|
+
return false;
|
|
3532
|
+
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 };
|
|
3533
|
+
it.locked = false; // its own gesture for the moment: it may shrink and shift
|
|
3534
|
+
if (it.w !== z.keep.w || it.h !== z.keep.h)
|
|
3535
|
+
engine.resizeCheck(z.target.id, z.keep.w, z.keep.h);
|
|
3536
|
+
const now = engine.getItem(z.target.id);
|
|
3537
|
+
if (now && (now.x !== z.keep.x || now.y !== z.keep.y))
|
|
3538
|
+
engine.moveCheck(z.target.id, z.keep.x, z.keep.y, { gate: false });
|
|
3539
|
+
const ok = l.place(z.born);
|
|
3540
|
+
project();
|
|
3541
|
+
placeholder === null || placeholder === void 0 ? void 0 : placeholder.remove(); // the accent overlay says which half; the grey placeholder under it is noise
|
|
3542
|
+
placeholder = null;
|
|
3543
|
+
return ok;
|
|
3544
|
+
};
|
|
3545
|
+
let zone = { kind: 'board' };
|
|
3546
|
+
let key = zoneKey(zone);
|
|
3547
|
+
const applyZone = (z, world) => {
|
|
3548
|
+
var _a;
|
|
3549
|
+
const k = zoneKey(z);
|
|
3550
|
+
const same = k === key;
|
|
3551
|
+
key = k;
|
|
3552
|
+
zone = z;
|
|
3553
|
+
if (same) {
|
|
3554
|
+
if (z.kind === 'board')
|
|
3555
|
+
(_a = ensureLeg(world)) === null || _a === void 0 ? void 0 : _a.move(world);
|
|
3556
|
+
return;
|
|
3557
|
+
}
|
|
3558
|
+
undoSplitPreview();
|
|
3559
|
+
undoInsertRows();
|
|
3560
|
+
if (z.kind !== 'root')
|
|
3561
|
+
relockGroups();
|
|
3562
|
+
plan.markDrop(null, null);
|
|
3563
|
+
hideOverlay();
|
|
3564
|
+
switch (z.kind) {
|
|
3565
|
+
case 'strip':
|
|
3566
|
+
leg === null || leg === void 0 ? void 0 : leg.leave();
|
|
3567
|
+
showOverlay(frameOfGroup(z.target));
|
|
3568
|
+
plan.markDrop(z.target.id, z.index);
|
|
3569
|
+
break;
|
|
3570
|
+
case 'join':
|
|
3571
|
+
leg === null || leg === void 0 ? void 0 : leg.leave();
|
|
3572
|
+
showOverlay(frameOfGroup(z.target));
|
|
3573
|
+
break;
|
|
3574
|
+
case 'split': {
|
|
3575
|
+
if (!previewSplit(z, world)) {
|
|
3576
|
+
zone = { kind: 'join', target: z.target };
|
|
3577
|
+
key = zoneKey(zone);
|
|
3578
|
+
leg === null || leg === void 0 ? void 0 : leg.leave();
|
|
3579
|
+
showOverlay(frameOfGroup(z.target));
|
|
3580
|
+
break;
|
|
3581
|
+
}
|
|
3582
|
+
showOverlay(cellToRect(z.born, frame(), geom(), rows()));
|
|
3583
|
+
break;
|
|
3584
|
+
}
|
|
3585
|
+
case 'root': {
|
|
3586
|
+
const l = ensureLeg(world);
|
|
3587
|
+
if (l) {
|
|
3588
|
+
unlockGroups();
|
|
3589
|
+
l.place(z.cell);
|
|
3590
|
+
insertRows(z.cell, l);
|
|
3591
|
+
project();
|
|
3592
|
+
placeholder === null || placeholder === void 0 ? void 0 : placeholder.remove();
|
|
3593
|
+
placeholder = null;
|
|
3594
|
+
showOverlay(cellToRect(z.cell, frame(), geom(), rows()));
|
|
3595
|
+
}
|
|
3596
|
+
break;
|
|
3597
|
+
}
|
|
3598
|
+
case 'reorder':
|
|
3599
|
+
leg === null || leg === void 0 ? void 0 : leg.leave();
|
|
3600
|
+
plan.markDrop(fromGroupId, z.index);
|
|
3601
|
+
break;
|
|
3602
|
+
case 'home':
|
|
3603
|
+
leg === null || leg === void 0 ? void 0 : leg.leave();
|
|
3604
|
+
break;
|
|
3605
|
+
case 'board': {
|
|
3606
|
+
const l = ensureLeg(world);
|
|
3607
|
+
l === null || l === void 0 ? void 0 : l.enter(world);
|
|
3608
|
+
break;
|
|
3609
|
+
}
|
|
3610
|
+
}
|
|
3198
3611
|
};
|
|
3199
|
-
moveChip(ev.clientX, ev.clientY);
|
|
3200
|
-
armGlide();
|
|
3201
|
-
api.render();
|
|
3202
3612
|
let last = { x: ev.clientX, y: ev.clientY };
|
|
3203
3613
|
const detach = () => {
|
|
3204
3614
|
window.removeEventListener('pointermove', onMove, true);
|
|
@@ -3206,85 +3616,104 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
3206
3616
|
window.removeEventListener('pointercancel', onCancel, true);
|
|
3207
3617
|
window.removeEventListener('keydown', onKey, true);
|
|
3208
3618
|
};
|
|
3209
|
-
let homeIndex = null;
|
|
3210
3619
|
const onMove = (e) => {
|
|
3211
3620
|
if (disposed)
|
|
3212
3621
|
return detach();
|
|
3213
3622
|
last = { x: e.clientX, y: e.clientY };
|
|
3214
3623
|
moveChip(e.clientX, e.clientY);
|
|
3215
3624
|
const world = toWorld(e.clientX, e.clientY);
|
|
3216
|
-
const
|
|
3217
|
-
|
|
3218
|
-
//
|
|
3219
|
-
|
|
3220
|
-
|
|
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)));
|
|
3223
|
-
if (target)
|
|
3224
|
-
plan.markDrop(target.id, plan.dropIndex(target.id, e.clientX, e.clientY));
|
|
3225
|
-
else if (homeIndex !== null)
|
|
3226
|
-
plan.markDrop(fromGroupId, homeIndex);
|
|
3227
|
-
else if (!home)
|
|
3228
|
-
leg.move(world);
|
|
3625
|
+
const z = zoneAt(e.clientX, e.clientY, world);
|
|
3626
|
+
applyZone(z, world);
|
|
3627
|
+
// Dimmed = a release here does nothing: home, off the board, or a board
|
|
3628
|
+
// that refused the ghost (bounded and full).
|
|
3629
|
+
chip.classList.toggle('axdb-out', zone.kind === 'home' || (zone.kind === 'root' && !leg) || (zone.kind === 'board' && (!leg || !worldInsideBoardGrace(world.x, world.y))));
|
|
3229
3630
|
api.render();
|
|
3230
3631
|
};
|
|
3632
|
+
const done = (changed, kind) => {
|
|
3633
|
+
var _a;
|
|
3634
|
+
disarmGlideSoon();
|
|
3635
|
+
enforceBoardHeight();
|
|
3636
|
+
persistLayouts();
|
|
3637
|
+
api.renderNow();
|
|
3638
|
+
(_a = options.onGesture) === null || _a === void 0 ? void 0 : _a.call(options, { type: kind, kind: 'move', nodeId: pageId, changed });
|
|
3639
|
+
};
|
|
3231
3640
|
const finish = (commit) => {
|
|
3232
|
-
var _a, _b
|
|
3641
|
+
var _a, _b;
|
|
3233
3642
|
detach();
|
|
3234
3643
|
chip.remove();
|
|
3235
|
-
|
|
3644
|
+
hideOverlay();
|
|
3645
|
+
plan.markDrop(null, null);
|
|
3236
3646
|
tearing = null;
|
|
3237
3647
|
if (disposed)
|
|
3238
3648
|
return;
|
|
3239
3649
|
const world = toWorld(last.x, last.y);
|
|
3240
|
-
const
|
|
3241
|
-
if (
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3650
|
+
const z = commit ? zoneAt(last.x, last.y, world) : { kind: 'home' };
|
|
3651
|
+
if (z.kind !== 'root')
|
|
3652
|
+
undoInsertRows();
|
|
3653
|
+
if (!commit || z.kind === 'home' || (z.kind === 'root' && !ensureLeg(world)) || (z.kind === 'board' && (!ensureLeg(world) || !worldInsideBoardGrace(world.x, world.y)))) {
|
|
3654
|
+
undoSplitPreview();
|
|
3655
|
+
leg === null || leg === void 0 ? void 0 : leg.abort();
|
|
3656
|
+
relockGroups();
|
|
3657
|
+
done(false, 'cancel');
|
|
3658
|
+
return;
|
|
3659
|
+
}
|
|
3660
|
+
if (z.kind === 'reorder') {
|
|
3661
|
+
undoSplitPreview();
|
|
3662
|
+
leg === null || leg === void 0 ? void 0 : leg.abort();
|
|
3663
|
+
const cmds = plan.reorder(z.index);
|
|
3245
3664
|
const changed = cmds.length > 0 ? execute('Reorder tab', cmds) : false;
|
|
3246
|
-
|
|
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 });
|
|
3665
|
+
done(changed, changed ? 'commit' : 'cancel');
|
|
3249
3666
|
return;
|
|
3250
3667
|
}
|
|
3251
|
-
|
|
3252
|
-
if (target) {
|
|
3668
|
+
if (z.kind === 'strip' || z.kind === 'join') {
|
|
3253
3669
|
// JOIN: no cell on this board — the leg is abandoned (its displaced
|
|
3254
3670
|
// tiles are already home) and the page becomes the target's tab.
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
const
|
|
3259
|
-
|
|
3260
|
-
enforceBoardHeight();
|
|
3261
|
-
persistLayouts();
|
|
3262
|
-
api.renderNow();
|
|
3263
|
-
(_b = options.onGesture) === null || _b === void 0 ? void 0 : _b.call(options, { type: 'commit', kind: 'move', nodeId: pageId, changed });
|
|
3671
|
+
undoSplitPreview();
|
|
3672
|
+
const index = z.kind === 'strip' ? z.index : plan.dropIndex(z.target.id, last.x, last.y);
|
|
3673
|
+
leg === null || leg === void 0 ? void 0 : leg.abort();
|
|
3674
|
+
const planned = plan.join(z.target.id, index, group.id);
|
|
3675
|
+
done(execute('Move tab', [...planned.move, ...planned.collapse]), 'commit');
|
|
3264
3676
|
return;
|
|
3265
3677
|
}
|
|
3266
|
-
if (
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3678
|
+
if (z.kind === 'split') {
|
|
3679
|
+
// SPLIT: the target keeps one half of its cell, the page's new group the
|
|
3680
|
+
// other. The preview already holds both in the engine; commit them.
|
|
3681
|
+
if (!split || split.id !== z.target.id || zoneKey(zone) !== zoneKey(z))
|
|
3682
|
+
applyZone(z, world);
|
|
3683
|
+
const fin = (_a = leg === null || leg === void 0 ? void 0 : leg.finalize()) !== null && _a !== void 0 ? _a : null;
|
|
3684
|
+
const it = engine.getItem(z.target.id);
|
|
3685
|
+
const before = split;
|
|
3686
|
+
if (it)
|
|
3687
|
+
it.locked = true;
|
|
3688
|
+
split = null;
|
|
3689
|
+
if (!fin || !before || !it) {
|
|
3690
|
+
leg === null || leg === void 0 ? void 0 : leg.abort();
|
|
3691
|
+
done(false, 'cancel');
|
|
3692
|
+
return;
|
|
3693
|
+
}
|
|
3694
|
+
const keep = { x: it.x, y: it.y, w: it.w, h: it.h };
|
|
3695
|
+
const planned = plan.commands(fin.cell, fin.rect, group.id);
|
|
3696
|
+
const changed = execute('Split group', [
|
|
3697
|
+
...fin.commands,
|
|
3698
|
+
...groupCommands(fin, z.target.id),
|
|
3699
|
+
new SetGroupCellCommand(z.target.id, before.before, keep, before.frameBefore, cellToRect(keep, frame(), geom(), rows())),
|
|
3700
|
+
...planned.move,
|
|
3701
|
+
...planned.collapse,
|
|
3702
|
+
]);
|
|
3703
|
+
done(changed, 'commit');
|
|
3271
3704
|
return;
|
|
3272
3705
|
}
|
|
3273
|
-
|
|
3706
|
+
// ROOT DOCK or a free cell on the board: the leg sits where it will land.
|
|
3707
|
+
if (zoneKey(zone) !== zoneKey(z))
|
|
3708
|
+
applyZone(z, world);
|
|
3709
|
+
const fin = (_b = leg === null || leg === void 0 ? void 0 : leg.finalize()) !== null && _b !== void 0 ? _b : null;
|
|
3710
|
+
relockGroups();
|
|
3274
3711
|
if (!fin) {
|
|
3275
|
-
|
|
3276
|
-
api.renderNow();
|
|
3712
|
+
done(false, 'cancel');
|
|
3277
3713
|
return;
|
|
3278
3714
|
}
|
|
3279
3715
|
const planned = plan.commands(fin.cell, fin.rect, group.id);
|
|
3280
|
-
|
|
3281
|
-
// goes too (the plan carries the removal's settle).
|
|
3282
|
-
const changed = execute('Move tab out', [...fin.commands, ...planned.move, ...planned.collapse]);
|
|
3283
|
-
disarmGlideSoon();
|
|
3284
|
-
enforceBoardHeight();
|
|
3285
|
-
persistLayouts();
|
|
3286
|
-
api.renderNow();
|
|
3287
|
-
(_d = options.onGesture) === null || _d === void 0 ? void 0 : _d.call(options, { type: 'commit', kind: 'move', nodeId: pageId, changed });
|
|
3716
|
+
done(execute(z.kind === 'root' ? 'Dock tab' : 'Move tab out', [...fin.commands, ...groupCommands(fin), ...planned.move, ...planned.collapse]), 'commit');
|
|
3288
3717
|
};
|
|
3289
3718
|
const onUp = () => finish(true);
|
|
3290
3719
|
const onCancel = () => finish(false);
|