entangle-ui 0.10.0 → 0.11.0
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/CHANGELOG.md +72 -0
- package/README.md +7 -5
- package/dist/esm/assets/src/components/controls/Select/{Select.css.ts.vanilla-oZnFUorL.css → Select.css.ts.vanilla-Cpu2BvWL.css} +3 -0
- package/dist/esm/assets/src/components/editor/PropertyInspector/{PropertyPanel.css.ts.vanilla-RYv_XgOu.css → PropertyPanel.css.ts.vanilla-Dpr_k5I3.css} +4 -1
- package/dist/esm/assets/src/components/editor/PropertyInspector/{PropertyRow.css.ts.vanilla-C3YZnnHb.css → PropertyRow.css.ts.vanilla-pq1Uhd2a.css} +15 -8
- package/dist/esm/assets/src/components/editor/PropertyInspector/{PropertySection.css.ts.vanilla-DK12opZc.css → PropertySection.css.ts.vanilla-6MWWAqyd.css} +50 -32
- package/dist/esm/components/Icons/AddIcon.js.map +1 -1
- package/dist/esm/components/Icons/AiChatIcon.js.map +1 -1
- package/dist/esm/components/Icons/AiSparklesIcon.js.map +1 -1
- package/dist/esm/components/Icons/ArchiveIcon.js.map +1 -1
- package/dist/esm/components/Icons/ArrowDownIcon.js.map +1 -1
- package/dist/esm/components/Icons/ArrowLeftIcon.js.map +1 -1
- package/dist/esm/components/Icons/ArrowRightIcon.js.map +1 -1
- package/dist/esm/components/Icons/ArrowUpIcon.js.map +1 -1
- package/dist/esm/components/Icons/BookmarkIcon.js.map +1 -1
- package/dist/esm/components/Icons/BugIcon.js.map +1 -1
- package/dist/esm/components/Icons/BuildingIcon.js.map +1 -1
- package/dist/esm/components/Icons/CalendarIcon.js.map +1 -1
- package/dist/esm/components/Icons/CameraIcon.js +31 -0
- package/dist/esm/components/Icons/CameraIcon.js.map +1 -0
- package/dist/esm/components/Icons/CheckIcon.js.map +1 -1
- package/dist/esm/components/Icons/ChevronDownIcon.js.map +1 -1
- package/dist/esm/components/Icons/ChevronLeftIcon.js.map +1 -1
- package/dist/esm/components/Icons/ChevronRightIcon.js.map +1 -1
- package/dist/esm/components/Icons/ChevronUpIcon.js.map +1 -1
- package/dist/esm/components/Icons/CircleIcon.js.map +1 -1
- package/dist/esm/components/Icons/ClockIcon.js.map +1 -1
- package/dist/esm/components/Icons/CloseIcon.js.map +1 -1
- package/dist/esm/components/Icons/CloudUploadIcon.js.map +1 -1
- package/dist/esm/components/Icons/CodeIcon.js.map +1 -1
- package/dist/esm/components/Icons/CopyIcon.js.map +1 -1
- package/dist/esm/components/Icons/CubeIcon.js +31 -0
- package/dist/esm/components/Icons/CubeIcon.js.map +1 -0
- package/dist/esm/components/Icons/CutIcon.js.map +1 -1
- package/dist/esm/components/Icons/DotsHorizontalIcon.js.map +1 -1
- package/dist/esm/components/Icons/DotsVerticalIcon.js.map +1 -1
- package/dist/esm/components/Icons/DownloadIcon.js.map +1 -1
- package/dist/esm/components/Icons/EditIcon.js.map +1 -1
- package/dist/esm/components/Icons/ErrorIcon.js.map +1 -1
- package/dist/esm/components/Icons/ExternalLinkIcon.js.map +1 -1
- package/dist/esm/components/Icons/EyeDropperIcon.js.map +1 -1
- package/dist/esm/components/Icons/EyeIcon.js.map +1 -1
- package/dist/esm/components/Icons/FileTextIcon.js.map +1 -1
- package/dist/esm/components/Icons/FilterIcon.js.map +1 -1
- package/dist/esm/components/Icons/FirstIcon.js.map +1 -1
- package/dist/esm/components/Icons/FolderCogIcon.js.map +1 -1
- package/dist/esm/components/Icons/FolderIcon.js.map +1 -1
- package/dist/esm/components/Icons/FolderOpenIcon.js.map +1 -1
- package/dist/esm/components/Icons/FullscreenIcon.js.map +1 -1
- package/dist/esm/components/Icons/GitBranchIcon.js.map +1 -1
- package/dist/esm/components/Icons/GridIcon.js.map +1 -1
- package/dist/esm/components/Icons/HeartIcon.js.map +1 -1
- package/dist/esm/components/Icons/HelpIcon.js.map +1 -1
- package/dist/esm/components/Icons/HomeIcon.js.map +1 -1
- package/dist/esm/components/Icons/ImageIcon.js.map +1 -1
- package/dist/esm/components/Icons/InfoIcon.js.map +1 -1
- package/dist/esm/components/Icons/LastIcon.js.map +1 -1
- package/dist/esm/components/Icons/LinkIcon.js.map +1 -1
- package/dist/esm/components/Icons/ListIcon.js.map +1 -1
- package/dist/esm/components/Icons/LockIcon.js.map +1 -1
- package/dist/esm/components/Icons/MaximizeIcon.js.map +1 -1
- package/dist/esm/components/Icons/MenuIcon.js.map +1 -1
- package/dist/esm/components/Icons/MinimizeIcon.js.map +1 -1
- package/dist/esm/components/Icons/MinusIcon.js.map +1 -1
- package/dist/esm/components/Icons/OrbitIcon.js +31 -0
- package/dist/esm/components/Icons/OrbitIcon.js.map +1 -0
- package/dist/esm/components/Icons/PasteIcon.js.map +1 -1
- package/dist/esm/components/Icons/PauseIcon.js.map +1 -1
- package/dist/esm/components/Icons/PinIcon.js.map +1 -1
- package/dist/esm/components/Icons/PlayIcon.js.map +1 -1
- package/dist/esm/components/Icons/RedoIcon.js.map +1 -1
- package/dist/esm/components/Icons/RefreshIcon.js.map +1 -1
- package/dist/esm/components/Icons/RobotIcon.js.map +1 -1
- package/dist/esm/components/Icons/RotateIcon.js +32 -0
- package/dist/esm/components/Icons/RotateIcon.js.map +1 -0
- package/dist/esm/components/Icons/SaveIcon.js.map +1 -1
- package/dist/esm/components/Icons/SearchIcon.js.map +1 -1
- package/dist/esm/components/Icons/SendIcon.js.map +1 -1
- package/dist/esm/components/Icons/SettingsIcon.js.map +1 -1
- package/dist/esm/components/Icons/SortIcon.js.map +1 -1
- package/dist/esm/components/Icons/SpinIcon.js +32 -0
- package/dist/esm/components/Icons/SpinIcon.js.map +1 -0
- package/dist/esm/components/Icons/StarIcon.js.map +1 -1
- package/dist/esm/components/Icons/StopIcon.js.map +1 -1
- package/dist/esm/components/Icons/SuccessIcon.js.map +1 -1
- package/dist/esm/components/Icons/TagIcon.js.map +1 -1
- package/dist/esm/components/Icons/TangentAlignedIcon.js.map +1 -1
- package/dist/esm/components/Icons/TangentAutoIcon.js.map +1 -1
- package/dist/esm/components/Icons/TangentFreeIcon.js.map +1 -1
- package/dist/esm/components/Icons/TangentLinearIcon.js.map +1 -1
- package/dist/esm/components/Icons/TangentMirroredIcon.js.map +1 -1
- package/dist/esm/components/Icons/TangentStepIcon.js.map +1 -1
- package/dist/esm/components/Icons/TerminalIcon.js.map +1 -1
- package/dist/esm/components/Icons/TrashIcon.js.map +1 -1
- package/dist/esm/components/Icons/UndoIcon.js.map +1 -1
- package/dist/esm/components/Icons/UnlinkIcon.js.map +1 -1
- package/dist/esm/components/Icons/UnlockIcon.js.map +1 -1
- package/dist/esm/components/Icons/UploadIcon.js.map +1 -1
- package/dist/esm/components/Icons/UserIcon.js.map +1 -1
- package/dist/esm/components/Icons/UsersIcon.js.map +1 -1
- package/dist/esm/components/Icons/WarningIcon.js.map +1 -1
- package/dist/esm/components/Icons/ZoomFitIcon.js +32 -0
- package/dist/esm/components/Icons/ZoomFitIcon.js.map +1 -0
- package/dist/esm/components/Icons/ZoomInIcon.js.map +1 -1
- package/dist/esm/components/Icons/ZoomOutIcon.js.map +1 -1
- package/dist/esm/components/controls/CartesianPicker/CartesianPicker.js.map +1 -1
- package/dist/esm/components/controls/CartesianPicker/useCartesianInteraction.js.map +1 -1
- package/dist/esm/components/controls/CartesianPicker/useCartesianRenderer.js.map +1 -1
- package/dist/esm/components/controls/ColorPicker/AlphaSlider.js.map +1 -1
- package/dist/esm/components/controls/ColorPicker/ColorArea.js.map +1 -1
- package/dist/esm/components/controls/ColorPicker/ColorInputs.js.map +1 -1
- package/dist/esm/components/controls/ColorPicker/ColorPalette.js.map +1 -1
- package/dist/esm/components/controls/ColorPicker/ColorPicker.js.map +1 -1
- package/dist/esm/components/controls/ColorPicker/ColorPresets.js.map +1 -1
- package/dist/esm/components/controls/ColorPicker/ColorSwatch.js.map +1 -1
- package/dist/esm/components/controls/ColorPicker/EyeDropper.js.map +1 -1
- package/dist/esm/components/controls/ColorPicker/HueSlider.js.map +1 -1
- package/dist/esm/components/controls/ColorPicker/colorUtils.js.map +1 -1
- package/dist/esm/components/controls/ColorPicker/palettes/material.js.map +1 -1
- package/dist/esm/components/controls/ColorPicker/palettes/professional.js.map +1 -1
- package/dist/esm/components/controls/ColorPicker/palettes/tailwind.js.map +1 -1
- package/dist/esm/components/controls/ColorPicker/useColor.js.map +1 -1
- package/dist/esm/components/controls/Combobox/Combobox.js.map +1 -1
- package/dist/esm/components/controls/CurveEditor/CurveCanvas.js.map +1 -1
- package/dist/esm/components/controls/CurveEditor/CurveEditor.js.map +1 -1
- package/dist/esm/components/controls/CurveEditor/CurveToolbar.js.map +1 -1
- package/dist/esm/components/controls/CurveEditor/curvePresets.js.map +1 -1
- package/dist/esm/components/controls/CurveEditor/curveUtils.js.map +1 -1
- package/dist/esm/components/controls/CurveEditor/useCurveInteraction.js.map +1 -1
- package/dist/esm/components/controls/CurveEditor/useCurveRenderer.js.map +1 -1
- package/dist/esm/components/controls/FileTree/FileTree.js +7 -1
- package/dist/esm/components/controls/FileTree/FileTree.js.map +1 -1
- package/dist/esm/components/controls/FileTree/fileTreeIcons.js.map +1 -1
- package/dist/esm/components/controls/FileTree/fileTreeLabels.js.map +1 -1
- package/dist/esm/components/controls/FileTree/useFileTreeDrop.js +1 -1
- package/dist/esm/components/controls/FileTree/useFileTreeDrop.js.map +1 -1
- package/dist/esm/components/controls/FileUploader/FileUploader.js.map +1 -1
- package/dist/esm/components/controls/MultiSelect/MultiSelect.js.map +1 -1
- package/dist/esm/components/controls/NumberInput/NumberInput.js.map +1 -1
- package/dist/esm/components/controls/NumberInput/useNumberInput.js.map +1 -1
- package/dist/esm/components/controls/Select/Select.css.js +1 -1
- package/dist/esm/components/controls/Select/Select.js +15 -7
- package/dist/esm/components/controls/Select/Select.js.map +1 -1
- package/dist/esm/components/controls/Slider/Slider.js.map +1 -1
- package/dist/esm/components/controls/TagInput/TagInput.js.map +1 -1
- package/dist/esm/components/controls/TreeView/TreeNode.js +6 -0
- package/dist/esm/components/controls/TreeView/TreeNode.js.map +1 -1
- package/dist/esm/components/controls/TreeView/TreeView.js.map +1 -1
- package/dist/esm/components/controls/TreeView/useTreeState.js.map +1 -1
- package/dist/esm/components/controls/VectorInput/VectorInput.js +6 -0
- package/dist/esm/components/controls/VectorInput/VectorInput.js.map +1 -1
- package/dist/esm/components/data/DataTable/DataTable.js.map +1 -1
- package/dist/esm/components/editor/AssetBrowser/AssetBrowser.js.map +1 -1
- package/dist/esm/components/editor/AssetBrowser/AssetBrowserAnnouncementLive.js.map +1 -1
- package/dist/esm/components/editor/AssetBrowser/AssetBrowserBreadcrumbs.js.map +1 -1
- package/dist/esm/components/editor/AssetBrowser/AssetBrowserContent.js +6 -0
- package/dist/esm/components/editor/AssetBrowser/AssetBrowserContent.js.map +1 -1
- package/dist/esm/components/editor/AssetBrowser/AssetBrowserContext.js.map +1 -1
- package/dist/esm/components/editor/AssetBrowser/AssetBrowserContextMenu.js.map +1 -1
- package/dist/esm/components/editor/AssetBrowser/AssetBrowserDefaultMenus.js +6 -0
- package/dist/esm/components/editor/AssetBrowser/AssetBrowserDefaultMenus.js.map +1 -1
- package/dist/esm/components/editor/AssetBrowser/AssetBrowserGrid.js +1 -1
- package/dist/esm/components/editor/AssetBrowser/AssetBrowserGrid.js.map +1 -1
- package/dist/esm/components/editor/AssetBrowser/AssetBrowserGridItem.js +6 -0
- package/dist/esm/components/editor/AssetBrowser/AssetBrowserGridItem.js.map +1 -1
- package/dist/esm/components/editor/AssetBrowser/AssetBrowserList.js +6 -0
- package/dist/esm/components/editor/AssetBrowser/AssetBrowserList.js.map +1 -1
- package/dist/esm/components/editor/AssetBrowser/AssetBrowserRenameField.js.map +1 -1
- package/dist/esm/components/editor/AssetBrowser/AssetBrowserSidebar.js.map +1 -1
- package/dist/esm/components/editor/AssetBrowser/AssetBrowserStatusBar.js.map +1 -1
- package/dist/esm/components/editor/AssetBrowser/AssetBrowserStore.js.map +1 -1
- package/dist/esm/components/editor/AssetBrowser/AssetBrowserSurface.js +6 -0
- package/dist/esm/components/editor/AssetBrowser/AssetBrowserSurface.js.map +1 -1
- package/dist/esm/components/editor/AssetBrowser/AssetBrowserToolbar.js +6 -0
- package/dist/esm/components/editor/AssetBrowser/AssetBrowserToolbar.js.map +1 -1
- package/dist/esm/components/editor/AssetBrowser/assetBrowserFilter.js.map +1 -1
- package/dist/esm/components/editor/AssetBrowser/assetBrowserFormat.js.map +1 -1
- package/dist/esm/components/editor/AssetBrowser/assetBrowserGeometry.js.map +1 -1
- package/dist/esm/components/editor/AssetBrowser/assetBrowserKeyboard.js.map +1 -1
- package/dist/esm/components/editor/AssetBrowser/assetBrowserLabels.js.map +1 -1
- package/dist/esm/components/editor/AssetBrowser/slots.js.map +1 -1
- package/dist/esm/components/editor/AssetBrowser/useAssetBrowserAnnouncement.js.map +1 -1
- package/dist/esm/components/editor/AssetBrowser/useAssetBrowserHandle.js +1 -1
- package/dist/esm/components/editor/AssetBrowser/useAssetBrowserHandle.js.map +1 -1
- package/dist/esm/components/editor/AssetBrowser/useAssetBrowserViewState.js +1 -1
- package/dist/esm/components/editor/AssetBrowser/useAssetBrowserViewState.js.map +1 -1
- package/dist/esm/components/editor/AssetBrowser/useAssetDnd.js +1 -1
- package/dist/esm/components/editor/AssetBrowser/useAssetDnd.js.map +1 -1
- package/dist/esm/components/editor/AssetBrowser/useAssetGridKeyboardNav.js.map +1 -1
- package/dist/esm/components/editor/AssetBrowser/useAssetGridVirtualizer.js +1 -1
- package/dist/esm/components/editor/AssetBrowser/useAssetGridVirtualizer.js.map +1 -1
- package/dist/esm/components/editor/AssetBrowser/useAssetMarqueeGesture.js +1 -1
- package/dist/esm/components/editor/AssetBrowser/useAssetMarqueeGesture.js.map +1 -1
- package/dist/esm/components/editor/AssetBrowser/useAssetMutationKeyboard.js +1 -1
- package/dist/esm/components/editor/AssetBrowser/useAssetMutationKeyboard.js.map +1 -1
- package/dist/esm/components/editor/AssetBrowser/useAssetMutations.js +1 -1
- package/dist/esm/components/editor/AssetBrowser/useAssetMutations.js.map +1 -1
- package/dist/esm/components/editor/AssetBrowser/useAssetNavigation.js +1 -1
- package/dist/esm/components/editor/AssetBrowser/useAssetNavigation.js.map +1 -1
- package/dist/esm/components/editor/AssetBrowser/useAssetSelectionController.js +1 -1
- package/dist/esm/components/editor/AssetBrowser/useAssetSelectionController.js.map +1 -1
- package/dist/esm/components/editor/ChatPanel/ChatActionBar.js.map +1 -1
- package/dist/esm/components/editor/ChatPanel/ChatAttachment.js.map +1 -1
- package/dist/esm/components/editor/ChatPanel/ChatBubble.js.map +1 -1
- package/dist/esm/components/editor/ChatPanel/ChatCodeBlock.js +6 -0
- package/dist/esm/components/editor/ChatPanel/ChatCodeBlock.js.map +1 -1
- package/dist/esm/components/editor/ChatPanel/ChatContextChip.js.map +1 -1
- package/dist/esm/components/editor/ChatPanel/ChatEmptyState.js.map +1 -1
- package/dist/esm/components/editor/ChatPanel/ChatIcons.js.map +1 -1
- package/dist/esm/components/editor/ChatPanel/ChatInput.js +6 -0
- package/dist/esm/components/editor/ChatPanel/ChatInput.js.map +1 -1
- package/dist/esm/components/editor/ChatPanel/ChatInputToolbar.js.map +1 -1
- package/dist/esm/components/editor/ChatPanel/ChatMarkdownRenderer.js.map +1 -1
- package/dist/esm/components/editor/ChatPanel/ChatMessage.js +6 -0
- package/dist/esm/components/editor/ChatPanel/ChatMessage.js.map +1 -1
- package/dist/esm/components/editor/ChatPanel/ChatMessageList.js.map +1 -1
- package/dist/esm/components/editor/ChatPanel/ChatPanel.js.map +1 -1
- package/dist/esm/components/editor/ChatPanel/ChatToolCall.js.map +1 -1
- package/dist/esm/components/editor/ChatPanel/ChatTypingIndicator.js.map +1 -1
- package/dist/esm/components/editor/ChatPanel/useChatInput.js.map +1 -1
- package/dist/esm/components/editor/ChatPanel/useChatMessages.js.map +1 -1
- package/dist/esm/components/editor/ChatPanel/useChatScroll.js.map +1 -1
- package/dist/esm/components/editor/Minimap/Minimap.js +1 -1
- package/dist/esm/components/editor/Minimap/Minimap.js.map +1 -1
- package/dist/esm/components/editor/Minimap/Minimap.types.js.map +1 -1
- package/dist/esm/components/editor/Minimap/MinimapContext.js.map +1 -1
- package/dist/esm/components/editor/Minimap/MinimapCorner.js.map +1 -1
- package/dist/esm/components/editor/Minimap/MinimapFooter.js.map +1 -1
- package/dist/esm/components/editor/Minimap/MinimapStore.js.map +1 -1
- package/dist/esm/components/editor/Minimap/MinimapTitle.js.map +1 -1
- package/dist/esm/components/editor/Minimap/ViewportMinimap.js +1 -1
- package/dist/esm/components/editor/Minimap/ViewportMinimap.js.map +1 -1
- package/dist/esm/components/editor/Minimap/computeBoundsFromItems.js.map +1 -1
- package/dist/esm/components/editor/Minimap/index.js.map +1 -1
- package/dist/esm/components/editor/Minimap/minimapCoords.js.map +1 -1
- package/dist/esm/components/editor/Minimap/minimapDrawing.js.map +1 -1
- package/dist/esm/components/editor/Minimap/minimapHitTest.js.map +1 -1
- package/dist/esm/components/editor/Minimap/useMinimapGestures.js +1 -1
- package/dist/esm/components/editor/Minimap/useMinimapGestures.js.map +1 -1
- package/dist/esm/components/editor/NodeGraph/NodeGraph.js +1 -1
- package/dist/esm/components/editor/NodeGraph/NodeGraph.js.map +1 -1
- package/dist/esm/components/editor/NodeGraph/NodeGraph.types.js.map +1 -1
- package/dist/esm/components/editor/NodeGraph/NodeGraphContext.js.map +1 -1
- package/dist/esm/components/editor/NodeGraph/NodeGraphEdgeLabels.js.map +1 -1
- package/dist/esm/components/editor/NodeGraph/NodeGraphGroup.js.map +1 -1
- package/dist/esm/components/editor/NodeGraph/NodeGraphMinimap.js.map +1 -1
- package/dist/esm/components/editor/NodeGraph/NodeGraphNode.js +1 -1
- package/dist/esm/components/editor/NodeGraph/NodeGraphNode.js.map +1 -1
- package/dist/esm/components/editor/NodeGraph/NodeGraphNodeContext.js.map +1 -1
- package/dist/esm/components/editor/NodeGraph/NodeGraphNodeParts.js.map +1 -1
- package/dist/esm/components/editor/NodeGraph/NodeGraphNodeSection.js +1 -1
- package/dist/esm/components/editor/NodeGraph/NodeGraphNodeSection.js.map +1 -1
- package/dist/esm/components/editor/NodeGraph/NodeGraphNodeSectionContext.js.map +1 -1
- package/dist/esm/components/editor/NodeGraph/NodeGraphPin.js.map +1 -1
- package/dist/esm/components/editor/NodeGraph/NodeGraphPort.js +1 -1
- package/dist/esm/components/editor/NodeGraph/NodeGraphPort.js.map +1 -1
- package/dist/esm/components/editor/NodeGraph/NodeGraphPortVisual.js.map +1 -1
- package/dist/esm/components/editor/NodeGraph/NodeGraphSlots.js.map +1 -1
- package/dist/esm/components/editor/NodeGraph/NodeGraphSpawnPalette.js.map +1 -1
- package/dist/esm/components/editor/NodeGraph/NodeGraphStore.js.map +1 -1
- package/dist/esm/components/editor/NodeGraph/NodeGraphToolbar.js.map +1 -1
- package/dist/esm/components/editor/NodeGraph/createTypeMatchValidator.js.map +1 -1
- package/dist/esm/components/editor/NodeGraph/nodeGraphActions.js.map +1 -1
- package/dist/esm/components/editor/NodeGraph/nodeGraphDrawing.js.map +1 -1
- package/dist/esm/components/editor/NodeGraph/nodeGraphIds.js.map +1 -1
- package/dist/esm/components/editor/NodeGraph/nodeGraphMath.js.map +1 -1
- package/dist/esm/components/editor/NodeGraph/sortSlots.js.map +1 -1
- package/dist/esm/components/editor/NodeGraph/useDragGesture.js.map +1 -1
- package/dist/esm/components/editor/NodeGraph/useNodeGraph.js +1 -1
- package/dist/esm/components/editor/NodeGraph/useNodeGraph.js.map +1 -1
- package/dist/esm/components/editor/NodeGraph/useNodeGraphConnection.js +1 -1
- package/dist/esm/components/editor/NodeGraph/useNodeGraphConnection.js.map +1 -1
- package/dist/esm/components/editor/NodeGraph/useNodeGraphEdgeInteraction.js +1 -1
- package/dist/esm/components/editor/NodeGraph/useNodeGraphEdgeInteraction.js.map +1 -1
- package/dist/esm/components/editor/NodeGraph/useNodeGraphGroupDrag.js +1 -1
- package/dist/esm/components/editor/NodeGraph/useNodeGraphGroupDrag.js.map +1 -1
- package/dist/esm/components/editor/NodeGraph/useNodeGraphKeyboard.js +1 -1
- package/dist/esm/components/editor/NodeGraph/useNodeGraphKeyboard.js.map +1 -1
- package/dist/esm/components/editor/NodeGraph/useNodeGraphNodeDrag.js +1 -1
- package/dist/esm/components/editor/NodeGraph/useNodeGraphNodeDrag.js.map +1 -1
- package/dist/esm/components/editor/NodeGraph/useStoreSlice.js.map +1 -1
- package/dist/esm/components/editor/PropertyInspector/PropertyGroup.js.map +1 -1
- package/dist/esm/components/editor/PropertyInspector/PropertyPanel.css.js +4 -3
- package/dist/esm/components/editor/PropertyInspector/PropertyPanel.css.js.map +1 -1
- package/dist/esm/components/editor/PropertyInspector/PropertyPanel.js +5 -4
- package/dist/esm/components/editor/PropertyInspector/PropertyPanel.js.map +1 -1
- package/dist/esm/components/editor/PropertyInspector/PropertyRow.css.js +7 -6
- package/dist/esm/components/editor/PropertyInspector/PropertyRow.css.js.map +1 -1
- package/dist/esm/components/editor/PropertyInspector/PropertyRow.js +18 -3
- package/dist/esm/components/editor/PropertyInspector/PropertyRow.js.map +1 -1
- package/dist/esm/components/editor/PropertyInspector/PropertySection.css.js +11 -10
- package/dist/esm/components/editor/PropertyInspector/PropertySection.css.js.map +1 -1
- package/dist/esm/components/editor/PropertyInspector/PropertySection.js +29 -21
- package/dist/esm/components/editor/PropertyInspector/PropertySection.js.map +1 -1
- package/dist/esm/components/editor/PropertyInspector/usePropertyUndo.js.map +1 -1
- package/dist/esm/components/editor/Timeline/Timeline.js +1 -1
- package/dist/esm/components/editor/Timeline/Timeline.js.map +1 -1
- package/dist/esm/components/editor/Timeline/Timeline.types.js.map +1 -1
- package/dist/esm/components/editor/Timeline/TimelineContext.js.map +1 -1
- package/dist/esm/components/editor/Timeline/TimelineFooter.js.map +1 -1
- package/dist/esm/components/editor/Timeline/TimelineMinimap.js +1 -1
- package/dist/esm/components/editor/Timeline/TimelineMinimap.js.map +1 -1
- package/dist/esm/components/editor/Timeline/TimelineStore.js.map +1 -1
- package/dist/esm/components/editor/Timeline/TimelineToolbar.js.map +1 -1
- package/dist/esm/components/editor/Timeline/TimelineTrackHeaders.js +1 -1
- package/dist/esm/components/editor/Timeline/TimelineTrackHeaders.js.map +1 -1
- package/dist/esm/components/editor/Timeline/TimelineTrackScale.js.map +1 -1
- package/dist/esm/components/editor/Timeline/index.js.map +1 -1
- package/dist/esm/components/editor/Timeline/timelineCoords.js.map +1 -1
- package/dist/esm/components/editor/Timeline/timelineDrawing.js.map +1 -1
- package/dist/esm/components/editor/Timeline/timelineEdits.js.map +1 -1
- package/dist/esm/components/editor/Timeline/timelineHitTest.js.map +1 -1
- package/dist/esm/components/editor/Timeline/timelineLayout.js.map +1 -1
- package/dist/esm/components/editor/Timeline/timelineSelection.js.map +1 -1
- package/dist/esm/components/editor/Timeline/useTimelineDraw.js +1 -1
- package/dist/esm/components/editor/Timeline/useTimelineDraw.js.map +1 -1
- package/dist/esm/components/editor/Timeline/useTimelineGestures.js +1 -1
- package/dist/esm/components/editor/Timeline/useTimelineGestures.js.map +1 -1
- package/dist/esm/components/editor/Timeline/useTimelinePlayback.js.map +1 -1
- package/dist/esm/components/editor/TransformControl/TransformControl.js.map +1 -1
- package/dist/esm/components/editor/ViewportGizmo/ViewportGizmo.js.map +1 -1
- package/dist/esm/components/editor/ViewportGizmo/gizmoMath.js.map +1 -1
- package/dist/esm/components/editor/ViewportGizmo/useGizmoInteraction.js.map +1 -1
- package/dist/esm/components/editor/ViewportGizmo/useGizmoRenderer.js.map +1 -1
- package/dist/esm/components/feedback/Alert/Alert.js.map +1 -1
- package/dist/esm/components/feedback/Alert/AlertActions.js.map +1 -1
- package/dist/esm/components/feedback/Alert/AlertDescription.js.map +1 -1
- package/dist/esm/components/feedback/Alert/AlertTitle.js.map +1 -1
- package/dist/esm/components/feedback/CommandPalette/CommandPalette.js.map +1 -1
- package/dist/esm/components/feedback/CommandPalette/fuzzySearch.js.map +1 -1
- package/dist/esm/components/feedback/CommandPalette/useRecentItems.js.map +1 -1
- package/dist/esm/components/feedback/Dialog/Dialog.js.map +1 -1
- package/dist/esm/components/feedback/Dialog/DialogBody.js.map +1 -1
- package/dist/esm/components/feedback/Dialog/DialogClose.js.map +1 -1
- package/dist/esm/components/feedback/Dialog/DialogFooter.js.map +1 -1
- package/dist/esm/components/feedback/Dialog/DialogHeader.js.map +1 -1
- package/dist/esm/components/feedback/Dialog/useDialogAnimation.js.map +1 -1
- package/dist/esm/components/feedback/Drawer/Drawer.js.map +1 -1
- package/dist/esm/components/feedback/Drawer/useDrawerAnimation.js.map +1 -1
- package/dist/esm/components/feedback/EmptyState/EmptyState.js.map +1 -1
- package/dist/esm/components/feedback/LogView/LogView.js.map +1 -1
- package/dist/esm/components/feedback/LogView/LogViewBody.js.map +1 -1
- package/dist/esm/components/feedback/LogView/LogViewContext.js.map +1 -1
- package/dist/esm/components/feedback/LogView/LogViewParts.js.map +1 -1
- package/dist/esm/components/feedback/LogView/LogViewStore.js.map +1 -1
- package/dist/esm/components/feedback/LogView/logViewLabels.js.map +1 -1
- package/dist/esm/components/feedback/LogView/logViewLevels.js.map +1 -1
- package/dist/esm/components/feedback/LogView/logViewUtils.js.map +1 -1
- package/dist/esm/components/feedback/LogView/useFollowTail.js.map +1 -1
- package/dist/esm/components/feedback/LogView/useLogSelection.js.map +1 -1
- package/dist/esm/components/feedback/ProgressBar/CircularProgress.js.map +1 -1
- package/dist/esm/components/feedback/ProgressBar/ProgressBar.js.map +1 -1
- package/dist/esm/components/feedback/Skeleton/Skeleton.js.map +1 -1
- package/dist/esm/components/feedback/Skeleton/SkeletonGroup.js.map +1 -1
- package/dist/esm/components/feedback/Skeleton/SkeletonLayout.js.map +1 -1
- package/dist/esm/components/feedback/Spinner/Spinner.js.map +1 -1
- package/dist/esm/components/feedback/Stat/Stat.js.map +1 -1
- package/dist/esm/components/feedback/Toast/ToastContainer.js.map +1 -1
- package/dist/esm/components/feedback/Toast/ToastItem.js.map +1 -1
- package/dist/esm/components/feedback/Toast/ToastProvider.js.map +1 -1
- package/dist/esm/components/feedback/Toast/useToast.js.map +1 -1
- package/dist/esm/components/form/FormHelperText.js.map +1 -1
- package/dist/esm/components/form/FormLabel.js.map +1 -1
- package/dist/esm/components/form/InputWrapper.js.map +1 -1
- package/dist/esm/components/layout/Accordion/Accordion.js.map +1 -1
- package/dist/esm/components/layout/Accordion/AccordionContent.js.map +1 -1
- package/dist/esm/components/layout/Accordion/AccordionItem.js.map +1 -1
- package/dist/esm/components/layout/Accordion/AccordionTrigger.js.map +1 -1
- package/dist/esm/components/layout/Card/Card.js.map +1 -1
- package/dist/esm/components/layout/Divider/Divider.js.map +1 -1
- package/dist/esm/components/layout/Flex/Flex.js.map +1 -1
- package/dist/esm/components/layout/Grid/Grid.js.map +1 -1
- package/dist/esm/components/layout/ListItem/ListItem.js.map +1 -1
- package/dist/esm/components/layout/PageHeader/PageHeader.js.map +1 -1
- package/dist/esm/components/layout/PanelSurface/PanelSurface.js.map +1 -1
- package/dist/esm/components/layout/ScrollArea/ScrollArea.js.map +1 -1
- package/dist/esm/components/layout/Spacer/Spacer.js.map +1 -1
- package/dist/esm/components/layout/SplitPane/SplitPane.js.map +1 -1
- package/dist/esm/components/layout/SplitPane/SplitPanePanel.js.map +1 -1
- package/dist/esm/components/layout/Stack/Stack.js.map +1 -1
- package/dist/esm/components/navigation/Breadcrumbs/BreadcrumbEllipsis.js.map +1 -1
- package/dist/esm/components/navigation/Breadcrumbs/BreadcrumbItem.js.map +1 -1
- package/dist/esm/components/navigation/Breadcrumbs/BreadcrumbSeparator.js.map +1 -1
- package/dist/esm/components/navigation/Breadcrumbs/Breadcrumbs.js +6 -0
- package/dist/esm/components/navigation/Breadcrumbs/Breadcrumbs.js.map +1 -1
- package/dist/esm/components/navigation/ContextMenu/ContextMenu.js.map +1 -1
- package/dist/esm/components/navigation/Menu/Menu.js.map +1 -1
- package/dist/esm/components/navigation/Menu/MenuGapContext.js.map +1 -1
- package/dist/esm/components/navigation/Pagination/Pagination.js.map +1 -1
- package/dist/esm/components/navigation/Pagination/usePagination.js.map +1 -1
- package/dist/esm/components/navigation/PathBar/PathBar.js +7 -1
- package/dist/esm/components/navigation/PathBar/PathBar.js.map +1 -1
- package/dist/esm/components/navigation/PathBar/PathBarSiblingMenu.js +6 -0
- package/dist/esm/components/navigation/PathBar/PathBarSiblingMenu.js.map +1 -1
- package/dist/esm/components/navigation/PathBar/pathUtils.js.map +1 -1
- package/dist/esm/components/navigation/SegmentedControl/SegmentedControl.js.map +1 -1
- package/dist/esm/components/navigation/SegmentedControl/SegmentedControlItem.js.map +1 -1
- package/dist/esm/components/navigation/Tabs/Tab.js.map +1 -1
- package/dist/esm/components/navigation/Tabs/TabList.js.map +1 -1
- package/dist/esm/components/navigation/Tabs/TabPanel.js.map +1 -1
- package/dist/esm/components/navigation/Tabs/Tabs.js.map +1 -1
- package/dist/esm/components/primitives/Avatar/Avatar.js +6 -0
- package/dist/esm/components/primitives/Avatar/Avatar.js.map +1 -1
- package/dist/esm/components/primitives/Avatar/AvatarGroup.js.map +1 -1
- package/dist/esm/components/primitives/Badge/Badge.js +6 -0
- package/dist/esm/components/primitives/Badge/Badge.js.map +1 -1
- package/dist/esm/components/primitives/Button/Button.js.map +1 -1
- package/dist/esm/components/primitives/Checkbox/Checkbox.js.map +1 -1
- package/dist/esm/components/primitives/Checkbox/CheckboxGroup.js.map +1 -1
- package/dist/esm/components/primitives/Code/Code.js.map +1 -1
- package/dist/esm/components/primitives/Collapsible/Collapsible.js.map +1 -1
- package/dist/esm/components/primitives/HoverCard/HoverCard.js.map +1 -1
- package/dist/esm/components/primitives/Icon/Icon.js.map +1 -1
- package/dist/esm/components/primitives/IconButton/IconButton.js.map +1 -1
- package/dist/esm/components/primitives/Input/Input.js.map +1 -1
- package/dist/esm/components/primitives/Kbd/Kbd.js.map +1 -1
- package/dist/esm/components/primitives/Link/Link.js.map +1 -1
- package/dist/esm/components/primitives/Paper/Paper.js.map +1 -1
- package/dist/esm/components/primitives/Popover/Popover.js.map +1 -1
- package/dist/esm/components/primitives/Popover/PopoverClose.js.map +1 -1
- package/dist/esm/components/primitives/Popover/PopoverContent.js.map +1 -1
- package/dist/esm/components/primitives/Popover/PopoverTrigger.js.map +1 -1
- package/dist/esm/components/primitives/Radio/Radio.js.map +1 -1
- package/dist/esm/components/primitives/Radio/RadioGroup.js.map +1 -1
- package/dist/esm/components/primitives/Switch/Switch.js.map +1 -1
- package/dist/esm/components/primitives/Text/Text.js.map +1 -1
- package/dist/esm/components/primitives/TextArea/TextArea.js.map +1 -1
- package/dist/esm/components/primitives/Tooltip/Arrow.js.map +1 -1
- package/dist/esm/components/primitives/Tooltip/Tooltip.js.map +1 -1
- package/dist/esm/components/primitives/Tooltip/utils.js.map +1 -1
- package/dist/esm/components/primitives/VisuallyHidden/VisuallyHidden.js.map +1 -1
- package/dist/esm/components/primitives/canvas/CanvasContainer.js.map +1 -1
- package/dist/esm/components/primitives/canvas/canvasCoords.js.map +1 -1
- package/dist/esm/components/primitives/canvas/canvasDrawing.js.map +1 -1
- package/dist/esm/components/primitives/canvas/canvasTheme.js.map +1 -1
- package/dist/esm/components/primitives/canvas/useCanvasRenderer.js.map +1 -1
- package/dist/esm/components/primitives/viewport/Viewport.js +1 -1
- package/dist/esm/components/primitives/viewport/Viewport.js.map +1 -1
- package/dist/esm/components/primitives/viewport/ViewportContext.js.map +1 -1
- package/dist/esm/components/primitives/viewport/ViewportLayer.js +1 -1
- package/dist/esm/components/primitives/viewport/ViewportLayer.js.map +1 -1
- package/dist/esm/components/primitives/viewport/ViewportOverlay.js.map +1 -1
- package/dist/esm/components/primitives/viewport/ViewportStore.js.map +1 -1
- package/dist/esm/components/primitives/viewport/ViewportWorld.js.map +1 -1
- package/dist/esm/components/primitives/viewport/useViewportGestures.js +1 -1
- package/dist/esm/components/primitives/viewport/useViewportGestures.js.map +1 -1
- package/dist/esm/components/primitives/viewport/viewportCoords.js.map +1 -1
- package/dist/esm/components/primitives/viewport/viewportMath.js.map +1 -1
- package/dist/esm/components/shell/AppShell/AppShell.js.map +1 -1
- package/dist/esm/components/shell/FloatingPanel/FloatingPanel.js.map +1 -1
- package/dist/esm/components/shell/MenuBar/MenuBar.js.map +1 -1
- package/dist/esm/components/shell/StatusBar/StatusBar.js.map +1 -1
- package/dist/esm/components/shell/Toolbar/Toolbar.js.map +1 -1
- package/dist/esm/context/KeyboardContext.js.map +1 -1
- package/dist/esm/hooks/useBreakpoint/useBreakpoint.js.map +1 -1
- package/dist/esm/hooks/useClickOutside/useClickOutside.js.map +1 -1
- package/dist/esm/hooks/useClipboard/useClipboard.js.map +1 -1
- package/dist/esm/hooks/useControlledState/useControlledState.js.map +1 -1
- package/dist/esm/hooks/useDebounced/useDebouncedCallback.js.map +1 -1
- package/dist/esm/hooks/useDebounced/useDebouncedValue.js.map +1 -1
- package/dist/esm/hooks/useDisclosure/useDisclosure.js.map +1 -1
- package/dist/esm/hooks/useEventCallback/useEventCallback.js.map +1 -1
- package/dist/esm/hooks/useFocusTrap/useFocusTrap.js.map +1 -1
- package/dist/esm/hooks/useHotkey/useHotkey.js.map +1 -1
- package/dist/esm/hooks/useIntersectionObserver/useIntersectionObserver.js.map +1 -1
- package/dist/esm/hooks/useIsMounted/useIsMounted.js.map +1 -1
- package/dist/esm/hooks/useKeyboard/useKeyboard.js.map +1 -1
- package/dist/esm/hooks/useKeyboard/utils.js.map +1 -1
- package/dist/esm/hooks/useLatest/useLatest.js.map +1 -1
- package/dist/esm/hooks/useListboxNav/useListboxNav.js.map +1 -1
- package/dist/esm/hooks/useMediaQuery/useMediaQuery.js.map +1 -1
- package/dist/esm/hooks/useMergedRef/useMergedRef.js.map +1 -1
- package/dist/esm/hooks/useNavigationHistory/useNavigationHistory.js.map +1 -1
- package/dist/esm/hooks/useResizeObserver/useResizeObserver.js.map +1 -1
- package/dist/esm/hooks/useTheme/useTheme.js +1 -1
- package/dist/esm/hooks/useTheme/useTheme.js.map +1 -1
- package/dist/esm/hooks/useThrottledCallback/useThrottledCallback.js.map +1 -1
- package/dist/esm/index.js +6 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/node_modules/tslib/tslib.es6.js.map +1 -1
- package/dist/esm/styles.js +3 -0
- package/dist/esm/styles.js.map +1 -0
- package/dist/esm/theme/ThemeProvider.js +5 -3
- package/dist/esm/theme/ThemeProvider.js.map +1 -1
- package/dist/esm/theme/VanillaThemeProvider.js.map +1 -1
- package/dist/esm/theme/breakpoints.js.map +1 -1
- package/dist/esm/theme/createCustomTheme.js +1 -1
- package/dist/esm/theme/createCustomTheme.js.map +1 -1
- package/dist/esm/theme/createLightTheme.js.map +1 -1
- package/dist/esm/theme/darkThemeValues.js.map +1 -1
- package/dist/esm/theme/lightThemeValues.js.map +1 -1
- package/dist/esm/theme/themeContractData.js.map +1 -1
- package/dist/esm/utils/cx.js.map +1 -1
- package/dist/esm/utils/devWarn.js.map +1 -1
- package/dist/esm/utils/mathExpression.js.map +1 -1
- package/dist/esm/utils/mathUtils.js.map +1 -1
- package/dist/esm/utils/objects.js.map +1 -1
- package/dist/esm/utils/platform.js.map +1 -1
- package/dist/tokens/tokens.dark.css +1 -1
- package/dist/tokens/tokens.json +1 -1
- package/dist/tokens/tokens.light.css +1 -1
- package/dist/types/components/Icons/CameraIcon.d.ts +34 -0
- package/dist/types/components/Icons/CubeIcon.d.ts +34 -0
- package/dist/types/components/Icons/OrbitIcon.d.ts +34 -0
- package/dist/types/components/Icons/RotateIcon.d.ts +35 -0
- package/dist/types/components/Icons/SpinIcon.d.ts +35 -0
- package/dist/types/components/Icons/ZoomFitIcon.d.ts +35 -0
- package/dist/types/components/controls/Select/Select.d.ts +1 -1
- package/dist/types/components/controls/Select/Select.types.d.ts +43 -9
- package/dist/types/components/editor/PropertyInspector/PropertyInspector.types.d.ts +49 -3
- package/dist/types/components/primitives/Button/Button.d.ts +1 -1
- package/dist/types/components/primitives/IconButton/IconButton.d.ts +1 -1
- package/dist/types/index.d.ts +6 -0
- package/dist/types/theme/ThemeProvider.d.ts +5 -3
- package/package.json +6 -2
- package/dist/esm/_virtual/_commonjsHelpers.js +0 -6
- package/dist/esm/_virtual/_commonjsHelpers.js.map +0 -1
- package/dist/esm/_virtual/cjs.js +0 -4
- package/dist/esm/_virtual/cjs.js.map +0 -1
- package/dist/esm/_virtual/cssesc.js +0 -8
- package/dist/esm/_virtual/cssesc.js.map +0 -1
- package/dist/esm/_virtual/picocolors.js +0 -8
- package/dist/esm/_virtual/picocolors.js.map +0 -1
- package/dist/esm/_virtual/picocolors2.js +0 -4
- package/dist/esm/_virtual/picocolors2.js.map +0 -1
- package/dist/esm/node_modules/@emotion/hash/dist/emotion-hash.esm.js +0 -56
- package/dist/esm/node_modules/@emotion/hash/dist/emotion-hash.esm.js.map +0 -1
- package/dist/esm/node_modules/@vanilla-extract/css/adapter/dist/vanilla-extract-css-adapter.esm.js +0 -46
- package/dist/esm/node_modules/@vanilla-extract/css/adapter/dist/vanilla-extract-css-adapter.esm.js.map +0 -1
- package/dist/esm/node_modules/@vanilla-extract/css/dist/taggedTemplateLiteral-10998315.esm.js +0 -13
- package/dist/esm/node_modules/@vanilla-extract/css/dist/taggedTemplateLiteral-10998315.esm.js.map +0 -1
- package/dist/esm/node_modules/@vanilla-extract/css/dist/transformCss-fd0786e1.esm.js +0 -962
- package/dist/esm/node_modules/@vanilla-extract/css/dist/transformCss-fd0786e1.esm.js.map +0 -1
- package/dist/esm/node_modules/@vanilla-extract/css/dist/vanilla-extract-css.esm.js +0 -309
- package/dist/esm/node_modules/@vanilla-extract/css/dist/vanilla-extract-css.esm.js.map +0 -1
- package/dist/esm/node_modules/@vanilla-extract/css/fileScope/dist/vanilla-extract-css-fileScope.esm.js +0 -18
- package/dist/esm/node_modules/@vanilla-extract/css/fileScope/dist/vanilla-extract-css-fileScope.esm.js.map +0 -1
- package/dist/esm/node_modules/@vanilla-extract/css/injectStyles/dist/vanilla-extract-css-injectStyles.esm.js +0 -23
- package/dist/esm/node_modules/@vanilla-extract/css/injectStyles/dist/vanilla-extract-css-injectStyles.esm.js.map +0 -1
- package/dist/esm/node_modules/@vanilla-extract/css/node_modules/lru-cache/dist/esm/index.js +0 -1543
- package/dist/esm/node_modules/@vanilla-extract/css/node_modules/lru-cache/dist/esm/index.js.map +0 -1
- package/dist/esm/node_modules/@vanilla-extract/private/dist/vanilla-extract-private.esm.js +0 -38
- package/dist/esm/node_modules/@vanilla-extract/private/dist/vanilla-extract-private.esm.js.map +0 -1
- package/dist/esm/node_modules/css-what/lib/es/parse.js +0 -424
- package/dist/esm/node_modules/css-what/lib/es/parse.js.map +0 -1
- package/dist/esm/node_modules/css-what/lib/es/types.js +0 -29
- package/dist/esm/node_modules/css-what/lib/es/types.js.map +0 -1
- package/dist/esm/node_modules/cssesc/cssesc.js +0 -120
- package/dist/esm/node_modules/cssesc/cssesc.js.map +0 -1
- package/dist/esm/node_modules/dedent/dist/dedent.js +0 -100
- package/dist/esm/node_modules/dedent/dist/dedent.js.map +0 -1
- package/dist/esm/node_modules/deep-object-diff/mjs/diff.js +0 -40
- package/dist/esm/node_modules/deep-object-diff/mjs/diff.js.map +0 -1
- package/dist/esm/node_modules/deep-object-diff/mjs/utils.js +0 -9
- package/dist/esm/node_modules/deep-object-diff/mjs/utils.js.map +0 -1
- package/dist/esm/node_modules/deepmerge/dist/cjs.js +0 -143
- package/dist/esm/node_modules/deepmerge/dist/cjs.js.map +0 -1
- package/dist/esm/node_modules/media-query-parser/dist/media-query-parser.esm.js +0 -1386
- package/dist/esm/node_modules/media-query-parser/dist/media-query-parser.esm.js.map +0 -1
- package/dist/esm/node_modules/modern-ahocorasick/dist/index.js +0 -102
- package/dist/esm/node_modules/modern-ahocorasick/dist/index.js.map +0 -1
- package/dist/esm/node_modules/picocolors/picocolors.js +0 -87
- package/dist/esm/node_modules/picocolors/picocolors.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NodeGraph.js","sources":["../../../../../../src/components/editor/NodeGraph/NodeGraph.tsx"],"sourcesContent":[null],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsGA;AACA;AACA;AACA;AACE;AACA;AACA;;AAEF;AAEA;AAEA;;;;;;;;;;;;;;;;;;;;;;;;;AAyBG;AACH;;AAyCE;AACA;AACA;;;;AAKA;;;;;AASA;AACE;AACA;AACA;AACA;AACD;AACD;AACE;AACA;AACA;AACA;AACD;AACD;AACE;AACA;AACA;AACA;AACD;AACD;AACE;AACA;AACA;AACA;AACD;;;;;;;;AASE;AACH;;AAGE;AACF;;AAGA;AACA;AACE;;;AAIF;;AAGA;;;;AAMI;AACA;;AACA;AACA;;;;AAQA;AACF;;AAKF;AACA;AACA;AACA;AACA;AACA;;;AAKI;AACA;;;AAGA;AACE;AACA;AAEE;;;;;;AAYJ;;AAQA;;AACA;AACE;;;;;AAKF;AACF;;;;;;;AAQC;;AAIH;AACE;;;;;;AAMA;AACA;AACD;;AAGD;;;;;;AAME;AACA;AACD;;AAGD;;;;;;;;AAYE;AACA;AACA;AACD;;;;;;;;AAUG;;AAEE;AAAuB;;AACU;AACjC;;;;AAKE;;;AAGJ;AACA;AACF;;AAME;;AACA;AAGA;AACF;;AAKF;AACA;;;AAGI;;;;AAII;AACA;AACA;;;AAGC;;AAEF;;;;;AAMH;;;;;;AAOA;;AAGE;;AAQE;;AAEE;AACA;AACA;AACD;;;;;AAML;AACE;AAKA;AAME;;;AAGJ;AACA;AACE;;;;;;;AAOE;AACA;AACA;AACD;AACH;AACF;;;;;;;;AASC;;AAIH;AACA;;;;;;;AAOI;;;;;;;AAOE;;;;AAIA;;;AAGA;;AAEF;;AAEA;;AAEE;AACE;;;;;;;;;;;AASJ;AACE;;;;;;AAMF;;;AAGA;;;AAGF;;;;;;;AAQC;;;;;;;;;AAYA;;;;;;;;;;;;;;AAeH;;;;;AAMI;;AAEA;;;;AAIA;;AAGE;;;AAIF;;;AAKA;;;;;;AASA;;;AAKA;AACF;;;;;;;;;AAUC;;AAIH;;AAEE;AACA;AACA;;;;AAID;;AAGD;AACA;AACA;AACA;AACA;;;AAQI;AACE;AACA;AACA;AACA;AACD;;;AAUD;AACA;AACA;;AAEA;AACF;AAIF;;AAMI;AACA;AACA;AACA;;AAEA;AAYF;;AASE;AACA;;;;AAOI;AAKA;AAAe;AACf;;AAIN;;;;;;AASA;AACE;AACF;AACA;AACE;;;AAGA;AACA;;;AAGA;AACA;;AAEA;;;;;AAKA;;;;AAIA;;;AAGA;;AAEA;;;;AAIA;;AAEF;;AAC8B;AAC9B;AACF;;AAGA;;AAIM;AACA;;AACA;;;;;;AASA;AACA;;AACA;AACA;;AACA;;AAIA;;;;;;AASA;AACA;;;AAEA;;AACA;;;;AAQC;;AAEH;;;AAGA;;;AAGA;AACA;;;AAIA;AACD;;;;AAOH;AAEA;;;;AAmFoB;AAetB;AAyBA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEO;;"}
|
|
1
|
+
{"version":3,"file":"NodeGraph.js","sources":["src/components/editor/NodeGraph/NodeGraph.tsx"],"sourcesContent":["'use client';\n\nimport React, {\n useCallback,\n useImperativeHandle,\n useLayoutEffect,\n useMemo,\n useRef,\n useTransition,\n} from 'react';\n\nimport {\n Viewport,\n ViewportLayer,\n ViewportWorld,\n ViewportOverlay,\n screenToWorld as worldFromScreen,\n worldToScreen as screenFromWorld,\n} from '@/components/primitives/viewport';\nimport { useControlledState, useLatest } from '@/hooks';\nimport { cx } from '@/utils/cx';\n\nimport { nodeGraphRootStyle } from './NodeGraph.css';\nimport { NodeGraphStoreContext } from './NodeGraphContext';\nimport {\n buildDrawTheme,\n drawBackground,\n drawConnectionPreview,\n drawEdges,\n drawGroups,\n} from './nodeGraphDrawing';\nimport { EdgeLabelsLayer } from './NodeGraphEdgeLabels';\nimport { NodeGraphGroupView } from './NodeGraphGroup';\nimport {\n computeNodesBounds,\n findEdgeAtPoint,\n getNodeBox,\n isPointInNode,\n isPointInRect,\n rectsIntersect,\n resolvePortRef,\n toggleSelected,\n} from './nodeGraphMath';\nimport { NodeGraphMinimapInner } from './NodeGraphMinimap';\nimport { NodeGraphNodeView } from './NodeGraphNode';\nimport {\n NodeGraphNodeBody,\n NodeGraphNodeHeader,\n NodeGraphPinList,\n NodeGraphPinRow,\n} from './NodeGraphNodeParts';\nimport { NodeGraphNodeSection } from './NodeGraphNodeSection';\nimport { NodeGraphPin } from './NodeGraphPin';\nimport { NodeGraphPort } from './NodeGraphPort';\nimport { NodeGraphPortVisual } from './NodeGraphPortVisual';\nimport {\n NodeGraphBackground,\n NodeGraphMinimap,\n NodeGraphSpawnPalette,\n NodeGraphToolbar,\n} from './NodeGraphSlots';\nimport { NodeGraphSpawnPaletteInner } from './NodeGraphSpawnPalette';\nimport { NodeGraphStore } from './NodeGraphStore';\nimport {\n NodeGraphToolbarInner,\n NodeGraphFitContentButton,\n NodeGraphFitSelectionButton,\n NodeGraphZoomInButton,\n NodeGraphZoomOutButton,\n NodeGraphResetZoomButton,\n NodeGraphToolbarSeparator,\n} from './NodeGraphToolbar';\nimport { sortSlots } from './sortSlots';\nimport { useNodeGraphConnection } from './useNodeGraphConnection';\nimport {\n useNodeGraphEdgeInteraction,\n EDGE_HIT_PX,\n} from './useNodeGraphEdgeInteraction';\nimport { useNodeGraphGroupDrag } from './useNodeGraphGroupDrag';\nimport { useNodeGraphKeyboard } from './useNodeGraphKeyboard';\nimport { useNodeGraphNodeDrag } from './useNodeGraphNodeDrag';\n\nimport type {\n NodeGraphContextMenuInfo,\n NodeGraphEdge,\n NodeGraphGroup,\n NodeGraphHandle,\n NodeGraphLayerName,\n NodeGraphNode,\n NodeGraphPortRef,\n NodeGraphProps,\n NodeGraphSelection,\n NodeGraphTarget,\n} from './NodeGraph.types';\nimport type { Point2D } from '@/components/primitives/canvas/canvas.types';\nimport type {\n ViewportHandle,\n ViewportSelectionEvent,\n ViewportTransform,\n WorldRect,\n} from '@/components/primitives/viewport';\n\nconst EMPTY_NODES: NodeGraphNode[] = [];\nconst EMPTY_EDGES: NodeGraphEdge[] = [];\nconst EMPTY_GROUPS: NodeGraphGroup[] = [];\nconst EMPTY_SELECTION: NodeGraphSelection = {\n nodes: [],\n edges: [],\n groups: [],\n};\nconst DEFAULT_NODE_SIZE = { width: 180, height: 80 };\n\n// ─── Main component ───\n\n/**\n * `NodeGraph` — interactive, data-driven node editor surface built on\n * `Viewport`. Renders nodes as HTML in world space (`renderNode`) and\n * edges as Béziers on a perf-isolated canvas layer.\n *\n * The component is fully controlled-or-uncontrolled across four data\n * dimensions (nodes / edges / groups / selection) — each emits a full\n * array on change. Connection drags, marquee selection, drag-to-move,\n * snap-to-grid, and keyboard nav are all wired in.\n *\n * @example\n * ```tsx\n * <NodeGraph\n * nodes={nodes}\n * edges={edges}\n * onNodesChange={setNodes}\n * onEdgesChange={setEdges}\n * renderNode={node => <MyNode data={node.data} />}\n * isValidConnection={(src, tgt) => src.node !== tgt.node}\n * responsive\n * >\n * <NodeGraph.Background variant=\"dots\" gap={24} />\n * <NodeGraph.Minimap placement=\"bottom-right\" />\n * </NodeGraph>\n * ```\n */\nconst NodeGraphImpl = ({\n nodes: nodesProp,\n defaultNodes,\n onNodesChange,\n edges: edgesProp,\n defaultEdges,\n onEdgesChange,\n groups: groupsProp,\n defaultGroups,\n onGroupsChange,\n selection: selectionProp,\n defaultSelection,\n onSelectionChange,\n renderNode,\n renderEdgeLabel,\n edgeStyle,\n isValidConnection,\n snapToGrid = false,\n onConnectStart,\n onConnectEnd,\n onContextMenu,\n onDelete,\n onActivate,\n pan,\n zoom,\n minZoom = 0.1,\n maxZoom = 4,\n selectionRect = true,\n responsive = false,\n height = 480,\n defaultNodeSize: defaultNodeSizeProp = DEFAULT_NODE_SIZE,\n disabled = false,\n ariaLabel = 'Node graph',\n className,\n style,\n testId,\n id,\n children,\n ref,\n}: NodeGraphProps): React.ReactElement => {\n // ── Refs ──\n const rootRef = useRef<HTMLDivElement>(null);\n const viewportHandleRef = useRef<ViewportHandle>(null);\n const store = useMemo(() => new NodeGraphStore(), []);\n\n // Stabilize defaultNodeSize identity across renders when the consumer\n // passes an inline literal — downstream effects/memos depend on its\n // reference and would otherwise re-fire every render.\n const defaultNodeSize = useMemo(\n () => ({\n width: defaultNodeSizeProp.width,\n height: defaultNodeSizeProp.height,\n }),\n [defaultNodeSizeProp.width, defaultNodeSizeProp.height]\n );\n\n // ── Controlled / uncontrolled data ──\n const [nodes, setNodes] = useControlledState<NodeGraphNode[]>({\n value: nodesProp,\n defaultValue: defaultNodes,\n onChange: onNodesChange,\n fallback: EMPTY_NODES,\n });\n const [edges, setEdges] = useControlledState<NodeGraphEdge[]>({\n value: edgesProp,\n defaultValue: defaultEdges,\n onChange: onEdgesChange,\n fallback: EMPTY_EDGES,\n });\n const [groups, setGroups] = useControlledState<NodeGraphGroup[]>({\n value: groupsProp,\n defaultValue: defaultGroups,\n onChange: onGroupsChange,\n fallback: EMPTY_GROUPS,\n });\n const [selection, setSelection] = useControlledState<NodeGraphSelection>({\n value: selectionProp,\n defaultValue: defaultSelection,\n onChange: onSelectionChange,\n fallback: EMPTY_SELECTION,\n });\n\n // ── Mirror React state into the store ──\n useLayoutEffect(() => {\n store.setData({\n nodes,\n edges,\n groups,\n defaultNodeSize,\n });\n }, [store, nodes, edges, groups, defaultNodeSize]);\n\n useLayoutEffect(() => {\n store.setSelection(selection);\n }, [store, selection]);\n\n // ── Transform tracking (mirror Viewport transform into a ref for math) ──\n const transformRef = useRef<ViewportTransform>({ x: 0, y: 0, zoom: 1 });\n const handleTransformChange = useCallback((next: ViewportTransform) => {\n transformRef.current = next;\n }, []);\n\n // ── Slot sorting ──\n const slots = useMemo(() => sortSlots(children), [children]);\n\n // ── Helpers ──\n const getTransform = useCallback((): ViewportTransform => {\n return viewportHandleRef.current?.getTransform() ?? transformRef.current;\n }, []);\n\n const localScreenPoint = useCallback(\n (clientX: number, clientY: number): Point2D => {\n const el = rootRef.current;\n if (!el) return { x: 0, y: 0 };\n const rect = el.getBoundingClientRect();\n return { x: clientX - rect.left, y: clientY - rect.top };\n },\n []\n );\n\n const screenToWorldLocal = useCallback(\n (clientX: number, clientY: number): Point2D => {\n const local = localScreenPoint(clientX, clientY);\n return worldFromScreen(local, getTransform());\n },\n [localScreenPoint, getTransform]\n );\n\n // ── Targets / hit testing ──\n const nodesRef = useLatest(nodes);\n const groupsRef = useLatest(groups);\n const selectionRef = useLatest(selection);\n const setSelectionRef = useLatest(setSelection);\n const setNodesRef = useLatest(setNodes);\n const disabledRef = useLatest(disabled);\n\n const findHitTarget = useCallback(\n (clientX: number, clientY: number): NodeGraphTarget => {\n const worldPoint = screenToWorldLocal(clientX, clientY);\n const currentNodes = nodesRef.current;\n const currentGroups = groupsRef.current;\n // Iterate top-to-bottom so the last-rendered node \"wins\" — matches what\n // the user sees on top.\n for (let i = currentNodes.length - 1; i >= 0; i--) {\n const node = currentNodes[i];\n if (\n node &&\n isPointInNode(\n worldPoint,\n node,\n store.getMeasuredSize(node.id),\n defaultNodeSize\n )\n ) {\n return { kind: 'node', id: node.id };\n }\n }\n // Edges sit above groups / background but below nodes, so hit-test them\n // after nodes and before groups. Drawn on a canvas → no DOM to walk.\n const edgeThreshold = EDGE_HIT_PX / (getTransform().zoom || 1);\n const edgeId = findEdgeAtPoint(\n worldPoint,\n store.getData().edges,\n store.getNodeById,\n store.getPortPosition,\n edgeThreshold\n );\n if (edgeId) return { kind: 'edge', id: edgeId };\n for (let i = currentGroups.length - 1; i >= 0; i--) {\n const group = currentGroups[i];\n if (group && isPointInRect(worldPoint, group.bounds)) {\n return { kind: 'group', id: group.id };\n }\n }\n return { kind: 'empty', worldPoint };\n },\n [\n defaultNodeSize,\n screenToWorldLocal,\n nodesRef,\n groupsRef,\n store,\n getTransform,\n ]\n );\n\n // ── Connection drag (ports) ──\n const { onPortPointerDown, onEdgeReconnectStart } = useNodeGraphConnection({\n viewportRef: rootRef,\n store,\n getTransform,\n isValidConnection,\n onConnectStart,\n onConnectEnd,\n emitEdgesChange: setEdges,\n emitSelectionChange: setSelection,\n });\n\n // ── Node body pointerdown — drag (single/multi) + select on release ──\n const { onNodeBodyPointerDown, onNodeBodyPointerUp } = useNodeGraphNodeDrag({\n store,\n getTransform,\n localScreenPoint,\n snapToGrid,\n disabled,\n emitNodesChange: setNodes,\n emitSelectionChange: setSelection,\n });\n\n // ── Group interaction (drag + resize + click-select) ──\n const setGroupsRef = useLatest(setGroups);\n const {\n onGroupBodyPointerDown,\n onGroupHandlePointerDown,\n onGroupBodyPointerUp,\n } = useNodeGraphGroupDrag({\n store,\n getTransform,\n localScreenPoint,\n snapToGrid,\n disabled,\n defaultNodeSize,\n emitGroupsChange: setGroups,\n emitNodesChange: setNodes,\n emitSelectionChange: setSelection,\n });\n\n // ── Group label / colour edits ──\n //\n // The library's group overlay owns the inline label editor and the\n // colour swatch — these handlers translate UI commits into a controlled\n // `onGroupsChange` emission so the consumer receives the full next\n // groups array, same as every other group mutation.\n const handleGroupLabelChange = useCallback(\n (group: NodeGraphGroup, nextLabel: string): void => {\n const trimmed = nextLabel.trim();\n const nextGroups = groupsRef.current.map(g => {\n if (g.id !== group.id) return g;\n if ((g.label ?? '') === trimmed) return g;\n if (trimmed === '') {\n // Empty label collapses back to `undefined` so the default\n // \"Group\" placeholder shows up cleanly on next render.\n const { label: _omit, ...rest } = g;\n void _omit;\n return rest;\n }\n return { ...g, label: trimmed };\n });\n setGroupsRef.current(nextGroups);\n },\n [groupsRef, setGroupsRef]\n );\n\n const handleGroupColorChange = useCallback(\n (group: NodeGraphGroup, nextColor: string): void => {\n if (group.color === nextColor) return;\n const nextGroups = groupsRef.current.map(g =>\n g.id === group.id ? { ...g, color: nextColor } : g\n );\n setGroupsRef.current(nextGroups);\n },\n [groupsRef, setGroupsRef]\n );\n\n // ── Marquee selection from the Viewport ──\n const [, startMarqueeTransition] = useTransition();\n const handleViewportSelection = useCallback(\n (info: ViewportSelectionEvent): void => {\n if (disabledRef.current) return;\n if (info.inProgress) {\n // Live preview: drive the marquee overlay via the store so canvas\n // hit-test for hover/edge can also respond if needed.\n store.setInteraction({\n kind: 'marquee',\n startWorld: { x: info.rect.x, y: info.rect.y },\n currentWorld: {\n x: info.rect.x + info.rect.width,\n y: info.rect.y + info.rect.height,\n },\n additive: info.additive,\n });\n return;\n }\n\n // Final marquee — compute selection from rect intersection.\n store.setInteraction({ kind: 'idle' });\n const rect: WorldRect = info.rect;\n\n // A near-zero-area rect is a plain click, not a drag. Use it to select\n // an edge — edges are drawn on a canvas and never receive the pointer\n // event directly, so this is where edge clicks are resolved. Falls\n // through to the normal node-marquee / clear path when no edge is hit.\n const zoom = getTransform().zoom || 1;\n const isClick =\n rect.width * zoom <= EDGE_HIT_PX && rect.height * zoom <= EDGE_HIT_PX;\n if (isClick) {\n const edgeId = findEdgeAtPoint(\n { x: rect.x, y: rect.y },\n store.getData().edges,\n store.getNodeById,\n store.getPortPosition,\n EDGE_HIT_PX / zoom\n );\n if (edgeId) {\n const current = selectionRef.current;\n setSelectionRef.current({\n nodes: info.additive ? current.nodes : [],\n edges: toggleSelected(current.edges, edgeId, info.additive),\n groups: info.additive ? current.groups : [],\n });\n return;\n }\n }\n\n const hits: string[] = [];\n for (const node of nodesRef.current) {\n const box = getNodeBox(\n node,\n store.getMeasuredSize(node.id),\n defaultNodeSize\n );\n if (\n rectsIntersect(\n { x: box.x, y: box.y, width: box.width, height: box.height },\n rect\n )\n ) {\n hits.push(node.id);\n }\n }\n const current = selectionRef.current;\n const nextNodes = info.additive\n ? Array.from(new Set([...current.nodes, ...hits]))\n : hits;\n // Selection commits can cascade into expensive renderNode bodies for\n // a large hit set — let React schedule it as non-urgent so the\n // gesture release doesn't hitch.\n startMarqueeTransition(() => {\n setSelectionRef.current({\n nodes: nextNodes,\n edges: info.additive ? current.edges : [],\n groups: info.additive ? current.groups : [],\n });\n });\n },\n [\n store,\n nodesRef,\n selectionRef,\n setSelectionRef,\n defaultNodeSize,\n disabledRef,\n getTransform,\n ]\n );\n\n // ── Context menu ──\n const onContextMenuRef = useLatest(onContextMenu);\n const handleContextMenu = useCallback(\n (event: React.MouseEvent<HTMLDivElement>): void => {\n if (disabledRef.current) return;\n event.preventDefault();\n // Walk up the DOM from the event target to find data attributes\n // identifying a port or a node. Ports take priority because they're\n // descendants of nodes — the loop bails on the first match.\n const targetEl = event.target as Element | null;\n let portRef: NodeGraphPortRef | null = null;\n let nodeId: string | null = null;\n let cursor: Element | null = targetEl;\n while (cursor && cursor !== rootRef.current) {\n const portId = cursor.getAttribute('data-port-id');\n const dataNodeId = cursor.getAttribute('data-node-id');\n if (portId && dataNodeId) {\n portRef = { node: dataNodeId, port: portId };\n break;\n }\n if (dataNodeId && !nodeId) {\n nodeId = dataNodeId;\n }\n cursor = cursor.parentElement;\n }\n const screenPoint = localScreenPoint(event.clientX, event.clientY);\n const worldPoint = worldFromScreen(screenPoint, getTransform());\n let resolvedTarget: NodeGraphTarget;\n if (portRef) {\n resolvedTarget = {\n kind: 'port',\n node: portRef.node,\n port: portRef.port,\n };\n } else if (nodeId) {\n resolvedTarget = { kind: 'node', id: nodeId };\n } else {\n resolvedTarget = findHitTarget(event.clientX, event.clientY);\n }\n const info: NodeGraphContextMenuInfo = {\n target: resolvedTarget,\n screenPoint,\n worldPoint,\n };\n // Consumer callback fires first — they may render their own menu,\n // log telemetry, etc.\n onContextMenuRef.current?.(info);\n // SpawnPalette subscribers (if mounted) get pinged for empty /\n // group targets: those are the \"drop a new node here\" spots.\n if (resolvedTarget.kind === 'empty' || resolvedTarget.kind === 'group') {\n store.requestSpawn({ worldPoint, screenPoint });\n }\n },\n [\n findHitTarget,\n localScreenPoint,\n getTransform,\n onContextMenuRef,\n disabledRef,\n store,\n ]\n );\n\n // ── Edge hover + endpoint grab (canvas edges → pointer hit-test) ──\n const { onRootPointerMove, onRootPointerLeave, onRootPointerDownCapture } =\n useNodeGraphEdgeInteraction({\n store,\n getTransform,\n screenToWorldLocal,\n localScreenPoint,\n onEdgeReconnectStart,\n disabled,\n });\n\n // ── Delete ──\n //\n // Without a consumer-supplied `onDelete`, the library cascades the\n // selection through nodes / edges / groups automatically: nodes get\n // removed, edges are filtered out when either endpoint is gone (or\n // when the edge itself is selected), groups in the selection drop.\n // Saves every consumer the same 8-line filter and avoids the classic\n // \"I forgot to clean up orphan edges\" bug.\n //\n // If the consumer provides `onDelete`, they take over completely —\n // they can still call the exported `applyCascadeDelete` helper to do\n // the same filtering plus their own snapshot / undo / confirmation\n // logic on top.\n const handleDeleteInternal = useCallback(\n (sel: NodeGraphSelection): void => {\n if (onDelete) {\n onDelete(sel);\n return;\n }\n const currentNodes = nodesRef.current;\n const currentEdges = store.getData().edges;\n const currentGroups = groupsRef.current;\n const removedNodeIds = new Set(sel.nodes);\n const removedEdgeIds = new Set(sel.edges);\n const removedGroupIds = new Set(sel.groups);\n if (\n removedNodeIds.size === 0 &&\n removedEdgeIds.size === 0 &&\n removedGroupIds.size === 0\n ) {\n return;\n }\n if (removedNodeIds.size > 0) {\n setNodesRef.current(\n currentNodes.filter(n => !removedNodeIds.has(n.id))\n );\n }\n const nextEdges = currentEdges.filter(\n e =>\n !removedEdgeIds.has(e.id) &&\n !removedNodeIds.has(e.source.node) &&\n !removedNodeIds.has(e.target.node)\n );\n if (nextEdges.length !== currentEdges.length) {\n setEdges(nextEdges);\n }\n if (removedGroupIds.size > 0) {\n setGroupsRef.current(\n currentGroups.filter(g => !removedGroupIds.has(g.id))\n );\n }\n setSelectionRef.current({ nodes: [], edges: [], groups: [] });\n },\n [\n onDelete,\n store,\n nodesRef,\n groupsRef,\n setNodesRef,\n setGroupsRef,\n setSelectionRef,\n setEdges,\n ]\n );\n\n // ── Keyboard ──\n const { onKeyDown } = useNodeGraphKeyboard({\n store,\n emitNodesChange: setNodes,\n emitSelectionChange: setSelection,\n onDelete: handleDeleteInternal,\n onActivate,\n snapToGrid,\n disabled,\n });\n\n // ── Canvas draw callbacks (stable identities via useLatest) ──\n const backgroundProps = slots.backgroundProps;\n const backgroundVariant = backgroundProps?.variant ?? 'dots';\n const backgroundGap = backgroundProps?.gap ?? 24;\n const backgroundColor = backgroundProps?.color;\n const backgroundFill = backgroundProps?.background;\n\n const drawBackgroundLayer = useCallback(\n (\n ctx: CanvasRenderingContext2D,\n info: Parameters<React.ComponentProps<typeof ViewportLayer>['draw']>[1]\n ): void => {\n const theme = buildDrawTheme(info.theme);\n drawBackground(ctx, info, theme, {\n variant: backgroundVariant,\n gap: backgroundGap,\n color: backgroundColor,\n background: backgroundFill,\n });\n },\n [backgroundVariant, backgroundGap, backgroundColor, backgroundFill]\n );\n\n const drawGroupsLayer = useCallback(\n (\n ctx: CanvasRenderingContext2D,\n info: Parameters<React.ComponentProps<typeof ViewportLayer>['draw']>[1]\n ): void => {\n const data = store.getData();\n const sel = store.getSelection();\n const interaction = store.getInteraction();\n const theme = buildDrawTheme(info.theme);\n drawGroups(ctx, info, data, sel, interaction, theme);\n },\n [store]\n );\n\n const edgeStyleRef = useLatest(edgeStyle);\n const drawEdgesLayer = useCallback(\n (\n ctx: CanvasRenderingContext2D,\n info: Parameters<React.ComponentProps<typeof ViewportLayer>['draw']>[1]\n ): void => {\n const data = store.getData();\n const sel = store.getSelection();\n const hover = store.getHover();\n const interaction = store.getInteraction();\n const theme = buildDrawTheme(info.theme);\n drawEdges(\n ctx,\n info,\n data,\n sel,\n hover.hoveredEdgeId,\n interaction,\n store.getNodeById,\n store.getPortPosition,\n edgeStyleRef.current,\n theme\n );\n },\n [store, edgeStyleRef]\n );\n\n const drawPreviewLayer = useCallback(\n (\n ctx: CanvasRenderingContext2D,\n info: Parameters<React.ComponentProps<typeof ViewportLayer>['draw']>[1]\n ): void => {\n const interaction = store.getInteraction();\n if (interaction.kind !== 'connect') return;\n const theme = buildDrawTheme(info.theme);\n drawConnectionPreview(\n ctx,\n info,\n interaction,\n portRef => {\n const resolved = resolvePortRef(\n portRef,\n store.getNodeById,\n store.getPortPosition\n );\n if (!resolved) return null;\n return { position: resolved.position, side: resolved.side };\n },\n theme\n );\n },\n [store]\n );\n\n // Force invalidation of canvas layers when the relevant store slices change.\n // `invalidate` reads `viewportHandleRef.current` at call time so a viewport\n // remount (which would re-bind the imperative handle) doesn't leave us\n // pointing at a stale handle reference.\n useLayoutEffect(() => {\n const invalidate = (layer: NodeGraphLayerName): void => {\n viewportHandleRef.current?.invalidate(layer);\n };\n const unsubs: Array<() => void> = [\n store.subscribeData(() => {\n invalidate('groups');\n invalidate('edges');\n }),\n store.subscribeSelection(() => {\n invalidate('groups');\n invalidate('edges');\n }),\n store.subscribeHover(() => {\n invalidate('edges');\n }),\n // Interaction drives the in-flight drag delta — every canvas layer\n // that visualises something positional needs to redraw on each tick:\n // edges (connectors follow dragged nodes live, not snap on release),\n // groups (drag-groups / resize-group), and the connection preview.\n store.subscribeInteraction(() => {\n invalidate('edges');\n invalidate('groups');\n invalidate('preview');\n }),\n // Port positions (re)registered by `<NodeGraph.Port>` slots — edges\n // anchor at the registered point, so any change must redraw.\n store.subscribePortPositions(() => {\n invalidate('edges');\n }),\n // Measured node sizes feed the marquee / hit-test bounds — no canvas\n // redraw needed, but subscribe so React-side reads in renderers see\n // the updates if they depend on it.\n store.subscribeMeasuredSizes(() => undefined),\n ];\n return () => {\n for (const unsub of unsubs) unsub();\n };\n }, [store]);\n\n // ── Imperative handle ──\n useImperativeHandle(\n ref,\n (): NodeGraphHandle => ({\n fitToContent: padding => {\n const vp = viewportHandleRef.current;\n if (!vp) return;\n const bounds = computeNodesBounds(\n nodesRef.current,\n store.getMeasuredSize,\n defaultNodeSize\n );\n if (bounds.width === 0 && bounds.height === 0) return;\n vp.fitToContent(bounds, padding ?? 32);\n },\n fitToSelection: padding => {\n const vp = viewportHandleRef.current;\n if (!vp) return;\n const sel = selectionRef.current.nodes;\n if (sel.length === 0) return;\n const selectedIds = new Set(sel);\n const selectedNodes = nodesRef.current.filter(n =>\n selectedIds.has(n.id)\n );\n const bounds = computeNodesBounds(\n selectedNodes,\n store.getMeasuredSize,\n defaultNodeSize\n );\n if (bounds.width === 0 && bounds.height === 0) return;\n vp.fitToContent(bounds, padding ?? 64);\n },\n focusNode: idStr => {\n const vp = viewportHandleRef.current;\n if (!vp) return;\n const node = store.getNodeById(idStr);\n if (!node) return;\n const box = getNodeBox(\n node,\n store.getMeasuredSize(node.id),\n defaultNodeSize\n );\n vp.centerOn({\n x: box.x + box.width / 2,\n y: box.y + box.height / 2,\n });\n },\n centerOn: (point, z) => {\n viewportHandleRef.current?.centerOn(point, z);\n },\n zoomToRect: (rect, padding) => {\n viewportHandleRef.current?.zoomToRect(rect, padding);\n },\n getTransform: () => getTransform(),\n getSize: () =>\n viewportHandleRef.current?.getSize() ?? { width: 0, height: 0 },\n worldToScreen: point => screenFromWorld(point, getTransform()),\n screenToWorld: point => worldFromScreen(point, getTransform()),\n invalidate: layerName => viewportHandleRef.current?.invalidate(layerName),\n }),\n [nodesRef, selectionRef, defaultNodeSize, getTransform, store]\n );\n\n // Edge labels rendered as world-space children at the midpoint of each\n // edge. Mounted as its own subscriber component so the labels follow\n // dragged nodes live without re-rendering the whole NodeGraph.\n const renderEdgeLabelRef = useLatest(renderEdgeLabel);\n\n return (\n <NodeGraphStoreContext.Provider value={store}>\n {/*\n The root is an event-delegation wrapper for graph-wide keyboard\n shortcuts and pointer capture; it is not itself focusable. The\n focusable, labelled interactive surface is the inner <Viewport>\n (ariaLabel + ariaRoledescription=\"node graph\"), which jsx-a11y\n cannot see through this wrapper.\n */}\n {/* eslint-disable-next-line jsx-a11y/no-static-element-interactions */}\n <div\n ref={rootRef}\n id={id}\n data-testid={testId}\n onKeyDown={onKeyDown}\n onContextMenu={handleContextMenu}\n onPointerDownCapture={onRootPointerDownCapture}\n onPointerMove={onRootPointerMove}\n onPointerLeave={onRootPointerLeave}\n className={cx(nodeGraphRootStyle, className)}\n style={style}\n >\n <Viewport\n ref={viewportHandleRef}\n responsive={responsive}\n height={height}\n pan={pan}\n zoom={zoom}\n minZoom={minZoom}\n maxZoom={maxZoom}\n selectionRect={{ enabled: selectionRect, button: 'left' }}\n onSelectionChange={handleViewportSelection}\n onTransformChange={handleTransformChange}\n disabled={disabled}\n ariaLabel={ariaLabel}\n ariaRoledescription=\"node graph\"\n >\n {slots.hasBackground ? (\n <ViewportLayer name=\"background\" draw={drawBackgroundLayer} />\n ) : null}\n <ViewportLayer name=\"groups\" draw={drawGroupsLayer} />\n <ViewportLayer name=\"edges\" draw={drawEdgesLayer} />\n <ViewportWorld>\n {groups.map(group => (\n <NodeGraphGroupView\n key={group.id}\n group={group}\n onBodyPointerDown={onGroupBodyPointerDown}\n onBodyPointerUp={onGroupBodyPointerUp}\n onHandlePointerDown={onGroupHandlePointerDown}\n onBodyContextMenu={handleContextMenu}\n onLabelChange={handleGroupLabelChange}\n onColorChange={handleGroupColorChange}\n />\n ))}\n {nodes.map(node => (\n <NodeGraphNodeView\n key={node.id}\n node={node}\n renderNode={renderNode}\n onBodyPointerDown={onNodeBodyPointerDown}\n onBodyPointerUp={onNodeBodyPointerUp}\n onPortPointerDown={onPortPointerDown}\n onBodyContextMenu={handleContextMenu}\n />\n ))}\n <EdgeLabelsLayer\n renderEdgeLabelRef={renderEdgeLabelRef}\n hasRenderEdgeLabel={renderEdgeLabel !== undefined}\n />\n </ViewportWorld>\n <ViewportLayer name=\"preview\" draw={drawPreviewLayer} />\n {slots.hasMinimap || slots.toolbars.length > 0 ? (\n <ViewportOverlay>\n {slots.hasMinimap ? (\n <NodeGraphMinimapInner {...(slots.minimapProps ?? {})} />\n ) : null}\n {slots.toolbars.map((toolbarProps, i) => (\n <NodeGraphToolbarInner\n // Placement is part of the identity — multiple toolbars\n // in the same NodeGraph use different placements as\n // their natural key.\n key={toolbarProps.placement ?? `toolbar-${i}`}\n {...toolbarProps}\n />\n ))}\n </ViewportOverlay>\n ) : null}\n </Viewport>\n {/* SpawnPalette renders in a portal (via CommandPalette) so it\n sits at the React tree root, outside the Viewport. Subscribes\n to the store's spawn-request channel for right-click triggers. */}\n {slots.spawnPaletteProps ? (\n <NodeGraphSpawnPaletteInner {...slots.spawnPaletteProps} />\n ) : null}\n </div>\n </NodeGraphStoreContext.Provider>\n );\n};\n\n// ─── Compound exports ───\n\ntype NodeGraphCompound = typeof NodeGraphImpl & {\n Minimap: typeof NodeGraphMinimap;\n Background: typeof NodeGraphBackground;\n Port: typeof NodeGraphPort;\n PortVisual: typeof NodeGraphPortVisual;\n Pin: typeof NodeGraphPin;\n NodeBody: typeof NodeGraphNodeBody;\n NodeHeader: typeof NodeGraphNodeHeader;\n NodeSection: typeof NodeGraphNodeSection;\n PinList: typeof NodeGraphPinList;\n PinRow: typeof NodeGraphPinRow;\n Toolbar: typeof NodeGraphToolbar;\n ToolbarSeparator: typeof NodeGraphToolbarSeparator;\n FitContentButton: typeof NodeGraphFitContentButton;\n FitSelectionButton: typeof NodeGraphFitSelectionButton;\n ZoomInButton: typeof NodeGraphZoomInButton;\n ZoomOutButton: typeof NodeGraphZoomOutButton;\n ResetZoomButton: typeof NodeGraphResetZoomButton;\n SpawnPalette: typeof NodeGraphSpawnPalette;\n};\n\nNodeGraphImpl.displayName = 'NodeGraph';\n\nconst NodeGraphWithSlots = NodeGraphImpl as NodeGraphCompound;\nNodeGraphWithSlots.Minimap = NodeGraphMinimap;\nNodeGraphWithSlots.Background = NodeGraphBackground;\nNodeGraphWithSlots.Port = NodeGraphPort;\nNodeGraphWithSlots.PortVisual = NodeGraphPortVisual;\nNodeGraphWithSlots.Pin = NodeGraphPin;\nNodeGraphWithSlots.NodeBody = NodeGraphNodeBody;\nNodeGraphWithSlots.NodeHeader = NodeGraphNodeHeader;\nNodeGraphWithSlots.NodeSection = NodeGraphNodeSection;\nNodeGraphWithSlots.PinList = NodeGraphPinList;\nNodeGraphWithSlots.PinRow = NodeGraphPinRow;\nNodeGraphWithSlots.Toolbar = NodeGraphToolbar;\nNodeGraphWithSlots.ToolbarSeparator = NodeGraphToolbarSeparator;\nNodeGraphWithSlots.FitContentButton = NodeGraphFitContentButton;\nNodeGraphWithSlots.FitSelectionButton = NodeGraphFitSelectionButton;\nNodeGraphWithSlots.ZoomInButton = NodeGraphZoomInButton;\nNodeGraphWithSlots.ZoomOutButton = NodeGraphZoomOutButton;\nNodeGraphWithSlots.ResetZoomButton = NodeGraphResetZoomButton;\nNodeGraphWithSlots.SpawnPalette = NodeGraphSpawnPalette;\n\nexport const NodeGraph = NodeGraphWithSlots;\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsGA;AACA;AACA;AACA;AACE;AACA;AACA;;AAEF;AAEA;AAEA;;;;;;;;;;;;;;;;;;;;;;;;;AAyBG;AACH;;AAyCE;AACA;AACA;;;;AAKA;;;;;AASA;AACE;AACA;AACA;AACA;AACD;AACD;AACE;AACA;AACA;AACA;AACD;AACD;AACE;AACA;AACA;AACA;AACD;AACD;AACE;AACA;AACA;AACA;AACD;;;;;;;;AASE;AACH;;AAGE;AACF;;AAGA;AACA;AACE;;;AAIF;;AAGA;;;;AAMI;AACA;;AACA;AACA;;;;AAQA;AACF;;AAKF;AACA;AACA;AACA;AACA;AACA;;;AAKI;AACA;;;AAGA;AACE;AACA;AAEE;;;;;;AAYJ;;AAQA;;AACA;AACE;;;;;AAKF;AACF;;;;;;;AAQC;;AAIH;AACE;;;;;;AAMA;AACA;AACD;;AAGD;;;;;;AAME;AACA;AACD;;AAGD;;;;;;;;AAYE;AACA;AACA;AACD;;;;;;;;AAUG;;AAEE;AAAuB;;AACU;AACjC;;;;AAKE;;;AAGJ;AACA;AACF;;AAME;;AACA;AAGA;AACF;;AAKF;AACA;;;AAGI;;;;AAII;AACA;AACA;;;AAGC;;AAEF;;;;;AAMH;;;;;;AAOA;;AAGE;;AAQE;;AAEE;AACA;AACA;AACD;;;;;AAML;AACE;AAKA;AAME;;;AAGJ;AACA;AACE;;;;;;;AAOE;AACA;AACA;AACD;AACH;AACF;;;;;;;;AASC;;AAIH;AACA;;;;;;;AAOI;;;;;;;AAOE;;;;AAIA;;;AAGA;;AAEF;;AAEA;;AAEE;AACE;;;;;;;;;;;AASJ;AACE;;;;;;AAMF;;;AAGA;;;AAGF;;;;;;;AAQC;;;;;;;;;AAYA;;;;;;;;;;;;;;AAeH;;;;;AAMI;;AAEA;;;;AAIA;;AAGE;;;AAIF;;;AAKA;;;;;;AASA;;;AAKA;AACF;;;;;;;;;AAUC;;AAIH;;AAEE;AACA;AACA;;;;AAID;;AAGD;AACA;AACA;AACA;AACA;;;AAQI;AACE;AACA;AACA;AACA;AACD;;;AAUD;AACA;AACA;;AAEA;AACF;AAIF;;AAMI;AACA;AACA;AACA;;AAEA;AAYF;;AASE;AACA;;;;AAOI;AAKA;AAAe;AACf;;AAIN;;;;;;AASA;AACE;AACF;AACA;AACE;;;AAGA;AACA;;;AAGA;AACA;;AAEA;;;;;AAKA;;;;AAIA;;;AAGA;;AAEA;;;;AAIA;;AAEF;;AAC8B;AAC9B;AACF;;AAGA;;AAIM;AACA;;AACA;;;;;;AASA;AACA;;AACA;AACA;;AACA;;AAIA;;;;;;AASA;AACA;;;AAEA;;AACA;;;;AAQC;;AAEH;;;AAGA;;;AAGA;AACA;;;AAIA;AACD;;;;AAOH;AAEA;;;;AAmFoB;AAetB;AAyBA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEO;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NodeGraph.types.js","sources":["../../../../../../src/components/editor/NodeGraph/NodeGraph.types.ts"],"sourcesContent":[null],"names":[],"mappings":"AAwUA;AAEA;;;;AAIG;AACI,MAAM,eAAe,GAAkB,MAAM,CAAC,GAAG,CAAC,qBAAqB;;;;"}
|
|
1
|
+
{"version":3,"file":"NodeGraph.types.js","sources":["src/components/editor/NodeGraph/NodeGraph.types.ts"],"sourcesContent":["import type { Point2D } from '@/components/primitives/canvas/canvas.types';\nimport type {\n ViewportPanConfig,\n ViewportSize,\n ViewportTransform,\n ViewportZoomConfig,\n WorldRect,\n} from '@/components/primitives/viewport';\nimport type { BaseComponent } from '@/types/common';\nimport type { Prettify } from '@/types/utilities';\nimport type React from 'react';\n\n// ─── Geometry primitives ───\n\n/** Side of a node a port is anchored to. */\nexport type NodeGraphPortSide = 'left' | 'right' | 'top' | 'bottom';\n\n/** Reference to a single port on a node. */\nexport interface NodeGraphPortRef {\n /** Owning node id. */\n node: string;\n /** Port id within that node. */\n port: string;\n}\n\n// ─── Data model ───\n\nexport interface NodeGraphNode {\n /** Stable identity. */\n id: string;\n /** Position in world coordinates (top-left corner of the node body). */\n position: Point2D;\n /**\n * Override the auto-measured width of the node body. Use this when you\n * need fixed-size nodes. When omitted, the library measures the rendered\n * node DOM and uses that size for hit-testing, marquee selection, and\n * minimap geometry.\n */\n width?: number;\n /** Override the auto-measured height of the node body. See {@link width}. */\n height?: number;\n /** Arbitrary consumer payload passed to `renderNode`. */\n data?: unknown;\n /**\n * When false, the node cannot be moved by drag. Selection still works.\n * @default true\n */\n draggable?: boolean;\n /**\n * When false, the node cannot be selected (clicks pass through to the\n * background). Drag is also disabled.\n * @default true\n */\n selectable?: boolean;\n}\n\nexport interface NodeGraphEdge {\n /** Stable identity. */\n id: string;\n /** Edge source endpoint. */\n source: NodeGraphPortRef;\n /** Edge target endpoint. */\n target: NodeGraphPortRef;\n /** Arbitrary consumer payload (e.g. animation flags, weights). */\n data?: unknown;\n /**\n * Optional HTML label rendered absolutely above the edge midpoint.\n * Use `renderEdgeLabel` for full control, or pass a node here for the\n * default centred placement.\n */\n label?: React.ReactNode;\n /**\n * Override the stroke colour of this edge in its default state.\n * Selected / hovered edges still pick up the theme accents (so the\n * interaction state stays visible), this colour is used only when\n * neither state is active. Useful for type-coloured wires — assign\n * the colour at edge-creation time based on the source pin's data\n * type, the way UE5 paints exec / float / bool wires differently.\n */\n color?: string;\n}\n\n/**\n * A visual backdrop rectangle drawn under nodes. Groups in v1 are purely\n * decorative — nodes are not children of a group, they simply happen to\n * overlap with it. Selection of a group through marquee/click can be\n * wired up by the consumer via `onContextMenu` / future selection API.\n */\nexport interface NodeGraphGroup {\n /** Stable identity. */\n id: string;\n /** Group rectangle in world coordinates. */\n bounds: WorldRect;\n /** Optional title shown above the group's top edge. */\n label?: string;\n /** Optional CSS colour string used for the group tint and label. */\n color?: string;\n}\n\nexport interface NodeGraphSelection {\n /** Selected node ids. */\n nodes: string[];\n /** Selected edge ids. */\n edges: string[];\n /** Selected group ids. */\n groups: string[];\n}\n\n// ─── Targets / events ───\n\n/** Discriminated union describing what was hit by a pointer event. */\nexport type NodeGraphTarget =\n | { kind: 'node'; id: string }\n | { kind: 'edge'; id: string }\n | { kind: 'port'; node: string; port: string }\n | { kind: 'group'; id: string }\n | { kind: 'empty'; worldPoint: Point2D };\n\nexport interface NodeGraphContextMenuInfo {\n target: NodeGraphTarget;\n /** Screen-space (CSS-pixel) position of the pointer relative to viewport. */\n screenPoint: Point2D;\n /** World-space position of the pointer. */\n worldPoint: Point2D;\n}\n\nexport interface NodeGraphConnectStartInfo {\n source: NodeGraphPortRef;\n /** World-space position the drag started at (the source port). */\n worldPoint: Point2D;\n}\n\nexport interface NodeGraphConnectEndInfo {\n source: NodeGraphPortRef;\n /** Resolved drop target, or null when the drag ended on empty space. */\n target: NodeGraphPortRef | null;\n /** True when the drop was rejected (target invalid or empty). */\n cancelled: boolean;\n /**\n * World-space position the drag was released at. With `cancelled` + a null\n * `target` this is an empty-space drop — the hook for a \"drag a wire onto\n * the canvas → open a create-node menu here → connect the new node to\n * `source`\" flow. Place the spawned node at this point.\n */\n worldPoint: Point2D;\n /**\n * Screen-space (CSS-pixel, relative to the viewport top-left) release\n * position — position your create-node menu / popover here.\n */\n screenPoint: Point2D;\n}\n\nexport interface NodeGraphConnectionValidationInfo {\n /** True when source and target reference the same node. */\n sameNode: boolean;\n /** Side combination shorthand, e.g. `'right->left'`. */\n sideCombo: string;\n /** `dataType` declared on the source `<NodeGraph.Port>`, if any. */\n sourceDataType?: string;\n /** `dataType` declared on the target `<NodeGraph.Port>`, if any. */\n targetDataType?: string;\n}\n\n// ─── Rendering ───\n\n/** Context passed to `renderNode`. */\nexport interface NodeGraphRenderCtx {\n /** True when the node is part of the current selection. */\n selected: boolean;\n /** True when the node is being dragged (single or multi). */\n dragging: boolean;\n /** True when the pointer is hovering over the node body. */\n hovered: boolean;\n /** Current viewport zoom — useful for LOD rendering. */\n zoom: number;\n}\n\n/** Resolved port metadata passed to the `edgeStyle` callback. */\nexport interface NodeGraphEdgeStyleCtx {\n /** True when the edge is in the current selection. */\n selected: boolean;\n /** True when the pointer is currently hovering over the edge. */\n hovered: boolean;\n /**\n * The `side` and `dataType` declared on the source `<NodeGraph.Port>`,\n * resolved from the store. `null` when the port hasn't been measured\n * yet (e.g. first paint before `useLayoutEffect` has run).\n */\n sourcePort: { side: NodeGraphPortSide; dataType?: string } | null;\n /** Same for the target endpoint. */\n targetPort: { side: NodeGraphPortSide; dataType?: string } | null;\n}\n\n/**\n * Style overrides for a single edge, returned by `edgeStyle`. Any field\n * left undefined falls back to the default (theme + `edge.color`).\n */\nexport interface NodeGraphEdgeStyle {\n /** Stroke colour. Overrides `edge.color`. */\n color?: string;\n /** Stroke width in screen pixels. */\n width?: number;\n /**\n * Marching-ant dash pattern, in screen pixels.\n * Example: `[8, 6]` draws 8 px dash + 6 px gap repeating.\n * Pass `null` (or omit) for a solid stroke.\n */\n dash?: ReadonlyArray<number> | null;\n}\n\n/**\n * Per-edge style hook. Called on every frame for every edge — keep it\n * cheap (map lookup, ternary). The library memoizes nothing here; if\n * the result is expensive, hoist into a `useMemo` keyed by inputs.\n */\nexport type NodeGraphEdgeStyleFn = (\n edge: NodeGraphEdge,\n ctx: NodeGraphEdgeStyleCtx\n) => NodeGraphEdgeStyle | undefined;\n\n// ─── Port slot ───\n\n/**\n * Built-in port handle shapes. `circle` is the default data-pin look,\n * `triangle` is the UE-style exec/flow arrow, `diamond` / `square` cover\n * the other common node-editor conventions. Render any other visual by\n * passing `children` to `<NodeGraph.Port>`.\n */\nexport type NodeGraphPortShape = 'circle' | 'triangle' | 'diamond' | 'square';\n\n/**\n * Props for `<NodeGraph.Port>` — the compound child rendered inside\n * `renderNode` to declare a connection endpoint.\n *\n * The library tracks the slot's DOM position and registers it as the\n * anchor for any edge that references this port. Pointer events on the\n * slot start a connection drag automatically — no manual wiring from the\n * consumer.\n *\n * @example\n * ```tsx\n * <NodeGraph\n * renderNode={(node, ctx) => (\n * <MyNodeBody>\n * <Row>\n * <NodeGraph.Port id=\"exec-in\" side=\"left\" dataType=\"exec\" />\n * Execute\n * </Row>\n * </MyNodeBody>\n * )}\n * />\n * ```\n */\nexport interface NodeGraphPortSlotProps {\n /** Stable identity within the owning node — referenced by `NodeGraphEdge`. */\n id: string;\n /** Which side of the node the port anchors to. Determines Bézier tangent. */\n side: NodeGraphPortSide;\n /**\n * Opaque type token forwarded to `isValidConnection` and exposed on\n * `data-port-data-type` for CSS theming. Not interpreted by the library.\n */\n dataType?: string;\n /**\n * Built-in handle shape used when no `children` are supplied.\n * @default 'circle'\n */\n shape?: NodeGraphPortShape;\n /**\n * Handle colour for the built-in visual (any CSS colour). When omitted,\n * the port inherits the theme focus colour; the connection-drag states\n * (source / candidate / invalid) still override it so interaction stays\n * visible. Ignored when `children` are supplied.\n */\n color?: string;\n /**\n * Force the built-in visual filled (`true`) or hollow (`false`). When\n * omitted, the port fills automatically while it is connected to at\n * least one edge and stays hollow otherwise — the standard \"wired vs\n * unwired pin\" convention. Ignored when `children` are supplied.\n */\n filled?: boolean;\n /**\n * Replace the built-in port visual entirely. When provided, no built-in\n * chrome is rendered — the children fill the slot wrapper. State for\n * theming (source / candidate / hover / invalid / connected) is exposed\n * via `data-port-*` attributes on the wrapper.\n */\n children?: React.ReactNode;\n /** Accessible label. Falls back to `${side} port ${id}`. */\n label?: string;\n /** Extra class applied to the slot wrapper. */\n className?: string;\n /** Inline style override on the slot wrapper. */\n style?: React.CSSProperties;\n}\n\n// ─── Imperative handle ───\n\nexport interface NodeGraphHandle {\n /** Pan/zoom so all nodes fit inside the viewport with optional padding. */\n fitToContent(padding?: number): void;\n /** Pan/zoom so all *selected* nodes fit. No-op when selection is empty. */\n fitToSelection(padding?: number): void;\n /** Center the viewport on a specific node. */\n focusNode(id: string): void;\n /** Center on a world point, optionally setting zoom. */\n centerOn(point: Point2D, zoom?: number): void;\n /** Pan/zoom to fit a world rectangle. */\n zoomToRect(rect: WorldRect, padding?: number): void;\n /** Read the current transform. */\n getTransform(): ViewportTransform;\n /** Read the current viewport size in CSS pixels. */\n getSize(): ViewportSize;\n /** Convert a world point to a screen (CSS-pixel) point. */\n worldToScreen(point: Point2D): Point2D;\n /** Convert a screen (CSS-pixel) point to a world point. */\n screenToWorld(point: Point2D): Point2D;\n /**\n * Force a redraw. With no `layerName`, all canvas layers redraw on the\n * next frame; with a name (`'edges' | 'groups' | 'preview'`), only that\n * layer redraws.\n */\n invalidate(layerName?: NodeGraphLayerName): void;\n}\n\nexport type NodeGraphLayerName = 'groups' | 'edges' | 'preview';\n\n// ─── Slot markers ───\n\n/**\n * Symbol attached to `<NodeGraph.Minimap>` and other slot subcomponents so\n * the parent `<NodeGraph>` can identify them without relying on\n * `displayName` (which gets clobbered by `React.memo` / minifiers).\n */\nexport const NODE_GRAPH_SLOT: unique symbol = Symbol.for('etui.nodegraph.slot');\n\nexport type NodeGraphSlotKind =\n | 'minimap'\n | 'background'\n | 'toolbar'\n | 'spawn-palette';\n\n/**\n * Template registered with `<NodeGraph.SpawnPalette>` — describes a\n * spawn-able node + how to build it at a given world point. The\n * library handles search / filtering / focus / selection of templates\n * in the popover; this is the data slice consumer cares about.\n */\nexport interface NodeGraphTemplate {\n /** Stable identifier (used for keys + the palette's recent list). */\n id: string;\n /** Human-readable title shown in the palette. */\n title: string;\n /** Optional secondary line. */\n subtitle?: string;\n /** Group label used to section the palette list. */\n group?: string;\n /** Extra strings the fuzzy matcher considers (synonyms, tags). */\n keywords?: ReadonlyArray<string>;\n /** Optional leading icon. */\n icon?: React.ReactNode;\n /**\n * Build the node body from a world position. The library assigns\n * the final `id` (so consumer doesn't have to roll their own\n * uniqueness scheme) — return everything except `id`.\n */\n build: (worldPoint: Point2D) => Omit<NodeGraphNode, 'id'>;\n}\n\nexport interface NodeGraphSpawnContext {\n /** World point where the user invoked the palette. */\n worldPoint: Point2D;\n /** Screen-space point (CSS pixels relative to viewport top-left). */\n screenPoint: Point2D;\n}\n\nexport interface NodeGraphSpawnPaletteSlotProps {\n /** All spawn-able templates. Filtering happens inside the library. */\n templates: ReadonlyArray<NodeGraphTemplate>;\n /**\n * Called when the user picks a template. Library has already\n * assigned an `id`; consumer typically appends to their nodes state.\n */\n onSpawn: (node: NodeGraphNode, ctx: NodeGraphSpawnContext) => void;\n /** Placeholder for the search input. */\n placeholder?: string;\n /** localStorage key for the palette's recent list. */\n recentKey?: string;\n /** Popover width. @default 400 */\n width?: number | string;\n /** Popover max height. @default 360 */\n maxHeight?: number;\n}\n\nexport interface NodeGraphToolbarSlotProps {\n /**\n * Anchored corner inside the graph. Defaults to top-left.\n */\n placement?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';\n /** Distance from the edge, CSS px. @default 12 */\n margin?: number;\n /** Gap between toolbar buttons / children, CSS px. @default 4 */\n gap?: number;\n /** Optional className applied to the toolbar wrapper. */\n className?: string;\n /** Inline style override. */\n style?: React.CSSProperties;\n /** Toolbar contents — typically `<NodeGraph.FitContentButton>` etc. */\n children?: React.ReactNode;\n}\n\nexport interface NodeGraphSlotMarker {\n [NODE_GRAPH_SLOT]: NodeGraphSlotKind;\n}\n\n// ─── Props ───\n\nexport interface NodeGraphBaseProps extends Omit<\n BaseComponent,\n 'onChange' | 'onContextMenu' | 'ref'\n> {\n // ── Nodes (controlled / uncontrolled) ──\n nodes?: NodeGraphNode[];\n defaultNodes?: NodeGraphNode[];\n onNodesChange?: (nodes: NodeGraphNode[]) => void;\n\n // ── Edges (controlled / uncontrolled) ──\n edges?: NodeGraphEdge[];\n defaultEdges?: NodeGraphEdge[];\n onEdgesChange?: (edges: NodeGraphEdge[]) => void;\n\n // ── Groups (controlled / uncontrolled) ──\n groups?: NodeGraphGroup[];\n defaultGroups?: NodeGraphGroup[];\n onGroupsChange?: (groups: NodeGraphGroup[]) => void;\n\n // ── Selection (controlled / uncontrolled) ──\n selection?: NodeGraphSelection;\n defaultSelection?: NodeGraphSelection;\n onSelectionChange?: (selection: NodeGraphSelection) => void;\n\n // ── Rendering ──\n /**\n * Render the body of a node. The returned content sits inside a wrapper\n * `<div>` positioned in world space — do not set position/transform\n * yourself. Declare connection endpoints by placing `<NodeGraph.Port>`\n * children anywhere inside the returned tree; the library tracks their\n * DOM positions automatically and routes edges through them.\n */\n renderNode?: (\n node: NodeGraphNode,\n ctx: NodeGraphRenderCtx\n ) => React.ReactNode;\n /**\n * Render a label above the midpoint of an edge. Receives the edge data.\n * When omitted, `edge.label` is used as-is (if defined).\n */\n renderEdgeLabel?: (edge: NodeGraphEdge) => React.ReactNode;\n /**\n * Per-edge style hook called on every draw frame. The returned shape\n * is shallow-merged into the default edge style; any field left\n * undefined falls back to `edge.color` / theme defaults.\n *\n * The second argument carries resolved port metadata from the store\n * so consumers don't need to maintain a parallel index just to colour\n * wires by their source pin's `dataType`.\n *\n * @example\n * ```tsx\n * <NodeGraph\n * edgeStyle={(edge, ctx) => ({\n * color: TYPE_COLOR[ctx.sourcePort?.dataType ?? 'any'],\n * width: ctx.selected ? 3 : 1.5,\n * })}\n * />\n * ```\n */\n edgeStyle?: NodeGraphEdgeStyleFn;\n\n // ── Interaction ──\n /**\n * Validate a connection between two ports. Return `false` to reject the\n * drop; the preview edge is still drawn while the user is hovering, but\n * styled as invalid.\n */\n isValidConnection?: (\n source: NodeGraphPortRef,\n target: NodeGraphPortRef,\n info: NodeGraphConnectionValidationInfo\n ) => boolean;\n /**\n * Snap node positions to a grid of `snapToGrid` world units. Pass `false`\n * to disable snapping. Affects drag (mouse), not programmatic position\n * updates.\n * @default false\n */\n snapToGrid?: number | false;\n /** Fired when a connection drag starts (pointer-down on a port). */\n onConnectStart?: (info: NodeGraphConnectStartInfo) => void;\n /** Fired when a connection drag ends (drop on a port or cancellation). */\n onConnectEnd?: (info: NodeGraphConnectEndInfo) => void;\n /**\n * Fired on a context menu (right-click) over any part of the graph.\n * Includes a discriminated target so the consumer can render contextual\n * menus for nodes / edges / ports / groups / empty background.\n */\n onContextMenu?: (info: NodeGraphContextMenuInfo) => void;\n /** Fired when Delete/Backspace is pressed with a non-empty selection. */\n onDelete?: (selection: NodeGraphSelection) => void;\n /** Fired when Enter is pressed with a single focused node. */\n onActivate?: (node: NodeGraphNode) => void;\n\n // ── Viewport pass-through ──\n /**\n * Pan gesture configuration, or `false` to disable. Defaults match\n * `<Viewport>`: middle-mouse drag + space-key + left-drag.\n */\n pan?: ViewportPanConfig | false;\n /** Zoom gesture configuration, or `false` to disable. */\n zoom?: ViewportZoomConfig | false;\n /** @default 0.1 */\n minZoom?: number;\n /** @default 4 */\n maxZoom?: number;\n /**\n * Enable marquee selection rectangle. When true, dragging on empty\n * background draws a marquee that selects intersecting nodes.\n * @default true\n */\n selectionRect?: boolean;\n /**\n * Track parent size via `ResizeObserver`. Identical to `<Viewport>`'s\n * `responsive`. @default false\n */\n responsive?: boolean;\n /** Fixed height when not responsive. @default 480 */\n height?: number;\n /**\n * Fallback size used when neither `node.width`/`height` nor the measured\n * DOM size is yet available — for the brief window between mount and\n * first ResizeObserver tick, and for the imperative `fitToContent` /\n * minimap reads at first paint.\n * @default { width: 180, height: 80 }\n */\n defaultNodeSize?: { width: number; height: number };\n /**\n * Disable all interaction and dim the surface.\n * @default false\n */\n disabled?: boolean;\n\n // ── Accessibility ──\n /** @default 'Node graph' */\n ariaLabel?: string;\n\n /** Slot children: `<NodeGraph.Minimap>`, `<NodeGraph.Background>`. */\n children?: React.ReactNode;\n\n /** Imperative handle. */\n ref?: React.Ref<NodeGraphHandle>;\n}\n\nexport type NodeGraphProps = Prettify<NodeGraphBaseProps>;\n\n// ─── Subcomponent props ───\n\n/**\n * Per-node colouring for the minimap. Return `{ color }` for a flat tinted\n * rect, or add `headerColor` for a two-tone \"header strip + body\" mini-node\n * that echoes the real node's look at a glance.\n */\nexport interface NodeGraphMinimapNodeStyle {\n /** Body fill colour. Falls back to the minimap's default item colour. */\n color?: string;\n /** When set, a header strip is drawn on top in this colour (two-tone). */\n headerColor?: string;\n}\n\nexport interface NodeGraphMinimapSlotProps {\n /**\n * Anchored position inside the graph. Matches `ViewportMinimap`'s\n * `placement` (string preset or custom anchor object).\n * @default 'bottom-right'\n */\n placement?:\n | 'top-left'\n | 'top-right'\n | 'bottom-left'\n | 'bottom-right'\n | {\n top?: number | string;\n right?: number | string;\n bottom?: number | string;\n left?: number | string;\n width?: number | string;\n height?: number | string;\n };\n /** Distance from the edge for preset placements (CSS px). @default 12 */\n margin?: number;\n /** Minimap width in CSS pixels. @default 200 */\n width?: number;\n /** Optional title (rendered as `<Minimap.Title>`). */\n title?: React.ReactNode;\n /** Optional className applied to the minimap wrapper. */\n className?: string;\n /**\n * CSS colour used to highlight selected nodes inside the minimap. When\n * omitted, selected nodes use the same colour as unselected ones.\n */\n selectedColor?: string;\n /**\n * Per-node colouring. Return `{ color }` to tint the rect, or\n * `{ color, headerColor }` to draw a two-tone header/body mini-node that\n * mirrors the real node. When omitted, every node uses the default colour\n * (or `selectedColor` while selected).\n */\n nodeStyle?: (node: NodeGraphNode) => NodeGraphMinimapNodeStyle | undefined;\n}\n\nexport interface NodeGraphBackgroundSlotProps {\n /**\n * Background style. `'dots'` and `'grid'` are built in; pass `'none'`\n * for an opaque background only.\n * @default 'dots'\n */\n variant?: 'dots' | 'grid' | 'none';\n /**\n * Grid spacing in world units. The pattern repeats at this interval\n * regardless of zoom.\n * @default 24\n */\n gap?: number;\n /** Optional dot/line colour. Defaults to theme `border.default`. */\n color?: string;\n /** Optional background colour. Defaults to theme `background.primary`. */\n background?: string;\n}\n"],"names":[],"mappings":"AAwUA;AAEA;;;;AAIG;AACI,MAAM,eAAe,GAAkB,MAAM,CAAC,GAAG,CAAC,qBAAqB;;;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NodeGraphContext.js","sources":["
|
|
1
|
+
{"version":3,"file":"NodeGraphContext.js","sources":["src/components/editor/NodeGraph/NodeGraphContext.ts"],"sourcesContent":["'use client';\n\nimport { createContext, useContext, useSyncExternalStore } from 'react';\n\nimport type { NodeGraphSelection } from './NodeGraph.types';\nimport type {\n NodeGraphDataState,\n NodeGraphHoverState,\n NodeGraphInteractionState,\n NodeGraphStore,\n} from './NodeGraphStore';\n\nexport const NodeGraphStoreContext =\n /*#__PURE__*/ createContext<NodeGraphStore | null>(null);\n\nfunction useStoreInternal(): NodeGraphStore {\n const store = useContext(NodeGraphStoreContext);\n if (!store) {\n throw new Error(\n 'useNodeGraph* hooks must be called inside a <NodeGraph> component'\n );\n }\n return store;\n}\n\n/**\n * Access the underlying `NodeGraphStore` instance — escape hatch for\n * subscribing to custom slice combinations or for imperative reads from\n * non-React code paths (gesture handlers, canvas draw frames).\n */\nexport function useNodeGraphStore(): NodeGraphStore {\n return useStoreInternal();\n}\n\n/**\n * Subscribe to the data slice — re-renders when the graph contents\n * (`nodes`, `edges`, `groups`, default size) change.\n */\nexport function useNodeGraphData(): NodeGraphDataState {\n const store = useStoreInternal();\n return useSyncExternalStore(store.subscribeData, store.getData);\n}\n\n/**\n * Subscribe to the selection slice — re-renders when the selection changes.\n */\nexport function useNodeGraphSelection(): NodeGraphSelection {\n const store = useStoreInternal();\n return useSyncExternalStore(store.subscribeSelection, store.getSelection);\n}\n\n/**\n * Subscribe to the interaction slice — re-renders on gesture lifecycle\n * (drag, connect, marquee) and on per-frame deltas during a drag.\n */\nexport function useNodeGraphInteraction(): NodeGraphInteractionState {\n const store = useStoreInternal();\n return useSyncExternalStore(store.subscribeInteraction, store.getInteraction);\n}\n\n/**\n * Subscribe to the hover slice — re-renders when the hovered node / edge /\n * port changes.\n */\nexport function useNodeGraphHover(): NodeGraphHoverState {\n const store = useStoreInternal();\n return useSyncExternalStore(store.subscribeHover, store.getHover);\n}\n"],"names":[],"mappings":";;;;AAaE;AAEF;AACE;;AAEE;;AAIF;AACF;AAEA;;;;AAIG;;;AAGH;AAEA;;;AAGG;;AAED;;AAEF;AAEA;;AAEG;;AAED;;AAEF;AAEA;;;AAGG;;AAED;;AAEF;AAEA;;;AAGG;;AAED;;AAEF;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NodeGraphEdgeLabels.js","sources":["
|
|
1
|
+
{"version":3,"file":"NodeGraphEdgeLabels.js","sources":["src/components/editor/NodeGraph/NodeGraphEdgeLabels.tsx"],"sourcesContent":["'use client';\n\nimport React, { useCallback, useSyncExternalStore } from 'react';\n\nimport { useNodeGraphStore } from './NodeGraphContext';\nimport { resolvePortRef } from './nodeGraphMath';\nimport { useStoreSlice } from './useStoreSlice';\n\nimport type { NodeGraphEdge } from './NodeGraph.types';\nimport type { NodeGraphInteractionState } from './NodeGraphStore';\nimport type { Point2D } from '@/components/primitives/canvas/canvas.types';\n\ninterface RenderEdgeLabelRef {\n current: ((edge: NodeGraphEdge) => React.ReactNode) | undefined;\n}\n\n/** Live drag deltas for an edge's two endpoints during the current gesture. */\ninterface EdgeEndpointDeltas {\n src: Point2D | null;\n tgt: Point2D | null;\n}\n\n// Shared sentinel for \"this edge isn't affected by the current gesture\" —\n// one reference so the equality check below short-circuits cleanly.\nconst NO_EDGE_DELTAS: EdgeEndpointDeltas = { src: null, tgt: null };\n\nfunction pointEqualNullable(a: Point2D | null, b: Point2D | null): boolean {\n if (a === b) return true;\n if (a === null || b === null) return false;\n return a.x === b.x && a.y === b.y;\n}\n\nfunction edgeDeltasEqual(\n a: EdgeEndpointDeltas,\n b: EdgeEndpointDeltas\n): boolean {\n return pointEqualNullable(a.src, b.src) && pointEqualNullable(a.tgt, b.tgt);\n}\n\n/**\n * Live drag delta for an edge's endpoints, or {@link NO_EDGE_DELTAS} when\n * neither endpoint's node is part of the current drag. Mirrors the per-node\n * selector in `NodeGraphNode` so a label only updates when an endpoint it\n * actually owns is moving.\n */\nfunction selectEdgeDeltas(\n interaction: NodeGraphInteractionState,\n sourceNode: string,\n targetNode: string\n): EdgeEndpointDeltas {\n let ids: ReadonlySet<string> | null = null;\n let delta: Point2D | null = null;\n if (interaction.kind === 'drag-nodes') {\n ids = interaction.nodeIds;\n delta = interaction.delta;\n } else if (interaction.kind === 'drag-groups') {\n ids = interaction.containedNodeIds;\n delta = interaction.delta;\n }\n if (!ids || !delta) return NO_EDGE_DELTAS;\n const src = ids.has(sourceNode) ? delta : null;\n const tgt = ids.has(targetNode) ? delta : null;\n if (src === null && tgt === null) return NO_EDGE_DELTAS;\n return { src, tgt };\n}\n\n/**\n * Mounts one `<EdgeLabel>` per edge as a world-space sibling inside\n * `<ViewportWorld>`. Each label subscribes to the interaction slice on\n * its own so a drag of an unrelated node doesn't re-render every label\n * in the graph — only the labels whose endpoints actually move.\n *\n * One subscriber per edge: each `EdgeLabel` subscribes to the `interaction`\n * slice through `useStoreSlice` with an endpoint-delta selector + equality,\n * so it re-renders only when an endpoint it actually owns moves. Labels on\n * edges untouched by the current gesture short-circuit on the equality check\n * and skip the per-frame re-render entirely.\n */\nexport function EdgeLabelsLayer({\n renderEdgeLabelRef,\n hasRenderEdgeLabel,\n}: {\n renderEdgeLabelRef: RenderEdgeLabelRef;\n /**\n * Whether the consumer supplied a `renderEdgeLabel` callback. When `false`,\n * edges without an explicit `edge.label` prop are skipped — no `EdgeLabel`\n * subscriber is mounted for them. This avoids the cost of one interaction-\n * slice subscriber per edge in graphs that don't use labels at all (the\n * benchmark, simple recipes, etc.), which is otherwise pure waste — the\n * subscriber fires on every drag tick and the body returns `null`.\n */\n hasRenderEdgeLabel: boolean;\n}): React.ReactElement | null {\n const store = useNodeGraphStore();\n // Subscribe to data + port positions at this level so we know which\n // edges exist. Each `EdgeLabel` below subscribes to interaction on its\n // own for live drag tracking.\n const data = useSyncExternalStore(store.subscribeData, store.getData);\n useSyncExternalStore(\n store.subscribePortPositions,\n store.getPortPositionsVersion\n );\n\n return (\n <>\n {data.edges.map(edge => {\n // Skip mounting an `EdgeLabel` subscriber when there's nothing it\n // could ever render. When `renderEdgeLabel` is provided the\n // consumer controls visibility per edge from inside their callback\n // (and may return `null`), so we still have to mount — but if no\n // callback exists, the only thing the label could show is\n // `edge.label`, and a `null` value there means there's nothing to\n // draw and no reason to pay for a per-edge store subscription.\n if (!hasRenderEdgeLabel && edge.label == null) return null;\n return (\n <EdgeLabel\n key={edge.id}\n edge={edge}\n renderEdgeLabelRef={renderEdgeLabelRef}\n />\n );\n })}\n </>\n );\n}\n\nfunction EdgeLabel({\n edge,\n renderEdgeLabelRef,\n}: {\n edge: NodeGraphEdge;\n renderEdgeLabelRef: RenderEdgeLabelRef;\n}): React.ReactElement | null {\n const store = useNodeGraphStore();\n const sourceNode = edge.source.node;\n const targetNode = edge.target.node;\n // Subscribe to interaction changes affecting either endpoint via the\n // per-node channel — a drag of an unrelated node never wakes this\n // subscriber, even before the selector runs. When source and target\n // sit on the same node, a single subscription is enough (deduplicates\n // the notification).\n const subscribeEndpoints = useCallback(\n (cb: () => void): (() => void) => {\n const unsubSource = store.subscribeNodeInteraction(sourceNode, cb);\n if (sourceNode === targetNode) return unsubSource;\n const unsubTarget = store.subscribeNodeInteraction(targetNode, cb);\n return () => {\n unsubSource();\n unsubTarget();\n };\n },\n [store, sourceNode, targetNode]\n );\n // Selector still reads the global snapshot to compute per-endpoint\n // deltas — the subscription change above only narrows *when* this\n // subscriber gets woken, not *what* it sees.\n const { src: srcDelta, tgt: tgtDelta } = useStoreSlice<\n NodeGraphInteractionState,\n EdgeEndpointDeltas\n >(\n subscribeEndpoints,\n store.getInteraction,\n interaction => selectEdgeDeltas(interaction, sourceNode, targetNode),\n edgeDeltasEqual\n );\n // Re-render when port positions update too (initial measure + body\n // re-layout shift the anchor).\n useSyncExternalStore(\n store.subscribePortPositions,\n store.getPortPositionsVersion\n );\n\n const src = resolvePortRef(\n edge.source,\n store.getNodeById,\n store.getPortPosition\n );\n const tgt = resolvePortRef(\n edge.target,\n store.getNodeById,\n store.getPortPosition\n );\n if (!src || !tgt) return null;\n\n const sx = src.position.x + (srcDelta?.x ?? 0);\n const sy = src.position.y + (srcDelta?.y ?? 0);\n const tx = tgt.position.x + (tgtDelta?.x ?? 0);\n const ty = tgt.position.y + (tgtDelta?.y ?? 0);\n const midX = (sx + tx) / 2;\n const midY = (sy + ty) / 2;\n // The render callback lives in a `useLatest` ref so swapping it doesn't\n // re-render every edge label; reading the current value here is deliberate.\n // Labels refresh on the next data / interaction tick, which is the only\n // moment their content is consumed anyway.\n // eslint-disable-next-line react-hooks/refs -- intentional latest-callback read\n const render = renderEdgeLabelRef.current;\n const content = render ? render(edge) : (edge.label ?? null);\n if (content == null || content === false) return null;\n\n return (\n <div\n style={{\n position: 'absolute',\n left: 0,\n top: 0,\n // World-space position via `transform` so each frame's update is\n // a GPU-friendly transform mutation (no CSS layout). The\n // -50%/-50% piece centres the label on its midpoint.\n transform: `translate3d(${midX}px, ${midY}px, 0) translate(-50%, -50%)`,\n pointerEvents: 'auto',\n willChange: 'transform',\n }}\n >\n {content}\n </div>\n );\n}\n"],"names":[],"mappings":";;;;;;;AAsBA;AACA;AACA;AAEA;;AACe;AACb;AAA8B;AAC9B;AACF;AAEA;;AAKA;AAEA;;;;;AAKG;AACH;;;AAOE;AACE;AACA;;AACK;AACL;AACA;;AAEF;AAAoB;AACpB;AACA;AACA;AAAkC;AAClC;AACF;AAEA;;;;;;;;;;;AAWG;;AAgBD;;;;AAIA;;;;;;;;;;AAgBM;AAA+C;AAC/C;;AAUR;AAEA;AAOE;AACA;AACA;;;;;;AAMA;;;AAGmC;;AAE/B;AACE;AACA;AACF;;;;;AAOJ;;;;AAgBA;AAKA;AAKA;AAAkB;AAElB;AACA;AACA;AACA;;;;;;;;AAQA;;AAEA;AAA0C;;AAKpC;AACA;AACA;;;;AAIA;AACA;AACA;;AAMR;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NodeGraphGroup.js","sources":["../../../../../../src/components/editor/NodeGraph/NodeGraphGroup.tsx"],"sourcesContent":[null],"names":[],"mappings":";;;;;;;;;;;;;AAyDA;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAGF;;;;;;;;;;AAcA;;;;;;;;;;AAWA;AAuBA;;AAIe;AACb;AAA8B;AAC9B;AAAuB;AACvB;AAA6B;AAC7B;;;AAGA;AACE;;;;;;;;AAUF;AACF;AAEA;AAIE;;AAEI;;;;;AAKJ;;AAEI;;;;;;;AAOJ;AACF;AAEA;;;AAKE;;;;AAII;AACA;;;AAGJ;AACF;AAEA;;;;;;;;;;;;;;AAcG;AACH;AASE;AACA;;;AAeA;AACA;;;;;;;;AASA;AACA;;;;AAKI;AACA;;AAEJ;AAEA;AACE;;;;AAGA;;AAGF;;AAEE;AACF;AAEA;;;;;AAMI;;AAEF;AAIF;AAEI;;AAEE;;AACK;;AAEL;;;;AAIJ;;;;;;;AAUF;AAEI;AACF;;;AAMF;AAEA;;;;;AAQK;AAIC;AACF;AAEE;;;;;;;AAWA;AAUM;;AAgDN;;AAMQ;;AAKA;AACF;AAGN;AAGR;AAEO;AACP;;"}
|
|
1
|
+
{"version":3,"file":"NodeGraphGroup.js","sources":["src/components/editor/NodeGraph/NodeGraphGroup.tsx"],"sourcesContent":["'use client';\n\nimport React, { useCallback, useEffect, useRef, useState } from 'react';\n\nimport { ColorPicker } from '@/components/controls/ColorPicker/ColorPicker';\nimport { Popover } from '@/components/primitives/Popover/Popover';\nimport { PopoverContent } from '@/components/primitives/Popover/PopoverContent';\nimport { PopoverTrigger } from '@/components/primitives/Popover/PopoverTrigger';\nimport { cx } from '@/utils/cx';\n\nimport {\n groupColorSwatchStyle,\n groupLabelBarStyle,\n groupLabelInputStyle,\n groupLabelStyle,\n groupOverlayRecipe,\n groupResizeHandleStyle,\n} from './NodeGraph.css';\nimport { useNodeGraphStore } from './NodeGraphContext';\nimport { applyGroupResize } from './nodeGraphMath';\nimport { useStoreSlice } from './useStoreSlice';\n\nimport type { NodeGraphGroup } from './NodeGraph.types';\nimport type {\n NodeGraphGroupResizeHandle,\n NodeGraphInteractionState,\n} from './NodeGraphStore';\nimport type { WorldRect } from '@/components/primitives/viewport';\n\ninterface NodeGraphGroupViewProps {\n group: NodeGraphGroup;\n /** Pointer-down on the group body — used for drag / click selection. */\n onBodyPointerDown: (\n event: React.PointerEvent<HTMLDivElement>,\n group: NodeGraphGroup\n ) => void;\n /** Pointer-up on the group body — completes click-to-select if no drag. */\n onBodyPointerUp: (\n event: React.PointerEvent<HTMLDivElement>,\n group: NodeGraphGroup\n ) => void;\n /** Pointer-down on a resize handle. */\n onHandlePointerDown: (\n event: React.PointerEvent<HTMLDivElement>,\n group: NodeGraphGroup,\n handle: NodeGraphGroupResizeHandle\n ) => void;\n onBodyContextMenu: (event: React.MouseEvent<HTMLDivElement>) => void;\n /**\n * Commit a label or colour edit. The library calls this when the user\n * finishes editing the label (Enter / blur) or picks a new colour from\n * the swatch. Parent forwards to `setGroups` / `onGroupsChange`.\n */\n onLabelChange: (group: NodeGraphGroup, nextLabel: string) => void;\n onColorChange: (group: NodeGraphGroup, nextColor: string) => void;\n}\n\nconst RESIZE_CURSORS: Record<NodeGraphGroupResizeHandle, string> = {\n nw: 'nwse-resize',\n n: 'ns-resize',\n ne: 'nesw-resize',\n e: 'ew-resize',\n se: 'nwse-resize',\n s: 'ns-resize',\n sw: 'nesw-resize',\n w: 'ew-resize',\n};\n\nconst HANDLE_POSITIONS: Record<\n NodeGraphGroupResizeHandle,\n { left?: string; right?: string; top?: string; bottom?: string }\n> = {\n nw: { left: '-5px', top: '-5px' },\n n: { left: 'calc(50% - 5px)', top: '-5px' },\n ne: { right: '-5px', top: '-5px' },\n e: { right: '-5px', top: 'calc(50% - 5px)' },\n se: { right: '-5px', bottom: '-5px' },\n s: { left: 'calc(50% - 5px)', bottom: '-5px' },\n sw: { left: '-5px', bottom: '-5px' },\n w: { left: '-5px', top: 'calc(50% - 5px)' },\n};\n\nconst ALL_HANDLES: NodeGraphGroupResizeHandle[] = [\n 'nw',\n 'n',\n 'ne',\n 'e',\n 'se',\n 's',\n 'sw',\n 'w',\n];\n\nconst DEFAULT_PICKER_COLOR = '#5d8dff';\n\n/**\n * Per-group slice of the interaction state — `null` for groups that\n * aren't currently being dragged or resized. Two distinct `null`\n * references would defeat the equality short-circuit, so we share the\n * module-level constant below.\n */\ntype GroupGestureState =\n | {\n kind: 'drag';\n delta: { x: number; y: number };\n blocked: boolean;\n }\n | {\n kind: 'resize';\n handle: NodeGraphGroupResizeHandle;\n startBounds: WorldRect;\n delta: { x: number; y: number };\n blocked: boolean;\n }\n | null;\n\nfunction gestureStateEqual(\n a: GroupGestureState,\n b: GroupGestureState\n): boolean {\n if (a === b) return true;\n if (a === null || b === null) return false;\n if (a.kind !== b.kind) return false;\n if (a.blocked !== b.blocked) return false;\n if (a.kind === 'drag' && b.kind === 'drag') {\n return a.delta.x === b.delta.x && a.delta.y === b.delta.y;\n }\n if (a.kind === 'resize' && b.kind === 'resize') {\n return (\n a.handle === b.handle &&\n a.delta.x === b.delta.x &&\n a.delta.y === b.delta.y &&\n a.startBounds.x === b.startBounds.x &&\n a.startBounds.y === b.startBounds.y &&\n a.startBounds.width === b.startBounds.width &&\n a.startBounds.height === b.startBounds.height\n );\n }\n return false;\n}\n\nfunction selectGroupGesture(\n interaction: NodeGraphInteractionState,\n groupId: string\n): GroupGestureState {\n if (interaction.kind === 'drag-groups' && interaction.groupIds.has(groupId)) {\n return {\n kind: 'drag',\n delta: interaction.delta,\n blocked: interaction.blocked,\n };\n }\n if (interaction.kind === 'resize-group' && interaction.groupId === groupId) {\n return {\n kind: 'resize',\n handle: interaction.handle,\n startBounds: interaction.startBounds,\n delta: interaction.delta,\n blocked: interaction.blocked,\n };\n }\n return null;\n}\n\nfunction computeLiveBounds(\n group: NodeGraphGroup,\n gesture: GroupGestureState\n): WorldRect {\n if (gesture === null) return group.bounds;\n if (gesture.kind === 'drag') {\n return {\n x: group.bounds.x + gesture.delta.x,\n y: group.bounds.y + gesture.delta.y,\n width: group.bounds.width,\n height: group.bounds.height,\n };\n }\n return applyGroupResize(gesture.startBounds, gesture.handle, gesture.delta);\n}\n\n/**\n * Render a single interactive group overlay positioned in world space.\n * The canvas layer draws the visual fill underneath — this HTML element\n * handles selection, drag-to-move, corner/edge resize, label editing,\n * and the colour swatch.\n *\n * Uses per-id selectors so a group only re-renders when *its* selection\n * or gesture state changes — drag deltas of other groups never reach the\n * subscriber.\n *\n * Wrapped in `React.memo` for the same reason as\n * {@link NodeGraphNodeView}: a parent render caused by an unrelated\n * controlled-state update would otherwise iterate every group. Handlers\n * passed from `NodeGraphImpl` are all `useCallback`-stable.\n */\nfunction NodeGraphGroupViewImpl({\n group,\n onBodyPointerDown,\n onBodyPointerUp,\n onHandlePointerDown,\n onBodyContextMenu,\n onLabelChange,\n onColorChange,\n}: NodeGraphGroupViewProps): React.ReactElement {\n const store = useNodeGraphStore();\n const groupId = group.id;\n\n const selected = useStoreSlice(\n store.subscribeSelection,\n store.getSelection,\n sel => sel.groups.includes(groupId)\n );\n\n const gesture = useStoreSlice<NodeGraphInteractionState, GroupGestureState>(\n store.subscribeInteraction,\n store.getInteraction,\n interaction => selectGroupGesture(interaction, groupId),\n gestureStateEqual\n );\n\n const dragging = gesture?.kind === 'drag';\n const blocked = gesture?.blocked ?? false;\n const bounds = computeLiveBounds(group, gesture);\n\n // ── Inline label edit ──\n // `draft` is initialised from `group.label` on mount and re-synced by\n // `cancelLabel` / `commitLabel` / `handleLabelDoubleClick` on every\n // transition out of / into edit mode. Mirroring `group.label` into\n // state via a side effect would be the classic anti-pattern.\n const [editing, setEditing] = useState(false);\n const [draft, setDraft] = useState(group.label ?? '');\n const inputRef = useRef<HTMLInputElement>(null);\n\n useEffect(() => {\n if (editing) {\n // Defer focus until the input has been mounted.\n inputRef.current?.focus();\n inputRef.current?.select();\n }\n }, [editing]);\n\n const commitLabel = useCallback((): void => {\n const trimmed = draft.trim();\n setEditing(false);\n if (trimmed === (group.label ?? '')) return;\n onLabelChange(group, trimmed);\n }, [draft, group, onLabelChange]);\n\n const cancelLabel = useCallback((): void => {\n setEditing(false);\n setDraft(group.label ?? '');\n }, [group.label]);\n\n const handleLabelDoubleClick = useCallback(\n (event: React.MouseEvent<HTMLSpanElement>): void => {\n event.stopPropagation();\n // Re-sync from the prop on open — covers the case where `group.label`\n // changed externally while we weren't editing, so the input opens\n // with the current value rather than a stale draft.\n setDraft(group.label ?? '');\n setEditing(true);\n },\n [group.label]\n );\n\n const handleInputKeyDown = useCallback(\n (event: React.KeyboardEvent<HTMLInputElement>): void => {\n if (event.key === 'Enter') {\n event.preventDefault();\n commitLabel();\n } else if (event.key === 'Escape') {\n event.preventDefault();\n cancelLabel();\n }\n // Stop arrow keys from triggering the graph's nudge handler.\n event.stopPropagation();\n },\n [commitLabel, cancelLabel]\n );\n\n // ── Colour picker ──\n //\n // The swatch is a `<button>` that triggers the entangle-ui `<ColorPicker>`\n // popover. The picker has `showAlpha` enabled so it returns proper\n // `rgba(...)` strings when the user lowers opacity — the canvas\n // `drawGroups` fill plumbs that straight through with no transform.\n const handleColorChange = useCallback(\n (next: string): void => {\n onColorChange(group, next);\n },\n [group, onColorChange]\n );\n\n // Picker value falls back to a sensible default when the group hasn't\n // been coloured yet (the library doesn't impose a default).\n const pickerValue = group.color ?? DEFAULT_PICKER_COLOR;\n\n return (\n <div\n className={cx(groupOverlayRecipe({ selected, dragging, blocked }))}\n style={{\n left: bounds.x,\n top: bounds.y,\n width: bounds.width,\n height: bounds.height,\n }}\n data-group-id={group.id}\n data-blocked={blocked || undefined}\n onPointerDown={e => {\n onBodyPointerDown(e, group);\n }}\n onPointerUp={e => {\n onBodyPointerUp(e, group);\n }}\n onContextMenu={onBodyContextMenu}\n >\n <div\n className={groupLabelBarStyle}\n // Don't let pointerdown on the label bar (label text, color swatch,\n // input) fall through to the body drag handler — the label bar is\n // an interactive control row, not a draggable handle.\n onPointerDown={e => {\n e.stopPropagation();\n }}\n >\n {editing ? (\n <input\n ref={inputRef}\n className={groupLabelInputStyle}\n value={draft}\n placeholder=\"Group name\"\n spellCheck={false}\n onChange={e => {\n setDraft(e.target.value);\n }}\n onBlur={commitLabel}\n onKeyDown={handleInputKeyDown}\n />\n ) : (\n <span\n className={groupLabelStyle}\n onDoubleClick={handleLabelDoubleClick}\n title=\"Double-click to rename\"\n >\n {group.label?.trim() ? group.label : 'Group'}\n </span>\n )}\n <Popover placement=\"bottom-end\">\n <PopoverTrigger>\n <button\n type=\"button\"\n aria-label=\"Change group colour\"\n title=\"Change group colour\"\n className={groupColorSwatchStyle}\n style={{ background: group.color ?? DEFAULT_PICKER_COLOR }}\n />\n </PopoverTrigger>\n <PopoverContent padding=\"md\">\n {/* Uncontrolled picker — `useColor` only mutates its internal\n state when `value` is undefined, so passing `defaultValue`\n lets the user actually drag the sliders / click the\n palette. PopoverContent unmounts on close, so each open\n gets a fresh mount that picks up the current\n `group.color` via `defaultValue`. `showAlpha` lets the\n picker emit translucent fills directly — we ask for\n `format=\"rgba\"` so the alpha channel is actually included\n in the output string (`\"rgb\"` strips it). Survives\n round-trip through the canvas group fill with no\n consumer-side transform. */}\n <ColorPicker\n inline\n showAlpha\n defaultValue={pickerValue}\n format=\"rgba\"\n palette=\"material\"\n onChangeComplete={handleColorChange}\n />\n </PopoverContent>\n </Popover>\n </div>\n {selected\n ? ALL_HANDLES.map(handle => (\n <div\n key={handle}\n className={groupResizeHandleStyle}\n style={{\n ...HANDLE_POSITIONS[handle],\n cursor: RESIZE_CURSORS[handle],\n }}\n data-group-id={group.id}\n data-group-handle={handle}\n onPointerDown={e => {\n onHandlePointerDown(e, group, handle);\n }}\n />\n ))\n : null}\n </div>\n );\n}\n\nexport const NodeGraphGroupView = React.memo(NodeGraphGroupViewImpl);\nNodeGraphGroupView.displayName = 'NodeGraphGroupView';\n"],"names":[],"mappings":";;;;;;;;;;;;;AAyDA;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAGF;;;;;;;;;;AAcA;;;;;;;;;;AAWA;AAuBA;;AAIe;AACb;AAA8B;AAC9B;AAAuB;AACvB;AAA6B;AAC7B;;;AAGA;AACE;;;;;;;;AAUF;AACF;AAEA;AAIE;;AAEI;;;;;AAKJ;;AAEI;;;;;;;AAOJ;AACF;AAEA;;;AAKE;;;;AAII;AACA;;;AAGJ;AACF;AAEA;;;;;;;;;;;;;;AAcG;AACH;AASE;AACA;;;AAeA;AACA;;;;;;;;AASA;AACA;;;;AAKI;AACA;;AAEJ;AAEA;AACE;;;;AAGA;;AAGF;;AAEE;AACF;AAEA;;;;;AAMI;;AAEF;AAIF;AAEI;;AAEE;;AACK;;AAEL;;;;AAIJ;;;;;;;AAUF;AAEI;AACF;;;AAMF;AAEA;;;;;AAQK;AAIC;AACF;AAEE;;;;;;;AAWA;AAUM;;AAgDN;;AAMQ;;AAKA;AACF;AAGN;AAGR;AAEO;AACP;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NodeGraphMinimap.js","sources":["
|
|
1
|
+
{"version":3,"file":"NodeGraphMinimap.js","sources":["src/components/editor/NodeGraph/NodeGraphMinimap.tsx"],"sourcesContent":["'use client';\n\nimport React, { useMemo, useSyncExternalStore } from 'react';\n\nimport { Minimap } from '@/components/editor/Minimap';\nimport { ViewportMinimap } from '@/components/editor/Minimap/ViewportMinimap';\nimport { cx } from '@/utils/cx';\n\nimport { minimapSlotStyle } from './NodeGraph.css';\nimport {\n useNodeGraphData,\n useNodeGraphSelection,\n useNodeGraphStore,\n} from './NodeGraphContext';\nimport { computeNodesBounds, getNodeBox } from './nodeGraphMath';\n\nimport type { NodeGraphMinimapSlotProps } from './NodeGraph.types';\nimport type {\n MinimapCustomItem,\n MinimapDrawInfo,\n MinimapItem,\n MinimapRectItem,\n} from '@/components/editor/Minimap';\nimport type { WorldRect } from '@/components/primitives/viewport';\n\n/**\n * Live renderer for `<NodeGraph.Minimap>`. Mounted inside the `<Viewport>`\n * subtree by the main `NodeGraph` component when the user supplies a\n * minimap slot child.\n */\n/**\n * Draw a two-tone \"header strip + body\" mini-node into the minimap canvas,\n * converting the node's world rect to minimap pixels via the draw helpers.\n */\nfunction drawMiniNode(\n ctx: CanvasRenderingContext2D,\n info: MinimapDrawInfo,\n rect: WorldRect,\n bodyColor: string | undefined,\n headerColor: string\n): void {\n const tl = info.worldToMinimap({ x: rect.x, y: rect.y });\n const br = info.worldToMinimap({\n x: rect.x + rect.width,\n y: rect.y + rect.height,\n });\n const x = Math.min(tl.x, br.x);\n const y = Math.min(tl.y, br.y);\n const w = Math.abs(br.x - tl.x);\n const h = Math.abs(br.y - tl.y);\n ctx.fillStyle = bodyColor ?? 'rgba(180, 190, 210, 0.5)';\n ctx.fillRect(x, y, w, h);\n const headerH = Math.min(h, Math.max(2, h * 0.32));\n ctx.fillStyle = headerColor;\n ctx.fillRect(x, y, w, headerH);\n}\n\nexport function NodeGraphMinimapInner({\n placement = 'bottom-right',\n margin = 12,\n width = 200,\n title,\n className,\n selectedColor,\n nodeStyle,\n}: NodeGraphMinimapSlotProps): React.ReactElement {\n const data = useNodeGraphData();\n const selection = useNodeGraphSelection();\n const store = useNodeGraphStore();\n // Subscribe to measured-size updates so the minimap rect for each node\n // grows/shrinks as its DOM body changes height (auto-sized nodes). The\n // version snapshot doubles as a `useMemo` dep below, so the item /\n // bounds caches invalidate on every real size change.\n const measuredSizesVersion = useSyncExternalStore(\n store.subscribeMeasuredSizes,\n store.getMeasuredSizesVersion\n );\n\n const items = useMemo<MinimapItem[]>(() => {\n return data.nodes.map<MinimapItem>(node => {\n const box = getNodeBox(\n node,\n store.getMeasuredSize(node.id),\n data.defaultNodeSize\n );\n const isSelected = selection.nodes.includes(node.id);\n const style = nodeStyle?.(node);\n // Selection tint wins over the per-node body colour so the highlight\n // stays visible.\n const bodyColor =\n isSelected && selectedColor ? selectedColor : style?.color;\n const headerColor = style?.headerColor;\n\n if (headerColor) {\n const rect: WorldRect = {\n x: box.x,\n y: box.y,\n width: box.width,\n height: box.height,\n };\n const item: MinimapCustomItem = {\n id: node.id,\n type: 'custom',\n bounds: rect,\n draw: (ctx, info) => {\n drawMiniNode(ctx, info, rect, bodyColor, headerColor);\n },\n };\n return item;\n }\n\n const item: MinimapRectItem = {\n id: node.id,\n type: 'rect',\n x: box.x,\n y: box.y,\n width: box.width,\n height: box.height,\n };\n if (bodyColor) item.color = bodyColor;\n return item;\n });\n // `measuredSizesVersion` is an intentional invalidation key: the body reads\n // `store.getMeasuredSize(...)` — mutable state React can't track — so the\n // memo must recompute whenever a node's measured size changes. The linter\n // flags it as unused because it isn't referenced directly.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [\n data.nodes,\n data.defaultNodeSize,\n selection.nodes,\n selectedColor,\n nodeStyle,\n store,\n measuredSizesVersion,\n ]);\n\n const worldBounds = useMemo(() => {\n const bounds = computeNodesBounds(\n data.nodes,\n store.getMeasuredSize,\n data.defaultNodeSize\n );\n if (bounds.width === 0 && bounds.height === 0) {\n // Provide a sensible default world rect so the minimap renders even\n // when the graph is empty.\n return { x: -200, y: -150, width: 400, height: 300 };\n }\n // Add 10% padding around content for nicer framing.\n const padX = bounds.width * 0.1;\n const padY = bounds.height * 0.1;\n return {\n x: bounds.x - padX,\n y: bounds.y - padY,\n width: bounds.width + padX * 2,\n height: bounds.height + padY * 2,\n };\n // See note above: `measuredSizesVersion` keys the cache off mutable measured\n // sizes that `computeNodesBounds` reads via `store.getMeasuredSize`.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [data.nodes, data.defaultNodeSize, store, measuredSizesVersion]);\n\n return (\n <ViewportMinimap\n placement={placement}\n margin={margin}\n width={width}\n items={items}\n worldBounds={worldBounds}\n className={cx(minimapSlotStyle, className)}\n >\n {title ? <Minimap.Title>{title}</Minimap.Title> : null}\n </ViewportMinimap>\n );\n}\n\nexport { NodeGraphMinimap, NodeGraphBackground } from './NodeGraphSlots';\n"],"names":[],"mappings":";;;;;;;;;;;AAyBA;;;;AAIG;AACH;;;AAGG;AACH;;AAQE;AACE;AACA;AACD;AACD;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEF;AAEM;AASJ;AACA;AACA;;;;;AAKA;AAKA;;AAEI;AAKA;AACA;;;AAGA;AAEA;;AAGE;;;;;;AAMA;;AAEE;AACA;AACA;;;;AAIF;;AAGF;;AAEE;;;;;;AAMF;AAAe;AACf;AACF;;;;;;AAMF;AACE;AACA;AACA;;;;;AAKD;AAED;AACE;AAKA;;;AAGE;;;AAGF;AACA;;AAEE;AACA;AACA;AACA;;;;;AAKJ;;AAcF;;"}
|
|
@@ -7,7 +7,7 @@ import '../../../utils/devWarn.js';
|
|
|
7
7
|
import { useResizeObserver } from '../../../hooks/useResizeObserver/useResizeObserver.js';
|
|
8
8
|
import '../../../hooks/useBreakpoint/useBreakpoint.js';
|
|
9
9
|
import './../../../assets/src/theme/lightTheme.css.ts.vanilla-Ct6f9Iym.css';
|
|
10
|
-
import '
|
|
10
|
+
import '@vanilla-extract/css';
|
|
11
11
|
import './../../../assets/src/theme/darkTheme.css.ts.vanilla-BHZ3ecRn.css';
|
|
12
12
|
import './../../../assets/src/theme/globalScrollbars.css.ts.vanilla-BAJwnUEJ.css';
|
|
13
13
|
import '../../../hooks/useNavigationHistory/useNavigationHistory.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NodeGraphNode.js","sources":["
|
|
1
|
+
{"version":3,"file":"NodeGraphNode.js","sources":["src/components/editor/NodeGraph/NodeGraphNode.tsx"],"sourcesContent":["'use client';\n\nimport { assignInlineVars } from '@vanilla-extract/dynamic';\nimport React, { useCallback, useDeferredValue, useMemo, useRef } from 'react';\n\nimport { useViewportStore } from '@/components/primitives/viewport/ViewportContext';\nimport { useResizeObserver } from '@/hooks';\nimport { cx } from '@/utils/cx';\n\nimport {\n defaultNodeBodyRecipe,\n nodeHeightVar,\n nodeWidthVar,\n nodeWrapperRecipe,\n} from './NodeGraph.css';\nimport { useNodeGraphStore } from './NodeGraphContext';\nimport { NodeGraphNodeContext } from './NodeGraphNodeContext';\nimport { useStoreSlice } from './useStoreSlice';\n\nimport type { NodeGraphNode, NodeGraphRenderCtx } from './NodeGraph.types';\nimport type { NodeGraphNodeContextValue } from './NodeGraphNodeContext';\nimport type { Point2D } from '@/components/primitives/canvas/canvas.types';\n\ninterface NodeGraphNodeViewProps {\n node: NodeGraphNode;\n renderNode?: (\n node: NodeGraphNode,\n ctx: NodeGraphRenderCtx\n ) => React.ReactNode;\n /** Pointer-down on the node body (drag / select). */\n onBodyPointerDown: (\n event: React.PointerEvent<HTMLDivElement>,\n node: NodeGraphNode\n ) => void;\n /** Pointer-up on the node body (used to detect plain clicks for selection). */\n onBodyPointerUp: (\n event: React.PointerEvent<HTMLDivElement>,\n node: NodeGraphNode\n ) => void;\n /** Pointer-down on a port (starts a connection drag). */\n onPortPointerDown: (\n event: React.PointerEvent<HTMLElement>,\n nodeId: string,\n portId: string\n ) => void;\n /** Native context menu (right-click) — dispatched up to the main component. */\n onBodyContextMenu: (\n event: React.MouseEvent<HTMLDivElement>,\n node: NodeGraphNode\n ) => void;\n}\n\nfunction DefaultNodeBody({\n node,\n selected,\n}: {\n node: NodeGraphNode;\n selected: boolean;\n}): React.ReactElement {\n let label = node.id;\n if (\n node.data !== null &&\n typeof node.data === 'object' &&\n 'label' in node.data\n ) {\n const candidate = (node.data as { label?: unknown }).label;\n if (typeof candidate === 'string') label = candidate;\n }\n return (\n <div className={defaultNodeBodyRecipe({ selected })}>\n <span>{label}</span>\n </div>\n );\n}\n\nfunction pointEqualNullable(a: Point2D | null, b: Point2D | null): boolean {\n if (a === b) return true;\n if (a === null || b === null) return false;\n return a.x === b.x && a.y === b.y;\n}\n\n/**\n * Internal — render a single node as an HTML element positioned in world\n * space. Connection endpoints live inside the consumer's `renderNode`\n * tree as `<NodeGraph.Port>` slots; this view just provides the wrapper,\n * the `NodeGraphNodeContext`, and the ResizeObserver that feeds the\n * node's measured size back to the store.\n *\n * Subscribes to per-id slices via `useStoreSlice` so each node re-renders\n * only when its own selection / drag delta / hover state changes — not on\n * every pointermove tick of an unrelated drag.\n *\n * Wrapped in `React.memo` so a parent re-render (e.g. caused by an\n * unrelated controlled-state update) doesn't cascade through every\n * mounted node. Stable identity for the parent-provided handlers\n * (`onBodyPointerDown`, etc.) is ensured by `useCallback` in\n * `NodeGraphImpl`; `node` identity is preserved by consumer immutability.\n */\nfunction NodeGraphNodeViewImpl(\n props: NodeGraphNodeViewProps\n): React.ReactElement {\n const {\n node,\n renderNode,\n onBodyPointerDown,\n onBodyPointerUp,\n onPortPointerDown,\n onBodyContextMenu,\n } = props;\n const store = useNodeGraphStore();\n const viewportStore = useViewportStore();\n const wrapperRef = useRef<HTMLDivElement>(null);\n\n const selected = useStoreSlice(\n store.subscribeSelection,\n store.getSelection,\n sel => sel.nodes.includes(node.id)\n );\n\n // Subscribe to the per-node interaction channel rather than the global\n // one. The store fans notifications out only to ids in the prev ∪ next\n // drag set, so an unrelated drag of another node never wakes this\n // subscriber. The selector itself is unchanged — `useStoreSlice` still\n // reads from the global getInteraction snapshot to decide whether *this*\n // id has a delta to apply.\n const subscribeNodeInteraction = useCallback(\n (cb: () => void) => store.subscribeNodeInteraction(node.id, cb),\n [store, node.id]\n );\n const dragDelta = useStoreSlice<\n ReturnType<typeof store.getInteraction>,\n Point2D | null\n >(\n subscribeNodeInteraction,\n store.getInteraction,\n interaction => {\n if (\n interaction.kind === 'drag-nodes' &&\n interaction.nodeIds.has(node.id)\n ) {\n return interaction.delta;\n }\n // Nodes fully contained inside a dragged group move with the group.\n if (\n interaction.kind === 'drag-groups' &&\n interaction.containedNodeIds.has(node.id)\n ) {\n return interaction.delta;\n }\n return null;\n },\n pointEqualNullable\n );\n const dragging = dragDelta !== null;\n\n const hovered = useStoreSlice(\n store.subscribeHover,\n store.getHover,\n hover => hover.hoveredNodeId === node.id\n );\n\n const liveZoom = useStoreSlice(\n viewportStore.subscribeTransform,\n viewportStore.getTransform,\n transform => transform.zoom\n );\n // Defer the zoom value passed into consumer renderNode so heavy node\n // bodies don't block the pointer gesture loop during a zoom-while-drag.\n const zoom = useDeferredValue(liveZoom);\n\n // ── Auto-size: feed measured wrapper size back to the store ──\n //\n // When the consumer omits `node.width`/`height`, the library reads the\n // measured DOM size for hit-testing, marquee, and fitToContent. Explicit\n // overrides on the node still win — this just records the rendered size.\n useResizeObserver(wrapperRef, entry => {\n store.setMeasuredSize(node.id, {\n width: entry.contentRect.width,\n height: entry.contentRect.height,\n });\n });\n\n const explicitWidth = node.width;\n const explicitHeight = node.height;\n const autoSize = explicitWidth === undefined && explicitHeight === undefined;\n const draggable = node.draggable !== false;\n const selectable = node.selectable !== false;\n\n const wrapperStyle = useMemo<React.CSSProperties>(() => {\n const x = node.position.x + (dragDelta?.x ?? 0);\n const y = node.position.y + (dragDelta?.y ?? 0);\n // assignInlineVars only emits the keys it's given — when explicit\n // dimensions are absent the auto-sized recipe variant kicks in.\n const dimensionVars: Record<string, string> = {};\n if (explicitWidth !== undefined) {\n dimensionVars[nodeWidthVar] = `${explicitWidth}px`;\n }\n if (explicitHeight !== undefined) {\n dimensionVars[nodeHeightVar] = `${explicitHeight}px`;\n }\n return {\n transform: `translate(${x}px, ${y}px)`,\n ...assignInlineVars(dimensionVars),\n };\n }, [\n node.position.x,\n node.position.y,\n dragDelta?.x,\n dragDelta?.y,\n explicitWidth,\n explicitHeight,\n ]);\n\n const renderCtx: NodeGraphRenderCtx = {\n selected,\n dragging,\n hovered,\n zoom,\n };\n\n const body = renderNode ? (\n renderNode(node, renderCtx)\n ) : (\n <DefaultNodeBody node={node} selected={selected} />\n );\n\n // ── Hover wiring ──\n const onPointerEnter = useCallback((): void => {\n const current = store.getHover();\n if (current.hoveredNodeId === node.id) return;\n store.setHover({ ...current, hoveredNodeId: node.id });\n }, [store, node.id]);\n\n const onPointerLeave = useCallback((): void => {\n const current = store.getHover();\n if (current.hoveredNodeId !== node.id) return;\n store.setHover({ ...current, hoveredNodeId: null });\n }, [store, node.id]);\n\n // ── Per-node context for `<NodeGraph.Port>` slots ──\n const nodeContext = useMemo<NodeGraphNodeContextValue>(\n () => ({\n nodeId: node.id,\n onPortPointerDown: (event, portId) => {\n onPortPointerDown(event, node.id, portId);\n },\n }),\n [node.id, onPortPointerDown]\n );\n\n return (\n <div\n ref={wrapperRef}\n className={cx(\n nodeWrapperRecipe({\n draggable,\n selectable,\n dragging,\n autoSize,\n })\n )}\n style={wrapperStyle}\n data-node-id={node.id}\n data-hovered={hovered ? 'true' : undefined}\n data-selected={selected ? 'true' : undefined}\n data-dragging={dragging ? 'true' : undefined}\n onPointerDown={e => {\n onBodyPointerDown(e, node);\n }}\n onPointerUp={e => {\n onBodyPointerUp(e, node);\n }}\n onPointerEnter={onPointerEnter}\n onPointerLeave={onPointerLeave}\n onContextMenu={e => {\n onBodyContextMenu(e, node);\n }}\n >\n <NodeGraphNodeContext.Provider value={nodeContext}>\n {body}\n </NodeGraphNodeContext.Provider>\n </div>\n );\n}\n\nexport const NodeGraphNodeView = React.memo(NodeGraphNodeViewImpl);\nNodeGraphNodeView.displayName = 'NodeGraphNodeView';\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;AAoDA;AAOE;AACA;AAEE;AACA;AAEA;;;;AAGF;AAKF;AAEA;;AACe;AACb;AAA8B;AAC9B;AACF;AAEA;;;;;;;;;;;;;;;;AAgBG;AACH;AAGE;AAQA;AACA;AACA;;;;;;;;AAcA;AAIA;AAOI;;;;;AAOA;;;;AAMA;;AAIJ;;;;;AAeA;;;;;;AAOA;AACE;AACE;AACA;AACD;AACH;AAEA;AACA;;AAEA;AACA;AAEA;AACE;AACA;;;;AAIA;AACE;;AAEF;AACE;;;AAGA;;;AAGJ;;;AAGE;AACA;;;AAGD;AAED;;;;;;AAOA;;AAOA;AACE;AACA;;AACA;;AAGF;AACE;AACA;;AACA;;;AAIF;;AAGI;;;;;;;;;;AAwBE;AACF;AAEE;AACF;AAIE;AACF;AAON;AAEO;AACP;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NodeGraphNodeContext.js","sources":["
|
|
1
|
+
{"version":3,"file":"NodeGraphNodeContext.js","sources":["src/components/editor/NodeGraph/NodeGraphNodeContext.ts"],"sourcesContent":["'use client';\n\nimport { createContext, useContext } from 'react';\n\nimport type React from 'react';\n\n/**\n * Per-node context wired by `NodeGraphNodeView` so `<NodeGraph.Port>` slots\n * rendered inside `renderNode` can identify their owning node and start a\n * connection drag without the consumer threading any IDs through manually.\n */\nexport interface NodeGraphNodeContextValue {\n /** Owning node id. */\n nodeId: string;\n /**\n * Start a connection drag from the given port. Wired by the parent to\n * the shared `useNodeGraphConnection` controller.\n */\n onPortPointerDown: (\n event: React.PointerEvent<HTMLElement>,\n portId: string\n ) => void;\n}\n\nexport const NodeGraphNodeContext =\n /*#__PURE__*/ createContext<NodeGraphNodeContextValue | null>(null);\n\n/**\n * Read the active per-node context. Throws when used outside a node body\n * (i.e. outside `renderNode`) — the throw is intentional, since the\n * library guarantees the context is provided when the consumer's\n * `renderNode` is invoked.\n */\nexport function useNodeGraphNodeContext(): NodeGraphNodeContextValue {\n const ctx = useContext(NodeGraphNodeContext);\n if (!ctx) {\n throw new Error(\n '<NodeGraph.Port> must be rendered inside the renderNode body of a <NodeGraph>'\n );\n }\n return ctx;\n}\n"],"names":[],"mappings":";;;;AAyBE;AAEF;;;;;AAKG;;AAED;;AAEE;;AAIF;AACF;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NodeGraphNodeParts.js","sources":["
|
|
1
|
+
{"version":3,"file":"NodeGraphNodeParts.js","sources":["src/components/editor/NodeGraph/NodeGraphNodeParts.tsx"],"sourcesContent":["'use client';\n\nimport React from 'react';\n\nimport { cx } from '@/utils/cx';\n\nimport {\n nodeBodyRecipe,\n nodeHeaderStyle,\n nodeHeaderIconStyle,\n nodeHeaderTextStyle,\n nodeHeaderTitleStyle,\n nodeHeaderSubtitleStyle,\n pinListStyle,\n pinListColumnStyle,\n pinRowRecipe,\n} from './NodeGraph.css';\nimport { useNodeGraphStore } from './NodeGraphContext';\nimport { useNodeGraphNodeContext } from './NodeGraphNodeContext';\nimport { useStoreSlice } from './useStoreSlice';\n\nimport type { NodeGraphPortSide } from './NodeGraph.types';\n\n/**\n * Default node body wrapper — a themed panel with the standard\n * selected/hovered visual states the library provides out of the box.\n * Drop it inside your `renderNode` and you get a node that looks at\n * home next to the rest of the library; override `className` / `style`\n * to take over.\n *\n * Selection / hover / drag state is read from the store via the\n * per-node context so the body re-themes itself without the consumer\n * threading state in from `renderNode`'s `ctx`. Pass `selected` /\n * `hovered` explicitly to drive the visual from outside (e.g. running\n * a node-state machine in your own React tree).\n */\nexport interface NodeGraphNodeBodyProps {\n /** Override the accent colour (default border / shadow when selected). */\n accent?: string;\n /** Override the auto-derived `selected` state. */\n selected?: boolean;\n /** Override the auto-derived `hovered` state. */\n hovered?: boolean;\n /** Visual variant — affects default chrome density. */\n variant?: 'panel' | 'flat' | 'minimal';\n /** Extra classes applied to the body wrapper. */\n className?: string;\n /** Inline style override (lower priority than CSS classes). */\n style?: React.CSSProperties;\n /** Body content — typically `<NodeGraph.NodeHeader>` + `<NodeGraph.PinList>`. */\n children?: React.ReactNode;\n}\n\nexport function NodeGraphNodeBody({\n accent,\n selected: selectedProp,\n hovered: hoveredProp,\n variant = 'panel',\n className,\n style,\n children,\n}: NodeGraphNodeBodyProps): React.ReactElement {\n const { nodeId } = useNodeGraphNodeContext();\n const store = useNodeGraphStore();\n\n const autoSelected = useStoreSlice(\n store.subscribeSelection,\n store.getSelection,\n sel => sel.nodes.includes(nodeId)\n );\n const autoHovered = useStoreSlice(\n store.subscribeHover,\n store.getHover,\n hover => hover.hoveredNodeId === nodeId\n );\n\n const selected = selectedProp ?? autoSelected;\n const hovered = hoveredProp ?? autoHovered;\n\n // Accent drives both the selected border and a soft glow. CSS vars\n // let recipe variants pick up the consumer's value without us\n // pre-baking a token contract for every possible colour.\n const accentStyle = accent\n ? ({ ['--etui-ng-accent' as string]: accent } as React.CSSProperties)\n : undefined;\n\n return (\n <div\n className={cx(nodeBodyRecipe({ selected, hovered, variant }), className)}\n style={{ ...accentStyle, ...style }}\n >\n {children}\n </div>\n );\n}\n\n/**\n * Header bar for a node — gradient strip + optional icon, title and\n * subtitle. Pass `children` to take full control of the header layout.\n */\nexport interface NodeGraphNodeHeaderProps {\n title?: React.ReactNode;\n subtitle?: React.ReactNode;\n /** Small leading icon (typically 14-18 px). */\n icon?: React.ReactNode;\n /** Override the header background — defaults to theme accent gradient. */\n background?: string;\n className?: string;\n style?: React.CSSProperties;\n children?: React.ReactNode;\n}\n\nexport function NodeGraphNodeHeader({\n title,\n subtitle,\n icon,\n background,\n className,\n style,\n children,\n}: NodeGraphNodeHeaderProps): React.ReactElement {\n const styleOverride: React.CSSProperties = {\n ...(background ? { background } : null),\n ...style,\n };\n return (\n <div className={cx(nodeHeaderStyle, className)} style={styleOverride}>\n {children ?? (\n <>\n {icon != null ? (\n <span className={nodeHeaderIconStyle}>{icon}</span>\n ) : null}\n <div className={nodeHeaderTextStyle}>\n {title != null ? (\n <div className={nodeHeaderTitleStyle}>{title}</div>\n ) : null}\n {subtitle != null ? (\n <div className={nodeHeaderSubtitleStyle}>{subtitle}</div>\n ) : null}\n </div>\n </>\n )}\n </div>\n );\n}\n\n/**\n * Two-column pin container — children are routed into the left or right\n * column based on each `<NodeGraph.PinRow side>`. Anything that isn't a\n * `<NodeGraph.PinRow>` falls through into a center \"loose\" slot below\n * the columns (so you can drop inline editors / body content next to\n * your pin rows without breaking layout).\n */\nexport interface NodeGraphPinListProps {\n className?: string;\n style?: React.CSSProperties;\n /** Gap between left and right columns, in CSS px. Default 16. */\n columnGap?: number;\n children?: React.ReactNode;\n}\n\nexport function NodeGraphPinList({\n className,\n style,\n columnGap = 16,\n children,\n}: NodeGraphPinListProps): React.ReactElement {\n const left: React.ReactNode[] = [];\n const right: React.ReactNode[] = [];\n const loose: React.ReactNode[] = [];\n\n React.Children.forEach(children, child => {\n if (!React.isValidElement(child)) {\n loose.push(child);\n return;\n }\n const side =\n (child.props as { side?: NodeGraphPortSide } | undefined)?.side ?? null;\n if (side === 'left') left.push(child);\n else if (side === 'right') right.push(child);\n else loose.push(child);\n });\n\n return (\n <div\n className={cx(pinListStyle, className)}\n style={{ columnGap, ...style }}\n >\n <div className={pinListColumnStyle}>{left}</div>\n <div className={pinListColumnStyle}>{right}</div>\n {loose.length > 0 ? (\n <div style={{ gridColumn: '1 / -1' }}>{loose}</div>\n ) : null}\n </div>\n );\n}\n\n/**\n * Single pin row — typically holds a `<NodeGraph.Port>` plus a label.\n * Side determines which end of the row the port sits at: `left` puts\n * the port first (left edge), `right` puts it last (right edge).\n *\n * The row is layout-only — pin position math comes from the slot's DOM\n * measurement, not from where in the row it lives. You can rearrange\n * the order inside the row without breaking edges.\n */\nexport interface NodeGraphPinRowProps {\n /** Which side this pin anchors to. Drives the layout / justification. */\n side: NodeGraphPortSide;\n /** Optional row height (CSS px). Default 22. */\n height?: number;\n className?: string;\n style?: React.CSSProperties;\n children?: React.ReactNode;\n}\n\nexport function NodeGraphPinRow({\n side,\n height = 22,\n className,\n style,\n children,\n}: NodeGraphPinRowProps): React.ReactElement {\n return (\n <div\n className={cx(pinRowRecipe({ side }), className)}\n style={{ height, ...style }}\n data-pin-side={side}\n >\n {children}\n </div>\n );\n}\n"],"names":[],"mappings":";;;;;;;;;AAqDM;AASJ;AACA;;;AAaA;AACA;;;;;AAME;;AAGF;AAQF;;AA2BE;AACE;AACA;;AAEF;AAmBF;AAiBM;;;;;;AAYA;;;;;AAKmB;;AACM;;AACtB;AACP;AAEA;AAYF;AAqBM;AAOJ;AASF;;"}
|
|
@@ -5,7 +5,7 @@ import '../../../utils/devWarn.js';
|
|
|
5
5
|
import { useControlledState } from '../../../hooks/useControlledState/useControlledState.js';
|
|
6
6
|
import '../../../hooks/useBreakpoint/useBreakpoint.js';
|
|
7
7
|
import './../../../assets/src/theme/lightTheme.css.ts.vanilla-Ct6f9Iym.css';
|
|
8
|
-
import '
|
|
8
|
+
import '@vanilla-extract/css';
|
|
9
9
|
import './../../../assets/src/theme/darkTheme.css.ts.vanilla-BHZ3ecRn.css';
|
|
10
10
|
import './../../../assets/src/theme/globalScrollbars.css.ts.vanilla-BAJwnUEJ.css';
|
|
11
11
|
import '../../../hooks/useNavigationHistory/useNavigationHistory.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NodeGraphNodeSection.js","sources":["
|
|
1
|
+
{"version":3,"file":"NodeGraphNodeSection.js","sources":["src/components/editor/NodeGraph/NodeGraphNodeSection.tsx"],"sourcesContent":["'use client';\n\nimport React, { useCallback, useMemo } from 'react';\n\nimport { useControlledState } from '@/hooks';\nimport { cx } from '@/utils/cx';\n\nimport {\n nodeSectionStyle,\n nodeSectionHeaderStyle,\n nodeSectionChevronRecipe,\n nodeSectionTitleStyle,\n nodeSectionContentStyle,\n} from './NodeGraph.css';\nimport { NodeGraphNodeSectionContext } from './NodeGraphNodeSectionContext';\n\nimport type { NodeGraphNodeSectionContextValue } from './NodeGraphNodeSectionContext';\n\nexport interface NodeGraphNodeSectionProps {\n /** Header label shown next to the collapse toggle. */\n title?: React.ReactNode;\n /**\n * Whether the header toggles the section. When false the section is a\n * static labelled group (no chevron, always shown). @default true\n */\n collapsible?: boolean;\n /** Controlled collapsed state. Pair with `onCollapsedChange`. */\n collapsed?: boolean;\n /** Initial collapsed state when uncontrolled. @default false */\n defaultCollapsed?: boolean;\n /** Fires when the user toggles the section. */\n onCollapsedChange?: (collapsed: boolean) => void;\n className?: string;\n style?: React.CSSProperties;\n /** Section body — typically a `<NodeGraph.PinList>` of the extra pins. */\n children?: React.ReactNode;\n}\n\n/**\n * A collapsible section inside a node body — the place to tuck \"advanced\" /\n * overflow pins so the node stays compact. The collapse is **purely visual**:\n * the children never unmount, so their state is preserved and there's no\n * remount cost. `<NodeGraph.Port>` slots inside a collapsed section\n * unregister their position while hidden, so the pins' edges hide with them\n * and reappear on expand — no dangling wires, no manual re-anchoring.\n *\n * @example\n * ```tsx\n * <NodeGraph.NodeBody>\n * <NodeGraph.NodeHeader title=\"Make Transform\" />\n * <NodeGraph.PinList>{mainPins}</NodeGraph.PinList>\n * <NodeGraph.NodeSection title=\"Advanced\" defaultCollapsed>\n * <NodeGraph.PinList>{advancedPins}</NodeGraph.PinList>\n * </NodeGraph.NodeSection>\n * </NodeGraph.NodeBody>\n * ```\n */\nexport function NodeGraphNodeSection({\n title,\n collapsible = true,\n collapsed: collapsedProp,\n defaultCollapsed,\n onCollapsedChange,\n className,\n style,\n children,\n}: NodeGraphNodeSectionProps): React.ReactElement {\n const [collapsedState, setCollapsed] = useControlledState<boolean>({\n value: collapsedProp,\n defaultValue: defaultCollapsed,\n onChange: onCollapsedChange,\n fallback: false,\n });\n // A non-collapsible section is always expanded regardless of state.\n const collapsed = collapsible ? collapsedState : false;\n\n const toggle = useCallback(() => {\n if (!collapsible) return;\n setCollapsed(prev => !prev);\n }, [collapsible, setCollapsed]);\n\n // Toggling the section must not start a node drag / selection.\n const stopPointer = useCallback(\n (event: React.PointerEvent<HTMLButtonElement>) => {\n event.stopPropagation();\n },\n []\n );\n\n const ctxValue = useMemo<NodeGraphNodeSectionContextValue>(\n () => ({ collapsed }),\n [collapsed]\n );\n\n return (\n <div className={cx(nodeSectionStyle, className)} style={style}>\n <button\n type=\"button\"\n className={nodeSectionHeaderStyle}\n onClick={toggle}\n onPointerDown={stopPointer}\n disabled={!collapsible}\n aria-expanded={collapsible ? !collapsed : undefined}\n >\n {collapsible ? (\n <span\n className={nodeSectionChevronRecipe({ collapsed })}\n aria-hidden=\"true\"\n />\n ) : null}\n {title != null ? (\n <span className={nodeSectionTitleStyle}>{title}</span>\n ) : null}\n </button>\n <div\n className={nodeSectionContentStyle}\n style={collapsed ? { display: 'none' } : undefined}\n >\n <NodeGraphNodeSectionContext.Provider value={ctxValue}>\n {children}\n </NodeGraphNodeSectionContext.Provider>\n </div>\n </div>\n );\n}\n\nNodeGraphNodeSection.displayName = 'NodeGraphNodeSection';\n"],"names":[],"mappings":";;;;;;;;;;;;;;;AAsCA;;;;;;;;;;;;;;;;;;AAkBG;AACG;AAUJ;AACE;AACA;AACA;AACA;AACD;;;AAID;AACE;;;AAEF;;AAGA;;;AAOA;AAKA;AA8BF;AAEA;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NodeGraphNodeSectionContext.js","sources":["
|
|
1
|
+
{"version":3,"file":"NodeGraphNodeSectionContext.js","sources":["src/components/editor/NodeGraph/NodeGraphNodeSectionContext.ts"],"sourcesContent":["'use client';\n\nimport { createContext, useContext } from 'react';\n\n/**\n * Context published by `<NodeGraph.NodeSection>` so the `<NodeGraph.Port>`\n * slots rendered inside it know whether they're currently collapsed. The\n * section never unmounts its children (a collapse is purely visual), so a\n * collapsed port stays in the React tree but unregisters its measured\n * position — which hides its edges until the section is expanded again,\n * without any remount cost or re-anchor bookkeeping.\n */\nexport interface NodeGraphNodeSectionContextValue {\n collapsed: boolean;\n}\n\nconst DEFAULT: NodeGraphNodeSectionContextValue = { collapsed: false };\n\nexport const NodeGraphNodeSectionContext =\n /*#__PURE__*/ createContext<NodeGraphNodeSectionContextValue>(DEFAULT);\n\n/**\n * Read the enclosing section's collapsed state. Returns `{ collapsed: false }`\n * when the port isn't inside a `<NodeGraph.NodeSection>`, so ports outside a\n * section behave exactly as before.\n */\nexport function useNodeGraphNodeSection(): NodeGraphNodeSectionContextValue {\n return useContext(NodeGraphNodeSectionContext);\n}\n"],"names":[],"mappings":";;;AAgBA;;AAGE;AAEF;;;;AAIG;;AAED;AACF;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NodeGraphPin.js","sources":["
|
|
1
|
+
{"version":3,"file":"NodeGraphPin.js","sources":["src/components/editor/NodeGraph/NodeGraphPin.tsx"],"sourcesContent":["'use client';\n\nimport React from 'react';\n\nimport { cx } from '@/utils/cx';\n\nimport { pinLabelStyle } from './NodeGraph.css';\nimport { NodeGraphPinRow } from './NodeGraphNodeParts';\nimport { NodeGraphPort } from './NodeGraphPort';\n\nimport type { NodeGraphPortShape, NodeGraphPortSide } from './NodeGraph.types';\n\nexport interface NodeGraphPinProps {\n /** Port id — referenced by `NodeGraphEdge.source` / `.target`. */\n id: string;\n /** Which side the pin anchors to. Drives row layout and edge tangent. */\n side: NodeGraphPortSide;\n /** Opaque type token forwarded to `isValidConnection` and edge styling. */\n dataType?: string;\n /** Row label shown next to the handle. */\n label?: React.ReactNode;\n /** Handle colour (any CSS colour). Defaults to the theme focus colour. */\n color?: string;\n /** Built-in handle shape. @default 'circle' */\n shape?: NodeGraphPortShape;\n /**\n * Force the handle filled / hollow. When omitted, the handle fills\n * automatically while the pin is connected to an edge.\n */\n filled?: boolean;\n /** Row height in CSS px. @default 22 */\n height?: number;\n /** Accessible label for the port handle. Falls back to the port default. */\n portLabel?: string;\n /** Class applied to the row wrapper. */\n className?: string;\n /** Inline style applied to the row wrapper. */\n style?: React.CSSProperties;\n /** Class applied to the label span. */\n labelClassName?: string;\n /** Inline style applied to the label span. */\n labelStyle?: React.CSSProperties;\n}\n\n/**\n * `<NodeGraph.Pin>` — the one-liner for the common \"handle + label\" row.\n * Renders a `<NodeGraph.PinRow>` containing a `<NodeGraph.Port>` and a\n * label, ordered so the handle always sits flush against the node edge\n * (port → label on the left/top, label → port on the right). Reach for\n * `<NodeGraph.PinRow>` + `<NodeGraph.Port>` directly only when you need a\n * layout this doesn't cover (multiple controls in a row, inline editors).\n *\n * @example\n * ```tsx\n * <NodeGraph.PinList>\n * <NodeGraph.Pin id=\"exec\" side=\"left\" shape=\"triangle\" color=\"#fff\" />\n * <NodeGraph.Pin id=\"value\" side=\"right\" dataType=\"float\" color=\"#9ee65a\" label=\"Value\" />\n * </NodeGraph.PinList>\n * ```\n */\nexport function NodeGraphPin({\n id,\n side,\n dataType,\n label,\n color,\n shape,\n filled,\n height,\n portLabel,\n className,\n style,\n labelClassName,\n labelStyle,\n}: NodeGraphPinProps): React.ReactElement {\n const port = (\n <NodeGraphPort\n id={id}\n side={side}\n {...(dataType !== undefined ? { dataType } : {})}\n {...(color !== undefined ? { color } : {})}\n {...(shape !== undefined ? { shape } : {})}\n {...(filled !== undefined ? { filled } : {})}\n {...(portLabel !== undefined ? { label: portLabel } : {})}\n />\n );\n const labelEl =\n label != null && label !== '' ? (\n <span className={cx(pinLabelStyle, labelClassName)} style={labelStyle}>\n {label}\n </span>\n ) : null;\n // Port leads the row on the left / top so the handle hugs the node edge;\n // on the right the label leads and the handle trails.\n const portFirst = side === 'left' || side === 'top';\n return (\n <NodeGraphPinRow\n side={side}\n {...(height !== undefined ? { height } : {})}\n {...(className !== undefined ? { className } : {})}\n {...(style !== undefined ? { style } : {})}\n >\n {portFirst ? (\n <>\n {port}\n {labelEl}\n </>\n ) : (\n <>\n {labelEl}\n {port}\n </>\n )}\n </NodeGraphPinRow>\n );\n}\n\nNodeGraphPin.displayName = 'NodeGraphPin';\n"],"names":[],"mappings":";;;;;;;AA4CA;;;;;;;;;;;;;;;AAeG;AACG;;AA0BJ;;;;AASA;AAoBF;AAEA;;"}
|
|
@@ -6,7 +6,7 @@ import '../../../utils/devWarn.js';
|
|
|
6
6
|
import { useResizeObserver } from '../../../hooks/useResizeObserver/useResizeObserver.js';
|
|
7
7
|
import '../../../hooks/useBreakpoint/useBreakpoint.js';
|
|
8
8
|
import './../../../assets/src/theme/lightTheme.css.ts.vanilla-Ct6f9Iym.css';
|
|
9
|
-
import '
|
|
9
|
+
import '@vanilla-extract/css';
|
|
10
10
|
import './../../../assets/src/theme/darkTheme.css.ts.vanilla-BHZ3ecRn.css';
|
|
11
11
|
import './../../../assets/src/theme/globalScrollbars.css.ts.vanilla-BAJwnUEJ.css';
|
|
12
12
|
import '../../../hooks/useNavigationHistory/useNavigationHistory.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NodeGraphPort.js","sources":["../../../../../../src/components/editor/NodeGraph/NodeGraphPort.tsx"],"sourcesContent":[null],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;AA8BA;AACE;AACA;AACA;AACA;;AAGF;AACE;AAEE;AACA;AACA;AAEJ;AAEA;;;;;;;;;;;;;;;;;;;;;;AAsBG;AACH;;AAaE;AACA;AACA;AACA;;;;;;AAYA;AAII;AAAoC;AACpC;AAEA;AAEE;AACA;AACF;AAA+B;;;;AAI7B;AACA;;;AAMN;AAII;AACA;AAEE;AACA;;AAKN;AACA;AACA;AACA;;;;;;;;;;;;;AAcA;;;;;AAKI;;;AAGF;AACA;;;AAEA;;AACA;AACA;;;;;;AAMA;AACA;AACA;AACF;;;;;;;;;;AAkBE;AACF;AAEA;AACE;AACF;;;AAIE;AACE;AACF;;;;;;;AAQF;AACE;AACA;;;;AAKF;AACE;AACA;;AAEA;;AAGF;AAEI;AACF;;AAKF;;;;;AAMA;;;;;AAuBQ;AACA;AACA;AACA;;AAgBV;AAEO;AACP;;"}
|
|
1
|
+
{"version":3,"file":"NodeGraphPort.js","sources":["src/components/editor/NodeGraph/NodeGraphPort.tsx"],"sourcesContent":["'use client';\n\nimport React, {\n useCallback,\n useLayoutEffect,\n useMemo,\n useRef,\n useSyncExternalStore,\n} from 'react';\n\nimport { useViewportStore } from '@/components/primitives/viewport/ViewportContext';\nimport { useResizeObserver } from '@/hooks';\nimport { cx } from '@/utils/cx';\n\nimport { portSlotRecipe } from './NodeGraph.css';\nimport { useNodeGraphStore } from './NodeGraphContext';\nimport { useNodeGraphNodeContext } from './NodeGraphNodeContext';\nimport { useNodeGraphNodeSection } from './NodeGraphNodeSectionContext';\nimport { NodeGraphPortVisual } from './NodeGraphPortVisual';\nimport { useStoreSlice } from './useStoreSlice';\n\nimport type { NodeGraphPortSlotProps } from './NodeGraph.types';\n\ninterface PortVisualState {\n isSource: boolean;\n isCandidate: boolean;\n isInvalid: boolean;\n isHovered: boolean;\n}\n\nconst EMPTY_VISUAL: PortVisualState = {\n isSource: false,\n isCandidate: false,\n isInvalid: false,\n isHovered: false,\n};\n\nfunction visualEqual(a: PortVisualState, b: PortVisualState): boolean {\n return (\n a.isSource === b.isSource &&\n a.isCandidate === b.isCandidate &&\n a.isInvalid === b.isInvalid &&\n a.isHovered === b.isHovered\n );\n}\n\n/**\n * `<NodeGraph.Port>` — compound slot that declares a connection endpoint\n * for the current node. Render it anywhere inside `renderNode`; the\n * library measures its DOM position, registers it as the anchor for any\n * edge that references this port id, and wires pointer events so the\n * user can drag from it to start a connection.\n *\n * The slot renders a single `<span>` with `display: inline-flex` — drop\n * it inline next to a label and it will sit beside the text. The wrapper\n * carries the default UE-style circle / exec-triangle visual; pass\n * `children` to replace the chrome entirely (state for theming is\n * exposed via `data-port-*` attributes).\n *\n * @example\n * ```tsx\n * <NodeGraph.Port id=\"in\" side=\"left\" dataType=\"exec\" />\n * ```\n *\n * Wrapped in `React.memo` — ports re-mount on every consumer `renderNode`\n * call by default, so memoization only helps consumers who memoize their\n * own node body. When they do, ports skip re-renders on unrelated store\n * updates entirely.\n */\nfunction NodeGraphPortImpl({\n id,\n side,\n dataType,\n shape = 'circle',\n color,\n filled,\n children,\n label,\n className,\n style,\n}: NodeGraphPortSlotProps): React.ReactElement {\n const { nodeId, onPortPointerDown } = useNodeGraphNodeContext();\n const { collapsed } = useNodeGraphNodeSection();\n const store = useNodeGraphStore();\n const viewportStore = useViewportStore();\n const elementRef = useRef<HTMLSpanElement>(null);\n\n // ── Connected state (slice subscription — only flips re-render) ──\n // The store recomputes the connected-ports index when edges change, so\n // each port re-renders only when its own connected-ness actually flips.\n const connected = useStoreSlice(\n store.subscribeConnectedPorts,\n store.getConnectedPorts,\n () => store.isPortConnected(nodeId, id)\n );\n\n // ── Per-port visual state (slice subscription — no re-render storm) ──\n const visual = useStoreSlice(\n store.subscribeInteraction,\n store.getInteraction,\n interaction => {\n if (interaction.kind !== 'connect') return EMPTY_VISUAL;\n const isSource =\n interaction.source.node === nodeId && interaction.source.port === id;\n const isCandidate =\n interaction.candidate !== null &&\n interaction.candidate.node === nodeId &&\n interaction.candidate.port === id;\n if (!isSource && !isCandidate) return EMPTY_VISUAL;\n return {\n isSource,\n isCandidate,\n isInvalid: isCandidate && interaction.invalid,\n isHovered: false,\n };\n },\n visualEqual\n );\n\n const hoverState = useStoreSlice(\n store.subscribeHover,\n store.getHover,\n hover => ({\n ...EMPTY_VISUAL,\n isHovered:\n hover.hoveredPort !== null &&\n hover.hoveredPort.node === nodeId &&\n hover.hoveredPort.port === id,\n }),\n visualEqual\n );\n\n const isHovered = hoverState.isHovered;\n const isSource = visual.isSource;\n const isCandidate = visual.isCandidate;\n const isInvalid = visual.isInvalid;\n\n // ── DOM measurement → store ──\n //\n // Run synchronously before paint so first frame's edges anchor at the\n // correct point. `useResizeObserver` picks up later size changes on the\n // port itself, and the per-node layout-version subscription below picks\n // up layout shifts inside the node body that move the port without\n // resizing it (e.g. a sibling row expands).\n //\n // `closest('[data-node-id]:not([data-port-id])')` skips the slot itself\n // — the slot wrapper carries `data-node-id` too (so connection-drop\n // hit-tests can walk up from any descendant), so a plain `[data-node-id]`\n // would match the port and report position (~6, ~6) for every port.\n const measure = useCallback((): void => {\n // Inside a collapsed section the port is hidden; drop its registered\n // position so its edges hide too (re-registers on expand). The component\n // stays mounted, so no remount churn.\n if (collapsed) {\n store.removePortPosition(nodeId, id);\n return;\n }\n const el = elementRef.current;\n if (!el) return;\n const wrapper = el.closest('[data-node-id]:not([data-port-id])');\n if (!wrapper) return;\n const portRect = el.getBoundingClientRect();\n const nodeRect = wrapper.getBoundingClientRect();\n // Convert from screen pixels to node-local world units. `ViewportWorld`\n // applies `transform: scale(zoom)` to all node wrappers, so the rects\n // we just read are scaled by `zoom`. Dividing brings us back to the\n // un-scaled layout pixels, which are 1:1 with world units inside a node.\n const zoom = viewportStore.getTransform().zoom || 1;\n const x = (portRect.left + portRect.width / 2 - nodeRect.left) / zoom;\n const y = (portRect.top + portRect.height / 2 - nodeRect.top) / zoom;\n store.setPortPosition(nodeId, id, { x, y, side, dataType });\n }, [store, viewportStore, nodeId, id, side, dataType, collapsed]);\n\n // Re-measure triggers (the per-port `ResizeObserver` only fires when\n // the port's own size changes, which misses layout shifts inside the\n // node body that move the port without resizing it — e.g. a sibling\n // row expanding). Subscribing to the per-node measured size makes this\n // component re-render on every wrapper resize, and the layout effect\n // below re-runs the measurement.\n const getMeasuredForNode = useMemo(\n () => () => store.getMeasuredSize(nodeId),\n [store, nodeId]\n );\n const measuredSize = useSyncExternalStore(\n store.subscribeMeasuredSizes,\n getMeasuredForNode\n );\n\n useLayoutEffect(() => {\n measure();\n }, [measure, measuredSize]);\n\n useResizeObserver(elementRef, () => {\n measure();\n });\n\n // Unregister on unmount / id change.\n useLayoutEffect(() => {\n return () => {\n store.removePortPosition(nodeId, id);\n };\n }, [store, nodeId, id]);\n\n // ── Hover wiring ──\n //\n // Update the store's `hoveredPort` slice on enter/leave so node bodies,\n // edges, and other ports can react. Library guards against duplicate\n // notifications when the value doesn't actually change.\n const onPointerEnter = useCallback((): void => {\n const current = store.getHover();\n if (current.hoveredPort?.node === nodeId && current.hoveredPort.port === id)\n return;\n store.setHover({ ...current, hoveredPort: { node: nodeId, port: id } });\n }, [store, nodeId, id]);\n\n const onPointerLeave = useCallback((): void => {\n const current = store.getHover();\n if (current.hoveredPort?.node !== nodeId || current.hoveredPort.port !== id)\n return;\n store.setHover({ ...current, hoveredPort: null });\n }, [store, nodeId, id]);\n\n const handlePointerDown = useCallback(\n (event: React.PointerEvent<HTMLSpanElement>): void => {\n onPortPointerDown(event, id);\n },\n [onPortPointerDown, id]\n );\n\n const hasCustomVisual = children !== undefined && children !== null;\n const isFilled = filled ?? connected;\n\n // Route the consumer's `color` into the slot via a CSS custom property so\n // the built-in visual (which paints with `currentColor`) picks it up,\n // while the source / candidate / invalid recipe variants can still\n // override `color` to keep connection-drag feedback visible.\n const portStyle: React.CSSProperties | undefined =\n color !== undefined\n ? { ['--etui-ng-port-color' as string]: color, ...style }\n : style;\n\n return (\n <span\n ref={elementRef}\n role=\"button\"\n tabIndex={-1}\n aria-label={label ?? `${side} port ${id}`}\n data-node-id={nodeId}\n data-port-id={id}\n data-port-side={side}\n data-port-data-type={dataType}\n data-port-source={isSource || undefined}\n data-port-candidate={isCandidate || undefined}\n data-port-invalid={isInvalid || undefined}\n data-port-hovered={isHovered || undefined}\n data-port-connected={connected || undefined}\n className={cx(\n portSlotRecipe({\n side,\n custom: hasCustomVisual,\n source: isSource,\n candidate: isCandidate,\n invalid: isInvalid,\n }),\n className\n )}\n style={portStyle}\n onPointerDown={handlePointerDown}\n onPointerEnter={onPointerEnter}\n onPointerLeave={onPointerLeave}\n >\n {hasCustomVisual ? (\n children\n ) : (\n <NodeGraphPortVisual shape={shape} filled={isFilled} />\n )}\n </span>\n );\n}\n\nexport const NodeGraphPort = React.memo(NodeGraphPortImpl);\nNodeGraphPort.displayName = 'NodeGraphPort';\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;AA8BA;AACE;AACA;AACA;AACA;;AAGF;AACE;AAEE;AACA;AACA;AAEJ;AAEA;;;;;;;;;;;;;;;;;;;;;;AAsBG;AACH;;AAaE;AACA;AACA;AACA;;;;;;AAYA;AAII;AAAoC;AACpC;AAEA;AAEE;AACA;AACF;AAA+B;;;;AAI7B;AACA;;;AAMN;AAII;AACA;AAEE;AACA;;AAKN;AACA;AACA;AACA;;;;;;;;;;;;;AAcA;;;;;AAKI;;;AAGF;AACA;;;AAEA;;AACA;AACA;;;;;;AAMA;AACA;AACA;AACF;;;;;;;;;;AAkBE;AACF;AAEA;AACE;AACF;;;AAIE;AACE;AACF;;;;;;;AAQF;AACE;AACA;;;;AAKF;AACE;AACA;;AAEA;;AAGF;AAEI;AACF;;AAKF;;;;;AAMA;;;;;AAuBQ;AACA;AACA;AACA;;AAgBV;AAEO;AACP;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NodeGraphPortVisual.js","sources":["
|
|
1
|
+
{"version":3,"file":"NodeGraphPortVisual.js","sources":["src/components/editor/NodeGraph/NodeGraphPortVisual.tsx"],"sourcesContent":["'use client';\n\nimport React from 'react';\n\nimport type { NodeGraphPortShape } from './NodeGraph.types';\n\nexport interface NodeGraphPortVisualProps {\n /**\n * Handle shape. @default 'circle'\n */\n shape?: NodeGraphPortShape;\n /**\n * Stroke / fill colour. Defaults to `currentColor` so the parent\n * `<NodeGraph.Port>` can route its colour prop and interaction-state\n * colours (source / candidate / invalid) into the shape via the CSS\n * `color` cascade. Pass an explicit colour when using the visual\n * standalone (outside a port slot).\n */\n color?: string;\n /**\n * Filled (`true`) paints the interior; hollow (`false`) draws an outline\n * only — the standard \"wired vs unwired pin\" convention.\n * @default false\n */\n filled?: boolean;\n /**\n * Box size in CSS pixels. @default 14\n */\n size?: number;\n className?: string;\n style?: React.CSSProperties;\n}\n\n// All shapes are authored in a 16×16 user space and scaled to `size`, so a\n// single stroke width reads consistently across shapes.\nconst VIEW = 16;\nconst STROKE = 2;\n\nfunction shapeElement(\n shape: NodeGraphPortShape,\n stroke: string,\n fill: string\n): React.ReactElement {\n const common = {\n stroke,\n fill,\n strokeWidth: STROKE,\n strokeLinejoin: 'round' as const,\n };\n switch (shape) {\n case 'square':\n return <rect x={3} y={3} width={10} height={10} rx={1.5} {...common} />;\n case 'diamond':\n return <polygon points=\"8,2 14,8 8,14 2,8\" {...common} />;\n case 'triangle':\n // Right-pointing exec/flow arrow (UE Blueprint convention).\n return <polygon points=\"3,2.5 13.5,8 3,13.5\" {...common} />;\n case 'circle':\n default:\n return <circle cx={8} cy={8} r={5} {...common} />;\n }\n}\n\n/**\n * Presentational handle shape for a node-graph port. Rendered by\n * `<NodeGraph.Port>` when no `children` are supplied, and exported so\n * consumers can reuse the exact handle look inside fully custom port\n * visuals (e.g. a coloured ring beside a typed label).\n *\n * The shape paints with `currentColor` unless `color` is set, which lets\n * the port slot drive the colour from its `color` prop and override it on\n * the connection-drag states without this component knowing about them.\n */\nexport function NodeGraphPortVisual({\n shape = 'circle',\n color,\n filled = false,\n size = 14,\n className,\n style,\n}: NodeGraphPortVisualProps): React.ReactElement {\n const stroke = color ?? 'currentColor';\n const fill = filled ? (color ?? 'currentColor') : 'none';\n return (\n <svg\n width={size}\n height={size}\n viewBox={`0 0 ${VIEW} ${VIEW}`}\n className={className}\n style={{ display: 'block', overflow: 'visible', ...style }}\n aria-hidden=\"true\"\n focusable=\"false\"\n >\n {shapeElement(shape, stroke, fill)}\n </svg>\n );\n}\n\nNodeGraphPortVisual.displayName = 'NodeGraphPortVisual';\n"],"names":[],"mappings":";;;AAiCA;AACA;AACA;AACA;AAEA;AAKE;;;AAGE;AACA;;;AAGA;;AAEA;AACE;AACF;;AAEE;AACF;AACA;AACE;;AAEN;AAEA;;;;;;;;;AASG;AACG;AAQJ;AACA;;AAcF;AAEA;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NodeGraphSlots.js","sources":["
|
|
1
|
+
{"version":3,"file":"NodeGraphSlots.js","sources":["src/components/editor/NodeGraph/NodeGraphSlots.ts"],"sourcesContent":["import { NODE_GRAPH_SLOT } from './NodeGraph.types';\n\nimport type {\n NodeGraphBackgroundSlotProps,\n NodeGraphMinimapSlotProps,\n NodeGraphSlotMarker,\n NodeGraphSpawnPaletteSlotProps,\n NodeGraphToolbarSlotProps,\n} from './NodeGraph.types';\n\n/**\n * Marker behind `<NodeGraph.Minimap>` — returns `null` and carries a Symbol\n * the parent uses to identify it inside `children`. The actual minimap is\n * mounted by `NodeGraph` itself as `<NodeGraphMinimapInner>`.\n *\n * Pure marker, no hooks — kept out of any `'use client'` boundary so RSC\n * frameworks can keep the type-level slot identity on the server.\n */\nconst NodeGraphMinimapImpl: (props: NodeGraphMinimapSlotProps) => null = () =>\n null;\n\nexport const NodeGraphMinimap = Object.assign(NodeGraphMinimapImpl, {\n displayName: 'NodeGraph.Minimap',\n [NODE_GRAPH_SLOT]: 'minimap' as const,\n}) as unknown as ((props: NodeGraphMinimapSlotProps) => null) &\n NodeGraphSlotMarker & { displayName: string };\n\n/**\n * Marker behind `<NodeGraph.Background>` — see {@link NodeGraphMinimap} for\n * the rationale. The parent picks this up and mounts the background canvas\n * layer in the correct position in the layer stack.\n */\nconst NodeGraphBackgroundImpl: (\n props: NodeGraphBackgroundSlotProps\n) => null = () => null;\n\nexport const NodeGraphBackground = Object.assign(NodeGraphBackgroundImpl, {\n displayName: 'NodeGraph.Background',\n [NODE_GRAPH_SLOT]: 'background' as const,\n}) as unknown as ((props: NodeGraphBackgroundSlotProps) => null) &\n NodeGraphSlotMarker & { displayName: string };\n\n/**\n * Marker behind `<NodeGraph.Toolbar>` — picked up from `<NodeGraph>` children\n * and mounted into the viewport overlay layer at the requested\n * placement. Children render via `<NodeGraphToolbarInner>` which keeps\n * the toolbar positioned over the graph.\n */\nconst NodeGraphToolbarImpl: (props: NodeGraphToolbarSlotProps) => null = () =>\n null;\n\nexport const NodeGraphToolbar = Object.assign(NodeGraphToolbarImpl, {\n displayName: 'NodeGraph.Toolbar',\n [NODE_GRAPH_SLOT]: 'toolbar' as const,\n}) as unknown as ((props: NodeGraphToolbarSlotProps) => null) &\n NodeGraphSlotMarker & { displayName: string };\n\n/**\n * Marker behind `<NodeGraph.SpawnPalette>` — picked up by the parent\n * and mounted as a portal-rendered fuzzy-search palette. Auto-opens on\n * right-click of empty / group targets via the store's spawn-request\n * channel.\n */\nconst NodeGraphSpawnPaletteImpl: (\n props: NodeGraphSpawnPaletteSlotProps\n) => null = () => null;\n\nexport const NodeGraphSpawnPalette = Object.assign(NodeGraphSpawnPaletteImpl, {\n displayName: 'NodeGraph.SpawnPalette',\n [NODE_GRAPH_SLOT]: 'spawn-palette' as const,\n}) as unknown as ((props: NodeGraphSpawnPaletteSlotProps) => null) &\n NodeGraphSlotMarker & { displayName: string };\n"],"names":[],"mappings":";;AAUA;;;;;;;AAOG;AACH,MAAM,oBAAoB,GAA+C,MACvE,IAAI;MAEO,gBAAgB,GAAG,MAAM,CAAC,MAAM,CAAC,oBAAoB,EAAE;AAClE,IAAA,WAAW,EAAE,mBAAmB;IAChC,CAAC,eAAe,GAAG,SAAkB;AACtC,CAAA;AAGD;;;;AAIG;AACH,MAAM,uBAAuB,GAEjB,MAAM,IAAI;MAET,mBAAmB,GAAG,MAAM,CAAC,MAAM,CAAC,uBAAuB,EAAE;AACxE,IAAA,WAAW,EAAE,sBAAsB;IACnC,CAAC,eAAe,GAAG,YAAqB;AACzC,CAAA;AAGD;;;;;AAKG;AACH,MAAM,oBAAoB,GAA+C,MACvE,IAAI;MAEO,gBAAgB,GAAG,MAAM,CAAC,MAAM,CAAC,oBAAoB,EAAE;AAClE,IAAA,WAAW,EAAE,mBAAmB;IAChC,CAAC,eAAe,GAAG,SAAkB;AACtC,CAAA;AAGD;;;;;AAKG;AACH,MAAM,yBAAyB,GAEnB,MAAM,IAAI;MAET,qBAAqB,GAAG,MAAM,CAAC,MAAM,CAAC,yBAAyB,EAAE;AAC5E,IAAA,WAAW,EAAE,wBAAwB;IACrC,CAAC,eAAe,GAAG,eAAwB;AAC5C,CAAA;;;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NodeGraphSpawnPalette.js","sources":["
|
|
1
|
+
{"version":3,"file":"NodeGraphSpawnPalette.js","sources":["src/components/editor/NodeGraph/NodeGraphSpawnPalette.tsx"],"sourcesContent":["'use client';\n\nimport React, { useCallback, useEffect, useMemo, useState } from 'react';\n\nimport { CommandPalette } from '@/components/feedback/CommandPalette/CommandPalette';\n\nimport { useNodeGraphStore } from './NodeGraphContext';\nimport { generateNodeId } from './nodeGraphIds';\n\nimport type {\n NodeGraphNode,\n NodeGraphSpawnContext,\n NodeGraphSpawnPaletteSlotProps,\n} from './NodeGraph.types';\nimport type { CommandItem } from '@/components/feedback/CommandPalette/CommandPalette.types';\nimport type { Point2D } from '@/components/primitives/canvas/canvas.types';\n\n/**\n * Live renderer for `<NodeGraph.SpawnPalette>`. Mounted by the parent\n * when the slot is present in `<NodeGraph>` children. Subscribes to the\n * store's spawn-request channel — the main `NodeGraph` component fires\n * the request when the user right-clicks empty space or a group, so\n * the consumer doesn't need to wire context-menu state through React.\n *\n * Selection on a template:\n * 1. Calls `template.build(worldPoint)` to get the node body\n * 2. Assigns a unique id\n * 3. Hands the full node to `onSpawn(node, ctx)` — typically appends\n * to the consumer's `nodes` state\n * 4. Closes the palette\n */\nexport function NodeGraphSpawnPaletteInner({\n templates,\n onSpawn,\n placeholder = 'Search nodes…',\n recentKey,\n width = 400,\n maxHeight = 360,\n}: NodeGraphSpawnPaletteSlotProps): React.ReactElement {\n const store = useNodeGraphStore();\n const [open, setOpen] = useState(false);\n const [spawnAt, setSpawnAt] = useState<{\n worldPoint: Point2D;\n screenPoint: Point2D;\n } | null>(null);\n\n // Listen for \"user wants to spawn a node here\" pings from the main\n // NodeGraph component (fired from its handleContextMenu for empty /\n // group targets). Multiple subscribers is fine — the store fans out.\n useEffect(() => {\n return store.subscribeSpawnRequest(info => {\n setSpawnAt(info);\n setOpen(true);\n });\n }, [store]);\n\n const items = useMemo<CommandItem[]>(() => {\n return templates.map(t => ({\n id: t.id,\n label: t.title,\n ...(t.subtitle !== undefined ? { description: t.subtitle } : {}),\n ...(t.group !== undefined ? { group: t.group } : {}),\n ...(t.keywords !== undefined ? { keywords: [...t.keywords] } : {}),\n ...(t.icon !== undefined ? { icon: t.icon } : {}),\n }));\n }, [templates]);\n\n const handleClose = useCallback(() => {\n setOpen(false);\n setSpawnAt(null);\n }, []);\n\n const handleSelect = useCallback(\n (item: CommandItem): void => {\n const template = templates.find(t => t.id === item.id);\n if (!template || !spawnAt) return;\n const worldPoint = spawnAt.worldPoint;\n const draft = template.build(worldPoint);\n const node: NodeGraphNode = {\n ...draft,\n id: generateNodeId(template.id),\n };\n const ctx: NodeGraphSpawnContext = {\n worldPoint,\n screenPoint: spawnAt.screenPoint,\n };\n onSpawn(node, ctx);\n handleClose();\n },\n [templates, spawnAt, onSpawn, handleClose]\n );\n\n return (\n <CommandPalette\n open={open}\n onClose={handleClose}\n items={items}\n onSelect={handleSelect}\n placeholder={placeholder}\n {...(recentKey !== undefined ? { recentKey } : {})}\n width={width}\n maxHeight={maxHeight}\n />\n );\n}\n"],"names":[],"mappings":";;;;;;;AAiBA;;;;;;;;;;;;;AAaG;AACG;AAQJ;;;;;;;AAWE;;;AAGA;AACF;AAEA;;;;;;;;AAQG;AACH;AAEA;;;;AAKA;AAEI;AACA;;AACA;;AAEA;AACE;AACA;;AAEF;;;;AAIA;AACA;;;AAiBN;;"}
|