@grafloria/element 0.4.3 → 0.4.5
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 +11 -0
- package/src/lib/dashboard-kit/dashboard.js +29 -13
- package/src/lib/dashboard-kit/grid-binder.d.ts +26 -0
- package/src/lib/dashboard-kit/grid-binder.js +54 -9
- package/src/lib/dashboard-kit/split-binder.d.ts +1 -1
- package/src/lib/dashboard-kit/split-binder.js +61 -1
- package/src/lib/dashboard-kit/styles.js +8 -0
package/package.json
CHANGED
|
@@ -188,6 +188,14 @@ export interface DashboardOptions {
|
|
|
188
188
|
* so a designer/viewer pair is one flag apart. Live: `handle.setStatic()`.
|
|
189
189
|
*/
|
|
190
190
|
static?: boolean;
|
|
191
|
+
/**
|
|
192
|
+
* DRAG HANDLE — DevExpress drags an item by its caption. `true`: a widget
|
|
193
|
+
* moves only from its header, which shows a grip; a selector string: your
|
|
194
|
+
* own handle inside the card; off (default): the whole card. Resize edges
|
|
195
|
+
* and the keyboard are unaffected; the body stays interactive. Live:
|
|
196
|
+
* `handle.setDragHandle()`.
|
|
197
|
+
*/
|
|
198
|
+
dragHandle?: boolean | string;
|
|
191
199
|
/**
|
|
192
200
|
* RIGHT-TO-LEFT boards: column x=0 renders at the RIGHT edge and columns run
|
|
193
201
|
* leftwards. Cells are untouched — the same `widgets` array describes the
|
|
@@ -287,6 +295,9 @@ export interface DashboardHandle {
|
|
|
287
295
|
/** Static (read-only for the pointer) mode, live — the viewer/designer switch. */
|
|
288
296
|
setStatic(on: boolean): void;
|
|
289
297
|
getStatic(): boolean;
|
|
298
|
+
/** Drag-handle mode, live, every view: `true` = the header, a selector = your own handle, `false` = the whole card. */
|
|
299
|
+
setDragHandle(v: boolean | string): void;
|
|
300
|
+
getDragHandle(): boolean | string;
|
|
290
301
|
/**
|
|
291
302
|
* Add a widget to a view. CREATES the node (you do not pre-build one), wires
|
|
292
303
|
* its metadata, and commits node + membership as ONE undoable step.
|
|
@@ -561,6 +561,13 @@ export function createDashboardHandle(ctx) {
|
|
|
561
561
|
(_a = ctx.apiRef) === null || _a === void 0 ? void 0 : _a.renderNow();
|
|
562
562
|
},
|
|
563
563
|
getStatic: () => { var _a, _b, _c; return (_b = (_a = binders.get(ctx.active)) === null || _a === void 0 ? void 0 : _a.getStatic()) !== null && _b !== void 0 ? _b : ((_c = ctx.optionsBase.static) !== null && _c !== void 0 ? _c : false); },
|
|
564
|
+
setDragHandle(v) {
|
|
565
|
+
var _a;
|
|
566
|
+
for (const b of binders.values())
|
|
567
|
+
b.setDragHandle(v);
|
|
568
|
+
(_a = ctx.apiRef) === null || _a === void 0 ? void 0 : _a.renderNow();
|
|
569
|
+
},
|
|
570
|
+
getDragHandle: () => { var _a, _b, _c; return (_b = (_a = binders.get(ctx.active)) === null || _a === void 0 ? void 0 : _a.getDragHandle()) !== null && _b !== void 0 ? _b : ((_c = ctx.optionsBase.dragHandle) !== null && _c !== void 0 ? _c : false); },
|
|
564
571
|
addWidget(spec, viewId) {
|
|
565
572
|
var _a, _b, _c, _d, _e, _f;
|
|
566
573
|
const vid = viewId !== null && viewId !== void 0 ? viewId : ctx.active;
|
|
@@ -657,7 +664,7 @@ export function createDashboardHandle(ctx) {
|
|
|
657
664
|
// `sizing` and `float` are the two a user changes from the toolbar, and
|
|
658
665
|
// reading them from the authored literal would restore the board they
|
|
659
666
|
// started with rather than the one they are looking at.
|
|
660
|
-
return Object.assign(Object.assign({}, ctx.optionsBase), { renderWidget: undefined, onLayoutChange: undefined, columns: ctx.columns, gap: ctx.gap, rowHeight: ctx.rowHeight, mode: ctx.mode, overflow: ctx.overflow, sizing: handle.getSizing(), float: handle.getFloat(), rtl: handle.getRtl(), static: handle.getStatic(), layout: handle.getLayout(), views: savedViews });
|
|
667
|
+
return Object.assign(Object.assign({}, ctx.optionsBase), { renderWidget: undefined, onLayoutChange: undefined, columns: ctx.columns, gap: ctx.gap, rowHeight: ctx.rowHeight, mode: ctx.mode, overflow: ctx.overflow, sizing: handle.getSizing(), float: handle.getFloat(), rtl: handle.getRtl(), static: handle.getStatic(), dragHandle: handle.getDragHandle(), layout: handle.getLayout(), views: savedViews });
|
|
661
668
|
},
|
|
662
669
|
dispose() {
|
|
663
670
|
var _a, _b, _c, _d, _e;
|
|
@@ -964,7 +971,7 @@ export function dashboard(options) {
|
|
|
964
971
|
}, get handle() {
|
|
965
972
|
return handle;
|
|
966
973
|
}, finalize: (api) => {
|
|
967
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
|
|
974
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
|
|
968
975
|
const a = api;
|
|
969
976
|
if (!a)
|
|
970
977
|
return;
|
|
@@ -1002,16 +1009,17 @@ export function dashboard(options) {
|
|
|
1002
1009
|
fluid: mode === 'fluid',
|
|
1003
1010
|
overflow,
|
|
1004
1011
|
static: (_g = options.static) !== null && _g !== void 0 ? _g : false,
|
|
1005
|
-
|
|
1012
|
+
dragHandle: (_h = options.dragHandle) !== null && _h !== void 0 ? _h : false,
|
|
1013
|
+
layout: (_j = ctx.layoutOf.get(v.id)) !== null && _j !== void 0 ? _j : layout,
|
|
1006
1014
|
});
|
|
1007
|
-
if (((
|
|
1015
|
+
if (((_k = ctx.layoutOf.get(v.id)) !== null && _k !== void 0 ? _k : layout) === 'split' && v.tree !== undefined)
|
|
1008
1016
|
g.setMetadata(SPLIT_TREE_KEY, v.tree);
|
|
1009
1017
|
g.size = { width: viewW(v), height: viewH(v), depth: 0 };
|
|
1010
1018
|
g.position = { x: v.id === ctx.active ? 0 : OFFSCREEN_X, y: 0 };
|
|
1011
1019
|
groups.set(v.id, g);
|
|
1012
1020
|
ctx.boardGroups.set(v.id, g);
|
|
1013
1021
|
mountBoard(v.id, v.id, v.widgets, g);
|
|
1014
|
-
binders.set(v.id, bindView(v, g, (
|
|
1022
|
+
binders.set(v.id, bindView(v, g, (_l = ctx.layoutOf.get(v.id)) !== null && _l !== void 0 ? _l : layout));
|
|
1015
1023
|
}
|
|
1016
1024
|
// LIVE LAYOUT SWITCH (setLayout): keep the picture, swap the binder.
|
|
1017
1025
|
// Either way the outgoing binder's cells are written where the grid
|
|
@@ -1026,6 +1034,7 @@ export function dashboard(options) {
|
|
|
1026
1034
|
if (!v || !g || !b)
|
|
1027
1035
|
return;
|
|
1028
1036
|
const cells = b.saveLayout().cells;
|
|
1037
|
+
const live = { rtl: b.getRtl(), static: b.getStatic(), dragHandle: b.getDragHandle() };
|
|
1029
1038
|
b.dispose();
|
|
1030
1039
|
const write = (fn) => (model.runSystemWrite ? model.runSystemWrite(fn) : fn());
|
|
1031
1040
|
write(() => {
|
|
@@ -1043,7 +1052,7 @@ export function dashboard(options) {
|
|
|
1043
1052
|
g.setMetadata('dashboardBoard', Object.assign(Object.assign({}, board), { layout: next }));
|
|
1044
1053
|
});
|
|
1045
1054
|
ctx.layoutOf.set(viewId, next);
|
|
1046
|
-
binders.set(viewId, bindView(v, g, next));
|
|
1055
|
+
binders.set(viewId, bindView(v, g, next, live));
|
|
1047
1056
|
(_a = binders.get(viewId)) === null || _a === void 0 ? void 0 : _a.sync();
|
|
1048
1057
|
};
|
|
1049
1058
|
handle.showView(ctx.active);
|
|
@@ -1056,7 +1065,7 @@ export function dashboard(options) {
|
|
|
1056
1065
|
ctx.boardGroups.set(id, g);
|
|
1057
1066
|
bindContainer(g, w, (_a = ctx.viewOfBoard.get(id)) !== null && _a !== void 0 ? _a : ctx.active);
|
|
1058
1067
|
};
|
|
1059
|
-
(
|
|
1068
|
+
(_m = ctx.attachHistory) === null || _m === void 0 ? void 0 : _m.call(ctx);
|
|
1060
1069
|
return;
|
|
1061
1070
|
/**
|
|
1062
1071
|
* Mount one board's widgets into its group — and recurse for CONTAINERS.
|
|
@@ -1131,22 +1140,28 @@ export function dashboard(options) {
|
|
|
1131
1140
|
}
|
|
1132
1141
|
}
|
|
1133
1142
|
/** Bind a VIEW's board on its group, under the given layout. */
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1143
|
+
/**
|
|
1144
|
+
* Bind a view's board. `live` carries the switches a previous binder of
|
|
1145
|
+
* the same view held (a re-bind on a layout change): without it a board
|
|
1146
|
+
* switched to static, RTL or drag-by-header LIVE snapped back to its
|
|
1147
|
+
* authored options the moment its layout changed (s34 caught it).
|
|
1148
|
+
*/
|
|
1149
|
+
function bindView(v, g, viewLayout, live) {
|
|
1150
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
|
|
1151
|
+
const common = Object.assign(Object.assign({ gap, padding: gap, rtl: (_b = (_a = live === null || live === void 0 ? void 0 : live.rtl) !== null && _a !== void 0 ? _a : options.rtl) !== null && _b !== void 0 ? _b : false, fluid: mode === 'fluid', static: (_d = (_c = live === null || live === void 0 ? void 0 : live.static) !== null && _c !== void 0 ? _c : options.static) !== null && _d !== void 0 ? _d : false, dragHandle: (_f = (_e = live === null || live === void 0 ? void 0 : live.dragHandle) !== null && _e !== void 0 ? _e : options.dragHandle) !== null && _f !== void 0 ? _f : false }, ((_g = options.binder) !== null && _g !== void 0 ? _g : {})), { onGesture: (e) => {
|
|
1137
1152
|
var _a, _b;
|
|
1138
1153
|
if (e.type === 'commit')
|
|
1139
1154
|
reportChanged();
|
|
1140
1155
|
(_b = (_a = options.binder) === null || _a === void 0 ? void 0 : _a.onGesture) === null || _b === void 0 ? void 0 : _b.call(_a, e);
|
|
1141
1156
|
} });
|
|
1142
1157
|
if (viewLayout === 'split') {
|
|
1143
|
-
return bindDashboardSplit(a, g, Object.assign(Object.assign(Object.assign({}, common), { columns: (
|
|
1158
|
+
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 } : {})));
|
|
1144
1159
|
}
|
|
1145
|
-
return bindDashboardGrid(a, g, Object.assign(Object.assign(Object.assign({}, common), { columns: (
|
|
1160
|
+
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 } : {})));
|
|
1146
1161
|
}
|
|
1147
1162
|
/** Bind (or re-bind) a container's inner grid on its group. */
|
|
1148
1163
|
function bindContainer(cg, w, viewId) {
|
|
1149
|
-
var _a, _b, _c, _d;
|
|
1164
|
+
var _a, _b, _c, _d, _e;
|
|
1150
1165
|
binders.set(w.id, bindDashboardGrid(a, cg, {
|
|
1151
1166
|
columns: innerColumnsOf(w),
|
|
1152
1167
|
gap,
|
|
@@ -1158,6 +1173,7 @@ export function dashboard(options) {
|
|
|
1158
1173
|
float: false,
|
|
1159
1174
|
rtl: (_c = options.rtl) !== null && _c !== void 0 ? _c : false,
|
|
1160
1175
|
static: (_d = options.static) !== null && _d !== void 0 ? _d : false,
|
|
1176
|
+
dragHandle: (_e = options.dragHandle) !== null && _e !== void 0 ? _e : false,
|
|
1161
1177
|
onGesture: (e) => {
|
|
1162
1178
|
var _a, _b;
|
|
1163
1179
|
if (e.type === 'commit')
|
|
@@ -212,7 +212,28 @@ export interface DashboardGridOptions {
|
|
|
212
212
|
* still works. The viewer's mode — see `setStatic` for the live switch.
|
|
213
213
|
*/
|
|
214
214
|
static?: boolean;
|
|
215
|
+
/**
|
|
216
|
+
* DRAG HANDLE (DevExpress drags an item by its caption; gridstack's
|
|
217
|
+
* `handle`): `true` — a widget moves only from its header, which shows a
|
|
218
|
+
* grip; a selector string — your own handle inside the host. Off (default):
|
|
219
|
+
* the whole card is the handle. Resize edges are unaffected and the body
|
|
220
|
+
* stays interactive (scroll a table, click a legend). Live: `setDragHandle`.
|
|
221
|
+
*/
|
|
222
|
+
dragHandle?: boolean | string;
|
|
215
223
|
}
|
|
224
|
+
/** The selector a `dragHandle` value names — `null` when the whole card is the handle. */
|
|
225
|
+
export declare const dragHandleSelector: (v: boolean | string | undefined) => string | null;
|
|
226
|
+
/** The public form of a stored handle selector (the inverse of `dragHandleSelector`). */
|
|
227
|
+
export declare const dragHandleValue: (sel: string | null) => boolean | string;
|
|
228
|
+
/** The container class that turns the header grip on. */
|
|
229
|
+
export declare const DRAG_HANDLE_CLASS = "axdb-drag-handle";
|
|
230
|
+
/**
|
|
231
|
+
* Did a press land on the drag handle? A press INSIDE the handle element
|
|
232
|
+
* always does. The default handle is a CAPTION BAR the DevExpress way: the
|
|
233
|
+
* strip from the card's top edge down to the header's bottom, padding
|
|
234
|
+
* included — so the pointer need not hit the header's text to grab the tile.
|
|
235
|
+
*/
|
|
236
|
+
export declare function pressOnDragHandle(sel: string, target: Element | null, hostEl: HTMLElement | null, clientX: number, clientY: number): boolean;
|
|
216
237
|
export interface DashboardGridHandle {
|
|
217
238
|
/** Rebuild the engine from the group's members + their cells, re-project pixels. */
|
|
218
239
|
sync(): void;
|
|
@@ -245,6 +266,9 @@ export interface DashboardGridHandle {
|
|
|
245
266
|
/** Static mode, live: pointer gestures off (and handles gone) or back on. */
|
|
246
267
|
setStatic(on: boolean): void;
|
|
247
268
|
getStatic(): boolean;
|
|
269
|
+
/** Drag-handle mode, live: `true` = the header, a selector = your own handle, `false` = the whole card. */
|
|
270
|
+
setDragHandle(v: boolean | string): void;
|
|
271
|
+
getDragHandle(): boolean | string;
|
|
248
272
|
/**
|
|
249
273
|
* Move keyboard focus to a member (the roving tabindex lands on it). The
|
|
250
274
|
* host takes DOM focus when it exists; returns false for a non-member.
|
|
@@ -271,6 +295,8 @@ export interface DashboardGridHandle {
|
|
|
271
295
|
responsive: boolean;
|
|
272
296
|
fluid: boolean;
|
|
273
297
|
static: boolean;
|
|
298
|
+
/** `true` = the header is the grip, a selector = a custom one, `false` = the whole card. */
|
|
299
|
+
dragHandle: boolean | string;
|
|
274
300
|
/** Fit-mode row capacity (undefined when unbounded). */
|
|
275
301
|
capacity: number | undefined;
|
|
276
302
|
gap: number;
|
|
@@ -54,6 +54,32 @@ import { AddToGroupCommand, BatchCommand, Command, GridPackEngine, RemoveFromGro
|
|
|
54
54
|
import { LiveRegionController, registerTool } from '@grafloria/renderer';
|
|
55
55
|
import { buildCommitCommands, cellFromGridItem, cellToRect, columnUnitFor, gridItemFromCell, pointToCell, rowHeightFor, sizeToSpan, } from './grid-mapping.js';
|
|
56
56
|
import { ensureDashboardKitStyles } from './styles.js';
|
|
57
|
+
/** The selector a `dragHandle` value names — `null` when the whole card is the handle. */
|
|
58
|
+
export const dragHandleSelector = (v) => v === true ? '.axdb-widget-h' : typeof v === 'string' && v.length > 0 ? v : null;
|
|
59
|
+
/** The public form of a stored handle selector (the inverse of `dragHandleSelector`). */
|
|
60
|
+
export const dragHandleValue = (sel) => sel === null ? false : sel === '.axdb-widget-h' ? true : sel;
|
|
61
|
+
/** The container class that turns the header grip on. */
|
|
62
|
+
export const DRAG_HANDLE_CLASS = 'axdb-drag-handle';
|
|
63
|
+
/**
|
|
64
|
+
* Did a press land on the drag handle? A press INSIDE the handle element
|
|
65
|
+
* always does. The default handle is a CAPTION BAR the DevExpress way: the
|
|
66
|
+
* strip from the card's top edge down to the header's bottom, padding
|
|
67
|
+
* included — so the pointer need not hit the header's text to grab the tile.
|
|
68
|
+
*/
|
|
69
|
+
export function pressOnDragHandle(sel, target, hostEl, clientX, clientY) {
|
|
70
|
+
var _a, _b;
|
|
71
|
+
const grip = (_b = (_a = target === null || target === void 0 ? void 0 : target.closest) === null || _a === void 0 ? void 0 : _a.call(target, sel)) !== null && _b !== void 0 ? _b : null;
|
|
72
|
+
if (grip && (!hostEl || hostEl.contains(grip)))
|
|
73
|
+
return true;
|
|
74
|
+
if (sel !== '.axdb-widget-h' || !hostEl)
|
|
75
|
+
return false;
|
|
76
|
+
const header = hostEl.querySelector('.axdb-widget-h');
|
|
77
|
+
if (!header)
|
|
78
|
+
return false;
|
|
79
|
+
const hr = hostEl.getBoundingClientRect();
|
|
80
|
+
const r = header.getBoundingClientRect();
|
|
81
|
+
return clientX >= hr.left && clientX <= hr.right && clientY >= hr.top && clientY <= r.bottom;
|
|
82
|
+
}
|
|
57
83
|
/**
|
|
58
84
|
* Binders on the same canvas find each other here. A WeakMap: a canvas that
|
|
59
85
|
* is gone takes its (empty) peer set with it — the strong Map it replaced kept
|
|
@@ -182,6 +208,8 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
182
208
|
const fluid = options.fluid === true;
|
|
183
209
|
const overflow = (_h = options.overflow) !== null && _h !== void 0 ? _h : 'bounded';
|
|
184
210
|
let isStatic = options.static === true;
|
|
211
|
+
let dragHandle = dragHandleSelector(options.dragHandle);
|
|
212
|
+
api.container.classList.toggle(DRAG_HANDLE_CLASS, dragHandle !== null);
|
|
185
213
|
/** The member the ROVING TABINDEX rests on (one tab stop per board). */
|
|
186
214
|
let focusedId;
|
|
187
215
|
const live = liveRegionFor(api.container);
|
|
@@ -1621,24 +1649,29 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1621
1649
|
const hostEl = hostOf(node.id);
|
|
1622
1650
|
const resizable = ((_g = node.getMetadata) === null || _g === void 0 ? void 0 : _g.call(node, 'widgetResizable')) !== false;
|
|
1623
1651
|
const movable = ((_h = node.getMetadata) === null || _h === void 0 ? void 0 : _h.call(node, 'widgetMovable')) !== false;
|
|
1652
|
+
// `ev.screen` is ELEMENT-LOCAL px; host rects are in client px. Compare
|
|
1653
|
+
// like with like — the source event's clientX/Y when there is one, else
|
|
1654
|
+
// the container's origin plus the local offset.
|
|
1655
|
+
const src = ev.source;
|
|
1656
|
+
const cr = api.container.getBoundingClientRect();
|
|
1657
|
+
const cx = typeof (src === null || src === void 0 ? void 0 : src.clientX) === 'number' ? src.clientX : cr.left + ev.screen.x;
|
|
1658
|
+
const cy = typeof (src === null || src === void 0 ? void 0 : src.clientY) === 'number' ? src.clientY : cr.top + ev.screen.y;
|
|
1624
1659
|
let edges = NO_EDGES;
|
|
1625
1660
|
if (resizable) {
|
|
1626
1661
|
if (onHandle)
|
|
1627
1662
|
edges = rtl ? { n: false, e: false, s: true, w: true } : { n: false, e: true, s: true, w: false };
|
|
1628
|
-
else if (hostEl)
|
|
1629
|
-
// `ev.screen` is ELEMENT-LOCAL px; the host rect is in client px.
|
|
1630
|
-
// Compare like with like — the source event's clientX/Y when there
|
|
1631
|
-
// is one, else the container's origin plus the local offset.
|
|
1632
|
-
const src = ev.source;
|
|
1633
|
-
const cr = api.container.getBoundingClientRect();
|
|
1634
|
-
const cx = typeof (src === null || src === void 0 ? void 0 : src.clientX) === 'number' ? src.clientX : cr.left + ev.screen.x;
|
|
1635
|
-
const cy = typeof (src === null || src === void 0 ? void 0 : src.clientY) === 'number' ? src.clientY : cr.top + ev.screen.y;
|
|
1663
|
+
else if (hostEl)
|
|
1636
1664
|
edges = edgesNear(hostEl, cx, cy);
|
|
1637
|
-
}
|
|
1638
1665
|
}
|
|
1639
1666
|
const isResize = anyEdge(edges);
|
|
1640
1667
|
if (!isResize && !movable)
|
|
1641
1668
|
return; // a fixed tile: refuse the drag, click still focuses
|
|
1669
|
+
// Drag-handle mode: only a press on the handle (the caption strip by
|
|
1670
|
+
// default) moves the tile. Anywhere else the press is claimed (no group
|
|
1671
|
+
// drag) but starts nothing, so the body keeps its own behaviour — a
|
|
1672
|
+
// table scrolls, a legend clicks.
|
|
1673
|
+
if (!isResize && dragHandle !== null && !pressOnDragHandle(dragHandle, target, hostEl, cx, cy))
|
|
1674
|
+
return;
|
|
1642
1675
|
// Arm the glide class NOW, a full task before any displacement can
|
|
1643
1676
|
// happen: a transition defined in the same style recalc as the first
|
|
1644
1677
|
// left/top write does not run (CSS transitions fire only when the
|
|
@@ -2114,6 +2147,17 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2114
2147
|
api.renderNow();
|
|
2115
2148
|
},
|
|
2116
2149
|
getStatic: () => isStatic,
|
|
2150
|
+
setDragHandle(v) {
|
|
2151
|
+
const sel = dragHandleSelector(v);
|
|
2152
|
+
if (sel === dragHandle)
|
|
2153
|
+
return;
|
|
2154
|
+
dragHandle = sel;
|
|
2155
|
+
if (gesture)
|
|
2156
|
+
cancelActiveGesture(false);
|
|
2157
|
+
api.container.classList.toggle(DRAG_HANDLE_CLASS, dragHandle !== null);
|
|
2158
|
+
api.renderNow();
|
|
2159
|
+
},
|
|
2160
|
+
getDragHandle: () => dragHandleValue(dragHandle),
|
|
2117
2161
|
saveLayout() {
|
|
2118
2162
|
const saved = engine.saveLayout();
|
|
2119
2163
|
return {
|
|
@@ -2154,6 +2198,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2154
2198
|
responsive: !!responsive && !responsivePinned,
|
|
2155
2199
|
fluid,
|
|
2156
2200
|
static: isStatic,
|
|
2201
|
+
dragHandle: dragHandleValue(dragHandle),
|
|
2157
2202
|
capacity,
|
|
2158
2203
|
gap,
|
|
2159
2204
|
padding,
|
|
@@ -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' | 'designHeight' | 'baseRowHeight' | 'dragOut' | 'removeZone' | 'onRemoveRequest' | 'onDropIn' | 'onGesture'> {
|
|
30
|
+
export interface DashboardSplitOptions extends Pick<DashboardGridOptions, 'columns' | 'gap' | 'padding' | 'rtl' | 'fluid' | 'static' | 'dragHandle' | 'designHeight' | 'baseRowHeight' | 'dragOut' | 'removeZone' | 'onRemoveRequest' | 'onDropIn' | 'onGesture'> {
|
|
31
31
|
/** An authored tree. Default: the persisted one, else derived from the members' cells. */
|
|
32
32
|
tree?: SplitNode | null;
|
|
33
33
|
}
|
|
@@ -25,6 +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, dragHandleValue, pressOnDragHandle, DRAG_HANDLE_CLASS } from './grid-binder.js';
|
|
28
29
|
import { cellFromGridItem } from './grid-mapping.js';
|
|
29
30
|
import { addSplitLeaf, cellsFromSplit, cloneSplit, dividersOf, groupRectsOf, insertSplitLeaf, moveSplitDivider, normalizeSplit, pathToLeaf, projectSplit, removeSplitLeaf, splitFromCells, splitLeaves, } from './split-layout.js';
|
|
30
31
|
import { ensureDashboardKitStyles } from './styles.js';
|
|
@@ -91,6 +92,8 @@ export function bindDashboardSplit(api, group, options = {}) {
|
|
|
91
92
|
const fluid = options.fluid === true;
|
|
92
93
|
let rtl = options.rtl === true;
|
|
93
94
|
let isStatic = options.static === true;
|
|
95
|
+
let dragHandle = dragHandleSelector(options.dragHandle);
|
|
96
|
+
api.container.classList.toggle(DRAG_HANDLE_CLASS, dragHandle !== null);
|
|
94
97
|
let designH = (_h = (_f = options.designHeight) !== null && _f !== void 0 ? _f : (_g = group.size) === null || _g === void 0 ? void 0 : _g.height) !== null && _h !== void 0 ? _h : 0;
|
|
95
98
|
let designW = (_k = (_j = group.size) === null || _j === void 0 ? void 0 : _j.width) !== null && _k !== void 0 ? _k : 0;
|
|
96
99
|
let disposed = false;
|
|
@@ -111,6 +114,8 @@ export function bindDashboardSplit(api, group, options = {}) {
|
|
|
111
114
|
w: api.container.clientWidth || 0,
|
|
112
115
|
h: api.container.clientHeight || 0,
|
|
113
116
|
});
|
|
117
|
+
/** Reentrancy guard: our own frame write must not re-project through bounds:changed. */
|
|
118
|
+
let writing = false;
|
|
114
119
|
/** FLUID: the frame is the container, both axes. Returns true when it changed. */
|
|
115
120
|
const applyFluidFrame = () => {
|
|
116
121
|
if (!fluid || disposed)
|
|
@@ -124,7 +129,13 @@ export function bindDashboardSplit(api, group, options = {}) {
|
|
|
124
129
|
return false;
|
|
125
130
|
designW = box.w;
|
|
126
131
|
designH = height;
|
|
127
|
-
|
|
132
|
+
writing = true;
|
|
133
|
+
try {
|
|
134
|
+
diagram.runSystemWrite(() => group.setFrame({ x: f.x, y: f.y, width: box.w, height }));
|
|
135
|
+
}
|
|
136
|
+
finally {
|
|
137
|
+
writing = false;
|
|
138
|
+
}
|
|
128
139
|
return true;
|
|
129
140
|
};
|
|
130
141
|
// -- the tree ---------------------------------------------------------------
|
|
@@ -622,6 +633,16 @@ export function bindDashboardSplit(api, group, options = {}) {
|
|
|
622
633
|
return;
|
|
623
634
|
if (((_g = node.getMetadata) === null || _g === void 0 ? void 0 : _g.call(node, 'widgetMovable')) === false)
|
|
624
635
|
return;
|
|
636
|
+
// Drag-handle mode: only the handle (the caption strip by default) lifts
|
|
637
|
+
// a widget out — see pressOnDragHandle in grid-binder.
|
|
638
|
+
if (dragHandle !== null) {
|
|
639
|
+
const src = ev.source;
|
|
640
|
+
const cr = api.container.getBoundingClientRect();
|
|
641
|
+
const cx = typeof (src === null || src === void 0 ? void 0 : src.clientX) === 'number' ? src.clientX : cr.left + ev.screen.x;
|
|
642
|
+
const cy = typeof (src === null || src === void 0 ? void 0 : src.clientY) === 'number' ? src.clientY : cr.top + ev.screen.y;
|
|
643
|
+
if (!pressOnDragHandle(dragHandle, target, hostOf(node.id), cx, cy))
|
|
644
|
+
return;
|
|
645
|
+
}
|
|
625
646
|
const tree = readTree();
|
|
626
647
|
gesture = {
|
|
627
648
|
kind: 'move',
|
|
@@ -840,6 +861,32 @@ export function bindDashboardSplit(api, group, options = {}) {
|
|
|
840
861
|
api.container.addEventListener('focusin', onFocusIn);
|
|
841
862
|
api.container.addEventListener('keydown', onKey);
|
|
842
863
|
// -- observers --------------------------------------------------------------
|
|
864
|
+
// THE GROUP'S FRAME IS THE BOARD. When it moves — a view parked off-camera by
|
|
865
|
+
// showView, a container re-slotted by its parent, an external resize — the
|
|
866
|
+
// widgets must follow, exactly as the grid binder's do. Without this the
|
|
867
|
+
// parked view's hosts stayed on camera under the shown view (the Angular
|
|
868
|
+
// conformance drive: the sales cards bleeding through the ops tab). Member
|
|
869
|
+
// churn re-reconciles the tree the same way.
|
|
870
|
+
const groupSubs = [
|
|
871
|
+
group.on('bounds:changed', (() => {
|
|
872
|
+
if (disposed || writing)
|
|
873
|
+
return;
|
|
874
|
+
project();
|
|
875
|
+
api.render();
|
|
876
|
+
})),
|
|
877
|
+
group.on('member:added', (() => {
|
|
878
|
+
if (disposed)
|
|
879
|
+
return;
|
|
880
|
+
project(reconcile());
|
|
881
|
+
api.render();
|
|
882
|
+
})),
|
|
883
|
+
group.on('member:removed', (() => {
|
|
884
|
+
if (disposed)
|
|
885
|
+
return;
|
|
886
|
+
project(reconcile());
|
|
887
|
+
api.render();
|
|
888
|
+
})),
|
|
889
|
+
];
|
|
843
890
|
const containerObserver = fluid && typeof ResizeObserver !== 'undefined'
|
|
844
891
|
? new ResizeObserver(() => {
|
|
845
892
|
if (disposed)
|
|
@@ -902,6 +949,16 @@ export function bindDashboardSplit(api, group, options = {}) {
|
|
|
902
949
|
api.renderNow();
|
|
903
950
|
},
|
|
904
951
|
getStatic: () => isStatic,
|
|
952
|
+
setDragHandle(v) {
|
|
953
|
+
const sel = dragHandleSelector(v);
|
|
954
|
+
if (sel === dragHandle)
|
|
955
|
+
return;
|
|
956
|
+
dragHandle = sel;
|
|
957
|
+
cancelActiveGesture();
|
|
958
|
+
api.container.classList.toggle(DRAG_HANDLE_CLASS, dragHandle !== null);
|
|
959
|
+
api.renderNow();
|
|
960
|
+
},
|
|
961
|
+
getDragHandle: () => dragHandleValue(dragHandle),
|
|
905
962
|
focusWidget(id) {
|
|
906
963
|
var _a, _b, _c;
|
|
907
964
|
if (!((_a = group.members) !== null && _a !== void 0 ? _a : new Set()).has(id) || !diagram.getNode(id))
|
|
@@ -924,6 +981,7 @@ export function bindDashboardSplit(api, group, options = {}) {
|
|
|
924
981
|
responsive: false,
|
|
925
982
|
fluid,
|
|
926
983
|
static: isStatic,
|
|
984
|
+
dragHandle: dragHandleValue(dragHandle),
|
|
927
985
|
capacity: undefined,
|
|
928
986
|
gap,
|
|
929
987
|
padding,
|
|
@@ -1023,6 +1081,8 @@ export function bindDashboardSplit(api, group, options = {}) {
|
|
|
1023
1081
|
api.container.removeEventListener('keydown', onKey);
|
|
1024
1082
|
containerObserver === null || containerObserver === void 0 ? void 0 : containerObserver.disconnect();
|
|
1025
1083
|
hostObserver === null || hostObserver === void 0 ? void 0 : hostObserver.disconnect();
|
|
1084
|
+
for (const off of groupSubs)
|
|
1085
|
+
off();
|
|
1026
1086
|
for (const el of dividerEls)
|
|
1027
1087
|
el.remove();
|
|
1028
1088
|
dividerEls.length = 0;
|
|
@@ -177,6 +177,14 @@ const CSS = `
|
|
|
177
177
|
text-transform: uppercase;
|
|
178
178
|
color: var(--axdb-muted);
|
|
179
179
|
}
|
|
180
|
+
/* Drag-handle mode (DevExpress drags an item by its caption): the header is
|
|
181
|
+
the grip and says so with a dot pattern and a grab cursor; the body keeps
|
|
182
|
+
its own cursor because a press there starts nothing. */
|
|
183
|
+
.axdb-drag-handle .axdb-widget > .axdb-widget-h { cursor: grab; }
|
|
184
|
+
.axdb-drag-handle .axdb-widget > .axdb-widget-h::before {
|
|
185
|
+
content: ''; flex: none; width: 8px; height: 12px; opacity: .55;
|
|
186
|
+
background: radial-gradient(circle, currentColor 1.1px, transparent 1.5px) 0 0 / 4px 4px;
|
|
187
|
+
}
|
|
180
188
|
.axdb-widget-b { flex: 1; min-height: 0; position: relative; }
|
|
181
189
|
.axdb-widget-b > svg { display: block; width: 100%; height: 100%; }
|
|
182
190
|
.axdb-widget-b.axdb-scroll { overflow: auto; }
|