@statelyai/flow-react 0.9.2 → 0.9.3

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.
Files changed (2) hide show
  1. package/dist/esm/index.js +39 -12
  2. package/package.json +7 -4
package/dist/esm/index.js CHANGED
@@ -561,8 +561,31 @@ function assignRef(ref, value) {
561
561
  if (typeof ref === "function") ref(value);
562
562
  else if (ref) ref.current = value;
563
563
  }
564
+ /**
565
+ * `dragRevision` changes only for transient drag geometry. Topology, paint
566
+ * order, and viewport membership stay stable during that transition, so
567
+ * list-level selectors can retain their prior value while per-entity DOM
568
+ * observers update only the moved cohort.
569
+ */
570
+ function useDragStableFlowSelector(selector, isEqual) {
571
+ const cached = useRef(null);
572
+ return useFlowSelector((context) => {
573
+ if (cached.current && context.canvasState === "dragging" && cached.current.canvasState === "dragging" && context.dragRevision !== cached.current.dragRevision) {
574
+ cached.current.canvasState = context.canvasState;
575
+ cached.current.dragRevision = context.dragRevision;
576
+ return cached.current.value;
577
+ }
578
+ const value = selector(context);
579
+ cached.current = {
580
+ canvasState: context.canvasState,
581
+ dragRevision: context.dragRevision,
582
+ value
583
+ };
584
+ return value;
585
+ }, { isEqual });
586
+ }
564
587
  function useRenderableIds(instance, enabled) {
565
- return useFlowSelector((context) => computeRenderableIds(instance.internals.engine, context, enabled), { isEqual: renderableIdsEqual });
588
+ return useDragStableFlowSelector((context) => computeRenderableIds(instance.internals.engine, context, enabled), renderableIdsEqual);
566
589
  }
