@ai-matrx/canvas 0.6.1 → 0.7.1

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/dist/react.cjs CHANGED
@@ -34,6 +34,8 @@ var react_exports = {};
34
34
  __export(react_exports, {
35
35
  BrowserBar: () => BrowserBar,
36
36
  BrowserTabs: () => BrowserTabs,
37
+ CANVAS_ACTIVE_TAB_MIN_WIDTH: () => CANVAS_ACTIVE_TAB_MIN_WIDTH,
38
+ CANVAS_TAB_MIN_WIDTH: () => CANVAS_TAB_MIN_WIDTH,
37
39
  CanvasColumn: () => CanvasColumn,
38
40
  CanvasFrame: () => CanvasFrame,
39
41
  CanvasHeaderButton: () => CanvasHeaderButton,
@@ -46,8 +48,10 @@ __export(react_exports, {
46
48
  CanvasTab: () => CanvasTab,
47
49
  CanvasToggle: () => CanvasToggle,
48
50
  CanvasWindowScope: () => CanvasWindowScope,
51
+ beginCanvasResize: () => beginCanvasResize,
49
52
  canvasSessionKey: () => canvasSessionKey,
50
53
  defineCanvasKind: () => defineCanvasKind,
54
+ fitCanvasTabs: () => fitCanvasTabs,
51
55
  getCanvasKind: () => getCanvasKind,
52
56
  itemTitle: () => itemTitle,
53
57
  listCanvasKinds: () => listCanvasKinds,
@@ -62,6 +66,7 @@ __export(react_exports, {
62
66
  useCanvasKinds: () => useCanvasKinds,
63
67
  useCanvasLauncherEntries: () => useCanvasLauncherEntries,
64
68
  useCanvasLauncherEntry: () => useCanvasLauncherEntry,
69
+ useCanvasMenuItems: () => useCanvasMenuItems,
65
70
  useCanvasNativeBodies: () => useCanvasNativeBodies,
66
71
  useCanvasPresentation: () => useCanvasPresentation,
67
72
  useCanvasState: () => useCanvasState,
@@ -72,7 +77,46 @@ __export(react_exports, {
72
77
  module.exports = __toCommonJS(react_exports);
73
78
 
74
79
  // src/react/provider.tsx
75
- var import_react2 = require("react");
80
+ var import_react3 = require("react");
81
+
82
+ // src/react/menuItems.ts
83
+ var import_react = require("react");
84
+ var useIsoLayoutEffect = typeof window === "undefined" ? import_react.useEffect : import_react.useLayoutEffect;
85
+ function createCanvasItemMenus() {
86
+ const sources = /* @__PURE__ */ new Map();
87
+ return {
88
+ register(itemId, read) {
89
+ let set = sources.get(itemId);
90
+ if (!set) sources.set(itemId, set = /* @__PURE__ */ new Set());
91
+ set.add(read);
92
+ return () => {
93
+ const current = sources.get(itemId);
94
+ current?.delete(read);
95
+ if (current && current.size === 0) sources.delete(itemId);
96
+ };
97
+ },
98
+ read(itemId) {
99
+ const out = [];
100
+ for (const read of sources.get(itemId) ?? []) {
101
+ try {
102
+ out.push(...read());
103
+ } catch (error) {
104
+ console.error("[@ai-matrx/canvas] a body's menu entries threw", error);
105
+ }
106
+ }
107
+ return out;
108
+ }
109
+ };
110
+ }
111
+ function useCanvasMenuItems(itemId, items) {
112
+ const { itemMenus } = useCanvasInternals();
113
+ const latest = (0, import_react.useRef)(items);
114
+ latest.current = items;
115
+ useIsoLayoutEffect(() => {
116
+ if (!itemId) return void 0;
117
+ return itemMenus.register(itemId, () => latest.current);
118
+ }, [itemMenus, itemId]);
119
+ }
76
120
 
77
121
  // src/core/types.ts
78
122
  var CANVAS_MAIN_WINDOW = "main";
@@ -241,6 +285,8 @@ var canvasActions = {
241
285
  navigate: (itemId, to) => ({ type: `${P}navigate`, payload: { itemId, to, now: Date.now() } }),
242
286
  /** Returns a tab to what it showed before its last navigate. */
243
287
  back: (itemId) => ({ type: `${P}back`, payload: { itemId, now: Date.now() } }),
288
+ /** Re-does the last Back (the step Back left), with that step's live body. */
289
+ forward: (itemId) => ({ type: `${P}forward`, payload: { itemId, now: Date.now() } }),
244
290
  closeItem: (itemId) => ({ type: `${P}closeItem`, payload: { itemId } }),
245
291
  closeOthers: (itemId) => ({ type: `${P}closeOthers`, payload: { itemId } }),
246
292
  activate: (itemId) => ({ type: `${P}activate`, payload: { itemId } }),
@@ -441,6 +487,7 @@ function newItem(kind, key, title, data, now, steps) {
441
487
  const id = canvasItemId(kind, key);
442
488
  const history = steps?.history ?? [];
443
489
  const kept = steps?.kept ?? [];
490
+ const forward = steps?.forward ?? [];
444
491
  return {
445
492
  id,
446
493
  kind,
@@ -452,7 +499,8 @@ function newItem(kind, key, title, data, now, steps) {
452
499
  // A reused instance is the SAME live body coming back (Back, or returning to a kept step).
453
500
  instance: steps?.instance ?? `${now.toString(36)}:${id}`,
454
501
  ...history.length > 0 ? { history: history.slice(-CANVAS_HISTORY_LIMIT) } : {},
455
- ...kept.length > 0 ? { kept: kept.slice(-CANVAS_KEEP_ALIVE_DEPTH) } : {}
502
+ ...kept.length > 0 ? { kept: kept.slice(-CANVAS_KEEP_ALIVE_DEPTH) } : {},
503
+ ...forward.length > 0 ? { forward: forward.slice(-CANVAS_HISTORY_LIMIT) } : {}
456
504
  };
457
505
  }
458
506
  function historyEntry(item) {
@@ -498,12 +546,14 @@ function sanitizeHistory(raw) {
498
546
  function sanitizeItem(item) {
499
547
  const history = sanitizeHistory(item.history);
500
548
  const kept = sanitizeHistory(item.kept).slice(-CANVAS_KEEP_ALIVE_DEPTH);
501
- const { history: _history, kept: _kept, instance, ...rest } = item;
549
+ const forward = sanitizeHistory(item.forward);
550
+ const { history: _history, kept: _kept, forward: _forward, instance, ...rest } = item;
502
551
  return {
503
552
  ...rest,
504
553
  ...typeof instance === "string" && instance ? { instance } : {},
505
554
  ...history.length > 0 ? { history } : {},
506
- ...kept.length > 0 ? { kept } : {}
555
+ ...kept.length > 0 ? { kept } : {},
556
+ ...forward.length > 0 ? { forward } : {}
507
557
  };
508
558
  }
509
559
  function clampWidth(width) {
@@ -694,6 +744,9 @@ function canvasReducer(state = createInitialCanvasState(), action) {
694
744
  const keptIndex = keptAll.findIndex((entry) => canvasItemId(entry.kind, entry.key) === targetId);
695
745
  const revived = keptIndex >= 0 ? keptAll[keptIndex] : void 0;
696
746
  const kept = keptAll.filter((_, i) => i !== keptIndex);
747
+ const forwardAll = current.forward ?? [];
748
+ const nextForward = forwardAll[forwardAll.length - 1];
749
+ const forward = nextForward && canvasItemId(nextForward.kind, nextForward.key) === targetId ? forwardAll.slice(0, -1) : [];
697
750
  return replaceInPlace(
698
751
  state,
699
752
  current,
@@ -703,7 +756,7 @@ function canvasReducer(state = createInitialCanvasState(), action) {
703
756
  to.title !== void 0 ? to.title : revived?.title ?? null,
704
757
  to.data !== void 0 ? to.data : revived?.data ?? null,
705
758
  now,
706
- { history, kept, instance: revived?.instance }
759
+ { history, kept, forward, instance: revived?.instance }
707
760
  )
708
761
  );
709
762
  }
@@ -719,10 +772,36 @@ function canvasReducer(state = createInitialCanvasState(), action) {
719
772
  return focusExisting(canvasReducer(state, canvasActions.closeItem(itemId)), targetId);
720
773
  }
721
774
  const kept = [...(current.kept ?? []).filter((k) => canvasItemId(k.kind, k.key) !== itemId), historyEntry(current)];
775
+ const forward = [...current.forward ?? [], historyEntry(current)];
722
776
  return replaceInPlace(
723
777
  state,
724
778
  current,
725
- newItem(entry.kind, entry.key, entry.title, entry.data, now, { history: history.slice(0, -1), kept, instance: entry.instance })
779
+ newItem(entry.kind, entry.key, entry.title, entry.data, now, { history: history.slice(0, -1), kept, forward, instance: entry.instance })
780
+ );
781
+ }
782
+ case `${P}forward`: {
783
+ const { itemId, now } = action.payload;
784
+ const current = state.items[itemId];
785
+ const forwardAll = current?.forward ?? [];
786
+ const entry = forwardAll[forwardAll.length - 1];
787
+ if (!current || !entry || !paneOf(state, itemId)) return state;
788
+ const targetId = canvasItemId(entry.kind, entry.key);
789
+ if (targetId === itemId) return state;
790
+ if (state.items[targetId]) {
791
+ return focusExisting(canvasReducer(state, canvasActions.closeItem(itemId)), targetId);
792
+ }
793
+ const keptAll = current.kept ?? [];
794
+ const revived = keptAll.find((k) => canvasItemId(k.kind, k.key) === targetId);
795
+ const kept = keptAll.filter((k) => canvasItemId(k.kind, k.key) !== targetId);
796
+ return replaceInPlace(
797
+ state,
798
+ current,
799
+ newItem(entry.kind, entry.key, entry.title, entry.data, now, {
800
+ history: [...current.history ?? [], historyEntry(current)],
801
+ kept,
802
+ forward: forwardAll.slice(0, -1),
803
+ instance: revived?.instance ?? entry.instance
804
+ })
726
805
  );
727
806
  }
728
807
  case `${P}closeItem`: {
@@ -974,6 +1053,7 @@ function toPersistableSnapshot(state, isRestorable) {
974
1053
  }
975
1054
 
976
1055
  // src/core/rules.ts
1056
+ var CANVAS_PANE_MIN_WIDTH = 280;
977
1057
  var CANVAS_WEB_LAYOUT_RULES = {
978
1058
  defaultWidth: CANVAS_DEFAULT_WIDTH,
979
1059
  minWidth: CANVAS_MIN_WIDTH,
@@ -981,7 +1061,9 @@ var CANVAS_WEB_LAYOUT_RULES = {
981
1061
  // Split down 70/30 like the desktop: what opens below (notifications, a feed) is the smaller pane.
982
1062
  stackSplit: 0.7,
983
1063
  sideSplit: 0.5,
984
- paneMinWidth: 0
1064
+ // A pane narrower than this cannot show a header (Back, a readable tab, "+", the menu, close) and a
1065
+ // body side by side with another: a split right that would go under it stacks instead.
1066
+ paneMinWidth: CANVAS_PANE_MIN_WIDTH
985
1067
  };
986
1068
  var clampFraction = (value, fallback) => Number.isFinite(value) ? Math.min(0.88, Math.max(0.12, value)) : fallback;
987
1069
  function resolveCanvasLayoutRules(rules) {
@@ -1020,6 +1102,9 @@ var CANVAS_SHORTCUTS = [
1020
1102
  { id: "canvas.toggleFullscreen", accelerator: "CmdOrCtrl+Shift+Enter", when: "canvas-focus", label: "Expand canvas", web: true },
1021
1103
  { id: "canvas.splitRight", accelerator: "CmdOrCtrl+Alt+\\", when: "canvas-focus", label: "Split right", web: true },
1022
1104
  { id: "canvas.splitDown", accelerator: "CmdOrCtrl+Alt+Shift+\\", when: "canvas-focus", label: "Split down", web: true },
1105
+ // Browsers own ⌘[ / ⌘] for their own history: data for a desktop host (and the header's Back / Forward).
1106
+ { id: "canvas.back", accelerator: "CmdOrCtrl+[", when: "canvas-focus", label: "Back", web: false },
1107
+ { id: "canvas.forward", accelerator: "CmdOrCtrl+]", when: "canvas-focus", label: "Forward", web: false },
1023
1108
  { id: "canvas.closeTab", accelerator: "CmdOrCtrl+W", when: "canvas-focus", label: "Close tab", web: false },
1024
1109
  { id: "canvas.nextTab", accelerator: "Ctrl+Tab", when: "canvas-focus", label: "Next tab", web: false },
1025
1110
  { id: "canvas.previousTab", accelerator: "Ctrl+Shift+Tab", when: "canvas-focus", label: "Previous tab", web: false }
@@ -1069,11 +1154,17 @@ function runCanvasCommand(controller, id, windowId = controller.windowId) {
1069
1154
  if (!frame2?.isOpen || !pane) return false;
1070
1155
  controller.splitPane(pane.id, id === "canvas.splitRight" ? "horizontal" : "vertical", pane.itemIds.length > 1 ? pane.activeItemId ?? void 0 : void 0);
1071
1156
  return true;
1157
+ case "canvas.back":
1158
+ if (!frame2?.isOpen || !pane?.activeItemId) return false;
1159
+ return controller.back(pane.activeItemId) !== null;
1160
+ case "canvas.forward":
1161
+ if (!frame2?.isOpen || !pane?.activeItemId) return false;
1162
+ return controller.forward(pane.activeItemId) !== null;
1072
1163
  default:
1073
1164
  return false;
1074
1165
  }
1075
1166
  }
1076
- var CODE_FOR = { "\\": "Backslash", Tab: "Tab", Escape: "Escape", Enter: "Enter" };
1167
+ var CODE_FOR = { "\\": "Backslash", "[": "BracketLeft", "]": "BracketRight", Tab: "Tab", Escape: "Escape", Enter: "Enter" };
1077
1168
  function matchesCanvasAccelerator(input, accelerator, isMac3) {
1078
1169
  const parts = accelerator.split("+");
1079
1170
  const key = parts.pop() ?? "";
@@ -1190,6 +1281,13 @@ function view(shared, windowId) {
1190
1281
  dispatch(canvasActions.back(itemId));
1191
1282
  return canvasItemId(entry.kind, entry.key);
1192
1283
  },
1284
+ forward(itemId) {
1285
+ const forward = store.getState().items[itemId]?.forward ?? [];
1286
+ const entry = forward[forward.length - 1];
1287
+ if (!entry) return null;
1288
+ dispatch(canvasActions.forward(itemId));
1289
+ return canvasItemId(entry.kind, entry.key);
1290
+ },
1193
1291
  newTab(paneId) {
1194
1292
  const state = store.getState();
1195
1293
  const frame2 = canvasWindowFrame(state, windowId);
@@ -1456,6 +1554,16 @@ function selectCanvasWindowItemCount(s, windowId) {
1456
1554
  var NO_WINDOWS = Object.freeze({});
1457
1555
  var NO_HISTORY = Object.freeze([]);
1458
1556
  var selectCanvasCanGoBack = (s, itemId) => (s.items[itemId]?.history?.length ?? 0) > 0;
1557
+ var selectCanvasCanGoForward = (s, itemId) => (s.items[itemId]?.forward?.length ?? 0) > 0;
1558
+ function selectCanvasPresentedPaneId(s, windowId) {
1559
+ const f = frame(s, windowId);
1560
+ if (!f) return null;
1561
+ const ids = listPaneIds(f.layout);
1562
+ const holds = (id) => (s.panes[id]?.itemIds.length ?? 0) > 0;
1563
+ const focused = ids.find((id) => id === f.focusedPaneId);
1564
+ if (focused && holds(focused)) return focused;
1565
+ return ids.find(holds) ?? focused ?? ids[0] ?? null;
1566
+ }
1459
1567
 
1460
1568
  // src/core/presentation.ts
1461
1569
  var CANVAS_PHONE_MAX_WIDTH = 767;
@@ -1499,7 +1607,9 @@ function toggleKind(controller, input) {
1499
1607
  const before = selectCanvasKindVisibility(controller.getState(), input.kind, key);
1500
1608
  if (before === "visible") {
1501
1609
  if ((input.whenVisible ?? "close") === "close") {
1610
+ const windowId = canvasWindowOfItem(controller.getState(), itemId);
1502
1611
  controller.close(itemId);
1612
+ if (windowId && selectCanvasWindowItemCount(controller.getState(), windowId) === 0) controller.hide(windowId);
1503
1613
  } else {
1504
1614
  controller.hide(canvasWindowOfItem(controller.getState(), itemId) ?? void 0);
1505
1615
  }
@@ -1521,7 +1631,7 @@ function toggleKind(controller, input) {
1521
1631
  }
1522
1632
 
1523
1633
  // src/react/layer.tsx
1524
- var import_react = require("react");
1634
+ var import_react2 = require("react");
1525
1635
  var import_react_dom = require("react-dom");
1526
1636
  var import_jsx_runtime = require("react/jsx-runtime");
1527
1637
  function createCanvasLayer(getPorts) {
@@ -1588,9 +1698,9 @@ function createCanvasLayer(getPorts) {
1588
1698
  };
1589
1699
  }
1590
1700
  function CanvasDomOverlay({ layer }) {
1591
- const elements = (0, import_react.useSyncExternalStore)(layer.dom.subscribe, layer.dom.list, layer.dom.list);
1592
- const [mounted, setMounted] = (0, import_react.useState)(false);
1593
- (0, import_react.useEffect)(() => setMounted(true), []);
1701
+ const elements = (0, import_react2.useSyncExternalStore)(layer.dom.subscribe, layer.dom.list, layer.dom.list);
1702
+ const [mounted, setMounted] = (0, import_react2.useState)(false);
1703
+ (0, import_react2.useEffect)(() => setMounted(true), []);
1594
1704
  if (!mounted || elements.length === 0 || typeof document === "undefined") return null;
1595
1705
  return (0, import_react_dom.createPortal)(
1596
1706
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "mxc-overlay", "aria-hidden": "true", children: elements.map(([id, element]) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FloatingView, { element }, id)) }),
@@ -1606,11 +1716,11 @@ function FloatingView({ element }) {
1606
1716
  return null;
1607
1717
  }
1608
1718
  function Tooltip({ element }) {
1609
- const ref = (0, import_react.useRef)(null);
1719
+ const ref = (0, import_react2.useRef)(null);
1610
1720
  const { anchor, side, text } = element;
1611
1721
  const top = side === "bottom" ? anchor.y + anchor.height + 6 : anchor.y - 6;
1612
1722
  const centre = anchor.x + anchor.width / 2;
1613
- (0, import_react.useLayoutEffect)(() => {
1723
+ (0, import_react2.useLayoutEffect)(() => {
1614
1724
  const el = ref.current;
1615
1725
  if (!el) return;
1616
1726
  const width = el.offsetWidth;
@@ -1714,7 +1824,7 @@ function escapeBelongsElsewhere(event) {
1714
1824
  const active = typeof document === "undefined" ? null : document.activeElement;
1715
1825
  return !!active?.closest?.('[role="menu"], [role="dialog"], [role="alertdialog"], [role="listbox"]');
1716
1826
  }
1717
- var CanvasContext = (0, import_react2.createContext)(null);
1827
+ var CanvasContext = (0, import_react3.createContext)(null);
1718
1828
  function sessionHooksFor(kindId) {
1719
1829
  const kind = getCanvasKind(kindId);
1720
1830
  if (!kind || kind.restore !== "session") return void 0;
@@ -1741,13 +1851,13 @@ function CanvasProvider({
1741
1851
  overlay,
1742
1852
  onNativeBodyFrame
1743
1853
  }) {
1744
- const hostPorts = (0, import_react2.useMemo)(
1854
+ const hostPorts = (0, import_react3.useMemo)(
1745
1855
  () => ({ itemActions, popOut, dockBack, showMenu, overlay, onNativeBodyFrame }),
1746
1856
  [itemActions, popOut, dockBack, showMenu, overlay, onNativeBodyFrame]
1747
1857
  );
1748
- const portsRef = (0, import_react2.useRef)(hostPorts);
1858
+ const portsRef = (0, import_react3.useRef)(hostPorts);
1749
1859
  portsRef.current = hostPorts;
1750
- const [{ controller, layer, launcherEntries }] = (0, import_react2.useState)(() => ({
1860
+ const [{ controller, layer, launcherEntries, itemMenus }] = (0, import_react3.useState)(() => ({
1751
1861
  controller: createCanvasController({
1752
1862
  store: store ?? createCanvasStore(),
1753
1863
  persistence: persistence === void 0 ? createLocalStorageCanvasPersistence() : persistence,
@@ -1761,11 +1871,12 @@ function CanvasProvider({
1761
1871
  preferredTarget: (kind) => getCanvasKind(kind)?.preferredTarget
1762
1872
  }),
1763
1873
  layer: createCanvasLayer(() => portsRef.current),
1764
- launcherEntries: createLauncherEntryStore()
1874
+ launcherEntries: createLauncherEntryStore(),
1875
+ itemMenus: createCanvasItemMenus()
1765
1876
  }));
1766
1877
  const windowId = controller.windowId;
1767
- (0, import_react2.useEffect)(() => controller.start(), [controller]);
1768
- (0, import_react2.useEffect)(() => {
1878
+ (0, import_react3.useEffect)(() => controller.start(), [controller]);
1879
+ (0, import_react3.useEffect)(() => {
1769
1880
  if (!hotkeys) return;
1770
1881
  const isMac3 = isMacPlatform();
1771
1882
  const applies = (when, event) => {
@@ -1786,9 +1897,9 @@ function CanvasProvider({
1786
1897
  window.addEventListener("keydown", onKey);
1787
1898
  return () => window.removeEventListener("keydown", onKey);
1788
1899
  }, [controller, hotkeys, windowId]);
1789
- const value = (0, import_react2.useMemo)(
1790
- () => ({ controller, windowId, ports: hostPorts, layer, regionWidth, tabStrip, launcherEntries }),
1791
- [controller, windowId, hostPorts, layer, regionWidth, tabStrip, launcherEntries]
1900
+ const value = (0, import_react3.useMemo)(
1901
+ () => ({ controller, windowId, ports: hostPorts, layer, regionWidth, tabStrip, launcherEntries, itemMenus }),
1902
+ [controller, windowId, hostPorts, layer, regionWidth, tabStrip, launcherEntries, itemMenus]
1792
1903
  );
1793
1904
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(CanvasContext.Provider, { value, children: [
1794
1905
  children,
@@ -1797,14 +1908,14 @@ function CanvasProvider({
1797
1908
  }
1798
1909
  function CanvasWindowScope({ windowId, children }) {
1799
1910
  const parent = useCanvasContext();
1800
- const value = (0, import_react2.useMemo)(() => {
1911
+ const value = (0, import_react3.useMemo)(() => {
1801
1912
  const id = canvasWindowId(windowId);
1802
1913
  return { ...parent, windowId: id, controller: parent.controller.forWindow(id) };
1803
1914
  }, [parent, windowId]);
1804
1915
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(CanvasContext.Provider, { value, children });
1805
1916
  }
1806
1917
  function useCanvasContext() {
1807
- const value = (0, import_react2.useContext)(CanvasContext);
1918
+ const value = (0, import_react3.useContext)(CanvasContext);
1808
1919
  if (!value) throw new Error("[@ai-matrx/canvas] useCanvas* must be used inside <CanvasProvider>.");
1809
1920
  return value;
1810
1921
  }
@@ -1812,16 +1923,16 @@ function useCanvasInternals() {
1812
1923
  return useCanvasContext();
1813
1924
  }
1814
1925
  function useOptionalCanvasInternals() {
1815
- return (0, import_react2.useContext)(CanvasContext);
1926
+ return (0, import_react3.useContext)(CanvasContext);
1816
1927
  }
1817
1928
  function useCanvas() {
1818
1929
  return useCanvasContext().controller;
1819
1930
  }
1820
1931
  function useOptionalCanvas() {
1821
- return (0, import_react2.useContext)(CanvasContext)?.controller ?? null;
1932
+ return (0, import_react3.useContext)(CanvasContext)?.controller ?? null;
1822
1933
  }
1823
1934
  function useCanvasWindowId() {
1824
- return (0, import_react2.useContext)(CanvasContext)?.windowId ?? CANVAS_MAIN_WINDOW;
1935
+ return (0, import_react3.useContext)(CanvasContext)?.windowId ?? CANVAS_MAIN_WINDOW;
1825
1936
  }
1826
1937
  function useCanvasHostPorts() {
1827
1938
  return useCanvasContext().ports;
@@ -1830,16 +1941,16 @@ function useCanvasNativeBodies() {
1830
1941
  return useCanvasContext().layer.native;
1831
1942
  }
1832
1943
  function useRegisterCanvasPresence() {
1833
- const context = (0, import_react2.useContext)(CanvasContext);
1944
+ const context = (0, import_react3.useContext)(CanvasContext);
1834
1945
  const controller = context?.controller;
1835
1946
  const windowId = context?.windowId;
1836
- (0, import_react2.useEffect)(() => controller?.registerPresentation(windowId), [controller, windowId]);
1947
+ (0, import_react3.useEffect)(() => controller?.registerPresentation(windowId), [controller, windowId]);
1837
1948
  }
1838
1949
  var noPresence = () => () => void 0;
1839
1950
  function useCanvasIsPresented() {
1840
- const context = (0, import_react2.useContext)(CanvasContext);
1951
+ const context = (0, import_react3.useContext)(CanvasContext);
1841
1952
  const controller = context?.controller;
1842
- return (0, import_react2.useSyncExternalStore)(
1953
+ return (0, import_react3.useSyncExternalStore)(
1843
1954
  controller ? controller.subscribePresentation : noPresence,
1844
1955
  () => controller?.isPresented() ?? false,
1845
1956
  () => false
@@ -1847,7 +1958,7 @@ function useCanvasIsPresented() {
1847
1958
  }
1848
1959
  function useCanvasState(selector) {
1849
1960
  const { store } = useCanvasContext().controller;
1850
- return (0, import_react2.useSyncExternalStore)(
1961
+ return (0, import_react3.useSyncExternalStore)(
1851
1962
  store.subscribe,
1852
1963
  () => selector(store.getState()),
1853
1964
  () => selector(store.getState())
@@ -1855,9 +1966,9 @@ function useCanvasState(selector) {
1855
1966
  }
1856
1967
  var NO_SUBSCRIPTION = () => () => void 0;
1857
1968
  function useOptionalCanvasState(selector, fallback) {
1858
- const store = (0, import_react2.useContext)(CanvasContext)?.controller.store;
1969
+ const store = (0, import_react3.useContext)(CanvasContext)?.controller.store;
1859
1970
  const read = () => store ? selector(store.getState()) : fallback;
1860
- return (0, import_react2.useSyncExternalStore)(store ? store.subscribe : NO_SUBSCRIPTION, read, read);
1971
+ return (0, import_react3.useSyncExternalStore)(store ? store.subscribe : NO_SUBSCRIPTION, read, read);
1861
1972
  }
1862
1973
  function useCanvasKindToggle(input) {
1863
1974
  const controller = useCanvasContext().controller;
@@ -1871,28 +1982,28 @@ function useCanvasKindToggle(input) {
1871
1982
  };
1872
1983
  }
1873
1984
  function useCanvasKinds() {
1874
- (0, import_react2.useSyncExternalStore)(subscribeCanvasKinds, getCanvasKindsVersion, getCanvasKindsVersion);
1985
+ (0, import_react3.useSyncExternalStore)(subscribeCanvasKinds, getCanvasKindsVersion, getCanvasKindsVersion);
1875
1986
  return listCanvasKinds();
1876
1987
  }
1877
1988
  function useCanvasKind(id) {
1878
- (0, import_react2.useSyncExternalStore)(subscribeCanvasKinds, getCanvasKindsVersion, getCanvasKindsVersion);
1989
+ (0, import_react3.useSyncExternalStore)(subscribeCanvasKinds, getCanvasKindsVersion, getCanvasKindsVersion);
1879
1990
  return getCanvasKind(id);
1880
1991
  }
1881
1992
  var NO_ENTRIES = Object.freeze([]);
1882
1993
  var noEntries = () => NO_ENTRIES;
1883
1994
  function useCanvasLauncherEntry(entry) {
1884
- const store = (0, import_react2.useContext)(CanvasContext)?.launcherEntries;
1995
+ const store = (0, import_react3.useContext)(CanvasContext)?.launcherEntries;
1885
1996
  const fingerprint = JSON.stringify([entry.kind, entry.key, entry.title ?? null, entry.data ?? null]);
1886
1997
  const icon = entry.icon;
1887
- (0, import_react2.useEffect)(() => {
1998
+ (0, import_react3.useEffect)(() => {
1888
1999
  if (!store) return void 0;
1889
2000
  const [kind, key, title, data] = JSON.parse(fingerprint);
1890
2001
  return store.add({ kind, key, title, data, ...icon ? { icon } : {} });
1891
2002
  }, [store, fingerprint, icon]);
1892
2003
  }
1893
2004
  function useCanvasLauncherEntries() {
1894
- const store = (0, import_react2.useContext)(CanvasContext)?.launcherEntries;
1895
- const all = (0, import_react2.useSyncExternalStore)(store ? store.subscribe : NO_SUBSCRIPTION, store ? store.list : noEntries, store ? store.list : noEntries);
2005
+ const store = (0, import_react3.useContext)(CanvasContext)?.launcherEntries;
2006
+ const all = (0, import_react3.useSyncExternalStore)(store ? store.subscribe : NO_SUBSCRIPTION, store ? store.list : noEntries, store ? store.list : noEntries);
1896
2007
  const seen = /* @__PURE__ */ new Set();
1897
2008
  const out = [];
1898
2009
  for (let i = all.length - 1; i >= 0; i -= 1) {
@@ -1905,14 +2016,14 @@ function useCanvasLauncherEntries() {
1905
2016
  }
1906
2017
 
1907
2018
  // src/react/CanvasColumn.tsx
1908
- var import_react9 = require("react");
2019
+ var import_react10 = require("react");
1909
2020
  var import_tap_target = require("@ai-matrx/tap-target");
1910
2021
 
1911
2022
  // src/react/bodies.tsx
1912
- var import_react7 = require("react");
2023
+ var import_react8 = require("react");
1913
2024
 
1914
2025
  // src/react/chrome.tsx
1915
- var import_react3 = require("react");
2026
+ var import_react4 = require("react");
1916
2027
 
1917
2028
  // src/react/icons.tsx
1918
2029
  var import_jsx_runtime3 = require("react/jsx-runtime");
@@ -2010,16 +2121,15 @@ var LayoutGridIcon = (p) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Glyph,
2010
2121
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("rect", { width: "7", height: "7", x: "14", y: "14", rx: "1" }),
2011
2122
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("rect", { width: "7", height: "7", x: "3", y: "14", rx: "1" })
2012
2123
  ] });
2013
- var ChevronLeftIcon = (p) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Glyph, { ...p, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "m15 18-6-6 6-6" }) });
2014
- var ChevronRightIcon = (p) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Glyph, { ...p, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "m9 18 6-6-6-6" }) });
2124
+ var ChevronDownIcon = (p) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Glyph, { ...p, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "m6 9 6 6 6-6" }) });
2015
2125
 
2016
2126
  // src/react/chrome.tsx
2017
2127
  var import_jsx_runtime4 = require("react/jsx-runtime");
2018
- var useIsoLayoutEffect = typeof window === "undefined" ? import_react3.useEffect : import_react3.useLayoutEffect;
2128
+ var useIsoLayoutEffect2 = typeof window === "undefined" ? import_react4.useEffect : import_react4.useLayoutEffect;
2019
2129
  function useTruncated(title) {
2020
- const ref = (0, import_react3.useRef)(null);
2021
- const [truncated, setTruncated] = (0, import_react3.useState)(false);
2022
- useIsoLayoutEffect(() => {
2130
+ const ref = (0, import_react4.useRef)(null);
2131
+ const [truncated, setTruncated] = (0, import_react4.useState)(false);
2132
+ useIsoLayoutEffect2(() => {
2023
2133
  const el = ref.current;
2024
2134
  if (!el) return void 0;
2025
2135
  const check = () => setTruncated(el.scrollWidth - el.clientWidth > 1);
@@ -2033,15 +2143,15 @@ function useTruncated(title) {
2033
2143
  var cx = (...names) => names.filter(Boolean).join(" ");
2034
2144
  function useTooltip(text) {
2035
2145
  const internals = useOptionalCanvasInternals();
2036
- const timer = (0, import_react3.useRef)(null);
2037
- const hide = (0, import_react3.useRef)(null);
2146
+ const timer = (0, import_react4.useRef)(null);
2147
+ const hide = (0, import_react4.useRef)(null);
2038
2148
  const close = () => {
2039
2149
  if (timer.current) clearTimeout(timer.current);
2040
2150
  timer.current = null;
2041
2151
  hide.current?.();
2042
2152
  hide.current = null;
2043
2153
  };
2044
- (0, import_react3.useEffect)(() => close, []);
2154
+ (0, import_react4.useEffect)(() => close, []);
2045
2155
  if (!text) return { close, handlers: {} };
2046
2156
  if (!internals) return { close, handlers: { title: text } };
2047
2157
  const open = (element, delay) => {
@@ -2067,7 +2177,7 @@ function useTooltip(text) {
2067
2177
  }
2068
2178
  };
2069
2179
  }
2070
- var CanvasHeaderButton = (0, import_react3.forwardRef)(function CanvasHeaderButton2({ label, shortcut, available = true, size = "sm", pressed, className, children, onClick, onPointerEnter, onPointerLeave, onPointerDown, onFocus, onBlur, ...rest }, ref) {
2180
+ var CanvasHeaderButton = (0, import_react4.forwardRef)(function CanvasHeaderButton2({ label, shortcut, available = true, size = "sm", pressed, className, children, onClick, onPointerEnter, onPointerLeave, onPointerDown, onFocus, onBlur, ...rest }, ref) {
2071
2181
  const tooltip = useTooltip(shortcut ? `${label} (${shortcut})` : label);
2072
2182
  const own = tooltip.handlers;
2073
2183
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
@@ -2112,12 +2222,12 @@ var CanvasHeaderButton = (0, import_react3.forwardRef)(function CanvasHeaderButt
2112
2222
  }
2113
2223
  );
2114
2224
  });
2115
- var CanvasMenuTrigger = (0, import_react3.forwardRef)(
2225
+ var CanvasMenuTrigger = (0, import_react4.forwardRef)(
2116
2226
  function CanvasMenuTrigger2({ label = "More", ...props }, ref) {
2117
2227
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(CanvasHeaderButton, { ref, label, ...props, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(MoreIcon, {}) });
2118
2228
  }
2119
2229
  );
2120
- var CanvasPanel = (0, import_react3.forwardRef)(function CanvasPanel2({
2230
+ var CanvasPanel = (0, import_react4.forwardRef)(function CanvasPanel2({
2121
2231
  panelId,
2122
2232
  title,
2123
2233
  leading,
@@ -2167,7 +2277,7 @@ var CanvasPanel = (0, import_react3.forwardRef)(function CanvasPanel2({
2167
2277
  function CanvasPanelEmpty({ children, className }) {
2168
2278
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { "data-slot": "canvas-panel-empty", className: cx("mxc-empty-line", className), children });
2169
2279
  }
2170
- var CanvasTab = (0, import_react3.forwardRef)(function CanvasTab2({ title, icon, note, active, onSelect, onClose, className, onKeyDown, onClick, ...rest }, ref) {
2280
+ var CanvasTab = (0, import_react4.forwardRef)(function CanvasTab2({ title, icon, note, active, onSelect, onClose, className, onKeyDown, onClick, ...rest }, ref) {
2171
2281
  const shown = title || "New tab";
2172
2282
  const name = useTruncated(shown);
2173
2283
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
@@ -2236,9 +2346,9 @@ function BrowserTabs({ tabs, activeId, onSelect, onClose, onNew, newLabel = "New
2236
2346
  ] });
2237
2347
  }
2238
2348
  function BrowserBar({ url, canGoBack, canGoForward, onBack, onForward, onReload, onNavigate, trailing, className }) {
2239
- const [draft, setDraft] = (0, import_react3.useState)(url);
2240
- const [editing, setEditing] = (0, import_react3.useState)(false);
2241
- (0, import_react3.useEffect)(() => {
2349
+ const [draft, setDraft] = (0, import_react4.useState)(url);
2350
+ const [editing, setEditing] = (0, import_react4.useState)(false);
2351
+ (0, import_react4.useEffect)(() => {
2242
2352
  if (!editing) setDraft(url);
2243
2353
  }, [url, editing]);
2244
2354
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { "data-slot": "browser-bar", className: cx("mxc-browser-bar", className), children: [
@@ -2280,15 +2390,15 @@ function BrowserBar({ url, canGoBack, canGoForward, onBack, onForward, onReload,
2280
2390
  }
2281
2391
 
2282
2392
  // src/react/dnd.ts
2283
- var import_react4 = require("react");
2393
+ var import_react5 = require("react");
2284
2394
  var CANVAS_DRAG_MIME = "application/x-matrx-canvas-item";
2285
2395
  function useTabDrag(itemId, title) {
2286
2396
  const { layer } = useCanvasInternals();
2287
- const active = (0, import_react4.useRef)([]);
2397
+ const active = (0, import_react5.useRef)([]);
2288
2398
  const end = () => {
2289
2399
  for (const dispose of active.current.splice(0)) dispose();
2290
2400
  };
2291
- (0, import_react4.useEffect)(() => end, []);
2401
+ (0, import_react5.useEffect)(() => end, []);
2292
2402
  return {
2293
2403
  draggable: true,
2294
2404
  onDragStart: (event) => {
@@ -2303,8 +2413,8 @@ function useTabDrag(itemId, title) {
2303
2413
  function usePaneDrop(paneId, getOutline) {
2304
2414
  const canvas = useCanvas();
2305
2415
  const { layer } = useCanvasInternals();
2306
- const drop = (0, import_react4.useRef)(null);
2307
- (0, import_react4.useEffect)(() => () => drop.current?.(), []);
2416
+ const drop = (0, import_react5.useRef)(null);
2417
+ (0, import_react5.useEffect)(() => () => drop.current?.(), []);
2308
2418
  const hide = () => {
2309
2419
  drop.current?.();
2310
2420
  drop.current = null;
@@ -2435,16 +2545,19 @@ function useItemKind(kindId) {
2435
2545
  const registered = useCanvasKind(kindId);
2436
2546
  return kindId === CANVAS_LAUNCHER_KIND ? CANVAS_LAUNCHER_KIND_DEFINITION : registered;
2437
2547
  }
2548
+ function resolveItemKind(kindId, lookup) {
2549
+ return kindId === CANVAS_LAUNCHER_KIND ? CANVAS_LAUNCHER_KIND_DEFINITION : lookup(kindId);
2550
+ }
2438
2551
 
2439
2552
  // src/react/presentation.tsx
2440
- var import_react5 = require("react");
2441
- var CanvasPresentationContext = (0, import_react5.createContext)(null);
2553
+ var import_react6 = require("react");
2554
+ var CanvasPresentationContext = (0, import_react6.createContext)(null);
2442
2555
  function useCanvasPresentation() {
2443
- return (0, import_react5.useContext)(CanvasPresentationContext);
2556
+ return (0, import_react6.useContext)(CanvasPresentationContext);
2444
2557
  }
2445
2558
 
2446
2559
  // src/react/slots.ts
2447
- var import_react6 = require("react");
2560
+ var import_react7 = require("react");
2448
2561
  var ZERO = Object.freeze({ width: 0, height: 0 });
2449
2562
  function createCanvasSlots() {
2450
2563
  const slots = /* @__PURE__ */ new Map();
@@ -2511,16 +2624,16 @@ function createCanvasSlots() {
2511
2624
  }
2512
2625
  };
2513
2626
  }
2514
- var CanvasSlotsContext = (0, import_react6.createContext)(null);
2627
+ var CanvasSlotsContext = (0, import_react7.createContext)(null);
2515
2628
  function useCanvasSlots() {
2516
- return (0, import_react6.useContext)(CanvasSlotsContext);
2629
+ return (0, import_react7.useContext)(CanvasSlotsContext);
2517
2630
  }
2518
- var CanvasSinglePaneContext = (0, import_react6.createContext)(false);
2631
+ var CanvasSinglePaneContext = (0, import_react7.createContext)(false);
2519
2632
  function useCanvasSinglePane() {
2520
- return (0, import_react6.useContext)(CanvasSinglePaneContext);
2633
+ return (0, import_react7.useContext)(CanvasSinglePaneContext);
2521
2634
  }
2522
2635
  function useSlotSize(slots, paneId) {
2523
- return (0, import_react6.useSyncExternalStore)(
2636
+ return (0, import_react7.useSyncExternalStore)(
2524
2637
  slots.subscribe,
2525
2638
  () => slots.size(paneId),
2526
2639
  () => ZERO
@@ -2529,12 +2642,12 @@ function useSlotSize(slots, paneId) {
2529
2642
 
2530
2643
  // src/react/bodies.tsx
2531
2644
  var import_jsx_runtime6 = require("react/jsx-runtime");
2532
- var useIsoLayoutEffect2 = typeof window === "undefined" ? import_react7.useEffect : import_react7.useLayoutEffect;
2645
+ var useIsoLayoutEffect3 = typeof window === "undefined" ? import_react8.useEffect : import_react8.useLayoutEffect;
2533
2646
  var LAZY = /* @__PURE__ */ Symbol.for("ai-matrx.canvas.lazy");
2534
2647
  function kindComponent(kind) {
2535
2648
  if (kind.component) return kind.component;
2536
2649
  const holder = kind;
2537
- if (!holder[LAZY] && kind.load) holder[LAZY] = (0, import_react7.lazy)(kind.load);
2650
+ if (!holder[LAZY] && kind.load) holder[LAZY] = (0, import_react8.lazy)(kind.load);
2538
2651
  return holder[LAZY] ?? null;
2539
2652
  }
2540
2653
  function resetKindComponent(kind) {
@@ -2542,7 +2655,7 @@ function resetKindComponent(kind) {
2542
2655
  }
2543
2656
  function usePanePresentation(slots, paneId, isFullscreen, paneCount) {
2544
2657
  const size = useSlotSize(slots, paneId);
2545
- return (0, import_react7.useMemo)(
2658
+ return (0, import_react8.useMemo)(
2546
2659
  () => canvasPresentation({ width: size.width, height: size.height, isFullscreen, paneCount }),
2547
2660
  [size.width, size.height, isFullscreen, paneCount]
2548
2661
  );
@@ -2580,17 +2693,24 @@ function layerSignature(state, windowId, onlyPane) {
2580
2693
  }
2581
2694
  }
2582
2695
  rows.sort((a, b) => a.born - b.born || a.hostId.localeCompare(b.hostId));
2583
- const head = [frame2.isOpen ? "1" : "0", frame2.isFullscreen ? "1" : "0", frame2.focusedPaneId, String(paneIds.length)].join(SEP2);
2696
+ const head = [
2697
+ frame2.isOpen ? "1" : "0",
2698
+ frame2.isFullscreen ? "1" : "0",
2699
+ frame2.focusedPaneId,
2700
+ String(paneIds.length),
2701
+ selectCanvasPresentedPaneId(state, windowId) ?? ""
2702
+ ].join(SEP2);
2584
2703
  return [head, ...rows.map((r) => r.row)].join(ROW);
2585
2704
  }
2586
2705
  function parseSignature(signature) {
2587
2706
  if (!signature) return null;
2588
2707
  const [head = "", ...rows] = signature.split(ROW);
2589
- const [open, full, focusedPaneId = "", count = "1"] = head.split(SEP2);
2708
+ const [open, full, focusedPaneId = "", count = "1", presentedPaneId = ""] = head.split(SEP2);
2590
2709
  return {
2591
2710
  isOpen: open === "1",
2592
2711
  isFullscreen: full === "1",
2593
2712
  focusedPaneId,
2713
+ presentedPaneId,
2594
2714
  paneCount: Number(count) || 1,
2595
2715
  placed: rows.map((row) => {
2596
2716
  const [hostId = "", itemId = "", paneId = "", active, kept] = row.split(SEP2);
@@ -2642,12 +2762,12 @@ function CanvasBodyLayer() {
2642
2762
  const slots = useCanvasSlots();
2643
2763
  const signature = useCanvasState((s) => layerSignature(s, windowId));
2644
2764
  const single = useCanvasSinglePane();
2645
- const root = (0, import_react7.useRef)(null);
2646
- const [placement] = (0, import_react7.useState)(
2765
+ const root = (0, import_react8.useRef)(null);
2766
+ const [placement] = (0, import_react8.useState)(
2647
2767
  () => slots ? createPlacement(() => root.current, slots, () => layer.native.remeasure()) : null
2648
2768
  );
2649
- useIsoLayoutEffect2(() => placement?.place());
2650
- useIsoLayoutEffect2(() => slots && placement ? slots.subscribe(placement.place) : void 0, [slots, placement]);
2769
+ useIsoLayoutEffect3(() => placement?.place());
2770
+ useIsoLayoutEffect3(() => slots && placement ? slots.subscribe(placement.place) : void 0, [slots, placement]);
2651
2771
  const shape = parseSignature(signature);
2652
2772
  if (!slots || !placement || !shape) return null;
2653
2773
  return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { ref: root, className: "mxc-bodies", "data-mxc-bodies": windowId, children: shape.placed.map(({ hostId, itemId, paneId, active, kept }) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
@@ -2657,7 +2777,7 @@ function CanvasBodyLayer() {
2657
2777
  itemId,
2658
2778
  kept,
2659
2779
  paneId,
2660
- visible: shape.isOpen && active && (!single || paneId === shape.focusedPaneId),
2780
+ visible: shape.isOpen && active && (!single || paneId === shape.presentedPaneId),
2661
2781
  focused: active && shape.focusedPaneId === paneId,
2662
2782
  isFullscreen: shape.isFullscreen,
2663
2783
  paneCount: shape.paneCount,
@@ -2700,7 +2820,7 @@ function stepAsItem(step, openedAt) {
2700
2820
  ...step.instance ? { instance: step.instance } : {}
2701
2821
  };
2702
2822
  }
2703
- var BodyHost = (0, import_react7.memo)(function BodyHost2({
2823
+ var BodyHost = (0, import_react8.memo)(function BodyHost2({
2704
2824
  hostId,
2705
2825
  itemId,
2706
2826
  kept,
@@ -2720,16 +2840,16 @@ var BodyHost = (0, import_react7.memo)(function BodyHost2({
2720
2840
  return ownerItem ? keptCanvasSteps(ownerItem).find((e) => e.instance === hostId) : void 0;
2721
2841
  });
2722
2842
  const owner = useCanvasState((s) => kept ? s.items[itemId]?.openedAt : void 0);
2723
- const keptItem = (0, import_react7.useMemo)(() => step ? stepAsItem(step, owner ?? 0) : void 0, [step, owner]);
2843
+ const keptItem = (0, import_react8.useMemo)(() => step ? stepAsItem(step, owner ?? 0) : void 0, [step, owner]);
2724
2844
  const item = kept ? keptItem : live;
2725
2845
  const kind = useItemKind(item?.kind ?? "");
2726
- const element = (0, import_react7.useRef)(null);
2727
- const [everShown, setEverShown] = (0, import_react7.useState)(visible);
2846
+ const element = (0, import_react8.useRef)(null);
2847
+ const [everShown, setEverShown] = (0, import_react8.useState)(visible);
2728
2848
  if (visible && !everShown) setEverShown(true);
2729
2849
  const mounted = kind?.unmountWhenHidden ? visible : everShown;
2730
2850
  const presentation = usePanePresentation(slots, paneId, isFullscreen, paneCount);
2731
2851
  const drop = usePaneDrop(paneId, () => slots.section(paneId));
2732
- useIsoLayoutEffect2(() => {
2852
+ useIsoLayoutEffect3(() => {
2733
2853
  const el = element.current;
2734
2854
  if (!placement || !el) return void 0;
2735
2855
  return placement.register(hostId, { element: el, paneId });
@@ -2795,7 +2915,7 @@ function ItemContent({
2795
2915
  }
2796
2916
  return rendered;
2797
2917
  }
2798
- var Rendered = (0, import_react7.memo)(function Rendered2({
2918
+ var Rendered = (0, import_react8.memo)(function Rendered2({
2799
2919
  kind,
2800
2920
  item,
2801
2921
  paneId,
@@ -2806,7 +2926,7 @@ var Rendered = (0, import_react7.memo)(function Rendered2({
2806
2926
  presentation
2807
2927
  }) {
2808
2928
  const props = { item, data: item.data, paneId, isFocused, isVisible, canvas, windowId, presentation };
2809
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ItemBoundary, { item, onClose: () => canvas.close(item.id), onRetry: () => resetKindComponent(kind), children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react7.Suspense, { fallback: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "mxc-loading", "aria-busy": "true" }), children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(KindRender, { kind, props }) }) });
2929
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ItemBoundary, { item, onClose: () => canvas.close(item.id), onRetry: () => resetKindComponent(kind), children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react8.Suspense, { fallback: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "mxc-loading", "aria-busy": "true" }), children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(KindRender, { kind, props }) }) });
2810
2930
  });
2811
2931
  function KindRender({ kind, props }) {
2812
2932
  const Render = kindComponent(kind);
@@ -2815,8 +2935,8 @@ function KindRender({ kind, props }) {
2815
2935
  }
2816
2936
  function NativeBody({ itemId, windowId, visible, children }) {
2817
2937
  const { layer } = useCanvasInternals();
2818
- const ref = (0, import_react7.useRef)(null);
2819
- const measure = (0, import_react7.useRef)(() => void 0);
2938
+ const ref = (0, import_react8.useRef)(null);
2939
+ const measure = (0, import_react8.useRef)(() => void 0);
2820
2940
  measure.current = () => {
2821
2941
  const el = ref.current;
2822
2942
  const box = visible && el?.isConnected ? el.getBoundingClientRect() : null;
@@ -2827,8 +2947,8 @@ function NativeBody({ itemId, windowId, visible, children }) {
2827
2947
  const rect = { ...toCanvasRect(box), devicePixelRatio: window.devicePixelRatio || 1 };
2828
2948
  layer.native.report(itemId, rect, { visible: !layer.isBlocked(), windowId });
2829
2949
  };
2830
- useIsoLayoutEffect2(() => measure.current());
2831
- useIsoLayoutEffect2(() => {
2950
+ useIsoLayoutEffect3(() => measure.current());
2951
+ useIsoLayoutEffect3(() => {
2832
2952
  const el = ref.current;
2833
2953
  const run = () => measure.current();
2834
2954
  const observer = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(run);
@@ -2854,7 +2974,7 @@ function ItemProblem({ message, onClose, onRetry }) {
2854
2974
  ] })
2855
2975
  ] });
2856
2976
  }
2857
- var ItemBoundary = class extends import_react7.Component {
2977
+ var ItemBoundary = class extends import_react8.Component {
2858
2978
  state = { error: null };
2859
2979
  static getDerivedStateFromError(error) {
2860
2980
  return { error };
@@ -2880,11 +3000,28 @@ var ItemBoundary = class extends import_react7.Component {
2880
3000
  }
2881
3001
  };
2882
3002
 
3003
+ // src/react/resizeGesture.ts
3004
+ function beginCanvasResize(mode) {
3005
+ if (typeof document === "undefined") return () => void 0;
3006
+ const root = document.documentElement;
3007
+ root.dataset.mxcResizing = mode;
3008
+ document.getSelection?.()?.removeAllRanges();
3009
+ const refuse = (event) => event.preventDefault();
3010
+ document.addEventListener("selectstart", refuse, true);
3011
+ let ended = false;
3012
+ return () => {
3013
+ if (ended) return;
3014
+ ended = true;
3015
+ delete root.dataset.mxcResizing;
3016
+ document.removeEventListener("selectstart", refuse, true);
3017
+ };
3018
+ }
3019
+
2883
3020
  // src/react/CanvasPane.tsx
2884
- var import_react8 = require("react");
3021
+ var import_react9 = require("react");
2885
3022
  var Menu = __toESM(require("@radix-ui/react-dropdown-menu"), 1);
2886
3023
  var import_jsx_runtime7 = require("react/jsx-runtime");
2887
- var useIsoLayoutEffect3 = typeof window === "undefined" ? import_react8.useEffect : import_react8.useLayoutEffect;
3024
+ var useIsoLayoutEffect4 = typeof window === "undefined" ? import_react9.useEffect : import_react9.useLayoutEffect;
2888
3025
  function itemTitle(item, kind) {
2889
3026
  if (item.title) return item.title;
2890
3027
  if (item.kind === CANVAS_LAUNCHER_KIND) return "New tab";
@@ -2921,7 +3058,7 @@ function CanvasPaneView({ paneId }) {
2921
3058
  const canvas = useCanvas();
2922
3059
  const { windowId, tabStrip } = useCanvasInternals();
2923
3060
  const columnSlots = useCanvasSlots();
2924
- const [ownSlots] = (0, import_react8.useState)(createCanvasSlots);
3061
+ const [ownSlots] = (0, import_react9.useState)(createCanvasSlots);
2925
3062
  const slots = columnSlots ?? ownSlots;
2926
3063
  const pane = useCanvasState((s) => s.panes[paneId]);
2927
3064
  const active = useCanvasState((s) => {
@@ -2934,14 +3071,15 @@ function CanvasPaneView({ paneId }) {
2934
3071
  const paneCount = useCanvasState((s) => selectCanvasPaneCount(s, windowId));
2935
3072
  const role = useCanvasState((s) => windowId === CANVAS_MAIN_WINDOW ? "main" : s.windows[windowId]?.role ?? "window");
2936
3073
  const canGoBack = useCanvasState((s) => active ? selectCanvasCanGoBack(s, active.id) : false);
3074
+ const canGoForward = useCanvasState((s) => active ? selectCanvasCanGoForward(s, active.id) : false);
2937
3075
  const kind = useItemKind(active?.kind ?? "");
2938
3076
  const singlePane = useCanvasSinglePane();
2939
3077
  const windowTabs = useCanvasState((s) => singlePane ? windowTabsSignature(s, windowId) : "");
2940
- const sectionRef = (0, import_react8.useRef)(null);
2941
- const bodyRef = (0, import_react8.useRef)(null);
3078
+ const sectionRef = (0, import_react9.useRef)(null);
3079
+ const bodyRef = (0, import_react9.useRef)(null);
2942
3080
  const presentation = usePanePresentation(slots, paneId, isFullscreen, paneCount);
2943
3081
  const drop = usePaneDrop(paneId, () => sectionRef.current);
2944
- useIsoLayoutEffect3(() => {
3082
+ useIsoLayoutEffect4(() => {
2945
3083
  const body = bodyRef.current;
2946
3084
  const section = sectionRef.current;
2947
3085
  if (body) slots.set(paneId, body);
@@ -2969,7 +3107,17 @@ function CanvasPaneView({ paneId }) {
2969
3107
  focused,
2970
3108
  "data-mxc-pane": paneId,
2971
3109
  header: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
2972
- canGoBack && active ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(CanvasHeaderButton, { className: "mxc-back", label: "Back", onClick: () => void canvas.back(active.id), children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ArrowLeftIcon, {}) }) : null,
3110
+ canGoBack && active ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(CanvasHeaderButton, { className: "mxc-back", label: "Back", shortcut: canvasShortcutHint("canvas.back", isMac()), onClick: () => void canvas.back(active.id), children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ArrowLeftIcon, {}) }) : null,
3111
+ canGoForward && active ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3112
+ CanvasHeaderButton,
3113
+ {
3114
+ className: "mxc-forward",
3115
+ label: "Forward",
3116
+ shortcut: canvasShortcutHint("canvas.forward", isMac()),
3117
+ onClick: () => void canvas.forward(active.id),
3118
+ children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ArrowRightIcon, {})
3119
+ }
3120
+ ) : null,
2973
3121
  single && active ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(SingleTitle, { item: active, kind }) : tabs.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(TabStrip, { tabs, activeItemId: pane.activeItemId }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "mxc-panel-title", children: "New tab" }),
2974
3122
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(CanvasHeaderButton, { className: "mxc-new-tab", label: "New tab", onClick: () => void canvas.newTab(paneId), children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(PlusIcon, {}) })
2975
3123
  ] }),
@@ -2978,7 +3126,19 @@ function CanvasPaneView({ paneId }) {
2978
3126
  // Its own gapless row: a kind's tap buttons space themselves (the tap-target placement rule).
2979
3127
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "mxc-kind-actions", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Action, { ...kindProps }) })
2980
3128
  ) : null,
2981
- renderMenu: () => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(PaneMenu, { paneId, windowId, activeItem: active, kind, kindProps, tabCount: pane.itemIds.length, role }),
3129
+ renderMenu: () => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3130
+ PaneMenu,
3131
+ {
3132
+ paneId,
3133
+ windowId,
3134
+ activeItem: active,
3135
+ kind,
3136
+ kindProps,
3137
+ tabCount: pane.itemIds.length,
3138
+ role,
3139
+ canSplit: !singlePane
3140
+ }
3141
+ ),
2982
3142
  expanded: isFullscreen,
2983
3143
  onToggleExpand: () => canvas.setFullscreen(!isFullscreen),
2984
3144
  expandShortcut: canvasShortcutHint("canvas.toggleFullscreen", isMac()),
@@ -2996,7 +3156,7 @@ function CanvasPaneView({ paneId }) {
2996
3156
  }
2997
3157
  );
2998
3158
  }
2999
- var ToolbarBoundary = class extends import_react8.Component {
3159
+ var ToolbarBoundary = class extends import_react9.Component {
3000
3160
  state = { failed: false };
3001
3161
  static getDerivedStateFromError() {
3002
3162
  return { failed: true };
@@ -3012,77 +3172,135 @@ function SingleTitle({ item, kind }) {
3012
3172
  const canvas = useCanvas();
3013
3173
  const title = itemTitle(item, kind);
3014
3174
  const drag = useTabDrag(item.id, title);
3175
+ const name = useTruncated(title);
3015
3176
  const Icon = kind?.icon ?? AlertIcon;
3016
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: "mxc-panel-title mxc-single-title", "data-draggable": "", title, ...drag, children: [
3177
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: "mxc-panel-title mxc-single-title", "data-draggable": "", "data-truncated": name.truncated ? "" : void 0, title, ...drag, children: [
3017
3178
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Icon, { className: "mxc-title-icon" }),
3018
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "mxc-tab-title", children: title }),
3179
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { ref: name.ref, className: "mxc-tab-title", children: title }),
3019
3180
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { type: "button", className: "mxc-tab-close", "data-slot": "browser-tab-close", "aria-label": `Close ${title}`, onClick: () => canvas.close(item.id), children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(CloseIcon, {}) })
3020
3181
  ] });
3021
3182
  }
3183
+ var CANVAS_TAB_MIN_WIDTH = 88;
3184
+ var CANVAS_ACTIVE_TAB_MIN_WIDTH = 112;
3185
+ var TAB_LIST_BUTTON_ROOM = 36;
3186
+ var TAB_GAP = 2;
3187
+ function fitCanvasTabs(count, activeIndex, width) {
3188
+ if (count <= 0) return { start: 0, end: 0, overflow: false, compact: false };
3189
+ const need = (n) => CANVAS_ACTIVE_TAB_MIN_WIDTH + (n - 1) * (CANVAS_TAB_MIN_WIDTH + TAB_GAP);
3190
+ if (width <= 0 || need(count) <= width) return { start: 0, end: count, overflow: false, compact: false };
3191
+ if (count === 1) return { start: 0, end: 1, overflow: false, compact: true };
3192
+ let fit = 1;
3193
+ while (fit + 1 < count && need(fit + 1) + TAB_LIST_BUTTON_ROOM <= width) fit += 1;
3194
+ const active = Math.min(Math.max(activeIndex, 0), count - 1);
3195
+ const start = active < fit ? 0 : active - fit + 1;
3196
+ return { start, end: start + fit, overflow: true, compact: need(fit) + TAB_LIST_BUTTON_ROOM > width };
3197
+ }
3022
3198
  function TabStrip({ tabs, activeItemId }) {
3023
- const strip = (0, import_react8.useRef)(null);
3024
- const [overflow, setOverflow] = (0, import_react8.useState)({ start: false, end: false });
3025
- const measure = (0, import_react8.useRef)(() => void 0);
3026
- measure.current = () => {
3027
- const el = strip.current;
3028
- if (!el) return;
3029
- const start = el.scrollLeft > 1;
3030
- const end = el.scrollLeft + el.clientWidth < el.scrollWidth - 1;
3031
- setOverflow((prev) => prev.start === start && prev.end === end ? prev : { start, end });
3032
- };
3033
- const tabKey = tabs.map((tab) => tab.itemId).join("|");
3034
- useIsoLayoutEffect3(() => {
3199
+ const canvas = useCanvas();
3200
+ const strip = (0, import_react9.useRef)(null);
3201
+ const [width, setWidth] = (0, import_react9.useState)(0);
3202
+ useIsoLayoutEffect4(() => {
3035
3203
  const el = strip.current;
3036
3204
  if (!el) return void 0;
3037
- const reveal = () => {
3038
- const tab = el.querySelector('[role="tab"][aria-selected="true"]');
3039
- if (tab) {
3040
- const left = tab.offsetLeft;
3041
- const right = left + tab.offsetWidth;
3042
- if (left < el.scrollLeft) el.scrollLeft = left;
3043
- else if (right > el.scrollLeft + el.clientWidth) el.scrollLeft = right - el.clientWidth;
3044
- }
3045
- measure.current();
3046
- };
3047
- reveal();
3048
- const observer = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(reveal);
3205
+ const read = () => setWidth(Math.floor(el.getBoundingClientRect().width));
3206
+ read();
3207
+ const observer = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(read);
3049
3208
  observer?.observe(el);
3050
- for (const tab of el.querySelectorAll('[role="tab"]')) observer?.observe(tab);
3051
3209
  return () => observer?.disconnect();
3052
- }, [activeItemId, tabKey]);
3053
- const page = (direction) => {
3054
- const el = strip.current;
3055
- if (!el) return;
3056
- const by = Math.max(48, Math.round(el.clientWidth * 0.8)) * direction;
3057
- if (typeof el.scrollBy === "function") el.scrollBy({ left: by, behavior: "smooth" });
3058
- else el.scrollLeft += by;
3059
- };
3060
- const overflowing = overflow.start || overflow.end;
3210
+ }, []);
3211
+ const activeIndex = Math.max(0, tabs.findIndex((tab) => tab.itemId === activeItemId));
3212
+ const fit = fitCanvasTabs(tabs.length, activeIndex, width);
3213
+ const shown = tabs.slice(fit.start, fit.end);
3214
+ const hidden = tabs.length - shown.length;
3061
3215
  return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
3062
3216
  "div",
3063
3217
  {
3218
+ ref: strip,
3064
3219
  className: "mxc-tabstrip",
3065
- "data-overflow": overflowing ? "" : void 0,
3066
- "data-overflow-start": overflow.start ? "" : void 0,
3067
- "data-overflow-end": overflow.end ? "" : void 0,
3220
+ "data-overflow": fit.overflow ? "" : void 0,
3221
+ "data-compact": fit.compact ? "" : void 0,
3222
+ style: { ["--mxc-tab-min"]: `${CANVAS_TAB_MIN_WIDTH}px`, ["--mxc-tab-active-min"]: `${CANVAS_ACTIVE_TAB_MIN_WIDTH}px` },
3068
3223
  children: [
3069
- overflowing ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(CanvasHeaderButton, { className: "mxc-tabs-scroll", label: "Scroll tabs left", available: overflow.start, onClick: () => page(-1), children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ChevronLeftIcon, {}) }) : null,
3070
3224
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3071
3225
  "div",
3072
3226
  {
3073
- ref: strip,
3074
3227
  className: "mxc-tabs",
3075
3228
  role: "tablist",
3076
3229
  "aria-label": "Canvas tabs",
3077
- onScroll: () => measure.current(),
3078
3230
  onWheel: (event) => {
3079
- const el = strip.current;
3080
- if (el && Math.abs(event.deltaY) > Math.abs(event.deltaX)) el.scrollLeft += event.deltaY;
3231
+ if (!fit.overflow || Math.abs(event.deltaY) < Math.abs(event.deltaX) || tabs.length < 2) return;
3232
+ const next = tabs[(activeIndex + (event.deltaY > 0 ? 1 : -1) + tabs.length) % tabs.length];
3233
+ if (next) canvas.activate(next.itemId);
3081
3234
  },
3082
- children: tabs.map((tab) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ItemTab, { itemId: tab.itemId, paneId: tab.paneId, index: tab.index, active: tab.itemId === activeItemId }, tab.itemId))
3235
+ children: shown.map((tab) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ItemTab, { itemId: tab.itemId, paneId: tab.paneId, index: tab.index, active: tab.itemId === activeItemId }, tab.itemId))
3083
3236
  }
3084
3237
  ),
3085
- overflowing ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(CanvasHeaderButton, { className: "mxc-tabs-scroll", label: "Scroll tabs right", available: overflow.end, onClick: () => page(1), children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ChevronRightIcon, {}) }) : null
3238
+ fit.overflow ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(TabListMenu, { tabs, activeItemId, hidden }) : null
3239
+ ]
3240
+ }
3241
+ );
3242
+ }
3243
+ function TabListMenu({ tabs, activeItemId, hidden }) {
3244
+ const canvas = useCanvas();
3245
+ const { ports, layer } = useCanvasInternals();
3246
+ const blocking = (0, import_react9.useRef)(null);
3247
+ (0, import_react9.useEffect)(() => () => blocking.current?.(), []);
3248
+ const label = `All tabs (${tabs.length})`;
3249
+ const trigger = (props) => /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(CanvasHeaderButton, { className: "mxc-tab-list", label, "data-hidden-count": hidden, ...props, children: [
3250
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ChevronDownIcon, {}),
3251
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "mxc-tab-list-count", "aria-hidden": "true", children: hidden })
3252
+ ] });
3253
+ const showMenu = ports.showMenu;
3254
+ if (showMenu) {
3255
+ return trigger({
3256
+ onClick: (event) => {
3257
+ const state = canvas.getState();
3258
+ const entries = tabs.map((tab) => {
3259
+ const item = state.items[tab.itemId];
3260
+ const title = item ? itemTitle(item, resolveItemKind(item.kind, getCanvasKind)) : tab.itemId;
3261
+ return { type: "item", id: tab.itemId, label: tab.itemId === activeItemId ? `${title} (current)` : title };
3262
+ });
3263
+ void showMenu(entries, toCanvasRect(event.currentTarget.getBoundingClientRect())).then(
3264
+ (chosen) => {
3265
+ if (chosen) canvas.activate(chosen);
3266
+ },
3267
+ (error) => console.error("[@ai-matrx/canvas] the host menu failed", error)
3268
+ );
3269
+ }
3270
+ });
3271
+ }
3272
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
3273
+ Menu.Root,
3274
+ {
3275
+ onOpenChange: (open) => {
3276
+ blocking.current?.();
3277
+ blocking.current = open ? layer.beginBlocking() : null;
3278
+ },
3279
+ children: [
3280
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Menu.Trigger, { asChild: true, children: trigger({}) }),
3281
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Menu.Portal, { children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Menu.Content, { align: "end", sideOffset: 4, className: "mxc-menu mxc-tab-list-menu", "aria-label": "All tabs", children: tabs.map((tab) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(TabListEntry, { itemId: tab.itemId, active: tab.itemId === activeItemId }, tab.itemId)) }) })
3282
+ ]
3283
+ }
3284
+ );
3285
+ }
3286
+ function TabListEntry({ itemId, active }) {
3287
+ const canvas = useCanvas();
3288
+ const item = useCanvasState((s) => s.items[itemId]);
3289
+ const kind = useItemKind(item?.kind ?? "");
3290
+ if (!item) return null;
3291
+ const Icon = kind?.icon ?? AlertIcon;
3292
+ const title = itemTitle(item, kind);
3293
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
3294
+ Menu.Item,
3295
+ {
3296
+ className: "mxc-menu-item mxc-tab-list-item",
3297
+ "data-active": active ? "" : void 0,
3298
+ "aria-current": active ? "true" : void 0,
3299
+ title,
3300
+ onSelect: () => canvas.activate(itemId),
3301
+ children: [
3302
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Icon, {}),
3303
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "mxc-tab-list-title", children: title })
3086
3304
  ]
3087
3305
  }
3088
3306
  );
@@ -3126,42 +3344,48 @@ function PaneMenu({
3126
3344
  kind,
3127
3345
  kindProps,
3128
3346
  tabCount,
3129
- role
3347
+ role,
3348
+ canSplit
3130
3349
  }) {
3131
3350
  const canvas = useCanvas();
3132
- const { ports, layer } = useCanvasInternals();
3351
+ const { ports, layer, itemMenus } = useCanvasInternals();
3133
3352
  const moveId = tabCount > 1 ? activeItem?.id : void 0;
3134
- const blocking = (0, import_react8.useRef)(null);
3135
- (0, import_react8.useEffect)(() => () => blocking.current?.(), []);
3136
- const kindItems = kind?.menuItems && kindProps ? safeMenu(() => kind.menuItems?.(kindProps)) : [];
3137
- const hostItems = activeItem && ports.itemActions ? safeMenu(() => ports.itemActions?.(activeItem, kind)) : [];
3138
- const sections = [
3139
- [...kindItems, ...hostItems].map((entry) => ({ ...entry, id: `item:${entry.id}` })),
3140
- [
3141
- { id: "split-right", label: "Split right", icon: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(SplitRightIcon, {}), onSelect: () => canvas.splitPane(paneId, "horizontal", moveId) },
3142
- { id: "split-down", label: "Split down", icon: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(SplitDownIcon, {}), onSelect: () => canvas.splitPane(paneId, "vertical", moveId) },
3143
- ...activeItem && role === "pop-out" ? [{ id: "dock-back", label: "Dock back", icon: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(DockIcon, {}), onSelect: () => void canvas.dockBack(activeItem.id) }] : activeItem && ports.popOut ? [{ id: "pop-out", label: "Pop out", icon: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(PopOutIcon, {}), onSelect: () => void canvas.popOut(activeItem.id) }] : []
3144
- ],
3145
- activeItem ? [
3146
- { id: "close-tab", label: "Close tab", onSelect: () => canvas.close(activeItem.id) },
3147
- ...tabCount > 1 ? [{ id: "close-others", label: "Close other tabs", onSelect: () => canvas.closeOthers(activeItem.id) }] : []
3148
- ] : []
3149
- ].filter((section) => section.length > 0);
3353
+ const blocking = (0, import_react9.useRef)(null);
3354
+ (0, import_react9.useEffect)(() => () => blocking.current?.(), []);
3355
+ const buildSections = () => {
3356
+ const kindItems = kind?.menuItems && kindProps ? safeMenu(() => kind.menuItems?.(kindProps)) : [];
3357
+ const hostItems = activeItem && ports.itemActions ? safeMenu(() => ports.itemActions?.(activeItem, kind)) : [];
3358
+ const bodyItems = activeItem ? itemMenus.read(activeItem.id) : [];
3359
+ return [
3360
+ [...kindItems, ...bodyItems, ...hostItems].map((entry) => ({ ...entry, id: `item:${entry.id}` })),
3361
+ [
3362
+ ...canSplit ? [
3363
+ { id: "split-right", label: "Split right", icon: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(SplitRightIcon, {}), onSelect: () => canvas.splitPane(paneId, "horizontal", moveId) },
3364
+ { id: "split-down", label: "Split down", icon: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(SplitDownIcon, {}), onSelect: () => canvas.splitPane(paneId, "vertical", moveId) }
3365
+ ] : [],
3366
+ ...activeItem && role === "pop-out" ? [{ id: "dock-back", label: "Dock back", icon: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(DockIcon, {}), onSelect: () => void canvas.dockBack(activeItem.id) }] : activeItem && ports.popOut ? [{ id: "pop-out", label: "Pop out", icon: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(PopOutIcon, {}), onSelect: () => void canvas.popOut(activeItem.id) }] : []
3367
+ ],
3368
+ activeItem ? [
3369
+ { id: "close-tab", label: "Close tab", onSelect: () => canvas.close(activeItem.id) },
3370
+ ...tabCount > 1 ? [{ id: "close-others", label: "Close other tabs", onSelect: () => canvas.closeOthers(activeItem.id) }] : []
3371
+ ] : []
3372
+ ].filter((section) => section.length > 0);
3373
+ };
3150
3374
  const showMenu = ports.showMenu;
3151
3375
  if (showMenu) {
3152
- const entries = [];
3153
- const handlers = /* @__PURE__ */ new Map();
3154
- sections.forEach((section, i) => {
3155
- if (i > 0) entries.push({ type: "separator" });
3156
- for (const action of section) {
3157
- entries.push({ type: "item", id: action.id, label: action.label, ...action.destructive ? { destructive: true } : {} });
3158
- handlers.set(action.id, action.onSelect);
3159
- }
3160
- });
3161
3376
  return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3162
3377
  CanvasMenuTrigger,
3163
3378
  {
3164
3379
  onClick: (event) => {
3380
+ const entries = [];
3381
+ const handlers = /* @__PURE__ */ new Map();
3382
+ buildSections().forEach((section, i) => {
3383
+ if (i > 0) entries.push({ type: "separator" });
3384
+ for (const action of section) {
3385
+ entries.push({ type: "item", id: action.id, label: action.label, ...action.destructive ? { destructive: true } : {} });
3386
+ handlers.set(action.id, action.onSelect);
3387
+ }
3388
+ });
3165
3389
  const anchor = toCanvasRect(event.currentTarget.getBoundingClientRect());
3166
3390
  void showMenu(entries, anchor).then(
3167
3391
  (chosen) => {
@@ -3182,11 +3406,14 @@ function PaneMenu({
3182
3406
  },
3183
3407
  children: [
3184
3408
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Menu.Trigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(CanvasMenuTrigger, {}) }),
3185
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Menu.Portal, { children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Menu.Content, { align: "end", sideOffset: 4, className: "mxc-menu", children: sections.map((section, i) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(MenuSection, { first: i === 0, actions: section }, i)) }) })
3409
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Menu.Portal, { children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Menu.Content, { align: "end", sideOffset: 4, className: "mxc-menu", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(MenuSections, { build: buildSections }) }) })
3186
3410
  ]
3187
3411
  }
3188
3412
  );
3189
3413
  }
3414
+ function MenuSections({ build }) {
3415
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_jsx_runtime7.Fragment, { children: build().map((section, i) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(MenuSection, { first: i === 0, actions: section }, i)) });
3416
+ }
3190
3417
  function MenuSection({ first, actions }) {
3191
3418
  return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
3192
3419
  first ? null : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Menu.Separator, { className: "mxc-menu-separator" }),
@@ -3207,13 +3434,13 @@ function safeMenu(build) {
3207
3434
 
3208
3435
  // src/react/CanvasColumn.tsx
3209
3436
  var import_jsx_runtime8 = require("react/jsx-runtime");
3210
- var useIsoLayoutEffect4 = typeof window === "undefined" ? import_react9.useEffect : import_react9.useLayoutEffect;
3437
+ var useIsoLayoutEffect5 = typeof window === "undefined" ? import_react10.useEffect : import_react10.useLayoutEffect;
3211
3438
  function subscribeViewport(listener) {
3212
3439
  window.addEventListener("resize", listener);
3213
3440
  return () => window.removeEventListener("resize", listener);
3214
3441
  }
3215
3442
  function useViewportWidth() {
3216
- return (0, import_react9.useSyncExternalStore)(subscribeViewport, () => window.innerWidth, () => 0);
3443
+ return (0, import_react10.useSyncExternalStore)(subscribeViewport, () => window.innerWidth, () => 0);
3217
3444
  }
3218
3445
  function useRegionWidth() {
3219
3446
  const viewport = useViewportWidth();
@@ -3236,15 +3463,15 @@ function CanvasColumn({ className, style, onLiveWidth, placement = "docked" }) {
3236
3463
  const isFullscreen = useCanvasState((s) => selectCanvasIsFullscreen(s, windowId));
3237
3464
  const width = useCanvasState((s) => selectCanvasWidth(s, windowId));
3238
3465
  const layout = useCanvasState((s) => selectCanvasLayout(s, windowId));
3239
- const focusedPaneId = useCanvasState((s) => selectCanvasFocusedPaneId(s, windowId));
3466
+ const presentedPaneId = useCanvasState((s) => selectCanvasPresentedPaneId(s, windowId));
3240
3467
  const region = useRegionWidth();
3241
3468
  const viewport = useViewportWidth();
3242
3469
  const singlePane = placement === "docked" && viewport > 0 && viewport <= CANVAS_PHONE_MAX_WIDTH;
3243
- const [dragWidth, setDragWidth] = (0, import_react9.useState)(null);
3244
- const [slots] = (0, import_react9.useState)(createCanvasSlots);
3245
- const ref = (0, import_react9.useRef)(null);
3470
+ const [dragWidth, setDragWidth] = (0, import_react10.useState)(null);
3471
+ const [slots] = (0, import_react10.useState)(createCanvasSlots);
3472
+ const ref = (0, import_react10.useRef)(null);
3246
3473
  useRegisterCanvasPresence();
3247
- (0, import_react9.useEffect)(() => {
3474
+ (0, import_react10.useEffect)(() => {
3248
3475
  const el = ref.current;
3249
3476
  if (!el) return void 0;
3250
3477
  let frame2 = 0;
@@ -3305,7 +3532,7 @@ function CanvasColumn({ className, style, onLiveWidth, placement = "docked" }) {
3305
3532
  }
3306
3533
  ) : null,
3307
3534
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(CanvasSlotsContext.Provider, { value: slots, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(CanvasSinglePaneContext.Provider, { value: singlePane, children: [
3308
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "mxc-layout", "data-single-pane": singlePane ? "" : void 0, children: singlePane ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(CanvasPaneView, { paneId: presentedPane(layout, focusedPaneId) }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(LayoutNodeView, { node: layout }) }),
3535
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "mxc-layout", "data-single-pane": singlePane ? "" : void 0, children: singlePane ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(CanvasPaneView, { paneId: presentedPaneId ?? listPaneIds(layout)[0] }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(LayoutNodeView, { node: layout }) }),
3309
3536
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(CanvasBodyLayer, {})
3310
3537
  ] }) })
3311
3538
  ] })
@@ -3314,13 +3541,14 @@ function CanvasColumn({ className, style, onLiveWidth, placement = "docked" }) {
3314
3541
  }
3315
3542
  function WidthHandle({ width, region, onDrag }) {
3316
3543
  const canvas = useCanvas();
3317
- const start = (0, import_react9.useRef)(null);
3544
+ const start = (0, import_react10.useRef)(null);
3545
+ (0, import_react10.useEffect)(() => () => start.current?.end(), []);
3318
3546
  const clamp = (next) => clampDraggedCanvasWidth(next, region, canvas.rules);
3319
3547
  const onPointerDown = (event) => {
3320
3548
  event.preventDefault();
3321
3549
  event.currentTarget.setPointerCapture(event.pointerId);
3322
- start.current = { x: event.clientX, width };
3323
- document.documentElement.dataset.mxcResizing = "";
3550
+ start.current?.end();
3551
+ start.current = { x: event.clientX, width, end: beginCanvasResize("") };
3324
3552
  };
3325
3553
  const onPointerMove = (event) => {
3326
3554
  if (!start.current) return;
@@ -3329,8 +3557,8 @@ function WidthHandle({ width, region, onDrag }) {
3329
3557
  const finish = (event) => {
3330
3558
  if (!start.current) return;
3331
3559
  const next = clamp(start.current.width + (start.current.x - event.clientX));
3560
+ start.current.end();
3332
3561
  start.current = null;
3333
- delete document.documentElement.dataset.mxcResizing;
3334
3562
  onDrag(null);
3335
3563
  canvas.setWidth(next);
3336
3564
  };
@@ -3347,6 +3575,7 @@ function WidthHandle({ width, region, onDrag }) {
3347
3575
  onPointerMove,
3348
3576
  onPointerUp: finish,
3349
3577
  onPointerCancel: finish,
3578
+ onLostPointerCapture: finish,
3350
3579
  onDoubleClick: () => canvas.setWidth(clamp(Math.round(region / 2))),
3351
3580
  onKeyDown: (event) => {
3352
3581
  if (event.key === "ArrowLeft") canvas.setWidth(clamp(width + 32));
@@ -3355,17 +3584,13 @@ function WidthHandle({ width, region, onDrag }) {
3355
3584
  }
3356
3585
  );
3357
3586
  }
3358
- function presentedPane(layout, focused) {
3359
- const ids = listPaneIds(layout);
3360
- return ids.find((id) => id === focused) ?? ids[0];
3361
- }
3362
3587
  function LayoutNodeView({ node }) {
3363
3588
  if (node.type === "pane") return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(CanvasPaneView, { paneId: node.paneId });
3364
3589
  return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(SplitView, { node });
3365
3590
  }
3366
3591
  function useMeasuredWidth(ref, enabled) {
3367
- const [width, setWidth] = (0, import_react9.useState)(0);
3368
- useIsoLayoutEffect4(() => {
3592
+ const [width, setWidth] = (0, import_react10.useState)(0);
3593
+ useIsoLayoutEffect5(() => {
3369
3594
  const el = ref.current;
3370
3595
  if (!enabled || !el) return;
3371
3596
  setWidth(el.getBoundingClientRect().width);
@@ -3378,8 +3603,8 @@ function useMeasuredWidth(ref, enabled) {
3378
3603
  }
3379
3604
  function SplitView({ node }) {
3380
3605
  const canvas = useCanvas();
3381
- const ref = (0, import_react9.useRef)(null);
3382
- const [liveSizes, setLiveSizes] = (0, import_react9.useState)(null);
3606
+ const ref = (0, import_react10.useRef)(null);
3607
+ const [liveSizes, setLiveSizes] = (0, import_react10.useState)(null);
3383
3608
  const sizes = liveSizes ?? node.sizes;
3384
3609
  const measuredWidth = useMeasuredWidth(ref, node.orientation === "horizontal" && canvas.rules.paneMinWidth > 0);
3385
3610
  const stacked = node.orientation === "horizontal" && shouldStackSplit(measuredWidth, node.children.length, canvas.rules);
@@ -3396,7 +3621,7 @@ function SplitView({ node }) {
3396
3621
  const base = [...node.sizes];
3397
3622
  const pair = (base[index] ?? 0) + (base[index + 1] ?? 0);
3398
3623
  const target = event.currentTarget;
3399
- document.documentElement.dataset.mxcResizing = horizontal ? "col" : "row";
3624
+ const end = beginCanvasResize(horizontal ? "col" : "row");
3400
3625
  const compute = (pos) => {
3401
3626
  const delta = (pos - origin) / total;
3402
3627
  const min = CANVAS_MIN_SPLIT_FRACTION;
@@ -3411,7 +3636,7 @@ function SplitView({ node }) {
3411
3636
  target.removeEventListener("pointermove", move);
3412
3637
  target.removeEventListener("pointerup", up);
3413
3638
  target.removeEventListener("pointercancel", up);
3414
- delete document.documentElement.dataset.mxcResizing;
3639
+ end();
3415
3640
  setLiveSizes(null);
3416
3641
  canvas.resizeSplit(node.id, compute(horizontal ? e.clientX : e.clientY));
3417
3642
  };