@grafloria/element 0.4.30 → 0.4.32

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.
@@ -44,7 +44,7 @@
44
44
  */
45
45
  import { __awaiter, __rest } from "tslib";
46
46
  import { BatchCommand, BringNodeToFrontCommand, Command, GroupModel, NodeModel, AddGroupCommand, AddToGroupCommand, RemoveFromGroupCommand, RemoveGroupCommand, RemoveNodeCommand, SendNodeToBackCommand, } from '@grafloria/engine';
47
- import { bindDashboardGrid, parentPeerOf, } from './grid-binder.js';
47
+ import { bindDashboardGrid, parentPeerOf, SequenceCommand, } from './grid-binder.js';
48
48
  import { bindDashboardSplit, SPLIT_TREE_KEY } from './split-binder.js';
49
49
  import { gridItemFromCell } from './grid-mapping.js';
50
50
  import { ensureDashboardKitStyles } from './styles.js';
@@ -348,16 +348,22 @@ export function attachTabsRuntime(ctx, model, container, handle) {
348
348
  // exactly the "the content is taken and the tab remains" report — and a page
349
349
  // that arrived never got one.
350
350
  const pagesOf = (id) => {
351
- var _a, _b, _c, _d;
351
+ var _a, _b, _c, _d, _e, _f;
352
352
  const cg = (_a = ctx.boardGroups.get(id)) !== null && _a !== void 0 ? _a : model.getGroup(id);
353
353
  const authored = ((_c = (_b = ctx.specById.get(id)) === null || _b === void 0 ? void 0 : _b.widgets) !== null && _c !== void 0 ? _c : []).map((c) => c.id);
354
354
  const live = [...((_d = cg === null || cg === void 0 ? void 0 : cg.members) !== null && _d !== void 0 ? _d : [])].filter((m) => !!model.getGroup(m));
355
+ // A REORDERED strip persists its order on the container; it outranks the
356
+ // authored list, which outranks arrival order.
357
+ const saved = ((_f = (_e = cg === null || cg === void 0 ? void 0 : cg.getMetadata('containerWidget')) === null || _e === void 0 ? void 0 : _e.order) !== null && _f !== void 0 ? _f : []);
355
358
  const rank = (pid) => {
359
+ const o = saved.indexOf(pid);
360
+ if (o >= 0)
361
+ return o;
356
362
  // A plain child is wrapped as `<id>__page`, so the authored order is
357
363
  // keyed on the id BEFORE the wrap as well as after it.
358
364
  const i = authored.indexOf(pid);
359
365
  const j = i < 0 ? authored.indexOf(pid.replace(/__page$/, '')) : i;
360
- return j < 0 ? Number.MAX_SAFE_INTEGER : j;
366
+ return j < 0 ? Number.MAX_SAFE_INTEGER : saved.length + j;
361
367
  };
362
368
  live.sort((a, b) => rank(a) - rank(b));
363
369
  return live.map((pid) => { var _a, _b; return ({ id: pid, label: (_b = (_a = ctx.specById.get(pid)) === null || _a === void 0 ? void 0 : _a.title) !== null && _b !== void 0 ? _b : pid }); });
@@ -404,7 +410,14 @@ export function attachTabsRuntime(ctx, model, container, handle) {
404
410
  const key = tabStripKey(pages, active, ctx.tabsOf.get(id), rtl);
405
411
  if (el.getAttribute('data-key') === key)
406
412
  return;
407
- paintTabStrip(el, pages, active, ctx.tabsOf.get(id), rtl, (pid) => handle.activateTab(id, pid), () => handle.selectWidget(id), (pid, ev) => tearOut(id, pid, ev));
413
+ paintTabStrip(el, pages, active, ctx.tabsOf.get(id), rtl, (pid) => handle.activateTab(id, pid), (e) => {
414
+ var _a;
415
+ // The strip's empty space selects the container — and, travelled,
416
+ // drags the whole group by it, as a section is dragged by its caption.
417
+ handle.selectWidget(id);
418
+ if (container)
419
+ (_a = parentPeerOf(container, id)) === null || _a === void 0 ? void 0 : _a.dragMember(id, e);
420
+ }, (pid, ev) => tearOut(id, pid, ev));
408
421
  el.setAttribute('data-key', key);
409
422
  };
410
423
  const sync = (id) => {
@@ -413,12 +426,13 @@ export function attachTabsRuntime(ctx, model, container, handle) {
413
426
  if (!cg || !isTabs(id))
414
427
  return;
415
428
  const pages = pagesOf(id);
416
- // The page that was showing may have just been dragged out.
429
+ // The page that was showing may have just been dragged out or closed:
430
+ // whatever shows now is the active one, and it is written back so the
431
+ // handle answers what the strip paints.
417
432
  let active = (_b = ctx.activeTab.get(id)) !== null && _b !== void 0 ? _b : pages[0].id;
418
- if (!pages.some((p) => p.id === active)) {
433
+ if (!pages.some((p) => p.id === active))
419
434
  active = pages[0].id;
420
- ctx.activeTab.set(id, active);
421
- }
435
+ ctx.activeTab.set(id, active);
422
436
  const strip = tabStripReserve(ctx.tabsOf.get(id), pages.length);
423
437
  const f = { x: cg.position.x, y: cg.position.y, width: (_d = (_c = cg.size) === null || _c === void 0 ? void 0 : _c.width) !== null && _d !== void 0 ? _d : 0, height: (_f = (_e = cg.size) === null || _e === void 0 ? void 0 : _e.height) !== null && _f !== void 0 ? _f : 0 };
424
438
  const inner = { width: f.width, height: Math.max(0, f.height - strip) };
@@ -501,37 +515,6 @@ export function attachTabsRuntime(ctx, model, container, handle) {
501
515
  if (isTabs(id))
502
516
  attach(id);
503
517
  }
504
- /**
505
- * Steps that depend on each other's RESULT — create a group, then move a page
506
- * into it, then place the group on the board — run as one history step. A
507
- * batch checks every member's `canExecute` before running any of them, and
508
- * the second step's precondition (the group exists) is what the first step
509
- * creates, so as a batch the chain is refused. This runs the chain in order
510
- * and reverses it on undo, judging nothing up front.
511
- */
512
- class SequenceCommand extends Command {
513
- constructor(name, steps) {
514
- super(name);
515
- this.steps = steps;
516
- }
517
- execute(context) {
518
- for (const c of this.steps)
519
- c.execute(context);
520
- }
521
- undo(context) {
522
- for (let i = this.steps.length - 1; i >= 0; i--)
523
- this.steps[i].undo(context);
524
- }
525
- canExecute() {
526
- return true;
527
- }
528
- canUndo() {
529
- return true;
530
- }
531
- serialize() {
532
- return { id: this.id, name: this.name, timestamp: this.timestamp, data: { steps: this.steps.map((c) => c.serialize()) } };
533
- }
534
- }
535
518
  /** `setCaption` as one history step: execute re-applies the value, undo the previous one. */
536
519
  class SetCaptionCommand extends Command {
537
520
  constructor(sectionId, before, after, apply) {
@@ -619,7 +602,7 @@ export function createDashboardHandle(ctx) {
619
602
  * tile under the container it is actually in, in every one of those states.
620
603
  */
621
604
  const treeOf = (boardId) => {
622
- var _a, _b, _c, _d, _e, _f;
605
+ var _a, _b, _c, _d, _e, _f, _g, _h;
623
606
  const g = ctx.boardGroups.get(boardId);
624
607
  const b = binders.get(boardId);
625
608
  if (!g)
@@ -636,7 +619,7 @@ export function createDashboardHandle(ctx) {
636
619
  if (ctx.boardGroups.has(memberId)) {
637
620
  // The container's LIVE layout and, under split, its live tree — the
638
621
  // authored `tree` is stale the moment a divider moves.
639
- const _g = spec !== null && spec !== void 0 ? spec : {}, { tree: _authored } = _g, rest = __rest(_g, ["tree"]);
622
+ const _j = spec !== null && spec !== void 0 ? spec : {}, { tree: _authored } = _j, rest = __rest(_j, ["tree"]);
640
623
  void _authored;
641
624
  const clayout = (_c = (_b = ctx.layoutOf.get(memberId)) !== null && _b !== void 0 ? _b : spec === null || spec === void 0 ? void 0 : spec.layout) !== null && _c !== void 0 ? _c : 'grid';
642
625
  const cb = binders.get(memberId);
@@ -656,6 +639,14 @@ export function createDashboardHandle(ctx) {
656
639
  entries.push(entry);
657
640
  }
658
641
  }
642
+ if (ctx.layoutOf.get(boardId) === 'tabs') {
643
+ // A tab container's pages have no cells that mean anything: their order
644
+ // is the strip's (the authored list, as reordered), not a sort by cell.
645
+ const order = ((_h = (_g = specById.get(boardId)) === null || _g === void 0 ? void 0 : _g.widgets) !== null && _h !== void 0 ? _h : []).map((w) => w.id);
646
+ const rank = (id) => (order.indexOf(id) < 0 ? Number.MAX_SAFE_INTEGER : order.indexOf(id));
647
+ entries.sort((p1, p2) => rank(p1.id) - rank(p2.id));
648
+ return entries;
649
+ }
659
650
  entries.sort((p1, p2) => { var _a, _b, _c, _d; return ((_a = p1.y) !== null && _a !== void 0 ? _a : 0) - ((_b = p2.y) !== null && _b !== void 0 ? _b : 0) || ((_c = p1.x) !== null && _c !== void 0 ? _c : 0) - ((_d = p2.x) !== null && _d !== void 0 ? _d : 0); });
660
651
  return entries;
661
652
  };
@@ -670,6 +661,350 @@ export function createDashboardHandle(ctx) {
670
661
  * whose last editor leaves; undo reopens it. The board that lands it adds
671
662
  * its own displaced-tile commands around these.
672
663
  */
664
+ /** The tab index a CLIENT x means along a strip: before the first tab whose middle lies past it. */
665
+ const indexInStrip = (strip, cx) => {
666
+ const rtl = strip.getAttribute('dir') === 'rtl';
667
+ let i = 0;
668
+ for (const t of Array.from(strip.querySelectorAll('.axdb-tab'))) {
669
+ const tr = t.getBoundingClientRect();
670
+ const mid = tr.left + tr.width / 2;
671
+ if (rtl ? cx < mid : cx > mid)
672
+ i++;
673
+ else
674
+ break;
675
+ }
676
+ return i;
677
+ };
678
+ /** Paint (or, with null, clear) the insertion mark on a strip. */
679
+ const markTabDrop = (targetId, index) => {
680
+ for (const [id, el] of ctx.tabStrips) {
681
+ const on = id === targetId;
682
+ el.classList.toggle('axdb-tabs--drop', on);
683
+ const tabs = Array.from(el.querySelectorAll('.axdb-tab'));
684
+ tabs.forEach((t, i) => t.classList.toggle('axdb-tab--drop-before', on && index !== null && i === index));
685
+ el.classList.toggle('axdb-tabs--drop-end', on && index !== null && index >= tabs.length);
686
+ }
687
+ };
688
+ const livePages = (containerId) => {
689
+ var _a, _b, _c;
690
+ const model = (_a = ctx.apiRef) === null || _a === void 0 ? void 0 : _a.getModel();
691
+ return [...((_c = (_b = ctx.boardGroups.get(containerId)) === null || _b === void 0 ? void 0 : _b.members) !== null && _c !== void 0 ? _c : [])].filter((m) => !!(model === null || model === void 0 ? void 0 : model.getGroup(m)));
692
+ };
693
+ /**
694
+ * CLOSE a tab container: its slab leaves `boardId` (the tiles around it
695
+ * settle as a removal settles them), its registry goes and its strip with
696
+ * it. Undo reopens it. The registry goes FIRST so that on undo it comes
697
+ * back AFTER the group and its slab do.
698
+ */
699
+ const closeContainer = (containerId, boardId) => {
700
+ var _a, _b, _c, _d;
701
+ const model = (_a = ctx.apiRef) === null || _a === void 0 ? void 0 : _a.getModel();
702
+ if (!model)
703
+ return [];
704
+ const fromSpec = specById.get(containerId);
705
+ const viewId = (_b = ctx.viewOfBoard.get(containerId)) !== null && _b !== void 0 ? _b : ctx.active;
706
+ let fromSlot = -1;
707
+ const gone = {
708
+ register: () => {
709
+ var _a;
710
+ const live = model.getGroup(containerId);
711
+ if (!live)
712
+ return;
713
+ ctx.boardGroups.set(containerId, live);
714
+ ctx.layoutOf.set(containerId, 'tabs');
715
+ ctx.viewOfBoard.set(containerId, viewId);
716
+ if (fromSpec)
717
+ specById.set(containerId, fromSpec);
718
+ const arr = ctx.boardWidgets.get(boardId);
719
+ if (arr && fromSpec && !arr.some((w) => w.id === containerId)) {
720
+ arr.splice(fromSlot < 0 ? arr.length : Math.min(fromSlot, arr.length), 0, fromSpec);
721
+ }
722
+ (_a = ctx.attachTabsContainer) === null || _a === void 0 ? void 0 : _a.call(ctx, containerId);
723
+ },
724
+ unregister: () => {
725
+ var _a;
726
+ (_a = ctx.detachTabsContainer) === null || _a === void 0 ? void 0 : _a.call(ctx, containerId);
727
+ ctx.boardGroups.delete(containerId);
728
+ ctx.layoutOf.delete(containerId);
729
+ ctx.viewOfBoard.delete(containerId);
730
+ specById.delete(containerId);
731
+ const arr = ctx.boardWidgets.get(boardId);
732
+ if (arr) {
733
+ const i = arr.findIndex((w) => w.id === containerId);
734
+ if (i >= 0) {
735
+ fromSlot = i;
736
+ arr.splice(i, 1);
737
+ }
738
+ }
739
+ },
740
+ };
741
+ return [
742
+ ...((_d = (_c = binders.get(boardId)) === null || _c === void 0 ? void 0 : _c.planRemoval(containerId)) !== null && _d !== void 0 ? _d : []),
743
+ new SequenceCommand('Close empty tab container', [
744
+ new RegisterWidgetCommand(gone, 'unregister'),
745
+ new RemoveFromGroupCommand(boardId, containerId),
746
+ new RemoveGroupCommand(containerId),
747
+ ]),
748
+ ];
749
+ };
750
+ /** The tab container that holds `pageId` as a page, if any. */
751
+ const containerOfPage = (pageId) => {
752
+ var _a;
753
+ for (const [cid, g] of ctx.boardGroups)
754
+ if (ctx.layoutOf.get(cid) === 'tabs' && ((_a = g.members) === null || _a === void 0 ? void 0 : _a.has(pageId)))
755
+ return cid;
756
+ return undefined;
757
+ };
758
+ /**
759
+ * AN EMPTY PAGE CLOSES. When a page's last widget leaves — dragged out, moved
760
+ * into another board, removed, made a tab of its own — the page and its tab
761
+ * go, and if that was the container's last page the container goes too: a
762
+ * tab pointing at nothing is what "the content is taken and the tab remains"
763
+ * looked like. Undo reopens the page (its grid re-binds on the history event)
764
+ * and then the widget comes back into it.
765
+ */
766
+ ctx.closePageIfEmptied = (boardId, leaving) => {
767
+ var _a, _b, _c, _d, _e, _f;
768
+ const model = (_a = ctx.apiRef) === null || _a === void 0 ? void 0 : _a.getModel();
769
+ const pg = ctx.boardGroups.get(boardId);
770
+ if (!model || !pg)
771
+ return [];
772
+ if ([...((_b = pg.members) !== null && _b !== void 0 ? _b : [])].some((m) => m !== leaving))
773
+ return [];
774
+ const containerId = containerOfPage(boardId);
775
+ if (!containerId)
776
+ return [];
777
+ const container = ctx.boardGroups.get(containerId);
778
+ if (!container)
779
+ return [];
780
+ const hostBoard = (_d = (_c = viewOfWidget.get(containerId)) !== null && _c !== void 0 ? _c : ctx.viewOfBoard.get(containerId)) !== null && _d !== void 0 ? _d : ctx.active;
781
+ const viewId = (_e = ctx.viewOfBoard.get(boardId)) !== null && _e !== void 0 ? _e : ctx.active;
782
+ const pageSpec = specById.get(boardId);
783
+ const containerSpec = specById.get(containerId);
784
+ const layout = (_f = ctx.layoutOf.get(boardId)) !== null && _f !== void 0 ? _f : 'grid';
785
+ const prevActive = ctx.activeTab.get(containerId);
786
+ let slot = -1;
787
+ const page = {
788
+ register: () => {
789
+ var _a;
790
+ const live = model.getGroup(boardId);
791
+ if (!live)
792
+ return;
793
+ ctx.boardGroups.set(boardId, live);
794
+ ctx.layoutOf.set(boardId, layout);
795
+ ctx.viewOfBoard.set(boardId, viewId);
796
+ if (pageSpec) {
797
+ specById.set(boardId, pageSpec);
798
+ ctx.boardWidgets.set(boardId, (_a = pageSpec.widgets) !== null && _a !== void 0 ? _a : []);
799
+ if ((containerSpec === null || containerSpec === void 0 ? void 0 : containerSpec.widgets) && !containerSpec.widgets.some((p) => p.id === boardId)) {
800
+ containerSpec.widgets.splice(slot < 0 ? containerSpec.widgets.length : Math.min(slot, containerSpec.widgets.length), 0, pageSpec);
801
+ }
802
+ }
803
+ if (prevActive)
804
+ ctx.activeTab.set(containerId, prevActive);
805
+ },
806
+ unregister: () => {
807
+ var _a;
808
+ (_a = binders.get(boardId)) === null || _a === void 0 ? void 0 : _a.dispose();
809
+ binders.delete(boardId);
810
+ ctx.boardGroups.delete(boardId);
811
+ ctx.layoutOf.delete(boardId);
812
+ ctx.viewOfBoard.delete(boardId);
813
+ specById.delete(boardId);
814
+ ctx.boardWidgets.delete(boardId);
815
+ if (containerSpec === null || containerSpec === void 0 ? void 0 : containerSpec.widgets) {
816
+ const i = containerSpec.widgets.findIndex((p) => p.id === boardId);
817
+ if (i >= 0) {
818
+ slot = i;
819
+ containerSpec.widgets.splice(i, 1);
820
+ }
821
+ }
822
+ if (ctx.activeTab.get(containerId) === boardId)
823
+ ctx.activeTab.delete(containerId);
824
+ },
825
+ };
826
+ const cmds = [
827
+ new SequenceCommand('Close empty tab page', [
828
+ new RegisterWidgetCommand(page, 'unregister'),
829
+ new RemoveFromGroupCommand(containerId, boardId),
830
+ new RemoveGroupCommand(boardId),
831
+ ]),
832
+ ];
833
+ if (livePages(containerId).filter((m) => m !== boardId).length === 0)
834
+ cmds.push(...closeContainer(containerId, hostBoard));
835
+ return cmds;
836
+ };
837
+ /**
838
+ * A WIDGET BECOMES A TAB: the commands that wrap `widgetId` into a fresh page
839
+ * `<widget>__page` — built as an authored page is — and put that page into
840
+ * `containerId` at `index`, active. `displaced` are the source board's
841
+ * survivors' cells (a drag has them; the API plans them). The board it left
842
+ * re-packs, and a page it emptied closes.
843
+ */
844
+ const moveWidgetToTabCommands = (widgetId, containerId, index, sourceBoardId, displaced) => {
845
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s;
846
+ const model = (_a = ctx.apiRef) === null || _a === void 0 ? void 0 : _a.getModel();
847
+ const node = model === null || model === void 0 ? void 0 : model.getNode(widgetId);
848
+ const spec = specById.get(widgetId);
849
+ const container = ctx.boardGroups.get(containerId);
850
+ const containerSpec = specById.get(containerId);
851
+ if (!model || !node || !spec || spec.widgets || !container || !containerSpec)
852
+ return [];
853
+ if (((_b = ctx.layoutOf.get(containerId)) !== null && _b !== void 0 ? _b : containerSpec.layout) !== 'tabs')
854
+ return [];
855
+ const pageId = `${widgetId}__page`;
856
+ if (model.getGroup(pageId))
857
+ return [];
858
+ const title = (_c = spec.title) !== null && _c !== void 0 ? _c : widgetId;
859
+ const cw = ((_d = container.getMetadata('containerWidget')) !== null && _d !== void 0 ? _d : {});
860
+ const cols = Math.max(1, (_g = (_f = (_e = cw.columns) !== null && _e !== void 0 ? _e : containerSpec.columns) !== null && _f !== void 0 ? _f : spec.span) !== null && _g !== void 0 ? _g : 3);
861
+ const rows = Math.max(1, (_h = spec.rows) !== null && _h !== void 0 ? _h : 1);
862
+ const g = new GroupModel({ id: pageId, name: title });
863
+ g.setMetadata('frameChrome', 'none');
864
+ g.setMetadata('gridItem', gridItemFromCell({ x: 0, y: 0, w: cols, h: rows }));
865
+ g.setMetadata('containerWidget', { title, columns: cols, maxRows: rows, layout: 'grid' });
866
+ g.setMetadata('dashboardBoard', {
867
+ columns: cols,
868
+ gap: ctx.gap,
869
+ padding: 0,
870
+ sizing: 'fit',
871
+ baseRowHeight: ctx.rowHeight,
872
+ designHeight: 0,
873
+ maxRows: rows,
874
+ float: false,
875
+ rtl: (_j = ctx.optionsBase.rtl) !== null && _j !== void 0 ? _j : false,
876
+ layout: 'grid',
877
+ escalate: true,
878
+ });
879
+ g.position = { x: container.position.x, y: container.position.y };
880
+ g.size = { width: (_l = (_k = container.size) === null || _k === void 0 ? void 0 : _k.width) !== null && _l !== void 0 ? _l : 100, height: (_o = (_m = container.size) === null || _m === void 0 ? void 0 : _m.height) !== null && _o !== void 0 ? _o : 100, depth: 0 };
881
+ const pageSpec = { id: pageId, title, columns: cols, widgets: [spec] };
882
+ const viewId = (_p = ctx.viewOfBoard.get(containerId)) !== null && _p !== void 0 ? _p : ctx.active;
883
+ const prevActive = ctx.activeTab.get(containerId);
884
+ const prevXY = { x: spec.x, y: spec.y };
885
+ const prevGrid = (_q = node.getGridItem) === null || _q === void 0 ? void 0 : _q.call(node);
886
+ let srcSlot = -1;
887
+ const cwOf = () => { var _a; return ((_a = container.getMetadata('containerWidget')) !== null && _a !== void 0 ? _a : {}); };
888
+ const reg = {
889
+ register: () => {
890
+ var _a, _b;
891
+ const live = model.getGroup(pageId);
892
+ if (!live)
893
+ return;
894
+ ctx.boardGroups.set(pageId, live);
895
+ ctx.layoutOf.set(pageId, 'grid');
896
+ ctx.viewOfBoard.set(pageId, viewId);
897
+ specById.set(pageId, pageSpec);
898
+ ctx.boardWidgets.set(pageId, pageSpec.widgets);
899
+ viewOfWidget.set(pageId, containerId);
900
+ viewOfWidget.set(widgetId, pageId);
901
+ const src = ctx.boardWidgets.get(sourceBoardId);
902
+ if (src) {
903
+ const i = src.findIndex((w) => w.id === widgetId);
904
+ if (i >= 0) {
905
+ srcSlot = i;
906
+ src.splice(i, 1);
907
+ }
908
+ }
909
+ containerSpec.widgets = (_a = containerSpec.widgets) !== null && _a !== void 0 ? _a : [];
910
+ if (!containerSpec.widgets.some((p) => p.id === pageId)) {
911
+ containerSpec.widgets.splice(Math.max(0, Math.min(index, containerSpec.widgets.length)), 0, pageSpec);
912
+ }
913
+ spec.x = 0;
914
+ spec.y = 0;
915
+ node.setGridItem(gridItemFromCell({ x: 0, y: 0, w: Math.min(cols, (_b = spec.span) !== null && _b !== void 0 ? _b : cols), h: rows }));
916
+ ctx.activeTab.set(containerId, pageId);
917
+ containerSpec.active = pageId;
918
+ container.setMetadata('containerWidget', Object.assign(Object.assign({}, cwOf()), { active: pageId }));
919
+ },
920
+ unregister: () => {
921
+ var _a;
922
+ (_a = binders.get(pageId)) === null || _a === void 0 ? void 0 : _a.dispose();
923
+ binders.delete(pageId);
924
+ ctx.boardGroups.delete(pageId);
925
+ ctx.layoutOf.delete(pageId);
926
+ ctx.viewOfBoard.delete(pageId);
927
+ specById.delete(pageId);
928
+ ctx.boardWidgets.delete(pageId);
929
+ viewOfWidget.delete(pageId);
930
+ viewOfWidget.set(widgetId, sourceBoardId);
931
+ const src = ctx.boardWidgets.get(sourceBoardId);
932
+ if (src && !src.some((w) => w.id === widgetId))
933
+ src.splice(srcSlot < 0 ? src.length : Math.min(srcSlot, src.length), 0, spec);
934
+ if (containerSpec.widgets) {
935
+ const i = containerSpec.widgets.findIndex((p) => p.id === pageId);
936
+ if (i >= 0)
937
+ containerSpec.widgets.splice(i, 1);
938
+ }
939
+ spec.x = prevXY.x;
940
+ spec.y = prevXY.y;
941
+ if (prevGrid)
942
+ node.setGridItem(prevGrid);
943
+ if (prevActive) {
944
+ ctx.activeTab.set(containerId, prevActive);
945
+ containerSpec.active = prevActive;
946
+ container.setMetadata('containerWidget', Object.assign(Object.assign({}, cwOf()), { active: prevActive }));
947
+ }
948
+ else
949
+ ctx.activeTab.delete(containerId);
950
+ },
951
+ };
952
+ return [
953
+ new SequenceCommand('Move widget into a new tab', [
954
+ ...displaced,
955
+ new AddGroupCommand(g),
956
+ new RemoveFromGroupCommand(sourceBoardId, widgetId),
957
+ new AddToGroupCommand(pageId, widgetId),
958
+ new AddToGroupCommand(containerId, pageId),
959
+ new RegisterWidgetCommand(reg, 'register'),
960
+ ]),
961
+ ...((_s = (_r = ctx.closePageIfEmptied) === null || _r === void 0 ? void 0 : _r.call(ctx, sourceBoardId, widgetId)) !== null && _s !== void 0 ? _s : []),
962
+ ];
963
+ };
964
+ /** REORDER a page along its strip: the container's spec order and its saved `order` move together. */
965
+ const reorderTabCommands = (containerId, pageId, index) => {
966
+ var _a;
967
+ const containerSpec = specById.get(containerId);
968
+ const container = ctx.boardGroups.get(containerId);
969
+ if (!container || !(containerSpec === null || containerSpec === void 0 ? void 0 : containerSpec.widgets) || !((_a = container.members) === null || _a === void 0 ? void 0 : _a.has(pageId)))
970
+ return [];
971
+ const before = livePages(containerId).sort((a, b) => {
972
+ const ia = containerSpec.widgets.findIndex((p) => p.id === a);
973
+ const ib = containerSpec.widgets.findIndex((p) => p.id === b);
974
+ return (ia < 0 ? 1e9 : ia) - (ib < 0 ? 1e9 : ib);
975
+ });
976
+ const from = before.indexOf(pageId);
977
+ if (from < 0)
978
+ return [];
979
+ const to = Math.max(0, Math.min(index, before.length - 1));
980
+ if (to === from)
981
+ return [];
982
+ const after = [...before];
983
+ after.splice(from, 1);
984
+ after.splice(to, 0, pageId);
985
+ const apply = (order) => {
986
+ var _a, _b;
987
+ const rank = (id) => (order.indexOf(id) < 0 ? Number.MAX_SAFE_INTEGER : order.indexOf(id));
988
+ containerSpec.widgets.sort((p, q) => rank(p.id) - rank(q.id));
989
+ container.setMetadata('containerWidget', Object.assign(Object.assign({}, ((_a = container.getMetadata('containerWidget')) !== null && _a !== void 0 ? _a : {})), { order }));
990
+ (_b = ctx.syncTabs) === null || _b === void 0 ? void 0 : _b.call(ctx, containerId);
991
+ };
992
+ return [new RegisterWidgetCommand({ register: () => apply(after), unregister: () => apply(before) }, 'register')];
993
+ };
994
+ ctx.tabDrop = {
995
+ stripAt: (cx, cy) => {
996
+ for (const [id, el] of ctx.tabStrips) {
997
+ if (!ctx.boardGroups.has(id))
998
+ continue;
999
+ const r = el.getBoundingClientRect();
1000
+ if (r.width > 0 && cx >= r.left && cx <= r.right && cy >= r.top && cy <= r.bottom)
1001
+ return { containerId: id, index: indexInStrip(el, cx) };
1002
+ }
1003
+ return null;
1004
+ },
1005
+ markDrop: markTabDrop,
1006
+ dropIntoStrip: (widgetId, containerId, index, sourceBoardId, displaced) => moveWidgetToTabCommands(widgetId, containerId, index, sourceBoardId, displaced),
1007
+ };
673
1008
  ctx.tearOutPlan = (containerId, pageId) => {
674
1009
  var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
675
1010
  const model = (_a = ctx.apiRef) === null || _a === void 0 ? void 0 : _a.getModel();
@@ -761,57 +1096,105 @@ export function createDashboardHandle(ctx) {
761
1096
  new RegisterWidgetCommand(born, 'register'),
762
1097
  ]),
763
1098
  ];
764
- if (remaining.length > 0)
765
- return { move, collapse: [] };
766
- // The last page left: the container closes. Its registry goes FIRST
767
- // so that on undo it comes back AFTER the group and its slab do.
768
- let fromSlot = -1;
769
- const gone = {
1099
+ return { move, collapse: remaining.length > 0 ? [] : collapseOf(boardId) };
1100
+ },
1101
+ joinTargets: [...ctx.layoutOf.keys()].filter((id) => { var _a, _b; return id !== containerId && id !== pageId && ((_a = ctx.layoutOf.get(id)) !== null && _a !== void 0 ? _a : (_b = specById.get(id)) === null || _b === void 0 ? void 0 : _b.layout) === 'tabs' && !!model.getGroup(id) && !insidePage(id); }),
1102
+ stripIndex: (targetId, cx, cy) => {
1103
+ const strip = ctx.tabStrips.get(targetId);
1104
+ if (!strip)
1105
+ return null;
1106
+ const r = strip.getBoundingClientRect();
1107
+ if (r.width === 0 || cx < r.left || cx > r.right || cy < r.top || cy > r.bottom)
1108
+ return null;
1109
+ return indexInStrip(strip, cx);
1110
+ },
1111
+ dropIndex: (targetId, cx, cy) => {
1112
+ const strip = ctx.tabStrips.get(targetId);
1113
+ const n = liveCount(targetId);
1114
+ if (!strip)
1115
+ return n;
1116
+ const r = strip.getBoundingClientRect();
1117
+ if (r.width === 0 || cx < r.left || cx > r.right || cy < r.top || cy > r.bottom)
1118
+ return n;
1119
+ return indexInStrip(strip, cx);
1120
+ },
1121
+ markDrop: markTabDrop,
1122
+ reorder: (index) => reorderTabCommands(containerId, pageId, index),
1123
+ join: (targetId, index, boardId) => {
1124
+ var _a;
1125
+ const target = (_a = ctx.boardGroups.get(targetId)) !== null && _a !== void 0 ? _a : model.getGroup(targetId);
1126
+ const targetSpec = specById.get(targetId);
1127
+ if (!target || !targetSpec)
1128
+ return { move: [], collapse: [] };
1129
+ const prevActive = ctx.activeTab.get(targetId);
1130
+ const cwOf = (g) => { var _a; return ((_a = g.getMetadata('containerWidget')) !== null && _a !== void 0 ? _a : {}); };
1131
+ let slot = -1;
1132
+ const joined = {
770
1133
  register: () => {
771
1134
  var _a;
772
- const live = model.getGroup(containerId);
773
- if (!live)
774
- return;
775
- ctx.boardGroups.set(containerId, live);
776
- ctx.layoutOf.set(containerId, 'tabs');
777
- ctx.viewOfBoard.set(containerId, viewId);
778
- if (fromSpec)
779
- specById.set(containerId, fromSpec);
780
- const arr = ctx.boardWidgets.get(boardId);
781
- if (arr && fromSpec && !arr.some((w) => w.id === containerId)) {
782
- arr.splice(fromSlot < 0 ? arr.length : Math.min(fromSlot, arr.length), 0, fromSpec);
1135
+ viewOfWidget.set(pageId, targetId);
1136
+ if (fromSpec === null || fromSpec === void 0 ? void 0 : fromSpec.widgets) {
1137
+ const i = fromSpec.widgets.findIndex((p) => p.id === pageId);
1138
+ if (i >= 0) {
1139
+ slot = i;
1140
+ fromSpec.widgets.splice(i, 1);
1141
+ }
783
1142
  }
784
- (_a = ctx.attachTabsContainer) === null || _a === void 0 ? void 0 : _a.call(ctx, containerId);
1143
+ targetSpec.widgets = (_a = targetSpec.widgets) !== null && _a !== void 0 ? _a : [];
1144
+ if (!targetSpec.widgets.some((p) => p.id === pageId)) {
1145
+ targetSpec.widgets.splice(Math.max(0, Math.min(index, targetSpec.widgets.length)), 0, pageSpec);
1146
+ }
1147
+ ctx.boardWidgets.set(targetId, targetSpec.widgets);
1148
+ // The moved tab is the one showing, as it is in VS Code.
1149
+ ctx.activeTab.set(targetId, pageId);
1150
+ targetSpec.active = pageId;
1151
+ target.setMetadata('containerWidget', Object.assign(Object.assign({}, cwOf(target)), { active: pageId }));
785
1152
  },
786
1153
  unregister: () => {
787
- var _a;
788
- (_a = ctx.detachTabsContainer) === null || _a === void 0 ? void 0 : _a.call(ctx, containerId);
789
- ctx.boardGroups.delete(containerId);
790
- ctx.layoutOf.delete(containerId);
791
- ctx.viewOfBoard.delete(containerId);
792
- specById.delete(containerId);
793
- const arr = ctx.boardWidgets.get(boardId);
794
- if (arr) {
795
- const i = arr.findIndex((w) => w.id === containerId);
796
- if (i >= 0) {
797
- fromSlot = i;
798
- arr.splice(i, 1);
799
- }
1154
+ viewOfWidget.set(pageId, containerId);
1155
+ if (targetSpec.widgets) {
1156
+ const i = targetSpec.widgets.findIndex((p) => p.id === pageId);
1157
+ if (i >= 0)
1158
+ targetSpec.widgets.splice(i, 1);
1159
+ }
1160
+ if ((fromSpec === null || fromSpec === void 0 ? void 0 : fromSpec.widgets) && !fromSpec.widgets.some((p) => p.id === pageId)) {
1161
+ fromSpec.widgets.splice(slot < 0 ? fromSpec.widgets.length : Math.min(slot, fromSpec.widgets.length), 0, pageSpec);
1162
+ }
1163
+ if (prevActive) {
1164
+ ctx.activeTab.set(targetId, prevActive);
1165
+ targetSpec.active = prevActive;
1166
+ target.setMetadata('containerWidget', Object.assign(Object.assign({}, cwOf(target)), { active: prevActive }));
800
1167
  }
801
1168
  },
802
1169
  };
803
- return {
804
- move,
805
- collapse: [
806
- new SequenceCommand('Close empty tab container', [
807
- new RegisterWidgetCommand(gone, 'unregister'),
808
- new RemoveFromGroupCommand(boardId, containerId),
809
- new RemoveGroupCommand(containerId),
810
- ]),
811
- ],
812
- };
1170
+ const move = [
1171
+ new SequenceCommand('Move tab', [
1172
+ new RemoveFromGroupCommand(containerId, pageId),
1173
+ new AddToGroupCommand(targetId, pageId),
1174
+ new RegisterWidgetCommand(joined, 'register'),
1175
+ ]),
1176
+ ];
1177
+ return { move, collapse: remaining.length > 0 ? [] : collapseOf(boardId) };
813
1178
  },
814
1179
  };
1180
+ /** A tab container nested inside the page being moved cannot be its target. */
1181
+ function insidePage(id) {
1182
+ let cur = model.getGroup(id);
1183
+ for (let i = 0; cur && i < 32; i++) {
1184
+ if (cur.id === pageId)
1185
+ return true;
1186
+ cur = cur.parentGroupId ? model.getGroup(cur.parentGroupId) : undefined;
1187
+ }
1188
+ return false;
1189
+ }
1190
+ function liveCount(id) {
1191
+ var _a, _b;
1192
+ return [...((_b = (_a = model.getGroup(id)) === null || _a === void 0 ? void 0 : _a.members) !== null && _b !== void 0 ? _b : [])].filter((m) => !!model.getGroup(m)).length;
1193
+ }
1194
+ /** The last page left: the container closes (see closeContainer). */
1195
+ function collapseOf(boardId) {
1196
+ return closeContainer(containerId, boardId);
1197
+ }
815
1198
  };
816
1199
  /** Bookkeeping closures for one widget, shared by the add and remove
817
1200
  * commands (and applied synchronously by the handle — idempotent). */
@@ -1046,6 +1429,31 @@ export function createDashboardHandle(ctx) {
1046
1429
  return true;
1047
1430
  },
1048
1431
  getActiveTab: (containerId) => ctx.activeTab.get(containerId),
1432
+ moveToTab(widgetId, containerId, index) {
1433
+ return __awaiter(this, void 0, void 0, function* () {
1434
+ var _a, _b, _c;
1435
+ const sourceBoardId = viewOfWidget.get(widgetId);
1436
+ if (!sourceBoardId)
1437
+ return false;
1438
+ const cmds = moveWidgetToTabCommands(widgetId, containerId, index !== null && index !== void 0 ? index : Number.MAX_SAFE_INTEGER, sourceBoardId, (_b = (_a = binders.get(sourceBoardId)) === null || _a === void 0 ? void 0 : _a.planRemoval(widgetId)) !== null && _b !== void 0 ? _b : []);
1439
+ if (cmds.length === 0)
1440
+ return false;
1441
+ yield execCommand(new BatchCommand('Move widget into a new tab', cmds));
1442
+ (_c = ctx.apiRef) === null || _c === void 0 ? void 0 : _c.renderNow();
1443
+ reportChanged();
1444
+ return true;
1445
+ });
1446
+ },
1447
+ moveTab(containerId, pageId, index) {
1448
+ var _a;
1449
+ const cmds = reorderTabCommands(containerId, pageId, index);
1450
+ if (cmds.length === 0)
1451
+ return false;
1452
+ void execCommand(new BatchCommand('Reorder tab', cmds));
1453
+ (_a = ctx.apiRef) === null || _a === void 0 ? void 0 : _a.renderNow();
1454
+ reportChanged();
1455
+ return true;
1456
+ },
1049
1457
  setSizing(mode) {
1050
1458
  var _a;
1051
1459
  for (const b of binders.values())
@@ -1301,7 +1709,7 @@ export function createDashboardHandle(ctx) {
1301
1709
  ctx.renderWidget(spec, host);
1302
1710
  },
1303
1711
  remove(displaced) {
1304
- var _a, _b, _c, _d, _e, _f;
1712
+ var _a, _b, _c, _d, _e, _f, _g, _h;
1305
1713
  if (spec.widgets) {
1306
1714
  // A CONTAINER (review D12): its subtree, its own group, its slab in
1307
1715
  // the parent board and the parent's re-pack, as ONE undoable batch.
@@ -1367,16 +1775,21 @@ export function createDashboardHandle(ctx) {
1367
1775
  // the batch in reverse — re-registers the spec BEFORE the node and its
1368
1776
  // membership come back and the painter is asked to paint it (D2).
1369
1777
  const registry = registryOf(id, viewId, spec);
1778
+ // …and the page it emptied, if it was one, closes with it — in which
1779
+ // case everything rides INSIDE one sequence, or the batch could never
1780
+ // undo (a membership command cannot undo once its group is gone).
1781
+ const closing = (_g = (_f = ctx.closePageIfEmptied) === null || _f === void 0 ? void 0 : _f.call(ctx, viewId, id)) !== null && _g !== void 0 ? _g : [];
1370
1782
  const cmds = [
1371
1783
  ...survivors,
1372
1784
  new RemoveFromGroupCommand(group.id, id),
1373
1785
  new RemoveNodeCommand(id),
1374
1786
  new RegisterWidgetCommand(registry, 'unregister'),
1787
+ ...closing,
1375
1788
  ];
1376
- void execCommand(new BatchCommand('Remove widget', cmds));
1789
+ void execCommand(closing.length > 0 ? new SequenceCommand('Remove widget', cmds) : new BatchCommand('Remove widget', cmds));
1377
1790
  registry.unregister(); // now, for the caller reading the handle next
1378
1791
  b.sync();
1379
- (_f = ctx.apiRef) === null || _f === void 0 ? void 0 : _f.renderNow();
1792
+ (_h = ctx.apiRef) === null || _h === void 0 ? void 0 : _h.renderNow();
1380
1793
  },
1381
1794
  repaint() {
1382
1795
  const host = hostOf(id);
@@ -1780,12 +2193,12 @@ export function dashboard(options) {
1780
2193
  }
1781
2194
  function bindView(v, g, viewLayout, live) {
1782
2195
  var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
1783
- const common = Object.assign(Object.assign(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 }, (options.squeeze !== undefined ? { squeeze: options.squeeze } : {})), ((_g = options.binder) !== null && _g !== void 0 ? _g : {})), { onGesture: (e) => {
2196
+ const common = Object.assign(Object.assign(Object.assign(Object.assign(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 }, (options.squeeze !== undefined ? { squeeze: options.squeeze } : {})), ((_g = options.binder) !== null && _g !== void 0 ? _g : {})), { onGesture: (e) => {
1784
2197
  var _a, _b;
1785
2198
  if (e.type === 'commit')
1786
2199
  reportChanged();
1787
2200
  (_b = (_a = options.binder) === null || _a === void 0 ? void 0 : _a.onGesture) === null || _b === void 0 ? void 0 : _b.call(_a, e);
1788
- }, onSelect: (id) => { var _a; return (_a = options.onSelect) === null || _a === void 0 ? void 0 : _a.call(options, id, v.id); } }), captionHooks(v.id));
2201
+ }, onSelect: (id) => { var _a; return (_a = options.onSelect) === null || _a === void 0 ? void 0 : _a.call(options, id, v.id); } }), captionHooks(v.id)), { onMemberLeaving: (memberId) => { var _a, _b; return (_b = (_a = ctx.closePageIfEmptied) === null || _a === void 0 ? void 0 : _a.call(ctx, v.id, memberId)) !== null && _b !== void 0 ? _b : []; } }), (ctx.tabDrop ? { tabDrop: ctx.tabDrop } : {}));
1789
2202
  if (viewLayout === 'split') {
1790
2203
  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 } : {})));
1791
2204
  }
@@ -1798,12 +2211,12 @@ export function dashboard(options) {
1798
2211
  // The LIVE switches ride on the view's binder when it exists (a
1799
2212
  // container bound after a setStatic/setRtl/setDragHandle must match).
1800
2213
  const vb = binders.get((_a = ctx.viewOfBoard.get(w.id)) !== null && _a !== void 0 ? _a : ctx.active);
1801
- const inner = Object.assign(Object.assign(Object.assign({ columns: innerColumnsOf(w), gap, padding: 0, baseRowHeight: rowHeight, rtl: (_c = (_b = vb === null || vb === void 0 ? void 0 : vb.getRtl()) !== null && _b !== void 0 ? _b : options.rtl) !== null && _c !== void 0 ? _c : false, static: (_e = (_d = vb === null || vb === void 0 ? void 0 : vb.getStatic()) !== null && _d !== void 0 ? _d : options.static) !== null && _e !== void 0 ? _e : false, dragHandle: (_g = (_f = vb === null || vb === void 0 ? void 0 : vb.getDragHandle()) !== null && _f !== void 0 ? _f : options.dragHandle) !== null && _g !== void 0 ? _g : false }, (options.squeeze !== undefined ? { squeeze: options.squeeze } : {})), { onGesture: (e) => {
2214
+ const inner = Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({ columns: innerColumnsOf(w), gap, padding: 0, baseRowHeight: rowHeight, rtl: (_c = (_b = vb === null || vb === void 0 ? void 0 : vb.getRtl()) !== null && _b !== void 0 ? _b : options.rtl) !== null && _c !== void 0 ? _c : false, static: (_e = (_d = vb === null || vb === void 0 ? void 0 : vb.getStatic()) !== null && _d !== void 0 ? _d : options.static) !== null && _e !== void 0 ? _e : false, dragHandle: (_g = (_f = vb === null || vb === void 0 ? void 0 : vb.getDragHandle()) !== null && _f !== void 0 ? _f : options.dragHandle) !== null && _g !== void 0 ? _g : false }, (options.squeeze !== undefined ? { squeeze: options.squeeze } : {})), { onGesture: (e) => {
1802
2215
  var _a, _b;
1803
2216
  if (e.type === 'commit')
1804
2217
  reportChanged();
1805
2218
  (_b = (_a = options.binder) === null || _a === void 0 ? void 0 : _a.onGesture) === null || _b === void 0 ? void 0 : _b.call(_a, e);
1806
- }, onSelect: (id) => { var _a, _b; return (_a = options.onSelect) === null || _a === void 0 ? void 0 : _a.call(options, id, (_b = ctx.viewOfBoard.get(w.id)) !== null && _b !== void 0 ? _b : ctx.active); } }), captionHooks((_h = ctx.viewOfBoard.get(w.id)) !== null && _h !== void 0 ? _h : ctx.active));
2219
+ }, onSelect: (id) => { var _a, _b; return (_a = options.onSelect) === null || _a === void 0 ? void 0 : _a.call(options, id, (_b = ctx.viewOfBoard.get(w.id)) !== null && _b !== void 0 ? _b : ctx.active); } }), captionHooks((_h = ctx.viewOfBoard.get(w.id)) !== null && _h !== void 0 ? _h : ctx.active)), { onMemberLeaving: (memberId) => { var _a, _b; return (_b = (_a = ctx.closePageIfEmptied) === null || _a === void 0 ? void 0 : _a.call(ctx, w.id, memberId)) !== null && _b !== void 0 ? _b : []; } }), (ctx.tabDrop ? { tabDrop: ctx.tabDrop } : {}));
1807
2220
  if (((_j = ctx.layoutOf.get(w.id)) !== null && _j !== void 0 ? _j : w.layout) === 'split') {
1808
2221
  // A splitter tree covering the pane; the pane's frame is the parent's
1809
2222
  // slab, so no design height of its own.