@grafloria/element 0.4.22 → 0.4.23

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.22",
3
+ "version": "0.4.23",
4
4
  "type": "module",
5
5
  "main": "./src/index.js",
6
6
  "types": "./src/index.d.ts",
package/src/index.d.ts CHANGED
@@ -79,7 +79,7 @@ export type { JoinColumn, JoinEnd, MatchTier, JoinGuidanceApi, JoinGuidanceOptio
79
79
  export type { ErColumn, ErEntitySpec, ErRelationshipSpec, ErCardinality, ErSide, ErDiagramOptions, UmlClassSpec, UmlRelationshipSpec, UmlRelationKind, UmlSide, UmlDiagramOptions, ErEntityDelta, UmlClassDelta, CardEditingHandle, } from './lib/diagram-kit/index.js';
80
80
  export { erTable, umlClass, erTables, umlClasses, CardHandle, ErTable, ErField, ErColumnList, UmlClass, UmlMemberList, } from './lib/diagram-kit/index.js';
81
81
  export type { HandleApi } from './lib/diagram-kit/index.js';
82
- export { bindDashboardGrid, rowHeightFor, boardHeightFor, columnUnitFor, cellToRect, pointToCell, sizeToSpan, gridItemFromCell, cellFromGridItem, buildCommitCommands, ensureDashboardKitStyles, DASHBOARD_KIT_STYLE_ID, dashboard, defaultWidgetRenderer, renderKpiWidget, renderLineWidget, renderBarWidget, renderDonutWidget, renderFunnelWidget, renderTableWidget, BUILT_IN_WIDGET_KINDS, } from './lib/dashboard-kit/index.js';
82
+ export { bindDashboardGrid, normalizeCaption, captionReserve, captionBandHeight, paintCaptionBand, CAPTION_HEIGHT, CAPTION_HEIGHT_SUBTITLE, CAPTION_HEIGHT_TIGHT, CAPTION_PASS_THROUGH, rowHeightFor, boardHeightFor, columnUnitFor, cellToRect, pointToCell, sizeToSpan, gridItemFromCell, cellFromGridItem, buildCommitCommands, ensureDashboardKitStyles, DASHBOARD_KIT_STYLE_ID, dashboard, defaultWidgetRenderer, renderKpiWidget, renderLineWidget, renderBarWidget, renderDonutWidget, renderFunnelWidget, renderTableWidget, BUILT_IN_WIDGET_KINDS, } from './lib/dashboard-kit/index.js';
83
83
  export type { DashboardGridApi, DashboardGridOptions, DashboardGridHandle, CellRect, WorldRect, DashboardGridGeometry, TileDelta, DashboardOptions, DashboardSpec, DashboardSnapshot, DragHandleOption, DragGripOptions, DashboardHandle, DashboardViewSpec, DashboardWidgetSpec, SectionCaption, SectionCaptionOptions, SectionCaptionAction, SectionCaptionFont, WidgetHandle, WidgetRenderer, KpiWidgetData, LineWidgetData, LineSeries, BarWidgetData, DonutWidgetData, FunnelWidgetData, TableWidgetData, } from './lib/dashboard-kit/index.js';
84
84
  export { bindStencilPalette, bindShapeDataPanel, ensureStencilKitStyles } from './lib/stencil-kit/index.js';
85
85
  export type { StencilPaletteApi, StencilPaletteOptions, StencilPaletteHandle, ShapeDataPanelApi, ShapeDataPanelOptions, ShapeDataPanelHandle, } from './lib/stencil-kit/index.js';
package/src/index.js CHANGED
@@ -230,7 +230,7 @@ export { erTable, umlClass, erTables, umlClasses, CardHandle, ErTable, ErField,
230
230
  // GridItemConfig. Element-local code, so the Phase-0 wholesale re-export
231
231
  // below does NOT cover it — this curated block is its only door.
232
232
  // ===========================================================================
233
- export { bindDashboardGrid, rowHeightFor, boardHeightFor, columnUnitFor, cellToRect, pointToCell, sizeToSpan, gridItemFromCell, cellFromGridItem, buildCommitCommands, ensureDashboardKitStyles, DASHBOARD_KIT_STYLE_ID,
233
+ export { bindDashboardGrid, normalizeCaption, captionReserve, captionBandHeight, paintCaptionBand, CAPTION_HEIGHT, CAPTION_HEIGHT_SUBTITLE, CAPTION_HEIGHT_TIGHT, CAPTION_PASS_THROUGH, rowHeightFor, boardHeightFor, columnUnitFor, cellToRect, pointToCell, sizeToSpan, gridItemFromCell, cellFromGridItem, buildCommitCommands, ensureDashboardKitStyles, DASHBOARD_KIT_STYLE_ID,
234
234
  // The DATA-FIRST authoring API — the erDiagram()/umlDiagram() equivalent.
235
235
  dashboard,
236
236
  // …and the built-in renderers behind `kind`, so a dashboard is useful with
@@ -40,7 +40,12 @@ export interface SectionCaptionOptions {
40
40
  description?: string;
41
41
  /** A glyph before the text (emoji or short text). */
42
42
  icon?: string;
43
- /** 'inside' (default): a band inside the frame, reserved. 'tab': above the frame, nothing reserved. */
43
+ /**
44
+ * 'inside' (default): a band across the top of the frame. 'tab': the same
45
+ * band sized to its text, a label chip at the leading corner. BOTH reserve
46
+ * their height inside the SECTION's own cell — a header that hangs over the
47
+ * neighbour above is the overlap bug this feature exists to end.
48
+ */
44
49
  position?: 'inside' | 'tab';
45
50
  /** Horizontal alignment (default 'start', mirrored on RTL). */
46
51
  align?: 'start' | 'center' | 'end';
@@ -73,6 +78,10 @@ export declare const CAPTION_HEIGHT_SUBTITLE = 44;
73
78
  export declare const CAPTION_HEIGHT_TIGHT = 22;
74
79
  /** A section shorter than this steps its band down to the tight tier. */
75
80
  export declare const CAPTION_TIGHT_BELOW = 90;
81
+ /** A band never paints thinner than this. */
82
+ export declare const CAPTION_MIN_HEIGHT = 16;
83
+ /** …nor takes the last pixels: the children keep at least this much. */
84
+ export declare const CAPTION_MIN_CONTENT = 20;
76
85
  export declare const CAPTION_PASS_THROUGH = "button, a, input, select, textarea, [data-axdb-pass]";
77
86
  /** The caption as options, or null when there is none. `title` fills the text. */
78
87
  export declare function normalizeCaption(c: SectionCaption | undefined, title?: string): SectionCaptionOptions | null;
@@ -84,9 +93,21 @@ export declare function captionOfGroup(grp: {
84
93
  export declare function pairOf(v: number | [number, number] | undefined, dflt: [number, number]): [number, number];
85
94
  /** Painted at all? `show: 'design'` disappears under static. */
86
95
  export declare function captionPainted(c: SectionCaptionOptions | null, isStatic: boolean): boolean;
87
- /** The band's height for a section of `sectionH` px. */
96
+ /**
97
+ * The band's height for a section of `sectionH` px: the authored height (or
98
+ * the tier), CLAMPED so the children always keep `CAPTION_MIN_CONTENT` px. A
99
+ * one-row section used to hand its whole 34 px to a 22 px band and paint a
100
+ * 12 px sliver of a child.
101
+ */
88
102
  export declare function captionBandHeight(c: SectionCaptionOptions, sectionH: number): number;
89
- /** Pixels the nested board's frame gives up at the top: the band plus its vertical margins. */
103
+ /**
104
+ * Pixels the nested board's frame gives up at the top: the band plus its
105
+ * vertical margins. A TAB reserves too — it is a narrower band, not a header
106
+ * hanging over whatever the parent board put above the section (the stress
107
+ * page had one lying across a chart's legend, and one clipped off the top of
108
+ * the canvas). Only `show: 'hover'` reserves nothing: it is an overlay by
109
+ * definition, and it paints opaque so the content beneath stays readable.
110
+ */
90
111
  export declare function captionReserve(c: SectionCaptionOptions | null, ctx: {
91
112
  static: boolean;
92
113
  sectionH: number;
@@ -19,6 +19,10 @@ export const CAPTION_HEIGHT_SUBTITLE = 44;
19
19
  export const CAPTION_HEIGHT_TIGHT = 22;
20
20
  /** A section shorter than this steps its band down to the tight tier. */
21
21
  export const CAPTION_TIGHT_BELOW = 90;
22
+ /** A band never paints thinner than this. */
23
+ export const CAPTION_MIN_HEIGHT = 16;
24
+ /** …nor takes the last pixels: the children keep at least this much. */
25
+ export const CAPTION_MIN_CONTENT = 20;
22
26
  export const CAPTION_PASS_THROUGH = 'button, a, input, select, textarea, [data-axdb-pass]';
23
27
  /** The caption as options, or null when there is none. `title` fills the text. */
24
28
  export function normalizeCaption(c, title) {
@@ -50,18 +54,31 @@ export function captionPainted(c, isStatic) {
50
54
  return false;
51
55
  return !(c.show === 'design' && isStatic);
52
56
  }
53
- /** The band's height for a section of `sectionH` px. */
57
+ /**
58
+ * The band's height for a section of `sectionH` px: the authored height (or
59
+ * the tier), CLAMPED so the children always keep `CAPTION_MIN_CONTENT` px. A
60
+ * one-row section used to hand its whole 34 px to a 22 px band and paint a
61
+ * 12 px sliver of a child.
62
+ */
54
63
  export function captionBandHeight(c, sectionH) {
55
64
  var _a;
56
- if (sectionH > 0 && sectionH < CAPTION_TIGHT_BELOW)
57
- return CAPTION_HEIGHT_TIGHT;
58
- return (_a = c.height) !== null && _a !== void 0 ? _a : (c.subtitle ? CAPTION_HEIGHT_SUBTITLE : CAPTION_HEIGHT);
65
+ const want = captionTight(sectionH) ? CAPTION_HEIGHT_TIGHT : (_a = c.height) !== null && _a !== void 0 ? _a : (c.subtitle ? CAPTION_HEIGHT_SUBTITLE : CAPTION_HEIGHT);
66
+ if (sectionH <= 0)
67
+ return want;
68
+ return Math.max(CAPTION_MIN_HEIGHT, Math.min(want, sectionH - CAPTION_MIN_CONTENT));
59
69
  }
60
- /** Pixels the nested board's frame gives up at the top: the band plus its vertical margins. */
70
+ /**
71
+ * Pixels the nested board's frame gives up at the top: the band plus its
72
+ * vertical margins. A TAB reserves too — it is a narrower band, not a header
73
+ * hanging over whatever the parent board put above the section (the stress
74
+ * page had one lying across a chart's legend, and one clipped off the top of
75
+ * the canvas). Only `show: 'hover'` reserves nothing: it is an overlay by
76
+ * definition, and it paints opaque so the content beneath stays readable.
77
+ */
61
78
  export function captionReserve(c, ctx) {
62
79
  if (!c || !captionPainted(c, ctx.static))
63
80
  return 0;
64
- if (c.position === 'tab' || c.show === 'hover')
81
+ if (c.show === 'hover')
65
82
  return 0;
66
83
  const [mv] = pairOf(c.margin, [0, 0]);
67
84
  return captionBandHeight(c, ctx.sectionH) + 2 * mv;
@@ -72,13 +89,16 @@ export function captionKey(c, ctx) {
72
89
  }
73
90
  /** Is a section of `sectionH` px in the tight tier? */
74
91
  export const captionTight = (sectionH) => sectionH > 0 && sectionH < CAPTION_TIGHT_BELOW;
92
+ /** Where the band's box sits horizontally: a tab hugs the LEADING edge, mirrored on RTL. */
93
+ function bandSides(c, mh, rtl) {
94
+ if (c.position !== 'tab')
95
+ return { left: `${mh}px`, right: `${mh}px` };
96
+ return rtl ? { left: 'auto', right: `${mh}px` } : { left: `${mh}px`, right: 'auto' };
97
+ }
75
98
  /** The per-sync geometry of a painted band: height and tier follow the section's live size. */
76
99
  export function sizeCaptionBand(band, c, sectionH) {
77
- const h = captionBandHeight(c, sectionH);
78
100
  band.classList.toggle('axdb-slab-h--tight', captionTight(sectionH));
79
- band.style.height = `${h}px`;
80
- if (c.position === 'tab')
81
- band.style.top = `${-h}px`;
101
+ band.style.height = `${captionBandHeight(c, sectionH)}px`;
82
102
  }
83
103
  /**
84
104
  * Is a press on `target`, inside `band`, content rather than the band? An
@@ -129,9 +149,10 @@ export function paintCaptionBand(band, c, ctx) {
129
149
  band.classList.add(k);
130
150
  band.setAttribute('dir', ctx.rtl ? 'rtl' : 'ltr');
131
151
  band.style.height = `${h}px`;
132
- band.style.top = c.position === 'tab' ? `${-h}px` : `${mv}px`;
133
- band.style.left = `${mh}px`;
134
- band.style.right = c.position === 'tab' ? 'auto' : `${mh}px`; // a tab is sized to its text
152
+ band.style.top = `${mv}px`;
153
+ const sides = bandSides(c, mh, ctx.rtl);
154
+ band.style.left = sides.left;
155
+ band.style.right = sides.right;
135
156
  const v = (name, value) => {
136
157
  if (value === undefined)
137
158
  band.style.removeProperty(name);
@@ -436,6 +436,14 @@ export interface DashboardGridHandle {
436
436
  * the feature).
437
437
  */
438
438
  interface BinderPeer {
439
+ /**
440
+ * Does this board paint SECTION CHROME (the slab overlay that carries the
441
+ * ring, the corner handle and the caption band) for its member groups? Only
442
+ * the grid binder does. A section reserves its caption's pixels ONLY when
443
+ * its parent paints one — otherwise a captioned section inside a SPLIT
444
+ * board pushed its children down by 44 px under a band nobody drew.
445
+ */
446
+ paintsCaptions?: boolean;
439
447
  group: GroupModel;
440
448
  /** True when this board's engine holds `id` as an item (member lookup). */
441
449
  hasItem(id: string): boolean;
@@ -453,7 +453,12 @@ export function bindDashboardGrid(api, group, options = {}) {
453
453
  * band is outside `containsWorld`, so a press on it is the PARENT's (it
454
454
  * selects the section) rather than an empty press of this board.
455
455
  */
456
- const ownReserve = () => { var _a, _b; return captionReserve(captionOfGroup(group), { static: isStatic, sectionH: (_b = (_a = group.size) === null || _a === void 0 ? void 0 : _a.height) !== null && _b !== void 0 ? _b : 0 }); };
456
+ const ownReserve = () => {
457
+ var _a, _b, _c;
458
+ return ((_a = parentPeer()) === null || _a === void 0 ? void 0 : _a.paintsCaptions) === true
459
+ ? captionReserve(captionOfGroup(group), { static: isStatic, sectionH: (_c = (_b = group.size) === null || _b === void 0 ? void 0 : _b.height) !== null && _c !== void 0 ? _c : 0 })
460
+ : 0;
461
+ };
457
462
  const frame = () => {
458
463
  var _a, _b, _c, _d;
459
464
  const r = ownReserve();
@@ -2186,6 +2191,7 @@ export function bindDashboardGrid(api, group, options = {}) {
2186
2191
  containsWorldExtended: worldInsideBoardExtended,
2187
2192
  frameArea: boardArea,
2188
2193
  adopt,
2194
+ paintsCaptions: true, // the grid binder owns the slab overlays (syncSlabs)
2189
2195
  };
2190
2196
  peersOnCanvas().add(selfPeer);
2191
2197
  selfPeerRef = selfPeer;
@@ -2412,10 +2418,34 @@ export function bindDashboardGrid(api, group, options = {}) {
2412
2418
  * container; the corner handle keeps its own cursor from the stylesheet.
2413
2419
  */
2414
2420
  let hoverHost = null;
2421
+ /**
2422
+ * A `show: 'hover'` caption cannot ride CSS `:hover`: the slab overlay takes
2423
+ * no pointer (by design — it must never steal a press), and while the band
2424
+ * is hidden it takes none either, so nothing in the section is ever hovered
2425
+ * in CSS terms. The binder already tracks the pointer; it marks the section
2426
+ * under it instead.
2427
+ */
2428
+ const markHotSection = (clientX, clientY) => {
2429
+ if (!slabEls.size)
2430
+ return;
2431
+ for (const [id, el] of slabEls) {
2432
+ if (!el.querySelector(':scope > .axdb-slab-h--hover'))
2433
+ continue;
2434
+ const r = el.getBoundingClientRect();
2435
+ const hot = clientX >= r.left && clientX <= r.right && clientY >= r.top && clientY <= r.bottom;
2436
+ el.classList.toggle('axdb-slab--hot', hot);
2437
+ void id;
2438
+ }
2439
+ };
2440
+ const onHoverLeave = () => {
2441
+ for (const el of slabEls.values())
2442
+ el.classList.remove('axdb-slab--hot');
2443
+ };
2415
2444
  const onHover = (e) => {
2416
2445
  var _a, _b, _c, _d, _e, _f, _g, _h;
2417
2446
  if (disposed || gesture)
2418
2447
  return;
2448
+ markHotSection(e.clientX, e.clientY);
2419
2449
  // The event may target the host, its content, or (when a host's content
2420
2450
  // is pointer-transparent) the canvas under it — find the member host by
2421
2451
  // the pointer's position in that case.
@@ -2465,6 +2495,7 @@ export function bindDashboardGrid(api, group, options = {}) {
2465
2495
  host.removeAttribute('data-axdb-edge');
2466
2496
  };
2467
2497
  api.container.addEventListener('pointermove', onHover, { passive: true });
2498
+ api.container.addEventListener('pointerleave', onHoverLeave, { passive: true });
2468
2499
  /**
2469
2500
  * STATIC BOARDS LET CONTENT BE CLICKED. The renderer prevents the default of
2470
2501
  * every press a tool claims, which cancels the compatibility mouse events —
@@ -3041,6 +3072,7 @@ export function bindDashboardGrid(api, group, options = {}) {
3041
3072
  peersOnCanvas().delete(selfPeer);
3042
3073
  unregisterTool();
3043
3074
  api.container.removeEventListener('pointermove', onHover);
3075
+ api.container.removeEventListener('pointerleave', onHoverLeave);
3044
3076
  guardedLayer === null || guardedLayer === void 0 ? void 0 : guardedLayer.removeEventListener('pointerdown', staticGuard);
3045
3077
  api.container.removeEventListener('focusin', onFocusIn);
3046
3078
  api.container.removeEventListener('keydown', onKey);
@@ -105,8 +105,19 @@ export function bindDashboardSplit(api, group, options = {}) {
105
105
  let focusedId;
106
106
  const live = liveRegionFor(api.container);
107
107
  // -- geometry ---------------------------------------------------------------
108
- /** Our own caption band's pixels, given up at the top of the pane (see grid-binder). */
109
- const ownReserve = () => { var _a, _b; return captionReserve(captionOfGroup(group), { static: isStatic, sectionH: (_b = (_a = group.size) === null || _a === void 0 ? void 0 : _a.height) !== null && _b !== void 0 ? _b : 0 }); };
108
+ /**
109
+ * Our own caption band's pixels, given up at the top of the pane but only
110
+ * when the parent board actually paints one (see BinderPeer.paintsCaptions).
111
+ * A split board paints no section chrome, so a captioned section inside one
112
+ * reserves nothing until the tab-container round gives the split binder its
113
+ * own overlays.
114
+ */
115
+ const ownReserve = () => {
116
+ var _a, _b, _c;
117
+ return ((_a = parentPeerOf(api.container, group.id)) === null || _a === void 0 ? void 0 : _a.paintsCaptions) === true
118
+ ? captionReserve(captionOfGroup(group), { static: isStatic, sectionH: (_c = (_b = group.size) === null || _b === void 0 ? void 0 : _b.height) !== null && _c !== void 0 ? _c : 0 })
119
+ : 0;
120
+ };
110
121
  const frame = () => {
111
122
  var _a, _b, _c, _d;
112
123
  const r = ownReserve();
@@ -373,8 +373,10 @@ const CSS = `
373
373
  position: absolute; box-sizing: border-box; pointer-events: auto; z-index: 1;
374
374
  display: flex; align-items: center; gap: 6px; min-width: 0;
375
375
  padding: var(--axdb-caption-pad, 0 10px);
376
- background: var(--axdb-caption-bg, rgba(31, 36, 48, .045));
377
- border-bottom: var(--axdb-caption-border, none);
376
+ background: var(--axdb-caption-bg, rgba(31, 36, 48, .055));
377
+ /* A hairline by default: two bands meeting (a captioned section inside a
378
+ captioned section) read as two headers, not one grey block. */
379
+ border-bottom: var(--axdb-caption-border, 1px solid rgba(31, 36, 48, .1));
378
380
  color: var(--axdb-caption-fg, #1f2430);
379
381
  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);
380
382
  text-transform: var(--axdb-caption-transform, none);
@@ -388,13 +390,27 @@ const CSS = `
388
390
  .grafloria-html-layer > .axdb-slab > .axdb-slab-h.axdb-slab-h--end { justify-content: flex-end; }
389
391
  .grafloria-html-layer > .axdb-slab > .axdb-slab-h.axdb-slab-h--vtop { align-items: flex-start; }
390
392
  .grafloria-html-layer > .axdb-slab > .axdb-slab-h.axdb-slab-h--vbottom { align-items: flex-end; }
391
- /* 'tab': above the frame, sized to its text */
392
- .grafloria-html-layer > .axdb-slab > .axdb-slab-h.axdb-slab-h--tab { right: auto; max-width: 100%; }
393
- /* 'hover': an overlay that appears with the pointer or the selection */
394
- .grafloria-html-layer > .axdb-slab > .axdb-slab-h.axdb-slab-h--hover { opacity: 0; }
395
- .grafloria-html-layer > .axdb-slab:hover > .axdb-slab-h.axdb-slab-h--hover,
396
- .grafloria-html-layer > .axdb-slab.axdb-slab--selected > .axdb-slab-h.axdb-slab-h--hover,
397
- .grafloria-html-layer > .axdb-slab > .axdb-slab-h.axdb-slab-h--hover:focus-within { opacity: 1; }
393
+ /* 'tab': the same band sized to its text, a chip at the leading corner. It
394
+ reserves its height like 'inside' see captionReserve. */
395
+ .grafloria-html-layer > .axdb-slab > .axdb-slab-h.axdb-slab-h--tab {
396
+ max-width: 100%; border-radius: var(--axdb-rs-radius, 3px);
397
+ border-bottom: var(--axdb-caption-border-tab, none);
398
+ box-shadow: inset 0 0 0 1px rgba(31, 36, 48, .08);
399
+ }
400
+ /* 'hover': an overlay. It reserves nothing, so while hidden it must not take
401
+ the pointer (an invisible band swallowed clicks on the content beneath),
402
+ and while shown it paints OPAQUE — a 5% tint over a chart is a smear. */
403
+ .grafloria-html-layer > .axdb-slab > .axdb-slab-h.axdb-slab-h--hover {
404
+ opacity: 0; pointer-events: none;
405
+ background: var(--axdb-caption-bg, #fff);
406
+ box-shadow: 0 1px 4px rgba(31, 36, 48, .16);
407
+ }
408
+ /* .axdb-slab--hot is set by the binder from the live pointer (the overlay
409
+ takes no pointer of its own, so CSS :hover can never fire on it). */
410
+ .grafloria-html-layer > .axdb-slab.axdb-slab--hot > .axdb-slab-h.axdb-slab-h--hover,
411
+ .grafloria-html-layer > .axdb-slab.axdb-slab--selected > .axdb-slab-h.axdb-slab-h--hover { pointer-events: auto; opacity: 1; }
412
+ /* the actions of a hovered or selected section */
413
+ .grafloria-html-layer > .axdb-slab.axdb-slab--hot > .axdb-slab-h > .axdb-slab-h-actions { opacity: 1; }
398
414
  /* the tight tier: a section under 90 px */
399
415
  .grafloria-html-layer > .axdb-slab > .axdb-slab-h.axdb-slab-h--tight { font-size: min(var(--axdb-caption-font-size, 12px), 12px); gap: 4px; }
400
416
  .axdb-slab-h--tight .axdb-slab-h-sub, .axdb-slab-h--tight .axdb-slab-h-actions { display: none; }
@@ -416,7 +432,13 @@ const CSS = `
416
432
  .axdb-slab-h-action[disabled]:hover { background: none; }
417
433
  .grafloria-html-layer > .axdb-slab.axdb-slab--static > .axdb-slab-h { cursor: default; }
418
434
  @media (prefers-color-scheme: dark) {
419
- .grafloria-html-layer > .axdb-slab > .axdb-slab-h { background: var(--axdb-caption-bg, rgba(236, 238, 244, .06)); color: var(--axdb-caption-fg, #eceef4); }
435
+ .grafloria-html-layer > .axdb-slab > .axdb-slab-h {
436
+ background: var(--axdb-caption-bg, rgba(236, 238, 244, .07));
437
+ color: var(--axdb-caption-fg, #eceef4);
438
+ border-bottom: var(--axdb-caption-border, 1px solid rgba(236, 238, 244, .12));
439
+ }
440
+ .grafloria-html-layer > .axdb-slab > .axdb-slab-h.axdb-slab-h--tab { box-shadow: inset 0 0 0 1px rgba(236, 238, 244, .12); }
441
+ .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); }
420
442
  .axdb-slab-h-action:hover { background: rgba(236, 238, 244, .1); }
421
443
  }
422
444