reflex-xyflow 0.0.1__py3-none-any.whl

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.
@@ -0,0 +1,147 @@
1
+ """reflex-xyflow: a Reflex custom component wrapping ``@xyflow/react``.
2
+
3
+ Import the factory functions to build node-based UIs in pure Python::
4
+
5
+ import reflex as rx
6
+ import reflex_xyflow as xyflow
7
+
8
+ def index():
9
+ return rx.box(
10
+ xyflow.react_flow(
11
+ xyflow.background(),
12
+ xyflow.controls(),
13
+ xyflow.mini_map(),
14
+ nodes=State.nodes,
15
+ edges=State.edges,
16
+ on_nodes_change=State.on_nodes_change,
17
+ on_connect=State.on_connect,
18
+ fit_view=True,
19
+ ),
20
+ width="100vw",
21
+ height="100vh",
22
+ )
23
+ """
24
+
25
+ from __future__ import annotations
26
+
27
+ from ._base import XYFLOW_NPM_PACKAGE, XYFLOW_NPM_VERSION
28
+ from .components import (
29
+ Background,
30
+ Controls,
31
+ Handle,
32
+ MiniMap,
33
+ NodeResizer,
34
+ NodeToolbar,
35
+ Panel,
36
+ background,
37
+ controls,
38
+ handle,
39
+ mini_map,
40
+ node_resizer,
41
+ node_toolbar,
42
+ panel,
43
+ )
44
+ from .custom import XYFlowCustomTypes, custom_types, js_component
45
+ from .enums import (
46
+ BackgroundVariant,
47
+ ColorMode,
48
+ ConnectionLineType,
49
+ ConnectionMode,
50
+ HandleType,
51
+ MarkerType,
52
+ PanOnScrollMode,
53
+ Position,
54
+ SelectionMode,
55
+ )
56
+ from .flow import (
57
+ ReactFlow,
58
+ ReactFlowProvider,
59
+ react_flow,
60
+ react_flow_provider,
61
+ )
62
+ from .imperative import (
63
+ XYFlowController,
64
+ download_image,
65
+ fit_view,
66
+ flow_controller,
67
+ get_edges,
68
+ get_nodes,
69
+ get_viewport,
70
+ set_center,
71
+ set_viewport,
72
+ zoom_in,
73
+ zoom_out,
74
+ )
75
+ from .utils import (
76
+ add_edge,
77
+ apply_edge_changes,
78
+ apply_node_changes,
79
+ deserialize_graph,
80
+ layout_nodes,
81
+ reconnect_edge,
82
+ serialize_graph,
83
+ )
84
+
85
+ __version__ = "0.0.1"
86
+
87
+ __all__ = [
88
+ # version / package metadata
89
+ "__version__",
90
+ "XYFLOW_NPM_PACKAGE",
91
+ "XYFLOW_NPM_VERSION",
92
+ # core flow
93
+ "ReactFlow",
94
+ "ReactFlowProvider",
95
+ "react_flow",
96
+ "react_flow_provider",
97
+ # components (classes)
98
+ "Background",
99
+ "Controls",
100
+ "MiniMap",
101
+ "Panel",
102
+ "Handle",
103
+ "NodeResizer",
104
+ "NodeToolbar",
105
+ # components (factories)
106
+ "background",
107
+ "controls",
108
+ "mini_map",
109
+ "panel",
110
+ "handle",
111
+ "node_resizer",
112
+ "node_toolbar",
113
+ # enums
114
+ "Position",
115
+ "HandleType",
116
+ "BackgroundVariant",
117
+ "ConnectionMode",
118
+ "ConnectionLineType",
119
+ "MarkerType",
120
+ "PanOnScrollMode",
121
+ "SelectionMode",
122
+ "ColorMode",
123
+ # utils
124
+ "apply_node_changes",
125
+ "apply_edge_changes",
126
+ "add_edge",
127
+ "reconnect_edge",
128
+ "serialize_graph",
129
+ "deserialize_graph",
130
+ "layout_nodes",
131
+ # imperative useReactFlow API
132
+ "XYFlowController",
133
+ "flow_controller",
134
+ "fit_view",
135
+ "zoom_in",
136
+ "zoom_out",
137
+ "set_viewport",
138
+ "set_center",
139
+ "get_nodes",
140
+ "get_edges",
141
+ "get_viewport",
142
+ "download_image",
143
+ # custom node/edge types
144
+ "js_component",
145
+ "custom_types",
146
+ "XYFlowCustomTypes",
147
+ ]
reflex_xyflow/_base.py ADDED
@@ -0,0 +1,29 @@
1
+ """Base component for the reflex-xyflow wrapper.
2
+
3
+ All wrapped React Flow components inherit from :class:`XYFlowBase`, which pins
4
+ the ``@xyflow/react`` npm dependency and injects the required stylesheet once
5
+ per page.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import reflex as rx
11
+
12
+ # Pin the wrapped npm package version. Bump this together with the Python
13
+ # package version when adopting a new React Flow release.
14
+ XYFLOW_NPM_PACKAGE = "@xyflow/react"
15
+ XYFLOW_NPM_VERSION = "12.8.6"
16
+
17
+
18
+ class XYFlowBase(rx.Component):
19
+ """Base class for every component wrapped from ``@xyflow/react``."""
20
+
21
+ # The npm dependency installed into the compiled frontend.
22
+ library = f"{XYFLOW_NPM_PACKAGE}@{XYFLOW_NPM_VERSION}"
23
+
24
+ # React Flow is a default export-less ESM module: every public component is
25
+ # a named export, so ``is_default`` stays False (the Reflex default).
26
+
27
+ def add_custom_code(self) -> list[str]:
28
+ """Import the React Flow base stylesheet exactly once per page."""
29
+ return ['import "@xyflow/react/dist/style.css";']
@@ -0,0 +1,141 @@
1
+ """Supporting React Flow components: Background, Controls, MiniMap, Panel,
2
+ Handle, NodeResizer and NodeToolbar.
3
+
4
+ All of these are named exports of ``@xyflow/react`` (v12), so they share the
5
+ :class:`XYFlowBase` base and only declare the props we surface to Python.
6
+ """
7
+
8
+ from typing import Any
9
+
10
+ import reflex as rx
11
+ from reflex.vars import Var
12
+
13
+ from ._base import XYFlowBase
14
+
15
+
16
+ # -- Event-handler argument specs --------------------------------------------
17
+ def _no_args_spec() -> tuple:
18
+ return ()
19
+
20
+
21
+ def _interactive_spec(is_interactive: Var[bool]) -> tuple[Var[bool]]:
22
+ return (is_interactive,)
23
+
24
+
25
+ def _resize_spec(event: Var, params: Var[dict]) -> tuple[Var[dict]]:
26
+ """NodeResizer resize handlers: forward the ``{x, y, width, height}`` params."""
27
+ return (params,)
28
+
29
+
30
+ class Background(XYFlowBase):
31
+ """Renders the dotted / lined / cross background pattern of the canvas."""
32
+
33
+ tag = "Background"
34
+
35
+ variant: Var[str] # "dots" | "lines" | "cross"
36
+ color: Var[str]
37
+ bg_color: Var[str]
38
+ gap: Var[int]
39
+ size: Var[float]
40
+ offset: Var[float]
41
+ line_width: Var[float]
42
+ # Multiple backgrounds may be layered; an id keeps their patterns distinct.
43
+ id: Var[str]
44
+
45
+
46
+ class Controls(XYFlowBase):
47
+ """Zoom in / out, fit-view and interactivity-lock control panel."""
48
+
49
+ tag = "Controls"
50
+
51
+ show_zoom: Var[bool]
52
+ show_fit_view: Var[bool]
53
+ show_interactive: Var[bool]
54
+ fit_view_options: Var[dict[str, Any]]
55
+ position: Var[str]
56
+ orientation: Var[str] # "horizontal" | "vertical"
57
+
58
+ on_zoom_in: rx.EventHandler[_no_args_spec]
59
+ on_zoom_out: rx.EventHandler[_no_args_spec]
60
+ on_fit_view: rx.EventHandler[_no_args_spec]
61
+ on_interactive_change: rx.EventHandler[_interactive_spec]
62
+
63
+
64
+ class MiniMap(XYFlowBase):
65
+ """Overview map of the whole graph."""
66
+
67
+ tag = "MiniMap"
68
+
69
+ pannable: Var[bool]
70
+ zoomable: Var[bool]
71
+ node_color: Var[str]
72
+ node_stroke_color: Var[str]
73
+ node_border_radius: Var[int]
74
+ node_stroke_width: Var[float]
75
+ mask_color: Var[str]
76
+ mask_stroke_color: Var[str]
77
+ position: Var[str]
78
+ aria_label: Var[str]
79
+
80
+
81
+ class Panel(XYFlowBase):
82
+ """Absolutely-positioned overlay (e.g. for toolbars/buttons on the canvas)."""
83
+
84
+ tag = "Panel"
85
+
86
+ position: Var[str] # "top-left" | "top-right" | "bottom-left" ...
87
+
88
+
89
+ class Handle(XYFlowBase):
90
+ """Connection point rendered inside a custom node component."""
91
+
92
+ tag = "Handle"
93
+
94
+ type: Var[str] # "source" | "target"
95
+ position: Var[str] # "top" | "right" | "bottom" | "left"
96
+ id: Var[str]
97
+ is_connectable: Var[bool]
98
+ is_connectable_start: Var[bool]
99
+ is_connectable_end: Var[bool]
100
+
101
+
102
+ class NodeResizer(XYFlowBase):
103
+ """Adds resize handles around a custom node."""
104
+
105
+ tag = "NodeResizer"
106
+
107
+ color: Var[str]
108
+ handle_class_name: Var[str]
109
+ line_class_name: Var[str]
110
+ is_visible: Var[bool]
111
+ min_width: Var[int]
112
+ min_height: Var[int]
113
+ max_width: Var[int]
114
+ max_height: Var[int]
115
+ keep_aspect_ratio: Var[bool]
116
+
117
+ on_resize_start: rx.EventHandler[_resize_spec]
118
+ on_resize: rx.EventHandler[_resize_spec]
119
+ on_resize_end: rx.EventHandler[_resize_spec]
120
+
121
+
122
+ class NodeToolbar(XYFlowBase):
123
+ """Floating toolbar attached to a node (shown on selection by default)."""
124
+
125
+ tag = "NodeToolbar"
126
+
127
+ node_id: Var[str]
128
+ is_visible: Var[bool]
129
+ position: Var[str]
130
+ offset: Var[float]
131
+ align: Var[str] # "center" | "start" | "end"
132
+
133
+
134
+ # Convenience factory aliases -------------------------------------------------
135
+ background = Background.create
136
+ controls = Controls.create
137
+ mini_map = MiniMap.create
138
+ panel = Panel.create
139
+ handle = Handle.create
140
+ node_resizer = NodeResizer.create
141
+ node_toolbar = NodeToolbar.create
@@ -0,0 +1,124 @@
1
+ """Custom node & edge type registration (Phase 3).
2
+
3
+ React Flow renders custom nodes/edges by mapping a *type* string to a React
4
+ component via the ``nodeTypes`` / ``edgeTypes`` props. Those values must be
5
+ component *functions*, not strings. Authoring full JSX renderers in Python is a
6
+ documented non-goal, so this module ships:
7
+
8
+ * :func:`js_component` — wrap a JS identifier as a raw Var so it compiles to a
9
+ bare reference (``BadgeNode``) instead of a string (``"BadgeNode"``);
10
+ * :func:`custom_types` — a headless component that injects two ready-made JS
11
+ templates whose content is driven by each element's Python ``data``:
12
+
13
+ - ``BadgeNode`` — a pill node with left/right :class:`Handle`s rendering
14
+ ``data.label``;
15
+ - ``ButtonEdge`` — a custom edge using ``getBezierPath`` + ``BaseEdge`` with
16
+ an ``EdgeLabelRenderer`` label from ``data.label``.
17
+
18
+ Register them on the flow::
19
+
20
+ xyflow.react_flow(
21
+ xyflow.custom_types(),
22
+ node_types={"badge": xyflow.js_component("BadgeNode")},
23
+ edge_types={"button": xyflow.js_component("ButtonEdge")},
24
+ nodes=[{"id": "1", "type": "badge", "data": {"label": "Hi"}, ...}],
25
+ )
26
+ """
27
+
28
+ from __future__ import annotations
29
+
30
+ from typing import Any
31
+
32
+ import reflex as rx
33
+ from reflex.vars import Var
34
+
35
+
36
+ def js_component(name: str) -> Var:
37
+ """Reference a JS component by name as a raw Var (unquoted identifier)."""
38
+ return Var(_js_expr=name, _var_type=Any)
39
+
40
+
41
+ # JSX templates injected into the page module. They are transformed by the
42
+ # frontend bundler (the page is a .jsx file). Content comes from each element's
43
+ # ``data`` so Python state drives what they show.
44
+ _CUSTOM_TYPES_CODE = """
45
+ function BadgeNode({ data, selected }) {
46
+ return (
47
+ <div
48
+ className="xyflow-badge-node"
49
+ style={{
50
+ padding: "10px 18px",
51
+ borderRadius: "9999px",
52
+ border: "2px solid " + (selected ? "#ec4899" : "#6366f1"),
53
+ background: "var(--xyflow-badge-bg, #ffffff)",
54
+ color: "var(--xyflow-badge-fg, #1f2937)",
55
+ fontSize: "12px",
56
+ fontWeight: 600,
57
+ boxShadow: "0 1px 3px rgba(0,0,0,0.12)",
58
+ }}
59
+ >
60
+ <Handle type="target" position={Position.Left} />
61
+ {data?.label ?? ""}
62
+ <Handle type="source" position={Position.Right} />
63
+ </div>
64
+ );
65
+ }
66
+
67
+ function ButtonEdge({ id, sourceX, sourceY, targetX, targetY, sourcePosition, targetPosition, markerEnd, style, data }) {
68
+ const [edgePath, labelX, labelY] = getBezierPath({
69
+ sourceX, sourceY, sourcePosition, targetX, targetY, targetPosition,
70
+ });
71
+ return (
72
+ <>
73
+ <BaseEdge id={id} path={edgePath} markerEnd={markerEnd} style={style} />
74
+ <EdgeLabelRenderer>
75
+ <div
76
+ className="nodrag nopan"
77
+ style={{
78
+ position: "absolute",
79
+ transform: "translate(-50%, -50%) translate(" + labelX + "px," + labelY + "px)",
80
+ pointerEvents: "all",
81
+ background: "#ffffff",
82
+ border: "1px solid #6366f1",
83
+ borderRadius: "6px",
84
+ padding: "2px 6px",
85
+ fontSize: "11px",
86
+ fontWeight: 600,
87
+ }}
88
+ >
89
+ {data?.label ?? "edge"}
90
+ </div>
91
+ </EdgeLabelRenderer>
92
+ </>
93
+ );
94
+ }
95
+ """.strip()
96
+
97
+
98
+ class XYFlowCustomTypes(rx.Component):
99
+ """Headless component that defines the bundled custom node/edge templates.
100
+
101
+ Place it once inside (or alongside) a flow; it renders nothing and only
102
+ injects the ``BadgeNode`` / ``ButtonEdge`` JS definitions and the React Flow
103
+ helpers they import.
104
+ """
105
+
106
+ tag = "XYFlowCustomTypes"
107
+ library = None # local component defined in custom code
108
+
109
+ def add_imports(self) -> dict[str, list[str]]:
110
+ return {
111
+ "@xyflow/react": [
112
+ "Handle",
113
+ "Position",
114
+ "BaseEdge",
115
+ "EdgeLabelRenderer",
116
+ "getBezierPath",
117
+ ],
118
+ }
119
+
120
+ def add_custom_code(self) -> list[str]:
121
+ return [_CUSTOM_TYPES_CODE + "\n\nfunction XYFlowCustomTypes() { return null; }"]
122
+
123
+
124
+ custom_types = XYFlowCustomTypes.create
reflex_xyflow/enums.py ADDED
@@ -0,0 +1,76 @@
1
+ """String enums mirroring the ``@xyflow/react`` literal-union props.
2
+
3
+ These are plain ``str`` enums so they serialize transparently to the JS values
4
+ React Flow expects (``"top"``, ``"dots"``, ``"smoothstep"`` ...).
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from enum import Enum
10
+
11
+
12
+ class Position(str, Enum):
13
+ """Handle / toolbar placement on a node."""
14
+
15
+ TOP = "top"
16
+ RIGHT = "right"
17
+ BOTTOM = "bottom"
18
+ LEFT = "left"
19
+
20
+
21
+ class HandleType(str, Enum):
22
+ """Whether a handle starts (source) or receives (target) a connection."""
23
+
24
+ SOURCE = "source"
25
+ TARGET = "target"
26
+
27
+
28
+ class BackgroundVariant(str, Enum):
29
+ """Pattern rendered by the :class:`Background` component."""
30
+
31
+ DOTS = "dots"
32
+ LINES = "lines"
33
+ CROSS = "cross"
34
+
35
+
36
+ class ConnectionMode(str, Enum):
37
+ """Strictness of which handles may be connected together."""
38
+
39
+ STRICT = "strict"
40
+ LOOSE = "loose"
41
+
42
+
43
+ class ConnectionLineType(str, Enum):
44
+ """Shape of the line drawn while creating a connection."""
45
+
46
+ BEZIER = "default"
47
+ STRAIGHT = "straight"
48
+ STEP = "step"
49
+ SMOOTHSTEP = "smoothstep"
50
+ SIMPLEBEZIER = "simplebezier"
51
+
52
+
53
+ class MarkerType(str, Enum):
54
+ """Arrow head style for edge markers."""
55
+
56
+ ARROW = "arrow"
57
+ ARROWCLOSED = "arrowclosed"
58
+
59
+
60
+ class PanOnScrollMode(str, Enum):
61
+ FREE = "free"
62
+ VERTICAL = "vertical"
63
+ HORIZONTAL = "horizontal"
64
+
65
+
66
+ class SelectionMode(str, Enum):
67
+ PARTIAL = "partial"
68
+ FULL = "full"
69
+
70
+
71
+ class ColorMode(str, Enum):
72
+ """Built-in theming mode for React Flow 12."""
73
+
74
+ LIGHT = "light"
75
+ DARK = "dark"
76
+ SYSTEM = "system"