@ai-matrx/canvas 0.9.3 → 0.10.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.js CHANGED
@@ -5,9 +5,9 @@
5
5
  import {
6
6
  createContext,
7
7
  useContext,
8
- useEffect as useEffect3,
8
+ useEffect as useEffect4,
9
9
  useMemo,
10
- useRef as useRef3,
10
+ useRef as useRef4,
11
11
  useState as useState2,
12
12
  useSyncExternalStore as useSyncExternalStore2
13
13
  } from "react";
@@ -51,6 +51,163 @@ function useCanvasMenuItems(itemId, items) {
51
51
  }, [itemMenus, itemId]);
52
52
  }
53
53
 
54
+ // src/react/output.ts
55
+ import { useEffect as useEffect2, useLayoutEffect as useLayoutEffect2, useRef as useRef2 } from "react";
56
+ var useIsoLayoutEffect2 = typeof window === "undefined" ? useEffect2 : useLayoutEffect2;
57
+ function createCanvasItemOutputs() {
58
+ const sources = /* @__PURE__ */ new Map();
59
+ return {
60
+ register(itemId, read) {
61
+ let set = sources.get(itemId);
62
+ if (!set) sources.set(itemId, set = /* @__PURE__ */ new Set());
63
+ set.add(read);
64
+ return () => {
65
+ const current = sources.get(itemId);
66
+ current?.delete(read);
67
+ if (current && current.size === 0) sources.delete(itemId);
68
+ };
69
+ },
70
+ read(itemId) {
71
+ let out = {};
72
+ for (const read of sources.get(itemId) ?? []) {
73
+ try {
74
+ const next = read();
75
+ out = { ...out, ...definedOnly(next), unavailable: { ...out.unavailable, ...next.unavailable } };
76
+ } catch (error) {
77
+ console.error("[@ai-matrx/canvas] a body's output handlers threw", error);
78
+ }
79
+ }
80
+ return out;
81
+ }
82
+ };
83
+ }
84
+ function definedOnly(handlers) {
85
+ const out = {};
86
+ if (handlers.print) out.print = handlers.print;
87
+ if (handlers.savePdf) out.savePdf = handlers.savePdf;
88
+ if (handlers.capture) out.capture = handlers.capture;
89
+ return out;
90
+ }
91
+ function canvasItemElement(itemId) {
92
+ if (typeof document === "undefined") return null;
93
+ const hosts = document.querySelectorAll(".mxc-item-host[data-item]:not([data-kept])");
94
+ for (const host of hosts) {
95
+ if (host.dataset.item === itemId) return host.querySelector(":scope > .mxc-item");
96
+ }
97
+ return null;
98
+ }
99
+ function canvasKindSurface(kind) {
100
+ return kind?.surface ?? (kind?.body === "native" ? "native" : "dom");
101
+ }
102
+ async function defaultCopyImage(image) {
103
+ const png = image.type === "image/png" ? image : new Blob([image], { type: "image/png" });
104
+ await navigator.clipboard.write([new ClipboardItem({ "image/png": png })]);
105
+ }
106
+ function canvasOutputMenu(input) {
107
+ const { item, kind, title, body, ports } = input;
108
+ const surface = canvasKindSurface(kind);
109
+ const request = { item, kind, surface, element: canvasItemElement(item.id), title };
110
+ const report = (message) => (error) => ports?.onError ? ports.onError(message, error) : console.error(`[@ai-matrx/canvas] ${message}`, error);
111
+ const run = (fn, message) => () => {
112
+ try {
113
+ void Promise.resolve(fn()).catch(report(message));
114
+ } catch (error) {
115
+ report(message)(error);
116
+ }
117
+ };
118
+ const kindPrint = typeof kind?.print === "function" ? kind.print : null;
119
+ const kindPrintReason = typeof kind?.print === "string" ? kind.print : null;
120
+ const kindCapture = typeof kind?.capture === "function" ? kind.capture : null;
121
+ const kindCaptureReason = typeof kind?.capture === "string" ? kind.capture : null;
122
+ const hostServes = surface === "dom";
123
+ let print = null;
124
+ if (kind?.print !== false) {
125
+ if (body.print) print = body.print;
126
+ else if (kindPrint) print = () => kindPrint(request);
127
+ else if (hostServes && ports?.print) print = ports.print(request);
128
+ }
129
+ const printReason = print || kind?.print === false ? null : body.unavailable?.print ?? kindPrintReason;
130
+ let savePdf = print;
131
+ if (print && body.savePdf) savePdf = body.savePdf;
132
+ else if (print && !body.print && !kindPrint && hostServes && ports?.savePdf) savePdf = ports.savePdf(request) ?? print;
133
+ let capture = null;
134
+ if (kind?.capture !== false) {
135
+ if (body.capture) capture = body.capture;
136
+ else if (kindCapture) capture = () => kindCapture(request);
137
+ else if (surface !== "native" && ports?.capture) capture = ports.capture(request);
138
+ }
139
+ const captureReason = capture || kind?.capture === false ? null : body.unavailable?.capture ?? kindCaptureReason;
140
+ const entries = [];
141
+ if (printReason) {
142
+ entries.push({ id: "output:print", label: `Print \u2014 ${printReason}`, disabled: true, onSelect: () => void 0 });
143
+ } else if (print) {
144
+ entries.push({ id: "output:print", label: "Print", onSelect: run(print, "Print failed") });
145
+ if (savePdf) entries.push({ id: "output:pdf", label: "Save as PDF", onSelect: run(savePdf, "Save as PDF failed") });
146
+ }
147
+ if (captureReason) {
148
+ entries.push({ id: "output:copy-image", label: `Copy image \u2014 ${captureReason}`, disabled: true, onSelect: () => void 0 });
149
+ } else if (capture) {
150
+ const copy = ports?.copyImage ?? defaultCopyImage;
151
+ entries.push({
152
+ id: "output:copy-image",
153
+ label: "Copy image",
154
+ onSelect: run(async () => copy(await capture(), request), "Copy image failed")
155
+ });
156
+ }
157
+ if (ports?.attachOptions) {
158
+ try {
159
+ entries.push(...ports.attachOptions(request, captureReason ? null : capture));
160
+ } catch (error) {
161
+ report("Attach options failed")(error);
162
+ }
163
+ }
164
+ return entries;
165
+ }
166
+ function useCanvasOutput(itemId, handlers) {
167
+ const { itemOutputs } = useCanvasInternals();
168
+ const latest = useRef2(handlers);
169
+ latest.current = handlers;
170
+ useIsoLayoutEffect2(() => {
171
+ if (!itemId) return void 0;
172
+ return itemOutputs.register(itemId, () => latest.current);
173
+ }, [itemOutputs, itemId]);
174
+ }
175
+ function canvasKindOutputProblems(kind) {
176
+ const problems = [];
177
+ const surface = kind.surface;
178
+ if (surface !== "dom" && surface !== "frame" && surface !== "native") {
179
+ problems.push(`"${kind.id}" declares no surface ("dom" | "frame" | "native")`);
180
+ return problems;
181
+ }
182
+ if (kind.body === "native" && surface !== "native") problems.push(`"${kind.id}" has a native body but surface "${surface}"`);
183
+ if (surface !== "dom") {
184
+ if (kind.print === void 0) problems.push(`"${kind.id}" is a ${surface} kind with no print (a handler or an honest reason)`);
185
+ if (kind.capture === void 0) problems.push(`"${kind.id}" is a ${surface} kind with no capture (a handler or an honest reason)`);
186
+ }
187
+ return problems;
188
+ }
189
+ var warnedKinds = /* @__PURE__ */ new Set();
190
+ function watchDomSurface(element, kindId) {
191
+ if (typeof MutationObserver === "undefined" || warnedKinds.has(kindId)) return () => void 0;
192
+ const check = () => {
193
+ if (warnedKinds.has(kindId)) return true;
194
+ const found = element.querySelector("iframe, canvas");
195
+ if (!found) return false;
196
+ warnedKinds.add(kindId);
197
+ console.error(
198
+ `[@ai-matrx/canvas] kind "${kindId}" declares surface "dom" but its body contains a <${found.tagName.toLowerCase()}>. DOM print and capture cannot copy it \u2014 declare surface "frame" with its own print and capture.`,
199
+ found
200
+ );
201
+ return true;
202
+ };
203
+ if (check()) return () => void 0;
204
+ const observer = new MutationObserver(() => {
205
+ if (check()) observer.disconnect();
206
+ });
207
+ observer.observe(element, { childList: true, subtree: true });
208
+ return () => observer.disconnect();
209
+ }
210
+
54
211
  // src/core/types.ts
