@ai-matrx/canvas 0.6.0 → 0.7.0

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,23 +2121,37 @@ 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");
2128
+ var useIsoLayoutEffect2 = typeof window === "undefined" ? import_react4.useEffect : import_react4.useLayoutEffect;
2129
+ function useTruncated(title) {
2130
+ const ref = (0, import_react4.useRef)(null);
2131
+ const [truncated, setTruncated] = (0, import_react4.useState)(false);
2132
+ useIsoLayoutEffect2(() => {
2133
+ const el = ref.current;
2134
+ if (!el) return void 0;
2135
+ const check = () => setTruncated(el.scrollWidth - el.clientWidth > 1);
2136
+ check();
2137
+ const observer = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(check);
2138
+ observer?.observe(el);
2139
+ return () => observer?.disconnect();
2140
+ }, [title]);
2141
+ return { ref, truncated };
2142
+ }
2018
2143
  var cx = (...names) => names.filter(Boolean).join(" ");
2019
2144
  function useTooltip(text) {
2020
2145
  const internals = useOptionalCanvasInternals();
2021
- const timer = (0, import_react3.useRef)(null);
2022
- const hide = (0, import_react3.useRef)(null);
2146
+ const timer = (0, import_react4.useRef)(null);
2147
+ const hide = (0, import_react4.useRef)(null);
2023
2148
  const close = () => {
2024
2149
  if (timer.current) clearTimeout(timer.current);
2025
2150
  timer.current = null;
2026
2151
  hide.current?.();
2027
2152
  hide.current = null;
2028
2153
  };
2029
- (0, import_react3.useEffect)(() => close, []);
2154
+ (0, import_react4.useEffect)(() => close, []);
2030
2155
  if (!text) return { close, handlers: {} };
2031
2156
  if (!internals) return { close, handlers: { title: text } };
2032
2157
  const open = (element, delay) => {
@@ -2052,7 +2177,7 @@ function useTooltip(text) {
2052
2177
  }
2053
2178
  };
2054
2179
  }
2055
- 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) {
2056
2181
  const tooltip = useTooltip(shortcut ? `${label} (${shortcut})` : label);
2057
2182
  const own = tooltip.handlers;
2058
2183
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
@@ -2097,12 +2222,12 @@ var CanvasHeaderButton = (0, import_react3.forwardRef)(function CanvasHeaderButt
2097
2222
  }
2098
2223
  );
2099
2224
  });
