@ai-matrx/canvas 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +35 -0
- package/README.md +66 -1
- package/dist/index.cjs +203 -21
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +7 -3
- package/dist/index.d.ts +7 -3
- package/dist/index.js +203 -21
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +972 -286
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +100 -3
- package/dist/react.d.ts +100 -3
- package/dist/react.js +980 -291
- package/dist/react.js.map +1 -1
- package/dist/styles.css +238 -26
- package/dist/{toggleKind-A9v45oSb.d.cts → toggleKind-BD78LnMB.d.cts} +105 -1
- package/dist/{toggleKind-A9v45oSb.d.ts → toggleKind-BD78LnMB.d.ts} +105 -1
- package/dist/tokens.css +3 -0
- package/package.json +1 -1
package/dist/react.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,69 +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;
|
|
2777
|
+
const Toolbar = kind?.Toolbar;
|
|
2052
2778
|
const single = tabStrip === "auto" && pane.itemIds.length === 1 && active;
|
|
2053
|
-
const hideDrop = () => {
|
|
2054
|
-
drop.current?.();
|
|
2055
|
-
drop.current = null;
|
|
2056
|
-
};
|
|
2057
|
-
const onDragOver = (event) => {
|
|
2058
|
-
if (!event.dataTransfer.types.includes(DRAG_MIME)) return;
|
|
2059
|
-
event.preventDefault();
|
|
2060
|
-
event.dataTransfer.dropEffect = "move";
|
|
2061
|
-
if (!drop.current && sectionRef.current) {
|
|
2062
|
-
drop.current = layer.show({ type: "drop-indicator", rect: toCanvasRect(sectionRef.current.getBoundingClientRect()), paneId });
|
|
2063
|
-
}
|
|
2064
|
-
};
|
|
2065
|
-
const onDragLeave = (event) => {
|
|
2066
|
-
const next = event.relatedTarget;
|
|
2067
|
-
if (next && sectionRef.current?.contains(next)) return;
|
|
2068
|
-
hideDrop();
|
|
2069
|
-
};
|
|
2070
|
-
const onDrop = (event) => {
|
|
2071
|
-
hideDrop();
|
|
2072
|
-
const itemId = event.dataTransfer.getData(DRAG_MIME);
|
|
2073
|
-
if (!itemId) return;
|
|
2074
|
-
event.preventDefault();
|
|
2075
|
-
canvas.moveItem(itemId, paneId);
|
|
2076
|
-
};
|
|
2077
2779
|
const closeLabel = role === "pop-out" && paneCount === 1 ? "Close window" : paneCount > 1 ? "Close pane" : "Close canvas";
|
|
2078
|
-
return /* @__PURE__ */ (0,
|
|
2780
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
2079
2781
|
CanvasPanel,
|
|
2080
2782
|
{
|
|
2081
2783
|
ref: sectionRef,
|
|
@@ -2084,10 +2786,14 @@ function CanvasPaneView({ paneId }) {
|
|
|
2084
2786
|
title: active ? itemTitle(active, kind) : "Canvas",
|
|
2085
2787
|
focused,
|
|
2086
2788
|
"data-mxc-pane": paneId,
|
|
2087
|
-
header:
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
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 }),
|
|
2091
2797
|
expanded: isFullscreen,
|
|
2092
2798
|
onToggleExpand: () => canvas.setFullscreen(!isFullscreen),
|
|
2093
2799
|
expandShortcut: canvasShortcutHint("canvas.toggleFullscreen", isMac()),
|
|
@@ -2097,54 +2803,92 @@ function CanvasPaneView({ paneId }) {
|
|
|
2097
2803
|
onPointerDownCapture: () => {
|
|
2098
2804
|
if (!focused) canvas.focusPane(paneId);
|
|
2099
2805
|
},
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
if (!item) return null;
|
|
2106
|
-
const isActive = itemId === pane.activeItemId;
|
|
2107
|
-
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ItemBody, { item, paneId, windowId, isActive, isFocused: focused && isActive, hasNativePort: !!ports.onNativeBodyFrame }, itemId);
|
|
2108
|
-
})
|
|
2806
|
+
...drop,
|
|
2807
|
+
children: [
|
|
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 }) })
|
|
2810
|
+
]
|
|
2109
2811
|
}
|
|
2110
2812
|
);
|
|
2111
2813
|
}
|
|
2814
|
+
var ToolbarBoundary = class extends import_react8.Component {
|
|
2815
|
+
state = { failed: false };
|
|
2816
|
+
static getDerivedStateFromError() {
|
|
2817
|
+
return { failed: true };
|
|
2818
|
+
}
|
|
2819
|
+
componentDidCatch(error) {
|
|
2820
|
+
console.error(`[@ai-matrx/canvas] "${this.props.kindId}" Toolbar threw`, error);
|
|
2821
|
+
}
|
|
2822
|
+
render() {
|
|
2823
|
+
return this.state.failed ? null : this.props.children;
|
|
2824
|
+
}
|
|
2825
|
+
};
|
|
2112
2826
|
function SingleTitle({ item, kind }) {
|
|
2827
|
+
const canvas = useCanvas();
|
|
2113
2828
|
const title = itemTitle(item, kind);
|
|
2114
2829
|
const drag = useTabDrag(item.id, title);
|
|
2115
2830
|
const Icon = kind?.icon ?? AlertIcon;
|
|
2116
|
-
return /* @__PURE__ */ (0,
|
|
2117
|
-
/* @__PURE__ */ (0,
|
|
2118
|
-
/* @__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, {}) })
|
|
2119
2835
|
] });
|
|
2120
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
|
+
}
|
|
2121
2870
|
function ItemTab({ itemId, paneId, index, active }) {
|
|
2122
2871
|
const canvas = useCanvas();
|
|
2123
2872
|
const item = useCanvasState((s) => s.items[itemId]);
|
|
2124
|
-
const kind =
|
|
2125
|
-
const ref = (0, import_react4.useRef)(null);
|
|
2873
|
+
const kind = useItemKind(item?.kind ?? "");
|
|
2126
2874
|
const title = item ? itemTitle(item, kind) : "";
|
|
2127
2875
|
const drag = useTabDrag(itemId, title);
|
|
2128
|
-
(0, import_react4.useEffect)(() => {
|
|
2129
|
-
if (active) ref.current?.scrollIntoView?.({ block: "nearest", inline: "nearest" });
|
|
2130
|
-
}, [active]);
|
|
2131
2876
|
if (!item) return null;
|
|
2132
2877
|
const Icon = kind?.icon ?? AlertIcon;
|
|
2133
|
-
return /* @__PURE__ */ (0,
|
|
2878
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
2134
2879
|
CanvasTab,
|
|
2135
2880
|
{
|
|
2136
|
-
ref,
|
|
2137
2881
|
title,
|
|
2138
|
-
icon: /* @__PURE__ */ (0,
|
|
2882
|
+
icon: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Icon, {}),
|
|
2139
2883
|
active,
|
|
2140
2884
|
onSelect: () => canvas.activate(itemId),
|
|
2141
2885
|
onClose: () => canvas.close(itemId),
|
|
2142
2886
|
...drag,
|
|
2143
2887
|
onDragOver: (event) => {
|
|
2144
|
-
if (event.dataTransfer.types.includes(
|
|
2888
|
+
if (event.dataTransfer.types.includes(CANVAS_DRAG_MIME)) event.preventDefault();
|
|
2145
2889
|
},
|
|
2146
2890
|
onDrop: (event) => {
|
|
2147
|
-
const dragged = event.dataTransfer.getData(
|
|
2891
|
+
const dragged = event.dataTransfer.getData(CANVAS_DRAG_MIME);
|
|
2148
2892
|
if (!dragged) return;
|
|
2149
2893
|
event.preventDefault();
|
|
2150
2894
|
if (dragged !== itemId) canvas.moveItem(dragged, paneId, index);
|
|
@@ -2167,16 +2911,16 @@ function PaneMenu({
|
|
|
2167
2911
|
const canvas = useCanvas();
|
|
2168
2912
|
const { ports, layer } = useCanvasInternals();
|
|
2169
2913
|
const moveId = tabCount > 1 ? activeItem?.id : void 0;
|
|
2170
|
-
const blocking = (0,
|
|
2171
|
-
(0,
|
|
2914
|
+
const blocking = (0, import_react8.useRef)(null);
|
|
2915
|
+
(0, import_react8.useEffect)(() => () => blocking.current?.(), []);
|
|
2172
2916
|
const kindItems = kind?.menuItems && kindProps ? safeMenu(() => kind.menuItems?.(kindProps)) : [];
|
|
2173
2917
|
const hostItems = activeItem && ports.itemActions ? safeMenu(() => ports.itemActions?.(activeItem, kind)) : [];
|
|
2174
2918
|
const sections = [
|
|
2175
2919
|
[...kindItems, ...hostItems].map((entry) => ({ ...entry, id: `item:${entry.id}` })),
|
|
2176
2920
|
[
|
|
2177
|
-
{ id: "split-right", label: "Split right", icon: /* @__PURE__ */ (0,
|
|
2178
|
-
{ id: "split-down", label: "Split down", icon: /* @__PURE__ */ (0,
|
|
2179
|
-
...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) }] : []
|
|
2180
2924
|
],
|
|
2181
2925
|
activeItem ? [
|
|
2182
2926
|
{ id: "close-tab", label: "Close tab", onSelect: () => canvas.close(activeItem.id) },
|
|
@@ -2194,7 +2938,7 @@ function PaneMenu({
|
|
|
2194
2938
|
handlers.set(action.id, action.onSelect);
|
|
2195
2939
|
}
|
|
2196
2940
|
});
|
|
2197
|
-
return /* @__PURE__ */ (0,
|
|
2941
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
2198
2942
|
CanvasMenuTrigger,
|
|
2199
2943
|
{
|
|
2200
2944
|
onClick: (event) => {
|
|
@@ -2209,7 +2953,7 @@ function PaneMenu({
|
|
|
2209
2953
|
}
|
|
2210
2954
|
);
|
|
2211
2955
|
}
|
|
2212
|
-
return /* @__PURE__ */ (0,
|
|
2956
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
2213
2957
|
Menu.Root,
|
|
2214
2958
|
{
|
|
2215
2959
|
onOpenChange: (open) => {
|
|
@@ -2217,16 +2961,16 @@ function PaneMenu({
|
|
|
2217
2961
|
blocking.current = open ? layer.beginBlocking() : null;
|
|
2218
2962
|
},
|
|
2219
2963
|
children: [
|
|
2220
|
-
/* @__PURE__ */ (0,
|
|
2221
|
-
/* @__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)) }) })
|
|
2222
2966
|
]
|
|
2223
2967
|
}
|
|
2224
2968
|
);
|
|
2225
2969
|
}
|
|
2226
2970
|
function MenuSection({ first, actions }) {
|
|
2227
|
-
return /* @__PURE__ */ (0,
|
|
2228
|
-
first ? null : /* @__PURE__ */ (0,
|
|
2229
|
-
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: [
|
|
2230
2974
|
action.icon,
|
|
2231
2975
|
action.label
|
|
2232
2976
|
] }, action.id))
|
|
@@ -2240,150 +2984,16 @@ function safeMenu(build) {
|
|
|
2240
2984
|
return [];
|
|
2241
2985
|
}
|
|
2242
2986
|
}
|
|
2243
|
-
function ItemBody({
|
|
2244
|
-
item,
|
|
2245
|
-
paneId,
|
|
2246
|
-
windowId,
|
|
2247
|
-
isActive,
|
|
2248
|
-
isFocused,
|
|
2249
|
-
hasNativePort
|
|
2250
|
-
}) {
|
|
2251
|
-
const canvas = useCanvas();
|
|
2252
|
-
const kind = useCanvasKind(item.kind);
|
|
2253
|
-
if (!isActive && !kind?.keepAlive) return null;
|
|
2254
|
-
const props = { item, data: item.data, paneId, isFocused, canvas, windowId };
|
|
2255
|
-
let unavailable = null;
|
|
2256
|
-
try {
|
|
2257
|
-
unavailable = kind?.unavailable?.(item.data, item) ?? null;
|
|
2258
|
-
} catch (error) {
|
|
2259
|
-
console.error(`[@ai-matrx/canvas] "${item.kind}" unavailable() threw`, error);
|
|
2260
|
-
}
|
|
2261
|
-
let body;
|
|
2262
|
-
if (!kind) {
|
|
2263
|
-
body = /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ItemProblem, { message: "This item can no longer be shown.", onClose: () => canvas.close(item.id) });
|
|
2264
|
-
} else if (unavailable) {
|
|
2265
|
-
body = /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(CanvasPanelEmpty, { children: unavailable });
|
|
2266
|
-
} else if (kind.body === "native") {
|
|
2267
|
-
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" });
|
|
2268
|
-
} else {
|
|
2269
|
-
body = /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Rendered, { kind, item, props });
|
|
2270
|
-
}
|
|
2271
|
-
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "mxc-item", hidden: !isActive, "data-kind": item.kind, children: body });
|
|
2272
|
-
}
|
|
2273
|
-
function Rendered({ kind, item, props }) {
|
|
2274
|
-
const canvas = useCanvas();
|
|
2275
|
-
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 }) }) });
|
|
2276
|
-
}
|
|
2277
|
-
function KindRender({ kind, props }) {
|
|
2278
|
-
const Render = kindComponent(kind);
|
|
2279
|
-
if (!Render) throw new Error(`[@ai-matrx/canvas] kind "${kind.id}" has nothing to render.`);
|
|
2280
|
-
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Render, { ...props });
|
|
2281
|
-
}
|
|
2282
|
-
function NativeBody({ itemId, windowId, children }) {
|
|
2283
|
-
const { layer } = useCanvasInternals();
|
|
2284
|
-
const ref = (0, import_react4.useRef)(null);
|
|
2285
|
-
const measure = (0, import_react4.useRef)(() => void 0);
|
|
2286
|
-
measure.current = () => {
|
|
2287
|
-
const el = ref.current;
|
|
2288
|
-
const box = el?.isConnected ? el.getBoundingClientRect() : null;
|
|
2289
|
-
if (!box || box.width <= 0 || box.height <= 0) {
|
|
2290
|
-
layer.native.report(itemId, null, { visible: false, windowId });
|
|
2291
|
-
return;
|
|
2292
|
-
}
|
|
2293
|
-
const rect = { ...toCanvasRect(box), devicePixelRatio: window.devicePixelRatio || 1 };
|
|
2294
|
-
layer.native.report(itemId, rect, { visible: !layer.isBlocked(), windowId });
|
|
2295
|
-
};
|
|
2296
|
-
useIsoLayoutEffect(() => measure.current());
|
|
2297
|
-
useIsoLayoutEffect(() => {
|
|
2298
|
-
const el = ref.current;
|
|
2299
|
-
const run = () => measure.current();
|
|
2300
|
-
const observer = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(run);
|
|
2301
|
-
if (el) observer?.observe(el);
|
|
2302
|
-
const unregister = layer.native.register(run);
|
|
2303
|
-
window.addEventListener("resize", run);
|
|
2304
|
-
return () => {
|
|
2305
|
-
observer?.disconnect();
|
|
2306
|
-
unregister();
|
|
2307
|
-
window.removeEventListener("resize", run);
|
|
2308
|
-
layer.native.report(itemId, null, { visible: false, windowId });
|
|
2309
|
-
};
|
|
2310
|
-
}, [itemId, windowId, layer]);
|
|
2311
|
-
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { ref, className: "mxc-native-body", "data-native-body": itemId, children });
|
|
2312
|
-
}
|
|
2313
|
-
function ItemProblem({ message, onClose, onRetry }) {
|
|
2314
|
-
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "mxc-problem", role: "alert", children: [
|
|
2315
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(AlertIcon, {}),
|
|
2316
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { children: message }),
|
|
2317
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "mxc-problem-actions", children: [
|
|
2318
|
-
onRetry ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", className: "mxc-text-button", onClick: onRetry, children: "Try again" }) : null,
|
|
2319
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", className: "mxc-text-button", onClick: onClose, children: "Close tab" })
|
|
2320
|
-
] })
|
|
2321
|
-
] });
|
|
2322
|
-
}
|
|
2323
|
-
var ItemBoundary = class extends import_react4.Component {
|
|
2324
|
-
state = { error: null };
|
|
2325
|
-
static getDerivedStateFromError(error) {
|
|
2326
|
-
return { error };
|
|
2327
|
-
}
|
|
2328
|
-
componentDidCatch(error, info) {
|
|
2329
|
-
console.error(`[@ai-matrx/canvas] "${this.props.item.kind}" failed to render`, error, info.componentStack);
|
|
2330
|
-
}
|
|
2331
|
-
render() {
|
|
2332
|
-
if (this.state.error) {
|
|
2333
|
-
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
2334
|
-
ItemProblem,
|
|
2335
|
-
{
|
|
2336
|
-
message: "This item failed to load.",
|
|
2337
|
-
onClose: this.props.onClose,
|
|
2338
|
-
onRetry: () => {
|
|
2339
|
-
this.props.onRetry();
|
|
2340
|
-
this.setState({ error: null });
|
|
2341
|
-
}
|
|
2342
|
-
}
|
|
2343
|
-
);
|
|
2344
|
-
}
|
|
2345
|
-
return this.props.children;
|
|
2346
|
-
}
|
|
2347
|
-
};
|
|
2348
|
-
function Launcher({ paneId }) {
|
|
2349
|
-
const canvas = useCanvas();
|
|
2350
|
-
const kinds = useCanvasKinds().filter((kind) => kind.launcher);
|
|
2351
|
-
if (kinds.length === 0) return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(CanvasPanelEmpty, { children: "Nothing open" });
|
|
2352
|
-
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "mxc-launcher", children: kinds.map((kind) => {
|
|
2353
|
-
const Icon = kind.icon;
|
|
2354
|
-
const launcher = kind.launcher;
|
|
2355
|
-
if (!launcher) return null;
|
|
2356
|
-
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
|
|
2357
|
-
"button",
|
|
2358
|
-
{
|
|
2359
|
-
type: "button",
|
|
2360
|
-
className: "mxc-launcher-item",
|
|
2361
|
-
onClick: () => canvas.open({
|
|
2362
|
-
kind: kind.id,
|
|
2363
|
-
key: launcher.key,
|
|
2364
|
-
data: launcher.data,
|
|
2365
|
-
title: launcher.title ?? null,
|
|
2366
|
-
target: { paneId }
|
|
2367
|
-
}),
|
|
2368
|
-
children: [
|
|
2369
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Icon, { className: "mxc-launcher-icon" }),
|
|
2370
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children: launcher.title ?? kind.label })
|
|
2371
|
-
]
|
|
2372
|
-
},
|
|
2373
|
-
kind.id
|
|
2374
|
-
);
|
|
2375
|
-
}) });
|
|
2376
|
-
}
|
|
2377
2987
|
|
|
2378
2988
|
// src/react/CanvasColumn.tsx
|
|
2379
|
-
var
|
|
2380
|
-
var
|
|
2989
|
+
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
2990
|
+
var useIsoLayoutEffect3 = typeof window === "undefined" ? import_react9.useEffect : import_react9.useLayoutEffect;
|
|
2381
2991
|
function subscribeViewport(listener) {
|
|
2382
2992
|
window.addEventListener("resize", listener);
|
|
2383
2993
|
return () => window.removeEventListener("resize", listener);
|
|
2384
2994
|
}
|
|
2385
2995
|
function useViewportWidth() {
|
|
2386
|
-
return (0,
|
|
2996
|
+
return (0, import_react9.useSyncExternalStore)(subscribeViewport, () => window.innerWidth, () => 0);
|
|
2387
2997
|
}
|
|
2388
2998
|
function useRegionWidth() {
|
|
2389
2999
|
const viewport = useViewportWidth();
|
|
@@ -2401,28 +3011,66 @@ function useCanvasColumnWidth() {
|
|
|
2401
3011
|
return fitCanvasWidth(width, region, controller.rules);
|
|
2402
3012
|
}
|
|
2403
3013
|
function CanvasColumn({ className, style, onLiveWidth, placement = "docked" }) {
|
|
2404
|
-
const { controller, windowId } = useCanvasInternals();
|
|
3014
|
+
const { controller, windowId, layer } = useCanvasInternals();
|
|
2405
3015
|
const isOpen = useCanvasState((s) => selectCanvasIsOpen(s, windowId));
|
|
2406
3016
|
const isFullscreen = useCanvasState((s) => selectCanvasIsFullscreen(s, windowId));
|
|
2407
3017
|
const width = useCanvasState((s) => selectCanvasWidth(s, windowId));
|
|
2408
3018
|
const layout = useCanvasState((s) => selectCanvasLayout(s, windowId));
|
|
2409
3019
|
const region = useRegionWidth();
|
|
2410
|
-
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);
|
|
2411
3023
|
useRegisterCanvasPresence();
|
|
2412
|
-
|
|
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;
|
|
2413
3057
|
const fitted = fitCanvasWidth(width, region, controller.rules);
|
|
2414
3058
|
const shown = dragWidth ?? fitted;
|
|
2415
|
-
return /* @__PURE__ */ (0,
|
|
3059
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
2416
3060
|
"aside",
|
|
2417
3061
|
{
|
|
3062
|
+
ref,
|
|
2418
3063
|
className: ["mxc-column", className].filter(Boolean).join(" "),
|
|
2419
|
-
"data-
|
|
3064
|
+
"data-open": isOpen ? "" : void 0,
|
|
3065
|
+
"data-fullscreen": isOpen && isFullscreen ? "" : void 0,
|
|
2420
3066
|
"data-placement": placement,
|
|
2421
3067
|
"data-mxc-window": windowId,
|
|
2422
3068
|
"aria-label": "Canvas",
|
|
3069
|
+
"aria-hidden": isOpen ? void 0 : true,
|
|
3070
|
+
inert: !isOpen,
|
|
2423
3071
|
style: { ...style, ["--mxc-width"]: `${shown}px` },
|
|
2424
|
-
children: [
|
|
2425
|
-
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)(
|
|
2426
3074
|
WidthHandle,
|
|
2427
3075
|
{
|
|
2428
3076
|
width: fitted,
|
|
@@ -2433,14 +3081,17 @@ function CanvasColumn({ className, style, onLiveWidth, placement = "docked" }) {
|
|
|
2433
3081
|
}
|
|
2434
3082
|
}
|
|
2435
3083
|
) : null,
|
|
2436
|
-
/* @__PURE__ */ (0,
|
|
2437
|
-
|
|
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
|
+
] })
|
|
2438
3089
|
}
|
|
2439
3090
|
);
|
|
2440
3091
|
}
|
|
2441
3092
|
function WidthHandle({ width, region, onDrag }) {
|
|
2442
3093
|
const canvas = useCanvas();
|
|
2443
|
-
const start = (0,
|
|
3094
|
+
const start = (0, import_react9.useRef)(null);
|
|
2444
3095
|
const clamp = (next) => clampDraggedCanvasWidth(next, region, canvas.rules);
|
|
2445
3096
|
const onPointerDown = (event) => {
|
|
2446
3097
|
event.preventDefault();
|
|
@@ -2460,7 +3111,7 @@ function WidthHandle({ width, region, onDrag }) {
|
|
|
2460
3111
|
onDrag(null);
|
|
2461
3112
|
canvas.setWidth(next);
|
|
2462
3113
|
};
|
|
2463
|
-
return /* @__PURE__ */ (0,
|
|
3114
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
2464
3115
|
"div",
|
|
2465
3116
|
{
|
|
2466
3117
|
className: "mxc-width-handle",
|
|
@@ -2482,12 +3133,12 @@ function WidthHandle({ width, region, onDrag }) {
|
|
|
2482
3133
|
);
|
|
2483
3134
|
}
|
|
2484
3135
|
function LayoutNodeView({ node }) {
|
|
2485
|
-
if (node.type === "pane") return /* @__PURE__ */ (0,
|
|
2486
|
-
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 });
|
|
2487
3138
|
}
|
|
2488
3139
|
function useMeasuredWidth(ref, enabled) {
|
|
2489
|
-
const [width, setWidth] = (0,
|
|
2490
|
-
|
|
3140
|
+
const [width, setWidth] = (0, import_react9.useState)(0);
|
|
3141
|
+
useIsoLayoutEffect3(() => {
|
|
2491
3142
|
const el = ref.current;
|
|
2492
3143
|
if (!enabled || !el) return;
|
|
2493
3144
|
setWidth(el.getBoundingClientRect().width);
|
|
@@ -2500,8 +3151,8 @@ function useMeasuredWidth(ref, enabled) {
|
|
|
2500
3151
|
}
|
|
2501
3152
|
function SplitView({ node }) {
|
|
2502
3153
|
const canvas = useCanvas();
|
|
2503
|
-
const ref = (0,
|
|
2504
|
-
const [liveSizes, setLiveSizes] = (0,
|
|
3154
|
+
const ref = (0, import_react9.useRef)(null);
|
|
3155
|
+
const [liveSizes, setLiveSizes] = (0, import_react9.useState)(null);
|
|
2505
3156
|
const sizes = liveSizes ?? node.sizes;
|
|
2506
3157
|
const measuredWidth = useMeasuredWidth(ref, node.orientation === "horizontal" && canvas.rules.paneMinWidth > 0);
|
|
2507
3158
|
const stacked = node.orientation === "horizontal" && shouldStackSplit(measuredWidth, node.children.length, canvas.rules);
|
|
@@ -2541,11 +3192,11 @@ function SplitView({ node }) {
|
|
|
2541
3192
|
target.addEventListener("pointerup", up);
|
|
2542
3193
|
target.addEventListener("pointercancel", up);
|
|
2543
3194
|
};
|
|
2544
|
-
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)(
|
|
2545
3196
|
SplitChild,
|
|
2546
3197
|
{
|
|
2547
3198
|
size: sizes[i] ?? 1 / node.children.length,
|
|
2548
|
-
handle: i < node.children.length - 1 ? /* @__PURE__ */ (0,
|
|
3199
|
+
handle: i < node.children.length - 1 ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
2549
3200
|
"div",
|
|
2550
3201
|
{
|
|
2551
3202
|
className: "mxc-split-handle",
|
|
@@ -2555,14 +3206,14 @@ function SplitView({ node }) {
|
|
|
2555
3206
|
onDoubleClick: () => canvas.resizeSplit(node.id, node.children.map(() => 1 / node.children.length))
|
|
2556
3207
|
}
|
|
2557
3208
|
) : null,
|
|
2558
|
-
children: /* @__PURE__ */ (0,
|
|
3209
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(LayoutNodeView, { node: child })
|
|
2559
3210
|
},
|
|
2560
3211
|
child.type === "pane" ? child.paneId : child.id
|
|
2561
3212
|
)) });
|
|
2562
3213
|
}
|
|
2563
3214
|
function SplitChild({ size, handle, children }) {
|
|
2564
|
-
return /* @__PURE__ */ (0,
|
|
2565
|
-
/* @__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 }),
|
|
2566
3217
|
handle
|
|
2567
3218
|
] });
|
|
2568
3219
|
}
|
|
@@ -2576,26 +3227,61 @@ function CanvasToggle({
|
|
|
2576
3227
|
const count = useCanvasState((s) => selectCanvasWindowItemCount(s, windowId));
|
|
2577
3228
|
const hint = canvasShortcutHint("canvas.toggle", isMac2());
|
|
2578
3229
|
const Button = variant === "glass" ? import_tap_target.TapTargetButton : import_tap_target.TapTargetButtonTransparent;
|
|
2579
|
-
return /* @__PURE__ */ (0,
|
|
2580
|
-
/* @__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)(
|
|
2581
3232
|
Button,
|
|
2582
3233
|
{
|
|
2583
3234
|
ariaLabel: isOpen ? "Hide canvas" : "Show canvas",
|
|
2584
3235
|
tooltip: `${isOpen ? "Hide canvas" : "Show canvas"}${hint ? ` (${hint})` : ""}`,
|
|
2585
|
-
icon: /* @__PURE__ */ (0,
|
|
3236
|
+
icon: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(PanelRightIcon, {}),
|
|
2586
3237
|
onClick: () => controller.toggle()
|
|
2587
3238
|
}
|
|
2588
3239
|
),
|
|
2589
|
-
count > 0 && !isOpen ? /* @__PURE__ */ (0,
|
|
3240
|
+
count > 0 && !isOpen ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "mxc-toggle-badge", children: count }) : null
|
|
2590
3241
|
] });
|
|
2591
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
|
+
}
|
|
2592
3278
|
function CanvasFrame({ children, className }) {
|
|
2593
3279
|
const { windowId } = useCanvasInternals();
|
|
2594
3280
|
const isFullscreen = useCanvasState((s) => selectCanvasIsFullscreen(s, windowId));
|
|
2595
3281
|
const isOpen = useCanvasState((s) => selectCanvasIsOpen(s, windowId));
|
|
2596
|
-
return /* @__PURE__ */ (0,
|
|
2597
|
-
/* @__PURE__ */ (0,
|
|
2598
|
-
/* @__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, {})
|
|
2599
3285
|
] });
|
|
2600
3286
|
}
|
|
2601
3287
|
//# sourceMappingURL=react.cjs.map
|