@grafloria/element 0.4.4 → 0.4.6

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@grafloria/element",
3
- "version": "0.4.4",
3
+ "version": "0.4.6",
4
4
  "type": "module",
5
5
  "main": "./src/index.js",
6
6
  "types": "./src/index.d.ts",
@@ -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
- layout: (_h = ctx.layoutOf.get(v.id)) !== null && _h !== void 0 ? _h : layout,
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 (((_j = ctx.layoutOf.get(v.id)) !== null && _j !== void 0 ? _j : layout) === 'split' && v.tree !== undefined)
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, (_k = ctx.layoutOf.get(v.id)) !== null && _k !== void 0 ? _k : layout));
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
- (_l = ctx.attachHistory) === null || _l === void 0 ? void 0 : _l.call(ctx);
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
- function bindView(v, g, viewLayout) {
1135
- var _a, _b, _c, _d, _e, _f;
1136
- const common = Object.assign(Object.assign({ gap, padding: gap, rtl: (_a = options.rtl) !== null && _a !== void 0 ? _a : false, fluid: mode === 'fluid', static: (_b = options.static) !== null && _b !== void 0 ? _b : false }, ((_c = options.binder) !== null && _c !== void 0 ? _c : {})), { onGesture: (e) => {
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: (_d = v.columns) !== null && _d !== void 0 ? _d : columns, baseRowHeight: rowHeight, designHeight: viewH(v) }), (v.tree !== undefined ? { tree: v.tree } : {})));
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: (_e = v.columns) !== null && _e !== void 0 ? _e : columns, sizing, baseRowHeight: rowHeight, designHeight: viewH(v), float: (_f = options.float) !== null && _f !== void 0 ? _f : false, overflow }), (options.responsive ? { responsive: options.responsive } : {})));
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;
@@ -630,6 +633,16 @@ export function bindDashboardSplit(api, group, options = {}) {
630
633
  return;
631
634
  if (((_g = node.getMetadata) === null || _g === void 0 ? void 0 : _g.call(node, 'widgetMovable')) === false)
632
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
+ }
633
646
  const tree = readTree();
634
647
  gesture = {
635
648
  kind: 'move',
@@ -936,6 +949,16 @@ export function bindDashboardSplit(api, group, options = {}) {
936
949
  api.renderNow();
937
950
  },
938
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),
939
962
  focusWidget(id) {
940
963
  var _a, _b, _c;
941
964
  if (!((_a = group.members) !== null && _a !== void 0 ? _a : new Set()).has(id) || !diagram.getNode(id))
@@ -958,6 +981,7 @@ export function bindDashboardSplit(api, group, options = {}) {
958
981
  responsive: false,
959
982
  fluid,
960
983
  static: isStatic,
984
+ dragHandle: dragHandleValue(dragHandle),
961
985
  capacity: undefined,
962
986
  gap,
963
987
  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, 6px);
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, 6px);
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, 6px);
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, 6px);
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, 6px);
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);
@@ -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; }