@ai-matrx/canvas 0.4.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,70 +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;
2052
2777
  const Toolbar = kind?.Toolbar;
2053
2778
  const single = tabStrip === "auto" && pane.itemIds.length === 1 && active;
2054
- const hideDrop = () => {
2055
- drop.current?.();
2056
- drop.current = null;
2057
- };
2058
- const onDragOver = (event) => {
2059
- if (!event.dataTransfer.types.includes(DRAG_MIME)) return;
2060
- event.preventDefault();
2061
- event.dataTransfer.dropEffect = "move";
2062
- if (!drop.current && sectionRef.current) {
2063
- drop.current = layer.show({ type: "drop-indicator", rect: toCanvasRect(sectionRef.current.getBoundingClientRect()), paneId });
2064
- }
2065
- };
2066
- const onDragLeave = (event) => {
2067
- const next = event.relatedTarget;
2068
- if (next && sectionRef.current?.contains(next)) return;
2069
- hideDrop();
2070
- };
2071
- const onDrop = (event) => {
2072
- hideDrop();
2073
- const itemId = event.dataTransfer.getData(DRAG_MIME);
2074
- if (!itemId) return;
2075
- event.preventDefault();
2076
- canvas.moveItem(itemId, paneId);
2077
- };
2078
2779
  const closeLabel = role === "pop-out" && paneCount === 1 ? "Close window" : paneCount > 1 ? "Close pane" : "Close canvas";
2079
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
2780
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
2080
2781
  CanvasPanel,
2081
2782
  {
2082
2783
  ref: sectionRef,
@@ -2085,10 +2786,14 @@ function CanvasPaneView({ paneId }) {
2085
2786
  title: active ? itemTitle(active, kind) : "Canvas",
2086
2787
  focused,
2087
2788
  "data-mxc-pane": paneId,
2088
- 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" }),
2089
- leading: Leading && kindProps ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Leading, { ...kindProps }) : null,
2090
- actions: Action && kindProps ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Action, { ...kindProps }) : null,
2091
- 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 }),
2092
2797
  expanded: isFullscreen,
2093
2798
  onToggleExpand: () => canvas.setFullscreen(!isFullscreen),
2094
2799
  expandShortcut: canvasShortcutHint("canvas.toggleFullscreen", isMac()),
@@ -2098,22 +2803,15 @@ function CanvasPaneView({ paneId }) {
2098
2803
  onPointerDownCapture: () => {
2099
2804
  if (!focused) canvas.focusPane(paneId);
2100
2805
  },
2101
- onDragOver,
2102
- onDragLeave,
2103
- onDrop,
2806
+ ...drop,
2104
2807
  children: [
2105
- Toolbar && kindProps ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "mxc-item-toolbar", "data-kind": active?.kind, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ToolbarBoundary, { kindId: kind?.id ?? "", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Toolbar, { ...kindProps }) }) }) : null,
2106
- pane.itemIds.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Launcher, { paneId }) : pane.itemIds.map((itemId) => {
2107
- const item = items[itemId];
2108
- if (!item) return null;
2109
- const isActive = itemId === pane.activeItemId;
2110
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ItemBody, { item, paneId, windowId, isActive, isFocused: focused && isActive, hasNativePort: !!ports.onNativeBodyFrame }, itemId);
2111
- })
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 }) })
2112
2810
  ]
2113
2811
  }
2114
2812
  );
2115
2813
  }
