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