567
590
  function useViewportTransform(ref, instance) {
568
591
  useLayoutEffect(() => {
@@ -934,7 +957,7 @@ function EdgeLayer(props) {
934
957
  const instance = useFlowInstance();
935
958
  const markerPrefix = `stately-flow-${useId().replace(/[^a-zA-Z0-9_-]/g, "")}`;
936
959
  const renderableIds = useRenderableIds(instance, props.onlyRenderVisible);
937
- const edgeIds = useFlowSelector((_context) => getOrderedEntities(instance).edges.map((edge) => edge.id), { isEqual: stringArraysEqual });
960
+ const edgeIds = useDragStableFlowSelector((_context) => getOrderedEntities(instance).edges.map((edge) => edge.id), stringArraysEqual);
938
961
  const renderEdge = props.renderEdge;
939
962
  return /* @__PURE__ */ jsxs("svg", {
940
963
  "aria-label": "edges",
@@ -979,7 +1002,8 @@ const EdgeView = memo(function EdgeView(props) {
979
1002
  hovered: context.hoveredEntityIds.has(props.id),
980
1003
  highlights: props.flow.internals.engine.getHighlights(props.id)
981
1004
  }), { isEqual: (a, b) => {
982
- if (!(a.selected === b.selected && a.focused === b.focused && a.hovered === b.hovered && a.highlights === b.highlights) || a.geometryActive !== b.geometryActive) return false;
1005
+ if (!(a.selected === b.selected && a.focused === b.focused && a.hovered === b.hovered && a.highlights === b.highlights)) return false;
1006
+ if (a.geometryActive !== b.geometryActive) return b.geometryActive ? edgeRenderContentEqual(a.edge, b.edge) : false;
983
1007
  if (a.geometryActive) return edgeRenderContentEqual(a.edge, b.edge);
984
1008
  return a.graph === b.graph && a.edge === b.edge && a.pathData === b.pathData;
985
1009
  } });
@@ -1258,7 +1282,8 @@ const EdgeLabelItem = memo(function EdgeLabelItem(props) {
1258
1282
  hovered: context.hoveredEntityIds.has(props.id),
1259
1283
  highlights: props.flow.internals.engine.getHighlights(props.id)
1260
1284
  }), { isEqual: (a, b) => {
1261
- if (!(a.selected === b.selected && a.dragging === b.dragging && a.hovered === b.hovered && a.highlights === b.highlights) || a.geometryActive !== b.geometryActive) return false;
1285
+ if (!(a.selected === b.selected && a.dragging === b.dragging && a.hovered === b.hovered && a.highlights === b.highlights)) return false;
1286
+ if (a.geometryActive !== b.geometryActive) return b.geometryActive ? edgeRenderContentEqual(a.edge, b.edge) : false;
1262
1287
  if (a.geometryActive) return edgeRenderContentEqual(a.edge, b.edge);
1263
1288
  return a.graph === b.graph && a.edge === b.edge && a.pathData === b.pathData;
1264
1289
  } });
@@ -1383,16 +1408,17 @@ function projectedEdgeLabelBounds(pathData, label) {
1383
1408
  function NodeLayer(props) {
1384
1409
  const instance = useFlowInstance();
1385
1410
  const renderableIds = useRenderableIds(instance, props.onlyRenderVisible);
1386
- const nodePaintEntries = useFlowSelector((context) => {
1387
- const paintOrder = new Map(getOrderedEntities(instance).nodes.map((node, orderIndex) => [node.id, orderIndex]));
1411
+ const nodePaintEntries = useDragStableFlowSelector((context) => {
1412
+ const paintOrder = new Map(getOrderedEntities(instance, { elevateSelected: false }).nodes.map((node, orderIndex) => [node.id, orderIndex]));
1413
+ const selectedBoost = paintOrder.size;
1388
1414
  return context.graph.nodes.flatMap((node) => {
1389
1415
  const orderIndex = paintOrder.get(node.id);
1390
1416
  return orderIndex === void 0 ? [] : [{
1391
1417
  id: node.id,
1392
- orderIndex
1418
+ orderIndex: orderIndex + (context.selection.has(node.id) ? selectedBoost : 0)
1393
1419
  }];
1394
1420
  });
1395
- }, { isEqual: nodePaintEntriesEqual });
1421
+ }, nodePaintEntriesEqual);
1396
1422
  const observer = useMeasurementObserver();
1397
1423
  return /* @__PURE__ */ jsx("div", {
1398
1424
  "aria-label": "nodes",
@@ -1419,8 +1445,8 @@ function NodeLayer(props) {
1419
1445
  })
1420
1446
  });
1421
1447
  }
1422
- function getOrderedEntities(flow) {
1423
- return flow.internals.engine.getVisibleEntities();
1448
+ function getOrderedEntities(flow, options = {}) {
1449
+ return flow.internals.engine.getVisibleEntities(options);
1424
1450
  }
1425
1451
  function rectsShallowEqual(a, b) {
1426
1452
  return a.x === b.x && a.y === b.y && a.width === b.width && a.height === b.height;
@@ -1469,7 +1495,8 @@ const NodeView = memo(function NodeView(props) {
1469
1495
  bounds: context.resizingEntityId === props.id && context.resizeCorners ? props.flow.internals.engine.getResizeBounds() ?? props.flow.internals.engine.visualBounds(props.id) : props.flow.internals.engine.visualBounds(props.id)
1470
1496
  };
1471
1497
  }, { isEqual: (a, b) => {
1472
- if (!(a.hidden === b.hidden && a.selected === b.selected && a.dragging === b.dragging && a.focused === b.focused && a.hovered === b.hovered && a.highlights === b.highlights) || a.geometryActive !== b.geometryActive) return false;
1498
+ if (!(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;
1499
+ if (a.geometryActive !== b.geometryActive) return b.geometryActive ? nodeRenderContentEqual(a.node, b.node) : false;
1473
1500
  if (a.geometryActive) return nodeRenderContentEqual(a.node, b.node);
1474
1501
  return a.graph === b.graph && a.node === b.node && rectsShallowEqual(a.bounds, b.bounds);
1475
1502
  } });
@@ -1540,7 +1567,7 @@ const NodeView = memo(function NodeView(props) {
1540
1567
  dragging
1541
1568
  }),
1542
1569
  cursor: dragging ? "grabbing" : "grab",
1543
- pointerEvents: node.group && groupHasInteractiveContent ? "none" : "auto",
1570
+ pointerEvents: node.group?.type === "derived" || node.group && groupHasInteractiveContent ? "none" : "auto",
1544
1571
  userSelect: "none",
1545
1572
  zIndex: props.orderIndex,
1546
1573
  opacity: attrs.opacity
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@statelyai/flow-react",
3
- "version": "0.9.2",
3
+ "version": "0.9.3",
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.8.2",
38
- "@statelyai/flow-dom": "3.4.2"
37
+ "@statelyai/flow": "0.9.0",
38
+ "@statelyai/flow-dom": "3.4.3"
39
39
  },
40
40
  "peerDependencies": {
41
41
  "react": "^18.2.0 || ^19.0.0",
@@ -83,7 +83,10 @@
83
83
  "typecheck": "tsc --noEmit",
84
84
  "test:browser": "vitest run --config vitest.browser.config.mjs",
85
85
  "test:e2e": "playwright test",
86
- "bench:browser": "playwright test --config playwright.performance.config.ts",
86
+ "bench:browser": "playwright test --config playwright.performance.config.ts && node ../../scripts/report-browser-benchmarks.mjs",
87
+ "bench:react": "playwright test --config playwright.performance.config.ts --grep StatelyFlow && node ../../scripts/report-browser-benchmarks.mjs",
88
+ "bench:interaction": "playwright test --config playwright.performance.config.ts --grep 'viewport pan' && node ../../scripts/report-browser-benchmarks.mjs",
89
+ "bench:comparison": "playwright test --config playwright.performance.config.ts --grep 'StatelyFlow|React Flow' && node ../../scripts/report-browser-benchmarks.mjs",
87
90
  "storybook": "storybook dev -p 6008",
88
91
  "storybook:build": "storybook build"
89
92
  }