@ai-matrx/canvas 0.7.3 → 0.8.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 CHANGED
@@ -1,5 +1,25 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.8.1
4
+
5
+ Live check of 0.8.0 (1440px, canvas expanded, 5 tabs): a 1428px window strip drew 127px tabs — a `flex-basis` alone let the tab list size itself to the names.
6
+
7
+ - Every tab now has a definite `width: 12rem` (`flex: 0 1 auto`), a joined split tab `n × 12rem`: with room, tabs are 12rem; short of room they shrink evenly to the 120px floor, then scroll.
8
+ - Keeping the current tab in view brings a split's joined tab into view whole, not only its current half.
9
+ - Consumer action: none.
10
+
11
+ ## 0.8.0
12
+
13
+ Owner feedback (Arman, 2026-10-03), a split canvas: tabs read "The Shap" | "Agent con" | an icon sliver | "+" — "when there is a split, the tabs should not get split like this."
14
+
15
+ - **A split keeps ONE window-wide tab strip** (Chrome split view). `CanvasColumn` draws `CanvasWindowTabs` above the panes whenever the layout is a split (side by side, stacked or down): every pane's tabs in one row, the panes' visible tabs drawn as ONE joined tab (the focused pane's half strongest), one "+" that opens in the focused pane. Pane headers keep Back/Forward, a draggable title, kind controls, menu, expand and close — no strip of their own. Phones are unchanged (one presented pane whose strip carries every tab).
16
+ - **Tabs shrink evenly to a readable floor, then scroll.** Every tab: 12rem with room, floor `CANVAS_TAB_MIN_WIDTH` 120px (icon + ~10 letters + "…"); a cut name always ends in an ellipsis (never clipped). Past the floor the tab list scrolls sideways — wheel and trackpad, the current tab kept in view, no drawn scrollbar — and "All tabs (N)" stays.
17
+ - **Removed:** the icon-sliver compact mode (`data-compact`), the windowed `fitCanvasTabs`, `CANVAS_ACTIVE_TAB_MIN_WIDTH`, and the hidden-count badge on "All tabs". **Added:** `CanvasWindowTabs`, `canvasTabsOverflow`, `planCanvasTabStrip`, types `CanvasStripTab` / `CanvasStripEntry`.
18
+
19
+ ### Consumer action
20
+
21
+ - None for hosts that render `<CanvasColumn/>`. A host that imported `fitCanvasTabs` or `CANVAS_ACTIVE_TAB_MIN_WIDTH` (none known) uses `canvasTabsOverflow` / `CANVAS_TAB_MIN_WIDTH`.
22
+
3
23
  ## 0.7.3
4
24
 
5
25
  Census on 0.7.2 (40 web routes, 1440px window, chat open, canvas 360/640/900): with a 900px canvas the page beside it was 56px and route headers were 29-67px wide.
package/README.md CHANGED
@@ -67,9 +67,9 @@ Navigating to something already open elsewhere focuses it there (a launcher tab
67
67
 
68
68
  **Forward.** After Back the header also shows Forward (`selectCanvasCanGoForward`); it returns the same live body Back left. A navigate somewhere new ends the forward trail. Shortcut data `canvas.back` / `canvas.forward` (⌘[ / ⌘]) is for a desktop host; browsers own those keys.
69
69
 
70
- **The strip.** A tab is as wide as its name (up to 12rem). Short of room, tabs shrink — the active tab least — to icon + ~6 letters + "…" (`CANVAS_TAB_MIN_WIDTH`; the active tab keeps `CANVAS_ACTIVE_TAB_MIN_WIDTH`); a name really cut ends in an ellipsis and every tab's tooltip is its full name. Tabs that do not fit at their floor are reached from "All tabs (N)"; the active tab is always shown. At the narrowest the strip is the active tab's title and that menu. Nothing scrolls and nothing in the header overlaps another control (`fitCanvasTabs` is the rule).
70
+ **The strip** (Chrome's model). Tabs share the strip evenly — 12rem each with room — and shrink together to one readable floor, icon + ~10 letters + "…" (`CANVAS_TAB_MIN_WIDTH`, 120px); a cut name always ends in an ellipsis and every tab's tooltip is its full name. Past the floor the tab list scrolls sideways (wheel, trackpad; the current tab is kept in view) and "All tabs (N)" lists every tab (`canvasTabsOverflow` is the rule). A tab never shrinks to an icon sliver and nothing in the header overlaps another control.
71
71
 
72
- **Splits.** A side-by-side split whose panes would be narrower than `paneMinWidth` (web: `CANVAS_PANE_MIN_WIDTH`, 280px) renders stacked; the tree is unchanged.
72
+ **Splits.** A split canvas keeps ONE window-wide tab strip above its panes (`CanvasWindowTabs`), never one per pane: every pane's tabs are in it, the panes' visible tabs are drawn as ONE joined tab (the focused pane's half strongest; `planCanvasTabStrip`), and its "+" opens in the focused pane. Each pane header keeps its own Back/Forward, title, kind controls and menu. A side-by-side split whose panes would be narrower than `paneMinWidth` (web: `CANVAS_PANE_MIN_WIDTH`, 280px) renders stacked under the same one strip; the tree is unchanged.
73
73
 
74
74
  **Phones** (`CANVAS_PHONE_MAX_WIDTH`, 767px): the column presents ONE pane — the focused one when it holds a tab, else the first that does (`selectCanvasPresentedPaneId`) — whose strip carries every pane's tabs; choosing a tab from another pane presents that pane. The pane menu offers no Split. The layout tree is untouched, so a wider screen gets its split back.
75
75
 
package/dist/react.cjs CHANGED
@@ -34,7 +34,6 @@ var react_exports = {};
34
34
  __export(react_exports, {
35
35
  BrowserBar: () => BrowserBar,
36
36
  BrowserTabs: () => BrowserTabs,
37
- CANVAS_ACTIVE_TAB_MIN_WIDTH: () => CANVAS_ACTIVE_TAB_MIN_WIDTH,
38
37
  CANVAS_TAB_MIN_WIDTH: () => CANVAS_TAB_MIN_WIDTH,
39
38
  CanvasColumn: () => CanvasColumn,
40
39
  CanvasFrame: () => CanvasFrame,
@@ -48,13 +47,15 @@ __export(react_exports, {
48
47
  CanvasTab: () => CanvasTab,
49
48
  CanvasToggle: () => CanvasToggle,
50
49
  CanvasWindowScope: () => CanvasWindowScope,
50
+ CanvasWindowTabs: () => CanvasWindowTabs,
51
51
  beginCanvasResize: () => beginCanvasResize,
52
52
  canvasSessionKey: () => canvasSessionKey,
53
+ canvasTabsOverflow: () => canvasTabsOverflow,
53
54
  defineCanvasKind: () => defineCanvasKind,
54
- fitCanvasTabs: () => fitCanvasTabs,
55
55
  getCanvasKind: () => getCanvasKind,
56
56
  itemTitle: () => itemTitle,
57
57
  listCanvasKinds: () => listCanvasKinds,
58
+ planCanvasTabStrip: () => planCanvasTabStrip,
58
59
  registerCanvasKind: () => registerCanvasKind,
59
60
  registerCanvasKinds: () => registerCanvasKinds,
60
61
  useCanvas: () => useCanvas,
@@ -2636,6 +2637,10 @@ var CanvasSinglePaneContext = (0, import_react7.createContext)(false);
2636
2637
  function useCanvasSinglePane() {
2637
2638
  return (0, import_react7.useContext)(CanvasSinglePaneContext);
2638
2639
  }
2640
+ var CanvasWindowTabsContext = (0, import_react7.createContext)(false);
2641
+ function useCanvasWindowTabs() {
2642
+ return (0, import_react7.useContext)(CanvasWindowTabsContext);
2643
+ }
2639
2644
  function useSlotSize(slots, paneId) {
2640
2645
  return (0, import_react7.useSyncExternalStore)(
2641
2646
  slots.subscribe,
@@ -3048,6 +3053,20 @@ function windowTabsSignature(state, windowId) {
3048
3053
  }
3049
3054
  return rows.join("");
3050
3055
  }
3056
+ function paneActivesSignature(state, windowId) {
3057
+ const frame2 = canvasWindowFrame(state, windowId);
3058
+ if (!frame2) return "";
3059
+ return listPaneIds(frame2.layout).map((paneId) => `${paneId}${state.panes[paneId]?.activeItemId ?? ""}`).join("");
3060
+ }
3061
+ function parsePaneActives(signature) {
3062
+ const actives = /* @__PURE__ */ new Map();
3063
+ if (!signature) return actives;
3064
+ for (const row of signature.split("")) {
3065
+ const [paneId = "", itemId = ""] = row.split("");
3066
+ if (itemId) actives.set(paneId, itemId);
3067
+ }
3068
+ return actives;
3069
+ }
3051
3070
  function parseWindowTabs(signature) {
3052
3071
  if (!signature) return [];
3053
3072
  const perPane = /* @__PURE__ */ new Map();
@@ -3078,6 +3097,7 @@ function CanvasPaneView({ paneId }) {
3078
3097
  const canGoForward = useCanvasState((s) => active ? selectCanvasCanGoForward(s, active.id) : false);
3079
3098
  const kind = useItemKind(active?.kind ?? "");
3080
3099
  const singlePane = useCanvasSinglePane();
3100
+ const inWindowStrip = useCanvasWindowTabs() && !singlePane;
3081
3101
  const windowTabs = useCanvasState((s) => singlePane ? windowTabsSignature(s, windowId) : "");
3082
3102
  const sectionRef = (0, import_react9.useRef)(null);
3083
3103
  const bodyRef = (0, import_react9.useRef)(null);
@@ -3122,8 +3142,8 @@ function CanvasPaneView({ paneId }) {
3122
3142
  children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ArrowRightIcon, {})
3123
3143
  }
3124
3144
  ) : null,
3125
- single && active ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(SingleTitle, { item: active, kind }) : tabs.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(TabStrip, { tabs, activeItemId: pane.activeItemId }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "mxc-panel-title", children: "New tab" }),
3126
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(CanvasHeaderButton, { className: "mxc-new-tab", label: "New tab", onClick: () => void canvas.newTab(paneId), children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(PlusIcon, {}) })
3145
+ inWindowStrip ? active ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(PaneTitle, { item: active, kind }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "mxc-panel-title", children: "New tab" }) : single && active ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(SingleTitle, { item: active, kind }) : tabs.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(TabStrip, { tabs, activeItemId: pane.activeItemId }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "mxc-panel-title", children: "New tab" }),
3146
+ inWindowStrip ? null : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(CanvasHeaderButton, { className: "mxc-new-tab", label: "New tab", onClick: () => void canvas.newTab(paneId), children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(PlusIcon, {}) })
3127
3147
  ] }),
3128
3148
  leading: Leading && kindProps ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Leading, { ...kindProps }) : null,
3129
3149
  actions: Action && kindProps ? (
@@ -3184,24 +3204,56 @@ function SingleTitle({ item, kind }) {
3184
3204
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { type: "button", className: "mxc-tab-close", "data-slot": "browser-tab-close", "aria-label": `Close ${title}`, onClick: () => canvas.close(item.id), children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(CloseIcon, {}) })
3185
3205
  ] });
