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