@ai-matrx/canvas 0.9.3 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/react.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
  }
@@ -1846,9 +2010,9 @@ function CanvasProvider({
1846
2010
  () => ({ itemActions, popOut, dockBack, showMenu, overlay, onNativeBodyFrame }),
1847
2011
  [itemActions, popOut, dockBack, showMenu, overlay, onNativeBodyFrame]
1848
2012
  );
1849
- const portsRef = useRef3(hostPorts);
2013
+ const portsRef = useRef4(hostPorts);
1850
2014
  portsRef.current = hostPorts;
1851
- const [{ controller, layer, launcherEntries, itemMenus }] = useState2(() => ({
2015
+ const [{ controller, layer, launcherEntries, itemMenus, itemOutputs }] = useState2(() => ({
1852
2016
  controller: createCanvasController({
1853
2017
  store: store ?? createCanvasStore(),
1854
2018
  persistence: persistence === void 0 ? createLocalStorageCanvasPersistence() : persistence,
@@ -1863,11 +2027,12 @@ function CanvasProvider({
1863
2027
  }),
1864
2028
  layer: createCanvasLayer(() => portsRef.current),
1865
2029
  launcherEntries: createLauncherEntryStore(),
1866
- itemMenus: createCanvasItemMenus()
2030
+ itemMenus: createCanvasItemMenus(),
2031
+ itemOutputs: createCanvasItemOutputs()
1867
2032
  }));
1868
2033
  const windowId = controller.windowId;
1869
- useEffect3(() => controller.start(), [controller]);
1870
- useEffect3(() => {
2034
+ useEffect4(() => controller.start(), [controller]);
2035
+ useEffect4(() => {
1871
2036
  if (!hotkeys) return;
1872
2037
  const isMac3 = isMacPlatform();
1873
2038
  const applies = (when, event) => {
@@ -1889,8 +2054,8 @@ function CanvasProvider({
1889
2054
  return () => window.removeEventListener("keydown", onKey);
1890
2055
  }, [controller, hotkeys, windowId]);
1891
2056
  const value = useMemo(
1892
- () => ({ controller, windowId, ports: hostPorts, layer, regionWidth, tabStrip, launcherEntries, itemMenus }),
1893
- [controller, windowId, hostPorts, layer, regionWidth, tabStrip, launcherEntries, itemMenus]
2057
+ () => ({ controller, windowId, ports: hostPorts, layer, regionWidth, tabStrip, launcherEntries, itemMenus, itemOutputs }),
2058
+ [controller, windowId, hostPorts, layer, regionWidth, tabStrip, launcherEntries, itemMenus, itemOutputs]
1894
2059
  );
1895
2060
  return /* @__PURE__ */ jsxs(CanvasContext.Provider, { value, children: [
1896
2061
  children,
@@ -1935,7 +2100,7 @@ function useRegisterCanvasPresence() {
1935
2100
  const context = useContext(CanvasContext);
1936
2101
  const controller = context?.controller;
1937
2102
  const windowId = context?.windowId;
1938
- useEffect3(() => controller?.registerPresentation(windowId), [controller, windowId]);
2103
+ useEffect4(() => controller?.registerPresentation(windowId), [controller, windowId]);
1939
2104
  }
1940
2105
  var noPresence = () => () => void 0;
1941
2106
  function useCanvasIsPresented() {
@@ -1986,7 +2151,7 @@ function useCanvasLauncherEntry(entry) {
1986
2151
  const store = useContext(CanvasContext)?.launcherEntries;
1987
2152
  const fingerprint = JSON.stringify([entry.kind, entry.key, entry.title ?? null, entry.data ?? null]);
1988
2153
  const icon = entry.icon;
1989
- useEffect3(() => {
2154
+ useEffect4(() => {
1990
2155
  if (!store) return void 0;
1991
2156
  const [kind, key, title, data] = JSON.parse(fingerprint);
1992
2157
  return store.add({ kind, key, title, data, ...icon ? { icon } : {} });
@@ -2008,13 +2173,13 @@ function useCanvasLauncherEntries() {
2008
2173
 
2009
2174
  // src/react/CanvasColumn.tsx
2010
2175
  import {
2011
- useEffect as useEffect8,
2012
- useLayoutEffect as useLayoutEffect6,
2013
- useRef as useRef8,
2176
+ useEffect as useEffect9,
2177
+ useLayoutEffect as useLayoutEffect7,
2178
+ useRef as useRef9,
2014
2179
  useState as useState6,
2015
2180
  useSyncExternalStore as useSyncExternalStore4
2016
2181
  } from "react";
2017
- import { TapTargetButton, TapTargetButtonTransparent } from "@ai-matrx/tap-target";
2182
+ import { TapTargetButton, TapTargetButtonTransparent } from "@ai-matrx/design-system/tap-target";
2018
2183
 
2019
2184
  // src/react/bodies.tsx
2020
2185
  import {
@@ -2022,19 +2187,19 @@ import {
2022
2187
  Suspense,
2023
2188
  lazy,
2024
2189
  memo,
2025
- useEffect as useEffect6,
2026
- useLayoutEffect as useLayoutEffect4,
2190
+ useEffect as useEffect7,
2191
+ useLayoutEffect as useLayoutEffect5,
2027
2192
  useMemo as useMemo2,
2028
- useRef as useRef6,
2193
+ useRef as useRef7,
2029
2194
  useState as useState4
2030
2195
  } from "react";
2031
2196
 
2032
2197
  // src/react/chrome.tsx
2033
2198
  import {
2034
2199
  forwardRef,
2035
- useEffect as useEffect4,
2036
- useLayoutEffect as useLayoutEffect3,
2037
- useRef as useRef4,
2200
+ useEffect as useEffect5,
2201
+ useLayoutEffect as useLayoutEffect4,
2202
+ useRef as useRef5,
2038
2203
  useState as useState3
2039
2204
  } from "react";
2040
2205
 
@@ -2138,11 +2303,11 @@ var ChevronDownIcon = (p) => /* @__PURE__ */ jsx3(Glyph, { ...p, children: /* @_
2138
2303
 
2139
2304
  // src/react/chrome.tsx
2140
2305
  import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
2141
- var useIsoLayoutEffect2 = typeof window === "undefined" ? useEffect4 : useLayoutEffect3;
2306
+ var useIsoLayoutEffect3 = typeof window === "undefined" ? useEffect5 : useLayoutEffect4;
2142
2307
  function useTruncated(title) {
2143
- const ref = useRef4(null);
2308
+ const ref = useRef5(null);
2144
2309
  const [truncated, setTruncated] = useState3(false);
2145
- useIsoLayoutEffect2(() => {
2310
+ useIsoLayoutEffect3(() => {
2146
2311
  const el = ref.current;
2147
2312
  if (!el) return void 0;
2148
2313
  const check = () => setTruncated(el.scrollWidth - el.clientWidth > 1);
@@ -2156,15 +2321,15 @@ function useTruncated(title) {
2156
2321
  var cx = (...names) => names.filter(Boolean).join(" ");
2157
2322
  function useTooltip(text) {
2158
2323
  const internals = useOptionalCanvasInternals();
2159
- const timer = useRef4(null);
2160
- const hide = useRef4(null);
2324
+ const timer = useRef5(null);
2325
+ const hide = useRef5(null);
2161
2326
  const close = () => {
2162
2327
  if (timer.current) clearTimeout(timer.current);
2163
2328
  timer.current = null;
2164
2329
  hide.current?.();
2165
2330
  hide.current = null;
2166
2331
  };
2167
- useEffect4(() => close, []);
2332
+ useEffect5(() => close, []);
2168
2333
  if (!text) return { close, handlers: {} };
2169
2334
  if (!internals) return { close, handlers: { title: text } };
2170
2335
  const open = (element, delay) => {
@@ -2361,7 +2526,7 @@ function BrowserTabs({ tabs, activeId, onSelect, onClose, onNew, newLabel = "New
2361
2526
  function BrowserBar({ url, canGoBack, canGoForward, onBack, onForward, onReload, onNavigate, trailing, className }) {
2362
2527
  const [draft, setDraft] = useState3(url);
2363
2528
  const [editing, setEditing] = useState3(false);
2364
- useEffect4(() => {
2529
+ useEffect5(() => {
2365
2530
  if (!editing) setDraft(url);
2366
2531
  }, [url, editing]);
2367
2532
  return /* @__PURE__ */ jsxs3("div", { "data-slot": "browser-bar", className: cx("mxc-browser-bar", className), children: [
@@ -2403,15 +2568,15 @@ function BrowserBar({ url, canGoBack, canGoForward, onBack, onForward, onReload,
2403
2568
  }
2404
2569
 
2405
2570
  // src/react/dnd.ts
2406
- import { useEffect as useEffect5, useRef as useRef5 } from "react";
2571
+ import { useEffect as useEffect6, useRef as useRef6 } from "react";
2407
2572
  var CANVAS_DRAG_MIME = "application/x-matrx-canvas-item";
2408
2573
  function useTabDrag(itemId, title) {
2409
2574
  const { layer } = useCanvasInternals();
2410
- const active = useRef5([]);
2575
+ const active = useRef6([]);
2411
2576
  const end = () => {
2412
2577
  for (const dispose of active.current.splice(0)) dispose();
2413
2578
  };
2414
- useEffect5(() => end, []);
2579
+ useEffect6(() => end, []);
2415
2580
  return {
2416
2581
  draggable: true,
2417
2582
  onDragStart: (event) => {
@@ -2426,8 +2591,8 @@ function useTabDrag(itemId, title) {
2426
2591
  function usePaneDrop(paneId, getOutline) {
2427
2592
  const canvas = useCanvas();
2428
2593
  const { layer } = useCanvasInternals();
2429
- const drop = useRef5(null);
2430
- useEffect5(() => () => drop.current?.(), []);
2594
+ const drop = useRef6(null);
2595
+ useEffect6(() => () => drop.current?.(), []);
2431
2596
  const hide = () => {
2432
2597
  drop.current?.();
2433
2598
  drop.current = null;
@@ -2550,6 +2715,10 @@ function EmptyPaneLauncher({ paneId }) {
2550
2715
  }
2551
2716
  var CANVAS_LAUNCHER_KIND_DEFINITION = {
2552
2717
  id: CANVAS_LAUNCHER_KIND,
2718
+ surface: "dom",
2719
+ // A launcher is a menu of things to open — nothing to print or capture.
2720
+ print: false,
2721
+ capture: false,
2553
2722
  label: "New tab",
2554
2723
  icon: LayoutGridIcon,
2555
2724
  component: LauncherBody
@@ -2659,7 +2828,7 @@ function useSlotSize(slots, paneId) {
2659
2828
 
2660
2829
  // src/react/bodies.tsx
2661
2830
  import { Fragment, jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
2662
- var useIsoLayoutEffect3 = typeof window === "undefined" ? useEffect6 : useLayoutEffect4;
2831
+ var useIsoLayoutEffect4 = typeof window === "undefined" ? useEffect7 : useLayoutEffect5;
2663
2832
  var LAZY = /* @__PURE__ */ Symbol.for("ai-matrx.canvas.lazy");
2664
2833
  function kindComponent(kind) {
2665
2834
  if (kind.component) return kind.component;
@@ -2779,12 +2948,12 @@ function CanvasBodyLayer() {
2779
2948
  const slots = useCanvasSlots();
2780
2949
  const signature = useCanvasState((s) => layerSignature(s, windowId));
2781
2950
  const single = useCanvasSinglePane();
2782
- const root = useRef6(null);
2951
+ const root = useRef7(null);
2783
2952
  const [placement] = useState4(
2784
2953
  () => slots ? createPlacement(() => root.current, slots, () => layer.native.remeasure()) : null
2785
2954
  );
2786
- useIsoLayoutEffect3(() => placement?.place());
2787
- useIsoLayoutEffect3(() => slots && placement ? slots.subscribe(placement.place) : void 0, [slots, placement]);
2955
+ useIsoLayoutEffect4(() => placement?.place());
2956
+ useIsoLayoutEffect4(() => slots && placement ? slots.subscribe(placement.place) : void 0, [slots, placement]);
2788
2957
  const shape = parseSignature(signature);
2789
2958
  if (!slots || !placement || !shape) return null;
2790
2959
  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 +3029,23 @@ var BodyHost = memo(function BodyHost2({
2860
3029
  const keptItem = useMemo2(() => step ? stepAsItem(step, owner ?? 0) : void 0, [step, owner]);
2861
3030
  const item = kept ? keptItem : live;
2862
3031
  const kind = useItemKind(item?.kind ?? "");
2863
- const element = useRef6(null);
3032
+ const element = useRef7(null);
2864
3033
  const [everShown, setEverShown] = useState4(visible);
2865
3034
  if (visible && !everShown) setEverShown(true);
2866
3035
  const mounted = kind?.unmountWhenHidden ? visible : everShown;
2867
3036
  const presentation = usePanePresentation(slots, paneId, isFullscreen, paneCount);
2868
3037
  const drop = usePaneDrop(paneId, () => slots.section(paneId));
2869
- useIsoLayoutEffect3(() => {
3038
+ useIsoLayoutEffect4(() => {
2870
3039
  const el = element.current;
2871
3040
  if (!placement || !el) return void 0;
2872
3041
  return placement.register(hostId, { element: el, paneId });
2873
3042
  }, [placement, hostId, paneId]);
3043
+ const domSurface = kind?.surface === "dom" && mounted && !kept;
3044
+ useEffect7(() => {
3045
+ if (process.env.NODE_ENV === "production" || !domSurface || !kind) return void 0;
3046
+ const el = element.current;
3047
+ return el ? watchDomSurface(el, kind.id) : void 0;
3048
+ }, [domSurface, kind]);
2874
3049
  if (!item) return null;
2875
3050
  return /* @__PURE__ */ jsx6(
2876
3051
  "div",
@@ -2953,7 +3128,7 @@ var Rendered = memo(function Rendered2({
2953
3128
  var CANVAS_BODY_STALL_MS = 2e4;
2954
3129
  function LoadingBody({ onRetry, onClose }) {
2955
3130
  const [stalled, setStalled] = useState4(false);
2956
- useEffect6(() => {
3131
+ useEffect7(() => {
2957
3132
  const timer = setTimeout(() => setStalled(true), CANVAS_BODY_STALL_MS);
2958
3133
  return () => clearTimeout(timer);
2959
3134
  }, []);
@@ -2967,8 +3142,8 @@ function KindRender({ kind, props }) {
2967
3142
  }
2968
3143
  function NativeBody({ itemId, windowId, visible, children }) {
2969
3144
  const { layer } = useCanvasInternals();
2970
- const ref = useRef6(null);
2971
- const measure = useRef6(() => void 0);
3145
+ const ref = useRef7(null);
3146
+ const measure = useRef7(() => void 0);
2972
3147
  measure.current = () => {
2973
3148
  const el = ref.current;
2974
3149
  const box = visible && el?.isConnected ? el.getBoundingClientRect() : null;
@@ -2979,8 +3154,8 @@ function NativeBody({ itemId, windowId, visible, children }) {
2979
3154
  const rect = { ...toCanvasRect(box), devicePixelRatio: window.devicePixelRatio || 1 };
2980
3155
  layer.native.report(itemId, rect, { visible: !layer.isBlocked(), windowId });
2981
3156
  };
2982
- useIsoLayoutEffect3(() => measure.current());
2983
- useIsoLayoutEffect3(() => {
3157
+ useIsoLayoutEffect4(() => measure.current());
3158
+ useIsoLayoutEffect4(() => {
2984
3159
  const el = ref.current;
2985
3160
  const run = () => measure.current();
2986
3161
  const observer = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(run);
@@ -3050,10 +3225,10 @@ function beginCanvasResize(mode) {
3050
3225
  }
3051
3226
 
3052
3227
  // src/react/CanvasPane.tsx
3053
- import { Component as Component2, useEffect as useEffect7, useLayoutEffect as useLayoutEffect5, useRef as useRef7, useState as useState5 } from "react";
3228
+ import { Component as Component2, useEffect as useEffect8, useLayoutEffect as useLayoutEffect6, useRef as useRef8, useState as useState5 } from "react";
3054
3229
  import * as Menu from "@radix-ui/react-dropdown-menu";
3055
3230
  import { Fragment as Fragment2, jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
3056
- var useIsoLayoutEffect4 = typeof window === "undefined" ? useEffect7 : useLayoutEffect5;
3231
+ var useIsoLayoutEffect5 = typeof window === "undefined" ? useEffect8 : useLayoutEffect6;
3057
3232
  function itemTitle(item, kind) {
3058
3233
  if (item.title) return item.title;
3059
3234
  if (item.kind === CANVAS_LAUNCHER_KIND) return "New tab";
@@ -3122,11 +3297,11 @@ function CanvasPaneView({ paneId }) {
3122
3297
  const singlePane = useCanvasSinglePane();
3123
3298
  const inWindowStrip = useCanvasWindowTabs() && !singlePane;
3124
3299
  const windowTabs = useCanvasState((s) => singlePane ? windowTabsSignature(s, windowId) : "");
3125
- const sectionRef = useRef7(null);
3126
- const bodyRef = useRef7(null);
3300
+ const sectionRef = useRef8(null);
3301
+ const bodyRef = useRef8(null);
3127
3302
  const presentation = usePanePresentation(slots, paneId, isFullscreen, paneCount);
3128
3303
  const drop = usePaneDrop(paneId, () => sectionRef.current);
3129
- useIsoLayoutEffect4(() => {
3304
+ useIsoLayoutEffect5(() => {
3130
3305
  const body = bodyRef.current;
3131
3306
  const section = sectionRef.current;
3132
3307
  if (body) slots.set(paneId, body);
@@ -3303,14 +3478,14 @@ function TabStrip({
3303
3478
  actives
3304
3479
  }) {
3305
3480
  const canvas = useCanvas();
3306
- const strip = useRef7(null);
3307
- const list = useRef7(null);
3481
+ const strip = useRef8(null);
3482
+ const list = useRef8(null);
3308
3483
  const [width, setWidth] = useState5(0);
3309
- const windowStart = useRef7(0);
3310
- const focusAfterMove = useRef7(false);
3311
- const read = useRef7(() => {
3484
+ const windowStart = useRef8(0);
3485
+ const focusAfterMove = useRef8(false);
3486
+ const read = useRef8(() => {
3312
3487
  });
3313
- useIsoLayoutEffect4(() => {
3488
+ useIsoLayoutEffect5(() => {
3314
3489
  const el = strip.current;
3315
3490
  if (!el) return void 0;
3316
3491
  let live = true;
@@ -3329,10 +3504,10 @@ function TabStrip({
3329
3504
  if (frame2 && typeof cancelAnimationFrame !== "undefined") cancelAnimationFrame(frame2);
3330
3505
  };
3331
3506
  }, []);
3332
- useIsoLayoutEffect4(() => {
3507
+ useIsoLayoutEffect5(() => {
3333
3508
  read.current();
3334
3509
  }, [tabs.length]);
3335
- useIsoLayoutEffect4(() => {
3510
+ useIsoLayoutEffect5(() => {
3336
3511
  if (!focusAfterMove.current || !activeItemId) return;
3337
3512
  focusAfterMove.current = false;
3338
3513
  tabElement(list.current, activeItemId)?.focus();
@@ -3349,7 +3524,7 @@ function TabStrip({
3349
3524
  width,
3350
3525
  windowStart.current
3351
3526
  );
3352
- useIsoLayoutEffect4(() => {
3527
+ useIsoLayoutEffect5(() => {
3353
3528
  windowStart.current = fit.start;
3354
3529
  });
3355
3530
  const shown = entries.slice(fit.start, fit.end);
@@ -3429,8 +3604,8 @@ function tabElement(list, itemId) {
3429
3604
  function TabListMenu({ tabs, activeItemId, hidden }) {
3430
3605
  const canvas = useCanvas();
3431
3606
  const { ports, layer } = useCanvasInternals();
3432
- const blocking = useRef7(null);
3433
- useEffect7(() => () => blocking.current?.(), []);
3607
+ const blocking = useRef8(null);
3608
+ useEffect8(() => () => blocking.current?.(), []);
3434
3609
  const label = `All tabs (${tabs.length})`;
3435
3610
  const trigger = (props) => /* @__PURE__ */ jsxs6(CanvasHeaderButton, { className: "mxc-tab-list", label, ...props, children: [
3436
3611
  /* @__PURE__ */ jsx7("span", { className: "mxc-tab-list-count", "aria-hidden": "true", children: hidden }),
@@ -3542,16 +3717,26 @@ function PaneMenu({
3542
3717
  canSplit
3543
3718
  }) {
3544
3719
  const canvas = useCanvas();
3545
- const { ports, layer, itemMenus } = useCanvasInternals();
3720
+ const { ports, layer, itemMenus, itemOutputs } = useCanvasInternals();
3546
3721
  const moveId = tabCount > 1 ? activeItem?.id : void 0;
3547
- const blocking = useRef7(null);
3548
- useEffect7(() => () => blocking.current?.(), []);
3722
+ const blocking = useRef8(null);
3723
+ useEffect8(() => () => blocking.current?.(), []);
3549
3724
  const buildSections = () => {
3550
3725
  const kindItems = kind?.menuItems && kindProps ? safeMenu(() => kind.menuItems?.(kindProps)) : [];
3551
3726
  const hostItems = activeItem && ports.itemActions ? safeMenu(() => ports.itemActions?.(activeItem, kind)) : [];
3552
3727
  const bodyItems = activeItem ? itemMenus.read(activeItem.id) : [];
3728
+ const outputItems = activeItem ? safeMenu(
3729
+ () => canvasOutputMenu({
3730
+ item: activeItem,
3731
+ kind,
3732
+ title: itemTitle(activeItem, kind),
3733
+ body: itemOutputs.read(activeItem.id),
3734
+ ports: ports.output
3735
+ })
3736
+ ) : [];
3553
3737
  return [
3554
3738
  [...kindItems, ...bodyItems, ...hostItems].map((entry) => ({ ...entry, id: `item:${entry.id}` })),
3739
+ outputItems.map((entry) => ({ ...entry, id: `item:${entry.id}` })),
3555
3740
  [
3556
3741
  // Only a split that leaves both panes usable (the layout rules' minimums) is offered.
3557
3742
  ...canSplit && canvas.fitSplit(paneId, "horizontal") === "horizontal" ? [{ id: "split-right", label: "Split right", icon: /* @__PURE__ */ jsx7(SplitRightIcon, {}), onSelect: () => canvas.splitPane(paneId, "horizontal", moveId) }] : [],
@@ -3575,8 +3760,14 @@ function PaneMenu({
3575
3760
  buildSections().forEach((section, i) => {
3576
3761
  if (i > 0) entries.push({ type: "separator" });
3577
3762
  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);
3763
+ entries.push({
3764
+ type: "item",
3765
+ id: action.id,
3766
+ label: action.label,
3767
+ ...action.destructive ? { destructive: true } : {},
3768
+ ...action.disabled ? { disabled: true } : {}
3769
+ });
3770
+ if (!action.disabled) handlers.set(action.id, action.onSelect);
3580
3771
  }
3581
3772
  });
3582
3773
  const anchor = toCanvasRect(event.currentTarget.getBoundingClientRect());
@@ -3610,10 +3801,20 @@ function MenuSections({ build }) {
3610
3801
  function MenuSection({ first, actions }) {
3611
3802
  return /* @__PURE__ */ jsxs6(Fragment2, { children: [
3612
3803
  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))
3804
+ actions.map((action) => /* @__PURE__ */ jsxs6(
3805
+ Menu.Item,
3806
+ {
3807
+ onSelect: action.onSelect,
3808
+ disabled: action.disabled ?? false,
3809
+ className: "mxc-menu-item",
3810
+ "data-destructive": action.destructive ? "" : void 0,
3811
+ children: [
3812
+ action.icon,
3813
+ action.label
3814
+ ]
3815
+ },
3816
+ action.id
3817
+ ))
3617
3818
  ] });
3618
3819
  }
3619
3820
  function safeMenu(build) {
@@ -3627,7 +3828,7 @@ function safeMenu(build) {
3627
3828
 
3628
3829
  // src/react/CanvasColumn.tsx
3629
3830
  import { Fragment as Fragment3, jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
3630
- var useIsoLayoutEffect5 = typeof window === "undefined" ? useEffect8 : useLayoutEffect6;
3831
+ var useIsoLayoutEffect6 = typeof window === "undefined" ? useEffect9 : useLayoutEffect7;
3631
3832
  function subscribeViewport(listener) {
3632
3833
  window.addEventListener("resize", listener);
3633
3834
  return () => window.removeEventListener("resize", listener);
@@ -3662,9 +3863,9 @@ function CanvasColumn({ className, style, onLiveWidth, placement = "docked" }) {
3662
3863
  const singlePane = placement === "docked" && viewport > 0 && viewport <= CANVAS_PHONE_MAX_WIDTH;
3663
3864
  const [dragWidth, setDragWidth] = useState6(null);
3664
3865
  const [slots] = useState6(createCanvasSlots);
3665
- const ref = useRef8(null);
3866
+ const ref = useRef9(null);
3666
3867
  useRegisterCanvasPresence();
3667
- useEffect8(
3868
+ useEffect9(
3668
3869
  () => controller.registerPaneMeasure((paneId) => {
3669
3870
  const section = slots.section(paneId);
3670
3871
  if (!section?.isConnected) return null;
@@ -3673,7 +3874,7 @@ function CanvasColumn({ className, style, onLiveWidth, placement = "docked" }) {
3673
3874
  }),
3674
3875
  [controller, slots]
3675
3876
  );
3676
- useEffect8(() => {
3877
+ useEffect9(() => {
3677
3878
  const el = ref.current;
3678
3879
  if (!el) return void 0;
3679
3880
  let frame2 = 0;
@@ -3747,8 +3948,8 @@ function CanvasColumn({ className, style, onLiveWidth, placement = "docked" }) {
3747
3948
  }
3748
3949
  function WidthHandle({ width, region, onDrag }) {
3749
3950
  const canvas = useCanvas();
3750
- const start = useRef8(null);
3751
- useEffect8(() => () => start.current?.end(), []);
3951
+ const start = useRef9(null);
3952
+ useEffect9(() => () => start.current?.end(), []);
3752
3953
  const clamp = (next) => clampDraggedCanvasWidth(next, region, canvas.rules);
3753
3954
  const onPointerDown = (event) => {
3754
3955
  event.preventDefault();
@@ -3796,7 +3997,7 @@ function LayoutNodeView({ node }) {
3796
3997
  }
3797
3998
  function useMeasuredSize(ref, enabled) {
3798
3999
  const [size, setSize] = useState6({ width: 0, height: 0 });
3799
- useIsoLayoutEffect5(() => {
4000
+ useIsoLayoutEffect6(() => {
3800
4001
  const el = ref.current;
3801
4002
  if (!enabled || !el) return;
3802
4003
  const read = () => {
@@ -3822,7 +4023,7 @@ function childMinimums(node, orientation, rules) {
3822
4023
  }
3823
4024
  function SplitView({ node }) {
3824
4025
  const canvas = useCanvas();
3825
- const ref = useRef8(null);
4026
+ const ref = useRef9(null);
3826
4027
  const [liveSizes, setLiveSizes] = useState6(null);
3827
4028
  const measured = useMeasuredSize(ref, canvas.rules.paneMinWidth > 0 || canvas.rules.paneMinHeight > 0);
3828
4029
  const stacked = node.orientation === "horizontal" && shouldStackSplit(measured.width, node.children.length, canvas.rules);
@@ -3977,6 +4178,10 @@ export {
3977
4178
  CanvasWindowScope,
3978
4179
  CanvasWindowTabs,
3979
4180
  beginCanvasResize,
4181
+ canvasItemElement,
4182
+ canvasKindOutputProblems,
4183
+ canvasKindSurface,
4184
+ canvasOutputMenu,
3980
4185
  canvasSessionKey,
3981
4186
  defineCanvasKind,
3982
4187
  fitCanvasTabWindow,
@@ -3997,6 +4202,7 @@ export {
3997
4202
  useCanvasLauncherEntry,
3998
4203
  useCanvasMenuItems,
3999
4204
  useCanvasNativeBodies,
4205
+ useCanvasOutput,
4000
4206
  useCanvasPresentation,
4001
4207
  useCanvasState,
4002
4208
  useCanvasWindowId,