@ai-matrx/canvas 0.8.0 → 0.8.2

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,11 +1,25 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.8.2
4
+ ### Unreleased
5
+
6
+ - The tab strip follows the tablist keyboard pattern: Left/Right (wrapping), Home and End move to and open the neighbouring tab. Only the active tab is in the Tab order, so a background tab was unreachable without a mouse.
7
+ - Consumer action: none.
8
+
9
+ ## 0.8.1
10
+
11
+ 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.
12
+
13
+ - 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.
14
+ - Keeping the current tab in view brings a split's joined tab into view whole, not only its current half.
15
+ - Consumer action: none.
16
+
3
17
  ## 0.8.0
4
18
 
5
19
  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."
6
20
 
7
21
  - **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).
8
- - **Tabs shrink evenly to a readable floor, then scroll.** Every tab: `flex: 0 1 12rem`, 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.
22
+ - **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.
9
23
  - **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`.
10
24
 
11
25
  ### Consumer action
@@ -223,3 +237,4 @@ One canvas for the web app and the desktop app: the package now owns the pane ch
223
237
 
224
238
  - Replace workspace Canvas imports with `@ai-matrx/canvas` at `latest`; remove source aliases and transpile overrides.
225
239
  - Import `@ai-matrx/canvas/styles.css` and optionally `@ai-matrx/canvas/tokens.css`; keep the existing host identity, store and theme ports.
240
+
package/dist/react.cjs CHANGED
@@ -3295,8 +3295,9 @@ function TabStrip({
3295
3295
  if (!el || !activeItemId) return;
3296
3296
  const tab = [...el.querySelectorAll("[data-mxc-tab]")].find((node) => node.dataset.mxcTab === activeItemId);
3297
3297
  if (!tab) return;
3298
+ const target = tab.closest(".mxc-tab-group") ?? tab;
3298
3299
  const box = el.getBoundingClientRect();
3299
- const rect = tab.getBoundingClientRect();
3300
+ const rect = target.getBoundingClientRect();
3300
3301
  if (rect.left < box.left) el.scrollLeft -= box.left - rect.left;
3301
3302
  else if (rect.right > box.right) el.scrollLeft += rect.right - box.right;
3302
3303
  }, [activeItemId, width, tabs.length]);
@@ -3323,24 +3324,49 @@ function TabStrip({
3323
3324
  "data-overflow": overflow ? "" : void 0,
3324
3325
  style: { ["--mxc-tab-min"]: `${CANVAS_TAB_MIN_WIDTH}px` },
3325
3326
  children: [
3326
- /* @__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(
3327
- (entry) => entry.type === "tab" ? tabView(entry.tab) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3328
- "div",
3329
- {
3330
- className: "mxc-tab-group",
3331
- "data-split": "",
3332
- role: "presentation",
3333
- style: { ["--mxc-group-n"]: entry.tabs.length },
3334
- children: entry.tabs.map(tabView)
3335
- },
3336
- `split:${entry.tabs.map((tab) => tab.itemId).join("|")}`
3337
- )
3338
- ) }),
3327
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3328
+ "div",
3329
+ {
3330
+ ref: list,
3331
+ className: "mxc-tabs",
3332
+ role: "tablist",
3333
+ "aria-label": "Canvas tabs",
3334
+ "aria-multiselectable": joined ? true : void 0,
3335
+ onKeyDown: moveTabFocus,
3336
+ children: entries.map(
3337
+ (entry) => entry.type === "tab" ? tabView(entry.tab) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3338
+ "div",
3339
+ {
3340
+ className: "mxc-tab-group",
3341
+ "data-split": "",
3342
+ role: "presentation",
3343
+ style: { ["--mxc-group-n"]: entry.tabs.length },
3344
+ children: entry.tabs.map(tabView)
3345
+ },
3346
+ `split:${entry.tabs.map((tab) => tab.itemId).join("|")}`
3347
+ )
3348
+ )
3349
+ }
3350
+ ),
3339
3351
  overflow ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(TabListMenu, { tabs, activeItemId }) : null
3340
3352
  ]
3341
3353
  }
3342
3354
  );
3343
3355
  }
3356
+ function moveTabFocus(event) {
3357
+ const keys = ["ArrowLeft", "ArrowRight", "Home", "End"];
3358
+ if (!keys.includes(event.key) || event.altKey || event.ctrlKey || event.metaKey) return;
3359
+ const tabs = [...event.currentTarget.querySelectorAll('[role="tab"]')];
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
+ }
3344
3370
  function TabListMenu({ tabs, activeItemId }) {
3345
3371
  const canvas = useCanvas();
3346
3372
  const { ports, layer } = useCanvasInternals();