@statelyai/flow-react 0.8.0 → 0.9.2

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, resize, 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, 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.
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
 
@@ -53,9 +53,9 @@ DOM measurement is one-directional. Nodes, ports, and edge labels report their r
53
53
  | `PanePortal` / `ViewportPortal` | Render custom content into fixed screen-coordinate or transformed canvas-coordinate layers |
54
54
  | `useFitViewExclusion()` | Register an overlay element whose measured edge band fit and center commands avoid |
55
55
  | `EntityAnchor` | Position custom controls on entity sides or free-standing canvas bounds with screen-stable offsets and inverse scaling |
56
+ | `Port` | Render a measured connection port or a graph-owned readonly inspection port |
56
57
  | `RenderEdgeLabelProps` | Edge, path, bounds, interaction state, and projected attrs passed to `renderEdgeLabel` |
57
58
  | `Panel` | Positioned overlay panel for controls and custom UI |
58
- | `NodeResizer` | Resize handles for custom node renderers; constraints live on `node.resize` |
59
59
  | `ReactActiveGraphProjection` | React-typed active projection for `<StatelyFlow projections={...} />` or controller-owned projections |
60
60
  | `ReactGraphNodeAttrs` / `ReactGraphEdgeAttrs` | React-typed visual attrs exposed to custom node/edge renderers |
61
61
  | `FlowColorModeScope` | Applies the current flow color mode as DOM data attributes and `color-scheme` |
@@ -75,7 +75,11 @@ Color mode is store data, not engine behavior. `@statelyai/flow-dom` exports sma
75
75
 
76
76
  Attach the ref from `useFitViewExclusion({ side, gap })` to floating controls that cover the canvas. The hook measures the element relative to the Flow root at command time, including content portaled outside the root, so responsive toolbars and panel-relative chrome do not need guessed fit padding.
77
77
 
78
- Set `showSelectionBounds` to render a zoom-stable, square frame around established multi-selections. Its geometry follows drag, resize, and layout frames through Flow DOM without React commits; theme `[data-flow-selection-bounds]`, `--flow-selection-color`, or the screen-pixel `--flow-selection-bounds-outset`. Set `preserveViewportCenterOnResize` when root container resizes should retain the world point at its center.
78
+ Set `showSelectionBounds` to render a zoom-stable, square frame around established multi-selections. Its geometry follows drag and layout frames through Flow DOM without React commits; theme `[data-flow-selection-bounds]`, `--flow-selection-color`, or the screen-pixel `--flow-selection-bounds-outset`. Set `preserveViewportCenterOnResize` when root container resizes should retain the world point at its center.
79
+
80
+ The default edge renderer honors `edge.style.markerStart` and `markerEnd` values `arrow`, `arrow-closed`, `circle`, and `none`, plus safe local custom marker IDs or `url(#id)` references. External and malformed marker URLs are rejected. Built-in marker definitions are namespaced per `StatelyFlow`, so multiple mounted flows cannot collide. It also renders canonical graph `edge.points`/`routing` and optional `edge.svgPath` without rerouting or splitting the route around a label.
81
+
82
+ Render layout-owned ports with `<Port measured={false} interaction="inspect" />`. Inspection ports are focusable, selectable addressables (`${node.id}:${port.name}`), emit the normal entity click events, and never start a connection gesture. Omit `interaction` (or use `"connect"`) to retain existing connection behavior.
79
83
 
80
84
  Custom node and edge renderers should branch on the application-defined `entity.kind`. Keep `entity.type` as the structural `"node"` or `"edge"` discriminant so core selection, drag, connection, and CRUD behavior remains valid.
81
85
 
@@ -1,6 +1,6 @@
1
1
  import { Background, BackgroundProps, BackgroundVariant, CrossBackground, DotsBackground, LinesBackground } from "./backgrounds.js";
2
2
  import { FlowView, ResolvedFlowColorMode } from "@statelyai/flow-dom";
3
- import { ActiveGraphProjection, AnchorSpec, ConnectionEndedEvent, ConnectionState, CreateEdgeInput, CreateFlowOptions, EdgePathData, FitViewExclusionSide, FlowColorMode, FlowCommitEvent, FlowConnection, FlowController, FlowEdge, FlowGraph, FlowInputMode, FlowKeymap, FlowNode, FlowProfile, FlowStoreContext, GraphEdgeAttrs, GraphNodeAttrs, LayoutRequestedEvent, Point, Rect, ResizeHandle, Viewport, VisualPort } from "@statelyai/flow";
3
+ import { ActiveGraphProjection, AnchorSpec, ConnectionEndedEvent, ConnectionState, CreateEdgeInput, CreateFlowOptions, EdgePathData, FitViewExclusionSide, FlowColorMode, FlowCommitEvent, FlowConnection, FlowController, FlowEdge, FlowGraph, FlowInputMode, FlowKeymap, FlowNode, FlowProfile, FlowStoreContext, GraphEdgeAttrs, GraphNodeAttrs, LayoutRequestedEvent, Point, Rect, Viewport, VisualPort } from "@statelyai/flow";
4
4
  import { CSSProperties, ComponentPropsWithoutRef, HTMLAttributes, ReactNode, Ref, RefCallback, RefObject } from "react";
5
5
  export * from "@statelyai/flow-dom";
6
6
  //#region src/createFlowInstance.d.ts
@@ -54,6 +54,13 @@ type FlowInstance<TNodeData = any, TEdgeData = any, TLabelData = any> = FlowCont
54
54
  fireImmediately?: boolean;
55
55
  }): () => void;
56
56
  };
57
+ /**
58
+ * Create the React adapter's imperative Flow instance.
59
+ *
60
+ * The instance owns its controller and all listeners registered through
61
+ * `listen` or creation options. `destroy()` removes those listeners and
62
+ * destroys the underlying controller.
63
+ */
57
64
  declare function createFlowInstance<TNodeData = any, TEdgeData = any, TLabelData = any>(options: CreateFlowInstanceOptions<TNodeData, TEdgeData, TLabelData>): FlowInstance<TNodeData, TEdgeData, TLabelData>;
