@ai-matrx/canvas 0.4.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 +27 -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 +958 -288
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +93 -3
- package/dist/react.d.ts +93 -3
- package/dist/react.js +966 -293
- package/dist/react.js.map +1 -1
- package/dist/styles.css +221 -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 ?? "");
|
|
@@ -1511,7 +1705,7 @@ function CanvasProvider({
|
|
|
1511
1705
|
}) {
|
|
1512
1706
|
const portsRef = (0, import_react2.useRef)(hostPorts);
|
|
1513
1707
|
portsRef.current = hostPorts;
|
|
1514
|
-
const [{ controller, layer }] = (0, import_react2.useState)(() => ({
|
|
1708
|
+
const [{ controller, layer, launcherEntries }] = (0, import_react2.useState)(() => ({
|
|
1515
1709
|
controller: createCanvasController({
|
|
1516
1710
|
store: store ?? createCanvasStore(),
|
|
1517
1711
|
persistence: persistence === void 0 ? createLocalStorageCanvasPersistence() : persistence,
|
|
@@ -1521,9 +1715,11 @@ function CanvasProvider({
|
|
|
1521
1715
|
windowId: windowIdProp ?? CANVAS_MAIN_WINDOW,
|
|
1522
1716
|
layoutRules,
|
|
1523
1717
|
windowPorts: () => portsRef.current,
|
|
1524
|
-
sessions: sessionHooksFor
|
|
1718
|
+
sessions: sessionHooksFor,
|
|
1719
|
+
preferredTarget: (kind) => getCanvasKind(kind)?.preferredTarget
|
|
1525
1720
|
}),
|
|
1526
|
-
layer: createCanvasLayer(() => portsRef.current)
|
|
1721
|
+
layer: createCanvasLayer(() => portsRef.current),
|
|
1722
|
+
launcherEntries: createLauncherEntryStore()
|
|
1527
1723
|
}));
|
|
1528
1724
|
const windowId = controller.windowId;
|
|
1529
1725
|
(0, import_react2.useEffect)(() => controller.start(), [controller]);
|
|
@@ -1548,7 +1744,7 @@ function CanvasProvider({
|
|
|
1548
1744
|
window.addEventListener("keydown", onKey);
|
|
1549
1745
|
return () => window.removeEventListener("keydown", onKey);
|
|
1550
1746
|
}, [controller, hotkeys, windowId]);
|
|
1551
|
-
const value = { controller, windowId, ports: hostPorts, layer, regionWidth, tabStrip };
|
|
1747
|
+
const value = { controller, windowId, ports: hostPorts, layer, regionWidth, tabStrip, launcherEntries };
|
|
1552
1748
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(CanvasContext.Provider, { value, children: [
|
|
1553
1749
|
children,
|
|
1554
1750
|
hostPorts.overlay ? null : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(CanvasDomOverlay, { layer })
|
|
@@ -1635,14 +1831,38 @@ function useCanvasKind(id) {
|
|
|
1635
1831
|
(0, import_react2.useSyncExternalStore)(subscribeCanvasKinds, getCanvasKindsVersion, getCanvasKindsVersion);
|
|
1636
1832
|
return getCanvasKind(id);
|
|
1637
1833
|
}
|
|
1834
|
+
var NO_ENTRIES = Object.freeze([]);
|
|
1835
|
+
var noEntries = () => NO_ENTRIES;
|
|
1836
|
+
function useCanvasLauncherEntry(entry) {
|
|
1837
|
+
const store = (0, import_react2.useContext)(CanvasContext)?.launcherEntries;
|
|
1838
|
+
const fingerprint = JSON.stringify([entry.kind, entry.key, entry.title ?? null, entry.data ?? null]);
|
|
1839
|
+
const icon = entry.icon;
|
|
1840
|
+
(0, import_react2.useEffect)(() => {
|
|
1841
|
+
if (!store) return void 0;
|
|
1842
|
+
const [kind, key, title, data] = JSON.parse(fingerprint);
|
|
1843
|
+
return store.add({ kind, key, title, data, ...icon ? { icon } : {} });
|
|
1844
|
+
}, [store, fingerprint, icon]);
|
|
1845
|
+
}
|
|
1846
|
+
function useCanvasLauncherEntries() {
|
|
1847
|
+
const store = (0, import_react2.useContext)(CanvasContext)?.launcherEntries;
|
|
1848
|
+
const all = (0, import_react2.useSyncExternalStore)(store ? store.subscribe : NO_SUBSCRIPTION, store ? store.list : noEntries, store ? store.list : noEntries);
|
|
1849
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1850
|
+
const out = [];
|
|
1851
|
+
for (let i = all.length - 1; i >= 0; i -= 1) {
|
|
1852
|
+
const entry = all[i];
|
|
1853
|
+
if (!entry || seen.has(`${entry.kind}::${entry.key}`)) continue;
|
|
1854
|
+
seen.add(`${entry.kind}::${entry.key}`);
|
|
1855
|
+
out.unshift(entry);
|
|
1856
|
+
}
|
|
1857
|
+
return out;
|
|
1858
|
+
}
|
|
1638
1859
|
|
|
1639
1860
|
// src/react/CanvasColumn.tsx
|
|
1640
|
-
var
|
|
1861
|
+
var import_react9 = require("react");
|
|
1641
1862
|
var import_tap_target = require("@ai-matrx/tap-target");
|
|
1642
1863
|
|
|
1643
|
-
// src/react/
|
|
1644
|
-
var
|
|
1645
|
-
var Menu = __toESM(require("@radix-ui/react-dropdown-menu"), 1);
|
|
1864
|
+
// src/react/bodies.tsx
|
|
1865
|
+
var import_react7 = require("react");
|
|
1646
1866
|
|
|
1647
1867
|
// src/react/chrome.tsx
|
|
1648
1868
|
var import_react3 = require("react");
|
|
@@ -1737,6 +1957,12 @@ var DockIcon = (p) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Glyph, { ...
|
|
|
1737
1957
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M21 3 12 12" }),
|
|
1738
1958
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M12 6v6h6" })
|
|
1739
1959
|
] });
|
|
1960
|
+
var LayoutGridIcon = (p) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Glyph, { ...p, children: [
|
|
1961
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("rect", { width: "7", height: "7", x: "3", y: "3", rx: "1" }),
|
|
1962
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("rect", { width: "7", height: "7", x: "14", y: "3", rx: "1" }),
|
|
1963
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("rect", { width: "7", height: "7", x: "14", y: "14", rx: "1" }),
|
|
1964
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("rect", { width: "7", height: "7", x: "3", y: "14", rx: "1" })
|
|
1965
|
+
] });
|
|
1740
1966
|
|
|
1741
1967
|
// src/react/chrome.tsx
|
|
1742
1968
|
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
@@ -1987,22 +2213,523 @@ function BrowserBar({ url, canGoBack, canGoForward, onBack, onForward, onReload,
|
|
|
1987
2213
|
] });
|
|
1988
2214
|
}
|
|
1989
2215
|
|
|
1990
|
-
// src/react/
|
|
2216
|
+
// src/react/dnd.ts
|
|
2217
|
+
var import_react4 = require("react");
|
|
2218
|
+
var CANVAS_DRAG_MIME = "application/x-matrx-canvas-item";
|
|
2219
|
+
function useTabDrag(itemId, title) {
|
|
2220
|
+
const { layer } = useCanvasInternals();
|
|
2221
|
+
const active = (0, import_react4.useRef)([]);
|
|
2222
|
+
const end = () => {
|
|
2223
|
+
for (const dispose of active.current.splice(0)) dispose();
|
|
2224
|
+
};
|
|
2225
|
+
(0, import_react4.useEffect)(() => end, []);
|
|
2226
|
+
return {
|
|
2227
|
+
draggable: true,
|
|
2228
|
+
onDragStart: (event) => {
|
|
2229
|
+
event.dataTransfer.setData(CANVAS_DRAG_MIME, itemId);
|
|
2230
|
+
event.dataTransfer.effectAllowed = "move";
|
|
2231
|
+
end();
|
|
2232
|
+
active.current.push(layer.beginBlocking(), layer.show({ type: "tab-drag", itemId, title }));
|
|
2233
|
+
},
|
|
2234
|
+
onDragEnd: end
|
|
2235
|
+
};
|
|
2236
|
+
}
|
|
2237
|
+
function usePaneDrop(paneId, getOutline) {
|
|
2238
|
+
const canvas = useCanvas();
|
|
2239
|
+
const { layer } = useCanvasInternals();
|
|
2240
|
+
const drop = (0, import_react4.useRef)(null);
|
|
2241
|
+
(0, import_react4.useEffect)(() => () => drop.current?.(), []);
|
|
2242
|
+
const hide = () => {
|
|
2243
|
+
drop.current?.();
|
|
2244
|
+
drop.current = null;
|
|
2245
|
+
};
|
|
2246
|
+
return {
|
|
2247
|
+
onDragOver: (event) => {
|
|
2248
|
+
if (!event.dataTransfer.types.includes(CANVAS_DRAG_MIME)) return;
|
|
2249
|
+
event.preventDefault();
|
|
2250
|
+
event.dataTransfer.dropEffect = "move";
|
|
2251
|
+
const outline = getOutline();
|
|
2252
|
+
if (!drop.current && outline) {
|
|
2253
|
+
drop.current = layer.show({ type: "drop-indicator", rect: toCanvasRect(outline.getBoundingClientRect()), paneId });
|
|
2254
|
+
}
|
|
2255
|
+
},
|
|
2256
|
+
onDragLeave: (event) => {
|
|
2257
|
+
const next = event.relatedTarget;
|
|
2258
|
+
if (next && event.currentTarget.contains(next)) return;
|
|
2259
|
+
hide();
|
|
2260
|
+
},
|
|
2261
|
+
onDrop: (event) => {
|
|
2262
|
+
hide();
|
|
2263
|
+
const itemId = event.dataTransfer.getData(CANVAS_DRAG_MIME);
|
|
2264
|
+
if (!itemId) return;
|
|
2265
|
+
event.preventDefault();
|
|
2266
|
+
canvas.moveItem(itemId, paneId);
|
|
2267
|
+
}
|
|
2268
|
+
};
|
|
2269
|
+
}
|
|
2270
|
+
|
|
2271
|
+
// src/react/launcher.tsx
|
|
1991
2272
|
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
1992
|
-
|
|
1993
|
-
|
|
2273
|
+
function useChoices() {
|
|
2274
|
+
const entries = useCanvasLauncherEntries();
|
|
2275
|
+
const kinds = useCanvasKinds();
|
|
2276
|
+
const byId = new Map(kinds.map((kind) => [kind.id, kind]));
|
|
2277
|
+
const contextual = [];
|
|
2278
|
+
for (const entry of entries) {
|
|
2279
|
+
const kind = byId.get(entry.kind);
|
|
2280
|
+
if (!kind) continue;
|
|
2281
|
+
contextual.push({
|
|
2282
|
+
id: `ctx:${canvasItemId(entry.kind, entry.key)}`,
|
|
2283
|
+
label: entry.title ?? kind.label,
|
|
2284
|
+
icon: entry.icon ?? kind.icon,
|
|
2285
|
+
input: { kind: entry.kind, key: entry.key, title: entry.title ?? null, ...entry.data !== void 0 ? { data: entry.data } : {} }
|
|
2286
|
+
});
|
|
2287
|
+
}
|
|
2288
|
+
const global = [];
|
|
2289
|
+
for (const kind of kinds) {
|
|
2290
|
+
const launcher = kind.launcher;
|
|
2291
|
+
if (!launcher) continue;
|
|
2292
|
+
global.push({
|
|
2293
|
+
id: `kind:${kind.id}`,
|
|
2294
|
+
label: launcher.title ?? kind.label,
|
|
2295
|
+
icon: kind.icon,
|
|
2296
|
+
input: { kind: kind.id, key: launcher.key, data: launcher.data, title: launcher.title ?? null }
|
|
2297
|
+
});
|
|
2298
|
+
}
|
|
2299
|
+
return { contextual, global };
|
|
2300
|
+
}
|
|
2301
|
+
function LauncherGrid({ onChoose }) {
|
|
2302
|
+
const { contextual, global } = useChoices();
|
|
2303
|
+
if (contextual.length === 0 && global.length === 0) return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(CanvasPanelEmpty, { children: "Nothing to open" });
|
|
2304
|
+
const section = (label, choices) => choices.length === 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("section", { className: "mxc-launcher-section", "aria-label": label ?? "Open", children: [
|
|
2305
|
+
label ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("h3", { className: "mxc-launcher-heading", children: label }) : null,
|
|
2306
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "mxc-launcher-grid", children: choices.map((choice) => {
|
|
2307
|
+
const Icon = choice.icon;
|
|
2308
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("button", { type: "button", className: "mxc-launcher-item", onClick: () => onChoose(choice.input), children: [
|
|
2309
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Icon, { className: "mxc-launcher-icon" }),
|
|
2310
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children: choice.label })
|
|
2311
|
+
] }, choice.id);
|
|
2312
|
+
}) })
|
|
2313
|
+
] });
|
|
2314
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "mxc-launcher", children: [
|
|
2315
|
+
section(contextual.length > 0 ? "On this page" : null, contextual),
|
|
2316
|
+
section(contextual.length > 0 ? "Open" : null, global)
|
|
2317
|
+
] });
|
|
2318
|
+
}
|
|
2319
|
+
function LauncherBody({ item, canvas }) {
|
|
2320
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(LauncherGrid, { onChoose: (input) => void canvas.navigate(item.id, input) });
|
|
2321
|
+
}
|
|
2322
|
+
function EmptyPaneLauncher({ paneId }) {
|
|
2323
|
+
const canvas = useCanvas();
|
|
2324
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
2325
|
+
LauncherGrid,
|
|
2326
|
+
{
|
|
2327
|
+
onChoose: (input) => {
|
|
2328
|
+
const tab = canvas.newTab(paneId);
|
|
2329
|
+
canvas.navigate(tab, input);
|
|
2330
|
+
}
|
|
2331
|
+
}
|
|
2332
|
+
);
|
|
2333
|
+
}
|
|
2334
|
+
var CANVAS_LAUNCHER_KIND_DEFINITION = {
|
|
2335
|
+
id: CANVAS_LAUNCHER_KIND,
|
|
2336
|
+
label: "New tab",
|
|
2337
|
+
icon: LayoutGridIcon,
|
|
2338
|
+
component: LauncherBody
|
|
2339
|
+
};
|
|
2340
|
+
function useItemKind(kindId) {
|
|
2341
|
+
const registered = useCanvasKind(kindId);
|
|
2342
|
+
return kindId === CANVAS_LAUNCHER_KIND ? CANVAS_LAUNCHER_KIND_DEFINITION : registered;
|
|
2343
|
+
}
|
|
2344
|
+
|
|
2345
|
+
// src/react/presentation.tsx
|
|
2346
|
+
var import_react5 = require("react");
|
|
2347
|
+
var CanvasPresentationContext = (0, import_react5.createContext)(null);
|
|
2348
|
+
function useCanvasPresentation() {
|
|
2349
|
+
return (0, import_react5.useContext)(CanvasPresentationContext);
|
|
2350
|
+
}
|
|
2351
|
+
|
|
2352
|
+
// src/react/slots.ts
|
|
2353
|
+
var import_react6 = require("react");
|
|
2354
|
+
var ZERO = Object.freeze({ width: 0, height: 0 });
|
|
2355
|
+
function createCanvasSlots() {
|
|
2356
|
+
const slots = /* @__PURE__ */ new Map();
|
|
2357
|
+
const sections = /* @__PURE__ */ new Map();
|
|
2358
|
+
const sizes = /* @__PURE__ */ new Map();
|
|
2359
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
2360
|
+
let observer = null;
|
|
2361
|
+
const emit = () => {
|
|
2362
|
+
for (const listener of [...listeners]) listener();
|
|
2363
|
+
};
|
|
2364
|
+
const read = (paneId, element) => {
|
|
2365
|
+
const box = element.getBoundingClientRect();
|
|
2366
|
+
const width = Math.round(box.width);
|
|
2367
|
+
const height = Math.round(box.height);
|
|
2368
|
+
const previous = sizes.get(paneId);
|
|
2369
|
+
if (previous && previous.width === width && previous.height === height) return false;
|
|
2370
|
+
sizes.set(paneId, { width, height });
|
|
2371
|
+
return true;
|
|
2372
|
+
};
|
|
2373
|
+
const measureAll = () => {
|
|
2374
|
+
let changed = false;
|
|
2375
|
+
for (const [paneId, element] of slots) changed = read(paneId, element) || changed;
|
|
2376
|
+
return changed;
|
|
2377
|
+
};
|
|
2378
|
+
const watch = () => {
|
|
2379
|
+
if (observer || typeof ResizeObserver === "undefined") return observer;
|
|
2380
|
+
observer = new ResizeObserver(() => {
|
|
2381
|
+
measureAll();
|
|
2382
|
+
emit();
|
|
2383
|
+
});
|
|
2384
|
+
return observer;
|
|
2385
|
+
};
|
|
2386
|
+
return {
|
|
2387
|
+
set(paneId, element) {
|
|
2388
|
+
if (slots.get(paneId) === element) return;
|
|
2389
|
+
const previous = slots.get(paneId);
|
|
2390
|
+
if (previous) observer?.unobserve(previous);
|
|
2391
|
+
slots.set(paneId, element);
|
|
2392
|
+
watch()?.observe(element);
|
|
2393
|
+
read(paneId, element);
|
|
2394
|
+
emit();
|
|
2395
|
+
},
|
|
2396
|
+
unset(paneId, element) {
|
|
2397
|
+
const current = slots.get(paneId);
|
|
2398
|
+
if (!current || element && current !== element) return;
|
|
2399
|
+
observer?.unobserve(current);
|
|
2400
|
+
slots.delete(paneId);
|
|
2401
|
+
sizes.delete(paneId);
|
|
2402
|
+
emit();
|
|
2403
|
+
},
|
|
2404
|
+
setSection(paneId, element) {
|
|
2405
|
+
if (element) sections.set(paneId, element);
|
|
2406
|
+
else sections.delete(paneId);
|
|
2407
|
+
},
|
|
2408
|
+
slot: (paneId) => slots.get(paneId) ?? null,
|
|
2409
|
+
section: (paneId) => sections.get(paneId) ?? null,
|
|
2410
|
+
size: (paneId) => sizes.get(paneId) ?? ZERO,
|
|
2411
|
+
measure() {
|
|
2412
|
+
if (measureAll()) emit();
|
|
2413
|
+
},
|
|
2414
|
+
subscribe(listener) {
|
|
2415
|
+
listeners.add(listener);
|
|
2416
|
+
return () => listeners.delete(listener);
|
|
2417
|
+
}
|
|
2418
|
+
};
|
|
2419
|
+
}
|
|
2420
|
+
var CanvasSlotsContext = (0, import_react6.createContext)(null);
|
|
2421
|
+
function useCanvasSlots() {
|
|
2422
|
+
return (0, import_react6.useContext)(CanvasSlotsContext);
|
|
2423
|
+
}
|
|
2424
|
+
function useSlotSize(slots, paneId) {
|
|
2425
|
+
return (0, import_react6.useSyncExternalStore)(
|
|
2426
|
+
slots.subscribe,
|
|
2427
|
+
() => slots.size(paneId),
|
|
2428
|
+
() => ZERO
|
|
2429
|
+
);
|
|
2430
|
+
}
|
|
2431
|
+
|
|
2432
|
+
// src/react/bodies.tsx
|
|
2433
|
+
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
2434
|
+
var useIsoLayoutEffect = typeof window === "undefined" ? import_react7.useEffect : import_react7.useLayoutEffect;
|
|
1994
2435
|
var LAZY = /* @__PURE__ */ Symbol.for("ai-matrx.canvas.lazy");
|
|
1995
2436
|
function kindComponent(kind) {
|
|
1996
2437
|
if (kind.component) return kind.component;
|
|
1997
2438
|
const holder = kind;
|
|
1998
|
-
if (!holder[LAZY] && kind.load) holder[LAZY] = (0,
|
|
2439
|
+
if (!holder[LAZY] && kind.load) holder[LAZY] = (0, import_react7.lazy)(kind.load);
|
|
1999
2440
|
return holder[LAZY] ?? null;
|
|
2000
2441
|
}
|
|
2001
2442
|
function resetKindComponent(kind) {
|
|
2002
2443
|
if (kind) delete kind[LAZY];
|
|
2003
2444
|
}
|
|
2445
|
+
function usePanePresentation(slots, paneId, isFullscreen, paneCount) {
|
|
2446
|
+
const size = useSlotSize(slots, paneId);
|
|
2447
|
+
return (0, import_react7.useMemo)(
|
|
2448
|
+
() => canvasPresentation({ width: size.width, height: size.height, isFullscreen, paneCount }),
|
|
2449
|
+
[size.width, size.height, isFullscreen, paneCount]
|
|
2450
|
+
);
|
|
2451
|
+
}
|
|
2452
|
+
function placedItems(state, windowId, onlyPane) {
|
|
2453
|
+
const frame2 = canvasWindowFrame(state, windowId);
|
|
2454
|
+
if (!frame2) return [];
|
|
2455
|
+
const out = [];
|
|
2456
|
+
for (const paneId of onlyPane ? [onlyPane] : listPaneIds(frame2.layout)) {
|
|
2457
|
+
const pane = state.panes[paneId];
|
|
2458
|
+
for (const itemId of pane?.itemIds ?? []) {
|
|
2459
|
+
const item = state.items[itemId];
|
|
2460
|
+
if (item) out.push({ item, paneId, active: pane?.activeItemId === itemId });
|
|
2461
|
+
}
|
|
2462
|
+
}
|
|
2463
|
+
return out.sort((a, b) => a.item.openedAt - b.item.openedAt || hostKey(a.item).localeCompare(hostKey(b.item)));
|
|
2464
|
+
}
|
|
2465
|
+
var hostKey = (item) => item.instance ?? item.id;
|
|
2466
|
+
function createPlacement(getRoot, slots, afterPlace) {
|
|
2467
|
+
const hosts = /* @__PURE__ */ new Map();
|
|
2468
|
+
let placing = false;
|
|
2469
|
+
const place = () => {
|
|
2470
|
+
const root = getRoot();
|
|
2471
|
+
if (!root || placing) return;
|
|
2472
|
+
placing = true;
|
|
2473
|
+
try {
|
|
2474
|
+
slots.measure();
|
|
2475
|
+
const base = root.getBoundingClientRect();
|
|
2476
|
+
for (const { element, paneId } of hosts.values()) {
|
|
2477
|
+
const slot = slots.slot(paneId);
|
|
2478
|
+
if (!slot) {
|
|
2479
|
+
element.dataset.placed = "false";
|
|
2480
|
+
continue;
|
|
2481
|
+
}
|
|
2482
|
+
const box = slot.getBoundingClientRect();
|
|
2483
|
+
const style = element.style;
|
|
2484
|
+
style.left = `${box.left - base.left}px`;
|
|
2485
|
+
style.top = `${box.top - base.top}px`;
|
|
2486
|
+
style.width = `${box.width}px`;
|
|
2487
|
+
style.height = `${box.height}px`;
|
|
2488
|
+
element.dataset.placed = "true";
|
|
2489
|
+
}
|
|
2490
|
+
} finally {
|
|
2491
|
+
placing = false;
|
|
2492
|
+
}
|
|
2493
|
+
afterPlace();
|
|
2494
|
+
};
|
|
2495
|
+
return {
|
|
2496
|
+
register(key, entry) {
|
|
2497
|
+
hosts.set(key, entry);
|
|
2498
|
+
return () => {
|
|
2499
|
+
if (hosts.get(key) === entry) hosts.delete(key);
|
|
2500
|
+
};
|
|
2501
|
+
},
|
|
2502
|
+
place
|
|
2503
|
+
};
|
|
2504
|
+
}
|
|
2505
|
+
function CanvasBodyLayer() {
|
|
2506
|
+
const { windowId, layer } = useCanvasInternals();
|
|
2507
|
+
const slots = useCanvasSlots();
|
|
2508
|
+
const state = useCanvasState((s) => s);
|
|
2509
|
+
const root = (0, import_react7.useRef)(null);
|
|
2510
|
+
const [placement] = (0, import_react7.useState)(
|
|
2511
|
+
() => slots ? createPlacement(() => root.current, slots, () => layer.native.remeasure()) : null
|
|
2512
|
+
);
|
|
2513
|
+
useIsoLayoutEffect(() => placement?.place());
|
|
2514
|
+
useIsoLayoutEffect(() => slots && placement ? slots.subscribe(placement.place) : void 0, [slots, placement]);
|
|
2515
|
+
if (!slots || !placement) return null;
|
|
2516
|
+
const frame2 = canvasWindowFrame(state, windowId);
|
|
2517
|
+
if (!frame2) return null;
|
|
2518
|
+
const paneCount = listPaneIds(frame2.layout).length;
|
|
2519
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { ref: root, className: "mxc-bodies", "data-mxc-bodies": windowId, children: placedItems(state, windowId).map(({ item, paneId, active }) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
2520
|
+
BodyHost,
|
|
2521
|
+
{
|
|
2522
|
+
hostId: hostKey(item),
|
|
2523
|
+
item,
|
|
2524
|
+
paneId,
|
|
2525
|
+
visible: frame2.isOpen && active,
|
|
2526
|
+
focused: active && frame2.focusedPaneId === paneId,
|
|
2527
|
+
isFullscreen: frame2.isFullscreen,
|
|
2528
|
+
paneCount,
|
|
2529
|
+
slots,
|
|
2530
|
+
placement
|
|
2531
|
+
},
|
|
2532
|
+
hostKey(item)
|
|
2533
|
+
)) });
|
|
2534
|
+
}
|
|
2535
|
+
function CanvasPaneInlineBodies({ paneId, slots }) {
|
|
2536
|
+
const { windowId } = useCanvasInternals();
|
|
2537
|
+
const state = useCanvasState((s) => s);
|
|
2538
|
+
const frame2 = canvasWindowFrame(state, windowId);
|
|
2539
|
+
const paneCount = frame2 ? listPaneIds(frame2.layout).length : 1;
|
|
2540
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_jsx_runtime6.Fragment, { children: placedItems(state, windowId, paneId).map(({ item, active }) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
2541
|
+
BodyHost,
|
|
2542
|
+
{
|
|
2543
|
+
hostId: hostKey(item),
|
|
2544
|
+
item,
|
|
2545
|
+
paneId,
|
|
2546
|
+
visible: active,
|
|
2547
|
+
focused: active && frame2?.focusedPaneId === paneId,
|
|
2548
|
+
isFullscreen: frame2?.isFullscreen ?? false,
|
|
2549
|
+
paneCount,
|
|
2550
|
+
slots,
|
|
2551
|
+
placement: null
|
|
2552
|
+
},
|
|
2553
|
+
hostKey(item)
|
|
2554
|
+
)) });
|
|
2555
|
+
}
|
|
2556
|
+
function BodyHost({
|
|
2557
|
+
hostId,
|
|
2558
|
+
item,
|
|
2559
|
+
paneId,
|
|
2560
|
+
visible,
|
|
2561
|
+
focused,
|
|
2562
|
+
isFullscreen,
|
|
2563
|
+
paneCount,
|
|
2564
|
+
slots,
|
|
2565
|
+
placement
|
|
2566
|
+
}) {
|
|
2567
|
+
const canvas = useCanvas();
|
|
2568
|
+
const { windowId, ports } = useCanvasInternals();
|
|
2569
|
+
const kind = useItemKind(item.kind);
|
|
2570
|
+
const element = (0, import_react7.useRef)(null);
|
|
2571
|
+
const [everShown, setEverShown] = (0, import_react7.useState)(visible);
|
|
2572
|
+
if (visible && !everShown) setEverShown(true);
|
|
2573
|
+
const mounted = kind?.unmountWhenHidden ? visible : everShown;
|
|
2574
|
+
const presentation = usePanePresentation(slots, paneId, isFullscreen, paneCount);
|
|
2575
|
+
const drop = usePaneDrop(paneId, () => slots.section(paneId));
|
|
2576
|
+
useIsoLayoutEffect(() => {
|
|
2577
|
+
const el = element.current;
|
|
2578
|
+
if (!placement || !el) return void 0;
|
|
2579
|
+
return placement.register(hostId, { element: el, paneId });
|
|
2580
|
+
}, [placement, hostId, paneId]);
|
|
2581
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
2582
|
+
"div",
|
|
2583
|
+
{
|
|
2584
|
+
ref: element,
|
|
2585
|
+
className: "mxc-item-host",
|
|
2586
|
+
"data-inline": placement ? void 0 : "",
|
|
2587
|
+
"data-placed": placement ? "false" : void 0,
|
|
2588
|
+
"data-visible": visible ? "true" : "false",
|
|
2589
|
+
"data-kind": item.kind,
|
|
2590
|
+
"data-item": item.id,
|
|
2591
|
+
"data-pane": paneId,
|
|
2592
|
+
inert: !visible,
|
|
2593
|
+
"aria-hidden": visible ? void 0 : true,
|
|
2594
|
+
onPointerDownCapture: () => {
|
|
2595
|
+
if (!focused) canvas.focusPane(paneId);
|
|
2596
|
+
},
|
|
2597
|
+
...drop,
|
|
2598
|
+
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)(
|
|
2599
|
+
ItemContent,
|
|
2600
|
+
{
|
|
2601
|
+
item,
|
|
2602
|
+
kind,
|
|
2603
|
+
paneId,
|
|
2604
|
+
windowId,
|
|
2605
|
+
focused,
|
|
2606
|
+
visible,
|
|
2607
|
+
presentation,
|
|
2608
|
+
canvas,
|
|
2609
|
+
hasNativePort: !!ports.onNativeBodyFrame
|
|
2610
|
+
}
|
|
2611
|
+
) }) }) : null
|
|
2612
|
+
}
|
|
2613
|
+
);
|
|
2614
|
+
}
|
|
2615
|
+
function ItemContent({
|
|
2616
|
+
item,
|
|
2617
|
+
kind,
|
|
2618
|
+
paneId,
|
|
2619
|
+
windowId,
|
|
2620
|
+
focused,
|
|
2621
|
+
visible,
|
|
2622
|
+
presentation,
|
|
2623
|
+
canvas,
|
|
2624
|
+
hasNativePort
|
|
2625
|
+
}) {
|
|
2626
|
+
let unavailable = null;
|
|
2627
|
+
try {
|
|
2628
|
+
unavailable = kind?.unavailable?.(item.data, item) ?? null;
|
|
2629
|
+
} catch (error) {
|
|
2630
|
+
console.error(`[@ai-matrx/canvas] "${item.kind}" unavailable() threw`, error);
|
|
2631
|
+
}
|
|
2632
|
+
if (!kind) return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ItemProblem, { message: "This item can no longer be shown.", onClose: () => canvas.close(item.id) });
|
|
2633
|
+
if (unavailable) return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(CanvasPanelEmpty, { children: unavailable });
|
|
2634
|
+
const rendered = kindComponent(kind) ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Rendered, { kind, item, paneId, isFocused: focused, isVisible: visible, canvas, windowId, presentation }) : null;
|
|
2635
|
+
if (kind.body === "native") {
|
|
2636
|
+
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" });
|
|
2637
|
+
}
|
|
2638
|
+
return rendered;
|
|
2639
|
+
}
|
|
2640
|
+
var Rendered = (0, import_react7.memo)(function Rendered2({
|
|
2641
|
+
kind,
|
|
2642
|
+
item,
|
|
2643
|
+
paneId,
|
|
2644
|
+
isFocused,
|
|
2645
|
+
isVisible,
|
|
2646
|
+
canvas,
|
|
2647
|
+
windowId,
|
|
2648
|
+
presentation
|
|
2649
|
+
}) {
|
|
2650
|
+
const props = { item, data: item.data, paneId, isFocused, isVisible, canvas, windowId, presentation };
|
|
2651
|
+
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 }) }) });
|
|
2652
|
+
});
|
|
2653
|
+
function KindRender({ kind, props }) {
|
|
2654
|
+
const Render = kindComponent(kind);
|
|
2655
|
+
if (!Render) throw new Error(`[@ai-matrx/canvas] kind "${kind.id}" has nothing to render.`);
|
|
2656
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Render, { ...props });
|
|
2657
|
+
}
|
|
2658
|
+
function NativeBody({ itemId, windowId, visible, children }) {
|
|
2659
|
+
const { layer } = useCanvasInternals();
|
|
2660
|
+
const ref = (0, import_react7.useRef)(null);
|
|
2661
|
+
const measure = (0, import_react7.useRef)(() => void 0);
|
|
2662
|
+
measure.current = () => {
|
|
2663
|
+
const el = ref.current;
|
|
2664
|
+
const box = visible && el?.isConnected ? el.getBoundingClientRect() : null;
|
|
2665
|
+
if (!box || box.width <= 0 || box.height <= 0) {
|
|
2666
|
+
layer.native.report(itemId, null, { visible: false, windowId });
|
|
2667
|
+
return;
|
|
2668
|
+
}
|
|
2669
|
+
const rect = { ...toCanvasRect(box), devicePixelRatio: window.devicePixelRatio || 1 };
|
|
2670
|
+
layer.native.report(itemId, rect, { visible: !layer.isBlocked(), windowId });
|
|
2671
|
+
};
|
|
2672
|
+
useIsoLayoutEffect(() => measure.current());
|
|
2673
|
+
useIsoLayoutEffect(() => {
|
|
2674
|
+
const el = ref.current;
|
|
2675
|
+
const run = () => measure.current();
|
|
2676
|
+
const observer = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(run);
|
|
2677
|
+
if (el) observer?.observe(el);
|
|
2678
|
+
const unregister = layer.native.register(run);
|
|
2679
|
+
window.addEventListener("resize", run);
|
|
2680
|
+
return () => {
|
|
2681
|
+
observer?.disconnect();
|
|
2682
|
+
unregister();
|
|
2683
|
+
window.removeEventListener("resize", run);
|
|
2684
|
+
layer.native.report(itemId, null, { visible: false, windowId });
|
|
2685
|
+
};
|
|
2686
|
+
}, [itemId, windowId, layer]);
|
|
2687
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { ref, className: "mxc-native-body", "data-native-body": itemId, children });
|
|
2688
|
+
}
|
|
2689
|
+
function ItemProblem({ message, onClose, onRetry }) {
|
|
2690
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "mxc-problem", role: "alert", children: [
|
|
2691
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(AlertIcon, {}),
|
|
2692
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { children: message }),
|
|
2693
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "mxc-problem-actions", children: [
|
|
2694
|
+
onRetry ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", className: "mxc-text-button", onClick: onRetry, children: "Try again" }) : null,
|
|
2695
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", className: "mxc-text-button", onClick: onClose, children: "Close tab" })
|
|
2696
|
+
] })
|
|
2697
|
+
] });
|
|
2698
|
+
}
|
|
2699
|
+
var ItemBoundary = class extends import_react7.Component {
|
|
2700
|
+
state = { error: null };
|
|
2701
|
+
static getDerivedStateFromError(error) {
|
|
2702
|
+
return { error };
|
|
2703
|
+
}
|
|
2704
|
+
componentDidCatch(error, info) {
|
|
2705
|
+
console.error(`[@ai-matrx/canvas] "${this.props.item.kind}" failed to render`, error, info.componentStack);
|
|
2706
|
+
}
|
|
2707
|
+
render() {
|
|
2708
|
+
if (this.state.error) {
|
|
2709
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
2710
|
+
ItemProblem,
|
|
2711
|
+
{
|
|
2712
|
+
message: "This item failed to load.",
|
|
2713
|
+
onClose: this.props.onClose,
|
|
2714
|
+
onRetry: () => {
|
|
2715
|
+
this.props.onRetry();
|
|
2716
|
+
this.setState({ error: null });
|
|
2717
|
+
}
|
|
2718
|
+
}
|
|
2719
|
+
);
|
|
2720
|
+
}
|
|
2721
|
+
return this.props.children;
|
|
2722
|
+
}
|
|
2723
|
+
};
|
|
2724
|
+
|
|
2725
|
+
// src/react/CanvasPane.tsx
|
|
2726
|
+
var import_react8 = require("react");
|
|
2727
|
+
var Menu = __toESM(require("@radix-ui/react-dropdown-menu"), 1);
|
|
2728
|
+
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
2729
|
+
var useIsoLayoutEffect2 = typeof window === "undefined" ? import_react8.useEffect : import_react8.useLayoutEffect;
|
|
2004
2730
|
function itemTitle(item, kind) {
|
|
2005
2731
|
if (item.title) return item.title;
|
|
2732
|
+
if (item.kind === CANVAS_LAUNCHER_KIND) return "New tab";
|
|
2006
2733
|
if (kind?.title) {
|
|
2007
2734
|
try {
|
|
2008
2735
|
const title = kind.title(item.data, item);
|
|
@@ -2013,70 +2740,44 @@ function itemTitle(item, kind) {
|
|
|
2013
2740
|
return kind?.label ?? item.kind;
|
|
2014
2741
|
}
|
|
2015
2742
|
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
2743
|
function CanvasPaneView({ paneId }) {
|
|
2035
2744
|
const canvas = useCanvas();
|
|
2036
|
-
const { windowId,
|
|
2745
|
+
const { windowId, tabStrip } = useCanvasInternals();
|
|
2746
|
+
const columnSlots = useCanvasSlots();
|
|
2747
|
+
const [ownSlots] = (0, import_react8.useState)(createCanvasSlots);
|
|
2748
|
+
const slots = columnSlots ?? ownSlots;
|
|
2037
2749
|
const pane = useCanvasState((s) => s.panes[paneId]);
|
|
2038
2750
|
const items = useCanvasState((s) => s.items);
|
|
2039
2751
|
const isFullscreen = useCanvasState((s) => selectCanvasIsFullscreen(s, windowId));
|
|
2752
|
+
const isOpen = useCanvasState((s) => selectCanvasIsOpen(s, windowId));
|
|
2040
2753
|
const focused = useCanvasState((s) => selectCanvasFocusedPaneId(s, windowId) === paneId);
|
|
2041
2754
|
const paneCount = useCanvasState((s) => selectCanvasPaneCount(s, windowId));
|
|
2042
2755
|
const role = useCanvasState((s) => windowId === CANVAS_MAIN_WINDOW ? "main" : s.windows[windowId]?.role ?? "window");
|
|
2043
2756
|
const active = pane?.activeItemId ? items[pane.activeItemId] : void 0;
|
|
2044
|
-
const
|
|
2045
|
-
const
|
|
2046
|
-
const sectionRef = (0,
|
|
2047
|
-
(0,
|
|
2757
|
+
const canGoBack = useCanvasState((s) => active ? selectCanvasCanGoBack(s, active.id) : false);
|
|
2758
|
+
const kind = useItemKind(active?.kind ?? "");
|
|
2759
|
+
const sectionRef = (0, import_react8.useRef)(null);
|
|
2760
|
+
const bodyRef = (0, import_react8.useRef)(null);
|
|
2761
|
+
const presentation = usePanePresentation(slots, paneId, isFullscreen, paneCount);
|
|
2762
|
+
const drop = usePaneDrop(paneId, () => sectionRef.current);
|
|
2763
|
+
useIsoLayoutEffect2(() => {
|
|
2764
|
+
const body = bodyRef.current;
|
|
2765
|
+
const section = sectionRef.current;
|
|
2766
|
+
if (body) slots.set(paneId, body);
|
|
2767
|
+
slots.setSection(paneId, section);
|
|
2768
|
+
return () => {
|
|
2769
|
+
slots.unset(paneId, body);
|
|
2770
|
+
if (slots.section(paneId) === section) slots.setSection(paneId, null);
|
|
2771
|
+
};
|
|
2772
|
+
}, [slots, paneId]);
|
|
2048
2773
|
if (!pane) return null;
|
|
2049
|
-
const kindProps = active && kind ? { item: active, data: active.data, paneId, isFocused: focused, canvas, windowId } : null;
|
|
2774
|
+
const kindProps = active && kind ? { item: active, data: active.data, paneId, isFocused: focused, isVisible: isOpen, canvas, windowId, presentation } : null;
|
|
2050
2775
|
const Leading = kind?.HeaderLeading;
|
|
2051
2776
|
const Action = kind?.HeaderAction;
|
|
2052
2777
|
const Toolbar = kind?.Toolbar;
|
|
2053
2778
|
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
2779
|
const closeLabel = role === "pop-out" && paneCount === 1 ? "Close window" : paneCount > 1 ? "Close pane" : "Close canvas";
|
|
2079
|
-
return /* @__PURE__ */ (0,
|
|
2780
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
2080
2781
|
CanvasPanel,
|
|
2081
2782
|
{
|
|
2082
2783
|
ref: sectionRef,
|
|
@@ -2085,10 +2786,14 @@ function CanvasPaneView({ paneId }) {
|
|
|
2085
2786
|
title: active ? itemTitle(active, kind) : "Canvas",
|
|
2086
2787
|
focused,
|
|
2087
2788
|
"data-mxc-pane": paneId,
|
|
2088
|
-
header:
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2789
|
+
header: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
|
|
2790
|
+
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,
|
|
2791
|
+
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" }),
|
|
2792
|
+
/* @__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, {}) })
|
|
2793
|
+
] }),
|
|
2794
|
+
leading: Leading && kindProps ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Leading, { ...kindProps }) : null,
|
|
2795
|
+
actions: Action && kindProps ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Action, { ...kindProps }) : null,
|
|
2796
|
+
renderMenu: () => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(PaneMenu, { paneId, windowId, activeItem: active, kind, kindProps, tabCount: pane.itemIds.length, role }),
|
|
2092
2797
|
expanded: isFullscreen,
|
|
2093
2798
|
onToggleExpand: () => canvas.setFullscreen(!isFullscreen),
|
|
2094
2799
|
expandShortcut: canvasShortcutHint("canvas.toggleFullscreen", isMac()),
|
|
@@ -2098,22 +2803,15 @@ function CanvasPaneView({ paneId }) {
|
|
|
2098
2803
|
onPointerDownCapture: () => {
|
|
2099
2804
|
if (!focused) canvas.focusPane(paneId);
|
|
2100
2805
|
},
|
|
2101
|
-
|
|
2102
|
-
onDragLeave,
|
|
2103
|
-
onDrop,
|
|
2806
|
+
...drop,
|
|
2104
2807
|
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
|
-
})
|
|
2808
|
+
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,
|
|
2809
|
+
/* @__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
2810
|
]
|
|
2113
2811
|
}
|
|
2114
2812
|
);
|
|
2115
2813
|
}
|
|
2116
|
-
var ToolbarBoundary = class extends
|
|
2814
|
+
var ToolbarBoundary = class extends import_react8.Component {
|
|
2117
2815
|
state = { failed: false };
|
|
2118
2816
|
static getDerivedStateFromError() {
|
|
2119
2817
|
return { failed: true };
|
|
@@ -2126,41 +2824,71 @@ var ToolbarBoundary = class extends import_react4.Component {
|
|
|
2126
2824
|
}
|
|
2127
2825
|
};
|
|
2128
2826
|
function SingleTitle({ item, kind }) {
|
|
2827
|
+
const canvas = useCanvas();
|
|
2129
2828
|
const title = itemTitle(item, kind);
|
|
2130
2829
|
const drag = useTabDrag(item.id, title);
|
|
2131
2830
|
const Icon = kind?.icon ?? AlertIcon;
|
|
2132
|
-
return /* @__PURE__ */ (0,
|
|
2133
|
-
/* @__PURE__ */ (0,
|
|
2134
|
-
/* @__PURE__ */ (0,
|
|
2831
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: "mxc-panel-title mxc-single-title", "data-draggable": "", title, ...drag, children: [
|
|
2832
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Icon, { className: "mxc-title-icon" }),
|
|
2833
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "mxc-tab-title", children: title }),
|
|
2834
|
+
/* @__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
2835
|
] });
|
|
2136
2836
|
}
|
|
2837
|
+
function TabStrip({ paneId, itemIds, activeItemId }) {
|
|
2838
|
+
const strip = (0, import_react8.useRef)(null);
|
|
2839
|
+
useIsoLayoutEffect2(() => {
|
|
2840
|
+
const el = strip.current;
|
|
2841
|
+
if (!el) return void 0;
|
|
2842
|
+
const reveal = () => {
|
|
2843
|
+
const tab = el.querySelector('[role="tab"][aria-selected="true"]');
|
|
2844
|
+
if (!tab) return;
|
|
2845
|
+
const left = tab.offsetLeft - el.offsetLeft;
|
|
2846
|
+
const right = left + tab.offsetWidth;
|
|
2847
|
+
if (left < el.scrollLeft) el.scrollLeft = left;
|
|
2848
|
+
else if (right > el.scrollLeft + el.clientWidth) el.scrollLeft = right - el.clientWidth;
|
|
2849
|
+
};
|
|
2850
|
+
reveal();
|
|
2851
|
+
const observer = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(reveal);
|
|
2852
|
+
observer?.observe(el);
|
|
2853
|
+
return () => observer?.disconnect();
|
|
2854
|
+
}, [activeItemId, itemIds.length]);
|
|
2855
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
2856
|
+
"div",
|
|
2857
|
+
{
|
|
2858
|
+
ref: strip,
|
|
2859
|
+
className: "mxc-tabs",
|
|
2860
|
+
role: "tablist",
|
|
2861
|
+
"aria-label": "Canvas tabs",
|
|
2862
|
+
onWheel: (event) => {
|
|
2863
|
+
const el = strip.current;
|
|
2864
|
+
if (el && Math.abs(event.deltaY) > Math.abs(event.deltaX)) el.scrollLeft += event.deltaY;
|
|
2865
|
+
},
|
|
2866
|
+
children: itemIds.map((itemId, index) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ItemTab, { itemId, paneId, index, active: itemId === activeItemId }, itemId))
|
|
2867
|
+
}
|
|
2868
|
+
);
|
|
2869
|
+
}
|
|
2137
2870
|
function ItemTab({ itemId, paneId, index, active }) {
|
|
2138
2871
|
const canvas = useCanvas();
|
|
2139
2872
|
const item = useCanvasState((s) => s.items[itemId]);
|
|
2140
|
-
const kind =
|
|
2141
|
-
const ref = (0, import_react4.useRef)(null);
|
|
2873
|
+
const kind = useItemKind(item?.kind ?? "");
|
|
2142
2874
|
const title = item ? itemTitle(item, kind) : "";
|
|
2143
2875
|
const drag = useTabDrag(itemId, title);
|
|
2144
|
-
(0, import_react4.useEffect)(() => {
|
|
2145
|
-
if (active) ref.current?.scrollIntoView?.({ block: "nearest", inline: "nearest" });
|
|
2146
|
-
}, [active]);
|
|
2147
2876
|
if (!item) return null;
|
|
2148
2877
|
const Icon = kind?.icon ?? AlertIcon;
|
|
2149
|
-
return /* @__PURE__ */ (0,
|
|
2878
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
2150
2879
|
CanvasTab,
|
|
2151
2880
|
{
|
|
2152
|
-
ref,
|
|
2153
2881
|
title,
|
|
2154
|
-
icon: /* @__PURE__ */ (0,
|
|
2882
|
+
icon: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Icon, {}),
|
|
2155
2883
|
active,
|
|
2156
2884
|
onSelect: () => canvas.activate(itemId),
|
|
2157
2885
|
onClose: () => canvas.close(itemId),
|
|
2158
2886
|
...drag,
|
|
2159
2887
|
onDragOver: (event) => {
|
|
2160
|
-
if (event.dataTransfer.types.includes(
|
|
2888
|
+
if (event.dataTransfer.types.includes(CANVAS_DRAG_MIME)) event.preventDefault();
|
|
2161
2889
|
},
|
|
2162
2890
|
onDrop: (event) => {
|
|
2163
|
-
const dragged = event.dataTransfer.getData(
|
|
2891
|
+
const dragged = event.dataTransfer.getData(CANVAS_DRAG_MIME);
|
|
2164
2892
|
if (!dragged) return;
|
|
2165
2893
|
event.preventDefault();
|
|
2166
2894
|
if (dragged !== itemId) canvas.moveItem(dragged, paneId, index);
|
|
@@ -2183,16 +2911,16 @@ function PaneMenu({
|
|
|
2183
2911
|
const canvas = useCanvas();
|
|
2184
2912
|
const { ports, layer } = useCanvasInternals();
|
|
2185
2913
|
const moveId = tabCount > 1 ? activeItem?.id : void 0;
|
|
2186
|
-
const blocking = (0,
|
|
2187
|
-
(0,
|
|
2914
|
+
const blocking = (0, import_react8.useRef)(null);
|
|
2915
|
+
(0, import_react8.useEffect)(() => () => blocking.current?.(), []);
|
|
2188
2916
|
const kindItems = kind?.menuItems && kindProps ? safeMenu(() => kind.menuItems?.(kindProps)) : [];
|
|
2189
2917
|
const hostItems = activeItem && ports.itemActions ? safeMenu(() => ports.itemActions?.(activeItem, kind)) : [];
|
|
2190
2918
|
const sections = [
|
|
2191
2919
|
[...kindItems, ...hostItems].map((entry) => ({ ...entry, id: `item:${entry.id}` })),
|
|
2192
2920
|
[
|
|
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,
|
|
2921
|
+
{ id: "split-right", label: "Split right", icon: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(SplitRightIcon, {}), onSelect: () => canvas.splitPane(paneId, "horizontal", moveId) },
|
|
2922
|
+
{ id: "split-down", label: "Split down", icon: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(SplitDownIcon, {}), onSelect: () => canvas.splitPane(paneId, "vertical", moveId) },
|
|
2923
|
+
...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
2924
|
],
|
|
2197
2925
|
activeItem ? [
|
|
2198
2926
|
{ id: "close-tab", label: "Close tab", onSelect: () => canvas.close(activeItem.id) },
|
|
@@ -2210,7 +2938,7 @@ function PaneMenu({
|
|
|
2210
2938
|
handlers.set(action.id, action.onSelect);
|
|
2211
2939
|
}
|
|
2212
2940
|
});
|
|
2213
|
-
return /* @__PURE__ */ (0,
|
|
2941
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
2214
2942
|
CanvasMenuTrigger,
|
|
2215
2943
|
{
|
|
2216
2944
|
onClick: (event) => {
|
|
@@ -2225,7 +2953,7 @@ function PaneMenu({
|
|
|
2225
2953
|
}
|
|
2226
2954
|
);
|
|
2227
2955
|
}
|
|
2228
|
-
return /* @__PURE__ */ (0,
|
|
2956
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
2229
2957
|
Menu.Root,
|
|
2230
2958
|
{
|
|
2231
2959
|
onOpenChange: (open) => {
|
|
@@ -2233,16 +2961,16 @@ function PaneMenu({
|
|
|
2233
2961
|
blocking.current = open ? layer.beginBlocking() : null;
|
|
2234
2962
|
},
|
|
2235
2963
|
children: [
|
|
2236
|
-
/* @__PURE__ */ (0,
|
|
2237
|
-
/* @__PURE__ */ (0,
|
|
2964
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Menu.Trigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(CanvasMenuTrigger, {}) }),
|
|
2965
|
+
/* @__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
2966
|
]
|
|
2239
2967
|
}
|
|
2240
2968
|
);
|
|
2241
2969
|
}
|
|
2242
2970
|
function MenuSection({ first, actions }) {
|
|
2243
|
-
return /* @__PURE__ */ (0,
|
|
2244
|
-
first ? null : /* @__PURE__ */ (0,
|
|
2245
|
-
actions.map((action) => /* @__PURE__ */ (0,
|
|
2971
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
|
|
2972
|
+
first ? null : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Menu.Separator, { className: "mxc-menu-separator" }),
|
|
2973
|
+
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
2974
|
action.icon,
|
|
2247
2975
|
action.label
|
|
2248
2976
|
] }, action.id))
|
|
@@ -2256,150 +2984,16 @@ function safeMenu(build) {
|
|
|
2256
2984
|
return [];
|
|
2257
2985
|
}
|
|
2258
2986
|
}
|
|
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
2987
|
|
|
2394
2988
|
// src/react/CanvasColumn.tsx
|
|
2395
|
-
var
|
|
2396
|
-
var
|
|
2989
|
+
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
2990
|
+
var useIsoLayoutEffect3 = typeof window === "undefined" ? import_react9.useEffect : import_react9.useLayoutEffect;
|
|
2397
2991
|
function subscribeViewport(listener) {
|
|
2398
2992
|
window.addEventListener("resize", listener);
|
|
2399
2993
|
return () => window.removeEventListener("resize", listener);
|
|
2400
2994
|
}
|
|
2401
2995
|
function useViewportWidth() {
|
|
2402
|
-
return (0,
|
|
2996
|
+
return (0, import_react9.useSyncExternalStore)(subscribeViewport, () => window.innerWidth, () => 0);
|
|
2403
2997
|
}
|
|
2404
2998
|
function useRegionWidth() {
|
|
2405
2999
|
const viewport = useViewportWidth();
|
|
@@ -2417,28 +3011,66 @@ function useCanvasColumnWidth() {
|
|
|
2417
3011
|
return fitCanvasWidth(width, region, controller.rules);
|
|
2418
3012
|
}
|
|
2419
3013
|
function CanvasColumn({ className, style, onLiveWidth, placement = "docked" }) {
|
|
2420
|
-
const { controller, windowId } = useCanvasInternals();
|
|
3014
|
+
const { controller, windowId, layer } = useCanvasInternals();
|
|
2421
3015
|
const isOpen = useCanvasState((s) => selectCanvasIsOpen(s, windowId));
|
|
2422
3016
|
const isFullscreen = useCanvasState((s) => selectCanvasIsFullscreen(s, windowId));
|
|
2423
3017
|
const width = useCanvasState((s) => selectCanvasWidth(s, windowId));
|
|
2424
3018
|
const layout = useCanvasState((s) => selectCanvasLayout(s, windowId));
|
|
2425
3019
|
const region = useRegionWidth();
|
|
2426
|
-
const [dragWidth, setDragWidth] = (0,
|
|
3020
|
+
const [dragWidth, setDragWidth] = (0, import_react9.useState)(null);
|
|
3021
|
+
const [slots] = (0, import_react9.useState)(createCanvasSlots);
|
|
3022
|
+
const ref = (0, import_react9.useRef)(null);
|
|
2427
3023
|
useRegisterCanvasPresence();
|
|
2428
|
-
|
|
3024
|
+
(0, import_react9.useEffect)(() => {
|
|
3025
|
+
const el = ref.current;
|
|
3026
|
+
if (!el) return void 0;
|
|
3027
|
+
let frame2 = 0;
|
|
3028
|
+
let running = false;
|
|
3029
|
+
const tick = () => {
|
|
3030
|
+
layer.native.remeasure();
|
|
3031
|
+
frame2 = running ? requestAnimationFrame(tick) : 0;
|
|
3032
|
+
};
|
|
3033
|
+
const start = (event) => {
|
|
3034
|
+
if (event.target !== el || running) return;
|
|
3035
|
+
running = true;
|
|
3036
|
+
frame2 = requestAnimationFrame(tick);
|
|
3037
|
+
};
|
|
3038
|
+
const stop = (event) => {
|
|
3039
|
+
if (event.target !== el) return;
|
|
3040
|
+
running = false;
|
|
3041
|
+
if (frame2) cancelAnimationFrame(frame2);
|
|
3042
|
+
frame2 = 0;
|
|
3043
|
+
layer.native.remeasure();
|
|
3044
|
+
};
|
|
3045
|
+
el.addEventListener("transitionrun", start);
|
|
3046
|
+
el.addEventListener("transitionend", stop);
|
|
3047
|
+
el.addEventListener("transitioncancel", stop);
|
|
3048
|
+
return () => {
|
|
3049
|
+
running = false;
|
|
3050
|
+
if (frame2) cancelAnimationFrame(frame2);
|
|
3051
|
+
el.removeEventListener("transitionrun", start);
|
|
3052
|
+
el.removeEventListener("transitionend", stop);
|
|
3053
|
+
el.removeEventListener("transitioncancel", stop);
|
|
3054
|
+
};
|
|
3055
|
+
}, [layer]);
|
|
3056
|
+
if (!layout) return null;
|
|
2429
3057
|
const fitted = fitCanvasWidth(width, region, controller.rules);
|
|
2430
3058
|
const shown = dragWidth ?? fitted;
|
|
2431
|
-
return /* @__PURE__ */ (0,
|
|
3059
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
2432
3060
|
"aside",
|
|
2433
3061
|
{
|
|
3062
|
+
ref,
|
|
2434
3063
|
className: ["mxc-column", className].filter(Boolean).join(" "),
|
|
2435
|
-
"data-
|
|
3064
|
+
"data-open": isOpen ? "" : void 0,
|
|
3065
|
+
"data-fullscreen": isOpen && isFullscreen ? "" : void 0,
|
|
2436
3066
|
"data-placement": placement,
|
|
2437
3067
|
"data-mxc-window": windowId,
|
|
2438
3068
|
"aria-label": "Canvas",
|
|
3069
|
+
"aria-hidden": isOpen ? void 0 : true,
|
|
3070
|
+
inert: !isOpen,
|
|
2439
3071
|
style: { ...style, ["--mxc-width"]: `${shown}px` },
|
|
2440
|
-
children: [
|
|
2441
|
-
placement === "docked" && !isFullscreen ? /* @__PURE__ */ (0,
|
|
3072
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "mxc-column-inner", children: [
|
|
3073
|
+
placement === "docked" && isOpen && !isFullscreen ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
2442
3074
|
WidthHandle,
|
|
2443
3075
|
{
|
|
2444
3076
|
width: fitted,
|
|
@@ -2449,14 +3081,17 @@ function CanvasColumn({ className, style, onLiveWidth, placement = "docked" }) {
|
|
|
2449
3081
|
}
|
|
2450
3082
|
}
|
|
2451
3083
|
) : null,
|
|
2452
|
-
/* @__PURE__ */ (0,
|
|
2453
|
-
|
|
3084
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(CanvasSlotsContext.Provider, { value: slots, children: [
|
|
3085
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "mxc-layout", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(LayoutNodeView, { node: layout }) }),
|
|
3086
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(CanvasBodyLayer, {})
|
|
3087
|
+
] })
|
|
3088
|
+
] })
|
|
2454
3089
|
}
|
|
2455
3090
|
);
|
|
2456
3091
|
}
|
|
2457
3092
|
function WidthHandle({ width, region, onDrag }) {
|
|
2458
3093
|
const canvas = useCanvas();
|
|
2459
|
-
const start = (0,
|
|
3094
|
+
const start = (0, import_react9.useRef)(null);
|
|
2460
3095
|
const clamp = (next) => clampDraggedCanvasWidth(next, region, canvas.rules);
|
|
2461
3096
|
const onPointerDown = (event) => {
|
|
2462
3097
|
event.preventDefault();
|
|
@@ -2476,7 +3111,7 @@ function WidthHandle({ width, region, onDrag }) {
|
|
|
2476
3111
|
onDrag(null);
|
|
2477
3112
|
canvas.setWidth(next);
|
|
2478
3113
|
};
|
|
2479
|
-
return /* @__PURE__ */ (0,
|
|
3114
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
2480
3115
|
"div",
|
|
2481
3116
|
{
|
|
2482
3117
|
className: "mxc-width-handle",
|
|
@@ -2498,12 +3133,12 @@ function WidthHandle({ width, region, onDrag }) {
|
|
|
2498
3133
|
);
|
|
2499
3134
|
}
|
|
2500
3135
|
function LayoutNodeView({ node }) {
|
|
2501
|
-
if (node.type === "pane") return /* @__PURE__ */ (0,
|
|
2502
|
-
return /* @__PURE__ */ (0,
|
|
3136
|
+
if (node.type === "pane") return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(CanvasPaneView, { paneId: node.paneId });
|
|
3137
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(SplitView, { node });
|
|
2503
3138
|
}
|
|
2504
3139
|
function useMeasuredWidth(ref, enabled) {
|
|
2505
|
-
const [width, setWidth] = (0,
|
|
2506
|
-
|
|
3140
|
+
const [width, setWidth] = (0, import_react9.useState)(0);
|
|
3141
|
+
useIsoLayoutEffect3(() => {
|
|
2507
3142
|
const el = ref.current;
|
|
2508
3143
|
if (!enabled || !el) return;
|
|
2509
3144
|
setWidth(el.getBoundingClientRect().width);
|
|
@@ -2516,8 +3151,8 @@ function useMeasuredWidth(ref, enabled) {
|
|
|
2516
3151
|
}
|
|
2517
3152
|
function SplitView({ node }) {
|
|
2518
3153
|
const canvas = useCanvas();
|
|
2519
|
-
const ref = (0,
|
|
2520
|
-
const [liveSizes, setLiveSizes] = (0,
|
|
3154
|
+
const ref = (0, import_react9.useRef)(null);
|
|
3155
|
+
const [liveSizes, setLiveSizes] = (0, import_react9.useState)(null);
|
|
2521
3156
|
const sizes = liveSizes ?? node.sizes;
|
|
2522
3157
|
const measuredWidth = useMeasuredWidth(ref, node.orientation === "horizontal" && canvas.rules.paneMinWidth > 0);
|
|
2523
3158
|
const stacked = node.orientation === "horizontal" && shouldStackSplit(measuredWidth, node.children.length, canvas.rules);
|
|
@@ -2557,11 +3192,11 @@ function SplitView({ node }) {
|
|
|
2557
3192
|
target.addEventListener("pointerup", up);
|
|
2558
3193
|
target.addEventListener("pointercancel", up);
|
|
2559
3194
|
};
|
|
2560
|
-
return /* @__PURE__ */ (0,
|
|
3195
|
+
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
3196
|
SplitChild,
|
|
2562
3197
|
{
|
|
2563
3198
|
size: sizes[i] ?? 1 / node.children.length,
|
|
2564
|
-
handle: i < node.children.length - 1 ? /* @__PURE__ */ (0,
|
|
3199
|
+
handle: i < node.children.length - 1 ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
2565
3200
|
"div",
|
|
2566
3201
|
{
|
|
2567
3202
|
className: "mxc-split-handle",
|
|
@@ -2571,14 +3206,14 @@ function SplitView({ node }) {
|
|
|
2571
3206
|
onDoubleClick: () => canvas.resizeSplit(node.id, node.children.map(() => 1 / node.children.length))
|
|
2572
3207
|
}
|
|
2573
3208
|
) : null,
|
|
2574
|
-
children: /* @__PURE__ */ (0,
|
|
3209
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(LayoutNodeView, { node: child })
|
|
2575
3210
|
},
|
|
2576
3211
|
child.type === "pane" ? child.paneId : child.id
|
|
2577
3212
|
)) });
|
|
2578
3213
|
}
|
|
2579
3214
|
function SplitChild({ size, handle, children }) {
|
|
2580
|
-
return /* @__PURE__ */ (0,
|
|
2581
|
-
/* @__PURE__ */ (0,
|
|
3215
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
|
|
3216
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "mxc-split-child", style: { flexGrow: size, flexBasis: 0 }, children }),
|
|
2582
3217
|
handle
|
|
2583
3218
|
] });
|
|
2584
3219
|
}
|
|
@@ -2592,26 +3227,61 @@ function CanvasToggle({
|
|
|
2592
3227
|
const count = useCanvasState((s) => selectCanvasWindowItemCount(s, windowId));
|
|
2593
3228
|
const hint = canvasShortcutHint("canvas.toggle", isMac2());
|
|
2594
3229
|
const Button = variant === "glass" ? import_tap_target.TapTargetButton : import_tap_target.TapTargetButtonTransparent;
|
|
2595
|
-
return /* @__PURE__ */ (0,
|
|
2596
|
-
/* @__PURE__ */ (0,
|
|
3230
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { className: ["mxc-toggle", className].filter(Boolean).join(" "), "data-open": isOpen ? "" : void 0, children: [
|
|
3231
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
2597
3232
|
Button,
|
|
2598
3233
|
{
|
|
2599
3234
|
ariaLabel: isOpen ? "Hide canvas" : "Show canvas",
|
|
2600
3235
|
tooltip: `${isOpen ? "Hide canvas" : "Show canvas"}${hint ? ` (${hint})` : ""}`,
|
|
2601
|
-
icon: /* @__PURE__ */ (0,
|
|
3236
|
+
icon: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(PanelRightIcon, {}),
|
|
2602
3237
|
onClick: () => controller.toggle()
|
|
2603
3238
|
}
|
|
2604
3239
|
),
|
|
2605
|
-
count > 0 && !isOpen ? /* @__PURE__ */ (0,
|
|
3240
|
+
count > 0 && !isOpen ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "mxc-toggle-badge", children: count }) : null
|
|
2606
3241
|
] });
|
|
2607
3242
|
}
|
|
3243
|
+
function CanvasKindIcons({
|
|
3244
|
+
kinds,
|
|
3245
|
+
variant = "transparent",
|
|
3246
|
+
className,
|
|
3247
|
+
label = "Canvas tools"
|
|
3248
|
+
}) {
|
|
3249
|
+
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) => {
|
|
3250
|
+
const icon = typeof entry === "string" ? { kind: entry } : entry;
|
|
3251
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(KindIconButton, { icon, variant }, `${icon.kind}::${icon.key ?? CANVAS_DEFAULT_KEY}`);
|
|
3252
|
+
}) });
|
|
3253
|
+
}
|
|
3254
|
+
function KindIconButton({ icon, variant }) {
|
|
3255
|
+
const kind = useCanvasKind(icon.kind);
|
|
3256
|
+
const toggle = useCanvasKindToggle({
|
|
3257
|
+
kind: icon.kind,
|
|
3258
|
+
key: icon.key,
|
|
3259
|
+
data: icon.data,
|
|
3260
|
+
title: icon.title,
|
|
3261
|
+
whenVisible: icon.whenVisible
|
|
3262
|
+
});
|
|
3263
|
+
if (!kind) return null;
|
|
3264
|
+
const Icon = kind.icon;
|
|
3265
|
+
const Button = variant === "glass" ? import_tap_target.TapTargetButton : import_tap_target.TapTargetButtonTransparent;
|
|
3266
|
+
const name = icon.label ?? kind.label;
|
|
3267
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
3268
|
+
Button,
|
|
3269
|
+
{
|
|
3270
|
+
ariaLabel: name,
|
|
3271
|
+
tooltip: name,
|
|
3272
|
+
icon: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Icon, {}),
|
|
3273
|
+
pressed: toggle.isVisible,
|
|
3274
|
+
onClick: () => void toggle.toggle()
|
|
3275
|
+
}
|
|
3276
|
+
);
|
|
3277
|
+
}
|
|
2608
3278
|
function CanvasFrame({ children, className }) {
|
|
2609
3279
|
const { windowId } = useCanvasInternals();
|
|
2610
3280
|
const isFullscreen = useCanvasState((s) => selectCanvasIsFullscreen(s, windowId));
|
|
2611
3281
|
const isOpen = useCanvasState((s) => selectCanvasIsOpen(s, windowId));
|
|
2612
|
-
return /* @__PURE__ */ (0,
|
|
2613
|
-
/* @__PURE__ */ (0,
|
|
2614
|
-
/* @__PURE__ */ (0,
|
|
3282
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: ["mxc-frame", className].filter(Boolean).join(" "), "data-canvas-open": isOpen ? "" : void 0, children: [
|
|
3283
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "mxc-frame-main", hidden: isOpen && isFullscreen, children }),
|
|
3284
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(CanvasColumn, {})
|
|
2615
3285
|
] });
|
|
2616
3286
|
}
|
|
2617
3287
|
//# sourceMappingURL=react.cjs.map
|