@vuer-ai/vuer-uikit 0.0.96 → 0.0.98

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 (341) hide show
  1. package/README.md +106 -7
  2. package/cli/dial-cli.js +49 -7
  3. package/dist/SyncScroll/SyncScroll.cjs +10 -10
  4. package/dist/SyncScroll/SyncScroll.mjs +3 -3
  5. package/dist/SyncScroll/index.cjs +10 -10
  6. package/dist/SyncScroll/index.mjs +3 -3
  7. package/dist/chunk-3HEZVWRW.mjs +62 -0
  8. package/dist/chunk-4KWGGESI.cjs +494 -0
  9. package/dist/{chunk-CCMKL2OA.cjs → chunk-7GWDO25E.cjs} +2 -2
  10. package/dist/chunk-A5LCX2UQ.cjs +208 -0
  11. package/dist/chunk-BEJIZ56L.mjs +300 -0
  12. package/dist/chunk-C7VGRU3O.mjs +283 -0
  13. package/dist/{chunk-LONOMMFA.cjs → chunk-LJMNHTTG.cjs} +21 -21
  14. package/dist/chunk-O66RESRR.cjs +285 -0
  15. package/dist/{chunk-RINTUFYQ.cjs → chunk-RMK6W774.cjs} +24 -19
  16. package/dist/{chunk-BFQ2WL5U.mjs → chunk-TTYSYGVE.mjs} +2 -2
  17. package/dist/chunk-VA3PEYFM.mjs +489 -0
  18. package/dist/chunk-VBBJSIY7.cjs +308 -0
  19. package/dist/{chunk-AIINOWEH.mjs → chunk-W4JCKCW7.mjs} +5 -5
  20. package/dist/chunk-WWGF6TBZ.mjs +206 -0
  21. package/dist/chunk-ZGN4UEJR.cjs +679 -0
  22. package/dist/chunk-ZQLRMOUW.mjs +661 -0
  23. package/dist/dial/DialPanel.cjs +24 -24
  24. package/dist/dial/DialPanel.mjs +23 -23
  25. package/dist/dial/DialProvider.cjs +3 -3
  26. package/dist/dial/DialProvider.d.cts +1 -0
  27. package/dist/dial/DialProvider.d.ts +1 -0
  28. package/dist/dial/DialProvider.example.cjs +72 -0
  29. package/dist/dial/DialProvider.example.d.cts +7 -0
  30. package/dist/dial/DialProvider.example.d.ts +7 -0
  31. package/dist/dial/DialProvider.example.mjs +68 -0
  32. package/dist/dial/DialProvider.mjs +1 -1
  33. package/dist/dial/index.cjs +42 -42
  34. package/dist/dial/index.mjs +23 -23
  35. package/dist/dial/wrapped-inputs/ControlledInputs.cjs +27 -27
  36. package/dist/dial/wrapped-inputs/ControlledInputs.mjs +23 -23
  37. package/dist/dial/wrapped-inputs/DialInputs.cjs +34 -34
  38. package/dist/dial/wrapped-inputs/DialInputs.mjs +23 -23
  39. package/dist/dial/wrapped-inputs/DialVectorInput.cjs +24 -24
  40. package/dist/dial/wrapped-inputs/DialVectorInput.mjs +23 -23
  41. package/dist/dial/wrapped-inputs/index.cjs +39 -39
  42. package/dist/dial/wrapped-inputs/index.mjs +23 -23
  43. package/dist/highlight-cursor/cursor-provider.cjs +3 -3
  44. package/dist/highlight-cursor/cursor-provider.mjs +2 -2
  45. package/dist/highlight-cursor/enhanced-components.cjs +10 -10
  46. package/dist/highlight-cursor/enhanced-components.mjs +5 -5
  47. package/dist/highlight-cursor/index.cjs +16 -16
  48. package/dist/highlight-cursor/index.mjs +6 -6
  49. package/dist/hooks/index.cjs +5 -5
  50. package/dist/hooks/index.mjs +1 -1
  51. package/dist/index.cjs +190 -190
  52. package/dist/index.mjs +23 -23
  53. package/dist/ui/UIKitBadge.cjs +6 -6
  54. package/dist/ui/UIKitBadge.mjs +2 -2
  55. package/dist/ui/avatar.cjs +1 -1
  56. package/dist/ui/avatar.mjs +1 -1
  57. package/dist/ui/badge.cjs +1 -1
  58. package/dist/ui/badge.d.cts +1 -1
  59. package/dist/ui/badge.d.ts +1 -1
  60. package/dist/ui/badge.mjs +1 -1
  61. package/dist/ui/button.cjs +1 -1
  62. package/dist/ui/button.mjs +1 -1
  63. package/dist/ui/card.cjs +1 -1
  64. package/dist/ui/card.mjs +1 -1
  65. package/dist/ui/checkbox.cjs +1 -1
  66. package/dist/ui/checkbox.mjs +1 -1
  67. package/dist/ui/collapsible.cjs +1 -1
  68. package/dist/ui/collapsible.mjs +1 -1
  69. package/dist/ui/drawer.cjs +1 -1
  70. package/dist/ui/drawer.mjs +1 -1
  71. package/dist/ui/dropdown.cjs +1 -1
  72. package/dist/ui/dropdown.mjs +1 -1
  73. package/dist/ui/index.cjs +107 -107
  74. package/dist/ui/index.mjs +17 -17
  75. package/dist/ui/inputs/color-input.cjs +1 -1
  76. package/dist/ui/inputs/color-input.mjs +1 -1
  77. package/dist/ui/inputs/index.cjs +11 -11
  78. package/dist/ui/inputs/index.mjs +3 -3
  79. package/dist/ui/inputs/input-numbers.cjs +1 -1
  80. package/dist/ui/inputs/input-numbers.mjs +1 -1
  81. package/dist/ui/inputs/input.cjs +1 -1
  82. package/dist/ui/inputs/input.d.cts +1 -1
  83. package/dist/ui/inputs/input.d.ts +1 -1
  84. package/dist/ui/inputs/input.mjs +1 -1
  85. package/dist/ui/inputs/number-inputs/CmInput.cjs +1 -1
  86. package/dist/ui/inputs/number-inputs/CmInput.mjs +1 -1
  87. package/dist/ui/inputs/number-inputs/DegInput.cjs +1 -1
  88. package/dist/ui/inputs/number-inputs/DegInput.mjs +1 -1
  89. package/dist/ui/inputs/number-inputs/EulerDegInput.cjs +1 -1
  90. package/dist/ui/inputs/number-inputs/EulerDegInput.mjs +1 -1
  91. package/dist/ui/inputs/number-inputs/EulerInput.cjs +1 -1
  92. package/dist/ui/inputs/number-inputs/EulerInput.mjs +1 -1
  93. package/dist/ui/inputs/number-inputs/EulerRadInput.cjs +1 -1
  94. package/dist/ui/inputs/number-inputs/EulerRadInput.mjs +1 -1
  95. package/dist/ui/inputs/number-inputs/InchInput.cjs +1 -1
  96. package/dist/ui/inputs/number-inputs/InchInput.mjs +1 -1
  97. package/dist/ui/inputs/number-inputs/IntInput.cjs +1 -1
  98. package/dist/ui/inputs/number-inputs/IntInput.mjs +1 -1
  99. package/dist/ui/inputs/number-inputs/KVectorInput.cjs +1 -1
  100. package/dist/ui/inputs/number-inputs/KVectorInput.mjs +1 -1
  101. package/dist/ui/inputs/number-inputs/QuaternionInput.cjs +1 -1
  102. package/dist/ui/inputs/number-inputs/QuaternionInput.mjs +1 -1
  103. package/dist/ui/inputs/number-inputs/RadInput.cjs +1 -1
  104. package/dist/ui/inputs/number-inputs/RadInput.mjs +1 -1
  105. package/dist/ui/inputs/number-inputs/TimeInput.cjs +1 -1
  106. package/dist/ui/inputs/number-inputs/TimeInput.mjs +1 -1
  107. package/dist/ui/inputs/number-inputs/Vec3Input.cjs +1 -1
  108. package/dist/ui/inputs/number-inputs/Vec3Input.mjs +1 -1
  109. package/dist/ui/inputs/number-inputs/VectorInput.cjs +1 -1
  110. package/dist/ui/inputs/number-inputs/VectorInput.mjs +1 -1
  111. package/dist/ui/inputs/number-inputs/index.cjs +11 -11
  112. package/dist/ui/inputs/number-inputs/index.mjs +3 -3
  113. package/dist/ui/inputs/presets-input.cjs +1 -1
  114. package/dist/ui/inputs/presets-input.mjs +1 -1
  115. package/dist/ui/label.cjs +1 -1
  116. package/dist/ui/label.mjs +1 -1
  117. package/dist/ui/layout.cjs +1 -1
  118. package/dist/ui/layout.mjs +1 -1
  119. package/dist/ui/layouts/dock-layout/DockLayoutView.cjs +1 -1
  120. package/dist/ui/layouts/dock-layout/DockLayoutView.mjs +1 -1
  121. package/dist/ui/layouts/dock-layout/LayoutSlots.cjs +1 -1
  122. package/dist/ui/layouts/dock-layout/LayoutSlots.mjs +1 -1
  123. package/dist/ui/layouts/dock-layout/index.cjs +1 -1
  124. package/dist/ui/layouts/dock-layout/index.mjs +1 -1
  125. package/dist/ui/layouts/index.cjs +2 -2
  126. package/dist/ui/layouts/index.mjs +2 -2
  127. package/dist/ui/layouts/liquid-layout/LayoutSlots.cjs +1 -1
  128. package/dist/ui/layouts/liquid-layout/LayoutSlots.mjs +1 -1
  129. package/dist/ui/layouts/liquid-layout/LiquidLayoutView.cjs +1 -1
  130. package/dist/ui/layouts/liquid-layout/LiquidLayoutView.mjs +1 -1
  131. package/dist/ui/layouts/liquid-layout/index.cjs +1 -1
  132. package/dist/ui/layouts/liquid-layout/index.mjs +1 -1
  133. package/dist/ui/modal.cjs +1 -1
  134. package/dist/ui/modal.mjs +1 -1
  135. package/dist/ui/navigation.cjs +1 -1
  136. package/dist/ui/navigation.mjs +1 -1
  137. package/dist/ui/pagination.cjs +1 -1
  138. package/dist/ui/pagination.mjs +1 -1
  139. package/dist/ui/panel.cjs +1 -1
  140. package/dist/ui/panel.mjs +1 -1
  141. package/dist/ui/popover.cjs +1 -1
  142. package/dist/ui/popover.mjs +1 -1
  143. package/dist/ui/radio-group.cjs +1 -1
  144. package/dist/ui/radio-group.mjs +1 -1
  145. package/dist/ui/resizable.cjs +1 -1
  146. package/dist/ui/resizable.mjs +1 -1
  147. package/dist/ui/select.cjs +1 -1
  148. package/dist/ui/select.d.cts +1 -1
  149. package/dist/ui/select.d.ts +1 -1
  150. package/dist/ui/select.mjs +1 -1
  151. package/dist/ui/separator.cjs +1 -1
  152. package/dist/ui/separator.mjs +1 -1
  153. package/dist/ui/sheet.cjs +1 -1
  154. package/dist/ui/sheet.mjs +1 -1
  155. package/dist/ui/sidebar.cjs +26 -26
  156. package/dist/ui/sidebar.mjs +2 -2
  157. package/dist/ui/simple-tree-view.cjs +1 -1
  158. package/dist/ui/simple-tree-view.mjs +1 -1
  159. package/dist/ui/skeleton.cjs +1 -1
  160. package/dist/ui/skeleton.mjs +1 -1
  161. package/dist/ui/slider.cjs +1 -1
  162. package/dist/ui/slider.mjs +1 -1
  163. package/dist/ui/switch.cjs +1 -1
  164. package/dist/ui/switch.mjs +1 -1
  165. package/dist/ui/table.cjs +1 -1
  166. package/dist/ui/table.mjs +1 -1
  167. package/dist/ui/tabs.cjs +1 -1
  168. package/dist/ui/tabs.mjs +1 -1
  169. package/dist/ui/textarea.cjs +1 -1
  170. package/dist/ui/textarea.d.cts +1 -1
  171. package/dist/ui/textarea.d.ts +1 -1
  172. package/dist/ui/textarea.mjs +1 -1
  173. package/dist/ui/theme/ThemeToggles.cjs +1 -1
  174. package/dist/ui/theme/ThemeToggles.mjs +1 -1
  175. package/dist/ui/theme/index.cjs +1 -1
  176. package/dist/ui/theme/index.mjs +1 -1
  177. package/dist/ui/toggle-buttons.cjs +1 -1
  178. package/dist/ui/toggle-buttons.mjs +1 -1
  179. package/dist/ui/toggle-group.cjs +1 -1
  180. package/dist/ui/toggle-group.mjs +1 -1
  181. package/dist/ui/toggle.cjs +1 -1
  182. package/dist/ui/toggle.mjs +1 -1
  183. package/dist/ui/toolbar.cjs +1 -1
  184. package/dist/ui/toolbar.mjs +1 -1
  185. package/dist/ui/tooltip.cjs +1 -1
  186. package/dist/ui/tooltip.mjs +1 -1
  187. package/dist/ui/tree-view/TreeSearchBar.cjs +1 -1
  188. package/dist/ui/tree-view/TreeSearchBar.mjs +1 -1
  189. package/dist/ui/tree-view/TreeView.cjs +1 -1
  190. package/dist/ui/tree-view/TreeView.mjs +1 -1
  191. package/dist/ui/tree-view/index.cjs +6 -6
  192. package/dist/ui/tree-view/index.mjs +2 -2
  193. package/dist/ui/tree-view-legacy.cjs +9 -9
  194. package/dist/ui/tree-view-legacy.mjs +5 -5
  195. package/dist/ui/waterfall/CursorOverlay.cjs +1 -1
  196. package/dist/ui/waterfall/CursorOverlay.mjs +1 -1
  197. package/dist/ui/waterfall/TimelineEvent.cjs +1 -1
  198. package/dist/ui/waterfall/TimelineEvent.mjs +1 -1
  199. package/dist/ui/waterfall/TimelineProcessBar.cjs +1 -1
  200. package/dist/ui/waterfall/TimelineProcessBar.mjs +1 -1
  201. package/dist/ui/waterfall/Wedges.cjs +1 -1
  202. package/dist/ui/waterfall/Wedges.mjs +1 -1
  203. package/dist/ui/waterfall/index.cjs +8 -8
  204. package/dist/ui/waterfall/index.mjs +7 -7
  205. package/package.json +28 -2
  206. package/src/SyncScroll/README.md +283 -0
  207. package/src/SyncScroll/SyncScroll.tsx +361 -0
  208. package/src/SyncScroll/index.ts +22 -0
  209. package/src/SyncScroll/useSyncScroll.tsx +226 -0
  210. package/src/cli/dial-cli.ts +1133 -0
  211. package/src/dial/DialPanel.tsx +208 -0
  212. package/src/dial/DialProvider.example.tsx +80 -0
  213. package/src/dial/DialProvider.tsx +138 -0
  214. package/src/dial/index.ts +26 -0
  215. package/src/dial/wrapped-inputs/ControlledInputs.tsx +176 -0
  216. package/src/dial/wrapped-inputs/DialInputs.tsx +401 -0
  217. package/src/dial/wrapped-inputs/DialVectorInput.tsx +125 -0
  218. package/src/dial/wrapped-inputs/index.ts +25 -0
  219. package/src/highlight-cursor/cursor-context.tsx +23 -0
  220. package/src/highlight-cursor/cursor-provider.tsx +264 -0
  221. package/src/highlight-cursor/enhanced-components.tsx +16 -0
  222. package/src/highlight-cursor/index.ts +21 -0
  223. package/src/highlight-cursor/tabs-cursor-context.tsx +121 -0
  224. package/src/highlight-cursor/types.ts +40 -0
  225. package/src/highlight-cursor/with-cursor.tsx +144 -0
  226. package/src/hooks/clientOnly.tsx +54 -0
  227. package/src/hooks/cn.ts +33 -0
  228. package/src/hooks/index.ts +9 -0
  229. package/src/hooks/useDocument.tsx +18 -0
  230. package/src/hooks/useDragSelect.ts +116 -0
  231. package/src/hooks/useIsMobile.ts +35 -0
  232. package/src/hooks/useLocalStorage.ts +122 -0
  233. package/src/hooks/useLocation.tsx +95 -0
  234. package/src/hooks/useQueryParams.tsx +31 -0
  235. package/src/hooks/useWindow.tsx +18 -0
  236. package/src/index.css +5 -0
  237. package/src/index.ts +5 -0
  238. package/src/styles/.editorconfig +0 -0
  239. package/src/styles/theme.css +152 -0
  240. package/src/styles/toast.css +67 -0
  241. package/src/styles/variables.css +229 -0
  242. package/src/ui/UIKitBadge.tsx +171 -0
  243. package/src/ui/avatar.tsx +221 -0
  244. package/src/ui/badge.tsx +74 -0
  245. package/src/ui/button.tsx +143 -0
  246. package/src/ui/card.tsx +146 -0
  247. package/src/ui/checkbox.tsx +78 -0
  248. package/src/ui/collapsible.tsx +43 -0
  249. package/src/ui/drag-selectable/DragSelectProvider.tsx +178 -0
  250. package/src/ui/drag-selectable/createSelectable.tsx +43 -0
  251. package/src/ui/drag-selectable/index.ts +2 -0
  252. package/src/ui/drawer.tsx +137 -0
  253. package/src/ui/dropdown.tsx +707 -0
  254. package/src/ui/icons/MouseCursorIcons.tsx +39 -0
  255. package/src/ui/icons/cursor.tsx +38 -0
  256. package/src/ui/icons/index.ts +2 -0
  257. package/src/ui/index.ts +45 -0
  258. package/src/ui/inputs/color-input.tsx +61 -0
  259. package/src/ui/inputs/index.tsx +5 -0
  260. package/src/ui/inputs/input-numbers.tsx +394 -0
  261. package/src/ui/inputs/input.tsx +155 -0
  262. package/src/ui/inputs/number-inputs/CmInput.tsx +26 -0
  263. package/src/ui/inputs/number-inputs/DegInput.tsx +26 -0
  264. package/src/ui/inputs/number-inputs/EulerDegInput.tsx +14 -0
  265. package/src/ui/inputs/number-inputs/EulerInput.tsx +30 -0
  266. package/src/ui/inputs/number-inputs/EulerRadInput.tsx +14 -0
  267. package/src/ui/inputs/number-inputs/InchInput.tsx +26 -0
  268. package/src/ui/inputs/number-inputs/IntInput.tsx +46 -0
  269. package/src/ui/inputs/number-inputs/KVectorInput.tsx +20 -0
  270. package/src/ui/inputs/number-inputs/QuaternionInput.tsx +27 -0
  271. package/src/ui/inputs/number-inputs/RadInput.tsx +26 -0
  272. package/src/ui/inputs/number-inputs/TimeInput.tsx +26 -0
  273. package/src/ui/inputs/number-inputs/Vec3Input.tsx +26 -0
  274. package/src/ui/inputs/number-inputs/VectorInput.tsx +38 -0
  275. package/src/ui/inputs/number-inputs/index.ts +38 -0
  276. package/src/ui/inputs/presets-input.tsx +59 -0
  277. package/src/ui/label.tsx +35 -0
  278. package/src/ui/layout.tsx +43 -0
  279. package/src/ui/layouts/dock-layout/DockLayoutView.tsx +128 -0
  280. package/src/ui/layouts/dock-layout/LayoutSlots.tsx +85 -0
  281. package/src/ui/layouts/dock-layout/index.tsx +2 -0
  282. package/src/ui/layouts/index.ts +2 -0
  283. package/src/ui/layouts/liquid-layout/LayoutSlots.tsx +82 -0
  284. package/src/ui/layouts/liquid-layout/LiquidLayoutView.tsx +76 -0
  285. package/src/ui/layouts/liquid-layout/index.ts +1 -0
  286. package/src/ui/modal.tsx +211 -0
  287. package/src/ui/navigation.tsx +86 -0
  288. package/src/ui/pagination.tsx +129 -0
  289. package/src/ui/panel.tsx +146 -0
  290. package/src/ui/popover.tsx +112 -0
  291. package/src/ui/radio-group.tsx +63 -0
  292. package/src/ui/resizable.tsx +52 -0
  293. package/src/ui/select.tsx +365 -0
  294. package/src/ui/separator.tsx +26 -0
  295. package/src/ui/sheet.tsx +257 -0
  296. package/src/ui/sidebar.tsx +695 -0
  297. package/src/ui/simple-tree-view.tsx +604 -0
  298. package/src/ui/skeleton.tsx +15 -0
  299. package/src/ui/slider.tsx +204 -0
  300. package/src/ui/switch.tsx +45 -0
  301. package/src/ui/table.tsx +99 -0
  302. package/src/ui/tabs.tsx +192 -0
  303. package/src/ui/textarea.tsx +55 -0
  304. package/src/ui/theme/ThemeProvider.tsx +319 -0
  305. package/src/ui/theme/ThemeToggles.tsx +84 -0
  306. package/src/ui/theme/index.ts +21 -0
  307. package/src/ui/theme/themeScript.tsx +179 -0
  308. package/src/ui/theme/types.ts +61 -0
  309. package/src/ui/toast.tsx +30 -0
  310. package/src/ui/toggle-buttons.tsx +290 -0
  311. package/src/ui/toggle-group.tsx +236 -0
  312. package/src/ui/toggle.tsx +94 -0
  313. package/src/ui/toolbar.tsx +54 -0
  314. package/src/ui/tooltip.tsx +171 -0
  315. package/src/ui/tree-view/TreeSearchBar.tsx +88 -0
  316. package/src/ui/tree-view/TreeView.tsx +232 -0
  317. package/src/ui/tree-view/hooks.tsx +289 -0
  318. package/src/ui/tree-view/index.ts +4 -0
  319. package/src/ui/tree-view/types.ts +23 -0
  320. package/src/ui/tree-view-legacy.tsx +644 -0
  321. package/src/ui/version-badge.tsx +0 -0
  322. package/src/ui/waterfall/CursorOverlay.tsx +96 -0
  323. package/src/ui/waterfall/NavigationControls.tsx +42 -0
  324. package/src/ui/waterfall/Tick.tsx +51 -0
  325. package/src/ui/waterfall/TimeRuleEventDot.tsx +19 -0
  326. package/src/ui/waterfall/TimelineEvent.tsx +60 -0
  327. package/src/ui/waterfall/TimelineProcessBar.tsx +207 -0
  328. package/src/ui/waterfall/Wedges.tsx +67 -0
  329. package/src/ui/waterfall/WheelZoomContext.tsx +128 -0
  330. package/src/ui/waterfall/hooks/useTimelineState.tsx +134 -0
  331. package/src/ui/waterfall/hooks/useViewport.tsx +293 -0
  332. package/src/ui/waterfall/index.tsx +326 -0
  333. package/src/ui/waterfall/types.ts +20 -0
  334. package/src/ui/waterfall/utils.ts +91 -0
  335. package/dist/chunk-W2YAQV5N.mjs +0 -57
  336. package/dist/{chunk-QLCEHV4Q.mjs → chunk-2OZK5DY5.mjs} +2 -2
  337. package/dist/{chunk-Z35DNFRZ.cjs → chunk-7IS37C3P.cjs} +2 -2
  338. package/dist/{chunk-4AOAH77D.mjs → chunk-A2PCEL5S.mjs} +2 -2
  339. package/dist/{chunk-K4VD5PPY.mjs → chunk-BIUDC66P.mjs} +1 -1
  340. package/dist/{chunk-RKJR6RZU.cjs → chunk-OYNLQTHW.cjs} +1 -1
  341. package/dist/{chunk-VE3XLQLO.cjs → chunk-QUFZWF4E.cjs} +2 -2
