@grafloria/element 0.4.17 → 0.4.19
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 +31 -0
- package/src/lib/dashboard-kit/grid-binder.js +362 -25
- package/src/lib/dashboard-kit/split-binder.d.ts +1 -1
- package/src/lib/dashboard-kit/split-binder.js +41 -6
- package/src/lib/dashboard-kit/styles.js +14 -3
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
|
|
@@ -429,6 +431,27 @@ interface BinderPeer {
|
|
|
429
431
|
group: GroupModel;
|
|
430
432
|
/** True when this board's engine holds `id` as an item (member lookup). */
|
|
431
433
|
hasItem(id: string): boolean;
|
|
434
|
+
/**
|
|
435
|
+
* Drop this board's selection: a press that selects a tile on ONE board of
|
|
436
|
+
* a canvas clears the others, so a section and its parent never show two
|
|
437
|
+
* rings at once (Quantia Groups page: three tiles selected across three
|
|
438
|
+
* boards after three clicks).
|
|
439
|
+
*/
|
|
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;
|
|
432
455
|
/** The member's current cell in this board (undefined when absent). */
|
|
433
456
|
memberCell(id: string): CellRect | undefined;
|
|
434
457
|
/**
|
|
@@ -490,6 +513,14 @@ interface AdoptedLeg {
|
|
|
490
513
|
* unregister. A split board adopts nothing and grows no slab: its `adopt`
|
|
491
514
|
* answers null and `resizeMemberBy` answers unchanged.
|
|
492
515
|
*/
|
|
516
|
+
/** Clear the selection on every OTHER board of the canvas — one selection per canvas. */
|
|
517
|
+
export declare function clearOtherSelections(container: HTMLElement, self: BinderPeer | null): void;
|
|
493
518
|
export declare function registerBoardPeer(container: HTMLElement, peer: BinderPeer): () => void;
|
|
494
519
|
export type { BinderPeer };
|
|
520
|
+
interface ResizeEdges {
|
|
521
|
+
n: boolean;
|
|
522
|
+
e: boolean;
|
|
523
|
+
s: boolean;
|
|
524
|
+
w: boolean;
|
|
525
|
+
}
|
|
495
526
|
export declare function bindDashboardGrid(api: DashboardGridApi, group: GroupModel, options?: DashboardGridOptions): DashboardGridHandle;
|
|
@@ -153,6 +153,13 @@ const BOARD_REGISTRY = new WeakMap();
|
|
|
153
153
|
* unregister. A split board adopts nothing and grows no slab: its `adopt`
|
|
154
154
|
* answers null and `resizeMemberBy` answers unchanged.
|
|
155
155
|
*/
|
|
156
|
+
/** Clear the selection on every OTHER board of the canvas — one selection per canvas. */
|
|
157
|
+
export function clearOtherSelections(container, self) {
|
|
158
|
+
var _a, _b;
|
|
159
|
+
for (const p of (_a = BOARD_REGISTRY.get(container)) !== null && _a !== void 0 ? _a : [])
|
|
160
|
+
if (p !== self)
|
|
161
|
+
(_b = p.clearSelection) === null || _b === void 0 ? void 0 : _b.call(p);
|
|
162
|
+
}
|
|
156
163
|
export function registerBoardPeer(container, peer) {
|
|
157
164
|
let set = BOARD_REGISTRY.get(container);
|
|
158
165
|
if (!set) {
|
|
@@ -458,6 +465,19 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
458
465
|
/** The engine item a member should enter as (cells from GridItemConfig /
|
|
459
466
|
* group metadata; spans falling back to metadata columnSpan/rowSpan; last
|
|
460
467
|
* resort: adopt from the current pixel geometry). */
|
|
468
|
+
/**
|
|
469
|
+
* A SECTION's floor: its nested board's live rows (the peer), else the rows
|
|
470
|
+
* it was mounted with — a section resized by hand never squeezes its
|
|
471
|
+
* children below their cells.
|
|
472
|
+
*/
|
|
473
|
+
const innerRowsOf = (id) => {
|
|
474
|
+
var _a, _b, _c, _d;
|
|
475
|
+
for (const p of (_a = BOARD_REGISTRY.get(api.container)) !== null && _a !== void 0 ? _a : [])
|
|
476
|
+
if (p.group.id === id && p.innerRows)
|
|
477
|
+
return Math.max(1, p.innerRows());
|
|
478
|
+
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');
|
|
479
|
+
return Math.max(1, (_d = meta === null || meta === void 0 ? void 0 : meta.maxRows) !== null && _d !== void 0 ? _d : 1);
|
|
480
|
+
};
|
|
461
481
|
const itemFor = (id) => {
|
|
462
482
|
var _a, _b, _c, _d, _e, _f, _g;
|
|
463
483
|
const node = diagram.getNode(id);
|
|
@@ -701,19 +721,27 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
701
721
|
*/
|
|
702
722
|
let selectedId;
|
|
703
723
|
const selectWidget = (id) => {
|
|
724
|
+
var _a;
|
|
725
|
+
if (id !== undefined)
|
|
726
|
+
clearOtherSelections(api.container, selfPeerRef);
|
|
704
727
|
if (id === selectedId)
|
|
705
728
|
return;
|
|
706
729
|
selectedId = id;
|
|
707
730
|
syncA11y();
|
|
731
|
+
syncSlabs();
|
|
732
|
+
(_a = options.onSelect) === null || _a === void 0 ? void 0 : _a.call(options, id);
|
|
708
733
|
};
|
|
734
|
+
/** Set once the peer object exists (below); `selectWidget` runs before that only on boot. */
|
|
735
|
+
let selfPeerRef = null;
|
|
709
736
|
const syncA11y = (only) => {
|
|
710
|
-
var _a, _b;
|
|
737
|
+
var _a, _b, _c;
|
|
711
738
|
if (disposed)
|
|
712
739
|
return;
|
|
713
740
|
const members = [...((_a = group.members) !== null && _a !== void 0 ? _a : [])].filter((id) => !!diagram.getNode(id));
|
|
714
741
|
if (focusedId && !members.includes(focusedId))
|
|
715
742
|
focusedId = undefined;
|
|
716
|
-
|
|
743
|
+
// A selected SECTION is a group member, not a node: keep it as long as it is a member.
|
|
744
|
+
if (selectedId && !members.includes(selectedId) && !(((_b = group.members) === null || _b === void 0 ? void 0 : _b.has(selectedId)) && memberEntity(selectedId)))
|
|
717
745
|
selectedId = undefined;
|
|
718
746
|
const stop = focusedId !== null && focusedId !== void 0 ? focusedId : members[0];
|
|
719
747
|
for (const id of members) {
|
|
@@ -727,7 +755,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
727
755
|
const bits = [nameOf(node)];
|
|
728
756
|
if (cell)
|
|
729
757
|
bits.push(describeCell(cell));
|
|
730
|
-
if (((
|
|
758
|
+
if (((_c = node.state) === null || _c === void 0 ? void 0 : _c.locked) === true)
|
|
731
759
|
bits.push('pinned');
|
|
732
760
|
host.setAttribute('role', 'group');
|
|
733
761
|
host.setAttribute('aria-roledescription', 'dashboard widget');
|
|
@@ -748,6 +776,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
748
776
|
if (disposed)
|
|
749
777
|
return;
|
|
750
778
|
ensureStaticGuard();
|
|
779
|
+
syncSlabs();
|
|
751
780
|
const grip = gripOf(dragHandle);
|
|
752
781
|
for (const id of (_a = group.members) !== null && _a !== void 0 ? _a : []) {
|
|
753
782
|
if (only && !only.has(id))
|
|
@@ -964,6 +993,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
964
993
|
finally {
|
|
965
994
|
writing = false;
|
|
966
995
|
}
|
|
996
|
+
syncSlabs();
|
|
967
997
|
};
|
|
968
998
|
const applyColumns = (n, layout) => {
|
|
969
999
|
var _a;
|
|
@@ -1033,6 +1063,80 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1033
1063
|
const band = rowHeightFor(geom(), rows()) + gap;
|
|
1034
1064
|
return x >= f.x && x <= f.x + f.width && y >= f.y && y <= f.y + boardVisualHeight() + band;
|
|
1035
1065
|
};
|
|
1066
|
+
/** The member SECTION whose frame holds the world point, if any. */
|
|
1067
|
+
const memberGroupAt = (x, y) => {
|
|
1068
|
+
var _a;
|
|
1069
|
+
for (const id of (_a = group.members) !== null && _a !== void 0 ? _a : []) {
|
|
1070
|
+
const grp = diagram.getGroup(id);
|
|
1071
|
+
if (!grp)
|
|
1072
|
+
continue;
|
|
1073
|
+
const p = grp.position;
|
|
1074
|
+
const s = sizeOf(grp);
|
|
1075
|
+
if (x >= p.x && x <= p.x + s.width && y >= p.y && y <= p.y + s.height)
|
|
1076
|
+
return id;
|
|
1077
|
+
}
|
|
1078
|
+
return null;
|
|
1079
|
+
};
|
|
1080
|
+
/** Which of a section frame's edges a world point is within EDGE_GRIP of. */
|
|
1081
|
+
const slabEdgesNear = (grp, x, y) => {
|
|
1082
|
+
const p = grp.position;
|
|
1083
|
+
const s = sizeOf(grp);
|
|
1084
|
+
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 };
|
|
1085
|
+
};
|
|
1086
|
+
/**
|
|
1087
|
+
* SECTION CHROME. A section (member group) paints no card of its own, so it
|
|
1088
|
+
* had nothing to press: a click on its empty band cleared the selection and
|
|
1089
|
+
* its frame had no handle and no edge — a section with many children could
|
|
1090
|
+
* not be selected at all, and could only be resized by pulling a child
|
|
1091
|
+
* (Quantia, Groups page). Every section gets a pointer-transparent overlay
|
|
1092
|
+
* in the HTML layer that wears the selection ring and, while selected, the
|
|
1093
|
+
* corner handle; its frame edges answer the resize cursor.
|
|
1094
|
+
*/
|
|
1095
|
+
const slabEls = new Map();
|
|
1096
|
+
let slabLayer = null;
|
|
1097
|
+
const syncSlabs = () => {
|
|
1098
|
+
var _a, _b;
|
|
1099
|
+
if (disposed)
|
|
1100
|
+
return;
|
|
1101
|
+
const layer = (slabLayer === null || slabLayer === void 0 ? void 0 : slabLayer.isConnected) ? slabLayer : (slabLayer = htmlLayer());
|
|
1102
|
+
if (!layer)
|
|
1103
|
+
return;
|
|
1104
|
+
const seen = new Set();
|
|
1105
|
+
for (const id of (_a = group.members) !== null && _a !== void 0 ? _a : []) {
|
|
1106
|
+
const grp = diagram.getGroup(id);
|
|
1107
|
+
if (!grp || diagram.getNode(id))
|
|
1108
|
+
continue;
|
|
1109
|
+
seen.add(id);
|
|
1110
|
+
let el = slabEls.get(id);
|
|
1111
|
+
if (!el || el.parentElement !== layer) {
|
|
1112
|
+
el === null || el === void 0 ? void 0 : el.remove();
|
|
1113
|
+
el = document.createElement('div');
|
|
1114
|
+
el.className = 'axdb-slab';
|
|
1115
|
+
el.setAttribute('data-slab-id', id);
|
|
1116
|
+
const rs = document.createElement('div');
|
|
1117
|
+
rs.className = 'axdb-rs';
|
|
1118
|
+
rs.setAttribute('title', 'Resize section');
|
|
1119
|
+
el.appendChild(rs);
|
|
1120
|
+
layer.appendChild(el);
|
|
1121
|
+
slabEls.set(id, el);
|
|
1122
|
+
}
|
|
1123
|
+
const p = grp.position;
|
|
1124
|
+
const sz = sizeOf(grp);
|
|
1125
|
+
el.style.left = `${p.x}px`;
|
|
1126
|
+
el.style.top = `${p.y}px`;
|
|
1127
|
+
el.style.width = `${sz.width}px`;
|
|
1128
|
+
el.style.height = `${sz.height}px`;
|
|
1129
|
+
el.classList.toggle('axdb-slab--selected', selectedId === id);
|
|
1130
|
+
el.classList.toggle('axdb-slab--static', isStatic);
|
|
1131
|
+
(_b = el.querySelector(':scope > .axdb-rs')) === null || _b === void 0 ? void 0 : _b.classList.toggle('axdb-rs--rtl', rtl);
|
|
1132
|
+
}
|
|
1133
|
+
for (const [id, el] of slabEls) {
|
|
1134
|
+
if (!seen.has(id)) {
|
|
1135
|
+
el.remove();
|
|
1136
|
+
slabEls.delete(id);
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
};
|
|
1036
1140
|
const insideMemberGroupFrame = (x, y) => {
|
|
1037
1141
|
var _a;
|
|
1038
1142
|
for (const id of (_a = group.members) !== null && _a !== void 0 ? _a : []) {
|
|
@@ -1046,6 +1150,131 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1046
1150
|
}
|
|
1047
1151
|
return false;
|
|
1048
1152
|
};
|
|
1153
|
+
let slabGesture = null;
|
|
1154
|
+
/** The PARENT running a resize of OUR section from a press this tool claimed. */
|
|
1155
|
+
let forwardSlab = null;
|
|
1156
|
+
const frameOfGroup = (grp) => ({ x: grp.position.x, y: grp.position.y, width: sizeOf(grp).width, height: sizeOf(grp).height });
|
|
1157
|
+
const beginSlabResize = (id, edges, ev) => {
|
|
1158
|
+
const grp = diagram.getGroup(id);
|
|
1159
|
+
const it = engine.getItem(id);
|
|
1160
|
+
if (!grp || !it || gesture || slabGesture)
|
|
1161
|
+
return;
|
|
1162
|
+
engine.beginGesture();
|
|
1163
|
+
const snap = snapshotAll();
|
|
1164
|
+
slabGesture = {
|
|
1165
|
+
id,
|
|
1166
|
+
edges,
|
|
1167
|
+
pointerId: typeof PointerEvent !== 'undefined' && ev.source instanceof PointerEvent ? ev.source.pointerId : null,
|
|
1168
|
+
started: false,
|
|
1169
|
+
downScreen: { x: ev.screen.x, y: ev.screen.y },
|
|
1170
|
+
startCells: snap.cells,
|
|
1171
|
+
startGeom: snap.geoms,
|
|
1172
|
+
cellBefore: { x: it.x, y: it.y, w: it.w, h: it.h },
|
|
1173
|
+
frameBefore: frameOfGroup(grp),
|
|
1174
|
+
grab: {
|
|
1175
|
+
dx: edges.e ? grp.position.x + sizeOf(grp).width - ev.world.x : edges.w ? grp.position.x - ev.world.x : 0,
|
|
1176
|
+
dy: edges.s ? grp.position.y + sizeOf(grp).height - ev.world.y : edges.n ? grp.position.y - ev.world.y : 0,
|
|
1177
|
+
},
|
|
1178
|
+
};
|
|
1179
|
+
capturePointer(slabGesture.pointerId);
|
|
1180
|
+
api.container.style.cursor = cursorFor(edges);
|
|
1181
|
+
};
|
|
1182
|
+
const slabMove = (ev) => {
|
|
1183
|
+
const g = slabGesture;
|
|
1184
|
+
if (!g)
|
|
1185
|
+
return;
|
|
1186
|
+
if (!g.started) {
|
|
1187
|
+
if (Math.abs(ev.screen.x - g.downScreen.x) + Math.abs(ev.screen.y - g.downScreen.y) < DRAG_THRESHOLD)
|
|
1188
|
+
return;
|
|
1189
|
+
g.started = true;
|
|
1190
|
+
armGlide();
|
|
1191
|
+
}
|
|
1192
|
+
const it = engine.getItem(g.id);
|
|
1193
|
+
if (!it)
|
|
1194
|
+
return;
|
|
1195
|
+
const f = frame();
|
|
1196
|
+
const gg = geom();
|
|
1197
|
+
const cu = columnUnitFor(gg, f.width);
|
|
1198
|
+
const rh = rowHeightFor(gg, rows());
|
|
1199
|
+
// The grid line nearest the pointer, in cells (mirrored on RTL).
|
|
1200
|
+
const colAt = (wx) => Math.round((rtl ? f.x + f.width - padding - wx : wx - f.x - padding) / (cu + gap));
|
|
1201
|
+
const rowAt = (wy) => Math.round((wy - f.y - padding) / (rh + gap));
|
|
1202
|
+
let { x, y, w, h } = it;
|
|
1203
|
+
const px = ev.world.x + g.grab.dx;
|
|
1204
|
+
const py = ev.world.y + g.grab.dy;
|
|
1205
|
+
if (g.edges.e)
|
|
1206
|
+
w = Math.max(1, colAt(px) - x);
|
|
1207
|
+
if (g.edges.s)
|
|
1208
|
+
h = Math.max(1, rowAt(py) - y);
|
|
1209
|
+
if (g.edges.w) {
|
|
1210
|
+
const nx = Math.max(0, Math.min(x + w - 1, colAt(px)));
|
|
1211
|
+
w = x + w - nx;
|
|
1212
|
+
x = nx;
|
|
1213
|
+
}
|
|
1214
|
+
if (g.edges.n) {
|
|
1215
|
+
const ny = Math.max(0, Math.min(y + h - 1, rowAt(py)));
|
|
1216
|
+
h = y + h - ny;
|
|
1217
|
+
y = ny;
|
|
1218
|
+
}
|
|
1219
|
+
w = Math.max(1, Math.min(w, columns - x));
|
|
1220
|
+
// Never below the children's rows (the nested board's live bound).
|
|
1221
|
+
const floor = innerRowsOf(g.id);
|
|
1222
|
+
if (h < floor) {
|
|
1223
|
+
if (g.edges.n)
|
|
1224
|
+
y = y + h - floor;
|
|
1225
|
+
h = floor;
|
|
1226
|
+
}
|
|
1227
|
+
let changed = false;
|
|
1228
|
+
if (x !== it.x || y !== it.y)
|
|
1229
|
+
changed = engine.moveCheck(g.id, x, y, { gate: false }).changed || changed;
|
|
1230
|
+
if (w !== it.w || h !== it.h)
|
|
1231
|
+
changed = engine.resizeCheck(g.id, w, h).changed || changed;
|
|
1232
|
+
if (changed)
|
|
1233
|
+
project();
|
|
1234
|
+
};
|
|
1235
|
+
const slabUp = () => {
|
|
1236
|
+
var _a;
|
|
1237
|
+
const g = slabGesture;
|
|
1238
|
+
if (!g)
|
|
1239
|
+
return;
|
|
1240
|
+
slabGesture = null;
|
|
1241
|
+
releasePointer(g.pointerId);
|
|
1242
|
+
api.container.style.cursor = '';
|
|
1243
|
+
if (!g.started) {
|
|
1244
|
+
engine.endGesture();
|
|
1245
|
+
return;
|
|
1246
|
+
}
|
|
1247
|
+
engine.endGesture();
|
|
1248
|
+
project();
|
|
1249
|
+
const it = engine.getItem(g.id);
|
|
1250
|
+
const grp = diagram.getGroup(g.id);
|
|
1251
|
+
const commands = buildCommitCommands(deltasSince(g.startCells, g.startGeom, g.id));
|
|
1252
|
+
const b = g.cellBefore;
|
|
1253
|
+
if (it && grp && (b.x !== it.x || b.y !== it.y || b.w !== it.w || b.h !== it.h)) {
|
|
1254
|
+
commands.push(new SetGroupCellCommand(g.id, b, { x: it.x, y: it.y, w: it.w, h: it.h }, g.frameBefore, frameOfGroup(grp)));
|
|
1255
|
+
}
|
|
1256
|
+
const changed = execute('Resize section', commands);
|
|
1257
|
+
disarmGlideSoon();
|
|
1258
|
+
syncHandles();
|
|
1259
|
+
api.renderNow();
|
|
1260
|
+
(_a = options.onGesture) === null || _a === void 0 ? void 0 : _a.call(options, { type: 'commit', kind: 'resize', nodeId: g.id, changed });
|
|
1261
|
+
};
|
|
1262
|
+
const slabCancel = () => {
|
|
1263
|
+
var _a;
|
|
1264
|
+
const g = slabGesture;
|
|
1265
|
+
if (!g)
|
|
1266
|
+
return;
|
|
1267
|
+
slabGesture = null;
|
|
1268
|
+
releasePointer(g.pointerId);
|
|
1269
|
+
api.container.style.cursor = '';
|
|
1270
|
+
if (g.started)
|
|
1271
|
+
engine.cancelGesture();
|
|
1272
|
+
else
|
|
1273
|
+
engine.endGesture();
|
|
1274
|
+
project();
|
|
1275
|
+
disarmGlideSoon();
|
|
1276
|
+
(_a = options.onGesture) === null || _a === void 0 ? void 0 : _a.call(options, { type: 'cancel', kind: 'resize', nodeId: g.id, changed: false });
|
|
1277
|
+
};
|
|
1049
1278
|
const capturePointer = (pointerId) => {
|
|
1050
1279
|
var _a, _b;
|
|
1051
1280
|
if (pointerId === null)
|
|
@@ -1152,7 +1381,13 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1152
1381
|
(_a = options.onGesture) === null || _a === void 0 ? void 0 : _a.call(options, { type: 'commit', kind: g.kind, nodeId: g.id, changed });
|
|
1153
1382
|
};
|
|
1154
1383
|
const cancelActiveGesture = (notify = true) => {
|
|
1155
|
-
var _a, _b, _c;
|
|
1384
|
+
var _a, _b, _c, _d;
|
|
1385
|
+
if (forwardSlab) {
|
|
1386
|
+
(_a = forwardSlab.slabCancel) === null || _a === void 0 ? void 0 : _a.call(forwardSlab);
|
|
1387
|
+
forwardSlab = null;
|
|
1388
|
+
}
|
|
1389
|
+
if (slabGesture)
|
|
1390
|
+
slabCancel();
|
|
1156
1391
|
const g = gesture;
|
|
1157
1392
|
if (!g)
|
|
1158
1393
|
return;
|
|
@@ -1173,7 +1408,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1173
1408
|
engine.endGesture();
|
|
1174
1409
|
const items = [];
|
|
1175
1410
|
for (const [id, c] of g.startCells) {
|
|
1176
|
-
const lockedNode = ((
|
|
1411
|
+
const lockedNode = ((_c = (_b = diagram.getNode(id)) === null || _b === void 0 ? void 0 : _b.state) === null || _c === void 0 ? void 0 : _c.locked) === true;
|
|
1177
1412
|
items.push(Object.assign(Object.assign({ id }, c), { locked: lockedNode || isGroupMember(id) }));
|
|
1178
1413
|
}
|
|
1179
1414
|
engine = engineFrom(items);
|
|
@@ -1221,7 +1456,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1221
1456
|
enforceBoardHeight();
|
|
1222
1457
|
api.renderNow();
|
|
1223
1458
|
if (notify) {
|
|
1224
|
-
(
|
|
1459
|
+
(_d = options.onGesture) === null || _d === void 0 ? void 0 : _d.call(options, { type: 'cancel', kind: g.kind, nodeId: g.id, changed: false });
|
|
1225
1460
|
}
|
|
1226
1461
|
};
|
|
1227
1462
|
/** Centre a w×h-span tile's top-left under the cursor, in world px. */
|
|
@@ -1790,6 +2025,32 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1790
2025
|
};
|
|
1791
2026
|
const selfPeer = {
|
|
1792
2027
|
group,
|
|
2028
|
+
clearSelection: () => {
|
|
2029
|
+
var _a;
|
|
2030
|
+
if (selectedId === undefined)
|
|
2031
|
+
return;
|
|
2032
|
+
selectedId = undefined;
|
|
2033
|
+
syncA11y();
|
|
2034
|
+
syncSlabs();
|
|
2035
|
+
(_a = options.onSelect) === null || _a === void 0 ? void 0 : _a.call(options, undefined);
|
|
2036
|
+
},
|
|
2037
|
+
innerRows: () => maxRows !== null && maxRows !== void 0 ? maxRows : rows(),
|
|
2038
|
+
selectMember: (id) => {
|
|
2039
|
+
var _a;
|
|
2040
|
+
if (((_a = group.members) !== null && _a !== void 0 ? _a : new Set()).has(id)) {
|
|
2041
|
+
selectWidget(id);
|
|
2042
|
+
api.render();
|
|
2043
|
+
}
|
|
2044
|
+
},
|
|
2045
|
+
beginSlabResize: (id, edges, ev) => {
|
|
2046
|
+
if (isStatic)
|
|
2047
|
+
return false;
|
|
2048
|
+
beginSlabResize(id, edges, ev);
|
|
2049
|
+
return (slabGesture === null || slabGesture === void 0 ? void 0 : slabGesture.id) === id;
|
|
2050
|
+
},
|
|
2051
|
+
slabMove: (ev) => slabMove(ev),
|
|
2052
|
+
slabUp: () => slabUp(),
|
|
2053
|
+
slabCancel: () => slabCancel(),
|
|
1793
2054
|
hasItem: (id) => !!engine.getItem(id),
|
|
1794
2055
|
memberCell: (id) => {
|
|
1795
2056
|
const it = engine.getItem(id);
|
|
@@ -1826,6 +2087,9 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1826
2087
|
adopt,
|
|
1827
2088
|
};
|
|
1828
2089
|
peersOnCanvas().add(selfPeer);
|
|
2090
|
+
selfPeerRef = selfPeer;
|
|
2091
|
+
// The board's gap, for chrome that must fit BETWEEN tiles (the outside grip tab).
|
|
2092
|
+
api.container.style.setProperty('--axdb-gap', `${gap}px`);
|
|
1829
2093
|
const tool = {
|
|
1830
2094
|
id: `dashboard-grid:${group.id}:${++binderSeq}`,
|
|
1831
2095
|
priority: 2, // point-specific claim — outranks mode-style tools (see ext/tools.ts)
|
|
@@ -1833,7 +2097,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1833
2097
|
var _a, _b;
|
|
1834
2098
|
if (disposed)
|
|
1835
2099
|
return false;
|
|
1836
|
-
if (gesture)
|
|
2100
|
+
if (gesture || slabGesture || forwardSlab)
|
|
1837
2101
|
return true; // own the rest of an in-flight gesture
|
|
1838
2102
|
if (!ownsPress(api.container, diagram, ev, hit))
|
|
1839
2103
|
return false;
|
|
@@ -1862,7 +2126,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1862
2126
|
return insideMemberGroupFrame(ev.world.x, ev.world.y) || worldInsideBoard(ev.world.x, ev.world.y);
|
|
1863
2127
|
},
|
|
1864
2128
|
onPointerDown(ev, hit) {
|
|
1865
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
|
|
2129
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s;
|
|
1866
2130
|
if (gesture)
|
|
1867
2131
|
return; // mid-palette
|
|
1868
2132
|
const target = ((_b = (_a = ev.source) === null || _a === void 0 ? void 0 : _a.target) !== null && _b !== void 0 ? _b : null);
|
|
@@ -1873,9 +2137,43 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1873
2137
|
const gripId = (_c = gripHost === null || gripHost === void 0 ? void 0 : gripHost.getAttribute('data-node-id')) !== null && _c !== void 0 ? _c : null;
|
|
1874
2138
|
const onGrip = !!gripId && ((_d = group.members) !== null && _d !== void 0 ? _d : new Set()).has(gripId);
|
|
1875
2139
|
if (!hit.node && !onGrip) {
|
|
2140
|
+
// A press on a SECTION — its empty band, its corner handle or its
|
|
2141
|
+
// frame edge — selects the section; the handle or an edge resizes it.
|
|
2142
|
+
const slabHandle = (_e = target === null || target === void 0 ? void 0 : target.closest) === null || _e === void 0 ? void 0 : _e.call(target, '.axdb-slab > .axdb-rs');
|
|
2143
|
+
const slabId = (_g = (_f = slabHandle === null || slabHandle === void 0 ? void 0 : slabHandle.parentElement) === null || _f === void 0 ? void 0 : _f.getAttribute('data-slab-id')) !== null && _g !== void 0 ? _g : memberGroupAt(ev.world.x, ev.world.y);
|
|
2144
|
+
const grp = slabId && ((_h = group.members) !== null && _h !== void 0 ? _h : new Set()).has(slabId) ? diagram.getGroup(slabId) : undefined;
|
|
2145
|
+
if (slabId && grp) {
|
|
2146
|
+
selectWidget(slabId);
|
|
2147
|
+
api.render();
|
|
2148
|
+
if (isStatic)
|
|
2149
|
+
return;
|
|
2150
|
+
const edges = slabHandle
|
|
2151
|
+
? rtl ? { n: false, e: false, s: true, w: true } : { n: false, e: true, s: true, w: false }
|
|
2152
|
+
: slabEdgesNear(grp, ev.world.x, ev.world.y);
|
|
2153
|
+
if (anyEdge(edges))
|
|
2154
|
+
beginSlabResize(slabId, edges, ev);
|
|
2155
|
+
return;
|
|
2156
|
+
}
|
|
2157
|
+
// OUR OWN empty band, and we are a section of a parent board: the
|
|
2158
|
+
// press selects the section there, and its edge or corner handle
|
|
2159
|
+
// starts the section resize, which the parent runs while this tool
|
|
2160
|
+
// forwards the pointer sequence.
|
|
2161
|
+
const parent = parentPeer();
|
|
2162
|
+
if ((parent === null || parent === void 0 ? void 0 : parent.selectMember) && worldInsideBoard(ev.world.x, ev.world.y)) {
|
|
2163
|
+
const ownHandle = ((_j = slabHandle === null || slabHandle === void 0 ? void 0 : slabHandle.parentElement) === null || _j === void 0 ? void 0 : _j.getAttribute('data-slab-id')) === group.id;
|
|
2164
|
+
parent.selectMember(group.id);
|
|
2165
|
+
if (!isStatic && parent.beginSlabResize) {
|
|
2166
|
+
const edges = ownHandle
|
|
2167
|
+
? rtl ? { n: false, e: false, s: true, w: true } : { n: false, e: true, s: true, w: false }
|
|
2168
|
+
: slabEdgesNear(group, ev.world.x, ev.world.y);
|
|
2169
|
+
if (anyEdge(edges) && parent.beginSlabResize(group.id, edges, ev))
|
|
2170
|
+
forwardSlab = parent;
|
|
2171
|
+
}
|
|
2172
|
+
return;
|
|
2173
|
+
}
|
|
1876
2174
|
// The board's own empty area: a void click. Nothing to drag, and the
|
|
1877
2175
|
// selection clears exactly as a click outside any board would.
|
|
1878
|
-
(
|
|
2176
|
+
(_l = (_k = diagram).clearSelection) === null || _l === void 0 ? void 0 : _l.call(_k);
|
|
1879
2177
|
selectWidget(undefined);
|
|
1880
2178
|
api.render();
|
|
1881
2179
|
return;
|
|
@@ -1884,17 +2182,17 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1884
2182
|
if (!node)
|
|
1885
2183
|
return;
|
|
1886
2184
|
selectWidget(node.id); // a press selects, whether or not it starts a gesture
|
|
1887
|
-
if (((
|
|
2185
|
+
if (((_m = node.state) === null || _m === void 0 ? void 0 : _m.locked) === true)
|
|
1888
2186
|
return; // pinned: refuse; click still focuses
|
|
1889
2187
|
if (isStatic)
|
|
1890
2188
|
return; // a static board: claimed and deadened, click still focuses
|
|
1891
2189
|
// Which edges did the press take? The corner handle names its own (s+e,
|
|
1892
2190
|
// or s+w on RTL); a bare press within EDGE_GRIP of the tile's border
|
|
1893
2191
|
// takes that border; anywhere else is a move. A grip press is a move.
|
|
1894
|
-
const onHandle = !!((
|
|
2192
|
+
const onHandle = !!((_o = target === null || target === void 0 ? void 0 : target.closest) === null || _o === void 0 ? void 0 : _o.call(target, '.axdb-rs'));
|
|
1895
2193
|
const hostEl = hostOf(node.id);
|
|
1896
|
-
const resizable = ((
|
|
1897
|
-
const movable = ((
|
|
2194
|
+
const resizable = ((_p = node.getMetadata) === null || _p === void 0 ? void 0 : _p.call(node, 'widgetResizable')) !== false;
|
|
2195
|
+
const movable = ((_q = node.getMetadata) === null || _q === void 0 ? void 0 : _q.call(node, 'widgetMovable')) !== false;
|
|
1898
2196
|
// `ev.screen` is ELEMENT-LOCAL px; host rects are in client px. Compare
|
|
1899
2197
|
// like with like — the source event's clientX/Y when there is one, else
|
|
1900
2198
|
// the container's origin plus the local offset.
|
|
@@ -1942,7 +2240,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1942
2240
|
startSize: { width: node.size.width, height: node.size.height },
|
|
1943
2241
|
startPos: { x: node.position.x, y: node.position.y },
|
|
1944
2242
|
edges,
|
|
1945
|
-
spans: { w: (
|
|
2243
|
+
spans: { w: (_r = it === null || it === void 0 ? void 0 : it.w) !== null && _r !== void 0 ? _r : 1, h: (_s = it === null || it === void 0 ? void 0 : it.h) !== null && _s !== void 0 ? _s : 1 },
|
|
1946
2244
|
removedFromBoard: false,
|
|
1947
2245
|
leg: null,
|
|
1948
2246
|
lastWorld: null,
|
|
@@ -1953,10 +2251,24 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1953
2251
|
};
|
|
1954
2252
|
},
|
|
1955
2253
|
onPointerMove(ev) {
|
|
1956
|
-
|
|
2254
|
+
var _a;
|
|
2255
|
+
if (forwardSlab)
|
|
2256
|
+
(_a = forwardSlab.slabMove) === null || _a === void 0 ? void 0 : _a.call(forwardSlab, ev);
|
|
2257
|
+
else if (slabGesture)
|
|
2258
|
+
slabMove(ev);
|
|
2259
|
+
else
|
|
2260
|
+
onToolMove(ev);
|
|
1957
2261
|
},
|
|
1958
2262
|
onPointerUp() {
|
|
1959
|
-
|
|
2263
|
+
var _a;
|
|
2264
|
+
if (forwardSlab) {
|
|
2265
|
+
(_a = forwardSlab.slabUp) === null || _a === void 0 ? void 0 : _a.call(forwardSlab);
|
|
2266
|
+
forwardSlab = null;
|
|
2267
|
+
}
|
|
2268
|
+
else if (slabGesture)
|
|
2269
|
+
slabUp();
|
|
2270
|
+
else
|
|
2271
|
+
onToolUp();
|
|
1960
2272
|
},
|
|
1961
2273
|
onCancel() {
|
|
1962
2274
|
cancelActiveGesture();
|
|
@@ -1970,7 +2282,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1970
2282
|
*/
|
|
1971
2283
|
let hoverHost = null;
|
|
1972
2284
|
const onHover = (e) => {
|
|
1973
|
-
var _a, _b, _c, _d, _e, _f, _g;
|
|
2285
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
1974
2286
|
if (disposed || gesture)
|
|
1975
2287
|
return;
|
|
1976
2288
|
// The event may target the host, its content, or (when a host's content
|
|
@@ -1994,13 +2306,23 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1994
2306
|
hoverHost.removeAttribute('data-axdb-edge'); // the affordance follows the pointer off a tile
|
|
1995
2307
|
}
|
|
1996
2308
|
hoverHost = host;
|
|
1997
|
-
if (!host)
|
|
2309
|
+
if (!host) {
|
|
2310
|
+
// No tile under the pointer: a SECTION's frame edge still says resize.
|
|
2311
|
+
const wpt = ((_d = api.viewport) === null || _d === void 0 ? void 0 : _d.clientToWorld) ? api.viewport.clientToWorld(e.clientX, e.clientY, api.container.getBoundingClientRect()) : null;
|
|
2312
|
+
const sid = wpt ? memberGroupAt(wpt.x, wpt.y) : null;
|
|
2313
|
+
const grp = sid ? diagram.getGroup(sid) : undefined;
|
|
2314
|
+
const c = grp && wpt && !isStatic ? cursorFor(slabEdgesNear(grp, wpt.x, wpt.y)) : '';
|
|
2315
|
+
if (!slabGesture)
|
|
2316
|
+
api.container.style.cursor = c;
|
|
1998
2317
|
return;
|
|
1999
|
-
|
|
2000
|
-
if (!
|
|
2318
|
+
}
|
|
2319
|
+
if (!slabGesture && api.container.style.cursor)
|
|
2320
|
+
api.container.style.cursor = '';
|
|
2321
|
+
const id = (_e = host.getAttribute('data-node-id')) !== null && _e !== void 0 ? _e : '';
|
|
2322
|
+
if (!((_f = group.members) !== null && _f !== void 0 ? _f : new Set()).has(id))
|
|
2001
2323
|
return;
|
|
2002
2324
|
const node = diagram.getNode(id);
|
|
2003
|
-
const resizable = !!node && !isStatic && ((
|
|
2325
|
+
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;
|
|
2004
2326
|
const cursor = resizable ? cursorFor(edgesNear(host, e.clientX, e.clientY)) : '';
|
|
2005
2327
|
// The affordance rides on an ATTRIBUTE, not the inline style: a repaint
|
|
2006
2328
|
// rewrites the host's style and used to clear the cursor mid-hover, and
|
|
@@ -2070,7 +2392,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2070
2392
|
return;
|
|
2071
2393
|
if (focusedId !== hit.id || selectedId !== hit.id) {
|
|
2072
2394
|
focusedId = hit.id;
|
|
2073
|
-
|
|
2395
|
+
selectWidget(hit.id);
|
|
2074
2396
|
syncA11y();
|
|
2075
2397
|
}
|
|
2076
2398
|
};
|
|
@@ -2334,6 +2656,17 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2334
2656
|
writing = false;
|
|
2335
2657
|
}
|
|
2336
2658
|
const commands = buildCommitCommands(deltasSince(snap.cells, snap.geoms));
|
|
2659
|
+
if (isGroupMember(id)) {
|
|
2660
|
+
// A SECTION: its cell lives in group metadata and its frame is written
|
|
2661
|
+
// by project(); the node commands above skip groups (D5 of the review).
|
|
2662
|
+
const it = engine.getItem(id);
|
|
2663
|
+
const grp = diagram.getGroup(id);
|
|
2664
|
+
const cb = snap.cells.get(id);
|
|
2665
|
+
const gb = snap.geoms.get(id);
|
|
2666
|
+
if (it && grp && cb && gb && (cb.x !== it.x || cb.y !== it.y || cb.w !== it.w || cb.h !== it.h)) {
|
|
2667
|
+
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)));
|
|
2668
|
+
}
|
|
2669
|
+
}
|
|
2337
2670
|
engine.endGesture();
|
|
2338
2671
|
disarmGlideSoon();
|
|
2339
2672
|
enforceBoardHeight();
|
|
@@ -2422,7 +2755,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2422
2755
|
if (disposed || !((_a = group.members) !== null && _a !== void 0 ? _a : new Set()).has(id) || !diagram.getNode(id))
|
|
2423
2756
|
return false;
|
|
2424
2757
|
focusedId = id;
|
|
2425
|
-
|
|
2758
|
+
selectWidget(id);
|
|
2426
2759
|
syncA11y();
|
|
2427
2760
|
(_c = (_b = hostOf(id)) === null || _b === void 0 ? void 0 : _b.focus) === null || _c === void 0 ? void 0 : _c.call(_b, { preventScroll: true });
|
|
2428
2761
|
return true;
|
|
@@ -2432,7 +2765,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2432
2765
|
var _a;
|
|
2433
2766
|
if (disposed)
|
|
2434
2767
|
return false;
|
|
2435
|
-
if (id !== undefined && (!((_a = group.members) !== null && _a !== void 0 ? _a : new Set()).has(id) || !
|
|
2768
|
+
if (id !== undefined && (!((_a = group.members) !== null && _a !== void 0 ? _a : new Set()).has(id) || !memberEntity(id)))
|
|
2436
2769
|
return false;
|
|
2437
2770
|
selectWidget(id);
|
|
2438
2771
|
return true;
|
|
@@ -2561,7 +2894,8 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2561
2894
|
return programmatic('Move widget', id, () => engine.moveCheck(id, x, y).changed);
|
|
2562
2895
|
},
|
|
2563
2896
|
resizeTo(id, w, h) {
|
|
2564
|
-
|
|
2897
|
+
const hh = isGroupMember(id) ? Math.max(h, innerRowsOf(id)) : h; // a section: never below its children
|
|
2898
|
+
return programmatic('Resize widget', id, () => engine.resizeCheck(id, w, hh).changed);
|
|
2565
2899
|
},
|
|
2566
2900
|
beginPaletteDrag,
|
|
2567
2901
|
dispose() {
|
|
@@ -2588,6 +2922,9 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2588
2922
|
placeholder = null;
|
|
2589
2923
|
if (glideTimer)
|
|
2590
2924
|
clearTimeout(glideTimer);
|
|
2925
|
+
for (const el of slabEls.values())
|
|
2926
|
+
el.remove();
|
|
2927
|
+
slabEls.clear();
|
|
2591
2928
|
// A rebind inside the 60 ms window (a layout switch right after an undo)
|
|
2592
2929
|
// disposed this binder with the timer pending — the host kept its
|
|
2593
2930
|
// 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
|
}
|
|
@@ -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, registerBoardPeer, syncGrip, DRAG_HANDLE_CLASS } from './grid-binder.js';
|
|
28
|
+
import { clearOtherSelections, 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';
|
|
@@ -372,11 +372,16 @@ 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;
|
|
376
|
+
if (id !== undefined)
|
|
377
|
+
clearOtherSelections(api.container, selfPeerRef);
|
|
375
378
|
if (id === selectedId)
|
|
376
379
|
return;
|
|
377
380
|
selectedId = id;
|
|
378
381
|
syncA11y();
|
|
382
|
+
(_a = options.onSelect) === null || _a === void 0 ? void 0 : _a.call(options, id);
|
|
379
383
|
};
|
|
384
|
+
let selfPeerRef = null;
|
|
380
385
|
// Static boards let content be clicked — see grid-binder's staticGuard.
|
|
381
386
|
const staticGuard = (e) => {
|
|
382
387
|
var _a, _b, _c, _d;
|
|
@@ -823,7 +828,7 @@ export function bindDashboardSplit(api, group, options = {}) {
|
|
|
823
828
|
return;
|
|
824
829
|
if (focusedId !== hit.id || selectedId !== hit.id) {
|
|
825
830
|
focusedId = hit.id;
|
|
826
|
-
|
|
831
|
+
selectWidget(hit.id);
|
|
827
832
|
syncA11y();
|
|
828
833
|
}
|
|
829
834
|
};
|
|
@@ -967,7 +972,26 @@ export function bindDashboardSplit(api, group, options = {}) {
|
|
|
967
972
|
if (layerEl && hostObserver)
|
|
968
973
|
hostObserver.observe(layerEl, { childList: true });
|
|
969
974
|
// -- the handle -------------------------------------------------------------
|
|
970
|
-
|
|
975
|
+
/**
|
|
976
|
+
* The row count the tree's cells are written in. A grid-authored board (or
|
|
977
|
+
* one switched from the grid) carries its members' cells: their extent IS
|
|
978
|
+
* the row count, and switching back must return the same cells — the guess
|
|
979
|
+
* below quantised three 14-row sections on a 758-px fit board to 5 rows
|
|
980
|
+
* (base row height 130), and the grid came back with 14-row inner grids
|
|
981
|
+
* inside 5-row slabs (Quantia, Groups page, Split → Grid). The guess stays
|
|
982
|
+
* for a tree-authored board that never had cells.
|
|
983
|
+
*/
|
|
984
|
+
const rowsAtBind = (() => {
|
|
985
|
+
let max = 0;
|
|
986
|
+
for (const id of members()) {
|
|
987
|
+
const c = persistedCell(id);
|
|
988
|
+
if (!c)
|
|
989
|
+
return undefined;
|
|
990
|
+
max = Math.max(max, c.y + c.h);
|
|
991
|
+
}
|
|
992
|
+
return max > 0 ? max : undefined;
|
|
993
|
+
})();
|
|
994
|
+
const rowsGuess = () => rowsAtBind !== null && rowsAtBind !== void 0 ? rowsAtBind : Math.max(1, Math.round(frame().height / (baseRowHeight + gap)));
|
|
971
995
|
const handle = {
|
|
972
996
|
sync() {
|
|
973
997
|
if (disposed)
|
|
@@ -1021,7 +1045,7 @@ export function bindDashboardSplit(api, group, options = {}) {
|
|
|
1021
1045
|
if (!((_a = group.members) !== null && _a !== void 0 ? _a : new Set()).has(id) || !diagram.getNode(id))
|
|
1022
1046
|
return false;
|
|
1023
1047
|
focusedId = id;
|
|
1024
|
-
|
|
1048
|
+
selectWidget(id);
|
|
1025
1049
|
syncA11y();
|
|
1026
1050
|
(_c = (_b = hostOf(id)) === null || _b === void 0 ? void 0 : _b.focus) === null || _c === void 0 ? void 0 : _c.call(_b, { preventScroll: true });
|
|
1027
1051
|
return true;
|
|
@@ -1168,8 +1192,16 @@ export function bindDashboardSplit(api, group, options = {}) {
|
|
|
1168
1192
|
api.renderNow();
|
|
1169
1193
|
// A split board on a CONTAINER (item 7) sits inside a parent grid: register
|
|
1170
1194
|
// as a peer so the parent's hitTest hands presses on our tiles to us.
|
|
1171
|
-
const
|
|
1195
|
+
const selfPeer = {
|
|
1172
1196
|
group,
|
|
1197
|
+
clearSelection: () => {
|
|
1198
|
+
var _a;
|
|
1199
|
+
if (selectedId === undefined)
|
|
1200
|
+
return;
|
|
1201
|
+
selectedId = undefined;
|
|
1202
|
+
syncA11y();
|
|
1203
|
+
(_a = options.onSelect) === null || _a === void 0 ? void 0 : _a.call(options, undefined);
|
|
1204
|
+
},
|
|
1173
1205
|
hasItem: (id) => { var _a; return ((_a = group.members) !== null && _a !== void 0 ? _a : new Set()).has(id); },
|
|
1174
1206
|
memberCell: (id) => handle.cellOf(id),
|
|
1175
1207
|
resizeMemberBy: () => ({ changed: false }),
|
|
@@ -1180,7 +1212,10 @@ export function bindDashboardSplit(api, group, options = {}) {
|
|
|
1180
1212
|
return f.width * f.height;
|
|
1181
1213
|
},
|
|
1182
1214
|
adopt: () => null,
|
|
1183
|
-
}
|
|
1215
|
+
};
|
|
1216
|
+
const unregisterPeer = registerBoardPeer(api.container, selfPeer);
|
|
1217
|
+
selfPeerRef = selfPeer;
|
|
1218
|
+
api.container.style.setProperty('--axdb-gap', `${gap}px`);
|
|
1184
1219
|
const disposeHandle = handle.dispose.bind(handle);
|
|
1185
1220
|
handle.dispose = () => {
|
|
1186
1221
|
unregisterPeer();
|
|
@@ -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;
|
|
@@ -235,7 +235,10 @@ const CSS = `
|
|
|
235
235
|
.grafloria-node-host > .axdb-grip--inside { top: 2px; }
|
|
236
236
|
}
|
|
237
237
|
/* OUTSIDE: a tab on the card's top edge, its corners in line with the card's. */
|
|
238
|
-
|
|
238
|
+
/* The OUTSIDE tab lives in the gap between tiles: never taller than the gap
|
|
239
|
+
less a pixel (an 11-px tab in a 10-px gap sat on the tile above), never
|
|
240
|
+
shorter than 6 px so the dots still read. */
|
|
241
|
+
.grafloria-node-host > .axdb-grip--outside { top: calc(-1 * clamp(6px, var(--axdb-gap, 11px) - 1px, 11px)); height: clamp(6px, calc(var(--axdb-gap, 11px) - 1px), 11px); border-bottom-left-radius: 0; border-bottom-right-radius: 0; border-bottom: 0; box-shadow: 0 -1px 2px rgba(16, 24, 40, .08); }
|
|
239
242
|
.grafloria-node-host > .axdb-grip--outside.axdb-grip--left { left: 10px; }
|
|
240
243
|
.grafloria-node-host > .axdb-grip--outside.axdb-grip--right { right: 10px; }
|
|
241
244
|
.grafloria-node-host > .axdb-grip--center { left: 50%; transform: translateX(-50%); }
|
|
@@ -352,6 +355,14 @@ const CSS = `
|
|
|
352
355
|
.axdb-tier-2 .axdb-xt, .axdb-tier-2 .axdb-vt,
|
|
353
356
|
.axdb-lg--off { display: none; }
|
|
354
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
|
+
.grafloria-html-layer > .axdb-slab.axdb-slab--selected { box-shadow: 0 0 0 1.5px var(--axdb-accent-ring, rgba(59, 82, 217, .55)); }
|
|
362
|
+
.grafloria-html-layer > .axdb-slab > .axdb-rs { pointer-events: auto; opacity: 0; }
|
|
363
|
+
.grafloria-html-layer > .axdb-slab.axdb-slab--selected > .axdb-rs { opacity: 1; }
|
|
364
|
+
.grafloria-html-layer > .axdb-slab.axdb-slab--static > .axdb-rs { display: none; }
|
|
365
|
+
|
|
355
366
|
/* legend chips, shared by line and donut */
|
|
356
367
|
.axdb-lg { display: flex; flex-wrap: wrap; gap: 4px 12px; margin-top: 9px; }
|
|
357
368
|
.axdb-lg--col { flex-direction: column; flex-wrap: nowrap; gap: 6px; margin-top: 0; }
|