@grafloria/element 0.4.45 → 0.4.47

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@grafloria/element",
3
- "version": "0.4.45",
3
+ "version": "0.4.47",
4
4
  "type": "module",
5
5
  "main": "./src/index.js",
6
6
  "types": "./src/index.d.ts",
@@ -1264,105 +1264,158 @@ export function bindDashboardGrid(api, group, options = {}) {
1264
1264
  const TAB_FRAME_GRIP = 3;
1265
1265
  const edgeGripFor = (grp) => (isTabsGroup(grp) ? TAB_FRAME_GRIP : EDGE_GRIP);
1266
1266
  const BESIDE_BAND = 0.2;
1267
+ /** How much wider the band is for a pointer ALREADY in it — a hand resting on the boundary must not flicker between beside and into. */
1268
+ const BESIDE_STAY = 0.05;
1269
+ /** The outer band of a container FRAME the point is in — null in the strip, in the middle, or outside. */
1270
+ const bandOf = (f, wx, wy, band = BESIDE_BAND) => {
1271
+ if (wx < f.x || wx > f.x + f.width || wy < f.y || wy > f.y + f.height)
1272
+ return null;
1273
+ const bodyY = f.y + TAB_STRIP_HEIGHT;
1274
+ const bodyH = Math.max(1, f.height - TAB_STRIP_HEIGHT);
1275
+ const rx = (wx - f.x) / Math.max(1, f.width);
1276
+ const ry = (wy - bodyY) / bodyH;
1277
+ if (ry < 0)
1278
+ return null; // the strip: a new tab, the strip's own business
1279
+ if (rx < band)
1280
+ return 'left'; // the sides first: they take the corners
1281
+ if (rx > 1 - band)
1282
+ return 'right';
1283
+ if (ry < band)
1284
+ return 'top';
1285
+ if (ry > 1 - band)
1286
+ return 'bottom';
1287
+ return null; // the middle: into the page
1288
+ };
1267
1289
  const besideZoneAt = (wx, wy) => {
1268
1290
  var _a;
1269
1291
  for (const id of (_a = group.members) !== null && _a !== void 0 ? _a : []) {
1270
1292
  const grp = diagram.getGroup(id);
1271
1293
  if (!grp || diagram.getNode(id) || !isTabsGroup(grp))
1272
1294
  continue;
1273
- const p = grp.position;
1274
- const sz = sizeOf(grp);
1275
- if (wx < p.x || wx > p.x + sz.width || wy < p.y || wy > p.y + sz.height)
1276
- continue;
1277
- const bodyY = p.y + TAB_STRIP_HEIGHT;
1278
- const bodyH = Math.max(1, sz.height - TAB_STRIP_HEIGHT);
1279
- const rx = (wx - p.x) / Math.max(1, sz.width);
1280
- const ry = (wy - bodyY) / bodyH;
1281
- if (ry < 0)
1282
- return null; // the strip: a new tab, the strip's own business
1283
- if (rx >= BESIDE_BAND && rx <= 1 - BESIDE_BAND && ry >= BESIDE_BAND && ry <= 1 - BESIDE_BAND)
1284
- return null; // the middle: into the page
1285
- const d = [['left', rx], ['right', 1 - rx], ['top', ry], ['bottom', 1 - ry]];
1286
- d.sort((a, b) => a[1] - b[1]);
1287
- return { id, side: d[0][0] };
1295
+ const side = bandOf(frameOfGroup(grp), wx, wy);
1296
+ if (side)
1297
+ return { id, side };
1288
1298
  }
1289
1299
  return null;
1290
1300
  };
1291
- /** The container a BESIDE drop unlocked (and maybe shifted): relocked, and put back if asked, when the zone or the gesture ends. */
1301
+ /** The BESIDE promise the overlay shows: the cell the widget takes on release, and where the container shifts to make it (null: it is pushed, or has room). */
1292
1302
  let beside = null;
1293
- const endBeside = (restore) => {
1303
+ let besideEl = null;
1304
+ const showBesideOverlay = (r) => {
1305
+ const layer = htmlLayer();
1306
+ if (!layer)
1307
+ return;
1308
+ if (!besideEl || besideEl.parentElement !== layer) {
1309
+ besideEl === null || besideEl === void 0 ? void 0 : besideEl.remove();
1310
+ besideEl = document.createElement('div');
1311
+ besideEl.className = 'axdb-join';
1312
+ layer.prepend(besideEl);
1313
+ }
1314
+ besideEl.style.left = `${r.x}px`;
1315
+ besideEl.style.top = `${r.y}px`;
1316
+ besideEl.style.width = `${r.width}px`;
1317
+ besideEl.style.height = `${r.height}px`;
1318
+ };
1319
+ const hideBesideOverlay = () => {
1320
+ besideEl === null || besideEl === void 0 ? void 0 : besideEl.remove();
1321
+ besideEl = null;
1322
+ };
1323
+ /** The promise withdrawn (the pointer left, the gesture ended) or kept (realized and committed): the overlay goes, the container and the others relock. */
1324
+ const endBeside = () => {
1294
1325
  if (!beside)
1295
1326
  return;
1296
1327
  const it = engine.getItem(beside.id);
1297
- if (it && restore && (it.x !== beside.from.x || it.y !== beside.from.y))
1298
- engine.moveCheck(beside.id, beside.from.x, beside.from.y, { gate: false });
1299
1328
  if (it)
1300
1329
  it.locked = true;
1330
+ relockOthersForSlab();
1331
+ hideBesideOverlay();
1301
1332
  beside = null;
1302
1333
  };
1303
- const applyBeside = (g, z) => {
1304
- var _a;
1305
- const tc = engine.getItem(z.id);
1306
- if (!tc)
1307
- return;
1308
- if (g.leg) {
1309
- g.leg.adopted.abort();
1310
- g.leg = null;
1311
- }
1312
- const w = g.spans.w;
1313
- const h = g.spans.h;
1314
- if (g.removedFromBoard) {
1315
- g.removedFromBoard = false;
1316
- (_a = hostOf(g.id)) === null || _a === void 0 ? void 0 : _a.classList.remove('axdb-out');
1317
- engine.add({ id: g.id, x: 0, y: engine.rows(), w, h });
1318
- }
1319
- if (beside && beside.id !== z.id)
1320
- endBeside(true);
1321
- const from = beside ? beside.from : { x: tc.x, y: tc.y };
1334
+ /** Where a widget of this span lands beside the container, and where the container goes to make room — from the layout AT REST, since nothing moves while held. */
1335
+ const besidePlan = (tc, side, w) => {
1322
1336
  let cell;
1323
1337
  let shiftTo = null;
1324
- switch (z.side) {
1338
+ switch (side) {
1325
1339
  case 'right':
1326
- cell = { x: from.x + tc.w, y: from.y };
1340
+ cell = { x: tc.x + tc.w, y: tc.y };
1327
1341
  if (cell.x + w > columns) {
1328
- shiftTo = { x: columns - w - tc.w, y: from.y };
1329
- cell = { x: columns - w, y: from.y };
1342
+ shiftTo = { x: columns - w - tc.w, y: tc.y };
1343
+ cell = { x: columns - w, y: tc.y };
1330
1344
  }
1331
1345
  break;
1332
1346
  case 'left':
1333
- cell = { x: from.x - w, y: from.y };
1347
+ cell = { x: tc.x - w, y: tc.y };
1334
1348
  if (cell.x < 0) {
1335
- shiftTo = { x: w, y: from.y };
1336
- cell = { x: 0, y: from.y };
1349
+ shiftTo = { x: w, y: tc.y };
1350
+ cell = { x: 0, y: tc.y };
1337
1351
  }
1338
1352
  break;
1339
1353
  case 'top':
1340
- 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
1354
+ cell = { x: Math.max(0, Math.min(tc.x, columns - w)), y: tc.y }; // the widget takes the container's rows; the container is pushed down under it
1341
1355
  break;
1342
1356
  default:
1343
- cell = { x: Math.max(0, Math.min(from.x, columns - w)), y: from.y + tc.h };
1357
+ cell = { x: Math.max(0, Math.min(tc.x, columns - w)), y: tc.y + tc.h };
1344
1358
  }
1345
1359
  if (shiftTo && shiftTo.x < 0)
1346
1360
  shiftTo = null; // no room even shifted: the widget goes where it can
1361
+ return { cell, shiftTo };
1362
+ };
1363
+ const applyBeside = (g, z) => {
1364
+ var _a;
1365
+ const tc = engine.getItem(z.id);
1366
+ if (!tc)
1367
+ return;
1368
+ if (g.leg) {
1369
+ g.leg.adopted.abort();
1370
+ g.leg = null;
1371
+ }
1372
+ // The ghost leaves the board, as it does over a strip: the survivors
1373
+ // settle home and nothing under the hand moves until the release.
1374
+ if (!g.removedFromBoard) {
1375
+ g.removedFromBoard = true;
1376
+ engine.remove(g.id);
1377
+ project();
1378
+ }
1379
+ (_a = hostOf(g.id)) === null || _a === void 0 ? void 0 : _a.classList.remove('axdb-out');
1380
+ const plan = besidePlan(tc, z.side, g.spans.w);
1381
+ if (!beside || beside.id !== z.id || beside.side !== z.side || beside.cell.x !== plan.cell.x || beside.cell.y !== plan.cell.y) {
1382
+ beside = { id: z.id, side: z.side, cell: plan.cell, shiftTo: plan.shiftTo };
1383
+ showBesideOverlay(cellToRect({ x: plan.cell.x, y: plan.cell.y, w: g.spans.w, h: g.spans.h }, frame(), geom(), rows()));
1384
+ }
1385
+ };
1386
+ /**
1387
+ * The release: the container shifts (or is pushed) and the ghost takes the
1388
+ * promised cell — for real now, so the commit's deltas carry both. The
1389
+ * ghost steps back on at the bottom edge first: the engine will not push
1390
+ * the dragged tile, so a chart as wide as the panel had its own cell as the
1391
+ * panel's target and the shift was refused (lab L94).
1392
+ */
1393
+ const realizeBeside = (g) => {
1394
+ var _a;
1347
1395
  if (!beside)
1348
- beside = { id: z.id, from, vacated: cell };
1349
- tc.locked = false; // for the gesture: it shifts, or the ghost pushes it
1350
- // The ghost steps off the board while the container shifts: on a bounded
1351
- // board a chart as wide as the panel had its own cell as the panel's
1352
- // target, could not be pushed down, and the shift was refused (lab L94).
1353
- // Then it takes the cell the shift vacated.
1354
- if (shiftTo && (tc.x !== shiftTo.x || tc.y !== shiftTo.y)) {
1355
- if (engine.getItem(g.id))
1356
- engine.remove(g.id);
1357
- engine.moveCheck(z.id, shiftTo.x, shiftTo.y, { gate: false });
1396
+ return;
1397
+ const tc = engine.getItem(beside.id);
1398
+ if (!tc)
1399
+ return;
1400
+ const w = g.spans.w;
1401
+ const h = g.spans.h;
1402
+ // The other sections give way to the shift the way they give way to a
1403
+ // moved group (0.4.44): on the demo the Operations section spans the
1404
+ // row under the panel and a locked tile refused the shift outright.
1405
+ unlockOthersForSlab(beside.id);
1406
+ tc.locked = false; // it shifts, or the ghost pushes it
1407
+ if (beside.shiftTo && (tc.x !== beside.shiftTo.x || tc.y !== beside.shiftTo.y))
1408
+ engine.moveCheck(beside.id, beside.shiftTo.x, beside.shiftTo.y, { gate: false });
1409
+ if (g.removedFromBoard) {
1410
+ g.removedFromBoard = false;
1411
+ (_a = hostOf(g.id)) === null || _a === void 0 ? void 0 : _a.classList.remove('axdb-out');
1358
1412
  }
1359
- beside.vacated = cell;
1360
1413
  if (!engine.getItem(g.id))
1361
1414
  engine.add({ id: g.id, x: 0, y: engine.rows(), w, h });
1362
- if (!engine.moveCheck(g.id, cell.x, cell.y, { gate: false }).changed) {
1415
+ if (!engine.moveCheck(g.id, beside.cell.x, beside.cell.y, { gate: false }).changed) {
1363
1416
  const at = engine.getItem(g.id);
1364
- if (!at || at.x !== cell.x || at.y !== cell.y)
1365
- placeNear(g.id, cell.x, cell.y, w);
1417
+ if (!at || at.x !== beside.cell.x || at.y !== beside.cell.y)
1418
+ placeNear(g.id, beside.cell.x, beside.cell.y, w);
1366
1419
  }
1367
1420
  project();
1368
1421
  };
@@ -1909,7 +1962,7 @@ export function bindDashboardGrid(api, group, options = {}) {
1909
1962
  const deltas = deltasSince(g.startCells, g.startGeom);
1910
1963
  const commands = buildCommitCommands(deltas);
1911
1964
  commands.push(...groupCellCommands(deltas)); // a container a BESIDE drop shifted (0.4.45)
1912
- endBeside(false);
1965
+ endBeside();
1913
1966
  if (g.esc && g.esc.rowsAdded !== 0) {
1914
1967
  commands.push(new SetGroupCellCommand(group.id, g.esc.cellBefore, g.esc.cellAfter, g.esc.frameBefore, g.esc.frameAfter));
1915
1968
  }
@@ -1948,7 +2001,7 @@ export function bindDashboardGrid(api, group, options = {}) {
1948
2001
  g.esc.peer.resizeMemberBy(group.id, -g.esc.rowsAdded); // slab back down
1949
2002
  g.esc = null;
1950
2003
  }
1951
- endBeside(true);
2004
+ endBeside();
1952
2005
  if (g.started) {
1953
2006
  if (g.removedFromBoard || g.kind === 'palette') {
1954
2007
  // The engine cannot resurrect a removed item — rebuild from the
@@ -2048,6 +2101,7 @@ export function bindDashboardGrid(api, group, options = {}) {
2048
2101
  g.leg.adopted.abort();
2049
2102
  g.leg = null;
2050
2103
  }
2104
+ endBeside(); // a band's promise gives way to the strip's
2051
2105
  if (!g.removedFromBoard) {
2052
2106
  g.removedFromBoard = true;
2053
2107
  engine.remove(g.id);
@@ -2069,6 +2123,7 @@ export function bindDashboardGrid(api, group, options = {}) {
2069
2123
  }
2070
2124
  // BESIDE a tab container (0.4.45): its outer band puts the widget next
2071
2125
  // to it — at the board's edge the container shifts over to make room.
2126
+ // Nothing moves while held (0.4.47): an overlay marks the cell.
2072
2127
  if (!isStatic) {
2073
2128
  const z = besideZoneAt(ev.world.x, ev.world.y);
2074
2129
  if (z) {
@@ -2077,19 +2132,16 @@ export function bindDashboardGrid(api, group, options = {}) {
2077
2132
  return;
2078
2133
  }
2079
2134
  if (beside) {
2080
- // Off the band: the container comes back unless the POINTER is
2081
- // still over the cell it vacated the widget's own landing, which
2082
- // is what "beside" leaves under the pointer once the container has
2083
- // shifted away (the ghost's wanted cell carries the grab offset and
2084
- // read as "elsewhere" a move later, and the shift undid itself).
2085
- const r = cellToRect({ x: beside.vacated.x, y: beside.vacated.y, w: g.spans.w, h: g.spans.h }, frame(), geom(), rows());
2086
- const over = ev.world.x >= r.x - gap && ev.world.x <= r.x + r.width + gap && ev.world.y >= r.y - gap && ev.world.y <= r.y + r.height + gap;
2087
- if (!over)
2088
- endBeside(true);
2089
- else {
2135
+ // Off the band — unless the hand is resting on its boundary: the
2136
+ // band is a little wider for a pointer already in it, so the
2137
+ // promise does not flicker against "into the page" at the line.
2138
+ const grp = diagram.getGroup(beside.id);
2139
+ const stay = !!grp && bandOf(frameOfGroup(grp), ev.world.x, ev.world.y, BESIDE_BAND + BESIDE_STAY) === beside.side;
2140
+ if (stay) {
2090
2141
  syncPlaceholder();
2091
2142
  return;
2092
2143
  }
2144
+ endBeside(); // the overlay goes; the handoff below puts the ghost back on a board
2093
2145
  }
2094
2146
  }
2095
2147
  // Deepest board under the pointer wins: the nested KPI strip beats the
@@ -2526,6 +2578,13 @@ export function bindDashboardGrid(api, group, options = {}) {
2526
2578
  (_b = options.onGesture) === null || _b === void 0 ? void 0 : _b.call(options, { type: 'commit', kind: g.kind, nodeId: g.id, changed: true });
2527
2579
  return;
2528
2580
  }
2581
+ if (beside) {
2582
+ // -- BESIDE A TAB CONTAINER (0.4.47): the shift or push the overlay
2583
+ // promised happens now, and the commit below records both.
2584
+ realizeBeside(g);
2585
+ commitGesture(g);
2586
+ return;
2587
+ }
2529
2588
  if (g.leg) {
2530
2589
  // -- CROSS-CONTAINER COMMIT: one batch across both boards -----------
2531
2590
  const fin = g.leg.adopted.finalize();