@grafloria/element 0.4.46 → 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.46",
3
+ "version": "0.4.47",
4
4
  "type": "module",
5
5
  "main": "./src/index.js",
6
6
  "types": "./src/index.d.ts",
@@ -1264,8 +1264,10 @@ 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;
1267
1269
  /** The outer band of a container FRAME the point is in — null in the strip, in the middle, or outside. */
1268
- const bandOf = (f, wx, wy) => {
1270
+ const bandOf = (f, wx, wy, band = BESIDE_BAND) => {
1269
1271
  if (wx < f.x || wx > f.x + f.width || wy < f.y || wy > f.y + f.height)
1270
1272
  return null;
1271
1273
  const bodyY = f.y + TAB_STRIP_HEIGHT;
@@ -1274,11 +1276,15 @@ export function bindDashboardGrid(api, group, options = {}) {
1274
1276
  const ry = (wy - bodyY) / bodyH;
1275
1277
  if (ry < 0)
1276
1278
  return null; // the strip: a new tab, the strip's own business
1277
- if (rx >= BESIDE_BAND && rx <= 1 - BESIDE_BAND && ry >= BESIDE_BAND && ry <= 1 - BESIDE_BAND)
1278
- return null; // the middle: into the page
1279
- const d = [['left', rx], ['right', 1 - rx], ['top', ry], ['bottom', 1 - ry]];
1280
- d.sort((a, b) => a[1] - b[1]);
1281
- return d[0][0];
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
1282
1288
  };
1283
1289
  const besideZoneAt = (wx, wy) => {
1284
1290
  var _a;
@@ -1292,101 +1298,124 @@ export function bindDashboardGrid(api, group, options = {}) {
1292
1298
  }
1293
1299
  return null;
1294
1300
  };
1295
- /** 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). */
1296
1302
  let beside = null;
1297
- 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 = () => {
1298
1325
  if (!beside)
1299
1326
  return;
1300
1327
  const it = engine.getItem(beside.id);
1301
- if (it && restore && (it.x !== beside.from.x || it.y !== beside.from.y))
1302
- engine.moveCheck(beside.id, beside.from.x, beside.from.y, { gate: false });
1303
- if (restore) {
1304
- // The sections the shift pushed come back with it.
1305
- for (const [oid, c] of beside.others) {
1306
- const o = engine.getItem(oid);
1307
- if (o && (o.x !== c.x || o.y !== c.y))
1308
- engine.moveCheck(oid, c.x, c.y, { gate: false });
1309
- }
1310
- }
1311
1328
  if (it)
1312
1329
  it.locked = true;
1313
1330
  relockOthersForSlab();
1331
+ hideBesideOverlay();
1314
1332
  beside = null;
1315
1333
  };
1316
- const applyBeside = (g, z) => {
1317
- var _a;
1318
- const tc = engine.getItem(z.id);
1319
- if (!tc)
1320
- return;
1321
- if (g.leg) {
1322
- g.leg.adopted.abort();
1323
- g.leg = null;
1324
- }
1325
- const w = g.spans.w;
1326
- const h = g.spans.h;
1327
- if (g.removedFromBoard) {
1328
- g.removedFromBoard = false;
1329
- (_a = hostOf(g.id)) === null || _a === void 0 ? void 0 : _a.classList.remove('axdb-out');
1330
- engine.add({ id: g.id, x: 0, y: engine.rows(), w, h });
1331
- }
1332
- if (beside && (beside.id !== z.id || beside.side !== z.side))
1333
- endBeside(true); // another container, or another side of it: start over from the rest layout
1334
- 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) => {
1335
1336
  let cell;
1336
1337
  let shiftTo = null;
1337
- switch (z.side) {
1338
+ switch (side) {
1338
1339
  case 'right':
1339
- cell = { x: from.x + tc.w, y: from.y };
1340
+ cell = { x: tc.x + tc.w, y: tc.y };
1340
1341
  if (cell.x + w > columns) {
1341
- shiftTo = { x: columns - w - tc.w, y: from.y };
1342
- 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 };
1343
1344
  }
1344
1345
  break;
1345
1346
  case 'left':
1346
- cell = { x: from.x - w, y: from.y };
1347
+ cell = { x: tc.x - w, y: tc.y };
1347
1348
  if (cell.x < 0) {
1348
- shiftTo = { x: w, y: from.y };
1349
- cell = { x: 0, y: from.y };
1349
+ shiftTo = { x: w, y: tc.y };
1350
+ cell = { x: 0, y: tc.y };
1350
1351
  }
1351
1352
  break;
1352
1353
  case 'top':
1353
- 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
1354
1355
  break;
1355
1356
  default:
1356
- 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 };
1357
1358
  }
1358
1359
  if (shiftTo && shiftTo.x < 0)
1359
1360
  shiftTo = null; // no room even shifted: the widget goes where it can
1360
- if (!beside) {
1361
- // The other sections give way to the shift the way they give way to a
1362
- // moved group (0.4.44): on the demo the Operations section spans the
1363
- // row under the panel and a locked tile refused the shift outright.
1364
- // Their cells are kept so they come back if the pointer leaves.
1365
- const others = new Map();
1366
- for (const o of engine.getItems())
1367
- if (o.id !== z.id && o.id !== g.id && isGroupMember(o.id))
1368
- others.set(o.id, { x: o.x, y: o.y });
1369
- const grp0 = diagram.getGroup(z.id);
1370
- beside = { id: z.id, side: z.side, from, frame0: grp0 ? frameOfGroup(grp0) : cellToRect({ x: from.x, y: from.y, w: tc.w, h: tc.h }, frame(), geom(), rows()), vacated: cell, others };
1371
- unlockOthersForSlab(z.id);
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;
1372
1371
  }
1373
- tc.locked = false; // for the gesture: it shifts, or the ghost pushes it
1374
- // The ghost steps off the board while the container shifts: the engine
1375
- // will not push the dragged tile, so a chart as wide as the panel had its
1376
- // own cell as the panel's target and the shift was refused (lab L94).
1377
- // Then it takes the cell the shift vacated.
1378
- if (shiftTo && (tc.x !== shiftTo.x || tc.y !== shiftTo.y)) {
1379
- if (engine.getItem(g.id))
1380
- engine.remove(g.id);
1381
- engine.moveCheck(z.id, shiftTo.x, shiftTo.y, { gate: false });
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;
1395
+ if (!beside)
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');
1382
1412
  }
1383
- beside.vacated = cell;
1384
1413
  if (!engine.getItem(g.id))
1385
1414
  engine.add({ id: g.id, x: 0, y: engine.rows(), w, h });
1386
- 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) {
1387
1416
  const at = engine.getItem(g.id);
1388
- if (!at || at.x !== cell.x || at.y !== cell.y)
1389
- 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);
1390
1419
  }
