UNPKG

@lumina-study/graph

Version:

Graph library for Lumina Study

1 lines 34.4 kB
{"version":3,"file":"index.cjs","sources":["../src/constants/NODE_WIDTH_PX.ts","../src/constants/NODE_HEIGHT_PX.ts","../src/constants/MOBILE_NODE_WIDTH_PX.ts","../src/constants/MOBILE_NODE_HEIGHT_PX.ts","../src/utils/getNodeDimensions.ts","../src/utils/getHandlePositions.ts","../src/utils/getTreeNodeClassName.ts","../src/utils/hasSubModules.ts","../src/utils/handleClick.ts","../src/utils/handleKeyDown.ts","../src/utils/highlightText.tsx","../src/utils/getBlockIcon.ts","../src/utils/autoLayout.ts","../src/components/TreeNode/TreeNodeHandles.tsx","../src/components/TreeNode/CollapseButton.tsx","../src/components/TreeNode/ZoomBadge.tsx","../src/components/TreeNode/TreeNodeHeader.tsx","../src/components/TreeNode/TreeNodeSubmodules.tsx","../src/components/TreeNode/useMobileDetection.ts","../src/components/TreeNode/TreeNode.tsx","../src/components/Tree/Tree.tsx"],"sourcesContent":["/**\n * Node width in pixels - used for calculations and corresponds to Tailwind w-60 class\n */\nexport const NODE_WIDTH_PX = 240;\n","/**\n * Node height in pixels - used for calculations and vertical spacing between nodes\n */\nexport const NODE_HEIGHT_PX = 80;\n","/**\n * Mobile node width in pixels - narrower for mobile devices\n */\nexport const MOBILE_NODE_WIDTH_PX = 180;\n","/**\n * Mobile node height in pixels - taller for mobile devices\n */\nexport const MOBILE_NODE_HEIGHT_PX = 120;\n","import { NODE_WIDTH_PX, NODE_HEIGHT_PX, MOBILE_NODE_WIDTH_PX, MOBILE_NODE_HEIGHT_PX } from '../constants';\n\n/**\n * Get node dimensions based on device type\n */\nexport function getNodeDimensions(isMobile: boolean): { width: number; height: number } {\n return {\n width: isMobile ? MOBILE_NODE_WIDTH_PX : NODE_WIDTH_PX,\n height: isMobile ? MOBILE_NODE_HEIGHT_PX : NODE_HEIGHT_PX,\n };\n}\n","import { Position } from '@xyflow/react';\nimport type { Direction } from '../types';\n\n/**\n * Get handle positions based on layout direction\n */\nexport function getHandlePositions(\n isVertical: boolean,\n dir: Direction\n): { targetPosition: Position; sourcePosition: Position } {\n if (isVertical) {\n // Top to bottom layout\n return {\n targetPosition: Position.Top,\n sourcePosition: Position.Bottom,\n };\n }\n\n // Horizontal layouts (ltr or rtl)\n if (dir === 'rtl') {\n return {\n targetPosition: Position.Right,\n sourcePosition: Position.Left,\n };\n }\n\n // Default ltr\n return {\n targetPosition: Position.Left,\n sourcePosition: Position.Right,\n };\n}\n","import type { TreeNodeData } from '../types';\n\n/**\n * Get CSS class names for a tree node based on its state\n */\nexport function getTreeNodeClassName(data: TreeNodeData): string {\n const classes = ['tree-node'];\n\n if (data.isSelected) {\n classes.push('tree-node--selected');\n }\n\n if (data.isHighlighted) {\n classes.push('tree-node--highlighted');\n }\n\n if (data.disabled) {\n classes.push('tree-node--disabled');\n }\n\n return classes.join(' ');\n}\n","import type { TreeNodeData } from '../types';\n\n/**\n * Check if a node has submodules\n */\nexport function hasSubModules(data: TreeNodeData): boolean {\n return Boolean(data.subModules && data.subModules.length > 0);\n}\n","import type { MouseEvent } from 'react';\n\n/**\n * Handle click events on tree nodes\n */\nexport function handleClick(\n event: MouseEvent<HTMLDivElement>,\n onClick?: () => void\n): void {\n if (!onClick) {\n return;\n }\n\n const target = event.target;\n // eslint-disable-next-line guard-clauses/prefer-guard-at-function-start -- onClick check should be first\n if (!(target instanceof HTMLElement)) {\n return;\n }\n\n // Don't trigger if clicking on interactive elements\n if (\n target.tagName === 'BUTTON' ||\n target.closest('button') ||\n target.tagName === 'A' ||\n target.closest('a')\n ) {\n return;\n }\n\n onClick();\n}\n","import type { KeyboardEvent } from 'react';\n\n/**\n * Handle keyboard events on tree nodes\n */\nexport function handleKeyDown(\n event: KeyboardEvent<HTMLDivElement>,\n onClick?: () => void\n): void {\n if (!onClick) {\n return;\n }\n\n // Trigger on Enter or Space\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault();\n onClick();\n }\n}\n","import type { ReactNode } from 'react';\n\n/**\n * Highlight search term in text\n */\nexport function highlightText(text: string, searchTerm?: string): ReactNode {\n if (!searchTerm || !searchTerm.trim()) {\n return text;\n }\n\n // Escape special regex characters\n const escapedTerm = searchTerm.replace(/[$()*+.?[\\\\\\]^{|}]/g, '\\\\$&');\n // eslint-disable-next-line security/detect-non-literal-regexp -- escapedTerm is sanitized above\n const parts = text.split(new RegExp(`(${escapedTerm})`, 'gi'));\n\n return (\n <>\n {parts.map((part, index) => {\n const isMatch = part.toLowerCase() === searchTerm.toLowerCase();\n return isMatch ? (\n <mark key={index} className=\"bg-yellow-200\">\n {part}\n </mark>\n ) : (\n <span key={index} className=\"\">\n {part}\n </span>\n );\n })}\n </>\n );\n}\n","import type { BlockStyleType } from '../types';\n\n/**\n * Get icon path for block style\n * @param style - The block style type\n * @returns Path to the icon or undefined\n */\nexport function getBlockIcon(style?: BlockStyleType): string | undefined {\n if (!style) {\n return undefined;\n }\n\n // This would typically map to actual icon paths in your application\n // For now, returning undefined as icons are application-specific\n return undefined;\n}\n","import type { Node, Edge } from '@xyflow/react';\nimport type { TreeNodeData } from '../types';\n\nexport type LayoutDirection = 'vertical' | 'horizontal';\n\nexport interface LayoutOptions {\n /**\n * Direction of the tree layout\n * @default 'vertical'\n */\n direction?: LayoutDirection;\n /**\n * Horizontal spacing between nodes\n * @default 300\n */\n horizontalSpacing?: number;\n /**\n * Vertical spacing between nodes\n * @default 150\n */\n verticalSpacing?: number;\n}\n\ninterface TreeNode<T extends Record<string, unknown>> {\n node: Node<T>;\n children: TreeNode<T>[];\n width: number;\n}\n\n/**\n * Calculate positions for tree nodes using a simple hierarchical layout\n *\n * @example\n * ```typescript\n * const nodes = [\n * { id: '1', data: { label: 'Root' } },\n * { id: '2', data: { label: 'Child 1' } },\n * { id: '3', data: { label: 'Child 2' } }\n * ];\n * const edges = [\n * { id: 'e1-2', source: '1', target: '2' },\n * { id: 'e1-3', source: '1', target: '3' }\n * ];\n *\n * const layoutedNodes = autoLayout(nodes, edges);\n * ```\n */\nexport function autoLayout<T extends TreeNodeData = TreeNodeData>(\n nodes: Omit<Node<T>, 'position'>[],\n edges: Edge[],\n options: LayoutOptions = {}\n): Node<T>[] {\n const {\n direction = 'vertical',\n horizontalSpacing = 300,\n verticalSpacing = 150,\n } = options;\n\n if (nodes.length === 0) {\n return [];\n }\n\n // Build adjacency map\n const childrenMap = new Map<string, string[]>();\n const parentMap = new Map<string, string>();\n\n edges.forEach(edge => {\n const children = childrenMap.get(edge.source) || [];\n children.push(edge.target);\n childrenMap.set(edge.source, children);\n parentMap.set(edge.target, edge.source);\n });\n\n // Find root nodes (nodes without parents)\n const roots = nodes.filter(node => !parentMap.has(node.id));\n\n if (roots.length === 0) {\n // No clear root, use first node\n roots.push(nodes[0]);\n }\n\n // Build tree structure\n const nodeMap = new Map(nodes.map(n => [n.id, n]));\n\n function buildTree(nodeId: string): TreeNode<T> {\n const node = nodeMap.get(nodeId)!;\n const children = (childrenMap.get(nodeId) || []).map(buildTree);\n\n // Calculate subtree width\n const width = children.length === 0\n ? 1\n : children.reduce((sum, child) => sum + child.width, 0);\n\n return { node: node as Node<T>, children, width };\n }\n\n const trees = roots.map(root => buildTree(root.id));\n\n // Position nodes\n const positioned = new Map<string, { x: number; y: number }>();\n\n function positionTree(tree: TreeNode<T>, x: number, y: number): void {\n if (direction === 'vertical') {\n positioned.set(tree.node.id, { x, y });\n\n if (tree.children.length > 0) {\n const totalWidth = tree.width;\n const childY = y + verticalSpacing;\n let childX = x - ((totalWidth - 1) * horizontalSpacing) / 2;\n\n tree.children.forEach(child => {\n const childCenterX = childX + (child.width * horizontalSpacing) / 2;\n positionTree(child, childCenterX, childY);\n childX += child.width * horizontalSpacing;\n });\n }\n } else {\n // Horizontal layout\n positioned.set(tree.node.id, { x: y, y: x });\n\n if (tree.children.length > 0) {\n const totalWidth = tree.width;\n const childX = y + horizontalSpacing;\n let childY = x - ((totalWidth - 1) * verticalSpacing) / 2;\n\n tree.children.forEach(child => {\n const childCenterY = childY + (child.width * verticalSpacing) / 2;\n positionTree(child, childCenterY, childX);\n childY += child.width * verticalSpacing;\n });\n }\n }\n }\n\n // Position each tree\n let offsetX = 0;\n trees.forEach(tree => {\n const treeWidth = tree.width * horizontalSpacing;\n positionTree(tree, offsetX + treeWidth / 2, 0);\n offsetX += treeWidth + horizontalSpacing;\n });\n\n // Apply positions to nodes\n return nodes.map(node => ({\n ...node,\n position: positioned.get(node.id) || { x: 0, y: 0 },\n })) as Node<T>[];\n}\n\n/**\n * Helper to create a tree from a simple parent-child structure\n *\n * @example\n * ```typescript\n * const tree = createTreeFromHierarchy([\n * { id: '1', data: { label: 'Root' } },\n * { id: '2', parentId: '1', data: { label: 'Child 1' } },\n * { id: '3', parentId: '1', data: { label: 'Child 2' } }\n * ]);\n * // Returns { nodes, edges }\n * ```\n */\nexport function createTreeFromHierarchy<T extends TreeNodeData>(\n items: Array<{ id: string; parentId?: string; data: T }>\n): { nodes: Node<T>[]; edges: Edge[] } {\n const nodes: Node<T>[] = items.map(item => ({\n id: item.id,\n type: 'treeNode',\n position: { x: 0, y: 0 }, // Will be calculated by autoLayout\n data: item.data,\n }));\n\n const edges: Edge[] = items\n .filter(item => item.parentId)\n .map(item => ({\n id: `e-${item.parentId}-${item.id}`,\n source: item.parentId!,\n target: item.id,\n }));\n\n return { nodes, edges };\n}\n","import { Handle } from '@xyflow/react';\nimport { getHandlePositions } from '../../utils';\nimport type { Direction } from '../../types';\n\ninterface TreeNodeHandlesProps {\n readonly isVertical: boolean;\n readonly dir: Direction;\n}\n\n/** Handles used by React Flow nodes */\nexport const TreeNodeHandles = ({\n isVertical,\n dir,\n}: TreeNodeHandlesProps) => {\n const { targetPosition, sourcePosition } = getHandlePositions(\n isVertical,\n dir\n );\n\n return (\n <>\n <Handle\n id=\"target\"\n type=\"target\"\n position={targetPosition}\n style={{ background: '#3b82f6' }}\n />\n <Handle\n id=\"source\"\n type=\"source\"\n position={sourcePosition}\n style={{ background: '#3b82f6' }}\n />\n </>\n );\n};\n","import type { MouseEvent, KeyboardEvent } from 'react';\n\ninterface CollapseButtonProps {\n readonly isCollapsed: boolean;\n readonly setIsCollapsed: (collapsed: boolean) => void;\n readonly onToggleCollapse: (nodeId: string, collapsed: boolean) => void;\n readonly nodeId: string;\n readonly isMobile: boolean;\n}\n\n/** Button used to toggle node collapse state */\nexport const CollapseButton = ({\n isCollapsed,\n setIsCollapsed,\n onToggleCollapse,\n nodeId,\n isMobile,\n}: CollapseButtonProps) => {\n const handleToggle = (e: MouseEvent<HTMLButtonElement> | KeyboardEvent<HTMLButtonElement>) => {\n e.stopPropagation();\n const newCollapsed = !isCollapsed;\n setIsCollapsed(newCollapsed);\n onToggleCollapse(nodeId, newCollapsed);\n };\n\n const handleKeyDown = (e: KeyboardEvent<HTMLButtonElement>) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n handleToggle(e);\n }\n };\n\n const buttonSize = isMobile ? 'w-8 h-8 text-base' : 'w-4 h-4 text-xs';\n const buttonPadding = isMobile ? 'p-1' : 'p-0.5';\n\n return (\n <button\n id={`tree-node-collapse-button-${nodeId}`}\n onClick={handleToggle}\n onKeyDown={handleKeyDown}\n className={`bg-transparent border-none cursor-pointer ${buttonPadding} rounded-sm text-gray-500 flex items-center justify-center ${buttonSize}`}\n title={isCollapsed ? 'Expand sub-modules' : 'Collapse sub-modules'}\n aria-label={isCollapsed ? 'Expand sub-modules' : 'Collapse sub-modules'}\n type=\"button\"\n >\n {isCollapsed ? '▶' : '▼'}\n </button>\n );\n};\n","/** Badge displayed when a node can be zoomed into */\nexport const ZoomBadge = () => (\n <span\n className=\"zoom-badge\"\n style={{\n fontSize: '0.75rem',\n color: '#2563eb',\n backgroundColor: '#eff6ff',\n padding: '0.25rem 0.5rem',\n borderRadius: '9999px',\n display: 'flex',\n alignItems: 'center',\n gap: '0.25rem',\n fontWeight: 400,\n }}\n >\n <svg\n className=\"zoom-icon\"\n style={{ width: '0.75rem', height: '0.75rem' }}\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n >\n <path\n className=\"zoom-icon-path\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0zM10 7v3m0 0v3m0-3h3m-3 0H7\"\n />\n </svg>\n Zoom in\n </span>\n);\n","import { CollapseButton } from './CollapseButton';\nimport { ZoomBadge } from './ZoomBadge';\nimport { highlightText } from '../../utils';\n\nexport interface TreeNodeHeaderProps {\n readonly hasSubModules: boolean;\n readonly isCollapsed: boolean;\n readonly setIsCollapsed: (collapsed: boolean) => void;\n readonly onToggleCollapse: (nodeId: string, collapsed: boolean) => void;\n readonly nodeId: string;\n readonly label: string;\n readonly searchTerm?: string;\n readonly iconPath?: string;\n readonly canZoom?: boolean;\n readonly isMobile: boolean;\n}\n\n/** Header section for tree nodes including collapse control */\nexport const TreeNodeHeader = ({\n hasSubModules,\n isCollapsed,\n setIsCollapsed,\n onToggleCollapse,\n nodeId,\n label,\n searchTerm,\n iconPath,\n canZoom,\n isMobile,\n}: TreeNodeHeaderProps) => {\n return (\n <div\n id={`tree-node-header-${nodeId}`}\n className={`${isMobile ? 'flex flex-col gap-2' : ''}`}\n style={{ display: isMobile ? 'flex' : 'block' }}\n >\n {hasSubModules ? (\n <div\n id={`tree-node-collapse-wrapper-${nodeId}`}\n className={`${isMobile ? 'flex justify-start' : ''}`}\n style={{ display: isMobile ? 'flex' : 'inline' }}\n >\n <CollapseButton\n isCollapsed={isCollapsed}\n setIsCollapsed={setIsCollapsed}\n onToggleCollapse={onToggleCollapse}\n nodeId={nodeId}\n isMobile={isMobile}\n />\n </div>\n ) : null}\n <div\n id={`tree-node-content-${nodeId}`}\n className={`relative ${isMobile ? 'text-start' : ''}`}\n style={{ lineHeight: '1.25', textAlign: isMobile ? 'start' : 'initial' }}\n >\n {iconPath && (\n <div\n className=\"icon-wrapper\"\n style={{\n width: '3rem',\n marginBottom: isMobile ? '0.5rem' : 0,\n float: isMobile ? 'none' : 'right',\n }}\n >\n <img src={iconPath} alt=\"\" role=\"presentation\" className=\"icon-img\" />\n </div>\n )}\n <span\n className=\"tree-node__label\"\n style={{\n fontWeight: 600,\n display: canZoom ? 'flex' : 'block',\n fontSize: isMobile ? '1.125rem' : '1.25rem',\n textAlign: 'start',\n }}\n >\n {highlightText(label, searchTerm)}\n {canZoom && <ZoomBadge />}\n </span>\n </div>\n </div>\n );\n};\n","import { highlightText } from '../../utils';\n\nexport interface TreeNodeSubmodulesProps {\n readonly subModules?: readonly string[];\n readonly searchTerm?: string;\n}\n\n/** Display submodules as badges */\nexport const TreeNodeSubmodules = ({\n subModules,\n searchTerm,\n}: TreeNodeSubmodulesProps) => {\n if (!subModules || subModules.length === 0) {\n return null;\n }\n\n return (\n <div className=\"tree-node__submodules\">\n {subModules.map((subModule, index) => (\n <span key={index} className=\"tree-node__submodule\">\n {highlightText(subModule, searchTerm)}\n </span>\n ))}\n </div>\n );\n};\n","import { useState, useEffect } from 'react';\n\n/**\n * Hook to detect if the current device is mobile\n */\nexport function useMobileDetection(): boolean {\n const [isMobile, setIsMobile] = useState(false);\n\n useEffect(() => {\n const checkMobile = () => {\n setIsMobile(window.innerWidth < 768);\n };\n checkMobile();\n window.addEventListener('resize', checkMobile);\n return () => window.removeEventListener('resize', checkMobile);\n }, []);\n\n return isMobile;\n}\n","import { type NodeProps } from '@xyflow/react';\nimport { useState, useEffect } from 'react';\nimport { getTreeNodeClassName, getBlockIcon, handleClick, handleKeyDown, hasSubModules, getNodeDimensions } from '../../utils';\nimport type { TreeNodeData } from '../../types';\nimport { TreeNodeHandles } from './TreeNodeHandles';\nimport { TreeNodeHeader } from './TreeNodeHeader';\nimport { TreeNodeSubmodules } from './TreeNodeSubmodules';\nimport { useMobileDetection } from './useMobileDetection';\nimport './TreeNode.css';\n\ntype TreeNodeProps = NodeProps & { data: TreeNodeData };\n\nfunction TreeNodeContainer(props: TreeNodeProps) {\n const { data } = props;\n const isMobile = useMobileDetection();\n const isVertical = data.direction === 'ttb';\n const layoutDirection = data.direction || 'ttb';\n\n const [isCollapsed, setIsCollapsed] = useState(\n data.isCollapsed !== undefined ? data.isCollapsed : false\n );\n\n useEffect(() => {\n if (data.isCollapsed !== undefined) {\n setIsCollapsed(data.isCollapsed);\n }\n }, [data.isCollapsed]);\n\n const hasSubModulesFlag = hasSubModules(data);\n const iconPath = getBlockIcon(data.style);\n\n // Set text direction based on language, but always align text to start\n const textDirection = data.language === 'he' ? 'rtl' : 'ltr';\n const textAlign = 'start';\n\n // Get responsive dimensions based on device type\n const { width: nodeWidth, height: nodeHeight } = getNodeDimensions(isMobile);\n\n const handleToggleCollapse = (nodeId: string, collapsed: boolean) => {\n if (data.onToggleCollapse) {\n data.onToggleCollapse(nodeId, collapsed);\n }\n };\n\n return (\n <div\n id={`tree-node-${props.id}`}\n onClick={(e) => handleClick(e, data.disabled ? undefined : data.onClick)}\n onKeyDown={(e) =>\n handleKeyDown(e, data.disabled ? undefined : data.onClick)\n }\n className={getTreeNodeClassName(data)}\n style={{\n direction: textDirection,\n textAlign,\n width: nodeWidth,\n height: nodeHeight,\n }}\n role=\"treeitem\"\n aria-expanded={hasSubModulesFlag ? !isCollapsed : undefined}\n aria-selected={data.isSelected}\n aria-disabled={data.disabled}\n tabIndex={data.isSelected ? 0 : -1}\n >\n <TreeNodeHandles isVertical={isVertical} dir={layoutDirection} />\n <TreeNodeHeader\n hasSubModules={hasSubModulesFlag}\n isCollapsed={isCollapsed}\n setIsCollapsed={setIsCollapsed}\n onToggleCollapse={handleToggleCollapse}\n nodeId={props.id}\n label={data.label}\n searchTerm={data.searchTerm}\n iconPath={iconPath}\n canZoom={data.canZoom}\n isMobile={isMobile}\n />\n {!isCollapsed && (\n <TreeNodeSubmodules\n subModules={data.subModules}\n searchTerm={data.searchTerm}\n />\n )}\n </div>\n );\n}\n\nexport const TreeNode = TreeNodeContainer;\n","import { ReactFlow, Background, Controls } from '@xyflow/react';\nimport type { Node, Edge, ReactFlowProps } from '@xyflow/react';\nimport '@xyflow/react/dist/style.css';\nimport { TreeNode } from '../TreeNode/TreeNode';\nimport type { TreeNodeData, Direction } from '../../types';\nimport { autoLayout, type LayoutOptions } from '../../utils/autoLayout';\n\nexport interface TreeProps extends Partial<Omit<ReactFlowProps, 'nodes' | 'edges' | 'nodeTypes' | 'width' | 'height' | 'fitView'>> {\n /**\n * Array of tree nodes to render.\n * If using auto-layout, positions can be omitted or set to {x:0, y:0}\n */\n nodes: Node<TreeNodeData>[] | Omit<Node<TreeNodeData>, 'position'>[];\n /**\n * Array of edges connecting the nodes\n */\n edges?: Edge[];\n /**\n * Width of the tree container\n * @default '100%'\n */\n width?: string | number;\n /**\n * Height of the tree container\n * @default '600px'\n */\n height?: string | number;\n /**\n * Whether to show the background grid\n * @default true\n */\n showBackground?: boolean;\n /**\n * Whether to show the controls (zoom, fit view, etc.)\n * @default true\n */\n showControls?: boolean;\n /**\n * Whether to fit the view to show all nodes on mount\n * @default true\n */\n fitView?: boolean;\n /**\n * Custom node types (TreeNode is registered by default)\n */\n nodeTypes?: ReactFlowProps['nodeTypes'];\n /**\n * Enable auto-layout. When true, node positions are calculated automatically.\n * @default false\n */\n autoLayout?: boolean;\n /**\n * Layout options for auto-layout\n */\n layoutOptions?: LayoutOptions;\n /**\n * Direction for all nodes in the tree (for handle positioning)\n * @default 'ttb'\n */\n direction?: Direction;\n}\n\n/**\n * Tree component that wraps ReactFlow with TreeNode integration\n *\n * @example\n * ```tsx\n * import { Tree } from '@lumina-study/graph';\n *\n * const nodes = [\n * {\n * id: '1',\n * type: 'treeNode',\n * position: { x: 0, y: 0 },\n * data: { label: 'Node 1', direction: 'ttb' }\n * }\n * ];\n *\n * <Tree nodes={nodes} />\n * ```\n */\nexport function Tree({\n nodes,\n edges = [],\n width = '100%',\n height = '600px',\n showBackground = true,\n showControls = true,\n fitView = true,\n nodeTypes: customNodeTypes,\n autoLayout: enableAutoLayout = false,\n layoutOptions,\n direction = 'ttb',\n ...reactFlowProps\n}: TreeProps) {\n // Default node types with TreeNode registered\n const nodeTypes = {\n treeNode: TreeNode,\n ...customNodeTypes,\n };\n\n // Apply auto-layout if enabled\n let processedNodes = enableAutoLayout\n ? autoLayout(nodes as Omit<Node<TreeNodeData>, 'position'>[], edges, layoutOptions)\n : (nodes as Node<TreeNodeData>[]);\n\n // Apply tree-level direction to all nodes (if not already set on individual nodes)\n processedNodes = processedNodes.map(node => ({\n ...node,\n data: {\n ...node.data,\n direction: node.data.direction || direction,\n },\n }));\n\n const containerStyle = {\n width: typeof width === 'number' ? `${width}px` : width,\n height: typeof height === 'number' ? `${height}px` : height,\n };\n\n return (\n <div style={containerStyle}>\n <ReactFlow\n nodes={processedNodes}\n edges={edges}\n nodeTypes={nodeTypes}\n fitView={fitView}\n {...reactFlowProps}\n >\n {showBackground && <Background />}\n {showControls && <Controls />}\n </ReactFlow>\n </div>\n );\n}\n"],"names":["Position","jsx","Fragment","jsxs","Handle","handleKeyDown","hasSubModules","useState","useEffect","ReactFlow","Background","Controls"],"mappings":"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"}