entangle-ui 0.10.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (589) hide show
  1. package/CHANGELOG.md +145 -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/controls/Slider/{Slider.css.ts.vanilla-Cqm3fQ0S.css → Slider.css.ts.vanilla-CLf_RC0A.css} +50 -47
  5. package/dist/esm/assets/src/components/editor/PropertyInspector/{PropertyPanel.css.ts.vanilla-RYv_XgOu.css → PropertyPanel.css.ts.vanilla-Dpr_k5I3.css} +4 -1
  6. package/dist/esm/assets/src/components/editor/PropertyInspector/{PropertyRow.css.ts.vanilla-C3YZnnHb.css → PropertyRow.css.ts.vanilla-pq1Uhd2a.css} +15 -8
  7. package/dist/esm/assets/src/components/editor/PropertyInspector/{PropertySection.css.ts.vanilla-DK12opZc.css → PropertySection.css.ts.vanilla-6MWWAqyd.css} +50 -32
  8. package/dist/esm/assets/src/components/navigation/Menu/{Menu.css.ts.vanilla-B5iVe5S2.css → Menu.css.ts.vanilla-Bhh1L5yl.css} +14 -11
  9. package/dist/esm/assets/src/components/primitives/Tooltip/{Tooltip.css.ts.vanilla-CrEFoLBX.css → Tooltip.css.ts.vanilla-BqaiGrFQ.css} +12 -9
  10. package/dist/esm/assets/src/components/shell/AppShell/{AppShell.css.ts.vanilla-BYmtU0O_.css → AppShell.css.ts.vanilla-BLK2-DRL.css} +1 -0
  11. package/dist/esm/assets/src/components/shell/MenuBar/{MenuBar.css.ts.vanilla-TgPcl4yA.css → MenuBar.css.ts.vanilla-Dt3w4f-q.css} +18 -6
  12. package/dist/esm/components/Icons/AddIcon.js.map +1 -1
  13. package/dist/esm/components/Icons/AiChatIcon.js.map +1 -1
  14. package/dist/esm/components/Icons/AiSparklesIcon.js.map +1 -1
  15. package/dist/esm/components/Icons/ArchiveIcon.js.map +1 -1
  16. package/dist/esm/components/Icons/ArrowDownIcon.js.map +1 -1
  17. package/dist/esm/components/Icons/ArrowLeftIcon.js.map +1 -1
  18. package/dist/esm/components/Icons/ArrowRightIcon.js.map +1 -1
  19. package/dist/esm/components/Icons/ArrowUpIcon.js.map +1 -1
  20. package/dist/esm/components/Icons/BookmarkIcon.js.map +1 -1
  21. package/dist/esm/components/Icons/BugIcon.js.map +1 -1
  22. package/dist/esm/components/Icons/BuildingIcon.js.map +1 -1
  23. package/dist/esm/components/Icons/CalendarIcon.js.map +1 -1
  24. package/dist/esm/components/Icons/CameraIcon.js +31 -0
  25. package/dist/esm/components/Icons/CameraIcon.js.map +1 -0
  26. package/dist/esm/components/Icons/CheckIcon.js.map +1 -1
  27. package/dist/esm/components/Icons/ChevronDownIcon.js.map +1 -1
  28. package/dist/esm/components/Icons/ChevronLeftIcon.js.map +1 -1
  29. package/dist/esm/components/Icons/ChevronRightIcon.js.map +1 -1
  30. package/dist/esm/components/Icons/ChevronUpIcon.js.map +1 -1
  31. package/dist/esm/components/Icons/CircleIcon.js.map +1 -1
  32. package/dist/esm/components/Icons/ClockIcon.js.map +1 -1
  33. package/dist/esm/components/Icons/CloseIcon.js.map +1 -1
  34. package/dist/esm/components/Icons/CloudUploadIcon.js.map +1 -1
  35. package/dist/esm/components/Icons/CodeIcon.js.map +1 -1
  36. package/dist/esm/components/Icons/CopyIcon.js.map +1 -1
  37. package/dist/esm/components/Icons/CubeIcon.js +31 -0
  38. package/dist/esm/components/Icons/CubeIcon.js.map +1 -0
  39. package/dist/esm/components/Icons/CutIcon.js.map +1 -1
  40. package/dist/esm/components/Icons/DotsHorizontalIcon.js.map +1 -1
  41. package/dist/esm/components/Icons/DotsVerticalIcon.js.map +1 -1
  42. package/dist/esm/components/Icons/DownloadIcon.js.map +1 -1
  43. package/dist/esm/components/Icons/EditIcon.js.map +1 -1
  44. package/dist/esm/components/Icons/ErrorIcon.js.map +1 -1
  45. package/dist/esm/components/Icons/ExternalLinkIcon.js.map +1 -1
  46. package/dist/esm/components/Icons/EyeDropperIcon.js.map +1 -1
  47. package/dist/esm/components/Icons/EyeIcon.js.map +1 -1
  48. package/dist/esm/components/Icons/FileTextIcon.js.map +1 -1
  49. package/dist/esm/components/Icons/FilterIcon.js.map +1 -1
  50. package/dist/esm/components/Icons/FirstIcon.js.map +1 -1
  51. package/dist/esm/components/Icons/FolderCogIcon.js.map +1 -1
  52. package/dist/esm/components/Icons/FolderIcon.js.map +1 -1
  53. package/dist/esm/components/Icons/FolderOpenIcon.js.map +1 -1
  54. package/dist/esm/components/Icons/FullscreenIcon.js.map +1 -1
  55. package/dist/esm/components/Icons/GitBranchIcon.js.map +1 -1
  56. package/dist/esm/components/Icons/GridIcon.js.map +1 -1
  57. package/dist/esm/components/Icons/HeartIcon.js.map +1 -1
  58. package/dist/esm/components/Icons/HelpIcon.js.map +1 -1
  59. package/dist/esm/components/Icons/HomeIcon.js.map +1 -1
  60. package/dist/esm/components/Icons/ImageIcon.js.map +1 -1
  61. package/dist/esm/components/Icons/InfoIcon.js.map +1 -1
  62. package/dist/esm/components/Icons/LastIcon.js.map +1 -1
  63. package/dist/esm/components/Icons/LinkIcon.js.map +1 -1
  64. package/dist/esm/components/Icons/ListIcon.js.map +1 -1
  65. package/dist/esm/components/Icons/LockIcon.js.map +1 -1
  66. package/dist/esm/components/Icons/MaximizeIcon.js.map +1 -1
  67. package/dist/esm/components/Icons/MenuIcon.js.map +1 -1
  68. package/dist/esm/components/Icons/MinimizeIcon.js.map +1 -1
  69. package/dist/esm/components/Icons/MinusIcon.js.map +1 -1
  70. package/dist/esm/components/Icons/OrbitIcon.js +31 -0
  71. package/dist/esm/components/Icons/OrbitIcon.js.map +1 -0
  72. package/dist/esm/components/Icons/PasteIcon.js.map +1 -1
  73. package/dist/esm/components/Icons/PauseIcon.js.map +1 -1
  74. package/dist/esm/components/Icons/PinIcon.js.map +1 -1
  75. package/dist/esm/components/Icons/PlayIcon.js.map +1 -1
  76. package/dist/esm/components/Icons/RedoIcon.js.map +1 -1
  77. package/dist/esm/components/Icons/RefreshIcon.js.map +1 -1
  78. package/dist/esm/components/Icons/RobotIcon.js.map +1 -1
  79. package/dist/esm/components/Icons/RotateIcon.js +32 -0
  80. package/dist/esm/components/Icons/RotateIcon.js.map +1 -0
  81. package/dist/esm/components/Icons/SaveIcon.js.map +1 -1
  82. package/dist/esm/components/Icons/SearchIcon.js.map +1 -1
  83. package/dist/esm/components/Icons/SendIcon.js.map +1 -1
  84. package/dist/esm/components/Icons/SettingsIcon.js.map +1 -1
  85. package/dist/esm/components/Icons/SortIcon.js.map +1 -1
  86. package/dist/esm/components/Icons/SpinIcon.js +32 -0
  87. package/dist/esm/components/Icons/SpinIcon.js.map +1 -0
  88. package/dist/esm/components/Icons/StarIcon.js.map +1 -1
  89. package/dist/esm/components/Icons/StopIcon.js.map +1 -1
  90. package/dist/esm/components/Icons/SuccessIcon.js.map +1 -1
  91. package/dist/esm/components/Icons/TagIcon.js.map +1 -1
  92. package/dist/esm/components/Icons/TangentAlignedIcon.js.map +1 -1
  93. package/dist/esm/components/Icons/TangentAutoIcon.js.map +1 -1
  94. package/dist/esm/components/Icons/TangentFreeIcon.js.map +1 -1
  95. package/dist/esm/components/Icons/TangentLinearIcon.js.map +1 -1
  96. package/dist/esm/components/Icons/TangentMirroredIcon.js.map +1 -1
  97. package/dist/esm/components/Icons/TangentStepIcon.js.map +1 -1
  98. package/dist/esm/components/Icons/TerminalIcon.js.map +1 -1
  99. package/dist/esm/components/Icons/TrashIcon.js.map +1 -1
  100. package/dist/esm/components/Icons/UndoIcon.js.map +1 -1
  101. package/dist/esm/components/Icons/UnlinkIcon.js.map +1 -1
  102. package/dist/esm/components/Icons/UnlockIcon.js.map +1 -1
  103. package/dist/esm/components/Icons/UploadIcon.js.map +1 -1
  104. package/dist/esm/components/Icons/UserIcon.js.map +1 -1
  105. package/dist/esm/components/Icons/UsersIcon.js.map +1 -1
  106. package/dist/esm/components/Icons/WarningIcon.js.map +1 -1
  107. package/dist/esm/components/Icons/ZoomFitIcon.js +32 -0
  108. package/dist/esm/components/Icons/ZoomFitIcon.js.map +1 -0
  109. package/dist/esm/components/Icons/ZoomInIcon.js.map +1 -1
  110. package/dist/esm/components/Icons/ZoomOutIcon.js.map +1 -1
  111. package/dist/esm/components/controls/CartesianPicker/CartesianPicker.js.map +1 -1
  112. package/dist/esm/components/controls/CartesianPicker/useCartesianInteraction.js.map +1 -1
  113. package/dist/esm/components/controls/CartesianPicker/useCartesianRenderer.js.map +1 -1
  114. package/dist/esm/components/controls/ColorPicker/AlphaSlider.js.map +1 -1
  115. package/dist/esm/components/controls/ColorPicker/ColorArea.js.map +1 -1
  116. package/dist/esm/components/controls/ColorPicker/ColorInputs.js.map +1 -1
  117. package/dist/esm/components/controls/ColorPicker/ColorPalette.js.map +1 -1
  118. package/dist/esm/components/controls/ColorPicker/ColorPicker.js.map +1 -1
  119. package/dist/esm/components/controls/ColorPicker/ColorPresets.js.map +1 -1
  120. package/dist/esm/components/controls/ColorPicker/ColorSwatch.js.map +1 -1
  121. package/dist/esm/components/controls/ColorPicker/EyeDropper.js.map +1 -1
  122. package/dist/esm/components/controls/ColorPicker/HueSlider.js.map +1 -1
  123. package/dist/esm/components/controls/ColorPicker/colorUtils.js.map +1 -1
  124. package/dist/esm/components/controls/ColorPicker/palettes/material.js.map +1 -1
  125. package/dist/esm/components/controls/ColorPicker/palettes/professional.js.map +1 -1
  126. package/dist/esm/components/controls/ColorPicker/palettes/tailwind.js.map +1 -1
  127. package/dist/esm/components/controls/ColorPicker/useColor.js.map +1 -1
  128. package/dist/esm/components/controls/Combobox/Combobox.js.map +1 -1
  129. package/dist/esm/components/controls/CurveEditor/CurveCanvas.js.map +1 -1
  130. package/dist/esm/components/controls/CurveEditor/CurveEditor.js.map +1 -1
  131. package/dist/esm/components/controls/CurveEditor/CurveToolbar.js.map +1 -1
  132. package/dist/esm/components/controls/CurveEditor/curvePresets.js.map +1 -1
  133. package/dist/esm/components/controls/CurveEditor/curveUtils.js.map +1 -1
  134. package/dist/esm/components/controls/CurveEditor/useCurveInteraction.js.map +1 -1
  135. package/dist/esm/components/controls/CurveEditor/useCurveRenderer.js.map +1 -1
  136. package/dist/esm/components/controls/FileTree/FileTree.js +7 -1
  137. package/dist/esm/components/controls/FileTree/FileTree.js.map +1 -1
  138. package/dist/esm/components/controls/FileTree/fileTreeIcons.js.map +1 -1
  139. package/dist/esm/components/controls/FileTree/fileTreeLabels.js.map +1 -1
  140. package/dist/esm/components/controls/FileTree/useFileTreeDrop.js +1 -1
  141. package/dist/esm/components/controls/FileTree/useFileTreeDrop.js.map +1 -1
  142. package/dist/esm/components/controls/FileUploader/FileUploader.js.map +1 -1
  143. package/dist/esm/components/controls/MultiSelect/MultiSelect.js.map +1 -1
  144. package/dist/esm/components/controls/NumberInput/NumberInput.js.map +1 -1
  145. package/dist/esm/components/controls/NumberInput/useNumberInput.js.map +1 -1
  146. package/dist/esm/components/controls/Select/Select.css.js +1 -1
  147. package/dist/esm/components/controls/Select/Select.js +15 -7
  148. package/dist/esm/components/controls/Select/Select.js.map +1 -1
  149. package/dist/esm/components/controls/Slider/Slider.css.js +10 -11
  150. package/dist/esm/components/controls/Slider/Slider.css.js.map +1 -1
  151. package/dist/esm/components/controls/Slider/Slider.js +40 -7
  152. package/dist/esm/components/controls/Slider/Slider.js.map +1 -1
  153. package/dist/esm/components/controls/TagInput/TagInput.js.map +1 -1
  154. package/dist/esm/components/controls/TreeView/TreeNode.js +6 -0
  155. package/dist/esm/components/controls/TreeView/TreeNode.js.map +1 -1
  156. package/dist/esm/components/controls/TreeView/TreeView.js.map +1 -1
  157. package/dist/esm/components/controls/TreeView/useTreeState.js.map +1 -1
  158. package/dist/esm/components/controls/VectorInput/VectorInput.js +6 -0
  159. package/dist/esm/components/controls/VectorInput/VectorInput.js.map +1 -1
  160. package/dist/esm/components/data/DataTable/DataTable.js.map +1 -1
  161. package/dist/esm/components/editor/AssetBrowser/AssetBrowser.js.map +1 -1
  162. package/dist/esm/components/editor/AssetBrowser/AssetBrowserAnnouncementLive.js.map +1 -1
  163. package/dist/esm/components/editor/AssetBrowser/AssetBrowserBreadcrumbs.js.map +1 -1
  164. package/dist/esm/components/editor/AssetBrowser/AssetBrowserContent.js +6 -0
  165. package/dist/esm/components/editor/AssetBrowser/AssetBrowserContent.js.map +1 -1
  166. package/dist/esm/components/editor/AssetBrowser/AssetBrowserContext.js.map +1 -1
  167. package/dist/esm/components/editor/AssetBrowser/AssetBrowserContextMenu.js.map +1 -1
  168. package/dist/esm/components/editor/AssetBrowser/AssetBrowserDefaultMenus.js +6 -0
  169. package/dist/esm/components/editor/AssetBrowser/AssetBrowserDefaultMenus.js.map +1 -1
  170. package/dist/esm/components/editor/AssetBrowser/AssetBrowserGrid.js +1 -1
  171. package/dist/esm/components/editor/AssetBrowser/AssetBrowserGrid.js.map +1 -1
  172. package/dist/esm/components/editor/AssetBrowser/AssetBrowserGridItem.js +6 -0
  173. package/dist/esm/components/editor/AssetBrowser/AssetBrowserGridItem.js.map +1 -1
  174. package/dist/esm/components/editor/AssetBrowser/AssetBrowserList.js +6 -0
  175. package/dist/esm/components/editor/AssetBrowser/AssetBrowserList.js.map +1 -1
  176. package/dist/esm/components/editor/AssetBrowser/AssetBrowserRenameField.js.map +1 -1
  177. package/dist/esm/components/editor/AssetBrowser/AssetBrowserSidebar.js.map +1 -1
  178. package/dist/esm/components/editor/AssetBrowser/AssetBrowserStatusBar.js.map +1 -1
  179. package/dist/esm/components/editor/AssetBrowser/AssetBrowserStore.js.map +1 -1
  180. package/dist/esm/components/editor/AssetBrowser/AssetBrowserSurface.js +6 -0
  181. package/dist/esm/components/editor/AssetBrowser/AssetBrowserSurface.js.map +1 -1
  182. package/dist/esm/components/editor/AssetBrowser/AssetBrowserToolbar.js +6 -0
  183. package/dist/esm/components/editor/AssetBrowser/AssetBrowserToolbar.js.map +1 -1
  184. package/dist/esm/components/editor/AssetBrowser/assetBrowserFilter.js.map +1 -1
  185. package/dist/esm/components/editor/AssetBrowser/assetBrowserFormat.js.map +1 -1
  186. package/dist/esm/components/editor/AssetBrowser/assetBrowserGeometry.js.map +1 -1
  187. package/dist/esm/components/editor/AssetBrowser/assetBrowserKeyboard.js.map +1 -1
  188. package/dist/esm/components/editor/AssetBrowser/assetBrowserLabels.js.map +1 -1
  189. package/dist/esm/components/editor/AssetBrowser/slots.js.map +1 -1
  190. package/dist/esm/components/editor/AssetBrowser/useAssetBrowserAnnouncement.js.map +1 -1
  191. package/dist/esm/components/editor/AssetBrowser/useAssetBrowserHandle.js +1 -1
  192. package/dist/esm/components/editor/AssetBrowser/useAssetBrowserHandle.js.map +1 -1
  193. package/dist/esm/components/editor/AssetBrowser/useAssetBrowserViewState.js +1 -1
  194. package/dist/esm/components/editor/AssetBrowser/useAssetBrowserViewState.js.map +1 -1
  195. package/dist/esm/components/editor/AssetBrowser/useAssetDnd.js +1 -1
  196. package/dist/esm/components/editor/AssetBrowser/useAssetDnd.js.map +1 -1
  197. package/dist/esm/components/editor/AssetBrowser/useAssetGridKeyboardNav.js.map +1 -1
  198. package/dist/esm/components/editor/AssetBrowser/useAssetGridVirtualizer.js +1 -1
  199. package/dist/esm/components/editor/AssetBrowser/useAssetGridVirtualizer.js.map +1 -1
  200. package/dist/esm/components/editor/AssetBrowser/useAssetMarqueeGesture.js +1 -1
  201. package/dist/esm/components/editor/AssetBrowser/useAssetMarqueeGesture.js.map +1 -1
  202. package/dist/esm/components/editor/AssetBrowser/useAssetMutationKeyboard.js +1 -1
  203. package/dist/esm/components/editor/AssetBrowser/useAssetMutationKeyboard.js.map +1 -1
  204. package/dist/esm/components/editor/AssetBrowser/useAssetMutations.js +1 -1
  205. package/dist/esm/components/editor/AssetBrowser/useAssetMutations.js.map +1 -1
  206. package/dist/esm/components/editor/AssetBrowser/useAssetNavigation.js +1 -1
  207. package/dist/esm/components/editor/AssetBrowser/useAssetNavigation.js.map +1 -1
  208. package/dist/esm/components/editor/AssetBrowser/useAssetSelectionController.js +1 -1
  209. package/dist/esm/components/editor/AssetBrowser/useAssetSelectionController.js.map +1 -1
  210. package/dist/esm/components/editor/ChatPanel/ChatActionBar.js.map +1 -1
  211. package/dist/esm/components/editor/ChatPanel/ChatAttachment.js.map +1 -1
  212. package/dist/esm/components/editor/ChatPanel/ChatBubble.js.map +1 -1
  213. package/dist/esm/components/editor/ChatPanel/ChatCodeBlock.js +6 -0
  214. package/dist/esm/components/editor/ChatPanel/ChatCodeBlock.js.map +1 -1
  215. package/dist/esm/components/editor/ChatPanel/ChatContextChip.js.map +1 -1
  216. package/dist/esm/components/editor/ChatPanel/ChatEmptyState.js.map +1 -1
  217. package/dist/esm/components/editor/ChatPanel/ChatIcons.js.map +1 -1
  218. package/dist/esm/components/editor/ChatPanel/ChatInput.js +6 -0
  219. package/dist/esm/components/editor/ChatPanel/ChatInput.js.map +1 -1
  220. package/dist/esm/components/editor/ChatPanel/ChatInputToolbar.js.map +1 -1
  221. package/dist/esm/components/editor/ChatPanel/ChatMarkdownRenderer.js.map +1 -1
  222. package/dist/esm/components/editor/ChatPanel/ChatMessage.js +6 -0
  223. package/dist/esm/components/editor/ChatPanel/ChatMessage.js.map +1 -1
  224. package/dist/esm/components/editor/ChatPanel/ChatMessageList.js.map +1 -1
  225. package/dist/esm/components/editor/ChatPanel/ChatPanel.js.map +1 -1
  226. package/dist/esm/components/editor/ChatPanel/ChatToolCall.js.map +1 -1
  227. package/dist/esm/components/editor/ChatPanel/ChatTypingIndicator.js.map +1 -1
  228. package/dist/esm/components/editor/ChatPanel/useChatInput.js.map +1 -1
  229. package/dist/esm/components/editor/ChatPanel/useChatMessages.js.map +1 -1
  230. package/dist/esm/components/editor/ChatPanel/useChatScroll.js.map +1 -1
  231. package/dist/esm/components/editor/Minimap/Minimap.js +1 -1
  232. package/dist/esm/components/editor/Minimap/Minimap.js.map +1 -1
  233. package/dist/esm/components/editor/Minimap/Minimap.types.js.map +1 -1
  234. package/dist/esm/components/editor/Minimap/MinimapContext.js.map +1 -1
  235. package/dist/esm/components/editor/Minimap/MinimapCorner.js.map +1 -1
  236. package/dist/esm/components/editor/Minimap/MinimapFooter.js.map +1 -1
  237. package/dist/esm/components/editor/Minimap/MinimapStore.js.map +1 -1
  238. package/dist/esm/components/editor/Minimap/MinimapTitle.js.map +1 -1
  239. package/dist/esm/components/editor/Minimap/ViewportMinimap.js +1 -1
  240. package/dist/esm/components/editor/Minimap/ViewportMinimap.js.map +1 -1
  241. package/dist/esm/components/editor/Minimap/computeBoundsFromItems.js.map +1 -1
  242. package/dist/esm/components/editor/Minimap/index.js.map +1 -1
  243. package/dist/esm/components/editor/Minimap/minimapCoords.js.map +1 -1
  244. package/dist/esm/components/editor/Minimap/minimapDrawing.js.map +1 -1
  245. package/dist/esm/components/editor/Minimap/minimapHitTest.js.map +1 -1
  246. package/dist/esm/components/editor/Minimap/useMinimapGestures.js +1 -1
  247. package/dist/esm/components/editor/Minimap/useMinimapGestures.js.map +1 -1
  248. package/dist/esm/components/editor/NodeGraph/NodeGraph.js +1 -1
  249. package/dist/esm/components/editor/NodeGraph/NodeGraph.js.map +1 -1
  250. package/dist/esm/components/editor/NodeGraph/NodeGraph.types.js.map +1 -1
  251. package/dist/esm/components/editor/NodeGraph/NodeGraphContext.js.map +1 -1
  252. package/dist/esm/components/editor/NodeGraph/NodeGraphEdgeLabels.js.map +1 -1
  253. package/dist/esm/components/editor/NodeGraph/NodeGraphGroup.js.map +1 -1
  254. package/dist/esm/components/editor/NodeGraph/NodeGraphMinimap.js.map +1 -1
  255. package/dist/esm/components/editor/NodeGraph/NodeGraphNode.js +1 -1
  256. package/dist/esm/components/editor/NodeGraph/NodeGraphNode.js.map +1 -1
  257. package/dist/esm/components/editor/NodeGraph/NodeGraphNodeContext.js.map +1 -1
  258. package/dist/esm/components/editor/NodeGraph/NodeGraphNodeParts.js.map +1 -1
  259. package/dist/esm/components/editor/NodeGraph/NodeGraphNodeSection.js +1 -1
  260. package/dist/esm/components/editor/NodeGraph/NodeGraphNodeSection.js.map +1 -1
  261. package/dist/esm/components/editor/NodeGraph/NodeGraphNodeSectionContext.js.map +1 -1
  262. package/dist/esm/components/editor/NodeGraph/NodeGraphPin.js.map +1 -1
  263. package/dist/esm/components/editor/NodeGraph/NodeGraphPort.js +1 -1
  264. package/dist/esm/components/editor/NodeGraph/NodeGraphPort.js.map +1 -1
  265. package/dist/esm/components/editor/NodeGraph/NodeGraphPortVisual.js.map +1 -1
  266. package/dist/esm/components/editor/NodeGraph/NodeGraphSlots.js.map +1 -1
  267. package/dist/esm/components/editor/NodeGraph/NodeGraphSpawnPalette.js.map +1 -1
  268. package/dist/esm/components/editor/NodeGraph/NodeGraphStore.js.map +1 -1
  269. package/dist/esm/components/editor/NodeGraph/NodeGraphToolbar.js.map +1 -1
  270. package/dist/esm/components/editor/NodeGraph/createTypeMatchValidator.js.map +1 -1
  271. package/dist/esm/components/editor/NodeGraph/nodeGraphActions.js.map +1 -1
  272. package/dist/esm/components/editor/NodeGraph/nodeGraphDrawing.js.map +1 -1
  273. package/dist/esm/components/editor/NodeGraph/nodeGraphIds.js.map +1 -1
  274. package/dist/esm/components/editor/NodeGraph/nodeGraphMath.js.map +1 -1
  275. package/dist/esm/components/editor/NodeGraph/sortSlots.js.map +1 -1
  276. package/dist/esm/components/editor/NodeGraph/useDragGesture.js.map +1 -1
  277. package/dist/esm/components/editor/NodeGraph/useNodeGraph.js +1 -1
  278. package/dist/esm/components/editor/NodeGraph/useNodeGraph.js.map +1 -1
  279. package/dist/esm/components/editor/NodeGraph/useNodeGraphConnection.js +1 -1
  280. package/dist/esm/components/editor/NodeGraph/useNodeGraphConnection.js.map +1 -1
  281. package/dist/esm/components/editor/NodeGraph/useNodeGraphEdgeInteraction.js +1 -1
  282. package/dist/esm/components/editor/NodeGraph/useNodeGraphEdgeInteraction.js.map +1 -1
  283. package/dist/esm/components/editor/NodeGraph/useNodeGraphGroupDrag.js +1 -1
  284. package/dist/esm/components/editor/NodeGraph/useNodeGraphGroupDrag.js.map +1 -1
  285. package/dist/esm/components/editor/NodeGraph/useNodeGraphKeyboard.js +1 -1
  286. package/dist/esm/components/editor/NodeGraph/useNodeGraphKeyboard.js.map +1 -1
  287. package/dist/esm/components/editor/NodeGraph/useNodeGraphNodeDrag.js +1 -1
  288. package/dist/esm/components/editor/NodeGraph/useNodeGraphNodeDrag.js.map +1 -1
  289. package/dist/esm/components/editor/NodeGraph/useStoreSlice.js.map +1 -1
  290. package/dist/esm/components/editor/PropertyInspector/PropertyGroup.js.map +1 -1
  291. package/dist/esm/components/editor/PropertyInspector/PropertyPanel.css.js +4 -3
  292. package/dist/esm/components/editor/PropertyInspector/PropertyPanel.css.js.map +1 -1
  293. package/dist/esm/components/editor/PropertyInspector/PropertyPanel.js +5 -4
  294. package/dist/esm/components/editor/PropertyInspector/PropertyPanel.js.map +1 -1
  295. package/dist/esm/components/editor/PropertyInspector/PropertyRow.css.js +7 -6
  296. package/dist/esm/components/editor/PropertyInspector/PropertyRow.css.js.map +1 -1
  297. package/dist/esm/components/editor/PropertyInspector/PropertyRow.js +18 -3
  298. package/dist/esm/components/editor/PropertyInspector/PropertyRow.js.map +1 -1
  299. package/dist/esm/components/editor/PropertyInspector/PropertySection.css.js +11 -10
  300. package/dist/esm/components/editor/PropertyInspector/PropertySection.css.js.map +1 -1
  301. package/dist/esm/components/editor/PropertyInspector/PropertySection.js +29 -21
  302. package/dist/esm/components/editor/PropertyInspector/PropertySection.js.map +1 -1
  303. package/dist/esm/components/editor/PropertyInspector/usePropertyUndo.js.map +1 -1
  304. package/dist/esm/components/editor/Timeline/Timeline.js +1 -1
  305. package/dist/esm/components/editor/Timeline/Timeline.js.map +1 -1
  306. package/dist/esm/components/editor/Timeline/Timeline.types.js.map +1 -1
  307. package/dist/esm/components/editor/Timeline/TimelineContext.js.map +1 -1
  308. package/dist/esm/components/editor/Timeline/TimelineFooter.js.map +1 -1
  309. package/dist/esm/components/editor/Timeline/TimelineMinimap.js +1 -1
  310. package/dist/esm/components/editor/Timeline/TimelineMinimap.js.map +1 -1
  311. package/dist/esm/components/editor/Timeline/TimelineStore.js.map +1 -1
  312. package/dist/esm/components/editor/Timeline/TimelineToolbar.js.map +1 -1
  313. package/dist/esm/components/editor/Timeline/TimelineTrackHeaders.js +1 -1
  314. package/dist/esm/components/editor/Timeline/TimelineTrackHeaders.js.map +1 -1
  315. package/dist/esm/components/editor/Timeline/TimelineTrackScale.js.map +1 -1
  316. package/dist/esm/components/editor/Timeline/index.js.map +1 -1
  317. package/dist/esm/components/editor/Timeline/timelineCoords.js.map +1 -1
  318. package/dist/esm/components/editor/Timeline/timelineDrawing.js.map +1 -1
  319. package/dist/esm/components/editor/Timeline/timelineEdits.js.map +1 -1
  320. package/dist/esm/components/editor/Timeline/timelineHitTest.js.map +1 -1
  321. package/dist/esm/components/editor/Timeline/timelineLayout.js.map +1 -1
  322. package/dist/esm/components/editor/Timeline/timelineSelection.js.map +1 -1
  323. package/dist/esm/components/editor/Timeline/useTimelineDraw.js +1 -1
  324. package/dist/esm/components/editor/Timeline/useTimelineDraw.js.map +1 -1
  325. package/dist/esm/components/editor/Timeline/useTimelineGestures.js +1 -1
  326. package/dist/esm/components/editor/Timeline/useTimelineGestures.js.map +1 -1
  327. package/dist/esm/components/editor/Timeline/useTimelinePlayback.js.map +1 -1
  328. package/dist/esm/components/editor/TransformControl/TransformControl.js.map +1 -1
  329. package/dist/esm/components/editor/ViewportGizmo/ViewportGizmo.js.map +1 -1
  330. package/dist/esm/components/editor/ViewportGizmo/gizmoMath.js.map +1 -1
  331. package/dist/esm/components/editor/ViewportGizmo/useGizmoInteraction.js.map +1 -1
  332. package/dist/esm/components/editor/ViewportGizmo/useGizmoRenderer.js.map +1 -1
  333. package/dist/esm/components/feedback/Alert/Alert.js.map +1 -1
  334. package/dist/esm/components/feedback/Alert/AlertActions.js.map +1 -1
  335. package/dist/esm/components/feedback/Alert/AlertDescription.js.map +1 -1
  336. package/dist/esm/components/feedback/Alert/AlertTitle.js.map +1 -1
  337. package/dist/esm/components/feedback/CommandPalette/CommandPalette.js.map +1 -1
  338. package/dist/esm/components/feedback/CommandPalette/fuzzySearch.js.map +1 -1
  339. package/dist/esm/components/feedback/CommandPalette/useRecentItems.js.map +1 -1
  340. package/dist/esm/components/feedback/Dialog/Dialog.js.map +1 -1
  341. package/dist/esm/components/feedback/Dialog/DialogBody.js.map +1 -1
  342. package/dist/esm/components/feedback/Dialog/DialogClose.js.map +1 -1
  343. package/dist/esm/components/feedback/Dialog/DialogFooter.js.map +1 -1
  344. package/dist/esm/components/feedback/Dialog/DialogHeader.js.map +1 -1
  345. package/dist/esm/components/feedback/Dialog/useDialogAnimation.js.map +1 -1
  346. package/dist/esm/components/feedback/Drawer/Drawer.js.map +1 -1
  347. package/dist/esm/components/feedback/Drawer/useDrawerAnimation.js.map +1 -1
  348. package/dist/esm/components/feedback/EmptyState/EmptyState.js.map +1 -1
  349. package/dist/esm/components/feedback/LogView/LogView.js.map +1 -1
  350. package/dist/esm/components/feedback/LogView/LogViewBody.js.map +1 -1
  351. package/dist/esm/components/feedback/LogView/LogViewContext.js.map +1 -1
  352. package/dist/esm/components/feedback/LogView/LogViewParts.js.map +1 -1
  353. package/dist/esm/components/feedback/LogView/LogViewStore.js.map +1 -1
  354. package/dist/esm/components/feedback/LogView/logViewLabels.js.map +1 -1
  355. package/dist/esm/components/feedback/LogView/logViewLevels.js.map +1 -1
  356. package/dist/esm/components/feedback/LogView/logViewUtils.js.map +1 -1
  357. package/dist/esm/components/feedback/LogView/useFollowTail.js.map +1 -1
  358. package/dist/esm/components/feedback/LogView/useLogSelection.js.map +1 -1
  359. package/dist/esm/components/feedback/ProgressBar/CircularProgress.js.map +1 -1
  360. package/dist/esm/components/feedback/ProgressBar/ProgressBar.js.map +1 -1
  361. package/dist/esm/components/feedback/Skeleton/Skeleton.js.map +1 -1
  362. package/dist/esm/components/feedback/Skeleton/SkeletonGroup.js.map +1 -1
  363. package/dist/esm/components/feedback/Skeleton/SkeletonLayout.js.map +1 -1
  364. package/dist/esm/components/feedback/Spinner/Spinner.js +12 -3
  365. package/dist/esm/components/feedback/Spinner/Spinner.js.map +1 -1
  366. package/dist/esm/components/feedback/Stat/Stat.js.map +1 -1
  367. package/dist/esm/components/feedback/Toast/ToastContainer.js.map +1 -1
  368. package/dist/esm/components/feedback/Toast/ToastItem.js.map +1 -1
  369. package/dist/esm/components/feedback/Toast/ToastProvider.js.map +1 -1
  370. package/dist/esm/components/feedback/Toast/useToast.js.map +1 -1
  371. package/dist/esm/components/form/FormHelperText.js.map +1 -1
  372. package/dist/esm/components/form/FormLabel.js.map +1 -1
  373. package/dist/esm/components/form/InputWrapper.js.map +1 -1
  374. package/dist/esm/components/layout/Accordion/Accordion.js.map +1 -1
  375. package/dist/esm/components/layout/Accordion/AccordionContent.js.map +1 -1
  376. package/dist/esm/components/layout/Accordion/AccordionItem.js.map +1 -1
  377. package/dist/esm/components/layout/Accordion/AccordionTrigger.js.map +1 -1
  378. package/dist/esm/components/layout/Card/Card.js.map +1 -1
  379. package/dist/esm/components/layout/Divider/Divider.js.map +1 -1
  380. package/dist/esm/components/layout/Flex/Flex.js.map +1 -1
  381. package/dist/esm/components/layout/Grid/Grid.js.map +1 -1
  382. package/dist/esm/components/layout/ListItem/ListItem.js.map +1 -1
  383. package/dist/esm/components/layout/PageHeader/PageHeader.js.map +1 -1
  384. package/dist/esm/components/layout/PanelSurface/PanelSurface.js.map +1 -1
  385. package/dist/esm/components/layout/ScrollArea/ScrollArea.js.map +1 -1
  386. package/dist/esm/components/layout/Spacer/Spacer.js.map +1 -1
  387. package/dist/esm/components/layout/SplitPane/SplitPane.js.map +1 -1
  388. package/dist/esm/components/layout/SplitPane/SplitPanePanel.js.map +1 -1
  389. package/dist/esm/components/layout/Stack/Stack.js.map +1 -1
  390. package/dist/esm/components/navigation/Breadcrumbs/BreadcrumbEllipsis.js.map +1 -1
  391. package/dist/esm/components/navigation/Breadcrumbs/BreadcrumbItem.js.map +1 -1
  392. package/dist/esm/components/navigation/Breadcrumbs/BreadcrumbSeparator.js.map +1 -1
  393. package/dist/esm/components/navigation/Breadcrumbs/Breadcrumbs.js +6 -0
  394. package/dist/esm/components/navigation/Breadcrumbs/Breadcrumbs.js.map +1 -1
  395. package/dist/esm/components/navigation/ContextMenu/ContextMenu.js +2 -2
  396. package/dist/esm/components/navigation/ContextMenu/ContextMenu.js.map +1 -1
  397. package/dist/esm/components/navigation/Menu/Menu.css.js +11 -10
  398. package/dist/esm/components/navigation/Menu/Menu.css.js.map +1 -1
  399. package/dist/esm/components/navigation/Menu/Menu.js +2 -2
  400. package/dist/esm/components/navigation/Menu/Menu.js.map +1 -1
  401. package/dist/esm/components/navigation/Menu/MenuGapContext.js.map +1 -1
  402. package/dist/esm/components/navigation/Pagination/Pagination.js.map +1 -1
  403. package/dist/esm/components/navigation/Pagination/usePagination.js.map +1 -1
  404. package/dist/esm/components/navigation/PathBar/PathBar.js +7 -1
  405. package/dist/esm/components/navigation/PathBar/PathBar.js.map +1 -1
  406. package/dist/esm/components/navigation/PathBar/PathBarSiblingMenu.js +6 -0
  407. package/dist/esm/components/navigation/PathBar/PathBarSiblingMenu.js.map +1 -1
  408. package/dist/esm/components/navigation/PathBar/pathUtils.js.map +1 -1
  409. package/dist/esm/components/navigation/SegmentedControl/SegmentedControl.js.map +1 -1
  410. package/dist/esm/components/navigation/SegmentedControl/SegmentedControlItem.js.map +1 -1
  411. package/dist/esm/components/navigation/Tabs/Tab.js.map +1 -1
  412. package/dist/esm/components/navigation/Tabs/TabList.js.map +1 -1
  413. package/dist/esm/components/navigation/Tabs/TabPanel.js.map +1 -1
  414. package/dist/esm/components/navigation/Tabs/Tabs.js.map +1 -1
  415. package/dist/esm/components/primitives/Avatar/Avatar.js +6 -0
  416. package/dist/esm/components/primitives/Avatar/Avatar.js.map +1 -1
  417. package/dist/esm/components/primitives/Avatar/AvatarGroup.js.map +1 -1
  418. package/dist/esm/components/primitives/Badge/Badge.js +6 -0
  419. package/dist/esm/components/primitives/Badge/Badge.js.map +1 -1
  420. package/dist/esm/components/primitives/Button/Button.js.map +1 -1
  421. package/dist/esm/components/primitives/Checkbox/Checkbox.js.map +1 -1
  422. package/dist/esm/components/primitives/Checkbox/CheckboxGroup.js.map +1 -1
  423. package/dist/esm/components/primitives/Code/Code.js.map +1 -1
  424. package/dist/esm/components/primitives/Collapsible/Collapsible.js.map +1 -1
  425. package/dist/esm/components/primitives/HoverCard/HoverCard.js.map +1 -1
  426. package/dist/esm/components/primitives/Icon/Icon.js.map +1 -1
  427. package/dist/esm/components/primitives/IconButton/IconButton.js +4 -2
  428. package/dist/esm/components/primitives/IconButton/IconButton.js.map +1 -1
  429. package/dist/esm/components/primitives/Input/Input.js.map +1 -1
  430. package/dist/esm/components/primitives/Kbd/Kbd.js.map +1 -1
  431. package/dist/esm/components/primitives/Link/Link.js.map +1 -1
  432. package/dist/esm/components/primitives/Paper/Paper.js.map +1 -1
  433. package/dist/esm/components/primitives/Popover/Popover.js.map +1 -1
  434. package/dist/esm/components/primitives/Popover/PopoverClose.js.map +1 -1
  435. package/dist/esm/components/primitives/Popover/PopoverContent.js.map +1 -1
  436. package/dist/esm/components/primitives/Popover/PopoverTrigger.js.map +1 -1
  437. package/dist/esm/components/primitives/Radio/Radio.js.map +1 -1
  438. package/dist/esm/components/primitives/Radio/RadioGroup.js.map +1 -1
  439. package/dist/esm/components/primitives/Switch/Switch.js.map +1 -1
  440. package/dist/esm/components/primitives/Text/Text.js.map +1 -1
  441. package/dist/esm/components/primitives/TextArea/TextArea.js.map +1 -1
  442. package/dist/esm/components/primitives/Tooltip/Arrow.js.map +1 -1
  443. package/dist/esm/components/primitives/Tooltip/Tooltip.css.js +6 -5
  444. package/dist/esm/components/primitives/Tooltip/Tooltip.css.js.map +1 -1
  445. package/dist/esm/components/primitives/Tooltip/Tooltip.js +12 -2
  446. package/dist/esm/components/primitives/Tooltip/Tooltip.js.map +1 -1
  447. package/dist/esm/components/primitives/Tooltip/utils.js.map +1 -1
  448. package/dist/esm/components/primitives/VisuallyHidden/VisuallyHidden.js.map +1 -1
  449. package/dist/esm/components/primitives/canvas/CanvasContainer.js.map +1 -1
  450. package/dist/esm/components/primitives/canvas/canvasCoords.js.map +1 -1
  451. package/dist/esm/components/primitives/canvas/canvasDrawing.js.map +1 -1
  452. package/dist/esm/components/primitives/canvas/canvasTheme.js.map +1 -1
  453. package/dist/esm/components/primitives/canvas/useCanvasRenderer.js.map +1 -1
  454. package/dist/esm/components/primitives/viewport/Viewport.js +6 -3
  455. package/dist/esm/components/primitives/viewport/Viewport.js.map +1 -1
  456. package/dist/esm/components/primitives/viewport/ViewportContext.js.map +1 -1
  457. package/dist/esm/components/primitives/viewport/ViewportLayer.js +1 -1
  458. package/dist/esm/components/primitives/viewport/ViewportLayer.js.map +1 -1
  459. package/dist/esm/components/primitives/viewport/ViewportOverlay.js.map +1 -1
  460. package/dist/esm/components/primitives/viewport/ViewportStore.js.map +1 -1
  461. package/dist/esm/components/primitives/viewport/ViewportWorld.js.map +1 -1
  462. package/dist/esm/components/primitives/viewport/useViewportGestures.js +1 -1
  463. package/dist/esm/components/primitives/viewport/useViewportGestures.js.map +1 -1
  464. package/dist/esm/components/primitives/viewport/viewportCoords.js.map +1 -1
  465. package/dist/esm/components/primitives/viewport/viewportMath.js.map +1 -1
  466. package/dist/esm/components/shell/AppShell/AppShell.css.js +1 -1
  467. package/dist/esm/components/shell/AppShell/AppShell.js.map +1 -1
  468. package/dist/esm/components/shell/FloatingPanel/FloatingPanel.js.map +1 -1
  469. package/dist/esm/components/shell/MenuBar/MenuBar.css.js +7 -6
  470. package/dist/esm/components/shell/MenuBar/MenuBar.css.js.map +1 -1
  471. package/dist/esm/components/shell/MenuBar/MenuBar.js +52 -3
  472. package/dist/esm/components/shell/MenuBar/MenuBar.js.map +1 -1
  473. package/dist/esm/components/shell/StatusBar/StatusBar.js.map +1 -1
  474. package/dist/esm/components/shell/Toolbar/Toolbar.js.map +1 -1
  475. package/dist/esm/context/KeyboardContext.js.map +1 -1
  476. package/dist/esm/hooks/useBreakpoint/useBreakpoint.js.map +1 -1
  477. package/dist/esm/hooks/useClickOutside/useClickOutside.js.map +1 -1
  478. package/dist/esm/hooks/useClipboard/useClipboard.js.map +1 -1
  479. package/dist/esm/hooks/useControlledState/useControlledState.js.map +1 -1
  480. package/dist/esm/hooks/useDebounced/useDebouncedCallback.js.map +1 -1
  481. package/dist/esm/hooks/useDebounced/useDebouncedValue.js.map +1 -1
  482. package/dist/esm/hooks/useDisclosure/useDisclosure.js.map +1 -1
  483. package/dist/esm/hooks/useEventCallback/useEventCallback.js.map +1 -1
  484. package/dist/esm/hooks/useFocusTrap/useFocusTrap.js.map +1 -1
  485. package/dist/esm/hooks/useHotkey/useHotkey.js.map +1 -1
  486. package/dist/esm/hooks/useIntersectionObserver/useIntersectionObserver.js.map +1 -1
  487. package/dist/esm/hooks/useIsMounted/useIsMounted.js.map +1 -1
  488. package/dist/esm/hooks/useKeyboard/useKeyboard.js.map +1 -1
  489. package/dist/esm/hooks/useKeyboard/utils.js.map +1 -1
  490. package/dist/esm/hooks/useLatest/useLatest.js.map +1 -1
  491. package/dist/esm/hooks/useListboxNav/useListboxNav.js.map +1 -1
  492. package/dist/esm/hooks/useMediaQuery/useMediaQuery.js.map +1 -1
  493. package/dist/esm/hooks/useMergedRef/useMergedRef.js.map +1 -1
  494. package/dist/esm/hooks/useNavigationHistory/useNavigationHistory.js.map +1 -1
  495. package/dist/esm/hooks/useResizeObserver/useResizeObserver.js.map +1 -1
  496. package/dist/esm/hooks/useTheme/useTheme.js +1 -1
  497. package/dist/esm/hooks/useTheme/useTheme.js.map +1 -1
  498. package/dist/esm/hooks/useThrottledCallback/useThrottledCallback.js.map +1 -1
  499. package/dist/esm/index.js +6 -0
  500. package/dist/esm/index.js.map +1 -1
  501. package/dist/esm/node_modules/tslib/tslib.es6.js.map +1 -1
  502. package/dist/esm/styles.css.js +3 -0
  503. package/dist/esm/styles.css.js.map +1 -0
  504. package/dist/esm/theme/ThemeProvider.js +5 -3
  505. package/dist/esm/theme/ThemeProvider.js.map +1 -1
  506. package/dist/esm/theme/VanillaThemeProvider.js.map +1 -1
  507. package/dist/esm/theme/breakpoints.js.map +1 -1
  508. package/dist/esm/theme/createCustomTheme.js +1 -1
  509. package/dist/esm/theme/createCustomTheme.js.map +1 -1
  510. package/dist/esm/theme/createLightTheme.js.map +1 -1
  511. package/dist/esm/theme/darkThemeValues.js.map +1 -1
  512. package/dist/esm/theme/lightThemeValues.js.map +1 -1
  513. package/dist/esm/theme/themeContractData.js.map +1 -1
  514. package/dist/esm/utils/cx.js.map +1 -1
  515. package/dist/esm/utils/devWarn.js.map +1 -1
  516. package/dist/esm/utils/mathExpression.js.map +1 -1
  517. package/dist/esm/utils/mathUtils.js.map +1 -1
  518. package/dist/esm/utils/objects.js.map +1 -1
  519. package/dist/esm/utils/platform.js.map +1 -1
  520. package/dist/tokens/tokens.dark.css +1 -1
  521. package/dist/tokens/tokens.json +1 -1
  522. package/dist/tokens/tokens.light.css +1 -1
  523. package/dist/types/components/Icons/CameraIcon.d.ts +34 -0
  524. package/dist/types/components/Icons/CubeIcon.d.ts +34 -0
  525. package/dist/types/components/Icons/OrbitIcon.d.ts +34 -0
  526. package/dist/types/components/Icons/RotateIcon.d.ts +35 -0
  527. package/dist/types/components/Icons/SpinIcon.d.ts +35 -0
  528. package/dist/types/components/Icons/ZoomFitIcon.d.ts +35 -0
  529. package/dist/types/components/controls/Select/Select.d.ts +1 -1
  530. package/dist/types/components/controls/Select/Select.types.d.ts +43 -9
  531. package/dist/types/components/controls/Slider/Slider.d.ts +7 -0
  532. package/dist/types/components/editor/PropertyInspector/PropertyInspector.types.d.ts +49 -3
  533. package/dist/types/components/feedback/Spinner/Spinner.d.ts +6 -1
  534. package/dist/types/components/feedback/Spinner/Spinner.types.d.ts +8 -0
  535. package/dist/types/components/primitives/Button/Button.d.ts +1 -1
  536. package/dist/types/components/primitives/IconButton/IconButton.d.ts +3 -2
  537. package/dist/types/components/primitives/viewport/Viewport.d.ts +5 -2
  538. package/dist/types/components/shell/MenuBar/MenuBar.d.ts +862 -0
  539. package/dist/types/index.d.ts +6 -0
  540. package/dist/types/theme/ThemeProvider.d.ts +5 -3
  541. package/package.json +6 -2
  542. package/dist/esm/_virtual/_commonjsHelpers.js +0 -6
  543. package/dist/esm/_virtual/_commonjsHelpers.js.map +0 -1
  544. package/dist/esm/_virtual/cjs.js +0 -4
  545. package/dist/esm/_virtual/cjs.js.map +0 -1
  546. package/dist/esm/_virtual/cssesc.js +0 -8
  547. package/dist/esm/_virtual/cssesc.js.map +0 -1
  548. package/dist/esm/_virtual/picocolors.js +0 -8
  549. package/dist/esm/_virtual/picocolors.js.map +0 -1
  550. package/dist/esm/_virtual/picocolors2.js +0 -4
  551. package/dist/esm/_virtual/picocolors2.js.map +0 -1
  552. package/dist/esm/node_modules/@emotion/hash/dist/emotion-hash.esm.js +0 -56
  553. package/dist/esm/node_modules/@emotion/hash/dist/emotion-hash.esm.js.map +0 -1
  554. package/dist/esm/node_modules/@vanilla-extract/css/adapter/dist/vanilla-extract-css-adapter.esm.js +0 -46
  555. package/dist/esm/node_modules/@vanilla-extract/css/adapter/dist/vanilla-extract-css-adapter.esm.js.map +0 -1
  556. package/dist/esm/node_modules/@vanilla-extract/css/dist/taggedTemplateLiteral-10998315.esm.js +0 -13
  557. package/dist/esm/node_modules/@vanilla-extract/css/dist/taggedTemplateLiteral-10998315.esm.js.map +0 -1
  558. package/dist/esm/node_modules/@vanilla-extract/css/dist/transformCss-fd0786e1.esm.js +0 -962
  559. package/dist/esm/node_modules/@vanilla-extract/css/dist/transformCss-fd0786e1.esm.js.map +0 -1
  560. package/dist/esm/node_modules/@vanilla-extract/css/dist/vanilla-extract-css.esm.js +0 -309
  561. package/dist/esm/node_modules/@vanilla-extract/css/dist/vanilla-extract-css.esm.js.map +0 -1
  562. package/dist/esm/node_modules/@vanilla-extract/css/fileScope/dist/vanilla-extract-css-fileScope.esm.js +0 -18
  563. package/dist/esm/node_modules/@vanilla-extract/css/fileScope/dist/vanilla-extract-css-fileScope.esm.js.map +0 -1
  564. package/dist/esm/node_modules/@vanilla-extract/css/injectStyles/dist/vanilla-extract-css-injectStyles.esm.js +0 -23
  565. package/dist/esm/node_modules/@vanilla-extract/css/injectStyles/dist/vanilla-extract-css-injectStyles.esm.js.map +0 -1
  566. package/dist/esm/node_modules/@vanilla-extract/css/node_modules/lru-cache/dist/esm/index.js +0 -1543
  567. package/dist/esm/node_modules/@vanilla-extract/css/node_modules/lru-cache/dist/esm/index.js.map +0 -1
  568. package/dist/esm/node_modules/@vanilla-extract/private/dist/vanilla-extract-private.esm.js +0 -38
  569. package/dist/esm/node_modules/@vanilla-extract/private/dist/vanilla-extract-private.esm.js.map +0 -1
  570. package/dist/esm/node_modules/css-what/lib/es/parse.js +0 -424
  571. package/dist/esm/node_modules/css-what/lib/es/parse.js.map +0 -1
  572. package/dist/esm/node_modules/css-what/lib/es/types.js +0 -29
  573. package/dist/esm/node_modules/css-what/lib/es/types.js.map +0 -1
  574. package/dist/esm/node_modules/cssesc/cssesc.js +0 -120
  575. package/dist/esm/node_modules/cssesc/cssesc.js.map +0 -1
  576. package/dist/esm/node_modules/dedent/dist/dedent.js +0 -100
  577. package/dist/esm/node_modules/dedent/dist/dedent.js.map +0 -1
  578. package/dist/esm/node_modules/deep-object-diff/mjs/diff.js +0 -40
  579. package/dist/esm/node_modules/deep-object-diff/mjs/diff.js.map +0 -1
  580. package/dist/esm/node_modules/deep-object-diff/mjs/utils.js +0 -9
  581. package/dist/esm/node_modules/deep-object-diff/mjs/utils.js.map +0 -1
  582. package/dist/esm/node_modules/deepmerge/dist/cjs.js +0 -143
  583. package/dist/esm/node_modules/deepmerge/dist/cjs.js.map +0 -1
  584. package/dist/esm/node_modules/media-query-parser/dist/media-query-parser.esm.js +0 -1386
  585. package/dist/esm/node_modules/media-query-parser/dist/media-query-parser.esm.js.map +0 -1
  586. package/dist/esm/node_modules/modern-ahocorasick/dist/index.js +0 -102
  587. package/dist/esm/node_modules/modern-ahocorasick/dist/index.js.map +0 -1
  588. package/dist/esm/node_modules/picocolors/picocolors.js +0 -87
  589. package/dist/esm/node_modules/picocolors/picocolors.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"Combobox.js","sources":["../../../../../../src/components/controls/Combobox/Combobox.tsx"],"sourcesContent":[null],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;AAkDA;AACE;AACA;AACA;;AAGF;AAOA;AAIE;AACA;AACF;AAEA;AAKE;AAAY;;AAGZ;AACE;;;AAGE;;;;;;;;AAMA;;;;;AAIJ;AACA;AACF;AAEA;;;;;;;;;;;;;;;;;;;AAmBG;AACG;AAqCJ;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;;AAIA;;;;AAIE;AACD;AAED;;AAEsB;AAClB;AACA;AACF;AAIF;;;;;;;;AAQA;;;;AAKE;AACE;;;;AAKJ;;;;AAKA;;;;;;;;;;AAmBA;AACE;AAAoB;AACpB;;AAEF;;;AAKE;AACA;;;;;;AAOF;AACE;;;AAGI;AACA;AACD;;AAEH;;AAGF;;;AAEE;;;AAEA;;AAGF;AACE;;;AAEA;AACF;AAEA;;;AAGI;;AAEE;;;;;;AAGA;;;;AAMF;;;AAMF;;AAEA;AACA;AACD;AAED;AACE;AACA;;AACA;;AAEA;AAGA;;AAGI;AAEJ;;AAEE;AACA;AACE;;AAEI;AACD;AACH;AACH;AACH;;AAGE;;AACA;AACF;;;AAIE;;AACA;AACE;;;AAKE;;AAEJ;AACA;AACA;AACE;AACF;AACF;AAEA;AAEI;;;AAGA;AACA;AAAa;;;;;AAKf;AAIF;;;AAII;;;AAGI;;;;AAKJ;;AAEE;;AAEE;AACE;;;;;;;;;AAMF;;;;;;AAOF;;;;AAGE;;;;AAKJ;AACF;;;;;;;;;;;;;AAcC;AAGH;;AAEE;AAAiB;AACnB;AAEA;;;;;AAKI;;;AAIJ;;;;;;AAOI;AACF;AAIF;;;AAEE;AAAY;;AACP;AACL;AACF;AAEA;AACA;;;AAII;;AAMF;AACE;;;AAGA;;AAEA;AACA;AACA;AAUQ;AACA;AACA;;;AAMJ;AAEE;;;;AAIF;AAEE;AAAiB;AACnB;;AAMI;AAYV;AACF;AAEA;AACA;;;;;;;AAuBO;;AAaK;;;AAsDN;AA8BR;AAEA;;"}
