@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,361 @@
1
+ import { Slot } from "@radix-ui/react-slot";
2
+ import React, { type HTMLProps, RefObject, useEffect, useRef, useState } from "react";
3
+
4
+ import { useScrollSlave, useSyncDrag, useSyncScroll } from "./useSyncScroll";
5
+ import { cn } from "../hooks";
6
+
7
+ export type SyncScrollProps = HTMLProps<HTMLDivElement> & {
8
+ asChild?: boolean;
9
+ };
10
+
11
+ export const SyncScroll = ({ children, className, asChild = false, ...props }: SyncScrollProps) => {
12
+ const Comp = asChild ? Slot : "div";
13
+ return (
14
+ <Comp ref={useSyncScroll()} className={cn("overflow-y-auto", className)} {...props}>
15
+ {children}
16
+ </Comp>
17
+ );
18
+ };
19
+
20
+ export const SyncScrollSlave = ({
21
+ children,
22
+ className,
23
+ asChild = false,
24
+ ...props
25
+ }: SyncScrollProps) => {
26
+ const Comp = asChild ? Slot : "div";
27
+ return (
28
+ <Comp ref={useScrollSlave()} className={cn("overflow-y-auto", className)} {...props}>
29
+ {children}
30
+ </Comp>
31
+ );
32
+ };
33
+
34
+ export const SyncDrag = ({
35
+ ref: extRef,
36
+ children,
37
+ className,
38
+ asChild = false,
39
+ ...props
40
+ }: SyncScrollProps) => {
41
+ const ref = useSyncDrag({ ref: extRef as RefObject<HTMLDivElement>, axis: "both" });
42
+ const [isDragging, setIsDragging] = useState(false);
43
+ const dragStartX = useRef(0);
44
+ const dragStartY = useRef(0);
45
+ const scrollStartLeft = useRef(0);
46
+ const scrollStartTop = useRef(0);
47
+
48
+ useEffect(() => {
49
+ const element = ref.current;
50
+ if (!element) return;
51
+
52
+ const handleMouseDown = (e: MouseEvent) => {
53
+ setIsDragging(true);
54
+ dragStartX.current = e.clientX;
55
+ dragStartY.current = e.clientY;
56
+ scrollStartLeft.current = element.scrollLeft;
57
+ scrollStartTop.current = element.scrollTop;
58
+ e.preventDefault();
59
+ };
60
+
61
+ const handleMouseMove = (e: MouseEvent) => {
62
+ if (!isDragging || !element) return;
63
+ const deltaX = e.clientX - dragStartX.current;
64
+ const deltaY = e.clientY - dragStartY.current;
65
+ element.scrollLeft = scrollStartLeft.current - deltaX * 2;
66
+ element.scrollTop = scrollStartTop.current - deltaY * 2;
67
+ };
68
+
69
+ const handleMouseUp = () => {
70
+ setIsDragging(false);
71
+ };
72
+
73
+ element.addEventListener("mousedown", handleMouseDown);
74
+
75
+ if (isDragging) {
76
+ document.addEventListener("mousemove", handleMouseMove);
77
+ document.addEventListener("mouseup", handleMouseUp);
78
+ document.addEventListener("mouseleave", handleMouseUp);
79
+ }
80
+
81
+ return () => {
82
+ element.removeEventListener("mousedown", handleMouseDown);
83
+ document.removeEventListener("mousemove", handleMouseMove);
84
+ document.removeEventListener("mouseup", handleMouseUp);
85
+ document.removeEventListener("mouseleave", handleMouseUp);
86
+ };
87
+ }, [isDragging, ref]);
88
+
89
+ const Comp = asChild ? Slot : "div";
90
+
91
+ return (
92
+ <Comp
93
+ ref={ref}
94
+ className={cn("cursor-grab overflow-auto", isDragging && "cursor-grabbing", className)}
95
+ style={{ userSelect: isDragging ? "none" : "auto", ...props.style }}
96
+ {...props}
97
+ >
98
+ {children}
99
+ </Comp>
100
+ );
101
+ };
102
+
103
+ export const SyncDragX = ({ children, className, asChild = false, ...props }: SyncScrollProps) => {
104
+ const ref = useSyncDrag({ axis: "horizontal" });
105
+ const [isDragging, setIsDragging] = useState(false);
106
+ const dragStartX = useRef(0);
107
+ const scrollStartLeft = useRef(0);
108
+
109
+ useEffect(() => {
110
+ const element = ref.current;
111
+ if (!element) return;
112
+
113
+ const handleMouseDown = (e: MouseEvent) => {
114
+ setIsDragging(true);
115
+ dragStartX.current = e.clientX;
116
+ scrollStartLeft.current = element.scrollLeft;
117
+ e.preventDefault();
118
+ };
119
+
120
+ const handleMouseMove = (e: MouseEvent) => {
121
+ if (!isDragging || !element) return;
122
+ const deltaX = e.clientX - dragStartX.current;
123
+ element.scrollLeft = scrollStartLeft.current - deltaX * 2;
124
+ };
125
+
126
+ const handleMouseUp = () => {
127
+ setIsDragging(false);
128
+ };
129
+
130
+ element.addEventListener("mousedown", handleMouseDown);
131
+
132
+ if (isDragging) {
133
+ document.addEventListener("mousemove", handleMouseMove);
134
+ document.addEventListener("mouseup", handleMouseUp);
135
+ document.addEventListener("mouseleave", handleMouseUp);
136
+ }
137
+
138
+ return () => {
139
+ element.removeEventListener("mousedown", handleMouseDown);
140
+ document.removeEventListener("mousemove", handleMouseMove);
141
+ document.removeEventListener("mouseup", handleMouseUp);
142
+ document.removeEventListener("mouseleave", handleMouseUp);
143
+ };
144
+ }, [isDragging, ref]);
145
+
146
+ const Comp = asChild ? Slot : "div";
147
+
148
+ return (
149
+ <Comp
150
+ ref={ref}
151
+ className={cn("cursor-grab overflow-x-auto", isDragging ? "cursor-grabbing" : "", className)}
152
+ style={{ userSelect: isDragging ? "none" : "auto", ...props.style }}
153
+ {...props}
154
+ >
155
+ {children}
156
+ </Comp>
157
+ );
158
+ };
159
+
160
+ export const SyncDragSlave = ({
161
+ ref: extRef,
162
+ children,
163
+ className,
164
+ asChild = false,
165
+ ...props
166
+ }: SyncScrollProps) => {
167
+ const ref = useScrollSlave({ ref: extRef as RefObject<HTMLDivElement>, axis: "both" });
168
+ const [isDragging, setIsDragging] = useState(false);
169
+ const dragStartX = useRef(0);
170
+ const dragStartY = useRef(0);
171
+ const scrollStartLeft = useRef(0);
172
+ const scrollStartTop = useRef(0);
173
+
174
+ useEffect(() => {
175
+ const element = ref.current;
176
+ if (!element) return;
177
+
178
+ const handleMouseDown = (e: MouseEvent) => {
179
+ setIsDragging(true);
180
+ dragStartX.current = e.clientX;
181
+ dragStartY.current = e.clientY;
182
+ scrollStartLeft.current = element.scrollLeft;
183
+ scrollStartTop.current = element.scrollTop;
184
+ e.preventDefault();
185
+ };
186
+
187
+ const handleMouseMove = (e: MouseEvent) => {
188
+ if (!isDragging || !element) return;
189
+ const deltaX = e.clientX - dragStartX.current;
190
+ const deltaY = e.clientY - dragStartY.current;
191
+ element.scrollLeft = scrollStartLeft.current - deltaX * 2;
192
+ element.scrollTop = scrollStartTop.current - deltaY * 2;
193
+ };
194
+
195
+ const handleMouseUp = () => {
196
+ setIsDragging(false);
197
+ };
198
+
199
+ element.addEventListener("mousedown", handleMouseDown);
200
+
201
+ if (isDragging) {
202
+ document.addEventListener("mousemove", handleMouseMove);
203
+ document.addEventListener("mouseup", handleMouseUp);
204
+ document.addEventListener("mouseleave", handleMouseUp);
205
+ }
206
+
207
+ return () => {
208
+ element.removeEventListener("mousedown", handleMouseDown);
209
+ document.removeEventListener("mousemove", handleMouseMove);
210
+ document.removeEventListener("mouseup", handleMouseUp);
211
+ document.removeEventListener("mouseleave", handleMouseUp);
212
+ };
213
+ }, [isDragging, ref]);
214
+
215
+ const Comp = asChild ? Slot : "div";
216
+
217
+ return (
218
+ <Comp
219
+ ref={ref}
220
+ className={cn("cursor-grab overflow-auto", isDragging && "cursor-grabbing", className)}
221
+ style={{ userSelect: isDragging ? "none" : "auto", ...props.style }}
222
+ {...props}
223
+ >
224
+ {children}
225
+ </Comp>
226
+ );
227
+ };
228
+
229
+ export const SyncDragSlaveX = ({
230
+ ref: extRef,
231
+ children,
232
+ className,
233
+ asChild = false,
234
+ ...props
235
+ }: SyncScrollProps) => {
236
+ const ref = useScrollSlave({ ref: extRef as RefObject<HTMLDivElement>, axis: "horizontal" });
237
+ const [isDragging, setIsDragging] = useState(false);
238
+ const dragStartX = useRef(0);
239
+ const scrollStartLeft = useRef(0);
240
+
241
+ useEffect(() => {
242
+ const element = ref.current;
243
+ if (!element) return;
244
+
245
+ const handleMouseDown = (e: MouseEvent) => {
246
+ setIsDragging(true);
247
+ dragStartX.current = e.clientX;
248
+ scrollStartLeft.current = element.scrollLeft;
249
+ e.preventDefault();
250
+ };
251
+
252
+ const handleMouseMove = (e: MouseEvent) => {
253
+ if (!isDragging || !element) return;
254
+ const deltaX = e.clientX - dragStartX.current;
255
+ element.scrollLeft = scrollStartLeft.current - deltaX * 2;
256
+ };
257
+
258
+ const handleMouseUp = () => {
259
+ setIsDragging(false);
260
+ };
261
+
262
+ element.addEventListener("mousedown", handleMouseDown);
263
+
264
+ if (isDragging) {
265
+ document.addEventListener("mousemove", handleMouseMove);
266
+ document.addEventListener("mouseup", handleMouseUp);
267
+ document.addEventListener("mouseleave", handleMouseUp);
268
+ }
269
+
270
+ return () => {
271
+ element.removeEventListener("mousedown", handleMouseDown);
272
+ document.removeEventListener("mousemove", handleMouseMove);
273
+ document.removeEventListener("mouseup", handleMouseUp);
274
+ document.removeEventListener("mouseleave", handleMouseUp);
275
+ };
276
+ }, [isDragging, ref]);
277
+
278
+ const Comp = asChild ? Slot : "div";
279
+
280
+ return (
281
+ <Comp
282
+ ref={ref}
283
+ className={cn(
284
+ "cursor-grab overflow-x-auto overflow-y-hidden",
285
+ isDragging && "cursor-grabbing",
286
+ className,
287
+ )}
288
+ style={{ userSelect: isDragging ? "none" : "auto", ...props.style }}
289
+ {...props}
290
+ >
291
+ {children}
292
+ </Comp>
293
+ );
294
+ };
295
+
296
+ export const SyncDragY = ({
297
+ ref: extRef,
298
+ children,
299
+ className,
300
+ asChild = false,
301
+ ...props
302
+ }: SyncScrollProps) => {
303
+ const ref = useSyncDrag({ ref: extRef as RefObject<HTMLDivElement>, axis: "vertical" });
304
+ const [isDragging, setIsDragging] = useState(false);
305
+ const dragStartY = useRef(0);
306
+ const scrollStartTop = useRef(0);
307
+
308
+ useEffect(() => {
309
+ const element = ref.current;
310
+ if (!element) return;
311
+
312
+ const handleMouseDown = (e: MouseEvent) => {
313
+ setIsDragging(true);
314
+ dragStartY.current = e.clientY;
315
+ scrollStartTop.current = element.scrollTop;
316
+ e.preventDefault();
317
+ };
318
+
319
+ const handleMouseMove = (e: MouseEvent) => {
320
+ if (!isDragging || !element) return;
321
+ const deltaY = e.clientY - dragStartY.current;
322
+ element.scrollTop = scrollStartTop.current - deltaY * 2;
323
+ };
324
+
325
+ const handleMouseUp = () => {
326
+ setIsDragging(false);
327
+ };
328
+
329
+ element.addEventListener("mousedown", handleMouseDown);
330
+
331
+ if (isDragging) {
332
+ document.addEventListener("mousemove", handleMouseMove);
333
+ document.addEventListener("mouseup", handleMouseUp);
334
+ document.addEventListener("mouseleave", handleMouseUp);
335
+ }
336
+
337
+ return () => {
338
+ element.removeEventListener("mousedown", handleMouseDown);
339
+ document.removeEventListener("mousemove", handleMouseMove);
340
+ document.removeEventListener("mouseup", handleMouseUp);
341
+ document.removeEventListener("mouseleave", handleMouseUp);
342
+ };
343
+ }, [isDragging, ref]);
344
+
345
+ const Comp = asChild ? Slot : "div";
346
+
347
+ return (
348
+ <Comp
349
+ ref={ref}
350
+ className={cn(
351
+ "cursor-grab overflow-x-hidden overflow-y-auto",
352
+ isDragging && "cursor-grabbing",
353
+ className,
354
+ )}
355
+ style={{ userSelect: isDragging ? "none" : "auto", ...props.style }}
356
+ {...props}
357
+ >
358
+ {children}
359
+ </Comp>
360
+ );
361
+ };
@@ -0,0 +1,22 @@
1
+ // Main exports for the SyncScroll module
2
+ export {
3
+ SyncScrollProvider,
4
+ // New primary API
5
+ useSyncScroll,
6
+ useScrollSlave,
7
+ useSyncDrag,
8
+ } from "./useSyncScroll";
9
+
10
+ // Component exports
11
+ export {
12
+ SyncScroll,
13
+ SyncScrollSlave,
14
+ SyncDrag,
15
+ SyncDragX,
16
+ SyncDragY,
17
+ SyncDragSlave,
18
+ SyncDragSlaveX,
19
+ } from "./SyncScroll";
20
+
21
+ export type { SyncScrollProps } from "./SyncScroll";
22
+ export type { UseSyncScrollOptions, BaseScrollOptions, ScrollRole } from "./useSyncScroll";
@@ -0,0 +1,226 @@
1
+ import React, {
2
+ createContext,
3
+ type RefObject,
4
+ useCallback,
5
+ useContext,
6
+ useEffect,
7
+ useId,
8
+ useRef,
9
+ } from "react";
10
+
11
+ type ScrollRole = "master" | "slave" | "drag";
12
+
13
+ interface ScrollableElement {
14
+ ref: RefObject<HTMLElement>;
15
+ role: ScrollRole;
16
+ }
17
+
18
+ interface SyncScrollContextValue {
19
+ registerScrollable: (id: string, ref: RefObject<HTMLElement>, role: ScrollRole) => void;
20
+ unregisterScrollable: (id: string) => void;
21
+ syncScroll: (sourceId: string, scrollTop: number, scrollLeft: number) => void;
22
+ }
23
+
24
+ const SyncScrollContext = createContext<SyncScrollContextValue | null>(null);
25
+
26
+ interface SyncScrollProviderProps {
27
+ children: React.ReactNode;
28
+ }
29
+
30
+ /**
31
+ * Provider component that manages synchronized scrolling between multiple elements.
32
+ * Supports master-slave relationships where masters can control slaves but not vice versa.
33
+ */
34
+ export function SyncScrollProvider({ children }: SyncScrollProviderProps) {
35
+ const scrollablesRef = useRef<Map<string, ScrollableElement>>(new Map());
36
+ const syncingRef = useRef<Set<string>>(new Set());
37
+
38
+ const registerScrollable = useCallback(
39
+ (id: string, ref: RefObject<HTMLElement>, role: ScrollRole) => {
40
+ scrollablesRef.current.set(id, { ref, role });
41
+ },
42
+ [],
43
+ );
44
+
45
+ const unregisterScrollable = useCallback((id: string) => {
46
+ scrollablesRef.current.delete(id);
47
+ syncingRef.current.delete(id);
48
+ }, []);
49
+
50
+ const syncScroll = useCallback((sourceId: string, scrollTop: number, scrollLeft: number) => {
51
+ // Prevent circular syncing
52
+ if (syncingRef.current.has(sourceId)) {
53
+ return;
54
+ }
55
+
56
+ const sourceElement = scrollablesRef.current.get(sourceId);
57
+ if (!sourceElement) return;
58
+
59
+ // Determine which elements should be synced based on source role
60
+ scrollablesRef.current.forEach((element, id) => {
61
+ if (id === sourceId) return;
62
+
63
+ // Master and drag can sync everyone
64
+ if (sourceElement.role === "master" || sourceElement.role === "drag") {
65
+ syncingRef.current.add(id);
66
+ if (element.ref.current) {
67
+ element.ref.current.scrollTop = scrollTop;
68
+ element.ref.current.scrollLeft = scrollLeft;
69
+ }
70
+ }
71
+ // // Slave can only sync other slaves (not masters or drag)
72
+ // else if (sourceElement.role === "slave" && element.role === "slave") {
73
+ // syncingRef.current.add(id);
74
+ // if (element.ref.current) {
75
+ // element.ref.current.scrollTop = scrollTop;
76
+ // element.ref.current.scrollLeft = scrollLeft;
77
+ // }
78
+ // }
79
+ });
80
+
81
+ // Clear syncing flags after a short delay
82
+ requestAnimationFrame(() => {
83
+ syncingRef.current.clear();
84
+ });
85
+ }, []);
86
+
87
+ return (
88
+ <SyncScrollContext.Provider value={{ registerScrollable, unregisterScrollable, syncScroll }}>
89
+ {children}
90
+ </SyncScrollContext.Provider>
91
+ );
92
+ }
93
+
94
+ interface BaseScrollOptions {
95
+ /** Whether to enable scroll syncing (default: true) */
96
+ enabled?: boolean;
97
+ /** Axis to sync: 'vertical', 'horizontal', or 'both' (default: 'vertical') */
98
+ axis?: "vertical" | "horizontal" | "both";
99
+ /** Optional ref to use instead of creating a new one */
100
+ ref?: RefObject<HTMLDivElement>;
101
+ }
102
+
103
+ interface UseSyncScrollOptions extends BaseScrollOptions {
104
+ /** Unique identifier for this scrollable element */
105
+ id?: string;
106
+ /** Role: 'master' (controls others), 'slave' (controlled by master), 'drag' (like master but for drag interactions) */
107
+ role?: ScrollRole;
108
+ }
109
+
110
+ /**
111
+ * Base hook for synchronized scrolling functionality.
112
+ * Internal use - prefer useScroll, useScrollSlave, or useSyncDrag instead.
113
+ */
114
+ function useSyncScrollBase({
115
+ id: providedId,
116
+ role,
117
+ enabled = true,
118
+ axis = "vertical",
119
+ ref: providedRef,
120
+ }: UseSyncScrollOptions & { role: ScrollRole }) {
121
+ const context = useContext(SyncScrollContext);
122
+ const autoId = useId();
123
+ const id = providedId || autoId;
124
+ const internalRef = useRef<HTMLDivElement>(null);
125
+ const elementRef = providedRef || internalRef;
126
+ const lastScrollTop = useRef(0);
127
+ const lastScrollLeft = useRef(0);
128
+
129
+ if (!context) {
130
+ throw new Error("Sync scroll hooks must be used within a SyncScrollProvider");
131
+ }
132
+
133
+ const { registerScrollable, unregisterScrollable, syncScroll } = context;
134
+
135
+ useEffect(() => {
136
+ if (!enabled || !elementRef.current) return;
137
+
138
+ const element = elementRef.current;
139
+
140
+ // Register this element with its role
141
+ registerScrollable(id, elementRef as RefObject<HTMLElement>, role);
142
+
143
+ const handleScroll = (e: Event) => {
144
+ const target = e.target as HTMLElement;
145
+ let hasChanged = false;
146
+
147
+ if (axis === "vertical" || axis === "both") {
148
+ if (target.scrollTop !== lastScrollTop.current) {
149
+ lastScrollTop.current = target.scrollTop;
150
+ hasChanged = true;
151
+ }
152
+ }
153
+
154
+ if (axis === "horizontal" || axis === "both") {
155
+ if (target.scrollLeft !== lastScrollLeft.current) {
156
+ lastScrollLeft.current = target.scrollLeft;
157
+ hasChanged = true;
158
+ }
159
+ }
160
+
161
+ if (hasChanged) {
162
+ syncScroll(id, lastScrollTop.current, lastScrollLeft.current);
163
+ }
164
+ };
165
+
166
+ element.addEventListener("scroll", handleScroll, { passive: true });
167
+
168
+ return () => {
169
+ element.removeEventListener("scroll", handleScroll);
170
+ unregisterScrollable(id);
171
+ };
172
+ }, [id, role, enabled, axis, registerScrollable, unregisterScrollable, syncScroll, elementRef]);
173
+
174
+ return elementRef;
175
+ }
176
+
177
+ /**
178
+ * Hook for a master scroll element that controls all other elements.
179
+ *
180
+ * @example
181
+ * ```tsx
182
+ * function MasterPanel() {
183
+ * const scrollRef = useSyncScroll();
184
+ * return <div ref={scrollRef}>...</div>;
185
+ * }
186
+ * ```
187
+ */
188
+ export function useSyncScroll(options: BaseScrollOptions = {}) {
189
+ return useSyncScrollBase({ ...options, role: "master" });
190
+ }
191
+
192
+ /**
193
+ * Hook for a slave scroll element that is controlled by masters.
194
+ * Slaves can sync with other slaves but cannot control masters.
195
+ *
196
+ * @example
197
+ * ```tsx
198
+ * function SlavePanel() {
199
+ * const scrollRef = useScrollSlave();
200
+ * return <div ref={scrollRef}>...</div>;
201
+ * }
202
+ * ```
203
+ */
204
+ export function useScrollSlave(options: BaseScrollOptions = {}) {
205
+ return useSyncScrollBase({ ...options, role: "slave" });
206
+ }
207
+
208
+ /**
209
+ * Hook for drag-controlled scrolling that acts like a master.
210
+ * Typically used with custom drag handlers.
211
+ *
212
+ * @example
213
+ * ```tsx
214
+ * function DragPanel() {
215
+ * const scrollRef = useSyncDrag();
216
+ * // Add custom drag handling logic
217
+ * return <div ref={scrollRef}>...</div>;
218
+ * }
219
+ * ```
220
+ */
221
+ export function useSyncDrag(options: BaseScrollOptions = {}) {
222
+ return useSyncScrollBase({ ...options, role: "drag" });
223
+ }
224
+
225
+ // Export types
226
+ export type { ScrollRole, UseSyncScrollOptions, BaseScrollOptions };