entangle-ui 0.10.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (563) hide show
  1. package/CHANGELOG.md +72 -0
  2. package/README.md +7 -5
  3. package/dist/esm/assets/src/components/controls/Select/{Select.css.ts.vanilla-oZnFUorL.css → Select.css.ts.vanilla-Cpu2BvWL.css} +3 -0
  4. package/dist/esm/assets/src/components/editor/PropertyInspector/{PropertyPanel.css.ts.vanilla-RYv_XgOu.css → PropertyPanel.css.ts.vanilla-Dpr_k5I3.css} +4 -1
  5. package/dist/esm/assets/src/components/editor/PropertyInspector/{PropertyRow.css.ts.vanilla-C3YZnnHb.css → PropertyRow.css.ts.vanilla-pq1Uhd2a.css} +15 -8
  6. package/dist/esm/assets/src/components/editor/PropertyInspector/{PropertySection.css.ts.vanilla-DK12opZc.css → PropertySection.css.ts.vanilla-6MWWAqyd.css} +50 -32
  7. package/dist/esm/components/Icons/AddIcon.js.map +1 -1
  8. package/dist/esm/components/Icons/AiChatIcon.js.map +1 -1
  9. package/dist/esm/components/Icons/AiSparklesIcon.js.map +1 -1
  10. package/dist/esm/components/Icons/ArchiveIcon.js.map +1 -1
  11. package/dist/esm/components/Icons/ArrowDownIcon.js.map +1 -1
  12. package/dist/esm/components/Icons/ArrowLeftIcon.js.map +1 -1
  13. package/dist/esm/components/Icons/ArrowRightIcon.js.map +1 -1
  14. package/dist/esm/components/Icons/ArrowUpIcon.js.map +1 -1
  15. package/dist/esm/components/Icons/BookmarkIcon.js.map +1 -1
  16. package/dist/esm/components/Icons/BugIcon.js.map +1 -1
  17. package/dist/esm/components/Icons/BuildingIcon.js.map +1 -1
  18. package/dist/esm/components/Icons/CalendarIcon.js.map +1 -1
  19. package/dist/esm/components/Icons/CameraIcon.js +31 -0
  20. package/dist/esm/components/Icons/CameraIcon.js.map +1 -0
  21. package/dist/esm/components/Icons/CheckIcon.js.map +1 -1
  22. package/dist/esm/components/Icons/ChevronDownIcon.js.map +1 -1
  23. package/dist/esm/components/Icons/ChevronLeftIcon.js.map +1 -1
  24. package/dist/esm/components/Icons/ChevronRightIcon.js.map +1 -1
  25. package/dist/esm/components/Icons/ChevronUpIcon.js.map +1 -1
  26. package/dist/esm/components/Icons/CircleIcon.js.map +1 -1
  27. package/dist/esm/components/Icons/ClockIcon.js.map +1 -1
  28. package/dist/esm/components/Icons/CloseIcon.js.map +1 -1
  29. package/dist/esm/components/Icons/CloudUploadIcon.js.map +1 -1
  30. package/dist/esm/components/Icons/CodeIcon.js.map +1 -1
  31. package/dist/esm/components/Icons/CopyIcon.js.map +1 -1
  32. package/dist/esm/components/Icons/CubeIcon.js +31 -0
  33. package/dist/esm/components/Icons/CubeIcon.js.map +1 -0
  34. package/dist/esm/components/Icons/CutIcon.js.map +1 -1
  35. package/dist/esm/components/Icons/DotsHorizontalIcon.js.map +1 -1
  36. package/dist/esm/components/Icons/DotsVerticalIcon.js.map +1 -1
  37. package/dist/esm/components/Icons/DownloadIcon.js.map +1 -1
  38. package/dist/esm/components/Icons/EditIcon.js.map +1 -1
  39. package/dist/esm/components/Icons/ErrorIcon.js.map +1 -1
  40. package/dist/esm/components/Icons/ExternalLinkIcon.js.map +1 -1
  41. package/dist/esm/components/Icons/EyeDropperIcon.js.map +1 -1
  42. package/dist/esm/components/Icons/EyeIcon.js.map +1 -1
  43. package/dist/esm/components/Icons/FileTextIcon.js.map +1 -1
  44. package/dist/esm/components/Icons/FilterIcon.js.map +1 -1
  45. package/dist/esm/components/Icons/FirstIcon.js.map +1 -1
  46. package/dist/esm/components/Icons/FolderCogIcon.js.map +1 -1
  47. package/dist/esm/components/Icons/FolderIcon.js.map +1 -1
  48. package/dist/esm/components/Icons/FolderOpenIcon.js.map +1 -1
  49. package/dist/esm/components/Icons/FullscreenIcon.js.map +1 -1
  50. package/dist/esm/components/Icons/GitBranchIcon.js.map +1 -1
  51. package/dist/esm/components/Icons/GridIcon.js.map +1 -1
  52. package/dist/esm/components/Icons/HeartIcon.js.map +1 -1
  53. package/dist/esm/components/Icons/HelpIcon.js.map +1 -1
  54. package/dist/esm/components/Icons/HomeIcon.js.map +1 -1
  55. package/dist/esm/components/Icons/ImageIcon.js.map +1 -1
  56. package/dist/esm/components/Icons/InfoIcon.js.map +1 -1
  57. package/dist/esm/components/Icons/LastIcon.js.map +1 -1
  58. package/dist/esm/components/Icons/LinkIcon.js.map +1 -1
  59. package/dist/esm/components/Icons/ListIcon.js.map +1 -1
  60. package/dist/esm/components/Icons/LockIcon.js.map +1 -1
  61. package/dist/esm/components/Icons/MaximizeIcon.js.map +1 -1
  62. package/dist/esm/components/Icons/MenuIcon.js.map +1 -1
  63. package/dist/esm/components/Icons/MinimizeIcon.js.map +1 -1
  64. package/dist/esm/components/Icons/MinusIcon.js.map +1 -1
  65. package/dist/esm/components/Icons/OrbitIcon.js +31 -0
  66. package/dist/esm/components/Icons/OrbitIcon.js.map +1 -0
  67. package/dist/esm/components/Icons/PasteIcon.js.map +1 -1
  68. package/dist/esm/components/Icons/PauseIcon.js.map +1 -1
  69. package/dist/esm/components/Icons/PinIcon.js.map +1 -1
  70. package/dist/esm/components/Icons/PlayIcon.js.map +1 -1
  71. package/dist/esm/components/Icons/RedoIcon.js.map +1 -1
  72. package/dist/esm/components/Icons/RefreshIcon.js.map +1 -1
  73. package/dist/esm/components/Icons/RobotIcon.js.map +1 -1
  74. package/dist/esm/components/Icons/RotateIcon.js +32 -0
  75. package/dist/esm/components/Icons/RotateIcon.js.map +1 -0
  76. package/dist/esm/components/Icons/SaveIcon.js.map +1 -1
  77. package/dist/esm/components/Icons/SearchIcon.js.map +1 -1
  78. package/dist/esm/components/Icons/SendIcon.js.map +1 -1
  79. package/dist/esm/components/Icons/SettingsIcon.js.map +1 -1
  80. package/dist/esm/components/Icons/SortIcon.js.map +1 -1
  81. package/dist/esm/components/Icons/SpinIcon.js +32 -0
  82. package/dist/esm/components/Icons/SpinIcon.js.map +1 -0
  83. package/dist/esm/components/Icons/StarIcon.js.map +1 -1
  84. package/dist/esm/components/Icons/StopIcon.js.map +1 -1
  85. package/dist/esm/components/Icons/SuccessIcon.js.map +1 -1
  86. package/dist/esm/components/Icons/TagIcon.js.map +1 -1
  87. package/dist/esm/components/Icons/TangentAlignedIcon.js.map +1 -1
  88. package/dist/esm/components/Icons/TangentAutoIcon.js.map +1 -1
  89. package/dist/esm/components/Icons/TangentFreeIcon.js.map +1 -1
  90. package/dist/esm/components/Icons/TangentLinearIcon.js.map +1 -1
  91. package/dist/esm/components/Icons/TangentMirroredIcon.js.map +1 -1
  92. package/dist/esm/components/Icons/TangentStepIcon.js.map +1 -1
  93. package/dist/esm/components/Icons/TerminalIcon.js.map +1 -1
  94. package/dist/esm/components/Icons/TrashIcon.js.map +1 -1
  95. package/dist/esm/components/Icons/UndoIcon.js.map +1 -1
  96. package/dist/esm/components/Icons/UnlinkIcon.js.map +1 -1
  97. package/dist/esm/components/Icons/UnlockIcon.js.map +1 -1
  98. package/dist/esm/components/Icons/UploadIcon.js.map +1 -1
  99. package/dist/esm/components/Icons/UserIcon.js.map +1 -1
  100. package/dist/esm/components/Icons/UsersIcon.js.map +1 -1
  101. package/dist/esm/components/Icons/WarningIcon.js.map +1 -1
  102. package/dist/esm/components/Icons/ZoomFitIcon.js +32 -0
  103. package/dist/esm/components/Icons/ZoomFitIcon.js.map +1 -0
  104. package/dist/esm/components/Icons/ZoomInIcon.js.map +1 -1
  105. package/dist/esm/components/Icons/ZoomOutIcon.js.map +1 -1
  106. package/dist/esm/components/controls/CartesianPicker/CartesianPicker.js.map +1 -1
  107. package/dist/esm/components/controls/CartesianPicker/useCartesianInteraction.js.map +1 -1
  108. package/dist/esm/components/controls/CartesianPicker/useCartesianRenderer.js.map +1 -1
  109. package/dist/esm/components/controls/ColorPicker/AlphaSlider.js.map +1 -1
  110. package/dist/esm/components/controls/ColorPicker/ColorArea.js.map +1 -1
  111. package/dist/esm/components/controls/ColorPicker/ColorInputs.js.map +1 -1
  112. package/dist/esm/components/controls/ColorPicker/ColorPalette.js.map +1 -1
  113. package/dist/esm/components/controls/ColorPicker/ColorPicker.js.map +1 -1
  114. package/dist/esm/components/controls/ColorPicker/ColorPresets.js.map +1 -1
  115. package/dist/esm/components/controls/ColorPicker/ColorSwatch.js.map +1 -1
  116. package/dist/esm/components/controls/ColorPicker/EyeDropper.js.map +1 -1
  117. package/dist/esm/components/controls/ColorPicker/HueSlider.js.map +1 -1
  118. package/dist/esm/components/controls/ColorPicker/colorUtils.js.map +1 -1
  119. package/dist/esm/components/controls/ColorPicker/palettes/material.js.map +1 -1
  120. package/dist/esm/components/controls/ColorPicker/palettes/professional.js.map +1 -1
  121. package/dist/esm/components/controls/ColorPicker/palettes/tailwind.js.map +1 -1
  122. package/dist/esm/components/controls/ColorPicker/useColor.js.map +1 -1
  123. package/dist/esm/components/controls/Combobox/Combobox.js.map +1 -1
  124. package/dist/esm/components/controls/CurveEditor/CurveCanvas.js.map +1 -1
  125. package/dist/esm/components/controls/CurveEditor/CurveEditor.js.map +1 -1
  126. package/dist/esm/components/controls/CurveEditor/CurveToolbar.js.map +1 -1
  127. package/dist/esm/components/controls/CurveEditor/curvePresets.js.map +1 -1
  128. package/dist/esm/components/controls/CurveEditor/curveUtils.js.map +1 -1
  129. package/dist/esm/components/controls/CurveEditor/useCurveInteraction.js.map +1 -1
  130. package/dist/esm/components/controls/CurveEditor/useCurveRenderer.js.map +1 -1
  131. package/dist/esm/components/controls/FileTree/FileTree.js +7 -1
  132. package/dist/esm/components/controls/FileTree/FileTree.js.map +1 -1
  133. package/dist/esm/components/controls/FileTree/fileTreeIcons.js.map +1 -1
  134. package/dist/esm/components/controls/FileTree/fileTreeLabels.js.map +1 -1
  135. package/dist/esm/components/controls/FileTree/useFileTreeDrop.js +1 -1
  136. package/dist/esm/components/controls/FileTree/useFileTreeDrop.js.map +1 -1
  137. package/dist/esm/components/controls/FileUploader/FileUploader.js.map +1 -1
  138. package/dist/esm/components/controls/MultiSelect/MultiSelect.js.map +1 -1
  139. package/dist/esm/components/controls/NumberInput/NumberInput.js.map +1 -1
  140. package/dist/esm/components/controls/NumberInput/useNumberInput.js.map +1 -1
  141. package/dist/esm/components/controls/Select/Select.css.js +1 -1
  142. package/dist/esm/components/controls/Select/Select.js +15 -7
  143. package/dist/esm/components/controls/Select/Select.js.map +1 -1
  144. package/dist/esm/components/controls/Slider/Slider.js.map +1 -1
  145. package/dist/esm/components/controls/TagInput/TagInput.js.map +1 -1
  146. package/dist/esm/components/controls/TreeView/TreeNode.js +6 -0
  147. package/dist/esm/components/controls/TreeView/TreeNode.js.map +1 -1
  148. package/dist/esm/components/controls/TreeView/TreeView.js.map +1 -1
  149. package/dist/esm/components/controls/TreeView/useTreeState.js.map +1 -1
  150. package/dist/esm/components/controls/VectorInput/VectorInput.js +6 -0
  151. package/dist/esm/components/controls/VectorInput/VectorInput.js.map +1 -1
  152. package/dist/esm/components/data/DataTable/DataTable.js.map +1 -1
  153. package/dist/esm/components/editor/AssetBrowser/AssetBrowser.js.map +1 -1
  154. package/dist/esm/components/editor/AssetBrowser/AssetBrowserAnnouncementLive.js.map +1 -1
  155. package/dist/esm/components/editor/AssetBrowser/AssetBrowserBreadcrumbs.js.map +1 -1
  156. package/dist/esm/components/editor/AssetBrowser/AssetBrowserContent.js +6 -0
  157. package/dist/esm/components/editor/AssetBrowser/AssetBrowserContent.js.map +1 -1
  158. package/dist/esm/components/editor/AssetBrowser/AssetBrowserContext.js.map +1 -1
  159. package/dist/esm/components/editor/AssetBrowser/AssetBrowserContextMenu.js.map +1 -1
  160. package/dist/esm/components/editor/AssetBrowser/AssetBrowserDefaultMenus.js +6 -0
  161. package/dist/esm/components/editor/AssetBrowser/AssetBrowserDefaultMenus.js.map +1 -1
  162. package/dist/esm/components/editor/AssetBrowser/AssetBrowserGrid.js +1 -1
  163. package/dist/esm/components/editor/AssetBrowser/AssetBrowserGrid.js.map +1 -1
  164. package/dist/esm/components/editor/AssetBrowser/AssetBrowserGridItem.js +6 -0
  165. package/dist/esm/components/editor/AssetBrowser/AssetBrowserGridItem.js.map +1 -1
  166. package/dist/esm/components/editor/AssetBrowser/AssetBrowserList.js +6 -0
  167. package/dist/esm/components/editor/AssetBrowser/AssetBrowserList.js.map +1 -1
  168. package/dist/esm/components/editor/AssetBrowser/AssetBrowserRenameField.js.map +1 -1
  169. package/dist/esm/components/editor/AssetBrowser/AssetBrowserSidebar.js.map +1 -1
  170. package/dist/esm/components/editor/AssetBrowser/AssetBrowserStatusBar.js.map +1 -1
  171. package/dist/esm/components/editor/AssetBrowser/AssetBrowserStore.js.map +1 -1
  172. package/dist/esm/components/editor/AssetBrowser/AssetBrowserSurface.js +6 -0
  173. package/dist/esm/components/editor/AssetBrowser/AssetBrowserSurface.js.map +1 -1
  174. package/dist/esm/components/editor/AssetBrowser/AssetBrowserToolbar.js +6 -0
  175. package/dist/esm/components/editor/AssetBrowser/AssetBrowserToolbar.js.map +1 -1
  176. package/dist/esm/components/editor/AssetBrowser/assetBrowserFilter.js.map +1 -1
  177. package/dist/esm/components/editor/AssetBrowser/assetBrowserFormat.js.map +1 -1
  178. package/dist/esm/components/editor/AssetBrowser/assetBrowserGeometry.js.map +1 -1
  179. package/dist/esm/components/editor/AssetBrowser/assetBrowserKeyboard.js.map +1 -1
  180. package/dist/esm/components/editor/AssetBrowser/assetBrowserLabels.js.map +1 -1
  181. package/dist/esm/components/editor/AssetBrowser/slots.js.map +1 -1
  182. package/dist/esm/components/editor/AssetBrowser/useAssetBrowserAnnouncement.js.map +1 -1
  183. package/dist/esm/components/editor/AssetBrowser/useAssetBrowserHandle.js +1 -1
  184. package/dist/esm/components/editor/AssetBrowser/useAssetBrowserHandle.js.map +1 -1
  185. package/dist/esm/components/editor/AssetBrowser/useAssetBrowserViewState.js +1 -1
  186. package/dist/esm/components/editor/AssetBrowser/useAssetBrowserViewState.js.map +1 -1
  187. package/dist/esm/components/editor/AssetBrowser/useAssetDnd.js +1 -1
  188. package/dist/esm/components/editor/AssetBrowser/useAssetDnd.js.map +1 -1
  189. package/dist/esm/components/editor/AssetBrowser/useAssetGridKeyboardNav.js.map +1 -1
  190. package/dist/esm/components/editor/AssetBrowser/useAssetGridVirtualizer.js +1 -1
  191. package/dist/esm/components/editor/AssetBrowser/useAssetGridVirtualizer.js.map +1 -1
  192. package/dist/esm/components/editor/AssetBrowser/useAssetMarqueeGesture.js +1 -1
  193. package/dist/esm/components/editor/AssetBrowser/useAssetMarqueeGesture.js.map +1 -1
  194. package/dist/esm/components/editor/AssetBrowser/useAssetMutationKeyboard.js +1 -1
  195. package/dist/esm/components/editor/AssetBrowser/useAssetMutationKeyboard.js.map +1 -1
  196. package/dist/esm/components/editor/AssetBrowser/useAssetMutations.js +1 -1
  197. package/dist/esm/components/editor/AssetBrowser/useAssetMutations.js.map +1 -1
  198. package/dist/esm/components/editor/AssetBrowser/useAssetNavigation.js +1 -1
  199. package/dist/esm/components/editor/AssetBrowser/useAssetNavigation.js.map +1 -1
  200. package/dist/esm/components/editor/AssetBrowser/useAssetSelectionController.js +1 -1
  201. package/dist/esm/components/editor/AssetBrowser/useAssetSelectionController.js.map +1 -1
  202. package/dist/esm/components/editor/ChatPanel/ChatActionBar.js.map +1 -1
  203. package/dist/esm/components/editor/ChatPanel/ChatAttachment.js.map +1 -1
  204. package/dist/esm/components/editor/ChatPanel/ChatBubble.js.map +1 -1
  205. package/dist/esm/components/editor/ChatPanel/ChatCodeBlock.js +6 -0
  206. package/dist/esm/components/editor/ChatPanel/ChatCodeBlock.js.map +1 -1
  207. package/dist/esm/components/editor/ChatPanel/ChatContextChip.js.map +1 -1
  208. package/dist/esm/components/editor/ChatPanel/ChatEmptyState.js.map +1 -1
  209. package/dist/esm/components/editor/ChatPanel/ChatIcons.js.map +1 -1
  210. package/dist/esm/components/editor/ChatPanel/ChatInput.js +6 -0
  211. package/dist/esm/components/editor/ChatPanel/ChatInput.js.map +1 -1
  212. package/dist/esm/components/editor/ChatPanel/ChatInputToolbar.js.map +1 -1
  213. package/dist/esm/components/editor/ChatPanel/ChatMarkdownRenderer.js.map +1 -1
  214. package/dist/esm/components/editor/ChatPanel/ChatMessage.js +6 -0
  215. package/dist/esm/components/editor/ChatPanel/ChatMessage.js.map +1 -1
  216. package/dist/esm/components/editor/ChatPanel/ChatMessageList.js.map +1 -1
  217. package/dist/esm/components/editor/ChatPanel/ChatPanel.js.map +1 -1
  218. package/dist/esm/components/editor/ChatPanel/ChatToolCall.js.map +1 -1
  219. package/dist/esm/components/editor/ChatPanel/ChatTypingIndicator.js.map +1 -1
  220. package/dist/esm/components/editor/ChatPanel/useChatInput.js.map +1 -1
  221. package/dist/esm/components/editor/ChatPanel/useChatMessages.js.map +1 -1
  222. package/dist/esm/components/editor/ChatPanel/useChatScroll.js.map +1 -1
  223. package/dist/esm/components/editor/Minimap/Minimap.js +1 -1
  224. package/dist/esm/components/editor/Minimap/Minimap.js.map +1 -1
  225. package/dist/esm/components/editor/Minimap/Minimap.types.js.map +1 -1
  226. package/dist/esm/components/editor/Minimap/MinimapContext.js.map +1 -1
  227. package/dist/esm/components/editor/Minimap/MinimapCorner.js.map +1 -1
  228. package/dist/esm/components/editor/Minimap/MinimapFooter.js.map +1 -1
  229. package/dist/esm/components/editor/Minimap/MinimapStore.js.map +1 -1
  230. package/dist/esm/components/editor/Minimap/MinimapTitle.js.map +1 -1
  231. package/dist/esm/components/editor/Minimap/ViewportMinimap.js +1 -1
  232. package/dist/esm/components/editor/Minimap/ViewportMinimap.js.map +1 -1
  233. package/dist/esm/components/editor/Minimap/computeBoundsFromItems.js.map +1 -1
  234. package/dist/esm/components/editor/Minimap/index.js.map +1 -1
  235. package/dist/esm/components/editor/Minimap/minimapCoords.js.map +1 -1
  236. package/dist/esm/components/editor/Minimap/minimapDrawing.js.map +1 -1
  237. package/dist/esm/components/editor/Minimap/minimapHitTest.js.map +1 -1
  238. package/dist/esm/components/editor/Minimap/useMinimapGestures.js +1 -1
  239. package/dist/esm/components/editor/Minimap/useMinimapGestures.js.map +1 -1
  240. package/dist/esm/components/editor/NodeGraph/NodeGraph.js +1 -1
  241. package/dist/esm/components/editor/NodeGraph/NodeGraph.js.map +1 -1
  242. package/dist/esm/components/editor/NodeGraph/NodeGraph.types.js.map +1 -1
  243. package/dist/esm/components/editor/NodeGraph/NodeGraphContext.js.map +1 -1
  244. package/dist/esm/components/editor/NodeGraph/NodeGraphEdgeLabels.js.map +1 -1
  245. package/dist/esm/components/editor/NodeGraph/NodeGraphGroup.js.map +1 -1
  246. package/dist/esm/components/editor/NodeGraph/NodeGraphMinimap.js.map +1 -1
  247. package/dist/esm/components/editor/NodeGraph/NodeGraphNode.js +1 -1
  248. package/dist/esm/components/editor/NodeGraph/NodeGraphNode.js.map +1 -1
  249. package/dist/esm/components/editor/NodeGraph/NodeGraphNodeContext.js.map +1 -1
  250. package/dist/esm/components/editor/NodeGraph/NodeGraphNodeParts.js.map +1 -1
  251. package/dist/esm/components/editor/NodeGraph/NodeGraphNodeSection.js +1 -1
  252. package/dist/esm/components/editor/NodeGraph/NodeGraphNodeSection.js.map +1 -1
  253. package/dist/esm/components/editor/NodeGraph/NodeGraphNodeSectionContext.js.map +1 -1
  254. package/dist/esm/components/editor/NodeGraph/NodeGraphPin.js.map +1 -1
  255. package/dist/esm/components/editor/NodeGraph/NodeGraphPort.js +1 -1
  256. package/dist/esm/components/editor/NodeGraph/NodeGraphPort.js.map +1 -1
  257. package/dist/esm/components/editor/NodeGraph/NodeGraphPortVisual.js.map +1 -1
  258. package/dist/esm/components/editor/NodeGraph/NodeGraphSlots.js.map +1 -1
  259. package/dist/esm/components/editor/NodeGraph/NodeGraphSpawnPalette.js.map +1 -1
  260. package/dist/esm/components/editor/NodeGraph/NodeGraphStore.js.map +1 -1
  261. package/dist/esm/components/editor/NodeGraph/NodeGraphToolbar.js.map +1 -1
  262. package/dist/esm/components/editor/NodeGraph/createTypeMatchValidator.js.map +1 -1
  263. package/dist/esm/components/editor/NodeGraph/nodeGraphActions.js.map +1 -1
  264. package/dist/esm/components/editor/NodeGraph/nodeGraphDrawing.js.map +1 -1
  265. package/dist/esm/components/editor/NodeGraph/nodeGraphIds.js.map +1 -1
  266. package/dist/esm/components/editor/NodeGraph/nodeGraphMath.js.map +1 -1
  267. package/dist/esm/components/editor/NodeGraph/sortSlots.js.map +1 -1
  268. package/dist/esm/components/editor/NodeGraph/useDragGesture.js.map +1 -1
  269. package/dist/esm/components/editor/NodeGraph/useNodeGraph.js +1 -1
  270. package/dist/esm/components/editor/NodeGraph/useNodeGraph.js.map +1 -1
  271. package/dist/esm/components/editor/NodeGraph/useNodeGraphConnection.js +1 -1
  272. package/dist/esm/components/editor/NodeGraph/useNodeGraphConnection.js.map +1 -1
  273. package/dist/esm/components/editor/NodeGraph/useNodeGraphEdgeInteraction.js +1 -1
  274. package/dist/esm/components/editor/NodeGraph/useNodeGraphEdgeInteraction.js.map +1 -1
  275. package/dist/esm/components/editor/NodeGraph/useNodeGraphGroupDrag.js +1 -1
  276. package/dist/esm/components/editor/NodeGraph/useNodeGraphGroupDrag.js.map +1 -1
  277. package/dist/esm/components/editor/NodeGraph/useNodeGraphKeyboard.js +1 -1
  278. package/dist/esm/components/editor/NodeGraph/useNodeGraphKeyboard.js.map +1 -1
  279. package/dist/esm/components/editor/NodeGraph/useNodeGraphNodeDrag.js +1 -1
  280. package/dist/esm/components/editor/NodeGraph/useNodeGraphNodeDrag.js.map +1 -1
  281. package/dist/esm/components/editor/NodeGraph/useStoreSlice.js.map +1 -1
  282. package/dist/esm/components/editor/PropertyInspector/PropertyGroup.js.map +1 -1
  283. package/dist/esm/components/editor/PropertyInspector/PropertyPanel.css.js +4 -3
  284. package/dist/esm/components/editor/PropertyInspector/PropertyPanel.css.js.map +1 -1
  285. package/dist/esm/components/editor/PropertyInspector/PropertyPanel.js +5 -4
  286. package/dist/esm/components/editor/PropertyInspector/PropertyPanel.js.map +1 -1
  287. package/dist/esm/components/editor/PropertyInspector/PropertyRow.css.js +7 -6
  288. package/dist/esm/components/editor/PropertyInspector/PropertyRow.css.js.map +1 -1
  289. package/dist/esm/components/editor/PropertyInspector/PropertyRow.js +18 -3
  290. package/dist/esm/components/editor/PropertyInspector/PropertyRow.js.map +1 -1
  291. package/dist/esm/components/editor/PropertyInspector/PropertySection.css.js +11 -10
  292. package/dist/esm/components/editor/PropertyInspector/PropertySection.css.js.map +1 -1
  293. package/dist/esm/components/editor/PropertyInspector/PropertySection.js +29 -21
  294. package/dist/esm/components/editor/PropertyInspector/PropertySection.js.map +1 -1
  295. package/dist/esm/components/editor/PropertyInspector/usePropertyUndo.js.map +1 -1
  296. package/dist/esm/components/editor/Timeline/Timeline.js +1 -1
  297. package/dist/esm/components/editor/Timeline/Timeline.js.map +1 -1
  298. package/dist/esm/components/editor/Timeline/Timeline.types.js.map +1 -1
  299. package/dist/esm/components/editor/Timeline/TimelineContext.js.map +1 -1
  300. package/dist/esm/components/editor/Timeline/TimelineFooter.js.map +1 -1
  301. package/dist/esm/components/editor/Timeline/TimelineMinimap.js +1 -1
  302. package/dist/esm/components/editor/Timeline/TimelineMinimap.js.map +1 -1
  303. package/dist/esm/components/editor/Timeline/TimelineStore.js.map +1 -1
  304. package/dist/esm/components/editor/Timeline/TimelineToolbar.js.map +1 -1
  305. package/dist/esm/components/editor/Timeline/TimelineTrackHeaders.js +1 -1
  306. package/dist/esm/components/editor/Timeline/TimelineTrackHeaders.js.map +1 -1
  307. package/dist/esm/components/editor/Timeline/TimelineTrackScale.js.map +1 -1
  308. package/dist/esm/components/editor/Timeline/index.js.map +1 -1
  309. package/dist/esm/components/editor/Timeline/timelineCoords.js.map +1 -1
  310. package/dist/esm/components/editor/Timeline/timelineDrawing.js.map +1 -1
  311. package/dist/esm/components/editor/Timeline/timelineEdits.js.map +1 -1
  312. package/dist/esm/components/editor/Timeline/timelineHitTest.js.map +1 -1
  313. package/dist/esm/components/editor/Timeline/timelineLayout.js.map +1 -1
  314. package/dist/esm/components/editor/Timeline/timelineSelection.js.map +1 -1
  315. package/dist/esm/components/editor/Timeline/useTimelineDraw.js +1 -1
  316. package/dist/esm/components/editor/Timeline/useTimelineDraw.js.map +1 -1
  317. package/dist/esm/components/editor/Timeline/useTimelineGestures.js +1 -1
  318. package/dist/esm/components/editor/Timeline/useTimelineGestures.js.map +1 -1
  319. package/dist/esm/components/editor/Timeline/useTimelinePlayback.js.map +1 -1
  320. package/dist/esm/components/editor/TransformControl/TransformControl.js.map +1 -1
  321. package/dist/esm/components/editor/ViewportGizmo/ViewportGizmo.js.map +1 -1
  322. package/dist/esm/components/editor/ViewportGizmo/gizmoMath.js.map +1 -1
  323. package/dist/esm/components/editor/ViewportGizmo/useGizmoInteraction.js.map +1 -1
  324. package/dist/esm/components/editor/ViewportGizmo/useGizmoRenderer.js.map +1 -1
  325. package/dist/esm/components/feedback/Alert/Alert.js.map +1 -1
  326. package/dist/esm/components/feedback/Alert/AlertActions.js.map +1 -1
  327. package/dist/esm/components/feedback/Alert/AlertDescription.js.map +1 -1
  328. package/dist/esm/components/feedback/Alert/AlertTitle.js.map +1 -1
  329. package/dist/esm/components/feedback/CommandPalette/CommandPalette.js.map +1 -1
  330. package/dist/esm/components/feedback/CommandPalette/fuzzySearch.js.map +1 -1
  331. package/dist/esm/components/feedback/CommandPalette/useRecentItems.js.map +1 -1
  332. package/dist/esm/components/feedback/Dialog/Dialog.js.map +1 -1
  333. package/dist/esm/components/feedback/Dialog/DialogBody.js.map +1 -1
  334. package/dist/esm/components/feedback/Dialog/DialogClose.js.map +1 -1
  335. package/dist/esm/components/feedback/Dialog/DialogFooter.js.map +1 -1
  336. package/dist/esm/components/feedback/Dialog/DialogHeader.js.map +1 -1
  337. package/dist/esm/components/feedback/Dialog/useDialogAnimation.js.map +1 -1
  338. package/dist/esm/components/feedback/Drawer/Drawer.js.map +1 -1
  339. package/dist/esm/components/feedback/Drawer/useDrawerAnimation.js.map +1 -1
  340. package/dist/esm/components/feedback/EmptyState/EmptyState.js.map +1 -1
  341. package/dist/esm/components/feedback/LogView/LogView.js.map +1 -1
  342. package/dist/esm/components/feedback/LogView/LogViewBody.js.map +1 -1
  343. package/dist/esm/components/feedback/LogView/LogViewContext.js.map +1 -1
  344. package/dist/esm/components/feedback/LogView/LogViewParts.js.map +1 -1
  345. package/dist/esm/components/feedback/LogView/LogViewStore.js.map +1 -1
  346. package/dist/esm/components/feedback/LogView/logViewLabels.js.map +1 -1
  347. package/dist/esm/components/feedback/LogView/logViewLevels.js.map +1 -1
  348. package/dist/esm/components/feedback/LogView/logViewUtils.js.map +1 -1
  349. package/dist/esm/components/feedback/LogView/useFollowTail.js.map +1 -1
  350. package/dist/esm/components/feedback/LogView/useLogSelection.js.map +1 -1
  351. package/dist/esm/components/feedback/ProgressBar/CircularProgress.js.map +1 -1
  352. package/dist/esm/components/feedback/ProgressBar/ProgressBar.js.map +1 -1
  353. package/dist/esm/components/feedback/Skeleton/Skeleton.js.map +1 -1
  354. package/dist/esm/components/feedback/Skeleton/SkeletonGroup.js.map +1 -1
  355. package/dist/esm/components/feedback/Skeleton/SkeletonLayout.js.map +1 -1
  356. package/dist/esm/components/feedback/Spinner/Spinner.js.map +1 -1
  357. package/dist/esm/components/feedback/Stat/Stat.js.map +1 -1
  358. package/dist/esm/components/feedback/Toast/ToastContainer.js.map +1 -1
  359. package/dist/esm/components/feedback/Toast/ToastItem.js.map +1 -1
  360. package/dist/esm/components/feedback/Toast/ToastProvider.js.map +1 -1
  361. package/dist/esm/components/feedback/Toast/useToast.js.map +1 -1
  362. package/dist/esm/components/form/FormHelperText.js.map +1 -1
  363. package/dist/esm/components/form/FormLabel.js.map +1 -1
  364. package/dist/esm/components/form/InputWrapper.js.map +1 -1
  365. package/dist/esm/components/layout/Accordion/Accordion.js.map +1 -1
  366. package/dist/esm/components/layout/Accordion/AccordionContent.js.map +1 -1
  367. package/dist/esm/components/layout/Accordion/AccordionItem.js.map +1 -1
  368. package/dist/esm/components/layout/Accordion/AccordionTrigger.js.map +1 -1
  369. package/dist/esm/components/layout/Card/Card.js.map +1 -1
  370. package/dist/esm/components/layout/Divider/Divider.js.map +1 -1
  371. package/dist/esm/components/layout/Flex/Flex.js.map +1 -1
  372. package/dist/esm/components/layout/Grid/Grid.js.map +1 -1
  373. package/dist/esm/components/layout/ListItem/ListItem.js.map +1 -1
  374. package/dist/esm/components/layout/PageHeader/PageHeader.js.map +1 -1
  375. package/dist/esm/components/layout/PanelSurface/PanelSurface.js.map +1 -1
  376. package/dist/esm/components/layout/ScrollArea/ScrollArea.js.map +1 -1
  377. package/dist/esm/components/layout/Spacer/Spacer.js.map +1 -1
  378. package/dist/esm/components/layout/SplitPane/SplitPane.js.map +1 -1
  379. package/dist/esm/components/layout/SplitPane/SplitPanePanel.js.map +1 -1
  380. package/dist/esm/components/layout/Stack/Stack.js.map +1 -1
  381. package/dist/esm/components/navigation/Breadcrumbs/BreadcrumbEllipsis.js.map +1 -1
  382. package/dist/esm/components/navigation/Breadcrumbs/BreadcrumbItem.js.map +1 -1
  383. package/dist/esm/components/navigation/Breadcrumbs/BreadcrumbSeparator.js.map +1 -1
  384. package/dist/esm/components/navigation/Breadcrumbs/Breadcrumbs.js +6 -0
  385. package/dist/esm/components/navigation/Breadcrumbs/Breadcrumbs.js.map +1 -1
  386. package/dist/esm/components/navigation/ContextMenu/ContextMenu.js.map +1 -1
  387. package/dist/esm/components/navigation/Menu/Menu.js.map +1 -1
  388. package/dist/esm/components/navigation/Menu/MenuGapContext.js.map +1 -1
  389. package/dist/esm/components/navigation/Pagination/Pagination.js.map +1 -1
  390. package/dist/esm/components/navigation/Pagination/usePagination.js.map +1 -1
  391. package/dist/esm/components/navigation/PathBar/PathBar.js +7 -1
  392. package/dist/esm/components/navigation/PathBar/PathBar.js.map +1 -1
  393. package/dist/esm/components/navigation/PathBar/PathBarSiblingMenu.js +6 -0
  394. package/dist/esm/components/navigation/PathBar/PathBarSiblingMenu.js.map +1 -1
  395. package/dist/esm/components/navigation/PathBar/pathUtils.js.map +1 -1
  396. package/dist/esm/components/navigation/SegmentedControl/SegmentedControl.js.map +1 -1
  397. package/dist/esm/components/navigation/SegmentedControl/SegmentedControlItem.js.map +1 -1
  398. package/dist/esm/components/navigation/Tabs/Tab.js.map +1 -1
  399. package/dist/esm/components/navigation/Tabs/TabList.js.map +1 -1
  400. package/dist/esm/components/navigation/Tabs/TabPanel.js.map +1 -1
  401. package/dist/esm/components/navigation/Tabs/Tabs.js.map +1 -1
  402. package/dist/esm/components/primitives/Avatar/Avatar.js +6 -0
  403. package/dist/esm/components/primitives/Avatar/Avatar.js.map +1 -1
  404. package/dist/esm/components/primitives/Avatar/AvatarGroup.js.map +1 -1
  405. package/dist/esm/components/primitives/Badge/Badge.js +6 -0
  406. package/dist/esm/components/primitives/Badge/Badge.js.map +1 -1
  407. package/dist/esm/components/primitives/Button/Button.js.map +1 -1
  408. package/dist/esm/components/primitives/Checkbox/Checkbox.js.map +1 -1
  409. package/dist/esm/components/primitives/Checkbox/CheckboxGroup.js.map +1 -1
  410. package/dist/esm/components/primitives/Code/Code.js.map +1 -1
  411. package/dist/esm/components/primitives/Collapsible/Collapsible.js.map +1 -1
  412. package/dist/esm/components/primitives/HoverCard/HoverCard.js.map +1 -1
  413. package/dist/esm/components/primitives/Icon/Icon.js.map +1 -1
  414. package/dist/esm/components/primitives/IconButton/IconButton.js.map +1 -1
  415. package/dist/esm/components/primitives/Input/Input.js.map +1 -1
  416. package/dist/esm/components/primitives/Kbd/Kbd.js.map +1 -1
  417. package/dist/esm/components/primitives/Link/Link.js.map +1 -1
  418. package/dist/esm/components/primitives/Paper/Paper.js.map +1 -1
  419. package/dist/esm/components/primitives/Popover/Popover.js.map +1 -1
  420. package/dist/esm/components/primitives/Popover/PopoverClose.js.map +1 -1
  421. package/dist/esm/components/primitives/Popover/PopoverContent.js.map +1 -1
  422. package/dist/esm/components/primitives/Popover/PopoverTrigger.js.map +1 -1
  423. package/dist/esm/components/primitives/Radio/Radio.js.map +1 -1
  424. package/dist/esm/components/primitives/Radio/RadioGroup.js.map +1 -1
  425. package/dist/esm/components/primitives/Switch/Switch.js.map +1 -1
  426. package/dist/esm/components/primitives/Text/Text.js.map +1 -1
  427. package/dist/esm/components/primitives/TextArea/TextArea.js.map +1 -1
  428. package/dist/esm/components/primitives/Tooltip/Arrow.js.map +1 -1
  429. package/dist/esm/components/primitives/Tooltip/Tooltip.js.map +1 -1
  430. package/dist/esm/components/primitives/Tooltip/utils.js.map +1 -1
  431. package/dist/esm/components/primitives/VisuallyHidden/VisuallyHidden.js.map +1 -1
  432. package/dist/esm/components/primitives/canvas/CanvasContainer.js.map +1 -1
  433. package/dist/esm/components/primitives/canvas/canvasCoords.js.map +1 -1
  434. package/dist/esm/components/primitives/canvas/canvasDrawing.js.map +1 -1
  435. package/dist/esm/components/primitives/canvas/canvasTheme.js.map +1 -1
  436. package/dist/esm/components/primitives/canvas/useCanvasRenderer.js.map +1 -1
  437. package/dist/esm/components/primitives/viewport/Viewport.js +1 -1
  438. package/dist/esm/components/primitives/viewport/Viewport.js.map +1 -1
  439. package/dist/esm/components/primitives/viewport/ViewportContext.js.map +1 -1
  440. package/dist/esm/components/primitives/viewport/ViewportLayer.js +1 -1
  441. package/dist/esm/components/primitives/viewport/ViewportLayer.js.map +1 -1
  442. package/dist/esm/components/primitives/viewport/ViewportOverlay.js.map +1 -1
  443. package/dist/esm/components/primitives/viewport/ViewportStore.js.map +1 -1
  444. package/dist/esm/components/primitives/viewport/ViewportWorld.js.map +1 -1
  445. package/dist/esm/components/primitives/viewport/useViewportGestures.js +1 -1
  446. package/dist/esm/components/primitives/viewport/useViewportGestures.js.map +1 -1
  447. package/dist/esm/components/primitives/viewport/viewportCoords.js.map +1 -1
  448. package/dist/esm/components/primitives/viewport/viewportMath.js.map +1 -1
  449. package/dist/esm/components/shell/AppShell/AppShell.js.map +1 -1
  450. package/dist/esm/components/shell/FloatingPanel/FloatingPanel.js.map +1 -1
  451. package/dist/esm/components/shell/MenuBar/MenuBar.js.map +1 -1
  452. package/dist/esm/components/shell/StatusBar/StatusBar.js.map +1 -1
  453. package/dist/esm/components/shell/Toolbar/Toolbar.js.map +1 -1
  454. package/dist/esm/context/KeyboardContext.js.map +1 -1
  455. package/dist/esm/hooks/useBreakpoint/useBreakpoint.js.map +1 -1
  456. package/dist/esm/hooks/useClickOutside/useClickOutside.js.map +1 -1
  457. package/dist/esm/hooks/useClipboard/useClipboard.js.map +1 -1
  458. package/dist/esm/hooks/useControlledState/useControlledState.js.map +1 -1
  459. package/dist/esm/hooks/useDebounced/useDebouncedCallback.js.map +1 -1
  460. package/dist/esm/hooks/useDebounced/useDebouncedValue.js.map +1 -1
  461. package/dist/esm/hooks/useDisclosure/useDisclosure.js.map +1 -1
  462. package/dist/esm/hooks/useEventCallback/useEventCallback.js.map +1 -1
  463. package/dist/esm/hooks/useFocusTrap/useFocusTrap.js.map +1 -1
  464. package/dist/esm/hooks/useHotkey/useHotkey.js.map +1 -1
  465. package/dist/esm/hooks/useIntersectionObserver/useIntersectionObserver.js.map +1 -1
  466. package/dist/esm/hooks/useIsMounted/useIsMounted.js.map +1 -1
  467. package/dist/esm/hooks/useKeyboard/useKeyboard.js.map +1 -1
  468. package/dist/esm/hooks/useKeyboard/utils.js.map +1 -1
  469. package/dist/esm/hooks/useLatest/useLatest.js.map +1 -1
  470. package/dist/esm/hooks/useListboxNav/useListboxNav.js.map +1 -1
  471. package/dist/esm/hooks/useMediaQuery/useMediaQuery.js.map +1 -1
  472. package/dist/esm/hooks/useMergedRef/useMergedRef.js.map +1 -1
  473. package/dist/esm/hooks/useNavigationHistory/useNavigationHistory.js.map +1 -1
  474. package/dist/esm/hooks/useResizeObserver/useResizeObserver.js.map +1 -1
  475. package/dist/esm/hooks/useTheme/useTheme.js +1 -1
  476. package/dist/esm/hooks/useTheme/useTheme.js.map +1 -1
  477. package/dist/esm/hooks/useThrottledCallback/useThrottledCallback.js.map +1 -1
  478. package/dist/esm/index.js +6 -0
  479. package/dist/esm/index.js.map +1 -1
  480. package/dist/esm/node_modules/tslib/tslib.es6.js.map +1 -1
  481. package/dist/esm/styles.js +3 -0
  482. package/dist/esm/styles.js.map +1 -0
  483. package/dist/esm/theme/ThemeProvider.js +5 -3
  484. package/dist/esm/theme/ThemeProvider.js.map +1 -1
  485. package/dist/esm/theme/VanillaThemeProvider.js.map +1 -1
  486. package/dist/esm/theme/breakpoints.js.map +1 -1
  487. package/dist/esm/theme/createCustomTheme.js +1 -1
  488. package/dist/esm/theme/createCustomTheme.js.map +1 -1
  489. package/dist/esm/theme/createLightTheme.js.map +1 -1
  490. package/dist/esm/theme/darkThemeValues.js.map +1 -1
  491. package/dist/esm/theme/lightThemeValues.js.map +1 -1
  492. package/dist/esm/theme/themeContractData.js.map +1 -1
  493. package/dist/esm/utils/cx.js.map +1 -1
  494. package/dist/esm/utils/devWarn.js.map +1 -1
  495. package/dist/esm/utils/mathExpression.js.map +1 -1
  496. package/dist/esm/utils/mathUtils.js.map +1 -1
  497. package/dist/esm/utils/objects.js.map +1 -1
  498. package/dist/esm/utils/platform.js.map +1 -1
  499. package/dist/tokens/tokens.dark.css +1 -1
  500. package/dist/tokens/tokens.json +1 -1
  501. package/dist/tokens/tokens.light.css +1 -1
  502. package/dist/types/components/Icons/CameraIcon.d.ts +34 -0
  503. package/dist/types/components/Icons/CubeIcon.d.ts +34 -0
  504. package/dist/types/components/Icons/OrbitIcon.d.ts +34 -0
  505. package/dist/types/components/Icons/RotateIcon.d.ts +35 -0
  506. package/dist/types/components/Icons/SpinIcon.d.ts +35 -0
  507. package/dist/types/components/Icons/ZoomFitIcon.d.ts +35 -0
  508. package/dist/types/components/controls/Select/Select.d.ts +1 -1
  509. package/dist/types/components/controls/Select/Select.types.d.ts +43 -9
  510. package/dist/types/components/editor/PropertyInspector/PropertyInspector.types.d.ts +49 -3
  511. package/dist/types/components/primitives/Button/Button.d.ts +1 -1
  512. package/dist/types/components/primitives/IconButton/IconButton.d.ts +1 -1
  513. package/dist/types/index.d.ts +6 -0
  514. package/dist/types/theme/ThemeProvider.d.ts +5 -3
  515. package/package.json +6 -2
  516. package/dist/esm/_virtual/_commonjsHelpers.js +0 -6
  517. package/dist/esm/_virtual/_commonjsHelpers.js.map +0 -1
  518. package/dist/esm/_virtual/cjs.js +0 -4
  519. package/dist/esm/_virtual/cjs.js.map +0 -1
  520. package/dist/esm/_virtual/cssesc.js +0 -8
  521. package/dist/esm/_virtual/cssesc.js.map +0 -1
  522. package/dist/esm/_virtual/picocolors.js +0 -8
  523. package/dist/esm/_virtual/picocolors.js.map +0 -1
  524. package/dist/esm/_virtual/picocolors2.js +0 -4
  525. package/dist/esm/_virtual/picocolors2.js.map +0 -1
  526. package/dist/esm/node_modules/@emotion/hash/dist/emotion-hash.esm.js +0 -56
  527. package/dist/esm/node_modules/@emotion/hash/dist/emotion-hash.esm.js.map +0 -1
  528. package/dist/esm/node_modules/@vanilla-extract/css/adapter/dist/vanilla-extract-css-adapter.esm.js +0 -46
  529. package/dist/esm/node_modules/@vanilla-extract/css/adapter/dist/vanilla-extract-css-adapter.esm.js.map +0 -1
  530. package/dist/esm/node_modules/@vanilla-extract/css/dist/taggedTemplateLiteral-10998315.esm.js +0 -13
  531. package/dist/esm/node_modules/@vanilla-extract/css/dist/taggedTemplateLiteral-10998315.esm.js.map +0 -1
  532. package/dist/esm/node_modules/@vanilla-extract/css/dist/transformCss-fd0786e1.esm.js +0 -962
  533. package/dist/esm/node_modules/@vanilla-extract/css/dist/transformCss-fd0786e1.esm.js.map +0 -1
  534. package/dist/esm/node_modules/@vanilla-extract/css/dist/vanilla-extract-css.esm.js +0 -309
  535. package/dist/esm/node_modules/@vanilla-extract/css/dist/vanilla-extract-css.esm.js.map +0 -1
  536. package/dist/esm/node_modules/@vanilla-extract/css/fileScope/dist/vanilla-extract-css-fileScope.esm.js +0 -18
  537. package/dist/esm/node_modules/@vanilla-extract/css/fileScope/dist/vanilla-extract-css-fileScope.esm.js.map +0 -1
  538. package/dist/esm/node_modules/@vanilla-extract/css/injectStyles/dist/vanilla-extract-css-injectStyles.esm.js +0 -23
  539. package/dist/esm/node_modules/@vanilla-extract/css/injectStyles/dist/vanilla-extract-css-injectStyles.esm.js.map +0 -1
  540. package/dist/esm/node_modules/@vanilla-extract/css/node_modules/lru-cache/dist/esm/index.js +0 -1543
  541. package/dist/esm/node_modules/@vanilla-extract/css/node_modules/lru-cache/dist/esm/index.js.map +0 -1
  542. package/dist/esm/node_modules/@vanilla-extract/private/dist/vanilla-extract-private.esm.js +0 -38
  543. package/dist/esm/node_modules/@vanilla-extract/private/dist/vanilla-extract-private.esm.js.map +0 -1
  544. package/dist/esm/node_modules/css-what/lib/es/parse.js +0 -424
  545. package/dist/esm/node_modules/css-what/lib/es/parse.js.map +0 -1
  546. package/dist/esm/node_modules/css-what/lib/es/types.js +0 -29
  547. package/dist/esm/node_modules/css-what/lib/es/types.js.map +0 -1
  548. package/dist/esm/node_modules/cssesc/cssesc.js +0 -120
  549. package/dist/esm/node_modules/cssesc/cssesc.js.map +0 -1
  550. package/dist/esm/node_modules/dedent/dist/dedent.js +0 -100
  551. package/dist/esm/node_modules/dedent/dist/dedent.js.map +0 -1
  552. package/dist/esm/node_modules/deep-object-diff/mjs/diff.js +0 -40
  553. package/dist/esm/node_modules/deep-object-diff/mjs/diff.js.map +0 -1
  554. package/dist/esm/node_modules/deep-object-diff/mjs/utils.js +0 -9
  555. package/dist/esm/node_modules/deep-object-diff/mjs/utils.js.map +0 -1
  556. package/dist/esm/node_modules/deepmerge/dist/cjs.js +0 -143
  557. package/dist/esm/node_modules/deepmerge/dist/cjs.js.map +0 -1
  558. package/dist/esm/node_modules/media-query-parser/dist/media-query-parser.esm.js +0 -1386
  559. package/dist/esm/node_modules/media-query-parser/dist/media-query-parser.esm.js.map +0 -1
  560. package/dist/esm/node_modules/modern-ahocorasick/dist/index.js +0 -102
  561. package/dist/esm/node_modules/modern-ahocorasick/dist/index.js.map +0 -1
  562. package/dist/esm/node_modules/picocolors/picocolors.js +0 -87
  563. package/dist/esm/node_modules/picocolors/picocolors.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"useCurveRenderer.js","sources":["../../../../../../src/components/controls/CurveEditor/useCurveRenderer.ts"],"sourcesContent":[null],"names":[],"mappings":";;;;;;AAsDA;AAEM;AACJ;AAEA;AACE;AACA;;;AAGA;;AAEA;AAEA;AAEA;AACA;AACA;;;;AAKA;;AAEE;AACA;AACA;;;AAEA;;AAGF;;AAkBA;AACE;;;;AAMF;AACA;;;;;;AAOI;AACA;;;;AAID;;;;;;AAOC;AACA;;;AAGD;;;;;;;AAiBC;AACA;;;;;AAKD;;;AAIH;AACA;;AAGA;AACE;;;;;;AA6BA;;AAGF;AACF;;;;;;AAOE;;;AAGI;AACA;AACF;AACA;AACA;AACE;AACF;;AAGF;AACA;;AAEJ;AAEA;AACA;AAEA;AASE;;AAEA;AACA;;AAEA;AACA;AACA;AACA;;AAGA;AACA;;AACA;;AAGA;AACE;AACA;;AACA;;;;AAKJ;AAEA;AAWE;AAEA;AACE;AACA;;AACA;;;;AAGA;AACA;;AAGA;AAQA;AACA;AACA;;;;;;AAMA;;;AAIA;AAEA;AACA;;AAEA;;;AAUA;AAQA;AACA;AACA;;;;;;AAMA;;;AAIA;AAEE;AACF;AACA;;AAEA;;;AASJ;AAEA;AAYE;AAEA;AACE;AACA;;;AAEA;AAEA;;AAIA;;;AAIE;;;;;;;;;;AASA;;AAEA;;;AAIA;;AAEA;AACA;;;;AAGA;;AAEA;AACA;;;;AAIN;AAEA;AAKE;AACA;AACA;AACA;AAEA;AACA;;AAGA;AACA;AACA;;;AAGA;AACF;;"}
