@grafloria/element 0.4.35 → 0.4.37
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 +1 -1
- package/src/index.d.ts +1 -1
- package/src/index.js +1 -1
- package/src/lib/dashboard-kit/dashboard.d.ts +3 -0
- package/src/lib/dashboard-kit/dashboard.js +56 -26
- package/src/lib/dashboard-kit/grid-binder.js +7 -0
- package/src/lib/dashboard-kit/index.d.ts +1 -1
- package/src/lib/dashboard-kit/index.js +1 -1
- package/src/lib/dashboard-kit/split-binder.js +189 -4
- package/src/lib/dashboard-kit/styles.d.ts +2 -0
- package/src/lib/dashboard-kit/styles.js +90 -56
- package/src/lib/load.js +5 -3
package/package.json
CHANGED
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, paintTabStrip, tabStripReserve, tabStripKey, TAB_STRIP_HEIGHT, TAB_DRAG_THRESHOLD, 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';
|
|
82
|
+
export { bindDashboardGrid, paintTabStrip, tabStripReserve, tabStripKey, TAB_STRIP_HEIGHT, TAB_DRAG_THRESHOLD, 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_KIT_CSS, 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, TabsOptions, TabPage, 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, paintTabStrip, tabStripReserve, tabStripKey, TAB_STRIP_HEIGHT, TAB_DRAG_THRESHOLD, 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,
|
|
233
|
+
export { bindDashboardGrid, paintTabStrip, tabStripReserve, tabStripKey, TAB_STRIP_HEIGHT, TAB_DRAG_THRESHOLD, 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_KIT_CSS,
|
|
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
|
|
@@ -626,6 +626,9 @@ export interface DashboardHandleContext {
|
|
|
626
626
|
active: string;
|
|
627
627
|
/** MUTABLE — set by the caller's finalize once the render API exists. */
|
|
628
628
|
apiRef: DashboardApiRef | null;
|
|
629
|
+
/** MUTABLE — the canvas element, set with apiRef; parking a page or a view
|
|
630
|
+
* reaches the html layer through it (see `teleport`). */
|
|
631
|
+
container: HTMLElement | null;
|
|
629
632
|
/** The consumer's layout hook, if any (dashboard() passes its option). */
|
|
630
633
|
onLayoutChange?: (viewId: string, widgets: DashboardWidgetSpec[]) => void;
|
|
631
634
|
/**
|
|
@@ -184,6 +184,29 @@ class SetWidgetLockCommand extends Command {
|
|
|
184
184
|
const HISTORY_EVENTS = ['command:executed', 'command:undone', 'command:redone'];
|
|
185
185
|
const DEFAULTS = { columns: 12, gap: 8, rowHeight: 130, width: 1180, height: 660 };
|
|
186
186
|
const OFFSCREEN_X = -20000;
|
|
187
|
+
/**
|
|
188
|
+
* Parking is a TELEPORT, never a glide. `.axdb-glide` on the html layer eases
|
|
189
|
+
* every left/top write while a gesture runs and for 400 ms past its drop; a
|
|
190
|
+
* page or a view coming back from OFFSCREEN_X under it SLID in from 20,000 px
|
|
191
|
+
* away over 280 ms (0.4.36). The class comes off for the writes, the styles
|
|
192
|
+
* are flushed so the landing becomes the before-change state, and the class
|
|
193
|
+
* goes back to whichever gesture still holds it.
|
|
194
|
+
*/
|
|
195
|
+
const teleport = (container, fn) => {
|
|
196
|
+
const layer = container === null || container === void 0 ? void 0 : container.querySelector('.grafloria-html-layer');
|
|
197
|
+
const held = (layer === null || layer === void 0 ? void 0 : layer.classList.contains('axdb-glide')) === true;
|
|
198
|
+
if (held)
|
|
199
|
+
layer.classList.remove('axdb-glide');
|
|
200
|
+
try {
|
|
201
|
+
fn();
|
|
202
|
+
}
|
|
203
|
+
finally {
|
|
204
|
+
if (held && layer) {
|
|
205
|
+
void layer.offsetWidth; // flush: the new positions are the before-change style now
|
|
206
|
+
layer.classList.add('axdb-glide');
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
};
|
|
187
210
|
let autoId = 0;
|
|
188
211
|
/** The node a widget spec becomes — one place, so addWidget() and the initial
|
|
189
212
|
* spec build can never drift apart on metadata. */
|
|
@@ -1305,18 +1328,20 @@ export function createDashboardHandle(ctx) {
|
|
|
1305
1328
|
return ctx.active;
|
|
1306
1329
|
},
|
|
1307
1330
|
showView(id) {
|
|
1308
|
-
var _a, _b, _c;
|
|
1309
1331
|
if (!groups.has(id))
|
|
1310
1332
|
return;
|
|
1311
1333
|
ctx.active = id;
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
g.
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1334
|
+
teleport(ctx.container, () => {
|
|
1335
|
+
var _a, _b, _c;
|
|
1336
|
+
for (const [vid, g] of groups) {
|
|
1337
|
+
const x = vid === id ? 0 : OFFSCREEN_X;
|
|
1338
|
+
const s = (_a = g.size) !== null && _a !== void 0 ? _a : { width: ctx.boardW, height: ctx.boardH };
|
|
1339
|
+
if (g.position.x !== x)
|
|
1340
|
+
g.setFrame({ x, y: 0, width: s.width, height: s.height });
|
|
1341
|
+
}
|
|
1342
|
+
(_b = binders.get(id)) === null || _b === void 0 ? void 0 : _b.sync();
|
|
1343
|
+
(_c = ctx.apiRef) === null || _c === void 0 ? void 0 : _c.renderNow();
|
|
1344
|
+
});
|
|
1320
1345
|
frameView(groups.get(id));
|
|
1321
1346
|
},
|
|
1322
1347
|
widget(id) {
|
|
@@ -1411,7 +1436,7 @@ export function createDashboardHandle(ctx) {
|
|
|
1411
1436
|
},
|
|
1412
1437
|
getCaption: (id) => { var _a; return (_a = specById.get(id)) === null || _a === void 0 ? void 0 : _a.caption; },
|
|
1413
1438
|
activateTab(containerId, pageId) {
|
|
1414
|
-
var _a, _b, _c, _d, _e
|
|
1439
|
+
var _a, _b, _c, _d, _e;
|
|
1415
1440
|
const cg = ctx.boardGroups.get(containerId);
|
|
1416
1441
|
const w = specById.get(containerId);
|
|
1417
1442
|
if (!cg || !w || ((_a = ctx.layoutOf.get(containerId)) !== null && _a !== void 0 ? _a : w.layout) !== 'tabs')
|
|
@@ -1424,9 +1449,12 @@ export function createDashboardHandle(ctx) {
|
|
|
1424
1449
|
w.active = pageId;
|
|
1425
1450
|
const cw = (_c = cg.getMetadata('containerWidget')) !== null && _c !== void 0 ? _c : {};
|
|
1426
1451
|
cg.setMetadata('containerWidget', Object.assign(Object.assign({}, cw), { active: pageId }));
|
|
1427
|
-
(
|
|
1428
|
-
|
|
1429
|
-
|
|
1452
|
+
teleport(ctx.container, () => {
|
|
1453
|
+
var _a, _b;
|
|
1454
|
+
(_a = ctx.syncTabs) === null || _a === void 0 ? void 0 : _a.call(ctx, containerId);
|
|
1455
|
+
(_b = ctx.apiRef) === null || _b === void 0 ? void 0 : _b.renderNow();
|
|
1456
|
+
});
|
|
1457
|
+
(_d = ctx.onTabChange) === null || _d === void 0 ? void 0 : _d.call(ctx, containerId, pageId, (_e = ctx.viewOfBoard.get(containerId)) !== null && _e !== void 0 ? _e : ctx.active);
|
|
1430
1458
|
reportChanged();
|
|
1431
1459
|
return true;
|
|
1432
1460
|
},
|
|
@@ -1922,6 +1950,7 @@ export function dashboard(options) {
|
|
|
1922
1950
|
optionsBase: options,
|
|
1923
1951
|
active: (_p = (_o = views[0]) === null || _o === void 0 ? void 0 : _o.id) !== null && _p !== void 0 ? _p : 'main',
|
|
1924
1952
|
apiRef: null,
|
|
1953
|
+
container: null,
|
|
1925
1954
|
onLayoutChange: options.onLayoutChange,
|
|
1926
1955
|
};
|
|
1927
1956
|
const { binders, groups } = ctx;
|
|
@@ -1936,24 +1965,25 @@ export function dashboard(options) {
|
|
|
1936
1965
|
}, get handle() {
|
|
1937
1966
|
return handle;
|
|
1938
1967
|
}, finalize: (api) => {
|
|
1939
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
|
|
1968
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p;
|
|
1940
1969
|
const a = api;
|
|
1941
1970
|
if (!a)
|
|
1942
1971
|
return;
|
|
1943
1972
|
ctx.apiRef = a;
|
|
1973
|
+
ctx.container = (_a = a.container) !== null && _a !== void 0 ? _a : null;
|
|
1944
1974
|
const model = a.getModel();
|
|
1945
1975
|
// FLUID: the board starts at the container's box when it can be measured
|
|
1946
1976
|
// (the binder keeps it there); the authored defaults only fill in for a
|
|
1947
1977
|
// container with no size yet.
|
|
1948
1978
|
const box = mode === 'fluid'
|
|
1949
|
-
? { w: ((
|
|
1979
|
+
? { w: ((_b = a.container) === null || _b === void 0 ? void 0 : _b.clientWidth) || 0, h: ((_c = a.container) === null || _c === void 0 ? void 0 : _c.clientHeight) || 0 }
|
|
1950
1980
|
: { w: 0, h: 0 };
|
|
1951
1981
|
const viewW = (v) => { var _a; return (mode === 'fluid' && box.w > 0 ? box.w : (_a = v.width) !== null && _a !== void 0 ? _a : boardW); };
|
|
1952
1982
|
const viewH = (v) => { var _a; return (mode === 'fluid' && box.h > 0 ? box.h : (_a = v.height) !== null && _a !== void 0 ? _a : boardH); };
|
|
1953
1983
|
for (const v of views) {
|
|
1954
1984
|
// One board per view; the group is a pure LAYOUT CONTAINER, so its
|
|
1955
1985
|
// chrome is suppressed (frameChrome) exactly as a dashboard needs.
|
|
1956
|
-
const g = new GroupModel({ id: v.id, name: (
|
|
1986
|
+
const g = new GroupModel({ id: v.id, name: (_d = v.name) !== null && _d !== void 0 ? _d : v.id });
|
|
1957
1987
|
model.addGroup(g);
|
|
1958
1988
|
g.setMetadata('frameChrome', 'none');
|
|
1959
1989
|
// THE BOARD'S GEOMETRY, PERSISTED. Cells alone do not describe a board:
|
|
@@ -1963,28 +1993,28 @@ export function dashboard(options) {
|
|
|
1963
1993
|
// reload would be a picture of a dashboard rather than a dashboard.
|
|
1964
1994
|
// Serializable values only: the binder's callbacks belong to the app.
|
|
1965
1995
|
g.setMetadata('dashboardBoard', {
|
|
1966
|
-
columns: (
|
|
1996
|
+
columns: (_e = v.columns) !== null && _e !== void 0 ? _e : columns,
|
|
1967
1997
|
gap,
|
|
1968
1998
|
padding: gap,
|
|
1969
1999
|
sizing,
|
|
1970
2000
|
baseRowHeight: rowHeight,
|
|
1971
2001
|
designHeight: viewH(v),
|
|
1972
|
-
float: (
|
|
1973
|
-
rtl: (
|
|
2002
|
+
float: (_f = options.float) !== null && _f !== void 0 ? _f : false,
|
|
2003
|
+
rtl: (_g = options.rtl) !== null && _g !== void 0 ? _g : false,
|
|
1974
2004
|
fluid: mode === 'fluid',
|
|
1975
2005
|
overflow,
|
|
1976
|
-
static: (
|
|
1977
|
-
dragHandle: (
|
|
1978
|
-
layout: (
|
|
2006
|
+
static: (_h = options.static) !== null && _h !== void 0 ? _h : false,
|
|
2007
|
+
dragHandle: (_j = options.dragHandle) !== null && _j !== void 0 ? _j : false,
|
|
2008
|
+
layout: (_k = ctx.layoutOf.get(v.id)) !== null && _k !== void 0 ? _k : layout,
|
|
1979
2009
|
});
|
|
1980
|
-
if (((
|
|
2010
|
+
if (((_l = ctx.layoutOf.get(v.id)) !== null && _l !== void 0 ? _l : layout) === 'split' && v.tree !== undefined)
|
|
1981
2011
|
g.setMetadata(SPLIT_TREE_KEY, v.tree);
|
|
1982
2012
|
g.size = { width: viewW(v), height: viewH(v), depth: 0 };
|
|
1983
2013
|
g.position = { x: v.id === ctx.active ? 0 : OFFSCREEN_X, y: 0 };
|
|
1984
2014
|
groups.set(v.id, g);
|
|
1985
2015
|
ctx.boardGroups.set(v.id, g);
|
|
1986
2016
|
mountBoard(v.id, v.id, v.widgets, g);
|
|
1987
|
-
binders.set(v.id, bindView(v, g, (
|
|
2017
|
+
binders.set(v.id, bindView(v, g, (_m = ctx.layoutOf.get(v.id)) !== null && _m !== void 0 ? _m : layout));
|
|
1988
2018
|
}
|
|
1989
2019
|
// LIVE LAYOUT SWITCH (setLayout): keep the picture, swap the binder.
|
|
1990
2020
|
// Either way the outgoing binder's cells are written where the grid
|
|
@@ -2100,8 +2130,8 @@ export function dashboard(options) {
|
|
|
2100
2130
|
// runs. That cost me a debugging round: `ctx.syncTabs` stayed undefined
|
|
2101
2131
|
// and every tab switch silently did nothing.)
|
|
2102
2132
|
ctx.onTabChange = options.onTabChange;
|
|
2103
|
-
attachTabsRuntime(ctx, model, (
|
|
2104
|
-
(
|
|
2133
|
+
attachTabsRuntime(ctx, model, (_o = a.container) !== null && _o !== void 0 ? _o : null, handle);
|
|
2134
|
+
(_p = ctx.attachHistory) === null || _p === void 0 ? void 0 : _p.call(ctx);
|
|
2105
2135
|
return;
|
|
2106
2136
|
/**
|
|
2107
2137
|
* Mount one board's widgets into its group — and recurse for CONTAINERS.
|
|
@@ -3038,6 +3038,12 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
3038
3038
|
}
|
|
3039
3039
|
finally {
|
|
3040
3040
|
flushDeferredRebuild();
|
|
3041
|
+
// EVERY press ends disarmed. The press armed the glide (a task ahead
|
|
3042
|
+
// of any displacement — see onPointerDown); a drop disarms on its own
|
|
3043
|
+
// path, but a press that never travelled — a plain CLICK — left it
|
|
3044
|
+
// armed for good, and every later left/top write eased: a tab switch
|
|
3045
|
+
// slid its page in from 20,000 px off canvas over 280 ms (0.4.36).
|
|
3046
|
+
disarmGlideSoon();
|
|
3041
3047
|
}
|
|
3042
3048
|
},
|
|
3043
3049
|
onCancel() {
|
|
@@ -3047,6 +3053,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
3047
3053
|
}
|
|
3048
3054
|
finally {
|
|
3049
3055
|
flushDeferredRebuild();
|
|
3056
|
+
disarmGlideSoon();
|
|
3050
3057
|
}
|
|
3051
3058
|
},
|
|
3052
3059
|
};
|
|
@@ -2,7 +2,7 @@ export { bindDashboardGrid, type DashboardGridApi, type DashboardGridOptions, ty
|
|
|
2
2
|
export { bindDashboardSplit, SetSplitTreeCommand, SPLIT_TREE_KEY, type DashboardSplitOptions, type DashboardSplitHandle, } from './split-binder.js';
|
|
3
3
|
export { projectSplit, dividersOf, addSplitLeaf, removeSplitLeaf, insertSplitLeaf, moveSplitDivider, splitFromCells, cellsFromSplit, splitLeaves, type SplitNode, type SplitGroup, type SplitLeaf, type SplitDir, type SplitSide, type SplitDivider, } from './split-layout.js';
|
|
4
4
|
export { rowHeightFor, boardHeightFor, columnUnitFor, cellToRect, pointToCell, sizeToSpan, spanWidthPx, gridItemFromCell, cellFromGridItem, buildCommitCommands, type CellRect, type WorldRect, type DashboardGridGeometry, type TileDelta, } from './grid-mapping.js';
|
|
5
|
-
export { ensureDashboardKitStyles, DASHBOARD_KIT_STYLE_ID } from './styles.js';
|
|
5
|
+
export { ensureDashboardKitStyles, DASHBOARD_KIT_STYLE_ID, DASHBOARD_KIT_CSS } from './styles.js';
|
|
6
6
|
export { paintTabStrip, tabStripReserve, tabStripKey, TAB_STRIP_HEIGHT, TAB_DRAG_THRESHOLD, type TabsOptions, type TabPage } from './tabs.js';
|
|
7
7
|
export { normalizeCaption, captionReserve, captionBandHeight, paintCaptionBand, CAPTION_HEIGHT, CAPTION_HEIGHT_SUBTITLE, CAPTION_HEIGHT_TIGHT, CAPTION_PASS_THROUGH, type SectionCaption, type SectionCaptionOptions, type SectionCaptionAction, type SectionCaptionFont, } from './caption.js';
|
|
8
8
|
export { dashboard, type DashboardOptions, type DashboardSpec, type DashboardSnapshot, type DashboardHandle, type DashboardViewSpec, type DashboardWidgetSpec, type WidgetHandle, } from './dashboard.js';
|
|
@@ -2,7 +2,7 @@ export { bindDashboardGrid, normalizeDragHandle, } from './grid-binder.js';
|
|
|
2
2
|
export { bindDashboardSplit, SetSplitTreeCommand, SPLIT_TREE_KEY, } from './split-binder.js';
|
|
3
3
|
export { projectSplit, dividersOf, addSplitLeaf, removeSplitLeaf, insertSplitLeaf, moveSplitDivider, splitFromCells, cellsFromSplit, splitLeaves, } from './split-layout.js';
|
|
4
4
|
export { rowHeightFor, boardHeightFor, columnUnitFor, cellToRect, pointToCell, sizeToSpan, spanWidthPx, gridItemFromCell, cellFromGridItem, buildCommitCommands, } from './grid-mapping.js';
|
|
5
|
-
export { ensureDashboardKitStyles, DASHBOARD_KIT_STYLE_ID } from './styles.js';
|
|
5
|
+
export { ensureDashboardKitStyles, DASHBOARD_KIT_STYLE_ID, DASHBOARD_KIT_CSS } from './styles.js';
|
|
6
6
|
export { paintTabStrip, tabStripReserve, tabStripKey, TAB_STRIP_HEIGHT, TAB_DRAG_THRESHOLD } from './tabs.js';
|
|
7
7
|
export { normalizeCaption, captionReserve, captionBandHeight, paintCaptionBand, CAPTION_HEIGHT, CAPTION_HEIGHT_SUBTITLE, CAPTION_HEIGHT_TIGHT, CAPTION_PASS_THROUGH, } from './caption.js';
|
|
8
8
|
// The DATA-FIRST authoring API (the erDiagram/umlDiagram equivalent).
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
* the authored size.
|
|
24
24
|
*/
|
|
25
25
|
import { __awaiter } from "tslib";
|
|
26
|
-
import { Command } from '@grafloria/engine';
|
|
26
|
+
import { BatchCommand, Command } from '@grafloria/engine';
|
|
27
27
|
import { LiveRegionController, registerTool } from '@grafloria/renderer';
|
|
28
28
|
import { anyEdge, clearOtherSelections, dragHandleSelector, gripHostOf, gripOf, normalizeDragHandle, ownsPress, parentPeerOf, pressOnDragHandle, registerBoardPeer, syncGrip, DRAG_HANDLE_CLASS, EDGE_GRIP } from './grid-binder.js';
|
|
29
29
|
import { cellFromGridItem } from './grid-mapping.js';
|
|
@@ -779,6 +779,192 @@ export function bindDashboardSplit(api, group, options = {}) {
|
|
|
779
779
|
},
|
|
780
780
|
};
|
|
781
781
|
const unregisterTool = registerTool(tool);
|
|
782
|
+
// -- a tab torn out of a container on THIS board (0.4.37) --------------------
|
|
783
|
+
// The split board REFUSED every tear-out ("no cells to drop a page into"),
|
|
784
|
+
// so on a split board a tab press was a dead click — no chip, no reorder,
|
|
785
|
+
// no join. The board has a drop model of its own: a widget dropped on a
|
|
786
|
+
// pane's edge inserts there. A torn-out page uses it — the page becomes a
|
|
787
|
+
// one-tab group that IS a pane — and keeps the strip rules of the grid
|
|
788
|
+
// board: its own strip reorders, another container's strip or the centre of
|
|
789
|
+
// its body joins, the centre of its own body is home, off the canvas is
|
|
790
|
+
// nothing. One history step, like every other drop.
|
|
791
|
+
let tearing = false;
|
|
792
|
+
const EDGE_GRACE = 60;
|
|
793
|
+
const execute = (name, commands) => {
|
|
794
|
+
if (commands.length === 0)
|
|
795
|
+
return false;
|
|
796
|
+
void execCommand(new BatchCommand(name, commands));
|
|
797
|
+
return true;
|
|
798
|
+
};
|
|
799
|
+
const beginTearOut = (pageId, fromGroupId, ev, plan) => {
|
|
800
|
+
var _a;
|
|
801
|
+
if (disposed || isStatic || gesture || tearing)
|
|
802
|
+
return false;
|
|
803
|
+
const from = diagram.getGroup(fromGroupId);
|
|
804
|
+
if (!from)
|
|
805
|
+
return false;
|
|
806
|
+
const tree0 = readTree();
|
|
807
|
+
tearing = true;
|
|
808
|
+
const doc = (_a = api.container.ownerDocument) !== null && _a !== void 0 ? _a : document;
|
|
809
|
+
const chip = doc.createElement('div');
|
|
810
|
+
chip.className = 'axdb-drag-chip axdb-tab-chip';
|
|
811
|
+
chip.textContent = plan.label;
|
|
812
|
+
doc.body.appendChild(chip);
|
|
813
|
+
const moveChip = (cx, cy) => {
|
|
814
|
+
chip.style.left = `${cx + 6}px`;
|
|
815
|
+
chip.style.top = `${cy + 6}px`;
|
|
816
|
+
};
|
|
817
|
+
moveChip(ev.clientX, ev.clientY);
|
|
818
|
+
const layer = htmlLayer();
|
|
819
|
+
let joinEl = null;
|
|
820
|
+
const showJoin = (r) => {
|
|
821
|
+
if (!layer)
|
|
822
|
+
return;
|
|
823
|
+
if (!joinEl) {
|
|
824
|
+
joinEl = doc.createElement('div');
|
|
825
|
+
joinEl.className = 'axdb-join';
|
|
826
|
+
layer.prepend(joinEl);
|
|
827
|
+
}
|
|
828
|
+
joinEl.style.left = `${r.x}px`;
|
|
829
|
+
joinEl.style.top = `${r.y}px`;
|
|
830
|
+
joinEl.style.width = `${r.width}px`;
|
|
831
|
+
joinEl.style.height = `${r.height}px`;
|
|
832
|
+
};
|
|
833
|
+
const hideJoin = () => {
|
|
834
|
+
joinEl === null || joinEl === void 0 ? void 0 : joinEl.remove();
|
|
835
|
+
joinEl = null;
|
|
836
|
+
};
|
|
837
|
+
const frameOfGroup = (g) => { var _a, _b, _c, _d; return ({ x: g.position.x, y: g.position.y, width: (_b = (_a = g.size) === null || _a === void 0 ? void 0 : _a.width) !== null && _b !== void 0 ? _b : 0, height: (_d = (_c = g.size) === null || _c === void 0 ? void 0 : _c.height) !== null && _d !== void 0 ? _d : 0 }); };
|
|
838
|
+
const inRect = (r, x, y) => x >= r.x && x <= r.x + r.width && y >= r.y && y <= r.y + r.height;
|
|
839
|
+
const targets = plan.joinTargets
|
|
840
|
+
.map((id) => diagram.getGroup(id))
|
|
841
|
+
.filter((g) => !!g && g.id !== fromGroupId && !plan.ownBoards.includes(g.id));
|
|
842
|
+
/** Inside the visible canvas plus a grace band; an unmeasurable container (no layout) is never off. */
|
|
843
|
+
const clientInsideCanvasGrace = (cx, cy) => {
|
|
844
|
+
const rect = api.container.getBoundingClientRect();
|
|
845
|
+
if (rect.width <= 0 || rect.height <= 0)
|
|
846
|
+
return true;
|
|
847
|
+
return cx >= rect.left - EDGE_GRACE && cx <= rect.right + EDGE_GRACE && cy >= rect.top - EDGE_GRACE && cy <= rect.bottom + EDGE_GRACE;
|
|
848
|
+
};
|
|
849
|
+
/** The centre third of a container's BODY (below its strip), both axes — the grid board's join zone. */
|
|
850
|
+
const centreThird = (g, w) => {
|
|
851
|
+
const f = frameOfGroup(g);
|
|
852
|
+
if (!inRect(f, w.x, w.y))
|
|
853
|
+
return false;
|
|
854
|
+
const top = f.y + plan.stripHeight(g.id);
|
|
855
|
+
const h = Math.max(0, f.y + f.height - top);
|
|
856
|
+
return w.x >= f.x + f.width / 3 && w.x <= f.x + (2 * f.width) / 3 && w.y >= top + h / 3 && w.y <= top + (2 * h) / 3;
|
|
857
|
+
};
|
|
858
|
+
const zoneAt = (cx, cy, w) => {
|
|
859
|
+
if (!clientInsideCanvasGrace(cx, cy))
|
|
860
|
+
return { kind: 'off' };
|
|
861
|
+
const own = plan.stripIndex(fromGroupId, cx, cy);
|
|
862
|
+
if (own !== null)
|
|
863
|
+
return { kind: 'reorder', index: own };
|
|
864
|
+
for (const t of targets) {
|
|
865
|
+
const i = plan.stripIndex(t.id, cx, cy);
|
|
866
|
+
if (i !== null)
|
|
867
|
+
return { kind: 'strip', targetId: t.id, index: i };
|
|
868
|
+
}
|
|
869
|
+
for (const t of targets)
|
|
870
|
+
if (centreThird(t, w))
|
|
871
|
+
return { kind: 'join', targetId: t.id };
|
|
872
|
+
if (centreThird(from, w))
|
|
873
|
+
return { kind: 'home' };
|
|
874
|
+
if (worldInsideBoard(w.x, w.y)) {
|
|
875
|
+
const t = dropTargetAt(tree0, w.x, w.y);
|
|
876
|
+
if (t)
|
|
877
|
+
return { kind: 'pane', target: t };
|
|
878
|
+
}
|
|
879
|
+
return { kind: 'home' };
|
|
880
|
+
};
|
|
881
|
+
const apply = (z) => {
|
|
882
|
+
plan.markDrop(z.kind === 'reorder' ? fromGroupId : z.kind === 'strip' ? z.targetId : null, z.kind === 'reorder' || z.kind === 'strip' ? z.index : null);
|
|
883
|
+
const jt = z.kind === 'join' ? diagram.getGroup(z.targetId) : undefined;
|
|
884
|
+
if (jt)
|
|
885
|
+
showJoin(frameOfGroup(jt));
|
|
886
|
+
else
|
|
887
|
+
hideJoin();
|
|
888
|
+
showInsertion(z.kind === 'pane' ? insertionRect(z.target.rect, z.target.side) : null);
|
|
889
|
+
chip.classList.toggle('axdb-out', z.kind === 'home' || z.kind === 'off');
|
|
890
|
+
};
|
|
891
|
+
let last = { x: ev.clientX, y: ev.clientY };
|
|
892
|
+
const detach = () => {
|
|
893
|
+
window.removeEventListener('pointermove', onMove, true);
|
|
894
|
+
window.removeEventListener('pointerup', onUp, true);
|
|
895
|
+
window.removeEventListener('pointercancel', onCancel, true);
|
|
896
|
+
window.removeEventListener('keydown', onKey, true);
|
|
897
|
+
};
|
|
898
|
+
const done = (changed, kind) => {
|
|
899
|
+
tearing = false;
|
|
900
|
+
project(readTree());
|
|
901
|
+
api.renderNow();
|
|
902
|
+
fire({ type: kind, kind: 'move', nodeId: pageId, changed });
|
|
903
|
+
};
|
|
904
|
+
const finish = (commit) => {
|
|
905
|
+
detach();
|
|
906
|
+
chip.remove();
|
|
907
|
+
hideJoin();
|
|
908
|
+
showInsertion(null);
|
|
909
|
+
plan.markDrop(null, null);
|
|
910
|
+
if (disposed) {
|
|
911
|
+
tearing = false;
|
|
912
|
+
return;
|
|
913
|
+
}
|
|
914
|
+
const z = commit ? zoneAt(last.x, last.y, toWorld(last.x, last.y)) : { kind: 'home' };
|
|
915
|
+
if (z.kind === 'home' || z.kind === 'off') {
|
|
916
|
+
done(false, 'cancel');
|
|
917
|
+
return;
|
|
918
|
+
}
|
|
919
|
+
if (z.kind === 'reorder') {
|
|
920
|
+
const cmds = plan.reorder(z.index);
|
|
921
|
+
const changed = cmds.length > 0 && execute('Reorder tab', cmds);
|
|
922
|
+
done(changed, changed ? 'commit' : 'cancel');
|
|
923
|
+
return;
|
|
924
|
+
}
|
|
925
|
+
if (z.kind === 'strip' || z.kind === 'join') {
|
|
926
|
+
const index = z.kind === 'strip' ? z.index : plan.dropIndex(z.targetId, last.x, last.y);
|
|
927
|
+
const planned = plan.join(z.targetId, index, group.id);
|
|
928
|
+
done(execute('Move tab', [...planned.move, ...planned.collapse]), 'commit');
|
|
929
|
+
return;
|
|
930
|
+
}
|
|
931
|
+
// A PANE: the page's new group takes the named side of the target — the
|
|
932
|
+
// widget drop's own rule — and the tree swap rides in the same step as
|
|
933
|
+
// the group's birth, so one undo removes both.
|
|
934
|
+
const side = rtl && (z.target.side === 'left' || z.target.side === 'right') ? (z.target.side === 'left' ? 'right' : 'left') : z.target.side;
|
|
935
|
+
const after = normalizeSplit(insertSplitLeaf(tree0, plan.arrivingId, targetRef(z.target), side));
|
|
936
|
+
const rect = rectsOf(after).get(plan.arrivingId);
|
|
937
|
+
const cell = cellsFromSplit(after, columns, rowsGuess()).get(plan.arrivingId);
|
|
938
|
+
if (!rect || !cell) {
|
|
939
|
+
done(false, 'cancel');
|
|
940
|
+
return;
|
|
941
|
+
}
|
|
942
|
+
const planned = plan.commands(cell, rect, group.id);
|
|
943
|
+
done(execute('Move tab out', [...planned.move, new SetSplitTreeCommand(group.id, tree0, after), ...planned.collapse]), 'commit');
|
|
944
|
+
live.announce(`${plan.label} is a pane ${side === 'left' || side === 'top' ? 'before' : 'after'} ${targetName(z.target)}`, 'polite', true);
|
|
945
|
+
};
|
|
946
|
+
const onMove = (e) => {
|
|
947
|
+
if (disposed)
|
|
948
|
+
return finish(false);
|
|
949
|
+
last = { x: e.clientX, y: e.clientY };
|
|
950
|
+
moveChip(e.clientX, e.clientY);
|
|
951
|
+
apply(zoneAt(e.clientX, e.clientY, toWorld(e.clientX, e.clientY)));
|
|
952
|
+
api.render();
|
|
953
|
+
};
|
|
954
|
+
const onUp = () => finish(true);
|
|
955
|
+
const onCancel = () => finish(false);
|
|
956
|
+
const onKey = (e) => {
|
|
957
|
+
if (e.key === 'Escape')
|
|
958
|
+
finish(false);
|
|
959
|
+
};
|
|
960
|
+
window.addEventListener('pointermove', onMove, true);
|
|
961
|
+
window.addEventListener('pointerup', onUp, true);
|
|
962
|
+
window.addEventListener('pointercancel', onCancel, true);
|
|
963
|
+
window.addEventListener('keydown', onKey, true);
|
|
964
|
+
apply(zoneAt(ev.clientX, ev.clientY, toWorld(ev.clientX, ev.clientY)));
|
|
965
|
+
api.render();
|
|
966
|
+
return true;
|
|
967
|
+
};
|
|
782
968
|
// -- palette drag-in --------------------------------------------------------
|
|
783
969
|
const beginPaletteDrag = (node, spec, event) => {
|
|
784
970
|
var _a;
|
|
@@ -1251,9 +1437,8 @@ export function bindDashboardSplit(api, group, options = {}) {
|
|
|
1251
1437
|
hasItem: (id) => { var _a; return ((_a = group.members) !== null && _a !== void 0 ? _a : new Set()).has(id); },
|
|
1252
1438
|
memberCell: (id) => handle.cellOf(id),
|
|
1253
1439
|
resizeMemberBy: () => ({ changed: false }),
|
|
1254
|
-
// A
|
|
1255
|
-
|
|
1256
|
-
tearOutMember: (_pageId, _fromGroupId, _ev, _plan) => false,
|
|
1440
|
+
// A torn-out page becomes a PANE (0.4.37) — see beginTearOut.
|
|
1441
|
+
tearOutMember: (pageId, fromGroupId, ev, plan) => beginTearOut(pageId, fromGroupId, ev, plan),
|
|
1257
1442
|
// …and it has no cells to move a section across: a strip press stays a selection.
|
|
1258
1443
|
dragMember: () => false,
|
|
1259
1444
|
containsWorld: (x, y) => worldInsideBoard(x, y),
|
|
@@ -21,5 +21,7 @@
|
|
|
21
21
|
* (pages own their card innerHTML).
|
|
22
22
|
*/
|
|
23
23
|
export declare const DASHBOARD_KIT_STYLE_ID = "grafloria-dashboard-kit-styles";
|
|
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";
|
|
24
26
|
/** Idempotently inject the kit stylesheet (safe to call per binder). */
|
|
25
27
|
export declare function ensureDashboardKitStyles(doc?: Document): void;
|
|
@@ -37,7 +37,7 @@ const CSS = `
|
|
|
37
37
|
.grafloria-html-layer > .grafloria-node-host:focus-visible {
|
|
38
38
|
outline: 2px solid var(--axdb-accent, #3b52d9);
|
|
39
39
|
outline-offset: 2px;
|
|
40
|
-
border-radius: var(--axdb-rs-radius,
|
|
40
|
+
border-radius: var(--axdb-rs-radius, 8px);
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
/* ===== the held tile: transition-exempt ghost, above everything ===== */
|
|
@@ -55,7 +55,7 @@ const CSS = `
|
|
|
55
55
|
/* ===== the placeholder: dashed slab, truthful, never animated ===== */
|
|
56
56
|
.grafloria-html-layer > .axdb-ph {
|
|
57
57
|
position: absolute;
|
|
58
|
-
border-radius: var(--axdb-rs-radius,
|
|
58
|
+
border-radius: var(--axdb-rs-radius, 8px);
|
|
59
59
|
background: rgba(30, 34, 45, .14);
|
|
60
60
|
border: 2px dashed rgba(30, 34, 45, .28);
|
|
61
61
|
box-sizing: border-box;
|
|
@@ -80,7 +80,7 @@ const CSS = `
|
|
|
80
80
|
cursor: nwse-resize;
|
|
81
81
|
border-right: 3px solid rgba(120, 130, 148, .55);
|
|
82
82
|
border-bottom: 3px solid rgba(120, 130, 148, .55);
|
|
83
|
-
border-bottom-right-radius: var(--axdb-rs-radius,
|
|
83
|
+
border-bottom-right-radius: var(--axdb-rs-radius, 8px);
|
|
84
84
|
opacity: 0;
|
|
85
85
|
transition: opacity .12s;
|
|
86
86
|
z-index: 5;
|
|
@@ -98,7 +98,7 @@ const CSS = `
|
|
|
98
98
|
border-right: none;
|
|
99
99
|
border-left: 3px solid rgba(120, 130, 148, .55);
|
|
100
100
|
border-bottom-right-radius: 0;
|
|
101
|
-
border-bottom-left-radius: var(--axdb-rs-radius,
|
|
101
|
+
border-bottom-left-radius: var(--axdb-rs-radius, 8px);
|
|
102
102
|
}
|
|
103
103
|
|
|
104
104
|
/* ===== palette drag-in chip (screen-space clone following the cursor) ===== */
|
|
@@ -125,14 +125,14 @@ const CSS = `
|
|
|
125
125
|
font: 500 12px/1.4 var(--axdb-font, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif);
|
|
126
126
|
color: var(--axdb-tabs-on-fg, #1f2430);
|
|
127
127
|
background: var(--axdb-tabs-on-bg, #fff);
|
|
128
|
-
border-radius: var(--axdb-
|
|
129
|
-
box-shadow: 0 0 0 1px rgba(31, 36, 48, .
|
|
128
|
+
border-radius: var(--axdb-tab-radius, 6px);
|
|
129
|
+
box-shadow: 0 1px 2px rgba(16, 24, 40, .1), 0 0 0 1px rgba(31, 36, 48, .08);
|
|
130
130
|
}
|
|
131
131
|
@media (prefers-color-scheme: dark) {
|
|
132
132
|
.axdb-tab-chip {
|
|
133
133
|
color: var(--axdb-tabs-on-fg, #eceef4);
|
|
134
|
-
background: var(--axdb-tabs-on-bg, #
|
|
135
|
-
box-shadow: 0 0 0 1px rgba(236, 238, 244, .
|
|
134
|
+
background: var(--axdb-tabs-on-bg, #232836);
|
|
135
|
+
box-shadow: 0 1px 2px rgba(0, 0, 0, .45), 0 0 0 1px rgba(236, 238, 244, .12);
|
|
136
136
|
}
|
|
137
137
|
}
|
|
138
138
|
/* A tab held over ANOTHER group: it will join that group, not the board. The
|
|
@@ -145,7 +145,7 @@ const CSS = `
|
|
|
145
145
|
box-sizing: border-box;
|
|
146
146
|
border: 2px dashed var(--axdb-accent, #3b52d9);
|
|
147
147
|
background: var(--axdb-accent-soft, rgba(59, 82, 217, .08));
|
|
148
|
-
border-radius: var(--axdb-rs-radius,
|
|
148
|
+
border-radius: var(--axdb-rs-radius, 8px);
|
|
149
149
|
}
|
|
150
150
|
.axdb-tabs.axdb-tabs--drop { box-shadow: inset 0 -2px 0 var(--axdb-accent, #3b52d9); }
|
|
151
151
|
.axdb-tab.axdb-tab--drop-before { position: relative; }
|
|
@@ -173,7 +173,7 @@ const CSS = `
|
|
|
173
173
|
--axdb-muted: #5a6478;
|
|
174
174
|
--axdb-grid: rgba(120, 130, 148, .22);
|
|
175
175
|
--axdb-card: #fff;
|
|
176
|
-
--axdb-line: #
|
|
176
|
+
--axdb-line: #e5e8ef;
|
|
177
177
|
--axdb-soft: rgba(120, 130, 148, .14);
|
|
178
178
|
--axdb-up: #0f7a3d; /* 5.42:1 */
|
|
179
179
|
--axdb-down: #be123c; /* 6.29:1 */
|
|
@@ -181,17 +181,22 @@ const CSS = `
|
|
|
181
181
|
steps: every entry clears 3:1 against its card (WCAG 1.4.11). */
|
|
182
182
|
--axdb-c1: #3b52d9; --axdb-c2: #0369a1; --axdb-c3: #0f766e;
|
|
183
183
|
--axdb-c4: #b45309; --axdb-c5: #6d28d9; --axdb-c6: #475569;
|
|
184
|
+
/* The header band: a panel wears its title in a band across its top, the
|
|
185
|
+
way DevExpress, Grafana and Metabase panels do — tinted ground, hairline
|
|
186
|
+
below, sentence case (0.4.36; the tracked uppercase micro-label before). */
|
|
187
|
+
--axdb-head-bg: #f7f8fb;
|
|
188
|
+
--axdb-head-fg: #2a3140;
|
|
184
189
|
box-sizing: border-box;
|
|
185
190
|
width: 100%;
|
|
186
191
|
height: 100%;
|
|
187
192
|
display: flex;
|
|
188
193
|
flex-direction: column;
|
|
189
194
|
overflow: hidden;
|
|
190
|
-
padding:
|
|
195
|
+
padding: 0;
|
|
191
196
|
background: var(--axdb-card);
|
|
192
197
|
border: 1px solid var(--axdb-line);
|
|
193
|
-
border-radius: var(--axdb-rs-radius,
|
|
194
|
-
box-shadow: 0 1px 2px rgba(16, 24, 40, .
|
|
198
|
+
border-radius: var(--axdb-rs-radius, 8px);
|
|
199
|
+
box-shadow: 0 1px 2px rgba(16, 24, 40, .04);
|
|
195
200
|
font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
|
|
196
201
|
color: var(--axdb-ink);
|
|
197
202
|
}
|
|
@@ -203,25 +208,33 @@ const CSS = `
|
|
|
203
208
|
below and would lose the cascade at equal specificity — at 54 px the margin
|
|
204
209
|
stayed 8 px, the body shrank to 14 px and the KPI figure hid. */
|
|
205
210
|
@container axdb-tile (max-height: 90px) {
|
|
206
|
-
.axdb-widget { padding:
|
|
207
|
-
.axdb-widget > .axdb-widget-
|
|
211
|
+
.axdb-widget > .axdb-widget-h { padding: 5px 12px; font-size: 11.5px; }
|
|
212
|
+
.axdb-widget > .axdb-widget-b { padding: 6px 12px; }
|
|
208
213
|
}
|
|
214
|
+
/* The row floor: the band gives way to an inline label beside the figure. */
|
|
209
215
|
@container axdb-tile (max-height: 46px) {
|
|
210
|
-
.axdb-widget { padding:
|
|
211
|
-
.axdb-widget > .axdb-widget-
|
|
216
|
+
.axdb-widget > .axdb-widget-h { padding: 0 0 0 12px; background: none; border-bottom: 0; }
|
|
217
|
+
.axdb-widget > .axdb-widget-b { padding: 3px 12px; }
|
|
212
218
|
}
|
|
213
219
|
@container axdb-tile (max-height: 26px) {
|
|
214
|
-
.axdb-widget {
|
|
220
|
+
.axdb-widget > .axdb-widget-h { display: none; }
|
|
221
|
+
.axdb-widget > .axdb-widget-b { padding: 1px 12px; }
|
|
215
222
|
}
|
|
216
223
|
.axdb-widget-h {
|
|
217
224
|
display: flex;
|
|
218
225
|
align-items: center;
|
|
219
|
-
gap:
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
226
|
+
gap: 8px;
|
|
227
|
+
flex: none;
|
|
228
|
+
min-width: 0;
|
|
229
|
+
padding: 9px 14px;
|
|
230
|
+
background: var(--axdb-head-bg);
|
|
231
|
+
border-bottom: 1px solid var(--axdb-line);
|
|
232
|
+
font: 600 12.5px/1.3 system-ui, -apple-system, "Segoe UI", sans-serif;
|
|
233
|
+
letter-spacing: -.005em;
|
|
234
|
+
color: var(--axdb-head-fg);
|
|
235
|
+
white-space: nowrap;
|
|
236
|
+
overflow: hidden;
|
|
237
|
+
text-overflow: ellipsis;
|
|
225
238
|
}
|
|
226
239
|
/* Drag-handle mode (DevExpress drags an item by its caption): the header is
|
|
227
240
|
the grip and says so with a dot pattern and a grab cursor; the body keeps
|
|
@@ -261,7 +274,7 @@ const CSS = `
|
|
|
261
274
|
.grafloria-node-host[data-axdb-edge="nesw-resize"], .grafloria-node-host[data-axdb-edge="nesw-resize"] * { cursor: nesw-resize !important; }
|
|
262
275
|
/* INSIDE: centred on the header's text line and flush with the card padding,
|
|
263
276
|
per size tier (padding 13/15, then 8/14, 4/12, 2/12). */
|
|
264
|
-
.grafloria-node-host > .axdb-grip--inside { top:
|
|
277
|
+
.grafloria-node-host > .axdb-grip--inside { top: 12px; }
|
|
265
278
|
.grafloria-node-host > .axdb-grip--inside.axdb-grip--left { left: 15px; }
|
|
266
279
|
.grafloria-node-host > .axdb-grip--inside.axdb-grip--right { right: 15px; }
|
|
267
280
|
.grafloria-node-host > .axdb-grip--inside.axdb-grip--center { top: 3px; }
|
|
@@ -270,12 +283,18 @@ const CSS = `
|
|
|
270
283
|
.grafloria-node-host > .axdb-grip--inside.axdb-grip--left { left: 14px; }
|
|
271
284
|
.grafloria-node-host > .axdb-grip--inside.axdb-grip--right { right: 14px; }
|
|
272
285
|
.grafloria-node-host > .axdb-grip--inside.axdb-grip--center { top: 2px; }
|
|
286
|
+
.grafloria-node-host.axdb-gp-inside.axdb-gp-left .axdb-widget > .axdb-widget-h { padding-left: 46px; }
|
|
287
|
+
.grafloria-node-host.axdb-gp-inside.axdb-gp-right .axdb-widget > .axdb-widget-h { padding-right: 46px; }
|
|
288
|
+
.grafloria-node-host.axdb-gp-inside.axdb-gp-center .axdb-widget > .axdb-widget-h { padding-top: 15px; }
|
|
273
289
|
}
|
|
274
290
|
@container axdb-tile (max-height: 46px) {
|
|
275
291
|
.grafloria-node-host > .axdb-grip--inside { top: 5px; }
|
|
276
292
|
.grafloria-node-host > .axdb-grip--inside.axdb-grip--left { left: 12px; }
|
|
277
293
|
.grafloria-node-host > .axdb-grip--inside.axdb-grip--right { right: 12px; }
|
|
278
294
|
.grafloria-node-host > .axdb-grip--inside.axdb-grip--center { top: 1px; }
|
|
295
|
+
.grafloria-node-host.axdb-gp-inside.axdb-gp-left .axdb-widget > .axdb-widget-h { padding-left: 44px; }
|
|
296
|
+
.grafloria-node-host.axdb-gp-inside.axdb-gp-right .axdb-widget > .axdb-widget-h { padding-right: 44px; }
|
|
297
|
+
.grafloria-node-host.axdb-gp-inside.axdb-gp-center .axdb-widget > .axdb-widget-h { padding-top: 13px; }
|
|
279
298
|
}
|
|
280
299
|
@container axdb-tile (max-height: 26px) {
|
|
281
300
|
.grafloria-node-host > .axdb-grip--inside { top: 2px; }
|
|
@@ -288,11 +307,13 @@ const CSS = `
|
|
|
288
307
|
.grafloria-node-host > .axdb-grip--outside.axdb-grip--left { left: 10px; }
|
|
289
308
|
.grafloria-node-host > .axdb-grip--outside.axdb-grip--right { right: 10px; }
|
|
290
309
|
.grafloria-node-host > .axdb-grip--center { left: 50%; transform: translateX(-50%); }
|
|
291
|
-
/* The header makes room for an inside grip on its side
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
.grafloria-node-host.axdb-gp-inside.axdb-gp-
|
|
295
|
-
.axdb-
|
|
310
|
+
/* The header makes room for an inside grip on its side — inset + the 24-px
|
|
311
|
+
grip + an 8-px gap, per tier (32 px under the band's 14-px inset hid the
|
|
312
|
+
first letter of the title) — and a centred one sits above the text. */
|
|
313
|
+
.grafloria-node-host.axdb-gp-inside.axdb-gp-left .axdb-widget > .axdb-widget-h { padding-left: 47px; }
|
|
314
|
+
.grafloria-node-host.axdb-gp-inside.axdb-gp-right .axdb-widget > .axdb-widget-h { padding-right: 47px; }
|
|
315
|
+
.grafloria-node-host.axdb-gp-inside.axdb-gp-center .axdb-widget > .axdb-widget-h { padding-top: 17px; }
|
|
316
|
+
.axdb-widget-b { flex: 1; min-height: 0; position: relative; padding: 12px 14px; }
|
|
296
317
|
/* A drag across a STATIC board (nothing prevents the press's default there)
|
|
297
318
|
used to select every label on it; kit cards are not prose. Tables stay
|
|
298
319
|
copyable — a figure in a grid is the one thing a viewer selects. */
|
|
@@ -358,7 +379,7 @@ const CSS = `
|
|
|
358
379
|
when the tile is wide enough. */
|
|
359
380
|
@container axdb-tile (max-height: 46px) {
|
|
360
381
|
.axdb-widget--kpi { flex-direction: row; align-items: center; gap: 12px; }
|
|
361
|
-
.axdb-widget--kpi > .axdb-widget-h { flex: none; }
|
|
382
|
+
.axdb-widget--kpi > .axdb-widget-h { flex: none; padding: 0 0 0 12px; background: none; border-bottom: 0; }
|
|
362
383
|
/* The body is a SIZE container: in a centred row it would collapse to zero
|
|
363
384
|
and every height query would fire. It takes the card's full inner height. */
|
|
364
385
|
.axdb-widget--kpi > .axdb-kpi { flex: 1; align-self: stretch; }
|
|
@@ -403,7 +424,7 @@ const CSS = `
|
|
|
403
424
|
|
|
404
425
|
/* SECTION CHROME: a pointer-transparent overlay on every member group. It
|
|
405
426
|
wears the selection ring and, while selected, the corner handle. */
|
|
406
|
-
.grafloria-html-layer > .axdb-slab { position: absolute; pointer-events: none; border-radius: var(--axdb-rs-radius,
|
|
427
|
+
.grafloria-html-layer > .axdb-slab { position: absolute; pointer-events: none; border-radius: var(--axdb-rs-radius, 8px); z-index: 4; }
|
|
407
428
|
/* Selected, the overlay rises above the tiles so ITS corner handle wins a
|
|
408
429
|
corner it shares with a child's; unselected, its handle takes no presses. */
|
|
409
430
|
.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)); }
|
|
@@ -418,16 +439,16 @@ const CSS = `
|
|
|
418
439
|
.grafloria-html-layer > .axdb-slab > .axdb-slab-h {
|
|
419
440
|
position: absolute; box-sizing: border-box; pointer-events: auto; z-index: 1;
|
|
420
441
|
display: flex; align-items: center; gap: 6px; min-width: 0;
|
|
421
|
-
padding: var(--axdb-caption-pad, 0
|
|
422
|
-
background: var(--axdb-caption-bg, rgba(31, 36, 48, .
|
|
442
|
+
padding: var(--axdb-caption-pad, 0 12px);
|
|
443
|
+
background: var(--axdb-caption-bg, rgba(31, 36, 48, .045));
|
|
423
444
|
/* A hairline by default: two bands meeting (a captioned section inside a
|
|
424
445
|
captioned section) read as two headers, not one grey block. */
|
|
425
446
|
border-bottom: var(--axdb-caption-border, 1px solid rgba(31, 36, 48, .1));
|
|
426
447
|
color: var(--axdb-caption-fg, #1f2430);
|
|
427
448
|
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);
|
|
428
449
|
text-transform: var(--axdb-caption-transform, none);
|
|
429
|
-
letter-spacing:
|
|
430
|
-
border-radius: var(--axdb-rs-radius,
|
|
450
|
+
letter-spacing: -.005em;
|
|
451
|
+
border-radius: var(--axdb-rs-radius, 8px) var(--axdb-rs-radius, 8px) 0 0;
|
|
431
452
|
cursor: default; user-select: none; -webkit-user-select: none; overflow: hidden;
|
|
432
453
|
transition: opacity .12s;
|
|
433
454
|
}
|
|
@@ -439,7 +460,7 @@ const CSS = `
|
|
|
439
460
|
/* 'tab': the same band sized to its text, a chip at the leading corner. It
|
|
440
461
|
reserves its height like 'inside' — see captionReserve. */
|
|
441
462
|
.grafloria-html-layer > .axdb-slab > .axdb-slab-h.axdb-slab-h--tab {
|
|
442
|
-
max-width: 100%; border-radius: var(--axdb-rs-radius,
|
|
463
|
+
max-width: 100%; border-radius: var(--axdb-rs-radius, 8px);
|
|
443
464
|
border-bottom: var(--axdb-caption-border-tab, none);
|
|
444
465
|
box-shadow: inset 0 0 0 1px rgba(31, 36, 48, .08);
|
|
445
466
|
}
|
|
@@ -463,14 +484,14 @@ const CSS = `
|
|
|
463
484
|
.axdb-slab-h-icon { flex: none; }
|
|
464
485
|
.axdb-slab-h-body { display: flex; flex-direction: column; justify-content: center; min-width: 0; flex: 0 1 auto; }
|
|
465
486
|
.axdb-slab-h-text, .axdb-slab-h-sub { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
466
|
-
.axdb-slab-h-sub { font-size: .
|
|
487
|
+
.axdb-slab-h-sub { font-size: .88em; font-weight: 500; opacity: .7; }
|
|
467
488
|
.axdb-slab-h-info { flex: none; opacity: .6; font-size: .9em; cursor: help; }
|
|
468
489
|
.axdb-slab-h-actions { flex: none; display: flex; gap: 2px; margin-inline-start: auto; opacity: 0; transition: opacity .12s; }
|
|
469
490
|
.axdb-slab-h--center .axdb-slab-h-actions, .axdb-slab-h--end .axdb-slab-h-actions { margin-inline-start: 0; }
|
|
470
491
|
.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; }
|
|
471
492
|
.axdb-slab-h-action {
|
|
472
|
-
all: unset; box-sizing: border-box; width:
|
|
473
|
-
border-radius:
|
|
493
|
+
all: unset; box-sizing: border-box; width: 26px; height: 26px; display: inline-flex; align-items: center; justify-content: center;
|
|
494
|
+
border-radius: 6px; cursor: pointer; font-size: 14px; line-height: 1; color: inherit; transition: background-color .12s;
|
|
474
495
|
}
|
|
475
496
|
.axdb-slab-h-action:hover { background: rgba(31, 36, 48, .08); }
|
|
476
497
|
.axdb-slab-h-action:focus-visible { outline: 2px solid var(--axdb-accent-ring, rgba(59, 82, 217, .55)); outline-offset: -2px; }
|
|
@@ -478,8 +499,11 @@ const CSS = `
|
|
|
478
499
|
.axdb-slab-h-action[disabled]:hover { background: none; }
|
|
479
500
|
.grafloria-html-layer > .axdb-slab.axdb-slab--static > .axdb-slab-h { cursor: default; }
|
|
480
501
|
@media (prefers-color-scheme: dark) {
|
|
502
|
+
/* The band sits on the PAGE, whose ground the kit does not own (a host that
|
|
503
|
+
stays light while the kit goes dark is common): a solid ink, not a tint,
|
|
504
|
+
so the light caption always reads. */
|
|
481
505
|
.grafloria-html-layer > .axdb-slab > .axdb-slab-h {
|
|
482
|
-
background: var(--axdb-caption-bg,
|
|
506
|
+
background: var(--axdb-caption-bg, #1b2029);
|
|
483
507
|
color: var(--axdb-caption-fg, #eceef4);
|
|
484
508
|
border-bottom: var(--axdb-caption-border, 1px solid rgba(236, 238, 244, .12));
|
|
485
509
|
}
|
|
@@ -493,36 +517,42 @@ const CSS = `
|
|
|
493
517
|
ordinary boards. */
|
|
494
518
|
.grafloria-html-layer > .axdb-tabs {
|
|
495
519
|
position: absolute; box-sizing: border-box; pointer-events: auto; z-index: 5;
|
|
496
|
-
display: flex; align-items:
|
|
497
|
-
background: var(--axdb-tabs-bg,
|
|
498
|
-
border-bottom: 1px solid var(--axdb-tabs-line,
|
|
499
|
-
border-radius: var(--axdb-rs-radius,
|
|
520
|
+
display: flex; align-items: center; gap: 2px; padding: 3px 6px; overflow-x: auto; overflow-y: hidden;
|
|
521
|
+
background: var(--axdb-tabs-bg, #f1f3f8);
|
|
522
|
+
border-bottom: 1px solid var(--axdb-tabs-line, #e5e8ef);
|
|
523
|
+
border-radius: var(--axdb-rs-radius, 8px) var(--axdb-rs-radius, 8px) 0 0;
|
|
500
524
|
scrollbar-width: thin;
|
|
501
525
|
}
|
|
502
526
|
.axdb-tabs--center { justify-content: center; }
|
|
503
527
|
.axdb-tabs--end { justify-content: flex-end; }
|
|
504
528
|
.axdb-tabs--stretch > .axdb-tab { flex: 1 1 0; }
|
|
529
|
+
/* A tab is a PILL on the track (a segmented control): quiet until hovered,
|
|
530
|
+
the active one lifted in the card's own ground. */
|
|
505
531
|
.axdb-tab {
|
|
506
532
|
all: unset; box-sizing: border-box; flex: 0 0 auto; max-width: 200px;
|
|
507
|
-
padding: 0 12px; height:
|
|
508
|
-
font:
|
|
533
|
+
padding: 0 12px; height: 100%; display: inline-flex; align-items: center;
|
|
534
|
+
font: 500 12.5px/1 system-ui, -apple-system, "Segoe UI", sans-serif;
|
|
535
|
+
letter-spacing: -.005em;
|
|
509
536
|
color: var(--axdb-tabs-fg, #5a6478); cursor: pointer;
|
|
510
|
-
border-radius: var(--axdb-
|
|
537
|
+
border-radius: var(--axdb-tab-radius, 6px);
|
|
511
538
|
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
|
539
|
+
transition: background-color .12s, color .12s, box-shadow .12s;
|
|
512
540
|
}
|
|
513
|
-
.axdb-tab:hover { background: rgba(31, 36, 48, .06); }
|
|
541
|
+
.axdb-tab:hover { background: rgba(31, 36, 48, .06); color: var(--axdb-tabs-on-fg, #1f2430); }
|
|
514
542
|
.axdb-tab:focus-visible { outline: 2px solid var(--axdb-accent-ring, rgba(59, 82, 217, .55)); outline-offset: -2px; }
|
|
515
|
-
/* The active tab reads as the front page: the card's own ground, lifted. */
|
|
516
543
|
.axdb-tab.axdb-tab--on {
|
|
517
544
|
background: var(--axdb-tabs-on-bg, #fff);
|
|
518
545
|
color: var(--axdb-tabs-on-fg, #1f2430);
|
|
519
|
-
|
|
546
|
+
font-weight: 600;
|
|
547
|
+
box-shadow: 0 1px 2px rgba(16, 24, 40, .1), 0 0 0 1px rgba(31, 36, 48, .06);
|
|
520
548
|
}
|
|
549
|
+
@media (prefers-reduced-motion: reduce) { .axdb-tab { transition: none; } }
|
|
521
550
|
@media (prefers-color-scheme: dark) {
|
|
522
|
-
|
|
551
|
+
/* Solid for the same reason as the caption band: the track sits on the page. */
|
|
552
|
+
.grafloria-html-layer > .axdb-tabs { background: var(--axdb-tabs-bg, #1b2029); border-bottom-color: var(--axdb-tabs-line, rgba(236, 238, 244, .12)); }
|
|
523
553
|
.axdb-tab { color: var(--axdb-tabs-fg, #98a1b4); }
|
|
524
|
-
.axdb-tab:hover { background: rgba(236, 238, 244, .08); }
|
|
525
|
-
.axdb-tab.axdb-tab--on { background: var(--axdb-tabs-on-bg, #
|
|
554
|
+
.axdb-tab:hover { background: rgba(236, 238, 244, .08); color: var(--axdb-tabs-on-fg, #eceef4); }
|
|
555
|
+
.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); }
|
|
526
556
|
}
|
|
527
557
|
|
|
528
558
|
/* legend chips, shared by line and donut */
|
|
@@ -550,8 +580,10 @@ const CSS = `
|
|
|
550
580
|
--axdb-ink: #eceef4;
|
|
551
581
|
--axdb-muted: #98a1b4;
|
|
552
582
|
--axdb-grid: rgba(150, 160, 182, .2);
|
|
553
|
-
--axdb-card: #
|
|
554
|
-
--axdb-line: #
|
|
583
|
+
--axdb-card: #161a22;
|
|
584
|
+
--axdb-line: #2a3040;
|
|
585
|
+
--axdb-head-bg: rgba(255, 255, 255, .035);
|
|
586
|
+
--axdb-head-fg: #e6e9f0;
|
|
555
587
|
--axdb-soft: rgba(150, 160, 182, .16);
|
|
556
588
|
--axdb-up: #4ade80;
|
|
557
589
|
--axdb-down: #fb7185;
|
|
@@ -560,6 +592,8 @@ const CSS = `
|
|
|
560
592
|
}
|
|
561
593
|
}
|
|
562
594
|
`;
|
|
595
|
+
/** The kit stylesheet as text — for a host that bundles its own CSS, and for the styling spec. */
|
|
596
|
+
export const DASHBOARD_KIT_CSS = CSS;
|
|
563
597
|
/** Idempotently inject the kit stylesheet (safe to call per binder). */
|
|
564
598
|
export function ensureDashboardKitStyles(doc) {
|
|
565
599
|
// SERVER-SAFE: `dashboard()` is a pure spec builder and runs where there is
|
package/src/lib/load.js
CHANGED
|
@@ -238,6 +238,7 @@ export function fromDocument(document, options = {}) {
|
|
|
238
238
|
: {},
|
|
239
239
|
active: (_x = activeGroup === null || activeGroup === void 0 ? void 0 : activeGroup.id) !== null && _x !== void 0 ? _x : 'main',
|
|
240
240
|
apiRef: null,
|
|
241
|
+
container: null,
|
|
241
242
|
};
|
|
242
243
|
const handle = createDashboardHandle(ctx);
|
|
243
244
|
const renderCustomNode = (node, host) => {
|
|
@@ -255,7 +256,7 @@ export function fromDocument(document, options = {}) {
|
|
|
255
256
|
(_b = getNodeType(node.type)) === null || _b === void 0 ? void 0 : _b(node, host);
|
|
256
257
|
};
|
|
257
258
|
const finalize = (api) => {
|
|
258
|
-
var _a, _b, _c, _d, _e, _f;
|
|
259
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
259
260
|
const a = api;
|
|
260
261
|
if (!a)
|
|
261
262
|
return;
|
|
@@ -296,6 +297,7 @@ export function fromDocument(document, options = {}) {
|
|
|
296
297
|
// work immediately — no camera move, no showView, so the paint is byte-for-
|
|
297
298
|
// byte what a boards-only load produced.
|
|
298
299
|
ctx.apiRef = a;
|
|
300
|
+
ctx.container = (_e = a.container) !== null && _e !== void 0 ? _e : null;
|
|
299
301
|
for (const group of groups) {
|
|
300
302
|
const board = group.getMetadata('dashboardBoard');
|
|
301
303
|
if (!board)
|
|
@@ -308,7 +310,7 @@ export function fromDocument(document, options = {}) {
|
|
|
308
310
|
}
|
|
309
311
|
// Tab containers, with the same runtime dashboard() uses — a reloaded
|
|
310
312
|
// document must switch pages exactly like a board built from a literal.
|
|
311
|
-
attachTabsRuntime(ctx, model, (
|
|
313
|
+
attachTabsRuntime(ctx, model, (_f = a.container) !== null && _f !== void 0 ? _f : null, handle);
|
|
312
314
|
// LIVE LAYOUT SWITCH on a loaded document — the same contract dashboard()
|
|
313
315
|
// finalize offers: cells persisted where the grid reads them, any tree and
|
|
314
316
|
// column cache cleared, the board's `layout` flag flipped, a fresh binder.
|
|
@@ -355,7 +357,7 @@ export function fromDocument(document, options = {}) {
|
|
|
355
357
|
}
|
|
356
358
|
// A loaded board follows the history exactly as an authored one does: an
|
|
357
359
|
// undo re-syncs every binder without the consumer calling refresh().
|
|
358
|
-
(
|
|
360
|
+
(_g = ctx.attachHistory) === null || _g === void 0 ? void 0 : _g.call(ctx);
|
|
359
361
|
};
|
|
360
362
|
return Object.assign({ nodes, edges: model.getLinks(), renderCustomNode,
|
|
361
363
|
finalize,
|