3186
3206
  }
3187
- var CANVAS_TAB_MIN_WIDTH = 88;
3188
- var CANVAS_ACTIVE_TAB_MIN_WIDTH = 112;
3189
- var TAB_LIST_BUTTON_ROOM = 36;
3207
+ function PaneTitle({ item, kind }) {
3208
+ const title = itemTitle(item, kind);
3209
+ const drag = useTabDrag(item.id, title);
3210
+ const Icon = kind?.icon ?? AlertIcon;
3211
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: "mxc-panel-title mxc-pane-title", "data-draggable": "", title, ...drag, children: [
3212
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Icon, { className: "mxc-title-icon" }),
3213
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "mxc-tab-title", children: title })
3214
+ ] });
3215
+ }
3216
+ var CANVAS_TAB_MIN_WIDTH = 120;
3190
3217
  var TAB_GAP = 2;
3191
- function fitCanvasTabs(count, activeIndex, width) {
3192
- if (count <= 0) return { start: 0, end: 0, overflow: false, compact: false };
3193
- const need = (n) => CANVAS_ACTIVE_TAB_MIN_WIDTH + (n - 1) * (CANVAS_TAB_MIN_WIDTH + TAB_GAP);
3194
- if (width <= 0 || need(count) <= width) return { start: 0, end: count, overflow: false, compact: false };
3195
- if (count === 1) return { start: 0, end: 1, overflow: false, compact: true };
3196
- let fit = 1;
3197
- while (fit + 1 < count && need(fit + 1) + TAB_LIST_BUTTON_ROOM <= width) fit += 1;
3198
- const active = Math.min(Math.max(activeIndex, 0), count - 1);
3199
- const start = active < fit ? 0 : active - fit + 1;
3200
- return { start, end: start + fit, overflow: true, compact: need(fit) + TAB_LIST_BUTTON_ROOM > width };
3201
- }
3202
- function TabStrip({ tabs, activeItemId }) {
3218
+ function canvasTabsOverflow(count, width) {
3219
+ if (count <= 1 || width <= 0) return false;
3220
+ return count * CANVAS_TAB_MIN_WIDTH + (count - 1) * TAB_GAP > width;
3221
+ }
3222
+ function planCanvasTabStrip(tabs, actives) {
3223
+ const joined = actives ? tabs.filter((tab) => actives.get(tab.paneId) === tab.itemId) : [];
3224
+ if (joined.length < 2) return tabs.map((tab) => ({ type: "tab", tab }));
3225
+ const entries = [];
3226
+ let placed = false;
3227
+ for (const tab of tabs) {
3228
+ if (!joined.includes(tab)) entries.push({ type: "tab", tab });
3229
+ else if (!placed) {
3230
+ entries.push({ type: "split", tabs: joined });
3231
+ placed = true;
3232
+ }
3233
+ }
3234
+ return entries;
3235
+ }
3236
+ function CanvasWindowTabs() {
3203
3237
  const canvas = useCanvas();
3238
+ const { windowId } = useCanvasInternals();
3239
+ const signature = useCanvasState((s) => windowTabsSignature(s, windowId));
3240
+ const activesSignature = useCanvasState((s) => paneActivesSignature(s, windowId));
3241
+ const focusedPaneId = useCanvasState((s) => selectCanvasFocusedPaneId(s, windowId));
3242
+ const focusedActive = useCanvasState((s) => focusedPaneId ? s.panes[focusedPaneId]?.activeItemId ?? null : null);
3243
+ const tabs = parseWindowTabs(signature);
3244
+ const actives = parsePaneActives(activesSignature);
3245
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "mxc-window-tabs", "data-slot": "canvas-window-tabs", children: [
3246
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(TabStrip, { tabs, activeItemId: focusedActive, actives }),
3247
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(CanvasHeaderButton, { className: "mxc-new-tab", label: "New tab", onClick: () => void canvas.newTab(focusedPaneId ?? void 0), children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(PlusIcon, {}) })
3248
+ ] });
3249
+ }
3250
+ function TabStrip({
3251
+ tabs,
3252
+ activeItemId,
3253
+ actives
3254
+ }) {
3204
3255
  const strip = (0, import_react9.useRef)(null);
3256
+ const list = (0, import_react9.useRef)(null);
3205
3257
  const [width, setWidth] = (0, import_react9.useState)(0);
3206
3258
  const read = (0, import_react9.useRef)(() => {
3207
3259
  });
@@ -3227,48 +3279,76 @@ function TabStrip({ tabs, activeItemId }) {
3227
3279
  useIsoLayoutEffect4(() => {
3228
3280
  read.current();
3229
3281
  }, [tabs.length]);
3230
- const activeIndex = Math.max(0, tabs.findIndex((tab) => tab.itemId === activeItemId));
3231
- const fit = fitCanvasTabs(tabs.length, activeIndex, width);
3232
- const shown = tabs.slice(fit.start, fit.end);
3233
- const hidden = tabs.length - shown.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
+ useIsoLayoutEffect4(() => {
3294
+ const el = list.current;
3295
+ if (!el || !activeItemId) return;
3296
+ const tab = [...el.querySelectorAll("[data-mxc-tab]")].find((node) => node.dataset.mxcTab === activeItemId);
3297
+ if (!tab) return;
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]);
3304
+ const entries = planCanvasTabStrip(tabs, actives);
3305
+ const overflow = canvasTabsOverflow(tabs.length, width);
3306
+ const joined = entries.some((entry) => entry.type === "split");
3307
+ const isActive = (tab) => actives ? actives.get(tab.paneId) === tab.itemId : tab.itemId === activeItemId;
3308
+ const tabView = (tab) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3309
+ ItemTab,
3310
+ {
3311
+ itemId: tab.itemId,
3312
+ paneId: tab.paneId,
3313
+ index: tab.index,
3314
+ active: isActive(tab),
3315
+ current: joined && tab.itemId === activeItemId
3316
+ },
3317
+ tab.itemId
3318
+ );
3234
3319
  return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
3235
3320
  "div",
3236
3321
  {
3237
3322
  ref: strip,
3238
3323
  className: "mxc-tabstrip",
3239
- "data-overflow": fit.overflow ? "" : void 0,
3240
- "data-compact": fit.compact ? "" : void 0,
3241
- style: { ["--mxc-tab-min"]: `${CANVAS_TAB_MIN_WIDTH}px`, ["--mxc-tab-active-min"]: `${CANVAS_ACTIVE_TAB_MIN_WIDTH}px` },
3324
+ "data-overflow": overflow ? "" : void 0,
3325
+ style: { ["--mxc-tab-min"]: `${CANVAS_TAB_MIN_WIDTH}px` },
3242
3326
  children: [
3243
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3244
- "div",
3245
- {
3246
- className: "mxc-tabs",
3247
- role: "tablist",
3248
- "aria-label": "Canvas tabs",
3249
- onWheel: (event) => {
3250
- if (!fit.overflow || Math.abs(event.deltaY) < Math.abs(event.deltaX) || tabs.length < 2) return;
3251
- const next = tabs[(activeIndex + (event.deltaY > 0 ? 1 : -1) + tabs.length) % tabs.length];
3252
- if (next) canvas.activate(next.itemId);
3327
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { ref: list, className: "mxc-tabs", role: "tablist", "aria-label": "Canvas tabs", "aria-multiselectable": joined ? true : void 0, children: entries.map(
3328
+ (entry) => entry.type === "tab" ? tabView(entry.tab) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3329
+ "div",
3330
+ {
3331
+ className: "mxc-tab-group",
3332
+ "data-split": "",
3333
+ role: "presentation",
3334
+ style: { ["--mxc-group-n"]: entry.tabs.length },
3335
+ children: entry.tabs.map(tabView)
3253
3336
  },
3254
- children: shown.map((tab) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ItemTab, { itemId: tab.itemId, paneId: tab.paneId, index: tab.index, active: tab.itemId === activeItemId }, tab.itemId))
3255
- }
3256
- ),
3257
- fit.overflow ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(TabListMenu, { tabs, activeItemId, hidden }) : null
3337
+ `split:${entry.tabs.map((tab) => tab.itemId).join("|")}`
3338
+ )
3339
+ ) }),
3340
+ overflow ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(TabListMenu, { tabs, activeItemId }) : null
3258
3341
  ]