1
+ {"version":3,"file":"useCurveRenderer.js","sources":["src/components/controls/CurveEditor/useCurveRenderer.ts"],"sourcesContent":["'use client';\n\nimport { useEffect, useRef, useCallback } from 'react';\n\nimport {\n drawGrid as sharedDrawGrid,\n drawDomainBounds as sharedDrawDomainBounds,\n drawAxisLabels as sharedDrawAxisLabels,\n} from '@/components/primitives/canvas/canvasDrawing';\nimport {\n resolveCanvasTheme,\n resolveVarValue,\n} from '@/components/primitives/canvas/canvasTheme';\n\nimport { domainToCanvas, sampleCurve } from './curveUtils';\n\nimport type {\n CurveData,\n CurveViewport,\n CurveHitTest,\n CurveBackgroundInfo,\n} from './CurveEditor.types';\nimport type { CanvasThemeColors } from '@/components/primitives/canvas/canvas.types';\nimport type React from 'react';\n\ninterface UseCurveRendererOptions {\n canvasRef: React.RefObject<HTMLCanvasElement | null>;\n curve: CurveData;\n viewport: CurveViewport;\n showGrid: boolean;\n gridSubdivisions: number;\n showAxisLabels: boolean;\n labelX?: string;\n labelY?: string;\n curveColor: string;\n curveWidth: number;\n renderBackground?: (\n ctx: CanvasRenderingContext2D,\n info: CurveBackgroundInfo\n ) => void;\n selectedIds: Set<string>;\n hoveredElement: CurveHitTest | null;\n selectionBox: {\n x1: number;\n y1: number;\n x2: number;\n y2: number;\n } | null;\n disabled: boolean;\n isDragging: boolean;\n lockTangents: boolean;\n resizeToken?: number;\n}\n\n// CanvasThemeColors and resolveCanvasTheme are now imported from shared canvas primitives\n\nexport function useCurveRenderer(options: UseCurveRendererOptions): void {\n const rafRef = useRef<number>(0);\n\n const draw = useCallback(() => {\n const canvas = options.canvasRef.current;\n if (!canvas) return;\n\n const ctx = canvas.getContext('2d');\n if (!ctx) return;\n\n const theme = resolveCanvasTheme(canvas);\n\n const dpr =\n typeof window !== 'undefined' ? window.devicePixelRatio || 1 : 1;\n const rect = canvas.getBoundingClientRect();\n const w = rect.width;\n const h = rect.height;\n\n // Set canvas resolution for retina. Writing the canvas backing-store size\n // is a DOM side effect of drawing; the rule mistakes it for mutating the\n // `options` argument because `canvas` is reached via options.canvasRef.\n if (canvas.width !== w * dpr || canvas.height !== h * dpr) {\n // eslint-disable-next-line react-hooks/immutability\n canvas.width = w * dpr;\n canvas.height = h * dpr;\n ctx.scale(dpr, dpr);\n } else {\n ctx.setTransform(dpr, 0, 0, dpr, 0, 0);\n }\n\n const {\n curve,\n viewport,\n showGrid,\n gridSubdivisions,\n showAxisLabels,\n labelX,\n labelY,\n curveColor: rawCurveColor,\n curveWidth,\n renderBackground,\n selectedIds,\n hoveredElement,\n selectionBox,\n disabled,\n } = options;\n\n // Resolve curveColor if it's a CSS variable reference\n const curveColor = rawCurveColor.startsWith('var(')\n ? resolveVarValue(canvas, rawCurveColor)\n : rawCurveColor;\n\n const opacity = disabled ? 0.4 : 1;\n\n // 1. Clear background\n ctx.globalAlpha = 1;\n ctx.fillStyle = theme.backgroundSecondary;\n ctx.fillRect(0, 0, w, h);\n\n // 1b. Custom background (histograms, gradients, etc.)\n if (renderBackground) {\n ctx.save();\n renderBackground(ctx, {\n width: w,\n height: h,\n viewport,\n domainX: curve.domainX,\n domainY: curve.domainY,\n });\n ctx.restore();\n }\n\n // 2. Grid lines -- based on domain values, not viewport\n if (showGrid) {\n sharedDrawGrid(ctx, w, h, theme, {\n subdivisions: gridSubdivisions,\n domain: { domainX: curve.domainX, domainY: curve.domainY },\n viewport,\n opacity,\n });\n }\n\n // 3. Domain boundary axes (stronger lines at domain min/max)\n sharedDrawDomainBounds(\n ctx,\n w,\n h,\n theme,\n { domainX: curve.domainX, domainY: curve.domainY },\n viewport,\n opacity\n );\n\n // 4. Axis labels -- based on domain values\n if (showAxisLabels) {\n sharedDrawAxisLabels(ctx, w, h, theme, {\n subdivisions: gridSubdivisions,\n domain: { domainX: curve.domainX, domainY: curve.domainY },\n viewport,\n opacity,\n labelX,\n labelY,\n });\n }\n\n // 5. Curve line\n ctx.globalAlpha = opacity;\n drawCurveLine(ctx, curve, viewport, w, h, curveColor, curveWidth);\n\n // 6. Tangent handles (for selected keyframes)\n if (!options.lockTangents) {\n drawTangentHandles(\n ctx,\n curve,\n viewport,\n w,\n h,\n selectedIds,\n hoveredElement,\n theme,\n opacity\n );\n }\n\n // 7. Keyframe points\n drawKeyframes(\n ctx,\n curve,\n viewport,\n w,\n h,\n curveColor,\n selectedIds,\n hoveredElement,\n theme,\n opacity\n );\n\n // 8. Selection box\n if (selectionBox) {\n drawSelectionBox(ctx, selectionBox, curveColor);\n }\n\n ctx.globalAlpha = 1;\n }, [options]);\n\n // Redraw on state changes. The draw callback mutates the canvas DOM element\n // (backing-store size), which the rule reports as modifying `options`; that\n // is a benign DOM side effect, not a props mutation.\n // eslint-disable-next-line react-hooks/immutability\n useEffect(() => {\n if (options.isDragging) {\n // Use rAF for smooth updates during drag\n const render = () => {\n draw();\n rafRef.current = requestAnimationFrame(render);\n };\n rafRef.current = requestAnimationFrame(render);\n return () => {\n cancelAnimationFrame(rafRef.current);\n };\n }\n\n draw();\n return undefined;\n }, [draw, options.isDragging]);\n}\n\n// drawGrid, drawDomainBounds, drawAxisLabels, and formatLabel are now\n// provided by shared canvas primitives and called in the draw() function above.\n\nfunction drawCurveLine(\n ctx: CanvasRenderingContext2D,\n curve: CurveData,\n viewport: CurveViewport,\n w: number,\n h: number,\n curveColor: string,\n lineWidth: number\n): void {\n if (curve.keyframes.length < 2) return;\n\n const samples = sampleCurve(curve, Math.max(200, w));\n if (samples.length === 0) return;\n\n ctx.strokeStyle = curveColor;\n ctx.lineWidth = lineWidth;\n ctx.lineJoin = 'round';\n ctx.lineCap = 'round';\n ctx.beginPath();\n\n const first = samples[0];\n if (!first) return;\n const firstCanvas = domainToCanvas(first.x, first.y, viewport, w, h);\n ctx.moveTo(firstCanvas.px, firstCanvas.py);\n\n for (let i = 1; i < samples.length; i++) {\n const s = samples[i];\n if (!s) continue;\n const sCanvas = domainToCanvas(s.x, s.y, viewport, w, h);\n ctx.lineTo(sCanvas.px, sCanvas.py);\n }\n\n ctx.stroke();\n}\n\nfunction drawTangentHandles(\n ctx: CanvasRenderingContext2D,\n curve: CurveData,\n viewport: CurveViewport,\n w: number,\n h: number,\n selectedIds: Set<string>,\n hoveredElement: CurveHitTest | null,\n theme: CanvasThemeColors,\n opacity: number\n): void {\n const { keyframes } = curve;\n\n for (let i = 0; i < keyframes.length; i++) {\n const kf = keyframes[i];\n if (!kf) continue;\n if (!kf.id || !selectedIds.has(kf.id)) continue;\n if (kf.tangentMode === 'linear' || kf.tangentMode === 'step') continue;\n\n const isAutoMode = kf.tangentMode === 'auto';\n const kfCanvas = domainToCanvas(kf.x, kf.y, viewport, w, h);\n\n // Handle In\n const hIn = domainToCanvas(\n kf.x + kf.handleIn.x,\n kf.y + kf.handleIn.y,\n viewport,\n w,\n h\n );\n\n ctx.globalAlpha = opacity * (isAutoMode ? 0.4 : 0.7);\n ctx.strokeStyle = theme.textMuted;\n ctx.lineWidth = 1;\n ctx.setLineDash([3, 3]);\n ctx.beginPath();\n ctx.moveTo(kfCanvas.px, kfCanvas.py);\n ctx.lineTo(hIn.px, hIn.py);\n ctx.stroke();\n ctx.setLineDash([]);\n\n // Handle In point\n const hInRadius = isAutoMode ? 3 : 4;\n const isHInHovered =\n hoveredElement?.type === 'handleIn' && hoveredElement.keyframeIndex === i;\n ctx.globalAlpha = opacity;\n ctx.fillStyle = isHInHovered ? theme.textPrimary : theme.textSecondary;\n ctx.beginPath();\n ctx.arc(\n hIn.px,\n hIn.py,\n isHInHovered ? hInRadius + 1 : hInRadius,\n 0,\n Math.PI * 2\n );\n ctx.fill();\n\n // Handle Out\n const hOut = domainToCanvas(\n kf.x + kf.handleOut.x,\n kf.y + kf.handleOut.y,\n viewport,\n w,\n h\n );\n\n ctx.globalAlpha = opacity * (isAutoMode ? 0.4 : 0.7);\n ctx.strokeStyle = theme.textMuted;\n ctx.lineWidth = 1;\n ctx.setLineDash([3, 3]);\n ctx.beginPath();\n ctx.moveTo(kfCanvas.px, kfCanvas.py);\n ctx.lineTo(hOut.px, hOut.py);\n ctx.stroke();\n ctx.setLineDash([]);\n\n // Handle Out point\n const hOutRadius = isAutoMode ? 3 : 4;\n const isHOutHovered =\n hoveredElement?.type === 'handleOut' &&\n hoveredElement.keyframeIndex === i;\n ctx.globalAlpha = opacity;\n ctx.fillStyle = isHOutHovered ? theme.textPrimary : theme.textSecondary;\n ctx.beginPath();\n ctx.arc(\n hOut.px,\n hOut.py,\n isHOutHovered ? hOutRadius + 1 : hOutRadius,\n 0,\n Math.PI * 2\n );\n ctx.fill();\n }\n}\n\nfunction drawKeyframes(\n ctx: CanvasRenderingContext2D,\n curve: CurveData,\n viewport: CurveViewport,\n w: number,\n h: number,\n curveColor: string,\n selectedIds: Set<string>,\n hoveredElement: CurveHitTest | null,\n theme: CanvasThemeColors,\n opacity: number\n): void {\n const { keyframes } = curve;\n\n for (let i = 0; i < keyframes.length; i++) {\n const kf = keyframes[i];\n if (!kf) continue;\n const isSelected = kf.id ? selectedIds.has(kf.id) : false;\n const isHovered =\n hoveredElement?.type === 'keyframe' && hoveredElement.keyframeIndex === i;\n const isEndpoint = i === 0 || i === keyframes.length - 1;\n\n const { px, py } = domainToCanvas(kf.x, kf.y, viewport, w, h);\n\n ctx.globalAlpha = opacity;\n\n if (isEndpoint) {\n // Diamond shape for endpoints\n const size = isHovered ? 8 : isSelected ? 7 : 6;\n ctx.beginPath();\n ctx.moveTo(px, py - size);\n ctx.lineTo(px + size, py);\n ctx.lineTo(px, py + size);\n ctx.lineTo(px - size, py);\n ctx.closePath();\n } else {\n // Circle for middle keyframes\n const radius = isHovered ? 8 : isSelected ? 7 : 6;\n ctx.beginPath();\n ctx.arc(px, py, radius, 0, Math.PI * 2);\n }\n\n if (isSelected) {\n ctx.fillStyle = curveColor;\n ctx.fill();\n ctx.strokeStyle = theme.textPrimary;\n ctx.lineWidth = 2;\n ctx.stroke();\n } else {\n ctx.fillStyle = theme.textPrimary;\n ctx.fill();\n ctx.strokeStyle = curveColor;\n ctx.lineWidth = 1.5;\n ctx.stroke();\n }\n }\n}\n\nfunction drawSelectionBox(\n ctx: CanvasRenderingContext2D,\n box: { x1: number; y1: number; x2: number; y2: number },\n curveColor: string\n): void {\n const x = Math.min(box.x1, box.x2);\n const y = Math.min(box.y1, box.y2);\n const bw = Math.abs(box.x2 - box.x1);\n const bh = Math.abs(box.y2 - box.y1);\n\n ctx.globalAlpha = 0.1;\n ctx.fillStyle = curveColor;\n ctx.fillRect(x, y, bw, bh);\n\n ctx.globalAlpha = 0.5;\n ctx.strokeStyle = curveColor;\n ctx.lineWidth = 1;\n ctx.setLineDash([4, 4]);\n ctx.strokeRect(x, y, bw, bh);\n ctx.setLineDash([]);\n}\n"],"names":[],"mappings":";;;;;;AAsDA;AAEM;AACJ;AAEA;AACE;AACA;;;AAGA;;AAEA;AAEA;AAEA;AACA;AACA;;;;AAKA;;AAEE;AACA;AACA;;;AAEA;;AAGF;;AAkBA;AACE;;;;AAMF;AACA;;;;;;AAOI;AACA;;;;AAID;;;;;;AAOC;AACA;;;AAGD;;;;;;;AAiBC;AACA;;;;;AAKD;;;AAIH;AACA;;AAGA;AACE;;;;;;AA6BA;;AAGF;AACF;;;;;;AAOE;;;AAGI;AACA;AACF;AACA;AACA;AACE;AACF;;AAGF;AACA;;AAEJ;AAEA;AACA;AAEA;AASE;;AAEA;AACA;;AAEA;AACA;AACA;AACA;;AAGA;AACA;;AACA;;AAGA;AACE;AACA;;AACA;;;;AAKJ;AAEA;AAWE;AAEA;AACE;AACA;;AACA;;;;AAGA;AACA;;AAGA;AAQA;AACA;AACA;;;;;;AAMA;;;AAIA;AAEA;AACA;;AAEA;;;AAUA;AAQA;AACA;AACA;;;;;;AAMA;;;AAIA;AAEE;AACF;AACA;;AAEA;;;AASJ;AAEA;AAYE;AAEA;AACE;AACA;;;AAEA;AAEA;;AAIA;;;AAIE;;;;;;;;;;AASA;;AAEA;;;AAIA;;AAEA;AACA;;;;AAGA;;AAEA;AACA;;;;AAIN;AAEA;AAKE;AACA;AACA;AACA;AAEA;AACA;;AAGA;AACA;AACA;;;AAGA;AACF;;"}
@@ -89,11 +89,17 @@ import '../../Icons/GitBranchIcon.js';
89
89
  import '../../Icons/BugIcon.js';
