@grafloria/element 0.4.18 → 0.4.20
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 +6 -0
- package/src/lib/dashboard-kit/dashboard.js +2 -2
- package/src/lib/dashboard-kit/grid-binder.d.ts +22 -0
- package/src/lib/dashboard-kit/grid-binder.js +388 -35
- package/src/lib/dashboard-kit/split-binder.d.ts +1 -1
- package/src/lib/dashboard-kit/split-binder.js +4 -0
- package/src/lib/dashboard-kit/styles.js +12 -2
package/package.json
CHANGED
|
@@ -252,6 +252,12 @@ export interface DashboardOptions {
|
|
|
252
252
|
* hook). Omit for a titled placeholder frame.
|
|
253
253
|
*/
|
|
254
254
|
renderWidget?: (widget: DashboardWidgetSpec, host: HTMLElement) => void;
|
|
255
|
+
/**
|
|
256
|
+
* The selection changed on a board: the selected id — a widget or a SECTION
|
|
257
|
+
* (container) — or undefined when cleared, and the view it belongs to. A
|
|
258
|
+
* press on a section's empty band selects the section.
|
|
259
|
+
*/
|
|
260
|
+
onSelect?: (id: string | undefined, viewId: string) => void;
|
|
255
261
|
/** Fires after any committed gesture, with the view whose layout changed. */
|
|
256
262
|
onLayoutChange?: (viewId: string, widgets: DashboardWidgetSpec[]) => void;
|
|
257
263
|
/** Extra binder options, merged last (escape hatch to the layer below). */
|
|
@@ -1264,7 +1264,7 @@ export function dashboard(options) {
|
|
|
1264
1264
|
if (e.type === 'commit')
|
|
1265
1265
|
reportChanged();
|
|
1266
1266
|
(_b = (_a = options.binder) === null || _a === void 0 ? void 0 : _a.onGesture) === null || _b === void 0 ? void 0 : _b.call(_a, e);
|
|
1267
|
-
} });
|
|
1267
|
+
}, onSelect: (id) => { var _a; return (_a = options.onSelect) === null || _a === void 0 ? void 0 : _a.call(options, id, v.id); } });
|
|
1268
1268
|
if (viewLayout === 'split') {
|
|
1269
1269
|
return bindDashboardSplit(a, g, Object.assign(Object.assign(Object.assign({}, common), { columns: (_h = v.columns) !== null && _h !== void 0 ? _h : columns, baseRowHeight: rowHeight, designHeight: viewH(v) }), (v.tree !== undefined ? { tree: v.tree } : {})));
|
|
1270
1270
|
}
|
|
@@ -1282,7 +1282,7 @@ export function dashboard(options) {
|
|
|
1282
1282
|
if (e.type === 'commit')
|
|
1283
1283
|
reportChanged();
|
|
1284
1284
|
(_b = (_a = options.binder) === null || _a === void 0 ? void 0 : _a.onGesture) === null || _b === void 0 ? void 0 : _b.call(_a, e);
|
|
1285
|
-
} });
|
|
1285
|
+
}, onSelect: (id) => { var _a, _b; return (_a = options.onSelect) === null || _a === void 0 ? void 0 : _a.call(options, id, (_b = ctx.viewOfBoard.get(w.id)) !== null && _b !== void 0 ? _b : ctx.active); } });
|
|
1286
1286
|
if (((_h = ctx.layoutOf.get(w.id)) !== null && _h !== void 0 ? _h : w.layout) === 'split') {
|
|
1287
1287
|
// A splitter tree covering the pane; the pane's frame is the parent's
|
|
1288
1288
|
// slab, so no design height of its own.
|
|
@@ -196,6 +196,8 @@ export interface DashboardGridOptions {
|
|
|
196
196
|
changed: boolean;
|
|
197
197
|
}) => void;
|
|
198
198
|
/** Inject the hover-revealed corner resize handle into member hosts (default true). */
|
|
199
|
+
/** The selection on this board changed: the selected member — a widget or a SECTION (container) — or undefined. */
|
|
200
|
+
onSelect?: (id: string | undefined) => void;
|
|
199
201
|
resizeHandles?: boolean;
|
|
200
202
|
/**
|
|
201
203
|
* FLUID board: the group's frame follows the CANVAS CONTAINER — width
|
|
@@ -436,6 +438,20 @@ interface BinderPeer {
|
|
|
436
438
|
* boards after three clicks).
|
|
437
439
|
*/
|
|
438
440
|
clearSelection?(): void;
|
|
441
|
+
/** A nested board's live row bound — what its slab must never shrink below. */
|
|
442
|
+
innerRows?(): number;
|
|
443
|
+
/**
|
|
444
|
+
* SECTION PRESSES. A press on a section's empty band lands on the NESTED
|
|
445
|
+
* board's tool (it claims its own frame), which knows nothing to select;
|
|
446
|
+
* the section is a member of the PARENT. The child asks the parent to
|
|
447
|
+
* select it and, on an edge or the corner handle, to run the section
|
|
448
|
+
* resize — and forwards the rest of the pointer sequence.
|
|
449
|
+
*/
|
|
450
|
+
selectMember?(id: string): void;
|
|
451
|
+
beginSlabResize?(id: string, edges: ResizeEdges, ev: ToolPointerEvent): boolean;
|
|
452
|
+
slabMove?(ev: ToolPointerEvent): void;
|
|
453
|
+
slabUp?(): void;
|
|
454
|
+
slabCancel?(): void;
|
|
439
455
|
/** The member's current cell in this board (undefined when absent). */
|
|
440
456
|
memberCell(id: string): CellRect | undefined;
|
|
441
457
|
/**
|
|
@@ -501,4 +517,10 @@ interface AdoptedLeg {
|
|
|
501
517
|
export declare function clearOtherSelections(container: HTMLElement, self: BinderPeer | null): void;
|
|
502
518
|
export declare function registerBoardPeer(container: HTMLElement, peer: BinderPeer): () => void;
|
|
503
519
|
export type { BinderPeer };
|
|
520
|
+
interface ResizeEdges {
|
|
521
|
+
n: boolean;
|
|
522
|
+
e: boolean;
|
|
523
|
+
s: boolean;
|
|
524
|
+
w: boolean;
|
|
525
|
+
}
|
|
504
526
|
export declare function bindDashboardGrid(api: DashboardGridApi, group: GroupModel, options?: DashboardGridOptions): DashboardGridHandle;
|
|
@@ -453,7 +453,15 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
453
453
|
designHeight: sizing === 'fit' ? frame().height : designH,
|
|
454
454
|
rtl,
|
|
455
455
|
});
|
|
456
|
-
|
|
456
|
+
/**
|
|
457
|
+
* The rows the board is laid out in. A BOUNDED board (a section) keeps its
|
|
458
|
+
* bound's rows even when its content ends higher — empty rows stay empty
|
|
459
|
+
* rows, as in gridstack — instead of stretching the rest to fill the slab:
|
|
460
|
+
* that stretch made a tile pulled shorter shrink FASTER than the pointer
|
|
461
|
+
* (each row it gave up made the remaining rows taller, so the same pixel
|
|
462
|
+
* height quantised to fewer rows on the next move — kit lab L37).
|
|
463
|
+
*/
|
|
464
|
+
const rows = () => Math.max(1, engine.rows(), maxRows !== null && maxRows !== void 0 ? maxRows : 0);
|
|
457
465
|
const htmlLayer = () => api.container.querySelector('.grafloria-html-layer');
|
|
458
466
|
const hostOf = (id) => {
|
|
459
467
|
const esc = typeof CSS !== 'undefined' && CSS.escape ? CSS.escape(id) : id.replace(/"/g, '\\"');
|
|
@@ -465,6 +473,19 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
465
473
|
/** The engine item a member should enter as (cells from GridItemConfig /
|
|
466
474
|
* group metadata; spans falling back to metadata columnSpan/rowSpan; last
|
|
467
475
|
* resort: adopt from the current pixel geometry). */
|
|
476
|
+
/**
|
|
477
|
+
* A SECTION's floor: its nested board's live rows (the peer), else the rows
|
|
478
|
+
* it was mounted with — a section resized by hand never squeezes its
|
|
479
|
+
* children below their cells.
|
|
480
|
+
*/
|
|
481
|
+
const innerRowsOf = (id) => {
|
|
482
|
+
var _a, _b, _c, _d;
|
|
483
|
+
for (const p of (_a = BOARD_REGISTRY.get(api.container)) !== null && _a !== void 0 ? _a : [])
|
|
484
|
+
if (p.group.id === id && p.innerRows)
|
|
485
|
+
return Math.max(1, p.innerRows());
|
|
486
|
+
const meta = (_c = (_b = diagram.getGroup(id)) === null || _b === void 0 ? void 0 : _b.getMetadata) === null || _c === void 0 ? void 0 : _c.call(_b, 'containerWidget');
|
|
487
|
+
return Math.max(1, (_d = meta === null || meta === void 0 ? void 0 : meta.maxRows) !== null && _d !== void 0 ? _d : 1);
|
|
488
|
+
};
|
|
468
489
|
const itemFor = (id) => {
|
|
469
490
|
var _a, _b, _c, _d, _e, _f, _g;
|
|
470
491
|
const node = diagram.getNode(id);
|
|
@@ -708,23 +729,27 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
708
729
|
*/
|
|
709
730
|
let selectedId;
|
|
710
731
|
const selectWidget = (id) => {
|
|
732
|
+
var _a;
|
|
711
733
|
if (id !== undefined)
|
|
712
734
|
clearOtherSelections(api.container, selfPeerRef);
|
|
713
735
|
if (id === selectedId)
|
|
714
736
|
return;
|
|
715
737
|
selectedId = id;
|
|
716
738
|
syncA11y();
|
|
739
|
+
syncSlabs();
|
|
740
|
+
(_a = options.onSelect) === null || _a === void 0 ? void 0 : _a.call(options, id);
|
|
717
741
|
};
|
|
718
742
|
/** Set once the peer object exists (below); `selectWidget` runs before that only on boot. */
|
|
719
743
|
let selfPeerRef = null;
|
|
720
744
|
const syncA11y = (only) => {
|
|
721
|
-
var _a, _b;
|
|
745
|
+
var _a, _b, _c;
|
|
722
746
|
if (disposed)
|
|
723
747
|
return;
|
|
724
748
|
const members = [...((_a = group.members) !== null && _a !== void 0 ? _a : [])].filter((id) => !!diagram.getNode(id));
|
|
725
749
|
if (focusedId && !members.includes(focusedId))
|
|
726
750
|
focusedId = undefined;
|
|
727
|
-
|
|
751
|
+
// A selected SECTION is a group member, not a node: keep it as long as it is a member.
|
|
752
|
+
if (selectedId && !members.includes(selectedId) && !(((_b = group.members) === null || _b === void 0 ? void 0 : _b.has(selectedId)) && memberEntity(selectedId)))
|
|
728
753
|
selectedId = undefined;
|
|
729
754
|
const stop = focusedId !== null && focusedId !== void 0 ? focusedId : members[0];
|
|
730
755
|
for (const id of members) {
|
|
@@ -738,7 +763,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
738
763
|
const bits = [nameOf(node)];
|
|
739
764
|
if (cell)
|
|
740
765
|
bits.push(describeCell(cell));
|
|
741
|
-
if (((
|
|
766
|
+
if (((_c = node.state) === null || _c === void 0 ? void 0 : _c.locked) === true)
|
|
742
767
|
bits.push('pinned');
|
|
743
768
|
host.setAttribute('role', 'group');
|
|
744
769
|
host.setAttribute('aria-roledescription', 'dashboard widget');
|
|
@@ -759,6 +784,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
759
784
|
if (disposed)
|
|
760
785
|
return;
|
|
761
786
|
ensureStaticGuard();
|
|
787
|
+
syncSlabs();
|
|
762
788
|
const grip = gripOf(dragHandle);
|
|
763
789
|
for (const id of (_a = group.members) !== null && _a !== void 0 ? _a : []) {
|
|
764
790
|
if (only && !only.has(id))
|
|
@@ -975,6 +1001,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
975
1001
|
finally {
|
|
976
1002
|
writing = false;
|
|
977
1003
|
}
|
|
1004
|
+
syncSlabs();
|
|
978
1005
|
};
|
|
979
1006
|
const applyColumns = (n, layout) => {
|
|
980
1007
|
var _a;
|
|
@@ -1044,6 +1071,80 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1044
1071
|
const band = rowHeightFor(geom(), rows()) + gap;
|
|
1045
1072
|
return x >= f.x && x <= f.x + f.width && y >= f.y && y <= f.y + boardVisualHeight() + band;
|
|
1046
1073
|
};
|
|
1074
|
+
/** The member SECTION whose frame holds the world point, if any. */
|
|
1075
|
+
const memberGroupAt = (x, y) => {
|
|
1076
|
+
var _a;
|
|
1077
|
+
for (const id of (_a = group.members) !== null && _a !== void 0 ? _a : []) {
|
|
1078
|
+
const grp = diagram.getGroup(id);
|
|
1079
|
+
if (!grp)
|
|
1080
|
+
continue;
|
|
1081
|
+
const p = grp.position;
|
|
1082
|
+
const s = sizeOf(grp);
|
|
1083
|
+
if (x >= p.x && x <= p.x + s.width && y >= p.y && y <= p.y + s.height)
|
|
1084
|
+
return id;
|
|
1085
|
+
}
|
|
1086
|
+
return null;
|
|
1087
|
+
};
|
|
1088
|
+
/** Which of a section frame's edges a world point is within EDGE_GRIP of. */
|
|
1089
|
+
const slabEdgesNear = (grp, x, y) => {
|
|
1090
|
+
const p = grp.position;
|
|
1091
|
+
const s = sizeOf(grp);
|
|
1092
|
+
return { n: y - p.y <= EDGE_GRIP, s: p.y + s.height - y <= EDGE_GRIP, w: x - p.x <= EDGE_GRIP, e: p.x + s.width - x <= EDGE_GRIP };
|
|
1093
|
+
};
|
|
1094
|
+
/**
|
|
1095
|
+
* SECTION CHROME. A section (member group) paints no card of its own, so it
|
|
1096
|
+
* had nothing to press: a click on its empty band cleared the selection and
|
|
1097
|
+
* its frame had no handle and no edge — a section with many children could
|
|
1098
|
+
* not be selected at all, and could only be resized by pulling a child
|
|
1099
|
+
* (Quantia, Groups page). Every section gets a pointer-transparent overlay
|
|
1100
|
+
* in the HTML layer that wears the selection ring and, while selected, the
|
|
1101
|
+
* corner handle; its frame edges answer the resize cursor.
|
|
1102
|
+
*/
|
|
1103
|
+
const slabEls = new Map();
|
|
1104
|
+
let slabLayer = null;
|
|
1105
|
+
const syncSlabs = () => {
|
|
1106
|
+
var _a, _b;
|
|
1107
|
+
if (disposed)
|
|
1108
|
+
return;
|
|
1109
|
+
const layer = (slabLayer === null || slabLayer === void 0 ? void 0 : slabLayer.isConnected) ? slabLayer : (slabLayer = htmlLayer());
|
|
1110
|
+
if (!layer)
|
|
1111
|
+
return;
|
|
1112
|
+
const seen = new Set();
|
|
1113
|
+
for (const id of (_a = group.members) !== null && _a !== void 0 ? _a : []) {
|
|
1114
|
+
const grp = diagram.getGroup(id);
|
|
1115
|
+
if (!grp || diagram.getNode(id))
|
|
1116
|
+
continue;
|
|
1117
|
+
seen.add(id);
|
|
1118
|
+
let el = slabEls.get(id);
|
|
1119
|
+
if (!el || el.parentElement !== layer) {
|
|
1120
|
+
el === null || el === void 0 ? void 0 : el.remove();
|
|
1121
|
+
el = document.createElement('div');
|
|
1122
|
+
el.className = 'axdb-slab';
|
|
1123
|
+
el.setAttribute('data-slab-id', id);
|
|
1124
|
+
const rs = document.createElement('div');
|
|
1125
|
+
rs.className = 'axdb-rs';
|
|
1126
|
+
rs.setAttribute('title', 'Resize section');
|
|
1127
|
+
el.appendChild(rs);
|
|
1128
|
+
layer.appendChild(el);
|
|
1129
|
+
slabEls.set(id, el);
|
|
1130
|
+
}
|
|
1131
|
+
const p = grp.position;
|
|
1132
|
+
const sz = sizeOf(grp);
|
|
1133
|
+
el.style.left = `${p.x}px`;
|
|
1134
|
+
el.style.top = `${p.y}px`;
|
|
1135
|
+
el.style.width = `${sz.width}px`;
|
|
1136
|
+
el.style.height = `${sz.height}px`;
|
|
1137
|
+
el.classList.toggle('axdb-slab--selected', selectedId === id);
|
|
1138
|
+
el.classList.toggle('axdb-slab--static', isStatic);
|
|
1139
|
+
(_b = el.querySelector(':scope > .axdb-rs')) === null || _b === void 0 ? void 0 : _b.classList.toggle('axdb-rs--rtl', rtl);
|
|
1140
|
+
}
|
|
1141
|
+
for (const [id, el] of slabEls) {
|
|
1142
|
+
if (!seen.has(id)) {
|
|
1143
|
+
el.remove();
|
|
1144
|
+
slabEls.delete(id);
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
};
|
|
1047
1148
|
const insideMemberGroupFrame = (x, y) => {
|
|
1048
1149
|
var _a;
|
|
1049
1150
|
for (const id of (_a = group.members) !== null && _a !== void 0 ? _a : []) {
|
|
@@ -1057,6 +1158,131 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1057
1158
|
}
|
|
1058
1159
|
return false;
|
|
1059
1160
|
};
|
|
1161
|
+
let slabGesture = null;
|
|
1162
|
+
/** The PARENT running a resize of OUR section from a press this tool claimed. */
|
|
1163
|
+
let forwardSlab = null;
|
|
1164
|
+
const frameOfGroup = (grp) => ({ x: grp.position.x, y: grp.position.y, width: sizeOf(grp).width, height: sizeOf(grp).height });
|
|
1165
|
+
const beginSlabResize = (id, edges, ev) => {
|
|
1166
|
+
const grp = diagram.getGroup(id);
|
|
1167
|
+
const it = engine.getItem(id);
|
|
1168
|
+
if (!grp || !it || gesture || slabGesture)
|
|
1169
|
+
return;
|
|
1170
|
+
engine.beginGesture();
|
|
1171
|
+
const snap = snapshotAll();
|
|
1172
|
+
slabGesture = {
|
|
1173
|
+
id,
|
|
1174
|
+
edges,
|
|
1175
|
+
pointerId: typeof PointerEvent !== 'undefined' && ev.source instanceof PointerEvent ? ev.source.pointerId : null,
|
|
1176
|
+
started: false,
|
|
1177
|
+
downScreen: { x: ev.screen.x, y: ev.screen.y },
|
|
1178
|
+
startCells: snap.cells,
|
|
1179
|
+
startGeom: snap.geoms,
|
|
1180
|
+
cellBefore: { x: it.x, y: it.y, w: it.w, h: it.h },
|
|
1181
|
+
frameBefore: frameOfGroup(grp),
|
|
1182
|
+
grab: {
|
|
1183
|
+
dx: edges.e ? grp.position.x + sizeOf(grp).width - ev.world.x : edges.w ? grp.position.x - ev.world.x : 0,
|
|
1184
|
+
dy: edges.s ? grp.position.y + sizeOf(grp).height - ev.world.y : edges.n ? grp.position.y - ev.world.y : 0,
|
|
1185
|
+
},
|
|
1186
|
+
};
|
|
1187
|
+
capturePointer(slabGesture.pointerId);
|
|
1188
|
+
api.container.style.cursor = cursorFor(edges);
|
|
1189
|
+
};
|
|
1190
|
+
const slabMove = (ev) => {
|
|
1191
|
+
const g = slabGesture;
|
|
1192
|
+
if (!g)
|
|
1193
|
+
return;
|
|
1194
|
+
if (!g.started) {
|
|
1195
|
+
if (Math.abs(ev.screen.x - g.downScreen.x) + Math.abs(ev.screen.y - g.downScreen.y) < DRAG_THRESHOLD)
|
|
1196
|
+
return;
|
|
1197
|
+
g.started = true;
|
|
1198
|
+
armGlide();
|
|
1199
|
+
}
|
|
1200
|
+
const it = engine.getItem(g.id);
|
|
1201
|
+
if (!it)
|
|
1202
|
+
return;
|
|
1203
|
+
const f = frame();
|
|
1204
|
+
const gg = geom();
|
|
1205
|
+
const cu = columnUnitFor(gg, f.width);
|
|
1206
|
+
const rh = rowHeightFor(gg, rows());
|
|
1207
|
+
// The grid line nearest the pointer, in cells (mirrored on RTL).
|
|
1208
|
+
const colAt = (wx) => Math.round((rtl ? f.x + f.width - padding - wx : wx - f.x - padding) / (cu + gap));
|
|
1209
|
+
const rowAt = (wy) => Math.round((wy - f.y - padding) / (rh + gap));
|
|
1210
|
+
let { x, y, w, h } = it;
|
|
1211
|
+
const px = ev.world.x + g.grab.dx;
|
|
1212
|
+
const py = ev.world.y + g.grab.dy;
|
|
1213
|
+
if (g.edges.e)
|
|
1214
|
+
w = Math.max(1, colAt(px) - x);
|
|
1215
|
+
if (g.edges.s)
|
|
1216
|
+
h = Math.max(1, rowAt(py) - y);
|
|
1217
|
+
if (g.edges.w) {
|
|
1218
|
+
const nx = Math.max(0, Math.min(x + w - 1, colAt(px)));
|
|
1219
|
+
w = x + w - nx;
|
|
1220
|
+
x = nx;
|
|
1221
|
+
}
|
|
1222
|
+
if (g.edges.n) {
|
|
1223
|
+
const ny = Math.max(0, Math.min(y + h - 1, rowAt(py)));
|
|
1224
|
+
h = y + h - ny;
|
|
1225
|
+
y = ny;
|
|
1226
|
+
}
|
|
1227
|
+
w = Math.max(1, Math.min(w, columns - x));
|
|
1228
|
+
// Never below the children's rows (the nested board's live bound).
|
|
1229
|
+
const floor = innerRowsOf(g.id);
|
|
1230
|
+
if (h < floor) {
|
|
1231
|
+
if (g.edges.n)
|
|
1232
|
+
y = y + h - floor;
|
|
1233
|
+
h = floor;
|
|
1234
|
+
}
|
|
1235
|
+
let changed = false;
|
|
1236
|
+
if (x !== it.x || y !== it.y)
|
|
1237
|
+
changed = engine.moveCheck(g.id, x, y, { gate: false }).changed || changed;
|
|
1238
|
+
if (w !== it.w || h !== it.h)
|
|
1239
|
+
changed = engine.resizeCheck(g.id, w, h).changed || changed;
|
|
1240
|
+
if (changed)
|
|
1241
|
+
project();
|
|
1242
|
+
};
|
|
1243
|
+
const slabUp = () => {
|
|
1244
|
+
var _a;
|
|
1245
|
+
const g = slabGesture;
|
|
1246
|
+
if (!g)
|
|
1247
|
+
return;
|
|
1248
|
+
slabGesture = null;
|
|
1249
|
+
releasePointer(g.pointerId);
|
|
1250
|
+
api.container.style.cursor = '';
|
|
1251
|
+
if (!g.started) {
|
|
1252
|
+
engine.endGesture();
|
|
1253
|
+
return;
|
|
1254
|
+
}
|
|
1255
|
+
engine.endGesture();
|
|
1256
|
+
project();
|
|
1257
|
+
const it = engine.getItem(g.id);
|
|
1258
|
+
const grp = diagram.getGroup(g.id);
|
|
1259
|
+
const commands = buildCommitCommands(deltasSince(g.startCells, g.startGeom, g.id));
|
|
1260
|
+
const b = g.cellBefore;
|
|
1261
|
+
if (it && grp && (b.x !== it.x || b.y !== it.y || b.w !== it.w || b.h !== it.h)) {
|
|
1262
|
+
commands.push(new SetGroupCellCommand(g.id, b, { x: it.x, y: it.y, w: it.w, h: it.h }, g.frameBefore, frameOfGroup(grp)));
|
|
1263
|
+
}
|
|
1264
|
+
const changed = execute('Resize section', commands);
|
|
1265
|
+
disarmGlideSoon();
|
|
1266
|
+
syncHandles();
|
|
1267
|
+
api.renderNow();
|
|
1268
|
+
(_a = options.onGesture) === null || _a === void 0 ? void 0 : _a.call(options, { type: 'commit', kind: 'resize', nodeId: g.id, changed });
|
|
1269
|
+
};
|
|
1270
|
+
const slabCancel = () => {
|
|
1271
|
+
var _a;
|
|
1272
|
+
const g = slabGesture;
|
|
1273
|
+
if (!g)
|
|
1274
|
+
return;
|
|
1275
|
+
slabGesture = null;
|
|
1276
|
+
releasePointer(g.pointerId);
|
|
1277
|
+
api.container.style.cursor = '';
|
|
1278
|
+
if (g.started)
|
|
1279
|
+
engine.cancelGesture();
|
|
1280
|
+
else
|
|
1281
|
+
engine.endGesture();
|
|
1282
|
+
project();
|
|
1283
|
+
disarmGlideSoon();
|
|
1284
|
+
(_a = options.onGesture) === null || _a === void 0 ? void 0 : _a.call(options, { type: 'cancel', kind: 'resize', nodeId: g.id, changed: false });
|
|
1285
|
+
};
|
|
1060
1286
|
const capturePointer = (pointerId) => {
|
|
1061
1287
|
var _a, _b;
|
|
1062
1288
|
if (pointerId === null)
|
|
@@ -1163,7 +1389,13 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1163
1389
|
(_a = options.onGesture) === null || _a === void 0 ? void 0 : _a.call(options, { type: 'commit', kind: g.kind, nodeId: g.id, changed });
|
|
1164
1390
|
};
|
|
1165
1391
|
const cancelActiveGesture = (notify = true) => {
|
|
1166
|
-
var _a, _b, _c;
|
|
1392
|
+
var _a, _b, _c, _d;
|
|
1393
|
+
if (forwardSlab) {
|
|
1394
|
+
(_a = forwardSlab.slabCancel) === null || _a === void 0 ? void 0 : _a.call(forwardSlab);
|
|
1395
|
+
forwardSlab = null;
|
|
1396
|
+
}
|
|
1397
|
+
if (slabGesture)
|
|
1398
|
+
slabCancel();
|
|
1167
1399
|
const g = gesture;
|
|
1168
1400
|
if (!g)
|
|
1169
1401
|
return;
|
|
@@ -1184,7 +1416,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1184
1416
|
engine.endGesture();
|
|
1185
1417
|
const items = [];
|
|
1186
1418
|
for (const [id, c] of g.startCells) {
|
|
1187
|
-
const lockedNode = ((
|
|
1419
|
+
const lockedNode = ((_c = (_b = diagram.getNode(id)) === null || _b === void 0 ? void 0 : _b.state) === null || _c === void 0 ? void 0 : _c.locked) === true;
|
|
1188
1420
|
items.push(Object.assign(Object.assign({ id }, c), { locked: lockedNode || isGroupMember(id) }));
|
|
1189
1421
|
}
|
|
1190
1422
|
engine = engineFrom(items);
|
|
@@ -1232,7 +1464,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1232
1464
|
enforceBoardHeight();
|
|
1233
1465
|
api.renderNow();
|
|
1234
1466
|
if (notify) {
|
|
1235
|
-
(
|
|
1467
|
+
(_d = options.onGesture) === null || _d === void 0 ? void 0 : _d.call(options, { type: 'cancel', kind: g.kind, nodeId: g.id, changed: false });
|
|
1236
1468
|
}
|
|
1237
1469
|
};
|
|
1238
1470
|
/** Centre a w×h-span tile's top-left under the cursor, in world px. */
|
|
@@ -1468,6 +1700,10 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1468
1700
|
touched = true;
|
|
1469
1701
|
}
|
|
1470
1702
|
}
|
|
1703
|
+
else if (!E.s) {
|
|
1704
|
+
// A top-edge pull needs the row ABOVE: the anchored resize path
|
|
1705
|
+
// decides it (refused when that row is taken) — never the section.
|
|
1706
|
+
}
|
|
1471
1707
|
else if (!engine.resizeCheck(g.id, pulled.w, pulled.h + 1).changed) {
|
|
1472
1708
|
const res = parent.resizeMemberBy(group.id, +1);
|
|
1473
1709
|
if (res.changed) {
|
|
@@ -1496,15 +1732,22 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1496
1732
|
touched = true;
|
|
1497
1733
|
}
|
|
1498
1734
|
}
|
|
1499
|
-
else if (pulled.h > 1) {
|
|
1500
|
-
|
|
1735
|
+
else if (pulled.h > 1 && E.s) {
|
|
1736
|
+
// Shrink to the row the pointer asks for (one row per move lagged
|
|
1737
|
+
// a fast pull and overshot a slow one), then hand every row the
|
|
1738
|
+
// layout no longer needs back to the board.
|
|
1739
|
+
engine.resizeCheck(g.id, pulled.w, Math.max(1, wantRows));
|
|
1501
1740
|
const floor = Math.max(designRows, extentOf(engine.getItems()));
|
|
1502
|
-
|
|
1741
|
+
let slab = slabRows;
|
|
1742
|
+
let bound = inner;
|
|
1743
|
+
while (slab > floor) {
|
|
1503
1744
|
const res = parent.resizeMemberBy(group.id, -1);
|
|
1504
|
-
if (res.changed)
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1745
|
+
if (!res.changed)
|
|
1746
|
+
break;
|
|
1747
|
+
record(res, -1);
|
|
1748
|
+
slab -= 1;
|
|
1749
|
+
bound -= 1;
|
|
1750
|
+
setInnerRows(bound);
|
|
1508
1751
|
}
|
|
1509
1752
|
touched = true;
|
|
1510
1753
|
}
|
|
@@ -1573,9 +1816,23 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1573
1816
|
const moves = tx !== itemNow.x || ty !== itemNow.y;
|
|
1574
1817
|
const growing = span.w > itemNow.w || span.h > itemNow.h;
|
|
1575
1818
|
let changed = false;
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1819
|
+
// A north or west pull grows by MOVING the anchor first; when that move
|
|
1820
|
+
// is refused (the row above is taken) the growth is refused with it —
|
|
1821
|
+
// the first version fell through to a plain resize and grew the tile at
|
|
1822
|
+
// the OPPOSITE edge, so pulling a control's top edge up made it taller
|
|
1823
|
+
// at the bottom and pushed its section a row (kit lab L37).
|
|
1824
|
+
let anchored = true;
|
|
1825
|
+
if (moves && growing) {
|
|
1826
|
+
// The anchor may move only into FREE cells: a move that pushes would
|
|
1827
|
+
// relocate the tile (a top-edge pull sent a control to row 0 and its
|
|
1828
|
+
// neighbours below it) instead of growing it.
|
|
1829
|
+
const probe = { x: tx, y: ty, w: itemNow.w, h: itemNow.h };
|
|
1830
|
+
const blocked = engine.getItems().some((o) => o.id !== g.id && o.x < probe.x + probe.w && probe.x < o.x + o.w && o.y < probe.y + probe.h && probe.y < o.y + o.h);
|
|
1831
|
+
anchored = !blocked && engine.moveCheck(g.id, tx, ty, { gate: false }).changed;
|
|
1832
|
+
changed = anchored || changed;
|
|
1833
|
+
}
|
|
1834
|
+
if (anchored)
|
|
1835
|
+
changed = engine.resizeCheck(g.id, span.w, span.h).changed || changed;
|
|
1579
1836
|
if (moves && !growing)
|
|
1580
1837
|
changed = engine.moveCheck(g.id, tx, ty, { gate: false }).changed || changed;
|
|
1581
1838
|
if (changed)
|
|
@@ -1802,11 +2059,31 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1802
2059
|
const selfPeer = {
|
|
1803
2060
|
group,
|
|
1804
2061
|
clearSelection: () => {
|
|
2062
|
+
var _a;
|
|
1805
2063
|
if (selectedId === undefined)
|
|
1806
2064
|
return;
|
|
1807
2065
|
selectedId = undefined;
|
|
1808
2066
|
syncA11y();
|
|
2067
|
+
syncSlabs();
|
|
2068
|
+
(_a = options.onSelect) === null || _a === void 0 ? void 0 : _a.call(options, undefined);
|
|
2069
|
+
},
|
|
2070
|
+
innerRows: () => maxRows !== null && maxRows !== void 0 ? maxRows : rows(),
|
|
2071
|
+
selectMember: (id) => {
|
|
2072
|
+
var _a;
|
|
2073
|
+
if (((_a = group.members) !== null && _a !== void 0 ? _a : new Set()).has(id)) {
|
|
2074
|
+
selectWidget(id);
|
|
2075
|
+
api.render();
|
|
2076
|
+
}
|
|
1809
2077
|
},
|
|
2078
|
+
beginSlabResize: (id, edges, ev) => {
|
|
2079
|
+
if (isStatic)
|
|
2080
|
+
return false;
|
|
2081
|
+
beginSlabResize(id, edges, ev);
|
|
2082
|
+
return (slabGesture === null || slabGesture === void 0 ? void 0 : slabGesture.id) === id;
|
|
2083
|
+
},
|
|
2084
|
+
slabMove: (ev) => slabMove(ev),
|
|
2085
|
+
slabUp: () => slabUp(),
|
|
2086
|
+
slabCancel: () => slabCancel(),
|
|
1810
2087
|
hasItem: (id) => !!engine.getItem(id),
|
|
1811
2088
|
memberCell: (id) => {
|
|
1812
2089
|
const it = engine.getItem(id);
|
|
@@ -1853,7 +2130,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1853
2130
|
var _a, _b;
|
|
1854
2131
|
if (disposed)
|
|
1855
2132
|
return false;
|
|
1856
|
-
if (gesture)
|
|
2133
|
+
if (gesture || slabGesture || forwardSlab)
|
|
1857
2134
|
return true; // own the rest of an in-flight gesture
|
|
1858
2135
|
if (!ownsPress(api.container, diagram, ev, hit))
|
|
1859
2136
|
return false;
|
|
@@ -1882,7 +2159,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1882
2159
|
return insideMemberGroupFrame(ev.world.x, ev.world.y) || worldInsideBoard(ev.world.x, ev.world.y);
|
|
1883
2160
|
},
|
|
1884
2161
|
onPointerDown(ev, hit) {
|
|
1885
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
|
|
2162
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t;
|
|
1886
2163
|
if (gesture)
|
|
1887
2164
|
return; // mid-palette
|
|
1888
2165
|
const target = ((_b = (_a = ev.source) === null || _a === void 0 ? void 0 : _a.target) !== null && _b !== void 0 ? _b : null);
|
|
@@ -1892,10 +2169,47 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1892
2169
|
const gripHost = gripHostOf(target);
|
|
1893
2170
|
const gripId = (_c = gripHost === null || gripHost === void 0 ? void 0 : gripHost.getAttribute('data-node-id')) !== null && _c !== void 0 ? _c : null;
|
|
1894
2171
|
const onGrip = !!gripId && ((_d = group.members) !== null && _d !== void 0 ? _d : new Set()).has(gripId);
|
|
1895
|
-
|
|
2172
|
+
// A SECTION's corner handle sits on top of whatever tile shares that
|
|
2173
|
+
// corner; the DOM target names the section, the hit test the tile.
|
|
2174
|
+
const sectionHandle = (_e = target === null || target === void 0 ? void 0 : target.closest) === null || _e === void 0 ? void 0 : _e.call(target, '.axdb-slab > .axdb-rs');
|
|
2175
|
+
if ((!hit.node && !onGrip) || sectionHandle) {
|
|
2176
|
+
// A press on a SECTION — its empty band, its corner handle or its
|
|
2177
|
+
// frame edge — selects the section; the handle or an edge resizes it.
|
|
2178
|
+
const slabHandle = (_f = target === null || target === void 0 ? void 0 : target.closest) === null || _f === void 0 ? void 0 : _f.call(target, '.axdb-slab > .axdb-rs');
|
|
2179
|
+
const slabId = (_h = (_g = slabHandle === null || slabHandle === void 0 ? void 0 : slabHandle.parentElement) === null || _g === void 0 ? void 0 : _g.getAttribute('data-slab-id')) !== null && _h !== void 0 ? _h : memberGroupAt(ev.world.x, ev.world.y);
|
|
2180
|
+
const grp = slabId && ((_j = group.members) !== null && _j !== void 0 ? _j : new Set()).has(slabId) ? diagram.getGroup(slabId) : undefined;
|
|
2181
|
+
if (slabId && grp) {
|
|
2182
|
+
selectWidget(slabId);
|
|
2183
|
+
api.render();
|
|
2184
|
+
if (isStatic)
|
|
2185
|
+
return;
|
|
2186
|
+
const edges = slabHandle
|
|
2187
|
+
? rtl ? { n: false, e: false, s: true, w: true } : { n: false, e: true, s: true, w: false }
|
|
2188
|
+
: slabEdgesNear(grp, ev.world.x, ev.world.y);
|
|
2189
|
+
if (anyEdge(edges))
|
|
2190
|
+
beginSlabResize(slabId, edges, ev);
|
|
2191
|
+
return;
|
|
2192
|
+
}
|
|
2193
|
+
// OUR OWN empty band, and we are a section of a parent board: the
|
|
2194
|
+
// press selects the section there, and its edge or corner handle
|
|
2195
|
+
// starts the section resize, which the parent runs while this tool
|
|
2196
|
+
// forwards the pointer sequence.
|
|
2197
|
+
const parent = parentPeer();
|
|
2198
|
+
if ((parent === null || parent === void 0 ? void 0 : parent.selectMember) && worldInsideBoard(ev.world.x, ev.world.y)) {
|
|
2199
|
+
const ownHandle = ((_k = slabHandle === null || slabHandle === void 0 ? void 0 : slabHandle.parentElement) === null || _k === void 0 ? void 0 : _k.getAttribute('data-slab-id')) === group.id;
|
|
2200
|
+
parent.selectMember(group.id);
|
|
2201
|
+
if (!isStatic && parent.beginSlabResize) {
|
|
2202
|
+
const edges = ownHandle
|
|
2203
|
+
? rtl ? { n: false, e: false, s: true, w: true } : { n: false, e: true, s: true, w: false }
|
|
2204
|
+
: slabEdgesNear(group, ev.world.x, ev.world.y);
|
|
2205
|
+
if (anyEdge(edges) && parent.beginSlabResize(group.id, edges, ev))
|
|
2206
|
+
forwardSlab = parent;
|
|
2207
|
+
}
|
|
2208
|
+
return;
|
|
2209
|
+
}
|
|
1896
2210
|
// The board's own empty area: a void click. Nothing to drag, and the
|
|
1897
2211
|
// selection clears exactly as a click outside any board would.
|
|
1898
|
-
(
|
|
2212
|
+
(_m = (_l = diagram).clearSelection) === null || _m === void 0 ? void 0 : _m.call(_l);
|
|
1899
2213
|
selectWidget(undefined);
|
|
1900
2214
|
api.render();
|
|
1901
2215
|
return;
|
|
@@ -1904,17 +2218,17 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1904
2218
|
if (!node)
|
|
1905
2219
|
return;
|
|
1906
2220
|
selectWidget(node.id); // a press selects, whether or not it starts a gesture
|
|
1907
|
-
if (((
|
|
2221
|
+
if (((_o = node.state) === null || _o === void 0 ? void 0 : _o.locked) === true)
|
|
1908
2222
|
return; // pinned: refuse; click still focuses
|
|
1909
2223
|
if (isStatic)
|
|
1910
2224
|
return; // a static board: claimed and deadened, click still focuses
|
|
1911
2225
|
// Which edges did the press take? The corner handle names its own (s+e,
|
|
1912
2226
|
// or s+w on RTL); a bare press within EDGE_GRIP of the tile's border
|
|
1913
2227
|
// takes that border; anywhere else is a move. A grip press is a move.
|
|
1914
|
-
const onHandle = !!((
|
|
2228
|
+
const onHandle = !!((_p = target === null || target === void 0 ? void 0 : target.closest) === null || _p === void 0 ? void 0 : _p.call(target, '.axdb-rs'));
|
|
1915
2229
|
const hostEl = hostOf(node.id);
|
|
1916
|
-
const resizable = ((
|
|
1917
|
-
const movable = ((
|
|
2230
|
+
const resizable = ((_q = node.getMetadata) === null || _q === void 0 ? void 0 : _q.call(node, 'widgetResizable')) !== false;
|
|
2231
|
+
const movable = ((_r = node.getMetadata) === null || _r === void 0 ? void 0 : _r.call(node, 'widgetMovable')) !== false;
|
|
1918
2232
|
// `ev.screen` is ELEMENT-LOCAL px; host rects are in client px. Compare
|
|
1919
2233
|
// like with like — the source event's clientX/Y when there is one, else
|
|
1920
2234
|
// the container's origin plus the local offset.
|
|
@@ -1962,7 +2276,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1962
2276
|
startSize: { width: node.size.width, height: node.size.height },
|
|
1963
2277
|
startPos: { x: node.position.x, y: node.position.y },
|
|
1964
2278
|
edges,
|
|
1965
|
-
spans: { w: (
|
|
2279
|
+
spans: { w: (_s = it === null || it === void 0 ? void 0 : it.w) !== null && _s !== void 0 ? _s : 1, h: (_t = it === null || it === void 0 ? void 0 : it.h) !== null && _t !== void 0 ? _t : 1 },
|
|
1966
2280
|
removedFromBoard: false,
|
|
1967
2281
|
leg: null,
|
|
1968
2282
|
lastWorld: null,
|
|
@@ -1973,10 +2287,24 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1973
2287
|
};
|
|
1974
2288
|
},
|
|
1975
2289
|
onPointerMove(ev) {
|
|
1976
|
-
|
|
2290
|
+
var _a;
|
|
2291
|
+
if (forwardSlab)
|
|
2292
|
+
(_a = forwardSlab.slabMove) === null || _a === void 0 ? void 0 : _a.call(forwardSlab, ev);
|
|
2293
|
+
else if (slabGesture)
|
|
2294
|
+
slabMove(ev);
|
|
2295
|
+
else
|
|
2296
|
+
onToolMove(ev);
|
|
1977
2297
|
},
|
|
1978
2298
|
onPointerUp() {
|
|
1979
|
-
|
|
2299
|
+
var _a;
|
|
2300
|
+
if (forwardSlab) {
|
|
2301
|
+
(_a = forwardSlab.slabUp) === null || _a === void 0 ? void 0 : _a.call(forwardSlab);
|
|
2302
|
+
forwardSlab = null;
|
|
2303
|
+
}
|
|
2304
|
+
else if (slabGesture)
|
|
2305
|
+
slabUp();
|
|
2306
|
+
else
|
|
2307
|
+
onToolUp();
|
|
1980
2308
|
},
|
|
1981
2309
|
onCancel() {
|
|
1982
2310
|
cancelActiveGesture();
|
|
@@ -1990,7 +2318,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1990
2318
|
*/
|
|
1991
2319
|
let hoverHost = null;
|
|
1992
2320
|
const onHover = (e) => {
|
|
1993
|
-
var _a, _b, _c, _d, _e, _f, _g;
|
|
2321
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
1994
2322
|
if (disposed || gesture)
|
|
1995
2323
|
return;
|
|
1996
2324
|
// The event may target the host, its content, or (when a host's content
|
|
@@ -2014,13 +2342,23 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2014
2342
|
hoverHost.removeAttribute('data-axdb-edge'); // the affordance follows the pointer off a tile
|
|
2015
2343
|
}
|
|
2016
2344
|
hoverHost = host;
|
|
2017
|
-
if (!host)
|
|
2345
|
+
if (!host) {
|
|
2346
|
+
// No tile under the pointer: a SECTION's frame edge still says resize.
|
|
2347
|
+
const wpt = ((_d = api.viewport) === null || _d === void 0 ? void 0 : _d.clientToWorld) ? api.viewport.clientToWorld(e.clientX, e.clientY, api.container.getBoundingClientRect()) : null;
|
|
2348
|
+
const sid = wpt ? memberGroupAt(wpt.x, wpt.y) : null;
|
|
2349
|
+
const grp = sid ? diagram.getGroup(sid) : undefined;
|
|
2350
|
+
const c = grp && wpt && !isStatic ? cursorFor(slabEdgesNear(grp, wpt.x, wpt.y)) : '';
|
|
2351
|
+
if (!slabGesture)
|
|
2352
|
+
api.container.style.cursor = c;
|
|
2018
2353
|
return;
|
|
2019
|
-
|
|
2020
|
-
if (!
|
|
2354
|
+
}
|
|
2355
|
+
if (!slabGesture && api.container.style.cursor)
|
|
2356
|
+
api.container.style.cursor = '';
|
|
2357
|
+
const id = (_e = host.getAttribute('data-node-id')) !== null && _e !== void 0 ? _e : '';
|
|
2358
|
+
if (!((_f = group.members) !== null && _f !== void 0 ? _f : new Set()).has(id))
|
|
2021
2359
|
return;
|
|
2022
2360
|
const node = diagram.getNode(id);
|
|
2023
|
-
const resizable = !!node && !isStatic && ((
|
|
2361
|
+
const resizable = !!node && !isStatic && ((_g = node.state) === null || _g === void 0 ? void 0 : _g.locked) !== true && ((_h = node.getMetadata) === null || _h === void 0 ? void 0 : _h.call(node, 'widgetResizable')) !== false;
|
|
2024
2362
|
const cursor = resizable ? cursorFor(edgesNear(host, e.clientX, e.clientY)) : '';
|
|
2025
2363
|
// The affordance rides on an ATTRIBUTE, not the inline style: a repaint
|
|
2026
2364
|
// rewrites the host's style and used to clear the cursor mid-hover, and
|
|
@@ -2354,6 +2692,17 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2354
2692
|
writing = false;
|
|
2355
2693
|
}
|
|
2356
2694
|
const commands = buildCommitCommands(deltasSince(snap.cells, snap.geoms));
|
|
2695
|
+
if (isGroupMember(id)) {
|
|
2696
|
+
// A SECTION: its cell lives in group metadata and its frame is written
|
|
2697
|
+
// by project(); the node commands above skip groups (D5 of the review).
|
|
2698
|
+
const it = engine.getItem(id);
|
|
2699
|
+
const grp = diagram.getGroup(id);
|
|
2700
|
+
const cb = snap.cells.get(id);
|
|
2701
|
+
const gb = snap.geoms.get(id);
|
|
2702
|
+
if (it && grp && cb && gb && (cb.x !== it.x || cb.y !== it.y || cb.w !== it.w || cb.h !== it.h)) {
|
|
2703
|
+
commands.push(new SetGroupCellCommand(id, cb, { x: it.x, y: it.y, w: it.w, h: it.h }, { x: gb.pos.x, y: gb.pos.y, width: gb.size.width, height: gb.size.height }, frameOfGroup(grp)));
|
|
2704
|
+
}
|
|
2705
|
+
}
|
|
2357
2706
|
engine.endGesture();
|
|
2358
2707
|
disarmGlideSoon();
|
|
2359
2708
|
enforceBoardHeight();
|
|
@@ -2452,7 +2801,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2452
2801
|
var _a;
|
|
2453
2802
|
if (disposed)
|
|
2454
2803
|
return false;
|
|
2455
|
-
if (id !== undefined && (!((_a = group.members) !== null && _a !== void 0 ? _a : new Set()).has(id) || !
|
|
2804
|
+
if (id !== undefined && (!((_a = group.members) !== null && _a !== void 0 ? _a : new Set()).has(id) || !memberEntity(id)))
|
|
2456
2805
|
return false;
|
|
2457
2806
|
selectWidget(id);
|
|
2458
2807
|
return true;
|
|
@@ -2581,7 +2930,8 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2581
2930
|
return programmatic('Move widget', id, () => engine.moveCheck(id, x, y).changed);
|
|
2582
2931
|
},
|
|
2583
2932
|
resizeTo(id, w, h) {
|
|
2584
|
-
|
|
2933
|
+
const hh = isGroupMember(id) ? Math.max(h, innerRowsOf(id)) : h; // a section: never below its children
|
|
2934
|
+
return programmatic('Resize widget', id, () => engine.resizeCheck(id, w, hh).changed);
|
|
2585
2935
|
},
|
|
2586
2936
|
beginPaletteDrag,
|
|
2587
2937
|
dispose() {
|
|
@@ -2608,6 +2958,9 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2608
2958
|
placeholder = null;
|
|
2609
2959
|
if (glideTimer)
|
|
2610
2960
|
clearTimeout(glideTimer);
|
|
2961
|
+
for (const el of slabEls.values())
|
|
2962
|
+
el.remove();
|
|
2963
|
+
slabEls.clear();
|
|
2611
2964
|
// A rebind inside the 60 ms window (a layout switch right after an undo)
|
|
2612
2965
|
// disposed this binder with the timer pending — the host kept its
|
|
2613
2966
|
// lifted ghost for good (visual gate, nested-containers ⑤).
|
|
@@ -27,7 +27,7 @@ import type { DashboardGridApi, DashboardGridHandle, DashboardGridOptions } from
|
|
|
27
27
|
import { type SplitNode } from './split-layout.js';
|
|
28
28
|
/** Group metadata key the tree persists under. */
|
|
29
29
|
export declare const SPLIT_TREE_KEY = "dashboardTree";
|
|
30
|
-
export interface DashboardSplitOptions extends Pick<DashboardGridOptions, 'columns' | 'gap' | 'padding' | 'rtl' | 'fluid' | 'static' | 'dragHandle' | 'squeeze' | 'designHeight' | 'baseRowHeight' | 'dragOut' | 'removeZone' | 'onRemoveRequest' | 'onDropIn' | 'onGesture'> {
|
|
30
|
+
export interface DashboardSplitOptions extends Pick<DashboardGridOptions, 'columns' | 'gap' | 'padding' | 'rtl' | 'fluid' | 'static' | 'dragHandle' | 'squeeze' | 'designHeight' | 'baseRowHeight' | 'dragOut' | 'removeZone' | 'onRemoveRequest' | 'onDropIn' | 'onGesture' | 'onSelect'> {
|
|
31
31
|
/** An authored tree. Default: the persisted one, else derived from the members' cells. */
|
|
32
32
|
tree?: SplitNode | null;
|
|
33
33
|
}
|
|
@@ -372,12 +372,14 @@ export function bindDashboardSplit(api, group, options = {}) {
|
|
|
372
372
|
/** The selected widget (see grid-binder): stamped `axdb-selected`, shows the grip. */
|
|
373
373
|
let selectedId;
|
|
374
374
|
const selectWidget = (id) => {
|
|
375
|
+
var _a;
|
|
375
376
|
if (id !== undefined)
|
|
376
377
|
clearOtherSelections(api.container, selfPeerRef);
|
|
377
378
|
if (id === selectedId)
|
|
378
379
|
return;
|
|
379
380
|
selectedId = id;
|
|
380
381
|
syncA11y();
|
|
382
|
+
(_a = options.onSelect) === null || _a === void 0 ? void 0 : _a.call(options, id);
|
|
381
383
|
};
|
|
382
384
|
let selfPeerRef = null;
|
|
383
385
|
// Static boards let content be clicked — see grid-binder's staticGuard.
|
|
@@ -1193,10 +1195,12 @@ export function bindDashboardSplit(api, group, options = {}) {
|
|
|
1193
1195
|
const selfPeer = {
|
|
1194
1196
|
group,
|
|
1195
1197
|
clearSelection: () => {
|
|
1198
|
+
var _a;
|
|
1196
1199
|
if (selectedId === undefined)
|
|
1197
1200
|
return;
|
|
1198
1201
|
selectedId = undefined;
|
|
1199
1202
|
syncA11y();
|
|
1203
|
+
(_a = options.onSelect) === null || _a === void 0 ? void 0 : _a.call(options, undefined);
|
|
1200
1204
|
},
|
|
1201
1205
|
hasItem: (id) => { var _a; return ((_a = group.members) !== null && _a !== void 0 ? _a : new Set()).has(id); },
|
|
1202
1206
|
memberCell: (id) => handle.cellOf(id),
|
|
@@ -68,7 +68,7 @@ const CSS = `
|
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
/* ===== corner resize handle (hover-revealed, radius-matched) ===== */
|
|
71
|
-
.grafloria-node-host > .axdb-rs {
|
|
71
|
+
:is(.grafloria-node-host, .axdb-slab) > .axdb-rs {
|
|
72
72
|
position: absolute;
|
|
73
73
|
right: 0;
|
|
74
74
|
bottom: 0;
|
|
@@ -88,7 +88,7 @@ const CSS = `
|
|
|
88
88
|
@media (hover: none) { .grafloria-node-host > .axdb-rs { opacity: .8; } }
|
|
89
89
|
.grafloria-node-host > .axdb-rs:hover { border-color: #3b52d9; }
|
|
90
90
|
/* RTL boards grow leftwards, so the grab corner mirrors with them. */
|
|
91
|
-
.grafloria-node-host > .axdb-rs.axdb-rs--rtl {
|
|
91
|
+
:is(.grafloria-node-host, .axdb-slab) > .axdb-rs.axdb-rs--rtl {
|
|
92
92
|
right: auto;
|
|
93
93
|
left: 0;
|
|
94
94
|
cursor: nesw-resize;
|
|
@@ -355,6 +355,16 @@ const CSS = `
|
|
|
355
355
|
.axdb-tier-2 .axdb-xt, .axdb-tier-2 .axdb-vt,
|
|
356
356
|
.axdb-lg--off { display: none; }
|
|
357
357
|
|
|
358
|
+
/* SECTION CHROME: a pointer-transparent overlay on every member group. It
|
|
359
|
+
wears the selection ring and, while selected, the corner handle. */
|
|
360
|
+
.grafloria-html-layer > .axdb-slab { position: absolute; pointer-events: none; border-radius: var(--axdb-rs-radius, 3px); z-index: 4; }
|
|
361
|
+
/* Selected, the overlay rises above the tiles so ITS corner handle wins a
|
|
362
|
+
corner it shares with a child's; unselected, its handle takes no presses. */
|
|
363
|
+
.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)); }
|
|
364
|
+
.grafloria-html-layer > .axdb-slab > .axdb-rs { pointer-events: none; opacity: 0; }
|
|
365
|
+
.grafloria-html-layer > .axdb-slab.axdb-slab--selected > .axdb-rs { pointer-events: auto; opacity: 1; }
|
|
366
|
+
.grafloria-html-layer > .axdb-slab.axdb-slab--static > .axdb-rs { display: none; }
|
|
367
|
+
|
|
358
368
|
/* legend chips, shared by line and donut */
|
|
359
369
|
.axdb-lg { display: flex; flex-wrap: wrap; gap: 4px 12px; margin-top: 9px; }
|
|
360
370
|
.axdb-lg--col { flex-direction: column; flex-wrap: nowrap; gap: 6px; margin-top: 0; }
|