@statelyai/flow-react 0.9.5 → 0.9.6
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/README.md +1 -1
- package/dist/esm/index.js +73 -31
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@ The React package owns React lifecycle, context, selector subscriptions, and a c
|
|
|
17
17
|
|
|
18
18
|
<!-- transient entity rendering split between src/StatelyFlow.tsx and flow-dom src/entity-geometry.ts -->
|
|
19
19
|
|
|
20
|
-
Optimization is part of the architecture, not a later cleanup pass. Components subscribe only to durable content and interaction state. During drag and animated layout sessions, `@statelyai/flow-dom` batches node rectangles, edge paths, endpoint handles, label positions, and multi-selection bounds into imperative animation-frame writes, so React commits only at transient-session boundaries. Default edge paths expose `data-flow-edge-segment`; custom edge renderers can use the same indexed attribute to join the imperative path updates. Router-provided `PathData.svgPath` geometry is honored by both initial React rendering and those imperative updates.
|
|
20
|
+
Optimization is part of the architecture, not a later cleanup pass. Components subscribe only to durable content and interaction state. During drag and animated layout sessions, `@statelyai/flow-dom` batches node rectangles, default group child-area and header rectangles, edge paths, endpoint handles, label positions, and multi-selection bounds into imperative animation-frame writes, so React commits only at transient-session boundaries. Default group headers keep projected styles on a separate element from imperative geometry, preserving projected transforms during drag. Default edge paths expose `data-flow-edge-segment`; custom edge renderers can use the same indexed attribute to join the imperative path updates. Existing custom paths whose `d` exactly matches a routed segment are also bound automatically, including duplicate hit strokes; unrelated decoration paths are left alone. Router-provided `PathData.svgPath` geometry is honored by both initial React rendering and those imperative updates.
|
|
21
21
|
|
|
22
22
|
The graph data structure is the source of truth for graph content, not for transient UI state. Core node and edge data should not grow flags like `selected: true` or `dragging: true`; selection, focus, dragging, hover, connection state, and similar interaction state live in the flow store and are derived into render props such as `selected` or `dragging`.
|
|
23
23
|
|
package/dist/esm/index.js
CHANGED
|
@@ -1588,6 +1588,7 @@ const NodeView = memo(function NodeView(props) {
|
|
|
1588
1588
|
highlights
|
|
1589
1589
|
})
|
|
1590
1590
|
}) : node.group ? /* @__PURE__ */ jsx(DefaultGroupNode, {
|
|
1591
|
+
geometryObserver: props.geometryObserver,
|
|
1591
1592
|
flow: props.flow,
|
|
1592
1593
|
node,
|
|
1593
1594
|
bounds,
|
|
@@ -1615,51 +1616,92 @@ const NodeView = memo(function NodeView(props) {
|
|
|
1615
1616
|
* `engine.getGroupGeometry`; custom renderers can do the same.
|
|
1616
1617
|
*/
|
|
1617
1618
|
function DefaultGroupNode(props) {
|
|
1619
|
+
const childAreaRef = useRef(null);
|
|
1620
|
+
const contentRef = useRef(null);
|
|
1621
|
+
useLayoutEffect(() => {
|
|
1622
|
+
const observePart = (element, part) => {
|
|
1623
|
+
if (!element) return () => {};
|
|
1624
|
+
return props.geometryObserver.observeRect(element, {
|
|
1625
|
+
id: props.node.id,
|
|
1626
|
+
getRect: () => {
|
|
1627
|
+
const geometry = props.flow.internals.engine.getGroupGeometry(props.node.id);
|
|
1628
|
+
const bounds = props.flow.internals.engine.visualBounds(props.node.id);
|
|
1629
|
+
if (!geometry || !bounds) return null;
|
|
1630
|
+
return {
|
|
1631
|
+
...geometry[part],
|
|
1632
|
+
x: geometry[part].x - bounds.x,
|
|
1633
|
+
y: geometry[part].y - bounds.y
|
|
1634
|
+
};
|
|
1635
|
+
}
|
|
1636
|
+
});
|
|
1637
|
+
};
|
|
1638
|
+
const stopChildArea = observePart(childAreaRef.current, "childArea");
|
|
1639
|
+
const stopContent = observePart(contentRef.current, "contentBounds");
|
|
1640
|
+
return () => {
|
|
1641
|
+
stopChildArea();
|
|
1642
|
+
stopContent();
|
|
1643
|
+
};
|
|
1644
|
+
}, [
|
|
1645
|
+
props.flow,
|
|
1646
|
+
props.geometryObserver,
|
|
1647
|
+
props.node.id
|
|
1648
|
+
]);
|
|
1618
1649
|
const geometry = props.flow.internals.engine.getGroupGeometry(props.node.id);
|
|
1619
1650
|
const label = props.node.data?.label;
|
|
1620
1651
|
const badges = decorationsToArray(props.attrs.badge);
|
|
1621
1652
|
const title = decorationTitle(props.attrs.tooltip);
|
|
1622
1653
|
if (!geometry) return /* @__PURE__ */ jsx(DefaultNode, { ...props });
|
|
1623
1654
|
const relative = (rect) => ({
|
|
1624
|
-
left:
|
|
1625
|
-
top:
|
|
1655
|
+
left: 0,
|
|
1656
|
+
top: 0,
|
|
1657
|
+
transform: `translate(${rect.x - props.bounds.x}px, ${rect.y - props.bounds.y}px)`,
|
|
1626
1658
|
width: rect.width,
|
|
1627
1659
|
height: rect.height
|
|
1628
1660
|
});
|
|
1629
1661
|
const dropTarget = props.highlights.some((entry) => entry.kind === "drop-target");
|
|
1630
|
-
return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("div", {
|
|
1631
|
-
|
|
1632
|
-
...relative(geometry.childArea),
|
|
1633
|
-
boxSizing: "border-box",
|
|
1634
|
-
border: `2px dashed ${dropTarget ? "#16a34a" : props.selected ? "#93c5fd" : props.attrs.color ?? "#cbd5e1"}`,
|
|
1635
|
-
...dropTarget ? { background: "rgba(22, 163, 74, 0.08)" } : null,
|
|
1636
|
-
borderRadius: 10,
|
|
1637
|
-
background: "rgba(148, 163, 184, 0.07)",
|
|
1638
|
-
pointerEvents: "none"
|
|
1639
|
-
} }), /* @__PURE__ */ jsxs("div", {
|
|
1640
|
-
"data-flow-drag-handle": true,
|
|
1641
|
-
title,
|
|
1662
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("div", {
|
|
1663
|
+
ref: childAreaRef,
|
|
1642
1664
|
style: {
|
|
1643
1665
|
position: "absolute",
|
|
1644
|
-
...relative(geometry.
|
|
1666
|
+
...relative(geometry.childArea),
|
|
1645
1667
|
boxSizing: "border-box",
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
...props.attrs.style
|
|
1668
|
+
border: `2px dashed ${dropTarget ? "#16a34a" : props.selected ? "#93c5fd" : props.attrs.color ?? "#cbd5e1"}`,
|
|
1669
|
+
...dropTarget ? { background: "rgba(22, 163, 74, 0.08)" } : null,
|
|
1670
|
+
borderRadius: 10,
|
|
1671
|
+
background: "rgba(148, 163, 184, 0.07)",
|
|
1672
|
+
pointerEvents: "none"
|
|
1673
|
+
}
|
|
1674
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
1675
|
+
ref: contentRef,
|
|
1676
|
+
style: {
|
|
1677
|
+
position: "absolute",
|
|
1678
|
+
...relative(geometry.contentBounds)
|
|
1658
1679
|
},
|
|
1659
|
-
children:
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1680
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
1681
|
+
"data-flow-drag-handle": true,
|
|
1682
|
+
title,
|
|
1683
|
+
style: {
|
|
1684
|
+
width: "100%",
|
|
1685
|
+
height: "100%",
|
|
1686
|
+
boxSizing: "border-box",
|
|
1687
|
+
display: "flex",
|
|
1688
|
+
alignItems: "center",
|
|
1689
|
+
padding: "0 12px",
|
|
1690
|
+
border: `2px solid ${props.selected ? "#2563eb" : props.attrs.color ?? "#94a3b8"}`,
|
|
1691
|
+
borderRadius: 8,
|
|
1692
|
+
background: props.selected ? "#eff6ff" : "#f8fafc",
|
|
1693
|
+
color: "#0f172a",
|
|
1694
|
+
font: "600 13px/1.2 system-ui, sans-serif",
|
|
1695
|
+
cursor: props.dragging ? "grabbing" : "grab",
|
|
1696
|
+
pointerEvents: "auto",
|
|
1697
|
+
userSelect: "none",
|
|
1698
|
+
...props.attrs.style
|
|
1699
|
+
},
|
|
1700
|
+
children: [/* @__PURE__ */ jsx("span", { children: String(label ?? props.node.id) }), badges.length ? /* @__PURE__ */ jsx("span", {
|
|
1701
|
+
style: defaultBadgeListStyle,
|
|
1702
|
+
children: badges
|
|
1703
|
+
}) : null]
|
|
1704
|
+
})
|
|
1663
1705
|
})] });
|
|
1664
1706
|
}
|
|
1665
1707
|
function DefaultNode(props) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@statelyai/flow-react",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.6",
|
|
4
4
|
"description": "Thin React adapter for @statelyai/flow with core and DOM re-exports.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -34,8 +34,8 @@
|
|
|
34
34
|
"license": "MIT",
|
|
35
35
|
"dependencies": {
|
|
36
36
|
"use-sync-external-store": "^1.6.0",
|
|
37
|
-
"@statelyai/flow": "0.
|
|
38
|
-
"@statelyai/flow-dom": "3.4.
|
|
37
|
+
"@statelyai/flow": "0.11.0",
|
|
38
|
+
"@statelyai/flow-dom": "3.4.6"
|
|
39
39
|
},
|
|
40
40
|
"peerDependencies": {
|
|
41
41
|
"react": "^18.2.0 || ^19.0.0",
|
|
@@ -45,6 +45,7 @@
|
|
|
45
45
|
"@dagrejs/dagre": "^3.0.0",
|
|
46
46
|
"@playwright/test": "^1.60.0",
|
|
47
47
|
"@statelyai/graph": "^2.1.0",
|
|
48
|
+
"@statelyai/layout": "^0.0.5",
|
|
48
49
|
"@storybook/addon-docs": "^10.1.2",
|
|
49
50
|
"@storybook/react-vite": "^10.1.2",
|
|
50
51
|
"@testing-library/react": "^16.1.0",
|