@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/dist/react.d.cts CHANGED
@@ -1,6 +1,6 @@
1
1
  import * as react from 'react';
2
2
  import { ComponentType, ReactNode, CSSProperties, ButtonHTMLAttributes, HTMLAttributes, ReactElement } from 'react';
3
- import { b as CanvasJson, i as CanvasItem, d as CanvasPaneId, h as CanvasController, a as CanvasWindowId, O as CanvasPresentation, T as CanvasSessionEvent, R as CanvasSessionAttachReason, S as CanvasSessionDetachReason, N as CanvasPreferredTarget, C as CanvasItemId, Q as CanvasRect, e as CanvasStoreBinding, M as CanvasPersistencePort, E as CanvasErrorSink, H as CanvasLayoutRules, _ as ToggleKindInput, G as CanvasKindVisibility, f as CanvasState } from './toggleKind-BoFnqhzq.cjs';
3
+ import { b as CanvasJson, i as CanvasItem, d as CanvasPaneId, h as CanvasController, a as CanvasWindowId, Q as CanvasPresentation, V as CanvasSessionEvent, T as CanvasSessionAttachReason, U as CanvasSessionDetachReason, P as CanvasPreferredTarget, C as CanvasItemId, S as CanvasRect, e as CanvasStoreBinding, O as CanvasPersistencePort, F as CanvasErrorSink, I as CanvasLayoutRules, a0 as ToggleKindInput, H as CanvasKindVisibility, f as CanvasState } from './toggleKind-CI2nBjum.cjs';
4
4
 
5
5
  /**
6
6
  * THE canvas kind registry. A kind is one sort of thing the canvas can show —
@@ -392,13 +392,29 @@ declare function CanvasPaneView({ paneId }: {
392
392
  paneId: CanvasPaneId;
393
393
  }): react.JSX.Element | null;
394
394
  /**
395
- * Every tab's floor (px): icon + ~10 letters + "…" — still readable. Tabs shrink
396
- * evenly from 12rem down to this; past it the strip scrolls. Mirrored to CSS as
397
- * --mxc-tab-min.
395
+ * Every tab's floor (px): icon + ~10 letters + "…" — still readable. Tabs share
396
+ * the strip evenly from 12rem down to this; a tab that would go under it is not
397
+ * drawn (it waits in "All tabs"). Mirrored to CSS as --mxc-tab-min.
398
398
  */
399
399
  declare const CANVAS_TAB_MIN_WIDTH = 120;
400
- /** True when `count` tabs at their floor do not fit `width` px: the strip scrolls and offers "All tabs". */
401
- declare function canvasTabsOverflow(count: number, width: number): boolean;
400
+ /** Room kept for "All tabs" (its chevron and the count of tabs out of the strip) while it shows. */
401
+ declare const CANVAS_TAB_LIST_ROOM = 44;
402
+ /** The run of strip entries drawn whole: `start` (inclusive) to `end` (exclusive). */
403
+ interface CanvasTabWindow {
404
+ readonly start: number;
405
+ readonly end: number;
406
+ /** True when even the current entry alone is wider than the room: it is drawn under its floor (ellipsis). */
407
+ readonly squeezed: boolean;
408
+ }
409
+ /**
410
+ * Which strip entries are drawn: every one when they fit at their floor; else
411
+ * the run that fits WHOLE beside "All tabs" and holds the current entry, moved
412
+ * as little as possible from `previousStart` (so switching tabs does not
413
+ * reshuffle the strip). A tab is never drawn sliced at an edge — the rest wait
414
+ * in "All tabs". `units` is each entry's width in tabs (a split's joined tab
415
+ * counts its halves). An unmeasured strip (width 0) draws everything.
416
+ */
417
+ declare function fitCanvasTabWindow(units: readonly number[], current: number, width: number, previousStart?: number): CanvasTabWindow;
402
418
  /** One entry of a strip: a tab, or a split's visible tabs drawn as ONE joined tab (Chrome split view). */
