@grafloria/element 0.4.31 → 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();
@@ -764,6 +1099,15 @@ export function createDashboardHandle(ctx) {
764
1099
  return { move, collapse: remaining.length > 0 ? [] : collapseOf(boardId) };
765
1100
  },
766
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
+ },
767
1111
  dropIndex: (targetId, cx, cy) => {
768
1112
  const strip = ctx.tabStrips.get(targetId);
769
1113
  const n = liveCount(targetId);
@@ -772,27 +1116,10 @@ export function createDashboardHandle(ctx) {
772
1116
  const r = strip.getBoundingClientRect();
773
1117
  if (r.width === 0 || cx < r.left || cx > r.right || cy < r.top || cy > r.bottom)
774
1118
  return n;
775
- const rtl = strip.getAttribute('dir') === 'rtl';
776
- let i = 0;
777
- for (const t of Array.from(strip.querySelectorAll('.axdb-tab'))) {
778
- const tr = t.getBoundingClientRect();
779
- const mid = tr.left + tr.width / 2;
780
- if (rtl ? cx < mid : cx > mid)
781
- i++;
782
- else
783
- break;
784
- }
785
- return i;
786
- },
787
- markDrop: (targetId, index) => {
788
- for (const [id, el] of ctx.tabStrips) {
789
- const on = id === targetId;
790
- el.classList.toggle('axdb-tabs--drop', on);
791
- const tabs = Array.from(el.querySelectorAll('.axdb-tab'));
792
- tabs.forEach((t, i) => t.classList.toggle('axdb-tab--drop-before', on && index !== null && i === index));
793
- el.classList.toggle('axdb-tabs--drop-end', on && index !== null && index >= tabs.length);
794
- }
1119
+ return indexInStrip(strip, cx);
795
1120
  },
1121
+ markDrop: markTabDrop,
1122
+ reorder: (index) => reorderTabCommands(containerId, pageId, index),
796
1123
  join: (targetId, index, boardId) => {
797
1124
  var _a;
798
1125
  const target = (_a = ctx.boardGroups.get(targetId)) !== null && _a !== void 0 ? _a : model.getGroup(targetId);
@@ -864,53 +1191,9 @@ export function createDashboardHandle(ctx) {
864
1191
  var _a, _b;
865
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;
866
1193
  }
867
- /**
868
- * The last page left: the container closes. Its registry goes FIRST so
869
- * that on undo it comes back AFTER the group and its slab do.
870
- */
1194
+ /** The last page left: the container closes (see closeContainer). */
871
1195
  function collapseOf(boardId) {
872
- let fromSlot = -1;
873
- const gone = {
874
- register: () => {
875
- var _a;
876
- const live = model.getGroup(containerId);
877
- if (!live)
878
- return;
879
- ctx.boardGroups.set(containerId, live);
880
- ctx.layoutOf.set(containerId, 'tabs');
881
- ctx.viewOfBoard.set(containerId, viewId);
882
- if (fromSpec)
883
- specById.set(containerId, fromSpec);
884
- const arr = ctx.boardWidgets.get(boardId);
885
- if (arr && fromSpec && !arr.some((w) => w.id === containerId)) {
886
- arr.splice(fromSlot < 0 ? arr.length : Math.min(fromSlot, arr.length), 0, fromSpec);
887
- }
888
- (_a = ctx.attachTabsContainer) === null || _a === void 0 ? void 0 : _a.call(ctx, containerId);
889
- },
890
- unregister: () => {
891
- var _a;
892
- (_a = ctx.detachTabsContainer) === null || _a === void 0 ? void 0 : _a.call(ctx, containerId);
893
- ctx.boardGroups.delete(containerId);
894
- ctx.layoutOf.delete(containerId);
895
- ctx.viewOfBoard.delete(containerId);
896
- specById.delete(containerId);
897
- const arr = ctx.boardWidgets.get(boardId);
898
- if (arr) {
899
- const i = arr.findIndex((w) => w.id === containerId);
900
- if (i >= 0) {
901
- fromSlot = i;
902
- arr.splice(i, 1);
903
- }
904
- }
905
- },
906
- };
907
- return [
908
- new SequenceCommand('Close empty tab container', [
909
- new RegisterWidgetCommand(gone, 'unregister'),
910
- new RemoveFromGroupCommand(boardId, containerId),
911
- new RemoveGroupCommand(containerId),
912
- ]),
913
- ];
1196
+ return closeContainer(containerId, boardId);
914
1197
  }
915
1198
  };
916
1199
  /** Bookkeeping closures for one widget, shared by the add and remove
@@ -1146,6 +1429,31 @@ export function createDashboardHandle(ctx) {
1146
1429
  return true;
1147
1430
  },
1148
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
+ },
1149
1457
  setSizing(mode) {
1150
1458
  var _a;
1151
1459
  for (const b of binders.values())
@@ -1401,7 +1709,7 @@ export function createDashboardHandle(ctx) {
1401
1709
  ctx.renderWidget(spec, host);
1402
1710
  },
1403
1711
  remove(displaced) {
1404
- var _a, _b, _c, _d, _e, _f;
1712
+ var _a, _b, _c, _d, _e, _f, _g, _h;
1405
1713
  if (spec.widgets) {
1406
1714
  // A CONTAINER (review D12): its subtree, its own group, its slab in
1407
1715
  // the parent board and the parent's re-pack, as ONE undoable batch.
@@ -1467,16 +1775,21 @@ export function createDashboardHandle(ctx) {
1467
1775
  // the batch in reverse — re-registers the spec BEFORE the node and its
1468
1776
  // membership come back and the painter is asked to paint it (D2).
1469
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 : [];
1470
1782
  const cmds = [
1471
1783
  ...survivors,
1472
1784
  new RemoveFromGroupCommand(group.id, id),
1473
1785
  new RemoveNodeCommand(id),
1474
1786
  new RegisterWidgetCommand(registry, 'unregister'),
1787
+ ...closing,
1475
1788
  ];
1476
- void execCommand(new BatchCommand('Remove widget', cmds));
1789
+ void execCommand(closing.length > 0 ? new SequenceCommand('Remove widget', cmds) : new BatchCommand('Remove widget', cmds));
1477
1790
  registry.unregister(); // now, for the caller reading the handle next
1478
1791
  b.sync();
1479
- (_f = ctx.apiRef) === null || _f === void 0 ? void 0 : _f.renderNow();
1792
+ (_h = ctx.apiRef) === null || _h === void 0 ? void 0 : _h.renderNow();
1480
1793
  },
1481
1794
  repaint() {
1482
1795
  const host = hostOf(id);
@@ -1880,12 +2193,12 @@ export function dashboard(options) {
1880
2193
  }
1881
2194
  function bindView(v, g, viewLayout, live) {
1882
2195
  var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
1883
- 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) => {
1884
2197
  var _a, _b;
1885
2198
  if (e.type === 'commit')
1886
2199
  reportChanged();
1887
2200
  (_b = (_a = options.binder) === null || _a === void 0 ? void 0 : _a.onGesture) === null || _b === void 0 ? void 0 : _b.call(_a, e);
1888
- }, 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 } : {}));
1889
2202
  if (viewLayout === 'split') {
1890
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 } : {})));
1891
2204
  }
@@ -1898,12 +2211,12 @@ export function dashboard(options) {
1898
2211
  // The LIVE switches ride on the view's binder when it exists (a
1899
2212
  // container bound after a setStatic/setRtl/setDragHandle must match).
1900
2213
  const vb = binders.get((_a = ctx.viewOfBoard.get(w.id)) !== null && _a !== void 0 ? _a : ctx.active);
1901
- 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) => {
1902
2215
  var _a, _b;
1903
2216
  if (e.type === 'commit')
1904
2217
  reportChanged();
1905
2218
  (_b = (_a = options.binder) === null || _a === void 0 ? void 0 : _a.onGesture) === null || _b === void 0 ? void 0 : _b.call(_a, e);
1906
- }, 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 } : {}));
1907
2220
  if (((_j = ctx.layoutOf.get(w.id)) !== null && _j !== void 0 ? _j : w.layout) === 'split') {
1908
2221
  // A splitter tree covering the pane; the pane's frame is the parent's
1909
2222
  // slab, so no design height of its own.