58
65
  //#endregion
59
66
  //#region src/FlowInstanceContext.d.ts
@@ -67,9 +74,16 @@ declare function useOptionalFlowInstance(): FlowInstance | null;
67
74
  //#region src/hooks.d.ts
68
75
  type UseCreateFlowInstanceOptions<TNodeData = any, TEdgeData = any, TLabelData = any> = CreateFlowInstanceOptions<TNodeData, TEdgeData, TLabelData>;
69
76
  type UseFlowOptions<TNodeData = any, TEdgeData = any, TLabelData = any> = UseCreateFlowInstanceOptions<TNodeData, TEdgeData, TLabelData>;
77
+ /**
78
+ * Create one Flow instance for a component lifetime.
79
+ *
80
+ * Creation settings are initial-only; use `flow.configure()` for runtime
81
+ * changes. Event/listener props are rebound when their identities change.
82
+ */
70
83
  declare function useFlow<TNodeData = any, TEdgeData = any, TLabelData = any>(options: UseFlowOptions<TNodeData, TEdgeData, TLabelData>): FlowInstance<TNodeData, TEdgeData, TLabelData>;
71
84
  /** @deprecated Use `useFlow(options)`. */
72
85
  declare function useCreateFlowInstance<TNodeData = any, TEdgeData = any, TLabelData = any>(options: UseCreateFlowInstanceOptions<TNodeData, TEdgeData, TLabelData>): FlowInstance<TNodeData, TEdgeData, TLabelData>;