403
419
  type CanvasStripEntry = {
404
420
  readonly type: "tab";
@@ -531,4 +547,4 @@ interface BrowserBarProps {
531
547
  /** A browser's back · forward · reload · address bar, for a kind's leading slot or its body. */
532
548
  declare function BrowserBar({ url, canGoBack, canGoForward, onBack, onForward, onReload, onNavigate, trailing, className }: BrowserBarProps): react.JSX.Element;
533
549
 
534
- export { type AnyCanvasKind, BrowserBar, type BrowserBarProps, type BrowserTab, BrowserTabs, type BrowserTabsProps, CANVAS_TAB_MIN_WIDTH, CanvasColumn, type CanvasColumnProps, type CanvasFloating, CanvasFrame, CanvasHeaderButton, type CanvasHeaderButtonProps, type CanvasHostPorts, type CanvasKind, type CanvasKindIcon, CanvasKindIcons, type CanvasKindProps, type CanvasLauncherEntry, type CanvasMenuEntry, type CanvasMenuItem, CanvasMenuTrigger, type CanvasNativeBodyFrame, type CanvasNativeBodyInfo, type CanvasNativeRect, type CanvasOverlayPort, CanvasPaneView, CanvasPanel, CanvasPanelEmpty, type CanvasPanelProps, CanvasProvider, type CanvasProviderProps, type CanvasShowMenu, type CanvasStripEntry, type CanvasStripTab, CanvasTab, type CanvasTabProps, type CanvasTabStripMode, CanvasToggle, CanvasWindowScope, CanvasWindowTabs, beginCanvasResize, canvasSessionKey, canvasTabsOverflow, defineCanvasKind, getCanvasKind, itemTitle, listCanvasKinds, planCanvasTabStrip, registerCanvasKind, registerCanvasKinds, useCanvas, useCanvasColumnWidth, useCanvasHostPorts, useCanvasIsPresented, useCanvasKind, useCanvasKindToggle, useCanvasKinds, useCanvasLauncherEntries, useCanvasLauncherEntry, useCanvasMenuItems, useCanvasNativeBodies, useCanvasPresentation, useCanvasState, useCanvasWindowId, useOptionalCanvas, useOptionalCanvasState };
550
+ export { type AnyCanvasKind, BrowserBar, type BrowserBarProps, type BrowserTab, BrowserTabs, type BrowserTabsProps, CANVAS_TAB_LIST_ROOM, CANVAS_TAB_MIN_WIDTH, CanvasColumn, type CanvasColumnProps, type CanvasFloating, CanvasFrame, CanvasHeaderButton, type CanvasHeaderButtonProps, type CanvasHostPorts, type CanvasKind, type CanvasKindIcon, CanvasKindIcons, type CanvasKindProps, type CanvasLauncherEntry, type CanvasMenuEntry, type CanvasMenuItem, CanvasMenuTrigger, type CanvasNativeBodyFrame, type CanvasNativeBodyInfo, type CanvasNativeRect, type CanvasOverlayPort, CanvasPaneView, CanvasPanel, CanvasPanelEmpty, type CanvasPanelProps, CanvasProvider, type CanvasProviderProps, type CanvasShowMenu, type CanvasStripEntry, type CanvasStripTab, CanvasTab, type CanvasTabProps, type CanvasTabStripMode, type CanvasTabWindow, CanvasToggle, CanvasWindowScope, CanvasWindowTabs, beginCanvasResize, canvasSessionKey, defineCanvasKind, fitCanvasTabWindow, getCanvasKind, itemTitle, listCanvasKinds, planCanvasTabStrip, registerCanvasKind, registerCanvasKinds, useCanvas, useCanvasColumnWidth, useCanvasHostPorts, useCanvasIsPresented, useCanvasKind, useCanvasKindToggle, useCanvasKinds, useCanvasLauncherEntries, useCanvasLauncherEntry, useCanvasMenuItems, useCanvasNativeBodies, useCanvasPresentation, useCanvasState, useCanvasWindowId, useOptionalCanvas, useOptionalCanvasState };
package/dist/react.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import * as react from 'react';
2
2
  import { ComponentType, ReactNode, CSSProperties, ButtonHTMLAttributes, HTMLAttributes, ReactElement } from 'react';
3
- import { b as CanvasJson, i as CanvasItem, d as CanvasPaneId, h as CanvasController, a as CanvasWindowId, O as CanvasPresentation, T as CanvasSessionEvent, R as CanvasSessionAttachReason, S as CanvasSessionDetachReason, N as CanvasPreferredTarget, C as CanvasItemId, Q as CanvasRect, e as CanvasStoreBinding, M as CanvasPersistencePort, E as CanvasErrorSink, H as CanvasLayoutRules, _ as ToggleKindInput, G as CanvasKindVisibility, f as CanvasState } from './toggleKind-BoFnqhzq.js';
3
+ import { b as CanvasJson, i as CanvasItem, d as CanvasPaneId, h as CanvasController, a as CanvasWindowId, Q as CanvasPresentation, V as CanvasSessionEvent, T as CanvasSessionAttachReason, U as CanvasSessionDetachReason, P as CanvasPreferredTarget, C as CanvasItemId, S as CanvasRect, e as CanvasStoreBinding, O as CanvasPersistencePort, F as CanvasErrorSink, I as CanvasLayoutRules, a0 as ToggleKindInput, H as CanvasKindVisibility, f as CanvasState } from './toggleKind-CI2nBjum.js';
4
4
 
5
5
  /**
6
6
  * THE canvas kind registry. A kind is one sort of thing the canvas can show —
@@ -392,13 +392,29 @@ declare function CanvasPaneView({ paneId }: {
392
392
  paneId: CanvasPaneId;
393
393
  }): react.JSX.Element | null;
394
394
  /**
395
- * Every tab's floor (px): icon + ~10 letters + "…" — still readable. Tabs shrink
396
- * evenly from 12rem down to this; past it the strip scrolls. Mirrored to CSS as
397
- * --mxc-tab-min.
395
+ * Every tab's floor (px): icon + ~10 letters + "…" — still readable. Tabs share
396
+ * the strip evenly from 12rem down to this; a tab that would go under it is not
397
+ * drawn (it waits in "All tabs"). Mirrored to CSS as --mxc-tab-min.
398
398
  */
399
399
  declare const CANVAS_TAB_MIN_WIDTH = 120;
400
- /** True when `count` tabs at their floor do not fit `width` px: the strip scrolls and offers "All tabs". */
401
- declare function canvasTabsOverflow(count: number, width: number): boolean;
400
+ /** Room kept for "All tabs" (its chevron and the count of tabs out of the strip) while it shows. */
401
+ declare const CANVAS_TAB_LIST_ROOM = 44;
402
+ /** The run of strip entries drawn whole: `start` (inclusive) to `end` (exclusive). */
403
+ interface CanvasTabWindow {
404
+ readonly start: number;
405
+ readonly end: number;
406
+ /** True when even the current entry alone is wider than the room: it is drawn under its floor (ellipsis). */
407
+ readonly squeezed: boolean;
408
+ }
409
+ /**
410
+ * Which strip entries are drawn: every one when they fit at their floor; else
411
+ * the run that fits WHOLE beside "All tabs" and holds the current entry, moved
412
+ * as little as possible from `previousStart` (so switching tabs does not
413
+ * reshuffle the strip). A tab is never drawn sliced at an edge — the rest wait
414
+ * in "All tabs". `units` is each entry's width in tabs (a split's joined tab
415
+ * counts its halves). An unmeasured strip (width 0) draws everything.
416
+ */
417
+ declare function fitCanvasTabWindow(units: readonly number[], current: number, width: number, previousStart?: number): CanvasTabWindow;
402
418
  /** One entry of a strip: a tab, or a split's visible tabs drawn as ONE joined tab (Chrome split view). */
403
419
  type CanvasStripEntry = {
404
420
  readonly type: "tab";
@@ -531,4 +547,4 @@ interface BrowserBarProps {
531
547
  /** A browser's back · forward · reload · address bar, for a kind's leading slot or its body. */
532
548
  declare function BrowserBar({ url, canGoBack, canGoForward, onBack, onForward, onReload, onNavigate, trailing, className }: BrowserBarProps): react.JSX.Element;
533
549
 
534
- export { type AnyCanvasKind, BrowserBar, type BrowserBarProps, type BrowserTab, BrowserTabs, type BrowserTabsProps, CANVAS_TAB_MIN_WIDTH, CanvasColumn, type CanvasColumnProps, type CanvasFloating, CanvasFrame, CanvasHeaderButton, type CanvasHeaderButtonProps, type CanvasHostPorts, type CanvasKind, type CanvasKindIcon, CanvasKindIcons, type CanvasKindProps, type CanvasLauncherEntry, type CanvasMenuEntry, type CanvasMenuItem, CanvasMenuTrigger, type CanvasNativeBodyFrame, type CanvasNativeBodyInfo, type CanvasNativeRect, type CanvasOverlayPort, CanvasPaneView, CanvasPanel, CanvasPanelEmpty, type CanvasPanelProps, CanvasProvider, type CanvasProviderProps, type CanvasShowMenu, type CanvasStripEntry, type CanvasStripTab, CanvasTab, type CanvasTabProps, type CanvasTabStripMode, CanvasToggle, CanvasWindowScope, CanvasWindowTabs, beginCanvasResize, canvasSessionKey, canvasTabsOverflow, defineCanvasKind, getCanvasKind, itemTitle, listCanvasKinds, planCanvasTabStrip, registerCanvasKind, registerCanvasKinds, useCanvas, useCanvasColumnWidth, useCanvasHostPorts, useCanvasIsPresented, useCanvasKind, useCanvasKindToggle, useCanvasKinds, useCanvasLauncherEntries, useCanvasLauncherEntry, useCanvasMenuItems, useCanvasNativeBodies, useCanvasPresentation, useCanvasState, useCanvasWindowId, useOptionalCanvas, useOptionalCanvasState };
550
+ export { type AnyCanvasKind, BrowserBar, type BrowserBarProps, type BrowserTab, BrowserTabs, type BrowserTabsProps, CANVAS_TAB_LIST_ROOM, CANVAS_TAB_MIN_WIDTH, CanvasColumn, type CanvasColumnProps, type CanvasFloating, CanvasFrame, CanvasHeaderButton, type CanvasHeaderButtonProps, type CanvasHostPorts, type CanvasKind, type CanvasKindIcon, CanvasKindIcons, type CanvasKindProps, type CanvasLauncherEntry, type CanvasMenuEntry, type CanvasMenuItem, CanvasMenuTrigger, type CanvasNativeBodyFrame, type CanvasNativeBodyInfo, type CanvasNativeRect, type CanvasOverlayPort, CanvasPaneView, CanvasPanel, CanvasPanelEmpty, type CanvasPanelProps, CanvasProvider, type CanvasProviderProps, type CanvasShowMenu, type CanvasStripEntry, type CanvasStripTab, CanvasTab, type CanvasTabProps, type CanvasTabStripMode, type CanvasTabWindow, CanvasToggle, CanvasWindowScope, CanvasWindowTabs, beginCanvasResize, canvasSessionKey, defineCanvasKind, fitCanvasTabWindow, getCanvasKind, itemTitle, listCanvasKinds, planCanvasTabStrip, registerCanvasKind, registerCanvasKinds, useCanvas, useCanvasColumnWidth, useCanvasHostPorts, useCanvasIsPresented, useCanvasKind, useCanvasKindToggle, useCanvasKinds, useCanvasLauncherEntries, useCanvasLauncherEntry, useCanvasMenuItems, useCanvasNativeBodies, useCanvasPresentation, useCanvasState, useCanvasWindowId, useOptionalCanvas, useOptionalCanvasState };
package/dist/react.js CHANGED
@@ -987,6 +987,7 @@ function toPersistableSnapshot(state, isRestorable) {
987
987
 
988
988
  // src/core/rules.ts
989
989
  var CANVAS_PANE_MIN_WIDTH = 280;
990
+ var CANVAS_PANE_MIN_HEIGHT = 200;
990
991
  var CANVAS_WEB_LAYOUT_RULES = {
991
992
  defaultWidth: CANVAS_DEFAULT_WIDTH,
992
993
  minWidth: CANVAS_MIN_WIDTH,
@@ -1000,7 +1001,10 @@ var CANVAS_WEB_LAYOUT_RULES = {
1000
1001
  sideSplit: 0.5,
1001
1002
  // A pane narrower than this cannot show a header (Back, a readable tab, "+", the menu, close) and a
1002
1003
  // body side by side with another: a split right that would go under it stacks instead.
1003
- paneMinWidth: CANVAS_PANE_MIN_WIDTH
1004
+ paneMinWidth: CANVAS_PANE_MIN_WIDTH,
1005
+ // 2026-10-07 live: Quick Notes / Notifications / Quick Chat stacked in an 800px window left
1006
+ // Notifications its header and one row.
1007
+ paneMinHeight: CANVAS_PANE_MIN_HEIGHT
1004
1008
  };
1005
1009
  var clampFraction = (value, fallback) => Number.isFinite(value) ? Math.min(0.88, Math.max(0.12, value)) : fallback;
1006
1010
  function resolveCanvasLayoutRules(rules) {
@@ -1012,7 +1016,8 @@ function resolveCanvasLayoutRules(rules) {
1012
1016
  centreMinWidth: positive(merged.centreMinWidth, CANVAS_WEB_LAYOUT_RULES.centreMinWidth),
1013
1017
  stackSplit: clampFraction(merged.stackSplit, 0.5),
1014
1018
  sideSplit: clampFraction(merged.sideSplit, 0.5),
1015
- paneMinWidth: positive(merged.paneMinWidth, 0)
1019
+ paneMinWidth: positive(merged.paneMinWidth, 0),
1020
+ paneMinHeight: positive(merged.paneMinHeight, 0)
1016
1021
  };
1017
1022
  }
1018
1023
  function defaultCanvasWidth(rules, region) {
@@ -1031,6 +1036,30 @@ function clampDraggedCanvasWidth(next, region, rules) {
1031
1036
  function shouldStackSplit(width, children, rules) {
1032
1037
  return rules.paneMinWidth > 0 && width > 0 && width < rules.paneMinWidth * children;
1033
1038
  }
1039
+ function fitCanvasSplit(size, orientation, rules) {
1040
+ if (!size || size.width <= 0 || size.height <= 0) return orientation;
1041
+ const sideFits = rules.paneMinWidth <= 0 || size.width >= 2 * rules.paneMinWidth;
1042
+ const stackFits = rules.paneMinHeight <= 0 || size.height >= 2 * rules.paneMinHeight;
1043
+ if (orientation === "horizontal") return sideFits || stackFits ? "horizontal" : null;
1044
+ return stackFits ? "vertical" : sideFits ? "horizontal" : null;
1045
+ }
1046
+ function fitCanvasSplitSizes(sizes, total, mins) {
1047
+ if (!(total > 0) || sizes.length === 0) return [...sizes];
1048
+ const sum = sizes.reduce((a, b) => a + b, 0) || 1;
1049
+ const px = sizes.map((s) => s / sum * total);
1050
+ const floor = mins.map((m) => Number.isFinite(m) && m > 0 ? m : 0);
1051
+ const needed = floor.reduce((a, b) => a + b, 0);
1052
+ if (needed >= total) return needed > 0 ? floor.map((m) => m / needed) : px.map((p) => p / total);
1053
+ const short = px.reduce((acc, p, i) => acc + Math.max(0, (floor[i] ?? 0) - p), 0);
1054
+ if (short <= 0) return px.map((p) => p / total);
1055
+ const spare = px.map((p, i) => Math.max(0, p - (floor[i] ?? 0)));
1056
+ const spareSum = spare.reduce((a, b) => a + b, 0);
1057
+ return px.map((p, i) => {
1058
+ const min = floor[i] ?? 0;
1059
+ if (p <= min) return min / total;
1060
+ return (p - short * (spare[i] ?? 0) / spareSum) / total;
1061
+ });
1062
+ }
1034
1063
 
1035
1064
  // src/core/commands.ts
1036
1065
  var CANVAS_SHORTCUTS = [
@@ -1128,6 +1157,7 @@ function createCanvasController(options) {
1128
1157
  options,
1129
1158
  onError: options.onError ?? consoleCanvasErrorSink,
1130
1159
  rules: resolveCanvasLayoutRules(options.layoutRules),
1160
+ measures: /* @__PURE__ */ new Set(),
1131
1161
  presentations: /* @__PURE__ */ new Map(),
1132
1162
  presentationListeners: /* @__PURE__ */ new Set(),
1133
1163
  views: /* @__PURE__ */ new Map(),
@@ -1148,6 +1178,14 @@ function view(shared, windowId) {
1148
1178
  for (const listener of [...shared.presentationListeners]) listener();
1149
1179
  };
1150
1180
  const shareFor = (orientation) => orientation === "vertical" ? rules.stackSplit : rules.sideSplit;
1181
+ const measurePane = (paneId) => {
1182
+ for (const measure of shared.measures) {
1183
+ const size = measure(paneId);
1184
+ if (size) return size;
1185
+ }
1186
+ return null;
1187
+ };
1188
+ const fitSplit = (paneId, orientation) => fitCanvasSplit(measurePane(paneId), orientation, rules);
1151
1189
  const acceptable = (input) => {
1152
1190
  if (input.kind !== CANVAS_LAUNCHER_KIND && options.isKnownKind && !options.isKnownKind(input.kind)) {
1153
1191
  onError({ code: "unknown-kind", message: `No canvas kind "${input.kind}" is registered.`, detail: input });
@@ -1178,6 +1216,13 @@ function view(shared, windowId) {
1178
1216
  if (holding) return { paneId: holding };
1179
1217
  return state.panes[frame2.focusedPaneId]?.activeItemId ? preferred : "focused";
1180
1218
  };
1219
+ const splitTargetFor = (asked, forWindow) => {
1220
+ const state = store.getState();
1221
+ const frame2 = canvasWindowFrame(state, forWindow ? canvasWindowId(forWindow) : windowId);
1222
+ if (!frame2) return asked;
1223
+ const fitted = fitSplit(frame2.focusedPaneId, asked === "split-down" ? "vertical" : "horizontal");
1224
+ return fitted === null ? "focused" : fitted === "vertical" ? "split-down" : "split-right";
1225
+ };
1181
1226
  const controller = {
1182
1227
  store,
1183
1228
  windowId,
@@ -1186,7 +1231,8 @@ function view(shared, windowId) {
1186
1231
  getState: () => store.getState(),
1187
1232
  open(input) {
1188
1233
  if (!acceptable(input)) return null;
1189
- const target = input.target ?? preferredTargetFor(input);
1234
+ const asked = input.target ?? preferredTargetFor(input);
1235
+ const target = asked === "split-down" || asked === "split-right" ? splitTargetFor(asked, input.windowId) : asked;
1190
1236
  const splitShare = target === "split-down" ? rules.stackSplit : target === "split-right" ? rules.sideSplit : void 0;
1191
1237
  const withTarget = input.target === target ? input : { ...input, target };
1192
1238
  const resolved = withTarget.windowId || windowId === CANVAS_MAIN_WINDOW ? withTarget : { ...withTarget, windowId };
@@ -1251,7 +1297,15 @@ function view(shared, windowId) {
1251
1297
  activate: (itemId) => dispatch(canvasActions.activate(itemId)),
1252
1298
  focusPane: (paneId) => dispatch(canvasActions.focusPane(paneId)),
1253
1299
  moveItem: (itemId, toPaneId, index) => dispatch(canvasActions.moveItem(itemId, toPaneId, index)),
1254
- splitPane: (paneId, orientation, moveItemId) => dispatch(canvasActions.splitPane(paneId, orientation, moveItemId, shareFor(orientation))),
1300
+ splitPane(paneId, orientation, moveItemId) {
1301
+ const fitted = fitSplit(paneId, orientation);
1302
+ if (fitted) dispatch(canvasActions.splitPane(paneId, fitted, moveItemId, shareFor(fitted)));
1303
+ },
1304
+ fitSplit,
1305
+ registerPaneMeasure(measure) {
1306
+ shared.measures.add(measure);
1307
+ return () => void shared.measures.delete(measure);
1308
+ },
1255
1309
  closePane: (paneId) => dispatch(canvasActions.closePane(paneId)),
1256
1310
  resizeSplit: (splitId, sizes) => dispatch(canvasActions.resizeSplit(splitId, sizes)),
1257
1311
  show: (w = windowId) => dispatch(canvasActions.setOpen(true, w)),
@@ -2332,7 +2386,7 @@ function BrowserBar({ url, canGoBack, canGoForward, onBack, onForward, onReload,
2332
2386
  "data-slot": "browser-address",
2333
2387
  className: "mxc-address",
2334
2388
  value: draft,
2335
- placeholder: "Search or enter an address",
2389
+ placeholder: "Search or enter website",
2336
2390
  spellCheck: false,
2337
2391
  onFocus: (event) => {
2338
2392
  setEditing(true);
@@ -3169,9 +3223,36 @@ function PaneTitle({ item, kind }) {
3169
3223
  }
3170
3224
  var CANVAS_TAB_MIN_WIDTH = 120;
3171
3225
  var TAB_GAP = 2;
3172
- function canvasTabsOverflow(count, width) {
3173
- if (count <= 1 || width <= 0) return false;
3174
- return count * CANVAS_TAB_MIN_WIDTH + (count - 1) * TAB_GAP > width;
3226
+ var CANVAS_TAB_LIST_ROOM = 44;
3227
+ function fitCanvasTabWindow(units, current, width, previousStart = 0) {
3228
+ const n = units.length;
3229
+ const need = (i) => Math.max(1, units[i] ?? 1) * CANVAS_TAB_MIN_WIDTH;
3230
+ const all = units.reduce((sum, _, i) => sum + need(i), 0) + Math.max(0, n - 1) * TAB_GAP;
3231
+ if (n <= 1 || width <= 0 || all <= width) return { start: 0, end: n, squeezed: n === 1 && width > 0 && all > width };
3232
+ const room = width - CANVAS_TAB_LIST_ROOM;
3233
+ const at = Math.min(n - 1, Math.max(0, current));
3234
+ let start = Math.min(Math.max(0, previousStart), at);
3235
+ let used = 0;
3236
+ let end = start;
3237
+ while (end < n && used + need(end) + (end > start ? TAB_GAP : 0) <= room) {
3238
+ used += need(end) + (end > start ? TAB_GAP : 0);
3239
+ end += 1;
3240
+ }
3241
+ if (end <= at) {
3242
+ start = at;
3243
+ end = at + 1;
3244
+ used = need(at);
3245
+ while (start > 0 && used + TAB_GAP + need(start - 1) <= room) {
3246
+ start -= 1;
3247
+ used += TAB_GAP + need(start);
3248
+ }
3249
+ } else {
3250
+ while (start > 0 && used + TAB_GAP + need(start - 1) <= room) {
3251
+ start -= 1;
3252
+ used += TAB_GAP + need(start);
3253
+ }
3254
+ }
3255
+ return { start, end, squeezed: need(at) > room && end - start === 1 };
3175
3256
  }
3176
3257
  function planCanvasTabStrip(tabs, actives) {
3177
3258
  const joined = actives ? tabs.filter((tab) => actives.get(tab.paneId) === tab.itemId) : [];
@@ -3206,9 +3287,12 @@ function TabStrip({
3206
3287
  activeItemId,
3207
3288
  actives
3208
3289
  }) {
3290
+ const canvas = useCanvas();
3209
3291
  const strip = useRef7(null);
3210
3292
  const list = useRef7(null);
3211
3293
  const [width, setWidth] = useState5(0);
3294
+ const windowStart = useRef7(0);
3295
+ const focusAfterMove = useRef7(false);
3212
3296
  const read = useRef7(() => {
3213
3297
  });
3214
3298
  useIsoLayoutEffect4(() => {
@@ -3233,31 +3317,31 @@ function TabStrip({
3233
3317
  useIsoLayoutEffect4(() => {
3234
3318
  read.current();
3235
3319
  }, [tabs.length]);
3236
- useEffect7(() => {
3237
- const el = list.current;
3238
- if (!el) return void 0;
3239
- const onWheel = (event) => {
3240
- if (Math.abs(event.deltaY) <= Math.abs(event.deltaX) || el.scrollWidth <= el.clientWidth) return;
3241
- event.preventDefault();
3242
- el.scrollLeft += event.deltaY;
3243
- };
3244
- el.addEventListener("wheel", onWheel, { passive: false });
3245
- return () => el.removeEventListener("wheel", onWheel);
3246
- }, []);
3247
3320
  useIsoLayoutEffect4(() => {
3248
- const el = list.current;
3249
- if (!el || !activeItemId) return;
3250
- const tab = [...el.querySelectorAll("[data-mxc-tab]")].find((node) => node.dataset.mxcTab === activeItemId);
3251
- if (!tab) return;
3252
- const target = tab.closest(".mxc-tab-group") ?? tab;
3253
- const box = el.getBoundingClientRect();
3254
- const rect = target.getBoundingClientRect();
3255
- if (rect.left < box.left) el.scrollLeft -= box.left - rect.left;
3256
- else if (rect.right > box.right) el.scrollLeft += rect.right - box.right;
3257
- }, [activeItemId, width, tabs.length]);
3321
+ if (!focusAfterMove.current || !activeItemId) return;
3322
+ focusAfterMove.current = false;
3323
+ tabElement(list.current, activeItemId)?.focus();
3324
+ }, [activeItemId]);
3258
3325
  const entries = planCanvasTabStrip(tabs, actives);
3259
- const overflow = canvasTabsOverflow(tabs.length, width);
3326
+ const entryTabs = (entry) => entry.type === "tab" ? [entry.tab] : entry.tabs;
3327
+ const current = Math.max(
3328
+ 0,
3329
+ entries.findIndex((entry) => entryTabs(entry).some((tab) => tab.itemId === activeItemId))
3330
+ );
3331
+ const fit = fitCanvasTabWindow(
3332
+ entries.map((entry) => entryTabs(entry).length),
3333
+ current,
3334
+ width,
3335
+ windowStart.current
3336
+ );
3337
+ useIsoLayoutEffect4(() => {
3338
+ windowStart.current = fit.start;
3339
+ });
3340
+ const shown = entries.slice(fit.start, fit.end);
3341
+ const hidden = tabs.length - shown.reduce((sum, entry) => sum + entryTabs(entry).length, 0);
3342
+ const overflow = hidden > 0;
3260
3343
  const joined = entries.some((entry) => entry.type === "split");
3344
+ const order = entries.flatMap(entryTabs).map((tab) => tab.itemId);
3261
3345
  const isActive = (tab) => actives ? actives.get(tab.paneId) === tab.itemId : tab.itemId === activeItemId;
3262
3346
  const tabView = (tab) => /* @__PURE__ */ jsx7(
3263
3347
  ItemTab,
@@ -3270,6 +3354,22 @@ function TabStrip({
3270
3354
  },
3271
3355
  tab.itemId
3272
3356
  );
3357
+ const onKeyDown = (event) => {
3358
+ const keys = ["ArrowLeft", "ArrowRight", "Home", "End"];
3359
+ if (!keys.includes(event.key) || event.altKey || event.ctrlKey || event.metaKey || order.length < 2) return;
3360
+ const from = event.target.closest("[data-mxc-tab]")?.dataset.mxcTab;
3361
+ const at = from ? order.indexOf(from) : -1;
3362
+ if (at < 0) return;
3363
+ event.preventDefault();
3364
+ const rtl = getComputedStyle(event.currentTarget).direction === "rtl";
3365
+ const step = event.key === "ArrowRight" !== rtl ? 1 : -1;
3366
+ const next = event.key === "Home" ? 0 : event.key === "End" ? order.length - 1 : (at + step + order.length) % order.length;
3367
+ const target = order[next];
3368
+ if (!target) return;
3369
+ focusAfterMove.current = true;
3370
+ canvas.activate(target);
3371
+ tabElement(list.current, target)?.focus();
3372
+ };
3273
3373
  return /* @__PURE__ */ jsxs6(
3274
3374
  "div",
3275
3375
  {
@@ -3286,8 +3386,9 @@ function TabStrip({
3286
3386
  role: "tablist",
3287
3387
  "aria-label": "Canvas tabs",
3288
3388
  "aria-multiselectable": joined ? true : void 0,
3289
- onKeyDown: moveTabFocus,
3290
- children: entries.map(
3389
+ "data-squeezed": fit.squeezed ? "" : void 0,
3390
+ onKeyDown,
3391
+ children: shown.map(
3291
3392
  (entry) => entry.type === "tab" ? tabView(entry.tab) : /* @__PURE__ */ jsx7(
3292
3393
  "div",
3293
3394
  {
@@ -3302,32 +3403,24 @@ function TabStrip({
3302
3403
  )
3303
3404
  }
3304
3405
  ),
3305
- overflow ? /* @__PURE__ */ jsx7(TabListMenu, { tabs, activeItemId }) : null
3406
+ overflow ? /* @__PURE__ */ jsx7(TabListMenu, { tabs, activeItemId, hidden }) : null
3306
3407
  ]
3307
3408
  }
3308
3409
  );
3309
3410
  }
3310
- function moveTabFocus(event) {
3311
- const keys = ["ArrowLeft", "ArrowRight", "Home", "End"];
3312
- if (!keys.includes(event.key) || event.altKey || event.ctrlKey || event.metaKey) return;
3313
- const tabs = [...event.currentTarget.querySelectorAll('[role="tab"]')];
3314
- const at = tabs.findIndex((tab) => tab === event.target || tab.contains(event.target));
3315
- if (at < 0 || tabs.length < 2) return;
3316
- event.preventDefault();
3317
- const rtl = getComputedStyle(event.currentTarget).direction === "rtl";
3318
- const step = event.key === "ArrowRight" !== rtl ? 1 : -1;
3319
- const next = event.key === "Home" ? 0 : event.key === "End" ? tabs.length - 1 : (at + step + tabs.length) % tabs.length;
3320
- const target = tabs[next];
3321
- target?.focus();
3322
- target?.click();
3323
- }
3324
- function TabListMenu({ tabs, activeItemId }) {
3411
+ function tabElement(list, itemId) {
3412
+ return [...list?.querySelectorAll("[data-mxc-tab]") ?? []].find((node) => node.dataset.mxcTab === itemId);
3413
+ }
3414
+ function TabListMenu({ tabs, activeItemId, hidden }) {
3325
3415
  const canvas = useCanvas();
3326
3416
  const { ports, layer } = useCanvasInternals();
3327
3417
  const blocking = useRef7(null);
3328
3418
  useEffect7(() => () => blocking.current?.(), []);
3329
3419
  const label = `All tabs (${tabs.length})`;
3330
- const trigger = (props) => /* @__PURE__ */ jsx7(CanvasHeaderButton, { className: "mxc-tab-list", label, ...props, children: /* @__PURE__ */ jsx7(ChevronDownIcon, {}) });
3420
+ const trigger = (props) => /* @__PURE__ */ jsxs6(CanvasHeaderButton, { className: "mxc-tab-list", label, ...props, children: [
3421
+ /* @__PURE__ */ jsx7("span", { className: "mxc-tab-list-count", "aria-hidden": "true", children: hidden }),
3422
+ /* @__PURE__ */ jsx7(ChevronDownIcon, {})
3423
+ ] });
3331
3424
  const showMenu = ports.showMenu;
3332
3425
  if (showMenu) {
3333
3426
  return trigger({
@@ -3445,10 +3538,9 @@ function PaneMenu({
3445
3538
  return [
3446
3539
  [...kindItems, ...bodyItems, ...hostItems].map((entry) => ({ ...entry, id: `item:${entry.id}` })),
3447
3540
  [
3448
- ...canSplit ? [
3449
- { id: "split-right", label: "Split right", icon: /* @__PURE__ */ jsx7(SplitRightIcon, {}), onSelect: () => canvas.splitPane(paneId, "horizontal", moveId) },
3450
- { id: "split-down", label: "Split down", icon: /* @__PURE__ */ jsx7(SplitDownIcon, {}), onSelect: () => canvas.splitPane(paneId, "vertical", moveId) }
3451
- ] : [],
3541
+ // Only a split that leaves both panes usable (the layout rules' minimums) is offered.
3542
+ ...canSplit && canvas.fitSplit(paneId, "horizontal") === "horizontal" ? [{ id: "split-right", label: "Split right", icon: /* @__PURE__ */ jsx7(SplitRightIcon, {}), onSelect: () => canvas.splitPane(paneId, "horizontal", moveId) }] : [],
3543
+ ...canSplit && canvas.fitSplit(paneId, "vertical") === "vertical" ? [{ id: "split-down", label: "Split down", icon: /* @__PURE__ */ jsx7(SplitDownIcon, {}), onSelect: () => canvas.splitPane(paneId, "vertical", moveId) }] : [],
3452
3544
  ...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) }] : []
3453
3545
  ],
3454
3546
  activeItem ? [
@@ -3557,6 +3649,15 @@ function CanvasColumn({ className, style, onLiveWidth, placement = "docked" }) {
3557
3649
  const [slots] = useState6(createCanvasSlots);
3558
3650
  const ref = useRef8(null);
3559
3651
  useRegisterCanvasPresence();
3652
+ useEffect8(
3653
+ () => controller.registerPaneMeasure((paneId) => {
3654
+ const section = slots.section(paneId);
3655
+ if (!section?.isConnected) return null;
3656
+ const rect = section.getBoundingClientRect();
3657
+ return rect.width > 0 && rect.height > 0 ? { width: rect.width, height: rect.height } : null;
3658
+ }),
3659
+ [controller, slots]
3660
+ );
3560
3661
  useEffect8(() => {
3561
3662
  const el = ref.current;
3562
3663
  if (!el) return void 0;
@@ -3678,28 +3779,42 @@ function LayoutNodeView({ node }) {
3678
3779
  if (node.type === "pane") return /* @__PURE__ */ jsx8(CanvasPaneView, { paneId: node.paneId });
3679
3780
  return /* @__PURE__ */ jsx8(SplitView, { node });
3680
3781
  }
3681
- function useMeasuredWidth(ref, enabled) {
3682
- const [width, setWidth] = useState6(0);
3782
+ function useMeasuredSize(ref, enabled) {
3783
+ const [size, setSize] = useState6({ width: 0, height: 0 });
3683
3784
  useIsoLayoutEffect5(() => {
3684
3785
  const el = ref.current;
3685
3786
  if (!enabled || !el) return;
3686
- setWidth(el.getBoundingClientRect().width);
3787
+ const read = () => {
3788
+ const rect = el.getBoundingClientRect();
3789
+ setSize((prev) => prev.width === rect.width && prev.height === rect.height ? prev : { width: rect.width, height: rect.height });
3790
+ };
3791
+ read();
3687
3792
  if (typeof ResizeObserver === "undefined") return;
3688
- const observer = new ResizeObserver(() => setWidth(el.getBoundingClientRect().width));
3793
+ const observer = new ResizeObserver(read);
3689
3794
  observer.observe(el);
3690
3795
  return () => observer.disconnect();
3691
3796
  }, [enabled]);
3692
- return width;
3797
+ return size;
3798
+ }
3799
+ function panesAlong(node, orientation) {
3800
+ if (node.type === "pane") return 1;
3801
+ const counts = node.children.map((child) => panesAlong(child, orientation));
3802
+ return node.orientation === orientation ? counts.reduce((a, b) => a + b, 0) : Math.max(...counts);
3803
+ }
3804
+ function childMinimums(node, orientation, rules) {
3805
+ const unit = orientation === "vertical" ? rules.paneMinHeight : rules.paneMinWidth;
3806
+ return node.children.map((child) => unit * panesAlong(child, orientation));
3693
3807
  }
3694
3808
  function SplitView({ node }) {
3695
3809
  const canvas = useCanvas();
3696
3810
  const ref = useRef8(null);
3697
3811
  const [liveSizes, setLiveSizes] = useState6(null);
3698
- const sizes = liveSizes ?? node.sizes;
3699
- const measuredWidth = useMeasuredWidth(ref, node.orientation === "horizontal" && canvas.rules.paneMinWidth > 0);
3700
- const stacked = node.orientation === "horizontal" && shouldStackSplit(measuredWidth, node.children.length, canvas.rules);
3812
+ const measured = useMeasuredSize(ref, canvas.rules.paneMinWidth > 0 || canvas.rules.paneMinHeight > 0);
3813
+ const stacked = node.orientation === "horizontal" && shouldStackSplit(measured.width, node.children.length, canvas.rules);
3701
3814
  const orientation = stacked ? "vertical" : node.orientation;
3702
3815
  const horizontal = orientation === "horizontal";
3816
+ const mins = childMinimums(node, orientation, canvas.rules);
3817
+ const sizes = fitCanvasSplitSizes(liveSizes ?? node.sizes, horizontal ? measured.width : measured.height, mins);
3703
3818
  const beginDrag = (index) => (event) => {
3704
3819
  const el = ref.current;
3705
3820
  if (!el) return;
@@ -3708,14 +3823,15 @@ function SplitView({ node }) {
3708
3823
  const rect = el.getBoundingClientRect();
3709
3824
  const total = horizontal ? rect.width : rect.height;
3710
3825
  const origin = horizontal ? event.clientX : event.clientY;
3711
- const base = [...node.sizes];
3826
+ const base = [...sizes];
3712
3827
  const pair = (base[index] ?? 0) + (base[index + 1] ?? 0);
3828
+ const minFirst = Math.max(CANVAS_MIN_SPLIT_FRACTION, total > 0 ? (mins[index] ?? 0) / total : 0);
3829
+ const minSecond = Math.max(CANVAS_MIN_SPLIT_FRACTION, total > 0 ? (mins[index + 1] ?? 0) / total : 0);
3713
3830
  const target = event.currentTarget;
3714
3831
  const end = beginCanvasResize(horizontal ? "col" : "row");
3715
3832
  const compute = (pos) => {
3716
3833
  const delta = (pos - origin) / total;
3717
- const min = CANVAS_MIN_SPLIT_FRACTION;
3718
- const first = Math.min(pair - min, Math.max(min, (base[index] ?? 0) + delta));
3834
+ const first = Math.max(Math.min(pair - minSecond, Math.max(minFirst, (base[index] ?? 0) + delta)), Math.min(minFirst, pair / 2));
3719
3835
  const next = [...base];
3720
3836
  next[index] = first;
3721
3837
  next[index + 1] = pair - first;
@@ -3830,6 +3946,7 @@ function CanvasFrame({ children, className }) {
3830
3946
  export {
3831
3947
  BrowserBar,
3832
3948
  BrowserTabs,
3949
+ CANVAS_TAB_LIST_ROOM,
3833
3950
  CANVAS_TAB_MIN_WIDTH,
3834
3951
  CanvasColumn,
3835
3952
  CanvasFrame,
@@ -3846,8 +3963,8 @@ export {
3846
3963
  CanvasWindowTabs,
3847
3964
  beginCanvasResize,
3848
3965
  canvasSessionKey,
3849
- canvasTabsOverflow,
3850
3966
  defineCanvasKind,
3967
+ fitCanvasTabWindow,
3851
3968
  getCanvasKind,
3852
3969
  itemTitle,
3853
3970
  listCanvasKinds,