@ai-matrx/canvas 0.4.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/react.cjs CHANGED
@@ -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 ?? "");
@@ -1507,11 +1701,20 @@ function CanvasProvider({
1507
1701
  layoutRules,
1508
1702
  regionWidth = null,
1509
1703
  tabStrip = "auto",
1510
- ...hostPorts
1704
+ itemActions,
1705
+ popOut,
1706
+ dockBack,
1707
+ showMenu,
1708
+ overlay,
1709
+ onNativeBodyFrame
1511
1710
  }) {
1711
+ const hostPorts = (0, import_react2.useMemo)(
1712
+ () => ({ itemActions, popOut, dockBack, showMenu, overlay, onNativeBodyFrame }),
1713
+ [itemActions, popOut, dockBack, showMenu, overlay, onNativeBodyFrame]
1714
+ );
1512
1715
  const portsRef = (0, import_react2.useRef)(hostPorts);
1513
1716
  portsRef.current = hostPorts;
1514
- const [{ controller, layer }] = (0, import_react2.useState)(() => ({
1717
+ const [{ controller, layer, launcherEntries }] = (0, import_react2.useState)(() => ({
1515
1718
  controller: createCanvasController({
1516
1719
  store: store ?? createCanvasStore(),
1517
1720
  persistence: persistence === void 0 ? createLocalStorageCanvasPersistence() : persistence,
@@ -1521,9 +1724,11 @@ function CanvasProvider({
1521
1724
  windowId: windowIdProp ?? CANVAS_MAIN_WINDOW,
1522
1725
  layoutRules,
1523
1726
  windowPorts: () => portsRef.current,
1524
- sessions: sessionHooksFor
1727
+ sessions: sessionHooksFor,
1728
+ preferredTarget: (kind) => getCanvasKind(kind)?.preferredTarget
1525
1729
  }),
1526
- layer: createCanvasLayer(() => portsRef.current)
1730
+ layer: createCanvasLayer(() => portsRef.current),
1731
+ launcherEntries: createLauncherEntryStore()
1527
1732
  }));
1528
1733
  const windowId = controller.windowId;
1529
1734
  (0, import_react2.useEffect)(() => controller.start(), [controller]);
@@ -1548,7 +1753,10 @@ function CanvasProvider({
1548
1753
  window.addEventListener("keydown", onKey);
1549
1754
  return () => window.removeEventListener("keydown", onKey);
1550
1755
  }, [controller, hotkeys, windowId]);
1551
- const value = { controller, windowId, ports: hostPorts, layer, regionWidth, tabStrip };
1756
+ const value = (0, import_react2.useMemo)(
1757
+ () => ({ controller, windowId, ports: hostPorts, layer, regionWidth, tabStrip, launcherEntries }),
1758
+ [controller, windowId, hostPorts, layer, regionWidth, tabStrip, launcherEntries]
1759
+ );
1552
1760
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(CanvasContext.Provider, { value, children: [
1553
1761
  children,
1554
1762
  hostPorts.overlay ? null : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(CanvasDomOverlay, { layer })
@@ -1556,8 +1764,10 @@ function CanvasProvider({
1556
1764
  }
1557
1765
  function CanvasWindowScope({ windowId, children }) {
1558
1766
  const parent = useCanvasContext();
1559
- const id = canvasWindowId(windowId);
1560
- const value = { ...parent, windowId: id, controller: parent.controller.forWindow(id) };
1767
+ const value = (0, import_react2.useMemo)(() => {
1768
+ const id = canvasWindowId(windowId);
1769
+ return { ...parent, windowId: id, controller: parent.controller.forWindow(id) };
1770
+ }, [parent, windowId]);
1561
1771
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(CanvasContext.Provider, { value, children });
1562
1772
  }
1563
1773
  function useCanvasContext() {
@@ -1635,14 +1845,38 @@ function useCanvasKind(id) {
1635
1845
  (0, import_react2.useSyncExternalStore)(subscribeCanvasKinds, getCanvasKindsVersion, getCanvasKindsVersion);
1636
1846
  return getCanvasKind(id);
1637
1847
  }
1848
+ var NO_ENTRIES = Object.freeze([]);
1849
+ var noEntries = () => NO_ENTRIES;
1850
+ function useCanvasLauncherEntry(entry) {
1851
+ const store = (0, import_react2.useContext)(CanvasContext)?.launcherEntries;
1852
+ const fingerprint = JSON.stringify([entry.kind, entry.key, entry.title ?? null, entry.data ?? null]);
1853
+ const icon = entry.icon;
1854
+ (0, import_react2.useEffect)(() => {
1855
+ if (!store) return void 0;
1856
+ const [kind, key, title, data] = JSON.parse(fingerprint);
1857
+ return store.add({ kind, key, title, data, ...icon ? { icon } : {} });
1858
+ }, [store, fingerprint, icon]);
1859
+ }
1860
+ function useCanvasLauncherEntries() {
1861
+ const store = (0, import_react2.useContext)(CanvasContext)?.launcherEntries;
1862
+ const all = (0, import_react2.useSyncExternalStore)(store ? store.subscribe : NO_SUBSCRIPTION, store ? store.list : noEntries, store ? store.list : noEntries);
1863
+ const seen = /* @__PURE__ */ new Set();
1864
+ const out = [];
1865
+ for (let i = all.length - 1; i >= 0; i -= 1) {
1866
+ const entry = all[i];
1867
+ if (!entry || seen.has(`${entry.kind}::${entry.key}`)) continue;
1868
+ seen.add(`${entry.kind}::${entry.key}`);
1869
+ out.unshift(entry);
1870
+ }
1871
+ return out;
1872
+ }
1638
1873
 
1639
1874
  // src/react/CanvasColumn.tsx
1640
- var import_react5 = require("react");
1875
+ var import_react9 = require("react");
1641
1876
  var import_tap_target = require("@ai-matrx/tap-target");
1642
1877
 
1643
- // src/react/CanvasPane.tsx
1644
- var import_react4 = require("react");
1645
- var Menu = __toESM(require("@radix-ui/react-dropdown-menu"), 1);
1878
+ // src/react/bodies.tsx
1879
+ var import_react7 = require("react");
1646
1880
 
1647
1881
  // src/react/chrome.tsx
1648
1882
  var import_react3 = require("react");
@@ -1737,6 +1971,12 @@ var DockIcon = (p) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Glyph, { ...
1737
1971
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M21 3 12 12" }),
1738
1972
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M12 6v6h6" })
1739
1973
  ] });
1974
+ var LayoutGridIcon = (p) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Glyph, { ...p, children: [
1975
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("rect", { width: "7", height: "7", x: "3", y: "3", rx: "1" }),
1976
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("rect", { width: "7", height: "7", x: "14", y: "3", rx: "1" }),
1977
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("rect", { width: "7", height: "7", x: "14", y: "14", rx: "1" }),
1978
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("rect", { width: "7", height: "7", x: "3", y: "14", rx: "1" })
1979
+ ] });
1740
1980
 
1741
1981
  // src/react/chrome.tsx
1742
1982
  var import_jsx_runtime4 = require("react/jsx-runtime");
@@ -1987,22 +2227,542 @@ function BrowserBar({ url, canGoBack, canGoForward, onBack, onForward, onReload,
1987
2227
  ] });
1988
2228
  }
1989
2229
 
1990
- // src/react/CanvasPane.tsx
2230
+ // src/react/dnd.ts
2231
+ var import_react4 = require("react");
2232
+ var CANVAS_DRAG_MIME = "application/x-matrx-canvas-item";
2233
+ function useTabDrag(itemId, title) {
2234
+ const { layer } = useCanvasInternals();
2235
+ const active = (0, import_react4.useRef)([]);
2236
+ const end = () => {
2237
+ for (const dispose of active.current.splice(0)) dispose();
2238
+ };
2239
+ (0, import_react4.useEffect)(() => end, []);
2240
+ return {
2241
+ draggable: true,
2242
+ onDragStart: (event) => {
2243
+ event.dataTransfer.setData(CANVAS_DRAG_MIME, itemId);
2244
+ event.dataTransfer.effectAllowed = "move";
2245
+ end();
2246
+ active.current.push(layer.beginBlocking(), layer.show({ type: "tab-drag", itemId, title }));
2247
+ },
2248
+ onDragEnd: end
2249
+ };
2250
+ }
2251
+ function usePaneDrop(paneId, getOutline) {
2252
+ const canvas = useCanvas();
2253
+ const { layer } = useCanvasInternals();
2254
+ const drop = (0, import_react4.useRef)(null);
2255
+ (0, import_react4.useEffect)(() => () => drop.current?.(), []);
2256
+ const hide = () => {
2257
+ drop.current?.();
2258
+ drop.current = null;
2259
+ };
2260
+ return {
2261
+ onDragOver: (event) => {
2262
+ if (!event.dataTransfer.types.includes(CANVAS_DRAG_MIME)) return;
2263
+ event.preventDefault();
2264
+ event.dataTransfer.dropEffect = "move";
2265
+ const outline = getOutline();
2266
+ if (!drop.current && outline) {
2267
+ drop.current = layer.show({ type: "drop-indicator", rect: toCanvasRect(outline.getBoundingClientRect()), paneId });
2268
+ }
2269
+ },
2270
+ onDragLeave: (event) => {
2271
+ const next = event.relatedTarget;
2272
+ if (next && event.currentTarget.contains(next)) return;
2273
+ hide();
2274
+ },
2275
+ onDrop: (event) => {
2276
+ hide();
2277
+ const itemId = event.dataTransfer.getData(CANVAS_DRAG_MIME);
2278
+ if (!itemId) return;
2279
+ event.preventDefault();
2280
+ canvas.moveItem(itemId, paneId);
2281
+ }
2282
+ };
2283
+ }
2284
+
2285
+ // src/react/launcher.tsx
1991
2286
  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;
2287
+ function useChoices() {
2288
+ const entries = useCanvasLauncherEntries();
2289
+ const kinds = useCanvasKinds();
2290
+ const byId = new Map(kinds.map((kind) => [kind.id, kind]));
2291
+ const contextual = [];
2292
+ for (const entry of entries) {
2293
+ const kind = byId.get(entry.kind);
2294
+ if (!kind) continue;
2295
+ contextual.push({
2296
+ id: `ctx:${canvasItemId(entry.kind, entry.key)}`,
2297
+ label: entry.title ?? kind.label,
2298
+ icon: entry.icon ?? kind.icon,
2299
+ input: { kind: entry.kind, key: entry.key, title: entry.title ?? null, ...entry.data !== void 0 ? { data: entry.data } : {} }
2300
+ });
2301
+ }
2302
+ const global = [];
2303
+ for (const kind of kinds) {
2304
+ const launcher = kind.launcher;
2305
+ if (!launcher) continue;
2306
+ global.push({
2307
+ id: `kind:${kind.id}`,
2308
+ label: launcher.title ?? kind.label,
2309
+ icon: kind.icon,
2310
+ input: { kind: kind.id, key: launcher.key, data: launcher.data, title: launcher.title ?? null }
2311
+ });
2312
+ }
2313
+ return { contextual, global };
2314
+ }
2315
+ function LauncherGrid({ onChoose }) {
2316
+ const { contextual, global } = useChoices();
2317
+ if (contextual.length === 0 && global.length === 0) return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(CanvasPanelEmpty, { children: "Nothing to open" });
2318
+ const section = (label, choices) => choices.length === 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("section", { className: "mxc-launcher-section", "aria-label": label ?? "Open", children: [
2319
+ label ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("h3", { className: "mxc-launcher-heading", children: label }) : null,
2320
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "mxc-launcher-grid", children: choices.map((choice) => {
2321
+ const Icon = choice.icon;
2322
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("button", { type: "button", className: "mxc-launcher-item", onClick: () => onChoose(choice.input), children: [
2323
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Icon, { className: "mxc-launcher-icon" }),
2324
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children: choice.label })
2325
+ ] }, choice.id);
2326
+ }) })
2327
+ ] });
2328
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "mxc-launcher", children: [
2329
+ section(contextual.length > 0 ? "On this page" : null, contextual),
2330
+ section(contextual.length > 0 ? "Open" : null, global)
2331
+ ] });
2332
+ }
2333
+ function LauncherBody({ item, canvas }) {
2334
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(LauncherGrid, { onChoose: (input) => void canvas.navigate(item.id, input) });
2335
+ }
2336
+ function EmptyPaneLauncher({ paneId }) {
2337
+ const canvas = useCanvas();
2338
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2339
+ LauncherGrid,
2340
+ {
2341
+ onChoose: (input) => {
2342
+ const tab = canvas.newTab(paneId);
2343
+ canvas.navigate(tab, input);
2344
+ }
2345
+ }
2346
+ );
2347
+ }
2348
+ var CANVAS_LAUNCHER_KIND_DEFINITION = {
2349
+ id: CANVAS_LAUNCHER_KIND,
2350
+ label: "New tab",
2351
+ icon: LayoutGridIcon,
2352
+ component: LauncherBody
2353
+ };
2354
+ function useItemKind(kindId) {
2355
+ const registered = useCanvasKind(kindId);
2356
+ return kindId === CANVAS_LAUNCHER_KIND ? CANVAS_LAUNCHER_KIND_DEFINITION : registered;
2357
+ }
2358
+
2359
+ // src/react/presentation.tsx
2360
+ var import_react5 = require("react");
2361
+ var CanvasPresentationContext = (0, import_react5.createContext)(null);
2362
+ function useCanvasPresentation() {
2363
+ return (0, import_react5.useContext)(CanvasPresentationContext);
2364
+ }
2365
+
2366
+ // src/react/slots.ts
2367
+ var import_react6 = require("react");
2368
+ var ZERO = Object.freeze({ width: 0, height: 0 });
2369
+ function createCanvasSlots() {
2370
+ const slots = /* @__PURE__ */ new Map();
2371
+ const sections = /* @__PURE__ */ new Map();
2372
+ const sizes = /* @__PURE__ */ new Map();
2373
+ const listeners = /* @__PURE__ */ new Set();
2374
+ let observer = null;
2375
+ const emit = () => {
2376
+ for (const listener of [...listeners]) listener();
2377
+ };
2378
+ const read = (paneId, element) => {
2379
+ const box = element.getBoundingClientRect();
2380
+ const width = Math.round(box.width);
2381
+ const height = Math.round(box.height);
2382
+ const previous = sizes.get(paneId);
2383
+ if (previous && previous.width === width && previous.height === height) return false;
2384
+ sizes.set(paneId, { width, height });
2385
+ return true;
2386
+ };
2387
+ const measureAll = () => {
2388
+ let changed = false;
2389
+ for (const [paneId, element] of slots) changed = read(paneId, element) || changed;
2390
+ return changed;
2391
+ };
2392
+ const watch = () => {
2393
+ if (observer || typeof ResizeObserver === "undefined") return observer;
2394
+ observer = new ResizeObserver(() => {
2395
+ measureAll();
2396
+ emit();
2397
+ });
2398
+ return observer;
2399
+ };
2400
+ return {
2401
+ set(paneId, element) {
2402
+ if (slots.get(paneId) === element) return;
2403
+ const previous = slots.get(paneId);
2404
+ if (previous) observer?.unobserve(previous);
2405
+ slots.set(paneId, element);
2406
+ watch()?.observe(element);
2407
+ read(paneId, element);
2408
+ emit();
2409
+ },
2410
+ unset(paneId, element) {
2411
+ const current = slots.get(paneId);
2412
+ if (!current || element && current !== element) return;
2413
+ observer?.unobserve(current);
2414
+ slots.delete(paneId);
2415
+ sizes.delete(paneId);
2416
+ emit();
2417
+ },
2418
+ setSection(paneId, element) {
2419
+ if (element) sections.set(paneId, element);
2420
+ else sections.delete(paneId);
2421
+ },
2422
+ slot: (paneId) => slots.get(paneId) ?? null,
2423
+ section: (paneId) => sections.get(paneId) ?? null,
2424
+ size: (paneId) => sizes.get(paneId) ?? ZERO,
2425
+ measure() {
2426
+ if (measureAll()) emit();
2427
+ },
2428
+ subscribe(listener) {
2429
+ listeners.add(listener);
2430
+ return () => listeners.delete(listener);
2431
+ }
2432
+ };
2433
+ }
2434
+ var CanvasSlotsContext = (0, import_react6.createContext)(null);
2435
+ function useCanvasSlots() {
2436
+ return (0, import_react6.useContext)(CanvasSlotsContext);
2437
+ }
2438
+ function useSlotSize(slots, paneId) {
2439
+ return (0, import_react6.useSyncExternalStore)(
2440
+ slots.subscribe,
2441
+ () => slots.size(paneId),
2442
+ () => ZERO
2443
+ );
2444
+ }
2445
+
2446
+ // src/react/bodies.tsx
2447
+ var import_jsx_runtime6 = require("react/jsx-runtime");
2448
+ var useIsoLayoutEffect = typeof window === "undefined" ? import_react7.useEffect : import_react7.useLayoutEffect;
1994
2449
  var LAZY = /* @__PURE__ */ Symbol.for("ai-matrx.canvas.lazy");