3259
3342
  }
3260
3343
  );
3261
3344
  }
3262
- function TabListMenu({ tabs, activeItemId, hidden }) {
3345
+ function TabListMenu({ tabs, activeItemId }) {
3263
3346
  const canvas = useCanvas();
3264
3347
  const { ports, layer } = useCanvasInternals();
3265
3348
  const blocking = (0, import_react9.useRef)(null);
3266
3349
  (0, import_react9.useEffect)(() => () => blocking.current?.(), []);
3267
3350
  const label = `All tabs (${tabs.length})`;
3268
- const trigger = (props) => /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(CanvasHeaderButton, { className: "mxc-tab-list", label, "data-hidden-count": hidden, ...props, children: [
3269
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ChevronDownIcon, {}),
3270
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "mxc-tab-list-count", "aria-hidden": "true", children: hidden })
3271
- ] });
3351
+ const trigger = (props) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(CanvasHeaderButton, { className: "mxc-tab-list", label, ...props, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ChevronDownIcon, {}) });
3272
3352
  const showMenu = ports.showMenu;
3273
3353
  if (showMenu) {
3274
3354
  return trigger({
@@ -3324,7 +3404,13 @@ function TabListEntry({ itemId, active }) {
3324
3404
  }
3325
3405
  );
3326
3406
  }
3327
- function ItemTab({ itemId, paneId, index, active }) {
3407
+ function ItemTab({
3408
+ itemId,
3409
+ paneId,
3410
+ index,
3411
+ active,
3412
+ current = false
3413
+ }) {
3328
3414
  const canvas = useCanvas();
3329
3415
  const item = useCanvasState((s) => s.items[itemId]);
3330
3416
  const kind = useItemKind(item?.kind ?? "");
@@ -3338,6 +3424,8 @@ function ItemTab({ itemId, paneId, index, active }) {
3338
3424
  title,
3339
3425
  icon: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Icon, {}),
3340
3426
  active,
3427
+ "data-mxc-tab": itemId,
3428
+ "data-current": current ? "" : void 0,
3341
3429
  onSelect: () => canvas.activate(itemId),
3342
3430
  onClose: () => canvas.close(itemId),
3343
3431
  ...drag,
@@ -3523,6 +3611,7 @@ function CanvasColumn({ className, style, onLiveWidth, placement = "docked" }) {
3523
3611
  };
3524
3612
  }, [layer]);
3525
3613
  if (!layout) return null;
3614
+ const windowTabs = !singlePane && layout.type === "split";
3526
3615
  const fitted = fitCanvasWidth(width, region, controller.rules);
3527
3616
  const shown = dragWidth ?? fitted;
3528
3617
  return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
@@ -3550,10 +3639,13 @@ function CanvasColumn({ className, style, onLiveWidth, placement = "docked" }) {
3550
3639
  }
3551
3640
  }
