@ai-matrx/canvas 0.5.0 → 0.5.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 +13 -0
- package/dist/react.cjs +76 -41
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +1 -1
- package/dist/react.d.ts +1 -1
- package/dist/react.js +79 -43
- package/dist/react.js.map +1 -1
- package/dist/styles.css +2 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.5.1
|
|
4
|
+
|
|
5
|
+
Measured live slowness (perf lane), fixed at the subscriptions:
|
|
6
|
+
|
|
7
|
+
- **A tab's data change re-renders only that tab.** The body layer subscribes to a structural signature (which tabs are where, active, open, expanded) instead of the whole state; each body host reads its own item and is memoized on primitives; a pane reads its own active item, never `state.items`. Updating tab A re-renders neither tab B's body nor another pane.
|
|
8
|
+
- **Stable context.** `CanvasProvider` memoizes its host ports and its context value (and `CanvasWindowScope` its scope), so a provider re-rendered by its parent no longer re-renders every canvas consumer.
|
|
9
|
+
- The tab strip is the tabs' offset parent, so keeping the active tab in view measures in strip coordinates.
|
|
10
|
+
- (Already in 0.5.0, now guarded too: putting the canvas away hides the column; hide → show remounts nothing.)
|
|
11
|
+
|
|
12
|
+
### Consumer action
|
|
13
|
+
|
|
14
|
+
- None.
|
|
15
|
+
|
|
3
16
|
## 0.5.0
|
|
4
17
|
|
|
5
18
|
Owner feedback after using the canvas live (Arman, 2026-10-02): a stable canvas that never dismounts, no UI traps, readable tabs, the shell's motion, content that knows its shape, and the pieces web and desktop both need.
|
package/dist/react.cjs
CHANGED
|
@@ -1701,8 +1701,17 @@ function CanvasProvider({
|
|
|
1701
1701
|
layoutRules,
|
|
1702
1702
|
regionWidth = null,
|
|
1703
1703
|
tabStrip = "auto",
|
|
1704
|
-
|
|
1704
|
+
itemActions,
|
|
1705
|
+
popOut,
|
|
1706
|
+
dockBack,
|
|
1707
|
+
showMenu,
|
|
1708
|
+
overlay,
|
|
1709
|
+
onNativeBodyFrame
|
|
1705
1710
|
}) {
|
|
1711
|
+
const hostPorts = (0, import_react2.useMemo)(
|
|
1712
|
+
() => ({ itemActions, popOut, dockBack, showMenu, overlay, onNativeBodyFrame }),
|
|
1713
|
+
[itemActions, popOut, dockBack, showMenu, overlay, onNativeBodyFrame]
|
|
1714
|
+
);
|
|
1706
1715
|
const portsRef = (0, import_react2.useRef)(hostPorts);
|
|
1707
1716
|
portsRef.current = hostPorts;
|
|
1708
1717
|
const [{ controller, layer, launcherEntries }] = (0, import_react2.useState)(() => ({
|
|
@@ -1744,7 +1753,10 @@ function CanvasProvider({
|
|
|
1744
1753
|
window.addEventListener("keydown", onKey);
|
|
1745
1754
|
return () => window.removeEventListener("keydown", onKey);
|
|
1746
1755
|
}, [controller, hotkeys, windowId]);
|
|
1747
|
-
const value =
|
|
1756
|
+
const value = (0, import_react2.useMemo)(
|
|
1757
|
+
() => ({ controller, windowId, ports: hostPorts, layer, regionWidth, tabStrip, launcherEntries }),
|
|
1758
|
+
[controller, windowId, hostPorts, layer, regionWidth, tabStrip, launcherEntries]
|
|
1759
|
+
);
|
|
1748
1760
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(CanvasContext.Provider, { value, children: [
|
|
1749
1761
|
children,
|
|
1750
1762
|
hostPorts.overlay ? null : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(CanvasDomOverlay, { layer })
|
|
@@ -1752,8 +1764,10 @@ function CanvasProvider({
|
|
|
1752
1764
|
}
|
|
1753
1765
|
function CanvasWindowScope({ windowId, children }) {
|
|
1754
1766
|
const parent = useCanvasContext();
|
|
1755
|
-
const
|
|
1756
|
-
|
|
1767
|
+
const value = (0, import_react2.useMemo)(() => {
|
|
1768
|
+
const id = canvasWindowId(windowId);
|
|
1769
|
+
return { ...parent, windowId: id, controller: parent.controller.forWindow(id) };
|
|
1770
|
+
}, [parent, windowId]);
|
|
1757
1771
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(CanvasContext.Provider, { value, children });
|
|
1758
1772
|
}
|
|
1759
1773
|
function useCanvasContext() {
|
|
@@ -2449,20 +2463,40 @@ function usePanePresentation(slots, paneId, isFullscreen, paneCount) {
|
|
|
2449
2463
|
[size.width, size.height, isFullscreen, paneCount]
|
|
2450
2464
|
);
|
|
2451
2465
|
}
|
|
2452
|
-
|
|
2466
|
+
var hostKey = (item) => item.instance ?? item.id;
|
|
2467
|
+
var SEP2 = "";
|
|
2468
|
+
var ROW = "";
|
|
2469
|
+
function layerSignature(state, windowId, onlyPane) {
|
|
2453
2470
|
const frame2 = canvasWindowFrame(state, windowId);
|
|
2454
|
-
if (!frame2) return
|
|
2455
|
-
const
|
|
2456
|
-
|
|
2471
|
+
if (!frame2) return "";
|
|
2472
|
+
const rows = [];
|
|
2473
|
+
const paneIds = listPaneIds(frame2.layout);
|
|
2474
|
+
for (const paneId of onlyPane ? [onlyPane] : paneIds) {
|
|
2457
2475
|
const pane = state.panes[paneId];
|
|
2458
2476
|
for (const itemId of pane?.itemIds ?? []) {
|
|
2459
2477
|
const item = state.items[itemId];
|
|
2460
|
-
if (item)
|
|
2478
|
+
if (item) rows.push({ item, row: [hostKey(item), itemId, paneId, pane?.activeItemId === itemId ? "1" : "0"].join(SEP2) });
|
|
2461
2479
|
}
|
|
2462
2480
|
}
|
|
2463
|
-
|
|
2481
|
+
rows.sort((a, b) => a.item.openedAt - b.item.openedAt || hostKey(a.item).localeCompare(hostKey(b.item)));
|
|
2482
|
+
const head = [frame2.isOpen ? "1" : "0", frame2.isFullscreen ? "1" : "0", frame2.focusedPaneId, String(paneIds.length)].join(SEP2);
|
|
2483
|
+
return [head, ...rows.map((r) => r.row)].join(ROW);
|
|
2484
|
+
}
|
|
2485
|
+
function parseSignature(signature) {
|
|
2486
|
+
if (!signature) return null;
|
|
2487
|
+
const [head = "", ...rows] = signature.split(ROW);
|
|
2488
|
+
const [open, full, focusedPaneId = "", count = "1"] = head.split(SEP2);
|
|
2489
|
+
return {
|
|
2490
|
+
isOpen: open === "1",
|
|
2491
|
+
isFullscreen: full === "1",
|
|
2492
|
+
focusedPaneId,
|
|
2493
|
+
paneCount: Number(count) || 1,
|
|
2494
|
+
placed: rows.map((row) => {
|
|
2495
|
+
const [hostId = "", itemId = "", paneId = "", active] = row.split(SEP2);
|
|
2496
|
+
return { hostId, itemId, paneId, active: active === "1" };
|
|
2497
|
+
})
|
|
2498
|
+
};
|
|
2464
2499
|
}
|
|
2465
|
-
var hostKey = (item) => item.instance ?? item.id;
|
|
2466
2500
|
function createPlacement(getRoot, slots, afterPlace) {
|
|
2467
2501
|
const hosts = /* @__PURE__ */ new Map();
|
|
2468
2502
|
let placing = false;
|
|
@@ -2505,57 +2539,54 @@ function createPlacement(getRoot, slots, afterPlace) {
|
|
|
2505
2539
|
function CanvasBodyLayer() {
|
|
2506
2540
|
const { windowId, layer } = useCanvasInternals();
|
|
2507
2541
|
const slots = useCanvasSlots();
|
|
2508
|
-
const
|
|
2542
|
+
const signature = useCanvasState((s) => layerSignature(s, windowId));
|
|
2509
2543
|
const root = (0, import_react7.useRef)(null);
|
|
2510
2544
|
const [placement] = (0, import_react7.useState)(
|
|
2511
2545
|
() => slots ? createPlacement(() => root.current, slots, () => layer.native.remeasure()) : null
|
|
2512
2546
|
);
|
|
2513
2547
|
useIsoLayoutEffect(() => placement?.place());
|
|
2514
2548
|
useIsoLayoutEffect(() => slots && placement ? slots.subscribe(placement.place) : void 0, [slots, placement]);
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
const paneCount = listPaneIds(frame2.layout).length;
|
|
2519
|
-
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { ref: root, className: "mxc-bodies", "data-mxc-bodies": windowId, children: placedItems(state, windowId).map(({ item, paneId, active }) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
2549
|
+
const shape = parseSignature(signature);
|
|
2550
|
+
if (!slots || !placement || !shape) return null;
|
|
2551
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { ref: root, className: "mxc-bodies", "data-mxc-bodies": windowId, children: shape.placed.map(({ hostId, itemId, paneId, active }) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
2520
2552
|
BodyHost,
|
|
2521
2553
|
{
|
|
2522
|
-
hostId
|
|
2523
|
-
|
|
2554
|
+
hostId,
|
|
2555
|
+
itemId,
|
|
2524
2556
|
paneId,
|
|
2525
|
-
visible:
|
|
2526
|
-
focused: active &&
|
|
2527
|
-
isFullscreen:
|
|
2528
|
-
paneCount,
|
|
2557
|
+
visible: shape.isOpen && active,
|
|
2558
|
+
focused: active && shape.focusedPaneId === paneId,
|
|
2559
|
+
isFullscreen: shape.isFullscreen,
|
|
2560
|
+
paneCount: shape.paneCount,
|
|
2529
2561
|
slots,
|
|
2530
2562
|
placement
|
|
2531
2563
|
},
|
|
2532
|
-
|
|
2564
|
+
hostId
|
|
2533
2565
|
)) });
|
|
2534
2566
|
}
|
|
2535
2567
|
function CanvasPaneInlineBodies({ paneId, slots }) {
|
|
2536
2568
|
const { windowId } = useCanvasInternals();
|
|
2537
|
-
const
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_jsx_runtime6.Fragment, { children: placedItems(state, windowId, paneId).map(({ item, active }) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
2569
|
+
const shape = parseSignature(useCanvasState((s) => layerSignature(s, windowId, paneId)));
|
|
2570
|
+
if (!shape) return null;
|
|
2571
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_jsx_runtime6.Fragment, { children: shape.placed.map(({ hostId, itemId, active }) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
2541
2572
|
BodyHost,
|
|
2542
2573
|
{
|
|
2543
|
-
hostId
|
|
2544
|
-
|
|
2574
|
+
hostId,
|
|
2575
|
+
itemId,
|
|
2545
2576
|
paneId,
|
|
2546
2577
|
visible: active,
|
|
2547
|
-
focused: active &&
|
|
2548
|
-
isFullscreen:
|
|
2549
|
-
paneCount,
|
|
2578
|
+
focused: active && shape.focusedPaneId === paneId,
|
|
2579
|
+
isFullscreen: shape.isFullscreen,
|
|
2580
|
+
paneCount: shape.paneCount,
|
|
2550
2581
|
slots,
|
|
2551
2582
|
placement: null
|
|
2552
2583
|
},
|
|
2553
|
-
|
|
2584
|
+
hostId
|
|
2554
2585
|
)) });
|
|
2555
2586
|
}
|
|
2556
|
-
|
|
2587
|
+
var BodyHost = (0, import_react7.memo)(function BodyHost2({
|
|
2557
2588
|
hostId,
|
|
2558
|
-
|
|
2589
|
+
itemId,
|
|
2559
2590
|
paneId,
|
|
2560
2591
|
visible,
|
|
2561
2592
|
focused,
|
|
@@ -2566,7 +2597,8 @@ function BodyHost({
|
|
|
2566
2597
|
}) {
|
|
2567
2598
|
const canvas = useCanvas();
|
|
2568
2599
|
const { windowId, ports } = useCanvasInternals();
|
|
2569
|
-
const
|
|
2600
|
+
const item = useCanvasState((s) => s.items[itemId]);
|
|
2601
|
+
const kind = useItemKind(item?.kind ?? "");
|
|
2570
2602
|
const element = (0, import_react7.useRef)(null);
|
|
2571
2603
|
const [everShown, setEverShown] = (0, import_react7.useState)(visible);
|
|
2572
2604
|
if (visible && !everShown) setEverShown(true);
|
|
@@ -2578,6 +2610,7 @@ function BodyHost({
|
|
|
2578
2610
|
if (!placement || !el) return void 0;
|
|
2579
2611
|
return placement.register(hostId, { element: el, paneId });
|
|
2580
2612
|
}, [placement, hostId, paneId]);
|
|
2613
|
+
if (!item) return null;
|
|
2581
2614
|
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
2582
2615
|
"div",
|
|
2583
2616
|
{
|
|
@@ -2611,7 +2644,7 @@ function BodyHost({
|
|
|
2611
2644
|
) }) }) : null
|
|
2612
2645
|
}
|
|
2613
2646
|
);
|
|
2614
|
-
}
|
|
2647
|
+
});
|
|
2615
2648
|
function ItemContent({
|
|
2616
2649
|
item,
|
|
2617
2650
|
kind,
|
|
@@ -2747,13 +2780,15 @@ function CanvasPaneView({ paneId }) {
|
|
|
2747
2780
|
const [ownSlots] = (0, import_react8.useState)(createCanvasSlots);
|
|
2748
2781
|
const slots = columnSlots ?? ownSlots;
|
|
2749
2782
|
const pane = useCanvasState((s) => s.panes[paneId]);
|
|
2750
|
-
const
|
|
2783
|
+
const active = useCanvasState((s) => {
|
|
2784
|
+
const activeId = s.panes[paneId]?.activeItemId;
|
|
2785
|
+
return activeId ? s.items[activeId] : void 0;
|
|
2786
|
+
});
|
|
2751
2787
|
const isFullscreen = useCanvasState((s) => selectCanvasIsFullscreen(s, windowId));
|
|
2752
2788
|
const isOpen = useCanvasState((s) => selectCanvasIsOpen(s, windowId));
|
|
2753
2789
|
const focused = useCanvasState((s) => selectCanvasFocusedPaneId(s, windowId) === paneId);
|
|
2754
2790
|
const paneCount = useCanvasState((s) => selectCanvasPaneCount(s, windowId));
|
|
2755
2791
|
const role = useCanvasState((s) => windowId === CANVAS_MAIN_WINDOW ? "main" : s.windows[windowId]?.role ?? "window");
|
|
2756
|
-
const active = pane?.activeItemId ? items[pane.activeItemId] : void 0;
|
|
2757
2792
|
const canGoBack = useCanvasState((s) => active ? selectCanvasCanGoBack(s, active.id) : false);
|
|
2758
2793
|
const kind = useItemKind(active?.kind ?? "");
|
|
2759
2794
|
const sectionRef = (0, import_react8.useRef)(null);
|
|
@@ -2842,7 +2877,7 @@ function TabStrip({ paneId, itemIds, activeItemId }) {
|
|
|
2842
2877
|
const reveal = () => {
|
|
2843
2878
|
const tab = el.querySelector('[role="tab"][aria-selected="true"]');
|
|
2844
2879
|
if (!tab) return;
|
|
2845
|
-
const left = tab.offsetLeft
|
|
2880
|
+
const left = tab.offsetLeft;
|
|
2846
2881
|
const right = left + tab.offsetWidth;
|
|
2847
2882
|
if (left < el.scrollLeft) el.scrollLeft = left;
|
|
2848
2883
|
else if (right > el.scrollLeft + el.clientWidth) el.scrollLeft = right - el.clientWidth;
|