@ai-matrx/canvas 0.8.4 → 0.9.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 +24 -1
- 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 +182 -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 +182 -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)),
|
|
@@ -2378,7 +2433,7 @@ function BrowserBar({ url, canGoBack, canGoForward, onBack, onForward, onReload,
|
|
|
2378
2433
|
"data-slot": "browser-address",
|
|
2379
2434
|
className: "mxc-address",
|
|
2380
2435
|
value: draft,
|
|
2381
|
-
placeholder: "Search or enter
|
|
2436
|
+
placeholder: "Search or enter website",
|
|
2382
2437
|
spellCheck: false,
|
|
2383
2438
|
onFocus: (event) => {
|
|
2384
2439
|
setEditing(true);
|
|
@@ -3215,9 +3270,36 @@ function PaneTitle({ item, kind }) {
|
|
|
3215
3270
|
}
|
|
3216
3271
|
var CANVAS_TAB_MIN_WIDTH = 120;
|
|
3217
3272
|
var TAB_GAP = 2;
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3273
|
+
var CANVAS_TAB_LIST_ROOM = 44;
|
|
3274
|
+
function fitCanvasTabWindow(units, current, width, previousStart = 0) {
|
|
3275
|
+
const n = units.length;
|
|
3276
|
+
const need = (i) => Math.max(1, units[i] ?? 1) * CANVAS_TAB_MIN_WIDTH;
|
|
3277
|
+
const all = units.reduce((sum, _, i) => sum + need(i), 0) + Math.max(0, n - 1) * TAB_GAP;
|
|
3278
|
+
if (n <= 1 || width <= 0 || all <= width) return { start: 0, end: n, squeezed: n === 1 && width > 0 && all > width };
|
|
3279
|
+
const room = width - CANVAS_TAB_LIST_ROOM;
|
|
3280
|
+
const at = Math.min(n - 1, Math.max(0, current));
|
|
3281
|
+
let start = Math.min(Math.max(0, previousStart), at);
|
|
3282
|
+
let used = 0;
|
|
3283
|
+
let end = start;
|
|
3284
|
+
while (end < n && used + need(end) + (end > start ? TAB_GAP : 0) <= room) {
|
|
3285
|
+
used += need(end) + (end > start ? TAB_GAP : 0);
|
|
3286
|
+
end += 1;
|
|
3287
|
+
}
|
|
3288
|
+
if (end <= at) {
|
|
3289
|
+
start = at;
|
|
3290
|
+
end = at + 1;
|
|
3291
|
+
used = need(at);
|
|
3292
|
+
while (start > 0 && used + TAB_GAP + need(start - 1) <= room) {
|
|
3293
|
+
start -= 1;
|
|
3294
|
+
used += TAB_GAP + need(start);
|
|
3295
|
+
}
|
|
3296
|
+
} else {
|
|
3297
|
+
while (start > 0 && used + TAB_GAP + need(start - 1) <= room) {
|
|
3298
|
+
start -= 1;
|
|
3299
|
+
used += TAB_GAP + need(start);
|
|
3300
|
+
}
|
|
3301
|
+
}
|
|
3302
|
+
return { start, end, squeezed: need(at) > room && end - start === 1 };
|
|
3221
3303
|
}
|
|
3222
3304
|
function planCanvasTabStrip(tabs, actives) {
|
|
3223
3305
|
const joined = actives ? tabs.filter((tab) => actives.get(tab.paneId) === tab.itemId) : [];
|
|
@@ -3252,9 +3334,12 @@ function TabStrip({
|
|
|
3252
3334
|
activeItemId,
|
|
3253
3335
|
actives
|
|
3254
3336
|
}) {
|
|
3337
|
+
const canvas = useCanvas();
|
|
3255
3338
|
const strip = (0, import_react9.useRef)(null);
|
|
3256
3339
|
const list = (0, import_react9.useRef)(null);
|
|
3257
3340
|
const [width, setWidth] = (0, import_react9.useState)(0);
|
|
3341
|
+
const windowStart = (0, import_react9.useRef)(0);
|
|
3342
|
+
const focusAfterMove = (0, import_react9.useRef)(false);
|
|
3258
3343
|
const read = (0, import_react9.useRef)(() => {
|
|
3259
3344
|
});
|
|
3260
3345
|
useIsoLayoutEffect4(() => {
|
|
@@ -3279,31 +3364,31 @@ function TabStrip({
|
|
|
3279
3364
|
useIsoLayoutEffect4(() => {
|
|
3280
3365
|
read.current();
|
|
3281
3366
|
}, [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
3367
|
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]);
|
|
3368
|
+
if (!focusAfterMove.current || !activeItemId) return;
|
|
3369
|
+
focusAfterMove.current = false;
|
|
3370
|
+
tabElement(list.current, activeItemId)?.focus();
|
|
3371
|
+
}, [activeItemId]);
|
|
3304
3372
|
const entries = planCanvasTabStrip(tabs, actives);
|
|
3305
|
-
const
|
|
3373
|
+
const entryTabs = (entry) => entry.type === "tab" ? [entry.tab] : entry.tabs;
|
|
3374
|
+
const current = Math.max(
|
|
3375
|
+
0,
|
|
3376
|
+
entries.findIndex((entry) => entryTabs(entry).some((tab) => tab.itemId === activeItemId))
|
|
3377
|
+
);
|
|
3378
|
+
const fit = fitCanvasTabWindow(
|
|
3379
|
+
entries.map((entry) => entryTabs(entry).length),
|
|
3380
|
+
current,
|
|
3381
|
+
width,
|
|
3382
|
+
windowStart.current
|
|
3383
|
+
);
|
|
3384
|
+
useIsoLayoutEffect4(() => {
|
|
3385
|
+
windowStart.current = fit.start;
|
|
3386
|
+
});
|
|
3387
|
+
const shown = entries.slice(fit.start, fit.end);
|
|
3388
|
+
const hidden = tabs.length - shown.reduce((sum, entry) => sum + entryTabs(entry).length, 0);
|
|
3389
|
+
const overflow = hidden > 0;
|
|
3306
3390
|
const joined = entries.some((entry) => entry.type === "split");
|
|
3391
|
+
const order = entries.flatMap(entryTabs).map((tab) => tab.itemId);
|
|
3307
3392
|
const isActive = (tab) => actives ? actives.get(tab.paneId) === tab.itemId : tab.itemId === activeItemId;
|
|
3308
3393
|
const tabView = (tab) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
3309
3394
|
ItemTab,
|
|
@@ -3316,6 +3401,22 @@ function TabStrip({
|
|
|
3316
3401
|
},
|
|
3317
3402
|
tab.itemId
|
|
3318
3403
|
);
|
|
3404
|
+
const onKeyDown = (event) => {
|
|
3405
|
+
const keys = ["ArrowLeft", "ArrowRight", "Home", "End"];
|
|
3406
|
+
if (!keys.includes(event.key) || event.altKey || event.ctrlKey || event.metaKey || order.length < 2) return;
|
|
3407
|
+
const from = event.target.closest("[data-mxc-tab]")?.dataset.mxcTab;
|
|
3408
|
+
const at = from ? order.indexOf(from) : -1;
|
|
3409
|
+
if (at < 0) return;
|
|
3410
|
+
event.preventDefault();
|
|
3411
|
+
const rtl = getComputedStyle(event.currentTarget).direction === "rtl";
|
|
3412
|
+
const step = event.key === "ArrowRight" !== rtl ? 1 : -1;
|
|
3413
|
+
const next = event.key === "Home" ? 0 : event.key === "End" ? order.length - 1 : (at + step + order.length) % order.length;
|
|
3414
|
+
const target = order[next];
|
|
3415
|
+
if (!target) return;
|
|
3416
|
+
focusAfterMove.current = true;
|
|
3417
|
+
canvas.activate(target);
|
|
3418
|
+
tabElement(list.current, target)?.focus();
|
|
3419
|
+
};
|
|
3319
3420
|
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
3320
3421
|
"div",
|
|
3321
3422
|
{
|
|
@@ -3332,8 +3433,9 @@ function TabStrip({
|
|
|
3332
3433
|
role: "tablist",
|
|
3333
3434
|
"aria-label": "Canvas tabs",
|
|
3334
3435
|
"aria-multiselectable": joined ? true : void 0,
|
|
3335
|
-
|
|
3336
|
-
|
|
3436
|
+
"data-squeezed": fit.squeezed ? "" : void 0,
|
|
3437
|
+
onKeyDown,
|
|
3438
|
+
children: shown.map(
|
|
3337
3439
|
(entry) => entry.type === "tab" ? tabView(entry.tab) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
3338
3440
|
"div",
|
|
3339
3441
|
{
|
|
@@ -3348,32 +3450,24 @@ function TabStrip({
|
|
|
3348
3450
|
)
|
|
3349
3451
|
}
|
|
3350
3452
|
),
|
|
3351
|
-
overflow ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(TabListMenu, { tabs, activeItemId }) : null
|
|
3453
|
+
overflow ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(TabListMenu, { tabs, activeItemId, hidden }) : null
|
|
3352
3454
|
]
|
|
3353
3455
|
}
|
|
3354
3456
|
);
|
|
3355
3457
|
}
|
|
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 }) {
|
|
3458
|
+
function tabElement(list, itemId) {
|
|
3459
|
+
return [...list?.querySelectorAll("[data-mxc-tab]") ?? []].find((node) => node.dataset.mxcTab === itemId);
|
|
3460
|
+
}
|
|
3461
|
+
function TabListMenu({ tabs, activeItemId, hidden }) {
|
|
3371
3462
|
const canvas = useCanvas();
|
|
3372
3463
|
const { ports, layer } = useCanvasInternals();
|
|
3373
3464
|
const blocking = (0, import_react9.useRef)(null);
|
|
3374
3465
|
(0, import_react9.useEffect)(() => () => blocking.current?.(), []);
|
|
3375
3466
|
const label = `All tabs (${tabs.length})`;
|
|
3376
|
-
const trigger = (props) => /* @__PURE__ */ (0, import_jsx_runtime7.
|
|
3467
|
+
const trigger = (props) => /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(CanvasHeaderButton, { className: "mxc-tab-list", label, ...props, children: [
|
|
3468
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "mxc-tab-list-count", "aria-hidden": "true", children: hidden }),
|
|
3469
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ChevronDownIcon, {})
|
|
3470
|
+
] });
|
|
3377
3471
|
const showMenu = ports.showMenu;
|
|
3378
3472
|
if (showMenu) {
|
|
3379
3473
|
return trigger({
|
|
@@ -3491,10 +3585,9 @@ function PaneMenu({
|
|
|
3491
3585
|
return [
|
|
3492
3586
|
[...kindItems, ...bodyItems, ...hostItems].map((entry) => ({ ...entry, id: `item:${entry.id}` })),
|
|
3493
3587
|
[
|
|
3494
|
-
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
] : [],
|
|
3588
|
+
// Only a split that leaves both panes usable (the layout rules' minimums) is offered.
|
|
3589
|
+
...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) }] : [],
|
|
3590
|
+
...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
3591
|
...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
3592
|
],
|
|
3500
3593
|
activeItem ? [
|
|
@@ -3603,6 +3696,15 @@ function CanvasColumn({ className, style, onLiveWidth, placement = "docked" }) {
|
|
|
3603
3696
|
const [slots] = (0, import_react10.useState)(createCanvasSlots);
|
|
3604
3697
|
const ref = (0, import_react10.useRef)(null);
|
|
3605
3698
|
useRegisterCanvasPresence();
|
|
3699
|
+
(0, import_react10.useEffect)(
|
|
3700
|
+
() => controller.registerPaneMeasure((paneId) => {
|
|
3701
|
+
const section = slots.section(paneId);
|
|
3702
|
+
if (!section?.isConnected) return null;
|
|
3703
|
+
const rect = section.getBoundingClientRect();
|
|
3704
|
+
return rect.width > 0 && rect.height > 0 ? { width: rect.width, height: rect.height } : null;
|
|
3705
|
+
}),
|
|
3706
|
+
[controller, slots]
|
|
3707
|
+
);
|
|
3606
3708
|
(0, import_react10.useEffect)(() => {
|
|
3607
3709
|
const el = ref.current;
|
|
3608
3710
|
if (!el) return void 0;
|
|
@@ -3724,28 +3826,42 @@ function LayoutNodeView({ node }) {
|
|
|
3724
3826
|
if (node.type === "pane") return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(CanvasPaneView, { paneId: node.paneId });
|
|
3725
3827
|
return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(SplitView, { node });
|
|
3726
3828
|
}
|
|
3727
|
-
function
|
|
3728
|
-
const [
|
|
3829
|
+
function useMeasuredSize(ref, enabled) {
|
|
3830
|
+
const [size, setSize] = (0, import_react10.useState)({ width: 0, height: 0 });
|
|
3729
3831
|
useIsoLayoutEffect5(() => {
|
|
3730
3832
|
const el = ref.current;
|
|
3731
3833
|
if (!enabled || !el) return;
|
|
3732
|
-
|
|
3834
|
+
const read = () => {
|
|
3835
|
+
const rect = el.getBoundingClientRect();
|
|
3836
|
+
setSize((prev) => prev.width === rect.width && prev.height === rect.height ? prev : { width: rect.width, height: rect.height });
|
|
3837
|
+
};
|
|
3838
|
+
read();
|
|
3733
3839
|
if (typeof ResizeObserver === "undefined") return;
|
|
3734
|
-
const observer = new ResizeObserver(
|
|
3840
|
+
const observer = new ResizeObserver(read);
|
|
3735
3841
|
observer.observe(el);
|
|
3736
3842
|
return () => observer.disconnect();
|
|
3737
3843
|
}, [enabled]);
|
|
3738
|
-
return
|
|
3844
|
+
return size;
|
|
3845
|
+
}
|
|
3846
|
+
function panesAlong(node, orientation) {
|
|
3847
|
+
if (node.type === "pane") return 1;
|
|
3848
|
+
const counts = node.children.map((child) => panesAlong(child, orientation));
|
|
3849
|
+
return node.orientation === orientation ? counts.reduce((a, b) => a + b, 0) : Math.max(...counts);
|
|
3850
|
+
}
|
|
3851
|
+
function childMinimums(node, orientation, rules) {
|
|
3852
|
+
const unit = orientation === "vertical" ? rules.paneMinHeight : rules.paneMinWidth;
|
|
3853
|
+
return node.children.map((child) => unit * panesAlong(child, orientation));
|
|
3739
3854
|
}
|
|
3740
3855
|
function SplitView({ node }) {
|
|
3741
3856
|
const canvas = useCanvas();
|
|
3742
3857
|
const ref = (0, import_react10.useRef)(null);
|
|
3743
3858
|
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);
|
|
3859
|
+
const measured = useMeasuredSize(ref, canvas.rules.paneMinWidth > 0 || canvas.rules.paneMinHeight > 0);
|
|
3860
|
+
const stacked = node.orientation === "horizontal" && shouldStackSplit(measured.width, node.children.length, canvas.rules);
|
|
3747
3861
|
const orientation = stacked ? "vertical" : node.orientation;
|
|
3748
3862
|
const horizontal = orientation === "horizontal";
|
|
3863
|
+
const mins = childMinimums(node, orientation, canvas.rules);
|
|
3864
|
+
const sizes = fitCanvasSplitSizes(liveSizes ?? node.sizes, horizontal ? measured.width : measured.height, mins);
|
|
3749
3865
|
const beginDrag = (index) => (event) => {
|
|
3750
3866
|
const el = ref.current;
|
|
3751
3867
|
if (!el) return;
|
|
@@ -3754,14 +3870,15 @@ function SplitView({ node }) {
|
|
|
3754
3870
|
const rect = el.getBoundingClientRect();
|
|
3755
3871
|
const total = horizontal ? rect.width : rect.height;
|
|
3756
3872
|
const origin = horizontal ? event.clientX : event.clientY;
|
|
3757
|
-
const base = [...
|
|
3873
|
+
const base = [...sizes];
|
|
3758
3874
|
const pair = (base[index] ?? 0) + (base[index + 1] ?? 0);
|
|
3875
|
+
const minFirst = Math.max(CANVAS_MIN_SPLIT_FRACTION, total > 0 ? (mins[index] ?? 0) / total : 0);
|
|
3876
|
+
const minSecond = Math.max(CANVAS_MIN_SPLIT_FRACTION, total > 0 ? (mins[index + 1] ?? 0) / total : 0);
|
|
3759
3877
|
const target = event.currentTarget;
|
|
3760
3878
|
const end = beginCanvasResize(horizontal ? "col" : "row");
|
|
3761
3879
|
const compute = (pos) => {
|
|
3762
3880
|
const delta = (pos - origin) / total;
|
|
3763
|
-
const
|
|
3764
|
-
const first = Math.min(pair - min, Math.max(min, (base[index] ?? 0) + delta));
|
|
3881
|
+
const first = Math.max(Math.min(pair - minSecond, Math.max(minFirst, (base[index] ?? 0) + delta)), Math.min(minFirst, pair / 2));
|
|
3765
3882
|
const next = [...base];
|
|
3766
3883
|
next[index] = first;
|
|
3767
3884
|
next[index + 1] = pair - first;
|