@grafloria/element 0.4.52 → 0.4.54
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/commit.d.ts +82 -0
- package/src/lib/dashboard-kit/commit.js +121 -0
- package/src/lib/dashboard-kit/dashboard.d.ts +11 -3
- package/src/lib/dashboard-kit/dashboard.js +11 -6
- package/src/lib/dashboard-kit/grid-binder.d.ts +33 -40
- package/src/lib/dashboard-kit/grid-binder.js +328 -351
- package/src/lib/dashboard-kit/split-binder.js +1 -1
|
@@ -50,13 +50,15 @@
|
|
|
50
50
|
* second `bindDashboardGrid` on the section for a nested pack grid.
|
|
51
51
|
*/
|
|
52
52
|
import { __awaiter } from "tslib";
|
|
53
|
-
import { AddToGroupCommand, BatchCommand,
|
|
53
|
+
import { AddToGroupCommand, BatchCommand, GridPackEngine, RemoveFromGroupCommand, } from '@grafloria/engine';
|
|
54
54
|
import { LiveRegionController, registerTool } from '@grafloria/renderer';
|
|
55
55
|
import { buildCommitCommands, cellFromGridItem, cellToRect, columnUnitFor, gridItemFromCell, pointToCell, rowHeightFor, sizeToSpan, } from './grid-mapping.js';
|
|
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
59
|
import { BESIDE_BAND, resolve as resolveZone, resolveTabZone } from './zones.js';
|
|
60
|
+
import { SequenceCommand, SetGroupCellCommand, tileCommands } from './commit.js';
|
|
61
|
+
export { SequenceCommand, SetGroupCellCommand } from './commit.js';
|
|
60
62
|
/** The class of the painted grip element (a child of the node host). */
|
|
61
63
|
export const GRIP_CLASS = 'axdb-grip';
|
|
62
64
|
/** The container class that turns the caption strip's grip dots on. */
|
|
@@ -158,38 +160,6 @@ export function pressOnDragHandle(sel, target, hostEl, clientX, clientY) {
|
|
|
158
160
|
export const CAPTION_BAND = 28;
|
|
159
161
|
/** A torn-out page never arrives shorter than this: a strip with no room under it is not a group. */
|
|
160
162
|
export const TEAR_OUT_MIN_ROWS = 2;
|
|
161
|
-
/**
|
|
162
|
-
* Steps that depend on each other's RESULT — create a group, then move a page
|
|
163
|
-
* into it, then place the group on the board; or remove a member and then the
|
|
164
|
-
* group it emptied — run as one history step. A batch checks every member's
|
|
165
|
-
* `canExecute` before running any of them and every member's `canUndo` before
|
|
166
|
-
* undoing any, and a membership command's precondition (its group exists) is
|
|
167
|
-
* exactly what a neighbouring step creates or removes. This runs the chain in
|
|
168
|
-
* order and reverses it on undo, judging nothing up front.
|
|
169
|
-
*/
|
|
170
|
-
export class SequenceCommand extends Command {
|
|
171
|
-
constructor(name, steps) {
|
|
172
|
-
super(name);
|
|
173
|
-
this.steps = steps;
|
|
174
|
-
}
|
|
175
|
-
execute(context) {
|
|
176
|
-
for (const c of this.steps)
|
|
177
|
-
c.execute(context);
|
|
178
|
-
}
|
|
179
|
-
undo(context) {
|
|
180
|
-
for (let i = this.steps.length - 1; i >= 0; i--)
|
|
181
|
-
this.steps[i].undo(context);
|
|
182
|
-
}
|
|
183
|
-
canExecute() {
|
|
184
|
-
return true;
|
|
185
|
-
}
|
|
186
|
-
canUndo() {
|
|
187
|
-
return true;
|
|
188
|
-
}
|
|
189
|
-
serialize() {
|
|
190
|
-
return { id: this.id, name: this.name, timestamp: this.timestamp, data: { steps: this.steps.map((c) => c.serialize()) } };
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
163
|
/**
|
|
194
164
|
* Binders on the same canvas find each other here. A WeakMap: a canvas that
|
|
195
165
|
* is gone takes its (empty) peer set with it — the strong Map it replaced kept
|
|
@@ -256,50 +226,6 @@ function directionName(dx, dy) {
|
|
|
256
226
|
function describeCell(c) {
|
|
257
227
|
return `column ${c.x + 1}, row ${c.y + 1}, ${c.w} by ${c.h}`;
|
|
258
228
|
}
|
|
259
|
-
/**
|
|
260
|
-
* Undoable cell+frame write for a GROUP member (the strip's slab). The engine
|
|
261
|
-
* has Move/Resize commands for nodes but none for a group's frame, and slab
|
|
262
|
-
* cells live in group metadata — this closes nested height escalation into
|
|
263
|
-
* the gesture's single BatchCommand so one undo restores the strip too.
|
|
264
|
-
*/
|
|
265
|
-
class SetGroupCellCommand extends Command {
|
|
266
|
-
constructor(groupId, cellBefore, cellAfter, frameBefore, frameAfter) {
|
|
267
|
-
super('Resize section');
|
|
268
|
-
this.groupId = groupId;
|
|
269
|
-
this.cellBefore = cellBefore;
|
|
270
|
-
this.cellAfter = cellAfter;
|
|
271
|
-
this.frameBefore = frameBefore;
|
|
272
|
-
this.frameAfter = frameAfter;
|
|
273
|
-
}
|
|
274
|
-
apply(context, cell, frame) {
|
|
275
|
-
const diagram = context.diagram;
|
|
276
|
-
const grp = diagram === null || diagram === void 0 ? void 0 : diagram.getGroup(this.groupId);
|
|
277
|
-
if (!grp)
|
|
278
|
-
return;
|
|
279
|
-
grp.setMetadata('gridItem', gridItemFromCell(cell));
|
|
280
|
-
grp.setFrame(Object.assign({}, frame));
|
|
281
|
-
}
|
|
282
|
-
execute(context) {
|
|
283
|
-
this.apply(context, this.cellAfter, this.frameAfter);
|
|
284
|
-
}
|
|
285
|
-
undo(context) {
|
|
286
|
-
this.apply(context, this.cellBefore, this.frameBefore);
|
|
287
|
-
}
|
|
288
|
-
serialize() {
|
|
289
|
-
return {
|
|
290
|
-
id: this.id,
|
|
291
|
-
name: this.name,
|
|
292
|
-
timestamp: this.timestamp,
|
|
293
|
-
data: {
|
|
294
|
-
groupId: this.groupId,
|
|
295
|
-
cellBefore: this.cellBefore,
|
|
296
|
-
cellAfter: this.cellAfter,
|
|
297
|
-
frameBefore: this.frameBefore,
|
|
298
|
-
frameAfter: this.frameAfter,
|
|
299
|
-
},
|
|
300
|
-
};
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
229
|
const DRAG_THRESHOLD = 4;
|
|
304
230
|
const GLIDE_OFF_DELAY = 400;
|
|
305
231
|
/** A press this close (CSS px) to a tile's border takes that edge for a resize. */
|
|
@@ -1004,19 +930,6 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1004
930
|
}
|
|
1005
931
|
return out;
|
|
1006
932
|
};
|
|
1007
|
-
/** The member groups a gesture displaced, as their own cell commands — `buildCommitCommands` skips groups by design. */
|
|
1008
|
-
const groupCellCommands = (deltas) => {
|
|
1009
|
-
const out = [];
|
|
1010
|
-
for (const d of deltas) {
|
|
1011
|
-
if (!d.isGroup || (d.cellBefore.x === d.cellAfter.x && d.cellBefore.y === d.cellAfter.y && d.cellBefore.w === d.cellAfter.w && d.cellBefore.h === d.cellAfter.h))
|
|
1012
|
-
continue;
|
|
1013
|
-
const og = diagram.getGroup(d.id);
|
|
1014
|
-
if (!og)
|
|
1015
|
-
continue;
|
|
1016
|
-
out.push(new SetGroupCellCommand(d.id, d.cellBefore, d.cellAfter, { x: d.posBefore.x, y: d.posBefore.y, width: d.sizeBefore.width, height: d.sizeBefore.height }, frameOfGroup(og)));
|
|
1017
|
-
}
|
|
1018
|
-
return out;
|
|
1019
|
-
};
|
|
1020
933
|
const execute = (name, commands) => {
|
|
1021
934
|
if (commands.length === 0)
|
|
1022
935
|
return false;
|
|
@@ -1024,20 +937,48 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1024
937
|
return true;
|
|
1025
938
|
};
|
|
1026
939
|
// -- membership + bounds sync ----------------------------------------------
|
|
940
|
+
/**
|
|
941
|
+
* A DROP LEAVES ITS TILE IN THE ENGINE, waiting for the member the commit
|
|
942
|
+
* adds under the same id — or, for a palette drop, for whatever the host's
|
|
943
|
+
* command adds: a host that mints its own id would otherwise leave the
|
|
944
|
+
* ghost behind as a phantom holding the cell. The next member to arrive
|
|
945
|
+
* claims the phantom's place: the same id simply takes it over, another id
|
|
946
|
+
* removes it first and lands where the drop settled.
|
|
947
|
+
*/
|
|
948
|
+
let pendingDrop = null;
|
|
1027
949
|
const onMemberAdded = (id) => {
|
|
1028
|
-
var _a;
|
|
950
|
+
var _a, _b, _c;
|
|
1029
951
|
if (disposed)
|
|
1030
952
|
return;
|
|
953
|
+
// The cell a waiting tile held for an arriving member of ANOTHER id. The
|
|
954
|
+
// removal settles the board — the tiles the drop pushed float back into
|
|
955
|
+
// the hole — so the member cannot simply be added there: it collided and
|
|
956
|
+
// auto-positioned to the left edge (Quantia's "lands on the cell it was
|
|
957
|
+
// aimed at"). It enters at the bottom edge instead and takes the cell
|
|
958
|
+
// gatelessly, pushing them down again, all inside this one call.
|
|
959
|
+
let claim = null;
|
|
960
|
+
if (pendingDrop && pendingDrop !== id) {
|
|
961
|
+
const ph = engine.getItem(pendingDrop);
|
|
962
|
+
if (ph && !((_a = group.members) !== null && _a !== void 0 ? _a : new Set()).has(pendingDrop)) {
|
|
963
|
+
claim = { x: ph.x, y: ph.y };
|
|
964
|
+
engine.remove(pendingDrop);
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
pendingDrop = null;
|
|
1031
968
|
if (!engine.getItem(id)) {
|
|
1032
969
|
const item = itemFor(id);
|
|
1033
|
-
let placed = engine.add(item);
|
|
970
|
+
let placed = claim ? engine.add(Object.assign(Object.assign({}, item), { x: 0, y: engine.rows(), autoPosition: false })) : engine.add(item);
|
|
971
|
+
if (placed && claim) {
|
|
972
|
+
engine.moveCheck(id, claim.x, claim.y, { gate: false, pushSolid: !!placed.solid });
|
|
973
|
+
placed = (_b = engine.getItem(id)) !== null && _b !== void 0 ? _b : placed;
|
|
974
|
+
}
|
|
1034
975
|
if (!placed && capacity !== undefined) {
|
|
1035
976
|
// Membership is a document fact (an undo just restored it, say); a
|
|
1036
977
|
// bounded board must not strand the node invisible. Lift the capacity
|
|
1037
978
|
// for this adoption — it floors at the content on the next refresh.
|
|
1038
979
|
capacity = undefined;
|
|
1039
980
|
engine = engineFrom([...engine.getItems().map((i) => (Object.assign({}, i))), item]);
|
|
1040
|
-
placed = (
|
|
981
|
+
placed = (_c = engine.getItem(id)) !== null && _c !== void 0 ? _c : null;
|
|
1041
982
|
refreshCapacity();
|
|
1042
983
|
}
|
|
1043
984
|
if (placed)
|
|
@@ -1321,38 +1262,49 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1321
1262
|
const pitch = Math.max(1, r1.y - r0.y);
|
|
1322
1263
|
return Math.max(0, Math.floor((y - r0.y) / pitch));
|
|
1323
1264
|
};
|
|
1324
|
-
|
|
1325
|
-
|
|
1265
|
+
/**
|
|
1266
|
+
* The ghost `id` (spanning `spans`) beside container `z.id` on THIS board at
|
|
1267
|
+
* `row`: the engine shifts the container or pushes what stands behind it,
|
|
1268
|
+
* and the beside is remembered for the resolve's stickiness. One function
|
|
1269
|
+
* for a tile of this board and for a tile another board's gesture placed
|
|
1270
|
+
* here through its leg.
|
|
1271
|
+
*/
|
|
1272
|
+
const besideOn = (id, spans, z, row) => {
|
|
1326
1273
|
const tc = engine.getItem(z.id);
|
|
1327
1274
|
if (!tc)
|
|
1328
1275
|
return;
|
|
1329
|
-
if (g.leg) {
|
|
1330
|
-
g.leg.adopted.abort();
|
|
1331
|
-
g.leg = null;
|
|
1332
|
-
}
|
|
1333
|
-
if (g.removedFromBoard) {
|
|
1334
|
-
g.removedFromBoard = false;
|
|
1335
|
-
(_a = hostOf(g.id)) === null || _a === void 0 ? void 0 : _a.classList.remove('axdb-out');
|
|
1336
|
-
engine.add({ id: g.id, x: 0, y: engine.rows(), w: g.spans.w, h: g.spans.h });
|
|
1337
|
-
}
|
|
1338
1276
|
if (beside && (beside.id !== z.id || beside.side !== z.side))
|
|
1339
1277
|
endBeside(true); // another container, or another side of it: from the rest layout
|
|
1340
1278
|
if (!beside) {
|
|
1341
1279
|
const grp0 = diagram.getGroup(z.id);
|
|
1342
1280
|
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 };
|
|
1343
1281
|
}
|
|
1344
|
-
const r = engine.placeBeside(
|
|
1345
|
-
const at = engine.getItem(
|
|
1282
|
+
const r = engine.placeBeside(id, z.id, z.side, row);
|
|
1283
|
+
const at = engine.getItem(id);
|
|
1346
1284
|
if (r.changed && at) {
|
|
1347
1285
|
beside.vacated = { x: at.x, y: at.y };
|
|
1348
1286
|
beside.row = row;
|
|
1349
1287
|
}
|
|
1350
1288
|
else if (at) {
|
|
1351
1289
|
// Refused (a bound): the widget goes where it can, as any refused cell does.
|
|
1352
|
-
placeNear(
|
|
1290
|
+
placeNear(id, at.x, at.y, spans.w);
|
|
1353
1291
|
}
|
|
1354
1292
|
project();
|
|
1355
1293
|
};
|
|
1294
|
+
const applyBeside = (g, z, row) => {
|
|
1295
|
+
if (!engine.getItem(z.id))
|
|
1296
|
+
return;
|
|
1297
|
+
if (g.leg) {
|
|
1298
|
+
g.leg.adopted.abort();
|
|
1299
|
+
g.leg = null;
|
|
1300
|
+
}
|
|
1301
|
+
if (g.removedFromBoard) {
|
|
1302
|
+
g.removedFromBoard = false;
|
|
1303
|
+
setDim(g, false);
|
|
1304
|
+
engine.add({ id: g.id, x: 0, y: engine.rows(), w: g.spans.w, h: g.spans.h });
|
|
1305
|
+
}
|
|
1306
|
+
besideOn(g.id, g.spans, z, row);
|
|
1307
|
+
};
|
|
1356
1308
|
/**
|
|
1357
1309
|
* CARRIED (0.4.43): a group dragged by its strip or band moves as ONE thing.
|
|
1358
1310
|
* The held TILE is transition-exempt (the ghost), but a group has no host of
|
|
@@ -1731,19 +1683,9 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1731
1683
|
}
|
|
1732
1684
|
engine.endGesture();
|
|
1733
1685
|
project();
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
const
|
|
1737
|
-
const commands = buildCommitCommands(deltas);
|
|
1738
|
-
// The sections and groups the move PUSHED (0.4.44): the tile commit skips
|
|
1739
|
-
// groups by design (a group's cell is its own command), so they are
|
|
1740
|
-
// committed here the way a dock commits the groups it displaced — or the
|
|
1741
|
-
// model snapped every one of them, and the mover, straight back.
|
|
1742
|
-
commands.push(...groupCellCommands(deltas));
|
|
1743
|
-
const b = g.cellBefore;
|
|
1744
|
-
if (it && grp && (b.x !== it.x || b.y !== it.y || b.w !== it.w || b.h !== it.h)) {
|
|
1745
|
-
commands.push(new SetGroupCellCommand(g.id, b, { x: it.x, y: it.y, w: it.w, h: it.h }, g.frameBefore, frameOfGroup(grp)));
|
|
1746
|
-
}
|
|
1686
|
+
// The mover and the sections it PUSHED (0.4.44) commit alike: a group's
|
|
1687
|
+
// cell-and-frame command comes out of the same deltas as a node's.
|
|
1688
|
+
const commands = tileCommands(deltasSince(g.startCells, g.startGeom));
|
|
1747
1689
|
const changed = execute(g.move ? 'Move section' : 'Resize section', commands);
|
|
1748
1690
|
disarmGlideSoon();
|
|
1749
1691
|
syncHandles();
|
|
@@ -1828,6 +1770,12 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1828
1770
|
if (rect.height !== undefined)
|
|
1829
1771
|
el.style.height = `${rect.height}px`;
|
|
1830
1772
|
};
|
|
1773
|
+
/** "A release here lands nowhere": the ghost's host dims, and so does a palette chip (which has no host). */
|
|
1774
|
+
const setDim = (g, on) => {
|
|
1775
|
+
var _a, _b;
|
|
1776
|
+
(_a = hostOf(g.id)) === null || _a === void 0 ? void 0 : _a.classList.toggle('axdb-out', on);
|
|
1777
|
+
(_b = g.chip) === null || _b === void 0 ? void 0 : _b.classList.toggle('axdb-out', on);
|
|
1778
|
+
};
|
|
1831
1779
|
const cleanupGestureVisuals = (g) => {
|
|
1832
1780
|
var _a;
|
|
1833
1781
|
if (g.kind !== 'palette')
|
|
@@ -1858,8 +1806,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1858
1806
|
gesture = null;
|
|
1859
1807
|
project();
|
|
1860
1808
|
const deltas = deltasSince(g.startCells, g.startGeom);
|
|
1861
|
-
const commands =
|
|
1862
|
-
commands.push(...groupCellCommands(deltas)); // a container a BESIDE drop shifted (0.4.45)
|
|
1809
|
+
const commands = tileCommands(deltas); // a container a BESIDE drop shifted commits with the widgets (0.4.45)
|
|
1863
1810
|
endBeside(false);
|
|
1864
1811
|
if (g.esc && g.esc.rowsAdded !== 0) {
|
|
1865
1812
|
commands.push(new SetGroupCellCommand(group.id, g.esc.cellBefore, g.esc.cellAfter, g.esc.frameBefore, g.esc.frameAfter));
|
|
@@ -1891,11 +1838,11 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1891
1838
|
if (!g)
|
|
1892
1839
|
return;
|
|
1893
1840
|
gesture = null;
|
|
1894
|
-
if (g.
|
|
1841
|
+
if (g.leg) {
|
|
1895
1842
|
g.leg.adopted.abort(); // target board back to its pre-entry layout
|
|
1896
1843
|
g.leg = null;
|
|
1897
1844
|
}
|
|
1898
|
-
if (g.
|
|
1845
|
+
if (g.esc && g.esc.rowsAdded !== 0) {
|
|
1899
1846
|
g.esc.peer.resizeMemberBy(group.id, -g.esc.rowsAdded); // slab back down
|
|
1900
1847
|
g.esc = null;
|
|
1901
1848
|
}
|
|
@@ -1959,141 +1906,186 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1959
1906
|
y: worldY - (spans.h * (rh + gap) - gap) / 2,
|
|
1960
1907
|
};
|
|
1961
1908
|
};
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1909
|
+
/**
|
|
1910
|
+
* THE GHOST FOLLOWS THE HAND — for a tile of this board and for a palette
|
|
1911
|
+
* chip alike (tile first, step 4a): the zone under the pointer is resolved
|
|
1912
|
+
* once over the boards' membership tree, and whichever board it names
|
|
1913
|
+
* takes the ghost through a leg — beside a container of that board, with
|
|
1914
|
+
* intent where a section of that board refused it (D2), or on a plain cell.
|
|
1915
|
+
*/
|
|
1916
|
+
const moveGhost = (g, ev) => {
|
|
1917
|
+
var _a;
|
|
1918
|
+
const desired = { x: ev.world.x - g.grab.dx, y: ev.world.y - g.grab.dy };
|
|
1919
|
+
g.node.setPosition(desired.x, desired.y);
|
|
1920
|
+
ghostStyleFastPath(g, desired);
|
|
1921
|
+
g.lastWorld = { x: ev.world.x, y: ev.world.y };
|
|
1922
|
+
g.lastScreen = { x: ev.screen.x, y: ev.screen.y };
|
|
1923
|
+
/** The ghost's pixel size on THIS board — a palette chip has spans, not a size. */
|
|
1924
|
+
const pxSize = () => {
|
|
1925
|
+
if (g.kind !== 'palette')
|
|
1926
|
+
return { width: g.node.size.width, height: g.node.size.height };
|
|
1927
|
+
const f = frame();
|
|
1928
|
+
const gg = geom();
|
|
1929
|
+
return { width: g.spans.w * (columnUnitFor(gg, f.width) + gap) - gap, height: g.spans.h * (rowHeightFor(gg, rows()) + gap) - gap };
|
|
1930
|
+
};
|
|
1931
|
+
/** The ghost leaves THIS board's engine (survivors settle home); the old leg is closed first. */
|
|
1932
|
+
const leaveSelf = () => {
|
|
1933
|
+
if (g.leg) {
|
|
1934
|
+
g.leg.adopted.abort();
|
|
1935
|
+
g.leg = null;
|
|
1936
|
+
}
|
|
1937
|
+
if (!g.removedFromBoard) {
|
|
1938
|
+
g.removedFromBoard = true;
|
|
1939
|
+
engine.remove(g.id);
|
|
1940
|
+
project();
|
|
1941
|
+
}
|
|
1942
|
+
};
|
|
1943
|
+
/** A leg on `peer`, entered with `opts`; the old leg (another board's) is closed first. Answers whether the peer took the ghost. */
|
|
1944
|
+
const enterLeg = (peer, opts) => {
|
|
1945
|
+
if (g.leg && g.leg.peer !== peer) {
|
|
1946
|
+
g.leg.adopted.abort();
|
|
1947
|
+
g.leg = null;
|
|
1948
|
+
}
|
|
1949
|
+
if (!g.removedFromBoard) {
|
|
1950
|
+
g.removedFromBoard = true;
|
|
1951
|
+
engine.remove(g.id); // survivors settle home (gesture memory intact)
|
|
1952
|
+
project();
|
|
1953
|
+
}
|
|
1954
|
+
if (!g.leg) {
|
|
1955
|
+
const adopted = peer.adopt(g.node, ev.world, pxSize(), opts);
|
|
1956
|
+
if (!adopted)
|
|
1957
|
+
return false;
|
|
1958
|
+
g.leg = { peer, adopted };
|
|
1959
|
+
}
|
|
1960
|
+
return true;
|
|
1961
|
+
};
|
|
1962
|
+
// THE ZONE (tile first, step 2): one recursive resolve over the boards'
|
|
1963
|
+
// membership tree — a strip slot, a cell beside a container, a plain
|
|
1964
|
+
// cell on the deepest board the pointer may enter, or off — decided
|
|
1965
|
+
// BEFORE any engine is asked anything. The strip still wins over every
|
|
1966
|
+
// board; a band's stickiness and the vacated cell still hold a beside.
|
|
1967
|
+
const z = resolveTileZone(g, ev);
|
|
1968
|
+
if (z.kind === 'strip' && options.tabDrop && !isStatic && g.kind !== 'palette') {
|
|
1969
|
+
// -- INTO A STRIP: the widget becomes a new tab there, so it leaves
|
|
1970
|
+
// this board (survivors settle home) and the strip marks the slot.
|
|
1971
|
+
endBeside(true); // a band's shift gives way to the strip: the container comes back
|
|
1972
|
+
leaveSelf();
|
|
1973
|
+
setDim(g, false);
|
|
1974
|
+
if (!g.strip || g.strip.containerId !== z.containerId || g.strip.index !== z.index) {
|
|
1975
|
+
options.tabDrop.markDrop(z.containerId, z.index);
|
|
1976
|
+
}
|
|
1977
|
+
g.strip = { containerId: z.containerId, index: z.index };
|
|
1978
|
+
syncPlaceholder();
|
|
1979
|
+
api.render();
|
|
1966
1980
|
return;
|
|
1967
|
-
if (!g.started) {
|
|
1968
|
-
const dx = ev.screen.x - g.downClient.x;
|
|
1969
|
-
const dy = ev.screen.y - g.downClient.y;
|
|
1970
|
-
if (Math.abs(dx) + Math.abs(dy) < DRAG_THRESHOLD)
|
|
1971
|
-
return;
|
|
1972
|
-
beginGestureVisuals(g);
|
|
1973
1981
|
}
|
|
1974
|
-
if (g.
|
|
1975
|
-
|
|
1976
|
-
g.
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1982
|
+
if (g.strip) {
|
|
1983
|
+
(_a = options.tabDrop) === null || _a === void 0 ? void 0 : _a.markDrop(null, null);
|
|
1984
|
+
g.strip = null;
|
|
1985
|
+
}
|
|
1986
|
+
// -- BESIDE a tab container: its outer band puts the widget next to it
|
|
1987
|
+
// — at the board's edge the container shifts over to make room, live
|
|
1988
|
+
// and gliding, like any widget gives way (0.4.48). On THIS board the
|
|
1989
|
+
// engine is driven directly; on another board its leg drives it (4a).
|
|
1990
|
+
if (z.kind === 'beside' && !isStatic && z.board.ref === selfPeer) {
|
|
1991
|
+
const row = rowOfPoint(ev.world.y);
|
|
1992
|
+
if (!z.kept || !beside || beside.row !== row)
|
|
1993
|
+
applyBeside(g, { id: z.containerId, side: z.side }, row);
|
|
1994
|
+
syncPlaceholder();
|
|
1995
|
+
return;
|
|
1996
|
+
}
|
|
1997
|
+
if (beside)
|
|
1998
|
+
endBeside(true); // the hand left the band: the container comes back
|
|
1999
|
+
if (z.kind === 'beside' && !isStatic) {
|
|
2000
|
+
const peer = z.board.ref;
|
|
2001
|
+
if (g.leg && g.leg.peer === peer)
|
|
2002
|
+
g.leg.adopted.beside(z.containerId, z.side, ev.world);
|
|
2003
|
+
else if (enterLeg(peer, { beside: { containerId: z.containerId, side: z.side } }))
|
|
2004
|
+
g.refusedPeer = null;
|
|
2005
|
+
setDim(g, !g.leg);
|
|
2006
|
+
syncPlaceholder();
|
|
2007
|
+
return;
|
|
2008
|
+
}
|
|
2009
|
+
// Last resort: the grace band — a small slip past the edge stays ON
|
|
2010
|
+
// this board (the engine clamps the cell; prototype parity).
|
|
2011
|
+
const onSelf = z.kind !== 'off' && z.kind !== 'strip' && z.board.ref === selfPeer;
|
|
2012
|
+
const inside = onSelf || (z.kind === 'off' && worldInsideBoardGrace(ev.world.x, ev.world.y));
|
|
2013
|
+
const peer = !onSelf && z.kind !== 'off' && z.kind !== 'strip' ? z.board.ref : null;
|
|
2014
|
+
/** A section that refused the widget is PUSHED by it on the board that holds the section (D2): this board directly, another through its leg. */
|
|
2015
|
+
const pushRefused = (refused) => {
|
|
2016
|
+
const parent = parentPeerOf(api.container, refused.group.id);
|
|
2017
|
+
if (parent === selfPeer) {
|
|
1989
2018
|
if (g.leg) {
|
|
1990
2019
|
g.leg.adopted.abort();
|
|
1991
2020
|
g.leg = null;
|
|
1992
2021
|
}
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
g.removedFromBoard = true;
|
|
1996
|
-
engine.remove(g.id);
|
|
1997
|
-
project();
|
|
1998
|
-
}
|
|
1999
|
-
(_a = hostOf(g.id)) === null || _a === void 0 ? void 0 : _a.classList.remove('axdb-out');
|
|
2000
|
-
if (!g.strip || g.strip.containerId !== z.containerId || g.strip.index !== z.index) {
|
|
2001
|
-
options.tabDrop.markDrop(z.containerId, z.index);
|
|
2002
|
-
}
|
|
2003
|
-
g.strip = { containerId: z.containerId, index: z.index };
|
|
2004
|
-
syncPlaceholder();
|
|
2005
|
-
api.render();
|
|
2006
|
-
return;
|
|
2022
|
+
placeOnSelf(g, desired, true);
|
|
2023
|
+
setDim(g, false);
|
|
2007
2024
|
}
|
|
2008
|
-
if (g.
|
|
2009
|
-
(
|
|
2010
|
-
g
|
|
2025
|
+
else if (parent && g.leg && g.leg.peer === parent) {
|
|
2026
|
+
g.leg.adopted.move(ev.world, { push: true });
|
|
2027
|
+
setDim(g, false);
|
|
2011
2028
|
}
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
// to make room, live and gliding, like any widget gives way (0.4.48).
|
|
2015
|
-
// (A container on another board still resolves as a plain cell there
|
|
2016
|
-
// until the gesture scope spans boards — step 3.)
|
|
2017
|
-
if (z.kind === 'beside' && !isStatic && z.board.ref === selfPeer) {
|
|
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);
|
|
2021
|
-
syncPlaceholder();
|
|
2022
|
-
return;
|
|
2029
|
+
else if (parent && enterLeg(parent, { push: true })) {
|
|
2030
|
+
setDim(g, false);
|
|
2023
2031
|
}
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
const onSelf = z.kind !== 'off' && z.kind !== 'strip' && z.board.ref === selfPeer;
|
|
2029
|
-
const inside = onSelf || (z.kind === 'off' && worldInsideBoardGrace(ev.world.x, ev.world.y));
|
|
2030
|
-
const peer = !onSelf && z.kind !== 'off' && z.kind !== 'strip' ? z.board.ref : null;
|
|
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);
|
|
2032
|
+
else {
|
|
2033
|
+
// No board holds the refusing section (a view): dim = will snap home.
|
|
2034
|
+
leaveSelf();
|
|
2035
|
+
setDim(g, true);
|
|
2036
2036
|
}
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
}
|
|
2047
|
-
if (!g.removedFromBoard) {
|
|
2048
|
-
g.removedFromBoard = true;
|
|
2049
|
-
engine.remove(g.id); // survivors settle home (gesture memory intact)
|
|
2050
|
-
project();
|
|
2051
|
-
}
|
|
2052
|
-
const adopted = peer.adopt(g.node, ev.world, {
|
|
2053
|
-
width: g.node.size.width,
|
|
2054
|
-
height: g.node.size.height,
|
|
2055
|
-
});
|
|
2056
|
-
if (adopted) {
|
|
2057
|
-
g.refusedPeer = null;
|
|
2058
|
-
(_c = hostOf(g.id)) === null || _c === void 0 ? void 0 : _c.classList.remove('axdb-out');
|
|
2059
|
-
g.leg = { peer, adopted };
|
|
2060
|
-
g.leg.adopted.move(ev.world);
|
|
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
|
-
}
|
|
2067
|
-
else {
|
|
2068
|
-
// A board deeper down refused the adoption: dim = will snap home (the N-board scope is step 4).
|
|
2069
|
-
g.refusedPeer = peer;
|
|
2070
|
-
(_d = hostOf(g.id)) === null || _d === void 0 ? void 0 : _d.classList.add('axdb-out');
|
|
2071
|
-
}
|
|
2072
|
-
}
|
|
2037
|
+
};
|
|
2038
|
+
if (peer && g.refusedPeer === peer) {
|
|
2039
|
+
// -- REFUSED, PUSHED (D2): asked once; every move over it pushes.
|
|
2040
|
+
pushRefused(peer);
|
|
2041
|
+
}
|
|
2042
|
+
else if (peer) {
|
|
2043
|
+
// -- HANDOFF: the pointer is over another board -------------------
|
|
2044
|
+
if (g.leg && g.leg.peer === peer) {
|
|
2045
|
+
g.leg.adopted.move(ev.world);
|
|
2073
2046
|
}
|
|
2074
|
-
else if (
|
|
2075
|
-
// -- back on (or still on) our own board --------------------------
|
|
2047
|
+
else if (enterLeg(peer, {})) {
|
|
2076
2048
|
g.refusedPeer = null;
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
g.leg = null;
|
|
2080
|
-
}
|
|
2081
|
-
placeOnSelf(g, desired);
|
|
2049
|
+
setDim(g, false);
|
|
2050
|
+
g.leg.adopted.move(ev.world);
|
|
2082
2051
|
}
|
|
2083
2052
|
else {
|
|
2084
|
-
//
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
g.leg = null;
|
|
2088
|
-
}
|
|
2089
|
-
if (!g.removedFromBoard) {
|
|
2090
|
-
g.removedFromBoard = true;
|
|
2091
|
-
engine.remove(g.id); // survivors settle home; cells minted nowhere
|
|
2092
|
-
(_e = hostOf(g.id)) === null || _e === void 0 ? void 0 : _e.classList.add('axdb-out');
|
|
2093
|
-
project();
|
|
2094
|
-
}
|
|
2053
|
+
// The board refused (full, fit): the widget pushes it on its parent (D2).
|
|
2054
|
+
g.refusedPeer = peer;
|
|
2055
|
+
pushRefused(peer);
|
|
2095
2056
|
}
|
|
2096
|
-
|
|
2057
|
+
}
|
|
2058
|
+
else if (inside) {
|
|
2059
|
+
// -- back on (or still on) our own board --------------------------
|
|
2060
|
+
g.refusedPeer = null;
|
|
2061
|
+
if (g.leg) {
|
|
2062
|
+
g.leg.adopted.abort();
|
|
2063
|
+
g.leg = null;
|
|
2064
|
+
}
|
|
2065
|
+
setDim(g, false);
|
|
2066
|
+
placeOnSelf(g, desired);
|
|
2067
|
+
}
|
|
2068
|
+
else {
|
|
2069
|
+
// -- outside every board ------------------------------------------
|
|
2070
|
+
leaveSelf();
|
|
2071
|
+
setDim(g, true);
|
|
2072
|
+
}
|
|
2073
|
+
syncPlaceholder();
|
|
2074
|
+
};
|
|
2075
|
+
const onToolMove = (ev) => {
|
|
2076
|
+
var _a, _b, _c;
|
|
2077
|
+
const g = gesture;
|
|
2078
|
+
if (!g || g.kind === 'palette')
|
|
2079
|
+
return;
|
|
2080
|
+
if (!g.started) {
|
|
2081
|
+
const dx = ev.screen.x - g.downClient.x;
|
|
2082
|
+
const dy = ev.screen.y - g.downClient.y;
|
|
2083
|
+
if (Math.abs(dx) + Math.abs(dy) < DRAG_THRESHOLD)
|
|
2084
|
+
return;
|
|
2085
|
+
beginGestureVisuals(g);
|
|
2086
|
+
}
|
|
2087
|
+
if (g.kind === 'move') {
|
|
2088
|
+
moveGhost(g, ev);
|
|
2097
2089
|
return;
|
|
2098
2090
|
}
|
|
2099
2091
|
// resize: fluid pixel preview on the ghost, cell-stepped live push.
|
|
@@ -2199,7 +2191,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2199
2191
|
maxRows = n;
|
|
2200
2192
|
engine.maxRows = n;
|
|
2201
2193
|
};
|
|
2202
|
-
const slabRows = (
|
|
2194
|
+
const slabRows = (_b = (_a = parent.memberCell(group.id)) === null || _a === void 0 ? void 0 : _a.h) !== null && _b !== void 0 ? _b : maxRows;
|
|
2203
2195
|
const inner = maxRows;
|
|
2204
2196
|
const rhNow = rowHeightFor(geom(), rows());
|
|
2205
2197
|
const rowPx = rhNow + gap;
|
|
@@ -2363,7 +2355,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2363
2355
|
const anchorBottom = liveRect ? liveRect.y + liveRect.height : bottom;
|
|
2364
2356
|
const px = E.w ? anchorRight - w : anchorLeft;
|
|
2365
2357
|
const py = E.n ? anchorBottom - h : anchorTop;
|
|
2366
|
-
g.node.setSize(w, h, (
|
|
2358
|
+
g.node.setSize(w, h, (_c = g.node.size.depth) !== null && _c !== void 0 ? _c : 0);
|
|
2367
2359
|
g.node.setPosition(px, py);
|
|
2368
2360
|
ghostStyleFastPath(g, { x: px, y: py, width: w, height: h });
|
|
2369
2361
|
const spanF = bound() !== undefined ? frame() : f;
|
|
@@ -2421,7 +2413,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2421
2413
|
g.leg.adopted.abort();
|
|
2422
2414
|
g.leg = null;
|
|
2423
2415
|
}
|
|
2424
|
-
const displaced =
|
|
2416
|
+
const displaced = tileCommands(deltasSince(g.startCells, g.startGeom, g.id));
|
|
2425
2417
|
const snap = g.startGeom.get(g.id);
|
|
2426
2418
|
if (snap) {
|
|
2427
2419
|
g.node.setPosition(snap.pos.x, snap.pos.y);
|
|
@@ -2462,7 +2454,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2462
2454
|
finally {
|
|
2463
2455
|
writing = false;
|
|
2464
2456
|
}
|
|
2465
|
-
const sourceDisplaced =
|
|
2457
|
+
const sourceDisplaced = tileCommands(deltasSince(g.startCells, g.startGeom, g.id));
|
|
2466
2458
|
const before = g.startCells.get(g.id);
|
|
2467
2459
|
const geomBefore = g.startGeom.get(g.id);
|
|
2468
2460
|
const crossing = [
|
|
@@ -2527,7 +2519,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2527
2519
|
}
|
|
2528
2520
|
if (g.removedFromBoard && dragOut === 'remove') {
|
|
2529
2521
|
// Release OUTSIDE the board → remove via the page's atomic command path.
|
|
2530
|
-
const displaced =
|
|
2522
|
+
const displaced = tileCommands(deltasSince(g.startCells, g.startGeom, g.id));
|
|
2531
2523
|
const snap = g.startGeom.get(g.id);
|
|
2532
2524
|
if (snap) {
|
|
2533
2525
|
// Park the node on its start rect so the page's RemoveNodeCommand
|
|
@@ -2609,10 +2601,10 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2609
2601
|
};
|
|
2610
2602
|
/** 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
2603
|
const placeOnSelf = (g, desired, pushSolid = false) => {
|
|
2612
|
-
var _a, _b
|
|
2604
|
+
var _a, _b;
|
|
2613
2605
|
if (g.removedFromBoard) {
|
|
2614
2606
|
g.removedFromBoard = false;
|
|
2615
|
-
(
|
|
2607
|
+
setDim(g, false);
|
|
2616
2608
|
const cell = pointToCell(desired.x, desired.y, frame(), geom(), rows(), g.spans.w);
|
|
2617
2609
|
engine.add({ id: g.id, x: 0, y: engine.rows(), w: g.spans.w, h: g.spans.h });
|
|
2618
2610
|
if (!engine.moveCheck(g.id, cell.x, cell.y, { gate: false, pushSolid }).changed)
|
|
@@ -2620,7 +2612,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2620
2612
|
project();
|
|
2621
2613
|
}
|
|
2622
2614
|
else {
|
|
2623
|
-
const spanW = (
|
|
2615
|
+
const spanW = (_b = (_a = engine.getItem(g.id)) === null || _a === void 0 ? void 0 : _a.w) !== null && _b !== void 0 ? _b : g.spans.w;
|
|
2624
2616
|
const cell = pointToCell(desired.x, desired.y, frame(), geom(), rows(), spanW);
|
|
2625
2617
|
if (engine.moveCheck(g.id, cell.x, cell.y, { pushSolid }).changed)
|
|
2626
2618
|
project();
|
|
@@ -2628,8 +2620,9 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2628
2620
|
};
|
|
2629
2621
|
/** What the pointer means for the dragged tile: the resolve over the live tree, with the beside the hand holds. */
|
|
2630
2622
|
const resolveTileZone = (g, ev) => {
|
|
2623
|
+
var _a, _b;
|
|
2631
2624
|
let strip = null;
|
|
2632
|
-
if (options.tabDrop && !isStatic) {
|
|
2625
|
+
if (options.tabDrop && !isStatic && g.kind !== 'palette') {
|
|
2633
2626
|
const crect = api.container.getBoundingClientRect();
|
|
2634
2627
|
strip = options.tabDrop.stripAt(crect.left + ev.screen.x, crect.top + ev.screen.y);
|
|
2635
2628
|
}
|
|
@@ -2638,7 +2631,9 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2638
2631
|
y: ev.world.y,
|
|
2639
2632
|
roots: zoneRoots(),
|
|
2640
2633
|
strip,
|
|
2641
|
-
prev: beside
|
|
2634
|
+
prev: beside
|
|
2635
|
+
? { 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()) }
|
|
2636
|
+
: ((_b = (_a = g.leg) === null || _a === void 0 ? void 0 : _a.adopted.besideState()) !== null && _b !== void 0 ? _b : null), // a beside another board holds for the ghost, through its leg
|
|
2642
2637
|
maxDepth: nesting,
|
|
2643
2638
|
ghostDepth: 0,
|
|
2644
2639
|
ghostSubtree: EMPTY_SUBTREE,
|
|
@@ -2858,11 +2853,16 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2858
2853
|
const tl = centredTopLeft(wx, wy, { w: itemW, h: opts.anchor === 'top' ? 0 : itemH });
|
|
2859
2854
|
return pointToCell(tl.x, tl.y, frame(), geom(), rows(), itemW);
|
|
2860
2855
|
};
|
|
2861
|
-
const place = (cell, itemW) => opts.fit === 'shrink' ? placeFitting(node.id, cell.x, cell.y, itemW, hNatural) : placeNear(node.id, cell.x, cell.y, itemW);
|
|
2856
|
+
const place = (cell, itemW, push = false) => opts.fit === 'shrink' ? placeFitting(node.id, cell.x, cell.y, itemW, hNatural) : placeNear(node.id, cell.x, cell.y, itemW, push);
|
|
2862
2857
|
let lastWant = null;
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2858
|
+
if (opts.beside) {
|
|
2859
|
+
besideOn(node.id, span, { id: opts.beside.containerId, side: opts.beside.side }, rowOfPoint(world.y));
|
|
2860
|
+
}
|
|
2861
|
+
else {
|
|
2862
|
+
const cell0 = wantedCell(world.x, world.y, span.w, span.h);
|
|
2863
|
+
lastWant = cell0;
|
|
2864
|
+
place(cell0, span.w, opts.push);
|
|
2865
|
+
}
|
|
2866
2866
|
armGlide();
|
|
2867
2867
|
project();
|
|
2868
2868
|
syncPlaceholder();
|
|
@@ -2904,22 +2904,36 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2904
2904
|
const at = engine.getItem(node.id);
|
|
2905
2905
|
return !!at && at.x === cell.x && at.y === cell.y && at.w === cell.w && at.h === cell.h;
|
|
2906
2906
|
},
|
|
2907
|
-
move: (w) => {
|
|
2907
|
+
move: (w, o) => {
|
|
2908
2908
|
const item = engine.getItem(node.id);
|
|
2909
2909
|
if (!item)
|
|
2910
2910
|
return;
|
|
2911
|
+
if (beside)
|
|
2912
|
+
endBeside(true); // the hand left the band: the container comes back before the tile takes a plain cell
|
|
2911
2913
|
const cell = wantedCell(w.x, w.y, item.w, opts.fit === 'shrink' ? hNatural : item.h);
|
|
2912
2914
|
// The search is worth running once per wanted cell, not per pixel.
|
|
2913
2915
|
if (lastWant && lastWant.x === cell.x && lastWant.y === cell.y)
|
|
2914
2916
|
return;
|
|
2915
2917
|
lastWant = cell;
|
|
2916
|
-
if (place(cell, item.w))
|
|
2918
|
+
if (place(cell, item.w, !!(o === null || o === void 0 ? void 0 : o.push)))
|
|
2917
2919
|
project();
|
|
2918
2920
|
syncPlaceholder();
|
|
2919
2921
|
},
|
|
2922
|
+
beside: (containerId, side, w) => {
|
|
2923
|
+
if (!engine.getItem(node.id) && !engine.add({ id: node.id, x: 0, y: engine.rows(), w: span.w, h: hNatural }))
|
|
2924
|
+
return;
|
|
2925
|
+
const row = rowOfPoint(w.y);
|
|
2926
|
+
if (beside && beside.id === containerId && beside.side === side && beside.row === row)
|
|
2927
|
+
return;
|
|
2928
|
+
lastWant = null;
|
|
2929
|
+
besideOn(node.id, span, { id: containerId, side }, row);
|
|
2930
|
+
syncPlaceholder();
|
|
2931
|
+
},
|
|
2932
|
+
besideState: () => beside ? { containerId: beside.id, side: beside.side, frame0: beside.frame0, vacated: cellToRect({ x: beside.vacated.x, y: beside.vacated.y, w: span.w, h: hNatural }, frame(), geom(), rows()) } : null,
|
|
2920
2933
|
leave: () => {
|
|
2921
2934
|
if (!engine.getItem(node.id))
|
|
2922
2935
|
return;
|
|
2936
|
+
endBeside(true); // a container shifted for the tile comes back by itself: the shift was deliberate, so the memory never brings it
|
|
2923
2937
|
engine.remove(node.id); // displaced tiles come home (gesture memory)
|
|
2924
2938
|
lastWant = null;
|
|
2925
2939
|
project();
|
|
@@ -2938,6 +2952,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2938
2952
|
syncPlaceholder();
|
|
2939
2953
|
},
|
|
2940
2954
|
abort: () => {
|
|
2955
|
+
beside = null; // the snapshot restores the container with everything else
|
|
2941
2956
|
if (engine.getItem(node.id))
|
|
2942
2957
|
engine.remove(node.id);
|
|
2943
2958
|
engine.cancelGesture(); // pre-entry layout, memory cleared
|
|
@@ -2948,6 +2963,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2948
2963
|
syncPlaceholder();
|
|
2949
2964
|
},
|
|
2950
2965
|
finalize: () => {
|
|
2966
|
+
endBeside(false); // the shift stays: the deltas below carry it
|
|
2951
2967
|
const item = engine.getItem(node.id);
|
|
2952
2968
|
if (!item) {
|
|
2953
2969
|
engine.endGesture();
|
|
@@ -2957,26 +2973,11 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2957
2973
|
}
|
|
2958
2974
|
const cell = { x: item.x, y: item.y, w: item.w, h: item.h };
|
|
2959
2975
|
const rect = cellToRect(item, frame(), geom(), rows());
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
if (!isGroupMember(id))
|
|
2964
|
-
continue;
|
|
2965
|
-
const it = engine.getItem(id);
|
|
2966
|
-
const g0 = startGeom.get(id);
|
|
2967
|
-
if (!it || !g0)
|
|
2968
|
-
continue;
|
|
2969
|
-
if (it.x === before.x && it.y === before.y && it.w === before.w && it.h === before.h)
|
|
2970
|
-
continue;
|
|
2971
|
-
groups.push({
|
|
2972
|
-
id,
|
|
2973
|
-
cellBefore: before,
|
|
2974
|
-
cellAfter: { x: it.x, y: it.y, w: it.w, h: it.h },
|
|
2975
|
-
frameBefore: { x: g0.pos.x, y: g0.pos.y, width: g0.size.width, height: g0.size.height },
|
|
2976
|
-
frameAfter: cellToRect(it, frame(), geom(), rows()),
|
|
2977
|
-
});
|
|
2978
|
-
}
|
|
2976
|
+
// Widgets AND sections this board's adoption displaced, as one list: a
|
|
2977
|
+
// dock or a push that moved a section commits it with the drop.
|
|
2978
|
+
const commands = tileCommands(deltasSince(startCells, startGeom, node.id));
|
|
2979
2979
|
engine.endGesture();
|
|
2980
|
+
pendingDrop = node.id; // the tile stays for the member the commit (or the host's drop-in) adds
|
|
2980
2981
|
if (squeezeRoom !== undefined) {
|
|
2981
2982
|
// What the board now HOLDS is its bound, not the squeeze's room.
|
|
2982
2983
|
squeezeRoom = undefined;
|
|
@@ -2985,7 +2986,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2985
2986
|
adoptedGhostId = null;
|
|
2986
2987
|
disarmGlideSoon();
|
|
2987
2988
|
syncPlaceholder();
|
|
2988
|
-
return { commands, cell, rect
|
|
2989
|
+
return { commands, cell, rect };
|
|
2989
2990
|
},
|
|
2990
2991
|
};
|
|
2991
2992
|
};
|
|
@@ -3947,13 +3948,6 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
3947
3948
|
}
|
|
3948
3949
|
};
|
|
3949
3950
|
const zoneKey = (z) => JSON.stringify(z, (k, v) => (k === 'target' ? v.id : k === 'peer' ? v.group.id : v));
|
|
3950
|
-
// DOCKING PUSHES SECTIONS. A section is a locked tile so that a widget
|
|
3951
|
-
// never pushes it; a group docked against the board's edge is the one
|
|
3952
|
-
// gesture that must — everything below the top band moves down. For the
|
|
3953
|
-
// preview every member group is unlocked; they are relocked when the
|
|
3954
|
-
// pointer leaves the band, and their moved cells are committed with the
|
|
3955
|
-
// dock.
|
|
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));
|
|
3957
3951
|
// A TOP DOCK INSERTS ROWS. The engine's push cascade resolves the band's
|
|
3958
3952
|
// collisions one tile at a time and scrambles what stood beneath it (the
|
|
3959
3953
|
// demo's KPI row came apart, two of its tiles under the chart). VS Code
|
|
@@ -4204,23 +4198,17 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
4204
4198
|
return;
|
|
4205
4199
|
}
|
|
4206
4200
|
const fin = (_b = leg === null || leg === void 0 ? void 0 : leg.finalize()) !== null && _b !== void 0 ? _b : null;
|
|
4207
|
-
const
|
|
4208
|
-
const before = split;
|
|
4201
|
+
const halved = !!split && !!engine.getItem(z.target.id);
|
|
4209
4202
|
split = null;
|
|
4210
|
-
if (!fin || !
|
|
4203
|
+
if (!fin || !halved) {
|
|
4211
4204
|
leg === null || leg === void 0 ? void 0 : leg.abort();
|
|
4212
4205
|
done(false, 'cancel');
|
|
4213
4206
|
return;
|
|
4214
4207
|
}
|
|
4215
|
-
const keep = { x: it.x, y: it.y, w: it.w, h: it.h };
|
|
4216
4208
|
const planned = plan.commands(fin.cell, fin.rect, group.id);
|
|
4217
|
-
|
|
4218
|
-
|
|
4219
|
-
|
|
4220
|
-
new SetGroupCellCommand(z.target.id, before.before, keep, before.frameBefore, cellToRect(keep, frame(), geom(), rows())),
|
|
4221
|
-
...planned.move,
|
|
4222
|
-
...planned.collapse,
|
|
4223
|
-
]);
|
|
4209
|
+
// The target's halving is in the leg's own deltas: its cell at rest was
|
|
4210
|
+
// snapshotted when the ghost entered, and its half is where it stands now.
|
|
4211
|
+
const changed = execute('Split group', [...fin.commands, ...planned.move, ...planned.collapse]);
|
|
4224
4212
|
done(changed, 'commit');
|
|
4225
4213
|
return;
|
|
4226
4214
|
}
|
|
@@ -4238,7 +4226,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
4238
4226
|
return;
|
|
4239
4227
|
}
|
|
4240
4228
|
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);
|
|
4241
|
-
done(execute(z.kind === 'root' ? 'Dock tab' : 'Move tab out', [...fin.commands, ...
|
|
4229
|
+
done(execute(z.kind === 'root' ? 'Dock tab' : 'Move tab out', [...fin.commands, ...planned.move, ...planned.collapse]), 'commit');
|
|
4242
4230
|
};
|
|
4243
4231
|
const onUp = () => finish(true);
|
|
4244
4232
|
const onCancel = () => finish(false);
|
|
@@ -4310,43 +4298,39 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
4310
4298
|
return;
|
|
4311
4299
|
}
|
|
4312
4300
|
beginGestureVisuals(g);
|
|
4301
|
+
// The chip is held by its middle: the ghost centres under the hand.
|
|
4302
|
+
const f = frame();
|
|
4303
|
+
const gg = geom();
|
|
4304
|
+
g.grab = { dx: (g.spans.w * (columnUnitFor(gg, f.width) + gap) - gap) / 2, dy: (g.spans.h * (rowHeightFor(gg, rows()) + gap) - gap) / 2 };
|
|
4313
4305
|
}
|
|
4314
4306
|
if (chip) {
|
|
4315
4307
|
chip.style.left = `${e.clientX + 6}px`;
|
|
4316
4308
|
chip.style.top = `${e.clientY + 6}px`;
|
|
4317
4309
|
}
|
|
4310
|
+
// THE SAME PATH AS A TILE OF THIS BOARD (tile first, step 4a): the zone
|
|
4311
|
+
// under the hand names the board — this one, a page, a section — and
|
|
4312
|
+
// that board takes the ghost. The palette used to test "inside this
|
|
4313
|
+
// board" and nothing else, so a chip over a page landed under it.
|
|
4314
|
+
const rect = api.container.getBoundingClientRect();
|
|
4318
4315
|
const world = toWorld(e.clientX, e.clientY);
|
|
4319
|
-
|
|
4320
|
-
if (inside) {
|
|
4321
|
-
const tl = centredTopLeft(world.x, world.y, g.spans);
|
|
4322
|
-
const cell = pointToCell(tl.x, tl.y, frame(), geom(), rows(), g.spans.w);
|
|
4323
|
-
if (g.removedFromBoard) {
|
|
4324
|
-
g.removedFromBoard = false;
|
|
4325
|
-
// Enter at the bottom edge (collision-free), then take the cursor
|
|
4326
|
-
// cell GATELESSLY — gridstack's drag-in skips the gate on entry.
|
|
4327
|
-
// A bounded board with no room refuses the entry: the chip dims to
|
|
4328
|
-
// say so, and the release will snap it home.
|
|
4329
|
-
const entered = engine.add({ id: g.id, x: 0, y: engine.rows(), w: g.spans.w, h: g.spans.h });
|
|
4330
|
-
chip === null || chip === void 0 ? void 0 : chip.classList.toggle('axdb-out', !entered);
|
|
4331
|
-
if (entered)
|
|
4332
|
-
engine.moveCheck(g.id, cell.x, cell.y, { gate: false });
|
|
4333
|
-
project();
|
|
4334
|
-
}
|
|
4335
|
-
else if (engine.moveCheck(g.id, cell.x, cell.y).changed) {
|
|
4336
|
-
project();
|
|
4337
|
-
}
|
|
4338
|
-
}
|
|
4339
|
-
else if (!g.removedFromBoard) {
|
|
4340
|
-
g.removedFromBoard = true;
|
|
4341
|
-
chip === null || chip === void 0 ? void 0 : chip.classList.remove('axdb-out');
|
|
4342
|
-
engine.remove(g.id); // displaced tiles come home (gesture memory)
|
|
4343
|
-
project();
|
|
4344
|
-
}
|
|
4345
|
-
syncPlaceholder();
|
|
4316
|
+
moveGhost(g, { type: 'move', world, screen: { x: e.clientX - rect.left, y: e.clientY - rect.top }, modifiers: { shift: e.shiftKey, ctrl: e.ctrlKey, alt: e.altKey, meta: e.metaKey } });
|
|
4346
4317
|
api.render();
|
|
4347
4318
|
};
|
|
4348
|
-
|
|
4319
|
+
/** The drop, on whichever board holds the ghost: the node carries its cell, the host's command adds it there. */
|
|
4320
|
+
const dropOn = (boardId, cell, displaced) => {
|
|
4349
4321
|
var _a, _b;
|
|
4322
|
+
node.setGridItem(gridItemFromCell(cell));
|
|
4323
|
+
if (boardId === group.id)
|
|
4324
|
+
pendingDrop = g.id; // on another board its own finalize() pended it
|
|
4325
|
+
engine.endGesture();
|
|
4326
|
+
cleanupGestureVisuals(g);
|
|
4327
|
+
gesture = null;
|
|
4328
|
+
void ((_a = options.onDropIn) === null || _a === void 0 ? void 0 : _a.call(options, node, cell, displaced, { boardId }));
|
|
4329
|
+
persistLayouts();
|
|
4330
|
+
(_b = options.onGesture) === null || _b === void 0 ? void 0 : _b.call(options, { type: 'drop-in', kind: 'palette', nodeId: g.id, changed: true });
|
|
4331
|
+
api.renderNow();
|
|
4332
|
+
};
|
|
4333
|
+
const finish = (commit) => {
|
|
4350
4334
|
detach();
|
|
4351
4335
|
if (gesture !== g)
|
|
4352
4336
|
return;
|
|
@@ -4357,18 +4341,21 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
4357
4341
|
chip === null || chip === void 0 ? void 0 : chip.remove();
|
|
4358
4342
|
return;
|
|
4359
4343
|
}
|
|
4360
|
-
if (commit &&
|
|
4344
|
+
if (commit && g.leg) {
|
|
4345
|
+
// Dropped into another board (a page, a section): its leg closes with
|
|
4346
|
+
// that board's displaced tiles; this board displaced nothing.
|
|
4347
|
+
const fin = g.leg.adopted.finalize();
|
|
4348
|
+
const boardId = g.leg.adopted.groupId;
|
|
4349
|
+
g.leg = null;
|
|
4350
|
+
if (fin) {
|
|
4351
|
+
dropOn(boardId, fin.cell, [...tileCommands(deltasSince(g.startCells, g.startGeom, g.id)), ...fin.commands]);
|
|
4352
|
+
return;
|
|
4353
|
+
}
|
|
4354
|
+
}
|
|
4355
|
+
else if (commit && !g.removedFromBoard && engine.getItem(g.id)) {
|
|
4361
4356
|
const item = engine.getItem(g.id);
|
|
4362
|
-
|
|
4363
|
-
|
|
4364
|
-
const displaced = buildCommitCommands(deltasSince(g.startCells, g.startGeom, g.id));
|
|
4365
|
-
engine.endGesture();
|
|
4366
|
-
cleanupGestureVisuals(g);
|
|
4367
|
-
gesture = null;
|
|
4368
|
-
void ((_a = options.onDropIn) === null || _a === void 0 ? void 0 : _a.call(options, node, cell, displaced));
|
|
4369
|
-
persistLayouts();
|
|
4370
|
-
(_b = options.onGesture) === null || _b === void 0 ? void 0 : _b.call(options, { type: 'drop-in', kind: 'palette', nodeId: g.id, changed: true });
|
|
4371
|
-
api.renderNow();
|
|
4357
|
+
endBeside(false); // a container that shifted for the chip stays shifted: its delta commits with the drop
|
|
4358
|
+
dropOn(group.id, { x: item.x, y: item.y, w: item.w, h: item.h }, tileCommands(deltasSince(g.startCells, g.startGeom, g.id)));
|
|
4372
4359
|
return;
|
|
4373
4360
|
}
|
|
4374
4361
|
// Abort (released outside, or Escape): restore the board.
|
|
@@ -4411,18 +4398,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
4411
4398
|
finally {
|
|
4412
4399
|
writing = false;
|
|
4413
4400
|
}
|
|
4414
|
-
const commands =
|
|
4415
|
-
if (isGroupMember(id)) {
|
|
4416
|
-
// A SECTION: its cell lives in group metadata and its frame is written
|
|
4417
|
-
// by project(); the node commands above skip groups (D5 of the review).
|
|
4418
|
-
const it = engine.getItem(id);
|
|
4419
|
-
const grp = diagram.getGroup(id);
|
|
4420
|
-
const cb = snap.cells.get(id);
|
|
4421
|
-
const gb = snap.geoms.get(id);
|
|
4422
|
-
if (it && grp && cb && gb && (cb.x !== it.x || cb.y !== it.y || cb.w !== it.w || cb.h !== it.h)) {
|
|
4423
|
-
commands.push(new SetGroupCellCommand(id, cb, { x: it.x, y: it.y, w: it.w, h: it.h }, { x: gb.pos.x, y: gb.pos.y, width: gb.size.width, height: gb.size.height }, frameOfGroup(grp)));
|
|
4424
|
-
}
|
|
4425
|
-
}
|
|
4401
|
+
const commands = tileCommands(deltasSince(snap.cells, snap.geoms)); // a section's cell-and-frame command rides with the nodes'
|
|
4426
4402
|
engine.endGesture();
|
|
4427
4403
|
disarmGlideSoon();
|
|
4428
4404
|
enforceBoardHeight();
|
|
@@ -4463,6 +4439,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
4463
4439
|
return;
|
|
4464
4440
|
}
|
|
4465
4441
|
applyFluidFrame();
|
|
4442
|
+
pendingDrop = null; // rebuilt from the members: a phantom is gone with the engine
|
|
4466
4443
|
const items = [];
|
|
4467
4444
|
for (const id of (_a = group.members) !== null && _a !== void 0 ? _a : []) {
|
|
4468
4445
|
if (!memberEntity(id))
|
|
@@ -4679,7 +4656,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
4679
4656
|
sizeAfter: { width: target.width, height: target.height },
|
|
4680
4657
|
});
|
|
4681
4658
|
}
|
|
4682
|
-
return
|
|
4659
|
+
return tileCommands(deltas);
|
|
4683
4660
|
},
|
|
4684
4661
|
moveTo(id, x, y) {
|
|
4685
4662
|
return programmatic('Move widget', id, () => engine.moveCheck(id, x, y, { pushSolid: isGroupMember(id) }).changed);
|