@grafloria/element 0.4.51 → 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.
- package/package.json +2 -2
- 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 +1 -1
- package/src/lib/dashboard-kit/grid-binder.d.ts +35 -42
- package/src/lib/dashboard-kit/grid-binder.js +403 -525
- package/src/lib/dashboard-kit/split-binder.js +1 -1
- package/src/lib/dashboard-kit/zones.d.ts +7 -0
- package/src/lib/dashboard-kit/zones.js +12 -6
|
@@ -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. */
|
|
@@ -367,8 +293,8 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
367
293
|
const fluid = options.fluid === true;
|
|
368
294
|
const overflow = (_h = options.overflow) !== null && _h !== void 0 ? _h : 'bounded';
|
|
369
295
|
let isStatic = options.static === true;
|
|
370
|
-
/** The deepest board a drop may enter — a root is 0
|
|
371
|
-
const nesting = (_j = options.nesting) !== null && _j !== void 0 ? _j :
|
|
296
|
+
/** The deepest board a drop may enter — a root is 0. Unbounded unless asked for: three-level drops are in the specs today (tile first, step 2). */
|
|
297
|
+
const nesting = (_j = options.nesting) !== null && _j !== void 0 ? _j : Number.POSITIVE_INFINITY;
|
|
372
298
|
let dragHandle = normalizeDragHandle(options.dragHandle);
|
|
373
299
|
let dragSel = dragHandleSelector(dragHandle);
|
|
374
300
|
api.container.classList.toggle(DRAG_HANDLE_CLASS, dragHandle === true);
|
|
@@ -624,10 +550,14 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
624
550
|
const cell = grp
|
|
625
551
|
? cellFromGridItem((_g = grp.getMetadata) === null || _g === void 0 ? void 0 : _g.call(grp, 'gridItem'))
|
|
626
552
|
: null;
|
|
627
|
-
//
|
|
553
|
+
// A member group is a SOLID tile (tile first, step 3): a widget carried
|
|
554
|
+
// over it slides aside and it never packs — the board under the hand
|
|
555
|
+
// holds still, so its zones (into, a tab, beside) stay reachable — and
|
|
556
|
+
// it is moved by INTENT: placed beside, pushed by a moved section, a
|
|
557
|
+
// dock or a widget it refused, moved by its own gesture.
|
|
628
558
|
if (cell)
|
|
629
|
-
return Object.assign(Object.assign({ id }, cell), {
|
|
630
|
-
return { id, x: 0, y: 0, w: columns, h: 1,
|
|
559
|
+
return Object.assign(Object.assign({ id }, cell), { solid: true });
|
|
560
|
+
return { id, x: 0, y: 0, w: columns, h: 1, solid: true, autoPosition: true };
|
|
631
561
|
};
|
|
632
562
|
/** Persist adopted cells so save/undo round-trips them. */
|
|
633
563
|
const persistAdoptedCell = (id, item) => {
|
|
@@ -1000,19 +930,6 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1000
930
|
}
|
|
1001
931
|
return out;
|
|
1002
932
|
};
|
|
1003
|
-
/** The member groups a gesture displaced, as their own cell commands — `buildCommitCommands` skips groups by design. */
|
|
1004
|
-
const groupCellCommands = (deltas) => {
|
|
1005
|
-
const out = [];
|
|
1006
|
-
for (const d of deltas) {
|
|
1007
|
-
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))
|
|
1008
|
-
continue;
|
|
1009
|
-
const og = diagram.getGroup(d.id);
|
|
1010
|
-
if (!og)
|
|
1011
|
-
continue;
|
|
1012
|
-
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)));
|
|
1013
|
-
}
|
|
1014
|
-
return out;
|
|
1015
|
-
};
|
|
1016
933
|
const execute = (name, commands) => {
|
|
1017
934
|
if (commands.length === 0)
|
|
1018
935
|
return false;
|
|
@@ -1020,10 +937,23 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1020
937
|
return true;
|
|
1021
938
|
};
|
|
1022
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;
|
|
1023
949
|
const onMemberAdded = (id) => {
|
|
1024
|
-
var _a;
|
|
950
|
+
var _a, _b;
|
|
1025
951
|
if (disposed)
|
|
1026
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;
|
|
1027
957
|
if (!engine.getItem(id)) {
|
|
1028
958
|
const item = itemFor(id);
|
|
1029
959
|
let placed = engine.add(item);
|
|
@@ -1033,7 +963,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1033
963
|
// for this adoption — it floors at the content on the next refresh.
|
|
1034
964
|
capacity = undefined;
|
|
1035
965
|
engine = engineFrom([...engine.getItems().map((i) => (Object.assign({}, i))), item]);
|
|
1036
|
-
placed = (
|
|
966
|
+
placed = (_b = engine.getItem(id)) !== null && _b !== void 0 ? _b : null;
|
|
1037
967
|
refreshCapacity();
|
|
1038
968
|
}
|
|
1039
969
|
if (placed)
|
|
@@ -1288,7 +1218,13 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1288
1218
|
* carried along the top of a tall panel to its far right means "after it",
|
|
1289
1219
|
* not "above it". The shift is undone if the pointer leaves the band.
|
|
1290
1220
|
*/
|
|
1291
|
-
/**
|
|
1221
|
+
/**
|
|
1222
|
+
* The beside the hand holds (tile first, step 3): the container's cell and
|
|
1223
|
+
* frame AT REST (the resolve reads the held container at rest), the cell
|
|
1224
|
+
* the widget took, and the row it took it at. The shift itself is the
|
|
1225
|
+
* engine's `placeBeside`; the sections it pushed remember their cells and
|
|
1226
|
+
* come back by themselves when the widget leaves (S2).
|
|
1227
|
+
*/
|
|
1292
1228
|
let beside = null;
|
|
1293
1229
|
const endBeside = (restore) => {
|
|
1294
1230
|
if (!beside)
|
|
@@ -1296,104 +1232,64 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1296
1232
|
const it = engine.getItem(beside.id);
|
|
1297
1233
|
let moved = false;
|
|
1298
1234
|
if (it && restore && (it.x !== beside.from.x || it.y !== beside.from.y))
|
|
1299
|
-
moved = engine.moveCheck(beside.id, beside.from.x, beside.from.y, { gate: false }).changed
|
|
1300
|
-
if (restore) {
|
|
1301
|
-
// The sections the shift pushed come back with it.
|
|
1302
|
-
for (const [oid, c] of beside.others) {
|
|
1303
|
-
const o = engine.getItem(oid);
|
|
1304
|
-
if (o && (o.x !== c.x || o.y !== c.y))
|
|
1305
|
-
moved = engine.moveCheck(oid, c.x, c.y, { gate: false }).changed || moved;
|
|
1306
|
-
}
|
|
1307
|
-
}
|
|
1308
|
-
if (it)
|
|
1309
|
-
it.locked = true;
|
|
1310
|
-
relockOthersForSlab();
|
|
1235
|
+
moved = engine.moveCheck(beside.id, beside.from.x, beside.from.y, { gate: false }).changed;
|
|
1311
1236
|
beside = null;
|
|
1312
1237
|
// The frames the zone test reads are the MODEL's: a restore that moved
|
|
1313
1238
|
// the container without projecting left its frame where the push had
|
|
1314
|
-
// put it
|
|
1315
|
-
// 3440-px corner on 0.4.48: along the top band the panel was pushed
|
|
1316
|
-
// down, and the corner never turned into "right").
|
|
1239
|
+
// put it (the user's 3440-px corner on 0.4.48).
|
|
1317
1240
|
if (moved)
|
|
1318
1241
|
project();
|
|
1319
1242
|
};
|
|
1320
|
-
|
|
1321
|
-
|
|
1243
|
+
/** The row of this board's grid under a world y — where a beside lands (D3: the pointer's row, not the container's top). */
|
|
1244
|
+
const rowOfPoint = (y) => {
|
|
1245
|
+
const r0 = cellToRect({ x: 0, y: 0, w: 1, h: 1 }, frame(), geom(), rows());
|
|
1246
|
+
const r1 = cellToRect({ x: 0, y: 1, w: 1, h: 1 }, frame(), geom(), rows());
|
|
1247
|
+
const pitch = Math.max(1, r1.y - r0.y);
|
|
1248
|
+
return Math.max(0, Math.floor((y - r0.y) / pitch));
|
|
1249
|
+
};
|
|
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) => {
|
|
1322
1258
|
const tc = engine.getItem(z.id);
|
|
1323
1259
|
if (!tc)
|
|
1324
1260
|
return;
|
|
1325
|
-
if (g.leg) {
|
|
1326
|
-
g.leg.adopted.abort();
|
|
1327
|
-
g.leg = null;
|
|
1328
|
-
}
|
|
1329
|
-
const w = g.spans.w;
|
|
1330
|
-
const h = g.spans.h;
|
|
1331
|
-
if (g.removedFromBoard) {
|
|
1332
|
-
g.removedFromBoard = false;
|
|
1333
|
-
(_a = hostOf(g.id)) === null || _a === void 0 ? void 0 : _a.classList.remove('axdb-out');
|
|
1334
|
-
engine.add({ id: g.id, x: 0, y: engine.rows(), w, h });
|
|
1335
|
-
}
|
|
1336
1261
|
if (beside && (beside.id !== z.id || beside.side !== z.side))
|
|
1337
|
-
endBeside(true); // another container, or another side of it:
|
|
1338
|
-
const from = beside ? beside.from : { x: tc.x, y: tc.y };
|
|
1339
|
-
let cell;
|
|
1340
|
-
let shiftTo = null;
|
|
1341
|
-
switch (z.side) {
|
|
1342
|
-
case 'right':
|
|
1343
|
-
cell = { x: from.x + tc.w, y: from.y };
|
|
1344
|
-
if (cell.x + w > columns) {
|
|
1345
|
-
shiftTo = { x: columns - w - tc.w, y: from.y };
|
|
1346
|
-
cell = { x: columns - w, y: from.y };
|
|
1347
|
-
}
|
|
1348
|
-
break;
|
|
1349
|
-
case 'left':
|
|
1350
|
-
cell = { x: from.x - w, y: from.y };
|
|
1351
|
-
if (cell.x < 0) {
|
|
1352
|
-
shiftTo = { x: w, y: from.y };
|
|
1353
|
-
cell = { x: 0, y: from.y };
|
|
1354
|
-
}
|
|
1355
|
-
break;
|
|
1356
|
-
case 'top':
|
|
1357
|
-
cell = { x: Math.max(0, Math.min(from.x, columns - w)), y: from.y }; // the ghost takes the container's rows; the container is pushed down under it
|
|
1358
|
-
break;
|
|
1359
|
-
default:
|
|
1360
|
-
cell = { x: Math.max(0, Math.min(from.x, columns - w)), y: from.y + tc.h };
|
|
1361
|
-
}
|
|
1362
|
-
if (shiftTo && shiftTo.x < 0)
|
|
1363
|
-
shiftTo = null; // no room even shifted: the widget goes where it can
|
|
1262
|
+
endBeside(true); // another container, or another side of it: from the rest layout
|
|
1364
1263
|
if (!beside) {
|
|
1365
|
-
// The other sections give way to the shift the way they give way to a
|
|
1366
|
-
// moved group (0.4.44): on the demo the Operations section spans the
|
|
1367
|
-
// row under the panel and a locked tile refused the shift outright.
|
|
1368
|
-
// Their cells are kept so they come back if the pointer leaves.
|
|
1369
|
-
const others = new Map();
|
|
1370
|
-
for (const o of engine.getItems())
|
|
1371
|
-
if (o.id !== z.id && o.id !== g.id && isGroupMember(o.id))
|
|
1372
|
-
others.set(o.id, { x: o.x, y: o.y });
|
|
1373
1264
|
const grp0 = diagram.getGroup(z.id);
|
|
1374
|
-
beside = { id: z.id, side: z.side, from, frame0: grp0 ? frameOfGroup(grp0) : cellToRect({ x:
|
|
1375
|
-
unlockOthersForSlab(z.id);
|
|
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 };
|
|
1376
1266
|
}
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
if (shiftTo && (tc.x !== shiftTo.x || tc.y !== shiftTo.y)) {
|
|
1383
|
-
if (engine.getItem(g.id))
|
|
1384
|
-
engine.remove(g.id);
|
|
1385
|
-
engine.moveCheck(z.id, shiftTo.x, shiftTo.y, { gate: false });
|
|
1267
|
+
const r = engine.placeBeside(id, z.id, z.side, row);
|
|
1268
|
+
const at = engine.getItem(id);
|
|
1269
|
+
if (r.changed && at) {
|
|
1270
|
+
beside.vacated = { x: at.x, y: at.y };
|
|
1271
|
+
beside.row = row;
|
|
1386
1272
|
}
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
if (!engine.moveCheck(g.id, cell.x, cell.y, { gate: false }).changed) {
|
|
1391
|
-
const at = engine.getItem(g.id);
|
|
1392
|
-
if (!at || at.x !== cell.x || at.y !== cell.y)
|
|
1393
|
-
placeNear(g.id, cell.x, cell.y, w);
|
|
1273
|
+
else if (at) {
|
|
1274
|
+
// Refused (a bound): the widget goes where it can, as any refused cell does.
|
|
1275
|
+
placeNear(id, at.x, at.y, spans.w);
|
|
1394
1276
|
}
|
|
1395
1277
|
project();
|
|
1396
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
|
+
};
|
|
1397
1293
|
/**
|
|
1398
1294
|
* CARRIED (0.4.43): a group dragged by its strip or band moves as ONE thing.
|
|
1399
1295
|
* The held TILE is transition-exempt (the ghost), but a group has no host of
|
|
@@ -1610,13 +1506,6 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1610
1506
|
},
|
|
1611
1507
|
move: false,
|
|
1612
1508
|
};
|
|
1613
|
-
// A TOP or LEFT edge moves the slab's origin, and the engine refuses to
|
|
1614
|
-
// move a locked tile: the move never landed, only the resize did, and a
|
|
1615
|
-
// section pulled up by its caption grew DOWNWARD a row per pointer step
|
|
1616
|
-
// — 18 rows for a 2-row pull (identification round, F16). Unlocked for
|
|
1617
|
-
// its own gesture, as a move is; relocked on release.
|
|
1618
|
-
if (edges.n || edges.w)
|
|
1619
|
-
it.locked = false;
|
|
1620
1509
|
capturePointer(slabGesture.pointerId);
|
|
1621
1510
|
api.container.style.cursor = cursorFor(edges);
|
|
1622
1511
|
};
|
|
@@ -1633,24 +1522,6 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1633
1522
|
* pushes it; a group's children ride along: the frame moves, the nested
|
|
1634
1523
|
* board re-projects. The others relock on release.
|
|
1635
1524
|
*/
|
|
1636
|
-
let slabUnlocked = [];
|
|
1637
|
-
const unlockOthersForSlab = (id) => {
|
|
1638
|
-
slabUnlocked = [];
|
|
1639
|
-
for (const o of engine.getItems()) {
|
|
1640
|
-
if (o.id !== id && o.locked && isGroupMember(o.id)) {
|
|
1641
|
-
o.locked = false;
|
|
1642
|
-
slabUnlocked.push(o.id);
|
|
1643
|
-
}
|
|
1644
|
-
}
|
|
1645
|
-
};
|
|
1646
|
-
const relockOthersForSlab = () => {
|
|
1647
|
-
for (const oid of slabUnlocked) {
|
|
1648
|
-
const o = engine.getItem(oid);
|
|
1649
|
-
if (o)
|
|
1650
|
-
o.locked = true;
|
|
1651
|
-
}
|
|
1652
|
-
slabUnlocked = [];
|
|
1653
|
-
};
|
|
1654
1525
|
const beginSlabMove = (id, ev) => {
|
|
1655
1526
|
const grp = diagram.getGroup(id);
|
|
1656
1527
|
const it = engine.getItem(id);
|
|
@@ -1658,8 +1529,6 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1658
1529
|
return;
|
|
1659
1530
|
engine.beginGesture();
|
|
1660
1531
|
const snap = snapshotAll();
|
|
1661
|
-
it.locked = false;
|
|
1662
|
-
unlockOthersForSlab(id);
|
|
1663
1532
|
slabGesture = {
|
|
1664
1533
|
id,
|
|
1665
1534
|
edges: NO_EDGES,
|
|
@@ -1676,11 +1545,6 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1676
1545
|
capturePointer(slabGesture.pointerId);
|
|
1677
1546
|
api.container.style.cursor = 'grabbing';
|
|
1678
1547
|
};
|
|
1679
|
-
const relockSlab = (id) => {
|
|
1680
|
-
const it = engine.getItem(id);
|
|
1681
|
-
if (it)
|
|
1682
|
-
it.locked = true;
|
|
1683
|
-
};
|
|
1684
1548
|
/** The cell a slab move asked for and could not have — painted so the refusal is visible; null clears it. */
|
|
1685
1549
|
let refusal = null;
|
|
1686
1550
|
const showRefusal = (cell, w, h) => {
|
|
@@ -1734,7 +1598,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1734
1598
|
// Along its ROW only: a section dragged LEFT that wandered DOWN its
|
|
1735
1599
|
// column (the row search) read as the wrong tile moving.
|
|
1736
1600
|
const was = { x: it.x, y: it.y };
|
|
1737
|
-
const moved = engine.moveCheck(g.id, cell.x, cell.y, { gate: false }).changed || placeOnRow(g.id, cell.x, cell.y, it.w);
|
|
1601
|
+
const moved = engine.moveCheck(g.id, cell.x, cell.y, { gate: false, pushSolid: true }).changed || placeOnRow(g.id, cell.x, cell.y, it.w, true); // a moved section pushes the sections in its way (0.4.44)
|
|
1738
1602
|
if (moved) {
|
|
1739
1603
|
project();
|
|
1740
1604
|
setCarried(g.id, true); // chrome repainted by the projection is carried too
|
|
@@ -1781,7 +1645,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1781
1645
|
}
|
|
1782
1646
|
let changed = false;
|
|
1783
1647
|
if (x !== it.x || y !== it.y)
|
|
1784
|
-
changed = engine.moveCheck(g.id, x, y, { gate: false }).changed || changed;
|
|
1648
|
+
changed = engine.moveCheck(g.id, x, y, { gate: false, pushSolid: true }).changed || changed;
|
|
1785
1649
|
if (w !== it.w || h !== it.h)
|
|
1786
1650
|
changed = engine.resizeCheck(g.id, w, h).changed || changed;
|
|
1787
1651
|
if (changed)
|
|
@@ -1796,8 +1660,6 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1796
1660
|
showRefusal(null, 0, 0);
|
|
1797
1661
|
releasePointer(g.pointerId);
|
|
1798
1662
|
api.container.style.cursor = '';
|
|
1799
|
-
relockSlab(g.id);
|
|
1800
|
-
relockOthersForSlab();
|
|
1801
1663
|
if (g.move && g.started)
|
|
1802
1664
|
setCarried(g.id, false); // exempt through the drop write, then the glides resume
|
|
1803
1665
|
if (!g.started) {
|
|
@@ -1806,19 +1668,9 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1806
1668
|
}
|
|
1807
1669
|
engine.endGesture();
|
|
1808
1670
|
project();
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
const
|
|
1812
|
-
const commands = buildCommitCommands(deltas);
|
|
1813
|
-
// The sections and groups the move PUSHED (0.4.44): the tile commit skips
|
|
1814
|
-
// groups by design (a group's cell is its own command), so they are
|
|
1815
|
-
// committed here the way a dock commits the groups it displaced — or the
|
|
1816
|
-
// model snapped every one of them, and the mover, straight back.
|
|
1817
|
-
commands.push(...groupCellCommands(deltas));
|
|
1818
|
-
const b = g.cellBefore;
|
|
1819
|
-
if (it && grp && (b.x !== it.x || b.y !== it.y || b.w !== it.w || b.h !== it.h)) {
|
|
1820
|
-
commands.push(new SetGroupCellCommand(g.id, b, { x: it.x, y: it.y, w: it.w, h: it.h }, g.frameBefore, frameOfGroup(grp)));
|
|
1821
|
-
}
|
|
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));
|
|
1822
1674
|
const changed = execute(g.move ? 'Move section' : 'Resize section', commands);
|
|
1823
1675
|
disarmGlideSoon();
|
|
1824
1676
|
syncHandles();
|
|
@@ -1833,8 +1685,6 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1833
1685
|
slabGesture = null;
|
|
1834
1686
|
releasePointer(g.pointerId);
|
|
1835
1687
|
api.container.style.cursor = '';
|
|
1836
|
-
relockSlab(g.id);
|
|
1837
|
-
relockOthersForSlab();
|
|
1838
1688
|
if (g.move && g.started)
|
|
1839
1689
|
setCarried(g.id, false);
|
|
1840
1690
|
if (g.started)
|
|
@@ -1905,6 +1755,12 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1905
1755
|
if (rect.height !== undefined)
|
|
1906
1756
|
el.style.height = `${rect.height}px`;
|
|
1907
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
|
+
};
|
|
1908
1764
|
const cleanupGestureVisuals = (g) => {
|
|
1909
1765
|
var _a;
|
|
1910
1766
|
if (g.kind !== 'palette')
|
|
@@ -1935,8 +1791,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1935
1791
|
gesture = null;
|
|
1936
1792
|
project();
|
|
1937
1793
|
const deltas = deltasSince(g.startCells, g.startGeom);
|
|
1938
|
-
const commands =
|
|
1939
|
-
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)
|
|
1940
1795
|
endBeside(false);
|
|
1941
1796
|
if (g.esc && g.esc.rowsAdded !== 0) {
|
|
1942
1797
|
commands.push(new SetGroupCellCommand(group.id, g.esc.cellBefore, g.esc.cellAfter, g.esc.frameBefore, g.esc.frameAfter));
|
|
@@ -1953,7 +1808,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1953
1808
|
(_a = options.onGesture) === null || _a === void 0 ? void 0 : _a.call(options, { type: 'commit', kind: g.kind, nodeId: g.id, changed });
|
|
1954
1809
|
};
|
|
1955
1810
|
const cancelActiveGesture = (notify = true) => {
|
|
1956
|
-
var _a, _b, _c
|
|
1811
|
+
var _a, _b, _c;
|
|
1957
1812
|
if (gesture === null || gesture === void 0 ? void 0 : gesture.strip) {
|
|
1958
1813
|
(_a = options.tabDrop) === null || _a === void 0 ? void 0 : _a.markDrop(null, null);
|
|
1959
1814
|
gesture.strip = null;
|
|
@@ -1968,31 +1823,20 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1968
1823
|
if (!g)
|
|
1969
1824
|
return;
|
|
1970
1825
|
gesture = null;
|
|
1971
|
-
if (g.
|
|
1826
|
+
if (g.leg) {
|
|
1972
1827
|
g.leg.adopted.abort(); // target board back to its pre-entry layout
|
|
1973
1828
|
g.leg = null;
|
|
1974
1829
|
}
|
|
1975
|
-
if (g.
|
|
1830
|
+
if (g.esc && g.esc.rowsAdded !== 0) {
|
|
1976
1831
|
g.esc.peer.resizeMemberBy(group.id, -g.esc.rowsAdded); // slab back down
|
|
1977
1832
|
g.esc = null;
|
|
1978
1833
|
}
|
|
1979
1834
|
endBeside(true);
|
|
1980
1835
|
if (g.started) {
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
engine.endGesture();
|
|
1986
|
-
const items = [];
|
|
1987
|
-
for (const [id, c] of g.startCells) {
|
|
1988
|
-
const lockedNode = ((_d = (_c = diagram.getNode(id)) === null || _c === void 0 ? void 0 : _c.state) === null || _d === void 0 ? void 0 : _d.locked) === true;
|
|
1989
|
-
items.push(Object.assign(Object.assign({ id }, c), { locked: lockedNode || isGroupMember(id) }));
|
|
1990
|
-
}
|
|
1991
|
-
engine = engineFrom(items);
|
|
1992
|
-
}
|
|
1993
|
-
else {
|
|
1994
|
-
engine.cancelGesture();
|
|
1995
|
-
}
|
|
1836
|
+
// The engine's gesture snapshot restores cells, sizes AND membership
|
|
1837
|
+
// (engine 0.3.8): a ghost removed mid-gesture comes back at its start
|
|
1838
|
+
// cell, a palette tile added mid-gesture is gone.
|
|
1839
|
+
engine.cancelGesture();
|
|
1996
1840
|
if (designRows !== undefined) {
|
|
1997
1841
|
maxRows = liveBound(engine.getItems());
|
|
1998
1842
|
engine.maxRows = maxRows;
|
|
@@ -2033,7 +1877,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2033
1877
|
enforceBoardHeight();
|
|
2034
1878
|
api.renderNow();
|
|
2035
1879
|
if (notify) {
|
|
2036
|
-
(
|
|
1880
|
+
(_c = options.onGesture) === null || _c === void 0 ? void 0 : _c.call(options, { type: 'cancel', kind: g.kind, nodeId: g.id, changed: false });
|
|
2037
1881
|
}
|
|
2038
1882
|
};
|
|
2039
1883
|
/** Centre a w×h-span tile's top-left under the cursor, in world px. */
|
|
@@ -2047,141 +1891,186 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2047
1891
|
y: worldY - (spans.h * (rh + gap) - gap) / 2,
|
|
2048
1892
|
};
|
|
2049
1893
|
};
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
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();
|
|
2054
1965
|
return;
|
|
2055
|
-
if (!g.started) {
|
|
2056
|
-
const dx = ev.screen.x - g.downClient.x;
|
|
2057
|
-
const dy = ev.screen.y - g.downClient.y;
|
|
2058
|
-
if (Math.abs(dx) + Math.abs(dy) < DRAG_THRESHOLD)
|
|
2059
|
-
return;
|
|
2060
|
-
beginGestureVisuals(g);
|
|
2061
1966
|
}
|
|
2062
|
-
if (g.
|
|
2063
|
-
|
|
2064
|
-
g.
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
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) {
|
|
2077
2003
|
if (g.leg) {
|
|
2078
2004
|
g.leg.adopted.abort();
|
|
2079
2005
|
g.leg = null;
|
|
2080
2006
|
}
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
g.removedFromBoard = true;
|
|
2084
|
-
engine.remove(g.id);
|
|
2085
|
-
project();
|
|
2086
|
-
}
|
|
2087
|
-
(_a = hostOf(g.id)) === null || _a === void 0 ? void 0 : _a.classList.remove('axdb-out');
|
|
2088
|
-
if (!g.strip || g.strip.containerId !== z.containerId || g.strip.index !== z.index) {
|
|
2089
|
-
options.tabDrop.markDrop(z.containerId, z.index);
|
|
2090
|
-
}
|
|
2091
|
-
g.strip = { containerId: z.containerId, index: z.index };
|
|
2092
|
-
syncPlaceholder();
|
|
2093
|
-
api.render();
|
|
2094
|
-
return;
|
|
2007
|
+
placeOnSelf(g, desired, true);
|
|
2008
|
+
setDim(g, false);
|
|
2095
2009
|
}
|
|
2096
|
-
if (g.
|
|
2097
|
-
(
|
|
2098
|
-
g
|
|
2010
|
+
else if (parent && g.leg && g.leg.peer === parent) {
|
|
2011
|
+
g.leg.adopted.move(ev.world, { push: true });
|
|
2012
|
+
setDim(g, false);
|
|
2099
2013
|
}
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
// to make room, live and gliding, like any widget gives way (0.4.48).
|
|
2103
|
-
// (A container on another board still resolves as a plain cell there
|
|
2104
|
-
// until the gesture scope spans boards — step 3.)
|
|
2105
|
-
if (z.kind === 'beside' && !isStatic && z.board.ref === selfPeer) {
|
|
2106
|
-
if (!z.kept)
|
|
2107
|
-
applyBeside(g, { id: z.containerId, side: z.side });
|
|
2108
|
-
syncPlaceholder();
|
|
2109
|
-
return;
|
|
2014
|
+
else if (parent && enterLeg(parent, { push: true })) {
|
|
2015
|
+
setDim(g, false);
|
|
2110
2016
|
}
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
const onSelf = z.kind !== 'off' && z.kind !== 'strip' && z.board.ref === selfPeer;
|
|
2116
|
-
const inside = onSelf || (z.kind === 'off' && worldInsideBoardGrace(ev.world.x, ev.world.y));
|
|
2117
|
-
const peer = !onSelf && z.kind !== 'off' && z.kind !== 'strip' ? z.board.ref : null;
|
|
2118
|
-
if (peer) {
|
|
2119
|
-
// -- HANDOFF: the pointer is over another board -------------------
|
|
2120
|
-
if (g.leg && g.leg.peer === peer) {
|
|
2121
|
-
g.leg.adopted.move(ev.world);
|
|
2122
|
-
}
|
|
2123
|
-
else {
|
|
2124
|
-
if (g.leg) {
|
|
2125
|
-
g.leg.adopted.abort();
|
|
2126
|
-
g.leg = null;
|
|
2127
|
-
}
|
|
2128
|
-
if (!g.removedFromBoard) {
|
|
2129
|
-
g.removedFromBoard = true;
|
|
2130
|
-
engine.remove(g.id); // survivors settle home (gesture memory intact)
|
|
2131
|
-
project();
|
|
2132
|
-
}
|
|
2133
|
-
const adopted = peer.adopt(g.node, ev.world, {
|
|
2134
|
-
width: g.node.size.width,
|
|
2135
|
-
height: g.node.size.height,
|
|
2136
|
-
});
|
|
2137
|
-
if (adopted) {
|
|
2138
|
-
(_c = hostOf(g.id)) === null || _c === void 0 ? void 0 : _c.classList.remove('axdb-out');
|
|
2139
|
-
g.leg = { peer, adopted };
|
|
2140
|
-
g.leg.adopted.move(ev.world);
|
|
2141
|
-
}
|
|
2142
|
-
else {
|
|
2143
|
-
// Bounded/full board refused the adoption: dim = will snap home.
|
|
2144
|
-
(_d = hostOf(g.id)) === null || _d === void 0 ? void 0 : _d.classList.add('axdb-out');
|
|
2145
|
-
}
|
|
2146
|
-
}
|
|
2017
|
+
else {
|
|
2018
|
+
// No board holds the refusing section (a view): dim = will snap home.
|
|
2019
|
+
leaveSelf();
|
|
2020
|
+
setDim(g, true);
|
|
2147
2021
|
}
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
project();
|
|
2163
|
-
}
|
|
2164
|
-
else {
|
|
2165
|
-
const spanW = (_g = (_f = engine.getItem(g.id)) === null || _f === void 0 ? void 0 : _f.w) !== null && _g !== void 0 ? _g : g.spans.w;
|
|
2166
|
-
const cell = pointToCell(desired.x, desired.y, frame(), geom(), rows(), spanW);
|
|
2167
|
-
if (engine.moveCheck(g.id, cell.x, cell.y).changed)
|
|
2168
|
-
project();
|
|
2169
|
-
}
|
|
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);
|
|
2031
|
+
}
|
|
2032
|
+
else if (enterLeg(peer, {})) {
|
|
2033
|
+
g.refusedPeer = null;
|
|
2034
|
+
setDim(g, false);
|
|
2035
|
+
g.leg.adopted.move(ev.world);
|
|
2170
2036
|
}
|
|
2171
2037
|
else {
|
|
2172
|
-
//
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
g.leg = null;
|
|
2176
|
-
}
|
|
2177
|
-
if (!g.removedFromBoard) {
|
|
2178
|
-
g.removedFromBoard = true;
|
|
2179
|
-
engine.remove(g.id); // survivors settle home; cells minted nowhere
|
|
2180
|
-
(_h = hostOf(g.id)) === null || _h === void 0 ? void 0 : _h.classList.add('axdb-out');
|
|
2181
|
-
project();
|
|
2182
|
-
}
|
|
2038
|
+
// The board refused (full, fit): the widget pushes it on its parent (D2).
|
|
2039
|
+
g.refusedPeer = peer;
|
|
2040
|
+
pushRefused(peer);
|
|
2183
2041
|
}
|
|
2184
|
-
|
|
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);
|
|
2185
2074
|
return;
|
|
2186
2075
|
}
|
|
2187
2076
|
// resize: fluid pixel preview on the ghost, cell-stepped live push.
|
|
@@ -2287,7 +2176,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2287
2176
|
maxRows = n;
|
|
2288
2177
|
engine.maxRows = n;
|
|
2289
2178
|
};
|
|
2290
|
-
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;
|
|
2291
2180
|
const inner = maxRows;
|
|
2292
2181
|
const rhNow = rowHeightFor(geom(), rows());
|
|
2293
2182
|
const rowPx = rhNow + gap;
|
|
@@ -2451,7 +2340,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2451
2340
|
const anchorBottom = liveRect ? liveRect.y + liveRect.height : bottom;
|
|
2452
2341
|
const px = E.w ? anchorRight - w : anchorLeft;
|
|
2453
2342
|
const py = E.n ? anchorBottom - h : anchorTop;
|
|
2454
|
-
g.node.setSize(w, h, (
|
|
2343
|
+
g.node.setSize(w, h, (_c = g.node.size.depth) !== null && _c !== void 0 ? _c : 0);
|
|
2455
2344
|
g.node.setPosition(px, py);
|
|
2456
2345
|
ghostStyleFastPath(g, { x: px, y: py, width: w, height: h });
|
|
2457
2346
|
const spanF = bound() !== undefined ? frame() : f;
|
|
@@ -2509,7 +2398,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2509
2398
|
g.leg.adopted.abort();
|
|
2510
2399
|
g.leg = null;
|
|
2511
2400
|
}
|
|
2512
|
-
const displaced =
|
|
2401
|
+
const displaced = tileCommands(deltasSince(g.startCells, g.startGeom, g.id));
|
|
2513
2402
|
const snap = g.startGeom.get(g.id);
|
|
2514
2403
|
if (snap) {
|
|
2515
2404
|
g.node.setPosition(snap.pos.x, snap.pos.y);
|
|
@@ -2550,7 +2439,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2550
2439
|
finally {
|
|
2551
2440
|
writing = false;
|
|
2552
2441
|
}
|
|
2553
|
-
const sourceDisplaced =
|
|
2442
|
+
const sourceDisplaced = tileCommands(deltasSince(g.startCells, g.startGeom, g.id));
|
|
2554
2443
|
const before = g.startCells.get(g.id);
|
|
2555
2444
|
const geomBefore = g.startGeom.get(g.id);
|
|
2556
2445
|
const crossing = [
|
|
@@ -2615,7 +2504,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2615
2504
|
}
|
|
2616
2505
|
if (g.removedFromBoard && dragOut === 'remove') {
|
|
2617
2506
|
// Release OUTSIDE the board → remove via the page's atomic command path.
|
|
2618
|
-
const displaced =
|
|
2507
|
+
const displaced = tileCommands(deltasSince(g.startCells, g.startGeom, g.id));
|
|
2619
2508
|
const snap = g.startGeom.get(g.id);
|
|
2620
2509
|
if (snap) {
|
|
2621
2510
|
// Park the node on its start rect so the page's RemoveNodeCommand
|
|
@@ -2695,10 +2584,30 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2695
2584
|
});
|
|
2696
2585
|
return peers.filter((p) => !p.group.parentGroupId).map((p) => boardRef(p, 0));
|
|
2697
2586
|
};
|
|
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. */
|
|
2588
|
+
const placeOnSelf = (g, desired, pushSolid = false) => {
|
|
2589
|
+
var _a, _b;
|
|
2590
|
+
if (g.removedFromBoard) {
|
|
2591
|
+
g.removedFromBoard = false;
|
|
2592
|
+
setDim(g, false);
|
|
2593
|
+
const cell = pointToCell(desired.x, desired.y, frame(), geom(), rows(), g.spans.w);
|
|
2594
|
+
engine.add({ id: g.id, x: 0, y: engine.rows(), w: g.spans.w, h: g.spans.h });
|
|
2595
|
+
if (!engine.moveCheck(g.id, cell.x, cell.y, { gate: false, pushSolid }).changed)
|
|
2596
|
+
placeNear(g.id, cell.x, cell.y, g.spans.w, pushSolid);
|
|
2597
|
+
project();
|
|
2598
|
+
}
|
|
2599
|
+
else {
|
|
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;
|
|
2601
|
+
const cell = pointToCell(desired.x, desired.y, frame(), geom(), rows(), spanW);
|
|
2602
|
+
if (engine.moveCheck(g.id, cell.x, cell.y, { pushSolid }).changed)
|
|
2603
|
+
project();
|
|
2604
|
+
}
|
|
2605
|
+
};
|
|
2698
2606
|
/** What the pointer means for the dragged tile: the resolve over the live tree, with the beside the hand holds. */
|
|
2699
2607
|
const resolveTileZone = (g, ev) => {
|
|
2608
|
+
var _a, _b;
|
|
2700
2609
|
let strip = null;
|
|
2701
|
-
if (options.tabDrop && !isStatic) {
|
|
2610
|
+
if (options.tabDrop && !isStatic && g.kind !== 'palette') {
|
|
2702
2611
|
const crect = api.container.getBoundingClientRect();
|
|
2703
2612
|
strip = options.tabDrop.stripAt(crect.left + ev.screen.x, crect.top + ev.screen.y);
|
|
2704
2613
|
}
|
|
@@ -2707,13 +2616,26 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2707
2616
|
y: ev.world.y,
|
|
2708
2617
|
roots: zoneRoots(),
|
|
2709
2618
|
strip,
|
|
2710
|
-
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
|
|
2711
2622
|
maxDepth: nesting,
|
|
2712
2623
|
ghostDepth: 0,
|
|
2713
2624
|
ghostSubtree: EMPTY_SUBTREE,
|
|
2714
2625
|
gap,
|
|
2626
|
+
homeChain: homeChain(),
|
|
2715
2627
|
});
|
|
2716
2628
|
};
|
|
2629
|
+
/** The groups this board sits in, all the way up: their bands never apply to a tile of this board. */
|
|
2630
|
+
const homeChain = () => {
|
|
2631
|
+
const out = new Set();
|
|
2632
|
+
let cur = group;
|
|
2633
|
+
for (let i = 0; cur && i < 32; i++) {
|
|
2634
|
+
out.add(cur.id);
|
|
2635
|
+
cur = cur.parentGroupId ? diagram.getGroup(cur.parentGroupId) : undefined;
|
|
2636
|
+
}
|
|
2637
|
+
return out;
|
|
2638
|
+
};
|
|
2717
2639
|
/** The board whose engine holds OUR group as an item (nesting parent). */
|
|
2718
2640
|
const parentPeer = () => {
|
|
2719
2641
|
for (const p of peersOnCanvas()) {
|
|
@@ -2756,7 +2678,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2756
2678
|
* wherever the last swing left it. Ask where the tile IS, not whether the
|
|
2757
2679
|
* call moved it.
|
|
2758
2680
|
*/
|
|
2759
|
-
const placeOnRow = (id, x, y, w) => {
|
|
2681
|
+
const placeOnRow = (id, x, y, w, pushSolid = false) => {
|
|
2760
2682
|
const at = (cx) => {
|
|
2761
2683
|
const i = engine.getItem(id);
|
|
2762
2684
|
return !!i && i.x === cx && i.y === y;
|
|
@@ -2764,7 +2686,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2764
2686
|
const maxX = Math.max(0, columns - w);
|
|
2765
2687
|
if (at(x))
|
|
2766
2688
|
return true;
|
|
2767
|
-
if (engine.moveCheck(id, x, y, { gate: false }).changed)
|
|
2689
|
+
if (engine.moveCheck(id, x, y, { gate: false, pushSolid }).changed)
|
|
2768
2690
|
return true;
|
|
2769
2691
|
for (let d = 1; d <= columns; d++) {
|
|
2770
2692
|
for (const cx of [x - d, x + d]) {
|
|
@@ -2772,14 +2694,14 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2772
2694
|
continue;
|
|
2773
2695
|
if (at(cx))
|
|
2774
2696
|
return true;
|
|
2775
|
-
if (engine.moveCheck(id, cx, y, { gate: false }).changed)
|
|
2697
|
+
if (engine.moveCheck(id, cx, y, { gate: false, pushSolid }).changed)
|
|
2776
2698
|
return true;
|
|
2777
2699
|
}
|
|
2778
2700
|
}
|
|
2779
2701
|
return false;
|
|
2780
2702
|
};
|
|
2781
|
-
const placeNear = (id, x, y, w) => {
|
|
2782
|
-
if (placeOnRow(id, x, y, w))
|
|
2703
|
+
const placeNear = (id, x, y, w, pushSolid = false) => {
|
|
2704
|
+
if (placeOnRow(id, x, y, w, pushSolid))
|
|
2783
2705
|
return true;
|
|
2784
2706
|
// EVERY column on that row refused — which is what a locked section
|
|
2785
2707
|
// spanning the full width does, and there are plenty of those. Sliding
|
|
@@ -2791,7 +2713,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2791
2713
|
for (const cy of [y - d, y + d]) {
|
|
2792
2714
|
if (cy < 0)
|
|
2793
2715
|
continue;
|
|
2794
|
-
if (placeOnRow(id, x, cy, w))
|
|
2716
|
+
if (placeOnRow(id, x, cy, w, pushSolid))
|
|
2795
2717
|
return true;
|
|
2796
2718
|
}
|
|
2797
2719
|
}
|
|
@@ -2806,7 +2728,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2806
2728
|
const fitHeightAt = (id, x, y, w, hNatural) => {
|
|
2807
2729
|
let limit = hNatural;
|
|
2808
2730
|
for (const it of engine.getItems()) {
|
|
2809
|
-
if (it.id === id || !it.locked)
|
|
2731
|
+
if (it.id === id || !(it.locked || it.solid))
|
|
2810
2732
|
continue;
|
|
2811
2733
|
if (!(it.x < x + w && x < it.x + it.w))
|
|
2812
2734
|
continue;
|
|
@@ -2916,11 +2838,16 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2916
2838
|
const tl = centredTopLeft(wx, wy, { w: itemW, h: opts.anchor === 'top' ? 0 : itemH });
|
|
2917
2839
|
return pointToCell(tl.x, tl.y, frame(), geom(), rows(), itemW);
|
|
2918
2840
|
};
|
|
2919
|
-
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);
|
|
2920
2842
|
let lastWant = null;
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
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
|
+
}
|
|
2924
2851
|
armGlide();
|
|
2925
2852
|
project();
|
|
2926
2853
|
syncPlaceholder();
|
|
@@ -2935,7 +2862,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2935
2862
|
const it = engine.getItem(node.id);
|
|
2936
2863
|
return it ? cellToRect(it, frame(), geom(), rows()) : null;
|
|
2937
2864
|
},
|
|
2938
|
-
place: (cell) => {
|
|
2865
|
+
place: (cell, pushSolid = false) => {
|
|
2939
2866
|
if (!engine.getItem(node.id) && !engine.add({ id: node.id, x: 0, y: engine.rows(), w: cell.w, h: cell.h }))
|
|
2940
2867
|
return false;
|
|
2941
2868
|
const it = engine.getItem(node.id);
|
|
@@ -2952,32 +2879,46 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2952
2879
|
engine.resizeCheck(node.id, w0, h0);
|
|
2953
2880
|
const moved = engine.getItem(node.id);
|
|
2954
2881
|
if (moved && (moved.x !== cell.x || moved.y !== cell.y))
|
|
2955
|
-
engine.moveCheck(node.id, cell.x, cell.y, { gate: false });
|
|
2882
|
+
engine.moveCheck(node.id, cell.x, cell.y, { gate: false, pushSolid });
|
|
2956
2883
|
const now = engine.getItem(node.id);
|
|
2957
2884
|
if (now && (now.w !== cell.w || now.h !== cell.h))
|
|
2958
|
-
engine.resizeCheck(node.id, cell.w, cell.h);
|
|
2885
|
+
engine.resizeCheck(node.id, cell.w, cell.h, { pushSolid });
|
|
2959
2886
|
lastWant = null;
|
|
2960
2887
|
project();
|
|
2961
2888
|
syncPlaceholder();
|
|
2962
2889
|
const at = engine.getItem(node.id);
|
|
2963
2890
|
return !!at && at.x === cell.x && at.y === cell.y && at.w === cell.w && at.h === cell.h;
|
|
2964
2891
|
},
|
|
2965
|
-
move: (w) => {
|
|
2892
|
+
move: (w, o) => {
|
|
2966
2893
|
const item = engine.getItem(node.id);
|
|
2967
2894
|
if (!item)
|
|
2968
2895
|
return;
|
|
2896
|
+
if (beside)
|
|
2897
|
+
endBeside(true); // the hand left the band: the container comes back before the tile takes a plain cell
|
|
2969
2898
|
const cell = wantedCell(w.x, w.y, item.w, opts.fit === 'shrink' ? hNatural : item.h);
|
|
2970
2899
|
// The search is worth running once per wanted cell, not per pixel.
|
|
2971
2900
|
if (lastWant && lastWant.x === cell.x && lastWant.y === cell.y)
|
|
2972
2901
|
return;
|
|
2973
2902
|
lastWant = cell;
|
|
2974
|
-
if (place(cell, item.w))
|
|
2903
|
+
if (place(cell, item.w, !!(o === null || o === void 0 ? void 0 : o.push)))
|
|
2975
2904
|
project();
|
|
2976
2905
|
syncPlaceholder();
|
|
2977
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,
|
|
2978
2918
|
leave: () => {
|
|
2979
2919
|
if (!engine.getItem(node.id))
|
|
2980
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
|
|
2981
2922
|
engine.remove(node.id); // displaced tiles come home (gesture memory)
|
|
2982
2923
|
lastWant = null;
|
|
2983
2924
|
project();
|
|
@@ -2996,6 +2937,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2996
2937
|
syncPlaceholder();
|
|
2997
2938
|
},
|
|
2998
2939
|
abort: () => {
|
|
2940
|
+
beside = null; // the snapshot restores the container with everything else
|
|
2999
2941
|
if (engine.getItem(node.id))
|
|
3000
2942
|
engine.remove(node.id);
|
|
3001
2943
|
engine.cancelGesture(); // pre-entry layout, memory cleared
|
|
@@ -3006,6 +2948,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
3006
2948
|
syncPlaceholder();
|
|
3007
2949
|
},
|
|
3008
2950
|
finalize: () => {
|
|
2951
|
+
endBeside(false); // the shift stays: the deltas below carry it
|
|
3009
2952
|
const item = engine.getItem(node.id);
|
|
3010
2953
|
if (!item) {
|
|
3011
2954
|
engine.endGesture();
|
|
@@ -3015,26 +2958,11 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
3015
2958
|
}
|
|
3016
2959
|
const cell = { x: item.x, y: item.y, w: item.w, h: item.h };
|
|
3017
2960
|
const rect = cellToRect(item, frame(), geom(), rows());
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
if (!isGroupMember(id))
|
|
3022
|
-
continue;
|
|
3023
|
-
const it = engine.getItem(id);
|
|
3024
|
-
const g0 = startGeom.get(id);
|
|
3025
|
-
if (!it || !g0)
|
|
3026
|
-
continue;
|
|
3027
|
-
if (it.x === before.x && it.y === before.y && it.w === before.w && it.h === before.h)
|
|
3028
|
-
continue;
|
|
3029
|
-
groups.push({
|
|
3030
|
-
id,
|
|
3031
|
-
cellBefore: before,
|
|
3032
|
-
cellAfter: { x: it.x, y: it.y, w: it.w, h: it.h },
|
|
3033
|
-
frameBefore: { x: g0.pos.x, y: g0.pos.y, width: g0.size.width, height: g0.size.height },
|
|
3034
|
-
frameAfter: cellToRect(it, frame(), geom(), rows()),
|
|
3035
|
-
});
|
|
3036
|
-
}
|
|
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));
|
|
3037
2964
|
engine.endGesture();
|
|
2965
|
+
pendingDrop = node.id; // the tile stays for the member the commit (or the host's drop-in) adds
|
|
3038
2966
|
if (squeezeRoom !== undefined) {
|
|
3039
2967
|
// What the board now HOLDS is its bound, not the squeeze's room.
|
|
3040
2968
|
squeezeRoom = undefined;
|
|
@@ -3043,7 +2971,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
3043
2971
|
adoptedGhostId = null;
|
|
3044
2972
|
disarmGlideSoon();
|
|
3045
2973
|
syncPlaceholder();
|
|
3046
|
-
return { commands, cell, rect
|
|
2974
|
+
return { commands, cell, rect };
|
|
3047
2975
|
},
|
|
3048
2976
|
};
|
|
3049
2977
|
};
|
|
@@ -4005,32 +3933,6 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
4005
3933
|
}
|
|
4006
3934
|
};
|
|
4007
3935
|
const zoneKey = (z) => JSON.stringify(z, (k, v) => (k === 'target' ? v.id : k === 'peer' ? v.group.id : v));
|
|
4008
|
-
// DOCKING PUSHES SECTIONS. A section is a locked tile so that a widget
|
|
4009
|
-
// never pushes it; a group docked against the board's edge is the one
|
|
4010
|
-
// gesture that must — everything below the top band moves down. For the
|
|
4011
|
-
// preview every member group is unlocked; they are relocked when the
|
|
4012
|
-
// pointer leaves the band, and their moved cells are committed with the
|
|
4013
|
-
// dock.
|
|
4014
|
-
let unlockedGroups = [];
|
|
4015
|
-
const unlockGroups = () => {
|
|
4016
|
-
if (unlockedGroups.length > 0)
|
|
4017
|
-
return;
|
|
4018
|
-
for (const it of engine.getItems()) {
|
|
4019
|
-
if (it.id !== plan.arrivingId && it.locked && isGroupMember(it.id)) {
|
|
4020
|
-
it.locked = false;
|
|
4021
|
-
unlockedGroups.push(it.id);
|
|
4022
|
-
}
|
|
4023
|
-
}
|
|
4024
|
-
};
|
|
4025
|
-
const relockGroups = () => {
|
|
4026
|
-
for (const id of unlockedGroups) {
|
|
4027
|
-
const it = engine.getItem(id);
|
|
4028
|
-
if (it)
|
|
4029
|
-
it.locked = true;
|
|
4030
|
-
}
|
|
4031
|
-
unlockedGroups = [];
|
|
4032
|
-
};
|
|
4033
|
-
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));
|
|
4034
3936
|
// A TOP DOCK INSERTS ROWS. The engine's push cascade resolves the band's
|
|
4035
3937
|
// collisions one tile at a time and scrambles what stood beneath it (the
|
|
4036
3938
|
// demo's KPI row came apart, two of its tiles under the chart). VS Code
|
|
@@ -4097,18 +3999,24 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
4097
3999
|
engine.moveCheck(split.id, split.before.x, split.before.y, { gate: false });
|
|
4098
4000
|
if (it.w !== split.before.w || it.h !== split.before.h)
|
|
4099
4001
|
engine.resizeCheck(split.id, split.before.w, split.before.h);
|
|
4100
|
-
it.locked = true;
|
|
4101
4002
|
}
|
|
4102
4003
|
split = null;
|
|
4103
4004
|
project();
|
|
4104
4005
|
};
|
|
4105
4006
|
const previewSplit = (z, world) => {
|
|
4106
4007
|
const it = engine.getItem(z.target.id);
|
|
4008
|
+
if (!it)
|
|
4009
|
+
return false;
|
|
4010
|
+
// The target's cell AT REST is the one the commit restores — read it
|
|
4011
|
+
// BEFORE the leg enters: the arriving ghost is placed under the pointer
|
|
4012
|
+
// first, and a target that is a tile like any other is pushed by it
|
|
4013
|
+
// for a moment (tile first, step 3) until it takes its half below.
|
|
4014
|
+
const before = { x: it.x, y: it.y, w: it.w, h: it.h };
|
|
4015
|
+
const frameBefore = frameOfGroup(z.target);
|
|
4107
4016
|
const l = ensureLeg(world, null);
|
|
4108
|
-
if (!
|
|
4017
|
+
if (!l)
|
|
4109
4018
|
return false;
|
|
4110
|
-
split = { id: z.target.id, before
|
|
4111
|
-
it.locked = false; // its own gesture for the moment: it may shrink and shift
|
|
4019
|
+
split = { id: z.target.id, before, frameBefore, keep: z.keep };
|
|
4112
4020
|
if (it.w !== z.keep.w || it.h !== z.keep.h)
|
|
4113
4021
|
engine.resizeCheck(z.target.id, z.keep.w, z.keep.h);
|
|
4114
4022
|
const now = engine.getItem(z.target.id);
|
|
@@ -4130,8 +4038,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
4130
4038
|
const l = ensureLeg(world, null);
|
|
4131
4039
|
if (!l)
|
|
4132
4040
|
return false;
|
|
4133
|
-
|
|
4134
|
-
l.place(z.cell);
|
|
4041
|
+
l.place(z.cell, true); // DOCKING PUSHES SECTIONS: everything below the band moves down, solid or not
|
|
4135
4042
|
insertRows(z.cell, l);
|
|
4136
4043
|
project();
|
|
4137
4044
|
placeholder === null || placeholder === void 0 ? void 0 : placeholder.remove();
|
|
@@ -4153,7 +4060,6 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
4153
4060
|
}
|
|
4154
4061
|
undoSplitPreview();
|
|
4155
4062
|
undoInsertRows();
|
|
4156
|
-
relockGroups();
|
|
4157
4063
|
plan.markDrop(null, null);
|
|
4158
4064
|
hideOverlay();
|
|
4159
4065
|
// A PREVIEW IS AN OVERLAY. A split and a dock used to be applied LIVE
|
|
@@ -4241,7 +4147,6 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
4241
4147
|
if (!commit || z.kind === 'home' || z.kind === 'off' || (z.kind === 'root' && !ensureLeg(world, null)) || (z.kind === 'board' && (!ensureLeg(world, (_a = z.peer) !== null && _a !== void 0 ? _a : null) || landingHidden()))) {
|
|
4242
4148
|
undoSplitPreview();
|
|
4243
4149
|
leg === null || leg === void 0 ? void 0 : leg.abort();
|
|
4244
|
-
relockGroups();
|
|
4245
4150
|
done(false, 'cancel');
|
|
4246
4151
|
return;
|
|
4247
4152
|
}
|
|
@@ -4278,25 +4183,17 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
4278
4183
|
return;
|
|
4279
4184
|
}
|
|
4280
4185
|
const fin = (_b = leg === null || leg === void 0 ? void 0 : leg.finalize()) !== null && _b !== void 0 ? _b : null;
|
|
4281
|
-
const
|
|
4282
|
-
const before = split;
|
|
4283
|
-
if (it)
|
|
4284
|
-
it.locked = true;
|
|
4186
|
+
const halved = !!split && !!engine.getItem(z.target.id);
|
|
4285
4187
|
split = null;
|
|
4286
|
-
if (!fin || !
|
|
4188
|
+
if (!fin || !halved) {
|
|
4287
4189
|
leg === null || leg === void 0 ? void 0 : leg.abort();
|
|
4288
4190
|
done(false, 'cancel');
|
|
4289
4191
|
return;
|
|
4290
4192
|
}
|
|
4291
|
-
const keep = { x: it.x, y: it.y, w: it.w, h: it.h };
|
|
4292
4193
|
const planned = plan.commands(fin.cell, fin.rect, group.id);
|
|
4293
|
-
|
|
4294
|
-
|
|
4295
|
-
|
|
4296
|
-
new SetGroupCellCommand(z.target.id, before.before, keep, before.frameBefore, cellToRect(keep, frame(), geom(), rows())),
|
|
4297
|
-
...planned.move,
|
|
4298
|
-
...planned.collapse,
|
|
4299
|
-
]);
|
|
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]);
|
|
4300
4197
|
done(changed, 'commit');
|
|
4301
4198
|
return;
|
|
4302
4199
|
}
|
|
@@ -4309,13 +4206,12 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
4309
4206
|
hideOverlay(); // a re-applied zone repaints its preview; the release is not a preview
|
|
4310
4207
|
plan.markDrop(null, null);
|
|
4311
4208
|
const fin = (_c = leg === null || leg === void 0 ? void 0 : leg.finalize()) !== null && _c !== void 0 ? _c : null;
|
|
4312
|
-
relockGroups();
|
|
4313
4209
|
if (!fin) {
|
|
4314
4210
|
done(false, 'cancel');
|
|
4315
4211
|
return;
|
|
4316
4212
|
}
|
|
4317
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);
|
|
4318
|
-
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');
|
|
4319
4215
|
};
|
|
4320
4216
|
const onUp = () => finish(true);
|
|
4321
4217
|
const onCancel = () => finish(false);
|
|
@@ -4387,43 +4283,39 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
4387
4283
|
return;
|
|
4388
4284
|
}
|
|
4389
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 };
|
|
4390
4290
|
}
|
|
4391
4291
|
if (chip) {
|
|
4392
4292
|
chip.style.left = `${e.clientX + 6}px`;
|
|
4393
4293
|
chip.style.top = `${e.clientY + 6}px`;
|
|
4394
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();
|
|
4395
4300
|
const world = toWorld(e.clientX, e.clientY);
|
|
4396
|
-
|
|
4397
|
-
if (inside) {
|
|
4398
|
-
const tl = centredTopLeft(world.x, world.y, g.spans);
|
|
4399
|
-
const cell = pointToCell(tl.x, tl.y, frame(), geom(), rows(), g.spans.w);
|
|
4400
|
-
if (g.removedFromBoard) {
|
|
4401
|
-
g.removedFromBoard = false;
|
|
4402
|
-
// Enter at the bottom edge (collision-free), then take the cursor
|
|
4403
|
-
// cell GATELESSLY — gridstack's drag-in skips the gate on entry.
|
|
4404
|
-
// A bounded board with no room refuses the entry: the chip dims to
|
|
4405
|
-
// say so, and the release will snap it home.
|
|
4406
|
-
const entered = engine.add({ id: g.id, x: 0, y: engine.rows(), w: g.spans.w, h: g.spans.h });
|
|
4407
|
-
chip === null || chip === void 0 ? void 0 : chip.classList.toggle('axdb-out', !entered);
|
|
4408
|
-
if (entered)
|
|
4409
|
-
engine.moveCheck(g.id, cell.x, cell.y, { gate: false });
|
|
4410
|
-
project();
|
|
4411
|
-
}
|
|
4412
|
-
else if (engine.moveCheck(g.id, cell.x, cell.y).changed) {
|
|
4413
|
-
project();
|
|
4414
|
-
}
|
|
4415
|
-
}
|
|
4416
|
-
else if (!g.removedFromBoard) {
|
|
4417
|
-
g.removedFromBoard = true;
|
|
4418
|
-
chip === null || chip === void 0 ? void 0 : chip.classList.remove('axdb-out');
|
|
4419
|
-
engine.remove(g.id); // displaced tiles come home (gesture memory)
|
|
4420
|
-
project();
|
|
4421
|
-
}
|
|
4422
|
-
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 } });
|
|
4423
4302
|
api.render();
|
|
4424
4303
|
};
|
|
4425
|
-
|
|
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) => {
|
|
4426
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) => {
|
|
4427
4319
|
detach();
|
|
4428
4320
|
if (gesture !== g)
|
|
4429
4321
|
return;
|
|
@@ -4434,18 +4326,21 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
4434
4326
|
chip === null || chip === void 0 ? void 0 : chip.remove();
|
|
4435
4327
|
return;
|
|
4436
4328
|
}
|
|
4437
|
-
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)) {
|
|
4438
4341
|
const item = engine.getItem(g.id);
|
|
4439
|
-
|
|
4440
|
-
|
|
4441
|
-
const displaced = buildCommitCommands(deltasSince(g.startCells, g.startGeom, g.id));
|
|
4442
|
-
engine.endGesture();
|
|
4443
|
-
cleanupGestureVisuals(g);
|
|
4444
|
-
gesture = null;
|
|
4445
|
-
void ((_a = options.onDropIn) === null || _a === void 0 ? void 0 : _a.call(options, node, cell, displaced));
|
|
4446
|
-
persistLayouts();
|
|
4447
|
-
(_b = options.onGesture) === null || _b === void 0 ? void 0 : _b.call(options, { type: 'drop-in', kind: 'palette', nodeId: g.id, changed: true });
|
|
4448
|
-
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)));
|
|
4449
4344
|
return;
|
|
4450
4345
|
}
|
|
4451
4346
|
// Abort (released outside, or Escape): restore the board.
|
|
@@ -4472,14 +4367,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
4472
4367
|
return false;
|
|
4473
4368
|
engine.beginGesture();
|
|
4474
4369
|
const snap = snapshotAll();
|
|
4475
|
-
// A section is a LOCKED tile; for ITS OWN move it is the one moving.
|
|
4476
|
-
const self = engine.getItem(id);
|
|
4477
|
-
const wasLocked = !!(self === null || self === void 0 ? void 0 : self.locked);
|
|
4478
|
-
if (self && isGroupMember(id))
|
|
4479
|
-
self.locked = false;
|
|
4480
4370
|
const ok = op();
|
|
4481
|
-
if (self && isGroupMember(id))
|
|
4482
|
-
self.locked = wasLocked;
|
|
4483
4371
|
if (!ok) {
|
|
4484
4372
|
engine.endGesture();
|
|
4485
4373
|
return false;
|
|
@@ -4495,18 +4383,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
4495
4383
|
finally {
|
|
4496
4384
|
writing = false;
|
|
4497
4385
|
}
|
|
4498
|
-
const commands =
|
|
4499
|
-
if (isGroupMember(id)) {
|
|
4500
|
-
// A SECTION: its cell lives in group metadata and its frame is written
|
|
4501
|
-
// by project(); the node commands above skip groups (D5 of the review).
|
|
4502
|
-
const it = engine.getItem(id);
|
|
4503
|
-
const grp = diagram.getGroup(id);
|
|
4504
|
-
const cb = snap.cells.get(id);
|
|
4505
|
-
const gb = snap.geoms.get(id);
|
|
4506
|
-
if (it && grp && cb && gb && (cb.x !== it.x || cb.y !== it.y || cb.w !== it.w || cb.h !== it.h)) {
|
|
4507
|
-
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)));
|
|
4508
|
-
}
|
|
4509
|
-
}
|
|
4386
|
+
const commands = tileCommands(deltasSince(snap.cells, snap.geoms)); // a section's cell-and-frame command rides with the nodes'
|
|
4510
4387
|
engine.endGesture();
|
|
4511
4388
|
disarmGlideSoon();
|
|
4512
4389
|
enforceBoardHeight();
|
|
@@ -4547,6 +4424,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
4547
4424
|
return;
|
|
4548
4425
|
}
|
|
4549
4426
|
applyFluidFrame();
|
|
4427
|
+
pendingDrop = null; // rebuilt from the members: a phantom is gone with the engine
|
|
4550
4428
|
const items = [];
|
|
4551
4429
|
for (const id of (_a = group.members) !== null && _a !== void 0 ? _a : []) {
|
|
4552
4430
|
if (!memberEntity(id))
|
|
@@ -4763,10 +4641,10 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
4763
4641
|
sizeAfter: { width: target.width, height: target.height },
|
|
4764
4642
|
});
|
|
4765
4643
|
}
|
|
4766
|
-
return
|
|
4644
|
+
return tileCommands(deltas);
|
|
4767
4645
|
},
|
|
4768
4646
|
moveTo(id, x, y) {
|
|
4769
|
-
return programmatic('Move widget', id, () => engine.moveCheck(id, x, y).changed);
|
|
4647
|
+
return programmatic('Move widget', id, () => engine.moveCheck(id, x, y, { pushSolid: isGroupMember(id) }).changed);
|
|
4770
4648
|
},
|
|
4771
4649
|
resizeTo(id, w, h) {
|
|
4772
4650
|
const hh = isGroupMember(id) ? Math.max(h, innerRowsOf(id)) : h; // a section: never below its children
|