@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/CHANGELOG.md +38 -0
- package/dist/react.cjs +338 -132
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +103 -2
- package/dist/react.d.ts +103 -2
- package/dist/react.js +287 -81
- package/dist/react.js.map +1 -1
- package/dist/styles.css +5 -1
- package/package.json +2 -2
package/dist/react.js
CHANGED
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
import {
|
|
6
6
|
createContext,
|
|
7
7
|
useContext,
|
|
8
|
-
useEffect as
|
|
8
|
+
useEffect as useEffect4,
|
|
9
9
|
useMemo,
|
|
10
|
-
useRef as
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
1870
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
2012
|
-
useLayoutEffect as
|
|
2013
|
-
useRef as
|
|
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
|
|
2026
|
-
useLayoutEffect as
|
|
2190
|
+
useEffect as useEffect7,
|
|
2191
|
+
useLayoutEffect as useLayoutEffect5,
|
|
2027
2192
|
useMemo as useMemo2,
|
|
2028
|
-
useRef as
|
|
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
|
|
2036
|
-
useLayoutEffect as
|
|
2037
|
-
useRef as
|
|
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
|
|
2306
|
+
var useIsoLayoutEffect3 = typeof window === "undefined" ? useEffect5 : useLayoutEffect4;
|
|
2142
2307
|
function useTruncated(title) {
|
|
2143
|
-
const ref =
|
|
2308
|
+
const ref = useRef5(null);
|
|
2144
2309
|
const [truncated, setTruncated] = useState3(false);
|
|
2145
|
-
|
|
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 =
|
|
2160
|
-
const hide =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 =
|
|
2575
|
+
const active = useRef6([]);
|
|
2411
2576
|
const end = () => {
|
|
2412
2577
|
for (const dispose of active.current.splice(0)) dispose();
|
|
2413
2578
|
};
|
|
2414
|
-
|
|
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 =
|
|
2430
|
-
|
|
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
|
|
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 =
|
|
2951
|
+
const root = useRef7(null);
|
|
2783
2952
|
const [placement] = useState4(
|
|
2784
2953
|
() => slots ? createPlacement(() => root.current, slots, () => layer.native.remeasure()) : null
|
|
2785
2954
|
);
|
|
2786
|
-
|
|
2787
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
2971
|
-
const measure =
|
|
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
|
-
|
|
2983
|
-
|
|
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
|
|
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
|
|
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 =
|
|
3126
|
-
const bodyRef =
|
|
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
|
-
|
|
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 =
|
|
3307
|
-
const list =
|
|
3481
|
+
const strip = useRef8(null);
|
|
3482
|
+
const list = useRef8(null);
|
|
3308
3483
|
const [width, setWidth] = useState5(0);
|
|
3309
|
-
const windowStart =
|
|
3310
|
-
const focusAfterMove =
|
|
3311
|
-
const read =
|
|
3484
|
+
const windowStart = useRef8(0);
|
|
3485
|
+
const focusAfterMove = useRef8(false);
|
|
3486
|
+
const read = useRef8(() => {
|
|
3312
3487
|
});
|
|
3313
|
-
|
|
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
|
-
|
|
3507
|
+
useIsoLayoutEffect5(() => {
|
|
3333
3508
|
read.current();
|
|
3334
3509
|
}, [tabs.length]);
|
|
3335
|
-
|
|
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
|
-
|
|
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 =
|
|
3433
|
-
|
|
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 =
|
|
3548
|
-
|
|
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({
|
|
3579
|
-
|
|
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(
|
|
3614
|
-
|
|
3615
|
-
|
|
3616
|
-
|
|
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
|
|
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 =
|
|
3866
|
+
const ref = useRef9(null);
|
|
3666
3867
|
useRegisterCanvasPresence();
|
|
3667
|
-
|
|
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
|
-
|
|
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 =
|
|
3751
|
-
|
|
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
|
-
|
|
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 =
|
|
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,
|