2100
- var CanvasMenuTrigger = (0, import_react3.forwardRef)(
2225
+ var CanvasMenuTrigger = (0, import_react4.forwardRef)(
2101
2226
  function CanvasMenuTrigger2({ label = "More", ...props }, ref) {
2102
2227
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(CanvasHeaderButton, { ref, label, ...props, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(MoreIcon, {}) });
2103
2228
  }
2104
2229
  );
2105
- var CanvasPanel = (0, import_react3.forwardRef)(function CanvasPanel2({
2230
+ var CanvasPanel = (0, import_react4.forwardRef)(function CanvasPanel2({
2106
2231
  panelId,
2107
2232
  title,
2108
2233
  leading,
@@ -2152,8 +2277,9 @@ var CanvasPanel = (0, import_react3.forwardRef)(function CanvasPanel2({
2152
2277
  function CanvasPanelEmpty({ children, className }) {
2153
2278
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { "data-slot": "canvas-panel-empty", className: cx("mxc-empty-line", className), children });
2154
2279
  }
2155
- 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) {
2156
2281
  const shown = title || "New tab";
2282
+ const name = useTruncated(shown);
2157
2283
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
2158
2284
  "div",
2159
2285
  {
@@ -2163,6 +2289,7 @@ var CanvasTab = (0, import_react3.forwardRef)(function CanvasTab2({ title, icon,
2163
2289
  tabIndex: active ? 0 : -1,
2164
2290
  "data-slot": "browser-tab",
2165
2291
  "data-active": active ? "" : void 0,
2292
+ "data-truncated": name.truncated ? "" : void 0,
2166
2293
  title: shown,
2167
2294
  className: cx("mxc-tab", className),
2168
2295
  ...rest,
@@ -2180,7 +2307,7 @@ var CanvasTab = (0, import_react3.forwardRef)(function CanvasTab2({ title, icon,
2180
2307
  },
2181
2308
  children: [
2182
2309
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "mxc-tab-icon", children: icon }),
2183
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "mxc-tab-title", children: shown }),
2310
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { ref: name.ref, className: "mxc-tab-title", children: shown }),
2184
2311
  note ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "mxc-tab-note", "data-slot": "browser-tab-note", children: note }) : null,
2185
2312
  onClose ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2186
2313
  "button",
@@ -2219,9 +2346,9 @@ function BrowserTabs({ tabs, activeId, onSelect, onClose, onNew, newLabel = "New
2219
2346
  ] });
2220
2347
  }
2221
2348
  function BrowserBar({ url, canGoBack, canGoForward, onBack, onForward, onReload, onNavigate, trailing, className }) {
2222
- const [draft, setDraft] = (0, import_react3.useState)(url);
2223
- const [editing, setEditing] = (0, import_react3.useState)(false);
2224
- (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)(() => {
2225
2352
  if (!editing) setDraft(url);
2226
2353
  }, [url, editing]);
2227
2354
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { "data-slot": "browser-bar", className: cx("mxc-browser-bar", className), children: [
@@ -2263,15 +2390,15 @@ function BrowserBar({ url, canGoBack, canGoForward, onBack, onForward, onReload,
2263
2390
  }
2264
2391
 
2265
2392
  // src/react/dnd.ts
2266
- var import_react4 = require("react");
2393
+ var import_react5 = require("react");
2267
2394
  var CANVAS_DRAG_MIME = "application/x-matrx-canvas-item";
2268
2395
  function useTabDrag(itemId, title) {
2269
2396
  const { layer } = useCanvasInternals();
2270
- const active = (0, import_react4.useRef)([]);
2397
+ const active = (0, import_react5.useRef)([]);
2271
2398
  const end = () => {
2272
2399
  for (const dispose of active.current.splice(0)) dispose();
2273
2400
  };
2274
- (0, import_react4.useEffect)(() => end, []);
2401
+ (0, import_react5.useEffect)(() => end, []);
2275
2402
  return {
2276
2403
  draggable: true,
2277
2404
  onDragStart: (event) => {
@@ -2286,8 +2413,8 @@ function useTabDrag(itemId, title) {
2286
2413
  function usePaneDrop(paneId, getOutline) {
2287
2414
  const canvas = useCanvas();
2288
2415
  const { layer } = useCanvasInternals();
2289
- const drop = (0, import_react4.useRef)(null);
2290
- (0, import_react4.useEffect)(() => () => drop.current?.(), []);
2416
+ const drop = (0, import_react5.useRef)(null);
2417
+ (0, import_react5.useEffect)(() => () => drop.current?.(), []);
2291
2418
  const hide = () => {
2292
2419
  drop.current?.();
2293
2420
  drop.current = null;
@@ -2418,16 +2545,19 @@ function useItemKind(kindId) {
2418
2545
  const registered = useCanvasKind(kindId);
2419
2546
  return kindId === CANVAS_LAUNCHER_KIND ? CANVAS_LAUNCHER_KIND_DEFINITION : registered;
2420
2547
  }
2548
+ function resolveItemKind(kindId, lookup) {
2549
+ return kindId === CANVAS_LAUNCHER_KIND ? CANVAS_LAUNCHER_KIND_DEFINITION : lookup(kindId);
2550
+ }
2421
2551
 
2422
2552
  // src/react/presentation.tsx
2423
- var import_react5 = require("react");
2424
- var CanvasPresentationContext = (0, import_react5.createContext)(null);
2553
+ var import_react6 = require("react");
2554
+ var CanvasPresentationContext = (0, import_react6.createContext)(null);
2425
2555
  function useCanvasPresentation() {
2426
- return (0, import_react5.useContext)(CanvasPresentationContext);
2556
+ return (0, import_react6.useContext)(CanvasPresentationContext);
2427
2557
  }
2428
2558
 
2429
2559
  // src/react/slots.ts
2430
- var import_react6 = require("react");
2560
+ var import_react7 = require("react");
2431
2561
  var ZERO = Object.freeze({ width: 0, height: 0 });
2432
2562
  function createCanvasSlots() {
2433
2563
  const slots = /* @__PURE__ */ new Map();
@@ -2494,16 +2624,16 @@ function createCanvasSlots() {
2494
2624
  }
2495
2625
  };
2496
2626
  }
2497
- var CanvasSlotsContext = (0, import_react6.createContext)(null);
2627
+ var CanvasSlotsContext = (0, import_react7.createContext)(null);
2498
2628
  function useCanvasSlots() {
2499
- return (0, import_react6.useContext)(CanvasSlotsContext);
2629
+ return (0, import_react7.useContext)(CanvasSlotsContext);
2500
2630
  }
2501
- var CanvasSinglePaneContext = (0, import_react6.createContext)(false);
2631
+ var CanvasSinglePaneContext = (0, import_react7.createContext)(false);
2502
2632
  function useCanvasSinglePane() {
2503
- return (0, import_react6.useContext)(CanvasSinglePaneContext);
2633
+ return (0, import_react7.useContext)(CanvasSinglePaneContext);
2504
2634
  }
2505
2635
  function useSlotSize(slots, paneId) {
2506
- return (0, import_react6.useSyncExternalStore)(
2636
+ return (0, import_react7.useSyncExternalStore)(
2507
2637
  slots.subscribe,
2508
2638
  () => slots.size(paneId),
2509
2639
  () => ZERO
@@ -2512,12 +2642,12 @@ function useSlotSize(slots, paneId) {
2512
2642
 
2513
2643
  // src/react/bodies.tsx
2514
2644
  var import_jsx_runtime6 = require("react/jsx-runtime");
2515
- var useIsoLayoutEffect = typeof window === "undefined" ? import_react7.useEffect : import_react7.useLayoutEffect;
2645
+ var useIsoLayoutEffect3 = typeof window === "undefined" ? import_react8.useEffect : import_react8.useLayoutEffect;
2516
2646
  var LAZY = /* @__PURE__ */ Symbol.for("ai-matrx.canvas.lazy");
2517
2647
  function kindComponent(kind) {
2518
2648
  if (kind.component) return kind.component;
2519
2649
  const holder = kind;
2520
- 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);
2521
2651
  return holder[LAZY] ?? null;
2522
2652
  }
2523
2653
  function resetKindComponent(kind) {
@@ -2525,7 +2655,7 @@ function resetKindComponent(kind) {
2525
2655
  }
2526
2656
  function usePanePresentation(slots, paneId, isFullscreen, paneCount) {
2527
2657
  const size = useSlotSize(slots, paneId);
2528
- return (0, import_react7.useMemo)(
2658
+ return (0, import_react8.useMemo)(
2529
2659
  () => canvasPresentation({ width: size.width, height: size.height, isFullscreen, paneCount }),
2530
2660
  [size.width, size.height, isFullscreen, paneCount]
2531
2661
  );
@@ -2563,17 +2693,24 @@ function layerSignature(state, windowId, onlyPane) {
2563
2693
  }
2564
2694
  }
2565
2695
  rows.sort((a, b) => a.born - b.born || a.hostId.localeCompare(b.hostId));
2566
- 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);
2567
2703
  return [head, ...rows.map((r) => r.row)].join(ROW);
2568
2704
  }
2569
2705
  function parseSignature(signature) {
2570
2706
  if (!signature) return null;
2571
2707
  const [head = "", ...rows] = signature.split(ROW);
2572
- const [open, full, focusedPaneId = "", count = "1"] = head.split(SEP2);
2708
+ const [open, full, focusedPaneId = "", count = "1", presentedPaneId = ""] = head.split(SEP2);
2573
2709
  return {
2574
2710
  isOpen: open === "1",
2575
2711
  isFullscreen: full === "1",
2576
2712
  focusedPaneId,
2713
+ presentedPaneId,
2577
2714
  paneCount: Number(count) || 1,
2578
2715
  placed: rows.map((row) => {
2579
2716
  const [hostId = "", itemId = "", paneId = "", active, kept] = row.split(SEP2);
@@ -2625,12 +2762,12 @@ function CanvasBodyLayer() {
2625
2762
  const slots = useCanvasSlots();
2626
2763
  const signature = useCanvasState((s) => layerSignature(s, windowId));
2627
2764
  const single = useCanvasSinglePane();
2628
- const root = (0, import_react7.useRef)(null);
2629
- const [placement] = (0, import_react7.useState)(
2765
+ const root = (0, import_react8.useRef)(null);
2766
+ const [placement] = (0, import_react8.useState)(
2630
2767
  () => slots ? createPlacement(() => root.current, slots, () => layer.native.remeasure()) : null
2631
2768
  );
2632
- useIsoLayoutEffect(() => placement?.place());
2633
- useIsoLayoutEffect(() => 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]);
2634
2771
  const shape = parseSignature(signature);
2635
2772
  if (!slots || !placement || !shape) return null;
2636
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)(
@@ -2640,7 +2777,7 @@ function CanvasBodyLayer() {
2640
2777
  itemId,
2641
2778
  kept,
2642
2779
  paneId,
2643
- visible: shape.isOpen && active && (!single || paneId === shape.focusedPaneId),
2780
+ visible: shape.isOpen && active && (!single || paneId === shape.presentedPaneId),
2644
2781
  focused: active && shape.focusedPaneId === paneId,
2645
2782
  isFullscreen: shape.isFullscreen,
2646
2783
  paneCount: shape.paneCount,
@@ -2683,7 +2820,7 @@ function stepAsItem(step, openedAt) {
2683
2820
  ...step.instance ? { instance: step.instance } : {}
2684
2821
  };
2685
2822
  }
2686
- var BodyHost = (0, import_react7.memo)(function BodyHost2({
2823
+ var BodyHost = (0, import_react8.memo)(function BodyHost2({
2687
2824
  hostId,
2688
2825
  itemId,
2689
2826
  kept,
@@ -2703,16 +2840,16 @@ var BodyHost = (0, import_react7.memo)(function BodyHost2({
2703
2840
  return ownerItem ? keptCanvasSteps(ownerItem).find((e) => e.instance === hostId) : void 0;
2704
2841
  });
2705
2842
  const owner = useCanvasState((s) => kept ? s.items[itemId]?.openedAt : void 0);
2706
- 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]);
2707
2844
  const item = kept ? keptItem : live;
2708
2845
  const kind = useItemKind(item?.kind ?? "");
2709
- const element = (0, import_react7.useRef)(null);
2710
- 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);
2711
2848
  if (visible && !everShown) setEverShown(true);
2712
2849
  const mounted = kind?.unmountWhenHidden ? visible : everShown;
2713
2850
  const presentation = usePanePresentation(slots, paneId, isFullscreen, paneCount);
2714
2851
  const drop = usePaneDrop(paneId, () => slots.section(paneId));
2715
- useIsoLayoutEffect(() => {
2852
+ useIsoLayoutEffect3(() => {
2716
2853
  const el = element.current;
2717
2854
  if (!placement || !el) return void 0;
2718
2855
  return placement.register(hostId, { element: el, paneId });
@@ -2778,7 +2915,7 @@ function ItemContent({
2778
2915
  }
2779
2916
  return rendered;
2780
2917
  }
2781
- var Rendered = (0, import_react7.memo)(function Rendered2({
2918
+ var Rendered = (0, import_react8.memo)(function Rendered2({
2782
2919
  kind,
2783
2920
  item,
2784
2921
  paneId,
@@ -2789,7 +2926,7 @@ var Rendered = (0, import_react7.memo)(function Rendered2({
2789
2926
  presentation
2790
2927
  }) {
2791
2928
  const props = { item, data: item.data, paneId, isFocused, isVisible, canvas, windowId, presentation };
2792
- 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 }) }) });
2793
2930
  });
2794
2931
  function KindRender({ kind, props }) {
2795
2932
  const Render = kindComponent(kind);
@@ -2798,8 +2935,8 @@ function KindRender({ kind, props }) {
2798
2935
  }
2799
2936
  function NativeBody({ itemId, windowId, visible, children }) {
2800
2937
  const { layer } = useCanvasInternals();
2801
- const ref = (0, import_react7.useRef)(null);
2802
- 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);
2803
2940
  measure.current = () => {
2804
2941
  const el = ref.current;
2805
2942
  const box = visible && el?.isConnected ? el.getBoundingClientRect() : null;
@@ -2810,8 +2947,8 @@ function NativeBody({ itemId, windowId, visible, children }) {
2810
2947
  const rect = { ...toCanvasRect(box), devicePixelRatio: window.devicePixelRatio || 1 };
2811
2948
  layer.native.report(itemId, rect, { visible: !layer.isBlocked(), windowId });
2812
2949
  };
2813
- useIsoLayoutEffect(() => measure.current());
2814
- useIsoLayoutEffect(() => {
2950
+ useIsoLayoutEffect3(() => measure.current());
2951
+ useIsoLayoutEffect3(() => {
2815
2952
  const el = ref.current;
2816
2953
  const run = () => measure.current();
2817
2954
  const observer = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(run);
@@ -2837,7 +2974,7 @@ function ItemProblem({ message, onClose, onRetry }) {
2837
2974
  ] })
2838
2975
  ] });
2839
2976
  }
2840
- var ItemBoundary = class extends import_react7.Component {
2977
+ var ItemBoundary = class extends import_react8.Component {
2841
2978
  state = { error: null };
2842
2979
  static getDerivedStateFromError(error) {
2843
2980
  return { error };
@@ -2863,11 +3000,28 @@ var ItemBoundary = class extends import_react7.Component {
2863
3000
  }
2864
3001
  };
2865
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
+
2866
3020
  // src/react/CanvasPane.tsx
2867
- var import_react8 = require("react");
3021
+ var import_react9 = require("react");
2868
3022
  var Menu = __toESM(require("@radix-ui/react-dropdown-menu"), 1);
2869
3023
  var import_jsx_runtime7 = require("react/jsx-runtime");
2870
- var useIsoLayoutEffect2 = typeof window === "undefined" ? import_react8.useEffect : import_react8.useLayoutEffect;
3024
+ var useIsoLayoutEffect4 = typeof window === "undefined" ? import_react9.useEffect : import_react9.useLayoutEffect;
2871
3025
  function itemTitle(item, kind) {
2872
3026
  if (item.title) return item.title;
2873
3027
  if (item.kind === CANVAS_LAUNCHER_KIND) return "New tab";
@@ -2904,7 +3058,7 @@ function CanvasPaneView({ paneId }) {
2904
3058
  const canvas = useCanvas();
2905
3059
  const { windowId, tabStrip } = useCanvasInternals();
2906
3060
  const columnSlots = useCanvasSlots();
2907
- const [ownSlots] = (0, import_react8.useState)(createCanvasSlots);
3061
+ const [ownSlots] = (0, import_react9.useState)(createCanvasSlots);
2908
3062
  const slots = columnSlots ?? ownSlots;
2909
3063
  const pane = useCanvasState((s) => s.panes[paneId]);
2910
3064
  const active = useCanvasState((s) => {
@@ -2917,14 +3071,15 @@ function CanvasPaneView({ paneId }) {
2917
3071
  const paneCount = useCanvasState((s) => selectCanvasPaneCount(s, windowId));
2918
3072
  const role = useCanvasState((s) => windowId === CANVAS_MAIN_WINDOW ? "main" : s.windows[windowId]?.role ?? "window");
2919
3073
  const canGoBack = useCanvasState((s) => active ? selectCanvasCanGoBack(s, active.id) : false);
3074
+ const canGoForward = useCanvasState((s) => active ? selectCanvasCanGoForward(s, active.id) : false);
2920
3075
  const kind = useItemKind(active?.kind ?? "");
2921
3076
  const singlePane = useCanvasSinglePane();
2922
3077
  const windowTabs = useCanvasState((s) => singlePane ? windowTabsSignature(s, windowId) : "");
2923
- const sectionRef = (0, import_react8.useRef)(null);
2924
- const bodyRef = (0, import_react8.useRef)(null);
3078
+ const sectionRef = (0, import_react9.useRef)(null);
3079
+ const bodyRef = (0, import_react9.useRef)(null);
2925
3080
  const presentation = usePanePresentation(slots, paneId, isFullscreen, paneCount);
2926
3081
  const drop = usePaneDrop(paneId, () => sectionRef.current);
2927
- useIsoLayoutEffect2(() => {
3082
+ useIsoLayoutEffect4(() => {
2928
3083
  const body = bodyRef.current;
2929
3084
  const section = sectionRef.current;
2930
3085
  if (body) slots.set(paneId, body);
@@ -2952,7 +3107,17 @@ function CanvasPaneView({ paneId }) {
2952
3107
  focused,
2953
3108
  "data-mxc-pane": paneId,
2954
3109
  header: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
2955
- 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,
2956
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" }),
2957
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, {}) })
2958
3123
  ] }),
@@ -2961,7 +3126,19 @@ function CanvasPaneView({ paneId }) {
2961
3126
  // Its own gapless row: a kind's tap buttons space themselves (the tap-target placement rule).
2962
3127
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "mxc-kind-actions", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Action, { ...kindProps }) })
2963
3128
  ) : null,
2964
- 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
+ ),
2965
3142
  expanded: isFullscreen,
2966
3143
  onToggleExpand: () => canvas.setFullscreen(!isFullscreen),
2967
3144
  expandShortcut: canvasShortcutHint("canvas.toggleFullscreen", isMac()),
@@ -2979,7 +3156,7 @@ function CanvasPaneView({ paneId }) {
2979
3156
  }
2980
3157
  );
2981
3158
  }
2982
- var ToolbarBoundary = class extends import_react8.Component {
3159
+ var ToolbarBoundary = class extends import_react9.Component {
2983
3160
  state = { failed: false };
2984
3161
  static getDerivedStateFromError() {
2985
3162
  return { failed: true };
@@ -2995,77 +3172,135 @@ function SingleTitle({ item, kind }) {
2995
3172
  const canvas = useCanvas();
2996
3173
  const title = itemTitle(item, kind);
2997
3174
  const drag = useTabDrag(item.id, title);
3175
+ const name = useTruncated(title);
2998
3176
  const Icon = kind?.icon ?? AlertIcon;
2999
- 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: [
3000
3178
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Icon, { className: "mxc-title-icon" }),
3001
- /* @__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 }),
3002
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, {}) })
3003
3181
  ] });
3004
3182
  }
3183
+ var CANVAS_TAB_MIN_WIDTH = 78;
3184
+ var CANVAS_ACTIVE_TAB_MIN_WIDTH = 100;
3185
+ var TAB_LIST_BUTTON_ROOM = 30;
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
+ }
3005
3198
  function TabStrip({ tabs, activeItemId }) {
3006
- const strip = (0, import_react8.useRef)(null);
3007
- const [overflow, setOverflow] = (0, import_react8.useState)({ start: false, end: false });
3008
- const measure = (0, import_react8.useRef)(() => void 0);
3009
- measure.current = () => {
3010
- const el = strip.current;
3011
- if (!el) return;
3012
- const start = el.scrollLeft > 1;
3013
- const end = el.scrollLeft + el.clientWidth < el.scrollWidth - 1;
3014
- setOverflow((prev) => prev.start === start && prev.end === end ? prev : { start, end });
3015
- };
3016
- const tabKey = tabs.map((tab) => tab.itemId).join("|");
3017
- useIsoLayoutEffect2(() => {
3199
+ const canvas = useCanvas();
3200
+ const strip = (0, import_react9.useRef)(null);
3201
+ const [width, setWidth] = (0, import_react9.useState)(0);
3202
+ useIsoLayoutEffect4(() => {
3018
3203
  const el = strip.current;
3019
3204
  if (!el) return void 0;
3020
- const reveal = () => {
3021
- const tab = el.querySelector('[role="tab"][aria-selected="true"]');
3022
- if (tab) {
3023
- const left = tab.offsetLeft;
3024
- const right = left + tab.offsetWidth;
3025
- if (left < el.scrollLeft) el.scrollLeft = left;
3026
- else if (right > el.scrollLeft + el.clientWidth) el.scrollLeft = right - el.clientWidth;
3027
- }
3028
- measure.current();
3029
- };
3030
- reveal();
3031
- 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);
3032
3208
  observer?.observe(el);
3033
- for (const tab of el.querySelectorAll('[role="tab"]')) observer?.observe(tab);
3034
3209
  return () => observer?.disconnect();
3035
- }, [activeItemId, tabKey]);
3036
- const page = (direction) => {
3037
- const el = strip.current;
3038
- if (!el) return;
3039
- const by = Math.max(48, Math.round(el.clientWidth * 0.8)) * direction;
3040
- if (typeof el.scrollBy === "function") el.scrollBy({ left: by, behavior: "smooth" });
3041
- else el.scrollLeft += by;
3042
- };
3043
- 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;
3044
3215
  return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
3045
3216
  "div",
3046
3217
  {
3218
+ ref: strip,
3047
3219
  className: "mxc-tabstrip",
3048
- "data-overflow": overflowing ? "" : void 0,
3049
- "data-overflow-start": overflow.start ? "" : void 0,
3050
- "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` },
3051
3223
  children: [
3052
- 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,
3053
3224
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3054
3225
  "div",
3055
3226
  {
3056
- ref: strip,
3057
3227
  className: "mxc-tabs",
3058
3228
  role: "tablist",
3059
3229
  "aria-label": "Canvas tabs",
3060
- onScroll: () => measure.current(),
3061
3230
  onWheel: (event) => {
3062
- const el = strip.current;
3063
- 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);
3064
3234
  },
3065
- 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))
3066
3236
  }
3067
3237
  ),
3068
- 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 })
3069
3304
  ]
3070
3305
  }
3071
3306
  );
@@ -3109,42 +3344,48 @@ function PaneMenu({
3109
3344
  kind,
3110
3345
  kindProps,
3111
3346
  tabCount,
3112
- role
3347
+ role,
3348
+ canSplit
3113
3349
  }) {
3114
3350
  const canvas = useCanvas();
3115
- const { ports, layer } = useCanvasInternals();
3351
+ const { ports, layer, itemMenus } = useCanvasInternals();
3116
3352
  const moveId = tabCount > 1 ? activeItem?.id : void 0;
3117
- const blocking = (0, import_react8.useRef)(null);
3118
- (0, import_react8.useEffect)(() => () => blocking.current?.(), []);
3119
- const kindItems = kind?.menuItems && kindProps ? safeMenu(() => kind.menuItems?.(kindProps)) : [];
3120
- const hostItems = activeItem && ports.itemActions ? safeMenu(() => ports.itemActions?.(activeItem, kind)) : [];
3121
- const sections = [
3122
- [...kindItems, ...hostItems].map((entry) => ({ ...entry, id: `item:${entry.id}` })),
3123
- [
3124
- { id: "split-right", label: "Split right", icon: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(SplitRightIcon, {}), onSelect: () => canvas.splitPane(paneId, "horizontal", moveId) },
3125
- { id: "split-down", label: "Split down", icon: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(SplitDownIcon, {}), onSelect: () => canvas.splitPane(paneId, "vertical", moveId) },
3126
- ...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) }] : []
3127
- ],
3128
- activeItem ? [
3129
- { id: "close-tab", label: "Close tab", onSelect: () => canvas.close(activeItem.id) },
3130
- ...tabCount > 1 ? [{ id: "close-others", label: "Close other tabs", onSelect: () => canvas.closeOthers(activeItem.id) }] : []
3131
- ] : []
3132
- ].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
+ };
3133
3374
  const showMenu = ports.showMenu;
3134
3375
  if (showMenu) {
3135
- const entries = [];
3136
- const handlers = /* @__PURE__ */ new Map();
3137
- sections.forEach((section, i) => {
3138
- if (i > 0) entries.push({ type: "separator" });
3139
- for (const action of section) {
3140
- entries.push({ type: "item", id: action.id, label: action.label, ...action.destructive ? { destructive: true } : {} });
3141
- handlers.set(action.id, action.onSelect);
3142
- }
3143
- });
3144
3376
  return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3145
3377
  CanvasMenuTrigger,
3146
3378
  {
3147
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
+ });
3148
3389
  const anchor = toCanvasRect(event.currentTarget.getBoundingClientRect());
3149
3390
  void showMenu(entries, anchor).then(
3150
3391
  (chosen) => {
@@ -3165,11 +3406,14 @@ function PaneMenu({
3165
3406
  },
3166
3407
  children: [
3167
3408
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Menu.Trigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(CanvasMenuTrigger, {}) }),
3168
- /* @__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 }) }) })
3169
3410
  ]
3170
3411
  }
3171
3412
  );
3172
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
+ }
3173
3417
  function MenuSection({ first, actions }) {
3174
3418
  return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
3175
3419
  first ? null : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Menu.Separator, { className: "mxc-menu-separator" }),
@@ -3190,13 +3434,13 @@ function safeMenu(build) {
3190
3434
 
3191
3435
  // src/react/CanvasColumn.tsx
3192
3436
  var import_jsx_runtime8 = require("react/jsx-runtime");
3193
- var useIsoLayoutEffect3 = typeof window === "undefined" ? import_react9.useEffect : import_react9.useLayoutEffect;
3437
+ var useIsoLayoutEffect5 = typeof window === "undefined" ? import_react10.useEffect : import_react10.useLayoutEffect;
3194
3438
  function subscribeViewport(listener) {
3195
3439
  window.addEventListener("resize", listener);
3196
3440
  return () => window.removeEventListener("resize", listener);
3197
3441
  }
3198
3442
  function useViewportWidth() {
3199
- return (0, import_react9.useSyncExternalStore)(subscribeViewport, () => window.innerWidth, () => 0);
3443
+ return (0, import_react10.useSyncExternalStore)(subscribeViewport, () => window.innerWidth, () => 0);
3200
3444
  }
3201
3445
  function useRegionWidth() {
3202
3446
  const viewport = useViewportWidth();
@@ -3219,15 +3463,15 @@ function CanvasColumn({ className, style, onLiveWidth, placement = "docked" }) {
3219
3463
  const isFullscreen = useCanvasState((s) => selectCanvasIsFullscreen(s, windowId));
3220
3464
  const width = useCanvasState((s) => selectCanvasWidth(s, windowId));
3221
3465
  const layout = useCanvasState((s) => selectCanvasLayout(s, windowId));
3222
- const focusedPaneId = useCanvasState((s) => selectCanvasFocusedPaneId(s, windowId));
3466
+ const presentedPaneId = useCanvasState((s) => selectCanvasPresentedPaneId(s, windowId));
3223
3467
  const region = useRegionWidth();
3224
3468
  const viewport = useViewportWidth();
3225
3469
  const singlePane = placement === "docked" && viewport > 0 && viewport <= CANVAS_PHONE_MAX_WIDTH;
3226
- const [dragWidth, setDragWidth] = (0, import_react9.useState)(null);
3227
- const [slots] = (0, import_react9.useState)(createCanvasSlots);
3228
- 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);
3229
3473
  useRegisterCanvasPresence();
3230
- (0, import_react9.useEffect)(() => {
3474
+ (0, import_react10.useEffect)(() => {
3231
3475
  const el = ref.current;
3232
3476
  if (!el) return void 0;
3233
3477
  let frame2 = 0;
@@ -3288,7 +3532,7 @@ function CanvasColumn({ className, style, onLiveWidth, placement = "docked" }) {
3288
3532
  }
3289
3533
  ) : null,
3290
3534
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(CanvasSlotsContext.Provider, { value: slots, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(CanvasSinglePaneContext.Provider, { value: singlePane, children: [
3291
- /* @__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 }) }),
3292
3536
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(CanvasBodyLayer, {})
3293
3537
  ] }) })
3294
3538
  ] })
@@ -3297,13 +3541,14 @@ function CanvasColumn({ className, style, onLiveWidth, placement = "docked" }) {
3297
3541
  }
3298
3542
  function WidthHandle({ width, region, onDrag }) {
3299
3543
  const canvas = useCanvas();
3300
- const start = (0, import_react9.useRef)(null);
3544
+ const start = (0, import_react10.useRef)(null);
3545
+ (0, import_react10.useEffect)(() => () => start.current?.end(), []);
3301
3546
  const clamp = (next) => clampDraggedCanvasWidth(next, region, canvas.rules);
3302
3547
  const onPointerDown = (event) => {
3303
3548
  event.preventDefault();
3304
3549
  event.currentTarget.setPointerCapture(event.pointerId);
3305
- start.current = { x: event.clientX, width };
3306
- document.documentElement.dataset.mxcResizing = "";
3550
+ start.current?.end();
3551
+ start.current = { x: event.clientX, width, end: beginCanvasResize("") };
3307
3552
  };
3308
3553
  const onPointerMove = (event) => {
3309
3554
  if (!start.current) return;
@@ -3312,8 +3557,8 @@ function WidthHandle({ width, region, onDrag }) {
3312
3557
  const finish = (event) => {
3313
3558
  if (!start.current) return;
3314
3559
  const next = clamp(start.current.width + (start.current.x - event.clientX));
3560
+ start.current.end();
3315
3561
  start.current = null;
3316
- delete document.documentElement.dataset.mxcResizing;
3317
3562
  onDrag(null);
3318
3563
  canvas.setWidth(next);
3319
3564
  };
@@ -3330,6 +3575,7 @@ function WidthHandle({ width, region, onDrag }) {
3330
3575
  onPointerMove,
3331
3576
  onPointerUp: finish,
3332
3577
  onPointerCancel: finish,
3578
+ onLostPointerCapture: finish,
3333
3579
  onDoubleClick: () => canvas.setWidth(clamp(Math.round(region / 2))),
3334
3580
  onKeyDown: (event) => {
3335
3581
  if (event.key === "ArrowLeft") canvas.setWidth(clamp(width + 32));
@@ -3338,17 +3584,13 @@ function WidthHandle({ width, region, onDrag }) {
3338
3584
  }
3339
3585
  );
3340
3586
  }
3341
- function presentedPane(layout, focused) {
3342
- const ids = listPaneIds(layout);
3343
- return ids.find((id) => id === focused) ?? ids[0];
3344
- }
3345
3587
  function LayoutNodeView({ node }) {
3346
3588
  if (node.type === "pane") return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(CanvasPaneView, { paneId: node.paneId });
3347
3589
  return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(SplitView, { node });
3348
3590
  }
3349
3591
  function useMeasuredWidth(ref, enabled) {
3350
- const [width, setWidth] = (0, import_react9.useState)(0);
3351
- useIsoLayoutEffect3(() => {
3592
+ const [width, setWidth] = (0, import_react10.useState)(0);
3593
+ useIsoLayoutEffect5(() => {
3352
3594
  const el = ref.current;
3353
3595
  if (!enabled || !el) return;
3354
3596
  setWidth(el.getBoundingClientRect().width);
@@ -3361,8 +3603,8 @@ function useMeasuredWidth(ref, enabled) {
3361
3603
  }
3362
3604
  function SplitView({ node }) {
3363
3605
  const canvas = useCanvas();
3364
- const ref = (0, import_react9.useRef)(null);
3365
- 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);
3366
3608
  const sizes = liveSizes ?? node.sizes;
3367
3609
  const measuredWidth = useMeasuredWidth(ref, node.orientation === "horizontal" && canvas.rules.paneMinWidth > 0);
3368
3610
  const stacked = node.orientation === "horizontal" && shouldStackSplit(measuredWidth, node.children.length, canvas.rules);
@@ -3379,7 +3621,7 @@ function SplitView({ node }) {
3379
3621
  const base = [...node.sizes];
3380
3622
  const pair = (base[index] ?? 0) + (base[index + 1] ?? 0);
3381
3623
  const target = event.currentTarget;
3382
- document.documentElement.dataset.mxcResizing = horizontal ? "col" : "row";
3624
+ const end = beginCanvasResize(horizontal ? "col" : "row");
3383
3625
  const compute = (pos) => {
3384
3626
  const delta = (pos - origin) / total;
3385
3627
  const min = CANVAS_MIN_SPLIT_FRACTION;
@@ -3394,7 +3636,7 @@ function SplitView({ node }) {
3394
3636
  target.removeEventListener("pointermove", move);
3395
3637
  target.removeEventListener("pointerup", up);
3396
3638
  target.removeEventListener("pointercancel", up);
3397
- delete document.documentElement.dataset.mxcResizing;
3639
+ end();
3398
3640
  setLiveSizes(null);
3399
3641
  canvas.resizeSplit(node.id, compute(horizontal ? e.clientX : e.clientY));
3400
3642
  };