@grafloria/element 0.4.52 → 0.4.53
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.
|
@@ -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,10 +937,23 @@ 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;
|
|
1029
951
|
if (disposed)
|
|
1030
952
|
return;
|
|
953
|
+
if (pendingDrop && pendingDrop !== id && engine.getItem(pendingDrop) && !((_a = group.members) !== null && _a !== void 0 ? _a : new Set()).has(pendingDrop)) {
|
|
954
|
+
engine.remove(pendingDrop);
|
|
955
|
+
}
|
|
956
|
+
pendingDrop = null;
|
|
1031
957
|
if (!engine.getItem(id)) {
|
|
1032
958
|
const item = itemFor(id);
|
|
1033
959
|
let placed = engine.add(item);
|
|
@@ -1037,7 +963,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1037
963
|
// for this adoption — it floors at the content on the next refresh.
|
|
1038
964
|
capacity = undefined;
|
|
1039
965
|
engine = engineFrom([...engine.getItems().map((i) => (Object.assign({}, i))), item]);
|
|
1040
|
-
placed = (
|
|
966
|
+
placed = (_b = engine.getItem(id)) !== null && _b !== void 0 ? _b : null;
|
|
1041
967
|
refreshCapacity();
|
|
1042
968
|
}
|
|
1043
969
|
if (placed)
|
|
@@ -1321,38 +1247,49 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1321
1247
|
const pitch = Math.max(1, r1.y - r0.y);
|
|
1322
1248
|
return Math.max(0, Math.floor((y - r0.y) / pitch));
|
|
1323
1249
|
};
|
|
1324
|
-
|
|
1325
|
-
|
|
1250
|
+
/**
|
|
1251
|
+
* The ghost `id` (spanning `spans`) beside container `z.id` on THIS board at
|
|
1252
|
+
* `row`: the engine shifts the container or pushes what stands behind it,
|
|
1253
|
+
* and the beside is remembered for the resolve's stickiness. One function
|
|
1254
|
+
* for a tile of this board and for a tile another board's gesture placed
|
|
1255
|
+
* here through its leg.
|
|
1256
|
+
*/
|
|
1257
|
+
const besideOn = (id, spans, z, row) => {
|
|
1326
1258
|
const tc = engine.getItem(z.id);
|
|
1327
1259
|
if (!tc)
|
|
1328
1260
|
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
1261
|
if (beside && (beside.id !== z.id || beside.side !== z.side))
|
|
1339
1262
|
endBeside(true); // another container, or another side of it: from the rest layout
|
|
1340
1263
|
if (!beside) {
|
|
1341
1264
|
const grp0 = diagram.getGroup(z.id);
|
|
1342
1265
|
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
1266
|
}
|
|
1344
|
-
const r = engine.placeBeside(
|
|
1345
|
-
const at = engine.getItem(
|
|
1267
|
+
const r = engine.placeBeside(id, z.id, z.side, row);
|
|
1268
|
+
const at = engine.getItem(id);
|
|
1346
1269
|
if (r.changed && at) {
|
|
1347
1270
|
beside.vacated = { x: at.x, y: at.y };
|
|
1348
1271
|
beside.row = row;
|
|
1349
1272
|
}
|
|
1350
1273
|
else if (at) {
|
|
1351
1274
|
// Refused (a bound): the widget goes where it can, as any refused cell does.
|
|
1352
|
-
placeNear(
|
|
1275
|
+
placeNear(id, at.x, at.y, spans.w);
|
|
1353
1276
|
}
|
|
1354
1277
|
project();
|
|
1355
1278
|
};
|
|
1279
|
+
const applyBeside = (g, z, row) => {
|
|
1280
|
+
if (!engine.getItem(z.id))
|
|
1281
|
+
return;
|
|
1282
|
+
if (g.leg) {
|
|
1283
|
+
g.leg.adopted.abort();
|
|
1284
|
+
g.leg = null;
|
|
1285
|
+
}
|
|
1286
|
+
if (g.removedFromBoard) {
|
|
1287
|
+
g.removedFromBoard = false;
|
|
1288
|
+
setDim(g, false);
|
|
1289
|
+
engine.add({ id: g.id, x: 0, y: engine.rows(), w: g.spans.w, h: g.spans.h });
|
|
1290
|
+
}
|
|
1291
|
+
besideOn(g.id, g.spans, z, row);
|
|
1292
|
+
};
|
|
1356
1293
|
/**
|
|
1357
1294
|
* CARRIED (0.4.43): a group dragged by its strip or band moves as ONE thing.
|
|
1358
1295
|
* The held TILE is transition-exempt (the ghost), but a group has no host of
|
|
@@ -1731,19 +1668,9 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1731
1668
|
}
|
|
1732
1669
|
engine.endGesture();
|
|
1733
1670
|
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
|
-
}
|
|
1671
|
+
// The mover and the sections it PUSHED (0.4.44) commit alike: a group's
|
|
1672
|
+
// cell-and-frame command comes out of the same deltas as a node's.
|
|
1673
|
+
const commands = tileCommands(deltasSince(g.startCells, g.startGeom));
|
|
1747
1674
|
const changed = execute(g.move ? 'Move section' : 'Resize section', commands);
|
|
1748
1675
|
disarmGlideSoon();
|
|
1749
1676
|
syncHandles();
|
|
@@ -1828,6 +1755,12 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1828
1755
|
if (rect.height !== undefined)
|
|
1829
1756
|
el.style.height = `${rect.height}px`;
|
|
1830
1757
|
};
|
|
1758
|
+
/** "A release here lands nowhere": the ghost's host dims, and so does a palette chip (which has no host). */
|
|
1759
|
+
const setDim = (g, on) => {
|
|
1760
|
+
var _a, _b;
|
|
1761
|
+
(_a = hostOf(g.id)) === null || _a === void 0 ? void 0 : _a.classList.toggle('axdb-out', on);
|
|
1762
|
+
(_b = g.chip) === null || _b === void 0 ? void 0 : _b.classList.toggle('axdb-out', on);
|
|
1763
|
+
};
|
|
1831
1764
|
const cleanupGestureVisuals = (g) => {
|
|
1832
1765
|
var _a;
|
|
1833
1766
|
if (g.kind !== 'palette')
|
|
@@ -1858,8 +1791,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1858
1791
|
gesture = null;
|
|
1859
1792
|
project();
|
|
1860
1793
|
const deltas = deltasSince(g.startCells, g.startGeom);
|
|
1861
|
-
const commands =
|
|
1862
|
-
commands.push(...groupCellCommands(deltas)); // a container a BESIDE drop shifted (0.4.45)
|
|
1794
|
+
const commands = tileCommands(deltas); // a container a BESIDE drop shifted commits with the widgets (0.4.45)
|
|
1863
1795
|
endBeside(false);
|
|
1864
1796
|
if (g.esc && g.esc.rowsAdded !== 0) {
|
|
1865
1797
|
commands.push(new SetGroupCellCommand(group.id, g.esc.cellBefore, g.esc.cellAfter, g.esc.frameBefore, g.esc.frameAfter));
|
|
@@ -1891,11 +1823,11 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1891
1823
|
if (!g)
|
|
1892
1824
|
return;
|
|
1893
1825
|
gesture = null;
|
|
1894
|
-
if (g.
|
|
1826
|
+
if (g.leg) {
|
|
1895
1827
|
g.leg.adopted.abort(); // target board back to its pre-entry layout
|
|
1896
1828
|
g.leg = null;
|
|
1897
1829
|
}
|
|
1898
|
-
if (g.
|
|
1830
|
+
if (g.esc && g.esc.rowsAdded !== 0) {
|
|
1899
1831
|
g.esc.peer.resizeMemberBy(group.id, -g.esc.rowsAdded); // slab back down
|
|
1900
1832
|
g.esc = null;
|
|
1901
1833
|
}
|
|
@@ -1959,141 +1891,186 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1959
1891
|
y: worldY - (spans.h * (rh + gap) - gap) / 2,
|
|
1960
1892
|
};
|
|
1961
1893
|
};
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1894
|
+
/**
|
|
1895
|
+
* THE GHOST FOLLOWS THE HAND — for a tile of this board and for a palette
|
|
1896
|
+
* chip alike (tile first, step 4a): the zone under the pointer is resolved
|
|
1897
|
+
* once over the boards' membership tree, and whichever board it names
|
|
1898
|
+
* takes the ghost through a leg — beside a container of that board, with
|
|
1899
|
+
* intent where a section of that board refused it (D2), or on a plain cell.
|
|
1900
|
+
*/
|
|
1901
|
+
const moveGhost = (g, ev) => {
|
|
1902
|
+
var _a;
|
|
1903
|
+
const desired = { x: ev.world.x - g.grab.dx, y: ev.world.y - g.grab.dy };
|
|
1904
|
+
g.node.setPosition(desired.x, desired.y);
|
|
1905
|
+
ghostStyleFastPath(g, desired);
|
|
1906
|
+
g.lastWorld = { x: ev.world.x, y: ev.world.y };
|
|
1907
|
+
g.lastScreen = { x: ev.screen.x, y: ev.screen.y };
|
|
1908
|
+
/** The ghost's pixel size on THIS board — a palette chip has spans, not a size. */
|
|
1909
|
+
const pxSize = () => {
|
|
1910
|
+
if (g.kind !== 'palette')
|
|
1911
|
+
return { width: g.node.size.width, height: g.node.size.height };
|
|
1912
|
+
const f = frame();
|
|
1913
|
+
const gg = geom();
|
|
1914
|
+
return { width: g.spans.w * (columnUnitFor(gg, f.width) + gap) - gap, height: g.spans.h * (rowHeightFor(gg, rows()) + gap) - gap };
|
|
1915
|
+
};
|
|
1916
|
+
/** The ghost leaves THIS board's engine (survivors settle home); the old leg is closed first. */
|
|
1917
|
+
const leaveSelf = () => {
|
|
1918
|
+
if (g.leg) {
|
|
1919
|
+
g.leg.adopted.abort();
|
|
1920
|
+
g.leg = null;
|
|
1921
|
+
}
|
|
1922
|
+
if (!g.removedFromBoard) {
|
|
1923
|
+
g.removedFromBoard = true;
|
|
1924
|
+
engine.remove(g.id);
|
|
1925
|
+
project();
|
|
1926
|
+
}
|
|
1927
|
+
};
|
|
1928
|
+
/** A leg on `peer`, entered with `opts`; the old leg (another board's) is closed first. Answers whether the peer took the ghost. */
|
|
1929
|
+
const enterLeg = (peer, opts) => {
|
|
1930
|
+
if (g.leg && g.leg.peer !== peer) {
|
|
1931
|
+
g.leg.adopted.abort();
|
|
1932
|
+
g.leg = null;
|
|
1933
|
+
}
|
|
1934
|
+
if (!g.removedFromBoard) {
|
|
1935
|
+
g.removedFromBoard = true;
|
|
1936
|
+
engine.remove(g.id); // survivors settle home (gesture memory intact)
|
|
1937
|
+
project();
|
|
1938
|
+
}
|
|
1939
|
+
if (!g.leg) {
|
|
1940
|
+
const adopted = peer.adopt(g.node, ev.world, pxSize(), opts);
|
|
1941
|
+
if (!adopted)
|
|
1942
|
+
return false;
|
|
1943
|
+
g.leg = { peer, adopted };
|
|
1944
|
+
}
|
|
1945
|
+
return true;
|
|
1946
|
+
};
|
|
1947
|
+
// THE ZONE (tile first, step 2): one recursive resolve over the boards'
|
|
1948
|
+
// membership tree — a strip slot, a cell beside a container, a plain
|
|
1949
|
+
// cell on the deepest board the pointer may enter, or off — decided
|
|
1950
|
+
// BEFORE any engine is asked anything. The strip still wins over every
|
|
1951
|
+
// board; a band's stickiness and the vacated cell still hold a beside.
|
|
1952
|
+
const z = resolveTileZone(g, ev);
|
|
1953
|
+
if (z.kind === 'strip' && options.tabDrop && !isStatic && g.kind !== 'palette') {
|
|
1954
|
+
// -- INTO A STRIP: the widget becomes a new tab there, so it leaves
|
|
1955
|
+
// this board (survivors settle home) and the strip marks the slot.
|
|
1956
|
+
endBeside(true); // a band's shift gives way to the strip: the container comes back
|
|
1957
|
+
leaveSelf();
|
|
1958
|
+
setDim(g, false);
|
|
1959
|
+
if (!g.strip || g.strip.containerId !== z.containerId || g.strip.index !== z.index) {
|
|
1960
|
+
options.tabDrop.markDrop(z.containerId, z.index);
|
|
1961
|
+
}
|
|
1962
|
+
g.strip = { containerId: z.containerId, index: z.index };
|
|
1963
|
+
syncPlaceholder();
|
|
1964
|
+
api.render();
|
|
1966
1965
|
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
1966
|
}
|
|
1974
|
-
if (g.
|
|
1975
|
-
|
|
1976
|
-
g.
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1967
|
+
if (g.strip) {
|
|
1968
|
+
(_a = options.tabDrop) === null || _a === void 0 ? void 0 : _a.markDrop(null, null);
|
|
1969
|
+
g.strip = null;
|
|
1970
|
+
}
|
|
1971
|
+
// -- BESIDE a tab container: its outer band puts the widget next to it
|
|
1972
|
+
// — at the board's edge the container shifts over to make room, live
|
|
1973
|
+
// and gliding, like any widget gives way (0.4.48). On THIS board the
|
|
1974
|
+
// engine is driven directly; on another board its leg drives it (4a).
|
|
1975
|
+
if (z.kind === 'beside' && !isStatic && z.board.ref === selfPeer) {
|
|
1976
|
+
const row = rowOfPoint(ev.world.y);
|
|
1977
|
+
if (!z.kept || !beside || beside.row !== row)
|
|
1978
|
+
applyBeside(g, { id: z.containerId, side: z.side }, row);
|
|
1979
|
+
syncPlaceholder();
|
|
1980
|
+
return;
|
|
1981
|
+
}
|
|
1982
|
+
if (beside)
|
|
1983
|
+
endBeside(true); // the hand left the band: the container comes back
|
|
1984
|
+
if (z.kind === 'beside' && !isStatic) {
|
|
1985
|
+
const peer = z.board.ref;
|
|
1986
|
+
if (g.leg && g.leg.peer === peer)
|
|
1987
|
+
g.leg.adopted.beside(z.containerId, z.side, ev.world);
|
|
1988
|
+
else if (enterLeg(peer, { beside: { containerId: z.containerId, side: z.side } }))
|
|
1989
|
+
g.refusedPeer = null;
|
|
1990
|
+
setDim(g, !g.leg);
|
|
1991
|
+
syncPlaceholder();
|
|
1992
|
+
return;
|
|
1993
|
+
}
|
|
1994
|
+
// Last resort: the grace band — a small slip past the edge stays ON
|
|
1995
|
+
// this board (the engine clamps the cell; prototype parity).
|
|
1996
|
+
const onSelf = z.kind !== 'off' && z.kind !== 'strip' && z.board.ref === selfPeer;
|
|
1997
|
+
const inside = onSelf || (z.kind === 'off' && worldInsideBoardGrace(ev.world.x, ev.world.y));
|
|
1998
|
+
const peer = !onSelf && z.kind !== 'off' && z.kind !== 'strip' ? z.board.ref : null;
|
|
1999
|
+
/** 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. */
|
|
2000
|
+
const pushRefused = (refused) => {
|
|
2001
|
+
const parent = parentPeerOf(api.container, refused.group.id);
|
|
2002
|
+
if (parent === selfPeer) {
|
|
1989
2003
|
if (g.leg) {
|
|
1990
2004
|
g.leg.adopted.abort();
|
|
1991
2005
|
g.leg = null;
|
|
1992
2006
|
}
|
|
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;
|
|
2007
|
+
placeOnSelf(g, desired, true);
|
|
2008
|
+
setDim(g, false);
|
|
2007
2009
|
}
|
|
2008
|
-
if (g.
|
|
2009
|
-
(
|
|
2010
|
-
g
|
|
2010
|
+
else if (parent && g.leg && g.leg.peer === parent) {
|
|
2011
|
+
g.leg.adopted.move(ev.world, { push: true });
|
|
2012
|
+
setDim(g, false);
|
|
2011
2013
|
}
|
|
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;
|
|
2014
|
+
else if (parent && enterLeg(parent, { push: true })) {
|
|
2015
|
+
setDim(g, false);
|
|
2023
2016
|
}
|
|
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);
|
|
2017
|
+
else {
|
|
2018
|
+
// No board holds the refusing section (a view): dim = will snap home.
|
|
2019
|
+
leaveSelf();
|
|
2020
|
+
setDim(g, true);
|
|
2036
2021
|
}
|
|
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
|
-
}
|
|
2022
|
+
};
|
|
2023
|
+
if (peer && g.refusedPeer === peer) {
|
|
2024
|
+
// -- REFUSED, PUSHED (D2): asked once; every move over it pushes.
|
|
2025
|
+
pushRefused(peer);
|
|
2026
|
+
}
|
|
2027
|
+
else if (peer) {
|
|
2028
|
+
// -- HANDOFF: the pointer is over another board -------------------
|
|
2029
|
+
if (g.leg && g.leg.peer === peer) {
|
|
2030
|
+
g.leg.adopted.move(ev.world);
|
|
2073
2031
|
}
|
|
2074
|
-
else if (
|
|
2075
|
-
// -- back on (or still on) our own board --------------------------
|
|
2032
|
+
else if (enterLeg(peer, {})) {
|
|
2076
2033
|
g.refusedPeer = null;
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
g.leg = null;
|
|
2080
|
-
}
|
|
2081
|
-
placeOnSelf(g, desired);
|
|
2034
|
+
setDim(g, false);
|
|
2035
|
+
g.leg.adopted.move(ev.world);
|
|
2082
2036
|
}
|
|
2083
2037
|
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
|
-
}
|
|
2038
|
+
// The board refused (full, fit): the widget pushes it on its parent (D2).
|
|
2039
|
+
g.refusedPeer = peer;
|
|
2040
|
+
pushRefused(peer);
|
|
2095
2041
|
}
|
|
2096
|
-
|
|
2042
|
+
}
|
|
2043
|
+
else if (inside) {
|
|
2044
|
+
// -- back on (or still on) our own board --------------------------
|
|
2045
|
+
g.refusedPeer = null;
|
|
2046
|
+
if (g.leg) {
|
|
2047
|
+
g.leg.adopted.abort();
|
|
2048
|
+
g.leg = null;
|
|
2049
|
+
}
|
|
2050
|
+
setDim(g, false);
|
|
2051
|
+
placeOnSelf(g, desired);
|
|
2052
|
+
}
|
|
2053
|
+
else {
|
|
2054
|
+
// -- outside every board ------------------------------------------
|
|
2055
|
+
leaveSelf();
|
|
2056
|
+
setDim(g, true);
|
|
2057
|
+
}
|
|
2058
|
+
syncPlaceholder();
|
|
2059
|
+
};
|
|
2060
|
+
const onToolMove = (ev) => {
|
|
2061
|
+
var _a, _b, _c;
|
|
2062
|
+
const g = gesture;
|
|
2063
|
+
if (!g || g.kind === 'palette')
|
|
2064
|
+
return;
|
|
2065
|
+
if (!g.started) {
|
|
2066
|
+
const dx = ev.screen.x - g.downClient.x;
|
|
2067
|
+
const dy = ev.screen.y - g.downClient.y;
|
|
2068
|
+
if (Math.abs(dx) + Math.abs(dy) < DRAG_THRESHOLD)
|
|
2069
|
+
return;
|
|
2070
|
+
beginGestureVisuals(g);
|
|
2071
|
+
}
|
|
2072
|
+
if (g.kind === 'move') {
|
|
2073
|
+
moveGhost(g, ev);
|
|
2097
2074
|
return;
|
|
2098
2075
|
}
|
|
2099
2076
|
// resize: fluid pixel preview on the ghost, cell-stepped live push.
|
|
@@ -2199,7 +2176,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2199
2176
|
maxRows = n;
|
|
2200
2177
|
engine.maxRows = n;
|
|
2201
2178
|
};
|
|
2202
|
-
const slabRows = (
|
|
2179
|
+
const slabRows = (_b = (_a = parent.memberCell(group.id)) === null || _a === void 0 ? void 0 : _a.h) !== null && _b !== void 0 ? _b : maxRows;
|
|
2203
2180
|
const inner = maxRows;
|
|
2204
2181
|
const rhNow = rowHeightFor(geom(), rows());
|
|
2205
2182
|
const rowPx = rhNow + gap;
|
|
@@ -2363,7 +2340,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2363
2340
|
const anchorBottom = liveRect ? liveRect.y + liveRect.height : bottom;
|
|
2364
2341
|
const px = E.w ? anchorRight - w : anchorLeft;
|
|
2365
2342
|
const py = E.n ? anchorBottom - h : anchorTop;
|
|
2366
|
-
g.node.setSize(w, h, (
|
|
2343
|
+
g.node.setSize(w, h, (_c = g.node.size.depth) !== null && _c !== void 0 ? _c : 0);
|
|
2367
2344
|
g.node.setPosition(px, py);
|
|
2368
2345
|
ghostStyleFastPath(g, { x: px, y: py, width: w, height: h });
|
|
2369
2346
|
const spanF = bound() !== undefined ? frame() : f;
|
|
@@ -2421,7 +2398,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2421
2398
|
g.leg.adopted.abort();
|
|
2422
2399
|
g.leg = null;
|
|
2423
2400
|
}
|
|
2424
|
-
const displaced =
|
|
2401
|
+
const displaced = tileCommands(deltasSince(g.startCells, g.startGeom, g.id));
|
|
2425
2402
|
const snap = g.startGeom.get(g.id);
|
|
2426
2403
|
if (snap) {
|
|
2427
2404
|
g.node.setPosition(snap.pos.x, snap.pos.y);
|
|
@@ -2462,7 +2439,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2462
2439
|
finally {
|
|
2463
2440
|
writing = false;
|
|
2464
2441
|
}
|
|
2465
|
-
const sourceDisplaced =
|
|
2442
|
+
const sourceDisplaced = tileCommands(deltasSince(g.startCells, g.startGeom, g.id));
|
|
2466
2443
|
const before = g.startCells.get(g.id);
|
|
2467
2444
|
const geomBefore = g.startGeom.get(g.id);
|
|
2468
2445
|
const crossing = [
|
|
@@ -2527,7 +2504,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2527
2504
|
}
|
|
2528
2505
|
if (g.removedFromBoard && dragOut === 'remove') {
|
|
2529
2506
|
// Release OUTSIDE the board → remove via the page's atomic command path.
|
|
2530
|
-
const displaced =
|
|
2507
|
+
const displaced = tileCommands(deltasSince(g.startCells, g.startGeom, g.id));
|
|
2531
2508
|
const snap = g.startGeom.get(g.id);
|
|
2532
2509
|
if (snap) {
|
|
2533
2510
|
// Park the node on its start rect so the page's RemoveNodeCommand
|
|
@@ -2609,10 +2586,10 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2609
2586
|
};
|
|
2610
2587
|
/** 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
2588
|
const placeOnSelf = (g, desired, pushSolid = false) => {
|
|
2612
|
-
var _a, _b
|
|
2589
|
+
var _a, _b;
|
|
2613
2590
|
if (g.removedFromBoard) {
|
|
2614
2591
|
g.removedFromBoard = false;
|
|
2615
|
-
(
|
|
2592
|
+
setDim(g, false);
|
|
2616
2593
|
const cell = pointToCell(desired.x, desired.y, frame(), geom(), rows(), g.spans.w);
|
|
2617
2594
|
engine.add({ id: g.id, x: 0, y: engine.rows(), w: g.spans.w, h: g.spans.h });
|
|
2618
2595
|
if (!engine.moveCheck(g.id, cell.x, cell.y, { gate: false, pushSolid }).changed)
|
|
@@ -2620,7 +2597,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2620
2597
|
project();
|
|
2621
2598
|
}
|
|
2622
2599
|
else {
|
|
2623
|
-
const spanW = (
|
|
2600
|
+
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
2601
|
const cell = pointToCell(desired.x, desired.y, frame(), geom(), rows(), spanW);
|
|
2625
2602
|
if (engine.moveCheck(g.id, cell.x, cell.y, { pushSolid }).changed)
|
|
2626
2603
|
project();
|
|
@@ -2628,8 +2605,9 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2628
2605
|
};
|
|
2629
2606
|
/** What the pointer means for the dragged tile: the resolve over the live tree, with the beside the hand holds. */
|
|
2630
2607
|
const resolveTileZone = (g, ev) => {
|
|
2608
|
+
var _a, _b;
|
|
2631
2609
|
let strip = null;
|
|
2632
|
-
if (options.tabDrop && !isStatic) {
|
|
2610
|
+
if (options.tabDrop && !isStatic && g.kind !== 'palette') {
|
|
2633
2611
|
const crect = api.container.getBoundingClientRect();
|
|
2634
2612
|
strip = options.tabDrop.stripAt(crect.left + ev.screen.x, crect.top + ev.screen.y);
|
|
2635
2613
|
}
|
|
@@ -2638,7 +2616,9 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2638
2616
|
y: ev.world.y,
|
|
2639
2617
|
roots: zoneRoots(),
|
|
2640
2618
|
strip,
|
|
2641
|
-
prev: beside
|
|
2619
|
+
prev: beside
|
|
2620
|
+
? { 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()) }
|
|
2621
|
+
: ((_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
2622
|
maxDepth: nesting,
|
|
2643
2623
|
ghostDepth: 0,
|
|
2644
2624
|
ghostSubtree: EMPTY_SUBTREE,
|
|
@@ -2858,11 +2838,16 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2858
2838
|
const tl = centredTopLeft(wx, wy, { w: itemW, h: opts.anchor === 'top' ? 0 : itemH });
|
|
2859
2839
|
return pointToCell(tl.x, tl.y, frame(), geom(), rows(), itemW);
|
|
2860
2840
|
};
|
|
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);
|
|
2841
|
+
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
2842
|
let lastWant = null;
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2843
|
+
if (opts.beside) {
|
|
2844
|
+
besideOn(node.id, span, { id: opts.beside.containerId, side: opts.beside.side }, rowOfPoint(world.y));
|
|
2845
|
+
}
|
|
2846
|
+
else {
|
|
2847
|
+
const cell0 = wantedCell(world.x, world.y, span.w, span.h);
|
|
2848
|
+
lastWant = cell0;
|
|
2849
|
+
place(cell0, span.w, opts.push);
|
|
2850
|
+
}
|
|
2866
2851
|
armGlide();
|
|
2867
2852
|
project();
|
|
2868
2853
|
syncPlaceholder();
|
|
@@ -2904,22 +2889,36 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2904
2889
|
const at = engine.getItem(node.id);
|
|
2905
2890
|
return !!at && at.x === cell.x && at.y === cell.y && at.w === cell.w && at.h === cell.h;
|
|
2906
2891
|
},
|
|
2907
|
-
move: (w) => {
|
|
2892
|
+
move: (w, o) => {
|
|
2908
2893
|
const item = engine.getItem(node.id);
|
|
2909
2894
|
if (!item)
|
|
2910
2895
|
return;
|
|
2896
|
+
if (beside)
|
|
2897
|
+
endBeside(true); // the hand left the band: the container comes back before the tile takes a plain cell
|
|
2911
2898
|
const cell = wantedCell(w.x, w.y, item.w, opts.fit === 'shrink' ? hNatural : item.h);
|
|
2912
2899
|
// The search is worth running once per wanted cell, not per pixel.
|
|
2913
2900
|
if (lastWant && lastWant.x === cell.x && lastWant.y === cell.y)
|
|
2914
2901
|
return;
|
|
2915
2902
|
lastWant = cell;
|
|
2916
|
-
if (place(cell, item.w))
|
|
2903
|
+
if (place(cell, item.w, !!(o === null || o === void 0 ? void 0 : o.push)))
|
|
2917
2904
|
project();
|
|
2918
2905
|
syncPlaceholder();
|
|
2919
2906
|
},
|
|
2907
|
+
beside: (containerId, side, w) => {
|
|
2908
|
+
if (!engine.getItem(node.id) && !engine.add({ id: node.id, x: 0, y: engine.rows(), w: span.w, h: hNatural }))
|
|
2909
|
+
return;
|
|
2910
|
+
const row = rowOfPoint(w.y);
|
|
2911
|
+
if (beside && beside.id === containerId && beside.side === side && beside.row === row)
|
|
2912
|
+
return;
|
|
2913
|
+
lastWant = null;
|
|
2914
|
+
besideOn(node.id, span, { id: containerId, side }, row);
|
|
2915
|
+
syncPlaceholder();
|
|
2916
|
+
},
|
|
2917
|
+
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
2918
|
leave: () => {
|
|
2921
2919
|
if (!engine.getItem(node.id))
|
|
2922
2920
|
return;
|
|
2921
|
+
endBeside(true); // a container shifted for the tile comes back by itself: the shift was deliberate, so the memory never brings it
|
|
2923
2922
|
engine.remove(node.id); // displaced tiles come home (gesture memory)
|
|
2924
2923
|
lastWant = null;
|
|
2925
2924
|
project();
|
|
@@ -2938,6 +2937,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2938
2937
|
syncPlaceholder();
|
|
2939
2938
|
},
|
|
2940
2939
|
abort: () => {
|
|
2940
|
+
beside = null; // the snapshot restores the container with everything else
|
|
2941
2941
|
if (engine.getItem(node.id))
|
|
2942
2942
|
engine.remove(node.id);
|
|
2943
2943
|
engine.cancelGesture(); // pre-entry layout, memory cleared
|
|
@@ -2948,6 +2948,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2948
2948
|
syncPlaceholder();
|
|
2949
2949
|
},
|
|
2950
2950
|
finalize: () => {
|
|
2951
|
+
endBeside(false); // the shift stays: the deltas below carry it
|
|
2951
2952
|
const item = engine.getItem(node.id);
|
|
2952
2953
|
if (!item) {
|
|
2953
2954
|
engine.endGesture();
|
|
@@ -2957,26 +2958,11 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2957
2958
|
}
|
|
2958
2959
|
const cell = { x: item.x, y: item.y, w: item.w, h: item.h };
|
|
2959
2960
|
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
|
-
}
|
|
2961
|
+
// Widgets AND sections this board's adoption displaced, as one list: a
|
|
2962
|
+
// dock or a push that moved a section commits it with the drop.
|
|
2963
|
+
const commands = tileCommands(deltasSince(startCells, startGeom, node.id));
|
|
2979
2964
|
engine.endGesture();
|
|
2965
|
+
pendingDrop = node.id; // the tile stays for the member the commit (or the host's drop-in) adds
|
|
2980
2966
|
if (squeezeRoom !== undefined) {
|
|
2981
2967
|
// What the board now HOLDS is its bound, not the squeeze's room.
|
|
2982
2968
|
squeezeRoom = undefined;
|
|
@@ -2985,7 +2971,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2985
2971
|
adoptedGhostId = null;
|
|
2986
2972
|
disarmGlideSoon();
|
|
2987
2973
|
syncPlaceholder();
|
|
2988
|
-
return { commands, cell, rect
|
|
2974
|
+
return { commands, cell, rect };
|
|
2989
2975
|
},
|
|
2990
2976
|
};
|
|
2991
2977
|
};
|
|
@@ -3947,13 +3933,6 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
3947
3933
|
}
|
|
3948
3934
|
};
|
|
3949
3935
|
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
3936
|
// A TOP DOCK INSERTS ROWS. The engine's push cascade resolves the band's
|
|
3958
3937
|
// collisions one tile at a time and scrambles what stood beneath it (the
|
|
3959
3938
|
// demo's KPI row came apart, two of its tiles under the chart). VS Code
|
|
@@ -4204,23 +4183,17 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
4204
4183
|
return;
|
|
4205
4184
|
}
|
|
4206
4185
|
const fin = (_b = leg === null || leg === void 0 ? void 0 : leg.finalize()) !== null && _b !== void 0 ? _b : null;
|
|
4207
|
-
const
|
|
4208
|
-
const before = split;
|
|
4186
|
+
const halved = !!split && !!engine.getItem(z.target.id);
|
|
4209
4187
|
split = null;
|
|
4210
|
-
if (!fin || !
|
|
4188
|
+
if (!fin || !halved) {
|
|
4211
4189
|
leg === null || leg === void 0 ? void 0 : leg.abort();
|
|
4212
4190
|
done(false, 'cancel');
|
|
4213
4191
|
return;
|
|
4214
4192
|
}
|
|
4215
|
-
const keep = { x: it.x, y: it.y, w: it.w, h: it.h };
|
|
4216
4193
|
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
|
-
]);
|
|
4194
|
+
// The target's halving is in the leg's own deltas: its cell at rest was
|
|
4195
|
+
// snapshotted when the ghost entered, and its half is where it stands now.
|
|
4196
|
+
const changed = execute('Split group', [...fin.commands, ...planned.move, ...planned.collapse]);
|
|
4224
4197
|
done(changed, 'commit');
|
|
4225
4198
|
return;
|
|
4226
4199
|
}
|
|
@@ -4238,7 +4211,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
4238
4211
|
return;
|
|
4239
4212
|
}
|
|
4240
4213
|
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, ...
|
|
4214
|
+
done(execute(z.kind === 'root' ? 'Dock tab' : 'Move tab out', [...fin.commands, ...planned.move, ...planned.collapse]), 'commit');
|
|
4242
4215
|
};
|
|
4243
4216
|
const onUp = () => finish(true);
|
|
4244
4217
|
const onCancel = () => finish(false);
|
|
@@ -4310,43 +4283,39 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
4310
4283
|
return;
|
|
4311
4284
|
}
|
|
4312
4285
|
beginGestureVisuals(g);
|
|
4286
|
+
// The chip is held by its middle: the ghost centres under the hand.
|
|
4287
|
+
const f = frame();
|
|
4288
|
+
const gg = geom();
|
|
4289
|
+
g.grab = { dx: (g.spans.w * (columnUnitFor(gg, f.width) + gap) - gap) / 2, dy: (g.spans.h * (rowHeightFor(gg, rows()) + gap) - gap) / 2 };
|
|
4313
4290
|
}
|
|
4314
4291
|
if (chip) {
|
|
4315
4292
|
chip.style.left = `${e.clientX + 6}px`;
|
|
4316
4293
|
chip.style.top = `${e.clientY + 6}px`;
|
|
4317
4294
|
}
|
|
4295
|
+
// THE SAME PATH AS A TILE OF THIS BOARD (tile first, step 4a): the zone
|
|
4296
|
+
// under the hand names the board — this one, a page, a section — and
|
|
4297
|
+
// that board takes the ghost. The palette used to test "inside this
|
|
4298
|
+
// board" and nothing else, so a chip over a page landed under it.
|
|
4299
|
+
const rect = api.container.getBoundingClientRect();
|
|
4318
4300
|
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();
|
|
4301
|
+
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
4302
|
api.render();
|
|
4347
4303
|
};
|
|
4348
|
-
|
|
4304
|
+
/** The drop, on whichever board holds the ghost: the node carries its cell, the host's command adds it there. */
|
|
4305
|
+
const dropOn = (boardId, cell, displaced) => {
|
|
4349
4306
|
var _a, _b;
|
|
4307
|
+
node.setGridItem(gridItemFromCell(cell));
|
|
4308
|
+
if (boardId === group.id)
|
|
4309
|
+
pendingDrop = g.id; // on another board its own finalize() pended it
|
|
4310
|
+
engine.endGesture();
|
|
4311
|
+
cleanupGestureVisuals(g);
|
|
4312
|
+
gesture = null;
|
|
4313
|
+
void ((_a = options.onDropIn) === null || _a === void 0 ? void 0 : _a.call(options, node, cell, displaced, { boardId }));
|
|
4314
|
+
persistLayouts();
|
|
4315
|
+
(_b = options.onGesture) === null || _b === void 0 ? void 0 : _b.call(options, { type: 'drop-in', kind: 'palette', nodeId: g.id, changed: true });
|
|
4316
|
+
api.renderNow();
|
|
4317
|
+
};
|
|
4318
|
+
const finish = (commit) => {
|
|
4350
4319
|
detach();
|
|
4351
4320
|
if (gesture !== g)
|
|
4352
4321
|
return;
|
|
@@ -4357,18 +4326,21 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
4357
4326
|
chip === null || chip === void 0 ? void 0 : chip.remove();
|
|
4358
4327
|
return;
|
|
4359
4328
|
}
|
|
4360
|
-
if (commit &&
|
|
4329
|
+
if (commit && g.leg) {
|
|
4330
|
+
// Dropped into another board (a page, a section): its leg closes with
|
|
4331
|
+
// that board's displaced tiles; this board displaced nothing.
|
|
4332
|
+
const fin = g.leg.adopted.finalize();
|
|
4333
|
+
const boardId = g.leg.adopted.groupId;
|
|
4334
|
+
g.leg = null;
|
|
4335
|
+
if (fin) {
|
|
4336
|
+
dropOn(boardId, fin.cell, [...tileCommands(deltasSince(g.startCells, g.startGeom, g.id)), ...fin.commands]);
|
|
4337
|
+
return;
|
|
4338
|
+
}
|
|
4339
|
+
}
|
|
4340
|
+
else if (commit && !g.removedFromBoard && engine.getItem(g.id)) {
|
|
4361
4341
|
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();
|
|
4342
|
+
endBeside(false); // a container that shifted for the chip stays shifted: its delta commits with the drop
|
|
4343
|
+
dropOn(group.id, { x: item.x, y: item.y, w: item.w, h: item.h }, tileCommands(deltasSince(g.startCells, g.startGeom, g.id)));
|
|
4372
4344
|
return;
|
|
4373
4345
|
}
|
|
4374
4346
|
// Abort (released outside, or Escape): restore the board.
|
|
@@ -4411,18 +4383,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
4411
4383
|
finally {
|
|
4412
4384
|
writing = false;
|
|
4413
4385
|
}
|
|
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
|
-
}
|
|
4386
|
+
const commands = tileCommands(deltasSince(snap.cells, snap.geoms)); // a section's cell-and-frame command rides with the nodes'
|
|
4426
4387
|
engine.endGesture();
|
|
4427
4388
|
disarmGlideSoon();
|
|
4428
4389
|
enforceBoardHeight();
|
|
@@ -4463,6 +4424,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
4463
4424
|
return;
|
|
4464
4425
|
}
|
|
4465
4426
|
applyFluidFrame();
|
|
4427
|
+
pendingDrop = null; // rebuilt from the members: a phantom is gone with the engine
|
|
4466
4428
|
const items = [];
|
|
4467
4429
|
for (const id of (_a = group.members) !== null && _a !== void 0 ? _a : []) {
|
|
4468
4430
|
if (!memberEntity(id))
|
|
@@ -4679,7 +4641,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
4679
4641
|
sizeAfter: { width: target.width, height: target.height },
|
|
4680
4642
|
});
|
|
4681
4643
|
}
|
|
4682
|
-
return
|
|
4644
|
+
return tileCommands(deltas);
|
|
4683
4645
|
},
|
|
4684
4646
|
moveTo(id, x, y) {
|
|
4685
4647
|
return programmatic('Move widget', id, () => engine.moveCheck(id, x, y, { pushSolid: isGroupMember(id) }).changed);
|