@ai-matrx/canvas 0.3.0 → 0.5.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 +35 -0
- package/README.md +66 -1
- package/dist/index.cjs +203 -21
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +7 -3
- package/dist/index.d.ts +7 -3
- package/dist/index.js +203 -21
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +972 -286
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +100 -3
- package/dist/react.d.ts +100 -3
- package/dist/react.js +980 -291
- package/dist/react.js.map +1 -1
- package/dist/styles.css +238 -26
- package/dist/{toggleKind-A9v45oSb.d.cts → toggleKind-BD78LnMB.d.cts} +105 -1
- package/dist/{toggleKind-A9v45oSb.d.ts → toggleKind-BD78LnMB.d.ts} +105 -1
- package/dist/tokens.css +3 -0
- package/package.json +1 -1
package/dist/react.js
CHANGED
|
@@ -13,6 +13,8 @@ import {
|
|
|
13
13
|
|
|
14
14
|
// src/core/types.ts
|
|
15
15
|
var CANVAS_MAIN_WINDOW = "main";
|
|
16
|
+
var CANVAS_LAUNCHER_KIND = "canvas.launcher";
|
|
17
|
+
var CANVAS_HISTORY_LIMIT = 50;
|
|
16
18
|
var CANVAS_MIN_WIDTH = 360;
|
|
17
19
|
var CANVAS_DEFAULT_WIDTH = 640;
|
|
18
20
|
var CANVAS_MIN_SPLIT_FRACTION = 0.12;
|
|
@@ -171,6 +173,10 @@ var canvasActions = {
|
|
|
171
173
|
}),
|
|
172
174
|
/** Gives an item a new identity in place (e.g. a draft that was just saved and now has an id). */
|
|
173
175
|
rekey: (itemId, key) => ({ type: `${P}rekey`, payload: { itemId, key } }),
|
|
176
|
+
/** Replaces a tab's content with another item in place, remembering what it showed (Back returns). */
|
|
177
|
+
navigate: (itemId, to) => ({ type: `${P}navigate`, payload: { itemId, to, now: Date.now() } }),
|
|
178
|
+
/** Returns a tab to what it showed before its last navigate. */
|
|
179
|
+
back: (itemId) => ({ type: `${P}back`, payload: { itemId, now: Date.now() } }),
|
|
174
180
|
closeItem: (itemId) => ({ type: `${P}closeItem`, payload: { itemId } }),
|
|
175
181
|
closeOthers: (itemId) => ({ type: `${P}closeOthers`, payload: { itemId } }),
|
|
176
182
|
activate: (itemId) => ({ type: `${P}activate`, payload: { itemId } }),
|
|
@@ -367,6 +373,63 @@ function sendHome(state, itemId, home, activate) {
|
|
|
367
373
|
next = insertInto(next, homeFrame.focusedPaneId, itemId, activate);
|
|
368
374
|
return activate ? withFrame(next, target, { focusedPaneId: homeFrame.focusedPaneId }) : next;
|
|
369
375
|
}
|
|
376
|
+
function newItem(kind, key, title, data, now, history) {
|
|
377
|
+
const id = canvasItemId(kind, key);
|
|
378
|
+
return {
|
|
379
|
+
id,
|
|
380
|
+
kind,
|
|
381
|
+
key,
|
|
382
|
+
title,
|
|
383
|
+
data,
|
|
384
|
+
openedAt: now,
|
|
385
|
+
updatedAt: now,
|
|
386
|
+
instance: `${now.toString(36)}:${id}`,
|
|
387
|
+
...history && history.length > 0 ? { history: history.slice(-CANVAS_HISTORY_LIMIT) } : {}
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
function historyEntry(item) {
|
|
391
|
+
return { kind: item.kind, key: item.key, title: item.title, data: item.data };
|
|
392
|
+
}
|
|
393
|
+
function replaceInPlace(state, current, next) {
|
|
394
|
+
const pane = paneOf(state, current.id);
|
|
395
|
+
if (!pane) return state;
|
|
396
|
+
const items = { ...state.items, [next.id]: next };
|
|
397
|
+
delete items[current.id];
|
|
398
|
+
const withItems = { ...state, items };
|
|
399
|
+
const replaced = withPane(withItems, {
|
|
400
|
+
...pane,
|
|
401
|
+
itemIds: pane.itemIds.map((id) => id === current.id ? next.id : id),
|
|
402
|
+
activeItemId: pane.activeItemId === current.id ? next.id : pane.activeItemId
|
|
403
|
+
});
|
|
404
|
+
return focus(replaced, pane.id);
|
|
405
|
+
}
|
|
406
|
+
function focusExisting(state, itemId) {
|
|
407
|
+
const pane = paneOf(state, itemId);
|
|
408
|
+
if (!pane) return state;
|
|
409
|
+
const next = focus(withPane(state, { ...pane, activeItemId: itemId }), pane.id);
|
|
410
|
+
const windowId = canvasWindowOfPane(next, pane.id);
|
|
411
|
+
return windowId ? withFrame(next, windowId, { isOpen: true }) : next;
|
|
412
|
+
}
|
|
413
|
+
function sanitizeHistory(raw) {
|
|
414
|
+
if (!Array.isArray(raw)) return [];
|
|
415
|
+
const out = [];
|
|
416
|
+
for (const entry of raw) {
|
|
417
|
+
if (!entry || typeof entry !== "object") continue;
|
|
418
|
+
const e = entry;
|
|
419
|
+
if (typeof e.kind !== "string" || typeof e.key !== "string") continue;
|
|
420
|
+
out.push({ kind: e.kind, key: e.key, title: typeof e.title === "string" ? e.title : null, data: e.data ?? null });
|
|
421
|
+
}
|
|
422
|
+
return out.slice(-CANVAS_HISTORY_LIMIT);
|
|
423
|
+
}
|
|
424
|
+
function sanitizeItem(item) {
|
|
425
|
+
const history = sanitizeHistory(item.history);
|
|
426
|
+
const { history: _history, instance, ...rest } = item;
|
|
427
|
+
return {
|
|
428
|
+
...rest,
|
|
429
|
+
...typeof instance === "string" && instance ? { instance } : {},
|
|
430
|
+
...history.length > 0 ? { history } : {}
|
|
431
|
+
};
|
|
432
|
+
}
|
|
370
433
|
function clampWidth(width) {
|
|
371
434
|
return Number.isFinite(width) ? Math.max(CANVAS_MIN_WIDTH, Math.round(width)) : null;
|
|
372
435
|
}
|
|
@@ -414,7 +477,7 @@ function sanitizeCanvasSnapshot(raw) {
|
|
|
414
477
|
const items = {};
|
|
415
478
|
for (const [id, item] of Object.entries(s.items)) {
|
|
416
479
|
if (item && typeof item.kind === "string" && typeof item.key === "string" && canvasItemId(item.kind, item.key) === id) {
|
|
417
|
-
items[id] = item;
|
|
480
|
+
items[id] = sanitizeItem(item);
|
|
418
481
|
}
|
|
419
482
|
}
|
|
420
483
|
const placed = /* @__PURE__ */ new Set();
|
|
@@ -488,7 +551,7 @@ function canvasReducer(state = createInitialCanvasState(), action) {
|
|
|
488
551
|
next = insertInto(next, frame2.focusedPaneId, itemId, true);
|
|
489
552
|
}
|
|
490
553
|
} else {
|
|
491
|
-
const item =
|
|
554
|
+
const item = newItem(kind, key, title ?? null, data ?? null, now);
|
|
492
555
|
next = ensureWindow({ ...next, items: { ...next.items, [itemId]: item } }, revealIn);
|
|
493
556
|
const frame2 = canvasWindowFrame(next, revealIn) ?? next;
|
|
494
557
|
let paneId = frame2.focusedPaneId;
|
|
@@ -537,6 +600,35 @@ function canvasReducer(state = createInitialCanvasState(), action) {
|
|
|
537
600
|
}
|
|
538
601
|
return { ...state, items, panes };
|
|
539
602
|
}
|
|
603
|
+
case `${P}navigate`: {
|
|
604
|
+
const { itemId, to, now } = action.payload;
|
|
605
|
+
const current = state.items[itemId];
|
|
606
|
+
if (!current || !paneOf(state, itemId)) return state;
|
|
607
|
+
const targetId = canvasItemId(to.kind, to.key);
|
|
608
|
+
if (targetId === itemId) {
|
|
609
|
+
const refresh = { type: `${P}update`, payload: { itemId, data: to.data, title: to.title, now } };
|
|
610
|
+
return canvasReducer(state, refresh);
|
|
611
|
+
}
|
|
612
|
+
if (state.items[targetId]) {
|
|
613
|
+
const focused = focusExisting(state, targetId);
|
|
614
|
+
return current.kind === CANVAS_LAUNCHER_KIND ? canvasReducer(focused, canvasActions.closeItem(itemId)) : focused;
|
|
615
|
+
}
|
|
616
|
+
const history = [...current.history ?? [], historyEntry(current)];
|
|
617
|
+
return replaceInPlace(state, current, newItem(to.kind, to.key, to.title ?? null, to.data ?? null, now, history));
|
|
618
|
+
}
|
|
619
|
+
case `${P}back`: {
|
|
620
|
+
const { itemId, now } = action.payload;
|
|
621
|
+
const current = state.items[itemId];
|
|
622
|
+
const history = current?.history ?? [];
|
|
623
|
+
const entry = history[history.length - 1];
|
|
624
|
+
if (!current || !entry || !paneOf(state, itemId)) return state;
|
|
625
|
+
const targetId = canvasItemId(entry.kind, entry.key);
|
|
626
|
+
if (targetId === itemId) return state;
|
|
627
|
+
if (state.items[targetId]) {
|
|
628
|
+
return focusExisting(canvasReducer(state, canvasActions.closeItem(itemId)), targetId);
|
|
629
|
+
}
|
|
630
|
+
return replaceInPlace(state, current, newItem(entry.kind, entry.key, entry.title, entry.data, now, history.slice(0, -1)));
|
|
631
|
+
}
|
|
540
632
|
case `${P}closeItem`: {
|
|
541
633
|
const { itemId } = action.payload;
|
|
542
634
|
if (!state.items[itemId]) return state;
|
|
@@ -790,7 +882,8 @@ var CANVAS_WEB_LAYOUT_RULES = {
|
|
|
790
882
|
defaultWidth: CANVAS_DEFAULT_WIDTH,
|
|
791
883
|
minWidth: CANVAS_MIN_WIDTH,
|
|
792
884
|
centreMinWidth: 420,
|
|
793
|
-
|
|
885
|
+
// Split down 70/30 like the desktop: what opens below (notifications, a feed) is the smaller pane.
|
|
886
|
+
stackSplit: 0.7,
|
|
794
887
|
sideSplit: 0.5,
|
|
795
888
|
paneMinWidth: 0
|
|
796
889
|
};
|
|
@@ -915,7 +1008,8 @@ function createCanvasController(options) {
|
|
|
915
1008
|
presentationListeners: /* @__PURE__ */ new Set(),
|
|
916
1009
|
views: /* @__PURE__ */ new Map(),
|
|
917
1010
|
started: /* @__PURE__ */ new Map(),
|
|
918
|
-
sessionStop: null
|
|
1011
|
+
sessionStop: null,
|
|
1012
|
+
launcherSeq: 0
|
|
919
1013
|
};
|
|
920
1014
|
return view(shared, canvasWindowId(options.windowId ?? CANVAS_MAIN_WINDOW));
|
|
921
1015
|
}
|
|
@@ -930,6 +1024,36 @@ function view(shared, windowId) {
|
|
|
930
1024
|
for (const listener of [...shared.presentationListeners]) listener();
|
|
931
1025
|
};
|
|
932
1026
|
const shareFor = (orientation) => orientation === "vertical" ? rules.stackSplit : rules.sideSplit;
|
|
1027
|
+
const acceptable = (input) => {
|
|
1028
|
+
if (input.kind !== CANVAS_LAUNCHER_KIND && options.isKnownKind && !options.isKnownKind(input.kind)) {
|
|
1029
|
+
onError({ code: "unknown-kind", message: `No canvas kind "${input.kind}" is registered.`, detail: input });
|
|
1030
|
+
return false;
|
|
1031
|
+
}
|
|
1032
|
+
if (input.data !== void 0) {
|
|
1033
|
+
const bad = findNonJson(input.data);
|
|
1034
|
+
if (bad) {
|
|
1035
|
+
onError({
|
|
1036
|
+
code: "non-json-data",
|
|
1037
|
+
message: `Canvas data for "${input.kind}" must be plain JSON; ${bad} is not.`,
|
|
1038
|
+
detail: { kind: input.kind, key: input.key }
|
|
1039
|
+
});
|
|
1040
|
+
return false;
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
return true;
|
|
1044
|
+
};
|
|
1045
|
+
const preferredTargetFor = (input) => {
|
|
1046
|
+
const preferred = options.preferredTarget?.(input.kind);
|
|
1047
|
+
if (!preferred || preferred === "focused") return "focused";
|
|
1048
|
+
const state = store.getState();
|
|
1049
|
+
if (canvasItemId(input.kind, input.key) in state.items) return "focused";
|
|
1050
|
+
const frame2 = canvasWindowFrame(state, input.windowId ?? windowId);
|
|
1051
|
+
if (!frame2?.isOpen) return "focused";
|
|
1052
|
+
const panes = listPaneIds(frame2.layout);
|
|
1053
|
+
const holding = panes.find((paneId) => state.panes[paneId]?.itemIds.some((id) => state.items[id]?.kind === input.kind));
|
|
1054
|
+
if (holding) return { paneId: holding };
|
|
1055
|
+
return state.panes[frame2.focusedPaneId]?.activeItemId ? preferred : "focused";
|
|
1056
|
+
};
|
|
933
1057
|
const controller = {
|
|
934
1058
|
store,
|
|
935
1059
|
windowId,
|
|
@@ -937,24 +1061,11 @@ function view(shared, windowId) {
|
|
|
937
1061
|
forWindow: (id) => view(shared, canvasWindowId(id)),
|
|
938
1062
|
getState: () => store.getState(),
|
|
939
1063
|
open(input) {
|
|
940
|
-
if (
|
|
941
|
-
|
|
942
|
-
return null;
|
|
943
|
-
}
|
|
944
|
-
if (input.data !== void 0) {
|
|
945
|
-
const bad = findNonJson(input.data);
|
|
946
|
-
if (bad) {
|
|
947
|
-
onError({
|
|
948
|
-
code: "non-json-data",
|
|
949
|
-
message: `Canvas data for "${input.kind}" must be plain JSON; ${bad} is not.`,
|
|
950
|
-
detail: { kind: input.kind, key: input.key }
|
|
951
|
-
});
|
|
952
|
-
return null;
|
|
953
|
-
}
|
|
954
|
-
}
|
|
955
|
-
const target = input.target ?? "focused";
|
|
1064
|
+
if (!acceptable(input)) return null;
|
|
1065
|
+
const target = input.target ?? preferredTargetFor(input);
|
|
956
1066
|
const splitShare = target === "split-down" ? rules.stackSplit : target === "split-right" ? rules.sideSplit : void 0;
|
|
957
|
-
const
|
|
1067
|
+
const withTarget = input.target === target ? input : { ...input, target };
|
|
1068
|
+
const resolved = withTarget.windowId || windowId === CANVAS_MAIN_WINDOW ? withTarget : { ...withTarget, windowId };
|
|
958
1069
|
dispatch(canvasActions.open(resolved, splitShare === void 0 ? void 0 : { splitShare }));
|
|
959
1070
|
return canvasItemId(input.kind, input.key);
|
|
960
1071
|
},
|
|
@@ -970,6 +1081,34 @@ function view(shared, windowId) {
|
|
|
970
1081
|
dispatch(canvasActions.update(itemId, patch));
|
|
971
1082
|
return true;
|
|
972
1083
|
},
|
|
1084
|
+
navigate(itemId, to) {
|
|
1085
|
+
if (!store.getState().items[itemId] || !acceptable(to)) return null;
|
|
1086
|
+
dispatch(canvasActions.navigate(itemId, to));
|
|
1087
|
+
const shown = canvasItemId(to.kind, to.key);
|
|
1088
|
+
return shown in store.getState().items ? shown : null;
|
|
1089
|
+
},
|
|
1090
|
+
back(itemId) {
|
|
1091
|
+
const history = store.getState().items[itemId]?.history ?? [];
|
|
1092
|
+
const entry = history[history.length - 1];
|
|
1093
|
+
if (!entry) return null;
|
|
1094
|
+
dispatch(canvasActions.back(itemId));
|
|
1095
|
+
return canvasItemId(entry.kind, entry.key);
|
|
1096
|
+
},
|
|
1097
|
+
newTab(paneId) {
|
|
1098
|
+
const state = store.getState();
|
|
1099
|
+
const frame2 = canvasWindowFrame(state, windowId);
|
|
1100
|
+
const target = paneId && state.panes[paneId] ? paneId : frame2?.focusedPaneId ?? state.focusedPaneId;
|
|
1101
|
+
const existing = state.panes[target]?.itemIds.find((id) => state.items[id]?.kind === CANVAS_LAUNCHER_KIND);
|
|
1102
|
+
if (existing) {
|
|
1103
|
+
dispatch(canvasActions.activate(existing));
|
|
1104
|
+
dispatch(canvasActions.setOpen(true, canvasWindowOfItem(store.getState(), existing) ?? windowId));
|
|
1105
|
+
return existing;
|
|
1106
|
+
}
|
|
1107
|
+
shared.launcherSeq += 1;
|
|
1108
|
+
const key = `${Date.now().toString(36)}-${shared.launcherSeq}`;
|
|
1109
|
+
dispatch(canvasActions.open({ kind: CANVAS_LAUNCHER_KIND, key, target: { paneId: target }, ...windowId === CANVAS_MAIN_WINDOW ? {} : { windowId } }));
|
|
1110
|
+
return canvasItemId(CANVAS_LAUNCHER_KIND, key);
|
|
1111
|
+
},
|
|
973
1112
|
rekey(itemId, key) {
|
|
974
1113
|
const item = store.getState().items[itemId];
|
|
975
1114
|
if (!item) return null;
|
|
@@ -1219,6 +1358,32 @@ function selectCanvasWindowItemCount(s, windowId) {
|
|
|
1219
1358
|
return listPaneIds(layout).reduce((sum, paneId) => sum + (s.panes[paneId]?.itemIds.length ?? 0), 0);
|
|
1220
1359
|
}
|
|
1221
1360
|
var NO_WINDOWS = Object.freeze({});
|
|
1361
|
+
var NO_HISTORY = Object.freeze([]);
|
|
1362
|
+
var selectCanvasCanGoBack = (s, itemId) => (s.items[itemId]?.history?.length ?? 0) > 0;
|
|
1363
|
+
|
|
1364
|
+
// src/core/presentation.ts
|
|
1365
|
+
var CANVAS_NARROW_WIDTH = 480;
|
|
1366
|
+
var SQUARE_BAND = 1.2;
|
|
1367
|
+
function canvasOrientation(width, height) {
|
|
1368
|
+
if (!(width > 0) || !(height > 0)) return "square";
|
|
1369
|
+
const ratio = width / height;
|
|
1370
|
+
if (ratio >= SQUARE_BAND) return "landscape";
|
|
1371
|
+
if (ratio <= 1 / SQUARE_BAND) return "portrait";
|
|
1372
|
+
return "square";
|
|
1373
|
+
}
|
|
1374
|
+
function canvasPresentation(input) {
|
|
1375
|
+
const width = Math.max(0, Math.round(input.width));
|
|
1376
|
+
const height = Math.max(0, Math.round(input.height));
|
|
1377
|
+
return {
|
|
1378
|
+
host: "canvas",
|
|
1379
|
+
width,
|
|
1380
|
+
height,
|
|
1381
|
+
orientation: canvasOrientation(width, height),
|
|
1382
|
+
isNarrow: width > 0 && width < CANVAS_NARROW_WIDTH,
|
|
1383
|
+
isFullscreen: input.isFullscreen,
|
|
1384
|
+
paneCount: Math.max(1, input.paneCount)
|
|
1385
|
+
};
|
|
1386
|
+
}
|
|
1222
1387
|
|
|
1223
1388
|
// src/core/toggleKind.ts
|
|
1224
1389
|
var CANVAS_DEFAULT_KEY = "default";
|
|
@@ -1420,6 +1585,31 @@ function getCanvasKindsVersion() {
|
|
|
1420
1585
|
|
|
1421
1586
|
// src/react/provider.tsx
|
|
1422
1587
|
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
1588
|
+
function createLauncherEntryStore() {
|
|
1589
|
+
let entries = [];
|
|
1590
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
1591
|
+
const emit = () => {
|
|
1592
|
+
for (const listener of [...listeners]) listener();
|
|
1593
|
+
};
|
|
1594
|
+
return {
|
|
1595
|
+
add(entry) {
|
|
1596
|
+
entries = [...entries, entry];
|
|
1597
|
+
emit();
|
|
1598
|
+
let done = false;
|
|
1599
|
+
return () => {
|
|
1600
|
+
if (done) return;
|
|
1601
|
+
done = true;
|
|
1602
|
+
entries = entries.filter((e) => e !== entry);
|
|
1603
|
+
emit();
|
|
1604
|
+
};
|
|
1605
|
+
},
|
|
1606
|
+
list: () => entries,
|
|
1607
|
+
subscribe(listener) {
|
|
1608
|
+
listeners.add(listener);
|
|
1609
|
+
return () => listeners.delete(listener);
|
|
1610
|
+
}
|
|
1611
|
+
};
|
|
1612
|
+
}
|
|
1423
1613
|
var PHONE_MAX_WIDTH = 767;
|
|
1424
1614
|
var isPhoneViewport = () => typeof window !== "undefined" && window.innerWidth <= PHONE_MAX_WIDTH;
|
|
1425
1615
|
var isMacPlatform = () => typeof navigator !== "undefined" && /mac|iphone|ipad/i.test(navigator.userAgentData?.platform ?? navigator.platform ?? "");
|
|
@@ -1452,7 +1642,7 @@ function CanvasProvider({
|
|
|
1452
1642
|
}) {
|
|
1453
1643
|
const portsRef = useRef2(hostPorts);
|
|
1454
1644
|
portsRef.current = hostPorts;
|
|
1455
|
-
const [{ controller, layer }] = useState2(() => ({
|
|
1645
|
+
const [{ controller, layer, launcherEntries }] = useState2(() => ({
|
|
1456
1646
|
controller: createCanvasController({
|
|
1457
1647
|
store: store ?? createCanvasStore(),
|
|
1458
1648
|
persistence: persistence === void 0 ? createLocalStorageCanvasPersistence() : persistence,
|
|
@@ -1462,9 +1652,11 @@ function CanvasProvider({
|
|
|
1462
1652
|
windowId: windowIdProp ?? CANVAS_MAIN_WINDOW,
|
|
1463
1653
|
layoutRules,
|
|
1464
1654
|
windowPorts: () => portsRef.current,
|
|
1465
|
-
sessions: sessionHooksFor
|
|
1655
|
+
sessions: sessionHooksFor,
|
|
1656
|
+
preferredTarget: (kind) => getCanvasKind(kind)?.preferredTarget
|
|
1466
1657
|
}),
|
|
1467
|
-
layer: createCanvasLayer(() => portsRef.current)
|
|
1658
|
+
layer: createCanvasLayer(() => portsRef.current),
|
|
1659
|
+
launcherEntries: createLauncherEntryStore()
|
|
1468
1660
|
}));
|
|
1469
1661
|
const windowId = controller.windowId;
|
|
1470
1662
|
useEffect2(() => controller.start(), [controller]);
|
|
@@ -1489,7 +1681,7 @@ function CanvasProvider({
|
|
|
1489
1681
|
window.addEventListener("keydown", onKey);
|
|
1490
1682
|
return () => window.removeEventListener("keydown", onKey);
|
|
1491
1683
|
}, [controller, hotkeys, windowId]);
|
|
1492
|
-
const value = { controller, windowId, ports: hostPorts, layer, regionWidth, tabStrip };
|
|
1684
|
+
const value = { controller, windowId, ports: hostPorts, layer, regionWidth, tabStrip, launcherEntries };
|
|
1493
1685
|
return /* @__PURE__ */ jsxs(CanvasContext.Provider, { value, children: [
|
|
1494
1686
|
children,
|
|
1495
1687
|
hostPorts.overlay ? null : /* @__PURE__ */ jsx2(CanvasDomOverlay, { layer })
|
|
@@ -1576,27 +1768,54 @@ function useCanvasKind(id) {
|
|
|
1576
1768
|
useSyncExternalStore2(subscribeCanvasKinds, getCanvasKindsVersion, getCanvasKindsVersion);
|
|
1577
1769
|
return getCanvasKind(id);
|
|
1578
1770
|
}
|
|
1771
|
+
var NO_ENTRIES = Object.freeze([]);
|
|
1772
|
+
var noEntries = () => NO_ENTRIES;
|
|
1773
|
+
function useCanvasLauncherEntry(entry) {
|
|
1774
|
+
const store = useContext(CanvasContext)?.launcherEntries;
|
|
1775
|
+
const fingerprint = JSON.stringify([entry.kind, entry.key, entry.title ?? null, entry.data ?? null]);
|
|
1776
|
+
const icon = entry.icon;
|
|
1777
|
+
useEffect2(() => {
|
|
1778
|
+
if (!store) return void 0;
|
|
1779
|
+
const [kind, key, title, data] = JSON.parse(fingerprint);
|
|
1780
|
+
return store.add({ kind, key, title, data, ...icon ? { icon } : {} });
|
|
1781
|
+
}, [store, fingerprint, icon]);
|
|
1782
|
+
}
|
|
1783
|
+
function useCanvasLauncherEntries() {
|
|
1784
|
+
const store = useContext(CanvasContext)?.launcherEntries;
|
|
1785
|
+
const all = useSyncExternalStore2(store ? store.subscribe : NO_SUBSCRIPTION, store ? store.list : noEntries, store ? store.list : noEntries);
|
|
1786
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1787
|
+
const out = [];
|
|
1788
|
+
for (let i = all.length - 1; i >= 0; i -= 1) {
|
|
1789
|
+
const entry = all[i];
|
|
1790
|
+
if (!entry || seen.has(`${entry.kind}::${entry.key}`)) continue;
|
|
1791
|
+
seen.add(`${entry.kind}::${entry.key}`);
|
|
1792
|
+
out.unshift(entry);
|
|
1793
|
+
}
|
|
1794
|
+
return out;
|
|
1795
|
+
}
|
|
1579
1796
|
|
|
1580
1797
|
// src/react/CanvasColumn.tsx
|
|
1581
1798
|
import {
|
|
1582
|
-
useEffect as
|
|
1583
|
-
useLayoutEffect as
|
|
1584
|
-
useRef as
|
|
1585
|
-
useState as
|
|
1586
|
-
useSyncExternalStore as
|
|
1799
|
+
useEffect as useEffect7,
|
|
1800
|
+
useLayoutEffect as useLayoutEffect4,
|
|
1801
|
+
useRef as useRef7,
|
|
1802
|
+
useState as useState6,
|
|
1803
|
+
useSyncExternalStore as useSyncExternalStore4
|
|
1587
1804
|
} from "react";
|
|
1588
1805
|
import { TapTargetButton, TapTargetButtonTransparent } from "@ai-matrx/tap-target";
|
|
1589
1806
|
|
|
1590
|
-
// src/react/
|
|
1807
|
+
// src/react/bodies.tsx
|
|
1591
1808
|
import {
|
|
1592
1809
|
Component,
|
|
1593
1810
|
Suspense,
|
|
1594
1811
|
lazy,
|
|
1595
|
-
|
|
1812
|
+
memo,
|
|
1813
|
+
useEffect as useEffect5,
|
|
1596
1814
|
useLayoutEffect as useLayoutEffect2,
|
|
1597
|
-
|
|
1815
|
+
useMemo,
|
|
1816
|
+
useRef as useRef5,
|
|
1817
|
+
useState as useState4
|
|
1598
1818
|
} from "react";
|
|
1599
|
-
import * as Menu from "@radix-ui/react-dropdown-menu";
|
|
1600
1819
|
|
|
1601
1820
|
// src/react/chrome.tsx
|
|
1602
1821
|
import {
|
|
@@ -1696,6 +1915,12 @@ var DockIcon = (p) => /* @__PURE__ */ jsxs2(Glyph, { ...p, children: [
|
|
|
1696
1915
|
/* @__PURE__ */ jsx3("path", { d: "M21 3 12 12" }),
|
|
1697
1916
|
/* @__PURE__ */ jsx3("path", { d: "M12 6v6h6" })
|
|
1698
1917
|
] });
|
|
1918
|
+
var LayoutGridIcon = (p) => /* @__PURE__ */ jsxs2(Glyph, { ...p, children: [
|
|
1919
|
+
/* @__PURE__ */ jsx3("rect", { width: "7", height: "7", x: "3", y: "3", rx: "1" }),
|
|
1920
|
+
/* @__PURE__ */ jsx3("rect", { width: "7", height: "7", x: "14", y: "3", rx: "1" }),
|
|
1921
|
+
/* @__PURE__ */ jsx3("rect", { width: "7", height: "7", x: "14", y: "14", rx: "1" }),
|
|
1922
|
+
/* @__PURE__ */ jsx3("rect", { width: "7", height: "7", x: "3", y: "14", rx: "1" })
|
|
1923
|
+
] });
|
|
1699
1924
|
|
|
1700
1925
|
// src/react/chrome.tsx
|
|
1701
1926
|
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
@@ -1946,10 +2171,225 @@ function BrowserBar({ url, canGoBack, canGoForward, onBack, onForward, onReload,
|
|
|
1946
2171
|
] });
|
|
1947
2172
|
}
|
|
1948
2173
|
|
|
1949
|
-
// src/react/
|
|
1950
|
-
import {
|
|
1951
|
-
var
|
|
1952
|
-
|
|
2174
|
+
// src/react/dnd.ts
|
|
2175
|
+
import { useEffect as useEffect4, useRef as useRef4 } from "react";
|
|
2176
|
+
var CANVAS_DRAG_MIME = "application/x-matrx-canvas-item";
|
|
2177
|
+
function useTabDrag(itemId, title) {
|
|
2178
|
+
const { layer } = useCanvasInternals();
|
|
2179
|
+
const active = useRef4([]);
|
|
2180
|
+
const end = () => {
|
|
2181
|
+
for (const dispose of active.current.splice(0)) dispose();
|
|
2182
|
+
};
|
|
2183
|
+
useEffect4(() => end, []);
|
|
2184
|
+
return {
|
|
2185
|
+
draggable: true,
|
|
2186
|
+
onDragStart: (event) => {
|
|
2187
|
+
event.dataTransfer.setData(CANVAS_DRAG_MIME, itemId);
|
|
2188
|
+
event.dataTransfer.effectAllowed = "move";
|
|
2189
|
+
end();
|
|
2190
|
+
active.current.push(layer.beginBlocking(), layer.show({ type: "tab-drag", itemId, title }));
|
|
2191
|
+
},
|
|
2192
|
+
onDragEnd: end
|
|
2193
|
+
};
|
|
2194
|
+
}
|
|
2195
|
+
function usePaneDrop(paneId, getOutline) {
|
|
2196
|
+
const canvas = useCanvas();
|
|
2197
|
+
const { layer } = useCanvasInternals();
|
|
2198
|
+
const drop = useRef4(null);
|
|
2199
|
+
useEffect4(() => () => drop.current?.(), []);
|
|
2200
|
+
const hide = () => {
|
|
2201
|
+
drop.current?.();
|
|
2202
|
+
drop.current = null;
|
|
2203
|
+
};
|
|
2204
|
+
return {
|
|
2205
|
+
onDragOver: (event) => {
|
|
2206
|
+
if (!event.dataTransfer.types.includes(CANVAS_DRAG_MIME)) return;
|
|
2207
|
+
event.preventDefault();
|
|
2208
|
+
event.dataTransfer.dropEffect = "move";
|
|
2209
|
+
const outline = getOutline();
|
|
2210
|
+
if (!drop.current && outline) {
|
|
2211
|
+
drop.current = layer.show({ type: "drop-indicator", rect: toCanvasRect(outline.getBoundingClientRect()), paneId });
|
|
2212
|
+
}
|
|
2213
|
+
},
|
|
2214
|
+
onDragLeave: (event) => {
|
|
2215
|
+
const next = event.relatedTarget;
|
|
2216
|
+
if (next && event.currentTarget.contains(next)) return;
|
|
2217
|
+
hide();
|
|
2218
|
+
},
|
|
2219
|
+
onDrop: (event) => {
|
|
2220
|
+
hide();
|
|
2221
|
+
const itemId = event.dataTransfer.getData(CANVAS_DRAG_MIME);
|
|
2222
|
+
if (!itemId) return;
|
|
2223
|
+
event.preventDefault();
|
|
2224
|
+
canvas.moveItem(itemId, paneId);
|
|
2225
|
+
}
|
|
2226
|
+
};
|
|
2227
|
+
}
|
|
2228
|
+
|
|
2229
|
+
// src/react/launcher.tsx
|
|
2230
|
+
import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
2231
|
+
function useChoices() {
|
|
2232
|
+
const entries = useCanvasLauncherEntries();
|
|
2233
|
+
const kinds = useCanvasKinds();
|
|
2234
|
+
const byId = new Map(kinds.map((kind) => [kind.id, kind]));
|
|
2235
|
+
const contextual = [];
|
|
2236
|
+
for (const entry of entries) {
|
|
2237
|
+
const kind = byId.get(entry.kind);
|
|
2238
|
+
if (!kind) continue;
|
|
2239
|
+
contextual.push({
|
|
2240
|
+
id: `ctx:${canvasItemId(entry.kind, entry.key)}`,
|
|
2241
|
+
label: entry.title ?? kind.label,
|
|
2242
|
+
icon: entry.icon ?? kind.icon,
|
|
2243
|
+
input: { kind: entry.kind, key: entry.key, title: entry.title ?? null, ...entry.data !== void 0 ? { data: entry.data } : {} }
|
|
2244
|
+
});
|
|
2245
|
+
}
|
|
2246
|
+
const global = [];
|
|
2247
|
+
for (const kind of kinds) {
|
|
2248
|
+
const launcher = kind.launcher;
|
|
2249
|
+
if (!launcher) continue;
|
|
2250
|
+
global.push({
|
|
2251
|
+
id: `kind:${kind.id}`,
|
|
2252
|
+
label: launcher.title ?? kind.label,
|
|
2253
|
+
icon: kind.icon,
|
|
2254
|
+
input: { kind: kind.id, key: launcher.key, data: launcher.data, title: launcher.title ?? null }
|
|
2255
|
+
});
|
|
2256
|
+
}
|
|
2257
|
+
return { contextual, global };
|
|
2258
|
+
}
|
|
2259
|
+
function LauncherGrid({ onChoose }) {
|
|
2260
|
+
const { contextual, global } = useChoices();
|
|
2261
|
+
if (contextual.length === 0 && global.length === 0) return /* @__PURE__ */ jsx5(CanvasPanelEmpty, { children: "Nothing to open" });
|
|
2262
|
+
const section = (label, choices) => choices.length === 0 ? null : /* @__PURE__ */ jsxs4("section", { className: "mxc-launcher-section", "aria-label": label ?? "Open", children: [
|
|
2263
|
+
label ? /* @__PURE__ */ jsx5("h3", { className: "mxc-launcher-heading", children: label }) : null,
|
|
2264
|
+
/* @__PURE__ */ jsx5("div", { className: "mxc-launcher-grid", children: choices.map((choice) => {
|
|
2265
|
+
const Icon = choice.icon;
|
|
2266
|
+
return /* @__PURE__ */ jsxs4("button", { type: "button", className: "mxc-launcher-item", onClick: () => onChoose(choice.input), children: [
|
|
2267
|
+
/* @__PURE__ */ jsx5(Icon, { className: "mxc-launcher-icon" }),
|
|
2268
|
+
/* @__PURE__ */ jsx5("span", { children: choice.label })
|
|
2269
|
+
] }, choice.id);
|
|
2270
|
+
}) })
|
|
2271
|
+
] });
|
|
2272
|
+
return /* @__PURE__ */ jsxs4("div", { className: "mxc-launcher", children: [
|
|
2273
|
+
section(contextual.length > 0 ? "On this page" : null, contextual),
|
|
2274
|
+
section(contextual.length > 0 ? "Open" : null, global)
|
|
2275
|
+
] });
|
|
2276
|
+
}
|
|
2277
|
+
function LauncherBody({ item, canvas }) {
|
|
2278
|
+
return /* @__PURE__ */ jsx5(LauncherGrid, { onChoose: (input) => void canvas.navigate(item.id, input) });
|
|
2279
|
+
}
|
|
2280
|
+
function EmptyPaneLauncher({ paneId }) {
|
|
2281
|
+
const canvas = useCanvas();
|
|
2282
|
+
return /* @__PURE__ */ jsx5(
|
|
2283
|
+
LauncherGrid,
|
|
2284
|
+
{
|
|
2285
|
+
onChoose: (input) => {
|
|
2286
|
+
const tab = canvas.newTab(paneId);
|
|
2287
|
+
canvas.navigate(tab, input);
|
|
2288
|
+
}
|
|
2289
|
+
}
|
|
2290
|
+
);
|
|
2291
|
+
}
|
|
2292
|
+
var CANVAS_LAUNCHER_KIND_DEFINITION = {
|
|
2293
|
+
id: CANVAS_LAUNCHER_KIND,
|
|
2294
|
+
label: "New tab",
|
|
2295
|
+
icon: LayoutGridIcon,
|
|
2296
|
+
component: LauncherBody
|
|
2297
|
+
};
|
|
2298
|
+
function useItemKind(kindId) {
|
|
2299
|
+
const registered = useCanvasKind(kindId);
|
|
2300
|
+
return kindId === CANVAS_LAUNCHER_KIND ? CANVAS_LAUNCHER_KIND_DEFINITION : registered;
|
|
2301
|
+
}
|
|
2302
|
+
|
|
2303
|
+
// src/react/presentation.tsx
|
|
2304
|
+
import { createContext as createContext2, useContext as useContext2 } from "react";
|
|
2305
|
+
var CanvasPresentationContext = createContext2(null);
|
|
2306
|
+
function useCanvasPresentation() {
|
|
2307
|
+
return useContext2(CanvasPresentationContext);
|
|
2308
|
+
}
|
|
2309
|
+
|
|
2310
|
+
// src/react/slots.ts
|
|
2311
|
+
import { createContext as createContext3, useContext as useContext3, useSyncExternalStore as useSyncExternalStore3 } from "react";
|
|
2312
|
+
var ZERO = Object.freeze({ width: 0, height: 0 });
|
|
2313
|
+
function createCanvasSlots() {
|
|
2314
|
+
const slots = /* @__PURE__ */ new Map();
|
|
2315
|
+
const sections = /* @__PURE__ */ new Map();
|
|
2316
|
+
const sizes = /* @__PURE__ */ new Map();
|
|
2317
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
2318
|
+
let observer = null;
|
|
2319
|
+
const emit = () => {
|
|
2320
|
+
for (const listener of [...listeners]) listener();
|
|
2321
|
+
};
|
|
2322
|
+
const read = (paneId, element) => {
|
|
2323
|
+
const box = element.getBoundingClientRect();
|
|
2324
|
+
const width = Math.round(box.width);
|
|
2325
|
+
const height = Math.round(box.height);
|
|
2326
|
+
const previous = sizes.get(paneId);
|
|
2327
|
+
if (previous && previous.width === width && previous.height === height) return false;
|
|
2328
|
+
sizes.set(paneId, { width, height });
|
|
2329
|
+
return true;
|
|
2330
|
+
};
|
|
2331
|
+
const measureAll = () => {
|
|
2332
|
+
let changed = false;
|
|
2333
|
+
for (const [paneId, element] of slots) changed = read(paneId, element) || changed;
|
|
2334
|
+
return changed;
|
|
2335
|
+
};
|
|
2336
|
+
const watch = () => {
|
|
2337
|
+
if (observer || typeof ResizeObserver === "undefined") return observer;
|
|
2338
|
+
observer = new ResizeObserver(() => {
|
|
2339
|
+
measureAll();
|
|
2340
|
+
emit();
|
|
2341
|
+
});
|
|
2342
|
+
return observer;
|
|
2343
|
+
};
|
|
2344
|
+
return {
|
|
2345
|
+
set(paneId, element) {
|
|
2346
|
+
if (slots.get(paneId) === element) return;
|
|
2347
|
+
const previous = slots.get(paneId);
|
|
2348
|
+
if (previous) observer?.unobserve(previous);
|
|
2349
|
+
slots.set(paneId, element);
|
|
2350
|
+
watch()?.observe(element);
|
|
2351
|
+
read(paneId, element);
|
|
2352
|
+
emit();
|
|
2353
|
+
},
|
|
2354
|
+
unset(paneId, element) {
|
|
2355
|
+
const current = slots.get(paneId);
|
|
2356
|
+
if (!current || element && current !== element) return;
|
|
2357
|
+
observer?.unobserve(current);
|
|
2358
|
+
slots.delete(paneId);
|
|
2359
|
+
sizes.delete(paneId);
|
|
2360
|
+
emit();
|
|
2361
|
+
},
|
|
2362
|
+
setSection(paneId, element) {
|
|
2363
|
+
if (element) sections.set(paneId, element);
|
|
2364
|
+
else sections.delete(paneId);
|
|
2365
|
+
},
|
|
2366
|
+
slot: (paneId) => slots.get(paneId) ?? null,
|
|
2367
|
+
section: (paneId) => sections.get(paneId) ?? null,
|
|
2368
|
+
size: (paneId) => sizes.get(paneId) ?? ZERO,
|
|
2369
|
+
measure() {
|
|
2370
|
+
if (measureAll()) emit();
|
|
2371
|
+
},
|
|
2372
|
+
subscribe(listener) {
|
|
2373
|
+
listeners.add(listener);
|
|
2374
|
+
return () => listeners.delete(listener);
|
|
2375
|
+
}
|
|
2376
|
+
};
|
|
2377
|
+
}
|
|
2378
|
+
var CanvasSlotsContext = createContext3(null);
|
|
2379
|
+
function useCanvasSlots() {
|
|
2380
|
+
return useContext3(CanvasSlotsContext);
|
|
2381
|
+
}
|
|
2382
|
+
function useSlotSize(slots, paneId) {
|
|
2383
|
+
return useSyncExternalStore3(
|
|
2384
|
+
slots.subscribe,
|
|
2385
|
+
() => slots.size(paneId),
|
|
2386
|
+
() => ZERO
|
|
2387
|
+
);
|
|
2388
|
+
}
|
|
2389
|
+
|
|
2390
|
+
// src/react/bodies.tsx
|
|
2391
|
+
import { Fragment, jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
2392
|
+
var useIsoLayoutEffect = typeof window === "undefined" ? useEffect5 : useLayoutEffect2;
|
|
1953
2393
|
var LAZY = /* @__PURE__ */ Symbol.for("ai-matrx.canvas.lazy");
|
|
1954
2394
|
function kindComponent(kind) {
|
|
1955
2395
|
if (kind.component) return kind.component;
|
|
@@ -1960,8 +2400,294 @@ function kindComponent(kind) {
|
|
|
1960
2400
|
function resetKindComponent(kind) {
|
|
1961
2401
|
if (kind) delete kind[LAZY];
|
|
1962
2402
|
}
|
|
2403
|
+
function usePanePresentation(slots, paneId, isFullscreen, paneCount) {
|
|
2404
|
+
const size = useSlotSize(slots, paneId);
|
|
2405
|
+
return useMemo(
|
|
2406
|
+
() => canvasPresentation({ width: size.width, height: size.height, isFullscreen, paneCount }),
|
|
2407
|
+
[size.width, size.height, isFullscreen, paneCount]
|
|
2408
|
+
);
|
|
2409
|
+
}
|
|
2410
|
+
function placedItems(state, windowId, onlyPane) {
|
|
2411
|
+
const frame2 = canvasWindowFrame(state, windowId);
|
|
2412
|
+
if (!frame2) return [];
|
|
2413
|
+
const out = [];
|
|
2414
|
+
for (const paneId of onlyPane ? [onlyPane] : listPaneIds(frame2.layout)) {
|
|
2415
|
+
const pane = state.panes[paneId];
|
|
2416
|
+
for (const itemId of pane?.itemIds ?? []) {
|
|
2417
|
+
const item = state.items[itemId];
|
|
2418
|
+
if (item) out.push({ item, paneId, active: pane?.activeItemId === itemId });
|
|
2419
|
+
}
|
|
2420
|
+
}
|
|
2421
|
+
return out.sort((a, b) => a.item.openedAt - b.item.openedAt || hostKey(a.item).localeCompare(hostKey(b.item)));
|
|
2422
|
+
}
|
|
2423
|
+
var hostKey = (item) => item.instance ?? item.id;
|
|
2424
|
+
function createPlacement(getRoot, slots, afterPlace) {
|
|
2425
|
+
const hosts = /* @__PURE__ */ new Map();
|
|
2426
|
+
let placing = false;
|
|
2427
|
+
const place = () => {
|
|
2428
|
+
const root = getRoot();
|
|
2429
|
+
if (!root || placing) return;
|
|
2430
|
+
placing = true;
|
|
2431
|
+
try {
|
|
2432
|
+
slots.measure();
|
|
2433
|
+
const base = root.getBoundingClientRect();
|
|
2434
|
+
for (const { element, paneId } of hosts.values()) {
|
|
2435
|
+
const slot = slots.slot(paneId);
|
|
2436
|
+
if (!slot) {
|
|
2437
|
+
element.dataset.placed = "false";
|
|
2438
|
+
continue;
|
|
2439
|
+
}
|
|
2440
|
+
const box = slot.getBoundingClientRect();
|
|
2441
|
+
const style = element.style;
|
|
2442
|
+
style.left = `${box.left - base.left}px`;
|
|
2443
|
+
style.top = `${box.top - base.top}px`;
|
|
2444
|
+
style.width = `${box.width}px`;
|
|
2445
|
+
style.height = `${box.height}px`;
|
|
2446
|
+
element.dataset.placed = "true";
|
|
2447
|
+
}
|
|
2448
|
+
} finally {
|
|
2449
|
+
placing = false;
|
|
2450
|
+
}
|
|
2451
|
+
afterPlace();
|
|
2452
|
+
};
|
|
2453
|
+
return {
|
|
2454
|
+
register(key, entry) {
|
|
2455
|
+
hosts.set(key, entry);
|
|
2456
|
+
return () => {
|
|
2457
|
+
if (hosts.get(key) === entry) hosts.delete(key);
|
|
2458
|
+
};
|
|
2459
|
+
},
|
|
2460
|
+
place
|
|
2461
|
+
};
|
|
2462
|
+
}
|
|
2463
|
+
function CanvasBodyLayer() {
|
|
2464
|
+
const { windowId, layer } = useCanvasInternals();
|
|
2465
|
+
const slots = useCanvasSlots();
|
|
2466
|
+
const state = useCanvasState((s) => s);
|
|
2467
|
+
const root = useRef5(null);
|
|
2468
|
+
const [placement] = useState4(
|
|
2469
|
+
() => slots ? createPlacement(() => root.current, slots, () => layer.native.remeasure()) : null
|
|
2470
|
+
);
|
|
2471
|
+
useIsoLayoutEffect(() => placement?.place());
|
|
2472
|
+
useIsoLayoutEffect(() => slots && placement ? slots.subscribe(placement.place) : void 0, [slots, placement]);
|
|
2473
|
+
if (!slots || !placement) return null;
|
|
2474
|
+
const frame2 = canvasWindowFrame(state, windowId);
|
|
2475
|
+
if (!frame2) return null;
|
|
2476
|
+
const paneCount = listPaneIds(frame2.layout).length;
|
|
2477
|
+
return /* @__PURE__ */ jsx6("div", { ref: root, className: "mxc-bodies", "data-mxc-bodies": windowId, children: placedItems(state, windowId).map(({ item, paneId, active }) => /* @__PURE__ */ jsx6(
|
|
2478
|
+
BodyHost,
|
|
2479
|
+
{
|
|
2480
|
+
hostId: hostKey(item),
|
|
2481
|
+
item,
|
|
2482
|
+
paneId,
|
|
2483
|
+
visible: frame2.isOpen && active,
|
|
2484
|
+
focused: active && frame2.focusedPaneId === paneId,
|
|
2485
|
+
isFullscreen: frame2.isFullscreen,
|
|
2486
|
+
paneCount,
|
|
2487
|
+
slots,
|
|
2488
|
+
placement
|
|
2489
|
+
},
|
|
2490
|
+
hostKey(item)
|
|
2491
|
+
)) });
|
|
2492
|
+
}
|
|
2493
|
+
function CanvasPaneInlineBodies({ paneId, slots }) {
|
|
2494
|
+
const { windowId } = useCanvasInternals();
|
|
2495
|
+
const state = useCanvasState((s) => s);
|
|
2496
|
+
const frame2 = canvasWindowFrame(state, windowId);
|
|
2497
|
+
const paneCount = frame2 ? listPaneIds(frame2.layout).length : 1;
|
|
2498
|
+
return /* @__PURE__ */ jsx6(Fragment, { children: placedItems(state, windowId, paneId).map(({ item, active }) => /* @__PURE__ */ jsx6(
|
|
2499
|
+
BodyHost,
|
|
2500
|
+
{
|
|
2501
|
+
hostId: hostKey(item),
|
|
2502
|
+
item,
|
|
2503
|
+
paneId,
|
|
2504
|
+
visible: active,
|
|
2505
|
+
focused: active && frame2?.focusedPaneId === paneId,
|
|
2506
|
+
isFullscreen: frame2?.isFullscreen ?? false,
|
|
2507
|
+
paneCount,
|
|
2508
|
+
slots,
|
|
2509
|
+
placement: null
|
|
2510
|
+
},
|
|
2511
|
+
hostKey(item)
|
|
2512
|
+
)) });
|
|
2513
|
+
}
|
|
2514
|
+
function BodyHost({
|
|
2515
|
+
hostId,
|
|
2516
|
+
item,
|
|
2517
|
+
paneId,
|
|
2518
|
+
visible,
|
|
2519
|
+
focused,
|
|
2520
|
+
isFullscreen,
|
|
2521
|
+
paneCount,
|
|
2522
|
+
slots,
|
|
2523
|
+
placement
|
|
2524
|
+
}) {
|
|
2525
|
+
const canvas = useCanvas();
|
|
2526
|
+
const { windowId, ports } = useCanvasInternals();
|
|
2527
|
+
const kind = useItemKind(item.kind);
|
|
2528
|
+
const element = useRef5(null);
|
|
2529
|
+
const [everShown, setEverShown] = useState4(visible);
|
|
2530
|
+
if (visible && !everShown) setEverShown(true);
|
|
2531
|
+
const mounted = kind?.unmountWhenHidden ? visible : everShown;
|
|
2532
|
+
const presentation = usePanePresentation(slots, paneId, isFullscreen, paneCount);
|
|
2533
|
+
const drop = usePaneDrop(paneId, () => slots.section(paneId));
|
|
2534
|
+
useIsoLayoutEffect(() => {
|
|
2535
|
+
const el = element.current;
|
|
2536
|
+
if (!placement || !el) return void 0;
|
|
2537
|
+
return placement.register(hostId, { element: el, paneId });
|
|
2538
|
+
}, [placement, hostId, paneId]);
|
|
2539
|
+
return /* @__PURE__ */ jsx6(
|
|
2540
|
+
"div",
|
|
2541
|
+
{
|
|
2542
|
+
ref: element,
|
|
2543
|
+
className: "mxc-item-host",
|
|
2544
|
+
"data-inline": placement ? void 0 : "",
|
|
2545
|
+
"data-placed": placement ? "false" : void 0,
|
|
2546
|
+
"data-visible": visible ? "true" : "false",
|
|
2547
|
+
"data-kind": item.kind,
|
|
2548
|
+
"data-item": item.id,
|
|
2549
|
+
"data-pane": paneId,
|
|
2550
|
+
inert: !visible,
|
|
2551
|
+
"aria-hidden": visible ? void 0 : true,
|
|
2552
|
+
onPointerDownCapture: () => {
|
|
2553
|
+
if (!focused) canvas.focusPane(paneId);
|
|
2554
|
+
},
|
|
2555
|
+
...drop,
|
|
2556
|
+
children: mounted ? /* @__PURE__ */ jsx6(CanvasPresentationContext.Provider, { value: presentation, children: /* @__PURE__ */ jsx6("div", { className: "mxc-item", "data-kind": item.kind, children: /* @__PURE__ */ jsx6(
|
|
2557
|
+
ItemContent,
|
|
2558
|
+
{
|
|
2559
|
+
item,
|
|
2560
|
+
kind,
|
|
2561
|
+
paneId,
|
|
2562
|
+
windowId,
|
|
2563
|
+
focused,
|
|
2564
|
+
visible,
|
|
2565
|
+
presentation,
|
|
2566
|
+
canvas,
|
|
2567
|
+
hasNativePort: !!ports.onNativeBodyFrame
|
|
2568
|
+
}
|
|
2569
|
+
) }) }) : null
|
|
2570
|
+
}
|
|
2571
|
+
);
|
|
2572
|
+
}
|
|
2573
|
+
function ItemContent({
|
|
2574
|
+
item,
|
|
2575
|
+
kind,
|
|
2576
|
+
paneId,
|
|
2577
|
+
windowId,
|
|
2578
|
+
focused,
|
|
2579
|
+
visible,
|
|
2580
|
+
presentation,
|
|
2581
|
+
canvas,
|
|
2582
|
+
hasNativePort
|
|
2583
|
+
}) {
|
|
2584
|
+
let unavailable = null;
|
|
2585
|
+
try {
|
|
2586
|
+
unavailable = kind?.unavailable?.(item.data, item) ?? null;
|
|
2587
|
+
} catch (error) {
|
|
2588
|
+
console.error(`[@ai-matrx/canvas] "${item.kind}" unavailable() threw`, error);
|
|
2589
|
+
}
|
|
2590
|
+
if (!kind) return /* @__PURE__ */ jsx6(ItemProblem, { message: "This item can no longer be shown.", onClose: () => canvas.close(item.id) });
|
|
2591
|
+
if (unavailable) return /* @__PURE__ */ jsx6(CanvasPanelEmpty, { children: unavailable });
|
|
2592
|
+
const rendered = kindComponent(kind) ? /* @__PURE__ */ jsx6(Rendered, { kind, item, paneId, isFocused: focused, isVisible: visible, canvas, windowId, presentation }) : null;
|
|
2593
|
+
if (kind.body === "native") {
|
|
2594
|
+
return hasNativePort ? /* @__PURE__ */ jsx6(NativeBody, { itemId: item.id, windowId, visible, children: rendered }) : /* @__PURE__ */ jsx6(CanvasPanelEmpty, { children: "Opens in the desktop app" });
|
|
2595
|
+
}
|
|
2596
|
+
return rendered;
|
|
2597
|
+
}
|
|
2598
|
+
var Rendered = memo(function Rendered2({
|
|
2599
|
+
kind,
|
|
2600
|
+
item,
|
|
2601
|
+
paneId,
|
|
2602
|
+
isFocused,
|
|
2603
|
+
isVisible,
|
|
2604
|
+
canvas,
|
|
2605
|
+
windowId,
|
|
2606
|
+
presentation
|
|
2607
|
+
}) {
|
|
2608
|
+
const props = { item, data: item.data, paneId, isFocused, isVisible, canvas, windowId, presentation };
|
|
2609
|
+
return /* @__PURE__ */ jsx6(ItemBoundary, { item, onClose: () => canvas.close(item.id), onRetry: () => resetKindComponent(kind), children: /* @__PURE__ */ jsx6(Suspense, { fallback: /* @__PURE__ */ jsx6("div", { className: "mxc-loading", "aria-busy": "true" }), children: /* @__PURE__ */ jsx6(KindRender, { kind, props }) }) });
|
|
2610
|
+
});
|
|
2611
|
+
function KindRender({ kind, props }) {
|
|
2612
|
+
const Render = kindComponent(kind);
|
|
2613
|
+
if (!Render) throw new Error(`[@ai-matrx/canvas] kind "${kind.id}" has nothing to render.`);
|
|
2614
|
+
return /* @__PURE__ */ jsx6(Render, { ...props });
|
|
2615
|
+
}
|
|
2616
|
+
function NativeBody({ itemId, windowId, visible, children }) {
|
|
2617
|
+
const { layer } = useCanvasInternals();
|
|
2618
|
+
const ref = useRef5(null);
|
|
2619
|
+
const measure = useRef5(() => void 0);
|
|
2620
|
+
measure.current = () => {
|
|
2621
|
+
const el = ref.current;
|
|
2622
|
+
const box = visible && el?.isConnected ? el.getBoundingClientRect() : null;
|
|
2623
|
+
if (!box || box.width <= 0 || box.height <= 0) {
|
|
2624
|
+
layer.native.report(itemId, null, { visible: false, windowId });
|
|
2625
|
+
return;
|
|
2626
|
+
}
|
|
2627
|
+
const rect = { ...toCanvasRect(box), devicePixelRatio: window.devicePixelRatio || 1 };
|
|
2628
|
+
layer.native.report(itemId, rect, { visible: !layer.isBlocked(), windowId });
|
|
2629
|
+
};
|
|
2630
|
+
useIsoLayoutEffect(() => measure.current());
|
|
2631
|
+
useIsoLayoutEffect(() => {
|
|
2632
|
+
const el = ref.current;
|
|
2633
|
+
const run = () => measure.current();
|
|
2634
|
+
const observer = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(run);
|
|
2635
|
+
if (el) observer?.observe(el);
|
|
2636
|
+
const unregister = layer.native.register(run);
|
|
2637
|
+
window.addEventListener("resize", run);
|
|
2638
|
+
return () => {
|
|
2639
|
+
observer?.disconnect();
|
|
2640
|
+
unregister();
|
|
2641
|
+
window.removeEventListener("resize", run);
|
|
2642
|
+
layer.native.report(itemId, null, { visible: false, windowId });
|
|
2643
|
+
};
|
|
2644
|
+
}, [itemId, windowId, layer]);
|
|
2645
|
+
return /* @__PURE__ */ jsx6("div", { ref, className: "mxc-native-body", "data-native-body": itemId, children });
|
|
2646
|
+
}
|
|
2647
|
+
function ItemProblem({ message, onClose, onRetry }) {
|
|
2648
|
+
return /* @__PURE__ */ jsxs5("div", { className: "mxc-problem", role: "alert", children: [
|
|
2649
|
+
/* @__PURE__ */ jsx6(AlertIcon, {}),
|
|
2650
|
+
/* @__PURE__ */ jsx6("p", { children: message }),
|
|
2651
|
+
/* @__PURE__ */ jsxs5("div", { className: "mxc-problem-actions", children: [
|
|
2652
|
+
onRetry ? /* @__PURE__ */ jsx6("button", { type: "button", className: "mxc-text-button", onClick: onRetry, children: "Try again" }) : null,
|
|
2653
|
+
/* @__PURE__ */ jsx6("button", { type: "button", className: "mxc-text-button", onClick: onClose, children: "Close tab" })
|
|
2654
|
+
] })
|
|
2655
|
+
] });
|
|
2656
|
+
}
|
|
2657
|
+
var ItemBoundary = class extends Component {
|
|
2658
|
+
state = { error: null };
|
|
2659
|
+
static getDerivedStateFromError(error) {
|
|
2660
|
+
return { error };
|
|
2661
|
+
}
|
|
2662
|
+
componentDidCatch(error, info) {
|
|
2663
|
+
console.error(`[@ai-matrx/canvas] "${this.props.item.kind}" failed to render`, error, info.componentStack);
|
|
2664
|
+
}
|
|
2665
|
+
render() {
|
|
2666
|
+
if (this.state.error) {
|
|
2667
|
+
return /* @__PURE__ */ jsx6(
|
|
2668
|
+
ItemProblem,
|
|
2669
|
+
{
|
|
2670
|
+
message: "This item failed to load.",
|
|
2671
|
+
onClose: this.props.onClose,
|
|
2672
|
+
onRetry: () => {
|
|
2673
|
+
this.props.onRetry();
|
|
2674
|
+
this.setState({ error: null });
|
|
2675
|
+
}
|
|
2676
|
+
}
|
|
2677
|
+
);
|
|
2678
|
+
}
|
|
2679
|
+
return this.props.children;
|
|
2680
|
+
}
|
|
2681
|
+
};
|
|
2682
|
+
|
|
2683
|
+
// src/react/CanvasPane.tsx
|
|
2684
|
+
import { Component as Component2, useEffect as useEffect6, useLayoutEffect as useLayoutEffect3, useRef as useRef6, useState as useState5 } from "react";
|
|
2685
|
+
import * as Menu from "@radix-ui/react-dropdown-menu";
|
|
2686
|
+
import { Fragment as Fragment2, jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
2687
|
+
var useIsoLayoutEffect2 = typeof window === "undefined" ? useEffect6 : useLayoutEffect3;
|
|
1963
2688
|
function itemTitle(item, kind) {
|
|
1964
2689
|
if (item.title) return item.title;
|
|
2690
|
+
if (item.kind === CANVAS_LAUNCHER_KIND) return "New tab";
|
|
1965
2691
|
if (kind?.title) {
|
|
1966
2692
|
try {
|
|
1967
2693
|
const title = kind.title(item.data, item);
|
|
@@ -1972,69 +2698,44 @@ function itemTitle(item, kind) {
|
|
|
1972
2698
|
return kind?.label ?? item.kind;
|
|
1973
2699
|
}
|
|
1974
2700
|
var isMac = () => typeof navigator !== "undefined" && /mac|iphone|ipad/i.test(navigator.platform ?? "");
|
|
1975
|
-
function useTabDrag(itemId, title) {
|
|
1976
|
-
const { layer } = useCanvasInternals();
|
|
1977
|
-
const active = useRef4([]);
|
|
1978
|
-
const end = () => {
|
|
1979
|
-
for (const dispose of active.current.splice(0)) dispose();
|
|
1980
|
-
};
|
|
1981
|
-
useEffect4(() => end, []);
|
|
1982
|
-
return {
|
|
1983
|
-
draggable: true,
|
|
1984
|
-
onDragStart: (event) => {
|
|
1985
|
-
event.dataTransfer.setData(DRAG_MIME, itemId);
|
|
1986
|
-
event.dataTransfer.effectAllowed = "move";
|
|
1987
|
-
end();
|
|
1988
|
-
active.current.push(layer.beginBlocking(), layer.show({ type: "tab-drag", itemId, title }));
|
|
1989
|
-
},
|
|
1990
|
-
onDragEnd: end
|
|
1991
|
-
};
|
|
1992
|
-
}
|
|
1993
2701
|
function CanvasPaneView({ paneId }) {
|
|
1994
2702
|
const canvas = useCanvas();
|
|
1995
|
-
const { windowId,
|
|
2703
|
+
const { windowId, tabStrip } = useCanvasInternals();
|
|
2704
|
+
const columnSlots = useCanvasSlots();
|
|
2705
|
+
const [ownSlots] = useState5(createCanvasSlots);
|
|
2706
|
+
const slots = columnSlots ?? ownSlots;
|
|
1996
2707
|
const pane = useCanvasState((s) => s.panes[paneId]);
|
|
1997
2708
|
const items = useCanvasState((s) => s.items);
|
|
1998
2709
|
const isFullscreen = useCanvasState((s) => selectCanvasIsFullscreen(s, windowId));
|
|
2710
|
+
const isOpen = useCanvasState((s) => selectCanvasIsOpen(s, windowId));
|
|
1999
2711
|
const focused = useCanvasState((s) => selectCanvasFocusedPaneId(s, windowId) === paneId);
|
|
2000
2712
|
const paneCount = useCanvasState((s) => selectCanvasPaneCount(s, windowId));
|
|
2001
2713
|
const role = useCanvasState((s) => windowId === CANVAS_MAIN_WINDOW ? "main" : s.windows[windowId]?.role ?? "window");
|
|
2002
2714
|
const active = pane?.activeItemId ? items[pane.activeItemId] : void 0;
|
|
2003
|
-
const
|
|
2004
|
-
const
|
|
2005
|
-
const sectionRef =
|
|
2006
|
-
|
|
2715
|
+
const canGoBack = useCanvasState((s) => active ? selectCanvasCanGoBack(s, active.id) : false);
|
|
2716
|
+
const kind = useItemKind(active?.kind ?? "");
|
|
2717
|
+
const sectionRef = useRef6(null);
|
|
2718
|
+
const bodyRef = useRef6(null);
|
|
2719
|
+
const presentation = usePanePresentation(slots, paneId, isFullscreen, paneCount);
|
|
2720
|
+
const drop = usePaneDrop(paneId, () => sectionRef.current);
|
|
2721
|
+
useIsoLayoutEffect2(() => {
|
|
2722
|
+
const body = bodyRef.current;
|
|
2723
|
+
const section = sectionRef.current;
|
|
2724
|
+
if (body) slots.set(paneId, body);
|
|
2725
|
+
slots.setSection(paneId, section);
|
|
2726
|
+
return () => {
|
|
2727
|
+
slots.unset(paneId, body);
|
|
2728
|
+
if (slots.section(paneId) === section) slots.setSection(paneId, null);
|
|
2729
|
+
};
|
|
2730
|
+
}, [slots, paneId]);
|
|
2007
2731
|
if (!pane) return null;
|
|
2008
|
-
const kindProps = active && kind ? { item: active, data: active.data, paneId, isFocused: focused, canvas, windowId } : null;
|
|
2732
|
+
const kindProps = active && kind ? { item: active, data: active.data, paneId, isFocused: focused, isVisible: isOpen, canvas, windowId, presentation } : null;
|
|
2009
2733
|
const Leading = kind?.HeaderLeading;
|
|
2010
2734
|
const Action = kind?.HeaderAction;
|
|
2735
|
+
const Toolbar = kind?.Toolbar;
|
|
2011
2736
|
const single = tabStrip === "auto" && pane.itemIds.length === 1 && active;
|
|
2012
|
-
const hideDrop = () => {
|
|
2013
|
-
drop.current?.();
|
|
2014
|
-
drop.current = null;
|
|
2015
|
-
};
|
|
2016
|
-
const onDragOver = (event) => {
|
|
2017
|
-
if (!event.dataTransfer.types.includes(DRAG_MIME)) return;
|
|
2018
|
-
event.preventDefault();
|
|
2019
|
-
event.dataTransfer.dropEffect = "move";
|
|
2020
|
-
if (!drop.current && sectionRef.current) {
|
|
2021
|
-
drop.current = layer.show({ type: "drop-indicator", rect: toCanvasRect(sectionRef.current.getBoundingClientRect()), paneId });
|
|
2022
|
-
}
|
|
2023
|
-
};
|
|
2024
|
-
const onDragLeave = (event) => {
|
|
2025
|
-
const next = event.relatedTarget;
|
|
2026
|
-
if (next && sectionRef.current?.contains(next)) return;
|
|
2027
|
-
hideDrop();
|
|
2028
|
-
};
|
|
2029
|
-
const onDrop = (event) => {
|
|
2030
|
-
hideDrop();
|
|
2031
|
-
const itemId = event.dataTransfer.getData(DRAG_MIME);
|
|
2032
|
-
if (!itemId) return;
|
|
2033
|
-
event.preventDefault();
|
|
2034
|
-
canvas.moveItem(itemId, paneId);
|
|
2035
|
-
};
|
|
2036
2737
|
const closeLabel = role === "pop-out" && paneCount === 1 ? "Close window" : paneCount > 1 ? "Close pane" : "Close canvas";
|
|
2037
|
-
return /* @__PURE__ */
|
|
2738
|
+
return /* @__PURE__ */ jsxs6(
|
|
2038
2739
|
CanvasPanel,
|
|
2039
2740
|
{
|
|
2040
2741
|
ref: sectionRef,
|
|
@@ -2043,10 +2744,14 @@ function CanvasPaneView({ paneId }) {
|
|
|
2043
2744
|
title: active ? itemTitle(active, kind) : "Canvas",
|
|
2044
2745
|
focused,
|
|
2045
2746
|
"data-mxc-pane": paneId,
|
|
2046
|
-
header:
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2747
|
+
header: /* @__PURE__ */ jsxs6(Fragment2, { children: [
|
|
2748
|
+
canGoBack && active ? /* @__PURE__ */ jsx7(CanvasHeaderButton, { className: "mxc-back", label: "Back", onClick: () => void canvas.back(active.id), children: /* @__PURE__ */ jsx7(ArrowLeftIcon, {}) }) : null,
|
|
2749
|
+
single && active ? /* @__PURE__ */ jsx7(SingleTitle, { item: active, kind }) : pane.itemIds.length > 0 ? /* @__PURE__ */ jsx7(TabStrip, { paneId, itemIds: pane.itemIds, activeItemId: pane.activeItemId }) : /* @__PURE__ */ jsx7("span", { className: "mxc-panel-title", children: "New tab" }),
|
|
2750
|
+
/* @__PURE__ */ jsx7(CanvasHeaderButton, { className: "mxc-new-tab", label: "New tab", onClick: () => void canvas.newTab(paneId), children: /* @__PURE__ */ jsx7(PlusIcon, {}) })
|
|
2751
|
+
] }),
|
|
2752
|
+
leading: Leading && kindProps ? /* @__PURE__ */ jsx7(Leading, { ...kindProps }) : null,
|
|
2753
|
+
actions: Action && kindProps ? /* @__PURE__ */ jsx7(Action, { ...kindProps }) : null,
|
|
2754
|
+
renderMenu: () => /* @__PURE__ */ jsx7(PaneMenu, { paneId, windowId, activeItem: active, kind, kindProps, tabCount: pane.itemIds.length, role }),
|
|
2050
2755
|
expanded: isFullscreen,
|
|
2051
2756
|
onToggleExpand: () => canvas.setFullscreen(!isFullscreen),
|
|
2052
2757
|
expandShortcut: canvasShortcutHint("canvas.toggleFullscreen", isMac()),
|
|
@@ -2056,54 +2761,92 @@ function CanvasPaneView({ paneId }) {
|
|
|
2056
2761
|
onPointerDownCapture: () => {
|
|
2057
2762
|
if (!focused) canvas.focusPane(paneId);
|
|
2058
2763
|
},
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
if (!item) return null;
|
|
2065
|
-
const isActive = itemId === pane.activeItemId;
|
|
2066
|
-
return /* @__PURE__ */ jsx5(ItemBody, { item, paneId, windowId, isActive, isFocused: focused && isActive, hasNativePort: !!ports.onNativeBodyFrame }, itemId);
|
|
2067
|
-
})
|
|
2764
|
+
...drop,
|
|
2765
|
+
children: [
|
|
2766
|
+
Toolbar && kindProps ? /* @__PURE__ */ jsx7("div", { className: "mxc-item-toolbar", "data-kind": active?.kind, children: /* @__PURE__ */ jsx7(ToolbarBoundary, { kindId: kind?.id ?? "", children: /* @__PURE__ */ jsx7(Toolbar, { ...kindProps }) }) }) : null,
|
|
2767
|
+
/* @__PURE__ */ jsx7("div", { ref: bodyRef, className: "mxc-pane-slot", "data-mxc-slot": paneId, children: pane.itemIds.length === 0 ? /* @__PURE__ */ jsx7(EmptyPaneLauncher, { paneId }) : columnSlots ? null : /* @__PURE__ */ jsx7(CanvasPaneInlineBodies, { paneId, slots }) })
|
|
2768
|
+
]
|
|
2068
2769
|
}
|
|
2069
2770
|
);
|
|
2070
2771
|
}
|
|
2772
|
+
var ToolbarBoundary = class extends Component2 {
|
|
2773
|
+
state = { failed: false };
|
|
2774
|
+
static getDerivedStateFromError() {
|
|
2775
|
+
return { failed: true };
|
|
2776
|
+
}
|
|
2777
|
+
componentDidCatch(error) {
|
|
2778
|
+
console.error(`[@ai-matrx/canvas] "${this.props.kindId}" Toolbar threw`, error);
|
|
2779
|
+
}
|
|
2780
|
+
render() {
|
|
2781
|
+
return this.state.failed ? null : this.props.children;
|
|
2782
|
+
}
|
|
2783
|
+
};
|
|
2071
2784
|
function SingleTitle({ item, kind }) {
|
|
2785
|
+
const canvas = useCanvas();
|
|
2072
2786
|
const title = itemTitle(item, kind);
|
|
2073
2787
|
const drag = useTabDrag(item.id, title);
|
|
2074
2788
|
const Icon = kind?.icon ?? AlertIcon;
|
|
2075
|
-
return /* @__PURE__ */
|
|
2076
|
-
/* @__PURE__ */
|
|
2077
|
-
/* @__PURE__ */
|
|
2789
|
+
return /* @__PURE__ */ jsxs6("span", { className: "mxc-panel-title mxc-single-title", "data-draggable": "", title, ...drag, children: [
|
|
2790
|
+
/* @__PURE__ */ jsx7(Icon, { className: "mxc-title-icon" }),
|
|
2791
|
+
/* @__PURE__ */ jsx7("span", { className: "mxc-tab-title", children: title }),
|
|
2792
|
+
/* @__PURE__ */ jsx7("button", { type: "button", className: "mxc-tab-close", "data-slot": "browser-tab-close", "aria-label": `Close ${title}`, onClick: () => canvas.close(item.id), children: /* @__PURE__ */ jsx7(CloseIcon, {}) })
|
|
2078
2793
|
] });
|
|
2079
2794
|
}
|
|
2795
|
+
function TabStrip({ paneId, itemIds, activeItemId }) {
|
|
2796
|
+
const strip = useRef6(null);
|
|
2797
|
+
useIsoLayoutEffect2(() => {
|
|
2798
|
+
const el = strip.current;
|
|
2799
|
+
if (!el) return void 0;
|
|
2800
|
+
const reveal = () => {
|
|
2801
|
+
const tab = el.querySelector('[role="tab"][aria-selected="true"]');
|
|
2802
|
+
if (!tab) return;
|
|
2803
|
+
const left = tab.offsetLeft - el.offsetLeft;
|
|
2804
|
+
const right = left + tab.offsetWidth;
|
|
2805
|
+
if (left < el.scrollLeft) el.scrollLeft = left;
|
|
2806
|
+
else if (right > el.scrollLeft + el.clientWidth) el.scrollLeft = right - el.clientWidth;
|
|
2807
|
+
};
|
|
2808
|
+
reveal();
|
|
2809
|
+
const observer = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(reveal);
|
|
2810
|
+
observer?.observe(el);
|
|
2811
|
+
return () => observer?.disconnect();
|
|
2812
|
+
}, [activeItemId, itemIds.length]);
|
|
2813
|
+
return /* @__PURE__ */ jsx7(
|
|
2814
|
+
"div",
|
|
2815
|
+
{
|
|
2816
|
+
ref: strip,
|
|
2817
|
+
className: "mxc-tabs",
|
|
2818
|
+
role: "tablist",
|
|
2819
|
+
"aria-label": "Canvas tabs",
|
|
2820
|
+
onWheel: (event) => {
|
|
2821
|
+
const el = strip.current;
|
|
2822
|
+
if (el && Math.abs(event.deltaY) > Math.abs(event.deltaX)) el.scrollLeft += event.deltaY;
|
|
2823
|
+
},
|
|
2824
|
+
children: itemIds.map((itemId, index) => /* @__PURE__ */ jsx7(ItemTab, { itemId, paneId, index, active: itemId === activeItemId }, itemId))
|
|
2825
|
+
}
|
|
2826
|
+
);
|
|
2827
|
+
}
|
|
2080
2828
|
function ItemTab({ itemId, paneId, index, active }) {
|
|
2081
2829
|
const canvas = useCanvas();
|
|
2082
2830
|
const item = useCanvasState((s) => s.items[itemId]);
|
|
2083
|
-
const kind =
|
|
2084
|
-
const ref = useRef4(null);
|
|
2831
|
+
const kind = useItemKind(item?.kind ?? "");
|
|
2085
2832
|
const title = item ? itemTitle(item, kind) : "";
|
|
2086
2833
|
const drag = useTabDrag(itemId, title);
|
|
2087
|
-
useEffect4(() => {
|
|
2088
|
-
if (active) ref.current?.scrollIntoView?.({ block: "nearest", inline: "nearest" });
|
|
2089
|
-
}, [active]);
|
|
2090
2834
|
if (!item) return null;
|
|
2091
2835
|
const Icon = kind?.icon ?? AlertIcon;
|
|
2092
|
-
return /* @__PURE__ */
|
|
2836
|
+
return /* @__PURE__ */ jsx7(
|
|
2093
2837
|
CanvasTab,
|
|
2094
2838
|
{
|
|
2095
|
-
ref,
|
|
2096
2839
|
title,
|
|
2097
|
-
icon: /* @__PURE__ */
|
|
2840
|
+
icon: /* @__PURE__ */ jsx7(Icon, {}),
|
|
2098
2841
|
active,
|
|
2099
2842
|
onSelect: () => canvas.activate(itemId),
|
|
2100
2843
|
onClose: () => canvas.close(itemId),
|
|
2101
2844
|
...drag,
|
|
2102
2845
|
onDragOver: (event) => {
|
|
2103
|
-
if (event.dataTransfer.types.includes(
|
|
2846
|
+
if (event.dataTransfer.types.includes(CANVAS_DRAG_MIME)) event.preventDefault();
|
|
2104
2847
|
},
|
|
2105
2848
|
onDrop: (event) => {
|
|
2106
|
-
const dragged = event.dataTransfer.getData(
|
|
2849
|
+
const dragged = event.dataTransfer.getData(CANVAS_DRAG_MIME);
|
|
2107
2850
|
if (!dragged) return;
|
|
2108
2851
|
event.preventDefault();
|
|
2109
2852
|
if (dragged !== itemId) canvas.moveItem(dragged, paneId, index);
|
|
@@ -2126,16 +2869,16 @@ function PaneMenu({
|
|
|
2126
2869
|
const canvas = useCanvas();
|
|
2127
2870
|
const { ports, layer } = useCanvasInternals();
|
|
2128
2871
|
const moveId = tabCount > 1 ? activeItem?.id : void 0;
|
|
2129
|
-
const blocking =
|
|
2130
|
-
|
|
2872
|
+
const blocking = useRef6(null);
|
|
2873
|
+
useEffect6(() => () => blocking.current?.(), []);
|
|
2131
2874
|
const kindItems = kind?.menuItems && kindProps ? safeMenu(() => kind.menuItems?.(kindProps)) : [];
|
|
2132
2875
|
const hostItems = activeItem && ports.itemActions ? safeMenu(() => ports.itemActions?.(activeItem, kind)) : [];
|
|
2133
2876
|
const sections = [
|
|
2134
2877
|
[...kindItems, ...hostItems].map((entry) => ({ ...entry, id: `item:${entry.id}` })),
|
|
2135
2878
|
[
|
|
2136
|
-
{ id: "split-right", label: "Split right", icon: /* @__PURE__ */
|
|
2137
|
-
{ id: "split-down", label: "Split down", icon: /* @__PURE__ */
|
|
2138
|
-
...activeItem && role === "pop-out" ? [{ id: "dock-back", label: "Dock back", icon: /* @__PURE__ */
|
|
2879
|
+
{ id: "split-right", label: "Split right", icon: /* @__PURE__ */ jsx7(SplitRightIcon, {}), onSelect: () => canvas.splitPane(paneId, "horizontal", moveId) },
|
|
2880
|
+
{ id: "split-down", label: "Split down", icon: /* @__PURE__ */ jsx7(SplitDownIcon, {}), onSelect: () => canvas.splitPane(paneId, "vertical", moveId) },
|
|
2881
|
+
...activeItem && role === "pop-out" ? [{ id: "dock-back", label: "Dock back", icon: /* @__PURE__ */ jsx7(DockIcon, {}), onSelect: () => void canvas.dockBack(activeItem.id) }] : activeItem && ports.popOut ? [{ id: "pop-out", label: "Pop out", icon: /* @__PURE__ */ jsx7(PopOutIcon, {}), onSelect: () => void canvas.popOut(activeItem.id) }] : []
|
|
2139
2882
|
],
|
|
2140
2883
|
activeItem ? [
|
|
2141
2884
|
{ id: "close-tab", label: "Close tab", onSelect: () => canvas.close(activeItem.id) },
|
|
@@ -2153,7 +2896,7 @@ function PaneMenu({
|
|
|
2153
2896
|
handlers.set(action.id, action.onSelect);
|
|
2154
2897
|
}
|
|
2155
2898
|
});
|
|
2156
|
-
return /* @__PURE__ */
|
|
2899
|
+
return /* @__PURE__ */ jsx7(
|
|
2157
2900
|
CanvasMenuTrigger,
|
|
2158
2901
|
{
|
|
2159
2902
|
onClick: (event) => {
|
|
@@ -2168,7 +2911,7 @@ function PaneMenu({
|
|
|
2168
2911
|
}
|
|
2169
2912
|
);
|
|
2170
2913
|
}
|
|
2171
|
-
return /* @__PURE__ */
|
|
2914
|
+
return /* @__PURE__ */ jsxs6(
|
|
2172
2915
|
Menu.Root,
|
|
2173
2916
|
{
|
|
2174
2917
|
onOpenChange: (open) => {
|
|
@@ -2176,16 +2919,16 @@ function PaneMenu({
|
|
|
2176
2919
|
blocking.current = open ? layer.beginBlocking() : null;
|
|
2177
2920
|
},
|
|
2178
2921
|
children: [
|
|
2179
|
-
/* @__PURE__ */
|
|
2180
|
-
/* @__PURE__ */
|
|
2922
|
+
/* @__PURE__ */ jsx7(Menu.Trigger, { asChild: true, children: /* @__PURE__ */ jsx7(CanvasMenuTrigger, {}) }),
|
|
2923
|
+
/* @__PURE__ */ jsx7(Menu.Portal, { children: /* @__PURE__ */ jsx7(Menu.Content, { align: "end", sideOffset: 4, className: "mxc-menu", children: sections.map((section, i) => /* @__PURE__ */ jsx7(MenuSection, { first: i === 0, actions: section }, i)) }) })
|
|
2181
2924
|
]
|
|
2182
2925
|
}
|
|
2183
2926
|
);
|
|
2184
2927
|
}
|
|
2185
2928
|
function MenuSection({ first, actions }) {
|
|
2186
|
-
return /* @__PURE__ */
|
|
2187
|
-
first ? null : /* @__PURE__ */
|
|
2188
|
-
actions.map((action) => /* @__PURE__ */
|
|
2929
|
+
return /* @__PURE__ */ jsxs6(Fragment2, { children: [
|
|
2930
|
+
first ? null : /* @__PURE__ */ jsx7(Menu.Separator, { className: "mxc-menu-separator" }),
|
|
2931
|
+
actions.map((action) => /* @__PURE__ */ jsxs6(Menu.Item, { onSelect: action.onSelect, className: "mxc-menu-item", "data-destructive": action.destructive ? "" : void 0, children: [
|
|
2189
2932
|
action.icon,
|
|
2190
2933
|
action.label
|
|
2191
2934
|
] }, action.id))
|
|
@@ -2199,150 +2942,16 @@ function safeMenu(build) {
|
|
|
2199
2942
|
return [];
|
|
2200
2943
|
}
|
|
2201
2944
|
}
|
|
2202
|
-
function ItemBody({
|
|
2203
|
-
item,
|
|
2204
|
-
paneId,
|
|
2205
|
-
windowId,
|
|
2206
|
-
isActive,
|
|
2207
|
-
isFocused,
|
|
2208
|
-
hasNativePort
|
|
2209
|
-
}) {
|
|
2210
|
-
const canvas = useCanvas();
|
|
2211
|
-
const kind = useCanvasKind(item.kind);
|
|
2212
|
-
if (!isActive && !kind?.keepAlive) return null;
|
|
2213
|
-
const props = { item, data: item.data, paneId, isFocused, canvas, windowId };
|
|
2214
|
-
let unavailable = null;
|
|
2215
|
-
try {
|
|
2216
|
-
unavailable = kind?.unavailable?.(item.data, item) ?? null;
|
|
2217
|
-
} catch (error) {
|
|
2218
|
-
console.error(`[@ai-matrx/canvas] "${item.kind}" unavailable() threw`, error);
|
|
2219
|
-
}
|
|
2220
|
-
let body;
|
|
2221
|
-
if (!kind) {
|
|
2222
|
-
body = /* @__PURE__ */ jsx5(ItemProblem, { message: "This item can no longer be shown.", onClose: () => canvas.close(item.id) });
|
|
2223
|
-
} else if (unavailable) {
|
|
2224
|
-
body = /* @__PURE__ */ jsx5(CanvasPanelEmpty, { children: unavailable });
|
|
2225
|
-
} else if (kind.body === "native") {
|
|
2226
|
-
body = hasNativePort ? /* @__PURE__ */ jsx5(NativeBody, { itemId: item.id, windowId, children: kindComponent(kind) ? /* @__PURE__ */ jsx5(Rendered, { kind, item, props }) : null }) : /* @__PURE__ */ jsx5(CanvasPanelEmpty, { children: "Opens in the desktop app" });
|
|
2227
|
-
} else {
|
|
2228
|
-
body = /* @__PURE__ */ jsx5(Rendered, { kind, item, props });
|
|
2229
|
-
}
|
|
2230
|
-
return /* @__PURE__ */ jsx5("div", { className: "mxc-item", hidden: !isActive, "data-kind": item.kind, children: body });
|
|
2231
|
-
}
|
|
2232
|
-
function Rendered({ kind, item, props }) {
|
|
2233
|
-
const canvas = useCanvas();
|
|
2234
|
-
return /* @__PURE__ */ jsx5(ItemBoundary, { item, onClose: () => canvas.close(item.id), onRetry: () => resetKindComponent(kind), children: /* @__PURE__ */ jsx5(Suspense, { fallback: /* @__PURE__ */ jsx5("div", { className: "mxc-loading", "aria-busy": "true" }), children: /* @__PURE__ */ jsx5(KindRender, { kind, props }) }) });
|
|
2235
|
-
}
|
|
2236
|
-
function KindRender({ kind, props }) {
|
|
2237
|
-
const Render = kindComponent(kind);
|
|
2238
|
-
if (!Render) throw new Error(`[@ai-matrx/canvas] kind "${kind.id}" has nothing to render.`);
|
|
2239
|
-
return /* @__PURE__ */ jsx5(Render, { ...props });
|
|
2240
|
-
}
|
|
2241
|
-
function NativeBody({ itemId, windowId, children }) {
|
|
2242
|
-
const { layer } = useCanvasInternals();
|
|
2243
|
-
const ref = useRef4(null);
|
|
2244
|
-
const measure = useRef4(() => void 0);
|
|
2245
|
-
measure.current = () => {
|
|
2246
|
-
const el = ref.current;
|
|
2247
|
-
const box = el?.isConnected ? el.getBoundingClientRect() : null;
|
|
2248
|
-
if (!box || box.width <= 0 || box.height <= 0) {
|
|
2249
|
-
layer.native.report(itemId, null, { visible: false, windowId });
|
|
2250
|
-
return;
|
|
2251
|
-
}
|
|
2252
|
-
const rect = { ...toCanvasRect(box), devicePixelRatio: window.devicePixelRatio || 1 };
|
|
2253
|
-
layer.native.report(itemId, rect, { visible: !layer.isBlocked(), windowId });
|
|
2254
|
-
};
|
|
2255
|
-
useIsoLayoutEffect(() => measure.current());
|
|
2256
|
-
useIsoLayoutEffect(() => {
|
|
2257
|
-
const el = ref.current;
|
|
2258
|
-
const run = () => measure.current();
|
|
2259
|
-
const observer = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(run);
|
|
2260
|
-
if (el) observer?.observe(el);
|
|
2261
|
-
const unregister = layer.native.register(run);
|
|
2262
|
-
window.addEventListener("resize", run);
|
|
2263
|
-
return () => {
|
|
2264
|
-
observer?.disconnect();
|
|
2265
|
-
unregister();
|
|
2266
|
-
window.removeEventListener("resize", run);
|
|
2267
|
-
layer.native.report(itemId, null, { visible: false, windowId });
|
|
2268
|
-
};
|
|
2269
|
-
}, [itemId, windowId, layer]);
|
|
2270
|
-
return /* @__PURE__ */ jsx5("div", { ref, className: "mxc-native-body", "data-native-body": itemId, children });
|
|
2271
|
-
}
|
|
2272
|
-
function ItemProblem({ message, onClose, onRetry }) {
|
|
2273
|
-
return /* @__PURE__ */ jsxs4("div", { className: "mxc-problem", role: "alert", children: [
|
|
2274
|
-
/* @__PURE__ */ jsx5(AlertIcon, {}),
|
|
2275
|
-
/* @__PURE__ */ jsx5("p", { children: message }),
|
|
2276
|
-
/* @__PURE__ */ jsxs4("div", { className: "mxc-problem-actions", children: [
|
|
2277
|
-
onRetry ? /* @__PURE__ */ jsx5("button", { type: "button", className: "mxc-text-button", onClick: onRetry, children: "Try again" }) : null,
|
|
2278
|
-
/* @__PURE__ */ jsx5("button", { type: "button", className: "mxc-text-button", onClick: onClose, children: "Close tab" })
|
|
2279
|
-
] })
|
|
2280
|
-
] });
|
|
2281
|
-
}
|
|
2282
|
-
var ItemBoundary = class extends Component {
|
|
2283
|
-
state = { error: null };
|
|
2284
|
-
static getDerivedStateFromError(error) {
|
|
2285
|
-
return { error };
|
|
2286
|
-
}
|
|
2287
|
-
componentDidCatch(error, info) {
|
|
2288
|
-
console.error(`[@ai-matrx/canvas] "${this.props.item.kind}" failed to render`, error, info.componentStack);
|
|
2289
|
-
}
|
|
2290
|
-
render() {
|
|
2291
|
-
if (this.state.error) {
|
|
2292
|
-
return /* @__PURE__ */ jsx5(
|
|
2293
|
-
ItemProblem,
|
|
2294
|
-
{
|
|
2295
|
-
message: "This item failed to load.",
|
|
2296
|
-
onClose: this.props.onClose,
|
|
2297
|
-
onRetry: () => {
|
|
2298
|
-
this.props.onRetry();
|
|
2299
|
-
this.setState({ error: null });
|
|
2300
|
-
}
|
|
2301
|
-
}
|
|
2302
|
-
);
|
|
2303
|
-
}
|
|
2304
|
-
return this.props.children;
|
|
2305
|
-
}
|
|
2306
|
-
};
|
|
2307
|
-
function Launcher({ paneId }) {
|
|
2308
|
-
const canvas = useCanvas();
|
|
2309
|
-
const kinds = useCanvasKinds().filter((kind) => kind.launcher);
|
|
2310
|
-
if (kinds.length === 0) return /* @__PURE__ */ jsx5(CanvasPanelEmpty, { children: "Nothing open" });
|
|
2311
|
-
return /* @__PURE__ */ jsx5("div", { className: "mxc-launcher", children: kinds.map((kind) => {
|
|
2312
|
-
const Icon = kind.icon;
|
|
2313
|
-
const launcher = kind.launcher;
|
|
2314
|
-
if (!launcher) return null;
|
|
2315
|
-
return /* @__PURE__ */ jsxs4(
|
|
2316
|
-
"button",
|
|
2317
|
-
{
|
|
2318
|
-
type: "button",
|
|
2319
|
-
className: "mxc-launcher-item",
|
|
2320
|
-
onClick: () => canvas.open({
|
|
2321
|
-
kind: kind.id,
|
|
2322
|
-
key: launcher.key,
|
|
2323
|
-
data: launcher.data,
|
|
2324
|
-
title: launcher.title ?? null,
|
|
2325
|
-
target: { paneId }
|
|
2326
|
-
}),
|
|
2327
|
-
children: [
|
|
2328
|
-
/* @__PURE__ */ jsx5(Icon, { className: "mxc-launcher-icon" }),
|
|
2329
|
-
/* @__PURE__ */ jsx5("span", { children: launcher.title ?? kind.label })
|
|
2330
|
-
]
|
|
2331
|
-
},
|
|
2332
|
-
kind.id
|
|
2333
|
-
);
|
|
2334
|
-
}) });
|
|
2335
|
-
}
|
|
2336
2945
|
|
|
2337
2946
|
// src/react/CanvasColumn.tsx
|
|
2338
|
-
import { Fragment as
|
|
2339
|
-
var
|
|
2947
|
+
import { Fragment as Fragment3, jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
2948
|
+
var useIsoLayoutEffect3 = typeof window === "undefined" ? useEffect7 : useLayoutEffect4;
|
|
2340
2949
|
function subscribeViewport(listener) {
|
|
2341
2950
|
window.addEventListener("resize", listener);
|
|
2342
2951
|
return () => window.removeEventListener("resize", listener);
|
|
2343
2952
|
}
|
|
2344
2953
|
function useViewportWidth() {
|
|
2345
|
-
return
|
|
2954
|
+
return useSyncExternalStore4(subscribeViewport, () => window.innerWidth, () => 0);
|
|
2346
2955
|
}
|
|
2347
2956
|
function useRegionWidth() {
|
|
2348
2957
|
const viewport = useViewportWidth();
|
|
@@ -2360,28 +2969,66 @@ function useCanvasColumnWidth() {
|
|
|
2360
2969
|
return fitCanvasWidth(width, region, controller.rules);
|
|
2361
2970
|
}
|
|
2362
2971
|
function CanvasColumn({ className, style, onLiveWidth, placement = "docked" }) {
|
|
2363
|
-
const { controller, windowId } = useCanvasInternals();
|
|
2972
|
+
const { controller, windowId, layer } = useCanvasInternals();
|
|
2364
2973
|
const isOpen = useCanvasState((s) => selectCanvasIsOpen(s, windowId));
|
|
2365
2974
|
const isFullscreen = useCanvasState((s) => selectCanvasIsFullscreen(s, windowId));
|
|
2366
2975
|
const width = useCanvasState((s) => selectCanvasWidth(s, windowId));
|
|
2367
2976
|
const layout = useCanvasState((s) => selectCanvasLayout(s, windowId));
|
|
2368
2977
|
const region = useRegionWidth();
|
|
2369
|
-
const [dragWidth, setDragWidth] =
|
|
2978
|
+
const [dragWidth, setDragWidth] = useState6(null);
|
|
2979
|
+
const [slots] = useState6(createCanvasSlots);
|
|
2980
|
+
const ref = useRef7(null);
|
|
2370
2981
|
useRegisterCanvasPresence();
|
|
2371
|
-
|
|
2982
|
+
useEffect7(() => {
|
|
2983
|
+
const el = ref.current;
|
|
2984
|
+
if (!el) return void 0;
|
|
2985
|
+
let frame2 = 0;
|
|
2986
|
+
let running = false;
|
|
2987
|
+
const tick = () => {
|
|
2988
|
+
layer.native.remeasure();
|
|
2989
|
+
frame2 = running ? requestAnimationFrame(tick) : 0;
|
|
2990
|
+
};
|
|
2991
|
+
const start = (event) => {
|
|
2992
|
+
if (event.target !== el || running) return;
|
|
2993
|
+
running = true;
|
|
2994
|
+
frame2 = requestAnimationFrame(tick);
|
|
2995
|
+
};
|
|
2996
|
+
const stop = (event) => {
|
|
2997
|
+
if (event.target !== el) return;
|
|
2998
|
+
running = false;
|
|
2999
|
+
if (frame2) cancelAnimationFrame(frame2);
|
|
3000
|
+
frame2 = 0;
|
|
3001
|
+
layer.native.remeasure();
|
|
3002
|
+
};
|
|
3003
|
+
el.addEventListener("transitionrun", start);
|
|
3004
|
+
el.addEventListener("transitionend", stop);
|
|
3005
|
+
el.addEventListener("transitioncancel", stop);
|
|
3006
|
+
return () => {
|
|
3007
|
+
running = false;
|
|
3008
|
+
if (frame2) cancelAnimationFrame(frame2);
|
|
3009
|
+
el.removeEventListener("transitionrun", start);
|
|
3010
|
+
el.removeEventListener("transitionend", stop);
|
|
3011
|
+
el.removeEventListener("transitioncancel", stop);
|
|
3012
|
+
};
|
|
3013
|
+
}, [layer]);
|
|
3014
|
+
if (!layout) return null;
|
|
2372
3015
|
const fitted = fitCanvasWidth(width, region, controller.rules);
|
|
2373
3016
|
const shown = dragWidth ?? fitted;
|
|
2374
|
-
return /* @__PURE__ */
|
|
3017
|
+
return /* @__PURE__ */ jsx8(
|
|
2375
3018
|
"aside",
|
|
2376
3019
|
{
|
|
3020
|
+
ref,
|
|
2377
3021
|
className: ["mxc-column", className].filter(Boolean).join(" "),
|
|
2378
|
-
"data-
|
|
3022
|
+
"data-open": isOpen ? "" : void 0,
|
|
3023
|
+
"data-fullscreen": isOpen && isFullscreen ? "" : void 0,
|
|
2379
3024
|
"data-placement": placement,
|
|
2380
3025
|
"data-mxc-window": windowId,
|
|
2381
3026
|
"aria-label": "Canvas",
|
|
3027
|
+
"aria-hidden": isOpen ? void 0 : true,
|
|
3028
|
+
inert: !isOpen,
|
|
2382
3029
|
style: { ...style, ["--mxc-width"]: `${shown}px` },
|
|
2383
|
-
children: [
|
|
2384
|
-
placement === "docked" && !isFullscreen ? /* @__PURE__ */
|
|
3030
|
+
children: /* @__PURE__ */ jsxs7("div", { className: "mxc-column-inner", children: [
|
|
3031
|
+
placement === "docked" && isOpen && !isFullscreen ? /* @__PURE__ */ jsx8(
|
|
2385
3032
|
WidthHandle,
|
|
2386
3033
|
{
|
|
2387
3034
|
width: fitted,
|
|
@@ -2392,14 +3039,17 @@ function CanvasColumn({ className, style, onLiveWidth, placement = "docked" }) {
|
|
|
2392
3039
|
}
|
|
2393
3040
|
}
|
|
2394
3041
|
) : null,
|
|
2395
|
-
/* @__PURE__ */
|
|
2396
|
-
|
|
3042
|
+
/* @__PURE__ */ jsxs7(CanvasSlotsContext.Provider, { value: slots, children: [
|
|
3043
|
+
/* @__PURE__ */ jsx8("div", { className: "mxc-layout", children: /* @__PURE__ */ jsx8(LayoutNodeView, { node: layout }) }),
|
|
3044
|
+
/* @__PURE__ */ jsx8(CanvasBodyLayer, {})
|
|
3045
|
+
] })
|
|
3046
|
+
] })
|
|
2397
3047
|
}
|
|
2398
3048
|
);
|
|
2399
3049
|
}
|
|
2400
3050
|
function WidthHandle({ width, region, onDrag }) {
|
|
2401
3051
|
const canvas = useCanvas();
|
|
2402
|
-
const start =
|
|
3052
|
+
const start = useRef7(null);
|
|
2403
3053
|
const clamp = (next) => clampDraggedCanvasWidth(next, region, canvas.rules);
|
|
2404
3054
|
const onPointerDown = (event) => {
|
|
2405
3055
|
event.preventDefault();
|
|
@@ -2419,7 +3069,7 @@ function WidthHandle({ width, region, onDrag }) {
|
|
|
2419
3069
|
onDrag(null);
|
|
2420
3070
|
canvas.setWidth(next);
|
|
2421
3071
|
};
|
|
2422
|
-
return /* @__PURE__ */
|
|
3072
|
+
return /* @__PURE__ */ jsx8(
|
|
2423
3073
|
"div",
|
|
2424
3074
|
{
|
|
2425
3075
|
className: "mxc-width-handle",
|
|
@@ -2441,12 +3091,12 @@ function WidthHandle({ width, region, onDrag }) {
|
|
|
2441
3091
|
);
|
|
2442
3092
|
}
|
|
2443
3093
|
function LayoutNodeView({ node }) {
|
|
2444
|
-
if (node.type === "pane") return /* @__PURE__ */
|
|
2445
|
-
return /* @__PURE__ */
|
|
3094
|
+
if (node.type === "pane") return /* @__PURE__ */ jsx8(CanvasPaneView, { paneId: node.paneId });
|
|
3095
|
+
return /* @__PURE__ */ jsx8(SplitView, { node });
|
|
2446
3096
|
}
|
|
2447
3097
|
function useMeasuredWidth(ref, enabled) {
|
|
2448
|
-
const [width, setWidth] =
|
|
2449
|
-
|
|
3098
|
+
const [width, setWidth] = useState6(0);
|
|
3099
|
+
useIsoLayoutEffect3(() => {
|
|
2450
3100
|
const el = ref.current;
|
|
2451
3101
|
if (!enabled || !el) return;
|
|
2452
3102
|
setWidth(el.getBoundingClientRect().width);
|
|
@@ -2459,8 +3109,8 @@ function useMeasuredWidth(ref, enabled) {
|
|
|
2459
3109
|
}
|
|
2460
3110
|
function SplitView({ node }) {
|
|
2461
3111
|
const canvas = useCanvas();
|
|
2462
|
-
const ref =
|
|
2463
|
-
const [liveSizes, setLiveSizes] =
|
|
3112
|
+
const ref = useRef7(null);
|
|
3113
|
+
const [liveSizes, setLiveSizes] = useState6(null);
|
|
2464
3114
|
const sizes = liveSizes ?? node.sizes;
|
|
2465
3115
|
const measuredWidth = useMeasuredWidth(ref, node.orientation === "horizontal" && canvas.rules.paneMinWidth > 0);
|
|
2466
3116
|
const stacked = node.orientation === "horizontal" && shouldStackSplit(measuredWidth, node.children.length, canvas.rules);
|
|
@@ -2500,11 +3150,11 @@ function SplitView({ node }) {
|
|
|
2500
3150
|
target.addEventListener("pointerup", up);
|
|
2501
3151
|
target.addEventListener("pointercancel", up);
|
|
2502
3152
|
};
|
|
2503
|
-
return /* @__PURE__ */
|
|
3153
|
+
return /* @__PURE__ */ jsx8("div", { ref, className: "mxc-split", "data-orientation": orientation, "data-stacked": stacked ? "" : void 0, children: node.children.map((child, i) => /* @__PURE__ */ jsx8(
|
|
2504
3154
|
SplitChild,
|
|
2505
3155
|
{
|
|
2506
3156
|
size: sizes[i] ?? 1 / node.children.length,
|
|
2507
|
-
handle: i < node.children.length - 1 ? /* @__PURE__ */
|
|
3157
|
+
handle: i < node.children.length - 1 ? /* @__PURE__ */ jsx8(
|
|
2508
3158
|
"div",
|
|
2509
3159
|
{
|
|
2510
3160
|
className: "mxc-split-handle",
|
|
@@ -2514,14 +3164,14 @@ function SplitView({ node }) {
|
|
|
2514
3164
|
onDoubleClick: () => canvas.resizeSplit(node.id, node.children.map(() => 1 / node.children.length))
|
|
2515
3165
|
}
|
|
2516
3166
|
) : null,
|
|
2517
|
-
children: /* @__PURE__ */
|
|
3167
|
+
children: /* @__PURE__ */ jsx8(LayoutNodeView, { node: child })
|
|
2518
3168
|
},
|
|
2519
3169
|
child.type === "pane" ? child.paneId : child.id
|
|
2520
3170
|
)) });
|
|
2521
3171
|
}
|
|
2522
3172
|
function SplitChild({ size, handle, children }) {
|
|
2523
|
-
return /* @__PURE__ */
|
|
2524
|
-
/* @__PURE__ */
|
|
3173
|
+
return /* @__PURE__ */ jsxs7(Fragment3, { children: [
|
|
3174
|
+
/* @__PURE__ */ jsx8("div", { className: "mxc-split-child", style: { flexGrow: size, flexBasis: 0 }, children }),
|
|
2525
3175
|
handle
|
|
2526
3176
|
] });
|
|
2527
3177
|
}
|
|
@@ -2535,26 +3185,61 @@ function CanvasToggle({
|
|
|
2535
3185
|
const count = useCanvasState((s) => selectCanvasWindowItemCount(s, windowId));
|
|
2536
3186
|
const hint = canvasShortcutHint("canvas.toggle", isMac2());
|
|
2537
3187
|
const Button = variant === "glass" ? TapTargetButton : TapTargetButtonTransparent;
|
|
2538
|
-
return /* @__PURE__ */
|
|
2539
|
-
/* @__PURE__ */
|
|
3188
|
+
return /* @__PURE__ */ jsxs7("span", { className: ["mxc-toggle", className].filter(Boolean).join(" "), "data-open": isOpen ? "" : void 0, children: [
|
|
3189
|
+
/* @__PURE__ */ jsx8(
|
|
2540
3190
|
Button,
|
|
2541
3191
|
{
|
|
2542
3192
|
ariaLabel: isOpen ? "Hide canvas" : "Show canvas",
|
|
2543
3193
|
tooltip: `${isOpen ? "Hide canvas" : "Show canvas"}${hint ? ` (${hint})` : ""}`,
|
|
2544
|
-
icon: /* @__PURE__ */
|
|
3194
|
+
icon: /* @__PURE__ */ jsx8(PanelRightIcon, {}),
|
|
2545
3195
|
onClick: () => controller.toggle()
|
|
2546
3196
|
}
|
|
2547
3197
|
),
|
|
2548
|
-
count > 0 && !isOpen ? /* @__PURE__ */
|
|
3198
|
+
count > 0 && !isOpen ? /* @__PURE__ */ jsx8("span", { className: "mxc-toggle-badge", children: count }) : null
|
|
2549
3199
|
] });
|
|
2550
3200
|
}
|
|
3201
|
+
function CanvasKindIcons({
|
|
3202
|
+
kinds,
|
|
3203
|
+
variant = "transparent",
|
|
3204
|
+
className,
|
|
3205
|
+
label = "Canvas tools"
|
|
3206
|
+
}) {
|
|
3207
|
+
return /* @__PURE__ */ jsx8("span", { role: "group", "aria-label": label, className: ["mxc-kind-icons", className].filter(Boolean).join(" "), children: kinds.map((entry) => {
|
|
3208
|
+
const icon = typeof entry === "string" ? { kind: entry } : entry;
|
|
3209
|
+
return /* @__PURE__ */ jsx8(KindIconButton, { icon, variant }, `${icon.kind}::${icon.key ?? CANVAS_DEFAULT_KEY}`);
|
|
3210
|
+
}) });
|
|
3211
|
+
}
|
|
3212
|
+
function KindIconButton({ icon, variant }) {
|
|
3213
|
+
const kind = useCanvasKind(icon.kind);
|
|
3214
|
+
const toggle = useCanvasKindToggle({
|
|
3215
|
+
kind: icon.kind,
|
|
3216
|
+
key: icon.key,
|
|
3217
|
+
data: icon.data,
|
|
3218
|
+
title: icon.title,
|
|
3219
|
+
whenVisible: icon.whenVisible
|
|
3220
|
+
});
|
|
3221
|
+
if (!kind) return null;
|
|
3222
|
+
const Icon = kind.icon;
|
|
3223
|
+
const Button = variant === "glass" ? TapTargetButton : TapTargetButtonTransparent;
|
|
3224
|
+
const name = icon.label ?? kind.label;
|
|
3225
|
+
return /* @__PURE__ */ jsx8(
|
|
3226
|
+
Button,
|
|
3227
|
+
{
|
|
3228
|
+
ariaLabel: name,
|
|
3229
|
+
tooltip: name,
|
|
3230
|
+
icon: /* @__PURE__ */ jsx8(Icon, {}),
|
|
3231
|
+
pressed: toggle.isVisible,
|
|
3232
|
+
onClick: () => void toggle.toggle()
|
|
3233
|
+
}
|
|
3234
|
+
);
|
|
3235
|
+
}
|
|
2551
3236
|
function CanvasFrame({ children, className }) {
|
|
2552
3237
|
const { windowId } = useCanvasInternals();
|
|
2553
3238
|
const isFullscreen = useCanvasState((s) => selectCanvasIsFullscreen(s, windowId));
|
|
2554
3239
|
const isOpen = useCanvasState((s) => selectCanvasIsOpen(s, windowId));
|
|
2555
|
-
return /* @__PURE__ */
|
|
2556
|
-
/* @__PURE__ */
|
|
2557
|
-
/* @__PURE__ */
|
|
3240
|
+
return /* @__PURE__ */ jsxs7("div", { className: ["mxc-frame", className].filter(Boolean).join(" "), "data-canvas-open": isOpen ? "" : void 0, children: [
|
|
3241
|
+
/* @__PURE__ */ jsx8("div", { className: "mxc-frame-main", hidden: isOpen && isFullscreen, children }),
|
|
3242
|
+
/* @__PURE__ */ jsx8(CanvasColumn, {})
|
|
2558
3243
|
] });
|
|
2559
3244
|
}
|
|
2560
3245
|
export {
|
|
@@ -2563,6 +3248,7 @@ export {
|
|
|
2563
3248
|
CanvasColumn,
|
|
2564
3249
|
CanvasFrame,
|
|
2565
3250
|
CanvasHeaderButton,
|
|
3251
|
+
CanvasKindIcons,
|
|
2566
3252
|
CanvasMenuTrigger,
|
|
2567
3253
|
CanvasPaneView,
|
|
2568
3254
|
CanvasPanel,
|
|
@@ -2585,7 +3271,10 @@ export {
|
|
|
2585
3271
|
useCanvasKind,
|
|
2586
3272
|
useCanvasKindToggle,
|
|
2587
3273
|
useCanvasKinds,
|
|
3274
|
+
useCanvasLauncherEntries,
|
|
3275
|
+
useCanvasLauncherEntry,
|
|
2588
3276
|
useCanvasNativeBodies,
|
|
3277
|
+
useCanvasPresentation,
|
|
2589
3278
|
useCanvasState,
|
|
2590
3279
|
useCanvasWindowId,
|
|
2591
3280
|
useOptionalCanvas,
|