@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 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
 
@@ -341,10 +341,8 @@ type MiniMapProps = {
341
341
  renderNode?: (node: FlowNode, rect: Rect) => ReactNode;
342
342
  };
343
343
  /**
344
- * Overview map. Deliberately thin: all math lives in core
345
- * (`getMinimapProjection`, `viewportCenteredOn`) and every interaction is a
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: (context, screenPoint) => getAutoPanDelta(context, screenPoint, flow.internals.engine)
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: rect.x - props.bounds.x,
1625
- top: rect.y - props.bounds.y,
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", { style: {
1631
- position: "absolute",
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.contentBounds),
1666
+ ...relative(geometry.childArea),
1645
1667
  boxSizing: "border-box",
1646
- display: "flex",
1647
- alignItems: "center",
1648
- padding: "0 12px",
1649
- border: `2px solid ${props.selected ? "#2563eb" : props.attrs.color ?? "#94a3b8"}`,
1650
- borderRadius: 8,
1651
- background: props.selected ? "#eff6ff" : "#f8fafc",
1652
- color: "#0f172a",
1653
- font: "600 13px/1.2 system-ui, sans-serif",
1654
- cursor: props.dragging ? "grabbing" : "grab",
1655
- pointerEvents: "auto",
1656
- userSelect: "none",
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: [/* @__PURE__ */ jsx("span", { children: String(label ?? props.node.id) }), badges.length ? /* @__PURE__ */ jsx("span", {
1660
- style: defaultBadgeListStyle,
1661
- children: badges
1662
- }) : null]
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. Deliberately thin: all math lives in core
1817
- * (`getMinimapProjection`, `viewportCenteredOn`) and every interaction is a
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 viewport = useFlowSelector((context) => context.viewport);
1889
+ const measurements = useFlowSelector((context) => context.measurements);
1827
1890
  const viewportSize = useFlowSelector((context) => context.viewportSize);
1828
- const draggingRef = useRef(false);
1829
- const projection = getMinimapProjection({
1830
- contentBounds: flow.getGraphBounds(),
1831
- viewport,
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
- const jumpTo = (event) => {
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
- const canvasPoint = projection.toCanvas({
1951
+ return activeProjection.toCanvas({
1840
1952
  x: event.clientX - rect.left,
1841
1953
  y: event.clientY - rect.top
1842
1954
  });
1843
- flow.viewport.set(viewportCenteredOn(canvasPoint, viewportSize, viewport.zoom));
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
- draggingRef.current = true;
1852
- jumpTo(event);
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
- event.currentTarget.setPointerCapture(event.pointerId);
2029
+ element.setPointerCapture(event.pointerId);
1855
2030
  } catch {}
2031
+ if (!grabbedLens) navigate(event, session);
1856
2032
  },
1857
2033
  onPointerMove: (event) => {
1858
- if (draggingRef.current) jumpTo(event);
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
- draggingRef.current = false;
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: [nodes.map((node) => {
1877
- const rect = projection.rectToMinimap(flow.internals.engine.visualBounds(node.id));
1878
- return props.renderNode?.(node, rect) ?? /* @__PURE__ */ jsx("rect", {
1879
- x: rect.x,
1880
- y: rect.y,
1881
- width: Math.max(rect.width, 1),
1882
- height: Math.max(rect.height, 1),
1883
- rx: 1,
1884
- fill: "#94a3b8"
1885
- }, node.id);
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
- x: projection.viewportRect.x,
1888
- y: projection.viewportRect.y,
1889
- width: projection.viewportRect.width,
1890
- height: projection.viewportRect.height,
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.4",
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.9.1",
38
- "@statelyai/flow-dom": "3.4.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",