@grafloria/element 0.4.16 → 0.4.18

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.16",
3
+ "version": "0.4.18",
4
4
  "type": "module",
5
5
  "main": "./src/index.js",
6
6
  "types": "./src/index.d.ts",
@@ -1128,7 +1128,7 @@ export function dashboard(options) {
1128
1128
  * its metadata, a fresh binder on the same group, selection carried.
1129
1129
  */
1130
1130
  function rebindContainerLayout(id, next) {
1131
- var _a, _b, _c, _d, _e, _f, _g, _h, _j;
1131
+ var _a, _b, _c, _d, _e, _f;
1132
1132
  const cg = ctx.boardGroups.get(id);
1133
1133
  const w = specById.get(id);
1134
1134
  const b = binders.get(id);
@@ -1162,14 +1162,13 @@ export function dashboard(options) {
1162
1162
  // a child escalation grew to two rows must keep them through a
1163
1163
  // split → grid round trip (the visual gate caught it collapsing to one
1164
1164
  // inside a two-row slab).
1165
- const slabRows = (_c = (_b = binders.get((_a = ctx.viewOfWidget.get(id)) !== null && _a !== void 0 ? _a : '')) === null || _b === void 0 ? void 0 : _b.cellOf(id)) === null || _c === void 0 ? void 0 : _c.h;
1166
- const authored = (_d = w.maxRows) !== null && _d !== void 0 ? _d : rowExtentOf((_e = w.widgets) !== null && _e !== void 0 ? _e : []);
1167
- bindContainer(cg, w, (_f = ctx.viewOfBoard.get(id)) !== null && _f !== void 0 ? _f : ctx.active, Math.max(authored, slabRows !== null && slabRows !== void 0 ? slabRows : 0));
1168
- (_g = binders.get(id)) === null || _g === void 0 ? void 0 : _g.sync();
1165
+ void ((_b = binders.get((_a = ctx.viewOfWidget.get(id)) !== null && _a !== void 0 ? _a : '')) === null || _b === void 0 ? void 0 : _b.cellOf(id));
1166
+ bindContainer(cg, w, (_c = ctx.viewOfBoard.get(id)) !== null && _c !== void 0 ? _c : ctx.active);
1167
+ (_d = binders.get(id)) === null || _d === void 0 ? void 0 : _d.sync();
1169
1168
  if (focused)
1170
- (_h = binders.get(id)) === null || _h === void 0 ? void 0 : _h.focusWidget(focused);
1169
+ (_e = binders.get(id)) === null || _e === void 0 ? void 0 : _e.focusWidget(focused);
1171
1170
  else if (selected)
1172
- (_j = binders.get(id)) === null || _j === void 0 ? void 0 : _j.selectWidget(selected);
1171
+ (_f = binders.get(id)) === null || _f === void 0 ? void 0 : _f.selectWidget(selected);
1173
1172
  }
1174
1173
  (_m = ctx.attachHistory) === null || _m === void 0 ? void 0 : _m.call(ctx);
1175
1174
  return;
@@ -1273,7 +1272,7 @@ export function dashboard(options) {
1273
1272
  }
1274
1273
  /** Bind (or re-bind) a container's inner grid on its group. */
1275
1274
  function bindContainer(cg, w, viewId, innerRows) {
1276
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
1275
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
1277
1276
  void viewId;
1278
1277
  // The LIVE switches ride on the view's binder when it exists (a
1279
1278
  // container bound after a setStatic/setRtl/setDragHandle must match).
@@ -1290,7 +1289,10 @@ export function dashboard(options) {
1290
1289
  binders.set(w.id, bindDashboardSplit(a, cg, Object.assign(Object.assign({}, inner), (w.tree !== undefined ? { tree: w.tree } : {}))));
1291
1290
  return;
1292
1291
  }
1293
- binders.set(w.id, bindDashboardGrid(a, cg, Object.assign(Object.assign({}, inner), { sizing: 'fit', designHeight: 0, maxRows: (_j = innerRows !== null && innerRows !== void 0 ? innerRows : w.maxRows) !== null && _j !== void 0 ? _j : rowExtentOf((_k = w.widgets) !== null && _k !== void 0 ? _k : []), float: false, escalate: w.sizing !== 'fit' })));
1292
+ binders.set(w.id, bindDashboardGrid(a, cg, Object.assign(Object.assign({}, inner), { sizing: 'fit', designHeight: 0,
1293
+ // The DESIGN: authored, else what the group was mounted with, else
1294
+ // the children's extent — the binder's live bound follows the cells.
1295
+ maxRows: (_l = (_j = innerRows !== null && innerRows !== void 0 ? innerRows : w.maxRows) !== null && _j !== void 0 ? _j : (_k = cg.getMetadata('containerWidget')) === null || _k === void 0 ? void 0 : _k.maxRows) !== null && _l !== void 0 ? _l : rowExtentOf((_m = w.widgets) !== null && _m !== void 0 ? _m : []), float: false, escalate: w.sizing !== 'fit' })));
1294
1296
  }
1295
1297
  /**
1296
1298
  * One reporter for every binder on a view — the view's own and each
@@ -429,6 +429,13 @@ interface BinderPeer {
429
429
  group: GroupModel;
430
430
  /** True when this board's engine holds `id` as an item (member lookup). */
431
431
  hasItem(id: string): boolean;
432
+ /**
433
+ * Drop this board's selection: a press that selects a tile on ONE board of
434
+ * a canvas clears the others, so a section and its parent never show two
435
+ * rings at once (Quantia Groups page: three tiles selected across three
436
+ * boards after three clicks).
437
+ */
438
+ clearSelection?(): void;
432
439
  /** The member's current cell in this board (undefined when absent). */
433
440
  memberCell(id: string): CellRect | undefined;
434
441
  /**
@@ -490,6 +497,8 @@ interface AdoptedLeg {
490
497
  * unregister. A split board adopts nothing and grows no slab: its `adopt`
491
498
  * answers null and `resizeMemberBy` answers unchanged.
492
499
  */
500
+ /** Clear the selection on every OTHER board of the canvas — one selection per canvas. */
501
+ export declare function clearOtherSelections(container: HTMLElement, self: BinderPeer | null): void;
493
502
  export declare function registerBoardPeer(container: HTMLElement, peer: BinderPeer): () => void;
494
503
  export type { BinderPeer };
495
504
  export declare function bindDashboardGrid(api: DashboardGridApi, group: GroupModel, options?: DashboardGridOptions): DashboardGridHandle;
@@ -153,6 +153,13 @@ const BOARD_REGISTRY = new WeakMap();
153
153
  * unregister. A split board adopts nothing and grows no slab: its `adopt`
154
154
  * answers null and `resizeMemberBy` answers unchanged.
155
155
  */
156
+ /** Clear the selection on every OTHER board of the canvas — one selection per canvas. */
157
+ export function clearOtherSelections(container, self) {
158
+ var _a, _b;
159
+ for (const p of (_a = BOARD_REGISTRY.get(container)) !== null && _a !== void 0 ? _a : [])
160
+ if (p !== self)
161
+ (_b = p.clearSelection) === null || _b === void 0 ? void 0 : _b.call(p);
162
+ }
156
163
  export function registerBoardPeer(container, peer) {
157
164
  let set = BOARD_REGISTRY.get(container);
158
165
  if (!set) {
@@ -280,8 +287,17 @@ export function bindDashboardGrid(api, group, options = {}) {
280
287
  const minRowHeight = (_e = options.minRowHeight) !== null && _e !== void 0 ? _e : 28;
281
288
  const squeeze = options.squeeze !== false;
282
289
  let float = (_f = options.float) !== null && _f !== void 0 ? _f : false;
283
- /** The AUTHORED bound — a nested strip's design (row-first push, escalation). */
284
- const maxRows = options.maxRows;
290
+ /**
291
+ * The AUTHORED bound — a nested section's design (row-first push,
292
+ * escalation) — and the LIVE bound the gestures respect. A section that
293
+ * escalation grew holds more rows than its design; the live bound follows
294
+ * the members' extent on every rebuild (reload, undo, refresh), and a shrink
295
+ * never takes the section below `designRows`.
296
+ */
297
+ const designRows = options.maxRows;
298
+ let maxRows = options.maxRows;
299
+ const extentOf = (items) => items.reduce((m, i) => Math.max(m, i.y + i.h), 0);
300
+ const liveBound = (items) => designRows === undefined ? undefined : Math.max(designRows, extentOf(items));
285
301
  /** May a pull past the bound grow the slab in the parent? `false` = the pane is the bound. */
286
302
  const escalate = options.escalate !== false;
287
303
  const dragOut = (_g = options.dragOut) !== null && _g !== void 0 ? _g : 'cancel';
@@ -314,6 +330,7 @@ export function bindDashboardGrid(api, group, options = {}) {
314
330
  * kit's documented boot contract) and turning float off.
315
331
  */
316
332
  const engineFrom = (items, pack = false, at = columns) => {
333
+ maxRows = liveBound(items);
317
334
  const e = new GridPackEngine(items, { columns: at, float: pack ? float : true, maxRows, capacity });
318
335
  e.float = float;
319
336
  return e;
@@ -691,11 +708,15 @@ export function bindDashboardGrid(api, group, options = {}) {
691
708
  */
692
709
  let selectedId;
693
710
  const selectWidget = (id) => {
711
+ if (id !== undefined)
712
+ clearOtherSelections(api.container, selfPeerRef);
694
713
  if (id === selectedId)
695
714
  return;
696
715
  selectedId = id;
697
716
  syncA11y();
698
717
  };
718
+ /** Set once the peer object exists (below); `selectWidget` runs before that only on boot. */
719
+ let selfPeerRef = null;
699
720
  const syncA11y = (only) => {
700
721
  var _a, _b;
701
722
  if (disposed)
@@ -1171,6 +1192,10 @@ export function bindDashboardGrid(api, group, options = {}) {
1171
1192
  else {
1172
1193
  engine.cancelGesture();
1173
1194
  }
1195
+ if (designRows !== undefined) {
1196
+ maxRows = liveBound(engine.getItems());
1197
+ engine.maxRows = maxRows;
1198
+ }
1174
1199
  // Restore every pixel to its gesture-start state.
1175
1200
  writing = true;
1176
1201
  try {
@@ -1375,23 +1400,24 @@ export function bindDashboardGrid(api, group, options = {}) {
1375
1400
  // the strip's CURRENT slab rows, whatever gesture created them; the
1376
1401
  // ledger just accumulates this gesture's net change for the one-batch
1377
1402
  // commit and for Escape.
1378
- // ESCALATION SCALES THE WHOLE STRIP every tile inside taller together
1379
- // (s21, the model the user agreed on for a KPI strip). That is only sane
1380
- // when the pulled tile spans the strip's full height, which every tile of
1381
- // a one-row strip does. A partial-height tile in a MULTI-ROW section
1382
- // resizes within the section and is refused past its rows: the first
1383
- // version compared the tile's height with the whole section and shrank a
1384
- // 14-row control panel to 4 rows under a 150-px pull on a one-row
1385
- // drop-down (Quantia, Groups page "boom its destroyed"). And a section
1386
- // never shrinks below its designed rows.
1387
- const pulled = engine.getItem(g.id);
1388
- const spansStrip = !!pulled && pulled.y === 0 && pulled.h >= (maxRows !== null && maxRows !== void 0 ? maxRows : Infinity);
1389
- if (maxRows !== undefined && escalate && spansStrip && g.kind === 'resize') {
1403
+ // NESTED ESCALATION a section is a board one level down, and a pull
1404
+ // inside it can need rows the section does not hold. Two shapes:
1405
+ // · a tile spanning the section's FULL HEIGHT (every tile of a one-row
1406
+ // KPI strip): the section gains a row and every full-height tile grows
1407
+ // with it a row of tiles pulled taller stays a row (s21) — and gives
1408
+ // it back when pulled up, never below the design;
1409
+ // · a PARTIAL tile (a one-row control in a 14-row panel): it pushes what
1410
+ // is below it; a row the pushed layout needs and the section does not
1411
+ // hold is asked of the board, one per pointer step, and rows the
1412
+ // layout no longer needs go back. The board decides: grow extends, fit
1413
+ // squeezes or refuses. The first version grew the section only for
1414
+ // the full-height case and REFUSED the partial one; before that it
1415
+ // shrank a 14-row panel to 4 (Quantia, Groups page).
1416
+ // The live bound `maxRows` follows the section's rows through all of it.
1417
+ if (designRows !== undefined && maxRows !== undefined && escalate && g.kind === 'resize') {
1390
1418
  const parent = parentPeer();
1391
- if (parent) {
1392
- const visual = boardVisualHeight();
1393
- const slabRows = (_h = (_g = parent.memberCell(group.id)) === null || _g === void 0 ? void 0 : _g.h) !== null && _h !== void 0 ? _h : 1;
1394
- const rowPx = visual / Math.max(1, slabRows);
1419
+ const pulled = engine.getItem(g.id);
1420
+ if (parent && pulled) {
1395
1421
  const record = (res, d) => {
1396
1422
  if (!res.changed || !res.cellBefore || !res.cellAfter || !res.frameBefore || !res.frameAfter)
1397
1423
  return;
@@ -1408,14 +1434,83 @@ export function bindDashboardGrid(api, group, options = {}) {
1408
1434
  g.esc.rowsAdded += d;
1409
1435
  g.esc.cellAfter = res.cellAfter;
1410
1436
  g.esc.frameAfter = res.frameAfter;
1411
- project();
1412
1437
  };
1413
- if (h > visual + 24) {
1414
- record(parent.resizeMemberBy(group.id, +1), +1);
1438
+ const setInnerRows = (n) => {
1439
+ maxRows = n;
1440
+ engine.maxRows = n;
1441
+ };
1442
+ const slabRows = (_h = (_g = parent.memberCell(group.id)) === null || _g === void 0 ? void 0 : _g.h) !== null && _h !== void 0 ? _h : maxRows;
1443
+ const inner = maxRows;
1444
+ const rhNow = rowHeightFor(geom(), rows());
1445
+ const rowPx = rhNow + gap;
1446
+ // The SAME rounding the resize path quantises with, or the tile shrinks
1447
+ // through that path first and the section never gets its row back.
1448
+ const wantRows = Math.max(1, Math.round((h + gap) / rowPx));
1449
+ const wantsMore = wantRows > pulled.h;
1450
+ const wantsLess = wantRows < pulled.h;
1451
+ const fullHeight = pulled.y === 0 && pulled.h >= inner;
1452
+ const fullOnes = engine.getItems().filter((i) => i.y === 0 && i.h >= inner).map((i) => i.id);
1453
+ const resizeAll = (ids, rowsTo) => {
1454
+ for (const id of ids) {
1455
+ const it = engine.getItem(id);
1456
+ if (it)
1457
+ engine.resizeCheck(id, it.w, rowsTo);
1458
+ }
1459
+ };
1460
+ let touched = false;
1461
+ if (wantsMore) {
1462
+ if (fullHeight) {
1463
+ const res = parent.resizeMemberBy(group.id, +1);
1464
+ if (res.changed) {
1465
+ record(res, +1);
1466
+ setInnerRows(inner + 1);
1467
+ resizeAll(fullOnes, inner + 1);
1468
+ touched = true;
1469
+ }
1470
+ }
1471
+ else if (!engine.resizeCheck(g.id, pulled.w, pulled.h + 1).changed) {
1472
+ const res = parent.resizeMemberBy(group.id, +1);
1473
+ if (res.changed) {
1474
+ record(res, +1);
1475
+ setInnerRows(inner + 1);
1476
+ engine.resizeCheck(g.id, pulled.w, pulled.h + 1);
1477
+ touched = true;
1478
+ }
1479
+ }
1480
+ else {
1481
+ touched = true;
1482
+ }
1415
1483
  }
1416
- else if (slabRows > Math.max(1, maxRows) && h < visual - rowPx * 0.7) {
1417
- record(parent.resizeMemberBy(group.id, -1), -1);
1484
+ else if (wantsLess) {
1485
+ if (fullHeight) {
1486
+ if (slabRows > designRows && inner > 1) {
1487
+ resizeAll(fullOnes, inner - 1);
1488
+ const res = parent.resizeMemberBy(group.id, -1);
1489
+ if (res.changed) {
1490
+ record(res, -1);
1491
+ setInnerRows(inner - 1);
1492
+ }
1493
+ else {
1494
+ resizeAll(fullOnes, inner);
1495
+ }
1496
+ touched = true;
1497
+ }
1498
+ }
1499
+ else if (pulled.h > 1) {
1500
+ engine.resizeCheck(g.id, pulled.w, pulled.h - 1);
1501
+ const floor = Math.max(designRows, extentOf(engine.getItems()));
1502
+ if (slabRows > floor) {
1503
+ const res = parent.resizeMemberBy(group.id, -1);
1504
+ if (res.changed) {
1505
+ record(res, -1);
1506
+ setInnerRows(inner - 1);
1507
+ }
1508
+ }
1509
+ touched = true;
1510
+ }
1418
1511
  }
1512
+ if (touched)
1513
+ project();
1419
1514
  }
1420
1515
  }
1421
1516
  // The fluid preview must not outrun what the board can accept: on a
@@ -1706,6 +1801,12 @@ export function bindDashboardGrid(api, group, options = {}) {
1706
1801
  };
1707
1802
  const selfPeer = {
1708
1803
  group,
1804
+ clearSelection: () => {
1805
+ if (selectedId === undefined)
1806
+ return;
1807
+ selectedId = undefined;
1808
+ syncA11y();
1809
+ },
1709
1810
  hasItem: (id) => !!engine.getItem(id),
1710
1811
  memberCell: (id) => {
1711
1812
  const it = engine.getItem(id);
@@ -1742,6 +1843,9 @@ export function bindDashboardGrid(api, group, options = {}) {
1742
1843
  adopt,
1743
1844
  };
1744
1845
  peersOnCanvas().add(selfPeer);
1846
+ selfPeerRef = selfPeer;
1847
+ // The board's gap, for chrome that must fit BETWEEN tiles (the outside grip tab).
1848
+ api.container.style.setProperty('--axdb-gap', `${gap}px`);
1745
1849
  const tool = {
1746
1850
  id: `dashboard-grid:${group.id}:${++binderSeq}`,
1747
1851
  priority: 2, // point-specific claim — outranks mode-style tools (see ext/tools.ts)
@@ -1986,7 +2090,7 @@ export function bindDashboardGrid(api, group, options = {}) {
1986
2090
  return;
1987
2091
  if (focusedId !== hit.id || selectedId !== hit.id) {
1988
2092
  focusedId = hit.id;
1989
- selectedId = hit.id;
2093
+ selectWidget(hit.id);
1990
2094
  syncA11y();
1991
2095
  }
1992
2096
  };
@@ -2338,7 +2442,7 @@ export function bindDashboardGrid(api, group, options = {}) {
2338
2442
  if (disposed || !((_a = group.members) !== null && _a !== void 0 ? _a : new Set()).has(id) || !diagram.getNode(id))
2339
2443
  return false;
2340
2444
  focusedId = id;
2341
- selectedId = id;
2445
+ selectWidget(id);
2342
2446
  syncA11y();
2343
2447
  (_c = (_b = hostOf(id)) === null || _b === void 0 ? void 0 : _b.focus) === null || _c === void 0 ? void 0 : _c.call(_b, { preventScroll: true });
2344
2448
  return true;
@@ -25,7 +25,7 @@
25
25
  import { __awaiter } from "tslib";
26
26
  import { Command } from '@grafloria/engine';
27
27
  import { LiveRegionController, registerTool } from '@grafloria/renderer';
28
- import { dragHandleSelector, gripHostOf, gripOf, normalizeDragHandle, ownsPress, pressOnDragHandle, registerBoardPeer, syncGrip, DRAG_HANDLE_CLASS } from './grid-binder.js';
28
+ import { clearOtherSelections, dragHandleSelector, gripHostOf, gripOf, normalizeDragHandle, ownsPress, pressOnDragHandle, registerBoardPeer, syncGrip, DRAG_HANDLE_CLASS } from './grid-binder.js';
29
29
  import { cellFromGridItem } from './grid-mapping.js';
30
30
  import { addSplitLeaf, cellsFromSplit, cloneSplit, dividersOf, groupRectsOf, insertSplitLeaf, moveSplitDivider, normalizeSplit, pathToLeaf, projectSplit, removeSplitLeaf, splitFromCells, splitLeaves, } from './split-layout.js';
31
31
  import { ensureDashboardKitStyles } from './styles.js';
@@ -372,11 +372,14 @@ export function bindDashboardSplit(api, group, options = {}) {
372
372
  /** The selected widget (see grid-binder): stamped `axdb-selected`, shows the grip. */
373
373
  let selectedId;
374
374
  const selectWidget = (id) => {
375
+ if (id !== undefined)
376
+ clearOtherSelections(api.container, selfPeerRef);
375
377
  if (id === selectedId)
376
378
  return;
377
379
  selectedId = id;
378
380
  syncA11y();
379
381
  };
382
+ let selfPeerRef = null;
380
383
  // Static boards let content be clicked — see grid-binder's staticGuard.
381
384
  const staticGuard = (e) => {
382
385
  var _a, _b, _c, _d;
@@ -823,7 +826,7 @@ export function bindDashboardSplit(api, group, options = {}) {
823
826
  return;
824
827
  if (focusedId !== hit.id || selectedId !== hit.id) {
825
828
  focusedId = hit.id;
826
- selectedId = hit.id;
829
+ selectWidget(hit.id);
827
830
  syncA11y();
828
831
  }
829
832
  };
@@ -967,7 +970,26 @@ export function bindDashboardSplit(api, group, options = {}) {
967
970
  if (layerEl && hostObserver)
968
971
  hostObserver.observe(layerEl, { childList: true });
969
972
  // -- the handle -------------------------------------------------------------
970
- const rowsGuess = () => Math.max(1, Math.round(frame().height / (baseRowHeight + gap)));
973
+ /**
974
+ * The row count the tree's cells are written in. A grid-authored board (or
975
+ * one switched from the grid) carries its members' cells: their extent IS
976
+ * the row count, and switching back must return the same cells — the guess
977
+ * below quantised three 14-row sections on a 758-px fit board to 5 rows
978
+ * (base row height 130), and the grid came back with 14-row inner grids
979
+ * inside 5-row slabs (Quantia, Groups page, Split → Grid). The guess stays
980
+ * for a tree-authored board that never had cells.
981
+ */
982
+ const rowsAtBind = (() => {
983
+ let max = 0;
984
+ for (const id of members()) {
985
+ const c = persistedCell(id);
986
+ if (!c)
987
+ return undefined;
988
+ max = Math.max(max, c.y + c.h);
989
+ }
990
+ return max > 0 ? max : undefined;
991
+ })();
992
+ const rowsGuess = () => rowsAtBind !== null && rowsAtBind !== void 0 ? rowsAtBind : Math.max(1, Math.round(frame().height / (baseRowHeight + gap)));
971
993
  const handle = {
972
994
  sync() {
973
995
  if (disposed)
@@ -1021,7 +1043,7 @@ export function bindDashboardSplit(api, group, options = {}) {
1021
1043
  if (!((_a = group.members) !== null && _a !== void 0 ? _a : new Set()).has(id) || !diagram.getNode(id))
1022
1044
  return false;
1023
1045
  focusedId = id;
1024
- selectedId = id;
1046
+ selectWidget(id);
1025
1047
  syncA11y();
1026
1048
  (_c = (_b = hostOf(id)) === null || _b === void 0 ? void 0 : _b.focus) === null || _c === void 0 ? void 0 : _c.call(_b, { preventScroll: true });
1027
1049
  return true;
@@ -1168,8 +1190,14 @@ export function bindDashboardSplit(api, group, options = {}) {
1168
1190
  api.renderNow();
1169
1191
  // A split board on a CONTAINER (item 7) sits inside a parent grid: register
1170
1192
  // as a peer so the parent's hitTest hands presses on our tiles to us.
1171
- const unregisterPeer = registerBoardPeer(api.container, {
1193
+ const selfPeer = {
1172
1194
  group,
1195
+ clearSelection: () => {
1196
+ if (selectedId === undefined)
1197
+ return;
1198
+ selectedId = undefined;
1199
+ syncA11y();
1200
+ },
1173
1201
  hasItem: (id) => { var _a; return ((_a = group.members) !== null && _a !== void 0 ? _a : new Set()).has(id); },
1174
1202
  memberCell: (id) => handle.cellOf(id),
1175
1203
  resizeMemberBy: () => ({ changed: false }),
@@ -1180,7 +1208,10 @@ export function bindDashboardSplit(api, group, options = {}) {
1180
1208
  return f.width * f.height;
1181
1209
  },
1182
1210
  adopt: () => null,
1183
- });
1211
+ };
1212
+ const unregisterPeer = registerBoardPeer(api.container, selfPeer);
1213
+ selfPeerRef = selfPeer;
1214
+ api.container.style.setProperty('--axdb-gap', `${gap}px`);
1184
1215
  const disposeHandle = handle.dispose.bind(handle);
1185
1216
  handle.dispose = () => {
1186
1217
  unregisterPeer();
@@ -235,7 +235,10 @@ const CSS = `
235
235
  .grafloria-node-host > .axdb-grip--inside { top: 2px; }
236
236
  }
237
237
  /* OUTSIDE: a tab on the card's top edge, its corners in line with the card's. */
238
- .grafloria-node-host > .axdb-grip--outside { top: -11px; height: 11px; border-bottom-left-radius: 0; border-bottom-right-radius: 0; border-bottom: 0; box-shadow: 0 -1px 2px rgba(16, 24, 40, .08); }
238
+ /* The OUTSIDE tab lives in the gap between tiles: never taller than the gap
239
+ less a pixel (an 11-px tab in a 10-px gap sat on the tile above), never
240
+ shorter than 6 px so the dots still read. */
241
+ .grafloria-node-host > .axdb-grip--outside { top: calc(-1 * clamp(6px, var(--axdb-gap, 11px) - 1px, 11px)); height: clamp(6px, calc(var(--axdb-gap, 11px) - 1px), 11px); border-bottom-left-radius: 0; border-bottom-right-radius: 0; border-bottom: 0; box-shadow: 0 -1px 2px rgba(16, 24, 40, .08); }
239
242
  .grafloria-node-host > .axdb-grip--outside.axdb-grip--left { left: 10px; }
240
243
  .grafloria-node-host > .axdb-grip--outside.axdb-grip--right { right: 10px; }
241
244
  .grafloria-node-host > .axdb-grip--center { left: 50%; transform: translateX(-50%); }