2116
- var ToolbarBoundary = class extends import_react4.Component {
2814
+ var ToolbarBoundary = class extends import_react8.Component {
2117
2815
  state = { failed: false };
2118
2816
  static getDerivedStateFromError() {
2119
2817
  return { failed: true };
@@ -2126,41 +2824,71 @@ var ToolbarBoundary = class extends import_react4.Component {
2126
2824
  }
2127
2825
  };
2128
2826
  function SingleTitle({ item, kind }) {
2827
+ const canvas = useCanvas();
2129
2828
  const title = itemTitle(item, kind);
2130
2829
  const drag = useTabDrag(item.id, title);
2131
2830
  const Icon = kind?.icon ?? AlertIcon;
2132
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { className: "mxc-panel-title", "data-draggable": "", title, ...drag, children: [
2133
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Icon, { className: "mxc-title-icon" }),
2134
- /* @__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, {}) })
2135
2835
  ] });
2136
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
+ }
2137
2870
  function ItemTab({ itemId, paneId, index, active }) {
2138
2871
  const canvas = useCanvas();
2139
2872
  const item = useCanvasState((s) => s.items[itemId]);
2140
- const kind = useCanvasKind(item?.kind ?? "");
2141
- const ref = (0, import_react4.useRef)(null);
2873
+ const kind = useItemKind(item?.kind ?? "");
2142
2874
  const title = item ? itemTitle(item, kind) : "";
2143
2875
  const drag = useTabDrag(itemId, title);
2144
- (0, import_react4.useEffect)(() => {
2145
- if (active) ref.current?.scrollIntoView?.({ block: "nearest", inline: "nearest" });
2146
- }, [active]);
2147
2876
  if (!item) return null;
2148
2877
  const Icon = kind?.icon ?? AlertIcon;
2149
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2878
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2150
2879
  CanvasTab,
2151
2880
  {
2152
- ref,
2153
2881
  title,
2154
- icon: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Icon, {}),
2882
+ icon: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Icon, {}),
2155
2883
  active,
2156
2884
  onSelect: () => canvas.activate(itemId),
2157
2885
  onClose: () => canvas.close(itemId),
2158
2886
  ...drag,
2159
2887
  onDragOver: (event) => {
2160
- if (event.dataTransfer.types.includes(DRAG_MIME)) event.preventDefault();
2888
+ if (event.dataTransfer.types.includes(CANVAS_DRAG_MIME)) event.preventDefault();
2161
2889
  },
2162
2890
  onDrop: (event) => {
2163
- const dragged = event.dataTransfer.getData(DRAG_MIME);
2891
+ const dragged = event.dataTransfer.getData(CANVAS_DRAG_MIME);
2164
2892
  if (!dragged) return;
2165
2893
  event.preventDefault();
2166
2894
  if (dragged !== itemId) canvas.moveItem(dragged, paneId, index);
@@ -2183,16 +2911,16 @@ function PaneMenu({
2183
2911
  const canvas = useCanvas();
2184
2912
  const { ports, layer } = useCanvasInternals();
2185
2913
  const moveId = tabCount > 1 ? activeItem?.id : void 0;
2186
- const blocking = (0, import_react4.useRef)(null);
2187
- (0, import_react4.useEffect)(() => () => blocking.current?.(), []);
2914
+ const blocking = (0, import_react8.useRef)(null);
2915
+ (0, import_react8.useEffect)(() => () => blocking.current?.(), []);
2188
2916
  const kindItems = kind?.menuItems && kindProps ? safeMenu(() => kind.menuItems?.(kindProps)) : [];
2189
2917
  const hostItems = activeItem && ports.itemActions ? safeMenu(() => ports.itemActions?.(activeItem, kind)) : [];
2190
2918
  const sections = [
2191
2919
  [...kindItems, ...hostItems].map((entry) => ({ ...entry, id: `item:${entry.id}` })),
2192
2920
  [
2193
- { id: "split-right", label: "Split right", icon: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(SplitRightIcon, {}), onSelect: () => canvas.splitPane(paneId, "horizontal", moveId) },
2194
- { id: "split-down", label: "Split down", icon: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(SplitDownIcon, {}), onSelect: () => canvas.splitPane(paneId, "vertical", moveId) },
2195
- ...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) }] : []
2196
2924
  ],
2197
2925
  activeItem ? [
2198
2926
  { id: "close-tab", label: "Close tab", onSelect: () => canvas.close(activeItem.id) },
@@ -2210,7 +2938,7 @@ function PaneMenu({
2210
2938
  handlers.set(action.id, action.onSelect);
2211
2939
  }
2212
2940
  });
2213
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2941
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2214
2942
  CanvasMenuTrigger,
2215
2943
  {
2216
2944
  onClick: (event) => {
@@ -2225,7 +2953,7 @@ function PaneMenu({
2225
2953
  }
2226
2954
  );
2227
2955
  }
2228
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
2956
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
2229
2957
  Menu.Root,
2230
2958
  {
2231
2959
  onOpenChange: (open) => {
@@ -2233,16 +2961,16 @@ function PaneMenu({
2233
2961
  blocking.current = open ? layer.beginBlocking() : null;
2234
2962
  },
2235
2963
  children: [
2236
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Menu.Trigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(CanvasMenuTrigger, {}) }),
2237
- /* @__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)) }) })
2238
2966
  ]
2239
2967
  }
2240
2968
  );
2241
2969
  }
2242
2970
  function MenuSection({ first, actions }) {
2243
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
2244
- first ? null : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Menu.Separator, { className: "mxc-menu-separator" }),
2245
- 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: [
2246
2974
  action.icon,
2247
2975
  action.label
2248
2976
  ] }, action.id))
@@ -2256,150 +2984,16 @@ function safeMenu(build) {
2256
2984
  return [];
2257
2985
  }
2258
2986
  }
2259
- function ItemBody({
2260
- item,
2261
- paneId,
2262
- windowId,
2263
- isActive,
2264
- isFocused,
2265
- hasNativePort
2266
- }) {
2267
- const canvas = useCanvas();
2268
- const kind = useCanvasKind(item.kind);
2269
- if (!isActive && !kind?.keepAlive) return null;
2270
- const props = { item, data: item.data, paneId, isFocused, canvas, windowId };
2271
- let unavailable = null;
2272
- try {
2273
- unavailable = kind?.unavailable?.(item.data, item) ?? null;
2274
- } catch (error) {
2275
- console.error(`[@ai-matrx/canvas] "${item.kind}" unavailable() threw`, error);
2276
- }
2277
- let body;
2278
- if (!kind) {
2279
- body = /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ItemProblem, { message: "This item can no longer be shown.", onClose: () => canvas.close(item.id) });
2280
- } else if (unavailable) {
2281
- body = /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(CanvasPanelEmpty, { children: unavailable });
2282
- } else if (kind.body === "native") {
2283
- 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" });
2284
- } else {
2285
- body = /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Rendered, { kind, item, props });
2286
- }
2287
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "mxc-item", hidden: !isActive, "data-kind": item.kind, children: body });
2288
- }
2289
- function Rendered({ kind, item, props }) {
2290
- const canvas = useCanvas();
2291
- 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 }) }) });
2292
- }
2293
- function KindRender({ kind, props }) {
2294
- const Render = kindComponent(kind);
2295
- if (!Render) throw new Error(`[@ai-matrx/canvas] kind "${kind.id}" has nothing to render.`);
2296
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Render, { ...props });
2297
- }
2298
- function NativeBody({ itemId, windowId, children }) {
2299
- const { layer } = useCanvasInternals();
2300
- const ref = (0, import_react4.useRef)(null);
2301
- const measure = (0, import_react4.useRef)(() => void 0);
2302
- measure.current = () => {
2303
- const el = ref.current;
2304
- const box = el?.isConnected ? el.getBoundingClientRect() : null;
2305
- if (!box || box.width <= 0 || box.height <= 0) {
2306
- layer.native.report(itemId, null, { visible: false, windowId });
2307
- return;
2308
- }
2309
- const rect = { ...toCanvasRect(box), devicePixelRatio: window.devicePixelRatio || 1 };
2310
- layer.native.report(itemId, rect, { visible: !layer.isBlocked(), windowId });
2311
- };
2312
- useIsoLayoutEffect(() => measure.current());
2313
- useIsoLayoutEffect(() => {
2314
- const el = ref.current;
2315
- const run = () => measure.current();
2316
- const observer = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(run);
2317
- if (el) observer?.observe(el);
2318
- const unregister = layer.native.register(run);
2319
- window.addEventListener("resize", run);
2320
- return () => {
2321
- observer?.disconnect();
2322
- unregister();
2323
- window.removeEventListener("resize", run);
2324
- layer.native.report(itemId, null, { visible: false, windowId });
2325
- };
2326
- }, [itemId, windowId, layer]);
2327
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { ref, className: "mxc-native-body", "data-native-body": itemId, children });
2328
- }
2329
- function ItemProblem({ message, onClose, onRetry }) {
2330
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "mxc-problem", role: "alert", children: [
2331
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(AlertIcon, {}),
2332
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { children: message }),
2333
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "mxc-problem-actions", children: [
2334
- onRetry ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", className: "mxc-text-button", onClick: onRetry, children: "Try again" }) : null,
2335
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", className: "mxc-text-button", onClick: onClose, children: "Close tab" })
2336
- ] })
2337
- ] });
2338
- }
2339
- var ItemBoundary = class extends import_react4.Component {
2340
- state = { error: null };
2341
- static getDerivedStateFromError(error) {
2342
- return { error };
2343
- }
2344
- componentDidCatch(error, info) {
2345
- console.error(`[@ai-matrx/canvas] "${this.props.item.kind}" failed to render`, error, info.componentStack);
2346
- }
2347
- render() {
2348
- if (this.state.error) {
2349
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2350
- ItemProblem,
2351
- {
2352
- message: "This item failed to load.",
2353
- onClose: this.props.onClose,
2354
- onRetry: () => {
2355
- this.props.onRetry();
2356
- this.setState({ error: null });
2357
- }
2358
- }
2359
- );
2360
- }
2361
- return this.props.children;
2362
- }
2363
- };
2364
- function Launcher({ paneId }) {
2365
- const canvas = useCanvas();
2366
- const kinds = useCanvasKinds().filter((kind) => kind.launcher);
2367
- if (kinds.length === 0) return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(CanvasPanelEmpty, { children: "Nothing open" });
2368
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "mxc-launcher", children: kinds.map((kind) => {
2369
- const Icon = kind.icon;
2370
- const launcher = kind.launcher;
2371
- if (!launcher) return null;
2372
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
2373
- "button",
2374
- {
2375
- type: "button",
2376
- className: "mxc-launcher-item",
2377
- onClick: () => canvas.open({
2378
- kind: kind.id,
2379
- key: launcher.key,
2380
- data: launcher.data,
2381
- title: launcher.title ?? null,
2382
- target: { paneId }
2383
- }),
2384
- children: [
2385
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Icon, { className: "mxc-launcher-icon" }),
2386
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children: launcher.title ?? kind.label })
2387
- ]
2388
- },
2389
- kind.id
2390
- );
2391
- }) });
2392
- }
2393
2987
 
2394
2988
  // src/react/CanvasColumn.tsx
2395
- var import_jsx_runtime6 = require("react/jsx-runtime");
2396
- 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;
2397
2991
  function subscribeViewport(listener) {
2398
2992
  window.addEventListener("resize", listener);
2399
2993
  return () => window.removeEventListener("resize", listener);
2400
2994
  }
2401
2995
  function useViewportWidth() {
2402
- return (0, import_react5.useSyncExternalStore)(subscribeViewport, () => window.innerWidth, () => 0);
2996
+ return (0, import_react9.useSyncExternalStore)(subscribeViewport, () => window.innerWidth, () => 0);
2403
2997
  }
2404
2998
  function useRegionWidth() {
2405
2999
  const viewport = useViewportWidth();
@@ -2417,28 +3011,66 @@ function useCanvasColumnWidth() {
2417
3011
  return fitCanvasWidth(width, region, controller.rules);
2418
3012
  }
2419
3013
  function CanvasColumn({ className, style, onLiveWidth, placement = "docked" }) {
2420
- const { controller, windowId } = useCanvasInternals();
3014
+ const { controller, windowId, layer } = useCanvasInternals();
2421
3015
  const isOpen = useCanvasState((s) => selectCanvasIsOpen(s, windowId));
2422
3016
  const isFullscreen = useCanvasState((s) => selectCanvasIsFullscreen(s, windowId));
2423
3017
  const width = useCanvasState((s) => selectCanvasWidth(s, windowId));
2424
3018
  const layout = useCanvasState((s) => selectCanvasLayout(s, windowId));
2425
3019
  const region = useRegionWidth();
2426
- 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);
2427
3023
  useRegisterCanvasPresence();
2428
- 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;
2429
3057
  const fitted = fitCanvasWidth(width, region, controller.rules);
2430
3058
  const shown = dragWidth ?? fitted;
2431
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
3059
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2432
3060
  "aside",
2433
3061
  {
3062
+ ref,
2434
3063
  className: ["mxc-column", className].filter(Boolean).join(" "),
2435
- "data-fullscreen": isFullscreen ? "" : void 0,
3064
+ "data-open": isOpen ? "" : void 0,
3065
+ "data-fullscreen": isOpen && isFullscreen ? "" : void 0,
2436
3066
  "data-placement": placement,
2437
3067
  "data-mxc-window": windowId,
2438
3068
  "aria-label": "Canvas",
3069
+ "aria-hidden": isOpen ? void 0 : true,
3070
+ inert: !isOpen,
2439
3071
  style: { ...style, ["--mxc-width"]: `${shown}px` },
2440
- children: [
2441
- 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)(
2442
3074
  WidthHandle,
2443
3075
  {
2444
3076
  width: fitted,
@@ -2449,14 +3081,17 @@ function CanvasColumn({ className, style, onLiveWidth, placement = "docked" }) {
2449
3081
  }
2450
3082
  }
2451
3083
  ) : null,
2452
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "mxc-layout", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(LayoutNodeView, { node: layout }) })
2453
- ]
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
+ ] })
2454
3089
  }
2455
3090
  );
2456
3091
  }
2457
3092
  function WidthHandle({ width, region, onDrag }) {
2458
3093
  const canvas = useCanvas();
2459
- const start = (0, import_react5.useRef)(null);
3094
+ const start = (0, import_react9.useRef)(null);
2460
3095
  const clamp = (next) => clampDraggedCanvasWidth(next, region, canvas.rules);
2461
3096
  const onPointerDown = (event) => {
2462
3097
  event.preventDefault();
@@ -2476,7 +3111,7 @@ function WidthHandle({ width, region, onDrag }) {
2476
3111
  onDrag(null);
2477
3112
  canvas.setWidth(next);
2478
3113
  };
2479
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
3114
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2480
3115
  "div",
2481
3116
  {
2482
3117
  className: "mxc-width-handle",
@@ -2498,12 +3133,12 @@ function WidthHandle({ width, region, onDrag }) {
2498
3133
  );
2499
3134
  }
2500
3135
  function LayoutNodeView({ node }) {
2501
- if (node.type === "pane") return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(CanvasPaneView, { paneId: node.paneId });
2502
- 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 });
2503
3138
  }
2504
3139
  function useMeasuredWidth(ref, enabled) {
2505
- const [width, setWidth] = (0, import_react5.useState)(0);
2506
- useIsoLayoutEffect2(() => {
3140
+ const [width, setWidth] = (0, import_react9.useState)(0);
3141
+ useIsoLayoutEffect3(() => {
2507
3142
  const el = ref.current;
2508
3143
  if (!enabled || !el) return;
2509
3144
  setWidth(el.getBoundingClientRect().width);
@@ -2516,8 +3151,8 @@ function useMeasuredWidth(ref, enabled) {
2516
3151
  }
2517
3152
  function SplitView({ node }) {
2518
3153
  const canvas = useCanvas();
2519
- const ref = (0, import_react5.useRef)(null);
2520
- 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);
2521
3156
  const sizes = liveSizes ?? node.sizes;
2522
3157
  const measuredWidth = useMeasuredWidth(ref, node.orientation === "horizontal" && canvas.rules.paneMinWidth > 0);
2523
3158
  const stacked = node.orientation === "horizontal" && shouldStackSplit(measuredWidth, node.children.length, canvas.rules);
@@ -2557,11 +3192,11 @@ function SplitView({ node }) {
2557
3192
  target.addEventListener("pointerup", up);
2558
3193
  target.addEventListener("pointercancel", up);
2559
3194
  };
2560
- 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)(
2561
3196
  SplitChild,
2562
3197
  {
2563
3198
  size: sizes[i] ?? 1 / node.children.length,
2564
- 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)(
2565
3200
  "div",
2566
3201
  {
2567
3202
  className: "mxc-split-handle",
@@ -2571,14 +3206,14 @@ function SplitView({ node }) {
2571
3206
  onDoubleClick: () => canvas.resizeSplit(node.id, node.children.map(() => 1 / node.children.length))
2572
3207
  }
2573
3208
  ) : null,
2574
- children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(LayoutNodeView, { node: child })
3209
+ children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(LayoutNodeView, { node: child })
2575
3210
  },
2576
3211
  child.type === "pane" ? child.paneId : child.id
2577
3212
  )) });
2578
3213
  }
2579
3214
  function SplitChild({ size, handle, children }) {
2580
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
2581
- /* @__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 }),
2582
3217
  handle
2583
3218
  ] });
2584
3219
  }
@@ -2592,26 +3227,61 @@ function CanvasToggle({
2592
3227
  const count = useCanvasState((s) => selectCanvasWindowItemCount(s, windowId));
2593
3228
  const hint = canvasShortcutHint("canvas.toggle", isMac2());
2594
3229
  const Button = variant === "glass" ? import_tap_target.TapTargetButton : import_tap_target.TapTargetButtonTransparent;
2595
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: ["mxc-toggle", className].filter(Boolean).join(" "), "data-open": isOpen ? "" : void 0, children: [
2596
- /* @__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)(
2597
3232
  Button,
2598
3233
  {
2599
3234
  ariaLabel: isOpen ? "Hide canvas" : "Show canvas",
2600
3235
  tooltip: `${isOpen ? "Hide canvas" : "Show canvas"}${hint ? ` (${hint})` : ""}`,
2601
- icon: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(PanelRightIcon, {}),
3236
+ icon: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(PanelRightIcon, {}),
2602
3237
  onClick: () => controller.toggle()
2603
3238
  }
2604
3239
  ),
2605
- 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
2606
3241
  ] });
2607
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
+ }
2608
3278
  function CanvasFrame({ children, className }) {
2609
3279
  const { windowId } = useCanvasInternals();
2610
3280
  const isFullscreen = useCanvasState((s) => selectCanvasIsFullscreen(s, windowId));
2611
3281
  const isOpen = useCanvasState((s) => selectCanvasIsOpen(s, windowId));
2612
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: ["mxc-frame", className].filter(Boolean).join(" "), "data-canvas-open": isOpen ? "" : void 0, children: [
2613
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "mxc-frame-main", hidden: isOpen && isFullscreen, children }),
2614
- /* @__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, {})
2615
3285
  ] });
2616
3286
  }
2617
3287
  //# sourceMappingURL=react.cjs.map