@grafloria/element 0.4.5 → 0.4.7
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 +12 -9
- package/src/lib/dashboard-kit/grid-binder.d.ts +38 -15
- package/src/lib/dashboard-kit/grid-binder.js +89 -33
- package/src/lib/dashboard-kit/index.d.ts +1 -1
- package/src/lib/dashboard-kit/index.js +1 -1
- package/src/lib/dashboard-kit/split-binder.js +34 -20
- package/src/lib/dashboard-kit/styles.js +27 -5
package/package.json
CHANGED
|
@@ -44,6 +44,7 @@
|
|
|
44
44
|
*/
|
|
45
45
|
import { GroupModel, NodeModel, type GridColumnLayout } from '@grafloria/engine';
|
|
46
46
|
import { type DashboardGridHandle, type DashboardGridOptions, type DashboardResponsiveOptions } from './grid-binder.js';
|
|
47
|
+
import type { DragHandleOption } from './grid-binder.js';
|
|
47
48
|
import type { SplitNode } from './split-layout.js';
|
|
48
49
|
/** A widget, declared as data. */
|
|
49
50
|
export interface DashboardWidgetSpec {
|
|
@@ -189,13 +190,15 @@ export interface DashboardOptions {
|
|
|
189
190
|
*/
|
|
190
191
|
static?: boolean;
|
|
191
192
|
/**
|
|
192
|
-
* DRAG HANDLE — DevExpress drags an item by its caption. `true`:
|
|
193
|
-
*
|
|
194
|
-
* own
|
|
195
|
-
*
|
|
196
|
-
* `
|
|
193
|
+
* DRAG HANDLE — DevExpress drags an item by its caption. `true`: the caption
|
|
194
|
+
* strip is the only handle (the header shows grip dots); a selector string:
|
|
195
|
+
* your own element inside the card; `{ grip: true, position, placement }`:
|
|
196
|
+
* a painted grip, left / center / right along the top edge, `inside` the
|
|
197
|
+
* header band or `outside` as a tab above the card; off (default): the
|
|
198
|
+
* whole card. Resize edges and the keyboard are unaffected; the body stays
|
|
199
|
+
* interactive. Live: `handle.setDragHandle()`.
|
|
197
200
|
*/
|
|
198
|
-
dragHandle?:
|
|
201
|
+
dragHandle?: DragHandleOption;
|
|
199
202
|
/**
|
|
200
203
|
* RIGHT-TO-LEFT boards: column x=0 renders at the RIGHT edge and columns run
|
|
201
204
|
* leftwards. Cells are untouched — the same `widgets` array describes the
|
|
@@ -295,9 +298,9 @@ export interface DashboardHandle {
|
|
|
295
298
|
/** Static (read-only for the pointer) mode, live — the viewer/designer switch. */
|
|
296
299
|
setStatic(on: boolean): void;
|
|
297
300
|
getStatic(): boolean;
|
|
298
|
-
/** Drag-handle mode, live, every view: `true` = the
|
|
299
|
-
setDragHandle(v:
|
|
300
|
-
getDragHandle():
|
|
301
|
+
/** Drag-handle mode, live, every view: `true` = the caption strip, a selector = your own handle, `{ grip: true, … }` = a painted grip, `false` = the whole card. */
|
|
302
|
+
setDragHandle(v: DragHandleOption): void;
|
|
303
|
+
getDragHandle(): DragHandleOption;
|
|
301
304
|
/**
|
|
302
305
|
* Add a widget to a view. CREATES the node (you do not pre-build one), wires
|
|
303
306
|
* its metadata, and commits node + membership as ONE undoable step.
|
|
@@ -214,19 +214,42 @@ export interface DashboardGridOptions {
|
|
|
214
214
|
static?: boolean;
|
|
215
215
|
/**
|
|
216
216
|
* DRAG HANDLE (DevExpress drags an item by its caption; gridstack's
|
|
217
|
-
* `handle`)
|
|
218
|
-
*
|
|
219
|
-
*
|
|
220
|
-
*
|
|
217
|
+
* `handle`). `true` — the CAPTION STRIP is the handle (the header shows grip
|
|
218
|
+
* dots); a selector string — your own element inside the host; a
|
|
219
|
+
* `{ grip: true }` object — a dedicated GRIP the kit paints, positioned
|
|
220
|
+
* left / center / right and, for the top edge, `inside` the header band or
|
|
221
|
+
* `outside` as a tab above the card (the DevExpress item bar). Off
|
|
222
|
+
* (default): the whole card. Resize edges are unaffected and the body stays
|
|
223
|
+
* interactive (scroll a table, click a legend). Live: `setDragHandle`.
|
|
221
224
|
*/
|
|
222
|
-
dragHandle?:
|
|
225
|
+
dragHandle?: DragHandleOption;
|
|
223
226
|
}
|
|
224
|
-
/**
|
|
225
|
-
export
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
227
|
+
/** A painted grip: the only drag zone, placed along the card's top edge. */
|
|
228
|
+
export interface DragGripOptions {
|
|
229
|
+
grip: true;
|
|
230
|
+
/** Where along the top edge. Default 'left'. */
|
|
231
|
+
position?: 'left' | 'center' | 'right';
|
|
232
|
+
/** In the header band, or a tab above the card. Default 'inside'. */
|
|
233
|
+
placement?: 'inside' | 'outside';
|
|
234
|
+
}
|
|
235
|
+
export type DragHandleOption = boolean | string | DragGripOptions;
|
|
236
|
+
/** The class of the painted grip element (a child of the node host). */
|
|
237
|
+
export declare const GRIP_CLASS = "axdb-grip";
|
|
238
|
+
/** The container class that turns the caption strip's grip dots on. */
|
|
229
239
|
export declare const DRAG_HANDLE_CLASS = "axdb-drag-handle";
|
|
240
|
+
/** A `dragHandle` value with its defaults filled in — the form the handle reports. */
|
|
241
|
+
export declare const normalizeDragHandle: (v: DragHandleOption | undefined) => DragHandleOption;
|
|
242
|
+
/** The selector a `dragHandle` value names — `null` when the whole card is the handle. */
|
|
243
|
+
export declare const dragHandleSelector: (v: DragHandleOption) => string | null;
|
|
244
|
+
/** The grip config of a value, or null when it paints no grip. */
|
|
245
|
+
export declare const gripOf: (v: DragHandleOption) => DragGripOptions | null;
|
|
246
|
+
/**
|
|
247
|
+
* Paint (or remove) the grip on one host, and stamp the host with the grip's
|
|
248
|
+
* placement so the header can make room for it. `movable` false = no grip.
|
|
249
|
+
*/
|
|
250
|
+
export declare function syncGrip(host: HTMLElement, cfg: DragGripOptions | null, movable: boolean): void;
|
|
251
|
+
/** The member host a press on a painted grip belongs to (the grip may sit OUTSIDE the host's box). */
|
|
252
|
+
export declare function gripHostOf(target: Element | null): HTMLElement | null;
|
|
230
253
|
/**
|
|
231
254
|
* Did a press land on the drag handle? A press INSIDE the handle element
|
|
232
255
|
* always does. The default handle is a CAPTION BAR the DevExpress way: the
|
|
@@ -266,9 +289,9 @@ export interface DashboardGridHandle {
|
|
|
266
289
|
/** Static mode, live: pointer gestures off (and handles gone) or back on. */
|
|
267
290
|
setStatic(on: boolean): void;
|
|
268
291
|
getStatic(): boolean;
|
|
269
|
-
/** Drag-handle mode, live: `true` = the
|
|
270
|
-
setDragHandle(v:
|
|
271
|
-
getDragHandle():
|
|
292
|
+
/** Drag-handle mode, live: `true` = the caption strip, a selector = your own handle, `{ grip: true, … }` = a painted grip, `false` = the whole card. */
|
|
293
|
+
setDragHandle(v: DragHandleOption): void;
|
|
294
|
+
getDragHandle(): DragHandleOption;
|
|
272
295
|
/**
|
|
273
296
|
* Move keyboard focus to a member (the roving tabindex lands on it). The
|
|
274
297
|
* host takes DOM focus when it exists; returns false for a non-member.
|
|
@@ -295,8 +318,8 @@ export interface DashboardGridHandle {
|
|
|
295
318
|
responsive: boolean;
|
|
296
319
|
fluid: boolean;
|
|
297
320
|
static: boolean;
|
|
298
|
-
/** `true` = the
|
|
299
|
-
dragHandle:
|
|
321
|
+
/** `true` = the caption strip, a selector = a custom one, `{ grip: true, … }` = a painted grip, `false` = the whole card. */
|
|
322
|
+
dragHandle: DragHandleOption;
|
|
300
323
|
/** Fit-mode row capacity (undefined when unbounded). */
|
|
301
324
|
capacity: number | undefined;
|
|
302
325
|
gap: number;
|
|
@@ -54,12 +54,49 @@ 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
|
|
58
|
-
export const
|
|
59
|
-
/** The
|
|
60
|
-
export const dragHandleValue = (sel) => sel === null ? false : sel === '.axdb-widget-h' ? true : sel;
|
|
61
|
-
/** The container class that turns the header grip on. */
|
|
57
|
+
/** The class of the painted grip element (a child of the node host). */
|
|
58
|
+
export const GRIP_CLASS = 'axdb-grip';
|
|
59
|
+
/** The container class that turns the caption strip's grip dots on. */
|
|
62
60
|
export const DRAG_HANDLE_CLASS = 'axdb-drag-handle';
|
|
61
|
+
/** A `dragHandle` value with its defaults filled in — the form the handle reports. */
|
|
62
|
+
export const normalizeDragHandle = (v) => {
|
|
63
|
+
var _a, _b;
|
|
64
|
+
return typeof v === 'object' && v !== null && v.grip
|
|
65
|
+
? { grip: true, position: (_a = v.position) !== null && _a !== void 0 ? _a : 'left', placement: (_b = v.placement) !== null && _b !== void 0 ? _b : 'inside' }
|
|
66
|
+
: v === true ? true : typeof v === 'string' && v.length > 0 ? v : false;
|
|
67
|
+
};
|
|
68
|
+
/** The selector a `dragHandle` value names — `null` when the whole card is the handle. */
|
|
69
|
+
export const dragHandleSelector = (v) => v === true ? '.axdb-widget-h' : typeof v === 'string' ? v : typeof v === 'object' ? '.' + GRIP_CLASS : null;
|
|
70
|
+
/** The grip config of a value, or null when it paints no grip. */
|
|
71
|
+
export const gripOf = (v) => (typeof v === 'object' ? v : null);
|
|
72
|
+
const sameDragHandle = (a, b) => JSON.stringify(a) === JSON.stringify(b);
|
|
73
|
+
/**
|
|
74
|
+
* Paint (or remove) the grip on one host, and stamp the host with the grip's
|
|
75
|
+
* placement so the header can make room for it. `movable` false = no grip.
|
|
76
|
+
*/
|
|
77
|
+
export function syncGrip(host, cfg, movable) {
|
|
78
|
+
var _a, _b, _c, _d, _e;
|
|
79
|
+
const existing = host.querySelector(':scope > .' + GRIP_CLASS);
|
|
80
|
+
host.classList.remove('axdb-gp-inside', 'axdb-gp-outside', 'axdb-gp-left', 'axdb-gp-center', 'axdb-gp-right');
|
|
81
|
+
if (!cfg || !movable) {
|
|
82
|
+
existing === null || existing === void 0 ? void 0 : existing.remove();
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
const el = (_a = existing) !== null && _a !== void 0 ? _a : host.ownerDocument.createElement('div');
|
|
86
|
+
if (!existing) {
|
|
87
|
+
el.setAttribute('aria-hidden', 'true');
|
|
88
|
+
el.setAttribute('title', 'Drag');
|
|
89
|
+
host.appendChild(el);
|
|
90
|
+
}
|
|
91
|
+
el.className = `${GRIP_CLASS} ${GRIP_CLASS}--${(_b = cfg.position) !== null && _b !== void 0 ? _b : 'left'} ${GRIP_CLASS}--${(_c = cfg.placement) !== null && _c !== void 0 ? _c : 'inside'}`;
|
|
92
|
+
host.classList.add(`axdb-gp-${(_d = cfg.placement) !== null && _d !== void 0 ? _d : 'inside'}`, `axdb-gp-${(_e = cfg.position) !== null && _e !== void 0 ? _e : 'left'}`);
|
|
93
|
+
}
|
|
94
|
+
/** The member host a press on a painted grip belongs to (the grip may sit OUTSIDE the host's box). */
|
|
95
|
+
export function gripHostOf(target) {
|
|
96
|
+
var _a, _b;
|
|
97
|
+
const grip = (_a = target === null || target === void 0 ? void 0 : target.closest) === null || _a === void 0 ? void 0 : _a.call(target, '.' + GRIP_CLASS);
|
|
98
|
+
return (_b = grip === null || grip === void 0 ? void 0 : grip.closest('.grafloria-node-host')) !== null && _b !== void 0 ? _b : null;
|
|
99
|
+
}
|
|
63
100
|
/**
|
|
64
101
|
* Did a press land on the drag handle? A press INSIDE the handle element
|
|
65
102
|
* always does. The default handle is a CAPTION BAR the DevExpress way: the
|
|
@@ -208,8 +245,9 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
208
245
|
const fluid = options.fluid === true;
|
|
209
246
|
const overflow = (_h = options.overflow) !== null && _h !== void 0 ? _h : 'bounded';
|
|
210
247
|
let isStatic = options.static === true;
|
|
211
|
-
let dragHandle =
|
|
212
|
-
|
|
248
|
+
let dragHandle = normalizeDragHandle(options.dragHandle);
|
|
249
|
+
let dragSel = dragHandleSelector(dragHandle);
|
|
250
|
+
api.container.classList.toggle(DRAG_HANDLE_CLASS, dragHandle === true);
|
|
213
251
|
/** The member the ROVING TABINDEX rests on (one tab stop per board). */
|
|
214
252
|
let focusedId;
|
|
215
253
|
const live = liveRegionFor(api.container);
|
|
@@ -598,11 +636,18 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
598
636
|
host.setAttribute('tabindex', id === stop ? '0' : '-1');
|
|
599
637
|
}
|
|
600
638
|
};
|
|
639
|
+
/**
|
|
640
|
+
* The chrome on every member host: the painted grip (or none) and the corner
|
|
641
|
+
* resize handle — ONE host lookup per member, which the host observer's
|
|
642
|
+
* budget counts (a repaint of one host must cost that host's lookup, not a
|
|
643
|
+
* second pass).
|
|
644
|
+
*/
|
|
601
645
|
const syncHandles = (only) => {
|
|
602
|
-
var _a, _b, _c;
|
|
646
|
+
var _a, _b, _c, _d, _e;
|
|
603
647
|
syncA11y(only);
|
|
604
|
-
if (
|
|
648
|
+
if (disposed)
|
|
605
649
|
return;
|
|
650
|
+
const grip = gripOf(dragHandle);
|
|
606
651
|
for (const id of (_a = group.members) !== null && _a !== void 0 ? _a : []) {
|
|
607
652
|
if (only && !only.has(id))
|
|
608
653
|
continue;
|
|
@@ -612,8 +657,11 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
612
657
|
const host = hostOf(id);
|
|
613
658
|
if (!host)
|
|
614
659
|
continue;
|
|
660
|
+
syncGrip(host, grip, ((_b = node.state) === null || _b === void 0 ? void 0 : _b.locked) !== true && !isStatic && ((_c = node.getMetadata) === null || _c === void 0 ? void 0 : _c.call(node, 'widgetMovable')) !== false);
|
|
661
|
+
if (!wantHandles)
|
|
662
|
+
continue;
|
|
615
663
|
const existing = host.querySelector(':scope > .axdb-rs');
|
|
616
|
-
if (((
|
|
664
|
+
if (((_d = node.state) === null || _d === void 0 ? void 0 : _d.locked) === true || isStatic || ((_e = node.getMetadata) === null || _e === void 0 ? void 0 : _e.call(node, 'widgetResizable')) === false) {
|
|
617
665
|
existing === null || existing === void 0 ? void 0 : existing.remove();
|
|
618
666
|
continue;
|
|
619
667
|
}
|
|
@@ -1626,29 +1674,35 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1626
1674
|
return insideMemberGroupFrame(ev.world.x, ev.world.y) || worldInsideBoard(ev.world.x, ev.world.y);
|
|
1627
1675
|
},
|
|
1628
1676
|
onPointerDown(ev, hit) {
|
|
1629
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
|
|
1677
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
|
|
1630
1678
|
if (gesture)
|
|
1631
1679
|
return; // mid-palette
|
|
1632
|
-
|
|
1680
|
+
const target = ((_b = (_a = ev.source) === null || _a === void 0 ? void 0 : _a.target) !== null && _b !== void 0 ? _b : null);
|
|
1681
|
+
// A press on a painted grip names its widget by the DOM: an OUTSIDE tab
|
|
1682
|
+
// sits above the card's box, where the hit test sees the gap or the
|
|
1683
|
+
// neighbour above.
|
|
1684
|
+
const gripHost = gripHostOf(target);
|
|
1685
|
+
const gripId = (_c = gripHost === null || gripHost === void 0 ? void 0 : gripHost.getAttribute('data-node-id')) !== null && _c !== void 0 ? _c : null;
|
|
1686
|
+
const onGrip = !!gripId && ((_d = group.members) !== null && _d !== void 0 ? _d : new Set()).has(gripId);
|
|
1687
|
+
if (!hit.node && !onGrip) {
|
|
1633
1688
|
// The board's own empty area: a void click. Nothing to drag, and the
|
|
1634
1689
|
// selection clears exactly as a click outside any board would.
|
|
1635
|
-
(
|
|
1690
|
+
(_f = (_e = diagram).clearSelection) === null || _f === void 0 ? void 0 : _f.call(_e);
|
|
1636
1691
|
api.render();
|
|
1637
1692
|
return;
|
|
1638
1693
|
}
|
|
1639
|
-
const node = diagram.getNode(hit.node.id);
|
|
1640
|
-
if (!node || ((
|
|
1694
|
+
const node = diagram.getNode(onGrip ? gripId : hit.node.id);
|
|
1695
|
+
if (!node || ((_g = node.state) === null || _g === void 0 ? void 0 : _g.locked) === true)
|
|
1641
1696
|
return; // pinned: refuse; click still focuses
|
|
1642
1697
|
if (isStatic)
|
|
1643
1698
|
return; // a static board: claimed and deadened, click still focuses
|
|
1644
1699
|
// Which edges did the press take? The corner handle names its own (s+e,
|
|
1645
1700
|
// or s+w on RTL); a bare press within EDGE_GRIP of the tile's border
|
|
1646
|
-
// takes that border; anywhere else is a move.
|
|
1647
|
-
const
|
|
1648
|
-
const onHandle = !!((_f = target === null || target === void 0 ? void 0 : target.closest) === null || _f === void 0 ? void 0 : _f.call(target, '.axdb-rs'));
|
|
1701
|
+
// takes that border; anywhere else is a move. A grip press is a move.
|
|
1702
|
+
const onHandle = !!((_h = target === null || target === void 0 ? void 0 : target.closest) === null || _h === void 0 ? void 0 : _h.call(target, '.axdb-rs'));
|
|
1649
1703
|
const hostEl = hostOf(node.id);
|
|
1650
|
-
const resizable = ((
|
|
1651
|
-
const movable = ((
|
|
1704
|
+
const resizable = ((_j = node.getMetadata) === null || _j === void 0 ? void 0 : _j.call(node, 'widgetResizable')) !== false;
|
|
1705
|
+
const movable = ((_k = node.getMetadata) === null || _k === void 0 ? void 0 : _k.call(node, 'widgetMovable')) !== false;
|
|
1652
1706
|
// `ev.screen` is ELEMENT-LOCAL px; host rects are in client px. Compare
|
|
1653
1707
|
// like with like — the source event's clientX/Y when there is one, else
|
|
1654
1708
|
// the container's origin plus the local offset.
|
|
@@ -1657,7 +1711,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1657
1711
|
const cx = typeof (src === null || src === void 0 ? void 0 : src.clientX) === 'number' ? src.clientX : cr.left + ev.screen.x;
|
|
1658
1712
|
const cy = typeof (src === null || src === void 0 ? void 0 : src.clientY) === 'number' ? src.clientY : cr.top + ev.screen.y;
|
|
1659
1713
|
let edges = NO_EDGES;
|
|
1660
|
-
if (resizable) {
|
|
1714
|
+
if (resizable && !onGrip) {
|
|
1661
1715
|
if (onHandle)
|
|
1662
1716
|
edges = rtl ? { n: false, e: false, s: true, w: true } : { n: false, e: true, s: true, w: false };
|
|
1663
1717
|
else if (hostEl)
|
|
@@ -1666,11 +1720,11 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1666
1720
|
const isResize = anyEdge(edges);
|
|
1667
1721
|
if (!isResize && !movable)
|
|
1668
1722
|
return; // a fixed tile: refuse the drag, click still focuses
|
|
1669
|
-
// Drag-handle mode: only a press on the handle (the caption strip
|
|
1670
|
-
//
|
|
1671
|
-
// drag) but starts nothing, so the body keeps
|
|
1672
|
-
// table scrolls, a legend clicks.
|
|
1673
|
-
if (!isResize &&
|
|
1723
|
+
// Drag-handle mode: only a press on the handle (the caption strip, a
|
|
1724
|
+
// custom element, or the painted grip) moves the tile. Anywhere else the
|
|
1725
|
+
// press is claimed (no group drag) but starts nothing, so the body keeps
|
|
1726
|
+
// its own behaviour — a table scrolls, a legend clicks.
|
|
1727
|
+
if (!isResize && dragSel !== null && !pressOnDragHandle(dragSel, target, hostEl, cx, cy))
|
|
1674
1728
|
return;
|
|
1675
1729
|
// Arm the glide class NOW, a full task before any displacement can
|
|
1676
1730
|
// happen: a transition defined in the same style recalc as the first
|
|
@@ -1696,7 +1750,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1696
1750
|
startSize: { width: node.size.width, height: node.size.height },
|
|
1697
1751
|
startPos: { x: node.position.x, y: node.position.y },
|
|
1698
1752
|
edges,
|
|
1699
|
-
spans: { w: (
|
|
1753
|
+
spans: { w: (_l = it === null || it === void 0 ? void 0 : it.w) !== null && _l !== void 0 ? _l : 1, h: (_m = it === null || it === void 0 ? void 0 : it.h) !== null && _m !== void 0 ? _m : 1 },
|
|
1700
1754
|
removedFromBoard: false,
|
|
1701
1755
|
leg: null,
|
|
1702
1756
|
lastWorld: null,
|
|
@@ -2148,16 +2202,18 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2148
2202
|
},
|
|
2149
2203
|
getStatic: () => isStatic,
|
|
2150
2204
|
setDragHandle(v) {
|
|
2151
|
-
const
|
|
2152
|
-
if (
|
|
2205
|
+
const next = normalizeDragHandle(v);
|
|
2206
|
+
if (sameDragHandle(next, dragHandle))
|
|
2153
2207
|
return;
|
|
2154
|
-
dragHandle =
|
|
2208
|
+
dragHandle = next;
|
|
2209
|
+
dragSel = dragHandleSelector(next);
|
|
2155
2210
|
if (gesture)
|
|
2156
2211
|
cancelActiveGesture(false);
|
|
2157
|
-
api.container.classList.toggle(DRAG_HANDLE_CLASS, dragHandle
|
|
2212
|
+
api.container.classList.toggle(DRAG_HANDLE_CLASS, dragHandle === true);
|
|
2213
|
+
syncHandles();
|
|
2158
2214
|
api.renderNow();
|
|
2159
2215
|
},
|
|
2160
|
-
getDragHandle: () =>
|
|
2216
|
+
getDragHandle: () => (typeof dragHandle === 'object' ? Object.assign({}, dragHandle) : dragHandle),
|
|
2161
2217
|
saveLayout() {
|
|
2162
2218
|
const saved = engine.saveLayout();
|
|
2163
2219
|
return {
|
|
@@ -2198,7 +2254,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2198
2254
|
responsive: !!responsive && !responsivePinned,
|
|
2199
2255
|
fluid,
|
|
2200
2256
|
static: isStatic,
|
|
2201
|
-
dragHandle:
|
|
2257
|
+
dragHandle: typeof dragHandle === 'object' ? Object.assign({}, dragHandle) : dragHandle,
|
|
2202
2258
|
capacity,
|
|
2203
2259
|
gap,
|
|
2204
2260
|
padding,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { bindDashboardGrid, type DashboardGridApi, type DashboardGridOptions, type DashboardGridHandle, type DashboardResponsiveOptions, } from './grid-binder.js';
|
|
1
|
+
export { bindDashboardGrid, type DashboardGridApi, type DashboardGridOptions, type DashboardGridHandle, type DashboardResponsiveOptions, type DragHandleOption, type DragGripOptions, normalizeDragHandle, } from './grid-binder.js';
|
|
2
2
|
export { bindDashboardSplit, SetSplitTreeCommand, SPLIT_TREE_KEY, type DashboardSplitOptions, type DashboardSplitHandle, } from './split-binder.js';
|
|
3
3
|
export { projectSplit, dividersOf, addSplitLeaf, removeSplitLeaf, insertSplitLeaf, moveSplitDivider, splitFromCells, cellsFromSplit, splitLeaves, type SplitNode, type SplitGroup, type SplitLeaf, type SplitDir, type SplitSide, type SplitDivider, } from './split-layout.js';
|
|
4
4
|
export { rowHeightFor, boardHeightFor, columnUnitFor, cellToRect, pointToCell, sizeToSpan, spanWidthPx, gridItemFromCell, cellFromGridItem, buildCommitCommands, type CellRect, type WorldRect, type DashboardGridGeometry, type TileDelta, } from './grid-mapping.js';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { bindDashboardGrid, } from './grid-binder.js';
|
|
1
|
+
export { bindDashboardGrid, normalizeDragHandle, } from './grid-binder.js';
|
|
2
2
|
export { bindDashboardSplit, SetSplitTreeCommand, SPLIT_TREE_KEY, } from './split-binder.js';
|
|
3
3
|
export { projectSplit, dividersOf, addSplitLeaf, removeSplitLeaf, insertSplitLeaf, moveSplitDivider, splitFromCells, cellsFromSplit, splitLeaves, } from './split-layout.js';
|
|
4
4
|
export { rowHeightFor, boardHeightFor, columnUnitFor, cellToRect, pointToCell, sizeToSpan, spanWidthPx, gridItemFromCell, cellFromGridItem, buildCommitCommands, } from './grid-mapping.js';
|
|
@@ -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,
|
|
28
|
+
import { dragHandleSelector, gripHostOf, gripOf, normalizeDragHandle, pressOnDragHandle, 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';
|
|
@@ -92,8 +92,9 @@ export function bindDashboardSplit(api, group, options = {}) {
|
|
|
92
92
|
const fluid = options.fluid === true;
|
|
93
93
|
let rtl = options.rtl === true;
|
|
94
94
|
let isStatic = options.static === true;
|
|
95
|
-
let dragHandle =
|
|
96
|
-
|
|
95
|
+
let dragHandle = normalizeDragHandle(options.dragHandle);
|
|
96
|
+
let dragSel = dragHandleSelector(dragHandle);
|
|
97
|
+
api.container.classList.toggle(DRAG_HANDLE_CLASS, dragHandle === true);
|
|
97
98
|
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;
|
|
98
99
|
let designW = (_k = (_j = group.size) === null || _j === void 0 ? void 0 : _j.width) !== null && _k !== void 0 ? _k : 0;
|
|
99
100
|
let disposed = false;
|
|
@@ -369,7 +370,7 @@ export function bindDashboardSplit(api, group, options = {}) {
|
|
|
369
370
|
return `${Math.round((r.width / f.width) * 100)} percent wide, ${Math.round((r.height / f.height) * 100)} percent tall`;
|
|
370
371
|
};
|
|
371
372
|
const syncA11y = () => {
|
|
372
|
-
var _a, _b;
|
|
373
|
+
var _a, _b, _c, _d;
|
|
373
374
|
if (disposed)
|
|
374
375
|
return;
|
|
375
376
|
const order = splitLeaves(paintedTree()).filter((id) => !!diagram.getNode(id));
|
|
@@ -377,6 +378,13 @@ export function bindDashboardSplit(api, group, options = {}) {
|
|
|
377
378
|
// that carried the grid's handle (a board switched live) sheds it here.
|
|
378
379
|
for (const id of order)
|
|
379
380
|
(_b = (_a = hostOf(id)) === null || _a === void 0 ? void 0 : _a.querySelector(':scope > .axdb-rs')) === null || _b === void 0 ? void 0 : _b.remove();
|
|
381
|
+
// The painted grip (or none), on every leaf host.
|
|
382
|
+
for (const id of order) {
|
|
383
|
+
const host = hostOf(id);
|
|
384
|
+
const node = diagram.getNode(id);
|
|
385
|
+
if (host && node)
|
|
386
|
+
syncGrip(host, gripOf(dragHandle), ((_c = node.state) === null || _c === void 0 ? void 0 : _c.locked) !== true && !isStatic && ((_d = node.getMetadata) === null || _d === void 0 ? void 0 : _d.call(node, 'widgetMovable')) !== false);
|
|
387
|
+
}
|
|
380
388
|
if (focusedId && !order.includes(focusedId))
|
|
381
389
|
focusedId = undefined;
|
|
382
390
|
const stop = focusedId !== null && focusedId !== void 0 ? focusedId : order[0];
|
|
@@ -592,7 +600,7 @@ export function bindDashboardSplit(api, group, options = {}) {
|
|
|
592
600
|
return worldInsideBoard(ev.world.x, ev.world.y);
|
|
593
601
|
},
|
|
594
602
|
onPointerDown(ev, hit) {
|
|
595
|
-
var _a, _b, _c, _d, _e, _f, _g;
|
|
603
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
|
|
596
604
|
if (gesture)
|
|
597
605
|
return;
|
|
598
606
|
const target = ((_b = (_a = ev.source) === null || _a === void 0 ? void 0 : _a.target) !== null && _b !== void 0 ? _b : null);
|
|
@@ -623,24 +631,28 @@ export function bindDashboardSplit(api, group, options = {}) {
|
|
|
623
631
|
api.container.style.cursor = d.dir === 'row' ? 'col-resize' : 'row-resize';
|
|
624
632
|
return;
|
|
625
633
|
}
|
|
626
|
-
|
|
627
|
-
|
|
634
|
+
// A press on a painted grip names its widget by the DOM (an outside tab
|
|
635
|
+
// sits above the card's box) — see grid-binder.
|
|
636
|
+
const gripId = (_e = (_d = gripHostOf(target)) === null || _d === void 0 ? void 0 : _d.getAttribute('data-node-id')) !== null && _e !== void 0 ? _e : null;
|
|
637
|
+
const onGrip = !!gripId && ((_f = group.members) !== null && _f !== void 0 ? _f : new Set()).has(gripId);
|
|
638
|
+
if (!hit.node && !onGrip) {
|
|
639
|
+
(_h = (_g = diagram).clearSelection) === null || _h === void 0 ? void 0 : _h.call(_g);
|
|
628
640
|
api.render();
|
|
629
641
|
return;
|
|
630
642
|
}
|
|
631
|
-
const node = diagram.getNode(hit.node.id);
|
|
632
|
-
if (!node || ((
|
|
643
|
+
const node = diagram.getNode(onGrip ? gripId : hit.node.id);
|
|
644
|
+
if (!node || ((_j = node.state) === null || _j === void 0 ? void 0 : _j.locked) === true || isStatic)
|
|
633
645
|
return;
|
|
634
|
-
if (((
|
|
646
|
+
if (((_k = node.getMetadata) === null || _k === void 0 ? void 0 : _k.call(node, 'widgetMovable')) === false)
|
|
635
647
|
return;
|
|
636
|
-
// Drag-handle mode: only the handle (the caption strip
|
|
637
|
-
// a widget out — see pressOnDragHandle
|
|
638
|
-
if (
|
|
648
|
+
// Drag-handle mode: only the handle (the caption strip, a custom element
|
|
649
|
+
// or the painted grip) lifts a widget out — see pressOnDragHandle.
|
|
650
|
+
if (dragSel !== null) {
|
|
639
651
|
const src = ev.source;
|
|
640
652
|
const cr = api.container.getBoundingClientRect();
|
|
641
653
|
const cx = typeof (src === null || src === void 0 ? void 0 : src.clientX) === 'number' ? src.clientX : cr.left + ev.screen.x;
|
|
642
654
|
const cy = typeof (src === null || src === void 0 ? void 0 : src.clientY) === 'number' ? src.clientY : cr.top + ev.screen.y;
|
|
643
|
-
if (!pressOnDragHandle(
|
|
655
|
+
if (!pressOnDragHandle(dragSel, target, hostOf(node.id), cx, cy))
|
|
644
656
|
return;
|
|
645
657
|
}
|
|
646
658
|
const tree = readTree();
|
|
@@ -950,15 +962,17 @@ export function bindDashboardSplit(api, group, options = {}) {
|
|
|
950
962
|
},
|
|
951
963
|
getStatic: () => isStatic,
|
|
952
964
|
setDragHandle(v) {
|
|
953
|
-
const
|
|
954
|
-
if (
|
|
965
|
+
const next = normalizeDragHandle(v);
|
|
966
|
+
if (JSON.stringify(next) === JSON.stringify(dragHandle))
|
|
955
967
|
return;
|
|
956
|
-
dragHandle =
|
|
968
|
+
dragHandle = next;
|
|
969
|
+
dragSel = dragHandleSelector(next);
|
|
957
970
|
cancelActiveGesture();
|
|
958
|
-
api.container.classList.toggle(DRAG_HANDLE_CLASS, dragHandle
|
|
971
|
+
api.container.classList.toggle(DRAG_HANDLE_CLASS, dragHandle === true);
|
|
972
|
+
syncA11y();
|
|
959
973
|
api.renderNow();
|
|
960
974
|
},
|
|
961
|
-
getDragHandle: () =>
|
|
975
|
+
getDragHandle: () => (typeof dragHandle === 'object' ? Object.assign({}, dragHandle) : dragHandle),
|
|
962
976
|
focusWidget(id) {
|
|
963
977
|
var _a, _b, _c;
|
|
964
978
|
if (!((_a = group.members) !== null && _a !== void 0 ? _a : new Set()).has(id) || !diagram.getNode(id))
|
|
@@ -981,7 +995,7 @@ export function bindDashboardSplit(api, group, options = {}) {
|
|
|
981
995
|
responsive: false,
|
|
982
996
|
fluid,
|
|
983
997
|
static: isStatic,
|
|
984
|
-
dragHandle:
|
|
998
|
+
dragHandle: typeof dragHandle === 'object' ? Object.assign({}, dragHandle) : dragHandle,
|
|
985
999
|
capacity: undefined,
|
|
986
1000
|
gap,
|
|
987
1001
|
padding,
|
|
@@ -37,7 +37,7 @@ const CSS = `
|
|
|
37
37
|
.grafloria-html-layer > .grafloria-node-host:focus-visible {
|
|
38
38
|
outline: 2px solid #3b52d9;
|
|
39
39
|
outline-offset: 2px;
|
|
40
|
-
border-radius: var(--axdb-rs-radius,
|
|
40
|
+
border-radius: var(--axdb-rs-radius, 3px);
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
/* ===== the held tile: transition-exempt ghost, above everything ===== */
|
|
@@ -55,7 +55,7 @@ const CSS = `
|
|
|
55
55
|
/* ===== the placeholder: dashed slab, truthful, never animated ===== */
|
|
56
56
|
.grafloria-html-layer > .axdb-ph {
|
|
57
57
|
position: absolute;
|
|
58
|
-
border-radius: var(--axdb-rs-radius,
|
|
58
|
+
border-radius: var(--axdb-rs-radius, 3px);
|
|
59
59
|
background: rgba(30, 34, 45, .14);
|
|
60
60
|
border: 2px dashed rgba(30, 34, 45, .28);
|
|
61
61
|
box-sizing: border-box;
|
|
@@ -77,7 +77,7 @@ const CSS = `
|
|
|
77
77
|
cursor: nwse-resize;
|
|
78
78
|
border-right: 3px solid rgba(120, 130, 148, .55);
|
|
79
79
|
border-bottom: 3px solid rgba(120, 130, 148, .55);
|
|
80
|
-
border-bottom-right-radius: var(--axdb-rs-radius,
|
|
80
|
+
border-bottom-right-radius: var(--axdb-rs-radius, 3px);
|
|
81
81
|
opacity: 0;
|
|
82
82
|
transition: opacity .12s;
|
|
83
83
|
z-index: 5;
|
|
@@ -95,7 +95,7 @@ const CSS = `
|
|
|
95
95
|
border-right: none;
|
|
96
96
|
border-left: 3px solid rgba(120, 130, 148, .55);
|
|
97
97
|
border-bottom-right-radius: 0;
|
|
98
|
-
border-bottom-left-radius: var(--axdb-rs-radius,
|
|
98
|
+
border-bottom-left-radius: var(--axdb-rs-radius, 3px);
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
/* ===== palette drag-in chip (screen-space clone following the cursor) ===== */
|
|
@@ -144,7 +144,7 @@ const CSS = `
|
|
|
144
144
|
padding: 13px 15px 12px;
|
|
145
145
|
background: var(--axdb-card);
|
|
146
146
|
border: 1px solid var(--axdb-line);
|
|
147
|
-
border-radius: var(--axdb-rs-radius,
|
|
147
|
+
border-radius: var(--axdb-rs-radius, 3px);
|
|
148
148
|
box-shadow: 0 1px 2px rgba(16, 24, 40, .05), 0 1px 3px rgba(16, 24, 40, .05);
|
|
149
149
|
font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
|
|
150
150
|
color: var(--axdb-ink);
|
|
@@ -185,6 +185,28 @@ const CSS = `
|
|
|
185
185
|
content: ''; flex: none; width: 8px; height: 12px; opacity: .55;
|
|
186
186
|
background: radial-gradient(circle, currentColor 1.1px, transparent 1.5px) 0 0 / 4px 4px;
|
|
187
187
|
}
|
|
188
|
+
/* A PAINTED GRIP (dragHandle: { grip: true }): the only drag zone, a small
|
|
189
|
+
dotted tab along the card's top edge. Inside sits in the header band and
|
|
190
|
+
the header makes room for it; outside is a tab above the card, the
|
|
191
|
+
DevExpress item bar. The host must not clip it. */
|
|
192
|
+
.grafloria-node-host > .axdb-grip {
|
|
193
|
+
position: absolute; z-index: 4; box-sizing: border-box; width: 24px; height: 12px;
|
|
194
|
+
border: 1px solid var(--axdb-line, #e7eaf1); border-radius: 3px; background: var(--axdb-card, #fff);
|
|
195
|
+
color: var(--axdb-muted, #5a6478); cursor: grab; opacity: .8;
|
|
196
|
+
}
|
|
197
|
+
.grafloria-node-host > .axdb-grip::before {
|
|
198
|
+
content: ''; position: absolute; left: 5px; top: 2px; width: 12px; height: 6px;
|
|
199
|
+
background: radial-gradient(circle, currentColor 1px, transparent 1.4px) 0 0 / 4px 3px;
|
|
200
|
+
}
|
|
201
|
+
.grafloria-node-host:hover > .axdb-grip, .grafloria-node-host > .axdb-grip:hover { opacity: 1; border-color: #3b52d9; color: #3b52d9; }
|
|
202
|
+
.grafloria-node-host > .axdb-grip--inside { top: 5px; }
|
|
203
|
+
.grafloria-node-host > .axdb-grip--outside { top: -11px; height: 11px; border-bottom-left-radius: 0; border-bottom-right-radius: 0; border-bottom: 0; box-shadow: 0 -1px 2px rgba(16, 24, 40, .08); }
|
|
204
|
+
.grafloria-node-host > .axdb-grip--left { left: 8px; }
|
|
205
|
+
.grafloria-node-host > .axdb-grip--right { right: 8px; }
|
|
206
|
+
.grafloria-node-host > .axdb-grip--center { left: 50%; transform: translateX(-50%); }
|
|
207
|
+
.grafloria-node-host.axdb-gp-inside.axdb-gp-left .axdb-widget > .axdb-widget-h { padding-left: 24px; }
|
|
208
|
+
.grafloria-node-host.axdb-gp-inside.axdb-gp-right .axdb-widget > .axdb-widget-h { padding-right: 24px; }
|
|
209
|
+
.grafloria-node-host.axdb-gp-inside.axdb-gp-center .axdb-widget > .axdb-widget-h { padding-top: 10px; }
|
|
188
210
|
.axdb-widget-b { flex: 1; min-height: 0; position: relative; }
|
|
189
211
|
.axdb-widget-b > svg { display: block; width: 100%; height: 100%; }
|
|
190
212
|
.axdb-widget-b.axdb-scroll { overflow: auto; }
|