@grafloria/element 0.4.42 → 0.4.44

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.42",
3
+ "version": "0.4.44",
4
4
  "type": "module",
5
5
  "main": "./src/index.js",
6
6
  "types": "./src/index.d.ts",
@@ -48,7 +48,7 @@ import { bindDashboardGrid, parentPeerOf, SequenceCommand, } from './grid-binder
48
48
  import { bindDashboardSplit, SPLIT_TREE_KEY } from './split-binder.js';
49
49
  import { gridItemFromCell } from './grid-mapping.js';
50
50
  import { ensureDashboardKitStyles } from './styles.js';
51
- import { paintTabStrip, tabStripKey, tabStripReserve } from './tabs.js';
51
+ import { paintTabStrip, tabStripKey, tabStripReserve, tabPageInset } from './tabs.js';
52
52
  import { defaultWidgetRenderer } from './widgets.js';
53
53
  /**
54
54
  * Add a widget node AND its board membership as ONE undoable step.
@@ -457,8 +457,12 @@ export function attachTabsRuntime(ctx, model, container, handle) {
457
457
  active = pages[0].id;
458
458
  ctx.activeTab.set(id, active);
459
459
  const strip = tabStripReserve(ctx.tabsOf.get(id), pages.length);
460
+ // The pages sit INSET in the container's frame (0.4.43): below the strip
461
+ // and its inset, `inset` px in from the sides and the bottom, so their
462
+ // cards read as inside the panel rather than as loose cards under a strip.
463
+ const inset = tabPageInset(ctx.tabsOf.get(id));
460
464
  const f = { x: cg.position.x, y: cg.position.y, width: (_d = (_c = cg.size) === null || _c === void 0 ? void 0 : _c.width) !== null && _d !== void 0 ? _d : 0, height: (_f = (_e = cg.size) === null || _e === void 0 ? void 0 : _e.height) !== null && _f !== void 0 ? _f : 0 };
461
- const inner = { width: f.width, height: Math.max(0, f.height - strip) };
465
+ const inner = { width: Math.max(0, f.width - 2 * inset), height: Math.max(0, f.height - strip - 2 * inset) };
462
466
  const write = (fn) => (model.runSystemWrite ? model.runSystemWrite(fn) : fn());
463
467
  write(() => {
464
468
  for (const p of pages) {
@@ -467,7 +471,7 @@ export function attachTabsRuntime(ctx, model, container, handle) {
467
471
  continue;
468
472
  // A PARKED page keeps its size, so its own board keeps its layout and
469
473
  // comes back exactly as it was; only its x leaves the canvas.
470
- pg.setFrame({ x: p.id === active ? f.x : OFFSCREEN_X, y: f.y + strip, width: inner.width, height: inner.height });
474
+ pg.setFrame({ x: p.id === active ? f.x + inset : OFFSCREEN_X, y: f.y + strip + inset, width: inner.width, height: inner.height });
471
475
  }
472
476
  });
473
477
  for (const p of pages)
@@ -1049,7 +1053,9 @@ export function createDashboardHandle(ctx) {
1049
1053
  W = `${pageId}__group${n}`;
1050
1054
  const tabsOpts = (_d = ctx.tabsOf.get(containerId)) !== null && _d !== void 0 ? _d : {};
1051
1055
  const label = (_e = pageSpec.title) !== null && _e !== void 0 ? _e : pageId;
1052
- const size = { width: (_g = (_f = pg.size) === null || _f === void 0 ? void 0 : _f.width) !== null && _g !== void 0 ? _g : 0, height: ((_j = (_h = pg.size) === null || _h === void 0 ? void 0 : _h.height) !== null && _j !== void 0 ? _j : 0) + tabStripReserve(tabsOpts, 1) };
1056
+ // The new group is sized for the page PLUS its frame: the strip above, the inset around.
1057
+ const inset = tabPageInset(tabsOpts);
1058
+ const size = { width: ((_g = (_f = pg.size) === null || _f === void 0 ? void 0 : _f.width) !== null && _g !== void 0 ? _g : 0) + 2 * inset, height: ((_j = (_h = pg.size) === null || _h === void 0 ? void 0 : _h.height) !== null && _j !== void 0 ? _j : 0) + tabStripReserve(tabsOpts, 1) + 2 * inset };
1053
1059
  const remaining = [...((_k = from.members) !== null && _k !== void 0 ? _k : [])].filter((m) => m !== pageId && !!model.getGroup(m));
1054
1060
  // The page that was showing when the gesture began. Undo puts the tab
1055
1061
  // back — and shows THAT page again, not the one the container switched to
@@ -1196,10 +1196,10 @@ export function bindDashboardGrid(api, group, options = {}) {
1196
1196
  return null;
1197
1197
  };
1198
1198
  /** Which of a section frame's edges a world point is within EDGE_GRIP of. */
