@ai-matrx/canvas 0.4.0 → 0.5.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 +40 -0
- package/README.md +61 -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 +998 -293
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +94 -4
- package/dist/react.d.ts +94 -4
- package/dist/react.js +1007 -298
- package/dist/react.js.map +1 -1
- package/dist/styles.css +223 -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.cjs
CHANGED
|
@@ -37,6 +37,7 @@ __export(react_exports, {
|
|
|
37
37
|
CanvasColumn: () => CanvasColumn,
|
|
38
38
|
CanvasFrame: () => CanvasFrame,
|
|
39
39
|
CanvasHeaderButton: () => CanvasHeaderButton,
|
|
40
|
+
CanvasKindIcons: () => CanvasKindIcons,
|
|
40
41
|
CanvasMenuTrigger: () => CanvasMenuTrigger,
|
|
41
42
|
CanvasPaneView: () => CanvasPaneView,
|
|
42
43
|
CanvasPanel: () => CanvasPanel,
|
|
@@ -59,7 +60,10 @@ __export(react_exports, {
|
|
|
59
60
|
useCanvasKind: () => useCanvasKind,
|
|
60
61
|
useCanvasKindToggle: () => useCanvasKindToggle,
|
|
61
62
|
useCanvasKinds: () => useCanvasKinds,
|
|
63
|
+
useCanvasLauncherEntries: () => useCanvasLauncherEntries,
|
|
64
|
+
useCanvasLauncherEntry: () => useCanvasLauncherEntry,
|
|
62
65
|
useCanvasNativeBodies: () => useCanvasNativeBodies,
|
|
66
|
+
useCanvasPresentation: () => useCanvasPresentation,
|
|
63
67
|
useCanvasState: () => useCanvasState,
|
|
64
68
|
useCanvasWindowId: () => useCanvasWindowId,
|
|
65
69
|
useOptionalCanvas: () => useOptionalCanvas,
|
|
@@ -72,6 +76,8 @@ var import_react2 = require("react");
|
|
|
72
76
|
|
|
73
77
|
// src/core/types.ts
|
|
74
78
|
var CANVAS_MAIN_WINDOW = "main";
|
|
79
|
+
var CANVAS_LAUNCHER_KIND = "canvas.launcher";
|
|
80
|
+
var CANVAS_HISTORY_LIMIT = 50;
|
|
75
81
|
var CANVAS_MIN_WIDTH = 360;
|
|
76
82
|
var CANVAS_DEFAULT_WIDTH = 640;
|
|
77
83
|
var CANVAS_MIN_SPLIT_FRACTION = 0.12;
|
|
@@ -230,6 +236,10 @@ var canvasActions = {
|
|
|
230
236
|
}),
|
|
231
237
|
/** Gives an item a new identity in place (e.g. a draft that was just saved and now has an id). */
|
|
232
238
|
rekey: (itemId, key) => ({ type: `${P}rekey`, payload: { itemId, key } }),
|
|
239
|
+
/** Replaces a tab's content with another item in place, remembering what it showed (Back returns). */
|
|
240
|
+
navigate: (itemId, to) => ({ type: `${P}navigate`, payload: { itemId, to, now: Date.now() } }),
|
|
241
|
+
/** Returns a tab to what it showed before its last navigate. */
|
|
242
|
+
back: (itemId) => ({ type: `${P}back`, payload: { itemId, now: Date.now() } }),
|
|
233
243
|
closeItem: (itemId) => ({ type: `${P}closeItem`, payload: { itemId } }),
|
|
234
244
|
closeOthers: (itemId) => ({ type: `${P}closeOthers`, payload: { itemId } }),
|
|
235
245
|
activate: (itemId) => ({ type: `${P}activate`, payload: { itemId } }),
|
|
@@ -426,6 +436,63 @@ function sendHome(state, itemId, home, activate) {
|
|
|
426
436
|
next = insertInto(next, homeFrame.focusedPaneId, itemId, activate);
|
|
427
437
|
return activate ? withFrame(next, target, { focusedPaneId: homeFrame.focusedPaneId }) : next;
|
|
428
438
|
}
|
|
439
|
+
function newItem(kind, key, title, data, now, history) {
|
|
440
|
+
const id = canvasItemId(kind, key);
|
|
441
|
+
return {
|
|
442
|
+
id,
|
|
443
|
+
kind,
|
|
444
|
+
key,
|
|
445
|
+
title,
|
|
446
|
+
data,
|
|
447
|
+
openedAt: now,
|
|
448
|
+
updatedAt: now,
|
|
449
|
+
instance: `${now.toString(36)}:${id}`,
|
|
450
|
+
...history && history.length > 0 ? { history: history.slice(-CANVAS_HISTORY_LIMIT) } : {}
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
function historyEntry(item) {
|
|
454
|
+
return { kind: item.kind, key: item.key, title: item.title, data: item.data };
|
|
455
|
+
}
|
|
456
|
+
function replaceInPlace(state, current, next) {
|
|
457
|
+
const pane = paneOf(state, current.id);
|
|
458
|
+
if (!pane) return state;
|
|
459
|
+
const items = { ...state.items, [next.id]: next };
|
|
460
|
+
delete items[current.id];
|
|
461
|
+
const withItems = { ...state, items };
|
|
462
|
+
const replaced = withPane(withItems, {
|
|
463
|
+
...pane,
|
|
464
|
+
itemIds: pane.itemIds.map((id) => id === current.id ? next.id : id),
|
|
465
|
+
activeItemId: pane.activeItemId === current.id ? next.id : pane.activeItemId
|
|
466
|
+
});
|
|
467
|
+
return focus(replaced, pane.id);
|
|
468
|
+
}
|
|
469
|
+
function focusExisting(state, itemId) {
|
|
470
|
+
const pane = paneOf(state, itemId);
|
|
471
|
+
if (!pane) return state;
|
|
472
|
+
const next = focus(withPane(state, { ...pane, activeItemId: itemId }), pane.id);
|
|
473
|
+
const windowId = canvasWindowOfPane(next, pane.id);
|
|
474
|
+
return windowId ? withFrame(next, windowId, { isOpen: true }) : next;
|
|
475
|
+
}
|
|
476
|
+
function sanitizeHistory(raw) {
|
|
477
|
+
if (!Array.isArray(raw)) return [];
|
|
478
|
+
const out = [];
|
|
479
|
+
for (const entry of raw) {
|
|
480
|
+
if (!entry || typeof entry !== "object") continue;
|
|
481
|
+
const e = entry;
|
|
482
|
+
if (typeof e.kind !== "string" || typeof e.key !== "string") continue;
|
|
483
|
+
out.push({ kind: e.kind, key: e.key, title: typeof e.title === "string" ? e.title : null, data: e.data ?? null });
|
|
484
|
+
}
|
|
485
|
+
return out.slice(-CANVAS_HISTORY_LIMIT);
|
|
486
|
+
}
|
|
487
|
+
function sanitizeItem(item) {
|
|
488
|
+
const history = sanitizeHistory(item.history);
|
|
489
|
+
const { history: _history, instance, ...rest } = item;
|
|
490
|
+
return {
|
|
491
|
+
...rest,
|
|
492
|
+
...typeof instance === "string" && instance ? { instance } : {},
|
|
493
|
+
...history.length > 0 ? { history } : {}
|
|
494
|
+
};
|
|
495
|
+
}
|
|
429
496
|
function clampWidth(width) {
|
|
430
497
|
return Number.isFinite(width) ? Math.max(CANVAS_MIN_WIDTH, Math.round(width)) : null;
|
|
431
498
|
}
|
|
@@ -473,7 +540,7 @@ function sanitizeCanvasSnapshot(raw) {
|
|
|
473
540
|
const items = {};
|
|
474
541
|
for (const [id, item] of Object.entries(s.items)) {
|
|
475
542
|
if (item && typeof item.kind === "string" && typeof item.key === "string" && canvasItemId(item.kind, item.key) === id) {
|
|
476
|
-
items[id] = item;
|
|
543
|
+
items[id] = sanitizeItem(item);
|
|
477
544
|
}
|
|
478
545
|
}
|
|
479
546
|
const placed = /* @__PURE__ */ new Set();
|
|
@@ -547,7 +614,7 @@ function canvasReducer(state = createInitialCanvasState(), action) {
|
|
|
547
614
|
next = insertInto(next, frame2.focusedPaneId, itemId, true);
|
|
548
615
|
}
|
|
549
616
|
} else {
|
|
550
|
-
const item =
|
|
617
|
+
const item = newItem(kind, key, title ?? null, data ?? null, now);
|
|
551
618
|
next = ensureWindow({ ...next, items: { ...next.items, [itemId]: item } }, revealIn);
|
|
552
619
|
const frame2 = canvasWindowFrame(next, revealIn) ?? next;
|
|
553
620
|
let paneId = frame2.focusedPaneId;
|
|
@@ -596,6 +663,35 @@ function canvasReducer(state = createInitialCanvasState(), action) {
|
|
|
596
663
|
}
|
|
597
664
|
return { ...state, items, panes };
|
|
598
665
|
}
|
|
666
|
+
case `${P}navigate`: {
|
|
667
|
+
const { itemId, to, now } = action.payload;
|
|
668
|
+
const current = state.items[itemId];
|
|
669
|
+
if (!current || !paneOf(state, itemId)) return state;
|
|
670
|
+
const targetId = canvasItemId(to.kind, to.key);
|
|
671
|
+
if (targetId === itemId) {
|
|
672
|
+
const refresh = { type: `${P}update`, payload: { itemId, data: to.data, title: to.title, now } };
|
|
673
|
+
return canvasReducer(state, refresh);
|
|
674
|
+
}
|
|
675
|
+
if (state.items[targetId]) {
|
|
676
|
+
const focused = focusExisting(state, targetId);
|
|
677
|
+
return current.kind === CANVAS_LAUNCHER_KIND ? canvasReducer(focused, canvasActions.closeItem(itemId)) : focused;
|
|
678
|
+
}
|
|
679
|
+
const history = [...current.history ?? [], historyEntry(current)];
|
|
680
|
+
return replaceInPlace(state, current, newItem(to.kind, to.key, to.title ?? null, to.data ?? null, now, history));
|
|
681
|
+
}
|
|
682
|
+
case `${P}back`: {
|
|
683
|
+
const { itemId, now } = action.payload;
|
|
684
|
+
const current = state.items[itemId];
|
|
685
|
+
const history = current?.history ?? [];
|
|
686
|
+
const entry = history[history.length - 1];
|
|
687
|
+
if (!current || !entry || !paneOf(state, itemId)) return state;
|
|
688
|
+
const targetId = canvasItemId(entry.kind, entry.key);
|
|
689
|
+
if (targetId === itemId) return state;
|
|
690
|
+
if (state.items[targetId]) {
|
|
691
|
+
return focusExisting(canvasReducer(state, canvasActions.closeItem(itemId)), targetId);
|
|
692
|
+
}
|
|
693
|
+
return replaceInPlace(state, current, newItem(entry.kind, entry.key, entry.title, entry.data, now, history.slice(0, -1)));
|
|
694
|
+
}
|
|
599
695
|
case `${P}closeItem`: {
|
|
600
696
|
const { itemId } = action.payload;
|
|
601
697
|
if (!state.items[itemId]) return state;
|
|
@@ -849,7 +945,8 @@ var CANVAS_WEB_LAYOUT_RULES = {
|
|
|
849
945
|
defaultWidth: CANVAS_DEFAULT_WIDTH,
|
|
850
946
|
minWidth: CANVAS_MIN_WIDTH,
|
|
851
947
|
centreMinWidth: 420,
|
|
852
|
-
|
|
948
|
+
// Split down 70/30 like the desktop: what opens below (notifications, a feed) is the smaller pane.
|
|
949
|
+
stackSplit: 0.7,
|
|
853
950
|
sideSplit: 0.5,
|
|
854
951
|
paneMinWidth: 0
|
|
855
952
|
};
|
|
@@ -974,7 +1071,8 @@ function createCanvasController(options) {
|
|
|
974
1071
|
presentationListeners: /* @__PURE__ */ new Set(),
|
|
975
1072
|
views: /* @__PURE__ */ new Map(),
|
|
976
1073
|
started: /* @__PURE__ */ new Map(),
|
|
977
|
-
sessionStop: null
|
|
1074
|
+
sessionStop: null,
|
|
1075
|
+
launcherSeq: 0
|
|
978
1076
|
};
|
|
979
1077
|
return view(shared, canvasWindowId(options.windowId ?? CANVAS_MAIN_WINDOW));
|
|
980
1078
|
}
|
|
@@ -989,6 +1087,36 @@ function view(shared, windowId) {
|
|
|
989
1087
|
for (const listener of [...shared.presentationListeners]) listener();
|
|
990
1088
|
};
|
|
991
1089
|
const shareFor = (orientation) => orientation === "vertical" ? rules.stackSplit : rules.sideSplit;
|
|
1090
|
+
const acceptable = (input) => {
|
|
1091
|
+
if (input.kind !== CANVAS_LAUNCHER_KIND && options.isKnownKind && !options.isKnownKind(input.kind)) {
|
|
1092
|
+
onError({ code: "unknown-kind", message: `No canvas kind "${input.kind}" is registered.`, detail: input });
|
|
1093
|
+
return false;
|
|
1094
|
+
}
|
|
1095
|
+
if (input.data !== void 0) {
|
|
1096
|
+
const bad = findNonJson(input.data);
|
|
1097
|
+
if (bad) {
|
|
1098
|
+
onError({
|
|
1099
|
+
code: "non-json-data",
|
|
1100
|
+
message: `Canvas data for "${input.kind}" must be plain JSON; ${bad} is not.`,
|
|
1101
|
+
detail: { kind: input.kind, key: input.key }
|
|
1102
|
+
});
|
|
1103
|
+
return false;
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
return true;
|
|
1107
|
+
};
|
|
1108
|
+
const preferredTargetFor = (input) => {
|
|
1109
|
+
const preferred = options.preferredTarget?.(input.kind);
|
|
1110
|
+
if (!preferred || preferred === "focused") return "focused";
|
|
1111
|
+
const state = store.getState();
|
|
1112
|
+
if (canvasItemId(input.kind, input.key) in state.items) return "focused";
|
|
1113
|
+
const frame2 = canvasWindowFrame(state, input.windowId ?? windowId);
|
|
1114
|
+
if (!frame2?.isOpen) return "focused";
|
|
1115
|
+
const panes = listPaneIds(frame2.layout);
|
|
1116
|
+
const holding = panes.find((paneId) => state.panes[paneId]?.itemIds.some((id) => state.items[id]?.kind === input.kind));
|
|
1117
|
+
if (holding) return { paneId: holding };
|
|
1118
|
+
return state.panes[frame2.focusedPaneId]?.activeItemId ? preferred : "focused";
|
|
1119
|
+
};
|
|
992
1120
|
const controller = {
|
|
993
1121
|
store,
|
|
994
1122
|
windowId,
|
|
@@ -996,24 +1124,11 @@ function view(shared, windowId) {
|
|
|
996
1124
|
forWindow: (id) => view(shared, canvasWindowId(id)),
|
|
997
1125
|
getState: () => store.getState(),
|
|
998
1126
|
open(input) {
|
|
999
|
-
if (
|
|
1000
|
-
|
|
1001
|
-
return null;
|
|
1002
|
-
}
|
|
1003
|
-
if (input.data !== void 0) {
|
|
1004
|
-
const bad = findNonJson(input.data);
|
|
1005
|
-
if (bad) {
|
|
1006
|
-
onError({
|
|
1007
|
-
code: "non-json-data",
|
|
1008
|
-
message: `Canvas data for "${input.kind}" must be plain JSON; ${bad} is not.`,
|
|
1009
|
-
detail: { kind: input.kind, key: input.key }
|
|
1010
|
-
});
|
|
1011
|
-
return null;
|
|
1012
|
-
}
|
|
1013
|
-
}
|
|
1014
|
-
const target = input.target ?? "focused";
|
|
1127
|
+
if (!acceptable(input)) return null;
|
|
1128
|
+
const target = input.target ?? preferredTargetFor(input);
|
|
1015
1129
|
const splitShare = target === "split-down" ? rules.stackSplit : target === "split-right" ? rules.sideSplit : void 0;
|
|
1016
|
-
const
|
|
1130
|
+
const withTarget = input.target === target ? input : { ...input, target };
|
|
1131
|
+
const resolved = withTarget.windowId || windowId === CANVAS_MAIN_WINDOW ? withTarget : { ...withTarget, windowId };
|
|
1017
1132
|
dispatch(canvasActions.open(resolved, splitShare === void 0 ? void 0 : { splitShare }));
|
|
1018
1133
|
return canvasItemId(input.kind, input.key);
|
|
1019
1134
|
},
|
|
@@ -1029,6 +1144,34 @@ function view(shared, windowId) {
|
|
|
1029
1144
|
dispatch(canvasActions.update(itemId, patch));
|
|
1030
1145
|
return true;
|
|
1031
1146
|
},
|
|
1147
|
+
navigate(itemId, to) {
|
|
1148
|
+
if (!store.getState().items[itemId] || !acceptable(to)) return null;
|
|
1149
|
+
dispatch(canvasActions.navigate(itemId, to));
|
|
1150
|
+
const shown = canvasItemId(to.kind, to.key);
|
|
1151
|
+
return shown in store.getState().items ? shown : null;
|
|
1152
|
+
},
|
|
1153
|
+
back(itemId) {
|
|
1154
|
+
const history = store.getState().items[itemId]?.history ?? [];
|
|
1155
|
+
const entry = history[history.length - 1];
|
|
1156
|
+
if (!entry) return null;
|
|
1157
|
+
dispatch(canvasActions.back(itemId));
|
|
1158
|
+
return canvasItemId(entry.kind, entry.key);
|
|
1159
|
+
},
|
|
1160
|
+
newTab(paneId) {
|
|
1161
|
+
const state = store.getState();
|
|
1162
|
+
const frame2 = canvasWindowFrame(state, windowId);
|
|
1163
|
+
const target = paneId && state.panes[paneId] ? paneId : frame2?.focusedPaneId ?? state.focusedPaneId;
|
|
1164
|
+
const existing = state.panes[target]?.itemIds.find((id) => state.items[id]?.kind === CANVAS_LAUNCHER_KIND);
|
|
1165
|
+
if (existing) {
|
|
1166
|
+
dispatch(canvasActions.activate(existing));
|
|
1167
|
+
dispatch(canvasActions.setOpen(true, canvasWindowOfItem(store.getState(), existing) ?? windowId));
|
|
1168
|
+
return existing;
|
|
1169
|
+
}
|
|
1170
|
+
shared.launcherSeq += 1;
|
|
1171
|
+
const key = `${Date.now().toString(36)}-${shared.launcherSeq}`;
|
|
1172
|
+
dispatch(canvasActions.open({ kind: CANVAS_LAUNCHER_KIND, key, target: { paneId: target }, ...windowId === CANVAS_MAIN_WINDOW ? {} : { windowId } }));
|
|
1173
|
+
return canvasItemId(CANVAS_LAUNCHER_KIND, key);
|
|
1174
|
+
},
|
|
1032
1175
|
rekey(itemId, key) {
|
|
1033
1176
|
const item = store.getState().items[itemId];
|
|
1034
1177
|
if (!item) return null;
|
|
@@ -1278,6 +1421,32 @@ function selectCanvasWindowItemCount(s, windowId) {
|
|
|
1278
1421
|
return listPaneIds(layout).reduce((sum, paneId) => sum + (s.panes[paneId]?.itemIds.length ?? 0), 0);
|
|
1279
1422
|
}
|
|
1280
1423
|
var NO_WINDOWS = Object.freeze({});
|
|
1424
|
+
var NO_HISTORY = Object.freeze([]);
|
|
1425
|
+
var selectCanvasCanGoBack = (s, itemId) => (s.items[itemId]?.history?.length ?? 0) > 0;
|
|
1426
|
+
|
|
1427
|
+
// src/core/presentation.ts
|
|
1428
|
+
var CANVAS_NARROW_WIDTH = 480;
|
|
1429
|
+
var SQUARE_BAND = 1.2;
|
|
1430
|
+
function canvasOrientation(width, height) {
|
|
1431
|
+
if (!(width > 0) || !(height > 0)) return "square";
|
|
1432
|
+
const ratio = width / height;
|
|
1433
|
+
if (ratio >= SQUARE_BAND) return "landscape";
|
|
1434
|
+
if (ratio <= 1 / SQUARE_BAND) return "portrait";
|
|
1435
|
+
return "square";
|
|
1436
|
+
}
|
|
1437
|
+
function canvasPresentation(input) {
|
|
1438
|
+
const width = Math.max(0, Math.round(input.width));
|
|
1439
|
+
const height = Math.max(0, Math.round(input.height));
|
|
1440
|
+
return {
|
|
1441
|
+
host: "canvas",
|
|
1442
|
+
width,
|
|
1443
|
+
height,
|
|
1444
|
+
orientation: canvasOrientation(width, height),
|
|
1445
|
+
isNarrow: width > 0 && width < CANVAS_NARROW_WIDTH,
|
|
1446
|
+
isFullscreen: input.isFullscreen,
|
|
1447
|
+
paneCount: Math.max(1, input.paneCount)
|
|
1448
|
+
};
|
|
1449
|
+
}
|
|
1281
1450
|
|
|
1282
1451
|
// src/core/toggleKind.ts
|
|
1283
1452
|
var CANVAS_DEFAULT_KEY = "default";
|
|
@@ -1479,6 +1648,31 @@ function getCanvasKindsVersion() {
|
|
|
1479
1648
|
|
|
1480
1649
|
// src/react/provider.tsx
|
|
1481
1650
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
1651
|
+
function createLauncherEntryStore() {
|
|
1652
|
+
let entries = [];
|
|
1653
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
1654
|
+
const emit = () => {
|
|
1655
|
+
for (const listener of [...listeners]) listener();
|
|
1656
|
+
};
|
|
1657
|
+
return {
|
|
1658
|
+
add(entry) {
|
|
1659
|
+
entries = [...entries, entry];
|
|
1660
|
+
emit();
|
|
1661
|
+
let done = false;
|
|
1662
|
+
return () => {
|
|
1663
|
+
if (done) return;
|
|
1664
|
+
done = true;
|
|
1665
|
+
entries = entries.filter((e) => e !== entry);
|
|
1666
|
+
emit();
|
|
1667
|
+
};
|
|
1668
|
+
},
|
|
1669
|
+
list: () => entries,
|
|
1670
|
+
subscribe(listener) {
|
|
1671
|
+
listeners.add(listener);
|
|
1672
|
+
return () => listeners.delete(listener);
|
|
1673
|
+
}
|
|
1674
|
+
};
|
|
1675
|
+
}
|
|
1482
1676
|
var PHONE_MAX_WIDTH = 767;
|
|
1483
1677
|
var isPhoneViewport = () => typeof window !== "undefined" && window.innerWidth <= PHONE_MAX_WIDTH;
|
|
1484
1678
|
var isMacPlatform = () => typeof navigator !== "undefined" && /mac|iphone|ipad/i.test(navigator.userAgentData?.platform ?? navigator.platform ?? "");
|
|
@@ -1507,11 +1701,20 @@ function CanvasProvider({
|
|
|
1507
1701
|
layoutRules,
|
|
1508
1702
|
regionWidth = null,
|
|
1509
1703
|
tabStrip = "auto",
|
|
1510
|
-
|
|
1704
|
+
itemActions,
|
|
1705
|
+
popOut,
|
|
1706
|
+
dockBack,
|
|
1707
|
+
showMenu,
|
|
1708
|
+
overlay,
|
|
1709
|
+
onNativeBodyFrame
|
|
1511
1710
|
}) {
|
|
1711
|
+
const hostPorts = (0, import_react2.useMemo)(
|
|
1712
|
+
() => ({ itemActions, popOut, dockBack, showMenu, overlay, onNativeBodyFrame }),
|
|
1713
|
+
[itemActions, popOut, dockBack, showMenu, overlay, onNativeBodyFrame]
|
|
1714
|
+
);
|
|
1512
1715
|
const portsRef = (0, import_react2.useRef)(hostPorts);
|
|
1513
1716
|
portsRef.current = hostPorts;
|
|
1514
|
-
const [{ controller, layer }] = (0, import_react2.useState)(() => ({
|
|
1717
|
+
const [{ controller, layer, launcherEntries }] = (0, import_react2.useState)(() => ({
|
|
1515
1718
|
controller: createCanvasController({
|
|
1516
1719
|
store: store ?? createCanvasStore(),
|
|
1517
1720
|
persistence: persistence === void 0 ? createLocalStorageCanvasPersistence() : persistence,
|
|
@@ -1521,9 +1724,11 @@ function CanvasProvider({
|
|
|
1521
1724
|
windowId: windowIdProp ?? CANVAS_MAIN_WINDOW,
|
|
1522
1725
|
layoutRules,
|
|
1523
1726
|
windowPorts: () => portsRef.current,
|
|
1524
|
-
sessions: sessionHooksFor
|
|
1727
|
+
sessions: sessionHooksFor,
|
|
1728
|
+
preferredTarget: (kind) => getCanvasKind(kind)?.preferredTarget
|
|
1525
1729
|
}),
|
|
1526
|
-
layer: createCanvasLayer(() => portsRef.current)
|
|
1730
|
+
layer: createCanvasLayer(() => portsRef.current),
|
|
1731
|
+
launcherEntries: createLauncherEntryStore()
|
|
1527
1732
|
}));
|
|
1528
1733
|
const windowId = controller.windowId;
|
|
1529
1734
|
(0, import_react2.useEffect)(() => controller.start(), [controller]);
|
|
@@ -1548,7 +1753,10 @@ function CanvasProvider({
|
|
|
1548
1753
|
window.addEventListener("keydown", onKey);
|
|
1549
1754
|
return () => window.removeEventListener("keydown", onKey);
|
|
1550
1755
|
}, [controller, hotkeys, windowId]);
|
|
1551
|
-
const value =
|
|
1756
|
+
const value = (0, import_react2.useMemo)(
|
|
1757
|
+
() => ({ controller, windowId, ports: hostPorts, layer, regionWidth, tabStrip, launcherEntries }),
|
|
1758
|
+
[controller, windowId, hostPorts, layer, regionWidth, tabStrip, launcherEntries]
|
|
1759
|
+
);
|
|
1552
1760
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(CanvasContext.Provider, { value, children: [
|
|
1553
1761
|
children,
|
|
1554
1762
|
hostPorts.overlay ? null : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(CanvasDomOverlay, { layer })
|
|
@@ -1556,8 +1764,10 @@ function CanvasProvider({
|
|
|
1556
1764
|
}
|
|
1557
1765
|
function CanvasWindowScope({ windowId, children }) {
|
|
1558
1766
|
const parent = useCanvasContext();
|
|
1559
|
-
const
|
|
1560
|
-
|
|
1767
|
+
const value = (0, import_react2.useMemo)(() => {
|
|
1768
|
+
const id = canvasWindowId(windowId);
|
|
1769
|
+
return { ...parent, windowId: id, controller: parent.controller.forWindow(id) };
|
|
1770
|
+
}, [parent, windowId]);
|
|
1561
1771
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(CanvasContext.Provider, { value, children });
|
|
1562
1772
|
}
|
|
1563
1773
|
function useCanvasContext() {
|
|
@@ -1635,14 +1845,38 @@ function useCanvasKind(id) {
|
|
|
1635
1845
|
(0, import_react2.useSyncExternalStore)(subscribeCanvasKinds, getCanvasKindsVersion, getCanvasKindsVersion);
|
|
1636
1846
|
return getCanvasKind(id);
|
|
1637
1847
|
}
|
|
1848
|
+
var NO_ENTRIES = Object.freeze([]);
|
|
1849
|
+
var noEntries = () => NO_ENTRIES;
|
|
1850
|
+
function useCanvasLauncherEntry(entry) {
|
|
1851
|
+
const store = (0, import_react2.useContext)(CanvasContext)?.launcherEntries;
|
|
1852
|
+
const fingerprint = JSON.stringify([entry.kind, entry.key, entry.title ?? null, entry.data ?? null]);
|
|
1853
|
+
const icon = entry.icon;
|
|
1854
|
+
(0, import_react2.useEffect)(() => {
|
|
1855
|
+
if (!store) return void 0;
|
|
1856
|
+
const [kind, key, title, data] = JSON.parse(fingerprint);
|
|
1857
|
+
return store.add({ kind, key, title, data, ...icon ? { icon } : {} });
|
|
1858
|
+
}, [store, fingerprint, icon]);
|
|
1859
|
+
}
|
|
1860
|
+
function useCanvasLauncherEntries() {
|
|
1861
|
+
const store = (0, import_react2.useContext)(CanvasContext)?.launcherEntries;
|
|
1862
|
+
const all = (0, import_react2.useSyncExternalStore)(store ? store.subscribe : NO_SUBSCRIPTION, store ? store.list : noEntries, store ? store.list : noEntries);
|
|
1863
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1864
|
+
const out = [];
|
|
1865
|
+
for (let i = all.length - 1; i >= 0; i -= 1) {
|
|
1866
|
+
const entry = all[i];
|
|
1867
|
+
if (!entry || seen.has(`${entry.kind}::${entry.key}`)) continue;
|
|
1868
|
+
seen.add(`${entry.kind}::${entry.key}`);
|
|
1869
|
+
out.unshift(entry);
|
|
1870
|
+
}
|
|
1871
|
+
return out;
|
|
1872
|
+
}
|
|
1638
1873
|
|
|
1639
1874
|
// src/react/CanvasColumn.tsx
|
|
1640
|
-
var
|
|
1875
|
+
var import_react9 = require("react");
|
|
1641
1876
|
var import_tap_target = require("@ai-matrx/tap-target");
|
|
1642
1877
|
|
|
1643
|
-
// src/react/
|
|
1644
|
-
var
|
|
1645
|
-
var Menu = __toESM(require("@radix-ui/react-dropdown-menu"), 1);
|
|
1878
|
+
// src/react/bodies.tsx
|
|
1879
|
+
var import_react7 = require("react");
|
|
1646
1880
|
|
|
1647
1881
|
// src/react/chrome.tsx
|
|
1648
1882
|
var import_react3 = require("react");
|
|
@@ -1737,6 +1971,12 @@ var DockIcon = (p) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Glyph, { ...
|
|
|
1737
1971
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M21 3 12 12" }),
|
|
1738
1972
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M12 6v6h6" })
|
|
1739
1973
|
] });
|
|
1974
|
+
var LayoutGridIcon = (p) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Glyph, { ...p, children: [
|
|
1975
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("rect", { width: "7", height: "7", x: "3", y: "3", rx: "1" }),
|
|
1976
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("rect", { width: "7", height: "7", x: "14", y: "3", rx: "1" }),
|
|
1977
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("rect", { width: "7", height: "7", x: "14", y: "14", rx: "1" }),
|
|
1978
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("rect", { width: "7", height: "7", x: "3", y: "14", rx: "1" })
|
|
1979
|
+
] });
|
|
1740
1980
|
|
|
1741
1981
|
// src/react/chrome.tsx
|
|
1742
1982
|
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
@@ -1987,22 +2227,542 @@ function BrowserBar({ url, canGoBack, canGoForward, onBack, onForward, onReload,
|
|
|
1987
2227
|
] });
|
|
1988
2228
|
}
|
|
1989
2229
|
|
|
1990
|
-
// src/react/
|
|
2230
|
+
// src/react/dnd.ts
|
|
2231
|
+
var import_react4 = require("react");
|
|
2232
|
+
var CANVAS_DRAG_MIME = "application/x-matrx-canvas-item";
|
|
2233
|
+
function useTabDrag(itemId, title) {
|
|
2234
|
+
const { layer } = useCanvasInternals();
|
|
2235
|
+
const active = (0, import_react4.useRef)([]);
|
|
2236
|
+
const end = () => {
|
|
2237
|
+
for (const dispose of active.current.splice(0)) dispose();
|
|
2238
|
+
};
|
|
2239
|
+
(0, import_react4.useEffect)(() => end, []);
|
|
2240
|
+
return {
|
|
2241
|
+
draggable: true,
|
|
2242
|
+
onDragStart: (event) => {
|
|
2243
|
+
event.dataTransfer.setData(CANVAS_DRAG_MIME, itemId);
|
|
2244
|
+
event.dataTransfer.effectAllowed = "move";
|
|
2245
|
+
end();
|
|
2246
|
+
active.current.push(layer.beginBlocking(), layer.show({ type: "tab-drag", itemId, title }));
|
|
2247
|
+
},
|
|
2248
|
+
onDragEnd: end
|
|
2249
|
+
};
|
|
2250
|
+
}
|
|
2251
|
+
function usePaneDrop(paneId, getOutline) {
|
|
2252
|
+
const canvas = useCanvas();
|
|
2253
|
+
const { layer } = useCanvasInternals();
|
|
2254
|
+
const drop = (0, import_react4.useRef)(null);
|
|
2255
|
+
(0, import_react4.useEffect)(() => () => drop.current?.(), []);
|
|
2256
|
+
const hide = () => {
|
|
2257
|
+
drop.current?.();
|
|
2258
|
+
drop.current = null;
|
|
2259
|
+
};
|
|
2260
|
+
return {
|
|
2261
|
+
onDragOver: (event) => {
|
|
2262
|
+
if (!event.dataTransfer.types.includes(CANVAS_DRAG_MIME)) return;
|
|
2263
|
+
event.preventDefault();
|
|
2264
|
+
event.dataTransfer.dropEffect = "move";
|
|
2265
|
+
const outline = getOutline();
|
|
2266
|
+
if (!drop.current && outline) {
|
|
2267
|
+
drop.current = layer.show({ type: "drop-indicator", rect: toCanvasRect(outline.getBoundingClientRect()), paneId });
|
|
2268
|
+
}
|
|
2269
|
+
},
|
|
2270
|
+
onDragLeave: (event) => {
|
|
2271
|
+
const next = event.relatedTarget;
|
|
2272
|
+
if (next && event.currentTarget.contains(next)) return;
|
|
2273
|
+
hide();
|
|
2274
|
+
},
|
|
2275
|
+
onDrop: (event) => {
|
|
2276
|
+
hide();
|
|
2277
|
+
const itemId = event.dataTransfer.getData(CANVAS_DRAG_MIME);
|
|
2278
|
+
if (!itemId) return;
|
|
2279
|
+
event.preventDefault();
|
|
2280
|
+
canvas.moveItem(itemId, paneId);
|
|
2281
|
+
}
|
|
2282
|
+
};
|
|
2283
|
+
}
|
|
2284
|
+
|
|
2285
|
+
// src/react/launcher.tsx
|
|
1991
2286
|
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
1992
|
-
|
|
1993
|
-
|
|
2287
|
+
function useChoices() {
|
|
2288
|
+
const entries = useCanvasLauncherEntries();
|
|
2289
|
+
const kinds = useCanvasKinds();
|
|
2290
|
+
const byId = new Map(kinds.map((kind) => [kind.id, kind]));
|
|
2291
|
+
const contextual = [];
|
|
2292
|
+
for (const entry of entries) {
|
|
2293
|
+
const kind = byId.get(entry.kind);
|
|
2294
|
+
if (!kind) continue;
|
|
2295
|
+
contextual.push({
|
|
2296
|
+
id: `ctx:${canvasItemId(entry.kind, entry.key)}`,
|
|
2297
|
+
label: entry.title ?? kind.label,
|
|
2298
|
+
icon: entry.icon ?? kind.icon,
|
|
2299
|
+
input: { kind: entry.kind, key: entry.key, title: entry.title ?? null, ...entry.data !== void 0 ? { data: entry.data } : {} }
|
|
2300
|
+
});
|
|
2301
|
+
}
|
|
2302
|
+
const global = [];
|
|
2303
|
+
for (const kind of kinds) {
|
|
2304
|
+
const launcher = kind.launcher;
|
|
2305
|
+
if (!launcher) continue;
|
|
2306
|
+
global.push({
|
|
2307
|
+
id: `kind:${kind.id}`,
|
|
2308
|
+
label: launcher.title ?? kind.label,
|
|
2309
|
+
icon: kind.icon,
|
|
2310
|
+
input: { kind: kind.id, key: launcher.key, data: launcher.data, title: launcher.title ?? null }
|
|
2311
|
+
});
|
|
2312
|
+
}
|
|
2313
|
+
return { contextual, global };
|
|
2314
|
+
}
|
|
2315
|
+
function LauncherGrid({ onChoose }) {
|
|
2316
|
+
const { contextual, global } = useChoices();
|
|
2317
|
+
if (contextual.length === 0 && global.length === 0) return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(CanvasPanelEmpty, { children: "Nothing to open" });
|
|
2318
|
+
const section = (label, choices) => choices.length === 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("section", { className: "mxc-launcher-section", "aria-label": label ?? "Open", children: [
|
|
2319
|
+
label ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("h3", { className: "mxc-launcher-heading", children: label }) : null,
|
|
2320
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "mxc-launcher-grid", children: choices.map((choice) => {
|
|
2321
|
+
const Icon = choice.icon;
|
|
2322
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("button", { type: "button", className: "mxc-launcher-item", onClick: () => onChoose(choice.input), children: [
|
|
2323
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Icon, { className: "mxc-launcher-icon" }),
|
|
2324
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children: choice.label })
|
|
2325
|
+
] }, choice.id);
|
|
2326
|
+
}) })
|
|
2327
|
+
] });
|
|
2328
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "mxc-launcher", children: [
|
|
2329
|
+
section(contextual.length > 0 ? "On this page" : null, contextual),
|
|
2330
|
+
section(contextual.length > 0 ? "Open" : null, global)
|
|
2331
|
+
] });
|
|
2332
|
+
}
|
|
2333
|
+
function LauncherBody({ item, canvas }) {
|
|
2334
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(LauncherGrid, { onChoose: (input) => void canvas.navigate(item.id, input) });
|
|
2335
|
+
}
|
|
2336
|
+
function EmptyPaneLauncher({ paneId }) {
|
|
2337
|
+
const canvas = useCanvas();
|
|
2338
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
2339
|
+
LauncherGrid,
|
|
2340
|
+
{
|
|
2341
|
+
onChoose: (input) => {
|
|
2342
|
+
const tab = canvas.newTab(paneId);
|
|
2343
|
+
canvas.navigate(tab, input);
|
|
2344
|
+
}
|
|
2345
|
+
}
|
|
2346
|
+
);
|
|
2347
|
+
}
|
|
2348
|
+
var CANVAS_LAUNCHER_KIND_DEFINITION = {
|
|
2349
|
+
id: CANVAS_LAUNCHER_KIND,
|
|
2350
|
+
label: "New tab",
|
|
2351
|
+
icon: LayoutGridIcon,
|
|
2352
|
+
component: LauncherBody
|
|
2353
|
+
};
|
|
2354
|
+
function useItemKind(kindId) {
|
|
2355
|
+
const registered = useCanvasKind(kindId);
|
|
2356
|
+
return kindId === CANVAS_LAUNCHER_KIND ? CANVAS_LAUNCHER_KIND_DEFINITION : registered;
|
|
2357
|
+
}
|
|
2358
|
+
|
|
2359
|
+
// src/react/presentation.tsx
|
|
2360
|
+
var import_react5 = require("react");
|
|
2361
|
+
var CanvasPresentationContext = (0, import_react5.createContext)(null);
|
|
2362
|
+
function useCanvasPresentation() {
|
|
2363
|
+
return (0, import_react5.useContext)(CanvasPresentationContext);
|
|
2364
|
+
}
|
|
2365
|
+
|
|
2366
|
+
// src/react/slots.ts
|
|
2367
|
+
var import_react6 = require("react");
|
|
2368
|
+
var ZERO = Object.freeze({ width: 0, height: 0 });
|
|
2369
|
+
function createCanvasSlots() {
|
|
2370
|
+
const slots = /* @__PURE__ */ new Map();
|
|
2371
|
+
const sections = /* @__PURE__ */ new Map();
|
|
2372
|
+
const sizes = /* @__PURE__ */ new Map();
|
|
2373
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
2374
|
+
let observer = null;
|
|
2375
|
+
const emit = () => {
|
|
2376
|
+
for (const listener of [...listeners]) listener();
|
|
2377
|
+
};
|
|
2378
|
+
const read = (paneId, element) => {
|
|
2379
|
+
const box = element.getBoundingClientRect();
|
|
2380
|
+
const width = Math.round(box.width);
|
|
2381
|
+
const height = Math.round(box.height);
|
|
2382
|
+
const previous = sizes.get(paneId);
|
|
2383
|
+
if (previous && previous.width === width && previous.height === height) return false;
|
|
2384
|
+
sizes.set(paneId, { width, height });
|
|
2385
|
+
return true;
|
|
2386
|
+
};
|
|
2387
|
+
const measureAll = () => {
|
|
2388
|
+
let changed = false;
|
|
2389
|
+
for (const [paneId, element] of slots) changed = read(paneId, element) || changed;
|
|
2390
|
+
return changed;
|
|
2391
|
+
};
|
|
2392
|
+
const watch = () => {
|
|
2393
|
+
if (observer || typeof ResizeObserver === "undefined") return observer;
|
|
2394
|
+
observer = new ResizeObserver(() => {
|
|
2395
|
+
measureAll();
|
|
2396
|
+
emit();
|
|
2397
|
+
});
|
|
2398
|
+
return observer;
|
|
2399
|
+
};
|
|
2400
|
+
return {
|
|
2401
|
+
set(paneId, element) {
|
|
2402
|
+
if (slots.get(paneId) === element) return;
|
|
2403
|
+
const previous = slots.get(paneId);
|
|
2404
|
+
if (previous) observer?.unobserve(previous);
|
|
2405
|
+
slots.set(paneId, element);
|
|
2406
|
+
watch()?.observe(element);
|
|
2407
|
+
read(paneId, element);
|
|
2408
|
+
emit();
|
|
2409
|
+
},
|
|
2410
|
+
unset(paneId, element) {
|
|
2411
|
+
const current = slots.get(paneId);
|
|
2412
|
+
if (!current || element && current !== element) return;
|
|
2413
|
+
observer?.unobserve(current);
|
|
2414
|
+
slots.delete(paneId);
|
|
2415
|
+
sizes.delete(paneId);
|
|
2416
|
+
emit();
|
|
2417
|
+
},
|
|
2418
|
+
setSection(paneId, element) {
|
|
2419
|
+
if (element) sections.set(paneId, element);
|
|
2420
|
+
else sections.delete(paneId);
|
|
2421
|
+
},
|
|
2422
|
+
slot: (paneId) => slots.get(paneId) ?? null,
|
|
2423
|
+
section: (paneId) => sections.get(paneId) ?? null,
|
|
2424
|
+
size: (paneId) => sizes.get(paneId) ?? ZERO,
|
|
2425
|
+
measure() {
|
|
2426
|
+
if (measureAll()) emit();
|
|
2427
|
+
},
|
|
2428
|
+
subscribe(listener) {
|
|
2429
|
+
listeners.add(listener);
|
|
2430
|
+
return () => listeners.delete(listener);
|
|
2431
|
+
}
|
|
2432
|
+
};
|
|
2433
|
+
}
|
|
2434
|
+
var CanvasSlotsContext = (0, import_react6.createContext)(null);
|
|
2435
|
+
function useCanvasSlots() {
|
|
2436
|
+
return (0, import_react6.useContext)(CanvasSlotsContext);
|
|
2437
|
+
}
|
|
2438
|
+
function useSlotSize(slots, paneId) {
|
|
2439
|
+
return (0, import_react6.useSyncExternalStore)(
|
|
2440
|
+
slots.subscribe,
|
|
2441
|
+
() => slots.size(paneId),
|
|
2442
|
+
() => ZERO
|
|
2443
|
+
);
|
|
2444
|
+
}
|
|
2445
|
+
|
|
2446
|
+
// src/react/bodies.tsx
|
|
2447
|
+
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
2448
|
+
var useIsoLayoutEffect = typeof window === "undefined" ? import_react7.useEffect : import_react7.useLayoutEffect;
|
|
1994
2449
|
var LAZY = /* @__PURE__ */ Symbol.for("ai-matrx.canvas.lazy");
|
|
1995
2450
|
function kindComponent(kind) {
|
|
1996
2451
|
if (kind.component) return kind.component;
|
|
1997
2452
|
const holder = kind;
|
|
1998
|
-
if (!holder[LAZY] && kind.load) holder[LAZY] = (0,
|
|
2453
|
+
if (!holder[LAZY] && kind.load) holder[LAZY] = (0, import_react7.lazy)(kind.load);
|
|
1999
2454
|
return holder[LAZY] ?? null;
|
|
2000
2455
|
}
|
|
2001
2456
|
function resetKindComponent(kind) {
|
|
2002
2457
|
if (kind) delete kind[LAZY];
|
|
2003
2458
|
}
|
|
2459
|
+
function usePanePresentation(slots, paneId, isFullscreen, paneCount) {
|
|
2460
|
+
const size = useSlotSize(slots, paneId);
|
|
2461
|
+
return (0, import_react7.useMemo)(
|
|
2462
|
+
() => canvasPresentation({ width: size.width, height: size.height, isFullscreen, paneCount }),
|
|
2463
|
+
[size.width, size.height, isFullscreen, paneCount]
|
|
2464
|
+
);
|
|
2465
|
+
}
|
|
2466
|
+
var hostKey = (item) => item.instance ?? item.id;
|
|
2467
|
+
var SEP2 = "";
|
|
2468
|
+
var ROW = "";
|
|
2469
|
+
function layerSignature(state, windowId, onlyPane) {
|
|
2470
|
+
const frame2 = canvasWindowFrame(state, windowId);
|
|
2471
|
+
if (!frame2) return "";
|
|
2472
|
+
const rows = [];
|
|
2473
|
+
const paneIds = listPaneIds(frame2.layout);
|
|
2474
|
+
for (const paneId of onlyPane ? [onlyPane] : paneIds) {
|
|
2475
|
+
const pane = state.panes[paneId];
|
|
2476
|
+
for (const itemId of pane?.itemIds ?? []) {
|
|
2477
|
+
const item = state.items[itemId];
|
|
2478
|
+
if (item) rows.push({ item, row: [hostKey(item), itemId, paneId, pane?.activeItemId === itemId ? "1" : "0"].join(SEP2) });
|
|
2479
|
+
}
|
|
2480
|
+
}
|
|
2481
|
+
rows.sort((a, b) => a.item.openedAt - b.item.openedAt || hostKey(a.item).localeCompare(hostKey(b.item)));
|
|
2482
|
+
const head = [frame2.isOpen ? "1" : "0", frame2.isFullscreen ? "1" : "0", frame2.focusedPaneId, String(paneIds.length)].join(SEP2);
|
|
2483
|
+
return [head, ...rows.map((r) => r.row)].join(ROW);
|
|
2484
|
+
}
|
|
2485
|
+
function parseSignature(signature) {
|
|
2486
|
+
if (!signature) return null;
|
|
2487
|
+
const [head = "", ...rows] = signature.split(ROW);
|
|
2488
|
+
const [open, full, focusedPaneId = "", count = "1"] = head.split(SEP2);
|
|
2489
|
+
return {
|
|
2490
|
+
isOpen: open === "1",
|
|
2491
|
+
isFullscreen: full === "1",
|
|
2492
|
+
focusedPaneId,
|
|
2493
|
+
paneCount: Number(count) || 1,
|
|
2494
|
+
placed: rows.map((row) => {
|
|
2495
|
+
const [hostId = "", itemId = "", paneId = "", active] = row.split(SEP2);
|
|
2496
|
+
return { hostId, itemId, paneId, active: active === "1" };
|
|
2497
|
+
})
|
|
2498
|
+
};
|
|
2499
|
+
}
|
|
2500
|
+
function createPlacement(getRoot, slots, afterPlace) {
|
|
2501
|
+
const hosts = /* @__PURE__ */ new Map();
|
|
2502
|
+
let placing = false;
|
|
2503
|
+
const place = () => {
|
|
2504
|
+
const root = getRoot();
|
|
2505
|
+
if (!root || placing) return;
|
|
2506
|
+
placing = true;
|
|
2507
|
+
try {
|
|
2508
|
+
slots.measure();
|
|
2509
|
+
const base = root.getBoundingClientRect();
|
|
2510
|
+
for (const { element, paneId } of hosts.values()) {
|
|
2511
|
+
const slot = slots.slot(paneId);
|
|
2512
|
+
if (!slot) {
|
|
2513
|
+
element.dataset.placed = "false";
|
|
2514
|
+
continue;
|
|
2515
|
+
}
|
|
2516
|
+
const box = slot.getBoundingClientRect();
|
|
2517
|
+
const style = element.style;
|
|
2518
|
+
style.left = `${box.left - base.left}px`;
|
|
2519
|
+
style.top = `${box.top - base.top}px`;
|
|
2520
|
+
style.width = `${box.width}px`;
|
|
2521
|
+
style.height = `${box.height}px`;
|
|
2522
|
+
element.dataset.placed = "true";
|
|
2523
|
+
}
|
|
2524
|
+
} finally {
|
|
2525
|
+
placing = false;
|
|
2526
|
+
}
|
|
2527
|
+
afterPlace();
|
|
2528
|
+
};
|
|
2529
|
+
return {
|
|
2530
|
+
register(key, entry) {
|
|
2531
|
+
hosts.set(key, entry);
|
|
2532
|
+
return () => {
|
|
2533
|
+
if (hosts.get(key) === entry) hosts.delete(key);
|
|
2534
|
+
};
|
|
2535
|
+
},
|
|
2536
|
+
place
|
|
2537
|
+
};
|
|
2538
|
+
}
|
|
2539
|
+
function CanvasBodyLayer() {
|
|
2540
|
+
const { windowId, layer } = useCanvasInternals();
|
|
2541
|
+
const slots = useCanvasSlots();
|
|
2542
|
+
const signature = useCanvasState((s) => layerSignature(s, windowId));
|
|
2543
|
+
const root = (0, import_react7.useRef)(null);
|
|
2544
|
+
const [placement] = (0, import_react7.useState)(
|
|
2545
|
+
() => slots ? createPlacement(() => root.current, slots, () => layer.native.remeasure()) : null
|
|
2546
|
+
);
|
|
2547
|
+
useIsoLayoutEffect(() => placement?.place());
|
|
2548
|
+
useIsoLayoutEffect(() => slots && placement ? slots.subscribe(placement.place) : void 0, [slots, placement]);
|
|
2549
|
+
const shape = parseSignature(signature);
|
|
2550
|
+
if (!slots || !placement || !shape) return null;
|
|
2551
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { ref: root, className: "mxc-bodies", "data-mxc-bodies": windowId, children: shape.placed.map(({ hostId, itemId, paneId, active }) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
2552
|
+
BodyHost,
|
|
2553
|
+
{
|
|
2554
|
+
hostId,
|
|
2555
|
+
itemId,
|
|
2556
|
+
paneId,
|
|
2557
|
+
visible: shape.isOpen && active,
|
|
2558
|
+
focused: active && shape.focusedPaneId === paneId,
|
|
2559
|
+
isFullscreen: shape.isFullscreen,
|
|
2560
|
+
paneCount: shape.paneCount,
|
|
2561
|
+
slots,
|
|
2562
|
+
placement
|
|
2563
|
+
},
|
|
2564
|
+
hostId
|
|
2565
|
+
)) });
|
|
2566
|
+
}
|
|
2567
|
+
function CanvasPaneInlineBodies({ paneId, slots }) {
|
|
2568
|
+
const { windowId } = useCanvasInternals();
|
|
2569
|
+
const shape = parseSignature(useCanvasState((s) => layerSignature(s, windowId, paneId)));
|
|
2570
|
+
if (!shape) return null;
|
|
2571
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_jsx_runtime6.Fragment, { children: shape.placed.map(({ hostId, itemId, active }) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
2572
|
+
BodyHost,
|
|
2573
|
+
{
|
|
2574
|
+
hostId,
|
|
2575
|
+
itemId,
|
|
2576
|
+
paneId,
|
|
2577
|
+
visible: active,
|
|
2578
|
+
focused: active && shape.focusedPaneId === paneId,
|
|
2579
|
+
isFullscreen: shape.isFullscreen,
|
|
2580
|
+
paneCount: shape.paneCount,
|
|
2581
|
+
slots,
|
|
2582
|
+
placement: null
|
|
2583
|
+
},
|
|
2584
|
+
hostId
|
|
2585
|
+
)) });
|
|
2586
|
+
}
|
|
2587
|
+
var BodyHost = (0, import_react7.memo)(function BodyHost2({
|
|
2588
|
+
hostId,
|
|
2589
|
+
itemId,
|
|
2590
|
+
paneId,
|
|
2591
|
+
visible,
|
|
2592
|
+
focused,
|
|
2593
|
+
isFullscreen,
|
|
2594
|
+
paneCount,
|
|
2595
|
+
slots,
|
|
2596
|
+
placement
|
|
2597
|
+
}) {
|
|
2598
|
+
const canvas = useCanvas();
|
|
2599
|
+
const { windowId, ports } = useCanvasInternals();
|
|
2600
|
+
const item = useCanvasState((s) => s.items[itemId]);
|
|
2601
|
+
const kind = useItemKind(item?.kind ?? "");
|
|
2602
|
+
const element = (0, import_react7.useRef)(null);
|
|
2603
|
+
const [everShown, setEverShown] = (0, import_react7.useState)(visible);
|
|
2604
|
+
if (visible && !everShown) setEverShown(true);
|
|
2605
|
+
const mounted = kind?.unmountWhenHidden ? visible : everShown;
|
|
2606
|
+
const presentation = usePanePresentation(slots, paneId, isFullscreen, paneCount);
|
|
2607
|
+
const drop = usePaneDrop(paneId, () => slots.section(paneId));
|
|
2608
|
+
useIsoLayoutEffect(() => {
|
|
2609
|
+
const el = element.current;
|
|
2610
|
+
if (!placement || !el) return void 0;
|
|
2611
|
+
return placement.register(hostId, { element: el, paneId });
|
|
2612
|
+
}, [placement, hostId, paneId]);
|
|
2613
|
+
if (!item) return null;
|
|
2614
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
2615
|
+
"div",
|
|
2616
|
+
{
|
|
2617
|
+
ref: element,
|
|
2618
|
+
className: "mxc-item-host",
|
|
2619
|
+
"data-inline": placement ? void 0 : "",
|
|
2620
|
+
"data-placed": placement ? "false" : void 0,
|
|
2621
|
+
"data-visible": visible ? "true" : "false",
|
|
2622
|
+
"data-kind": item.kind,
|
|
2623
|
+
"data-item": item.id,
|
|
2624
|
+
"data-pane": paneId,
|
|
2625
|
+
inert: !visible,
|
|
2626
|
+
"aria-hidden": visible ? void 0 : true,
|
|
2627
|
+
onPointerDownCapture: () => {
|
|
2628
|
+
if (!focused) canvas.focusPane(paneId);
|
|
2629
|
+
},
|
|
2630
|
+
...drop,
|
|
2631
|
+
children: mounted ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(CanvasPresentationContext.Provider, { value: presentation, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "mxc-item", "data-kind": item.kind, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
2632
|
+
ItemContent,
|
|
2633
|
+
{
|
|
2634
|
+
item,
|
|
2635
|
+
kind,
|
|
2636
|
+
paneId,
|
|
2637
|
+
windowId,
|
|
2638
|
+
focused,
|
|
2639
|
+
visible,
|
|
2640
|
+
presentation,
|
|
2641
|
+
canvas,
|
|
2642
|
+
hasNativePort: !!ports.onNativeBodyFrame
|
|
2643
|
+
}
|
|
2644
|
+
) }) }) : null
|
|
2645
|
+
}
|
|
2646
|
+
);
|
|
2647
|
+
});
|
|
2648
|
+
function ItemContent({
|
|
2649
|
+
item,
|
|
2650
|
+
kind,
|
|
2651
|
+
paneId,
|
|
2652
|
+
windowId,
|
|
2653
|
+
focused,
|
|
2654
|
+
visible,
|
|
2655
|
+
presentation,
|
|
2656
|
+
canvas,
|
|
2657
|
+
hasNativePort
|
|
2658
|
+
}) {
|
|
2659
|
+
let unavailable = null;
|
|
2660
|
+
try {
|
|
2661
|
+
unavailable = kind?.unavailable?.(item.data, item) ?? null;
|
|
2662
|
+
} catch (error) {
|
|
2663
|
+
console.error(`[@ai-matrx/canvas] "${item.kind}" unavailable() threw`, error);
|
|
2664
|
+
}
|
|
2665
|
+
if (!kind) return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ItemProblem, { message: "This item can no longer be shown.", onClose: () => canvas.close(item.id) });
|
|
2666
|
+
if (unavailable) return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(CanvasPanelEmpty, { children: unavailable });
|
|
2667
|
+
const rendered = kindComponent(kind) ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Rendered, { kind, item, paneId, isFocused: focused, isVisible: visible, canvas, windowId, presentation }) : null;
|
|
2668
|
+
if (kind.body === "native") {
|
|
2669
|
+
return hasNativePort ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(NativeBody, { itemId: item.id, windowId, visible, children: rendered }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(CanvasPanelEmpty, { children: "Opens in the desktop app" });
|
|
2670
|
+
}
|
|
2671
|
+
return rendered;
|
|
2672
|
+
}
|
|
2673
|
+
var Rendered = (0, import_react7.memo)(function Rendered2({
|
|
2674
|
+
kind,
|
|
2675
|
+
item,
|
|
2676
|
+
paneId,
|
|
2677
|
+
isFocused,
|
|
2678
|
+
isVisible,
|
|
2679
|
+
canvas,
|
|
2680
|
+
windowId,
|
|
2681
|
+
presentation
|
|
2682
|
+
}) {
|
|
2683
|
+
const props = { item, data: item.data, paneId, isFocused, isVisible, canvas, windowId, presentation };
|
|
2684
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ItemBoundary, { item, onClose: () => canvas.close(item.id), onRetry: () => resetKindComponent(kind), children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react7.Suspense, { fallback: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "mxc-loading", "aria-busy": "true" }), children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(KindRender, { kind, props }) }) });
|
|
2685
|
+
});
|
|
2686
|
+
function KindRender({ kind, props }) {
|
|
2687
|
+
const Render = kindComponent(kind);
|
|
2688
|
+
if (!Render) throw new Error(`[@ai-matrx/canvas] kind "${kind.id}" has nothing to render.`);
|
|
2689
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Render, { ...props });
|
|
2690
|
+
}
|
|
2691
|
+
function NativeBody({ itemId, windowId, visible, children }) {
|
|
2692
|
+
const { layer } = useCanvasInternals();
|
|
2693
|
+
const ref = (0, import_react7.useRef)(null);
|
|
2694
|
+
const measure = (0, import_react7.useRef)(() => void 0);
|
|
2695
|
+
measure.current = () => {
|
|
2696
|
+
const el = ref.current;
|
|
2697
|
+
const box = visible && el?.isConnected ? el.getBoundingClientRect() : null;
|
|
2698
|
+
if (!box || box.width <= 0 || box.height <= 0) {
|
|
2699
|
+
layer.native.report(itemId, null, { visible: false, windowId });
|
|
2700
|
+
return;
|
|
2701
|
+
}
|
|
2702
|
+
const rect = { ...toCanvasRect(box), devicePixelRatio: window.devicePixelRatio || 1 };
|
|
2703
|
+
layer.native.report(itemId, rect, { visible: !layer.isBlocked(), windowId });
|
|
2704
|
+
};
|
|
2705
|
+
useIsoLayoutEffect(() => measure.current());
|
|
2706
|
+
useIsoLayoutEffect(() => {
|
|
2707
|
+
const el = ref.current;
|
|
2708
|
+
const run = () => measure.current();
|
|
2709
|
+
const observer = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(run);
|
|
2710
|
+
if (el) observer?.observe(el);
|
|
2711
|
+
const unregister = layer.native.register(run);
|
|
2712
|
+
window.addEventListener("resize", run);
|
|
2713
|
+
return () => {
|
|
2714
|
+
observer?.disconnect();
|
|
2715
|
+
unregister();
|
|
2716
|
+
window.removeEventListener("resize", run);
|
|
2717
|
+
layer.native.report(itemId, null, { visible: false, windowId });
|
|
2718
|
+
};
|
|
2719
|
+
}, [itemId, windowId, layer]);
|
|
2720
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { ref, className: "mxc-native-body", "data-native-body": itemId, children });
|
|
2721
|
+
}
|
|
2722
|
+
function ItemProblem({ message, onClose, onRetry }) {
|
|
2723
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "mxc-problem", role: "alert", children: [
|
|
2724
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(AlertIcon, {}),
|
|
2725
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { children: message }),
|
|
2726
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "mxc-problem-actions", children: [
|
|
2727
|
+
onRetry ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", className: "mxc-text-button", onClick: onRetry, children: "Try again" }) : null,
|
|
2728
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", className: "mxc-text-button", onClick: onClose, children: "Close tab" })
|
|
2729
|
+
] })
|
|
2730
|
+
] });
|
|
2731
|
+
}
|
|
2732
|
+
var ItemBoundary = class extends import_react7.Component {
|
|
2733
|
+
state = { error: null };
|
|
2734
|
+
static getDerivedStateFromError(error) {
|
|
2735
|
+
return { error };
|
|
2736
|
+
}
|
|
2737
|
+
componentDidCatch(error, info) {
|
|
2738
|
+
console.error(`[@ai-matrx/canvas] "${this.props.item.kind}" failed to render`, error, info.componentStack);
|
|
2739
|
+
}
|
|
2740
|
+
render() {
|
|
2741
|
+
if (this.state.error) {
|
|
2742
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
2743
|
+
ItemProblem,
|
|
2744
|
+
{
|
|
2745
|
+
message: "This item failed to load.",
|
|
2746
|
+
onClose: this.props.onClose,
|
|
2747
|
+
onRetry: () => {
|
|
2748
|
+
this.props.onRetry();
|
|
2749
|
+
this.setState({ error: null });
|
|
2750
|
+
}
|
|
2751
|
+
}
|
|
2752
|
+
);
|
|
2753
|
+
}
|
|
2754
|
+
return this.props.children;
|
|
2755
|
+
}
|
|
2756
|
+
};
|
|
2757
|
+
|
|
2758
|
+
// src/react/CanvasPane.tsx
|
|
2759
|
+
var import_react8 = require("react");
|
|
2760
|
+
var Menu = __toESM(require("@radix-ui/react-dropdown-menu"), 1);
|
|
2761
|
+
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
2762
|
+
var useIsoLayoutEffect2 = typeof window === "undefined" ? import_react8.useEffect : import_react8.useLayoutEffect;
|
|
2004
2763
|
function itemTitle(item, kind) {
|
|
2005
2764
|
if (item.title) return item.title;
|
|
2765
|
+
if (item.kind === CANVAS_LAUNCHER_KIND) return "New tab";
|
|
2006
2766
|
if (kind?.title) {
|
|
2007
2767
|
try {
|
|
2008
2768
|
const title = kind.title(item.data, item);
|
|
@@ -2013,70 +2773,46 @@ function itemTitle(item, kind) {
|
|
|
2013
2773
|
return kind?.label ?? item.kind;
|
|
2014
2774
|
}
|
|
2015
2775
|
var isMac = () => typeof navigator !== "undefined" && /mac|iphone|ipad/i.test(navigator.platform ?? "");
|
|
2016
|
-
function useTabDrag(itemId, title) {
|
|
2017
|
-
const { layer } = useCanvasInternals();
|
|
2018
|
-
const active = (0, import_react4.useRef)([]);
|
|
2019
|
-
const end = () => {
|
|
2020
|
-
for (const dispose of active.current.splice(0)) dispose();
|
|
2021
|
-
};
|
|
2022
|
-
(0, import_react4.useEffect)(() => end, []);
|
|
2023
|
-
return {
|
|
2024
|
-
draggable: true,
|
|
2025
|
-
onDragStart: (event) => {
|
|
2026
|
-
event.dataTransfer.setData(DRAG_MIME, itemId);
|
|
2027
|
-
event.dataTransfer.effectAllowed = "move";
|
|
2028
|
-
end();
|
|
2029
|
-
active.current.push(layer.beginBlocking(), layer.show({ type: "tab-drag", itemId, title }));
|
|
2030
|
-
},
|
|
2031
|
-
onDragEnd: end
|
|
2032
|
-
};
|
|
2033
|
-
}
|
|
2034
2776
|
function CanvasPaneView({ paneId }) {
|
|
2035
2777
|
const canvas = useCanvas();
|
|
2036
|
-
const { windowId,
|
|
2778
|
+
const { windowId, tabStrip } = useCanvasInternals();
|
|
2779
|
+
const columnSlots = useCanvasSlots();
|
|
2780
|
+
const [ownSlots] = (0, import_react8.useState)(createCanvasSlots);
|
|
2781
|
+
const slots = columnSlots ?? ownSlots;
|
|
2037
2782
|
const pane = useCanvasState((s) => s.panes[paneId]);
|
|
2038
|
-
const
|
|
2783
|
+
const active = useCanvasState((s) => {
|
|
2784
|
+
const activeId = s.panes[paneId]?.activeItemId;
|
|
2785
|
+
return activeId ? s.items[activeId] : void 0;
|
|
2786
|
+
});
|
|
2039
2787
|
const isFullscreen = useCanvasState((s) => selectCanvasIsFullscreen(s, windowId));
|
|
2788
|
+
const isOpen = useCanvasState((s) => selectCanvasIsOpen(s, windowId));
|
|
2040
2789
|
const focused = useCanvasState((s) => selectCanvasFocusedPaneId(s, windowId) === paneId);
|
|
2041
2790
|
const paneCount = useCanvasState((s) => selectCanvasPaneCount(s, windowId));
|
|
2042
2791
|
const role = useCanvasState((s) => windowId === CANVAS_MAIN_WINDOW ? "main" : s.windows[windowId]?.role ?? "window");
|
|
2043
|
-
const
|
|
2044
|
-
const kind =
|
|
2045
|
-
const
|
|
2046
|
-
const
|
|
2047
|
-
|
|
2792
|
+
const canGoBack = useCanvasState((s) => active ? selectCanvasCanGoBack(s, active.id) : false);
|
|
2793
|
+
const kind = useItemKind(active?.kind ?? "");
|
|
2794
|
+
const sectionRef = (0, import_react8.useRef)(null);
|
|
2795
|
+
const bodyRef = (0, import_react8.useRef)(null);
|
|
2796
|
+
const presentation = usePanePresentation(slots, paneId, isFullscreen, paneCount);
|
|
2797
|
+
const drop = usePaneDrop(paneId, () => sectionRef.current);
|
|
2798
|
+
useIsoLayoutEffect2(() => {
|
|
2799
|
+
const body = bodyRef.current;
|
|
2800
|
+
const section = sectionRef.current;
|
|
2801
|
+
if (body) slots.set(paneId, body);
|
|
2802
|
+
slots.setSection(paneId, section);
|
|
2803
|
+
return () => {
|
|
2804
|
+
slots.unset(paneId, body);
|
|
2805
|
+
if (slots.section(paneId) === section) slots.setSection(paneId, null);
|
|
2806
|
+
};
|
|
2807
|
+
}, [slots, paneId]);
|
|
2048
2808
|
if (!pane) return null;
|
|
2049
|
-
const kindProps = active && kind ? { item: active, data: active.data, paneId, isFocused: focused, canvas, windowId } : null;
|
|
2809
|
+
const kindProps = active && kind ? { item: active, data: active.data, paneId, isFocused: focused, isVisible: isOpen, canvas, windowId, presentation } : null;
|
|
2050
2810
|
const Leading = kind?.HeaderLeading;
|
|
2051
2811
|
const Action = kind?.HeaderAction;
|
|
2052
2812
|
const Toolbar = kind?.Toolbar;
|
|
2053
2813
|
const single = tabStrip === "auto" && pane.itemIds.length === 1 && active;
|
|
2054
|
-
const hideDrop = () => {
|
|
2055
|
-
drop.current?.();
|
|
2056
|
-
drop.current = null;
|
|
2057
|
-
};
|
|
2058
|
-
const onDragOver = (event) => {
|
|
2059
|
-
if (!event.dataTransfer.types.includes(DRAG_MIME)) return;
|
|
2060
|
-
event.preventDefault();
|
|
2061
|
-
event.dataTransfer.dropEffect = "move";
|
|
2062
|
-
if (!drop.current && sectionRef.current) {
|
|
2063
|
-
drop.current = layer.show({ type: "drop-indicator", rect: toCanvasRect(sectionRef.current.getBoundingClientRect()), paneId });
|
|
2064
|
-
}
|
|
2065
|
-
};
|
|
2066
|
-
const onDragLeave = (event) => {
|
|
2067
|
-
const next = event.relatedTarget;
|
|
2068
|
-
if (next && sectionRef.current?.contains(next)) return;
|
|
2069
|
-
hideDrop();
|
|
2070
|
-
};
|
|
2071
|
-
const onDrop = (event) => {
|
|
2072
|
-
hideDrop();
|
|
2073
|
-
const itemId = event.dataTransfer.getData(DRAG_MIME);
|
|
2074
|
-
if (!itemId) return;
|
|
2075
|
-
event.preventDefault();
|
|
2076
|
-
canvas.moveItem(itemId, paneId);
|
|
2077
|
-
};
|
|
2078
2814
|
const closeLabel = role === "pop-out" && paneCount === 1 ? "Close window" : paneCount > 1 ? "Close pane" : "Close canvas";
|
|
2079
|
-
return /* @__PURE__ */ (0,
|
|
2815
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
2080
2816
|
CanvasPanel,
|
|
2081
2817
|
{
|
|
2082
2818
|
ref: sectionRef,
|
|
@@ -2085,10 +2821,14 @@ function CanvasPaneView({ paneId }) {
|
|
|
2085
2821
|
title: active ? itemTitle(active, kind) : "Canvas",
|
|
2086
2822
|
focused,
|
|
2087
2823
|
"data-mxc-pane": paneId,
|
|
2088
|
-
header:
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2824
|
+
header: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
|
|
2825
|
+
canGoBack && active ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(CanvasHeaderButton, { className: "mxc-back", label: "Back", onClick: () => void canvas.back(active.id), children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ArrowLeftIcon, {}) }) : null,
|
|
2826
|
+
single && active ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(SingleTitle, { item: active, kind }) : pane.itemIds.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(TabStrip, { paneId, itemIds: pane.itemIds, activeItemId: pane.activeItemId }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "mxc-panel-title", children: "New tab" }),
|
|
2827
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(CanvasHeaderButton, { className: "mxc-new-tab", label: "New tab", onClick: () => void canvas.newTab(paneId), children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(PlusIcon, {}) })
|
|
2828
|
+
] }),
|
|
2829
|
+
leading: Leading && kindProps ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Leading, { ...kindProps }) : null,
|
|
2830
|
+
actions: Action && kindProps ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Action, { ...kindProps }) : null,
|
|
2831
|
+
renderMenu: () => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(PaneMenu, { paneId, windowId, activeItem: active, kind, kindProps, tabCount: pane.itemIds.length, role }),
|
|
2092
2832
|
expanded: isFullscreen,
|
|
2093
2833
|
onToggleExpand: () => canvas.setFullscreen(!isFullscreen),
|
|
2094
2834
|
expandShortcut: canvasShortcutHint("canvas.toggleFullscreen", isMac()),
|
|
@@ -2098,22 +2838,15 @@ function CanvasPaneView({ paneId }) {
|
|
|
2098
2838
|
onPointerDownCapture: () => {
|
|
2099
2839
|
if (!focused) canvas.focusPane(paneId);
|
|
2100
2840
|
},
|
|
2101
|
-
|
|
2102
|
-
onDragLeave,
|
|
2103
|
-
onDrop,
|
|
2841
|
+
...drop,
|
|
2104
2842
|
children: [
|
|
2105
|
-
Toolbar && kindProps ? /* @__PURE__ */ (0,
|
|
2106
|
-
pane.itemIds.length === 0 ? /* @__PURE__ */ (0,
|
|
2107
|
-
const item = items[itemId];
|
|
2108
|
-
if (!item) return null;
|
|
2109
|
-
const isActive = itemId === pane.activeItemId;
|
|
2110
|
-
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ItemBody, { item, paneId, windowId, isActive, isFocused: focused && isActive, hasNativePort: !!ports.onNativeBodyFrame }, itemId);
|
|
2111
|
-
})
|
|
2843
|
+
Toolbar && kindProps ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "mxc-item-toolbar", "data-kind": active?.kind, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ToolbarBoundary, { kindId: kind?.id ?? "", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Toolbar, { ...kindProps }) }) }) : null,
|
|
2844
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { ref: bodyRef, className: "mxc-pane-slot", "data-mxc-slot": paneId, children: pane.itemIds.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(EmptyPaneLauncher, { paneId }) : columnSlots ? null : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(CanvasPaneInlineBodies, { paneId, slots }) })
|
|
2112
2845
|
]
|
|
2113
2846
|
}
|
|
2114
2847
|
);
|
|
2115
2848
|
}
|
|
2116
|
-
var ToolbarBoundary = class extends
|
|
2849
|
+
var ToolbarBoundary = class extends import_react8.Component {
|
|
2117
2850
|
state = { failed: false };
|
|
2118
2851
|
static getDerivedStateFromError() {
|
|
2119
2852
|
return { failed: true };
|
|
@@ -2126,41 +2859,71 @@ var ToolbarBoundary = class extends import_react4.Component {
|
|
|
2126
2859
|
}
|
|
2127
2860
|
};
|
|
2128
2861
|
function SingleTitle({ item, kind }) {
|
|
2862
|
+
const canvas = useCanvas();
|
|
2129
2863
|
const title = itemTitle(item, kind);
|
|
2130
2864
|
const drag = useTabDrag(item.id, title);
|
|
2131
2865
|
const Icon = kind?.icon ?? AlertIcon;
|
|
2132
|
-
return /* @__PURE__ */ (0,
|
|
2133
|
-
/* @__PURE__ */ (0,
|
|
2134
|
-
/* @__PURE__ */ (0,
|
|
2866
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: "mxc-panel-title mxc-single-title", "data-draggable": "", title, ...drag, children: [
|
|
2867
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Icon, { className: "mxc-title-icon" }),
|
|
2868
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "mxc-tab-title", children: title }),
|
|
2869
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { type: "button", className: "mxc-tab-close", "data-slot": "browser-tab-close", "aria-label": `Close ${title}`, onClick: () => canvas.close(item.id), children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(CloseIcon, {}) })
|
|
2135
2870
|
] });
|
|
2136
2871
|
}
|
|
2872
|
+
function TabStrip({ paneId, itemIds, activeItemId }) {
|
|
2873
|
+
const strip = (0, import_react8.useRef)(null);
|
|
2874
|
+
useIsoLayoutEffect2(() => {
|
|
2875
|
+
const el = strip.current;
|
|
2876
|
+
if (!el) return void 0;
|
|
2877
|
+
const reveal = () => {
|
|
2878
|
+
const tab = el.querySelector('[role="tab"][aria-selected="true"]');
|
|
2879
|
+
if (!tab) return;
|
|
2880
|
+
const left = tab.offsetLeft;
|
|
2881
|
+
const right = left + tab.offsetWidth;
|
|
2882
|
+
if (left < el.scrollLeft) el.scrollLeft = left;
|
|
2883
|
+
else if (right > el.scrollLeft + el.clientWidth) el.scrollLeft = right - el.clientWidth;
|
|
2884
|
+
};
|
|
2885
|
+
reveal();
|
|
2886
|
+
const observer = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(reveal);
|
|
2887
|
+
observer?.observe(el);
|
|
2888
|
+
return () => observer?.disconnect();
|
|
2889
|
+
}, [activeItemId, itemIds.length]);
|
|
2890
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
2891
|
+
"div",
|
|
2892
|
+
{
|
|
2893
|
+
ref: strip,
|
|
2894
|
+
className: "mxc-tabs",
|
|
2895
|
+
role: "tablist",
|
|
2896
|
+
"aria-label": "Canvas tabs",
|
|
2897
|
+
onWheel: (event) => {
|
|
2898
|
+
const el = strip.current;
|
|
2899
|
+
if (el && Math.abs(event.deltaY) > Math.abs(event.deltaX)) el.scrollLeft += event.deltaY;
|
|
2900
|
+
},
|
|
2901
|
+
children: itemIds.map((itemId, index) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ItemTab, { itemId, paneId, index, active: itemId === activeItemId }, itemId))
|
|
2902
|
+
}
|
|
2903
|
+
);
|
|
2904
|
+
}
|
|
2137
2905
|
function ItemTab({ itemId, paneId, index, active }) {
|
|
2138
2906
|
const canvas = useCanvas();
|
|
2139
2907
|
const item = useCanvasState((s) => s.items[itemId]);
|
|
2140
|
-
const kind =
|
|
2141
|
-
const ref = (0, import_react4.useRef)(null);
|
|
2908
|
+
const kind = useItemKind(item?.kind ?? "");
|
|
2142
2909
|
const title = item ? itemTitle(item, kind) : "";
|
|
2143
2910
|
const drag = useTabDrag(itemId, title);
|
|
2144
|
-
(0, import_react4.useEffect)(() => {
|
|
2145
|
-
if (active) ref.current?.scrollIntoView?.({ block: "nearest", inline: "nearest" });
|
|
2146
|
-
}, [active]);
|
|
2147
2911
|
if (!item) return null;
|
|
2148
2912
|
const Icon = kind?.icon ?? AlertIcon;
|
|
2149
|
-
return /* @__PURE__ */ (0,
|
|
2913
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
2150
2914
|
CanvasTab,
|
|
2151
2915
|
{
|
|
2152
|
-
ref,
|
|
2153
2916
|
title,
|
|
2154
|
-
icon: /* @__PURE__ */ (0,
|
|
2917
|
+
icon: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Icon, {}),
|
|
2155
2918
|
active,
|
|
2156
2919
|
onSelect: () => canvas.activate(itemId),
|
|
2157
2920
|
onClose: () => canvas.close(itemId),
|
|
2158
2921
|
...drag,
|
|
2159
2922
|
onDragOver: (event) => {
|
|
2160
|
-
if (event.dataTransfer.types.includes(
|
|
2923
|
+
if (event.dataTransfer.types.includes(CANVAS_DRAG_MIME)) event.preventDefault();
|
|
2161
2924
|
},
|
|
2162
2925
|
onDrop: (event) => {
|
|
2163
|
-
const dragged = event.dataTransfer.getData(
|
|
2926
|
+
const dragged = event.dataTransfer.getData(CANVAS_DRAG_MIME);
|
|
2164
2927
|
if (!dragged) return;
|
|
2165
2928
|
event.preventDefault();
|
|
2166
2929
|
if (dragged !== itemId) canvas.moveItem(dragged, paneId, index);
|
|
@@ -2183,16 +2946,16 @@ function PaneMenu({
|
|
|
2183
2946
|
const canvas = useCanvas();
|
|
2184
2947
|
const { ports, layer } = useCanvasInternals();
|
|
2185
2948
|
const moveId = tabCount > 1 ? activeItem?.id : void 0;
|
|
2186
|
-
const blocking = (0,
|
|
2187
|
-
(0,
|
|
2949
|
+
const blocking = (0, import_react8.useRef)(null);
|
|
2950
|
+
(0, import_react8.useEffect)(() => () => blocking.current?.(), []);
|
|
2188
2951
|
const kindItems = kind?.menuItems && kindProps ? safeMenu(() => kind.menuItems?.(kindProps)) : [];
|
|
2189
2952
|
const hostItems = activeItem && ports.itemActions ? safeMenu(() => ports.itemActions?.(activeItem, kind)) : [];
|
|
2190
2953
|
const sections = [
|
|
2191
2954
|
[...kindItems, ...hostItems].map((entry) => ({ ...entry, id: `item:${entry.id}` })),
|
|
2192
2955
|
[
|
|
2193
|
-
{ id: "split-right", label: "Split right", icon: /* @__PURE__ */ (0,
|
|
2194
|
-
{ id: "split-down", label: "Split down", icon: /* @__PURE__ */ (0,
|
|
2195
|
-
...activeItem && role === "pop-out" ? [{ id: "dock-back", label: "Dock back", icon: /* @__PURE__ */ (0,
|
|
2956
|
+
{ id: "split-right", label: "Split right", icon: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(SplitRightIcon, {}), onSelect: () => canvas.splitPane(paneId, "horizontal", moveId) },
|
|
2957
|
+
{ id: "split-down", label: "Split down", icon: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(SplitDownIcon, {}), onSelect: () => canvas.splitPane(paneId, "vertical", moveId) },
|
|
2958
|
+
...activeItem && role === "pop-out" ? [{ id: "dock-back", label: "Dock back", icon: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(DockIcon, {}), onSelect: () => void canvas.dockBack(activeItem.id) }] : activeItem && ports.popOut ? [{ id: "pop-out", label: "Pop out", icon: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(PopOutIcon, {}), onSelect: () => void canvas.popOut(activeItem.id) }] : []
|
|
2196
2959
|
],
|
|
2197
2960
|
activeItem ? [
|
|
2198
2961
|
{ id: "close-tab", label: "Close tab", onSelect: () => canvas.close(activeItem.id) },
|
|
@@ -2210,7 +2973,7 @@ function PaneMenu({
|
|
|
2210
2973
|
handlers.set(action.id, action.onSelect);
|
|
2211
2974
|
}
|
|
2212
2975
|
});
|
|
2213
|
-
return /* @__PURE__ */ (0,
|
|
2976
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
2214
2977
|
CanvasMenuTrigger,
|
|
2215
2978
|
{
|
|
2216
2979
|
onClick: (event) => {
|
|
@@ -2225,7 +2988,7 @@ function PaneMenu({
|
|
|
2225
2988
|
}
|
|
2226
2989
|
);
|
|
2227
2990
|
}
|
|
2228
|
-
return /* @__PURE__ */ (0,
|
|
2991
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
2229
2992
|
Menu.Root,
|
|
2230
2993
|
{
|
|
2231
2994
|
onOpenChange: (open) => {
|
|
@@ -2233,16 +2996,16 @@ function PaneMenu({
|
|
|
2233
2996
|
blocking.current = open ? layer.beginBlocking() : null;
|
|
2234
2997
|
},
|
|
2235
2998
|
children: [
|
|
2236
|
-
/* @__PURE__ */ (0,
|
|
2237
|
-
/* @__PURE__ */ (0,
|
|
2999
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Menu.Trigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(CanvasMenuTrigger, {}) }),
|
|
3000
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Menu.Portal, { children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Menu.Content, { align: "end", sideOffset: 4, className: "mxc-menu", children: sections.map((section, i) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(MenuSection, { first: i === 0, actions: section }, i)) }) })
|
|
2238
3001
|
]
|
|
2239
3002
|
}
|
|
2240
3003
|
);
|
|
2241
3004
|
}
|
|
2242
3005
|
function MenuSection({ first, actions }) {
|
|
2243
|
-
return /* @__PURE__ */ (0,
|
|
2244
|
-
first ? null : /* @__PURE__ */ (0,
|
|
2245
|
-
actions.map((action) => /* @__PURE__ */ (0,
|
|
3006
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
|
|
3007
|
+
first ? null : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Menu.Separator, { className: "mxc-menu-separator" }),
|
|
3008
|
+
actions.map((action) => /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(Menu.Item, { onSelect: action.onSelect, className: "mxc-menu-item", "data-destructive": action.destructive ? "" : void 0, children: [
|
|
2246
3009
|
action.icon,
|
|
2247
3010
|
action.label
|
|
2248
3011
|
] }, action.id))
|
|
@@ -2256,150 +3019,16 @@ function safeMenu(build) {
|
|
|
2256
3019
|
return [];
|
|
2257
3020
|
}
|
|
2258
3021
|
}
|
|
2259
|
-
function ItemBody({
|
|
2260
|
-
item,
|
|
2261
|
-
paneId,
|
|
2262
|
-
windowId,
|
|
2263
|
-
isActive,
|
|
2264
|
-
isFocused,
|
|
2265
|
-
hasNativePort
|
|
2266
|
-
}) {
|
|
2267
|
-
const canvas = useCanvas();
|
|
2268
|
-
const kind = useCanvasKind(item.kind);
|
|
2269
|
-
if (!isActive && !kind?.keepAlive) return null;
|
|
2270
|
-
const props = { item, data: item.data, paneId, isFocused, canvas, windowId };
|
|
2271
|
-
let unavailable = null;
|
|
2272
|
-
try {
|
|
2273
|
-
unavailable = kind?.unavailable?.(item.data, item) ?? null;
|
|
2274
|
-
} catch (error) {
|
|
2275
|
-
console.error(`[@ai-matrx/canvas] "${item.kind}" unavailable() threw`, error);
|
|
2276
|
-
}
|
|
2277
|
-
let body;
|
|
2278
|
-
if (!kind) {
|
|
2279
|
-
body = /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ItemProblem, { message: "This item can no longer be shown.", onClose: () => canvas.close(item.id) });
|
|
2280
|
-
} else if (unavailable) {
|
|
2281
|
-
body = /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(CanvasPanelEmpty, { children: unavailable });
|
|
2282
|
-
} else if (kind.body === "native") {
|
|
2283
|
-
body = hasNativePort ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(NativeBody, { itemId: item.id, windowId, children: kindComponent(kind) ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Rendered, { kind, item, props }) : null }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(CanvasPanelEmpty, { children: "Opens in the desktop app" });
|
|
2284
|
-
} else {
|
|
2285
|
-
body = /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Rendered, { kind, item, props });
|
|
2286
|
-
}
|
|
2287
|
-
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "mxc-item", hidden: !isActive, "data-kind": item.kind, children: body });
|
|
2288
|
-
}
|
|
2289
|
-
function Rendered({ kind, item, props }) {
|
|
2290
|
-
const canvas = useCanvas();
|
|
2291
|
-
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ItemBoundary, { item, onClose: () => canvas.close(item.id), onRetry: () => resetKindComponent(kind), children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react4.Suspense, { fallback: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "mxc-loading", "aria-busy": "true" }), children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(KindRender, { kind, props }) }) });
|
|
2292
|
-
}
|
|
2293
|
-
function KindRender({ kind, props }) {
|
|
2294
|
-
const Render = kindComponent(kind);
|
|
2295
|
-
if (!Render) throw new Error(`[@ai-matrx/canvas] kind "${kind.id}" has nothing to render.`);
|
|
2296
|
-
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Render, { ...props });
|
|
2297
|
-
}
|
|
2298
|
-
function NativeBody({ itemId, windowId, children }) {
|
|
2299
|
-
const { layer } = useCanvasInternals();
|
|
2300
|
-
const ref = (0, import_react4.useRef)(null);
|
|
2301
|
-
const measure = (0, import_react4.useRef)(() => void 0);
|
|
2302
|
-
measure.current = () => {
|
|
2303
|
-
const el = ref.current;
|
|
2304
|
-
const box = el?.isConnected ? el.getBoundingClientRect() : null;
|
|
2305
|
-
if (!box || box.width <= 0 || box.height <= 0) {
|
|
2306
|
-
layer.native.report(itemId, null, { visible: false, windowId });
|
|
2307
|
-
return;
|
|
2308
|
-
}
|
|
2309
|
-
const rect = { ...toCanvasRect(box), devicePixelRatio: window.devicePixelRatio || 1 };
|
|
2310
|
-
layer.native.report(itemId, rect, { visible: !layer.isBlocked(), windowId });
|
|
2311
|
-
};
|
|
2312
|
-
useIsoLayoutEffect(() => measure.current());
|
|
2313
|
-
useIsoLayoutEffect(() => {
|
|
2314
|
-
const el = ref.current;
|
|
2315
|
-
const run = () => measure.current();
|
|
2316
|
-
const observer = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(run);
|
|
2317
|
-
if (el) observer?.observe(el);
|
|
2318
|
-
const unregister = layer.native.register(run);
|
|
2319
|
-
window.addEventListener("resize", run);
|
|
2320
|
-
return () => {
|
|
2321
|
-
observer?.disconnect();
|
|
2322
|
-
unregister();
|
|
2323
|
-
window.removeEventListener("resize", run);
|
|
2324
|
-
layer.native.report(itemId, null, { visible: false, windowId });
|
|
2325
|
-
};
|
|
2326
|
-
}, [itemId, windowId, layer]);
|
|
2327
|
-
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { ref, className: "mxc-native-body", "data-native-body": itemId, children });
|
|
2328
|
-
}
|
|
2329
|
-
function ItemProblem({ message, onClose, onRetry }) {
|
|
2330
|
-
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "mxc-problem", role: "alert", children: [
|
|
2331
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(AlertIcon, {}),
|
|
2332
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { children: message }),
|
|
2333
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "mxc-problem-actions", children: [
|
|
2334
|
-
onRetry ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", className: "mxc-text-button", onClick: onRetry, children: "Try again" }) : null,
|
|
2335
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", className: "mxc-text-button", onClick: onClose, children: "Close tab" })
|
|
2336
|
-
] })
|
|
2337
|
-
] });
|
|
2338
|
-
}
|
|
2339
|
-
var ItemBoundary = class extends import_react4.Component {
|
|
2340
|
-
state = { error: null };
|
|
2341
|
-
static getDerivedStateFromError(error) {
|
|
2342
|
-
return { error };
|
|
2343
|
-
}
|
|
2344
|
-
componentDidCatch(error, info) {
|
|
2345
|
-
console.error(`[@ai-matrx/canvas] "${this.props.item.kind}" failed to render`, error, info.componentStack);
|
|
2346
|
-
}
|
|
2347
|
-
render() {
|
|
2348
|
-
if (this.state.error) {
|
|
2349
|
-
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
2350
|
-
ItemProblem,
|
|
2351
|
-
{
|
|
2352
|
-
message: "This item failed to load.",
|
|
2353
|
-
onClose: this.props.onClose,
|
|
2354
|
-
onRetry: () => {
|
|
2355
|
-
this.props.onRetry();
|
|
2356
|
-
this.setState({ error: null });
|
|
2357
|
-
}
|
|
2358
|
-
}
|
|
2359
|
-
);
|
|
2360
|
-
}
|
|
2361
|
-
return this.props.children;
|
|
2362
|
-
}
|
|
2363
|
-
};
|
|
2364
|
-
function Launcher({ paneId }) {
|
|
2365
|
-
const canvas = useCanvas();
|
|
2366
|
-
const kinds = useCanvasKinds().filter((kind) => kind.launcher);
|
|
2367
|
-
if (kinds.length === 0) return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(CanvasPanelEmpty, { children: "Nothing open" });
|
|
2368
|
-
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "mxc-launcher", children: kinds.map((kind) => {
|
|
2369
|
-
const Icon = kind.icon;
|
|
2370
|
-
const launcher = kind.launcher;
|
|
2371
|
-
if (!launcher) return null;
|
|
2372
|
-
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
|
|
2373
|
-
"button",
|
|
2374
|
-
{
|
|
2375
|
-
type: "button",
|
|
2376
|
-
className: "mxc-launcher-item",
|
|
2377
|
-
onClick: () => canvas.open({
|
|
2378
|
-
kind: kind.id,
|
|
2379
|
-
key: launcher.key,
|
|
2380
|
-
data: launcher.data,
|
|
2381
|
-
title: launcher.title ?? null,
|
|
2382
|
-
target: { paneId }
|
|
2383
|
-
}),
|
|
2384
|
-
children: [
|
|
2385
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Icon, { className: "mxc-launcher-icon" }),
|
|
2386
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children: launcher.title ?? kind.label })
|
|
2387
|
-
]
|
|
2388
|
-
},
|
|
2389
|
-
kind.id
|
|
2390
|
-
);
|
|
2391
|
-
}) });
|
|
2392
|
-
}
|
|
2393
3022
|
|
|
2394
3023
|
// src/react/CanvasColumn.tsx
|
|
2395
|
-
var
|
|
2396
|
-
var
|
|
3024
|
+
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
3025
|
+
var useIsoLayoutEffect3 = typeof window === "undefined" ? import_react9.useEffect : import_react9.useLayoutEffect;
|
|
2397
3026
|
function subscribeViewport(listener) {
|
|
2398
3027
|
window.addEventListener("resize", listener);
|
|
2399
3028
|
return () => window.removeEventListener("resize", listener);
|
|
2400
3029
|
}
|
|
2401
3030
|
function useViewportWidth() {
|
|
2402
|
-
return (0,
|
|
3031
|
+
return (0, import_react9.useSyncExternalStore)(subscribeViewport, () => window.innerWidth, () => 0);
|
|
2403
3032
|
}
|
|
2404
3033
|
function useRegionWidth() {
|
|
2405
3034
|
const viewport = useViewportWidth();
|
|
@@ -2417,28 +3046,66 @@ function useCanvasColumnWidth() {
|
|
|
2417
3046
|
return fitCanvasWidth(width, region, controller.rules);
|
|
2418
3047
|
}
|
|
2419
3048
|
function CanvasColumn({ className, style, onLiveWidth, placement = "docked" }) {
|
|
2420
|
-
const { controller, windowId } = useCanvasInternals();
|
|
3049
|
+
const { controller, windowId, layer } = useCanvasInternals();
|
|
2421
3050
|
const isOpen = useCanvasState((s) => selectCanvasIsOpen(s, windowId));
|
|
2422
3051
|
const isFullscreen = useCanvasState((s) => selectCanvasIsFullscreen(s, windowId));
|
|
2423
3052
|
const width = useCanvasState((s) => selectCanvasWidth(s, windowId));
|
|
2424
3053
|
const layout = useCanvasState((s) => selectCanvasLayout(s, windowId));
|
|
2425
3054
|
const region = useRegionWidth();
|
|
2426
|
-
const [dragWidth, setDragWidth] = (0,
|
|
3055
|
+
const [dragWidth, setDragWidth] = (0, import_react9.useState)(null);
|
|
3056
|
+
const [slots] = (0, import_react9.useState)(createCanvasSlots);
|
|
3057
|
+
const ref = (0, import_react9.useRef)(null);
|
|
2427
3058
|
useRegisterCanvasPresence();
|
|
2428
|
-
|
|
3059
|
+
(0, import_react9.useEffect)(() => {
|
|
3060
|
+
const el = ref.current;
|
|
3061
|
+
if (!el) return void 0;
|
|
3062
|
+
let frame2 = 0;
|
|
3063
|
+
let running = false;
|
|
3064
|
+
const tick = () => {
|
|
3065
|
+
layer.native.remeasure();
|
|
3066
|
+
frame2 = running ? requestAnimationFrame(tick) : 0;
|
|
3067
|
+
};
|
|
3068
|
+
const start = (event) => {
|
|
3069
|
+
if (event.target !== el || running) return;
|
|
3070
|
+
running = true;
|
|
3071
|
+
frame2 = requestAnimationFrame(tick);
|
|
3072
|
+
};
|
|
3073
|
+
const stop = (event) => {
|
|
3074
|
+
if (event.target !== el) return;
|
|
3075
|
+
running = false;
|
|
3076
|
+
if (frame2) cancelAnimationFrame(frame2);
|
|
3077
|
+
frame2 = 0;
|
|
3078
|
+
layer.native.remeasure();
|
|
3079
|
+
};
|
|
3080
|
+
el.addEventListener("transitionrun", start);
|
|
3081
|
+
el.addEventListener("transitionend", stop);
|
|
3082
|
+
el.addEventListener("transitioncancel", stop);
|
|
3083
|
+
return () => {
|
|
3084
|
+
running = false;
|
|
3085
|
+
if (frame2) cancelAnimationFrame(frame2);
|
|
3086
|
+
el.removeEventListener("transitionrun", start);
|
|
3087
|
+
el.removeEventListener("transitionend", stop);
|
|
3088
|
+
el.removeEventListener("transitioncancel", stop);
|
|
3089
|
+
};
|
|
3090
|
+
}, [layer]);
|
|
3091
|
+
if (!layout) return null;
|
|
2429
3092
|
const fitted = fitCanvasWidth(width, region, controller.rules);
|
|
2430
3093
|
const shown = dragWidth ?? fitted;
|
|
2431
|
-
return /* @__PURE__ */ (0,
|
|
3094
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
2432
3095
|
"aside",
|
|
2433
3096
|
{
|
|
3097
|
+
ref,
|
|
2434
3098
|
className: ["mxc-column", className].filter(Boolean).join(" "),
|
|
2435
|
-
"data-
|
|
3099
|
+
"data-open": isOpen ? "" : void 0,
|
|
3100
|
+
"data-fullscreen": isOpen && isFullscreen ? "" : void 0,
|
|
2436
3101
|
"data-placement": placement,
|
|
2437
3102
|
"data-mxc-window": windowId,
|
|
2438
3103
|
"aria-label": "Canvas",
|
|
3104
|
+
"aria-hidden": isOpen ? void 0 : true,
|
|
3105
|
+
inert: !isOpen,
|
|
2439
3106
|
style: { ...style, ["--mxc-width"]: `${shown}px` },
|
|
2440
|
-
children: [
|
|
2441
|
-
placement === "docked" && !isFullscreen ? /* @__PURE__ */ (0,
|
|
3107
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "mxc-column-inner", children: [
|
|
3108
|
+
placement === "docked" && isOpen && !isFullscreen ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
2442
3109
|
WidthHandle,
|
|
2443
3110
|
{
|
|
2444
3111
|
width: fitted,
|
|
@@ -2449,14 +3116,17 @@ function CanvasColumn({ className, style, onLiveWidth, placement = "docked" }) {
|
|
|
2449
3116
|
}
|
|
2450
3117
|
}
|
|
2451
3118
|
) : null,
|
|
2452
|
-
/* @__PURE__ */ (0,
|
|
2453
|
-
|
|
3119
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(CanvasSlotsContext.Provider, { value: slots, children: [
|
|
3120
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "mxc-layout", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(LayoutNodeView, { node: layout }) }),
|
|
3121
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(CanvasBodyLayer, {})
|
|
3122
|
+
] })
|
|
3123
|
+
] })
|
|
2454
3124
|
}
|
|
2455
3125
|
);
|
|
2456
3126
|
}
|
|
2457
3127
|
function WidthHandle({ width, region, onDrag }) {
|
|
2458
3128
|
const canvas = useCanvas();
|
|
2459
|
-
const start = (0,
|
|
3129
|
+
const start = (0, import_react9.useRef)(null);
|
|
2460
3130
|
const clamp = (next) => clampDraggedCanvasWidth(next, region, canvas.rules);
|
|
2461
3131
|
const onPointerDown = (event) => {
|
|
2462
3132
|
event.preventDefault();
|
|
@@ -2476,7 +3146,7 @@ function WidthHandle({ width, region, onDrag }) {
|
|
|
2476
3146
|
onDrag(null);
|
|
2477
3147
|
canvas.setWidth(next);
|
|
2478
3148
|
};
|
|
2479
|
-
return /* @__PURE__ */ (0,
|
|
3149
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
2480
3150
|
"div",
|
|
2481
3151
|
{
|
|
2482
3152
|
className: "mxc-width-handle",
|
|
@@ -2498,12 +3168,12 @@ function WidthHandle({ width, region, onDrag }) {
|
|
|
2498
3168
|
);
|
|
2499
3169
|
}
|
|
2500
3170
|
function LayoutNodeView({ node }) {
|
|
2501
|
-
if (node.type === "pane") return /* @__PURE__ */ (0,
|
|
2502
|
-
return /* @__PURE__ */ (0,
|
|
3171
|
+
if (node.type === "pane") return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(CanvasPaneView, { paneId: node.paneId });
|
|
3172
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(SplitView, { node });
|
|
2503
3173
|
}
|
|
2504
3174
|
function useMeasuredWidth(ref, enabled) {
|
|
2505
|
-
const [width, setWidth] = (0,
|
|
2506
|
-
|
|
3175
|
+
const [width, setWidth] = (0, import_react9.useState)(0);
|
|
3176
|
+
useIsoLayoutEffect3(() => {
|
|
2507
3177
|
const el = ref.current;
|
|
2508
3178
|
if (!enabled || !el) return;
|
|
2509
3179
|
setWidth(el.getBoundingClientRect().width);
|
|
@@ -2516,8 +3186,8 @@ function useMeasuredWidth(ref, enabled) {
|
|
|
2516
3186
|
}
|
|
2517
3187
|
function SplitView({ node }) {
|
|
2518
3188
|
const canvas = useCanvas();
|
|
2519
|
-
const ref = (0,
|
|
2520
|
-
const [liveSizes, setLiveSizes] = (0,
|
|
3189
|
+
const ref = (0, import_react9.useRef)(null);
|
|
3190
|
+
const [liveSizes, setLiveSizes] = (0, import_react9.useState)(null);
|
|
2521
3191
|
const sizes = liveSizes ?? node.sizes;
|
|
2522
3192
|
const measuredWidth = useMeasuredWidth(ref, node.orientation === "horizontal" && canvas.rules.paneMinWidth > 0);
|
|
2523
3193
|
const stacked = node.orientation === "horizontal" && shouldStackSplit(measuredWidth, node.children.length, canvas.rules);
|
|
@@ -2557,11 +3227,11 @@ function SplitView({ node }) {
|
|
|
2557
3227
|
target.addEventListener("pointerup", up);
|
|
2558
3228
|
target.addEventListener("pointercancel", up);
|
|
2559
3229
|
};
|
|
2560
|
-
return /* @__PURE__ */ (0,
|
|
3230
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { ref, className: "mxc-split", "data-orientation": orientation, "data-stacked": stacked ? "" : void 0, children: node.children.map((child, i) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
2561
3231
|
SplitChild,
|
|
2562
3232
|
{
|
|
2563
3233
|
size: sizes[i] ?? 1 / node.children.length,
|
|
2564
|
-
handle: i < node.children.length - 1 ? /* @__PURE__ */ (0,
|
|
3234
|
+
handle: i < node.children.length - 1 ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
2565
3235
|
"div",
|
|
2566
3236
|
{
|
|
2567
3237
|
className: "mxc-split-handle",
|
|
@@ -2571,14 +3241,14 @@ function SplitView({ node }) {
|
|
|
2571
3241
|
onDoubleClick: () => canvas.resizeSplit(node.id, node.children.map(() => 1 / node.children.length))
|
|
2572
3242
|
}
|
|
2573
3243
|
) : null,
|
|
2574
|
-
children: /* @__PURE__ */ (0,
|
|
3244
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(LayoutNodeView, { node: child })
|
|
2575
3245
|
},
|
|
2576
3246
|
child.type === "pane" ? child.paneId : child.id
|
|
2577
3247
|
)) });
|
|
2578
3248
|
}
|
|
2579
3249
|
function SplitChild({ size, handle, children }) {
|
|
2580
|
-
return /* @__PURE__ */ (0,
|
|
2581
|
-
/* @__PURE__ */ (0,
|
|
3250
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
|
|
3251
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "mxc-split-child", style: { flexGrow: size, flexBasis: 0 }, children }),
|
|
2582
3252
|
handle
|
|
2583
3253
|
] });
|
|
2584
3254
|
}
|
|
@@ -2592,26 +3262,61 @@ function CanvasToggle({
|
|
|
2592
3262
|
const count = useCanvasState((s) => selectCanvasWindowItemCount(s, windowId));
|
|
2593
3263
|
const hint = canvasShortcutHint("canvas.toggle", isMac2());
|
|
2594
3264
|
const Button = variant === "glass" ? import_tap_target.TapTargetButton : import_tap_target.TapTargetButtonTransparent;
|
|
2595
|
-
return /* @__PURE__ */ (0,
|
|
2596
|
-
/* @__PURE__ */ (0,
|
|
3265
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { className: ["mxc-toggle", className].filter(Boolean).join(" "), "data-open": isOpen ? "" : void 0, children: [
|
|
3266
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
2597
3267
|
Button,
|
|
2598
3268
|
{
|
|
2599
3269
|
ariaLabel: isOpen ? "Hide canvas" : "Show canvas",
|
|
2600
3270
|
tooltip: `${isOpen ? "Hide canvas" : "Show canvas"}${hint ? ` (${hint})` : ""}`,
|
|
2601
|
-
icon: /* @__PURE__ */ (0,
|
|
3271
|
+
icon: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(PanelRightIcon, {}),
|
|
2602
3272
|
onClick: () => controller.toggle()
|
|
2603
3273
|
}
|
|
2604
3274
|
),
|
|
2605
|
-
count > 0 && !isOpen ? /* @__PURE__ */ (0,
|
|
3275
|
+
count > 0 && !isOpen ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "mxc-toggle-badge", children: count }) : null
|
|
2606
3276
|
] });
|
|
2607
3277
|
}
|
|
3278
|
+
function CanvasKindIcons({
|
|
3279
|
+
kinds,
|
|
3280
|
+
variant = "transparent",
|
|
3281
|
+
className,
|
|
3282
|
+
label = "Canvas tools"
|
|
3283
|
+
}) {
|
|
3284
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { role: "group", "aria-label": label, className: ["mxc-kind-icons", className].filter(Boolean).join(" "), children: kinds.map((entry) => {
|
|
3285
|
+
const icon = typeof entry === "string" ? { kind: entry } : entry;
|
|
3286
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(KindIconButton, { icon, variant }, `${icon.kind}::${icon.key ?? CANVAS_DEFAULT_KEY}`);
|
|
3287
|
+
}) });
|
|
3288
|
+
}
|
|
3289
|
+
function KindIconButton({ icon, variant }) {
|
|
3290
|
+
const kind = useCanvasKind(icon.kind);
|
|
3291
|
+
const toggle = useCanvasKindToggle({
|
|
3292
|
+
kind: icon.kind,
|
|
3293
|
+
key: icon.key,
|
|
3294
|
+
data: icon.data,
|
|
3295
|
+
title: icon.title,
|
|
3296
|
+
whenVisible: icon.whenVisible
|
|
3297
|
+
});
|
|
3298
|
+
if (!kind) return null;
|
|
3299
|
+
const Icon = kind.icon;
|
|
3300
|
+
const Button = variant === "glass" ? import_tap_target.TapTargetButton : import_tap_target.TapTargetButtonTransparent;
|
|
3301
|
+
const name = icon.label ?? kind.label;
|
|
3302
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
3303
|
+
Button,
|
|
3304
|
+
{
|
|
3305
|
+
ariaLabel: name,
|
|
3306
|
+
tooltip: name,
|
|
3307
|
+
icon: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Icon, {}),
|
|
3308
|
+
pressed: toggle.isVisible,
|
|
3309
|
+
onClick: () => void toggle.toggle()
|
|
3310
|
+
}
|
|
3311
|
+
);
|
|
3312
|
+
}
|
|
2608
3313
|
function CanvasFrame({ children, className }) {
|
|
2609
3314
|
const { windowId } = useCanvasInternals();
|
|
2610
3315
|
const isFullscreen = useCanvasState((s) => selectCanvasIsFullscreen(s, windowId));
|
|
2611
3316
|
const isOpen = useCanvasState((s) => selectCanvasIsOpen(s, windowId));
|
|
2612
|
-
return /* @__PURE__ */ (0,
|
|
2613
|
-
/* @__PURE__ */ (0,
|
|
2614
|
-
/* @__PURE__ */ (0,
|
|
3317
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: ["mxc-frame", className].filter(Boolean).join(" "), "data-canvas-open": isOpen ? "" : void 0, children: [
|
|
3318
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "mxc-frame-main", hidden: isOpen && isFullscreen, children }),
|
|
3319
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(CanvasColumn, {})
|
|
2615
3320
|
] });
|
|
2616
3321
|
}
|
|
2617
3322
|
//# sourceMappingURL=react.cjs.map
|