@grafloria/element 0.4.13 → 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 +19 -0
- package/src/lib/dashboard-kit/dashboard.js +92 -13
- package/src/lib/dashboard-kit/grid-binder.d.ts +86 -0
- package/src/lib/dashboard-kit/grid-binder.js +54 -4
- package/src/lib/dashboard-kit/split-binder.js +21 -1
- 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 {
|
|
@@ -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;
|
|
@@ -546,7 +553,8 @@ export function createDashboardHandle(ctx) {
|
|
|
546
553
|
setLayout(layout, viewId) {
|
|
547
554
|
var _a, _b, _c;
|
|
548
555
|
const vid = viewId !== null && viewId !== void 0 ? viewId : ctx.active;
|
|
549
|
-
|
|
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))
|
|
550
558
|
return;
|
|
551
559
|
if (((_a = ctx.layoutOf.get(vid)) !== null && _a !== void 0 ? _a : 'grid') === layout)
|
|
552
560
|
return;
|
|
@@ -1063,6 +1071,10 @@ export function dashboard(options) {
|
|
|
1063
1071
|
ctx.rebindView = (viewId, next) => {
|
|
1064
1072
|
var _a, _b, _c;
|
|
1065
1073
|
const v = views.find((x) => x.id === viewId);
|
|
1074
|
+
if (!v) {
|
|
1075
|
+
rebindContainerLayout(viewId, next);
|
|
1076
|
+
return;
|
|
1077
|
+
}
|
|
1066
1078
|
const g = groups.get(viewId);
|
|
1067
1079
|
const b = binders.get(viewId);
|
|
1068
1080
|
if (!v || !g || !b)
|
|
@@ -1109,6 +1121,56 @@ export function dashboard(options) {
|
|
|
1109
1121
|
ctx.boardGroups.set(id, g);
|
|
1110
1122
|
bindContainer(g, w, (_a = ctx.viewOfBoard.get(id)) !== null && _a !== void 0 ? _a : ctx.active);
|
|
1111
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
|
+
}
|
|
1112
1174
|
(_m = ctx.attachHistory) === null || _m === void 0 ? void 0 : _m.call(ctx);
|
|
1113
1175
|
return;
|
|
1114
1176
|
/**
|
|
@@ -1121,7 +1183,7 @@ export function dashboard(options) {
|
|
|
1121
1183
|
* hit-testing and the height-escalation ratchet all apply unchanged.
|
|
1122
1184
|
*/
|
|
1123
1185
|
function mountBoard(boardId, viewId, widgets, boardGroup) {
|
|
1124
|
-
var _a, _b, _c, _d, _e;
|
|
1186
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
1125
1187
|
for (const w of widgets) {
|
|
1126
1188
|
if (w.widgets) {
|
|
1127
1189
|
const innerColumns = innerColumnsOf(w);
|
|
@@ -1133,7 +1195,11 @@ export function dashboard(options) {
|
|
|
1133
1195
|
cg.setMetadata('gridItem', gridItemFromCell({ x: w.x, y: w.y, w: w.span, h: w.rows }));
|
|
1134
1196
|
// The container's own spec fields, persisted ON the group — a
|
|
1135
1197
|
// reloaded document has no authored literal to read them from.
|
|
1136
|
-
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);
|
|
1137
1203
|
cg.setMetadata('dashboardBoard', {
|
|
1138
1204
|
columns: innerColumns,
|
|
1139
1205
|
gap,
|
|
@@ -1146,7 +1212,9 @@ export function dashboard(options) {
|
|
|
1146
1212
|
designHeight: 0,
|
|
1147
1213
|
maxRows: innerRows,
|
|
1148
1214
|
float: false,
|
|
1149
|
-
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',
|
|
1150
1218
|
});
|
|
1151
1219
|
cg.size = { width: 100, height: rowHeight, depth: 0 };
|
|
1152
1220
|
boardGroup.addMember(w.id);
|
|
@@ -1166,7 +1234,7 @@ export function dashboard(options) {
|
|
|
1166
1234
|
// and made toJSON() → dashboard() NOT round-trip (a saved layout
|
|
1167
1235
|
// rebuilt back into its declaration order rather than its cells).
|
|
1168
1236
|
if (w.x !== undefined && w.y !== undefined) {
|
|
1169
|
-
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 }));
|
|
1170
1238
|
}
|
|
1171
1239
|
if (w.pinned)
|
|
1172
1240
|
n.setState({ locked: true });
|
|
@@ -1204,14 +1272,25 @@ export function dashboard(options) {
|
|
|
1204
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 } : {})));
|
|
1205
1273
|
}
|
|
1206
1274
|
/** Bind (or re-bind) a container's inner grid on its group. */
|
|
1207
|
-
function bindContainer(cg, w, viewId) {
|
|
1208
|
-
var _a, _b, _c, _d, _e;
|
|
1209
|
-
|
|
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) => {
|
|
1210
1282
|
var _a, _b;
|
|
1211
1283
|
if (e.type === 'commit')
|
|
1212
1284
|
reportChanged();
|
|
1213
1285
|
(_b = (_a = options.binder) === null || _a === void 0 ? void 0 : _a.onGesture) === null || _b === void 0 ? void 0 : _b.call(_a, e);
|
|
1214
|
-
} })
|
|
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' })));
|
|
1215
1294
|
}
|
|
1216
1295
|
/**
|
|
1217
1296
|
* One reporter for every binder on a view — the view's own and each
|
|
@@ -148,6 +148,13 @@ export interface DashboardGridOptions {
|
|
|
148
148
|
* row instead), and the strip can never be squeezed.
|
|
149
149
|
*/
|
|
150
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;
|
|
151
158
|
/**
|
|
152
159
|
* What dragging a tile OUT of the board means (default 'cancel' — the tile
|
|
153
160
|
* snaps back on release). 'remove' dims the ghost outside the board and a
|
|
@@ -406,4 +413,83 @@ export interface DashboardGridHandle {
|
|
|
406
413
|
}, event: PointerEvent): void;
|
|
407
414
|
dispose(): void;
|
|
408
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 };
|
|
409
495
|
export declare function bindDashboardGrid(api: DashboardGridApi, group: GroupModel, options?: DashboardGridOptions): DashboardGridHandle;
|
|
@@ -145,6 +145,26 @@ export const CAPTION_BAND = 28;
|
|
|
145
145
|
* page (review D12).
|
|
146
146
|
*/
|
|
147
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
|
+
}
|
|
148
168
|
/**
|
|
149
169
|
* ONE aria-live region per canvas, shared by every board on it — the
|
|
150
170
|
* renderer's own controller (coalescing, de-duplicating), so a dashboard
|
|
@@ -262,6 +282,8 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
262
282
|
let float = (_f = options.float) !== null && _f !== void 0 ? _f : false;
|
|
263
283
|
/** The AUTHORED bound — a nested strip's design (row-first push, escalation). */
|
|
264
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;
|
|
265
287
|
const dragOut = (_g = options.dragOut) !== null && _g !== void 0 ? _g : 'cancel';
|
|
266
288
|
const wantHandles = options.resizeHandles !== false;
|
|
267
289
|
const fluid = options.fluid === true;
|
|
@@ -594,15 +616,35 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
594
616
|
clearTimeout(glideTimer);
|
|
595
617
|
glideTimer = setTimeout(() => { var _a; return (_a = htmlLayer()) === null || _a === void 0 ? void 0 : _a.classList.remove('axdb-glide'); }, GLIDE_OFF_DELAY);
|
|
596
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
|
+
};
|
|
597
635
|
const setGhost = (id, on) => {
|
|
598
636
|
const host = hostOf(id);
|
|
599
637
|
if (!host)
|
|
600
638
|
return;
|
|
639
|
+
if (ghostHost && ghostHost !== host)
|
|
640
|
+
flushGhost();
|
|
601
641
|
if (on) {
|
|
602
642
|
if (ghostTimer)
|
|
603
643
|
clearTimeout(ghostTimer);
|
|
644
|
+
ghostTimer = null;
|
|
604
645
|
host.classList.add('axdb-ghost');
|
|
605
646
|
host.classList.remove('axdb-out');
|
|
647
|
+
ghostHost = host;
|
|
606
648
|
}
|
|
607
649
|
else {
|
|
608
650
|
host.classList.remove('axdb-out');
|
|
@@ -610,7 +652,13 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
610
652
|
// placeholder is INSTANT (gridstack-style), then let glides resume.
|
|
611
653
|
if (ghostTimer)
|
|
612
654
|
clearTimeout(ghostTimer);
|
|
613
|
-
|
|
655
|
+
ghostHost = host;
|
|
656
|
+
ghostTimer = setTimeout(() => {
|
|
657
|
+
host.classList.remove('axdb-ghost');
|
|
658
|
+
ghostTimer = null;
|
|
659
|
+
if (ghostHost === host)
|
|
660
|
+
ghostHost = null;
|
|
661
|
+
}, 60);
|
|
614
662
|
}
|
|
615
663
|
};
|
|
616
664
|
// -- resize handles ---------------------------------------------------------
|
|
@@ -1327,7 +1375,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1327
1375
|
// the strip's CURRENT slab rows, whatever gesture created them; the
|
|
1328
1376
|
// ledger just accumulates this gesture's net change for the one-batch
|
|
1329
1377
|
// commit and for Escape.
|
|
1330
|
-
if (maxRows !== undefined && g.kind === 'resize') {
|
|
1378
|
+
if (maxRows !== undefined && escalate && g.kind === 'resize') {
|
|
1331
1379
|
const parent = parentPeer();
|
|
1332
1380
|
if (parent) {
|
|
1333
1381
|
const visual = boardVisualHeight();
|
|
@@ -2445,8 +2493,10 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2445
2493
|
placeholder = null;
|
|
2446
2494
|
if (glideTimer)
|
|
2447
2495
|
clearTimeout(glideTimer);
|
|
2448
|
-
|
|
2449
|
-
|
|
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();
|
|
2450
2500
|
(_d = htmlLayer()) === null || _d === void 0 ? void 0 : _d.classList.remove('axdb-glide');
|
|
2451
2501
|
api.container.style.cursor = '';
|
|
2452
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, ownsPress, 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';
|
|
@@ -1166,6 +1166,26 @@ export function bindDashboardSplit(api, group, options = {}) {
|
|
|
1166
1166
|
applyFluidFrame();
|
|
1167
1167
|
project(reconcile());
|
|
1168
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
|
+
};
|
|
1169
1189
|
return handle;
|
|
1170
1190
|
}
|
|
1171
1191
|
//# sourceMappingURL=split-binder.js.map
|
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
|