react-state-flow 0.1.0 → 0.1.1

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,4 @@
1
+ import type { GraphData } from './types.js';
2
+ export type { GraphData, GraphNode, GraphEdge } from './types.js';
3
+ export declare function parseProject(projectRoot: string): GraphData;
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/parser/index.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAwB,MAAM,YAAY,CAAA;AAEjE,YAAY,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAqBjE,wBAAgB,YAAY,CAAC,WAAW,EAAE,MAAM,GAAG,SAAS,CAgD3D"}
@@ -0,0 +1,65 @@
1
+ import { readFileSync, readdirSync, statSync } from 'fs';
2
+ import { join, relative } from 'path';
3
+ import { parseFile } from './parse-file.js';
4
+ const EXTENSIONS = ['.tsx', '.jsx', '.ts', '.js'];
5
+ function collectFiles(dir) {
6
+ const results = [];
7
+ const IGNORE = ['node_modules', '.git', 'dist', 'build', '.next'];
8
+ for (const entry of readdirSync(dir)) {
9
+ if (IGNORE.includes(entry))
10
+ continue;
11
+ const full = join(dir, entry);
12
+ const stat = statSync(full);
13
+ if (stat.isDirectory()) {
14
+ results.push(...collectFiles(full));
15
+ }
16
+ else if (EXTENSIONS.some((ext) => full.endsWith(ext))) {
17
+ results.push(full);
18
+ }
19
+ }
20
+ return results;
21
+ }
22
+ export function parseProject(projectRoot) {
23
+ const files = collectFiles(projectRoot);
24
+ // Pass 1: collect all component node ids for cross-file edge resolution
25
+ const globalComponentSet = new Set();
26
+ for (const file of files) {
27
+ const code = readFileSync(file, 'utf-8');
28
+ const relPath = relative(projectRoot, file);
29
+ const { nodes } = parseFile(code, relPath);
30
+ for (const n of nodes) {
31
+ if (n.type === 'component')
32
+ globalComponentSet.add(n.id);
33
+ }
34
+ }
35
+ // Pass 2: full parse with global component set for cross-file edges
36
+ const allNodes = [];
37
+ const allEdges = [];
38
+ for (const file of files) {
39
+ const code = readFileSync(file, 'utf-8');
40
+ const relPath = relative(projectRoot, file);
41
+ const { nodes, edges } = parseFile(code, relPath, globalComponentSet);
42
+ allNodes.push(...nodes);
43
+ allEdges.push(...edges);
44
+ }
45
+ // Deduplicate nodes by id (keep first occurrence)
46
+ const seen = new Set();
47
+ const uniqueNodes = allNodes.filter((n) => {
48
+ if (seen.has(n.id))
49
+ return false;
50
+ seen.add(n.id);
51
+ return true;
52
+ });
53
+ // Remove edges referencing unknown nodes
54
+ const nodeIds = new Set(uniqueNodes.map((n) => n.id));
55
+ const validEdges = allEdges.filter((e) => nodeIds.has(e.source) && nodeIds.has(e.target));
56
+ // Deduplicate edges
57
+ const edgeSeen = new Set();
58
+ const uniqueEdges = validEdges.filter((e) => {
59
+ if (edgeSeen.has(e.id))
60
+ return false;
61
+ edgeSeen.add(e.id);
62
+ return true;
63
+ });
64
+ return { nodes: uniqueNodes, edges: uniqueEdges };
65
+ }
@@ -0,0 +1,8 @@
1
+ import type { GraphNode, GraphEdge } from './types.js';
2
+ interface FileResult {
3
+ nodes: GraphNode[];
4
+ edges: GraphEdge[];
5
+ }
6
+ export declare function parseFile(code: string, filePath: string, externalComponents?: Set<string>): FileResult;
7
+ export {};
8
+ //# sourceMappingURL=parse-file.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parse-file.d.ts","sourceRoot":"","sources":["../../src/parser/parse-file.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAKtD,UAAU,UAAU;IAClB,KAAK,EAAE,SAAS,EAAE,CAAA;IAClB,KAAK,EAAE,SAAS,EAAE,CAAA;CACnB;AA8CD,wBAAgB,SAAS,CACvB,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,kBAAkB,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,GAC/B,UAAU,CA8LZ"}
@@ -0,0 +1,251 @@
1
+ import { parse } from '@babel/parser';
2
+ import _traverse from '@babel/traverse';
3
+ import * as t from '@babel/types';
4
+ // @babel/traverse ESM interop
5
+ const traverse = _traverse.default ?? _traverse;
6
+ function isComponentName(name) {
7
+ return /^[A-Z]/.test(name);
8
+ }
9
+ function getStateSlots(path) {
10
+ const slots = [];
11
+ path.traverse({
12
+ CallExpression(innerPath) {
13
+ const callee = innerPath.node.callee;
14
+ const isHook = (t.isIdentifier(callee, { name: 'useState' }) ||
15
+ t.isIdentifier(callee, { name: 'useReducer' })) &&
16
+ t.isIdentifier(callee);
17
+ if (!isHook)
18
+ return;
19
+ // useState([initialValue]) → destructure [state, setState]
20
+ const parent = innerPath.parentPath;
21
+ if (parent?.isVariableDeclarator() &&
22
+ t.isArrayPattern(parent.node.id)) {
23
+ const first = parent.node.id.elements[0];
24
+ if (t.isIdentifier(first))
25
+ slots.push(first.name);
26
+ }
27
+ },
28
+ });
29
+ return slots;
30
+ }
31
+ function getContextUsages(path) {
32
+ const names = [];
33
+ path.traverse({
34
+ CallExpression(innerPath) {
35
+ const callee = innerPath.node.callee;
36
+ if (!t.isIdentifier(callee, { name: 'useContext' }))
37
+ return;
38
+ const arg = innerPath.node.arguments[0];
39
+ if (t.isIdentifier(arg))
40
+ names.push(arg.name);
41
+ },
42
+ });
43
+ return names;
44
+ }
45
+ // A1: Accept optional externalComponents for cross-file edge resolution
46
+ export function parseFile(code, filePath, externalComponents) {
47
+ const nodes = [];
48
+ const edges = [];
49
+ let ast;
50
+ try {
51
+ ast = parse(code, {
52
+ sourceType: 'module',
53
+ plugins: ['jsx', 'typescript', 'decorators-legacy'],
54
+ });
55
+ }
56
+ catch (err) {
57
+ const msg = err instanceof Error ? err.message : String(err);
58
+ console.warn(`[RSF] Failed to parse ${filePath}: ${msg}`);
59
+ return { nodes, edges };
60
+ }
61
+ // Map: context variable name → node id
62
+ const contextMap = new Map();
63
+ // Track component names found in this file for JSX child resolution
64
+ const componentSet = new Set();
65
+ // A5: Use Set for O(1) edge deduplication
66
+ const edgeIdSet = new Set();
67
+ function registerComponent(name, path, line) {
68
+ const stateSlots = getStateSlots(path);
69
+ const contextUsages = getContextUsages(path);
70
+ // Detect if this component renders a Context.Provider
71
+ let isContextProvider = false;
72
+ let contextName;
73
+ path.traverse({
74
+ JSXOpeningElement(innerPath) {
75
+ const el = innerPath.node.name;
76
+ // <XxxContext.Provider>
77
+ if (t.isJSXMemberExpression(el) &&
78
+ t.isJSXIdentifier(el.property, { name: 'Provider' }) &&
79
+ t.isJSXIdentifier(el.object)) {
80
+ isContextProvider = true;
81
+ contextName = el.object.name;
82
+ }
83
+ },
84
+ });
85
+ const node = {
86
+ id: name,
87
+ type: 'component',
88
+ label: name,
89
+ file: filePath,
90
+ line,
91
+ stateSlots,
92
+ isContextProvider,
93
+ contextName,
94
+ };
95
+ nodes.push(node);
96
+ componentSet.add(name);
97
+ // Add context-subscription edges
98
+ for (const ctxName of contextUsages) {
99
+ const ctxId = contextMap.get(ctxName) ?? ctxName;
100
+ const edgeId = `${ctxId}->${name}`;
101
+ if (!edgeIdSet.has(edgeId)) {
102
+ edgeIdSet.add(edgeId);
103
+ edges.push({
104
+ id: edgeId,
105
+ source: ctxId,
106
+ target: name,
107
+ type: 'context-subscription',
108
+ });
109
+ }
110
+ }
111
+ // A4: Create Provider → Context edge
112
+ if (isContextProvider && contextName) {
113
+ const ctxId = contextMap.get(contextName) ?? contextName;
114
+ const edgeId = `${name}->${ctxId}:provides`;
115
+ if (!edgeIdSet.has(edgeId)) {
116
+ edgeIdSet.add(edgeId);
117
+ edges.push({
118
+ id: edgeId,
119
+ source: name,
120
+ target: ctxId,
121
+ type: 'context-provision',
122
+ });
123
+ }
124
+ }
125
+ return node;
126
+ }
127
+ // First pass: find createContext calls to build contextMap
128
+ traverse(ast, {
129
+ VariableDeclarator(path) {
130
+ if (t.isCallExpression(path.node.init) &&
131
+ t.isIdentifier(path.node.init.callee, {
132
+ name: 'createContext',
133
+ }) &&
134
+ t.isIdentifier(path.node.id)) {
135
+ const ctxVarName = path.node.id.name;
136
+ const nodeId = ctxVarName;
137
+ contextMap.set(ctxVarName, nodeId);
138
+ nodes.push({
139
+ id: nodeId,
140
+ type: 'context',
141
+ label: ctxVarName,
142
+ file: filePath,
143
+ line: path.node.loc?.start.line ?? 0,
144
+ stateSlots: [],
145
+ isContextProvider: false,
146
+ });
147
+ }
148
+ },
149
+ });
150
+ // Second pass: find component declarations
151
+ traverse(ast, {
152
+ // function MyComponent() { ... }
153
+ FunctionDeclaration(path) {
154
+ const name = path.node.id?.name;
155
+ if (name && isComponentName(name)) {
156
+ registerComponent(name, path, path.node.loc?.start.line ?? 0);
157
+ }
158
+ },
159
+ // const MyComponent = () => { ... } or function() { ... } or memo(...) etc.
160
+ VariableDeclarator(path) {
161
+ if (!t.isIdentifier(path.node.id))
162
+ return;
163
+ const name = path.node.id.name;
164
+ if (!isComponentName(name))
165
+ return;
166
+ const init = path.node.init;
167
+ if (t.isArrowFunctionExpression(init) || t.isFunctionExpression(init)) {
168
+ registerComponent(name, path, path.node.loc?.start.line ?? 0);
169
+ return;
170
+ }
171
+ // A2: HOC / memo / forwardRef wrapping a function
172
+ if (t.isCallExpression(init)) {
173
+ const firstArg = init.arguments[0];
174
+ if (firstArg &&
175
+ (t.isArrowFunctionExpression(firstArg) ||
176
+ t.isFunctionExpression(firstArg) ||
177
+ t.isIdentifier(firstArg))) {
178
+ registerComponent(name, path, path.node.loc?.start.line ?? 0);
179
+ }
180
+ }
181
+ },
182
+ // A3: export default function() { ... } — derive name from filename
183
+ ExportDefaultDeclaration(path) {
184
+ const decl = path.node.declaration;
185
+ const isAnonFn = (t.isFunctionDeclaration(decl) || t.isArrowFunctionExpression(decl)) &&
186
+ !decl.id;
187
+ if (!isAnonFn)
188
+ return;
189
+ const base = filePath.split('/').pop() ?? filePath;
190
+ const name = base.replace(/\.[^.]+$/, '');
191
+ if (!isComponentName(name))
192
+ return;
193
+ registerComponent(name, path, path.node.loc?.start.line ?? 0);
194
+ },
195
+ });
196
+ // Third pass: find JSX parent→child relationships
197
+ traverse(ast, {
198
+ FunctionDeclaration(path) {
199
+ extractJSXChildren(path, edges, edgeIdSet, componentSet, externalComponents);
200
+ },
201
+ VariableDeclarator(path) {
202
+ if (!t.isIdentifier(path.node.id))
203
+ return;
204
+ const name = path.node.id.name;
205
+ if (!isComponentName(name))
206
+ return;
207
+ const init = path.node.init;
208
+ const isFn = t.isArrowFunctionExpression(init) || t.isFunctionExpression(init);
209
+ const isWrapped = t.isCallExpression(init);
210
+ if (isFn || isWrapped) {
211
+ extractJSXChildren(path, edges, edgeIdSet, componentSet, externalComponents, name);
212
+ }
213
+ },
214
+ });
215
+ return { nodes, edges };
216
+ }
217
+ function extractJSXChildren(path, edges, edgeIdSet, componentSet, externalComponents, parentNameOverride) {
218
+ const parentName = parentNameOverride ??
219
+ path.node.id?.name;
220
+ if (!parentName || !isComponentName(parentName))
221
+ return;
222
+ // A1: Use externalComponents (global set) when available for cross-file resolution
223
+ const allKnown = externalComponents ?? componentSet;
224
+ path.traverse({
225
+ JSXOpeningElement(innerPath) {
226
+ const el = innerPath.node.name;
227
+ let childName;
228
+ if (t.isJSXIdentifier(el) && isComponentName(el.name)) {
229
+ childName = el.name;
230
+ }
231
+ // A6: Handle <Namespace.Component /> — use namespace as the component reference
232
+ if (!childName && t.isJSXMemberExpression(el)) {
233
+ const ns = t.isJSXIdentifier(el.object) ? el.object.name : undefined;
234
+ if (ns && allKnown.has(ns))
235
+ childName = ns;
236
+ }
237
+ if (!childName || !allKnown.has(childName))
238
+ return;
239
+ const edgeId = `${parentName}->${childName}`;
240
+ if (edgeIdSet.has(edgeId))
241
+ return; // A5: O(1) check
242
+ edgeIdSet.add(edgeId);
243
+ edges.push({
244
+ id: edgeId,
245
+ source: parentName,
246
+ target: childName,
247
+ type: 'parent-child',
248
+ });
249
+ },
250
+ });
251
+ }
@@ -0,0 +1,23 @@
1
+ export type NodeType = 'component' | 'context';
2
+ export interface GraphNode {
3
+ id: string;
4
+ type: NodeType;
5
+ label: string;
6
+ file: string;
7
+ line: number;
8
+ stateSlots: string[];
9
+ isContextProvider: boolean;
10
+ contextName?: string;
11
+ }
12
+ export type EdgeType = 'parent-child' | 'context-subscription' | 'context-provision';
13
+ export interface GraphEdge {
14
+ id: string;
15
+ source: string;
16
+ target: string;
17
+ type: EdgeType;
18
+ }
19
+ export interface GraphData {
20
+ nodes: GraphNode[];
21
+ edges: GraphEdge[];
22
+ }
23
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/parser/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,QAAQ,GAAG,WAAW,GAAG,SAAS,CAAA;AAE9C,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,QAAQ,CAAA;IACd,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,UAAU,EAAE,MAAM,EAAE,CAAA;IACpB,iBAAiB,EAAE,OAAO,CAAA;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AAED,MAAM,MAAM,QAAQ,GAAG,cAAc,GAAG,sBAAsB,GAAG,mBAAmB,CAAA;AAEpF,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAA;IACV,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,QAAQ,CAAA;CACf;AAED,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,SAAS,EAAE,CAAA;IAClB,KAAK,EAAE,SAAS,EAAE,CAAA;CACnB"}
@@ -0,0 +1 @@
1
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-state-flow",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Visualize React component hierarchy, state flow, and real-time render events",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -19,7 +19,7 @@
19
19
  },
