entangle-ui 0.10.0 → 0.12.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 +145 -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/controls/Slider/{Slider.css.ts.vanilla-Cqm3fQ0S.css → Slider.css.ts.vanilla-CLf_RC0A.css} +50 -47
- 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/assets/src/components/navigation/Menu/{Menu.css.ts.vanilla-B5iVe5S2.css → Menu.css.ts.vanilla-Bhh1L5yl.css} +14 -11
- package/dist/esm/assets/src/components/primitives/Tooltip/{Tooltip.css.ts.vanilla-CrEFoLBX.css → Tooltip.css.ts.vanilla-BqaiGrFQ.css} +12 -9
- package/dist/esm/assets/src/components/shell/AppShell/{AppShell.css.ts.vanilla-BYmtU0O_.css → AppShell.css.ts.vanilla-BLK2-DRL.css} +1 -0
- package/dist/esm/assets/src/components/shell/MenuBar/{MenuBar.css.ts.vanilla-TgPcl4yA.css → MenuBar.css.ts.vanilla-Dt3w4f-q.css} +18 -6
- 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.css.js +10 -11
- package/dist/esm/components/controls/Slider/Slider.css.js.map +1 -1
- package/dist/esm/components/controls/Slider/Slider.js +40 -7
- 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 +12 -3
- 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 +2 -2
- package/dist/esm/components/navigation/ContextMenu/ContextMenu.js.map +1 -1
- package/dist/esm/components/navigation/Menu/Menu.css.js +11 -10
- package/dist/esm/components/navigation/Menu/Menu.css.js.map +1 -1
- package/dist/esm/components/navigation/Menu/Menu.js +2 -2
- 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 +4 -2
- 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.css.js +6 -5
- package/dist/esm/components/primitives/Tooltip/Tooltip.css.js.map +1 -1
- package/dist/esm/components/primitives/Tooltip/Tooltip.js +12 -2
- 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 +6 -3
- 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.css.js +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.css.js +7 -6
- package/dist/esm/components/shell/MenuBar/MenuBar.css.js.map +1 -1
- package/dist/esm/components/shell/MenuBar/MenuBar.js +52 -3
- 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.css.js +3 -0
- package/dist/esm/styles.css.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/controls/Slider/Slider.d.ts +7 -0
- package/dist/types/components/editor/PropertyInspector/PropertyInspector.types.d.ts +49 -3
- package/dist/types/components/feedback/Spinner/Spinner.d.ts +6 -1
- package/dist/types/components/feedback/Spinner/Spinner.types.d.ts +8 -0
- package/dist/types/components/primitives/Button/Button.d.ts +1 -1
- package/dist/types/components/primitives/IconButton/IconButton.d.ts +3 -2
- package/dist/types/components/primitives/viewport/Viewport.d.ts +5 -2
- package/dist/types/components/shell/MenuBar/MenuBar.d.ts +862 -0
- 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":"Combobox.js","sources":["../../../../../../src/components/controls/Combobox/Combobox.tsx"],"sourcesContent":[null],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;AAkDA;AACE;AACA;AACA;;AAGF;AAOA;AAIE;AACA;AACF;AAEA;AAKE;AAAY;;AAGZ;AACE;;;AAGE;;;;;;;;AAMA;;;;;AAIJ;AACA;AACF;AAEA;;;;;;;;;;;;;;;;;;;AAmBG;AACG;AAqCJ;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;;AAIA;;;;AAIE;AACD;AAED;;AAEsB;AAClB;AACA;AACF;AAIF;;;;;;;;AAQA;;;;AAKE;AACE;;;;AAKJ;;;;AAKA;;;;;;;;;;AAmBA;AACE;AAAoB;AACpB;;AAEF;;;AAKE;AACA;;;;;;AAOF;AACE;;;AAGI;AACA;AACD;;AAEH;;AAGF;;;AAEE;;;AAEA;;AAGF;AACE;;;AAEA;AACF;AAEA;;;AAGI;;AAEE;;;;;;AAGA;;;;AAMF;;;AAMF;;AAEA;AACA;AACD;AAED;AACE;AACA;;AACA;;AAEA;AAGA;;AAGI;AAEJ;;AAEE;AACA;AACE;;AAEI;AACD;AACH;AACH;AACH;;AAGE;;AACA;AACF;;;AAIE;;AACA;AACE;;;AAKE;;AAEJ;AACA;AACA;AACE;AACF;AACF;AAEA;AAEI;;;AAGA;AACA;AAAa;;;;;AAKf;AAIF;;;AAII;;;AAGI;;;;AAKJ;;AAEE;;AAEE;AACE;;;;;;;;;AAMF;;;;;;AAOF;;;;AAGE;;;;AAKJ;AACF;;;;;;;;;;;;;AAcC;AAGH;;AAEE;AAAiB;AACnB;AAEA;;;;;AAKI;;;AAIJ;;;;;;AAOI;AACF;AAIF;;;AAEE;AAAY;;AACP;AACL;AACF;AAEA;AACA;;;AAII;;AAMF;AACE;;;AAGA;;AAEA;AACA;AACA;AAUQ;AACA;AACA;;;AAMJ;AAEE;;;;AAIF;AAEE;AAAiB;AACnB;;AAMI;AAYV;AACF;AAEA;AACA;;;;;;;AAuBO;;AAaK;;;AAsDN;AA8BR;AAEA;;"}
|
|
1
|
+
{"version":3,"file":"Combobox.js","sources":["src/components/controls/Combobox/Combobox.tsx"],"sourcesContent":["'use client';\n\nimport React, {\n useCallback,\n useDeferredValue,\n useEffect,\n useId,\n useMemo,\n useRef,\n useState,\n} from 'react';\nimport { createPortal } from 'react-dom';\n\nimport { fuzzyScore } from '@/components/feedback/CommandPalette/fuzzySearch';\nimport { Spinner } from '@/components/feedback/Spinner';\nimport { FormHelperText } from '@/components/form/FormHelperText';\nimport { FormLabel } from '@/components/form/FormLabel';\nimport { CheckIcon } from '@/components/Icons/CheckIcon';\nimport { ChevronDownIcon } from '@/components/Icons/ChevronDownIcon';\nimport { CloseIcon } from '@/components/Icons/CloseIcon';\nimport { ScrollArea } from '@/components/layout/ScrollArea';\nimport { useControlledState } from '@/hooks/useControlledState';\nimport { useLatest } from '@/hooks/useLatest';\nimport { useListboxNav } from '@/hooks/useListboxNav';\nimport { useMergedRef } from '@/hooks/useMergedRef';\nimport { cx } from '@/utils/cx';\n\nimport {\n checkmarkStyle,\n chevronButtonStyle,\n chevronIconRecipe,\n clearButtonStyle,\n containerStyle,\n createRowStyle,\n dropdownStyle,\n emptyMessageStyle,\n inputStyle,\n inputWrapperRecipe,\n optionDescriptionStyle,\n optionItemRecipe,\n optionLabelStyle,\n optionsListStyle,\n} from './Combobox.css';\n\nimport type {\n ComboboxOption,\n ComboboxProps,\n ComboboxSize,\n} from './Combobox.types';\n\nconst CHEVRON_SIZES: Record<ComboboxSize, number> = {\n sm: 10,\n md: 12,\n lg: 14,\n};\n\nconst CREATE_ROW_VALUE = '__combobox_create__';\n\ninterface FilteredEntry<T extends string> {\n option: ComboboxOption<T>;\n score: number;\n}\n\nfunction defaultFilter<T extends string>(\n option: ComboboxOption<T>,\n query: string\n): number {\n const haystack = `${option.label ?? option.value} ${option.description ?? ''}`;\n return fuzzyScore(query, haystack);\n}\n\nfunction applyFilter<T extends string>(\n options: ComboboxOption<T>[],\n query: string,\n custom?: (option: ComboboxOption<T>, query: string) => boolean | number\n): ComboboxOption<T>[] {\n if (!query) return options;\n\n const scored: FilteredEntry<T>[] = [];\n for (const option of options) {\n let score: number;\n if (custom) {\n const result = custom(option, query);\n if (typeof result === 'boolean') {\n score = result ? 1 : 0;\n } else {\n score = result;\n }\n } else {\n score = defaultFilter(option, query);\n }\n if (score > 0) scored.push({ option, score });\n }\n scored.sort((a, b) => b.score - a.score);\n return scored.map(entry => entry.option);\n}\n\n/**\n * Single-value select with an editable input. Filters the option list as the\n * user types (built-in fuzzy matcher, configurable via `filterFn`), supports\n * controlled and uncontrolled modes, optional `freeSolo` typing, optional\n * `creatable` mode for adding new entries, and an `loading` state for async\n * data sources.\n *\n * @example\n * ```tsx\n * <Combobox\n * label=\"Framework\"\n * options={[\n * { value: 'react', label: 'React' },\n * { value: 'vue', label: 'Vue' },\n * ]}\n * value={framework}\n * onChange={setFramework}\n * />\n * ```\n */\nexport function Combobox<T extends string = string>({\n value,\n defaultValue,\n options,\n placeholder,\n filterFn,\n emptyMessage = 'No results found',\n loading = false,\n loadingMessage = 'Loading...',\n freeSolo = false,\n creatable = false,\n createLabel,\n openOnFocus = false,\n size = 'md',\n variant = 'default',\n label,\n helperText,\n error = false,\n errorMessage,\n disabled = false,\n required = false,\n readOnly = false,\n clearable = false,\n maxDropdownHeight = 240,\n minDropdownWidth,\n name,\n onChange,\n onInputChange,\n onCreate,\n onOpenChange,\n className,\n style,\n testId,\n ref,\n id: idProp,\n ...rest\n}: ComboboxProps<T>) {\n const autoId = useId();\n const fieldId = idProp ?? autoId;\n const labelId = `${fieldId}-label`;\n const helperId = `${fieldId}-helper`;\n const listboxId = `${fieldId}-listbox`;\n\n const inputRef = useRef<HTMLInputElement | null>(null);\n const wrapperRef = useRef<HTMLDivElement | null>(null);\n const dropdownRef = useRef<HTMLDivElement | null>(null);\n\n const setInputRef = useMergedRef(inputRef, ref);\n\n const [selected, setSelected] = useControlledState<T | null>({\n value,\n defaultValue,\n onChange,\n fallback: null,\n });\n\n const labelOf = useCallback(\n (val: T | null): string => {\n if (val === null) return '';\n const found = options.find(o => o.value === val);\n return found?.label ?? val;\n },\n [options]\n );\n\n const [query, setQuery] = useState<string>(() => labelOf(selected));\n const [isOpen, setIsOpen] = useState(false);\n const [focused, setFocused] = useState(false);\n const [dropdownPos, setDropdownPos] = useState<React.CSSProperties>({});\n // Editing flag is reactive (drives the filter memo below). The latest-ref\n // mirror lets the selected-sync effect read it without re-running when the\n // flag toggles.\n const [isEditing, setIsEditing] = useState(false);\n const isEditingRef = useLatest(isEditing);\n\n // Sync the query with the selected label when it changes externally and the\n // user is not actively typing.\n useEffect(() => {\n if (!isEditingRef.current) {\n setQuery(labelOf(selected));\n }\n }, [labelOf, selected, isEditingRef]);\n\n const effectiveCreatable = creatable;\n const effectiveFreeSolo = freeSolo || creatable;\n\n // Defer the query that drives filtering so the input stays responsive while\n // a large option list re-filters. The immediate `query` still drives the\n // input value and the create-row label below.\n const deferredQuery = useDeferredValue(query);\n\n const filteredOptions = useMemo(\n () =>\n applyFilter(\n options,\n // When the user hasn't typed yet (query equals the selected label) we\n // show the full list so the dropdown serves as a browseable menu.\n isEditing ? deferredQuery : '',\n filterFn\n ),\n [filterFn, options, deferredQuery, isEditing]\n );\n\n // Detect exact matches against the deferred query so this stays in lockstep\n // with `filteredOptions`. Using the immediate `query` here would desync\n // which rows *exist* from which rows *render*: backspacing to an exact match\n // flashes the create row / \"No results\" for a frame, and the navItems length\n // mismatch skews the useListboxNav and aria-activedescendant indices.\n const hasExactMatch = useMemo(() => {\n if (!deferredQuery) return false;\n const q = deferredQuery.toLowerCase();\n return options.some(o => (o.label ?? o.value).toLowerCase() === q);\n }, [options, deferredQuery]);\n\n const showCreateRow =\n effectiveCreatable &&\n deferredQuery.length > 0 &&\n !hasExactMatch &&\n !loading;\n\n // The list passed to useListboxNav. The \"create\" row is appended at the\n // end as a synthetic option so the same navigation works. Its existence is\n // gated by the deferred-query generation above, so navItems matches the\n // rendered rows exactly; only its label tracks the immediate `query` — that\n // is the literal text `commitOption` will create.\n const navItems = useMemo<ComboboxOption<T>[]>(() => {\n const list = [...filteredOptions];\n if (showCreateRow) {\n list.push({\n value: CREATE_ROW_VALUE as T,\n label: query,\n });\n }\n return list;\n }, [filteredOptions, query, showCreateRow]);\n\n const open = useCallback(() => {\n if (disabled || readOnly) return;\n if (isOpen) return;\n setIsOpen(true);\n onOpenChange?.(true);\n }, [disabled, readOnly, isOpen, onOpenChange]);\n\n const close = useCallback(() => {\n if (!isOpen) return;\n setIsOpen(false);\n onOpenChange?.(false);\n }, [isOpen, onOpenChange]);\n\n const commitOption = useCallback(\n (opt: ComboboxOption<T>) => {\n if (opt.disabled) return;\n if (opt.value === (CREATE_ROW_VALUE as T)) {\n const created = query;\n if (onCreate) onCreate(created);\n else setSelected(created as T);\n } else {\n setSelected(opt.value);\n }\n setIsEditing(false);\n setQuery(\n opt.value === (CREATE_ROW_VALUE as T) ? query : (opt.label ?? opt.value)\n );\n close();\n },\n [close, onCreate, query, setSelected]\n );\n\n const listbox = useListboxNav<ComboboxOption<T>>({\n items: navItems,\n isItemDisabled: opt => Boolean(opt.disabled),\n onSelect: commitOption,\n onEscape: close,\n });\n\n const updateDropdownPosition = useCallback(() => {\n const wrapper = wrapperRef.current;\n if (!wrapper) return;\n const rect = wrapper.getBoundingClientRect();\n const spaceBelow = window.innerHeight - rect.bottom;\n const openAbove =\n spaceBelow < maxDropdownHeight + 8 && rect.top > spaceBelow;\n\n const dropdownW =\n minDropdownWidth !== undefined\n ? Math.max(rect.width, minDropdownWidth)\n : rect.width;\n\n setDropdownPos({\n left: rect.left,\n width: dropdownW,\n ...(openAbove\n ? {\n bottom: window.innerHeight - rect.top + 4,\n transformOrigin: 'bottom',\n }\n : { top: rect.bottom + 4, transformOrigin: 'top' }),\n });\n }, [maxDropdownHeight, minDropdownWidth]);\n\n useEffect(() => {\n if (!isOpen) return;\n updateDropdownPosition();\n }, [isOpen, updateDropdownPosition]);\n\n // Outside click\n useEffect(() => {\n if (!isOpen) return;\n const handleClick = (e: MouseEvent) => {\n const target = e.target as Node;\n if (\n !wrapperRef.current?.contains(target) &&\n !dropdownRef.current?.contains(target)\n ) {\n close();\n }\n };\n document.addEventListener('mousedown', handleClick);\n return () => {\n document.removeEventListener('mousedown', handleClick);\n };\n }, [close, isOpen]);\n\n const handleInputChange = useCallback(\n (event: React.ChangeEvent<HTMLInputElement>) => {\n const next = event.target.value;\n setIsEditing(true);\n setQuery(next);\n onInputChange?.(next);\n if (!isOpen) open();\n // Clearing the input clears the selection.\n if (next === '' && selected !== null) {\n setSelected(null);\n }\n },\n [isOpen, onInputChange, open, selected, setSelected]\n );\n\n const handleInputKeyDown = useCallback(\n (event: React.KeyboardEvent<HTMLInputElement>) => {\n if (disabled || readOnly) return;\n\n if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {\n if (!isOpen) {\n event.preventDefault();\n open();\n return;\n }\n }\n\n if (event.key === 'Enter') {\n // freeSolo / creatable take precedence when no suggestion is active.\n if (effectiveFreeSolo && listbox.activeIndex < 0 && query.length > 0) {\n event.preventDefault();\n if (effectiveCreatable && !hasExactMatch) {\n if (onCreate) onCreate(query);\n else setSelected(query as T);\n } else {\n setSelected(query as T);\n }\n setIsEditing(false);\n close();\n return;\n }\n }\n\n if (event.key === 'Tab' && isOpen) {\n // Commit the active suggestion if any, otherwise close.\n if (listbox.activeIndex >= 0) {\n listbox.selectActive();\n } else {\n close();\n }\n return;\n }\n\n listbox.handleKeyDown(event);\n },\n [\n close,\n disabled,\n effectiveCreatable,\n effectiveFreeSolo,\n hasExactMatch,\n isOpen,\n listbox,\n onCreate,\n open,\n query,\n readOnly,\n setSelected,\n ]\n );\n\n const handleFocus = useCallback(() => {\n setFocused(true);\n if (openOnFocus) open();\n }, [open, openOnFocus]);\n\n const handleBlur = useCallback(() => {\n setFocused(false);\n setIsEditing(false);\n // Restore the last selected label unless freeSolo allowed the typed value.\n if (!effectiveFreeSolo) {\n setQuery(labelOf(selected));\n }\n }, [effectiveFreeSolo, labelOf, selected]);\n\n const handleClear = useCallback(\n (event: React.MouseEvent) => {\n event.preventDefault();\n event.stopPropagation();\n setSelected(null);\n setQuery('');\n setIsEditing(false);\n inputRef.current?.focus();\n },\n [setSelected]\n );\n\n const handleToggleClick = useCallback(() => {\n if (disabled || readOnly) return;\n if (isOpen) close();\n else open();\n inputRef.current?.focus();\n }, [close, disabled, isOpen, open, readOnly]);\n\n const showHelperText = error && errorMessage ? errorMessage : helperText;\n const chevronSize = CHEVRON_SIZES[size];\n\n const renderOptions = () => {\n if (loading) {\n return (\n <div className={emptyMessageStyle}>\n <Spinner size=\"xs\" /> {loadingMessage}\n </div>\n );\n }\n if (navItems.length === 0) {\n return <div className={emptyMessageStyle}>{emptyMessage}</div>;\n }\n return navItems.map((opt, idx) => {\n const isCreate = opt.value === (CREATE_ROW_VALUE as T);\n const isSelected = !isCreate && opt.value === selected;\n const isActive = idx === listbox.activeIndex;\n const isDisabled = opt.disabled ?? false;\n return (\n <div\n key={isCreate ? `create-${query}` : `${opt.value}-${String(idx)}`}\n role=\"option\"\n aria-selected={isSelected}\n aria-disabled={isDisabled || undefined}\n id={`${listboxId}-${String(idx)}`}\n tabIndex={-1}\n className={cx(\n optionItemRecipe({\n active: isActive,\n selected: isSelected,\n disabled: isDisabled,\n }),\n isCreate && createRowStyle\n )}\n onClick={() => {\n commitOption(opt);\n }}\n onKeyDown={e => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n commitOption(opt);\n }\n }}\n onMouseEnter={() => {\n if (!isDisabled) listbox.setActiveIndex(idx);\n }}\n >\n {opt.icon && <span>{opt.icon}</span>}\n <span className={optionLabelStyle}>\n {isCreate\n ? (createLabel?.(query) ?? <>Create "{query}"</>)\n : (opt.label ?? opt.value)}\n </span>\n {opt.description && !isCreate && (\n <span className={optionDescriptionStyle}>{opt.description}</span>\n )}\n {isSelected && (\n <span className={checkmarkStyle}>\n <CheckIcon size={10} decorative />\n </span>\n )}\n </div>\n );\n });\n };\n\n const formValue = selected ?? '';\n const displayValue = focused || isOpen ? query : labelOf(selected);\n\n return (\n <div className={cx(containerStyle, className)} style={style}>\n {label && (\n <FormLabel\n id={labelId}\n htmlFor={fieldId}\n required={required}\n disabled={disabled}\n >\n {label}\n </FormLabel>\n )}\n\n <div\n ref={wrapperRef}\n className={inputWrapperRecipe({\n size,\n variant,\n focused,\n error,\n disabled,\n })}\n >\n <input\n ref={setInputRef}\n id={fieldId}\n type=\"text\"\n role=\"combobox\"\n aria-expanded={isOpen}\n aria-controls={isOpen ? listboxId : undefined}\n aria-autocomplete=\"list\"\n aria-activedescendant={\n isOpen && listbox.activeIndex >= 0\n ? `${listboxId}-${String(listbox.activeIndex)}`\n : undefined\n }\n aria-labelledby={label ? labelId : undefined}\n aria-required={required || undefined}\n aria-invalid={error || undefined}\n aria-describedby={showHelperText ? helperId : undefined}\n autoComplete=\"off\"\n spellCheck={false}\n disabled={disabled}\n readOnly={readOnly}\n required={required}\n placeholder={placeholder}\n value={displayValue}\n className={inputStyle}\n onChange={handleInputChange}\n onKeyDown={handleInputKeyDown}\n onFocus={handleFocus}\n onBlur={handleBlur}\n data-testid={testId}\n {...rest}\n />\n\n {clearable && selected !== null && !disabled && !readOnly && (\n <button\n type=\"button\"\n tabIndex={-1}\n aria-label=\"Clear selection\"\n className={clearButtonStyle}\n onMouseDown={handleClear}\n >\n <CloseIcon size=\"sm\" decorative />\n </button>\n )}\n\n <button\n type=\"button\"\n tabIndex={-1}\n aria-label={isOpen ? 'Close suggestions' : 'Open suggestions'}\n className={chevronButtonStyle}\n onMouseDown={e => {\n e.preventDefault();\n }}\n onClick={handleToggleClick}\n disabled={disabled}\n >\n <span className={chevronIconRecipe({ open: isOpen })}>\n <ChevronDownIcon size={chevronSize} decorative />\n </span>\n </button>\n </div>\n\n {name && <input type=\"hidden\" name={name} value={formValue} />}\n\n {isOpen &&\n createPortal(\n <div\n ref={dropdownRef}\n role=\"listbox\"\n id={listboxId}\n aria-labelledby={label ? labelId : undefined}\n className={dropdownStyle}\n style={dropdownPos}\n tabIndex={-1}\n >\n <ScrollArea\n className={optionsListStyle}\n maxHeight={maxDropdownHeight}\n scrollbarWidth={4}\n scrollbarVisibility=\"auto\"\n hideDelay={600}\n >\n {renderOptions()}\n </ScrollArea>\n </div>,\n document.body\n )}\n\n {showHelperText && (\n <FormHelperText id={helperId} error={error}>\n {showHelperText}\n </FormHelperText>\n )}\n </div>\n );\n}\n\nCombobox.displayName = 'Combobox';\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;AAkDA;AACE;AACA;AACA;;AAGF;AAOA;AAIE;AACA;AACF;AAEA;AAKE;AAAY;;AAGZ;AACE;;;AAGE;;;;;;;;AAMA;;;;;AAIJ;AACA;AACF;AAEA;;;;;;;;;;;;;;;;;;;AAmBG;AACG;AAqCJ;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;;AAIA;;;;AAIE;AACD;AAED;;AAEsB;AAClB;AACA;AACF;AAIF;;;;;;;;AAQA;;;;AAKE;AACE;;;;AAKJ;;;;AAKA;;;;;;;;;;AAmBA;AACE;AAAoB;AACpB;;AAEF;;;AAKE;AACA;;;;;;AAOF;AACE;;;AAGI;AACA;AACD;;AAEH;;AAGF;;;AAEE;;;AAEA;;AAGF;AACE;;;AAEA;AACF;AAEA;;;AAGI;;AAEE;;;;;;AAGA;;;;AAMF;;;AAMF;;AAEA;AACA;AACD;AAED;AACE;AACA;;AACA;;AAEA;AAGA;;AAGI;AAEJ;;AAEE;AACA;AACE;;AAEI;AACD;AACH;AACH;AACH;;AAGE;;AACA;AACF;;;AAIE;;AACA;AACE;;;AAKE;;AAEJ;AACA;AACA;AACE;AACF;AACF;AAEA;AAEI;;;AAGA;AACA;AAAa;;;;;AAKf;AAIF;;;AAII;;;AAGI;;;;AAKJ;;AAEE;;AAEE;AACE;;;;;;;;;AAMF;;;;;;AAOF;;;;AAGE;;;;AAKJ;AACF;;;;;;;;;;;;;AAcC;AAGH;;AAEE;AAAiB;AACnB;AAEA;;;;;AAKI;;;AAIJ;;;;;;AAOI;AACF;AAIF;;;AAEE;AAAY;;AACP;AACL;AACF;AAEA;AACA;;;AAII;;AAMF;AACE;;;AAGA;;AAEA;AACA;AACA;AAUQ;AACA;AACA;;;AAMJ;AAEE;;;;AAIF;AAEE;AAAiB;AACnB;;AAMI;AAYV;AACF;AAEA;AACA;;;;;;;AAuBO;;AAaK;;;AAsDN;AA8BR;AAEA;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CurveCanvas.js","sources":["
|
|
1
|
+
{"version":3,"file":"CurveCanvas.js","sources":["src/components/controls/CurveEditor/CurveCanvas.tsx"],"sourcesContent":["'use client';\n\nimport { assignInlineVars } from '@vanilla-extract/dynamic';\nimport React, { useRef, useEffect, useState } from 'react';\n\nimport {\n canvasContainerRecipe,\n canvasRecipe,\n ariaLiveRegionStyle,\n canvasHeightVar,\n} from './CurveEditor.css';\nimport { useCurveRenderer } from './useCurveRenderer';\n\nimport type {\n CurveData,\n CurveViewport,\n CurveHitTest,\n CurveEditorSize,\n CurveBackgroundInfo,\n} from './CurveEditor.types';\n\ninterface CurveCanvasProps {\n canvasRef: React.RefObject<HTMLCanvasElement | null>;\n curve: CurveData;\n viewport: CurveViewport;\n showGrid: boolean;\n gridSubdivisions: number;\n showAxisLabels: boolean;\n labelX?: string;\n labelY?: string;\n curveColor: string;\n curveWidth: number;\n renderBackground?: (\n ctx: CanvasRenderingContext2D,\n info: CurveBackgroundInfo\n ) => void;\n selectedIds: Set<string>;\n hoveredElement: CurveHitTest | null;\n selectionBox: {\n x1: number;\n y1: number;\n x2: number;\n y2: number;\n } | null;\n isDragging: boolean;\n lockTangents: boolean;\n height: number;\n responsive: boolean;\n disabled: boolean;\n size: CurveEditorSize;\n handlers: {\n onPointerDown: (e: React.PointerEvent) => void;\n onPointerMove: (e: React.PointerEvent) => void;\n onPointerUp: (e: React.PointerEvent) => void;\n onDoubleClick: (e: React.MouseEvent) => void;\n onKeyDown: (e: React.KeyboardEvent) => void;\n };\n ariaLabel?: string;\n testId?: string;\n}\n\nexport const CurveCanvas: React.FC<CurveCanvasProps> = ({\n canvasRef,\n curve,\n viewport,\n showGrid,\n gridSubdivisions,\n showAxisLabels,\n labelX,\n labelY,\n curveColor,\n curveWidth,\n renderBackground,\n selectedIds,\n hoveredElement,\n selectionBox,\n isDragging,\n lockTangents,\n height,\n responsive,\n disabled,\n handlers,\n ariaLabel,\n testId,\n}) => {\n const containerRef = useRef<HTMLDivElement>(null);\n const [resizeToken, setResizeToken] = useState(0);\n\n // ResizeObserver for responsive mode -- triggers canvas re-render\n useEffect(() => {\n if (!responsive || !containerRef.current) return;\n\n const observer = new ResizeObserver(() => {\n setResizeToken(t => t + 1);\n });\n\n observer.observe(containerRef.current);\n return () => {\n observer.disconnect();\n };\n }, [responsive]);\n\n // Renderer hook\n useCurveRenderer({\n canvasRef,\n curve,\n viewport,\n showGrid,\n gridSubdivisions,\n showAxisLabels,\n labelX,\n labelY,\n curveColor,\n curveWidth,\n renderBackground,\n selectedIds,\n hoveredElement,\n selectionBox,\n disabled,\n isDragging,\n lockTangents,\n resizeToken,\n });\n\n // Build screen reader summary\n const kfCount = curve.keyframes.length;\n const firstKf = curve.keyframes[0];\n const lastKf = curve.keyframes[kfCount - 1];\n const srSummary =\n firstKf && lastKf\n ? `Curve from (${firstKf.x}, ${firstKf.y}) to (${lastKf.x}, ${lastKf.y}) with ${kfCount} keyframes`\n : `Empty curve`;\n\n // Selection announcement\n const selectedCount = selectedIds.size;\n const selectionAnnouncement =\n selectedCount > 0\n ? `${selectedCount} keyframe${selectedCount > 1 ? 's' : ''} selected`\n : '';\n\n return (\n <div\n ref={containerRef}\n className={canvasContainerRecipe({ responsive })}\n style={{\n ...assignInlineVars({ [canvasHeightVar]: `${height}px` }),\n minHeight: `${height}px`,\n }}\n >\n <canvas\n ref={canvasRef}\n className={canvasRecipe({ disabled })}\n // The canvas is a self-contained keyboard-driven editing surface, so\n // \"application\" is the correct ARIA role. jsx-a11y maps <canvas> to an\n // interactive element and flags the (non-interactive) application role.\n // eslint-disable-next-line jsx-a11y/no-interactive-element-to-noninteractive-role\n role=\"application\"\n aria-label={ariaLabel ?? 'Curve editor'}\n aria-roledescription={`curve editor with ${kfCount} keyframes`}\n tabIndex={disabled ? -1 : 0}\n data-testid={testId ? `${testId}-canvas` : undefined}\n onPointerDown={handlers.onPointerDown}\n onPointerMove={handlers.onPointerMove}\n onPointerUp={handlers.onPointerUp}\n onDoubleClick={handlers.onDoubleClick}\n onKeyDown={handlers.onKeyDown}\n />\n <div className={ariaLiveRegionStyle} aria-live=\"polite\" role=\"status\">\n {srSummary}. {selectionAnnouncement}\n </div>\n </div>\n );\n};\n\nCurveCanvas.displayName = 'CurveCanvas';\n"],"names":[],"mappings":";;;;;;;;AAqFE;;;;AAKE;;AAEA;;AAEA;AAEA;AACA;;AAEA;AACF;;AAGA;;;;;;;;;;;;;;;;;;;AAmBC;;AAGD;;;AAGA;AAEI;;;AAIJ;AACA;AAEI;;AAGJ;;;AAOK;;;;;AASC;AAgBR;AAEA;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CurveEditor.js","sources":["
|
|
1
|
+
{"version":3,"file":"CurveEditor.js","sources":["src/components/controls/CurveEditor/CurveEditor.tsx"],"sourcesContent":["'use client';\n\nimport { assignInlineVars } from '@vanilla-extract/dynamic';\nimport React, { useState, useCallback, useMemo, useRef } from 'react';\n\nimport { vars } from '@/theme/contract.css';\nimport { cx } from '@/utils/cx';\n\nimport { CurveCanvas } from './CurveCanvas';\nimport {\n curveEditorRecipe,\n editorWidthVar,\n bottomBarStyle,\n} from './CurveEditor.css';\nimport { CURVE_PRESETS } from './curvePresets';\nimport { CurveToolbar } from './CurveToolbar';\nimport { ensureKeyframeIds, evaluateCurve } from './curveUtils';\nimport { useCurveInteraction } from './useCurveInteraction';\n\nimport type {\n CurveEditorProps,\n CurveData,\n CurveViewport,\n TangentMode,\n} from './CurveEditor.types';\n\nexport const CurveEditor: React.FC<CurveEditorProps> = ({\n value,\n defaultValue,\n width = 320,\n height = 200,\n responsive = false,\n showToolbar = true,\n showGrid = true,\n gridSubdivisions = 4,\n showAxisLabels = true,\n allowAdd = true,\n allowDelete = true,\n maxKeyframes = Infinity,\n lockEndpoints = true,\n minKeyframeDistance = 0.001,\n clampY = true,\n snapToGrid = false,\n precision = 3,\n labelX,\n labelY,\n presets: userPresets,\n size = 'md',\n disabled = false,\n readOnly = false,\n curveColor,\n curveWidth = 2,\n renderBackground,\n renderBottomBar,\n lockTangents = false,\n onChange,\n onChangeComplete,\n onSelectionChange,\n className,\n testId,\n id,\n style,\n ...rest\n}) => {\n // For curveColor fallback, use the CSS variable reference directly.\n // At runtime, getComputedStyle on the canvas element will resolve it.\n // We pass the raw CSS var reference string which will be resolved by the canvas renderer.\n const effectiveCurveColor = curveColor ?? vars.colors.accent.primary;\n const canvasRef = useRef<HTMLCanvasElement>(null);\n\n // Controlled / uncontrolled -- defaults to ease-in-out\n const [internalValue, setInternalValue] = useState<CurveData>(() => {\n const easeInOut = CURVE_PRESETS.find(p => p.id === 'ease-in-out');\n const initial = defaultValue ??\n easeInOut?.curve ??\n CURVE_PRESETS[0]?.curve ?? {\n keyframes: [],\n domainX: [0, 1] as [number, number],\n domainY: [0, 1] as [number, number],\n };\n return { ...initial, keyframes: ensureKeyframeIds(initial.keyframes) };\n });\n\n const isControlled = value !== undefined;\n const curve = useMemo(() => {\n const c = isControlled ? value : internalValue;\n return { ...c, keyframes: ensureKeyframeIds(c.keyframes) };\n }, [isControlled, value, internalValue]);\n\n const handleChange = useCallback(\n (newCurve: CurveData) => {\n if (!isControlled) {\n setInternalValue(newCurve);\n }\n onChange?.(newCurve);\n },\n [isControlled, onChange]\n );\n\n const handleChangeComplete = useCallback(\n (newCurve: CurveData) => {\n onChangeComplete?.(newCurve);\n },\n [onChangeComplete]\n );\n\n // Viewport -- always matches domain with padding for labels (no user zoom/pan)\n const viewport = useMemo((): CurveViewport => {\n const axisName = labelX ?? labelY;\n const hasAxisNames = Boolean(axisName);\n const padding = hasAxisNames && showAxisLabels ? 0.12 : 0.08;\n const [dxMin, dxMax] = curve.domainX;\n const [dyMin, dyMax] = curve.domainY;\n const xPad = (dxMax - dxMin) * padding;\n const yPad = (dyMax - dyMin) * padding;\n return {\n viewX: [dxMin - xPad, dxMax + xPad] as [number, number],\n viewY: [dyMin - yPad, dyMax + yPad] as [number, number],\n zoom: 1,\n panX: 0,\n panY: 0,\n };\n }, [curve.domainX, curve.domainY, labelX, labelY, showAxisLabels]);\n\n // Merge presets\n const allPresets = useMemo(\n () => (userPresets ? [...CURVE_PRESETS, ...userPresets] : CURVE_PRESETS),\n [userPresets]\n );\n\n // Interaction hook\n const interaction = useCurveInteraction({\n curve,\n viewport,\n canvasRef,\n disabled,\n readOnly,\n allowAdd,\n allowDelete,\n maxKeyframes,\n lockEndpoints,\n lockTangents,\n clampY,\n snapToGrid,\n gridSubdivisions,\n minKeyframeDistance,\n precision,\n onChange: handleChange,\n onChangeComplete: handleChangeComplete,\n onSelectionChange,\n });\n\n // Compute tangent mode for selected keyframes (null if mixed or none selected)\n const selectedTangentMode = useMemo((): TangentMode | null => {\n if (interaction.selectedIds.size === 0) return null;\n\n let mode: TangentMode | null = null;\n for (const kf of curve.keyframes) {\n if (kf.id && interaction.selectedIds.has(kf.id)) {\n if (mode === null) {\n mode = kf.tangentMode;\n } else if (mode !== kf.tangentMode) {\n return null; // Mixed modes\n }\n }\n }\n return mode;\n }, [curve.keyframes, interaction.selectedIds]);\n\n const handlePresetSelect = useCallback(\n (preset: { curve: CurveData }) => {\n const newCurve = {\n ...preset.curve,\n keyframes: ensureKeyframeIds(preset.curve.keyframes),\n };\n handleChange(newCurve);\n handleChangeComplete(newCurve);\n },\n [handleChange, handleChangeComplete]\n );\n\n return (\n <div\n className={cx(curveEditorRecipe({ disabled, responsive }), className)}\n style={{\n ...style,\n ...(!responsive\n ? assignInlineVars({ [editorWidthVar]: `${width}px` })\n : {}),\n }}\n data-testid={testId}\n id={id}\n {...rest}\n >\n {showToolbar && (\n <CurveToolbar\n selectedTangentMode={selectedTangentMode}\n onTangentModeChange={interaction.setTangentMode}\n onPresetSelect={handlePresetSelect}\n presets={allPresets}\n disabled={disabled}\n lockTangents={lockTangents}\n size={size}\n testId={testId}\n />\n )}\n <CurveCanvas\n canvasRef={canvasRef}\n curve={curve}\n viewport={viewport}\n showGrid={showGrid}\n gridSubdivisions={gridSubdivisions}\n showAxisLabels={showAxisLabels}\n labelX={labelX}\n labelY={labelY}\n curveColor={effectiveCurveColor}\n curveWidth={curveWidth}\n renderBackground={renderBackground}\n selectedIds={interaction.selectedIds}\n hoveredElement={interaction.hoveredElement}\n selectionBox={interaction.selectionBox}\n isDragging={interaction.isDragging}\n lockTangents={lockTangents}\n height={height}\n responsive={responsive}\n disabled={disabled}\n size={size}\n handlers={interaction.handlers}\n testId={testId}\n />\n {renderBottomBar && (\n <div\n className={bottomBarStyle}\n data-testid={testId ? `${testId}-bottom-bar` : undefined}\n >\n {renderBottomBar({\n curve,\n selectedIds: Array.from(interaction.selectedIds),\n selectedKeyframes: curve.keyframes.filter(\n kf => kf.id && interaction.selectedIds.has(kf.id)\n ),\n evaluate: (x: number) => evaluateCurve(curve, x),\n disabled,\n readOnly,\n })}\n </div>\n )}\n </div>\n );\n};\n\nCurveEditor.displayName = 'CurveEditor';\n"],"names":[],"mappings":";;;;;;;;;;;;;AA0BO;;;;;AA0CL;;;AAIE;;AAEE;AACA;AACE;AACA;AACA;;AAEJ;AACF;AAEA;AACA;;AAEE;;AAGF;;;;AAKI;AACF;AAIF;AAEI;AACF;;AAKF;AACE;AACA;AACA;;;;;;;;AAQE;AACA;AACA;;AAEJ;;AAGA;;;;;;;;;;;;;;;;;;AAsBE;AACA;;AAED;;AAGD;AACE;AAAwC;;AAGxC;AACE;AACE;AACE;;AACK;;;;;AAKX;;AAGF;AAEI;;;;;;AAMF;AAIF;AAIM;;AAEE;;AAEH;;;;;;;AAuDI;AAKX;AAEA;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CurveToolbar.js","sources":["
|
|
1
|
+
{"version":3,"file":"CurveToolbar.js","sources":["src/components/controls/CurveEditor/CurveToolbar.tsx"],"sourcesContent":["'use client';\n\nimport { assignInlineVars } from '@vanilla-extract/dynamic';\nimport React from 'react';\n\nimport { Select } from '@/components/controls/Select';\nimport { TangentAlignedIcon } from '@/components/Icons/TangentAlignedIcon';\nimport { TangentAutoIcon } from '@/components/Icons/TangentAutoIcon';\nimport { TangentFreeIcon } from '@/components/Icons/TangentFreeIcon';\nimport { TangentLinearIcon } from '@/components/Icons/TangentLinearIcon';\nimport { TangentMirroredIcon } from '@/components/Icons/TangentMirroredIcon';\nimport { TangentStepIcon } from '@/components/Icons/TangentStepIcon';\nimport { IconButton } from '@/components/primitives/IconButton';\nimport { Tooltip } from '@/components/primitives/Tooltip';\n\nimport {\n toolbarStyle,\n toolbarSectionStyle,\n toolbarSeparatorStyle,\n toolbarHeightVar,\n} from './CurveEditor.css';\n\nimport type {\n CurveEditorSize,\n CurvePreset,\n TangentMode,\n} from './CurveEditor.types';\n\ninterface CurveToolbarProps {\n selectedTangentMode: TangentMode | null;\n onTangentModeChange: (mode: TangentMode) => void;\n onPresetSelect: (preset: CurvePreset) => void;\n presets: CurvePreset[];\n disabled: boolean;\n lockTangents: boolean;\n size: CurveEditorSize;\n testId?: string;\n}\n\nconst TOOLBAR_HEIGHT: Record<CurveEditorSize, number> = {\n sm: 28,\n md: 32,\n lg: 36,\n};\n\nconst TANGENT_MODES: Array<{\n mode: TangentMode;\n label: string;\n icon: React.ReactNode;\n shortcut: string;\n}> = [\n {\n mode: 'free',\n label: 'Free',\n icon: <TangentFreeIcon decorative />,\n shortcut: '1',\n },\n {\n mode: 'aligned',\n label: 'Aligned',\n icon: <TangentAlignedIcon decorative />,\n shortcut: '2',\n },\n {\n mode: 'mirrored',\n label: 'Mirrored',\n icon: <TangentMirroredIcon decorative />,\n shortcut: '3',\n },\n {\n mode: 'auto',\n label: 'Auto',\n icon: <TangentAutoIcon decorative />,\n shortcut: '4',\n },\n {\n mode: 'linear',\n label: 'Linear',\n icon: <TangentLinearIcon decorative />,\n shortcut: '5',\n },\n {\n mode: 'step',\n label: 'Step',\n icon: <TangentStepIcon decorative />,\n shortcut: '6',\n },\n];\n\nexport const CurveToolbar: React.FC<CurveToolbarProps> = ({\n selectedTangentMode,\n onTangentModeChange,\n onPresetSelect,\n presets,\n disabled,\n lockTangents,\n size,\n testId,\n}) => {\n const presetOptions = presets.map(p => ({\n value: p.id,\n label: p.label,\n }));\n\n const handlePresetChange = (id: string | null) => {\n if (!id) return;\n const preset = presets.find(p => p.id === id);\n if (preset) onPresetSelect(preset);\n };\n\n const iconSize = size === 'lg' ? 'md' : 'sm';\n\n return (\n <div\n className={toolbarStyle}\n style={assignInlineVars({\n [toolbarHeightVar]: `${TOOLBAR_HEIGHT[size]}px`,\n })}\n data-testid={testId ? `${testId}-toolbar` : undefined}\n >\n {/* Preset selector */}\n <div className={toolbarSectionStyle}>\n <Select\n options={presetOptions}\n onChange={handlePresetChange}\n size={size === 'lg' ? 'md' : 'sm'}\n variant=\"ghost\"\n disabled={disabled}\n placeholder=\"Preset...\"\n aria-label=\"Curve preset\"\n minDropdownWidth={180}\n />\n </div>\n\n {!lockTangents && (\n <>\n <div className={toolbarSeparatorStyle} />\n\n {/* Tangent mode buttons */}\n <div className={toolbarSectionStyle}>\n {TANGENT_MODES.map(({ mode, label, icon, shortcut }) => (\n <Tooltip\n key={mode}\n title={`${label} (${shortcut})`}\n placement=\"bottom\"\n >\n <IconButton\n size={iconSize}\n variant=\"ghost\"\n pressed={selectedTangentMode === mode}\n disabled={disabled || selectedTangentMode === null}\n aria-label={`${label} tangent mode`}\n onClick={() => {\n onTangentModeChange(mode);\n }}\n >\n {icon}\n </IconButton>\n </Tooltip>\n ))}\n </div>\n </>\n )}\n </div>\n );\n};\n\nCurveToolbar.displayName = 'CurveToolbar';\n"],"names":[],"mappings":";;;;;;;;;;;;;;AAuCA;AACE;AACA;AACA;;AAGF;AAME;AACE;AACA;AACA;AACA;AACD;AACD;AACE;AACA;AACA;AACA;AACD;AACD;AACE;AACA;AACA;AACA;AACD;AACD;AACE;AACA;AACA;AACA;AACD;AACD;AACE;AACA;AACA;AACA;AACD;AACD;AACE;AACA;AACA;AACA;AACD;;;;;;AAgBA;AAED;AACE;;AACA;AACA;;AACF;AAEA;;;AAOK;;AAqCW;AAWlB;AAEA;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"curvePresets.js","sources":["../../../../../../src/components/controls/CurveEditor/curvePresets.ts"],"sourcesContent":[null],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"curvePresets.js","sources":["src/components/controls/CurveEditor/curvePresets.ts"],"sourcesContent":["import { generateKeyframeId } from './curveUtils';\n\nimport type { CurvePreset, CurveKeyframe } from './CurveEditor.types';\n\nfunction kf(\n x: number,\n y: number,\n hInX: number,\n hInY: number,\n hOutX: number,\n hOutY: number,\n tangentMode: CurveKeyframe['tangentMode'] = 'free'\n): CurveKeyframe {\n return {\n x,\n y,\n handleIn: { x: hInX, y: hInY },\n handleOut: { x: hOutX, y: hOutY },\n tangentMode,\n id: generateKeyframeId(),\n };\n}\n\nexport const CURVE_PRESETS: CurvePreset[] = [\n // ─── Basic ───\n {\n id: 'linear',\n label: 'Linear',\n category: 'Basic',\n curve: {\n keyframes: [\n kf(0, 0, 0, 0, 0.333, 0.333, 'linear'),\n kf(1, 1, -0.333, -0.333, 0, 0, 'linear'),\n ],\n domainX: [0, 1],\n domainY: [0, 1],\n },\n },\n {\n id: 'constant',\n label: 'Constant',\n category: 'Basic',\n curve: {\n keyframes: [\n kf(0, 1, 0, 0, 0.333, 0, 'linear'),\n kf(1, 1, -0.333, 0, 0, 0, 'linear'),\n ],\n domainX: [0, 1],\n domainY: [0, 1],\n },\n },\n\n // ─── Ease ───\n {\n id: 'ease-in',\n label: 'Ease In',\n category: 'Ease',\n curve: {\n keyframes: [kf(0, 0, 0, 0, 0.42, 0), kf(1, 1, -0.333, -0.333, 0, 0)],\n domainX: [0, 1],\n domainY: [0, 1],\n },\n },\n {\n id: 'ease-out',\n label: 'Ease Out',\n category: 'Ease',\n curve: {\n keyframes: [kf(0, 0, 0, 0, 0.333, 0.333), kf(1, 1, -0.58, 0, 0, 0)],\n domainX: [0, 1],\n domainY: [0, 1],\n },\n },\n {\n id: 'ease-in-out',\n label: 'Ease In-Out',\n category: 'Ease',\n curve: {\n keyframes: [kf(0, 0, 0, 0, 0.42, 0), kf(1, 1, -0.58, 0, 0, 0)],\n domainX: [0, 1],\n domainY: [0, 1],\n },\n },\n\n // ─── Dramatic ───\n {\n id: 'ease-in-cubic',\n label: 'Ease In (Cubic)',\n category: 'Dramatic',\n curve: {\n keyframes: [kf(0, 0, 0, 0, 0.55, 0), kf(1, 1, -0.2, -0.333, 0, 0)],\n domainX: [0, 1],\n domainY: [0, 1],\n },\n },\n {\n id: 'ease-out-cubic',\n label: 'Ease Out (Cubic)',\n category: 'Dramatic',\n curve: {\n keyframes: [kf(0, 0, 0, 0, 0.2, 0.333), kf(1, 1, -0.55, 0, 0, 0)],\n domainX: [0, 1],\n domainY: [0, 1],\n },\n },\n {\n id: 'bounce',\n label: 'Bounce',\n category: 'Dramatic',\n curve: {\n keyframes: [\n kf(0, 0, 0, 0, 0.12, 0.33),\n kf(0.5, 1.15, -0.12, 0.05, 0.12, -0.05),\n kf(0.75, 0.9, -0.08, -0.05, 0.08, 0.05),\n kf(1, 1, -0.08, 0, 0, 0),\n ],\n domainX: [0, 1],\n domainY: [0, 1],\n },\n },\n {\n id: 'overshoot',\n label: 'Overshoot',\n category: 'Dramatic',\n curve: {\n keyframes: [\n kf(0, 0, 0, 0, 0.2, 0.5),\n kf(0.7, 1.2, -0.15, 0.1, 0.1, -0.05),\n kf(1, 1, -0.1, 0, 0, 0),\n ],\n domainX: [0, 1],\n domainY: [0, 1],\n },\n },\n\n // ─── Utility ───\n {\n id: 'step',\n label: 'Step',\n category: 'Utility',\n curve: {\n keyframes: [\n kf(0, 0, 0, 0, 0, 0, 'step'),\n kf(0.5, 1, 0, 0, 0, 0, 'step'),\n kf(1, 1, 0, 0, 0, 0, 'linear'),\n ],\n domainX: [0, 1],\n domainY: [0, 1],\n },\n },\n {\n id: 'inverse',\n label: 'Inverse',\n category: 'Utility',\n curve: {\n keyframes: [\n kf(0, 1, 0, 0, 0.333, -0.333, 'linear'),\n kf(1, 0, -0.333, 0.333, 0, 0, 'linear'),\n ],\n domainX: [0, 1],\n domainY: [0, 1],\n },\n },\n {\n id: 'bell',\n label: 'Bell Curve',\n category: 'Utility',\n curve: {\n keyframes: [\n kf(0, 0, 0, 0, 0.1, 0),\n kf(0.3, 0.8, -0.1, 0.1, 0.08, 0.08),\n kf(0.5, 1, -0.08, 0.02, 0.08, -0.02, 'mirrored'),\n kf(0.7, 0.8, -0.08, 0.08, 0.1, -0.1),\n kf(1, 0, -0.1, 0, 0, 0),\n ],\n domainX: [0, 1],\n domainY: [0, 1],\n },\n },\n {\n id: 'sawtooth',\n label: 'Sawtooth',\n category: 'Utility',\n curve: {\n keyframes: [\n kf(0, 0, 0, 0, 0.15, 0.5, 'linear'),\n kf(0.5, 1, -0.15, -0.5, 0, 0, 'linear'),\n kf(0.5, 0, 0, 0, 0.15, 0.5, 'linear'),\n kf(1, 1, -0.15, -0.5, 0, 0, 'linear'),\n ],\n domainX: [0, 1],\n domainY: [0, 1],\n },\n },\n];\n"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"curveUtils.js","sources":["../../../../../../src/components/controls/CurveEditor/curveUtils.ts"],"sourcesContent":[null],"names":["sharedDomainToCanvas","sharedCanvasToDomain"],"mappings":";;;AAcA,IAAI,SAAS,GAAG,CAAC;AAEjB;;AAEG;SACa,kBAAkB,GAAA;IAChC,SAAS,IAAI,CAAC;IACd,OAAO,CAAA,GAAA,EAAM,IAAI,CAAC,GAAG,EAAE,CAAA,CAAA,EAAI,SAAS,EAAE;AACxC;AAEA;;;AAGG;AACG,SAAU,cAAc,CAC5B,EAAU,EACV,EAAU,EACV,EAAU,EACV,EAAU,EACV,CAAS,EAAA;AAET,IAAA,MAAM,EAAE,GAAG,CAAC,GAAG,CAAC;AAChB,IAAA,QACE,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE;AACjB,QAAA,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,GAAG,EAAE;AACpB,QAAA,CAAC,GAAG,EAAE,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE;AACnB,QAAA,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE;AAElB;AAkBA;;;AAGG;AACH,SAAS,gBAAgB,CACvB,EAAU,EACV,EAAU,EACV,EAAU,EACV,EAAU,EACV,CAAS,EAAA;AAET,IAAA,MAAM,EAAE,GAAG,CAAC,GAAG,CAAC;AAChB,IAAA,QACE,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC,IAAI,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,EAAE,CAAC;AAE5E;AAEA;;;AAGG;SACa,SAAS,CACvB,EAAU,EACV,EAAU,EACV,EAAU,EACV,EAAU,EACV,OAAe,EACf,YAAoB,IAAI,EACxB,gBAAwB,EAAE,EAAA;;AAG1B,IAAA,IAAI,CAAC,GAAG,CAAC,OAAO,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC;IACvC,CAAC,GAAG,KAAK,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;;AAGlB,IAAA,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,aAAa,EAAE,CAAC,EAAE,EAAE;AACtC,QAAA,MAAM,CAAC,GAAG,cAAc,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC,GAAG,OAAO;AACrD,QAAA,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,SAAS;AAAE,YAAA,OAAO,CAAC;AAErC,QAAA,MAAM,EAAE,GAAG,gBAAgB,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC;AAC9C,QAAA,IAAI,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK;AAAE,YAAA,MAAM;AAEhC,QAAA,MAAM,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE;QACvB,CAAC,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC;IACvB;;IAGA,IAAI,EAAE,GAAG,CAAC;IACV,IAAI,EAAE,GAAG,CAAC;IACV,CAAC,GAAG,GAAG;AACP,IAAA,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,aAAa,EAAE,CAAC,EAAE,EAAE;AACtC,QAAA,MAAM,CAAC,GAAG,cAAc,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC;QAC3C,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,OAAO,CAAC,GAAG,SAAS;AAAE,YAAA,OAAO,CAAC;AAE/C,QAAA,IAAI,CAAC,GAAG,OAAO,EAAE;YACf,EAAE,GAAG,CAAC;QACR;aAAO;YACL,EAAE,GAAG,CAAC;QACR;QACA,CAAC,GAAG,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC;IACnB;AAEA,IAAA,OAAO,CAAC;AACV;AAEA;;;;;;AAMG;AACH,SAAS,oBAAoB,CAC3B,MAAgC,EAChC,OAAe,EAAA;IAEf,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC;AAC/B,IAAA,IAAI,IAAI,IAAI,OAAO,IAAI,IAAI,GAAG,KAAK;AAAE,QAAA,OAAO,MAAM;AAElD,IAAA,MAAM,KAAK,GAAG,OAAO,GAAG,IAAI;AAC5B,IAAA,OAAO,EAAE,CAAC,EAAE,MAAM,CAAC,CAAC,GAAG,KAAK,EAAE,CAAC,EAAE,MAAM,CAAC,CAAC,GAAG,KAAK,EAAE;AACrD;AAEA;;;;AAIG;AACH,SAAS,uBAAuB,CAC9B,GAAkB,EAClB,GAAkB,EAAA;IAOlB,MAAM,QAAQ,GAAG,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC;IAE9B,MAAM,IAAI,GAAG,oBAAoB,CAAC,GAAG,CAAC,SAAS,EAAE,QAAQ,CAAC;IAC1D,MAAM,GAAG,GAAG,oBAAoB,CAAC,GAAG,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAExD,OAAO;AACL,QAAA,EAAE,EAAE,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE;QAC1B,EAAE,EAAE,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,EAAE;QAC5C,EAAE,EAAE,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,EAAE;AAC1C,QAAA,EAAE,EAAE,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE;KAC3B;AACH;AAEA;;;AAGG;AACG,SAAU,aAAa,CAAC,KAAgB,EAAE,CAAS,EAAA;AACvD,IAAA,MAAM,EAAE,SAAS,EAAE,GAAG,KAAK;AAC3B,IAAA,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC;AAAE,QAAA,OAAO,CAAC;AAEpC,IAAA,MAAM,KAAK,GAAG,SAAS,CAAC,CAAC,CAAC;IAC1B,MAAM,IAAI,GAAG,SAAS,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC;AAC5C,IAAA,IAAI,CAAC,KAAK,IAAI,CAAC,IAAI;AAAE,QAAA,OAAO,CAAC;AAE7B,IAAA,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC,CAAC;;AAG1C,IAAA,IAAI,CAAC,IAAI,KAAK,CAAC,CAAC;QAAE,OAAO,KAAK,CAAC,CAAC;AAChC,IAAA,IAAI,CAAC,IAAI,IAAI,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC,CAAC;;AAG9B,IAAA,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE;AAC7C,QAAA,MAAM,GAAG,GAAG,SAAS,CAAC,CAAC,CAAC;QACxB,MAAM,GAAG,GAAG,SAAS,CAAC,CAAC,GAAG,CAAC,CAAC;AAC5B,QAAA,IAAI,CAAC,GAAG,IAAI,CAAC,GAAG;YAAE;AAElB,QAAA,IAAI,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,CAAC,EAAE;;AAE5B,YAAA,IAAI,GAAG,CAAC,WAAW,KAAK,MAAM,EAAE;gBAC9B,OAAO,GAAG,CAAC,CAAC;YACd;;AAGA,YAAA,IAAI,GAAG,CAAC,WAAW,KAAK,QAAQ,IAAI,GAAG,CAAC,WAAW,KAAK,QAAQ,EAAE;gBAChE,MAAM,IAAI,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC;AAC/C,gBAAA,OAAO,GAAG,CAAC,CAAC,GAAG,IAAI,IAAI,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC;YACvC;AAEA,YAAA,MAAM,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,GAAG,uBAAuB,CAAC,GAAG,EAAE,GAAG,CAAC;YAC5D,MAAM,CAAC,GAAG,SAAS,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;YAC9C,OAAO,cAAc,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;QAClD;IACF;IAEA,OAAO,IAAI,CAAC,CAAC;AACf;AAEA;;;AAGG;AACG,SAAU,WAAW,CACzB,KAAgB,EAChB,UAAkB,EAAA;IAElB,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,KAAK,CAAC,OAAO;IAClC,MAAM,OAAO,GAAoC,EAAE;AACnD,IAAA,MAAM,IAAI,GAAG,CAAC,IAAI,GAAG,IAAI,KAAK,UAAU,GAAG,CAAC,IAAI,CAAC,CAAC;AAElD,IAAA,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,UAAU,EAAE,CAAC,EAAE,EAAE;AACnC,QAAA,MAAM,CAAC,GAAG,IAAI,GAAG,CAAC,GAAG,IAAI;AACzB,QAAA,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,aAAa,CAAC,KAAK,EAAE,CAAC,CAAC,EAAE,CAAC;IACjD;AAEA,IAAA,OAAO,OAAO;AAChB;AAEA;;;AAGG;SACa,kBAAkB,CAChC,IAA0B,EAC1B,OAAsB,EACtB,IAA0B,EAAA;AAE1B,IAAA,MAAM,aAAa,GAAG,CAAC,GAAG,CAAC;AAE3B,IAAA,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,EAAE;QAClB,OAAO,EAAE,QAAQ,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,SAAS,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE;IAChE;AAEA,IAAA,IAAI,CAAC,IAAI,IAAI,IAAI,EAAE;;AAEjB,QAAA,MAAM,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,IAAI,aAAa;AAC/C,QAAA,MAAM,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,IAAI,aAAa;QAC/C,OAAO;YACL,QAAQ,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE;YAC5B,SAAS,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE;SAC5B;IACH;AAEA,IAAA,IAAI,CAAC,IAAI,IAAI,IAAI,EAAE;;AAEjB,QAAA,MAAM,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,IAAI,aAAa;AAC/C,QAAA,MAAM,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,IAAI,aAAa;QAC/C,OAAO;YACL,QAAQ,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE;YAC5B,SAAS,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE;SAC5B;IACH;AAEA,IAAA,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,EAAE;QAClB,OAAO,EAAE,QAAQ,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,SAAS,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE;IAChE;;;;;IAMA,MAAM,OAAO,GAAG,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IAC/B,MAAM,OAAO,GAAG,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IAE/B,MAAM,KAAK,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IAChC,MAAM,MAAM,GAAG,IAAI,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC;AACjC,IAAA,MAAM,QAAQ,GAAG,KAAK,GAAG,MAAM,IAAI,CAAC;IAEpC,OAAO;AACL,QAAA,QAAQ,EAAE;YACR,CAAC,EAAE,EAAE,KAAK,GAAG,QAAQ,CAAC,GAAG,OAAO,GAAG,aAAa;YAChD,CAAC,EAAE,EAAE,KAAK,GAAG,QAAQ,CAAC,GAAG,OAAO,GAAG,aAAa;AACjD,SAAA;AACD,QAAA,SAAS,EAAE;YACT,CAAC,EAAE,CAAC,MAAM,GAAG,QAAQ,IAAI,OAAO,GAAG,aAAa;YAChD,CAAC,EAAE,CAAC,MAAM,GAAG,QAAQ,IAAI,OAAO,GAAG,aAAa;AACjD,SAAA;KACF;AACH;AAEA;;;;AAIG;AACG,SAAU,gBAAgB,CAC9B,QAAuB,EACvB,WAAyB,EAAA;AAEzB,IAAA,MAAM,EAAE,WAAW,EAAE,GAAG,QAAQ;IAEhC,IACE,WAAW,KAAK,MAAM;AACtB,QAAA,WAAW,KAAK,MAAM;AACtB,QAAA,WAAW,KAAK,QAAQ;QACxB,WAAW,KAAK,MAAM,EACtB;AACA,QAAA,OAAO,QAAQ;IACjB;AAEA,IAAA,MAAM,KAAK,GAAG,WAAW,KAAK,IAAI,GAAG,QAAQ,CAAC,QAAQ,GAAG,QAAQ,CAAC,SAAS;IAC3E,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC;AAEpE,IAAA,IAAI,WAAW,GAAG,KAAK,EAAE;AACvB,QAAA,OAAO,QAAQ;IACjB;AAEA,IAAA,IAAI,WAAW,KAAK,UAAU,EAAE;;AAE9B,QAAA,MAAM,QAAQ,GAAG,EAAE,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE;QAC7C,OAAO,WAAW,KAAK;cACnB,EAAE,GAAG,QAAQ,EAAE,SAAS,EAAE,QAAQ;cAClC,EAAE,GAAG,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE;IACzC;;;AAIA,IAAA,MAAM,KAAK,GAAG,WAAW,KAAK,IAAI,GAAG,QAAQ,CAAC,SAAS,GAAG,QAAQ,CAAC,QAAQ;IAC3E,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC;AAEpE,IAAA,IAAI,WAAW,GAAG,KAAK,EAAE;AACvB,QAAA,OAAO,QAAQ;IACjB;;AAGA,IAAA,MAAM,KAAK,GAAG,WAAW,GAAG,WAAW;IACvC,MAAM,QAAQ,GAAG,EAAE,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,KAAK,EAAE,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,KAAK,EAAE;IAE7D,OAAO,WAAW,KAAK;UACnB,EAAE,GAAG,QAAQ,EAAE,SAAS,EAAE,QAAQ;UAClC,EAAE,GAAG,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE;AACzC;AAwEA;;;AAGG;AACG,SAAU,cAAc,CAC5B,OAAe,EACf,OAAe,EACf,QAAwC,EACxC,WAAmB,EACnB,YAAoB,EAAA;AAEpB,IAAA,OAAOA,gBAAoB,CACzB,OAAO,EACP,OAAO,EACP,QAAQ,EACR,WAAW,EACX,YAAY,CACb;AACH;AAEA;;;AAGG;AACG,SAAU,cAAc,CAC5B,EAAU,EACV,EAAU,EACV,QAAwC,EACxC,WAAmB,EACnB,YAAoB,EAAA;AAEpB,IAAA,OAAOC,gBAAoB,CAAC,EAAE,EAAE,EAAE,EAAE,QAAQ,EAAE,WAAW,EAAE,YAAY,CAAC;AAC1E;AAEA;;AAEG;SACa,OAAO,CACrB,EAAU,EACV,EAAU,EACV,KAAgB,EAChB,QAAuB,EACvB,WAAmB,EACnB,YAAoB,EACpB,cAAsB,EAAE,EAAA;AAExB,IAAA,MAAM,KAAK,GAAiB;AAC1B,QAAA,IAAI,EAAE,MAAM;QACZ,aAAa,EAAE,EAAE;AACjB,QAAA,QAAQ,EAAE,QAAQ;KACnB;IACD,IAAI,IAAI,GAAG,KAAK;AAEhB,IAAA,MAAM,EAAE,SAAS,EAAE,GAAG,KAAK;;AAG3B,IAAA,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,SAAS,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;AACzC,QAAA,MAAM,EAAE,GAAG,SAAS,CAAC,CAAC,CAAC;AACvB,QAAA,IAAI,CAAC,EAAE;YAAE;AACT,QAAA,MAAM,QAAQ,GAAG,cAAc,CAC7B,EAAE,CAAC,CAAC,EACJ,EAAE,CAAC,CAAC,EACJ,QAAQ,EACR,WAAW,EACX,YAAY,CACb;QACD,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,GAAG,QAAQ,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,EAAE,GAAG,QAAQ,CAAC,EAAE,KAAK,CAAC,CAAC;QAE3E,IAAI,MAAM,GAAG,WAAW,IAAI,MAAM,GAAG,IAAI,CAAC,QAAQ,EAAE;AAClD,YAAA,IAAI,GAAG,EAAE,IAAI,EAAE,UAAU,EAAE,aAAa,EAAE,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE;QACjE;;AAGA,QAAA,MAAM,SAAS,GAAG,cAAc,CAC9B,EAAE,CAAC,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,CAAC,EACpB,EAAE,CAAC,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,CAAC,EACpB,QAAQ,EACR,WAAW,EACX,YAAY,CACb;QACD,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CACvB,CAAC,EAAE,GAAG,SAAS,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,EAAE,GAAG,SAAS,CAAC,EAAE,KAAK,CAAC,CACpD;QACD,IAAI,OAAO,GAAG,WAAW,IAAI,OAAO,GAAG,IAAI,CAAC,QAAQ,EAAE;AACpD,YAAA,IAAI,GAAG,EAAE,IAAI,EAAE,UAAU,EAAE,aAAa,EAAE,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE;QAClE;AAEA,QAAA,MAAM,UAAU,GAAG,cAAc,CAC/B,EAAE,CAAC,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC,EACrB,EAAE,CAAC,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC,EACrB,QAAQ,EACR,WAAW,EACX,YAAY,CACb;QACD,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CACxB,CAAC,EAAE,GAAG,UAAU,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,EAAE,GAAG,UAAU,CAAC,EAAE,KAAK,CAAC,CACtD;QACD,IAAI,QAAQ,GAAG,WAAW,IAAI,QAAQ,GAAG,IAAI,CAAC,QAAQ,EAAE;AACtD,YAAA,IAAI,GAAG,EAAE,IAAI,EAAE,WAAW,EAAE,aAAa,EAAE,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE;QACpE;IACF;;AAGA,IAAA,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM;AAAE,QAAA,OAAO,IAAI;;AAGrC,IAAA,IAAI,SAAS,CAAC,MAAM,IAAI,CAAC,EAAE;QACzB,MAAM,OAAO,GAAG,WAAW,CAAC,KAAK,EAAE,GAAG,CAAC;AACvC,QAAA,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;AACvC,YAAA,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;AACpB,YAAA,IAAI,CAAC,CAAC;gBAAE;AACR,YAAA,MAAM,OAAO,GAAG,cAAc,CAC5B,CAAC,CAAC,CAAC,EACH,CAAC,CAAC,CAAC,EACH,QAAQ,EACR,WAAW,EACX,YAAY,CACb;YACD,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,GAAG,OAAO,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,EAAE,GAAG,OAAO,CAAC,EAAE,KAAK,CAAC,CAAC;YACvE,IAAI,IAAI,GAAG,WAAW,IAAI,IAAI,GAAG,IAAI,CAAC,QAAQ,EAAE;AAC9C,gBAAA,IAAI,GAAG,EAAE,IAAI,EAAE,OAAO,EAAE,aAAa,EAAE,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE;YAC7D;QACF;IACF;AAEA,IAAA,OAAO,IAAI;AACb;AAEA;;AAEG;AACG,SAAU,aAAa,CAAC,SAA0B,EAAA;IACtD,OAAO,CAAC,GAAG,SAAS,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;AACjD;AAEA;;AAEG;AACG,SAAU,iBAAiB,CAC/B,OAAA,GAA4B,CAAC,CAAC,EAAE,CAAC,CAAC,EAClC,OAAA,GAA4B,CAAC,CAAC,EAAE,CAAC,CAAC,EAAA;IAElC,OAAO;AACL,QAAA,SAAS,EAAE;AACT,YAAA;AACE,gBAAA,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;AACb,gBAAA,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;gBACb,QAAQ,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE;AACxB,gBAAA,SAAS,EAAE;AACT,oBAAA,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC;AAChC,oBAAA,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC;AACjC,iBAAA;AACD,gBAAA,WAAW,EAAE,QAAQ;gBACrB,EAAE,EAAE,kBAAkB,EAAE;AACzB,aAAA;AACD,YAAA;AACE,gBAAA,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;AACb,gBAAA,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;AACb,gBAAA,QAAQ,EAAE;AACR,oBAAA,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;AACjC,oBAAA,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;AAClC,iBAAA;gBACD,SAAS,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE;AACzB,gBAAA,WAAW,EAAE,QAAQ;gBACrB,EAAE,EAAE,kBAAkB,EAAE;AACzB,aAAA;AACF,SAAA;QACD,OAAO;QACP,OAAO;KACR;AACH;AAEA;;AAEG;AACG,SAAU,iBAAiB,CAAC,SAA0B,EAAA;AAC1D,IAAA,OAAO,SAAS,CAAC,GAAG,CAAC,EAAE,IACrB,EAAE,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE,EAAE,EAAE,kBAAkB,EAAE,EAAE,CACjD;AACH;;;;"}
|
|
1
|
+
{"version":3,"file":"curveUtils.js","sources":["src/components/controls/CurveEditor/curveUtils.ts"],"sourcesContent":["import {\n domainToCanvas as sharedDomainToCanvas,\n canvasToDomain as sharedCanvasToDomain,\n} from '@/components/primitives/canvas/canvasCoords';\nimport { clamp } from '@/utils/mathUtils';\n\nimport type {\n CurveData,\n CurveKeyframe,\n CurveViewport,\n CurveHitTest,\n} from './CurveEditor.types';\nimport type { CanvasViewport } from '@/components/primitives/canvas/canvas.types';\n\nlet idCounter = 0;\n\n/**\n * Generate a unique keyframe ID.\n */\nexport function generateKeyframeId(): string {\n idCounter += 1;\n return `kf-${Date.now()}-${idCounter}`;\n}\n\n/**\n * Evaluate a cubic bezier at parameter t in [0, 1].\n * B(t) = (1-t)^3*p0 + 3*(1-t)^2*t*p1 + 3*(1-t)*t^2*p2 + t^3*p3\n */\nexport function evaluateBezier(\n p0: number,\n p1: number,\n p2: number,\n p3: number,\n t: number\n): number {\n const mt = 1 - t;\n return (\n mt * mt * mt * p0 +\n 3 * mt * mt * t * p1 +\n 3 * mt * t * t * p2 +\n t * t * t * p3\n );\n}\n\n/**\n * Evaluate a 2D cubic bezier curve at parameter t.\n */\nexport function evaluateBezier2D(\n p0: { x: number; y: number },\n p1: { x: number; y: number },\n p2: { x: number; y: number },\n p3: { x: number; y: number },\n t: number\n): { x: number; y: number } {\n return {\n x: evaluateBezier(p0.x, p1.x, p2.x, p3.x, t),\n y: evaluateBezier(p0.y, p1.y, p2.y, p3.y, t),\n };\n}\n\n/**\n * Derivative of cubic bezier at parameter t.\n * B'(t) = 3*(1-t)^2*(p1-p0) + 6*(1-t)*t*(p2-p1) + 3*t^2*(p3-p2)\n */\nfunction bezierDerivative(\n p0: number,\n p1: number,\n p2: number,\n p3: number,\n t: number\n): number {\n const mt = 1 - t;\n return (\n 3 * mt * mt * (p1 - p0) + 6 * mt * t * (p2 - p1) + 3 * t * t * (p3 - p2)\n );\n}\n\n/**\n * Find the t parameter for a given X value on a bezier segment.\n * Uses Newton-Raphson iteration with bisection fallback.\n */\nexport function findTForX(\n x0: number,\n x1: number,\n x2: number,\n x3: number,\n targetX: number,\n tolerance: number = 1e-6,\n maxIterations: number = 20\n): number {\n // Initial guess using linear interpolation\n let t = (targetX - x0) / (x3 - x0 || 1);\n t = clamp(t, 0, 1);\n\n // Newton-Raphson iteration\n for (let i = 0; i < maxIterations; i++) {\n const x = evaluateBezier(x0, x1, x2, x3, t) - targetX;\n if (Math.abs(x) < tolerance) return t;\n\n const dx = bezierDerivative(x0, x1, x2, x3, t);\n if (Math.abs(dx) < 1e-10) break; // Derivative too small, fallback\n\n const newT = t - x / dx;\n t = clamp(newT, 0, 1);\n }\n\n // Bisection fallback for robustness\n let lo = 0;\n let hi = 1;\n t = 0.5;\n for (let i = 0; i < maxIterations; i++) {\n const x = evaluateBezier(x0, x1, x2, x3, t);\n if (Math.abs(x - targetX) < tolerance) return t;\n\n if (x < targetX) {\n lo = t;\n } else {\n hi = t;\n }\n t = (lo + hi) / 2;\n }\n\n return t;\n}\n\n/**\n * Clamp a tangent handle so its X component stays within the segment bounds.\n * Uses proportional scaling to preserve the tangent angle.\n *\n * @param handle Handle offset relative to its keyframe\n * @param maxAbsX Maximum allowed |handle.x| (typically the segment width)\n */\nfunction clampHandleToSegment(\n handle: { x: number; y: number },\n maxAbsX: number\n): { x: number; y: number } {\n const absX = Math.abs(handle.x);\n if (absX <= maxAbsX || absX < 1e-10) return handle;\n\n const scale = maxAbsX / absX;\n return { x: handle.x * scale, y: handle.y * scale };\n}\n\n/**\n * Get the bezier control points for a segment between two keyframes.\n * Handles are clamped so that p1.x and p2.x stay within [p0.x, p3.x],\n * preventing the bezier from folding back on itself (vertical spikes / loops).\n */\nfunction getSegmentControlPoints(\n kf0: CurveKeyframe,\n kf1: CurveKeyframe\n): {\n p0: { x: number; y: number };\n p1: { x: number; y: number };\n p2: { x: number; y: number };\n p3: { x: number; y: number };\n} {\n const segWidth = kf1.x - kf0.x;\n\n const hOut = clampHandleToSegment(kf0.handleOut, segWidth);\n const hIn = clampHandleToSegment(kf1.handleIn, segWidth);\n\n return {\n p0: { x: kf0.x, y: kf0.y },\n p1: { x: kf0.x + hOut.x, y: kf0.y + hOut.y },\n p2: { x: kf1.x + hIn.x, y: kf1.y + hIn.y },\n p3: { x: kf1.x, y: kf1.y },\n };\n}\n\n/**\n * Evaluate the entire curve at a given X position.\n * Finds the right segment, solves for t, returns Y.\n */\nexport function evaluateCurve(curve: CurveData, x: number): number {\n const { keyframes } = curve;\n if (keyframes.length === 0) return 0;\n\n const first = keyframes[0];\n const last = keyframes[keyframes.length - 1];\n if (!first || !last) return 0;\n\n if (keyframes.length === 1) return first.y;\n\n // Handle out-of-range X\n if (x <= first.x) return first.y;\n if (x >= last.x) return last.y;\n\n // Find the segment containing x\n for (let i = 0; i < keyframes.length - 1; i++) {\n const kf0 = keyframes[i];\n const kf1 = keyframes[i + 1];\n if (!kf0 || !kf1) continue;\n\n if (x >= kf0.x && x <= kf1.x) {\n // Handle step mode\n if (kf0.tangentMode === 'step') {\n return kf0.y;\n }\n\n // Handle linear mode (both keyframes)\n if (kf0.tangentMode === 'linear' && kf1.tangentMode === 'linear') {\n const segT = (x - kf0.x) / (kf1.x - kf0.x || 1);\n return kf0.y + segT * (kf1.y - kf0.y);\n }\n\n const { p0, p1, p2, p3 } = getSegmentControlPoints(kf0, kf1);\n const t = findTForX(p0.x, p1.x, p2.x, p3.x, x);\n return evaluateBezier(p0.y, p1.y, p2.y, p3.y, t);\n }\n }\n\n return last.y;\n}\n\n/**\n * Sample the curve at N evenly-spaced X points.\n * Returns array of {x, y} for rendering as a polyline.\n */\nexport function sampleCurve(\n curve: CurveData,\n numSamples: number\n): Array<{ x: number; y: number }> {\n const [xMin, xMax] = curve.domainX;\n const samples: Array<{ x: number; y: number }> = [];\n const step = (xMax - xMin) / (numSamples - 1 || 1);\n\n for (let i = 0; i < numSamples; i++) {\n const x = xMin + i * step;\n samples.push({ x, y: evaluateCurve(curve, x) });\n }\n\n return samples;\n}\n\n/**\n * Compute auto-tangent handles for a keyframe based on neighbors.\n * Catmull-Rom style: tangent direction = (next - prev) scaled by segment length.\n */\nexport function computeAutoTangent(\n prev: CurveKeyframe | null,\n current: CurveKeyframe,\n next: CurveKeyframe | null\n): { handleIn: { x: number; y: number }; handleOut: { x: number; y: number } } {\n const TANGENT_SCALE = 1 / 3;\n\n if (!prev && !next) {\n return { handleIn: { x: 0, y: 0 }, handleOut: { x: 0, y: 0 } };\n }\n\n if (!prev && next) {\n // First keyframe — use direction to next\n const dx = (next.x - current.x) * TANGENT_SCALE;\n const dy = (next.y - current.y) * TANGENT_SCALE;\n return {\n handleIn: { x: -dx, y: -dy },\n handleOut: { x: dx, y: dy },\n };\n }\n\n if (!next && prev) {\n // Last keyframe — use direction from prev\n const dx = (current.x - prev.x) * TANGENT_SCALE;\n const dy = (current.y - prev.y) * TANGENT_SCALE;\n return {\n handleIn: { x: -dx, y: -dy },\n handleOut: { x: dx, y: dy },\n };\n }\n\n if (!prev || !next) {\n return { handleIn: { x: 0, y: 0 }, handleOut: { x: 0, y: 0 } };\n }\n\n // Middle keyframe — Catmull-Rom tangent with per-segment scaling.\n // The tangent direction comes from (next - prev), but each handle is\n // scaled proportionally to its own segment length so that handles\n // never overshoot into neighboring segments.\n const totalDx = next.x - prev.x;\n const totalDy = next.y - prev.y;\n\n const segIn = current.x - prev.x;\n const segOut = next.x - current.x;\n const totalSeg = segIn + segOut || 1;\n\n return {\n handleIn: {\n x: -(segIn / totalSeg) * totalDx * TANGENT_SCALE,\n y: -(segIn / totalSeg) * totalDy * TANGENT_SCALE,\n },\n handleOut: {\n x: (segOut / totalSeg) * totalDx * TANGENT_SCALE,\n y: (segOut / totalSeg) * totalDy * TANGENT_SCALE,\n },\n };\n}\n\n/**\n * Apply tangent mode constraints to a keyframe after a handle was moved.\n * - aligned: project opposite handle to co-linear with moved handle\n * - mirrored: mirror moved handle to opposite\n */\nexport function constrainTangent(\n keyframe: CurveKeyframe,\n movedHandle: 'in' | 'out'\n): CurveKeyframe {\n const { tangentMode } = keyframe;\n\n if (\n tangentMode === 'free' ||\n tangentMode === 'auto' ||\n tangentMode === 'linear' ||\n tangentMode === 'step'\n ) {\n return keyframe;\n }\n\n const moved = movedHandle === 'in' ? keyframe.handleIn : keyframe.handleOut;\n const movedLength = Math.sqrt(moved.x * moved.x + moved.y * moved.y);\n\n if (movedLength < 1e-10) {\n return keyframe;\n }\n\n if (tangentMode === 'mirrored') {\n // Opposite handle is exact negative\n const opposite = { x: -moved.x, y: -moved.y };\n return movedHandle === 'in'\n ? { ...keyframe, handleOut: opposite }\n : { ...keyframe, handleIn: opposite };\n }\n\n // Remaining mode is 'aligned': the opposite handle keeps its length but\n // aligns direction with the moved handle.\n const other = movedHandle === 'in' ? keyframe.handleOut : keyframe.handleIn;\n const otherLength = Math.sqrt(other.x * other.x + other.y * other.y);\n\n if (otherLength < 1e-10) {\n return keyframe;\n }\n\n // Direction opposite to moved handle, scaled to original length\n const scale = otherLength / movedLength;\n const opposite = { x: -moved.x * scale, y: -moved.y * scale };\n\n return movedHandle === 'in'\n ? { ...keyframe, handleOut: opposite }\n : { ...keyframe, handleIn: opposite };\n}\n\n/**\n * Compute step function value (hold previous Y until next keyframe).\n */\nexport function evaluateStep(curve: CurveData, x: number): number {\n const { keyframes } = curve;\n if (keyframes.length === 0) return 0;\n\n const first = keyframes[0];\n if (!first) return 0;\n if (x <= first.x) return first.y;\n\n for (let i = keyframes.length - 1; i >= 0; i--) {\n const kf = keyframes[i];\n if (kf && kf.x <= x) {\n return kf.y;\n }\n }\n\n return first.y;\n}\n\n/**\n * Handle infinity behavior (cycle, pingpong, linear extrapolation).\n * Returns the wrapped X value and whether Y should be flipped (for pingpong).\n */\nexport function wrapX(\n x: number,\n domainX: [number, number],\n preInfinity: CurveData['preInfinity'],\n postInfinity: CurveData['postInfinity']\n): { wrappedX: number; flipY: boolean } {\n const [xMin, xMax] = domainX;\n const range = xMax - xMin;\n\n if (range <= 0) return { wrappedX: xMin, flipY: false };\n\n // Within range — no wrapping needed\n if (x >= xMin && x <= xMax) {\n return { wrappedX: x, flipY: false };\n }\n\n const mode =\n x < xMin ? (preInfinity ?? 'constant') : (postInfinity ?? 'constant');\n\n switch (mode) {\n case 'constant':\n return { wrappedX: clamp(x, xMin, xMax), flipY: false };\n\n case 'linear':\n // No wrapping — caller should extrapolate\n return { wrappedX: x, flipY: false };\n\n case 'cycle': {\n const normalized = (((x - xMin) % range) + range) % range;\n return { wrappedX: xMin + normalized, flipY: false };\n }\n\n case 'pingpong': {\n const normalized = (((x - xMin) % (range * 2)) + range * 2) % (range * 2);\n if (normalized > range) {\n return { wrappedX: xMin + (range * 2 - normalized), flipY: false };\n }\n return { wrappedX: xMin + normalized, flipY: false };\n }\n\n default:\n return { wrappedX: clamp(x, xMin, xMax), flipY: false };\n }\n}\n\n/**\n * Convert domain coordinates to canvas pixel coordinates.\n * Re-exported from shared canvas primitives for backward compatibility.\n */\nexport function domainToCanvas(\n domainX: number,\n domainY: number,\n viewport: CurveViewport | CanvasViewport,\n canvasWidth: number,\n canvasHeight: number\n): { px: number; py: number } {\n return sharedDomainToCanvas(\n domainX,\n domainY,\n viewport,\n canvasWidth,\n canvasHeight\n );\n}\n\n/**\n * Convert canvas pixel coordinates to domain coordinates.\n * Re-exported from shared canvas primitives for backward compatibility.\n */\nexport function canvasToDomain(\n px: number,\n py: number,\n viewport: CurveViewport | CanvasViewport,\n canvasWidth: number,\n canvasHeight: number\n): { x: number; y: number } {\n return sharedCanvasToDomain(px, py, viewport, canvasWidth, canvasHeight);\n}\n\n/**\n * Hit test — find the nearest element to a canvas pixel position.\n */\nexport function hitTest(\n px: number,\n py: number,\n curve: CurveData,\n viewport: CurveViewport,\n canvasWidth: number,\n canvasHeight: number,\n tolerancePx: number = 10\n): CurveHitTest {\n const noHit: CurveHitTest = {\n type: 'none',\n keyframeIndex: -1,\n distance: Infinity,\n };\n let best = noHit;\n\n const { keyframes } = curve;\n\n // Test keyframes and handles (closest wins)\n for (let i = 0; i < keyframes.length; i++) {\n const kf = keyframes[i];\n if (!kf) continue;\n const kfCanvas = domainToCanvas(\n kf.x,\n kf.y,\n viewport,\n canvasWidth,\n canvasHeight\n );\n const kfDist = Math.sqrt((px - kfCanvas.px) ** 2 + (py - kfCanvas.py) ** 2);\n\n if (kfDist < tolerancePx && kfDist < best.distance) {\n best = { type: 'keyframe', keyframeIndex: i, distance: kfDist };\n }\n\n // Check handles (only if handles are visible — selected state checked by caller)\n const hInCanvas = domainToCanvas(\n kf.x + kf.handleIn.x,\n kf.y + kf.handleIn.y,\n viewport,\n canvasWidth,\n canvasHeight\n );\n const hInDist = Math.sqrt(\n (px - hInCanvas.px) ** 2 + (py - hInCanvas.py) ** 2\n );\n if (hInDist < tolerancePx && hInDist < best.distance) {\n best = { type: 'handleIn', keyframeIndex: i, distance: hInDist };\n }\n\n const hOutCanvas = domainToCanvas(\n kf.x + kf.handleOut.x,\n kf.y + kf.handleOut.y,\n viewport,\n canvasWidth,\n canvasHeight\n );\n const hOutDist = Math.sqrt(\n (px - hOutCanvas.px) ** 2 + (py - hOutCanvas.py) ** 2\n );\n if (hOutDist < tolerancePx && hOutDist < best.distance) {\n best = { type: 'handleOut', keyframeIndex: i, distance: hOutDist };\n }\n }\n\n // If we found a keyframe or handle, return that (higher priority than curve)\n if (best.type !== 'none') return best;\n\n // Test curve line\n if (keyframes.length >= 2) {\n const samples = sampleCurve(curve, 200);\n for (let i = 0; i < samples.length; i++) {\n const s = samples[i];\n if (!s) continue;\n const sCanvas = domainToCanvas(\n s.x,\n s.y,\n viewport,\n canvasWidth,\n canvasHeight\n );\n const dist = Math.sqrt((px - sCanvas.px) ** 2 + (py - sCanvas.py) ** 2);\n if (dist < tolerancePx && dist < best.distance) {\n best = { type: 'curve', keyframeIndex: -1, distance: dist };\n }\n }\n }\n\n return best;\n}\n\n/**\n * Sort keyframes by X position (required after move).\n */\nexport function sortKeyframes(keyframes: CurveKeyframe[]): CurveKeyframe[] {\n return [...keyframes].sort((a, b) => a.x - b.x);\n}\n\n/**\n * Create a default linear curve.\n */\nexport function createLinearCurve(\n domainX: [number, number] = [0, 1],\n domainY: [number, number] = [0, 1]\n): CurveData {\n return {\n keyframes: [\n {\n x: domainX[0],\n y: domainY[0],\n handleIn: { x: 0, y: 0 },\n handleOut: {\n x: (domainX[1] - domainX[0]) / 3,\n y: (domainY[1] - domainY[0]) / 3,\n },\n tangentMode: 'linear',\n id: generateKeyframeId(),\n },\n {\n x: domainX[1],\n y: domainY[1],\n handleIn: {\n x: -(domainX[1] - domainX[0]) / 3,\n y: -(domainY[1] - domainY[0]) / 3,\n },\n handleOut: { x: 0, y: 0 },\n tangentMode: 'linear',\n id: generateKeyframeId(),\n },\n ],\n domainX,\n domainY,\n };\n}\n\n/**\n * Ensure every keyframe has an ID.\n */\nexport function ensureKeyframeIds(keyframes: CurveKeyframe[]): CurveKeyframe[] {\n return keyframes.map(kf =>\n kf.id ? kf : { ...kf, id: generateKeyframeId() }\n );\n}\n"],"names":["sharedDomainToCanvas","sharedCanvasToDomain"],"mappings":";;;AAcA,IAAI,SAAS,GAAG,CAAC;AAEjB;;AAEG;SACa,kBAAkB,GAAA;IAChC,SAAS,IAAI,CAAC;IACd,OAAO,CAAA,GAAA,EAAM,IAAI,CAAC,GAAG,EAAE,CAAA,CAAA,EAAI,SAAS,EAAE;AACxC;AAEA;;;AAGG;AACG,SAAU,cAAc,CAC5B,EAAU,EACV,EAAU,EACV,EAAU,EACV,EAAU,EACV,CAAS,EAAA;AAET,IAAA,MAAM,EAAE,GAAG,CAAC,GAAG,CAAC;AAChB,IAAA,QACE,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE;AACjB,QAAA,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,GAAG,EAAE;AACpB,QAAA,CAAC,GAAG,EAAE,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE;AACnB,QAAA,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE;AAElB;AAkBA;;;AAGG;AACH,SAAS,gBAAgB,CACvB,EAAU,EACV,EAAU,EACV,EAAU,EACV,EAAU,EACV,CAAS,EAAA;AAET,IAAA,MAAM,EAAE,GAAG,CAAC,GAAG,CAAC;AAChB,IAAA,QACE,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC,IAAI,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,EAAE,CAAC;AAE5E;AAEA;;;AAGG;SACa,SAAS,CACvB,EAAU,EACV,EAAU,EACV,EAAU,EACV,EAAU,EACV,OAAe,EACf,YAAoB,IAAI,EACxB,gBAAwB,EAAE,EAAA;;AAG1B,IAAA,IAAI,CAAC,GAAG,CAAC,OAAO,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC;IACvC,CAAC,GAAG,KAAK,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;;AAGlB,IAAA,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,aAAa,EAAE,CAAC,EAAE,EAAE;AACtC,QAAA,MAAM,CAAC,GAAG,cAAc,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC,GAAG,OAAO;AACrD,QAAA,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,SAAS;AAAE,YAAA,OAAO,CAAC;AAErC,QAAA,MAAM,EAAE,GAAG,gBAAgB,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC;AAC9C,QAAA,IAAI,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK;AAAE,YAAA,MAAM;AAEhC,QAAA,MAAM,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE;QACvB,CAAC,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC;IACvB;;IAGA,IAAI,EAAE,GAAG,CAAC;IACV,IAAI,EAAE,GAAG,CAAC;IACV,CAAC,GAAG,GAAG;AACP,IAAA,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,aAAa,EAAE,CAAC,EAAE,EAAE;AACtC,QAAA,MAAM,CAAC,GAAG,cAAc,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC;QAC3C,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,OAAO,CAAC,GAAG,SAAS;AAAE,YAAA,OAAO,CAAC;AAE/C,QAAA,IAAI,CAAC,GAAG,OAAO,EAAE;YACf,EAAE,GAAG,CAAC;QACR;aAAO;YACL,EAAE,GAAG,CAAC;QACR;QACA,CAAC,GAAG,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC;IACnB;AAEA,IAAA,OAAO,CAAC;AACV;AAEA;;;;;;AAMG;AACH,SAAS,oBAAoB,CAC3B,MAAgC,EAChC,OAAe,EAAA;IAEf,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC;AAC/B,IAAA,IAAI,IAAI,IAAI,OAAO,IAAI,IAAI,GAAG,KAAK;AAAE,QAAA,OAAO,MAAM;AAElD,IAAA,MAAM,KAAK,GAAG,OAAO,GAAG,IAAI;AAC5B,IAAA,OAAO,EAAE,CAAC,EAAE,MAAM,CAAC,CAAC,GAAG,KAAK,EAAE,CAAC,EAAE,MAAM,CAAC,CAAC,GAAG,KAAK,EAAE;AACrD;AAEA;;;;AAIG;AACH,SAAS,uBAAuB,CAC9B,GAAkB,EAClB,GAAkB,EAAA;IAOlB,MAAM,QAAQ,GAAG,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC;IAE9B,MAAM,IAAI,GAAG,oBAAoB,CAAC,GAAG,CAAC,SAAS,EAAE,QAAQ,CAAC;IAC1D,MAAM,GAAG,GAAG,oBAAoB,CAAC,GAAG,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAExD,OAAO;AACL,QAAA,EAAE,EAAE,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE;QAC1B,EAAE,EAAE,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,EAAE;QAC5C,EAAE,EAAE,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,EAAE;AAC1C,QAAA,EAAE,EAAE,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE;KAC3B;AACH;AAEA;;;AAGG;AACG,SAAU,aAAa,CAAC,KAAgB,EAAE,CAAS,EAAA;AACvD,IAAA,MAAM,EAAE,SAAS,EAAE,GAAG,KAAK;AAC3B,IAAA,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC;AAAE,QAAA,OAAO,CAAC;AAEpC,IAAA,MAAM,KAAK,GAAG,SAAS,CAAC,CAAC,CAAC;IAC1B,MAAM,IAAI,GAAG,SAAS,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC;AAC5C,IAAA,IAAI,CAAC,KAAK,IAAI,CAAC,IAAI;AAAE,QAAA,OAAO,CAAC;AAE7B,IAAA,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC,CAAC;;AAG1C,IAAA,IAAI,CAAC,IAAI,KAAK,CAAC,CAAC;QAAE,OAAO,KAAK,CAAC,CAAC;AAChC,IAAA,IAAI,CAAC,IAAI,IAAI,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC,CAAC;;AAG9B,IAAA,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE;AAC7C,QAAA,MAAM,GAAG,GAAG,SAAS,CAAC,CAAC,CAAC;QACxB,MAAM,GAAG,GAAG,SAAS,CAAC,CAAC,GAAG,CAAC,CAAC;AAC5B,QAAA,IAAI,CAAC,GAAG,IAAI,CAAC,GAAG;YAAE;AAElB,QAAA,IAAI,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,CAAC,EAAE;;AAE5B,YAAA,IAAI,GAAG,CAAC,WAAW,KAAK,MAAM,EAAE;gBAC9B,OAAO,GAAG,CAAC,CAAC;YACd;;AAGA,YAAA,IAAI,GAAG,CAAC,WAAW,KAAK,QAAQ,IAAI,GAAG,CAAC,WAAW,KAAK,QAAQ,EAAE;gBAChE,MAAM,IAAI,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC;AAC/C,gBAAA,OAAO,GAAG,CAAC,CAAC,GAAG,IAAI,IAAI,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC;YACvC;AAEA,YAAA,MAAM,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,GAAG,uBAAuB,CAAC,GAAG,EAAE,GAAG,CAAC;YAC5D,MAAM,CAAC,GAAG,SAAS,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;YAC9C,OAAO,cAAc,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;QAClD;IACF;IAEA,OAAO,IAAI,CAAC,CAAC;AACf;AAEA;;;AAGG;AACG,SAAU,WAAW,CACzB,KAAgB,EAChB,UAAkB,EAAA;IAElB,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,KAAK,CAAC,OAAO;IAClC,MAAM,OAAO,GAAoC,EAAE;AACnD,IAAA,MAAM,IAAI,GAAG,CAAC,IAAI,GAAG,IAAI,KAAK,UAAU,GAAG,CAAC,IAAI,CAAC,CAAC;AAElD,IAAA,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,UAAU,EAAE,CAAC,EAAE,EAAE;AACnC,QAAA,MAAM,CAAC,GAAG,IAAI,GAAG,CAAC,GAAG,IAAI;AACzB,QAAA,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,aAAa,CAAC,KAAK,EAAE,CAAC,CAAC,EAAE,CAAC;IACjD;AAEA,IAAA,OAAO,OAAO;AAChB;AAEA;;;AAGG;SACa,kBAAkB,CAChC,IAA0B,EAC1B,OAAsB,EACtB,IAA0B,EAAA;AAE1B,IAAA,MAAM,aAAa,GAAG,CAAC,GAAG,CAAC;AAE3B,IAAA,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,EAAE;QAClB,OAAO,EAAE,QAAQ,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,SAAS,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE;IAChE;AAEA,IAAA,IAAI,CAAC,IAAI,IAAI,IAAI,EAAE;;AAEjB,QAAA,MAAM,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,IAAI,aAAa;AAC/C,QAAA,MAAM,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,IAAI,aAAa;QAC/C,OAAO;YACL,QAAQ,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE;YAC5B,SAAS,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE;SAC5B;IACH;AAEA,IAAA,IAAI,CAAC,IAAI,IAAI,IAAI,EAAE;;AAEjB,QAAA,MAAM,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,IAAI,aAAa;AAC/C,QAAA,MAAM,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,IAAI,aAAa;QAC/C,OAAO;YACL,QAAQ,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE;YAC5B,SAAS,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE;SAC5B;IACH;AAEA,IAAA,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,EAAE;QAClB,OAAO,EAAE,QAAQ,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,SAAS,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE;IAChE;;;;;IAMA,MAAM,OAAO,GAAG,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IAC/B,MAAM,OAAO,GAAG,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IAE/B,MAAM,KAAK,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IAChC,MAAM,MAAM,GAAG,IAAI,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC;AACjC,IAAA,MAAM,QAAQ,GAAG,KAAK,GAAG,MAAM,IAAI,CAAC;IAEpC,OAAO;AACL,QAAA,QAAQ,EAAE;YACR,CAAC,EAAE,EAAE,KAAK,GAAG,QAAQ,CAAC,GAAG,OAAO,GAAG,aAAa;YAChD,CAAC,EAAE,EAAE,KAAK,GAAG,QAAQ,CAAC,GAAG,OAAO,GAAG,aAAa;AACjD,SAAA;AACD,QAAA,SAAS,EAAE;YACT,CAAC,EAAE,CAAC,MAAM,GAAG,QAAQ,IAAI,OAAO,GAAG,aAAa;YAChD,CAAC,EAAE,CAAC,MAAM,GAAG,QAAQ,IAAI,OAAO,GAAG,aAAa;AACjD,SAAA;KACF;AACH;AAEA;;;;AAIG;AACG,SAAU,gBAAgB,CAC9B,QAAuB,EACvB,WAAyB,EAAA;AAEzB,IAAA,MAAM,EAAE,WAAW,EAAE,GAAG,QAAQ;IAEhC,IACE,WAAW,KAAK,MAAM;AACtB,QAAA,WAAW,KAAK,MAAM;AACtB,QAAA,WAAW,KAAK,QAAQ;QACxB,WAAW,KAAK,MAAM,EACtB;AACA,QAAA,OAAO,QAAQ;IACjB;AAEA,IAAA,MAAM,KAAK,GAAG,WAAW,KAAK,IAAI,GAAG,QAAQ,CAAC,QAAQ,GAAG,QAAQ,CAAC,SAAS;IAC3E,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC;AAEpE,IAAA,IAAI,WAAW,GAAG,KAAK,EAAE;AACvB,QAAA,OAAO,QAAQ;IACjB;AAEA,IAAA,IAAI,WAAW,KAAK,UAAU,EAAE;;AAE9B,QAAA,MAAM,QAAQ,GAAG,EAAE,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE;QAC7C,OAAO,WAAW,KAAK;cACnB,EAAE,GAAG,QAAQ,EAAE,SAAS,EAAE,QAAQ;cAClC,EAAE,GAAG,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE;IACzC;;;AAIA,IAAA,MAAM,KAAK,GAAG,WAAW,KAAK,IAAI,GAAG,QAAQ,CAAC,SAAS,GAAG,QAAQ,CAAC,QAAQ;IAC3E,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC;AAEpE,IAAA,IAAI,WAAW,GAAG,KAAK,EAAE;AACvB,QAAA,OAAO,QAAQ;IACjB;;AAGA,IAAA,MAAM,KAAK,GAAG,WAAW,GAAG,WAAW;IACvC,MAAM,QAAQ,GAAG,EAAE,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,KAAK,EAAE,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,KAAK,EAAE;IAE7D,OAAO,WAAW,KAAK;UACnB,EAAE,GAAG,QAAQ,EAAE,SAAS,EAAE,QAAQ;UAClC,EAAE,GAAG,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE;AACzC;AAwEA;;;AAGG;AACG,SAAU,cAAc,CAC5B,OAAe,EACf,OAAe,EACf,QAAwC,EACxC,WAAmB,EACnB,YAAoB,EAAA;AAEpB,IAAA,OAAOA,gBAAoB,CACzB,OAAO,EACP,OAAO,EACP,QAAQ,EACR,WAAW,EACX,YAAY,CACb;AACH;AAEA;;;AAGG;AACG,SAAU,cAAc,CAC5B,EAAU,EACV,EAAU,EACV,QAAwC,EACxC,WAAmB,EACnB,YAAoB,EAAA;AAEpB,IAAA,OAAOC,gBAAoB,CAAC,EAAE,EAAE,EAAE,EAAE,QAAQ,EAAE,WAAW,EAAE,YAAY,CAAC;AAC1E;AAEA;;AAEG;SACa,OAAO,CACrB,EAAU,EACV,EAAU,EACV,KAAgB,EAChB,QAAuB,EACvB,WAAmB,EACnB,YAAoB,EACpB,cAAsB,EAAE,EAAA;AAExB,IAAA,MAAM,KAAK,GAAiB;AAC1B,QAAA,IAAI,EAAE,MAAM;QACZ,aAAa,EAAE,EAAE;AACjB,QAAA,QAAQ,EAAE,QAAQ;KACnB;IACD,IAAI,IAAI,GAAG,KAAK;AAEhB,IAAA,MAAM,EAAE,SAAS,EAAE,GAAG,KAAK;;AAG3B,IAAA,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,SAAS,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;AACzC,QAAA,MAAM,EAAE,GAAG,SAAS,CAAC,CAAC,CAAC;AACvB,QAAA,IAAI,CAAC,EAAE;YAAE;AACT,QAAA,MAAM,QAAQ,GAAG,cAAc,CAC7B,EAAE,CAAC,CAAC,EACJ,EAAE,CAAC,CAAC,EACJ,QAAQ,EACR,WAAW,EACX,YAAY,CACb;QACD,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,GAAG,QAAQ,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,EAAE,GAAG,QAAQ,CAAC,EAAE,KAAK,CAAC,CAAC;QAE3E,IAAI,MAAM,GAAG,WAAW,IAAI,MAAM,GAAG,IAAI,CAAC,QAAQ,EAAE;AAClD,YAAA,IAAI,GAAG,EAAE,IAAI,EAAE,UAAU,EAAE,aAAa,EAAE,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE;QACjE;;AAGA,QAAA,MAAM,SAAS,GAAG,cAAc,CAC9B,EAAE,CAAC,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,CAAC,EACpB,EAAE,CAAC,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,CAAC,EACpB,QAAQ,EACR,WAAW,EACX,YAAY,CACb;QACD,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CACvB,CAAC,EAAE,GAAG,SAAS,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,EAAE,GAAG,SAAS,CAAC,EAAE,KAAK,CAAC,CACpD;QACD,IAAI,OAAO,GAAG,WAAW,IAAI,OAAO,GAAG,IAAI,CAAC,QAAQ,EAAE;AACpD,YAAA,IAAI,GAAG,EAAE,IAAI,EAAE,UAAU,EAAE,aAAa,EAAE,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE;QAClE;AAEA,QAAA,MAAM,UAAU,GAAG,cAAc,CAC/B,EAAE,CAAC,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC,EACrB,EAAE,CAAC,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC,EACrB,QAAQ,EACR,WAAW,EACX,YAAY,CACb;QACD,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CACxB,CAAC,EAAE,GAAG,UAAU,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,EAAE,GAAG,UAAU,CAAC,EAAE,KAAK,CAAC,CACtD;QACD,IAAI,QAAQ,GAAG,WAAW,IAAI,QAAQ,GAAG,IAAI,CAAC,QAAQ,EAAE;AACtD,YAAA,IAAI,GAAG,EAAE,IAAI,EAAE,WAAW,EAAE,aAAa,EAAE,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE;QACpE;IACF;;AAGA,IAAA,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM;AAAE,QAAA,OAAO,IAAI;;AAGrC,IAAA,IAAI,SAAS,CAAC,MAAM,IAAI,CAAC,EAAE;QACzB,MAAM,OAAO,GAAG,WAAW,CAAC,KAAK,EAAE,GAAG,CAAC;AACvC,QAAA,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;AACvC,YAAA,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;AACpB,YAAA,IAAI,CAAC,CAAC;gBAAE;AACR,YAAA,MAAM,OAAO,GAAG,cAAc,CAC5B,CAAC,CAAC,CAAC,EACH,CAAC,CAAC,CAAC,EACH,QAAQ,EACR,WAAW,EACX,YAAY,CACb;YACD,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,GAAG,OAAO,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,EAAE,GAAG,OAAO,CAAC,EAAE,KAAK,CAAC,CAAC;YACvE,IAAI,IAAI,GAAG,WAAW,IAAI,IAAI,GAAG,IAAI,CAAC,QAAQ,EAAE;AAC9C,gBAAA,IAAI,GAAG,EAAE,IAAI,EAAE,OAAO,EAAE,aAAa,EAAE,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE;YAC7D;QACF;IACF;AAEA,IAAA,OAAO,IAAI;AACb;AAEA;;AAEG;AACG,SAAU,aAAa,CAAC,SAA0B,EAAA;IACtD,OAAO,CAAC,GAAG,SAAS,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;AACjD;AAEA;;AAEG;AACG,SAAU,iBAAiB,CAC/B,OAAA,GAA4B,CAAC,CAAC,EAAE,CAAC,CAAC,EAClC,OAAA,GAA4B,CAAC,CAAC,EAAE,CAAC,CAAC,EAAA;IAElC,OAAO;AACL,QAAA,SAAS,EAAE;AACT,YAAA;AACE,gBAAA,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;AACb,gBAAA,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;gBACb,QAAQ,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE;AACxB,gBAAA,SAAS,EAAE;AACT,oBAAA,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC;AAChC,oBAAA,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC;AACjC,iBAAA;AACD,gBAAA,WAAW,EAAE,QAAQ;gBACrB,EAAE,EAAE,kBAAkB,EAAE;AACzB,aAAA;AACD,YAAA;AACE,gBAAA,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;AACb,gBAAA,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;AACb,gBAAA,QAAQ,EAAE;AACR,oBAAA,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;AACjC,oBAAA,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;AAClC,iBAAA;gBACD,SAAS,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE;AACzB,gBAAA,WAAW,EAAE,QAAQ;gBACrB,EAAE,EAAE,kBAAkB,EAAE;AACzB,aAAA;AACF,SAAA;QACD,OAAO;QACP,OAAO;KACR;AACH;AAEA;;AAEG;AACG,SAAU,iBAAiB,CAAC,SAA0B,EAAA;AAC1D,IAAA,OAAO,SAAS,CAAC,GAAG,CAAC,EAAE,IACrB,EAAE,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE,EAAE,EAAE,kBAAkB,EAAE,EAAE,CACjD;AACH;;;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useCurveInteraction.js","sources":["../../../../../../src/components/controls/CurveEditor/useCurveInteraction.ts"],"sourcesContent":[null],"names":[],"mappings":";;;;;AAqEA;;;;;;;;AASA;AAIE;;AAEF;AAEM;AAGJ;AAqBA;;;;AAYA;;;;AAMA;AACE;AACA;;AACA;AACA;AACF;AAEA;;;AAIE;;AAME;;AAEF;AAIF;;AAGM;AAA+B;;;;;AAM7B;;;;AAIJ;;;AAMJ;AAEI;;AACA;AACA;;AAEA;;AAGA;;AAEA;AAEA;;;;AAIE;;AAEE;;AAEE;;;AAEA;;;;;;;;;AASA;AACA;;;AAGA;;;;;;AAMF;;;;AAKJ;AAEE;;;;;;;AAOE;AACA;;;;;;;AAOF;;;;AAKF;AACE;;AAGF;;AAEA;AACF;;;;;;;;;;AAWC;;AAIH;AAEI;AACA;;AAEA;;AAEA;AAEA;;AAEE;AACA;;AAGA;AACE;;AACK;;AAIL;;AACK;AACL;;;AAEA;;;;AAKJ;;;;AAKA;;AAEA;AACE;;AAEA;;AAEA;;AAIA;AACA;;;;AAKE;AACA;AACA;AACA;;AAGF;AACA;;AAGA;AACE;;;AAIF;AACE;AACA;;AAEE;;;AAGA;;;;;AAMF;;AAGF;AAIA;AACE;AACA;AACD;;;;;;;AAQD;;AAEA;AACA;AACE;AACA;;AAGF;AACA;;;;;;;;AAQA;;;;AAKA;AAKA;;;;AAOJ;;;;;;;;;;;;;;;;AAiBC;;AAIH;AAEI;AACA;;AAEA;AAEA;;;;AAII;AAEA;;AAEE;;;;;;AAKA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;;AAIE;AACA;;AAGA;;;;;;;AAUR;;AAGI;;AAGF;AACA;;;;AAKF;AACF;;;;;;;;;AAUC;;AAIH;;;AAGI;AACA;;AAEA;;AAEA;AAEA;AACE;;;;AAGA;;;;;AAMA;AAIA;AACE;AACA;;;AAGF;;;;;AAMA;;;;AAKA;;;;AAIE;;AAGF;AACE;AACA;;;AAGA;;;AAIF;AACA;AACE;AACA;;;AAGF;AAEA;;;;AAIJ;;;;;;;;;;;;;;;;AAiBC;;AAIH;;;;AAII;AAAuC;;;;AAKrC;AAAqD;;AAEvD;AACF;;AAGA;;AAEA;;AAEA;AACA;AACF;;;;;;;;;;AAUC;AAED;;;AAKA;AAEA;;;;AAKM;AAAuC;AAEvC;;AAEI;AACA;;;;;;AAMN;AAEA;;AAEA;AACF;;;;;;;;AASC;;;;;;;;AAaG;AAAuC;AAEvC;;AAEA;;AAGE;AACE;;;AAEF;AACE;;;AAEF;;;AAGA;;;;;AAMA;;;AAIA;AACA;AACA;;AAEJ;AAEA;AACE;AACA;;;AAGF;AACF;;;;;;;;;;AAWC;;;;AAMH;AAEI;;AAEA;AACE;;;;;AAIE;;;;;;AAME;;;;AAIJ;AACA;AACA;;AAEE;;;;;;AAKF;AACA;AACA;AACA;AACA;;;;;;AAKE;AACA;;;;;AAIN;;;;;;;;;;AAWC;;;;;;AAQD;;;;;;AAMC;AACD;AACA;AACA;;AAEJ;;"}
|
|
1
|
+
{"version":3,"file":"useCurveInteraction.js","sources":["src/components/controls/CurveEditor/useCurveInteraction.ts"],"sourcesContent":["'use client';\n\nimport { useState, useCallback, useRef } from 'react';\n\nimport { clamp, roundToPrecision } from '@/utils/mathUtils';\n\nimport {\n canvasToDomain,\n hitTest,\n sortKeyframes,\n generateKeyframeId,\n constrainTangent,\n computeAutoTangent,\n} from './curveUtils';\n\nimport type {\n CurveData,\n CurveKeyframe,\n CurveViewport,\n CurveHitTest,\n TangentMode,\n} from './CurveEditor.types';\nimport type React from 'react';\n\ntype DragState =\n | { type: 'idle' }\n | { type: 'keyframe'; keyframeIndex: number; startX: number; startY: number }\n | { type: 'handleIn'; keyframeIndex: number }\n | { type: 'handleOut'; keyframeIndex: number }\n | { type: 'boxSelect'; startPx: number; startPy: number };\n\ninterface UseCurveInteractionOptions {\n curve: CurveData;\n viewport: CurveViewport;\n canvasRef: React.RefObject<HTMLCanvasElement | null>;\n disabled: boolean;\n readOnly: boolean;\n allowAdd: boolean;\n allowDelete: boolean;\n maxKeyframes: number;\n lockEndpoints: boolean;\n clampY: boolean;\n snapToGrid: boolean;\n gridSubdivisions: number;\n lockTangents: boolean;\n minKeyframeDistance: number;\n precision: number;\n onChange: (curve: CurveData) => void;\n onChangeComplete?: (curve: CurveData) => void;\n onSelectionChange?: (ids: string[]) => void;\n}\n\ninterface UseCurveInteractionReturn {\n selectedIds: Set<string>;\n hoveredElement: CurveHitTest | null;\n selectionBox: { x1: number; y1: number; x2: number; y2: number } | null;\n isDragging: boolean;\n handlers: {\n onPointerDown: (e: React.PointerEvent) => void;\n onPointerMove: (e: React.PointerEvent) => void;\n onPointerUp: (e: React.PointerEvent) => void;\n onDoubleClick: (e: React.MouseEvent) => void;\n onKeyDown: (e: React.KeyboardEvent) => void;\n };\n setTangentMode: (mode: TangentMode) => void;\n selectAll: () => void;\n deleteSelected: () => void;\n}\n\nconst TANGENT_CYCLE: TangentMode[] = [\n 'free',\n 'aligned',\n 'mirrored',\n 'auto',\n 'linear',\n 'step',\n];\n\nfunction getCanvasCoords(\n e: React.PointerEvent | React.MouseEvent,\n canvas: HTMLCanvasElement\n): { px: number; py: number } {\n const rect = canvas.getBoundingClientRect();\n return { px: e.clientX - rect.left, py: e.clientY - rect.top };\n}\n\nexport function useCurveInteraction(\n options: UseCurveInteractionOptions\n): UseCurveInteractionReturn {\n const {\n curve,\n viewport,\n canvasRef,\n disabled,\n readOnly,\n allowAdd,\n allowDelete,\n maxKeyframes,\n lockEndpoints,\n lockTangents,\n clampY,\n snapToGrid,\n gridSubdivisions,\n minKeyframeDistance,\n precision,\n onChange,\n onChangeComplete,\n onSelectionChange,\n } = options;\n\n const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set());\n const [hoveredElement, setHoveredElement] = useState<CurveHitTest | null>(\n null\n );\n const [selectionBox, setSelectionBox] = useState<{\n x1: number;\n y1: number;\n x2: number;\n y2: number;\n } | null>(null);\n\n const dragStateRef = useRef<DragState>({ type: 'idle' });\n const curveBeforeDragRef = useRef<CurveData | null>(null);\n\n // Ref drives synchronous reads inside the pointer handlers; the state mirror\n // is the value exposed for rendering (kept in sync on drag start/end).\n const [isDragging, setIsDragging] = useState(false);\n\n const getCanvasSize = useCallback(() => {\n const canvas = canvasRef.current;\n if (!canvas) return { w: 320, h: 200 };\n const rect = canvas.getBoundingClientRect();\n return { w: rect.width, h: rect.height };\n }, [canvasRef]);\n\n const updateSelection = useCallback(\n (ids: Set<string>) => {\n setSelectedIds(ids);\n onSelectionChange?.(Array.from(ids));\n },\n [onSelectionChange]\n );\n\n const snapValue = useCallback(\n (value: number, domainRange: number): number => {\n const gridStep = domainRange / gridSubdivisions;\n return Math.round(value / gridStep) * gridStep;\n },\n [gridSubdivisions]\n );\n\n const applyAutoTangents = useCallback(\n (keyframes: CurveKeyframe[]): CurveKeyframe[] => {\n return keyframes.map((kf, i) => {\n if (kf.tangentMode !== 'auto') return kf;\n const prev = i > 0 ? (keyframes[i - 1] ?? null) : null;\n const next =\n i < keyframes.length - 1 ? (keyframes[i + 1] ?? null) : null;\n const tangents = computeAutoTangent(prev, kf, next);\n return {\n ...kf,\n handleIn: tangents.handleIn,\n handleOut: tangents.handleOut,\n };\n });\n },\n []\n );\n\n // ─── Pointer Down ───\n const onPointerDown = useCallback(\n (e: React.PointerEvent) => {\n if (disabled) return;\n const canvas = canvasRef.current;\n if (!canvas) return;\n\n const { px, py } = getCanvasCoords(e, canvas);\n const { w, h } = getCanvasSize();\n\n if (e.button !== 0) return;\n\n const hit = hitTest(px, py, curve, viewport, w, h);\n\n if (hit.type === 'keyframe') {\n const kf = curve.keyframes[hit.keyframeIndex];\n if (!kf?.id) return;\n\n if (e.shiftKey) {\n // Toggle selection\n const newSel = new Set(selectedIds);\n if (newSel.has(kf.id)) {\n newSel.delete(kf.id);\n } else {\n newSel.add(kf.id);\n }\n updateSelection(newSel);\n } else if (!selectedIds.has(kf.id)) {\n updateSelection(new Set([kf.id]));\n }\n\n if (!readOnly) {\n curveBeforeDragRef.current = {\n ...curve,\n keyframes: [...curve.keyframes],\n };\n dragStateRef.current = {\n type: 'keyframe',\n keyframeIndex: hit.keyframeIndex,\n startX: kf.x,\n startY: kf.y,\n };\n setIsDragging(true);\n canvas.setPointerCapture(e.pointerId);\n }\n return;\n }\n\n if (\n (hit.type === 'handleIn' || hit.type === 'handleOut') &&\n !readOnly &&\n !lockTangents\n ) {\n const kf = curve.keyframes[hit.keyframeIndex];\n if (!kf?.id) return;\n updateSelection(new Set([kf.id]));\n curveBeforeDragRef.current = {\n ...curve,\n keyframes: [...curve.keyframes],\n };\n dragStateRef.current = {\n type: hit.type,\n keyframeIndex: hit.keyframeIndex,\n };\n setIsDragging(true);\n canvas.setPointerCapture(e.pointerId);\n return;\n }\n\n // Empty area — start box select or clear selection\n if (!e.shiftKey) {\n updateSelection(new Set());\n }\n\n dragStateRef.current = { type: 'boxSelect', startPx: px, startPy: py };\n setIsDragging(true);\n canvas.setPointerCapture(e.pointerId);\n },\n [\n disabled,\n readOnly,\n lockTangents,\n canvasRef,\n curve,\n viewport,\n selectedIds,\n updateSelection,\n getCanvasSize,\n ]\n );\n\n // ─── Pointer Move ───\n const onPointerMove = useCallback(\n (e: React.PointerEvent) => {\n const canvas = canvasRef.current;\n if (!canvas) return;\n\n const { px, py } = getCanvasCoords(e, canvas);\n const { w, h } = getCanvasSize();\n const drag = dragStateRef.current;\n\n if (drag.type === 'idle') {\n // Hover detection\n const hit = hitTest(px, py, curve, viewport, w, h);\n setHoveredElement(hit.type !== 'none' ? hit : null);\n\n // Cursor\n if (hit.type === 'keyframe') {\n canvas.style.cursor = 'pointer';\n } else if (\n (hit.type === 'handleIn' || hit.type === 'handleOut') &&\n !lockTangents\n ) {\n canvas.style.cursor = 'crosshair';\n } else if (hit.type === 'curve') {\n canvas.style.cursor = 'pointer';\n } else {\n canvas.style.cursor = disabled ? 'not-allowed' : 'default';\n }\n return;\n }\n\n if (drag.type === 'boxSelect') {\n setSelectionBox({ x1: drag.startPx, y1: drag.startPy, x2: px, y2: py });\n return;\n }\n\n if (readOnly) return;\n\n if (drag.type === 'keyframe') {\n const domain = canvasToDomain(px, py, viewport, w, h);\n const kf = curve.keyframes[drag.keyframeIndex];\n if (!kf) return;\n\n const isEndpoint =\n drag.keyframeIndex === 0 ||\n drag.keyframeIndex === curve.keyframes.length - 1;\n\n let newX = domain.x;\n let newY = domain.y;\n\n // Apply snap\n const shouldSnap = snapToGrid !== e.ctrlKey; // Ctrl toggles snap\n if (shouldSnap) {\n const xRange = curve.domainX[1] - curve.domainX[0];\n const yRange = curve.domainY[1] - curve.domainY[0];\n newX = snapValue(newX, xRange);\n newY = snapValue(newY, yRange);\n }\n\n newX = roundToPrecision(newX, precision);\n newY = roundToPrecision(newY, precision);\n\n // Lock X for endpoints\n if (lockEndpoints && isEndpoint) {\n newX = kf.x;\n }\n\n // Clamp X between neighbors\n if (!isEndpoint || !lockEndpoints) {\n const prevKf = curve.keyframes[drag.keyframeIndex - 1];\n const nextKf = curve.keyframes[drag.keyframeIndex + 1];\n if (prevKf) {\n newX = Math.max(newX, prevKf.x + minKeyframeDistance);\n }\n if (nextKf) {\n newX = Math.min(newX, nextKf.x - minKeyframeDistance);\n }\n }\n\n // Clamp Y to domain\n if (clampY) {\n newY = clamp(newY, curve.domainY[0], curve.domainY[1]);\n }\n\n const newKeyframes = curve.keyframes.map((k, i) =>\n i === drag.keyframeIndex ? { ...k, x: newX, y: newY } : k\n );\n\n onChange({\n ...curve,\n keyframes: applyAutoTangents(newKeyframes),\n });\n return;\n }\n\n // The only remaining drag types here are 'handleIn' and 'handleOut'\n // (idle, boxSelect and keyframe are handled and returned above).\n {\n const kf = curve.keyframes[drag.keyframeIndex];\n if (!kf) return;\n\n const domain = canvasToDomain(px, py, viewport, w, h);\n const handleOffset = {\n x: roundToPrecision(domain.x - kf.x, precision),\n y: roundToPrecision(domain.y - kf.y, precision),\n };\n\n let updatedKf: CurveKeyframe;\n if (drag.type === 'handleIn') {\n updatedKf = { ...kf, handleIn: handleOffset };\n } else {\n updatedKf = { ...kf, handleOut: handleOffset };\n }\n\n // Promote auto → free when the user manually adjusts a handle.\n // This prevents applyAutoTangents from overwriting the change.\n if (updatedKf.tangentMode === 'auto') {\n updatedKf = { ...updatedKf, tangentMode: 'free' };\n }\n\n // Apply tangent constraints\n updatedKf = constrainTangent(\n updatedKf,\n drag.type === 'handleIn' ? 'in' : 'out'\n );\n\n const newKeyframes = curve.keyframes.map((k, i) =>\n i === drag.keyframeIndex ? updatedKf : k\n );\n\n onChange({ ...curve, keyframes: newKeyframes });\n return;\n }\n },\n [\n canvasRef,\n curve,\n viewport,\n disabled,\n readOnly,\n lockTangents,\n snapToGrid,\n lockEndpoints,\n clampY,\n minKeyframeDistance,\n precision,\n onChange,\n getCanvasSize,\n snapValue,\n applyAutoTangents,\n ]\n );\n\n // ─── Pointer Up ───\n const onPointerUp = useCallback(\n (e: React.PointerEvent) => {\n const canvas = canvasRef.current;\n if (!canvas) return;\n\n const drag = dragStateRef.current;\n\n if (drag.type === 'boxSelect') {\n // Resolve box selection\n if (selectionBox) {\n const { w, h } = getCanvasSize();\n const newSel = new Set(e.shiftKey ? selectedIds : new Set<string>());\n\n for (let i = 0; i < curve.keyframes.length; i++) {\n const kf = curve.keyframes[i];\n if (!kf) continue;\n if (!kf.id) continue;\n\n const [vxMin, vxMax] = viewport.viewX;\n const [vyMin, vyMax] = viewport.viewY;\n const vw = vxMax - vxMin || 1;\n const vh = vyMax - vyMin || 1;\n const kfPx = ((kf.x - vxMin) / vw) * w;\n const kfPy = ((vyMax - kf.y) / vh) * h;\n\n const boxLeft = Math.min(selectionBox.x1, selectionBox.x2);\n const boxRight = Math.max(selectionBox.x1, selectionBox.x2);\n const boxTop = Math.min(selectionBox.y1, selectionBox.y2);\n const boxBottom = Math.max(selectionBox.y1, selectionBox.y2);\n\n if (\n kfPx >= boxLeft &&\n kfPx <= boxRight &&\n kfPy >= boxTop &&\n kfPy <= boxBottom\n ) {\n newSel.add(kf.id);\n }\n }\n\n updateSelection(newSel);\n }\n\n setSelectionBox(null);\n }\n\n if (\n (drag.type === 'keyframe' ||\n drag.type === 'handleIn' ||\n drag.type === 'handleOut') &&\n curveBeforeDragRef.current\n ) {\n onChangeComplete?.(curve);\n curveBeforeDragRef.current = null;\n }\n\n dragStateRef.current = { type: 'idle' };\n setIsDragging(false);\n canvas.releasePointerCapture(e.pointerId);\n },\n [\n canvasRef,\n curve,\n viewport,\n selectionBox,\n selectedIds,\n updateSelection,\n onChangeComplete,\n getCanvasSize,\n ]\n );\n\n // ─── Double Click ───\n const onDoubleClick = useCallback(\n (e: React.MouseEvent) => {\n if (disabled || readOnly) return;\n const canvas = canvasRef.current;\n if (!canvas) return;\n\n const { px, py } = getCanvasCoords(e, canvas);\n const { w, h } = getCanvasSize();\n const hit = hitTest(px, py, curve, viewport, w, h);\n\n if (hit.type === 'keyframe') {\n if (lockTangents) return;\n // Cycle tangent mode\n const kf = curve.keyframes[hit.keyframeIndex];\n if (!kf) return;\n\n const currentIndex = TANGENT_CYCLE.indexOf(kf.tangentMode);\n const nextIndex = (currentIndex + 1) % TANGENT_CYCLE.length;\n const nextMode = TANGENT_CYCLE[nextIndex] ?? 'free';\n\n const newKeyframes = curve.keyframes.map((k, i) =>\n i === hit.keyframeIndex ? { ...k, tangentMode: nextMode } : k\n );\n\n const updated = {\n ...curve,\n keyframes: applyAutoTangents(newKeyframes),\n };\n onChange(updated);\n onChangeComplete?.(updated);\n return;\n }\n\n if (allowAdd && curve.keyframes.length < maxKeyframes) {\n // Add keyframe at cursor position\n const domain = canvasToDomain(px, py, viewport, w, h);\n const newX = roundToPrecision(domain.x, precision);\n let newY = roundToPrecision(domain.y, precision);\n\n // Check within domain X\n if (newX < curve.domainX[0] || newX > curve.domainX[1]) return;\n\n // Clamp Y to domain\n if (clampY) {\n newY = clamp(newY, curve.domainY[0], curve.domainY[1]);\n }\n\n const newKf: CurveKeyframe = {\n x: newX,\n y: newY,\n handleIn: { x: 0, y: 0 },\n handleOut: { x: 0, y: 0 },\n tangentMode: 'auto',\n id: generateKeyframeId(),\n };\n\n const newKeyframes = sortKeyframes([...curve.keyframes, newKf]);\n const updated = {\n ...curve,\n keyframes: applyAutoTangents(newKeyframes),\n };\n onChange(updated);\n onChangeComplete?.(updated);\n\n if (newKf.id) {\n updateSelection(new Set([newKf.id]));\n }\n }\n },\n [\n disabled,\n readOnly,\n lockTangents,\n canvasRef,\n curve,\n viewport,\n allowAdd,\n maxKeyframes,\n clampY,\n precision,\n onChange,\n onChangeComplete,\n updateSelection,\n getCanvasSize,\n applyAutoTangents,\n ]\n );\n\n // ─── Actions ───\n const deleteSelectedFn = useCallback(() => {\n if (readOnly || !allowDelete) return;\n\n const newKeyframes = curve.keyframes.filter(kf => {\n if (!kf.id || !selectedIds.has(kf.id)) return true;\n\n // Don't delete endpoints if locked\n if (lockEndpoints) {\n const idx = curve.keyframes.indexOf(kf);\n if (idx === 0 || idx === curve.keyframes.length - 1) return true;\n }\n return false;\n });\n\n // Must keep at least 2 keyframes\n if (newKeyframes.length < 2) return;\n\n const updated = { ...curve, keyframes: applyAutoTangents(newKeyframes) };\n onChange(updated);\n onChangeComplete?.(updated);\n updateSelection(new Set());\n }, [\n curve,\n selectedIds,\n readOnly,\n allowDelete,\n lockEndpoints,\n onChange,\n onChangeComplete,\n updateSelection,\n applyAutoTangents,\n ]);\n\n const selectAllFn = useCallback(() => {\n const allIds = new Set(\n curve.keyframes.map(kf => kf.id).filter(Boolean) as string[]\n );\n updateSelection(allIds);\n }, [curve, updateSelection]);\n\n const setTangentModeFn = useCallback(\n (mode: TangentMode) => {\n if (readOnly || lockTangents || selectedIds.size === 0) return;\n\n const newKeyframes = curve.keyframes.map(kf => {\n if (!kf.id || !selectedIds.has(kf.id)) return kf;\n\n if (mode === 'linear') {\n return {\n ...kf,\n tangentMode: mode,\n handleIn: { x: 0, y: 0 },\n handleOut: { x: 0, y: 0 },\n };\n }\n return { ...kf, tangentMode: mode };\n });\n\n const updated = { ...curve, keyframes: applyAutoTangents(newKeyframes) };\n onChange(updated);\n onChangeComplete?.(updated);\n },\n [\n curve,\n selectedIds,\n readOnly,\n lockTangents,\n onChange,\n onChangeComplete,\n applyAutoTangents,\n ]\n );\n\n const nudgeSelected = useCallback(\n (direction: string, fine: boolean) => {\n const [vxMin, vxMax] = viewport.viewX;\n const [vyMin, vyMax] = viewport.viewY;\n\n const baseStep = fine ? 0.001 : 0.01;\n const xStep = (vxMax - vxMin) * baseStep;\n const yStep = (vyMax - vyMin) * baseStep;\n\n const newKeyframes = curve.keyframes.map(kf => {\n if (!kf.id || !selectedIds.has(kf.id)) return kf;\n\n let { x, y } = kf;\n const idx = curve.keyframes.indexOf(kf);\n const isEndpoint = idx === 0 || idx === curve.keyframes.length - 1;\n\n switch (direction) {\n case 'ArrowLeft':\n if (!(lockEndpoints && isEndpoint)) x -= xStep;\n break;\n case 'ArrowRight':\n if (!(lockEndpoints && isEndpoint)) x += xStep;\n break;\n case 'ArrowUp':\n y += yStep;\n break;\n case 'ArrowDown':\n y -= yStep;\n break;\n }\n\n if (clampY) {\n y = clamp(y, curve.domainY[0], curve.domainY[1]);\n }\n\n return {\n ...kf,\n x: roundToPrecision(x, precision),\n y: roundToPrecision(y, precision),\n };\n });\n\n const updated = {\n ...curve,\n keyframes: applyAutoTangents(sortKeyframes(newKeyframes)),\n };\n onChange(updated);\n onChangeComplete?.(updated);\n },\n [\n curve,\n viewport,\n selectedIds,\n lockEndpoints,\n clampY,\n precision,\n onChange,\n onChangeComplete,\n applyAutoTangents,\n ]\n );\n\n // ─── Keyboard ───\n // Declared after the action callbacks it dispatches so they are referenced\n // (and depended on) by value rather than via a forward closure.\n const onKeyDown = useCallback(\n (e: React.KeyboardEvent) => {\n if (disabled) return;\n\n switch (e.key) {\n case 'Delete':\n case 'Backspace': {\n if (readOnly || !allowDelete) return;\n e.preventDefault();\n deleteSelectedFn();\n break;\n }\n case 'a': {\n if (e.ctrlKey || e.metaKey) {\n e.preventDefault();\n selectAllFn();\n }\n break;\n }\n case 'ArrowUp':\n case 'ArrowDown':\n case 'ArrowLeft':\n case 'ArrowRight': {\n if (readOnly || selectedIds.size === 0) return;\n e.preventDefault();\n nudgeSelected(e.key, e.shiftKey);\n break;\n }\n case '1':\n case '2':\n case '3':\n case '4':\n case '5':\n case '6': {\n if (readOnly || lockTangents || selectedIds.size === 0) return;\n e.preventDefault();\n const modeIndex = parseInt(e.key) - 1;\n const mode = TANGENT_CYCLE[modeIndex];\n if (mode) setTangentModeFn(mode);\n break;\n }\n }\n },\n [\n disabled,\n readOnly,\n lockTangents,\n allowDelete,\n selectedIds,\n deleteSelectedFn,\n selectAllFn,\n nudgeSelected,\n setTangentModeFn,\n ]\n );\n\n return {\n selectedIds,\n hoveredElement,\n selectionBox,\n isDragging,\n handlers: {\n onPointerDown,\n onPointerMove,\n onPointerUp,\n onDoubleClick,\n onKeyDown,\n },\n setTangentMode: setTangentModeFn,\n selectAll: selectAllFn,\n deleteSelected: deleteSelectedFn,\n };\n}\n"],"names":[],"mappings":";;;;;AAqEA;;;;;;;;AASA;AAIE;;AAEF;AAEM;AAGJ;AAqBA;;;;AAYA;;;;AAMA;AACE;AACA;;AACA;AACA;AACF;AAEA;;;AAIE;;AAME;;AAEF;AAIF;;AAGM;AAA+B;;;;;AAM7B;;;;AAIJ;;;AAMJ;AAEI;;AACA;AACA;;AAEA;;AAGA;;AAEA;AAEA;;;;AAIE;;AAEE;;AAEE;;;AAEA;;;;;;;;;AASA;AACA;;;AAGA;;;;;;AAMF;;;;AAKJ;AAEE;;;;;;;AAOE;AACA;;;;;;;AAOF;;;;AAKF;AACE;;AAGF;;AAEA;AACF;;;;;;;;;;AAWC;;AAIH;AAEI;AACA;;AAEA;;AAEA;AAEA;;AAEE;AACA;;AAGA;AACE;;AACK;;AAIL;;AACK;AACL;;;AAEA;;;;AAKJ;;;;AAKA;;AAEA;AACE;;AAEA;;AAEA;;AAIA;AACA;;;;AAKE;AACA;AACA;AACA;;AAGF;AACA;;AAGA;AACE;;;AAIF;AACE;AACA;;AAEE;;;AAGA;;;;;AAMF;;AAGF;AAIA;AACE;AACA;AACD;;;;;;;AAQD;;AAEA;AACA;AACE;AACA;;AAGF;AACA;;;;;;;;AAQA;;;;AAKA;AAKA;;;;AAOJ;;;;;;;;;;;;;;;;AAiBC;;AAIH;AAEI;AACA;;AAEA;AAEA;;;;AAII;AAEA;;AAEE;;;;;;AAKA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;;AAIE;AACA;;AAGA;;;;;;;AAUR;;AAGI;;AAGF;AACA;;;;AAKF;AACF;;;;;;;;;AAUC;;AAIH;;;AAGI;AACA;;AAEA;;AAEA;AAEA;AACE;;;;AAGA;;;;;AAMA;AAIA;AACE;AACA;;;AAGF;;;;;AAMA;;;;AAKA;;;;AAIE;;AAGF;AACE;AACA;;;AAGA;;;AAIF;AACA;AACE;AACA;;;AAGF;AAEA;;;;AAIJ;;;;;;;;;;;;;;;;AAiBC;;AAIH;;;;AAII;AAAuC;;;;AAKrC;AAAqD;;AAEvD;AACF;;AAGA;;AAEA;;AAEA;AACA;AACF;;;;;;;;;;AAUC;AAED;;;AAKA;AAEA;;;;AAKM;AAAuC;AAEvC;;AAEI;AACA;;;;;;AAMN;AAEA;;AAEA;AACF;;;;;;;;AASC;;;;;;;;AAaG;AAAuC;AAEvC;;AAEA;;AAGE;AACE;;;AAEF;AACE;;;AAEF;;;AAGA;;;;;AAMA;;;AAIA;AACA;AACA;;AAEJ;AAEA;AACE;AACA;;;AAGF;AACF;;;;;;;;;;AAWC;;;;AAMH;AAEI;;AAEA;AACE;;;;;AAIE;;;;;;AAME;;;;AAIJ;AACA;AACA;;AAEE;;;;;;AAKF;AACA;AACA;AACA;AACA;;;;;;AAKE;AACA;;;;;AAIN;;;;;;;;;;AAWC;;;;;;AAQD;;;;;;AAMC;AACD;AACA;AACA;;AAEJ;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useCurveRenderer.js","sources":["../../../../../../src/components/controls/CurveEditor/useCurveRenderer.ts"],"sourcesContent":[null],"names":[],"mappings":";;;;;;AAsDA;AAEM;AACJ;AAEA;AACE;AACA;;;AAGA;;AAEA;AAEA;AAEA;AACA;AACA;;;;AAKA;;AAEE;AACA;AACA;;;AAEA;;AAGF;;AAkBA;AACE;;;;AAMF;AACA;;;;;;AAOI;AACA;;;;AAID;;;;;;AAOC;AACA;;;AAGD;;;;;;;AAiBC;AACA;;;;;AAKD;;;AAIH;AACA;;AAGA;AACE;;;;;;AA6BA;;AAGF;AACF;;;;;;AAOE;;;AAGI;AACA;AACF;AACA;AACA;AACE;AACF;;AAGF;AACA;;AAEJ;AAEA;AACA;AAEA;AASE;;AAEA;AACA;;AAEA;AACA;AACA;AACA;;AAGA;AACA;;AACA;;AAGA;AACE;AACA;;AACA;;;;AAKJ;AAEA;AAWE;AAEA;AACE;AACA;;AACA;;;;AAGA;AACA;;AAGA;AAQA;AACA;AACA;;;;;;AAMA;;;AAIA;AAEA;AACA;;AAEA;;;AAUA;AAQA;AACA;AACA;;;;;;AAMA;;;AAIA;AAEE;AACF;AACA;;AAEA;;;AASJ;AAEA;AAYE;AAEA;AACE;AACA;;;AAEA;AAEA;;AAIA;;;AAIE;;;;;;;;;;AASA;;AAEA;;;AAIA;;AAEA;AACA;;;;AAGA;;AAEA;AACA;;;;AAIN;AAEA;AAKE;AACA;AACA;AACA;AAEA;AACA;;AAGA;AACA;AACA;;;AAGA;AACF;;"}
|
|
1
|
+
{"version":3,"file":"useCurveRenderer.js","sources":["src/components/controls/CurveEditor/useCurveRenderer.ts"],"sourcesContent":["'use client';\n\nimport { useEffect, useRef, useCallback } from 'react';\n\nimport {\n drawGrid as sharedDrawGrid,\n drawDomainBounds as sharedDrawDomainBounds,\n drawAxisLabels as sharedDrawAxisLabels,\n} from '@/components/primitives/canvas/canvasDrawing';\nimport {\n resolveCanvasTheme,\n resolveVarValue,\n} from '@/components/primitives/canvas/canvasTheme';\n\nimport { domainToCanvas, sampleCurve } from './curveUtils';\n\nimport type {\n CurveData,\n CurveViewport,\n CurveHitTest,\n CurveBackgroundInfo,\n} from './CurveEditor.types';\nimport type { CanvasThemeColors } from '@/components/primitives/canvas/canvas.types';\nimport type React from 'react';\n\ninterface UseCurveRendererOptions {\n canvasRef: React.RefObject<HTMLCanvasElement | null>;\n curve: CurveData;\n viewport: CurveViewport;\n showGrid: boolean;\n gridSubdivisions: number;\n showAxisLabels: boolean;\n labelX?: string;\n labelY?: string;\n curveColor: string;\n curveWidth: number;\n renderBackground?: (\n ctx: CanvasRenderingContext2D,\n info: CurveBackgroundInfo\n ) => void;\n selectedIds: Set<string>;\n hoveredElement: CurveHitTest | null;\n selectionBox: {\n x1: number;\n y1: number;\n x2: number;\n y2: number;\n } | null;\n disabled: boolean;\n isDragging: boolean;\n lockTangents: boolean;\n resizeToken?: number;\n}\n\n// CanvasThemeColors and resolveCanvasTheme are now imported from shared canvas primitives\n\nexport function useCurveRenderer(options: UseCurveRendererOptions): void {\n const rafRef = useRef<number>(0);\n\n const draw = useCallback(() => {\n const canvas = options.canvasRef.current;\n if (!canvas) return;\n\n const ctx = canvas.getContext('2d');\n if (!ctx) return;\n\n const theme = resolveCanvasTheme(canvas);\n\n const dpr =\n typeof window !== 'undefined' ? window.devicePixelRatio || 1 : 1;\n const rect = canvas.getBoundingClientRect();\n const w = rect.width;\n const h = rect.height;\n\n // Set canvas resolution for retina. Writing the canvas backing-store size\n // is a DOM side effect of drawing; the rule mistakes it for mutating the\n // `options` argument because `canvas` is reached via options.canvasRef.\n if (canvas.width !== w * dpr || canvas.height !== h * dpr) {\n // eslint-disable-next-line react-hooks/immutability\n canvas.width = w * dpr;\n canvas.height = h * dpr;\n ctx.scale(dpr, dpr);\n } else {\n ctx.setTransform(dpr, 0, 0, dpr, 0, 0);\n }\n\n const {\n curve,\n viewport,\n showGrid,\n gridSubdivisions,\n showAxisLabels,\n labelX,\n labelY,\n curveColor: rawCurveColor,\n curveWidth,\n renderBackground,\n selectedIds,\n hoveredElement,\n selectionBox,\n disabled,\n } = options;\n\n // Resolve curveColor if it's a CSS variable reference\n const curveColor = rawCurveColor.startsWith('var(')\n ? resolveVarValue(canvas, rawCurveColor)\n : rawCurveColor;\n\n const opacity = disabled ? 0.4 : 1;\n\n // 1. Clear background\n ctx.globalAlpha = 1;\n ctx.fillStyle = theme.backgroundSecondary;\n ctx.fillRect(0, 0, w, h);\n\n // 1b. Custom background (histograms, gradients, etc.)\n if (renderBackground) {\n ctx.save();\n renderBackground(ctx, {\n width: w,\n height: h,\n viewport,\n domainX: curve.domainX,\n domainY: curve.domainY,\n });\n ctx.restore();\n }\n\n // 2. Grid lines -- based on domain values, not viewport\n if (showGrid) {\n sharedDrawGrid(ctx, w, h, theme, {\n subdivisions: gridSubdivisions,\n domain: { domainX: curve.domainX, domainY: curve.domainY },\n viewport,\n opacity,\n });\n }\n\n // 3. Domain boundary axes (stronger lines at domain min/max)\n sharedDrawDomainBounds(\n ctx,\n w,\n h,\n theme,\n { domainX: curve.domainX, domainY: curve.domainY },\n viewport,\n opacity\n );\n\n // 4. Axis labels -- based on domain values\n if (showAxisLabels) {\n sharedDrawAxisLabels(ctx, w, h, theme, {\n subdivisions: gridSubdivisions,\n domain: { domainX: curve.domainX, domainY: curve.domainY },\n viewport,\n opacity,\n labelX,\n labelY,\n });\n }\n\n // 5. Curve line\n ctx.globalAlpha = opacity;\n drawCurveLine(ctx, curve, viewport, w, h, curveColor, curveWidth);\n\n // 6. Tangent handles (for selected keyframes)\n if (!options.lockTangents) {\n drawTangentHandles(\n ctx,\n curve,\n viewport,\n w,\n h,\n selectedIds,\n hoveredElement,\n theme,\n opacity\n );\n }\n\n // 7. Keyframe points\n drawKeyframes(\n ctx,\n curve,\n viewport,\n w,\n h,\n curveColor,\n selectedIds,\n hoveredElement,\n theme,\n opacity\n );\n\n // 8. Selection box\n if (selectionBox) {\n drawSelectionBox(ctx, selectionBox, curveColor);\n }\n\n ctx.globalAlpha = 1;\n }, [options]);\n\n // Redraw on state changes. The draw callback mutates the canvas DOM element\n // (backing-store size), which the rule reports as modifying `options`; that\n // is a benign DOM side effect, not a props mutation.\n // eslint-disable-next-line react-hooks/immutability\n useEffect(() => {\n if (options.isDragging) {\n // Use rAF for smooth updates during drag\n const render = () => {\n draw();\n rafRef.current = requestAnimationFrame(render);\n };\n rafRef.current = requestAnimationFrame(render);\n return () => {\n cancelAnimationFrame(rafRef.current);\n };\n }\n\n draw();\n return undefined;\n }, [draw, options.isDragging]);\n}\n\n// drawGrid, drawDomainBounds, drawAxisLabels, and formatLabel are now\n// provided by shared canvas primitives and called in the draw() function above.\n\nfunction drawCurveLine(\n ctx: CanvasRenderingContext2D,\n curve: CurveData,\n viewport: CurveViewport,\n w: number,\n h: number,\n curveColor: string,\n lineWidth: number\n): void {\n if (curve.keyframes.length < 2) return;\n\n const samples = sampleCurve(curve, Math.max(200, w));\n if (samples.length === 0) return;\n\n ctx.strokeStyle = curveColor;\n ctx.lineWidth = lineWidth;\n ctx.lineJoin = 'round';\n ctx.lineCap = 'round';\n ctx.beginPath();\n\n const first = samples[0];\n if (!first) return;\n const firstCanvas = domainToCanvas(first.x, first.y, viewport, w, h);\n ctx.moveTo(firstCanvas.px, firstCanvas.py);\n\n for (let i = 1; i < samples.length; i++) {\n const s = samples[i];\n if (!s) continue;\n const sCanvas = domainToCanvas(s.x, s.y, viewport, w, h);\n ctx.lineTo(sCanvas.px, sCanvas.py);\n }\n\n ctx.stroke();\n}\n\nfunction drawTangentHandles(\n ctx: CanvasRenderingContext2D,\n curve: CurveData,\n viewport: CurveViewport,\n w: number,\n h: number,\n selectedIds: Set<string>,\n hoveredElement: CurveHitTest | null,\n theme: CanvasThemeColors,\n opacity: number\n): void {\n const { keyframes } = curve;\n\n for (let i = 0; i < keyframes.length; i++) {\n const kf = keyframes[i];\n if (!kf) continue;\n if (!kf.id || !selectedIds.has(kf.id)) continue;\n if (kf.tangentMode === 'linear' || kf.tangentMode === 'step') continue;\n\n const isAutoMode = kf.tangentMode === 'auto';\n const kfCanvas = domainToCanvas(kf.x, kf.y, viewport, w, h);\n\n // Handle In\n const hIn = domainToCanvas(\n kf.x + kf.handleIn.x,\n kf.y + kf.handleIn.y,\n viewport,\n w,\n h\n );\n\n ctx.globalAlpha = opacity * (isAutoMode ? 0.4 : 0.7);\n ctx.strokeStyle = theme.textMuted;\n ctx.lineWidth = 1;\n ctx.setLineDash([3, 3]);\n ctx.beginPath();\n ctx.moveTo(kfCanvas.px, kfCanvas.py);\n ctx.lineTo(hIn.px, hIn.py);\n ctx.stroke();\n ctx.setLineDash([]);\n\n // Handle In point\n const hInRadius = isAutoMode ? 3 : 4;\n const isHInHovered =\n hoveredElement?.type === 'handleIn' && hoveredElement.keyframeIndex === i;\n ctx.globalAlpha = opacity;\n ctx.fillStyle = isHInHovered ? theme.textPrimary : theme.textSecondary;\n ctx.beginPath();\n ctx.arc(\n hIn.px,\n hIn.py,\n isHInHovered ? hInRadius + 1 : hInRadius,\n 0,\n Math.PI * 2\n );\n ctx.fill();\n\n // Handle Out\n const hOut = domainToCanvas(\n kf.x + kf.handleOut.x,\n kf.y + kf.handleOut.y,\n viewport,\n w,\n h\n );\n\n ctx.globalAlpha = opacity * (isAutoMode ? 0.4 : 0.7);\n ctx.strokeStyle = theme.textMuted;\n ctx.lineWidth = 1;\n ctx.setLineDash([3, 3]);\n ctx.beginPath();\n ctx.moveTo(kfCanvas.px, kfCanvas.py);\n ctx.lineTo(hOut.px, hOut.py);\n ctx.stroke();\n ctx.setLineDash([]);\n\n // Handle Out point\n const hOutRadius = isAutoMode ? 3 : 4;\n const isHOutHovered =\n hoveredElement?.type === 'handleOut' &&\n hoveredElement.keyframeIndex === i;\n ctx.globalAlpha = opacity;\n ctx.fillStyle = isHOutHovered ? theme.textPrimary : theme.textSecondary;\n ctx.beginPath();\n ctx.arc(\n hOut.px,\n hOut.py,\n isHOutHovered ? hOutRadius + 1 : hOutRadius,\n 0,\n Math.PI * 2\n );\n ctx.fill();\n }\n}\n\nfunction drawKeyframes(\n ctx: CanvasRenderingContext2D,\n curve: CurveData,\n viewport: CurveViewport,\n w: number,\n h: number,\n curveColor: string,\n selectedIds: Set<string>,\n hoveredElement: CurveHitTest | null,\n theme: CanvasThemeColors,\n opacity: number\n): void {\n const { keyframes } = curve;\n\n for (let i = 0; i < keyframes.length; i++) {\n const kf = keyframes[i];\n if (!kf) continue;\n const isSelected = kf.id ? selectedIds.has(kf.id) : false;\n const isHovered =\n hoveredElement?.type === 'keyframe' && hoveredElement.keyframeIndex === i;\n const isEndpoint = i === 0 || i === keyframes.length - 1;\n\n const { px, py } = domainToCanvas(kf.x, kf.y, viewport, w, h);\n\n ctx.globalAlpha = opacity;\n\n if (isEndpoint) {\n // Diamond shape for endpoints\n const size = isHovered ? 8 : isSelected ? 7 : 6;\n ctx.beginPath();\n ctx.moveTo(px, py - size);\n ctx.lineTo(px + size, py);\n ctx.lineTo(px, py + size);\n ctx.lineTo(px - size, py);\n ctx.closePath();\n } else {\n // Circle for middle keyframes\n const radius = isHovered ? 8 : isSelected ? 7 : 6;\n ctx.beginPath();\n ctx.arc(px, py, radius, 0, Math.PI * 2);\n }\n\n if (isSelected) {\n ctx.fillStyle = curveColor;\n ctx.fill();\n ctx.strokeStyle = theme.textPrimary;\n ctx.lineWidth = 2;\n ctx.stroke();\n } else {\n ctx.fillStyle = theme.textPrimary;\n ctx.fill();\n ctx.strokeStyle = curveColor;\n ctx.lineWidth = 1.5;\n ctx.stroke();\n }\n }\n}\n\nfunction drawSelectionBox(\n ctx: CanvasRenderingContext2D,\n box: { x1: number; y1: number; x2: number; y2: number },\n curveColor: string\n): void {\n const x = Math.min(box.x1, box.x2);\n const y = Math.min(box.y1, box.y2);\n const bw = Math.abs(box.x2 - box.x1);\n const bh = Math.abs(box.y2 - box.y1);\n\n ctx.globalAlpha = 0.1;\n ctx.fillStyle = curveColor;\n ctx.fillRect(x, y, bw, bh);\n\n ctx.globalAlpha = 0.5;\n ctx.strokeStyle = curveColor;\n ctx.lineWidth = 1;\n ctx.setLineDash([4, 4]);\n ctx.strokeRect(x, y, bw, bh);\n ctx.setLineDash([]);\n}\n"],"names":[],"mappings":";;;;;;AAsDA;AAEM;AACJ;AAEA;AACE;AACA;;;AAGA;;AAEA;AAEA;AAEA;AACA;AACA;;;;AAKA;;AAEE;AACA;AACA;;;AAEA;;AAGF;;AAkBA;AACE;;;;AAMF;AACA;;;;;;AAOI;AACA;;;;AAID;;;;;;AAOC;AACA;;;AAGD;;;;;;;AAiBC;AACA;;;;;AAKD;;;AAIH;AACA;;AAGA;AACE;;;;;;AA6BA;;AAGF;AACF;;;;;;AAOE;;;AAGI;AACA;AACF;AACA;AACA;AACE;AACF;;AAGF;AACA;;AAEJ;AAEA;AACA;AAEA;AASE;;AAEA;AACA;;AAEA;AACA;AACA;AACA;;AAGA;AACA;;AACA;;AAGA;AACE;AACA;;AACA;;;;AAKJ;AAEA;AAWE;AAEA;AACE;AACA;;AACA;;;;AAGA;AACA;;AAGA;AAQA;AACA;AACA;;;;;;AAMA;;;AAIA;AAEA;AACA;;AAEA;;;AAUA;AAQA;AACA;AACA;;;;;;AAMA;;;AAIA;AAEE;AACF;AACA;;AAEA;;;AASJ;AAEA;AAYE;AAEA;AACE;AACA;;;AAEA;AAEA;;AAIA;;;AAIE;;;;;;;;;;AASA;;AAEA;;;AAIA;;AAEA;AACA;;;;AAGA;;AAEA;AACA;;;;AAIN;AAEA;AAKE;AACA;AACA;AACA;AAEA;AACA;;AAGA;AACA;AACA;;;AAGA;AACF;;"}
|
|
@@ -89,11 +89,17 @@ import '../../Icons/GitBranchIcon.js';
|
|
|
89
89
|
import '../../Icons/BugIcon.js';
|
|
90
90
|
import '../../Icons/MinusIcon.js';
|
|
91
91
|
import '../../Icons/PinIcon.js';
|
|
92
|
+
import '../../Icons/OrbitIcon.js';
|
|
93
|
+
import '../../Icons/RotateIcon.js';
|
|
94
|
+
import '../../Icons/SpinIcon.js';
|
|
95
|
+
import '../../Icons/CameraIcon.js';
|
|
96
|
+
import '../../Icons/CubeIcon.js';
|
|
97
|
+
import '../../Icons/ZoomFitIcon.js';
|
|
92
98
|
import '../../../utils/devWarn.js';
|
|
93
99
|
import { useMergedRef } from '../../../hooks/useMergedRef/useMergedRef.js';
|
|
94
100
|
import '../../../hooks/useBreakpoint/useBreakpoint.js';
|
|
95
101
|
import './../../../assets/src/theme/lightTheme.css.ts.vanilla-Ct6f9Iym.css';
|
|
96
|
-
import '
|
|
102
|
+
import '@vanilla-extract/css';
|
|
97
103
|
import './../../../assets/src/theme/darkTheme.css.ts.vanilla-BHZ3ecRn.css';
|
|
98
104
|
import './../../../assets/src/theme/globalScrollbars.css.ts.vanilla-BAJwnUEJ.css';
|
|
99
105
|
import '../../../hooks/useNavigationHistory/useNavigationHistory.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FileTree.js","sources":["
|
|
1
|
+
{"version":3,"file":"FileTree.js","sources":["src/components/controls/FileTree/FileTree.tsx"],"sourcesContent":["'use client';\n\nimport React, { useCallback, useMemo, useRef } from 'react';\n\nimport { TreeView } from '@/components/controls/TreeView';\nimport {\n ArchiveIcon,\n CodeIcon,\n FileTextIcon,\n FolderIcon,\n FolderOpenIcon,\n ImageIcon,\n PlayIcon,\n} from '@/components/Icons';\nimport { useMergedRef } from '@/hooks';\nimport { cx } from '@/utils/cx';\n\nimport { containerStyle, iconStyle, labelStyle } from './FileTree.css';\nimport { getFileIconKind } from './fileTreeIcons';\nimport { DEFAULT_FILE_TREE_LABELS } from './fileTreeLabels';\nimport { useFileTreeDrop } from './useFileTreeDrop';\n\nimport type {\n FileTreeNode,\n FileTreeNodeState,\n FileTreeProps,\n} from './FileTree.types';\nimport type { FileIconKind } from './fileTreeIcons';\nimport type {\n TreeNodeData,\n TreeNodeState,\n TreeViewSize,\n} from '@/components/controls/TreeView';\n\nconst ICON_PX: Record<TreeViewSize, number> = { sm: 12, md: 14, lg: 16 };\n\n/** Renders a resolved {@link FileIconKind} into a themed icon element. */\nfunction fileIconForKind(kind: FileIconKind, px: number): React.ReactElement {\n const color =\n kind === 'folder' || kind === 'folder-open' ? 'secondary' : 'muted';\n switch (kind) {\n case 'folder':\n return <FolderIcon size={px} color={color} decorative />;\n case 'folder-open':\n return <FolderOpenIcon size={px} color={color} decorative />;\n case 'image':\n return <ImageIcon size={px} color={color} decorative />;\n case 'media':\n return <PlayIcon size={px} color={color} decorative />;\n case 'code':\n return <CodeIcon size={px} color={color} decorative />;\n case 'archive':\n return <ArchiveIcon size={px} color={color} decorative />;\n case 'text':\n case 'file':\n return <FileTextIcon size={px} color={color} decorative />;\n }\n}\n\n/** Maps a FileTreeNode subtree to TreeView's node shape. */\nfunction toTreeNode(node: FileTreeNode): TreeNodeData {\n return {\n id: node.id,\n label: node.name,\n children: node.children?.map(toTreeNode),\n disabled: node.disabled,\n // Folders accept dropped files; files do not (the field finally gets used).\n droppable: node.kind === 'folder',\n data: node.data,\n };\n}\n\n/**\n * A file-system-flavored specialization of {@link TreeView}: it maps a\n * file/folder model onto the tree, auto-assigns file-type icons (by extension)\n * and open/closed folder glyphs, and adds drag-and-drop import of OS files onto\n * a folder (or the root). Expansion, selection, keyboard navigation, and sizing\n * are inherited from TreeView unchanged.\n *\n * @example\n * ```tsx\n * const nodes: FileTreeNode[] = [\n * { id: 'src', name: 'src', kind: 'folder', children: [\n * { id: 'btn', name: 'Button.tsx', kind: 'file' },\n * { id: 'logo', name: 'logo.svg', kind: 'file' },\n * ]},\n * ];\n *\n * <FileTree\n * nodes={nodes}\n * defaultExpandedIds={['src']}\n * onImport={({ files, targetFolder }) =>\n * uploadInto(targetFolder?.id ?? 'root', files)\n * }\n * />\n * ```\n */\nexport const FileTree = ({\n nodes,\n expandedIds,\n defaultExpandedIds,\n onExpandedChange,\n selectedIds,\n defaultSelectedIds,\n selectionMode = 'single',\n onSelectionChange,\n size = 'md',\n indent,\n showChevrons,\n showGuideLines,\n expandOnClick = true,\n maxHeight,\n resolveIcon,\n renderNode,\n renderActions,\n emptyContent,\n onImport,\n onNodeClick,\n onNodeDoubleClick,\n onNodeContextMenu,\n labels: labelsProp,\n className,\n style,\n testId,\n ref,\n 'aria-label': ariaLabel,\n 'aria-labelledby': ariaLabelledby,\n ...rest\n}: FileTreeProps) => {\n const containerRef = useRef<HTMLDivElement>(null);\n const setContainerRef = useMergedRef(containerRef, ref);\n\n const treeNodes = useMemo(() => nodes.map(toTreeNode), [nodes]);\n\n // id → node and id → parent folder, used to resolve drop targets and to\n // recover the original FileTreeNode inside render / event callbacks.\n const { nodeById, parentFolderById } = useMemo(() => {\n const byId = new Map<string, FileTreeNode>();\n const parentById = new Map<string, FileTreeNode | null>();\n const walk = (list: FileTreeNode[], parent: FileTreeNode | null): void => {\n for (const node of list) {\n byId.set(node.id, node);\n parentById.set(node.id, parent);\n if (node.children) {\n walk(node.children, node.kind === 'folder' ? node : parent);\n }\n }\n };\n walk(nodes, null);\n return { nodeById: byId, parentFolderById: parentById };\n }, [nodes]);\n\n const dnd = useFileTreeDrop({ onImport, nodeById, parentFolderById });\n\n const labels = useMemo(\n () => ({ ...DEFAULT_FILE_TREE_LABELS, ...labelsProp }),\n [labelsProp]\n );\n\n const iconPx = ICON_PX[size];\n\n const toFileState = useCallback(\n (state: TreeNodeState, node: FileTreeNode): FileTreeNodeState => ({\n ...state,\n isFolder: node.kind === 'folder',\n }),\n []\n );\n\n const renderTreeNode = useCallback(\n (treeNode: TreeNodeData, state: TreeNodeState): React.ReactNode => {\n const node = nodeById.get(treeNode.id);\n if (!node) return null;\n if (renderNode) return renderNode(node, toFileState(state, node));\n\n const overrideIcon = resolveIcon?.(node, { expanded: state.expanded });\n const icon =\n overrideIcon ??\n fileIconForKind(getFileIconKind(node, state.expanded), iconPx);\n\n return (\n <>\n <span className={iconStyle({ size })}>{icon}</span>\n <span className={labelStyle({ size })}>{node.name}</span>\n </>\n );\n },\n [nodeById, renderNode, resolveIcon, toFileState, iconPx, size]\n );\n\n const renderTreeActions = useMemo(() => {\n if (!renderActions) return undefined;\n return (treeNode: TreeNodeData, state: TreeNodeState): React.ReactNode => {\n const node = nodeById.get(treeNode.id);\n if (!node) return null;\n return renderActions(node, toFileState(state, node));\n };\n }, [renderActions, nodeById, toFileState]);\n\n const handleNodeClick = useMemo(() => {\n if (!onNodeClick) return undefined;\n return (treeNode: TreeNodeData, event: React.MouseEvent): void => {\n const node = nodeById.get(treeNode.id);\n if (node) onNodeClick(node, event);\n };\n }, [onNodeClick, nodeById]);\n\n const handleNodeDoubleClick = useMemo(() => {\n if (!onNodeDoubleClick) return undefined;\n return (treeNode: TreeNodeData, event: React.MouseEvent): void => {\n const node = nodeById.get(treeNode.id);\n if (node) onNodeDoubleClick(node, event);\n };\n }, [onNodeDoubleClick, nodeById]);\n\n const handleNodeContextMenu = useMemo(() => {\n if (!onNodeContextMenu) return undefined;\n return (treeNode: TreeNodeData, event: React.MouseEvent): void => {\n const node = nodeById.get(treeNode.id);\n if (node) onNodeContextMenu(node, event);\n };\n }, [onNodeContextMenu, nodeById]);\n\n return (\n <div\n ref={setContainerRef}\n className={cx(containerStyle, className)}\n style={style}\n data-testid={testId}\n data-root-active={dnd.enabled && dnd.rootActive ? 'true' : undefined}\n {...rest}\n onDragOver={dnd.enabled ? dnd.onRootDragOver : undefined}\n onDragLeave={dnd.enabled ? dnd.onRootDragLeave : undefined}\n onDrop={dnd.enabled ? dnd.onRootDrop : undefined}\n >\n <TreeView\n nodes={treeNodes}\n expandedIds={expandedIds}\n defaultExpandedIds={defaultExpandedIds}\n onExpandedChange={onExpandedChange}\n selectedIds={selectedIds}\n defaultSelectedIds={defaultSelectedIds}\n selectionMode={selectionMode}\n onSelectionChange={onSelectionChange}\n size={size}\n indent={indent}\n showChevrons={showChevrons}\n showGuideLines={showGuideLines}\n expandOnClick={expandOnClick}\n maxHeight={maxHeight}\n renderNode={renderTreeNode}\n renderActions={renderTreeActions}\n emptyContent={emptyContent ?? labels.emptyLabel}\n aria-label={ariaLabel ?? labels.treeLabel}\n aria-labelledby={ariaLabelledby}\n dropTargetId={dnd.enabled ? dnd.dropTargetId : undefined}\n onNodeClick={handleNodeClick}\n onNodeDoubleClick={handleNodeDoubleClick}\n onNodeContextMenu={handleNodeContextMenu}\n onNodeDragOver={dnd.enabled ? dnd.onNodeDragOver : undefined}\n onNodeDrop={dnd.enabled ? dnd.onNodeDrop : undefined}\n />\n </div>\n );\n};\n\nFileTree.displayName = 'FileTree';\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkCA;AAEA;AACA;AACE;;AAGE;AACE;AACF;AACE;AACF;AACE;AACF;AACE;AACF;AACE;AACF;AACE;AACF;AACA;AACE;;AAEN;AAEA;AACA;;;;;;;AAOI;;;AAGJ;AAEA;;;;;;;;;;;;;;;;;;;;;;;;AAwBG;AACI;AAgCL;;AAGA;;;;AAKE;AACA;AACA;AACE;;;AAGE;AACE;;;AAGN;AACA;;AAEF;AAEA;;AAOA;;AAII;AACA;;;;AAQA;AAAW;AACX;;AAEA;;AAGE;AAEF;AAMF;AAIF;AACE;AAAoB;AACpB;;AAEE;AAAW;;AAEb;;AAGF;AACE;AAAkB;AAClB;;AAEE;AAAU;AACZ;AACF;AAEA;AACE;AAAwB;AACxB;;AAEE;AAAU;AACZ;AACF;AAEA;AACE;AAAwB;AACxB;;AAEE;AAAU;AACZ;AACF;;AA2CF;AAEA;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fileTreeIcons.js","sources":["
|
|
1
|
+
{"version":3,"file":"fileTreeIcons.js","sources":["src/components/controls/FileTree/fileTreeIcons.ts"],"sourcesContent":["import type { FileTreeNode } from './FileTree.types';\n\n/**\n * The icon categories FileTree resolves a node to. Folders get an open/closed\n * pair; files collapse to a small set of type buckets backed by the existing\n * icon set. Rendering a kind into an actual icon element lives in the client\n * component — this module is pure classification so it can be unit-tested\n * without React.\n */\nexport type FileIconKind =\n | 'folder'\n | 'folder-open'\n | 'image'\n | 'media'\n | 'code'\n | 'archive'\n | 'text'\n | 'file';\n\n/**\n * Maps a lowercased file extension (no dot) to an icon bucket. Unknown\n * extensions fall through to the generic `file` glyph.\n */\nconst KIND_BY_EXTENSION: Readonly<Record<string, FileIconKind>> = {\n // Images / raster + vector assets\n png: 'image',\n jpg: 'image',\n jpeg: 'image',\n gif: 'image',\n webp: 'image',\n avif: 'image',\n bmp: 'image',\n ico: 'image',\n svg: 'image',\n tif: 'image',\n tiff: 'image',\n psd: 'image',\n\n // Media (audio + video share the play glyph)\n mp3: 'media',\n wav: 'media',\n ogg: 'media',\n flac: 'media',\n aac: 'media',\n m4a: 'media',\n mp4: 'media',\n mov: 'media',\n avi: 'media',\n mkv: 'media',\n webm: 'media',\n\n // Code / markup / config / data\n js: 'code',\n jsx: 'code',\n ts: 'code',\n tsx: 'code',\n mjs: 'code',\n cjs: 'code',\n json: 'code',\n json5: 'code',\n html: 'code',\n htm: 'code',\n css: 'code',\n scss: 'code',\n sass: 'code',\n less: 'code',\n vue: 'code',\n svelte: 'code',\n astro: 'code',\n py: 'code',\n rb: 'code',\n go: 'code',\n rs: 'code',\n java: 'code',\n kt: 'code',\n swift: 'code',\n c: 'code',\n h: 'code',\n cpp: 'code',\n cc: 'code',\n hpp: 'code',\n cs: 'code',\n php: 'code',\n sh: 'code',\n bash: 'code',\n zsh: 'code',\n yml: 'code',\n yaml: 'code',\n toml: 'code',\n xml: 'code',\n sql: 'code',\n graphql: 'code',\n gql: 'code',\n lua: 'code',\n dart: 'code',\n r: 'code',\n\n // Archives\n zip: 'archive',\n rar: 'archive',\n '7z': 'archive',\n tar: 'archive',\n gz: 'archive',\n tgz: 'archive',\n bz2: 'archive',\n xz: 'archive',\n\n // Text / documents\n txt: 'text',\n md: 'text',\n mdx: 'text',\n rtf: 'text',\n pdf: 'text',\n doc: 'text',\n docx: 'text',\n csv: 'text',\n tsv: 'text',\n log: 'text',\n};\n\n/**\n * Extracts the lowercased extension (without the dot) from a file name, or\n * `undefined` when there isn't one. Dotfiles such as `.env` / `.gitignore` are\n * treated as having no extension (the leading dot is not a separator).\n */\nexport function getFileExtension(name: string): string | undefined {\n const dot = name.lastIndexOf('.');\n if (dot <= 0 || dot === name.length - 1) return undefined;\n return name.slice(dot + 1).toLowerCase();\n}\n\n/**\n * Classifies an extension into an icon bucket. `undefined` / unknown → `file`.\n */\nexport function classifyExtension(ext: string | undefined): FileIconKind {\n if (ext === undefined) return 'file';\n return KIND_BY_EXTENSION[ext.toLowerCase()] ?? 'file';\n}\n\n/**\n * Resolves the icon kind for a node. Folders return `folder-open` when\n * `expanded`, else `folder`. Files use the explicit `ext` when provided, falling\n * back to the extension parsed from `name`.\n */\nexport function getFileIconKind(\n node: Pick<FileTreeNode, 'kind' | 'name' | 'ext'>,\n expanded = false\n): FileIconKind {\n if (node.kind === 'folder') return expanded ? 'folder-open' : 'folder';\n const ext = node.ext ?? getFileExtension(node.name);\n return classifyExtension(ext);\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fileTreeLabels.js","sources":["
|
|
1
|
+
{"version":3,"file":"fileTreeLabels.js","sources":["src/components/controls/FileTree/fileTreeLabels.ts"],"sourcesContent":["/**\n * Localizable strings for {@link FileTree}. Every user-facing / screen-reader\n * string the component renders on its own goes through here so the whole\n * component can be localized in one place (file and folder names come from your\n * data, so they are already yours to localize).\n *\n * @see DEFAULT_FILE_TREE_LABELS for the English defaults.\n */\nexport interface FileTreeLabels {\n /** Accessible name for the tree region (`role=\"tree\"` `aria-label`). */\n treeLabel: string;\n /** Text shown when the tree is empty (unless `emptyContent` is provided). */\n emptyLabel: string;\n}\n\n/** English defaults for {@link FileTreeLabels}. Spread-and-tweak to localize. */\nexport const DEFAULT_FILE_TREE_LABELS: FileTreeLabels = {\n treeLabel: 'File tree',\n emptyLabel: 'No files',\n};\n"],"names":[],"mappings":"AAeA;AACO,MAAM,wBAAwB,GAAmB;AACtD,IAAA,SAAS,EAAE,WAAW;AACtB,IAAA,UAAU,EAAE,UAAU;;;;;"}
|
|
@@ -4,7 +4,7 @@ import '../../../utils/devWarn.js';
|
|
|
4
4
|
import { useLatest } from '../../../hooks/useLatest/useLatest.js';
|
|
5
5
|
import '../../../hooks/useBreakpoint/useBreakpoint.js';
|
|
6
6
|
import './../../../assets/src/theme/lightTheme.css.ts.vanilla-Ct6f9Iym.css';
|
|
7
|
-
import '
|
|
7
|
+
import '@vanilla-extract/css';
|
|
8
8
|
import './../../../assets/src/theme/darkTheme.css.ts.vanilla-BHZ3ecRn.css';
|
|
9
9
|
import 'react/jsx-runtime';
|
|
10
10
|
import './../../../assets/src/theme/globalScrollbars.css.ts.vanilla-BAJwnUEJ.css';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useFileTreeDrop.js","sources":["
|
|
1
|
+
{"version":3,"file":"useFileTreeDrop.js","sources":["src/components/controls/FileTree/useFileTreeDrop.ts"],"sourcesContent":["'use client';\n\nimport { useCallback, useState } from 'react';\n\nimport { useLatest } from '@/hooks';\n\nimport type { FileTreeImportPayload, FileTreeNode } from './FileTree.types';\nimport type { TreeNodeData } from '@/components/controls/TreeView';\nimport type { DragEvent } from 'react';\n\nexport interface UseFileTreeDropOptions {\n /** Import callback. Drop wiring is inert while this is `undefined`. */\n onImport?: (payload: FileTreeImportPayload) => void;\n /** Node lookup by id (the original `FileTreeNode`, recovered from TreeView). */\n nodeById: ReadonlyMap<string, FileTreeNode>;\n /** Parent folder lookup by node id. `null` means the node lives at the root. */\n parentFolderById: ReadonlyMap<string, FileTreeNode | null>;\n}\n\nexport interface UseFileTreeDropReturn {\n /** Whether import drop wiring is active (`onImport` provided). */\n enabled: boolean;\n /** Folder id to highlight as the drop target (drives TreeView `dropTargetId`). */\n dropTargetId: string | null;\n /** Whether the tree root (empty area) is the active drop target. */\n rootActive: boolean;\n /** Fired by TreeView on `dragover` over a node row. */\n onNodeDragOver: (node: TreeNodeData, event: DragEvent) => void;\n /** Fired by TreeView on `drop` onto a node row. */\n onNodeDrop: (node: TreeNodeData, event: DragEvent) => void;\n /** Container-level handlers for root / empty-area drops. */\n onRootDragOver: (event: DragEvent) => void;\n onRootDragLeave: (event: DragEvent) => void;\n onRootDrop: (event: DragEvent) => void;\n}\n\n/** True when the drag carries OS files (vs. a text/element drag). */\nfunction hasFiles(event: DragEvent): boolean {\n return Array.from(event.dataTransfer.types).includes('Files');\n}\n\n/** True when the drag is leaving the element entirely (not into a descendant). */\nfunction leftElement(event: DragEvent): boolean {\n const related = event.relatedTarget as Node | null;\n return !(related && event.currentTarget.contains(related));\n}\n\nfunction extractFiles(event: DragEvent): File[] {\n return Array.from(event.dataTransfer.files);\n}\n\n/**\n * External file-import drag-and-drop for FileTree. Per-node `dragover` resolves\n * (and highlights) the destination folder and stops propagation; the container\n * handles drops on empty space (→ root) and clears the highlight when the drag\n * leaves the tree, so there's no row-to-row flicker. All handlers have stable\n * identities and read the latest props through `useLatest` (patterns #3 / #11).\n */\nexport function useFileTreeDrop({\n onImport,\n nodeById,\n parentFolderById,\n}: UseFileTreeDropOptions): UseFileTreeDropReturn {\n // `null` = no active target; otherwise the destination folder id (`null`\n // folderId = the tree root).\n const [target, setTarget] = useState<{ folderId: string | null } | null>(\n null\n );\n\n const onImportRef = useLatest(onImport);\n const nodeByIdRef = useLatest(nodeById);\n const parentFolderByIdRef = useLatest(parentFolderById);\n\n const enabled = onImport !== undefined;\n\n /** The folder a drop on `node` should target (a folder targets itself; a file\n * targets its parent folder, or the root). */\n const resolveTargetFolder = useCallback(\n (nodeId: string): FileTreeNode | null => {\n const node = nodeByIdRef.current.get(nodeId);\n if (node?.kind === 'folder') return node;\n return parentFolderByIdRef.current.get(nodeId) ?? null;\n },\n [nodeByIdRef, parentFolderByIdRef]\n );\n\n const setTargetFolder = useCallback((folderId: string | null) => {\n setTarget(prev =>\n prev !== null && prev.folderId === folderId ? prev : { folderId }\n );\n }, []);\n\n const clearTarget = useCallback(() => {\n setTarget(prev => (prev === null ? prev : null));\n }, []);\n\n const onNodeDragOver = useCallback(\n (node: TreeNodeData, event: DragEvent): void => {\n if (onImportRef.current === undefined || !hasFiles(event)) return;\n event.preventDefault();\n // Owned by the row — keep the container from re-targeting the root.\n event.stopPropagation();\n event.dataTransfer.dropEffect = 'copy';\n setTargetFolder(resolveTargetFolder(node.id)?.id ?? null);\n },\n [onImportRef, resolveTargetFolder, setTargetFolder]\n );\n\n const onNodeDrop = useCallback(\n (node: TreeNodeData, event: DragEvent): void => {\n const onImportCb = onImportRef.current;\n if (onImportCb === undefined) return;\n const files = extractFiles(event);\n if (files.length === 0) {\n clearTarget();\n return;\n }\n event.preventDefault();\n event.stopPropagation();\n onImportCb({ files, targetFolder: resolveTargetFolder(node.id) });\n clearTarget();\n },\n [onImportRef, resolveTargetFolder, clearTarget]\n );\n\n const onRootDragOver = useCallback(\n (event: DragEvent): void => {\n if (onImportRef.current === undefined || !hasFiles(event)) return;\n event.preventDefault();\n event.dataTransfer.dropEffect = 'copy';\n setTargetFolder(null);\n },\n [onImportRef, setTargetFolder]\n );\n\n const onRootDragLeave = useCallback(\n (event: DragEvent): void => {\n if (!leftElement(event)) return;\n clearTarget();\n },\n [clearTarget]\n );\n\n const onRootDrop = useCallback(\n (event: DragEvent): void => {\n const onImportCb = onImportRef.current;\n if (onImportCb === undefined) return;\n const files = extractFiles(event);\n if (files.length === 0) {\n clearTarget();\n return;\n }\n event.preventDefault();\n onImportCb({ files, targetFolder: null });\n clearTarget();\n },\n [onImportRef, clearTarget]\n );\n\n return {\n enabled,\n dropTargetId: target?.folderId ?? null,\n rootActive: target !== null && target.folderId === null,\n onNodeDragOver,\n onNodeDrop,\n onRootDragOver,\n onRootDragLeave,\n onRootDrop,\n };\n}\n"],"names":[],"mappings":";;;;;;;;;;;;AAoCA;AACA;AACE;AACF;AAEA;AACA;AACE;AACA;AACF;AAEA;;AAEA;AAEA;;;;;;AAMG;AACG;;;;AAWJ;AACA;AACA;AAEA;AAEA;AAC8C;AAC9C;;AAGI;AAA6B;;AAE/B;AAIF;;;AAMA;AACE;;;;;;;;AASE;AACA;;;AAOA;;;AAEA;AACA;AACE;;;;;AAKF;AACA;;AAKJ;;;;AAII;;AAEF;AAIF;AAEI;;AACA;AACF;AAIF;AAEI;;;AAEA;AACA;AACE;;;;;AAKF;AACF;;;AAMA;;;;;;;;AAQJ;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FileUploader.js","sources":["../../../../../../src/components/controls/FileUploader/FileUploader.tsx"],"sourcesContent":[null],"names":[],"mappings":";;;;;;;;;;;;AAmCA;AACA;;AAEE;AACF;AAEA;AAEA;;AAC4C;;AACzB;AACjB;AAIA;;;AAIF;AAEA;;;AAGK;;AAEH;AAAyB;;;AAKzB;AACE;AACE;AAA0B;;AACrB;;AAEL;AAAmC;;AAC9B;AACL;;;AAGJ;AACF;AAEA;;;;;;;;;;;;;;;;;;;;;;;;AAwBG;;AAgCD;AACA;AACA;AACA;AAEA;AACA;AACA;;AAGA;;;;AAIE;AACD;;AAGD;;;AAKM;;;AAGA;;;AAGA;;;AAGA;;;AAGA;;AACsB;;AACU;;AAElC;AACF;AAIF;AAEI;;;;AAGA;AAEA;AAEA;;;AAGI;;;AAGF;;;;AAIE;AACD;;;AAIH;;;AAGA;;AAEA;AACF;AAIF;;;AAGI;;;AAGA;AAAa;;AAKjB;AAEI;AACA;;;;AAGA;AACF;AAIF;AACE;;AACA;AACF;AAEA;AAEI;;AACA;;AAEE;;AAEJ;AAIF;AAEI;;;AAEA;AACA;;AACF;AAIF;AAEI;;;AAEA;AACF;AAIF;AAEI;;;AAEA;AACA;;AACF;AAIF;AAEI;;;AAEA;;AAEA;;;AAGE;;;AAEA;AACE;AACE;AACA;AAAO;;;;;AAKf;AAIF;AAEA;;AAEI;;;;;;;AASK;;;AAMP;AAEA;AAiBQ;;AAgBF;AAMR;AAEA;;AACkC;;AAEhC;AAAY;;;;;AAKZ;;;;;;;;AAgFJ;AAEA;;"}
|
|
1
|
+
{"version":3,"file":"FileUploader.js","sources":["src/components/controls/FileUploader/FileUploader.tsx"],"sourcesContent":["'use client';\n\nimport React, { useCallback, useId, useMemo, useRef, useState } from 'react';\n\nimport { FormHelperText } from '@/components/form/FormHelperText';\nimport { FormLabel } from '@/components/form/FormLabel';\nimport { CloudUploadIcon } from '@/components/Icons/CloudUploadIcon';\nimport { TrashIcon } from '@/components/Icons/TrashIcon';\nimport { useControlledState } from '@/hooks/useControlledState';\nimport { useMergedRef } from '@/hooks/useMergedRef';\nimport { cx } from '@/utils/cx';\n\nimport {\n containerStyle,\n dropZoneHintStyle,\n dropZonePrimaryStyle,\n dropZoneRecipe,\n fileListStyle,\n fileMetaStyle,\n fileNameStyle,\n fileRowRecipe,\n fileSubStyle,\n hiddenInputStyle,\n progressBarStyle,\n progressFillRecipe,\n removeButtonStyle,\n statusBadgeRecipe,\n} from './FileUploader.css';\n\nimport type {\n FileUploaderItem,\n FileUploaderProps,\n FileUploaderRejectReason,\n} from './FileUploader.types';\n\nlet __fileUploaderIdCounter = 0;\nfunction nextFileId(): string {\n __fileUploaderIdCounter += 1;\n return `eui-file-${String(Date.now())}-${String(__fileUploaderIdCounter)}`;\n}\n\nconst SIZE_UNITS = ['B', 'KB', 'MB', 'GB', 'TB'];\n\nfunction formatBytes(bytes: number): string {\n if (!Number.isFinite(bytes) || bytes < 0) return '';\n if (bytes === 0) return '0 B';\n const exponent = Math.min(\n SIZE_UNITS.length - 1,\n Math.floor(Math.log(bytes) / Math.log(1024))\n );\n const value = bytes / Math.pow(1024, exponent);\n const formatted =\n value >= 100 || exponent === 0 ? value.toFixed(0) : value.toFixed(1);\n return `${formatted} ${SIZE_UNITS[exponent] ?? ''}`;\n}\n\nfunction fileMatchesAccept(file: File, accept: string): boolean {\n const tokens = accept\n .split(',')\n .map(t => t.trim().toLowerCase())\n .filter(Boolean);\n if (tokens.length === 0) return true;\n\n const name = file.name.toLowerCase();\n const type = file.type.toLowerCase();\n\n for (const token of tokens) {\n if (token.startsWith('.')) {\n if (name.endsWith(token)) return true;\n } else if (token.endsWith('/*')) {\n const prefix = token.slice(0, -2);\n if (type.startsWith(`${prefix}/`)) return true;\n } else if (token === type) {\n return true;\n }\n }\n return false;\n}\n\n/**\n * Drag-and-drop file uploader with click-to-browse fallback. Renders a list\n * of uploaded items with per-file status (`pending` / `uploading` / `done` /\n * `error`) and an animated progress bar.\n *\n * The component itself is purely presentational around the file list — it\n * does not perform the actual upload. The consumer is expected to:\n * 1. Listen to `onFilesAdd` (or `onChange`) to know which files were\n * accepted by the uploader.\n * 2. Drive the upload (fetch / XHR / SDK) externally.\n * 3. Reflect progress back into the component by passing a controlled\n * `value` and updating the `status` / `progress` of each item.\n *\n * @example\n * ```tsx\n * const [items, setItems] = useState<FileUploaderItem[]>([]);\n * <FileUploader\n * value={items}\n * onChange={setItems}\n * accept=\"image/*,.pdf\"\n * maxSize={5 * 1024 * 1024}\n * onFilesAdd={files => uploadAll(files, setItems)}\n * />\n * ```\n */\nexport function FileUploader({\n value,\n defaultValue,\n onChange,\n onFilesAdd,\n onFileRemove,\n onReject,\n accept,\n multiple = true,\n maxFiles,\n maxSize,\n minSize,\n validate,\n renderItem,\n size = 'md',\n label,\n helperText,\n error = false,\n errorMessage,\n disabled = false,\n required = false,\n dropZoneText = 'Drag files here or click to browse',\n dropZoneHint,\n name,\n className,\n style,\n testId,\n id: idProp,\n ref,\n ...rest\n}: FileUploaderProps) {\n const autoId = useId();\n const fieldId = idProp ?? autoId;\n const labelId = `${fieldId}-label`;\n const helperId = `${fieldId}-helper`;\n\n const inputRef = useRef<HTMLInputElement | null>(null);\n const dropZoneRef = useRef<HTMLDivElement | null>(null);\n const containerRef = useRef<HTMLDivElement | null>(null);\n const setContainerRef = useMergedRef(containerRef, ref);\n\n const [items, setItems] = useControlledState<FileUploaderItem[]>({\n value,\n defaultValue,\n onChange,\n fallback: [],\n });\n\n const [dragging, setDragging] = useState(false);\n const dragCounterRef = useRef(0);\n\n const validateFile = useCallback(\n (file: File, currentCount: number): FileUploaderRejectReason | null => {\n if (typeof maxFiles === 'number' && currentCount >= maxFiles) {\n return 'max-files';\n }\n if (accept && !fileMatchesAccept(file, accept)) {\n return 'wrong-type';\n }\n if (typeof maxSize === 'number' && file.size > maxSize) {\n return 'too-large';\n }\n if (typeof minSize === 'number' && file.size < minSize) {\n return 'too-small';\n }\n if (validate) {\n const result = validate(file);\n if (result === false) return 'invalid';\n if (typeof result === 'string') return result;\n }\n return null;\n },\n [accept, maxFiles, maxSize, minSize, validate]\n );\n\n const acceptFiles = useCallback(\n (incoming: File[]) => {\n if (incoming.length === 0) return;\n const accepted: File[] = [];\n const newItems: FileUploaderItem[] = [];\n let count = items.length;\n\n const list = multiple ? incoming : incoming.slice(0, 1);\n\n for (const file of list) {\n const rejection = validateFile(file, count);\n if (rejection) {\n onReject?.(file, rejection);\n continue;\n }\n accepted.push(file);\n newItems.push({\n id: nextFileId(),\n file,\n status: 'pending',\n });\n count += 1;\n }\n\n if (newItems.length === 0) return;\n\n // In single mode, replace any existing item.\n const next = multiple ? [...items, ...newItems] : newItems;\n setItems(next);\n onFilesAdd?.(accepted);\n },\n [items, multiple, onFilesAdd, onReject, setItems, validateFile]\n );\n\n const removeAt = useCallback(\n (index: number) => {\n if (index < 0 || index >= items.length) return;\n const removed = items[index];\n const next = items.slice(0, index).concat(items.slice(index + 1));\n setItems(next);\n if (removed) onFileRemove?.(removed);\n },\n [items, onFileRemove, setItems]\n );\n\n const handleInputChange = useCallback(\n (event: React.ChangeEvent<HTMLInputElement>) => {\n const list = event.target.files;\n if (!list) return;\n acceptFiles(Array.from(list));\n // Reset so selecting the same file again still fires onChange.\n event.target.value = '';\n },\n [acceptFiles]\n );\n\n const openPicker = useCallback(() => {\n if (disabled) return;\n inputRef.current?.click();\n }, [disabled]);\n\n const handleZoneKeyDown = useCallback(\n (event: React.KeyboardEvent<HTMLDivElement>) => {\n if (disabled) return;\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault();\n openPicker();\n }\n },\n [disabled, openPicker]\n );\n\n const handleDragEnter = useCallback(\n (event: React.DragEvent<HTMLDivElement>) => {\n if (disabled) return;\n event.preventDefault();\n dragCounterRef.current += 1;\n if (dragCounterRef.current === 1) setDragging(true);\n },\n [disabled]\n );\n\n const handleDragOver = useCallback(\n (event: React.DragEvent<HTMLDivElement>) => {\n if (disabled) return;\n event.preventDefault();\n event.dataTransfer.dropEffect = 'copy';\n },\n [disabled]\n );\n\n const handleDragLeave = useCallback(\n (event: React.DragEvent<HTMLDivElement>) => {\n if (disabled) return;\n event.preventDefault();\n dragCounterRef.current = Math.max(0, dragCounterRef.current - 1);\n if (dragCounterRef.current === 0) setDragging(false);\n },\n [disabled]\n );\n\n const handleDrop = useCallback(\n (event: React.DragEvent<HTMLDivElement>) => {\n if (disabled) return;\n event.preventDefault();\n dragCounterRef.current = 0;\n setDragging(false);\n const dt = event.dataTransfer;\n const dropped: File[] = [];\n if (dt.files.length > 0) {\n dropped.push(...Array.from(dt.files));\n } else if (dt.items.length > 0) {\n for (const item of Array.from(dt.items)) {\n if (item.kind === 'file') {\n const f = item.getAsFile();\n if (f) dropped.push(f);\n }\n }\n }\n acceptFiles(dropped);\n },\n [acceptFiles, disabled]\n );\n\n const showHelperText = error && errorMessage ? errorMessage : helperText;\n\n const renderRow = (item: FileUploaderItem, index: number) => {\n if (renderItem) {\n return (\n <React.Fragment key={item.id}>\n {renderItem({\n item,\n index,\n disabled,\n remove: () => {\n removeAt(index);\n },\n })}\n </React.Fragment>\n );\n }\n\n const progress = Math.max(0, Math.min(100, item.progress ?? 0));\n const sub = item.errorMessage ?? formatBytes(item.file.size);\n\n return (\n <div\n key={item.id}\n className={fileRowRecipe({ status: item.status })}\n data-testid={testId ? `${testId}-item-${item.id}` : undefined}\n >\n <div className={fileMetaStyle}>\n <span className={fileNameStyle} title={item.file.name}>\n {item.file.name}\n </span>\n <span className={fileSubStyle}>\n <span className={statusBadgeRecipe({ status: item.status })}>\n {item.status}\n </span>\n <span>{sub}</span>\n </span>\n {(item.status === 'uploading' ||\n (item.status === 'pending' && progress > 0)) && (\n <div className={progressBarStyle} aria-hidden=\"true\">\n <div\n className={progressFillRecipe({ status: item.status })}\n style={{ width: `${String(progress)}%` }}\n />\n </div>\n )}\n </div>\n <button\n type=\"button\"\n className={removeButtonStyle}\n aria-label={`Remove ${item.file.name}`}\n disabled={disabled}\n onClick={() => {\n removeAt(index);\n }}\n >\n <TrashIcon size=\"sm\" decorative />\n </button>\n </div>\n );\n };\n\n const acceptHint = useMemo(() => {\n if (dropZoneHint !== undefined) return dropZoneHint;\n const parts: string[] = [];\n if (accept) parts.push(accept);\n if (typeof maxSize === 'number')\n parts.push(`up to ${formatBytes(maxSize)}`);\n if (typeof maxFiles === 'number')\n parts.push(`max ${String(maxFiles)} files`);\n return parts.length > 0 ? parts.join(' • ') : null;\n }, [accept, dropZoneHint, maxFiles, maxSize]);\n\n return (\n <div\n ref={setContainerRef}\n className={cx(containerStyle, className)}\n style={style}\n data-testid={testId}\n {...rest}\n >\n {label && (\n <FormLabel\n id={labelId}\n htmlFor={fieldId}\n required={required}\n disabled={disabled}\n >\n {label}\n </FormLabel>\n )}\n\n <div\n ref={dropZoneRef}\n role=\"button\"\n tabIndex={disabled ? -1 : 0}\n aria-disabled={disabled || undefined}\n aria-labelledby={label ? labelId : undefined}\n aria-describedby={showHelperText ? helperId : undefined}\n className={dropZoneRecipe({\n size,\n dragging,\n disabled,\n error,\n })}\n onClick={openPicker}\n onKeyDown={handleZoneKeyDown}\n onDragEnter={handleDragEnter}\n onDragOver={handleDragOver}\n onDragLeave={handleDragLeave}\n onDrop={handleDrop}\n data-testid={testId ? `${testId}-dropzone` : undefined}\n >\n <CloudUploadIcon\n size={size === 'lg' ? 32 : size === 'sm' ? 18 : 24}\n decorative\n />\n <span className={dropZonePrimaryStyle}>{dropZoneText}</span>\n {acceptHint && <span className={dropZoneHintStyle}>{acceptHint}</span>}\n\n <input\n ref={inputRef}\n id={fieldId}\n type=\"file\"\n name={name}\n accept={accept}\n multiple={multiple}\n required={required}\n disabled={disabled}\n onChange={handleInputChange}\n className={hiddenInputStyle}\n aria-hidden=\"true\"\n tabIndex={-1}\n data-testid={testId ? `${testId}-input` : undefined}\n />\n </div>\n\n {items.length > 0 && (\n <div className={fileListStyle}>\n {items.map((item, idx) => renderRow(item, idx))}\n </div>\n )}\n\n {showHelperText && (\n <FormHelperText id={helperId} error={error}>\n {showHelperText}\n </FormHelperText>\n )}\n </div>\n );\n}\n\nFileUploader.displayName = 'FileUploader';\n"],"names":[],"mappings":";;;;;;;;;;;;AAmCA;AACA;;AAEE;AACF;AAEA;AAEA;;AAC4C;;AACzB;AACjB;AAIA;;;AAIF;AAEA;;;AAGK;;AAEH;AAAyB;;;AAKzB;AACE;AACE;AAA0B;;AACrB;;AAEL;AAAmC;;AAC9B;AACL;;;AAGJ;AACF;AAEA;;;;;;;;;;;;;;;;;;;;;;;;AAwBG;;AAgCD;AACA;AACA;AACA;AAEA;AACA;AACA;;AAGA;;;;AAIE;AACD;;AAGD;;;AAKM;;;AAGA;;;AAGA;;;AAGA;;;AAGA;;AACsB;;AACU;;AAElC;AACF;AAIF;AAEI;;;;AAGA;AAEA;AAEA;;;AAGI;;;AAGF;;;;AAIE;AACD;;;AAIH;;;AAGA;;AAEA;AACF;AAIF;;;AAGI;;;AAGA;AAAa;;AAKjB;AAEI;AACA;;;;AAGA;AACF;AAIF;AACE;;AACA;AACF;AAEA;AAEI;;AACA;;AAEE;;AAEJ;AAIF;AAEI;;;AAEA;AACA;;AACF;AAIF;AAEI;;;AAEA;AACF;AAIF;AAEI;;;AAEA;AACA;;AACF;AAIF;AAEI;;;AAEA;;AAEA;;;AAGE;;;AAEA;AACE;AACE;AACA;AAAO;;;;;AAKf;AAIF;AAEA;;AAEI;;;;;;;AASK;;;AAMP;AAEA;AAiBQ;;AAgBF;AAMR;AAEA;;AACkC;;AAEhC;AAAY;;;;;AAKZ;;;;;;;;AAgFJ;AAEA;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MultiSelect.js","sources":["../../../../../../src/components/controls/MultiSelect/MultiSelect.tsx"],"sourcesContent":[null],"names":[],"mappings":";;;;;;;;;;;;;;;;AAmDA;AAGE;AACF;AAEA;;AAIE;AACE;;;;AAGE;;;AAGJ;AACF;AAEA;AAIE;;AAEF;AAEA;AACE;AACA;AACA;;AAGF;;;;;;;;;;;;;;;;;;;;;AAqBG;AACG;AAiCJ;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;;AAIA;;;;AAIE;AACD;;;;AAMD;;;;AAKA;AAEA;AACA;AACE;AAAyC;AACzC;;AAGF;;AAII;;;;AAGE;;;;;;;;AASN;AACE;;;;AAGA;AACF;AAEA;;;AAGE;AACA;AACF;;AAGE;;;AAGE;AACA;AAAmB;;AAErB;AACD;AAED;AACE;AACA;;AACA;;AAEA;AAGA;;AAGI;AAEJ;;AAEE;AACA;AACE;;AAEI;AACD;AACH;AACH;AACH;;AAGE;;AACA;;;AAGI;AACF;;;;;AAMF;;AACA;AACE;;;AAKE;;AAEJ;AACA;AACA;AACE;AACF;AACF;AAEA;AAEI;;AACA;AACE;AACA;AACA;AACA;;AAEE;AAAa;;;;AAOrB;AAEI;AACF;AAIF;AAEI;AACF;AAIF;;;AAIE;AAIF;AACA;AAEA;AAGO;AACA;AAIP;AACA;AAEI;;;;;AAMF;AACE;;;;;AAMF;;;AAGI;AACE;;AAEA;;;;;AAgBF;AACF;;AAGF;AACF;AAEA;;AAEE;AACA;AACA;AAEA;;;AAII;AACA;AAAmB;;AAEvB;AAEA;AASM;AACA;AACA;;AAIA;;AAEE;;AAEJ;AAEE;AAA0B;;AAalC;;;;AAgCU;;;AAEA;;;;;AAOF;;;AAGA;;;AA8Bc;AACF;AAEE;;;AAGE;;AAEJ;AAsBR;;;;;;AAmBN;AAmBU;AACF;AA0BhB;AAEA;;"}
|
|
1
|
+
{"version":3,"file":"MultiSelect.js","sources":["src/components/controls/MultiSelect/MultiSelect.tsx"],"sourcesContent":["'use client';\n\nimport React, {\n useCallback,\n useDeferredValue,\n useEffect,\n useId,\n useMemo,\n useRef,\n useState,\n} from 'react';\nimport { createPortal } from 'react-dom';\n\nimport { FormHelperText } from '@/components/form/FormHelperText';\nimport { FormLabel } from '@/components/form/FormLabel';\nimport { CheckIcon } from '@/components/Icons/CheckIcon';\nimport { ChevronDownIcon } from '@/components/Icons/ChevronDownIcon';\nimport { CloseIcon } from '@/components/Icons/CloseIcon';\nimport { ScrollArea } from '@/components/layout/ScrollArea';\nimport { useControlledState } from '@/hooks/useControlledState';\nimport { useListboxNav } from '@/hooks/useListboxNav';\nimport { useMergedRef } from '@/hooks/useMergedRef';\nimport { cx } from '@/utils/cx';\n\nimport {\n checkboxRecipe,\n chevronRecipe,\n chipLabelStyle,\n chipRecipe,\n chipRemoveStyle,\n clearButtonStyle,\n containerStyle,\n dropdownStyle,\n emptyMessageStyle,\n groupLabelStyle,\n moreBadgeStyle,\n optionItemRecipe,\n optionsListStyle,\n placeholderStyle,\n searchInputStyle,\n triggerContentStyle,\n triggerRecipe,\n} from './MultiSelect.css';\n\nimport type {\n MultiSelectOption,\n MultiSelectOptionGroup,\n MultiSelectProps,\n MultiSelectSize,\n} from './MultiSelect.types';\n\nfunction isOptionGroup<T extends string>(\n item: MultiSelectOption<T> | MultiSelectOptionGroup<T>\n): item is MultiSelectOptionGroup<T> {\n return 'options' in item && Array.isArray(item.options);\n}\n\nfunction flattenOptions<T extends string>(\n items: Array<MultiSelectOption<T> | MultiSelectOptionGroup<T>>\n): MultiSelectOption<T>[] {\n const result: MultiSelectOption<T>[] = [];\n for (const item of items) {\n if (isOptionGroup(item)) {\n result.push(...item.options);\n } else {\n result.push(item);\n }\n }\n return result;\n}\n\nfunction defaultFilter<T extends string>(\n option: MultiSelectOption<T>,\n query: string\n): boolean {\n const label = (option.label ?? option.value).toLowerCase();\n return label.includes(query.toLowerCase());\n}\n\nconst CHEVRON_SIZES: Record<MultiSelectSize, number> = {\n sm: 10,\n md: 12,\n lg: 14,\n};\n\n/**\n * Multi-value select that lets the user pick several options from a dropdown.\n * Selected values are rendered as inline chips inside the trigger; the rest\n * collapse into a \"+N more\" badge.\n *\n * Supports searchable mode, grouped options, controlled and uncontrolled\n * modes, keyboard navigation, an optional `max` cap, and a clear-all button.\n *\n * @example\n * ```tsx\n * <MultiSelect\n * label=\"Tags\"\n * options={[\n * { value: 'react', label: 'React' },\n * { value: 'vue', label: 'Vue' },\n * { value: 'svelte', label: 'Svelte' },\n * ]}\n * value={tags}\n * onChange={setTags}\n * />\n * ```\n */\nexport function MultiSelect<T extends string = string>({\n value,\n defaultValue,\n options,\n placeholder = 'Select...',\n searchable = false,\n searchPlaceholder = 'Search...',\n filterFn,\n emptyMessage = 'No results found',\n max,\n maxInlineChips = 3,\n size = 'md',\n variant = 'default',\n label,\n helperText,\n error = false,\n errorMessage,\n disabled = false,\n required = false,\n clearable = false,\n maxDropdownHeight = 240,\n minDropdownWidth,\n closeOnSelect = false,\n name,\n onChange,\n onOpenChange,\n className,\n style,\n testId,\n ref,\n id: idProp,\n ...rest\n}: MultiSelectProps<T>) {\n const autoId = useId();\n const fieldId = idProp ?? autoId;\n const labelId = `${fieldId}-label`;\n const helperId = `${fieldId}-helper`;\n const listboxId = `${fieldId}-listbox`;\n\n const triggerRef = useRef<HTMLButtonElement | null>(null);\n const dropdownRef = useRef<HTMLDivElement | null>(null);\n const searchRef = useRef<HTMLInputElement | null>(null);\n\n const setTriggerRef = useMergedRef(triggerRef, ref);\n\n const [selected, setSelected] = useControlledState<T[]>({\n value,\n defaultValue,\n onChange,\n fallback: [],\n });\n\n const [isOpen, setIsOpen] = useState(false);\n const [searchQuery, setSearchQuery] = useState('');\n const [dropdownPos, setDropdownPos] = useState<React.CSSProperties>({});\n\n const allOptions = useMemo(() => flattenOptions(options), [options]);\n\n // Defer the query that drives filtering so the search input stays\n // responsive while a large option list re-filters. The immediate\n // `searchQuery` still drives the input value.\n const deferredSearchQuery = useDeferredValue(searchQuery);\n\n const filter = filterFn ?? defaultFilter;\n const filteredOptions = useMemo(() => {\n if (!searchable || !deferredSearchQuery) return allOptions;\n return allOptions.filter(opt => filter(opt, deferredSearchQuery));\n }, [allOptions, searchable, deferredSearchQuery, filter]);\n\n const selectedSet = useMemo(() => new Set(selected), [selected]);\n\n const toggleValue = useCallback(\n (val: T, isDisabled: boolean) => {\n if (isDisabled) return;\n const isSelected = selectedSet.has(val);\n if (isSelected) {\n setSelected(selected.filter(v => v !== val));\n } else {\n if (typeof max === 'number' && selected.length >= max) return;\n setSelected([...selected, val]);\n }\n },\n [max, selected, selectedSet, setSelected]\n );\n\n const open = useCallback(() => {\n if (disabled) return;\n setIsOpen(true);\n setSearchQuery('');\n onOpenChange?.(true);\n }, [disabled, onOpenChange]);\n\n const close = useCallback(() => {\n setIsOpen(false);\n setSearchQuery('');\n onOpenChange?.(false);\n triggerRef.current?.focus();\n }, [onOpenChange]);\n\n const listbox = useListboxNav({\n items: filteredOptions,\n isItemDisabled: opt => Boolean(opt.disabled),\n onSelect: opt => {\n toggleValue(opt.value, Boolean(opt.disabled));\n if (closeOnSelect) close();\n },\n onEscape: close,\n });\n\n const updateDropdownPosition = useCallback(() => {\n const trigger = triggerRef.current;\n if (!trigger) return;\n const rect = trigger.getBoundingClientRect();\n const spaceBelow = window.innerHeight - rect.bottom;\n const openAbove =\n spaceBelow < maxDropdownHeight + 8 && rect.top > spaceBelow;\n\n const dropdownW =\n minDropdownWidth !== undefined\n ? Math.max(rect.width, minDropdownWidth)\n : rect.width;\n\n setDropdownPos({\n left: rect.left,\n width: dropdownW,\n ...(openAbove\n ? {\n bottom: window.innerHeight - rect.top + 4,\n transformOrigin: 'bottom',\n }\n : { top: rect.bottom + 4, transformOrigin: 'top' }),\n });\n }, [maxDropdownHeight, minDropdownWidth]);\n\n useEffect(() => {\n if (!isOpen) return;\n updateDropdownPosition();\n if (searchable) {\n requestAnimationFrame(() => {\n searchRef.current?.focus();\n });\n }\n }, [isOpen, updateDropdownPosition, searchable]);\n\n // Outside click handling\n useEffect(() => {\n if (!isOpen) return;\n const handleClick = (e: MouseEvent) => {\n const target = e.target as Node;\n if (\n !triggerRef.current?.contains(target) &&\n !dropdownRef.current?.contains(target)\n ) {\n close();\n }\n };\n document.addEventListener('mousedown', handleClick);\n return () => {\n document.removeEventListener('mousedown', handleClick);\n };\n }, [isOpen, close]);\n\n const handleTriggerKeyDown = useCallback(\n (e: React.KeyboardEvent) => {\n if (disabled) return;\n switch (e.key) {\n case 'Enter':\n case ' ':\n case 'ArrowDown':\n case 'ArrowUp':\n e.preventDefault();\n if (!isOpen) open();\n break;\n }\n },\n [disabled, isOpen, open]\n );\n\n const handleDropdownKeyDown = useCallback(\n (e: React.KeyboardEvent) => {\n listbox.handleKeyDown(e);\n },\n [listbox]\n );\n\n const removeValue = useCallback(\n (val: T) => {\n setSelected(selected.filter(v => v !== val));\n },\n [selected, setSelected]\n );\n\n const handleClearAll = useCallback(\n (e: React.MouseEvent) => {\n e.stopPropagation();\n setSelected([]);\n },\n [setSelected]\n );\n\n const showHelperText = error && errorMessage ? errorMessage : helperText;\n const chevronSize = CHEVRON_SIZES[size];\n\n const selectedOptions = useMemo(\n () =>\n selected\n .map(v => allOptions.find(o => o.value === v))\n .filter((o): o is MultiSelectOption<T> => Boolean(o)),\n [allOptions, selected]\n );\n\n const isEmpty = selected.length === 0;\n const visibleChips =\n maxInlineChips === Infinity\n ? selectedOptions\n : selectedOptions.slice(0, maxInlineChips);\n const overflowCount = selectedOptions.length - visibleChips.length;\n\n // Render options\n const renderOptionsList = () => {\n if (filteredOptions.length === 0) {\n return <div className={emptyMessageStyle}>{emptyMessage}</div>;\n }\n\n const hasGroups = options.some(isOptionGroup);\n // Mirror the deferred query used by `filteredOptions` so the grouped /\n // flat switch stays consistent with the list being rendered.\n if (hasGroups && !deferredSearchQuery) {\n let flatIndex = 0;\n return options.map((item, groupIdx) => {\n if (isOptionGroup(item)) {\n const groupOptions = item.options;\n const startIdx = flatIndex;\n flatIndex += groupOptions.length;\n return (\n <div\n key={`group-${String(groupIdx)}`}\n role=\"group\"\n aria-label={item.label}\n >\n <div className={groupLabelStyle}>{item.label}</div>\n {groupOptions.map((opt, optIdx) =>\n renderOptionItem(opt, startIdx + optIdx)\n )}\n </div>\n );\n }\n const idx = flatIndex;\n flatIndex += 1;\n return renderOptionItem(item, idx);\n });\n }\n\n return filteredOptions.map((opt, idx) => renderOptionItem(opt, idx));\n };\n\n const renderOptionItem = (opt: MultiSelectOption<T>, index: number) => {\n const isSelected = selectedSet.has(opt.value);\n const isActive = index === listbox.activeIndex;\n const isDisabled = opt.disabled ?? false;\n const reachedMax =\n typeof max === 'number' && !isSelected && selected.length >= max;\n const interactiveDisabled = isDisabled || reachedMax;\n\n const selectOption = () => {\n if (!interactiveDisabled) {\n toggleValue(opt.value, false);\n if (closeOnSelect) close();\n }\n };\n\n return (\n <div\n key={opt.value}\n role=\"option\"\n tabIndex={-1}\n aria-selected={isSelected}\n aria-disabled={interactiveDisabled || undefined}\n id={`${listboxId}-${opt.value}`}\n className={optionItemRecipe({\n active: isActive,\n selected: isSelected,\n disabled: interactiveDisabled,\n })}\n onClick={selectOption}\n onKeyDown={e => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n selectOption();\n }\n }}\n onMouseEnter={() => {\n if (!interactiveDisabled) listbox.setActiveIndex(index);\n }}\n >\n <span\n className={checkboxRecipe({ selected: isSelected })}\n aria-hidden=\"true\"\n >\n {isSelected && <CheckIcon size={10} decorative />}\n </span>\n {opt.icon && <span>{opt.icon}</span>}\n <span>{opt.label ?? opt.value}</span>\n </div>\n );\n };\n\n const formValue = selected.join(',');\n\n return (\n <div className={cx(containerStyle, className)} style={style}>\n {label && (\n <FormLabel\n id={labelId}\n htmlFor={fieldId}\n required={required}\n disabled={disabled}\n >\n {label}\n </FormLabel>\n )}\n\n <button\n ref={setTriggerRef}\n id={fieldId}\n type=\"button\"\n role=\"combobox\"\n aria-haspopup=\"listbox\"\n aria-expanded={isOpen}\n aria-controls={isOpen ? listboxId : undefined}\n aria-labelledby={label ? labelId : undefined}\n aria-required={required || undefined}\n aria-invalid={error || undefined}\n aria-describedby={showHelperText ? helperId : undefined}\n disabled={disabled}\n onClick={() => {\n if (isOpen) {\n close();\n } else {\n open();\n }\n }}\n onKeyDown={handleTriggerKeyDown}\n className={triggerRecipe({\n size,\n variant,\n open: isOpen,\n disabled,\n error,\n empty: isEmpty,\n })}\n data-testid={testId}\n {...rest}\n >\n <span className={triggerContentStyle}>\n {isEmpty ? (\n <span className={placeholderStyle}>{placeholder}</span>\n ) : (\n <>\n {visibleChips.map(opt => (\n <span\n key={opt.value}\n className={chipRecipe({ size })}\n data-testid={\n testId ? `${testId}-chip-${opt.value}` : undefined\n }\n >\n {opt.icon && <span>{opt.icon}</span>}\n <span className={chipLabelStyle}>\n {opt.label ?? opt.value}\n </span>\n {!disabled && (\n <span\n role=\"button\"\n tabIndex={-1}\n aria-label={`Remove ${opt.label ?? opt.value}`}\n className={chipRemoveStyle}\n onClick={e => {\n e.stopPropagation();\n removeValue(opt.value);\n }}\n onKeyDown={e => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n e.stopPropagation();\n removeValue(opt.value);\n }\n }}\n >\n <CloseIcon size=\"sm\" decorative />\n </span>\n )}\n </span>\n ))}\n {overflowCount > 0 && (\n <span className={moreBadgeStyle}>+{overflowCount} more</span>\n )}\n </>\n )}\n </span>\n\n {clearable && !isEmpty && !disabled && (\n <span\n role=\"button\"\n tabIndex={-1}\n aria-label=\"Clear selection\"\n className={clearButtonStyle}\n onClick={handleClearAll}\n onKeyDown={e => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n e.stopPropagation();\n setSelected([]);\n }\n }}\n >\n <CloseIcon size=\"sm\" decorative />\n </span>\n )}\n\n <span className={chevronRecipe({ open: isOpen })}>\n <ChevronDownIcon size={chevronSize} decorative />\n </span>\n </button>\n\n {name && <input type=\"hidden\" name={name} value={formValue} />}\n\n {isOpen &&\n createPortal(\n <div\n ref={dropdownRef}\n role=\"listbox\"\n id={listboxId}\n aria-multiselectable=\"true\"\n aria-labelledby={label ? labelId : undefined}\n className={dropdownStyle}\n style={dropdownPos}\n onKeyDown={handleDropdownKeyDown}\n tabIndex={-1}\n >\n {searchable && (\n <input\n ref={searchRef}\n type=\"text\"\n placeholder={searchPlaceholder}\n value={searchQuery}\n onChange={e => {\n setSearchQuery(e.target.value);\n }}\n onKeyDown={handleDropdownKeyDown}\n className={searchInputStyle}\n aria-label=\"Search options\"\n />\n )}\n <ScrollArea\n className={optionsListStyle}\n maxHeight={maxDropdownHeight - (searchable ? 32 : 0)}\n scrollbarWidth={4}\n scrollbarVisibility=\"auto\"\n hideDelay={600}\n >\n {renderOptionsList()}\n </ScrollArea>\n </div>,\n document.body\n )}\n\n {showHelperText && (\n <FormHelperText id={helperId} error={error}>\n {showHelperText}\n </FormHelperText>\n )}\n </div>\n );\n}\n\nMultiSelect.displayName = 'MultiSelect';\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAmDA;AAGE;AACF;AAEA;;AAIE;AACE;;;;AAGE;;;AAGJ;AACF;AAEA;AAIE;;AAEF;AAEA;AACE;AACA;AACA;;AAGF;;;;;;;;;;;;;;;;;;;;;AAqBG;AACG;AAiCJ;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;;AAIA;;;;AAIE;AACD;;;;AAMD;;;;AAKA;AAEA;AACA;AACE;AAAyC;AACzC;;AAGF;;AAII;;;;AAGE;;;;;;;;AASN;AACE;;;;AAGA;AACF;AAEA;;;AAGE;AACA;AACF;;AAGE;;;AAGE;AACA;AAAmB;;AAErB;AACD;AAED;AACE;AACA;;AACA;;AAEA;AAGA;;AAGI;AAEJ;;AAEE;AACA;AACE;;AAEI;AACD;AACH;AACH;AACH;;AAGE;;AACA;;;AAGI;AACF;;;;;AAMF;;AACA;AACE;;;AAKE;;AAEJ;AACA;AACA;AACE;AACF;AACF;AAEA;AAEI;;AACA;AACE;AACA;AACA;AACA;;AAEE;AAAa;;;;AAOrB;AAEI;AACF;AAIF;AAEI;AACF;AAIF;;;AAIE;AAIF;AACA;AAEA;AAGO;AACA;AAIP;AACA;AAEI;;;;;AAMF;AACE;;;;;AAMF;;;AAGI;AACE;;AAEA;;;;;AAgBF;AACF;;AAGF;AACF;AAEA;;AAEE;AACA;AACA;AAEA;;;AAII;AACA;AAAmB;;AAEvB;AAEA;AASM;AACA;AACA;;AAIA;;AAEE;;AAEJ;AAEE;AAA0B;;AAalC;;;;AAgCU;;;AAEA;;;;;AAOF;;;AAGA;;;AA8Bc;AACF;AAEE;;;AAGE;;AAEJ;AAsBR;;;;;;AAmBN;AAmBU;AACF;AA0BhB;AAEA;;"}
|