@grafloria/element 0.4.69 → 0.4.71

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.69",
3
+ "version": "0.4.71",
4
4
  "type": "module",
5
5
  "main": "./src/index.js",
6
6
  "types": "./src/index.d.ts",
@@ -472,6 +472,13 @@ interface BinderPeer {
472
472
  cellAfter?: CellRect;
473
473
  frameBefore?: WorldRect;
474
474
  frameAfter?: WorldRect;
475
+ /**
476
+ * The parent's OWN commit for the growth so far, when a cell command
477
+ * cannot carry it — a SPLIT parent's tree with the pane's new share
478
+ * (0.4.71). Supersedes what an earlier ask answered; the arriving
479
+ * board's `finalize` carries it instead of a `SetGroupCellCommand`.
480
+ */
481
+ commands?: Command[];
475
482
  };
476
483
  containsWorld(x: number, y: number): boolean;
477
484
  /**
@@ -631,7 +638,7 @@ export interface AdoptedLeg {
631
638
  containerId: string;
632
639
  side: BesideSide;
633
640
  frame0: WorldRect;
634
- vacated: WorldRect;
641
+ vacated?: WorldRect;
635
642
  } | null;
636
643
  /**
637
644
  * Take the tile OFF the board while the pointer is somewhere the board is
@@ -2508,6 +2508,7 @@ export function bindDashboardGrid(api, group, options = {}) {
2508
2508
  let rowsAdded = 0;
2509
2509
  let firstBefore = null;
2510
2510
  let lastAfter = null;
2511
+ let parentCommands;
2511
2512
  // At most the tile's own height in rows: past that the board is not
2512
2513
  // "full", it is smaller than the thing being dropped into it.
2513
2514
  for (let i = 0; parent && !entered && i < Math.max(1, span.h); i++) {
@@ -2517,11 +2518,13 @@ export function bindDashboardGrid(api, group, options = {}) {
2517
2518
  rowsAdded += 1;
2518
2519
  firstBefore = firstBefore !== null && firstBefore !== void 0 ? firstBefore : { cell: res.cellBefore, frame: res.frameBefore };
2519
2520
  lastAfter = { cell: res.cellAfter, frame: res.frameAfter };
2521
+ if (res.commands)
2522
+ parentCommands = res.commands; // the latest ask's answer carries every row so far
2520
2523
  setLiveBound((maxRows !== null && maxRows !== void 0 ? maxRows : 0) + 1);
2521
2524
  entered = engine.add({ id: node.id, x: 0, y: engine.rows(), w: span.w, h: span.h });
2522
2525
  }
2523
2526
  if (parent && rowsAdded > 0 && firstBefore && lastAfter) {
2524
- grown = { peer: parent, rows: rowsAdded, cellBefore: firstBefore.cell, frameBefore: firstBefore.frame, cellAfter: lastAfter.cell, frameAfter: lastAfter.frame };
2527
+ grown = Object.assign({ peer: parent, rows: rowsAdded, cellBefore: firstBefore.cell, frameBefore: firstBefore.frame, cellAfter: lastAfter.cell, frameAfter: lastAfter.frame }, (parentCommands ? { commands: parentCommands } : {}));
2525
2528
  }
2526
2529
  }
2527
2530
  if (!entered) {
@@ -2663,6 +2666,7 @@ export function bindDashboardGrid(api, group, options = {}) {
2663
2666
  syncPlaceholder();
2664
2667
  },
2665
2668
  finalize: () => {
2669
+ var _a;
2666
2670
  endBeside(false); // the shift stays: the deltas below carry it
2667
2671
  const item = engine.getItem(node.id);
2668
2672
  if (!item) {
@@ -2678,7 +2682,8 @@ export function bindDashboardGrid(api, group, options = {}) {
2678
2682
  const commands = tileCommands(deltasSince(startCells, startGeom, node.id));
2679
2683
  // …and the rows this board took from its parent to hold the arrival (D4).
2680
2684
  if (grown) {
2681
- commands.push(new SetGroupCellCommand(group.id, grown.cellBefore, grown.cellAfter, grown.frameBefore, grown.frameAfter));
2685
+ // A grid parent's rows are the section's cell; a split parent's are its tree (0.4.71).
2686
+ commands.push(...((_a = grown.commands) !== null && _a !== void 0 ? _a : [new SetGroupCellCommand(group.id, grown.cellBefore, grown.cellAfter, grown.frameBefore, grown.frameAfter)]));
2682
2687
  grown = null;
2683
2688
  }
2684
2689
  engine.endGesture();
@@ -30,7 +30,7 @@ import { anyEdge, clearOtherSelections, clientPerWorldOf, dragHandleSelector, gr
30
30
  import { buildCommitCommands, cellFromGridItem } from './grid-mapping.js';
31
31
  import { SequenceCommand } from './commit.js';
32
32
  import { TAB_STRIP_HEIGHT } from './tabs.js';
33
- import { addSplitLeaf, cellsFromSplit, cloneSplit, dividersOf, groupRectsOf, insertSplitLeaf, moveSplitDivider, normalizeSplit, pathToLeaf, projectSplit, removeSplitLeaf, splitFromCells, splitLeaves, } from './split-layout.js';
33
+ import { addSplitLeaf, cellsFromSplit, cloneSplit, dividersOf, groupRectsOf, insertSplitLeaf, isSplitGroup, moveSplitDivider, nodeAt, normalizeSplit, pathToLeaf, projectSplit, removeSplitLeaf, splitFromCells, splitLeaves, } from './split-layout.js';
34
34
  import { ensureDashboardKitStyles } from './styles.js';
35
35
  import { captionKey, captionOfGroup, captionPainted, paintCaptionBand, sectionCaptionReserve, sizeCaptionBand } from './caption.js';
36
36
  /** Group metadata key the tree persists under. */
