@grafloria/element 0.4.19 → 0.4.21

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.19",
3
+ "version": "0.4.21",
4
4
  "type": "module",
5
5
  "main": "./src/index.js",
6
6
  "types": "./src/index.d.ts",
@@ -513,14 +513,19 @@ interface AdoptedLeg {
513
513
  * unregister. A split board adopts nothing and grows no slab: its `adopt`
514
514
  * answers null and `resizeMemberBy` answers unchanged.
515
515
  */
516
+ /** The board on the canvas that holds `groupId` as a member — a nested board's parent. */
517
+ export declare function parentPeerOf(container: HTMLElement, groupId: string): BinderPeer | null;
516
518
  /** Clear the selection on every OTHER board of the canvas — one selection per canvas. */
517
519
  export declare function clearOtherSelections(container: HTMLElement, self: BinderPeer | null): void;
518
520
  export declare function registerBoardPeer(container: HTMLElement, peer: BinderPeer): () => void;
519
521
  export type { BinderPeer };
520
- interface ResizeEdges {
522
+ /** A press this close (CSS px) to a tile's border takes that edge for a resize. */
523
+ export declare const EDGE_GRIP = 7;
524
+ export interface ResizeEdges {
521
525
  n: boolean;
522
526
  e: boolean;
523
527
  s: boolean;
524
528
  w: boolean;
525
529
  }
530
+ export declare const anyEdge: (E: ResizeEdges) => boolean;
526
531
  export declare function bindDashboardGrid(api: DashboardGridApi, group: GroupModel, options?: DashboardGridOptions): DashboardGridHandle;
@@ -153,6 +153,14 @@ 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
+ /** The board on the canvas that holds `groupId` as a member — a nested board's parent. */
157
+ export function parentPeerOf(container, groupId) {
158
+ var _a;
159
+ for (const p of (_a = BOARD_REGISTRY.get(container)) !== null && _a !== void 0 ? _a : [])
160
+ if (p.group.id !== groupId && p.hasItem(groupId))
161
+ return p;
162
+ return null;
163
+ }
156
164
  /** Clear the selection on every OTHER board of the canvas — one selection per canvas. */
157
165
  export function clearOtherSelections(container, self) {
158
166
  var _a, _b;
@@ -241,7 +249,7 @@ class SetGroupCellCommand extends Command {
241
249
  const DRAG_THRESHOLD = 4;
242
250
  const GLIDE_OFF_DELAY = 400;
243
251
  /** A press this close (CSS px) to a tile's border takes that edge for a resize. */
244
- const EDGE_GRIP = 7;
252
+ export const EDGE_GRIP = 7;
245
253
  const NO_EDGES = { n: false, e: false, s: false, w: false };
246
254
  /** Which of a host's edges a client point is within EDGE_GRIP of (none when outside). */
247
255
  function edgesNear(host, cx, cy) {
@@ -255,7 +263,7 @@ function edgesNear(host, cx, cy) {
255
263
  e: r.right - cx <= EDGE_GRIP,
256
264
  };
257
265
  }
258
- const anyEdge = (E) => E.n || E.e || E.s || E.w;
266
+ export const anyEdge = (E) => E.n || E.e || E.s || E.w;
259
267
  /** The resize cursor for a set of edges ('' when none). */
260
268
  function cursorFor(E) {
261
269
  const v = E.n || E.s;
@@ -453,7 +461,15 @@ export function bindDashboardGrid(api, group, options = {}) {
453
461
  designHeight: sizing === 'fit' ? frame().height : designH,
454
462
  rtl,
455
463
  });
456
- const rows = () => Math.max(1, engine.rows());
464
+ /**
465
+ * The rows the board is laid out in. A BOUNDED board (a section) keeps its
466
+ * bound's rows even when its content ends higher — empty rows stay empty
467
+ * rows, as in gridstack — instead of stretching the rest to fill the slab:
468
+ * that stretch made a tile pulled shorter shrink FASTER than the pointer
469
+ * (each row it gave up made the remaining rows taller, so the same pixel
470
+ * height quantised to fewer rows on the next move — kit lab L37).
471
+ */
472
+ const rows = () => Math.max(1, engine.rows(), maxRows !== null && maxRows !== void 0 ? maxRows : 0);
457
473
  const htmlLayer = () => api.container.querySelector('.grafloria-html-layer');
458
474
  const hostOf = (id) => {
459
475
  const esc = typeof CSS !== 'undefined' && CSS.escape ? CSS.escape(id) : id.replace(/"/g, '\\"');
@@ -1692,6 +1708,10 @@ export function bindDashboardGrid(api, group, options = {}) {
1692
1708
  touched = true;
1693
1709
  }
1694
1710
  }
1711
+ else if (!E.s) {
1712
+ // A top-edge pull needs the row ABOVE: the anchored resize path
1713
+ // decides it (refused when that row is taken) — never the section.
1714
+ }
1695
1715
  else if (!engine.resizeCheck(g.id, pulled.w, pulled.h + 1).changed) {
1696
1716
  const res = parent.resizeMemberBy(group.id, +1);
1697
1717
  if (res.changed) {
@@ -1720,15 +1740,22 @@ export function bindDashboardGrid(api, group, options = {}) {
1720
1740
  touched = true;
1721
1741
  }
1722
1742
  }
1723
- else if (pulled.h > 1) {
1724
- engine.resizeCheck(g.id, pulled.w, pulled.h - 1);
1743
+ else if (pulled.h > 1 && E.s) {
1744
+ // Shrink to the row the pointer asks for (one row per move lagged
1745
+ // a fast pull and overshot a slow one), then hand every row the
1746
+ // layout no longer needs back to the board.
1747
+ engine.resizeCheck(g.id, pulled.w, Math.max(1, wantRows));
1725
1748
  const floor = Math.max(designRows, extentOf(engine.getItems()));
1726
- if (slabRows > floor) {
1749
+ let slab = slabRows;
1750
+ let bound = inner;
1751
+ while (slab > floor) {
1727
1752
  const res = parent.resizeMemberBy(group.id, -1);
1728
- if (res.changed) {
1729
- record(res, -1);
1730
- setInnerRows(inner - 1);
1731
- }
1753
+ if (!res.changed)
1754
+ break;
1755
+ record(res, -1);
1756
+ slab -= 1;
1757
+ bound -= 1;
1758
+ setInnerRows(bound);
1732
1759
  }
1733
1760
  touched = true;
1734
1761
  }
@@ -1797,9 +1824,23 @@ export function bindDashboardGrid(api, group, options = {}) {
1797
1824
  const moves = tx !== itemNow.x || ty !== itemNow.y;
1798
1825
  const growing = span.w > itemNow.w || span.h > itemNow.h;
1799
1826
  let changed = false;
1800
- if (moves && growing)
1801
- changed = engine.moveCheck(g.id, tx, ty, { gate: false }).changed || changed;
1802
- changed = engine.resizeCheck(g.id, span.w, span.h).changed || changed;
1827
+ // A north or west pull grows by MOVING the anchor first; when that move
1828
+ // is refused (the row above is taken) the growth is refused with it —
1829
+ // the first version fell through to a plain resize and grew the tile at
1830
+ // the OPPOSITE edge, so pulling a control's top edge up made it taller
1831
+ // at the bottom and pushed its section a row (kit lab L37).
1832
+ let anchored = true;
1833
+ if (moves && growing) {
1834
+ // The anchor may move only into FREE cells: a move that pushes would
1835
+ // relocate the tile (a top-edge pull sent a control to row 0 and its
1836
+ // neighbours below it) instead of growing it.
1837
+ const probe = { x: tx, y: ty, w: itemNow.w, h: itemNow.h };
1838
+ const blocked = engine.getItems().some((o) => o.id !== g.id && o.x < probe.x + probe.w && probe.x < o.x + o.w && o.y < probe.y + probe.h && probe.y < o.y + o.h);
1839
+ anchored = !blocked && engine.moveCheck(g.id, tx, ty, { gate: false }).changed;
1840
+ changed = anchored || changed;
1841
+ }
1842
+ if (anchored)
1843
+ changed = engine.resizeCheck(g.id, span.w, span.h).changed || changed;
1803
1844
  if (moves && !growing)
1804
1845
  changed = engine.moveCheck(g.id, tx, ty, { gate: false }).changed || changed;
1805
1846
  if (changed)
@@ -2094,7 +2135,7 @@ export function bindDashboardGrid(api, group, options = {}) {
2094
2135
  id: `dashboard-grid:${group.id}:${++binderSeq}`,
2095
2136
  priority: 2, // point-specific claim — outranks mode-style tools (see ext/tools.ts)
2096
2137
  hitTest(ev, hit) {
2097
- var _a, _b;
2138
+ var _a, _b, _c, _d;
2098
2139
  if (disposed)
2099
2140
  return false;
2100
2141
  if (gesture || slabGesture || forwardSlab)
@@ -2117,6 +2158,17 @@ export function bindDashboardGrid(api, group, options = {}) {
2117
2158
  }
2118
2159
  return insideMemberGroupFrame(ev.world.x, ev.world.y);
2119
2160
  }
2161
+ // An EMPTY press inside a NESTED board's frame is that board's: its
2162
+ // dividers, its band, its own section press (which it hands back up).
2163
+ // Ties went to the first registered tool, and a section re-bound by a
2164
+ // layout switch registers AFTER its parent — so the parent took every
2165
+ // divider press in a split section (dead dividers) and, near the
2166
+ // section's edge, turned it into a section resize (the width changed
2167
+ // while a control's height was being dragged — Quantia, Groups page).
2168
+ for (const p of (_c = BOARD_REGISTRY.get(api.container)) !== null && _c !== void 0 ? _c : []) {
2169
+ if (p !== selfPeer && ((_d = group.members) !== null && _d !== void 0 ? _d : new Set()).has(p.group.id) && p.containsWorld(ev.world.x, ev.world.y))
2170
+ return false;
2171
+ }
2120
2172
  // Claim (and deaden) empty presses inside a member group's frame so the
2121
2173
  // built-in group-drag cannot fight the pack layout for the KPI slab —
2122
2174
  // and empty presses on the BOARD itself: its group is a layout
@@ -2126,7 +2178,7 @@ export function bindDashboardGrid(api, group, options = {}) {
2126
2178
  return insideMemberGroupFrame(ev.world.x, ev.world.y) || worldInsideBoard(ev.world.x, ev.world.y);
2127
2179
  },
2128
2180
  onPointerDown(ev, hit) {
2129
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s;
2181
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t;
2130
2182
  if (gesture)
2131
2183
  return; // mid-palette
2132
2184
  const target = ((_b = (_a = ev.source) === null || _a === void 0 ? void 0 : _a.target) !== null && _b !== void 0 ? _b : null);
@@ -2136,12 +2188,15 @@ export function bindDashboardGrid(api, group, options = {}) {
2136
2188
  const gripHost = gripHostOf(target);
2137
2189
  const gripId = (_c = gripHost === null || gripHost === void 0 ? void 0 : gripHost.getAttribute('data-node-id')) !== null && _c !== void 0 ? _c : null;
2138
2190
  const onGrip = !!gripId && ((_d = group.members) !== null && _d !== void 0 ? _d : new Set()).has(gripId);
2139
- if (!hit.node && !onGrip) {
2191
+ // A SECTION's corner handle sits on top of whatever tile shares that
2192
+ // corner; the DOM target names the section, the hit test the tile.
2193
+ const sectionHandle = (_e = target === null || target === void 0 ? void 0 : target.closest) === null || _e === void 0 ? void 0 : _e.call(target, '.axdb-slab > .axdb-rs');
2194
+ if ((!hit.node && !onGrip) || sectionHandle) {
2140
2195
  // A press on a SECTION — its empty band, its corner handle or its
2141
2196
  // 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;
2197
+ const slabHandle = (_f = target === null || target === void 0 ? void 0 : target.closest) === null || _f === void 0 ? void 0 : _f.call(target, '.axdb-slab > .axdb-rs');
2198
+ const slabId = (_h = (_g = slabHandle === null || slabHandle === void 0 ? void 0 : slabHandle.parentElement) === null || _g === void 0 ? void 0 : _g.getAttribute('data-slab-id')) !== null && _h !== void 0 ? _h : memberGroupAt(ev.world.x, ev.world.y);
2199
+ const grp = slabId && ((_j = group.members) !== null && _j !== void 0 ? _j : new Set()).has(slabId) ? diagram.getGroup(slabId) : undefined;
2145
2200
  if (slabId && grp) {
2146
2201
  selectWidget(slabId);
2147
2202
  api.render();
@@ -2160,7 +2215,7 @@ export function bindDashboardGrid(api, group, options = {}) {
2160
2215
  // forwards the pointer sequence.
2161
2216
  const parent = parentPeer();
2162
2217
  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;
2218
+ const ownHandle = ((_k = slabHandle === null || slabHandle === void 0 ? void 0 : slabHandle.parentElement) === null || _k === void 0 ? void 0 : _k.getAttribute('data-slab-id')) === group.id;
2164
2219
  parent.selectMember(group.id);
2165
2220
  if (!isStatic && parent.beginSlabResize) {
2166
2221
  const edges = ownHandle
@@ -2173,7 +2228,7 @@ export function bindDashboardGrid(api, group, options = {}) {
2173
2228
  }
2174
2229
  // The board's own empty area: a void click. Nothing to drag, and the
2175
2230
  // selection clears exactly as a click outside any board would.
2176
- (_l = (_k = diagram).clearSelection) === null || _l === void 0 ? void 0 : _l.call(_k);
2231
+ (_m = (_l = diagram).clearSelection) === null || _m === void 0 ? void 0 : _m.call(_l);
2177
2232
  selectWidget(undefined);
2178
2233
  api.render();
2179
2234
  return;
@@ -2182,17 +2237,17 @@ export function bindDashboardGrid(api, group, options = {}) {
2182
2237
  if (!node)
2183
2238
  return;
2184
2239
  selectWidget(node.id); // a press selects, whether or not it starts a gesture
2185
- if (((_m = node.state) === null || _m === void 0 ? void 0 : _m.locked) === true)
2240
+ if (((_o = node.state) === null || _o === void 0 ? void 0 : _o.locked) === true)
2186
2241
  return; // pinned: refuse; click still focuses
2187
2242
  if (isStatic)
2188
2243
  return; // a static board: claimed and deadened, click still focuses
2189
2244
  // Which edges did the press take? The corner handle names its own (s+e,
2190
2245
  // or s+w on RTL); a bare press within EDGE_GRIP of the tile's border
2191
2246
  // takes that border; anywhere else is a move. A grip press is a move.
2192
- const onHandle = !!((_o = target === null || target === void 0 ? void 0 : target.closest) === null || _o === void 0 ? void 0 : _o.call(target, '.axdb-rs'));
2247
+ const onHandle = !!((_p = target === null || target === void 0 ? void 0 : target.closest) === null || _p === void 0 ? void 0 : _p.call(target, '.axdb-rs'));
2193
2248
  const hostEl = hostOf(node.id);
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;
2249
+ const resizable = ((_q = node.getMetadata) === null || _q === void 0 ? void 0 : _q.call(node, 'widgetResizable')) !== false;
2250
+ const movable = ((_r = node.getMetadata) === null || _r === void 0 ? void 0 : _r.call(node, 'widgetMovable')) !== false;
2196
2251
  // `ev.screen` is ELEMENT-LOCAL px; host rects are in client px. Compare
2197
2252
  // like with like — the source event's clientX/Y when there is one, else
2198
2253
  // the container's origin plus the local offset.
@@ -2240,7 +2295,7 @@ export function bindDashboardGrid(api, group, options = {}) {
2240
2295
  startSize: { width: node.size.width, height: node.size.height },
2241
2296
  startPos: { x: node.position.x, y: node.position.y },
2242
2297
  edges,
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 },
2298
+ spans: { w: (_s = it === null || it === void 0 ? void 0 : it.w) !== null && _s !== void 0 ? _s : 1, h: (_t = it === null || it === void 0 ? void 0 : it.h) !== null && _t !== void 0 ? _t : 1 },
2244
2299
  removedFromBoard: false,
2245
2300
  leg: null,
2246
2301
  lastWorld: null,
@@ -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 { clearOtherSelections, dragHandleSelector, gripHostOf, gripOf, normalizeDragHandle, ownsPress, pressOnDragHandle, registerBoardPeer, syncGrip, DRAG_HANDLE_CLASS } from './grid-binder.js';
28
+ import { anyEdge, clearOtherSelections, dragHandleSelector, gripHostOf, gripOf, normalizeDragHandle, ownsPress, parentPeerOf, pressOnDragHandle, registerBoardPeer, syncGrip, DRAG_HANDLE_CLASS, EDGE_GRIP } 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';
@@ -99,6 +99,8 @@ export function bindDashboardSplit(api, group, options = {}) {
99
99
  let designW = (_k = (_j = group.size) === null || _j === void 0 ? void 0 : _j.width) !== null && _k !== void 0 ? _k : 0;
100
100
  let disposed = false;
101
101
  let gesture = null;
102
+ /** The PARENT running a resize of OUR section from a press this tool claimed. */
103
+ let forwardSlab = null;
102
104
  let focusedId;
103
105
  const live = liveRegionFor(api.container);
104
106
  // -- geometry ---------------------------------------------------------------
@@ -634,7 +636,7 @@ export function bindDashboardSplit(api, group, options = {}) {
634
636
  var _a;
635
637
  if (disposed)
636
638
  return false;
637
- if (gesture)
639
+ if (gesture || forwardSlab)
638
640
  return true;
639
641
  if (!ownsPress(api.container, diagram, ev, hit))
640
642
  return false;
@@ -643,7 +645,7 @@ export function bindDashboardSplit(api, group, options = {}) {
643
645
  return worldInsideBoard(ev.world.x, ev.world.y);
644
646
  },
645
647
  onPointerDown(ev, hit) {
646
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
648
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
647
649
  if (gesture)
648
650
  return;
649
651
  const target = ((_b = (_a = ev.source) === null || _a === void 0 ? void 0 : _a.target) !== null && _b !== void 0 ? _b : null);
@@ -678,8 +680,27 @@ export function bindDashboardSplit(api, group, options = {}) {
678
680
  // sits above the card's box) — see grid-binder.
679
681
  const gripId = (_e = (_d = gripHostOf(target)) === null || _d === void 0 ? void 0 : _d.getAttribute('data-node-id')) !== null && _e !== void 0 ? _e : null;
680
682
  const onGrip = !!gripId && ((_f = group.members) !== null && _f !== void 0 ? _f : new Set()).has(gripId);
681
- if (!hit.node && !onGrip) {
682
- (_h = (_g = diagram).clearSelection) === null || _h === void 0 ? void 0 : _h.call(_g);
683
+ const sectionHandle = (_g = target === null || target === void 0 ? void 0 : target.closest) === null || _g === void 0 ? void 0 : _g.call(target, '.axdb-slab > .axdb-rs');
684
+ if ((!hit.node && !onGrip) || sectionHandle) {
685
+ // OUR OWN empty band or corner handle, and we are a section of a
686
+ // parent board: select the section there; an edge or the handle
687
+ // starts the section resize, which the parent runs while this tool
688
+ // forwards the pointer sequence (the grid binder does the same).
689
+ const parent = parentPeerOf(api.container, group.id);
690
+ if ((parent === null || parent === void 0 ? void 0 : parent.selectMember) && (sectionHandle || worldInsideBoard(ev.world.x, ev.world.y))) {
691
+ const own = ((_h = sectionHandle === null || sectionHandle === void 0 ? void 0 : sectionHandle.parentElement) === null || _h === void 0 ? void 0 : _h.getAttribute('data-slab-id')) === group.id;
692
+ parent.selectMember(group.id);
693
+ if (!isStatic && parent.beginSlabResize) {
694
+ const f = frame();
695
+ const edges = own
696
+ ? rtl ? { n: false, e: false, s: true, w: true } : { n: false, e: true, s: true, w: false }
697
+ : { n: ev.world.y - f.y <= EDGE_GRIP, s: f.y + f.height - ev.world.y <= EDGE_GRIP, w: ev.world.x - f.x <= EDGE_GRIP, e: f.x + f.width - ev.world.x <= EDGE_GRIP };
698
+ if (anyEdge(edges) && parent.beginSlabResize(group.id, edges, ev))
699
+ forwardSlab = parent;
700
+ }
701
+ return;
702
+ }
703
+ (_k = (_j = diagram).clearSelection) === null || _k === void 0 ? void 0 : _k.call(_j);
683
704
  selectWidget(undefined);
684
705
  api.render();
685
706
  return;
@@ -688,9 +709,9 @@ export function bindDashboardSplit(api, group, options = {}) {
688
709
  if (!node)
689
710
  return;
690
711
  selectWidget(node.id); // a press selects, whether or not it starts a gesture
691
- if (((_j = node.state) === null || _j === void 0 ? void 0 : _j.locked) === true || isStatic)
712
+ if (((_l = node.state) === null || _l === void 0 ? void 0 : _l.locked) === true || isStatic)
692
713
  return;
693
- if (((_k = node.getMetadata) === null || _k === void 0 ? void 0 : _k.call(node, 'widgetMovable')) === false)
714
+ if (((_m = node.getMetadata) === null || _m === void 0 ? void 0 : _m.call(node, 'widgetMovable')) === false)
694
715
  return;
695
716
  // Drag-handle mode: only the handle (the caption strip, a custom element
696
717
  // or the painted grip) lifts a widget out — see pressOnDragHandle.
@@ -723,12 +744,27 @@ export function bindDashboardSplit(api, group, options = {}) {
723
744
  };
724
745
  },
725
746
  onPointerMove(ev) {
726
- onToolMove(ev);
747
+ var _a;
748
+ if (forwardSlab)
749
+ (_a = forwardSlab.slabMove) === null || _a === void 0 ? void 0 : _a.call(forwardSlab, ev);
750
+ else
751
+ onToolMove(ev);
727
752
  },
728
753
  onPointerUp() {
729
- onToolUp();
754
+ var _a;
755
+ if (forwardSlab) {
756
+ (_a = forwardSlab.slabUp) === null || _a === void 0 ? void 0 : _a.call(forwardSlab);
757
+ forwardSlab = null;
758
+ }
759
+ else
760
+ onToolUp();
730
761
  },
731
762
  onCancel() {
763
+ var _a;
764
+ if (forwardSlab) {
765
+ (_a = forwardSlab.slabCancel) === null || _a === void 0 ? void 0 : _a.call(forwardSlab);
766
+ forwardSlab = null;
767
+ }
732
768
  cancelActiveGesture();
733
769
  },
734
770
  };
@@ -358,9 +358,11 @@ const CSS = `
358
358
  /* SECTION CHROME: a pointer-transparent overlay on every member group. It
359
359
  wears the selection ring and, while selected, the corner handle. */
360
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; }
361
+ /* Selected, the overlay rises above the tiles so ITS corner handle wins a
362
+ corner it shares with a child's; unselected, its handle takes no presses. */
363
+ .grafloria-html-layer > .axdb-slab.axdb-slab--selected { z-index: 6; box-shadow: 0 0 0 1.5px var(--axdb-accent-ring, rgba(59, 82, 217, .55)); }
364
+ .grafloria-html-layer > .axdb-slab > .axdb-rs { pointer-events: none; opacity: 0; }
365
+ .grafloria-html-layer > .axdb-slab.axdb-slab--selected > .axdb-rs { pointer-events: auto; opacity: 1; }
364
366
  .grafloria-html-layer > .axdb-slab.axdb-slab--static > .axdb-rs { display: none; }
365
367
 
366
368
  /* legend chips, shared by line and donut */