@grafloria/element 0.4.50 → 0.4.52
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 +2 -2
- package/src/lib/dashboard-kit/dashboard.d.ts +1 -1
- package/src/lib/dashboard-kit/grid-binder.d.ts +2 -2
- package/src/lib/dashboard-kit/grid-binder.js +171 -247
- package/src/lib/dashboard-kit/split-binder.js +41 -29
- package/src/lib/dashboard-kit/zones.d.ts +78 -0
- package/src/lib/dashboard-kit/zones.js +47 -6
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@grafloria/element",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.52",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./src/index.js",
|
|
6
6
|
"types": "./src/index.d.ts",
|
|
7
7
|
"peerDependencies": {
|
|
8
|
-
"@grafloria/engine": "^0.3.
|
|
8
|
+
"@grafloria/engine": "^0.3.9",
|
|
9
9
|
"@grafloria/renderer": "^0.4.0"
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
@@ -227,7 +227,7 @@ export interface DashboardOptions {
|
|
|
227
227
|
* so a designer/viewer pair is one flag apart. Live: `handle.setStatic()`.
|
|
228
228
|
*/
|
|
229
229
|
static?: boolean;
|
|
230
|
-
/** The deepest board a drop may enter (a view is 0; default
|
|
230
|
+
/** The deepest board a drop may enter (a view is 0; unbounded by default). See the grid binder's `nesting`. */
|
|
231
231
|
nesting?: number;
|
|
232
232
|
/**
|
|
233
233
|
* DRAG HANDLE — DevExpress drags an item by its caption. `true`: the caption
|
|
@@ -243,7 +243,7 @@ export interface DashboardGridOptions {
|
|
|
243
243
|
* counting a view as 0 — a widget into a section is 1, into a section
|
|
244
244
|
* inside a tab page is 2. Deeper containers are opaque to a drag: the
|
|
245
245
|
* pointer over them means a cell on their parent board (or beside them).
|
|
246
|
-
*
|
|
246
|
+
* Unbounded by default.
|
|
247
247
|
*/
|
|
248
248
|
nesting?: number;
|
|
249
249
|
/**
|
|
@@ -670,7 +670,7 @@ interface AdoptedLeg {
|
|
|
670
670
|
y: number;
|
|
671
671
|
w: number;
|
|
672
672
|
h: number;
|
|
673
|
-
}): boolean;
|
|
673
|
+
}, pushSolid?: boolean): boolean;
|
|
674
674
|
/** Every other tile's cell before the ghost entered — the layout a row insert translates. */
|
|
675
675
|
baseline(): Map<string, CellRect>;
|
|
676
676
|
/** Where the tile sits right now, or null while it is off the board. */
|
|
@@ -56,7 +56,7 @@ import { buildCommitCommands, cellFromGridItem, cellToRect, columnUnitFor, gridI
|
|
|
56
56
|
import { ensureDashboardKitStyles } from './styles.js';
|
|
57
57
|
import { captionOfGroup, captionPainted, captionPassThrough, captionKey, paintCaptionBand, sectionCaptionReserve, sizeCaptionBand } from './caption.js';
|
|
58
58
|
import { TAB_STRIP_HEIGHT } from './tabs.js';
|
|
59
|
-
import { BESIDE_BAND, resolve as resolveZone } from './zones.js';
|
|
59
|
+
import { BESIDE_BAND, resolve as resolveZone, resolveTabZone } from './zones.js';
|
|
60
60
|
/** The class of the painted grip element (a child of the node host). */
|
|
61
61
|
export const GRIP_CLASS = 'axdb-grip';
|
|
62
62
|
/** The container class that turns the caption strip's grip dots on. */
|
|
@@ -367,8 +367,8 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
367
367
|
const fluid = options.fluid === true;
|
|
368
368
|
const overflow = (_h = options.overflow) !== null && _h !== void 0 ? _h : 'bounded';
|
|
369
369
|
let isStatic = options.static === true;
|
|
370
|
-
/** The deepest board a drop may enter — a root is 0
|
|
371
|
-
const nesting = (_j = options.nesting) !== null && _j !== void 0 ? _j :
|
|
370
|
+
/** The deepest board a drop may enter — a root is 0. Unbounded unless asked for: three-level drops are in the specs today (tile first, step 2). */
|
|
371
|
+
const nesting = (_j = options.nesting) !== null && _j !== void 0 ? _j : Number.POSITIVE_INFINITY;
|
|
372
372
|
let dragHandle = normalizeDragHandle(options.dragHandle);
|
|
373
373
|
let dragSel = dragHandleSelector(dragHandle);
|
|
374
374
|
api.container.classList.toggle(DRAG_HANDLE_CLASS, dragHandle === true);
|
|
@@ -624,10 +624,14 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
624
624
|
const cell = grp
|
|
625
625
|
? cellFromGridItem((_g = grp.getMetadata) === null || _g === void 0 ? void 0 : _g.call(grp, 'gridItem'))
|
|
626
626
|
: null;
|
|
627
|
-
//
|
|
627
|
+
// A member group is a SOLID tile (tile first, step 3): a widget carried
|
|
628
|
+
// over it slides aside and it never packs — the board under the hand
|
|
629
|
+
// holds still, so its zones (into, a tab, beside) stay reachable — and
|
|
630
|
+
// it is moved by INTENT: placed beside, pushed by a moved section, a
|
|
631
|
+
// dock or a widget it refused, moved by its own gesture.
|
|
628
632
|
if (cell)
|
|
629
|
-
return Object.assign(Object.assign({ id }, cell), {
|
|
630
|
-
return { id, x: 0, y: 0, w: columns, h: 1,
|
|
633
|
+
return Object.assign(Object.assign({ id }, cell), { solid: true });
|
|
634
|
+
return { id, x: 0, y: 0, w: columns, h: 1, solid: true, autoPosition: true };
|
|
631
635
|
};
|
|
632
636
|
/** Persist adopted cells so save/undo round-trips them. */
|
|
633
637
|
const persistAdoptedCell = (id, item) => {
|
|
@@ -1288,7 +1292,13 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1288
1292
|
* carried along the top of a tall panel to its far right means "after it",
|
|
1289
1293
|
* not "above it". The shift is undone if the pointer leaves the band.
|
|
1290
1294
|
*/
|
|
1291
|
-
/**
|
|
1295
|
+
/**
|
|
1296
|
+
* The beside the hand holds (tile first, step 3): the container's cell and
|
|
1297
|
+
* frame AT REST (the resolve reads the held container at rest), the cell
|
|
1298
|
+
* the widget took, and the row it took it at. The shift itself is the
|
|
1299
|
+
* engine's `placeBeside`; the sections it pushed remember their cells and
|
|
1300
|
+
* come back by themselves when the widget leaves (S2).
|
|
1301
|
+
*/
|
|
1292
1302
|
let beside = null;
|
|
1293
1303
|
const endBeside = (restore) => {
|
|
1294
1304
|
if (!beside)
|
|
@@ -1296,28 +1306,22 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1296
1306
|
const it = engine.getItem(beside.id);
|
|
1297
1307
|
let moved = false;
|
|
1298
1308
|
if (it && restore && (it.x !== beside.from.x || it.y !== beside.from.y))
|
|
1299
|
-
moved = engine.moveCheck(beside.id, beside.from.x, beside.from.y, { gate: false }).changed
|
|
1300
|
-
if (restore) {
|
|
1301
|
-
// The sections the shift pushed come back with it.
|
|
1302
|
-
for (const [oid, c] of beside.others) {
|
|
1303
|
-
const o = engine.getItem(oid);
|
|
1304
|
-
if (o && (o.x !== c.x || o.y !== c.y))
|
|
1305
|
-
moved = engine.moveCheck(oid, c.x, c.y, { gate: false }).changed || moved;
|
|
1306
|
-
}
|
|
1307
|
-
}
|
|
1308
|
-
if (it)
|
|
1309
|
-
it.locked = true;
|
|
1310
|
-
relockOthersForSlab();
|
|
1309
|
+
moved = engine.moveCheck(beside.id, beside.from.x, beside.from.y, { gate: false }).changed;
|
|
1311
1310
|
beside = null;
|
|
1312
1311
|
// The frames the zone test reads are the MODEL's: a restore that moved
|
|
1313
1312
|
// the container without projecting left its frame where the push had
|
|
1314
|
-
// put it
|
|
1315
|
-
// 3440-px corner on 0.4.48: along the top band the panel was pushed
|
|
1316
|
-
// down, and the corner never turned into "right").
|
|
1313
|
+
// put it (the user's 3440-px corner on 0.4.48).
|
|
1317
1314
|
if (moved)
|
|
1318
1315
|
project();
|
|
1319
1316
|
};
|
|
1320
|
-
|
|
1317
|
+
/** The row of this board's grid under a world y — where a beside lands (D3: the pointer's row, not the container's top). */
|
|
1318
|
+
const rowOfPoint = (y) => {
|
|
1319
|
+
const r0 = cellToRect({ x: 0, y: 0, w: 1, h: 1 }, frame(), geom(), rows());
|
|
1320
|
+
const r1 = cellToRect({ x: 0, y: 1, w: 1, h: 1 }, frame(), geom(), rows());
|
|
1321
|
+
const pitch = Math.max(1, r1.y - r0.y);
|
|
1322
|
+
return Math.max(0, Math.floor((y - r0.y) / pitch));
|
|
1323
|
+
};
|
|
1324
|
+
const applyBeside = (g, z, row) => {
|
|
1321
1325
|
var _a;
|
|
1322
1326
|
const tc = engine.getItem(z.id);
|
|
1323
1327
|
if (!tc)
|
|
@@ -1326,71 +1330,26 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1326
1330
|
g.leg.adopted.abort();
|
|
1327
1331
|
g.leg = null;
|
|
1328
1332
|
}
|
|
1329
|
-
const w = g.spans.w;
|
|
1330
|
-
const h = g.spans.h;
|
|
1331
1333
|
if (g.removedFromBoard) {
|
|
1332
1334
|
g.removedFromBoard = false;
|
|
1333
1335
|
(_a = hostOf(g.id)) === null || _a === void 0 ? void 0 : _a.classList.remove('axdb-out');
|
|
1334
|
-
engine.add({ id: g.id, x: 0, y: engine.rows(), w, h });
|
|
1336
|
+
engine.add({ id: g.id, x: 0, y: engine.rows(), w: g.spans.w, h: g.spans.h });
|
|
1335
1337
|
}
|
|
1336
1338
|
if (beside && (beside.id !== z.id || beside.side !== z.side))
|
|
1337
|
-
endBeside(true); // another container, or another side of it:
|
|
1338
|
-
const from = beside ? beside.from : { x: tc.x, y: tc.y };
|
|
1339
|
-
let cell;
|
|
1340
|
-
let shiftTo = null;
|
|
1341
|
-
switch (z.side) {
|
|
1342
|
-
case 'right':
|
|
1343
|
-
cell = { x: from.x + tc.w, y: from.y };
|
|
1344
|
-
if (cell.x + w > columns) {
|
|
1345
|
-
shiftTo = { x: columns - w - tc.w, y: from.y };
|
|
1346
|
-
cell = { x: columns - w, y: from.y };
|
|
1347
|
-
}
|
|
1348
|
-
break;
|
|
1349
|
-
case 'left':
|
|
1350
|
-
cell = { x: from.x - w, y: from.y };
|
|
1351
|
-
if (cell.x < 0) {
|
|
1352
|
-
shiftTo = { x: w, y: from.y };
|
|
1353
|
-
cell = { x: 0, y: from.y };
|
|
1354
|
-
}
|
|
1355
|
-
break;
|
|
1356
|
-
case 'top':
|
|
1357
|
-
cell = { x: Math.max(0, Math.min(from.x, columns - w)), y: from.y }; // the ghost takes the container's rows; the container is pushed down under it
|
|
1358
|
-
break;
|
|
1359
|
-
default:
|
|
1360
|
-
cell = { x: Math.max(0, Math.min(from.x, columns - w)), y: from.y + tc.h };
|
|
1361
|
-
}
|
|
1362
|
-
if (shiftTo && shiftTo.x < 0)
|
|
1363
|
-
shiftTo = null; // no room even shifted: the widget goes where it can
|
|
1339
|
+
endBeside(true); // another container, or another side of it: from the rest layout
|
|
1364
1340
|
if (!beside) {
|
|
1365
|
-
// The other sections give way to the shift the way they give way to a
|
|
1366
|
-
// moved group (0.4.44): on the demo the Operations section spans the
|
|
1367
|
-
// row under the panel and a locked tile refused the shift outright.
|
|
1368
|
-
// Their cells are kept so they come back if the pointer leaves.
|
|
1369
|
-
const others = new Map();
|
|
1370
|
-
for (const o of engine.getItems())
|
|
1371
|
-
if (o.id !== z.id && o.id !== g.id && isGroupMember(o.id))
|
|
1372
|
-
others.set(o.id, { x: o.x, y: o.y });
|
|
1373
1341
|
const grp0 = diagram.getGroup(z.id);
|
|
1374
|
-
beside = { id: z.id, side: z.side, from, frame0: grp0 ? frameOfGroup(grp0) : cellToRect({ x:
|
|
1375
|
-
unlockOthersForSlab(z.id);
|
|
1342
|
+
beside = { id: z.id, side: z.side, from: { x: tc.x, y: tc.y }, frame0: grp0 ? frameOfGroup(grp0) : cellToRect({ x: tc.x, y: tc.y, w: tc.w, h: tc.h }, frame(), geom(), rows()), vacated: { x: tc.x, y: tc.y }, row };
|
|
1376
1343
|
}
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
if (shiftTo && (tc.x !== shiftTo.x || tc.y !== shiftTo.y)) {
|
|
1383
|
-
if (engine.getItem(g.id))
|
|
1384
|
-
engine.remove(g.id);
|
|
1385
|
-
engine.moveCheck(z.id, shiftTo.x, shiftTo.y, { gate: false });
|
|
1344
|
+
const r = engine.placeBeside(g.id, z.id, z.side, row);
|
|
1345
|
+
const at = engine.getItem(g.id);
|
|
1346
|
+
if (r.changed && at) {
|
|
1347
|
+
beside.vacated = { x: at.x, y: at.y };
|
|
1348
|
+
beside.row = row;
|
|
1386
1349
|
}
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
if (!engine.moveCheck(g.id, cell.x, cell.y, { gate: false }).changed) {
|
|
1391
|
-
const at = engine.getItem(g.id);
|
|
1392
|
-
if (!at || at.x !== cell.x || at.y !== cell.y)
|
|
1393
|
-
placeNear(g.id, cell.x, cell.y, w);
|
|
1350
|
+
else if (at) {
|
|
1351
|
+
// Refused (a bound): the widget goes where it can, as any refused cell does.
|
|
1352
|
+
placeNear(g.id, at.x, at.y, g.spans.w);
|
|
1394
1353
|
}
|
|
1395
1354
|
project();
|
|
1396
1355
|
};
|
|
@@ -1610,13 +1569,6 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1610
1569
|
},
|
|
1611
1570
|
move: false,
|
|
1612
1571
|
};
|
|
1613
|
-
// A TOP or LEFT edge moves the slab's origin, and the engine refuses to
|
|
1614
|
-
// move a locked tile: the move never landed, only the resize did, and a
|
|
1615
|
-
// section pulled up by its caption grew DOWNWARD a row per pointer step
|
|
1616
|
-
// — 18 rows for a 2-row pull (identification round, F16). Unlocked for
|
|
1617
|
-
// its own gesture, as a move is; relocked on release.
|
|
1618
|
-
if (edges.n || edges.w)
|
|
1619
|
-
it.locked = false;
|
|
1620
1572
|
capturePointer(slabGesture.pointerId);
|
|
1621
1573
|
api.container.style.cursor = cursorFor(edges);
|
|
1622
1574
|
};
|
|
@@ -1633,24 +1585,6 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1633
1585
|
* pushes it; a group's children ride along: the frame moves, the nested
|
|
1634
1586
|
* board re-projects. The others relock on release.
|
|
1635
1587
|
*/
|
|
1636
|
-
let slabUnlocked = [];
|
|
1637
|
-
const unlockOthersForSlab = (id) => {
|
|
1638
|
-
slabUnlocked = [];
|
|
1639
|
-
for (const o of engine.getItems()) {
|
|
1640
|
-
if (o.id !== id && o.locked && isGroupMember(o.id)) {
|
|
1641
|
-
o.locked = false;
|
|
1642
|
-
slabUnlocked.push(o.id);
|
|
1643
|
-
}
|
|
1644
|
-
}
|
|
1645
|
-
};
|
|
1646
|
-
const relockOthersForSlab = () => {
|
|
1647
|
-
for (const oid of slabUnlocked) {
|
|
1648
|
-
const o = engine.getItem(oid);
|
|
1649
|
-
if (o)
|
|
1650
|
-
o.locked = true;
|
|
1651
|
-
}
|
|
1652
|
-
slabUnlocked = [];
|
|
1653
|
-
};
|
|
1654
1588
|
const beginSlabMove = (id, ev) => {
|
|
1655
1589
|
const grp = diagram.getGroup(id);
|
|
1656
1590
|
const it = engine.getItem(id);
|
|
@@ -1658,8 +1592,6 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1658
1592
|
return;
|
|
1659
1593
|
engine.beginGesture();
|
|
1660
1594
|
const snap = snapshotAll();
|
|
1661
|
-
it.locked = false;
|
|
1662
|
-
unlockOthersForSlab(id);
|
|
1663
1595
|
slabGesture = {
|
|
1664
1596
|
id,
|
|
1665
1597
|
edges: NO_EDGES,
|
|
@@ -1676,11 +1608,6 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1676
1608
|
capturePointer(slabGesture.pointerId);
|
|
1677
1609
|
api.container.style.cursor = 'grabbing';
|
|
1678
1610
|
};
|
|
1679
|
-
const relockSlab = (id) => {
|
|
1680
|
-
const it = engine.getItem(id);
|
|
1681
|
-
if (it)
|
|
1682
|
-
it.locked = true;
|
|
1683
|
-
};
|
|
1684
1611
|
/** The cell a slab move asked for and could not have — painted so the refusal is visible; null clears it. */
|
|
1685
1612
|
let refusal = null;
|
|
1686
1613
|
const showRefusal = (cell, w, h) => {
|
|
@@ -1734,7 +1661,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1734
1661
|
// Along its ROW only: a section dragged LEFT that wandered DOWN its
|
|
1735
1662
|
// column (the row search) read as the wrong tile moving.
|
|
1736
1663
|
const was = { x: it.x, y: it.y };
|
|
1737
|
-
const moved = engine.moveCheck(g.id, cell.x, cell.y, { gate: false }).changed || placeOnRow(g.id, cell.x, cell.y, it.w);
|
|
1664
|
+
const moved = engine.moveCheck(g.id, cell.x, cell.y, { gate: false, pushSolid: true }).changed || placeOnRow(g.id, cell.x, cell.y, it.w, true); // a moved section pushes the sections in its way (0.4.44)
|
|
1738
1665
|
if (moved) {
|
|
1739
1666
|
project();
|
|
1740
1667
|
setCarried(g.id, true); // chrome repainted by the projection is carried too
|
|
@@ -1781,7 +1708,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1781
1708
|
}
|
|
1782
1709
|
let changed = false;
|
|
1783
1710
|
if (x !== it.x || y !== it.y)
|
|
1784
|
-
changed = engine.moveCheck(g.id, x, y, { gate: false }).changed || changed;
|
|
1711
|
+
changed = engine.moveCheck(g.id, x, y, { gate: false, pushSolid: true }).changed || changed;
|
|
1785
1712
|
if (w !== it.w || h !== it.h)
|
|
1786
1713
|
changed = engine.resizeCheck(g.id, w, h).changed || changed;
|
|
1787
1714
|
if (changed)
|
|
@@ -1796,8 +1723,6 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1796
1723
|
showRefusal(null, 0, 0);
|
|
1797
1724
|
releasePointer(g.pointerId);
|
|
1798
1725
|
api.container.style.cursor = '';
|
|
1799
|
-
relockSlab(g.id);
|
|
1800
|
-
relockOthersForSlab();
|
|
1801
1726
|
if (g.move && g.started)
|
|
1802
1727
|
setCarried(g.id, false); // exempt through the drop write, then the glides resume
|
|
1803
1728
|
if (!g.started) {
|
|
@@ -1833,8 +1758,6 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1833
1758
|
slabGesture = null;
|
|
1834
1759
|
releasePointer(g.pointerId);
|
|
1835
1760
|
api.container.style.cursor = '';
|
|
1836
|
-
relockSlab(g.id);
|
|
1837
|
-
relockOthersForSlab();
|
|
1838
1761
|
if (g.move && g.started)
|
|
1839
1762
|
setCarried(g.id, false);
|
|
1840
1763
|
if (g.started)
|
|
@@ -1953,7 +1876,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1953
1876
|
(_a = options.onGesture) === null || _a === void 0 ? void 0 : _a.call(options, { type: 'commit', kind: g.kind, nodeId: g.id, changed });
|
|
1954
1877
|
};
|
|
1955
1878
|
const cancelActiveGesture = (notify = true) => {
|
|
1956
|
-
var _a, _b, _c
|
|
1879
|
+
var _a, _b, _c;
|
|
1957
1880
|
if (gesture === null || gesture === void 0 ? void 0 : gesture.strip) {
|
|
1958
1881
|
(_a = options.tabDrop) === null || _a === void 0 ? void 0 : _a.markDrop(null, null);
|
|
1959
1882
|
gesture.strip = null;
|
|
@@ -1978,21 +1901,10 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1978
1901
|
}
|
|
1979
1902
|
endBeside(true);
|
|
1980
1903
|
if (g.started) {
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
engine.endGesture();
|
|
1986
|
-
const items = [];
|
|
1987
|
-
for (const [id, c] of g.startCells) {
|
|
1988
|
-
const lockedNode = ((_d = (_c = diagram.getNode(id)) === null || _c === void 0 ? void 0 : _c.state) === null || _d === void 0 ? void 0 : _d.locked) === true;
|
|
1989
|
-
items.push(Object.assign(Object.assign({ id }, c), { locked: lockedNode || isGroupMember(id) }));
|
|
1990
|
-
}
|
|
1991
|
-
engine = engineFrom(items);
|
|
1992
|
-
}
|
|
1993
|
-
else {
|
|
1994
|
-
engine.cancelGesture();
|
|
1995
|
-
}
|
|
1904
|
+
// The engine's gesture snapshot restores cells, sizes AND membership
|
|
1905
|
+
// (engine 0.3.8): a ghost removed mid-gesture comes back at its start
|
|
1906
|
+
// cell, a palette tile added mid-gesture is gone.
|
|
1907
|
+
engine.cancelGesture();
|
|
1996
1908
|
if (designRows !== undefined) {
|
|
1997
1909
|
maxRows = liveBound(engine.getItems());
|
|
1998
1910
|
engine.maxRows = maxRows;
|
|
@@ -2033,7 +1945,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2033
1945
|
enforceBoardHeight();
|
|
2034
1946
|
api.renderNow();
|
|
2035
1947
|
if (notify) {
|
|
2036
|
-
(
|
|
1948
|
+
(_c = options.onGesture) === null || _c === void 0 ? void 0 : _c.call(options, { type: 'cancel', kind: g.kind, nodeId: g.id, changed: false });
|
|
2037
1949
|
}
|
|
2038
1950
|
};
|
|
2039
1951
|
/** Centre a w×h-span tile's top-left under the cursor, in world px. */
|
|
@@ -2048,7 +1960,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2048
1960
|
};
|
|
2049
1961
|
};
|
|
2050
1962
|
const onToolMove = (ev) => {
|
|
2051
|
-
var _a, _b, _c, _d, _e, _f, _g, _h
|
|
1963
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
2052
1964
|
const g = gesture;
|
|
2053
1965
|
if (!g || g.kind === 'palette')
|
|
2054
1966
|
return;
|
|
@@ -2103,8 +2015,9 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2103
2015
|
// (A container on another board still resolves as a plain cell there
|
|
2104
2016
|
// until the gesture scope spans boards — step 3.)
|
|
2105
2017
|
if (z.kind === 'beside' && !isStatic && z.board.ref === selfPeer) {
|
|
2106
|
-
|
|
2107
|
-
|
|
2018
|
+
const row = rowOfPoint(ev.world.y);
|
|
2019
|
+
if (!z.kept || !beside || beside.row !== row)
|
|
2020
|
+
applyBeside(g, { id: z.containerId, side: z.side }, row);
|
|
2108
2021
|
syncPlaceholder();
|
|
2109
2022
|
return;
|
|
2110
2023
|
}
|
|
@@ -2115,7 +2028,13 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2115
2028
|
const onSelf = z.kind !== 'off' && z.kind !== 'strip' && z.board.ref === selfPeer;
|
|
2116
2029
|
const inside = onSelf || (z.kind === 'off' && worldInsideBoardGrace(ev.world.x, ev.world.y));
|
|
2117
2030
|
const peer = !onSelf && z.kind !== 'off' && z.kind !== 'strip' ? z.board.ref : null;
|
|
2118
|
-
if (peer) {
|
|
2031
|
+
if (peer && g.refusedPeer === peer && parentPeerOf(api.container, peer.group.id) === selfPeer) {
|
|
2032
|
+
// -- REFUSED, PUSHED (D2): a section of this board that cannot take
|
|
2033
|
+
// the widget is pushed by it instead — the widget takes the cell
|
|
2034
|
+
// under the hand on THIS board, and means it.
|
|
2035
|
+
placeOnSelf(g, desired, true);
|
|
2036
|
+
}
|
|
2037
|
+
else if (peer) {
|
|
2119
2038
|
// -- HANDOFF: the pointer is over another board -------------------
|
|
2120
2039
|
if (g.leg && g.leg.peer === peer) {
|
|
2121
2040
|
g.leg.adopted.move(ev.world);
|
|
@@ -2135,38 +2054,31 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2135
2054
|
height: g.node.size.height,
|
|
2136
2055
|
});
|
|
2137
2056
|
if (adopted) {
|
|
2057
|
+
g.refusedPeer = null;
|
|
2138
2058
|
(_c = hostOf(g.id)) === null || _c === void 0 ? void 0 : _c.classList.remove('axdb-out');
|
|
2139
2059
|
g.leg = { peer, adopted };
|
|
2140
2060
|
g.leg.adopted.move(ev.world);
|
|
2141
2061
|
}
|
|
2062
|
+
else if (parentPeerOf(api.container, peer.group.id) === selfPeer) {
|
|
2063
|
+
// A section of this board refused (full, fit): the widget pushes it (D2).
|
|
2064
|
+
g.refusedPeer = peer;
|
|
2065
|
+
placeOnSelf(g, desired, true);
|
|
2066
|
+
}
|
|
2142
2067
|
else {
|
|
2143
|
-
//
|
|
2068
|
+
// A board deeper down refused the adoption: dim = will snap home (the N-board scope is step 4).
|
|
2069
|
+
g.refusedPeer = peer;
|
|
2144
2070
|
(_d = hostOf(g.id)) === null || _d === void 0 ? void 0 : _d.classList.add('axdb-out');
|
|
2145
2071
|
}
|
|
2146
2072
|
}
|
|
2147
2073
|
}
|
|
2148
2074
|
else if (inside) {
|
|
2149
2075
|
// -- back on (or still on) our own board --------------------------
|
|
2076
|
+
g.refusedPeer = null;
|
|
2150
2077
|
if (g.leg) {
|
|
2151
2078
|
g.leg.adopted.abort();
|
|
2152
2079
|
g.leg = null;
|
|
2153
2080
|
}
|
|
2154
|
-
|
|
2155
|
-
g.removedFromBoard = false;
|
|
2156
|
-
(_e = hostOf(g.id)) === null || _e === void 0 ? void 0 : _e.classList.remove('axdb-out');
|
|
2157
|
-
const cell = pointToCell(desired.x, desired.y, frame(), geom(), rows(), g.spans.w);
|
|
2158
|
-
// Re-enter at the bottom edge (collision-free), then take the cursor
|
|
2159
|
-
// cell GATELESSLY — a first placement skips the anti-jitter gate.
|
|
2160
|
-
engine.add({ id: g.id, x: 0, y: engine.rows(), w: g.spans.w, h: g.spans.h });
|
|
2161
|
-
engine.moveCheck(g.id, cell.x, cell.y, { gate: false });
|
|
2162
|
-
project();
|
|
2163
|
-
}
|
|
2164
|
-
else {
|
|
2165
|
-
const spanW = (_g = (_f = engine.getItem(g.id)) === null || _f === void 0 ? void 0 : _f.w) !== null && _g !== void 0 ? _g : g.spans.w;
|
|
2166
|
-
const cell = pointToCell(desired.x, desired.y, frame(), geom(), rows(), spanW);
|
|
2167
|
-
if (engine.moveCheck(g.id, cell.x, cell.y).changed)
|
|
2168
|
-
project();
|
|
2169
|
-
}
|
|
2081
|
+
placeOnSelf(g, desired);
|
|
2170
2082
|
}
|
|
2171
2083
|
else {
|
|
2172
2084
|
// -- outside every board ------------------------------------------
|
|
@@ -2177,7 +2089,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2177
2089
|
if (!g.removedFromBoard) {
|
|
2178
2090
|
g.removedFromBoard = true;
|
|
2179
2091
|
engine.remove(g.id); // survivors settle home; cells minted nowhere
|
|
2180
|
-
(
|
|
2092
|
+
(_e = hostOf(g.id)) === null || _e === void 0 ? void 0 : _e.classList.add('axdb-out');
|
|
2181
2093
|
project();
|
|
2182
2094
|
}
|
|
2183
2095
|
}
|
|
@@ -2287,7 +2199,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2287
2199
|
maxRows = n;
|
|
2288
2200
|
engine.maxRows = n;
|
|
2289
2201
|
};
|
|
2290
|
-
const slabRows = (
|
|
2202
|
+
const slabRows = (_g = (_f = parent.memberCell(group.id)) === null || _f === void 0 ? void 0 : _f.h) !== null && _g !== void 0 ? _g : maxRows;
|
|
2291
2203
|
const inner = maxRows;
|
|
2292
2204
|
const rhNow = rowHeightFor(geom(), rows());
|
|
2293
2205
|
const rowPx = rhNow + gap;
|
|
@@ -2451,7 +2363,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2451
2363
|
const anchorBottom = liveRect ? liveRect.y + liveRect.height : bottom;
|
|
2452
2364
|
const px = E.w ? anchorRight - w : anchorLeft;
|
|
2453
2365
|
const py = E.n ? anchorBottom - h : anchorTop;
|
|
2454
|
-
g.node.setSize(w, h, (
|
|
2366
|
+
g.node.setSize(w, h, (_h = g.node.size.depth) !== null && _h !== void 0 ? _h : 0);
|
|
2455
2367
|
g.node.setPosition(px, py);
|
|
2456
2368
|
ghostStyleFastPath(g, { x: px, y: py, width: w, height: h });
|
|
2457
2369
|
const spanF = bound() !== undefined ? frame() : f;
|
|
@@ -2695,6 +2607,25 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2695
2607
|
});
|
|
2696
2608
|
return peers.filter((p) => !p.group.parentGroupId).map((p) => boardRef(p, 0));
|
|
2697
2609
|
};
|
|
2610
|
+
/** The ghost takes the cell under the hand on THIS board: re-entering at the bottom edge first (collision-free), then gatelessly; a tile already here moves through the gate. */
|
|
2611
|
+
const placeOnSelf = (g, desired, pushSolid = false) => {
|
|
2612
|
+
var _a, _b, _c;
|
|
2613
|
+
if (g.removedFromBoard) {
|
|
2614
|
+
g.removedFromBoard = false;
|
|
2615
|
+
(_a = hostOf(g.id)) === null || _a === void 0 ? void 0 : _a.classList.remove('axdb-out');
|
|
2616
|
+
const cell = pointToCell(desired.x, desired.y, frame(), geom(), rows(), g.spans.w);
|
|
2617
|
+
engine.add({ id: g.id, x: 0, y: engine.rows(), w: g.spans.w, h: g.spans.h });
|
|
2618
|
+
if (!engine.moveCheck(g.id, cell.x, cell.y, { gate: false, pushSolid }).changed)
|
|
2619
|
+
placeNear(g.id, cell.x, cell.y, g.spans.w, pushSolid);
|
|
2620
|
+
project();
|
|
2621
|
+
}
|
|
2622
|
+
else {
|
|
2623
|
+
const spanW = (_c = (_b = engine.getItem(g.id)) === null || _b === void 0 ? void 0 : _b.w) !== null && _c !== void 0 ? _c : g.spans.w;
|
|
2624
|
+
const cell = pointToCell(desired.x, desired.y, frame(), geom(), rows(), spanW);
|
|
2625
|
+
if (engine.moveCheck(g.id, cell.x, cell.y, { pushSolid }).changed)
|
|
2626
|
+
project();
|
|
2627
|
+
}
|
|
2628
|
+
};
|
|
2698
2629
|
/** What the pointer means for the dragged tile: the resolve over the live tree, with the beside the hand holds. */
|
|
2699
2630
|
const resolveTileZone = (g, ev) => {
|
|
2700
2631
|
let strip = null;
|
|
@@ -2712,8 +2643,19 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2712
2643
|
ghostDepth: 0,
|
|
2713
2644
|
ghostSubtree: EMPTY_SUBTREE,
|
|
2714
2645
|
gap,
|
|
2646
|
+
homeChain: homeChain(),
|
|
2715
2647
|
});
|
|
2716
2648
|
};
|
|
2649
|
+
/** The groups this board sits in, all the way up: their bands never apply to a tile of this board. */
|
|
2650
|
+
const homeChain = () => {
|
|
2651
|
+
const out = new Set();
|
|
2652
|
+
let cur = group;
|
|
2653
|
+
for (let i = 0; cur && i < 32; i++) {
|
|
2654
|
+
out.add(cur.id);
|
|
2655
|
+
cur = cur.parentGroupId ? diagram.getGroup(cur.parentGroupId) : undefined;
|
|
2656
|
+
}
|
|
2657
|
+
return out;
|
|
2658
|
+
};
|
|
2717
2659
|
/** The board whose engine holds OUR group as an item (nesting parent). */
|
|
2718
2660
|
const parentPeer = () => {
|
|
2719
2661
|
for (const p of peersOnCanvas()) {
|
|
@@ -2756,7 +2698,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2756
2698
|
* wherever the last swing left it. Ask where the tile IS, not whether the
|
|
2757
2699
|
* call moved it.
|
|
2758
2700
|
*/
|
|
2759
|
-
const placeOnRow = (id, x, y, w) => {
|
|
2701
|
+
const placeOnRow = (id, x, y, w, pushSolid = false) => {
|
|
2760
2702
|
const at = (cx) => {
|
|
2761
2703
|
const i = engine.getItem(id);
|
|
2762
2704
|
return !!i && i.x === cx && i.y === y;
|
|
@@ -2764,7 +2706,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2764
2706
|
const maxX = Math.max(0, columns - w);
|
|
2765
2707
|
if (at(x))
|
|
2766
2708
|
return true;
|
|
2767
|
-
if (engine.moveCheck(id, x, y, { gate: false }).changed)
|
|
2709
|
+
if (engine.moveCheck(id, x, y, { gate: false, pushSolid }).changed)
|
|
2768
2710
|
return true;
|
|
2769
2711
|
for (let d = 1; d <= columns; d++) {
|
|
2770
2712
|
for (const cx of [x - d, x + d]) {
|
|
@@ -2772,14 +2714,14 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2772
2714
|
continue;
|
|
2773
2715
|
if (at(cx))
|
|
2774
2716
|
return true;
|
|
2775
|
-
if (engine.moveCheck(id, cx, y, { gate: false }).changed)
|
|
2717
|
+
if (engine.moveCheck(id, cx, y, { gate: false, pushSolid }).changed)
|
|
2776
2718
|
return true;
|
|
2777
2719
|
}
|
|
2778
2720
|
}
|
|
2779
2721
|
return false;
|
|
2780
2722
|
};
|
|
2781
|
-
const placeNear = (id, x, y, w) => {
|
|
2782
|
-
if (placeOnRow(id, x, y, w))
|
|
2723
|
+
const placeNear = (id, x, y, w, pushSolid = false) => {
|
|
2724
|
+
if (placeOnRow(id, x, y, w, pushSolid))
|
|
2783
2725
|
return true;
|
|
2784
2726
|
// EVERY column on that row refused — which is what a locked section
|
|
2785
2727
|
// spanning the full width does, and there are plenty of those. Sliding
|
|
@@ -2791,7 +2733,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2791
2733
|
for (const cy of [y - d, y + d]) {
|
|
2792
2734
|
if (cy < 0)
|
|
2793
2735
|
continue;
|
|
2794
|
-
if (placeOnRow(id, x, cy, w))
|
|
2736
|
+
if (placeOnRow(id, x, cy, w, pushSolid))
|
|
2795
2737
|
return true;
|
|
2796
2738
|
}
|
|
2797
2739
|
}
|
|
@@ -2806,7 +2748,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2806
2748
|
const fitHeightAt = (id, x, y, w, hNatural) => {
|
|
2807
2749
|
let limit = hNatural;
|
|
2808
2750
|
for (const it of engine.getItems()) {
|
|
2809
|
-
if (it.id === id || !it.locked)
|
|
2751
|
+
if (it.id === id || !(it.locked || it.solid))
|
|
2810
2752
|
continue;
|
|
2811
2753
|
if (!(it.x < x + w && x < it.x + it.w))
|
|
2812
2754
|
continue;
|
|
@@ -2935,7 +2877,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2935
2877
|
const it = engine.getItem(node.id);
|
|
2936
2878
|
return it ? cellToRect(it, frame(), geom(), rows()) : null;
|
|
2937
2879
|
},
|
|
2938
|
-
place: (cell) => {
|
|
2880
|
+
place: (cell, pushSolid = false) => {
|
|
2939
2881
|
if (!engine.getItem(node.id) && !engine.add({ id: node.id, x: 0, y: engine.rows(), w: cell.w, h: cell.h }))
|
|
2940
2882
|
return false;
|
|
2941
2883
|
const it = engine.getItem(node.id);
|
|
@@ -2952,10 +2894,10 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2952
2894
|
engine.resizeCheck(node.id, w0, h0);
|
|
2953
2895
|
const moved = engine.getItem(node.id);
|
|
2954
2896
|
if (moved && (moved.x !== cell.x || moved.y !== cell.y))
|
|
2955
|
-
engine.moveCheck(node.id, cell.x, cell.y, { gate: false });
|
|
2897
|
+
engine.moveCheck(node.id, cell.x, cell.y, { gate: false, pushSolid });
|
|
2956
2898
|
const now = engine.getItem(node.id);
|
|
2957
2899
|
if (now && (now.w !== cell.w || now.h !== cell.h))
|
|
2958
|
-
engine.resizeCheck(node.id, cell.w, cell.h);
|
|
2900
|
+
engine.resizeCheck(node.id, cell.w, cell.h, { pushSolid });
|
|
2959
2901
|
lastWant = null;
|
|
2960
2902
|
project();
|
|
2961
2903
|
syncPlaceholder();
|
|
@@ -3836,7 +3778,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
3836
3778
|
// thirds, and the user, putting a page back into the panel it came from,
|
|
3837
3779
|
// never found the "into": "close to the edge means beside it, the content
|
|
3838
3780
|
// area means inside, the header means a new tab" (0.4.42).
|
|
3839
|
-
const EDGE_BAND =
|
|
3781
|
+
const EDGE_BAND = BESIDE_BAND; // one band for a tab's split and a widget's beside (zones.ts)
|
|
3840
3782
|
const visibleFrame = () => {
|
|
3841
3783
|
const rect = api.container.getBoundingClientRect();
|
|
3842
3784
|
const o = toWorld(rect.left, rect.top);
|
|
@@ -3954,47 +3896,55 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
3954
3896
|
const zoneAt = (cx, cy, world) => {
|
|
3955
3897
|
// A pointer OUTSIDE the visible canvas is off, full stop: a camera that
|
|
3956
3898
|
// moved can map it to a world point inside a group, and that group
|
|
3957
|
-
// took a join or a split from a release in the page header.
|
|
3958
|
-
|
|
3959
|
-
|
|
3960
|
-
const target = targetAt(world.x, world.y);
|
|
3961
|
-
const
|
|
3962
|
-
// A strip is the most precise target there is: anyone's wins outright.
|
|
3963
|
-
if (target) {
|
|
3964
|
-
const idx = plan.stripIndex(target.id, cx, cy);
|
|
3965
|
-
if (idx !== null)
|
|
3966
|
-
return { kind: 'strip', target, index: idx };
|
|
3967
|
-
}
|
|
3968
|
-
else if (home) {
|
|
3969
|
-
const idx = plan.stripIndex(fromGroupId, cx, cy);
|
|
3970
|
-
if (idx !== null)
|
|
3971
|
-
return { kind: 'reorder', index: idx };
|
|
3972
|
-
}
|
|
3973
|
-
// The board's own edges beat whatever sits against them — Dockview's
|
|
3974
|
-
// container edges over its groups — so a group at the top of the board
|
|
3975
|
-
// still leaves the top band to the board.
|
|
3976
|
-
const root = rootAt(cx, cy);
|
|
3977
|
-
if (root)
|
|
3978
|
-
return root;
|
|
3979
|
-
if (target) {
|
|
3980
|
-
const f = anchor && anchor.g === target ? anchor.frame : frameOfGroup(target);
|
|
3981
|
-
const stripH = anchor && anchor.g === target ? anchor.stripH : plan.stripHeight(target.id);
|
|
3982
|
-
const bodyH = Math.max(1, f.height - stripH);
|
|
3983
|
-
const rx = Math.min(1, Math.max(0, (world.x - f.x) / Math.max(1, f.width)));
|
|
3984
|
-
const ry = Math.min(1, Math.max(0, (world.y - f.y - stripH) / bodyH));
|
|
3985
|
-
if (rx >= EDGE_BAND && rx <= 1 - EDGE_BAND && ry >= EDGE_BAND && ry <= 1 - EDGE_BAND)
|
|
3986
|
-
return { kind: 'join', target };
|
|
3987
|
-
const d = [['left', rx], ['right', 1 - rx], ['top', ry], ['bottom', 1 - ry]];
|
|
3988
|
-
d.sort((p, q) => p[1] - q[1]);
|
|
3989
|
-
const h = halves(target, d[0][0]);
|
|
3990
|
-
return h ? Object.assign({ kind: 'split', target, side: d[0][0] }, h) : { kind: 'join', target };
|
|
3991
|
-
}
|
|
3992
|
-
if (home)
|
|
3993
|
-
return { kind: 'home' };
|
|
3899
|
+
// took a join or a split from a release in the page header. The
|
|
3900
|
+
// target is looked up (and anchored) only inside it.
|
|
3901
|
+
const clientInside = clientInsideCanvasGrace(cx, cy);
|
|
3902
|
+
const target = clientInside ? targetAt(world.x, world.y) : null;
|
|
3903
|
+
const root = clientInside ? rootAt(cx, cy) : null;
|
|
3994
3904
|
const foreign = foreignAt(world.x, world.y);
|
|
3995
|
-
|
|
3996
|
-
|
|
3997
|
-
|
|
3905
|
+
// The ORDER is zones.ts's, shared with the split board: a strip, the
|
|
3906
|
+
// board's own edges, the target's fifth or middle, home, then a board.
|
|
3907
|
+
const tz = resolveTabZone({
|
|
3908
|
+
x: world.x,
|
|
3909
|
+
y: world.y,
|
|
3910
|
+
clientInside,
|
|
3911
|
+
ownStrip: plan.stripIndex(fromGroupId, cx, cy),
|
|
3912
|
+
stripOf: (id) => plan.stripIndex(id, cx, cy),
|
|
3913
|
+
root: root && root.kind === 'root' ? { side: root.side } : null,
|
|
3914
|
+
target: target
|
|
3915
|
+
? {
|
|
3916
|
+
id: target.id,
|
|
3917
|
+
frame: anchor && anchor.g === target ? anchor.frame : frameOfGroup(target),
|
|
3918
|
+
stripHeight: anchor && anchor.g === target ? anchor.stripH : plan.stripHeight(target.id),
|
|
3919
|
+
}
|
|
3920
|
+
: null,
|
|
3921
|
+
home: worldInsideGroup(from, world.x, world.y) ? frameOfGroup(from) : null,
|
|
3922
|
+
homeBand: 0, // on a grid board the whole source frame is home
|
|
3923
|
+
band: EDGE_BAND,
|
|
3924
|
+
canSplit: (_id, side) => !!target && !!halves(target, side),
|
|
3925
|
+
pane: false,
|
|
3926
|
+
foreign: !!foreign && (!worldInsideBoard(world.x, world.y) || foreign.frameArea() < boardArea()),
|
|
3927
|
+
});
|
|
3928
|
+
switch (tz.kind) {
|
|
3929
|
+
case 'off':
|
|
3930
|
+
return { kind: 'off' };
|
|
3931
|
+
case 'strip':
|
|
3932
|
+
return { kind: 'strip', target: target, index: tz.index };
|
|
3933
|
+
case 'reorder':
|
|
3934
|
+
return { kind: 'reorder', index: tz.index };
|
|
3935
|
+
case 'root':
|
|
3936
|
+
return root;
|
|
3937
|
+
case 'join':
|
|
3938
|
+
return { kind: 'join', target: target };
|
|
3939
|
+
case 'split': {
|
|
3940
|
+
const h = halves(target, tz.side);
|
|
3941
|
+
return h ? Object.assign({ kind: 'split', target: target, side: tz.side }, h) : { kind: 'join', target: target };
|
|
3942
|
+
}
|
|
3943
|
+
case 'home':
|
|
3944
|
+
return { kind: 'home' };
|
|
3945
|
+
default:
|
|
3946
|
+
return tz.kind === 'board' && tz.foreign && foreign ? { kind: 'board', peer: foreign } : { kind: 'board' };
|
|
3947
|
+
}
|
|
3998
3948
|
};
|
|
3999
3949
|
const zoneKey = (z) => JSON.stringify(z, (k, v) => (k === 'target' ? v.id : k === 'peer' ? v.group.id : v));
|
|
4000
3950
|
// DOCKING PUSHES SECTIONS. A section is a locked tile so that a widget
|
|
@@ -4003,25 +3953,6 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
4003
3953
|
// preview every member group is unlocked; they are relocked when the
|
|
4004
3954
|
// pointer leaves the band, and their moved cells are committed with the
|
|
4005
3955
|
// dock.
|
|
4006
|
-
let unlockedGroups = [];
|
|
4007
|
-
const unlockGroups = () => {
|
|
4008
|
-
if (unlockedGroups.length > 0)
|
|
4009
|
-
return;
|
|
4010
|
-
for (const it of engine.getItems()) {
|
|
4011
|
-
if (it.id !== plan.arrivingId && it.locked && isGroupMember(it.id)) {
|
|
4012
|
-
it.locked = false;
|
|
4013
|
-
unlockedGroups.push(it.id);
|
|
4014
|
-
}
|
|
4015
|
-
}
|
|
4016
|
-
};
|
|
4017
|
-
const relockGroups = () => {
|
|
4018
|
-
for (const id of unlockedGroups) {
|
|
4019
|
-
const it = engine.getItem(id);
|
|
4020
|
-
if (it)
|
|
4021
|
-
it.locked = true;
|
|
4022
|
-
}
|
|
4023
|
-
unlockedGroups = [];
|
|
4024
|
-
};
|
|
4025
3956
|
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));
|
|
4026
3957
|
// A TOP DOCK INSERTS ROWS. The engine's push cascade resolves the band's
|
|
4027
3958
|
// collisions one tile at a time and scrambles what stood beneath it (the
|
|
@@ -4089,18 +4020,24 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
4089
4020
|
engine.moveCheck(split.id, split.before.x, split.before.y, { gate: false });
|
|
4090
4021
|
if (it.w !== split.before.w || it.h !== split.before.h)
|
|
4091
4022
|
engine.resizeCheck(split.id, split.before.w, split.before.h);
|
|
4092
|
-
it.locked = true;
|
|
4093
4023
|
}
|
|
4094
4024
|
split = null;
|
|
4095
4025
|
project();
|
|
4096
4026
|
};
|
|
4097
4027
|
const previewSplit = (z, world) => {
|
|
4098
4028
|
const it = engine.getItem(z.target.id);
|
|
4029
|
+
if (!it)
|
|
4030
|
+
return false;
|
|
4031
|
+
// The target's cell AT REST is the one the commit restores — read it
|
|
4032
|
+
// BEFORE the leg enters: the arriving ghost is placed under the pointer
|
|
4033
|
+
// first, and a target that is a tile like any other is pushed by it
|
|
4034
|
+
// for a moment (tile first, step 3) until it takes its half below.
|
|
4035
|
+
const before = { x: it.x, y: it.y, w: it.w, h: it.h };
|
|
4036
|
+
const frameBefore = frameOfGroup(z.target);
|
|
4099
4037
|
const l = ensureLeg(world, null);
|
|
4100
|
-
if (!
|
|
4038
|
+
if (!l)
|
|
4101
4039
|
return false;
|
|
4102
|
-
split = { id: z.target.id, before
|
|
4103
|
-
it.locked = false; // its own gesture for the moment: it may shrink and shift
|
|
4040
|
+
split = { id: z.target.id, before, frameBefore, keep: z.keep };
|
|
4104
4041
|
if (it.w !== z.keep.w || it.h !== z.keep.h)
|
|
4105
4042
|
engine.resizeCheck(z.target.id, z.keep.w, z.keep.h);
|
|
4106
4043
|
const now = engine.getItem(z.target.id);
|
|
@@ -4122,8 +4059,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
4122
4059
|
const l = ensureLeg(world, null);
|
|
4123
4060
|
if (!l)
|
|
4124
4061
|
return false;
|
|
4125
|
-
|
|
4126
|
-
l.place(z.cell);
|
|
4062
|
+
l.place(z.cell, true); // DOCKING PUSHES SECTIONS: everything below the band moves down, solid or not
|
|
4127
4063
|
insertRows(z.cell, l);
|
|
4128
4064
|
project();
|
|
4129
4065
|
placeholder === null || placeholder === void 0 ? void 0 : placeholder.remove();
|
|
@@ -4145,7 +4081,6 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
4145
4081
|
}
|
|
4146
4082
|
undoSplitPreview();
|
|
4147
4083
|
undoInsertRows();
|
|
4148
|
-
relockGroups();
|
|
4149
4084
|
plan.markDrop(null, null);
|
|
4150
4085
|
hideOverlay();
|
|
4151
4086
|
// A PREVIEW IS AN OVERLAY. A split and a dock used to be applied LIVE
|
|
@@ -4233,7 +4168,6 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
4233
4168
|
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()))) {
|
|
4234
4169
|
undoSplitPreview();
|
|
4235
4170
|
leg === null || leg === void 0 ? void 0 : leg.abort();
|
|
4236
|
-
relockGroups();
|
|
4237
4171
|
done(false, 'cancel');
|
|
4238
4172
|
return;
|
|
4239
4173
|
}
|
|
@@ -4272,8 +4206,6 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
4272
4206
|
const fin = (_b = leg === null || leg === void 0 ? void 0 : leg.finalize()) !== null && _b !== void 0 ? _b : null;
|
|
4273
4207
|
const it = engine.getItem(z.target.id);
|
|
4274
4208
|
const before = split;
|
|
4275
|
-
if (it)
|
|
4276
|
-
it.locked = true;
|
|
4277
4209
|
split = null;
|
|
4278
4210
|
if (!fin || !before || !it) {
|
|
4279
4211
|
leg === null || leg === void 0 ? void 0 : leg.abort();
|
|
@@ -4301,7 +4233,6 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
4301
4233
|
hideOverlay(); // a re-applied zone repaints its preview; the release is not a preview
|
|
4302
4234
|
plan.markDrop(null, null);
|
|
4303
4235
|
const fin = (_c = leg === null || leg === void 0 ? void 0 : leg.finalize()) !== null && _c !== void 0 ? _c : null;
|
|
4304
|
-
relockGroups();
|
|
4305
4236
|
if (!fin) {
|
|
4306
4237
|
done(false, 'cancel');
|
|
4307
4238
|
return;
|
|
@@ -4464,14 +4395,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
4464
4395
|
return false;
|
|
4465
4396
|
engine.beginGesture();
|
|
4466
4397
|
const snap = snapshotAll();
|
|
4467
|
-
// A section is a LOCKED tile; for ITS OWN move it is the one moving.
|
|
4468
|
-
const self = engine.getItem(id);
|
|
4469
|
-
const wasLocked = !!(self === null || self === void 0 ? void 0 : self.locked);
|
|
4470
|
-
if (self && isGroupMember(id))
|
|
4471
|
-
self.locked = false;
|
|
4472
4398
|
const ok = op();
|
|
4473
|
-
if (self && isGroupMember(id))
|
|
4474
|
-
self.locked = wasLocked;
|
|
4475
4399
|
if (!ok) {
|
|
4476
4400
|
engine.endGesture();
|
|
4477
4401
|
return false;
|
|
@@ -4758,7 +4682,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
4758
4682
|
return buildCommitCommands(deltas);
|
|
4759
4683
|
},
|
|
4760
4684
|
moveTo(id, x, y) {
|
|
4761
|
-
return programmatic('Move widget', id, () => engine.moveCheck(id, x, y).changed);
|
|
4685
|
+
return programmatic('Move widget', id, () => engine.moveCheck(id, x, y, { pushSolid: isGroupMember(id) }).changed);
|
|
4762
4686
|
},
|
|
4763
4687
|
resizeTo(id, w, h) {
|
|
4764
4688
|
const hh = isGroupMember(id) ? Math.max(h, innerRowsOf(id)) : h; // a section: never below its children
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
* the authored size.
|
|
24
24
|
*/
|
|
25
25
|
import { __awaiter } from "tslib";
|
|
26
|
+
import { BESIDE_BAND, resolveTabZone } from './zones.js';
|
|
26
27
|
import { BatchCommand, Command } from '@grafloria/engine';
|
|
27
28
|
import { LiveRegionController, registerTool } from '@grafloria/renderer';
|
|
28
29
|
import { anyEdge, clearOtherSelections, dragHandleSelector, gripHostOf, gripOf, normalizeDragHandle, ownsPress, parentPeerOf, pressOnDragHandle, registerBoardPeer, syncGrip, DRAG_HANDLE_CLASS, EDGE_GRIP } from './grid-binder.js';
|
|
@@ -1037,37 +1038,48 @@ export function bindDashboardSplit(api, group, options = {}) {
|
|
|
1037
1038
|
return true;
|
|
1038
1039
|
return cx >= rect.left - EDGE_GRACE && cx <= rect.right + EDGE_GRACE && cy >= rect.top - EDGE_GRACE && cy <= rect.bottom + EDGE_GRACE;
|
|
1039
1040
|
};
|
|
1040
|
-
/** The centre third of a container's BODY (below its strip), both axes — the grid board's join zone. */
|
|
1041
|
-
const centreThird = (g, w) => {
|
|
1042
|
-
const f = frameOfGroup(g);
|
|
1043
|
-
if (!inRect(f, w.x, w.y))
|
|
1044
|
-
return false;
|
|
1045
|
-
const top = f.y + plan.stripHeight(g.id);
|
|
1046
|
-
const h = Math.max(0, f.y + f.height - top);
|
|
1047
|
-
return w.x >= f.x + f.width / 3 && w.x <= f.x + (2 * f.width) / 3 && w.y >= top + h / 3 && w.y <= top + (2 * h) / 3;
|
|
1048
|
-
};
|
|
1049
1041
|
const zoneAt = (cx, cy, w) => {
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1042
|
+
var _a;
|
|
1043
|
+
// The ORDER is zones.ts's, shared with the grid board: a strip, the
|
|
1044
|
+
// target's fifth (a pane beside it on that side, the sides taking the
|
|
1045
|
+
// corners) or its middle (a join), home — the source's middle, its own
|
|
1046
|
+
// edges meaning a pane beside itself — then the pane under the pointer.
|
|
1047
|
+
// The fifth replaced the centre third here too (0.4.42's rule, at last
|
|
1048
|
+
// on both boards).
|
|
1049
|
+
const clientInside = clientInsideCanvasGrace(cx, cy);
|
|
1050
|
+
const target = clientInside ? ((_a = targets.find((t) => inRect(frameOfGroup(t), w.x, w.y))) !== null && _a !== void 0 ? _a : null) : null;
|
|
1051
|
+
const paneTarget = clientInside && worldInsideBoard(w.x, w.y) ? dropTargetAt(tree0, w.x, w.y) : null;
|
|
1052
|
+
const tz = resolveTabZone({
|
|
1053
|
+
x: w.x,
|
|
1054
|
+
y: w.y,
|
|
1055
|
+
clientInside,
|
|
1056
|
+
ownStrip: plan.stripIndex(fromGroupId, cx, cy),
|
|
1057
|
+
stripOf: (id) => plan.stripIndex(id, cx, cy),
|
|
1058
|
+
root: null,
|
|
1059
|
+
target: target ? { id: target.id, frame: frameOfGroup(target), stripHeight: plan.stripHeight(target.id) } : null,
|
|
1060
|
+
home: inRect(frameOfGroup(from), w.x, w.y) ? frameOfGroup(from) : null,
|
|
1061
|
+
homeBand: BESIDE_BAND,
|
|
1062
|
+
band: BESIDE_BAND,
|
|
1063
|
+
canSplit: () => true,
|
|
1064
|
+
pane: !!paneTarget,
|
|
1065
|
+
foreign: false,
|
|
1066
|
+
});
|
|
1067
|
+
switch (tz.kind) {
|
|
1068
|
+
case 'off':
|
|
1069
|
+
return { kind: 'off' };
|
|
1070
|
+
case 'strip':
|
|
1071
|
+
return { kind: 'strip', targetId: tz.targetId, index: tz.index };
|
|
1072
|
+
case 'reorder':
|
|
1073
|
+
return { kind: 'reorder', index: tz.index };
|
|
1074
|
+
case 'join':
|
|
1075
|
+
return { kind: 'join', targetId: tz.targetId };
|
|
1076
|
+
case 'split':
|
|
1077
|
+
return { kind: 'pane', target: { id: tz.targetId, side: tz.side, rect: frameOfGroup(target) } };
|
|
1078
|
+
case 'pane':
|
|
1079
|
+
return { kind: 'pane', target: paneTarget };
|
|
1080
|
+
default:
|
|
1081
|
+
return { kind: 'home' };
|
|
1069
1082
|
}
|
|
1070
|
-
return { kind: 'home' };
|
|
1071
1083
|
};
|
|
1072
1084
|
const apply = (z) => {
|
|
1073
1085
|
plan.markDrop(z.kind === 'reorder' ? fromGroupId : z.kind === 'strip' ? z.targetId : null, z.kind === 'reorder' || z.kind === 'strip' ? z.index : null);
|
|
@@ -81,6 +81,13 @@ export interface ResolveInput {
|
|
|
81
81
|
ghostSubtree: ReadonlySet<string>;
|
|
82
82
|
/** The board's gap, the tolerance around the vacated cell. */
|
|
83
83
|
gap: number;
|
|
84
|
+
/**
|
|
85
|
+
* The containers the dragged tile's OWN board sits in, innermost first or
|
|
86
|
+
* in any order: their bands do not apply to it. A widget leaving its page
|
|
87
|
+
* through the page's left fifth is moving within the page, not asking to
|
|
88
|
+
* be put beside the container it is still inside.
|
|
89
|
+
*/
|
|
90
|
+
homeChain: ReadonlySet<string>;
|
|
84
91
|
}
|
|
85
92
|
export type Zone = {
|
|
86
93
|
kind: 'off';
|
|
@@ -111,3 +118,74 @@ export declare const BESIDE_STAY = 0.05;
|
|
|
111
118
|
*/
|
|
112
119
|
export declare function bandOf(f: ZoneRect, stripHeight: number, x: number, y: number, band?: number): BesideSide | null;
|
|
113
120
|
export declare function resolve(input: ResolveInput): Zone;
|
|
121
|
+
/** A join target as the walk sees it: another tab container, its frame anchored by the binder while the pointer is inside it. */
|
|
122
|
+
export interface TabTarget {
|
|
123
|
+
id: string;
|
|
124
|
+
frame: ZoneRect;
|
|
125
|
+
stripHeight: number;
|
|
126
|
+
}
|
|
127
|
+
export interface TabZoneInput {
|
|
128
|
+
x: number;
|
|
129
|
+
y: number;
|
|
130
|
+
/** Inside the visible canvas plus its grace, in CLIENT space: off otherwise, whatever the world point says. */
|
|
131
|
+
clientInside: boolean;
|
|
132
|
+
/** The source strip's slot under the pointer (client space), else null. */
|
|
133
|
+
ownStrip: number | null;
|
|
134
|
+
/** A target strip's slot under the pointer (client space), else null. */
|
|
135
|
+
stripOf(targetId: string): number | null;
|
|
136
|
+
/** The board's own edge band under the pointer (client space) — a dock; null on boards that have none. */
|
|
137
|
+
root: {
|
|
138
|
+
side: BesideSide;
|
|
139
|
+
} | null;
|
|
140
|
+
/** The join target whose frame holds the point, or null. */
|
|
141
|
+
target: TabTarget | null;
|
|
142
|
+
/** The source group's frame, when the pointer is inside it. */
|
|
143
|
+
home: ZoneRect | null;
|
|
144
|
+
/** The source group's own band: 0 keeps its whole frame "home", a fifth lets its edges mean "beside myself". */
|
|
145
|
+
homeBand: number;
|
|
146
|
+
/** The outer fraction of a target's body that splits it (Dockview's fifth). */
|
|
147
|
+
band: number;
|
|
148
|
+
/** Whether the target can be halved on that side (a grid target too small refuses; a pane always can). */
|
|
149
|
+
canSplit(targetId: string, side: BesideSide): boolean;
|
|
150
|
+
/** A pane insertion exists under the pointer (split boards). */
|
|
151
|
+
pane: boolean;
|
|
152
|
+
/** A foreign board lies under the pointer and takes the drop (grid boards). */
|
|
153
|
+
foreign: boolean;
|
|
154
|
+
}
|
|
155
|
+
export type TabZone = {
|
|
156
|
+
kind: 'off';
|
|
157
|
+
} | {
|
|
158
|
+
kind: 'reorder';
|
|
159
|
+
index: number;
|
|
160
|
+
} | {
|
|
161
|
+
kind: 'strip';
|
|
162
|
+
targetId: string;
|
|
163
|
+
index: number;
|
|
164
|
+
} | {
|
|
165
|
+
kind: 'root';
|
|
166
|
+
side: BesideSide;
|
|
167
|
+
} | {
|
|
168
|
+
kind: 'join';
|
|
169
|
+
targetId: string;
|
|
170
|
+
} | {
|
|
171
|
+
kind: 'split';
|
|
172
|
+
targetId: string;
|
|
173
|
+
side: BesideSide;
|
|
174
|
+
} | {
|
|
175
|
+
kind: 'home';
|
|
176
|
+
} | {
|
|
177
|
+
kind: 'pane';
|
|
178
|
+
} | {
|
|
179
|
+
kind: 'board';
|
|
180
|
+
foreign: boolean;
|
|
181
|
+
};
|
|
182
|
+
/**
|
|
183
|
+
* What a pointer means for a TORN-OUT PAGE, in the one order both binders
|
|
184
|
+
* use: off the canvas, then a strip (the most precise target there is —
|
|
185
|
+
* a target's, or the source's own for a reorder), then the board's own
|
|
186
|
+
* edges (Dockview's container edges beat the groups against them), then the
|
|
187
|
+
* target under the pointer — its outer fifth splits it on that side, the
|
|
188
|
+
* sides taking the corners, its middle joins it — then home, then a pane, a
|
|
189
|
+
* foreign board, or this board.
|
|
190
|
+
*/
|
|
191
|
+
export declare function resolveTabZone(input: TabZoneInput): TabZone;
|
|
@@ -106,22 +106,28 @@ export function resolve(input) {
|
|
|
106
106
|
const px = x + dx;
|
|
107
107
|
const py = y + dy;
|
|
108
108
|
for (const c of board.children()) {
|
|
109
|
-
|
|
109
|
+
// the held container at rest — unless the hand has left that frame for the live one
|
|
110
|
+
const atRest = held && c.id === held.containerId && inRect(held.frame0, x, y) ? held.frame0 : null;
|
|
110
111
|
const frame = atRest !== null && atRest !== void 0 ? atRest : c.frame;
|
|
111
112
|
const tx = atRest ? x : px;
|
|
112
113
|
const ty = atRest ? y : py;
|
|
113
114
|
if (!inRect(frame, tx, ty))
|
|
114
115
|
continue;
|
|
115
|
-
|
|
116
|
+
// A band does not reach THROUGH a nested container: a hand over a
|
|
117
|
+
// section inside the page is inside the page, wherever the outer
|
|
118
|
+
// container's fifth falls (the inset margin is what remains of the
|
|
119
|
+
// band there). The page's plain widgets do not stop it.
|
|
120
|
+
const ndx0 = atRest ? c.frame.x - atRest.x : dx;
|
|
121
|
+
const ndy0 = atRest ? c.frame.y - atRest.y : dy;
|
|
122
|
+
const overNested = !!c.inner && c.inner.children().some((cc) => inRect(cc.frame, x + ndx0, y + ndy0));
|
|
123
|
+
const side = overNested || input.homeChain.has(c.id) ? null : bandOf(frame, c.stripHeight, tx, ty, c.band);
|
|
116
124
|
if (side)
|
|
117
125
|
return { kind: 'beside', board, containerId: c.id, side, kept: false };
|
|
118
126
|
if (opaque(board, c) || !c.inner)
|
|
119
127
|
return { kind: 'plain', board, grace: false };
|
|
120
|
-
|
|
121
|
-
const ndy = atRest ? c.frame.y - atRest.y : dy;
|
|
122
|
-
if (!c.inner.contains(x + ndx, y + ndy))
|
|
128
|
+
if (!c.inner.contains(x + ndx0, y + ndy0))
|
|
123
129
|
return { kind: 'plain', board, grace: false }; // the margin: the container's own frame
|
|
124
|
-
return descend(c.inner,
|
|
130
|
+
return descend(c.inner, ndx0, ndy0);
|
|
125
131
|
}
|
|
126
132
|
return { kind: 'plain', board, grace: false };
|
|
127
133
|
};
|
|
@@ -159,4 +165,39 @@ function stripHeightOf(roots, containerId) {
|
|
|
159
165
|
}
|
|
160
166
|
return 0;
|
|
161
167
|
}
|
|
168
|
+
/**
|
|
169
|
+
* What a pointer means for a TORN-OUT PAGE, in the one order both binders
|
|
170
|
+
* use: off the canvas, then a strip (the most precise target there is —
|
|
171
|
+
* a target's, or the source's own for a reorder), then the board's own
|
|
172
|
+
* edges (Dockview's container edges beat the groups against them), then the
|
|
173
|
+
* target under the pointer — its outer fifth splits it on that side, the
|
|
174
|
+
* sides taking the corners, its middle joins it — then home, then a pane, a
|
|
175
|
+
* foreign board, or this board.
|
|
176
|
+
*/
|
|
177
|
+
export function resolveTabZone(input) {
|
|
178
|
+
if (!input.clientInside)
|
|
179
|
+
return { kind: 'off' };
|
|
180
|
+
const { x, y } = input;
|
|
181
|
+
if (input.target) {
|
|
182
|
+
const idx = input.stripOf(input.target.id);
|
|
183
|
+
if (idx !== null)
|
|
184
|
+
return { kind: 'strip', targetId: input.target.id, index: idx };
|
|
185
|
+
}
|
|
186
|
+
else if (input.ownStrip !== null)
|
|
187
|
+
return { kind: 'reorder', index: input.ownStrip };
|
|
188
|
+
if (input.root)
|
|
189
|
+
return { kind: 'root', side: input.root.side };
|
|
190
|
+
if (input.target) {
|
|
191
|
+
const t = input.target;
|
|
192
|
+
const side = bandOf(t.frame, t.stripHeight, x, y, input.band);
|
|
193
|
+
if (side && input.canSplit(t.id, side))
|
|
194
|
+
return { kind: 'split', targetId: t.id, side };
|
|
195
|
+
return { kind: 'join', targetId: t.id };
|
|
196
|
+
}
|
|
197
|
+
if (input.home && inRect(input.home, x, y) && !bandOf(input.home, 0, x, y, input.homeBand))
|
|
198
|
+
return { kind: 'home' };
|
|
199
|
+
if (input.pane)
|
|
200
|
+
return { kind: 'pane' };
|
|
201
|
+
return { kind: 'board', foreign: input.foreign };
|
|
202
|
+
}
|
|
162
203
|
//# sourceMappingURL=zones.js.map
|