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.
- package/dist/parser/index.d.ts +4 -0
- package/dist/parser/index.d.ts.map +1 -0
- package/dist/parser/index.js +65 -0
- package/dist/parser/parse-file.d.ts +8 -0
- package/dist/parser/parse-file.d.ts.map +1 -0
- package/dist/parser/parse-file.js +251 -0
- package/dist/parser/types.d.ts +23 -0
- package/dist/parser/types.d.ts.map +1 -0
- package/dist/parser/types.js +1 -0
- package/package.json +2 -2
- package/ui/dist/assets/index-BZV40eAE.css +1 -0
- package/ui/dist/assets/index-VkAtFIbS.js +62 -0
- package/ui/dist/index.html +17 -0
|
@@ -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.
|
|
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
|
-
"
|
|
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))}
|