1391
1420
  project();
1392
1421
  };
@@ -1933,7 +1962,7 @@ export function bindDashboardGrid(api, group, options = {}) {
1933
1962
  const deltas = deltasSince(g.startCells, g.startGeom);
1934
1963
  const commands = buildCommitCommands(deltas);
1935
1964
  commands.push(...groupCellCommands(deltas)); // a container a BESIDE drop shifted (0.4.45)
1936
- endBeside(false);
1965
+ endBeside();
1937
1966
  if (g.esc && g.esc.rowsAdded !== 0) {
1938
1967
  commands.push(new SetGroupCellCommand(group.id, g.esc.cellBefore, g.esc.cellAfter, g.esc.frameBefore, g.esc.frameAfter));
1939
1968
  }
@@ -1972,7 +2001,7 @@ export function bindDashboardGrid(api, group, options = {}) {
1972
2001
  g.esc.peer.resizeMemberBy(group.id, -g.esc.rowsAdded); // slab back down
1973
2002
  g.esc = null;
1974
2003
  }
1975
- endBeside(true);
2004
+ endBeside();
1976
2005
  if (g.started) {
1977
2006
  if (g.removedFromBoard || g.kind === 'palette') {
1978
2007
  // The engine cannot resurrect a removed item — rebuild from the
@@ -2072,6 +2101,7 @@ export function bindDashboardGrid(api, group, options = {}) {
2072
2101
  g.leg.adopted.abort();
2073
2102
  g.leg = null;
2074
2103
  }
2104
+ endBeside(); // a band's promise gives way to the strip's
2075
2105
  if (!g.removedFromBoard) {
2076
2106
  g.removedFromBoard = true;
2077
2107
  engine.remove(g.id);
@@ -2093,6 +2123,7 @@ export function bindDashboardGrid(api, group, options = {}) {
2093
2123
  }
2094
2124
  // BESIDE a tab container (0.4.45): its outer band puts the widget next
2095
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.
2096
2127
  if (!isStatic) {
2097
2128
  const z = besideZoneAt(ev.world.x, ev.world.y);
2098
2129
  if (z) {
@@ -2101,29 +2132,16 @@ export function bindDashboardGrid(api, group, options = {}) {
2101
2132
  return;
2102
2133
  }
2103
2134
  if (beside) {
2104
- // Off the band: the container comes back unless the POINTER is
2105
- // still over the cell it vacated the widget's own landing, which
2106
- // is what "beside" leaves under the pointer once the container has
2107
- // shifted away (the ghost's wanted cell carries the grab offset and
2108
- // read as "elsewhere" a move later, and the shift undid itself).
2109
- // "Still here" is the pointer in the SAME band of the container's
2110
- // ORIGINAL frame (it asked for beside from there, and the container
2111
- // is what moved) or over the cell the widget took: a one-row
2112
- // widget's cell is not where a hand hovering the band sits, and
2113
- // testing only that undid the shift on the next move, which pushed
2114
- // the widget to the bottom as the panel came back (live walk N).
2115
- // "Anywhere inside the original frame" is too much: a hand crossing
2116
- // the bottom band on its way to the middle never reached the page
2117
- // (lab L73) — the middle of the original frame is a zone change.
2118
- const r = cellToRect({ x: beside.vacated.x, y: beside.vacated.y, w: g.spans.w, h: g.spans.h }, frame(), geom(), rows());
2119
- const inR = (q) => ev.world.x >= q.x - gap && ev.world.x <= q.x + q.width + gap && ev.world.y >= q.y - gap && ev.world.y <= q.y + q.height + gap;
2120
- const over = bandOf(beside.frame0, ev.world.x, ev.world.y) === beside.side || inR(r);
2121
- if (!over)
2122
- endBeside(true);
2123
- 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) {
2124
2141
  syncPlaceholder();
2125
2142
  return;
2126
2143
  }
2144
+ endBeside(); // the overlay goes; the handoff below puts the ghost back on a board
2127
2145
  }
2128
2146
  }
2129
2147
  // Deepest board under the pointer wins: the nested KPI strip beats the
@@ -2560,6 +2578,13 @@ export function bindDashboardGrid(api, group, options = {}) {
2560
2578
  (_b = options.onGesture) === null || _b === void 0 ? void 0 : _b.call(options, { type: 'commit', kind: g.kind, nodeId: g.id, changed: true });
2561
2579
  return;
2562
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
+ }
2563
2588
  if (g.leg) {
2564
2589
  // -- CROSS-CONTAINER COMMIT: one batch across both boards -----------
2565
2590
  const fin = g.leg.adopted.finalize();