55
212
  var CANVAS_MAIN_WINDOW = "main";
56
213
  var CANVAS_LAUNCHER_KIND = "canvas.launcher";
@@ -1622,7 +1779,7 @@ function toggleKind(controller, input) {
1622
1779
  }
1623
1780
 
1624
1781
  // src/react/layer.tsx
1625
- import { useEffect as useEffect2, useLayoutEffect as useLayoutEffect2, useRef as useRef2, useState, useSyncExternalStore } from "react";
1782
+ import { useEffect as useEffect3, useLayoutEffect as useLayoutEffect3, useRef as useRef3, useState, useSyncExternalStore } from "react";
1626
1783
  import { createPortal } from "react-dom";
1627
1784
  import { jsx } from "react/jsx-runtime";
1628
1785
  function createCanvasLayer(getPorts) {
@@ -1691,7 +1848,7 @@ function createCanvasLayer(getPorts) {
1691
1848
  function CanvasDomOverlay({ layer }) {
1692
1849
  const elements = useSyncExternalStore(layer.dom.subscribe, layer.dom.list, layer.dom.list);
1693
1850
  const [mounted, setMounted] = useState(false);
1694
- useEffect2(() => setMounted(true), []);
1851
+ useEffect3(() => setMounted(true), []);
1695
1852
  if (!mounted || elements.length === 0 || typeof document === "undefined") return null;
1696
1853
  return createPortal(
1697
1854
  /* @__PURE__ */ jsx("div", { className: "mxc-overlay", "aria-hidden": "true", children: elements.map(([id, element]) => /* @__PURE__ */ jsx(FloatingView, { element }, id)) }),
@@ -1707,11 +1864,11 @@ function FloatingView({ element }) {
1707
1864
  return null;
1708
1865
  }
1709
1866
  function Tooltip({ element }) {
1710
- const ref = useRef2(null);
1867
+ const ref = useRef3(null);
1711
1868
  const { anchor, side, text } = element;
1712
1869
  const top = side === "bottom" ? anchor.y + anchor.height + 6 : anchor.y - 6;
1713
1870
  const centre = anchor.x + anchor.width / 2;
1714
- useLayoutEffect2(() => {
1871
+ useLayoutEffect3(() => {
1715
1872
  const el = ref.current;
1716
1873
  if (!el) return;
1717
1874
  const width = el.offsetWidth;
@@ -1727,6 +1884,13 @@ function toCanvasRect(rect) {
1727
1884
 
1728
1885
  // src/react/registry.ts
1729
1886
  function defineCanvasKind(kind) {
1887
+ const surface = kind.surface;
1888
+ if (surface !== "dom" && surface !== "frame" && surface !== "native") {
1889
+ throw new Error(`[@ai-matrx/canvas] kind "${kind.id}" must declare surface: "dom" | "frame" | "native".`);
1890
+ }
1891
+ if (kind.body === "native" && surface !== "native") {
1892
+ throw new Error(`[@ai-matrx/canvas] kind "${kind.id}" has a native body, so its surface is "native".`);
1893
+ }
1730
1894
  if (!kind.component && !kind.load && kind.body !== "native" && !kind.unavailable) {
1731
1895
  throw new Error(`[@ai-matrx/canvas] kind "${kind.id}" needs a component, a load(), a native body or an unavailable() state.`);
1732
1896
  }
@@ -1840,15 +2004,16 @@ function CanvasProvider({
1840
2004
  dockBack,
1841
2005
  showMenu,
1842
2006
  overlay,
1843
- onNativeBodyFrame
2007
+ onNativeBodyFrame,
2008
+ output
1844
2009
  }) {
1845
2010
  const hostPorts = useMemo(
1846
- () => ({ itemActions, popOut, dockBack, showMenu, overlay, onNativeBodyFrame }),
1847
- [itemActions, popOut, dockBack, showMenu, overlay, onNativeBodyFrame]
2011
+ () => ({ itemActions, popOut, dockBack, showMenu, overlay, onNativeBodyFrame, output }),
2012
+ [itemActions, popOut, dockBack, showMenu, overlay, onNativeBodyFrame, output]
1848
2013
  );
1849
- const portsRef = useRef3(hostPorts);
2014
+ const portsRef = useRef4(hostPorts);
1850
2015
  portsRef.current = hostPorts;
1851
- const [{ controller, layer, launcherEntries, itemMenus }] = useState2(() => ({
2016
+ const [{ controller, layer, launcherEntries, itemMenus, itemOutputs }] = useState2(() => ({
1852
2017
  controller: createCanvasController({
1853
2018
  store: store ?? createCanvasStore(),
1854
2019
  persistence: persistence === void 0 ? createLocalStorageCanvasPersistence() : persistence,
@@ -1863,11 +2028,12 @@ function CanvasProvider({
1863
2028
  }),
1864
2029
  layer: createCanvasLayer(() => portsRef.current),
1865
2030
  launcherEntries: createLauncherEntryStore(),
1866
- itemMenus: createCanvasItemMenus()
2031
+ itemMenus: createCanvasItemMenus(),
2032
+ itemOutputs: createCanvasItemOutputs()
1867
2033
  }));
1868
2034
  const windowId = controller.windowId;
1869
- useEffect3(() => controller.start(), [controller]);
1870
- useEffect3(() => {
2035
+ useEffect4(() => controller.start(), [controller]);
2036
+ useEffect4(() => {
1871
2037
  if (!hotkeys) return;
1872
2038
  const isMac3 = isMacPlatform();
1873
2039
  const applies = (when, event) => {
@@ -1889,8 +2055,8 @@ function CanvasProvider({
1889
2055
  return () => window.removeEventListener("keydown", onKey);
1890
2056
  }, [controller, hotkeys, windowId]);
1891
2057
  const value = useMemo(
1892
- () => ({ controller, windowId, ports: hostPorts, layer, regionWidth, tabStrip, launcherEntries, itemMenus }),
1893
- [controller, windowId, hostPorts, layer, regionWidth, tabStrip, launcherEntries, itemMenus]
2058
+ () => ({ controller, windowId, ports: hostPorts, layer, regionWidth, tabStrip, launcherEntries, itemMenus, itemOutputs }),
2059
+ [controller, windowId, hostPorts, layer, regionWidth, tabStrip, launcherEntries, itemMenus, itemOutputs]
1894
2060
  );
1895
2061
  return /* @__PURE__ */ jsxs(CanvasContext.Provider, { value, children: [
1896
2062
  children,
@@ -1935,7 +2101,7 @@ function useRegisterCanvasPresence() {
1935
2101
  const context = useContext(CanvasContext);
1936
2102
  const controller = context?.controller;
1937
2103
  const windowId = context?.windowId;
1938
- useEffect3(() => controller?.registerPresentation(windowId), [controller, windowId]);
2104
+ useEffect4(() => controller?.registerPresentation(windowId), [controller, windowId]);
1939
2105
  }
1940
2106
  var noPresence = () => () => void 0;
1941
2107
  function useCanvasIsPresented() {
@@ -1986,7 +2152,7 @@ function useCanvasLauncherEntry(entry) {
1986
2152
  const store = useContext(CanvasContext)?.launcherEntries;
1987
2153
  const fingerprint = JSON.stringify([entry.kind, entry.key, entry.title ?? null, entry.data ?? null]);
1988
2154
  const icon = entry.icon;
1989
- useEffect3(() => {
2155
+ useEffect4(() => {
1990
2156
  if (!store) return void 0;
1991
2157
  const [kind, key, title, data] = JSON.parse(fingerprint);
1992
2158
  return store.add({ kind, key, title, data, ...icon ? { icon } : {} });
@@ -2008,13 +2174,13 @@ function useCanvasLauncherEntries() {
2008
2174
 
2009
2175
  // src/react/CanvasColumn.tsx
2010
2176
  import {
2011
- useEffect as useEffect8,
2012
- useLayoutEffect as useLayoutEffect6,
2013
- useRef as useRef8,
2177
+ useEffect as useEffect9,
2178
+ useLayoutEffect as useLayoutEffect7,
2179
+ useRef as useRef9,
2014
2180
  useState as useState6,
2015
2181
  useSyncExternalStore as useSyncExternalStore4
2016
2182
  } from "react";
2017
- import { TapTargetButton, TapTargetButtonTransparent } from "@ai-matrx/tap-target";
2183
+ import { TapTargetButton, TapTargetButtonTransparent } from "@ai-matrx/design-system/tap-target";
2018
2184
 
2019
2185
  // src/react/bodies.tsx
2020
2186
  import {
@@ -2022,19 +2188,19 @@ import {
2022
2188
  Suspense,
2023
2189
  lazy,
2024
2190
  memo,
2025
- useEffect as useEffect6,
2026
- useLayoutEffect as useLayoutEffect4,
2191
+ useEffect as useEffect7,
2192
+ useLayoutEffect as useLayoutEffect5,
2027
2193
  useMemo as useMemo2,
2028
- useRef as useRef6,
2194
+ useRef as useRef7,
2029
2195
  useState as useState4
2030
2196
  } from "react";
2031
2197
 
2032
2198
  // src/react/chrome.tsx
2033
2199
  import {
2034
2200
  forwardRef,
2035
- useEffect as useEffect4,
2036
- useLayoutEffect as useLayoutEffect3,
2037
- useRef as useRef4,
2201
+ useEffect as useEffect5,
2202
+ useLayoutEffect as useLayoutEffect4,
2203
+ useRef as useRef5,
2038
2204
  useState as useState3
2039
2205
  } from "react";
2040
2206
 
@@ -2138,11 +2304,11 @@ var ChevronDownIcon = (p) => /* @__PURE__ */ jsx3(Glyph, { ...p, children: /* @_
2138
2304
 
2139
2305
  // src/react/chrome.tsx
2140
2306
  import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
2141
- var useIsoLayoutEffect2 = typeof window === "undefined" ? useEffect4 : useLayoutEffect3;
2307
+ var useIsoLayoutEffect3 = typeof window === "undefined" ? useEffect5 : useLayoutEffect4;
2142
2308
  function useTruncated(title) {
2143
- const ref = useRef4(null);
2309
+ const ref = useRef5(null);
2144
2310
  const [truncated, setTruncated] = useState3(false);
2145
- useIsoLayoutEffect2(() => {
2311
+ useIsoLayoutEffect3(() => {
2146
2312
  const el = ref.current;
2147
2313
  if (!el) return void 0;
2148
2314
  const check = () => setTruncated(el.scrollWidth - el.clientWidth > 1);
@@ -2156,15 +2322,15 @@ function useTruncated(title) {
2156
2322
  var cx = (...names) => names.filter(Boolean).join(" ");
2157
2323
  function useTooltip(text) {
2158
2324
  const internals = useOptionalCanvasInternals();
2159
- const timer = useRef4(null);
2160
- const hide = useRef4(null);
2325
+ const timer = useRef5(null);
2326
+ const hide = useRef5(null);
2161
2327
  const close = () => {
2162
2328
  if (timer.current) clearTimeout(timer.current);
2163
2329
  timer.current = null;
2164
2330
  hide.current?.();
2165
2331
  hide.current = null;
2166
2332
  };
2167
- useEffect4(() => close, []);
2333
+ useEffect5(() => close, []);
2168
2334
  if (!text) return { close, handlers: {} };
2169
2335
  if (!internals) return { close, handlers: { title: text } };
2170
2336
  const open = (element, delay) => {
@@ -2361,7 +2527,7 @@ function BrowserTabs({ tabs, activeId, onSelect, onClose, onNew, newLabel = "New
2361
2527
  function BrowserBar({ url, canGoBack, canGoForward, onBack, onForward, onReload, onNavigate, trailing, className }) {
2362
2528
  const [draft, setDraft] = useState3(url);
2363
2529
  const [editing, setEditing] = useState3(false);
2364
- useEffect4(() => {
2530
+ useEffect5(() => {
2365
2531
  if (!editing) setDraft(url);
2366
2532
  }, [url, editing]);
2367
2533
  return /* @__PURE__ */ jsxs3("div", { "data-slot": "browser-bar", className: cx("mxc-browser-bar", className), children: [
@@ -2403,15 +2569,15 @@ function BrowserBar({ url, canGoBack, canGoForward, onBack, onForward, onReload,
2403
2569
  }
2404
2570
 
2405
2571
  // src/react/dnd.ts
2406
- import { useEffect as useEffect5, useRef as useRef5 } from "react";
2572
+ import { useEffect as useEffect6, useRef as useRef6 } from "react";
2407
2573
  var CANVAS_DRAG_MIME = "application/x-matrx-canvas-item";
2408
2574
  function useTabDrag(itemId, title) {
2409
2575
  const { layer } = useCanvasInternals();
2410
- const active = useRef5([]);
2576
+ const active = useRef6([]);
2411
2577
  const end = () => {
2412
2578
  for (const dispose of active.current.splice(0)) dispose();
2413
2579
  };
2414
- useEffect5(() => end, []);
2580
+ useEffect6(() => end, []);
2415
2581
  return {
2416
2582
  draggable: true,
2417
2583
  onDragStart: (event) => {
@@ -2426,8 +2592,8 @@ function useTabDrag(itemId, title) {
2426
2592
  function usePaneDrop(paneId, getOutline) {
2427
2593
  const canvas = useCanvas();
2428
2594
  const { layer } = useCanvasInternals();
2429
- const drop = useRef5(null);
2430
- useEffect5(() => () => drop.current?.(), []);
2595
+ const drop = useRef6(null);
2596
+ useEffect6(() => () => drop.current?.(), []);
2431
2597
  const hide = () => {
2432
2598
  drop.current?.();
2433
2599
  drop.current = null;
@@ -2550,6 +2716,10 @@ function EmptyPaneLauncher({ paneId }) {
2550
2716
  }
2551
2717
  var CANVAS_LAUNCHER_KIND_DEFINITION = {
2552
2718
  id: CANVAS_LAUNCHER_KIND,
2719
+ surface: "dom",
2720
+ // A launcher is a menu of things to open — nothing to print or capture.
2721
+ print: false,
2722
+ capture: false,
2553
2723
  label: "New tab",
2554
2724
  icon: LayoutGridIcon,
2555
2725
  component: LauncherBody
@@ -2659,7 +2829,7 @@ function useSlotSize(slots, paneId) {
2659
2829
 
2660
2830
  // src/react/bodies.tsx
2661
2831
  import { Fragment, jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
2662
- var useIsoLayoutEffect3 = typeof window === "undefined" ? useEffect6 : useLayoutEffect4;
2832
+ var useIsoLayoutEffect4 = typeof window === "undefined" ? useEffect7 : useLayoutEffect5;
2663
2833
  var LAZY = /* @__PURE__ */ Symbol.for("ai-matrx.canvas.lazy");
2664
2834
  function kindComponent(kind) {
2665
2835
  if (kind.component) return kind.component;
@@ -2779,12 +2949,12 @@ function CanvasBodyLayer() {
2779
2949
  const slots = useCanvasSlots();
2780
2950
  const signature = useCanvasState((s) => layerSignature(s, windowId));
2781
2951
  const single = useCanvasSinglePane();
2782
- const root = useRef6(null);
2952
+ const root = useRef7(null);
2783
2953
  const [placement] = useState4(
2784
2954
  () => slots ? createPlacement(() => root.current, slots, () => layer.native.remeasure()) : null
2785
2955
  );
2786
- useIsoLayoutEffect3(() => placement?.place());
2787
- useIsoLayoutEffect3(() => slots && placement ? slots.subscribe(placement.place) : void 0, [slots, placement]);
2956
+ useIsoLayoutEffect4(() => placement?.place());
2957
+ useIsoLayoutEffect4(() => slots && placement ? slots.subscribe(placement.place) : void 0, [slots, placement]);
2788
2958
  const shape = parseSignature(signature);
2789
2959
  if (!slots || !placement || !shape) return null;
2790
2960
  return /* @__PURE__ */ jsx6("div", { ref: root, className: "mxc-bodies", "data-mxc-bodies": windowId, children: shape.placed.map(({ hostId, itemId, paneId, active, kept }) => /* @__PURE__ */ jsx6(
@@ -2860,17 +3030,23 @@ var BodyHost = memo(function BodyHost2({
2860
3030
  const keptItem = useMemo2(() => step ? stepAsItem(step, owner ?? 0) : void 0, [step, owner]);
2861
3031
  const item = kept ? keptItem : live;
2862
3032
  const kind = useItemKind(item?.kind ?? "");
2863
- const element = useRef6(null);
3033
+ const element = useRef7(null);
2864
3034
  const [everShown, setEverShown] = useState4(visible);
2865
3035
  if (visible && !everShown) setEverShown(true);
2866
3036
  const mounted = kind?.unmountWhenHidden ? visible : everShown;
2867
3037
  const presentation = usePanePresentation(slots, paneId, isFullscreen, paneCount);
2868
3038
  const drop = usePaneDrop(paneId, () => slots.section(paneId));
2869
- useIsoLayoutEffect3(() => {
3039
+ useIsoLayoutEffect4(() => {
2870
3040
  const el = element.current;
2871
3041
  if (!placement || !el) return void 0;
2872
3042
  return placement.register(hostId, { element: el, paneId });
2873
3043
  }, [placement, hostId, paneId]);
3044
+ const domSurface = kind?.surface === "dom" && mounted && !kept;
3045
+ useEffect7(() => {
3046
+ if (process.env.NODE_ENV === "production" || !domSurface || !kind) return void 0;
3047
+ const el = element.current;
3048
+ return el ? watchDomSurface(el, kind.id) : void 0;
3049
+ }, [domSurface, kind]);
2874
3050
  if (!item) return null;
2875
3051
  return /* @__PURE__ */ jsx6(
2876
3052
  "div",
@@ -2953,7 +3129,7 @@ var Rendered = memo(function Rendered2({
2953
3129
  var CANVAS_BODY_STALL_MS = 2e4;
2954
3130
  function LoadingBody({ onRetry, onClose }) {
2955
3131
  const [stalled, setStalled] = useState4(false);
2956
- useEffect6(() => {
3132
+ useEffect7(() => {
2957
3133
  const timer = setTimeout(() => setStalled(true), CANVAS_BODY_STALL_MS);
2958
3134
  return () => clearTimeout(timer);
2959
3135
  }, []);
@@ -2967,8 +3143,8 @@ function KindRender({ kind, props }) {
2967
3143
  }
2968
3144
  function NativeBody({ itemId, windowId, visible, children }) {
2969
3145
  const { layer } = useCanvasInternals();
2970
- const ref = useRef6(null);
2971
- const measure = useRef6(() => void 0);
3146
+ const ref = useRef7(null);
3147
+ const measure = useRef7(() => void 0);
2972
3148
  measure.current = () => {
2973
3149
  const el = ref.current;
2974
3150
  const box = visible && el?.isConnected ? el.getBoundingClientRect() : null;
@@ -2979,8 +3155,8 @@ function NativeBody({ itemId, windowId, visible, children }) {
2979
3155
  const rect = { ...toCanvasRect(box), devicePixelRatio: window.devicePixelRatio || 1 };
2980
3156
  layer.native.report(itemId, rect, { visible: !layer.isBlocked(), windowId });
2981
3157
  };
2982
- useIsoLayoutEffect3(() => measure.current());
2983
- useIsoLayoutEffect3(() => {
3158
+ useIsoLayoutEffect4(() => measure.current());
3159
+ useIsoLayoutEffect4(() => {
2984
3160
  const el = ref.current;
2985
3161
  const run = () => measure.current();
2986
3162
  const observer = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(run);
@@ -3050,10 +3226,10 @@ function beginCanvasResize(mode) {
3050
3226
  }
3051
3227
 
3052
3228
  // src/react/CanvasPane.tsx
3053
- import { Component as Component2, useEffect as useEffect7, useLayoutEffect as useLayoutEffect5, useRef as useRef7, useState as useState5 } from "react";
3229
+ import { Component as Component2, useEffect as useEffect8, useLayoutEffect as useLayoutEffect6, useRef as useRef8, useState as useState5 } from "react";
3054
3230
  import * as Menu from "@radix-ui/react-dropdown-menu";
3055
3231
  import { Fragment as Fragment2, jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
3056
- var useIsoLayoutEffect4 = typeof window === "undefined" ? useEffect7 : useLayoutEffect5;
3232
+ var useIsoLayoutEffect5 = typeof window === "undefined" ? useEffect8 : useLayoutEffect6;
3057
3233
  function itemTitle(item, kind) {
3058
3234
  if (item.title) return item.title;
3059
3235
  if (item.kind === CANVAS_LAUNCHER_KIND) return "New tab";
@@ -3122,11 +3298,11 @@ function CanvasPaneView({ paneId }) {
3122
3298
  const singlePane = useCanvasSinglePane();
3123
3299
  const inWindowStrip = useCanvasWindowTabs() && !singlePane;
3124
3300
  const windowTabs = useCanvasState((s) => singlePane ? windowTabsSignature(s, windowId) : "");
3125
- const sectionRef = useRef7(null);
3126
- const bodyRef = useRef7(null);
3301
+ const sectionRef = useRef8(null);
3302
+ const bodyRef = useRef8(null);
3127
3303
  const presentation = usePanePresentation(slots, paneId, isFullscreen, paneCount);
3128
3304
  const drop = usePaneDrop(paneId, () => sectionRef.current);
3129
- useIsoLayoutEffect4(() => {
3305
+ useIsoLayoutEffect5(() => {
3130
3306
  const body = bodyRef.current;
3131
3307
  const section = sectionRef.current;
3132
3308
  if (body) slots.set(paneId, body);
@@ -3303,14 +3479,14 @@ function TabStrip({
3303
3479
  actives
3304
3480
  }) {
3305
3481
  const canvas = useCanvas();
3306
- const strip = useRef7(null);
3307
- const list = useRef7(null);
3482
+ const strip = useRef8(null);
3483
+ const list = useRef8(null);
3308
3484
  const [width, setWidth] = useState5(0);
3309
- const windowStart = useRef7(0);
3310
- const focusAfterMove = useRef7(false);
3311
- const read = useRef7(() => {
3485
+ const windowStart = useRef8(0);
3486
+ const focusAfterMove = useRef8(false);
3487
+ const read = useRef8(() => {
3312
3488
  });
3313
- useIsoLayoutEffect4(() => {
3489
+ useIsoLayoutEffect5(() => {
3314
3490
  const el = strip.current;
3315
3491
  if (!el) return void 0;
3316
3492
  let live = true;
@@ -3329,10 +3505,10 @@ function TabStrip({
3329
3505
  if (frame2 && typeof cancelAnimationFrame !== "undefined") cancelAnimationFrame(frame2);
3330
3506
  };
3331
3507
  }, []);
3332
- useIsoLayoutEffect4(() => {
3508
+ useIsoLayoutEffect5(() => {
3333
3509
  read.current();
3334
3510
  }, [tabs.length]);
3335
- useIsoLayoutEffect4(() => {
3511
+ useIsoLayoutEffect5(() => {
3336
3512
  if (!focusAfterMove.current || !activeItemId) return;
3337
3513
  focusAfterMove.current = false;
3338
3514
  tabElement(list.current, activeItemId)?.focus();
@@ -3349,7 +3525,7 @@ function TabStrip({
3349
3525
  width,
3350
3526
  windowStart.current
3351
3527
  );
3352
- useIsoLayoutEffect4(() => {
3528
+ useIsoLayoutEffect5(() => {
3353
3529
  windowStart.current = fit.start;
3354
3530
  });
3355
3531
  const shown = entries.slice(fit.start, fit.end);
@@ -3429,8 +3605,8 @@ function tabElement(list, itemId) {
3429
3605
  function TabListMenu({ tabs, activeItemId, hidden }) {
3430
3606
  const canvas = useCanvas();
3431
3607
  const { ports, layer } = useCanvasInternals();
3432
- const blocking = useRef7(null);
3433
- useEffect7(() => () => blocking.current?.(), []);
3608
+ const blocking = useRef8(null);
3609
+ useEffect8(() => () => blocking.current?.(), []);
3434
3610
  const label = `All tabs (${tabs.length})`;
3435
3611
  const trigger = (props) => /* @__PURE__ */ jsxs6(CanvasHeaderButton, { className: "mxc-tab-list", label, ...props, children: [
3436
3612
  /* @__PURE__ */ jsx7("span", { className: "mxc-tab-list-count", "aria-hidden": "true", children: hidden }),
@@ -3542,16 +3718,26 @@ function PaneMenu({
3542
3718
  canSplit
3543
3719
  }) {
3544
3720
  const canvas = useCanvas();
3545
- const { ports, layer, itemMenus } = useCanvasInternals();
3721
+ const { ports, layer, itemMenus, itemOutputs } = useCanvasInternals();
3546
3722
  const moveId = tabCount > 1 ? activeItem?.id : void 0;
3547
- const blocking = useRef7(null);
3548
- useEffect7(() => () => blocking.current?.(), []);
3723
+ const blocking = useRef8(null);
3724
+ useEffect8(() => () => blocking.current?.(), []);
3549
3725
  const buildSections = () => {
3550
3726
  const kindItems = kind?.menuItems && kindProps ? safeMenu(() => kind.menuItems?.(kindProps)) : [];
3551
3727
  const hostItems = activeItem && ports.itemActions ? safeMenu(() => ports.itemActions?.(activeItem, kind)) : [];
3552
3728
  const bodyItems = activeItem ? itemMenus.read(activeItem.id) : [];
3729
+ const outputItems = activeItem ? safeMenu(
3730
+ () => canvasOutputMenu({
3731
+ item: activeItem,
3732
+ kind,
3733
+ title: itemTitle(activeItem, kind),
3734
+ body: itemOutputs.read(activeItem.id),
3735
+ ports: ports.output
3736
+ })
3737
+ ) : [];
3553
3738
  return [
3554
3739
  [...kindItems, ...bodyItems, ...hostItems].map((entry) => ({ ...entry, id: `item:${entry.id}` })),
3740
+ outputItems.map((entry) => ({ ...entry, id: `item:${entry.id}` })),
3555
3741
  [
3556
3742
  // Only a split that leaves both panes usable (the layout rules' minimums) is offered.
3557
3743
  ...canSplit && canvas.fitSplit(paneId, "horizontal") === "horizontal" ? [{ id: "split-right", label: "Split right", icon: /* @__PURE__ */ jsx7(SplitRightIcon, {}), onSelect: () => canvas.splitPane(paneId, "horizontal", moveId) }] : [],
@@ -3575,8 +3761,14 @@ function PaneMenu({
3575
3761
  buildSections().forEach((section, i) => {
3576
3762
  if (i > 0) entries.push({ type: "separator" });
3577
3763
  for (const action of section) {
3578
- entries.push({ type: "item", id: action.id, label: action.label, ...action.destructive ? { destructive: true } : {} });
3579
- handlers.set(action.id, action.onSelect);
3764
+ entries.push({
3765
+ type: "item",
3766
+ id: action.id,
3767
+ label: action.label,
3768
+ ...action.destructive ? { destructive: true } : {},
3769
+ ...action.disabled ? { disabled: true } : {}
3770
+ });
3771
+ if (!action.disabled) handlers.set(action.id, action.onSelect);
3580
3772
  }
3581
3773
  });
3582
3774
  const anchor = toCanvasRect(event.currentTarget.getBoundingClientRect());
@@ -3610,10 +3802,20 @@ function MenuSections({ build }) {
3610
3802
  function MenuSection({ first, actions }) {
3611
3803
  return /* @__PURE__ */ jsxs6(Fragment2, { children: [
3612
3804
  first ? null : /* @__PURE__ */ jsx7(Menu.Separator, { className: "mxc-menu-separator" }),
3613
- actions.map((action) => /* @__PURE__ */ jsxs6(Menu.Item, { onSelect: action.onSelect, className: "mxc-menu-item", "data-destructive": action.destructive ? "" : void 0, children: [
3614
- action.icon,
3615
- action.label
3616
- ] }, action.id))
3805
+ actions.map((action) => /* @__PURE__ */ jsxs6(
3806
+ Menu.Item,
3807
+ {
3808
+ onSelect: action.onSelect,
3809
+ disabled: action.disabled ?? false,
3810
+ className: "mxc-menu-item",
3811
+ "data-destructive": action.destructive ? "" : void 0,
3812
+ children: [
3813
+ action.icon,
3814
+ action.label
3815
+ ]
3816
+ },
3817
+ action.id
3818
+ ))
3617
3819
  ] });
3618
3820
  }
3619
3821
  function safeMenu(build) {
@@ -3627,7 +3829,7 @@ function safeMenu(build) {
3627
3829
 
3628
3830
  // src/react/CanvasColumn.tsx
3629
3831
  import { Fragment as Fragment3, jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
3630
- var useIsoLayoutEffect5 = typeof window === "undefined" ? useEffect8 : useLayoutEffect6;
3832
+ var useIsoLayoutEffect6 = typeof window === "undefined" ? useEffect9 : useLayoutEffect7;
3631
3833
  function subscribeViewport(listener) {
3632
3834
  window.addEventListener("resize", listener);
3633
3835
  return () => window.removeEventListener("resize", listener);
@@ -3662,9 +3864,9 @@ function CanvasColumn({ className, style, onLiveWidth, placement = "docked" }) {
3662
3864
  const singlePane = placement === "docked" && viewport > 0 && viewport <= CANVAS_PHONE_MAX_WIDTH;
3663
3865
  const [dragWidth, setDragWidth] = useState6(null);
3664
3866
  const [slots] = useState6(createCanvasSlots);
3665
- const ref = useRef8(null);
3867
+ const ref = useRef9(null);
3666
3868
  useRegisterCanvasPresence();
3667
- useEffect8(
3869
+ useEffect9(
3668
3870
  () => controller.registerPaneMeasure((paneId) => {
3669
3871
  const section = slots.section(paneId);
3670
3872
  if (!section?.isConnected) return null;
@@ -3673,7 +3875,7 @@ function CanvasColumn({ className, style, onLiveWidth, placement = "docked" }) {
3673
3875
  }),
3674
3876
  [controller, slots]
3675
3877
  );
3676
- useEffect8(() => {
3878
+ useEffect9(() => {
3677
3879
  const el = ref.current;
3678
3880
  if (!el) return void 0;
3679
3881
  let frame2 = 0;
@@ -3747,8 +3949,8 @@ function CanvasColumn({ className, style, onLiveWidth, placement = "docked" }) {
3747
3949
  }
3748
3950
  function WidthHandle({ width, region, onDrag }) {
3749
3951
  const canvas = useCanvas();
3750
- const start = useRef8(null);
3751
- useEffect8(() => () => start.current?.end(), []);
3952
+ const start = useRef9(null);
3953
+ useEffect9(() => () => start.current?.end(), []);
3752
3954
  const clamp = (next) => clampDraggedCanvasWidth(next, region, canvas.rules);
3753
3955
  const onPointerDown = (event) => {
3754
3956
  event.preventDefault();
@@ -3796,7 +3998,7 @@ function LayoutNodeView({ node }) {
3796
3998
  }
3797
3999
  function useMeasuredSize(ref, enabled) {
3798
4000
  const [size, setSize] = useState6({ width: 0, height: 0 });
3799
- useIsoLayoutEffect5(() => {
4001
+ useIsoLayoutEffect6(() => {
3800
4002
  const el = ref.current;
3801
4003
  if (!enabled || !el) return;
3802
4004
  const read = () => {
@@ -3822,7 +4024,7 @@ function childMinimums(node, orientation, rules) {
3822
4024
  }
3823
4025
  function SplitView({ node }) {
3824
4026
  const canvas = useCanvas();
3825
- const ref = useRef8(null);
4027
+ const ref = useRef9(null);
3826
4028
  const [liveSizes, setLiveSizes] = useState6(null);
3827
4029
  const measured = useMeasuredSize(ref, canvas.rules.paneMinWidth > 0 || canvas.rules.paneMinHeight > 0);
3828
4030
  const stacked = node.orientation === "horizontal" && shouldStackSplit(measured.width, node.children.length, canvas.rules);
@@ -3977,6 +4179,10 @@ export {
3977
4179
  CanvasWindowScope,
3978
4180
  CanvasWindowTabs,
3979
4181
  beginCanvasResize,
4182
+ canvasItemElement,
4183
+ canvasKindOutputProblems,
4184
+ canvasKindSurface,
4185
+ canvasOutputMenu,
3980
4186
  canvasSessionKey,
3981
4187
  defineCanvasKind,
3982
4188
  fitCanvasTabWindow,
@@ -3997,6 +4203,7 @@ export {
3997
4203
  useCanvasLauncherEntry,
3998
4204
  useCanvasMenuItems,
3999
4205
  useCanvasNativeBodies,
4206
+ useCanvasOutput,
4000
4207
  useCanvasPresentation,
4001
4208
  useCanvasState,
4002
4209
  useCanvasWindowId,