86
+ /** Subscribe to a derived slice of Flow context with optional equality. */
73
87
  declare function useFlowSelector<T>(selector: FlowSelector<T>, options?: {
74
88
  flow?: FlowInstance;
75
89
  isEqual?: FlowSelectorEquality<T>;
@@ -329,7 +343,7 @@ type MiniMapProps = {
329
343
  /**
330
344
  * Overview map. Deliberately thin: all math lives in core
331
345
  * (`getMinimapProjection`, `viewportCenteredOn`) and every interaction is a
332
- * plain store trigger — this component is exactly what you would write in
346
+ * controller viewport command — this component is exactly what you would write in
333
347
  * userland, kept here as a convenience.
334
348
  */
335
349
  declare function MiniMap(props: MiniMapProps): import("react").JSX.Element;
@@ -351,8 +365,7 @@ type ControlsProps = {
351
365
  children?: ReactNode;
352
366
  };
353
367
  /**
354
- * Zoom/fit toolbar. Deliberately thin: every button is one store trigger
355
- * (`zoomBy`, `fitView`) — replicate or extend it freely in userland.
368
+ * Zoom/fit toolbar backed by the controller's constrained viewport API.
356
369
  */
357
370
  declare function Controls(props: ControlsProps): import("react").JSX.Element;
358
371
  //#endregion
@@ -388,6 +401,11 @@ declare function EntityAnchor(props: EntityAnchorProps): import("react").JSX.Ele
388
401
  //#region src/Port.d.ts
389
402
  type PortProps = {
390
403
  name: string;
404
+ /**
405
+ * `inspect` makes a graph-owned port focusable/selectable without starting
406
+ * a connection. Defaults to `connect`.
407
+ */
408
+ interaction?: "connect" | "inspect";
391
409
  /**
392
410
  * Whether the DOM element reports its bounds back to the graph (default
393
411
  * true). Ports are unidirectional, one way or the other:
@@ -444,16 +462,6 @@ type DragHandleProps = HTMLAttributes<HTMLDivElement> & {
444
462
  */
445
463
  declare function DragHandle({ children, style, ...rest }: DragHandleProps): import("react").JSX.Element;
446
464
  //#endregion
447
- //#region src/NodeResizer.d.ts
448
- type NodeResizerProps = {
449
- handles?: ResizeHandle[];
450
- /** Defaults to node selection state. Constraints belong on `node.resize`. */
451
- isVisible?: boolean;
452
- className?: string;
453
- style?: CSSProperties;
454
- };
455
- declare function NodeResizer(props: NodeResizerProps): import("react").JSX.Element | null;
456
- //#endregion
457
465
  //#region src/baseStyles.d.ts
458
466
  /**
459
467
  * Minimal base stylesheet for the flow canvas. Intentionally tiny and
@@ -474,7 +482,7 @@ declare function NodeResizer(props: NodeResizerProps): import("react").JSX.Eleme
474
482
  * - `data-flow-selection-bounds` — the established multi-selection frame.
475
483
  */
476
484
  declare const FLOW_BASE_STYLES_ID = "flow-base-styles";
477
- declare const FLOW_BASE_CSS = "\n[data-flow-root] {\n --flow-selection-color: #2563eb;\n --flow-selection-bg: rgba(37, 99, 235, 0.12);\n --flow-selection-bounds-outset: 1px;\n --flow-layer-edges: 0;\n --flow-layer-entities: 10;\n --flow-layer-labels: 20;\n --flow-layer-interaction: 30;\n --flow-layer-guides: 40;\n --flow-layer-chrome: 50;\n touch-action: none;\n}\n\n[data-flow-root]:focus {\n outline: var(--flow-focus-outline, none);\n}\n\n[data-flow-interactive-when-selected] {\n pointer-events: none;\n}\n[data-flow-selected=\"true\"] [data-flow-interactive-when-selected] {\n pointer-events: auto;\n}\n\n[data-flow-selection-box] {\n position: absolute;\n box-sizing: border-box;\n border: calc(1px / var(--flow-zoom, 1)) solid var(--flow-selection-color);\n border-radius: 0;\n background: var(--flow-selection-bg);\n pointer-events: none;\n}\n\n[data-flow-selection-bounds] {\n position: absolute;\n box-sizing: border-box;\n outline: calc(1px / var(--flow-zoom, 1)) solid var(--flow-selection-color);\n outline-offset: calc(var(--flow-selection-bounds-outset) / var(--flow-zoom, 1));\n border-radius: 0;\n pointer-events: none;\n}\n";
485
+ declare const FLOW_BASE_CSS = "\n[data-flow-root] {\n --flow-selection-color: #2563eb;\n --flow-selection-bg: rgba(37, 99, 235, 0.12);\n --flow-selection-bounds-outset: 1px;\n --flow-layer-edges: 0;\n --flow-layer-entities: 10;\n --flow-layer-labels: 20;\n --flow-layer-interaction: 30;\n --flow-layer-guides: 40;\n --flow-layer-chrome: 50;\n touch-action: none;\n}\n\n[data-flow-root]:focus {\n outline: var(--flow-focus-outline, none);\n}\n\n[data-flow-interactive-when-selected] {\n pointer-events: none;\n}\n[data-flow-selected=\"true\"] [data-flow-interactive-when-selected] {\n pointer-events: auto;\n}\n\n[data-flow-custom-node] :where(\n button,\n input,\n select,\n textarea,\n a[href],\n [role=\"button\"],\n [tabindex],\n [data-flow-drag-handle],\n [data-flow-port-name]\n) {\n pointer-events: auto;\n}\n\n[data-flow-selection-box] {\n position: absolute;\n box-sizing: border-box;\n border: calc(1px / var(--flow-zoom, 1)) solid var(--flow-selection-color);\n border-radius: 0;\n background: var(--flow-selection-bg);\n pointer-events: none;\n}\n\n[data-flow-selection-bounds] {\n position: absolute;\n box-sizing: border-box;\n outline: calc(1px / var(--flow-zoom, 1)) solid var(--flow-selection-color);\n outline-offset: calc(var(--flow-selection-bounds-outset) / var(--flow-zoom, 1));\n border-radius: 0;\n pointer-events: none;\n}\n";
478
486
  /** Inject the base stylesheet once per document (deduped by element id). */
479
487
  declare function useFlowBaseStyles(): void;
480
488
  //#endregion
@@ -491,4 +499,4 @@ declare function useFlowColorMode(): {
491
499
  };
492
500
  declare function useApplyFlowColorMode(element: HTMLElement | null): void;
493
501
  //#endregion
494
- export { Background, type BackgroundProps, type BackgroundVariant, Controls, type ControlsProps, type CreateFlowInstanceOptions, CrossBackground, DotsBackground, DragHandle, type DragHandleProps, EntityAnchor, type EntityAnchorAlign, type EntityAnchorPoint, type EntityAnchorProps, type EntityAnchorSide, FLOW_BASE_CSS, FLOW_BASE_STYLES_ID, FlowColorModeScope, type FlowColorModeScopeProps, type FlowEdgeBinding, type FlowEventListeners, type FlowInstance, FlowInstanceProvider, type FlowInstanceSettings, type FlowNodeBinding, type FlowPortBinding, type FlowSelector, type FlowSelectorEquality, type FlowStoreListener, FlowToolbar, type FlowToolbarProps, type FlowToolbarState, LinesBackground, MiniMap, type MiniMapProps, NodeResizer, type NodeResizerProps, PanePortal, Panel, type PanelPosition, type PanelProps, Port, type PortProps, type ReactActiveGraphProjection, type ReactGraphEdgeAttrs, type ReactGraphNodeAttrs, type RenderConnectionLineProps, type RenderEdgeLabelProps, type RenderEdgeProps, type RenderNodeProps, StatelyFlow, type StatelyFlowProps, type StatelyFlowRootProps, type StatelyFlowStyle, type UseCreateFlowInstanceOptions, type UseFitViewExclusionOptions, type UseFlowInputOptions, type UseFlowOptions, type UseFlowToolbarOptions, ViewportPortal, createFlowInstance, useApplyFlowColorMode, useConnection, useCreateFlowInstance, useFitViewExclusion, useFlow, useFlowBaseStyles, useFlowColorMode, useFlowConnections, useFlowContext, useFlowEdge, useFlowEntity, useFlowInput, useFlowInstance, useFlowMeasurement, useFlowNode, useFlowPort, useFlowSelector, useFlowToolbar, useFlowValue, useFlowView, useFlowViewport, useKeyPress, useMeasuredSize, useNodesInitialized, useOptionalFlowInstance };
502
+ export { Background, type BackgroundProps, type BackgroundVariant, Controls, type ControlsProps, type CreateFlowInstanceOptions, CrossBackground, DotsBackground, DragHandle, type DragHandleProps, EntityAnchor, type EntityAnchorAlign, type EntityAnchorPoint, type EntityAnchorProps, type EntityAnchorSide, FLOW_BASE_CSS, FLOW_BASE_STYLES_ID, FlowColorModeScope, type FlowColorModeScopeProps, type FlowEdgeBinding, type FlowEventListeners, type FlowInstance, FlowInstanceProvider, type FlowInstanceSettings, type FlowNodeBinding, type FlowPortBinding, type FlowSelector, type FlowSelectorEquality, type FlowStoreListener, FlowToolbar, type FlowToolbarProps, type FlowToolbarState, LinesBackground, MiniMap, type MiniMapProps, PanePortal, Panel, type PanelPosition, type PanelProps, Port, type PortProps, type ReactActiveGraphProjection, type ReactGraphEdgeAttrs, type ReactGraphNodeAttrs, type RenderConnectionLineProps, type RenderEdgeLabelProps, type RenderEdgeProps, type RenderNodeProps, StatelyFlow, type StatelyFlowProps, type StatelyFlowRootProps, type StatelyFlowStyle, type UseCreateFlowInstanceOptions, type UseFitViewExclusionOptions, type UseFlowInputOptions, type UseFlowOptions, type UseFlowToolbarOptions, ViewportPortal, createFlowInstance, useApplyFlowColorMode, useConnection, useCreateFlowInstance, useFitViewExclusion, useFlow, useFlowBaseStyles, useFlowColorMode, useFlowConnections, useFlowContext, useFlowEdge, useFlowEntity, useFlowInput, useFlowInstance, useFlowMeasurement, useFlowNode, useFlowPort, useFlowSelector, useFlowToolbar, useFlowValue, useFlowView, useFlowViewport, useKeyPress, useMeasuredSize, useNodesInitialized, useOptionalFlowInstance };
package/dist/esm/index.js CHANGED
@@ -1,12 +1,19 @@
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, nodeDepths, pathToSVG, profileModesForContext, profilePoliciesForContext, viewportCenteredOn } from "@statelyai/flow";
4
- import { createContext, memo, useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
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
5
  import { Fragment, 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";
9
9
  //#region src/createFlowInstance.ts
10
+ /**
11
+ * Create the React adapter's imperative Flow instance.
12
+ *
13
+ * The instance owns its controller and all listeners registered through
14
+ * `listen` or creation options. `destroy()` removes those listeners and
15
+ * destroys the underlying controller.
16
+ */
10
17
  function createFlowInstance(options) {
11
18
  const { keymap, ...createFlowSettings } = options.settings ?? {};
12
19
  const flow = createFlow({
@@ -68,6 +75,12 @@ function createFlowInstance(options) {
68
75
  }
69
76
  //#endregion
70
77
  //#region src/hooks.ts
78
+ /**
79
+ * Create one Flow instance for a component lifetime.
80
+ *
81
+ * Creation settings are initial-only; use `flow.configure()` for runtime
82
+ * changes. Event/listener props are rebound when their identities change.
83
+ */
71
84
  function useFlow(options) {
72
85
  return useCreateFlowInstance(options);
73
86
  }
@@ -125,6 +138,7 @@ function useCreateFlowInstance(options) {
125
138
  }, [flow]);
126
139
  return flow;
127
140
  }
141
+ /** Subscribe to a derived slice of Flow context with optional equality. */
128
142
  function useFlowSelector(selector, options = {}) {
129
143
  const contextFlow = useOptionalFlowInstance();
130
144
  const flow = options.flow ?? contextFlow;
@@ -403,6 +417,20 @@ const FLOW_BASE_CSS = `
403
417
  pointer-events: auto;
404
418
  }
405
419
 
420
+ [data-flow-custom-node] :where(
421
+ button,
422
+ input,
423
+ select,
424
+ textarea,
425
+ a[href],
426
+ [role="button"],
427
+ [tabindex],
428
+ [data-flow-drag-handle],
429
+ [data-flow-port-name]
430
+ ) {
431
+ pointer-events: auto;
432
+ }
433
+
406
434
  [data-flow-selection-box] {
407
435
  position: absolute;
408
436
  box-sizing: border-box;
@@ -442,6 +470,18 @@ function useNodeRenderContext() {
442
470
  return context;
443
471
  }
444
472
  //#endregion
473
+ //#region src/focus.ts
474
+ /** Whether a focused descendant owns its own graph focus identity. */
475
+ function descendantOwnsFlowFocus(event) {
476
+ const target = event.target;
477
+ return target instanceof Element && target.closest("[data-flow-port-interaction=\"inspect\"]") !== null;
478
+ }
479
+ function clearEntityFocusOnBlur(flow, entityId, event) {
480
+ const nextTarget = event.relatedTarget;
481
+ if (nextTarget instanceof Node && event.currentTarget.contains(nextTarget)) return;
482
+ if (flow.getSnapshot().focusedEntityId === entityId) flow.internals.store.trigger.clearFocus();
483
+ }
484
+ //#endregion
445
485
  //#region src/LayerPortals.tsx
446
486
  const FlowLayerTargetsContext = createContext(null);
447
487
  function FlowLayerTargetsProvider(props) {
@@ -517,11 +557,6 @@ function nodePaintEntriesEqual(a, b) {
517
557
  function classNames$1(...values) {
518
558
  return values.filter(Boolean).join(" ");
519
559
  }
520
- function clearEntityFocusOnBlur(flow, entityId, event) {
521
- const nextTarget = event.relatedTarget;
522
- if (nextTarget instanceof Node && event.currentTarget.contains(nextTarget)) return;
523
- if (flow.getSnapshot().focusedEntityId === entityId) flow.internals.store.trigger.clearFocus();
524
- }
525
560
  function assignRef(ref, value) {
526
561
  if (typeof ref === "function") ref(value);
527
562
  else if (ref) ref.current = value;
@@ -897,10 +932,11 @@ function SelectionBoundsLayer(props) {
897
932
  }
898
933
  function EdgeLayer(props) {
899
934
  const instance = useFlowInstance();
935
+ const markerPrefix = `stately-flow-${useId().replace(/[^a-zA-Z0-9_-]/g, "")}`;
900
936
  const renderableIds = useRenderableIds(instance, props.onlyRenderVisible);
901
- const edgeIds = useFlowSelector((context) => getOrderedEntities(instance, context).edges.map((edge) => edge.id), { isEqual: stringArraysEqual });
937
+ const edgeIds = useFlowSelector((_context) => getOrderedEntities(instance).edges.map((edge) => edge.id), { isEqual: stringArraysEqual });
902
938
  const renderEdge = props.renderEdge;
903
- return /* @__PURE__ */ jsx("svg", {
939
+ return /* @__PURE__ */ jsxs("svg", {
904
940
  "aria-label": "edges",
905
941
  className: "stately-flow__edges",
906
942
  "data-flow-canvas-layer": true,
@@ -913,20 +949,22 @@ function EdgeLayer(props) {
913
949
  overflow: "visible",
914
950
  pointerEvents: "none"
915
951
  },
916
- children: /* @__PURE__ */ jsxs("g", { children: [edgeIds.map((id) => {
952
+ children: [/* @__PURE__ */ jsx(DefaultMarkerDefinitions, { prefix: markerPrefix }), /* @__PURE__ */ jsxs("g", { children: [edgeIds.map((id) => {
917
953
  if (renderableIds && !renderableIds.edgeIds.has(id)) return null;
918
954
  return /* @__PURE__ */ jsx(EdgeView, {
919
955
  flow: instance,
920
956
  geometryObserver: props.geometryObserver,
921
957
  id,
922
958
  projections: props.projections,
923
- renderEdge
959
+ renderEdge,
960
+ markerPrefix
924
961
  }, id);
925
962
  }), /* @__PURE__ */ jsx(ConnectionPreviewEdge, {
926
963
  flow: instance,
927
964
  renderEdge,
928
- renderConnectionLine: props.renderConnectionLine
929
- })] })
965
+ renderConnectionLine: props.renderConnectionLine,
966
+ markerPrefix
967
+ })] })]
930
968
  });
931
969
  }
932
970
  const EdgeView = memo(function EdgeView(props) {
@@ -970,7 +1008,10 @@ const EdgeView = memo(function EdgeView(props) {
970
1008
  "aria-label": props.flow.internals.engine.getAriaLabel(props.id),
971
1009
  "aria-selected": selected,
972
1010
  tabIndex: focusable ? 0 : void 0,
973
- onFocus: () => props.flow.internals.store.trigger.focus({ entityId: props.id }),
1011
+ onFocus: (event) => {
1012
+ if (descendantOwnsFlowFocus(event)) return;
1013
+ props.flow.internals.store.trigger.focus({ entityId: props.id });
1014
+ },
974
1015
  onBlur: (event) => clearEntityFocusOnBlur(props.flow, props.id, event)
975
1016
  };
976
1017
  if (props.renderEdge) return /* @__PURE__ */ jsxs("g", {
@@ -1010,7 +1051,8 @@ const EdgeView = memo(function EdgeView(props) {
1010
1051
  edge,
1011
1052
  pathData,
1012
1053
  attrs,
1013
- selected
1054
+ selected,
1055
+ markerPrefix: props.markerPrefix
1014
1056
  })
1015
1057
  });
1016
1058
  });
@@ -1042,6 +1084,7 @@ function ConnectionPreviewEdge(props) {
1042
1084
  pathData,
1043
1085
  attrs: {},
1044
1086
  selected: false,
1087
+ markerPrefix: props.markerPrefix,
1045
1088
  preview: true
1046
1089
  })
1047
1090
  });
@@ -1053,9 +1096,11 @@ function DefaultEdge(props) {
1053
1096
  d,
1054
1097
  "data-flow-edge-segment": index,
1055
1098
  fill: "none",
1056
- stroke: props.attrs.color ?? (props.preview ? props.edge.data?.isValid === false ? "#dc2626" : props.edge.data?.isValid === true ? "#16a34a" : "#0f766e" : props.selected ? "#2563eb" : "#94a3b8"),
1057
- strokeDasharray: props.preview ? "6 6" : void 0,
1058
- strokeWidth: props.attrs.strokeWidth ?? (props.selected ? 3 : 2),
1099
+ stroke: props.attrs.color ?? (props.preview ? props.edge.data?.isValid === false ? "#dc2626" : props.edge.data?.isValid === true ? "#16a34a" : "#0f766e" : props.selected ? "#2563eb" : props.edge.style?.stroke ?? "#94a3b8"),
1100
+ strokeDasharray: props.preview ? "6 6" : props.edge.style?.strokeDasharray,
1101
+ strokeWidth: props.attrs.strokeWidth ?? (props.selected ? 3 : props.edge.style?.strokeWidth ?? 2),
1102
+ markerStart: index === 0 ? markerToSVG(props.edge.style?.markerStart, props.markerPrefix) : void 0,
1103
+ markerEnd: index === props.pathData.segments.length - 1 ? markerToSVG(props.edge.style?.markerEnd, props.markerPrefix) : void 0,
1059
1104
  style: props.attrs.style
1060
1105
  }), props.preview ? null : /* @__PURE__ */ jsx("path", {
1061
1106
  d,
@@ -1074,6 +1119,52 @@ function DefaultEdge(props) {
1074
1119
  selected: props.selected
1075
1120
  })] });
1076
1121
  }
1122
+ function DefaultMarkerDefinitions(props) {
1123
+ return /* @__PURE__ */ jsxs("defs", { children: [
1124
+ /* @__PURE__ */ jsx("marker", {
1125
+ id: `${props.prefix}-arrow`,
1126
+ viewBox: "0 0 10 10",
1127
+ refX: "9",
1128
+ refY: "5",
1129
+ markerWidth: "6",
1130
+ markerHeight: "6",
1131
+ orient: "auto-start-reverse",
1132
+ children: /* @__PURE__ */ jsx("path", {
1133
+ d: "M 1 1 L 9 5 L 1 9",
1134
+ fill: "none",
1135
+ stroke: "context-stroke"
1136
+ })
1137
+ }),
1138
+ /* @__PURE__ */ jsx("marker", {
1139
+ id: `${props.prefix}-arrow-closed`,
1140
+ viewBox: "0 0 10 10",
1141
+ refX: "9",
1142
+ refY: "5",
1143
+ markerWidth: "6",
1144
+ markerHeight: "6",
1145
+ orient: "auto-start-reverse",
1146
+ children: /* @__PURE__ */ jsx("path", {
1147
+ d: "M 1 1 L 9 5 L 1 9 Z",
1148
+ fill: "context-stroke"
1149
+ })
1150
+ }),
1151
+ /* @__PURE__ */ jsx("marker", {
1152
+ id: `${props.prefix}-circle`,
1153
+ viewBox: "0 0 10 10",
1154
+ refX: "5",
1155
+ refY: "5",
1156
+ markerWidth: "5",
1157
+ markerHeight: "5",
1158
+ orient: "auto",
1159
+ children: /* @__PURE__ */ jsx("circle", {
1160
+ cx: "5",
1161
+ cy: "5",
1162
+ r: "4",
1163
+ fill: "context-stroke"
1164
+ })
1165
+ })
1166
+ ] });
1167
+ }
1077
1168
  function EdgeHitTarget(props) {
1078
1169
  return /* @__PURE__ */ jsx(Fragment, { children: props.pathData.segments.map((segment, index) => {
1079
1170
  return /* @__PURE__ */ jsx("path", {
@@ -1293,7 +1384,7 @@ function NodeLayer(props) {
1293
1384
  const instance = useFlowInstance();
1294
1385
  const renderableIds = useRenderableIds(instance, props.onlyRenderVisible);
1295
1386
  const nodePaintEntries = useFlowSelector((context) => {
1296
- const paintOrder = new Map(getOrderedEntities(instance, context).nodes.map((node, orderIndex) => [node.id, orderIndex]));
1387
+ const paintOrder = new Map(getOrderedEntities(instance).nodes.map((node, orderIndex) => [node.id, orderIndex]));
1297
1388
  return context.graph.nodes.flatMap((node) => {
1298
1389
  const orderIndex = paintOrder.get(node.id);
1299
1390
  return orderIndex === void 0 ? [] : [{
@@ -1328,33 +1419,8 @@ function NodeLayer(props) {
1328
1419
  })
1329
1420
  });
1330
1421
  }
1331
- function getOrderedEntities(flow, context) {
1332
- const maybeEngine = flow.internals.engine;
1333
- const visibleOrdered = maybeEngine.getVisibleEntities?.();
1334
- if (visibleOrdered) return visibleOrdered;
1335
- const ordered = maybeEngine.getEntityOrder?.();
1336
- if (ordered) return ordered;
1337
- const depths = nodeDepths(context.graph.nodes);
1338
- const nodeOrder = new Map(context.graph.nodes.map((node, index) => [node.id, index]));
1339
- const edgeOrder = new Map(context.graph.edges.map((edge, index) => [edge.id, index]));
1340
- return {
1341
- nodes: [...context.graph.nodes].sort((a, b) => {
1342
- const depthDelta = (depths.get(a.id) ?? 0) - (depths.get(b.id) ?? 0);
1343
- if (depthDelta !== 0) return depthDelta;
1344
- const zDelta = (a.zIndex ?? 0) - (b.zIndex ?? 0);
1345
- if (zDelta !== 0) return zDelta;
1346
- const selectedDelta = Number(context.selection.has(a.id)) - Number(context.selection.has(b.id));
1347
- if (selectedDelta !== 0) return selectedDelta;
1348
- return (nodeOrder.get(a.id) ?? 0) - (nodeOrder.get(b.id) ?? 0);
1349
- }),
1350
- edges: [...context.graph.edges].sort((a, b) => {
1351
- const depthDelta = Math.max(depths.get(a.sourceId) ?? 0, depths.get(a.targetId) ?? 0) - Math.max(depths.get(b.sourceId) ?? 0, depths.get(b.targetId) ?? 0);
1352
- if (depthDelta !== 0) return depthDelta;
1353
- const zDelta = (a.zIndex ?? 0) - (b.zIndex ?? 0);
1354
- if (zDelta !== 0) return zDelta;
1355
- return (edgeOrder.get(a.id) ?? 0) - (edgeOrder.get(b.id) ?? 0);
1356
- })
1357
- };
1422
+ function getOrderedEntities(flow) {
1423
+ return flow.internals.engine.getVisibleEntities();
1358
1424
  }
1359
1425
  function rectsShallowEqual(a, b) {
1360
1426
  return a.x === b.x && a.y === b.y && a.width === b.width && a.height === b.height;
@@ -1445,6 +1511,8 @@ const NodeView = memo(function NodeView(props) {
1445
1511
  projections: props.projections
1446
1512
  });
1447
1513
  const focusable = props.flow.internals.engine.resolveEntityConfig(node).focusable;
1514
+ const groupGeometry = node.group ? props.flow.internals.engine.getGroupGeometry(node.id) : void 0;
1515
+ const groupHasInteractiveContent = Boolean(groupGeometry?.contentBounds.width && groupGeometry.contentBounds.height);
1448
1516
  return /* @__PURE__ */ jsx(NodeRenderContextProvider, {
1449
1517
  value: {
1450
1518
  node,
@@ -1461,7 +1529,10 @@ const NodeView = memo(function NodeView(props) {
1461
1529
  "aria-selected": selected,
1462
1530
  "aria-label": props.flow.internals.engine.getAriaLabel(props.id) + (selected ? ", selected" : ""),
1463
1531
  tabIndex: focusable ? 0 : void 0,
1464
- onFocus: () => props.flow.internals.store.trigger.focus({ entityId: props.id }),
1532
+ onFocus: (event) => {
1533
+ if (descendantOwnsFlowFocus(event)) return;
1534
+ props.flow.internals.store.trigger.focus({ entityId: props.id });
1535
+ },
1465
1536
  onBlur: (event) => clearEntityFocusOnBlur(props.flow, props.id, event),
1466
1537
  style: {
1467
1538
  ...nodePositionStyle(bounds, {
@@ -1469,20 +1540,27 @@ const NodeView = memo(function NodeView(props) {
1469
1540
  dragging
1470
1541
  }),
1471
1542
  cursor: dragging ? "grabbing" : "grab",
1472
- pointerEvents: "auto",
1543
+ pointerEvents: node.group && groupHasInteractiveContent ? "none" : "auto",
1473
1544
  userSelect: "none",
1474
1545
  zIndex: props.orderIndex,
1475
1546
  opacity: attrs.opacity
1476
1547
  },
1477
- children: props.renderNode ? props.renderNode({
1478
- flow: props.flow,
1479
- node,
1480
- bounds,
1481
- attrs,
1482
- selected,
1483
- dragging,
1484
- hovered,
1485
- highlights
1548
+ children: props.renderNode ? /* @__PURE__ */ jsx("div", {
1549
+ "data-flow-custom-node": true,
1550
+ style: {
1551
+ display: "contents",
1552
+ pointerEvents: "none"
1553
+ },
1554
+ children: props.renderNode({
1555
+ flow: props.flow,
1556
+ node,
1557
+ bounds,
1558
+ attrs,
1559
+ selected,
1560
+ dragging,
1561
+ hovered,
1562
+ highlights
1563
+ })
1486
1564
  }) : node.group ? /* @__PURE__ */ jsx(DefaultGroupNode, {
1487
1565
  flow: props.flow,
1488
1566
  node,
@@ -1548,6 +1626,7 @@ function DefaultGroupNode(props) {
1548
1626
  color: "#0f172a",
1549
1627
  font: "600 13px/1.2 system-ui, sans-serif",
1550
1628
  cursor: props.dragging ? "grabbing" : "grab",
1629
+ pointerEvents: "auto",
1551
1630
  userSelect: "none",
1552
1631
  ...props.attrs.style
1553
1632
  },
@@ -1710,7 +1789,7 @@ function FlowToolbar(props) {
1710
1789
  /**
1711
1790
  * Overview map. Deliberately thin: all math lives in core
1712
1791
  * (`getMinimapProjection`, `viewportCenteredOn`) and every interaction is a
1713
- * plain store trigger — this component is exactly what you would write in
1792
+ * controller viewport command — this component is exactly what you would write in
1714
1793
  * userland, kept here as a convenience.
1715
1794
  */
1716
1795
  function MiniMap(props) {
@@ -1735,7 +1814,7 @@ function MiniMap(props) {
1735
1814
  x: event.clientX - rect.left,
1736
1815
  y: event.clientY - rect.top
1737
1816
  });
1738
- flow.internals.store.trigger.setViewport(viewportCenteredOn(canvasPoint, viewportSize, viewport.zoom));
1817
+ flow.viewport.set(viewportCenteredOn(canvasPoint, viewportSize, viewport.zoom));
1739
1818
  };
1740
1819
  return /* @__PURE__ */ jsxs("svg", {
1741
1820
  "aria-label": "minimap",
@@ -1858,8 +1937,7 @@ const buttonStyle = {
1858
1937
  cursor: "pointer"
1859
1938
  };
1860
1939
  /**
1861
- * Zoom/fit toolbar. Deliberately thin: every button is one store trigger
1862
- * (`zoomBy`, `fitView`) — replicate or extend it freely in userland.
1940
+ * Zoom/fit toolbar backed by the controller's constrained viewport API.
1863
1941
  */
1864
1942
  function Controls(props) {
1865
1943
  const flow = useFlowInstance();
@@ -1879,21 +1957,21 @@ function Controls(props) {
1879
1957
  type: "button",
1880
1958
  "aria-label": "zoom in",
1881
1959
  style: buttonStyle,
1882
- onClick: () => flow.internals.store.trigger.zoomBy({ factor: 1.2 }),
1960
+ onClick: () => flow.viewport.zoomBy(1.2),
1883
1961
  children: "+"
1884
1962
  }),
1885
1963
  /* @__PURE__ */ jsx("button", {
1886
1964
  type: "button",
1887
1965
  "aria-label": "zoom out",
1888
1966
  style: buttonStyle,
1889
- onClick: () => flow.internals.store.trigger.zoomBy({ factor: 1 / 1.2 }),
1967
+ onClick: () => flow.viewport.zoomBy(1 / 1.2),
1890
1968
  children: "−"
1891
1969
  }),
1892
1970
  /* @__PURE__ */ jsx("button", {
1893
1971
  type: "button",
1894
1972
  "aria-label": "fit view",
1895
1973
  style: buttonStyle,
1896
- onClick: () => flow.internals.store.trigger.fitView({ padding: .1 }),
1974
+ onClick: () => flow.viewport.fit({ padding: .1 }),
1897
1975
  children: "⛶"
1898
1976
  }),
1899
1977
  props.children
@@ -2000,6 +2078,11 @@ function Port(props) {
2000
2078
  const observerRef = useRef(null);
2001
2079
  const observer = useMeasurementObserver();
2002
2080
  const measured = props.measured ?? true;
2081
+ const interaction = props.interaction ?? "connect";
2082
+ const port = node.ports?.find((candidate) => candidate.name === props.name);
2083
+ const portId = `${node.id}:${props.name}`;
2084
+ const focusable = flow.internals.engine.resolveEntityConfig(node).focusable;
2085
+ const selected = useFlowSelector((context) => context.selection.has(portId), { flow });
2003
2086
  useLayoutEffect(() => {
2004
2087
  const element = ref.current;
2005
2088
  if (!element || !measured) return;
@@ -2035,8 +2118,20 @@ function Port(props) {
2035
2118
  className: props.className,
2036
2119
  "data-flow-port-name": props.name,
2037
2120
  "data-flow-node-id": node.id,
2121
+ "data-flow-port-id": portId,
2122
+ "data-flow-port-interaction": interaction,
2123
+ "data-flow-selected": selected ? "true" : void 0,
2038
2124
  "data-connection-candidate": candidate ?? void 0,
2039
- style: props.style,
2125
+ role: interaction === "inspect" ? "button" : void 0,
2126
+ "aria-label": interaction === "inspect" ? `${port?.label ?? props.name} port` : void 0,
2127
+ "aria-pressed": interaction === "inspect" ? selected : void 0,
2128
+ tabIndex: interaction === "inspect" && focusable ? 0 : void 0,
2129
+ onFocus: interaction === "inspect" && focusable ? () => flow.internals.store.trigger.focus({ entityId: portId }) : void 0,
2130
+ onBlur: interaction === "inspect" ? (event) => clearEntityFocusOnBlur(flow, portId, event) : void 0,
2131
+ style: {
2132
+ pointerEvents: "auto",
2133
+ ...props.style
2134
+ },
2040
2135
  children: props.children
2041
2136
  });
2042
2137
  }
@@ -2073,96 +2168,6 @@ function DragHandle({ children, style, ...rest }) {
2073
2168
  });
2074
2169
  }
2075
2170
  //#endregion
2076
- //#region src/NodeResizer.tsx
2077
- const DEFAULT_HANDLES = [
2078
- "top",
2079
- "right",
2080
- "bottom",
2081
- "left",
2082
- "top-left",
2083
- "top-right",
2084
- "bottom-left",
2085
- "bottom-right"
2086
- ];
2087
- const HANDLE_STYLE = {
2088
- top: {
2089
- left: "50%",
2090
- top: 0,
2091
- cursor: "ns-resize"
2092
- },
2093
- right: {
2094
- left: "100%",
2095
- top: "50%",
2096
- cursor: "ew-resize"
2097
- },
2098
- bottom: {
2099
- left: "50%",
2100
- top: "100%",
2101
- cursor: "ns-resize"
2102
- },
2103
- left: {
2104
- left: 0,
2105
- top: "50%",
2106
- cursor: "ew-resize"
2107
- },
2108
- "top-left": {
2109
- left: 0,
2110
- top: 0,
2111
- cursor: "nwse-resize"
2112
- },
2113
- "top-right": {
2114
- left: "100%",
2115
- top: 0,
2116
- cursor: "nesw-resize"
2117
- },
2118
- "bottom-left": {
2119
- left: 0,
2120
- top: "100%",
2121
- cursor: "nesw-resize"
2122
- },
2123
- "bottom-right": {
2124
- left: "100%",
2125
- top: "100%",
2126
- cursor: "nwse-resize"
2127
- }
2128
- };
2129
- function NodeResizer(props) {
2130
- const { node } = useNodeRenderContext();
2131
- const selected = useFlowSelector((context) => context.selection.has(node.id));
2132
- if (!(props.isVisible ?? selected)) return null;
2133
- return /* @__PURE__ */ jsx("div", {
2134
- "aria-hidden": "true",
2135
- className: props.className,
2136
- "data-flow-node-resizer": "",
2137
- style: {
2138
- position: "absolute",
2139
- inset: 0,
2140
- pointerEvents: "none",
2141
- ...props.style
2142
- },
2143
- children: (props.handles ?? DEFAULT_HANDLES).map((handle) => /* @__PURE__ */ jsx("div", {
2144
- "data-entity-id": node.id,
2145
- "data-flow-part": `resize-handle:${handle}`,
2146
- "data-flow-resize-handle": handle,
2147
- style: {
2148
- position: "absolute",
2149
- width: "calc(10px / var(--flow-zoom, 1))",
2150
- height: "calc(10px / var(--flow-zoom, 1))",
2151
- minWidth: 6,
2152
- minHeight: 6,
2153
- boxSizing: "border-box",
2154
- border: "calc(1px / var(--flow-zoom, 1)) solid #ffffff",
2155
- borderRadius: "calc(2px / var(--flow-zoom, 1))",
2156
- background: "var(--flow-selection-color, #2563eb)",
2157
- boxShadow: "0 0 0 calc(1px / var(--flow-zoom, 1)) rgba(15, 23, 42, 0.8)",
2158
- pointerEvents: "auto",
2159
- transform: "translate(-50%, -50%)",
2160
- ...HANDLE_STYLE[handle]
2161
- }
2162
- }, handle))
2163
- });
2164
- }
2165
- //#endregion
2166
2171
  //#region src/ColorMode.tsx
2167
2172
  function FlowColorModeScope(props) {
2168
2173
  const { colorMode, resolvedColorMode } = useFlowColorMode();
@@ -2207,4 +2212,4 @@ function useApplyFlowColorMode(element) {
2207
2212
  ]);
2208
2213
  }
2209
2214
  //#endregion
2210
- export { Background, Controls, CrossBackground, DotsBackground, DragHandle, EntityAnchor, FLOW_BASE_CSS, FLOW_BASE_STYLES_ID, FlowColorModeScope, FlowInstanceProvider, FlowToolbar, LinesBackground, MiniMap, NodeResizer, PanePortal, Panel, Port, StatelyFlow, ViewportPortal, createFlowInstance, useApplyFlowColorMode, useConnection, useCreateFlowInstance, useFitViewExclusion, useFlow, useFlowBaseStyles, useFlowColorMode, useFlowConnections, useFlowContext, useFlowEdge, useFlowEntity, useFlowInput, useFlowInstance, useFlowMeasurement, useFlowNode, useFlowPort, useFlowSelector, useFlowToolbar, useFlowValue, useFlowView, useFlowViewport, useKeyPress, useMeasuredSize, useNodesInitialized, useOptionalFlowInstance };
2215
+ export { Background, Controls, CrossBackground, DotsBackground, DragHandle, EntityAnchor, FLOW_BASE_CSS, FLOW_BASE_STYLES_ID, FlowColorModeScope, FlowInstanceProvider, FlowToolbar, LinesBackground, MiniMap, PanePortal, Panel, Port, StatelyFlow, ViewportPortal, createFlowInstance, useApplyFlowColorMode, useConnection, useCreateFlowInstance, useFitViewExclusion, useFlow, useFlowBaseStyles, useFlowColorMode, useFlowConnections, useFlowContext, useFlowEdge, useFlowEntity, useFlowInput, useFlowInstance, useFlowMeasurement, useFlowNode, useFlowPort, useFlowSelector, useFlowToolbar, useFlowValue, useFlowView, useFlowViewport, useKeyPress, useMeasuredSize, useNodesInitialized, useOptionalFlowInstance };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@statelyai/flow-react",
3
- "version": "0.8.0",
3
+ "version": "0.9.2",
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.7.0",
38
- "@statelyai/flow-dom": "3.3.0"
37
+ "@statelyai/flow": "0.8.2",
38
+ "@statelyai/flow-dom": "3.4.2"
39
39
  },
40
40
  "peerDependencies": {
41
41
  "react": "^18.2.0 || ^19.0.0",