@statelyai/flow-react 0.9.4 → 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.d.ts +2 -4
- package/dist/esm/index.js +264 -71
- 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.d.ts
CHANGED
|
@@ -341,10 +341,8 @@ type MiniMapProps = {
|
|
|
341
341
|
renderNode?: (node: FlowNode, rect: Rect) => ReactNode;
|
|
342
342
|
};
|
|
343
343
|
/**
|
|
344
|
-
* Overview map.
|
|
345
|
-
*
|
|
346
|
-
* controller viewport command — this component is exactly what you would write in
|
|
347
|
-
* userland, kept here as a convenience.
|
|
344
|
+
* Overview map. Projection and navigation math live in core; this adapter owns
|
|
345
|
+
* only React rendering and the browser pointer session.
|
|
348
346
|
*/
|
|
349
347
|
declare function MiniMap(props: MiniMapProps): import("react").JSX.Element;
|
|
350
348
|
//#endregion
|
package/dist/esm/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { a as FlowInstanceProvider, i as LinesBackground, n as CrossBackground, o as useFlowInstance, r as DotsBackground, s as useOptionalFlowInstance, t as Background } from "./backgrounds2.js";
|
|
2
2
|
import { applyFlowColorMode, attachContainer, attachInput, attachViewportTransform, clientToCanvas, computeRenderableIds, createEntityGeometryObserver, createFlowView, createMeasurementObserver, nodePositionStyle, renderableIdsEqual, resolveFlowColorMode, viewportStyle } from "@statelyai/flow-dom";
|
|
3
|
-
import { CONNECTION_PREVIEW_EDGE_ID, areNodesMeasured, createConnectionPreviewGraph, createEntityChangeNotifier, createFlow, createFlowInputRuntime, getAnchorTransform, getAutoPanDelta, getMinimapProjection, getProjectedEdgeAttrs, getProjectedNodeAttrs, getToolbarPlacement, markerToSVG, pathToSVG, profileModesForContext, profilePoliciesForContext, viewportCenteredOn } from "@statelyai/flow";
|
|
4
|
-
import { createContext, memo, useCallback, useContext, useEffect, useId, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
|
5
|
-
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { CONNECTION_PREVIEW_EDGE_ID, areNodesMeasured, createConnectionPreviewGraph, createEntityChangeNotifier, createFlow, createFlowInputRuntime, getAnchorTransform, getAutoPanDelta, getMinimapProjection, getMinimapViewportCenter, getProjectedEdgeAttrs, getProjectedNodeAttrs, getToolbarPlacement, getVisibleBounds, markerToSVG, pathToSVG, profileModesForContext, profilePoliciesForContext, viewportCenteredOn } from "@statelyai/flow";
|
|
4
|
+
import { Fragment, createContext, memo, useCallback, useContext, useEffect, useId, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
|
5
|
+
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
6
6
|
import { useSyncExternalStoreWithSelector } from "use-sync-external-store/shim/with-selector";
|
|
7
7
|
import { createPortal } from "react-dom";
|
|
8
8
|
export * from "@statelyai/flow-dom";
|
|
@@ -323,7 +323,7 @@ function useFlowInput(flow, containerRef, options = {}) {
|
|
|
323
323
|
store: flow.internals.store,
|
|
324
324
|
runtime,
|
|
325
325
|
keyboardTarget,
|
|
326
|
-
autoPan:
|
|
326
|
+
autoPan: getAutoPanDelta
|
|
327
327
|
});
|
|
328
328
|
}, [
|
|
329
329
|
flow,
|
|
@@ -1114,7 +1114,7 @@ function ConnectionPreviewEdge(props) {
|
|
|
1114
1114
|
});
|
|
1115
1115
|
}
|
|
1116
1116
|
function DefaultEdge(props) {
|
|
1117
|
-
return /* @__PURE__ */ jsxs(Fragment, { children: [props.pathData.segments.map((segment, index) => {
|
|
1117
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [props.pathData.segments.map((segment, index) => {
|
|
1118
1118
|
const d = pathToSVG(segment);
|
|
1119
1119
|
return /* @__PURE__ */ jsxs("g", { children: [/* @__PURE__ */ jsx("path", {
|
|
1120
1120
|
d,
|
|
@@ -1190,7 +1190,7 @@ function DefaultMarkerDefinitions(props) {
|
|
|
1190
1190
|
] });
|
|
1191
1191
|
}
|
|
1192
1192
|
function EdgeHitTarget(props) {
|
|
1193
|
-
return /* @__PURE__ */ jsx(Fragment, { children: props.pathData.segments.map((segment, index) => {
|
|
1193
|
+
return /* @__PURE__ */ jsx(Fragment$1, { children: props.pathData.segments.map((segment, index) => {
|
|
1194
1194
|
return /* @__PURE__ */ jsx("path", {
|
|
1195
1195
|
d: pathToSVG(segment),
|
|
1196
1196
|
"data-flow-edge-segment": index,
|
|
@@ -1211,7 +1211,7 @@ function EdgeEndpointHandles(props) {
|
|
|
1211
1211
|
if (!first || !last) return null;
|
|
1212
1212
|
const radius = 6 / props.flow.getContext().viewport.zoom;
|
|
1213
1213
|
const strokeWidth = 2 / props.flow.getContext().viewport.zoom;
|
|
1214
|
-
return /* @__PURE__ */ jsx(Fragment, { children: [{
|
|
1214
|
+
return /* @__PURE__ */ jsx(Fragment$1, { children: [{
|
|
1215
1215
|
end: "source",
|
|
1216
1216
|
point: first,
|
|
1217
1217
|
cursor: "crosshair"
|
|
@@ -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, { 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) {
|
|
@@ -1812,35 +1854,115 @@ function FlowToolbar(props) {
|
|
|
1812
1854
|
}
|
|
1813
1855
|
//#endregion
|
|
1814
1856
|
//#region src/MiniMap.tsx
|
|
1857
|
+
const DRAG_JITTER_THRESHOLD_SQ = 4;
|
|
1858
|
+
function containsPoint(rect, point) {
|
|
1859
|
+
return point.x >= rect.x && point.x <= rect.x + rect.width && point.y >= rect.y && point.y <= rect.y + rect.height;
|
|
1860
|
+
}
|
|
1861
|
+
function setRect(element, rect) {
|
|
1862
|
+
if (!element) return;
|
|
1863
|
+
element.setAttribute("x", String(rect.x));
|
|
1864
|
+
element.setAttribute("y", String(rect.y));
|
|
1865
|
+
element.setAttribute("width", String(rect.width));
|
|
1866
|
+
element.setAttribute("height", String(rect.height));
|
|
1867
|
+
}
|
|
1868
|
+
function projectionTransform(from, to) {
|
|
1869
|
+
const scale = to.scale / from.scale;
|
|
1870
|
+
const fromOrigin = from.toMinimap({
|
|
1871
|
+
x: 0,
|
|
1872
|
+
y: 0
|
|
1873
|
+
});
|
|
1874
|
+
const toOrigin = to.toMinimap({
|
|
1875
|
+
x: 0,
|
|
1876
|
+
y: 0
|
|
1877
|
+
});
|
|
1878
|
+
return `translate(${toOrigin.x - fromOrigin.x * scale} ${toOrigin.y - fromOrigin.y * scale}) scale(${scale})`;
|
|
1879
|
+
}
|
|
1815
1880
|
/**
|
|
1816
|
-
* Overview map.
|
|
1817
|
-
*
|
|
1818
|
-
* controller viewport command — this component is exactly what you would write in
|
|
1819
|
-
* userland, kept here as a convenience.
|
|
1881
|
+
* Overview map. Projection and navigation math live in core; this adapter owns
|
|
1882
|
+
* only React rendering and the browser pointer session.
|
|
1820
1883
|
*/
|
|
1821
1884
|
function MiniMap(props) {
|
|
1822
1885
|
const flow = useFlowInstance();
|
|
1823
1886
|
const width = props.width ?? 200;
|
|
1824
1887
|
const height = props.height ?? 140;
|
|
1825
1888
|
const nodes = useFlowSelector((context) => context.graph.nodes);
|
|
1826
|
-
const
|
|
1889
|
+
const measurements = useFlowSelector((context) => context.measurements);
|
|
1827
1890
|
const viewportSize = useFlowSelector((context) => context.viewportSize);
|
|
1828
|
-
const
|
|
1829
|
-
const
|
|
1830
|
-
|
|
1831
|
-
|
|
1891
|
+
const markerGroupRef = useRef(null);
|
|
1892
|
+
const viewportRectRef = useRef(null);
|
|
1893
|
+
const sessionRef = useRef(null);
|
|
1894
|
+
const endSessionRef = useRef(null);
|
|
1895
|
+
const paintViewportRef = useRef(() => {});
|
|
1896
|
+
const contentBounds = useMemo(() => {
|
|
1897
|
+
return flow.getGraphBounds();
|
|
1898
|
+
}, [
|
|
1899
|
+
flow,
|
|
1900
|
+
nodes,
|
|
1901
|
+
measurements
|
|
1902
|
+
]);
|
|
1903
|
+
const nodeBounds = useMemo(() => {
|
|
1904
|
+
return nodes.map((node) => flow.internals.engine.visualBounds(node.id));
|
|
1905
|
+
}, [
|
|
1906
|
+
flow,
|
|
1907
|
+
nodes,
|
|
1908
|
+
measurements
|
|
1909
|
+
]);
|
|
1910
|
+
const baseProjection = useMemo(() => getMinimapProjection({
|
|
1911
|
+
contentBounds,
|
|
1912
|
+
viewport: flow.getSnapshot().viewport,
|
|
1832
1913
|
viewportSize,
|
|
1833
1914
|
width,
|
|
1834
1915
|
height,
|
|
1835
1916
|
padding: props.padding
|
|
1836
|
-
})
|
|
1837
|
-
|
|
1917
|
+
}), [
|
|
1918
|
+
contentBounds,
|
|
1919
|
+
flow,
|
|
1920
|
+
height,
|
|
1921
|
+
props.padding,
|
|
1922
|
+
viewportSize,
|
|
1923
|
+
width
|
|
1924
|
+
]);
|
|
1925
|
+
const projectedNodes = useMemo(() => nodeBounds.map((bounds) => baseProjection.rectToMinimap(bounds)), [baseProjection, nodeBounds]);
|
|
1926
|
+
const initialViewport = flow.getSnapshot().viewport;
|
|
1927
|
+
const viewportRect = baseProjection.rectToMinimap(getVisibleBounds(initialViewport, viewportSize));
|
|
1928
|
+
paintViewportRef.current = (nextViewport) => {
|
|
1929
|
+
const projection = sessionRef.current?.projection ?? getMinimapProjection({
|
|
1930
|
+
contentBounds,
|
|
1931
|
+
viewport: nextViewport,
|
|
1932
|
+
viewportSize,
|
|
1933
|
+
width,
|
|
1934
|
+
height,
|
|
1935
|
+
padding: props.padding
|
|
1936
|
+
});
|
|
1937
|
+
markerGroupRef.current?.setAttribute("transform", projectionTransform(baseProjection, projection));
|
|
1938
|
+
setRect(viewportRectRef.current, projection.rectToMinimap(getVisibleBounds(nextViewport, viewportSize)));
|
|
1939
|
+
};
|
|
1940
|
+
useLayoutEffect(() => {
|
|
1941
|
+
paintViewportRef.current(flow.getSnapshot().viewport);
|
|
1942
|
+
}, [
|
|
1943
|
+
baseProjection,
|
|
1944
|
+
flow,
|
|
1945
|
+
nodeBounds
|
|
1946
|
+
]);
|
|
1947
|
+
useEffect(() => flow.listen((context) => context.viewport, (nextViewport) => paintViewportRef.current(nextViewport)), [flow]);
|
|
1948
|
+
useEffect(() => () => endSessionRef.current?.(), []);
|
|
1949
|
+
const pointFromEvent = (event, activeProjection) => {
|
|
1838
1950
|
const rect = event.currentTarget.getBoundingClientRect();
|
|
1839
|
-
|
|
1951
|
+
return activeProjection.toCanvas({
|
|
1840
1952
|
x: event.clientX - rect.left,
|
|
1841
1953
|
y: event.clientY - rect.top
|
|
1842
1954
|
});
|
|
1843
|
-
|
|
1955
|
+
};
|
|
1956
|
+
const navigate = (event, session) => {
|
|
1957
|
+
const viewport = flow.getSnapshot().viewport;
|
|
1958
|
+
const visibleBounds = getVisibleBounds(viewport, viewportSize);
|
|
1959
|
+
const center = getMinimapViewportCenter({
|
|
1960
|
+
point: pointFromEvent(event, session.projection),
|
|
1961
|
+
grabOffset: session.grabOffset,
|
|
1962
|
+
contentBounds,
|
|
1963
|
+
viewportBounds: visibleBounds
|
|
1964
|
+
});
|
|
1965
|
+
flow.viewport.set(viewportCenteredOn(center, viewportSize, viewport.zoom));
|
|
1844
1966
|
};
|
|
1845
1967
|
return /* @__PURE__ */ jsxs("svg", {
|
|
1846
1968
|
"aria-label": "minimap",
|
|
@@ -1848,19 +1970,84 @@ function MiniMap(props) {
|
|
|
1848
1970
|
height,
|
|
1849
1971
|
className: props.className,
|
|
1850
1972
|
onPointerDown: (event) => {
|
|
1851
|
-
|
|
1852
|
-
|
|
1973
|
+
if (event.button !== 0 || nodes.length === 0) return;
|
|
1974
|
+
endSessionRef.current?.();
|
|
1975
|
+
const viewport = flow.getSnapshot().viewport;
|
|
1976
|
+
const currentProjection = getMinimapProjection({
|
|
1977
|
+
contentBounds,
|
|
1978
|
+
viewport,
|
|
1979
|
+
viewportSize,
|
|
1980
|
+
width,
|
|
1981
|
+
height,
|
|
1982
|
+
padding: props.padding
|
|
1983
|
+
});
|
|
1984
|
+
const pointerPoint = pointFromEvent(event, currentProjection);
|
|
1985
|
+
const visibleBounds = getVisibleBounds(viewport, viewportSize);
|
|
1986
|
+
const viewportCenter = {
|
|
1987
|
+
x: visibleBounds.x + visibleBounds.width / 2,
|
|
1988
|
+
y: visibleBounds.y + visibleBounds.height / 2
|
|
1989
|
+
};
|
|
1990
|
+
const grabbedLens = containsPoint(visibleBounds, pointerPoint);
|
|
1991
|
+
const session = {
|
|
1992
|
+
pointerId: event.pointerId,
|
|
1993
|
+
originClient: {
|
|
1994
|
+
x: event.clientX,
|
|
1995
|
+
y: event.clientY
|
|
1996
|
+
},
|
|
1997
|
+
grabOffset: grabbedLens ? {
|
|
1998
|
+
x: pointerPoint.x - viewportCenter.x,
|
|
1999
|
+
y: pointerPoint.y - viewportCenter.y
|
|
2000
|
+
} : {
|
|
2001
|
+
x: 0,
|
|
2002
|
+
y: 0
|
|
2003
|
+
},
|
|
2004
|
+
projection: currentProjection
|
|
2005
|
+
};
|
|
2006
|
+
sessionRef.current = session;
|
|
2007
|
+
const element = event.currentTarget;
|
|
2008
|
+
const body = element.ownerDocument.body;
|
|
2009
|
+
const finish = (pointerId) => {
|
|
2010
|
+
if (pointerId !== void 0 && pointerId !== session.pointerId) return;
|
|
2011
|
+
if (sessionRef.current?.pointerId !== session.pointerId) return;
|
|
2012
|
+
sessionRef.current = null;
|
|
2013
|
+
endSessionRef.current = null;
|
|
2014
|
+
body.removeEventListener("pointerup", onPointerEnd);
|
|
2015
|
+
body.removeEventListener("pointercancel", onPointerEnd);
|
|
2016
|
+
body.removeEventListener("contextmenu", onContextMenu, true);
|
|
2017
|
+
try {
|
|
2018
|
+
if (element.hasPointerCapture?.(session.pointerId)) element.releasePointerCapture(session.pointerId);
|
|
2019
|
+
} catch {}
|
|
2020
|
+
paintViewportRef.current(flow.getSnapshot().viewport);
|
|
2021
|
+
};
|
|
2022
|
+
const onPointerEnd = (pointerEvent) => finish(pointerEvent.pointerId);
|
|
2023
|
+
const onContextMenu = () => finish();
|
|
2024
|
+
endSessionRef.current = () => finish();
|
|
2025
|
+
body.addEventListener("pointerup", onPointerEnd);
|
|
2026
|
+
body.addEventListener("pointercancel", onPointerEnd);
|
|
2027
|
+
body.addEventListener("contextmenu", onContextMenu, true);
|
|
1853
2028
|
try {
|
|
1854
|
-
|
|
2029
|
+
element.setPointerCapture(event.pointerId);
|
|
1855
2030
|
} catch {}
|
|
2031
|
+
if (!grabbedLens) navigate(event, session);
|
|
1856
2032
|
},
|
|
1857
2033
|
onPointerMove: (event) => {
|
|
1858
|
-
|
|
2034
|
+
const session = sessionRef.current;
|
|
2035
|
+
if (!session || event.pointerId !== session.pointerId) return;
|
|
2036
|
+
const dx = event.clientX - session.originClient.x;
|
|
2037
|
+
const dy = event.clientY - session.originClient.y;
|
|
2038
|
+
if (dx * dx + dy * dy <= DRAG_JITTER_THRESHOLD_SQ) return;
|
|
2039
|
+
navigate(event, session);
|
|
1859
2040
|
},
|
|
1860
2041
|
onPointerUp: (event) => {
|
|
1861
|
-
|
|
1862
|
-
if (event.currentTarget.hasPointerCapture(event.pointerId)) event.currentTarget.releasePointerCapture(event.pointerId);
|
|
2042
|
+
if (sessionRef.current?.pointerId === event.pointerId) endSessionRef.current?.();
|
|
1863
2043
|
},
|
|
2044
|
+
onPointerCancel: (event) => {
|
|
2045
|
+
if (sessionRef.current?.pointerId === event.pointerId) endSessionRef.current?.();
|
|
2046
|
+
},
|
|
2047
|
+
onLostPointerCapture: (event) => {
|
|
2048
|
+
if (sessionRef.current?.pointerId === event.pointerId) endSessionRef.current?.();
|
|
2049
|
+
},
|
|
2050
|
+
onContextMenu: () => endSessionRef.current?.(),
|
|
1864
2051
|
style: {
|
|
1865
2052
|
position: "absolute",
|
|
1866
2053
|
right: 12,
|
|
@@ -1873,21 +2060,27 @@ function MiniMap(props) {
|
|
|
1873
2060
|
pointerEvents: "auto",
|
|
1874
2061
|
...props.style
|
|
1875
2062
|
},
|
|
1876
|
-
children: [
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
2063
|
+
children: [/* @__PURE__ */ jsx("g", {
|
|
2064
|
+
ref: markerGroupRef,
|
|
2065
|
+
children: nodes.map((node, index) => {
|
|
2066
|
+
const rect = projectedNodes[index];
|
|
2067
|
+
return /* @__PURE__ */ jsx(Fragment, { children: props.renderNode?.(node, rect) ?? /* @__PURE__ */ jsx("rect", {
|
|
2068
|
+
"data-flow-minimap-node": node.id,
|
|
2069
|
+
x: rect.x,
|
|
2070
|
+
y: rect.y,
|
|
2071
|
+
width: Math.max(rect.width, 1),
|
|
2072
|
+
height: Math.max(rect.height, 1),
|
|
2073
|
+
rx: 1,
|
|
2074
|
+
fill: "#94a3b8"
|
|
2075
|
+
}) }, node.id);
|
|
2076
|
+
})
|
|
1886
2077
|
}), /* @__PURE__ */ jsx("rect", {
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
2078
|
+
ref: viewportRectRef,
|
|
2079
|
+
"data-flow-minimap-viewport": "",
|
|
2080
|
+
x: viewportRect.x,
|
|
2081
|
+
y: viewportRect.y,
|
|
2082
|
+
width: viewportRect.width,
|
|
2083
|
+
height: viewportRect.height,
|
|
1891
2084
|
fill: "rgba(37, 99, 235, 0.08)",
|
|
1892
2085
|
stroke: "#2563eb",
|
|
1893
2086
|
strokeWidth: 1
|
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",
|