@@ -0,0 +1,134 @@
1
+ import { useState, useMemo } from "react";
2
+
3
+ import { LogItemType, LogItemWithMeta } from "../types";
4
+
5
+ /**
6
+ * Custom hook for managing timeline waterfall state including item expansion, hover interactions, and hierarchical data processing.
7
+ *
8
+ * This hook processes log data into a hierarchical structure with parent-child relationships, manages which items
9
+ * are expanded/collapsed, tracks hover states for visual feedback, and provides utilities for filtering visible items.
10
+ *
11
+ * @param logData - Array of log items representing timeline events. Each item can have:
12
+ * - Basic properties: id, parentId, label, type, color, icon
13
+ * - Timing data: time, createTime, startTime, duration
14
+ * - Hierarchy: parentId (null for root items), isCollapsible flag
15
+ * - Display: indent level for visual nesting
16
+ *
17
+ * @example
18
+ * ```typescript
19
+ * const logData = [
20
+ * {
21
+ * id: "1",
22
+ * parentId: null,
23
+ * type: "task",
24
+ * label: "generate-report",
25
+ * startTime: 0,
26
+ * duration: 20,
27
+ * isCollapsible: true
28
+ * },
29
+ * {
30
+ * id: "2",
31
+ * parentId: "1",
32
+ * type: "step",
33
+ * label: "Fetch database records",
34
+ * startTime: 0.5,
35
+ * duration: 3
36
+ * }
37
+ * ];
38
+ *
39
+ * const {
40
+ * expandedItems, // Set of expanded item IDs
41
+ * hoveredId, // Currently hovered item ID
42
+ * toggleItem, // Function to expand/collapse items
43
+ * logDataWithMeta, // Enhanced data with ancestry info
44
+ * getVisibleLogData // Filter function for visible items
45
+ * } = useTimelineState(logData);
46
+ * ```
47
+ *
48
+ * @returns Object containing:
49
+ * - expandedItems: Set of item IDs that are currently expanded
50
+ * - hoveredId: ID of currently hovered item (null if none)
51
+ * - setHoveredId: Function to update hovered item
52
+ * - toggleItem: Function to expand/collapse collapsible items
53
+ * - logDataWithMeta: Enhanced log data with ancestor chain and position metadata
54
+ * - getVisibleLogData: Function that filters data based on parent expansion state
55
+ */
56
+ export function useTimelineState(logData: LogItemType[]) {
57
+ const [expandedItems, setExpandedItems] = useState(() => {
58
+ const initial = new Set<string>();
59
+ logData.forEach((item) => {
60
+ if (item.isCollapsible) initial.add(item.id);
61
+ });
62
+ return initial;
63
+ });
64
+
65
+ const [hoveredId, setHoveredId] = useState<string | null>(null);
66
+
67
+ const toggleItem = (id: string) => {
68
+ setExpandedItems((prev) => {
69
+ const newSet = new Set(prev);
70
+ if (newSet.has(id)) {
71
+ newSet.delete(id);
72
+ } else {
73
+ newSet.add(id);
74
+ }
75
+ return newSet;
76
+ });
77
+ };
78
+
79
+ const childrenMap = useMemo(() => {
80
+ const map = new Map<string | null, LogItemType[]>();
81
+ logData.forEach((item) => {
82
+ if (!map.has(item.parentId)) {
83
+ map.set(item.parentId, []);
84
+ }
85
+ map.get(item.parentId)!.push(item);
86
+ });
87
+ return map;
88
+ }, [logData]);
89
+
90
+ const logDataWithMeta = useMemo(() => {
91
+ const dataMap = new Map(logData.map((item) => [item.id, item]));
92
+
93
+ const getAncestors = (item: LogItemType) => {
94
+ const ancestors: LogItemType[] = [];
95
+ let current = item.parentId;
96
+ while (current) {
97
+ const parent = dataMap.get(current);
98
+ if (parent) {
99
+ ancestors.unshift(parent);
100
+ current = parent.parentId;
101
+ } else {
102
+ break;
103
+ }
104
+ }
105
+ return ancestors;
106
+ };
107
+
108
+ return logData.map((item) => {
109
+ const siblings = childrenMap.get(item.parentId) || [];
110
+ const isLast = siblings.length > 0 && siblings[siblings.length - 1].id === item.id;
111
+ const ancestors = getAncestors(item);
112
+ return { ...item, isLast, ancestors };
113
+ });
114
+ }, [childrenMap, logData]);
115
+
116
+ const isVisible = (item: { ancestors: LogItemType[] }) => {
117
+ return item.ancestors.every(
118
+ (ancestor) => !ancestor.isCollapsible || expandedItems.has(ancestor.id),
119
+ );
120
+ };
121
+
122
+ const getVisibleLogData = (filteredData: LogItemWithMeta[]) => {
123
+ return filteredData.filter(isVisible);
124
+ };
125
+
126
+ return {
127
+ expandedItems,
128
+ hoveredId,
129
+ setHoveredId,
130
+ toggleItem,
131
+ logDataWithMeta,
132
+ getVisibleLogData,
133
+ };
134
+ }
@@ -0,0 +1,293 @@
1
+ import { useState, useMemo, useRef, useCallback, useEffect } from "react";
2
+
3
+ import { LogItemWithMeta } from "../types";
4
+ import { formatDuration, TOTAL_DURATION } from "../utils";
5
+
6
+ interface UseViewportProps {
7
+ visibleLogData: LogItemWithMeta[];
8
+ onTemporalCursorChange?: (time: number) => void;
9
+ temporalCursor?: number;
10
+ }
11
+
12
+ export function useViewport({
13
+ visibleLogData,
14
+ onTemporalCursorChange,
15
+ temporalCursor,
16
+ }: UseViewportProps) {
17
+ const [viewStart, setViewStart] = useState(-TOTAL_DURATION * 0.25);
18
+ const [viewDuration, setViewDuration] = useState(TOTAL_DURATION * 1.5);
19
+ const [isDragging, setIsDragging] = useState(false);
20
+ const [internalTemporalCursor, setInternalTemporalCursor] = useState<number | null>(null);
21
+
22
+ const timelineContainerRef = useRef<HTMLDivElement>(null);
23
+ const lastClientX = useRef(0);
24
+ const isMouseOver = useRef(false);
25
+ const animationFrameRef = useRef<number | null>(null);
26
+
27
+ // Cursor state
28
+ const [cursorVisible, setCursorVisible] = useState(false);
29
+ const [cursorPosition, setCursorPosition] = useState(0);
30
+ const [cursorLabel, setCursorLabel] = useState("");
31
+ const [showMagnet, setShowMagnet] = useState(false);
32
+
33
+ const activeTemporalCursor = temporalCursor ?? internalTemporalCursor;
34
+
35
+ const timeToPercent = useCallback(
36
+ (time: number) => ((time - viewStart) / viewDuration) * 100,
37
+ [viewStart, viewDuration],
38
+ );
39
+
40
+ const ticks = useMemo(() => {
41
+ const markers: { time: number; label: string }[] = [];
42
+ const niceIntervals = [
43
+ 0.001, 0.002, 0.005, 0.01, 0.02, 0.05, 0.1, 0.2, 0.5, 1, 2, 5, 10, 20, 50, 100,
44
+ ];
45
+ const targetMarkerCount = 10;
46
+ const rawInterval = viewDuration / targetMarkerCount;
47
+ const interval =
48
+ niceIntervals.find((i) => i > rawInterval) || niceIntervals[niceIntervals.length - 1];
49
+
50
+ const viewEnd = viewStart + viewDuration;
51
+ const shownSeconds = new Set<number>();
52
+
53
+ const formatTickLabel = (seconds: number) => {
54
+ const sign = seconds < 0 ? "-" : "";
55
+ const absSeconds = Math.abs(seconds);
56
+
57
+ let s = Math.floor(absSeconds);
58
+ let ms = Math.round((absSeconds - s) * 1000);
59
+
60
+ if (ms >= 1000) {
61
+ s += 1;
62
+ ms -= 1000;
63
+ }
64
+
65
+ if (absSeconds < 1 && absSeconds > -1) {
66
+ return `${sign}${ms}ms`;
67
+ }
68
+
69
+ const baseSecond = s * (sign === "-" ? -1 : 1);
70
+ if (shownSeconds.has(baseSecond)) {
71
+ return ms > 0 ? `+${ms}ms` : "";
72
+ }
73
+
74
+ shownSeconds.add(baseSecond);
75
+ if (ms === 0) {
76
+ return `${sign}${s}s`;
77
+ }
78
+
79
+ return `${sign}${s}s`;
80
+ };
81
+
82
+ const firstMarkerTime = Math.floor(viewStart / interval) * interval;
83
+ const lastMarkerTime = Math.ceil(viewEnd / interval) * interval;
84
+
85
+ for (let time = firstMarkerTime; time <= lastMarkerTime; time += interval) {
86
+ const roundedTime = Number.parseFloat(time.toPrecision(15));
87
+ const label = formatTickLabel(roundedTime);
88
+ if (label) {
89
+ markers.push({ time: roundedTime, label });
90
+ }
91
+ }
92
+ return markers;
93
+ }, [viewStart, viewDuration]);
94
+
95
+ const eventDots = useMemo(() => {
96
+ const events: { time: number; type: string }[] = [];
97
+ const timeSet = new Set<string>();
98
+
99
+ const addEvent = (time: number, type: string) => {
100
+ const key = `${time.toFixed(6)}-${type}`;
101
+ if (!timeSet.has(key)) {
102
+ events.push({ time, type });
103
+ timeSet.add(key);
104
+ }
105
+ };
106
+
107
+ visibleLogData.forEach((item) => {
108
+ if (item.createTime !== undefined) addEvent(item.createTime, "create");
109
+ if (item.startTime !== undefined) addEvent(item.startTime, "start");
110
+ if (item.startTime !== undefined && item.duration !== undefined) {
111
+ addEvent(item.startTime + item.duration, "end");
112
+ }
113
+ if (item.time !== undefined) addEvent(item.time, "event");
114
+ });
115
+ return events.sort((a, b) => a.time - b.time);
116
+ }, [visibleLogData]);
117
+
118
+ const updateCursor = useCallback(() => {
119
+ if (!isMouseOver.current) return;
120
+
121
+ const timelineEl = timelineContainerRef.current;
122
+ if (!timelineEl) return;
123
+
124
+ const rect = timelineEl.getBoundingClientRect();
125
+ const cursorX = lastClientX.current - rect.left;
126
+ const rawHoverTime = viewStart + (cursorX / timelineEl.offsetWidth) * viewDuration;
127
+
128
+ // Snapping logic
129
+ const snapThresholdInPixels = 8;
130
+ const snapThresholdInTime = (snapThresholdInPixels / timelineEl.offsetWidth) * viewDuration;
131
+
132
+ let closestSnap: { time: number; type: string } | null = null;
133
+ let minDistance = Number.POSITIVE_INFINITY;
134
+
135
+ for (const event of eventDots) {
136
+ const distance = Math.abs(event.time - rawHoverTime);
137
+ if (distance < minDistance && distance < snapThresholdInTime) {
138
+ minDistance = distance;
139
+ closestSnap = event;
140
+ }
141
+ }
142
+
143
+ const displayTime = closestSnap?.time ?? rawHoverTime;
144
+ const percent = timeToPercent(displayTime);
145
+
146
+ setCursorPosition(percent);
147
+ setCursorLabel(formatDuration(displayTime));
148
+ setShowMagnet(!!closestSnap);
149
+ setCursorVisible(true);
150
+ }, [viewStart, viewDuration, eventDots, timeToPercent]);
151
+
152
+ const handlePan = (direction: "left" | "right") => {
153
+ const panAmount = viewDuration * 0.1;
154
+ if (direction === "left") {
155
+ setViewStart((s) => s - panAmount);
156
+ } else {
157
+ setViewStart((s) => s + panAmount);
158
+ }
159
+ };
160
+
161
+ const handleZoomDragStart = (e: React.MouseEvent) => {
162
+ e.preventDefault();
163
+ e.stopPropagation();
164
+
165
+ const startX = e.clientX;
166
+ const startDuration = viewDuration;
167
+ const centerTime = viewStart + viewDuration / 2;
168
+ let isDraggingLocal = true;
169
+
170
+ setIsDragging(true);
171
+
172
+ const handleMouseMove = (e: MouseEvent) => {
173
+ if (!isDraggingLocal) return;
174
+
175
+ const deltaX = e.clientX - startX;
176
+ const sensitivity = 0.05;
177
+ const zoomFactor = Math.pow(1.1, deltaX * sensitivity);
178
+ const newDuration = startDuration * zoomFactor;
179
+
180
+ const minDuration = 0.01;
181
+ const maxDuration = TOTAL_DURATION * 10;
182
+
183
+ if (newDuration >= minDuration && newDuration <= maxDuration) {
184
+ const newViewStart = centerTime - newDuration / 2;
185
+ setViewDuration(newDuration);
186
+ setViewStart(newViewStart);
187
+ }
188
+ };
189
+
190
+ const handleMouseUp = () => {
191
+ isDraggingLocal = false;
192
+ setIsDragging(false);
193
+ document.removeEventListener("mousemove", handleMouseMove);
194
+ document.removeEventListener("mouseup", handleMouseUp);
195
+ };
196
+
197
+ document.addEventListener("mousemove", handleMouseMove);
198
+ document.addEventListener("mouseup", handleMouseUp);
199
+ };
200
+
201
+ const handleTimelineClick = (e: React.MouseEvent) => {
202
+ const container = timelineContainerRef.current;
203
+ if (!container) return;
204
+
205
+ const rect = container.getBoundingClientRect();
206
+ const clickX = e.clientX - rect.left;
207
+ const clickedTime = viewStart + (clickX / container.offsetWidth) * viewDuration;
208
+
209
+ // Snap to nearest key event if close enough
210
+ const snapThresholdInPixels = 8;
211
+ const snapThresholdInTime = (snapThresholdInPixels / container.offsetWidth) * viewDuration;
212
+
213
+ let closestSnap: { time: number; type: string } | null = null;
214
+ let minDistance = Number.POSITIVE_INFINITY;
215
+
216
+ for (const event of eventDots) {
217
+ const distance = Math.abs(event.time - clickedTime);
218
+ if (distance < minDistance && distance < snapThresholdInTime) {
219
+ minDistance = distance;
220
+ closestSnap = event;
221
+ }
222
+ }
223
+
224
+ const finalTime = closestSnap?.time ?? clickedTime;
225
+
226
+ if (onTemporalCursorChange) {
227
+ onTemporalCursorChange(finalTime);
228
+ } else {
229
+ setInternalTemporalCursor(finalTime);
230
+ }
231
+ };
232
+
233
+ useEffect(() => {
234
+ const timelineEl = timelineContainerRef.current;
235
+ if (!timelineEl) return;
236
+
237
+ const handleMouseMove = (e: MouseEvent) => {
238
+ if (isDragging) return;
239
+
240
+ lastClientX.current = e.clientX;
241
+ if (animationFrameRef.current) cancelAnimationFrame(animationFrameRef.current);
242
+ animationFrameRef.current = requestAnimationFrame(updateCursor);
243
+ };
244
+
245
+ const handleMouseEnter = () => {
246
+ isMouseOver.current = true;
247
+ };
248
+
249
+ const handleMouseLeave = () => {
250
+ isMouseOver.current = false;
251
+ if (animationFrameRef.current) cancelAnimationFrame(animationFrameRef.current);
252
+ setCursorVisible(false);
253
+ };
254
+
255
+ timelineEl.addEventListener("mousemove", handleMouseMove);
256
+ timelineEl.addEventListener("mouseenter", handleMouseEnter);
257
+ timelineEl.addEventListener("mouseleave", handleMouseLeave);
258
+
259
+ return () => {
260
+ timelineEl.removeEventListener("mousemove", handleMouseMove);
261
+ timelineEl.removeEventListener("mouseenter", handleMouseEnter);
262
+ timelineEl.removeEventListener("mouseleave", handleMouseLeave);
263
+ if (animationFrameRef.current) cancelAnimationFrame(animationFrameRef.current);
264
+ };
265
+ }, [updateCursor, isDragging]);
266
+
267
+ useEffect(() => {
268
+ if (isMouseOver.current) {
269
+ if (animationFrameRef.current) cancelAnimationFrame(animationFrameRef.current);
270
+ animationFrameRef.current = requestAnimationFrame(updateCursor);
271
+ }
272
+ }, [viewStart, viewDuration, updateCursor]);
273
+
274
+ return {
275
+ viewStart,
276
+ viewDuration,
277
+ timelineContainerRef,
278
+ activeTemporalCursor,
279
+ timeToPercent,
280
+ ticks,
281
+ eventDots,
282
+ handlePan,
283
+ handleZoomDragStart,
284
+ handleTimelineClick,
285
+ setViewStart,
286
+ setViewDuration,
287
+ // Cursor state
288
+ cursorVisible,
289
+ cursorPosition,
290
+ cursorLabel,
291
+ showMagnet,
292
+ };
293
+ }