@ai-matrx/canvas 0.3.0 → 0.5.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
@@ -37,6 +37,7 @@ __export(react_exports, {
37
37
  CanvasColumn: () => CanvasColumn,
38
38
  CanvasFrame: () => CanvasFrame,
39
39
  CanvasHeaderButton: () => CanvasHeaderButton,
40
+ CanvasKindIcons: () => CanvasKindIcons,
40
41
  CanvasMenuTrigger: () => CanvasMenuTrigger,
41
42
  CanvasPaneView: () => CanvasPaneView,
42
43
  CanvasPanel: () => CanvasPanel,
@@ -59,7 +60,10 @@ __export(react_exports, {
59
60
  useCanvasKind: () => useCanvasKind,
60
61
  useCanvasKindToggle: () => useCanvasKindToggle,
61
62
  useCanvasKinds: () => useCanvasKinds,
63
+ useCanvasLauncherEntries: () => useCanvasLauncherEntries,
64
+ useCanvasLauncherEntry: () => useCanvasLauncherEntry,
62
65
  useCanvasNativeBodies: () => useCanvasNativeBodies,
66
+ useCanvasPresentation: () => useCanvasPresentation,
63
67
  useCanvasState: () => useCanvasState,
64
68
  useCanvasWindowId: () => useCanvasWindowId,
65
69
  useOptionalCanvas: () => useOptionalCanvas,
@@ -72,6 +76,8 @@ var import_react2 = require("react");
72
76
 
73
77
  // src/core/types.ts
74
78
  var CANVAS_MAIN_WINDOW = "main";
79
+ var CANVAS_LAUNCHER_KIND = "canvas.launcher";
80
+ var CANVAS_HISTORY_LIMIT = 50;
75
81
  var CANVAS_MIN_WIDTH = 360;
76
82
  var CANVAS_DEFAULT_WIDTH = 640;
77
83
  var CANVAS_MIN_SPLIT_FRACTION = 0.12;
@@ -230,6 +236,10 @@ var canvasActions = {
230
236
  }),
231
237
  /** Gives an item a new identity in place (e.g. a draft that was just saved and now has an id). */
232
238
  rekey: (itemId, key) => ({ type: `${P}rekey`, payload: { itemId, key } }),
239
+ /** Replaces a tab's content with another item in place, remembering what it showed (Back returns). */
240
+ navigate: (itemId, to) => ({ type: `${P}navigate`, payload: { itemId, to, now: Date.now() } }),
241
+ /** Returns a tab to what it showed before its last navigate. */
242
+ back: (itemId) => ({ type: `${P}back`, payload: { itemId, now: Date.now() } }),
233
243
  closeItem: (itemId) => ({ type: `${P}closeItem`, payload: { itemId } }),
234
244
  closeOthers: (itemId) => ({ type: `${P}closeOthers`, payload: { itemId } }),
235
245
  activate: (itemId) => ({ type: `${P}activate`, payload: { itemId } }),
@@ -426,6 +436,63 @@ function sendHome(state, itemId, home, activate) {
426
436
  next = insertInto(next, homeFrame.focusedPaneId, itemId, activate);
427
437
  return activate ? withFrame(next, target, { focusedPaneId: homeFrame.focusedPaneId }) : next;
428
438
  }
439
+ function newItem(kind, key, title, data, now, history) {
440
+ const id = canvasItemId(kind, key);
441
+ return {
442
+ id,
443
+ kind,
444
+ key,
445
+ title,
446
+ data,
447
+ openedAt: now,
448
+ updatedAt: now,
449
+ instance: `${now.toString(36)}:${id}`,
450
+ ...history && history.length > 0 ? { history: history.slice(-CANVAS_HISTORY_LIMIT) } : {}
451
+ };
452
+ }
453
+ function historyEntry(item) {
454
+ return { kind: item.kind, key: item.key, title: item.title, data: item.data };
455
+ }
456
+ function replaceInPlace(state, current, next) {
457
+ const pane = paneOf(state, current.id);
458
+ if (!pane) return state;
459
+ const items = { ...state.items, [next.id]: next };
460
+ delete items[current.id];
461
+ const withItems = { ...state, items };
462
+ const replaced = withPane(withItems, {
463
+ ...pane,
464
+ itemIds: pane.itemIds.map((id) => id === current.id ? next.id : id),
465
+ activeItemId: pane.activeItemId === current.id ? next.id : pane.activeItemId
466
+ });
467
+ return focus(replaced, pane.id);
468
+ }
469
+ function focusExisting(state, itemId) {
470
+ const pane = paneOf(state, itemId);
471
+ if (!pane) return state;
472
+ const next = focus(withPane(state, { ...pane, activeItemId: itemId }), pane.id);
473
+ const windowId = canvasWindowOfPane(next, pane.id);
474
+ return windowId ? withFrame(next, windowId, { isOpen: true }) : next;
475
+ }
476
+ function sanitizeHistory(raw) {
477
+ if (!Array.isArray(raw)) return [];
478
+ const out = [];
479
+ for (const entry of raw) {
480
+ if (!entry || typeof entry !== "object") continue;
481
+ const e = entry;
482
+ if (typeof e.kind !== "string" || typeof e.key !== "string") continue;
483
+ out.push({ kind: e.kind, key: e.key, title: typeof e.title === "string" ? e.title : null, data: e.data ?? null });
484
+ }
485
+ return out.slice(-CANVAS_HISTORY_LIMIT);
486
+ }
487
+ function sanitizeItem(item) {
488
+ const history = sanitizeHistory(item.history);
489
+ const { history: _history, instance, ...rest } = item;
490
+ return {
491
+ ...rest,
492
+ ...typeof instance === "string" && instance ? { instance } : {},
493
+ ...history.length > 0 ? { history } : {}
494
+ };
495
+ }
429
496
  function clampWidth(width) {
430
497
  return Number.isFinite(width) ? Math.max(CANVAS_MIN_WIDTH, Math.round(width)) : null;
431
498
  }
@@ -473,7 +540,7 @@ function sanitizeCanvasSnapshot(raw) {
473
540
  const items = {};
474
541
  for (const [id, item] of Object.entries(s.items)) {
475
542
  if (item && typeof item.kind === "string" && typeof item.key === "string" && canvasItemId(item.kind, item.key) === id) {
476
- items[id] = item;
543
+ items[id] = sanitizeItem(item);
477
544
  }
478
545
  }
479
546
  const placed = /* @__PURE__ */ new Set();
@@ -547,7 +614,7 @@ function canvasReducer(state = createInitialCanvasState(), action) {
547
614
  next = insertInto(next, frame2.focusedPaneId, itemId, true);
548
615
  }
549
616
  } else {
550
- const item = { id: itemId, kind, key, title: title ?? null, data: data ?? null, openedAt: now, updatedAt: now };
617
+ const item = newItem(kind, key, title ?? null, data ?? null, now);
551
618
  next = ensureWindow({ ...next, items: { ...next.items, [itemId]: item } }, revealIn);
552
619
  const frame2 = canvasWindowFrame(next, revealIn) ?? next;
553
620
  let paneId = frame2.focusedPaneId;
@@ -596,6 +663,35 @@ function canvasReducer(state = createInitialCanvasState(), action) {
596
663
  }
597
664
  return { ...state, items, panes };
598
665
  }
666
+ case `${P}navigate`: {
667
+ const { itemId, to, now } = action.payload;
668
+ const current = state.items[itemId];
669
+ if (!current || !paneOf(state, itemId)) return state;
670
+ const targetId = canvasItemId(to.kind, to.key);
671
+ if (targetId === itemId) {
672
+ const refresh = { type: `${P}update`, payload: { itemId, data: to.data, title: to.title, now } };
673
+ return canvasReducer(state, refresh);
674
+ }
675
+ if (state.items[targetId]) {
676
+ const focused = focusExisting(state, targetId);
677
+ return current.kind === CANVAS_LAUNCHER_KIND ? canvasReducer(focused, canvasActions.closeItem(itemId)) : focused;
678
+ }
679
+ const history = [...current.history ?? [], historyEntry(current)];
680
+ return replaceInPlace(state, current, newItem(to.kind, to.key, to.title ?? null, to.data ?? null, now, history));
681
+ }
682
+ case `${P}back`: {
683
+ const { itemId, now } = action.payload;
684
+ const current = state.items[itemId];
685
+ const history = current?.history ?? [];
686
+ const entry = history[history.length - 1];
687
+ if (!current || !entry || !paneOf(state, itemId)) return state;
688
+ const targetId = canvasItemId(entry.kind, entry.key);
689
+ if (targetId === itemId) return state;
690
+ if (state.items[targetId]) {
691
+ return focusExisting(canvasReducer(state, canvasActions.closeItem(itemId)), targetId);
692
+ }
693
+ return replaceInPlace(state, current, newItem(entry.kind, entry.key, entry.title, entry.data, now, history.slice(0, -1)));
694
+ }
599
695
  case `${P}closeItem`: {
600
696
  const { itemId } = action.payload;
601
697
  if (!state.items[itemId]) return state;
@@ -849,7 +945,8 @@ var CANVAS_WEB_LAYOUT_RULES = {
849
945
  defaultWidth: CANVAS_DEFAULT_WIDTH,
850
946
  minWidth: CANVAS_MIN_WIDTH,
851
947
  centreMinWidth: 420,
852
- stackSplit: 0.5,
948
+ // Split down 70/30 like the desktop: what opens below (notifications, a feed) is the smaller pane.
949
+ stackSplit: 0.7,
853
950
  sideSplit: 0.5,
854
951
  paneMinWidth: 0
855
952
  };
@@ -974,7 +1071,8 @@ function createCanvasController(options) {
974
1071
  presentationListeners: /* @__PURE__ */ new Set(),
975
1072
  views: /* @__PURE__ */ new Map(),
976
1073
  started: /* @__PURE__ */ new Map(),
977
- sessionStop: null
1074
+ sessionStop: null,
1075
+ launcherSeq: 0
978
1076
  };
979
1077
  return view(shared, canvasWindowId(options.windowId ?? CANVAS_MAIN_WINDOW));
980
1078
  }
@@ -989,6 +1087,36 @@ function view(shared, windowId) {
989
1087
  for (const listener of [...shared.presentationListeners]) listener();
990
1088
  };
991
1089
  const shareFor = (orientation) => orientation === "vertical" ? rules.stackSplit : rules.sideSplit;
1090
+ const acceptable = (input) => {
1091
+ if (input.kind !== CANVAS_LAUNCHER_KIND && options.isKnownKind && !options.isKnownKind(input.kind)) {
1092
+ onError({ code: "unknown-kind", message: `No canvas kind "${input.kind}" is registered.`, detail: input });
1093
+ return false;
1094
+ }
1095
+ if (input.data !== void 0) {
1096
+ const bad = findNonJson(input.data);
1097
+ if (bad) {
1098
+ onError({
1099
+ code: "non-json-data",
1100
+ message: `Canvas data for "${input.kind}" must be plain JSON; ${bad} is not.`,
1101
+ detail: { kind: input.kind, key: input.key }
1102
+ });
1103
+ return false;
1104
+ }
1105
+ }
1106
+ return true;
1107
+ };
1108
+ const preferredTargetFor = (input) => {
1109
+ const preferred = options.preferredTarget?.(input.kind);
1110
+ if (!preferred || preferred === "focused") return "focused";
1111
+ const state = store.getState();
1112
+ if (canvasItemId(input.kind, input.key) in state.items) return "focused";
1113
+ const frame2 = canvasWindowFrame(state, input.windowId ?? windowId);
1114
+ if (!frame2?.isOpen) return "focused";
1115
+ const panes = listPaneIds(frame2.layout);
1116
+ const holding = panes.find((paneId) => state.panes[paneId]?.itemIds.some((id) => state.items[id]?.kind === input.kind));
1117
+ if (holding) return { paneId: holding };
1118
+ return state.panes[frame2.focusedPaneId]?.activeItemId ? preferred : "focused";
1119
+ };
992
1120
  const controller = {
993
1121
  store,
994
1122
  windowId,
@@ -996,24 +1124,11 @@ function view(shared, windowId) {
996
1124
  forWindow: (id) => view(shared, canvasWindowId(id)),
997
1125
  getState: () => store.getState(),
998
1126
  open(input) {
999
- if (options.isKnownKind && !options.isKnownKind(input.kind)) {
1000
- onError({ code: "unknown-kind", message: `No canvas kind "${input.kind}" is registered.`, detail: input });
1001
- return null;
1002
- }
1003
- if (input.data !== void 0) {
1004
- const bad = findNonJson(input.data);
1005
- if (bad) {
1006
- onError({
1007
- code: "non-json-data",
1008
- message: `Canvas data for "${input.kind}" must be plain JSON; ${bad} is not.`,
1009
- detail: { kind: input.kind, key: input.key }
1010
- });
1011
- return null;
1012
- }
1013
- }
1014
- const target = input.target ?? "focused";
1127
+ if (!acceptable(input)) return null;
1128
+ const target = input.target ?? preferredTargetFor(input);
1015
1129
  const splitShare = target === "split-down" ? rules.stackSplit : target === "split-right" ? rules.sideSplit : void 0;
1016
- const resolved = input.windowId || windowId === CANVAS_MAIN_WINDOW ? input : { ...input, windowId };
1130
+ const withTarget = input.target === target ? input : { ...input, target };
1131
+ const resolved = withTarget.windowId || windowId === CANVAS_MAIN_WINDOW ? withTarget : { ...withTarget, windowId };
1017
1132
  dispatch(canvasActions.open(resolved, splitShare === void 0 ? void 0 : { splitShare }));
1018
1133
  return canvasItemId(input.kind, input.key);
1019
1134
  },
@@ -1029,6 +1144,34 @@ function view(shared, windowId) {
1029
1144
  dispatch(canvasActions.update(itemId, patch));
1030
1145
  return true;
1031
1146
  },
1147
+ navigate(itemId, to) {
1148
+ if (!store.getState().items[itemId] || !acceptable(to)) return null;
1149
+ dispatch(canvasActions.navigate(itemId, to));
1150
+ const shown = canvasItemId(to.kind, to.key);
1151
+ return shown in store.getState().items ? shown : null;
1152
+ },
1153
+ back(itemId) {
1154
+ const history = store.getState().items[itemId]?.history ?? [];
1155
+ const entry = history[history.length - 1];
1156
+ if (!entry) return null;
1157
+ dispatch(canvasActions.back(itemId));
1158
+ return canvasItemId(entry.kind, entry.key);
1159
+ },
1160
+ newTab(paneId) {
1161
+ const state = store.getState();
1162
+ const frame2 = canvasWindowFrame(state, windowId);
1163
+ const target = paneId && state.panes[paneId] ? paneId : frame2?.focusedPaneId ?? state.focusedPaneId;
1164
+ const existing = state.panes[target]?.itemIds.find((id) => state.items[id]?.kind === CANVAS_LAUNCHER_KIND);
1165
+ if (existing) {
1166
+ dispatch(canvasActions.activate(existing));
1167
+ dispatch(canvasActions.setOpen(true, canvasWindowOfItem(store.getState(), existing) ?? windowId));
1168
+ return existing;
1169
+ }
1170
+ shared.launcherSeq += 1;
1171
+ const key = `${Date.now().toString(36)}-${shared.launcherSeq}`;
1172
+ dispatch(canvasActions.open({ kind: CANVAS_LAUNCHER_KIND, key, target: { paneId: target }, ...windowId === CANVAS_MAIN_WINDOW ? {} : { windowId } }));
1173
+ return canvasItemId(CANVAS_LAUNCHER_KIND, key);
1174
+ },
1032
1175
  rekey(itemId, key) {
1033
1176
  const item = store.getState().items[itemId];
1034
1177
  if (!item) return null;
@@ -1278,6 +1421,32 @@ function selectCanvasWindowItemCount(s, windowId) {
1278
1421
  return listPaneIds(layout).reduce((sum, paneId) => sum + (s.panes[paneId]?.itemIds.length ?? 0), 0);
1279
1422
  }
1280
1423
  var NO_WINDOWS = Object.freeze({});
1424
+ var NO_HISTORY = Object.freeze([]);
1425
+ var selectCanvasCanGoBack = (s, itemId) => (s.items[itemId]?.history?.length ?? 0) > 0;
1426
+
1427
+ // src/core/presentation.ts
1428
+ var CANVAS_NARROW_WIDTH = 480;
1429
+ var SQUARE_BAND = 1.2;
1430
+ function canvasOrientation(width, height) {
1431
+ if (!(width > 0) || !(height > 0)) return "square";
1432
+ const ratio = width / height;
1433
+ if (ratio >= SQUARE_BAND) return "landscape";
1434
+ if (ratio <= 1 / SQUARE_BAND) return "portrait";
1435
+ return "square";
1436
+ }
1437
+ function canvasPresentation(input) {
1438
+ const width = Math.max(0, Math.round(input.width));
1439
+ const height = Math.max(0, Math.round(input.height));
1440
+ return {
1441
+ host: "canvas",
1442
+ width,
1443
+ height,
1444
+ orientation: canvasOrientation(width, height),
1445
+ isNarrow: width > 0 && width < CANVAS_NARROW_WIDTH,
1446
+ isFullscreen: input.isFullscreen,
1447
+ paneCount: Math.max(1, input.paneCount)
1448
+ };
1449
+ }
1281
1450
 
1282
1451
  // src/core/toggleKind.ts
1283
1452
  var CANVAS_DEFAULT_KEY = "default";
@@ -1479,6 +1648,31 @@ function getCanvasKindsVersion() {
1479
1648
 
1480
1649
  // src/react/provider.tsx
1481
1650
  var import_jsx_runtime2 = require("react/jsx-runtime");
1651
+ function createLauncherEntryStore() {
1652
+ let entries = [];
1653
+ const listeners = /* @__PURE__ */ new Set();
1654
+ const emit = () => {
1655
+ for (const listener of [...listeners]) listener();
1656
+ };
1657
+ return {
1658
+ add(entry) {
1659
+ entries = [...entries, entry];
1660
+ emit();
1661
+ let done = false;
1662
+ return () => {
1663
+ if (done) return;
1664
+ done = true;
1665
+ entries = entries.filter((e) => e !== entry);
1666
+ emit();
1667
+ };
1668
+ },
1669
+ list: () => entries,
1670
+ subscribe(listener) {
1671
+ listeners.add(listener);
1672
+ return () => listeners.delete(listener);
1673
+ }
1674
+ };
1675
+ }
1482
1676
  var PHONE_MAX_WIDTH = 767;
1483
1677
  var isPhoneViewport = () => typeof window !== "undefined" && window.innerWidth <= PHONE_MAX_WIDTH;
1484
1678
  var isMacPlatform = () => typeof navigator !== "undefined" && /mac|iphone|ipad/i.test(navigator.userAgentData?.platform ?? navigator.platform ?? "");
@@ -1511,7 +1705,7 @@ function CanvasProvider({
1511
1705
  }) {
1512
1706
  const portsRef = (0, import_react2.useRef)(hostPorts);
1513
1707
  portsRef.current = hostPorts;
1514
- const [{ controller, layer }] = (0, import_react2.useState)(() => ({
1708
+ const [{ controller, layer, launcherEntries }] = (0, import_react2.useState)(() => ({
1515
1709
  controller: createCanvasController({
1516
1710
  store: store ?? createCanvasStore(),
1517
1711
  persistence: persistence === void 0 ? createLocalStorageCanvasPersistence() : persistence,
@@ -1521,9 +1715,11 @@ function CanvasProvider({
1521
1715
  windowId: windowIdProp ?? CANVAS_MAIN_WINDOW,
1522
1716
  layoutRules,
1523
1717
  windowPorts: () => portsRef.current,
1524
- sessions: sessionHooksFor
1718
+ sessions: sessionHooksFor,
1719
+ preferredTarget: (kind) => getCanvasKind(kind)?.preferredTarget
1525
1720
  }),
1526
- layer: createCanvasLayer(() => portsRef.current)
1721
+ layer: createCanvasLayer(() => portsRef.current),
1722
+ launcherEntries: createLauncherEntryStore()
1527
1723
  }));
1528
1724
  const windowId = controller.windowId;
1529
1725
  (0, import_react2.useEffect)(() => controller.start(), [controller]);
@@ -1548,7 +1744,7 @@ function CanvasProvider({
1548
1744
  window.addEventListener("keydown", onKey);
1549
1745
  return () => window.removeEventListener("keydown", onKey);
1550
1746
  }, [controller, hotkeys, windowId]);
1551
- const value = { controller, windowId, ports: hostPorts, layer, regionWidth, tabStrip };
1747
+ const value = { controller, windowId, ports: hostPorts, layer, regionWidth, tabStrip, launcherEntries };
1552
1748
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(CanvasContext.Provider, { value, children: [
1553
1749
  children,
1554
1750
  hostPorts.overlay ? null : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(CanvasDomOverlay, { layer })
@@ -1635,14 +1831,38 @@ function useCanvasKind(id) {
1635
1831
  (0, import_react2.useSyncExternalStore)(subscribeCanvasKinds, getCanvasKindsVersion, getCanvasKindsVersion);
1636
1832
  return getCanvasKind(id);
1637
1833
  }
1834
+ var NO_ENTRIES = Object.freeze([]);
1835
+ var noEntries = () => NO_ENTRIES;
1836
+ function useCanvasLauncherEntry(entry) {
1837
+ const store = (0, import_react2.useContext)(CanvasContext)?.launcherEntries;
1838
+ const fingerprint = JSON.stringify([entry.kind, entry.key, entry.title ?? null, entry.data ?? null]);
1839
+ const icon = entry.icon;
1840
+ (0, import_react2.useEffect)(() => {
1841
+ if (!store) return void 0;
1842
+ const [kind, key, title, data] = JSON.parse(fingerprint);
1843
+ return store.add({ kind, key, title, data, ...icon ? { icon } : {} });
1844
+ }, [store, fingerprint, icon]);
1845
+ }
1846
+ function useCanvasLauncherEntries() {
1847
+ const store = (0, import_react2.useContext)(CanvasContext)?.launcherEntries;
1848
+ const all = (0, import_react2.useSyncExternalStore)(store ? store.subscribe : NO_SUBSCRIPTION, store ? store.list : noEntries, store ? store.list : noEntries);
1849
+ const seen = /* @__PURE__ */ new Set();
1850
+ const out = [];
1851
+ for (let i = all.length - 1; i >= 0; i -= 1) {
1852
+ const entry = all[i];
1853
+ if (!entry || seen.has(`${entry.kind}::${entry.key}`)) continue;
1854
+ seen.add(`${entry.kind}::${entry.key}`);
1855
+ out.unshift(entry);
1856
+ }
1857
+ return out;
1858
+ }
1638
1859
 
1639
1860
  // src/react/CanvasColumn.tsx
1640
- var import_react5 = require("react");
1861
+ var import_react9 = require("react");
1641
1862
  var import_tap_target = require("@ai-matrx/tap-target");
1642
1863
 
1643
- // src/react/CanvasPane.tsx
1644
- var import_react4 = require("react");
1645
- var Menu = __toESM(require("@radix-ui/react-dropdown-menu"), 1);
1864
+ // src/react/bodies.tsx
1865
+ var import_react7 = require("react");
1646
1866
 
1647
1867
  // src/react/chrome.tsx
1648
1868
  var import_react3 = require("react");
@@ -1737,6 +1957,12 @@ var DockIcon = (p) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Glyph, { ...
1737
1957
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M21 3 12 12" }),
1738
1958
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M12 6v6h6" })
1739
1959
  ] });
1960
+ var LayoutGridIcon = (p) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Glyph, { ...p, children: [
1961
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("rect", { width: "7", height: "7", x: "3", y: "3", rx: "1" }),
1962
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("rect", { width: "7", height: "7", x: "14", y: "3", rx: "1" }),
1963
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("rect", { width: "7", height: "7", x: "14", y: "14", rx: "1" }),
1964
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("rect", { width: "7", height: "7", x: "3", y: "14", rx: "1" })
1965
+ ] });
1740
1966
 
1741
1967
  // src/react/chrome.tsx
1742
1968
  var import_jsx_runtime4 = require("react/jsx-runtime");
@@ -1987,22 +2213,523 @@ function BrowserBar({ url, canGoBack, canGoForward, onBack, onForward, onReload,
1987
2213
  ] });
1988
2214
  }
1989
2215
 
1990
- // src/react/CanvasPane.tsx
2216
+ // src/react/dnd.ts
2217
+ var import_react4 = require("react");
2218
+ var CANVAS_DRAG_MIME = "application/x-matrx-canvas-item";
2219
+ function useTabDrag(itemId, title) {
2220
+ const { layer } = useCanvasInternals();
2221
+ const active = (0, import_react4.useRef)([]);
2222
+ const end = () => {
2223
+ for (const dispose of active.current.splice(0)) dispose();
2224
+ };
2225
+ (0, import_react4.useEffect)(() => end, []);
2226
+ return {
2227
+ draggable: true,
2228
+ onDragStart: (event) => {
2229
+ event.dataTransfer.setData(CANVAS_DRAG_MIME, itemId);
2230
+ event.dataTransfer.effectAllowed = "move";
2231
+ end();
2232
+ active.current.push(layer.beginBlocking(), layer.show({ type: "tab-drag", itemId, title }));
2233
+ },
2234
+ onDragEnd: end
2235
+ };
2236
+ }
2237
+ function usePaneDrop(paneId, getOutline) {
2238
+ const canvas = useCanvas();
2239
+ const { layer } = useCanvasInternals();
2240
+ const drop = (0, import_react4.useRef)(null);
2241
+ (0, import_react4.useEffect)(() => () => drop.current?.(), []);
2242
+ const hide = () => {
2243
+ drop.current?.();
2244
+ drop.current = null;
2245
+ };
2246
+ return {
2247
+ onDragOver: (event) => {
2248
+ if (!event.dataTransfer.types.includes(CANVAS_DRAG_MIME)) return;
2249
+ event.preventDefault();
2250
+ event.dataTransfer.dropEffect = "move";
2251
+ const outline = getOutline();
2252
+ if (!drop.current && outline) {
2253
+ drop.current = layer.show({ type: "drop-indicator", rect: toCanvasRect(outline.getBoundingClientRect()), paneId });
2254
+ }
2255
+ },
2256
+ onDragLeave: (event) => {
2257
+ const next = event.relatedTarget;
2258
+ if (next && event.currentTarget.contains(next)) return;
2259
+ hide();
2260
+ },
2261
+ onDrop: (event) => {
2262
+ hide();
2263
+ const itemId = event.dataTransfer.getData(CANVAS_DRAG_MIME);
2264
+ if (!itemId) return;
2265
+ event.preventDefault();
2266
+ canvas.moveItem(itemId, paneId);
2267
+ }
2268
+ };
2269
+ }
2270
+
2271
+ // src/react/launcher.tsx
1991
2272
  var import_jsx_runtime5 = require("react/jsx-runtime");
1992
- var DRAG_MIME = "application/x-matrx-canvas-item";
1993
- var useIsoLayoutEffect = typeof window === "undefined" ? import_react4.useEffect : import_react4.useLayoutEffect;
2273
+ function useChoices() {
2274
+ const entries = useCanvasLauncherEntries();
2275
+ const kinds = useCanvasKinds();
2276
+ const byId = new Map(kinds.map((kind) => [kind.id, kind]));
2277
+ const contextual = [];
2278
+ for (const entry of entries) {
2279
+ const kind = byId.get(entry.kind);
2280
+ if (!kind) continue;
2281
+ contextual.push({
2282
+ id: `ctx:${canvasItemId(entry.kind, entry.key)}`,
2283
+ label: entry.title ?? kind.label,
2284
+ icon: entry.icon ?? kind.icon,
2285
+ input: { kind: entry.kind, key: entry.key, title: entry.title ?? null, ...entry.data !== void 0 ? { data: entry.data } : {} }
2286
+ });
2287
+ }
2288
+ const global = [];
2289
+ for (const kind of kinds) {
2290
+ const launcher = kind.launcher;
2291
+ if (!launcher) continue;
2292
+ global.push({
2293
+ id: `kind:${kind.id}`,
2294
+ label: launcher.title ?? kind.label,
2295
+ icon: kind.icon,
2296
+ input: { kind: kind.id, key: launcher.key, data: launcher.data, title: launcher.title ?? null }
2297
+ });
2298
+ }
2299
+ return { contextual, global };
2300
+ }
2301
+ function LauncherGrid({ onChoose }) {
2302
+ const { contextual, global } = useChoices();
2303
+ if (contextual.length === 0 && global.length === 0) return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(CanvasPanelEmpty, { children: "Nothing to open" });
2304
+ const section = (label, choices) => choices.length === 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("section", { className: "mxc-launcher-section", "aria-label": label ?? "Open", children: [
2305
+ label ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("h3", { className: "mxc-launcher-heading", children: label }) : null,
2306
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "mxc-launcher-grid", children: choices.map((choice) => {
2307
+ const Icon = choice.icon;
2308
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("button", { type: "button", className: "mxc-launcher-item", onClick: () => onChoose(choice.input), children: [
2309
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Icon, { className: "mxc-launcher-icon" }),
2310
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children: choice.label })
2311
+ ] }, choice.id);
2312
+ }) })
2313
+ ] });
2314
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "mxc-launcher", children: [
2315
+ section(contextual.length > 0 ? "On this page" : null, contextual),
2316
+ section(contextual.length > 0 ? "Open" : null, global)
2317
+ ] });
2318
+ }
2319
+ function LauncherBody({ item, canvas }) {
2320
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(LauncherGrid, { onChoose: (input) => void canvas.navigate(item.id, input) });
2321
+ }
2322
+ function EmptyPaneLauncher({ paneId }) {
2323
+ const canvas = useCanvas();
2324
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2325
+ LauncherGrid,
2326
+ {
2327
+ onChoose: (input) => {
2328
+ const tab = canvas.newTab(paneId);
2329
+ canvas.navigate(tab, input);
2330
+ }
2331
+ }
2332
+ );
2333
+ }
2334
+ var CANVAS_LAUNCHER_KIND_DEFINITION = {
2335
+ id: CANVAS_LAUNCHER_KIND,
2336
+ label: "New tab",
2337
+ icon: LayoutGridIcon,
2338
+ component: LauncherBody
2339
+ };
2340
+ function useItemKind(kindId) {
2341
+ const registered = useCanvasKind(kindId);
2342
+ return kindId === CANVAS_LAUNCHER_KIND ? CANVAS_LAUNCHER_KIND_DEFINITION : registered;
2343
+ }
2344
+
2345
+ // src/react/presentation.tsx
2346
+ var import_react5 = require("react");
2347
+ var CanvasPresentationContext = (0, import_react5.createContext)(null);
2348
+ function useCanvasPresentation() {
2349
+ return (0, import_react5.useContext)(CanvasPresentationContext);
2350
+ }
2351
+
2352
+ // src/react/slots.ts
2353
+ var import_react6 = require("react");
2354
+ var ZERO = Object.freeze({ width: 0, height: 0 });
2355
+ function createCanvasSlots() {
2356
+ const slots = /* @__PURE__ */ new Map();
2357
+ const sections = /* @__PURE__ */ new Map();
2358
+ const sizes = /* @__PURE__ */ new Map();
2359
+ const listeners = /* @__PURE__ */ new Set();
2360
+ let observer = null;
2361
+ const emit = () => {
2362
+ for (const listener of [...listeners]) listener();
2363
+ };
2364
+ const read = (paneId, element) => {
2365
+ const box = element.getBoundingClientRect();
2366
+ const width = Math.round(box.width);
2367
+ const height = Math.round(box.height);
2368
+ const previous = sizes.get(paneId);
2369
+ if (previous && previous.width === width && previous.height === height) return false;
2370
+ sizes.set(paneId, { width, height });
2371
+ return true;
2372
+ };
2373
+ const measureAll = () => {
2374
+ let changed = false;
2375
+ for (const [paneId, element] of slots) changed = read(paneId, element) || changed;
2376
+ return changed;
2377
+ };
2378
+ const watch = () => {
2379
+ if (observer || typeof ResizeObserver === "undefined") return observer;
2380
+ observer = new ResizeObserver(() => {
2381
+ measureAll();
2382
+ emit();
2383
+ });
2384
+ return observer;
2385
+ };
2386
+ return {
2387
+ set(paneId, element) {
2388
+ if (slots.get(paneId) === element) return;
2389
+ const previous = slots.get(paneId);
2390
+ if (previous) observer?.unobserve(previous);
2391
+ slots.set(paneId, element);
2392
+ watch()?.observe(element);
2393
+ read(paneId, element);
2394
+ emit();
2395
+ },
2396
+ unset(paneId, element) {
2397
+ const current = slots.get(paneId);
2398
+ if (!current || element && current !== element) return;
2399
+ observer?.unobserve(current);
2400
+ slots.delete(paneId);
2401
+ sizes.delete(paneId);
2402
+ emit();
2403
+ },
2404
+ setSection(paneId, element) {
2405
+ if (element) sections.set(paneId, element);
2406
+ else sections.delete(paneId);
2407
+ },
2408
+ slot: (paneId) => slots.get(paneId) ?? null,
2409
+ section: (paneId) => sections.get(paneId) ?? null,
2410
+ size: (paneId) => sizes.get(paneId) ?? ZERO,
2411
+ measure() {
2412
+ if (measureAll()) emit();
2413
+ },
2414
+ subscribe(listener) {
2415
+ listeners.add(listener);
2416
+ return () => listeners.delete(listener);
2417
+ }
2418
+ };
2419
+ }
2420
+ var CanvasSlotsContext = (0, import_react6.createContext)(null);
2421
+ function useCanvasSlots() {
2422
+ return (0, import_react6.useContext)(CanvasSlotsContext);
2423
+ }
2424
+ function useSlotSize(slots, paneId) {
2425
+ return (0, import_react6.useSyncExternalStore)(
2426
+ slots.subscribe,
2427
+ () => slots.size(paneId),
2428
+ () => ZERO
2429
+ );
2430
+ }
2431
+
2432
+ // src/react/bodies.tsx
2433
+ var import_jsx_runtime6 = require("react/jsx-runtime");
2434
+ var useIsoLayoutEffect = typeof window === "undefined" ? import_react7.useEffect : import_react7.useLayoutEffect;
1994
2435
  var LAZY = /* @__PURE__ */ Symbol.for("ai-matrx.canvas.lazy");
1995
2436
  function kindComponent(kind) {
1996
2437
  if (kind.component) return kind.component;
1997
2438
  const holder = kind;
1998
- if (!holder[LAZY] && kind.load) holder[LAZY] = (0, import_react4.lazy)(kind.load);
2439
+ if (!holder[LAZY] && kind.load) holder[LAZY] = (0, import_react7.lazy)(kind.load);
1999
2440
  return holder[LAZY] ?? null;
2000
2441
  }
2001
2442
  function resetKindComponent(kind) {
2002
2443
  if (kind) delete kind[LAZY];
2003
2444
  }
2445
+ function usePanePresentation(slots, paneId, isFullscreen, paneCount) {
2446
+ const size = useSlotSize(slots, paneId);
2447
+ return (0, import_react7.useMemo)(
2448
+ () => canvasPresentation({ width: size.width, height: size.height, isFullscreen, paneCount }),
2449
+ [size.width, size.height, isFullscreen, paneCount]
2450
+ );
2451
+ }
2452
+ function placedItems(state, windowId, onlyPane) {
2453
+ const frame2 = canvasWindowFrame(state, windowId);
2454
+ if (!frame2) return [];
2455
+ const out = [];
2456
+ for (const paneId of onlyPane ? [onlyPane] : listPaneIds(frame2.layout)) {
2457
+ const pane = state.panes[paneId];
2458
+ for (const itemId of pane?.itemIds ?? []) {
2459
+ const item = state.items[itemId];
2460
+ if (item) out.push({ item, paneId, active: pane?.activeItemId === itemId });
2461
+ }
2462
+ }
2463
+ return out.sort((a, b) => a.item.openedAt - b.item.openedAt || hostKey(a.item).localeCompare(hostKey(b.item)));
2464
+ }
2465
+ var hostKey = (item) => item.instance ?? item.id;
2466
+ function createPlacement(getRoot, slots, afterPlace) {
2467
+ const hosts = /* @__PURE__ */ new Map();
2468
+ let placing = false;
2469
+ const place = () => {
2470
+ const root = getRoot();
2471
+ if (!root || placing) return;
2472
+ placing = true;
2473
+ try {
2474
+ slots.measure();
2475
+ const base = root.getBoundingClientRect();
2476
+ for (const { element, paneId } of hosts.values()) {
2477
+ const slot = slots.slot(paneId);
2478
+ if (!slot) {
2479
+ element.dataset.placed = "false";
2480
+ continue;
2481
+ }
2482
+ const box = slot.getBoundingClientRect();
2483
+ const style = element.style;
2484
+ style.left = `${box.left - base.left}px`;
2485
+ style.top = `${box.top - base.top}px`;
2486
+ style.width = `${box.width}px`;
2487
+ style.height = `${box.height}px`;
2488
+ element.dataset.placed = "true";
2489
+ }
2490
+ } finally {
2491
+ placing = false;
2492
+ }
2493
+ afterPlace();
2494
+ };
2495
+ return {
2496
+ register(key, entry) {
2497
+ hosts.set(key, entry);
2498
+ return () => {
2499
+ if (hosts.get(key) === entry) hosts.delete(key);
2500
+ };
2501
+ },
2502
+ place
2503
+ };
2504
+ }
2505
+ function CanvasBodyLayer() {
2506
+ const { windowId, layer } = useCanvasInternals();
2507
+ const slots = useCanvasSlots();
2508
+ const state = useCanvasState((s) => s);
2509
+ const root = (0, import_react7.useRef)(null);
2510
+ const [placement] = (0, import_react7.useState)(
2511
+ () => slots ? createPlacement(() => root.current, slots, () => layer.native.remeasure()) : null
2512
+ );
2513
+ useIsoLayoutEffect(() => placement?.place());
2514
+ useIsoLayoutEffect(() => slots && placement ? slots.subscribe(placement.place) : void 0, [slots, placement]);
2515
+ if (!slots || !placement) return null;
2516
+ const frame2 = canvasWindowFrame(state, windowId);
2517
+ if (!frame2) return null;
2518
+ const paneCount = listPaneIds(frame2.layout).length;
2519
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { ref: root, className: "mxc-bodies", "data-mxc-bodies": windowId, children: placedItems(state, windowId).map(({ item, paneId, active }) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2520
+ BodyHost,
2521
+ {
2522
+ hostId: hostKey(item),
2523
+ item,
2524
+ paneId,
2525
+ visible: frame2.isOpen && active,
2526
+ focused: active && frame2.focusedPaneId === paneId,
2527
+ isFullscreen: frame2.isFullscreen,
2528
+ paneCount,
2529
+ slots,
2530
+ placement
2531
+ },
2532
+ hostKey(item)
2533
+ )) });
2534
+ }
2535
+ function CanvasPaneInlineBodies({ paneId, slots }) {
2536
+ const { windowId } = useCanvasInternals();
2537
+ const state = useCanvasState((s) => s);
2538
+ const frame2 = canvasWindowFrame(state, windowId);
2539
+ const paneCount = frame2 ? listPaneIds(frame2.layout).length : 1;
2540
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_jsx_runtime6.Fragment, { children: placedItems(state, windowId, paneId).map(({ item, active }) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2541
+ BodyHost,
2542
+ {
2543
+ hostId: hostKey(item),
2544
+ item,
2545
+ paneId,
2546
+ visible: active,
2547
+ focused: active && frame2?.focusedPaneId === paneId,
2548
+ isFullscreen: frame2?.isFullscreen ?? false,
2549
+ paneCount,
2550
+ slots,
2551
+ placement: null
2552
+ },
2553
+ hostKey(item)
2554
+ )) });
2555
+ }
2556
+ function BodyHost({
2557
+ hostId,
2558
+ item,
2559
+ paneId,
2560
+ visible,
2561
+ focused,
2562
+ isFullscreen,
2563
+ paneCount,
2564
+ slots,
2565
+ placement
2566
+ }) {
2567
+ const canvas = useCanvas();
2568
+ const { windowId, ports } = useCanvasInternals();
2569
+ const kind = useItemKind(item.kind);
2570
+ const element = (0, import_react7.useRef)(null);
2571
+ const [everShown, setEverShown] = (0, import_react7.useState)(visible);
2572
+ if (visible && !everShown) setEverShown(true);
2573
+ const mounted = kind?.unmountWhenHidden ? visible : everShown;
2574
+ const presentation = usePanePresentation(slots, paneId, isFullscreen, paneCount);
2575
+ const drop = usePaneDrop(paneId, () => slots.section(paneId));
2576
+ useIsoLayoutEffect(() => {
2577
+ const el = element.current;
2578
+ if (!placement || !el) return void 0;
2579
+ return placement.register(hostId, { element: el, paneId });
2580
+ }, [placement, hostId, paneId]);
2581
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2582
+ "div",
2583
+ {
2584
+ ref: element,
2585
+ className: "mxc-item-host",
2586
+ "data-inline": placement ? void 0 : "",
2587
+ "data-placed": placement ? "false" : void 0,
2588
+ "data-visible": visible ? "true" : "false",
2589
+ "data-kind": item.kind,
2590
+ "data-item": item.id,
2591
+ "data-pane": paneId,
2592
+ inert: !visible,
2593
+ "aria-hidden": visible ? void 0 : true,
2594
+ onPointerDownCapture: () => {
2595
+ if (!focused) canvas.focusPane(paneId);
2596
+ },
2597
+ ...drop,
2598
+ children: mounted ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(CanvasPresentationContext.Provider, { value: presentation, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "mxc-item", "data-kind": item.kind, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2599
+ ItemContent,
2600
+ {
2601
+ item,
2602
+ kind,
2603
+ paneId,
2604
+ windowId,
2605
+ focused,
2606
+ visible,
2607
+ presentation,
2608
+ canvas,
2609
+ hasNativePort: !!ports.onNativeBodyFrame
2610
+ }
2611
+ ) }) }) : null
2612
+ }
2613
+ );
2614
+ }
2615
+ function ItemContent({
2616
+ item,
2617
+ kind,
2618
+ paneId,
2619
+ windowId,
2620
+ focused,
2621
+ visible,
2622
+ presentation,
2623
+ canvas,
2624
+ hasNativePort
2625
+ }) {
2626
+ let unavailable = null;
2627
+ try {
2628
+ unavailable = kind?.unavailable?.(item.data, item) ?? null;
2629
+ } catch (error) {
2630
+ console.error(`[@ai-matrx/canvas] "${item.kind}" unavailable() threw`, error);
2631
+ }
2632
+ if (!kind) return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ItemProblem, { message: "This item can no longer be shown.", onClose: () => canvas.close(item.id) });
2633
+ if (unavailable) return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(CanvasPanelEmpty, { children: unavailable });
2634
+ const rendered = kindComponent(kind) ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Rendered, { kind, item, paneId, isFocused: focused, isVisible: visible, canvas, windowId, presentation }) : null;
2635
+ if (kind.body === "native") {
2636
+ return hasNativePort ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(NativeBody, { itemId: item.id, windowId, visible, children: rendered }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(CanvasPanelEmpty, { children: "Opens in the desktop app" });
2637
+ }
2638
+ return rendered;
2639
+ }
2640
+ var Rendered = (0, import_react7.memo)(function Rendered2({
2641
+ kind,
2642
+ item,
2643
+ paneId,
2644
+ isFocused,
2645
+ isVisible,
2646
+ canvas,
2647
+ windowId,
2648
+ presentation
2649
+ }) {
2650
+ const props = { item, data: item.data, paneId, isFocused, isVisible, canvas, windowId, presentation };
2651
+ 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 }) }) });
2652
+ });
2653
+ function KindRender({ kind, props }) {
2654
+ const Render = kindComponent(kind);
2655
+ if (!Render) throw new Error(`[@ai-matrx/canvas] kind "${kind.id}" has nothing to render.`);
2656
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Render, { ...props });
2657
+ }
2658
+ function NativeBody({ itemId, windowId, visible, children }) {
2659
+ const { layer } = useCanvasInternals();
2660
+ const ref = (0, import_react7.useRef)(null);
2661
+ const measure = (0, import_react7.useRef)(() => void 0);
2662
+ measure.current = () => {
2663
+ const el = ref.current;
2664
+ const box = visible && el?.isConnected ? el.getBoundingClientRect() : null;
2665
+ if (!box || box.width <= 0 || box.height <= 0) {
2666
+ layer.native.report(itemId, null, { visible: false, windowId });
2667
+ return;
2668
+ }
2669
+ const rect = { ...toCanvasRect(box), devicePixelRatio: window.devicePixelRatio || 1 };
2670
+ layer.native.report(itemId, rect, { visible: !layer.isBlocked(), windowId });
2671
+ };
2672
+ useIsoLayoutEffect(() => measure.current());
2673
+ useIsoLayoutEffect(() => {
2674
+ const el = ref.current;
2675
+ const run = () => measure.current();
2676
+ const observer = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(run);
2677
+ if (el) observer?.observe(el);
2678
+ const unregister = layer.native.register(run);
2679
+ window.addEventListener("resize", run);
2680
+ return () => {
2681
+ observer?.disconnect();
2682
+ unregister();
2683
+ window.removeEventListener("resize", run);
2684
+ layer.native.report(itemId, null, { visible: false, windowId });
2685
+ };
2686
+ }, [itemId, windowId, layer]);
2687
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { ref, className: "mxc-native-body", "data-native-body": itemId, children });
2688
+ }
2689
+ function ItemProblem({ message, onClose, onRetry }) {
2690
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "mxc-problem", role: "alert", children: [
2691
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(AlertIcon, {}),
2692
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { children: message }),
2693
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "mxc-problem-actions", children: [
2694
+ onRetry ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", className: "mxc-text-button", onClick: onRetry, children: "Try again" }) : null,
2695
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", className: "mxc-text-button", onClick: onClose, children: "Close tab" })
2696
+ ] })
2697
+ ] });
2698
+ }
2699
+ var ItemBoundary = class extends import_react7.Component {
2700
+ state = { error: null };
2701
+ static getDerivedStateFromError(error) {
2702
+ return { error };
2703
+ }
2704
+ componentDidCatch(error, info) {
2705
+ console.error(`[@ai-matrx/canvas] "${this.props.item.kind}" failed to render`, error, info.componentStack);
2706
+ }
2707
+ render() {
2708
+ if (this.state.error) {
2709
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2710
+ ItemProblem,
2711
+ {
2712
+ message: "This item failed to load.",
2713
+ onClose: this.props.onClose,
2714
+ onRetry: () => {
2715
+ this.props.onRetry();
2716
+ this.setState({ error: null });
2717
+ }
2718
+ }
2719
+ );
2720
+ }
2721
+ return this.props.children;
2722
+ }
2723
+ };
2724
+
2725
+ // src/react/CanvasPane.tsx
2726
+ var import_react8 = require("react");
2727
+ var Menu = __toESM(require("@radix-ui/react-dropdown-menu"), 1);
2728
+ var import_jsx_runtime7 = require("react/jsx-runtime");
2729
+ var useIsoLayoutEffect2 = typeof window === "undefined" ? import_react8.useEffect : import_react8.useLayoutEffect;
2004
2730
  function itemTitle(item, kind) {
2005
2731
  if (item.title) return item.title;
2732
+ if (item.kind === CANVAS_LAUNCHER_KIND) return "New tab";
2006
2733
  if (kind?.title) {
2007
2734
  try {
2008
2735
  const title = kind.title(item.data, item);
@@ -2013,69 +2740,44 @@ function itemTitle(item, kind) {
2013
2740
  return kind?.label ?? item.kind;
2014
2741
  }
2015
2742
  var isMac = () => typeof navigator !== "undefined" && /mac|iphone|ipad/i.test(navigator.platform ?? "");
2016
- function useTabDrag(itemId, title) {
2017
- const { layer } = useCanvasInternals();
2018
- const active = (0, import_react4.useRef)([]);
2019
- const end = () => {
2020
- for (const dispose of active.current.splice(0)) dispose();
2021
- };
2022
- (0, import_react4.useEffect)(() => end, []);
2023
- return {
2024
- draggable: true,
2025
- onDragStart: (event) => {
2026
- event.dataTransfer.setData(DRAG_MIME, itemId);
2027
- event.dataTransfer.effectAllowed = "move";
2028
- end();
2029
- active.current.push(layer.beginBlocking(), layer.show({ type: "tab-drag", itemId, title }));
2030
- },
2031
- onDragEnd: end
2032
- };
2033
- }
2034
2743
  function CanvasPaneView({ paneId }) {
2035
2744
  const canvas = useCanvas();
2036
- const { windowId, layer, ports, tabStrip } = useCanvasInternals();
2745
+ const { windowId, tabStrip } = useCanvasInternals();
2746
+ const columnSlots = useCanvasSlots();
2747
+ const [ownSlots] = (0, import_react8.useState)(createCanvasSlots);
2748
+ const slots = columnSlots ?? ownSlots;
2037
2749
  const pane = useCanvasState((s) => s.panes[paneId]);
2038
2750
  const items = useCanvasState((s) => s.items);
2039
2751
  const isFullscreen = useCanvasState((s) => selectCanvasIsFullscreen(s, windowId));
2752
+ const isOpen = useCanvasState((s) => selectCanvasIsOpen(s, windowId));
2040
2753
  const focused = useCanvasState((s) => selectCanvasFocusedPaneId(s, windowId) === paneId);
2041
2754
  const paneCount = useCanvasState((s) => selectCanvasPaneCount(s, windowId));
2042
2755
  const role = useCanvasState((s) => windowId === CANVAS_MAIN_WINDOW ? "main" : s.windows[windowId]?.role ?? "window");
2043
2756
  const active = pane?.activeItemId ? items[pane.activeItemId] : void 0;
2044
- const kind = useCanvasKind(active?.kind ?? "");
2045
- const drop = (0, import_react4.useRef)(null);
2046
- const sectionRef = (0, import_react4.useRef)(null);
2047
- (0, import_react4.useEffect)(() => () => drop.current?.(), []);
2757
+ const canGoBack = useCanvasState((s) => active ? selectCanvasCanGoBack(s, active.id) : false);
2758
+ const kind = useItemKind(active?.kind ?? "");
2759
+ const sectionRef = (0, import_react8.useRef)(null);
2760
+ const bodyRef = (0, import_react8.useRef)(null);
2761
+ const presentation = usePanePresentation(slots, paneId, isFullscreen, paneCount);
2762
+ const drop = usePaneDrop(paneId, () => sectionRef.current);
2763
+ useIsoLayoutEffect2(() => {
2764
+ const body = bodyRef.current;
2765
+ const section = sectionRef.current;
2766
+ if (body) slots.set(paneId, body);
2767
+ slots.setSection(paneId, section);
2768
+ return () => {
2769
+ slots.unset(paneId, body);
2770
+ if (slots.section(paneId) === section) slots.setSection(paneId, null);
2771
+ };
2772
+ }, [slots, paneId]);
2048
2773
  if (!pane) return null;
2049
- const kindProps = active && kind ? { item: active, data: active.data, paneId, isFocused: focused, canvas, windowId } : null;
2774
+ const kindProps = active && kind ? { item: active, data: active.data, paneId, isFocused: focused, isVisible: isOpen, canvas, windowId, presentation } : null;
2050
2775
  const Leading = kind?.HeaderLeading;
2051
2776
  const Action = kind?.HeaderAction;
2777
+ const Toolbar = kind?.Toolbar;
2052
2778
  const single = tabStrip === "auto" && pane.itemIds.length === 1 && active;
2053
- const hideDrop = () => {
2054
- drop.current?.();
2055
- drop.current = null;
2056
- };
2057
- const onDragOver = (event) => {
2058
- if (!event.dataTransfer.types.includes(DRAG_MIME)) return;
2059
- event.preventDefault();
2060
- event.dataTransfer.dropEffect = "move";
2061
- if (!drop.current && sectionRef.current) {
2062
- drop.current = layer.show({ type: "drop-indicator", rect: toCanvasRect(sectionRef.current.getBoundingClientRect()), paneId });
2063
- }
2064
- };
2065
- const onDragLeave = (event) => {
2066
- const next = event.relatedTarget;
2067
- if (next && sectionRef.current?.contains(next)) return;
2068
- hideDrop();
2069
- };
2070
- const onDrop = (event) => {
2071
- hideDrop();
2072
- const itemId = event.dataTransfer.getData(DRAG_MIME);
2073
- if (!itemId) return;
2074
- event.preventDefault();
2075
- canvas.moveItem(itemId, paneId);
2076
- };
2077
2779
  const closeLabel = role === "pop-out" && paneCount === 1 ? "Close window" : paneCount > 1 ? "Close pane" : "Close canvas";
2078
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2780
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
2079
2781
  CanvasPanel,
2080
2782
  {
2081
2783
  ref: sectionRef,
@@ -2084,10 +2786,14 @@ function CanvasPaneView({ paneId }) {
2084
2786
  title: active ? itemTitle(active, kind) : "Canvas",
2085
2787
  focused,
2086
2788
  "data-mxc-pane": paneId,
2087
- header: single && active ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(SingleTitle, { item: active, kind }) : pane.itemIds.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "mxc-tabs", role: "tablist", "aria-label": "Canvas tabs", children: pane.itemIds.map((itemId, index) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ItemTab, { itemId, paneId, index, active: itemId === pane.activeItemId }, itemId)) }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "mxc-panel-title", children: "New tab" }),
2088
- leading: Leading && kindProps ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Leading, { ...kindProps }) : null,
2089
- actions: Action && kindProps ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Action, { ...kindProps }) : null,
2090
- renderMenu: () => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(PaneMenu, { paneId, windowId, activeItem: active, kind, kindProps, tabCount: pane.itemIds.length, role }),
2789
+ header: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
2790
+ 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,
2791
+ single && active ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(SingleTitle, { item: active, kind }) : pane.itemIds.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(TabStrip, { paneId, itemIds: pane.itemIds, activeItemId: pane.activeItemId }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "mxc-panel-title", children: "New tab" }),
2792
+ /* @__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, {}) })
2793
+ ] }),
2794
+ leading: Leading && kindProps ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Leading, { ...kindProps }) : null,
2795
+ actions: Action && kindProps ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Action, { ...kindProps }) : null,
2796
+ renderMenu: () => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(PaneMenu, { paneId, windowId, activeItem: active, kind, kindProps, tabCount: pane.itemIds.length, role }),
2091
2797
  expanded: isFullscreen,
2092
2798
  onToggleExpand: () => canvas.setFullscreen(!isFullscreen),
2093
2799
  expandShortcut: canvasShortcutHint("canvas.toggleFullscreen", isMac()),
@@ -2097,54 +2803,92 @@ function CanvasPaneView({ paneId }) {
2097
2803
  onPointerDownCapture: () => {
2098
2804
  if (!focused) canvas.focusPane(paneId);
2099
2805
  },
2100
- onDragOver,
2101
- onDragLeave,
2102
- onDrop,
2103
- children: pane.itemIds.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Launcher, { paneId }) : pane.itemIds.map((itemId) => {
2104
- const item = items[itemId];
2105
- if (!item) return null;
2106
- const isActive = itemId === pane.activeItemId;
2107
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ItemBody, { item, paneId, windowId, isActive, isFocused: focused && isActive, hasNativePort: !!ports.onNativeBodyFrame }, itemId);
2108
- })
2806
+ ...drop,
2807
+ children: [
2808
+ Toolbar && kindProps ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "mxc-item-toolbar", "data-kind": active?.kind, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ToolbarBoundary, { kindId: kind?.id ?? "", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Toolbar, { ...kindProps }) }) }) : null,
2809
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { ref: bodyRef, className: "mxc-pane-slot", "data-mxc-slot": paneId, children: pane.itemIds.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(EmptyPaneLauncher, { paneId }) : columnSlots ? null : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(CanvasPaneInlineBodies, { paneId, slots }) })
2810
+ ]
2109
2811
  }
2110
2812
  );
2111
2813
  }
2814
+ var ToolbarBoundary = class extends import_react8.Component {
2815
+ state = { failed: false };
2816
+ static getDerivedStateFromError() {
2817
+ return { failed: true };
2818
+ }
2819
+ componentDidCatch(error) {
2820
+ console.error(`[@ai-matrx/canvas] "${this.props.kindId}" Toolbar threw`, error);
2821
+ }
2822
+ render() {
2823
+ return this.state.failed ? null : this.props.children;
2824
+ }
2825
+ };
2112
2826
  function SingleTitle({ item, kind }) {
2827
+ const canvas = useCanvas();
2113
2828
  const title = itemTitle(item, kind);
2114
2829
  const drag = useTabDrag(item.id, title);
2115
2830
  const Icon = kind?.icon ?? AlertIcon;
2116
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { className: "mxc-panel-title", "data-draggable": "", title, ...drag, children: [
2117
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Icon, { className: "mxc-title-icon" }),
2118
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "mxc-tab-title", children: title })
2831
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: "mxc-panel-title mxc-single-title", "data-draggable": "", title, ...drag, children: [
2832
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Icon, { className: "mxc-title-icon" }),
2833
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "mxc-tab-title", children: title }),
2834
+ /* @__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, {}) })
2119
2835
  ] });
2120
2836
  }
2837
+ function TabStrip({ paneId, itemIds, activeItemId }) {
2838
+ const strip = (0, import_react8.useRef)(null);
2839
+ useIsoLayoutEffect2(() => {
2840
+ const el = strip.current;
2841
+ if (!el) return void 0;
2842
+ const reveal = () => {
2843
+ const tab = el.querySelector('[role="tab"][aria-selected="true"]');
2844
+ if (!tab) return;
2845
+ const left = tab.offsetLeft - el.offsetLeft;
2846
+ const right = left + tab.offsetWidth;
2847
+ if (left < el.scrollLeft) el.scrollLeft = left;
2848
+ else if (right > el.scrollLeft + el.clientWidth) el.scrollLeft = right - el.clientWidth;
2849
+ };
2850
+ reveal();
2851
+ const observer = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(reveal);
2852
+ observer?.observe(el);
2853
+ return () => observer?.disconnect();
2854
+ }, [activeItemId, itemIds.length]);
2855
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2856
+ "div",
2857
+ {
2858
+ ref: strip,
2859
+ className: "mxc-tabs",
2860
+ role: "tablist",
2861
+ "aria-label": "Canvas tabs",
2862
+ onWheel: (event) => {
2863
+ const el = strip.current;
2864
+ if (el && Math.abs(event.deltaY) > Math.abs(event.deltaX)) el.scrollLeft += event.deltaY;
2865
+ },
2866
+ children: itemIds.map((itemId, index) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ItemTab, { itemId, paneId, index, active: itemId === activeItemId }, itemId))
2867
+ }
2868
+ );
2869
+ }
2121
2870
  function ItemTab({ itemId, paneId, index, active }) {
2122
2871
  const canvas = useCanvas();
2123
2872
  const item = useCanvasState((s) => s.items[itemId]);
2124
- const kind = useCanvasKind(item?.kind ?? "");
2125
- const ref = (0, import_react4.useRef)(null);
2873
+ const kind = useItemKind(item?.kind ?? "");
2126
2874
  const title = item ? itemTitle(item, kind) : "";
2127
2875
  const drag = useTabDrag(itemId, title);
2128
- (0, import_react4.useEffect)(() => {
2129
- if (active) ref.current?.scrollIntoView?.({ block: "nearest", inline: "nearest" });
2130
- }, [active]);
2131
2876
  if (!item) return null;
2132
2877
  const Icon = kind?.icon ?? AlertIcon;
2133
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2878
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2134
2879
  CanvasTab,
2135
2880
  {
2136
- ref,
2137
2881
  title,
2138
- icon: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Icon, {}),
2882
+ icon: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Icon, {}),
2139
2883
  active,
2140
2884
  onSelect: () => canvas.activate(itemId),
2141
2885
  onClose: () => canvas.close(itemId),
2142
2886
  ...drag,
2143
2887
  onDragOver: (event) => {
2144
- if (event.dataTransfer.types.includes(DRAG_MIME)) event.preventDefault();
2888
+ if (event.dataTransfer.types.includes(CANVAS_DRAG_MIME)) event.preventDefault();
2145
2889
  },
2146
2890
  onDrop: (event) => {
2147
- const dragged = event.dataTransfer.getData(DRAG_MIME);
2891
+ const dragged = event.dataTransfer.getData(CANVAS_DRAG_MIME);
2148
2892
  if (!dragged) return;
2149
2893
  event.preventDefault();
2150
2894
  if (dragged !== itemId) canvas.moveItem(dragged, paneId, index);
@@ -2167,16 +2911,16 @@ function PaneMenu({
2167
2911
  const canvas = useCanvas();
2168
2912
  const { ports, layer } = useCanvasInternals();
2169
2913
  const moveId = tabCount > 1 ? activeItem?.id : void 0;
2170
- const blocking = (0, import_react4.useRef)(null);
2171
- (0, import_react4.useEffect)(() => () => blocking.current?.(), []);
2914
+ const blocking = (0, import_react8.useRef)(null);
2915
+ (0, import_react8.useEffect)(() => () => blocking.current?.(), []);
2172
2916
  const kindItems = kind?.menuItems && kindProps ? safeMenu(() => kind.menuItems?.(kindProps)) : [];
2173
2917
  const hostItems = activeItem && ports.itemActions ? safeMenu(() => ports.itemActions?.(activeItem, kind)) : [];
2174
2918
  const sections = [
2175
2919
  [...kindItems, ...hostItems].map((entry) => ({ ...entry, id: `item:${entry.id}` })),
2176
2920
  [
2177
- { id: "split-right", label: "Split right", icon: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(SplitRightIcon, {}), onSelect: () => canvas.splitPane(paneId, "horizontal", moveId) },
2178
- { id: "split-down", label: "Split down", icon: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(SplitDownIcon, {}), onSelect: () => canvas.splitPane(paneId, "vertical", moveId) },
2179
- ...activeItem && role === "pop-out" ? [{ id: "dock-back", label: "Dock back", icon: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(DockIcon, {}), onSelect: () => void canvas.dockBack(activeItem.id) }] : activeItem && ports.popOut ? [{ id: "pop-out", label: "Pop out", icon: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(PopOutIcon, {}), onSelect: () => void canvas.popOut(activeItem.id) }] : []
2921
+ { id: "split-right", label: "Split right", icon: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(SplitRightIcon, {}), onSelect: () => canvas.splitPane(paneId, "horizontal", moveId) },
2922
+ { id: "split-down", label: "Split down", icon: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(SplitDownIcon, {}), onSelect: () => canvas.splitPane(paneId, "vertical", moveId) },
2923
+ ...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) }] : []
2180
2924
  ],
2181
2925
  activeItem ? [
2182
2926
  { id: "close-tab", label: "Close tab", onSelect: () => canvas.close(activeItem.id) },
@@ -2194,7 +2938,7 @@ function PaneMenu({
2194
2938
  handlers.set(action.id, action.onSelect);
2195
2939
  }
2196
2940
  });
2197
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2941
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2198
2942
  CanvasMenuTrigger,
2199
2943
  {
2200
2944
  onClick: (event) => {
@@ -2209,7 +2953,7 @@ function PaneMenu({
2209
2953
  }
2210
2954
  );
2211
2955
  }
2212
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
2956
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
2213
2957
  Menu.Root,
2214
2958
  {
2215
2959
  onOpenChange: (open) => {
@@ -2217,16 +2961,16 @@ function PaneMenu({
2217
2961
  blocking.current = open ? layer.beginBlocking() : null;
2218
2962
  },
2219
2963
  children: [
2220
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Menu.Trigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(CanvasMenuTrigger, {}) }),
2221
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Menu.Portal, { children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Menu.Content, { align: "end", sideOffset: 4, className: "mxc-menu", children: sections.map((section, i) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(MenuSection, { first: i === 0, actions: section }, i)) }) })
2964
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Menu.Trigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(CanvasMenuTrigger, {}) }),
2965
+ /* @__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)) }) })
2222
2966
  ]
2223
2967
  }
2224
2968
  );
2225
2969
  }
2226
2970
  function MenuSection({ first, actions }) {
2227
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
2228
- first ? null : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Menu.Separator, { className: "mxc-menu-separator" }),
2229
- actions.map((action) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Menu.Item, { onSelect: action.onSelect, className: "mxc-menu-item", "data-destructive": action.destructive ? "" : void 0, children: [
2971
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
2972
+ first ? null : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Menu.Separator, { className: "mxc-menu-separator" }),
2973
+ actions.map((action) => /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(Menu.Item, { onSelect: action.onSelect, className: "mxc-menu-item", "data-destructive": action.destructive ? "" : void 0, children: [
2230
2974
  action.icon,
2231
2975
  action.label
2232
2976
  ] }, action.id))
@@ -2240,150 +2984,16 @@ function safeMenu(build) {
2240
2984
  return [];
2241
2985
  }
2242
2986
  }
2243
- function ItemBody({
2244
- item,
2245
- paneId,
2246
- windowId,
2247
- isActive,
2248
- isFocused,
2249
- hasNativePort
2250
- }) {
2251
- const canvas = useCanvas();
2252
- const kind = useCanvasKind(item.kind);
2253
- if (!isActive && !kind?.keepAlive) return null;
2254
- const props = { item, data: item.data, paneId, isFocused, canvas, windowId };
2255
- let unavailable = null;
2256
- try {
2257
- unavailable = kind?.unavailable?.(item.data, item) ?? null;
2258
- } catch (error) {
2259
- console.error(`[@ai-matrx/canvas] "${item.kind}" unavailable() threw`, error);
2260
- }
2261
- let body;
2262
- if (!kind) {
2263
- body = /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ItemProblem, { message: "This item can no longer be shown.", onClose: () => canvas.close(item.id) });
2264
- } else if (unavailable) {
2265
- body = /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(CanvasPanelEmpty, { children: unavailable });
2266
- } else if (kind.body === "native") {
2267
- body = hasNativePort ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(NativeBody, { itemId: item.id, windowId, children: kindComponent(kind) ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Rendered, { kind, item, props }) : null }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(CanvasPanelEmpty, { children: "Opens in the desktop app" });
2268
- } else {
2269
- body = /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Rendered, { kind, item, props });
2270
- }
2271
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "mxc-item", hidden: !isActive, "data-kind": item.kind, children: body });
2272
- }
2273
- function Rendered({ kind, item, props }) {
2274
- const canvas = useCanvas();
2275
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ItemBoundary, { item, onClose: () => canvas.close(item.id), onRetry: () => resetKindComponent(kind), children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react4.Suspense, { fallback: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "mxc-loading", "aria-busy": "true" }), children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(KindRender, { kind, props }) }) });
2276
- }
2277
- function KindRender({ kind, props }) {
2278
- const Render = kindComponent(kind);
2279
- if (!Render) throw new Error(`[@ai-matrx/canvas] kind "${kind.id}" has nothing to render.`);
2280
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Render, { ...props });
2281
- }
2282
- function NativeBody({ itemId, windowId, children }) {
2283
- const { layer } = useCanvasInternals();
2284
- const ref = (0, import_react4.useRef)(null);
2285
- const measure = (0, import_react4.useRef)(() => void 0);
2286
- measure.current = () => {
2287
- const el = ref.current;
2288
- const box = el?.isConnected ? el.getBoundingClientRect() : null;
2289
- if (!box || box.width <= 0 || box.height <= 0) {
2290
- layer.native.report(itemId, null, { visible: false, windowId });
2291
- return;
2292
- }
2293
- const rect = { ...toCanvasRect(box), devicePixelRatio: window.devicePixelRatio || 1 };
2294
- layer.native.report(itemId, rect, { visible: !layer.isBlocked(), windowId });
2295
- };
2296
- useIsoLayoutEffect(() => measure.current());
2297
- useIsoLayoutEffect(() => {
2298
- const el = ref.current;
2299
- const run = () => measure.current();
2300
- const observer = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(run);
2301
- if (el) observer?.observe(el);
2302
- const unregister = layer.native.register(run);
2303
- window.addEventListener("resize", run);
2304
- return () => {
2305
- observer?.disconnect();
2306
- unregister();
2307
- window.removeEventListener("resize", run);
2308
- layer.native.report(itemId, null, { visible: false, windowId });
2309
- };
2310
- }, [itemId, windowId, layer]);
2311
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { ref, className: "mxc-native-body", "data-native-body": itemId, children });
2312
- }
2313
- function ItemProblem({ message, onClose, onRetry }) {
2314
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "mxc-problem", role: "alert", children: [
2315
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(AlertIcon, {}),
2316
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { children: message }),
2317
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "mxc-problem-actions", children: [
2318
- onRetry ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", className: "mxc-text-button", onClick: onRetry, children: "Try again" }) : null,
2319
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", className: "mxc-text-button", onClick: onClose, children: "Close tab" })
2320
- ] })
2321
- ] });
2322
- }
2323
- var ItemBoundary = class extends import_react4.Component {
2324
- state = { error: null };
2325
- static getDerivedStateFromError(error) {
2326
- return { error };
2327
- }
2328
- componentDidCatch(error, info) {
2329
- console.error(`[@ai-matrx/canvas] "${this.props.item.kind}" failed to render`, error, info.componentStack);
2330
- }
2331
- render() {
2332
- if (this.state.error) {
2333
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2334
- ItemProblem,
2335
- {
2336
- message: "This item failed to load.",
2337
- onClose: this.props.onClose,
2338
- onRetry: () => {
2339
- this.props.onRetry();
2340
- this.setState({ error: null });
2341
- }
2342
- }
2343
- );
2344
- }
2345
- return this.props.children;
2346
- }
2347
- };
2348
- function Launcher({ paneId }) {
2349
- const canvas = useCanvas();
2350
- const kinds = useCanvasKinds().filter((kind) => kind.launcher);
2351
- if (kinds.length === 0) return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(CanvasPanelEmpty, { children: "Nothing open" });
2352
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "mxc-launcher", children: kinds.map((kind) => {
2353
- const Icon = kind.icon;
2354
- const launcher = kind.launcher;
2355
- if (!launcher) return null;
2356
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
2357
- "button",
2358
- {
2359
- type: "button",
2360
- className: "mxc-launcher-item",
2361
- onClick: () => canvas.open({
2362
- kind: kind.id,
2363
- key: launcher.key,
2364
- data: launcher.data,
2365
- title: launcher.title ?? null,
2366
- target: { paneId }
2367
- }),
2368
- children: [
2369
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Icon, { className: "mxc-launcher-icon" }),
2370
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children: launcher.title ?? kind.label })
2371
- ]
2372
- },
2373
- kind.id
2374
- );
2375
- }) });
2376
- }
2377
2987
 
2378
2988
  // src/react/CanvasColumn.tsx
2379
- var import_jsx_runtime6 = require("react/jsx-runtime");
2380
- var useIsoLayoutEffect2 = typeof window === "undefined" ? import_react5.useEffect : import_react5.useLayoutEffect;
2989
+ var import_jsx_runtime8 = require("react/jsx-runtime");
2990
+ var useIsoLayoutEffect3 = typeof window === "undefined" ? import_react9.useEffect : import_react9.useLayoutEffect;
2381
2991
  function subscribeViewport(listener) {
2382
2992
  window.addEventListener("resize", listener);
2383
2993
  return () => window.removeEventListener("resize", listener);
2384
2994
  }
2385
2995
  function useViewportWidth() {
2386
- return (0, import_react5.useSyncExternalStore)(subscribeViewport, () => window.innerWidth, () => 0);
2996
+ return (0, import_react9.useSyncExternalStore)(subscribeViewport, () => window.innerWidth, () => 0);
2387
2997
  }
2388
2998
  function useRegionWidth() {
2389
2999
  const viewport = useViewportWidth();
@@ -2401,28 +3011,66 @@ function useCanvasColumnWidth() {
2401
3011
  return fitCanvasWidth(width, region, controller.rules);
2402
3012
  }
2403
3013
  function CanvasColumn({ className, style, onLiveWidth, placement = "docked" }) {
2404
- const { controller, windowId } = useCanvasInternals();
3014
+ const { controller, windowId, layer } = useCanvasInternals();
2405
3015
  const isOpen = useCanvasState((s) => selectCanvasIsOpen(s, windowId));
2406
3016
  const isFullscreen = useCanvasState((s) => selectCanvasIsFullscreen(s, windowId));
2407
3017
  const width = useCanvasState((s) => selectCanvasWidth(s, windowId));
2408
3018
  const layout = useCanvasState((s) => selectCanvasLayout(s, windowId));
2409
3019
  const region = useRegionWidth();
2410
- const [dragWidth, setDragWidth] = (0, import_react5.useState)(null);
3020
+ const [dragWidth, setDragWidth] = (0, import_react9.useState)(null);
3021
+ const [slots] = (0, import_react9.useState)(createCanvasSlots);
3022
+ const ref = (0, import_react9.useRef)(null);
2411
3023
  useRegisterCanvasPresence();
2412
- if (!isOpen || !layout) return null;
3024
+ (0, import_react9.useEffect)(() => {
3025
+ const el = ref.current;
3026
+ if (!el) return void 0;
3027
+ let frame2 = 0;
3028
+ let running = false;
3029
+ const tick = () => {
3030
+ layer.native.remeasure();
3031
+ frame2 = running ? requestAnimationFrame(tick) : 0;
3032
+ };
3033
+ const start = (event) => {
3034
+ if (event.target !== el || running) return;
3035
+ running = true;
3036
+ frame2 = requestAnimationFrame(tick);
3037
+ };
3038
+ const stop = (event) => {
3039
+ if (event.target !== el) return;
3040
+ running = false;
3041
+ if (frame2) cancelAnimationFrame(frame2);
3042
+ frame2 = 0;
3043
+ layer.native.remeasure();
3044
+ };
3045
+ el.addEventListener("transitionrun", start);
3046
+ el.addEventListener("transitionend", stop);
3047
+ el.addEventListener("transitioncancel", stop);
3048
+ return () => {
3049
+ running = false;
3050
+ if (frame2) cancelAnimationFrame(frame2);
3051
+ el.removeEventListener("transitionrun", start);
3052
+ el.removeEventListener("transitionend", stop);
3053
+ el.removeEventListener("transitioncancel", stop);
3054
+ };
3055
+ }, [layer]);
3056
+ if (!layout) return null;
2413
3057
  const fitted = fitCanvasWidth(width, region, controller.rules);
2414
3058
  const shown = dragWidth ?? fitted;
2415
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
3059
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2416
3060
  "aside",
2417
3061
  {
3062
+ ref,
2418
3063
  className: ["mxc-column", className].filter(Boolean).join(" "),
2419
- "data-fullscreen": isFullscreen ? "" : void 0,
3064
+ "data-open": isOpen ? "" : void 0,
3065
+ "data-fullscreen": isOpen && isFullscreen ? "" : void 0,
2420
3066
  "data-placement": placement,
2421
3067
  "data-mxc-window": windowId,
2422
3068
  "aria-label": "Canvas",
3069
+ "aria-hidden": isOpen ? void 0 : true,
3070
+ inert: !isOpen,
2423
3071
  style: { ...style, ["--mxc-width"]: `${shown}px` },
2424
- children: [
2425
- placement === "docked" && !isFullscreen ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
3072
+ children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "mxc-column-inner", children: [
3073
+ placement === "docked" && isOpen && !isFullscreen ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2426
3074
  WidthHandle,
2427
3075
  {
2428
3076
  width: fitted,
@@ -2433,14 +3081,17 @@ function CanvasColumn({ className, style, onLiveWidth, placement = "docked" }) {
2433
3081
  }
2434
3082
  }
2435
3083
  ) : null,
2436
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "mxc-layout", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(LayoutNodeView, { node: layout }) })
2437
- ]
3084
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(CanvasSlotsContext.Provider, { value: slots, children: [
3085
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "mxc-layout", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(LayoutNodeView, { node: layout }) }),
3086
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(CanvasBodyLayer, {})
3087
+ ] })
3088
+ ] })
2438
3089
  }
2439
3090
  );
2440
3091
  }
2441
3092
  function WidthHandle({ width, region, onDrag }) {
2442
3093
  const canvas = useCanvas();
2443
- const start = (0, import_react5.useRef)(null);
3094
+ const start = (0, import_react9.useRef)(null);
2444
3095
  const clamp = (next) => clampDraggedCanvasWidth(next, region, canvas.rules);
2445
3096
  const onPointerDown = (event) => {
2446
3097
  event.preventDefault();
@@ -2460,7 +3111,7 @@ function WidthHandle({ width, region, onDrag }) {
2460
3111
  onDrag(null);
2461
3112
  canvas.setWidth(next);
2462
3113
  };
2463
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
3114
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2464
3115
  "div",
2465
3116
  {
2466
3117
  className: "mxc-width-handle",
@@ -2482,12 +3133,12 @@ function WidthHandle({ width, region, onDrag }) {
2482
3133
  );
2483
3134
  }
2484
3135
  function LayoutNodeView({ node }) {
2485
- if (node.type === "pane") return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(CanvasPaneView, { paneId: node.paneId });
2486
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(SplitView, { node });
3136
+ if (node.type === "pane") return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(CanvasPaneView, { paneId: node.paneId });
3137
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(SplitView, { node });
2487
3138
  }
2488
3139
  function useMeasuredWidth(ref, enabled) {
2489
- const [width, setWidth] = (0, import_react5.useState)(0);
2490
- useIsoLayoutEffect2(() => {
3140
+ const [width, setWidth] = (0, import_react9.useState)(0);
3141
+ useIsoLayoutEffect3(() => {
2491
3142
  const el = ref.current;
2492
3143
  if (!enabled || !el) return;
2493
3144
  setWidth(el.getBoundingClientRect().width);
@@ -2500,8 +3151,8 @@ function useMeasuredWidth(ref, enabled) {
2500
3151
  }
2501
3152
  function SplitView({ node }) {
2502
3153
  const canvas = useCanvas();
2503
- const ref = (0, import_react5.useRef)(null);
2504
- const [liveSizes, setLiveSizes] = (0, import_react5.useState)(null);
3154
+ const ref = (0, import_react9.useRef)(null);
3155
+ const [liveSizes, setLiveSizes] = (0, import_react9.useState)(null);
2505
3156
  const sizes = liveSizes ?? node.sizes;
2506
3157
  const measuredWidth = useMeasuredWidth(ref, node.orientation === "horizontal" && canvas.rules.paneMinWidth > 0);
2507
3158
  const stacked = node.orientation === "horizontal" && shouldStackSplit(measuredWidth, node.children.length, canvas.rules);
@@ -2541,11 +3192,11 @@ function SplitView({ node }) {
2541
3192
  target.addEventListener("pointerup", up);
2542
3193
  target.addEventListener("pointercancel", up);
2543
3194
  };
2544
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { ref, className: "mxc-split", "data-orientation": orientation, "data-stacked": stacked ? "" : void 0, children: node.children.map((child, i) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
3195
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { ref, className: "mxc-split", "data-orientation": orientation, "data-stacked": stacked ? "" : void 0, children: node.children.map((child, i) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2545
3196
  SplitChild,
2546
3197
  {
2547
3198
  size: sizes[i] ?? 1 / node.children.length,
2548
- handle: i < node.children.length - 1 ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
3199
+ handle: i < node.children.length - 1 ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2549
3200
  "div",
2550
3201
  {
2551
3202
  className: "mxc-split-handle",
@@ -2555,14 +3206,14 @@ function SplitView({ node }) {
2555
3206
  onDoubleClick: () => canvas.resizeSplit(node.id, node.children.map(() => 1 / node.children.length))
2556
3207
  }
2557
3208
  ) : null,
2558
- children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(LayoutNodeView, { node: child })
3209
+ children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(LayoutNodeView, { node: child })
2559
3210
  },
2560
3211
  child.type === "pane" ? child.paneId : child.id
2561
3212
  )) });
2562
3213
  }
2563
3214
  function SplitChild({ size, handle, children }) {
2564
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
2565
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "mxc-split-child", style: { flexGrow: size, flexBasis: 0 }, children }),
3215
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
3216
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "mxc-split-child", style: { flexGrow: size, flexBasis: 0 }, children }),
2566
3217
  handle
2567
3218
  ] });
2568
3219
  }
@@ -2576,26 +3227,61 @@ function CanvasToggle({
2576
3227
  const count = useCanvasState((s) => selectCanvasWindowItemCount(s, windowId));
2577
3228
  const hint = canvasShortcutHint("canvas.toggle", isMac2());
2578
3229
  const Button = variant === "glass" ? import_tap_target.TapTargetButton : import_tap_target.TapTargetButtonTransparent;
2579
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: ["mxc-toggle", className].filter(Boolean).join(" "), "data-open": isOpen ? "" : void 0, children: [
2580
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
3230
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { className: ["mxc-toggle", className].filter(Boolean).join(" "), "data-open": isOpen ? "" : void 0, children: [
3231
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2581
3232
  Button,
2582
3233
  {
2583
3234
  ariaLabel: isOpen ? "Hide canvas" : "Show canvas",
2584
3235
  tooltip: `${isOpen ? "Hide canvas" : "Show canvas"}${hint ? ` (${hint})` : ""}`,
2585
- icon: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(PanelRightIcon, {}),
3236
+ icon: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(PanelRightIcon, {}),
2586
3237
  onClick: () => controller.toggle()
2587
3238
  }
2588
3239
  ),
2589
- count > 0 && !isOpen ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "mxc-toggle-badge", children: count }) : null
3240
+ count > 0 && !isOpen ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "mxc-toggle-badge", children: count }) : null
2590
3241
  ] });
2591
3242
  }
3243
+ function CanvasKindIcons({
3244
+ kinds,
3245
+ variant = "transparent",
3246
+ className,
3247
+ label = "Canvas tools"
3248
+ }) {
3249
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { role: "group", "aria-label": label, className: ["mxc-kind-icons", className].filter(Boolean).join(" "), children: kinds.map((entry) => {
3250
+ const icon = typeof entry === "string" ? { kind: entry } : entry;
3251
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(KindIconButton, { icon, variant }, `${icon.kind}::${icon.key ?? CANVAS_DEFAULT_KEY}`);
3252
+ }) });
3253
+ }
3254
+ function KindIconButton({ icon, variant }) {
3255
+ const kind = useCanvasKind(icon.kind);
3256
+ const toggle = useCanvasKindToggle({
3257
+ kind: icon.kind,
3258
+ key: icon.key,
3259
+ data: icon.data,
3260
+ title: icon.title,
3261
+ whenVisible: icon.whenVisible
3262
+ });
3263
+ if (!kind) return null;
3264
+ const Icon = kind.icon;
3265
+ const Button = variant === "glass" ? import_tap_target.TapTargetButton : import_tap_target.TapTargetButtonTransparent;
3266
+ const name = icon.label ?? kind.label;
3267
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3268
+ Button,
3269
+ {
3270
+ ariaLabel: name,
3271
+ tooltip: name,
3272
+ icon: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Icon, {}),
3273
+ pressed: toggle.isVisible,
3274
+ onClick: () => void toggle.toggle()
3275
+ }
3276
+ );
3277
+ }
2592
3278
  function CanvasFrame({ children, className }) {
2593
3279
  const { windowId } = useCanvasInternals();
2594
3280
  const isFullscreen = useCanvasState((s) => selectCanvasIsFullscreen(s, windowId));
2595
3281
  const isOpen = useCanvasState((s) => selectCanvasIsOpen(s, windowId));
2596
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: ["mxc-frame", className].filter(Boolean).join(" "), "data-canvas-open": isOpen ? "" : void 0, children: [
2597
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "mxc-frame-main", hidden: isOpen && isFullscreen, children }),
2598
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(CanvasColumn, {})
3282
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: ["mxc-frame", className].filter(Boolean).join(" "), "data-canvas-open": isOpen ? "" : void 0, children: [
3283
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "mxc-frame-main", hidden: isOpen && isFullscreen, children }),
3284
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(CanvasColumn, {})
2599
3285
  ] });
2600
3286
  }
2601
3287
  //# sourceMappingURL=react.cjs.map