@grafloria/element 0.4.55 → 0.4.57
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/lib/dashboard-kit/board-ctx.d.ts +6 -0
- package/src/lib/dashboard-kit/board-ctx.js +6 -1
- package/src/lib/dashboard-kit/dashboard.d.ts +2 -0
- package/src/lib/dashboard-kit/dashboard.js +50 -2
- package/src/lib/dashboard-kit/grid-binder.d.ts +9 -4
- package/src/lib/dashboard-kit/grid-binder.js +231 -678
- package/src/lib/dashboard-kit/tear-out.d.ts +55 -0
- package/src/lib/dashboard-kit/tear-out.js +613 -0
- package/src/lib/dashboard-kit/zones.d.ts +10 -0
- package/src/lib/dashboard-kit/zones.js +13 -3
|
@@ -56,13 +56,16 @@ import { buildCommitCommands, cellFromGridItem, cellToRect, columnUnitFor, gridI
|
|
|
56
56
|
import { ensureDashboardKitStyles } from './styles.js';
|
|
57
57
|
import { captionOfGroup, captionPassThrough, sectionCaptionReserve } from './caption.js';
|
|
58
58
|
import { TAB_STRIP_HEIGHT } from './tabs.js';
|
|
59
|
-
import { BESIDE_BAND, resolve as resolveZone
|
|
59
|
+
import { BESIDE_BAND, resolve as resolveZone } from './zones.js';
|
|
60
60
|
import { SequenceCommand, SetGroupCellCommand, tileCommands } from './commit.js';
|
|
61
|
+
import { EDGE_GRACE } from './board-ctx.js';
|
|
61
62
|
import { createProjection } from './project.js';
|
|
62
63
|
import { cursorFor, edgesNear, NO_EDGES, anyEdge } from './edges.js';
|
|
63
64
|
import { DRAG_HANDLE_CLASS, dragHandleSelector, gripHostOf, normalizeDragHandle, pressOnDragHandle, sameDragHandle } from './grip.js';
|
|
64
65
|
import { createChrome, edgeGripFor, isTabsGroup } from './chrome.js';
|
|
65
66
|
import { createKeyboard, describeCell, liveRegionFor } from './keyboard.js';
|
|
67
|
+
import { createTearOut, TEAR_OUT_MIN_ROWS } from './tear-out.js';
|
|
68
|
+
export { TEAR_OUT_MIN_ROWS } from './tear-out.js';
|
|
66
69
|
export { EDGE_GRIP, anyEdge } from './edges.js';
|
|
67
70
|
export { GRIP_CLASS, DRAG_HANDLE_CLASS, CAPTION_BAND, normalizeDragHandle, dragHandleSelector, gripOf, syncGrip, gripHostOf, pressOnDragHandle } from './grip.js';
|
|
68
71
|
export { SequenceCommand, SetGroupCellCommand } from './commit.js';
|
|
@@ -101,8 +104,6 @@ export function ownsPress(container, diagram, ev, hit) {
|
|
|
101
104
|
}
|
|
102
105
|
return true;
|
|
103
106
|
}
|
|
104
|
-
/** A torn-out page never arrives shorter than this: a strip with no room under it is not a group. */
|
|
105
|
-
export const TEAR_OUT_MIN_ROWS = 2;
|
|
106
107
|
/**
|
|
107
108
|
* Binders on the same canvas find each other here. A WeakMap: a canvas that
|
|
108
109
|
* is gone takes its (empty) peer set with it — the strong Map it replaced kept
|
|
@@ -148,6 +149,8 @@ export function registerBoardPeer(container, peer) {
|
|
|
148
149
|
};
|
|
149
150
|
}
|
|
150
151
|
const DRAG_THRESHOLD = 4;
|
|
152
|
+
/** The node a gesture holds — node-only paths (the pixel ghost, a resize, a strip drop, a removal) ask through this. */
|
|
153
|
+
const nodeOf = (g) => g.entity;
|
|
151
154
|
let binderSeq = 0;
|
|
152
155
|
export function bindDashboardGrid(api, group, options = {}) {
|
|
153
156
|
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
|
|
@@ -487,7 +490,8 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
487
490
|
hostOf,
|
|
488
491
|
memberEntity,
|
|
489
492
|
sizeOf,
|
|
490
|
-
|
|
493
|
+
// A NODE ghost follows the hand by pixels and gets the placeholder; a GROUP ghost (a container adopted here) is projected to its cell like any tile, carried.
|
|
494
|
+
ghostId: () => (adoptedGhostId && !diagram.getGroup(adoptedGhostId) ? adoptedGhostId : (gesture === null || gesture === void 0 ? void 0 : gesture.started) && gesture.subject === 'node' ? gesture.id : null),
|
|
491
495
|
write: (fn) => {
|
|
492
496
|
writing = true;
|
|
493
497
|
try {
|
|
@@ -818,7 +822,6 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
818
822
|
/** Small overshoots CLAMP onto the board instead of counting as off-board —
|
|
819
823
|
* the plan prototype cannot leave its board at all (cells clamp at the
|
|
820
824
|
* edges), so a 60px slip past the frame must not dim or delete. */
|
|
821
|
-
const EDGE_GRACE = 60;
|
|
822
825
|
/** A point inside a member group's own frame (its strip and its pages). */
|
|
823
826
|
const worldInsideGroup = (g, x, y) => {
|
|
824
827
|
const s = sizeOf(g);
|
|
@@ -947,6 +950,12 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
947
950
|
g.leg.adopted.abort();
|
|
948
951
|
g.leg = null;
|
|
949
952
|
}
|
|
953
|
+
if (g.subject === 'group' && !g.removedFromBoard && !beside) {
|
|
954
|
+
// A group ghost pushed its way here with intent: the containers it moved
|
|
955
|
+
// come home (the engine's memory) before the beside is measured on them.
|
|
956
|
+
engine.remove(g.id);
|
|
957
|
+
g.removedFromBoard = true;
|
|
958
|
+
}
|
|
950
959
|
if (g.removedFromBoard) {
|
|
951
960
|
g.removedFromBoard = false;
|
|
952
961
|
setDim(g, false);
|
|
@@ -968,8 +977,8 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
968
977
|
return false;
|
|
969
978
|
};
|
|
970
979
|
let slabGesture = null;
|
|
971
|
-
/** The
|
|
972
|
-
const
|
|
980
|
+
/** The gesture as it is NOW, for the same reason. */
|
|
981
|
+
const currentGesture = () => gesture;
|
|
973
982
|
/** The PARENT running a resize of OUR section from a press this tool claimed. */
|
|
974
983
|
let forwardSlab = null;
|
|
975
984
|
const frameOfGroup = (grp) => ({ x: grp.position.x, y: grp.position.y, width: sizeOf(grp).width, height: sizeOf(grp).height });
|
|
@@ -1017,25 +1026,45 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1017
1026
|
const it = engine.getItem(id);
|
|
1018
1027
|
if (!grp || !it || gesture || slabGesture || isStatic)
|
|
1019
1028
|
return;
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1029
|
+
// A GROUP MOVE IS A GESTURE OF THE ONE MACHINE (tile first, 4b-ii): the
|
|
1030
|
+
// same threshold, snapshot, zone walk, legs, beside and commit a widget
|
|
1031
|
+
// gets — the group being the subject. The frame follows its cell.
|
|
1032
|
+
//
|
|
1033
|
+
// THE PRESS ARMS NOTHING. `beginGestureVisuals` arms the glide when the
|
|
1034
|
+
// threshold is crossed, as it does for a widget: arming it here left a
|
|
1035
|
+
// plain CLICK on a caption band gliding for 400 ms, and everything the
|
|
1036
|
+
// page did next — a caption change, a layout switch — animated instead of
|
|
1037
|
+
// landing (the gallery's fluid-board: "and the rows are back" measured a
|
|
1038
|
+
// tile still travelling).
|
|
1039
|
+
gesture = {
|
|
1040
|
+
kind: 'move',
|
|
1023
1041
|
id,
|
|
1024
|
-
|
|
1042
|
+
subject: 'group',
|
|
1043
|
+
entity: grp,
|
|
1025
1044
|
pointerId: typeof PointerEvent !== 'undefined' && ev.source instanceof PointerEvent ? ev.source.pointerId : null,
|
|
1026
1045
|
started: false,
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1046
|
+
downClient: { x: ev.screen.x, y: ev.screen.y },
|
|
1047
|
+
downWorld: { x: ev.world.x, y: ev.world.y },
|
|
1048
|
+
grab: { dx: ev.world.x - grp.position.x, dy: ev.world.y - grp.position.y },
|
|
1049
|
+
startCells: new Map(),
|
|
1050
|
+
startGeom: new Map(),
|
|
1051
|
+
startSize: { width: sizeOf(grp).width, height: sizeOf(grp).height },
|
|
1052
|
+
startPos: { x: grp.position.x, y: grp.position.y },
|
|
1053
|
+
edges: NO_EDGES,
|
|
1054
|
+
spans: { w: it.w, h: it.h },
|
|
1055
|
+
removedFromBoard: false,
|
|
1056
|
+
leg: null,
|
|
1057
|
+
strip: null,
|
|
1058
|
+
lastWorld: null,
|
|
1059
|
+
lastScreen: null,
|
|
1060
|
+
hostEl: null,
|
|
1061
|
+
esc: null,
|
|
1062
|
+
chip: null,
|
|
1034
1063
|
};
|
|
1035
|
-
capturePointer(slabGesture.pointerId);
|
|
1036
|
-
api.container.style.cursor = 'grabbing';
|
|
1037
1064
|
};
|
|
1038
1065
|
const slabMove = (ev) => {
|
|
1066
|
+
if ((gesture === null || gesture === void 0 ? void 0 : gesture.subject) === 'group')
|
|
1067
|
+
return onToolMove(ev);
|
|
1039
1068
|
const g = slabGesture;
|
|
1040
1069
|
if (!g)
|
|
1041
1070
|
return;
|
|
@@ -1120,6 +1149,8 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1120
1149
|
};
|
|
1121
1150
|
const slabUp = () => {
|
|
1122
1151
|
var _a;
|
|
1152
|
+
if ((gesture === null || gesture === void 0 ? void 0 : gesture.subject) === 'group')
|
|
1153
|
+
return onToolUp();
|
|
1123
1154
|
const g = slabGesture;
|
|
1124
1155
|
if (!g)
|
|
1125
1156
|
return;
|
|
@@ -1146,6 +1177,8 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1146
1177
|
};
|
|
1147
1178
|
const slabCancel = () => {
|
|
1148
1179
|
var _a;
|
|
1180
|
+
if ((gesture === null || gesture === void 0 ? void 0 : gesture.subject) === 'group')
|
|
1181
|
+
return cancelActiveGesture();
|
|
1149
1182
|
const g = slabGesture;
|
|
1150
1183
|
if (!g)
|
|
1151
1184
|
return;
|
|
@@ -1193,8 +1226,12 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1193
1226
|
g.started = true;
|
|
1194
1227
|
armGlide();
|
|
1195
1228
|
if (g.kind !== 'palette') {
|
|
1196
|
-
|
|
1197
|
-
|
|
1229
|
+
if (g.subject === 'node') {
|
|
1230
|
+
setGhost(g.id, true);
|
|
1231
|
+
g.hostEl = hostOf(g.id);
|
|
1232
|
+
}
|
|
1233
|
+
else
|
|
1234
|
+
setCarried(g.id, true); // a group moves as one thing: its whole subtree is exempt from the glide
|
|
1198
1235
|
capturePointer(g.pointerId);
|
|
1199
1236
|
api.container.style.cursor = g.kind === 'resize' ? 'nwse-resize' : 'grabbing';
|
|
1200
1237
|
}
|
|
@@ -1230,8 +1267,14 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1230
1267
|
};
|
|
1231
1268
|
const cleanupGestureVisuals = (g) => {
|
|
1232
1269
|
var _a;
|
|
1233
|
-
if (g.kind !== 'palette')
|
|
1234
|
-
|
|
1270
|
+
if (g.kind !== 'palette') {
|
|
1271
|
+
if (g.subject === 'node')
|
|
1272
|
+
setGhost(g.id, false);
|
|
1273
|
+
else {
|
|
1274
|
+
setCarried(g.id, false); // exempt through the drop write, then the glides resume
|
|
1275
|
+
showRefusal(null, 0, 0);
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1235
1278
|
disarmGlideSoon();
|
|
1236
1279
|
releasePointer(g.pointerId);
|
|
1237
1280
|
api.container.style.cursor = '';
|
|
@@ -1266,8 +1309,8 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1266
1309
|
persistLayouts(); // an edit at a narrow count propagated into the wide cache
|
|
1267
1310
|
if (changed) {
|
|
1268
1311
|
const it = engine.getItem(g.id);
|
|
1269
|
-
if (it)
|
|
1270
|
-
live.announce(`${nameOf(g
|
|
1312
|
+
if (it && g.subject === 'node')
|
|
1313
|
+
live.announce(`${nameOf(nodeOf(g))} ${g.kind === 'resize' ? 'resized' : 'moved'} to ${describeCell(it)}`);
|
|
1271
1314
|
}
|
|
1272
1315
|
syncA11y();
|
|
1273
1316
|
api.renderNow();
|
|
@@ -1367,14 +1410,17 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1367
1410
|
const moveGhost = (g, ev) => {
|
|
1368
1411
|
var _a;
|
|
1369
1412
|
const desired = { x: ev.world.x - g.grab.dx, y: ev.world.y - g.grab.dy };
|
|
1370
|
-
g.node
|
|
1371
|
-
|
|
1413
|
+
if (g.subject === 'node') {
|
|
1414
|
+
// The pixel ghost follows the hand; a group's frame is projected from its cell, carried.
|
|
1415
|
+
nodeOf(g).setPosition(desired.x, desired.y);
|
|
1416
|
+
ghostStyleFastPath(g, desired);
|
|
1417
|
+
}
|
|
1372
1418
|
g.lastWorld = { x: ev.world.x, y: ev.world.y };
|
|
1373
1419
|
g.lastScreen = { x: ev.screen.x, y: ev.screen.y };
|
|
1374
1420
|
/** The ghost's pixel size on THIS board — a palette chip has spans, not a size. */
|
|
1375
1421
|
const pxSize = () => {
|
|
1376
1422
|
if (g.kind !== 'palette')
|
|
1377
|
-
return
|
|
1423
|
+
return sizeOf(g.entity);
|
|
1378
1424
|
const f = frame();
|
|
1379
1425
|
const gg = geom();
|
|
1380
1426
|
return { width: g.spans.w * (columnUnitFor(gg, f.width) + gap) - gap, height: g.spans.h * (rowHeightFor(gg, rows()) + gap) - gap };
|
|
@@ -1403,7 +1449,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1403
1449
|
project();
|
|
1404
1450
|
}
|
|
1405
1451
|
if (!g.leg) {
|
|
1406
|
-
const adopted = peer.adopt(g.
|
|
1452
|
+
const adopted = peer.adopt(g.entity, ev.world, pxSize(), opts);
|
|
1407
1453
|
if (!adopted)
|
|
1408
1454
|
return false;
|
|
1409
1455
|
g.leg = { peer, adopted };
|
|
@@ -1416,7 +1462,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1416
1462
|
// BEFORE any engine is asked anything. The strip still wins over every
|
|
1417
1463
|
// board; a band's stickiness and the vacated cell still hold a beside.
|
|
1418
1464
|
const z = resolveTileZone(g, ev);
|
|
1419
|
-
if (z.kind === 'strip' && options.tabDrop && !isStatic && g.kind !== 'palette') {
|
|
1465
|
+
if (z.kind === 'strip' && options.tabDrop && !isStatic && g.kind !== 'palette' && g.subject === 'node') {
|
|
1420
1466
|
// -- INTO A STRIP: the widget becomes a new tab there, so it leaves
|
|
1421
1467
|
// this board (survivors settle home) and the strip marks the slot.
|
|
1422
1468
|
endBeside(true); // a band's shift gives way to the strip: the container comes back
|
|
@@ -1514,7 +1560,10 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1514
1560
|
g.leg = null;
|
|
1515
1561
|
}
|
|
1516
1562
|
setDim(g, false);
|
|
1517
|
-
|
|
1563
|
+
// A group moved by hand takes the cell under the hand and PUSHES what
|
|
1564
|
+
// stands in its way (0.4.44) — and the zone walk reads the containers
|
|
1565
|
+
// it pushed at REST, so the hand still finds their zones where they were.
|
|
1566
|
+
placeOnSelf(g, desired, g.subject === 'group');
|
|
1518
1567
|
}
|
|
1519
1568
|
else {
|
|
1520
1569
|
// -- outside every board ------------------------------------------
|
|
@@ -1522,6 +1571,10 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1522
1571
|
setDim(g, true);
|
|
1523
1572
|
}
|
|
1524
1573
|
syncPlaceholder();
|
|
1574
|
+
// A carried group's slab and frame are this board's chrome, but its frame
|
|
1575
|
+
// is written by whichever board holds it now: re-sync so they follow.
|
|
1576
|
+
if (g.subject === 'group')
|
|
1577
|
+
syncSlabs();
|
|
1525
1578
|
};
|
|
1526
1579
|
const onToolMove = (ev) => {
|
|
1527
1580
|
var _a, _b, _c;
|
|
@@ -1806,8 +1859,8 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1806
1859
|
const anchorBottom = liveRect ? liveRect.y + liveRect.height : bottom;
|
|
1807
1860
|
const px = E.w ? anchorRight - w : anchorLeft;
|
|
1808
1861
|
const py = E.n ? anchorBottom - h : anchorTop;
|
|
1809
|
-
g.
|
|
1810
|
-
g.
|
|
1862
|
+
nodeOf(g).setSize(w, h, (_c = nodeOf(g).size.depth) !== null && _c !== void 0 ? _c : 0);
|
|
1863
|
+
nodeOf(g).setPosition(px, py);
|
|
1811
1864
|
ghostStyleFastPath(g, { x: px, y: py, width: w, height: h });
|
|
1812
1865
|
const spanF = bound() !== undefined ? frame() : f;
|
|
1813
1866
|
const spanG = bound() !== undefined ? geom() : gg;
|
|
@@ -1847,7 +1900,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1847
1900
|
syncPlaceholder();
|
|
1848
1901
|
};
|
|
1849
1902
|
const onToolUp = () => {
|
|
1850
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
|
|
1903
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
|
|
1851
1904
|
const g = gesture;
|
|
1852
1905
|
if (!g || g.kind === 'palette')
|
|
1853
1906
|
return;
|
|
@@ -1867,8 +1920,8 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1867
1920
|
const displaced = tileCommands(deltasSince(g.startCells, g.startGeom, g.id));
|
|
1868
1921
|
const snap = g.startGeom.get(g.id);
|
|
1869
1922
|
if (snap) {
|
|
1870
|
-
g.
|
|
1871
|
-
g.
|
|
1923
|
+
nodeOf(g).setPosition(snap.pos.x, snap.pos.y);
|
|
1924
|
+
nodeOf(g).setSize(snap.size.width, snap.size.height, (_a = snap.size.depth) !== null && _a !== void 0 ? _a : 0);
|
|
1872
1925
|
}
|
|
1873
1926
|
const cmds = options.tabDrop.dropIntoStrip(g.id, target.containerId, target.index, group.id, displaced);
|
|
1874
1927
|
if (cmds.length === 0) {
|
|
@@ -1898,8 +1951,12 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1898
1951
|
try {
|
|
1899
1952
|
diagram.runSystemWrite(() => {
|
|
1900
1953
|
var _a;
|
|
1901
|
-
g.node
|
|
1902
|
-
|
|
1954
|
+
if (g.subject === 'node') {
|
|
1955
|
+
nodeOf(g).setPosition(fin.rect.x, fin.rect.y);
|
|
1956
|
+
nodeOf(g).setSize(fin.rect.width, fin.rect.height, (_a = nodeOf(g).size.depth) !== null && _a !== void 0 ? _a : 0);
|
|
1957
|
+
}
|
|
1958
|
+
else
|
|
1959
|
+
g.entity.setFrame(Object.assign({}, fin.rect));
|
|
1903
1960
|
});
|
|
1904
1961
|
}
|
|
1905
1962
|
finally {
|
|
@@ -1908,6 +1965,27 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1908
1965
|
const sourceDisplaced = tileCommands(deltasSince(g.startCells, g.startGeom, g.id));
|
|
1909
1966
|
const before = g.startCells.get(g.id);
|
|
1910
1967
|
const geomBefore = g.startGeom.get(g.id);
|
|
1968
|
+
// A GROUP crossing boards: its cell-and-frame command, and the host's
|
|
1969
|
+
// bookkeeping for a container that changed boards (the spec entry, the
|
|
1970
|
+
// registry) rides along through `onMemberMoving` (tile first, 4b-ii).
|
|
1971
|
+
const own = g.subject === 'group'
|
|
1972
|
+
? [
|
|
1973
|
+
new SetGroupCellCommand(g.id, before !== null && before !== void 0 ? before : fin.cell, fin.cell, geomBefore ? { x: geomBefore.pos.x, y: geomBefore.pos.y, width: geomBefore.size.width, height: geomBefore.size.height } : fin.rect, fin.rect),
|
|
1974
|
+
...((_d = (_c = options.onMemberMoving) === null || _c === void 0 ? void 0 : _c.call(options, g.id, group.id, targetGroupId)) !== null && _d !== void 0 ? _d : []),
|
|
1975
|
+
]
|
|
1976
|
+
: buildCommitCommands([
|
|
1977
|
+
{
|
|
1978
|
+
id: g.id,
|
|
1979
|
+
locked: false,
|
|
1980
|
+
isGroup: false,
|
|
1981
|
+
cellBefore: before !== null && before !== void 0 ? before : fin.cell,
|
|
1982
|
+
cellAfter: fin.cell,
|
|
1983
|
+
posBefore: (_e = geomBefore === null || geomBefore === void 0 ? void 0 : geomBefore.pos) !== null && _e !== void 0 ? _e : { x: fin.rect.x, y: fin.rect.y },
|
|
1984
|
+
posAfter: { x: fin.rect.x, y: fin.rect.y },
|
|
1985
|
+
sizeBefore: (_f = geomBefore === null || geomBefore === void 0 ? void 0 : geomBefore.size) !== null && _f !== void 0 ? _f : { width: fin.rect.width, height: fin.rect.height },
|
|
1986
|
+
sizeAfter: { width: fin.rect.width, height: fin.rect.height },
|
|
1987
|
+
},
|
|
1988
|
+
]);
|
|
1911
1989
|
const crossing = [
|
|
1912
1990
|
...sourceDisplaced,
|
|
1913
1991
|
// …and the TARGET board's own displaced tiles. Dropping onto an
|
|
@@ -1920,24 +1998,12 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1920
1998
|
...fin.commands,
|
|
1921
1999
|
new RemoveFromGroupCommand(group.id, g.id),
|
|
1922
2000
|
new AddToGroupCommand(targetGroupId, g.id),
|
|
1923
|
-
...
|
|
1924
|
-
{
|
|
1925
|
-
id: g.id,
|
|
1926
|
-
locked: false,
|
|
1927
|
-
isGroup: false,
|
|
1928
|
-
cellBefore: before !== null && before !== void 0 ? before : fin.cell,
|
|
1929
|
-
cellAfter: fin.cell,
|
|
1930
|
-
posBefore: (_c = geomBefore === null || geomBefore === void 0 ? void 0 : geomBefore.pos) !== null && _c !== void 0 ? _c : { x: fin.rect.x, y: fin.rect.y },
|
|
1931
|
-
posAfter: { x: fin.rect.x, y: fin.rect.y },
|
|
1932
|
-
sizeBefore: (_d = geomBefore === null || geomBefore === void 0 ? void 0 : geomBefore.size) !== null && _d !== void 0 ? _d : { width: fin.rect.width, height: fin.rect.height },
|
|
1933
|
-
sizeAfter: { width: fin.rect.width, height: fin.rect.height },
|
|
1934
|
-
},
|
|
1935
|
-
]),
|
|
2001
|
+
...own,
|
|
1936
2002
|
];
|
|
1937
2003
|
// …and whatever follows a member out of this board: an emptied page
|
|
1938
2004
|
// closes — in which case the membership commands ride INSIDE one
|
|
1939
2005
|
// sequence with it, or the batch could never undo (its group is gone).
|
|
1940
|
-
const leaving = (
|
|
2006
|
+
const leaving = (_h = (_g = options.onMemberLeaving) === null || _g === void 0 ? void 0 : _g.call(options, g.id)) !== null && _h !== void 0 ? _h : [];
|
|
1941
2007
|
execute('Move widget', leaving.length > 0 ? [new SequenceCommand('Move widget', [...crossing, ...leaving])] : crossing);
|
|
1942
2008
|
engine.endGesture();
|
|
1943
2009
|
cleanupGestureVisuals(g);
|
|
@@ -1945,10 +2011,10 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1945
2011
|
enforceBoardHeight();
|
|
1946
2012
|
persistLayouts();
|
|
1947
2013
|
api.renderNow();
|
|
1948
|
-
(
|
|
2014
|
+
(_j = options.onGesture) === null || _j === void 0 ? void 0 : _j.call(options, { type: 'commit', kind: g.kind, nodeId: g.id, changed: true });
|
|
1949
2015
|
return;
|
|
1950
2016
|
}
|
|
1951
|
-
if (g.removedFromBoard && dragOut === 'cancel') {
|
|
2017
|
+
if (g.removedFromBoard && (dragOut === 'cancel' || g.subject === 'group')) {
|
|
1952
2018
|
// Released outside every board on a snap-home board: full restore,
|
|
1953
2019
|
// nothing committed (the parked-outside release).
|
|
1954
2020
|
cancelActiveGesture();
|
|
@@ -1975,16 +2041,16 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1975
2041
|
if (snap) {
|
|
1976
2042
|
// Park the node on its start rect so the page's RemoveNodeCommand
|
|
1977
2043
|
// captures sane geometry for undo.
|
|
1978
|
-
g.
|
|
1979
|
-
g.
|
|
2044
|
+
nodeOf(g).setPosition(snap.pos.x, snap.pos.y);
|
|
2045
|
+
nodeOf(g).setSize(snap.size.width, snap.size.height, (_k = snap.size.depth) !== null && _k !== void 0 ? _k : 0);
|
|
1980
2046
|
}
|
|
1981
2047
|
engine.endGesture();
|
|
1982
2048
|
cleanupGestureVisuals(g);
|
|
1983
2049
|
gesture = null;
|
|
1984
2050
|
enforceBoardHeight();
|
|
1985
2051
|
api.renderNow();
|
|
1986
|
-
void ((
|
|
1987
|
-
(
|
|
2052
|
+
void ((_l = options.onRemoveRequest) === null || _l === void 0 ? void 0 : _l.call(options, g.id, displaced));
|
|
2053
|
+
(_m = options.onGesture) === null || _m === void 0 ? void 0 : _m.call(options, { type: 'remove', kind: g.kind, nodeId: g.id, changed: true });
|
|
1988
2054
|
return;
|
|
1989
2055
|
}
|
|
1990
2056
|
commitGesture(g);
|
|
@@ -2062,6 +2128,27 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2062
2128
|
placeNear(g.id, cell.x, cell.y, g.spans.w, pushSolid);
|
|
2063
2129
|
project();
|
|
2064
2130
|
}
|
|
2131
|
+
else if (g.subject === 'group') {
|
|
2132
|
+
// The cell under the pointer, else the nearest legal one ALONG ITS ROW
|
|
2133
|
+
// (a section dragged left that wandered down its column read as the
|
|
2134
|
+
// wrong tile moving); when even that fails the wanted cell is painted
|
|
2135
|
+
// as refused (identification round, D6/D7).
|
|
2136
|
+
const it = engine.getItem(g.id);
|
|
2137
|
+
if (!it)
|
|
2138
|
+
return;
|
|
2139
|
+
const cell = pointToCell(desired.x, desired.y, frame(), geom(), rows(), it.w);
|
|
2140
|
+
if (cell.x === it.x && cell.y === it.y)
|
|
2141
|
+
return;
|
|
2142
|
+
const was = { x: it.x, y: it.y };
|
|
2143
|
+
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);
|
|
2144
|
+
if (moved) {
|
|
2145
|
+
project();
|
|
2146
|
+
setCarried(g.id, true); // chrome repainted by the projection is carried too
|
|
2147
|
+
}
|
|
2148
|
+
const now = engine.getItem(g.id);
|
|
2149
|
+
const stuck = !!now && now.x === was.x && now.y === was.y;
|
|
2150
|
+
showRefusal(stuck ? cell : null, it.w, it.h);
|
|
2151
|
+
}
|
|
2065
2152
|
else {
|
|
2066
2153
|
const spanW = (_b = (_a = engine.getItem(g.id)) === null || _a === void 0 ? void 0 : _a.w) !== null && _b !== void 0 ? _b : g.spans.w;
|
|
2067
2154
|
const cell = pointToCell(desired.x, desired.y, frame(), geom(), rows(), spanW);
|
|
@@ -2073,24 +2160,65 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2073
2160
|
const resolveTileZone = (g, ev) => {
|
|
2074
2161
|
var _a, _b;
|
|
2075
2162
|
let strip = null;
|
|
2076
|
-
if (options.tabDrop && !isStatic && g.kind !== 'palette') {
|
|
2163
|
+
if (options.tabDrop && !isStatic && g.kind !== 'palette' && g.subject === 'node') {
|
|
2164
|
+
// A group never becomes a tab: over a container's strip it is over the
|
|
2165
|
+
// container's margin — a cell on the parent board, pushing with intent.
|
|
2077
2166
|
const crect = api.container.getBoundingClientRect();
|
|
2078
2167
|
strip = options.tabDrop.stripAt(crect.left + ev.screen.x, crect.top + ev.screen.y);
|
|
2079
2168
|
}
|
|
2080
|
-
return resolveZone({
|
|
2081
|
-
x: ev.world.x,
|
|
2082
|
-
y: ev.world.y,
|
|
2083
|
-
roots: zoneRoots(),
|
|
2084
|
-
strip,
|
|
2085
|
-
prev: beside
|
|
2169
|
+
return resolveZone(Object.assign(Object.assign({ x: ev.world.x, y: ev.world.y, roots: zoneRoots(), strip, prev: beside
|
|
2086
2170
|
? { containerId: beside.id, side: beside.side, frame0: beside.frame0, vacated: cellToRect({ x: beside.vacated.x, y: beside.vacated.y, w: g.spans.w, h: g.spans.h }, frame(), geom(), rows()) }
|
|
2087
|
-
: ((_b = (_a = g.leg) === null || _a === void 0 ? void 0 : _a.adopted.besideState()) !== null && _b !== void 0 ? _b : null),
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2171
|
+
: ((_b = (_a = g.leg) === null || _a === void 0 ? void 0 : _a.adopted.besideState()) !== null && _b !== void 0 ? _b : null), maxDepth: nesting, ghostDepth: g.subject === 'group' ? 1 + levelsInside(g.id) : 0 }, (g.subject === 'group' ? { restFrames: restFramesOf(g) } : {})), { ghostSubtree: g.subject === 'group' ? descendantGroups(g.id) : EMPTY_SUBTREE, gap, homeChain: homeChain() }));
|
|
2172
|
+
};
|
|
2173
|
+
/** This board's containers as they stood at the press: a group ghost's intent pushes never change what the hand means. */
|
|
2174
|
+
const restFramesOf = (g) => {
|
|
2175
|
+
const out = new Map();
|
|
2176
|
+
for (const [id, snap] of g.startGeom) {
|
|
2177
|
+
if (id === g.id || !isGroupMember(id))
|
|
2178
|
+
continue;
|
|
2179
|
+
out.set(id, { x: snap.pos.x, y: snap.pos.y, width: snap.size.width, height: snap.size.height });
|
|
2180
|
+
}
|
|
2181
|
+
return out;
|
|
2182
|
+
};
|
|
2183
|
+
/** Every group under `id`, itself included — the boards a group ghost may never enter. */
|
|
2184
|
+
const descendantGroups = (id) => {
|
|
2185
|
+
var _a;
|
|
2186
|
+
const out = new Set();
|
|
2187
|
+
const queue = [id];
|
|
2188
|
+
while (queue.length) {
|
|
2189
|
+
const cur = queue.shift();
|
|
2190
|
+
if (out.has(cur))
|
|
2191
|
+
continue;
|
|
2192
|
+
const grp = diagram.getGroup(cur);
|
|
2193
|
+
if (!grp)
|
|
2194
|
+
continue;
|
|
2195
|
+
out.add(cur);
|
|
2196
|
+
for (const m of (_a = grp.members) !== null && _a !== void 0 ? _a : [])
|
|
2197
|
+
if (diagram.getGroup(m))
|
|
2198
|
+
queue.push(m);
|
|
2199
|
+
}
|
|
2200
|
+
return out;
|
|
2201
|
+
};
|
|
2202
|
+
/**
|
|
2203
|
+
* The container LEVELS a group carries INSIDE it: 0 for a section of
|
|
2204
|
+
* widgets or a tab container of plain pages (a container and its page are
|
|
2205
|
+
* one level), 1 for a section holding a section, and so on. The group's own
|
|
2206
|
+
* level is the +1 above: its widgets sit one board deeper than the board it
|
|
2207
|
+
* lands on. The nesting policy counts both.
|
|
2208
|
+
*/
|
|
2209
|
+
const levelsInside = (id) => {
|
|
2210
|
+
var _a;
|
|
2211
|
+
const grp = diagram.getGroup(id);
|
|
2212
|
+
if (!grp)
|
|
2213
|
+
return 0;
|
|
2214
|
+
const tabs = isTabsGroup(grp);
|
|
2215
|
+
let deepest = 0;
|
|
2216
|
+
for (const m of (_a = grp.members) !== null && _a !== void 0 ? _a : []) {
|
|
2217
|
+
if (!diagram.getGroup(m))
|
|
2218
|
+
continue;
|
|
2219
|
+
deepest = Math.max(deepest, tabs ? levelsInside(m) : 1 + levelsInside(m));
|
|
2220
|
+
}
|
|
2221
|
+
return deepest;
|
|
2094
2222
|
};
|
|
2095
2223
|
/** The groups this board sits in, all the way up: their bands never apply to a tile of this board. */
|
|
2096
2224
|
const homeChain = () => {
|
|
@@ -2472,7 +2600,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2472
2600
|
if (isStatic)
|
|
2473
2601
|
return false;
|
|
2474
2602
|
beginSlabMove(id, ev);
|
|
2475
|
-
return (
|
|
2603
|
+
return (gesture === null || gesture === void 0 ? void 0 : gesture.id) === id;
|
|
2476
2604
|
},
|
|
2477
2605
|
dragMember: (id, ev) => {
|
|
2478
2606
|
var _a;
|
|
@@ -2485,7 +2613,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2485
2613
|
return { world, screen: { x: e.clientX - rect.left, y: e.clientY - rect.top }, source: e };
|
|
2486
2614
|
};
|
|
2487
2615
|
beginSlabMove(id, toTool(ev));
|
|
2488
|
-
if (((_a =
|
|
2616
|
+
if (((_a = currentGesture()) === null || _a === void 0 ? void 0 : _a.id) !== id)
|
|
2489
2617
|
return false;
|
|
2490
2618
|
const detachAll = () => {
|
|
2491
2619
|
window.removeEventListener('pointermove', onMove, true);
|
|
@@ -2495,7 +2623,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2495
2623
|
};
|
|
2496
2624
|
const onMove = (e) => {
|
|
2497
2625
|
var _a;
|
|
2498
|
-
if (disposed || ((_a =
|
|
2626
|
+
if (disposed || ((_a = currentGesture()) === null || _a === void 0 ? void 0 : _a.id) !== id)
|
|
2499
2627
|
return detachAll();
|
|
2500
2628
|
slabMove(toTool(e));
|
|
2501
2629
|
api.render();
|
|
@@ -2765,7 +2893,8 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2765
2893
|
gesture = {
|
|
2766
2894
|
kind: isResize ? 'resize' : 'move',
|
|
2767
2895
|
id: node.id,
|
|
2768
|
-
node,
|
|
2896
|
+
subject: 'node',
|
|
2897
|
+
entity: node,
|
|
2769
2898
|
pointerId: typeof PointerEvent !== 'undefined' && ev.source instanceof PointerEvent
|
|
2770
2899
|
? ev.source.pointerId
|
|
2771
2900
|
: null,
|
|
@@ -2860,606 +2989,29 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2860
2989
|
api.container.addEventListener('focusin', keyboard.onFocusIn);
|
|
2861
2990
|
api.container.addEventListener('keydown', keyboard.onKey);
|
|
2862
2991
|
// -- tab tear-out -----------------------------------------------------------
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
};
|
|
2887
|
-
// The board holds the NEW group, not the bare page: it is adopted under the
|
|
2888
|
-
// group's id, sized for the page plus its strip, taking the room under the
|
|
2889
|
-
// pointer with its top edge at the pointer — the tab is what the pointer
|
|
2890
|
-
// holds, the page hangs below it. NOT at the press: a ghost in the engine
|
|
2891
|
-
// costs rows, and on a fit board every tile squeezes the moment it enters,
|
|
2892
|
-
// so the ghost enters only when the pointer is over free board space, and
|
|
2893
|
-
// leaves whenever it is over a strip, a group or an edge. A full board that
|
|
2894
|
-
// refuses the ghost still lets the page join, reorder or split.
|
|
2895
|
-
let leg = null;
|
|
2896
|
-
/** The board holding the leg: null = this one, else the foreign peer under the pointer. */
|
|
2897
|
-
let legPeer = null;
|
|
2898
|
-
const ensureLeg = (world, peer = null) => {
|
|
2899
|
-
// The page TRAVELS at most half the board tall (the dock's measure): at
|
|
2900
|
-
// its natural height — a page fills its container, eight rows on the
|
|
2901
|
-
// demo — the ghost crossing the KPI row tore the whole dashboard apart
|
|
2902
|
-
// on its way to a cell (identification round, D1 03–08).
|
|
2903
|
-
const capPx = dockSpan.h * (rowHeightFor(geom(), rows()) + gap) - gap;
|
|
2904
|
-
const size = { width: plan.size.width, height: Math.min(plan.size.height, capPx) };
|
|
2905
|
-
// A different board under the pointer takes the leg over, the old one
|
|
2906
|
-
// back to its pre-entry layout first. An INNER tab used to know only
|
|
2907
|
-
// the board owning its container — over the main board the chip dimmed
|
|
2908
|
-
// and the release did nothing (identification round, L2).
|
|
2909
|
-
if (leg && legPeer !== peer) {
|
|
2910
|
-
leg.abort();
|
|
2911
|
-
leg = null;
|
|
2912
|
-
}
|
|
2913
|
-
if (!leg) {
|
|
2914
|
-
leg = peer ? peer.adopt({ id: plan.arrivingId }, world, size, { fit: 'shrink', anchor: 'top' }) : adopt({ id: plan.arrivingId }, world, size, { fit: 'shrink', anchor: 'top' });
|
|
2915
|
-
legPeer = leg ? peer : null;
|
|
2916
|
-
}
|
|
2917
|
-
return leg;
|
|
2918
|
-
};
|
|
2919
|
-
/** The deepest OTHER board under the point that may take the page — never one of the page's own boards. */
|
|
2920
|
-
const foreignAt = (wx, wy) => {
|
|
2921
|
-
let best = null;
|
|
2922
|
-
for (const p of peersOnCanvas()) {
|
|
2923
|
-
if (p === selfPeer || plan.ownBoards.includes(p.group.id))
|
|
2924
|
-
continue;
|
|
2925
|
-
if (!p.containsWorld(wx, wy))
|
|
2926
|
-
continue;
|
|
2927
|
-
if (!best || p.frameArea() < best.frameArea())
|
|
2928
|
-
best = p;
|
|
2929
|
-
}
|
|
2930
|
-
return best;
|
|
2931
|
-
};
|
|
2932
|
-
const naturalSpan = (() => {
|
|
2933
|
-
const sp = sizeToSpan(plan.size.width, plan.size.height, frame(), geom(), rows());
|
|
2934
|
-
return { w: Math.max(1, Math.min(columns, sp.w)), h: Math.max(TEAR_OUT_MIN_ROWS, sp.h) };
|
|
2935
|
-
})();
|
|
2936
|
-
// A DOCKED group takes at most HALF the board — VS Code's edge drop splits
|
|
2937
|
-
// the area in two. A page as tall as the container it left would
|
|
2938
|
-
// otherwise shove the whole dashboard out of view. Half of the board as
|
|
2939
|
-
// it stood at the press, and of what the canvas shows, whichever is less.
|
|
2940
|
-
const dockSpan = (() => {
|
|
2941
|
-
const rows0 = rows();
|
|
2942
|
-
const rect = api.container.getBoundingClientRect();
|
|
2943
|
-
const rh = rowHeightFor(geom(), rows0) + gap;
|
|
2944
|
-
const visible = rect.height > 0 && rh > 0 ? Math.max(1, Math.floor((rect.height + gap) / rh)) : rows0;
|
|
2945
|
-
const halfRows = Math.max(TEAR_OUT_MIN_ROWS, Math.ceil(Math.min(rows0, visible) / 2));
|
|
2946
|
-
const halfCols = Math.max(1, Math.ceil(columns / 2));
|
|
2947
|
-
return { w: Math.min(naturalSpan.w, halfCols), h: Math.min(naturalSpan.h, halfRows) };
|
|
2948
|
-
})();
|
|
2949
|
-
tearing = pageId;
|
|
2950
|
-
const doc = (_a = api.container.ownerDocument) !== null && _a !== void 0 ? _a : document;
|
|
2951
|
-
const chip = doc.createElement('div');
|
|
2952
|
-
chip.className = 'axdb-drag-chip axdb-tab-chip';
|
|
2953
|
-
chip.textContent = plan.label;
|
|
2954
|
-
doc.body.appendChild(chip);
|
|
2955
|
-
const moveChip = (cx, cy) => {
|
|
2956
|
-
chip.style.left = `${cx + 6}px`;
|
|
2957
|
-
chip.style.top = `${cy + 6}px`;
|
|
2958
|
-
};
|
|
2959
|
-
moveChip(ev.clientX, ev.clientY);
|
|
2960
|
-
armGlide();
|
|
2961
|
-
api.render();
|
|
2962
|
-
const layer = htmlLayer();
|
|
2963
|
-
const area = (g) => {
|
|
2964
|
-
const sz = sizeOf(g);
|
|
2965
|
-
return sz.width * sz.height;
|
|
2966
|
-
};
|
|
2967
|
-
const targets = plan.joinTargets
|
|
2968
|
-
.map((id) => diagram.getGroup(id))
|
|
2969
|
-
.filter((g) => !!g && g.id !== fromGroupId)
|
|
2970
|
-
.sort((a, b) => area(a) - area(b));
|
|
2971
|
-
// GEOMETRY MUST NOT MOVE WITH THE GHOST. On a fit board the rows squeeze
|
|
2972
|
-
// while the ghost occupies some and relax when it leaves, so a target's
|
|
2973
|
-
// frame changes with the very decision being made about it: a join makes
|
|
2974
|
-
// the ghost leave, the frame grows, and the same pointer now reads as a
|
|
2975
|
-
// split. The target and its frame are therefore anchored when the pointer
|
|
2976
|
-
// enters it and kept until it leaves; the board frame is the one at press.
|
|
2977
|
-
let anchor = null;
|
|
2978
|
-
const inRect = (r, wx, wy) => wx >= r.x && wx <= r.x + r.width && wy >= r.y && wy <= r.y + r.height;
|
|
2979
|
-
const targetAt = (wx, wy) => {
|
|
2980
|
-
var _a;
|
|
2981
|
-
if (anchor && inRect(anchor.frame, wx, wy))
|
|
2982
|
-
return anchor.g;
|
|
2983
|
-
const t = (_a = targets.find((x) => worldInsideGroup(x, wx, wy))) !== null && _a !== void 0 ? _a : null;
|
|
2984
|
-
if (t)
|
|
2985
|
-
leg === null || leg === void 0 ? void 0 : leg.leave(); // the ghost leaves first, so the frame anchored is the relaxed one
|
|
2986
|
-
anchor = t ? { g: t, frame: frameOfGroup(t), stripH: plan.stripHeight(t.id) } : null;
|
|
2987
|
-
return t;
|
|
2988
|
-
};
|
|
2989
|
-
// THE BANDS ARE ON THE SCREEN. Dockview measures its container edges on
|
|
2990
|
-
// the element, and so must we: the camera moves during the drag (the
|
|
2991
|
-
// board glides, the ghost adds rows a bounded scroll answers to), so a
|
|
2992
|
-
// band fixed in world space at the press drifts away from the edge the
|
|
2993
|
-
// user sees. The frame is mapped to client space through the live
|
|
2994
|
-
// camera and clipped to the canvas, so a scrolled board's bands sit at
|
|
2995
|
-
// its VISIBLE edges.
|
|
2996
|
-
const ROOT_TOP = 20;
|
|
2997
|
-
const ROOT_BOTTOM = 20;
|
|
2998
|
-
const ROOT_SIDE = 40;
|
|
2999
|
-
// THE EDGE BANDS ARE A FIFTH OF THE BODY — Dockview's activation size, not
|
|
3000
|
-
// VS Code's third. Two thirds of every group read as "beside" under the
|
|
3001
|
-
// thirds, and the user, putting a page back into the panel it came from,
|
|
3002
|
-
// never found the "into": "close to the edge means beside it, the content
|
|
3003
|
-
// area means inside, the header means a new tab" (0.4.42).
|
|
3004
|
-
const EDGE_BAND = BESIDE_BAND; // one band for a tab's split and a widget's beside (zones.ts)
|
|
3005
|
-
const visibleFrame = () => {
|
|
3006
|
-
const rect = api.container.getBoundingClientRect();
|
|
3007
|
-
const o = toWorld(rect.left, rect.top);
|
|
3008
|
-
const u = toWorld(rect.left + 100, rect.top + 100);
|
|
3009
|
-
const sx = 100 / (u.x - o.x || 100);
|
|
3010
|
-
const sy = 100 / (u.y - o.y || 100);
|
|
3011
|
-
const f = frame();
|
|
3012
|
-
const left = rect.left + (f.x - o.x) * sx;
|
|
3013
|
-
const top = rect.top + (f.y - o.y) * sy;
|
|
3014
|
-
const right = left + f.width * sx;
|
|
3015
|
-
const bottom = top + f.height * sy;
|
|
3016
|
-
if (rect.width <= 0 || rect.height <= 0)
|
|
3017
|
-
return { left, top, right, bottom }; // unlaid-out: nothing to clip to
|
|
3018
|
-
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) };
|
|
3019
|
-
};
|
|
3020
|
-
/** 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. */
|
|
3021
|
-
const clientInsideCanvasGrace = (cx, cy) => {
|
|
3022
|
-
// The CANVAS, not this board's frame: a nested page's binder runs the
|
|
3023
|
-
// tear-out of the tabs inside it, and its pages must still land on the
|
|
3024
|
-
// boards around it. An unmeasurable container (no layout) is never off.
|
|
3025
|
-
const rect = api.container.getBoundingClientRect();
|
|
3026
|
-
if (rect.width <= 0 || rect.height <= 0)
|
|
3027
|
-
return true;
|
|
3028
|
-
return cx >= rect.left - EDGE_GRACE && cx <= rect.right + EDGE_GRACE && cy >= rect.top - EDGE_GRACE && cy <= rect.bottom + EDGE_GRACE;
|
|
3029
|
-
};
|
|
3030
|
-
/**
|
|
3031
|
-
* The board's rows for a dock: as they stood BEFORE the ghost entered.
|
|
3032
|
-
* Read live, the ghost's own pushing inflated them on every re-entry —
|
|
3033
|
-
* side docks 14, 21, 104 rows tall, a bottom dock landing at row 29.
|
|
3034
|
-
*/
|
|
3035
|
-
const baseRows = () => {
|
|
3036
|
-
const b = leg === null || leg === void 0 ? void 0 : leg.baseline();
|
|
3037
|
-
if (!b)
|
|
3038
|
-
return rowsWithout(plan.arrivingId);
|
|
3039
|
-
let r = 0;
|
|
3040
|
-
for (const c of b.values())
|
|
3041
|
-
r = Math.max(r, c.y + c.h);
|
|
3042
|
-
return r;
|
|
3043
|
-
};
|
|
3044
|
-
const worldToClient = (wx, wy) => {
|
|
3045
|
-
const rect = api.container.getBoundingClientRect();
|
|
3046
|
-
const o = toWorld(rect.left, rect.top);
|
|
3047
|
-
const u = toWorld(rect.left + 100, rect.top + 100);
|
|
3048
|
-
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)) };
|
|
3049
|
-
};
|
|
3050
|
-
/**
|
|
3051
|
-
* Would the page land where the user cannot see it? A cell whose room
|
|
3052
|
-
* lies below the fold (the only cell left under a locked section — D1
|
|
3053
|
-
* hold3) used to take the drop a screen away. Hidden = not one pixel of
|
|
3054
|
-
* the cell inside the visible canvas: a landing whose top edge shows,
|
|
3055
|
-
* the rest below the fold of a small canvas, is a landing the user can
|
|
3056
|
-
* see and a grow board scrolls to (lab L61 dropped at the canvas foot).
|
|
3057
|
-
*/
|
|
3058
|
-
const landingHidden = () => {
|
|
3059
|
-
const r = leg === null || leg === void 0 ? void 0 : leg.rect();
|
|
3060
|
-
const rect = api.container.getBoundingClientRect();
|
|
3061
|
-
if (!r || rect.width <= 0 || rect.height <= 0)
|
|
3062
|
-
return false;
|
|
3063
|
-
const tl = worldToClient(r.x, r.y);
|
|
3064
|
-
const br = worldToClient(r.x + r.width, r.y + r.height);
|
|
3065
|
-
return tl.y >= rect.bottom || br.y <= rect.top || tl.x >= rect.right || br.x <= rect.left;
|
|
3066
|
-
};
|
|
3067
|
-
const rootAt = (cx, cy) => {
|
|
3068
|
-
// The bands reach a little OUTSIDE the frame too: a pointer that
|
|
3069
|
-
// overshoots the board's top edge by a few pixels means "the top".
|
|
3070
|
-
const v = visibleFrame();
|
|
3071
|
-
if (cx < v.left - ROOT_SIDE || cx > v.right + ROOT_SIDE || cy < v.top - ROOT_TOP || cy > v.bottom + ROOT_BOTTOM)
|
|
3072
|
-
return null;
|
|
3073
|
-
const rows = Math.max(TEAR_OUT_MIN_ROWS, baseRows());
|
|
3074
|
-
const w = dockSpan.w;
|
|
3075
|
-
const h = dockSpan.h;
|
|
3076
|
-
if (cy - v.top <= ROOT_TOP)
|
|
3077
|
-
return { kind: 'root', side: 'top', cell: { x: 0, y: 0, w: columns, h } };
|
|
3078
|
-
if (cx - v.left <= ROOT_SIDE)
|
|
3079
|
-
return { kind: 'root', side: 'left', cell: { x: 0, y: 0, w, h: rows } };
|
|
3080
|
-
if (v.right - cx <= ROOT_SIDE)
|
|
3081
|
-
return { kind: 'root', side: 'right', cell: { x: Math.max(0, columns - w), y: 0, w, h: rows } };
|
|
3082
|
-
if (v.bottom - cy <= ROOT_BOTTOM)
|
|
3083
|
-
return { kind: 'root', side: 'bottom', cell: { x: 0, y: rows, w: columns, h } };
|
|
3084
|
-
return null;
|
|
3085
|
-
};
|
|
3086
|
-
const rowsWithout = (id) => {
|
|
3087
|
-
let r = 0;
|
|
3088
|
-
for (const it of engine.getItems())
|
|
3089
|
-
if (it.id !== id)
|
|
3090
|
-
r = Math.max(r, it.y + it.h);
|
|
3091
|
-
return r;
|
|
3092
|
-
};
|
|
3093
|
-
/** A split being previewed: the target shrunk to its half, to be restored when the pointer leaves. */
|
|
3094
|
-
let split = null;
|
|
3095
|
-
const halves = (target, side) => {
|
|
3096
|
-
// The cell to halve is the target's own — not the half it is already
|
|
3097
|
-
// shrunk to while a split is being previewed.
|
|
3098
|
-
const live = engine.getItem(target.id);
|
|
3099
|
-
const it = split && split.id === target.id ? split.before : live;
|
|
3100
|
-
if (!it || !live)
|
|
3101
|
-
return null; // a group on another board: no cell of ours to halve
|
|
3102
|
-
if (side === 'left' || side === 'right') {
|
|
3103
|
-
if (it.w < 2)
|
|
3104
|
-
return null;
|
|
3105
|
-
const a = Math.ceil(it.w / 2);
|
|
3106
|
-
const b = it.w - a;
|
|
3107
|
-
return side === 'right'
|
|
3108
|
-
? { keep: { x: it.x, y: it.y, w: a, h: it.h }, born: { x: it.x + a, y: it.y, w: b, h: it.h } }
|
|
3109
|
-
: { keep: { x: it.x + b, y: it.y, w: a, h: it.h }, born: { x: it.x, y: it.y, w: b, h: it.h } };
|
|
3110
|
-
}
|
|
3111
|
-
if (it.h < 2 * TEAR_OUT_MIN_ROWS)
|
|
3112
|
-
return null;
|
|
3113
|
-
const a = Math.ceil(it.h / 2);
|
|
3114
|
-
const b = it.h - a;
|
|
3115
|
-
return side === 'bottom'
|
|
3116
|
-
? { keep: { x: it.x, y: it.y, w: it.w, h: a }, born: { x: it.x, y: it.y + a, w: it.w, h: b } }
|
|
3117
|
-
: { 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 } };
|
|
3118
|
-
};
|
|
3119
|
-
const zoneAt = (cx, cy, world) => {
|
|
3120
|
-
// A pointer OUTSIDE the visible canvas is off, full stop: a camera that
|
|
3121
|
-
// moved can map it to a world point inside a group, and that group
|
|
3122
|
-
// took a join or a split from a release in the page header. The
|
|
3123
|
-
// target is looked up (and anchored) only inside it.
|
|
3124
|
-
const clientInside = clientInsideCanvasGrace(cx, cy);
|
|
3125
|
-
const target = clientInside ? targetAt(world.x, world.y) : null;
|
|
3126
|
-
const root = clientInside ? rootAt(cx, cy) : null;
|
|
3127
|
-
const foreign = foreignAt(world.x, world.y);
|
|
3128
|
-
// The ORDER is zones.ts's, shared with the split board: a strip, the
|
|
3129
|
-
// board's own edges, the target's fifth or middle, home, then a board.
|
|
3130
|
-
const tz = resolveTabZone({
|
|
3131
|
-
x: world.x,
|
|
3132
|
-
y: world.y,
|
|
3133
|
-
clientInside,
|
|
3134
|
-
ownStrip: plan.stripIndex(fromGroupId, cx, cy),
|
|
3135
|
-
stripOf: (id) => plan.stripIndex(id, cx, cy),
|
|
3136
|
-
root: root && root.kind === 'root' ? { side: root.side } : null,
|
|
3137
|
-
target: target
|
|
3138
|
-
? {
|
|
3139
|
-
id: target.id,
|
|
3140
|
-
frame: anchor && anchor.g === target ? anchor.frame : frameOfGroup(target),
|
|
3141
|
-
stripHeight: anchor && anchor.g === target ? anchor.stripH : plan.stripHeight(target.id),
|
|
3142
|
-
}
|
|
3143
|
-
: null,
|
|
3144
|
-
home: worldInsideGroup(from, world.x, world.y) ? frameOfGroup(from) : null,
|
|
3145
|
-
homeBand: 0, // on a grid board the whole source frame is home
|
|
3146
|
-
band: EDGE_BAND,
|
|
3147
|
-
canSplit: (_id, side) => !!target && !!halves(target, side),
|
|
3148
|
-
pane: false,
|
|
3149
|
-
foreign: !!foreign && (!worldInsideBoard(world.x, world.y) || foreign.frameArea() < boardArea()),
|
|
3150
|
-
});
|
|
3151
|
-
switch (tz.kind) {
|
|
3152
|
-
case 'off':
|
|
3153
|
-
return { kind: 'off' };
|
|
3154
|
-
case 'strip':
|
|
3155
|
-
return { kind: 'strip', target: target, index: tz.index };
|
|
3156
|
-
case 'reorder':
|
|
3157
|
-
return { kind: 'reorder', index: tz.index };
|
|
3158
|
-
case 'root':
|
|
3159
|
-
return root;
|
|
3160
|
-
case 'join':
|
|
3161
|
-
return { kind: 'join', target: target };
|
|
3162
|
-
case 'split': {
|
|
3163
|
-
const h = halves(target, tz.side);
|
|
3164
|
-
return h ? Object.assign({ kind: 'split', target: target, side: tz.side }, h) : { kind: 'join', target: target };
|
|
3165
|
-
}
|
|
3166
|
-
case 'home':
|
|
3167
|
-
return { kind: 'home' };
|
|
3168
|
-
default:
|
|
3169
|
-
return tz.kind === 'board' && tz.foreign && foreign ? { kind: 'board', peer: foreign } : { kind: 'board' };
|
|
3170
|
-
}
|
|
3171
|
-
};
|
|
3172
|
-
const zoneKey = (z) => JSON.stringify(z, (k, v) => (k === 'target' ? v.id : k === 'peer' ? v.group.id : v));
|
|
3173
|
-
// A TOP DOCK INSERTS ROWS. The engine's push cascade resolves the band's
|
|
3174
|
-
// collisions one tile at a time and scrambles what stood beneath it (the
|
|
3175
|
-
// demo's KPI row came apart, two of its tiles under the chart). VS Code
|
|
3176
|
-
// shoves the whole area down intact, so after the ghost takes the band
|
|
3177
|
-
// every tile that stood at or below it is put back at its own column,
|
|
3178
|
-
// exactly the band's height lower — a translation of a layout without
|
|
3179
|
-
// overlaps has none — and comes back the same way when the pointer leaves.
|
|
3180
|
-
// The layout translated is the one from BEFORE the ghost entered (the
|
|
3181
|
-
// leg's baseline), not the engine's mid-gesture state: the adoption
|
|
3182
|
-
// itself pushes tiles about, and a snapshot taken after it would carry
|
|
3183
|
-
// that cascade into the insert.
|
|
3184
|
-
let inserted = null;
|
|
3185
|
-
const insertRows = (cell, l) => {
|
|
3186
|
-
if (cell.w < columns)
|
|
3187
|
-
return; // a side dock has no whole rows to insert
|
|
3188
|
-
inserted = l.baseline();
|
|
3189
|
-
for (const [id, c] of inserted) {
|
|
3190
|
-
const it = engine.getItem(id);
|
|
3191
|
-
if (!it)
|
|
3192
|
-
continue;
|
|
3193
|
-
it.x = c.x;
|
|
3194
|
-
it.y = c.y >= cell.y ? c.y + cell.h : c.y;
|
|
3195
|
-
}
|
|
3196
|
-
};
|
|
3197
|
-
const undoInsertRows = () => {
|
|
3198
|
-
if (!inserted)
|
|
3199
|
-
return;
|
|
3200
|
-
leg === null || leg === void 0 ? void 0 : leg.leave(); // the ghost frees its rows first
|
|
3201
|
-
for (const [id, c] of inserted) {
|
|
3202
|
-
const it = engine.getItem(id);
|
|
3203
|
-
if (it) {
|
|
3204
|
-
it.x = c.x;
|
|
3205
|
-
it.y = c.y;
|
|
3206
|
-
}
|
|
3207
|
-
}
|
|
3208
|
-
inserted = null;
|
|
3209
|
-
project();
|
|
3210
|
-
};
|
|
3211
|
-
let joinEl = null;
|
|
3212
|
-
const showOverlay = (r) => {
|
|
3213
|
-
if (!layer)
|
|
3214
|
-
return;
|
|
3215
|
-
if (!joinEl) {
|
|
3216
|
-
joinEl = doc.createElement('div');
|
|
3217
|
-
joinEl.className = 'axdb-join';
|
|
3218
|
-
layer.prepend(joinEl);
|
|
3219
|
-
}
|
|
3220
|
-
joinEl.style.left = `${r.x}px`;
|
|
3221
|
-
joinEl.style.top = `${r.y}px`;
|
|
3222
|
-
joinEl.style.width = `${r.width}px`;
|
|
3223
|
-
joinEl.style.height = `${r.height}px`;
|
|
3224
|
-
};
|
|
3225
|
-
const hideOverlay = () => {
|
|
3226
|
-
joinEl === null || joinEl === void 0 ? void 0 : joinEl.remove();
|
|
3227
|
-
joinEl = null;
|
|
3228
|
-
};
|
|
3229
|
-
const undoSplitPreview = () => {
|
|
3230
|
-
if (!split)
|
|
3231
|
-
return;
|
|
3232
|
-
const it = engine.getItem(split.id);
|
|
3233
|
-
leg === null || leg === void 0 ? void 0 : leg.leave(); // the born half must be free before the target grows back into it
|
|
3234
|
-
if (it) {
|
|
3235
|
-
if (it.x !== split.before.x || it.y !== split.before.y)
|
|
3236
|
-
engine.moveCheck(split.id, split.before.x, split.before.y, { gate: false });
|
|
3237
|
-
if (it.w !== split.before.w || it.h !== split.before.h)
|
|
3238
|
-
engine.resizeCheck(split.id, split.before.w, split.before.h);
|
|
3239
|
-
}
|
|
3240
|
-
split = null;
|
|
3241
|
-
project();
|
|
3242
|
-
};
|
|
3243
|
-
const previewSplit = (z, world) => {
|
|
3244
|
-
const it = engine.getItem(z.target.id);
|
|
3245
|
-
if (!it)
|
|
3246
|
-
return false;
|
|
3247
|
-
// The target's cell AT REST is the one the commit restores — read it
|
|
3248
|
-
// BEFORE the leg enters: the arriving ghost is placed under the pointer
|
|
3249
|
-
// first, and a target that is a tile like any other is pushed by it
|
|
3250
|
-
// for a moment (tile first, step 3) until it takes its half below.
|
|
3251
|
-
const before = { x: it.x, y: it.y, w: it.w, h: it.h };
|
|
3252
|
-
const frameBefore = frameOfGroup(z.target);
|
|
3253
|
-
const l = ensureLeg(world, null);
|
|
3254
|
-
if (!l)
|
|
3255
|
-
return false;
|
|
3256
|
-
split = { id: z.target.id, before, frameBefore, keep: z.keep };
|
|
3257
|
-
if (it.w !== z.keep.w || it.h !== z.keep.h)
|
|
3258
|
-
engine.resizeCheck(z.target.id, z.keep.w, z.keep.h);
|
|
3259
|
-
const now = engine.getItem(z.target.id);
|
|
3260
|
-
if (now && (now.x !== z.keep.x || now.y !== z.keep.y))
|
|
3261
|
-
engine.moveCheck(z.target.id, z.keep.x, z.keep.y, { gate: false });
|
|
3262
|
-
const ok = l.place(z.born);
|
|
3263
|
-
project();
|
|
3264
|
-
hidePlaceholder(); // the accent overlay says which half; the grey placeholder under it is noise
|
|
3265
|
-
return ok;
|
|
3266
|
-
};
|
|
3267
|
-
/** The band a dock is promised: the cell it takes — a BOTTOM dock's lies past the last row, so its tint sits on the board's last rows instead. */
|
|
3268
|
-
const dockOverlayRect = (z) => {
|
|
3269
|
-
const cell = z.side === 'bottom' ? Object.assign(Object.assign({}, z.cell), { y: Math.max(0, z.cell.y - z.cell.h) }) : z.cell;
|
|
3270
|
-
return cellToRect(cell, frame(), geom(), rows());
|
|
3271
|
-
};
|
|
3272
|
-
/** The dock, applied for real at the release: the ghost takes the band, every member group is pushed, a top dock inserts rows. */
|
|
3273
|
-
const realizeDock = (z, world) => {
|
|
3274
|
-
const l = ensureLeg(world, null);
|
|
3275
|
-
if (!l)
|
|
3276
|
-
return false;
|
|
3277
|
-
l.place(z.cell, true); // DOCKING PUSHES SECTIONS: everything below the band moves down, solid or not
|
|
3278
|
-
insertRows(z.cell, l);
|
|
3279
|
-
project();
|
|
3280
|
-
hidePlaceholder();
|
|
3281
|
-
return true;
|
|
3282
|
-
};
|
|
3283
|
-
let zone = { kind: 'board' };
|
|
3284
|
-
let key = zoneKey(zone);
|
|
3285
|
-
const applyZone = (z, world) => {
|
|
3286
|
-
var _a, _b, _c;
|
|
3287
|
-
const k = zoneKey(z);
|
|
3288
|
-
const same = k === key;
|
|
3289
|
-
key = k;
|
|
3290
|
-
zone = z;
|
|
3291
|
-
if (same) {
|
|
3292
|
-
if (z.kind === 'board')
|
|
3293
|
-
(_b = ensureLeg(world, (_a = z.peer) !== null && _a !== void 0 ? _a : null)) === null || _b === void 0 ? void 0 : _b.move(world);
|
|
3294
|
-
return;
|
|
3295
|
-
}
|
|
3296
|
-
undoSplitPreview();
|
|
3297
|
-
undoInsertRows();
|
|
3298
|
-
plan.markDrop(null, null);
|
|
3299
|
-
hideOverlay();
|
|
3300
|
-
// A PREVIEW IS AN OVERLAY. A split and a dock used to be applied LIVE
|
|
3301
|
-
// while the pointer hovered — the target halved and moved to its other
|
|
3302
|
-
// half, a top dock shoved every tile down — so the very group the user
|
|
3303
|
-
// was entering jumped away from under the pointer (the fluid demo's
|
|
3304
|
-
// side panel went off the screen in one frame as a page came back to
|
|
3305
|
-
// it). VS Code, Dockview and Golden Layout tint the half or the band and
|
|
3306
|
-
// move nothing until the drop; so does this now: the layout stays where
|
|
3307
|
-
// the user sees it, and the halving or the row insert happens on release.
|
|
3308
|
-
switch (z.kind) {
|
|
3309
|
-
case 'strip':
|
|
3310
|
-
leg === null || leg === void 0 ? void 0 : leg.leave();
|
|
3311
|
-
showOverlay(frameOfGroup(z.target));
|
|
3312
|
-
plan.markDrop(z.target.id, z.index);
|
|
3313
|
-
break;
|
|
3314
|
-
case 'join':
|
|
3315
|
-
leg === null || leg === void 0 ? void 0 : leg.leave();
|
|
3316
|
-
showOverlay(frameOfGroup(z.target));
|
|
3317
|
-
break;
|
|
3318
|
-
case 'split':
|
|
3319
|
-
leg === null || leg === void 0 ? void 0 : leg.leave(); // the ghost leaves first, so the half is measured on the relaxed board
|
|
3320
|
-
showOverlay(cellToRect(z.born, frame(), geom(), rows()));
|
|
3321
|
-
break;
|
|
3322
|
-
case 'root':
|
|
3323
|
-
leg === null || leg === void 0 ? void 0 : leg.leave();
|
|
3324
|
-
showOverlay(dockOverlayRect(z));
|
|
3325
|
-
break;
|
|
3326
|
-
case 'reorder':
|
|
3327
|
-
leg === null || leg === void 0 ? void 0 : leg.leave();
|
|
3328
|
-
plan.markDrop(fromGroupId, z.index);
|
|
3329
|
-
break;
|
|
3330
|
-
case 'home':
|
|
3331
|
-
case 'off':
|
|
3332
|
-
leg === null || leg === void 0 ? void 0 : leg.leave();
|
|
3333
|
-
break;
|
|
3334
|
-
case 'board': {
|
|
3335
|
-
const l = ensureLeg(world, (_c = z.peer) !== null && _c !== void 0 ? _c : null);
|
|
3336
|
-
l === null || l === void 0 ? void 0 : l.enter(world);
|
|
3337
|
-
break;
|
|
3338
|
-
}
|
|
3339
|
-
}
|
|
3340
|
-
};
|
|
3341
|
-
let last = { x: ev.clientX, y: ev.clientY };
|
|
3342
|
-
const detach = () => {
|
|
3343
|
-
window.removeEventListener('pointermove', onMove, true);
|
|
3344
|
-
window.removeEventListener('pointerup', onUp, true);
|
|
3345
|
-
window.removeEventListener('pointercancel', onCancel, true);
|
|
3346
|
-
window.removeEventListener('keydown', onKey, true);
|
|
3347
|
-
};
|
|
3348
|
-
const onMove = (e) => {
|
|
3349
|
-
if (disposed)
|
|
3350
|
-
return detach();
|
|
3351
|
-
last = { x: e.clientX, y: e.clientY };
|
|
3352
|
-
moveChip(e.clientX, e.clientY);
|
|
3353
|
-
const world = toWorld(e.clientX, e.clientY);
|
|
3354
|
-
const z = zoneAt(e.clientX, e.clientY, world);
|
|
3355
|
-
applyZone(z, world);
|
|
3356
|
-
// Dimmed = a release here does nothing: home, off the board, or a board
|
|
3357
|
-
// that refused the ghost (bounded and full).
|
|
3358
|
-
chip.classList.toggle('axdb-out', zone.kind === 'home' || zone.kind === 'off' || (zone.kind === 'board' && (!leg || landingHidden())));
|
|
3359
|
-
api.render();
|
|
3360
|
-
};
|
|
3361
|
-
const done = (changed, kind) => {
|
|
3362
|
-
var _a;
|
|
3363
|
-
disarmGlideSoon();
|
|
3364
|
-
enforceBoardHeight();
|
|
3365
|
-
persistLayouts();
|
|
3366
|
-
api.renderNow();
|
|
3367
|
-
(_a = options.onGesture) === null || _a === void 0 ? void 0 : _a.call(options, { type: kind, kind: 'move', nodeId: pageId, changed });
|
|
3368
|
-
};
|
|
3369
|
-
const finish = (commit) => {
|
|
3370
|
-
var _a, _b, _c, _d;
|
|
3371
|
-
detach();
|
|
3372
|
-
chip.remove();
|
|
3373
|
-
hideOverlay();
|
|
3374
|
-
plan.markDrop(null, null);
|
|
3375
|
-
tearing = null;
|
|
3376
|
-
if (disposed)
|
|
3377
|
-
return;
|
|
3378
|
-
const world = toWorld(last.x, last.y);
|
|
3379
|
-
const z = commit ? zoneAt(last.x, last.y, world) : { kind: 'home' };
|
|
3380
|
-
if (z.kind !== 'root')
|
|
3381
|
-
undoInsertRows();
|
|
3382
|
-
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()))) {
|
|
3383
|
-
undoSplitPreview();
|
|
3384
|
-
leg === null || leg === void 0 ? void 0 : leg.abort();
|
|
3385
|
-
done(false, 'cancel');
|
|
3386
|
-
return;
|
|
3387
|
-
}
|
|
3388
|
-
if (z.kind === 'reorder') {
|
|
3389
|
-
undoSplitPreview();
|
|
3390
|
-
leg === null || leg === void 0 ? void 0 : leg.abort();
|
|
3391
|
-
const cmds = plan.reorder(z.index);
|
|
3392
|
-
const changed = cmds.length > 0 ? execute('Reorder tab', cmds) : false;
|
|
3393
|
-
done(changed, changed ? 'commit' : 'cancel');
|
|
3394
|
-
return;
|
|
3395
|
-
}
|
|
3396
|
-
if (z.kind === 'strip' || z.kind === 'join') {
|
|
3397
|
-
// JOIN: no cell on this board — the leg is abandoned (its displaced
|
|
3398
|
-
// tiles are already home) and the page becomes the target's tab.
|
|
3399
|
-
undoSplitPreview();
|
|
3400
|
-
const index = z.kind === 'strip' ? z.index : plan.dropIndex(z.target.id, last.x, last.y);
|
|
3401
|
-
leg === null || leg === void 0 ? void 0 : leg.abort();
|
|
3402
|
-
const planned = plan.join(z.target.id, index, group.id);
|
|
3403
|
-
done(execute('Move tab', [...planned.move, ...planned.collapse]), 'commit');
|
|
3404
|
-
return;
|
|
3405
|
-
}
|
|
3406
|
-
if (z.kind === 'split') {
|
|
3407
|
-
// SPLIT: the target keeps one half of its cell, the page's new group the
|
|
3408
|
-
// other. The hover only tinted the half; the halving happens NOW.
|
|
3409
|
-
hideOverlay();
|
|
3410
|
-
plan.markDrop(null, null);
|
|
3411
|
-
if (!previewSplit(z, world)) {
|
|
3412
|
-
// The half cannot be taken (a bounded board with no room for the
|
|
3413
|
-
// ghost): the page joins the target instead, as it would have had
|
|
3414
|
-
// the target been too small to halve.
|
|
3415
|
-
leg === null || leg === void 0 ? void 0 : leg.abort();
|
|
3416
|
-
const planned = plan.join(z.target.id, plan.dropIndex(z.target.id, last.x, last.y), group.id);
|
|
3417
|
-
done(execute('Move tab', [...planned.move, ...planned.collapse]), 'commit');
|
|
3418
|
-
return;
|
|
3419
|
-
}
|
|
3420
|
-
const fin = (_b = leg === null || leg === void 0 ? void 0 : leg.finalize()) !== null && _b !== void 0 ? _b : null;
|
|
3421
|
-
const halved = !!split && !!engine.getItem(z.target.id);
|
|
3422
|
-
split = null;
|
|
3423
|
-
if (!fin || !halved) {
|
|
3424
|
-
leg === null || leg === void 0 ? void 0 : leg.abort();
|
|
3425
|
-
done(false, 'cancel');
|
|
3426
|
-
return;
|
|
3427
|
-
}
|
|
3428
|
-
const planned = plan.commands(fin.cell, fin.rect, group.id);
|
|
3429
|
-
// The target's halving is in the leg's own deltas: its cell at rest was
|
|
3430
|
-
// snapshotted when the ghost entered, and its half is where it stands now.
|
|
3431
|
-
const changed = execute('Split group', [...fin.commands, ...planned.move, ...planned.collapse]);
|
|
3432
|
-
done(changed, 'commit');
|
|
3433
|
-
return;
|
|
3434
|
-
}
|
|
3435
|
-
// ROOT DOCK: the hover only tinted the band; the ghost takes it NOW. A
|
|
3436
|
-
// free cell on the board: the leg already sits where it will land.
|
|
3437
|
-
if (z.kind === 'root')
|
|
3438
|
-
realizeDock(z, world);
|
|
3439
|
-
else if (zoneKey(zone) !== zoneKey(z))
|
|
3440
|
-
applyZone(z, world);
|
|
3441
|
-
hideOverlay(); // a re-applied zone repaints its preview; the release is not a preview
|
|
3442
|
-
plan.markDrop(null, null);
|
|
3443
|
-
const fin = (_c = leg === null || leg === void 0 ? void 0 : leg.finalize()) !== null && _c !== void 0 ? _c : null;
|
|
3444
|
-
if (!fin) {
|
|
3445
|
-
done(false, 'cancel');
|
|
3446
|
-
return;
|
|
3447
|
-
}
|
|
3448
|
-
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);
|
|
3449
|
-
done(execute(z.kind === 'root' ? 'Dock tab' : 'Move tab out', [...fin.commands, ...planned.move, ...planned.collapse]), 'commit');
|
|
3450
|
-
};
|
|
3451
|
-
const onUp = () => finish(true);
|
|
3452
|
-
const onCancel = () => finish(false);
|
|
3453
|
-
const onKey = (e) => {
|
|
3454
|
-
if (e.key === 'Escape')
|
|
3455
|
-
finish(false);
|
|
3456
|
-
};
|
|
3457
|
-
window.addEventListener('pointermove', onMove, true);
|
|
3458
|
-
window.addEventListener('pointerup', onUp, true);
|
|
3459
|
-
window.addEventListener('pointercancel', onCancel, true);
|
|
3460
|
-
window.addEventListener('keydown', onKey, true);
|
|
3461
|
-
return true;
|
|
3462
|
-
};
|
|
2992
|
+
// -- tab tear-out (tear-out.ts) --------------------------------------------
|
|
2993
|
+
const beginTearOut = createTearOut(ctx, {
|
|
2994
|
+
columns: () => columns,
|
|
2995
|
+
adopt: (node, world, pxSize, opts) => adopt(node, world, pxSize, opts),
|
|
2996
|
+
boardArea,
|
|
2997
|
+
peersOnCanvas,
|
|
2998
|
+
selfPeer: () => selfPeer,
|
|
2999
|
+
worldInsideBoard,
|
|
3000
|
+
worldInsideGroup,
|
|
3001
|
+
frameOfGroup,
|
|
3002
|
+
execute,
|
|
3003
|
+
project,
|
|
3004
|
+
hidePlaceholder,
|
|
3005
|
+
armGlide,
|
|
3006
|
+
disarmGlideSoon,
|
|
3007
|
+
enforceBoardHeight,
|
|
3008
|
+
persistLayouts,
|
|
3009
|
+
busy: () => !!gesture || !!slabGesture,
|
|
3010
|
+
tearing: () => tearing,
|
|
3011
|
+
setTearing: (id) => {
|
|
3012
|
+
tearing = id;
|
|
3013
|
+
},
|
|
3014
|
+
});
|
|
3463
3015
|
// -- palette drag-in --------------------------------------------------------
|
|
3464
3016
|
const beginPaletteDrag = (node, spec, event) => {
|
|
3465
3017
|
var _a, _b;
|
|
@@ -3475,7 +3027,8 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
3475
3027
|
const g = {
|
|
3476
3028
|
kind: 'palette',
|
|
3477
3029
|
id: node.id,
|
|
3478
|
-
node,
|
|
3030
|
+
subject: 'node',
|
|
3031
|
+
entity: node,
|
|
3479
3032
|
pointerId: (_b = event.pointerId) !== null && _b !== void 0 ? _b : null,
|
|
3480
3033
|
started: false,
|
|
3481
3034
|
downClient: { x: event.clientX, y: event.clientY },
|