@grafloria/element 0.4.12 → 0.4.14
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/lib/dashboard-kit/dashboard.d.ts +27 -0
- package/src/lib/dashboard-kit/dashboard.js +117 -15
- package/src/lib/dashboard-kit/grid-binder.d.ts +112 -0
- package/src/lib/dashboard-kit/grid-binder.js +88 -5
- package/src/lib/dashboard-kit/split-binder.js +33 -1
- package/src/lib/dashboard-kit/styles.js +8 -0
- package/src/lib/load.js +1 -1
package/package.json
CHANGED
|
@@ -106,6 +106,25 @@ export interface DashboardWidgetSpec {
|
|
|
106
106
|
* extent of the declared children.
|
|
107
107
|
*/
|
|
108
108
|
maxRows?: number;
|
|
109
|
+
/**
|
|
110
|
+
* Container only: the inner board's layout, exactly as a view's. `'grid'`
|
|
111
|
+
* (default) packs the children in cells; `'split'` is a splitter tree that
|
|
112
|
+
* always covers the pane. Switch live with `setLayout(mode, containerId)`;
|
|
113
|
+
* `toJSON()` writes it per container.
|
|
114
|
+
*/
|
|
115
|
+
layout?: 'grid' | 'split';
|
|
116
|
+
/**
|
|
117
|
+
* Container only, split layout: the authored splitter tree. Omit it and the
|
|
118
|
+
* tree is derived from the children's cells. `toJSON()` writes it back.
|
|
119
|
+
*/
|
|
120
|
+
tree?: SplitNode | null;
|
|
121
|
+
/**
|
|
122
|
+
* Container only: what a pull past the pane's rows does. `'grow'` (default):
|
|
123
|
+
* the container's slab grows a row in the parent — the ratchet above.
|
|
124
|
+
* `'fit'`: the pane is the bound — a child that needs a row the pane does
|
|
125
|
+
* not hold is refused where it stands, and nothing outside the pane moves.
|
|
126
|
+
*/
|
|
127
|
+
sizing?: 'fit' | 'grow';
|
|
109
128
|
}
|
|
110
129
|
/** One board. Multiple views are the tab pattern: only one is on-camera. */
|
|
111
130
|
export interface DashboardViewSpec {
|
|
@@ -282,6 +301,14 @@ export interface DashboardHandle {
|
|
|
282
301
|
* and a quiet ring; a void click clears. False when the id is unknown.
|
|
283
302
|
*/
|
|
284
303
|
focusWidget(id: string): boolean;
|
|
304
|
+
/**
|
|
305
|
+
* SELECT a widget WITHOUT moving keyboard focus — the ring and the grip,
|
|
306
|
+
* nothing else; what a mouse press does. `undefined` clears the selection
|
|
307
|
+
* on every view. False when the id is unknown.
|
|
308
|
+
*/
|
|
309
|
+
selectWidget(id: string | undefined): boolean;
|
|
310
|
+
/** The selected widget, if any (across views: only the on-camera one can be). */
|
|
311
|
+
getSelectedWidget(): string | undefined;
|
|
285
312
|
/** Every widget handle of a view (default: the active one). */
|
|
286
313
|
widgetsOf(viewId?: string): WidgetHandle[];
|
|
287
314
|
/**
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
* Cells are the truth and live in the existing `GridItemConfig`, so save/load
|
|
43
43
|
* round-trips with no extra work — same as every other kit.
|
|
44
44
|
*/
|
|
45
|
-
import { __awaiter } from "tslib";
|
|
45
|
+
import { __awaiter, __rest } from "tslib";
|
|
46
46
|
import { BatchCommand, BringNodeToFrontCommand, Command, GroupModel, NodeModel, RemoveFromGroupCommand, RemoveGroupCommand, RemoveNodeCommand, SendNodeToBackCommand, } from '@grafloria/engine';
|
|
47
47
|
import { bindDashboardGrid, } from './grid-binder.js';
|
|
48
48
|
import { bindDashboardSplit, SPLIT_TREE_KEY } from './split-binder.js';
|
|
@@ -358,7 +358,7 @@ export function createDashboardHandle(ctx) {
|
|
|
358
358
|
* tile under the container it is actually in, in every one of those states.
|
|
359
359
|
*/
|
|
360
360
|
const treeOf = (boardId) => {
|
|
361
|
-
var _a, _b, _c, _d;
|
|
361
|
+
var _a, _b, _c, _d, _e, _f;
|
|
362
362
|
const g = ctx.boardGroups.get(boardId);
|
|
363
363
|
const b = binders.get(boardId);
|
|
364
364
|
if (!g)
|
|
@@ -373,7 +373,14 @@ export function createDashboardHandle(ctx) {
|
|
|
373
373
|
const cell = cellOf(memberId);
|
|
374
374
|
const at = cell ? { x: cell.x, y: cell.y, span: cell.w, rows: cell.h } : {};
|
|
375
375
|
if (ctx.boardGroups.has(memberId)) {
|
|
376
|
-
|
|
376
|
+
// The container's LIVE layout and, under split, its live tree — the
|
|
377
|
+
// authored `tree` is stale the moment a divider moves.
|
|
378
|
+
const _g = spec !== null && spec !== void 0 ? spec : {}, { tree: _authored } = _g, rest = __rest(_g, ["tree"]);
|
|
379
|
+
void _authored;
|
|
380
|
+
const clayout = (_c = (_b = ctx.layoutOf.get(memberId)) !== null && _b !== void 0 ? _b : spec === null || spec === void 0 ? void 0 : spec.layout) !== null && _c !== void 0 ? _c : 'grid';
|
|
381
|
+
const cb = binders.get(memberId);
|
|
382
|
+
const ctree = clayout === 'split' && (cb === null || cb === void 0 ? void 0 : cb.getSplitTree) ? cb.getSplitTree() : undefined;
|
|
383
|
+
entries.push(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({ id: memberId }, rest), at), { layout: clayout }), (ctree !== undefined ? { tree: ctree } : {})), { widgets: treeOf(memberId) }));
|
|
377
384
|
}
|
|
378
385
|
else if (spec) {
|
|
379
386
|
// `pinned` is read from the NODE's lock, not the authored spec: pin()
|
|
@@ -381,7 +388,7 @@ export function createDashboardHandle(ctx) {
|
|
|
381
388
|
// the user left it (D5). Written only when true, so an unpinned
|
|
382
389
|
// widget serialises exactly as it always did.
|
|
383
390
|
const entry = Object.assign(Object.assign({}, spec), at);
|
|
384
|
-
if (((
|
|
391
|
+
if (((_f = (_e = (_d = ctx.apiRef) === null || _d === void 0 ? void 0 : _d.getModel().getNode(memberId)) === null || _e === void 0 ? void 0 : _e.state) === null || _f === void 0 ? void 0 : _f.locked) === true)
|
|
385
392
|
entry.pinned = true;
|
|
386
393
|
else
|
|
387
394
|
delete entry.pinned;
|
|
@@ -520,6 +527,24 @@ export function createDashboardHandle(ctx) {
|
|
|
520
527
|
queueMicrotask(() => retry(4));
|
|
521
528
|
return true;
|
|
522
529
|
},
|
|
530
|
+
selectWidget(id) {
|
|
531
|
+
var _a;
|
|
532
|
+
if (id === undefined) {
|
|
533
|
+
for (const b of binders.values())
|
|
534
|
+
b.selectWidget(undefined);
|
|
535
|
+
return true;
|
|
536
|
+
}
|
|
537
|
+
const b = binders.get((_a = viewOfWidget.get(id)) !== null && _a !== void 0 ? _a : '');
|
|
538
|
+
return !!b && b.selectWidget(id);
|
|
539
|
+
},
|
|
540
|
+
getSelectedWidget() {
|
|
541
|
+
for (const b of binders.values()) {
|
|
542
|
+
const s = b.getSelectedWidget();
|
|
543
|
+
if (s)
|
|
544
|
+
return s;
|
|
545
|
+
}
|
|
546
|
+
return undefined;
|
|
547
|
+
},
|
|
523
548
|
widgetsOf(viewId) {
|
|
524
549
|
var _a;
|
|
525
550
|
const v = views.find((x) => x.id === (viewId !== null && viewId !== void 0 ? viewId : ctx.active));
|
|
@@ -528,7 +553,8 @@ export function createDashboardHandle(ctx) {
|
|
|
528
553
|
setLayout(layout, viewId) {
|
|
529
554
|
var _a, _b, _c;
|
|
530
555
|
const vid = viewId !== null && viewId !== void 0 ? viewId : ctx.active;
|
|
531
|
-
|
|
556
|
+
// A view, or a CONTAINER (item 7): the same switch one level down.
|
|
557
|
+
if (!views.some((v) => v.id === vid) && !ctx.boardGroups.has(vid))
|
|
532
558
|
return;
|
|
533
559
|
if (((_a = ctx.layoutOf.get(vid)) !== null && _a !== void 0 ? _a : 'grid') === layout)
|
|
534
560
|
return;
|
|
@@ -1043,8 +1069,12 @@ export function dashboard(options) {
|
|
|
1043
1069
|
// its tree from exactly those, so any stale tree is cleared first;
|
|
1044
1070
|
// split → grid rebuilds from them, so the column cache goes too.
|
|
1045
1071
|
ctx.rebindView = (viewId, next) => {
|
|
1046
|
-
var _a, _b;
|
|
1072
|
+
var _a, _b, _c;
|
|
1047
1073
|
const v = views.find((x) => x.id === viewId);
|
|
1074
|
+
if (!v) {
|
|
1075
|
+
rebindContainerLayout(viewId, next);
|
|
1076
|
+
return;
|
|
1077
|
+
}
|
|
1048
1078
|
const g = groups.get(viewId);
|
|
1049
1079
|
const b = binders.get(viewId);
|
|
1050
1080
|
if (!v || !g || !b)
|
|
@@ -1052,6 +1082,7 @@ export function dashboard(options) {
|
|
|
1052
1082
|
const cells = b.saveLayout().cells;
|
|
1053
1083
|
const live = { rtl: b.getRtl(), static: b.getStatic(), dragHandle: b.getDragHandle() };
|
|
1054
1084
|
const focused = b.getFocusedWidget();
|
|
1085
|
+
const selected = b.getSelectedWidget();
|
|
1055
1086
|
b.dispose();
|
|
1056
1087
|
const write = (fn) => (model.runSystemWrite ? model.runSystemWrite(fn) : fn());
|
|
1057
1088
|
write(() => {
|
|
@@ -1072,9 +1103,13 @@ export function dashboard(options) {
|
|
|
1072
1103
|
binders.set(viewId, bindView(v, g, next, live));
|
|
1073
1104
|
(_a = binders.get(viewId)) === null || _a === void 0 ? void 0 : _a.sync();
|
|
1074
1105
|
// The selected widget (and its grip) survives the switch, as it does in
|
|
1075
|
-
// the DevExpress designer — the host used to have to restate it.
|
|
1106
|
+
// the DevExpress designer — the host used to have to restate it. A
|
|
1107
|
+
// MOUSE selection is not a focus (the press never focuses the host),
|
|
1108
|
+
// so it is carried on its own: the kit lab's L21 lost it (2026-09-08).
|
|
1076
1109
|
if (focused)
|
|
1077
1110
|
(_b = binders.get(viewId)) === null || _b === void 0 ? void 0 : _b.focusWidget(focused);
|
|
1111
|
+
else if (selected)
|
|
1112
|
+
(_c = binders.get(viewId)) === null || _c === void 0 ? void 0 : _c.selectWidget(selected);
|
|
1078
1113
|
};
|
|
1079
1114
|
handle.showView(ctx.active);
|
|
1080
1115
|
ctx.rebindContainer = (id) => {
|
|
@@ -1086,6 +1121,56 @@ export function dashboard(options) {
|
|
|
1086
1121
|
ctx.boardGroups.set(id, g);
|
|
1087
1122
|
bindContainer(g, w, (_a = ctx.viewOfBoard.get(id)) !== null && _a !== void 0 ? _a : ctx.active);
|
|
1088
1123
|
};
|
|
1124
|
+
/**
|
|
1125
|
+
* LIVE LAYOUT SWITCH ON A CONTAINER (item 7) — the view's contract one
|
|
1126
|
+
* level down: cells persisted where the grid reads them, the tree and
|
|
1127
|
+
* column cache cleared, the container's `layout` flipped on its spec and
|
|
1128
|
+
* its metadata, a fresh binder on the same group, selection carried.
|
|
1129
|
+
*/
|
|
1130
|
+
function rebindContainerLayout(id, next) {
|
|
1131
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
1132
|
+
const cg = ctx.boardGroups.get(id);
|
|
1133
|
+
const w = specById.get(id);
|
|
1134
|
+
const b = binders.get(id);
|
|
1135
|
+
if (!cg || !w || !w.widgets || !b)
|
|
1136
|
+
return;
|
|
1137
|
+
const cells = b.saveLayout().cells;
|
|
1138
|
+
const focused = b.getFocusedWidget();
|
|
1139
|
+
const selected = b.getSelectedWidget();
|
|
1140
|
+
b.dispose();
|
|
1141
|
+
const write = (fn) => (model.runSystemWrite ? model.runSystemWrite(fn) : fn());
|
|
1142
|
+
write(() => {
|
|
1143
|
+
var _a, _b, _c;
|
|
1144
|
+
for (const [cid, cell] of cells) {
|
|
1145
|
+
const n = model.getNode(cid);
|
|
1146
|
+
if (n)
|
|
1147
|
+
n.setMetadata('gridItem', gridItemFromCell(cell));
|
|
1148
|
+
else
|
|
1149
|
+
(_a = model.getGroup(cid)) === null || _a === void 0 ? void 0 : _a.setMetadata('gridItem', gridItemFromCell(cell));
|
|
1150
|
+
}
|
|
1151
|
+
cg.setMetadata(SPLIT_TREE_KEY, undefined);
|
|
1152
|
+
cg.setMetadata('dashboardLayouts', undefined);
|
|
1153
|
+
const board = (_b = cg.getMetadata('dashboardBoard')) !== null && _b !== void 0 ? _b : {};
|
|
1154
|
+
cg.setMetadata('dashboardBoard', Object.assign(Object.assign({}, board), { layout: next }));
|
|
1155
|
+
const cw = (_c = cg.getMetadata('containerWidget')) !== null && _c !== void 0 ? _c : {};
|
|
1156
|
+
cg.setMetadata('containerWidget', Object.assign(Object.assign({}, cw), { layout: next }));
|
|
1157
|
+
});
|
|
1158
|
+
w.layout = next;
|
|
1159
|
+
delete w.tree;
|
|
1160
|
+
ctx.layoutOf.set(id, next);
|
|
1161
|
+
// The inner bound is the slab's LIVE row count, not the authored design:
|
|
1162
|
+
// a child escalation grew to two rows must keep them through a
|
|
1163
|
+
// split → grid round trip (the visual gate caught it collapsing to one
|
|
1164
|
+
// inside a two-row slab).
|
|
1165
|
+
const slabRows = (_c = (_b = binders.get((_a = ctx.viewOfWidget.get(id)) !== null && _a !== void 0 ? _a : '')) === null || _b === void 0 ? void 0 : _b.cellOf(id)) === null || _c === void 0 ? void 0 : _c.h;
|
|
1166
|
+
const authored = (_d = w.maxRows) !== null && _d !== void 0 ? _d : rowExtentOf((_e = w.widgets) !== null && _e !== void 0 ? _e : []);
|
|
1167
|
+
bindContainer(cg, w, (_f = ctx.viewOfBoard.get(id)) !== null && _f !== void 0 ? _f : ctx.active, Math.max(authored, slabRows !== null && slabRows !== void 0 ? slabRows : 0));
|
|
1168
|
+
(_g = binders.get(id)) === null || _g === void 0 ? void 0 : _g.sync();
|
|
1169
|
+
if (focused)
|
|
1170
|
+
(_h = binders.get(id)) === null || _h === void 0 ? void 0 : _h.focusWidget(focused);
|
|
1171
|
+
else if (selected)
|
|
1172
|
+
(_j = binders.get(id)) === null || _j === void 0 ? void 0 : _j.selectWidget(selected);
|
|
1173
|
+
}
|
|
1089
1174
|
(_m = ctx.attachHistory) === null || _m === void 0 ? void 0 : _m.call(ctx);
|
|
1090
1175
|
return;
|
|
1091
1176
|
/**
|
|
@@ -1098,7 +1183,7 @@ export function dashboard(options) {
|
|
|
1098
1183
|
* hit-testing and the height-escalation ratchet all apply unchanged.
|
|
1099
1184
|
*/
|
|
1100
1185
|
function mountBoard(boardId, viewId, widgets, boardGroup) {
|
|
1101
|
-
var _a, _b, _c, _d, _e;
|
|
1186
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
1102
1187
|
for (const w of widgets) {
|
|
1103
1188
|
if (w.widgets) {
|
|
1104
1189
|
const innerColumns = innerColumnsOf(w);
|
|
@@ -1110,7 +1195,11 @@ export function dashboard(options) {
|
|
|
1110
1195
|
cg.setMetadata('gridItem', gridItemFromCell({ x: w.x, y: w.y, w: w.span, h: w.rows }));
|
|
1111
1196
|
// The container's own spec fields, persisted ON the group — a
|
|
1112
1197
|
// reloaded document has no authored literal to read them from.
|
|
1113
|
-
cg.setMetadata('containerWidget', Object.assign(Object.assign(Object.assign(Object.assign({}, (w.kind !== undefined ? { kind: w.kind } : {})), (w.title !== undefined ? { title: w.title } : {})), { columns: innerColumns, maxRows: innerRows }), (w.data !== undefined ? { data: w.data } : {})));
|
|
1198
|
+
cg.setMetadata('containerWidget', Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, (w.kind !== undefined ? { kind: w.kind } : {})), (w.title !== undefined ? { title: w.title } : {})), { columns: innerColumns, maxRows: innerRows }), (w.data !== undefined ? { data: w.data } : {})), (w.layout !== undefined ? { layout: w.layout } : {})), (w.sizing !== undefined ? { sizing: w.sizing } : {})));
|
|
1199
|
+
// Item 7: the container's own layout and bound, persisted like a view's.
|
|
1200
|
+
ctx.layoutOf.set(w.id, (_c = w.layout) !== null && _c !== void 0 ? _c : 'grid');
|
|
1201
|
+
if (w.layout === 'split' && w.tree !== undefined)
|
|
1202
|
+
cg.setMetadata(SPLIT_TREE_KEY, w.tree);
|
|
1114
1203
|
cg.setMetadata('dashboardBoard', {
|
|
1115
1204
|
columns: innerColumns,
|
|
1116
1205
|
gap,
|
|
@@ -1123,7 +1212,9 @@ export function dashboard(options) {
|
|
|
1123
1212
|
designHeight: 0,
|
|
1124
1213
|
maxRows: innerRows,
|
|
1125
1214
|
float: false,
|
|
1126
|
-
rtl: (
|
|
1215
|
+
rtl: (_d = options.rtl) !== null && _d !== void 0 ? _d : false,
|
|
1216
|
+
layout: (_e = w.layout) !== null && _e !== void 0 ? _e : 'grid',
|
|
1217
|
+
escalate: w.sizing !== 'fit',
|
|
1127
1218
|
});
|
|
1128
1219
|
cg.size = { width: 100, height: rowHeight, depth: 0 };
|
|
1129
1220
|
boardGroup.addMember(w.id);
|
|
@@ -1143,7 +1234,7 @@ export function dashboard(options) {
|
|
|
1143
1234
|
// and made toJSON() → dashboard() NOT round-trip (a saved layout
|
|
1144
1235
|
// rebuilt back into its declaration order rather than its cells).
|
|
1145
1236
|
if (w.x !== undefined && w.y !== undefined) {
|
|
1146
|
-
n.setGridItem(gridItemFromCell({ x: w.x, y: w.y, w: (
|
|
1237
|
+
n.setGridItem(gridItemFromCell({ x: w.x, y: w.y, w: (_f = w.span) !== null && _f !== void 0 ? _f : 3, h: (_g = w.rows) !== null && _g !== void 0 ? _g : 1 }));
|
|
1147
1238
|
}
|
|
1148
1239
|
if (w.pinned)
|
|
1149
1240
|
n.setState({ locked: true });
|
|
@@ -1181,14 +1272,25 @@ export function dashboard(options) {
|
|
|
1181
1272
|
return bindDashboardGrid(a, g, Object.assign(Object.assign(Object.assign({}, common), { columns: (_j = v.columns) !== null && _j !== void 0 ? _j : columns, sizing, baseRowHeight: rowHeight, designHeight: viewH(v), float: (_k = options.float) !== null && _k !== void 0 ? _k : false, overflow }), (options.responsive ? { responsive: options.responsive } : {})));
|
|
1182
1273
|
}
|
|
1183
1274
|
/** Bind (or re-bind) a container's inner grid on its group. */
|
|
1184
|
-
function bindContainer(cg, w, viewId) {
|
|
1185
|
-
var _a, _b, _c, _d, _e;
|
|
1186
|
-
|
|
1275
|
+
function bindContainer(cg, w, viewId, innerRows) {
|
|
1276
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
|
|
1277
|
+
void viewId;
|
|
1278
|
+
// The LIVE switches ride on the view's binder when it exists (a
|
|
1279
|
+
// container bound after a setStatic/setRtl/setDragHandle must match).
|
|
1280
|
+
const vb = binders.get((_a = ctx.viewOfBoard.get(w.id)) !== null && _a !== void 0 ? _a : ctx.active);
|
|
1281
|
+
const inner = Object.assign(Object.assign({ columns: innerColumnsOf(w), gap, padding: 0, baseRowHeight: rowHeight, rtl: (_c = (_b = vb === null || vb === void 0 ? void 0 : vb.getRtl()) !== null && _b !== void 0 ? _b : options.rtl) !== null && _c !== void 0 ? _c : false, static: (_e = (_d = vb === null || vb === void 0 ? void 0 : vb.getStatic()) !== null && _d !== void 0 ? _d : options.static) !== null && _e !== void 0 ? _e : false, dragHandle: (_g = (_f = vb === null || vb === void 0 ? void 0 : vb.getDragHandle()) !== null && _f !== void 0 ? _f : options.dragHandle) !== null && _g !== void 0 ? _g : false }, (options.squeeze !== undefined ? { squeeze: options.squeeze } : {})), { onGesture: (e) => {
|
|
1187
1282
|
var _a, _b;
|
|
1188
1283
|
if (e.type === 'commit')
|
|
1189
1284
|
reportChanged();
|
|
1190
1285
|
(_b = (_a = options.binder) === null || _a === void 0 ? void 0 : _a.onGesture) === null || _b === void 0 ? void 0 : _b.call(_a, e);
|
|
1191
|
-
} })
|
|
1286
|
+
} });
|
|
1287
|
+
if (((_h = ctx.layoutOf.get(w.id)) !== null && _h !== void 0 ? _h : w.layout) === 'split') {
|
|
1288
|
+
// A splitter tree covering the pane; the pane's frame is the parent's
|
|
1289
|
+
// slab, so no design height of its own.
|
|
1290
|
+
binders.set(w.id, bindDashboardSplit(a, cg, Object.assign(Object.assign({}, inner), (w.tree !== undefined ? { tree: w.tree } : {}))));
|
|
1291
|
+
return;
|
|
1292
|
+
}
|
|
1293
|
+
binders.set(w.id, bindDashboardGrid(a, cg, Object.assign(Object.assign({}, inner), { sizing: 'fit', designHeight: 0, maxRows: (_j = innerRows !== null && innerRows !== void 0 ? innerRows : w.maxRows) !== null && _j !== void 0 ? _j : rowExtentOf((_k = w.widgets) !== null && _k !== void 0 ? _k : []), float: false, escalate: w.sizing !== 'fit' })));
|
|
1192
1294
|
}
|
|
1193
1295
|
/**
|
|
1194
1296
|
* One reporter for every binder on a view — the view's own and each
|
|
@@ -50,6 +50,7 @@
|
|
|
50
50
|
* second `bindDashboardGrid` on the section for a nested pack grid.
|
|
51
51
|
*/
|
|
52
52
|
import { Command, type DiagramModel, type GridColumnLayout, type GroupModel, type NodeModel } from '@grafloria/engine';
|
|
53
|
+
import { type ToolPointerEvent } from '@grafloria/renderer';
|
|
53
54
|
import { type CellRect, type WorldRect } from './grid-mapping.js';
|
|
54
55
|
/** The slice of a DiagramInstance the binder needs (structural, test-friendly). */
|
|
55
56
|
export interface DashboardGridApi {
|
|
@@ -147,6 +148,13 @@ export interface DashboardGridOptions {
|
|
|
147
148
|
* row instead), and the strip can never be squeezed.
|
|
148
149
|
*/
|
|
149
150
|
maxRows?: number;
|
|
151
|
+
/**
|
|
152
|
+
* Nested boards only. `true` (default): a child pulled clearly past the
|
|
153
|
+
* bound GROWS the container's slab in the parent (height escalation).
|
|
154
|
+
* `false`: the pane is the bound — the pull is refused where it stands, the
|
|
155
|
+
* container's `sizing: 'fit'`.
|
|
156
|
+
*/
|
|
157
|
+
escalate?: boolean;
|
|
150
158
|
/**
|
|
151
159
|
* What dragging a tile OUT of the board means (default 'cancel' — the tile
|
|
152
160
|
* snaps back on release). 'remove' dims the ghost outside the board and a
|
|
@@ -261,6 +269,23 @@ export declare const gripOf: (v: DragHandleOption) => DragGripOptions | null;
|
|
|
261
269
|
*/
|
|
262
270
|
export declare function syncGrip(host: HTMLElement, cfg: DragGripOptions | null, movable: boolean): void;
|
|
263
271
|
/** The member host a press on a painted grip belongs to (the grip may sit OUTSIDE the host's box). */
|
|
272
|
+
/**
|
|
273
|
+
* Is this press OURS? The renderer's tool registry is PAGE-GLOBAL: every
|
|
274
|
+
* registered tool is asked about every press on every canvas, ties going to
|
|
275
|
+
* the first registered. Two boards on one page sharing widget ids — a designer
|
|
276
|
+
* beside its preview, a page of examples — had the FIRST board's tool claim
|
|
277
|
+
* the second's presses: it selected its own tile and moved nothing (kit lab,
|
|
278
|
+
* 2026-09-08: 16 of 23 boards dead to the mouse). A tool claims only a press
|
|
279
|
+
* whose DOM target sits in its own container and whose hit node is its own
|
|
280
|
+
* diagram's object, not a namesake from another model.
|
|
281
|
+
*/
|
|
282
|
+
export declare function ownsPress(container: HTMLElement, diagram: {
|
|
283
|
+
getNode(id: string): unknown;
|
|
284
|
+
}, ev: ToolPointerEvent, hit: {
|
|
285
|
+
node?: {
|
|
286
|
+
id: string;
|
|
287
|
+
};
|
|
288
|
+
}): boolean;
|
|
264
289
|
export declare function gripHostOf(target: Element | null): HTMLElement | null;
|
|
265
290
|
/**
|
|
266
291
|
* Did a press land on the drag handle? A press INSIDE the handle element
|
|
@@ -313,6 +338,14 @@ export interface DashboardGridHandle {
|
|
|
313
338
|
focusWidget(id: string): boolean;
|
|
314
339
|
/** The member the roving tabindex currently rests on. */
|
|
315
340
|
getFocusedWidget(): string | undefined;
|
|
341
|
+
/**
|
|
342
|
+
* SELECT a member without moving keyboard focus — what a mouse press does
|
|
343
|
+
* (the renderer cancels the press's default, so a click never focuses the
|
|
344
|
+
* host). `undefined` clears. False for a non-member.
|
|
345
|
+
*/
|
|
346
|
+
selectWidget(id: string | undefined): boolean;
|
|
347
|
+
/** The selected member: the one wearing the ring and, in grip mode, the grip. */
|
|
348
|
+
getSelectedWidget(): string | undefined;
|
|
316
349
|
/**
|
|
317
350
|
* The layout to PERSIST — from the engine's LARGEST cached column count, so
|
|
318
351
|
* saving while the board is narrow still saves the wide layout the user
|
|
@@ -380,4 +413,83 @@ export interface DashboardGridHandle {
|
|
|
380
413
|
}, event: PointerEvent): void;
|
|
381
414
|
dispose(): void;
|
|
382
415
|
}
|
|
416
|
+
/**
|
|
417
|
+
* CROSS-CONTAINER HANDOFF. Binders on the same canvas register here; a move
|
|
418
|
+
* gesture whose pointer enters ANOTHER registered board (deepest wins — the
|
|
419
|
+
* nested KPI section beats the tab that contains it) hands the tile off: the
|
|
420
|
+
* source engine drops it (survivors settle home), the target engine ADOPTS it
|
|
421
|
+
* (gateless first placement, then the normal live-push loop), and release
|
|
422
|
+
* commits ONE batch across both boards — displaced tiles on each side,
|
|
423
|
+
* RemoveFromGroup + AddToGroup, the tile's new cells and geometry. This is
|
|
424
|
+
* what makes "drag Total Revenue under Top reps" MOVE the KPI to the main
|
|
425
|
+
* board rather than snapping it home (live review: parking was a guard, not
|
|
426
|
+
* the feature).
|
|
427
|
+
*/
|
|
428
|
+
interface BinderPeer {
|
|
429
|
+
group: GroupModel;
|
|
430
|
+
/** True when this board's engine holds `id` as an item (member lookup). */
|
|
431
|
+
hasItem(id: string): boolean;
|
|
432
|
+
/** The member's current cell in this board (undefined when absent). */
|
|
433
|
+
memberCell(id: string): CellRect | undefined;
|
|
434
|
+
/**
|
|
435
|
+
* Grow/shrink a member's row span by `dRows` — the parent half of nested
|
|
436
|
+
* HEIGHT ESCALATION: pulling a KPI taller than its one-row strip grows the
|
|
437
|
+
* STRIP's slab in the board that contains it (live report: "i cant
|
|
438
|
+
* increase height"). Returns the cell+frame before/after when accepted.
|
|
439
|
+
*/
|
|
440
|
+
resizeMemberBy(id: string, dRows: number): {
|
|
441
|
+
changed: boolean;
|
|
442
|
+
cellBefore?: CellRect;
|
|
443
|
+
cellAfter?: CellRect;
|
|
444
|
+
frameBefore?: WorldRect;
|
|
445
|
+
frameAfter?: WorldRect;
|
|
446
|
+
};
|
|
447
|
+
containsWorld(x: number, y: number): boolean;
|
|
448
|
+
/**
|
|
449
|
+
* Containment plus ONE extra row of grace below the frame — gridstack's
|
|
450
|
+
* `_extraDragRow`: dropping "under the last row" appends a row rather than
|
|
451
|
+
* counting as off-board. Consulted only when NO strict frame matched, so a
|
|
452
|
+
* nested strip's band can never steal a point that strictly belongs to the
|
|
453
|
+
* board below it.
|
|
454
|
+
*/
|
|
455
|
+
containsWorldExtended(x: number, y: number): boolean;
|
|
456
|
+
frameArea(): number;
|
|
457
|
+
adopt(node: NodeModel, world: {
|
|
458
|
+
x: number;
|
|
459
|
+
y: number;
|
|
460
|
+
}, pxSize: {
|
|
461
|
+
width: number;
|
|
462
|
+
height: number;
|
|
463
|
+
}): AdoptedLeg | null;
|
|
464
|
+
}
|
|
465
|
+
interface AdoptedLeg {
|
|
466
|
+
groupId: string;
|
|
467
|
+
/** Drive the target engine from the source binder's pointer stream. */
|
|
468
|
+
move(world: {
|
|
469
|
+
x: number;
|
|
470
|
+
y: number;
|
|
471
|
+
}): void;
|
|
472
|
+
/** Undo the adoption: target board back to its pre-entry layout. */
|
|
473
|
+
abort(): void;
|
|
474
|
+
/**
|
|
475
|
+
* Close the leg for commit: returns the target-side displaced commands, the
|
|
476
|
+
* tile's final cell and its projected rect. Null when the tile is somehow
|
|
477
|
+
* gone (treat as abort).
|
|
478
|
+
*/
|
|
479
|
+
finalize(): {
|
|
480
|
+
commands: Command[];
|
|
481
|
+
cell: CellRect;
|
|
482
|
+
rect: WorldRect;
|
|
483
|
+
} | null;
|
|
484
|
+
}
|
|
485
|
+
/**
|
|
486
|
+
* Register a board that is NOT a grid (the split binder on a container) as a
|
|
487
|
+
* peer on its canvas, so the parent grid's hitTest defers a press on one of
|
|
488
|
+
* its tiles to it ("a press on a tile that belongs to a NESTED board must
|
|
489
|
+
* reach that board's tool") whatever the registration order. Returns the
|
|
490
|
+
* unregister. A split board adopts nothing and grows no slab: its `adopt`
|
|
491
|
+
* answers null and `resizeMemberBy` answers unchanged.
|
|
492
|
+
*/
|
|
493
|
+
export declare function registerBoardPeer(container: HTMLElement, peer: BinderPeer): () => void;
|
|
494
|
+
export type { BinderPeer };
|
|
383
495
|
export declare function bindDashboardGrid(api: DashboardGridApi, group: GroupModel, options?: DashboardGridOptions): DashboardGridHandle;
|
|
@@ -92,6 +92,25 @@ export function syncGrip(host, cfg, movable) {
|
|
|
92
92
|
host.classList.add(`axdb-gp-${(_d = cfg.placement) !== null && _d !== void 0 ? _d : 'inside'}`, `axdb-gp-${(_e = cfg.position) !== null && _e !== void 0 ? _e : 'left'}`);
|
|
93
93
|
}
|
|
94
94
|
/** The member host a press on a painted grip belongs to (the grip may sit OUTSIDE the host's box). */
|
|
95
|
+
/**
|
|
96
|
+
* Is this press OURS? The renderer's tool registry is PAGE-GLOBAL: every
|
|
97
|
+
* registered tool is asked about every press on every canvas, ties going to
|
|
98
|
+
* the first registered. Two boards on one page sharing widget ids — a designer
|
|
99
|
+
* beside its preview, a page of examples — had the FIRST board's tool claim
|
|
100
|
+
* the second's presses: it selected its own tile and moved nothing (kit lab,
|
|
101
|
+
* 2026-09-08: 16 of 23 boards dead to the mouse). A tool claims only a press
|
|
102
|
+
* whose DOM target sits in its own container and whose hit node is its own
|
|
103
|
+
* diagram's object, not a namesake from another model.
|
|
104
|
+
*/
|
|
105
|
+
export function ownsPress(container, diagram, ev, hit) {
|
|
106
|
+
var _a;
|
|
107
|
+
const t = (_a = ev.source) === null || _a === void 0 ? void 0 : _a.target;
|
|
108
|
+
if (typeof Node !== 'undefined' && t instanceof Node && !container.contains(t))
|
|
109
|
+
return false;
|
|
110
|
+
if (hit.node && diagram.getNode(hit.node.id) !== hit.node)
|
|
111
|
+
return false;
|
|
112
|
+
return true;
|
|
113
|
+
}
|
|
95
114
|
export function gripHostOf(target) {
|
|
96
115
|
var _a, _b;
|
|
97
116
|
const grip = (_a = target === null || target === void 0 ? void 0 : target.closest) === null || _a === void 0 ? void 0 : _a.call(target, '.' + GRIP_CLASS);
|
|
@@ -126,6 +145,26 @@ export const CAPTION_BAND = 28;
|
|
|
126
145
|
* page (review D12).
|
|
127
146
|
*/
|
|
128
147
|
const BOARD_REGISTRY = new WeakMap();
|
|
148
|
+
/**
|
|
149
|
+
* Register a board that is NOT a grid (the split binder on a container) as a
|
|
150
|
+
* peer on its canvas, so the parent grid's hitTest defers a press on one of
|
|
151
|
+
* its tiles to it ("a press on a tile that belongs to a NESTED board must
|
|
152
|
+
* reach that board's tool") whatever the registration order. Returns the
|
|
153
|
+
* unregister. A split board adopts nothing and grows no slab: its `adopt`
|
|
154
|
+
* answers null and `resizeMemberBy` answers unchanged.
|
|
155
|
+
*/
|
|
156
|
+
export function registerBoardPeer(container, peer) {
|
|
157
|
+
let set = BOARD_REGISTRY.get(container);
|
|
158
|
+
if (!set) {
|
|
159
|
+
set = new Set();
|
|
160
|
+
BOARD_REGISTRY.set(container, set);
|
|
161
|
+
}
|
|
162
|
+
set.add(peer);
|
|
163
|
+
const s = set;
|
|
164
|
+
return () => {
|
|
165
|
+
s.delete(peer);
|
|
166
|
+
};
|
|
167
|
+
}
|
|
129
168
|
/**
|
|
130
169
|
* ONE aria-live region per canvas, shared by every board on it — the
|
|
131
170
|
* renderer's own controller (coalescing, de-duplicating), so a dashboard
|
|
@@ -243,6 +282,8 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
243
282
|
let float = (_f = options.float) !== null && _f !== void 0 ? _f : false;
|
|
244
283
|
/** The AUTHORED bound — a nested strip's design (row-first push, escalation). */
|
|
245
284
|
const maxRows = options.maxRows;
|
|
285
|
+
/** May a pull past the bound grow the slab in the parent? `false` = the pane is the bound. */
|
|
286
|
+
const escalate = options.escalate !== false;
|
|
246
287
|
const dragOut = (_g = options.dragOut) !== null && _g !== void 0 ? _g : 'cancel';
|
|
247
288
|
const wantHandles = options.resizeHandles !== false;
|
|
248
289
|
const fluid = options.fluid === true;
|
|
@@ -575,15 +616,35 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
575
616
|
clearTimeout(glideTimer);
|
|
576
617
|
glideTimer = setTimeout(() => { var _a; return (_a = htmlLayer()) === null || _a === void 0 ? void 0 : _a.classList.remove('axdb-glide'); }, GLIDE_OFF_DELAY);
|
|
577
618
|
};
|
|
619
|
+
/** The host whose ghost class the pending timer will lift. */
|
|
620
|
+
let ghostHost = null;
|
|
621
|
+
/**
|
|
622
|
+
* Lift a pending ghost NOW. One timer serves every host, so superseding it
|
|
623
|
+
* (a new gesture within 60 ms of the last drop — ③ then ④ in the
|
|
624
|
+
* nested-containers checks — or a dispose on a rebind) used to clear the
|
|
625
|
+
* timer and leave the previous tile lifted for good: a permanent drop
|
|
626
|
+
* shadow the visual gate finally caught.
|
|
627
|
+
*/
|
|
628
|
+
const flushGhost = () => {
|
|
629
|
+
if (ghostTimer)
|
|
630
|
+
clearTimeout(ghostTimer);
|
|
631
|
+
ghostTimer = null;
|
|
632
|
+
ghostHost === null || ghostHost === void 0 ? void 0 : ghostHost.classList.remove('axdb-ghost', 'axdb-out');
|
|
633
|
+
ghostHost = null;
|
|
634
|
+
};
|
|
578
635
|
const setGhost = (id, on) => {
|
|
579
636
|
const host = hostOf(id);
|
|
580
637
|
if (!host)
|
|
581
638
|
return;
|
|
639
|
+
if (ghostHost && ghostHost !== host)
|
|
640
|
+
flushGhost();
|
|
582
641
|
if (on) {
|
|
583
642
|
if (ghostTimer)
|
|
584
643
|
clearTimeout(ghostTimer);
|
|
644
|
+
ghostTimer = null;
|
|
585
645
|
host.classList.add('axdb-ghost');
|
|
586
646
|
host.classList.remove('axdb-out');
|
|
647
|
+
ghostHost = host;
|
|
587
648
|
}
|
|
588
649
|
else {
|
|
589
650
|
host.classList.remove('axdb-out');
|
|
@@ -591,7 +652,13 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
591
652
|
// placeholder is INSTANT (gridstack-style), then let glides resume.
|
|
592
653
|
if (ghostTimer)
|
|
593
654
|
clearTimeout(ghostTimer);
|
|
594
|
-
|
|
655
|
+
ghostHost = host;
|
|
656
|
+
ghostTimer = setTimeout(() => {
|
|
657
|
+
host.classList.remove('axdb-ghost');
|
|
658
|
+
ghostTimer = null;
|
|
659
|
+
if (ghostHost === host)
|
|
660
|
+
ghostHost = null;
|
|
661
|
+
}, 60);
|
|
595
662
|
}
|
|
596
663
|
};
|
|
597
664
|
// -- resize handles ---------------------------------------------------------
|
|
@@ -1308,7 +1375,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1308
1375
|
// the strip's CURRENT slab rows, whatever gesture created them; the
|
|
1309
1376
|
// ledger just accumulates this gesture's net change for the one-batch
|
|
1310
1377
|
// commit and for Escape.
|
|
1311
|
-
if (maxRows !== undefined && g.kind === 'resize') {
|
|
1378
|
+
if (maxRows !== undefined && escalate && g.kind === 'resize') {
|
|
1312
1379
|
const parent = parentPeer();
|
|
1313
1380
|
if (parent) {
|
|
1314
1381
|
const visual = boardVisualHeight();
|
|
@@ -1673,6 +1740,8 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1673
1740
|
return false;
|
|
1674
1741
|
if (gesture)
|
|
1675
1742
|
return true; // own the rest of an in-flight gesture
|
|
1743
|
+
if (!ownsPress(api.container, diagram, ev, hit))
|
|
1744
|
+
return false;
|
|
1676
1745
|
if (hit.node) {
|
|
1677
1746
|
if (((_a = group.members) !== null && _a !== void 0 ? _a : new Set()).has(hit.node.id))
|
|
1678
1747
|
return true;
|
|
@@ -1825,8 +1894,10 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1825
1894
|
}
|
|
1826
1895
|
}
|
|
1827
1896
|
}
|
|
1828
|
-
if (hoverHost && hoverHost !== host)
|
|
1897
|
+
if (hoverHost && hoverHost !== host) {
|
|
1829
1898
|
hoverHost.style.cursor = '';
|
|
1899
|
+
hoverHost.removeAttribute('data-axdb-edge'); // the affordance follows the pointer off a tile
|
|
1900
|
+
}
|
|
1830
1901
|
hoverHost = host;
|
|
1831
1902
|
if (!host)
|
|
1832
1903
|
return;
|
|
@@ -2262,6 +2333,16 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2262
2333
|
return true;
|
|
2263
2334
|
},
|
|
2264
2335
|
getFocusedWidget: () => focusedId,
|
|
2336
|
+
selectWidget(id) {
|
|
2337
|
+
var _a;
|
|
2338
|
+
if (disposed)
|
|
2339
|
+
return false;
|
|
2340
|
+
if (id !== undefined && (!((_a = group.members) !== null && _a !== void 0 ? _a : new Set()).has(id) || !diagram.getNode(id)))
|
|
2341
|
+
return false;
|
|
2342
|
+
selectWidget(id);
|
|
2343
|
+
return true;
|
|
2344
|
+
},
|
|
2345
|
+
getSelectedWidget: () => selectedId,
|
|
2265
2346
|
setStatic(on) {
|
|
2266
2347
|
if (on === isStatic)
|
|
2267
2348
|
return;
|
|
@@ -2412,8 +2493,10 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2412
2493
|
placeholder = null;
|
|
2413
2494
|
if (glideTimer)
|
|
2414
2495
|
clearTimeout(glideTimer);
|
|
2415
|
-
|
|
2416
|
-
|
|
2496
|
+
// A rebind inside the 60 ms window (a layout switch right after an undo)
|
|
2497
|
+
// disposed this binder with the timer pending — the host kept its
|
|
2498
|
+
// lifted ghost for good (visual gate, nested-containers ⑤).
|
|
2499
|
+
flushGhost();
|
|
2417
2500
|
(_d = htmlLayer()) === null || _d === void 0 ? void 0 : _d.classList.remove('axdb-glide');
|
|
2418
2501
|
api.container.style.cursor = '';
|
|
2419
2502
|
},
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
import { __awaiter } from "tslib";
|
|
26
26
|
import { Command } from '@grafloria/engine';
|
|
27
27
|
import { LiveRegionController, registerTool } from '@grafloria/renderer';
|
|
28
|
-
import { dragHandleSelector, gripHostOf, gripOf, normalizeDragHandle, pressOnDragHandle, syncGrip, DRAG_HANDLE_CLASS } from './grid-binder.js';
|
|
28
|
+
import { dragHandleSelector, gripHostOf, gripOf, normalizeDragHandle, ownsPress, pressOnDragHandle, registerBoardPeer, syncGrip, DRAG_HANDLE_CLASS } from './grid-binder.js';
|
|
29
29
|
import { cellFromGridItem } from './grid-mapping.js';
|
|
30
30
|
import { addSplitLeaf, cellsFromSplit, cloneSplit, dividersOf, groupRectsOf, insertSplitLeaf, moveSplitDivider, normalizeSplit, pathToLeaf, projectSplit, removeSplitLeaf, splitFromCells, splitLeaves, } from './split-layout.js';
|
|
31
31
|
import { ensureDashboardKitStyles } from './styles.js';
|
|
@@ -631,6 +631,8 @@ export function bindDashboardSplit(api, group, options = {}) {
|
|
|
631
631
|
return false;
|
|
632
632
|
if (gesture)
|
|
633
633
|
return true;
|
|
634
|
+
if (!ownsPress(api.container, diagram, ev, hit))
|
|
635
|
+
return false;
|
|
634
636
|
if (hit.node)
|
|
635
637
|
return ((_a = group.members) !== null && _a !== void 0 ? _a : new Set()).has(hit.node.id);
|
|
636
638
|
return worldInsideBoard(ev.world.x, ev.world.y);
|
|
@@ -1025,6 +1027,16 @@ export function bindDashboardSplit(api, group, options = {}) {
|
|
|
1025
1027
|
return true;
|
|
1026
1028
|
},
|
|
1027
1029
|
getFocusedWidget: () => focusedId,
|
|
1030
|
+
selectWidget(id) {
|
|
1031
|
+
var _a;
|
|
1032
|
+
if (disposed)
|
|
1033
|
+
return false;
|
|
1034
|
+
if (id !== undefined && (!((_a = group.members) !== null && _a !== void 0 ? _a : new Set()).has(id) || !diagram.getNode(id)))
|
|
1035
|
+
return false;
|
|
1036
|
+
selectWidget(id);
|
|
1037
|
+
return true;
|
|
1038
|
+
},
|
|
1039
|
+
getSelectedWidget: () => selectedId,
|
|
1028
1040
|
saveLayout() {
|
|
1029
1041
|
return { columns, cells: cellsFromSplit(readTree(), columns, rowsGuess()) };
|
|
1030
1042
|
},
|
|
@@ -1154,6 +1166,26 @@ export function bindDashboardSplit(api, group, options = {}) {
|
|
|
1154
1166
|
applyFluidFrame();
|
|
1155
1167
|
project(reconcile());
|
|
1156
1168
|
api.renderNow();
|
|
1169
|
+
// A split board on a CONTAINER (item 7) sits inside a parent grid: register
|
|
1170
|
+
// as a peer so the parent's hitTest hands presses on our tiles to us.
|
|
1171
|
+
const unregisterPeer = registerBoardPeer(api.container, {
|
|
1172
|
+
group,
|
|
1173
|
+
hasItem: (id) => { var _a; return ((_a = group.members) !== null && _a !== void 0 ? _a : new Set()).has(id); },
|
|
1174
|
+
memberCell: (id) => handle.cellOf(id),
|
|
1175
|
+
resizeMemberBy: () => ({ changed: false }),
|
|
1176
|
+
containsWorld: (x, y) => worldInsideBoard(x, y),
|
|
1177
|
+
containsWorldExtended: (x, y) => worldInsideBoard(x, y),
|
|
1178
|
+
frameArea: () => {
|
|
1179
|
+
const f = frame();
|
|
1180
|
+
return f.width * f.height;
|
|
1181
|
+
},
|
|
1182
|
+
adopt: () => null,
|
|
1183
|
+
});
|
|
1184
|
+
const disposeHandle = handle.dispose.bind(handle);
|
|
1185
|
+
handle.dispose = () => {
|
|
1186
|
+
unregisterPeer();
|
|
1187
|
+
disposeHandle();
|
|
1188
|
+
};
|
|
1157
1189
|
return handle;
|
|
1158
1190
|
}
|
|
1159
1191
|
//# sourceMappingURL=split-binder.js.map
|
|
@@ -244,6 +244,11 @@ const CSS = `
|
|
|
244
244
|
.grafloria-node-host.axdb-gp-inside.axdb-gp-right .axdb-widget > .axdb-widget-h { padding-right: 32px; }
|
|
245
245
|
.grafloria-node-host.axdb-gp-inside.axdb-gp-center .axdb-widget > .axdb-widget-h { padding-top: 12px; }
|
|
246
246
|
.axdb-widget-b { flex: 1; min-height: 0; position: relative; }
|
|
247
|
+
/* A drag across a STATIC board (nothing prevents the press's default there)
|
|
248
|
+
used to select every label on it; kit cards are not prose. Tables stay
|
|
249
|
+
copyable — a figure in a grid is the one thing a viewer selects. */
|
|
250
|
+
.axdb-widget { user-select: none; -webkit-user-select: none; }
|
|
251
|
+
.axdb-widget .axdb-table { user-select: text; -webkit-user-select: text; }
|
|
247
252
|
.axdb-widget-b > svg { display: block; width: 100%; height: 100%; }
|
|
248
253
|
.axdb-widget-b.axdb-scroll { overflow: auto; }
|
|
249
254
|
/* A chart WITH a legend under it: the plot yields height, the legend keeps its
|
|
@@ -332,6 +337,9 @@ const CSS = `
|
|
|
332
337
|
tile gets a bigger ring, not dead card), square, capped so its centre figure
|
|
333
338
|
stays a figure and not a headline. */
|
|
334
339
|
.axdb-widget-b.axdb-donut { display: flex; align-items: center; gap: 10px; }
|
|
340
|
+
/* A legend taller than a SHORT body (a 2-row donut on a squeezed board) is
|
|
341
|
+
clipped at its own foot, not centred over the title above it. */
|
|
342
|
+
.axdb-widget-b.axdb-donut > .axdb-lg--col { max-height: 100%; min-height: 0; overflow: hidden; }
|
|
335
343
|
.axdb-widget-b.axdb-donut > svg {
|
|
336
344
|
flex: 0 0 auto; width: auto; height: 100%; max-height: 260px; max-width: 60%; aspect-ratio: 1 / 1;
|
|
337
345
|
}
|
package/src/lib/load.js
CHANGED
|
@@ -206,7 +206,7 @@ export function fromDocument(document, options = {}) {
|
|
|
206
206
|
// a fixed world — it stays one. Fluid is only what was saved fluid.
|
|
207
207
|
mode: (firstBoard === null || firstBoard === void 0 ? void 0 : firstBoard.fluid) === true ? 'fluid' : 'fixed',
|
|
208
208
|
overflow: (_p = firstBoard === null || firstBoard === void 0 ? void 0 : firstBoard.overflow) !== null && _p !== void 0 ? _p : 'bounded',
|
|
209
|
-
layoutOf: new Map(
|
|
209
|
+
layoutOf: new Map(dashGroups.map((g) => { var _a, _b; return [g.id, ((_b = (_a = g.getMetadata('dashboardBoard')) === null || _a === void 0 ? void 0 : _a.layout) !== null && _b !== void 0 ? _b : 'grid')]; })),
|
|
210
210
|
// responsive is NOT in the document (a runtime seam), so it is deliberately
|
|
211
211
|
// absent from the round-trip; width/height/columns/gap/sizing/float/rtl are.
|
|
212
212
|
optionsBase: firstBoard
|