20
20
  "files": [
21
21
  "dist",
22
- "!dist/parser"
22
+ "ui/dist"
23
23
  ],
24
24
  "engines": {
25
25
  "node": ">=18"
@@ -0,0 +1 @@
1
+ .react-flow{direction:ltr;--xy-edge-stroke-default: #b1b1b7;--xy-edge-stroke-width-default: 1;--xy-edge-stroke-selected-default: #555;--xy-connectionline-stroke-default: #b1b1b7;--xy-connectionline-stroke-width-default: 1;--xy-attribution-background-color-default: rgba(255, 255, 255, .5);--xy-minimap-background-color-default: #fff;--xy-minimap-mask-background-color-default: rgba(240, 240, 240, .6);--xy-minimap-mask-stroke-color-default: transparent;--xy-minimap-mask-stroke-width-default: 1;--xy-minimap-node-background-color-default: #e2e2e2;--xy-minimap-node-stroke-color-default: transparent;--xy-minimap-node-stroke-width-default: 2;--xy-background-color-default: transparent;--xy-background-pattern-dots-color-default: #91919a;--xy-background-pattern-lines-color-default: #eee;--xy-background-pattern-cross-color-default: #e2e2e2;background-color:var(--xy-background-color, var(--xy-background-color-default));--xy-node-color-default: inherit;--xy-node-border-default: 1px solid #1a192b;--xy-node-background-color-default: #fff;--xy-node-group-background-color-default: rgba(240, 240, 240, .25);--xy-node-boxshadow-hover-default: 0 1px 4px 1px rgba(0, 0, 0, .08);--xy-node-boxshadow-selected-default: 0 0 0 .5px #1a192b;--xy-node-border-radius-default: 3px;--xy-handle-background-color-default: #1a192b;--xy-handle-border-color-default: #fff;--xy-selection-background-color-default: rgba(0, 89, 220, .08);--xy-selection-border-default: 1px dotted rgba(0, 89, 220, .8);--xy-controls-button-background-color-default: #fefefe;--xy-controls-button-background-color-hover-default: #f4f4f4;--xy-controls-button-color-default: inherit;--xy-controls-button-color-hover-default: inherit;--xy-controls-button-border-color-default: #eee;--xy-controls-box-shadow-default: 0 0 2px 1px rgba(0, 0, 0, .08);--xy-edge-label-background-color-default: #ffffff;--xy-edge-label-color-default: inherit;--xy-resize-background-color-default: #3367d9}.react-flow.dark{--xy-edge-stroke-default: #3e3e3e;--xy-edge-stroke-width-default: 1;--xy-edge-stroke-selected-default: #727272;--xy-connectionline-stroke-default: #b1b1b7;--xy-connectionline-stroke-width-default: 1;--xy-attribution-background-color-default: rgba(150, 150, 150, .25);--xy-minimap-background-color-default: #141414;--xy-minimap-mask-background-color-default: rgba(60, 60, 60, .6);--xy-minimap-mask-stroke-color-default: transparent;--xy-minimap-mask-stroke-width-default: 1;--xy-minimap-node-background-color-default: #2b2b2b;--xy-minimap-node-stroke-color-default: transparent;--xy-minimap-node-stroke-width-default: 2;--xy-background-color-default: #141414;--xy-background-pattern-dots-color-default: #777;--xy-background-pattern-lines-color-default: #777;--xy-background-pattern-cross-color-default: #777;--xy-node-color-default: #f8f8f8;--xy-node-border-default: 1px solid #3c3c3c;--xy-node-background-color-default: #1e1e1e;--xy-node-group-background-color-default: rgba(240, 240, 240, .25);--xy-node-boxshadow-hover-default: 0 1px 4px 1px rgba(255, 255, 255, .08);--xy-node-boxshadow-selected-default: 0 0 0 .5px #999;--xy-handle-background-color-default: #bebebe;--xy-handle-border-color-default: #1e1e1e;--xy-selection-background-color-default: rgba(200, 200, 220, .08);--xy-selection-border-default: 1px dotted rgba(200, 200, 220, .8);--xy-controls-button-background-color-default: #2b2b2b;--xy-controls-button-background-color-hover-default: #3e3e3e;--xy-controls-button-color-default: #f8f8f8;--xy-controls-button-color-hover-default: #fff;--xy-controls-button-border-color-default: #5b5b5b;--xy-controls-box-shadow-default: 0 0 2px 1px rgba(0, 0, 0, .08);--xy-edge-label-background-color-default: #141414;--xy-edge-label-color-default: #f8f8f8}.react-flow__background{background-color:var(--xy-background-color-props, var(--xy-background-color, var(--xy-background-color-default)));pointer-events:none;z-index:-1}.react-flow__container{position:absolute;width:100%;height:100%;top:0;left:0}.react-flow__pane{z-index:1}.react-flow__pane.draggable{cursor:grab}.react-flow__pane.dragging{cursor:grabbing}.react-flow__pane.selection{cursor:pointer}.react-flow__viewport{transform-origin:0 0;z-index:2;pointer-events:none}.react-flow__renderer{z-index:4}.react-flow__selection{z-index:6}.react-flow__nodesselection-rect:focus,.react-flow__nodesselection-rect:focus-visible{outline:none}.react-flow__edge-path{stroke:var(--xy-edge-stroke, var(--xy-edge-stroke-default));stroke-width:var(--xy-edge-stroke-width, var(--xy-edge-stroke-width-default));fill:none}.react-flow__connection-path{stroke:var(--xy-connectionline-stroke, var(--xy-connectionline-stroke-default));stroke-width:var(--xy-connectionline-stroke-width, var(--xy-connectionline-stroke-width-default));fill:none}.react-flow .react-flow__edges{position:absolute}.react-flow .react-flow__edges svg{overflow:visible;position:absolute;pointer-events:none}.react-flow__edge{pointer-events:visibleStroke}.react-flow__edge.selectable{cursor:pointer}.react-flow__edge.animated path{stroke-dasharray:5;animation:dashdraw .5s linear infinite}.react-flow__edge.animated path.react-flow__edge-interaction{stroke-dasharray:none;animation:none}.react-flow__edge.inactive{pointer-events:none}.react-flow__edge.selected,.react-flow__edge:focus,.react-flow__edge:focus-visible{outline:none}.react-flow__edge.selected .react-flow__edge-path,.react-flow__edge.selectable:focus .react-flow__edge-path,.react-flow__edge.selectable:focus-visible .react-flow__edge-path{stroke:var(--xy-edge-stroke-selected, var(--xy-edge-stroke-selected-default))}.react-flow__edge-textwrapper{pointer-events:all}.react-flow__edge .react-flow__edge-text{pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none}.react-flow__arrowhead polyline{stroke:var(--xy-edge-stroke, var(--xy-edge-stroke-default))}.react-flow__arrowhead polyline.arrowclosed{fill:var(--xy-edge-stroke, var(--xy-edge-stroke-default))}.react-flow__connection{pointer-events:none}.react-flow__connection .animated{stroke-dasharray:5;animation:dashdraw .5s linear infinite}svg.react-flow__connectionline{z-index:1001;overflow:visible;position:absolute}.react-flow__nodes{pointer-events:none;transform-origin:0 0}.react-flow__node{position:absolute;-webkit-user-select:none;-moz-user-select:none;user-select:none;pointer-events:all;transform-origin:0 0;box-sizing:border-box;cursor:default}.react-flow__node.selectable{cursor:pointer}.react-flow__node.draggable{cursor:grab;pointer-events:all}.react-flow__node.draggable.dragging{cursor:grabbing}.react-flow__nodesselection{z-index:3;transform-origin:left top;pointer-events:none}.react-flow__nodesselection-rect{position:absolute;pointer-events:all;cursor:grab}.react-flow__handle{position:absolute;pointer-events:none;min-width:5px;min-height:5px;width:6px;height:6px;background-color:var(--xy-handle-background-color, var(--xy-handle-background-color-default));border:1px solid var(--xy-handle-border-color, var(--xy-handle-border-color-default));border-radius:100%}.react-flow__handle.connectingfrom{pointer-events:all}.react-flow__handle.connectionindicator{pointer-events:all;cursor:crosshair}.react-flow__handle-bottom{top:auto;left:50%;bottom:0;transform:translate(-50%,50%)}.react-flow__handle-top{top:0;left:50%;transform:translate(-50%,-50%)}.react-flow__handle-left{top:50%;left:0;transform:translate(-50%,-50%)}.react-flow__handle-right{top:50%;right:0;transform:translate(50%,-50%)}.react-flow__edgeupdater{cursor:move;pointer-events:all}.react-flow__pane.selection .react-flow__panel{pointer-events:none}.react-flow__panel{position:absolute;z-index:5;margin:15px}.react-flow__panel.top{top:0}.react-flow__panel.bottom{bottom:0}.react-flow__panel.top.center,.react-flow__panel.bottom.center{left:50%;transform:translate(-15px) translate(-50%)}.react-flow__panel.left{left:0}.react-flow__panel.right{right:0}.react-flow__panel.left.center,.react-flow__panel.right.center{top:50%;transform:translateY(-15px) translateY(-50%)}.react-flow__attribution{font-size:10px;background:var(--xy-attribution-background-color, var(--xy-attribution-background-color-default));padding:2px 3px;margin:0}.react-flow__attribution a{text-decoration:none;color:#999}@keyframes dashdraw{0%{stroke-dashoffset:10}}.react-flow__edgelabel-renderer{position:absolute;width:100%;height:100%;pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none;left:0;top:0}.react-flow__viewport-portal{position:absolute;width:100%;height:100%;left:0;top:0;-webkit-user-select:none;-moz-user-select:none;user-select:none}.react-flow__minimap{background:var( --xy-minimap-background-color-props, var(--xy-minimap-background-color, var(--xy-minimap-background-color-default)) )}.react-flow__minimap-svg{display:block}.react-flow__minimap-mask{fill:var( --xy-minimap-mask-background-color-props, var(--xy-minimap-mask-background-color, var(--xy-minimap-mask-background-color-default)) );stroke:var( --xy-minimap-mask-stroke-color-props, var(--xy-minimap-mask-stroke-color, var(--xy-minimap-mask-stroke-color-default)) );stroke-width:var( --xy-minimap-mask-stroke-width-props, var(--xy-minimap-mask-stroke-width, var(--xy-minimap-mask-stroke-width-default)) )}.react-flow__minimap-node{fill:var( --xy-minimap-node-background-color-props, var(--xy-minimap-node-background-color, var(--xy-minimap-node-background-color-default)) );stroke:var( --xy-minimap-node-stroke-color-props, var(--xy-minimap-node-stroke-color, var(--xy-minimap-node-stroke-color-default)) );stroke-width:var( --xy-minimap-node-stroke-width-props, var(--xy-minimap-node-stroke-width, var(--xy-minimap-node-stroke-width-default)) )}.react-flow__background-pattern.dots{fill:var( --xy-background-pattern-color-props, var(--xy-background-pattern-color, var(--xy-background-pattern-dots-color-default)) )}.react-flow__background-pattern.lines{stroke:var( --xy-background-pattern-color-props, var(--xy-background-pattern-color, var(--xy-background-pattern-lines-color-default)) )}.react-flow__background-pattern.cross{stroke:var( --xy-background-pattern-color-props, var(--xy-background-pattern-color, var(--xy-background-pattern-cross-color-default)) )}.react-flow__controls{display:flex;flex-direction:column;box-shadow:var(--xy-controls-box-shadow, var(--xy-controls-box-shadow-default))}.react-flow__controls.horizontal{flex-direction:row}.react-flow__controls-button{display:flex;justify-content:center;align-items:center;height:26px;width:26px;padding:4px;border:none;background:var(--xy-controls-button-background-color, var(--xy-controls-button-background-color-default));border-bottom:1px solid var( --xy-controls-button-border-color-props, var(--xy-controls-button-border-color, var(--xy-controls-button-border-color-default)) );color:var( --xy-controls-button-color-props, var(--xy-controls-button-color, var(--xy-controls-button-color-default)) );cursor:pointer;-webkit-user-select:none;-moz-user-select:none;user-select:none}.react-flow__controls-button svg{width:100%;max-width:12px;max-height:12px;fill:currentColor}.react-flow__edge.updating .react-flow__edge-path{stroke:#777}.react-flow__edge-text{font-size:10px}.react-flow__node.selectable:focus,.react-flow__node.selectable:focus-visible{outline:none}.react-flow__node-input,.react-flow__node-default,.react-flow__node-output,.react-flow__node-group{padding:10px;border-radius:var(--xy-node-border-radius, var(--xy-node-border-radius-default));width:150px;font-size:12px;color:var(--xy-node-color, var(--xy-node-color-default));text-align:center;border:var(--xy-node-border, var(--xy-node-border-default));background-color:var(--xy-node-background-color, var(--xy-node-background-color-default))}.react-flow__node-input.selectable:hover,.react-flow__node-default.selectable:hover,.react-flow__node-output.selectable:hover,.react-flow__node-group.selectable:hover{box-shadow:var(--xy-node-boxshadow-hover, var(--xy-node-boxshadow-hover-default))}.react-flow__node-input.selectable.selected,.react-flow__node-input.selectable:focus,.react-flow__node-input.selectable:focus-visible,.react-flow__node-default.selectable.selected,.react-flow__node-default.selectable:focus,.react-flow__node-default.selectable:focus-visible,.react-flow__node-output.selectable.selected,.react-flow__node-output.selectable:focus,.react-flow__node-output.selectable:focus-visible,.react-flow__node-group.selectable.selected,.react-flow__node-group.selectable:focus,.react-flow__node-group.selectable:focus-visible{box-shadow:var(--xy-node-boxshadow-selected, var(--xy-node-boxshadow-selected-default))}.react-flow__node-group{background-color:var(--xy-node-group-background-color, var(--xy-node-group-background-color-default))}.react-flow__nodesselection-rect,.react-flow__selection{background:var(--xy-selection-background-color, var(--xy-selection-background-color-default));border:var(--xy-selection-border, var(--xy-selection-border-default))}.react-flow__nodesselection-rect:focus,.react-flow__nodesselection-rect:focus-visible,.react-flow__selection:focus,.react-flow__selection:focus-visible{outline:none}.react-flow__controls-button:hover{background:var( --xy-controls-button-background-color-hover-props, var(--xy-controls-button-background-color-hover, var(--xy-controls-button-background-color-hover-default)) );color:var( --xy-controls-button-color-hover-props, var(--xy-controls-button-color-hover, var(--xy-controls-button-color-hover-default)) )}.react-flow__controls-button:disabled{pointer-events:none}.react-flow__controls-button:disabled svg{fill-opacity:.4}.react-flow__controls-button:last-child{border-bottom:none}.react-flow__controls.horizontal .react-flow__controls-button{border-bottom:none;border-right:1px solid var( --xy-controls-button-border-color-props, var(--xy-controls-button-border-color, var(--xy-controls-button-border-color-default)) )}.react-flow__controls.horizontal .react-flow__controls-button:last-child{border-right:none}.react-flow__resize-control{position:absolute}.react-flow__resize-control.left,.react-flow__resize-control.right{cursor:ew-resize}.react-flow__resize-control.top,.react-flow__resize-control.bottom{cursor:ns-resize}.react-flow__resize-control.top.left,.react-flow__resize-control.bottom.right{cursor:nwse-resize}.react-flow__resize-control.bottom.left,.react-flow__resize-control.top.right{cursor:nesw-resize}.react-flow__resize-control.handle{width:5px;height:5px;border:1px solid #fff;border-radius:1px;background-color:var(--xy-resize-background-color, var(--xy-resize-background-color-default));translate:-50% -50%}.react-flow__resize-control.handle.left{left:0;top:50%}.react-flow__resize-control.handle.right{left:100%;top:50%}.react-flow__resize-control.handle.top{left:50%;top:0}.react-flow__resize-control.handle.bottom{left:50%;top:100%}.react-flow__resize-control.handle.top.left,.react-flow__resize-control.handle.bottom.left{left:0}.react-flow__resize-control.handle.top.right,.react-flow__resize-control.handle.bottom.right{left:100%}.react-flow__resize-control.line{border-color:var(--xy-resize-background-color, var(--xy-resize-background-color-default));border-width:0;border-style:solid}.react-flow__resize-control.line.left,.react-flow__resize-control.line.right{width:1px;transform:translate(-50%);top:0;height:100%}.react-flow__resize-control.line.left{left:0;border-left-width:1px}.react-flow__resize-control.line.right{left:100%;border-right-width:1px}.react-flow__resize-control.line.top,.react-flow__resize-control.line.bottom{height:1px;transform:translateY(-50%);left:0;width:100%}.react-flow__resize-control.line.top{top:0;border-top-width:1px}.react-flow__resize-control.line.bottom{border-bottom-width:1px;top:100%}.react-flow__edge-textbg{fill:var(--xy-edge-label-background-color, var(--xy-edge-label-background-color-default))}.react-flow__edge-text{fill:var(--xy-edge-label-color, var(--xy-edge-label-color-default))}