1995
2450
  function kindComponent(kind) {
1996
2451
  if (kind.component) return kind.component;
1997
2452
  const holder = kind;
1998
- if (!holder[LAZY] && kind.load) holder[LAZY] = (0, import_react4.lazy)(kind.load);
2453
+ if (!holder[LAZY] && kind.load) holder[LAZY] = (0, import_react7.lazy)(kind.load);
1999
2454
  return holder[LAZY] ?? null;
2000
2455
  }
2001
2456
  function resetKindComponent(kind) {
2002
2457
  if (kind) delete kind[LAZY];
2003
2458
  }
2459
+ function usePanePresentation(slots, paneId, isFullscreen, paneCount) {
2460
+ const size = useSlotSize(slots, paneId);
2461
+ return (0, import_react7.useMemo)(
2462
+ () => canvasPresentation({ width: size.width, height: size.height, isFullscreen, paneCount }),
2463
+ [size.width, size.height, isFullscreen, paneCount]
2464
+ );
2465
+ }
2466
+ var hostKey = (item) => item.instance ?? item.id;
2467
+ var SEP2 = "";
2468
+ var ROW = "";
2469
+ function layerSignature(state, windowId, onlyPane) {
2470
+ const frame2 = canvasWindowFrame(state, windowId);
2471
+ if (!frame2) return "";
2472
+ const rows = [];
2473
+ const paneIds = listPaneIds(frame2.layout);
2474
+ for (const paneId of onlyPane ? [onlyPane] : paneIds) {
2475
+ const pane = state.panes[paneId];
2476
+ for (const itemId of pane?.itemIds ?? []) {
2477
+ const item = state.items[itemId];
2478
+ if (item) rows.push({ item, row: [hostKey(item), itemId, paneId, pane?.activeItemId === itemId ? "1" : "0"].join(SEP2) });
2479
+ }
2480
+ }
2481
+ rows.sort((a, b) => a.item.openedAt - b.item.openedAt || hostKey(a.item).localeCompare(hostKey(b.item)));
2482
+ const head = [frame2.isOpen ? "1" : "0", frame2.isFullscreen ? "1" : "0", frame2.focusedPaneId, String(paneIds.length)].join(SEP2);
2483
+ return [head, ...rows.map((r) => r.row)].join(ROW);
2484
+ }
2485
+ function parseSignature(signature) {
2486
+ if (!signature) return null;
2487
+ const [head = "", ...rows] = signature.split(ROW);
2488
+ const [open, full, focusedPaneId = "", count = "1"] = head.split(SEP2);
2489
+ return {
2490
+ isOpen: open === "1",
2491
+ isFullscreen: full === "1",
2492
+ focusedPaneId,
2493
+ paneCount: Number(count) || 1,
2494
+ placed: rows.map((row) => {
2495
+ const [hostId = "", itemId = "", paneId = "", active] = row.split(SEP2);
2496
+ return { hostId, itemId, paneId, active: active === "1" };
2497
+ })
2498
+ };
2499
+ }
2500
+ function createPlacement(getRoot, slots, afterPlace) {
2501
+ const hosts = /* @__PURE__ */ new Map();
2502
+ let placing = false;
2503
+ const place = () => {
2504
+ const root = getRoot();
2505
+ if (!root || placing) return;
2506
+ placing = true;
2507
+ try {
2508
+ slots.measure();
2509
+ const base = root.getBoundingClientRect();
2510
+ for (const { element, paneId } of hosts.values()) {
2511
+ const slot = slots.slot(paneId);
2512
+ if (!slot) {
2513
+ element.dataset.placed = "false";
2514
+ continue;
2515
+ }
2516
+ const box = slot.getBoundingClientRect();
2517
+ const style = element.style;
2518
+ style.left = `${box.left - base.left}px`;
2519
+ style.top = `${box.top - base.top}px`;
2520
+ style.width = `${box.width}px`;
2521
+ style.height = `${box.height}px`;
2522
+ element.dataset.placed = "true";
2523
+ }
2524
+ } finally {
2525
+ placing = false;
2526
+ }
2527
+ afterPlace();
2528
+ };
2529
+ return {
2530
+ register(key, entry) {
2531
+ hosts.set(key, entry);
2532
+ return () => {
2533
+ if (hosts.get(key) === entry) hosts.delete(key);
2534
+ };
2535
+ },
2536
+ place
2537
+ };
2538
+ }
2539
+ function CanvasBodyLayer() {
2540
+ const { windowId, layer } = useCanvasInternals();
2541
+ const slots = useCanvasSlots();
2542
+ const signature = useCanvasState((s) => layerSignature(s, windowId));
2543
+ const root = (0, import_react7.useRef)(null);
2544
+ const [placement] = (0, import_react7.useState)(
2545
+ () => slots ? createPlacement(() => root.current, slots, () => layer.native.remeasure()) : null
2546
+ );
2547
+ useIsoLayoutEffect(() => placement?.place());
2548
+ useIsoLayoutEffect(() => slots && placement ? slots.subscribe(placement.place) : void 0, [slots, placement]);
2549
+ const shape = parseSignature(signature);
2550
+ if (!slots || !placement || !shape) return null;
2551
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { ref: root, className: "mxc-bodies", "data-mxc-bodies": windowId, children: shape.placed.map(({ hostId, itemId, paneId, active }) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2552
+ BodyHost,
2553
+ {
2554
+ hostId,
2555
+ itemId,
2556
+ paneId,
2557
+ visible: shape.isOpen && active,
2558
+ focused: active && shape.focusedPaneId === paneId,
2559
+ isFullscreen: shape.isFullscreen,
2560
+ paneCount: shape.paneCount,
2561
+ slots,
2562
+ placement
2563
+ },
2564
+ hostId
2565
+ )) });
2566
+ }
2567
+ function CanvasPaneInlineBodies({ paneId, slots }) {
2568
+ const { windowId } = useCanvasInternals();
2569
+ const shape = parseSignature(useCanvasState((s) => layerSignature(s, windowId, paneId)));
2570
+ if (!shape) return null;
2571
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_jsx_runtime6.Fragment, { children: shape.placed.map(({ hostId, itemId, active }) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2572
+ BodyHost,
2573
+ {
2574
+ hostId,
2575
+ itemId,
2576
+ paneId,
2577
+ visible: active,
2578
+ focused: active && shape.focusedPaneId === paneId,
2579
+ isFullscreen: shape.isFullscreen,
2580
+ paneCount: shape.paneCount,
2581
+ slots,
2582
+ placement: null
2583
+ },
2584
+ hostId
2585
+ )) });
2586
+ }
2587
+ var BodyHost = (0, import_react7.memo)(function BodyHost2({
2588
+ hostId,
2589
+ itemId,
2590
+ paneId,
2591
+ visible,
2592
+ focused,
2593
+ isFullscreen,
2594
+ paneCount,
2595
+ slots,
2596
+ placement
2597
+ }) {
2598
+ const canvas = useCanvas();
2599
+ const { windowId, ports } = useCanvasInternals();
2600
+ const item = useCanvasState((s) => s.items[itemId]);
2601
+ const kind = useItemKind(item?.kind ?? "");
2602
+ const element = (0, import_react7.useRef)(null);
2603
+ const [everShown, setEverShown] = (0, import_react7.useState)(visible);
2604
+ if (visible && !everShown) setEverShown(true);
2605
+ const mounted = kind?.unmountWhenHidden ? visible : everShown;
2606
+ const presentation = usePanePresentation(slots, paneId, isFullscreen, paneCount);
2607
+ const drop = usePaneDrop(paneId, () => slots.section(paneId));
2608
+ useIsoLayoutEffect(() => {
2609
+ const el = element.current;
2610
+ if (!placement || !el) return void 0;
2611
+ return placement.register(hostId, { element: el, paneId });
2612
+ }, [placement, hostId, paneId]);
2613
+ if (!item) return null;
2614
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2615
+ "div",
2616
+ {
2617
+ ref: element,
2618
+ className: "mxc-item-host",
2619
+ "data-inline": placement ? void 0 : "",
2620
+ "data-placed": placement ? "false" : void 0,
2621
+ "data-visible": visible ? "true" : "false",
2622
+ "data-kind": item.kind,
2623
+ "data-item": item.id,
2624
+ "data-pane": paneId,
2625
+ inert: !visible,
2626
+ "aria-hidden": visible ? void 0 : true,
2627
+ onPointerDownCapture: () => {
2628
+ if (!focused) canvas.focusPane(paneId);
2629
+ },
2630
+ ...drop,
2631
+ 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)(
2632
+ ItemContent,
2633
+ {
2634
+ item,
2635
+ kind,
2636
+ paneId,
2637
+ windowId,
2638
+ focused,
2639
+ visible,
2640
+ presentation,
2641
+ canvas,
2642
+ hasNativePort: !!ports.onNativeBodyFrame
2643
+ }
2644
+ ) }) }) : null
2645
+ }
2646
+ );
2647
+ });
2648
+ function ItemContent({
2649
+ item,
2650
+ kind,
2651
+ paneId,
2652
+ windowId,
2653
+ focused,
2654
+ visible,
2655
+ presentation,
2656
+ canvas,
2657
+ hasNativePort
2658
+ }) {
2659
+ let unavailable = null;
2660
+ try {
2661
+ unavailable = kind?.unavailable?.(item.data, item) ?? null;
2662
+ } catch (error) {
2663
+ console.error(`[@ai-matrx/canvas] "${item.kind}" unavailable() threw`, error);
2664
+ }
2665
+ if (!kind) return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ItemProblem, { message: "This item can no longer be shown.", onClose: () => canvas.close(item.id) });
2666
+ if (unavailable) return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(CanvasPanelEmpty, { children: unavailable });
2667
+ const rendered = kindComponent(kind) ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Rendered, { kind, item, paneId, isFocused: focused, isVisible: visible, canvas, windowId, presentation }) : null;
2668
+ if (kind.body === "native") {
2669
+ 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" });
2670
+ }
2671
+ return rendered;
2672
+ }
2673
+ var Rendered = (0, import_react7.memo)(function Rendered2({
2674
+ kind,
2675
+ item,
2676
+ paneId,
2677
+ isFocused,
2678
+ isVisible,
2679
+ canvas,
2680
+ windowId,
2681
+ presentation
2682
+ }) {
2683
+ const props = { item, data: item.data, paneId, isFocused, isVisible, canvas, windowId, presentation };
2684
+ 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 }) }) });
2685
+ });
2686
+ function KindRender({ kind, props }) {
2687
+ const Render = kindComponent(kind);
2688
+ if (!Render) throw new Error(`[@ai-matrx/canvas] kind "${kind.id}" has nothing to render.`);
2689
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Render, { ...props });
2690
+ }
2691
+ function NativeBody({ itemId, windowId, visible, children }) {
2692
+ const { layer } = useCanvasInternals();
2693
+ const ref = (0, import_react7.useRef)(null);
2694
+ const measure = (0, import_react7.useRef)(() => void 0);
2695
+ measure.current = () => {
2696
+ const el = ref.current;
2697
+ const box = visible && el?.isConnected ? el.getBoundingClientRect() : null;
2698
+ if (!box || box.width <= 0 || box.height <= 0) {
2699
+ layer.native.report(itemId, null, { visible: false, windowId });
2700
+ return;
2701
+ }
2702
+ const rect = { ...toCanvasRect(box), devicePixelRatio: window.devicePixelRatio || 1 };
2703
+ layer.native.report(itemId, rect, { visible: !layer.isBlocked(), windowId });
2704
+ };
2705
+ useIsoLayoutEffect(() => measure.current());
2706
+ useIsoLayoutEffect(() => {
2707
+ const el = ref.current;
2708
+ const run = () => measure.current();
2709
+ const observer = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(run);
2710
+ if (el) observer?.observe(el);
2711
+ const unregister = layer.native.register(run);
2712
+ window.addEventListener("resize", run);
2713
+ return () => {
2714
+ observer?.disconnect();
2715
+ unregister();
2716
+ window.removeEventListener("resize", run);
2717
+ layer.native.report(itemId, null, { visible: false, windowId });
2718
+ };
2719
+ }, [itemId, windowId, layer]);
2720
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { ref, className: "mxc-native-body", "data-native-body": itemId, children });
2721
+ }
2722
+ function ItemProblem({ message, onClose, onRetry }) {
2723
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "mxc-problem", role: "alert", children: [
2724
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(AlertIcon, {}),
2725
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { children: message }),
2726
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "mxc-problem-actions", children: [
2727
+ onRetry ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", className: "mxc-text-button", onClick: onRetry, children: "Try again" }) : null,
2728
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", className: "mxc-text-button", onClick: onClose, children: "Close tab" })
2729
+ ] })
2730
+ ] });
2731
+ }
2732
+ var ItemBoundary = class extends import_react7.Component {
2733
+ state = { error: null };
2734
+ static getDerivedStateFromError(error) {
2735
+ return { error };
2736
+ }
2737
+ componentDidCatch(error, info) {
2738
+ console.error(`[@ai-matrx/canvas] "${this.props.item.kind}" failed to render`, error, info.componentStack);
2739
+ }
2740
+ render() {
2741
+ if (this.state.error) {
2742
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2743
+ ItemProblem,
2744
+ {
2745
+ message: "This item failed to load.",
2746
+ onClose: this.props.onClose,
2747
+ onRetry: () => {
2748
+ this.props.onRetry();
2749
+ this.setState({ error: null });
2750
+ }
2751
+ }
2752
+ );
2753
+ }
2754
+ return this.props.children;
2755
+ }
2756
+ };
2757
+
2758
+ // src/react/CanvasPane.tsx
2759
+ var import_react8 = require("react");
2760
+ var Menu = __toESM(require("@radix-ui/react-dropdown-menu"), 1);
2761
+ var import_jsx_runtime7 = require("react/jsx-runtime");
2762
+ var useIsoLayoutEffect2 = typeof window === "undefined" ? import_react8.useEffect : import_react8.useLayoutEffect;
2004
2763
  function itemTitle(item, kind) {
2005
2764
  if (item.title) return item.title;
2765
+ if (item.kind === CANVAS_LAUNCHER_KIND) return "New tab";
2006
2766
  if (kind?.title) {
2007
2767
  try {
2008
2768
  const title = kind.title(item.data, item);
@@ -2013,70 +2773,46 @@ function itemTitle(item, kind) {
2013
2773
  return kind?.label ?? item.kind;
2014
2774
  }
2015
2775
  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
2776
  function CanvasPaneView({ paneId }) {
2035
2777
  const canvas = useCanvas();
2036
- const { windowId, layer, ports, tabStrip } = useCanvasInternals();
2778
+ const { windowId, tabStrip } = useCanvasInternals();
2779
+ const columnSlots = useCanvasSlots();
2780
+ const [ownSlots] = (0, import_react8.useState)(createCanvasSlots);
2781
+ const slots = columnSlots ?? ownSlots;
2037
2782
  const pane = useCanvasState((s) => s.panes[paneId]);
2038
- const items = useCanvasState((s) => s.items);
2783
+ const active = useCanvasState((s) => {
2784
+ const activeId = s.panes[paneId]?.activeItemId;
2785
+ return activeId ? s.items[activeId] : void 0;
2786
+ });
2039
2787
  const isFullscreen = useCanvasState((s) => selectCanvasIsFullscreen(s, windowId));
2788
+ const isOpen = useCanvasState((s) => selectCanvasIsOpen(s, windowId));
2040
2789
  const focused = useCanvasState((s) => selectCanvasFocusedPaneId(s, windowId) === paneId);
2041
2790
  const paneCount = useCanvasState((s) => selectCanvasPaneCount(s, windowId));
2042
2791
  const role = useCanvasState((s) => windowId === CANVAS_MAIN_WINDOW ? "main" : s.windows[windowId]?.role ?? "window");
2043
- 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?.(), []);
2792
+ const canGoBack = useCanvasState((s) => active ? selectCanvasCanGoBack(s, active.id) : false);
2793
+ const kind = useItemKind(active?.kind ?? "");
2794
+ const sectionRef = (0, import_react8.useRef)(null);
2795
+ const bodyRef = (0, import_react8.useRef)(null);
2796
+ const presentation = usePanePresentation(slots, paneId, isFullscreen, paneCount);
2797
+ const drop = usePaneDrop(paneId, () => sectionRef.current);
2798
+ useIsoLayoutEffect2(() => {
2799
+ const body = bodyRef.current;
2800
+ const section = sectionRef.current;
2801
+ if (body) slots.set(paneId, body);
2802
+ slots.setSection(paneId, section);
2803
+ return () => {
2804
+ slots.unset(paneId, body);
2805
+ if (slots.section(paneId) === section) slots.setSection(paneId, null);
2806
+ };
2807
+ }, [slots, paneId]);
2048
2808
  if (!pane) return null;
2049
- const kindProps = active && kind ? { item: active, data: active.data, paneId, isFocused: focused, canvas, windowId } : null;
2809
+ const kindProps = active && kind ? { item: active, data: active.data, paneId, isFocused: focused, isVisible: isOpen, canvas, windowId, presentation } : null;
2050
2810
  const Leading = kind?.HeaderLeading;
2051
2811
  const Action = kind?.HeaderAction;
2052
2812
  const Toolbar = kind?.Toolbar;
2053
2813
  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
2814
  const closeLabel = role === "pop-out" && paneCount === 1 ? "Close window" : paneCount > 1 ? "Close pane" : "Close canvas";
2079
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
2815
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
2080
2816
  CanvasPanel,
2081
2817
  {
2082
2818
  ref: sectionRef,
@@ -2085,10 +2821,14 @@ function CanvasPaneView({ paneId }) {
2085
2821
  title: active ? itemTitle(active, kind) : "Canvas",
2086
2822
  focused,
2087
2823
  "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 }),
2824
+ header: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
2825
+ 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,
2826
+ 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" }),
2827
+ /* @__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, {}) })
2828
+ ] }),
2829
+ leading: Leading && kindProps ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Leading, { ...kindProps }) : null,
2830
+ actions: Action && kindProps ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Action, { ...kindProps }) : null,
2831
+ renderMenu: () => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(PaneMenu, { paneId, windowId, activeItem: active, kind, kindProps, tabCount: pane.itemIds.length, role }),
2092
2832
  expanded: isFullscreen,
2093
2833
  onToggleExpand: () => canvas.setFullscreen(!isFullscreen),
2094
2834
  expandShortcut: canvasShortcutHint("canvas.toggleFullscreen", isMac()),
@@ -2098,22 +2838,15 @@ function CanvasPaneView({ paneId }) {
2098
2838
  onPointerDownCapture: () => {
2099
2839
  if (!focused) canvas.focusPane(paneId);
2100
2840
  },
2101
- onDragOver,
2102
- onDragLeave,
2103
- onDrop,
2841
+ ...drop,
2104
2842
  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
- })
2843
+ 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,
2844
+ /* @__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
2845
  ]
2113
2846
  }
2114
2847
  );
2115
2848
  }
2116
- var ToolbarBoundary = class extends import_react4.Component {
2849
+ var ToolbarBoundary = class extends import_react8.Component {
2117
2850
  state = { failed: false };
2118
2851
  static getDerivedStateFromError() {
2119
2852
  return { failed: true };
@@ -2126,41 +2859,71 @@ var ToolbarBoundary = class extends import_react4.Component {
2126
2859
  }
2127
2860
  };
2128
2861
  function SingleTitle({ item, kind }) {
2862
+ const canvas = useCanvas();
2129
2863
  const title = itemTitle(item, kind);
2130
2864
  const drag = useTabDrag(item.id, title);
2131
2865
  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 })
2866
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: "mxc-panel-title mxc-single-title", "data-draggable": "", title, ...drag, children: [
2867
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Icon, { className: "mxc-title-icon" }),
2868
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "mxc-tab-title", children: title }),
2869
+ /* @__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
2870
  ] });
2136
2871
  }
2872
+ function TabStrip({ paneId, itemIds, activeItemId }) {
2873
+ const strip = (0, import_react8.useRef)(null);
2874
+ useIsoLayoutEffect2(() => {
2875
+ const el = strip.current;
2876
+ if (!el) return void 0;
2877
+ const reveal = () => {
2878
+ const tab = el.querySelector('[role="tab"][aria-selected="true"]');
2879
+ if (!tab) return;
2880
+ const left = tab.offsetLeft;
2881
+ const right = left + tab.offsetWidth;
2882
+ if (left < el.scrollLeft) el.scrollLeft = left;
2883
+ else if (right > el.scrollLeft + el.clientWidth) el.scrollLeft = right - el.clientWidth;
2884
+ };
2885
+ reveal();
2886
+ const observer = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(reveal);
2887
+ observer?.observe(el);
2888
+ return () => observer?.disconnect();
2889
+ }, [activeItemId, itemIds.length]);
2890
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2891
+ "div",
2892
+ {
2893
+ ref: strip,
2894
+ className: "mxc-tabs",
2895
+ role: "tablist",
2896
+ "aria-label": "Canvas tabs",
2897
+ onWheel: (event) => {
2898
+ const el = strip.current;
2899
+ if (el && Math.abs(event.deltaY) > Math.abs(event.deltaX)) el.scrollLeft += event.deltaY;
2900
+ },
2901
+ children: itemIds.map((itemId, index) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ItemTab, { itemId, paneId, index, active: itemId === activeItemId }, itemId))
2902
+ }
2903
+ );
2904
+ }
2137
2905
  function ItemTab({ itemId, paneId, index, active }) {
2138
2906
  const canvas = useCanvas();
2139
2907
  const item = useCanvasState((s) => s.items[itemId]);
2140
- const kind = useCanvasKind(item?.kind ?? "");
2141
- const ref = (0, import_react4.useRef)(null);
2908
+ const kind = useItemKind(item?.kind ?? "");
2142
2909
  const title = item ? itemTitle(item, kind) : "";
2143
2910
  const drag = useTabDrag(itemId, title);
2144
- (0, import_react4.useEffect)(() => {
2145
- if (active) ref.current?.scrollIntoView?.({ block: "nearest", inline: "nearest" });
2146
- }, [active]);
2147
2911
  if (!item) return null;
2148
2912
  const Icon = kind?.icon ?? AlertIcon;
2149
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2913
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2150
2914
  CanvasTab,
2151
2915
  {
2152
- ref,
2153
2916
  title,
2154
- icon: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Icon, {}),
2917
+ icon: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Icon, {}),
2155
2918
  active,
2156
2919
  onSelect: () => canvas.activate(itemId),
2157
2920
  onClose: () => canvas.close(itemId),
2158
2921
  ...drag,
2159
2922
  onDragOver: (event) => {
2160
- if (event.dataTransfer.types.includes(DRAG_MIME)) event.preventDefault();
2923
+ if (event.dataTransfer.types.includes(CANVAS_DRAG_MIME)) event.preventDefault();
2161
2924
  },
2162
2925
  onDrop: (event) => {
2163
- const dragged = event.dataTransfer.getData(DRAG_MIME);
2926
+ const dragged = event.dataTransfer.getData(CANVAS_DRAG_MIME);
2164
2927
  if (!dragged) return;
2165
2928
  event.preventDefault();
2166
2929
  if (dragged !== itemId) canvas.moveItem(dragged, paneId, index);
@@ -2183,16 +2946,16 @@ function PaneMenu({
2183
2946
  const canvas = useCanvas();
2184
2947
  const { ports, layer } = useCanvasInternals();
2185
2948
  const moveId = tabCount > 1 ? activeItem?.id : void 0;
2186
- const blocking = (0, import_react4.useRef)(null);
2187
- (0, import_react4.useEffect)(() => () => blocking.current?.(), []);
2949
+ const blocking = (0, import_react8.useRef)(null);
2950
+ (0, import_react8.useEffect)(() => () => blocking.current?.(), []);
2188
2951
  const kindItems = kind?.menuItems && kindProps ? safeMenu(() => kind.menuItems?.(kindProps)) : [];
2189
2952
  const hostItems = activeItem && ports.itemActions ? safeMenu(() => ports.itemActions?.(activeItem, kind)) : [];
2190
2953
  const sections = [
2191
2954
  [...kindItems, ...hostItems].map((entry) => ({ ...entry, id: `item:${entry.id}` })),
2192
2955
  [
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) }] : []
2956
+ { id: "split-right", label: "Split right", icon: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(SplitRightIcon, {}), onSelect: () => canvas.splitPane(paneId, "horizontal", moveId) },
2957
+ { id: "split-down", label: "Split down", icon: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(SplitDownIcon, {}), onSelect: () => canvas.splitPane(paneId, "vertical", moveId) },
2958
+ ...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
2959
  ],
2197
2960
  activeItem ? [
2198
2961
  { id: "close-tab", label: "Close tab", onSelect: () => canvas.close(activeItem.id) },
@@ -2210,7 +2973,7 @@ function PaneMenu({
2210
2973
  handlers.set(action.id, action.onSelect);
2211
2974
  }
2212
2975
  });
2213
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2976
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2214
2977
  CanvasMenuTrigger,
2215
2978
  {
2216
2979
  onClick: (event) => {
@@ -2225,7 +2988,7 @@ function PaneMenu({
2225
2988
  }
2226
2989
  );
2227
2990
  }
2228
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
2991
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
2229
2992
  Menu.Root,
2230
2993
  {
2231
2994
  onOpenChange: (open) => {
@@ -2233,16 +2996,16 @@ function PaneMenu({
2233
2996
  blocking.current = open ? layer.beginBlocking() : null;
2234
2997
  },
2235
2998
  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)) }) })
2999
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Menu.Trigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(CanvasMenuTrigger, {}) }),
3000
+ /* @__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
3001
  ]
2239
3002
  }
2240
3003
  );
2241
3004
  }
2242
3005
  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: [
3006
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
3007
+ first ? null : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Menu.Separator, { className: "mxc-menu-separator" }),
3008
+ 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
3009
  action.icon,
2247
3010
  action.label
2248
3011
  ] }, action.id))
@@ -2256,150 +3019,16 @@ function safeMenu(build) {
2256
3019
  return [];
2257
3020
  }
2258
3021
  }
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
3022
 
2394
3023
  // 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;
3024
+ var import_jsx_runtime8 = require("react/jsx-runtime");
3025
+ var useIsoLayoutEffect3 = typeof window === "undefined" ? import_react9.useEffect : import_react9.useLayoutEffect;
2397
3026
  function subscribeViewport(listener) {
2398
3027
  window.addEventListener("resize", listener);
2399
3028
  return () => window.removeEventListener("resize", listener);
2400
3029
  }
2401
3030
  function useViewportWidth() {
2402
- return (0, import_react5.useSyncExternalStore)(subscribeViewport, () => window.innerWidth, () => 0);
3031
+ return (0, import_react9.useSyncExternalStore)(subscribeViewport, () => window.innerWidth, () => 0);
2403
3032
  }
2404
3033
  function useRegionWidth() {
2405
3034
  const viewport = useViewportWidth();
@@ -2417,28 +3046,66 @@ function useCanvasColumnWidth() {
2417
3046
  return fitCanvasWidth(width, region, controller.rules);
2418
3047
  }
2419
3048
  function CanvasColumn({ className, style, onLiveWidth, placement = "docked" }) {
2420
- const { controller, windowId } = useCanvasInternals();
3049
+ const { controller, windowId, layer } = useCanvasInternals();
2421
3050
  const isOpen = useCanvasState((s) => selectCanvasIsOpen(s, windowId));
2422
3051
  const isFullscreen = useCanvasState((s) => selectCanvasIsFullscreen(s, windowId));
2423
3052
  const width = useCanvasState((s) => selectCanvasWidth(s, windowId));
2424
3053
  const layout = useCanvasState((s) => selectCanvasLayout(s, windowId));
2425
3054
  const region = useRegionWidth();
2426
- const [dragWidth, setDragWidth] = (0, import_react5.useState)(null);
3055
+ const [dragWidth, setDragWidth] = (0, import_react9.useState)(null);
3056
+ const [slots] = (0, import_react9.useState)(createCanvasSlots);
3057
+ const ref = (0, import_react9.useRef)(null);
2427
3058
  useRegisterCanvasPresence();
2428
- if (!isOpen || !layout) return null;
3059
+ (0, import_react9.useEffect)(() => {
3060
+ const el = ref.current;
3061
+ if (!el) return void 0;
3062
+ let frame2 = 0;
3063
+ let running = false;
3064
+ const tick = () => {
3065
+ layer.native.remeasure();
3066
+ frame2 = running ? requestAnimationFrame(tick) : 0;
3067
+ };
3068
+ const start = (event) => {
3069
+ if (event.target !== el || running) return;
3070
+ running = true;
3071
+ frame2 = requestAnimationFrame(tick);
3072
+ };
3073
+ const stop = (event) => {
3074
+ if (event.target !== el) return;
3075
+ running = false;
3076
+ if (frame2) cancelAnimationFrame(frame2);
3077
+ frame2 = 0;
3078
+ layer.native.remeasure();
3079
+ };
3080
+ el.addEventListener("transitionrun", start);
3081
+ el.addEventListener("transitionend", stop);
3082
+ el.addEventListener("transitioncancel", stop);
3083
+ return () => {
3084
+ running = false;
3085
+ if (frame2) cancelAnimationFrame(frame2);
3086
+ el.removeEventListener("transitionrun", start);
3087
+ el.removeEventListener("transitionend", stop);
3088
+ el.removeEventListener("transitioncancel", stop);
3089
+ };
3090
+ }, [layer]);
3091
+ if (!layout) return null;
2429
3092
  const fitted = fitCanvasWidth(width, region, controller.rules);
2430
3093
  const shown = dragWidth ?? fitted;
2431
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
3094
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2432
3095
  "aside",
2433
3096
  {
3097
+ ref,
2434
3098
  className: ["mxc-column", className].filter(Boolean).join(" "),
2435
- "data-fullscreen": isFullscreen ? "" : void 0,
3099
+ "data-open": isOpen ? "" : void 0,
3100
+ "data-fullscreen": isOpen && isFullscreen ? "" : void 0,
2436
3101
  "data-placement": placement,
2437
3102
  "data-mxc-window": windowId,
2438
3103
  "aria-label": "Canvas",
3104
+ "aria-hidden": isOpen ? void 0 : true,
3105
+ inert: !isOpen,
2439
3106
  style: { ...style, ["--mxc-width"]: `${shown}px` },
2440
- children: [
2441
- placement === "docked" && !isFullscreen ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
3107
+ children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "mxc-column-inner", children: [
3108
+ placement === "docked" && isOpen && !isFullscreen ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2442
3109
  WidthHandle,
2443
3110
  {
2444
3111
  width: fitted,
@@ -2449,14 +3116,17 @@ function CanvasColumn({ className, style, onLiveWidth, placement = "docked" }) {
2449
3116
  }
2450
3117
  }
2451
3118
  ) : null,
2452
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "mxc-layout", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(LayoutNodeView, { node: layout }) })
2453
- ]
3119
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(CanvasSlotsContext.Provider, { value: slots, children: [
3120
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "mxc-layout", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(LayoutNodeView, { node: layout }) }),
3121
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(CanvasBodyLayer, {})
3122
+ ] })
3123
+ ] })
2454
3124
  }
2455
3125
  );
2456
3126
  }
2457
3127
  function WidthHandle({ width, region, onDrag }) {
2458
3128
  const canvas = useCanvas();
2459
- const start = (0, import_react5.useRef)(null);
3129
+ const start = (0, import_react9.useRef)(null);
2460
3130
  const clamp = (next) => clampDraggedCanvasWidth(next, region, canvas.rules);
2461
3131
  const onPointerDown = (event) => {
2462
3132
  event.preventDefault();
@@ -2476,7 +3146,7 @@ function WidthHandle({ width, region, onDrag }) {
2476
3146
  onDrag(null);
2477
3147
  canvas.setWidth(next);
2478
3148
  };
2479
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
3149
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2480
3150
  "div",
2481
3151
  {
2482
3152
  className: "mxc-width-handle",
@@ -2498,12 +3168,12 @@ function WidthHandle({ width, region, onDrag }) {
2498
3168
  );
2499
3169
  }
2500
3170
  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 });
3171
+ if (node.type === "pane") return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(CanvasPaneView, { paneId: node.paneId });
3172
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(SplitView, { node });
2503
3173
  }
2504
3174
  function useMeasuredWidth(ref, enabled) {
2505
- const [width, setWidth] = (0, import_react5.useState)(0);
2506
- useIsoLayoutEffect2(() => {
3175
+ const [width, setWidth] = (0, import_react9.useState)(0);
3176
+ useIsoLayoutEffect3(() => {
2507
3177
  const el = ref.current;
2508
3178
  if (!enabled || !el) return;
2509
3179
  setWidth(el.getBoundingClientRect().width);
@@ -2516,8 +3186,8 @@ function useMeasuredWidth(ref, enabled) {
2516
3186
  }
2517
3187
  function SplitView({ node }) {
2518
3188
  const canvas = useCanvas();
2519
- const ref = (0, import_react5.useRef)(null);
2520
- const [liveSizes, setLiveSizes] = (0, import_react5.useState)(null);
3189
+ const ref = (0, import_react9.useRef)(null);
3190
+ const [liveSizes, setLiveSizes] = (0, import_react9.useState)(null);
2521
3191
  const sizes = liveSizes ?? node.sizes;
2522
3192
  const measuredWidth = useMeasuredWidth(ref, node.orientation === "horizontal" && canvas.rules.paneMinWidth > 0);
2523
3193
  const stacked = node.orientation === "horizontal" && shouldStackSplit(measuredWidth, node.children.length, canvas.rules);
@@ -2557,11 +3227,11 @@ function SplitView({ node }) {
2557
3227
  target.addEventListener("pointerup", up);
2558
3228
  target.addEventListener("pointercancel", up);
2559
3229
  };
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)(
3230
+ 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
3231
  SplitChild,
2562
3232
  {
2563
3233
  size: sizes[i] ?? 1 / node.children.length,
2564
- handle: i < node.children.length - 1 ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
3234
+ handle: i < node.children.length - 1 ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2565
3235
  "div",
2566
3236
  {
2567
3237
  className: "mxc-split-handle",
@@ -2571,14 +3241,14 @@ function SplitView({ node }) {
2571
3241
  onDoubleClick: () => canvas.resizeSplit(node.id, node.children.map(() => 1 / node.children.length))
2572
3242
  }
2573
3243
  ) : null,
2574
- children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(LayoutNodeView, { node: child })
3244
+ children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(LayoutNodeView, { node: child })
2575
3245
  },
2576
3246
  child.type === "pane" ? child.paneId : child.id
2577
3247
  )) });
2578
3248
  }
2579
3249
  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 }),
3250
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
3251
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "mxc-split-child", style: { flexGrow: size, flexBasis: 0 }, children }),
2582
3252
  handle
2583
3253
  ] });
2584
3254
  }
@@ -2592,26 +3262,61 @@ function CanvasToggle({
2592
3262
  const count = useCanvasState((s) => selectCanvasWindowItemCount(s, windowId));
2593
3263
  const hint = canvasShortcutHint("canvas.toggle", isMac2());
2594
3264
  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)(
3265
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { className: ["mxc-toggle", className].filter(Boolean).join(" "), "data-open": isOpen ? "" : void 0, children: [
3266
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2597
3267
  Button,
2598
3268
  {
2599
3269
  ariaLabel: isOpen ? "Hide canvas" : "Show canvas",
2600
3270
  tooltip: `${isOpen ? "Hide canvas" : "Show canvas"}${hint ? ` (${hint})` : ""}`,
2601
- icon: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(PanelRightIcon, {}),
3271
+ icon: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(PanelRightIcon, {}),
2602
3272
  onClick: () => controller.toggle()
2603
3273
  }
2604
3274
  ),
2605
- count > 0 && !isOpen ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "mxc-toggle-badge", children: count }) : null
3275
+ count > 0 && !isOpen ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "mxc-toggle-badge", children: count }) : null
2606
3276
  ] });
2607
3277
  }
3278
+ function CanvasKindIcons({
3279
+ kinds,
3280
+ variant = "transparent",
3281
+ className,
3282
+ label = "Canvas tools"
3283
+ }) {
3284
+ 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) => {
3285
+ const icon = typeof entry === "string" ? { kind: entry } : entry;
3286
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(KindIconButton, { icon, variant }, `${icon.kind}::${icon.key ?? CANVAS_DEFAULT_KEY}`);
3287
+ }) });
3288
+ }
3289
+ function KindIconButton({ icon, variant }) {
3290
+ const kind = useCanvasKind(icon.kind);
3291
+ const toggle = useCanvasKindToggle({
3292
+ kind: icon.kind,
3293
+ key: icon.key,
3294
+ data: icon.data,
3295
+ title: icon.title,
3296
+ whenVisible: icon.whenVisible
3297
+ });
3298
+ if (!kind) return null;
3299
+ const Icon = kind.icon;
3300
+ const Button = variant === "glass" ? import_tap_target.TapTargetButton : import_tap_target.TapTargetButtonTransparent;
3301
+ const name = icon.label ?? kind.label;
3302
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3303
+ Button,
3304
+ {
3305
+ ariaLabel: name,
3306
+ tooltip: name,
3307
+ icon: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Icon, {}),
3308
+ pressed: toggle.isVisible,
3309
+ onClick: () => void toggle.toggle()
3310
+ }
3311
+ );
3312
+ }
2608
3313
  function CanvasFrame({ children, className }) {
2609
3314
  const { windowId } = useCanvasInternals();
2610
3315
  const isFullscreen = useCanvasState((s) => selectCanvasIsFullscreen(s, windowId));
2611
3316
  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, {})
3317
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: ["mxc-frame", className].filter(Boolean).join(" "), "data-canvas-open": isOpen ? "" : void 0, children: [
3318
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "mxc-frame-main", hidden: isOpen && isFullscreen, children }),
3319
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(CanvasColumn, {})
2615
3320
  ] });
2616
3321
  }
2617
3322
  //# sourceMappingURL=react.cjs.map