@@ -243,8 +243,8 @@ export function bindDashboardSplit(api, group, options = {}) {
243
243
  if (grp)
244
244
  diagram.runSystemWrite(() => grp.setFrame(Object.assign({}, r)));
245
245
  };
246
- /** The tree currently PAINTED — the gesture's live tree while one runs. */
247
- const paintedTree = () => ((gesture === null || gesture === void 0 ? void 0 : gesture.started) ? gesture.liveTree : readTree());
246
+ /** The tree currently PAINTED — the gesture's live tree while one runs, else a pane grown for an arriving tile (0.4.71), else the persisted one. */
247
+ const paintedTree = () => { var _a; return ((gesture === null || gesture === void 0 ? void 0 : gesture.started) ? gesture.liveTree : ((_a = grown === null || grown === void 0 ? void 0 : grown.tree) !== null && _a !== void 0 ? _a : readTree())); };
248
248
  const project = (tree = paintedTree()) => {
249
249
  const rects = rectsOf(tree);
250
250
  for (const [id, r] of rects) {
@@ -580,6 +580,253 @@ export function bindDashboardSplit(api, group, options = {}) {
580
580
  g.leg.adopted.abort();
581
581
  g.leg = null;
582
582
  };
583
+ /**
584
+ * What the zone means for the pane under the hand — the one step the widget
585
+ * drag and the palette drag share (0.4.70): a strip marks its slot (a
586
+ * widget's, never a chip's — the walk answers no strip for a chip); a band
587
+ * beside a container is a pane on that side, this board's line; a plain
588
+ * cell on a NESTED board, or on the board holding this one, hands the tile
589
+ * to that board through a leg (its own placeholder), a board that refuses
590
+ * leaving the split rule to answer; anything else is the nearest edge of
591
+ * the pane under the pointer. Answers the insertion target, or null.
592
+ */
593
+ const zoneTarget = (g, z, world, inside, px) => {
594
+ var _a, _b, _c;
595
+ if (z.kind === 'strip') {
596
+ endLeg(g);
597
+ g.beside = null;
598
+ if (!g.strip || g.strip.containerId !== z.containerId || g.strip.index !== z.index)
599
+ (_a = options.tabDrop) === null || _a === void 0 ? void 0 : _a.markDrop(z.containerId, z.index);
600
+ g.strip = { containerId: z.containerId, index: z.index };
601
+ return null;
602
+ }
603
+ endStrip(g);
604
+ const peer = z.kind === 'plain' ? ((_b = z.board.ref) !== null && _b !== void 0 ? _b : null) : null;
605
+ if (z.kind === 'beside') {
606
+ endLeg(g);
607
+ const grp = diagram.getGroup(z.containerId);
608
+ const rect = (_c = rectsOf(g.liveTree).get(z.containerId)) !== null && _c !== void 0 ? _c : (grp ? frameOfGroupW(grp) : null);
609
+ if (!rect)
610
+ return null;
611
+ g.beside = z.kept && g.beside ? g.beside : { containerId: z.containerId, side: z.side, frame0: rect };
612
+ return { id: z.containerId, side: z.side, rect };
613
+ }
614
+ g.beside = null;
615
+ if (peer && peer !== selfPeerRef) {
616
+ if (g.leg && g.leg.peer !== peer)
617
+ endLeg(g);
618
+ if (!g.leg) {
619
+ const adopted = peer.adopt({ id: g.id }, world, px, {});
620
+ if (adopted)
621
+ g.leg = { peer, adopted };
622
+ }
623
+ if (g.leg) {
624
+ g.leg.adopted.move(world);
625
+ return null;
626
+ }
627
+ }
628
+ else
629
+ endLeg(g);
630
+ return inside ? dropTargetAt(g.liveTree, world.x, world.y, g.id) : null;
631
+ };
632
+ // -- the split board as an adoption TARGET (0.4.70) ----------------------------
633
+ /**
634
+ * A tile from ANOTHER board — a widget leaving a page or a section, a
635
+ * palette chip — held over this board through a leg. Measured live on
636
+ * 0.4.69: a widget dragged out of the Filters page in split mode got no
637
+ * line and no placeholder anywhere, the ghost dimmed, every release
638
+ * snapped home — this peer's `adopt` answered null. The leg's promise is
639
+ * the insertion line on the nearest edge of the pane under the pointer;
640
+ * nothing moves until the release (the split model's rule); `finalize`
641
+ * answers the tree with the new pane, so the source's commit carries it. A
642
+ * beside on a container of this board is a pane on that side of it. An
643
+ * empty board takes the tile as its whole.
644
+ */
645
+ const adoptPane = (node, world, opts = {}) => {
646
+ if (disposed || isStatic)
647
+ return null;
648
+ const tree = readTree();
649
+ if (tree && splitLeaves(tree).includes(node.id))
650
+ return null; // a pane of this board never adopts itself
651
+ const whole = { path: [], side: 'top', rect: frame() };
652
+ const state = { target: null };
653
+ const targetAt = (w) => (!worldInsideBoard(w.x, w.y) ? null : tree ? dropTargetAt(tree, w.x, w.y) : whole);
654
+ const paint = () => {
655
+ showInsertion(state.target ? insertionRect(state.target.rect, state.target.side) : null);
656
+ api.render();
657
+ };
658
+ const after = () => {
659
+ if (!state.target)
660
+ return null;
661
+ if (!tree)
662
+ return normalizeSplit(addSplitLeaf(null, node.id, frame(), gap, padding));
663
+ const t = state.target;
664
+ const side = rtl && (t.side === 'left' || t.side === 'right') ? (t.side === 'left' ? 'right' : 'left') : t.side;
665
+ return normalizeSplit(insertSplitLeaf(tree, node.id, targetRef(t), side));
666
+ };
667
+ const cellOf = () => {
668
+ var _a;
669
+ const t = after();
670
+ return t ? ((_a = cellsFromSplit(t, columns, rowsGuess()).get(node.id)) !== null && _a !== void 0 ? _a : null) : null;
671
+ };
672
+ const rectOf = () => {
673
+ var _a;
674
+ const t = after();
675
+ return t ? ((_a = rectsOf(t).get(node.id)) !== null && _a !== void 0 ? _a : null) : null;
676
+ };
677
+ const besideOn = (containerId, side) => {
678
+ const rect = rectsOf(tree).get(containerId);
679
+ if (!rect)
680
+ return;
681
+ state.target = { id: containerId, side, rect };
682
+ paint();
683
+ };
684
+ if (opts.beside)
685
+ besideOn(opts.beside.containerId, opts.beside.side);
686
+ else {
687
+ state.target = targetAt(world);
688
+ paint();
689
+ }
690
+ return {
691
+ groupId: group.id,
692
+ move: (w) => {
693
+ state.target = targetAt(w);
694
+ paint();
695
+ },
696
+ beside: (containerId, side) => besideOn(containerId, side),
697
+ besideState: () => {
698
+ const t = state.target;
699
+ if (!t || !('id' in t) || !diagram.getGroup(t.id))
700
+ return null;
701
+ return { containerId: t.id, side: t.side, frame0: t.rect };
702
+ },
703
+ leave: () => {
704
+ state.target = null;
705
+ paint();
706
+ },
707
+ enter: (w) => {
708
+ state.target = targetAt(w);
709
+ paint();
710
+ },
711
+ place: () => false, // a pane is the tree's to size: no prescribed cell here
712
+ baseline: () => (tree ? cellsFromSplit(tree, columns, rowsGuess()) : new Map()),
713
+ cell: () => cellOf(),
714
+ rect: () => rectOf(),
715
+ abort: () => {
716
+ state.target = null;
717
+ showInsertion(null);
718
+ api.render();
719
+ },
720
+ finalize: () => {
721
+ const t = after();
722
+ const cell = cellOf();
723
+ const rect = rectOf();
724
+ state.target = null;
725
+ showInsertion(null);
726
+ if (!t || !cell || !rect)
727
+ return null;
728
+ return { commands: [new SetSplitTreeCommand(group.id, tree, t)], cell, rect };
729
+ },
730
+ };
731
+ };
732
+ // -- D4 on a split board (0.4.71): a section pane GROWS for a tile arriving by hand ------
733
+ /**
734
+ * The rows a nested GROW board asks its parent for (the grid binder's D4
735
+ * loop, one at a time). Measured live on 0.4.70: the fluid demo's
736
+ * Operations section refused a widget in split mode and the line beside it
737
+ * answered — a pane could not ask for rows. A pane's height is a share of
738
+ * its COLUMN, so the row comes from the divider above or below it: the
739
+ * pane's subtree in the nearest column ancestor grows by one row's pixels
740
+ * and that sibling gives them (never below the divider's floor — then the
741
+ * ask is refused, as a full grid parent refuses). The grown tree is painted
742
+ * live, so the section's frame follows and its board can take the tile;
743
+ * the commit is this board's own tree command, handed back for the
744
+ * arriving board's `finalize` to carry; a negative ask gives the rows
745
+ * back, and at zero the persisted tree paints again, exactly.
746
+ */
747
+ let grown = null;
748
+ class GrownTreeCommand extends SetSplitTreeCommand {
749
+ execute(context) {
750
+ super.execute(context);
751
+ grown = null;
752
+ if (!disposed)
753
+ project(readTree());
754
+ api.render();
755
+ }
756
+ undo(context) {
757
+ super.undo(context);
758
+ grown = null;
759
+ if (!disposed)
760
+ project(readTree());
761
+ api.render();
762
+ }
763
+ }
764
+ const resizePaneBy = (id, dRows) => {
765
+ var _a;
766
+ if (disposed || isStatic || !Number.isFinite(dRows) || dRows === 0)
767
+ return { changed: false };
768
+ const base = (grown === null || grown === void 0 ? void 0 : grown.id) === id ? grown.base : readTree();
769
+ if (!base)
770
+ return { changed: false };
771
+ const rowsNow = (grown === null || grown === void 0 ? void 0 : grown.id) === id ? grown.rows : 0;
772
+ const rowsNext = rowsNow + dRows;
773
+ const paintFor = (tree) => {
774
+ project(tree !== null && tree !== void 0 ? tree : readTree());
775
+ api.render();
776
+ };
777
+ const frameOf = (tree) => rectsOf(tree).get(id);
778
+ const cellOf = (tree) => cellsFromSplit(tree, columns, rowsGuess()).get(id);
779
+ if (rowsNext <= 0) {
780
+ // every row given back: the persisted tree, exactly
781
+ const before = (_a = grown === null || grown === void 0 ? void 0 : grown.tree) !== null && _a !== void 0 ? _a : base;
782
+ grown = null;
783
+ paintFor(null);
784
+ const fb = frameOf(before);
785
+ const fa = frameOf(base);
786
+ const cb = cellOf(before);
787
+ const ca = cellOf(base);
788
+ return rowsNow > 0 && fb && fa && cb && ca ? { changed: true, cellBefore: cb, cellAfter: ca, frameBefore: fb, frameAfter: fa } : { changed: false };
789
+ }
790
+ const path = pathToLeaf(base, id);
791
+ if (!path)
792
+ return { changed: false };
793
+ // the nearest COLUMN ancestor: the pane's subtree there is what grows
794
+ let groupPath = null;
795
+ let childIndex = -1;
796
+ for (let depth = path.length - 1; depth >= 0 && !groupPath; depth--) {
797
+ const gp = path.slice(0, depth);
798
+ const g = nodeAt(base, gp);
799
+ if (isSplitGroup(g) && g.dir === 'column' && g.children.length > 1) {
800
+ groupPath = gp;
801
+ childIndex = path[depth];
802
+ }
803
+ }
804
+ if (!groupPath)
805
+ return { changed: false }; // the pane already spans the board's height: nobody to take a row from
806
+ const group0 = nodeAt(base, groupPath);
807
+ if (!isSplitGroup(group0))
808
+ return { changed: false };
809
+ const key = groupPath.join('/');
810
+ const divider = dividersOf(base, frame(), gap, padding, rtl).find((d) => d.path.join('/') === key);
811
+ if (!divider || divider.length <= 0)
812
+ return { changed: false };
813
+ const px = rowsNext * (baseRowHeight + gap);
814
+ const fraction = px / divider.length;
815
+ const last = childIndex === group0.children.length - 1;
816
+ const tree = moveSplitDivider(base, groupPath, last ? childIndex - 1 : childIndex, last ? -fraction : fraction);
817
+ const fb = frameOf((grown === null || grown === void 0 ? void 0 : grown.id) === id ? grown.tree : base);
818
+ const f0 = frameOf(base);
819
+ const fa = frameOf(tree);
820
+ const cb = cellOf((grown === null || grown === void 0 ? void 0 : grown.id) === id ? grown.tree : base);
821
+ const ca = cellOf(tree);
822
+ if (!fb || !f0 || !fa || !cb || !ca)
823
+ return { changed: false };
824
+ if (fa.height < f0.height + px - 1)
825
+ return { changed: false }; // the sibling has no row to give (the divider's floor)
826
+ grown = { id, base, tree, rows: rowsNext };
827
+ paintFor(tree);
828
+ return { changed: true, cellBefore: cb, cellAfter: ca, frameBefore: fb, frameAfter: fa, commands: [new GrownTreeCommand(group.id, base, normalizeSplit(tree))] };
829
+ };
583
830
  // -- a11y -------------------------------------------------------------------
584
831
  const nameOf = (id) => {
585
832
  var _a, _b, _c, _d;
@@ -760,7 +1007,7 @@ export function bindDashboardSplit(api, group, options = {}) {
760
1007
  api.render();
761
1008
  };
762
1009
  const onToolMove = (ev) => {
763
- var _a, _b, _c, _d, _e;
1010
+ var _a, _b;
764
1011
  const g = gesture;
765
1012
  if (!g || g.kind === 'palette')
766
1013
  return;
@@ -801,53 +1048,7 @@ export function bindDashboardSplit(api, group, options = {}) {
801
1048
  const inside = worldInsideBoard(ev.world.x, ev.world.y);
802
1049
  const prev = prevWorld;
803
1050
  prevWorld = { x: ev.world.x, y: ev.world.y };
804
- const z = tileZone(g, ev, prev);
805
- let t = null;
806
- if (z.kind === 'strip') {
807
- // -- INTO A STRIP: the strip marks the slot; nothing else is painted.
808
- endLeg(g);
809
- g.beside = null;
810
- if (!g.strip || g.strip.containerId !== z.containerId || g.strip.index !== z.index)
811
- (_b = options.tabDrop) === null || _b === void 0 ? void 0 : _b.markDrop(z.containerId, z.index);
812
- g.strip = { containerId: z.containerId, index: z.index };
813
- }
814
- else {
815
- endStrip(g);
816
- const peer = z.kind === 'plain' ? (_c = z.board.ref) !== null && _c !== void 0 ? _c : null : null;
817
- if (z.kind === 'beside') {
818
- // -- BESIDE a container: a pane on that side of it — the split board's own line.
819
- endLeg(g);
820
- const grp = diagram.getGroup(z.containerId);
821
- const rect = (_d = rectsOf(g.liveTree).get(z.containerId)) !== null && _d !== void 0 ? _d : (grp ? frameOfGroupW(grp) : null);
822
- if (rect) {
823
- g.beside = z.kept && g.beside ? g.beside : { containerId: z.containerId, side: z.side, frame0: rect };
824
- t = { id: z.containerId, side: z.side, rect };
825
- }
826
- }
827
- else if (peer && peer !== selfPeerRef) {
828
- // -- INTO a nested board (a page, a section) — or out onto the board
829
- // that holds this one: that board holds the widget through a leg and
830
- // paints its own placeholder. A board that refuses (full, fit) leaves
831
- // the split rule to answer.
832
- g.beside = null;
833
- if (g.leg && g.leg.peer !== peer)
834
- endLeg(g);
835
- if (!g.leg) {
836
- const adopted = peer.adopt({ id: g.id }, ev.world, pxSizeOf(g), {});
837
- if (adopted)
838
- g.leg = { peer, adopted };
839
- }
840
- if (g.leg)
841
- g.leg.adopted.move(ev.world);
842
- else
843
- t = inside ? dropTargetAt(g.liveTree, ev.world.x, ev.world.y, g.id) : null;
844
- }
845
- else {
846
- g.beside = null;
847
- endLeg(g);
848
- t = inside ? dropTargetAt(g.liveTree, ev.world.x, ev.world.y, g.id) : null;
849
- }
850
- }
1051
+ const t = zoneTarget(g, tileZone(g, ev, prev), ev.world, inside, pxSizeOf(g));
851
1052
  g.target = t ? targetOf(t) : null;
852
1053
  showInsertion(t ? insertionRect(t.rect, t.side) : null);
853
1054
  const out = !inside &&
@@ -857,7 +1058,7 @@ export function bindDashboardSplit(api, group, options = {}) {
857
1058
  options.dragOut === 'remove' &&
858
1059
  (!options.removeZone || options.removeZone({ x: ev.screen.x, y: ev.screen.y }, { x: ev.world.x, y: ev.world.y }));
859
1060
  g.out = out;
860
- (_e = g.hostEl) === null || _e === void 0 ? void 0 : _e.classList.toggle('axdb-out', out);
1061
+ (_b = g.hostEl) === null || _b === void 0 ? void 0 : _b.classList.toggle('axdb-out', out);
861
1062
  api.render();
862
1063
  };
863
1064
  const onToolUp = () => {
@@ -1553,6 +1754,11 @@ export function bindDashboardSplit(api, group, options = {}) {
1553
1754
  window.removeEventListener('pointermove', onMove, true);
1554
1755
  window.removeEventListener('pointerup', onUp, true);
1555
1756
  };
1757
+ // The chip's size in this board's units — what a page or a section that
1758
+ // adopts it makes its tile from (the grid board does the same, 0.4.53).
1759
+ const f0 = frame();
1760
+ const colW0 = Math.max(1, (f0.width - 2 * padding - (columns - 1) * gap) / columns);
1761
+ const chipPx = { width: Math.max(1, spec.w * (colW0 + gap) - gap), height: Math.max(1, spec.h * (baseRowHeight + gap) - gap) };
1556
1762
  const onMove = (e) => {
1557
1763
  if (gesture !== g)
1558
1764
  return detach();
@@ -1561,19 +1767,39 @@ export function bindDashboardSplit(api, group, options = {}) {
1561
1767
  chip.style.top = `${e.clientY + 6}px`;
1562
1768
  }
1563
1769
  const w = toWorld(e.clientX, e.clientY);
1564
- const t = worldInsideBoard(w.x, w.y) ? dropTargetAt(g.liveTree, w.x, w.y) : null;
1770
+ const inside = worldInsideBoard(w.x, w.y);
1771
+ // Through the zones (0.4.70): a page or a section takes the chip through
1772
+ // a leg and shows its own placeholder; a container's band means a pane
1773
+ // beside it; a chip never becomes a tab (the grid's rule too).
1774
+ const ev = { type: 'move', world: w, screen: { x: e.clientX, y: e.clientY }, modifiers: { shift: false, ctrl: false, alt: false, meta: false } };
1775
+ const t = zoneTarget(g, tileZone(g, ev, null), w, inside, chipPx);
1565
1776
  g.target = t ? targetOf(t) : null;
1566
1777
  showInsertion(t ? insertionRect(t.rect, t.side) : null);
1567
- chip === null || chip === void 0 ? void 0 : chip.classList.toggle('axdb-out', !t && !!g.liveTree);
1778
+ chip === null || chip === void 0 ? void 0 : chip.classList.toggle('axdb-out', !t && !g.leg && !!g.liveTree);
1568
1779
  api.render();
1569
1780
  };
1570
1781
  const onUp = () => {
1571
- var _a, _b;
1782
+ var _a, _b, _c;
1572
1783
  detach();
1573
1784
  if (gesture !== g)
1574
1785
  return;
1575
1786
  gesture = null;
1576
1787
  teardownGesture(g);
1788
+ if (g.leg) {
1789
+ // Dropped into another board (a page, a section): its leg closes with
1790
+ // that board's displaced tiles; this tree is untouched.
1791
+ const leg = g.leg;
1792
+ g.leg = null;
1793
+ const fin = leg.adopted.finalize();
1794
+ if (!fin) {
1795
+ api.renderNow();
1796
+ fire({ type: 'cancel', kind: 'palette', nodeId: node.id, changed: false });
1797
+ return;
1798
+ }
1799
+ void ((_a = options.onDropIn) === null || _a === void 0 ? void 0 : _a.call(options, node, fin.cell, fin.commands, { boardId: leg.adopted.groupId }));
1800
+ fire({ type: 'drop-in', kind: 'palette', nodeId: node.id, changed: true });
1801
+ return;
1802
+ }
1577
1803
  const tree = g.liveTree;
1578
1804
  const after = g.target
1579
1805
  ? insertSplitLeaf(tree, node.id, targetRef(g.target), g.target.side)
@@ -1586,9 +1812,9 @@ export function bindDashboardSplit(api, group, options = {}) {
1586
1812
  return;
1587
1813
  }
1588
1814
  const cells = cellsFromSplit(after, columns, rowsGuess());
1589
- const cell = (_a = cells.get(node.id)) !== null && _a !== void 0 ? _a : { x: 0, y: 0, w: Math.max(1, spec.w), h: Math.max(1, spec.h) };
1815
+ const cell = (_b = cells.get(node.id)) !== null && _b !== void 0 ? _b : { x: 0, y: 0, w: Math.max(1, spec.w), h: Math.max(1, spec.h) };
1590
1816
  const displaced = [new SetSplitTreeCommand(group.id, tree, normalizeSplit(after))];
1591
- void ((_b = options.onDropIn) === null || _b === void 0 ? void 0 : _b.call(options, node, cell, displaced, { boardId: group.id }));
1817
+ void ((_c = options.onDropIn) === null || _c === void 0 ? void 0 : _c.call(options, node, cell, displaced, { boardId: group.id }));
1592
1818
  fire({ type: 'drop-in', kind: 'palette', nodeId: node.id, changed: true });
1593
1819
  };
1594
1820
  window.addEventListener('pointermove', onMove, true);
@@ -1997,7 +2223,8 @@ export function bindDashboardSplit(api, group, options = {}) {
1997
2223
  },
1998
2224
  hasItem: (id) => { var _a; return ((_a = group.members) !== null && _a !== void 0 ? _a : new Set()).has(id); },
1999
2225
  memberCell: (id) => handle.cellOf(id),
2000
- resizeMemberBy: () => ({ changed: false }),
2226
+ // A section pane grows a row for a tile arriving by hand (D4 on a split board, 0.4.71) — see resizePaneBy.
2227
+ resizeMemberBy: (id, dRows) => resizePaneBy(id, dRows),
2001
2228
  // A torn-out page becomes a PANE (0.4.37) — see beginTearOut.
2002
2229
  tearOutMember: (pageId, fromGroupId, ev, plan) => beginTearOut(pageId, fromGroupId, ev, plan),
2003
2230
  // A tab group (or a section) moved by its strip's empty space becomes a
@@ -2009,7 +2236,8 @@ export function bindDashboardSplit(api, group, options = {}) {
2009
2236
  const f = frame();
2010
2237
  return f.width * f.height;
2011
2238
  },
2012
- adopt: () => null,
2239
+ // A tile from another board lands as a PANE where the line shows (0.4.70) see adoptPane.
2240
+ adopt: (node, world, _px, opts) => adoptPane(node, world, opts),
2013
2241
  };
2014
2242
  const unregisterPeer = registerBoardPeer(api.container, selfPeer);
2015
2243
  selfPeerRef = selfPeer;