@grafloria/element 0.4.44 → 0.4.46

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.44",
3
+ "version": "0.4.46",
4
4
  "type": "module",
5
5
  "main": "./src/index.js",
6
6
  "types": "./src/index.d.ts",
@@ -55,6 +55,7 @@ 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
+ import { TAB_STRIP_HEIGHT } from './tabs.js';
58
59
  /** The class of the painted grip element (a child of the node host). */
59
60
  export const GRIP_CLASS = 'axdb-grip';
60
61
  /** The container class that turns the caption strip's grip dots on. */
@@ -994,6 +995,19 @@ export function bindDashboardGrid(api, group, options = {}) {
994
995
  }
995
996
  return out;
996
997
  };
998
+ /** The member groups a gesture displaced, as their own cell commands — `buildCommitCommands` skips groups by design. */
999
+ const groupCellCommands = (deltas) => {
1000
+ const out = [];
1001
+ for (const d of deltas) {
1002
+ 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))
1003
+ continue;
1004
+ const og = diagram.getGroup(d.id);
1005
+ if (!og)
1006
+ continue;
1007
+ 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)));
1008
+ }
1009
+ return out;
1010
+ };
997
1011
  const execute = (name, commands) => {
998
1012
  if (commands.length === 0)
999
1013
  return false;
@@ -1249,6 +1263,133 @@ export function bindDashboardGrid(api, group, options = {}) {
1249
1263
  */
1250
1264
  const TAB_FRAME_GRIP = 3;
1251
1265
  const edgeGripFor = (grp) => (isTabsGroup(grp) ? TAB_FRAME_GRIP : EDGE_GRIP);
1266
+ const BESIDE_BAND = 0.2;
1267
+ /** 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) => {
1269
+ if (wx < f.x || wx > f.x + f.width || wy < f.y || wy > f.y + f.height)
1270
+ return null;
1271
+ const bodyY = f.y + TAB_STRIP_HEIGHT;
1272
+ const bodyH = Math.max(1, f.height - TAB_STRIP_HEIGHT);
1273
+ const rx = (wx - f.x) / Math.max(1, f.width);
1274
+ const ry = (wy - bodyY) / bodyH;
1275
+ if (ry < 0)
1276
+ 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];
1282
+ };
1283
+ const besideZoneAt = (wx, wy) => {
1284
+ var _a;
1285
+ for (const id of (_a = group.members) !== null && _a !== void 0 ? _a : []) {
1286
+ const grp = diagram.getGroup(id);
1287
+ if (!grp || diagram.getNode(id) || !isTabsGroup(grp))
1288
+ continue;
1289
+ const side = bandOf(frameOfGroup(grp), wx, wy);
1290
+ if (side)
1291
+ return { id, side };
1292
+ }
1293
+ return null;
1294
+ };
1295
+ /** The container a BESIDE drop unlocked (and maybe shifted): relocked, and put back if asked, when the zone or the gesture ends. */
1296
+ let beside = null;
1297
+ const endBeside = (restore) => {
1298
+ if (!beside)
1299
+ return;
1300
+ 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
+ if (it)
1312
+ it.locked = true;
1313
+ relockOthersForSlab();
1314
+ beside = null;
1315
+ };
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 };
1335
+ let cell;
1336
+ let shiftTo = null;
1337
+ switch (z.side) {
1338
+ case 'right':
1339
+ cell = { x: from.x + tc.w, y: from.y };
1340
+ if (cell.x + w > columns) {
1341
+ shiftTo = { x: columns - w - tc.w, y: from.y };
1342
+ cell = { x: columns - w, y: from.y };
1343
+ }
1344
+ break;
1345
+ case 'left':
1346
+ cell = { x: from.x - w, y: from.y };
1347
+ if (cell.x < 0) {
1348
+ shiftTo = { x: w, y: from.y };
1349
+ cell = { x: 0, y: from.y };
1350
+ }
1351
+ break;
1352
+ 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
+ break;
1355
+ default:
1356
+ cell = { x: Math.max(0, Math.min(from.x, columns - w)), y: from.y + tc.h };
1357
+ }
1358
+ if (shiftTo && shiftTo.x < 0)
1359
+ 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);
1372
+ }
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 });
1382
+ }
1383
+ beside.vacated = cell;
1384
+ if (!engine.getItem(g.id))
1385
+ 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) {
1387
+ 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);
1390
+ }
1391
+ project();
1392
+ };
1252
1393
  /**
1253
1394
  * CARRIED (0.4.43): a group dragged by its strip or band moves as ONE thing.
1254
1395
  * The held TILE is transition-exempt (the ghost), but a group has no host of
@@ -1669,14 +1810,7 @@ export function bindDashboardGrid(api, group, options = {}) {
1669
1810
  // groups by design (a group's cell is its own command), so they are
1670
1811
  // committed here the way a dock commits the groups it displaced — or the
1671
1812
  // model snapped every one of them, and the mover, straight back.
1672
- for (const d of deltas) {
1673
- 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))
1674
- continue;
1675
- const og = diagram.getGroup(d.id);
1676
- if (!og)
1677
- continue;
1678
- commands.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)));
1679
- }
1813
+ commands.push(...groupCellCommands(deltas));
1680
1814
  const b = g.cellBefore;
1681
1815
  if (it && grp && (b.x !== it.x || b.y !== it.y || b.w !== it.w || b.h !== it.h)) {
1682
1816
  commands.push(new SetGroupCellCommand(g.id, b, { x: it.x, y: it.y, w: it.w, h: it.h }, g.frameBefore, frameOfGroup(grp)));
@@ -1798,6 +1932,8 @@ export function bindDashboardGrid(api, group, options = {}) {
1798
1932
  project();
1799
1933
  const deltas = deltasSince(g.startCells, g.startGeom);
1800
1934
  const commands = buildCommitCommands(deltas);
1935
+ commands.push(...groupCellCommands(deltas)); // a container a BESIDE drop shifted (0.4.45)
1936
+ endBeside(false);
1801
1937
  if (g.esc && g.esc.rowsAdded !== 0) {
1802
1938
  commands.push(new SetGroupCellCommand(group.id, g.esc.cellBefore, g.esc.cellAfter, g.esc.frameBefore, g.esc.frameAfter));
1803
1939
  }
@@ -1836,6 +1972,7 @@ export function bindDashboardGrid(api, group, options = {}) {
1836
1972
  g.esc.peer.resizeMemberBy(group.id, -g.esc.rowsAdded); // slab back down
1837
1973
  g.esc = null;
1838
1974
  }
1975
+ endBeside(true);
1839
1976
  if (g.started) {
1840
1977
  if (g.removedFromBoard || g.kind === 'palette') {
1841
1978
  // The engine cannot resurrect a removed item — rebuild from the
@@ -1954,6 +2091,41 @@ export function bindDashboardGrid(api, group, options = {}) {
1954
2091
  g.strip = null;
1955
2092
  }
1956
2093
  }
2094
+ // BESIDE a tab container (0.4.45): its outer band puts the widget next
2095
+ // to it — at the board's edge the container shifts over to make room.
2096
+ if (!isStatic) {
2097
+ const z = besideZoneAt(ev.world.x, ev.world.y);
2098
+ if (z) {
2099
+ applyBeside(g, z);
2100
+ syncPlaceholder();
2101
+ return;
2102
+ }
2103
+ 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 {
2124
+ syncPlaceholder();
2125
+ return;
2126
+ }
2127
+ }
2128
+ }
1957
2129
  // Deepest board under the pointer wins: the nested KPI strip beats the
1958
2130
  // tab that contains it; a foreign board beats "outside". Strict frames
1959
2131
  // first; the one-row grace band below each board (gridstack's extra