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":"NumberInput.js","sources":["../../../../../../src/components/controls/NumberInput/NumberInput.tsx"],"sourcesContent":[null],"names":[],"mappings":";;;;;;;;;AA6IA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2CG;AACI;AACL;AACA;AAGA;AACA;AAMA;AACA;AAWA;AACA;AAgBA;AACA;AACA;;AAGA;AAKA;AACA;;;;AAKC;AAED;;;;;;;;;;;;;;;;AAgCC;;AAGD;AACA;AACA;;AAGA;;;;AAIE;;;AAGA;;;;;;AAME;AACA;;;AAIJ;AAEA;AACE;;AAEA;;;AAKE;;AAGA;AACA;;AAEJ;;AAGE;;AAEA;AACA;;AAIA;AACE;;;AAGE;;;AAIJ;AACF;;;AAIE;;AAEA;AACE;AACF;;AAGE;AACA;AACF;AAEA;AACA;AAEA;AACE;AACA;AACF;;;AAIF;AACE;AACF;AAEA;;AAEE;AACF;AAEA;;AAEE;AACA;AACF;AAEA;;AAEE;AACF;;;AAIE;;;AAGF;;;;AAKE;AACF;;AAGA;;;AAGE;;AAEF;AAEA;;;AAGE;;AAEF;;AAGA;;AAIA;;AAGA;AAEA;;;;;;;;AASE;;AAyBM;;;AAGD;AAOK;;AAEA;;;;;;AAwCA;AACD;AAcC;;AAEA;AACD;AAgBD;AACA;AAKZ;AAEA;;"}
|
|
1
|
+
{"version":3,"file":"NumberInput.js","sources":["src/components/controls/NumberInput/NumberInput.tsx"],"sourcesContent":["'use client';\n\n// src/controls/NumberInput/NumberInput.tsx\nimport React, { useRef, useEffect } from 'react';\n\nimport { inputWrapperRecipe } from '@/components/form/InputWrapper.css';\nimport { ChevronDownIcon } from '@/components/Icons/ChevronDownIcon';\nimport { cx } from '@/utils/cx';\n\nimport {\n numberInputContainerStyle,\n containerHoveredStyle,\n containerDraggingStyle,\n labelRecipe,\n stepButtonRecipe,\n stepButtonIconLeftStyle,\n stepButtonIconRightStyle,\n inputRecipe,\n valueDisplayRecipe,\n unitLabelStyle,\n helperTextRecipe,\n} from './NumberInput.css';\nimport { useNumberInput } from './useNumberInput';\n\nimport type { UseNumberInputOptions } from './useNumberInput';\nimport type { BaseComponent, Size } from '@/types/common';\nimport type { Prettify } from '@/types/utilities';\n\n/**\n * Props specific to NumberInput component\n */\nexport interface NumberInputBaseProps\n extends Omit<BaseComponent, 'onChange'>, UseNumberInputOptions {\n /**\n * Current numeric value\n */\n value: number;\n\n /**\n * Callback when value changes\n */\n onChange: (value: number) => void;\n\n /**\n * Unit suffix to display (e.g., \"px\", \"%\", \"°\")\n * Can be a string or a function that formats the value\n */\n unit?: string | ((value: number) => string);\n\n /**\n * Whether to show increment/decrement buttons on hover\n * @default true\n */\n showStepButtons?: boolean;\n\n /**\n * Custom validation function\n * Return error message string if invalid, undefined if valid\n */\n validate?: (value: number) => string | undefined;\n\n /**\n * Format value for display (e.g., add commas, currency symbols)\n * This is separate from unit display\n */\n formatValue?: (value: number) => string;\n\n /**\n * Parse custom formatted input back to number\n * Used in conjunction with formatValue\n */\n parseValue?: (input: string) => number | null;\n\n /**\n * Input size using standard library sizing\n * - `sm`: 20px height, compact for toolbars\n * - `md`: 24px height, standard for forms\n * - `lg`: 32px height, prominent inputs\n * @default \"md\"\n */\n size?: Size;\n\n /**\n * Whether the input is disabled\n * @default false\n */\n disabled?: boolean;\n\n /**\n * Whether the input is read-only\n * @default false\n */\n readOnly?: boolean;\n\n /**\n * Error message displayed when error is true\n */\n errorMessage?: string | undefined;\n\n /**\n * Placeholder text\n */\n placeholder?: string | undefined;\n\n /**\n * Input label\n */\n label?: string | undefined;\n\n /**\n * Helper text displayed below the input\n */\n helperText?: string | undefined;\n\n /**\n * Whether the input is required\n * @default false\n */\n required?: boolean;\n\n /**\n * Focus event handler\n */\n onFocus?: (event: React.FocusEvent<HTMLInputElement>) => void;\n\n /**\n * Blur event handler\n */\n onBlur?: (event: React.FocusEvent<HTMLInputElement>) => void;\n\n /**\n * Key down event handler\n */\n onKeyDown?: (event: React.KeyboardEvent<HTMLInputElement>) => void;\n}\n\n/**\n * Props for the NumberInput component with prettified type for better IntelliSense\n */\nexport type NumberInputProps = Prettify<NumberInputBaseProps>;\n\n/**\n * A specialized number input component with Blender-like functionality.\n *\n * Features:\n * - Drag left/right to change values with mouse\n * - Click chevron buttons to increment/decrement\n * - Keyboard input with mathematical expression support\n * - Modifier keys: Ctrl (large steps), Shift (precision), Minus (negate)\n * - Soft and hard value limits\n * - Unit display support\n * - Expression evaluation (pi, sqrt, sin, etc.)\n * - Custom validation and formatting\n * - Centered value and unit display\n *\n * @example\n * ```tsx\n * // Basic number input\n * <NumberInput\n * value={rotation}\n * onChange={setRotation}\n * unit=\"°\"\n * step={1}\n * />\n *\n * // With limits and precision\n * <NumberInput\n * value={opacity}\n * onChange={setOpacity}\n * min={0}\n * max={1}\n * step={0.1}\n * precision={2}\n * unit=\"%\"\n * />\n *\n * // With custom validation\n * <NumberInput\n * value={dimension}\n * onChange={setDimension}\n * validate={(v) => v <= 0 ? \"Must be positive\" : undefined}\n * placeholder=\"Enter value or expression\"\n * />\n * ```\n */\nexport const NumberInput = ({\n // Value props\n value,\n onChange,\n\n // NumberInput specific props\n unit,\n showStepButtons = true,\n validate,\n formatValue,\n parseValue,\n\n // UseNumberInput options\n min,\n max,\n softMin,\n softMax,\n step,\n precisionStep,\n largeStep,\n precision,\n allowExpressions = true,\n dragSensitivity = 1,\n\n // Input props\n size = 'md',\n disabled = false,\n readOnly = false,\n errorMessage,\n placeholder,\n label,\n helperText,\n required = false,\n className,\n testId,\n onFocus,\n onBlur,\n onKeyDown,\n ref,\n ...props\n}: NumberInputProps) => {\n const containerRef = useRef<HTMLDivElement>(null);\n const inputRef = useRef<HTMLInputElement>(null);\n const [focused, setFocused] = React.useState(false);\n\n // Track mouse state for distinguishing click vs drag\n const mouseDownRef = useRef<{\n x: number;\n time: number;\n hasMoved: boolean;\n } | null>(null);\n const dragThreshold = 3; // pixels\n const clickTimeThreshold = 200; // milliseconds\n\n // Combine refs\n React.useImperativeHandle(ref, () => inputRef.current as HTMLInputElement, [\n inputRef,\n ]);\n\n const {\n displayValue,\n isEditing,\n isDragging,\n isHovered,\n error: internalError,\n increment,\n decrement,\n startDrag,\n updateDrag,\n endDrag,\n startEditing,\n endEditing,\n updateDisplayValue,\n setHovered,\n handleKeyDown,\n } = useNumberInput({\n value,\n onChange,\n ...(min !== undefined && { min }),\n ...(max !== undefined && { max }),\n ...(softMin !== undefined && { softMin }),\n ...(softMax !== undefined && { softMax }),\n ...(step !== undefined && { step }),\n ...(precisionStep !== undefined && { precisionStep }),\n ...(largeStep !== undefined && { largeStep }),\n ...(precision !== undefined && { precision }),\n allowExpressions,\n disabled: disabled || readOnly,\n dragSensitivity,\n ...(formatValue !== undefined && { formatValue }),\n ...(parseValue !== undefined && { parseValue }),\n });\n\n // External validation\n const externalError = validate ? validate(value) : undefined;\n const effectiveError = !!errorMessage || !!externalError || !!internalError;\n const effectiveErrorMessage = errorMessage ?? externalError ?? internalError;\n\n // Mouse event handlers for dragging vs clicking\n const handleMouseDown = (event: React.MouseEvent) => {\n if (disabled || readOnly) return;\n\n // Only handle left mouse button\n if (event.button !== 0) return;\n\n // Don't handle if clicking on step buttons\n const target = event.target as HTMLElement;\n if (target.tagName === 'BUTTON' || target.closest('button')) return;\n\n // Record mouse down state\n mouseDownRef.current = {\n x: event.clientX,\n time: Date.now(),\n hasMoved: false,\n };\n\n event.preventDefault();\n };\n\n const handleMouseMove = (event: React.MouseEvent) => {\n if (!mouseDownRef.current || disabled || readOnly) return;\n\n const deltaX = Math.abs(event.clientX - mouseDownRef.current.x);\n\n // If we've moved beyond threshold and not already dragging, start drag\n if (\n deltaX > dragThreshold &&\n !mouseDownRef.current.hasMoved &&\n !isDragging\n ) {\n mouseDownRef.current.hasMoved = true;\n startDrag(mouseDownRef.current.x);\n }\n };\n\n const handleMouseUp = () => {\n if (!mouseDownRef.current || disabled || readOnly) return;\n\n const timeDelta = Date.now() - mouseDownRef.current.time;\n const wasClick =\n !mouseDownRef.current.hasMoved && timeDelta < clickTimeThreshold;\n\n // If it was a click (not drag), start editing\n if (wasClick && !isEditing) {\n startEditing();\n // Focus the input after a short delay to ensure it's visible\n setTimeout(() => {\n inputRef.current?.focus();\n }, 0);\n }\n\n mouseDownRef.current = null;\n };\n\n // Global mouse move and up handlers for dragging\n useEffect(() => {\n if (!isDragging) return;\n\n const handleGlobalMouseMove = (event: MouseEvent) => {\n updateDrag(event.clientX);\n };\n\n const handleGlobalMouseUp = () => {\n endDrag();\n mouseDownRef.current = null;\n };\n\n document.addEventListener('mousemove', handleGlobalMouseMove);\n document.addEventListener('mouseup', handleGlobalMouseUp);\n\n return () => {\n document.removeEventListener('mousemove', handleGlobalMouseMove);\n document.removeEventListener('mouseup', handleGlobalMouseUp);\n };\n }, [isDragging, updateDrag, endDrag]);\n\n // Input event handlers\n const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {\n updateDisplayValue(event.target.value);\n };\n\n const handleInputFocus = (event: React.FocusEvent<HTMLInputElement>) => {\n setFocused(true);\n onFocus?.(event);\n };\n\n const handleInputBlur = (event: React.FocusEvent<HTMLInputElement>) => {\n setFocused(false);\n endEditing();\n onBlur?.(event);\n };\n\n const handleInputKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {\n handleKeyDown(event);\n onKeyDown?.(event);\n };\n\n // Container event handlers\n const handleContainerMouseEnter = () => {\n if (!disabled && !readOnly) {\n setHovered(true);\n }\n };\n\n const handleContainerMouseLeave = () => {\n setHovered(false);\n // Reset mouse state if leaving container\n mouseDownRef.current = null;\n };\n\n // Step button handlers\n const handleIncrementClick = (event: React.MouseEvent) => {\n event.preventDefault();\n event.stopPropagation();\n increment();\n // Don't focus input after step button click\n };\n\n const handleDecrementClick = (event: React.MouseEvent) => {\n event.preventDefault();\n event.stopPropagation();\n decrement();\n // Don't focus input after step button click\n };\n\n // Show step buttons when appropriate\n const shouldShowStepButtons =\n showStepButtons && isHovered && !isEditing && !disabled && !readOnly;\n\n // Format unit display\n const unitDisplay = typeof unit === 'function' ? unit(value) : unit;\n\n // Format display value\n const formattedValue = formatValue ? formatValue(value) : displayValue;\n\n const inputId = React.useId();\n\n return (\n // The mouse handlers below implement a pointer-only drag-to-scrub\n // enhancement on this presentational wrapper. Keyboard accessibility is\n // provided by the inner native <input> (handleInputKeyDown) and the\n // native step <button>s, so no role/keyboard handlers belong on the\n // wrapper itself.\n // eslint-disable-next-line jsx-a11y/no-static-element-interactions\n <div\n ref={containerRef}\n className={cx(\n numberInputContainerStyle,\n isHovered && !disabled && !isDragging && containerHoveredStyle,\n isDragging && containerDraggingStyle,\n className\n )}\n onMouseEnter={handleContainerMouseEnter}\n onMouseLeave={handleContainerMouseLeave}\n onMouseDown={handleMouseDown}\n onMouseMove={handleMouseMove}\n onMouseUp={handleMouseUp}\n data-testid={testId}\n >\n {label && (\n <label htmlFor={inputId} className={labelRecipe({ disabled })}>\n {label}\n {required && <span style={{ color: 'var(--accent-error)' }}> *</span>}\n </label>\n )}\n\n <div\n className={inputWrapperRecipe({\n size,\n error: effectiveError,\n disabled,\n focused,\n })}\n >\n {/* Decrement button */}\n {showStepButtons && (\n <button\n type=\"button\"\n className={stepButtonRecipe({\n position: 'left',\n size,\n visible: shouldShowStepButtons,\n })}\n onClick={handleDecrementClick}\n disabled={disabled}\n tabIndex={-1}\n aria-label=\"Decrement value\"\n >\n <span className={stepButtonIconLeftStyle}>\n <ChevronDownIcon size={size === 'sm' ? 'sm' : 'md'} />\n </span>\n </button>\n )}\n\n {/* Input field - hidden when not editing */}\n <input\n {...props}\n ref={inputRef}\n id={inputId}\n type=\"text\"\n value={isEditing ? displayValue : ''}\n onChange={handleInputChange}\n onFocus={handleInputFocus}\n onBlur={handleInputBlur}\n onKeyDown={handleInputKeyDown}\n placeholder={isEditing ? placeholder : ''}\n disabled={disabled}\n required={required}\n readOnly={readOnly}\n className={inputRecipe({ size })}\n style={{\n opacity: isEditing ? 1 : 0,\n pointerEvents: isEditing ? 'auto' : 'none',\n }}\n />\n\n {/* Value display - shown when not editing */}\n {!isEditing && (\n <div\n className={valueDisplayRecipe({\n size,\n hasStepButtons: shouldShowStepButtons,\n })}\n >\n <span>{formattedValue}</span>\n {unitDisplay && (\n <span className={unitLabelStyle}>{unitDisplay}</span>\n )}\n </div>\n )}\n\n {/* Increment button */}\n {showStepButtons && (\n <button\n type=\"button\"\n className={stepButtonRecipe({\n position: 'right',\n size,\n visible: shouldShowStepButtons,\n })}\n onClick={handleIncrementClick}\n disabled={disabled}\n tabIndex={-1}\n aria-label=\"Increment value\"\n >\n <span className={stepButtonIconRightStyle}>\n <ChevronDownIcon size={size === 'sm' ? 'sm' : 'md'} />\n </span>\n </button>\n )}\n </div>\n\n {(helperText ?? (effectiveError && effectiveErrorMessage)) && (\n <div className={helperTextRecipe({ error: effectiveError })}>\n {effectiveError && effectiveErrorMessage\n ? effectiveErrorMessage\n : helperText}\n </div>\n )}\n </div>\n );\n};\n\nNumberInput.displayName = 'NumberInput';\n"],"names":[],"mappings":";;;;;;;;;AA6IA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2CG;AACI;AACL;AACA;AAGA;AACA;AAMA;AACA;AAWA;AACA;AAgBA;AACA;AACA;;AAGA;AAKA;AACA;;;;AAKC;AAED;;;;;;;;;;;;;;;;AAgCC;;AAGD;AACA;AACA;;AAGA;;;;AAIE;;;AAGA;;;;;;AAME;AACA;;;AAIJ;AAEA;AACE;;AAEA;;;AAKE;;AAGA;AACA;;AAEJ;;AAGE;;AAEA;AACA;;AAIA;AACE;;;AAGE;;;AAIJ;AACF;;;AAIE;;AAEA;AACE;AACF;;AAGE;AACA;AACF;AAEA;AACA;AAEA;AACE;AACA;AACF;;;AAIF;AACE;AACF;AAEA;;AAEE;AACF;AAEA;;AAEE;AACA;AACF;AAEA;;AAEE;AACF;;;AAIE;;;AAGF;;;;AAKE;AACF;;AAGA;;;AAGE;;AAEF;AAEA;;;AAGE;;AAEF;;AAGA;;AAIA;;AAGA;AAEA;;;;;;;;AASE;;AAyBM;;;AAGD;AAOK;;AAEA;;;;;;AAwCA;AACD;AAcC;;AAEA;AACD;AAgBD;AACA;AAKZ;AAEA;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useNumberInput.js","sources":["../../../../../../src/components/controls/NumberInput/useNumberInput.ts"],"sourcesContent":[null],"names":[],"mappings":";;;;;;AAwMA;;AAEG;AACH;;AAEE;;AACA;;AACA;AACF;AAEA;;AAEG;AACH;;AAC+B;;;AAG/B;AAEA;;AAEG;AACH;AACE;AACE;;;AAGF;AACF;AAEA;;;;;;;;;;;;AAYG;;;AAmBD;AACA;AACA;;;AAKI;;;;;;;;AAUJ;AACA;AACA;;AAGA;;;AAIE;;AAEI;AACA;;;;;;;;AAQN;AAEA;;AAEG;AACH;;AACuB;AACrB;AAAyC;AACzC;AACF;AACE;AACA;AACA;;;;AAID;AAED;;AAEG;;AAGC;;;;;AAMA;AACA;;AAGA;;;AAGF;AAIF;;AAEG;AACH;AACE;;;AAGI;;;;AAIE;;;;;AAMF;;AAEE;;AAEA;;;AAEA;AACA;;;;AAKJ;AACA;;;AAGE;;;AAIF;;;;AAGA;AACA;;;;AAKJ;AACE;;AACA;AACA;;AAGF;AACE;;AACA;AACA;;AAGF;AAEI;;;AAEA;AACA;AACA;;AAEF;AAIF;;;AAII;AACA;;;AAIA;;;;;AAME;;;AAIA;;;AAIE;AACA;;;AAGN;;;;;;;;AASC;AAGH;;;;;AAKI;AACA;;;AAIJ;AACE;;;;;;AAKE;AACA;AACE;AACA;;;AAGN;AAEA;;;;;AAKA;AAEA;;;AAGI;AACA;;;;AAKJ;;;;;;AAOE;AAIF;AAEI;;AAEA;AACE;;;AAGI;;;AAEA;;;AAIJ;;;AAGI;;;AAIJ;;;AAGI;;;AAIJ;;;AAGI;;;AAIJ;;;;AAII;;;;AAIR;;;;;;;;;;AAWC;;;;;;;;;;;AAcD;;;;;;;;;;;AAYA;AACA;;;AAGJ;;"}
|
|
1
|
+
{"version":3,"file":"useNumberInput.js","sources":["src/components/controls/NumberInput/useNumberInput.ts"],"sourcesContent":["'use client';\n\nimport React, { useState, useCallback, useRef, useEffect } from 'react';\n\nimport { useKeyboardContext } from '@/context/KeyboardContext';\nimport { devError } from '@/utils/devWarn';\nimport { parseNumericInput, isExpression } from '@/utils/mathExpression';\n\n/**\n * Configuration options for NumberInput behavior\n */\nexport interface UseNumberInputOptions {\n /**\n * Minimum allowed value (hard limit)\n */\n min?: number;\n\n /**\n * Maximum allowed value (hard limit)\n */\n max?: number;\n\n /**\n * Soft minimum for drag operations (can be overridden by keyboard)\n */\n softMin?: number;\n\n /**\n * Soft maximum for drag operations (can be overridden by keyboard)\n */\n softMax?: number;\n\n /**\n * Step size for increment/decrement operations\n * @default 1\n */\n step?: number;\n\n /**\n * Step size when Shift is held (precision mode)\n * @default step / 10\n */\n precisionStep?: number;\n\n /**\n * Step size when Ctrl is held (large steps)\n * @default step * 10\n */\n largeStep?: number;\n\n /**\n * Number of decimal places to round to\n * @default 2\n */\n precision?: number;\n\n /**\n * Whether to allow mathematical expressions\n * @default true\n */\n allowExpressions?: boolean;\n\n /**\n * Whether the input is disabled\n * @default false\n */\n disabled?: boolean;\n\n /**\n * Drag sensitivity multiplier\n * @default 1\n */\n dragSensitivity?: number;\n\n /**\n * Custom format function for display\n */\n formatValue?: (value: number) => string;\n\n /**\n * Custom parse function for input\n */\n parseValue?: (input: string) => number | null;\n}\n\n/**\n * Props for useNumberInput hook\n */\nexport interface UseNumberInputProps extends UseNumberInputOptions {\n /**\n * Current numeric value\n */\n value: number;\n\n /**\n * Callback when value changes\n */\n onChange: (value: number) => void;\n}\n\n/**\n * State returned by useNumberInput hook\n */\nexport interface UseNumberInputReturn {\n /**\n * Current display value (string representation)\n */\n displayValue: string;\n\n /**\n * Whether currently in text editing mode\n */\n isEditing: boolean;\n\n /**\n * Whether currently dragging\n */\n isDragging: boolean;\n\n /**\n * Whether mouse is hovering over the input\n */\n isHovered: boolean;\n\n /**\n * Whether input has focus\n */\n isFocused: boolean;\n\n /**\n * Current error message (if any)\n */\n error: string | undefined;\n\n /**\n * Whether the current input looks like an expression\n */\n isExpression: boolean;\n\n /**\n * Increment value by step amount\n */\n increment: () => void;\n\n /**\n * Decrement value by step amount\n */\n decrement: () => void;\n\n /**\n * Start dragging operation\n */\n startDrag: (clientX: number) => void;\n\n /**\n * Update drag operation\n */\n updateDrag: (clientX: number) => void;\n\n /**\n * End dragging operation\n */\n endDrag: () => void;\n\n /**\n * Start text editing mode\n */\n startEditing: () => void;\n\n /**\n * End text editing mode and apply value\n */\n endEditing: () => void;\n\n /**\n * Cancel text editing mode without applying\n */\n cancelEditing: () => void;\n\n /**\n * Update the display value (during text editing)\n */\n updateDisplayValue: (value: string) => void;\n\n /**\n * Handle hover state changes\n */\n setHovered: (hovered: boolean) => void;\n\n /**\n * Handle focus state changes\n */\n setFocused: (focused: boolean) => void;\n\n /**\n * Handle keyboard input\n */\n handleKeyDown: (event: React.KeyboardEvent) => void;\n}\n\n/**\n * Clamps a value between min and max bounds\n */\nfunction clamp(value: number, min?: number, max?: number): number {\n let result = value;\n if (min !== undefined && result < min) result = min;\n if (max !== undefined && result > max) result = max;\n return result;\n}\n\n/**\n * Rounds a number to specified decimal places\n */\nfunction roundToPrecision(value: number, precision?: number): number {\n if (precision === undefined) return value;\n const factor = Math.pow(10, precision);\n return Math.round(value * factor) / factor;\n}\n\n/**\n * Default formatter for display values\n */\nfunction defaultFormatValue(value: number, precision?: number): string {\n if (precision !== undefined) {\n return value.toFixed(precision);\n }\n // Remove trailing zeros and unnecessary decimal point\n return value.toString();\n}\n\n/**\n * Hook for managing NumberInput state and interactions.\n *\n * Provides Blender-like number input functionality including:\n * - Drag to change values\n * - Keyboard input with expression evaluation\n * - Modifier key support (Ctrl, Shift)\n * - Soft and hard limits\n * - Precision control\n *\n * @param props Configuration options\n * @returns Object with state and actions\n */\nexport function useNumberInput({\n value,\n onChange,\n min,\n max,\n softMin,\n softMax,\n step = 1,\n precisionStep,\n largeStep,\n precision,\n allowExpressions = true,\n disabled = false,\n dragSensitivity = 1,\n formatValue,\n parseValue,\n}: UseNumberInputProps): UseNumberInputReturn {\n // Calculate default step sizes if not provided\n const effectivePrecisionStep = precisionStep ?? step / 10;\n const effectiveLargeStep = largeStep ?? step * 10;\n const effectivePrecision = precision ?? 2;\n\n // State\n const [displayValue, setDisplayValue] = useState(() =>\n formatValue\n ? formatValue(value)\n : defaultFormatValue(value, effectivePrecision)\n );\n const [isEditing, setIsEditing] = useState(false);\n const [isDragging, setIsDragging] = useState(false);\n const [isHovered, setIsHovered] = useState(false);\n const [isFocused, setIsFocused] = useState(false);\n const [error, setError] = useState<string | undefined>();\n\n // Refs for drag state\n const dragStartX = useRef(0);\n const dragStartValue = useRef(0);\n const accumulatedDelta = useRef(0);\n\n // Keyboard state\n const { modifiers } = useKeyboardContext();\n\n // Update display value when external value changes (but not during editing)\n useEffect(() => {\n if (!isEditing && !isDragging) {\n const formatted = formatValue\n ? formatValue(value)\n : defaultFormatValue(value, effectivePrecision);\n // Sync the display string to the controlled `value` prop while the user\n // is not actively editing/dragging — an external-value sync that the\n // edit guard makes safe from cascading renders.\n // eslint-disable-next-line react-hooks/set-state-in-effect\n setDisplayValue(formatted);\n setError(undefined);\n }\n }, [value, effectivePrecision, isEditing, isDragging, formatValue]);\n\n /**\n * Gets the appropriate step size based on modifier keys\n */\n const getStepSize = useCallback((): number => {\n if (modifiers.shift) return effectivePrecisionStep;\n if (modifiers.control || modifiers.meta) return effectiveLargeStep;\n return step;\n }, [\n modifiers.shift,\n modifiers.control,\n modifiers.meta,\n effectivePrecisionStep,\n effectiveLargeStep,\n step,\n ]);\n\n /**\n * Applies a new value with proper bounds checking and rounding\n */\n const applyValue = useCallback(\n (newValue: number, useSoftLimits = false): void => {\n if (disabled) return;\n\n // Apply rounding\n const rounded = roundToPrecision(newValue, effectivePrecision);\n\n // Apply limits\n const minLimit = useSoftLimits && softMin !== undefined ? softMin : min;\n const maxLimit = useSoftLimits && softMax !== undefined ? softMax : max;\n const clamped = clamp(rounded, minLimit, maxLimit);\n\n if (clamped !== value) {\n onChange(clamped);\n }\n },\n [disabled, effectivePrecision, min, max, softMin, softMax, value, onChange]\n );\n\n /**\n * Parses and applies text input value\n */\n const parseAndApplyInput = useCallback((): boolean => {\n try {\n // Try custom parser first\n if (parseValue) {\n const parsed = parseValue(displayValue);\n if (parsed !== null && !isNaN(parsed)) {\n applyValue(parsed);\n setError(undefined);\n return true;\n }\n }\n\n // Try expression evaluation if allowed\n if (allowExpressions) {\n const result = parseNumericInput(displayValue);\n if (result.success && result.value !== undefined) {\n applyValue(result.value);\n setError(undefined);\n return true;\n } else {\n setError(result.error ?? 'Invalid expression');\n return false;\n }\n }\n\n // Fall back to simple number parsing\n const numValue = parseFloat(displayValue);\n if (!isNaN(numValue)) {\n applyValue(numValue);\n setError(undefined);\n return true;\n }\n\n setError('Invalid number');\n return false;\n } catch (err) {\n setError('Invalid input');\n devError('Error parsing input:', err);\n return false;\n }\n }, [displayValue, parseValue, allowExpressions, applyValue]);\n\n // Actions\n const increment = useCallback(() => {\n if (disabled) return;\n const currentStep = getStepSize();\n applyValue(value + currentStep);\n }, [disabled, getStepSize, value, applyValue]);\n\n const decrement = useCallback(() => {\n if (disabled) return;\n const currentStep = getStepSize();\n applyValue(value - currentStep);\n }, [disabled, getStepSize, value, applyValue]);\n\n const startDrag = useCallback(\n (clientX: number) => {\n if (disabled) return;\n setIsDragging(true);\n dragStartX.current = clientX;\n dragStartValue.current = value;\n accumulatedDelta.current = 0;\n document.body.style.cursor = 'ew-resize';\n },\n [disabled, value]\n );\n\n const updateDrag = useCallback(\n (clientX: number) => {\n if (!isDragging || disabled) return;\n\n const deltaX = clientX - dragStartX.current;\n const currentStep = getStepSize();\n const sensitivity = dragSensitivity;\n\n // Accumulate fractional changes for smooth dragging\n const pixelsPerStep = 5; // Pixels needed to trigger one step\n const totalDelta = (deltaX * sensitivity) / pixelsPerStep;\n\n // Only update if we've moved enough for at least one step\n const steps = Math.floor(totalDelta);\n if (steps !== Math.floor(accumulatedDelta.current)) {\n accumulatedDelta.current = totalDelta;\n const newValue = dragStartValue.current + steps * currentStep;\n\n // Use soft limits for dragging\n applyValue(newValue, true);\n\n // Update display to show current value\n const formatted = formatValue\n ? formatValue(newValue)\n : defaultFormatValue(newValue, effectivePrecision);\n setDisplayValue(formatted);\n }\n },\n [\n isDragging,\n disabled,\n getStepSize,\n dragSensitivity,\n applyValue,\n formatValue,\n effectivePrecision,\n ]\n );\n\n const endDrag = useCallback(() => {\n setIsDragging(false);\n document.body.style.cursor = '';\n // Refresh display value to ensure it matches the current value\n const formatted = formatValue\n ? formatValue(value)\n : defaultFormatValue(value, effectivePrecision);\n setDisplayValue(formatted);\n }, [value, formatValue, effectivePrecision]);\n\n const startEditing = useCallback(() => {\n if (disabled) return;\n setIsEditing(true);\n setError(undefined);\n // Place cursor at end of input\n setTimeout(() => {\n const input = document.activeElement;\n if (input instanceof HTMLInputElement) {\n const len = input.value.length;\n input.setSelectionRange(len, len);\n }\n }, 0);\n }, [disabled]);\n\n const endEditing = useCallback(() => {\n if (parseAndApplyInput()) {\n setIsEditing(false);\n }\n // If parsing failed, stay in editing mode to show error\n }, [parseAndApplyInput]);\n\n const cancelEditing = useCallback(() => {\n setIsEditing(false);\n const formatted = formatValue\n ? formatValue(value)\n : defaultFormatValue(value, effectivePrecision);\n setDisplayValue(formatted);\n setError(undefined);\n }, [value, effectivePrecision, formatValue]);\n\n const updateDisplayValue = useCallback(\n (newValue: string) => {\n setDisplayValue(newValue);\n // Clear error when user starts typing\n if (error) {\n setError(undefined);\n }\n },\n [error]\n );\n\n const handleKeyDown = useCallback(\n (event: React.KeyboardEvent) => {\n if (disabled) return;\n\n switch (event.key) {\n case 'Enter':\n event.preventDefault();\n if (isEditing) {\n endEditing();\n } else {\n startEditing();\n }\n break;\n\n case 'Escape':\n event.preventDefault();\n if (isEditing) {\n cancelEditing();\n }\n break;\n\n case 'ArrowUp':\n if (!isEditing) {\n event.preventDefault();\n increment();\n }\n break;\n\n case 'ArrowDown':\n if (!isEditing) {\n event.preventDefault();\n decrement();\n }\n break;\n\n case '-':\n // Only negate when not editing and minus is pressed alone (not in the middle of text)\n if (!isEditing) {\n event.preventDefault();\n applyValue(-value);\n }\n break;\n }\n },\n [\n disabled,\n isEditing,\n endEditing,\n startEditing,\n cancelEditing,\n increment,\n decrement,\n value,\n applyValue,\n ]\n );\n\n // Determine if current input looks like an expression\n const isExpressionValue = allowExpressions && isExpression(displayValue);\n\n return {\n // State\n displayValue,\n isEditing,\n isDragging,\n isHovered,\n isFocused,\n error,\n isExpression: isExpressionValue,\n\n // Actions\n increment,\n decrement,\n startDrag,\n updateDrag,\n endDrag,\n startEditing,\n endEditing,\n cancelEditing,\n updateDisplayValue,\n setHovered: setIsHovered,\n setFocused: setIsFocused,\n handleKeyDown,\n };\n}\n"],"names":[],"mappings":";;;;;;AAwMA;;AAEG;AACH;;AAEE;;AACA;;AACA;AACF;AAEA;;AAEG;AACH;;AAC+B;;;AAG/B;AAEA;;AAEG;AACH;AACE;AACE;;;AAGF;AACF;AAEA;;;;;;;;;;;;AAYG;;;AAmBD;AACA;AACA;;;AAKI;;;;;;;;AAUJ;AACA;AACA;;AAGA;;;AAIE;;AAEI;AACA;;;;;;;;AAQN;AAEA;;AAEG;AACH;;AACuB;AACrB;AAAyC;AACzC;AACF;AACE;AACA;AACA;;;;AAID;AAED;;AAEG;;AAGC;;;;;AAMA;AACA;;AAGA;;;AAGF;AAIF;;AAEG;AACH;AACE;;;AAGI;;;;AAIE;;;;;AAMF;;AAEE;;AAEA;;;AAEA;AACA;;;;AAKJ;AACA;;;AAGE;;;AAIF;;;;AAGA;AACA;;;;AAKJ;AACE;;AACA;AACA;;AAGF;AACE;;AACA;AACA;;AAGF;AAEI;;;AAEA;AACA;AACA;;AAEF;AAIF;;;AAII;AACA;;;AAIA;;;;;AAME;;;AAIA;;;AAIE;AACA;;;AAGN;;;;;;;;AASC;AAGH;;;;;AAKI;AACA;;;AAIJ;AACE;;;;;;AAKE;AACA;AACE;AACA;;;AAGN;AAEA;;;;;AAKA;AAEA;;;AAGI;AACA;;;;AAKJ;;;;;;AAOE;AAIF;AAEI;;AAEA;AACE;;;AAGI;;;AAEA;;;AAIJ;;;AAGI;;;AAIJ;;;AAGI;;;AAIJ;;;AAGI;;;AAIJ;;;;AAII;;;;AAIR;;;;;;;;;;AAWC;;;;;;;;;;;AAcD;;;;;;;;;;;AAYA;AACA;;;AAGJ;;"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import './../../../assets/src/components/controls/Select/Select.css.ts.vanilla-
|
|
1
|
+
import './../../../assets/src/components/controls/Select/Select.css.ts.vanilla-Cpu2BvWL.css';
|
|
2
2
|
import { createRuntimeFn } from '@vanilla-extract/recipes/createRuntimeFn';
|
|
3
3
|
|
|
4
4
|
var checkmarkStyle = 'Select_checkmarkStyle__ycpn7018';
|
|
@@ -58,7 +58,7 @@ const CHEVRON_SIZES = {
|
|
|
58
58
|
* />
|
|
59
59
|
* ```
|
|
60
60
|
*/
|
|
61
|
-
function Select({ value: valueProp, defaultValue, options, placeholder = 'Select...', searchable = false, searchPlaceholder = 'Search...', filterFn, emptyMessage = 'No results found', size = 'md', variant = 'default', label, helperText, error = false, errorMessage, disabled = false, required = false, clearable = false, maxDropdownHeight = 240, minDropdownWidth, name, onChange, onOpenChange, className, style, testId, ref, id: idProp, ...rest }) {
|
|
61
|
+
function Select({ value: valueProp, defaultValue, options, placeholder = 'Select...', searchable = false, searchPlaceholder = 'Search...', filterFn, emptyMessage = 'No results found', size = 'md', variant = 'default', label, helperText, error = false, errorMessage, disabled = false, required = false, clearable = false, maxDropdownHeight = 240, minDropdownWidth, fullWidth = false, name, onChange, onOpenChange, className, style, testId, ref, id: idProp, ...rest }) {
|
|
62
62
|
const autoId = useId();
|
|
63
63
|
const selectId = idProp ?? autoId;
|
|
64
64
|
const labelId = `${selectId}-label`;
|
|
@@ -93,12 +93,17 @@ function Select({ value: valueProp, defaultValue, options, placeholder = 'Select
|
|
|
93
93
|
const rect = trigger.getBoundingClientRect();
|
|
94
94
|
const spaceBelow = window.innerHeight - rect.bottom;
|
|
95
95
|
const openAbove = spaceBelow < maxDropdownHeight + 8 && rect.top > spaceBelow;
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
96
|
+
// The dropdown sizes to its content (`width: max-content` in CSS). Anchor
|
|
97
|
+
// that growth between a minimum (at least the trigger, and `minDropdownWidth`
|
|
98
|
+
// when set) and a maximum that keeps it inside the viewport so long option
|
|
99
|
+
// labels are never clipped by a narrow trigger.
|
|
100
|
+
const VIEWPORT_MARGIN = 8;
|
|
101
|
+
const minWidth = Math.max(rect.width, minDropdownWidth ?? 0);
|
|
102
|
+
const maxWidth = Math.max(minWidth, window.innerWidth - rect.left - VIEWPORT_MARGIN);
|
|
99
103
|
setDropdownStyleState({
|
|
100
104
|
left: rect.left,
|
|
101
|
-
|
|
105
|
+
minWidth,
|
|
106
|
+
maxWidth,
|
|
102
107
|
...(openAbove
|
|
103
108
|
? {
|
|
104
109
|
bottom: window.innerHeight - rect.top + 4,
|
|
@@ -127,6 +132,9 @@ function Select({ value: valueProp, defaultValue, options, placeholder = 'Select
|
|
|
127
132
|
if (!isControlled) {
|
|
128
133
|
setInternalValue(val);
|
|
129
134
|
}
|
|
135
|
+
// `onChange` is typed per `clearable` (it only receives `null` from the
|
|
136
|
+
// clear button, which requires `clearable`). Internally we always call it
|
|
137
|
+
// with `T | null`; the public discriminated union enforces the contract.
|
|
130
138
|
onChange?.(val);
|
|
131
139
|
close();
|
|
132
140
|
}, [isControlled, onChange, close]);
|
|
@@ -315,7 +323,7 @@ function Select({ value: valueProp, defaultValue, options, placeholder = 'Select
|
|
|
315
323
|
}
|
|
316
324
|
}, children: [opt.icon && jsx("span", { children: opt.icon }), jsx("span", { children: opt.label ?? opt.value }), isSelected && (jsx("span", { className: checkmarkStyle, children: jsx(CheckIcon, { size: 10, decorative: true }) }))] }, opt.value));
|
|
317
325
|
};
|
|
318
|
-
return (jsxs("div", { className: cx(selectContainerStyle, className), style: style, children: [label && (jsx(FormLabel, { id: labelId, htmlFor: selectId, required: required, disabled: disabled, children: label })), jsxs("button", { ref: setTriggerRef, type: "button", role: "combobox", "aria-haspopup": "listbox", "aria-expanded": isOpen, "aria-controls": isOpen ? listboxId : undefined, "aria-labelledby": label ? labelId : undefined, "aria-required": required || undefined, "aria-invalid": error || undefined, "aria-describedby": showHelperText ? helperId : undefined, disabled: disabled, onClick: () => {
|
|
326
|
+
return (jsxs("div", { className: cx(selectContainerStyle, className), style: { ...(fullWidth ? { width: '100%' } : null), ...style }, children: [label && (jsx(FormLabel, { id: labelId, htmlFor: selectId, required: required, disabled: disabled, children: label })), jsxs("button", { ref: setTriggerRef, type: "button", role: "combobox", "aria-haspopup": "listbox", "aria-expanded": isOpen, "aria-controls": isOpen ? listboxId : undefined, "aria-labelledby": label ? labelId : undefined, "aria-required": required || undefined, "aria-invalid": error || undefined, "aria-describedby": showHelperText ? helperId : undefined, disabled: disabled, onClick: () => {
|
|
319
327
|
if (isOpen) {
|
|
320
328
|
close();
|
|
321
329
|
}
|
|
@@ -329,7 +337,7 @@ function Select({ value: valueProp, defaultValue, options, placeholder = 'Select
|
|
|
329
337
|
disabled,
|
|
330
338
|
error,
|
|
331
339
|
hasValue: currentValue !== null,
|
|
332
|
-
}), "data-testid": testId, ...rest, children: [jsx("span", { className: triggerContentStyle, children: selectedOption ? (jsxs(Fragment, { children: [selectedOption.icon && jsx("span", { children: selectedOption.icon }), jsx("span", { children: selectedOption.label ?? selectedOption.value })] })) : (jsx("span", { children: placeholder })) }), clearable && currentValue !== null && (jsx("span", { className: clearButtonStyle, role: "button", onClick: handleClear, onKeyDown: e => {
|
|
340
|
+
}), style: fullWidth ? { width: '100%' } : undefined, "data-testid": testId, ...rest, children: [jsx("span", { className: triggerContentStyle, children: selectedOption ? (jsxs(Fragment, { children: [selectedOption.icon && jsx("span", { children: selectedOption.icon }), jsx("span", { children: selectedOption.label ?? selectedOption.value })] })) : (jsx("span", { children: placeholder })) }), clearable && currentValue !== null && (jsx("span", { className: clearButtonStyle, role: "button", onClick: handleClear, onKeyDown: e => {
|
|
333
341
|
if (e.key === 'Enter' || e.key === ' ') {
|
|
334
342
|
e.preventDefault();
|
|
335
343
|
e.stopPropagation();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Select.js","sources":["../../../../../../src/components/controls/Select/Select.tsx"],"sourcesContent":[null],"names":[],"mappings":";;;;;;;;;;;;;AA0CA;AAEA;AAGE;AACF;AAEA;;AAIE;AACE;;;;AAGE;;;AAGJ;AACF;AAEA;AAIE;;AAEF;AAEA;AAEA;AACE;AACA;AACA;;AAGF;AAEA;;;;;;;;;;;;;;;;;;;AAmBG;;AA+BD;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;;AAGA;;;;AAQA;;;AAIA;;AAGA;AACA;AACE;AAAiC;AACjC;;;AAIF;;AAMA;AACE;AACA;;AAEA;;AAEA;AAGA;;AAGI;AAEJ;;AAEE;AACA;AACE;;AAEI;AACD;AACH;AACH;AACH;AAEA;AACE;;;;AAGA;AACA;;AAEF;AAEA;;;AAGE;AACA;AACA;AACF;AAEA;;;;AAKI;AACA;;;;;AAQA;;;;AAII;AACF;;;;;;AAOJ;;AAEA;AACE;;;AAKE;;AAEJ;AAEA;AACA;AACE;AACF;AACF;;AAGA;;AAIO;AAIP;AAEI;;AAEA;;;AAIE;AACE;;AAEF;;;AAGA;;AAGI;AAEA;;;;;;AAKJ;AACE;;;;;;;;;AASN;;AAKF;AAEI;;AAEA;AACE;AACA;AACA;;;AAGI;;;AAGJ;;;AAGI;;;;;;AASV;AAEI;AACE;;;;AAIA;;;;AAIA;;;;AAIA;;;;AAIA;;;AAII;AAEA;AACA;AACE;;;;AAIN;;AAEE;;;AAGN;AAIF;;;AAIE;AAIF;AACA;;AAGA;AAEI;AACA;;;;AAGE;;AAEJ;;;AAMA;AACE;;;;AAMF;;;AAGI;AACE;;AAEA;AAEA;AAIM;AACA;AACF;;;;AAON;AACF;;AAGF;AACF;AAEA;AACE;AACA;;AAGE;AACE;;AAEJ;;AAUM;AACA;AACA;;AAIA;;AAEE;;AAEJ;AAEE;;;;AAcR;;;AA6BU;;;AAEA;;;;;AAOF;;;;AAID;AAqBK;;;;;AAKF;AAgBJ;AAkBU;AACA;AACF;AA0BhB;AAEA;;"}
|
|
1
|
+
{"version":3,"file":"Select.js","sources":["src/components/controls/Select/Select.tsx"],"sourcesContent":["'use client';\n\nimport React, {\n useCallback,\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 { cx } from '@/utils/cx';\n\nimport {\n selectContainerStyle,\n triggerRecipe,\n triggerContentStyle,\n chevronRecipe,\n clearButtonStyle,\n dropdownStyle,\n searchInputStyle,\n optionsListStyle,\n optionItemRecipe,\n groupLabelStyle,\n emptyMessageStyle,\n checkmarkStyle,\n} from './Select.css';\n\nimport type {\n SelectOptionItem,\n SelectOptionGroup,\n SelectProps,\n SelectSize,\n} from './Select.types';\n\n// --- Helpers ---\n\nfunction isOptionGroup<T extends string>(\n item: SelectOptionItem<T> | SelectOptionGroup<T>\n): item is SelectOptionGroup<T> {\n return 'options' in item && Array.isArray(item.options);\n}\n\nfunction flattenOptions<T extends string>(\n items: Array<SelectOptionItem<T> | SelectOptionGroup<T>>\n): SelectOptionItem<T>[] {\n const result: SelectOptionItem<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: SelectOptionItem<T>,\n query: string\n): boolean {\n const label = (option.label ?? option.value).toLowerCase();\n return label.includes(query.toLowerCase());\n}\n\n// --- Size maps ---\n\nconst CHEVRON_SIZES: Record<SelectSize, number> = {\n sm: 10,\n md: 12,\n lg: 14,\n};\n\n// --- Component ---\n\n/**\n * Select component for choosing single values from a dropdown list.\n *\n * Supports searchable mode, grouped options, keyboard navigation,\n * clearable state, and multiple visual variants.\n *\n * @example\n * ```tsx\n * <Select\n * label=\"Blend Mode\"\n * options={[\n * { value: 'normal', label: 'Normal' },\n * { value: 'multiply', label: 'Multiply' },\n * { value: 'screen', label: 'Screen' },\n * ]}\n * value={blendMode}\n * onChange={setBlendMode}\n * />\n * ```\n */\nexport function Select<T extends string = string>({\n value: valueProp,\n defaultValue,\n options,\n placeholder = 'Select...',\n searchable = false,\n searchPlaceholder = 'Search...',\n filterFn,\n emptyMessage = 'No results found',\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 fullWidth = false,\n name,\n onChange,\n onOpenChange,\n className,\n style,\n testId,\n ref,\n id: idProp,\n ...rest\n}: SelectProps<T>) {\n const autoId = useId();\n const selectId = idProp ?? autoId;\n const labelId = `${selectId}-label`;\n const helperId = `${selectId}-helper`;\n const listboxId = `${selectId}-listbox`;\n\n const triggerRef = useRef<HTMLButtonElement | null>(null);\n const dropdownRef = useRef<HTMLDivElement>(null);\n const searchRef = useRef<HTMLInputElement>(null);\n\n const [isOpen, setIsOpen] = useState(false);\n const [internalValue, setInternalValue] = useState<T | null>(\n defaultValue ?? null\n );\n const [searchQuery, setSearchQuery] = useState('');\n const [highlightedIndex, setHighlightedIndex] = useState(-1);\n const [dropdownStyleState, setDropdownStyleState] =\n useState<React.CSSProperties>({});\n\n const isControlled = valueProp !== undefined;\n const currentValue = isControlled ? valueProp : internalValue;\n\n // Flatten all options for navigation\n const allOptions = useMemo(() => flattenOptions(options), [options]);\n\n // Filter options when searchable\n const filter = filterFn ?? defaultFilter;\n const filteredOptions = useMemo(() => {\n if (!searchable || !searchQuery) return allOptions;\n return allOptions.filter(opt => filter(opt, searchQuery));\n }, [allOptions, searchable, searchQuery, filter]);\n\n // Find selected option\n const selectedOption = useMemo(\n () => allOptions.find(o => o.value === currentValue) ?? null,\n [allOptions, currentValue]\n );\n\n // Position dropdown\n const updateDropdownPosition = useCallback(() => {\n const trigger = triggerRef.current;\n if (!trigger) return;\n\n const rect = trigger.getBoundingClientRect();\n const spaceBelow = window.innerHeight - rect.bottom;\n const openAbove =\n spaceBelow < maxDropdownHeight + 8 && rect.top > spaceBelow;\n\n // The dropdown sizes to its content (`width: max-content` in CSS). Anchor\n // that growth between a minimum (at least the trigger, and `minDropdownWidth`\n // when set) and a maximum that keeps it inside the viewport so long option\n // labels are never clipped by a narrow trigger.\n const VIEWPORT_MARGIN = 8;\n const minWidth = Math.max(rect.width, minDropdownWidth ?? 0);\n const maxWidth = Math.max(\n minWidth,\n window.innerWidth - rect.left - VIEWPORT_MARGIN\n );\n\n setDropdownStyleState({\n left: rect.left,\n minWidth,\n maxWidth,\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 const open = useCallback(() => {\n if (disabled) return;\n setIsOpen(true);\n setSearchQuery('');\n setHighlightedIndex(-1);\n onOpenChange?.(true);\n // Position is calculated in useEffect after render\n }, [disabled, onOpenChange]);\n\n const close = useCallback(() => {\n setIsOpen(false);\n setSearchQuery('');\n setHighlightedIndex(-1);\n onOpenChange?.(false);\n triggerRef.current?.focus();\n }, [onOpenChange]);\n\n const selectValue = useCallback(\n (val: T | null) => {\n if (!isControlled) {\n setInternalValue(val);\n }\n // `onChange` is typed per `clearable` (it only receives `null` from the\n // clear button, which requires `clearable`). Internally we always call it\n // with `T | null`; the public discriminated union enforces the contract.\n (onChange as ((value: T | null) => void) | undefined)?.(val);\n close();\n },\n [isControlled, onChange, close]\n );\n\n // Update position when open\n useEffect(() => {\n if (isOpen) {\n updateDropdownPosition();\n if (searchable) {\n // Focus search input after portal renders\n requestAnimationFrame(() => {\n searchRef.current?.focus();\n });\n }\n }\n }, [isOpen, updateDropdownPosition, searchable]);\n\n // Close on outside click\n useEffect(() => {\n if (!isOpen) return;\n\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\n document.addEventListener('mousedown', handleClick);\n return () => {\n document.removeEventListener('mousedown', handleClick);\n };\n }, [isOpen, close]);\n\n // Get navigable options (non-disabled)\n const navigableIndices = useMemo(\n () =>\n filteredOptions\n .map((opt, i) => (opt.disabled ? -1 : i))\n .filter(i => i !== -1),\n [filteredOptions]\n );\n\n const navigateHighlight = useCallback(\n (direction: 'next' | 'prev' | 'first' | 'last') => {\n if (navigableIndices.length === 0) return;\n\n let nextIndex: number;\n const currentNavIdx = navigableIndices.indexOf(highlightedIndex);\n\n switch (direction) {\n case 'first':\n nextIndex = navigableIndices[0] ?? 0;\n break;\n case 'last':\n nextIndex = navigableIndices[navigableIndices.length - 1] ?? 0;\n break;\n case 'next':\n if (\n currentNavIdx === -1 ||\n currentNavIdx >= navigableIndices.length - 1\n ) {\n nextIndex = navigableIndices[0] ?? 0;\n } else {\n nextIndex = navigableIndices[currentNavIdx + 1] ?? 0;\n }\n break;\n case 'prev':\n if (currentNavIdx <= 0) {\n nextIndex = navigableIndices[navigableIndices.length - 1] ?? 0;\n } else {\n nextIndex = navigableIndices[currentNavIdx - 1] ?? 0;\n }\n break;\n }\n\n setHighlightedIndex(nextIndex);\n },\n [navigableIndices, highlightedIndex]\n );\n\n // Keyboard handling for trigger\n const handleTriggerKeyDown = useCallback(\n (e: React.KeyboardEvent) => {\n if (disabled) return;\n\n switch (e.key) {\n case 'Enter':\n case ' ':\n case 'ArrowDown':\n e.preventDefault();\n if (!isOpen) {\n open();\n }\n break;\n case 'ArrowUp':\n e.preventDefault();\n if (!isOpen) {\n open();\n }\n break;\n }\n },\n [disabled, isOpen, open]\n );\n\n // Keyboard handling for dropdown\n const handleDropdownKeyDown = useCallback(\n (e: React.KeyboardEvent) => {\n switch (e.key) {\n case 'ArrowDown':\n e.preventDefault();\n navigateHighlight('next');\n break;\n case 'ArrowUp':\n e.preventDefault();\n navigateHighlight('prev');\n break;\n case 'Home':\n e.preventDefault();\n navigateHighlight('first');\n break;\n case 'End':\n e.preventDefault();\n navigateHighlight('last');\n break;\n case 'Enter':\n e.preventDefault();\n if (\n highlightedIndex >= 0 &&\n highlightedIndex < filteredOptions.length\n ) {\n const opt = filteredOptions[highlightedIndex];\n if (opt && !opt.disabled) {\n selectValue(opt.value);\n }\n }\n break;\n case 'Escape':\n e.preventDefault();\n close();\n break;\n }\n },\n [navigateHighlight, highlightedIndex, filteredOptions, selectValue, close]\n );\n\n const handleClear = useCallback(\n (e: React.MouseEvent) => {\n e.stopPropagation();\n selectValue(null);\n },\n [selectValue]\n );\n\n const showHelperText = error && errorMessage ? errorMessage : helperText;\n const chevronSize = CHEVRON_SIZES[size];\n\n // Assign ref\n const setTriggerRef = useCallback(\n (node: HTMLButtonElement | null) => {\n triggerRef.current = node;\n if (typeof ref === 'function') {\n ref(node);\n } else if (ref) {\n ref.current = node;\n }\n },\n [ref]\n );\n\n // Render options list\n const renderOptions = () => {\n if (filteredOptions.length === 0) {\n return <div className={emptyMessageStyle}>{emptyMessage}</div>;\n }\n\n // If options are grouped, render with group headers\n const hasGroups = options.some(isOptionGroup);\n\n if (hasGroups && !searchQuery) {\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\n return (\n <div key={`group-${groupIdx}`} role=\"group\" aria-label={item.label}>\n <div className={groupLabelStyle}>{item.label}</div>\n {groupOptions.map((opt, optIdx) => {\n const idx = startIdx + optIdx;\n return renderOptionItem(opt, idx);\n })}\n </div>\n );\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: SelectOptionItem<T>, index: number) => {\n const isSelected = opt.value === currentValue;\n const isHighlighted = index === highlightedIndex;\n\n const selectOption = () => {\n if (!opt.disabled) {\n selectValue(opt.value);\n }\n };\n\n return (\n <div\n key={opt.value}\n role=\"option\"\n tabIndex={-1}\n aria-selected={isSelected}\n aria-disabled={opt.disabled ?? undefined}\n className={optionItemRecipe({\n highlighted: isHighlighted,\n selected: isSelected,\n disabled: opt.disabled ?? false,\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 (!opt.disabled) {\n setHighlightedIndex(index);\n }\n }}\n >\n {opt.icon && <span>{opt.icon}</span>}\n <span>{opt.label ?? opt.value}</span>\n {isSelected && (\n <span className={checkmarkStyle}>\n <CheckIcon size={10} decorative />\n </span>\n )}\n </div>\n );\n };\n\n return (\n <div\n className={cx(selectContainerStyle, className)}\n style={{ ...(fullWidth ? { width: '100%' } : null), ...style }}\n >\n {label && (\n <FormLabel\n id={labelId}\n htmlFor={selectId}\n required={required}\n disabled={disabled}\n >\n {label}\n </FormLabel>\n )}\n\n <button\n ref={setTriggerRef}\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 hasValue: currentValue !== null,\n })}\n style={fullWidth ? { width: '100%' } : undefined}\n data-testid={testId}\n {...rest}\n >\n <span className={triggerContentStyle}>\n {selectedOption ? (\n <>\n {selectedOption.icon && <span>{selectedOption.icon}</span>}\n <span>{selectedOption.label ?? selectedOption.value}</span>\n </>\n ) : (\n <span>{placeholder}</span>\n )}\n </span>\n\n {clearable && currentValue !== null && (\n <span\n className={clearButtonStyle}\n role=\"button\"\n onClick={handleClear}\n onKeyDown={e => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n e.stopPropagation();\n selectValue(null);\n }\n }}\n aria-label=\"Clear selection\"\n tabIndex={-1}\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={currentValue ?? ''} />}\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={dropdownStyleState}\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 setHighlightedIndex(-1);\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 {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\nSelect.displayName = 'Select';\n"],"names":[],"mappings":";;;;;;;;;;;;;AA0CA;AAEA;AAGE;AACF;AAEA;;AAIE;AACE;;;;AAGE;;;AAGJ;AACF;AAEA;AAIE;;AAEF;AAEA;AAEA;AACE;AACA;AACA;;AAGF;AAEA;;;;;;;;;;;;;;;;;;;AAmBG;;AAgCD;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;;AAGA;;;;AAQA;;;AAIA;;AAGA;AACA;AACE;AAAiC;AACjC;;;AAIF;;AAMA;AACE;AACA;;AAEA;;AAEA;;;;;;AAQA;AACA;AAKA;;;;AAIE;AACE;;AAEI;AACD;AACH;AACH;AACH;AAEA;AACE;;;;AAGA;AACA;;AAEF;AAEA;;;AAGE;AACA;AACA;AACF;AAEA;;;;;;;AAQK;AACD;;;;;AAQA;;;;AAII;AACF;;;;;;AAOJ;;AAEA;AACE;;;AAKE;;AAEJ;AAEA;AACA;AACE;AACF;AACF;;AAGA;;AAIO;AAIP;AAEI;;AAEA;;;AAIE;AACE;;AAEF;;;AAGA;;AAGI;AAEA;;;;;;AAKJ;AACE;;;;;;;;;AASN;;AAKF;AAEI;;AAEA;AACE;AACA;AACA;;;AAGI;;;AAGJ;;;AAGI;;;;;;AASV;AAEI;AACE;;;;AAIA;;;;AAIA;;;;AAIA;;;;AAIA;;;AAII;AAEA;AACA;AACE;;;;AAIN;;AAEE;;;AAGN;AAIF;;;AAIE;AAIF;AACA;;AAGA;AAEI;AACA;;;;AAGE;;AAEJ;;;AAMA;AACE;;;;AAMF;;;AAGI;AACE;;AAEA;AAEA;AAIM;AACA;AACF;;;;AAON;AACF;;AAGF;AACF;AAEA;AACE;AACA;;AAGE;AACE;;AAEJ;;AAUM;AACA;AACA;;AAIA;;AAEE;;AAEJ;AAEE;;;;AAcR;;;AAgCU;;;AAEA;;;;;AAOF;;;;AAID;AAsBK;;;;;AAKF;AAgBJ;AAkBU;AACA;AACF;AA0BhB;AAEA;;"}
|
|
@@ -1,17 +1,16 @@
|
|
|
1
|
-
import './../../../assets/src/components/controls/Slider/Slider.css.ts.vanilla-
|
|
1
|
+
import './../../../assets/src/components/controls/Slider/Slider.css.ts.vanilla-CLf_RC0A.css';
|
|
2
2
|
import { createRuntimeFn } from '@vanilla-extract/recipes/createRuntimeFn';
|
|
3
3
|
|
|
4
4
|
var fillPercentageVar = 'var(--fillPercentageVar__t8kxxs0)';
|
|
5
|
-
var fillRecipe = createRuntimeFn({defaultClassName:'
|
|
6
|
-
var sliderContainerRecipe = createRuntimeFn({defaultClassName:'
|
|
7
|
-
var
|
|
5
|
+
var fillRecipe = createRuntimeFn({defaultClassName:'Slider_fillRecipe__t8kxxsc',variantClassNames:{error:{true:'Slider_fillRecipe_error_true__t8kxxsd',false:'Slider_fillRecipe_error_false__t8kxxse'},isDragging:{true:'Slider_fillRecipe_isDragging_true__t8kxxsf',false:'Slider_fillRecipe_isDragging_false__t8kxxsg'}},defaultVariants:{error:false,isDragging:false},compoundVariants:[]});
|
|
6
|
+
var sliderContainerRecipe = createRuntimeFn({defaultClassName:'Slider_sliderContainerRecipe__t8kxxs2',variantClassNames:{disabled:{true:'Slider_sliderContainerRecipe_disabled_true__t8kxxs3',false:'Slider_sliderContainerRecipe_disabled_false__t8kxxs4'}},defaultVariants:{disabled:false},compoundVariants:[]});
|
|
7
|
+
var sliderTooltipRecipe = createRuntimeFn({defaultClassName:'Slider_sliderTooltipRecipe__t8kxxsr',variantClassNames:{placement:{top:'Slider_sliderTooltipRecipe_placement_top__t8kxxss',bottom:'Slider_sliderTooltipRecipe_placement_bottom__t8kxxst'}},defaultVariants:{placement:'top'},compoundVariants:[]});
|
|
8
|
+
var sliderWrapperStyle = 'Slider_sliderWrapperStyle__t8kxxs5';
|
|
8
9
|
var thumbPercentageVar = 'var(--thumbPercentageVar__t8kxxs1)';
|
|
9
|
-
var thumbRecipe = createRuntimeFn({defaultClassName:'
|
|
10
|
-
var tickStyle = '
|
|
11
|
-
var ticksStyle = '
|
|
12
|
-
var
|
|
13
|
-
var tooltipRecipe = createRuntimeFn({defaultClassName:'Slider_tooltipRecipe__t8kxxss',variantClassNames:{visible:{true:'Slider_tooltipRecipe_visible_true__t8kxxst',false:'Slider_tooltipRecipe_visible_false__t8kxxsu'}},defaultVariants:{visible:false},compoundVariants:[]});
|
|
14
|
-
var trackRecipe = createRuntimeFn({defaultClassName:'Slider_trackRecipe__t8kxxs7',variantClassNames:{size:{sm:'Slider_trackRecipe_size_sm__t8kxxs8',md:'Slider_trackRecipe_size_md__t8kxxs9',lg:'Slider_trackRecipe_size_lg__t8kxxsa'},error:{true:'Slider_trackRecipe_error_true__t8kxxsb',false:'Slider_trackRecipe_error_false__t8kxxsc'}},defaultVariants:{size:'md',error:false},compoundVariants:[]});
|
|
10
|
+
var thumbRecipe = createRuntimeFn({defaultClassName:'Slider_thumbRecipe__t8kxxsh',variantClassNames:{size:{sm:'Slider_thumbRecipe_size_sm__t8kxxsi',md:'Slider_thumbRecipe_size_md__t8kxxsj',lg:'Slider_thumbRecipe_size_lg__t8kxxsk'},error:{true:'Slider_thumbRecipe_error_true__t8kxxsl',false:'Slider_thumbRecipe_error_false__t8kxxsm'},isDragging:{true:'Slider_thumbRecipe_isDragging_true__t8kxxsn',false:'Slider_thumbRecipe_isDragging_false__t8kxxso'}},defaultVariants:{size:'md',error:false,isDragging:false},compoundVariants:[]});
|
|
11
|
+
var tickStyle = 'Slider_tickStyle__t8kxxsq';
|
|
12
|
+
var ticksStyle = 'Slider_ticksStyle__t8kxxsp';
|
|
13
|
+
var trackRecipe = createRuntimeFn({defaultClassName:'Slider_trackRecipe__t8kxxs6',variantClassNames:{size:{sm:'Slider_trackRecipe_size_sm__t8kxxs7',md:'Slider_trackRecipe_size_md__t8kxxs8',lg:'Slider_trackRecipe_size_lg__t8kxxs9'},error:{true:'Slider_trackRecipe_error_true__t8kxxsa',false:'Slider_trackRecipe_error_false__t8kxxsb'}},defaultVariants:{size:'md',error:false},compoundVariants:[]});
|
|
15
14
|
|
|
16
|
-
export { fillPercentageVar, fillRecipe, sliderContainerRecipe, sliderWrapperStyle, thumbPercentageVar, thumbRecipe, tickStyle, ticksStyle,
|
|
15
|
+
export { fillPercentageVar, fillRecipe, sliderContainerRecipe, sliderTooltipRecipe, sliderWrapperStyle, thumbPercentageVar, thumbRecipe, tickStyle, ticksStyle, trackRecipe };
|
|
17
16
|
//# sourceMappingURL=Slider.css.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Slider.css.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"Slider.css.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;"}
|
|
@@ -2,12 +2,13 @@
|
|
|
2
2
|
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
3
3
|
import { assignInlineVars } from '@vanilla-extract/dynamic';
|
|
4
4
|
import React, { useState, useRef, useCallback, useEffect } from 'react';
|
|
5
|
+
import { createPortal } from 'react-dom';
|
|
5
6
|
import { FormHelperText } from '../../form/FormHelperText.js';
|
|
6
7
|
import { FormLabel } from '../../form/FormLabel.js';
|
|
7
8
|
import { InputWrapper } from '../../form/InputWrapper.js';
|
|
8
9
|
import { useKeyboardContext } from '../../../context/KeyboardContext.js';
|
|
9
10
|
import { cx } from '../../../utils/cx.js';
|
|
10
|
-
import { sliderWrapperStyle, fillPercentageVar, fillRecipe, trackRecipe, thumbPercentageVar, thumbRecipe, ticksStyle, tickStyle,
|
|
11
|
+
import { sliderWrapperStyle, fillPercentageVar, fillRecipe, trackRecipe, thumbPercentageVar, thumbRecipe, ticksStyle, tickStyle, sliderTooltipRecipe, sliderContainerRecipe } from './Slider.css.js';
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* Clamps a value between min and max bounds
|
|
@@ -73,10 +74,13 @@ function roundToPrecision(value, precision) {
|
|
|
73
74
|
* />
|
|
74
75
|
* ```
|
|
75
76
|
*/
|
|
76
|
-
const Slider = ({ value, onChange, min = 0, max = 100, step = 1, precisionStep, largeStep, precision = 2, size = 'md', disabled = false, readOnly = false, label, helperText, errorMessage, error = false, required = false, unit, formatValue, showTooltip = true, showTicks = false, tickCount = 5, className, testId, onFocus, onBlur, onKeyDown, ref, ...props }) => {
|
|
77
|
+
const Slider = ({ value, onChange, min = 0, max = 100, step = 1, precisionStep, largeStep, precision = 2, size = 'md', disabled = false, readOnly = false, label, helperText, errorMessage, error = false, required = false, unit, formatValue, showTooltip = true, tooltipPlacement = 'top', showTicks = false, tickCount = 5, className, testId, onFocus, onBlur, onKeyDown, ref, ...props }) => {
|
|
77
78
|
const [isDragging, setIsDragging] = useState(false);
|
|
78
79
|
const [isFocused, setIsFocused] = useState(false);
|
|
79
80
|
const [showTooltipState, setShowTooltipState] = useState(false);
|
|
81
|
+
// Viewport coordinates of the portaled value tooltip (null until a drag
|
|
82
|
+
// starts). Updated only while dragging so the portal stays off the idle path.
|
|
83
|
+
const [tooltipPos, setTooltipPos] = useState(null);
|
|
80
84
|
const sliderRef = useRef(null);
|
|
81
85
|
const trackRef = useRef(null);
|
|
82
86
|
const dragStartRef = useRef(null);
|
|
@@ -135,6 +139,22 @@ const Slider = ({ value, onChange, min = 0, max = 100, step = 1, precisionStep,
|
|
|
135
139
|
const steps = Math.round((newValue - min) / currentStep);
|
|
136
140
|
return min + steps * currentStep;
|
|
137
141
|
}, [min, max, value, getStepSize]);
|
|
142
|
+
/**
|
|
143
|
+
* Computes the portaled tooltip's viewport position for a (snapped) value,
|
|
144
|
+
* anchored to the thumb. Reads the same track rect the drag math already
|
|
145
|
+
* reads, so it adds no extra layout pass per frame.
|
|
146
|
+
*/
|
|
147
|
+
const updateTooltipPos = useCallback((v) => {
|
|
148
|
+
const track = trackRef.current;
|
|
149
|
+
if (!track)
|
|
150
|
+
return;
|
|
151
|
+
const rect = track.getBoundingClientRect();
|
|
152
|
+
const pct = clamp((v - min) / (max - min), 0, 1);
|
|
153
|
+
setTooltipPos({
|
|
154
|
+
left: rect.left + pct * rect.width,
|
|
155
|
+
top: tooltipPlacement === 'bottom' ? rect.bottom : rect.top,
|
|
156
|
+
});
|
|
157
|
+
}, [min, max, tooltipPlacement]);
|
|
138
158
|
/**
|
|
139
159
|
* Handle mouse down on track or thumb
|
|
140
160
|
*/
|
|
@@ -147,11 +167,20 @@ const Slider = ({ value, onChange, min = 0, max = 100, step = 1, precisionStep,
|
|
|
147
167
|
// Use the current step size (depends on keyboard modifiers)
|
|
148
168
|
const newValue = positionToValue(event.clientX);
|
|
149
169
|
applyValue(newValue);
|
|
170
|
+
if (showTooltip)
|
|
171
|
+
updateTooltipPos(newValue);
|
|
150
172
|
dragStartRef.current = {
|
|
151
173
|
startX: event.clientX,
|
|
152
174
|
startValue: newValue,
|
|
153
175
|
};
|
|
154
|
-
}, [
|
|
176
|
+
}, [
|
|
177
|
+
disabled,
|
|
178
|
+
readOnly,
|
|
179
|
+
positionToValue,
|
|
180
|
+
applyValue,
|
|
181
|
+
showTooltip,
|
|
182
|
+
updateTooltipPos,
|
|
183
|
+
]);
|
|
155
184
|
/**
|
|
156
185
|
* Handle global mouse move during drag
|
|
157
186
|
*/
|
|
@@ -163,8 +192,10 @@ const Slider = ({ value, onChange, min = 0, max = 100, step = 1, precisionStep,
|
|
|
163
192
|
// Use the current step size (depends on keyboard modifiers)
|
|
164
193
|
const newValue = positionToValue(event.clientX);
|
|
165
194
|
applyValue(newValue);
|
|
195
|
+
if (showTooltip)
|
|
196
|
+
updateTooltipPos(newValue);
|
|
166
197
|
});
|
|
167
|
-
}, [isDragging, positionToValue, applyValue]);
|
|
198
|
+
}, [isDragging, positionToValue, applyValue, showTooltip, updateTooltipPos]);
|
|
168
199
|
/**
|
|
169
200
|
* Handle global mouse up to end drag
|
|
170
201
|
*/
|
|
@@ -254,9 +285,11 @@ const Slider = ({ value, onChange, min = 0, max = 100, step = 1, precisionStep,
|
|
|
254
285
|
[fillPercentageVar]: `${percentage}%`,
|
|
255
286
|
}) }) }), jsx("div", { className: thumbRecipe({ size, error, isDragging }), style: assignInlineVars({
|
|
256
287
|
[thumbPercentageVar]: `${percentage}%`,
|
|
257
|
-
}) }), showTicks && (jsx("div", { className: ticksStyle, children: ticks.map(i => (jsx("div", { className: tickStyle }, i))) })), showTooltip &&
|
|
258
|
-
|
|
259
|
-
|
|
288
|
+
}) }), showTicks && (jsx("div", { className: ticksStyle, children: ticks.map(i => (jsx("div", { className: tickStyle }, i))) })), showTooltip &&
|
|
289
|
+
showTooltipState &&
|
|
290
|
+
tooltipPos &&
|
|
291
|
+
typeof document !== 'undefined' &&
|
|
292
|
+
createPortal(jsx("div", { className: sliderTooltipRecipe({ placement: tooltipPlacement }), style: { left: tooltipPos.left, top: tooltipPos.top }, role: "presentation", "data-testid": testId ? `${testId}-tooltip` : undefined, children: displayValue }), document.body)] }) }), (helperText ?? (error && errorMessage)) && (jsx(FormHelperText, { error: error, children: error && errorMessage ? errorMessage : helperText }))] }));
|
|
260
293
|
};
|
|
261
294
|
Slider.displayName = 'Slider';
|
|
262
295
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Slider.js","sources":["../../../../../../src/components/controls/Slider/Slider.tsx"],"sourcesContent":[null],"names":[],"mappings":";;;;;;;;;;;AA+KA;;AAEG;AACH;AACE;AACF;AAEA;;AAEG;AACH;;AAC+B;;;AAG/B;AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgDG;AACI;;;;AAkCL;AACA;AACA;AAIA;;AAGA;AACA;AACA;;AAGA;;AACuB;AACrB;AAAyC;AACzC;AACF;AACE;AACA;AACA;;;;AAID;;;AAIC;AACA;;AAEE;AAEJ;;AAEG;AACH;;;;;;;AASI;;;AAGF;AAIF;;AAEG;AACH;;AAE2B;;;;;AAOvB;;AAGA;AACA;;AAKJ;;AAEG;AACH;;;;;;;;;;;AAcM;;AAEJ;AAIF;;AAEG;AACH;AAEI;;;;;;;AAMA;;AAKJ;;AAEG;AACH;;;AAGE;;AAGF;;AAEG;AACH;;;AAII;;AAGA;AACE;AACA;AACE;;AAEF;AACA;AACE;;AAEF;;;AAGA;;;AAGA;AACE;;AAEF;AACE;;AAEF;;;;;;AAQF;AACF;;;;;;;;;;AAWC;AAGH;;AAEG;AACH;;AAGI;AACF;AAIF;;AAGI;AACF;;;AAMA;;AAEA;AACA;AAEA;AACE;AACA;AACF;;;AAIF;AAEA;AAEA;AAmBQ;AACA;AACA;AACA;;AAyBM;;AAQF;;AAgBE;AACD;AAef;AAEA;;"}
|
|
1
|
+
{"version":3,"file":"Slider.js","sources":["src/components/controls/Slider/Slider.tsx"],"sourcesContent":["'use client';\n\n// src/components/controls/Slider/Slider.tsx\nimport { assignInlineVars } from '@vanilla-extract/dynamic';\nimport React, { useRef, useCallback, useState, useEffect } from 'react';\nimport { createPortal } from 'react-dom';\n\nimport { FormHelperText } from '@/components/form/FormHelperText';\nimport { FormLabel } from '@/components/form/FormLabel';\nimport { InputWrapper } from '@/components/form/InputWrapper';\nimport { useKeyboardContext } from '@/context/KeyboardContext';\nimport { cx } from '@/utils/cx';\n\nimport {\n sliderContainerRecipe,\n sliderWrapperStyle,\n trackRecipe,\n fillRecipe,\n fillPercentageVar,\n thumbRecipe,\n thumbPercentageVar,\n ticksStyle,\n tickStyle,\n sliderTooltipRecipe,\n} from './Slider.css';\n\nimport type { BaseComponent, Size } from '@/types/common';\nimport type { Prettify } from '@/types/utilities';\n\n/**\n * Props specific to Slider component\n */\nexport interface SliderBaseProps extends Omit<BaseComponent, 'onChange'> {\n /**\n * Current numeric value\n */\n value: number;\n\n /**\n * Callback when value changes\n */\n onChange: (value: number) => void;\n\n /**\n * Minimum allowed value\n * @default 0\n */\n min?: number;\n\n /**\n * Maximum allowed value\n * @default 100\n */\n max?: number;\n\n /**\n * Step size for value increments\n * @default 1\n */\n step?: number;\n\n /**\n * Step size when Shift is held (precision mode)\n * @default step / 10\n */\n precisionStep?: number;\n\n /**\n * Step size when Ctrl is held (large steps)\n * @default step * 10\n */\n largeStep?: number;\n\n /**\n * Number of decimal places to round to\n */\n precision?: number;\n\n /**\n * Slider size using standard library sizing\n * - `sm`: Compact for toolbars\n * - `md`: Standard for forms\n * - `lg`: Prominent sliders\n * @default \"md\"\n */\n size?: Size;\n\n /**\n * Whether the slider is disabled\n * @default false\n */\n disabled?: boolean;\n\n /**\n * Whether the slider is read-only\n * @default false\n */\n readOnly?: boolean;\n\n /**\n * Slider label\n */\n label?: string;\n\n /**\n * Helper text displayed below the slider\n */\n helperText?: string;\n\n /**\n * Error message displayed when error is true\n */\n errorMessage?: string;\n\n /**\n * Whether the slider has an error state\n * @default false\n */\n error?: boolean;\n\n /**\n * Whether the slider is required\n * @default false\n */\n required?: boolean;\n\n /**\n * Unit suffix to display (e.g., \"px\", \"%\", \"°\")\n */\n unit?: string;\n\n /**\n * Format value for display\n */\n formatValue?: (value: number) => string;\n\n /**\n * Show value tooltip while dragging\n * @default true\n */\n showTooltip?: boolean;\n\n /**\n * Placement of the value tooltip relative to the thumb. The tooltip is\n * portaled to `document.body`, so it is never clipped by an ancestor's\n * `overflow`.\n * @default \"top\"\n */\n tooltipPlacement?: 'top' | 'bottom';\n\n /**\n * Show tick marks along the track\n * @default false\n */\n showTicks?: boolean;\n\n /**\n * Number of tick marks to show (when showTicks is true)\n * @default 5\n */\n tickCount?: number;\n\n /**\n * Focus event handler\n */\n onFocus?: (event: React.FocusEvent<HTMLElement>) => void;\n\n /**\n * Blur event handler\n */\n onBlur?: (event: React.FocusEvent<HTMLElement>) => void;\n\n /**\n * Key down event handler\n */\n onKeyDown?: (event: React.KeyboardEvent<HTMLElement>) => void;\n}\n\n/**\n * Props for the Slider component with prettified type for better IntelliSense\n */\nexport type SliderProps = Prettify<SliderBaseProps>;\n\n/**\n * Clamps a value between min and max bounds\n */\nfunction clamp(value: number, min: number, max: number): number {\n return Math.min(Math.max(value, min), max);\n}\n\n/**\n * Rounds a number to specified decimal places\n */\nfunction roundToPrecision(value: number, precision?: number): number {\n if (precision === undefined) return value;\n const factor = Math.pow(10, precision);\n return Math.round(value * factor) / factor;\n}\n\n/**\n * A professional slider component with drag interaction and keyboard support.\n *\n * Features:\n * - Smooth drag interaction with visual feedback\n * - Keyboard navigation with arrow keys\n * - Modifier key support: Ctrl (large steps), Shift (precision)\n * - Optional tick marks and value tooltip\n * - Comprehensive theming and size variants\n * - Accessible with proper ARIA attributes\n *\n * @example\n * ```tsx\n * // Basic slider\n * <Slider\n * value={opacity}\n * onChange={setOpacity}\n * min={0}\n * max={1}\n * step={0.1}\n * unit=\"%\"\n * />\n *\n * // Slider with ticks and custom formatting\n * <Slider\n * value={rotation}\n * onChange={setRotation}\n * min={0}\n * max={360}\n * step={15}\n * unit=\"°\"\n * showTicks\n * tickCount={8}\n * formatValue={(v) => `${v}°`}\n * />\n *\n * // Precision slider for fine control\n * <Slider\n * value={scale}\n * onChange={setScale}\n * min={0.1}\n * max={5}\n * step={0.1}\n * precisionStep={0.01}\n * largeStep={1}\n * precision={2}\n * />\n * ```\n */\nexport const Slider: React.FC<SliderProps> = ({\n value,\n onChange,\n min = 0,\n max = 100,\n step = 1,\n precisionStep,\n largeStep,\n precision = 2,\n size = 'md',\n disabled = false,\n readOnly = false,\n label,\n helperText,\n errorMessage,\n error = false,\n required = false,\n unit,\n formatValue,\n showTooltip = true,\n tooltipPlacement = 'top',\n showTicks = false,\n tickCount = 5,\n className,\n testId,\n onFocus,\n onBlur,\n onKeyDown,\n ref,\n ...props\n}) => {\n const [isDragging, setIsDragging] = useState(false);\n const [isFocused, setIsFocused] = useState(false);\n const [showTooltipState, setShowTooltipState] = useState(false);\n // Viewport coordinates of the portaled value tooltip (null until a drag\n // starts). Updated only while dragging so the portal stays off the idle path.\n const [tooltipPos, setTooltipPos] = useState<{\n left: number;\n top: number;\n } | null>(null);\n\n const sliderRef = useRef<HTMLDivElement>(null);\n const trackRef = useRef<HTMLDivElement>(null);\n const dragStartRef = useRef<{ startX: number; startValue: number } | null>(\n null\n );\n\n const { modifiers } = useKeyboardContext();\n\n // Calculate derived values\n const effectivePrecisionStep = precisionStep ?? step / 10;\n const effectiveLargeStep = largeStep ?? step * 10;\n const percentage = ((value - min) / (max - min)) * 100;\n const clampedValue = clamp(value, min, max);\n\n const getStepSize = useCallback((): number => {\n if (modifiers.shift) return effectivePrecisionStep;\n if (modifiers.control || modifiers.meta) return effectiveLargeStep;\n return step;\n }, [\n modifiers.shift,\n modifiers.control,\n modifiers.meta,\n effectivePrecisionStep,\n effectiveLargeStep,\n step,\n ]);\n\n // Format display value with consistent precision to prevent tooltip size changes\n const displayValue = formatValue\n ? formatValue(clampedValue)\n : unit\n ? `${clampedValue.toFixed(precision)}${unit}`\n : clampedValue.toFixed(precision);\n\n /**\n * Applies a new value with proper bounds checking and rounding\n */\n const applyValue = useCallback(\n (newValue: number): void => {\n if (disabled || readOnly) return;\n\n if (newValue === value) return;\n\n const rounded = roundToPrecision(newValue, precision);\n const clamped = clamp(rounded, min, max);\n\n if (clamped !== value) {\n onChange(clamped);\n }\n },\n [disabled, readOnly, precision, min, max, value, onChange]\n );\n\n /**\n * Converts mouse position to value\n */\n const positionToValue = useCallback(\n (clientX: number): number => {\n if (!trackRef.current) return value;\n\n const rect = trackRef.current.getBoundingClientRect();\n const percentage = clamp((clientX - rect.left) / rect.width, 0, 1);\n const newValue = min + percentage * (max - min);\n\n // Use the current step size (depends on keyboard modifiers)\n const currentStep = getStepSize();\n\n // Snap to step increments\n const steps = Math.round((newValue - min) / currentStep);\n return min + steps * currentStep;\n },\n [min, max, value, getStepSize]\n );\n\n /**\n * Computes the portaled tooltip's viewport position for a (snapped) value,\n * anchored to the thumb. Reads the same track rect the drag math already\n * reads, so it adds no extra layout pass per frame.\n */\n const updateTooltipPos = useCallback(\n (v: number): void => {\n const track = trackRef.current;\n if (!track) return;\n const rect = track.getBoundingClientRect();\n const pct = clamp((v - min) / (max - min), 0, 1);\n setTooltipPos({\n left: rect.left + pct * rect.width,\n top: tooltipPlacement === 'bottom' ? rect.bottom : rect.top,\n });\n },\n [min, max, tooltipPlacement]\n );\n\n /**\n * Handle mouse down on track or thumb\n */\n const handleMouseDown = useCallback(\n (event: React.MouseEvent) => {\n if (disabled || readOnly) return;\n\n event.preventDefault();\n setIsDragging(true);\n setShowTooltipState(showTooltip);\n\n // Use the current step size (depends on keyboard modifiers)\n const newValue = positionToValue(event.clientX);\n applyValue(newValue);\n if (showTooltip) updateTooltipPos(newValue);\n\n dragStartRef.current = {\n startX: event.clientX,\n startValue: newValue,\n };\n },\n [\n disabled,\n readOnly,\n positionToValue,\n applyValue,\n showTooltip,\n updateTooltipPos,\n ]\n );\n\n /**\n * Handle global mouse move during drag\n */\n const handleMouseMove = useCallback(\n (event: MouseEvent) => {\n if (!isDragging || !dragStartRef.current) return;\n // Use requestAnimationFrame for smoother performance\n requestAnimationFrame(() => {\n // Use the current step size (depends on keyboard modifiers)\n const newValue = positionToValue(event.clientX);\n applyValue(newValue);\n if (showTooltip) updateTooltipPos(newValue);\n });\n },\n [isDragging, positionToValue, applyValue, showTooltip, updateTooltipPos]\n );\n\n /**\n * Handle global mouse up to end drag\n */\n const handleMouseUp = useCallback(() => {\n setIsDragging(false);\n setShowTooltipState(false);\n dragStartRef.current = null;\n }, []);\n\n /**\n * Handle keyboard navigation\n */\n const handleKeyDown = useCallback(\n (event: React.KeyboardEvent<HTMLElement>) => {\n if (disabled || readOnly) return;\n\n const currentStep = getStepSize();\n let handled = true;\n\n switch (event.key) {\n case 'ArrowRight':\n case 'ArrowUp':\n applyValue(value + currentStep);\n break;\n case 'ArrowLeft':\n case 'ArrowDown':\n applyValue(value - currentStep);\n break;\n case 'Home':\n applyValue(min);\n break;\n case 'End':\n applyValue(max);\n break;\n case 'PageUp':\n applyValue(value + effectiveLargeStep);\n break;\n case 'PageDown':\n applyValue(value - effectiveLargeStep);\n break;\n default:\n handled = false;\n }\n\n if (handled) {\n event.preventDefault();\n }\n\n onKeyDown?.(event);\n },\n [\n disabled,\n readOnly,\n getStepSize,\n applyValue,\n value,\n min,\n max,\n effectiveLargeStep,\n onKeyDown,\n ]\n );\n\n /**\n * Handle focus events\n */\n const handleFocus = useCallback(\n (event: React.FocusEvent<HTMLElement>) => {\n setIsFocused(true);\n onFocus?.(event);\n },\n [onFocus]\n );\n\n const handleBlur = useCallback(\n (event: React.FocusEvent<HTMLElement>) => {\n setIsFocused(false);\n onBlur?.(event);\n },\n [onBlur]\n );\n\n // Global mouse events for dragging\n useEffect(() => {\n if (!isDragging) return;\n\n document.addEventListener('mousemove', handleMouseMove);\n document.addEventListener('mouseup', handleMouseUp);\n\n return () => {\n document.removeEventListener('mousemove', handleMouseMove);\n document.removeEventListener('mouseup', handleMouseUp);\n };\n }, [isDragging, handleMouseMove, handleMouseUp]);\n\n // Generate tick marks\n const ticks = showTicks ? Array.from({ length: tickCount }, (_, i) => i) : [];\n\n const sliderId = React.useId();\n\n return (\n <div\n ref={ref}\n className={cx(sliderContainerRecipe({ disabled }), className)}\n data-testid={testId}\n {...props}\n >\n {label && (\n <FormLabel htmlFor={sliderId} disabled={disabled} required={required}>\n {label}\n </FormLabel>\n )}\n\n <InputWrapper\n size={size}\n error={error}\n disabled={disabled}\n focused={isFocused}\n style={{\n border: 'none',\n background: 'transparent',\n padding: 0,\n height: 5,\n }}\n >\n <div\n ref={sliderRef}\n className={sliderWrapperStyle}\n onMouseDown={handleMouseDown}\n tabIndex={disabled ? -1 : 0}\n role=\"slider\"\n aria-valuemin={min}\n aria-valuemax={max}\n aria-valuenow={clampedValue}\n aria-valuetext={displayValue}\n aria-disabled={disabled}\n aria-readonly={readOnly}\n aria-invalid={error}\n aria-labelledby={label ? sliderId : undefined}\n onFocus={handleFocus}\n onBlur={handleBlur}\n onKeyDown={handleKeyDown}\n >\n <div ref={trackRef} className={trackRecipe({ size, error })}>\n <div\n className={fillRecipe({ error, isDragging })}\n style={assignInlineVars({\n [fillPercentageVar]: `${percentage}%`,\n })}\n />\n </div>\n\n <div\n className={thumbRecipe({ size, error, isDragging })}\n style={assignInlineVars({\n [thumbPercentageVar]: `${percentage}%`,\n })}\n />\n\n {showTicks && (\n <div className={ticksStyle}>\n {ticks.map(i => (\n <div key={i} className={tickStyle} />\n ))}\n </div>\n )}\n\n {showTooltip &&\n showTooltipState &&\n tooltipPos &&\n typeof document !== 'undefined' &&\n createPortal(\n <div\n className={sliderTooltipRecipe({ placement: tooltipPlacement })}\n style={{ left: tooltipPos.left, top: tooltipPos.top }}\n role=\"presentation\"\n data-testid={testId ? `${testId}-tooltip` : undefined}\n >\n {displayValue}\n </div>,\n document.body\n )}\n </div>\n </InputWrapper>\n\n {(helperText ?? (error && errorMessage)) && (\n <FormHelperText error={error}>\n {error && errorMessage ? errorMessage : helperText}\n </FormHelperText>\n )}\n </div>\n );\n};\n\nSlider.displayName = 'Slider';\n"],"names":[],"mappings":";;;;;;;;;;;;AAuLA;;AAEG;AACH;AACE;AACF;AAEA;;AAEG;AACH;;AAC+B;;;AAG/B;AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgDG;;;;;;;;AA0CD;AACA;AACA;AAIA;;AAGA;AACA;AACA;;AAGA;;AACuB;AACrB;AAAyC;AACzC;AACF;AACE;AACA;AACA;;;;AAID;;;AAIC;AACA;;AAEE;AAEJ;;AAEG;AACH;;;;;;;AASI;;;AAGF;AAIF;;AAEG;AACH;;AAE2B;;;;;AAOvB;;AAGA;AACA;;AAKJ;;;;AAIG;AACH;AAEI;AACA;;AACA;;AAEA;;AAEE;AACD;;AAKL;;AAEG;AACH;;;;;;;;;AAWI;;;;AAIE;;AAEJ;;;;;;;AAQC;AAGH;;AAEG;AACH;AAEI;;;;;;;AAME;;AACF;AACF;AAIF;;AAEG;AACH;;;AAGE;;AAGF;;AAEG;AACH;;;AAII;;AAGA;AACE;AACA;AACE;;AAEF;AACA;AACE;;AAEF;;;AAGA;;;AAGA;AACE;;AAEF;AACE;;AAEF;;;;;;AAQF;AACF;;;;;;;;;;AAWC;AAGH;;AAEG;AACH;;AAGI;AACF;AAIF;;AAGI;AACF;;;AAMA;;AAEA;AACA;AAEA;AACE;AACA;AACF;;;AAIF;AAEA;AAEA;AAmBQ;AACA;AACA;AACA;;AAyBM;;AAQF;AACD;;;;AAeD;AAqBZ;AAEA;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TagInput.js","sources":["../../../../../../src/components/controls/TagInput/TagInput.tsx"],"sourcesContent":[null],"names":[],"mappings":";;;;;;;;;;;AA0BA;AACE;AACA;AACA;AACA;;AAGF;AAEA;AACE;AACF;AAEA;;;;;;;;;;;;;;AAcG;;AAiCD;AACA;AACA;AACA;AAEA;;;;AAIE;AACD;;;AAKD;AACA;;;AAKA;AACA;AAEA;;AAOI;AACA;AACE;AAAoB;AACpB;;;AAGA;AACA;;;AAGA;AACA;;;;AAIA;AACE;AACA;;AAEF;AACE;AACA;;;AAGJ;AACF;AAIF;;AAGI;AAAW;;AAEX;;AAKJ;;;;;AAKE;AAIF;AAEI;;;;;;;AAME;;;;;AAGO;;;;;;AAOT;;AAEE;;AAEJ;AAIF;AAEI;;;;;;;;;;;;AAeA;AACE;;;AAEA;;;;;;;AAQN;;;;;;;AAOA;;;;;AAMM;;;AAMN;;;AAGE;;AAEE;;;;;;;;AAaF;AAqBF;AAEA;;;;;;;;;;AAwDU;AAeZ;AAEA;;"}
|
|
1
|
+
{"version":3,"file":"TagInput.js","sources":["src/components/controls/TagInput/TagInput.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 { CloseIcon } from '@/components/Icons/CloseIcon';\nimport { useControlledState } from '@/hooks/useControlledState';\nimport { useMergedRef } from '@/hooks/useMergedRef';\nimport { cx } from '@/utils/cx';\n\nimport {\n containerStyle,\n inputStyle,\n tagLabelStyle,\n tagRecipe,\n tagRemoveStyle,\n wrapperRecipe,\n} from './TagInput.css';\n\nimport type {\n TagInputProps,\n TagInputRenderTagState,\n TagInputSeparator,\n} from './TagInput.types';\n\nconst SEPARATOR_KEY: Record<TagInputSeparator, string> = {\n Enter: 'Enter',\n Comma: ',',\n Space: ' ',\n Tab: 'Tab',\n};\n\nconst DEFAULT_SEPARATORS: TagInputSeparator[] = ['Enter', 'Comma'];\n\nfunction defaultNormalize(value: string): string {\n return value.trim();\n}\n\n/**\n * Multi-value input that captures a list of strings (\"tags\") rendered as\n * removable chips. Supports controlled and uncontrolled modes, validation,\n * configurable commit keys, and custom chip rendering.\n *\n * @example\n * ```tsx\n * <TagInput\n * label=\"Keywords\"\n * placeholder=\"Add a keyword...\"\n * defaultValue={['react', 'typescript']}\n * onChange={setTags}\n * />\n * ```\n */\nexport function TagInput({\n value,\n defaultValue,\n onChange,\n placeholder,\n separators = DEFAULT_SEPARATORS,\n addOnBlur = false,\n allowDuplicates = false,\n max,\n validate,\n normalize = defaultNormalize,\n onValidate,\n renderTag,\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 name,\n inputAriaLabel,\n inputRef: inputRefProp,\n className,\n style,\n testId,\n id: idProp,\n ref,\n ...rest\n}: TagInputProps) {\n const autoId = useId();\n const fieldId = idProp ?? autoId;\n const labelId = `${fieldId}-label`;\n const helperId = `${fieldId}-helper`;\n\n const [tags, setTags] = useControlledState<string[]>({\n value,\n defaultValue,\n onChange,\n fallback: [],\n });\n\n const [draft, setDraft] = useState('');\n const [focused, setFocused] = useState(false);\n\n const innerInputRef = useRef<HTMLInputElement | null>(null);\n const wrapperRef = useRef<HTMLDivElement | null>(null);\n\n const setInputRef = useMergedRef(innerInputRef, inputRefProp);\n const setWrapperRef = useMergedRef(wrapperRef, ref);\n\n const atMax = typeof max === 'number' && tags.length >= max;\n const inputDisabled = disabled || readOnly || atMax;\n\n const separatorKeys = useMemo(\n () => new Set(separators.map(s => SEPARATOR_KEY[s])),\n [separators]\n );\n\n const tryAppend = useCallback(\n (current: string[], raw: string): string[] | null => {\n const normalized = normalize(raw);\n if (normalized.length === 0) {\n if (raw.length > 0) onValidate?.(raw, 'empty');\n return null;\n }\n if (typeof max === 'number' && current.length >= max) {\n onValidate?.(raw, 'max');\n return null;\n }\n if (!allowDuplicates && current.includes(normalized)) {\n onValidate?.(raw, 'duplicate');\n return null;\n }\n if (validate) {\n const result = validate(normalized, current);\n if (result === false) {\n onValidate?.(raw, 'invalid');\n return null;\n }\n if (typeof result === 'string') {\n onValidate?.(raw, result);\n return null;\n }\n }\n return [...current, normalized];\n },\n [allowDuplicates, max, normalize, onValidate, validate]\n );\n\n const commitTag = useCallback(\n (raw: string): boolean => {\n const next = tryAppend(tags, raw);\n if (!next) return false;\n setTags(next);\n return true;\n },\n [setTags, tags, tryAppend]\n );\n\n const removeTagAt = useCallback(\n (index: number) => {\n if (index < 0 || index >= tags.length) return;\n const next = tags.slice(0, index).concat(tags.slice(index + 1));\n setTags(next);\n },\n [setTags, tags]\n );\n\n const handleKeyDown = useCallback(\n (event: React.KeyboardEvent<HTMLInputElement>) => {\n if (inputDisabled) return;\n\n if (separatorKeys.has(event.key)) {\n // Tab as separator: only commit when there's a draft, otherwise let\n // the focus move out naturally.\n if (event.key === 'Tab' && draft.length === 0) return;\n if (draft.length > 0) {\n event.preventDefault();\n if (commitTag(draft)) setDraft('');\n } else if (event.key === 'Enter') {\n // Prevent submitting an enclosing form on empty Enter.\n event.preventDefault();\n }\n return;\n }\n\n if (event.key === 'Backspace' && draft.length === 0 && tags.length > 0) {\n event.preventDefault();\n removeTagAt(tags.length - 1);\n }\n },\n [commitTag, draft, inputDisabled, removeTagAt, separatorKeys, tags.length]\n );\n\n const handleChange = useCallback(\n (event: React.ChangeEvent<HTMLInputElement>) => {\n const next = event.target.value;\n // If the user typed/pasted a separator character (e.g. comma), commit\n // each piece. Newlines are split too so multi-line paste still works.\n const splitChars = Array.from(separatorKeys).filter(\n k => k !== 'Enter' && k !== 'Tab'\n );\n if (splitChars.length === 0 || !splitChars.some(c => next.includes(c))) {\n setDraft(next);\n return;\n }\n\n const pattern = new RegExp(`[${splitChars.map(c => `\\\\${c}`).join('')}]`);\n const parts = next.split(pattern);\n const trailing = parts.pop() ?? '';\n let working = tags;\n for (const part of parts) {\n if (part.length === 0) continue;\n const appended = tryAppend(working, part);\n if (appended) working = appended;\n }\n if (working !== tags) setTags(working);\n setDraft(trailing);\n },\n [separatorKeys, setTags, tags, tryAppend]\n );\n\n const handleBlur = useCallback(() => {\n setFocused(false);\n if (addOnBlur && draft.length > 0) {\n if (commitTag(draft)) setDraft('');\n }\n }, [addOnBlur, commitTag, draft]);\n\n const handleWrapperMouseDown = useCallback(\n (event: React.MouseEvent<HTMLDivElement>) => {\n // Forward clicks on empty padding to the input, but do not steal focus\n // from chip remove buttons.\n if (event.target === wrapperRef.current) {\n event.preventDefault();\n innerInputRef.current?.focus();\n }\n },\n []\n );\n\n const renderChip = (tag: string, index: number) => {\n const remove = () => {\n removeTagAt(index);\n };\n if (renderTag) {\n const state: TagInputRenderTagState = {\n tag,\n index,\n disabled,\n remove,\n };\n return (\n <React.Fragment key={`${tag}-${String(index)}`}>\n {renderTag(state)}\n </React.Fragment>\n );\n }\n\n return (\n <span\n key={`${tag}-${String(index)}`}\n className={tagRecipe({ size })}\n data-testid={testId ? `${testId}-tag-${String(index)}` : undefined}\n >\n <span className={tagLabelStyle}>{tag}</span>\n {!readOnly && (\n <button\n type=\"button\"\n tabIndex={-1}\n disabled={disabled}\n aria-label={`Remove ${tag}`}\n className={tagRemoveStyle}\n onClick={remove}\n >\n <CloseIcon size=\"sm\" decorative />\n </button>\n )}\n </span>\n );\n };\n\n const showHelperText = error && errorMessage ? errorMessage : helperText;\n const formValue = tags.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 {/*\n The onMouseDown handler only forwards clicks on the wrapper padding to\n focus the inner native <input>, which is the actual accessible control\n (focusable, keyboard-driven). The wrapper is presentational, so no\n widget role belongs on it.\n */}\n {/* eslint-disable-next-line jsx-a11y/no-static-element-interactions */}\n <div\n ref={setWrapperRef}\n className={wrapperRecipe({\n size,\n variant,\n focused,\n error,\n disabled,\n })}\n onMouseDown={handleWrapperMouseDown}\n data-testid={testId}\n {...rest}\n >\n {tags.map((tag, index) => renderChip(tag, index))}\n\n {!readOnly && (\n <input\n ref={setInputRef}\n id={fieldId}\n type=\"text\"\n value={draft}\n placeholder={tags.length === 0 ? placeholder : undefined}\n disabled={inputDisabled}\n required={required && tags.length === 0}\n aria-label={inputAriaLabel}\n aria-labelledby={!inputAriaLabel && label ? labelId : undefined}\n aria-describedby={showHelperText ? helperId : undefined}\n aria-invalid={error || undefined}\n className={inputStyle}\n onChange={handleChange}\n onKeyDown={handleKeyDown}\n onFocus={() => {\n setFocused(true);\n }}\n onBlur={handleBlur}\n />\n )}\n </div>\n\n {name && <input type=\"hidden\" name={name} value={formValue} />}\n\n {showHelperText && (\n <FormHelperText id={helperId} error={error}>\n {showHelperText}\n </FormHelperText>\n )}\n </div>\n );\n}\n\nTagInput.displayName = 'TagInput';\n"],"names":[],"mappings":";;;;;;;;;;;AA0BA;AACE;AACA;AACA;AACA;;AAGF;AAEA;AACE;AACF;AAEA;;;;;;;;;;;;;;AAcG;;AAiCD;AACA;AACA;AACA;AAEA;;;;AAIE;AACD;;;AAKD;AACA;;;AAKA;AACA;AAEA;;AAOI;AACA;AACE;AAAoB;AACpB;;;AAGA;AACA;;;AAGA;AACA;;;;AAIA;AACE;AACA;;AAEF;AACE;AACA;;;AAGJ;AACF;AAIF;;AAGI;AAAW;;AAEX;;AAKJ;;;;;AAKE;AAIF;AAEI;;;;;;;AAME;;;;;AAGO;;;;;;AAOT;;AAEE;;AAEJ;AAIF;AAEI;;;;;;;;;;;;AAeA;AACE;;;AAEA;;;;;;;AAQN;;;;;;;AAOA;;;;;AAMM;;;AAMN;;;AAGE;;AAEE;;;;;;;;AAaF;AAqBF;AAEA;;;;;;;;;;AAwDU;AAeZ;AAEA;;"}
|
|
@@ -89,6 +89,12 @@ 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 { cx } from '../../../utils/cx.js';
|
|
93
99
|
import { guideLineStyle, guideLineLeftVar, chevronRecipe, iconRecipe, renameInputRecipe, labelRecipe, actionsStyle, paddingLeftVar, dropTargetStyle, rowRecipe } from './TreeNode.css.js';
|
|
94
100
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TreeNode.js","sources":["
|
|
1
|
+
{"version":3,"file":"TreeNode.js","sources":["src/components/controls/TreeView/TreeNode.tsx"],"sourcesContent":["'use client';\n\nimport { assignInlineVars } from '@vanilla-extract/dynamic';\nimport React, { useState, useRef, useCallback } from 'react';\n\nimport { ChevronRightIcon } from '@/components/Icons';\nimport { cx } from '@/utils/cx';\n\nimport {\n rowRecipe,\n chevronRecipe,\n iconRecipe,\n labelRecipe,\n actionsStyle,\n renameInputRecipe,\n guideLineStyle,\n dropTargetStyle,\n paddingLeftVar,\n guideLineLeftVar,\n} from './TreeNode.css';\n\nimport type {\n TreeNodeData,\n TreeNodeState,\n TreeViewSize,\n} from './TreeView.types';\n\n// --- Size configurations ---\n\nconst sizeConfig: Record<\n TreeViewSize,\n {\n rowHeight: number;\n chevronSize: number;\n iconSize: number;\n fontSizeKey: 'xs' | 'sm' | 'md' | 'lg';\n defaultIndent: number;\n }\n> = {\n sm: {\n rowHeight: 20,\n chevronSize: 10,\n iconSize: 12,\n fontSizeKey: 'md',\n defaultIndent: 12,\n },\n md: {\n rowHeight: 24,\n chevronSize: 12,\n iconSize: 14,\n fontSizeKey: 'md',\n defaultIndent: 16,\n },\n lg: {\n rowHeight: 32,\n chevronSize: 14,\n iconSize: 16,\n fontSizeKey: 'lg',\n defaultIndent: 20,\n },\n};\n\n// --- Chevron icon SVG ---\n\nconst ChevronSvg = ({ size }: { size: number }) => (\n <ChevronRightIcon size={size} color=\"currentColor\" decorative />\n);\n\n// --- TreeNode component ---\n\nexport interface TreeNodeComponentProps {\n node: TreeNodeData;\n depth: number;\n size: TreeViewSize;\n indent: number;\n isSelected: boolean;\n isExpanded: boolean;\n isFocused: boolean;\n isFirst: boolean;\n isLast: boolean;\n showChevrons: boolean;\n showGuideLines: boolean;\n renamable: boolean;\n nodeId: string;\n isDropTarget: boolean;\n renderNode?: (node: TreeNodeData, state: TreeNodeState) => React.ReactNode;\n renderActions?: (node: TreeNodeData, state: TreeNodeState) => React.ReactNode;\n onToggleExpand: (id: string) => void;\n onClick: (node: TreeNodeData, event: React.MouseEvent) => void;\n onDoubleClick?: (node: TreeNodeData, event: React.MouseEvent) => void;\n onContextMenu?: (node: TreeNodeData, event: React.MouseEvent) => void;\n onRename?: (nodeId: string, newLabel: string) => void;\n onDragOver?: (node: TreeNodeData, event: React.DragEvent) => void;\n onDragLeave?: (node: TreeNodeData, event: React.DragEvent) => void;\n onDrop?: (node: TreeNodeData, event: React.DragEvent) => void;\n}\n\nexport const TreeNodeComponent = ({\n node,\n depth,\n size,\n indent,\n isSelected,\n isExpanded,\n isFocused,\n isFirst,\n isLast,\n showChevrons,\n showGuideLines,\n renamable,\n nodeId,\n isDropTarget,\n renderNode,\n renderActions,\n onToggleExpand,\n onClick,\n onDoubleClick,\n onContextMenu,\n onRename,\n onDragOver,\n onDragLeave,\n onDrop,\n}: TreeNodeComponentProps) => {\n const [isRenaming, setIsRenaming] = useState(false);\n const [renameValue, setRenameValue] = useState(node.label);\n const renameInputRef = useRef<HTMLInputElement>(null);\n\n const isLeaf = !node.children || node.children.length === 0;\n const config = sizeConfig[size];\n\n const state: TreeNodeState = {\n selected: isSelected,\n expanded: isExpanded,\n focused: isFocused,\n depth,\n isLeaf,\n isFirst,\n isLast,\n };\n\n const handleChevronClick = useCallback(\n (e: React.MouseEvent) => {\n e.stopPropagation();\n if (!node.disabled && !isLeaf) {\n onToggleExpand(node.id);\n }\n },\n [node.id, node.disabled, isLeaf, onToggleExpand]\n );\n\n const handleClick = useCallback(\n (e: React.MouseEvent) => {\n if (node.disabled || isRenaming) return;\n onClick(node, e);\n },\n [node, isRenaming, onClick]\n );\n\n const handleDoubleClick = useCallback(\n (e: React.MouseEvent) => {\n if (node.disabled) return;\n\n // Start rename if renamable\n if (renamable && node.renamable !== false) {\n setIsRenaming(true);\n setRenameValue(node.label);\n setTimeout(() => {\n renameInputRef.current?.focus();\n renameInputRef.current?.select();\n }, 0);\n return;\n }\n\n onDoubleClick?.(node, e);\n },\n [node, renamable, onDoubleClick]\n );\n\n const handleContextMenu = useCallback(\n (e: React.MouseEvent) => {\n onContextMenu?.(node, e);\n },\n [node, onContextMenu]\n );\n\n const handleDragOver = useCallback(\n (e: React.DragEvent) => {\n onDragOver?.(node, e);\n },\n [node, onDragOver]\n );\n\n const handleDragLeave = useCallback(\n (e: React.DragEvent) => {\n onDragLeave?.(node, e);\n },\n [node, onDragLeave]\n );\n\n const handleDrop = useCallback(\n (e: React.DragEvent) => {\n onDrop?.(node, e);\n },\n [node, onDrop]\n );\n\n const confirmRename = useCallback(() => {\n if (renameValue.trim() && renameValue !== node.label) {\n onRename?.(node.id, renameValue.trim());\n }\n setIsRenaming(false);\n }, [renameValue, node.id, node.label, onRename]);\n\n const cancelRename = useCallback(() => {\n setRenameValue(node.label);\n setIsRenaming(false);\n }, [node.label]);\n\n const handleRenameKeyDown = useCallback(\n (e: React.KeyboardEvent) => {\n if (e.key === 'Enter') {\n confirmRename();\n } else if (e.key === 'Escape') {\n cancelRename();\n }\n },\n [confirmRename, cancelRename]\n );\n\n // Guide lines for each parent level\n const guideLines =\n showGuideLines && depth > 0\n ? Array.from({ length: depth }, (_, i) => (\n <span\n key={i}\n className={guideLineStyle}\n style={assignInlineVars({\n [guideLineLeftVar]: `${i * indent + config.defaultIndent / 2}px`,\n })}\n />\n ))\n : null;\n\n return (\n // Keyboard interaction is handled centrally by the parent role=\"tree\"\n // container via the aria-activedescendant pattern (it owns tabIndex and\n // onKeyDown). Individual treeitems are not separately focusable, so their\n // onClick is a pointer-only enhancement and they carry no key handler.\n // eslint-disable-next-line jsx-a11y/click-events-have-key-events\n <div\n id={`treenode-${nodeId}`}\n role=\"treeitem\"\n aria-expanded={isLeaf ? undefined : isExpanded}\n aria-selected={isSelected}\n aria-disabled={node.disabled ?? undefined}\n aria-level={depth + 1}\n data-drop-target={isDropTarget ? 'true' : undefined}\n className={cx(\n rowRecipe({\n size,\n selected: isSelected,\n focused: isFocused,\n disabled: node.disabled ?? false,\n }),\n dropTargetStyle\n )}\n style={assignInlineVars({\n [paddingLeftVar]: `${depth * indent}px`,\n })}\n onClick={handleClick}\n onDoubleClick={handleDoubleClick}\n onContextMenu={handleContextMenu}\n onDragOver={onDragOver ? handleDragOver : undefined}\n onDragLeave={onDragLeave ? handleDragLeave : undefined}\n onDrop={onDrop ? handleDrop : undefined}\n tabIndex={-1}\n >\n {guideLines}\n\n {/* Chevron */}\n {showChevrons && (\n <span\n className={chevronRecipe({\n expanded: isExpanded,\n size,\n visible: !isLeaf,\n })}\n onClick={handleChevronClick}\n aria-hidden=\"true\"\n >\n <ChevronSvg size={config.chevronSize} />\n </span>\n )}\n\n {/* Content */}\n {renderNode ? (\n renderNode(node, state)\n ) : (\n <>\n {node.icon && (\n <span className={iconRecipe({ size })}>{node.icon}</span>\n )}\n {isRenaming ? (\n <input\n ref={renameInputRef}\n className={renameInputRecipe({ size })}\n value={renameValue}\n onChange={e => {\n setRenameValue(e.target.value);\n }}\n onKeyDown={handleRenameKeyDown}\n onBlur={confirmRename}\n aria-label={`Rename ${node.label}`}\n onClick={e => {\n e.stopPropagation();\n }}\n />\n ) : (\n <span className={labelRecipe({ size })}>{node.label}</span>\n )}\n </>\n )}\n\n {/* Actions */}\n {renderActions && (\n // This span is a non-interactive container; its onClick only stops\n // row-click propagation so that the genuinely interactive controls\n // produced by renderActions handle their own activation. It has no\n // activatable behavior of its own, so no role or key handler applies.\n // eslint-disable-next-line jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events\n <span\n className={actionsStyle}\n onClick={e => {\n e.stopPropagation();\n }}\n >\n {renderActions(node, state)}\n </span>\n )}\n </div>\n );\n};\n\nTreeNodeComponent.displayName = 'TreeNode';\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2BA;AAEA;AAUE;AACE;AACA;AACA;AACA;AACA;AACD;AACD;AACE;AACA;AACA;AACA;AACA;AACD;AACD;AACE;AACA;AACA;AACA;AACA;AACD;;AAGH;AAEA;AAiCO;;AA2BL;AACA;AAEA;AACA;AAEA;AACE;AACA;AACA;;;;;;AAOF;;;AAIM;;AAEJ;AAIF;AAEI;;AACA;;AAKJ;;;;;;AAOM;;AAEE;AACA;;;;AAKJ;;AAKJ;AAEI;AACF;AAIF;AAEI;AACF;AAIF;AAEI;AACF;AAIF;AAEI;AACF;AAIF;;;;;AAKA;AAEA;AACE;;AAEF;AAEA;AAEI;AACE;;AACK;AACL;;AAEJ;;AAKF;;AAOY;AACD;;;;;;;;;;AAsBH;AACA;AACA;AACD;AAID;AACD;AAeK;;;AAGD;AAsBK;AACF;;;;;;;;;;;AAgCd;AAEA;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TreeView.js","sources":["../../../../../../src/components/controls/TreeView/TreeView.tsx"],"sourcesContent":[null],"names":[],"mappings":";;;;;;;;;AAiBA;;;;;;;;;;;;;;;;;AAiBG;AACI;AAgCL;AAEA;;;;;;;;;;AAmBC;;;;;AAOG;AAEA;;AAEI;;AACK;AACL;;;AAEA;;;;;AAMJ;AACE;;AAGF;AACF;;;;;;;AAQC;;AAIH;;AAGM;;AAEF;AAGA;;;AAGI;AACA;AACE;;AAEE;;;;;;;;;;AAUJ;AACA;AACE;;AAEE;;;;;;;;;;;AAWF;AAEA;AACE;;AACK;;AAEL;AACA;AACE;;AAEE;;;;;;;;;;AAUN;AAEA;AACE;;AACK;;AAEL;;;;;;;AAOJ;;AAEE;;;;;;;;AAQA;;;;;;AAMF;AACE;;;;;;;AAOA;AACE;;AACK;AACL;;;;;;;;AAQF;;;;;AAKR;;;;;;;;;AAUC;;AAIH;AACE;AACE;;;;;AAOJ;;;AAIM;;;AAKN;AACE;;AAeF;AAeM;AACA;;AAgCR;AAEA;;"}
|
|
1
|
+
{"version":3,"file":"TreeView.js","sources":["src/components/controls/TreeView/TreeView.tsx"],"sourcesContent":["'use client';\n\nimport { assignInlineVars } from '@vanilla-extract/dynamic';\nimport React, { useCallback, useRef } from 'react';\n\nimport { cx } from '@/utils/cx';\n\nimport { TreeNodeComponent } from './TreeNode';\nimport {\n treeContainerStyle,\n emptyStateStyle,\n maxHeightVar,\n} from './TreeView.css';\nimport { useTreeState } from './useTreeState';\n\nimport type { TreeViewProps, TreeNodeData } from './TreeView.types';\n\n/**\n * A hierarchical collapsible tree for displaying and managing nested data.\n *\n * Supports multi-selection, keyboard navigation, inline renaming,\n * and custom node rendering. The most iconic editor panel — scene hierarchy,\n * file browser, layer stack.\n *\n * @example\n * ```tsx\n * const nodes = [\n * { id: '1', label: 'Root', children: [\n * { id: '1.1', label: 'Child A' },\n * { id: '1.2', label: 'Child B' },\n * ]},\n * ];\n * <TreeView nodes={nodes} onSelectionChange={setSelected} />\n * ```\n */\nexport const TreeView = ({\n nodes,\n expandedIds,\n defaultExpandedIds,\n selectedIds,\n defaultSelectedIds,\n selectionMode = 'single',\n renamable = false,\n size = 'md',\n indent = 16,\n showChevrons = true,\n showGuideLines = false,\n expandOnSelect = false,\n expandOnClick = false,\n maxHeight,\n renderNode,\n renderActions,\n emptyContent,\n dropTargetId,\n onExpandedChange,\n onSelectionChange,\n onNodeClick,\n onNodeDoubleClick,\n onNodeContextMenu,\n onNodeRename,\n onNodeDragOver,\n onNodeDragLeave,\n onNodeDrop,\n className,\n testId,\n ...rest\n}: TreeViewProps) => {\n const treeRef = useRef<HTMLDivElement>(null);\n\n const {\n expandedSet,\n selectedSet,\n focusedId,\n visibleNodes,\n toggleExpand,\n select,\n setFocused,\n selectAll,\n } = useTreeState({\n nodes,\n expandedIds,\n defaultExpandedIds,\n selectedIds,\n defaultSelectedIds,\n selectionMode,\n expandOnSelect,\n onExpandedChange,\n onSelectionChange,\n });\n\n // Handle node click with selection logic\n const handleNodeClick = useCallback(\n (node: TreeNodeData, event: React.MouseEvent) => {\n if (node.disabled) return;\n\n setFocused(node.id);\n\n if (selectionMode !== 'none') {\n if (event.ctrlKey || event.metaKey) {\n select(node.id, 'toggle');\n } else if (event.shiftKey) {\n select(node.id, 'range');\n } else {\n select(node.id, 'replace');\n }\n }\n\n // Toggle a parent node's expansion when its whole row is clicked. The\n // chevron stops propagation, so it never double-toggles.\n if (expandOnClick && node.children && node.children.length > 0) {\n toggleExpand(node.id);\n }\n\n onNodeClick?.(node, event);\n },\n [\n selectionMode,\n select,\n setFocused,\n onNodeClick,\n expandOnClick,\n toggleExpand,\n ]\n );\n\n // Keyboard navigation\n const handleKeyDown = useCallback(\n (event: React.KeyboardEvent) => {\n const currentIdx = focusedId\n ? visibleNodes.findIndex(vn => vn.node.id === focusedId)\n : -1;\n const currentNode =\n currentIdx >= 0 ? visibleNodes[currentIdx] : undefined;\n\n switch (event.key) {\n case 'ArrowDown': {\n event.preventDefault();\n const nextIdx = currentIdx + 1;\n if (nextIdx < visibleNodes.length) {\n const nextNode = visibleNodes[nextIdx];\n if (nextNode) {\n setFocused(nextNode.node.id);\n if (event.shiftKey && selectionMode === 'multiple') {\n select(nextNode.node.id, 'range');\n }\n }\n }\n break;\n }\n case 'ArrowUp': {\n event.preventDefault();\n const prevIdx = currentIdx - 1;\n if (prevIdx >= 0) {\n const prevNode = visibleNodes[prevIdx];\n if (prevNode) {\n setFocused(prevNode.node.id);\n if (event.shiftKey && selectionMode === 'multiple') {\n select(prevNode.node.id, 'range');\n }\n }\n }\n break;\n }\n case 'ArrowRight': {\n event.preventDefault();\n if (currentNode) {\n const hasChildren =\n currentNode.node.children && currentNode.node.children.length > 0;\n if (hasChildren && !expandedSet.has(currentNode.node.id)) {\n toggleExpand(currentNode.node.id);\n } else if (hasChildren && expandedSet.has(currentNode.node.id)) {\n // Move to first child\n const nextIdx = currentIdx + 1;\n if (nextIdx < visibleNodes.length) {\n const nextNode = visibleNodes[nextIdx];\n if (nextNode) {\n setFocused(nextNode.node.id);\n }\n }\n }\n }\n break;\n }\n case 'ArrowLeft': {\n event.preventDefault();\n if (currentNode) {\n const hasChildren =\n currentNode.node.children && currentNode.node.children.length > 0;\n if (hasChildren && expandedSet.has(currentNode.node.id)) {\n toggleExpand(currentNode.node.id);\n } else if (currentNode.parentId) {\n // Move to parent\n setFocused(currentNode.parentId);\n }\n }\n break;\n }\n case 'Home': {\n event.preventDefault();\n const firstNode = visibleNodes[0];\n if (firstNode) {\n setFocused(firstNode.node.id);\n }\n break;\n }\n case 'End': {\n event.preventDefault();\n const lastNode = visibleNodes[visibleNodes.length - 1];\n if (lastNode) {\n setFocused(lastNode.node.id);\n }\n break;\n }\n case 'Enter': {\n event.preventDefault();\n if (focusedId && selectionMode !== 'none') {\n select(focusedId, 'replace');\n }\n break;\n }\n case ' ': {\n event.preventDefault();\n if (focusedId) {\n if (selectionMode === 'multiple') {\n select(focusedId, 'toggle');\n } else if (selectionMode === 'single') {\n select(focusedId, 'replace');\n }\n }\n break;\n }\n case 'a': {\n if (event.ctrlKey || event.metaKey) {\n event.preventDefault();\n selectAll();\n }\n break;\n }\n }\n },\n [\n focusedId,\n visibleNodes,\n expandedSet,\n selectionMode,\n setFocused,\n select,\n toggleExpand,\n selectAll,\n ]\n );\n\n // Scroll focused node into view\n React.useEffect(() => {\n if (focusedId && treeRef.current) {\n const el = treeRef.current.querySelector(\n `#treenode-${CSS.escape(focusedId)}`\n );\n if (el && typeof el.scrollIntoView === 'function') {\n el.scrollIntoView({ block: 'nearest' });\n }\n }\n }, [focusedId]);\n\n const containerInlineVars = maxHeight\n ? assignInlineVars({\n [maxHeightVar]:\n typeof maxHeight === 'number' ? `${maxHeight}px` : maxHeight,\n })\n : undefined;\n\n if (nodes.length === 0) {\n return (\n <div\n ref={treeRef}\n className={cx(treeContainerStyle, className)}\n data-testid={testId}\n role=\"tree\"\n aria-label=\"Empty tree\"\n style={containerInlineVars}\n {...rest}\n >\n <div className={emptyStateStyle}>{emptyContent ?? 'No items'}</div>\n </div>\n );\n }\n\n return (\n <div\n ref={treeRef}\n className={cx(treeContainerStyle, className)}\n data-testid={testId}\n role=\"tree\"\n aria-label=\"Tree\"\n aria-multiselectable={selectionMode === 'multiple' ? true : undefined}\n aria-activedescendant={focusedId ? `treenode-${focusedId}` : undefined}\n tabIndex={0}\n onKeyDown={handleKeyDown}\n style={containerInlineVars}\n {...rest}\n >\n {visibleNodes.map((flatNode, idx) => {\n const { node, depth } = flatNode;\n return (\n <TreeNodeComponent\n key={node.id}\n node={node}\n depth={depth}\n size={size}\n indent={indent}\n isSelected={selectedSet.has(node.id)}\n isExpanded={expandedSet.has(node.id)}\n isFocused={focusedId === node.id}\n isFirst={idx === 0}\n isLast={idx === visibleNodes.length - 1}\n showChevrons={showChevrons}\n showGuideLines={showGuideLines}\n renamable={renamable}\n nodeId={node.id}\n isDropTarget={dropTargetId === node.id}\n renderNode={renderNode}\n renderActions={renderActions}\n onToggleExpand={toggleExpand}\n onClick={handleNodeClick}\n onDoubleClick={onNodeDoubleClick}\n onContextMenu={onNodeContextMenu}\n onRename={onNodeRename}\n onDragOver={onNodeDragOver}\n onDragLeave={onNodeDragLeave}\n onDrop={onNodeDrop}\n />\n );\n })}\n </div>\n );\n};\n\nTreeView.displayName = 'TreeView';\n"],"names":[],"mappings":";;;;;;;;;AAiBA;;;;;;;;;;;;;;;;;AAiBG;AACI;AAgCL;AAEA;;;;;;;;;;AAmBC;;;;;AAOG;AAEA;;AAEI;;AACK;AACL;;;AAEA;;;;;AAMJ;AACE;;AAGF;AACF;;;;;;;AAQC;;AAIH;;AAGM;;AAEF;AAGA;;;AAGI;AACA;AACE;;AAEE;;;;;;;;;;AAUJ;AACA;AACE;;AAEE;;;;;;;;;;;AAWF;AAEA;AACE;;AACK;;AAEL;AACA;AACE;;AAEE;;;;;;;;;;AAUN;AAEA;AACE;;AACK;;AAEL;;;;;;;AAOJ;;AAEE;;;;;;;;AAQA;;;;;;AAMF;AACE;;;;;;;AAOA;AACE;;AACK;AACL;;;;;;;;AAQF;;;;;AAKR;;;;;;;;;AAUC;;AAIH;AACE;AACE;;;;;AAOJ;;;AAIM;;;AAKN;AACE;;AAeF;AAeM;AACA;;AAgCR;AAEA;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useTreeState.js","sources":["
|
|
1
|
+
{"version":3,"file":"useTreeState.js","sources":["src/components/controls/TreeView/useTreeState.ts"],"sourcesContent":["'use client';\n\nimport { useState, useMemo, useCallback } from 'react';\n\nimport type { TreeNodeData, TreeSelectionMode } from './TreeView.types';\n\ninterface FlatNode {\n node: TreeNodeData;\n depth: number;\n parentId: string | null;\n}\n\ninterface UseTreeStateOptions {\n nodes: TreeNodeData[];\n expandedIds?: string[];\n defaultExpandedIds?: string[];\n selectedIds?: string[];\n defaultSelectedIds?: string[];\n selectionMode: TreeSelectionMode;\n expandOnSelect?: boolean;\n onExpandedChange?: (expandedIds: string[]) => void;\n onSelectionChange?: (selectedIds: string[]) => void;\n}\n\nexport interface UseTreeStateReturn {\n expandedSet: Set<string>;\n selectedSet: Set<string>;\n focusedId: string | null;\n visibleNodes: FlatNode[];\n toggleExpand: (id: string) => void;\n select: (id: string, mode: 'replace' | 'toggle' | 'range') => void;\n setFocused: (id: string | null) => void;\n selectAll: () => void;\n}\n\n/**\n * Flatten tree into a list of visible nodes, respecting expansion state.\n */\nfunction flattenTree(\n nodes: TreeNodeData[],\n expandedSet: Set<string>,\n depth: number = 0,\n parentId: string | null = null\n): FlatNode[] {\n const result: FlatNode[] = [];\n for (const node of nodes) {\n result.push({ node, depth, parentId });\n if (node.children && node.children.length > 0 && expandedSet.has(node.id)) {\n result.push(\n ...flattenTree(node.children, expandedSet, depth + 1, node.id)\n );\n }\n }\n return result;\n}\n\n/**\n * Collect all node IDs from a tree for \"expand all\" or \"select all\".\n */\nfunction collectAllIds(nodes: TreeNodeData[]): string[] {\n const ids: string[] = [];\n for (const node of nodes) {\n ids.push(node.id);\n if (node.children) {\n ids.push(...collectAllIds(node.children));\n }\n }\n return ids;\n}\n\nexport function useTreeState({\n nodes,\n expandedIds: controlledExpanded,\n defaultExpandedIds,\n selectedIds: controlledSelected,\n defaultSelectedIds,\n selectionMode,\n expandOnSelect = false,\n onExpandedChange,\n onSelectionChange,\n}: UseTreeStateOptions): UseTreeStateReturn {\n // Internal expansion state\n const [internalExpanded, setInternalExpanded] = useState<Set<string>>(\n () => new Set(defaultExpandedIds ?? [])\n );\n const expandedSet = useMemo(\n () => (controlledExpanded ? new Set(controlledExpanded) : internalExpanded),\n [controlledExpanded, internalExpanded]\n );\n\n // Internal selection state\n const [internalSelected, setInternalSelected] = useState<Set<string>>(\n () => new Set(defaultSelectedIds ?? [])\n );\n const selectedSet = useMemo(\n () => (controlledSelected ? new Set(controlledSelected) : internalSelected),\n [controlledSelected, internalSelected]\n );\n\n // Focused node\n const [focusedId, setFocused] = useState<string | null>(null);\n\n // Flatten visible nodes\n const visibleNodes = useMemo(\n () => flattenTree(nodes, expandedSet),\n [nodes, expandedSet]\n );\n\n // Toggle expansion\n const toggleExpand = useCallback(\n (id: string) => {\n const next = new Set(expandedSet);\n if (next.has(id)) {\n next.delete(id);\n } else {\n next.add(id);\n }\n if (controlledExpanded === undefined) {\n setInternalExpanded(next);\n }\n onExpandedChange?.([...next]);\n },\n [expandedSet, controlledExpanded, onExpandedChange]\n );\n\n // Selection\n const select = useCallback(\n (id: string, mode: 'replace' | 'toggle' | 'range') => {\n if (selectionMode === 'none') return;\n\n let next: Set<string>;\n\n if (selectionMode === 'single') {\n next = new Set([id]);\n } else {\n // multiple mode\n if (mode === 'replace') {\n next = new Set([id]);\n } else if (mode === 'toggle') {\n next = new Set(selectedSet);\n if (next.has(id)) {\n next.delete(id);\n } else {\n next.add(id);\n }\n } else {\n // range — select from last selected to this id\n const visibleIds = visibleNodes.map(vn => vn.node.id);\n const lastSelected = [...selectedSet].pop();\n if (!lastSelected) {\n next = new Set([id]);\n } else {\n const startIdx = visibleIds.indexOf(lastSelected);\n const endIdx = visibleIds.indexOf(id);\n if (startIdx === -1 || endIdx === -1) {\n next = new Set([id]);\n } else {\n const from = Math.min(startIdx, endIdx);\n const to = Math.max(startIdx, endIdx);\n next = new Set(selectedSet);\n for (let i = from; i <= to; i++) {\n const nodeId = visibleIds[i];\n if (nodeId !== undefined) {\n next.add(nodeId);\n }\n }\n }\n }\n }\n }\n\n if (controlledSelected === undefined) {\n setInternalSelected(next);\n }\n onSelectionChange?.([...next]);\n\n // Expand on select\n if (expandOnSelect && !expandedSet.has(id)) {\n // Check if node has children\n const flat = flattenTree(nodes, new Set(collectAllIds(nodes)));\n const targetNode = flat.find(fn => fn.node.id === id);\n if (targetNode?.node.children && targetNode.node.children.length > 0) {\n toggleExpand(id);\n }\n }\n },\n [\n selectionMode,\n selectedSet,\n controlledSelected,\n onSelectionChange,\n expandOnSelect,\n expandedSet,\n nodes,\n visibleNodes,\n toggleExpand,\n ]\n );\n\n // Select all visible nodes\n const selectAll = useCallback(() => {\n if (selectionMode !== 'multiple') return;\n const allIds = new Set(visibleNodes.map(vn => vn.node.id));\n if (controlledSelected === undefined) {\n setInternalSelected(allIds);\n }\n onSelectionChange?.([...allIds]);\n }, [selectionMode, visibleNodes, controlledSelected, onSelectionChange]);\n\n return {\n expandedSet,\n selectedSet,\n focusedId,\n visibleNodes,\n toggleExpand,\n select,\n setFocused,\n selectAll,\n };\n}\n"],"names":[],"mappings":";;;AAmCA;;AAEG;AACH;;AAOE;;;;;;AAQA;AACF;AAEA;;AAEG;AACH;;AAEE;AACE;AACA;;;;AAIF;AACF;AAEM;;AAYJ;AAGA;;AAMA;AAGA;;;;;;AAeA;AAEI;AACA;AACE;;;AAEA;;AAEF;;;AAGA;;;;;;AAUA;AAEA;;;;;AAIE;;;AAEO;AACL;AACA;AACE;;;AAEA;;;;;AAIF;;;;;;;;;;;;;;AAYI;AACA;AACE;AACA;AACE;;;;;;;AAQZ;;;AAGA;;;;AAKE;AACA;AACA;;;;AAIJ;;;;;;;;;;AAWC;;AAIH;;;;AAGE;;;AAGA;;;;;;;;;;;;AAaJ;;"}
|
|
@@ -94,6 +94,12 @@ import '../../Icons/GitBranchIcon.js';
|
|
|
94
94
|
import '../../Icons/BugIcon.js';
|
|
95
95
|
import '../../Icons/MinusIcon.js';
|
|
96
96
|
import '../../Icons/PinIcon.js';
|
|
97
|
+
import '../../Icons/OrbitIcon.js';
|
|
98
|
+
import '../../Icons/RotateIcon.js';
|
|
99
|
+
import '../../Icons/SpinIcon.js';
|
|
100
|
+
import '../../Icons/CameraIcon.js';
|
|
101
|
+
import '../../Icons/CubeIcon.js';
|
|
102
|
+
import '../../Icons/ZoomFitIcon.js';
|
|
97
103
|
import { vars } from '../../../theme/contract.css.js';
|
|
98
104
|
import { cx } from '../../../utils/cx.js';
|
|
99
105
|
import { axisInputStyle, axisColorVar, axisLabelRecipe, linkButtonRecipe, gapVar, vectorRowRecipe, vectorContainerStyle } from './VectorInput.css.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VectorInput.js","sources":["
|
|
1
|
+
{"version":3,"file":"VectorInput.js","sources":["src/components/controls/VectorInput/VectorInput.tsx"],"sourcesContent":["'use client';\n\nimport { assignInlineVars } from '@vanilla-extract/dynamic';\nimport React, { useState, useCallback, useRef } from 'react';\n\nimport { NumberInput } from '@/components/controls/NumberInput';\nimport { FormHelperText } from '@/components/form/FormHelperText';\nimport { FormLabel } from '@/components/form/FormLabel';\nimport { LinkIcon, UnlinkIcon } from '@/components/Icons';\nimport { vars } from '@/theme/contract.css';\nimport { cx } from '@/utils/cx';\n\nimport {\n vectorContainerStyle,\n vectorRowRecipe,\n axisInputStyle,\n axisLabelRecipe,\n axisColorVar,\n linkButtonRecipe,\n gapVar,\n} from './VectorInput.css';\n\nimport type { VectorInputProps } from './VectorInput.types';\n\n// --- Label presets ---\n\nconst LABEL_PRESETS: Record<string, string[]> = {\n xyz: ['X', 'Y', 'Z', 'W'],\n rgba: ['R', 'G', 'B', 'A'],\n uvw: ['U', 'V', 'W'],\n};\n\n// --- Color presets ---\n\nconst SPATIAL_COLORS = ['#EF4444', '#22C55E', '#3B82F6', '#A855F7'];\nconst COLOR_COLORS = ['#EF4444', '#22C55E', '#3B82F6', '#FFFFFF'];\n\nfunction getAxisColor(\n colorPreset: string,\n axisColors: string[] | undefined,\n axisIndex: number\n): string | null {\n if (colorPreset === 'custom' && axisColors) {\n return axisColors[axisIndex] ?? null;\n }\n if (colorPreset === 'spatial') {\n return SPATIAL_COLORS[axisIndex] ?? null;\n }\n if (colorPreset === 'color') {\n return COLOR_COLORS[axisIndex] ?? null;\n }\n // 'none' — will be resolved via theme\n return null;\n}\n\nfunction getAxisLabels(\n labelPreset: string,\n axisLabels: string[] | undefined,\n dimension: number\n): string[] {\n if (labelPreset === 'custom' && axisLabels) {\n return axisLabels;\n }\n const preset = LABEL_PRESETS[labelPreset];\n if (preset) {\n return preset.slice(0, dimension);\n }\n return Array.from({ length: dimension }, (_, i) => String(i));\n}\n\n/**\n * A grouped numeric input for Vec2, Vec3, and Vec4 vectors.\n *\n * Used for position, rotation, scale, color channels, UV coordinates,\n * bounding box extents, and more. Composes NumberInput with per-axis labels,\n * optional color coding, and linked/unlinked proportional editing.\n *\n * @example\n * ```tsx\n * <VectorInput\n * label=\"Position\"\n * value={[0, 0, 0]}\n * onChange={(val) => setPosition(val)}\n * unit=\"px\"\n * />\n * ```\n */\nexport const VectorInput = ({\n value: controlledValue,\n defaultValue,\n dimension = 3,\n labelPreset = 'xyz',\n axisLabels: customAxisLabels,\n colorPreset = 'spatial',\n axisColors: customAxisColors,\n min,\n max,\n step,\n precisionStep,\n largeStep,\n precision,\n unit,\n size = 'md',\n label,\n helperText,\n error = false,\n errorMessage,\n disabled = false,\n showLink = false,\n defaultLinked = false,\n linked: controlledLinked,\n onLinkedChange,\n direction = 'row',\n gap = 2,\n onChange,\n onChangeComplete,\n className,\n testId,\n ...rest\n}: VectorInputProps) => {\n // Internal value state (uncontrolled mode)\n const [internalValue, setInternalValue] = useState<number[]>(\n () => defaultValue ?? Array.from({ length: dimension }, () => 0)\n );\n const currentValue = controlledValue ?? internalValue;\n\n // Internal linked state (uncontrolled mode)\n const [internalLinked, setInternalLinked] = useState(defaultLinked);\n const isLinked = controlledLinked ?? internalLinked;\n\n // Ref to track value before linked changes for proportional math\n const prevValueRef = useRef<number[]>(currentValue);\n\n // Keep prevValueRef up to date when value changes externally\n React.useEffect(() => {\n prevValueRef.current = currentValue;\n }, [currentValue]);\n\n // Handle link toggle\n const handleLinkToggle = useCallback(() => {\n const newLinked = !isLinked;\n if (controlledLinked === undefined) {\n setInternalLinked(newLinked);\n }\n onLinkedChange?.(newLinked);\n }, [isLinked, controlledLinked, onLinkedChange]);\n\n // Handle axis value change\n const handleAxisChange = useCallback(\n (axisIndex: number, newAxisValue: number) => {\n const prevVector = prevValueRef.current;\n let nextValue: number[];\n\n if (isLinked) {\n const oldAxisValue = prevVector[axisIndex] ?? 0;\n if (oldAxisValue !== 0) {\n // Proportional scaling\n const ratio = newAxisValue / oldAxisValue;\n nextValue = prevVector.map((v, i) =>\n i === axisIndex ? newAxisValue : v * ratio\n );\n } else {\n // Additive delta when old value is 0\n const delta = newAxisValue - oldAxisValue;\n nextValue = prevVector.map((v, i) =>\n i === axisIndex ? newAxisValue : v + delta\n );\n }\n } else {\n nextValue = currentValue.map((v, i) =>\n i === axisIndex ? newAxisValue : v\n );\n }\n\n prevValueRef.current = nextValue;\n\n if (controlledValue === undefined) {\n setInternalValue(nextValue);\n }\n onChange?.(nextValue, axisIndex);\n },\n [isLinked, currentValue, controlledValue, onChange]\n );\n\n // Handle commit (blur/Enter on any NumberInput)\n const handleCommit = useCallback(() => {\n onChangeComplete?.(prevValueRef.current);\n }, [onChangeComplete]);\n\n // Resolve labels\n const labels = getAxisLabels(labelPreset, customAxisLabels, dimension);\n\n const iconSize = size === 'sm' ? 12 : size === 'md' ? 14 : 16;\n\n return (\n <div\n className={cx(vectorContainerStyle, className)}\n data-testid={testId}\n role=\"group\"\n aria-label={label ?? 'Vector input'}\n {...rest}\n >\n {label && <FormLabel disabled={disabled}>{label}</FormLabel>}\n\n <div\n className={vectorRowRecipe({ direction })}\n style={assignInlineVars({ [gapVar]: `${gap}px` })}\n >\n {Array.from({ length: dimension }, (_, i) => {\n const axisLabel = labels[i] ?? String(i);\n const axisValue = currentValue[i] ?? 0;\n const axisColor = getAxisColor(colorPreset, customAxisColors, i);\n // Fallback to theme secondary text color when no axis color\n const resolvedColor = axisColor ?? vars.colors.text.secondary;\n\n return (\n <div key={i} className={axisInputStyle}>\n <div\n className={axisLabelRecipe({ size })}\n style={assignInlineVars({ [axisColorVar]: resolvedColor })}\n >\n {axisLabel}\n </div>\n <NumberInput\n value={axisValue}\n onChange={(val: number) => {\n handleAxisChange(i, val);\n }}\n onBlur={handleCommit}\n min={min}\n max={max}\n step={step}\n precisionStep={precisionStep}\n largeStep={largeStep}\n precision={precision}\n unit={unit}\n size={size}\n disabled={disabled}\n showStepButtons={false}\n aria-label={`${axisLabel} axis`}\n />\n </div>\n );\n })}\n\n {showLink && (\n <button\n type=\"button\"\n className={linkButtonRecipe({ active: isLinked, size })}\n onClick={handleLinkToggle}\n disabled={disabled}\n aria-pressed={isLinked}\n aria-label={isLinked ? 'Unlink axes' : 'Link axes'}\n title={isLinked ? 'Unlink axes' : 'Link axes'}\n >\n {isLinked ? (\n <LinkIcon size={iconSize} color=\"currentColor\" decorative />\n ) : (\n <UnlinkIcon size={iconSize} color=\"currentColor\" decorative />\n )}\n </button>\n )}\n </div>\n\n {(error && errorMessage) || helperText ? (\n <FormHelperText error={error && !!errorMessage}>\n {error && errorMessage ? errorMessage : helperText}\n </FormHelperText>\n ) : null}\n </div>\n );\n};\n\nVectorInput.displayName = 'VectorInput';\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwBA;AAEA;;;AAGE;;AAGF;AAEA;AACA;AAEA;AAKE;AACE;;AAEF;AACE;;AAEF;AACE;;;AAGF;AACF;AAEA;AAKE;AACE;;AAEF;;;;;AAKF;AAEA;;;;;;;;;;;;;;;;AAgBG;;;AAmCD;AAGA;;;AAIA;;AAGA;;AAGA;AACE;AACF;;AAGA;AACE;AACA;;;AAGA;;;;AAME;AACA;;;AAIE;;AAEE;;;;;AAMA;;;;;;;AAWJ;AAEA;;;AAGA;;;AAMJ;AACE;AACF;;;;AAOA;;;;;;;AAgCgB;;AAiBV;AA4BR;AAEA;;"}
|