90
90
  import '../../Icons/MinusIcon.js';
91
91
  import '../../Icons/PinIcon.js';
92
+ import '../../Icons/OrbitIcon.js';
93
+ import '../../Icons/RotateIcon.js';
94
+ import '../../Icons/SpinIcon.js';
95
+ import '../../Icons/CameraIcon.js';
96
+ import '../../Icons/CubeIcon.js';
97
+ import '../../Icons/ZoomFitIcon.js';
92
98
  import '../../../utils/devWarn.js';
93
99
  import { useMergedRef } from '../../../hooks/useMergedRef/useMergedRef.js';
94
100
  import '../../../hooks/useBreakpoint/useBreakpoint.js';
95
101
  import './../../../assets/src/theme/lightTheme.css.ts.vanilla-Ct6f9Iym.css';
96
- import '../../../node_modules/@vanilla-extract/css/dist/vanilla-extract-css.esm.js';
102
+ import '@vanilla-extract/css';
97
103
  import './../../../assets/src/theme/darkTheme.css.ts.vanilla-BHZ3ecRn.css';
98
104
  import './../../../assets/src/theme/globalScrollbars.css.ts.vanilla-BAJwnUEJ.css';
99
105
  import '../../../hooks/useNavigationHistory/useNavigationHistory.js';