1199
- const slabEdgesNear = (grp, x, y) => {
1199
+ const slabEdgesNear = (grp, x, y, grip = edgeGripFor(grp)) => {
1200
1200
  const p = grp.position;
1201
1201
  const s = sizeOf(grp);
1202
- return { n: y - p.y <= EDGE_GRIP, s: p.y + s.height - y <= EDGE_GRIP, w: x - p.x <= EDGE_GRIP, e: p.x + s.width - x <= EDGE_GRIP };
1202
+ return { n: y - p.y <= grip, s: p.y + s.height - y <= grip, w: x - p.x <= grip, e: p.x + s.width - x <= grip };
1203
1203
  };
1204
1204
  /**
1205
1205
  * SECTION CHROME. A section (member group) paints no card of its own, so it
@@ -1211,9 +1211,121 @@ export function bindDashboardGrid(api, group, options = {}) {
1211
1211
  * corner handle; its frame edges answer the resize cursor.
1212
1212
  */
1213
1213
  const slabEls = new Map();
1214
+ /**
1215
+ * GROUP FRAME (0.4.43): a TAB CONTAINER wears a frame by default — its slab
1216
+ * is bordered and a tinted surface lies under its pages, first in the layer
1217
+ * so the tiles paint over it. A page torn out with two widgets under its tab
1218
+ * read as a strip floating over two loose cards: nothing said the second
1219
+ * card was the tab's. A plain section keeps its invisible slab.
1220
+ */
1221
+ const groupBgs = new Map();
1222
+ const syncGroupBg = (layer, id, on, x, y, w, h) => {
1223
+ var _a;
1224
+ let bg = (_a = groupBgs.get(id)) !== null && _a !== void 0 ? _a : null;
1225
+ if (!on) {
1226
+ bg === null || bg === void 0 ? void 0 : bg.remove();
1227
+ groupBgs.delete(id);
1228
+ return;
1229
+ }
1230
+ if (!bg || bg.parentElement !== layer) {
1231
+ bg === null || bg === void 0 ? void 0 : bg.remove();
1232
+ bg = document.createElement('div');
1233
+ bg.className = 'axdb-group-bg';
1234
+ bg.setAttribute('data-group-bg', id);
1235
+ layer.prepend(bg);
1236
+ groupBgs.set(id, bg);
1237
+ }
1238
+ bg.style.left = `${x}px`;
1239
+ bg.style.top = `${y}px`;
1240
+ bg.style.width = `${w}px`;
1241
+ bg.style.height = `${h}px`;
1242
+ };
1243
+ const isTabsGroup = (grp) => { var _a; return ((_a = grp.getMetadata('containerWidget')) === null || _a === void 0 ? void 0 : _a.layout) === 'tabs'; };
1244
+ /**
1245
+ * A TAB CONTAINER's frame is its drag handle (0.4.43): its 8-px margin —
1246
+ * under the strip, beside the pages — moves the group, so the edge-resize
1247
+ * zone shrinks to 3 px there (the corner handle still resizes). A section's
1248
+ * edges keep the full grip: its empty band is a drop target, not a handle.
1249
+ */
1250
+ const TAB_FRAME_GRIP = 3;
1251
+ const edgeGripFor = (grp) => (isTabsGroup(grp) ? TAB_FRAME_GRIP : EDGE_GRIP);
1252
+ /**
1253
+ * CARRIED (0.4.43): a group dragged by its strip or band moves as ONE thing.
1254
+ * The held TILE is transition-exempt (the ghost), but a group has no host of
1255
+ * its own: its strip jumped to the pointer while its pages' tiles GLIDED
1256
+ * after it — on the live demo the content trailed the strip by up to 140 px
1257
+ * at every step. Everything in the group's subtree — tiles, strips, slabs,
1258
+ * the surface — is exempt for the gesture and, like the ghost, through the
1259
+ * drop write; then the glides resume.
1260
+ */
1261
+ const carriedEls = new Set();
1262
+ let carriedTimer = null;
1263
+ const subtreeIds = (id) => {
1264
+ var _a;
1265
+ const groups = [];
1266
+ const nodes = [];
1267
+ const queue = [id];
1268
+ const seen = new Set();
1269
+ while (queue.length) {
1270
+ const cur = queue.shift();
1271
+ if (seen.has(cur))
1272
+ continue;
1273
+ seen.add(cur);
1274
+ const grp = diagram.getGroup(cur);
1275
+ if (grp) {
1276
+ groups.push(cur);
1277
+ for (const m of (_a = grp.members) !== null && _a !== void 0 ? _a : [])
1278
+ queue.push(m);
1279
+ }
1280
+ else if (diagram.getNode(cur))
1281
+ nodes.push(cur);
1282
+ }
1283
+ return { groups, nodes };
1284
+ };
1285
+ const cssId = (id) => (typeof CSS !== 'undefined' && CSS.escape ? CSS.escape(id) : id.replace(/"/g, '\\"'));
1286
+ const setCarried = (id, on) => {
1287
+ const layer = htmlLayer();
1288
+ if (!layer)
1289
+ return;
1290
+ if (carriedTimer) {
1291
+ clearTimeout(carriedTimer);
1292
+ carriedTimer = null;
1293
+ }
1294
+ if (on) {
1295
+ const { groups, nodes } = subtreeIds(id);
1296
+ const els = [];
1297
+ for (const n of nodes) {
1298
+ const h = hostOf(n);
1299
+ if (h)
1300
+ els.push(h);
1301
+ }
1302
+ for (const g of groups) {
1303
+ els.push(...Array.from(layer.querySelectorAll(`:scope > .axdb-tabs[data-tabs-id="${cssId(g)}"], :scope > .axdb-slab[data-slab-id="${cssId(g)}"], :scope > .axdb-group-bg[data-group-bg="${cssId(g)}"]`)));
1304
+ }
1305
+ for (const el of els) {
1306
+ el.classList.add('axdb-carried');
1307
+ carriedEls.add(el);
1308
+ }
1309
+ return;
1310
+ }
1311
+ carriedTimer = setTimeout(() => {
1312
+ for (const el of carriedEls)
1313
+ el.classList.remove('axdb-carried');
1314
+ carriedEls.clear();
1315
+ carriedTimer = null;
1316
+ }, 60);
1317
+ };
1318
+ const flushCarried = () => {
1319
+ if (carriedTimer)
1320
+ clearTimeout(carriedTimer);
1321
+ carriedTimer = null;
1322
+ for (const el of carriedEls)
1323
+ el.classList.remove('axdb-carried');
1324
+ carriedEls.clear();
1325
+ };
1214
1326
  let slabLayer = null;
1215
1327
  const syncSlabs = () => {
1216
- var _a, _b;
1328
+ var _a, _b, _c;
1217
1329
  if (disposed)
1218
1330
  return;
1219
1331
  const layer = (slabLayer === null || slabLayer === void 0 ? void 0 : slabLayer.isConnected) ? slabLayer : (slabLayer = htmlLayer());
@@ -1247,6 +1359,9 @@ export function bindDashboardGrid(api, group, options = {}) {
1247
1359
  el.classList.toggle('axdb-slab--selected', selectedId === id);
1248
1360
  el.classList.toggle('axdb-slab--static', isStatic);
1249
1361
  (_b = el.querySelector(':scope > .axdb-rs')) === null || _b === void 0 ? void 0 : _b.classList.toggle('axdb-rs--rtl', rtl);
1362
+ const tabs = isTabsGroup(grp);
1363
+ el.classList.toggle('axdb-slab--tabs', tabs);
1364
+ syncGroupBg(layer, id, tabs, p.x, p.y, sz.width, sz.height);
1250
1365
  syncCaption(el, id, grp, sz.height);
1251
1366
  }
1252
1367
  for (const [id, el] of slabEls) {
@@ -1254,6 +1369,8 @@ export function bindDashboardGrid(api, group, options = {}) {
1254
1369
  el.remove();
1255
1370
  hoverSlabs.delete(el);
1256
1371
  slabEls.delete(id);
1372
+ (_c = groupBgs.get(id)) === null || _c === void 0 ? void 0 : _c.remove();
1373
+ groupBgs.delete(id);
1257
1374
  }
1258
1375
  }
1259
1376
  };
@@ -1359,13 +1476,36 @@ export function bindDashboardGrid(api, group, options = {}) {
1359
1476
  api.container.style.cursor = cursorFor(edges);
1360
1477
  };
1361
1478
  /**
1362
- * MOVE a section by its caption band or its strip's empty space. A section
1363
- * is a LOCKED tile so that nothing pushes it; for the gesture's duration the
1364
- * tile is unlocked (it is the one moving), other sections stay locked and
1365
- * still refuse it (E4b), and every unlocked tile gets pushed the way a
1366
- * widget pushes it. Its children ride along: the frame moves, the nested
1367
- * board re-projects.
1479
+ * MOVE a section by its caption band, its strip's empty space or its frame
1480
+ * margin. A section is a LOCKED tile so that no WIDGET pushes it; for the
1481
+ * gesture's duration the moving tile is unlocked, and so are the OTHER
1482
+ * member groups (0.4.44): a moved section or group pushes the sections in
1483
+ * its way, the way a dock does. They used to stay locked against it too
1484
+ * (E4b), so the fluid demo's eight-row side panel, with the Operations
1485
+ * section spanning the row beneath it, had no legal column to its left —
1486
+ * the refusal tone at every cell, and the release moved nothing ("I can't
1487
+ * drag the tab group"). Every unlocked tile gets pushed the way a widget
1488
+ * pushes it; a group's children ride along: the frame moves, the nested
1489
+ * board re-projects. The others relock on release.
1368
1490
  */
1491
+ let slabUnlocked = [];
1492
+ const unlockOthersForSlab = (id) => {
1493
+ slabUnlocked = [];
1494
+ for (const o of engine.getItems()) {
1495
+ if (o.id !== id && o.locked && isGroupMember(o.id)) {
1496
+ o.locked = false;
1497
+ slabUnlocked.push(o.id);
1498
+ }
1499
+ }
1500
+ };
1501
+ const relockOthersForSlab = () => {
1502
+ for (const oid of slabUnlocked) {
1503
+ const o = engine.getItem(oid);
1504
+ if (o)
1505
+ o.locked = true;
1506
+ }
1507
+ slabUnlocked = [];
1508
+ };
1369
1509
  const beginSlabMove = (id, ev) => {
1370
1510
  const grp = diagram.getGroup(id);
1371
1511
  const it = engine.getItem(id);
@@ -1374,6 +1514,7 @@ export function bindDashboardGrid(api, group, options = {}) {
1374
1514
  engine.beginGesture();
1375
1515
  const snap = snapshotAll();
1376
1516
  it.locked = false;
1517
+ unlockOthersForSlab(id);
1377
1518
  slabGesture = {
1378
1519
  id,
1379
1520
  edges: NO_EDGES,
@@ -1427,6 +1568,8 @@ export function bindDashboardGrid(api, group, options = {}) {
1427
1568
  return;
1428
1569
  g.started = true;
1429
1570
  armGlide();
1571
+ if (g.move)
1572
+ setCarried(g.id, true);
1430
1573
  }
1431
1574
  const it = engine.getItem(g.id);
1432
1575
  if (!it)
@@ -1447,8 +1590,10 @@ export function bindDashboardGrid(api, group, options = {}) {
1447
1590
  // column (the row search) read as the wrong tile moving.
1448
1591
  const was = { x: it.x, y: it.y };
1449
1592
  const moved = engine.moveCheck(g.id, cell.x, cell.y, { gate: false }).changed || placeOnRow(g.id, cell.x, cell.y, it.w);
1450
- if (moved)
1593
+ if (moved) {
1451
1594
  project();
1595
+ setCarried(g.id, true); // chrome repainted by the projection is carried too
1596
+ }
1452
1597
  // STUCK: the pointer asks for another cell and the slab did not budge
1453
1598
  // (placeOnRow counts "already on a legal cell" as placed) — paint what
1454
1599
  // was asked for as refused.
@@ -1507,6 +1652,9 @@ export function bindDashboardGrid(api, group, options = {}) {
1507
1652
  releasePointer(g.pointerId);
1508
1653
  api.container.style.cursor = '';
1509
1654
  relockSlab(g.id);
1655
+ relockOthersForSlab();
1656
+ if (g.move && g.started)
1657
+ setCarried(g.id, false); // exempt through the drop write, then the glides resume
1510
1658
  if (!g.started) {
1511
1659
  engine.endGesture();
1512
1660
  return;
@@ -1515,7 +1663,20 @@ export function bindDashboardGrid(api, group, options = {}) {
1515
1663
  project();
1516
1664
  const it = engine.getItem(g.id);
1517
1665
  const grp = diagram.getGroup(g.id);
1518
- const commands = buildCommitCommands(deltasSince(g.startCells, g.startGeom, g.id));
1666
+ const deltas = deltasSince(g.startCells, g.startGeom, g.id);
1667
+ const commands = buildCommitCommands(deltas);
1668
+ // The sections and groups the move PUSHED (0.4.44): the tile commit skips
1669
+ // groups by design (a group's cell is its own command), so they are
1670
+ // committed here the way a dock commits the groups it displaced — or the
1671
+ // 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
+ }
1519
1680
  const b = g.cellBefore;
1520
1681
  if (it && grp && (b.x !== it.x || b.y !== it.y || b.w !== it.w || b.h !== it.h)) {
1521
1682
  commands.push(new SetGroupCellCommand(g.id, b, { x: it.x, y: it.y, w: it.w, h: it.h }, g.frameBefore, frameOfGroup(grp)));
@@ -1535,6 +1696,9 @@ export function bindDashboardGrid(api, group, options = {}) {
1535
1696
  releasePointer(g.pointerId);
1536
1697
  api.container.style.cursor = '';
1537
1698
  relockSlab(g.id);
1699
+ relockOthersForSlab();
1700
+ if (g.move && g.started)
1701
+ setCarried(g.id, false);
1538
1702
  if (g.started)
1539
1703
  engine.cancelGesture();
1540
1704
  else
@@ -2914,7 +3078,11 @@ export function bindDashboardGrid(api, group, options = {}) {
2914
3078
  beginSlabResize(slabId, edges, ev);
2915
3079
  // The caption band is the section's handle: pressed and travelled,
2916
3080
  // it moves the whole section (its inner empty space only selects).
2917
- else if (ownCaption)
3081
+ // A TAB CONTAINER's frame is its handle (0.4.43): the margin around
3082
+ // its pages moves the group — the strip's empty space, the only
3083
+ // handle before, was one nobody found ("I'm not able to drag an
3084
+ // entire tab group").
3085
+ else if (ownCaption || isTabsGroup(grp))
2918
3086
  beginSlabMove(slabId, ev);
2919
3087
  return;
2920
3088
  }
@@ -4417,10 +4585,14 @@ export function bindDashboardGrid(api, group, options = {}) {
4417
4585
  el.remove();
4418
4586
  slabEls.clear();
4419
4587
  hoverSlabs.clear();
4588
+ for (const bg of groupBgs.values())
4589
+ bg.remove();
4590
+ groupBgs.clear();
4420
4591
  // A rebind inside the 60 ms window (a layout switch right after an undo)
4421
4592
  // disposed this binder with the timer pending — the host kept its
4422
4593
  // lifted ghost for good (visual gate, nested-containers ⑤).
4423
4594
  flushGhost();
4595
+ flushCarried();
4424
4596
  (_d = htmlLayer()) === null || _d === void 0 ? void 0 : _d.classList.remove('axdb-glide');
4425
4597
  api.container.style.cursor = '';
4426
4598
  },
@@ -264,6 +264,43 @@ export function bindDashboardSplit(api, group, options = {}) {
264
264
  // resizes a pane); the band is the section's drag handle (beginMemberDrag).
265
265
  const slabEls = new Map();
266
266
  const hoverSlabs = new Set();
267
+ /** The member group (a pane's section or tab container) whose frame holds a world point. */
268
+ const memberGroupAt = (x, y) => {
269
+ var _a, _b;
270
+ for (const id of (_a = group.members) !== null && _a !== void 0 ? _a : new Set()) {
271
+ const grp = diagram.getGroup(id);
272
+ if (!grp || diagram.getNode(id))
273
+ continue;
274
+ const p = grp.position;
275
+ const sz = (_b = grp.size) !== null && _b !== void 0 ? _b : { width: 0, height: 0 };
276
+ if (x >= p.x && x <= p.x + sz.width && y >= p.y && y <= p.y + sz.height)
277
+ return id;
278
+ }
279
+ return null;
280
+ };
281
+ /** GROUP FRAME (0.4.43): a tab container's tinted surface, under its pages — see the grid binder. */
282
+ const groupBgs = new Map();
283
+ const syncGroupBg = (layer, id, on, x, y, w, h) => {
284
+ var _a;
285
+ let bg = (_a = groupBgs.get(id)) !== null && _a !== void 0 ? _a : null;
286
+ if (!on) {
287
+ bg === null || bg === void 0 ? void 0 : bg.remove();
288
+ groupBgs.delete(id);
289
+ return;
290
+ }
291
+ if (!bg || bg.parentElement !== layer) {
292
+ bg === null || bg === void 0 ? void 0 : bg.remove();
293
+ bg = document.createElement('div');
294
+ bg.className = 'axdb-group-bg';
295
+ bg.setAttribute('data-group-bg', id);
296
+ layer.prepend(bg);
297
+ groupBgs.set(id, bg);
298
+ }
299
+ bg.style.left = `${x}px`;
300
+ bg.style.top = `${y}px`;
301
+ bg.style.width = `${w}px`;
302
+ bg.style.height = `${h}px`;
303
+ };
267
304
  const syncCaption = (el, id, grp, sectionH) => {
268
305
  const cap = captionOfGroup(grp);
269
306
  let band = el.querySelector(':scope > .axdb-slab-h');
@@ -303,7 +340,7 @@ export function bindDashboardSplit(api, group, options = {}) {
303
340
  }
304
341
  };
305
342
  const syncSlabs = () => {
306
- var _a, _b;
343
+ var _a, _b, _c, _d;
307
344
  if (disposed)
308
345
  return;
309
346
  const layer = htmlLayer();
@@ -332,6 +369,9 @@ export function bindDashboardSplit(api, group, options = {}) {
332
369
  el.style.height = `${sz.height}px`;
333
370
  el.classList.toggle('axdb-slab--selected', selectedId === id);
334
371
  el.classList.toggle('axdb-slab--static', isStatic);
372
+ const tabs = ((_c = grp.getMetadata('containerWidget')) === null || _c === void 0 ? void 0 : _c.layout) === 'tabs';
373
+ el.classList.toggle('axdb-slab--tabs', tabs);
374
+ syncGroupBg(layer, id, tabs, p.x, p.y, sz.width, sz.height);
335
375
  syncCaption(el, id, grp, sz.height);
336
376
  }
337
377
  for (const [id, el] of slabEls) {
@@ -339,6 +379,8 @@ export function bindDashboardSplit(api, group, options = {}) {
339
379
  el.remove();
340
380
  hoverSlabs.delete(el);
341
381
  slabEls.delete(id);
382
+ (_d = groupBgs.get(id)) === null || _d === void 0 ? void 0 : _d.remove();
383
+ groupBgs.delete(id);
342
384
  }
343
385
  }
344
386
  };
@@ -767,7 +809,7 @@ export function bindDashboardSplit(api, group, options = {}) {
767
809
  return worldInsideBoard(ev.world.x, ev.world.y);
768
810
  },
769
811
  onPointerDown(ev, hit) {
770
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r;
812
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s;
771
813
  if (gesture)
772
814
  return;
773
815
  const target = ((_b = (_a = ev.source) === null || _a === void 0 ? void 0 : _a.target) !== null && _b !== void 0 ? _b : null);
@@ -817,13 +859,30 @@ export function bindDashboardSplit(api, group, options = {}) {
817
859
  return;
818
860
  }
819
861
  if ((!hit.node && !onGrip) || sectionHandle) {
862
+ // A press inside a MEMBER's frame — a tab container's margin under
863
+ // its strip or beside its pages, a section's empty band — selects
864
+ // that member; a tab container's frame is its handle (0.4.43) and
865
+ // drags it the way its strip's empty space does.
866
+ if (!sectionHandle) {
867
+ const member = memberGroupAt(ev.world.x, ev.world.y);
868
+ const mg = member ? diagram.getGroup(member) : undefined;
869
+ if (member && mg) {
870
+ selectWidget(member);
871
+ api.render();
872
+ const srcM = ev.source;
873
+ const tabsM = ((_m = mg.getMetadata('containerWidget')) === null || _m === void 0 ? void 0 : _m.layout) === 'tabs';
874
+ if (!isStatic && tabsM && typeof (srcM === null || srcM === void 0 ? void 0 : srcM.clientX) === 'number' && typeof (srcM === null || srcM === void 0 ? void 0 : srcM.clientY) === 'number')
875
+ beginMemberDrag(member, srcM);
876
+ return;
877
+ }
878
+ }
820
879
  // OUR OWN empty band or corner handle, and we are a section of a
821
880
  // parent board: select the section there; an edge or the handle
822
881
  // starts the section resize, which the parent runs while this tool
823
882
  // forwards the pointer sequence (the grid binder does the same).
824
883
  const parent = parentPeerOf(api.container, group.id);
825
884
  if ((parent === null || parent === void 0 ? void 0 : parent.selectMember) && (sectionHandle || worldInsideBoard(ev.world.x, ev.world.y))) {
826
- const own = ((_m = sectionHandle === null || sectionHandle === void 0 ? void 0 : sectionHandle.parentElement) === null || _m === void 0 ? void 0 : _m.getAttribute('data-slab-id')) === group.id;
885
+ const own = ((_o = sectionHandle === null || sectionHandle === void 0 ? void 0 : sectionHandle.parentElement) === null || _o === void 0 ? void 0 : _o.getAttribute('data-slab-id')) === group.id;
827
886
  parent.selectMember(group.id);
828
887
  if (!isStatic && parent.beginSlabResize) {
829
888
  const f = frame();
@@ -835,7 +894,7 @@ export function bindDashboardSplit(api, group, options = {}) {
835
894
  }
836
895
  return;
837
896
  }
838
- (_p = (_o = diagram).clearSelection) === null || _p === void 0 ? void 0 : _p.call(_o);
897
+ (_q = (_p = diagram).clearSelection) === null || _q === void 0 ? void 0 : _q.call(_p);
839
898
  selectWidget(undefined);
840
899
  api.render();
841
900
  return;
@@ -844,9 +903,9 @@ export function bindDashboardSplit(api, group, options = {}) {
844
903
  if (!node)
845
904
  return;
846
905
  selectWidget(node.id); // a press selects, whether or not it starts a gesture
847
- if (((_q = node.state) === null || _q === void 0 ? void 0 : _q.locked) === true || isStatic)
906
+ if (((_r = node.state) === null || _r === void 0 ? void 0 : _r.locked) === true || isStatic)
848
907
  return;
849
- if (((_r = node.getMetadata) === null || _r === void 0 ? void 0 : _r.call(node, 'widgetMovable')) === false)
908
+ if (((_s = node.getMetadata) === null || _s === void 0 ? void 0 : _s.call(node, 'widgetMovable')) === false)
850
909
  return;
851
910
  // Drag-handle mode: only the handle (the caption strip, a custom element
852
911
  // or the painted grip) lifts a widget out — see pressOnDragHandle.
@@ -1665,6 +1724,9 @@ export function bindDashboardSplit(api, group, options = {}) {
1665
1724
  api.container.removeEventListener('pointerleave', onHoverLeave);
1666
1725
  for (const el of slabEls.values())
1667
1726
  el.remove();
1727
+ for (const bg of groupBgs.values())
1728
+ bg.remove();
1729
+ groupBgs.clear();
1668
1730
  slabEls.clear();
1669
1731
  hoverSlabs.clear();
1670
1732
  insertion === null || insertion === void 0 ? void 0 : insertion.remove();
@@ -22,6 +22,6 @@
22
22
  */
23
23
  export declare const DASHBOARD_KIT_STYLE_ID = "grafloria-dashboard-kit-styles";
24
24
  /** The kit stylesheet as text — for a host that bundles its own CSS, and for the styling spec. */
25
- export declare const DASHBOARD_KIT_CSS = "\n/* ===== reflow glide (armed on the html layer for the whole gesture) ===== */\n.grafloria-html-layer.axdb-glide > .grafloria-node-host {\n transition: left .28s cubic-bezier(.2, 0, .2, 1), top .28s cubic-bezier(.2, 0, .2, 1),\n width .28s cubic-bezier(.2, 0, .2, 1), height .28s cubic-bezier(.2, 0, .2, 1);\n}\n\n/* Motion is a preference (WCAG 2.3.3): no glide for those who asked for none. */\n@media (prefers-reduced-motion: reduce) {\n .grafloria-html-layer.axdb-glide > .grafloria-node-host { transition: none; }\n}\n\n/* ===== keyboard focus: the roving tab stop shows where it is (WCAG 2.4.7) ===== */\n.grafloria-html-layer > .grafloria-node-host:focus-visible {\n outline: 2px solid var(--axdb-accent, #3b52d9);\n outline-offset: 2px;\n border-radius: var(--axdb-rs-radius, 8px);\n}\n\n/* ===== the held tile: transition-exempt ghost, above everything ===== */\n.grafloria-html-layer > .grafloria-node-host.axdb-ghost,\n.grafloria-html-layer.axdb-glide > .grafloria-node-host.axdb-ghost {\n transition: none;\n z-index: 30;\n opacity: .85;\n cursor: grabbing;\n filter: drop-shadow(0 10px 16px rgba(16, 24, 40, .3));\n}\n/* Outside the board: release will REMOVE \u2014 dim the ghost to say so. */\n.grafloria-html-layer > .grafloria-node-host.axdb-ghost.axdb-out { opacity: .35; filter: grayscale(.6); }\n\n/* ===== the placeholder: dashed slab, truthful, never animated ===== */\n.grafloria-html-layer > .axdb-ph {\n position: absolute;\n border-radius: var(--axdb-rs-radius, 8px);\n background: rgba(30, 34, 45, .14);\n border: 2px dashed rgba(30, 34, 45, .28);\n box-sizing: border-box;\n pointer-events: none;\n z-index: 0;\n transition: none;\n}\n/* A slab move asked for a cell it cannot have (a locked section in the way): the wanted cell in the danger tone. */\n.grafloria-html-layer > .axdb-ph.axdb-ph--no { border-color: var(--axdb-danger, #b3123c); background: var(--axdb-danger-soft, rgba(179, 18, 60, .07)); }\n\n@media (prefers-color-scheme: dark) {\n .grafloria-html-layer > .axdb-ph { background: rgba(220, 225, 240, .12); border-color: rgba(220, 225, 240, .3); }\n}\n\n/* ===== corner resize handle (hover-revealed, radius-matched) ===== */\n:is(.grafloria-node-host, .axdb-slab) > .axdb-rs {\n position: absolute;\n right: 0;\n bottom: 0;\n width: 24px; /* WCAG 2.5.8: a 24-px minimum target (was 18) */\n height: 24px;\n cursor: nwse-resize;\n border-right: 3px solid rgba(120, 130, 148, .55);\n border-bottom: 3px solid rgba(120, 130, 148, .55);\n border-bottom-right-radius: var(--axdb-rs-radius, 8px);\n opacity: 0;\n transition: opacity .12s;\n z-index: 5;\n}\n.grafloria-node-host:hover > .axdb-rs { opacity: 1; }\n/* A finger never hovers: on touch devices the corner handle is always visible\n (review D8 \u2014 hover-only meant no resize at all on a tablet). */\n@media (hover: none) { .grafloria-node-host > .axdb-rs { opacity: .8; } }\n.grafloria-node-host > .axdb-rs:hover { border-color: #3b52d9; }\n/* RTL boards grow leftwards, so the grab corner mirrors with them. */\n:is(.grafloria-node-host, .axdb-slab) > .axdb-rs.axdb-rs--rtl {\n right: auto;\n left: 0;\n cursor: nesw-resize;\n border-right: none;\n border-left: 3px solid rgba(120, 130, 148, .55);\n border-bottom-right-radius: 0;\n border-bottom-left-radius: var(--axdb-rs-radius, 8px);\n}\n\n/* ===== palette drag-in chip (screen-space clone following the cursor) ===== */\n.axdb-drag-chip {\n position: fixed;\n z-index: 9999;\n pointer-events: none;\n opacity: .9;\n filter: drop-shadow(0 8px 14px rgba(16, 24, 40, .3));\n transition: opacity .12s, filter .12s;\n}\n/* A bounded board with no room refuses the entry: the chip dims to say so\n (the same signal a tile ghost gives outside the board). Without this rule\n the class was set and nothing showed \u2014 a refusal a user could not see. */\n.axdb-drag-chip.axdb-out { opacity: .35; filter: grayscale(.7) drop-shadow(0 8px 14px rgba(16, 24, 40, .2)); }\n/* A tab dragged off its strip: VS Code carries the TAB, not the editor, so\n what follows the pointer is a chip wearing the tab's own label. */\n.axdb-tab-chip {\n padding: 5px 12px;\n max-width: 220px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font: 500 12px/1.4 var(--axdb-font, system-ui, -apple-system, \"Segoe UI\", Roboto, sans-serif);\n color: var(--axdb-tabs-on-fg, #1f2430);\n background: var(--axdb-tabs-on-bg, #fff);\n border-radius: var(--axdb-tab-radius, 6px);\n box-shadow: 0 1px 2px rgba(16, 24, 40, .1), 0 0 0 1px rgba(31, 36, 48, .08);\n}\n@media (prefers-color-scheme: dark) {\n .axdb-tab-chip {\n color: var(--axdb-tabs-on-fg, #eceef4);\n background: var(--axdb-tabs-on-bg, #232836);\n box-shadow: 0 1px 2px rgba(0, 0, 0, .45), 0 0 0 1px rgba(236, 238, 244, .12);\n }\n}\n/* A tab held over ANOTHER group: it will join that group, not the board. The\n group's frame lights up, and its strip marks the slot the tab will take \u2014\n a bar before the tab it lands in front of, or after the last one. */\n.grafloria-html-layer > .axdb-join {\n position: absolute;\n z-index: 3;\n pointer-events: none;\n box-sizing: border-box;\n border: 2px dashed var(--axdb-accent, #3b52d9);\n background: var(--axdb-accent-soft, rgba(59, 82, 217, .08));\n border-radius: var(--axdb-rs-radius, 8px);\n}\n.axdb-tabs.axdb-tabs--drop { box-shadow: inset 0 -2px 0 var(--axdb-accent, #3b52d9); }\n.axdb-tab.axdb-tab--drop-before { position: relative; }\n.axdb-tab.axdb-tab--drop-before::before {\n content: \"\"; position: absolute; left: -3px; top: 4px; bottom: 4px; width: 3px;\n border-radius: 2px; background: var(--axdb-accent, #3b52d9);\n}\n.axdb-tabs.axdb-tabs--drop-end::after {\n content: \"\"; display: inline-block; width: 3px; height: 18px; margin: 0 4px;\n border-radius: 2px; background: var(--axdb-accent, #3b52d9); vertical-align: middle; align-self: center;\n}\n\n/* ===========================================================================\n BUILT-IN WIDGET CARDS \u2014 what widgets.ts paints when a page writes no\n renderWidget. Class-prefixed (never element selectors), so this can only\n ever style DOM the kit itself produced. Colours are CSS variables on the\n card, so a page re-skins every built-in widget by setting them once; the\n corner radius follows --axdb-rs-radius, which is also the placeholder's and\n the resize handle's, so the three can never drift apart.\n =========================================================================== */\n.axdb-widget {\n --axdb-ink: #1f2430;\n /* 5.95:1 on the card \u2014 WCAG 1.4.3 for the 9\u201311px captions this paints\n (the #7a8496 it replaced sat at 3.77:1, axe-core's first finding). */\n --axdb-muted: #5a6478;\n --axdb-grid: rgba(120, 130, 148, .22);\n --axdb-card: #fff;\n --axdb-line: #e5e8ef;\n --axdb-soft: rgba(120, 130, 148, .14);\n --axdb-up: #0f7a3d; /* 5.42:1 */\n --axdb-down: #be123c; /* 6.29:1 */\n /* The categorical palette, as tokens so the dark card can carry its own\n steps: every entry clears 3:1 against its card (WCAG 1.4.11). */\n --axdb-c1: #3b52d9; --axdb-c2: #0369a1; --axdb-c3: #0f766e;\n --axdb-c4: #b45309; --axdb-c5: #6d28d9; --axdb-c6: #475569;\n /* The header band: a panel wears its title in a band across its top, the\n way DevExpress, Grafana and Metabase panels do \u2014 tinted ground, hairline\n below, sentence case (0.4.36; the tracked uppercase micro-label before). */\n --axdb-head-bg: #f7f8fb;\n --axdb-head-fg: #2a3140;\n box-sizing: border-box;\n width: 100%;\n height: 100%;\n display: flex;\n flex-direction: column;\n overflow: hidden;\n padding: 0;\n background: var(--axdb-card);\n border: 1px solid var(--axdb-line);\n border-radius: var(--axdb-rs-radius, 8px);\n box-shadow: 0 1px 2px rgba(16, 24, 40, .04);\n font-family: system-ui, -apple-system, \"Segoe UI\", sans-serif;\n color: var(--axdb-ink);\n}\n/* The host is the card's size container (named, so a widget's inner parts can\n ask the TILE's size as well as their own body's), so the card can shed its\n padding as the tile gets short instead of clipping its own content. */\n.grafloria-html-layer > .grafloria-node-host { container: axdb-tile / size; }\n/* Two class hops on the header: these blocks precede the header's own rule\n below and would lose the cascade at equal specificity \u2014 at 54 px the margin\n stayed 8 px, the body shrank to 14 px and the KPI figure hid. */\n@container axdb-tile (max-height: 90px) {\n .axdb-widget > .axdb-widget-h { padding: 5px 12px; font-size: 11.5px; }\n .axdb-widget > .axdb-widget-b { padding: 6px 12px; }\n}\n/* The row floor: the band gives way to an inline label beside the figure. */\n@container axdb-tile (max-height: 46px) {\n .axdb-widget > .axdb-widget-h { padding: 0 0 0 12px; background: none; border-bottom: 0; }\n .axdb-widget > .axdb-widget-b { padding: 3px 12px; }\n}\n@container axdb-tile (max-height: 26px) {\n .axdb-widget > .axdb-widget-h { display: none; }\n .axdb-widget > .axdb-widget-b { padding: 1px 12px; }\n}\n.axdb-widget-h {\n display: flex;\n align-items: center;\n gap: 8px;\n flex: none;\n min-width: 0;\n padding: 9px 14px;\n background: var(--axdb-head-bg);\n border-bottom: 1px solid var(--axdb-line);\n font: 600 12.5px/1.3 system-ui, -apple-system, \"Segoe UI\", sans-serif;\n letter-spacing: -.005em;\n color: var(--axdb-head-fg);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n/* Drag-handle mode (DevExpress drags an item by its caption): the header is\n the grip and says so with a dot pattern and a grab cursor; the body keeps\n its own cursor because a press there starts nothing. */\n.axdb-drag-handle .axdb-widget > .axdb-widget-h { cursor: grab; }\n.axdb-drag-handle .axdb-widget > .axdb-widget-h::before {\n content: ''; flex: none; width: 8px; height: 12px; opacity: .55;\n background: radial-gradient(circle, currentColor 1.1px, transparent 1.5px) 0 0 / 4px 4px;\n}\n/* A PAINTED GRIP (dragHandle: { grip: true }): the only drag zone, a small\n dotted tab along the card's top edge. Inside sits in the header band and\n the header makes room for it; outside is a tab above the card, the\n DevExpress item bar. The host must not clip it. */\n.grafloria-node-host > .axdb-grip {\n position: absolute; z-index: 4; box-sizing: border-box; width: 24px; height: 12px;\n border: 1px solid var(--axdb-line, #e7eaf1); border-radius: 3px; background: var(--axdb-card, #fff);\n color: var(--axdb-muted, #5a6478); cursor: grab;\n /* Shown on the SELECTED widget only (the DevExpress designer shows an item's\n bar on the selected item): a press or keyboard focus selects, a void\n click clears. Hidden, it takes no pointer either. */\n opacity: 0; pointer-events: none; transition: opacity .12s ease;\n}\n.grafloria-node-host.axdb-selected > .axdb-grip, .grafloria-node-host:focus-within > .axdb-grip { opacity: 1; pointer-events: auto; }\n.grafloria-node-host > .axdb-grip::before {\n content: ''; position: absolute; left: 5px; top: 2px; width: 12px; height: 6px;\n background: radial-gradient(circle, currentColor 1px, transparent 1.4px) 0 0 / 4px 3px;\n}\n.grafloria-node-host > .axdb-grip:hover { border-color: var(--axdb-accent, #3b52d9); color: var(--axdb-accent, #3b52d9); }\n/* The selected card says so, quietly. --axdb-accent themes the grip's hover, the\n ring and the focus outline together; --axdb-accent-ring the ring alone. */\n.grafloria-node-host.axdb-selected > .axdb-widget { box-shadow: 0 0 0 1.5px var(--axdb-accent-ring, rgba(59, 82, 217, .55)), 0 1px 2px rgba(16, 24, 40, .05), 0 1px 3px rgba(16, 24, 40, .05); }\n/* While a resize edge is near, the host and everything in it (a chart canvas\n with its own cursor included) show the edge's cursor. */\n.grafloria-node-host[data-axdb-edge=\"ns-resize\"], .grafloria-node-host[data-axdb-edge=\"ns-resize\"] * { cursor: ns-resize !important; }\n.grafloria-node-host[data-axdb-edge=\"ew-resize\"], .grafloria-node-host[data-axdb-edge=\"ew-resize\"] * { cursor: ew-resize !important; }\n.grafloria-node-host[data-axdb-edge=\"nwse-resize\"], .grafloria-node-host[data-axdb-edge=\"nwse-resize\"] * { cursor: nwse-resize !important; }\n.grafloria-node-host[data-axdb-edge=\"nesw-resize\"], .grafloria-node-host[data-axdb-edge=\"nesw-resize\"] * { cursor: nesw-resize !important; }\n/* INSIDE: centred on the header's text line and flush with the card padding,\n per size tier (padding 13/15, then 8/14, 4/12, 2/12). */\n.grafloria-node-host > .axdb-grip--inside { top: 12px; }\n.grafloria-node-host > .axdb-grip--inside.axdb-grip--left { left: 15px; }\n.grafloria-node-host > .axdb-grip--inside.axdb-grip--right { right: 15px; }\n.grafloria-node-host > .axdb-grip--inside.axdb-grip--center { top: 3px; }\n@container axdb-tile (max-height: 90px) {\n .grafloria-node-host > .axdb-grip--inside { top: 9px; }\n .grafloria-node-host > .axdb-grip--inside.axdb-grip--left { left: 14px; }\n .grafloria-node-host > .axdb-grip--inside.axdb-grip--right { right: 14px; }\n .grafloria-node-host > .axdb-grip--inside.axdb-grip--center { top: 2px; }\n .grafloria-node-host.axdb-gp-inside.axdb-gp-left .axdb-widget > .axdb-widget-h { padding-left: 46px; }\n .grafloria-node-host.axdb-gp-inside.axdb-gp-right .axdb-widget > .axdb-widget-h { padding-right: 46px; }\n .grafloria-node-host.axdb-gp-inside.axdb-gp-center .axdb-widget > .axdb-widget-h { padding-top: 15px; }\n}\n@container axdb-tile (max-height: 46px) {\n .grafloria-node-host > .axdb-grip--inside { top: 5px; }\n .grafloria-node-host > .axdb-grip--inside.axdb-grip--left { left: 12px; }\n .grafloria-node-host > .axdb-grip--inside.axdb-grip--right { right: 12px; }\n .grafloria-node-host > .axdb-grip--inside.axdb-grip--center { top: 1px; }\n .grafloria-node-host.axdb-gp-inside.axdb-gp-left .axdb-widget > .axdb-widget-h { padding-left: 44px; }\n .grafloria-node-host.axdb-gp-inside.axdb-gp-right .axdb-widget > .axdb-widget-h { padding-right: 44px; }\n .grafloria-node-host.axdb-gp-inside.axdb-gp-center .axdb-widget > .axdb-widget-h { padding-top: 13px; }\n}\n@container axdb-tile (max-height: 26px) {\n .grafloria-node-host > .axdb-grip--inside { top: 2px; }\n}\n/* OUTSIDE: a tab on the card's top edge, its corners in line with the card's. */\n/* The OUTSIDE tab lives in the gap between tiles: never taller than the gap\n less a pixel (an 11-px tab in a 10-px gap sat on the tile above), never\n shorter than 6 px so the dots still read. */\n.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); }\n.grafloria-node-host > .axdb-grip--outside.axdb-grip--left { left: 10px; }\n.grafloria-node-host > .axdb-grip--outside.axdb-grip--right { right: 10px; }\n.grafloria-node-host > .axdb-grip--center { left: 50%; transform: translateX(-50%); }\n/* The header makes room for an inside grip on its side \u2014 inset + the 24-px\n grip + an 8-px gap, per tier (32 px under the band's 14-px inset hid the\n first letter of the title) \u2014 and a centred one sits above the text. */\n.grafloria-node-host.axdb-gp-inside.axdb-gp-left .axdb-widget > .axdb-widget-h { padding-left: 47px; }\n.grafloria-node-host.axdb-gp-inside.axdb-gp-right .axdb-widget > .axdb-widget-h { padding-right: 47px; }\n.grafloria-node-host.axdb-gp-inside.axdb-gp-center .axdb-widget > .axdb-widget-h { padding-top: 17px; }\n.axdb-widget-b { flex: 1; min-height: 0; position: relative; padding: 12px 14px; }\n/* A drag across a STATIC board (nothing prevents the press's default there)\n used to select every label on it; kit cards are not prose. Tables stay\n copyable \u2014 a figure in a grid is the one thing a viewer selects. */\n.axdb-widget { user-select: none; -webkit-user-select: none; }\n.axdb-widget .axdb-table { user-select: text; -webkit-user-select: text; }\n.axdb-widget-b > svg { display: block; width: 100%; height: 100%; }\n.axdb-widget-b.axdb-scroll { overflow: auto; }\n/* A chart WITH a legend under it: the plot yields height, the legend keeps its\n own (without this the 100%-tall svg pushes the legend out of the card). */\n.axdb-widget-b.axdb-has-lg { display: flex; flex-direction: column; }\n.axdb-widget-b.axdb-has-lg > svg { flex: 1; height: auto; min-height: 0; }\n/* The data behind a chart, for readers that cannot see the chart: present in\n the accessibility tree, absent from the picture (WCAG 1.1.1). */\n.axdb-sr {\n position: absolute; top: 0; left: 0; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden;\n clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; border: 0;\n}\n.axdb-widget-empty {\n display: flex; align-items: center; justify-content: center; height: 100%;\n font: 500 11.5px/1.3 system-ui, sans-serif; color: var(--axdb-muted);\n}\n\n/* kpi: value + delta stack, the spark yields its height before they do */\n/* A SIZE CONTAINER, so the card's type scales with the tile instead of\n clipping. Fit-mode shrinks tiles when rows are added (dropping a chart above\n the KPI row took these from 122px to 85px) and fixed 30px/12px lines were\n sliced mid-glyph by the card's overflow \u2014 the audited \"trimmed\" widgets.\n cqh = 1% of the body's own height, clamped so full-size tiles look exactly\n as before and short tiles compress instead of cutting. */\n.axdb-widget-b.axdb-kpi { display: flex; flex-direction: column; container: axdb-kpi / size; }\n/* STEP DOWN, NEVER CLIP. As the body gets shorter the sparkline goes first, then the\n delta, then the value \u2014 the header alone at the row floor. Before this a 99-px row\n drew the sparkline as a 13-px sliver and a 28-px row cut the value mid-glyph (the\n fluid board, after a drag pushed the KPI row down). Thresholds are body heights. */\n/* Two class hops, so these outrank \".axdb-widget-b > svg { display: block }\" \u2014\n a bare .axdb-kpi-s lost that cascade and the sparkline stayed. */\n@container (max-height: 78px) { .axdb-kpi > .axdb-kpi-s { display: none; } }\n@container (max-height: 40px) { .axdb-kpi > .axdb-kpi-d { display: none; } }\n@container (max-height: 16px) { .axdb-kpi > .axdb-kpi-v { display: none; } }\n.axdb-kpi-v { font: 700 clamp(15px, 44cqh, 30px)/1.05 system-ui, sans-serif; letter-spacing: -.02em; color: var(--axdb-ink); white-space: nowrap; }\n.axdb-kpi-d { margin-top: clamp(1px, 5cqh, 6px); font: 600 clamp(9px, 19cqh, 12px)/1.2 system-ui, sans-serif; white-space: nowrap; }\n.axdb-kpi-d span { color: var(--axdb-muted); font-weight: 500; }\n.axdb-kpi-d.up { color: var(--axdb-up); }\n.axdb-kpi-d.down { color: var(--axdb-down); }\n/* Grows into a tall tile (a 3-row KPI is not a number over a void) and never\n takes more than two fifths of the body. */\n.axdb-kpi-s { margin-top: auto; height: auto; min-height: 0; flex: 1 1 34px; max-height: 40%; }\n/* SHORT AND WIDE \u2014 a one-row KPI on a fluid board. Stacked, a short tile dropped\n its sparkline and left a small figure in the corner of an empty card. Here the\n figure, its delta and the sparkline sit in a row and the spark fills the width.\n The outer query asks the TILE (host), the inner one the body, so a strip too\n thin for a readable spark (under 24 px) keeps the figure alone. 340 px is what\n a figure, its delta, the gaps and a spark worth reading need \u2014 the builder's\n 236-px tiles got a 40-px wedge of spark beside the delta. */\n@container axdb-tile (max-height: 125px) and (min-width: 340px) {\n .axdb-widget--kpi > .axdb-kpi { flex-direction: row; align-items: center; gap: 14px; }\n .axdb-widget--kpi .axdb-kpi-d { margin-top: 0; }\n @container axdb-kpi (min-height: 24px) {\n .axdb-kpi > .axdb-kpi-s { display: block; flex: 1 1 40%; min-width: 80px; height: 100%; max-height: 100%; margin-top: 0; align-self: stretch; }\n }\n}\n/* THE STRIP (row floor): header and figure on one line, the spark beside them\n when the tile is wide enough. */\n@container axdb-tile (max-height: 46px) {\n .axdb-widget--kpi { flex-direction: row; align-items: center; gap: 12px; }\n .axdb-widget--kpi > .axdb-widget-h { flex: none; padding: 0 0 0 12px; background: none; border-bottom: 0; }\n /* The body is a SIZE container: in a centred row it would collapse to zero\n and every height query would fire. It takes the card's full inner height. */\n .axdb-widget--kpi > .axdb-kpi { flex: 1; align-self: stretch; }\n}\n\n/* SPLIT LAYOUT chrome: a divider sits in the gap between two siblings (a\n 24-px hit zone around a 10-px gap) and shows its line on hover or while\n dragged; the insertion line marks the edge a dragged widget will land on. */\n.grafloria-html-layer > .axdb-div { position: absolute; z-index: 2; pointer-events: auto; background: transparent; }\n.grafloria-html-layer > .axdb-div--row { cursor: col-resize; }\n.grafloria-html-layer > .axdb-div--column { cursor: row-resize; }\n.grafloria-html-layer > .axdb-div::after {\n content: \"\"; position: absolute; border-radius: 2px; background: #3b52d9; opacity: 0; transition: opacity .12s ease;\n}\n.grafloria-html-layer > .axdb-div--row::after { left: 50%; top: 8px; bottom: 8px; width: 4px; margin-left: -2px; }\n.grafloria-html-layer > .axdb-div--column::after { top: 50%; left: 8px; right: 8px; height: 4px; margin-top: -2px; }\n.grafloria-html-layer > .axdb-div:hover::after, .grafloria-html-layer > .axdb-div.axdb-active::after { opacity: .9; }\n.grafloria-html-layer > .axdb-ins {\n position: absolute; z-index: 3; pointer-events: none; border-radius: 2px;\n background: #3b52d9; box-shadow: 0 0 0 3px rgba(59, 82, 217, .22);\n}\n@media (prefers-reduced-motion: reduce) { .grafloria-html-layer > .axdb-div::after { transition: none; } }\n\n/* donut: ring beside its legend. The ring takes the body's height (a tall\n tile gets a bigger ring, not dead card), square, capped so its centre figure\n stays a figure and not a headline. */\n.axdb-widget-b.axdb-donut { display: flex; align-items: center; gap: 10px; }\n/* A legend taller than a SHORT body (a 2-row donut on a squeezed board) is\n clipped at its own foot, not centred over the title above it. */\n.axdb-widget-b.axdb-donut > .axdb-lg--col { max-height: 100%; min-height: 0; overflow: hidden; }\n.axdb-widget-b.axdb-donut > svg {\n flex: 0 0 auto; width: auto; height: 100%; max-height: 260px; max-width: 60%; aspect-ratio: 1 / 1;\n}\n\n/* READABILITY TIERS (widgets.ts chartTier): the text is always in the DOM;\n what a short body cannot afford is hidden, not dropped. */\n.axdb-tier-1 .axdb-yt--q, .axdb-tier-1 .axdb-yl--q,\n.axdb-tier-2 .axdb-yt--q, .axdb-tier-2 .axdb-yl--q,\n.axdb-tier-2 .axdb-yt--h, .axdb-tier-2 .axdb-yl--h,\n.axdb-tier-2 .axdb-xt, .axdb-tier-2 .axdb-vt,\n.axdb-lg--off { display: none; }\n\n/* SECTION CHROME: a pointer-transparent overlay on every member group. It\n wears the selection ring and, while selected, the corner handle. */\n.grafloria-html-layer > .axdb-slab { position: absolute; pointer-events: none; border-radius: var(--axdb-rs-radius, 8px); z-index: 4; }\n/* Selected, the overlay rises above the tiles so ITS corner handle wins a\n corner it shares with a child's; unselected, its handle takes no presses. */\n.grafloria-html-layer > .axdb-slab.axdb-slab--selected { z-index: 6; box-shadow: 0 0 0 1.5px var(--axdb-accent-ring, rgba(59, 82, 217, .55)); }\n.grafloria-html-layer > .axdb-slab > .axdb-rs { pointer-events: none; opacity: 0; }\n.grafloria-html-layer > .axdb-slab.axdb-slab--selected > .axdb-rs { pointer-events: auto; opacity: 1; }\n.grafloria-html-layer > .axdb-slab.axdb-slab--static > .axdb-rs { display: none; }\n\n/* SECTION CAPTION (0.4.22): the band on the slab. Geometry is inline (the\n reserve and the pixels come from one function); everything visual is a\n variable the options write and a theme may override. The band takes the\n pointer \u2014 the slab does not \u2014 so a press on it is the section's. */\n.grafloria-html-layer > .axdb-slab > .axdb-slab-h {\n position: absolute; box-sizing: border-box; pointer-events: auto; z-index: 1;\n display: flex; align-items: center; gap: 6px; min-width: 0;\n padding: var(--axdb-caption-pad, 0 12px);\n background: var(--axdb-caption-bg, rgba(31, 36, 48, .045));\n /* A hairline by default: two bands meeting (a captioned section inside a\n captioned section) read as two headers, not one grey block. */\n border-bottom: var(--axdb-caption-border, 1px solid rgba(31, 36, 48, .1));\n color: var(--axdb-caption-fg, #1f2430);\n font: var(--axdb-caption-font-weight, 600) var(--axdb-caption-font-size, 13px)/1.2 var(--axdb-caption-font-family, system-ui, -apple-system, \"Segoe UI\", sans-serif);\n text-transform: var(--axdb-caption-transform, none);\n letter-spacing: -.005em;\n border-radius: var(--axdb-rs-radius, 8px) var(--axdb-rs-radius, 8px) 0 0;\n cursor: default; user-select: none; -webkit-user-select: none; overflow: hidden;\n transition: opacity .12s;\n}\n/* The modifiers carry the band's own selector so they outrank its defaults. */\n.grafloria-html-layer > .axdb-slab > .axdb-slab-h.axdb-slab-h--center { justify-content: center; }\n.grafloria-html-layer > .axdb-slab > .axdb-slab-h.axdb-slab-h--end { justify-content: flex-end; }\n.grafloria-html-layer > .axdb-slab > .axdb-slab-h.axdb-slab-h--vtop { align-items: flex-start; }\n.grafloria-html-layer > .axdb-slab > .axdb-slab-h.axdb-slab-h--vbottom { align-items: flex-end; }\n/* 'tab': the same band sized to its text, a chip at the leading corner. It\n reserves its height like 'inside' \u2014 see captionReserve. */\n.grafloria-html-layer > .axdb-slab > .axdb-slab-h.axdb-slab-h--tab {\n max-width: 100%; border-radius: var(--axdb-rs-radius, 8px);\n border-bottom: var(--axdb-caption-border-tab, none);\n box-shadow: inset 0 0 0 1px rgba(31, 36, 48, .08);\n}\n/* 'hover': an overlay. It reserves nothing, so while hidden it must not take\n the pointer (an invisible band swallowed clicks on the content beneath),\n and while shown it paints OPAQUE \u2014 a 5% tint over a chart is a smear. */\n.grafloria-html-layer > .axdb-slab > .axdb-slab-h.axdb-slab-h--hover {\n opacity: 0; pointer-events: none;\n background: var(--axdb-caption-bg, #fff);\n box-shadow: 0 1px 4px rgba(31, 36, 48, .16);\n}\n/* .axdb-slab--hot is set by the binder from the live pointer (the overlay\n takes no pointer of its own, so CSS :hover can never fire on it). */\n.grafloria-html-layer > .axdb-slab.axdb-slab--hot > .axdb-slab-h.axdb-slab-h--hover,\n.grafloria-html-layer > .axdb-slab.axdb-slab--selected > .axdb-slab-h.axdb-slab-h--hover { pointer-events: auto; opacity: 1; }\n/* the actions of a hovered or selected section */\n.grafloria-html-layer > .axdb-slab.axdb-slab--hot > .axdb-slab-h > .axdb-slab-h-actions { opacity: 1; }\n/* the tight tier: a section under 90 px */\n.grafloria-html-layer > .axdb-slab > .axdb-slab-h.axdb-slab-h--tight { font-size: min(var(--axdb-caption-font-size, 12px), 12px); gap: 4px; }\n.axdb-slab-h--tight .axdb-slab-h-sub, .axdb-slab-h--tight .axdb-slab-h-actions { display: none; }\n.axdb-slab-h-icon { flex: none; }\n.axdb-slab-h-body { display: flex; flex-direction: column; justify-content: center; min-width: 0; flex: 0 1 auto; }\n.axdb-slab-h-text, .axdb-slab-h-sub { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n.axdb-slab-h-sub { font-size: .88em; font-weight: 500; opacity: .7; }\n.axdb-slab-h-info { flex: none; opacity: .6; font-size: .9em; cursor: help; }\n.axdb-slab-h-actions { flex: none; display: flex; gap: 2px; margin-inline-start: auto; opacity: 0; transition: opacity .12s; }\n.axdb-slab-h--center .axdb-slab-h-actions, .axdb-slab-h--end .axdb-slab-h-actions { margin-inline-start: 0; }\n.axdb-slab-h:hover > .axdb-slab-h-actions, .axdb-slab--selected > .axdb-slab-h > .axdb-slab-h-actions, .axdb-slab-h:focus-within > .axdb-slab-h-actions { opacity: 1; }\n.axdb-slab-h-action {\n all: unset; box-sizing: border-box; width: 26px; height: 26px; display: inline-flex; align-items: center; justify-content: center;\n border-radius: 6px; cursor: pointer; font-size: 14px; line-height: 1; color: inherit; transition: background-color .12s;\n}\n.axdb-slab-h-action:hover { background: rgba(31, 36, 48, .08); }\n.axdb-slab-h-action:focus-visible { outline: 2px solid var(--axdb-accent-ring, rgba(59, 82, 217, .55)); outline-offset: -2px; }\n.axdb-slab-h-action[disabled] { opacity: .4; cursor: default; }\n.axdb-slab-h-action[disabled]:hover { background: none; }\n.grafloria-html-layer > .axdb-slab.axdb-slab--static > .axdb-slab-h { cursor: default; }\n@media (prefers-color-scheme: dark) {\n /* The band sits on the PAGE, whose ground the kit does not own (a host that\n stays light while the kit goes dark is common): a solid ink, not a tint,\n so the light caption always reads. */\n .grafloria-html-layer > .axdb-slab > .axdb-slab-h {\n background: var(--axdb-caption-bg, #1b2029);\n color: var(--axdb-caption-fg, #eceef4);\n border-bottom: var(--axdb-caption-border, 1px solid rgba(236, 238, 244, .12));\n }\n .grafloria-html-layer > .axdb-slab > .axdb-slab-h.axdb-slab-h--tab { box-shadow: inset 0 0 0 1px rgba(236, 238, 244, .12); }\n .grafloria-html-layer > .axdb-slab > .axdb-slab-h.axdb-slab-h--hover { background: var(--axdb-caption-bg, #1a1d25); box-shadow: 0 1px 4px rgba(0, 0, 0, .5); }\n .axdb-slab-h-action:hover { background: rgba(236, 238, 244, .1); }\n}\n\n/* TAB CONTAINER (0.4.27): a strip of pages across the container's top. The\n strip takes the pointer (the tabs are real buttons); the pages below it are\n ordinary boards. */\n.grafloria-html-layer > .axdb-tabs {\n position: absolute; box-sizing: border-box; pointer-events: auto; z-index: 5;\n display: flex; align-items: center; gap: 2px; padding: 3px 6px; overflow-x: auto; overflow-y: hidden;\n background: var(--axdb-tabs-bg, #f1f3f8);\n border-bottom: 1px solid var(--axdb-tabs-line, #e5e8ef);\n border-radius: var(--axdb-rs-radius, 8px) var(--axdb-rs-radius, 8px) 0 0;\n scrollbar-width: thin;\n}\n.axdb-tabs--center { justify-content: center; }\n.axdb-tabs--end { justify-content: flex-end; }\n.axdb-tabs--stretch > .axdb-tab { flex: 1 1 0; }\n/* A tab is a PILL on the track (a segmented control): quiet until hovered,\n the active one lifted in the card's own ground. */\n.axdb-tab {\n all: unset; box-sizing: border-box; flex: 0 0 auto; max-width: 200px;\n padding: 0 12px; height: 100%; display: inline-flex; align-items: center;\n font: 500 12.5px/1 system-ui, -apple-system, \"Segoe UI\", sans-serif;\n letter-spacing: -.005em;\n color: var(--axdb-tabs-fg, #5a6478); cursor: pointer;\n border-radius: var(--axdb-tab-radius, 6px);\n white-space: nowrap; overflow: hidden; text-overflow: ellipsis;\n transition: background-color .12s, color .12s, box-shadow .12s;\n}\n.axdb-tab:hover { background: rgba(31, 36, 48, .06); color: var(--axdb-tabs-on-fg, #1f2430); }\n.axdb-tab:focus-visible { outline: 2px solid var(--axdb-accent-ring, rgba(59, 82, 217, .55)); outline-offset: -2px; }\n.axdb-tab.axdb-tab--on {\n background: var(--axdb-tabs-on-bg, #fff);\n color: var(--axdb-tabs-on-fg, #1f2430);\n font-weight: 600;\n box-shadow: 0 1px 2px rgba(16, 24, 40, .1), 0 0 0 1px rgba(31, 36, 48, .06);\n}\n@media (prefers-reduced-motion: reduce) { .axdb-tab { transition: none; } }\n@media (prefers-color-scheme: dark) {\n /* Solid for the same reason as the caption band: the track sits on the page. */\n .grafloria-html-layer > .axdb-tabs { background: var(--axdb-tabs-bg, #1b2029); border-bottom-color: var(--axdb-tabs-line, rgba(236, 238, 244, .12)); }\n .axdb-tab { color: var(--axdb-tabs-fg, #98a1b4); }\n .axdb-tab:hover { background: rgba(236, 238, 244, .08); color: var(--axdb-tabs-on-fg, #eceef4); }\n .axdb-tab.axdb-tab--on { background: var(--axdb-tabs-on-bg, #232836); color: var(--axdb-tabs-on-fg, #eceef4); box-shadow: 0 1px 2px rgba(0, 0, 0, .45), 0 0 0 1px rgba(236, 238, 244, .1); }\n}\n\n/* legend chips, shared by line and donut */\n.axdb-lg { display: flex; flex-wrap: wrap; gap: 4px 12px; margin-top: 9px; }\n.axdb-lg--col { flex-direction: column; flex-wrap: nowrap; gap: 6px; margin-top: 0; }\n.axdb-lg i {\n display: inline-flex; align-items: center; gap: 5px; font-style: normal;\n font: 500 11px/1.3 system-ui, sans-serif; color: var(--axdb-muted);\n}\n.axdb-lg i b { width: 9px; height: 9px; border-radius: 3px; flex: none; }\n\n/* table */\n.axdb-table { width: 100%; border-collapse: collapse; font-size: 12px; color: var(--axdb-ink); }\n.axdb-table th {\n text-align: left; padding: 4px 8px 7px; border-bottom: 1px solid var(--axdb-line);\n font: 600 10px/1.3 system-ui, sans-serif; letter-spacing: .4px; text-transform: uppercase;\n color: var(--axdb-muted);\n}\n.axdb-table td { padding: 6px 8px; border-bottom: 1px solid var(--axdb-line); white-space: nowrap; }\n.axdb-table td.num { text-align: right; font-variant-numeric: tabular-nums; }\n.axdb-table tr:last-child td { border-bottom: none; }\n\n@media (prefers-color-scheme: dark) {\n .axdb-widget {\n --axdb-ink: #eceef4;\n --axdb-muted: #98a1b4;\n --axdb-grid: rgba(150, 160, 182, .2);\n --axdb-card: #161a22;\n --axdb-line: #2a3040;\n --axdb-head-bg: rgba(255, 255, 255, .035);\n --axdb-head-fg: #e6e9f0;\n --axdb-soft: rgba(150, 160, 182, .16);\n --axdb-up: #4ade80;\n --axdb-down: #fb7185;\n --axdb-c1: #8b9cff; --axdb-c2: #38bdf8; --axdb-c3: #2dd4bf;\n --axdb-c4: #fbbf24; --axdb-c5: #a78bfa; --axdb-c6: #94a3b8;\n }\n}\n";
25
+ export declare const DASHBOARD_KIT_CSS = "\n/* ===== reflow glide (armed on the html layer for the whole gesture) ===== */\n.grafloria-html-layer.axdb-glide > .grafloria-node-host {\n transition: left .28s cubic-bezier(.2, 0, .2, 1), top .28s cubic-bezier(.2, 0, .2, 1),\n width .28s cubic-bezier(.2, 0, .2, 1), height .28s cubic-bezier(.2, 0, .2, 1);\n}\n\n/* Motion is a preference (WCAG 2.3.3): no glide for those who asked for none. */\n@media (prefers-reduced-motion: reduce) {\n .grafloria-html-layer.axdb-glide > .grafloria-node-host { transition: none; }\n}\n\n/* ===== keyboard focus: the roving tab stop shows where it is (WCAG 2.4.7) ===== */\n.grafloria-html-layer > .grafloria-node-host:focus-visible {\n outline: 2px solid var(--axdb-accent, #3b52d9);\n outline-offset: 2px;\n border-radius: var(--axdb-rs-radius, 8px);\n}\n\n/* ===== the held tile: transition-exempt ghost, above everything ===== */\n.grafloria-html-layer > .grafloria-node-host.axdb-ghost,\n.grafloria-html-layer.axdb-glide > .grafloria-node-host.axdb-ghost {\n transition: none;\n z-index: 30;\n opacity: .85;\n cursor: grabbing;\n filter: drop-shadow(0 10px 16px rgba(16, 24, 40, .3));\n}\n/* Outside the board: release will REMOVE \u2014 dim the ghost to say so. */\n.grafloria-html-layer > .grafloria-node-host.axdb-ghost.axdb-out { opacity: .35; filter: grayscale(.6); }\n\n/* ===== the kit's chrome glides WITH the tiles it belongs to (0.4.43) =====\n A pushed group's strip, slab and surface used to jump to the new slot while\n its tiles slid there: one group, two motions. */\n.grafloria-html-layer.axdb-glide > .axdb-tabs,\n.grafloria-html-layer.axdb-glide > .axdb-slab,\n.grafloria-html-layer.axdb-glide > .axdb-group-bg {\n transition: left .28s cubic-bezier(.2, 0, .2, 1), top .28s cubic-bezier(.2, 0, .2, 1),\n width .28s cubic-bezier(.2, 0, .2, 1), height .28s cubic-bezier(.2, 0, .2, 1);\n}\n@media (prefers-reduced-motion: reduce) {\n .grafloria-html-layer.axdb-glide > .axdb-tabs,\n .grafloria-html-layer.axdb-glide > .axdb-slab,\n .grafloria-html-layer.axdb-glide > .axdb-group-bg { transition: none; }\n}\n\n/* ===== CARRIED (0.4.43): a group dragged by its strip or band moves as ONE thing =====\n Its whole subtree \u2014 tiles, strips, slabs, surface \u2014 is transition-exempt like\n the ghost and floats with it; the surface casts the shadow. */\n.grafloria-html-layer > .axdb-carried,\n.grafloria-html-layer.axdb-glide > .axdb-carried { transition: none; cursor: grabbing; }\n.grafloria-html-layer > .grafloria-node-host.axdb-carried { z-index: 30; opacity: .92; }\n.grafloria-html-layer > .axdb-group-bg.axdb-carried { z-index: 29; filter: drop-shadow(0 10px 16px rgba(16, 24, 40, .3)); }\n.grafloria-html-layer > .axdb-slab.axdb-carried { z-index: 31; }\n.grafloria-html-layer > .axdb-tabs.axdb-carried { z-index: 32; }\n\n/* ===== the placeholder: dashed slab, truthful, never animated ===== */\n.grafloria-html-layer > .axdb-ph {\n position: absolute;\n border-radius: var(--axdb-rs-radius, 8px);\n background: rgba(30, 34, 45, .14);\n border: 2px dashed rgba(30, 34, 45, .28);\n box-sizing: border-box;\n pointer-events: none;\n z-index: 0;\n transition: none;\n}\n/* A slab move asked for a cell it cannot have (a locked section in the way): the wanted cell in the danger tone. */\n.grafloria-html-layer > .axdb-ph.axdb-ph--no { border-color: var(--axdb-danger, #b3123c); background: var(--axdb-danger-soft, rgba(179, 18, 60, .07)); }\n\n@media (prefers-color-scheme: dark) {\n .grafloria-html-layer > .axdb-ph { background: rgba(220, 225, 240, .12); border-color: rgba(220, 225, 240, .3); }\n}\n\n/* ===== corner resize handle (hover-revealed, radius-matched) ===== */\n:is(.grafloria-node-host, .axdb-slab) > .axdb-rs {\n position: absolute;\n right: 0;\n bottom: 0;\n width: 24px; /* WCAG 2.5.8: a 24-px minimum target (was 18) */\n height: 24px;\n cursor: nwse-resize;\n border-right: 3px solid rgba(120, 130, 148, .55);\n border-bottom: 3px solid rgba(120, 130, 148, .55);\n border-bottom-right-radius: var(--axdb-rs-radius, 8px);\n opacity: 0;\n transition: opacity .12s;\n z-index: 5;\n}\n.grafloria-node-host:hover > .axdb-rs { opacity: 1; }\n/* A finger never hovers: on touch devices the corner handle is always visible\n (review D8 \u2014 hover-only meant no resize at all on a tablet). */\n@media (hover: none) { .grafloria-node-host > .axdb-rs { opacity: .8; } }\n.grafloria-node-host > .axdb-rs:hover { border-color: #3b52d9; }\n/* RTL boards grow leftwards, so the grab corner mirrors with them. */\n:is(.grafloria-node-host, .axdb-slab) > .axdb-rs.axdb-rs--rtl {\n right: auto;\n left: 0;\n cursor: nesw-resize;\n border-right: none;\n border-left: 3px solid rgba(120, 130, 148, .55);\n border-bottom-right-radius: 0;\n border-bottom-left-radius: var(--axdb-rs-radius, 8px);\n}\n\n/* ===== palette drag-in chip (screen-space clone following the cursor) ===== */\n.axdb-drag-chip {\n position: fixed;\n z-index: 9999;\n pointer-events: none;\n opacity: .9;\n filter: drop-shadow(0 8px 14px rgba(16, 24, 40, .3));\n transition: opacity .12s, filter .12s;\n}\n/* A bounded board with no room refuses the entry: the chip dims to say so\n (the same signal a tile ghost gives outside the board). Without this rule\n the class was set and nothing showed \u2014 a refusal a user could not see. */\n.axdb-drag-chip.axdb-out { opacity: .35; filter: grayscale(.7) drop-shadow(0 8px 14px rgba(16, 24, 40, .2)); }\n/* A tab dragged off its strip: VS Code carries the TAB, not the editor, so\n what follows the pointer is a chip wearing the tab's own label. */\n.axdb-tab-chip {\n padding: 5px 12px;\n max-width: 220px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font: 500 12px/1.4 var(--axdb-font, system-ui, -apple-system, \"Segoe UI\", Roboto, sans-serif);\n color: var(--axdb-tabs-on-fg, #1f2430);\n background: var(--axdb-tabs-on-bg, #fff);\n border-radius: var(--axdb-tab-radius, 6px);\n box-shadow: 0 1px 2px rgba(16, 24, 40, .1), 0 0 0 1px rgba(31, 36, 48, .08);\n}\n@media (prefers-color-scheme: dark) {\n .axdb-tab-chip {\n color: var(--axdb-tabs-on-fg, #eceef4);\n background: var(--axdb-tabs-on-bg, #232836);\n box-shadow: 0 1px 2px rgba(0, 0, 0, .45), 0 0 0 1px rgba(236, 238, 244, .12);\n }\n}\n/* A tab held over ANOTHER group: it will join that group, not the board. The\n group's frame lights up, and its strip marks the slot the tab will take \u2014\n a bar before the tab it lands in front of, or after the last one. */\n.grafloria-html-layer > .axdb-join {\n position: absolute;\n z-index: 3;\n pointer-events: none;\n box-sizing: border-box;\n border: 2px dashed var(--axdb-accent, #3b52d9);\n background: var(--axdb-accent-soft, rgba(59, 82, 217, .08));\n border-radius: var(--axdb-rs-radius, 8px);\n}\n.axdb-tabs.axdb-tabs--drop { box-shadow: inset 0 -2px 0 var(--axdb-accent, #3b52d9); }\n.axdb-tab.axdb-tab--drop-before { position: relative; }\n.axdb-tab.axdb-tab--drop-before::before {\n content: \"\"; position: absolute; left: -3px; top: 4px; bottom: 4px; width: 3px;\n border-radius: 2px; background: var(--axdb-accent, #3b52d9);\n}\n.axdb-tabs.axdb-tabs--drop-end::after {\n content: \"\"; display: inline-block; width: 3px; height: 18px; margin: 0 4px;\n border-radius: 2px; background: var(--axdb-accent, #3b52d9); vertical-align: middle; align-self: center;\n}\n\n/* ===========================================================================\n BUILT-IN WIDGET CARDS \u2014 what widgets.ts paints when a page writes no\n renderWidget. Class-prefixed (never element selectors), so this can only\n ever style DOM the kit itself produced. Colours are CSS variables on the\n card, so a page re-skins every built-in widget by setting them once; the\n corner radius follows --axdb-rs-radius, which is also the placeholder's and\n the resize handle's, so the three can never drift apart.\n =========================================================================== */\n.axdb-widget {\n --axdb-ink: #1f2430;\n /* 5.95:1 on the card \u2014 WCAG 1.4.3 for the 9\u201311px captions this paints\n (the #7a8496 it replaced sat at 3.77:1, axe-core's first finding). */\n --axdb-muted: #5a6478;\n --axdb-grid: rgba(120, 130, 148, .22);\n --axdb-card: #fff;\n --axdb-line: #e5e8ef;\n --axdb-soft: rgba(120, 130, 148, .14);\n --axdb-up: #0f7a3d; /* 5.42:1 */\n --axdb-down: #be123c; /* 6.29:1 */\n /* The categorical palette, as tokens so the dark card can carry its own\n steps: every entry clears 3:1 against its card (WCAG 1.4.11). */\n --axdb-c1: #3b52d9; --axdb-c2: #0369a1; --axdb-c3: #0f766e;\n --axdb-c4: #b45309; --axdb-c5: #6d28d9; --axdb-c6: #475569;\n /* The header band: a panel wears its title in a band across its top, the\n way DevExpress, Grafana and Metabase panels do \u2014 tinted ground, hairline\n below, sentence case (0.4.36; the tracked uppercase micro-label before). */\n --axdb-head-bg: #f7f8fb;\n --axdb-head-fg: #2a3140;\n box-sizing: border-box;\n width: 100%;\n height: 100%;\n display: flex;\n flex-direction: column;\n overflow: hidden;\n padding: 0;\n background: var(--axdb-card);\n border: 1px solid var(--axdb-line);\n border-radius: var(--axdb-rs-radius, 8px);\n box-shadow: 0 1px 2px rgba(16, 24, 40, .04);\n font-family: system-ui, -apple-system, \"Segoe UI\", sans-serif;\n color: var(--axdb-ink);\n}\n/* The host is the card's size container (named, so a widget's inner parts can\n ask the TILE's size as well as their own body's), so the card can shed its\n padding as the tile gets short instead of clipping its own content. */\n.grafloria-html-layer > .grafloria-node-host { container: axdb-tile / size; }\n/* Two class hops on the header: these blocks precede the header's own rule\n below and would lose the cascade at equal specificity \u2014 at 54 px the margin\n stayed 8 px, the body shrank to 14 px and the KPI figure hid. */\n@container axdb-tile (max-height: 90px) {\n .axdb-widget > .axdb-widget-h { padding: 5px 12px; font-size: 11.5px; }\n .axdb-widget > .axdb-widget-b { padding: 6px 12px; }\n}\n/* The row floor: the band gives way to an inline label beside the figure. */\n@container axdb-tile (max-height: 46px) {\n .axdb-widget > .axdb-widget-h { padding: 0 0 0 12px; background: none; border-bottom: 0; }\n .axdb-widget > .axdb-widget-b { padding: 3px 12px; }\n}\n@container axdb-tile (max-height: 26px) {\n .axdb-widget > .axdb-widget-h { display: none; }\n .axdb-widget > .axdb-widget-b { padding: 1px 12px; }\n}\n.axdb-widget-h {\n display: flex;\n align-items: center;\n gap: 8px;\n flex: none;\n min-width: 0;\n padding: 9px 14px;\n background: var(--axdb-head-bg);\n border-bottom: 1px solid var(--axdb-line);\n font: 600 12.5px/1.3 system-ui, -apple-system, \"Segoe UI\", sans-serif;\n letter-spacing: -.005em;\n color: var(--axdb-head-fg);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n/* Drag-handle mode (DevExpress drags an item by its caption): the header is\n the grip and says so with a dot pattern and a grab cursor; the body keeps\n its own cursor because a press there starts nothing. */\n.axdb-drag-handle .axdb-widget > .axdb-widget-h { cursor: grab; }\n.axdb-drag-handle .axdb-widget > .axdb-widget-h::before {\n content: ''; flex: none; width: 8px; height: 12px; opacity: .55;\n background: radial-gradient(circle, currentColor 1.1px, transparent 1.5px) 0 0 / 4px 4px;\n}\n/* A PAINTED GRIP (dragHandle: { grip: true }): the only drag zone, a small\n dotted tab along the card's top edge. Inside sits in the header band and\n the header makes room for it; outside is a tab above the card, the\n DevExpress item bar. The host must not clip it. */\n.grafloria-node-host > .axdb-grip {\n position: absolute; z-index: 4; box-sizing: border-box; width: 24px; height: 12px;\n border: 1px solid var(--axdb-line, #e7eaf1); border-radius: 3px; background: var(--axdb-card, #fff);\n color: var(--axdb-muted, #5a6478); cursor: grab;\n /* Shown on the SELECTED widget only (the DevExpress designer shows an item's\n bar on the selected item): a press or keyboard focus selects, a void\n click clears. Hidden, it takes no pointer either. */\n opacity: 0; pointer-events: none; transition: opacity .12s ease;\n}\n.grafloria-node-host.axdb-selected > .axdb-grip, .grafloria-node-host:focus-within > .axdb-grip { opacity: 1; pointer-events: auto; }\n.grafloria-node-host > .axdb-grip::before {\n content: ''; position: absolute; left: 5px; top: 2px; width: 12px; height: 6px;\n background: radial-gradient(circle, currentColor 1px, transparent 1.4px) 0 0 / 4px 3px;\n}\n.grafloria-node-host > .axdb-grip:hover { border-color: var(--axdb-accent, #3b52d9); color: var(--axdb-accent, #3b52d9); }\n/* The selected card says so, quietly. --axdb-accent themes the grip's hover, the\n ring and the focus outline together; --axdb-accent-ring the ring alone. */\n.grafloria-node-host.axdb-selected > .axdb-widget { box-shadow: 0 0 0 1.5px var(--axdb-accent-ring, rgba(59, 82, 217, .55)), 0 1px 2px rgba(16, 24, 40, .05), 0 1px 3px rgba(16, 24, 40, .05); }\n/* While a resize edge is near, the host and everything in it (a chart canvas\n with its own cursor included) show the edge's cursor. */\n.grafloria-node-host[data-axdb-edge=\"ns-resize\"], .grafloria-node-host[data-axdb-edge=\"ns-resize\"] * { cursor: ns-resize !important; }\n.grafloria-node-host[data-axdb-edge=\"ew-resize\"], .grafloria-node-host[data-axdb-edge=\"ew-resize\"] * { cursor: ew-resize !important; }\n.grafloria-node-host[data-axdb-edge=\"nwse-resize\"], .grafloria-node-host[data-axdb-edge=\"nwse-resize\"] * { cursor: nwse-resize !important; }\n.grafloria-node-host[data-axdb-edge=\"nesw-resize\"], .grafloria-node-host[data-axdb-edge=\"nesw-resize\"] * { cursor: nesw-resize !important; }\n/* INSIDE: centred on the header's text line and flush with the card padding,\n per size tier (padding 13/15, then 8/14, 4/12, 2/12). */\n.grafloria-node-host > .axdb-grip--inside { top: 12px; }\n.grafloria-node-host > .axdb-grip--inside.axdb-grip--left { left: 15px; }\n.grafloria-node-host > .axdb-grip--inside.axdb-grip--right { right: 15px; }\n.grafloria-node-host > .axdb-grip--inside.axdb-grip--center { top: 3px; }\n@container axdb-tile (max-height: 90px) {\n .grafloria-node-host > .axdb-grip--inside { top: 9px; }\n .grafloria-node-host > .axdb-grip--inside.axdb-grip--left { left: 14px; }\n .grafloria-node-host > .axdb-grip--inside.axdb-grip--right { right: 14px; }\n .grafloria-node-host > .axdb-grip--inside.axdb-grip--center { top: 2px; }\n .grafloria-node-host.axdb-gp-inside.axdb-gp-left .axdb-widget > .axdb-widget-h { padding-left: 46px; }\n .grafloria-node-host.axdb-gp-inside.axdb-gp-right .axdb-widget > .axdb-widget-h { padding-right: 46px; }\n .grafloria-node-host.axdb-gp-inside.axdb-gp-center .axdb-widget > .axdb-widget-h { padding-top: 15px; }\n}\n@container axdb-tile (max-height: 46px) {\n .grafloria-node-host > .axdb-grip--inside { top: 5px; }\n .grafloria-node-host > .axdb-grip--inside.axdb-grip--left { left: 12px; }\n .grafloria-node-host > .axdb-grip--inside.axdb-grip--right { right: 12px; }\n .grafloria-node-host > .axdb-grip--inside.axdb-grip--center { top: 1px; }\n .grafloria-node-host.axdb-gp-inside.axdb-gp-left .axdb-widget > .axdb-widget-h { padding-left: 44px; }\n .grafloria-node-host.axdb-gp-inside.axdb-gp-right .axdb-widget > .axdb-widget-h { padding-right: 44px; }\n .grafloria-node-host.axdb-gp-inside.axdb-gp-center .axdb-widget > .axdb-widget-h { padding-top: 13px; }\n}\n@container axdb-tile (max-height: 26px) {\n .grafloria-node-host > .axdb-grip--inside { top: 2px; }\n}\n/* OUTSIDE: a tab on the card's top edge, its corners in line with the card's. */\n/* The OUTSIDE tab lives in the gap between tiles: never taller than the gap\n less a pixel (an 11-px tab in a 10-px gap sat on the tile above), never\n shorter than 6 px so the dots still read. */\n.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); }\n.grafloria-node-host > .axdb-grip--outside.axdb-grip--left { left: 10px; }\n.grafloria-node-host > .axdb-grip--outside.axdb-grip--right { right: 10px; }\n.grafloria-node-host > .axdb-grip--center { left: 50%; transform: translateX(-50%); }\n/* The header makes room for an inside grip on its side \u2014 inset + the 24-px\n grip + an 8-px gap, per tier (32 px under the band's 14-px inset hid the\n first letter of the title) \u2014 and a centred one sits above the text. */\n.grafloria-node-host.axdb-gp-inside.axdb-gp-left .axdb-widget > .axdb-widget-h { padding-left: 47px; }\n.grafloria-node-host.axdb-gp-inside.axdb-gp-right .axdb-widget > .axdb-widget-h { padding-right: 47px; }\n.grafloria-node-host.axdb-gp-inside.axdb-gp-center .axdb-widget > .axdb-widget-h { padding-top: 17px; }\n.axdb-widget-b { flex: 1; min-height: 0; position: relative; padding: 12px 14px; }\n/* A drag across a STATIC board (nothing prevents the press's default there)\n used to select every label on it; kit cards are not prose. Tables stay\n copyable \u2014 a figure in a grid is the one thing a viewer selects. */\n.axdb-widget { user-select: none; -webkit-user-select: none; }\n.axdb-widget .axdb-table { user-select: text; -webkit-user-select: text; }\n.axdb-widget-b > svg { display: block; width: 100%; height: 100%; }\n.axdb-widget-b.axdb-scroll { overflow: auto; }\n/* A chart WITH a legend under it: the plot yields height, the legend keeps its\n own (without this the 100%-tall svg pushes the legend out of the card). */\n.axdb-widget-b.axdb-has-lg { display: flex; flex-direction: column; }\n.axdb-widget-b.axdb-has-lg > svg { flex: 1; height: auto; min-height: 0; }\n/* The data behind a chart, for readers that cannot see the chart: present in\n the accessibility tree, absent from the picture (WCAG 1.1.1). */\n.axdb-sr {\n position: absolute; top: 0; left: 0; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden;\n clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; border: 0;\n}\n.axdb-widget-empty {\n display: flex; align-items: center; justify-content: center; height: 100%;\n font: 500 11.5px/1.3 system-ui, sans-serif; color: var(--axdb-muted);\n}\n\n/* kpi: value + delta stack, the spark yields its height before they do */\n/* A SIZE CONTAINER, so the card's type scales with the tile instead of\n clipping. Fit-mode shrinks tiles when rows are added (dropping a chart above\n the KPI row took these from 122px to 85px) and fixed 30px/12px lines were\n sliced mid-glyph by the card's overflow \u2014 the audited \"trimmed\" widgets.\n cqh = 1% of the body's own height, clamped so full-size tiles look exactly\n as before and short tiles compress instead of cutting. */\n.axdb-widget-b.axdb-kpi { display: flex; flex-direction: column; container: axdb-kpi / size; }\n/* STEP DOWN, NEVER CLIP. As the body gets shorter the sparkline goes first, then the\n delta, then the value \u2014 the header alone at the row floor. Before this a 99-px row\n drew the sparkline as a 13-px sliver and a 28-px row cut the value mid-glyph (the\n fluid board, after a drag pushed the KPI row down). Thresholds are body heights. */\n/* Two class hops, so these outrank \".axdb-widget-b > svg { display: block }\" \u2014\n a bare .axdb-kpi-s lost that cascade and the sparkline stayed. */\n@container (max-height: 78px) { .axdb-kpi > .axdb-kpi-s { display: none; } }\n@container (max-height: 40px) { .axdb-kpi > .axdb-kpi-d { display: none; } }\n@container (max-height: 16px) { .axdb-kpi > .axdb-kpi-v { display: none; } }\n.axdb-kpi-v { font: 700 clamp(15px, 44cqh, 30px)/1.05 system-ui, sans-serif; letter-spacing: -.02em; color: var(--axdb-ink); white-space: nowrap; }\n.axdb-kpi-d { margin-top: clamp(1px, 5cqh, 6px); font: 600 clamp(9px, 19cqh, 12px)/1.2 system-ui, sans-serif; white-space: nowrap; }\n.axdb-kpi-d span { color: var(--axdb-muted); font-weight: 500; }\n.axdb-kpi-d.up { color: var(--axdb-up); }\n.axdb-kpi-d.down { color: var(--axdb-down); }\n/* Grows into a tall tile (a 3-row KPI is not a number over a void) and never\n takes more than two fifths of the body. */\n.axdb-kpi-s { margin-top: auto; height: auto; min-height: 0; flex: 1 1 34px; max-height: 40%; }\n/* SHORT AND WIDE \u2014 a one-row KPI on a fluid board. Stacked, a short tile dropped\n its sparkline and left a small figure in the corner of an empty card. Here the\n figure, its delta and the sparkline sit in a row and the spark fills the width.\n The outer query asks the TILE (host), the inner one the body, so a strip too\n thin for a readable spark (under 24 px) keeps the figure alone. 340 px is what\n a figure, its delta, the gaps and a spark worth reading need \u2014 the builder's\n 236-px tiles got a 40-px wedge of spark beside the delta. */\n@container axdb-tile (max-height: 125px) and (min-width: 340px) {\n .axdb-widget--kpi > .axdb-kpi { flex-direction: row; align-items: center; gap: 14px; }\n .axdb-widget--kpi .axdb-kpi-d { margin-top: 0; }\n @container axdb-kpi (min-height: 24px) {\n .axdb-kpi > .axdb-kpi-s { display: block; flex: 1 1 40%; min-width: 80px; height: 100%; max-height: 100%; margin-top: 0; align-self: stretch; }\n }\n}\n/* THE STRIP (row floor): header and figure on one line, the spark beside them\n when the tile is wide enough. */\n@container axdb-tile (max-height: 46px) {\n .axdb-widget--kpi { flex-direction: row; align-items: center; gap: 12px; }\n .axdb-widget--kpi > .axdb-widget-h { flex: none; padding: 0 0 0 12px; background: none; border-bottom: 0; }\n /* The body is a SIZE container: in a centred row it would collapse to zero\n and every height query would fire. It takes the card's full inner height. */\n .axdb-widget--kpi > .axdb-kpi { flex: 1; align-self: stretch; }\n}\n\n/* SPLIT LAYOUT chrome: a divider sits in the gap between two siblings (a\n 24-px hit zone around a 10-px gap) and shows its line on hover or while\n dragged; the insertion line marks the edge a dragged widget will land on. */\n.grafloria-html-layer > .axdb-div { position: absolute; z-index: 2; pointer-events: auto; background: transparent; }\n.grafloria-html-layer > .axdb-div--row { cursor: col-resize; }\n.grafloria-html-layer > .axdb-div--column { cursor: row-resize; }\n.grafloria-html-layer > .axdb-div::after {\n content: \"\"; position: absolute; border-radius: 2px; background: #3b52d9; opacity: 0; transition: opacity .12s ease;\n}\n.grafloria-html-layer > .axdb-div--row::after { left: 50%; top: 8px; bottom: 8px; width: 4px; margin-left: -2px; }\n.grafloria-html-layer > .axdb-div--column::after { top: 50%; left: 8px; right: 8px; height: 4px; margin-top: -2px; }\n.grafloria-html-layer > .axdb-div:hover::after, .grafloria-html-layer > .axdb-div.axdb-active::after { opacity: .9; }\n.grafloria-html-layer > .axdb-ins {\n position: absolute; z-index: 3; pointer-events: none; border-radius: 2px;\n background: #3b52d9; box-shadow: 0 0 0 3px rgba(59, 82, 217, .22);\n}\n@media (prefers-reduced-motion: reduce) { .grafloria-html-layer > .axdb-div::after { transition: none; } }\n\n/* donut: ring beside its legend. The ring takes the body's height (a tall\n tile gets a bigger ring, not dead card), square, capped so its centre figure\n stays a figure and not a headline. */\n.axdb-widget-b.axdb-donut { display: flex; align-items: center; gap: 10px; }\n/* A legend taller than a SHORT body (a 2-row donut on a squeezed board) is\n clipped at its own foot, not centred over the title above it. */\n.axdb-widget-b.axdb-donut > .axdb-lg--col { max-height: 100%; min-height: 0; overflow: hidden; }\n.axdb-widget-b.axdb-donut > svg {\n flex: 0 0 auto; width: auto; height: 100%; max-height: 260px; max-width: 60%; aspect-ratio: 1 / 1;\n}\n\n/* READABILITY TIERS (widgets.ts chartTier): the text is always in the DOM;\n what a short body cannot afford is hidden, not dropped. */\n.axdb-tier-1 .axdb-yt--q, .axdb-tier-1 .axdb-yl--q,\n.axdb-tier-2 .axdb-yt--q, .axdb-tier-2 .axdb-yl--q,\n.axdb-tier-2 .axdb-yt--h, .axdb-tier-2 .axdb-yl--h,\n.axdb-tier-2 .axdb-xt, .axdb-tier-2 .axdb-vt,\n.axdb-lg--off { display: none; }\n\n/* SECTION CHROME: a pointer-transparent overlay on every member group. It\n wears the selection ring and, while selected, the corner handle. */\n.grafloria-html-layer > .axdb-slab { position: absolute; pointer-events: none; border-radius: var(--axdb-rs-radius, 8px); z-index: 4; }\n/* Selected, the overlay rises above the tiles so ITS corner handle wins a\n corner it shares with a child's; unselected, its handle takes no presses. */\n.grafloria-html-layer > .axdb-slab.axdb-slab--selected { z-index: 6; box-shadow: 0 0 0 1.5px var(--axdb-accent-ring, rgba(59, 82, 217, .55)); }\n.grafloria-html-layer > .axdb-slab > .axdb-rs { pointer-events: none; opacity: 0; }\n.grafloria-html-layer > .axdb-slab.axdb-slab--selected > .axdb-rs { pointer-events: auto; opacity: 1; }\n.grafloria-html-layer > .axdb-slab.axdb-slab--static > .axdb-rs { display: none; }\n\n/* GROUP FRAME (0.4.43): a TAB CONTAINER wears a frame by default \u2014 a bordered\n slab, and a tinted surface UNDER its pages (the strip's own track colour, so\n strip and body read as one panel). The layer is isolated so the surface can\n sit below every tile at z -1 and still paint above the canvas. */\n.grafloria-html-layer { isolation: isolate; }\n.grafloria-html-layer > .axdb-group-bg {\n position: absolute; pointer-events: none; z-index: -1; box-sizing: border-box;\n border-radius: var(--axdb-rs-radius, 8px);\n background: var(--axdb-group-bg, #f1f3f8);\n}\n.grafloria-html-layer > .axdb-slab.axdb-slab--tabs {\n box-sizing: border-box;\n border: 1px solid var(--axdb-group-border, var(--axdb-line, #e5e8ef));\n}\n@media (prefers-color-scheme: dark) {\n .grafloria-html-layer > .axdb-group-bg { background: var(--axdb-group-bg, #1b2029); }\n .grafloria-html-layer > .axdb-slab.axdb-slab--tabs { border-color: var(--axdb-group-border, rgba(236, 238, 244, .12)); }\n}\n\n/* SECTION CAPTION (0.4.22): the band on the slab. Geometry is inline (the\n reserve and the pixels come from one function); everything visual is a\n variable the options write and a theme may override. The band takes the\n pointer \u2014 the slab does not \u2014 so a press on it is the section's. */\n.grafloria-html-layer > .axdb-slab > .axdb-slab-h {\n position: absolute; box-sizing: border-box; pointer-events: auto; z-index: 1;\n display: flex; align-items: center; gap: 6px; min-width: 0;\n padding: var(--axdb-caption-pad, 0 12px);\n background: var(--axdb-caption-bg, rgba(31, 36, 48, .045));\n /* A hairline by default: two bands meeting (a captioned section inside a\n captioned section) read as two headers, not one grey block. */\n border-bottom: var(--axdb-caption-border, 1px solid rgba(31, 36, 48, .1));\n color: var(--axdb-caption-fg, #1f2430);\n font: var(--axdb-caption-font-weight, 600) var(--axdb-caption-font-size, 13px)/1.2 var(--axdb-caption-font-family, system-ui, -apple-system, \"Segoe UI\", sans-serif);\n text-transform: var(--axdb-caption-transform, none);\n letter-spacing: -.005em;\n border-radius: var(--axdb-rs-radius, 8px) var(--axdb-rs-radius, 8px) 0 0;\n cursor: default; user-select: none; -webkit-user-select: none; overflow: hidden;\n transition: opacity .12s;\n}\n/* The modifiers carry the band's own selector so they outrank its defaults. */\n.grafloria-html-layer > .axdb-slab > .axdb-slab-h.axdb-slab-h--center { justify-content: center; }\n.grafloria-html-layer > .axdb-slab > .axdb-slab-h.axdb-slab-h--end { justify-content: flex-end; }\n.grafloria-html-layer > .axdb-slab > .axdb-slab-h.axdb-slab-h--vtop { align-items: flex-start; }\n.grafloria-html-layer > .axdb-slab > .axdb-slab-h.axdb-slab-h--vbottom { align-items: flex-end; }\n/* 'tab': the same band sized to its text, a chip at the leading corner. It\n reserves its height like 'inside' \u2014 see captionReserve. */\n.grafloria-html-layer > .axdb-slab > .axdb-slab-h.axdb-slab-h--tab {\n max-width: 100%; border-radius: var(--axdb-rs-radius, 8px);\n border-bottom: var(--axdb-caption-border-tab, none);\n box-shadow: inset 0 0 0 1px rgba(31, 36, 48, .08);\n}\n/* 'hover': an overlay. It reserves nothing, so while hidden it must not take\n the pointer (an invisible band swallowed clicks on the content beneath),\n and while shown it paints OPAQUE \u2014 a 5% tint over a chart is a smear. */\n.grafloria-html-layer > .axdb-slab > .axdb-slab-h.axdb-slab-h--hover {\n opacity: 0; pointer-events: none;\n background: var(--axdb-caption-bg, #fff);\n box-shadow: 0 1px 4px rgba(31, 36, 48, .16);\n}\n/* .axdb-slab--hot is set by the binder from the live pointer (the overlay\n takes no pointer of its own, so CSS :hover can never fire on it). */\n.grafloria-html-layer > .axdb-slab.axdb-slab--hot > .axdb-slab-h.axdb-slab-h--hover,\n.grafloria-html-layer > .axdb-slab.axdb-slab--selected > .axdb-slab-h.axdb-slab-h--hover { pointer-events: auto; opacity: 1; }\n/* the actions of a hovered or selected section */\n.grafloria-html-layer > .axdb-slab.axdb-slab--hot > .axdb-slab-h > .axdb-slab-h-actions { opacity: 1; }\n/* the tight tier: a section under 90 px */\n.grafloria-html-layer > .axdb-slab > .axdb-slab-h.axdb-slab-h--tight { font-size: min(var(--axdb-caption-font-size, 12px), 12px); gap: 4px; }\n.axdb-slab-h--tight .axdb-slab-h-sub, .axdb-slab-h--tight .axdb-slab-h-actions { display: none; }\n.axdb-slab-h-icon { flex: none; }\n.axdb-slab-h-body { display: flex; flex-direction: column; justify-content: center; min-width: 0; flex: 0 1 auto; }\n.axdb-slab-h-text, .axdb-slab-h-sub { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n.axdb-slab-h-sub { font-size: .88em; font-weight: 500; opacity: .7; }\n.axdb-slab-h-info { flex: none; opacity: .6; font-size: .9em; cursor: help; }\n.axdb-slab-h-actions { flex: none; display: flex; gap: 2px; margin-inline-start: auto; opacity: 0; transition: opacity .12s; }\n.axdb-slab-h--center .axdb-slab-h-actions, .axdb-slab-h--end .axdb-slab-h-actions { margin-inline-start: 0; }\n.axdb-slab-h:hover > .axdb-slab-h-actions, .axdb-slab--selected > .axdb-slab-h > .axdb-slab-h-actions, .axdb-slab-h:focus-within > .axdb-slab-h-actions { opacity: 1; }\n.axdb-slab-h-action {\n all: unset; box-sizing: border-box; width: 26px; height: 26px; display: inline-flex; align-items: center; justify-content: center;\n border-radius: 6px; cursor: pointer; font-size: 14px; line-height: 1; color: inherit; transition: background-color .12s;\n}\n.axdb-slab-h-action:hover { background: rgba(31, 36, 48, .08); }\n.axdb-slab-h-action:focus-visible { outline: 2px solid var(--axdb-accent-ring, rgba(59, 82, 217, .55)); outline-offset: -2px; }\n.axdb-slab-h-action[disabled] { opacity: .4; cursor: default; }\n.axdb-slab-h-action[disabled]:hover { background: none; }\n.grafloria-html-layer > .axdb-slab.axdb-slab--static > .axdb-slab-h { cursor: default; }\n@media (prefers-color-scheme: dark) {\n /* The band sits on the PAGE, whose ground the kit does not own (a host that\n stays light while the kit goes dark is common): a solid ink, not a tint,\n so the light caption always reads. */\n .grafloria-html-layer > .axdb-slab > .axdb-slab-h {\n background: var(--axdb-caption-bg, #1b2029);\n color: var(--axdb-caption-fg, #eceef4);\n border-bottom: var(--axdb-caption-border, 1px solid rgba(236, 238, 244, .12));\n }\n .grafloria-html-layer > .axdb-slab > .axdb-slab-h.axdb-slab-h--tab { box-shadow: inset 0 0 0 1px rgba(236, 238, 244, .12); }\n .grafloria-html-layer > .axdb-slab > .axdb-slab-h.axdb-slab-h--hover { background: var(--axdb-caption-bg, #1a1d25); box-shadow: 0 1px 4px rgba(0, 0, 0, .5); }\n .axdb-slab-h-action:hover { background: rgba(236, 238, 244, .1); }\n}\n\n/* TAB CONTAINER (0.4.27): a strip of pages across the container's top. The\n strip takes the pointer (the tabs are real buttons); the pages below it are\n ordinary boards. */\n.grafloria-html-layer > .axdb-tabs {\n position: absolute; box-sizing: border-box; pointer-events: auto; z-index: 5;\n display: flex; align-items: center; gap: 2px; padding: 3px 6px; overflow-x: auto; overflow-y: hidden;\n background: var(--axdb-tabs-bg, #f1f3f8);\n border-bottom: 1px solid var(--axdb-tabs-line, #e5e8ef);\n border-radius: var(--axdb-rs-radius, 8px) var(--axdb-rs-radius, 8px) 0 0;\n scrollbar-width: thin;\n}\n.axdb-tabs--center { justify-content: center; }\n.axdb-tabs--end { justify-content: flex-end; }\n.axdb-tabs--stretch > .axdb-tab { flex: 1 1 0; }\n/* A tab is a PILL on the track (a segmented control): quiet until hovered,\n the active one lifted in the card's own ground. */\n.axdb-tab {\n all: unset; box-sizing: border-box; flex: 0 0 auto; max-width: 200px;\n padding: 0 12px; height: 100%; display: inline-flex; align-items: center;\n font: 500 12.5px/1 system-ui, -apple-system, \"Segoe UI\", sans-serif;\n letter-spacing: -.005em;\n color: var(--axdb-tabs-fg, #5a6478); cursor: pointer;\n border-radius: var(--axdb-tab-radius, 6px);\n white-space: nowrap; overflow: hidden; text-overflow: ellipsis;\n transition: background-color .12s, color .12s, box-shadow .12s;\n}\n.axdb-tab:hover { background: rgba(31, 36, 48, .06); color: var(--axdb-tabs-on-fg, #1f2430); }\n.axdb-tab:focus-visible { outline: 2px solid var(--axdb-accent-ring, rgba(59, 82, 217, .55)); outline-offset: -2px; }\n.axdb-tab.axdb-tab--on {\n background: var(--axdb-tabs-on-bg, #fff);\n color: var(--axdb-tabs-on-fg, #1f2430);\n font-weight: 600;\n box-shadow: 0 1px 2px rgba(16, 24, 40, .1), 0 0 0 1px rgba(31, 36, 48, .06);\n}\n@media (prefers-reduced-motion: reduce) { .axdb-tab { transition: none; } }\n@media (prefers-color-scheme: dark) {\n /* Solid for the same reason as the caption band: the track sits on the page. */\n .grafloria-html-layer > .axdb-tabs { background: var(--axdb-tabs-bg, #1b2029); border-bottom-color: var(--axdb-tabs-line, rgba(236, 238, 244, .12)); }\n .axdb-tab { color: var(--axdb-tabs-fg, #98a1b4); }\n .axdb-tab:hover { background: rgba(236, 238, 244, .08); color: var(--axdb-tabs-on-fg, #eceef4); }\n .axdb-tab.axdb-tab--on { background: var(--axdb-tabs-on-bg, #232836); color: var(--axdb-tabs-on-fg, #eceef4); box-shadow: 0 1px 2px rgba(0, 0, 0, .45), 0 0 0 1px rgba(236, 238, 244, .1); }\n}\n\n/* legend chips, shared by line and donut */\n.axdb-lg { display: flex; flex-wrap: wrap; gap: 4px 12px; margin-top: 9px; }\n.axdb-lg--col { flex-direction: column; flex-wrap: nowrap; gap: 6px; margin-top: 0; }\n.axdb-lg i {\n display: inline-flex; align-items: center; gap: 5px; font-style: normal;\n font: 500 11px/1.3 system-ui, sans-serif; color: var(--axdb-muted);\n}\n.axdb-lg i b { width: 9px; height: 9px; border-radius: 3px; flex: none; }\n\n/* table */\n.axdb-table { width: 100%; border-collapse: collapse; font-size: 12px; color: var(--axdb-ink); }\n.axdb-table th {\n text-align: left; padding: 4px 8px 7px; border-bottom: 1px solid var(--axdb-line);\n font: 600 10px/1.3 system-ui, sans-serif; letter-spacing: .4px; text-transform: uppercase;\n color: var(--axdb-muted);\n}\n.axdb-table td { padding: 6px 8px; border-bottom: 1px solid var(--axdb-line); white-space: nowrap; }\n.axdb-table td.num { text-align: right; font-variant-numeric: tabular-nums; }\n.axdb-table tr:last-child td { border-bottom: none; }\n\n@media (prefers-color-scheme: dark) {\n .axdb-widget {\n --axdb-ink: #eceef4;\n --axdb-muted: #98a1b4;\n --axdb-grid: rgba(150, 160, 182, .2);\n --axdb-card: #161a22;\n --axdb-line: #2a3040;\n --axdb-head-bg: rgba(255, 255, 255, .035);\n --axdb-head-fg: #e6e9f0;\n --axdb-soft: rgba(150, 160, 182, .16);\n --axdb-up: #4ade80;\n --axdb-down: #fb7185;\n --axdb-c1: #8b9cff; --axdb-c2: #38bdf8; --axdb-c3: #2dd4bf;\n --axdb-c4: #fbbf24; --axdb-c5: #a78bfa; --axdb-c6: #94a3b8;\n }\n}\n";
26
26
  /** Idempotently inject the kit stylesheet (safe to call per binder). */
27
27
  export declare function ensureDashboardKitStyles(doc?: Document): void;
@@ -52,6 +52,31 @@ const CSS = `
52
52
  /* Outside the board: release will REMOVE — dim the ghost to say so. */
53
53
  .grafloria-html-layer > .grafloria-node-host.axdb-ghost.axdb-out { opacity: .35; filter: grayscale(.6); }
54
54
 
55
+ /* ===== the kit's chrome glides WITH the tiles it belongs to (0.4.43) =====
56
+ A pushed group's strip, slab and surface used to jump to the new slot while
57
+ its tiles slid there: one group, two motions. */
58
+ .grafloria-html-layer.axdb-glide > .axdb-tabs,
59
+ .grafloria-html-layer.axdb-glide > .axdb-slab,
60
+ .grafloria-html-layer.axdb-glide > .axdb-group-bg {
61
+ transition: left .28s cubic-bezier(.2, 0, .2, 1), top .28s cubic-bezier(.2, 0, .2, 1),
62
+ width .28s cubic-bezier(.2, 0, .2, 1), height .28s cubic-bezier(.2, 0, .2, 1);
63
+ }
64
+ @media (prefers-reduced-motion: reduce) {
65
+ .grafloria-html-layer.axdb-glide > .axdb-tabs,
66
+ .grafloria-html-layer.axdb-glide > .axdb-slab,
67
+ .grafloria-html-layer.axdb-glide > .axdb-group-bg { transition: none; }
68
+ }
69
+
70
+ /* ===== CARRIED (0.4.43): a group dragged by its strip or band moves as ONE thing =====
71
+ Its whole subtree — tiles, strips, slabs, surface — is transition-exempt like
72
+ the ghost and floats with it; the surface casts the shadow. */
73
+ .grafloria-html-layer > .axdb-carried,
74
+ .grafloria-html-layer.axdb-glide > .axdb-carried { transition: none; cursor: grabbing; }
75
+ .grafloria-html-layer > .grafloria-node-host.axdb-carried { z-index: 30; opacity: .92; }
76
+ .grafloria-html-layer > .axdb-group-bg.axdb-carried { z-index: 29; filter: drop-shadow(0 10px 16px rgba(16, 24, 40, .3)); }
77
+ .grafloria-html-layer > .axdb-slab.axdb-carried { z-index: 31; }
78
+ .grafloria-html-layer > .axdb-tabs.axdb-carried { z-index: 32; }
79
+
55
80
  /* ===== the placeholder: dashed slab, truthful, never animated ===== */
56
81
  .grafloria-html-layer > .axdb-ph {
57
82
  position: absolute;
@@ -432,6 +457,25 @@ const CSS = `
432
457
  .grafloria-html-layer > .axdb-slab.axdb-slab--selected > .axdb-rs { pointer-events: auto; opacity: 1; }
433
458
  .grafloria-html-layer > .axdb-slab.axdb-slab--static > .axdb-rs { display: none; }
434
459
 
460
+ /* GROUP FRAME (0.4.43): a TAB CONTAINER wears a frame by default — a bordered
461
+ slab, and a tinted surface UNDER its pages (the strip's own track colour, so
462
+ strip and body read as one panel). The layer is isolated so the surface can
463
+ sit below every tile at z -1 and still paint above the canvas. */
464
+ .grafloria-html-layer { isolation: isolate; }
465
+ .grafloria-html-layer > .axdb-group-bg {
466
+ position: absolute; pointer-events: none; z-index: -1; box-sizing: border-box;
467
+ border-radius: var(--axdb-rs-radius, 8px);
468
+ background: var(--axdb-group-bg, #f1f3f8);
469
+ }
470
+ .grafloria-html-layer > .axdb-slab.axdb-slab--tabs {
471
+ box-sizing: border-box;
472
+ border: 1px solid var(--axdb-group-border, var(--axdb-line, #e5e8ef));
473
+ }
474
+ @media (prefers-color-scheme: dark) {
475
+ .grafloria-html-layer > .axdb-group-bg { background: var(--axdb-group-bg, #1b2029); }
476
+ .grafloria-html-layer > .axdb-slab.axdb-slab--tabs { border-color: var(--axdb-group-border, rgba(236, 238, 244, .12)); }
477
+ }
478
+
435
479
  /* SECTION CAPTION (0.4.22): the band on the slab. Geometry is inline (the
436
480
  reserve and the pixels come from one function); everything visual is a
437
481
  variable the options write and a theme may override. The band takes the
@@ -9,6 +9,14 @@
9
9
  * binder's.
10
10
  */
11
11
  export interface TabsOptions {
12
+ /**
13
+ * Pixels between the frame and the pages — left, right, bottom, and below
14
+ * the strip (0.4.43). The cards of a page sit INSIDE the container's tinted
15
+ * frame, so two cards under one tab read as one panel; a border on the
16
+ * cell alone ran exactly where the cards' own borders were. Default 8;
17
+ * 0 puts the pages edge to edge, the look before 0.4.43.
18
+ */
19
+ inset?: number;
12
20
  /** Where the strip sits. Only 'top' today; 'bottom' | 'start' | 'end' follow. */
13
21
  position?: 'top';
14
22
  /** Along the strip (default 'start', mirrored on RTL). */
@@ -33,6 +41,9 @@ export declare const TAB_STRIP_HEIGHT = 30;
33
41
  export declare const TAB_DRAG_THRESHOLD = 4;
34
42
  /** Pixels the pages give up at the top of the container. */
35
43
  export declare function tabStripReserve(o: TabsOptions | undefined, pageCount: number): number;
44
+ export declare const TAB_PAGE_INSET = 8;
45
+ /** Pixels between the container's frame and its pages: left, right, bottom, and below the strip. */
46
+ export declare function tabPageInset(o: TabsOptions | undefined): number;
36
47
  /**
37
48
  * Paint the strip. Repainted only when its identity changes (the pages, the
38
49
  * active one, the options, RTL) so a page switch is a class toggle, not a
@@ -22,6 +22,12 @@ export function tabStripReserve(o, pageCount) {
22
22
  return 0;
23
23
  return Math.max(18, (_a = o === null || o === void 0 ? void 0 : o.height) !== null && _a !== void 0 ? _a : TAB_STRIP_HEIGHT);
24
24
  }
25
+ export const TAB_PAGE_INSET = 8;
26
+ /** Pixels between the container's frame and its pages: left, right, bottom, and below the strip. */
27
+ export function tabPageInset(o) {
28
+ var _a;
29
+ return Math.max(0, (_a = o === null || o === void 0 ? void 0 : o.inset) !== null && _a !== void 0 ? _a : TAB_PAGE_INSET);
30
+ }
25
31
  /**
26
32
  * Paint the strip. Repainted only when its identity changes (the pages, the
27
33
  * active one, the options, RTL) so a page switch is a class toggle, not a