@grafloria/element 0.4.33 → 0.4.35
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
|
@@ -1100,6 +1100,7 @@ export function createDashboardHandle(ctx) {
|
|
|
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
1102
|
stripHeight: (targetId) => tabStripReserve(ctx.tabsOf.get(targetId), Math.max(1, liveCount(targetId))),
|
|
1103
|
+
ownBoards: [pageId, ...[...ctx.boardGroups.keys()].filter((id) => insidePage(id))],
|
|
1103
1104
|
stripIndex: (targetId, cx, cy) => {
|
|
1104
1105
|
const strip = ctx.tabStrips.get(targetId);
|
|
1105
1106
|
if (!strip)
|
|
@@ -1323,7 +1324,7 @@ export function createDashboardHandle(ctx) {
|
|
|
1323
1324
|
},
|
|
1324
1325
|
focusWidget(id) {
|
|
1325
1326
|
var _a;
|
|
1326
|
-
const b = binders.get((_a =
|
|
1327
|
+
const b = binders.get((_a = boardOfWidget(id)) !== null && _a !== void 0 ? _a : '');
|
|
1327
1328
|
if (!b)
|
|
1328
1329
|
return false;
|
|
1329
1330
|
if (b.focusWidget(id))
|
|
@@ -1344,7 +1345,7 @@ export function createDashboardHandle(ctx) {
|
|
|
1344
1345
|
b.selectWidget(undefined);
|
|
1345
1346
|
return true;
|
|
1346
1347
|
}
|
|
1347
|
-
const b = binders.get((_a =
|
|
1348
|
+
const b = binders.get((_a = boardOfWidget(id)) !== null && _a !== void 0 ? _a : '');
|
|
1348
1349
|
return !!b && b.selectWidget(id);
|
|
1349
1350
|
},
|
|
1350
1351
|
getSelectedWidget() {
|
|
@@ -1628,16 +1629,39 @@ export function createDashboardHandle(ctx) {
|
|
|
1628
1629
|
ctx.hosts.clear();
|
|
1629
1630
|
},
|
|
1630
1631
|
};
|
|
1632
|
+
/**
|
|
1633
|
+
* The board that HOLDS a widget NOW — live membership, the way toJSON reads
|
|
1634
|
+
* it. A cross-board drag moves membership through the binder's own
|
|
1635
|
+
* commands, which the mount-time map never sees; a handle that kept asking
|
|
1636
|
+
* the widget's FIRST board answered `cell: null` for a tile that had just
|
|
1637
|
+
* been dropped into a page. The map is the fallback for a widget that is
|
|
1638
|
+
* not a member yet (an add still committing).
|
|
1639
|
+
*/
|
|
1640
|
+
const boardOfWidget = (id) => {
|
|
1641
|
+
var _a, _b, _c, _d;
|
|
1642
|
+
for (const [cid, g] of ctx.boardGroups)
|
|
1643
|
+
if ((_a = g.members) === null || _a === void 0 ? void 0 : _a.has(id))
|
|
1644
|
+
return cid;
|
|
1645
|
+
const model = (_b = ctx.apiRef) === null || _b === void 0 ? void 0 : _b.getModel();
|
|
1646
|
+
if (model)
|
|
1647
|
+
for (const vid of binders.keys())
|
|
1648
|
+
if ((_d = (_c = model.getGroup(vid)) === null || _c === void 0 ? void 0 : _c.members) === null || _d === void 0 ? void 0 : _d.has(id))
|
|
1649
|
+
return vid;
|
|
1650
|
+
return viewOfWidget.get(id);
|
|
1651
|
+
};
|
|
1631
1652
|
function makeWidgetHandle(id) {
|
|
1632
1653
|
const spec = specById.get(id);
|
|
1633
|
-
|
|
1634
|
-
if (!spec || !viewId)
|
|
1654
|
+
if (!spec || !boardOfWidget(id))
|
|
1635
1655
|
return undefined;
|
|
1636
|
-
const
|
|
1656
|
+
const board = () => { var _a; return (_a = boardOfWidget(id)) !== null && _a !== void 0 ? _a : ''; };
|
|
1657
|
+
const binder = () => binders.get(board());
|
|
1637
1658
|
const node = () => { var _a; return (_a = ctx.apiRef) === null || _a === void 0 ? void 0 : _a.getModel().getNode(id); };
|
|
1638
1659
|
return {
|
|
1639
1660
|
id,
|
|
1640
|
-
viewId
|
|
1661
|
+
get viewId() {
|
|
1662
|
+
var _a;
|
|
1663
|
+
return (_a = boardOfWidget(id)) !== null && _a !== void 0 ? _a : '';
|
|
1664
|
+
},
|
|
1641
1665
|
get node() {
|
|
1642
1666
|
return node();
|
|
1643
1667
|
},
|
|
@@ -1716,8 +1740,8 @@ export function createDashboardHandle(ctx) {
|
|
|
1716
1740
|
// the parent board and the parent's re-pack, as ONE undoable batch.
|
|
1717
1741
|
// Undo restores the groups as fresh GroupModels, so the history
|
|
1718
1742
|
// handler re-binds the container's grid (ctx.rebindContainer).
|
|
1719
|
-
const parentGroup = ctx.boardGroups.get(
|
|
1720
|
-
const parentBinder = binders.get(
|
|
1743
|
+
const parentGroup = ctx.boardGroups.get(board());
|
|
1744
|
+
const parentBinder = binders.get(board());
|
|
1721
1745
|
const model = (_a = ctx.apiRef) === null || _a === void 0 ? void 0 : _a.getModel();
|
|
1722
1746
|
if (!parentGroup || !parentBinder || !model)
|
|
1723
1747
|
return;
|
|
@@ -1751,7 +1775,7 @@ export function createDashboardHandle(ctx) {
|
|
|
1751
1775
|
}
|
|
1752
1776
|
};
|
|
1753
1777
|
removeSubtree(id);
|
|
1754
|
-
const registry = registryOf(id,
|
|
1778
|
+
const registry = registryOf(id, board(), spec);
|
|
1755
1779
|
cmds.push(...nodeRemovals, ...unregisters, new RegisterWidgetCommand(registry, 'unregister'));
|
|
1756
1780
|
(_c = binders.get(id)) === null || _c === void 0 ? void 0 : _c.dispose();
|
|
1757
1781
|
binders.delete(id);
|
|
@@ -1762,7 +1786,7 @@ export function createDashboardHandle(ctx) {
|
|
|
1762
1786
|
return;
|
|
1763
1787
|
}
|
|
1764
1788
|
const n = node();
|
|
1765
|
-
const group = ctx.boardGroups.get(
|
|
1789
|
+
const group = ctx.boardGroups.get(board());
|
|
1766
1790
|
const b = binder();
|
|
1767
1791
|
if (!n || !group || !b)
|
|
1768
1792
|
return;
|
|
@@ -1775,11 +1799,11 @@ export function createDashboardHandle(ctx) {
|
|
|
1775
1799
|
// The un-registration is the LAST command so that undo — which runs
|
|
1776
1800
|
// the batch in reverse — re-registers the spec BEFORE the node and its
|
|
1777
1801
|
// membership come back and the painter is asked to paint it (D2).
|
|
1778
|
-
const registry = registryOf(id,
|
|
1802
|
+
const registry = registryOf(id, board(), spec);
|
|
1779
1803
|
// …and the page it emptied, if it was one, closes with it — in which
|
|
1780
1804
|
// case everything rides INSIDE one sequence, or the batch could never
|
|
1781
1805
|
// undo (a membership command cannot undo once its group is gone).
|
|
1782
|
-
const closing = (_g = (_f = ctx.closePageIfEmptied) === null || _f === void 0 ? void 0 : _f.call(ctx,
|
|
1806
|
+
const closing = (_g = (_f = ctx.closePageIfEmptied) === null || _f === void 0 ? void 0 : _f.call(ctx, board(), id)) !== null && _g !== void 0 ? _g : [];
|
|
1783
1807
|
const cmds = [
|
|
1784
1808
|
...survivors,
|
|
1785
1809
|
new RemoveFromGroupCommand(group.id, id),
|
|
@@ -569,6 +569,8 @@ export interface TearOutPlan {
|
|
|
569
569
|
stripIndex(targetId: string, clientX: number, clientY: number): number | null;
|
|
570
570
|
/** The strip's height on `targetId`, px — the band above its body. */
|
|
571
571
|
stripHeight(targetId: string): number;
|
|
572
|
+
/** The page itself and every board inside it — never a board the page can land on. */
|
|
573
|
+
ownBoards: string[];
|
|
572
574
|
/** The commands that REORDER the page to `index` along its own strip. Empty when nothing changes. */
|
|
573
575
|
reorder(index: number): Command[];
|
|
574
576
|
/**
|
|
@@ -661,6 +663,10 @@ interface AdoptedLeg {
|
|
|
661
663
|
}): boolean;
|
|
662
664
|
/** Every other tile's cell before the ghost entered — the layout a row insert translates. */
|
|
663
665
|
baseline(): Map<string, CellRect>;
|
|
666
|
+
/** Where the tile sits right now, or null while it is off the board. */
|
|
667
|
+
cell(): CellRect | null;
|
|
668
|
+
/** That cell in world space, by the adopting board's own geometry. */
|
|
669
|
+
rect(): WorldRect | null;
|
|
664
670
|
/** Undo the adoption: target board back to its pre-entry layout. */
|
|
665
671
|
abort(): void;
|
|
666
672
|
/**
|
|
@@ -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
|
|
@@ -1317,6 +1348,13 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1317
1348
|
},
|
|
1318
1349
|
move: false,
|
|
1319
1350
|
};
|
|
1351
|
+
// A TOP or LEFT edge moves the slab's origin, and the engine refuses to
|
|
1352
|
+
// move a locked tile: the move never landed, only the resize did, and a
|
|
1353
|
+
// section pulled up by its caption grew DOWNWARD a row per pointer step
|
|
1354
|
+
// — 18 rows for a 2-row pull (identification round, F16). Unlocked for
|
|
1355
|
+
// its own gesture, as a move is; relocked on release.
|
|
1356
|
+
if (edges.n || edges.w)
|
|
1357
|
+
it.locked = false;
|
|
1320
1358
|
capturePointer(slabGesture.pointerId);
|
|
1321
1359
|
api.container.style.cursor = cursorFor(edges);
|
|
1322
1360
|
};
|
|
@@ -1357,6 +1395,29 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1357
1395
|
if (it)
|
|
1358
1396
|
it.locked = true;
|
|
1359
1397
|
};
|
|
1398
|
+
/** The cell a slab move asked for and could not have — painted so the refusal is visible; null clears it. */
|
|
1399
|
+
let refusal = null;
|
|
1400
|
+
const showRefusal = (cell, w, h) => {
|
|
1401
|
+
const layer = htmlLayer();
|
|
1402
|
+
if (!cell || !layer) {
|
|
1403
|
+
refusal === null || refusal === void 0 ? void 0 : refusal.remove();
|
|
1404
|
+
refusal = null;
|
|
1405
|
+
api.container.style.cursor = slabGesture ? 'grabbing' : '';
|
|
1406
|
+
return;
|
|
1407
|
+
}
|
|
1408
|
+
if (!refusal || refusal.parentElement !== layer) {
|
|
1409
|
+
refusal === null || refusal === void 0 ? void 0 : refusal.remove();
|
|
1410
|
+
refusal = document.createElement('div');
|
|
1411
|
+
refusal.className = 'axdb-ph axdb-ph--no';
|
|
1412
|
+
layer.prepend(refusal);
|
|
1413
|
+
}
|
|
1414
|
+
const r = cellToRect({ x: cell.x, y: cell.y, w, h }, frame(), geom(), rows());
|
|
1415
|
+
refusal.style.left = `${r.x}px`;
|
|
1416
|
+
refusal.style.top = `${r.y}px`;
|
|
1417
|
+
refusal.style.width = `${r.width}px`;
|
|
1418
|
+
refusal.style.height = `${r.height}px`;
|
|
1419
|
+
api.container.style.cursor = 'not-allowed';
|
|
1420
|
+
};
|
|
1360
1421
|
const slabMove = (ev) => {
|
|
1361
1422
|
const g = slabGesture;
|
|
1362
1423
|
if (!g)
|
|
@@ -1375,8 +1436,26 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1375
1436
|
if (g.move) {
|
|
1376
1437
|
// The section's top-left follows the pointer by the offset it was grabbed at.
|
|
1377
1438
|
const cell = pointToCell(ev.world.x + g.grab.dx, ev.world.y + g.grab.dy, f, gg, rows(), it.w);
|
|
1378
|
-
if (
|
|
1379
|
-
|
|
1439
|
+
if (cell.x !== it.x || cell.y !== it.y) {
|
|
1440
|
+
// The cell under the pointer, else the nearest legal one — a widget
|
|
1441
|
+
// slides along its row past a locked tile; a section grabbed 60 px
|
|
1442
|
+
// from its left edge used to have its right edge refused by the
|
|
1443
|
+
// locked panel at every cell and simply not move, with nothing on
|
|
1444
|
+
// screen to say why (identification round, D6/D7). When even that
|
|
1445
|
+
// fails the wanted cell is painted as refused.
|
|
1446
|
+
// Along its ROW only: a section dragged LEFT that wandered DOWN its
|
|
1447
|
+
// column (the row search) read as the wrong tile moving.
|
|
1448
|
+
const was = { x: it.x, y: it.y };
|
|
1449
|
+
const moved = engine.moveCheck(g.id, cell.x, cell.y, { gate: false }).changed || placeOnRow(g.id, cell.x, cell.y, it.w);
|
|
1450
|
+
if (moved)
|
|
1451
|
+
project();
|
|
1452
|
+
// STUCK: the pointer asks for another cell and the slab did not budge
|
|
1453
|
+
// (placeOnRow counts "already on a legal cell" as placed) — paint what
|
|
1454
|
+
// was asked for as refused.
|
|
1455
|
+
const now = engine.getItem(g.id);
|
|
1456
|
+
const stuck = !!now && now.x === was.x && now.y === was.y && (cell.x !== was.x || cell.y !== was.y);
|
|
1457
|
+
showRefusal(stuck ? cell : null, it.w, it.h);
|
|
1458
|
+
}
|
|
1380
1459
|
syncPlaceholder();
|
|
1381
1460
|
return;
|
|
1382
1461
|
}
|
|
@@ -1424,10 +1503,10 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1424
1503
|
if (!g)
|
|
1425
1504
|
return;
|
|
1426
1505
|
slabGesture = null;
|
|
1506
|
+
showRefusal(null, 0, 0);
|
|
1427
1507
|
releasePointer(g.pointerId);
|
|
1428
1508
|
api.container.style.cursor = '';
|
|
1429
|
-
|
|
1430
|
-
relockSlab(g.id);
|
|
1509
|
+
relockSlab(g.id);
|
|
1431
1510
|
if (!g.started) {
|
|
1432
1511
|
engine.endGesture();
|
|
1433
1512
|
return;
|
|
@@ -1455,8 +1534,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1455
1534
|
slabGesture = null;
|
|
1456
1535
|
releasePointer(g.pointerId);
|
|
1457
1536
|
api.container.style.cursor = '';
|
|
1458
|
-
|
|
1459
|
-
relockSlab(g.id);
|
|
1537
|
+
relockSlab(g.id);
|
|
1460
1538
|
if (g.started)
|
|
1461
1539
|
engine.cancelGesture();
|
|
1462
1540
|
else
|
|
@@ -2404,6 +2482,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2404
2482
|
const adopt = (
|
|
2405
2483
|
// Only the id is used: a tab page arriving here is a GROUP, not a node.
|
|
2406
2484
|
node, world, pxSize, opts = {}) => {
|
|
2485
|
+
var _a;
|
|
2407
2486
|
if (disposed)
|
|
2408
2487
|
return null;
|
|
2409
2488
|
const f = frame();
|
|
@@ -2416,8 +2495,29 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2416
2495
|
if (b !== undefined)
|
|
2417
2496
|
span.h = Math.max(1, Math.min(b, span.h));
|
|
2418
2497
|
engine.beginGesture(); // pre-entry snapshot — abort() restores it
|
|
2419
|
-
|
|
2498
|
+
// A FULL nested board SQUEEZES for a tile arriving by hand, the way a fit
|
|
2499
|
+
// board squeezes for its own. Its bound is the design the escalation path
|
|
2500
|
+
// grows through the parent, and a page has no parent tile to grow (its
|
|
2501
|
+
// height is its container's): so the rows shrink toward the floor instead
|
|
2502
|
+
// of refusing the drop with no sign of why — the fluid demo's Filters page
|
|
2503
|
+
// took a KPI dragged onto it and showed nothing at all. abort() puts the
|
|
2504
|
+
// bound back; a commit keeps what the board then holds.
|
|
2505
|
+
const squeezeBefore = squeezeRoom;
|
|
2506
|
+
let entered = engine.add({ id: node.id, x: 0, y: engine.rows(), w: span.w, h: span.h });
|
|
2507
|
+
// Only a board with NO parent tile to grow through squeezes — a page. A
|
|
2508
|
+
// full SECTION keeps refusing (grid-options s05: refused, then joined once
|
|
2509
|
+
// a strip was removed); growing its slab for a dropped tile is the
|
|
2510
|
+
// escalation path's, not a squeeze's, and is not built yet.
|
|
2511
|
+
if (!entered && !parentPeer()) {
|
|
2512
|
+
const room = elasticRows();
|
|
2513
|
+
if (room !== undefined && room > ((_a = bound()) !== null && _a !== void 0 ? _a : 0)) {
|
|
2514
|
+
setSqueeze(room);
|
|
2515
|
+
span.h = Math.max(1, Math.min(room, span.h));
|
|
2516
|
+
entered = engine.add({ id: node.id, x: 0, y: engine.rows(), w: span.w, h: span.h });
|
|
2517
|
+
}
|
|
2518
|
+
}
|
|
2420
2519
|
if (!entered) {
|
|
2520
|
+
setSqueeze(squeezeBefore);
|
|
2421
2521
|
engine.endGesture();
|
|
2422
2522
|
return null; // a bounded, full board refuses the adoption
|
|
2423
2523
|
}
|
|
@@ -2454,6 +2554,14 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2454
2554
|
return {
|
|
2455
2555
|
groupId: group.id,
|
|
2456
2556
|
baseline: () => startCells,
|
|
2557
|
+
cell: () => {
|
|
2558
|
+
const it = engine.getItem(node.id);
|
|
2559
|
+
return it ? { x: it.x, y: it.y, w: it.w, h: it.h } : null;
|
|
2560
|
+
},
|
|
2561
|
+
rect: () => {
|
|
2562
|
+
const it = engine.getItem(node.id);
|
|
2563
|
+
return it ? cellToRect(it, frame(), geom(), rows()) : null;
|
|
2564
|
+
},
|
|
2457
2565
|
place: (cell) => {
|
|
2458
2566
|
if (!engine.getItem(node.id) && !engine.add({ id: node.id, x: 0, y: engine.rows(), w: cell.w, h: cell.h }))
|
|
2459
2567
|
return false;
|
|
@@ -2518,6 +2626,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2518
2626
|
if (engine.getItem(node.id))
|
|
2519
2627
|
engine.remove(node.id);
|
|
2520
2628
|
engine.cancelGesture(); // pre-entry layout, memory cleared
|
|
2629
|
+
setSqueeze(squeezeBefore);
|
|
2521
2630
|
adoptedGhostId = null;
|
|
2522
2631
|
disarmGlideSoon();
|
|
2523
2632
|
project();
|
|
@@ -2553,6 +2662,11 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2553
2662
|
});
|
|
2554
2663
|
}
|
|
2555
2664
|
engine.endGesture();
|
|
2665
|
+
if (squeezeRoom !== undefined) {
|
|
2666
|
+
// What the board now HOLDS is its bound, not the squeeze's room.
|
|
2667
|
+
squeezeRoom = undefined;
|
|
2668
|
+
setLiveBound(liveBound(engine.getItems()));
|
|
2669
|
+
}
|
|
2556
2670
|
adoptedGhostId = null;
|
|
2557
2671
|
disarmGlideSoon();
|
|
2558
2672
|
syncPlaceholder();
|
|
@@ -2678,23 +2792,37 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2678
2792
|
selfPeerRef = selfPeer;
|
|
2679
2793
|
// The board's gap, for chrome that must fit BETWEEN tiles (the outside grip tab).
|
|
2680
2794
|
api.container.style.setProperty('--axdb-gap', `${gap}px`);
|
|
2795
|
+
/** A press on one of our containers' strips, claimed so the renderer stays out of it. */
|
|
2796
|
+
let stripPress = false;
|
|
2681
2797
|
const tool = {
|
|
2682
2798
|
id: `dashboard-grid:${group.id}:${++binderSeq}`,
|
|
2683
2799
|
priority: 2, // point-specific claim — outranks mode-style tools (see ext/tools.ts)
|
|
2684
2800
|
hitTest(ev, hit) {
|
|
2685
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
2801
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p;
|
|
2686
2802
|
if (disposed)
|
|
2687
2803
|
return false;
|
|
2688
|
-
if (gesture || slabGesture || forwardSlab)
|
|
2804
|
+
if (gesture || slabGesture || forwardSlab || stripPress)
|
|
2689
2805
|
return true; // own the rest of an in-flight gesture
|
|
2806
|
+
// A press on one of OUR containers' TAB STRIPS is ours to CLAIM and then
|
|
2807
|
+
// leave alone: the strip's own listeners run the click, the tab drag and
|
|
2808
|
+
// the container move. Left unclaimed (ownsPress calls a strip "content")
|
|
2809
|
+
// the renderer's ladder cleared the selection and armed its empty-canvas
|
|
2810
|
+
// PAN, and the camera slid 10–20 px under every tab drag — the reorder
|
|
2811
|
+
// mark lost after a detour, a release outside the canvas that committed,
|
|
2812
|
+
// the strip end reading as the right band (identification round, 2026-09-09).
|
|
2813
|
+
const stripEl = (_d = (_c = (_b = (_a = ev.source) === null || _a === void 0 ? void 0 : _a.target) === null || _b === void 0 ? void 0 : _b.closest) === null || _c === void 0 ? void 0 : _c.call(_b, '.axdb-tabs')) !== null && _d !== void 0 ? _d : null;
|
|
2814
|
+
if (stripEl) {
|
|
2815
|
+
const cid = stripEl.getAttribute('data-tabs-id');
|
|
2816
|
+
return !!cid && ((_e = group.members) !== null && _e !== void 0 ? _e : new Set()).has(cid) && api.container.contains(stripEl);
|
|
2817
|
+
}
|
|
2690
2818
|
if (!ownsPress(api.container, diagram, ev, hit))
|
|
2691
2819
|
return false;
|
|
2692
2820
|
// A press on one of OUR sections' caption bands is ours by the DOM: a
|
|
2693
2821
|
// 'tab' band sits above the frame, over the gap or the tile above,
|
|
2694
2822
|
// where the geometry says otherwise.
|
|
2695
|
-
const chrome = (
|
|
2696
|
-
const chromeId = (
|
|
2697
|
-
const mine = !!chromeId && ((
|
|
2823
|
+
const chrome = (_h = (_g = (_f = ev.source) === null || _f === void 0 ? void 0 : _f.target) === null || _g === void 0 ? void 0 : _g.closest) === null || _h === void 0 ? void 0 : _h.call(_g, '.axdb-slab > .axdb-slab-h, .axdb-slab > .axdb-rs');
|
|
2824
|
+
const chromeId = (_j = chrome === null || chrome === void 0 ? void 0 : chrome.parentElement) === null || _j === void 0 ? void 0 : _j.getAttribute('data-slab-id');
|
|
2825
|
+
const mine = !!chromeId && ((_k = group.members) !== null && _k !== void 0 ? _k : new Set()).has(chromeId);
|
|
2698
2826
|
if (mine)
|
|
2699
2827
|
return true;
|
|
2700
2828
|
// Someone else's SECTION HANDLE is never ours to claim. A tab
|
|
@@ -2707,7 +2835,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2707
2835
|
if (chromeId && (chrome === null || chrome === void 0 ? void 0 : chrome.classList.contains('axdb-rs')))
|
|
2708
2836
|
return false;
|
|
2709
2837
|
if (hit.node) {
|
|
2710
|
-
if (((
|
|
2838
|
+
if (((_l = group.members) !== null && _l !== void 0 ? _l : new Set()).has(hit.node.id))
|
|
2711
2839
|
return true;
|
|
2712
2840
|
// A press on a tile that belongs to a NESTED board must reach that
|
|
2713
2841
|
// board's tool. The dead-zone claim below deadens the slab's EMPTY
|
|
@@ -2716,7 +2844,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2716
2844
|
// parent's tool won the registration-order tie and the child's resize
|
|
2717
2845
|
// never armed; grid-options binds parent-first and worked by
|
|
2718
2846
|
// accident).
|
|
2719
|
-
for (const p of (
|
|
2847
|
+
for (const p of (_m = BOARD_REGISTRY.get(api.container)) !== null && _m !== void 0 ? _m : []) {
|
|
2720
2848
|
if (p !== selfPeer && p.hasItem(hit.node.id))
|
|
2721
2849
|
return false;
|
|
2722
2850
|
}
|
|
@@ -2729,8 +2857,8 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2729
2857
|
// divider press in a split section (dead dividers) and, near the
|
|
2730
2858
|
// section's edge, turned it into a section resize (the width changed
|
|
2731
2859
|
// while a control's height was being dragged — Quantia, Groups page).
|
|
2732
|
-
for (const p of (
|
|
2733
|
-
if (p !== selfPeer && ((
|
|
2860
|
+
for (const p of (_o = BOARD_REGISTRY.get(api.container)) !== null && _o !== void 0 ? _o : []) {
|
|
2861
|
+
if (p !== selfPeer && ((_p = group.members) !== null && _p !== void 0 ? _p : new Set()).has(p.group.id) && p.containsWorld(ev.world.x, ev.world.y))
|
|
2734
2862
|
return false;
|
|
2735
2863
|
}
|
|
2736
2864
|
// Claim (and deaden) empty presses inside a member group's frame so the
|
|
@@ -2742,34 +2870,38 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2742
2870
|
return insideMemberGroupFrame(ev.world.x, ev.world.y) || worldInsideBoard(ev.world.x, ev.world.y);
|
|
2743
2871
|
},
|
|
2744
2872
|
onPointerDown(ev, hit) {
|
|
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;
|
|
2873
|
+
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, _0;
|
|
2746
2874
|
if (gesture)
|
|
2747
2875
|
return; // mid-palette
|
|
2748
2876
|
const target = ((_b = (_a = ev.source) === null || _a === void 0 ? void 0 : _a.target) !== null && _b !== void 0 ? _b : null);
|
|
2877
|
+
if ((_c = target === null || target === void 0 ? void 0 : target.closest) === null || _c === void 0 ? void 0 : _c.call(target, '.axdb-tabs')) {
|
|
2878
|
+
stripPress = true; // claimed for the strip: nothing of ours starts
|
|
2879
|
+
return;
|
|
2880
|
+
}
|
|
2749
2881
|
// A press on a painted grip names its widget by the DOM: an OUTSIDE tab
|
|
2750
2882
|
// sits above the card's box, where the hit test sees the gap or the
|
|
2751
2883
|
// neighbour above.
|
|
2752
2884
|
const gripHost = gripHostOf(target);
|
|
2753
|
-
const gripId = (
|
|
2754
|
-
const onGrip = !!gripId && ((
|
|
2885
|
+
const gripId = (_d = gripHost === null || gripHost === void 0 ? void 0 : gripHost.getAttribute('data-node-id')) !== null && _d !== void 0 ? _d : null;
|
|
2886
|
+
const onGrip = !!gripId && ((_e = group.members) !== null && _e !== void 0 ? _e : new Set()).has(gripId);
|
|
2755
2887
|
// A SECTION's corner handle sits on top of whatever tile shares that
|
|
2756
2888
|
// corner; the DOM target names the section, the hit test the tile.
|
|
2757
|
-
const sectionHandle = (
|
|
2889
|
+
const sectionHandle = (_f = target === null || target === void 0 ? void 0 : target.closest) === null || _f === void 0 ? void 0 : _f.call(target, '.axdb-slab > .axdb-rs');
|
|
2758
2890
|
// A SECTION's CAPTION BAND names its section the same way — a 'tab'
|
|
2759
2891
|
// band sits above the frame, over the gap or the tile above. An action
|
|
2760
2892
|
// button in it fires and does nothing else.
|
|
2761
|
-
const captionBand = (
|
|
2762
|
-
const captionId = (
|
|
2763
|
-
const ownCaption = !!captionId && ((
|
|
2893
|
+
const captionBand = (_g = target === null || target === void 0 ? void 0 : target.closest) === null || _g === void 0 ? void 0 : _g.call(target, '.axdb-slab > .axdb-slab-h');
|
|
2894
|
+
const captionId = (_j = (_h = captionBand === null || captionBand === void 0 ? void 0 : captionBand.parentElement) === null || _h === void 0 ? void 0 : _h.getAttribute('data-slab-id')) !== null && _j !== void 0 ? _j : null;
|
|
2895
|
+
const ownCaption = !!captionId && ((_k = group.members) !== null && _k !== void 0 ? _k : new Set()).has(captionId);
|
|
2764
2896
|
// (An action button never reaches here: it is pass-through, and its own
|
|
2765
2897
|
// `click` fires onCaptionAction — see captionPassThrough.)
|
|
2766
2898
|
if ((!hit.node && !onGrip) || sectionHandle || ownCaption) {
|
|
2767
2899
|
// A press on a SECTION — its empty band, its caption, its corner
|
|
2768
2900
|
// handle or its frame edge — selects the section; the handle or an
|
|
2769
2901
|
// edge resizes it.
|
|
2770
|
-
const slabHandle = (
|
|
2771
|
-
const slabId = (
|
|
2772
|
-
const grp = slabId && ((
|
|
2902
|
+
const slabHandle = (_l = target === null || target === void 0 ? void 0 : target.closest) === null || _l === void 0 ? void 0 : _l.call(target, '.axdb-slab > .axdb-rs');
|
|
2903
|
+
const slabId = (_p = (_o = (_m = slabHandle === null || slabHandle === void 0 ? void 0 : slabHandle.parentElement) === null || _m === void 0 ? void 0 : _m.getAttribute('data-slab-id')) !== null && _o !== void 0 ? _o : (ownCaption ? captionId : null)) !== null && _p !== void 0 ? _p : memberGroupAt(ev.world.x, ev.world.y);
|
|
2904
|
+
const grp = slabId && ((_q = group.members) !== null && _q !== void 0 ? _q : new Set()).has(slabId) ? diagram.getGroup(slabId) : undefined;
|
|
2773
2905
|
if (slabId && grp) {
|
|
2774
2906
|
selectWidget(slabId);
|
|
2775
2907
|
api.render();
|
|
@@ -2792,7 +2924,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2792
2924
|
// forwards the pointer sequence.
|
|
2793
2925
|
const parent = parentPeer();
|
|
2794
2926
|
if ((parent === null || parent === void 0 ? void 0 : parent.selectMember) && worldInsideBoard(ev.world.x, ev.world.y)) {
|
|
2795
|
-
const ownHandle = ((
|
|
2927
|
+
const ownHandle = ((_r = slabHandle === null || slabHandle === void 0 ? void 0 : slabHandle.parentElement) === null || _r === void 0 ? void 0 : _r.getAttribute('data-slab-id')) === group.id;
|
|
2796
2928
|
parent.selectMember(group.id);
|
|
2797
2929
|
if (!isStatic && parent.beginSlabResize) {
|
|
2798
2930
|
const edges = ownHandle
|
|
@@ -2800,14 +2932,14 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2800
2932
|
: slabEdgesNear(group, ev.world.x, ev.world.y);
|
|
2801
2933
|
if (anyEdge(edges) && parent.beginSlabResize(group.id, edges, ev))
|
|
2802
2934
|
forwardSlab = parent;
|
|
2803
|
-
else if (!anyEdge(edges) && captionId === group.id && ((
|
|
2935
|
+
else if (!anyEdge(edges) && captionId === group.id && ((_s = parent.beginSlabMove) === null || _s === void 0 ? void 0 : _s.call(parent, group.id, ev)))
|
|
2804
2936
|
forwardSlab = parent;
|
|
2805
2937
|
}
|
|
2806
2938
|
return;
|
|
2807
2939
|
}
|
|
2808
2940
|
// The board's own empty area: a void click. Nothing to drag, and the
|
|
2809
2941
|
// selection clears exactly as a click outside any board would.
|
|
2810
|
-
(
|
|
2942
|
+
(_u = (_t = diagram).clearSelection) === null || _u === void 0 ? void 0 : _u.call(_t);
|
|
2811
2943
|
selectWidget(undefined);
|
|
2812
2944
|
api.render();
|
|
2813
2945
|
return;
|
|
@@ -2816,17 +2948,17 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2816
2948
|
if (!node)
|
|
2817
2949
|
return;
|
|
2818
2950
|
selectWidget(node.id); // a press selects, whether or not it starts a gesture
|
|
2819
|
-
if (((
|
|
2951
|
+
if (((_v = node.state) === null || _v === void 0 ? void 0 : _v.locked) === true)
|
|
2820
2952
|
return; // pinned: refuse; click still focuses
|
|
2821
2953
|
if (isStatic)
|
|
2822
2954
|
return; // a static board: claimed and deadened, click still focuses
|
|
2823
2955
|
// Which edges did the press take? The corner handle names its own (s+e,
|
|
2824
2956
|
// or s+w on RTL); a bare press within EDGE_GRIP of the tile's border
|
|
2825
2957
|
// takes that border; anywhere else is a move. A grip press is a move.
|
|
2826
|
-
const onHandle = !!((
|
|
2958
|
+
const onHandle = !!((_w = target === null || target === void 0 ? void 0 : target.closest) === null || _w === void 0 ? void 0 : _w.call(target, '.axdb-rs'));
|
|
2827
2959
|
const hostEl = hostOf(node.id);
|
|
2828
|
-
const resizable = ((
|
|
2829
|
-
const movable = ((
|
|
2960
|
+
const resizable = ((_x = node.getMetadata) === null || _x === void 0 ? void 0 : _x.call(node, 'widgetResizable')) !== false;
|
|
2961
|
+
const movable = ((_y = node.getMetadata) === null || _y === void 0 ? void 0 : _y.call(node, 'widgetMovable')) !== false;
|
|
2830
2962
|
// `ev.screen` is ELEMENT-LOCAL px; host rects are in client px. Compare
|
|
2831
2963
|
// like with like — the source event's clientX/Y when there is one, else
|
|
2832
2964
|
// the container's origin plus the local offset.
|
|
@@ -2874,7 +3006,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2874
3006
|
startSize: { width: node.size.width, height: node.size.height },
|
|
2875
3007
|
startPos: { x: node.position.x, y: node.position.y },
|
|
2876
3008
|
edges,
|
|
2877
|
-
spans: { w: (
|
|
3009
|
+
spans: { w: (_z = it === null || it === void 0 ? void 0 : it.w) !== null && _z !== void 0 ? _z : 1, h: (_0 = it === null || it === void 0 ? void 0 : it.h) !== null && _0 !== void 0 ? _0 : 1 },
|
|
2878
3010
|
removedFromBoard: false,
|
|
2879
3011
|
leg: null,
|
|
2880
3012
|
strip: null,
|
|
@@ -2887,6 +3019,8 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2887
3019
|
},
|
|
2888
3020
|
onPointerMove(ev) {
|
|
2889
3021
|
var _a;
|
|
3022
|
+
if (stripPress)
|
|
3023
|
+
return;
|
|
2890
3024
|
if (forwardSlab)
|
|
2891
3025
|
(_a = forwardSlab.slabMove) === null || _a === void 0 ? void 0 : _a.call(forwardSlab, ev);
|
|
2892
3026
|
else if (slabGesture)
|
|
@@ -2895,20 +3029,38 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2895
3029
|
onToolMove(ev);
|
|
2896
3030
|
},
|
|
2897
3031
|
onPointerUp() {
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
|
|
3032
|
+
if (stripPress) {
|
|
3033
|
+
stripPress = false;
|
|
3034
|
+
return;
|
|
3035
|
+
}
|
|
3036
|
+
try {
|
|
3037
|
+
onPointerUpInner();
|
|
3038
|
+
}
|
|
3039
|
+
finally {
|
|
3040
|
+
flushDeferredRebuild();
|
|
2902
3041
|
}
|
|
2903
|
-
else if (slabGesture)
|
|
2904
|
-
slabUp();
|
|
2905
|
-
else
|
|
2906
|
-
onToolUp();
|
|
2907
3042
|
},
|
|
2908
3043
|
onCancel() {
|
|
2909
|
-
|
|
3044
|
+
stripPress = false;
|
|
3045
|
+
try {
|
|
3046
|
+
cancelActiveGesture();
|
|
3047
|
+
}
|
|
3048
|
+
finally {
|
|
3049
|
+
flushDeferredRebuild();
|
|
3050
|
+
}
|
|
2910
3051
|
},
|
|
2911
3052
|
};
|
|
3053
|
+
const onPointerUpInner = () => {
|
|
3054
|
+
var _a;
|
|
3055
|
+
if (forwardSlab) {
|
|
3056
|
+
(_a = forwardSlab.slabUp) === null || _a === void 0 ? void 0 : _a.call(forwardSlab);
|
|
3057
|
+
forwardSlab = null;
|
|
3058
|
+
}
|
|
3059
|
+
else if (slabGesture)
|
|
3060
|
+
slabUp();
|
|
3061
|
+
else
|
|
3062
|
+
onToolUp();
|
|
3063
|
+
};
|
|
2912
3064
|
const unregisterTool = registerTool(tool);
|
|
2913
3065
|
/**
|
|
2914
3066
|
* Edge affordance: the cursor says which border a press would take, the
|
|
@@ -3191,11 +3343,42 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
3191
3343
|
// leaves whenever it is over a strip, a group or an edge. A full board that
|
|
3192
3344
|
// refuses the ghost still lets the page join, reorder or split.
|
|
3193
3345
|
let leg = null;
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3346
|
+
/** The board holding the leg: null = this one, else the foreign peer under the pointer. */
|
|
3347
|
+
let legPeer = null;
|
|
3348
|
+
const ensureLeg = (world, peer = null) => {
|
|
3349
|
+
// The page TRAVELS at most half the board tall (the dock's measure): at
|
|
3350
|
+
// its natural height — a page fills its container, eight rows on the
|
|
3351
|
+
// demo — the ghost crossing the KPI row tore the whole dashboard apart
|
|
3352
|
+
// on its way to a cell (identification round, D1 03–08).
|
|
3353
|
+
const capPx = dockSpan.h * (rowHeightFor(geom(), rows()) + gap) - gap;
|
|
3354
|
+
const size = { width: plan.size.width, height: Math.min(plan.size.height, capPx) };
|
|
3355
|
+
// A different board under the pointer takes the leg over, the old one
|
|
3356
|
+
// back to its pre-entry layout first. An INNER tab used to know only
|
|
3357
|
+
// the board owning its container — over the main board the chip dimmed
|
|
3358
|
+
// and the release did nothing (identification round, L2).
|
|
3359
|
+
if (leg && legPeer !== peer) {
|
|
3360
|
+
leg.abort();
|
|
3361
|
+
leg = null;
|
|
3362
|
+
}
|
|
3363
|
+
if (!leg) {
|
|
3364
|
+
leg = peer ? peer.adopt({ id: plan.arrivingId }, world, size, { fit: 'shrink', anchor: 'top' }) : adopt({ id: plan.arrivingId }, world, size, { fit: 'shrink', anchor: 'top' });
|
|
3365
|
+
legPeer = leg ? peer : null;
|
|
3366
|
+
}
|
|
3197
3367
|
return leg;
|
|
3198
3368
|
};
|
|
3369
|
+
/** The deepest OTHER board under the point that may take the page — never one of the page's own boards. */
|
|
3370
|
+
const foreignAt = (wx, wy) => {
|
|
3371
|
+
let best = null;
|
|
3372
|
+
for (const p of peersOnCanvas()) {
|
|
3373
|
+
if (p === selfPeer || plan.ownBoards.includes(p.group.id))
|
|
3374
|
+
continue;
|
|
3375
|
+
if (!p.containsWorld(wx, wy))
|
|
3376
|
+
continue;
|
|
3377
|
+
if (!best || p.frameArea() < best.frameArea())
|
|
3378
|
+
best = p;
|
|
3379
|
+
}
|
|
3380
|
+
return best;
|
|
3381
|
+
};
|
|
3199
3382
|
const naturalSpan = (() => {
|
|
3200
3383
|
const sp = sizeToSpan(plan.size.width, plan.size.height, frame(), geom(), rows());
|
|
3201
3384
|
return { w: Math.max(1, Math.min(columns, sp.w)), h: Math.max(TEAR_OUT_MIN_ROWS, sp.h) };
|
|
@@ -3278,13 +3461,60 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
3278
3461
|
return { left, top, right, bottom }; // unlaid-out: nothing to clip to
|
|
3279
3462
|
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
3463
|
};
|
|
3464
|
+
/** Inside the VISIBLE canvas plus the grace band, on the screen — a camera that moved must not turn a pointer outside the canvas into a world point inside the board. */
|
|
3465
|
+
const clientInsideCanvasGrace = (cx, cy) => {
|
|
3466
|
+
// The CANVAS, not this board's frame: a nested page's binder runs the
|
|
3467
|
+
// tear-out of the tabs inside it, and its pages must still land on the
|
|
3468
|
+
// boards around it. An unmeasurable container (no layout) is never off.
|
|
3469
|
+
const rect = api.container.getBoundingClientRect();
|
|
3470
|
+
if (rect.width <= 0 || rect.height <= 0)
|
|
3471
|
+
return true;
|
|
3472
|
+
return cx >= rect.left - EDGE_GRACE && cx <= rect.right + EDGE_GRACE && cy >= rect.top - EDGE_GRACE && cy <= rect.bottom + EDGE_GRACE;
|
|
3473
|
+
};
|
|
3474
|
+
/**
|
|
3475
|
+
* The board's rows for a dock: as they stood BEFORE the ghost entered.
|
|
3476
|
+
* Read live, the ghost's own pushing inflated them on every re-entry —
|
|
3477
|
+
* side docks 14, 21, 104 rows tall, a bottom dock landing at row 29.
|
|
3478
|
+
*/
|
|
3479
|
+
const baseRows = () => {
|
|
3480
|
+
const b = leg === null || leg === void 0 ? void 0 : leg.baseline();
|
|
3481
|
+
if (!b)
|
|
3482
|
+
return rowsWithout(plan.arrivingId);
|
|
3483
|
+
let r = 0;
|
|
3484
|
+
for (const c of b.values())
|
|
3485
|
+
r = Math.max(r, c.y + c.h);
|
|
3486
|
+
return r;
|
|
3487
|
+
};
|
|
3488
|
+
const worldToClient = (wx, wy) => {
|
|
3489
|
+
const rect = api.container.getBoundingClientRect();
|
|
3490
|
+
const o = toWorld(rect.left, rect.top);
|
|
3491
|
+
const u = toWorld(rect.left + 100, rect.top + 100);
|
|
3492
|
+
return { x: rect.left + (wx - o.x) * (100 / (u.x - o.x || 100)), y: rect.top + (wy - o.y) * (100 / (u.y - o.y || 100)) };
|
|
3493
|
+
};
|
|
3494
|
+
/**
|
|
3495
|
+
* Would the page land where the user cannot see it? A cell whose room
|
|
3496
|
+
* lies below the fold (the only cell left under a locked section — D1
|
|
3497
|
+
* hold3) used to take the drop a screen away. Hidden = not one pixel of
|
|
3498
|
+
* the cell inside the visible canvas: a landing whose top edge shows,
|
|
3499
|
+
* the rest below the fold of a small canvas, is a landing the user can
|
|
3500
|
+
* see and a grow board scrolls to (lab L61 dropped at the canvas foot).
|
|
3501
|
+
*/
|
|
3502
|
+
const landingHidden = () => {
|
|
3503
|
+
const r = leg === null || leg === void 0 ? void 0 : leg.rect();
|
|
3504
|
+
const rect = api.container.getBoundingClientRect();
|
|
3505
|
+
if (!r || rect.width <= 0 || rect.height <= 0)
|
|
3506
|
+
return false;
|
|
3507
|
+
const tl = worldToClient(r.x, r.y);
|
|
3508
|
+
const br = worldToClient(r.x + r.width, r.y + r.height);
|
|
3509
|
+
return tl.y >= rect.bottom || br.y <= rect.top || tl.x >= rect.right || br.x <= rect.left;
|
|
3510
|
+
};
|
|
3281
3511
|
const rootAt = (cx, cy) => {
|
|
3282
3512
|
// The bands reach a little OUTSIDE the frame too: a pointer that
|
|
3283
3513
|
// overshoots the board's top edge by a few pixels means "the top".
|
|
3284
3514
|
const v = visibleFrame();
|
|
3285
3515
|
if (cx < v.left - ROOT_SIDE || cx > v.right + ROOT_SIDE || cy < v.top - ROOT_TOP || cy > v.bottom + ROOT_BOTTOM)
|
|
3286
3516
|
return null;
|
|
3287
|
-
const rows = Math.max(TEAR_OUT_MIN_ROWS,
|
|
3517
|
+
const rows = Math.max(TEAR_OUT_MIN_ROWS, baseRows());
|
|
3288
3518
|
const w = dockSpan.w;
|
|
3289
3519
|
const h = dockSpan.h;
|
|
3290
3520
|
if (cy - v.top <= ROOT_TOP)
|
|
@@ -3331,6 +3561,11 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
3331
3561
|
: { 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
3562
|
};
|
|
3333
3563
|
const zoneAt = (cx, cy, world) => {
|
|
3564
|
+
// A pointer OUTSIDE the visible canvas is off, full stop: a camera that
|
|
3565
|
+
// moved can map it to a world point inside a group, and that group
|
|
3566
|
+
// took a join or a split from a release in the page header.
|
|
3567
|
+
if (!clientInsideCanvasGrace(cx, cy))
|
|
3568
|
+
return { kind: 'off' };
|
|
3334
3569
|
const target = targetAt(world.x, world.y);
|
|
3335
3570
|
const home = !target && worldInsideGroup(from, world.x, world.y);
|
|
3336
3571
|
// A strip is the most precise target there is: anyone's wins outright.
|
|
@@ -3365,9 +3600,12 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
3365
3600
|
}
|
|
3366
3601
|
if (home)
|
|
3367
3602
|
return { kind: 'home' };
|
|
3603
|
+
const foreign = foreignAt(world.x, world.y);
|
|
3604
|
+
if (foreign && (!worldInsideBoard(world.x, world.y) || foreign.frameArea() < boardArea()))
|
|
3605
|
+
return { kind: 'board', peer: foreign };
|
|
3368
3606
|
return { kind: 'board' };
|
|
3369
3607
|
};
|
|
3370
|
-
const zoneKey = (z) => JSON.stringify(z, (k, v) => (k === 'target' ? v.id : v));
|
|
3608
|
+
const zoneKey = (z) => JSON.stringify(z, (k, v) => (k === 'target' ? v.id : k === 'peer' ? v.group.id : v));
|
|
3371
3609
|
// DOCKING PUSHES SECTIONS. A section is a locked tile so that a widget
|
|
3372
3610
|
// never pushes it; a group docked against the board's edge is the one
|
|
3373
3611
|
// gesture that must — everything below the top band moves down. For the
|
|
@@ -3467,7 +3705,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
3467
3705
|
};
|
|
3468
3706
|
const previewSplit = (z, world) => {
|
|
3469
3707
|
const it = engine.getItem(z.target.id);
|
|
3470
|
-
const l = ensureLeg(world);
|
|
3708
|
+
const l = ensureLeg(world, null);
|
|
3471
3709
|
if (!it || !l)
|
|
3472
3710
|
return false;
|
|
3473
3711
|
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 };
|
|
@@ -3486,14 +3724,14 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
3486
3724
|
let zone = { kind: 'board' };
|
|
3487
3725
|
let key = zoneKey(zone);
|
|
3488
3726
|
const applyZone = (z, world) => {
|
|
3489
|
-
var _a;
|
|
3727
|
+
var _a, _b, _c;
|
|
3490
3728
|
const k = zoneKey(z);
|
|
3491
3729
|
const same = k === key;
|
|
3492
3730
|
key = k;
|
|
3493
3731
|
zone = z;
|
|
3494
3732
|
if (same) {
|
|
3495
3733
|
if (z.kind === 'board')
|
|
3496
|
-
(
|
|
3734
|
+
(_b = ensureLeg(world, (_a = z.peer) !== null && _a !== void 0 ? _a : null)) === null || _b === void 0 ? void 0 : _b.move(world);
|
|
3497
3735
|
return;
|
|
3498
3736
|
}
|
|
3499
3737
|
undoSplitPreview();
|
|
@@ -3524,7 +3762,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
3524
3762
|
break;
|
|
3525
3763
|
}
|
|
3526
3764
|
case 'root': {
|
|
3527
|
-
const l = ensureLeg(world);
|
|
3765
|
+
const l = ensureLeg(world, null);
|
|
3528
3766
|
if (l) {
|
|
3529
3767
|
unlockGroups();
|
|
3530
3768
|
l.place(z.cell);
|
|
@@ -3541,10 +3779,11 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
3541
3779
|
plan.markDrop(fromGroupId, z.index);
|
|
3542
3780
|
break;
|
|
3543
3781
|
case 'home':
|
|
3782
|
+
case 'off':
|
|
3544
3783
|
leg === null || leg === void 0 ? void 0 : leg.leave();
|
|
3545
3784
|
break;
|
|
3546
3785
|
case 'board': {
|
|
3547
|
-
const l = ensureLeg(world);
|
|
3786
|
+
const l = ensureLeg(world, (_c = z.peer) !== null && _c !== void 0 ? _c : null);
|
|
3548
3787
|
l === null || l === void 0 ? void 0 : l.enter(world);
|
|
3549
3788
|
break;
|
|
3550
3789
|
}
|
|
@@ -3567,7 +3806,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
3567
3806
|
applyZone(z, world);
|
|
3568
3807
|
// Dimmed = a release here does nothing: home, off the board, or a board
|
|
3569
3808
|
// that refused the ghost (bounded and full).
|
|
3570
|
-
chip.classList.toggle('axdb-out', zone.kind === 'home' || (zone.kind === 'root' && !leg) || (zone.kind === 'board' && (!leg ||
|
|
3809
|
+
chip.classList.toggle('axdb-out', zone.kind === 'home' || zone.kind === 'off' || (zone.kind === 'root' && !leg) || (zone.kind === 'board' && (!leg || landingHidden())));
|
|
3571
3810
|
api.render();
|
|
3572
3811
|
};
|
|
3573
3812
|
const done = (changed, kind) => {
|
|
@@ -3579,7 +3818,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
3579
3818
|
(_a = options.onGesture) === null || _a === void 0 ? void 0 : _a.call(options, { type: kind, kind: 'move', nodeId: pageId, changed });
|
|
3580
3819
|
};
|
|
3581
3820
|
const finish = (commit) => {
|
|
3582
|
-
var _a, _b;
|
|
3821
|
+
var _a, _b, _c, _d;
|
|
3583
3822
|
detach();
|
|
3584
3823
|
chip.remove();
|
|
3585
3824
|
hideOverlay();
|
|
@@ -3591,7 +3830,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
3591
3830
|
const z = commit ? zoneAt(last.x, last.y, world) : { kind: 'home' };
|
|
3592
3831
|
if (z.kind !== 'root')
|
|
3593
3832
|
undoInsertRows();
|
|
3594
|
-
if (!commit || z.kind === 'home' || (z.kind === 'root' && !ensureLeg(world)) || (z.kind === 'board' && (!ensureLeg(world) ||
|
|
3833
|
+
if (!commit || z.kind === 'home' || z.kind === 'off' || (z.kind === 'root' && !ensureLeg(world, null)) || (z.kind === 'board' && (!ensureLeg(world, (_a = z.peer) !== null && _a !== void 0 ? _a : null) || landingHidden()))) {
|
|
3595
3834
|
undoSplitPreview();
|
|
3596
3835
|
leg === null || leg === void 0 ? void 0 : leg.abort();
|
|
3597
3836
|
relockGroups();
|
|
@@ -3621,7 +3860,9 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
3621
3860
|
// other. The preview already holds both in the engine; commit them.
|
|
3622
3861
|
if (!split || split.id !== z.target.id || zoneKey(zone) !== zoneKey(z))
|
|
3623
3862
|
applyZone(z, world);
|
|
3624
|
-
|
|
3863
|
+
hideOverlay(); // a re-applied zone repaints its preview; the release is not a preview
|
|
3864
|
+
plan.markDrop(null, null);
|
|
3865
|
+
const fin = (_b = leg === null || leg === void 0 ? void 0 : leg.finalize()) !== null && _b !== void 0 ? _b : null;
|
|
3625
3866
|
const it = engine.getItem(z.target.id);
|
|
3626
3867
|
const before = split;
|
|
3627
3868
|
if (it)
|
|
@@ -3647,13 +3888,15 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
3647
3888
|
// ROOT DOCK or a free cell on the board: the leg sits where it will land.
|
|
3648
3889
|
if (zoneKey(zone) !== zoneKey(z))
|
|
3649
3890
|
applyZone(z, world);
|
|
3650
|
-
|
|
3891
|
+
hideOverlay(); // a re-applied zone repaints its preview; the release is not a preview
|
|
3892
|
+
plan.markDrop(null, null);
|
|
3893
|
+
const fin = (_c = leg === null || leg === void 0 ? void 0 : leg.finalize()) !== null && _c !== void 0 ? _c : null;
|
|
3651
3894
|
relockGroups();
|
|
3652
3895
|
if (!fin) {
|
|
3653
3896
|
done(false, 'cancel');
|
|
3654
3897
|
return;
|
|
3655
3898
|
}
|
|
3656
|
-
const planned = plan.commands(fin.cell, fin.rect, group.id);
|
|
3899
|
+
const planned = plan.commands(fin.cell, fin.rect, (_d = leg === null || leg === void 0 ? void 0 : leg.groupId) !== null && _d !== void 0 ? _d : group.id);
|
|
3657
3900
|
done(execute(z.kind === 'root' ? 'Dock tab' : 'Move tab out', [...fin.commands, ...groupCommands(fin), ...planned.move, ...planned.collapse]), 'commit');
|
|
3658
3901
|
};
|
|
3659
3902
|
const onUp = () => finish(true);
|
|
@@ -3858,12 +4101,33 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
3858
4101
|
});
|
|
3859
4102
|
/** Rebuild the engine from the members' persisted cells. `pack` runs gravity
|
|
3860
4103
|
* over the result (boot, float→off); a plain sync keeps the cells verbatim. */
|
|
4104
|
+
/**
|
|
4105
|
+
* A rebuild asked for while a gesture is live waits for the gesture. The
|
|
4106
|
+
* pointer's own crossing re-lays boards — adopting a widget into the Nested
|
|
4107
|
+
* page moved the inner tabs container, the tabs runtime re-placed its
|
|
4108
|
+
* pages, and the inner page's binder rebuilt from the model: its gesture
|
|
4109
|
+
* cancelled mid-drag, the dragged widget re-added, the ghost class gone
|
|
4110
|
+
* and the target's placeholder leaked through release and undo
|
|
4111
|
+
* (identification round, L5). The frame change itself is answered by
|
|
4112
|
+
* `onBoundsChanged` (a re-projection); the model is rebuilt afterwards.
|
|
4113
|
+
*/
|
|
4114
|
+
let rebuildDeferred = null;
|
|
4115
|
+
const flushDeferredRebuild = () => {
|
|
4116
|
+
if (!rebuildDeferred || gesture || slabGesture)
|
|
4117
|
+
return;
|
|
4118
|
+
const { pack } = rebuildDeferred;
|
|
4119
|
+
rebuildDeferred = null;
|
|
4120
|
+
rebuild(pack);
|
|
4121
|
+
};
|
|
3861
4122
|
const rebuild = (pack) => {
|
|
3862
4123
|
var _a, _b, _c, _d;
|
|
3863
4124
|
if (disposed)
|
|
3864
4125
|
return;
|
|
3865
|
-
if (gesture)
|
|
3866
|
-
|
|
4126
|
+
if (gesture || slabGesture) {
|
|
4127
|
+
rebuildDeferred = { pack: pack || (rebuildDeferred === null || rebuildDeferred === void 0 ? void 0 : rebuildDeferred.pack) === true };
|
|
4128
|
+
project();
|
|
4129
|
+
return;
|
|
4130
|
+
}
|
|
3867
4131
|
applyFluidFrame();
|
|
3868
4132
|
const items = [];
|
|
3869
4133
|
for (const id of (_a = group.members) !== null && _a !== void 0 ? _a : []) {
|
|
@@ -63,6 +63,9 @@ const CSS = `
|
|
|
63
63
|
z-index: 0;
|
|
64
64
|
transition: none;
|
|
65
65
|
}
|
|
66
|
+
/* A slab move asked for a cell it cannot have (a locked section in the way): the wanted cell in the danger tone. */
|
|
67
|
+
.grafloria-html-layer > .axdb-ph.axdb-ph--no { border-color: var(--axdb-danger, #b3123c); background: var(--axdb-danger-soft, rgba(179, 18, 60, .07)); }
|
|
68
|
+
|
|
66
69
|
@media (prefers-color-scheme: dark) {
|
|
67
70
|
.grafloria-html-layer > .axdb-ph { background: rgba(220, 225, 240, .12); border-color: rgba(220, 225, 240, .3); }
|
|
68
71
|
}
|