@@ -1 +1 @@
1
- {"version":3,"file":"FileTree.js","sources":["../../../../../../src/components/controls/FileTree/FileTree.tsx"],"sourcesContent":[null],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkCA;AAEA;AACA;AACE;;AAGE;AACE;AACF;AACE;AACF;AACE;AACF;AACE;AACF;AACE;AACF;AACE;AACF;AACA;AACE;;AAEN;AAEA;AACA;;;;;;;AAOI;;;AAGJ;AAEA;;;;;;;;;;;;;;;;;;;;;;;;AAwBG;AACI;AAgCL;;AAGA;;;;AAKE;AACA;AACA;AACE;;;AAGE;AACE;;;AAGN;AACA;;AAEF;AAEA;;AAOA;;AAII;AACA;;;;AAQA;AAAW;AACX;;AAEA;;AAGE;AAEF;AAMF;AAIF;AACE;AAAoB;AACpB;;AAEE;AAAW;;AAEb;;AAGF;AACE;AAAkB;AAClB;;AAEE;AAAU;AACZ;AACF;AAEA;AACE;AAAwB;AACxB;;AAEE;AAAU;AACZ;AACF;AAEA;AACE;AAAwB;AACxB;;AAEE;AAAU;AACZ;AACF;;AA2CF;AAEA;;"}
1
+ {"version":3,"file":"FileTree.js","sources":["src/components/controls/FileTree/FileTree.tsx"],"sourcesContent":["'use client';\n\nimport React, { useCallback, useMemo, useRef } from 'react';\n\nimport { TreeView } from '@/components/controls/TreeView';\nimport {\n ArchiveIcon,\n CodeIcon,\n FileTextIcon,\n FolderIcon,\n FolderOpenIcon,\n ImageIcon,\n PlayIcon,\n} from '@/components/Icons';\nimport { useMergedRef } from '@/hooks';\nimport { cx } from '@/utils/cx';\n\nimport { containerStyle, iconStyle, labelStyle } from './FileTree.css';\nimport { getFileIconKind } from './fileTreeIcons';\nimport { DEFAULT_FILE_TREE_LABELS } from './fileTreeLabels';\nimport { useFileTreeDrop } from './useFileTreeDrop';\n\nimport type {\n FileTreeNode,\n FileTreeNodeState,\n FileTreeProps,\n} from './FileTree.types';\nimport type { FileIconKind } from './fileTreeIcons';\nimport type {\n TreeNodeData,\n TreeNodeState,\n TreeViewSize,\n} from '@/components/controls/TreeView';\n\nconst ICON_PX: Record<TreeViewSize, number> = { sm: 12, md: 14, lg: 16 };\n\n/** Renders a resolved {@link FileIconKind} into a themed icon element. */\nfunction fileIconForKind(kind: FileIconKind, px: number): React.ReactElement {\n const color =\n kind === 'folder' || kind === 'folder-open' ? 'secondary' : 'muted';\n switch (kind) {\n case 'folder':\n return <FolderIcon size={px} color={color} decorative />;\n case 'folder-open':\n return <FolderOpenIcon size={px} color={color} decorative />;\n case 'image':\n return <ImageIcon size={px} color={color} decorative />;\n case 'media':\n return <PlayIcon size={px} color={color} decorative />;\n case 'code':\n return <CodeIcon size={px} color={color} decorative />;\n case 'archive':\n return <ArchiveIcon size={px} color={color} decorative />;\n case 'text':\n case 'file':\n return <FileTextIcon size={px} color={color} decorative />;\n }\n}\n\n/** Maps a FileTreeNode subtree to TreeView's node shape. */\nfunction toTreeNode(node: FileTreeNode): TreeNodeData {\n return {\n id: node.id,\n label: node.name,\n children: node.children?.map(toTreeNode),\n disabled: node.disabled,\n // Folders accept dropped files; files do not (the field finally gets used).\n droppable: node.kind === 'folder',\n data: node.data,\n };\n}\n\n/**\n * A file-system-flavored specialization of {@link TreeView}: it maps a\n * file/folder model onto the tree, auto-assigns file-type icons (by extension)\n * and open/closed folder glyphs, and adds drag-and-drop import of OS files onto\n * a folder (or the root). Expansion, selection, keyboard navigation, and sizing\n * are inherited from TreeView unchanged.\n *\n * @example\n * ```tsx\n * const nodes: FileTreeNode[] = [\n * { id: 'src', name: 'src', kind: 'folder', children: [\n * { id: 'btn', name: 'Button.tsx', kind: 'file' },\n * { id: 'logo', name: 'logo.svg', kind: 'file' },\n * ]},\n * ];\n *\n * <FileTree\n * nodes={nodes}\n * defaultExpandedIds={['src']}\n * onImport={({ files, targetFolder }) =>\n * uploadInto(targetFolder?.id ?? 'root', files)\n * }\n * />\n * ```\n */\nexport const FileTree = ({\n nodes,\n expandedIds,\n defaultExpandedIds,\n onExpandedChange,\n selectedIds,\n defaultSelectedIds,\n selectionMode = 'single',\n onSelectionChange,\n size = 'md',\n indent,\n showChevrons,\n showGuideLines,\n expandOnClick = true,\n maxHeight,\n resolveIcon,\n renderNode,\n renderActions,\n emptyContent,\n onImport,\n onNodeClick,\n onNodeDoubleClick,\n onNodeContextMenu,\n labels: labelsProp,\n className,\n style,\n testId,\n ref,\n 'aria-label': ariaLabel,\n 'aria-labelledby': ariaLabelledby,\n ...rest\n}: FileTreeProps) => {\n const containerRef = useRef<HTMLDivElement>(null);\n const setContainerRef = useMergedRef(containerRef, ref);\n\n const treeNodes = useMemo(() => nodes.map(toTreeNode), [nodes]);\n\n // id → node and id → parent folder, used to resolve drop targets and to\n // recover the original FileTreeNode inside render / event callbacks.\n const { nodeById, parentFolderById } = useMemo(() => {\n const byId = new Map<string, FileTreeNode>();\n const parentById = new Map<string, FileTreeNode | null>();\n const walk = (list: FileTreeNode[], parent: FileTreeNode | null): void => {\n for (const node of list) {\n byId.set(node.id, node);\n parentById.set(node.id, parent);\n if (node.children) {\n walk(node.children, node.kind === 'folder' ? node : parent);\n }\n }\n };\n walk(nodes, null);\n return { nodeById: byId, parentFolderById: parentById };\n }, [nodes]);\n\n const dnd = useFileTreeDrop({ onImport, nodeById, parentFolderById });\n\n const labels = useMemo(\n () => ({ ...DEFAULT_FILE_TREE_LABELS, ...labelsProp }),\n [labelsProp]\n );\n\n const iconPx = ICON_PX[size];\n\n const toFileState = useCallback(\n (state: TreeNodeState, node: FileTreeNode): FileTreeNodeState => ({\n ...state,\n isFolder: node.kind === 'folder',\n }),\n []\n );\n\n const renderTreeNode = useCallback(\n (treeNode: TreeNodeData, state: TreeNodeState): React.ReactNode => {\n const node = nodeById.get(treeNode.id);\n if (!node) return null;\n if (renderNode) return renderNode(node, toFileState(state, node));\n\n const overrideIcon = resolveIcon?.(node, { expanded: state.expanded });\n const icon =\n overrideIcon ??\n fileIconForKind(getFileIconKind(node, state.expanded), iconPx);\n\n return (\n <>\n <span className={iconStyle({ size })}>{icon}</span>\n <span className={labelStyle({ size })}>{node.name}</span>\n </>\n );\n },\n [nodeById, renderNode, resolveIcon, toFileState, iconPx, size]\n );\n\n const renderTreeActions = useMemo(() => {\n if (!renderActions) return undefined;\n return (treeNode: TreeNodeData, state: TreeNodeState): React.ReactNode => {\n const node = nodeById.get(treeNode.id);\n if (!node) return null;\n return renderActions(node, toFileState(state, node));\n };\n }, [renderActions, nodeById, toFileState]);\n\n const handleNodeClick = useMemo(() => {\n if (!onNodeClick) return undefined;\n return (treeNode: TreeNodeData, event: React.MouseEvent): void => {\n const node = nodeById.get(treeNode.id);\n if (node) onNodeClick(node, event);\n };\n }, [onNodeClick, nodeById]);\n\n const handleNodeDoubleClick = useMemo(() => {\n if (!onNodeDoubleClick) return undefined;\n return (treeNode: TreeNodeData, event: React.MouseEvent): void => {\n const node = nodeById.get(treeNode.id);\n if (node) onNodeDoubleClick(node, event);\n };\n }, [onNodeDoubleClick, nodeById]);\n\n const handleNodeContextMenu = useMemo(() => {\n if (!onNodeContextMenu) return undefined;\n return (treeNode: TreeNodeData, event: React.MouseEvent): void => {\n const node = nodeById.get(treeNode.id);\n if (node) onNodeContextMenu(node, event);\n };\n }, [onNodeContextMenu, nodeById]);\n\n return (\n <div\n ref={setContainerRef}\n className={cx(containerStyle, className)}\n style={style}\n data-testid={testId}\n data-root-active={dnd.enabled && dnd.rootActive ? 'true' : undefined}\n {...rest}\n onDragOver={dnd.enabled ? dnd.onRootDragOver : undefined}\n onDragLeave={dnd.enabled ? dnd.onRootDragLeave : undefined}\n onDrop={dnd.enabled ? dnd.onRootDrop : undefined}\n >\n <TreeView\n nodes={treeNodes}\n expandedIds={expandedIds}\n defaultExpandedIds={defaultExpandedIds}\n onExpandedChange={onExpandedChange}\n selectedIds={selectedIds}\n defaultSelectedIds={defaultSelectedIds}\n selectionMode={selectionMode}\n onSelectionChange={onSelectionChange}\n size={size}\n indent={indent}\n showChevrons={showChevrons}\n showGuideLines={showGuideLines}\n expandOnClick={expandOnClick}\n maxHeight={maxHeight}\n renderNode={renderTreeNode}\n renderActions={renderTreeActions}\n emptyContent={emptyContent ?? labels.emptyLabel}\n aria-label={ariaLabel ?? labels.treeLabel}\n aria-labelledby={ariaLabelledby}\n dropTargetId={dnd.enabled ? dnd.dropTargetId : undefined}\n onNodeClick={handleNodeClick}\n onNodeDoubleClick={handleNodeDoubleClick}\n onNodeContextMenu={handleNodeContextMenu}\n onNodeDragOver={dnd.enabled ? dnd.onNodeDragOver : undefined}\n onNodeDrop={dnd.enabled ? dnd.onNodeDrop : undefined}\n />\n </div>\n );\n};\n\nFileTree.displayName = 'FileTree';\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkCA;AAEA;AACA;AACE;;AAGE;AACE;AACF;AACE;AACF;AACE;AACF;AACE;AACF;AACE;AACF;AACE;AACF;AACA;AACE;;AAEN;AAEA;AACA;;;;;;;AAOI;;;AAGJ;AAEA;;;;;;;;;;;;;;;;;;;;;;;;AAwBG;AACI;AAgCL;;AAGA;;;;AAKE;AACA;AACA;AACE;;;AAGE;AACE;;;AAGN;AACA;;AAEF;AAEA;;AAOA;;AAII;AACA;;;;AAQA;AAAW;AACX;;AAEA;;AAGE;AAEF;AAMF;AAIF;AACE;AAAoB;AACpB;;AAEE;AAAW;;AAEb;;AAGF;AACE;AAAkB;AAClB;;AAEE;AAAU;AACZ;AACF;AAEA;AACE;AAAwB;AACxB;;AAEE;AAAU;AACZ;AACF;AAEA;AACE;AAAwB;AACxB;;AAEE;AAAU;AACZ;AACF;;AA2CF;AAEA;;"}
@@ -1 +1 @@
1
- {"version":3,"file":"fileTreeIcons.js","sources":["../../../../../../src/components/controls/FileTree/fileTreeIcons.ts"],"sourcesContent":[null],"names":[],"mappings":"AAmBA;;;AAGG;AACH,MAAM,iBAAiB,GAA2C;;AAEhE,IAAA,GAAG,EAAE,OAAO;AACZ,IAAA,GAAG,EAAE,OAAO;AACZ,IAAA,IAAI,EAAE,OAAO;AACb,IAAA,GAAG,EAAE,OAAO;AACZ,IAAA,IAAI,EAAE,OAAO;AACb,IAAA,IAAI,EAAE,OAAO;AACb,IAAA,GAAG,EAAE,OAAO;AACZ,IAAA,GAAG,EAAE,OAAO;AACZ,IAAA,GAAG,EAAE,OAAO;AACZ,IAAA,GAAG,EAAE,OAAO;AACZ,IAAA,IAAI,EAAE,OAAO;AACb,IAAA,GAAG,EAAE,OAAO;;AAGZ,IAAA,GAAG,EAAE,OAAO;AACZ,IAAA,GAAG,EAAE,OAAO;AACZ,IAAA,GAAG,EAAE,OAAO;AACZ,IAAA,IAAI,EAAE,OAAO;AACb,IAAA,GAAG,EAAE,OAAO;AACZ,IAAA,GAAG,EAAE,OAAO;AACZ,IAAA,GAAG,EAAE,OAAO;AACZ,IAAA,GAAG,EAAE,OAAO;AACZ,IAAA,GAAG,EAAE,OAAO;AACZ,IAAA,GAAG,EAAE,OAAO;AACZ,IAAA,IAAI,EAAE,OAAO;;AAGb,IAAA,EAAE,EAAE,MAAM;AACV,IAAA,GAAG,EAAE,MAAM;AACX,IAAA,EAAE,EAAE,MAAM;AACV,IAAA,GAAG,EAAE,MAAM;AACX,IAAA,GAAG,EAAE,MAAM;AACX,IAAA,GAAG,EAAE,MAAM;AACX,IAAA,IAAI,EAAE,MAAM;AACZ,IAAA,KAAK,EAAE,MAAM;AACb,IAAA,IAAI,EAAE,MAAM;AACZ,IAAA,GAAG,EAAE,MAAM;AACX,IAAA,GAAG,EAAE,MAAM;AACX,IAAA,IAAI,EAAE,MAAM;AACZ,IAAA,IAAI,EAAE,MAAM;AACZ,IAAA,IAAI,EAAE,MAAM;AACZ,IAAA,GAAG,EAAE,MAAM;AACX,IAAA,MAAM,EAAE,MAAM;AACd,IAAA,KAAK,EAAE,MAAM;AACb,IAAA,EAAE,EAAE,MAAM;AACV,IAAA,EAAE,EAAE,MAAM;AACV,IAAA,EAAE,EAAE,MAAM;AACV,IAAA,EAAE,EAAE,MAAM;AACV,IAAA,IAAI,EAAE,MAAM;AACZ,IAAA,EAAE,EAAE,MAAM;AACV,IAAA,KAAK,EAAE,MAAM;AACb,IAAA,CAAC,EAAE,MAAM;AACT,IAAA,CAAC,EAAE,MAAM;AACT,IAAA,GAAG,EAAE,MAAM;AACX,IAAA,EAAE,EAAE,MAAM;AACV,IAAA,GAAG,EAAE,MAAM;AACX,IAAA,EAAE,EAAE,MAAM;AACV,IAAA,GAAG,EAAE,MAAM;AACX,IAAA,EAAE,EAAE,MAAM;AACV,IAAA,IAAI,EAAE,MAAM;AACZ,IAAA,GAAG,EAAE,MAAM;AACX,IAAA,GAAG,EAAE,MAAM;AACX,IAAA,IAAI,EAAE,MAAM;AACZ,IAAA,IAAI,EAAE,MAAM;AACZ,IAAA,GAAG,EAAE,MAAM;AACX,IAAA,GAAG,EAAE,MAAM;AACX,IAAA,OAAO,EAAE,MAAM;AACf,IAAA,GAAG,EAAE,MAAM;AACX,IAAA,GAAG,EAAE,MAAM;AACX,IAAA,IAAI,EAAE,MAAM;AACZ,IAAA,CAAC,EAAE,MAAM;;AAGT,IAAA,GAAG,EAAE,SAAS;AACd,IAAA,GAAG,EAAE,SAAS;AACd,IAAA,IAAI,EAAE,SAAS;AACf,IAAA,GAAG,EAAE,SAAS;AACd,IAAA,EAAE,EAAE,SAAS;AACb,IAAA,GAAG,EAAE,SAAS;AACd,IAAA,GAAG,EAAE,SAAS;AACd,IAAA,EAAE,EAAE,SAAS;;AAGb,IAAA,GAAG,EAAE,MAAM;AACX,IAAA,EAAE,EAAE,MAAM;AACV,IAAA,GAAG,EAAE,MAAM;AACX,IAAA,GAAG,EAAE,MAAM;AACX,IAAA,GAAG,EAAE,MAAM;AACX,IAAA,GAAG,EAAE,MAAM;AACX,IAAA,IAAI,EAAE,MAAM;AACZ,IAAA,GAAG,EAAE,MAAM;AACX,IAAA,GAAG,EAAE,MAAM;AACX,IAAA,GAAG,EAAE,MAAM;CACZ;AAED;;;;AAIG;AACG,SAAU,gBAAgB,CAAC,IAAY,EAAA;IAC3C,MAAM,GAAG,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC;IACjC,IAAI,GAAG,IAAI,CAAC,IAAI,GAAG,KAAK,IAAI,CAAC,MAAM,GAAG,CAAC;AAAE,QAAA,OAAO,SAAS;IACzD,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,WAAW,EAAE;AAC1C;AAEA;;AAEG;AACG,SAAU,iBAAiB,CAAC,GAAuB,EAAA;IACvD,IAAI,GAAG,KAAK,SAAS;AAAE,QAAA,OAAO,MAAM;IACpC,OAAO,iBAAiB,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC,IAAI,MAAM;AACvD;AAEA;;;;AAIG;SACa,eAAe,CAC7B,IAAiD,EACjD,QAAQ,GAAG,KAAK,EAAA;AAEhB,IAAA,IAAI,IAAI,CAAC,IAAI,KAAK,QAAQ;QAAE,OAAO,QAAQ,GAAG,aAAa,GAAG,QAAQ;AACtE,IAAA,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC;AACnD,IAAA,OAAO,iBAAiB,CAAC,GAAG,CAAC;AAC/B;;;;"}
1
+ {"version":3,"file":"fileTreeIcons.js","sources":["src/components/controls/FileTree/fileTreeIcons.ts"],"sourcesContent":["import type { FileTreeNode } from './FileTree.types';\n\n/**\n * The icon categories FileTree resolves a node to. Folders get an open/closed\n * pair; files collapse to a small set of type buckets backed by the existing\n * icon set. Rendering a kind into an actual icon element lives in the client\n * component — this module is pure classification so it can be unit-tested\n * without React.\n */\nexport type FileIconKind =\n | 'folder'\n | 'folder-open'\n | 'image'\n | 'media'\n | 'code'\n | 'archive'\n | 'text'\n | 'file';\n\n/**\n * Maps a lowercased file extension (no dot) to an icon bucket. Unknown\n * extensions fall through to the generic `file` glyph.\n */\nconst KIND_BY_EXTENSION: Readonly<Record<string, FileIconKind>> = {\n // Images / raster + vector assets\n png: 'image',\n jpg: 'image',\n jpeg: 'image',\n gif: 'image',\n webp: 'image',\n avif: 'image',\n bmp: 'image',\n ico: 'image',\n svg: 'image',\n tif: 'image',\n tiff: 'image',\n psd: 'image',\n\n // Media (audio + video share the play glyph)\n mp3: 'media',\n wav: 'media',\n ogg: 'media',\n flac: 'media',\n aac: 'media',\n m4a: 'media',\n mp4: 'media',\n mov: 'media',\n avi: 'media',\n mkv: 'media',\n webm: 'media',\n\n // Code / markup / config / data\n js: 'code',\n jsx: 'code',\n ts: 'code',\n tsx: 'code',\n mjs: 'code',\n cjs: 'code',\n json: 'code',\n json5: 'code',\n html: 'code',\n htm: 'code',\n css: 'code',\n scss: 'code',\n sass: 'code',\n less: 'code',\n vue: 'code',\n svelte: 'code',\n astro: 'code',\n py: 'code',\n rb: 'code',\n go: 'code',\n rs: 'code',\n java: 'code',\n kt: 'code',\n swift: 'code',\n c: 'code',\n h: 'code',\n cpp: 'code',\n cc: 'code',\n hpp: 'code',\n cs: 'code',\n php: 'code',\n sh: 'code',\n bash: 'code',\n zsh: 'code',\n yml: 'code',\n yaml: 'code',\n toml: 'code',\n xml: 'code',\n sql: 'code',\n graphql: 'code',\n gql: 'code',\n lua: 'code',\n dart: 'code',\n r: 'code',\n\n // Archives\n zip: 'archive',\n rar: 'archive',\n '7z': 'archive',\n tar: 'archive',\n gz: 'archive',\n tgz: 'archive',\n bz2: 'archive',\n xz: 'archive',\n\n // Text / documents\n txt: 'text',\n md: 'text',\n mdx: 'text',\n rtf: 'text',\n pdf: 'text',\n doc: 'text',\n docx: 'text',\n csv: 'text',\n tsv: 'text',\n log: 'text',\n};\n\n/**\n * Extracts the lowercased extension (without the dot) from a file name, or\n * `undefined` when there isn't one. Dotfiles such as `.env` / `.gitignore` are\n * treated as having no extension (the leading dot is not a separator).\n */\nexport function getFileExtension(name: string): string | undefined {\n const dot = name.lastIndexOf('.');\n if (dot <= 0 || dot === name.length - 1) return undefined;\n return name.slice(dot + 1).toLowerCase();\n}\n\n/**\n * Classifies an extension into an icon bucket. `undefined` / unknown → `file`.\n */\nexport function classifyExtension(ext: string | undefined): FileIconKind {\n if (ext === undefined) return 'file';\n return KIND_BY_EXTENSION[ext.toLowerCase()] ?? 'file';\n}\n\n/**\n * Resolves the icon kind for a node. Folders return `folder-open` when\n * `expanded`, else `folder`. Files use the explicit `ext` when provided, falling\n * back to the extension parsed from `name`.\n */\nexport function getFileIconKind(\n node: Pick<FileTreeNode, 'kind' | 'name' | 'ext'>,\n expanded = false\n): FileIconKind {\n if (node.kind === 'folder') return expanded ? 'folder-open' : 'folder';\n const ext = node.ext ?? getFileExtension(node.name);\n return classifyExtension(ext);\n}\n"],"names":[],"mappings":"AAmBA;;;AAGG;AACH,MAAM,iBAAiB,GAA2C;;AAEhE,IAAA,GAAG,EAAE,OAAO;AACZ,IAAA,GAAG,EAAE,OAAO;AACZ,IAAA,IAAI,EAAE,OAAO;AACb,IAAA,GAAG,EAAE,OAAO;AACZ,IAAA,IAAI,EAAE,OAAO;AACb,IAAA,IAAI,EAAE,OAAO;AACb,IAAA,GAAG,EAAE,OAAO;AACZ,IAAA,GAAG,EAAE,OAAO;AACZ,IAAA,GAAG,EAAE,OAAO;AACZ,IAAA,GAAG,EAAE,OAAO;AACZ,IAAA,IAAI,EAAE,OAAO;AACb,IAAA,GAAG,EAAE,OAAO;;AAGZ,IAAA,GAAG,EAAE,OAAO;AACZ,IAAA,GAAG,EAAE,OAAO;AACZ,IAAA,GAAG,EAAE,OAAO;AACZ,IAAA,IAAI,EAAE,OAAO;AACb,IAAA,GAAG,EAAE,OAAO;AACZ,IAAA,GAAG,EAAE,OAAO;AACZ,IAAA,GAAG,EAAE,OAAO;AACZ,IAAA,GAAG,EAAE,OAAO;AACZ,IAAA,GAAG,EAAE,OAAO;AACZ,IAAA,GAAG,EAAE,OAAO;AACZ,IAAA,IAAI,EAAE,OAAO;;AAGb,IAAA,EAAE,EAAE,MAAM;AACV,IAAA,GAAG,EAAE,MAAM;AACX,IAAA,EAAE,EAAE,MAAM;AACV,IAAA,GAAG,EAAE,MAAM;AACX,IAAA,GAAG,EAAE,MAAM;AACX,IAAA,GAAG,EAAE,MAAM;AACX,IAAA,IAAI,EAAE,MAAM;AACZ,IAAA,KAAK,EAAE,MAAM;AACb,IAAA,IAAI,EAAE,MAAM;AACZ,IAAA,GAAG,EAAE,MAAM;AACX,IAAA,GAAG,EAAE,MAAM;AACX,IAAA,IAAI,EAAE,MAAM;AACZ,IAAA,IAAI,EAAE,MAAM;AACZ,IAAA,IAAI,EAAE,MAAM;AACZ,IAAA,GAAG,EAAE,MAAM;AACX,IAAA,MAAM,EAAE,MAAM;AACd,IAAA,KAAK,EAAE,MAAM;AACb,IAAA,EAAE,EAAE,MAAM;AACV,IAAA,EAAE,EAAE,MAAM;AACV,IAAA,EAAE,EAAE,MAAM;AACV,IAAA,EAAE,EAAE,MAAM;AACV,IAAA,IAAI,EAAE,MAAM;AACZ,IAAA,EAAE,EAAE,MAAM;AACV,IAAA,KAAK,EAAE,MAAM;AACb,IAAA,CAAC,EAAE,MAAM;AACT,IAAA,CAAC,EAAE,MAAM;AACT,IAAA,GAAG,EAAE,MAAM;AACX,IAAA,EAAE,EAAE,MAAM;AACV,IAAA,GAAG,EAAE,MAAM;AACX,IAAA,EAAE,EAAE,MAAM;AACV,IAAA,GAAG,EAAE,MAAM;AACX,IAAA,EAAE,EAAE,MAAM;AACV,IAAA,IAAI,EAAE,MAAM;AACZ,IAAA,GAAG,EAAE,MAAM;AACX,IAAA,GAAG,EAAE,MAAM;AACX,IAAA,IAAI,EAAE,MAAM;AACZ,IAAA,IAAI,EAAE,MAAM;AACZ,IAAA,GAAG,EAAE,MAAM;AACX,IAAA,GAAG,EAAE,MAAM;AACX,IAAA,OAAO,EAAE,MAAM;AACf,IAAA,GAAG,EAAE,MAAM;AACX,IAAA,GAAG,EAAE,MAAM;AACX,IAAA,IAAI,EAAE,MAAM;AACZ,IAAA,CAAC,EAAE,MAAM;;AAGT,IAAA,GAAG,EAAE,SAAS;AACd,IAAA,GAAG,EAAE,SAAS;AACd,IAAA,IAAI,EAAE,SAAS;AACf,IAAA,GAAG,EAAE,SAAS;AACd,IAAA,EAAE,EAAE,SAAS;AACb,IAAA,GAAG,EAAE,SAAS;AACd,IAAA,GAAG,EAAE,SAAS;AACd,IAAA,EAAE,EAAE,SAAS;;AAGb,IAAA,GAAG,EAAE,MAAM;AACX,IAAA,EAAE,EAAE,MAAM;AACV,IAAA,GAAG,EAAE,MAAM;AACX,IAAA,GAAG,EAAE,MAAM;AACX,IAAA,GAAG,EAAE,MAAM;AACX,IAAA,GAAG,EAAE,MAAM;AACX,IAAA,IAAI,EAAE,MAAM;AACZ,IAAA,GAAG,EAAE,MAAM;AACX,IAAA,GAAG,EAAE,MAAM;AACX,IAAA,GAAG,EAAE,MAAM;CACZ;AAED;;;;AAIG;AACG,SAAU,gBAAgB,CAAC,IAAY,EAAA;IAC3C,MAAM,GAAG,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC;IACjC,IAAI,GAAG,IAAI,CAAC,IAAI,GAAG,KAAK,IAAI,CAAC,MAAM,GAAG,CAAC;AAAE,QAAA,OAAO,SAAS;IACzD,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,WAAW,EAAE;AAC1C;AAEA;;AAEG;AACG,SAAU,iBAAiB,CAAC,GAAuB,EAAA;IACvD,IAAI,GAAG,KAAK,SAAS;AAAE,QAAA,OAAO,MAAM;IACpC,OAAO,iBAAiB,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC,IAAI,MAAM;AACvD;AAEA;;;;AAIG;SACa,eAAe,CAC7B,IAAiD,EACjD,QAAQ,GAAG,KAAK,EAAA;AAEhB,IAAA,IAAI,IAAI,CAAC,IAAI,KAAK,QAAQ;QAAE,OAAO,QAAQ,GAAG,aAAa,GAAG,QAAQ;AACtE,IAAA,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC;AACnD,IAAA,OAAO,iBAAiB,CAAC,GAAG,CAAC;AAC/B;;;;"}
@@ -1 +1 @@
1
- {"version":3,"file":"fileTreeLabels.js","sources":["../../../../../../src/components/controls/FileTree/fileTreeLabels.ts"],"sourcesContent":[null],"names":[],"mappings":"AAeA;AACO,MAAM,wBAAwB,GAAmB;AACtD,IAAA,SAAS,EAAE,WAAW;AACtB,IAAA,UAAU,EAAE,UAAU;;;;;"}
1
+ {"version":3,"file":"fileTreeLabels.js","sources":["src/components/controls/FileTree/fileTreeLabels.ts"],"sourcesContent":["/**\n * Localizable strings for {@link FileTree}. Every user-facing / screen-reader\n * string the component renders on its own goes through here so the whole\n * component can be localized in one place (file and folder names come from your\n * data, so they are already yours to localize).\n *\n * @see DEFAULT_FILE_TREE_LABELS for the English defaults.\n */\nexport interface FileTreeLabels {\n /** Accessible name for the tree region (`role=\"tree\"` `aria-label`). */\n treeLabel: string;\n /** Text shown when the tree is empty (unless `emptyContent` is provided). */\n emptyLabel: string;\n}\n\n/** English defaults for {@link FileTreeLabels}. Spread-and-tweak to localize. */\nexport const DEFAULT_FILE_TREE_LABELS: FileTreeLabels = {\n treeLabel: 'File tree',\n emptyLabel: 'No files',\n};\n"],"names":[],"mappings":"AAeA;AACO,MAAM,wBAAwB,GAAmB;AACtD,IAAA,SAAS,EAAE,WAAW;AACtB,IAAA,UAAU,EAAE,UAAU;;;;;"}
@@ -4,7 +4,7 @@ import '../../../utils/devWarn.js';
4
4
  import { useLatest } from '../../../hooks/useLatest/useLatest.js';
5
5
  import '../../../hooks/useBreakpoint/useBreakpoint.js';
6
6
  import './../../../assets/src/theme/lightTheme.css.ts.vanilla-Ct6f9Iym.css';
7
- import '../../../node_modules/@vanilla-extract/css/dist/vanilla-extract-css.esm.js';
7
+ import '@vanilla-extract/css';
8
8
  import './../../../assets/src/theme/darkTheme.css.ts.vanilla-BHZ3ecRn.css';
9
9
  import 'react/jsx-runtime';
10
10
  import './../../../assets/src/theme/globalScrollbars.css.ts.vanilla-BAJwnUEJ.css';
@@ -1 +1 @@
1
- {"version":3,"file":"useFileTreeDrop.js","sources":["../../../../../../src/components/controls/FileTree/useFileTreeDrop.ts"],"sourcesContent":[null],"names":[],"mappings":";;;;;;;;;;;;AAoCA;AACA;AACE;AACF;AAEA;AACA;AACE;AACA;AACF;AAEA;;AAEA;AAEA;;;;;;AAMG;AACG;;;;AAWJ;AACA;AACA;AAEA;AAEA;AAC8C;AAC9C;;AAGI;AAA6B;;AAE/B;AAIF;;;AAMA;AACE;;;;;;;;AASE;AACA;;;AAOA;;;AAEA;AACA;AACE;;;;;AAKF;AACA;;AAKJ;;;;AAII;;AAEF;AAIF;AAEI;;AACA;AACF;AAIF;AAEI;;;AAEA;AACA;AACE;;;;;AAKF;AACF;;;AAMA;;;;;;;;AAQJ;;"}
1
+ {"version":3,"file":"useFileTreeDrop.js","sources":["src/components/controls/FileTree/useFileTreeDrop.ts"],"sourcesContent":["'use client';\n\nimport { useCallback, useState } from 'react';\n\nimport { useLatest } from '@/hooks';\n\nimport type { FileTreeImportPayload, FileTreeNode } from './FileTree.types';\nimport type { TreeNodeData } from '@/components/controls/TreeView';\nimport type { DragEvent } from 'react';\n\nexport interface UseFileTreeDropOptions {\n /** Import callback. Drop wiring is inert while this is `undefined`. */\n onImport?: (payload: FileTreeImportPayload) => void;\n /** Node lookup by id (the original `FileTreeNode`, recovered from TreeView). */\n nodeById: ReadonlyMap<string, FileTreeNode>;\n /** Parent folder lookup by node id. `null` means the node lives at the root. */\n parentFolderById: ReadonlyMap<string, FileTreeNode | null>;\n}\n\nexport interface UseFileTreeDropReturn {\n /** Whether import drop wiring is active (`onImport` provided). */\n enabled: boolean;\n /** Folder id to highlight as the drop target (drives TreeView `dropTargetId`). */\n dropTargetId: string | null;\n /** Whether the tree root (empty area) is the active drop target. */\n rootActive: boolean;\n /** Fired by TreeView on `dragover` over a node row. */\n onNodeDragOver: (node: TreeNodeData, event: DragEvent) => void;\n /** Fired by TreeView on `drop` onto a node row. */\n onNodeDrop: (node: TreeNodeData, event: DragEvent) => void;\n /** Container-level handlers for root / empty-area drops. */\n onRootDragOver: (event: DragEvent) => void;\n onRootDragLeave: (event: DragEvent) => void;\n onRootDrop: (event: DragEvent) => void;\n}\n\n/** True when the drag carries OS files (vs. a text/element drag). */\nfunction hasFiles(event: DragEvent): boolean {\n return Array.from(event.dataTransfer.types).includes('Files');\n}\n\n/** True when the drag is leaving the element entirely (not into a descendant). */\nfunction leftElement(event: DragEvent): boolean {\n const related = event.relatedTarget as Node | null;\n return !(related && event.currentTarget.contains(related));\n}\n\nfunction extractFiles(event: DragEvent): File[] {\n return Array.from(event.dataTransfer.files);\n}\n\n/**\n * External file-import drag-and-drop for FileTree. Per-node `dragover` resolves\n * (and highlights) the destination folder and stops propagation; the container\n * handles drops on empty space (→ root) and clears the highlight when the drag\n * leaves the tree, so there's no row-to-row flicker. All handlers have stable\n * identities and read the latest props through `useLatest` (patterns #3 / #11).\n */\nexport function useFileTreeDrop({\n onImport,\n nodeById,\n parentFolderById,\n}: UseFileTreeDropOptions): UseFileTreeDropReturn {\n // `null` = no active target; otherwise the destination folder id (`null`\n // folderId = the tree root).\n const [target, setTarget] = useState<{ folderId: string | null } | null>(\n null\n );\n\n const onImportRef = useLatest(onImport);\n const nodeByIdRef = useLatest(nodeById);\n const parentFolderByIdRef = useLatest(parentFolderById);\n\n const enabled = onImport !== undefined;\n\n /** The folder a drop on `node` should target (a folder targets itself; a file\n * targets its parent folder, or the root). */\n const resolveTargetFolder = useCallback(\n (nodeId: string): FileTreeNode | null => {\n const node = nodeByIdRef.current.get(nodeId);\n if (node?.kind === 'folder') return node;\n return parentFolderByIdRef.current.get(nodeId) ?? null;\n },\n [nodeByIdRef, parentFolderByIdRef]\n );\n\n const setTargetFolder = useCallback((folderId: string | null) => {\n setTarget(prev =>\n prev !== null && prev.folderId === folderId ? prev : { folderId }\n );\n }, []);\n\n const clearTarget = useCallback(() => {\n setTarget(prev => (prev === null ? prev : null));\n }, []);\n\n const onNodeDragOver = useCallback(\n (node: TreeNodeData, event: DragEvent): void => {\n if (onImportRef.current === undefined || !hasFiles(event)) return;\n event.preventDefault();\n // Owned by the row — keep the container from re-targeting the root.\n event.stopPropagation();\n event.dataTransfer.dropEffect = 'copy';\n setTargetFolder(resolveTargetFolder(node.id)?.id ?? null);\n },\n [onImportRef, resolveTargetFolder, setTargetFolder]\n );\n\n const onNodeDrop = useCallback(\n (node: TreeNodeData, event: DragEvent): void => {\n const onImportCb = onImportRef.current;\n if (onImportCb === undefined) return;\n const files = extractFiles(event);\n if (files.length === 0) {\n clearTarget();\n return;\n }\n event.preventDefault();\n event.stopPropagation();\n onImportCb({ files, targetFolder: resolveTargetFolder(node.id) });\n clearTarget();\n },\n [onImportRef, resolveTargetFolder, clearTarget]\n );\n\n const onRootDragOver = useCallback(\n (event: DragEvent): void => {\n if (onImportRef.current === undefined || !hasFiles(event)) return;\n event.preventDefault();\n event.dataTransfer.dropEffect = 'copy';\n setTargetFolder(null);\n },\n [onImportRef, setTargetFolder]\n );\n\n const onRootDragLeave = useCallback(\n (event: DragEvent): void => {\n if (!leftElement(event)) return;\n clearTarget();\n },\n [clearTarget]\n );\n\n const onRootDrop = useCallback(\n (event: DragEvent): void => {\n const onImportCb = onImportRef.current;\n if (onImportCb === undefined) return;\n const files = extractFiles(event);\n if (files.length === 0) {\n clearTarget();\n return;\n }\n event.preventDefault();\n onImportCb({ files, targetFolder: null });\n clearTarget();\n },\n [onImportRef, clearTarget]\n );\n\n return {\n enabled,\n dropTargetId: target?.folderId ?? null,\n rootActive: target !== null && target.folderId === null,\n onNodeDragOver,\n onNodeDrop,\n onRootDragOver,\n onRootDragLeave,\n onRootDrop,\n };\n}\n"],"names":[],"mappings":";;;;;;;;;;;;AAoCA;AACA;AACE;AACF;AAEA;AACA;AACE;AACA;AACF;AAEA;;AAEA;AAEA;;;;;;AAMG;AACG;;;;AAWJ;AACA;AACA;AAEA;AAEA;AAC8C;AAC9C;;AAGI;AAA6B;;AAE/B;AAIF;;;AAMA;AACE;;;;;;;;AASE;AACA;;;AAOA;;;AAEA;AACA;AACE;;;;;AAKF;AACA;;AAKJ;;;;AAII;;AAEF;AAIF;AAEI;;AACA;AACF;AAIF;AAEI;;;AAEA;AACA;AACE;;;;;AAKF;AACF;;;AAMA;;;;;;;;AAQJ;;"}
@@ -1 +1 @@
1
- {"version":3,"file":"FileUploader.js","sources":["../../../../../../src/components/controls/FileUploader/FileUploader.tsx"],"sourcesContent":[null],"names":[],"mappings":";;;;;;;;;;;;AAmCA;AACA;;AAEE;AACF;AAEA;AAEA;;AAC4C;;AACzB;AACjB;AAIA;;;AAIF;AAEA;;;AAGK;;AAEH;AAAyB;;;AAKzB;AACE;AACE;AAA0B;;AACrB;;AAEL;AAAmC;;AAC9B;AACL;;;AAGJ;AACF;AAEA;;;;;;;;;;;;;;;;;;;;;;;;AAwBG;;AAgCD;AACA;AACA;AACA;AAEA;AACA;AACA;;AAGA;;;;AAIE;AACD;;AAGD;;;AAKM;;;AAGA;;;AAGA;;;AAGA;;;AAGA;;AACsB;;AACU;;AAElC;AACF;AAIF;AAEI;;;;AAGA;AAEA;AAEA;;;AAGI;;;AAGF;;;;AAIE;AACD;;;AAIH;;;AAGA;;AAEA;AACF;AAIF;;;AAGI;;;AAGA;AAAa;;AAKjB;AAEI;AACA;;;;AAGA;AACF;AAIF;AACE;;AACA;AACF;AAEA;AAEI;;AACA;;AAEE;;AAEJ;AAIF;AAEI;;;AAEA;AACA;;AACF;AAIF;AAEI;;;AAEA;AACF;AAIF;AAEI;;;AAEA;AACA;;AACF;AAIF;AAEI;;;AAEA;;AAEA;;;AAGE;;;AAEA;AACE;AACE;AACA;AAAO;;;;;AAKf;AAIF;AAEA;;AAEI;;;;;;;AASK;;;AAMP;AAEA;AAiBQ;;AAgBF;AAMR;AAEA;;AACkC;;AAEhC;AAAY;;;;;AAKZ;;;;;;;;AAgFJ;AAEA;;"}
1
+ {"version":3,"file":"FileUploader.js","sources":["src/components/controls/FileUploader/FileUploader.tsx"],"sourcesContent":["'use client';\n\nimport React, { useCallback, useId, useMemo, useRef, useState } from 'react';\n\nimport { FormHelperText } from '@/components/form/FormHelperText';\nimport { FormLabel } from '@/components/form/FormLabel';\nimport { CloudUploadIcon } from '@/components/Icons/CloudUploadIcon';\nimport { TrashIcon } from '@/components/Icons/TrashIcon';\nimport { useControlledState } from '@/hooks/useControlledState';\nimport { useMergedRef } from '@/hooks/useMergedRef';\nimport { cx } from '@/utils/cx';\n\nimport {\n containerStyle,\n dropZoneHintStyle,\n dropZonePrimaryStyle,\n dropZoneRecipe,\n fileListStyle,\n fileMetaStyle,\n fileNameStyle,\n fileRowRecipe,\n fileSubStyle,\n hiddenInputStyle,\n progressBarStyle,\n progressFillRecipe,\n removeButtonStyle,\n statusBadgeRecipe,\n} from './FileUploader.css';\n\nimport type {\n FileUploaderItem,\n FileUploaderProps,\n FileUploaderRejectReason,\n} from './FileUploader.types';\n\nlet __fileUploaderIdCounter = 0;\nfunction nextFileId(): string {\n __fileUploaderIdCounter += 1;\n return `eui-file-${String(Date.now())}-${String(__fileUploaderIdCounter)}`;\n}\n\nconst SIZE_UNITS = ['B', 'KB', 'MB', 'GB', 'TB'];\n\nfunction formatBytes(bytes: number): string {\n if (!Number.isFinite(bytes) || bytes < 0) return '';\n if (bytes === 0) return '0 B';\n const exponent = Math.min(\n SIZE_UNITS.length - 1,\n Math.floor(Math.log(bytes) / Math.log(1024))\n );\n const value = bytes / Math.pow(1024, exponent);\n const formatted =\n value >= 100 || exponent === 0 ? value.toFixed(0) : value.toFixed(1);\n return `${formatted} ${SIZE_UNITS[exponent] ?? ''}`;\n}\n\nfunction fileMatchesAccept(file: File, accept: string): boolean {\n const tokens = accept\n .split(',')\n .map(t => t.trim().toLowerCase())\n .filter(Boolean);\n if (tokens.length === 0) return true;\n\n const name = file.name.toLowerCase();\n const type = file.type.toLowerCase();\n\n for (const token of tokens) {\n if (token.startsWith('.')) {\n if (name.endsWith(token)) return true;\n } else if (token.endsWith('/*')) {\n const prefix = token.slice(0, -2);\n if (type.startsWith(`${prefix}/`)) return true;\n } else if (token === type) {\n return true;\n }\n }\n return false;\n}\n\n/**\n * Drag-and-drop file uploader with click-to-browse fallback. Renders a list\n * of uploaded items with per-file status (`pending` / `uploading` / `done` /\n * `error`) and an animated progress bar.\n *\n * The component itself is purely presentational around the file list — it\n * does not perform the actual upload. The consumer is expected to:\n * 1. Listen to `onFilesAdd` (or `onChange`) to know which files were\n * accepted by the uploader.\n * 2. Drive the upload (fetch / XHR / SDK) externally.\n * 3. Reflect progress back into the component by passing a controlled\n * `value` and updating the `status` / `progress` of each item.\n *\n * @example\n * ```tsx\n * const [items, setItems] = useState<FileUploaderItem[]>([]);\n * <FileUploader\n * value={items}\n * onChange={setItems}\n * accept=\"image/*,.pdf\"\n * maxSize={5 * 1024 * 1024}\n * onFilesAdd={files => uploadAll(files, setItems)}\n * />\n * ```\n */\nexport function FileUploader({\n value,\n defaultValue,\n onChange,\n onFilesAdd,\n onFileRemove,\n onReject,\n accept,\n multiple = true,\n maxFiles,\n maxSize,\n minSize,\n validate,\n renderItem,\n size = 'md',\n label,\n helperText,\n error = false,\n errorMessage,\n disabled = false,\n required = false,\n dropZoneText = 'Drag files here or click to browse',\n dropZoneHint,\n name,\n className,\n style,\n testId,\n id: idProp,\n ref,\n ...rest\n}: FileUploaderProps) {\n const autoId = useId();\n const fieldId = idProp ?? autoId;\n const labelId = `${fieldId}-label`;\n const helperId = `${fieldId}-helper`;\n\n const inputRef = useRef<HTMLInputElement | null>(null);\n const dropZoneRef = useRef<HTMLDivElement | null>(null);\n const containerRef = useRef<HTMLDivElement | null>(null);\n const setContainerRef = useMergedRef(containerRef, ref);\n\n const [items, setItems] = useControlledState<FileUploaderItem[]>({\n value,\n defaultValue,\n onChange,\n fallback: [],\n });\n\n const [dragging, setDragging] = useState(false);\n const dragCounterRef = useRef(0);\n\n const validateFile = useCallback(\n (file: File, currentCount: number): FileUploaderRejectReason | null => {\n if (typeof maxFiles === 'number' && currentCount >= maxFiles) {\n return 'max-files';\n }\n if (accept && !fileMatchesAccept(file, accept)) {\n return 'wrong-type';\n }\n if (typeof maxSize === 'number' && file.size > maxSize) {\n return 'too-large';\n }\n if (typeof minSize === 'number' && file.size < minSize) {\n return 'too-small';\n }\n if (validate) {\n const result = validate(file);\n if (result === false) return 'invalid';\n if (typeof result === 'string') return result;\n }\n return null;\n },\n [accept, maxFiles, maxSize, minSize, validate]\n );\n\n const acceptFiles = useCallback(\n (incoming: File[]) => {\n if (incoming.length === 0) return;\n const accepted: File[] = [];\n const newItems: FileUploaderItem[] = [];\n let count = items.length;\n\n const list = multiple ? incoming : incoming.slice(0, 1);\n\n for (const file of list) {\n const rejection = validateFile(file, count);\n if (rejection) {\n onReject?.(file, rejection);\n continue;\n }\n accepted.push(file);\n newItems.push({\n id: nextFileId(),\n file,\n status: 'pending',\n });\n count += 1;\n }\n\n if (newItems.length === 0) return;\n\n // In single mode, replace any existing item.\n const next = multiple ? [...items, ...newItems] : newItems;\n setItems(next);\n onFilesAdd?.(accepted);\n },\n [items, multiple, onFilesAdd, onReject, setItems, validateFile]\n );\n\n const removeAt = useCallback(\n (index: number) => {\n if (index < 0 || index >= items.length) return;\n const removed = items[index];\n const next = items.slice(0, index).concat(items.slice(index + 1));\n setItems(next);\n if (removed) onFileRemove?.(removed);\n },\n [items, onFileRemove, setItems]\n );\n\n const handleInputChange = useCallback(\n (event: React.ChangeEvent<HTMLInputElement>) => {\n const list = event.target.files;\n if (!list) return;\n acceptFiles(Array.from(list));\n // Reset so selecting the same file again still fires onChange.\n event.target.value = '';\n },\n [acceptFiles]\n );\n\n const openPicker = useCallback(() => {\n if (disabled) return;\n inputRef.current?.click();\n }, [disabled]);\n\n const handleZoneKeyDown = useCallback(\n (event: React.KeyboardEvent<HTMLDivElement>) => {\n if (disabled) return;\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault();\n openPicker();\n }\n },\n [disabled, openPicker]\n );\n\n const handleDragEnter = useCallback(\n (event: React.DragEvent<HTMLDivElement>) => {\n if (disabled) return;\n event.preventDefault();\n dragCounterRef.current += 1;\n if (dragCounterRef.current === 1) setDragging(true);\n },\n [disabled]\n );\n\n const handleDragOver = useCallback(\n (event: React.DragEvent<HTMLDivElement>) => {\n if (disabled) return;\n event.preventDefault();\n event.dataTransfer.dropEffect = 'copy';\n },\n [disabled]\n );\n\n const handleDragLeave = useCallback(\n (event: React.DragEvent<HTMLDivElement>) => {\n if (disabled) return;\n event.preventDefault();\n dragCounterRef.current = Math.max(0, dragCounterRef.current - 1);\n if (dragCounterRef.current === 0) setDragging(false);\n },\n [disabled]\n );\n\n const handleDrop = useCallback(\n (event: React.DragEvent<HTMLDivElement>) => {\n if (disabled) return;\n event.preventDefault();\n dragCounterRef.current = 0;\n setDragging(false);\n const dt = event.dataTransfer;\n const dropped: File[] = [];\n if (dt.files.length > 0) {\n dropped.push(...Array.from(dt.files));\n } else if (dt.items.length > 0) {\n for (const item of Array.from(dt.items)) {\n if (item.kind === 'file') {\n const f = item.getAsFile();\n if (f) dropped.push(f);\n }\n }\n }\n acceptFiles(dropped);\n },\n [acceptFiles, disabled]\n );\n\n const showHelperText = error && errorMessage ? errorMessage : helperText;\n\n const renderRow = (item: FileUploaderItem, index: number) => {\n if (renderItem) {\n return (\n <React.Fragment key={item.id}>\n {renderItem({\n item,\n index,\n disabled,\n remove: () => {\n removeAt(index);\n },\n })}\n </React.Fragment>\n );\n }\n\n const progress = Math.max(0, Math.min(100, item.progress ?? 0));\n const sub = item.errorMessage ?? formatBytes(item.file.size);\n\n return (\n <div\n key={item.id}\n className={fileRowRecipe({ status: item.status })}\n data-testid={testId ? `${testId}-item-${item.id}` : undefined}\n >\n <div className={fileMetaStyle}>\n <span className={fileNameStyle} title={item.file.name}>\n {item.file.name}\n </span>\n <span className={fileSubStyle}>\n <span className={statusBadgeRecipe({ status: item.status })}>\n {item.status}\n </span>\n <span>{sub}</span>\n </span>\n {(item.status === 'uploading' ||\n (item.status === 'pending' && progress > 0)) && (\n <div className={progressBarStyle} aria-hidden=\"true\">\n <div\n className={progressFillRecipe({ status: item.status })}\n style={{ width: `${String(progress)}%` }}\n />\n </div>\n )}\n </div>\n <button\n type=\"button\"\n className={removeButtonStyle}\n aria-label={`Remove ${item.file.name}`}\n disabled={disabled}\n onClick={() => {\n removeAt(index);\n }}\n >\n <TrashIcon size=\"sm\" decorative />\n </button>\n </div>\n );\n };\n\n const acceptHint = useMemo(() => {\n if (dropZoneHint !== undefined) return dropZoneHint;\n const parts: string[] = [];\n if (accept) parts.push(accept);\n if (typeof maxSize === 'number')\n parts.push(`up to ${formatBytes(maxSize)}`);\n if (typeof maxFiles === 'number')\n parts.push(`max ${String(maxFiles)} files`);\n return parts.length > 0 ? parts.join(' • ') : null;\n }, [accept, dropZoneHint, maxFiles, maxSize]);\n\n return (\n <div\n ref={setContainerRef}\n className={cx(containerStyle, className)}\n style={style}\n data-testid={testId}\n {...rest}\n >\n {label && (\n <FormLabel\n id={labelId}\n htmlFor={fieldId}\n required={required}\n disabled={disabled}\n >\n {label}\n </FormLabel>\n )}\n\n <div\n ref={dropZoneRef}\n role=\"button\"\n tabIndex={disabled ? -1 : 0}\n aria-disabled={disabled || undefined}\n aria-labelledby={label ? labelId : undefined}\n aria-describedby={showHelperText ? helperId : undefined}\n className={dropZoneRecipe({\n size,\n dragging,\n disabled,\n error,\n })}\n onClick={openPicker}\n onKeyDown={handleZoneKeyDown}\n onDragEnter={handleDragEnter}\n onDragOver={handleDragOver}\n onDragLeave={handleDragLeave}\n onDrop={handleDrop}\n data-testid={testId ? `${testId}-dropzone` : undefined}\n >\n <CloudUploadIcon\n size={size === 'lg' ? 32 : size === 'sm' ? 18 : 24}\n decorative\n />\n <span className={dropZonePrimaryStyle}>{dropZoneText}</span>\n {acceptHint && <span className={dropZoneHintStyle}>{acceptHint}</span>}\n\n <input\n ref={inputRef}\n id={fieldId}\n type=\"file\"\n name={name}\n accept={accept}\n multiple={multiple}\n required={required}\n disabled={disabled}\n onChange={handleInputChange}\n className={hiddenInputStyle}\n aria-hidden=\"true\"\n tabIndex={-1}\n data-testid={testId ? `${testId}-input` : undefined}\n />\n </div>\n\n {items.length > 0 && (\n <div className={fileListStyle}>\n {items.map((item, idx) => renderRow(item, idx))}\n </div>\n )}\n\n {showHelperText && (\n <FormHelperText id={helperId} error={error}>\n {showHelperText}\n </FormHelperText>\n )}\n </div>\n );\n}\n\nFileUploader.displayName = 'FileUploader';\n"],"names":[],"mappings":";;;;;;;;;;;;AAmCA;AACA;;AAEE;AACF;AAEA;AAEA;;AAC4C;;AACzB;AACjB;AAIA;;;AAIF;AAEA;;;AAGK;;AAEH;AAAyB;;;AAKzB;AACE;AACE;AAA0B;;AACrB;;AAEL;AAAmC;;AAC9B;AACL;;;AAGJ;AACF;AAEA;;;;;;;;;;;;;;;;;;;;;;;;AAwBG;;AAgCD;AACA;AACA;AACA;AAEA;AACA;AACA;;AAGA;;;;AAIE;AACD;;AAGD;;;AAKM;;;AAGA;;;AAGA;;;AAGA;;;AAGA;;AACsB;;AACU;;AAElC;AACF;AAIF;AAEI;;;;AAGA;AAEA;AAEA;;;AAGI;;;AAGF;;;;AAIE;AACD;;;AAIH;;;AAGA;;AAEA;AACF;AAIF;;;AAGI;;;AAGA;AAAa;;AAKjB;AAEI;AACA;;;;AAGA;AACF;AAIF;AACE;;AACA;AACF;AAEA;AAEI;;AACA;;AAEE;;AAEJ;AAIF;AAEI;;;AAEA;AACA;;AACF;AAIF;AAEI;;;AAEA;AACF;AAIF;AAEI;;;AAEA;AACA;;AACF;AAIF;AAEI;;;AAEA;;AAEA;;;AAGE;;;AAEA;AACE;AACE;AACA;AAAO;;;;;AAKf;AAIF;AAEA;;AAEI;;;;;;;AASK;;;AAMP;AAEA;AAiBQ;;AAgBF;AAMR;AAEA;;AACkC;;AAEhC;AAAY;;;;;AAKZ;;;;;;;;AAgFJ;AAEA;;"}
@@ -1 +1 @@
1
- {"version":3,"file":"MultiSelect.js","sources":["../../../../../../src/components/controls/MultiSelect/MultiSelect.tsx"],"sourcesContent":[null],"names":[],"mappings":";;;;;;;;;;;;;;;;AAmDA;AAGE;AACF;AAEA;;AAIE;AACE;;;;AAGE;;;AAGJ;AACF;AAEA;AAIE;;AAEF;AAEA;AACE;AACA;AACA;;AAGF;;;;;;;;;;;;;;;;;;;;;AAqBG;AACG;AAiCJ;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;;AAIA;;;;AAIE;AACD;;;;AAMD;;;;AAKA;AAEA;AACA;AACE;AAAyC;AACzC;;AAGF;;AAII;;;;AAGE;;;;;;;;AASN;AACE;;;;AAGA;AACF;AAEA;;;AAGE;AACA;AACF;;AAGE;;;AAGE;AACA;AAAmB;;AAErB;AACD;AAED;AACE;AACA;;AACA;;AAEA;AAGA;;AAGI;AAEJ;;AAEE;AACA;AACE;;AAEI;AACD;AACH;AACH;AACH;;AAGE;;AACA;;;AAGI;AACF;;;;;AAMF;;AACA;AACE;;;AAKE;;AAEJ;AACA;AACA;AACE;AACF;AACF;AAEA;AAEI;;AACA;AACE;AACA;AACA;AACA;;AAEE;AAAa;;;;AAOrB;AAEI;AACF;AAIF;AAEI;AACF;AAIF;;;AAIE;AAIF;AACA;AAEA;AAGO;AACA;AAIP;AACA;AAEI;;;;;AAMF;AACE;;;;;AAMF;;;AAGI;AACE;;AAEA;;;;;AAgBF;AACF;;AAGF;AACF;AAEA;;AAEE;AACA;AACA;AAEA;;;AAII;AACA;AAAmB;;AAEvB;AAEA;AASM;AACA;AACA;;AAIA;;AAEE;;AAEJ;AAEE;AAA0B;;AAalC;;;;AAgCU;;;AAEA;;;;;AAOF;;;AAGA;;;AA8Bc;AACF;AAEE;;;AAGE;;AAEJ;AAsBR;;;;;;AAmBN;AAmBU;AACF;AA0BhB;AAEA;;"}
1
+ {"version":3,"file":"MultiSelect.js","sources":["src/components/controls/MultiSelect/MultiSelect.tsx"],"sourcesContent":["'use client';\n\nimport React, {\n useCallback,\n useDeferredValue,\n useEffect,\n useId,\n useMemo,\n useRef,\n useState,\n} from 'react';\nimport { createPortal } from 'react-dom';\n\nimport { FormHelperText } from '@/components/form/FormHelperText';\nimport { FormLabel } from '@/components/form/FormLabel';\nimport { CheckIcon } from '@/components/Icons/CheckIcon';\nimport { ChevronDownIcon } from '@/components/Icons/ChevronDownIcon';\nimport { CloseIcon } from '@/components/Icons/CloseIcon';\nimport { ScrollArea } from '@/components/layout/ScrollArea';\nimport { useControlledState } from '@/hooks/useControlledState';\nimport { useListboxNav } from '@/hooks/useListboxNav';\nimport { useMergedRef } from '@/hooks/useMergedRef';\nimport { cx } from '@/utils/cx';\n\nimport {\n checkboxRecipe,\n chevronRecipe,\n chipLabelStyle,\n chipRecipe,\n chipRemoveStyle,\n clearButtonStyle,\n containerStyle,\n dropdownStyle,\n emptyMessageStyle,\n groupLabelStyle,\n moreBadgeStyle,\n optionItemRecipe,\n optionsListStyle,\n placeholderStyle,\n searchInputStyle,\n triggerContentStyle,\n triggerRecipe,\n} from './MultiSelect.css';\n\nimport type {\n MultiSelectOption,\n MultiSelectOptionGroup,\n MultiSelectProps,\n MultiSelectSize,\n} from './MultiSelect.types';\n\nfunction isOptionGroup<T extends string>(\n item: MultiSelectOption<T> | MultiSelectOptionGroup<T>\n): item is MultiSelectOptionGroup<T> {\n return 'options' in item && Array.isArray(item.options);\n}\n\nfunction flattenOptions<T extends string>(\n items: Array<MultiSelectOption<T> | MultiSelectOptionGroup<T>>\n): MultiSelectOption<T>[] {\n const result: MultiSelectOption<T>[] = [];\n for (const item of items) {\n if (isOptionGroup(item)) {\n result.push(...item.options);\n } else {\n result.push(item);\n }\n }\n return result;\n}\n\nfunction defaultFilter<T extends string>(\n option: MultiSelectOption<T>,\n query: string\n): boolean {\n const label = (option.label ?? option.value).toLowerCase();\n return label.includes(query.toLowerCase());\n}\n\nconst CHEVRON_SIZES: Record<MultiSelectSize, number> = {\n sm: 10,\n md: 12,\n lg: 14,\n};\n\n/**\n * Multi-value select that lets the user pick several options from a dropdown.\n * Selected values are rendered as inline chips inside the trigger; the rest\n * collapse into a \"+N more\" badge.\n *\n * Supports searchable mode, grouped options, controlled and uncontrolled\n * modes, keyboard navigation, an optional `max` cap, and a clear-all button.\n *\n * @example\n * ```tsx\n * <MultiSelect\n * label=\"Tags\"\n * options={[\n * { value: 'react', label: 'React' },\n * { value: 'vue', label: 'Vue' },\n * { value: 'svelte', label: 'Svelte' },\n * ]}\n * value={tags}\n * onChange={setTags}\n * />\n * ```\n */\nexport function MultiSelect<T extends string = string>({\n value,\n defaultValue,\n options,\n placeholder = 'Select...',\n searchable = false,\n searchPlaceholder = 'Search...',\n filterFn,\n emptyMessage = 'No results found',\n max,\n maxInlineChips = 3,\n size = 'md',\n variant = 'default',\n label,\n helperText,\n error = false,\n errorMessage,\n disabled = false,\n required = false,\n clearable = false,\n maxDropdownHeight = 240,\n minDropdownWidth,\n closeOnSelect = false,\n name,\n onChange,\n onOpenChange,\n className,\n style,\n testId,\n ref,\n id: idProp,\n ...rest\n}: MultiSelectProps<T>) {\n const autoId = useId();\n const fieldId = idProp ?? autoId;\n const labelId = `${fieldId}-label`;\n const helperId = `${fieldId}-helper`;\n const listboxId = `${fieldId}-listbox`;\n\n const triggerRef = useRef<HTMLButtonElement | null>(null);\n const dropdownRef = useRef<HTMLDivElement | null>(null);\n const searchRef = useRef<HTMLInputElement | null>(null);\n\n const setTriggerRef = useMergedRef(triggerRef, ref);\n\n const [selected, setSelected] = useControlledState<T[]>({\n value,\n defaultValue,\n onChange,\n fallback: [],\n });\n\n const [isOpen, setIsOpen] = useState(false);\n const [searchQuery, setSearchQuery] = useState('');\n const [dropdownPos, setDropdownPos] = useState<React.CSSProperties>({});\n\n const allOptions = useMemo(() => flattenOptions(options), [options]);\n\n // Defer the query that drives filtering so the search input stays\n // responsive while a large option list re-filters. The immediate\n // `searchQuery` still drives the input value.\n const deferredSearchQuery = useDeferredValue(searchQuery);\n\n const filter = filterFn ?? defaultFilter;\n const filteredOptions = useMemo(() => {\n if (!searchable || !deferredSearchQuery) return allOptions;\n return allOptions.filter(opt => filter(opt, deferredSearchQuery));\n }, [allOptions, searchable, deferredSearchQuery, filter]);\n\n const selectedSet = useMemo(() => new Set(selected), [selected]);\n\n const toggleValue = useCallback(\n (val: T, isDisabled: boolean) => {\n if (isDisabled) return;\n const isSelected = selectedSet.has(val);\n if (isSelected) {\n setSelected(selected.filter(v => v !== val));\n } else {\n if (typeof max === 'number' && selected.length >= max) return;\n setSelected([...selected, val]);\n }\n },\n [max, selected, selectedSet, setSelected]\n );\n\n const open = useCallback(() => {\n if (disabled) return;\n setIsOpen(true);\n setSearchQuery('');\n onOpenChange?.(true);\n }, [disabled, onOpenChange]);\n\n const close = useCallback(() => {\n setIsOpen(false);\n setSearchQuery('');\n onOpenChange?.(false);\n triggerRef.current?.focus();\n }, [onOpenChange]);\n\n const listbox = useListboxNav({\n items: filteredOptions,\n isItemDisabled: opt => Boolean(opt.disabled),\n onSelect: opt => {\n toggleValue(opt.value, Boolean(opt.disabled));\n if (closeOnSelect) close();\n },\n onEscape: close,\n });\n\n const updateDropdownPosition = useCallback(() => {\n const trigger = triggerRef.current;\n if (!trigger) return;\n const rect = trigger.getBoundingClientRect();\n const spaceBelow = window.innerHeight - rect.bottom;\n const openAbove =\n spaceBelow < maxDropdownHeight + 8 && rect.top > spaceBelow;\n\n const dropdownW =\n minDropdownWidth !== undefined\n ? Math.max(rect.width, minDropdownWidth)\n : rect.width;\n\n setDropdownPos({\n left: rect.left,\n width: dropdownW,\n ...(openAbove\n ? {\n bottom: window.innerHeight - rect.top + 4,\n transformOrigin: 'bottom',\n }\n : { top: rect.bottom + 4, transformOrigin: 'top' }),\n });\n }, [maxDropdownHeight, minDropdownWidth]);\n\n useEffect(() => {\n if (!isOpen) return;\n updateDropdownPosition();\n if (searchable) {\n requestAnimationFrame(() => {\n searchRef.current?.focus();\n });\n }\n }, [isOpen, updateDropdownPosition, searchable]);\n\n // Outside click handling\n useEffect(() => {\n if (!isOpen) return;\n const handleClick = (e: MouseEvent) => {\n const target = e.target as Node;\n if (\n !triggerRef.current?.contains(target) &&\n !dropdownRef.current?.contains(target)\n ) {\n close();\n }\n };\n document.addEventListener('mousedown', handleClick);\n return () => {\n document.removeEventListener('mousedown', handleClick);\n };\n }, [isOpen, close]);\n\n const handleTriggerKeyDown = useCallback(\n (e: React.KeyboardEvent) => {\n if (disabled) return;\n switch (e.key) {\n case 'Enter':\n case ' ':\n case 'ArrowDown':\n case 'ArrowUp':\n e.preventDefault();\n if (!isOpen) open();\n break;\n }\n },\n [disabled, isOpen, open]\n );\n\n const handleDropdownKeyDown = useCallback(\n (e: React.KeyboardEvent) => {\n listbox.handleKeyDown(e);\n },\n [listbox]\n );\n\n const removeValue = useCallback(\n (val: T) => {\n setSelected(selected.filter(v => v !== val));\n },\n [selected, setSelected]\n );\n\n const handleClearAll = useCallback(\n (e: React.MouseEvent) => {\n e.stopPropagation();\n setSelected([]);\n },\n [setSelected]\n );\n\n const showHelperText = error && errorMessage ? errorMessage : helperText;\n const chevronSize = CHEVRON_SIZES[size];\n\n const selectedOptions = useMemo(\n () =>\n selected\n .map(v => allOptions.find(o => o.value === v))\n .filter((o): o is MultiSelectOption<T> => Boolean(o)),\n [allOptions, selected]\n );\n\n const isEmpty = selected.length === 0;\n const visibleChips =\n maxInlineChips === Infinity\n ? selectedOptions\n : selectedOptions.slice(0, maxInlineChips);\n const overflowCount = selectedOptions.length - visibleChips.length;\n\n // Render options\n const renderOptionsList = () => {\n if (filteredOptions.length === 0) {\n return <div className={emptyMessageStyle}>{emptyMessage}</div>;\n }\n\n const hasGroups = options.some(isOptionGroup);\n // Mirror the deferred query used by `filteredOptions` so the grouped /\n // flat switch stays consistent with the list being rendered.\n if (hasGroups && !deferredSearchQuery) {\n let flatIndex = 0;\n return options.map((item, groupIdx) => {\n if (isOptionGroup(item)) {\n const groupOptions = item.options;\n const startIdx = flatIndex;\n flatIndex += groupOptions.length;\n return (\n <div\n key={`group-${String(groupIdx)}`}\n role=\"group\"\n aria-label={item.label}\n >\n <div className={groupLabelStyle}>{item.label}</div>\n {groupOptions.map((opt, optIdx) =>\n renderOptionItem(opt, startIdx + optIdx)\n )}\n </div>\n );\n }\n const idx = flatIndex;\n flatIndex += 1;\n return renderOptionItem(item, idx);\n });\n }\n\n return filteredOptions.map((opt, idx) => renderOptionItem(opt, idx));\n };\n\n const renderOptionItem = (opt: MultiSelectOption<T>, index: number) => {\n const isSelected = selectedSet.has(opt.value);\n const isActive = index === listbox.activeIndex;\n const isDisabled = opt.disabled ?? false;\n const reachedMax =\n typeof max === 'number' && !isSelected && selected.length >= max;\n const interactiveDisabled = isDisabled || reachedMax;\n\n const selectOption = () => {\n if (!interactiveDisabled) {\n toggleValue(opt.value, false);\n if (closeOnSelect) close();\n }\n };\n\n return (\n <div\n key={opt.value}\n role=\"option\"\n tabIndex={-1}\n aria-selected={isSelected}\n aria-disabled={interactiveDisabled || undefined}\n id={`${listboxId}-${opt.value}`}\n className={optionItemRecipe({\n active: isActive,\n selected: isSelected,\n disabled: interactiveDisabled,\n })}\n onClick={selectOption}\n onKeyDown={e => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n selectOption();\n }\n }}\n onMouseEnter={() => {\n if (!interactiveDisabled) listbox.setActiveIndex(index);\n }}\n >\n <span\n className={checkboxRecipe({ selected: isSelected })}\n aria-hidden=\"true\"\n >\n {isSelected && <CheckIcon size={10} decorative />}\n </span>\n {opt.icon && <span>{opt.icon}</span>}\n <span>{opt.label ?? opt.value}</span>\n </div>\n );\n };\n\n const formValue = selected.join(',');\n\n return (\n <div className={cx(containerStyle, className)} style={style}>\n {label && (\n <FormLabel\n id={labelId}\n htmlFor={fieldId}\n required={required}\n disabled={disabled}\n >\n {label}\n </FormLabel>\n )}\n\n <button\n ref={setTriggerRef}\n id={fieldId}\n type=\"button\"\n role=\"combobox\"\n aria-haspopup=\"listbox\"\n aria-expanded={isOpen}\n aria-controls={isOpen ? listboxId : undefined}\n aria-labelledby={label ? labelId : undefined}\n aria-required={required || undefined}\n aria-invalid={error || undefined}\n aria-describedby={showHelperText ? helperId : undefined}\n disabled={disabled}\n onClick={() => {\n if (isOpen) {\n close();\n } else {\n open();\n }\n }}\n onKeyDown={handleTriggerKeyDown}\n className={triggerRecipe({\n size,\n variant,\n open: isOpen,\n disabled,\n error,\n empty: isEmpty,\n })}\n data-testid={testId}\n {...rest}\n >\n <span className={triggerContentStyle}>\n {isEmpty ? (\n <span className={placeholderStyle}>{placeholder}</span>\n ) : (\n <>\n {visibleChips.map(opt => (\n <span\n key={opt.value}\n className={chipRecipe({ size })}\n data-testid={\n testId ? `${testId}-chip-${opt.value}` : undefined\n }\n >\n {opt.icon && <span>{opt.icon}</span>}\n <span className={chipLabelStyle}>\n {opt.label ?? opt.value}\n </span>\n {!disabled && (\n <span\n role=\"button\"\n tabIndex={-1}\n aria-label={`Remove ${opt.label ?? opt.value}`}\n className={chipRemoveStyle}\n onClick={e => {\n e.stopPropagation();\n removeValue(opt.value);\n }}\n onKeyDown={e => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n e.stopPropagation();\n removeValue(opt.value);\n }\n }}\n >\n <CloseIcon size=\"sm\" decorative />\n </span>\n )}\n </span>\n ))}\n {overflowCount > 0 && (\n <span className={moreBadgeStyle}>+{overflowCount} more</span>\n )}\n </>\n )}\n </span>\n\n {clearable && !isEmpty && !disabled && (\n <span\n role=\"button\"\n tabIndex={-1}\n aria-label=\"Clear selection\"\n className={clearButtonStyle}\n onClick={handleClearAll}\n onKeyDown={e => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n e.stopPropagation();\n setSelected([]);\n }\n }}\n >\n <CloseIcon size=\"sm\" decorative />\n </span>\n )}\n\n <span className={chevronRecipe({ open: isOpen })}>\n <ChevronDownIcon size={chevronSize} decorative />\n </span>\n </button>\n\n {name && <input type=\"hidden\" name={name} value={formValue} />}\n\n {isOpen &&\n createPortal(\n <div\n ref={dropdownRef}\n role=\"listbox\"\n id={listboxId}\n aria-multiselectable=\"true\"\n aria-labelledby={label ? labelId : undefined}\n className={dropdownStyle}\n style={dropdownPos}\n onKeyDown={handleDropdownKeyDown}\n tabIndex={-1}\n >\n {searchable && (\n <input\n ref={searchRef}\n type=\"text\"\n placeholder={searchPlaceholder}\n value={searchQuery}\n onChange={e => {\n setSearchQuery(e.target.value);\n }}\n onKeyDown={handleDropdownKeyDown}\n className={searchInputStyle}\n aria-label=\"Search options\"\n />\n )}\n <ScrollArea\n className={optionsListStyle}\n maxHeight={maxDropdownHeight - (searchable ? 32 : 0)}\n scrollbarWidth={4}\n scrollbarVisibility=\"auto\"\n hideDelay={600}\n >\n {renderOptionsList()}\n </ScrollArea>\n </div>,\n document.body\n )}\n\n {showHelperText && (\n <FormHelperText id={helperId} error={error}>\n {showHelperText}\n </FormHelperText>\n )}\n </div>\n );\n}\n\nMultiSelect.displayName = 'MultiSelect';\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAmDA;AAGE;AACF;AAEA;;AAIE;AACE;;;;AAGE;;;AAGJ;AACF;AAEA;AAIE;;AAEF;AAEA;AACE;AACA;AACA;;AAGF;;;;;;;;;;;;;;;;;;;;;AAqBG;AACG;AAiCJ;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;;AAIA;;;;AAIE;AACD;;;;AAMD;;;;AAKA;AAEA;AACA;AACE;AAAyC;AACzC;;AAGF;;AAII;;;;AAGE;;;;;;;;AASN;AACE;;;;AAGA;AACF;AAEA;;;AAGE;AACA;AACF;;AAGE;;;AAGE;AACA;AAAmB;;AAErB;AACD;AAED;AACE;AACA;;AACA;;AAEA;AAGA;;AAGI;AAEJ;;AAEE;AACA;AACE;;AAEI;AACD;AACH;AACH;AACH;;AAGE;;AACA;;;AAGI;AACF;;;;;AAMF;;AACA;AACE;;;AAKE;;AAEJ;AACA;AACA;AACE;AACF;AACF;AAEA;AAEI;;AACA;AACE;AACA;AACA;AACA;;AAEE;AAAa;;;;AAOrB;AAEI;AACF;AAIF;AAEI;AACF;AAIF;;;AAIE;AAIF;AACA;AAEA;AAGO;AACA;AAIP;AACA;AAEI;;;;;AAMF;AACE;;;;;AAMF;;;AAGI;AACE;;AAEA;;;;;AAgBF;AACF;;AAGF;AACF;AAEA;;AAEE;AACA;AACA;AAEA;;;AAII;AACA;AAAmB;;AAEvB;AAEA;AASM;AACA;AACA;;AAIA;;AAEE;;AAEJ;AAEE;AAA0B;;AAalC;;;;AAgCU;;;AAEA;;;;;AAOF;;;AAGA;;;AA8Bc;AACF;AAEE;;;AAGE;;AAEJ;AAsBR;;;;;;AAmBN;AAmBU;AACF;AA0BhB;AAEA;;"}
@@ -1 +1 @@
1
- {"version":3,"file":"NumberInput.js","sources":["../../../../../../src/components/controls/NumberInput/NumberInput.tsx"],"sourcesContent":[null],"names":[],"mappings":";;;;;;;;;AA6IA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2CG;AACI;AACL;AACA;AAGA;AACA;AAMA;AACA;AAWA;AACA;AAgBA;AACA;AACA;;AAGA;AAKA;AACA;;;;AAKC;AAED;;;;;;;;;;;;;;;;AAgCC;;AAGD;AACA;AACA;;AAGA;;;;AAIE;;;AAGA;;;;;;AAME;AACA;;;AAIJ;AAEA;AACE;;AAEA;;;AAKE;;AAGA;AACA;;AAEJ;;AAGE;;AAEA;AACA;;AAIA;AACE;;;AAGE;;;AAIJ;AACF;;;AAIE;;AAEA;AACE;AACF;;AAGE;AACA;AACF;AAEA;AACA;AAEA;AACE;AACA;AACF;;;AAIF;AACE;AACF;AAEA;;AAEE;AACF;AAEA;;AAEE;AACA;AACF;AAEA;;AAEE;AACF;;;AAIE;;;AAGF;;;;AAKE;AACF;;AAGA;;;AAGE;;AAEF;AAEA;;;AAGE;;AAEF;;AAGA;;AAIA;;AAGA;AAEA;;;;;;;;AASE;;AAyBM;;;AAGD;AAOK;;AAEA;;;;;;AAwCA;AACD;AAcC;;AAEA;AACD;AAgBD;AACA;AAKZ;AAEA;;"}
1
+ {"version":3,"file":"NumberInput.js","sources":["src/components/controls/NumberInput/NumberInput.tsx"],"sourcesContent":["'use client';\n\n// src/controls/NumberInput/NumberInput.tsx\nimport React, { useRef, useEffect } from 'react';\n\nimport { inputWrapperRecipe } from '@/components/form/InputWrapper.css';\nimport { ChevronDownIcon } from '@/components/Icons/ChevronDownIcon';\nimport { cx } from '@/utils/cx';\n\nimport {\n numberInputContainerStyle,\n containerHoveredStyle,\n containerDraggingStyle,\n labelRecipe,\n stepButtonRecipe,\n stepButtonIconLeftStyle,\n stepButtonIconRightStyle,\n inputRecipe,\n valueDisplayRecipe,\n unitLabelStyle,\n helperTextRecipe,\n} from './NumberInput.css';\nimport { useNumberInput } from './useNumberInput';\n\nimport type { UseNumberInputOptions } from './useNumberInput';\nimport type { BaseComponent, Size } from '@/types/common';\nimport type { Prettify } from '@/types/utilities';\n\n/**\n * Props specific to NumberInput component\n */\nexport interface NumberInputBaseProps\n extends Omit<BaseComponent, 'onChange'>, UseNumberInputOptions {\n /**\n * Current numeric value\n */\n value: number;\n\n /**\n * Callback when value changes\n */\n onChange: (value: number) => void;\n\n /**\n * Unit suffix to display (e.g., \"px\", \"%\", \"°\")\n * Can be a string or a function that formats the value\n */\n unit?: string | ((value: number) => string);\n\n /**\n * Whether to show increment/decrement buttons on hover\n * @default true\n */\n showStepButtons?: boolean;\n\n /**\n * Custom validation function\n * Return error message string if invalid, undefined if valid\n */\n validate?: (value: number) => string | undefined;\n\n /**\n * Format value for display (e.g., add commas, currency symbols)\n * This is separate from unit display\n */\n formatValue?: (value: number) => string;\n\n /**\n * Parse custom formatted input back to number\n * Used in conjunction with formatValue\n */\n parseValue?: (input: string) => number | null;\n\n /**\n * Input size using standard library sizing\n * - `sm`: 20px height, compact for toolbars\n * - `md`: 24px height, standard for forms\n * - `lg`: 32px height, prominent inputs\n * @default \"md\"\n */\n size?: Size;\n\n /**\n * Whether the input is disabled\n * @default false\n */\n disabled?: boolean;\n\n /**\n * Whether the input is read-only\n * @default false\n */\n readOnly?: boolean;\n\n /**\n * Error message displayed when error is true\n */\n errorMessage?: string | undefined;\n\n /**\n * Placeholder text\n */\n placeholder?: string | undefined;\n\n /**\n * Input label\n */\n label?: string | undefined;\n\n /**\n * Helper text displayed below the input\n */\n helperText?: string | undefined;\n\n /**\n * Whether the input is required\n * @default false\n */\n required?: boolean;\n\n /**\n * Focus event handler\n */\n onFocus?: (event: React.FocusEvent<HTMLInputElement>) => void;\n\n /**\n * Blur event handler\n */\n onBlur?: (event: React.FocusEvent<HTMLInputElement>) => void;\n\n /**\n * Key down event handler\n */\n onKeyDown?: (event: React.KeyboardEvent<HTMLInputElement>) => void;\n}\n\n/**\n * Props for the NumberInput component with prettified type for better IntelliSense\n */\nexport type NumberInputProps = Prettify<NumberInputBaseProps>;\n\n/**\n * A specialized number input component with Blender-like functionality.\n *\n * Features:\n * - Drag left/right to change values with mouse\n * - Click chevron buttons to increment/decrement\n * - Keyboard input with mathematical expression support\n * - Modifier keys: Ctrl (large steps), Shift (precision), Minus (negate)\n * - Soft and hard value limits\n * - Unit display support\n * - Expression evaluation (pi, sqrt, sin, etc.)\n * - Custom validation and formatting\n * - Centered value and unit display\n *\n * @example\n * ```tsx\n * // Basic number input\n * <NumberInput\n * value={rotation}\n * onChange={setRotation}\n * unit=\"°\"\n * step={1}\n * />\n *\n * // With limits and precision\n * <NumberInput\n * value={opacity}\n * onChange={setOpacity}\n * min={0}\n * max={1}\n * step={0.1}\n * precision={2}\n * unit=\"%\"\n * />\n *\n * // With custom validation\n * <NumberInput\n * value={dimension}\n * onChange={setDimension}\n * validate={(v) => v <= 0 ? \"Must be positive\" : undefined}\n * placeholder=\"Enter value or expression\"\n * />\n * ```\n */\nexport const NumberInput = ({\n // Value props\n value,\n onChange,\n\n // NumberInput specific props\n unit,\n showStepButtons = true,\n validate,\n formatValue,\n parseValue,\n\n // UseNumberInput options\n min,\n max,\n softMin,\n softMax,\n step,\n precisionStep,\n largeStep,\n precision,\n allowExpressions = true,\n dragSensitivity = 1,\n\n // Input props\n size = 'md',\n disabled = false,\n readOnly = false,\n errorMessage,\n placeholder,\n label,\n helperText,\n required = false,\n className,\n testId,\n onFocus,\n onBlur,\n onKeyDown,\n ref,\n ...props\n}: NumberInputProps) => {\n const containerRef = useRef<HTMLDivElement>(null);\n const inputRef = useRef<HTMLInputElement>(null);\n const [focused, setFocused] = React.useState(false);\n\n // Track mouse state for distinguishing click vs drag\n const mouseDownRef = useRef<{\n x: number;\n time: number;\n hasMoved: boolean;\n } | null>(null);\n const dragThreshold = 3; // pixels\n const clickTimeThreshold = 200; // milliseconds\n\n // Combine refs\n React.useImperativeHandle(ref, () => inputRef.current as HTMLInputElement, [\n inputRef,\n ]);\n\n const {\n displayValue,\n isEditing,\n isDragging,\n isHovered,\n error: internalError,\n increment,\n decrement,\n startDrag,\n updateDrag,\n endDrag,\n startEditing,\n endEditing,\n updateDisplayValue,\n setHovered,\n handleKeyDown,\n } = useNumberInput({\n value,\n onChange,\n ...(min !== undefined && { min }),\n ...(max !== undefined && { max }),\n ...(softMin !== undefined && { softMin }),\n ...(softMax !== undefined && { softMax }),\n ...(step !== undefined && { step }),\n ...(precisionStep !== undefined && { precisionStep }),\n ...(largeStep !== undefined && { largeStep }),\n ...(precision !== undefined && { precision }),\n allowExpressions,\n disabled: disabled || readOnly,\n dragSensitivity,\n ...(formatValue !== undefined && { formatValue }),\n ...(parseValue !== undefined && { parseValue }),\n });\n\n // External validation\n const externalError = validate ? validate(value) : undefined;\n const effectiveError = !!errorMessage || !!externalError || !!internalError;\n const effectiveErrorMessage = errorMessage ?? externalError ?? internalError;\n\n // Mouse event handlers for dragging vs clicking\n const handleMouseDown = (event: React.MouseEvent) => {\n if (disabled || readOnly) return;\n\n // Only handle left mouse button\n if (event.button !== 0) return;\n\n // Don't handle if clicking on step buttons\n const target = event.target as HTMLElement;\n if (target.tagName === 'BUTTON' || target.closest('button')) return;\n\n // Record mouse down state\n mouseDownRef.current = {\n x: event.clientX,\n time: Date.now(),\n hasMoved: false,\n };\n\n event.preventDefault();\n };\n\n const handleMouseMove = (event: React.MouseEvent) => {\n if (!mouseDownRef.current || disabled || readOnly) return;\n\n const deltaX = Math.abs(event.clientX - mouseDownRef.current.x);\n\n // If we've moved beyond threshold and not already dragging, start drag\n if (\n deltaX > dragThreshold &&\n !mouseDownRef.current.hasMoved &&\n !isDragging\n ) {\n mouseDownRef.current.hasMoved = true;\n startDrag(mouseDownRef.current.x);\n }\n };\n\n const handleMouseUp = () => {\n if (!mouseDownRef.current || disabled || readOnly) return;\n\n const timeDelta = Date.now() - mouseDownRef.current.time;\n const wasClick =\n !mouseDownRef.current.hasMoved && timeDelta < clickTimeThreshold;\n\n // If it was a click (not drag), start editing\n if (wasClick && !isEditing) {\n startEditing();\n // Focus the input after a short delay to ensure it's visible\n setTimeout(() => {\n inputRef.current?.focus();\n }, 0);\n }\n\n mouseDownRef.current = null;\n };\n\n // Global mouse move and up handlers for dragging\n useEffect(() => {\n if (!isDragging) return;\n\n const handleGlobalMouseMove = (event: MouseEvent) => {\n updateDrag(event.clientX);\n };\n\n const handleGlobalMouseUp = () => {\n endDrag();\n mouseDownRef.current = null;\n };\n\n document.addEventListener('mousemove', handleGlobalMouseMove);\n document.addEventListener('mouseup', handleGlobalMouseUp);\n\n return () => {\n document.removeEventListener('mousemove', handleGlobalMouseMove);\n document.removeEventListener('mouseup', handleGlobalMouseUp);\n };\n }, [isDragging, updateDrag, endDrag]);\n\n // Input event handlers\n const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {\n updateDisplayValue(event.target.value);\n };\n\n const handleInputFocus = (event: React.FocusEvent<HTMLInputElement>) => {\n setFocused(true);\n onFocus?.(event);\n };\n\n const handleInputBlur = (event: React.FocusEvent<HTMLInputElement>) => {\n setFocused(false);\n endEditing();\n onBlur?.(event);\n };\n\n const handleInputKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {\n handleKeyDown(event);\n onKeyDown?.(event);\n };\n\n // Container event handlers\n const handleContainerMouseEnter = () => {\n if (!disabled && !readOnly) {\n setHovered(true);\n }\n };\n\n const handleContainerMouseLeave = () => {\n setHovered(false);\n // Reset mouse state if leaving container\n mouseDownRef.current = null;\n };\n\n // Step button handlers\n const handleIncrementClick = (event: React.MouseEvent) => {\n event.preventDefault();\n event.stopPropagation();\n increment();\n // Don't focus input after step button click\n };\n\n const handleDecrementClick = (event: React.MouseEvent) => {\n event.preventDefault();\n event.stopPropagation();\n decrement();\n // Don't focus input after step button click\n };\n\n // Show step buttons when appropriate\n const shouldShowStepButtons =\n showStepButtons && isHovered && !isEditing && !disabled && !readOnly;\n\n // Format unit display\n const unitDisplay = typeof unit === 'function' ? unit(value) : unit;\n\n // Format display value\n const formattedValue = formatValue ? formatValue(value) : displayValue;\n\n const inputId = React.useId();\n\n return (\n // The mouse handlers below implement a pointer-only drag-to-scrub\n // enhancement on this presentational wrapper. Keyboard accessibility is\n // provided by the inner native <input> (handleInputKeyDown) and the\n // native step <button>s, so no role/keyboard handlers belong on the\n // wrapper itself.\n // eslint-disable-next-line jsx-a11y/no-static-element-interactions\n <div\n ref={containerRef}\n className={cx(\n numberInputContainerStyle,\n isHovered && !disabled && !isDragging && containerHoveredStyle,\n isDragging && containerDraggingStyle,\n className\n )}\n onMouseEnter={handleContainerMouseEnter}\n onMouseLeave={handleContainerMouseLeave}\n onMouseDown={handleMouseDown}\n onMouseMove={handleMouseMove}\n onMouseUp={handleMouseUp}\n data-testid={testId}\n >\n {label && (\n <label htmlFor={inputId} className={labelRecipe({ disabled })}>\n {label}\n {required && <span style={{ color: 'var(--accent-error)' }}> *</span>}\n </label>\n )}\n\n <div\n className={inputWrapperRecipe({\n size,\n error: effectiveError,\n disabled,\n focused,\n })}\n >\n {/* Decrement button */}\n {showStepButtons && (\n <button\n type=\"button\"\n className={stepButtonRecipe({\n position: 'left',\n size,\n visible: shouldShowStepButtons,\n })}\n onClick={handleDecrementClick}\n disabled={disabled}\n tabIndex={-1}\n aria-label=\"Decrement value\"\n >\n <span className={stepButtonIconLeftStyle}>\n <ChevronDownIcon size={size === 'sm' ? 'sm' : 'md'} />\n </span>\n </button>\n )}\n\n {/* Input field - hidden when not editing */}\n <input\n {...props}\n ref={inputRef}\n id={inputId}\n type=\"text\"\n value={isEditing ? displayValue : ''}\n onChange={handleInputChange}\n onFocus={handleInputFocus}\n onBlur={handleInputBlur}\n onKeyDown={handleInputKeyDown}\n placeholder={isEditing ? placeholder : ''}\n disabled={disabled}\n required={required}\n readOnly={readOnly}\n className={inputRecipe({ size })}\n style={{\n opacity: isEditing ? 1 : 0,\n pointerEvents: isEditing ? 'auto' : 'none',\n }}\n />\n\n {/* Value display - shown when not editing */}\n {!isEditing && (\n <div\n className={valueDisplayRecipe({\n size,\n hasStepButtons: shouldShowStepButtons,\n })}\n >\n <span>{formattedValue}</span>\n {unitDisplay && (\n <span className={unitLabelStyle}>{unitDisplay}</span>\n )}\n </div>\n )}\n\n {/* Increment button */}\n {showStepButtons && (\n <button\n type=\"button\"\n className={stepButtonRecipe({\n position: 'right',\n size,\n visible: shouldShowStepButtons,\n })}\n onClick={handleIncrementClick}\n disabled={disabled}\n tabIndex={-1}\n aria-label=\"Increment value\"\n >\n <span className={stepButtonIconRightStyle}>\n <ChevronDownIcon size={size === 'sm' ? 'sm' : 'md'} />\n </span>\n </button>\n )}\n </div>\n\n {(helperText ?? (effectiveError && effectiveErrorMessage)) && (\n <div className={helperTextRecipe({ error: effectiveError })}>\n {effectiveError && effectiveErrorMessage\n ? effectiveErrorMessage\n : helperText}\n </div>\n )}\n </div>\n );\n};\n\nNumberInput.displayName = 'NumberInput';\n"],"names":[],"mappings":";;;;;;;;;AA6IA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2CG;AACI;AACL;AACA;AAGA;AACA;AAMA;AACA;AAWA;AACA;AAgBA;AACA;AACA;;AAGA;AAKA;AACA;;;;AAKC;AAED;;;;;;;;;;;;;;;;AAgCC;;AAGD;AACA;AACA;;AAGA;;;;AAIE;;;AAGA;;;;;;AAME;AACA;;;AAIJ;AAEA;AACE;;AAEA;;;AAKE;;AAGA;AACA;;AAEJ;;AAGE;;AAEA;AACA;;AAIA;AACE;;;AAGE;;;AAIJ;AACF;;;AAIE;;AAEA;AACE;AACF;;AAGE;AACA;AACF;AAEA;AACA;AAEA;AACE;AACA;AACF;;;AAIF;AACE;AACF;AAEA;;AAEE;AACF;AAEA;;AAEE;AACA;AACF;AAEA;;AAEE;AACF;;;AAIE;;;AAGF;;;;AAKE;AACF;;AAGA;;;AAGE;;AAEF;AAEA;;;AAGE;;AAEF;;AAGA;;AAIA;;AAGA;AAEA;;;;;;;;AASE;;AAyBM;;;AAGD;AAOK;;AAEA;;;;;;AAwCA;AACD;AAcC;;AAEA;AACD;AAgBD;AACA;AAKZ;AAEA;;"}
@@ -1 +1 @@
1
- {"version":3,"file":"useNumberInput.js","sources":["../../../../../../src/components/controls/NumberInput/useNumberInput.ts"],"sourcesContent":[null],"names":[],"mappings":";;;;;;AAwMA;;AAEG;AACH;;AAEE;;AACA;;AACA;AACF;AAEA;;AAEG;AACH;;AAC+B;;;AAG/B;AAEA;;AAEG;AACH;AACE;AACE;;;AAGF;AACF;AAEA;;;;;;;;;;;;AAYG;;;AAmBD;AACA;AACA;;;AAKI;;;;;;;;AAUJ;AACA;AACA;;AAGA;;;AAIE;;AAEI;AACA;;;;;;;;AAQN;AAEA;;AAEG;AACH;;AACuB;AACrB;AAAyC;AACzC;AACF;AACE;AACA;AACA;;;;AAID;AAED;;AAEG;;AAGC;;;;;AAMA;AACA;;AAGA;;;AAGF;AAIF;;AAEG;AACH;AACE;;;AAGI;;;;AAIE;;;;;AAMF;;AAEE;;AAEA;;;AAEA;AACA;;;;AAKJ;AACA;;;AAGE;;;AAIF;;;;AAGA;AACA;;;;AAKJ;AACE;;AACA;AACA;;AAGF;AACE;;AACA;AACA;;AAGF;AAEI;;;AAEA;AACA;AACA;;AAEF;AAIF;;;AAII;AACA;;;AAIA;;;;;AAME;;;AAIA;;;AAIE;AACA;;;AAGN;;;;;;;;AASC;AAGH;;;;;AAKI;AACA;;;AAIJ;AACE;;;;;;AAKE;AACA;AACE;AACA;;;AAGN;AAEA;;;;;AAKA;AAEA;;;AAGI;AACA;;;;AAKJ;;;;;;AAOE;AAIF;AAEI;;AAEA;AACE;;;AAGI;;;AAEA;;;AAIJ;;;AAGI;;;AAIJ;;;AAGI;;;AAIJ;;;AAGI;;;AAIJ;;;;AAII;;;;AAIR;;;;;;;;;;AAWC;;;;;;;;;;;AAcD;;;;;;;;;;;AAYA;AACA;;;AAGJ;;"}
1
+ {"version":3,"file":"useNumberInput.js","sources":["src/components/controls/NumberInput/useNumberInput.ts"],"sourcesContent":["'use client';\n\nimport React, { useState, useCallback, useRef, useEffect } from 'react';\n\nimport { useKeyboardContext } from '@/context/KeyboardContext';\nimport { devError } from '@/utils/devWarn';\nimport { parseNumericInput, isExpression } from '@/utils/mathExpression';\n\n/**\n * Configuration options for NumberInput behavior\n */\nexport interface UseNumberInputOptions {\n /**\n * Minimum allowed value (hard limit)\n */\n min?: number;\n\n /**\n * Maximum allowed value (hard limit)\n */\n max?: number;\n\n /**\n * Soft minimum for drag operations (can be overridden by keyboard)\n */\n softMin?: number;\n\n /**\n * Soft maximum for drag operations (can be overridden by keyboard)\n */\n softMax?: number;\n\n /**\n * Step size for increment/decrement operations\n * @default 1\n */\n step?: number;\n\n /**\n * Step size when Shift is held (precision mode)\n * @default step / 10\n */\n precisionStep?: number;\n\n /**\n * Step size when Ctrl is held (large steps)\n * @default step * 10\n */\n largeStep?: number;\n\n /**\n * Number of decimal places to round to\n * @default 2\n */\n precision?: number;\n\n /**\n * Whether to allow mathematical expressions\n * @default true\n */\n allowExpressions?: boolean;\n\n /**\n * Whether the input is disabled\n * @default false\n */\n disabled?: boolean;\n\n /**\n * Drag sensitivity multiplier\n * @default 1\n */\n dragSensitivity?: number;\n\n /**\n * Custom format function for display\n */\n formatValue?: (value: number) => string;\n\n /**\n * Custom parse function for input\n */\n parseValue?: (input: string) => number | null;\n}\n\n/**\n * Props for useNumberInput hook\n */\nexport interface UseNumberInputProps extends UseNumberInputOptions {\n /**\n * Current numeric value\n */\n value: number;\n\n /**\n * Callback when value changes\n */\n onChange: (value: number) => void;\n}\n\n/**\n * State returned by useNumberInput hook\n */\nexport interface UseNumberInputReturn {\n /**\n * Current display value (string representation)\n */\n displayValue: string;\n\n /**\n * Whether currently in text editing mode\n */\n isEditing: boolean;\n\n /**\n * Whether currently dragging\n */\n isDragging: boolean;\n\n /**\n * Whether mouse is hovering over the input\n */\n isHovered: boolean;\n\n /**\n * Whether input has focus\n */\n isFocused: boolean;\n\n /**\n * Current error message (if any)\n */\n error: string | undefined;\n\n /**\n * Whether the current input looks like an expression\n */\n isExpression: boolean;\n\n /**\n * Increment value by step amount\n */\n increment: () => void;\n\n /**\n * Decrement value by step amount\n */\n decrement: () => void;\n\n /**\n * Start dragging operation\n */\n startDrag: (clientX: number) => void;\n\n /**\n * Update drag operation\n */\n updateDrag: (clientX: number) => void;\n\n /**\n * End dragging operation\n */\n endDrag: () => void;\n\n /**\n * Start text editing mode\n */\n startEditing: () => void;\n\n /**\n * End text editing mode and apply value\n */\n endEditing: () => void;\n\n /**\n * Cancel text editing mode without applying\n */\n cancelEditing: () => void;\n\n /**\n * Update the display value (during text editing)\n */\n updateDisplayValue: (value: string) => void;\n\n /**\n * Handle hover state changes\n */\n setHovered: (hovered: boolean) => void;\n\n /**\n * Handle focus state changes\n */\n setFocused: (focused: boolean) => void;\n\n /**\n * Handle keyboard input\n */\n handleKeyDown: (event: React.KeyboardEvent) => void;\n}\n\n/**\n * Clamps a value between min and max bounds\n */\nfunction clamp(value: number, min?: number, max?: number): number {\n let result = value;\n if (min !== undefined && result < min) result = min;\n if (max !== undefined && result > max) result = max;\n return result;\n}\n\n/**\n * Rounds a number to specified decimal places\n */\nfunction roundToPrecision(value: number, precision?: number): number {\n if (precision === undefined) return value;\n const factor = Math.pow(10, precision);\n return Math.round(value * factor) / factor;\n}\n\n/**\n * Default formatter for display values\n */\nfunction defaultFormatValue(value: number, precision?: number): string {\n if (precision !== undefined) {\n return value.toFixed(precision);\n }\n // Remove trailing zeros and unnecessary decimal point\n return value.toString();\n}\n\n/**\n * Hook for managing NumberInput state and interactions.\n *\n * Provides Blender-like number input functionality including:\n * - Drag to change values\n * - Keyboard input with expression evaluation\n * - Modifier key support (Ctrl, Shift)\n * - Soft and hard limits\n * - Precision control\n *\n * @param props Configuration options\n * @returns Object with state and actions\n */\nexport function useNumberInput({\n value,\n onChange,\n min,\n max,\n softMin,\n softMax,\n step = 1,\n precisionStep,\n largeStep,\n precision,\n allowExpressions = true,\n disabled = false,\n dragSensitivity = 1,\n formatValue,\n parseValue,\n}: UseNumberInputProps): UseNumberInputReturn {\n // Calculate default step sizes if not provided\n const effectivePrecisionStep = precisionStep ?? step / 10;\n const effectiveLargeStep = largeStep ?? step * 10;\n const effectivePrecision = precision ?? 2;\n\n // State\n const [displayValue, setDisplayValue] = useState(() =>\n formatValue\n ? formatValue(value)\n : defaultFormatValue(value, effectivePrecision)\n );\n const [isEditing, setIsEditing] = useState(false);\n const [isDragging, setIsDragging] = useState(false);\n const [isHovered, setIsHovered] = useState(false);\n const [isFocused, setIsFocused] = useState(false);\n const [error, setError] = useState<string | undefined>();\n\n // Refs for drag state\n const dragStartX = useRef(0);\n const dragStartValue = useRef(0);\n const accumulatedDelta = useRef(0);\n\n // Keyboard state\n const { modifiers } = useKeyboardContext();\n\n // Update display value when external value changes (but not during editing)\n useEffect(() => {\n if (!isEditing && !isDragging) {\n const formatted = formatValue\n ? formatValue(value)\n : defaultFormatValue(value, effectivePrecision);\n // Sync the display string to the controlled `value` prop while the user\n // is not actively editing/dragging — an external-value sync that the\n // edit guard makes safe from cascading renders.\n // eslint-disable-next-line react-hooks/set-state-in-effect\n setDisplayValue(formatted);\n setError(undefined);\n }\n }, [value, effectivePrecision, isEditing, isDragging, formatValue]);\n\n /**\n * Gets the appropriate step size based on modifier keys\n */\n const getStepSize = useCallback((): number => {\n if (modifiers.shift) return effectivePrecisionStep;\n if (modifiers.control || modifiers.meta) return effectiveLargeStep;\n return step;\n }, [\n modifiers.shift,\n modifiers.control,\n modifiers.meta,\n effectivePrecisionStep,\n effectiveLargeStep,\n step,\n ]);\n\n /**\n * Applies a new value with proper bounds checking and rounding\n */\n const applyValue = useCallback(\n (newValue: number, useSoftLimits = false): void => {\n if (disabled) return;\n\n // Apply rounding\n const rounded = roundToPrecision(newValue, effectivePrecision);\n\n // Apply limits\n const minLimit = useSoftLimits && softMin !== undefined ? softMin : min;\n const maxLimit = useSoftLimits && softMax !== undefined ? softMax : max;\n const clamped = clamp(rounded, minLimit, maxLimit);\n\n if (clamped !== value) {\n onChange(clamped);\n }\n },\n [disabled, effectivePrecision, min, max, softMin, softMax, value, onChange]\n );\n\n /**\n * Parses and applies text input value\n */\n const parseAndApplyInput = useCallback((): boolean => {\n try {\n // Try custom parser first\n if (parseValue) {\n const parsed = parseValue(displayValue);\n if (parsed !== null && !isNaN(parsed)) {\n applyValue(parsed);\n setError(undefined);\n return true;\n }\n }\n\n // Try expression evaluation if allowed\n if (allowExpressions) {\n const result = parseNumericInput(displayValue);\n if (result.success && result.value !== undefined) {\n applyValue(result.value);\n setError(undefined);\n return true;\n } else {\n setError(result.error ?? 'Invalid expression');\n return false;\n }\n }\n\n // Fall back to simple number parsing\n const numValue = parseFloat(displayValue);\n if (!isNaN(numValue)) {\n applyValue(numValue);\n setError(undefined);\n return true;\n }\n\n setError('Invalid number');\n return false;\n } catch (err) {\n setError('Invalid input');\n devError('Error parsing input:', err);\n return false;\n }\n }, [displayValue, parseValue, allowExpressions, applyValue]);\n\n // Actions\n const increment = useCallback(() => {\n if (disabled) return;\n const currentStep = getStepSize();\n applyValue(value + currentStep);\n }, [disabled, getStepSize, value, applyValue]);\n\n const decrement = useCallback(() => {\n if (disabled) return;\n const currentStep = getStepSize();\n applyValue(value - currentStep);\n }, [disabled, getStepSize, value, applyValue]);\n\n const startDrag = useCallback(\n (clientX: number) => {\n if (disabled) return;\n setIsDragging(true);\n dragStartX.current = clientX;\n dragStartValue.current = value;\n accumulatedDelta.current = 0;\n document.body.style.cursor = 'ew-resize';\n },\n [disabled, value]\n );\n\n const updateDrag = useCallback(\n (clientX: number) => {\n if (!isDragging || disabled) return;\n\n const deltaX = clientX - dragStartX.current;\n const currentStep = getStepSize();\n const sensitivity = dragSensitivity;\n\n // Accumulate fractional changes for smooth dragging\n const pixelsPerStep = 5; // Pixels needed to trigger one step\n const totalDelta = (deltaX * sensitivity) / pixelsPerStep;\n\n // Only update if we've moved enough for at least one step\n const steps = Math.floor(totalDelta);\n if (steps !== Math.floor(accumulatedDelta.current)) {\n accumulatedDelta.current = totalDelta;\n const newValue = dragStartValue.current + steps * currentStep;\n\n // Use soft limits for dragging\n applyValue(newValue, true);\n\n // Update display to show current value\n const formatted = formatValue\n ? formatValue(newValue)\n : defaultFormatValue(newValue, effectivePrecision);\n setDisplayValue(formatted);\n }\n },\n [\n isDragging,\n disabled,\n getStepSize,\n dragSensitivity,\n applyValue,\n formatValue,\n effectivePrecision,\n ]\n );\n\n const endDrag = useCallback(() => {\n setIsDragging(false);\n document.body.style.cursor = '';\n // Refresh display value to ensure it matches the current value\n const formatted = formatValue\n ? formatValue(value)\n : defaultFormatValue(value, effectivePrecision);\n setDisplayValue(formatted);\n }, [value, formatValue, effectivePrecision]);\n\n const startEditing = useCallback(() => {\n if (disabled) return;\n setIsEditing(true);\n setError(undefined);\n // Place cursor at end of input\n setTimeout(() => {\n const input = document.activeElement;\n if (input instanceof HTMLInputElement) {\n const len = input.value.length;\n input.setSelectionRange(len, len);\n }\n }, 0);\n }, [disabled]);\n\n const endEditing = useCallback(() => {\n if (parseAndApplyInput()) {\n setIsEditing(false);\n }\n // If parsing failed, stay in editing mode to show error\n }, [parseAndApplyInput]);\n\n const cancelEditing = useCallback(() => {\n setIsEditing(false);\n const formatted = formatValue\n ? formatValue(value)\n : defaultFormatValue(value, effectivePrecision);\n setDisplayValue(formatted);\n setError(undefined);\n }, [value, effectivePrecision, formatValue]);\n\n const updateDisplayValue = useCallback(\n (newValue: string) => {\n setDisplayValue(newValue);\n // Clear error when user starts typing\n if (error) {\n setError(undefined);\n }\n },\n [error]\n );\n\n const handleKeyDown = useCallback(\n (event: React.KeyboardEvent) => {\n if (disabled) return;\n\n switch (event.key) {\n case 'Enter':\n event.preventDefault();\n if (isEditing) {\n endEditing();\n } else {\n startEditing();\n }\n break;\n\n case 'Escape':\n event.preventDefault();\n if (isEditing) {\n cancelEditing();\n }\n break;\n\n case 'ArrowUp':\n if (!isEditing) {\n event.preventDefault();\n increment();\n }\n break;\n\n case 'ArrowDown':\n if (!isEditing) {\n event.preventDefault();\n decrement();\n }\n break;\n\n case '-':\n // Only negate when not editing and minus is pressed alone (not in the middle of text)\n if (!isEditing) {\n event.preventDefault();\n applyValue(-value);\n }\n break;\n }\n },\n [\n disabled,\n isEditing,\n endEditing,\n startEditing,\n cancelEditing,\n increment,\n decrement,\n value,\n applyValue,\n ]\n );\n\n // Determine if current input looks like an expression\n const isExpressionValue = allowExpressions && isExpression(displayValue);\n\n return {\n // State\n displayValue,\n isEditing,\n isDragging,\n isHovered,\n isFocused,\n error,\n isExpression: isExpressionValue,\n\n // Actions\n increment,\n decrement,\n startDrag,\n updateDrag,\n endDrag,\n startEditing,\n endEditing,\n cancelEditing,\n updateDisplayValue,\n setHovered: setIsHovered,\n setFocused: setIsFocused,\n handleKeyDown,\n };\n}\n"],"names":[],"mappings":";;;;;;AAwMA;;AAEG;AACH;;AAEE;;AACA;;AACA;AACF;AAEA;;AAEG;AACH;;AAC+B;;;AAG/B;AAEA;;AAEG;AACH;AACE;AACE;;;AAGF;AACF;AAEA;;;;;;;;;;;;AAYG;;;AAmBD;AACA;AACA;;;AAKI;;;;;;;;AAUJ;AACA;AACA;;AAGA;;;AAIE;;AAEI;AACA;;;;;;;;AAQN;AAEA;;AAEG;AACH;;AACuB;AACrB;AAAyC;AACzC;AACF;AACE;AACA;AACA;;;;AAID;AAED;;AAEG;;AAGC;;;;;AAMA;AACA;;AAGA;;;AAGF;AAIF;;AAEG;AACH;AACE;;;AAGI;;;;AAIE;;;;;AAMF;;AAEE;;AAEA;;;AAEA;AACA;;;;AAKJ;AACA;;;AAGE;;;AAIF;;;;AAGA;AACA;;;;AAKJ;AACE;;AACA;AACA;;AAGF;AACE;;AACA;AACA;;AAGF;AAEI;;;AAEA;AACA;AACA;;AAEF;AAIF;;;AAII;AACA;;;AAIA;;;;;AAME;;;AAIA;;;AAIE;AACA;;;AAGN;;;;;;;;AASC;AAGH;;;;;AAKI;AACA;;;AAIJ;AACE;;;;;;AAKE;AACA;AACE;AACA;;;AAGN;AAEA;;;;;AAKA;AAEA;;;AAGI;AACA;;;;AAKJ;;;;;;AAOE;AAIF;AAEI;;AAEA;AACE;;;AAGI;;;AAEA;;;AAIJ;;;AAGI;;;AAIJ;;;AAGI;;;AAIJ;;;AAGI;;;AAIJ;;;;AAII;;;;AAIR;;;;;;;;;;AAWC;;;;;;;;;;;AAcD;;;;;;;;;;;AAYA;AACA;;;AAGJ;;"}
@@ -1,4 +1,4 @@
1
- import './../../../assets/src/components/controls/Select/Select.css.ts.vanilla-oZnFUorL.css';
1
+ import './../../../assets/src/components/controls/Select/Select.css.ts.vanilla-Cpu2BvWL.css';
2
2
  import { createRuntimeFn } from '@vanilla-extract/recipes/createRuntimeFn';
3
3
 
4
4
  var checkmarkStyle = 'Select_checkmarkStyle__ycpn7018';
@@ -58,7 +58,7 @@ const CHEVRON_SIZES = {
58
58
  * />
59
59
  * ```
60
60
  */
61
- function Select({ value: valueProp, defaultValue, options, placeholder = 'Select...', searchable = false, searchPlaceholder = 'Search...', filterFn, emptyMessage = 'No results found', size = 'md', variant = 'default', label, helperText, error = false, errorMessage, disabled = false, required = false, clearable = false, maxDropdownHeight = 240, minDropdownWidth, name, onChange, onOpenChange, className, style, testId, ref, id: idProp, ...rest }) {
61
+ function Select({ value: valueProp, defaultValue, options, placeholder = 'Select...', searchable = false, searchPlaceholder = 'Search...', filterFn, emptyMessage = 'No results found', size = 'md', variant = 'default', label, helperText, error = false, errorMessage, disabled = false, required = false, clearable = false, maxDropdownHeight = 240, minDropdownWidth, fullWidth = false, name, onChange, onOpenChange, className, style, testId, ref, id: idProp, ...rest }) {
62
62
  const autoId = useId();
63
63
  const selectId = idProp ?? autoId;
64
64
  const labelId = `${selectId}-label`;
@@ -93,12 +93,17 @@ function Select({ value: valueProp, defaultValue, options, placeholder = 'Select
93
93
  const rect = trigger.getBoundingClientRect();
94
94
  const spaceBelow = window.innerHeight - rect.bottom;
95
95
  const openAbove = spaceBelow < maxDropdownHeight + 8 && rect.top > spaceBelow;
96
- const dropdownW = minDropdownWidth !== undefined
97
- ? Math.max(rect.width, minDropdownWidth)
98
- : rect.width;
96
+ // The dropdown sizes to its content (`width: max-content` in CSS). Anchor
97
+ // that growth between a minimum (at least the trigger, and `minDropdownWidth`
98
+ // when set) and a maximum that keeps it inside the viewport so long option
99
+ // labels are never clipped by a narrow trigger.
100
+ const VIEWPORT_MARGIN = 8;
101
+ const minWidth = Math.max(rect.width, minDropdownWidth ?? 0);
102
+ const maxWidth = Math.max(minWidth, window.innerWidth - rect.left - VIEWPORT_MARGIN);
99
103
  setDropdownStyleState({
100
104
  left: rect.left,
101
- width: dropdownW,
105
+ minWidth,
106
+ maxWidth,
102
107
  ...(openAbove
103
108
  ? {
104
109
  bottom: window.innerHeight - rect.top + 4,
@@ -127,6 +132,9 @@ function Select({ value: valueProp, defaultValue, options, placeholder = 'Select
127
132
  if (!isControlled) {
128
133
  setInternalValue(val);
129
134
  }
135
+ // `onChange` is typed per `clearable` (it only receives `null` from the
136
+ // clear button, which requires `clearable`). Internally we always call it
137
+ // with `T | null`; the public discriminated union enforces the contract.
130
138
  onChange?.(val);
131
139
  close();
132
140
  }, [isControlled, onChange, close]);
@@ -315,7 +323,7 @@ function Select({ value: valueProp, defaultValue, options, placeholder = 'Select
315
323
  }
316
324
  }, children: [opt.icon && jsx("span", { children: opt.icon }), jsx("span", { children: opt.label ?? opt.value }), isSelected && (jsx("span", { className: checkmarkStyle, children: jsx(CheckIcon, { size: 10, decorative: true }) }))] }, opt.value));
317
325
  };
318
- return (jsxs("div", { className: cx(selectContainerStyle, className), style: style, children: [label && (jsx(FormLabel, { id: labelId, htmlFor: selectId, required: required, disabled: disabled, children: label })), jsxs("button", { ref: setTriggerRef, type: "button", role: "combobox", "aria-haspopup": "listbox", "aria-expanded": isOpen, "aria-controls": isOpen ? listboxId : undefined, "aria-labelledby": label ? labelId : undefined, "aria-required": required || undefined, "aria-invalid": error || undefined, "aria-describedby": showHelperText ? helperId : undefined, disabled: disabled, onClick: () => {
326
+ return (jsxs("div", { className: cx(selectContainerStyle, className), style: { ...(fullWidth ? { width: '100%' } : null), ...style }, children: [label && (jsx(FormLabel, { id: labelId, htmlFor: selectId, required: required, disabled: disabled, children: label })), jsxs("button", { ref: setTriggerRef, type: "button", role: "combobox", "aria-haspopup": "listbox", "aria-expanded": isOpen, "aria-controls": isOpen ? listboxId : undefined, "aria-labelledby": label ? labelId : undefined, "aria-required": required || undefined, "aria-invalid": error || undefined, "aria-describedby": showHelperText ? helperId : undefined, disabled: disabled, onClick: () => {
319
327
  if (isOpen) {
320
328
  close();
321
329
  }
@@ -329,7 +337,7 @@ function Select({ value: valueProp, defaultValue, options, placeholder = 'Select
329
337
  disabled,
330
338
  error,
331
339
  hasValue: currentValue !== null,
332
- }), "data-testid": testId, ...rest, children: [jsx("span", { className: triggerContentStyle, children: selectedOption ? (jsxs(Fragment, { children: [selectedOption.icon && jsx("span", { children: selectedOption.icon }), jsx("span", { children: selectedOption.label ?? selectedOption.value })] })) : (jsx("span", { children: placeholder })) }), clearable && currentValue !== null && (jsx("span", { className: clearButtonStyle, role: "button", onClick: handleClear, onKeyDown: e => {
340
+ }), style: fullWidth ? { width: '100%' } : undefined, "data-testid": testId, ...rest, children: [jsx("span", { className: triggerContentStyle, children: selectedOption ? (jsxs(Fragment, { children: [selectedOption.icon && jsx("span", { children: selectedOption.icon }), jsx("span", { children: selectedOption.label ?? selectedOption.value })] })) : (jsx("span", { children: placeholder })) }), clearable && currentValue !== null && (jsx("span", { className: clearButtonStyle, role: "button", onClick: handleClear, onKeyDown: e => {
333
341
  if (e.key === 'Enter' || e.key === ' ') {
334
342
  e.preventDefault();
335
343
  e.stopPropagation();
@@ -1 +1 @@
1
- {"version":3,"file":"Select.js","sources":["../../../../../../src/components/controls/Select/Select.tsx"],"sourcesContent":[null],"names":[],"mappings":";;;;;;;;;;;;;AA0CA;AAEA;AAGE;AACF;AAEA;;AAIE;AACE;;;;AAGE;;;AAGJ;AACF;AAEA;AAIE;;AAEF;AAEA;AAEA;AACE;AACA;AACA;;AAGF;AAEA;;;;;;;;;;;;;;;;;;;AAmBG;;AA+BD;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;;AAGA;;;;AAQA;;;AAIA;;AAGA;AACA;AACE;AAAiC;AACjC;;;AAIF;;AAMA;AACE;AACA;;AAEA;;AAEA;AAGA;;AAGI;AAEJ;;AAEE;AACA;AACE;;AAEI;AACD;AACH;AACH;AACH;AAEA;AACE;;;;AAGA;AACA;;AAEF;AAEA;;;AAGE;AACA;AACA;AACF;AAEA;;;;AAKI;AACA;;;;;AAQA;;;;AAII;AACF;;;;;;AAOJ;;AAEA;AACE;;;AAKE;;AAEJ;AAEA;AACA;AACE;AACF;AACF;;AAGA;;AAIO;AAIP;AAEI;;AAEA;;;AAIE;AACE;;AAEF;;;AAGA;;AAGI;AAEA;;;;;;AAKJ;AACE;;;;;;;;;AASN;;AAKF;AAEI;;AAEA;AACE;AACA;AACA;;;AAGI;;;AAGJ;;;AAGI;;;;;;AASV;AAEI;AACE;;;;AAIA;;;;AAIA;;;;AAIA;;;;AAIA;;;AAII;AAEA;AACA;AACE;;;;AAIN;;AAEE;;;AAGN;AAIF;;;AAIE;AAIF;AACA;;AAGA;AAEI;AACA;;;;AAGE;;AAEJ;;;AAMA;AACE;;;;AAMF;;;AAGI;AACE;;AAEA;AAEA;AAIM;AACA;AACF;;;;AAON;AACF;;AAGF;AACF;AAEA;AACE;AACA;;AAGE;AACE;;AAEJ;;AAUM;AACA;AACA;;AAIA;;AAEE;;AAEJ;AAEE;;;;AAcR;;;AA6BU;;;AAEA;;;;;AAOF;;;;AAID;AAqBK;;;;;AAKF;AAgBJ;AAkBU;AACA;AACF;AA0BhB;AAEA;;"}
1
+ {"version":3,"file":"Select.js","sources":["src/components/controls/Select/Select.tsx"],"sourcesContent":["'use client';\n\nimport React, {\n useCallback,\n useEffect,\n useId,\n useMemo,\n useRef,\n useState,\n} from 'react';\nimport { createPortal } from 'react-dom';\n\nimport { FormHelperText } from '@/components/form/FormHelperText';\nimport { FormLabel } from '@/components/form/FormLabel';\nimport { CheckIcon } from '@/components/Icons/CheckIcon';\nimport { ChevronDownIcon } from '@/components/Icons/ChevronDownIcon';\nimport { CloseIcon } from '@/components/Icons/CloseIcon';\nimport { ScrollArea } from '@/components/layout/ScrollArea';\nimport { cx } from '@/utils/cx';\n\nimport {\n selectContainerStyle,\n triggerRecipe,\n triggerContentStyle,\n chevronRecipe,\n clearButtonStyle,\n dropdownStyle,\n searchInputStyle,\n optionsListStyle,\n optionItemRecipe,\n groupLabelStyle,\n emptyMessageStyle,\n checkmarkStyle,\n} from './Select.css';\n\nimport type {\n SelectOptionItem,\n SelectOptionGroup,\n SelectProps,\n SelectSize,\n} from './Select.types';\n\n// --- Helpers ---\n\nfunction isOptionGroup<T extends string>(\n item: SelectOptionItem<T> | SelectOptionGroup<T>\n): item is SelectOptionGroup<T> {\n return 'options' in item && Array.isArray(item.options);\n}\n\nfunction flattenOptions<T extends string>(\n items: Array<SelectOptionItem<T> | SelectOptionGroup<T>>\n): SelectOptionItem<T>[] {\n const result: SelectOptionItem<T>[] = [];\n for (const item of items) {\n if (isOptionGroup(item)) {\n result.push(...item.options);\n } else {\n result.push(item);\n }\n }\n return result;\n}\n\nfunction defaultFilter<T extends string>(\n option: SelectOptionItem<T>,\n query: string\n): boolean {\n const label = (option.label ?? option.value).toLowerCase();\n return label.includes(query.toLowerCase());\n}\n\n// --- Size maps ---\n\nconst CHEVRON_SIZES: Record<SelectSize, number> = {\n sm: 10,\n md: 12,\n lg: 14,\n};\n\n// --- Component ---\n\n/**\n * Select component for choosing single values from a dropdown list.\n *\n * Supports searchable mode, grouped options, keyboard navigation,\n * clearable state, and multiple visual variants.\n *\n * @example\n * ```tsx\n * <Select\n * label=\"Blend Mode\"\n * options={[\n * { value: 'normal', label: 'Normal' },\n * { value: 'multiply', label: 'Multiply' },\n * { value: 'screen', label: 'Screen' },\n * ]}\n * value={blendMode}\n * onChange={setBlendMode}\n * />\n * ```\n */\nexport function Select<T extends string = string>({\n value: valueProp,\n defaultValue,\n options,\n placeholder = 'Select...',\n searchable = false,\n searchPlaceholder = 'Search...',\n filterFn,\n emptyMessage = 'No results found',\n size = 'md',\n variant = 'default',\n label,\n helperText,\n error = false,\n errorMessage,\n disabled = false,\n required = false,\n clearable = false,\n maxDropdownHeight = 240,\n minDropdownWidth,\n fullWidth = false,\n name,\n onChange,\n onOpenChange,\n className,\n style,\n testId,\n ref,\n id: idProp,\n ...rest\n}: SelectProps<T>) {\n const autoId = useId();\n const selectId = idProp ?? autoId;\n const labelId = `${selectId}-label`;\n const helperId = `${selectId}-helper`;\n const listboxId = `${selectId}-listbox`;\n\n const triggerRef = useRef<HTMLButtonElement | null>(null);\n const dropdownRef = useRef<HTMLDivElement>(null);\n const searchRef = useRef<HTMLInputElement>(null);\n\n const [isOpen, setIsOpen] = useState(false);\n const [internalValue, setInternalValue] = useState<T | null>(\n defaultValue ?? null\n );\n const [searchQuery, setSearchQuery] = useState('');\n const [highlightedIndex, setHighlightedIndex] = useState(-1);\n const [dropdownStyleState, setDropdownStyleState] =\n useState<React.CSSProperties>({});\n\n const isControlled = valueProp !== undefined;\n const currentValue = isControlled ? valueProp : internalValue;\n\n // Flatten all options for navigation\n const allOptions = useMemo(() => flattenOptions(options), [options]);\n\n // Filter options when searchable\n const filter = filterFn ?? defaultFilter;\n const filteredOptions = useMemo(() => {\n if (!searchable || !searchQuery) return allOptions;\n return allOptions.filter(opt => filter(opt, searchQuery));\n }, [allOptions, searchable, searchQuery, filter]);\n\n // Find selected option\n const selectedOption = useMemo(\n () => allOptions.find(o => o.value === currentValue) ?? null,\n [allOptions, currentValue]\n );\n\n // Position dropdown\n const updateDropdownPosition = useCallback(() => {\n const trigger = triggerRef.current;\n if (!trigger) return;\n\n const rect = trigger.getBoundingClientRect();\n const spaceBelow = window.innerHeight - rect.bottom;\n const openAbove =\n spaceBelow < maxDropdownHeight + 8 && rect.top > spaceBelow;\n\n // The dropdown sizes to its content (`width: max-content` in CSS). Anchor\n // that growth between a minimum (at least the trigger, and `minDropdownWidth`\n // when set) and a maximum that keeps it inside the viewport so long option\n // labels are never clipped by a narrow trigger.\n const VIEWPORT_MARGIN = 8;\n const minWidth = Math.max(rect.width, minDropdownWidth ?? 0);\n const maxWidth = Math.max(\n minWidth,\n window.innerWidth - rect.left - VIEWPORT_MARGIN\n );\n\n setDropdownStyleState({\n left: rect.left,\n minWidth,\n maxWidth,\n ...(openAbove\n ? {\n bottom: window.innerHeight - rect.top + 4,\n transformOrigin: 'bottom',\n }\n : { top: rect.bottom + 4, transformOrigin: 'top' }),\n });\n }, [maxDropdownHeight, minDropdownWidth]);\n\n const open = useCallback(() => {\n if (disabled) return;\n setIsOpen(true);\n setSearchQuery('');\n setHighlightedIndex(-1);\n onOpenChange?.(true);\n // Position is calculated in useEffect after render\n }, [disabled, onOpenChange]);\n\n const close = useCallback(() => {\n setIsOpen(false);\n setSearchQuery('');\n setHighlightedIndex(-1);\n onOpenChange?.(false);\n triggerRef.current?.focus();\n }, [onOpenChange]);\n\n const selectValue = useCallback(\n (val: T | null) => {\n if (!isControlled) {\n setInternalValue(val);\n }\n // `onChange` is typed per `clearable` (it only receives `null` from the\n // clear button, which requires `clearable`). Internally we always call it\n // with `T | null`; the public discriminated union enforces the contract.\n (onChange as ((value: T | null) => void) | undefined)?.(val);\n close();\n },\n [isControlled, onChange, close]\n );\n\n // Update position when open\n useEffect(() => {\n if (isOpen) {\n updateDropdownPosition();\n if (searchable) {\n // Focus search input after portal renders\n requestAnimationFrame(() => {\n searchRef.current?.focus();\n });\n }\n }\n }, [isOpen, updateDropdownPosition, searchable]);\n\n // Close on outside click\n useEffect(() => {\n if (!isOpen) return;\n\n const handleClick = (e: MouseEvent) => {\n const target = e.target as Node;\n if (\n !triggerRef.current?.contains(target) &&\n !dropdownRef.current?.contains(target)\n ) {\n close();\n }\n };\n\n document.addEventListener('mousedown', handleClick);\n return () => {\n document.removeEventListener('mousedown', handleClick);\n };\n }, [isOpen, close]);\n\n // Get navigable options (non-disabled)\n const navigableIndices = useMemo(\n () =>\n filteredOptions\n .map((opt, i) => (opt.disabled ? -1 : i))\n .filter(i => i !== -1),\n [filteredOptions]\n );\n\n const navigateHighlight = useCallback(\n (direction: 'next' | 'prev' | 'first' | 'last') => {\n if (navigableIndices.length === 0) return;\n\n let nextIndex: number;\n const currentNavIdx = navigableIndices.indexOf(highlightedIndex);\n\n switch (direction) {\n case 'first':\n nextIndex = navigableIndices[0] ?? 0;\n break;\n case 'last':\n nextIndex = navigableIndices[navigableIndices.length - 1] ?? 0;\n break;\n case 'next':\n if (\n currentNavIdx === -1 ||\n currentNavIdx >= navigableIndices.length - 1\n ) {\n nextIndex = navigableIndices[0] ?? 0;\n } else {\n nextIndex = navigableIndices[currentNavIdx + 1] ?? 0;\n }\n break;\n case 'prev':\n if (currentNavIdx <= 0) {\n nextIndex = navigableIndices[navigableIndices.length - 1] ?? 0;\n } else {\n nextIndex = navigableIndices[currentNavIdx - 1] ?? 0;\n }\n break;\n }\n\n setHighlightedIndex(nextIndex);\n },\n [navigableIndices, highlightedIndex]\n );\n\n // Keyboard handling for trigger\n const handleTriggerKeyDown = useCallback(\n (e: React.KeyboardEvent) => {\n if (disabled) return;\n\n switch (e.key) {\n case 'Enter':\n case ' ':\n case 'ArrowDown':\n e.preventDefault();\n if (!isOpen) {\n open();\n }\n break;\n case 'ArrowUp':\n e.preventDefault();\n if (!isOpen) {\n open();\n }\n break;\n }\n },\n [disabled, isOpen, open]\n );\n\n // Keyboard handling for dropdown\n const handleDropdownKeyDown = useCallback(\n (e: React.KeyboardEvent) => {\n switch (e.key) {\n case 'ArrowDown':\n e.preventDefault();\n navigateHighlight('next');\n break;\n case 'ArrowUp':\n e.preventDefault();\n navigateHighlight('prev');\n break;\n case 'Home':\n e.preventDefault();\n navigateHighlight('first');\n break;\n case 'End':\n e.preventDefault();\n navigateHighlight('last');\n break;\n case 'Enter':\n e.preventDefault();\n if (\n highlightedIndex >= 0 &&\n highlightedIndex < filteredOptions.length\n ) {\n const opt = filteredOptions[highlightedIndex];\n if (opt && !opt.disabled) {\n selectValue(opt.value);\n }\n }\n break;\n case 'Escape':\n e.preventDefault();\n close();\n break;\n }\n },\n [navigateHighlight, highlightedIndex, filteredOptions, selectValue, close]\n );\n\n const handleClear = useCallback(\n (e: React.MouseEvent) => {\n e.stopPropagation();\n selectValue(null);\n },\n [selectValue]\n );\n\n const showHelperText = error && errorMessage ? errorMessage : helperText;\n const chevronSize = CHEVRON_SIZES[size];\n\n // Assign ref\n const setTriggerRef = useCallback(\n (node: HTMLButtonElement | null) => {\n triggerRef.current = node;\n if (typeof ref === 'function') {\n ref(node);\n } else if (ref) {\n ref.current = node;\n }\n },\n [ref]\n );\n\n // Render options list\n const renderOptions = () => {\n if (filteredOptions.length === 0) {\n return <div className={emptyMessageStyle}>{emptyMessage}</div>;\n }\n\n // If options are grouped, render with group headers\n const hasGroups = options.some(isOptionGroup);\n\n if (hasGroups && !searchQuery) {\n let flatIndex = 0;\n return options.map((item, groupIdx) => {\n if (isOptionGroup(item)) {\n const groupOptions = item.options;\n const startIdx = flatIndex;\n flatIndex += groupOptions.length;\n\n return (\n <div key={`group-${groupIdx}`} role=\"group\" aria-label={item.label}>\n <div className={groupLabelStyle}>{item.label}</div>\n {groupOptions.map((opt, optIdx) => {\n const idx = startIdx + optIdx;\n return renderOptionItem(opt, idx);\n })}\n </div>\n );\n }\n\n const idx = flatIndex;\n flatIndex += 1;\n return renderOptionItem(item, idx);\n });\n }\n\n return filteredOptions.map((opt, idx) => renderOptionItem(opt, idx));\n };\n\n const renderOptionItem = (opt: SelectOptionItem<T>, index: number) => {\n const isSelected = opt.value === currentValue;\n const isHighlighted = index === highlightedIndex;\n\n const selectOption = () => {\n if (!opt.disabled) {\n selectValue(opt.value);\n }\n };\n\n return (\n <div\n key={opt.value}\n role=\"option\"\n tabIndex={-1}\n aria-selected={isSelected}\n aria-disabled={opt.disabled ?? undefined}\n className={optionItemRecipe({\n highlighted: isHighlighted,\n selected: isSelected,\n disabled: opt.disabled ?? false,\n })}\n onClick={selectOption}\n onKeyDown={e => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n selectOption();\n }\n }}\n onMouseEnter={() => {\n if (!opt.disabled) {\n setHighlightedIndex(index);\n }\n }}\n >\n {opt.icon && <span>{opt.icon}</span>}\n <span>{opt.label ?? opt.value}</span>\n {isSelected && (\n <span className={checkmarkStyle}>\n <CheckIcon size={10} decorative />\n </span>\n )}\n </div>\n );\n };\n\n return (\n <div\n className={cx(selectContainerStyle, className)}\n style={{ ...(fullWidth ? { width: '100%' } : null), ...style }}\n >\n {label && (\n <FormLabel\n id={labelId}\n htmlFor={selectId}\n required={required}\n disabled={disabled}\n >\n {label}\n </FormLabel>\n )}\n\n <button\n ref={setTriggerRef}\n type=\"button\"\n role=\"combobox\"\n aria-haspopup=\"listbox\"\n aria-expanded={isOpen}\n aria-controls={isOpen ? listboxId : undefined}\n aria-labelledby={label ? labelId : undefined}\n aria-required={required || undefined}\n aria-invalid={error || undefined}\n aria-describedby={showHelperText ? helperId : undefined}\n disabled={disabled}\n onClick={() => {\n if (isOpen) {\n close();\n } else {\n open();\n }\n }}\n onKeyDown={handleTriggerKeyDown}\n className={triggerRecipe({\n size,\n variant,\n open: isOpen,\n disabled,\n error,\n hasValue: currentValue !== null,\n })}\n style={fullWidth ? { width: '100%' } : undefined}\n data-testid={testId}\n {...rest}\n >\n <span className={triggerContentStyle}>\n {selectedOption ? (\n <>\n {selectedOption.icon && <span>{selectedOption.icon}</span>}\n <span>{selectedOption.label ?? selectedOption.value}</span>\n </>\n ) : (\n <span>{placeholder}</span>\n )}\n </span>\n\n {clearable && currentValue !== null && (\n <span\n className={clearButtonStyle}\n role=\"button\"\n onClick={handleClear}\n onKeyDown={e => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n e.stopPropagation();\n selectValue(null);\n }\n }}\n aria-label=\"Clear selection\"\n tabIndex={-1}\n >\n <CloseIcon size=\"sm\" decorative />\n </span>\n )}\n\n <span className={chevronRecipe({ open: isOpen })}>\n <ChevronDownIcon size={chevronSize} decorative />\n </span>\n </button>\n\n {name && <input type=\"hidden\" name={name} value={currentValue ?? ''} />}\n\n {isOpen &&\n createPortal(\n <div\n ref={dropdownRef}\n role=\"listbox\"\n id={listboxId}\n aria-labelledby={label ? labelId : undefined}\n className={dropdownStyle}\n style={dropdownStyleState}\n onKeyDown={handleDropdownKeyDown}\n tabIndex={-1}\n >\n {searchable && (\n <input\n ref={searchRef}\n type=\"text\"\n placeholder={searchPlaceholder}\n value={searchQuery}\n onChange={e => {\n setSearchQuery(e.target.value);\n setHighlightedIndex(-1);\n }}\n onKeyDown={handleDropdownKeyDown}\n className={searchInputStyle}\n aria-label=\"Search options\"\n />\n )}\n <ScrollArea\n className={optionsListStyle}\n maxHeight={maxDropdownHeight - (searchable ? 32 : 0)}\n scrollbarWidth={4}\n scrollbarVisibility=\"auto\"\n hideDelay={600}\n >\n {renderOptions()}\n </ScrollArea>\n </div>,\n document.body\n )}\n\n {showHelperText && (\n <FormHelperText id={helperId} error={error}>\n {showHelperText}\n </FormHelperText>\n )}\n </div>\n );\n}\n\nSelect.displayName = 'Select';\n"],"names":[],"mappings":";;;;;;;;;;;;;AA0CA;AAEA;AAGE;AACF;AAEA;;AAIE;AACE;;;;AAGE;;;AAGJ;AACF;AAEA;AAIE;;AAEF;AAEA;AAEA;AACE;AACA;AACA;;AAGF;AAEA;;;;;;;;;;;;;;;;;;;AAmBG;;AAgCD;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;;AAGA;;;;AAQA;;;AAIA;;AAGA;AACA;AACE;AAAiC;AACjC;;;AAIF;;AAMA;AACE;AACA;;AAEA;;AAEA;;;;;;AAQA;AACA;AAKA;;;;AAIE;AACE;;AAEI;AACD;AACH;AACH;AACH;AAEA;AACE;;;;AAGA;AACA;;AAEF;AAEA;;;AAGE;AACA;AACA;AACF;AAEA;;;;;;;AAQK;AACD;;;;;AAQA;;;;AAII;AACF;;;;;;AAOJ;;AAEA;AACE;;;AAKE;;AAEJ;AAEA;AACA;AACE;AACF;AACF;;AAGA;;AAIO;AAIP;AAEI;;AAEA;;;AAIE;AACE;;AAEF;;;AAGA;;AAGI;AAEA;;;;;;AAKJ;AACE;;;;;;;;;AASN;;AAKF;AAEI;;AAEA;AACE;AACA;AACA;;;AAGI;;;AAGJ;;;AAGI;;;;;;AASV;AAEI;AACE;;;;AAIA;;;;AAIA;;;;AAIA;;;;AAIA;;;AAII;AAEA;AACA;AACE;;;;AAIN;;AAEE;;;AAGN;AAIF;;;AAIE;AAIF;AACA;;AAGA;AAEI;AACA;;;;AAGE;;AAEJ;;;AAMA;AACE;;;;AAMF;;;AAGI;AACE;;AAEA;AAEA;AAIM;AACA;AACF;;;;AAON;AACF;;AAGF;AACF;AAEA;AACE;AACA;;AAGE;AACE;;AAEJ;;AAUM;AACA;AACA;;AAIA;;AAEE;;AAEJ;AAEE;;;;AAcR;;;AAgCU;;;AAEA;;;;;AAOF;;;;AAID;AAsBK;;;;;AAKF;AAgBJ;AAkBU;AACA;AACF;AA0BhB;AAEA;;"}
@@ -1 +1 @@
1
- {"version":3,"file":"Slider.js","sources":["../../../../../../src/components/controls/Slider/Slider.tsx"],"sourcesContent":[null],"names":[],"mappings":";;;;;;;;;;;AA+KA;;AAEG;AACH;AACE;AACF;AAEA;;AAEG;AACH;;AAC+B;;;AAG/B;AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgDG;AACI;;;;AAkCL;AACA;AACA;AAIA;;AAGA;AACA;AACA;;AAGA;;AACuB;AACrB;AAAyC;AACzC;AACF;AACE;AACA;AACA;;;;AAID;;;AAIC;AACA;;AAEE;AAEJ;;AAEG;AACH;;;;;;;AASI;;;AAGF;AAIF;;AAEG;AACH;;AAE2B;;;;;AAOvB;;AAGA;AACA;;AAKJ;;AAEG;AACH;;;;;;;;;;;AAcM;;AAEJ;AAIF;;AAEG;AACH;AAEI;;;;;;;AAMA;;AAKJ;;AAEG;AACH;;;AAGE;;AAGF;;AAEG;AACH;;;AAII;;AAGA;AACE;AACA;AACE;;AAEF;AACA;AACE;;AAEF;;;AAGA;;;AAGA;AACE;;AAEF;AACE;;AAEF;;;;;;AAQF;AACF;;;;;;;;;;AAWC;AAGH;;AAEG;AACH;;AAGI;AACF;AAIF;;AAGI;AACF;;;AAMA;;AAEA;AACA;AAEA;AACE;AACA;AACF;;;AAIF;AAEA;AAEA;AAmBQ;AACA;AACA;AACA;;AAyBM;;AAQF;;AAgBE;AACD;AAef;AAEA;;"}
1
+ {"version":3,"file":"Slider.js","sources":["src/components/controls/Slider/Slider.tsx"],"sourcesContent":["'use client';\n\n// src/components/controls/Slider/Slider.tsx\nimport { assignInlineVars } from '@vanilla-extract/dynamic';\nimport React, { useRef, useCallback, useState, useEffect } from 'react';\n\nimport { FormHelperText } from '@/components/form/FormHelperText';\nimport { FormLabel } from '@/components/form/FormLabel';\nimport { InputWrapper } from '@/components/form/InputWrapper';\nimport { useKeyboardContext } from '@/context/KeyboardContext';\nimport { cx } from '@/utils/cx';\n\nimport {\n sliderContainerRecipe,\n sliderWrapperStyle,\n trackRecipe,\n fillRecipe,\n fillPercentageVar,\n thumbRecipe,\n thumbPercentageVar,\n ticksStyle,\n tickStyle,\n tooltipRecipe,\n tooltipPercentageVar,\n} from './Slider.css';\n\nimport type { BaseComponent, Size } from '@/types/common';\nimport type { Prettify } from '@/types/utilities';\n\n/**\n * Props specific to Slider component\n */\nexport interface SliderBaseProps extends Omit<BaseComponent, 'onChange'> {\n /**\n * Current numeric value\n */\n value: number;\n\n /**\n * Callback when value changes\n */\n onChange: (value: number) => void;\n\n /**\n * Minimum allowed value\n * @default 0\n */\n min?: number;\n\n /**\n * Maximum allowed value\n * @default 100\n */\n max?: number;\n\n /**\n * Step size for value increments\n * @default 1\n */\n step?: number;\n\n /**\n * Step size when Shift is held (precision mode)\n * @default step / 10\n */\n precisionStep?: number;\n\n /**\n * Step size when Ctrl is held (large steps)\n * @default step * 10\n */\n largeStep?: number;\n\n /**\n * Number of decimal places to round to\n */\n precision?: number;\n\n /**\n * Slider size using standard library sizing\n * - `sm`: Compact for toolbars\n * - `md`: Standard for forms\n * - `lg`: Prominent sliders\n * @default \"md\"\n */\n size?: Size;\n\n /**\n * Whether the slider is disabled\n * @default false\n */\n disabled?: boolean;\n\n /**\n * Whether the slider is read-only\n * @default false\n */\n readOnly?: boolean;\n\n /**\n * Slider label\n */\n label?: string;\n\n /**\n * Helper text displayed below the slider\n */\n helperText?: string;\n\n /**\n * Error message displayed when error is true\n */\n errorMessage?: string;\n\n /**\n * Whether the slider has an error state\n * @default false\n */\n error?: boolean;\n\n /**\n * Whether the slider is required\n * @default false\n */\n required?: boolean;\n\n /**\n * Unit suffix to display (e.g., \"px\", \"%\", \"°\")\n */\n unit?: string;\n\n /**\n * Format value for display\n */\n formatValue?: (value: number) => string;\n\n /**\n * Show value tooltip while dragging\n * @default true\n */\n showTooltip?: boolean;\n\n /**\n * Show tick marks along the track\n * @default false\n */\n showTicks?: boolean;\n\n /**\n * Number of tick marks to show (when showTicks is true)\n * @default 5\n */\n tickCount?: number;\n\n /**\n * Focus event handler\n */\n onFocus?: (event: React.FocusEvent<HTMLElement>) => void;\n\n /**\n * Blur event handler\n */\n onBlur?: (event: React.FocusEvent<HTMLElement>) => void;\n\n /**\n * Key down event handler\n */\n onKeyDown?: (event: React.KeyboardEvent<HTMLElement>) => void;\n}\n\n/**\n * Props for the Slider component with prettified type for better IntelliSense\n */\nexport type SliderProps = Prettify<SliderBaseProps>;\n\n/**\n * Clamps a value between min and max bounds\n */\nfunction clamp(value: number, min: number, max: number): number {\n return Math.min(Math.max(value, min), max);\n}\n\n/**\n * Rounds a number to specified decimal places\n */\nfunction roundToPrecision(value: number, precision?: number): number {\n if (precision === undefined) return value;\n const factor = Math.pow(10, precision);\n return Math.round(value * factor) / factor;\n}\n\n/**\n * A professional slider component with drag interaction and keyboard support.\n *\n * Features:\n * - Smooth drag interaction with visual feedback\n * - Keyboard navigation with arrow keys\n * - Modifier key support: Ctrl (large steps), Shift (precision)\n * - Optional tick marks and value tooltip\n * - Comprehensive theming and size variants\n * - Accessible with proper ARIA attributes\n *\n * @example\n * ```tsx\n * // Basic slider\n * <Slider\n * value={opacity}\n * onChange={setOpacity}\n * min={0}\n * max={1}\n * step={0.1}\n * unit=\"%\"\n * />\n *\n * // Slider with ticks and custom formatting\n * <Slider\n * value={rotation}\n * onChange={setRotation}\n * min={0}\n * max={360}\n * step={15}\n * unit=\"°\"\n * showTicks\n * tickCount={8}\n * formatValue={(v) => `${v}°`}\n * />\n *\n * // Precision slider for fine control\n * <Slider\n * value={scale}\n * onChange={setScale}\n * min={0.1}\n * max={5}\n * step={0.1}\n * precisionStep={0.01}\n * largeStep={1}\n * precision={2}\n * />\n * ```\n */\nexport const Slider: React.FC<SliderProps> = ({\n value,\n onChange,\n min = 0,\n max = 100,\n step = 1,\n precisionStep,\n largeStep,\n precision = 2,\n size = 'md',\n disabled = false,\n readOnly = false,\n label,\n helperText,\n errorMessage,\n error = false,\n required = false,\n unit,\n formatValue,\n showTooltip = true,\n showTicks = false,\n tickCount = 5,\n className,\n testId,\n onFocus,\n onBlur,\n onKeyDown,\n ref,\n ...props\n}) => {\n const [isDragging, setIsDragging] = useState(false);\n const [isFocused, setIsFocused] = useState(false);\n const [showTooltipState, setShowTooltipState] = useState(false);\n\n const sliderRef = useRef<HTMLDivElement>(null);\n const trackRef = useRef<HTMLDivElement>(null);\n const dragStartRef = useRef<{ startX: number; startValue: number } | null>(\n null\n );\n\n const { modifiers } = useKeyboardContext();\n\n // Calculate derived values\n const effectivePrecisionStep = precisionStep ?? step / 10;\n const effectiveLargeStep = largeStep ?? step * 10;\n const percentage = ((value - min) / (max - min)) * 100;\n const clampedValue = clamp(value, min, max);\n\n const getStepSize = useCallback((): number => {\n if (modifiers.shift) return effectivePrecisionStep;\n if (modifiers.control || modifiers.meta) return effectiveLargeStep;\n return step;\n }, [\n modifiers.shift,\n modifiers.control,\n modifiers.meta,\n effectivePrecisionStep,\n effectiveLargeStep,\n step,\n ]);\n\n // Format display value with consistent precision to prevent tooltip size changes\n const displayValue = formatValue\n ? formatValue(clampedValue)\n : unit\n ? `${clampedValue.toFixed(precision)}${unit}`\n : clampedValue.toFixed(precision);\n\n /**\n * Applies a new value with proper bounds checking and rounding\n */\n const applyValue = useCallback(\n (newValue: number): void => {\n if (disabled || readOnly) return;\n\n if (newValue === value) return;\n\n const rounded = roundToPrecision(newValue, precision);\n const clamped = clamp(rounded, min, max);\n\n if (clamped !== value) {\n onChange(clamped);\n }\n },\n [disabled, readOnly, precision, min, max, value, onChange]\n );\n\n /**\n * Converts mouse position to value\n */\n const positionToValue = useCallback(\n (clientX: number): number => {\n if (!trackRef.current) return value;\n\n const rect = trackRef.current.getBoundingClientRect();\n const percentage = clamp((clientX - rect.left) / rect.width, 0, 1);\n const newValue = min + percentage * (max - min);\n\n // Use the current step size (depends on keyboard modifiers)\n const currentStep = getStepSize();\n\n // Snap to step increments\n const steps = Math.round((newValue - min) / currentStep);\n return min + steps * currentStep;\n },\n [min, max, value, getStepSize]\n );\n\n /**\n * Handle mouse down on track or thumb\n */\n const handleMouseDown = useCallback(\n (event: React.MouseEvent) => {\n if (disabled || readOnly) return;\n\n event.preventDefault();\n setIsDragging(true);\n setShowTooltipState(showTooltip);\n\n // Use the current step size (depends on keyboard modifiers)\n const newValue = positionToValue(event.clientX);\n applyValue(newValue);\n\n dragStartRef.current = {\n startX: event.clientX,\n startValue: newValue,\n };\n },\n [disabled, readOnly, positionToValue, applyValue, showTooltip]\n );\n\n /**\n * Handle global mouse move during drag\n */\n const handleMouseMove = useCallback(\n (event: MouseEvent) => {\n if (!isDragging || !dragStartRef.current) return;\n // Use requestAnimationFrame for smoother performance\n requestAnimationFrame(() => {\n // Use the current step size (depends on keyboard modifiers)\n const newValue = positionToValue(event.clientX);\n applyValue(newValue);\n });\n },\n [isDragging, positionToValue, applyValue]\n );\n\n /**\n * Handle global mouse up to end drag\n */\n const handleMouseUp = useCallback(() => {\n setIsDragging(false);\n setShowTooltipState(false);\n dragStartRef.current = null;\n }, []);\n\n /**\n * Handle keyboard navigation\n */\n const handleKeyDown = useCallback(\n (event: React.KeyboardEvent<HTMLElement>) => {\n if (disabled || readOnly) return;\n\n const currentStep = getStepSize();\n let handled = true;\n\n switch (event.key) {\n case 'ArrowRight':\n case 'ArrowUp':\n applyValue(value + currentStep);\n break;\n case 'ArrowLeft':\n case 'ArrowDown':\n applyValue(value - currentStep);\n break;\n case 'Home':\n applyValue(min);\n break;\n case 'End':\n applyValue(max);\n break;\n case 'PageUp':\n applyValue(value + effectiveLargeStep);\n break;\n case 'PageDown':\n applyValue(value - effectiveLargeStep);\n break;\n default:\n handled = false;\n }\n\n if (handled) {\n event.preventDefault();\n }\n\n onKeyDown?.(event);\n },\n [\n disabled,\n readOnly,\n getStepSize,\n applyValue,\n value,\n min,\n max,\n effectiveLargeStep,\n onKeyDown,\n ]\n );\n\n /**\n * Handle focus events\n */\n const handleFocus = useCallback(\n (event: React.FocusEvent<HTMLElement>) => {\n setIsFocused(true);\n onFocus?.(event);\n },\n [onFocus]\n );\n\n const handleBlur = useCallback(\n (event: React.FocusEvent<HTMLElement>) => {\n setIsFocused(false);\n onBlur?.(event);\n },\n [onBlur]\n );\n\n // Global mouse events for dragging\n useEffect(() => {\n if (!isDragging) return;\n\n document.addEventListener('mousemove', handleMouseMove);\n document.addEventListener('mouseup', handleMouseUp);\n\n return () => {\n document.removeEventListener('mousemove', handleMouseMove);\n document.removeEventListener('mouseup', handleMouseUp);\n };\n }, [isDragging, handleMouseMove, handleMouseUp]);\n\n // Generate tick marks\n const ticks = showTicks ? Array.from({ length: tickCount }, (_, i) => i) : [];\n\n const sliderId = React.useId();\n\n return (\n <div\n ref={ref}\n className={cx(sliderContainerRecipe({ disabled }), className)}\n data-testid={testId}\n {...props}\n >\n {label && (\n <FormLabel htmlFor={sliderId} disabled={disabled} required={required}>\n {label}\n </FormLabel>\n )}\n\n <InputWrapper\n size={size}\n error={error}\n disabled={disabled}\n focused={isFocused}\n style={{\n border: 'none',\n background: 'transparent',\n padding: 0,\n height: 5,\n }}\n >\n <div\n ref={sliderRef}\n className={sliderWrapperStyle}\n onMouseDown={handleMouseDown}\n tabIndex={disabled ? -1 : 0}\n role=\"slider\"\n aria-valuemin={min}\n aria-valuemax={max}\n aria-valuenow={clampedValue}\n aria-valuetext={displayValue}\n aria-disabled={disabled}\n aria-readonly={readOnly}\n aria-invalid={error}\n aria-labelledby={label ? sliderId : undefined}\n onFocus={handleFocus}\n onBlur={handleBlur}\n onKeyDown={handleKeyDown}\n >\n <div ref={trackRef} className={trackRecipe({ size, error })}>\n <div\n className={fillRecipe({ error, isDragging })}\n style={assignInlineVars({\n [fillPercentageVar]: `${percentage}%`,\n })}\n />\n </div>\n\n <div\n className={thumbRecipe({ size, error, isDragging })}\n style={assignInlineVars({\n [thumbPercentageVar]: `${percentage}%`,\n })}\n />\n\n {showTicks && (\n <div className={ticksStyle}>\n {ticks.map(i => (\n <div key={i} className={tickStyle} />\n ))}\n </div>\n )}\n\n {showTooltip && (\n <div\n className={tooltipRecipe({ visible: showTooltipState })}\n style={assignInlineVars({\n [tooltipPercentageVar]: `${percentage}%`,\n })}\n >\n {displayValue}\n </div>\n )}\n </div>\n </InputWrapper>\n\n {(helperText ?? (error && errorMessage)) && (\n <FormHelperText error={error}>\n {error && errorMessage ? errorMessage : helperText}\n </FormHelperText>\n )}\n </div>\n );\n};\n\nSlider.displayName = 'Slider';\n"],"names":[],"mappings":";;;;;;;;;;;AA+KA;;AAEG;AACH;AACE;AACF;AAEA;;AAEG;AACH;;AAC+B;;;AAG/B;AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgDG;AACI;;;;AAkCL;AACA;AACA;AAIA;;AAGA;AACA;AACA;;AAGA;;AACuB;AACrB;AAAyC;AACzC;AACF;AACE;AACA;AACA;;;;AAID;;;AAIC;AACA;;AAEE;AAEJ;;AAEG;AACH;;;;;;;AASI;;;AAGF;AAIF;;AAEG;AACH;;AAE2B;;;;;AAOvB;;AAGA;AACA;;AAKJ;;AAEG;AACH;;;;;;;;;;;AAcM;;AAEJ;AAIF;;AAEG;AACH;AAEI;;;;;;;AAMA;;AAKJ;;AAEG;AACH;;;AAGE;;AAGF;;AAEG;AACH;;;AAII;;AAGA;AACE;AACA;AACE;;AAEF;AACA;AACE;;AAEF;;;AAGA;;;AAGA;AACE;;AAEF;AACE;;AAEF;;;;;;AAQF;AACF;;;;;;;;;;AAWC;AAGH;;AAEG;AACH;;AAGI;AACF;AAIF;;AAGI;AACF;;;AAMA;;AAEA;AACA;AAEA;AACE;AACA;AACF;;;AAIF;AAEA;AAEA;AAmBQ;AACA;AACA;AACA;;AAyBM;;AAQF;;AAgBE;AACD;AAef;AAEA;;"}
@@ -1 +1 @@
1
- {"version":3,"file":"TagInput.js","sources":["../../../../../../src/components/controls/TagInput/TagInput.tsx"],"sourcesContent":[null],"names":[],"mappings":";;;;;;;;;;;AA0BA;AACE;AACA;AACA;AACA;;AAGF;AAEA;AACE;AACF;AAEA;;;;;;;;;;;;;;AAcG;;AAiCD;AACA;AACA;AACA;AAEA;;;;AAIE;AACD;;;AAKD;AACA;;;AAKA;AACA;AAEA;;AAOI;AACA;AACE;AAAoB;AACpB;;;AAGA;AACA;;;AAGA;AACA;;;;AAIA;AACE;AACA;;AAEF;AACE;AACA;;;AAGJ;AACF;AAIF;;AAGI;AAAW;;AAEX;;AAKJ;;;;;AAKE;AAIF;AAEI;;;;;;;AAME;;;;;AAGO;;;;;;AAOT;;AAEE;;AAEJ;AAIF;AAEI;;;;;;;;;;;;AAeA;AACE;;;AAEA;;;;;;;AAQN;;;;;;;AAOA;;;;;AAMM;;;AAMN;;;AAGE;;AAEE;;;;;;;;AAaF;AAqBF;AAEA;;;;;;;;;;AAwDU;AAeZ;AAEA;;"}
1
+ {"version":3,"file":"TagInput.js","sources":["src/components/controls/TagInput/TagInput.tsx"],"sourcesContent":["'use client';\n\nimport React, { useCallback, useId, useMemo, useRef, useState } from 'react';\n\nimport { FormHelperText } from '@/components/form/FormHelperText';\nimport { FormLabel } from '@/components/form/FormLabel';\nimport { CloseIcon } from '@/components/Icons/CloseIcon';\nimport { useControlledState } from '@/hooks/useControlledState';\nimport { useMergedRef } from '@/hooks/useMergedRef';\nimport { cx } from '@/utils/cx';\n\nimport {\n containerStyle,\n inputStyle,\n tagLabelStyle,\n tagRecipe,\n tagRemoveStyle,\n wrapperRecipe,\n} from './TagInput.css';\n\nimport type {\n TagInputProps,\n TagInputRenderTagState,\n TagInputSeparator,\n} from './TagInput.types';\n\nconst SEPARATOR_KEY: Record<TagInputSeparator, string> = {\n Enter: 'Enter',\n Comma: ',',\n Space: ' ',\n Tab: 'Tab',\n};\n\nconst DEFAULT_SEPARATORS: TagInputSeparator[] = ['Enter', 'Comma'];\n\nfunction defaultNormalize(value: string): string {\n return value.trim();\n}\n\n/**\n * Multi-value input that captures a list of strings (\"tags\") rendered as\n * removable chips. Supports controlled and uncontrolled modes, validation,\n * configurable commit keys, and custom chip rendering.\n *\n * @example\n * ```tsx\n * <TagInput\n * label=\"Keywords\"\n * placeholder=\"Add a keyword...\"\n * defaultValue={['react', 'typescript']}\n * onChange={setTags}\n * />\n * ```\n */\nexport function TagInput({\n value,\n defaultValue,\n onChange,\n placeholder,\n separators = DEFAULT_SEPARATORS,\n addOnBlur = false,\n allowDuplicates = false,\n max,\n validate,\n normalize = defaultNormalize,\n onValidate,\n renderTag,\n size = 'md',\n variant = 'default',\n label,\n helperText,\n error = false,\n errorMessage,\n disabled = false,\n required = false,\n readOnly = false,\n name,\n inputAriaLabel,\n inputRef: inputRefProp,\n className,\n style,\n testId,\n id: idProp,\n ref,\n ...rest\n}: TagInputProps) {\n const autoId = useId();\n const fieldId = idProp ?? autoId;\n const labelId = `${fieldId}-label`;\n const helperId = `${fieldId}-helper`;\n\n const [tags, setTags] = useControlledState<string[]>({\n value,\n defaultValue,\n onChange,\n fallback: [],\n });\n\n const [draft, setDraft] = useState('');\n const [focused, setFocused] = useState(false);\n\n const innerInputRef = useRef<HTMLInputElement | null>(null);\n const wrapperRef = useRef<HTMLDivElement | null>(null);\n\n const setInputRef = useMergedRef(innerInputRef, inputRefProp);\n const setWrapperRef = useMergedRef(wrapperRef, ref);\n\n const atMax = typeof max === 'number' && tags.length >= max;\n const inputDisabled = disabled || readOnly || atMax;\n\n const separatorKeys = useMemo(\n () => new Set(separators.map(s => SEPARATOR_KEY[s])),\n [separators]\n );\n\n const tryAppend = useCallback(\n (current: string[], raw: string): string[] | null => {\n const normalized = normalize(raw);\n if (normalized.length === 0) {\n if (raw.length > 0) onValidate?.(raw, 'empty');\n return null;\n }\n if (typeof max === 'number' && current.length >= max) {\n onValidate?.(raw, 'max');\n return null;\n }\n if (!allowDuplicates && current.includes(normalized)) {\n onValidate?.(raw, 'duplicate');\n return null;\n }\n if (validate) {\n const result = validate(normalized, current);\n if (result === false) {\n onValidate?.(raw, 'invalid');\n return null;\n }\n if (typeof result === 'string') {\n onValidate?.(raw, result);\n return null;\n }\n }\n return [...current, normalized];\n },\n [allowDuplicates, max, normalize, onValidate, validate]\n );\n\n const commitTag = useCallback(\n (raw: string): boolean => {\n const next = tryAppend(tags, raw);\n if (!next) return false;\n setTags(next);\n return true;\n },\n [setTags, tags, tryAppend]\n );\n\n const removeTagAt = useCallback(\n (index: number) => {\n if (index < 0 || index >= tags.length) return;\n const next = tags.slice(0, index).concat(tags.slice(index + 1));\n setTags(next);\n },\n [setTags, tags]\n );\n\n const handleKeyDown = useCallback(\n (event: React.KeyboardEvent<HTMLInputElement>) => {\n if (inputDisabled) return;\n\n if (separatorKeys.has(event.key)) {\n // Tab as separator: only commit when there's a draft, otherwise let\n // the focus move out naturally.\n if (event.key === 'Tab' && draft.length === 0) return;\n if (draft.length > 0) {\n event.preventDefault();\n if (commitTag(draft)) setDraft('');\n } else if (event.key === 'Enter') {\n // Prevent submitting an enclosing form on empty Enter.\n event.preventDefault();\n }\n return;\n }\n\n if (event.key === 'Backspace' && draft.length === 0 && tags.length > 0) {\n event.preventDefault();\n removeTagAt(tags.length - 1);\n }\n },\n [commitTag, draft, inputDisabled, removeTagAt, separatorKeys, tags.length]\n );\n\n const handleChange = useCallback(\n (event: React.ChangeEvent<HTMLInputElement>) => {\n const next = event.target.value;\n // If the user typed/pasted a separator character (e.g. comma), commit\n // each piece. Newlines are split too so multi-line paste still works.\n const splitChars = Array.from(separatorKeys).filter(\n k => k !== 'Enter' && k !== 'Tab'\n );\n if (splitChars.length === 0 || !splitChars.some(c => next.includes(c))) {\n setDraft(next);\n return;\n }\n\n const pattern = new RegExp(`[${splitChars.map(c => `\\\\${c}`).join('')}]`);\n const parts = next.split(pattern);\n const trailing = parts.pop() ?? '';\n let working = tags;\n for (const part of parts) {\n if (part.length === 0) continue;\n const appended = tryAppend(working, part);\n if (appended) working = appended;\n }\n if (working !== tags) setTags(working);\n setDraft(trailing);\n },\n [separatorKeys, setTags, tags, tryAppend]\n );\n\n const handleBlur = useCallback(() => {\n setFocused(false);\n if (addOnBlur && draft.length > 0) {\n if (commitTag(draft)) setDraft('');\n }\n }, [addOnBlur, commitTag, draft]);\n\n const handleWrapperMouseDown = useCallback(\n (event: React.MouseEvent<HTMLDivElement>) => {\n // Forward clicks on empty padding to the input, but do not steal focus\n // from chip remove buttons.\n if (event.target === wrapperRef.current) {\n event.preventDefault();\n innerInputRef.current?.focus();\n }\n },\n []\n );\n\n const renderChip = (tag: string, index: number) => {\n const remove = () => {\n removeTagAt(index);\n };\n if (renderTag) {\n const state: TagInputRenderTagState = {\n tag,\n index,\n disabled,\n remove,\n };\n return (\n <React.Fragment key={`${tag}-${String(index)}`}>\n {renderTag(state)}\n </React.Fragment>\n );\n }\n\n return (\n <span\n key={`${tag}-${String(index)}`}\n className={tagRecipe({ size })}\n data-testid={testId ? `${testId}-tag-${String(index)}` : undefined}\n >\n <span className={tagLabelStyle}>{tag}</span>\n {!readOnly && (\n <button\n type=\"button\"\n tabIndex={-1}\n disabled={disabled}\n aria-label={`Remove ${tag}`}\n className={tagRemoveStyle}\n onClick={remove}\n >\n <CloseIcon size=\"sm\" decorative />\n </button>\n )}\n </span>\n );\n };\n\n const showHelperText = error && errorMessage ? errorMessage : helperText;\n const formValue = tags.join(',');\n\n return (\n <div className={cx(containerStyle, className)} style={style}>\n {label && (\n <FormLabel\n id={labelId}\n htmlFor={fieldId}\n required={required}\n disabled={disabled}\n >\n {label}\n </FormLabel>\n )}\n\n {/*\n The onMouseDown handler only forwards clicks on the wrapper padding to\n focus the inner native <input>, which is the actual accessible control\n (focusable, keyboard-driven). The wrapper is presentational, so no\n widget role belongs on it.\n */}\n {/* eslint-disable-next-line jsx-a11y/no-static-element-interactions */}\n <div\n ref={setWrapperRef}\n className={wrapperRecipe({\n size,\n variant,\n focused,\n error,\n disabled,\n })}\n onMouseDown={handleWrapperMouseDown}\n data-testid={testId}\n {...rest}\n >\n {tags.map((tag, index) => renderChip(tag, index))}\n\n {!readOnly && (\n <input\n ref={setInputRef}\n id={fieldId}\n type=\"text\"\n value={draft}\n placeholder={tags.length === 0 ? placeholder : undefined}\n disabled={inputDisabled}\n required={required && tags.length === 0}\n aria-label={inputAriaLabel}\n aria-labelledby={!inputAriaLabel && label ? labelId : undefined}\n aria-describedby={showHelperText ? helperId : undefined}\n aria-invalid={error || undefined}\n className={inputStyle}\n onChange={handleChange}\n onKeyDown={handleKeyDown}\n onFocus={() => {\n setFocused(true);\n }}\n onBlur={handleBlur}\n />\n )}\n </div>\n\n {name && <input type=\"hidden\" name={name} value={formValue} />}\n\n {showHelperText && (\n <FormHelperText id={helperId} error={error}>\n {showHelperText}\n </FormHelperText>\n )}\n </div>\n );\n}\n\nTagInput.displayName = 'TagInput';\n"],"names":[],"mappings":";;;;;;;;;;;AA0BA;AACE;AACA;AACA;AACA;;AAGF;AAEA;AACE;AACF;AAEA;;;;;;;;;;;;;;AAcG;;AAiCD;AACA;AACA;AACA;AAEA;;;;AAIE;AACD;;;AAKD;AACA;;;AAKA;AACA;AAEA;;AAOI;AACA;AACE;AAAoB;AACpB;;;AAGA;AACA;;;AAGA;AACA;;;;AAIA;AACE;AACA;;AAEF;AACE;AACA;;;AAGJ;AACF;AAIF;;AAGI;AAAW;;AAEX;;AAKJ;;;;;AAKE;AAIF;AAEI;;;;;;;AAME;;;;;AAGO;;;;;;AAOT;;AAEE;;AAEJ;AAIF;AAEI;;;;;;;;;;;;AAeA;AACE;;;AAEA;;;;;;;AAQN;;;;;;;AAOA;;;;;AAMM;;;AAMN;;;AAGE;;AAEE;;;;;;;;AAaF;AAqBF;AAEA;;;;;;;;;;AAwDU;AAeZ;AAEA;;"}
@@ -89,6 +89,12 @@ import '../../Icons/GitBranchIcon.js';
89
89
  import '../../Icons/BugIcon.js';
90
90
  import '../../Icons/MinusIcon.js';
91
91
  import '../../Icons/PinIcon.js';
92
+ import '../../Icons/OrbitIcon.js';
93
+ import '../../Icons/RotateIcon.js';
94
+ import '../../Icons/SpinIcon.js';
95
+ import '../../Icons/CameraIcon.js';
96
+ import '../../Icons/CubeIcon.js';
97
+ import '../../Icons/ZoomFitIcon.js';
92
98
  import { cx } from '../../../utils/cx.js';
93
99
  import { guideLineStyle, guideLineLeftVar, chevronRecipe, iconRecipe, renameInputRecipe, labelRecipe, actionsStyle, paddingLeftVar, dropTargetStyle, rowRecipe } from './TreeNode.css.js';
94
100
 
@@ -1 +1 @@
1
- {"version":3,"file":"TreeNode.js","sources":["../../../../../../src/components/controls/TreeView/TreeNode.tsx"],"sourcesContent":[null],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2BA;AAEA;AAUE;AACE;AACA;AACA;AACA;AACA;AACD;AACD;AACE;AACA;AACA;AACA;AACA;AACD;AACD;AACE;AACA;AACA;AACA;AACA;AACD;;AAGH;AAEA;AAiCO;;AA2BL;AACA;AAEA;AACA;AAEA;AACE;AACA;AACA;;;;;;AAOF;;;AAIM;;AAEJ;AAIF;AAEI;;AACA;;AAKJ;;;;;;AAOM;;AAEE;AACA;;;;AAKJ;;AAKJ;AAEI;AACF;AAIF;AAEI;AACF;AAIF;AAEI;AACF;AAIF;AAEI;AACF;AAIF;;;;;AAKA;AAEA;AACE;;AAEF;AAEA;AAEI;AACE;;AACK;AACL;;AAEJ;;AAKF;;AAOY;AACD;;;;;;;;;;AAsBH;AACA;AACA;AACD;AAID;AACD;AAeK;;;AAGD;AAsBK;AACF;;;;;;;;;;;AAgCd;AAEA;;"}
1
+ {"version":3,"file":"TreeNode.js","sources":["src/components/controls/TreeView/TreeNode.tsx"],"sourcesContent":["'use client';\n\nimport { assignInlineVars } from '@vanilla-extract/dynamic';\nimport React, { useState, useRef, useCallback } from 'react';\n\nimport { ChevronRightIcon } from '@/components/Icons';\nimport { cx } from '@/utils/cx';\n\nimport {\n rowRecipe,\n chevronRecipe,\n iconRecipe,\n labelRecipe,\n actionsStyle,\n renameInputRecipe,\n guideLineStyle,\n dropTargetStyle,\n paddingLeftVar,\n guideLineLeftVar,\n} from './TreeNode.css';\n\nimport type {\n TreeNodeData,\n TreeNodeState,\n TreeViewSize,\n} from './TreeView.types';\n\n// --- Size configurations ---\n\nconst sizeConfig: Record<\n TreeViewSize,\n {\n rowHeight: number;\n chevronSize: number;\n iconSize: number;\n fontSizeKey: 'xs' | 'sm' | 'md' | 'lg';\n defaultIndent: number;\n }\n> = {\n sm: {\n rowHeight: 20,\n chevronSize: 10,\n iconSize: 12,\n fontSizeKey: 'md',\n defaultIndent: 12,\n },\n md: {\n rowHeight: 24,\n chevronSize: 12,\n iconSize: 14,\n fontSizeKey: 'md',\n defaultIndent: 16,\n },\n lg: {\n rowHeight: 32,\n chevronSize: 14,\n iconSize: 16,\n fontSizeKey: 'lg',\n defaultIndent: 20,\n },\n};\n\n// --- Chevron icon SVG ---\n\nconst ChevronSvg = ({ size }: { size: number }) => (\n <ChevronRightIcon size={size} color=\"currentColor\" decorative />\n);\n\n// --- TreeNode component ---\n\nexport interface TreeNodeComponentProps {\n node: TreeNodeData;\n depth: number;\n size: TreeViewSize;\n indent: number;\n isSelected: boolean;\n isExpanded: boolean;\n isFocused: boolean;\n isFirst: boolean;\n isLast: boolean;\n showChevrons: boolean;\n showGuideLines: boolean;\n renamable: boolean;\n nodeId: string;\n isDropTarget: boolean;\n renderNode?: (node: TreeNodeData, state: TreeNodeState) => React.ReactNode;\n renderActions?: (node: TreeNodeData, state: TreeNodeState) => React.ReactNode;\n onToggleExpand: (id: string) => void;\n onClick: (node: TreeNodeData, event: React.MouseEvent) => void;\n onDoubleClick?: (node: TreeNodeData, event: React.MouseEvent) => void;\n onContextMenu?: (node: TreeNodeData, event: React.MouseEvent) => void;\n onRename?: (nodeId: string, newLabel: string) => void;\n onDragOver?: (node: TreeNodeData, event: React.DragEvent) => void;\n onDragLeave?: (node: TreeNodeData, event: React.DragEvent) => void;\n onDrop?: (node: TreeNodeData, event: React.DragEvent) => void;\n}\n\nexport const TreeNodeComponent = ({\n node,\n depth,\n size,\n indent,\n isSelected,\n isExpanded,\n isFocused,\n isFirst,\n isLast,\n showChevrons,\n showGuideLines,\n renamable,\n nodeId,\n isDropTarget,\n renderNode,\n renderActions,\n onToggleExpand,\n onClick,\n onDoubleClick,\n onContextMenu,\n onRename,\n onDragOver,\n onDragLeave,\n onDrop,\n}: TreeNodeComponentProps) => {\n const [isRenaming, setIsRenaming] = useState(false);\n const [renameValue, setRenameValue] = useState(node.label);\n const renameInputRef = useRef<HTMLInputElement>(null);\n\n const isLeaf = !node.children || node.children.length === 0;\n const config = sizeConfig[size];\n\n const state: TreeNodeState = {\n selected: isSelected,\n expanded: isExpanded,\n focused: isFocused,\n depth,\n isLeaf,\n isFirst,\n isLast,\n };\n\n const handleChevronClick = useCallback(\n (e: React.MouseEvent) => {\n e.stopPropagation();\n if (!node.disabled && !isLeaf) {\n onToggleExpand(node.id);\n }\n },\n [node.id, node.disabled, isLeaf, onToggleExpand]\n );\n\n const handleClick = useCallback(\n (e: React.MouseEvent) => {\n if (node.disabled || isRenaming) return;\n onClick(node, e);\n },\n [node, isRenaming, onClick]\n );\n\n const handleDoubleClick = useCallback(\n (e: React.MouseEvent) => {\n if (node.disabled) return;\n\n // Start rename if renamable\n if (renamable && node.renamable !== false) {\n setIsRenaming(true);\n setRenameValue(node.label);\n setTimeout(() => {\n renameInputRef.current?.focus();\n renameInputRef.current?.select();\n }, 0);\n return;\n }\n\n onDoubleClick?.(node, e);\n },\n [node, renamable, onDoubleClick]\n );\n\n const handleContextMenu = useCallback(\n (e: React.MouseEvent) => {\n onContextMenu?.(node, e);\n },\n [node, onContextMenu]\n );\n\n const handleDragOver = useCallback(\n (e: React.DragEvent) => {\n onDragOver?.(node, e);\n },\n [node, onDragOver]\n );\n\n const handleDragLeave = useCallback(\n (e: React.DragEvent) => {\n onDragLeave?.(node, e);\n },\n [node, onDragLeave]\n );\n\n const handleDrop = useCallback(\n (e: React.DragEvent) => {\n onDrop?.(node, e);\n },\n [node, onDrop]\n );\n\n const confirmRename = useCallback(() => {\n if (renameValue.trim() && renameValue !== node.label) {\n onRename?.(node.id, renameValue.trim());\n }\n setIsRenaming(false);\n }, [renameValue, node.id, node.label, onRename]);\n\n const cancelRename = useCallback(() => {\n setRenameValue(node.label);\n setIsRenaming(false);\n }, [node.label]);\n\n const handleRenameKeyDown = useCallback(\n (e: React.KeyboardEvent) => {\n if (e.key === 'Enter') {\n confirmRename();\n } else if (e.key === 'Escape') {\n cancelRename();\n }\n },\n [confirmRename, cancelRename]\n );\n\n // Guide lines for each parent level\n const guideLines =\n showGuideLines && depth > 0\n ? Array.from({ length: depth }, (_, i) => (\n <span\n key={i}\n className={guideLineStyle}\n style={assignInlineVars({\n [guideLineLeftVar]: `${i * indent + config.defaultIndent / 2}px`,\n })}\n />\n ))\n : null;\n\n return (\n // Keyboard interaction is handled centrally by the parent role=\"tree\"\n // container via the aria-activedescendant pattern (it owns tabIndex and\n // onKeyDown). Individual treeitems are not separately focusable, so their\n // onClick is a pointer-only enhancement and they carry no key handler.\n // eslint-disable-next-line jsx-a11y/click-events-have-key-events\n <div\n id={`treenode-${nodeId}`}\n role=\"treeitem\"\n aria-expanded={isLeaf ? undefined : isExpanded}\n aria-selected={isSelected}\n aria-disabled={node.disabled ?? undefined}\n aria-level={depth + 1}\n data-drop-target={isDropTarget ? 'true' : undefined}\n className={cx(\n rowRecipe({\n size,\n selected: isSelected,\n focused: isFocused,\n disabled: node.disabled ?? false,\n }),\n dropTargetStyle\n )}\n style={assignInlineVars({\n [paddingLeftVar]: `${depth * indent}px`,\n })}\n onClick={handleClick}\n onDoubleClick={handleDoubleClick}\n onContextMenu={handleContextMenu}\n onDragOver={onDragOver ? handleDragOver : undefined}\n onDragLeave={onDragLeave ? handleDragLeave : undefined}\n onDrop={onDrop ? handleDrop : undefined}\n tabIndex={-1}\n >\n {guideLines}\n\n {/* Chevron */}\n {showChevrons && (\n <span\n className={chevronRecipe({\n expanded: isExpanded,\n size,\n visible: !isLeaf,\n })}\n onClick={handleChevronClick}\n aria-hidden=\"true\"\n >\n <ChevronSvg size={config.chevronSize} />\n </span>\n )}\n\n {/* Content */}\n {renderNode ? (\n renderNode(node, state)\n ) : (\n <>\n {node.icon && (\n <span className={iconRecipe({ size })}>{node.icon}</span>\n )}\n {isRenaming ? (\n <input\n ref={renameInputRef}\n className={renameInputRecipe({ size })}\n value={renameValue}\n onChange={e => {\n setRenameValue(e.target.value);\n }}\n onKeyDown={handleRenameKeyDown}\n onBlur={confirmRename}\n aria-label={`Rename ${node.label}`}\n onClick={e => {\n e.stopPropagation();\n }}\n />\n ) : (\n <span className={labelRecipe({ size })}>{node.label}</span>\n )}\n </>\n )}\n\n {/* Actions */}\n {renderActions && (\n // This span is a non-interactive container; its onClick only stops\n // row-click propagation so that the genuinely interactive controls\n // produced by renderActions handle their own activation. It has no\n // activatable behavior of its own, so no role or key handler applies.\n // eslint-disable-next-line jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events\n <span\n className={actionsStyle}\n onClick={e => {\n e.stopPropagation();\n }}\n >\n {renderActions(node, state)}\n </span>\n )}\n </div>\n );\n};\n\nTreeNodeComponent.displayName = 'TreeNode';\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2BA;AAEA;AAUE;AACE;AACA;AACA;AACA;AACA;AACD;AACD;AACE;AACA;AACA;AACA;AACA;AACD;AACD;AACE;AACA;AACA;AACA;AACA;AACD;;AAGH;AAEA;AAiCO;;AA2BL;AACA;AAEA;AACA;AAEA;AACE;AACA;AACA;;;;;;AAOF;;;AAIM;;AAEJ;AAIF;AAEI;;AACA;;AAKJ;;;;;;AAOM;;AAEE;AACA;;;;AAKJ;;AAKJ;AAEI;AACF;AAIF;AAEI;AACF;AAIF;AAEI;AACF;AAIF;AAEI;AACF;AAIF;;;;;AAKA;AAEA;AACE;;AAEF;AAEA;AAEI;AACE;;AACK;AACL;;AAEJ;;AAKF;;AAOY;AACD;;;;;;;;;;AAsBH;AACA;AACA;AACD;AAID;AACD;AAeK;;;AAGD;AAsBK;AACF;;;;;;;;;;;AAgCd;AAEA;;"}