@ai-matrx/canvas 0.8.5 → 0.9.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 +22 -0
- package/README.md +5 -4
- package/dist/index.cjs +63 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +63 -5
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +197 -65
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +23 -7
- package/dist/react.d.ts +23 -7
- package/dist/react.js +197 -65
- package/dist/react.js.map +1 -1
- package/dist/styles.css +15 -10
- package/dist/{toggleKind-BoFnqhzq.d.cts → toggleKind-CI2nBjum.d.cts} +47 -2
- package/dist/{toggleKind-BoFnqhzq.d.ts → toggleKind-CI2nBjum.d.ts} +47 -2
- package/package.json +1 -1
package/dist/react.cjs
CHANGED
|
@@ -34,6 +34,7 @@ var react_exports = {};
|
|
|
34
34
|
__export(react_exports, {
|
|
35
35
|
BrowserBar: () => BrowserBar,
|
|
36
36
|
BrowserTabs: () => BrowserTabs,
|
|
37
|
+
CANVAS_TAB_LIST_ROOM: () => CANVAS_TAB_LIST_ROOM,
|
|
37
38
|
CANVAS_TAB_MIN_WIDTH: () => CANVAS_TAB_MIN_WIDTH,
|
|
38
39
|
CanvasColumn: () => CanvasColumn,
|
|
39
40
|
CanvasFrame: () => CanvasFrame,
|
|
@@ -50,8 +51,8 @@ __export(react_exports, {
|
|
|
50
51
|
CanvasWindowTabs: () => CanvasWindowTabs,
|
|
51
52
|
beginCanvasResize: () => beginCanvasResize,
|
|
52
53
|
canvasSessionKey: () => canvasSessionKey,
|
|
53
|
-
canvasTabsOverflow: () => canvasTabsOverflow,
|
|
54
54
|
defineCanvasKind: () => defineCanvasKind,
|
|
55
|
+
fitCanvasTabWindow: () => fitCanvasTabWindow,
|
|
55
56
|
getCanvasKind: () => getCanvasKind,
|
|
56
57
|
itemTitle: () => itemTitle,
|
|
57
58
|
listCanvasKinds: () => listCanvasKinds,
|
|
@@ -1055,6 +1056,7 @@ function toPersistableSnapshot(state, isRestorable) {
|
|
|
1055
1056
|
|
|
1056
1057
|
// src/core/rules.ts
|
|
1057
1058
|
var CANVAS_PANE_MIN_WIDTH = 280;
|
|
1059
|
+
var CANVAS_PANE_MIN_HEIGHT = 200;
|
|
1058
1060
|
var CANVAS_WEB_LAYOUT_RULES = {
|
|
1059
1061
|
defaultWidth: CANVAS_DEFAULT_WIDTH,
|
|
1060
1062
|
minWidth: CANVAS_MIN_WIDTH,
|
|
@@ -1068,7 +1070,10 @@ var CANVAS_WEB_LAYOUT_RULES = {
|
|
|
1068
1070
|
sideSplit: 0.5,
|
|
1069
1071
|
// A pane narrower than this cannot show a header (Back, a readable tab, "+", the menu, close) and a
|
|
1070
1072
|
// body side by side with another: a split right that would go under it stacks instead.
|
|
1071
|
-
paneMinWidth: CANVAS_PANE_MIN_WIDTH
|
|
1073
|
+
paneMinWidth: CANVAS_PANE_MIN_WIDTH,
|
|
1074
|
+
// 2026-10-07 live: Quick Notes / Notifications / Quick Chat stacked in an 800px window left
|
|
1075
|
+
// Notifications its header and one row.
|
|
1076
|
+
paneMinHeight: CANVAS_PANE_MIN_HEIGHT
|
|
1072
1077
|
};
|
|
1073
1078
|
var clampFraction = (value, fallback) => Number.isFinite(value) ? Math.min(0.88, Math.max(0.12, value)) : fallback;
|
|
1074
1079
|
function resolveCanvasLayoutRules(rules) {
|
|
@@ -1080,7 +1085,8 @@ function resolveCanvasLayoutRules(rules) {
|
|
|
1080
1085
|
centreMinWidth: positive(merged.centreMinWidth, CANVAS_WEB_LAYOUT_RULES.centreMinWidth),
|
|
1081
1086
|
stackSplit: clampFraction(merged.stackSplit, 0.5),
|
|
1082
1087
|
sideSplit: clampFraction(merged.sideSplit, 0.5),
|
|
1083
|
-
paneMinWidth: positive(merged.paneMinWidth, 0)
|
|
1088
|
+
paneMinWidth: positive(merged.paneMinWidth, 0),
|
|
1089
|
+
paneMinHeight: positive(merged.paneMinHeight, 0)
|
|
1084
1090
|
};
|
|
1085
1091
|
}
|
|
1086
1092
|
function defaultCanvasWidth(rules, region) {
|
|
@@ -1099,6 +1105,30 @@ function clampDraggedCanvasWidth(next, region, rules) {
|
|
|
1099
1105
|
function shouldStackSplit(width, children, rules) {
|
|
1100
1106
|
return rules.paneMinWidth > 0 && width > 0 && width < rules.paneMinWidth * children;
|
|
1101
1107
|
}
|
|
1108
|
+
function fitCanvasSplit(size, orientation, rules) {
|
|
1109
|
+
if (!size || size.width <= 0 || size.height <= 0) return orientation;
|
|
1110
|
+
const sideFits = rules.paneMinWidth <= 0 || size.width >= 2 * rules.paneMinWidth;
|
|
1111
|
+
const stackFits = rules.paneMinHeight <= 0 || size.height >= 2 * rules.paneMinHeight;
|
|
1112
|
+
if (orientation === "horizontal") return sideFits || stackFits ? "horizontal" : null;
|
|
1113
|
+
return stackFits ? "vertical" : sideFits ? "horizontal" : null;
|
|
1114
|
+
}
|
|
1115
|
+
function fitCanvasSplitSizes(sizes, total, mins) {
|
|
1116
|
+
if (!(total > 0) || sizes.length === 0) return [...sizes];
|
|
1117
|
+
const sum = sizes.reduce((a, b) => a + b, 0) || 1;
|
|
1118
|
+
const px = sizes.map((s) => s / sum * total);
|
|
1119
|
+
const floor = mins.map((m) => Number.isFinite(m) && m > 0 ? m : 0);
|
|
1120
|
+
const needed = floor.reduce((a, b) => a + b, 0);
|
|
1121
|
+
if (needed >= total) return needed > 0 ? floor.map((m) => m / needed) : px.map((p) => p / total);
|
|
1122
|
+
const short = px.reduce((acc, p, i) => acc + Math.max(0, (floor[i] ?? 0) - p), 0);
|
|
1123
|
+
if (short <= 0) return px.map((p) => p / total);
|
|
1124
|
+
const spare = px.map((p, i) => Math.max(0, p - (floor[i] ?? 0)));
|
|
1125
|
+
const spareSum = spare.reduce((a, b) => a + b, 0);
|
|
1126
|
+
return px.map((p, i) => {
|
|
1127
|
+
const min = floor[i] ?? 0;
|
|
1128
|
+
if (p <= min) return min / total;
|
|
1129
|
+
return (p - short * (spare[i] ?? 0) / spareSum) / total;
|
|
1130
|
+
});
|
|
1131
|
+
}
|
|
1102
1132
|
|
|
1103
1133
|
// src/core/commands.ts
|
|
1104
1134
|
var CANVAS_SHORTCUTS = [
|
|
@@ -1196,6 +1226,7 @@ function createCanvasController(options) {
|
|
|
1196
1226
|
options,
|
|
1197
1227
|
onError: options.onError ?? consoleCanvasErrorSink,
|
|
1198
1228
|
rules: resolveCanvasLayoutRules(options.layoutRules),
|
|
1229
|
+
measures: /* @__PURE__ */ new Set(),
|
|
1199
1230
|
presentations: /* @__PURE__ */ new Map(),
|
|
1200
1231
|
presentationListeners: /* @__PURE__ */ new Set(),
|
|
1201
1232
|
views: /* @__PURE__ */ new Map(),
|
|
@@ -1216,6 +1247,14 @@ function view(shared, windowId) {
|
|
|
1216
1247
|
for (const listener of [...shared.presentationListeners]) listener();
|
|
1217
1248
|
};
|
|
1218
1249
|
const shareFor = (orientation) => orientation === "vertical" ? rules.stackSplit : rules.sideSplit;
|
|
1250
|
+
const measurePane = (paneId) => {
|
|
1251
|
+
for (const measure of shared.measures) {
|
|
1252
|
+
const size = measure(paneId);
|
|
1253
|
+
if (size) return size;
|
|
1254
|
+
}
|
|
1255
|
+
return null;
|
|
1256
|
+
};
|
|
1257
|
+
const fitSplit = (paneId, orientation) => fitCanvasSplit(measurePane(paneId), orientation, rules);
|
|
1219
1258
|
const acceptable = (input) => {
|
|
1220
1259
|
if (input.kind !== CANVAS_LAUNCHER_KIND && options.isKnownKind && !options.isKnownKind(input.kind)) {
|
|
1221
1260
|
onError({ code: "unknown-kind", message: `No canvas kind "${input.kind}" is registered.`, detail: input });
|
|
@@ -1246,6 +1285,13 @@ function view(shared, windowId) {
|
|
|
1246
1285
|
if (holding) return { paneId: holding };
|
|
1247
1286
|
return state.panes[frame2.focusedPaneId]?.activeItemId ? preferred : "focused";
|
|
1248
1287
|
};
|
|
1288
|
+
const splitTargetFor = (asked, forWindow) => {
|
|
1289
|
+
const state = store.getState();
|
|
1290
|
+
const frame2 = canvasWindowFrame(state, forWindow ? canvasWindowId(forWindow) : windowId);
|
|
1291
|
+
if (!frame2) return asked;
|
|
1292
|
+
const fitted = fitSplit(frame2.focusedPaneId, asked === "split-down" ? "vertical" : "horizontal");
|
|
1293
|
+
return fitted === null ? "focused" : fitted === "vertical" ? "split-down" : "split-right";
|
|
1294
|
+
};
|
|
1249
1295
|
const controller = {
|
|
1250
1296
|
store,
|
|
1251
1297
|
windowId,
|
|
@@ -1254,7 +1300,8 @@ function view(shared, windowId) {
|
|
|
1254
1300
|
getState: () => store.getState(),
|
|
1255
1301
|
open(input) {
|
|
1256
1302
|
if (!acceptable(input)) return null;
|
|
1257
|
-
const
|
|
1303
|
+
const asked = input.target ?? preferredTargetFor(input);
|
|
1304
|
+
const target = asked === "split-down" || asked === "split-right" ? splitTargetFor(asked, input.windowId) : asked;
|
|
1258
1305
|
const splitShare = target === "split-down" ? rules.stackSplit : target === "split-right" ? rules.sideSplit : void 0;
|
|
1259
1306
|
const withTarget = input.target === target ? input : { ...input, target };
|
|
1260
1307
|
const resolved = withTarget.windowId || windowId === CANVAS_MAIN_WINDOW ? withTarget : { ...withTarget, windowId };
|
|
@@ -1319,7 +1366,15 @@ function view(shared, windowId) {
|
|
|
1319
1366
|
activate: (itemId) => dispatch(canvasActions.activate(itemId)),
|
|
1320
1367
|
focusPane: (paneId) => dispatch(canvasActions.focusPane(paneId)),
|
|
1321
1368
|
moveItem: (itemId, toPaneId, index) => dispatch(canvasActions.moveItem(itemId, toPaneId, index)),
|
|
1322
|
-
splitPane
|
|
1369
|
+
splitPane(paneId, orientation, moveItemId) {
|
|
1370
|
+
const fitted = fitSplit(paneId, orientation);
|
|
1371
|
+
if (fitted) dispatch(canvasActions.splitPane(paneId, fitted, moveItemId, shareFor(fitted)));
|
|
1372
|
+
},
|
|
1373
|
+
fitSplit,
|
|
1374
|
+
registerPaneMeasure(measure) {
|
|
1375
|
+
shared.measures.add(measure);
|
|
1376
|
+
return () => void shared.measures.delete(measure);
|
|
1377
|
+
},
|
|
1323
1378
|
closePane: (paneId) => dispatch(canvasActions.closePane(paneId)),
|
|
1324
1379
|
resizeSplit: (splitId, sizes) => dispatch(canvasActions.resizeSplit(splitId, sizes)),
|
|
1325
1380
|
show: (w = windowId) => dispatch(canvasActions.setOpen(true, w)),
|
|
@@ -2935,8 +2990,23 @@ var Rendered = (0, import_react8.memo)(function Rendered2({
|
|
|
2935
2990
|
presentation
|
|
2936
2991
|
}) {
|
|
2937
2992
|
const props = { item, data: item.data, paneId, isFocused, isVisible, canvas, windowId, presentation };
|
|
2938
|
-
|
|
2993
|
+
const [attempt, setAttempt] = (0, import_react8.useState)(0);
|
|
2994
|
+
const retry = () => {
|
|
2995
|
+
resetKindComponent(kind);
|
|
2996
|
+
setAttempt((n) => n + 1);
|
|
2997
|
+
};
|
|
2998
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ItemBoundary, { item, onClose: () => canvas.close(item.id), onRetry: retry, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react8.Suspense, { fallback: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(LoadingBody, { onRetry: retry, onClose: () => canvas.close(item.id) }), children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(KindRender, { kind, props }) }, attempt) });
|
|
2939
2999
|
});
|
|
3000
|
+
var CANVAS_BODY_STALL_MS = 2e4;
|
|
3001
|
+
function LoadingBody({ onRetry, onClose }) {
|
|
3002
|
+
const [stalled, setStalled] = (0, import_react8.useState)(false);
|
|
3003
|
+
(0, import_react8.useEffect)(() => {
|
|
3004
|
+
const timer = setTimeout(() => setStalled(true), CANVAS_BODY_STALL_MS);
|
|
3005
|
+
return () => clearTimeout(timer);
|
|
3006
|
+
}, []);
|
|
3007
|
+
if (!stalled) return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "mxc-loading", "aria-busy": "true" });
|
|
3008
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ItemProblem, { message: "This item is taking a long time to load.", onRetry, onClose });
|
|
3009
|
+
}
|
|
2940
3010
|
function KindRender({ kind, props }) {
|
|
2941
3011
|
const Render = kindComponent(kind);
|
|
2942
3012
|
if (!Render) throw new Error(`[@ai-matrx/canvas] kind "${kind.id}" has nothing to render.`);
|
|
@@ -3215,9 +3285,36 @@ function PaneTitle({ item, kind }) {
|
|
|
3215
3285
|
}
|
|
3216
3286
|
var CANVAS_TAB_MIN_WIDTH = 120;
|
|
3217
3287
|
var TAB_GAP = 2;
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3288
|
+
var CANVAS_TAB_LIST_ROOM = 44;
|
|
3289
|
+
function fitCanvasTabWindow(units, current, width, previousStart = 0) {
|
|
3290
|
+
const n = units.length;
|
|
3291
|
+
const need = (i) => Math.max(1, units[i] ?? 1) * CANVAS_TAB_MIN_WIDTH;
|
|
3292
|
+
const all = units.reduce((sum, _, i) => sum + need(i), 0) + Math.max(0, n - 1) * TAB_GAP;
|
|
3293
|
+
if (n <= 1 || width <= 0 || all <= width) return { start: 0, end: n, squeezed: n === 1 && width > 0 && all > width };
|
|
3294
|
+
const room = width - CANVAS_TAB_LIST_ROOM;
|
|
3295
|
+
const at = Math.min(n - 1, Math.max(0, current));
|
|
3296
|
+
let start = Math.min(Math.max(0, previousStart), at);
|
|
3297
|
+
let used = 0;
|
|
3298
|
+
let end = start;
|
|
3299
|
+
while (end < n && used + need(end) + (end > start ? TAB_GAP : 0) <= room) {
|
|
3300
|
+
used += need(end) + (end > start ? TAB_GAP : 0);
|
|
3301
|
+
end += 1;
|
|
3302
|
+
}
|
|
3303
|
+
if (end <= at) {
|
|
3304
|
+
start = at;
|
|
3305
|
+
end = at + 1;
|
|
3306
|
+
used = need(at);
|
|
3307
|
+
while (start > 0 && used + TAB_GAP + need(start - 1) <= room) {
|
|
3308
|
+
start -= 1;
|
|
3309
|
+
used += TAB_GAP + need(start);
|
|
3310
|
+
}
|
|
3311
|
+
} else {
|
|
3312
|
+
while (start > 0 && used + TAB_GAP + need(start - 1) <= room) {
|
|
3313
|
+
start -= 1;
|
|
3314
|
+
used += TAB_GAP + need(start);
|
|
3315
|
+
}
|
|
3316
|
+
}
|
|
3317
|
+
return { start, end, squeezed: need(at) > room && end - start === 1 };
|
|
3221
3318
|
}
|
|
3222
3319
|
function planCanvasTabStrip(tabs, actives) {
|
|
3223
3320
|
const joined = actives ? tabs.filter((tab) => actives.get(tab.paneId) === tab.itemId) : [];
|
|
@@ -3252,9 +3349,12 @@ function TabStrip({
|
|
|
3252
3349
|
activeItemId,
|
|
3253
3350
|
actives
|
|
3254
3351
|
}) {
|
|
3352
|
+
const canvas = useCanvas();
|
|
3255
3353
|
const strip = (0, import_react9.useRef)(null);
|
|
3256
3354
|
const list = (0, import_react9.useRef)(null);
|
|
3257
3355
|
const [width, setWidth] = (0, import_react9.useState)(0);
|
|
3356
|
+
const windowStart = (0, import_react9.useRef)(0);
|
|
3357
|
+
const focusAfterMove = (0, import_react9.useRef)(false);
|
|
3258
3358
|
const read = (0, import_react9.useRef)(() => {
|
|
3259
3359
|
});
|
|
3260
3360
|
useIsoLayoutEffect4(() => {
|
|
@@ -3279,31 +3379,31 @@ function TabStrip({
|
|
|
3279
3379
|
useIsoLayoutEffect4(() => {
|
|
3280
3380
|
read.current();
|
|
3281
3381
|
}, [tabs.length]);
|
|
3282
|
-
(0, import_react9.useEffect)(() => {
|
|
3283
|
-
const el = list.current;
|
|
3284
|
-
if (!el) return void 0;
|
|
3285
|
-
const onWheel = (event) => {
|
|
3286
|
-
if (Math.abs(event.deltaY) <= Math.abs(event.deltaX) || el.scrollWidth <= el.clientWidth) return;
|
|
3287
|
-
event.preventDefault();
|
|
3288
|
-
el.scrollLeft += event.deltaY;
|
|
3289
|
-
};
|
|
3290
|
-
el.addEventListener("wheel", onWheel, { passive: false });
|
|
3291
|
-
return () => el.removeEventListener("wheel", onWheel);
|
|
3292
|
-
}, []);
|
|
3293
3382
|
useIsoLayoutEffect4(() => {
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
const target = tab.closest(".mxc-tab-group") ?? tab;
|
|
3299
|
-
const box = el.getBoundingClientRect();
|
|
3300
|
-
const rect = target.getBoundingClientRect();
|
|
3301
|
-
if (rect.left < box.left) el.scrollLeft -= box.left - rect.left;
|
|
3302
|
-
else if (rect.right > box.right) el.scrollLeft += rect.right - box.right;
|
|
3303
|
-
}, [activeItemId, width, tabs.length]);
|
|
3383
|
+
if (!focusAfterMove.current || !activeItemId) return;
|
|
3384
|
+
focusAfterMove.current = false;
|
|
3385
|
+
tabElement(list.current, activeItemId)?.focus();
|
|
3386
|
+
}, [activeItemId]);
|
|
3304
3387
|
const entries = planCanvasTabStrip(tabs, actives);
|
|
3305
|
-
const
|
|
3388
|
+
const entryTabs = (entry) => entry.type === "tab" ? [entry.tab] : entry.tabs;
|
|
3389
|
+
const current = Math.max(
|
|
3390
|
+
0,
|
|
3391
|
+
entries.findIndex((entry) => entryTabs(entry).some((tab) => tab.itemId === activeItemId))
|
|
3392
|
+
);
|
|
3393
|
+
const fit = fitCanvasTabWindow(
|
|
3394
|
+
entries.map((entry) => entryTabs(entry).length),
|
|
3395
|
+
current,
|
|
3396
|
+
width,
|
|
3397
|
+
windowStart.current
|
|
3398
|
+
);
|
|
3399
|
+
useIsoLayoutEffect4(() => {
|
|
3400
|
+
windowStart.current = fit.start;
|
|
3401
|
+
});
|
|
3402
|
+
const shown = entries.slice(fit.start, fit.end);
|
|
3403
|
+
const hidden = tabs.length - shown.reduce((sum, entry) => sum + entryTabs(entry).length, 0);
|
|
3404
|
+
const overflow = hidden > 0;
|
|
3306
3405
|
const joined = entries.some((entry) => entry.type === "split");
|
|
3406
|
+
const order = entries.flatMap(entryTabs).map((tab) => tab.itemId);
|
|
3307
3407
|
const isActive = (tab) => actives ? actives.get(tab.paneId) === tab.itemId : tab.itemId === activeItemId;
|
|
3308
3408
|
const tabView = (tab) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
3309
3409
|
ItemTab,
|
|
@@ -3316,6 +3416,22 @@ function TabStrip({
|
|
|
3316
3416
|
},
|
|
3317
3417
|
tab.itemId
|
|
3318
3418
|
);
|
|
3419
|
+
const onKeyDown = (event) => {
|
|
3420
|
+
const keys = ["ArrowLeft", "ArrowRight", "Home", "End"];
|
|
3421
|
+
if (!keys.includes(event.key) || event.altKey || event.ctrlKey || event.metaKey || order.length < 2) return;
|
|
3422
|
+
const from = event.target.closest("[data-mxc-tab]")?.dataset.mxcTab;
|
|
3423
|
+
const at = from ? order.indexOf(from) : -1;
|
|
3424
|
+
if (at < 0) return;
|
|
3425
|
+
event.preventDefault();
|
|
3426
|
+
const rtl = getComputedStyle(event.currentTarget).direction === "rtl";
|
|
3427
|
+
const step = event.key === "ArrowRight" !== rtl ? 1 : -1;
|
|
3428
|
+
const next = event.key === "Home" ? 0 : event.key === "End" ? order.length - 1 : (at + step + order.length) % order.length;
|
|
3429
|
+
const target = order[next];
|
|
3430
|
+
if (!target) return;
|
|
3431
|
+
focusAfterMove.current = true;
|
|
3432
|
+
canvas.activate(target);
|
|
3433
|
+
tabElement(list.current, target)?.focus();
|
|
3434
|
+
};
|
|
3319
3435
|
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
3320
3436
|
"div",
|
|
3321
3437
|
{
|
|
@@ -3332,8 +3448,9 @@ function TabStrip({
|
|
|
3332
3448
|
role: "tablist",
|
|
3333
3449
|
"aria-label": "Canvas tabs",
|
|
3334
3450
|
"aria-multiselectable": joined ? true : void 0,
|
|
3335
|
-
|
|
3336
|
-
|
|
3451
|
+
"data-squeezed": fit.squeezed ? "" : void 0,
|
|
3452
|
+
onKeyDown,
|
|
3453
|
+
children: shown.map(
|
|
3337
3454
|
(entry) => entry.type === "tab" ? tabView(entry.tab) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
3338
3455
|
"div",
|
|
3339
3456
|
{
|
|
@@ -3348,32 +3465,24 @@ function TabStrip({
|
|
|
3348
3465
|
)
|
|
3349
3466
|
}
|
|
3350
3467
|
),
|
|
3351
|
-
overflow ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(TabListMenu, { tabs, activeItemId }) : null
|
|
3468
|
+
overflow ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(TabListMenu, { tabs, activeItemId, hidden }) : null
|
|
3352
3469
|
]
|
|
3353
3470
|
}
|
|
3354
3471
|
);
|
|
3355
3472
|
}
|
|
3356
|
-
function
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
const at = tabs.findIndex((tab) => tab === event.target || tab.contains(event.target));
|
|
3361
|
-
if (at < 0 || tabs.length < 2) return;
|
|
3362
|
-
event.preventDefault();
|
|
3363
|
-
const rtl = getComputedStyle(event.currentTarget).direction === "rtl";
|
|
3364
|
-
const step = event.key === "ArrowRight" !== rtl ? 1 : -1;
|
|
3365
|
-
const next = event.key === "Home" ? 0 : event.key === "End" ? tabs.length - 1 : (at + step + tabs.length) % tabs.length;
|
|
3366
|
-
const target = tabs[next];
|
|
3367
|
-
target?.focus();
|
|
3368
|
-
target?.click();
|
|
3369
|
-
}
|
|
3370
|
-
function TabListMenu({ tabs, activeItemId }) {
|
|
3473
|
+
function tabElement(list, itemId) {
|
|
3474
|
+
return [...list?.querySelectorAll("[data-mxc-tab]") ?? []].find((node) => node.dataset.mxcTab === itemId);
|
|
3475
|
+
}
|
|
3476
|
+
function TabListMenu({ tabs, activeItemId, hidden }) {
|
|
3371
3477
|
const canvas = useCanvas();
|
|
3372
3478
|
const { ports, layer } = useCanvasInternals();
|
|
3373
3479
|
const blocking = (0, import_react9.useRef)(null);
|
|
3374
3480
|
(0, import_react9.useEffect)(() => () => blocking.current?.(), []);
|
|
3375
3481
|
const label = `All tabs (${tabs.length})`;
|
|
3376
|
-
const trigger = (props) => /* @__PURE__ */ (0, import_jsx_runtime7.
|
|
3482
|
+
const trigger = (props) => /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(CanvasHeaderButton, { className: "mxc-tab-list", label, ...props, children: [
|
|
3483
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "mxc-tab-list-count", "aria-hidden": "true", children: hidden }),
|
|
3484
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ChevronDownIcon, {})
|
|
3485
|
+
] });
|
|
3377
3486
|
const showMenu = ports.showMenu;
|
|
3378
3487
|
if (showMenu) {
|
|
3379
3488
|
return trigger({
|
|
@@ -3491,10 +3600,9 @@ function PaneMenu({
|
|
|
3491
3600
|
return [
|
|
3492
3601
|
[...kindItems, ...bodyItems, ...hostItems].map((entry) => ({ ...entry, id: `item:${entry.id}` })),
|
|
3493
3602
|
[
|
|
3494
|
-
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
] : [],
|
|
3603
|
+
// Only a split that leaves both panes usable (the layout rules' minimums) is offered.
|
|
3604
|
+
...canSplit && canvas.fitSplit(paneId, "horizontal") === "horizontal" ? [{ id: "split-right", label: "Split right", icon: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(SplitRightIcon, {}), onSelect: () => canvas.splitPane(paneId, "horizontal", moveId) }] : [],
|
|
3605
|
+
...canSplit && canvas.fitSplit(paneId, "vertical") === "vertical" ? [{ id: "split-down", label: "Split down", icon: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(SplitDownIcon, {}), onSelect: () => canvas.splitPane(paneId, "vertical", moveId) }] : [],
|
|
3498
3606
|
...activeItem && role === "pop-out" ? [{ id: "dock-back", label: "Dock back", icon: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(DockIcon, {}), onSelect: () => void canvas.dockBack(activeItem.id) }] : activeItem && ports.popOut ? [{ id: "pop-out", label: "Pop out", icon: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(PopOutIcon, {}), onSelect: () => void canvas.popOut(activeItem.id) }] : []
|
|
3499
3607
|
],
|
|
3500
3608
|
activeItem ? [
|
|
@@ -3603,6 +3711,15 @@ function CanvasColumn({ className, style, onLiveWidth, placement = "docked" }) {
|
|
|
3603
3711
|
const [slots] = (0, import_react10.useState)(createCanvasSlots);
|
|
3604
3712
|
const ref = (0, import_react10.useRef)(null);
|
|
3605
3713
|
useRegisterCanvasPresence();
|
|
3714
|
+
(0, import_react10.useEffect)(
|
|
3715
|
+
() => controller.registerPaneMeasure((paneId) => {
|
|
3716
|
+
const section = slots.section(paneId);
|
|
3717
|
+
if (!section?.isConnected) return null;
|
|
3718
|
+
const rect = section.getBoundingClientRect();
|
|
3719
|
+
return rect.width > 0 && rect.height > 0 ? { width: rect.width, height: rect.height } : null;
|
|
3720
|
+
}),
|
|
3721
|
+
[controller, slots]
|
|
3722
|
+
);
|
|
3606
3723
|
(0, import_react10.useEffect)(() => {
|
|
3607
3724
|
const el = ref.current;
|
|
3608
3725
|
if (!el) return void 0;
|
|
@@ -3724,28 +3841,42 @@ function LayoutNodeView({ node }) {
|
|
|
3724
3841
|
if (node.type === "pane") return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(CanvasPaneView, { paneId: node.paneId });
|
|
3725
3842
|
return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(SplitView, { node });
|
|
3726
3843
|
}
|
|
3727
|
-
function
|
|
3728
|
-
const [
|
|
3844
|
+
function useMeasuredSize(ref, enabled) {
|
|
3845
|
+
const [size, setSize] = (0, import_react10.useState)({ width: 0, height: 0 });
|
|
3729
3846
|
useIsoLayoutEffect5(() => {
|
|
3730
3847
|
const el = ref.current;
|
|
3731
3848
|
if (!enabled || !el) return;
|
|
3732
|
-
|
|
3849
|
+
const read = () => {
|
|
3850
|
+
const rect = el.getBoundingClientRect();
|
|
3851
|
+
setSize((prev) => prev.width === rect.width && prev.height === rect.height ? prev : { width: rect.width, height: rect.height });
|
|
3852
|
+
};
|
|
3853
|
+
read();
|
|
3733
3854
|
if (typeof ResizeObserver === "undefined") return;
|
|
3734
|
-
const observer = new ResizeObserver(
|
|
3855
|
+
const observer = new ResizeObserver(read);
|
|
3735
3856
|
observer.observe(el);
|
|
3736
3857
|
return () => observer.disconnect();
|
|
3737
3858
|
}, [enabled]);
|
|
3738
|
-
return
|
|
3859
|
+
return size;
|
|
3860
|
+
}
|
|
3861
|
+
function panesAlong(node, orientation) {
|
|
3862
|
+
if (node.type === "pane") return 1;
|
|
3863
|
+
const counts = node.children.map((child) => panesAlong(child, orientation));
|
|
3864
|
+
return node.orientation === orientation ? counts.reduce((a, b) => a + b, 0) : Math.max(...counts);
|
|
3865
|
+
}
|
|
3866
|
+
function childMinimums(node, orientation, rules) {
|
|
3867
|
+
const unit = orientation === "vertical" ? rules.paneMinHeight : rules.paneMinWidth;
|
|
3868
|
+
return node.children.map((child) => unit * panesAlong(child, orientation));
|
|
3739
3869
|
}
|
|
3740
3870
|
function SplitView({ node }) {
|
|
3741
3871
|
const canvas = useCanvas();
|
|
3742
3872
|
const ref = (0, import_react10.useRef)(null);
|
|
3743
3873
|
const [liveSizes, setLiveSizes] = (0, import_react10.useState)(null);
|
|
3744
|
-
const
|
|
3745
|
-
const
|
|
3746
|
-
const stacked = node.orientation === "horizontal" && shouldStackSplit(measuredWidth, node.children.length, canvas.rules);
|
|
3874
|
+
const measured = useMeasuredSize(ref, canvas.rules.paneMinWidth > 0 || canvas.rules.paneMinHeight > 0);
|
|
3875
|
+
const stacked = node.orientation === "horizontal" && shouldStackSplit(measured.width, node.children.length, canvas.rules);
|
|
3747
3876
|
const orientation = stacked ? "vertical" : node.orientation;
|
|
3748
3877
|
const horizontal = orientation === "horizontal";
|
|
3878
|
+
const mins = childMinimums(node, orientation, canvas.rules);
|
|
3879
|
+
const sizes = fitCanvasSplitSizes(liveSizes ?? node.sizes, horizontal ? measured.width : measured.height, mins);
|
|
3749
3880
|
const beginDrag = (index) => (event) => {
|
|
3750
3881
|
const el = ref.current;
|
|
3751
3882
|
if (!el) return;
|
|
@@ -3754,14 +3885,15 @@ function SplitView({ node }) {
|
|
|
3754
3885
|
const rect = el.getBoundingClientRect();
|
|
3755
3886
|
const total = horizontal ? rect.width : rect.height;
|
|
3756
3887
|
const origin = horizontal ? event.clientX : event.clientY;
|
|
3757
|
-
const base = [...
|
|
3888
|
+
const base = [...sizes];
|
|
3758
3889
|
const pair = (base[index] ?? 0) + (base[index + 1] ?? 0);
|
|
3890
|
+
const minFirst = Math.max(CANVAS_MIN_SPLIT_FRACTION, total > 0 ? (mins[index] ?? 0) / total : 0);
|
|
3891
|
+
const minSecond = Math.max(CANVAS_MIN_SPLIT_FRACTION, total > 0 ? (mins[index + 1] ?? 0) / total : 0);
|
|
3759
3892
|
const target = event.currentTarget;
|
|
3760
3893
|
const end = beginCanvasResize(horizontal ? "col" : "row");
|
|
3761
3894
|
const compute = (pos) => {
|
|
3762
3895
|
const delta = (pos - origin) / total;
|
|
3763
|
-
const
|
|
3764
|
-
const first = Math.min(pair - min, Math.max(min, (base[index] ?? 0) + delta));
|
|
3896
|
+
const first = Math.max(Math.min(pair - minSecond, Math.max(minFirst, (base[index] ?? 0) + delta)), Math.min(minFirst, pair / 2));
|
|
3765
3897
|
const next = [...base];
|
|
3766
3898
|
next[index] = first;
|
|
3767
3899
|
next[index + 1] = pair - first;
|