@statelyai/flow-react 0.9.2 → 0.9.4

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 +44 -18
  2. package/package.json +7 -4
package/dist/esm/index.js CHANGED
@@ -551,7 +551,7 @@ function stringArraysEqual(a, b) {
551
551
  }
552
552
  function nodePaintEntriesEqual(a, b) {
553
553
  if (a.length !== b.length) return false;
554
- for (let index = 0; index < a.length; index++) if (a[index]?.id !== b[index]?.id || a[index]?.orderIndex !== b[index]?.orderIndex) return false;
554
+ for (let index = 0; index < a.length; index++) if (a[index]?.id !== b[index]?.id || a[index]?.zIndex !== b[index]?.zIndex) return false;
555
555
  return true;
556
556
  }
557
557
  function classNames$1(...values) {
@@ -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,16 @@ 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 paintZIndices = instance.internals.engine.getPaintZIndices().nodes;
1388
1413
  return context.graph.nodes.flatMap((node) => {
1389
- const orderIndex = paintOrder.get(node.id);
1390
- return orderIndex === void 0 ? [] : [{
1414
+ const zIndex = paintZIndices.get(node.id);
1415
+ return zIndex === void 0 ? [] : [{
1391
1416
  id: node.id,
1392
- orderIndex
1417
+ zIndex
1393
1418
  }];
1394
1419
  });
1395
- }, { isEqual: nodePaintEntriesEqual });
1420
+ }, nodePaintEntriesEqual);
1396
1421
  const observer = useMeasurementObserver();
1397
1422
  return /* @__PURE__ */ jsx("div", {
1398
1423
  "aria-label": "nodes",
@@ -1405,13 +1430,13 @@ function NodeLayer(props) {
1405
1430
  transformOrigin: "0 0",
1406
1431
  pointerEvents: "none"
1407
1432
  },
1408
- children: nodePaintEntries.map(({ id, orderIndex }) => {
1433
+ children: nodePaintEntries.map(({ id, zIndex }) => {
1409
1434
  if (renderableIds && !renderableIds.nodeIds.has(id)) return null;
1410
1435
  return /* @__PURE__ */ jsx(NodeView, {
1411
1436
  flow: instance,
1412
1437
  geometryObserver: props.geometryObserver,
1413
1438
  id,
1414
- orderIndex,
1439
+ zIndex,
1415
1440
  observer,
1416
1441
  projections: props.projections,
1417
1442
  renderNode: props.renderNode
@@ -1419,8 +1444,8 @@ function NodeLayer(props) {
1419
1444
  })
1420
1445
  });
1421
1446
  }
1422
- function getOrderedEntities(flow) {
1423
- return flow.internals.engine.getVisibleEntities();
1447
+ function getOrderedEntities(flow, options = {}) {
1448
+ return flow.internals.engine.getVisibleEntities(options);
1424
1449
  }
1425
1450
  function rectsShallowEqual(a, b) {
1426
1451
  return a.x === b.x && a.y === b.y && a.width === b.width && a.height === b.height;
@@ -1469,7 +1494,8 @@ const NodeView = memo(function NodeView(props) {
1469
1494
  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
1495
  };
1471
1496
  }, { 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;
1497
+ 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;
1498
+ if (a.geometryActive !== b.geometryActive) return b.geometryActive ? nodeRenderContentEqual(a.node, b.node) : false;
1473
1499
  if (a.geometryActive) return nodeRenderContentEqual(a.node, b.node);
1474
1500
  return a.graph === b.graph && a.node === b.node && rectsShallowEqual(a.bounds, b.bounds);
1475
1501
  } });
@@ -1540,9 +1566,9 @@ const NodeView = memo(function NodeView(props) {
1540
1566
  dragging
1541
1567
  }),
1542
1568
  cursor: dragging ? "grabbing" : "grab",
1543
- pointerEvents: node.group && groupHasInteractiveContent ? "none" : "auto",
1569
+ pointerEvents: node.group?.type === "derived" || node.group && groupHasInteractiveContent ? "none" : "auto",
1544
1570
  userSelect: "none",
1545
- zIndex: props.orderIndex,
1571
+ zIndex: props.zIndex,
1546
1572
  opacity: attrs.opacity
1547
1573
  },
1548
1574
  children: props.renderNode ? /* @__PURE__ */ jsx("div", {
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.4",
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.1",
38
+ "@statelyai/flow-dom": "3.4.4"
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
  }