1
+ {"version":3,"file":"Combobox.js","sources":["src/components/controls/Combobox/Combobox.tsx"],"sourcesContent":["'use client';\n\nimport React, {\n useCallback,\n useDeferredValue,\n useEffect,\n useId,\n useMemo,\n useRef,\n useState,\n} from 'react';\nimport { createPortal } from 'react-dom';\n\nimport { fuzzyScore } from '@/components/feedback/CommandPalette/fuzzySearch';\nimport { Spinner } from '@/components/feedback/Spinner';\nimport { FormHelperText } from '@/components/form/FormHelperText';\nimport { FormLabel } from '@/components/form/FormLabel';\nimport { CheckIcon } from '@/components/Icons/CheckIcon';\nimport { ChevronDownIcon } from '@/components/Icons/ChevronDownIcon';\nimport { CloseIcon } from '@/components/Icons/CloseIcon';\nimport { ScrollArea } from '@/components/layout/ScrollArea';\nimport { useControlledState } from '@/hooks/useControlledState';\nimport { useLatest } from '@/hooks/useLatest';\nimport { useListboxNav } from '@/hooks/useListboxNav';\nimport { useMergedRef } from '@/hooks/useMergedRef';\nimport { cx } from '@/utils/cx';\n\nimport {\n checkmarkStyle,\n chevronButtonStyle,\n chevronIconRecipe,\n clearButtonStyle,\n containerStyle,\n createRowStyle,\n dropdownStyle,\n emptyMessageStyle,\n inputStyle,\n inputWrapperRecipe,\n optionDescriptionStyle,\n optionItemRecipe,\n optionLabelStyle,\n optionsListStyle,\n} from './Combobox.css';\n\nimport type {\n ComboboxOption,\n ComboboxProps,\n ComboboxSize,\n} from './Combobox.types';\n\nconst CHEVRON_SIZES: Record<ComboboxSize, number> = {\n sm: 10,\n md: 12,\n lg: 14,\n};\n\nconst CREATE_ROW_VALUE = '__combobox_create__';\n\ninterface FilteredEntry<T extends string> {\n option: ComboboxOption<T>;\n score: number;\n}\n\nfunction defaultFilter<T extends string>(\n option: ComboboxOption<T>,\n query: string\n): number {\n const haystack = `${option.label ?? option.value} ${option.description ?? ''}`;\n return fuzzyScore(query, haystack);\n}\n\nfunction applyFilter<T extends string>(\n options: ComboboxOption<T>[],\n query: string,\n custom?: (option: ComboboxOption<T>, query: string) => boolean | number\n): ComboboxOption<T>[] {\n if (!query) return options;\n\n const scored: FilteredEntry<T>[] = [];\n for (const option of options) {\n let score: number;\n if (custom) {\n const result = custom(option, query);\n if (typeof result === 'boolean') {\n score = result ? 1 : 0;\n } else {\n score = result;\n }\n } else {\n score = defaultFilter(option, query);\n }\n if (score > 0) scored.push({ option, score });\n }\n scored.sort((a, b) => b.score - a.score);\n return scored.map(entry => entry.option);\n}\n\n/**\n * Single-value select with an editable input. Filters the option list as the\n * user types (built-in fuzzy matcher, configurable via `filterFn`), supports\n * controlled and uncontrolled modes, optional `freeSolo` typing, optional\n * `creatable` mode for adding new entries, and an `loading` state for async\n * data sources.\n *\n * @example\n * ```tsx\n * <Combobox\n * label=\"Framework\"\n * options={[\n * { value: 'react', label: 'React' },\n * { value: 'vue', label: 'Vue' },\n * ]}\n * value={framework}\n * onChange={setFramework}\n * />\n * ```\n */\nexport function Combobox<T extends string = string>({\n value,\n defaultValue,\n options,\n placeholder,\n filterFn,\n emptyMessage = 'No results found',\n loading = false,\n loadingMessage = 'Loading...',\n freeSolo = false,\n creatable = false,\n createLabel,\n openOnFocus = false,\n size = 'md',\n variant = 'default',\n label,\n helperText,\n error = false,\n errorMessage,\n disabled = false,\n required = false,\n readOnly = false,\n clearable = false,\n maxDropdownHeight = 240,\n minDropdownWidth,\n name,\n onChange,\n onInputChange,\n onCreate,\n onOpenChange,\n className,\n style,\n testId,\n ref,\n id: idProp,\n ...rest\n}: ComboboxProps<T>) {\n const autoId = useId();\n const fieldId = idProp ?? autoId;\n const labelId = `${fieldId}-label`;\n const helperId = `${fieldId}-helper`;\n const listboxId = `${fieldId}-listbox`;\n\n const inputRef = useRef<HTMLInputElement | null>(null);\n const wrapperRef = useRef<HTMLDivElement | null>(null);\n const dropdownRef = useRef<HTMLDivElement | null>(null);\n\n const setInputRef = useMergedRef(inputRef, ref);\n\n const [selected, setSelected] = useControlledState<T | null>({\n value,\n defaultValue,\n onChange,\n fallback: null,\n });\n\n const labelOf = useCallback(\n (val: T | null): string => {\n if (val === null) return '';\n const found = options.find(o => o.value === val);\n return found?.label ?? val;\n },\n [options]\n );\n\n const [query, setQuery] = useState<string>(() => labelOf(selected));\n const [isOpen, setIsOpen] = useState(false);\n const [focused, setFocused] = useState(false);\n const [dropdownPos, setDropdownPos] = useState<React.CSSProperties>({});\n // Editing flag is reactive (drives the filter memo below). The latest-ref\n // mirror lets the selected-sync effect read it without re-running when the\n // flag toggles.\n const [isEditing, setIsEditing] = useState(false);\n const isEditingRef = useLatest(isEditing);\n\n // Sync the query with the selected label when it changes externally and the\n // user is not actively typing.\n useEffect(() => {\n if (!isEditingRef.current) {\n setQuery(labelOf(selected));\n }\n }, [labelOf, selected, isEditingRef]);\n\n const effectiveCreatable = creatable;\n const effectiveFreeSolo = freeSolo || creatable;\n\n // Defer the query that drives filtering so the input stays responsive while\n // a large option list re-filters. The immediate `query` still drives the\n // input value and the create-row label below.\n const deferredQuery = useDeferredValue(query);\n\n const filteredOptions = useMemo(\n () =>\n applyFilter(\n options,\n // When the user hasn't typed yet (query equals the selected label) we\n // show the full list so the dropdown serves as a browseable menu.\n isEditing ? deferredQuery : '',\n filterFn\n ),\n [filterFn, options, deferredQuery, isEditing]\n );\n\n // Detect exact matches against the deferred query so this stays in lockstep\n // with `filteredOptions`. Using the immediate `query` here would desync\n // which rows *exist* from which rows *render*: backspacing to an exact match\n // flashes the create row / \"No results\" for a frame, and the navItems length\n // mismatch skews the useListboxNav and aria-activedescendant indices.\n const hasExactMatch = useMemo(() => {\n if (!deferredQuery) return false;\n const q = deferredQuery.toLowerCase();\n return options.some(o => (o.label ?? o.value).toLowerCase() === q);\n }, [options, deferredQuery]);\n\n const showCreateRow =\n effectiveCreatable &&\n deferredQuery.length > 0 &&\n !hasExactMatch &&\n !loading;\n\n // The list passed to useListboxNav. The \"create\" row is appended at the\n // end as a synthetic option so the same navigation works. Its existence is\n // gated by the deferred-query generation above, so navItems matches the\n // rendered rows exactly; only its label tracks the immediate `query` — that\n // is the literal text `commitOption` will create.\n const navItems = useMemo<ComboboxOption<T>[]>(() => {\n const list = [...filteredOptions];\n if (showCreateRow) {\n list.push({\n value: CREATE_ROW_VALUE as T,\n label: query,\n });\n }\n return list;\n }, [filteredOptions, query, showCreateRow]);\n\n const open = useCallback(() => {\n if (disabled || readOnly) return;\n if (isOpen) return;\n setIsOpen(true);\n onOpenChange?.(true);\n }, [disabled, readOnly, isOpen, onOpenChange]);\n\n const close = useCallback(() => {\n if (!isOpen) return;\n setIsOpen(false);\n onOpenChange?.(false);\n }, [isOpen, onOpenChange]);\n\n const commitOption = useCallback(\n (opt: ComboboxOption<T>) => {\n if (opt.disabled) return;\n if (opt.value === (CREATE_ROW_VALUE as T)) {\n const created = query;\n if (onCreate) onCreate(created);\n else setSelected(created as T);\n } else {\n setSelected(opt.value);\n }\n setIsEditing(false);\n setQuery(\n opt.value === (CREATE_ROW_VALUE as T) ? query : (opt.label ?? opt.value)\n );\n close();\n },\n [close, onCreate, query, setSelected]\n );\n\n const listbox = useListboxNav<ComboboxOption<T>>({\n items: navItems,\n isItemDisabled: opt => Boolean(opt.disabled),\n onSelect: commitOption,\n onEscape: close,\n });\n\n const updateDropdownPosition = useCallback(() => {\n const wrapper = wrapperRef.current;\n if (!wrapper) return;\n const rect = wrapper.getBoundingClientRect();\n const spaceBelow = window.innerHeight - rect.bottom;\n const openAbove =\n spaceBelow < maxDropdownHeight + 8 && rect.top > spaceBelow;\n\n const dropdownW =\n minDropdownWidth !== undefined\n ? Math.max(rect.width, minDropdownWidth)\n : rect.width;\n\n setDropdownPos({\n left: rect.left,\n width: dropdownW,\n ...(openAbove\n ? {\n bottom: window.innerHeight - rect.top + 4,\n transformOrigin: 'bottom',\n }\n : { top: rect.bottom + 4, transformOrigin: 'top' }),\n });\n }, [maxDropdownHeight, minDropdownWidth]);\n\n useEffect(() => {\n if (!isOpen) return;\n updateDropdownPosition();\n }, [isOpen, updateDropdownPosition]);\n\n // Outside click\n useEffect(() => {\n if (!isOpen) return;\n const handleClick = (e: MouseEvent) => {\n const target = e.target as Node;\n if (\n !wrapperRef.current?.contains(target) &&\n !dropdownRef.current?.contains(target)\n ) {\n close();\n }\n };\n document.addEventListener('mousedown', handleClick);\n return () => {\n document.removeEventListener('mousedown', handleClick);\n };\n }, [close, isOpen]);\n\n const handleInputChange = useCallback(\n (event: React.ChangeEvent<HTMLInputElement>) => {\n const next = event.target.value;\n setIsEditing(true);\n setQuery(next);\n onInputChange?.(next);\n if (!isOpen) open();\n // Clearing the input clears the selection.\n if (next === '' && selected !== null) {\n setSelected(null);\n }\n },\n [isOpen, onInputChange, open, selected, setSelected]\n );\n\n const handleInputKeyDown = useCallback(\n (event: React.KeyboardEvent<HTMLInputElement>) => {\n if (disabled || readOnly) return;\n\n if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {\n if (!isOpen) {\n event.preventDefault();\n open();\n return;\n }\n }\n\n if (event.key === 'Enter') {\n // freeSolo / creatable take precedence when no suggestion is active.\n if (effectiveFreeSolo && listbox.activeIndex < 0 && query.length > 0) {\n event.preventDefault();\n if (effectiveCreatable && !hasExactMatch) {\n if (onCreate) onCreate(query);\n else setSelected(query as T);\n } else {\n setSelected(query as T);\n }\n setIsEditing(false);\n close();\n return;\n }\n }\n\n if (event.key === 'Tab' && isOpen) {\n // Commit the active suggestion if any, otherwise close.\n if (listbox.activeIndex >= 0) {\n listbox.selectActive();\n } else {\n close();\n }\n return;\n }\n\n listbox.handleKeyDown(event);\n },\n [\n close,\n disabled,\n effectiveCreatable,\n effectiveFreeSolo,\n hasExactMatch,\n isOpen,\n listbox,\n onCreate,\n open,\n query,\n readOnly,\n setSelected,\n ]\n );\n\n const handleFocus = useCallback(() => {\n setFocused(true);\n if (openOnFocus) open();\n }, [open, openOnFocus]);\n\n const handleBlur = useCallback(() => {\n setFocused(false);\n setIsEditing(false);\n // Restore the last selected label unless freeSolo allowed the typed value.\n if (!effectiveFreeSolo) {\n setQuery(labelOf(selected));\n }\n }, [effectiveFreeSolo, labelOf, selected]);\n\n const handleClear = useCallback(\n (event: React.MouseEvent) => {\n event.preventDefault();\n event.stopPropagation();\n setSelected(null);\n setQuery('');\n setIsEditing(false);\n inputRef.current?.focus();\n },\n [setSelected]\n );\n\n const handleToggleClick = useCallback(() => {\n if (disabled || readOnly) return;\n if (isOpen) close();\n else open();\n inputRef.current?.focus();\n }, [close, disabled, isOpen, open, readOnly]);\n\n const showHelperText = error && errorMessage ? errorMessage : helperText;\n const chevronSize = CHEVRON_SIZES[size];\n\n const renderOptions = () => {\n if (loading) {\n return (\n <div className={emptyMessageStyle}>\n <Spinner size=\"xs\" /> {loadingMessage}\n </div>\n );\n }\n if (navItems.length === 0) {\n return <div className={emptyMessageStyle}>{emptyMessage}</div>;\n }\n return navItems.map((opt, idx) => {\n const isCreate = opt.value === (CREATE_ROW_VALUE as T);\n const isSelected = !isCreate && opt.value === selected;\n const isActive = idx === listbox.activeIndex;\n const isDisabled = opt.disabled ?? false;\n return (\n <div\n key={isCreate ? `create-${query}` : `${opt.value}-${String(idx)}`}\n role=\"option\"\n aria-selected={isSelected}\n aria-disabled={isDisabled || undefined}\n id={`${listboxId}-${String(idx)}`}\n tabIndex={-1}\n className={cx(\n optionItemRecipe({\n active: isActive,\n selected: isSelected,\n disabled: isDisabled,\n }),\n isCreate && createRowStyle\n )}\n onClick={() => {\n commitOption(opt);\n }}\n onKeyDown={e => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n commitOption(opt);\n }\n }}\n onMouseEnter={() => {\n if (!isDisabled) listbox.setActiveIndex(idx);\n }}\n >\n {opt.icon && <span>{opt.icon}</span>}\n <span className={optionLabelStyle}>\n {isCreate\n ? (createLabel?.(query) ?? <>Create &quot;{query}&quot;</>)\n : (opt.label ?? opt.value)}\n </span>\n {opt.description && !isCreate && (\n <span className={optionDescriptionStyle}>{opt.description}</span>\n )}\n {isSelected && (\n <span className={checkmarkStyle}>\n <CheckIcon size={10} decorative />\n </span>\n )}\n </div>\n );\n });\n };\n\n const formValue = selected ?? '';\n const displayValue = focused || isOpen ? query : labelOf(selected);\n\n return (\n <div className={cx(containerStyle, className)} style={style}>\n {label && (\n <FormLabel\n id={labelId}\n htmlFor={fieldId}\n required={required}\n disabled={disabled}\n >\n {label}\n </FormLabel>\n )}\n\n <div\n ref={wrapperRef}\n className={inputWrapperRecipe({\n size,\n variant,\n focused,\n error,\n disabled,\n })}\n >\n <input\n ref={setInputRef}\n id={fieldId}\n type=\"text\"\n role=\"combobox\"\n aria-expanded={isOpen}\n aria-controls={isOpen ? listboxId : undefined}\n aria-autocomplete=\"list\"\n aria-activedescendant={\n isOpen && listbox.activeIndex >= 0\n ? `${listboxId}-${String(listbox.activeIndex)}`\n : undefined\n }\n aria-labelledby={label ? labelId : undefined}\n aria-required={required || undefined}\n aria-invalid={error || undefined}\n aria-describedby={showHelperText ? helperId : undefined}\n autoComplete=\"off\"\n spellCheck={false}\n disabled={disabled}\n readOnly={readOnly}\n required={required}\n placeholder={placeholder}\n value={displayValue}\n className={inputStyle}\n onChange={handleInputChange}\n onKeyDown={handleInputKeyDown}\n onFocus={handleFocus}\n onBlur={handleBlur}\n data-testid={testId}\n {...rest}\n />\n\n {clearable && selected !== null && !disabled && !readOnly && (\n <button\n type=\"button\"\n tabIndex={-1}\n aria-label=\"Clear selection\"\n className={clearButtonStyle}\n onMouseDown={handleClear}\n >\n <CloseIcon size=\"sm\" decorative />\n </button>\n )}\n\n <button\n type=\"button\"\n tabIndex={-1}\n aria-label={isOpen ? 'Close suggestions' : 'Open suggestions'}\n className={chevronButtonStyle}\n onMouseDown={e => {\n e.preventDefault();\n }}\n onClick={handleToggleClick}\n disabled={disabled}\n >\n <span className={chevronIconRecipe({ open: isOpen })}>\n <ChevronDownIcon size={chevronSize} decorative />\n </span>\n </button>\n </div>\n\n {name && <input type=\"hidden\" name={name} value={formValue} />}\n\n {isOpen &&\n createPortal(\n <div\n ref={dropdownRef}\n role=\"listbox\"\n id={listboxId}\n aria-labelledby={label ? labelId : undefined}\n className={dropdownStyle}\n style={dropdownPos}\n tabIndex={-1}\n >\n <ScrollArea\n className={optionsListStyle}\n maxHeight={maxDropdownHeight}\n scrollbarWidth={4}\n scrollbarVisibility=\"auto\"\n hideDelay={600}\n >\n {renderOptions()}\n </ScrollArea>\n </div>,\n document.body\n )}\n\n {showHelperText && (\n <FormHelperText id={helperId} error={error}>\n {showHelperText}\n </FormHelperText>\n )}\n </div>\n );\n}\n\nCombobox.displayName = 'Combobox';\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;AAkDA;AACE;AACA;AACA;;AAGF;AAOA;AAIE;AACA;AACF;AAEA;AAKE;AAAY;;AAGZ;AACE;;;AAGE;;;;;;;;AAMA;;;;;AAIJ;AACA;AACF;AAEA;;;;;;;;;;;;;;;;;;;AAmBG;AACG;AAqCJ;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;;AAIA;;;;AAIE;AACD;AAED;;AAEsB;AAClB;AACA;AACF;AAIF;;;;;;;;AAQA;;;;AAKE;AACE;;;;AAKJ;;;;AAKA;;;;;;;;;;AAmBA;AACE;AAAoB;AACpB;;AAEF;;;AAKE;AACA;;;;;;AAOF;AACE;;;AAGI;AACA;AACD;;AAEH;;AAGF;;;AAEE;;;AAEA;;AAGF;AACE;;;AAEA;AACF;AAEA;;;AAGI;;AAEE;;;;;;AAGA;;;;AAMF;;;AAMF;;AAEA;AACA;AACD;AAED;AACE;AACA;;AACA;;AAEA;AAGA;;AAGI;AAEJ;;AAEE;AACA;AACE;;AAEI;AACD;AACH;AACH;AACH;;AAGE;;AACA;AACF;;;AAIE;;AACA;AACE;;;AAKE;;AAEJ;AACA;AACA;AACE;AACF;AACF;AAEA;AAEI;;;AAGA;AACA;AAAa;;;;;AAKf;AAIF;;;AAII;;;AAGI;;;;AAKJ;;AAEE;;AAEE;AACE;;;;;;;;;AAMF;;;;;;AAOF;;;;AAGE;;;;AAKJ;AACF;;;;;;;;;;;;;AAcC;AAGH;;AAEE;AAAiB;AACnB;AAEA;;;;;AAKI;;;AAIJ;;;;;;AAOI;AACF;AAIF;;;AAEE;AAAY;;AACP;AACL;AACF;AAEA;AACA;;;AAII;;AAMF;AACE;;;AAGA;;AAEA;AACA;AACA;AAUQ;AACA;AACA;;;AAMJ;AAEE;;;;AAIF;AAEE;AAAiB;AACnB;;AAMI;AAYV;AACF;AAEA;AACA;;;;;;;AAuBO;;AAaK;;;AAsDN;AA8BR;AAEA;;"}
@@ -1 +1 @@
1
- {"version":3,"file":"CurveCanvas.js","sources":["../../../../../../src/components/controls/CurveEditor/CurveCanvas.tsx"],"sourcesContent":[null],"names":[],"mappings":";;;;;;;;AAqFE;;;;AAKE;;AAEA;;AAEA;AAEA;AACA;;AAEA;AACF;;AAGA;;;;;;;;;;;;;;;;;;;AAmBC;;AAGD;;;AAGA;AAEI;;;AAIJ;AACA;AAEI;;AAGJ;;;AAOK;;;;;AASC;AAgBR;AAEA;;"}
1
+ {"version":3,"file":"CurveCanvas.js","sources":["src/components/controls/CurveEditor/CurveCanvas.tsx"],"sourcesContent":["'use client';\n\nimport { assignInlineVars } from '@vanilla-extract/dynamic';\nimport React, { useRef, useEffect, useState } from 'react';\n\nimport {\n canvasContainerRecipe,\n canvasRecipe,\n ariaLiveRegionStyle,\n canvasHeightVar,\n} from './CurveEditor.css';\nimport { useCurveRenderer } from './useCurveRenderer';\n\nimport type {\n CurveData,\n CurveViewport,\n CurveHitTest,\n CurveEditorSize,\n CurveBackgroundInfo,\n} from './CurveEditor.types';\n\ninterface CurveCanvasProps {\n canvasRef: React.RefObject<HTMLCanvasElement | null>;\n curve: CurveData;\n viewport: CurveViewport;\n showGrid: boolean;\n gridSubdivisions: number;\n showAxisLabels: boolean;\n labelX?: string;\n labelY?: string;\n curveColor: string;\n curveWidth: number;\n renderBackground?: (\n ctx: CanvasRenderingContext2D,\n info: CurveBackgroundInfo\n ) => void;\n selectedIds: Set<string>;\n hoveredElement: CurveHitTest | null;\n selectionBox: {\n x1: number;\n y1: number;\n x2: number;\n y2: number;\n } | null;\n isDragging: boolean;\n lockTangents: boolean;\n height: number;\n responsive: boolean;\n disabled: boolean;\n size: CurveEditorSize;\n handlers: {\n onPointerDown: (e: React.PointerEvent) => void;\n onPointerMove: (e: React.PointerEvent) => void;\n onPointerUp: (e: React.PointerEvent) => void;\n onDoubleClick: (e: React.MouseEvent) => void;\n onKeyDown: (e: React.KeyboardEvent) => void;\n };\n ariaLabel?: string;\n testId?: string;\n}\n\nexport const CurveCanvas: React.FC<CurveCanvasProps> = ({\n canvasRef,\n curve,\n viewport,\n showGrid,\n gridSubdivisions,\n showAxisLabels,\n labelX,\n labelY,\n curveColor,\n curveWidth,\n renderBackground,\n selectedIds,\n hoveredElement,\n selectionBox,\n isDragging,\n lockTangents,\n height,\n responsive,\n disabled,\n handlers,\n ariaLabel,\n testId,\n}) => {\n const containerRef = useRef<HTMLDivElement>(null);\n const [resizeToken, setResizeToken] = useState(0);\n\n // ResizeObserver for responsive mode -- triggers canvas re-render\n useEffect(() => {\n if (!responsive || !containerRef.current) return;\n\n const observer = new ResizeObserver(() => {\n setResizeToken(t => t + 1);\n });\n\n observer.observe(containerRef.current);\n return () => {\n observer.disconnect();\n };\n }, [responsive]);\n\n // Renderer hook\n useCurveRenderer({\n canvasRef,\n curve,\n viewport,\n showGrid,\n gridSubdivisions,\n showAxisLabels,\n labelX,\n labelY,\n curveColor,\n curveWidth,\n renderBackground,\n selectedIds,\n hoveredElement,\n selectionBox,\n disabled,\n isDragging,\n lockTangents,\n resizeToken,\n });\n\n // Build screen reader summary\n const kfCount = curve.keyframes.length;\n const firstKf = curve.keyframes[0];\n const lastKf = curve.keyframes[kfCount - 1];\n const srSummary =\n firstKf && lastKf\n ? `Curve from (${firstKf.x}, ${firstKf.y}) to (${lastKf.x}, ${lastKf.y}) with ${kfCount} keyframes`\n : `Empty curve`;\n\n // Selection announcement\n const selectedCount = selectedIds.size;\n const selectionAnnouncement =\n selectedCount > 0\n ? `${selectedCount} keyframe${selectedCount > 1 ? 's' : ''} selected`\n : '';\n\n return (\n <div\n ref={containerRef}\n className={canvasContainerRecipe({ responsive })}\n style={{\n ...assignInlineVars({ [canvasHeightVar]: `${height}px` }),\n minHeight: `${height}px`,\n }}\n >\n <canvas\n ref={canvasRef}\n className={canvasRecipe({ disabled })}\n // The canvas is a self-contained keyboard-driven editing surface, so\n // \"application\" is the correct ARIA role. jsx-a11y maps <canvas> to an\n // interactive element and flags the (non-interactive) application role.\n // eslint-disable-next-line jsx-a11y/no-interactive-element-to-noninteractive-role\n role=\"application\"\n aria-label={ariaLabel ?? 'Curve editor'}\n aria-roledescription={`curve editor with ${kfCount} keyframes`}\n tabIndex={disabled ? -1 : 0}\n data-testid={testId ? `${testId}-canvas` : undefined}\n onPointerDown={handlers.onPointerDown}\n onPointerMove={handlers.onPointerMove}\n onPointerUp={handlers.onPointerUp}\n onDoubleClick={handlers.onDoubleClick}\n onKeyDown={handlers.onKeyDown}\n />\n <div className={ariaLiveRegionStyle} aria-live=\"polite\" role=\"status\">\n {srSummary}. {selectionAnnouncement}\n </div>\n </div>\n );\n};\n\nCurveCanvas.displayName = 'CurveCanvas';\n"],"names":[],"mappings":";;;;;;;;AAqFE;;;;AAKE;;AAEA;;AAEA;AAEA;AACA;;AAEA;AACF;;AAGA;;;;;;;;;;;;;;;;;;;AAmBC;;AAGD;;;AAGA;AAEI;;;AAIJ;AACA;AAEI;;AAGJ;;;AAOK;;;;;AASC;AAgBR;AAEA;;"}
@@ -1 +1 @@
1
- {"version":3,"file":"CurveEditor.js","sources":["../../../../../../src/components/controls/CurveEditor/CurveEditor.tsx"],"sourcesContent":[null],"names":[],"mappings":";;;;;;;;;;;;;AA0BO;;;;;AA0CL;;;AAIE;;AAEE;AACA;AACE;AACA;AACA;;AAEJ;AACF;AAEA;AACA;;AAEE;;AAGF;;;;AAKI;AACF;AAIF;AAEI;AACF;;AAKF;AACE;AACA;AACA;;;;;;;;AAQE;AACA;AACA;;AAEJ;;AAGA;;;;;;;;;;;;;;;;;;AAsBE;AACA;;AAED;;AAGD;AACE;AAAwC;;AAGxC;AACE;AACE;AACE;;AACK;;;;;AAKX;;AAGF;AAEI;;;;;;AAMF;AAIF;AAIM;;AAEE;;AAEH;;;;;;;AAuDI;AAKX;AAEA;;"}
1
+ {"version":3,"file":"CurveEditor.js","sources":["src/components/controls/CurveEditor/CurveEditor.tsx"],"sourcesContent":["'use client';\n\nimport { assignInlineVars } from '@vanilla-extract/dynamic';\nimport React, { useState, useCallback, useMemo, useRef } from 'react';\n\nimport { vars } from '@/theme/contract.css';\nimport { cx } from '@/utils/cx';\n\nimport { CurveCanvas } from './CurveCanvas';\nimport {\n curveEditorRecipe,\n editorWidthVar,\n bottomBarStyle,\n} from './CurveEditor.css';\nimport { CURVE_PRESETS } from './curvePresets';\nimport { CurveToolbar } from './CurveToolbar';\nimport { ensureKeyframeIds, evaluateCurve } from './curveUtils';\nimport { useCurveInteraction } from './useCurveInteraction';\n\nimport type {\n CurveEditorProps,\n CurveData,\n CurveViewport,\n TangentMode,\n} from './CurveEditor.types';\n\nexport const CurveEditor: React.FC<CurveEditorProps> = ({\n value,\n defaultValue,\n width = 320,\n height = 200,\n responsive = false,\n showToolbar = true,\n showGrid = true,\n gridSubdivisions = 4,\n showAxisLabels = true,\n allowAdd = true,\n allowDelete = true,\n maxKeyframes = Infinity,\n lockEndpoints = true,\n minKeyframeDistance = 0.001,\n clampY = true,\n snapToGrid = false,\n precision = 3,\n labelX,\n labelY,\n presets: userPresets,\n size = 'md',\n disabled = false,\n readOnly = false,\n curveColor,\n curveWidth = 2,\n renderBackground,\n renderBottomBar,\n lockTangents = false,\n onChange,\n onChangeComplete,\n onSelectionChange,\n className,\n testId,\n id,\n style,\n ...rest\n}) => {\n // For curveColor fallback, use the CSS variable reference directly.\n // At runtime, getComputedStyle on the canvas element will resolve it.\n // We pass the raw CSS var reference string which will be resolved by the canvas renderer.\n const effectiveCurveColor = curveColor ?? vars.colors.accent.primary;\n const canvasRef = useRef<HTMLCanvasElement>(null);\n\n // Controlled / uncontrolled -- defaults to ease-in-out\n const [internalValue, setInternalValue] = useState<CurveData>(() => {\n const easeInOut = CURVE_PRESETS.find(p => p.id === 'ease-in-out');\n const initial = defaultValue ??\n easeInOut?.curve ??\n CURVE_PRESETS[0]?.curve ?? {\n keyframes: [],\n domainX: [0, 1] as [number, number],\n domainY: [0, 1] as [number, number],\n };\n return { ...initial, keyframes: ensureKeyframeIds(initial.keyframes) };\n });\n\n const isControlled = value !== undefined;\n const curve = useMemo(() => {\n const c = isControlled ? value : internalValue;\n return { ...c, keyframes: ensureKeyframeIds(c.keyframes) };\n }, [isControlled, value, internalValue]);\n\n const handleChange = useCallback(\n (newCurve: CurveData) => {\n if (!isControlled) {\n setInternalValue(newCurve);\n }\n onChange?.(newCurve);\n },\n [isControlled, onChange]\n );\n\n const handleChangeComplete = useCallback(\n (newCurve: CurveData) => {\n onChangeComplete?.(newCurve);\n },\n [onChangeComplete]\n );\n\n // Viewport -- always matches domain with padding for labels (no user zoom/pan)\n const viewport = useMemo((): CurveViewport => {\n const axisName = labelX ?? labelY;\n const hasAxisNames = Boolean(axisName);\n const padding = hasAxisNames && showAxisLabels ? 0.12 : 0.08;\n const [dxMin, dxMax] = curve.domainX;\n const [dyMin, dyMax] = curve.domainY;\n const xPad = (dxMax - dxMin) * padding;\n const yPad = (dyMax - dyMin) * padding;\n return {\n viewX: [dxMin - xPad, dxMax + xPad] as [number, number],\n viewY: [dyMin - yPad, dyMax + yPad] as [number, number],\n zoom: 1,\n panX: 0,\n panY: 0,\n };\n }, [curve.domainX, curve.domainY, labelX, labelY, showAxisLabels]);\n\n // Merge presets\n const allPresets = useMemo(\n () => (userPresets ? [...CURVE_PRESETS, ...userPresets] : CURVE_PRESETS),\n [userPresets]\n );\n\n // Interaction hook\n const interaction = useCurveInteraction({\n curve,\n viewport,\n canvasRef,\n disabled,\n readOnly,\n allowAdd,\n allowDelete,\n maxKeyframes,\n lockEndpoints,\n lockTangents,\n clampY,\n snapToGrid,\n gridSubdivisions,\n minKeyframeDistance,\n precision,\n onChange: handleChange,\n onChangeComplete: handleChangeComplete,\n onSelectionChange,\n });\n\n // Compute tangent mode for selected keyframes (null if mixed or none selected)\n const selectedTangentMode = useMemo((): TangentMode | null => {\n if (interaction.selectedIds.size === 0) return null;\n\n let mode: TangentMode | null = null;\n for (const kf of curve.keyframes) {\n if (kf.id && interaction.selectedIds.has(kf.id)) {\n if (mode === null) {\n mode = kf.tangentMode;\n } else if (mode !== kf.tangentMode) {\n return null; // Mixed modes\n }\n }\n }\n return mode;\n }, [curve.keyframes, interaction.selectedIds]);\n\n const handlePresetSelect = useCallback(\n (preset: { curve: CurveData }) => {\n const newCurve = {\n ...preset.curve,\n keyframes: ensureKeyframeIds(preset.curve.keyframes),\n };\n handleChange(newCurve);\n handleChangeComplete(newCurve);\n },\n [handleChange, handleChangeComplete]\n );\n\n return (\n <div\n className={cx(curveEditorRecipe({ disabled, responsive }), className)}\n style={{\n ...style,\n ...(!responsive\n ? assignInlineVars({ [editorWidthVar]: `${width}px` })\n : {}),\n }}\n data-testid={testId}\n id={id}\n {...rest}\n >\n {showToolbar && (\n <CurveToolbar\n selectedTangentMode={selectedTangentMode}\n onTangentModeChange={interaction.setTangentMode}\n onPresetSelect={handlePresetSelect}\n presets={allPresets}\n disabled={disabled}\n lockTangents={lockTangents}\n size={size}\n testId={testId}\n />\n )}\n <CurveCanvas\n canvasRef={canvasRef}\n curve={curve}\n viewport={viewport}\n showGrid={showGrid}\n gridSubdivisions={gridSubdivisions}\n showAxisLabels={showAxisLabels}\n labelX={labelX}\n labelY={labelY}\n curveColor={effectiveCurveColor}\n curveWidth={curveWidth}\n renderBackground={renderBackground}\n selectedIds={interaction.selectedIds}\n hoveredElement={interaction.hoveredElement}\n selectionBox={interaction.selectionBox}\n isDragging={interaction.isDragging}\n lockTangents={lockTangents}\n height={height}\n responsive={responsive}\n disabled={disabled}\n size={size}\n handlers={interaction.handlers}\n testId={testId}\n />\n {renderBottomBar && (\n <div\n className={bottomBarStyle}\n data-testid={testId ? `${testId}-bottom-bar` : undefined}\n >\n {renderBottomBar({\n curve,\n selectedIds: Array.from(interaction.selectedIds),\n selectedKeyframes: curve.keyframes.filter(\n kf => kf.id && interaction.selectedIds.has(kf.id)\n ),\n evaluate: (x: number) => evaluateCurve(curve, x),\n disabled,\n readOnly,\n })}\n </div>\n )}\n </div>\n );\n};\n\nCurveEditor.displayName = 'CurveEditor';\n"],"names":[],"mappings":";;;;;;;;;;;;;AA0BO;;;;;AA0CL;;;AAIE;;AAEE;AACA;AACE;AACA;AACA;;AAEJ;AACF;AAEA;AACA;;AAEE;;AAGF;;;;AAKI;AACF;AAIF;AAEI;AACF;;AAKF;AACE;AACA;AACA;;;;;;;;AAQE;AACA;AACA;;AAEJ;;AAGA;;;;;;;;;;;;;;;;;;AAsBE;AACA;;AAED;;AAGD;AACE;AAAwC;;AAGxC;AACE;AACE;AACE;;AACK;;;;;AAKX;;AAGF;AAEI;;;;;;AAMF;AAIF;AAIM;;AAEE;;AAEH;;;;;;;AAuDI;AAKX;AAEA;;"}
@@ -1 +1 @@
1
- {"version":3,"file":"CurveToolbar.js","sources":["../../../../../../src/components/controls/CurveEditor/CurveToolbar.tsx"],"sourcesContent":[null],"names":[],"mappings":";;;;;;;;;;;;;;AAuCA;AACE;AACA;AACA;;AAGF;AAME;AACE;AACA;AACA;AACA;AACD;AACD;AACE;AACA;AACA;AACA;AACD;AACD;AACE;AACA;AACA;AACA;AACD;AACD;AACE;AACA;AACA;AACA;AACD;AACD;AACE;AACA;AACA;AACA;AACD;AACD;AACE;AACA;AACA;AACA;AACD;;;;;;AAgBA;AAED;AACE;;AACA;AACA;;AACF;AAEA;;;AAOK;;AAqCW;AAWlB;AAEA;;"}
1
+ {"version":3,"file":"CurveToolbar.js","sources":["src/components/controls/CurveEditor/CurveToolbar.tsx"],"sourcesContent":["'use client';\n\nimport { assignInlineVars } from '@vanilla-extract/dynamic';\nimport React from 'react';\n\nimport { Select } from '@/components/controls/Select';\nimport { TangentAlignedIcon } from '@/components/Icons/TangentAlignedIcon';\nimport { TangentAutoIcon } from '@/components/Icons/TangentAutoIcon';\nimport { TangentFreeIcon } from '@/components/Icons/TangentFreeIcon';\nimport { TangentLinearIcon } from '@/components/Icons/TangentLinearIcon';\nimport { TangentMirroredIcon } from '@/components/Icons/TangentMirroredIcon';\nimport { TangentStepIcon } from '@/components/Icons/TangentStepIcon';\nimport { IconButton } from '@/components/primitives/IconButton';\nimport { Tooltip } from '@/components/primitives/Tooltip';\n\nimport {\n toolbarStyle,\n toolbarSectionStyle,\n toolbarSeparatorStyle,\n toolbarHeightVar,\n} from './CurveEditor.css';\n\nimport type {\n CurveEditorSize,\n CurvePreset,\n TangentMode,\n} from './CurveEditor.types';\n\ninterface CurveToolbarProps {\n selectedTangentMode: TangentMode | null;\n onTangentModeChange: (mode: TangentMode) => void;\n onPresetSelect: (preset: CurvePreset) => void;\n presets: CurvePreset[];\n disabled: boolean;\n lockTangents: boolean;\n size: CurveEditorSize;\n testId?: string;\n}\n\nconst TOOLBAR_HEIGHT: Record<CurveEditorSize, number> = {\n sm: 28,\n md: 32,\n lg: 36,\n};\n\nconst TANGENT_MODES: Array<{\n mode: TangentMode;\n label: string;\n icon: React.ReactNode;\n shortcut: string;\n}> = [\n {\n mode: 'free',\n label: 'Free',\n icon: <TangentFreeIcon decorative />,\n shortcut: '1',\n },\n {\n mode: 'aligned',\n label: 'Aligned',\n icon: <TangentAlignedIcon decorative />,\n shortcut: '2',\n },\n {\n mode: 'mirrored',\n label: 'Mirrored',\n icon: <TangentMirroredIcon decorative />,\n shortcut: '3',\n },\n {\n mode: 'auto',\n label: 'Auto',\n icon: <TangentAutoIcon decorative />,\n shortcut: '4',\n },\n {\n mode: 'linear',\n label: 'Linear',\n icon: <TangentLinearIcon decorative />,\n shortcut: '5',\n },\n {\n mode: 'step',\n label: 'Step',\n icon: <TangentStepIcon decorative />,\n shortcut: '6',\n },\n];\n\nexport const CurveToolbar: React.FC<CurveToolbarProps> = ({\n selectedTangentMode,\n onTangentModeChange,\n onPresetSelect,\n presets,\n disabled,\n lockTangents,\n size,\n testId,\n}) => {\n const presetOptions = presets.map(p => ({\n value: p.id,\n label: p.label,\n }));\n\n const handlePresetChange = (id: string | null) => {\n if (!id) return;\n const preset = presets.find(p => p.id === id);\n if (preset) onPresetSelect(preset);\n };\n\n const iconSize = size === 'lg' ? 'md' : 'sm';\n\n return (\n <div\n className={toolbarStyle}\n style={assignInlineVars({\n [toolbarHeightVar]: `${TOOLBAR_HEIGHT[size]}px`,\n })}\n data-testid={testId ? `${testId}-toolbar` : undefined}\n >\n {/* Preset selector */}\n <div className={toolbarSectionStyle}>\n <Select\n options={presetOptions}\n onChange={handlePresetChange}\n size={size === 'lg' ? 'md' : 'sm'}\n variant=\"ghost\"\n disabled={disabled}\n placeholder=\"Preset...\"\n aria-label=\"Curve preset\"\n minDropdownWidth={180}\n />\n </div>\n\n {!lockTangents && (\n <>\n <div className={toolbarSeparatorStyle} />\n\n {/* Tangent mode buttons */}\n <div className={toolbarSectionStyle}>\n {TANGENT_MODES.map(({ mode, label, icon, shortcut }) => (\n <Tooltip\n key={mode}\n title={`${label} (${shortcut})`}\n placement=\"bottom\"\n >\n <IconButton\n size={iconSize}\n variant=\"ghost\"\n pressed={selectedTangentMode === mode}\n disabled={disabled || selectedTangentMode === null}\n aria-label={`${label} tangent mode`}\n onClick={() => {\n onTangentModeChange(mode);\n }}\n >\n {icon}\n </IconButton>\n </Tooltip>\n ))}\n </div>\n </>\n )}\n </div>\n );\n};\n\nCurveToolbar.displayName = 'CurveToolbar';\n"],"names":[],"mappings":";;;;;;;;;;;;;;AAuCA;AACE;AACA;AACA;;AAGF;AAME;AACE;AACA;AACA;AACA;AACD;AACD;AACE;AACA;AACA;AACA;AACD;AACD;AACE;AACA;AACA;AACA;AACD;AACD;AACE;AACA;AACA;AACA;AACD;AACD;AACE;AACA;AACA;AACA;AACD;AACD;AACE;AACA;AACA;AACA;AACD;;;;;;AAgBA;AAED;AACE;;AACA;AACA;;AACF;AAEA;;;AAOK;;AAqCW;AAWlB;AAEA;;"}
@@ -1 +1 @@
1
- {"version":3,"file":"curvePresets.js","sources":["../../../../../../src/components/controls/CurveEditor/curvePresets.ts"],"sourcesContent":[null],"names":[],"mappings":";;AAIA,SAAS,EAAE,CACT,CAAS,EACT,CAAS,EACT,IAAY,EACZ,IAAY,EACZ,KAAa,EACb,KAAa,EACb,cAA4C,MAAM,EAAA;IAElD,OAAO;QACL,CAAC;QACD,CAAC;QACD,QAAQ,EAAE,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE;QAC9B,SAAS,EAAE,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE;QACjC,WAAW;QACX,EAAE,EAAE,kBAAkB,EAAE;KACzB;AACH;AAEO,MAAM,aAAa,GAAkB;;AAE1C,IAAA;AACE,QAAA,EAAE,EAAE,QAAQ;AACZ,QAAA,KAAK,EAAE,QAAQ;AACf,QAAA,QAAQ,EAAE,OAAO;AACjB,QAAA,KAAK,EAAE;AACL,YAAA,SAAS,EAAE;AACT,gBAAA,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,CAAC;AACtC,gBAAA,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,EAAE,QAAQ,CAAC;AACzC,aAAA;AACD,YAAA,OAAO,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;AACf,YAAA,OAAO,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;AAChB,SAAA;AACF,KAAA;AACD,IAAA;AACE,QAAA,EAAE,EAAE,UAAU;AACd,QAAA,KAAK,EAAE,UAAU;AACjB,QAAA,QAAQ,EAAE,OAAO;AACjB,QAAA,KAAK,EAAE;AACL,YAAA,SAAS,EAAE;AACT,gBAAA,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,CAAC;AAClC,gBAAA,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,QAAQ,CAAC;AACpC,aAAA;AACD,YAAA,OAAO,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;AACf,YAAA,OAAO,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;AAChB,SAAA;AACF,KAAA;;AAGD,IAAA;AACE,QAAA,EAAE,EAAE,SAAS;AACb,QAAA,KAAK,EAAE,SAAS;AAChB,QAAA,QAAQ,EAAE,MAAM;AAChB,QAAA,KAAK,EAAE;AACL,YAAA,SAAS,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;AACpE,YAAA,OAAO,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;AACf,YAAA,OAAO,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;AAChB,SAAA;AACF,KAAA;AACD,IAAA;AACE,QAAA,EAAE,EAAE,UAAU;AACd,QAAA,KAAK,EAAE,UAAU;AACjB,QAAA,QAAQ,EAAE,MAAM;AAChB,QAAA,KAAK,EAAE;AACL,YAAA,SAAS,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;AACnE,YAAA,OAAO,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;AACf,YAAA,OAAO,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;AAChB,SAAA;AACF,KAAA;AACD,IAAA;AACE,QAAA,EAAE,EAAE,aAAa;AACjB,QAAA,KAAK,EAAE,aAAa;AACpB,QAAA,QAAQ,EAAE,MAAM;AAChB,QAAA,KAAK,EAAE;AACL,YAAA,SAAS,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;AAC9D,YAAA,OAAO,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;AACf,YAAA,OAAO,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;AAChB,SAAA;AACF,KAAA;;AAGD,IAAA;AACE,QAAA,EAAE,EAAE,eAAe;AACnB,QAAA,KAAK,EAAE,iBAAiB;AACxB,QAAA,QAAQ,EAAE,UAAU;AACpB,QAAA,KAAK,EAAE;AACL,YAAA,SAAS,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;AAClE,YAAA,OAAO,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;AACf,YAAA,OAAO,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;AAChB,SAAA;AACF,KAAA;AACD,IAAA;AACE,QAAA,EAAE,EAAE,gBAAgB;AACpB,QAAA,KAAK,EAAE,kBAAkB;AACzB,QAAA,QAAQ,EAAE,UAAU;AACpB,QAAA,KAAK,EAAE;AACL,YAAA,SAAS,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;AACjE,YAAA,OAAO,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;AACf,YAAA,OAAO,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;AAChB,SAAA;AACF,KAAA;AACD,IAAA;AACE,QAAA,EAAE,EAAE,QAAQ;AACZ,QAAA,KAAK,EAAE,QAAQ;AACf,QAAA,QAAQ,EAAE,UAAU;AACpB,QAAA,KAAK,EAAE;AACL,YAAA,SAAS,EAAE;AACT,gBAAA,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC;AAC1B,gBAAA,EAAE,CAAC,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,CAAC;AACvC,gBAAA,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,CAAC;AACvC,gBAAA,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;AACzB,aAAA;AACD,YAAA,OAAO,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;AACf,YAAA,OAAO,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;AAChB,SAAA;AACF,KAAA;AACD,IAAA;AACE,QAAA,EAAE,EAAE,WAAW;AACf,QAAA,KAAK,EAAE,WAAW;AAClB,QAAA,QAAQ,EAAE,UAAU;AACpB,QAAA,KAAK,EAAE;AACL,YAAA,SAAS,EAAE;AACT,gBAAA,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,GAAG,EAAE,GAAG,CAAC;AACxB,gBAAA,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,KAAK,CAAC;AACpC,gBAAA,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;AACxB,aAAA;AACD,YAAA,OAAO,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;AACf,YAAA,OAAO,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;AAChB,SAAA;AACF,KAAA;;AAGD,IAAA;AACE,QAAA,EAAE,EAAE,MAAM;AACV,QAAA,KAAK,EAAE,MAAM;AACb,QAAA,QAAQ,EAAE,SAAS;AACnB,QAAA,KAAK,EAAE;AACL,YAAA,SAAS,EAAE;AACT,gBAAA,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC;AAC5B,gBAAA,EAAE,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC;AAC9B,gBAAA,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,QAAQ,CAAC;AAC/B,aAAA;AACD,YAAA,OAAO,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;AACf,YAAA,OAAO,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;AAChB,SAAA;AACF,KAAA;AACD,IAAA;AACE,QAAA,EAAE,EAAE,SAAS;AACb,QAAA,KAAK,EAAE,SAAS;AAChB,QAAA,QAAQ,EAAE,SAAS;AACnB,QAAA,KAAK,EAAE;AACL,YAAA,SAAS,EAAE;AACT,gBAAA,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC;AACvC,gBAAA,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,EAAE,QAAQ,CAAC;AACxC,aAAA;AACD,YAAA,OAAO,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;AACf,YAAA,OAAO,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;AAChB,SAAA;AACF,KAAA;AACD,IAAA;AACE,QAAA,EAAE,EAAE,MAAM;AACV,QAAA,KAAK,EAAE,YAAY;AACnB,QAAA,QAAQ,EAAE,SAAS;AACnB,QAAA,KAAK,EAAE;AACL,YAAA,SAAS,EAAE;AACT,gBAAA,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC;AACtB,gBAAA,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,CAAC;AACnC,gBAAA,EAAE,CAAC,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,UAAU,CAAC;AAChD,gBAAA,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,CAAC;AACpC,gBAAA,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;AACxB,aAAA;AACD,YAAA,OAAO,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;AACf,YAAA,OAAO,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;AAChB,SAAA;AACF,KAAA;AACD,IAAA;AACE,QAAA,EAAE,EAAE,UAAU;AACd,QAAA,KAAK,EAAE,UAAU;AACjB,QAAA,QAAQ,EAAE,SAAS;AACnB,QAAA,KAAK,EAAE;AACL,YAAA,SAAS,EAAE;AACT,gBAAA,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,QAAQ,CAAC;AACnC,gBAAA,EAAE,CAAC,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,EAAE,QAAQ,CAAC;AACvC,gBAAA,EAAE,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,QAAQ,CAAC;AACrC,gBAAA,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,EAAE,QAAQ,CAAC;AACtC,aAAA;AACD,YAAA,OAAO,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;AACf,YAAA,OAAO,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;AAChB,SAAA;AACF,KAAA;;;;;"}
1
+ {"version":3,"file":"curvePresets.js","sources":["src/components/controls/CurveEditor/curvePresets.ts"],"sourcesContent":["import { generateKeyframeId } from './curveUtils';\n\nimport type { CurvePreset, CurveKeyframe } from './CurveEditor.types';\n\nfunction kf(\n x: number,\n y: number,\n hInX: number,\n hInY: number,\n hOutX: number,\n hOutY: number,\n tangentMode: CurveKeyframe['tangentMode'] = 'free'\n): CurveKeyframe {\n return {\n x,\n y,\n handleIn: { x: hInX, y: hInY },\n handleOut: { x: hOutX, y: hOutY },\n tangentMode,\n id: generateKeyframeId(),\n };\n}\n\nexport const CURVE_PRESETS: CurvePreset[] = [\n // ─── Basic ───\n {\n id: 'linear',\n label: 'Linear',\n category: 'Basic',\n curve: {\n keyframes: [\n kf(0, 0, 0, 0, 0.333, 0.333, 'linear'),\n kf(1, 1, -0.333, -0.333, 0, 0, 'linear'),\n ],\n domainX: [0, 1],\n domainY: [0, 1],\n },\n },\n {\n id: 'constant',\n label: 'Constant',\n category: 'Basic',\n curve: {\n keyframes: [\n kf(0, 1, 0, 0, 0.333, 0, 'linear'),\n kf(1, 1, -0.333, 0, 0, 0, 'linear'),\n ],\n domainX: [0, 1],\n domainY: [0, 1],\n },\n },\n\n // ─── Ease ───\n {\n id: 'ease-in',\n label: 'Ease In',\n category: 'Ease',\n curve: {\n keyframes: [kf(0, 0, 0, 0, 0.42, 0), kf(1, 1, -0.333, -0.333, 0, 0)],\n domainX: [0, 1],\n domainY: [0, 1],\n },\n },\n {\n id: 'ease-out',\n label: 'Ease Out',\n category: 'Ease',\n curve: {\n keyframes: [kf(0, 0, 0, 0, 0.333, 0.333), kf(1, 1, -0.58, 0, 0, 0)],\n domainX: [0, 1],\n domainY: [0, 1],\n },\n },\n {\n id: 'ease-in-out',\n label: 'Ease In-Out',\n category: 'Ease',\n curve: {\n keyframes: [kf(0, 0, 0, 0, 0.42, 0), kf(1, 1, -0.58, 0, 0, 0)],\n domainX: [0, 1],\n domainY: [0, 1],\n },\n },\n\n // ─── Dramatic ───\n {\n id: 'ease-in-cubic',\n label: 'Ease In (Cubic)',\n category: 'Dramatic',\n curve: {\n keyframes: [kf(0, 0, 0, 0, 0.55, 0), kf(1, 1, -0.2, -0.333, 0, 0)],\n domainX: [0, 1],\n domainY: [0, 1],\n },\n },\n {\n id: 'ease-out-cubic',\n label: 'Ease Out (Cubic)',\n category: 'Dramatic',\n curve: {\n keyframes: [kf(0, 0, 0, 0, 0.2, 0.333), kf(1, 1, -0.55, 0, 0, 0)],\n domainX: [0, 1],\n domainY: [0, 1],\n },\n },\n {\n id: 'bounce',\n label: 'Bounce',\n category: 'Dramatic',\n curve: {\n keyframes: [\n kf(0, 0, 0, 0, 0.12, 0.33),\n kf(0.5, 1.15, -0.12, 0.05, 0.12, -0.05),\n kf(0.75, 0.9, -0.08, -0.05, 0.08, 0.05),\n kf(1, 1, -0.08, 0, 0, 0),\n ],\n domainX: [0, 1],\n domainY: [0, 1],\n },\n },\n {\n id: 'overshoot',\n label: 'Overshoot',\n category: 'Dramatic',\n curve: {\n keyframes: [\n kf(0, 0, 0, 0, 0.2, 0.5),\n kf(0.7, 1.2, -0.15, 0.1, 0.1, -0.05),\n kf(1, 1, -0.1, 0, 0, 0),\n ],\n domainX: [0, 1],\n domainY: [0, 1],\n },\n },\n\n // ─── Utility ───\n {\n id: 'step',\n label: 'Step',\n category: 'Utility',\n curve: {\n keyframes: [\n kf(0, 0, 0, 0, 0, 0, 'step'),\n kf(0.5, 1, 0, 0, 0, 0, 'step'),\n kf(1, 1, 0, 0, 0, 0, 'linear'),\n ],\n domainX: [0, 1],\n domainY: [0, 1],\n },\n },\n {\n id: 'inverse',\n label: 'Inverse',\n category: 'Utility',\n curve: {\n keyframes: [\n kf(0, 1, 0, 0, 0.333, -0.333, 'linear'),\n kf(1, 0, -0.333, 0.333, 0, 0, 'linear'),\n ],\n domainX: [0, 1],\n domainY: [0, 1],\n },\n },\n {\n id: 'bell',\n label: 'Bell Curve',\n category: 'Utility',\n curve: {\n keyframes: [\n kf(0, 0, 0, 0, 0.1, 0),\n kf(0.3, 0.8, -0.1, 0.1, 0.08, 0.08),\n kf(0.5, 1, -0.08, 0.02, 0.08, -0.02, 'mirrored'),\n kf(0.7, 0.8, -0.08, 0.08, 0.1, -0.1),\n kf(1, 0, -0.1, 0, 0, 0),\n ],\n domainX: [0, 1],\n domainY: [0, 1],\n },\n },\n {\n id: 'sawtooth',\n label: 'Sawtooth',\n category: 'Utility',\n curve: {\n keyframes: [\n kf(0, 0, 0, 0, 0.15, 0.5, 'linear'),\n kf(0.5, 1, -0.15, -0.5, 0, 0, 'linear'),\n kf(0.5, 0, 0, 0, 0.15, 0.5, 'linear'),\n kf(1, 1, -0.15, -0.5, 0, 0, 'linear'),\n ],\n domainX: [0, 1],\n domainY: [0, 1],\n },\n },\n];\n"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"curveUtils.js","sources":["../../../../../../src/components/controls/CurveEditor/curveUtils.ts"],"sourcesContent":[null],"names":["sharedDomainToCanvas","sharedCanvasToDomain"],"mappings":"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"}
1
+ {"version":3,"file":"curveUtils.js","sources":["src/components/controls/CurveEditor/curveUtils.ts"],"sourcesContent":["import {\n domainToCanvas as sharedDomainToCanvas,\n canvasToDomain as sharedCanvasToDomain,\n} from '@/components/primitives/canvas/canvasCoords';\nimport { clamp } from '@/utils/mathUtils';\n\nimport type {\n CurveData,\n CurveKeyframe,\n CurveViewport,\n CurveHitTest,\n} from './CurveEditor.types';\nimport type { CanvasViewport } from '@/components/primitives/canvas/canvas.types';\n\nlet idCounter = 0;\n\n/**\n * Generate a unique keyframe ID.\n */\nexport function generateKeyframeId(): string {\n idCounter += 1;\n return `kf-${Date.now()}-${idCounter}`;\n}\n\n/**\n * Evaluate a cubic bezier at parameter t in [0, 1].\n * B(t) = (1-t)^3*p0 + 3*(1-t)^2*t*p1 + 3*(1-t)*t^2*p2 + t^3*p3\n */\nexport function evaluateBezier(\n p0: number,\n p1: number,\n p2: number,\n p3: number,\n t: number\n): number {\n const mt = 1 - t;\n return (\n mt * mt * mt * p0 +\n 3 * mt * mt * t * p1 +\n 3 * mt * t * t * p2 +\n t * t * t * p3\n );\n}\n\n/**\n * Evaluate a 2D cubic bezier curve at parameter t.\n */\nexport function evaluateBezier2D(\n p0: { x: number; y: number },\n p1: { x: number; y: number },\n p2: { x: number; y: number },\n p3: { x: number; y: number },\n t: number\n): { x: number; y: number } {\n return {\n x: evaluateBezier(p0.x, p1.x, p2.x, p3.x, t),\n y: evaluateBezier(p0.y, p1.y, p2.y, p3.y, t),\n };\n}\n\n/**\n * Derivative of cubic bezier at parameter t.\n * B'(t) = 3*(1-t)^2*(p1-p0) + 6*(1-t)*t*(p2-p1) + 3*t^2*(p3-p2)\n */\nfunction bezierDerivative(\n p0: number,\n p1: number,\n p2: number,\n p3: number,\n t: number\n): number {\n const mt = 1 - t;\n return (\n 3 * mt * mt * (p1 - p0) + 6 * mt * t * (p2 - p1) + 3 * t * t * (p3 - p2)\n );\n}\n\n/**\n * Find the t parameter for a given X value on a bezier segment.\n * Uses Newton-Raphson iteration with bisection fallback.\n */\nexport function findTForX(\n x0: number,\n x1: number,\n x2: number,\n x3: number,\n targetX: number,\n tolerance: number = 1e-6,\n maxIterations: number = 20\n): number {\n // Initial guess using linear interpolation\n let t = (targetX - x0) / (x3 - x0 || 1);\n t = clamp(t, 0, 1);\n\n // Newton-Raphson iteration\n for (let i = 0; i < maxIterations; i++) {\n const x = evaluateBezier(x0, x1, x2, x3, t) - targetX;\n if (Math.abs(x) < tolerance) return t;\n\n const dx = bezierDerivative(x0, x1, x2, x3, t);\n if (Math.abs(dx) < 1e-10) break; // Derivative too small, fallback\n\n const newT = t - x / dx;\n t = clamp(newT, 0, 1);\n }\n\n // Bisection fallback for robustness\n let lo = 0;\n let hi = 1;\n t = 0.5;\n for (let i = 0; i < maxIterations; i++) {\n const x = evaluateBezier(x0, x1, x2, x3, t);\n if (Math.abs(x - targetX) < tolerance) return t;\n\n if (x < targetX) {\n lo = t;\n } else {\n hi = t;\n }\n t = (lo + hi) / 2;\n }\n\n return t;\n}\n\n/**\n * Clamp a tangent handle so its X component stays within the segment bounds.\n * Uses proportional scaling to preserve the tangent angle.\n *\n * @param handle Handle offset relative to its keyframe\n * @param maxAbsX Maximum allowed |handle.x| (typically the segment width)\n */\nfunction clampHandleToSegment(\n handle: { x: number; y: number },\n maxAbsX: number\n): { x: number; y: number } {\n const absX = Math.abs(handle.x);\n if (absX <= maxAbsX || absX < 1e-10) return handle;\n\n const scale = maxAbsX / absX;\n return { x: handle.x * scale, y: handle.y * scale };\n}\n\n/**\n * Get the bezier control points for a segment between two keyframes.\n * Handles are clamped so that p1.x and p2.x stay within [p0.x, p3.x],\n * preventing the bezier from folding back on itself (vertical spikes / loops).\n */\nfunction getSegmentControlPoints(\n kf0: CurveKeyframe,\n kf1: CurveKeyframe\n): {\n p0: { x: number; y: number };\n p1: { x: number; y: number };\n p2: { x: number; y: number };\n p3: { x: number; y: number };\n} {\n const segWidth = kf1.x - kf0.x;\n\n const hOut = clampHandleToSegment(kf0.handleOut, segWidth);\n const hIn = clampHandleToSegment(kf1.handleIn, segWidth);\n\n return {\n p0: { x: kf0.x, y: kf0.y },\n p1: { x: kf0.x + hOut.x, y: kf0.y + hOut.y },\n p2: { x: kf1.x + hIn.x, y: kf1.y + hIn.y },\n p3: { x: kf1.x, y: kf1.y },\n };\n}\n\n/**\n * Evaluate the entire curve at a given X position.\n * Finds the right segment, solves for t, returns Y.\n */\nexport function evaluateCurve(curve: CurveData, x: number): number {\n const { keyframes } = curve;\n if (keyframes.length === 0) return 0;\n\n const first = keyframes[0];\n const last = keyframes[keyframes.length - 1];\n if (!first || !last) return 0;\n\n if (keyframes.length === 1) return first.y;\n\n // Handle out-of-range X\n if (x <= first.x) return first.y;\n if (x >= last.x) return last.y;\n\n // Find the segment containing x\n for (let i = 0; i < keyframes.length - 1; i++) {\n const kf0 = keyframes[i];\n const kf1 = keyframes[i + 1];\n if (!kf0 || !kf1) continue;\n\n if (x >= kf0.x && x <= kf1.x) {\n // Handle step mode\n if (kf0.tangentMode === 'step') {\n return kf0.y;\n }\n\n // Handle linear mode (both keyframes)\n if (kf0.tangentMode === 'linear' && kf1.tangentMode === 'linear') {\n const segT = (x - kf0.x) / (kf1.x - kf0.x || 1);\n return kf0.y + segT * (kf1.y - kf0.y);\n }\n\n const { p0, p1, p2, p3 } = getSegmentControlPoints(kf0, kf1);\n const t = findTForX(p0.x, p1.x, p2.x, p3.x, x);\n return evaluateBezier(p0.y, p1.y, p2.y, p3.y, t);\n }\n }\n\n return last.y;\n}\n\n/**\n * Sample the curve at N evenly-spaced X points.\n * Returns array of {x, y} for rendering as a polyline.\n */\nexport function sampleCurve(\n curve: CurveData,\n numSamples: number\n): Array<{ x: number; y: number }> {\n const [xMin, xMax] = curve.domainX;\n const samples: Array<{ x: number; y: number }> = [];\n const step = (xMax - xMin) / (numSamples - 1 || 1);\n\n for (let i = 0; i < numSamples; i++) {\n const x = xMin + i * step;\n samples.push({ x, y: evaluateCurve(curve, x) });\n }\n\n return samples;\n}\n\n/**\n * Compute auto-tangent handles for a keyframe based on neighbors.\n * Catmull-Rom style: tangent direction = (next - prev) scaled by segment length.\n */\nexport function computeAutoTangent(\n prev: CurveKeyframe | null,\n current: CurveKeyframe,\n next: CurveKeyframe | null\n): { handleIn: { x: number; y: number }; handleOut: { x: number; y: number } } {\n const TANGENT_SCALE = 1 / 3;\n\n if (!prev && !next) {\n return { handleIn: { x: 0, y: 0 }, handleOut: { x: 0, y: 0 } };\n }\n\n if (!prev && next) {\n // First keyframe — use direction to next\n const dx = (next.x - current.x) * TANGENT_SCALE;\n const dy = (next.y - current.y) * TANGENT_SCALE;\n return {\n handleIn: { x: -dx, y: -dy },\n handleOut: { x: dx, y: dy },\n };\n }\n\n if (!next && prev) {\n // Last keyframe — use direction from prev\n const dx = (current.x - prev.x) * TANGENT_SCALE;\n const dy = (current.y - prev.y) * TANGENT_SCALE;\n return {\n handleIn: { x: -dx, y: -dy },\n handleOut: { x: dx, y: dy },\n };\n }\n\n if (!prev || !next) {\n return { handleIn: { x: 0, y: 0 }, handleOut: { x: 0, y: 0 } };\n }\n\n // Middle keyframe — Catmull-Rom tangent with per-segment scaling.\n // The tangent direction comes from (next - prev), but each handle is\n // scaled proportionally to its own segment length so that handles\n // never overshoot into neighboring segments.\n const totalDx = next.x - prev.x;\n const totalDy = next.y - prev.y;\n\n const segIn = current.x - prev.x;\n const segOut = next.x - current.x;\n const totalSeg = segIn + segOut || 1;\n\n return {\n handleIn: {\n x: -(segIn / totalSeg) * totalDx * TANGENT_SCALE,\n y: -(segIn / totalSeg) * totalDy * TANGENT_SCALE,\n },\n handleOut: {\n x: (segOut / totalSeg) * totalDx * TANGENT_SCALE,\n y: (segOut / totalSeg) * totalDy * TANGENT_SCALE,\n },\n };\n}\n\n/**\n * Apply tangent mode constraints to a keyframe after a handle was moved.\n * - aligned: project opposite handle to co-linear with moved handle\n * - mirrored: mirror moved handle to opposite\n */\nexport function constrainTangent(\n keyframe: CurveKeyframe,\n movedHandle: 'in' | 'out'\n): CurveKeyframe {\n const { tangentMode } = keyframe;\n\n if (\n tangentMode === 'free' ||\n tangentMode === 'auto' ||\n tangentMode === 'linear' ||\n tangentMode === 'step'\n ) {\n return keyframe;\n }\n\n const moved = movedHandle === 'in' ? keyframe.handleIn : keyframe.handleOut;\n const movedLength = Math.sqrt(moved.x * moved.x + moved.y * moved.y);\n\n if (movedLength < 1e-10) {\n return keyframe;\n }\n\n if (tangentMode === 'mirrored') {\n // Opposite handle is exact negative\n const opposite = { x: -moved.x, y: -moved.y };\n return movedHandle === 'in'\n ? { ...keyframe, handleOut: opposite }\n : { ...keyframe, handleIn: opposite };\n }\n\n // Remaining mode is 'aligned': the opposite handle keeps its length but\n // aligns direction with the moved handle.\n const other = movedHandle === 'in' ? keyframe.handleOut : keyframe.handleIn;\n const otherLength = Math.sqrt(other.x * other.x + other.y * other.y);\n\n if (otherLength < 1e-10) {\n return keyframe;\n }\n\n // Direction opposite to moved handle, scaled to original length\n const scale = otherLength / movedLength;\n const opposite = { x: -moved.x * scale, y: -moved.y * scale };\n\n return movedHandle === 'in'\n ? { ...keyframe, handleOut: opposite }\n : { ...keyframe, handleIn: opposite };\n}\n\n/**\n * Compute step function value (hold previous Y until next keyframe).\n */\nexport function evaluateStep(curve: CurveData, x: number): number {\n const { keyframes } = curve;\n if (keyframes.length === 0) return 0;\n\n const first = keyframes[0];\n if (!first) return 0;\n if (x <= first.x) return first.y;\n\n for (let i = keyframes.length - 1; i >= 0; i--) {\n const kf = keyframes[i];\n if (kf && kf.x <= x) {\n return kf.y;\n }\n }\n\n return first.y;\n}\n\n/**\n * Handle infinity behavior (cycle, pingpong, linear extrapolation).\n * Returns the wrapped X value and whether Y should be flipped (for pingpong).\n */\nexport function wrapX(\n x: number,\n domainX: [number, number],\n preInfinity: CurveData['preInfinity'],\n postInfinity: CurveData['postInfinity']\n): { wrappedX: number; flipY: boolean } {\n const [xMin, xMax] = domainX;\n const range = xMax - xMin;\n\n if (range <= 0) return { wrappedX: xMin, flipY: false };\n\n // Within range — no wrapping needed\n if (x >= xMin && x <= xMax) {\n return { wrappedX: x, flipY: false };\n }\n\n const mode =\n x < xMin ? (preInfinity ?? 'constant') : (postInfinity ?? 'constant');\n\n switch (mode) {\n case 'constant':\n return { wrappedX: clamp(x, xMin, xMax), flipY: false };\n\n case 'linear':\n // No wrapping — caller should extrapolate\n return { wrappedX: x, flipY: false };\n\n case 'cycle': {\n const normalized = (((x - xMin) % range) + range) % range;\n return { wrappedX: xMin + normalized, flipY: false };\n }\n\n case 'pingpong': {\n const normalized = (((x - xMin) % (range * 2)) + range * 2) % (range * 2);\n if (normalized > range) {\n return { wrappedX: xMin + (range * 2 - normalized), flipY: false };\n }\n return { wrappedX: xMin + normalized, flipY: false };\n }\n\n default:\n return { wrappedX: clamp(x, xMin, xMax), flipY: false };\n }\n}\n\n/**\n * Convert domain coordinates to canvas pixel coordinates.\n * Re-exported from shared canvas primitives for backward compatibility.\n */\nexport function domainToCanvas(\n domainX: number,\n domainY: number,\n viewport: CurveViewport | CanvasViewport,\n canvasWidth: number,\n canvasHeight: number\n): { px: number; py: number } {\n return sharedDomainToCanvas(\n domainX,\n domainY,\n viewport,\n canvasWidth,\n canvasHeight\n );\n}\n\n/**\n * Convert canvas pixel coordinates to domain coordinates.\n * Re-exported from shared canvas primitives for backward compatibility.\n */\nexport function canvasToDomain(\n px: number,\n py: number,\n viewport: CurveViewport | CanvasViewport,\n canvasWidth: number,\n canvasHeight: number\n): { x: number; y: number } {\n return sharedCanvasToDomain(px, py, viewport, canvasWidth, canvasHeight);\n}\n\n/**\n * Hit test — find the nearest element to a canvas pixel position.\n */\nexport function hitTest(\n px: number,\n py: number,\n curve: CurveData,\n viewport: CurveViewport,\n canvasWidth: number,\n canvasHeight: number,\n tolerancePx: number = 10\n): CurveHitTest {\n const noHit: CurveHitTest = {\n type: 'none',\n keyframeIndex: -1,\n distance: Infinity,\n };\n let best = noHit;\n\n const { keyframes } = curve;\n\n // Test keyframes and handles (closest wins)\n for (let i = 0; i < keyframes.length; i++) {\n const kf = keyframes[i];\n if (!kf) continue;\n const kfCanvas = domainToCanvas(\n kf.x,\n kf.y,\n viewport,\n canvasWidth,\n canvasHeight\n );\n const kfDist = Math.sqrt((px - kfCanvas.px) ** 2 + (py - kfCanvas.py) ** 2);\n\n if (kfDist < tolerancePx && kfDist < best.distance) {\n best = { type: 'keyframe', keyframeIndex: i, distance: kfDist };\n }\n\n // Check handles (only if handles are visible — selected state checked by caller)\n const hInCanvas = domainToCanvas(\n kf.x + kf.handleIn.x,\n kf.y + kf.handleIn.y,\n viewport,\n canvasWidth,\n canvasHeight\n );\n const hInDist = Math.sqrt(\n (px - hInCanvas.px) ** 2 + (py - hInCanvas.py) ** 2\n );\n if (hInDist < tolerancePx && hInDist < best.distance) {\n best = { type: 'handleIn', keyframeIndex: i, distance: hInDist };\n }\n\n const hOutCanvas = domainToCanvas(\n kf.x + kf.handleOut.x,\n kf.y + kf.handleOut.y,\n viewport,\n canvasWidth,\n canvasHeight\n );\n const hOutDist = Math.sqrt(\n (px - hOutCanvas.px) ** 2 + (py - hOutCanvas.py) ** 2\n );\n if (hOutDist < tolerancePx && hOutDist < best.distance) {\n best = { type: 'handleOut', keyframeIndex: i, distance: hOutDist };\n }\n }\n\n // If we found a keyframe or handle, return that (higher priority than curve)\n if (best.type !== 'none') return best;\n\n // Test curve line\n if (keyframes.length >= 2) {\n const samples = sampleCurve(curve, 200);\n for (let i = 0; i < samples.length; i++) {\n const s = samples[i];\n if (!s) continue;\n const sCanvas = domainToCanvas(\n s.x,\n s.y,\n viewport,\n canvasWidth,\n canvasHeight\n );\n const dist = Math.sqrt((px - sCanvas.px) ** 2 + (py - sCanvas.py) ** 2);\n if (dist < tolerancePx && dist < best.distance) {\n best = { type: 'curve', keyframeIndex: -1, distance: dist };\n }\n }\n }\n\n return best;\n}\n\n/**\n * Sort keyframes by X position (required after move).\n */\nexport function sortKeyframes(keyframes: CurveKeyframe[]): CurveKeyframe[] {\n return [...keyframes].sort((a, b) => a.x - b.x);\n}\n\n/**\n * Create a default linear curve.\n */\nexport function createLinearCurve(\n domainX: [number, number] = [0, 1],\n domainY: [number, number] = [0, 1]\n): CurveData {\n return {\n keyframes: [\n {\n x: domainX[0],\n y: domainY[0],\n handleIn: { x: 0, y: 0 },\n handleOut: {\n x: (domainX[1] - domainX[0]) / 3,\n y: (domainY[1] - domainY[0]) / 3,\n },\n tangentMode: 'linear',\n id: generateKeyframeId(),\n },\n {\n x: domainX[1],\n y: domainY[1],\n handleIn: {\n x: -(domainX[1] - domainX[0]) / 3,\n y: -(domainY[1] - domainY[0]) / 3,\n },\n handleOut: { x: 0, y: 0 },\n tangentMode: 'linear',\n id: generateKeyframeId(),\n },\n ],\n domainX,\n domainY,\n };\n}\n\n/**\n * Ensure every keyframe has an ID.\n */\nexport function ensureKeyframeIds(keyframes: CurveKeyframe[]): CurveKeyframe[] {\n return keyframes.map(kf =>\n kf.id ? kf : { ...kf, id: generateKeyframeId() }\n );\n}\n"],"names":["sharedDomainToCanvas","sharedCanvasToDomain"],"mappings":";;;AAcA,IAAI,SAAS,GAAG,CAAC;AAEjB;;AAEG;SACa,kBAAkB,GAAA;IAChC,SAAS,IAAI,CAAC;IACd,OAAO,CAAA,GAAA,EAAM,IAAI,CAAC,GAAG,EAAE,CAAA,CAAA,EAAI,SAAS,EAAE;AACxC;AAEA;;;AAGG;AACG,SAAU,cAAc,CAC5B,EAAU,EACV,EAAU,EACV,EAAU,EACV,EAAU,EACV,CAAS,EAAA;AAET,IAAA,MAAM,EAAE,GAAG,CAAC,GAAG,CAAC;AAChB,IAAA,QACE,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE;AACjB,QAAA,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,GAAG,EAAE;AACpB,QAAA,CAAC,GAAG,EAAE,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE;AACnB,QAAA,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE;AAElB;AAkBA;;;AAGG;AACH,SAAS,gBAAgB,CACvB,EAAU,EACV,EAAU,EACV,EAAU,EACV,EAAU,EACV,CAAS,EAAA;AAET,IAAA,MAAM,EAAE,GAAG,CAAC,GAAG,CAAC;AAChB,IAAA,QACE,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC,IAAI,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,EAAE,CAAC;AAE5E;AAEA;;;AAGG;SACa,SAAS,CACvB,EAAU,EACV,EAAU,EACV,EAAU,EACV,EAAU,EACV,OAAe,EACf,YAAoB,IAAI,EACxB,gBAAwB,EAAE,EAAA;;AAG1B,IAAA,IAAI,CAAC,GAAG,CAAC,OAAO,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC;IACvC,CAAC,GAAG,KAAK,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;;AAGlB,IAAA,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,aAAa,EAAE,CAAC,EAAE,EAAE;AACtC,QAAA,MAAM,CAAC,GAAG,cAAc,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC,GAAG,OAAO;AACrD,QAAA,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,SAAS;AAAE,YAAA,OAAO,CAAC;AAErC,QAAA,MAAM,EAAE,GAAG,gBAAgB,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC;AAC9C,QAAA,IAAI,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK;AAAE,YAAA,MAAM;AAEhC,QAAA,MAAM,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE;QACvB,CAAC,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC;IACvB;;IAGA,IAAI,EAAE,GAAG,CAAC;IACV,IAAI,EAAE,GAAG,CAAC;IACV,CAAC,GAAG,GAAG;AACP,IAAA,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,aAAa,EAAE,CAAC,EAAE,EAAE;AACtC,QAAA,MAAM,CAAC,GAAG,cAAc,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC;QAC3C,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,OAAO,CAAC,GAAG,SAAS;AAAE,YAAA,OAAO,CAAC;AAE/C,QAAA,IAAI,CAAC,GAAG,OAAO,EAAE;YACf,EAAE,GAAG,CAAC;QACR;aAAO;YACL,EAAE,GAAG,CAAC;QACR;QACA,CAAC,GAAG,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC;IACnB;AAEA,IAAA,OAAO,CAAC;AACV;AAEA;;;;;;AAMG;AACH,SAAS,oBAAoB,CAC3B,MAAgC,EAChC,OAAe,EAAA;IAEf,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC;AAC/B,IAAA,IAAI,IAAI,IAAI,OAAO,IAAI,IAAI,GAAG,KAAK;AAAE,QAAA,OAAO,MAAM;AAElD,IAAA,MAAM,KAAK,GAAG,OAAO,GAAG,IAAI;AAC5B,IAAA,OAAO,EAAE,CAAC,EAAE,MAAM,CAAC,CAAC,GAAG,KAAK,EAAE,CAAC,EAAE,MAAM,CAAC,CAAC,GAAG,KAAK,EAAE;AACrD;AAEA;;;;AAIG;AACH,SAAS,uBAAuB,CAC9B,GAAkB,EAClB,GAAkB,EAAA;IAOlB,MAAM,QAAQ,GAAG,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC;IAE9B,MAAM,IAAI,GAAG,oBAAoB,CAAC,GAAG,CAAC,SAAS,EAAE,QAAQ,CAAC;IAC1D,MAAM,GAAG,GAAG,oBAAoB,CAAC,GAAG,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAExD,OAAO;AACL,QAAA,EAAE,EAAE,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE;QAC1B,EAAE,EAAE,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,EAAE;QAC5C,EAAE,EAAE,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,EAAE;AAC1C,QAAA,EAAE,EAAE,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE;KAC3B;AACH;AAEA;;;AAGG;AACG,SAAU,aAAa,CAAC,KAAgB,EAAE,CAAS,EAAA;AACvD,IAAA,MAAM,EAAE,SAAS,EAAE,GAAG,KAAK;AAC3B,IAAA,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC;AAAE,QAAA,OAAO,CAAC;AAEpC,IAAA,MAAM,KAAK,GAAG,SAAS,CAAC,CAAC,CAAC;IAC1B,MAAM,IAAI,GAAG,SAAS,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC;AAC5C,IAAA,IAAI,CAAC,KAAK,IAAI,CAAC,IAAI;AAAE,QAAA,OAAO,CAAC;AAE7B,IAAA,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC,CAAC;;AAG1C,IAAA,IAAI,CAAC,IAAI,KAAK,CAAC,CAAC;QAAE,OAAO,KAAK,CAAC,CAAC;AAChC,IAAA,IAAI,CAAC,IAAI,IAAI,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC,CAAC;;AAG9B,IAAA,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE;AAC7C,QAAA,MAAM,GAAG,GAAG,SAAS,CAAC,CAAC,CAAC;QACxB,MAAM,GAAG,GAAG,SAAS,CAAC,CAAC,GAAG,CAAC,CAAC;AAC5B,QAAA,IAAI,CAAC,GAAG,IAAI,CAAC,GAAG;YAAE;AAElB,QAAA,IAAI,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,CAAC,EAAE;;AAE5B,YAAA,IAAI,GAAG,CAAC,WAAW,KAAK,MAAM,EAAE;gBAC9B,OAAO,GAAG,CAAC,CAAC;YACd;;AAGA,YAAA,IAAI,GAAG,CAAC,WAAW,KAAK,QAAQ,IAAI,GAAG,CAAC,WAAW,KAAK,QAAQ,EAAE;gBAChE,MAAM,IAAI,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC;AAC/C,gBAAA,OAAO,GAAG,CAAC,CAAC,GAAG,IAAI,IAAI,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC;YACvC;AAEA,YAAA,MAAM,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,GAAG,uBAAuB,CAAC,GAAG,EAAE,GAAG,CAAC;YAC5D,MAAM,CAAC,GAAG,SAAS,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;YAC9C,OAAO,cAAc,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;QAClD;IACF;IAEA,OAAO,IAAI,CAAC,CAAC;AACf;AAEA;;;AAGG;AACG,SAAU,WAAW,CACzB,KAAgB,EAChB,UAAkB,EAAA;IAElB,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,KAAK,CAAC,OAAO;IAClC,MAAM,OAAO,GAAoC,EAAE;AACnD,IAAA,MAAM,IAAI,GAAG,CAAC,IAAI,GAAG,IAAI,KAAK,UAAU,GAAG,CAAC,IAAI,CAAC,CAAC;AAElD,IAAA,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,UAAU,EAAE,CAAC,EAAE,EAAE;AACnC,QAAA,MAAM,CAAC,GAAG,IAAI,GAAG,CAAC,GAAG,IAAI;AACzB,QAAA,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,aAAa,CAAC,KAAK,EAAE,CAAC,CAAC,EAAE,CAAC;IACjD;AAEA,IAAA,OAAO,OAAO;AAChB;AAEA;;;AAGG;SACa,kBAAkB,CAChC,IAA0B,EAC1B,OAAsB,EACtB,IAA0B,EAAA;AAE1B,IAAA,MAAM,aAAa,GAAG,CAAC,GAAG,CAAC;AAE3B,IAAA,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,EAAE;QAClB,OAAO,EAAE,QAAQ,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,SAAS,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE;IAChE;AAEA,IAAA,IAAI,CAAC,IAAI,IAAI,IAAI,EAAE;;AAEjB,QAAA,MAAM,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,IAAI,aAAa;AAC/C,QAAA,MAAM,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,IAAI,aAAa;QAC/C,OAAO;YACL,QAAQ,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE;YAC5B,SAAS,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE;SAC5B;IACH;AAEA,IAAA,IAAI,CAAC,IAAI,IAAI,IAAI,EAAE;;AAEjB,QAAA,MAAM,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,IAAI,aAAa;AAC/C,QAAA,MAAM,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,IAAI,aAAa;QAC/C,OAAO;YACL,QAAQ,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE;YAC5B,SAAS,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE;SAC5B;IACH;AAEA,IAAA,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,EAAE;QAClB,OAAO,EAAE,QAAQ,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,SAAS,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE;IAChE;;;;;IAMA,MAAM,OAAO,GAAG,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IAC/B,MAAM,OAAO,GAAG,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IAE/B,MAAM,KAAK,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IAChC,MAAM,MAAM,GAAG,IAAI,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC;AACjC,IAAA,MAAM,QAAQ,GAAG,KAAK,GAAG,MAAM,IAAI,CAAC;IAEpC,OAAO;AACL,QAAA,QAAQ,EAAE;YACR,CAAC,EAAE,EAAE,KAAK,GAAG,QAAQ,CAAC,GAAG,OAAO,GAAG,aAAa;YAChD,CAAC,EAAE,EAAE,KAAK,GAAG,QAAQ,CAAC,GAAG,OAAO,GAAG,aAAa;AACjD,SAAA;AACD,QAAA,SAAS,EAAE;YACT,CAAC,EAAE,CAAC,MAAM,GAAG,QAAQ,IAAI,OAAO,GAAG,aAAa;YAChD,CAAC,EAAE,CAAC,MAAM,GAAG,QAAQ,IAAI,OAAO,GAAG,aAAa;AACjD,SAAA;KACF;AACH;AAEA;;;;AAIG;AACG,SAAU,gBAAgB,CAC9B,QAAuB,EACvB,WAAyB,EAAA;AAEzB,IAAA,MAAM,EAAE,WAAW,EAAE,GAAG,QAAQ;IAEhC,IACE,WAAW,KAAK,MAAM;AACtB,QAAA,WAAW,KAAK,MAAM;AACtB,QAAA,WAAW,KAAK,QAAQ;QACxB,WAAW,KAAK,MAAM,EACtB;AACA,QAAA,OAAO,QAAQ;IACjB;AAEA,IAAA,MAAM,KAAK,GAAG,WAAW,KAAK,IAAI,GAAG,QAAQ,CAAC,QAAQ,GAAG,QAAQ,CAAC,SAAS;IAC3E,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC;AAEpE,IAAA,IAAI,WAAW,GAAG,KAAK,EAAE;AACvB,QAAA,OAAO,QAAQ;IACjB;AAEA,IAAA,IAAI,WAAW,KAAK,UAAU,EAAE;;AAE9B,QAAA,MAAM,QAAQ,GAAG,EAAE,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE;QAC7C,OAAO,WAAW,KAAK;cACnB,EAAE,GAAG,QAAQ,EAAE,SAAS,EAAE,QAAQ;cAClC,EAAE,GAAG,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE;IACzC;;;AAIA,IAAA,MAAM,KAAK,GAAG,WAAW,KAAK,IAAI,GAAG,QAAQ,CAAC,SAAS,GAAG,QAAQ,CAAC,QAAQ;IAC3E,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC;AAEpE,IAAA,IAAI,WAAW,GAAG,KAAK,EAAE;AACvB,QAAA,OAAO,QAAQ;IACjB;;AAGA,IAAA,MAAM,KAAK,GAAG,WAAW,GAAG,WAAW;IACvC,MAAM,QAAQ,GAAG,EAAE,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,KAAK,EAAE,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,KAAK,EAAE;IAE7D,OAAO,WAAW,KAAK;UACnB,EAAE,GAAG,QAAQ,EAAE,SAAS,EAAE,QAAQ;UAClC,EAAE,GAAG,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE;AACzC;AAwEA;;;AAGG;AACG,SAAU,cAAc,CAC5B,OAAe,EACf,OAAe,EACf,QAAwC,EACxC,WAAmB,EACnB,YAAoB,EAAA;AAEpB,IAAA,OAAOA,gBAAoB,CACzB,OAAO,EACP,OAAO,EACP,QAAQ,EACR,WAAW,EACX,YAAY,CACb;AACH;AAEA;;;AAGG;AACG,SAAU,cAAc,CAC5B,EAAU,EACV,EAAU,EACV,QAAwC,EACxC,WAAmB,EACnB,YAAoB,EAAA;AAEpB,IAAA,OAAOC,gBAAoB,CAAC,EAAE,EAAE,EAAE,EAAE,QAAQ,EAAE,WAAW,EAAE,YAAY,CAAC;AAC1E;AAEA;;AAEG;SACa,OAAO,CACrB,EAAU,EACV,EAAU,EACV,KAAgB,EAChB,QAAuB,EACvB,WAAmB,EACnB,YAAoB,EACpB,cAAsB,EAAE,EAAA;AAExB,IAAA,MAAM,KAAK,GAAiB;AAC1B,QAAA,IAAI,EAAE,MAAM;QACZ,aAAa,EAAE,EAAE;AACjB,QAAA,QAAQ,EAAE,QAAQ;KACnB;IACD,IAAI,IAAI,GAAG,KAAK;AAEhB,IAAA,MAAM,EAAE,SAAS,EAAE,GAAG,KAAK;;AAG3B,IAAA,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,SAAS,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;AACzC,QAAA,MAAM,EAAE,GAAG,SAAS,CAAC,CAAC,CAAC;AACvB,QAAA,IAAI,CAAC,EAAE;YAAE;AACT,QAAA,MAAM,QAAQ,GAAG,cAAc,CAC7B,EAAE,CAAC,CAAC,EACJ,EAAE,CAAC,CAAC,EACJ,QAAQ,EACR,WAAW,EACX,YAAY,CACb;QACD,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,GAAG,QAAQ,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,EAAE,GAAG,QAAQ,CAAC,EAAE,KAAK,CAAC,CAAC;QAE3E,IAAI,MAAM,GAAG,WAAW,IAAI,MAAM,GAAG,IAAI,CAAC,QAAQ,EAAE;AAClD,YAAA,IAAI,GAAG,EAAE,IAAI,EAAE,UAAU,EAAE,aAAa,EAAE,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE;QACjE;;AAGA,QAAA,MAAM,SAAS,GAAG,cAAc,CAC9B,EAAE,CAAC,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,CAAC,EACpB,EAAE,CAAC,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,CAAC,EACpB,QAAQ,EACR,WAAW,EACX,YAAY,CACb;QACD,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CACvB,CAAC,EAAE,GAAG,SAAS,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,EAAE,GAAG,SAAS,CAAC,EAAE,KAAK,CAAC,CACpD;QACD,IAAI,OAAO,GAAG,WAAW,IAAI,OAAO,GAAG,IAAI,CAAC,QAAQ,EAAE;AACpD,YAAA,IAAI,GAAG,EAAE,IAAI,EAAE,UAAU,EAAE,aAAa,EAAE,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE;QAClE;AAEA,QAAA,MAAM,UAAU,GAAG,cAAc,CAC/B,EAAE,CAAC,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC,EACrB,EAAE,CAAC,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC,EACrB,QAAQ,EACR,WAAW,EACX,YAAY,CACb;QACD,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CACxB,CAAC,EAAE,GAAG,UAAU,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,EAAE,GAAG,UAAU,CAAC,EAAE,KAAK,CAAC,CACtD;QACD,IAAI,QAAQ,GAAG,WAAW,IAAI,QAAQ,GAAG,IAAI,CAAC,QAAQ,EAAE;AACtD,YAAA,IAAI,GAAG,EAAE,IAAI,EAAE,WAAW,EAAE,aAAa,EAAE,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE;QACpE;IACF;;AAGA,IAAA,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM;AAAE,QAAA,OAAO,IAAI;;AAGrC,IAAA,IAAI,SAAS,CAAC,MAAM,IAAI,CAAC,EAAE;QACzB,MAAM,OAAO,GAAG,WAAW,CAAC,KAAK,EAAE,GAAG,CAAC;AACvC,QAAA,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;AACvC,YAAA,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;AACpB,YAAA,IAAI,CAAC,CAAC;gBAAE;AACR,YAAA,MAAM,OAAO,GAAG,cAAc,CAC5B,CAAC,CAAC,CAAC,EACH,CAAC,CAAC,CAAC,EACH,QAAQ,EACR,WAAW,EACX,YAAY,CACb;YACD,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,GAAG,OAAO,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,EAAE,GAAG,OAAO,CAAC,EAAE,KAAK,CAAC,CAAC;YACvE,IAAI,IAAI,GAAG,WAAW,IAAI,IAAI,GAAG,IAAI,CAAC,QAAQ,EAAE;AAC9C,gBAAA,IAAI,GAAG,EAAE,IAAI,EAAE,OAAO,EAAE,aAAa,EAAE,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE;YAC7D;QACF;IACF;AAEA,IAAA,OAAO,IAAI;AACb;AAEA;;AAEG;AACG,SAAU,aAAa,CAAC,SAA0B,EAAA;IACtD,OAAO,CAAC,GAAG,SAAS,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;AACjD;AAEA;;AAEG;AACG,SAAU,iBAAiB,CAC/B,OAAA,GAA4B,CAAC,CAAC,EAAE,CAAC,CAAC,EAClC,OAAA,GAA4B,CAAC,CAAC,EAAE,CAAC,CAAC,EAAA;IAElC,OAAO;AACL,QAAA,SAAS,EAAE;AACT,YAAA;AACE,gBAAA,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;AACb,gBAAA,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;gBACb,QAAQ,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE;AACxB,gBAAA,SAAS,EAAE;AACT,oBAAA,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC;AAChC,oBAAA,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC;AACjC,iBAAA;AACD,gBAAA,WAAW,EAAE,QAAQ;gBACrB,EAAE,EAAE,kBAAkB,EAAE;AACzB,aAAA;AACD,YAAA;AACE,gBAAA,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;AACb,gBAAA,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;AACb,gBAAA,QAAQ,EAAE;AACR,oBAAA,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;AACjC,oBAAA,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;AAClC,iBAAA;gBACD,SAAS,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE;AACzB,gBAAA,WAAW,EAAE,QAAQ;gBACrB,EAAE,EAAE,kBAAkB,EAAE;AACzB,aAAA;AACF,SAAA;QACD,OAAO;QACP,OAAO;KACR;AACH;AAEA;;AAEG;AACG,SAAU,iBAAiB,CAAC,SAA0B,EAAA;AAC1D,IAAA,OAAO,SAAS,CAAC,GAAG,CAAC,EAAE,IACrB,EAAE,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE,EAAE,EAAE,kBAAkB,EAAE,EAAE,CACjD;AACH;;;;"}
@@ -1 +1 @@
1
- {"version":3,"file":"useCurveInteraction.js","sources":["../../../../../../src/components/controls/CurveEditor/useCurveInteraction.ts"],"sourcesContent":[null],"names":[],"mappings":";;;;;AAqEA;;;;;;;;AASA;AAIE;;AAEF;AAEM;AAGJ;AAqBA;;;;AAYA;;;;AAMA;AACE;AACA;;AACA;AACA;AACF;AAEA;;;AAIE;;AAME;;AAEF;AAIF;;AAGM;AAA+B;;;;;AAM7B;;;;AAIJ;;;AAMJ;AAEI;;AACA;AACA;;AAEA;;AAGA;;AAEA;AAEA;;;;AAIE;;AAEE;;AAEE;;;AAEA;;;;;;;;;AASA;AACA;;;AAGA;;;;;;AAMF;;;;AAKJ;AAEE;;;;;;;AAOE;AACA;;;;;;;AAOF;;;;AAKF;AACE;;AAGF;;AAEA;AACF;;;;;;;;;;AAWC;;AAIH;AAEI;AACA;;AAEA;;AAEA;AAEA;;AAEE;AACA;;AAGA;AACE;;AACK;;AAIL;;AACK;AACL;;;AAEA;;;;AAKJ;;;;AAKA;;AAEA;AACE;;AAEA;;AAEA;;AAIA;AACA;;;;AAKE;AACA;AACA;AACA;;AAGF;AACA;;AAGA;AACE;;;AAIF;AACE;AACA;;AAEE;;;AAGA;;;;;AAMF;;AAGF;AAIA;AACE;AACA;AACD;;;;;;;AAQD;;AAEA;AACA;AACE;AACA;;AAGF;AACA;;;;;;;;AAQA;;;;AAKA;AAKA;;;;AAOJ;;;;;;;;;;;;;;;;AAiBC;;AAIH;AAEI;AACA;;AAEA;AAEA;;;;AAII;AAEA;;AAEE;;;;;;AAKA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;;AAIE;AACA;;AAGA;;;;;;;AAUR;;AAGI;;AAGF;AACA;;;;AAKF;AACF;;;;;;;;;AAUC;;AAIH;;;AAGI;AACA;;AAEA;;AAEA;AAEA;AACE;;;;AAGA;;;;;AAMA;AAIA;AACE;AACA;;;AAGF;;;;;AAMA;;;;AAKA;;;;AAIE;;AAGF;AACE;AACA;;;AAGA;;;AAIF;AACA;AACE;AACA;;;AAGF;AAEA;;;;AAIJ;;;;;;;;;;;;;;;;AAiBC;;AAIH;;;;AAII;AAAuC;;;;AAKrC;AAAqD;;AAEvD;AACF;;AAGA;;AAEA;;AAEA;AACA;AACF;;;;;;;;;;AAUC;AAED;;;AAKA;AAEA;;;;AAKM;AAAuC;AAEvC;;AAEI;AACA;;;;;;AAMN;AAEA;;AAEA;AACF;;;;;;;;AASC;;;;;;;;AAaG;AAAuC;AAEvC;;AAEA;;AAGE;AACE;;;AAEF;AACE;;;AAEF;;;AAGA;;;;;AAMA;;;AAIA;AACA;AACA;;AAEJ;AAEA;AACE;AACA;;;AAGF;AACF;;;;;;;;;;AAWC;;;;AAMH;AAEI;;AAEA;AACE;;;;;AAIE;;;;;;AAME;;;;AAIJ;AACA;AACA;;AAEE;;;;;;AAKF;AACA;AACA;AACA;AACA;;;;;;AAKE;AACA;;;;;AAIN;;;;;;;;;;AAWC;;;;;;AAQD;;;;;;AAMC;AACD;AACA;AACA;;AAEJ;;"}
1
+ {"version":3,"file":"useCurveInteraction.js","sources":["src/components/controls/CurveEditor/useCurveInteraction.ts"],"sourcesContent":["'use client';\n\nimport { useState, useCallback, useRef } from 'react';\n\nimport { clamp, roundToPrecision } from '@/utils/mathUtils';\n\nimport {\n canvasToDomain,\n hitTest,\n sortKeyframes,\n generateKeyframeId,\n constrainTangent,\n computeAutoTangent,\n} from './curveUtils';\n\nimport type {\n CurveData,\n CurveKeyframe,\n CurveViewport,\n CurveHitTest,\n TangentMode,\n} from './CurveEditor.types';\nimport type React from 'react';\n\ntype DragState =\n | { type: 'idle' }\n | { type: 'keyframe'; keyframeIndex: number; startX: number; startY: number }\n | { type: 'handleIn'; keyframeIndex: number }\n | { type: 'handleOut'; keyframeIndex: number }\n | { type: 'boxSelect'; startPx: number; startPy: number };\n\ninterface UseCurveInteractionOptions {\n curve: CurveData;\n viewport: CurveViewport;\n canvasRef: React.RefObject<HTMLCanvasElement | null>;\n disabled: boolean;\n readOnly: boolean;\n allowAdd: boolean;\n allowDelete: boolean;\n maxKeyframes: number;\n lockEndpoints: boolean;\n clampY: boolean;\n snapToGrid: boolean;\n gridSubdivisions: number;\n lockTangents: boolean;\n minKeyframeDistance: number;\n precision: number;\n onChange: (curve: CurveData) => void;\n onChangeComplete?: (curve: CurveData) => void;\n onSelectionChange?: (ids: string[]) => void;\n}\n\ninterface UseCurveInteractionReturn {\n selectedIds: Set<string>;\n hoveredElement: CurveHitTest | null;\n selectionBox: { x1: number; y1: number; x2: number; y2: number } | null;\n isDragging: boolean;\n handlers: {\n onPointerDown: (e: React.PointerEvent) => void;\n onPointerMove: (e: React.PointerEvent) => void;\n onPointerUp: (e: React.PointerEvent) => void;\n onDoubleClick: (e: React.MouseEvent) => void;\n onKeyDown: (e: React.KeyboardEvent) => void;\n };\n setTangentMode: (mode: TangentMode) => void;\n selectAll: () => void;\n deleteSelected: () => void;\n}\n\nconst TANGENT_CYCLE: TangentMode[] = [\n 'free',\n 'aligned',\n 'mirrored',\n 'auto',\n 'linear',\n 'step',\n];\n\nfunction getCanvasCoords(\n e: React.PointerEvent | React.MouseEvent,\n canvas: HTMLCanvasElement\n): { px: number; py: number } {\n const rect = canvas.getBoundingClientRect();\n return { px: e.clientX - rect.left, py: e.clientY - rect.top };\n}\n\nexport function useCurveInteraction(\n options: UseCurveInteractionOptions\n): UseCurveInteractionReturn {\n const {\n curve,\n viewport,\n canvasRef,\n disabled,\n readOnly,\n allowAdd,\n allowDelete,\n maxKeyframes,\n lockEndpoints,\n lockTangents,\n clampY,\n snapToGrid,\n gridSubdivisions,\n minKeyframeDistance,\n precision,\n onChange,\n onChangeComplete,\n onSelectionChange,\n } = options;\n\n const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set());\n const [hoveredElement, setHoveredElement] = useState<CurveHitTest | null>(\n null\n );\n const [selectionBox, setSelectionBox] = useState<{\n x1: number;\n y1: number;\n x2: number;\n y2: number;\n } | null>(null);\n\n const dragStateRef = useRef<DragState>({ type: 'idle' });\n const curveBeforeDragRef = useRef<CurveData | null>(null);\n\n // Ref drives synchronous reads inside the pointer handlers; the state mirror\n // is the value exposed for rendering (kept in sync on drag start/end).\n const [isDragging, setIsDragging] = useState(false);\n\n const getCanvasSize = useCallback(() => {\n const canvas = canvasRef.current;\n if (!canvas) return { w: 320, h: 200 };\n const rect = canvas.getBoundingClientRect();\n return { w: rect.width, h: rect.height };\n }, [canvasRef]);\n\n const updateSelection = useCallback(\n (ids: Set<string>) => {\n setSelectedIds(ids);\n onSelectionChange?.(Array.from(ids));\n },\n [onSelectionChange]\n );\n\n const snapValue = useCallback(\n (value: number, domainRange: number): number => {\n const gridStep = domainRange / gridSubdivisions;\n return Math.round(value / gridStep) * gridStep;\n },\n [gridSubdivisions]\n );\n\n const applyAutoTangents = useCallback(\n (keyframes: CurveKeyframe[]): CurveKeyframe[] => {\n return keyframes.map((kf, i) => {\n if (kf.tangentMode !== 'auto') return kf;\n const prev = i > 0 ? (keyframes[i - 1] ?? null) : null;\n const next =\n i < keyframes.length - 1 ? (keyframes[i + 1] ?? null) : null;\n const tangents = computeAutoTangent(prev, kf, next);\n return {\n ...kf,\n handleIn: tangents.handleIn,\n handleOut: tangents.handleOut,\n };\n });\n },\n []\n );\n\n // ─── Pointer Down ───\n const onPointerDown = useCallback(\n (e: React.PointerEvent) => {\n if (disabled) return;\n const canvas = canvasRef.current;\n if (!canvas) return;\n\n const { px, py } = getCanvasCoords(e, canvas);\n const { w, h } = getCanvasSize();\n\n if (e.button !== 0) return;\n\n const hit = hitTest(px, py, curve, viewport, w, h);\n\n if (hit.type === 'keyframe') {\n const kf = curve.keyframes[hit.keyframeIndex];\n if (!kf?.id) return;\n\n if (e.shiftKey) {\n // Toggle selection\n const newSel = new Set(selectedIds);\n if (newSel.has(kf.id)) {\n newSel.delete(kf.id);\n } else {\n newSel.add(kf.id);\n }\n updateSelection(newSel);\n } else if (!selectedIds.has(kf.id)) {\n updateSelection(new Set([kf.id]));\n }\n\n if (!readOnly) {\n curveBeforeDragRef.current = {\n ...curve,\n keyframes: [...curve.keyframes],\n };\n dragStateRef.current = {\n type: 'keyframe',\n keyframeIndex: hit.keyframeIndex,\n startX: kf.x,\n startY: kf.y,\n };\n setIsDragging(true);\n canvas.setPointerCapture(e.pointerId);\n }\n return;\n }\n\n if (\n (hit.type === 'handleIn' || hit.type === 'handleOut') &&\n !readOnly &&\n !lockTangents\n ) {\n const kf = curve.keyframes[hit.keyframeIndex];\n if (!kf?.id) return;\n updateSelection(new Set([kf.id]));\n curveBeforeDragRef.current = {\n ...curve,\n keyframes: [...curve.keyframes],\n };\n dragStateRef.current = {\n type: hit.type,\n keyframeIndex: hit.keyframeIndex,\n };\n setIsDragging(true);\n canvas.setPointerCapture(e.pointerId);\n return;\n }\n\n // Empty area — start box select or clear selection\n if (!e.shiftKey) {\n updateSelection(new Set());\n }\n\n dragStateRef.current = { type: 'boxSelect', startPx: px, startPy: py };\n setIsDragging(true);\n canvas.setPointerCapture(e.pointerId);\n },\n [\n disabled,\n readOnly,\n lockTangents,\n canvasRef,\n curve,\n viewport,\n selectedIds,\n updateSelection,\n getCanvasSize,\n ]\n );\n\n // ─── Pointer Move ───\n const onPointerMove = useCallback(\n (e: React.PointerEvent) => {\n const canvas = canvasRef.current;\n if (!canvas) return;\n\n const { px, py } = getCanvasCoords(e, canvas);\n const { w, h } = getCanvasSize();\n const drag = dragStateRef.current;\n\n if (drag.type === 'idle') {\n // Hover detection\n const hit = hitTest(px, py, curve, viewport, w, h);\n setHoveredElement(hit.type !== 'none' ? hit : null);\n\n // Cursor\n if (hit.type === 'keyframe') {\n canvas.style.cursor = 'pointer';\n } else if (\n (hit.type === 'handleIn' || hit.type === 'handleOut') &&\n !lockTangents\n ) {\n canvas.style.cursor = 'crosshair';\n } else if (hit.type === 'curve') {\n canvas.style.cursor = 'pointer';\n } else {\n canvas.style.cursor = disabled ? 'not-allowed' : 'default';\n }\n return;\n }\n\n if (drag.type === 'boxSelect') {\n setSelectionBox({ x1: drag.startPx, y1: drag.startPy, x2: px, y2: py });\n return;\n }\n\n if (readOnly) return;\n\n if (drag.type === 'keyframe') {\n const domain = canvasToDomain(px, py, viewport, w, h);\n const kf = curve.keyframes[drag.keyframeIndex];\n if (!kf) return;\n\n const isEndpoint =\n drag.keyframeIndex === 0 ||\n drag.keyframeIndex === curve.keyframes.length - 1;\n\n let newX = domain.x;\n let newY = domain.y;\n\n // Apply snap\n const shouldSnap = snapToGrid !== e.ctrlKey; // Ctrl toggles snap\n if (shouldSnap) {\n const xRange = curve.domainX[1] - curve.domainX[0];\n const yRange = curve.domainY[1] - curve.domainY[0];\n newX = snapValue(newX, xRange);\n newY = snapValue(newY, yRange);\n }\n\n newX = roundToPrecision(newX, precision);\n newY = roundToPrecision(newY, precision);\n\n // Lock X for endpoints\n if (lockEndpoints && isEndpoint) {\n newX = kf.x;\n }\n\n // Clamp X between neighbors\n if (!isEndpoint || !lockEndpoints) {\n const prevKf = curve.keyframes[drag.keyframeIndex - 1];\n const nextKf = curve.keyframes[drag.keyframeIndex + 1];\n if (prevKf) {\n newX = Math.max(newX, prevKf.x + minKeyframeDistance);\n }\n if (nextKf) {\n newX = Math.min(newX, nextKf.x - minKeyframeDistance);\n }\n }\n\n // Clamp Y to domain\n if (clampY) {\n newY = clamp(newY, curve.domainY[0], curve.domainY[1]);\n }\n\n const newKeyframes = curve.keyframes.map((k, i) =>\n i === drag.keyframeIndex ? { ...k, x: newX, y: newY } : k\n );\n\n onChange({\n ...curve,\n keyframes: applyAutoTangents(newKeyframes),\n });\n return;\n }\n\n // The only remaining drag types here are 'handleIn' and 'handleOut'\n // (idle, boxSelect and keyframe are handled and returned above).\n {\n const kf = curve.keyframes[drag.keyframeIndex];\n if (!kf) return;\n\n const domain = canvasToDomain(px, py, viewport, w, h);\n const handleOffset = {\n x: roundToPrecision(domain.x - kf.x, precision),\n y: roundToPrecision(domain.y - kf.y, precision),\n };\n\n let updatedKf: CurveKeyframe;\n if (drag.type === 'handleIn') {\n updatedKf = { ...kf, handleIn: handleOffset };\n } else {\n updatedKf = { ...kf, handleOut: handleOffset };\n }\n\n // Promote auto → free when the user manually adjusts a handle.\n // This prevents applyAutoTangents from overwriting the change.\n if (updatedKf.tangentMode === 'auto') {\n updatedKf = { ...updatedKf, tangentMode: 'free' };\n }\n\n // Apply tangent constraints\n updatedKf = constrainTangent(\n updatedKf,\n drag.type === 'handleIn' ? 'in' : 'out'\n );\n\n const newKeyframes = curve.keyframes.map((k, i) =>\n i === drag.keyframeIndex ? updatedKf : k\n );\n\n onChange({ ...curve, keyframes: newKeyframes });\n return;\n }\n },\n [\n canvasRef,\n curve,\n viewport,\n disabled,\n readOnly,\n lockTangents,\n snapToGrid,\n lockEndpoints,\n clampY,\n minKeyframeDistance,\n precision,\n onChange,\n getCanvasSize,\n snapValue,\n applyAutoTangents,\n ]\n );\n\n // ─── Pointer Up ───\n const onPointerUp = useCallback(\n (e: React.PointerEvent) => {\n const canvas = canvasRef.current;\n if (!canvas) return;\n\n const drag = dragStateRef.current;\n\n if (drag.type === 'boxSelect') {\n // Resolve box selection\n if (selectionBox) {\n const { w, h } = getCanvasSize();\n const newSel = new Set(e.shiftKey ? selectedIds : new Set<string>());\n\n for (let i = 0; i < curve.keyframes.length; i++) {\n const kf = curve.keyframes[i];\n if (!kf) continue;\n if (!kf.id) continue;\n\n const [vxMin, vxMax] = viewport.viewX;\n const [vyMin, vyMax] = viewport.viewY;\n const vw = vxMax - vxMin || 1;\n const vh = vyMax - vyMin || 1;\n const kfPx = ((kf.x - vxMin) / vw) * w;\n const kfPy = ((vyMax - kf.y) / vh) * h;\n\n const boxLeft = Math.min(selectionBox.x1, selectionBox.x2);\n const boxRight = Math.max(selectionBox.x1, selectionBox.x2);\n const boxTop = Math.min(selectionBox.y1, selectionBox.y2);\n const boxBottom = Math.max(selectionBox.y1, selectionBox.y2);\n\n if (\n kfPx >= boxLeft &&\n kfPx <= boxRight &&\n kfPy >= boxTop &&\n kfPy <= boxBottom\n ) {\n newSel.add(kf.id);\n }\n }\n\n updateSelection(newSel);\n }\n\n setSelectionBox(null);\n }\n\n if (\n (drag.type === 'keyframe' ||\n drag.type === 'handleIn' ||\n drag.type === 'handleOut') &&\n curveBeforeDragRef.current\n ) {\n onChangeComplete?.(curve);\n curveBeforeDragRef.current = null;\n }\n\n dragStateRef.current = { type: 'idle' };\n setIsDragging(false);\n canvas.releasePointerCapture(e.pointerId);\n },\n [\n canvasRef,\n curve,\n viewport,\n selectionBox,\n selectedIds,\n updateSelection,\n onChangeComplete,\n getCanvasSize,\n ]\n );\n\n // ─── Double Click ───\n const onDoubleClick = useCallback(\n (e: React.MouseEvent) => {\n if (disabled || readOnly) return;\n const canvas = canvasRef.current;\n if (!canvas) return;\n\n const { px, py } = getCanvasCoords(e, canvas);\n const { w, h } = getCanvasSize();\n const hit = hitTest(px, py, curve, viewport, w, h);\n\n if (hit.type === 'keyframe') {\n if (lockTangents) return;\n // Cycle tangent mode\n const kf = curve.keyframes[hit.keyframeIndex];\n if (!kf) return;\n\n const currentIndex = TANGENT_CYCLE.indexOf(kf.tangentMode);\n const nextIndex = (currentIndex + 1) % TANGENT_CYCLE.length;\n const nextMode = TANGENT_CYCLE[nextIndex] ?? 'free';\n\n const newKeyframes = curve.keyframes.map((k, i) =>\n i === hit.keyframeIndex ? { ...k, tangentMode: nextMode } : k\n );\n\n const updated = {\n ...curve,\n keyframes: applyAutoTangents(newKeyframes),\n };\n onChange(updated);\n onChangeComplete?.(updated);\n return;\n }\n\n if (allowAdd && curve.keyframes.length < maxKeyframes) {\n // Add keyframe at cursor position\n const domain = canvasToDomain(px, py, viewport, w, h);\n const newX = roundToPrecision(domain.x, precision);\n let newY = roundToPrecision(domain.y, precision);\n\n // Check within domain X\n if (newX < curve.domainX[0] || newX > curve.domainX[1]) return;\n\n // Clamp Y to domain\n if (clampY) {\n newY = clamp(newY, curve.domainY[0], curve.domainY[1]);\n }\n\n const newKf: CurveKeyframe = {\n x: newX,\n y: newY,\n handleIn: { x: 0, y: 0 },\n handleOut: { x: 0, y: 0 },\n tangentMode: 'auto',\n id: generateKeyframeId(),\n };\n\n const newKeyframes = sortKeyframes([...curve.keyframes, newKf]);\n const updated = {\n ...curve,\n keyframes: applyAutoTangents(newKeyframes),\n };\n onChange(updated);\n onChangeComplete?.(updated);\n\n if (newKf.id) {\n updateSelection(new Set([newKf.id]));\n }\n }\n },\n [\n disabled,\n readOnly,\n lockTangents,\n canvasRef,\n curve,\n viewport,\n allowAdd,\n maxKeyframes,\n clampY,\n precision,\n onChange,\n onChangeComplete,\n updateSelection,\n getCanvasSize,\n applyAutoTangents,\n ]\n );\n\n // ─── Actions ───\n const deleteSelectedFn = useCallback(() => {\n if (readOnly || !allowDelete) return;\n\n const newKeyframes = curve.keyframes.filter(kf => {\n if (!kf.id || !selectedIds.has(kf.id)) return true;\n\n // Don't delete endpoints if locked\n if (lockEndpoints) {\n const idx = curve.keyframes.indexOf(kf);\n if (idx === 0 || idx === curve.keyframes.length - 1) return true;\n }\n return false;\n });\n\n // Must keep at least 2 keyframes\n if (newKeyframes.length < 2) return;\n\n const updated = { ...curve, keyframes: applyAutoTangents(newKeyframes) };\n onChange(updated);\n onChangeComplete?.(updated);\n updateSelection(new Set());\n }, [\n curve,\n selectedIds,\n readOnly,\n allowDelete,\n lockEndpoints,\n onChange,\n onChangeComplete,\n updateSelection,\n applyAutoTangents,\n ]);\n\n const selectAllFn = useCallback(() => {\n const allIds = new Set(\n curve.keyframes.map(kf => kf.id).filter(Boolean) as string[]\n );\n updateSelection(allIds);\n }, [curve, updateSelection]);\n\n const setTangentModeFn = useCallback(\n (mode: TangentMode) => {\n if (readOnly || lockTangents || selectedIds.size === 0) return;\n\n const newKeyframes = curve.keyframes.map(kf => {\n if (!kf.id || !selectedIds.has(kf.id)) return kf;\n\n if (mode === 'linear') {\n return {\n ...kf,\n tangentMode: mode,\n handleIn: { x: 0, y: 0 },\n handleOut: { x: 0, y: 0 },\n };\n }\n return { ...kf, tangentMode: mode };\n });\n\n const updated = { ...curve, keyframes: applyAutoTangents(newKeyframes) };\n onChange(updated);\n onChangeComplete?.(updated);\n },\n [\n curve,\n selectedIds,\n readOnly,\n lockTangents,\n onChange,\n onChangeComplete,\n applyAutoTangents,\n ]\n );\n\n const nudgeSelected = useCallback(\n (direction: string, fine: boolean) => {\n const [vxMin, vxMax] = viewport.viewX;\n const [vyMin, vyMax] = viewport.viewY;\n\n const baseStep = fine ? 0.001 : 0.01;\n const xStep = (vxMax - vxMin) * baseStep;\n const yStep = (vyMax - vyMin) * baseStep;\n\n const newKeyframes = curve.keyframes.map(kf => {\n if (!kf.id || !selectedIds.has(kf.id)) return kf;\n\n let { x, y } = kf;\n const idx = curve.keyframes.indexOf(kf);\n const isEndpoint = idx === 0 || idx === curve.keyframes.length - 1;\n\n switch (direction) {\n case 'ArrowLeft':\n if (!(lockEndpoints && isEndpoint)) x -= xStep;\n break;\n case 'ArrowRight':\n if (!(lockEndpoints && isEndpoint)) x += xStep;\n break;\n case 'ArrowUp':\n y += yStep;\n break;\n case 'ArrowDown':\n y -= yStep;\n break;\n }\n\n if (clampY) {\n y = clamp(y, curve.domainY[0], curve.domainY[1]);\n }\n\n return {\n ...kf,\n x: roundToPrecision(x, precision),\n y: roundToPrecision(y, precision),\n };\n });\n\n const updated = {\n ...curve,\n keyframes: applyAutoTangents(sortKeyframes(newKeyframes)),\n };\n onChange(updated);\n onChangeComplete?.(updated);\n },\n [\n curve,\n viewport,\n selectedIds,\n lockEndpoints,\n clampY,\n precision,\n onChange,\n onChangeComplete,\n applyAutoTangents,\n ]\n );\n\n // ─── Keyboard ───\n // Declared after the action callbacks it dispatches so they are referenced\n // (and depended on) by value rather than via a forward closure.\n const onKeyDown = useCallback(\n (e: React.KeyboardEvent) => {\n if (disabled) return;\n\n switch (e.key) {\n case 'Delete':\n case 'Backspace': {\n if (readOnly || !allowDelete) return;\n e.preventDefault();\n deleteSelectedFn();\n break;\n }\n case 'a': {\n if (e.ctrlKey || e.metaKey) {\n e.preventDefault();\n selectAllFn();\n }\n break;\n }\n case 'ArrowUp':\n case 'ArrowDown':\n case 'ArrowLeft':\n case 'ArrowRight': {\n if (readOnly || selectedIds.size === 0) return;\n e.preventDefault();\n nudgeSelected(e.key, e.shiftKey);\n break;\n }\n case '1':\n case '2':\n case '3':\n case '4':\n case '5':\n case '6': {\n if (readOnly || lockTangents || selectedIds.size === 0) return;\n e.preventDefault();\n const modeIndex = parseInt(e.key) - 1;\n const mode = TANGENT_CYCLE[modeIndex];\n if (mode) setTangentModeFn(mode);\n break;\n }\n }\n },\n [\n disabled,\n readOnly,\n lockTangents,\n allowDelete,\n selectedIds,\n deleteSelectedFn,\n selectAllFn,\n nudgeSelected,\n setTangentModeFn,\n ]\n );\n\n return {\n selectedIds,\n hoveredElement,\n selectionBox,\n isDragging,\n handlers: {\n onPointerDown,\n onPointerMove,\n onPointerUp,\n onDoubleClick,\n onKeyDown,\n },\n setTangentMode: setTangentModeFn,\n selectAll: selectAllFn,\n deleteSelected: deleteSelectedFn,\n };\n}\n"],"names":[],"mappings":";;;;;AAqEA;;;;;;;;AASA;AAIE;;AAEF;AAEM;AAGJ;AAqBA;;;;AAYA;;;;AAMA;AACE;AACA;;AACA;AACA;AACF;AAEA;;;AAIE;;AAME;;AAEF;AAIF;;AAGM;AAA+B;;;;;AAM7B;;;;AAIJ;;;AAMJ;AAEI;;AACA;AACA;;AAEA;;AAGA;;AAEA;AAEA;;;;AAIE;;AAEE;;AAEE;;;AAEA;;;;;;;;;AASA;AACA;;;AAGA;;;;;;AAMF;;;;AAKJ;AAEE;;;;;;;AAOE;AACA;;;;;;;AAOF;;;;AAKF;AACE;;AAGF;;AAEA;AACF;;;;;;;;;;AAWC;;AAIH;AAEI;AACA;;AAEA;;AAEA;AAEA;;AAEE;AACA;;AAGA;AACE;;AACK;;AAIL;;AACK;AACL;;;AAEA;;;;AAKJ;;;;AAKA;;AAEA;AACE;;AAEA;;AAEA;;AAIA;AACA;;;;AAKE;AACA;AACA;AACA;;AAGF;AACA;;AAGA;AACE;;;AAIF;AACE;AACA;;AAEE;;;AAGA;;;;;AAMF;;AAGF;AAIA;AACE;AACA;AACD;;;;;;;AAQD;;AAEA;AACA;AACE;AACA;;AAGF;AACA;;;;;;;;AAQA;;;;AAKA;AAKA;;;;AAOJ;;;;;;;;;;;;;;;;AAiBC;;AAIH;AAEI;AACA;;AAEA;AAEA;;;;AAII;AAEA;;AAEE;;;;;;AAKA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;;AAIE;AACA;;AAGA;;;;;;;AAUR;;AAGI;;AAGF;AACA;;;;AAKF;AACF;;;;;;;;;AAUC;;AAIH;;;AAGI;AACA;;AAEA;;AAEA;AAEA;AACE;;;;AAGA;;;;;AAMA;AAIA;AACE;AACA;;;AAGF;;;;;AAMA;;;;AAKA;;;;AAIE;;AAGF;AACE;AACA;;;AAGA;;;AAIF;AACA;AACE;AACA;;;AAGF;AAEA;;;;AAIJ;;;;;;;;;;;;;;;;AAiBC;;AAIH;;;;AAII;AAAuC;;;;AAKrC;AAAqD;;AAEvD;AACF;;AAGA;;AAEA;;AAEA;AACA;AACF;;;;;;;;;;AAUC;AAED;;;AAKA;AAEA;;;;AAKM;AAAuC;AAEvC;;AAEI;AACA;;;;;;AAMN;AAEA;;AAEA;AACF;;;;;;;;AASC;;;;;;;;AAaG;AAAuC;AAEvC;;AAEA;;AAGE;AACE;;;AAEF;AACE;;;AAEF;;;AAGA;;;;;AAMA;;;AAIA;AACA;AACA;;AAEJ;AAEA;AACE;AACA;;;AAGF;AACF;;;;;;;;;;AAWC;;;;AAMH;AAEI;;AAEA;AACE;;;;;AAIE;;;;;;AAME;;;;AAIJ;AACA;AACA;;AAEE;;;;;;AAKF;AACA;AACA;AACA;AACA;;;;;;AAKE;AACA;;;;;AAIN;;;;;;;;;;AAWC;;;;;;AAQD;;;;;;AAMC;AACD;AACA;AACA;;AAEJ;;"}
@@ -1 +1 @@
1
- {"version":3,"file":"useCurveRenderer.js","sources":["../../../../../../src/components/controls/CurveEditor/useCurveRenderer.ts"],"sourcesContent":[null],"names":[],"mappings":";;;;;;AAsDA;AAEM;AACJ;AAEA;AACE;AACA;;;AAGA;;AAEA;AAEA;AAEA;AACA;AACA;;;;AAKA;;AAEE;AACA;AACA;;;AAEA;;AAGF;;AAkBA;AACE;;;;AAMF;AACA;;;;;;AAOI;AACA;;;;AAID;;;;;;AAOC;AACA;;;AAGD;;;;;;;AAiBC;AACA;;;;;AAKD;;;AAIH;AACA;;AAGA;AACE;;;;;;AA6BA;;AAGF;AACF;;;;;;AAOE;;;AAGI;AACA;AACF;AACA;AACA;AACE;AACF;;AAGF;AACA;;AAEJ;AAEA;AACA;AAEA;AASE;;AAEA;AACA;;AAEA;AACA;AACA;AACA;;AAGA;AACA;;AACA;;AAGA;AACE;AACA;;AACA;;;;AAKJ;AAEA;AAWE;AAEA;AACE;AACA;;AACA;;;;AAGA;AACA;;AAGA;AAQA;AACA;AACA;;;;;;AAMA;;;AAIA;AAEA;AACA;;AAEA;;;AAUA;AAQA;AACA;AACA;;;;;;AAMA;;;AAIA;AAEE;AACF;AACA;;AAEA;;;AASJ;AAEA;AAYE;AAEA;AACE;AACA;;;AAEA;AAEA;;AAIA;;;AAIE;;;;;;;;;;AASA;;AAEA;;;AAIA;;AAEA;AACA;;;;AAGA;;AAEA;AACA;;;;AAIN;AAEA;AAKE;AACA;AACA;AACA;AAEA;AACA;;AAGA;AACA;AACA;;;AAGA;AACF;;"}
1
+ {"version":3,"file":"useCurveRenderer.js","sources":["src/components/controls/CurveEditor/useCurveRenderer.ts"],"sourcesContent":["'use client';\n\nimport { useEffect, useRef, useCallback } from 'react';\n\nimport {\n drawGrid as sharedDrawGrid,\n drawDomainBounds as sharedDrawDomainBounds,\n drawAxisLabels as sharedDrawAxisLabels,\n} from '@/components/primitives/canvas/canvasDrawing';\nimport {\n resolveCanvasTheme,\n resolveVarValue,\n} from '@/components/primitives/canvas/canvasTheme';\n\nimport { domainToCanvas, sampleCurve } from './curveUtils';\n\nimport type {\n CurveData,\n CurveViewport,\n CurveHitTest,\n CurveBackgroundInfo,\n} from './CurveEditor.types';\nimport type { CanvasThemeColors } from '@/components/primitives/canvas/canvas.types';\nimport type React from 'react';\n\ninterface UseCurveRendererOptions {\n canvasRef: React.RefObject<HTMLCanvasElement | null>;\n curve: CurveData;\n viewport: CurveViewport;\n showGrid: boolean;\n gridSubdivisions: number;\n showAxisLabels: boolean;\n labelX?: string;\n labelY?: string;\n curveColor: string;\n curveWidth: number;\n renderBackground?: (\n ctx: CanvasRenderingContext2D,\n info: CurveBackgroundInfo\n ) => void;\n selectedIds: Set<string>;\n hoveredElement: CurveHitTest | null;\n selectionBox: {\n x1: number;\n y1: number;\n x2: number;\n y2: number;\n } | null;\n disabled: boolean;\n isDragging: boolean;\n lockTangents: boolean;\n resizeToken?: number;\n}\n\n// CanvasThemeColors and resolveCanvasTheme are now imported from shared canvas primitives\n\nexport function useCurveRenderer(options: UseCurveRendererOptions): void {\n const rafRef = useRef<number>(0);\n\n const draw = useCallback(() => {\n const canvas = options.canvasRef.current;\n if (!canvas) return;\n\n const ctx = canvas.getContext('2d');\n if (!ctx) return;\n\n const theme = resolveCanvasTheme(canvas);\n\n const dpr =\n typeof window !== 'undefined' ? window.devicePixelRatio || 1 : 1;\n const rect = canvas.getBoundingClientRect();\n const w = rect.width;\n const h = rect.height;\n\n // Set canvas resolution for retina. Writing the canvas backing-store size\n // is a DOM side effect of drawing; the rule mistakes it for mutating the\n // `options` argument because `canvas` is reached via options.canvasRef.\n if (canvas.width !== w * dpr || canvas.height !== h * dpr) {\n // eslint-disable-next-line react-hooks/immutability\n canvas.width = w * dpr;\n canvas.height = h * dpr;\n ctx.scale(dpr, dpr);\n } else {\n ctx.setTransform(dpr, 0, 0, dpr, 0, 0);\n }\n\n const {\n curve,\n viewport,\n showGrid,\n gridSubdivisions,\n showAxisLabels,\n labelX,\n labelY,\n curveColor: rawCurveColor,\n curveWidth,\n renderBackground,\n selectedIds,\n hoveredElement,\n selectionBox,\n disabled,\n } = options;\n\n // Resolve curveColor if it's a CSS variable reference\n const curveColor = rawCurveColor.startsWith('var(')\n ? resolveVarValue(canvas, rawCurveColor)\n : rawCurveColor;\n\n const opacity = disabled ? 0.4 : 1;\n\n // 1. Clear background\n ctx.globalAlpha = 1;\n ctx.fillStyle = theme.backgroundSecondary;\n ctx.fillRect(0, 0, w, h);\n\n // 1b. Custom background (histograms, gradients, etc.)\n if (renderBackground) {\n ctx.save();\n renderBackground(ctx, {\n width: w,\n height: h,\n viewport,\n domainX: curve.domainX,\n domainY: curve.domainY,\n });\n ctx.restore();\n }\n\n // 2. Grid lines -- based on domain values, not viewport\n if (showGrid) {\n sharedDrawGrid(ctx, w, h, theme, {\n subdivisions: gridSubdivisions,\n domain: { domainX: curve.domainX, domainY: curve.domainY },\n viewport,\n opacity,\n });\n }\n\n // 3. Domain boundary axes (stronger lines at domain min/max)\n sharedDrawDomainBounds(\n ctx,\n w,\n h,\n theme,\n { domainX: curve.domainX, domainY: curve.domainY },\n viewport,\n opacity\n );\n\n // 4. Axis labels -- based on domain values\n if (showAxisLabels) {\n sharedDrawAxisLabels(ctx, w, h, theme, {\n subdivisions: gridSubdivisions,\n domain: { domainX: curve.domainX, domainY: curve.domainY },\n viewport,\n opacity,\n labelX,\n labelY,\n });\n }\n\n // 5. Curve line\n ctx.globalAlpha = opacity;\n drawCurveLine(ctx, curve, viewport, w, h, curveColor, curveWidth);\n\n // 6. Tangent handles (for selected keyframes)\n if (!options.lockTangents) {\n drawTangentHandles(\n ctx,\n curve,\n viewport,\n w,\n h,\n selectedIds,\n hoveredElement,\n theme,\n opacity\n );\n }\n\n // 7. Keyframe points\n drawKeyframes(\n ctx,\n curve,\n viewport,\n w,\n h,\n curveColor,\n selectedIds,\n hoveredElement,\n theme,\n opacity\n );\n\n // 8. Selection box\n if (selectionBox) {\n drawSelectionBox(ctx, selectionBox, curveColor);\n }\n\n ctx.globalAlpha = 1;\n }, [options]);\n\n // Redraw on state changes. The draw callback mutates the canvas DOM element\n // (backing-store size), which the rule reports as modifying `options`; that\n // is a benign DOM side effect, not a props mutation.\n // eslint-disable-next-line react-hooks/immutability\n useEffect(() => {\n if (options.isDragging) {\n // Use rAF for smooth updates during drag\n const render = () => {\n draw();\n rafRef.current = requestAnimationFrame(render);\n };\n rafRef.current = requestAnimationFrame(render);\n return () => {\n cancelAnimationFrame(rafRef.current);\n };\n }\n\n draw();\n return undefined;\n }, [draw, options.isDragging]);\n}\n\n// drawGrid, drawDomainBounds, drawAxisLabels, and formatLabel are now\n// provided by shared canvas primitives and called in the draw() function above.\n\nfunction drawCurveLine(\n ctx: CanvasRenderingContext2D,\n curve: CurveData,\n viewport: CurveViewport,\n w: number,\n h: number,\n curveColor: string,\n lineWidth: number\n): void {\n if (curve.keyframes.length < 2) return;\n\n const samples = sampleCurve(curve, Math.max(200, w));\n if (samples.length === 0) return;\n\n ctx.strokeStyle = curveColor;\n ctx.lineWidth = lineWidth;\n ctx.lineJoin = 'round';\n ctx.lineCap = 'round';\n ctx.beginPath();\n\n const first = samples[0];\n if (!first) return;\n const firstCanvas = domainToCanvas(first.x, first.y, viewport, w, h);\n ctx.moveTo(firstCanvas.px, firstCanvas.py);\n\n for (let i = 1; i < samples.length; i++) {\n const s = samples[i];\n if (!s) continue;\n const sCanvas = domainToCanvas(s.x, s.y, viewport, w, h);\n ctx.lineTo(sCanvas.px, sCanvas.py);\n }\n\n ctx.stroke();\n}\n\nfunction drawTangentHandles(\n ctx: CanvasRenderingContext2D,\n curve: CurveData,\n viewport: CurveViewport,\n w: number,\n h: number,\n selectedIds: Set<string>,\n hoveredElement: CurveHitTest | null,\n theme: CanvasThemeColors,\n opacity: number\n): void {\n const { keyframes } = curve;\n\n for (let i = 0; i < keyframes.length; i++) {\n const kf = keyframes[i];\n if (!kf) continue;\n if (!kf.id || !selectedIds.has(kf.id)) continue;\n if (kf.tangentMode === 'linear' || kf.tangentMode === 'step') continue;\n\n const isAutoMode = kf.tangentMode === 'auto';\n const kfCanvas = domainToCanvas(kf.x, kf.y, viewport, w, h);\n\n // Handle In\n const hIn = domainToCanvas(\n kf.x + kf.handleIn.x,\n kf.y + kf.handleIn.y,\n viewport,\n w,\n h\n );\n\n ctx.globalAlpha = opacity * (isAutoMode ? 0.4 : 0.7);\n ctx.strokeStyle = theme.textMuted;\n ctx.lineWidth = 1;\n ctx.setLineDash([3, 3]);\n ctx.beginPath();\n ctx.moveTo(kfCanvas.px, kfCanvas.py);\n ctx.lineTo(hIn.px, hIn.py);\n ctx.stroke();\n ctx.setLineDash([]);\n\n // Handle In point\n const hInRadius = isAutoMode ? 3 : 4;\n const isHInHovered =\n hoveredElement?.type === 'handleIn' && hoveredElement.keyframeIndex === i;\n ctx.globalAlpha = opacity;\n ctx.fillStyle = isHInHovered ? theme.textPrimary : theme.textSecondary;\n ctx.beginPath();\n ctx.arc(\n hIn.px,\n hIn.py,\n isHInHovered ? hInRadius + 1 : hInRadius,\n 0,\n Math.PI * 2\n );\n ctx.fill();\n\n // Handle Out\n const hOut = domainToCanvas(\n kf.x + kf.handleOut.x,\n kf.y + kf.handleOut.y,\n viewport,\n w,\n h\n );\n\n ctx.globalAlpha = opacity * (isAutoMode ? 0.4 : 0.7);\n ctx.strokeStyle = theme.textMuted;\n ctx.lineWidth = 1;\n ctx.setLineDash([3, 3]);\n ctx.beginPath();\n ctx.moveTo(kfCanvas.px, kfCanvas.py);\n ctx.lineTo(hOut.px, hOut.py);\n ctx.stroke();\n ctx.setLineDash([]);\n\n // Handle Out point\n const hOutRadius = isAutoMode ? 3 : 4;\n const isHOutHovered =\n hoveredElement?.type === 'handleOut' &&\n hoveredElement.keyframeIndex === i;\n ctx.globalAlpha = opacity;\n ctx.fillStyle = isHOutHovered ? theme.textPrimary : theme.textSecondary;\n ctx.beginPath();\n ctx.arc(\n hOut.px,\n hOut.py,\n isHOutHovered ? hOutRadius + 1 : hOutRadius,\n 0,\n Math.PI * 2\n );\n ctx.fill();\n }\n}\n\nfunction drawKeyframes(\n ctx: CanvasRenderingContext2D,\n curve: CurveData,\n viewport: CurveViewport,\n w: number,\n h: number,\n curveColor: string,\n selectedIds: Set<string>,\n hoveredElement: CurveHitTest | null,\n theme: CanvasThemeColors,\n opacity: number\n): void {\n const { keyframes } = curve;\n\n for (let i = 0; i < keyframes.length; i++) {\n const kf = keyframes[i];\n if (!kf) continue;\n const isSelected = kf.id ? selectedIds.has(kf.id) : false;\n const isHovered =\n hoveredElement?.type === 'keyframe' && hoveredElement.keyframeIndex === i;\n const isEndpoint = i === 0 || i === keyframes.length - 1;\n\n const { px, py } = domainToCanvas(kf.x, kf.y, viewport, w, h);\n\n ctx.globalAlpha = opacity;\n\n if (isEndpoint) {\n // Diamond shape for endpoints\n const size = isHovered ? 8 : isSelected ? 7 : 6;\n ctx.beginPath();\n ctx.moveTo(px, py - size);\n ctx.lineTo(px + size, py);\n ctx.lineTo(px, py + size);\n ctx.lineTo(px - size, py);\n ctx.closePath();\n } else {\n // Circle for middle keyframes\n const radius = isHovered ? 8 : isSelected ? 7 : 6;\n ctx.beginPath();\n ctx.arc(px, py, radius, 0, Math.PI * 2);\n }\n\n if (isSelected) {\n ctx.fillStyle = curveColor;\n ctx.fill();\n ctx.strokeStyle = theme.textPrimary;\n ctx.lineWidth = 2;\n ctx.stroke();\n } else {\n ctx.fillStyle = theme.textPrimary;\n ctx.fill();\n ctx.strokeStyle = curveColor;\n ctx.lineWidth = 1.5;\n ctx.stroke();\n }\n }\n}\n\nfunction drawSelectionBox(\n ctx: CanvasRenderingContext2D,\n box: { x1: number; y1: number; x2: number; y2: number },\n curveColor: string\n): void {\n const x = Math.min(box.x1, box.x2);\n const y = Math.min(box.y1, box.y2);\n const bw = Math.abs(box.x2 - box.x1);\n const bh = Math.abs(box.y2 - box.y1);\n\n ctx.globalAlpha = 0.1;\n ctx.fillStyle = curveColor;\n ctx.fillRect(x, y, bw, bh);\n\n ctx.globalAlpha = 0.5;\n ctx.strokeStyle = curveColor;\n ctx.lineWidth = 1;\n ctx.setLineDash([4, 4]);\n ctx.strokeRect(x, y, bw, bh);\n ctx.setLineDash([]);\n}\n"],"names":[],"mappings":";;;;;;AAsDA;AAEM;AACJ;AAEA;AACE;AACA;;;AAGA;;AAEA;AAEA;AAEA;AACA;AACA;;;;AAKA;;AAEE;AACA;AACA;;;AAEA;;AAGF;;AAkBA;AACE;;;;AAMF;AACA;;;;;;AAOI;AACA;;;;AAID;;;;;;AAOC;AACA;;;AAGD;;;;;;;AAiBC;AACA;;;;;AAKD;;;AAIH;AACA;;AAGA;AACE;;;;;;AA6BA;;AAGF;AACF;;;;;;AAOE;;;AAGI;AACA;AACF;AACA;AACA;AACE;AACF;;AAGF;AACA;;AAEJ;AAEA;AACA;AAEA;AASE;;AAEA;AACA;;AAEA;AACA;AACA;AACA;;AAGA;AACA;;AACA;;AAGA;AACE;AACA;;AACA;;;;AAKJ;AAEA;AAWE;AAEA;AACE;AACA;;AACA;;;;AAGA;AACA;;AAGA;AAQA;AACA;AACA;;;;;;AAMA;;;AAIA;AAEA;AACA;;AAEA;;;AAUA;AAQA;AACA;AACA;;;;;;AAMA;;;AAIA;AAEE;AACF;AACA;;AAEA;;;AASJ;AAEA;AAYE;AAEA;AACE;AACA;;;AAEA;AAEA;;AAIA;;;AAIE;;;;;;;;;;AASA;;AAEA;;;AAIA;;AAEA;AACA;;;;AAGA;;AAEA;AACA;;;;AAIN;AAEA;AAKE;AACA;AACA;AACA;AAEA;AACA;;AAGA;AACA;AACA;;;AAGA;AACF;;"}
@@ -89,11 +89,17 @@ import '../../Icons/GitBranchIcon.js';
89
89
  import '../../Icons/BugIcon.js';
90
90
  import '../../Icons/MinusIcon.js';
91
91
  import '../../Icons/PinIcon.js';
92
+ import '../../Icons/OrbitIcon.js';
93
+ import '../../Icons/RotateIcon.js';
94
+ import '../../Icons/SpinIcon.js';
95
+ import '../../Icons/CameraIcon.js';
96
+ import '../../Icons/CubeIcon.js';
97
+ import '../../Icons/ZoomFitIcon.js';
92
98
  import '../../../utils/devWarn.js';
93
99
  import { useMergedRef } from '../../../hooks/useMergedRef/useMergedRef.js';
94
100
  import '../../../hooks/useBreakpoint/useBreakpoint.js';
95
101
  import './../../../assets/src/theme/lightTheme.css.ts.vanilla-Ct6f9Iym.css';
96
- import '../../../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":"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"}
1
+ {"version":3,"file":"fileTreeIcons.js","sources":["src/components/controls/FileTree/fileTreeIcons.ts"],"sourcesContent":["import type { FileTreeNode } from './FileTree.types';\n\n/**\n * The icon categories FileTree resolves a node to. Folders get an open/closed\n * pair; files collapse to a small set of type buckets backed by the existing\n * icon set. Rendering a kind into an actual icon element lives in the client\n * component — this module is pure classification so it can be unit-tested\n * without React.\n */\nexport type FileIconKind =\n | 'folder'\n | 'folder-open'\n | 'image'\n | 'media'\n | 'code'\n | 'archive'\n | 'text'\n | 'file';\n\n/**\n * Maps a lowercased file extension (no dot) to an icon bucket. Unknown\n * extensions fall through to the generic `file` glyph.\n */\nconst KIND_BY_EXTENSION: Readonly<Record<string, FileIconKind>> = {\n // Images / raster + vector assets\n png: 'image',\n jpg: 'image',\n jpeg: 'image',\n gif: 'image',\n webp: 'image',\n avif: 'image',\n bmp: 'image',\n ico: 'image',\n svg: 'image',\n tif: 'image',\n tiff: 'image',\n psd: 'image',\n\n // Media (audio + video share the play glyph)\n mp3: 'media',\n wav: 'media',\n ogg: 'media',\n flac: 'media',\n aac: 'media',\n m4a: 'media',\n mp4: 'media',\n mov: 'media',\n avi: 'media',\n mkv: 'media',\n webm: 'media',\n\n // Code / markup / config / data\n js: 'code',\n jsx: 'code',\n ts: 'code',\n tsx: 'code',\n mjs: 'code',\n cjs: 'code',\n json: 'code',\n json5: 'code',\n html: 'code',\n htm: 'code',\n css: 'code',\n scss: 'code',\n sass: 'code',\n less: 'code',\n vue: 'code',\n svelte: 'code',\n astro: 'code',\n py: 'code',\n rb: 'code',\n go: 'code',\n rs: 'code',\n java: 'code',\n kt: 'code',\n swift: 'code',\n c: 'code',\n h: 'code',\n cpp: 'code',\n cc: 'code',\n hpp: 'code',\n cs: 'code',\n php: 'code',\n sh: 'code',\n bash: 'code',\n zsh: 'code',\n yml: 'code',\n yaml: 'code',\n toml: 'code',\n xml: 'code',\n sql: 'code',\n graphql: 'code',\n gql: 'code',\n lua: 'code',\n dart: 'code',\n r: 'code',\n\n // Archives\n zip: 'archive',\n rar: 'archive',\n '7z': 'archive',\n tar: 'archive',\n gz: 'archive',\n tgz: 'archive',\n bz2: 'archive',\n xz: 'archive',\n\n // Text / documents\n txt: 'text',\n md: 'text',\n mdx: 'text',\n rtf: 'text',\n pdf: 'text',\n doc: 'text',\n docx: 'text',\n csv: 'text',\n tsv: 'text',\n log: 'text',\n};\n\n/**\n * Extracts the lowercased extension (without the dot) from a file name, or\n * `undefined` when there isn't one. Dotfiles such as `.env` / `.gitignore` are\n * treated as having no extension (the leading dot is not a separator).\n */\nexport function getFileExtension(name: string): string | undefined {\n const dot = name.lastIndexOf('.');\n if (dot <= 0 || dot === name.length - 1) return undefined;\n return name.slice(dot + 1).toLowerCase();\n}\n\n/**\n * Classifies an extension into an icon bucket. `undefined` / unknown → `file`.\n */\nexport function classifyExtension(ext: string | undefined): FileIconKind {\n if (ext === undefined) return 'file';\n return KIND_BY_EXTENSION[ext.toLowerCase()] ?? 'file';\n}\n\n/**\n * Resolves the icon kind for a node. Folders return `folder-open` when\n * `expanded`, else `folder`. Files use the explicit `ext` when provided, falling\n * back to the extension parsed from `name`.\n */\nexport function getFileIconKind(\n node: Pick<FileTreeNode, 'kind' | 'name' | 'ext'>,\n expanded = false\n): FileIconKind {\n if (node.kind === 'folder') return expanded ? 'folder-open' : 'folder';\n const ext = node.ext ?? getFileExtension(node.name);\n return classifyExtension(ext);\n}\n"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"fileTreeLabels.js","sources":["../../../../../../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;;"}