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":"useTimelineGestures.js","sources":["../../../../../../src/components/editor/Timeline/useTimelineGestures.ts"],"sourcesContent":[null],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAoCA;AACA;AA+EA;AAIE;;AACA;;AAEF;AAEA;AAKE;;AAEE;AACE;AACA;;;AAGN;AAEA;AAIE;AACA;AACE;AAAwC;;AAE1C;AACF;AAEA;;;;;;;;;;;AAWG;AACG;;;;AAMJ;AAEA;AACA;AAEA;AAEI;AACA;;;;;;;;;;;;AAYE;AAED;AACH;;AAME;AACA;AACA;AACA;;;AAGA;AACA;AACE;;;AAoBJ;;AAME;AACA;AACA;AAAU;AACV;AACA;AAAW;;AAEX;;AAEA;AAAmB;AACnB;AAEA;;;AAGE;;AAEJ;AAIF;AAEI;AACA;AACA;;;AAEA;AACA;AAEA;;AAEE;;;AAGE;;;AAGA;;AAEF;;;AAGF;;AAEA;AAEA;;AAGE;AAEA;AACA;;;AAEA;;;AAGE;;;AAGA;AAEI;AACA;AACE;AACA;AACN;;AAEF;;;;;;;;AAUA;AACA;;AAGA;AACA;AACA;;;AAGE;;;AAGA;;;AAGF;AACA;;;;AAKF;;AAEE;AACA;AACA;;;AAGE;;;AAGA;;;AAGF;AACA;;;AAIF;;AAEE;;;AAGE;;;;AAIF;AACA;;;AAIF;;AAEE;;;AAGE;;;;AAIA;;;AAGF;;;AAIF;AACE;AACA;;;AAQF;AACE;AACA;AACA;AACA;AACA;;AAEE;;;AAGE;;;;AAIA;AACA;;;AAGF;;;;;AAMJ;;AAEA;;;AAGE;;;;;AAKF;AACE;;AAED;AACH;AAIF;AAEI;AACA;AACA;;;AAEA;;;;;AAME;AACA;AACE;;;AAGC;;;AAED;;;;AAKE;AACA;AACE;AACA;AACE;;;AAKV;;AACA;AACA;AAEA;AACE;AACA;;AAEE;;;;AAIC;;;;AAGC;;AAED;;;;;AAIC;AACD;;;;AAGD;;AAMA;;;;AAIJ;AACE;;;AAGF;AACE;;;;AAIE;AACA;AACD;;;AAGH;;;;;;;AAcA;;;;;AAKA;AACE;AACA;AACE;AACA;AACA;AACA;AACD;AACF;;AAKL;AACE;AACA;AACA;AACA;AACF;;AAII;AACA;AACA;;AACA;;AAEE;;;AAGF;AACA;;AAGE;;;AAGE;AACE;;;AAEG;AACL;;AACK;;;AAEA;;;;;;AAYA;AACL;AACE;AACA;AACA;AACA;;AAEF;AAEE;;AAEqB;;;;;AAKjB;;;;AAQJ;;;;;AAMN;AACA;;AAKJ;AAEI;AACF;AAGF;AAEI;AACF;AAIF;AAEI;;;AAEA;AACA;;;AAEA;AACA;;;AAEA;;AACA;AACA;AACA;;;;;;AAUE;;AAED;AACD;AACA;AACA;AACF;AAIF;AAEI;AACA;AACA;;;AAEA;AACA;;;AAIE;;;AAGE;AACA;AACD;;;;AAKH;AACE;;;AAIF;AACA;AACA;AACA;;AAEA;;AAEA;;AAEE;;AAED;AACH;AAIF;AAEI;AACA;AACE;;;AAGE;AACA;;;;AAIF;;AAEE;;AAEF;;AAEE;;AAEF;;;;AAGE;AACA;;;;AAGA;AACA;AACA;;;AAGF;;;;;AAIE;;;AAGF;;AAEE;;AACA;;;;AAGA;AACA;AACA;AACA;;;AAGF;;;AAGJ;AAIF;;;;;;;;AASG;;;;;;;;AASA;AAGH;AACF;;"}
|
|
1
|
+
{"version":3,"file":"useTimelineGestures.js","sources":["src/components/editor/Timeline/useTimelineGestures.ts"],"sourcesContent":["'use client';\n\nimport { useCallback, useMemo, useRef } from 'react';\n\nimport { useLatest } from '@/hooks';\n\nimport { clamp, snapFrame, xToFrame, yToValue } from './timelineCoords';\nimport {\n addKeyframe,\n copySelectedKeyframes,\n makeKeyframe,\n moveSelectedKeyframes,\n moveSelectedKeyframesGraph,\n pasteKeyframes,\n removeSelectedKeyframes,\n setKeyframeTangent,\n valueAtPointer,\n} from './timelineEdits';\nimport { hitTestTimeline, keyframesInRect } from './timelineHitTest';\nimport { sameRef } from './timelineSelection';\n\nimport type {\n TimelineGroup,\n TimelineKeyframeRef,\n TimelineLoop,\n TimelineSelection,\n TimelineTrack,\n TimelineView,\n} from './Timeline.types';\nimport type { TimelineClipboard } from './timelineEdits';\nimport type { TimelineRow, TrackGeometry } from './timelineLayout';\nimport type { TimelineStore } from './TimelineStore';\nimport type { Point2D } from '@/components/primitives/canvas/canvas.types';\nimport type { ViewportSize } from '@/components/primitives/viewport';\nimport type React from 'react';\n\nconst CLICK_THRESHOLD_PX = 3;\nconst KEYBOARD_LARGE_STEP = 10;\n\nexport interface TimelineGestureActions {\n /** Scrub live (snaps/clamps, fires onFrameChange). */\n seekLive: (frame: number) => void;\n /** Scrub commit (snaps/clamps, fires onFrameChange + onFrameChangeComplete). */\n seekCommit: (frame: number) => void;\n setSelection: (selection: TimelineSelection) => void;\n /** Live track update (onTracksChange). */\n setTracks: (tracks: TimelineTrack[]) => void;\n /** Commit (onTracksChangeComplete). */\n commitTracks: (tracks: TimelineTrack[]) => void;\n setView: (view: TimelineView) => void;\n setLoop: (loop: TimelineLoop) => void;\n scrollBy: (deltaPixels: number) => void;\n setGroups: (groups: TimelineGroup[]) => void;\n}\n\nexport interface UseTimelineGesturesOptions extends TimelineGestureActions {\n containerRef: React.RefObject<HTMLDivElement | null>;\n store: TimelineStore;\n view: TimelineView;\n size: ViewportSize;\n tracks: ReadonlyArray<TimelineTrack>;\n selection: TimelineSelection;\n frame: number;\n startFrame: number;\n endFrame: number;\n trackHeight: number;\n scrollTop: number;\n rulerHeight: number;\n loopStripHeight: number;\n loopHandles: 'edges' | 'brackets';\n snap: boolean | number;\n editable: boolean;\n allowAddKeyframe: boolean;\n allowDeleteKeyframe: boolean;\n showPlayhead: boolean;\n rows: ReadonlyArray<TimelineRow>;\n trackTops: ReadonlyMap<string, TrackGeometry>;\n expandedHeight: number;\n groups: ReadonlyArray<TimelineGroup>;\n minFramesVisible: number;\n maxFramesVisible: number | undefined;\n loopRegion: { startFrame: number; endFrame: number } | null;\n maxScrollTop: number;\n}\n\ninterface PointerState {\n pointerId: number;\n mode: 'scrub' | 'move' | 'marquee' | 'pan' | 'tangent' | 'loop';\n startX: number;\n startY: number;\n origTracks?: ReadonlyArray<TimelineTrack>;\n moveSelection?: TimelineSelection;\n graphMove?: boolean;\n startFramePos?: number;\n startView?: TimelineView;\n additive?: boolean;\n tangentRef?: TimelineKeyframeRef;\n tangentWhich?: 'in' | 'out';\n loopWhich?: 'start' | 'end' | 'body' | 'create';\n startLoop?: { startFrame: number; endFrame: number };\n}\n\nexport interface UseTimelineGesturesReturn {\n handlers: {\n onPointerDown: (e: React.PointerEvent<HTMLDivElement>) => void;\n onPointerMove: (e: React.PointerEvent<HTMLDivElement>) => void;\n onPointerUp: (e: React.PointerEvent<HTMLDivElement>) => void;\n onPointerCancel: (e: React.PointerEvent<HTMLDivElement>) => void;\n onPointerLeave: () => void;\n onDoubleClick: (e: React.MouseEvent<HTMLDivElement>) => void;\n onKeyDown: (e: React.KeyboardEvent<HTMLDivElement>) => void;\n };\n /** Attach via a native non-passive listener so `preventDefault` works. */\n onWheel: (e: WheelEvent) => void;\n}\n\nfunction localPoint(\n e: { clientX: number; clientY: number },\n container: HTMLDivElement | null\n): Point2D {\n if (!container) return { x: 0, y: 0 };\n const rect = container.getBoundingClientRect();\n return { x: e.clientX - rect.left, y: e.clientY - rect.top };\n}\n\nfunction nextSelection(\n selection: TimelineSelection,\n ref: TimelineKeyframeRef,\n additive: boolean\n): TimelineSelection {\n const exists = selection.some(r => sameRef(r, ref));\n if (additive) {\n return exists\n ? selection.filter(r => !sameRef(r, ref))\n : [...selection, ref];\n }\n return exists ? selection : [ref];\n}\n\nfunction mergeSelection(\n base: TimelineSelection,\n add: ReadonlyArray<TimelineKeyframeRef>\n): TimelineSelection {\n const result = [...base];\n for (const ref of add) {\n if (!result.some(r => sameRef(r, ref))) result.push(ref);\n }\n return result;\n}\n\n/**\n * Pointer / wheel / keyboard gesture machine for `<Timeline>`. The hook is\n * called every render with a fresh `options` object (live props + the\n * component's action callbacks, several of which change identity on every\n * frame). They are read through a single `useLatest` ref so the returned\n * handlers keep a **stable identity for the component's lifetime** — they are\n * attached to the DOM (`onPointerDown=…`) and to a native `wheel` listener, so\n * stability is what keeps listeners from re-attaching every render. Reads\n * happen at event time, by which point the ref already holds the latest\n * committed options. Drag state (cursor + marquee) is written to the store,\n * not React state.\n */\nexport function useTimelineGestures(\n options: UseTimelineGesturesOptions\n): UseTimelineGesturesReturn {\n // Single live snapshot of every option/action. `useLatest` keeps it synced\n // to the most recently committed render, so handlers below never need the\n // individual values in their dependency arrays.\n const optsRef = useLatest(options);\n\n const stateRef = useRef<PointerState | null>(null);\n const clipboardRef = useRef<TimelineClipboard | null>(null);\n\n const hitAt = useCallback(\n (point: Point2D) => {\n const o = optsRef.current;\n return hitTestTimeline({\n point,\n view: o.view,\n size: o.size,\n rows: o.rows,\n scrollTop: o.scrollTop,\n rulerHeight: o.rulerHeight,\n loopStripHeight: o.loopStripHeight,\n loopHandles: o.loopHandles,\n frame: o.frame,\n showPlayhead: o.showPlayhead,\n loopRegion: o.loopRegion,\n isSelected: (t, k) =>\n o.selection.some(r => r.trackId === t && r.keyframeId === k),\n });\n },\n [optsRef]\n );\n\n const moveFromState = useCallback(\n (state: PointerState, point: Point2D): TimelineTrack[] => {\n const o = optsRef.current;\n const view = o.view;\n const width = o.size.width;\n const curFrame = xToFrame(point.x, view, width);\n const delta = curFrame - (state.startFramePos ?? curFrame);\n const origTracks = state.origTracks ?? o.tracks;\n const sel = state.moveSelection ?? [];\n if (state.graphMove) {\n return moveSelectedKeyframesGraph(\n origTracks,\n sel,\n delta,\n point.y - state.startY,\n o.trackHeight,\n o.expandedHeight,\n o.snap,\n o.startFrame,\n o.endFrame\n );\n }\n return moveSelectedKeyframes(\n origTracks,\n sel,\n delta,\n o.snap,\n o.startFrame,\n o.endFrame\n );\n },\n [optsRef]\n );\n\n const tangentOffset = useCallback(\n (state: PointerState, point: Point2D): { x: number; y: number } | null => {\n const o = optsRef.current;\n const ref = state.tangentRef;\n if (!ref) return null;\n const geom = o.trackTops.get(ref.trackId);\n if (!geom) return null;\n const tracks = state.origTracks ?? o.tracks;\n const track = tracks.find(t => t.id === ref.trackId);\n const kf = track?.keyframes.find(k => k.id === ref.keyframeId);\n if (!track || !kf) return null;\n const screenTop =\n o.rulerHeight + o.loopStripHeight + geom.top - o.scrollTop;\n const range = geom.range;\n return {\n x: xToFrame(point.x, o.view, o.size.width) - kf.x,\n y: yToValue(point.y, range, screenTop, geom.height) - kf.y,\n };\n },\n [optsRef]\n );\n\n const onPointerDown = useCallback(\n (e: React.PointerEvent<HTMLDivElement>): void => {\n const o = optsRef.current;\n const container = o.containerRef.current;\n if (!container) return;\n const point = localPoint(e, container);\n const view = o.view;\n const width = o.size.width;\n\n if (e.button === 1) {\n e.preventDefault();\n container.setPointerCapture(e.pointerId);\n stateRef.current = {\n pointerId: e.pointerId,\n mode: 'pan',\n startX: point.x,\n startY: point.y,\n startView: view,\n };\n o.store.setDrag({ kind: 'pan', marquee: null });\n return;\n }\n if (e.button !== 0) return;\n\n const hit = hitAt(point);\n\n if (\n hit.kind === 'loop-start' ||\n hit.kind === 'loop-end' ||\n hit.kind === 'loop-body'\n ) {\n const region = o.loopRegion;\n if (!region) return;\n e.preventDefault();\n container.setPointerCapture(e.pointerId);\n stateRef.current = {\n pointerId: e.pointerId,\n mode: 'loop',\n startX: point.x,\n startY: point.y,\n loopWhich:\n hit.kind === 'loop-start'\n ? 'start'\n : hit.kind === 'loop-end'\n ? 'end'\n : 'body',\n startLoop: region,\n };\n o.store.setDrag({ kind: 'scrub', marquee: null });\n return;\n }\n\n // Alt+drag on the ruler creates / narrows the loop region — works\n // even when looping is currently off (the drag flips `loop` to a\n // sub-range object via `setLoop`). Gated on the ruler band so it still\n // fires when the drag starts right on the playhead line.\n if (\n e.altKey &&\n point.y < o.rulerHeight &&\n (hit.kind === 'ruler' || hit.kind === 'playhead')\n ) {\n e.preventDefault();\n container.setPointerCapture(e.pointerId);\n const anchorFrame = xToFrame(point.x, view, width);\n const anchor = clamp(anchorFrame, o.startFrame, o.endFrame);\n stateRef.current = {\n pointerId: e.pointerId,\n mode: 'loop',\n startX: point.x,\n startY: point.y,\n loopWhich: 'create',\n startLoop: { startFrame: anchor, endFrame: anchor },\n };\n o.store.setDrag({ kind: 'scrub', marquee: null });\n o.setLoop({ startFrame: anchor, endFrame: anchor });\n return;\n }\n\n // Plain drag on the dedicated loop strip = loop create (no Alt needed).\n if (hit.kind === 'loop-strip') {\n e.preventDefault();\n container.setPointerCapture(e.pointerId);\n const anchorFrame = xToFrame(point.x, view, width);\n const anchor = clamp(anchorFrame, o.startFrame, o.endFrame);\n stateRef.current = {\n pointerId: e.pointerId,\n mode: 'loop',\n startX: point.x,\n startY: point.y,\n loopWhich: 'create',\n startLoop: { startFrame: anchor, endFrame: anchor },\n };\n o.store.setDrag({ kind: 'scrub', marquee: null });\n o.setLoop({ startFrame: anchor, endFrame: anchor });\n return;\n }\n\n if (hit.kind === 'ruler' || hit.kind === 'playhead') {\n e.preventDefault();\n container.setPointerCapture(e.pointerId);\n stateRef.current = {\n pointerId: e.pointerId,\n mode: 'scrub',\n startX: point.x,\n startY: point.y,\n };\n o.store.setDrag({ kind: 'scrub', marquee: null });\n o.seekLive(xToFrame(point.x, view, width));\n return;\n }\n\n if (hit.kind === 'tangent') {\n e.preventDefault();\n container.setPointerCapture(e.pointerId);\n stateRef.current = {\n pointerId: e.pointerId,\n mode: 'tangent',\n startX: point.x,\n startY: point.y,\n origTracks: o.tracks,\n tangentRef: { trackId: hit.trackId, keyframeId: hit.keyframeId },\n tangentWhich: hit.which,\n };\n o.store.setDrag({ kind: 'move', marquee: null });\n return;\n }\n\n if (hit.kind === 'group') {\n const groups = o.groups;\n o.setGroups(\n groups.map(g =>\n g.id === hit.groupId ? { ...g, collapsed: !g.collapsed } : g\n )\n );\n return;\n }\n\n if (hit.kind === 'keyframe') {\n const additive = e.shiftKey || e.ctrlKey || e.metaKey;\n const ref = { trackId: hit.trackId, keyframeId: hit.keyframeId };\n const sel = nextSelection(o.selection, ref, additive);\n o.setSelection(sel);\n if (o.editable) {\n e.preventDefault();\n container.setPointerCapture(e.pointerId);\n stateRef.current = {\n pointerId: e.pointerId,\n mode: 'move',\n startX: point.x,\n startY: point.y,\n origTracks: o.tracks,\n moveSelection: sel,\n graphMove: o.trackTops.get(hit.trackId)?.graph ?? false,\n startFramePos: xToFrame(point.x, view, width),\n };\n o.store.setDrag({ kind: 'move', marquee: null });\n }\n return;\n }\n\n // empty / outside → marquee box-select\n const additive = e.shiftKey || e.ctrlKey || e.metaKey;\n e.preventDefault();\n container.setPointerCapture(e.pointerId);\n stateRef.current = {\n pointerId: e.pointerId,\n mode: 'marquee',\n startX: point.x,\n startY: point.y,\n additive,\n };\n o.store.setDrag({\n kind: 'marquee',\n marquee: { x: point.x, y: point.y, width: 0, height: 0 },\n });\n },\n [optsRef, hitAt]\n );\n\n const onPointerMove = useCallback(\n (e: React.PointerEvent<HTMLDivElement>): void => {\n const o = optsRef.current;\n const container = o.containerRef.current;\n if (!container) return;\n const point = localPoint(e, container);\n const state = stateRef.current;\n\n // Hover-tracking: only when no active drag (so the diamond / circle can\n // grow under the cursor as a \"grabbable\" affordance; playhead glows\n // when the cursor is over the line or its head).\n if (!state) {\n const hit = hitAt(point);\n if (hit.kind === 'keyframe') {\n o.store.setHover({\n trackId: hit.trackId,\n keyframeId: hit.keyframeId,\n });\n } else {\n o.store.setHover(null);\n }\n o.store.setHoverPlayhead(hit.kind === 'playhead');\n o.store.setHoverLoop(\n hit.kind === 'loop-start'\n ? 'start'\n : hit.kind === 'loop-end'\n ? 'end'\n : hit.kind === 'loop-body'\n ? 'body'\n : null\n );\n }\n\n if (state?.pointerId !== e.pointerId) return;\n const view = o.view;\n const width = o.size.width;\n\n if (state.mode === 'loop') {\n const frame = xToFrame(point.x, view, width);\n const sl = state.startLoop;\n if (sl && state.loopWhich === 'create') {\n const cur = clamp(frame, o.startFrame, o.endFrame);\n o.setLoop({\n startFrame: Math.min(sl.startFrame, cur),\n endFrame: Math.max(sl.startFrame, cur),\n });\n } else if (sl && state.loopWhich === 'start') {\n o.setLoop({\n startFrame: clamp(frame, o.startFrame, sl.endFrame),\n endFrame: sl.endFrame,\n });\n } else if (sl && state.loopWhich === 'end') {\n o.setLoop({\n startFrame: sl.startFrame,\n endFrame: clamp(frame, sl.startFrame, o.endFrame),\n });\n } else if (sl) {\n const span = sl.endFrame - sl.startFrame;\n const delta = frame - xToFrame(state.startX, view, width);\n const ns = clamp(\n sl.startFrame + delta,\n o.startFrame,\n o.endFrame - span\n );\n o.setLoop({ startFrame: ns, endFrame: ns + span });\n }\n return;\n }\n if (state.mode === 'scrub') {\n o.seekLive(xToFrame(point.x, view, width));\n return;\n }\n if (state.mode === 'pan') {\n const startView = state.startView ?? view;\n const span = startView.endFrame - startView.startFrame;\n const dxFrames = ((point.x - state.startX) / Math.max(1, width)) * span;\n o.setView({\n startFrame: startView.startFrame - dxFrames,\n endFrame: startView.endFrame - dxFrames,\n });\n return;\n }\n if (state.mode === 'tangent') {\n const offset = tangentOffset(state, point);\n if (offset && state.tangentRef && state.tangentWhich) {\n o.setTracks(\n setKeyframeTangent(\n state.origTracks ?? o.tracks,\n state.tangentRef,\n state.tangentWhich,\n offset\n )\n );\n }\n return;\n }\n if (state.mode === 'move') {\n o.setTracks(moveFromState(state, point));\n return;\n }\n // Only 'marquee' remains after the early returns above.\n o.store.setDrag({\n kind: 'marquee',\n marquee: {\n x: Math.min(state.startX, point.x),\n y: Math.min(state.startY, point.y),\n width: Math.abs(point.x - state.startX),\n height: Math.abs(point.y - state.startY),\n },\n });\n },\n [optsRef, hitAt, tangentOffset, moveFromState]\n );\n\n const onPointerLeave = useCallback((): void => {\n const o = optsRef.current;\n o.store.setHover(null);\n o.store.setHoverPlayhead(false);\n o.store.setHoverLoop(null);\n }, [optsRef]);\n\n const endGesture = useCallback(\n (e: React.PointerEvent<HTMLDivElement>, cancelled: boolean): void => {\n const o = optsRef.current;\n const state = stateRef.current;\n if (state?.pointerId !== e.pointerId) return;\n const container = o.containerRef.current;\n if (container?.hasPointerCapture(e.pointerId)) {\n container.releasePointerCapture(e.pointerId);\n }\n const point = localPoint(e, container);\n const view = o.view;\n const size = o.size;\n\n if (!cancelled) {\n if (state.mode === 'loop' && state.loopWhich === 'create') {\n // Bare Alt-click (no drag) on the ruler: don't leave a zero-width\n // loop hanging around — clear it.\n if (Math.abs(point.x - state.startX) < CLICK_THRESHOLD_PX) {\n o.setLoop(false);\n }\n } else if (state.mode === 'scrub') {\n o.seekCommit(xToFrame(point.x, view, size.width));\n } else if (state.mode === 'move') {\n o.commitTracks(moveFromState(state, point));\n } else if (state.mode === 'tangent') {\n const offset = tangentOffset(state, point);\n if (offset && state.tangentRef && state.tangentWhich) {\n o.commitTracks(\n setKeyframeTangent(\n state.origTracks ?? o.tracks,\n state.tangentRef,\n state.tangentWhich,\n offset\n )\n );\n }\n } else if (state.mode === 'marquee') {\n const rect = {\n x: Math.min(state.startX, point.x),\n y: Math.min(state.startY, point.y),\n width: Math.abs(point.x - state.startX),\n height: Math.abs(point.y - state.startY),\n };\n if (\n rect.width < CLICK_THRESHOLD_PX &&\n rect.height < CLICK_THRESHOLD_PX\n ) {\n if (!state.additive) o.setSelection([]);\n } else {\n const found = keyframesInRect(\n {\n x: rect.x,\n y: rect.y - o.rulerHeight - o.loopStripHeight + o.scrollTop,\n width: rect.width,\n height: rect.height,\n },\n view,\n size,\n o.rows\n );\n const base = state.additive ? o.selection : [];\n o.setSelection(mergeSelection(base, found));\n }\n }\n }\n\n o.store.setDrag({ kind: 'none', marquee: null });\n stateRef.current = null;\n },\n [optsRef, moveFromState, tangentOffset]\n );\n\n const onPointerUp = useCallback(\n (e: React.PointerEvent<HTMLDivElement>): void => {\n endGesture(e, false);\n },\n [endGesture]\n );\n const onPointerCancel = useCallback(\n (e: React.PointerEvent<HTMLDivElement>): void => {\n endGesture(e, true);\n },\n [endGesture]\n );\n\n const onDoubleClick = useCallback(\n (e: React.MouseEvent<HTMLDivElement>): void => {\n const o = optsRef.current;\n if (!o.editable || !o.allowAddKeyframe) return;\n const container = o.containerRef.current;\n if (!container) return;\n const point = localPoint(e, container);\n const hit = hitAt(point);\n if (hit.kind !== 'empty') return;\n const track = o.tracks.find(t => t.id === hit.trackId);\n if (!track || track.locked) return;\n const view = o.view;\n const width = o.size.width;\n const frame = clamp(\n snapFrame(xToFrame(point.x, view, width), o.snap),\n o.startFrame,\n o.endFrame\n );\n const value = valueAtPointer({\n pointerY: point.y,\n frame,\n track,\n geometry: o.trackTops.get(track.id),\n rulerHeight: o.rulerHeight + o.loopStripHeight,\n scrollTop: o.scrollTop,\n });\n const next = addKeyframe(o.tracks, track.id, makeKeyframe(frame, value));\n o.setTracks(next);\n o.commitTracks(next);\n },\n [optsRef, hitAt]\n );\n\n const onWheel = useCallback(\n (e: WheelEvent): void => {\n const o = optsRef.current;\n const container = o.containerRef.current;\n if (!container) return;\n e.preventDefault();\n const view = o.view;\n const width = Math.max(1, o.size.width);\n const span = view.endFrame - view.startFrame;\n\n if (e.shiftKey || Math.abs(e.deltaX) > Math.abs(e.deltaY)) {\n const delta = e.shiftKey ? e.deltaY : e.deltaX;\n const panFrames = (delta / width) * span;\n o.setView({\n startFrame: view.startFrame + panFrames,\n endFrame: view.endFrame + panFrames,\n });\n return;\n }\n\n // Plain vertical wheel scrolls tracks when they overflow; Ctrl/Cmd zooms.\n if (!e.ctrlKey && !e.metaKey && o.maxScrollTop > 0) {\n o.scrollBy(e.deltaY);\n return;\n }\n\n const rangeSpan = Math.max(1, o.endFrame - o.startFrame);\n const maxSpan = o.maxFramesVisible ?? rangeSpan;\n const factor = Math.exp(e.deltaY * 0.0015);\n const newSpan = clamp(span * factor, o.minFramesVisible, maxSpan);\n const point = localPoint(e, container);\n const frac = point.x / width;\n const cursorFrame = view.startFrame + frac * span;\n const newStart = cursorFrame - frac * newSpan;\n o.setView({\n startFrame: newStart,\n endFrame: newStart + newSpan,\n });\n },\n [optsRef]\n );\n\n const onKeyDown = useCallback(\n (e: React.KeyboardEvent<HTMLDivElement>): void => {\n const o = optsRef.current;\n switch (e.key) {\n case 'ArrowLeft':\n case 'ArrowRight': {\n e.preventDefault();\n const dir = e.key === 'ArrowLeft' ? -1 : 1;\n const step = (e.shiftKey ? KEYBOARD_LARGE_STEP : 1) * dir;\n o.seekCommit(o.frame + step);\n return;\n }\n case 'Home':\n e.preventDefault();\n o.seekCommit(o.startFrame);\n return;\n case 'End':\n e.preventDefault();\n o.seekCommit(o.endFrame);\n return;\n case 'Delete':\n case 'Backspace': {\n if (!o.editable || !o.allowDeleteKeyframe) return;\n const sel = o.selection;\n if (sel.length === 0) return;\n e.preventDefault();\n const next = removeSelectedKeyframes(o.tracks, sel);\n o.setTracks(next);\n o.commitTracks(next);\n o.setSelection([]);\n return;\n }\n case 'c':\n case 'C': {\n if (!(e.ctrlKey || e.metaKey)) return;\n e.preventDefault();\n clipboardRef.current = copySelectedKeyframes(o.tracks, o.selection);\n return;\n }\n case 'v':\n case 'V': {\n if (!(e.ctrlKey || e.metaKey) || !o.editable) return;\n const clip = clipboardRef.current;\n if (!clip || clip.entries.length === 0) return;\n e.preventDefault();\n const pasted = pasteKeyframes(o.tracks, clip, o.frame);\n o.setTracks(pasted.tracks);\n o.commitTracks(pasted.tracks);\n o.setSelection(pasted.refs);\n return;\n }\n default:\n return;\n }\n },\n [optsRef]\n );\n\n const handlers = useMemo(\n () => ({\n onPointerDown,\n onPointerMove,\n onPointerUp,\n onPointerCancel,\n onPointerLeave,\n onDoubleClick,\n onKeyDown,\n }),\n [\n onPointerDown,\n onPointerMove,\n onPointerUp,\n onPointerCancel,\n onPointerLeave,\n onDoubleClick,\n onKeyDown,\n ]\n );\n\n return { handlers, onWheel };\n}\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAoCA;AACA;AA+EA;AAIE;;AACA;;AAEF;AAEA;AAKE;;AAEE;AACE;AACA;;;AAGN;AAEA;AAIE;AACA;AACE;AAAwC;;AAE1C;AACF;AAEA;;;;;;;;;;;AAWG;AACG;;;;AAMJ;AAEA;AACA;AAEA;AAEI;AACA;;;;;;;;;;;;AAYE;AAED;AACH;;AAME;AACA;AACA;AACA;;;AAGA;AACA;AACE;;;AAoBJ;;AAME;AACA;AACA;AAAU;AACV;AACA;AAAW;;AAEX;;AAEA;AAAmB;AACnB;AAEA;;;AAGE;;AAEJ;AAIF;AAEI;AACA;AACA;;;AAEA;AACA;AAEA;;AAEE;;;AAGE;;;AAGA;;AAEF;;;AAGF;;AAEA;AAEA;;AAGE;AAEA;AACA;;;AAEA;;;AAGE;;;AAGA;AAEI;AACA;AACE;AACA;AACN;;AAEF;;;;;;;;AAUA;AACA;;AAGA;AACA;AACA;;;AAGE;;;AAGA;;;AAGF;AACA;;;;AAKF;;AAEE;AACA;AACA;;;AAGE;;;AAGA;;;AAGF;AACA;;;AAIF;;AAEE;;;AAGE;;;;AAIF;AACA;;;AAIF;;AAEE;;;AAGE;;;;AAIA;;;AAGF;;;AAIF;AACE;AACA;;;AAQF;AACE;AACA;AACA;AACA;AACA;;AAEE;;;AAGE;;;;AAIA;AACA;;;AAGF;;;;;AAMJ;;AAEA;;;AAGE;;;;;AAKF;AACE;;AAED;AACH;AAIF;AAEI;AACA;AACA;;;AAEA;;;;;AAME;AACA;AACE;;;AAGC;;;AAED;;;;AAKE;AACA;AACE;AACA;AACE;;;AAKV;;AACA;AACA;AAEA;AACE;AACA;;AAEE;;;;AAIC;;;;AAGC;;AAED;;;;;AAIC;AACD;;;;AAGD;;AAMA;;;;AAIJ;AACE;;;AAGF;AACE;;;;AAIE;AACA;AACD;;;AAGH;;;;;;;AAcA;;;;;AAKA;AACE;AACA;AACE;AACA;AACA;AACA;AACD;AACF;;AAKL;AACE;AACA;AACA;AACA;AACF;;AAII;AACA;AACA;;AACA;;AAEE;;;AAGF;AACA;;AAGE;;;AAGE;AACE;;;AAEG;AACL;;AACK;;;AAEA;;;;;;AAYA;AACL;AACE;AACA;AACA;AACA;;AAEF;AAEE;;AAEqB;;;;;AAKjB;;;;AAQJ;;;;;AAMN;AACA;;AAKJ;AAEI;AACF;AAGF;AAEI;AACF;AAIF;AAEI;;;AAEA;AACA;;;AAEA;AACA;;;AAEA;;AACA;AACA;AACA;;;;;;AAUE;;AAED;AACD;AACA;AACA;AACF;AAIF;AAEI;AACA;AACA;;;AAEA;AACA;;;AAIE;;;AAGE;AACA;AACD;;;;AAKH;AACE;;;AAIF;AACA;AACA;AACA;;AAEA;;AAEA;;AAEE;;AAED;AACH;AAIF;AAEI;AACA;AACE;;;AAGE;AACA;;;;AAIF;;AAEE;;AAEF;;AAEE;;AAEF;;;;AAGE;AACA;;;;AAGA;AACA;AACA;;;AAGF;;;;;AAIE;;;AAGF;;AAEE;;AACA;;;;AAGA;AACA;AACA;AACA;;;AAGF;;;AAGJ;AAIF;;;;;;;;AASG;;;;;;;;AASA;AAGH;AACF;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useTimelinePlayback.js","sources":["
|
|
1
|
+
{"version":3,"file":"useTimelinePlayback.js","sources":["src/components/editor/Timeline/useTimelinePlayback.ts"],"sourcesContent":["'use client';\n\nimport { useEffect, useEffectEvent, useRef } from 'react';\n\nimport { resolveLoop } from './timelineCoords';\n\nimport type { TimelineLoop } from './Timeline.types';\n\nexport interface PlaybackRange {\n startFrame: number;\n endFrame: number;\n}\n\nexport interface AdvanceResult {\n frame: number;\n /** True when a non-looping playback has reached the end. */\n ended: boolean;\n}\n\n/**\n * Advance a playhead by `dtSeconds` of wall-clock time at `fps`. Forward only.\n * With a `loop` region the playhead wraps within it; without one it stops at\n * `range.endFrame`. Pure — the rAF hook below drives it.\n */\nexport function advancePlayback(\n current: number,\n dtSeconds: number,\n fps: number,\n range: PlaybackRange,\n loop: PlaybackRange | null\n): AdvanceResult {\n const next = current + Math.max(0, dtSeconds) * Math.max(0, fps);\n if (loop) {\n if (next < loop.endFrame) return { frame: next, ended: false };\n const span = Math.max(1e-6, loop.endFrame - loop.startFrame);\n return {\n frame: loop.startFrame + ((next - loop.startFrame) % span),\n ended: false,\n };\n }\n if (next >= range.endFrame) return { frame: range.endFrame, ended: true };\n return { frame: next, ended: false };\n}\n\ninterface UseTimelinePlaybackOptions {\n playing: boolean;\n frame: number;\n fps: number;\n startFrame: number;\n endFrame: number;\n loop: TimelineLoop | undefined;\n /** Push the advanced (float) frame — the component clamps + fires onFrameChange. */\n onAdvance: (frame: number) => void;\n /** Called once when non-looping playback reaches the end. */\n onEnd: () => void;\n}\n\n/**\n * Drives the playhead while `playing` is true: a rAF loop advancing the frame\n * in real time at `fps`. Keeps its own float accumulator (independent of the\n * snapped/committed frame) and resyncs if the frame is changed externally\n * (e.g. the user scrubs mid-playback).\n */\nexport function useTimelinePlayback(opts: UseTimelinePlaybackOptions): void {\n const accRef = useRef(opts.frame);\n const lastEmittedRef = useRef(opts.frame);\n\n // Canonical `useEffectEvent` use case: `tick` is called *only* from inside\n // the rAF loop below (never attached to JSX or passed out), so it may read\n // the latest props/state without re-arming the loop. This is the React\n // \"timer with latest values\" pattern — keep it here; do not convert it to a\n // handler that escapes the effect.\n const tick = useEffectEvent((dt: number): boolean => {\n // External scrub during playback → resync the accumulator.\n if (opts.frame !== lastEmittedRef.current) {\n accRef.current = opts.frame;\n }\n const range: PlaybackRange = {\n startFrame: opts.startFrame,\n endFrame: opts.endFrame,\n };\n const region = resolveLoop(opts.loop, range.startFrame, range.endFrame);\n const result = advancePlayback(accRef.current, dt, opts.fps, range, region);\n accRef.current = result.frame;\n lastEmittedRef.current = result.frame;\n opts.onAdvance(result.frame);\n if (result.ended) {\n opts.onEnd();\n return true;\n }\n return false;\n });\n\n useEffect(() => {\n if (!opts.playing || typeof window === 'undefined') return;\n accRef.current = opts.frame;\n lastEmittedRef.current = opts.frame;\n let raf = 0;\n let last = performance.now();\n const loop = (now: number): void => {\n const dt = (now - last) / 1000;\n last = now;\n const ended = tick(dt);\n if (!ended) raf = requestAnimationFrame(loop);\n };\n raf = requestAnimationFrame(loop);\n return () => {\n cancelAnimationFrame(raf);\n };\n // Only `playing` drives the rAF lifecycle. `opts.frame` is read solely to\n // seed the accumulator at playback start; including it would restart the\n // loop on every emitted frame, so it is intentionally excluded.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [opts.playing]);\n}\n"],"names":[],"mappings":";;;;AAmBA;;;;AAIG;AACG;;;AASF;;AACA;;AAEE;AACA;;;AAGJ;;;AAEF;AAeA;;;;;AAKG;AACG;;;;;;;;AASJ;;;AAGI;;AAEF;;;;AAIA;AACA;AACA;AACA;AACA;AACA;;AAEE;;AAEF;AACF;;;;AAIE;AACA;;AAEA;AACA;;;AAGE;AACA;AAAY;AACd;AACA;AACA;;AAEA;;;;;AAKF;AACF;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TransformControl.js","sources":["../../../../../../src/components/editor/TransformControl/TransformControl.tsx"],"sourcesContent":[null],"names":[],"mappings":";;;;;;;;;;;;AA4BA;AAEA;AACE;AACA;AACA;;AAGF;AACE;AACA;AACA;;AAGF;AACE;AACA;AACA;AACA;;AAGF;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AAEA;AACE;AACA;AACA;AACD;AAED;AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2BG;AACI;AAyBL;AACE;;;AAGA;AACD;AAED;AAEI;AACA;AACA;AACA;AACD;AAEH;AACE;AACA;AACA;AACA;AACD;AAED;AACA;AACA;AACA;AAEA;AACE;AACA;AACA;AACA;;AAGF;AACE;AACA;AACA;;AAGF;AACE;AACA;AACA;;AAGF;AACE;AACA;AACA;;AAGF;;AAOA;AAEI;AACF;AAIF;AAEI;AACF;;AAME;;;AAGE;;;AAEA;;AAEJ;AAIF;;AAEA;AAEA;;;;AAIE;;AAMF;AACE;AACF;AAEA;AACE;AACF;AAEA;AACE;AACF;;AAIA;AAiIF;AAEA;;"}
|
|
1
|
+
{"version":3,"file":"TransformControl.js","sources":["src/components/editor/TransformControl/TransformControl.tsx"],"sourcesContent":["'use client';\n\nimport React, { useCallback, useMemo } from 'react';\n\nimport { Select } from '@/components/controls/Select';\nimport { VectorInput } from '@/components/controls/VectorInput';\nimport { PropertyRow } from '@/components/editor/PropertyInspector/PropertyRow';\nimport { LockIcon } from '@/components/Icons/LockIcon';\nimport { UnlockIcon } from '@/components/Icons/UnlockIcon';\nimport { useControlledState } from '@/hooks/useControlledState';\nimport { cx } from '@/utils/cx';\n\nimport {\n lockButtonRecipe,\n lockSpacerRecipe,\n rowInner,\n rowVectorWrapper,\n transformRoot,\n} from './TransformControl.css';\n\nimport type {\n CoordinateSpace,\n CoordinateSpaceOption,\n TransformControlProps,\n TransformValue,\n Vec3,\n} from './TransformControl.types';\n\n// --- Defaults ---\n\nconst DEFAULT_TRANSFORM: TransformValue = {\n position: { x: 0, y: 0, z: 0 },\n rotation: { x: 0, y: 0, z: 0 },\n scale: { x: 1, y: 1, z: 1 },\n};\n\nconst DEFAULT_COORDINATE_SPACE_OPTIONS: CoordinateSpaceOption[] = [\n { value: 'local', label: 'Local' },\n { value: 'world', label: 'World' },\n { value: 'parent', label: 'Parent' },\n];\n\nconst DEFAULT_LABELS = {\n position: 'Position',\n rotation: 'Rotation',\n scale: 'Scale',\n coordinateSpace: 'Space',\n} as const;\n\nconst DEFAULT_PRECISION = { position: 3, rotation: 1, scale: 3 } as const;\nconst DEFAULT_STEP = { position: 0.1, rotation: 1, scale: 0.01 } as const;\nconst DEFAULT_UNITS = { position: 'm', rotation: '°', scale: '' } as const;\n\n// Narrow label column so the three axis inputs (with units) have room\n// to render without clipping inside typical 320–400 px property panels.\nconst ROW_SPLIT_RATIO: [number, number] = [25, 75];\n\n// --- Helpers ---\n\nconst vecToArray = (v: Vec3): number[] => [v.x, v.y, v.z];\n\nconst arrayToVec = (arr: number[]): Vec3 => ({\n x: arr[0] ?? 0,\n y: arr[1] ?? 0,\n z: arr[2] ?? 0,\n});\n\n// --- Component ---\n\n/**\n * The canonical position / rotation / scale property control for 3D\n * editor interfaces. Composes `VectorInput` (per row) with `Select`\n * (coordinate space) and `PropertyRow` (layout) into one high-level\n * component, mirroring the transform widget found in Blender, Unity and\n * Unreal.\n *\n * Three independent atoms — `value`, `coordinateSpace`, `linkedScale` —\n * each support controlled and uncontrolled usage.\n *\n * Changing the coordinate-space dropdown does NOT transform the numeric\n * values. The component is purely a UI surface; the consumer's editor\n * logic is responsible for re-projecting values when the space changes.\n *\n * The component intentionally renders no `PropertySection` wrapper — slot\n * it inside an existing section in your panel.\n *\n * @example\n * ```tsx\n * <PropertySection title=\"Transform\">\n * <TransformControl\n * value={transform}\n * onChange={setTransform}\n * showReset\n * />\n * </PropertySection>\n * ```\n */\nexport const TransformControl: React.FC<TransformControlProps> = ({\n value: valueProp,\n defaultValue,\n onChange,\n coordinateSpace: coordinateSpaceProp,\n defaultCoordinateSpace,\n onCoordinateSpaceChange,\n coordinateSpaceOptions,\n linkedScale: linkedScaleProp,\n defaultLinkedScale,\n onLinkedScaleChange,\n show,\n labels,\n precision,\n step,\n units,\n size = 'sm',\n disabled = false,\n showReset = false,\n className,\n style,\n testId,\n ref,\n ...rest\n}) => {\n const [value, setValue] = useControlledState<TransformValue>({\n value: valueProp,\n defaultValue,\n onChange,\n fallback: DEFAULT_TRANSFORM,\n });\n\n const [coordinateSpace, setCoordinateSpace] =\n useControlledState<CoordinateSpace>({\n value: coordinateSpaceProp,\n defaultValue: defaultCoordinateSpace,\n onChange: onCoordinateSpaceChange,\n fallback: 'local',\n });\n\n const [linkedScale, setLinkedScale] = useControlledState<boolean>({\n value: linkedScaleProp,\n defaultValue: defaultLinkedScale,\n onChange: onLinkedScaleChange,\n fallback: true,\n });\n\n const showPosition = show?.position !== false;\n const showRotation = show?.rotation !== false;\n const showScale = show?.scale !== false;\n const showCoordinateSpace = showPosition || showRotation;\n\n const resolvedLabels = {\n position: labels?.position ?? DEFAULT_LABELS.position,\n rotation: labels?.rotation ?? DEFAULT_LABELS.rotation,\n scale: labels?.scale ?? DEFAULT_LABELS.scale,\n coordinateSpace: labels?.coordinateSpace ?? DEFAULT_LABELS.coordinateSpace,\n };\n\n const resolvedPrecision = {\n position: precision?.position ?? DEFAULT_PRECISION.position,\n rotation: precision?.rotation ?? DEFAULT_PRECISION.rotation,\n scale: precision?.scale ?? DEFAULT_PRECISION.scale,\n };\n\n const resolvedStep = {\n position: step?.position ?? DEFAULT_STEP.position,\n rotation: step?.rotation ?? DEFAULT_STEP.rotation,\n scale: step?.scale ?? DEFAULT_STEP.scale,\n };\n\n const resolvedUnits = {\n position: units?.position ?? DEFAULT_UNITS.position,\n rotation: units?.rotation ?? DEFAULT_UNITS.rotation,\n scale: units?.scale ?? DEFAULT_UNITS.scale,\n };\n\n const resolvedSpaceOptions = useMemo(\n () => coordinateSpaceOptions ?? DEFAULT_COORDINATE_SPACE_OPTIONS,\n [coordinateSpaceOptions]\n );\n\n // --- Per-row change handlers ---\n\n const handlePositionChange = useCallback(\n (next: number[]) => {\n setValue(prev => ({ ...prev, position: arrayToVec(next) }));\n },\n [setValue]\n );\n\n const handleRotationChange = useCallback(\n (next: number[]) => {\n setValue(prev => ({ ...prev, rotation: arrayToVec(next) }));\n },\n [setValue]\n );\n\n const handleScaleChange = useCallback(\n (next: number[], axisIndex: number) => {\n const nextScale = arrayToVec(next);\n if (linkedScale) {\n const v = next[axisIndex] ?? 0;\n setValue(prev => ({ ...prev, scale: { x: v, y: v, z: v } }));\n } else {\n setValue(prev => ({ ...prev, scale: nextScale }));\n }\n },\n [linkedScale, setValue]\n );\n\n const handleLockToggle = useCallback(() => {\n setLinkedScale(prev => !prev);\n }, [setLinkedScale]);\n\n const handleSpaceChange = useCallback(\n (next: string | null) => {\n if (next == null) return;\n setCoordinateSpace(next);\n },\n [setCoordinateSpace]\n );\n\n // --- Reset handlers ---\n\n const handlePositionReset = useCallback(() => {\n setValue(prev => ({ ...prev, position: { x: 0, y: 0, z: 0 } }));\n }, [setValue]);\n\n const handleRotationReset = useCallback(() => {\n setValue(prev => ({ ...prev, rotation: { x: 0, y: 0, z: 0 } }));\n }, [setValue]);\n\n const handleScaleReset = useCallback(() => {\n setValue(prev => ({ ...prev, scale: { x: 1, y: 1, z: 1 } }));\n }, [setValue]);\n\n // --- Render ---\n\n return (\n <div\n ref={ref}\n className={cx(transformRoot, className)}\n style={style}\n data-testid={testId}\n {...rest}\n >\n {showCoordinateSpace && (\n <PropertyRow\n label={resolvedLabels.coordinateSpace}\n size={size}\n disabled={disabled}\n splitRatio={ROW_SPLIT_RATIO}\n >\n <Select\n value={coordinateSpace}\n options={resolvedSpaceOptions}\n onChange={handleSpaceChange}\n size={size}\n disabled={disabled}\n aria-label={resolvedLabels.coordinateSpace}\n />\n </PropertyRow>\n )}\n\n {showPosition && (\n <PropertyRow\n label={resolvedLabels.position}\n size={size}\n disabled={disabled}\n splitRatio={ROW_SPLIT_RATIO}\n onReset={showReset ? handlePositionReset : undefined}\n >\n <div className={rowInner}>\n <span aria-hidden=\"true\" className={lockSpacerRecipe({ size })} />\n <div className={rowVectorWrapper}>\n <VectorInput\n value={vecToArray(value.position)}\n onChange={handlePositionChange}\n dimension={3}\n labelPreset=\"xyz\"\n colorPreset=\"spatial\"\n precision={resolvedPrecision.position}\n step={resolvedStep.position}\n unit={resolvedUnits.position}\n size={size}\n disabled={disabled}\n aria-label={resolvedLabels.position}\n />\n </div>\n </div>\n </PropertyRow>\n )}\n\n {showRotation && (\n <PropertyRow\n label={resolvedLabels.rotation}\n size={size}\n disabled={disabled}\n splitRatio={ROW_SPLIT_RATIO}\n onReset={showReset ? handleRotationReset : undefined}\n >\n <div className={rowInner}>\n <span aria-hidden=\"true\" className={lockSpacerRecipe({ size })} />\n <div className={rowVectorWrapper}>\n <VectorInput\n value={vecToArray(value.rotation)}\n onChange={handleRotationChange}\n dimension={3}\n labelPreset=\"xyz\"\n colorPreset=\"spatial\"\n precision={resolvedPrecision.rotation}\n step={resolvedStep.rotation}\n unit={resolvedUnits.rotation}\n size={size}\n disabled={disabled}\n aria-label={resolvedLabels.rotation}\n />\n </div>\n </div>\n </PropertyRow>\n )}\n\n {showScale && (\n <PropertyRow\n label={resolvedLabels.scale}\n size={size}\n disabled={disabled}\n splitRatio={ROW_SPLIT_RATIO}\n onReset={showReset ? handleScaleReset : undefined}\n >\n <div className={rowInner}>\n <button\n type=\"button\"\n className={lockButtonRecipe({ active: linkedScale, size })}\n onClick={handleLockToggle}\n disabled={disabled}\n aria-pressed={linkedScale}\n aria-label={linkedScale ? 'Unlink scale axes' : 'Link scale axes'}\n title={linkedScale ? 'Linked scale' : 'Unlinked scale'}\n data-testid=\"transform-scale-lock\"\n >\n {linkedScale ? (\n <LockIcon size={size} decorative />\n ) : (\n <UnlockIcon size={size} decorative />\n )}\n </button>\n <div className={rowVectorWrapper}>\n <VectorInput\n value={vecToArray(value.scale)}\n onChange={handleScaleChange}\n dimension={3}\n labelPreset=\"xyz\"\n colorPreset=\"spatial\"\n precision={resolvedPrecision.scale}\n step={resolvedStep.scale}\n unit={resolvedUnits.scale}\n size={size}\n disabled={disabled}\n aria-label={resolvedLabels.scale}\n />\n </div>\n </div>\n </PropertyRow>\n )}\n </div>\n );\n};\n\nTransformControl.displayName = 'TransformControl';\n"],"names":[],"mappings":";;;;;;;;;;;;AA4BA;AAEA;AACE;AACA;AACA;;AAGF;AACE;AACA;AACA;;AAGF;AACE;AACA;AACA;AACA;;AAGF;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AAEA;AACE;AACA;AACA;AACD;AAED;AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2BG;AACI;AAyBL;AACE;;;AAGA;AACD;AAED;AAEI;AACA;AACA;AACA;AACD;AAEH;AACE;AACA;AACA;AACA;AACD;AAED;AACA;AACA;AACA;AAEA;AACE;AACA;AACA;AACA;;AAGF;AACE;AACA;AACA;;AAGF;AACE;AACA;AACA;;AAGF;AACE;AACA;AACA;;AAGF;;AAOA;AAEI;AACF;AAIF;AAEI;AACF;;AAME;;;AAGE;;;AAEA;;AAEJ;AAIF;;AAEA;AAEA;;;;AAIE;;AAMF;AACE;AACF;AAEA;AACE;AACF;AAEA;AACE;AACF;;AAIA;AAiIF;AAEA;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ViewportGizmo.js","sources":["
|
|
1
|
+
{"version":3,"file":"ViewportGizmo.js","sources":["src/components/editor/ViewportGizmo/ViewportGizmo.tsx"],"sourcesContent":["'use client';\n\nimport { assignInlineVars } from '@vanilla-extract/dynamic';\nimport React, { useRef, useMemo } from 'react';\n\nimport { cx } from '@/utils/cx';\n\nimport { useGizmoInteraction } from './useGizmoInteraction';\nimport { useGizmoRenderer, AXIS_COLORS } from './useGizmoRenderer';\nimport {\n gizmoWrapperRecipe,\n gizmoCanvasStyle,\n gizmoDiameterVar,\n ariaLiveRegionStyle,\n} from './ViewportGizmo.css';\n\nimport type { ViewportGizmoProps } from './ViewportGizmo.types';\n\nconst DEFAULT_LABELS: Record<string, string> = { x: 'X', y: 'Y', z: 'Z' };\nconst DEFAULT_VISIBLE: Record<string, boolean> = { x: true, y: true, z: true };\n\nexport const ViewportGizmo: React.FC<ViewportGizmoProps> = ({\n orientation,\n upAxis = 'y-up',\n axisColorPreset = 'blender',\n axisConfig,\n showLabels = true,\n showNegativeAxes = true,\n showOrbitRing = true,\n showOriginHandle = true,\n background = 'subtle',\n interactionMode = 'full',\n orbitSpeed = 1,\n constrainPitch = true,\n onOrbit,\n onOrbitEnd,\n onSnapToView,\n onAxisClick,\n onOriginClick,\n diameter = 120,\n size = 'md',\n disabled = false,\n className,\n testId,\n id,\n style,\n ...rest\n}) => {\n const canvasRef = useRef<HTMLCanvasElement>(null);\n\n // Resolve axis colors from preset or custom config\n const axisColors = useMemo((): Record<string, string> => {\n if (axisColorPreset === 'custom' && axisConfig) {\n return {\n x: axisConfig[0].color ?? '#E63946',\n y: axisConfig[1].color ?? '#6AA84F',\n z: axisConfig[2].color ?? '#4A86C8',\n };\n }\n return { ...AXIS_COLORS };\n }, [axisColorPreset, axisConfig]);\n\n const axisLabels = useMemo((): Record<string, string> => {\n if (axisConfig) {\n return {\n x: axisConfig[0].label ?? 'X',\n y: axisConfig[1].label ?? 'Y',\n z: axisConfig[2].label ?? 'Z',\n };\n }\n return DEFAULT_LABELS;\n }, [axisConfig]);\n\n const axisVisible = useMemo((): Record<string, boolean> => {\n if (axisConfig) {\n return {\n x: axisConfig[0].visible ?? true,\n y: axisConfig[1].visible ?? true,\n z: axisConfig[2].visible ?? true,\n };\n }\n return DEFAULT_VISIBLE;\n }, [axisConfig]);\n\n // Interaction\n const interaction = useGizmoInteraction({\n orientation,\n canvasRef,\n diameter,\n upAxis,\n interactionMode,\n orbitSpeed,\n constrainPitch,\n disabled,\n onOrbit,\n onOrbitEnd,\n onSnapToView,\n onAxisClick,\n onOriginClick,\n });\n\n // Renderer\n useGizmoRenderer({\n canvasRef,\n orientation,\n diameter,\n upAxis,\n size,\n axisColors,\n axisLabels,\n axisVisible,\n showLabels,\n showNegativeAxes,\n showOrbitRing,\n showOriginHandle,\n background,\n disabled,\n isDragging: interaction.isDragging,\n hoveredRegion: interaction.hoveredRegion,\n });\n\n // ARIA\n const ariaDescription = `3D orientation gizmo, yaw: ${Math.round(orientation.yaw)}deg, pitch: ${Math.round(orientation.pitch)}deg`;\n\n return (\n <div\n className={cx(gizmoWrapperRecipe({ background, disabled }), className)}\n style={{\n ...style,\n ...assignInlineVars({ [gizmoDiameterVar]: `${diameter}px` }),\n }}\n data-testid={testId}\n id={id}\n {...rest}\n >\n <canvas\n ref={canvasRef}\n className={gizmoCanvasStyle({ disabled })}\n // The canvas is a self-contained interactive widget driven entirely by\n // custom pointer/keyboard handlers, so it is exposed as an ARIA\n // application rather than its implicit canvas role.\n // eslint-disable-next-line jsx-a11y/no-interactive-element-to-noninteractive-role\n role=\"application\"\n aria-label=\"Viewport orientation gizmo\"\n aria-roledescription={ariaDescription}\n tabIndex={disabled ? -1 : 0}\n onPointerDown={interaction.handlers.onPointerDown}\n onPointerMove={interaction.handlers.onPointerMove}\n onPointerUp={interaction.handlers.onPointerUp}\n onKeyDown={interaction.handlers.onKeyDown}\n />\n <div className={ariaLiveRegionStyle} aria-live=\"polite\" role=\"status\">\n {ariaDescription}\n </div>\n </div>\n );\n};\n\nViewportGizmo.displayName = 'ViewportGizmo';\n"],"names":[],"mappings":";;;;;;;;;AAkBA;AACA;AAEO;AA2BL;;AAGA;AACE;;;;;;;AAOA;AACF;AAEA;;;;;;;;AAQE;AACF;AAEA;;;;;;;;AAQE;AACF;;;;;;;;;;;;;;;;AAiBC;;AAGD;;;;;;;;;;;;;;;;AAiBC;;;AAKD;AAIM;;;;;;;AAcA;AAcR;AAEA;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gizmoMath.js","sources":["../../../../../../src/components/editor/ViewportGizmo/gizmoMath.ts"],"sourcesContent":[null],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"gizmoMath.js","sources":["src/components/editor/ViewportGizmo/gizmoMath.ts"],"sourcesContent":["import type {\n GizmoOrientation,\n GizmoPresetView,\n GizmoUpAxis,\n GizmoHitRegion,\n} from './ViewportGizmo.types';\n\n// ─── Vector Type ───\n\n/** 3D vector */\nexport interface Vec3 {\n x: number;\n y: number;\n z: number;\n}\n\n// ─── Constants ───\n\nconst DEG2RAD = Math.PI / 180;\nconst RAD2DEG = 180 / Math.PI;\n\n// ─── Euler → Rotation Matrix ───\n\n/**\n * Build a 3×3 rotation matrix from Euler angles (YXZ intrinsic order).\n * Matches Three.js default Euler order.\n *\n * @param yaw - Y-axis rotation in degrees\n * @param pitch - X-axis rotation in degrees\n * @param roll - Z-axis rotation in degrees\n * @returns [right, up, forward] basis vectors (columns of rotation matrix)\n */\nexport function eulerToRotationMatrix(\n yaw: number,\n pitch: number,\n roll: number = 0\n): [Vec3, Vec3, Vec3] {\n const cy = Math.cos(yaw * DEG2RAD);\n const sy = Math.sin(yaw * DEG2RAD);\n const cp = Math.cos(pitch * DEG2RAD);\n const sp = Math.sin(pitch * DEG2RAD);\n const cr = Math.cos(roll * DEG2RAD);\n const sr = Math.sin(roll * DEG2RAD);\n\n // YXZ rotation: R = Ry * Rx * Rz\n // Column 0 (right vector)\n const r00 = cy * cr + sy * sp * sr;\n const r10 = cp * sr;\n const r20 = -sy * cr + cy * sp * sr;\n\n // Column 1 (up vector)\n const r01 = -cy * sr + sy * sp * cr;\n const r11 = cp * cr;\n const r21 = sy * sr + cy * sp * cr;\n\n // Column 2 (forward vector)\n const r02 = sy * cp;\n const r12 = -sp;\n const r22 = cy * cp;\n\n return [\n { x: r00, y: r10, z: r20 }, // right\n { x: r01, y: r11, z: r21 }, // up\n { x: r02, y: r12, z: r22 }, // forward\n ];\n}\n\n// ─── 3D → 2D Projection ───\n\n/**\n * Project a 3D point to 2D canvas coordinates using orthographic projection.\n *\n * @param point - 3D point to project\n * @param orientation - Camera orientation\n * @param center - Canvas center in pixels\n * @param scale - Pixels per unit\n * @returns Projected position { x, y, depth }\n */\nexport function projectToCanvas(\n point: Vec3,\n orientation: GizmoOrientation,\n center: { x: number; y: number },\n scale: number\n): { x: number; y: number; depth: number } {\n const [right, up, forward] = eulerToRotationMatrix(\n orientation.yaw,\n orientation.pitch,\n orientation.roll ?? 0\n );\n\n // Project point onto camera basis (view space)\n const viewX = point.x * right.x + point.y * right.y + point.z * right.z;\n const viewY = point.x * up.x + point.y * up.y + point.z * up.z;\n const viewZ = point.x * forward.x + point.y * forward.y + point.z * forward.z;\n\n return {\n x: center.x + viewX * scale,\n y: center.y - viewY * scale, // canvas Y is flipped\n depth: viewZ,\n };\n}\n\n/** Projected axis arm data */\nexport interface ProjectedArm {\n axis: 'x' | 'y' | 'z';\n positive: boolean;\n screenX: number;\n screenY: number;\n depth: number;\n}\n\n/**\n * Get all 6 axis arm endpoints projected to 2D, sorted by depth (furthest first).\n */\nexport function projectAxes(\n orientation: GizmoOrientation,\n center: { x: number; y: number },\n armLength: number,\n upAxis: GizmoUpAxis\n): ProjectedArm[] {\n // The drawn direction of each labelled axis depends on the up-axis\n // convention. For z-up (Blender, Unreal, CAD) the world is rotated -90°\n // about X relative to y-up, so the Z arm points up and the Y arm points\n // into the scene. (x, y, z) -> (x, z, -y).\n const axes: Vec3[] =\n upAxis === 'z-up'\n ? [\n { x: 1, y: 0, z: 0 }, // X\n { x: 0, y: 0, z: -1 }, // Y (into the scene)\n { x: 0, y: 1, z: 0 }, // Z (up)\n ]\n : [\n { x: 1, y: 0, z: 0 }, // X\n { x: 0, y: 1, z: 0 }, // Y (up)\n { x: 0, y: 0, z: 1 }, // Z\n ];\n\n const axisNames: Array<'x' | 'y' | 'z'> = ['x', 'y', 'z'];\n const arms: ProjectedArm[] = [];\n\n for (let i = 0; i < 3; i++) {\n const ax = axes[i];\n const name = axisNames[i];\n if (!ax || !name) continue;\n\n // Positive arm\n const pos = projectToCanvas(\n { x: ax.x, y: ax.y, z: ax.z },\n orientation,\n center,\n armLength\n );\n arms.push({\n axis: name,\n positive: true,\n screenX: pos.x,\n screenY: pos.y,\n depth: pos.depth,\n });\n\n // Negative arm\n const neg = projectToCanvas(\n { x: -ax.x, y: -ax.y, z: -ax.z },\n orientation,\n center,\n armLength * 0.4\n );\n arms.push({\n axis: name,\n positive: false,\n screenX: neg.x,\n screenY: neg.y,\n depth: neg.depth,\n });\n }\n\n // Sort by depth — furthest first (painter's algorithm)\n arms.sort((a, b) => a.depth - b.depth);\n\n return arms;\n}\n\n// ─── Hit Testing ───\n\nfunction distPointToSegment(\n px: number,\n py: number,\n x1: number,\n y1: number,\n x2: number,\n y2: number\n): number {\n const dx = x2 - x1;\n const dy = y2 - y1;\n const lenSq = dx * dx + dy * dy;\n if (lenSq === 0) return Math.sqrt((px - x1) ** 2 + (py - y1) ** 2);\n\n let t = ((px - x1) * dx + (py - y1) * dy) / lenSq;\n t = Math.max(0, Math.min(1, t));\n\n const projX = x1 + t * dx;\n const projY = y1 + t * dy;\n return Math.sqrt((px - projX) ** 2 + (py - projY) ** 2);\n}\n\n/**\n * Hit test a screen position against the gizmo geometry.\n */\nexport function gizmoHitTest(\n px: number,\n py: number,\n orientation: GizmoOrientation,\n center: { x: number; y: number },\n armLength: number,\n upAxis: GizmoUpAxis,\n tolerancePx: number = 12,\n originRadius: number = 6\n): GizmoHitRegion {\n // Test origin handle first\n const originDist = Math.sqrt((px - center.x) ** 2 + (py - center.y) ** 2);\n if (originDist <= originRadius + tolerancePx * 0.5) {\n return { type: 'origin', distance: originDist };\n }\n\n const arms = projectAxes(orientation, center, armLength, upAxis);\n let closest: GizmoHitRegion = { type: 'none', distance: Infinity };\n\n for (const arm of arms) {\n // Check tip first (label / axis tip)\n const tipDist = Math.sqrt(\n (px - arm.screenX) ** 2 + (py - arm.screenY) ** 2\n );\n\n if (tipDist <= tolerancePx) {\n const hitType = arm.positive ? 'axis-positive' : 'axis-negative';\n if (tipDist < closest.distance) {\n closest = { type: hitType, axis: arm.axis, distance: tipDist };\n }\n }\n\n // Check arm line segment\n const segDist = distPointToSegment(\n px,\n py,\n center.x,\n center.y,\n arm.screenX,\n arm.screenY\n );\n if (segDist <= tolerancePx * 0.7 && segDist < closest.distance) {\n const hitType = arm.positive ? 'axis-positive' : 'axis-negative';\n closest = { type: hitType, axis: arm.axis, distance: segDist };\n }\n }\n\n // Check orbit ring\n const ringDist = Math.abs(originDist - armLength * 1.1);\n if (ringDist <= tolerancePx * 0.5 && ringDist < closest.distance) {\n return { type: 'ring', distance: ringDist };\n }\n\n return closest;\n}\n\n// ─── Preset Views ───\n\n/**\n * Convert a preset view name to Euler angles.\n */\nexport function presetViewToOrientation(\n view: GizmoPresetView,\n upAxis: GizmoUpAxis\n): GizmoOrientation {\n if (upAxis === 'y-up') {\n switch (view) {\n case 'front':\n return { yaw: 0, pitch: 0 };\n case 'back':\n return { yaw: 180, pitch: 0 };\n case 'right':\n return { yaw: 90, pitch: 0 };\n case 'left':\n return { yaw: -90, pitch: 0 };\n case 'top':\n return { yaw: 0, pitch: 90 };\n case 'bottom':\n return { yaw: 0, pitch: -90 };\n }\n }\n\n // Z-up\n switch (view) {\n case 'front':\n return { yaw: 0, pitch: 0 };\n case 'back':\n return { yaw: 180, pitch: 0 };\n case 'right':\n return { yaw: 90, pitch: 0 };\n case 'left':\n return { yaw: -90, pitch: 0 };\n case 'top':\n return { yaw: 0, pitch: 90 };\n case 'bottom':\n return { yaw: 0, pitch: -90 };\n }\n}\n\n// ─── Quaternion ↔ Euler ───\n\n/**\n * Convert quaternion [x, y, z, w] to Euler representation (YXZ order, degrees).\n * Utility for Three.js integration where `camera.quaternion` is available.\n */\nexport function quaternionToEuler(\n qx: number,\n qy: number,\n qz: number,\n qw: number\n): GizmoOrientation {\n // YXZ intrinsic rotation decomposition\n // Matrix from quaternion\n const xx = qx * qx;\n const yy = qy * qy;\n const zz = qz * qz;\n const xy = qx * qy;\n const xz = qx * qz;\n const yz = qy * qz;\n const wx = qw * qx;\n const wy = qw * qy;\n const wz = qw * qz;\n\n const m11 = 1 - 2 * (yy + zz);\n const m13 = 2 * (xz + wy);\n const m21 = 2 * (xy + wz);\n const m22 = 1 - 2 * (xx + zz);\n const m23 = 2 * (yz - wx);\n const m31 = 2 * (xz - wy);\n const m33 = 1 - 2 * (xx + yy);\n\n // YXZ order extraction\n const pitch = Math.asin(-Math.max(-1, Math.min(1, m23)));\n let yaw: number;\n let roll: number;\n\n if (Math.abs(m23) < 0.9999) {\n yaw = Math.atan2(m13, m33);\n roll = Math.atan2(m21, m22);\n } else {\n yaw = Math.atan2(-m31, m11);\n roll = 0;\n }\n\n return {\n yaw: yaw * RAD2DEG,\n pitch: pitch * RAD2DEG,\n roll: roll * RAD2DEG,\n };\n}\n\n/**\n * Map an axis tip click to the corresponding preset view.\n */\nexport function axisToPresetView(\n axis: 'x' | 'y' | 'z',\n positive: boolean,\n upAxis: GizmoUpAxis\n): GizmoPresetView | null {\n if (upAxis === 'y-up') {\n switch (axis) {\n case 'x':\n return positive ? 'right' : 'left';\n case 'y':\n return positive ? 'top' : 'bottom';\n case 'z':\n return positive ? 'front' : 'back';\n }\n }\n\n // Z-up\n switch (axis) {\n case 'x':\n return positive ? 'right' : 'left';\n case 'y':\n return positive ? 'front' : 'back';\n case 'z':\n return positive ? 'top' : 'bottom';\n }\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useGizmoInteraction.js","sources":["../../../../../../src/components/editor/ViewportGizmo/useGizmoInteraction.ts"],"sourcesContent":[null],"names":[],"mappings":";;;;;AA4CA;AAEA;AACA;AAEM;AAGJ;;;;AAmBA;;AAEA;AACA;AACA;;;;;AAaA;AAEI;AACA;;AACA;;AAEF;AAIF;;;AAII;AACA;;;AAIA;AACA;AACA;AACA;;AAEA;;AAGA;;AAEF;;;;;;;;;AAUC;AAGH;AAEI;AACA;;AAEA;;;;;;AAOE;AACE;;AAGF;AACE;;;AAIE;AACA;;;;AAMJ;;;;;;;AAOF;;;;AAKE;;AACK;AACL;;;AAEA;;;AAEA;;AAEJ;;;;;;;;;;;;;AAcC;AAGH;AAEI;AACA;;AAEA;AACE;;AAEA;AAEA;;AAEE;;;;;AAIA;AASA;;;;;AAIE;AACA;AAAU;;;;AAGV;AACA;AAAU;;;;AAIlB;;;;;;;;;;;;AAaC;AAGH;AAEI;;AAEA;;;AAIA;AACE;;AAEI;;;;AAIJ;;AAEI;;;;AAIJ;;;;;AAKQ;;;;;;AAMR;;AAEI;;;AAGI;;;;;;AAMR;;AAEI;;;;AAIJ;;AAEI;;;;AAIJ;;AAEI;;;;AAIJ;AACA;;;;;;;;AASJ;;;;;;;;;;AAWC;;;;AAMD;;;;;AAKC;;AAEL;;"}
|
|
1
|
+
{"version":3,"file":"useGizmoInteraction.js","sources":["src/components/editor/ViewportGizmo/useGizmoInteraction.ts"],"sourcesContent":["'use client';\n\nimport { useState, useCallback, useMemo, useRef } from 'react';\n\nimport { clamp } from '@/utils/mathUtils';\n\nimport { gizmoHitTest, axisToPresetView } from './gizmoMath';\n\nimport type {\n GizmoOrientation,\n GizmoUpAxis,\n GizmoPresetView,\n GizmoHitRegion,\n OrbitDelta,\n} from './ViewportGizmo.types';\nimport type React from 'react';\n\ninterface UseGizmoInteractionOptions {\n orientation: GizmoOrientation;\n canvasRef: React.RefObject<HTMLCanvasElement | null>;\n diameter: number;\n upAxis: GizmoUpAxis;\n interactionMode: 'full' | 'snap-only' | 'orbit-only' | 'display-only';\n orbitSpeed: number;\n constrainPitch: boolean;\n disabled: boolean;\n onOrbit?: (delta: OrbitDelta) => void;\n onOrbitEnd?: (finalOrientation: GizmoOrientation) => void;\n onSnapToView?: (view: GizmoPresetView) => void;\n onAxisClick?: (axis: 'x' | 'y' | 'z', positive: boolean) => void;\n onOriginClick?: () => void;\n}\n\ninterface UseGizmoInteractionReturn {\n isDragging: boolean;\n hoveredRegion: GizmoHitRegion;\n handlers: {\n onPointerDown: (e: React.PointerEvent) => void;\n onPointerMove: (e: React.PointerEvent) => void;\n onPointerUp: (e: React.PointerEvent) => void;\n onKeyDown: (e: React.KeyboardEvent) => void;\n };\n}\n\nconst NONE_HIT: GizmoHitRegion = { type: 'none', distance: Infinity };\n\nconst ORBIT_KEY_STEP = 15;\nconst ORBIT_FINE_STEP = 5;\n\nexport function useGizmoInteraction(\n options: UseGizmoInteractionOptions\n): UseGizmoInteractionReturn {\n const {\n orientation,\n canvasRef,\n diameter,\n upAxis,\n interactionMode,\n orbitSpeed,\n constrainPitch,\n disabled,\n onOrbit,\n onOrbitEnd,\n onSnapToView,\n onAxisClick,\n onOriginClick,\n } = options;\n\n const [hoveredRegion, setHoveredRegion] = useState<GizmoHitRegion>(NONE_HIT);\n // Ref drives synchronous reads inside the pointer handlers; the state mirror\n // is the value exposed for rendering (kept in sync on drag start/end).\n const isDraggingRef = useRef(false);\n const [isDragging, setIsDragging] = useState(false);\n const lastPointerRef = useRef<{ x: number; y: number }>({ x: 0, y: 0 });\n const startPointerRef = useRef<{ x: number; y: number }>({ x: 0, y: 0 });\n const hasDraggedRef = useRef(false);\n\n const allowSnap =\n interactionMode === 'full' || interactionMode === 'snap-only';\n const allowOrbit =\n interactionMode === 'full' || interactionMode === 'orbit-only';\n\n const armLength = (diameter / 2) * 0.65;\n const center = useMemo(\n () => ({ x: diameter / 2, y: diameter / 2 }),\n [diameter]\n );\n\n const getPointerPos = useCallback(\n (e: React.PointerEvent) => {\n const canvas = canvasRef.current;\n if (!canvas) return { px: 0, py: 0 };\n const rect = canvas.getBoundingClientRect();\n return { px: e.clientX - rect.left, py: e.clientY - rect.top };\n },\n [canvasRef]\n );\n\n const onPointerDown = useCallback(\n (e: React.PointerEvent) => {\n if (disabled || interactionMode === 'display-only' || e.button !== 0)\n return;\n const canvas = canvasRef.current;\n if (!canvas) return;\n\n const { px, py } = getPointerPos(e);\n\n isDraggingRef.current = true;\n hasDraggedRef.current = false;\n lastPointerRef.current = { x: e.clientX, y: e.clientY };\n startPointerRef.current = { x: e.clientX, y: e.clientY };\n setIsDragging(true);\n canvas.setPointerCapture(e.pointerId);\n\n // Store hit for click detection on pointer up\n const hit = gizmoHitTest(px, py, orientation, center, armLength, upAxis);\n setHoveredRegion(hit);\n },\n [\n disabled,\n interactionMode,\n canvasRef,\n getPointerPos,\n orientation,\n center,\n armLength,\n upAxis,\n ]\n );\n\n const onPointerMove = useCallback(\n (e: React.PointerEvent) => {\n const canvas = canvasRef.current;\n if (!canvas) return;\n\n if (isDraggingRef.current && allowOrbit) {\n const dx = e.clientX - lastPointerRef.current.x;\n const dy = e.clientY - lastPointerRef.current.y;\n\n // Only count as drag if moved > 3px from start\n const totalDx = e.clientX - startPointerRef.current.x;\n const totalDy = e.clientY - startPointerRef.current.y;\n if (Math.sqrt(totalDx ** 2 + totalDy ** 2) > 3) {\n hasDraggedRef.current = true;\n }\n\n if (hasDraggedRef.current) {\n const deltaYaw = dx * orbitSpeed * 0.5;\n let deltaPitch = -dy * orbitSpeed * 0.5;\n\n if (constrainPitch) {\n const newPitch = orientation.pitch + deltaPitch;\n deltaPitch = clamp(newPitch, -90, 90) - orientation.pitch;\n }\n\n onOrbit?.({ deltaYaw, deltaPitch });\n }\n\n lastPointerRef.current = { x: e.clientX, y: e.clientY };\n return;\n }\n\n // Hover detection\n if (interactionMode === 'display-only') return;\n const { px, py } = getPointerPos(e);\n const hit = gizmoHitTest(px, py, orientation, center, armLength, upAxis);\n setHoveredRegion(hit);\n\n // Cursor\n if (disabled) {\n canvas.style.cursor = 'default';\n } else if (hit.type === 'axis-positive' || hit.type === 'origin') {\n canvas.style.cursor = 'pointer';\n } else if (allowOrbit) {\n canvas.style.cursor = isDraggingRef.current ? 'grabbing' : 'grab';\n } else {\n canvas.style.cursor = 'default';\n }\n },\n [\n canvasRef,\n allowOrbit,\n orbitSpeed,\n constrainPitch,\n orientation,\n interactionMode,\n disabled,\n getPointerPos,\n center,\n armLength,\n upAxis,\n onOrbit,\n ]\n );\n\n const onPointerUp = useCallback(\n (e: React.PointerEvent) => {\n const canvas = canvasRef.current;\n if (!canvas) return;\n\n if (isDraggingRef.current) {\n isDraggingRef.current = false;\n setIsDragging(false);\n canvas.releasePointerCapture(e.pointerId);\n\n if (hasDraggedRef.current) {\n // Finished orbit drag\n onOrbitEnd?.(orientation);\n } else if (allowSnap) {\n // Click (no drag) — check hit\n const { px, py } = getPointerPos(e);\n const hit = gizmoHitTest(\n px,\n py,\n orientation,\n center,\n armLength,\n upAxis\n );\n\n if (hit.type === 'origin') {\n onOriginClick?.();\n } else if (hit.type === 'axis-positive' && hit.axis) {\n onAxisClick?.(hit.axis, true);\n const view = axisToPresetView(hit.axis, true, upAxis);\n if (view) onSnapToView?.(view);\n } else if (hit.type === 'axis-negative' && hit.axis) {\n onAxisClick?.(hit.axis, false);\n const view = axisToPresetView(hit.axis, false, upAxis);\n if (view) onSnapToView?.(view);\n }\n }\n }\n },\n [\n canvasRef,\n allowSnap,\n getPointerPos,\n orientation,\n center,\n armLength,\n upAxis,\n onOrbitEnd,\n onOriginClick,\n onAxisClick,\n onSnapToView,\n ]\n );\n\n const onKeyDown = useCallback(\n (e: React.KeyboardEvent) => {\n if (disabled || interactionMode === 'display-only') return;\n\n const fine = e.shiftKey;\n const step = fine ? ORBIT_FINE_STEP : ORBIT_KEY_STEP;\n let handled = false;\n\n switch (e.key) {\n case 'ArrowLeft':\n if (allowOrbit) {\n onOrbit?.({ deltaYaw: -step, deltaPitch: 0 });\n handled = true;\n }\n break;\n case 'ArrowRight':\n if (allowOrbit) {\n onOrbit?.({ deltaYaw: step, deltaPitch: 0 });\n handled = true;\n }\n break;\n case 'ArrowUp':\n if (allowOrbit) {\n let deltaPitch = step;\n if (constrainPitch) {\n deltaPitch =\n clamp(orientation.pitch + step, -90, 90) - orientation.pitch;\n }\n onOrbit?.({ deltaYaw: 0, deltaPitch });\n handled = true;\n }\n break;\n case 'ArrowDown':\n if (allowOrbit) {\n let deltaPitch = -step;\n if (constrainPitch) {\n deltaPitch =\n clamp(orientation.pitch - step, -90, 90) - orientation.pitch;\n }\n onOrbit?.({ deltaYaw: 0, deltaPitch });\n handled = true;\n }\n break;\n case '1':\n if (allowSnap) {\n onSnapToView?.(e.ctrlKey ? 'back' : 'front');\n handled = true;\n }\n break;\n case '3':\n if (allowSnap) {\n onSnapToView?.(e.ctrlKey ? 'left' : 'right');\n handled = true;\n }\n break;\n case '7':\n if (allowSnap) {\n onSnapToView?.(e.ctrlKey ? 'bottom' : 'top');\n handled = true;\n }\n break;\n case '5':\n case 'Home':\n onOriginClick?.();\n handled = true;\n break;\n }\n\n if (handled) {\n e.preventDefault();\n }\n },\n [\n disabled,\n interactionMode,\n allowOrbit,\n allowSnap,\n constrainPitch,\n orientation,\n onOrbit,\n onSnapToView,\n onOriginClick,\n ]\n );\n\n return {\n isDragging,\n hoveredRegion,\n handlers: {\n onPointerDown,\n onPointerMove,\n onPointerUp,\n onKeyDown,\n },\n };\n}\n"],"names":[],"mappings":";;;;;AA4CA;AAEA;AACA;AAEM;AAGJ;;;;AAmBA;;AAEA;AACA;AACA;;;;;AAaA;AAEI;AACA;;AACA;;AAEF;AAIF;;;AAII;AACA;;;AAIA;AACA;AACA;AACA;;AAEA;;AAGA;;AAEF;;;;;;;;;AAUC;AAGH;AAEI;AACA;;AAEA;;;;;;AAOE;AACE;;AAGF;AACE;;;AAIE;AACA;;;;AAMJ;;;;;;;AAOF;;;;AAKE;;AACK;AACL;;;AAEA;;;AAEA;;AAEJ;;;;;;;;;;;;;AAcC;AAGH;AAEI;AACA;;AAEA;AACE;;AAEA;AAEA;;AAEE;;;;;AAIA;AASA;;;;;AAIE;AACA;AAAU;;;;AAGV;AACA;AAAU;;;;AAIlB;;;;;;;;;;;;AAaC;AAGH;AAEI;;AAEA;;;AAIA;AACE;;AAEI;;;;AAIJ;;AAEI;;;;AAIJ;;;;;AAKQ;;;;;;AAMR;;AAEI;;;AAGI;;;;;;AAMR;;AAEI;;;;AAIJ;;AAEI;;;;AAIJ;;AAEI;;;;AAIJ;AACA;;;;;;;;AASJ;;;;;;;;;;AAWC;;;;AAMD;;;;;AAKC;;AAEL;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useGizmoRenderer.js","sources":["
|
|
1
|
+
{"version":3,"file":"useGizmoRenderer.js","sources":["src/components/editor/ViewportGizmo/useGizmoRenderer.ts"],"sourcesContent":["'use client';\n\nimport { useCallback } from 'react';\n\nimport { resolveCanvasTheme } from '@/components/primitives/canvas/canvasTheme';\nimport { useCanvasRenderer } from '@/components/primitives/canvas/useCanvasRenderer';\n\nimport { projectAxes } from './gizmoMath';\n\nimport type {\n GizmoOrientation,\n GizmoUpAxis,\n GizmoHitRegion,\n ViewportGizmoSize,\n} from './ViewportGizmo.types';\nimport type { CanvasThemeColors } from '@/components/primitives/canvas/canvas.types';\nimport type React from 'react';\n\n// ─── Size Presets ───\n\ninterface SizePreset {\n labelFontSize: number;\n fontWeight: number;\n armWidthPositive: number;\n armWidthNegative: number;\n tipRadius: number;\n tipHoverRadius: number;\n originRadius: number;\n}\n\nconst SIZE_PRESETS: Record<ViewportGizmoSize, SizePreset> = {\n sm: {\n labelFontSize: 9,\n fontWeight: 600,\n armWidthPositive: 1.5,\n armWidthNegative: 0.75,\n tipRadius: 4,\n tipHoverRadius: 5,\n originRadius: 4,\n },\n md: {\n labelFontSize: 11,\n fontWeight: 600,\n armWidthPositive: 2.5,\n armWidthNegative: 1,\n tipRadius: 5,\n tipHoverRadius: 7,\n originRadius: 5,\n },\n lg: {\n labelFontSize: 13,\n fontWeight: 600,\n armWidthPositive: 3,\n armWidthNegative: 1.5,\n tipRadius: 6,\n tipHoverRadius: 8,\n originRadius: 6,\n },\n};\n\n// ─── Default Axis Colors ───\n\nconst AXIS_COLORS: Record<string, string> = {\n x: '#E63946',\n y: '#6AA84F',\n z: '#4A86C8',\n};\n\ninterface UseGizmoRendererOptions {\n canvasRef: React.RefObject<HTMLCanvasElement | null>;\n orientation: GizmoOrientation;\n diameter: number;\n upAxis: GizmoUpAxis;\n size: ViewportGizmoSize;\n axisColors: Record<string, string>;\n axisLabels: Record<string, string>;\n axisVisible: Record<string, boolean>;\n showLabels: boolean;\n showNegativeAxes: boolean;\n showOrbitRing: boolean;\n showOriginHandle: boolean;\n background: 'transparent' | 'subtle' | 'solid';\n disabled: boolean;\n isDragging: boolean;\n hoveredRegion: GizmoHitRegion;\n}\n\nexport { AXIS_COLORS };\n\nexport function useGizmoRenderer(options: UseGizmoRendererOptions): void {\n const {\n canvasRef,\n orientation,\n diameter,\n upAxis,\n size,\n axisColors,\n axisLabels,\n axisVisible,\n showLabels,\n showNegativeAxes,\n showOrbitRing,\n showOriginHandle,\n background,\n disabled,\n hoveredRegion,\n } = options;\n\n const draw = useCallback(() => {\n const canvas = canvasRef.current;\n if (!canvas) return;\n\n const ctx = canvas.getContext('2d');\n if (!ctx) return;\n\n const dpr =\n typeof window !== 'undefined' ? window.devicePixelRatio || 1 : 1;\n const rect = canvas.getBoundingClientRect();\n const w = rect.width;\n const h = rect.height;\n\n if (canvas.width !== w * dpr || canvas.height !== h * dpr) {\n canvas.width = w * dpr;\n canvas.height = h * dpr;\n ctx.scale(dpr, dpr);\n } else {\n ctx.setTransform(dpr, 0, 0, dpr, 0, 0);\n }\n\n const theme: CanvasThemeColors = resolveCanvasTheme(canvas);\n const preset = SIZE_PRESETS[size];\n const opacity = disabled ? 0.4 : 1;\n const center = { x: w / 2, y: h / 2 };\n const armLength = (diameter / 2) * 0.65;\n\n // 1. Clear / background\n ctx.clearRect(0, 0, w, h);\n\n if (background === 'subtle') {\n ctx.globalAlpha = opacity * 0.15;\n ctx.fillStyle = theme.backgroundSecondary;\n ctx.beginPath();\n ctx.arc(center.x, center.y, diameter / 2, 0, Math.PI * 2);\n ctx.fill();\n } else if (background === 'solid') {\n ctx.globalAlpha = opacity;\n ctx.fillStyle = theme.backgroundSecondary;\n ctx.fillRect(0, 0, w, h);\n }\n\n ctx.globalAlpha = opacity;\n\n // 2. Orbit ring\n if (showOrbitRing) {\n ctx.strokeStyle = theme.borderDefault;\n ctx.lineWidth = 1;\n ctx.globalAlpha = opacity * 0.3;\n ctx.beginPath();\n ctx.arc(center.x, center.y, armLength * 1.1, 0, Math.PI * 2);\n ctx.stroke();\n ctx.globalAlpha = opacity;\n }\n\n // 3. Draw arms — depth sorted (furthest first = painter's algorithm)\n const arms = projectAxes(orientation, center, armLength, upAxis);\n\n for (const arm of arms) {\n if (!axisVisible[arm.axis]) continue;\n if (!arm.positive && !showNegativeAxes) continue;\n\n const color = axisColors[arm.axis] ?? AXIS_COLORS[arm.axis] ?? '#888';\n const isHovered =\n hoveredRegion.axis === arm.axis &&\n ((arm.positive && hoveredRegion.type === 'axis-positive') ||\n (!arm.positive && hoveredRegion.type === 'axis-negative'));\n\n const alpha = arm.positive ? 1.0 : 0.35;\n const lineWidth = arm.positive\n ? preset.armWidthPositive\n : preset.armWidthNegative;\n\n // Draw arm line\n ctx.globalAlpha = opacity * alpha;\n ctx.strokeStyle = color;\n ctx.lineWidth = lineWidth;\n ctx.lineCap = 'round';\n ctx.beginPath();\n ctx.moveTo(center.x, center.y);\n ctx.lineTo(arm.screenX, arm.screenY);\n ctx.stroke();\n\n // Draw tip handle (positive arms only)\n if (arm.positive) {\n const tipR = isHovered ? preset.tipHoverRadius : preset.tipRadius;\n ctx.globalAlpha = opacity;\n ctx.fillStyle = color;\n ctx.beginPath();\n ctx.arc(arm.screenX, arm.screenY, tipR, 0, Math.PI * 2);\n ctx.fill();\n\n // Draw label\n if (showLabels) {\n const label = axisLabels[arm.axis] ?? arm.axis.toUpperCase();\n const labelOffset = tipR + preset.labelFontSize * 0.6;\n\n // Position label outward from center\n const dx = arm.screenX - center.x;\n const dy = arm.screenY - center.y;\n const dist = Math.sqrt(dx * dx + dy * dy) || 1;\n const nx = dx / dist;\n const ny = dy / dist;\n const lx = arm.screenX + nx * labelOffset;\n const ly = arm.screenY + ny * labelOffset;\n\n ctx.globalAlpha = opacity;\n ctx.fillStyle = color;\n ctx.font = `${preset.fontWeight} ${preset.labelFontSize}px sans-serif`;\n ctx.textAlign = 'center';\n ctx.textBaseline = 'middle';\n ctx.fillText(label, lx, ly);\n }\n }\n }\n\n // 4. Origin handle\n if (showOriginHandle) {\n const isOriginHovered = hoveredRegion.type === 'origin';\n const originR = isOriginHovered\n ? preset.originRadius + 1.5\n : preset.originRadius;\n\n ctx.globalAlpha = opacity * 0.6;\n ctx.fillStyle = theme.textSecondary;\n ctx.beginPath();\n ctx.arc(center.x, center.y, originR, 0, Math.PI * 2);\n ctx.fill();\n\n // Border ring\n ctx.globalAlpha = opacity * 0.3;\n ctx.strokeStyle = theme.textMuted;\n ctx.lineWidth = 1;\n ctx.stroke();\n }\n\n ctx.globalAlpha = 1;\n }, [\n canvasRef,\n orientation,\n diameter,\n upAxis,\n size,\n axisColors,\n axisLabels,\n axisVisible,\n showLabels,\n showNegativeAxes,\n showOrbitRing,\n showOriginHandle,\n background,\n disabled,\n hoveredRegion,\n ]);\n\n useCanvasRenderer({\n draw,\n deps: [draw],\n paused: false,\n });\n}\n"],"names":[],"mappings":";;;;;;AA8BA;AACE;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACD;AACD;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACD;AACD;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACD;;AAGH;AAEA;AACE;AACA;AACA;;AAwBI;AACJ;AAkBA;AACE;AACA;;;AAGA;;AAEA;AAEA;AACA;AACA;AAEA;AACE;AACA;AACA;;;AAEA;;AAGF;AACA;;AAEA;;;;AAMA;AACE;AACA;;;;;AAIK;AACL;AACA;;;AAIF;;;AAIE;AACA;AACA;;;;AAIA;;;AAIF;AAEA;AACE;;AACA;;AAEA;;;AAII;AAEJ;AACA;;AAEE;;AAGF;AACA;AACA;AACA;;;;;;AAOA;AACE;AACA;AACA;;;;;;AAOE;;;;;AAMA;AACA;AACA;;;AAIA;AACA;AACA;AACA;AACA;;;;;;;AAQJ;;AAEE;AACA;AAEF;AACA;;;;;AAMA;AACA;AACA;;;AAIF;AACF;;;;;;;;;;;;;;;;AAgBC;AAED;;;AAGE;AACD;AACH;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Alert.js","sources":["
|
|
1
|
+
{"version":3,"file":"Alert.js","sources":["src/components/feedback/Alert/Alert.tsx"],"sourcesContent":["'use client';\n\nimport { assignInlineVars } from '@vanilla-extract/dynamic';\nimport React from 'react';\n\nimport { CheckIcon } from '@/components/Icons/CheckIcon';\nimport { CloseIcon } from '@/components/Icons/CloseIcon';\nimport { ErrorIcon } from '@/components/Icons/ErrorIcon';\nimport { InfoIcon } from '@/components/Icons/InfoIcon';\nimport { WarningIcon } from '@/components/Icons/WarningIcon';\nimport { IconButton } from '@/components/primitives/IconButton';\nimport { vars } from '@/theme/contract.css';\nimport { cx } from '@/utils/cx';\n\nimport {\n alertCloseButtonSolidStyle,\n alertCloseColumnStyle,\n alertColorVar,\n alertContentStyle,\n alertIconSolidStyle,\n alertIconStyle,\n alertRecipe,\n} from './Alert.css';\nimport { AlertActions } from './AlertActions';\nimport { AlertDescription } from './AlertDescription';\nimport { AlertTitle } from './AlertTitle';\n\nimport type { AlertProps, AlertVariant } from './Alert.types';\n\nconst VARIANT_COLOR: Record<AlertVariant, string> = {\n info: vars.colors.accent.primary,\n success: vars.colors.accent.success,\n warning: vars.colors.accent.warning,\n error: vars.colors.accent.error,\n neutral: vars.colors.text.muted,\n};\n\nconst VARIANT_ROLE: Record<AlertVariant, { role: string; ariaLabel?: string }> =\n {\n info: { role: 'status' },\n success: { role: 'status' },\n warning: { role: 'alert' },\n error: { role: 'alert' },\n neutral: { role: 'region', ariaLabel: 'Notice' },\n };\n\nconst VARIANT_DEFAULT_ICON: Record<AlertVariant, React.ReactNode> = {\n info: <InfoIcon size=\"md\" decorative />,\n success: <CheckIcon size=\"md\" decorative />,\n warning: <WarningIcon size=\"md\" decorative />,\n error: <ErrorIcon size=\"md\" decorative />,\n neutral: null,\n};\n\n/**\n * Persistent inline alert / callout.\n *\n * Use for non-transient status messages tied to the surrounding UI — read-only\n * notices, expired credentials, unsaved-changes banners, etc. For transient\n * confirmations like \"File saved\", reach for `useToast` instead.\n *\n * Compound API: pair with `<Alert.Title>`, `<Alert.Description>`, and\n * `<Alert.Actions>` for richer layouts. The same components are also\n * available as standalone named exports (`AlertTitle`, `AlertDescription`,\n * `AlertActions`).\n *\n * Accessibility: roles are derived from the variant — `alert` for\n * `error`/`warning`, `status` for `info`/`success`, `region` for `neutral`.\n * The component never auto-focuses; inline alerts shouldn't grab focus.\n *\n * @example\n * ```tsx\n * <Alert variant=\"warning\" title=\"Read-only mode\">\n * Switch to edit mode to make changes.\n * </Alert>\n *\n * <Alert variant=\"error\" onClose={() => setOpen(false)}>\n * <Alert.Title>Couldn't reach the server</Alert.Title>\n * <Alert.Description>The request timed out.</Alert.Description>\n * <Alert.Actions align=\"right\">\n * <Button onClick={retry}>Retry</Button>\n * </Alert.Actions>\n * </Alert>\n * ```\n */\nconst AlertImpl = /*#__PURE__*/ React.memo<AlertProps>(\n ({\n variant = 'info',\n appearance = 'subtle',\n icon = true,\n onClose,\n title,\n width = '100%',\n children,\n className,\n style,\n testId,\n 'aria-label': ariaLabelProp,\n ref,\n ...rest\n }) => {\n const isSolid = appearance === 'solid';\n const { role, ariaLabel: defaultAriaLabel } = VARIANT_ROLE[variant];\n\n const resolvedIcon: React.ReactNode =\n icon === false\n ? null\n : icon === true\n ? VARIANT_DEFAULT_ICON[variant]\n : icon;\n\n const inlineVars = assignInlineVars({\n [alertColorVar]: VARIANT_COLOR[variant],\n });\n\n const resolvedWidth = typeof width === 'number' ? `${width}px` : width;\n\n return (\n <div\n ref={ref}\n role={role}\n aria-label={ariaLabelProp ?? defaultAriaLabel}\n className={cx(alertRecipe({ variant, appearance }), className)}\n style={{ ...inlineVars, width: resolvedWidth, ...style }}\n data-testid={testId}\n data-variant={variant}\n data-appearance={appearance}\n {...rest}\n >\n {resolvedIcon ? (\n <div\n className={cx(alertIconStyle, isSolid && alertIconSolidStyle)}\n data-alert-icon=\"\"\n aria-hidden=\"true\"\n >\n {resolvedIcon}\n </div>\n ) : null}\n\n <div className={alertContentStyle}>\n {title !== undefined && title !== null ? (\n <AlertTitle>{title}</AlertTitle>\n ) : null}\n {typeof children === 'string' ? (\n <AlertDescription>{children}</AlertDescription>\n ) : (\n children\n )}\n </div>\n\n {onClose ? (\n <div className={alertCloseColumnStyle}>\n <IconButton\n type=\"button\"\n aria-label=\"Close alert\"\n variant=\"ghost\"\n size=\"sm\"\n radius=\"sm\"\n onClick={onClose}\n className={isSolid ? alertCloseButtonSolidStyle : undefined}\n >\n <CloseIcon size=\"sm\" decorative />\n </IconButton>\n </div>\n ) : null}\n </div>\n );\n }\n);\n\nAlertImpl.displayName = 'Alert';\n\n/**\n * Public API: `Alert` with attached compound members.\n *\n * `React.memo` returns an exotic component that doesn't accept arbitrary\n * static fields with proper typing — we expose the compound members through\n * a typed wrapper.\n */\ntype AlertCompound = typeof AlertImpl & {\n Title: typeof AlertTitle;\n Description: typeof AlertDescription;\n Actions: typeof AlertActions;\n};\n\nexport const Alert = AlertImpl as AlertCompound;\nAlert.Title = AlertTitle;\nAlert.Description = AlertDescription;\nAlert.Actions = AlertActions;\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AA6BA;AACE;AACA;AACA;AACA;AACA;;AAGF;AAEI;AACA;AACA;AACA;;;AAIJ;;;;;AAKE;;AAGF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8BG;AACH;AAgBI;AACA;AAEA;AAEI;;AAEE;;;AAIJ;AACD;AAED;;AAoDF;AAGF;AAeO;AACP;AACA;AACA;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AlertActions.js","sources":["
|
|
1
|
+
{"version":3,"file":"AlertActions.js","sources":["src/components/feedback/Alert/AlertActions.tsx"],"sourcesContent":["'use client';\n\nimport React from 'react';\n\nimport { cx } from '@/utils/cx';\n\nimport { alertActionsRecipe } from './Alert.css';\n\nimport type { AlertActionsProps } from './Alert.types';\n\n/**\n * Action-button row for an `Alert`. Lays out children with a horizontal gap\n * and configurable alignment. Renders only when actions are needed — for\n * dismissible alerts the close button in the top-right is usually enough.\n *\n * @example\n * ```tsx\n * <Alert variant=\"error\">\n * <Alert.Title>Couldn't reach the server</Alert.Title>\n * <Alert.Description>The request timed out after 30 seconds.</Alert.Description>\n * <Alert.Actions align=\"right\">\n * <Button variant=\"filled\" onClick={retry}>Retry</Button>\n * <Button onClick={dismiss}>Dismiss</Button>\n * </Alert.Actions>\n * </Alert>\n * ```\n */\nexport const AlertActions = /*#__PURE__*/ React.memo<AlertActionsProps>(\n ({ children, align = 'left', className, style, testId, ref, ...rest }) => {\n return (\n <div\n ref={ref}\n className={cx(alertActionsRecipe({ align }), className)}\n style={style}\n data-testid={testId}\n data-alert-actions=\"\"\n {...rest}\n >\n {children}\n </div>\n );\n }\n);\n\nAlertActions.displayName = 'AlertActions';\n"],"names":[],"mappings":";;;;;;AAUA;;;;;;;;;;;;;;;;AAgBG;AACI;AAEH;AAYF;AAGF;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AlertDescription.js","sources":["
|
|
1
|
+
{"version":3,"file":"AlertDescription.js","sources":["src/components/feedback/Alert/AlertDescription.tsx"],"sourcesContent":["'use client';\n\nimport React from 'react';\n\nimport { cx } from '@/utils/cx';\n\nimport { alertDescriptionStyle } from './Alert.css';\n\nimport type { AlertDescriptionProps } from './Alert.types';\n\n/**\n * Body text slot for an `Alert`. Most one-line alerts can pass a string as\n * children to `<Alert>` directly; reach for this when you need to compose\n * multiple paragraphs or inline elements.\n *\n * @example\n * ```tsx\n * <Alert variant=\"info\">\n * <Alert.Title>Heads up</Alert.Title>\n * <Alert.Description>\n * This editor is read-only. Open the file in edit mode to make changes.\n * </Alert.Description>\n * </Alert>\n * ```\n */\nexport const AlertDescription = /*#__PURE__*/ React.memo<AlertDescriptionProps>(\n ({ children, className, style, testId, ref, ...rest }) => {\n return (\n <div\n ref={ref}\n className={cx(alertDescriptionStyle, className)}\n style={style}\n data-testid={testId}\n data-alert-description=\"\"\n {...rest}\n >\n {children}\n </div>\n );\n }\n);\n\nAlertDescription.displayName = 'AlertDescription';\n"],"names":[],"mappings":";;;;;;AAUA;;;;;;;;;;;;;;AAcG;AACI;AAEH;AAYF;AAGF;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AlertTitle.js","sources":["
|
|
1
|
+
{"version":3,"file":"AlertTitle.js","sources":["src/components/feedback/Alert/AlertTitle.tsx"],"sourcesContent":["'use client';\n\nimport React from 'react';\n\nimport { cx } from '@/utils/cx';\n\nimport { alertTitleStyle } from './Alert.css';\n\nimport type { AlertTitleProps } from './Alert.types';\n\n/**\n * Title slot for an `Alert`. Renders a single line of emphasized text above\n * the description.\n *\n * Equivalent to passing `title` directly on `<Alert>`. Use this form when you\n * want richer content (links, inline elements) inside the title.\n *\n * @example\n * ```tsx\n * <Alert variant=\"warning\">\n * <Alert.Title>Read-only mode</Alert.Title>\n * <Alert.Description>Switch to edit to change this file.</Alert.Description>\n * </Alert>\n * ```\n */\nexport const AlertTitle = /*#__PURE__*/ React.memo<AlertTitleProps>(\n ({ children, className, style, testId, ref, ...rest }) => {\n return (\n <div\n ref={ref}\n className={cx(alertTitleStyle, className)}\n style={style}\n data-testid={testId}\n data-alert-title=\"\"\n {...rest}\n >\n {children}\n </div>\n );\n }\n);\n\nAlertTitle.displayName = 'AlertTitle';\n"],"names":[],"mappings":";;;;;;AAUA;;;;;;;;;;;;;;AAcG;AACI;AAEH;AAYF;AAGF;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CommandPalette.js","sources":["../../../../../../src/components/feedback/CommandPalette/CommandPalette.tsx"],"sourcesContent":[null],"names":[],"mappings":";;;;;;;;;;;AAuCA;AACA;AAUA;AACE;;AACsB;;;AAEtB;AACF;AAEA;;;;;;;;;;;;;;;;;;;;;;AAsBG;AACI;AAoBL;AACA;AACA;AAEA;AACA;;;;;;AAOA;AAEA;AACE;;AAED;;;;;;;;;;AAWD;;;;AAKI;AACE;;AAEF;;AAEA;;AAEF;AACF;;;;AAKE;;;;AAKE;;AAEE;AAAwB;;AAE1B;AACE;AACA;;;;AAKJ;AAEA;AACE;;AAEE;AACA;;;;;;;AASJ;;AAEE;;AACA;AAEI;;AAEE;;;;AAIF;AACA;;AAED;;AAEH;AACE;;AAEE;AACA;;;;AAID;AACD;;;AAIJ;;;;;AAMF;;;;AAEO;AACL;;AAGF;;AAGM;;AACe;AACf;;AACc;;;AAEd;AACF;AACF;AAIF;;;AAGI;AACA;;;;AAGE;;AAEF;;AAKJ;AAEI;;AAEE;;;AAGF;;;;;AAKA;;AAEE;;;AAGF;;;;;AAKA;;AAEE;;;AAGF;;AAEE;AACA;;;AAEJ;;;AAMA;;AACA;AACA;;AACA;;;;AAMF;AAEA;;AAGM;;AAEJ;AAIF;AAAW;AAEX;AACA;;;;AA8CY;;AAEF;AAqBE;;;AAWA;AACA;AACA;AAAsC;AACtC;;;;;;;AAOE;;;AAYE;;;AAOI;AACA;;AAOV;AAOV;;;AAGA;AACF;AAEA;AAEA;;"}
|
|
1
|
+
{"version":3,"file":"CommandPalette.js","sources":["src/components/feedback/CommandPalette/CommandPalette.tsx"],"sourcesContent":["'use client';\n\nimport React, {\n useCallback,\n useDeferredValue,\n useEffect,\n useId,\n useMemo,\n useRef,\n useState,\n} from 'react';\nimport { createPortal } from 'react-dom';\n\nimport { SearchIcon } from '@/components/Icons/SearchIcon';\nimport { Kbd } from '@/components/primitives/Kbd';\nimport { cx } from '@/utils/cx';\n\nimport {\n emptyStyle,\n groupHeaderStyle,\n inputStyle,\n inputWrapperStyle,\n itemDescriptionStyle,\n itemIconStyle,\n itemLabelStyle,\n itemSelectedStyle,\n itemShortcutStyle,\n itemStyle,\n itemTextStyle,\n listStyle,\n overlayStyle,\n panelStyle,\n searchIconStyle,\n} from './CommandPalette.css';\nimport { fuzzyFilter } from './fuzzySearch';\nimport { useRecentItems } from './useRecentItems';\n\nimport type { CommandItem, CommandPaletteProps } from './CommandPalette.types';\n\nconst DEFAULT_GROUP = 'default';\nconst RECENT_GROUP = '__recent__';\n\ninterface ResolvedRow {\n type: 'header' | 'item';\n group: string;\n groupLabel: string;\n item?: CommandItem;\n index?: number; // index into the flat selectable items list\n}\n\nfunction getItemSearchStrings(item: CommandItem): string[] {\n const out = [item.label];\n if (item.description) out.push(item.description);\n if (item.keywords) out.push(...item.keywords);\n return out;\n}\n\n/**\n * Search-driven command list shown as a centred floating dialog. Type to\n * fuzzy-filter, ArrowUp/ArrowDown to navigate, Enter to select, Escape to\n * close. Recent selections are tracked in localStorage when `recentKey` is\n * set. The component does not bind a global hotkey — wire `useHotkey` in\n * the consumer.\n *\n * @example\n * ```tsx\n * useHotkey('Mod+K', () => { setOpen(true); });\n *\n * <CommandPalette\n * open={open}\n * onClose={() => setOpen(false)}\n * items={[\n * { id: 'open', label: 'Open File', shortcut: 'Cmd+O', group: 'File' },\n * { id: 'save', label: 'Save', shortcut: 'Cmd+S', group: 'File' },\n * ]}\n * onSelect={(item) => runCommand(item.id)}\n * recentKey=\"myapp:command-palette\"\n * />\n * ```\n */\nexport const CommandPalette: React.FC<CommandPaletteProps> = ({\n open,\n onClose,\n items,\n onSelect,\n placeholder = 'Type a command or search...',\n emptyState,\n recentLabel = 'Recent',\n recentKey,\n maxRecent = 5,\n renderItem,\n portal = true,\n maxHeight = 400,\n width = 640,\n className,\n style,\n testId,\n id,\n ...rest\n}) => {\n const autoId = useId();\n const inputId = id ?? `command-palette-${autoId}`;\n const listboxId = `${inputId}-listbox`;\n\n const inputRef = useRef<HTMLInputElement>(null);\n const listRef = useRef<HTMLUListElement>(null);\n\n const [query, setQuery] = useState('');\n const [activeIndex, setActiveIndex] = useState(0);\n // Defer the query that drives fuzzy filtering so the input stays responsive\n // while a large command list re-filters and re-sorts. The immediate `query`\n // still drives the input value.\n const deferredQuery = useDeferredValue(query);\n\n const { recentIds, pushRecent } = useRecentItems({\n storageKey: recentKey,\n maxRecent,\n });\n\n // Reset state on each open transition. This reacts to the `open` prop\n // toggling rather than synchronizing continuous state, so the one-shot\n // setState in the effect is intentional.\n useEffect(() => {\n if (open) {\n // eslint-disable-next-line react-hooks/set-state-in-effect\n setQuery('');\n setActiveIndex(0);\n }\n }, [open]);\n\n // Focus the input on open.\n useEffect(() => {\n if (open) {\n const t = setTimeout(() => {\n inputRef.current?.focus();\n }, 0);\n return () => {\n clearTimeout(t);\n };\n }\n return undefined;\n }, [open]);\n\n // Resolve groups + filtering.\n const { rows, selectableItems } = useMemo(() => {\n const groupOrder: string[] = [];\n const groupMap = new Map<string, CommandItem[]>();\n\n if (deferredQuery === '' && recentIds.length > 0) {\n const byId = new Map(items.map(it => [it.id, it]));\n const recent: CommandItem[] = [];\n for (const id of recentIds) {\n const it = byId.get(id);\n if (it && !it.disabled) recent.push(it);\n }\n if (recent.length > 0) {\n groupOrder.push(RECENT_GROUP);\n groupMap.set(RECENT_GROUP, recent);\n }\n }\n\n const filtered = fuzzyFilter(deferredQuery, items, getItemSearchStrings);\n const filteredItems = filtered.map(r => r.item);\n\n for (const item of filteredItems) {\n const g = item.group ?? DEFAULT_GROUP;\n if (!groupMap.has(g)) {\n groupOrder.push(g);\n groupMap.set(g, []);\n }\n groupMap.get(g)?.push(item);\n }\n\n const rows: ResolvedRow[] = [];\n const flat: CommandItem[] = [];\n let nextIndex = 0;\n\n for (const groupKey of groupOrder) {\n const list = groupMap.get(groupKey);\n if (!list || list.length === 0) continue;\n const groupLabel =\n groupKey === RECENT_GROUP\n ? recentLabel\n : groupKey === DEFAULT_GROUP\n ? ''\n : groupKey;\n if (groupLabel) {\n rows.push({\n type: 'header',\n group: groupKey,\n groupLabel,\n });\n }\n for (const item of list) {\n const index = nextIndex++;\n rows.push({\n type: 'item',\n group: groupKey,\n groupLabel,\n item,\n index,\n });\n flat.push(item);\n }\n }\n\n return { rows, selectableItems: flat };\n }, [items, deferredQuery, recentIds, recentLabel]);\n\n // Clamp the active index during render when the result list shrinks. This is\n // React's documented adjust-state-during-render pattern — it avoids an extra\n // commit, and the guards make it self-terminating.\n if (selectableItems.length === 0) {\n if (activeIndex !== 0) setActiveIndex(0);\n } else if (activeIndex >= selectableItems.length) {\n setActiveIndex(selectableItems.length - 1);\n }\n\n const moveActive = useCallback(\n (delta: number) => {\n setActiveIndex(prev => {\n const len = selectableItems.length;\n if (len === 0) return 0;\n let next = prev + delta;\n if (next < 0) next = len - 1;\n if (next >= len) next = 0;\n return next;\n });\n },\n [selectableItems.length]\n );\n\n const runSelect = useCallback(\n (item: CommandItem) => {\n if (item.disabled) return;\n pushRecent(item.id);\n if (item.onSelect) {\n item.onSelect();\n } else {\n onSelect?.(item);\n }\n onClose();\n },\n [onClose, onSelect, pushRecent]\n );\n\n const handleKeyDown = useCallback(\n (e: React.KeyboardEvent<HTMLDivElement>) => {\n if (e.key === 'Escape') {\n e.preventDefault();\n onClose();\n return;\n }\n if (e.key === 'ArrowDown') {\n e.preventDefault();\n moveActive(1);\n return;\n }\n if (e.key === 'ArrowUp') {\n e.preventDefault();\n moveActive(-1);\n return;\n }\n if (e.key === 'Home') {\n e.preventDefault();\n setActiveIndex(0);\n return;\n }\n if (e.key === 'End') {\n e.preventDefault();\n setActiveIndex(Math.max(0, selectableItems.length - 1));\n return;\n }\n if (e.key === 'Enter') {\n e.preventDefault();\n const item = selectableItems[activeIndex];\n if (item) runSelect(item);\n }\n },\n [activeIndex, moveActive, onClose, runSelect, selectableItems]\n );\n\n // Scroll active item into view when it changes.\n useEffect(() => {\n if (!open) return;\n const list = listRef.current;\n if (!list) return;\n const el = list.querySelector<HTMLElement>(\n `[data-row-index=\"${String(activeIndex)}\"]`\n );\n if (el && typeof el.scrollIntoView === 'function') {\n el.scrollIntoView({ block: 'nearest' });\n }\n }, [activeIndex, open]);\n\n const handleOverlayClick = useCallback(\n (e: React.MouseEvent) => {\n if (e.target === e.currentTarget) {\n onClose();\n }\n },\n [onClose]\n );\n\n if (!open) return null;\n\n const widthCss = typeof width === 'number' ? `${String(width)}px` : width;\n const activeId =\n activeIndex < selectableItems.length\n ? `${listboxId}-item-${String(activeIndex)}`\n : undefined;\n\n const content = (\n <>\n {/* Decorative backdrop: pointer users can click to dismiss, while\n keyboard users dismiss via Escape handled on the dialog panel. */}\n <div\n aria-hidden=\"true\"\n className={overlayStyle}\n data-testid={testId ? `${testId}-overlay` : undefined}\n onClick={handleOverlayClick}\n />\n {/* The dialog panel needs a keydown handler for Escape-to-close and\n listbox navigation; this is required dialog behavior, not a static\n element repurposed as interactive. */}\n {/* eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions */}\n <div\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label=\"Command palette\"\n className={cx(panelStyle, className)}\n style={{ width: widthCss, ...style }}\n data-testid={testId}\n onKeyDown={handleKeyDown}\n {...rest}\n >\n <div className={inputWrapperStyle}>\n <span className={searchIconStyle} aria-hidden=\"true\">\n <SearchIcon />\n </span>\n <input\n ref={inputRef}\n id={inputId}\n type=\"text\"\n role=\"combobox\"\n aria-expanded=\"true\"\n aria-controls={listboxId}\n aria-activedescendant={activeId}\n aria-autocomplete=\"list\"\n className={inputStyle}\n placeholder={placeholder}\n value={query}\n onChange={e => {\n setQuery(e.target.value);\n setActiveIndex(0);\n }}\n data-testid={testId ? `${testId}-input` : undefined}\n />\n </div>\n\n {selectableItems.length === 0 ? (\n <div\n className={emptyStyle}\n data-testid={testId ? `${testId}-empty` : undefined}\n >\n {emptyState ?? `No matches for \"${deferredQuery}\"`}\n </div>\n ) : (\n <ul\n ref={listRef}\n id={listboxId}\n role=\"listbox\"\n className={listStyle}\n style={{ maxHeight }}\n >\n {rows.map((row, rowIndex) => {\n if (row.type === 'header') {\n return (\n <li\n key={`hdr-${row.group}-${String(rowIndex)}`}\n role=\"presentation\"\n className={groupHeaderStyle}\n >\n {row.groupLabel}\n </li>\n );\n }\n const item = row.item;\n const itemIndex = row.index;\n if (!item || itemIndex === undefined) return null;\n const isSelected = itemIndex === activeIndex;\n const itemDomId = `${listboxId}-item-${String(itemIndex)}`;\n return (\n // Keyboard selection is handled centrally via the\n // aria-activedescendant model on the dialog (Enter selects the\n // active option); the click is a pointer-only convenience.\n // eslint-disable-next-line jsx-a11y/click-events-have-key-events\n <li\n key={`${row.group}-${item.id}`}\n id={itemDomId}\n role=\"option\"\n aria-selected={isSelected}\n aria-disabled={item.disabled ? true : undefined}\n className={cx(itemStyle, isSelected && itemSelectedStyle)}\n data-disabled={item.disabled ? 'true' : undefined}\n data-row-index={itemIndex}\n data-testid={testId ? `${testId}-item-${item.id}` : undefined}\n onMouseEnter={() => {\n if (!item.disabled) setActiveIndex(itemIndex);\n }}\n onClick={() => {\n runSelect(item);\n }}\n >\n {renderItem ? (\n renderItem(item, {\n selected: isSelected,\n query: deferredQuery,\n })\n ) : (\n <DefaultItemContent item={item} />\n )}\n </li>\n );\n })}\n </ul>\n )}\n </div>\n </>\n );\n\n if (portal && typeof document !== 'undefined') {\n return createPortal(content, document.body);\n }\n return content;\n};\n\nCommandPalette.displayName = 'CommandPalette';\n\nconst DefaultItemContent: React.FC<{ item: CommandItem }> = ({ item }) => (\n <>\n {item.icon && <span className={itemIconStyle}>{item.icon}</span>}\n <span className={itemTextStyle}>\n <span className={itemLabelStyle}>{item.label}</span>\n {item.description && (\n <span className={itemDescriptionStyle}>{item.description}</span>\n )}\n </span>\n {item.shortcut && (\n <span className={itemShortcutStyle}>\n <Kbd size=\"sm\">{item.shortcut}</Kbd>\n </span>\n )}\n </>\n);\n"],"names":[],"mappings":";;;;;;;;;;;AAuCA;AACA;AAUA;AACE;;AACsB;;;AAEtB;AACF;AAEA;;;;;;;;;;;;;;;;;;;;;;AAsBG;AACI;AAoBL;AACA;AACA;AAEA;AACA;;;;;;AAOA;AAEA;AACE;;AAED;;;;;;;;;;AAWD;;;;AAKI;AACE;;AAEF;;AAEA;;AAEF;AACF;;;;AAKE;;;;AAKE;;AAEE;AAAwB;;AAE1B;AACE;AACA;;;;AAKJ;AAEA;AACE;;AAEE;AACA;;;;;;;AASJ;;AAEE;;AACA;AAEI;;AAEE;;;;AAIF;AACA;;AAED;;AAEH;AACE;;AAEE;AACA;;;;AAID;AACD;;;AAIJ;;;;;AAMF;;;;AAEO;AACL;;AAGF;;AAGM;;AACe;AACf;;AACc;;;AAEd;AACF;AACF;AAIF;;;AAGI;AACA;;;;AAGE;;AAEF;;AAKJ;AAEI;;AAEE;;;AAGF;;;;;AAKA;;AAEE;;;AAGF;;;;;AAKA;;AAEE;;;AAGF;;AAEE;AACA;;;AAEJ;;;AAMA;;AACA;AACA;;AACA;;;;AAMF;AAEA;;AAGM;;AAEJ;AAIF;AAAW;AAEX;AACA;;;;AA8CY;;AAEF;AAqBE;;;AAWA;AACA;AACA;AAAsC;AACtC;;;;;;;AAOE;;;AAYE;;;AAOI;AACA;;AAOV;AAOV;;;AAGA;AACF;AAEA;AAEA;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fuzzySearch.js","sources":["
|
|
1
|
+
{"version":3,"file":"fuzzySearch.js","sources":["src/components/feedback/CommandPalette/fuzzySearch.ts"],"sourcesContent":["/**\n * Score how well `query` matches `target`. Higher = better. Zero = no match.\n *\n * Heuristics, in priority order:\n * 1. Exact case-insensitive match (1000).\n * 2. Target starts with the query (800 + length bonus).\n * 3. Target contains the query as a contiguous substring (500 + position bonus).\n * 4. Subsequence match: every query char appears in target in order; bonuses\n * for consecutive matches, word-boundary matches, and earlier positions.\n *\n * @example\n * ```ts\n * fuzzyScore('opn', 'open file') // > 0, subsequence match\n * fuzzyScore('zzz', 'open file') // 0\n * ```\n */\nexport function fuzzyScore(query: string, target: string): number {\n if (query === '') return 1;\n if (target === '') return 0;\n\n const q = query.toLowerCase();\n const t = target.toLowerCase();\n\n if (q === t) return 1000;\n if (t.startsWith(q)) return 800 + Math.max(0, 50 - target.length);\n const containsAt = t.indexOf(q);\n if (containsAt !== -1)\n return 500 - containsAt + Math.max(0, 30 - target.length);\n\n let score = 0;\n let qi = 0;\n let lastMatch = -2;\n for (let ti = 0; ti < t.length && qi < q.length; ti++) {\n const ch = t[ti];\n if (ch === q[qi]) {\n // Earlier matches contribute more.\n score += Math.max(1, 30 - ti);\n if (lastMatch === ti - 1) {\n score += 25;\n }\n // Bonus when the match begins a \"word\" (after space/-/_/.)\n const prev = ti > 0 ? t[ti - 1] : '';\n if (\n ti === 0 ||\n prev === ' ' ||\n prev === '-' ||\n prev === '_' ||\n prev === '.'\n ) {\n score += 20;\n }\n lastMatch = ti;\n qi++;\n }\n }\n\n if (qi !== q.length) return 0;\n return score;\n}\n\nexport interface FuzzyMatch<T> {\n item: T;\n score: number;\n}\n\n/**\n * Score a list of items against a query. Items with score 0 are dropped, the\n * rest are sorted by descending score. The `getStrings` accessor returns one\n * or more strings per item to score against; the highest-scoring string wins.\n */\nexport function fuzzyFilter<T>(\n query: string,\n items: readonly T[],\n getStrings: (item: T) => readonly string[]\n): FuzzyMatch<T>[] {\n if (query === '') {\n return items.map(item => ({ item, score: 1 }));\n }\n const result: FuzzyMatch<T>[] = [];\n for (const item of items) {\n const strings = getStrings(item);\n let best = 0;\n for (const s of strings) {\n const score = fuzzyScore(query, s);\n if (score > best) best = score;\n }\n if (best > 0) {\n result.push({ item, score: best });\n }\n }\n result.sort((a, b) => b.score - a.score);\n return result;\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useRecentItems.js","sources":["
|
|
1
|
+
{"version":3,"file":"useRecentItems.js","sources":["src/components/feedback/CommandPalette/useRecentItems.ts"],"sourcesContent":["'use client';\n\nimport { useCallback, useEffect, useState } from 'react';\n\ninterface UseRecentItemsOptions {\n /** localStorage key (component does not bind/store unless this is set). */\n storageKey?: string;\n /** Maximum number of recent ids to keep. @default 5 */\n maxRecent?: number;\n}\n\ninterface UseRecentItemsReturn {\n recentIds: string[];\n pushRecent: (id: string) => void;\n clearRecent: () => void;\n}\n\nfunction readStorage(key: string): string[] {\n if (typeof window === 'undefined') return [];\n try {\n const raw = window.localStorage.getItem(key);\n if (!raw) return [];\n const parsed: unknown = JSON.parse(raw);\n if (Array.isArray(parsed)) {\n return parsed.filter((v): v is string => typeof v === 'string');\n }\n return [];\n } catch {\n return [];\n }\n}\n\nfunction writeStorage(key: string, ids: string[]): void {\n if (typeof window === 'undefined') return;\n try {\n window.localStorage.setItem(key, JSON.stringify(ids));\n } catch {\n // localStorage may be unavailable (private mode, full quota); silently ignore.\n }\n}\n\n/**\n * Track recently selected command ids. Backed by localStorage when\n * `storageKey` is provided; otherwise the list is in-memory only.\n */\nexport function useRecentItems({\n storageKey,\n maxRecent = 5,\n}: UseRecentItemsOptions): UseRecentItemsReturn {\n const [recentIds, setRecentIds] = useState<string[]>(() =>\n storageKey ? readStorage(storageKey).slice(0, maxRecent) : []\n );\n\n // Re-read storage if the key changes after mount. External-system sync\n // (localStorage), so setState in the effect is the correct pattern.\n useEffect(() => {\n if (storageKey) {\n // eslint-disable-next-line react-hooks/set-state-in-effect\n setRecentIds(readStorage(storageKey).slice(0, maxRecent));\n } else {\n setRecentIds([]);\n }\n }, [storageKey, maxRecent]);\n\n const pushRecent = useCallback(\n (id: string) => {\n setRecentIds(prev => {\n const next = [id, ...prev.filter(v => v !== id)].slice(0, maxRecent);\n if (storageKey) writeStorage(storageKey, next);\n return next;\n });\n },\n [storageKey, maxRecent]\n );\n\n const clearRecent = useCallback(() => {\n setRecentIds([]);\n if (storageKey) writeStorage(storageKey, []);\n }, [storageKey]);\n\n return { recentIds, pushRecent, clearRecent };\n}\n"],"names":[],"mappings":";;;AAiBA;;AACqC;AACnC;;AAEE;AAAU;;AAEV;AACE;;AAEF;;AACA;AACA;;AAEJ;AAEA;;;AAEE;AACE;;AACA;;;AAGJ;AAEA;;;AAGG;AACG;AAIJ;;;;;;AASI;;;;;AAIJ;AAEA;;;AAIM;AAAgB;AAChB;AACF;AACF;AAIF;;AAEE;AAAgB;AAClB;AAEA;AACF;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Dialog.js","sources":["
|
|
1
|
+
{"version":3,"file":"Dialog.js","sources":["src/components/feedback/Dialog/Dialog.tsx"],"sourcesContent":["'use client';\n\nimport React, {\n createContext,\n useCallback,\n useContext,\n useId,\n useRef,\n} from 'react';\nimport { createPortal } from 'react-dom';\n\nimport { useFocusTrap } from '@/hooks/useFocusTrap';\nimport { useMergedRef } from '@/hooks/useMergedRef';\nimport { cx } from '@/utils/cx';\n\nimport { overlayRecipe, dialogPanelRecipe } from './Dialog.css';\nimport { useDialogAnimation } from './useDialogAnimation';\n\nimport type { DialogContextValue, DialogProps } from './Dialog.types';\n\n// --- Context ---\n\nconst DialogContext = /*#__PURE__*/ createContext<DialogContextValue | null>(\n null\n);\n\nexport function useDialogContext(): DialogContextValue {\n const ctx = useContext(DialogContext);\n if (!ctx) {\n throw new Error(\n 'Dialog sub-components must be used within a <Dialog> component'\n );\n }\n return ctx;\n}\n\n// --- Component ---\n\n/**\n * Dialog component for modal overlays in editor interfaces.\n *\n * Renders an accessible modal dialog with overlay, focus trap,\n * and keyboard support. Compound component pattern: use with\n * DialogHeader, DialogBody, DialogFooter, and DialogClose.\n *\n * @example\n * ```tsx\n * <Dialog open={isOpen} onClose={() => setIsOpen(false)} title=\"Confirm\">\n * <DialogHeader>Confirm Action</DialogHeader>\n * <DialogBody>Are you sure?</DialogBody>\n * <DialogFooter align=\"right\">\n * <Button onClick={() => setIsOpen(false)}>Cancel</Button>\n * <Button variant=\"filled\">Confirm</Button>\n * </DialogFooter>\n * </Dialog>\n * ```\n */\nexport const Dialog: React.FC<DialogProps> = ({\n open,\n onClose,\n size = 'md',\n title,\n description,\n closeOnOverlayClick = true,\n closeOnEscape = true,\n showOverlay = true,\n trapFocus = true,\n initialFocusRef,\n portal = true,\n children,\n className,\n style,\n testId,\n id,\n ref,\n ...rest\n}) => {\n const autoId = useId();\n const titleId = `dialog-title-${autoId}`;\n const descriptionId = `dialog-desc-${autoId}`;\n const panelRef = useRef<HTMLDivElement>(null);\n\n const { mounted, closing } = useDialogAnimation({\n open,\n panelRef,\n initialFocusRef,\n });\n\n const handleFocusTrap = useFocusTrap({\n containerRef: panelRef,\n enabled: trapFocus,\n });\n\n const setPanelRef = useMergedRef<HTMLDivElement>(panelRef, ref);\n\n const handleKeyDown = useCallback(\n (e: React.KeyboardEvent) => {\n // Escape handler — inline instead of global document listener\n if (closeOnEscape && e.key === 'Escape') {\n e.stopPropagation();\n onClose();\n return;\n }\n // Focus trap\n handleFocusTrap(e);\n },\n [closeOnEscape, onClose, handleFocusTrap]\n );\n\n const handleOverlayClick = useCallback(\n (e: React.MouseEvent) => {\n if (closeOnOverlayClick && e.target === e.currentTarget) {\n onClose();\n }\n },\n [closeOnOverlayClick, onClose]\n );\n\n if (!mounted) return null;\n\n const contextValue: DialogContextValue = {\n onClose,\n titleId,\n descriptionId,\n };\n\n const dialogContent = (\n <DialogContext.Provider value={contextValue}>\n {showOverlay && (\n // Decorative backdrop: pointer users can click to dismiss, while\n // keyboard users dismiss via Escape handled on the dialog panel.\n <div\n aria-hidden=\"true\"\n className={overlayRecipe({ closing })}\n onClick={handleOverlayClick}\n data-testid={testId ? `${testId}-overlay` : undefined}\n />\n )}\n {/* The dialog panel needs a keydown handler for Escape-to-close and\n focus trapping; this is required modal-dialog behavior, not a static\n element repurposed as interactive. */}\n {/* eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions */}\n <div\n ref={setPanelRef}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby={title ? titleId : undefined}\n aria-describedby={description ? descriptionId : undefined}\n tabIndex={-1}\n className={cx(dialogPanelRecipe({ size, closing }), className)}\n style={style}\n id={id}\n data-testid={testId}\n onKeyDown={handleKeyDown}\n {...rest}\n >\n {/* Hidden elements for ARIA references when using title/description props */}\n {title && (\n <span id={titleId} style={{ display: 'none' }}>\n {title}\n </span>\n )}\n {description && (\n <span id={descriptionId} style={{ display: 'none' }}>\n {description}\n </span>\n )}\n {children}\n </div>\n </DialogContext.Provider>\n );\n\n if (portal) {\n return createPortal(dialogContent, document.body);\n }\n\n return dialogContent;\n};\n\nDialog.displayName = 'Dialog';\n"],"names":[],"mappings":";;;;;;;;;;AAoBA;AAEA;;AAKE;;AAEE;;AAIF;AACF;AAEA;AAEA;;;;;;;;;;;;;;;;;;AAkBG;AACI;AAoBL;AACA;AACA;AACA;AAEA;;;;AAIC;;AAGC;AACA;AACD;;AAID;;;;AAKM;;;;;;AASN;;AAGM;;AAEJ;AAIF;AAAc;AAEd;;;;;AAMA;;;AAKM;;;;AA6CN;AACF;AAEA;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DialogBody.js","sources":["
|
|
1
|
+
{"version":3,"file":"DialogBody.js","sources":["src/components/feedback/Dialog/DialogBody.tsx"],"sourcesContent":["'use client';\n\nimport React from 'react';\n\nimport { cx } from '@/utils/cx';\n\nimport { dialogBodyStyle } from './Dialog.css';\n\nimport type { DialogBodyProps } from './Dialog.types';\n\n// --- Component ---\n\n/**\n * DialogBody renders the scrollable content area of a Dialog.\n *\n * @example\n * ```tsx\n * <DialogBody>\n * <p>Dialog content goes here.</p>\n * </DialogBody>\n * ```\n */\nexport const DialogBody: React.FC<DialogBodyProps> = ({\n children,\n className,\n style,\n testId,\n ref,\n ...rest\n}) => {\n return (\n <div\n ref={ref}\n className={cx(dialogBodyStyle, className)}\n style={style}\n data-testid={testId}\n {...rest}\n >\n {children}\n </div>\n );\n};\n\nDialogBody.displayName = 'DialogBody';\n"],"names":[],"mappings":";;;;;AAUA;AAEA;;;;;;;;;AASG;;;AAoBH;AAEA;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DialogClose.js","sources":["
|
|
1
|
+
{"version":3,"file":"DialogClose.js","sources":["src/components/feedback/Dialog/DialogClose.tsx"],"sourcesContent":["'use client';\n\nimport React from 'react';\n\nimport { useDialogContext } from './Dialog';\n\ninterface DialogCloseProps {\n /** The element to wrap with close behavior */\n children: React.ReactElement<{ onClick?: React.MouseEventHandler }>;\n}\n\n/**\n * DialogClose wraps a child element and adds onClick to close the dialog.\n *\n * @example\n * ```tsx\n * <DialogClose>\n * <Button>Cancel</Button>\n * </DialogClose>\n * ```\n */\nexport const DialogClose: React.FC<DialogCloseProps> = ({ children }) => {\n const { onClose } = useDialogContext();\n\n return React.cloneElement(children, {\n onClick: (e: React.MouseEvent) => {\n children.props.onClick?.(e);\n onClose();\n },\n });\n};\n\nDialogClose.displayName = 'DialogClose';\n"],"names":[],"mappings":";;;;AAWA;;;;;;;;;AASG;;AAED;AAEA;AACE;;AAEE;;AAEH;AACH;AAEA;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DialogFooter.js","sources":["
|
|
1
|
+
{"version":3,"file":"DialogFooter.js","sources":["src/components/feedback/Dialog/DialogFooter.tsx"],"sourcesContent":["'use client';\n\nimport React from 'react';\n\nimport { cx } from '@/utils/cx';\n\nimport { dialogFooterRecipe } from './Dialog.css';\n\nimport type { DialogFooterProps } from './Dialog.types';\n\n// --- Component ---\n\n/**\n * DialogFooter renders the action area at the bottom of a Dialog.\n *\n * @example\n * ```tsx\n * <DialogFooter align=\"right\">\n * <Button onClick={onCancel}>Cancel</Button>\n * <Button variant=\"filled\" onClick={onConfirm}>Confirm</Button>\n * </DialogFooter>\n * ```\n */\nexport const DialogFooter: React.FC<DialogFooterProps> = ({\n children,\n align = 'right',\n className,\n style,\n testId,\n ref,\n ...rest\n}) => {\n return (\n <div\n ref={ref}\n className={cx(dialogFooterRecipe({ align }), className)}\n style={style}\n data-testid={testId}\n {...rest}\n >\n {children}\n </div>\n );\n};\n\nDialogFooter.displayName = 'DialogFooter';\n"],"names":[],"mappings":";;;;;AAUA;AAEA;;;;;;;;;;AAUG;AACI;AASL;AAWF;AAEA;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DialogHeader.js","sources":["
|
|
1
|
+
{"version":3,"file":"DialogHeader.js","sources":["src/components/feedback/Dialog/DialogHeader.tsx"],"sourcesContent":["'use client';\n\nimport React from 'react';\n\nimport { CloseIcon } from '@/components/Icons/CloseIcon';\nimport { cx } from '@/utils/cx';\n\nimport { useDialogContext } from './Dialog';\nimport {\n dialogHeaderStyle,\n dialogHeaderContentStyle,\n dialogTitleStyle,\n dialogDescriptionStyle,\n dialogCloseButtonStyle,\n} from './Dialog.css';\n\nimport type { DialogHeaderProps } from './Dialog.types';\n\n// --- Component ---\n\n/**\n * DialogHeader renders the title, optional description, and close button\n * for a Dialog.\n *\n * @example\n * ```tsx\n * <DialogHeader description=\"This cannot be undone\">\n * Delete Item\n * </DialogHeader>\n * ```\n */\nexport const DialogHeader: React.FC<DialogHeaderProps> = ({\n children,\n showClose = true,\n description,\n className,\n style,\n testId,\n ref,\n ...rest\n}) => {\n const { onClose, titleId, descriptionId } = useDialogContext();\n\n return (\n <div\n ref={ref}\n className={cx(dialogHeaderStyle, className)}\n style={style}\n data-testid={testId}\n {...rest}\n >\n <div className={dialogHeaderContentStyle}>\n <div id={titleId} className={dialogTitleStyle}>\n {children}\n </div>\n {description && (\n <div id={descriptionId} className={dialogDescriptionStyle}>\n {description}\n </div>\n )}\n </div>\n {showClose && (\n <button\n type=\"button\"\n onClick={onClose}\n aria-label=\"Close dialog\"\n className={dialogCloseButtonStyle}\n data-testid={testId ? `${testId}-close` : undefined}\n >\n <CloseIcon size=\"sm\" decorative />\n </button>\n )}\n </div>\n );\n};\n\nDialogHeader.displayName = 'DialogHeader';\n"],"names":[],"mappings":";;;;;;;AAkBA;AAEA;;;;;;;;;;AAUG;AACI;;;AA2CP;AAEA;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useDialogAnimation.js","sources":["
|
|
1
|
+
{"version":3,"file":"useDialogAnimation.js","sources":["src/components/feedback/Dialog/useDialogAnimation.ts"],"sourcesContent":["'use client';\n\nimport { useEffect, useRef, useState } from 'react';\n\nimport { FOCUSABLE_SELECTOR } from '@/hooks/useFocusTrap';\n\nimport { DIALOG_ANIMATION_MS } from './Dialog.css';\n\nimport type { RefObject } from 'react';\n\ninterface UseDialogAnimationOptions {\n open: boolean;\n panelRef: RefObject<HTMLElement | null>;\n initialFocusRef?: RefObject<HTMLElement | null>;\n}\n\ninterface UseDialogAnimationReturn {\n mounted: boolean;\n closing: boolean;\n}\n\n/**\n * Manages Dialog mount/unmount animation lifecycle and focus management.\n *\n * Combines three concerns into a single hook:\n * 1. Mount/unmount with closing animation delay\n * 2. Initial focus on open (respects initialFocusRef)\n * 3. Return focus to previously focused element on close\n */\nexport function useDialogAnimation({\n open,\n panelRef,\n initialFocusRef,\n}: UseDialogAnimationOptions): UseDialogAnimationReturn {\n const [mounted, setMounted] = useState(false);\n const [closing, setClosing] = useState(false);\n const previousFocusRef = useRef<HTMLElement | null>(null);\n const wasOpenRef = useRef(false);\n\n useEffect(() => {\n if (open) {\n // Opening: capture previous focus, mount immediately\n previousFocusRef.current = document.activeElement as HTMLElement;\n // Mount/closing state is driven by the `open` prop and gates a timed\n // enter/exit animation (delayed unmount below), so it must live in an\n // effect rather than be derived during render.\n // eslint-disable-next-line react-hooks/set-state-in-effect\n setClosing(false);\n setMounted(true);\n wasOpenRef.current = true;\n } else if (wasOpenRef.current) {\n // Closing: start close animation, unmount after delay. Under\n // prefers-reduced-motion: reduce, the close animation is suppressed\n // (Dialog.css.ts), so honor that here too — snap the unmount to\n // 0 ms instead of holding the dialog focus-trapped for nothing.\n wasOpenRef.current = false;\n setClosing(true);\n const prefersReducedMotion =\n typeof window !== 'undefined' &&\n typeof window.matchMedia === 'function' &&\n window.matchMedia('(prefers-reduced-motion: reduce)').matches;\n const delay = prefersReducedMotion ? 0 : DIALOG_ANIMATION_MS;\n const timer = setTimeout(() => {\n setMounted(false);\n setClosing(false);\n\n // Return focus after unmount\n if (previousFocusRef.current) {\n previousFocusRef.current.focus();\n previousFocusRef.current = null;\n }\n }, delay);\n return () => {\n clearTimeout(timer);\n };\n }\n return undefined;\n }, [open]);\n\n // Initial focus after mount\n useEffect(() => {\n if (!open || !mounted) return;\n\n const timer = setTimeout(() => {\n if (initialFocusRef?.current) {\n initialFocusRef.current.focus();\n } else {\n const panel = panelRef.current;\n if (panel) {\n const firstFocusable =\n panel.querySelector<HTMLElement>(FOCUSABLE_SELECTOR);\n if (firstFocusable) {\n firstFocusable.focus();\n } else {\n panel.focus();\n }\n }\n }\n }, 0);\n return () => {\n clearTimeout(timer);\n };\n }, [open, mounted, initialFocusRef, panelRef]);\n\n return { mounted, closing };\n}\n"],"names":[],"mappings":";;;;;AAqBA;;;;;;;AAOG;AACG;;;AAOJ;AACA;;;;AAKI;;;;;;;AAOA;;AACK;;;;;AAKL;;AAEA;AAEE;AACA;;AAEF;;;;AAKE;AACE;AACA;;;AAGJ;;AAEA;;AAEF;AACF;;;AAIE;;AAEA;AACE;AACE;;;AAEA;;;;;;;;;;;;AAYJ;;AAEA;;AAGF;AACF;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Drawer.js","sources":["
|
|
1
|
+
{"version":3,"file":"Drawer.js","sources":["src/components/feedback/Drawer/Drawer.tsx"],"sourcesContent":["'use client';\n\nimport React, {\n createContext,\n useCallback,\n useContext,\n useId,\n useRef,\n} from 'react';\nimport { createPortal } from 'react-dom';\n\nimport { CloseIcon } from '@/components/Icons/CloseIcon';\nimport { useFocusTrap } from '@/hooks/useFocusTrap';\nimport { useMergedRef } from '@/hooks/useMergedRef';\nimport { cx } from '@/utils/cx';\n\nimport {\n DRAWER_SIZE_MAP,\n drawerBodyStyle,\n drawerCloseButtonStyle,\n drawerDescriptionStyle,\n drawerFooterRecipe,\n drawerHeaderContentStyle,\n drawerHeaderStyle,\n drawerPanelRecipe,\n drawerTitleStyle,\n overlayRecipe,\n} from './Drawer.css';\nimport { useDrawerAnimation } from './useDrawerAnimation';\n\nimport type {\n DrawerBodyProps,\n DrawerCloseButtonProps,\n DrawerContextValue,\n DrawerFooterProps,\n DrawerHeaderProps,\n DrawerProps,\n DrawerSize,\n} from './Drawer.types';\n\nconst DrawerContext = /*#__PURE__*/ createContext<DrawerContextValue | null>(\n null\n);\n\nfunction useDrawerContext(): DrawerContextValue {\n const ctx = useContext(DrawerContext);\n if (!ctx) {\n throw new Error('Drawer sub-components must be used within <Drawer>');\n }\n return ctx;\n}\n\nfunction resolveSize(\n size: DrawerSize,\n anchor: 'left' | 'right' | 'top' | 'bottom'\n) {\n const isHorizontal = anchor === 'left' || anchor === 'right';\n const dimension = isHorizontal ? 'width' : 'height';\n let value: string;\n if (typeof size === 'number') {\n value = `${String(size)}px`;\n } else {\n value = DRAWER_SIZE_MAP[size] ?? size;\n }\n return {\n [dimension]: value,\n maxWidth: '100vw',\n maxHeight: '100vh',\n } as React.CSSProperties;\n}\n\n/**\n * Anchored sliding panel for secondary navigation, filters, or detail views.\n * Modal mode renders an overlay backdrop and traps focus; non-modal mode\n * leaves the rest of the page interactive.\n *\n * @example\n * ```tsx\n * <Drawer open={isOpen} onClose={close} anchor=\"right\" size=\"md\">\n * <Drawer.Header>Filters</Drawer.Header>\n * <Drawer.Body>...</Drawer.Body>\n * <Drawer.Footer>\n * <Button onClick={close}>Cancel</Button>\n * <Button variant=\"filled\">Apply</Button>\n * </Drawer.Footer>\n * </Drawer>\n * ```\n */\nconst DrawerRoot: React.FC<DrawerProps> = ({\n open,\n onClose,\n anchor = 'right',\n size = 'md',\n modal = true,\n closeOnOverlayClick = true,\n closeOnEscape = true,\n trapFocus,\n initialFocusRef,\n portal = true,\n title,\n description,\n className,\n style,\n testId,\n id,\n children,\n ref,\n ...rest\n}) => {\n const autoId = useId();\n const titleId = `drawer-title-${autoId}`;\n const descriptionId = `drawer-desc-${autoId}`;\n const panelRef = useRef<HTMLDivElement>(null);\n\n const shouldTrapFocus = trapFocus ?? modal;\n\n const { mounted, closing } = useDrawerAnimation({\n open,\n panelRef,\n initialFocusRef,\n shouldFocus: shouldTrapFocus,\n });\n\n const handleFocusTrap = useFocusTrap({\n containerRef: panelRef,\n enabled: shouldTrapFocus,\n });\n\n const setPanelRef = useMergedRef<HTMLDivElement>(panelRef, ref);\n\n const handleKeyDown = useCallback(\n (e: React.KeyboardEvent) => {\n if (closeOnEscape && e.key === 'Escape') {\n e.stopPropagation();\n onClose();\n return;\n }\n handleFocusTrap(e);\n },\n [closeOnEscape, onClose, handleFocusTrap]\n );\n\n const handleOverlayClick = useCallback(\n (e: React.MouseEvent) => {\n if (closeOnOverlayClick && e.target === e.currentTarget) {\n onClose();\n }\n },\n [closeOnOverlayClick, onClose]\n );\n\n if (!mounted) return null;\n\n const contextValue: DrawerContextValue = {\n onClose,\n titleId,\n descriptionId,\n };\n\n const sizeStyle = resolveSize(size, anchor);\n\n const drawerContent = (\n <DrawerContext.Provider value={contextValue}>\n {modal && (\n // Decorative backdrop: pointer users can click to dismiss, while\n // keyboard users dismiss via Escape handled on the drawer panel.\n <div\n aria-hidden=\"true\"\n className={overlayRecipe({ closing })}\n onClick={handleOverlayClick}\n data-testid={testId ? `${testId}-overlay` : undefined}\n />\n )}\n {/* The drawer panel needs a keydown handler for Escape-to-close and\n focus trapping; this is required modal-dialog behavior, not a static\n element repurposed as interactive. */}\n {/* eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions */}\n <div\n ref={setPanelRef}\n role=\"dialog\"\n aria-modal={modal ? 'true' : undefined}\n aria-labelledby={title ? titleId : undefined}\n aria-describedby={description ? descriptionId : undefined}\n tabIndex={-1}\n className={cx(\n drawerPanelRecipe({ anchor, state: closing ? 'closing' : 'open' }),\n className\n )}\n style={{ ...sizeStyle, ...style }}\n id={id}\n data-testid={testId}\n data-anchor={anchor}\n onKeyDown={handleKeyDown}\n {...rest}\n >\n {title && (\n <span id={titleId} style={{ display: 'none' }}>\n {title}\n </span>\n )}\n {description && (\n <span id={descriptionId} style={{ display: 'none' }}>\n {description}\n </span>\n )}\n {children}\n </div>\n </DrawerContext.Provider>\n );\n\n if (portal && typeof document !== 'undefined') {\n return createPortal(drawerContent, document.body);\n }\n return drawerContent;\n};\n\nDrawerRoot.displayName = 'Drawer';\n\nconst DrawerHeader: React.FC<DrawerHeaderProps> = ({\n children,\n showClose = true,\n description,\n className,\n style,\n testId,\n ref,\n ...rest\n}) => {\n const { onClose, titleId, descriptionId } = useDrawerContext();\n return (\n <div\n ref={ref}\n className={cx(drawerHeaderStyle, className)}\n style={style}\n data-testid={testId}\n {...rest}\n >\n <div className={drawerHeaderContentStyle}>\n <div id={titleId} className={drawerTitleStyle}>\n {children}\n </div>\n {description && (\n <div id={descriptionId} className={drawerDescriptionStyle}>\n {description}\n </div>\n )}\n </div>\n {showClose && (\n <button\n type=\"button\"\n onClick={onClose}\n aria-label=\"Close drawer\"\n className={drawerCloseButtonStyle}\n data-testid={testId ? `${testId}-close` : undefined}\n >\n <CloseIcon size=\"sm\" decorative />\n </button>\n )}\n </div>\n );\n};\n\nDrawerHeader.displayName = 'Drawer.Header';\n\nconst DrawerBody: React.FC<DrawerBodyProps> = ({\n children,\n className,\n style,\n testId,\n ref,\n ...rest\n}) => (\n <div\n ref={ref}\n className={cx(drawerBodyStyle, className)}\n style={style}\n data-testid={testId}\n {...rest}\n >\n {children}\n </div>\n);\n\nDrawerBody.displayName = 'Drawer.Body';\n\nconst DrawerFooter: React.FC<DrawerFooterProps> = ({\n children,\n align = 'right',\n className,\n style,\n testId,\n ref,\n ...rest\n}) => (\n <div\n ref={ref}\n className={cx(drawerFooterRecipe({ align }), className)}\n style={style}\n data-testid={testId}\n {...rest}\n >\n {children}\n </div>\n);\n\nDrawerFooter.displayName = 'Drawer.Footer';\n\nconst DrawerCloseButton: React.FC<DrawerCloseButtonProps> = ({\n children,\n className,\n style,\n testId,\n 'aria-label': ariaLabel = 'Close drawer',\n ref,\n ...rest\n}) => {\n const { onClose } = useDrawerContext();\n return (\n <button\n ref={ref}\n type=\"button\"\n onClick={onClose}\n aria-label={ariaLabel}\n className={cx(drawerCloseButtonStyle, className)}\n style={style}\n data-testid={testId}\n {...rest}\n >\n {children ?? <CloseIcon size=\"sm\" decorative />}\n </button>\n );\n};\n\nDrawerCloseButton.displayName = 'Drawer.CloseButton';\n\ninterface DrawerComponent extends React.FC<DrawerProps> {\n Header: React.FC<DrawerHeaderProps>;\n Body: React.FC<DrawerBodyProps>;\n Footer: React.FC<DrawerFooterProps>;\n CloseButton: React.FC<DrawerCloseButtonProps>;\n}\n\nexport const Drawer = Object.assign(DrawerRoot, {\n Header: DrawerHeader,\n Body: DrawerBody,\n Footer: DrawerFooter,\n CloseButton: DrawerCloseButton,\n}) as DrawerComponent;\n"],"names":[],"mappings":";;;;;;;;;;;AAwCA;AAIA;AACE;;AAEE;;AAEF;AACF;AAEA;;;AAME;AACA;AACE;;;AAEA;;;;AAIA;AACA;;AAEJ;AAEA;;;;;;;;;;;;;;;;AAgBG;AACH;AAqBE;AACA;AACA;AACA;AAEA;AAEA;;;;AAIE;AACD;;AAGC;AACA;AACD;;AAID;;;AAIM;;;;;AAQN;;AAGM;;AAEJ;AAIF;AAAc;AAEd;;;;;;AAQA;;;AAKM;AA4CN;;;AAGA;AACF;AAEA;AAEA;;;AA0CA;AAEA;AAEA;AAmBA;AAEA;AAoBA;AAEA;AASE;;AAeF;AAEA;;AAUE;AACA;AACA;AACA;AACD;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useDrawerAnimation.js","sources":["
|
|
1
|
+
{"version":3,"file":"useDrawerAnimation.js","sources":["src/components/feedback/Drawer/useDrawerAnimation.ts"],"sourcesContent":["'use client';\n\nimport { useEffect, useRef, useState } from 'react';\n\nimport { FOCUSABLE_SELECTOR } from '@/hooks/useFocusTrap';\n\nimport { DRAWER_ANIMATION_MS } from './Drawer.css';\n\nimport type { RefObject } from 'react';\n\ninterface UseDrawerAnimationOptions {\n open: boolean;\n panelRef: RefObject<HTMLElement | null>;\n initialFocusRef?: RefObject<HTMLElement | null>;\n shouldFocus: boolean;\n}\n\ninterface UseDrawerAnimationReturn {\n mounted: boolean;\n closing: boolean;\n}\n\n/**\n * Mount/unmount animation lifecycle plus focus management for Drawer.\n * Mirrors the Dialog hook so behavior stays consistent: mount on open,\n * delay unmount on close to play the slide-out, return focus to the\n * previously focused element. `prefers-reduced-motion: reduce` collapses\n * the animation delay to zero.\n */\nexport function useDrawerAnimation({\n open,\n panelRef,\n initialFocusRef,\n shouldFocus,\n}: UseDrawerAnimationOptions): UseDrawerAnimationReturn {\n const [mounted, setMounted] = useState(false);\n const [closing, setClosing] = useState(false);\n const previousFocusRef = useRef<HTMLElement | null>(null);\n const wasOpenRef = useRef(false);\n\n useEffect(() => {\n if (open) {\n previousFocusRef.current = document.activeElement as HTMLElement;\n // Mount/closing state is driven by the `open` prop and gates a timed\n // enter/exit animation (delayed unmount below), so it must live in an\n // effect rather than be derived during render.\n // eslint-disable-next-line react-hooks/set-state-in-effect\n setClosing(false);\n setMounted(true);\n wasOpenRef.current = true;\n return undefined;\n }\n if (wasOpenRef.current) {\n wasOpenRef.current = false;\n setClosing(true);\n const prefersReducedMotion =\n typeof window !== 'undefined' &&\n typeof window.matchMedia === 'function' &&\n window.matchMedia('(prefers-reduced-motion: reduce)').matches;\n const delay = prefersReducedMotion ? 0 : DRAWER_ANIMATION_MS;\n const timer = setTimeout(() => {\n setMounted(false);\n setClosing(false);\n if (previousFocusRef.current) {\n previousFocusRef.current.focus();\n previousFocusRef.current = null;\n }\n }, delay);\n return () => {\n clearTimeout(timer);\n };\n }\n return undefined;\n }, [open]);\n\n useEffect(() => {\n if (!open || !mounted || !shouldFocus) return undefined;\n const timer = setTimeout(() => {\n if (initialFocusRef?.current) {\n initialFocusRef.current.focus();\n return;\n }\n const panel = panelRef.current;\n if (!panel) return;\n const firstFocusable =\n panel.querySelector<HTMLElement>(FOCUSABLE_SELECTOR);\n if (firstFocusable) {\n firstFocusable.focus();\n } else {\n panel.focus();\n }\n }, 0);\n return () => {\n clearTimeout(timer);\n };\n }, [open, mounted, initialFocusRef, panelRef, shouldFocus]);\n\n return { mounted, closing };\n}\n"],"names":[],"mappings":";;;;;AAsBA;;;;;;AAMG;AACG;;;AAQJ;AACA;;;AAII;;;;;;;AAOA;AACA;;AAEF;AACE;;AAEA;AAEE;AACA;;AAEF;;;AAGE;AACE;AACA;;;AAGJ;;AAEA;;AAEF;AACF;;AAGE;AAAuC;AACvC;AACE;AACE;;;AAGF;AACA;;;;;;;;;;AASF;;AAEA;AACF;AAEA;AACF;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EmptyState.js","sources":["
|
|
1
|
+
{"version":3,"file":"EmptyState.js","sources":["src/components/feedback/EmptyState/EmptyState.tsx"],"sourcesContent":["'use client';\n\nimport React from 'react';\n\nimport { Spinner } from '@/components/feedback/Spinner';\nimport { cx } from '@/utils/cx';\n\nimport {\n emptyStateActionStyle,\n emptyStateDescriptionStyle,\n emptyStateIconStyle,\n emptyStateRecipe,\n emptyStateTextColumnCompactStyle,\n emptyStateTextColumnStyle,\n emptyStateTitleStyle,\n} from './EmptyState.css';\n\nimport type { EmptyStateProps } from './EmptyState.types';\n\n/**\n * Generic empty / loading state surface.\n *\n * Use for empty lists, filtered-out results, or \"not yet started\"\n * placeholders. When `loading` is `true`, a `Spinner` is rendered in\n * place of the icon and the root uses `role=\"status\"`.\n *\n * @example\n * ```tsx\n * <EmptyState\n * icon={<SearchIcon size=\"lg\" />}\n * title=\"No results\"\n * description=\"Try a different search term.\"\n * action={<Button>Reset filters</Button>}\n * />\n *\n * <EmptyState loading title=\"Loading conversation...\" />\n * ```\n */\nexport const EmptyState = /*#__PURE__*/ React.memo<EmptyStateProps>(\n ({\n icon,\n title,\n description,\n action,\n variant = 'default',\n loading = false,\n className,\n style,\n testId,\n ref,\n ...rest\n }) => {\n const textColumnClass =\n variant === 'compact'\n ? emptyStateTextColumnCompactStyle\n : emptyStateTextColumnStyle;\n\n const statusAttrs = loading\n ? {\n role: 'status' as const,\n 'aria-live': 'polite' as const,\n }\n : {};\n\n return (\n <div\n ref={ref}\n className={cx(emptyStateRecipe({ variant }), className)}\n style={style}\n data-testid={testId}\n {...statusAttrs}\n {...rest}\n >\n {loading ? (\n <span className={emptyStateIconStyle}>\n <Spinner size=\"lg\" />\n </span>\n ) : (\n icon && <span className={emptyStateIconStyle}>{icon}</span>\n )}\n\n {(title !== undefined && title !== null) ||\n (description !== undefined && description !== null) ? (\n <div className={textColumnClass}>\n {title !== undefined && title !== null && (\n <h3 className={emptyStateTitleStyle}>{title}</h3>\n )}\n {description !== undefined && description !== null && (\n <p className={emptyStateDescriptionStyle}>{description}</p>\n )}\n </div>\n ) : null}\n\n {action && <div className={emptyStateActionStyle}>{action}</div>}\n </div>\n );\n }\n);\n\nEmptyState.displayName = 'EmptyState';\n"],"names":[],"mappings":";;;;;;;AAmBA;;;;;;;;;;;;;;;;;;AAkBG;AACI;AAcH;AAEI;;;AAIF;AACI;AACA;AACD;;AAGL;AAkBI;AAcN;AAGF;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LogView.js","sources":["../../../../../../src/components/feedback/LogView/LogView.tsx"],"sourcesContent":[null],"names":[],"mappings":";;;;;;;;;;;;;;;;;AA6CA;AAqDE;AACA;AACA;;AAGA;;;;;;;;;AAYE;AAGF;AACA;;;;AAI6B;AAC7B;;AAGE;AACF;;AAGE;AACF;AAEA;;AAGE;;;AAUF;;;;;;AAUA;AACA;;;AAMA;AACE;AAAwB;;;;;AAU1B;AACA;AACA;AACA;AAEA;;AAGI;;AAEI;AACA;AAAc;;AACT;AACL;AACF;;;AAGF;AAGA;;;;AAYJ;AACE;AACA;AACA;AACA;AACD;AACD;AAEA;AACE;AACA;AACA;AACA;AACD;;AAGD;AACE;AACA;AACA;AAGA;AACD;AACD;AACA;AAEI;AACF;;AAKF;AACA;;;;AAGE;;;;AAKF;;;;;AAOK;AAIL;AACA;AACE;AACA;;;;;;AAOF;AAGI;AACE;AACE;AAEI;;;AAIN;;AAEF;AACE;;;;AAMA;;AAEF;;;;AAIA;AACE;;AAEF;AACA;;;AAMJ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmCG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmCA;AAGH;AACE;;AAEE;AACI;AAED;;AAEL;;AAGF;;AAkCF;AAEA;AAcA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsCG;AACI;AAEP;AACA;AACA;AACA;AACA;AACA;AACA;;"}
|
|
1
|
+
{"version":3,"file":"LogView.js","sources":["src/components/feedback/LogView/LogView.tsx"],"sourcesContent":["'use client';\n\nimport React, {\n useCallback,\n useEffect,\n useId,\n useImperativeHandle,\n useLayoutEffect,\n useMemo,\n useRef,\n useState,\n} from 'react';\n\nimport { useControlledState } from '@/hooks/useControlledState';\nimport { useLatest } from '@/hooks/useLatest';\nimport { useMergedRef } from '@/hooks/useMergedRef';\nimport { cx } from '@/utils/cx';\nimport { devWarn } from '@/utils/devWarn';\n\nimport { rootRecipe, toolbarSpacerStyle } from './LogView.css';\nimport { LogViewBody } from './LogViewBody';\nimport { LogViewContext } from './LogViewContext';\nimport { resolveLogViewLabels } from './logViewLabels';\nimport { resolveLevelDefinition, resolveLevelOrder } from './logViewLevels';\nimport {\n LogViewClear,\n LogViewCopy,\n LogViewFooter,\n LogViewLevelFilter,\n LogViewSearch,\n LogViewToolbar,\n} from './LogViewParts';\nimport { LogViewStore } from './LogViewStore';\nimport { defaultFormatTimestamp, filterEntries } from './logViewUtils';\n\nimport type {\n LogEntry,\n LogLevel,\n LogViewHandle,\n LogViewProps,\n ResolvedLogEntry,\n} from './LogView.types';\nimport type { LogViewContextValue } from './LogViewContext';\nimport type { ResolvedLevelDefinition } from './logViewLevels';\n\nconst LogViewRoot = ({\n entries,\n defaultEntries,\n maxEntries,\n getEntryId,\n query: queryProp,\n defaultQuery,\n onQueryChange,\n caseSensitive = false,\n levels: levelsProp,\n defaultLevels,\n onLevelsChange,\n levelConfig,\n levelOrder: levelOrderProp,\n follow: followProp,\n defaultFollow = true,\n onFollowChange,\n wrap = false,\n density = 'compact',\n showTimestamps = false,\n formatTimestamp = defaultFormatTimestamp,\n showSource = true,\n showToolbar = true,\n showSearch = true,\n showLevelFilter = true,\n showClear = true,\n showCopy = true,\n virtualized = 'auto',\n virtualizationThreshold = 100,\n estimatedRowHeight,\n overscan = 12,\n selectionMode = false,\n selectedIds: selectedIdsProp,\n defaultSelectedIds,\n onSelectionChange,\n height = 320,\n maxHeight,\n renderEntry,\n emptyState,\n footer,\n onClear,\n onEntryClick,\n onCopy,\n labels: labelsProp,\n slotProps,\n children,\n className,\n style,\n testId,\n ref,\n 'aria-label': ariaLabel,\n ...rest\n}: LogViewProps): React.ReactElement => {\n const rootRef = useRef<HTMLDivElement>(null);\n const setRootRef = useMergedRef<HTMLDivElement>(rootRef);\n const bodyId = useId();\n\n // Resolved i18n strings: English defaults merged with the `labels` override.\n const labels = useMemo(() => resolveLogViewLabels(labelsProp), [labelsProp]);\n\n // One store per LogView instance. Created once; later prop changes flow\n // through the effects below (the same per-instance store pattern as Viewport).\n const store = useMemo(\n () =>\n new LogViewStore({\n initial: entries ?? defaultEntries,\n maxEntries,\n getEntryId,\n }),\n // eslint-disable-next-line react-hooks/exhaustive-deps -- store is created once; prop updates are mirrored via effects.\n []\n );\n\n const isControlled = entries !== undefined;\n const isControlledRef = useLatest(isControlled);\n\n // Mirror the controlled entries prop into the store.\n useLayoutEffect(() => {\n if (entries !== undefined) store.setEntries(entries);\n }, [entries, store]);\n\n useEffect(() => {\n store.setMaxEntries(maxEntries);\n }, [maxEntries, store]);\n\n useEffect(() => {\n store.setGetEntryId(getEntryId);\n }, [getEntryId, store]);\n\n useEffect(\n () => () => {\n store.dispose();\n },\n [store]\n );\n\n // ── Levels ──\n const levelOrder = useMemo(\n () => resolveLevelOrder(levelConfig, levelOrderProp),\n [levelConfig, levelOrderProp]\n );\n\n const knownLevels = useMemo(\n () => new Set<LogLevel>(levelOrder),\n [levelOrder]\n );\n\n // Visibility is tracked as the set of *hidden* levels (uncontrolled) so the\n // active set can be derived as \"known minus hidden\". A level introduced later\n // via `levelConfig` is therefore visible by default, while an explicit\n // toggle-off survives changes to `levelOrder`. In controlled mode the\n // `levels` prop is the source of truth and this state is unused.\n const isLevelsControlled = levelsProp !== undefined;\n const [hiddenLevels, setHiddenLevels] = useState<ReadonlySet<LogLevel>>(() =>\n defaultLevels === undefined\n ? new Set<LogLevel>()\n : new Set(levelOrder.filter(level => !defaultLevels.includes(level)))\n );\n\n const activeLevels = useMemo<ReadonlySet<LogLevel>>(() => {\n if (isLevelsControlled) return new Set(levelsProp);\n return new Set(levelOrder.filter(level => !hiddenLevels.has(level)));\n }, [isLevelsControlled, levelsProp, levelOrder, hiddenLevels]);\n\n const isLevelActive = useCallback(\n (level: LogLevel) => activeLevels.has(level),\n [activeLevels]\n );\n\n // Read live values through refs so the toggle handler stays identity-stable.\n const onLevelsChangeRef = useLatest(onLevelsChange);\n const activeLevelsRef = useLatest(activeLevels);\n const levelOrderRef = useLatest(levelOrder);\n const isLevelsControlledRef = useLatest(isLevelsControlled);\n\n const toggleLevel = useCallback(\n (level: LogLevel) => {\n const willHide = activeLevelsRef.current.has(level);\n if (!isLevelsControlledRef.current) {\n setHiddenLevels(prev => {\n const next = new Set(prev);\n if (willHide) next.add(level);\n else next.delete(level);\n return next;\n });\n }\n // Emit the next visible set, ordered by `levelOrder`, in both modes.\n const visible = levelOrderRef.current.filter(other =>\n other === level ? !willHide : activeLevelsRef.current.has(other)\n );\n onLevelsChangeRef.current?.(visible);\n },\n [activeLevelsRef, isLevelsControlledRef, levelOrderRef, onLevelsChangeRef]\n );\n\n const getLevelDefinition = useCallback(\n (level: LogLevel): ResolvedLevelDefinition =>\n resolveLevelDefinition(level, levelConfig),\n [levelConfig]\n );\n\n // ── Filter / follow state ──\n const [query, setQuery] = useControlledState<string>({\n value: queryProp,\n defaultValue: defaultQuery,\n onChange: onQueryChange,\n fallback: '',\n });\n const queryRef = useLatest(query);\n\n const [follow, setFollow] = useControlledState<boolean>({\n value: followProp,\n defaultValue: defaultFollow,\n onChange: onFollowChange,\n fallback: true,\n });\n\n // ── Selection ──\n const [selection, setSelection] = useControlledState<readonly string[]>({\n value: selectedIdsProp,\n defaultValue: defaultSelectedIds,\n onChange: onSelectionChange as\n | ((value: readonly string[]) => void)\n | undefined,\n fallback: [],\n });\n const selectedIds = useMemo(() => new Set(selection), [selection]);\n const setSelectedIds = useCallback(\n (ids: ReadonlySet<string>) => {\n setSelection([...ids]);\n },\n [setSelection]\n );\n\n // ── Actions ──\n const onClearRef = useLatest(onClear);\n const clearLog = useCallback(() => {\n if (!isControlledRef.current) store.clear();\n setSelection([]);\n onClearRef.current?.();\n }, [store, isControlledRef, onClearRef, setSelection]);\n\n // Uses the live query (via ref), not the body's deferred query: this powers\n // Copy, an explicit user action, so it should reflect exactly what is typed.\n const getVisibleEntries = useCallback(\n (): ResolvedLogEntry[] =>\n filterEntries(store.getEntries(), {\n query: queryRef.current,\n caseSensitive,\n knownLevels,\n activeLevels,\n }),\n [store, queryRef, caseSensitive, knownLevels, activeLevels]\n );\n\n const selectedIdsRef = useLatest(selectedIds);\n const getCopyEntries = useCallback((): ResolvedLogEntry[] => {\n const ids = selectedIdsRef.current;\n if (ids.size > 0) {\n return store.getEntries().filter(entry => ids.has(entry.id));\n }\n return getVisibleEntries();\n }, [store, selectedIdsRef, getVisibleEntries]);\n\n // ── Imperative handle ──\n useImperativeHandle(\n ref,\n (): LogViewHandle => ({\n append: (entry: LogEntry) => {\n if (isControlledRef.current) {\n devWarn(\n '[LogView] handle.append() is a no-op while `entries` is controlled. ' +\n 'Append to your `entries` state instead, or drop the prop to go uncontrolled.'\n );\n return;\n }\n store.append(entry);\n },\n appendMany: (next: readonly LogEntry[]) => {\n if (isControlledRef.current) {\n devWarn(\n '[LogView] handle.appendMany() is a no-op while `entries` is controlled.'\n );\n return;\n }\n store.appendMany(next);\n },\n clear: clearLog,\n scrollToBottom: () => {\n store.scrollToBottom();\n },\n scrollToIndex: (index: number) => {\n store.scrollToIndex(index);\n },\n getEntries: () => store.getEntries(),\n getElement: () => rootRef.current,\n }),\n [store, isControlledRef, clearLog]\n );\n\n // ── Context ──\n const contextValue = useMemo<LogViewContextValue>(\n () => ({\n store,\n query,\n setQuery,\n caseSensitive,\n activeLevels,\n knownLevels,\n isLevelActive,\n toggleLevel,\n follow,\n setFollow,\n levelOrder,\n getLevelDefinition,\n wrap,\n density,\n showTimestamps,\n formatTimestamp,\n showSource,\n virtualized,\n virtualizationThreshold,\n estimatedRowHeight,\n overscan,\n selectionMode,\n selectedIds,\n setSelectedIds,\n renderEntry,\n emptyState,\n labels,\n onEntryClick,\n onCopy,\n clearLog,\n bodyId,\n getVisibleEntries,\n getCopyEntries,\n }),\n [\n store,\n query,\n setQuery,\n caseSensitive,\n activeLevels,\n knownLevels,\n isLevelActive,\n toggleLevel,\n follow,\n setFollow,\n levelOrder,\n getLevelDefinition,\n wrap,\n density,\n showTimestamps,\n formatTimestamp,\n showSource,\n virtualized,\n virtualizationThreshold,\n estimatedRowHeight,\n overscan,\n selectionMode,\n selectedIds,\n setSelectedIds,\n renderEntry,\n emptyState,\n labels,\n onEntryClick,\n onCopy,\n clearLog,\n bodyId,\n getVisibleEntries,\n getCopyEntries,\n ]\n );\n\n const rootStyle: React.CSSProperties = {\n height: typeof height === 'number' ? `${height}px` : height,\n ...(maxHeight !== undefined\n ? {\n maxHeight:\n typeof maxHeight === 'number' ? `${maxHeight}px` : maxHeight,\n }\n : {}),\n ...style,\n };\n\n const defaultComposition = (\n <>\n {showToolbar && (\n <LogViewToolbar {...slotProps?.toolbar}>\n {showSearch && <LogViewSearch {...slotProps?.search} />}\n {showLevelFilter && (\n <LogViewLevelFilter {...slotProps?.levelFilter} />\n )}\n <div className={toolbarSpacerStyle} />\n {showCopy && <LogViewCopy {...slotProps?.copy} />}\n {showClear && <LogViewClear {...slotProps?.clear} />}\n </LogViewToolbar>\n )}\n <LogViewBody {...slotProps?.body} />\n {footer != null && (\n <LogViewFooter {...slotProps?.footer}>{footer}</LogViewFooter>\n )}\n </>\n );\n\n return (\n <LogViewContext.Provider value={contextValue}>\n <div\n ref={setRootRef}\n className={cx(rootRecipe({ density }), className)}\n style={rootStyle}\n data-testid={testId}\n aria-label={ariaLabel ?? labels.regionLabel}\n {...rest}\n >\n {children ?? defaultComposition}\n </div>\n </LogViewContext.Provider>\n );\n};\n\nLogViewRoot.displayName = 'LogView';\n\ninterface LogViewComponent {\n (props: LogViewProps): React.ReactElement;\n displayName?: string;\n Toolbar: typeof LogViewToolbar;\n Footer: typeof LogViewFooter;\n Search: typeof LogViewSearch;\n LevelFilter: typeof LogViewLevelFilter;\n Clear: typeof LogViewClear;\n Copy: typeof LogViewCopy;\n Body: typeof LogViewBody;\n}\n\n/**\n * A virtualized console / log output panel.\n *\n * Renders a large, append-only stream of {@link LogEntry} efficiently\n * (virtualized via `@tanstack/react-virtual`), with level coloring, level\n * filtering, text search with match highlighting, follow-tail auto-scroll, per\n * line and bulk copy, timestamps, and source tags.\n *\n * **Data flow.** Pass a controlled `entries` array, or go uncontrolled and push\n * via the imperative handle — `ref.append(entry)` / `appendMany` / `clear` are\n * rAF-batched, so a high-frequency stream collapses to one render per frame.\n *\n * **Composition.** Use it batteries-included (default toolbar + body), or\n * compose the slots yourself: `LogView.Toolbar`, `LogView.Search`,\n * `LogView.LevelFilter`, `LogView.Copy`, `LogView.Clear`, `LogView.Body`.\n *\n * @example Uncontrolled streaming\n * ```tsx\n * const ref = useRef<LogViewHandle>(null);\n * useEffect(() => {\n * const id = setInterval(() => {\n * ref.current?.append({ level: 'info', message: 'tick', timestamp: Date.now() });\n * }, 50);\n * return () => clearInterval(id);\n * }, []);\n * return <LogView ref={ref} showTimestamps height={360} />;\n * ```\n *\n * @example Custom composition\n * ```tsx\n * <LogView entries={entries} wrap>\n * <LogView.Toolbar>\n * <LogView.LevelFilter />\n * <LogView.Search />\n * </LogView.Toolbar>\n * <LogView.Body />\n * </LogView>\n * ```\n */\nexport const LogView = LogViewRoot as LogViewComponent;\n\nLogView.Toolbar = LogViewToolbar;\nLogView.Footer = LogViewFooter;\nLogView.Search = LogViewSearch;\nLogView.LevelFilter = LogViewLevelFilter;\nLogView.Clear = LogViewClear;\nLogView.Copy = LogViewCopy;\nLogView.Body = LogViewBody;\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AA6CA;AAqDE;AACA;AACA;;AAGA;;;;;;;;;AAYE;AAGF;AACA;;;;AAI6B;AAC7B;;AAGE;AACF;;AAGE;AACF;AAEA;;AAGE;;;AAUF;;;;;;AAUA;AACA;;;AAMA;AACE;AAAwB;;;;;AAU1B;AACA;AACA;AACA;AAEA;;AAGI;;AAEI;AACA;AAAc;;AACT;AACL;AACF;;;AAGF;AAGA;;;;AAYJ;AACE;AACA;AACA;AACA;AACD;AACD;AAEA;AACE;AACA;AACA;AACA;AACD;;AAGD;AACE;AACA;AACA;AAGA;AACD;AACD;AACA;AAEI;AACF;;AAKF;AACA;;;;AAGE;;;;AAKF;;;;;AAOK;AAIL;AACA;AACE;AACA;;;;;;AAOF;AAGI;AACE;AACE;AAEI;;;AAIN;;AAEF;AACE;;;;AAMA;;AAEF;;;;AAIA;AACE;;AAEF;AACA;;;AAMJ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmCG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmCA;AAGH;AACE;;AAEE;AACI;AAED;;AAEL;;AAGF;;AAkCF;AAEA;AAcA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsCG;AACI;AAEP;AACA;AACA;AACA;AACA;AACA;AACA;;"}
|