@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/CHANGELOG.md +44 -0
- package/dist/react.cjs +342 -135
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +104 -3
- package/dist/react.d.ts +104 -3
- package/dist/react.js +291 -84
- 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
|
}
|
|
@@ -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 =
|
|
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
|
-
|
|
1870
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
2012
|
-
useLayoutEffect as
|
|
2013
|
-
useRef as
|
|
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
|
|
2026
|
-
useLayoutEffect as
|
|
2191
|
+
useEffect as useEffect7,
|
|
2192
|
+
useLayoutEffect as useLayoutEffect5,
|
|
2027
2193
|
useMemo as useMemo2,
|
|
2028
|
-
useRef as
|
|
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
|
|
2036
|
-
useLayoutEffect as
|
|
2037
|
-
useRef as
|
|
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
|
|
2307
|
+
var useIsoLayoutEffect3 = typeof window === "undefined" ? useEffect5 : useLayoutEffect4;
|
|
2142
2308
|
function useTruncated(title) {
|
|
2143
|
-
const ref =
|
|
2309
|
+
const ref = useRef5(null);
|
|
2144
2310
|
const [truncated, setTruncated] = useState3(false);
|
|
2145
|
-
|
|
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 =
|
|
2160
|
-
const hide =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 =
|
|
2576
|
+
const active = useRef6([]);
|
|
2411
2577
|
const end = () => {
|
|
2412
2578
|
for (const dispose of active.current.splice(0)) dispose();
|
|
2413
2579
|
};
|
|
2414
|
-
|
|
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 =
|
|
2430
|
-
|
|
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
|
|
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 =
|
|
2952
|
+
const root = useRef7(null);
|
|
2783
2953
|
const [placement] = useState4(
|
|
2784
2954
|
() => slots ? createPlacement(() => root.current, slots, () => layer.native.remeasure()) : null
|
|
2785
2955
|
);
|
|
2786
|
-
|
|
2787
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
2971
|
-
const measure =
|
|
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
|
-
|
|
2983
|
-
|
|
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
|
|
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
|
|
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 =
|
|
3126
|
-
const bodyRef =
|
|
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
|
-
|
|
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 =
|
|
3307
|
-
const list =
|
|
3482
|
+
const strip = useRef8(null);
|
|
3483
|
+
const list = useRef8(null);
|
|
3308
3484
|
const [width, setWidth] = useState5(0);
|
|
3309
|
-
const windowStart =
|
|
3310
|
-
const focusAfterMove =
|
|
3311
|
-
const read =
|
|
3485
|
+
const windowStart = useRef8(0);
|
|
3486
|
+
const focusAfterMove = useRef8(false);
|
|
3487
|
+
const read = useRef8(() => {
|
|
3312
3488
|
});
|
|
3313
|
-
|
|
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
|
-
|
|
3508
|
+
useIsoLayoutEffect5(() => {
|
|
3333
3509
|
read.current();
|
|
3334
3510
|
}, [tabs.length]);
|
|
3335
|
-
|
|
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
|
-
|
|
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 =
|
|
3433
|
-
|
|
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 =
|
|
3548
|
-
|
|
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({
|
|
3579
|
-
|
|
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(
|
|
3614
|
-
|
|
3615
|
-
|
|
3616
|
-
|
|
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
|
|
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 =
|
|
3867
|
+
const ref = useRef9(null);
|
|
3666
3868
|
useRegisterCanvasPresence();
|
|
3667
|
-
|
|
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
|
-
|
|
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 =
|
|
3751
|
-
|
|
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
|
-
|
|
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 =
|
|
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,
|