@ai-matrx/canvas 0.7.3 → 0.8.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
@@ -381,26 +381,45 @@ declare function CanvasFrame({ children, className }: {
381
381
  declare function useCanvasPresentation(): CanvasPresentation | null;
382
382
 
383
383
  declare function itemTitle(item: CanvasItem, kind: AnyCanvasKind | undefined): string;
384
+ /** One tab of a strip and the pane it lives in (a window-wide strip shows every pane's tabs). */
385
+ interface CanvasStripTab {
386
+ readonly itemId: CanvasItemId;
387
+ readonly paneId: CanvasPaneId;
388
+ /** Its index inside its own pane (where a tab dropped on it lands). */
389
+ readonly index: number;
390
+ }
384
391
  declare function CanvasPaneView({ paneId }: {
385
392
  paneId: CanvasPaneId;
386
393
  }): react.JSX.Element | null;
387
- /** A background tab's floor (px): icon + ~7 letters + "…" ("Messag…"). Mirrored to CSS as --mxc-tab-min. */
388
- declare const CANVAS_TAB_MIN_WIDTH = 88;
389
- /** The active tab's floor (px): icon + ~8 letters + "…" + its close. Mirrored to CSS as --mxc-tab-active-min. */
390
- declare const CANVAS_ACTIVE_TAB_MIN_WIDTH = 112;
391
394
  /**
392
- * Which tabs the strip shows at this width: all of them when every tab fits
393
- * at its floor, else as many as fit — always the active one — and the rest
394
- * are reached through the "All tabs" menu (Chrome / VS Code). Pure, so the
395
- * rule is testable without a layout engine. `width <= 0` (not measured yet)
396
- * shows everything.
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.
397
398
  */
398
- declare function fitCanvasTabs(count: number, activeIndex: number, width: number): {
399
- start: number;
400
- end: number;
401
- overflow: boolean;
402
- compact: boolean;
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;
402
+ /** One entry of a strip: a tab, or a split's visible tabs drawn as ONE joined tab (Chrome split view). */
403
+ type CanvasStripEntry = {
404
+ readonly type: "tab";
405
+ readonly tab: CanvasStripTab;
406
+ } | {
407
+ readonly type: "split";
408
+ readonly tabs: readonly CanvasStripTab[];
403
409
  };
410
+ /**
411
+ * The strip's entries. With `actives` (each pane's active item) for two or more
412
+ * panes, those tabs become one joined entry, placed where the first of them
413
+ * stands; every other tab keeps its place. Pure, so the rule is testable.
414
+ */
415
+ declare function planCanvasTabStrip(tabs: readonly CanvasStripTab[], actives?: ReadonlyMap<CanvasPaneId, CanvasItemId> | null): CanvasStripEntry[];
416
+ /**
417
+ * THE window-wide tab strip of a split canvas (Chrome split view): every
418
+ * pane's tabs in one row above the panes, the panes' visible tabs drawn as
419
+ * one joined tab, "+" opening in the focused pane. A split never halves the
420
+ * strip into one per pane.
421
+ */
422
+ declare function CanvasWindowTabs(): react.JSX.Element;
404
423
 
405
424
  /**
406
425
  * One resize gesture (the canvas edge, a split handle). While it runs:
@@ -512,4 +531,4 @@ interface BrowserBarProps {
512
531
  /** A browser's back · forward · reload · address bar, for a kind's leading slot or its body. */
513
532
  declare function BrowserBar({ url, canGoBack, canGoForward, onBack, onForward, onReload, onNavigate, trailing, className }: BrowserBarProps): react.JSX.Element;
514
533
 
515
- export { type AnyCanvasKind, BrowserBar, type BrowserBarProps, type BrowserTab, BrowserTabs, type BrowserTabsProps, CANVAS_ACTIVE_TAB_MIN_WIDTH, 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, CanvasTab, type CanvasTabProps, type CanvasTabStripMode, CanvasToggle, CanvasWindowScope, beginCanvasResize, canvasSessionKey, defineCanvasKind, fitCanvasTabs, getCanvasKind, itemTitle, listCanvasKinds, registerCanvasKind, registerCanvasKinds, useCanvas, useCanvasColumnWidth, useCanvasHostPorts, useCanvasIsPresented, useCanvasKind, useCanvasKindToggle, useCanvasKinds, useCanvasLauncherEntries, useCanvasLauncherEntry, useCanvasMenuItems, useCanvasNativeBodies, useCanvasPresentation, useCanvasState, useCanvasWindowId, useOptionalCanvas, useOptionalCanvasState };
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 };
package/dist/react.d.ts CHANGED
@@ -381,26 +381,45 @@ declare function CanvasFrame({ children, className }: {
381
381
  declare function useCanvasPresentation(): CanvasPresentation | null;
382
382
 
383
383
  declare function itemTitle(item: CanvasItem, kind: AnyCanvasKind | undefined): string;
384
+ /** One tab of a strip and the pane it lives in (a window-wide strip shows every pane's tabs). */
385
+ interface CanvasStripTab {
386
+ readonly itemId: CanvasItemId;
387
+ readonly paneId: CanvasPaneId;
388
+ /** Its index inside its own pane (where a tab dropped on it lands). */
389
+ readonly index: number;
390
+ }
384
391
  declare function CanvasPaneView({ paneId }: {
385
392
  paneId: CanvasPaneId;
386
393
  }): react.JSX.Element | null;
387
- /** A background tab's floor (px): icon + ~7 letters + "…" ("Messag…"). Mirrored to CSS as --mxc-tab-min. */
388
- declare const CANVAS_TAB_MIN_WIDTH = 88;
389
- /** The active tab's floor (px): icon + ~8 letters + "…" + its close. Mirrored to CSS as --mxc-tab-active-min. */
390
- declare const CANVAS_ACTIVE_TAB_MIN_WIDTH = 112;
391
394
  /**
392
- * Which tabs the strip shows at this width: all of them when every tab fits
393
- * at its floor, else as many as fit — always the active one — and the rest
394
- * are reached through the "All tabs" menu (Chrome / VS Code). Pure, so the
395
- * rule is testable without a layout engine. `width <= 0` (not measured yet)
396
- * shows everything.
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.
397
398
  */
398
- declare function fitCanvasTabs(count: number, activeIndex: number, width: number): {
399
- start: number;
400
- end: number;
401
- overflow: boolean;
402
- compact: boolean;
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;
402
+ /** One entry of a strip: a tab, or a split's visible tabs drawn as ONE joined tab (Chrome split view). */
403
+ type CanvasStripEntry = {
404
+ readonly type: "tab";
405
+ readonly tab: CanvasStripTab;
406
+ } | {
407
+ readonly type: "split";
408
+ readonly tabs: readonly CanvasStripTab[];
403
409
  };
410
+ /**
411
+ * The strip's entries. With `actives` (each pane's active item) for two or more
412
+ * panes, those tabs become one joined entry, placed where the first of them
413
+ * stands; every other tab keeps its place. Pure, so the rule is testable.
414
+ */
415
+ declare function planCanvasTabStrip(tabs: readonly CanvasStripTab[], actives?: ReadonlyMap<CanvasPaneId, CanvasItemId> | null): CanvasStripEntry[];
416
+ /**
417
+ * THE window-wide tab strip of a split canvas (Chrome split view): every
418
+ * pane's tabs in one row above the panes, the panes' visible tabs drawn as
419
+ * one joined tab, "+" opening in the focused pane. A split never halves the
420
+ * strip into one per pane.
421
+ */
422
+ declare function CanvasWindowTabs(): react.JSX.Element;
404
423
 
405
424
  /**
406
425
  * One resize gesture (the canvas edge, a split handle). While it runs:
@@ -512,4 +531,4 @@ interface BrowserBarProps {
512
531
  /** A browser's back · forward · reload · address bar, for a kind's leading slot or its body. */
513
532
  declare function BrowserBar({ url, canGoBack, canGoForward, onBack, onForward, onReload, onNavigate, trailing, className }: BrowserBarProps): react.JSX.Element;
514
533
 
515
- export { type AnyCanvasKind, BrowserBar, type BrowserBarProps, type BrowserTab, BrowserTabs, type BrowserTabsProps, CANVAS_ACTIVE_TAB_MIN_WIDTH, 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, CanvasTab, type CanvasTabProps, type CanvasTabStripMode, CanvasToggle, CanvasWindowScope, beginCanvasResize, canvasSessionKey, defineCanvasKind, fitCanvasTabs, getCanvasKind, itemTitle, listCanvasKinds, registerCanvasKind, registerCanvasKinds, useCanvas, useCanvasColumnWidth, useCanvasHostPorts, useCanvasIsPresented, useCanvasKind, useCanvasKindToggle, useCanvasKinds, useCanvasLauncherEntries, useCanvasLauncherEntry, useCanvasMenuItems, useCanvasNativeBodies, useCanvasPresentation, useCanvasState, useCanvasWindowId, useOptionalCanvas, useOptionalCanvasState };
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 };
package/dist/react.js CHANGED
@@ -2591,6 +2591,10 @@ var CanvasSinglePaneContext = createContext3(false);
2591
2591
  function useCanvasSinglePane() {
2592
2592
  return useContext3(CanvasSinglePaneContext);
2593
2593
  }
2594
+ var CanvasWindowTabsContext = createContext3(false);
2595
+ function useCanvasWindowTabs() {
2596
+ return useContext3(CanvasWindowTabsContext);
2597
+ }
2594
2598
  function useSlotSize(slots, paneId) {
2595
2599
  return useSyncExternalStore3(
2596
2600
  slots.subscribe,
@@ -3003,6 +3007,20 @@ function windowTabsSignature(state, windowId) {
3003
3007
  }
3004
3008
  return rows.join("");
3005
3009
  }
3010
+ function paneActivesSignature(state, windowId) {
3011
+ const frame2 = canvasWindowFrame(state, windowId);
3012
+ if (!frame2) return "";
3013
+ return listPaneIds(frame2.layout).map((paneId) => `${paneId}${state.panes[paneId]?.activeItemId ?? ""}`).join("");
3014
+ }
3015
+ function parsePaneActives(signature) {
3016
+ const actives = /* @__PURE__ */ new Map();
3017
+ if (!signature) return actives;
3018
+ for (const row of signature.split("")) {
3019
+ const [paneId = "", itemId = ""] = row.split("");
3020
+ if (itemId) actives.set(paneId, itemId);
3021
+ }
3022
+ return actives;
3023
+ }
3006
3024
  function parseWindowTabs(signature) {
3007
3025
  if (!signature) return [];
3008
3026
  const perPane = /* @__PURE__ */ new Map();
@@ -3033,6 +3051,7 @@ function CanvasPaneView({ paneId }) {
3033
3051
  const canGoForward = useCanvasState((s) => active ? selectCanvasCanGoForward(s, active.id) : false);
3034
3052
  const kind = useItemKind(active?.kind ?? "");
3035
3053
  const singlePane = useCanvasSinglePane();
3054
+ const inWindowStrip = useCanvasWindowTabs() && !singlePane;
3036
3055
  const windowTabs = useCanvasState((s) => singlePane ? windowTabsSignature(s, windowId) : "");
3037
3056
  const sectionRef = useRef7(null);
3038
3057
  const bodyRef = useRef7(null);
@@ -3077,8 +3096,8 @@ function CanvasPaneView({ paneId }) {
3077
3096
  children: /* @__PURE__ */ jsx7(ArrowRightIcon, {})
3078
3097
  }
3079
3098
  ) : null,
3080
- single && active ? /* @__PURE__ */ jsx7(SingleTitle, { item: active, kind }) : tabs.length > 0 ? /* @__PURE__ */ jsx7(TabStrip, { tabs, activeItemId: pane.activeItemId }) : /* @__PURE__ */ jsx7("span", { className: "mxc-panel-title", children: "New tab" }),
3081
- /* @__PURE__ */ jsx7(CanvasHeaderButton, { className: "mxc-new-tab", label: "New tab", onClick: () => void canvas.newTab(paneId), children: /* @__PURE__ */ jsx7(PlusIcon, {}) })
3099
+ inWindowStrip ? active ? /* @__PURE__ */ jsx7(PaneTitle, { item: active, kind }) : /* @__PURE__ */ jsx7("span", { className: "mxc-panel-title", children: "New tab" }) : single && active ? /* @__PURE__ */ jsx7(SingleTitle, { item: active, kind }) : tabs.length > 0 ? /* @__PURE__ */ jsx7(TabStrip, { tabs, activeItemId: pane.activeItemId }) : /* @__PURE__ */ jsx7("span", { className: "mxc-panel-title", children: "New tab" }),
3100
+ inWindowStrip ? null : /* @__PURE__ */ jsx7(CanvasHeaderButton, { className: "mxc-new-tab", label: "New tab", onClick: () => void canvas.newTab(paneId), children: /* @__PURE__ */ jsx7(PlusIcon, {}) })
3082
3101
  ] }),
3083
3102
  leading: Leading && kindProps ? /* @__PURE__ */ jsx7(Leading, { ...kindProps }) : null,
3084
3103
  actions: Action && kindProps ? (
@@ -3139,24 +3158,56 @@ function SingleTitle({ item, kind }) {
3139
3158
  /* @__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, {}) })
3140
3159
  ] });
3141
3160
  }
3142
- var CANVAS_TAB_MIN_WIDTH = 88;
3143
- var CANVAS_ACTIVE_TAB_MIN_WIDTH = 112;
3144
- var TAB_LIST_BUTTON_ROOM = 36;
3161
+ function PaneTitle({ item, kind }) {
3162
+ const title = itemTitle(item, kind);
3163
+ const drag = useTabDrag(item.id, title);
3164
+ const Icon = kind?.icon ?? AlertIcon;
3165
+ return /* @__PURE__ */ jsxs6("span", { className: "mxc-panel-title mxc-pane-title", "data-draggable": "", title, ...drag, children: [
3166
+ /* @__PURE__ */ jsx7(Icon, { className: "mxc-title-icon" }),
3167
+ /* @__PURE__ */ jsx7("span", { className: "mxc-tab-title", children: title })
3168
+ ] });
3169
+ }
3170
+ var CANVAS_TAB_MIN_WIDTH = 120;
3145
3171
  var TAB_GAP = 2;
3146
- function fitCanvasTabs(count, activeIndex, width) {
3147
- if (count <= 0) return { start: 0, end: 0, overflow: false, compact: false };
3148
- const need = (n) => CANVAS_ACTIVE_TAB_MIN_WIDTH + (n - 1) * (CANVAS_TAB_MIN_WIDTH + TAB_GAP);
3149
- if (width <= 0 || need(count) <= width) return { start: 0, end: count, overflow: false, compact: false };
3150
- if (count === 1) return { start: 0, end: 1, overflow: false, compact: true };
3151
- let fit = 1;
3152
- while (fit + 1 < count && need(fit + 1) + TAB_LIST_BUTTON_ROOM <= width) fit += 1;
3153
- const active = Math.min(Math.max(activeIndex, 0), count - 1);
3154
- const start = active < fit ? 0 : active - fit + 1;
3155
- return { start, end: start + fit, overflow: true, compact: need(fit) + TAB_LIST_BUTTON_ROOM > width };
3156
- }
3157
- function TabStrip({ tabs, activeItemId }) {
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;
3175
+ }
3176
+ function planCanvasTabStrip(tabs, actives) {
3177
+ const joined = actives ? tabs.filter((tab) => actives.get(tab.paneId) === tab.itemId) : [];
3178
+ if (joined.length < 2) return tabs.map((tab) => ({ type: "tab", tab }));
3179
+ const entries = [];
3180
+ let placed = false;
3181
+ for (const tab of tabs) {
3182
+ if (!joined.includes(tab)) entries.push({ type: "tab", tab });
3183
+ else if (!placed) {
3184
+ entries.push({ type: "split", tabs: joined });
3185
+ placed = true;
3186
+ }
3187
+ }
3188
+ return entries;
3189
+ }
3190
+ function CanvasWindowTabs() {
3158
3191
  const canvas = useCanvas();
3192
+ const { windowId } = useCanvasInternals();
3193
+ const signature = useCanvasState((s) => windowTabsSignature(s, windowId));
3194
+ const activesSignature = useCanvasState((s) => paneActivesSignature(s, windowId));
3195
+ const focusedPaneId = useCanvasState((s) => selectCanvasFocusedPaneId(s, windowId));
3196
+ const focusedActive = useCanvasState((s) => focusedPaneId ? s.panes[focusedPaneId]?.activeItemId ?? null : null);
3197
+ const tabs = parseWindowTabs(signature);
3198
+ const actives = parsePaneActives(activesSignature);
3199
+ return /* @__PURE__ */ jsxs6("div", { className: "mxc-window-tabs", "data-slot": "canvas-window-tabs", children: [
3200
+ /* @__PURE__ */ jsx7(TabStrip, { tabs, activeItemId: focusedActive, actives }),
3201
+ /* @__PURE__ */ jsx7(CanvasHeaderButton, { className: "mxc-new-tab", label: "New tab", onClick: () => void canvas.newTab(focusedPaneId ?? void 0), children: /* @__PURE__ */ jsx7(PlusIcon, {}) })
3202
+ ] });
3203
+ }
3204
+ function TabStrip({
3205
+ tabs,
3206
+ activeItemId,
3207
+ actives
3208
+ }) {
3159
3209
  const strip = useRef7(null);
3210
+ const list = useRef7(null);
3160
3211
  const [width, setWidth] = useState5(0);
3161
3212
  const read = useRef7(() => {
3162
3213
  });
@@ -3182,48 +3233,75 @@ function TabStrip({ tabs, activeItemId }) {
3182
3233
  useIsoLayoutEffect4(() => {
3183
3234
  read.current();
3184
3235
  }, [tabs.length]);
3185
- const activeIndex = Math.max(0, tabs.findIndex((tab) => tab.itemId === activeItemId));
3186
- const fit = fitCanvasTabs(tabs.length, activeIndex, width);
3187
- const shown = tabs.slice(fit.start, fit.end);
3188
- const hidden = tabs.length - shown.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
+ 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 box = el.getBoundingClientRect();
3253
+ const rect = tab.getBoundingClientRect();
3254
+ if (rect.left < box.left) el.scrollLeft -= box.left - rect.left;
3255
+ else if (rect.right > box.right) el.scrollLeft += rect.right - box.right;
3256
+ }, [activeItemId, width, tabs.length]);
3257
+ const entries = planCanvasTabStrip(tabs, actives);
3258
+ const overflow = canvasTabsOverflow(tabs.length, width);
3259
+ const joined = entries.some((entry) => entry.type === "split");
3260
+ const isActive = (tab) => actives ? actives.get(tab.paneId) === tab.itemId : tab.itemId === activeItemId;
3261
+ const tabView = (tab) => /* @__PURE__ */ jsx7(
3262
+ ItemTab,
3263
+ {
3264
+ itemId: tab.itemId,
3265
+ paneId: tab.paneId,
3266
+ index: tab.index,
3267
+ active: isActive(tab),
3268
+ current: joined && tab.itemId === activeItemId
3269
+ },
3270
+ tab.itemId
3271
+ );
3189
3272
  return /* @__PURE__ */ jsxs6(
3190
3273
  "div",
3191
3274
  {
3192
3275
  ref: strip,
3193
3276
  className: "mxc-tabstrip",
3194
- "data-overflow": fit.overflow ? "" : void 0,
3195
- "data-compact": fit.compact ? "" : void 0,
3196
- style: { ["--mxc-tab-min"]: `${CANVAS_TAB_MIN_WIDTH}px`, ["--mxc-tab-active-min"]: `${CANVAS_ACTIVE_TAB_MIN_WIDTH}px` },
3277
+ "data-overflow": overflow ? "" : void 0,
3278
+ style: { ["--mxc-tab-min"]: `${CANVAS_TAB_MIN_WIDTH}px` },
3197
3279
  children: [
3198
- /* @__PURE__ */ jsx7(
3199
- "div",
3200
- {
3201
- className: "mxc-tabs",
3202
- role: "tablist",
3203
- "aria-label": "Canvas tabs",
3204
- onWheel: (event) => {
3205
- if (!fit.overflow || Math.abs(event.deltaY) < Math.abs(event.deltaX) || tabs.length < 2) return;
3206
- const next = tabs[(activeIndex + (event.deltaY > 0 ? 1 : -1) + tabs.length) % tabs.length];
3207
- if (next) canvas.activate(next.itemId);
3280
+ /* @__PURE__ */ jsx7("div", { ref: list, className: "mxc-tabs", role: "tablist", "aria-label": "Canvas tabs", "aria-multiselectable": joined ? true : void 0, children: entries.map(
3281
+ (entry) => entry.type === "tab" ? tabView(entry.tab) : /* @__PURE__ */ jsx7(
3282
+ "div",
3283
+ {
3284
+ className: "mxc-tab-group",
3285
+ "data-split": "",
3286
+ role: "presentation",
3287
+ style: { ["--mxc-group-n"]: entry.tabs.length },
3288
+ children: entry.tabs.map(tabView)
3208
3289
  },
3209
- children: shown.map((tab) => /* @__PURE__ */ jsx7(ItemTab, { itemId: tab.itemId, paneId: tab.paneId, index: tab.index, active: tab.itemId === activeItemId }, tab.itemId))
3210
- }
3211
- ),
3212
- fit.overflow ? /* @__PURE__ */ jsx7(TabListMenu, { tabs, activeItemId, hidden }) : null
3290
+ `split:${entry.tabs.map((tab) => tab.itemId).join("|")}`
3291
+ )
3292
+ ) }),
3293
+ overflow ? /* @__PURE__ */ jsx7(TabListMenu, { tabs, activeItemId }) : null
3213
3294
  ]
3214
3295
  }
3215
3296
  );
3216
3297
  }
3217
- function TabListMenu({ tabs, activeItemId, hidden }) {
3298
+ function TabListMenu({ tabs, activeItemId }) {
3218
3299
  const canvas = useCanvas();
3219
3300
  const { ports, layer } = useCanvasInternals();
3220
3301
  const blocking = useRef7(null);
3221
3302
  useEffect7(() => () => blocking.current?.(), []);
3222
3303
  const label = `All tabs (${tabs.length})`;
3223
- const trigger = (props) => /* @__PURE__ */ jsxs6(CanvasHeaderButton, { className: "mxc-tab-list", label, "data-hidden-count": hidden, ...props, children: [
3224
- /* @__PURE__ */ jsx7(ChevronDownIcon, {}),
3225
- /* @__PURE__ */ jsx7("span", { className: "mxc-tab-list-count", "aria-hidden": "true", children: hidden })
3226
- ] });
3304
+ const trigger = (props) => /* @__PURE__ */ jsx7(CanvasHeaderButton, { className: "mxc-tab-list", label, ...props, children: /* @__PURE__ */ jsx7(ChevronDownIcon, {}) });
3227
3305
  const showMenu = ports.showMenu;
3228
3306
  if (showMenu) {
3229
3307
  return trigger({
@@ -3279,7 +3357,13 @@ function TabListEntry({ itemId, active }) {
3279
3357
  }
3280
3358
  );
3281
3359
  }
3282
- function ItemTab({ itemId, paneId, index, active }) {
3360
+ function ItemTab({
3361
+ itemId,
3362
+ paneId,
3363
+ index,
3364
+ active,
3365
+ current = false
3366
+ }) {
3283
3367
  const canvas = useCanvas();
3284
3368
  const item = useCanvasState((s) => s.items[itemId]);
3285
3369
  const kind = useItemKind(item?.kind ?? "");
@@ -3293,6 +3377,8 @@ function ItemTab({ itemId, paneId, index, active }) {
3293
3377
  title,
3294
3378
  icon: /* @__PURE__ */ jsx7(Icon, {}),
3295
3379
  active,
3380
+ "data-mxc-tab": itemId,
3381
+ "data-current": current ? "" : void 0,
3296
3382
  onSelect: () => canvas.activate(itemId),
3297
3383
  onClose: () => canvas.close(itemId),
3298
3384
  ...drag,
@@ -3478,6 +3564,7 @@ function CanvasColumn({ className, style, onLiveWidth, placement = "docked" }) {
3478
3564
  };
3479
3565
  }, [layer]);
3480
3566
  if (!layout) return null;
3567
+ const windowTabs = !singlePane && layout.type === "split";
3481
3568
  const fitted = fitCanvasWidth(width, region, controller.rules);
3482
3569
  const shown = dragWidth ?? fitted;
3483
3570
  return /* @__PURE__ */ jsx8(
@@ -3505,10 +3592,13 @@ function CanvasColumn({ className, style, onLiveWidth, placement = "docked" }) {
3505
3592
  }
3506
3593
  }
3507
3594
  ) : null,
3508
- /* @__PURE__ */ jsx8(CanvasSlotsContext.Provider, { value: slots, children: /* @__PURE__ */ jsxs7(CanvasSinglePaneContext.Provider, { value: singlePane, children: [
3509
- /* @__PURE__ */ jsx8("div", { className: "mxc-layout", "data-single-pane": singlePane ? "" : void 0, children: singlePane ? /* @__PURE__ */ jsx8(CanvasPaneView, { paneId: presentedPaneId ?? listPaneIds(layout)[0] }) : /* @__PURE__ */ jsx8(LayoutNodeView, { node: layout }) }),
3595
+ /* @__PURE__ */ jsx8(CanvasSlotsContext.Provider, { value: slots, children: /* @__PURE__ */ jsx8(CanvasSinglePaneContext.Provider, { value: singlePane, children: /* @__PURE__ */ jsxs7(CanvasWindowTabsContext.Provider, { value: windowTabs, children: [
3596
+ /* @__PURE__ */ jsxs7("div", { className: "mxc-layout", "data-single-pane": singlePane ? "" : void 0, "data-window-tabs": windowTabs ? "" : void 0, children: [
3597
+ windowTabs ? /* @__PURE__ */ jsx8(CanvasWindowTabs, {}) : null,
3598
+ /* @__PURE__ */ jsx8("div", { className: "mxc-layout-panes", children: singlePane ? /* @__PURE__ */ jsx8(CanvasPaneView, { paneId: presentedPaneId ?? listPaneIds(layout)[0] }) : /* @__PURE__ */ jsx8(LayoutNodeView, { node: layout }) })
3599
+ ] }),
3510
3600
  /* @__PURE__ */ jsx8(CanvasBodyLayer, {})
3511
- ] }) })
3601
+ ] }) }) })
3512
3602
  ] })
3513
3603
  }
3514
3604
  );
@@ -3714,7 +3804,6 @@ function CanvasFrame({ children, className }) {
3714
3804
  export {
3715
3805
  BrowserBar,
3716
3806
  BrowserTabs,
3717
- CANVAS_ACTIVE_TAB_MIN_WIDTH,
3718
3807
  CANVAS_TAB_MIN_WIDTH,
3719
3808
  CanvasColumn,
3720
3809
  CanvasFrame,
@@ -3728,13 +3817,15 @@ export {
3728
3817
  CanvasTab,
3729
3818
  CanvasToggle,
3730
3819
  CanvasWindowScope,
3820
+ CanvasWindowTabs,
3731
3821
  beginCanvasResize,
3732
3822
  canvasSessionKey,
3823
+ canvasTabsOverflow,
3733
3824
  defineCanvasKind,
3734
- fitCanvasTabs,
3735
3825
  getCanvasKind,
3736
3826
  itemTitle,
3737
3827
  listCanvasKinds,
3828
+ planCanvasTabStrip,
3738
3829
  registerCanvasKind,
3739
3830
  registerCanvasKinds,
3740
3831
  useCanvas,