3552
3641
  ) : null,
3553
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(CanvasSlotsContext.Provider, { value: slots, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(CanvasSinglePaneContext.Provider, { value: singlePane, children: [
3554
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "mxc-layout", "data-single-pane": singlePane ? "" : void 0, children: singlePane ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(CanvasPaneView, { paneId: presentedPaneId ?? listPaneIds(layout)[0] }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(LayoutNodeView, { node: layout }) }),
3642
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(CanvasSlotsContext.Provider, { value: slots, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(CanvasSinglePaneContext.Provider, { value: singlePane, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(CanvasWindowTabsContext.Provider, { value: windowTabs, children: [
3643
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "mxc-layout", "data-single-pane": singlePane ? "" : void 0, "data-window-tabs": windowTabs ? "" : void 0, children: [
3644
+ windowTabs ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(CanvasWindowTabs, {}) : null,
3645
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "mxc-layout-panes", children: singlePane ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(CanvasPaneView, { paneId: presentedPaneId ?? listPaneIds(layout)[0] }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(LayoutNodeView, { node: layout }) })
3646
+ ] }),
3555
3647
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(CanvasBodyLayer, {})
3556
- ] }) })
3648
+ ] }) }) })
3557
3649
  ] })
3558
3650
  }
3559
3651
  );