@statelyai/flow-react 0.11.0 → 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 +54 -30
- 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;
|
|
@@ -1094,8 +1112,7 @@ const EdgeView = memo(function EdgeView(props) {
|
|
|
1094
1112
|
const focusProps = {
|
|
1095
1113
|
role: "group",
|
|
1096
1114
|
"aria-roledescription": "flow edge",
|
|
1097
|
-
"aria-label": props.flow.internals.engine.getAriaLabel(props.id),
|
|
1098
|
-
"aria-selected": selected,
|
|
1115
|
+
"aria-label": props.flow.internals.engine.getAriaLabel(props.id) + (selected ? ", selected" : ""),
|
|
1099
1116
|
tabIndex: focusable ? 0 : void 0,
|
|
1100
1117
|
onFocus: (event) => {
|
|
1101
1118
|
if (descendantOwnsFlowFocus(event)) return;
|
|
@@ -1327,7 +1344,7 @@ function EdgeEndpointHandles(props) {
|
|
|
1327
1344
|
}
|
|
1328
1345
|
function EdgeLabelLayer(props) {
|
|
1329
1346
|
const instance = useFlowInstance();
|
|
1330
|
-
const renderableIds = useRenderableIds(instance, props.onlyRenderVisible);
|
|
1347
|
+
const renderableIds = useRenderableIds(instance, props.onlyRenderVisible, props.settledZoom);
|
|
1331
1348
|
const renderEdgeLabel = props.renderEdgeLabel;
|
|
1332
1349
|
const graphEdgeIds = useFlowSelector((context) => context.graph.edges.map((edge) => edge.id), { isEqual: stringArraysEqual });
|
|
1333
1350
|
const durableLabelIds = useFlowSelector((context) => context.graph.edges.filter((edge) => edge.label).map((edge) => edge.id), { isEqual: stringArraysEqual });
|
|
@@ -1377,7 +1394,7 @@ const EdgeLabelItem = memo(function EdgeLabelItem(props) {
|
|
|
1377
1394
|
if (!(a.configRevision === b.configRevision && a.selected === b.selected && a.dragging === b.dragging && a.hovered === b.hovered && a.highlights === b.highlights)) return false;
|
|
1378
1395
|
if (a.geometryActive !== b.geometryActive) return b.geometryActive ? edgeRenderContentEqual(a.edge, b.edge) : false;
|
|
1379
1396
|
if (a.geometryActive) return edgeRenderContentEqual(a.edge, b.edge);
|
|
1380
|
-
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;
|
|
1381
1398
|
} });
|
|
1382
1399
|
if (!edge || !pathData) return null;
|
|
1383
1400
|
const attrs = getProjectedEdgeAttrs({
|
|
@@ -1505,7 +1522,7 @@ function projectedEdgeLabelBounds(pathData, label) {
|
|
|
1505
1522
|
}
|
|
1506
1523
|
function NodeLayer(props) {
|
|
1507
1524
|
const instance = useFlowInstance();
|
|
1508
|
-
const renderableIds = useRenderableIds(instance, props.onlyRenderVisible);
|
|
1525
|
+
const renderableIds = useRenderableIds(instance, props.onlyRenderVisible, props.settledZoom);
|
|
1509
1526
|
const nodePaintEntries = useDragStableFlowSelector((context) => {
|
|
1510
1527
|
const paintZIndices = instance.internals.engine.getPaintZIndices().nodes;
|
|
1511
1528
|
return context.graph.nodes.flatMap((node) => {
|
|
@@ -1597,7 +1614,7 @@ const NodeView = memo(function NodeView(props) {
|
|
|
1597
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;
|
|
1598
1615
|
if (a.geometryActive !== b.geometryActive) return b.geometryActive ? nodeRenderContentEqual(a.node, b.node) : false;
|
|
1599
1616
|
if (a.geometryActive) return nodeRenderContentEqual(a.node, b.node);
|
|
1600
|
-
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);
|
|
1601
1618
|
} });
|
|
1602
1619
|
const appearance = node ? props.flow.internals.engine.resolveAppearance(node) : void 0;
|
|
1603
1620
|
useAppearanceMeasurement(ref, props.id, appearance, props.observer);
|
|
@@ -1614,7 +1631,8 @@ const NodeView = memo(function NodeView(props) {
|
|
|
1614
1631
|
props.id,
|
|
1615
1632
|
props.observer,
|
|
1616
1633
|
props.flow,
|
|
1617
|
-
hidden
|
|
1634
|
+
hidden,
|
|
1635
|
+
node?.group?.type
|
|
1618
1636
|
]);
|
|
1619
1637
|
useLayoutEffect(() => {
|
|
1620
1638
|
const element = ref.current;
|
|
@@ -1656,7 +1674,6 @@ const NodeView = memo(function NodeView(props) {
|
|
|
1656
1674
|
className: classNames$1("stately-flow__node", attrs.className),
|
|
1657
1675
|
role: "group",
|
|
1658
1676
|
"aria-roledescription": "flow node",
|
|
1659
|
-
"aria-selected": selected,
|
|
1660
1677
|
"aria-label": props.flow.internals.engine.getAriaLabel(props.id) + (selected ? ", selected" : ""),
|
|
1661
1678
|
tabIndex: focusable ? 0 : void 0,
|
|
1662
1679
|
onFocus: (event) => {
|
|
@@ -1982,6 +1999,20 @@ function projectionTransform(from, to) {
|
|
|
1982
1999
|
});
|
|
1983
2000
|
return `translate(${toOrigin.x - fromOrigin.x * scale} ${toOrigin.y - fromOrigin.y * scale}) scale(${scale})`;
|
|
1984
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
|
+
});
|
|
1985
2016
|
/**
|
|
1986
2017
|
* Overview map. Projection and navigation math live in core; this adapter owns
|
|
1987
2018
|
* only React rendering and the browser pointer session.
|
|
@@ -2167,17 +2198,10 @@ function MiniMap(props) {
|
|
|
2167
2198
|
},
|
|
2168
2199
|
children: [/* @__PURE__ */ jsx("g", {
|
|
2169
2200
|
ref: markerGroupRef,
|
|
2170
|
-
children:
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
x: rect.x,
|
|
2175
|
-
y: rect.y,
|
|
2176
|
-
width: Math.max(rect.width, 1),
|
|
2177
|
-
height: Math.max(rect.height, 1),
|
|
2178
|
-
rx: 1,
|
|
2179
|
-
fill: "#94a3b8"
|
|
2180
|
-
}) }, node.id);
|
|
2201
|
+
children: /* @__PURE__ */ jsx(MiniMapNodes, {
|
|
2202
|
+
nodes,
|
|
2203
|
+
rects: projectedNodes,
|
|
2204
|
+
renderNode: props.renderNode
|
|
2181
2205
|
})
|
|
2182
2206
|
}), /* @__PURE__ */ jsx("rect", {
|
|
2183
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",
|