@statelyai/flow-react 0.11.1 → 0.12.0
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/dist/esm/index.d.ts +12 -1
- package/dist/esm/index.js +51 -26
- package/package.json +3 -3
package/dist/esm/index.d.ts
CHANGED
|
@@ -239,6 +239,14 @@ type StatelyFlowBaseProps<TNodeData = unknown, TEdgeData = unknown> = {
|
|
|
239
239
|
* margin). Recommended for large graphs.
|
|
240
240
|
*/
|
|
241
241
|
onlyRenderVisible?: boolean;
|
|
242
|
+
/**
|
|
243
|
+
* The zoom that zoom-dependent work follows, when it should lag the live
|
|
244
|
+
* zoom. While the live zoom differs (a zoom gesture), each frame only
|
|
245
|
+
* changes the viewport transform: `--flow-zoom` and `--flow-inverse-zoom`
|
|
246
|
+
* keep this zoom's values and `onlyRenderVisible` keeps its last cull.
|
|
247
|
+
* Both catch up when this prop changes. Omit to follow the live zoom.
|
|
248
|
+
*/
|
|
249
|
+
settledZoom?: number;
|
|
242
250
|
/**
|
|
243
251
|
* Override the input modes driving gestures. Updates apply to new gestures
|
|
244
252
|
* without cancelling the current one. Defaults to
|
|
@@ -351,7 +359,10 @@ type MiniMapProps = {
|
|
|
351
359
|
padding?: number;
|
|
352
360
|
className?: string;
|
|
353
361
|
style?: CSSProperties;
|
|
354
|
-
/**
|
|
362
|
+
/**
|
|
363
|
+
* Render one node marker; defaults to a plain `<rect>`. Pass a stable
|
|
364
|
+
* function so viewport changes skip re-rendering the markers.
|
|
365
|
+
*/
|
|
355
366
|
renderNode?: (node: FlowNode, rect: Rect) => ReactNode;
|
|
356
367
|
};
|
|
357
368
|
/**
|
package/dist/esm/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
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
|
-
import { applyFlowColorMode, attachContainer, attachInput, attachViewportTransform, clientToCanvas,
|
|
2
|
+
import { applyFlowColorMode, attachContainer, attachInput, attachViewportTransform, clientToCanvas, computeSettledRenderableIds, createEntityGeometryObserver, createFlowView, createMeasurementObserver, nodePositionStyle, observeAppearanceBounds, renderableIdsEqual, resolveFlowColorMode, viewportStyle, writeZoomVariables } from "@statelyai/flow-dom";
|
|
3
3
|
import { CONNECTION_PREVIEW_EDGE_ID, appearanceMeasurementId, areNodesMeasured, createConnectionPreviewGraph, createEntityChangeNotifier, createFlow, createFlowInputRuntime, defaultModesForContext, getAnchorTransform, getAutoPanDelta, getMinimapProjection, getMinimapViewportCenter, getProjectedEdgeAttrs, getProjectedNodeAttrs, getToolbarPlacement, getVisibleBounds, markerToSVG, pathToSVG, profileModesForContext, profilePoliciesForContext, viewportCenteredOn } from "@statelyai/flow";
|
|
4
4
|
import { Fragment, createContext, forwardRef, memo, useCallback, useContext, useEffect, useId, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
|
5
5
|
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
@@ -646,15 +646,30 @@ function useDragStableFlowSelector(selector, isEqual) {
|
|
|
646
646
|
return value;
|
|
647
647
|
}, { isEqual });
|
|
648
648
|
}
|
|
649
|
-
function useRenderableIds(instance, enabled) {
|
|
650
|
-
|
|
649
|
+
function useRenderableIds(instance, enabled, settledZoom) {
|
|
650
|
+
const lastCull = useRef(null);
|
|
651
|
+
return useDragStableFlowSelector((context) => {
|
|
652
|
+
lastCull.current = computeSettledRenderableIds(instance.internals.engine, context, enabled, settledZoom, lastCull.current);
|
|
653
|
+
return lastCull.current.value;
|
|
654
|
+
}, renderableIdsEqual);
|
|
651
655
|
}
|
|
652
|
-
function useViewportTransform(ref, instance) {
|
|
656
|
+
function useViewportTransform(ref, instance, settledZoom) {
|
|
657
|
+
const settledZoomRef = useRef(settledZoom);
|
|
658
|
+
settledZoomRef.current = settledZoom;
|
|
653
659
|
useLayoutEffect(() => {
|
|
654
660
|
const el = ref.current;
|
|
655
661
|
if (!el) return;
|
|
656
|
-
return attachViewportTransform(instance.internals.store, el);
|
|
662
|
+
return attachViewportTransform(instance.internals.store, el, { getVariableZoom: () => settledZoomRef.current });
|
|
657
663
|
}, [ref, instance]);
|
|
664
|
+
useLayoutEffect(() => {
|
|
665
|
+
const el = ref.current;
|
|
666
|
+
if (!el) return;
|
|
667
|
+
writeZoomVariables(el, settledZoom ?? instance.getContext().viewport.zoom);
|
|
668
|
+
}, [
|
|
669
|
+
ref,
|
|
670
|
+
instance,
|
|
671
|
+
settledZoom
|
|
672
|
+
]);
|
|
658
673
|
}
|
|
659
674
|
function StatelyFlow(props) {
|
|
660
675
|
if (props.flow) return /* @__PURE__ */ jsx(StatelyFlowView, {
|
|
@@ -703,7 +718,7 @@ function StatelyFlowView(props) {
|
|
|
703
718
|
useLayoutEffect(() => props.flow.subscribe((context) => context.focusedEntityId, (nextId, previousId) => {
|
|
704
719
|
if (containerRef.current) blurClearedEntityFocus(containerRef.current, previousId, nextId);
|
|
705
720
|
}), [props.flow]);
|
|
706
|
-
useViewportTransform(viewportRef, props.flow);
|
|
721
|
+
useViewportTransform(viewportRef, props.flow, props.settledZoom);
|
|
707
722
|
useFlowBaseStyles();
|
|
708
723
|
const projections = useActiveProjections(props.flow, props.projections);
|
|
709
724
|
const { className: rootClassName, style: rootStyle, tabIndex: rootTabIndex, ...rootProps } = props.rootProps ?? {};
|
|
@@ -776,14 +791,16 @@ function StatelyFlowView(props) {
|
|
|
776
791
|
geometryObserver,
|
|
777
792
|
renderEdge: props.renderEdge,
|
|
778
793
|
projections,
|
|
779
|
-
onlyRenderVisible: props.onlyRenderVisible
|
|
794
|
+
onlyRenderVisible: props.onlyRenderVisible,
|
|
795
|
+
settledZoom: props.settledZoom
|
|
780
796
|
}),
|
|
781
797
|
/* @__PURE__ */ jsx(NodeLayer, {
|
|
782
798
|
geometryObserver,
|
|
783
799
|
appearanceRenderers: props.appearanceRenderers,
|
|
784
800
|
renderNode: props.renderNode,
|
|
785
801
|
projections,
|
|
786
|
-
onlyRenderVisible: props.onlyRenderVisible
|
|
802
|
+
onlyRenderVisible: props.onlyRenderVisible,
|
|
803
|
+
settledZoom: props.settledZoom
|
|
787
804
|
}),
|
|
788
805
|
/* @__PURE__ */ jsx(ConnectionPreviewEdge, {
|
|
789
806
|
flow: props.flow,
|
|
@@ -796,7 +813,8 @@ function StatelyFlowView(props) {
|
|
|
796
813
|
appearanceRenderers: props.appearanceRenderers,
|
|
797
814
|
renderEdgeLabel: props.renderEdgeLabel,
|
|
798
815
|
projections,
|
|
799
|
-
onlyRenderVisible: props.onlyRenderVisible
|
|
816
|
+
onlyRenderVisible: props.onlyRenderVisible,
|
|
817
|
+
settledZoom: props.settledZoom
|
|
800
818
|
}),
|
|
801
819
|
props.showSelectionBounds ? /* @__PURE__ */ jsx(SelectionBoundsLayer, { geometryObserver }) : null,
|
|
802
820
|
/* @__PURE__ */ jsx(SelectionBoxLayer, {}),
|
|
@@ -1025,7 +1043,7 @@ function SelectionBoundsLayer(props) {
|
|
|
1025
1043
|
function EdgeLayer(props) {
|
|
1026
1044
|
const instance = useFlowInstance();
|
|
1027
1045
|
const markerPrefix = `stately-flow-${useId().replace(/[^a-zA-Z0-9_-]/g, "")}`;
|
|
1028
|
-
const renderableIds = useRenderableIds(instance, props.onlyRenderVisible);
|
|
1046
|
+
const renderableIds = useRenderableIds(instance, props.onlyRenderVisible, props.settledZoom);
|
|
1029
1047
|
const edgeIds = useDragStableFlowSelector((_context) => getOrderedEntities(instance).edges.map((edge) => edge.id), stringArraysEqual);
|
|
1030
1048
|
const renderEdge = props.renderEdge;
|
|
1031
1049
|
return /* @__PURE__ */ jsxs("svg", {
|
|
@@ -1070,7 +1088,7 @@ const EdgeView = memo(function EdgeView(props) {
|
|
|
1070
1088
|
if (!(a.configRevision === b.configRevision && a.selected === b.selected && a.focused === b.focused && a.hovered === b.hovered && a.highlights === b.highlights)) return false;
|
|
1071
1089
|
if (a.geometryActive !== b.geometryActive) return b.geometryActive ? edgeRenderContentEqual(a.edge, b.edge) : false;
|
|
1072
1090
|
if (a.geometryActive) return edgeRenderContentEqual(a.edge, b.edge);
|
|
1073
|
-
return a.graph === b.graph && a.edge === b.edge && a.pathData === b.pathData;
|
|
1091
|
+
return (a.graph === b.graph || !props.projections?.length) && a.edge === b.edge && a.pathData === b.pathData;
|
|
1074
1092
|
} });
|
|
1075
1093
|
useLayoutEffect(() => {
|
|
1076
1094
|
const element = ref.current;
|
|
@@ -1326,7 +1344,7 @@ function EdgeEndpointHandles(props) {
|
|
|
1326
1344
|
}
|
|
1327
1345
|
function EdgeLabelLayer(props) {
|
|
1328
1346
|
const instance = useFlowInstance();
|
|
1329
|
-
const renderableIds = useRenderableIds(instance, props.onlyRenderVisible);
|
|
1347
|
+
const renderableIds = useRenderableIds(instance, props.onlyRenderVisible, props.settledZoom);
|
|
1330
1348
|
const renderEdgeLabel = props.renderEdgeLabel;
|
|
1331
1349
|
const graphEdgeIds = useFlowSelector((context) => context.graph.edges.map((edge) => edge.id), { isEqual: stringArraysEqual });
|
|
1332
1350
|
const durableLabelIds = useFlowSelector((context) => context.graph.edges.filter((edge) => edge.label).map((edge) => edge.id), { isEqual: stringArraysEqual });
|
|
@@ -1376,7 +1394,7 @@ const EdgeLabelItem = memo(function EdgeLabelItem(props) {
|
|
|
1376
1394
|
if (!(a.configRevision === b.configRevision && a.selected === b.selected && a.dragging === b.dragging && a.hovered === b.hovered && a.highlights === b.highlights)) return false;
|
|
1377
1395
|
if (a.geometryActive !== b.geometryActive) return b.geometryActive ? edgeRenderContentEqual(a.edge, b.edge) : false;
|
|
1378
1396
|
if (a.geometryActive) return edgeRenderContentEqual(a.edge, b.edge);
|
|
1379
|
-
return a.graph === b.graph && a.edge === b.edge && a.pathData === b.pathData;
|
|
1397
|
+
return (a.graph === b.graph || !props.projections?.length) && a.edge === b.edge && a.pathData === b.pathData;
|
|
1380
1398
|
} });
|
|
1381
1399
|
if (!edge || !pathData) return null;
|
|
1382
1400
|
const attrs = getProjectedEdgeAttrs({
|
|
@@ -1504,7 +1522,7 @@ function projectedEdgeLabelBounds(pathData, label) {
|
|
|
1504
1522
|
}
|
|
1505
1523
|
function NodeLayer(props) {
|
|
1506
1524
|
const instance = useFlowInstance();
|
|
1507
|
-
const renderableIds = useRenderableIds(instance, props.onlyRenderVisible);
|
|
1525
|
+
const renderableIds = useRenderableIds(instance, props.onlyRenderVisible, props.settledZoom);
|
|
1508
1526
|
const nodePaintEntries = useDragStableFlowSelector((context) => {
|
|
1509
1527
|
const paintZIndices = instance.internals.engine.getPaintZIndices().nodes;
|
|
1510
1528
|
return context.graph.nodes.flatMap((node) => {
|
|
@@ -1596,7 +1614,7 @@ const NodeView = memo(function NodeView(props) {
|
|
|
1596
1614
|
if (!(a.configRevision === b.configRevision && a.hidden === b.hidden && a.selected === b.selected && a.dragging === b.dragging && a.focused === b.focused && a.hovered === b.hovered && a.highlights === b.highlights)) return false;
|
|
1597
1615
|
if (a.geometryActive !== b.geometryActive) return b.geometryActive ? nodeRenderContentEqual(a.node, b.node) : false;
|
|
1598
1616
|
if (a.geometryActive) return nodeRenderContentEqual(a.node, b.node);
|
|
1599
|
-
return a.graph === b.graph && a.node === b.node && rectsShallowEqual(a.bounds, b.bounds);
|
|
1617
|
+
return (a.graph === b.graph || !props.projections?.length && !b.node?.group) && a.node === b.node && rectsShallowEqual(a.bounds, b.bounds);
|
|
1600
1618
|
} });
|
|
1601
1619
|
const appearance = node ? props.flow.internals.engine.resolveAppearance(node) : void 0;
|
|
1602
1620
|
useAppearanceMeasurement(ref, props.id, appearance, props.observer);
|
|
@@ -1981,6 +1999,20 @@ function projectionTransform(from, to) {
|
|
|
1981
1999
|
});
|
|
1982
2000
|
return `translate(${toOrigin.x - fromOrigin.x * scale} ${toOrigin.y - fromOrigin.y * scale}) scale(${scale})`;
|
|
1983
2001
|
}
|
|
2002
|
+
const MiniMapNodes = memo(function MiniMapNodes(props) {
|
|
2003
|
+
return props.nodes.map((node, index) => {
|
|
2004
|
+
const rect = props.rects[index];
|
|
2005
|
+
return /* @__PURE__ */ jsx(Fragment, { children: props.renderNode?.(node, rect) ?? /* @__PURE__ */ jsx("rect", {
|
|
2006
|
+
"data-flow-minimap-node": node.id,
|
|
2007
|
+
x: rect.x,
|
|
2008
|
+
y: rect.y,
|
|
2009
|
+
width: Math.max(rect.width, 1),
|
|
2010
|
+
height: Math.max(rect.height, 1),
|
|
2011
|
+
rx: 1,
|
|
2012
|
+
fill: "#94a3b8"
|
|
2013
|
+
}) }, node.id);
|
|
2014
|
+
});
|
|
2015
|
+
});
|
|
1984
2016
|
/**
|
|
1985
2017
|
* Overview map. Projection and navigation math live in core; this adapter owns
|
|
1986
2018
|
* only React rendering and the browser pointer session.
|
|
@@ -2166,17 +2198,10 @@ function MiniMap(props) {
|
|
|
2166
2198
|
},
|
|
2167
2199
|
children: [/* @__PURE__ */ jsx("g", {
|
|
2168
2200
|
ref: markerGroupRef,
|
|
2169
|
-
children:
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
x: rect.x,
|
|
2174
|
-
y: rect.y,
|
|
2175
|
-
width: Math.max(rect.width, 1),
|
|
2176
|
-
height: Math.max(rect.height, 1),
|
|
2177
|
-
rx: 1,
|
|
2178
|
-
fill: "#94a3b8"
|
|
2179
|
-
}) }, node.id);
|
|
2201
|
+
children: /* @__PURE__ */ jsx(MiniMapNodes, {
|
|
2202
|
+
nodes,
|
|
2203
|
+
rects: projectedNodes,
|
|
2204
|
+
renderNode: props.renderNode
|
|
2180
2205
|
})
|
|
2181
2206
|
}), /* @__PURE__ */ jsx("rect", {
|
|
2182
2207
|
ref: viewportRectRef,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@statelyai/flow-react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
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.13.
|
|
38
|
-
"@statelyai/flow-dom": "3.
|
|
37
|
+
"@statelyai/flow": "0.13.2",
|
|
38
|
+
"@statelyai/flow-dom": "3.6.0"
|
|
39
39
|
},
|
|
40
40
|
"peerDependencies": {
|
|
41
41
|
"react": "^18.2.0 || ^19.0.0",
|