@mkbabb/glass-ui 0.2.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 (335) hide show
  1. package/README.md +172 -0
  2. package/dist/glass-ui.css +1 -0
  3. package/dist/glass-ui.js +10019 -0
  4. package/dist/index.d.ts +6619 -0
  5. package/package.json +65 -0
  6. package/src/components/custom/aurora/Aurora.vue +34 -0
  7. package/src/components/custom/aurora/composables/color.ts +122 -0
  8. package/src/components/custom/aurora/composables/useAurora.ts +355 -0
  9. package/src/components/custom/aurora/index.ts +8 -0
  10. package/src/components/custom/confirm-dialog/ConfirmDialog.vue +88 -0
  11. package/src/components/custom/confirm-dialog/index.ts +1 -0
  12. package/src/components/custom/controls/DarkModeToggle.vue +96 -0
  13. package/src/components/custom/controls/index.ts +1 -0
  14. package/src/components/custom/dock/DockLayerGroup.vue +21 -0
  15. package/src/components/custom/dock/DockPopover.vue +263 -0
  16. package/src/components/custom/dock/GlassDock.vue +276 -0
  17. package/src/components/custom/dock/composables/index.ts +16 -0
  18. package/src/components/custom/dock/composables/isTeleportedTarget.ts +19 -0
  19. package/src/components/custom/dock/composables/useDockActionBar.ts +33 -0
  20. package/src/components/custom/dock/composables/useDockState.ts +301 -0
  21. package/src/components/custom/dock/composables/useDockTransition.ts +146 -0
  22. package/src/components/custom/dock/composables/useLayerTransition.ts +135 -0
  23. package/src/components/custom/dock/composables/usePopupMutex.ts +83 -0
  24. package/src/components/custom/dock/index.ts +9 -0
  25. package/src/components/custom/expandable-container/ExpandableContainer.vue +64 -0
  26. package/src/components/custom/expandable-container/index.ts +1 -0
  27. package/src/components/custom/glass-panel/GlassPanel.vue +98 -0
  28. package/src/components/custom/glass-panel/index.ts +2 -0
  29. package/src/components/custom/icon-tooltip/IconTooltip.vue +20 -0
  30. package/src/components/custom/icon-tooltip/index.ts +1 -0
  31. package/src/components/custom/index.ts +15 -0
  32. package/src/components/custom/infinite-scroll/InfiniteScroll.vue +55 -0
  33. package/src/components/custom/infinite-scroll/composables/index.ts +2 -0
  34. package/src/components/custom/infinite-scroll/composables/types.ts +23 -0
  35. package/src/components/custom/infinite-scroll/composables/useInfiniteScroll.ts +73 -0
  36. package/src/components/custom/infinite-scroll/index.ts +1 -0
  37. package/src/components/custom/labeled-field/LabeledInput.vue +29 -0
  38. package/src/components/custom/labeled-field/LabeledSelect.vue +59 -0
  39. package/src/components/custom/labeled-field/LabeledSlider.vue +32 -0
  40. package/src/components/custom/labeled-field/LabeledSwitch.vue +27 -0
  41. package/src/components/custom/labeled-field/index.ts +4 -0
  42. package/src/components/custom/metaballs/MetaballCanvas.vue +23 -0
  43. package/src/components/custom/metaballs/index.ts +4 -0
  44. package/src/components/custom/metaballs/shaders.ts +63 -0
  45. package/src/components/custom/metaballs/types.ts +29 -0
  46. package/src/components/custom/metaballs/useMetaballs.ts +252 -0
  47. package/src/components/custom/search/FuzzySearch.vue +589 -0
  48. package/src/components/custom/search/SearchBar.vue +44 -0
  49. package/src/components/custom/search/composables/fuzzySearchIndex.ts +224 -0
  50. package/src/components/custom/search/composables/index.ts +5 -0
  51. package/src/components/custom/search/composables/types.ts +34 -0
  52. package/src/components/custom/search/composables/useFuzzySearch.ts +115 -0
  53. package/src/components/custom/search/index.ts +7 -0
  54. package/src/components/custom/sidebar/ProgressiveSidebar.vue +256 -0
  55. package/src/components/custom/sidebar/composables/index.ts +6 -0
  56. package/src/components/custom/sidebar/composables/useScrollTracker.ts +242 -0
  57. package/src/components/custom/sidebar/composables/useSidebarFollow.ts +247 -0
  58. package/src/components/custom/sidebar/composables/useSidebarState.ts +72 -0
  59. package/src/components/custom/sidebar/composables/useTreeIndex.ts +152 -0
  60. package/src/components/custom/sidebar/index.ts +15 -0
  61. package/src/components/custom/sidebar/types.ts +50 -0
  62. package/src/components/custom/tabs/BouncyTabs.vue +39 -0
  63. package/src/components/custom/tabs/BouncyToggle.vue +352 -0
  64. package/src/components/custom/tabs/UnderlineTabs.vue +115 -0
  65. package/src/components/custom/tabs/index.ts +5 -0
  66. package/src/components/custom/timeline/GlassTimeline.vue +174 -0
  67. package/src/components/custom/timeline/index.ts +1 -0
  68. package/src/components/custom/typewriter/TypewriterText.vue +239 -0
  69. package/src/components/custom/typewriter/composables/index.ts +1 -0
  70. package/src/components/custom/typewriter/composables/useTypewriter.ts +413 -0
  71. package/src/components/custom/typewriter/index.ts +7 -0
  72. package/src/components/custom/typewriter/types.ts +159 -0
  73. package/src/components/custom/typewriter/utils/keyboard.ts +213 -0
  74. package/src/components/custom/typewriter/utils/pausePatterns.ts +55 -0
  75. package/src/components/custom/typewriter/utils/timing.ts +104 -0
  76. package/src/components/custom/typewriter/utils/typoStateMachine.ts +197 -0
  77. package/src/components/index.ts +2 -0
  78. package/src/components/ui/accordion/Accordion.vue +19 -0
  79. package/src/components/ui/accordion/AccordionContent.vue +24 -0
  80. package/src/components/ui/accordion/AccordionItem.vue +24 -0
  81. package/src/components/ui/accordion/AccordionTrigger.vue +39 -0
  82. package/src/components/ui/accordion/index.ts +4 -0
  83. package/src/components/ui/alert/Alert.vue +20 -0
  84. package/src/components/ui/alert/AlertDescription.vue +17 -0
  85. package/src/components/ui/alert/AlertTitle.vue +17 -0
  86. package/src/components/ui/alert/index.ts +23 -0
  87. package/src/components/ui/avatar/Avatar.vue +21 -0
  88. package/src/components/ui/avatar/AvatarFallback.vue +11 -0
  89. package/src/components/ui/avatar/AvatarImage.vue +9 -0
  90. package/src/components/ui/avatar/index.ts +24 -0
  91. package/src/components/ui/badge/Badge.vue +16 -0
  92. package/src/components/ui/badge/index.ts +25 -0
  93. package/src/components/ui/button/Button.vue +26 -0
  94. package/src/components/ui/button/index.ts +43 -0
  95. package/src/components/ui/card/Card.vue +28 -0
  96. package/src/components/ui/card/CardContent.vue +14 -0
  97. package/src/components/ui/card/CardDescription.vue +14 -0
  98. package/src/components/ui/card/CardFooter.vue +14 -0
  99. package/src/components/ui/card/CardHeader.vue +14 -0
  100. package/src/components/ui/card/CardTitle.vue +21 -0
  101. package/src/components/ui/card/index.ts +6 -0
  102. package/src/components/ui/carousel/Carousel.vue +53 -0
  103. package/src/components/ui/carousel/CarouselContent.vue +35 -0
  104. package/src/components/ui/carousel/CarouselItem.vue +24 -0
  105. package/src/components/ui/carousel/CarouselNext.vue +40 -0
  106. package/src/components/ui/carousel/CarouselPrevious.vue +40 -0
  107. package/src/components/ui/carousel/index.ts +10 -0
  108. package/src/components/ui/carousel/interface.ts +26 -0
  109. package/src/components/ui/carousel/useCarousel.ts +56 -0
  110. package/src/components/ui/checkbox/Checkbox.vue +33 -0
  111. package/src/components/ui/checkbox/index.ts +1 -0
  112. package/src/components/ui/collapsible/Collapsible.vue +15 -0
  113. package/src/components/ui/collapsible/CollapsibleContent.vue +11 -0
  114. package/src/components/ui/collapsible/CollapsibleTrigger.vue +11 -0
  115. package/src/components/ui/collapsible/index.ts +3 -0
  116. package/src/components/ui/combobox/Combobox.vue +17 -0
  117. package/src/components/ui/combobox/ComboboxAnchor.vue +23 -0
  118. package/src/components/ui/combobox/ComboboxEmpty.vue +21 -0
  119. package/src/components/ui/combobox/ComboboxGroup.vue +27 -0
  120. package/src/components/ui/combobox/ComboboxInput.vue +41 -0
  121. package/src/components/ui/combobox/ComboboxItem.vue +24 -0
  122. package/src/components/ui/combobox/ComboboxItemIndicator.vue +23 -0
  123. package/src/components/ui/combobox/ComboboxList.vue +29 -0
  124. package/src/components/ui/combobox/ComboboxSeparator.vue +21 -0
  125. package/src/components/ui/combobox/ComboboxViewport.vue +23 -0
  126. package/src/components/ui/combobox/index.ts +12 -0
  127. package/src/components/ui/command/Command.vue +30 -0
  128. package/src/components/ui/command/CommandDialog.vue +21 -0
  129. package/src/components/ui/command/CommandEmpty.vue +20 -0
  130. package/src/components/ui/command/CommandGroup.vue +29 -0
  131. package/src/components/ui/command/CommandInput.vue +33 -0
  132. package/src/components/ui/command/CommandItem.vue +26 -0
  133. package/src/components/ui/command/CommandList.vue +27 -0
  134. package/src/components/ui/command/CommandSeparator.vue +23 -0
  135. package/src/components/ui/command/CommandShortcut.vue +14 -0
  136. package/src/components/ui/command/index.ts +9 -0
  137. package/src/components/ui/context-menu/ContextMenu.vue +15 -0
  138. package/src/components/ui/context-menu/ContextMenuCheckboxItem.vue +40 -0
  139. package/src/components/ui/context-menu/ContextMenuContent.vue +36 -0
  140. package/src/components/ui/context-menu/ContextMenuGroup.vue +11 -0
  141. package/src/components/ui/context-menu/ContextMenuItem.vue +34 -0
  142. package/src/components/ui/context-menu/ContextMenuLabel.vue +25 -0
  143. package/src/components/ui/context-menu/ContextMenuPortal.vue +11 -0
  144. package/src/components/ui/context-menu/ContextMenuRadioGroup.vue +19 -0
  145. package/src/components/ui/context-menu/ContextMenuRadioItem.vue +40 -0
  146. package/src/components/ui/context-menu/ContextMenuSeparator.vue +20 -0
  147. package/src/components/ui/context-menu/ContextMenuShortcut.vue +14 -0
  148. package/src/components/ui/context-menu/ContextMenuSub.vue +19 -0
  149. package/src/components/ui/context-menu/ContextMenuSubContent.vue +35 -0
  150. package/src/components/ui/context-menu/ContextMenuSubTrigger.vue +34 -0
  151. package/src/components/ui/context-menu/ContextMenuTrigger.vue +13 -0
  152. package/src/components/ui/context-menu/index.ts +14 -0
  153. package/src/components/ui/data-table/DataTable.vue +167 -0
  154. package/src/components/ui/data-table/DataTablePagination.vue +112 -0
  155. package/src/components/ui/data-table/index.ts +3 -0
  156. package/src/components/ui/data-table/types.ts +48 -0
  157. package/src/components/ui/dialog/Dialog.vue +14 -0
  158. package/src/components/ui/dialog/DialogClose.vue +11 -0
  159. package/src/components/ui/dialog/DialogContent.vue +61 -0
  160. package/src/components/ui/dialog/DialogDescription.vue +24 -0
  161. package/src/components/ui/dialog/DialogFooter.vue +19 -0
  162. package/src/components/ui/dialog/DialogHeader.vue +16 -0
  163. package/src/components/ui/dialog/DialogScrollContent.vue +65 -0
  164. package/src/components/ui/dialog/DialogTitle.vue +29 -0
  165. package/src/components/ui/dialog/DialogTrigger.vue +11 -0
  166. package/src/components/ui/dialog/index.ts +9 -0
  167. package/src/components/ui/drawer/Drawer.vue +19 -0
  168. package/src/components/ui/drawer/DrawerContent.vue +28 -0
  169. package/src/components/ui/drawer/DrawerDescription.vue +20 -0
  170. package/src/components/ui/drawer/DrawerFooter.vue +14 -0
  171. package/src/components/ui/drawer/DrawerHeader.vue +14 -0
  172. package/src/components/ui/drawer/DrawerOverlay.vue +18 -0
  173. package/src/components/ui/drawer/DrawerTitle.vue +20 -0
  174. package/src/components/ui/drawer/index.ts +8 -0
  175. package/src/components/ui/dropdown-menu/DropdownMenu.vue +14 -0
  176. package/src/components/ui/dropdown-menu/DropdownMenuCheckboxItem.vue +40 -0
  177. package/src/components/ui/dropdown-menu/DropdownMenuContent.vue +44 -0
  178. package/src/components/ui/dropdown-menu/DropdownMenuGroup.vue +11 -0
  179. package/src/components/ui/dropdown-menu/DropdownMenuItem.vue +28 -0
  180. package/src/components/ui/dropdown-menu/DropdownMenuLabel.vue +24 -0
  181. package/src/components/ui/dropdown-menu/DropdownMenuRadioGroup.vue +19 -0
  182. package/src/components/ui/dropdown-menu/DropdownMenuRadioItem.vue +40 -0
  183. package/src/components/ui/dropdown-menu/DropdownMenuSeparator.vue +22 -0
  184. package/src/components/ui/dropdown-menu/DropdownMenuShortcut.vue +14 -0
  185. package/src/components/ui/dropdown-menu/DropdownMenuSub.vue +19 -0
  186. package/src/components/ui/dropdown-menu/DropdownMenuSubContent.vue +36 -0
  187. package/src/components/ui/dropdown-menu/DropdownMenuSubTrigger.vue +33 -0
  188. package/src/components/ui/dropdown-menu/DropdownMenuTrigger.vue +13 -0
  189. package/src/components/ui/dropdown-menu/index.ts +16 -0
  190. package/src/components/ui/hover-card/HoverCard.vue +14 -0
  191. package/src/components/ui/hover-card/HoverCardContent.vue +41 -0
  192. package/src/components/ui/hover-card/HoverCardTrigger.vue +11 -0
  193. package/src/components/ui/hover-card/index.ts +3 -0
  194. package/src/components/ui/index.ts +41 -0
  195. package/src/components/ui/input/Input.vue +24 -0
  196. package/src/components/ui/input/index.ts +1 -0
  197. package/src/components/ui/label/Label.vue +27 -0
  198. package/src/components/ui/label/index.ts +1 -0
  199. package/src/components/ui/multi-select/MultiSelect.vue +141 -0
  200. package/src/components/ui/multi-select/index.ts +7 -0
  201. package/src/components/ui/notification/Notification.vue +85 -0
  202. package/src/components/ui/notification/index.ts +1 -0
  203. package/src/components/ui/number-field/NumberField.vue +23 -0
  204. package/src/components/ui/number-field/NumberFieldContent.vue +14 -0
  205. package/src/components/ui/number-field/NumberFieldDecrement.vue +25 -0
  206. package/src/components/ui/number-field/NumberFieldIncrement.vue +25 -0
  207. package/src/components/ui/number-field/NumberFieldInput.vue +8 -0
  208. package/src/components/ui/number-field/index.ts +5 -0
  209. package/src/components/ui/popover/Popover.vue +15 -0
  210. package/src/components/ui/popover/PopoverContent.vue +61 -0
  211. package/src/components/ui/popover/PopoverTrigger.vue +11 -0
  212. package/src/components/ui/popover/index.ts +3 -0
  213. package/src/components/ui/progress/Progress.vue +39 -0
  214. package/src/components/ui/progress/index.ts +1 -0
  215. package/src/components/ui/radio-group/RadioGroup.vue +25 -0
  216. package/src/components/ui/radio-group/RadioGroupItem.vue +39 -0
  217. package/src/components/ui/radio-group/index.ts +2 -0
  218. package/src/components/ui/scroll-area/ScrollArea.vue +29 -0
  219. package/src/components/ui/scroll-area/ScrollBar.vue +30 -0
  220. package/src/components/ui/scroll-area/index.ts +2 -0
  221. package/src/components/ui/scroll-pane/ScrollPane.vue +25 -0
  222. package/src/components/ui/scroll-pane/ScrollPaneHeader.vue +75 -0
  223. package/src/components/ui/scroll-pane/index.ts +2 -0
  224. package/src/components/ui/select/Select.vue +15 -0
  225. package/src/components/ui/select/SelectContent.vue +57 -0
  226. package/src/components/ui/select/SelectGroup.vue +19 -0
  227. package/src/components/ui/select/SelectItem.vue +47 -0
  228. package/src/components/ui/select/SelectItemText.vue +11 -0
  229. package/src/components/ui/select/SelectLabel.vue +13 -0
  230. package/src/components/ui/select/SelectScrollDownButton.vue +24 -0
  231. package/src/components/ui/select/SelectScrollUpButton.vue +24 -0
  232. package/src/components/ui/select/SelectSeparator.vue +17 -0
  233. package/src/components/ui/select/SelectTrigger.vue +45 -0
  234. package/src/components/ui/select/SelectValue.vue +11 -0
  235. package/src/components/ui/select/index.ts +11 -0
  236. package/src/components/ui/separator/Separator.vue +35 -0
  237. package/src/components/ui/separator/index.ts +1 -0
  238. package/src/components/ui/sheet/Sheet.vue +14 -0
  239. package/src/components/ui/sheet/SheetClose.vue +11 -0
  240. package/src/components/ui/sheet/SheetContent.vue +56 -0
  241. package/src/components/ui/sheet/SheetDescription.vue +22 -0
  242. package/src/components/ui/sheet/SheetFooter.vue +19 -0
  243. package/src/components/ui/sheet/SheetHeader.vue +16 -0
  244. package/src/components/ui/sheet/SheetTitle.vue +22 -0
  245. package/src/components/ui/sheet/SheetTrigger.vue +11 -0
  246. package/src/components/ui/sheet/index.ts +31 -0
  247. package/src/components/ui/skeleton/Skeleton.vue +14 -0
  248. package/src/components/ui/skeleton/index.ts +1 -0
  249. package/src/components/ui/slider/Slider.vue +66 -0
  250. package/src/components/ui/slider/index.ts +1 -0
  251. package/src/components/ui/switch/Switch.vue +37 -0
  252. package/src/components/ui/switch/index.ts +1 -0
  253. package/src/components/ui/table/Table.vue +16 -0
  254. package/src/components/ui/table/TableBody.vue +14 -0
  255. package/src/components/ui/table/TableCaption.vue +14 -0
  256. package/src/components/ui/table/TableCell.vue +14 -0
  257. package/src/components/ui/table/TableEmpty.vue +39 -0
  258. package/src/components/ui/table/TableFooter.vue +16 -0
  259. package/src/components/ui/table/TableHead.vue +21 -0
  260. package/src/components/ui/table/TableHeader.vue +14 -0
  261. package/src/components/ui/table/TableRow.vue +21 -0
  262. package/src/components/ui/table/index.ts +9 -0
  263. package/src/components/ui/tabs/Tabs.vue +15 -0
  264. package/src/components/ui/tabs/TabsContent.vue +22 -0
  265. package/src/components/ui/tabs/TabsIndicator.vue +22 -0
  266. package/src/components/ui/tabs/TabsList.vue +25 -0
  267. package/src/components/ui/tabs/TabsTrigger.vue +29 -0
  268. package/src/components/ui/tabs/index.ts +5 -0
  269. package/src/components/ui/tags-input/TagsInput.vue +22 -0
  270. package/src/components/ui/tags-input/TagsInputInput.vue +19 -0
  271. package/src/components/ui/tags-input/TagsInputItem.vue +22 -0
  272. package/src/components/ui/tags-input/TagsInputItemDelete.vue +24 -0
  273. package/src/components/ui/tags-input/TagsInputItemText.vue +19 -0
  274. package/src/components/ui/tags-input/index.ts +5 -0
  275. package/src/components/ui/textarea/Textarea.vue +24 -0
  276. package/src/components/ui/textarea/index.ts +1 -0
  277. package/src/components/ui/toast/Toast.vue +57 -0
  278. package/src/components/ui/toast/ToastAction.vue +30 -0
  279. package/src/components/ui/toast/ToastClose.vue +31 -0
  280. package/src/components/ui/toast/ToastDescription.vue +25 -0
  281. package/src/components/ui/toast/ToastTitle.vue +25 -0
  282. package/src/components/ui/toast/Toaster.vue +31 -0
  283. package/src/components/ui/toast/index.ts +8 -0
  284. package/src/components/ui/toast/use-toast.ts +136 -0
  285. package/src/components/ui/toggle/Toggle.vue +35 -0
  286. package/src/components/ui/toggle/index.ts +27 -0
  287. package/src/components/ui/toggle-group/ToggleGroup.vue +34 -0
  288. package/src/components/ui/toggle-group/ToggleGroupItem.vue +35 -0
  289. package/src/components/ui/toggle-group/index.ts +2 -0
  290. package/src/components/ui/tooltip/Tooltip.vue +14 -0
  291. package/src/components/ui/tooltip/TooltipContent.vue +31 -0
  292. package/src/components/ui/tooltip/TooltipProvider.vue +11 -0
  293. package/src/components/ui/tooltip/TooltipTrigger.vue +11 -0
  294. package/src/components/ui/tooltip/index.ts +4 -0
  295. package/src/composables/glass/index.ts +8 -0
  296. package/src/composables/glass/useGlassRenderer.ts +252 -0
  297. package/src/composables/glass/webgl/frostShader.ts +221 -0
  298. package/src/composables/glass/webgpu/glassShader.wgsl +173 -0
  299. package/src/composables/index.ts +32 -0
  300. package/src/composables/infinite-scroll/index.ts +2 -0
  301. package/src/composables/infinite-scroll/types.ts +25 -0
  302. package/src/composables/infinite-scroll/useInfiniteScroll.ts +101 -0
  303. package/src/composables/interaction/index.ts +5 -0
  304. package/src/composables/interaction/useHeightTransition.ts +82 -0
  305. package/src/composables/interaction/useHoverPopover.ts +64 -0
  306. package/src/composables/interaction/useHoverToggle.ts +103 -0
  307. package/src/composables/interaction/useLeaveTimer.ts +17 -0
  308. package/src/composables/interaction/useTouchGate.ts +207 -0
  309. package/src/composables/pagination/index.ts +2 -0
  310. package/src/composables/pagination/useOffsetPagination.ts +70 -0
  311. package/src/composables/prng.ts +32 -0
  312. package/src/composables/useCharSplit.ts +31 -0
  313. package/src/composables/useClipboard.ts +46 -0
  314. package/src/composables/useGlobalDark.ts +61 -0
  315. package/src/composables/useKeyboardShortcuts.ts +205 -0
  316. package/src/composables/useWatercolorBlob.ts +136 -0
  317. package/src/composables/virtual/index.ts +22 -0
  318. package/src/composables/virtual/useVirtualSectionWindow.ts +338 -0
  319. package/src/composables/virtual/useWindowedStore.ts +86 -0
  320. package/src/composables/virtual/virtualSectionLayout.ts +212 -0
  321. package/src/index.ts +9 -0
  322. package/src/styles/animations.css +233 -0
  323. package/src/styles/cards.css +66 -0
  324. package/src/styles/dock.css +221 -0
  325. package/src/styles/floating-panel.css +49 -0
  326. package/src/styles/glass.css +266 -0
  327. package/src/styles/index.css +26 -0
  328. package/src/styles/scroll-pane.css +10 -0
  329. package/src/styles/theme.css +138 -0
  330. package/src/styles/tokens.css +333 -0
  331. package/src/styles/transitions.css +226 -0
  332. package/src/styles/typography.css +277 -0
  333. package/src/styles/utilities.css +697 -0
  334. package/src/utils/cn.ts +6 -0
  335. package/src/utils/index.ts +1 -0
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Generic dock action bar system.
3
+ *
4
+ * Any view can provide a DockActionBar — a list of actions with icons,
5
+ * titles, and callbacks. The dock renders them as dock-icon-btn buttons.
6
+ */
7
+
8
+ import type { Component, InjectionKey, Ref } from "vue";
9
+
10
+ export interface DockAction {
11
+ key: string;
12
+ icon: Component;
13
+ title: string;
14
+ description: string;
15
+ rotateOnClick?: boolean;
16
+ iconClass?: string;
17
+ disabled?: boolean;
18
+ handler: () => void;
19
+ }
20
+
21
+ export interface DockActionBar {
22
+ /** The label shown next to the Tools toggle button */
23
+ label: string;
24
+ /** Icon for the Tools toggle */
25
+ icon: Component;
26
+ /** Accent color for the action bar */
27
+ accentColor?: string;
28
+ /** The actions to display in the dock */
29
+ actions: Ref<DockAction[]>;
30
+ }
31
+
32
+ export const DOCK_ACTION_BAR_KEY: InjectionKey<Ref<DockActionBar | null>> =
33
+ Symbol("dockActionBar");
@@ -0,0 +1,301 @@
1
+ import { ref, watch, onUnmounted, provide, inject } from "vue";
2
+ import type { Ref } from "vue";
3
+ import { isTeleportedTarget } from "./isTeleportedTarget";
4
+
5
+ export interface UseDockStateOptions {
6
+ /** Delay before auto-collapse after mouse leaves (ms) */
7
+ collapseDelay?: number;
8
+ /** Root element ref — used for contains() checks */
9
+ rootEl: Ref<HTMLElement | null>;
10
+ /** Disable collapse behavior and keep the dock expanded. */
11
+ alwaysExpanded?: Ref<boolean> | boolean;
12
+ /** Called on every state transition */
13
+ onStateChange?: (newState: DockState, oldState: DockState) => void;
14
+ }
15
+
16
+ export type DockState = "collapsed" | "hover" | "pinned";
17
+
18
+ /**
19
+ * Dock state machine with three states and ref-counted child holds.
20
+ *
21
+ * ```
22
+ * State Machine:
23
+ * COLLAPSED ──(mouseenter/focusin)──→ HOVER
24
+ * HOVER ──(mouseleave + timeout)──→ COLLAPSED
25
+ * HOVER ──(clickCollapsed)──→ PINNED
26
+ * PINNED ──(clickOutside)──→ COLLAPSED
27
+ * Any ──(alwaysExpanded=true)──→ PINNED
28
+ *
29
+ * keepOpen/release ref-counting prevents TIMER-BASED collapse
30
+ * but NOT explicit click-away dismissal (onPointerDownOutside).
31
+ *
32
+ * Teleported targets (reka-ui portals, floating panels, dock popovers)
33
+ * are treated as "inside the dock" for mouse/focus/click-away purposes.
34
+ * ```
35
+ */
36
+ export function useDockState(options: UseDockStateOptions) {
37
+ const { collapseDelay = 2500, rootEl, alwaysExpanded = false, onStateChange } = options;
38
+
39
+ const getAlwaysExpanded = () =>
40
+ typeof alwaysExpanded === "boolean" ? alwaysExpanded : alwaysExpanded.value;
41
+
42
+ const initiallyExpanded = getAlwaysExpanded();
43
+ const state = ref<DockState>(initiallyExpanded ? "pinned" : "collapsed");
44
+ const expanded = ref(initiallyExpanded);
45
+ const isPinned = ref(initiallyExpanded);
46
+
47
+ let collapseTimer: ReturnType<typeof setTimeout> | null = null;
48
+ let keepOpenCount = 0;
49
+ let removeClickAway: (() => void) | null = null;
50
+ let isCollapsing = false;
51
+
52
+ // Suppress events briefly after mount to avoid phantom triggers
53
+ let ignoreEvents = true;
54
+ setTimeout(() => {
55
+ ignoreEvents = false;
56
+ }, 600);
57
+
58
+ let prevState: DockState = state.value;
59
+ function syncDerived() {
60
+ if (getAlwaysExpanded()) {
61
+ state.value = "pinned";
62
+ }
63
+ expanded.value = state.value !== "collapsed";
64
+ isPinned.value = state.value === "pinned";
65
+ if (onStateChange && prevState !== state.value) {
66
+ onStateChange(state.value, prevState);
67
+ }
68
+ prevState = state.value;
69
+ }
70
+
71
+ function clearTimer() {
72
+ if (collapseTimer) {
73
+ clearTimeout(collapseTimer);
74
+ collapseTimer = null;
75
+ }
76
+ }
77
+
78
+ function scheduleCollapse() {
79
+ if (getAlwaysExpanded()) return;
80
+ if (keepOpenCount > 0) return;
81
+ clearTimer();
82
+ collapseTimer = setTimeout(() => {
83
+ dismissOpenOverlays();
84
+ state.value = "collapsed";
85
+ syncDerived();
86
+ }, collapseDelay);
87
+ }
88
+
89
+ function dismissOpenOverlays() {
90
+ // Close any open reka-ui portals by programmatically clicking the document body.
91
+ // This triggers reka-ui's native "dismiss on outside click" without interfering
92
+ // with keyboard shortcut listeners (Escape would stop animations, etc.).
93
+ const active = document.querySelector(
94
+ '[data-reka-menu-content][data-state="open"], ' +
95
+ '[data-reka-popper-content-wrapper]'
96
+ );
97
+ if (active) {
98
+ // Pointer event outside the portal triggers reka-ui's onPointerDownOutside
99
+ document.body.dispatchEvent(new PointerEvent("pointerdown", { bubbles: true, composed: true }));
100
+ }
101
+ }
102
+
103
+ function collapse() {
104
+ if (getAlwaysExpanded()) {
105
+ state.value = "pinned";
106
+ syncDerived();
107
+ return;
108
+ }
109
+ if (isCollapsing) return;
110
+ isCollapsing = true;
111
+ clearTimer();
112
+ dismissOpenOverlays();
113
+ state.value = "collapsed";
114
+ syncDerived();
115
+ isCollapsing = false;
116
+ }
117
+
118
+ function expand() {
119
+ if (getAlwaysExpanded()) {
120
+ state.value = "pinned";
121
+ syncDerived();
122
+ return;
123
+ }
124
+ if (ignoreEvents) return;
125
+ clearTimer();
126
+ state.value = "hover";
127
+ syncDerived();
128
+ }
129
+
130
+ // --- Mouse handlers ---
131
+
132
+ function onMouseEnter() {
133
+ if (getAlwaysExpanded()) return;
134
+ if (ignoreEvents) return;
135
+ clearTimer();
136
+ if (state.value === "collapsed") {
137
+ state.value = "hover";
138
+ syncDerived();
139
+ }
140
+ // If pinned, no-op (stays pinned)
141
+ }
142
+
143
+ function onMouseLeave(e?: MouseEvent) {
144
+ if (getAlwaysExpanded()) return;
145
+ if (state.value === "hover") {
146
+ // Something is explicitly holding the dock open (dropdown, edit, etc.)
147
+ if (keepOpenCount > 0) return;
148
+ // Mouse moved to a descendant or teleported child (dropdown, popover)
149
+ if (e) {
150
+ const root = rootEl.value;
151
+ if (
152
+ root &&
153
+ e.relatedTarget instanceof Node &&
154
+ root.contains(e.relatedTarget)
155
+ )
156
+ return;
157
+ if (isTeleportedTarget(e.relatedTarget)) return;
158
+ }
159
+ scheduleCollapse();
160
+ }
161
+ // If pinned, no-op (stays pinned)
162
+ }
163
+
164
+ // --- Focus handlers ---
165
+
166
+ function onFocusIn() {
167
+ if (getAlwaysExpanded()) return;
168
+ if (ignoreEvents) return;
169
+ clearTimer();
170
+ if (state.value === "collapsed") {
171
+ state.value = "hover";
172
+ syncDerived();
173
+ }
174
+ }
175
+
176
+ function onFocusOut(e: FocusEvent) {
177
+ if (getAlwaysExpanded()) return;
178
+ if (state.value !== "hover") return;
179
+ if (keepOpenCount > 0) return;
180
+ const root = e.currentTarget as HTMLElement;
181
+ if (e.relatedTarget && root.contains(e.relatedTarget as Node)) return;
182
+ // Focus moved to a teleported element (dropdown content, select, popover)
183
+ if (isTeleportedTarget(e.relatedTarget)) return;
184
+ scheduleCollapse();
185
+ }
186
+
187
+ // --- Click on collapsed layer → PINNED ---
188
+
189
+ function onClickCollapsed() {
190
+ if (getAlwaysExpanded()) {
191
+ state.value = "pinned";
192
+ syncDerived();
193
+ return;
194
+ }
195
+ clearTimer();
196
+ state.value = "pinned";
197
+ syncDerived();
198
+ }
199
+
200
+ // --- keepOpen / release (ref-counted child holds) ---
201
+
202
+ function keepOpen() {
203
+ keepOpenCount++;
204
+ clearTimer();
205
+ }
206
+
207
+ function release() {
208
+ keepOpenCount = Math.max(0, keepOpenCount - 1);
209
+ if (keepOpenCount === 0 && state.value === "hover") {
210
+ // Grace period: don't collapse immediately after a child releases
211
+ // (e.g., dialog dismissed via Escape). Give the user time to re-engage.
212
+ clearTimer();
213
+ collapseTimer = setTimeout(() => {
214
+ if (keepOpenCount === 0 && state.value === "hover") {
215
+ scheduleCollapse();
216
+ }
217
+ }, Math.min(collapseDelay, 800));
218
+ }
219
+ }
220
+
221
+ // Provide for descendant components
222
+ provide("dockKeepOpen", keepOpen);
223
+ provide("dockRelease", release);
224
+ provide("dockExpanded", expanded);
225
+
226
+ // --- Click-away listener ---
227
+
228
+ function onPointerDownOutside(e: PointerEvent) {
229
+ if (getAlwaysExpanded()) return;
230
+ const root = rootEl.value;
231
+ if (!root || root.contains(e.target as Node)) return;
232
+ if (isTeleportedTarget(e.target)) return;
233
+
234
+ // Click outside → always collapse, even if keepOpenCount > 0
235
+ // (keepOpenCount prevents timer-based collapse, not explicit dismissal)
236
+ collapse();
237
+ }
238
+
239
+ watch(expanded, (isExpanded) => {
240
+ if (isExpanded) {
241
+ // Defer attachment past the current event's propagation.
242
+ // nextTick alone isn't enough — the opening pointerdown can still
243
+ // reach a capture-phase listener attached in the same microtask.
244
+ // requestAnimationFrame ensures we wait until the next frame.
245
+ requestAnimationFrame(() => {
246
+ document.addEventListener(
247
+ "pointerdown",
248
+ onPointerDownOutside,
249
+ true,
250
+ );
251
+ removeClickAway = () => {
252
+ document.removeEventListener(
253
+ "pointerdown",
254
+ onPointerDownOutside,
255
+ true,
256
+ );
257
+ removeClickAway = null;
258
+ };
259
+ });
260
+ } else {
261
+ removeClickAway?.();
262
+ }
263
+ });
264
+
265
+ if (typeof alwaysExpanded !== "boolean") {
266
+ watch(alwaysExpanded, (forceOpen) => {
267
+ if (forceOpen) {
268
+ clearTimer();
269
+ state.value = "pinned";
270
+ syncDerived();
271
+ return;
272
+ }
273
+ state.value = "collapsed";
274
+ syncDerived();
275
+ }, { immediate: true });
276
+ } else if (alwaysExpanded) {
277
+ state.value = "pinned";
278
+ syncDerived();
279
+ }
280
+
281
+ // Cleanup
282
+ onUnmounted(() => {
283
+ clearTimer();
284
+ removeClickAway?.();
285
+ });
286
+
287
+ return {
288
+ state,
289
+ expanded,
290
+ isPinned,
291
+ onMouseEnter,
292
+ onMouseLeave,
293
+ onFocusIn,
294
+ onFocusOut,
295
+ onClickCollapsed,
296
+ keepOpen,
297
+ release,
298
+ expand,
299
+ collapse,
300
+ };
301
+ }
@@ -0,0 +1,146 @@
1
+ import { ref, watch, nextTick, onUnmounted } from "vue";
2
+ import type { Ref } from "vue";
3
+
4
+ export interface UseDockTransitionOptions {
5
+ /** The logical expanded state (from useDockState) */
6
+ expanded: Ref<boolean>;
7
+ /** The dock root element ref — width is animated on this element */
8
+ rootEl: Ref<HTMLElement | null>;
9
+ /** Fade duration in ms before the layer swap (default: 60) */
10
+ fadeMs?: number;
11
+ /** When true, skip all width-pinning transitions (dock is always open) */
12
+ alwaysExpanded?: Ref<boolean>;
13
+ }
14
+
15
+ /**
16
+ * Orchestrates a deferred layer-swap transition for dock components.
17
+ *
18
+ * Both expand and collapse follow the same sequence:
19
+ * fade out → swap layer → animate width → fade in
20
+ *
21
+ * Width animation is driven through a reactive ref (`transitionWidth`)
22
+ * so it cooperates with Vue's reactive :style bindings.
23
+ */
24
+ export function useDockTransition(options: UseDockTransitionOptions) {
25
+ const { expanded, rootEl, fadeMs = 60, alwaysExpanded } = options;
26
+
27
+ const visualExpanded = ref(expanded.value);
28
+ const isTransitioning = ref(false);
29
+
30
+ /**
31
+ * Reactive width applied during transitions via :style.
32
+ * `null` means "no override — use natural sizing".
33
+ */
34
+ const transitionWidth = ref<string | null>(null);
35
+
36
+ /**
37
+ * When true, CSS transitions on the dock are suppressed.
38
+ * Used during the measure phase so width changes don't animate.
39
+ */
40
+ const suppressTransition = ref(false);
41
+
42
+ let fadeTimer: ReturnType<typeof setTimeout> | null = null;
43
+
44
+ function clearFadeTimer() {
45
+ if (fadeTimer) {
46
+ clearTimeout(fadeTimer);
47
+ fadeTimer = null;
48
+ }
49
+ }
50
+
51
+ let transitionId = 0;
52
+
53
+ watch(expanded, () => {
54
+ if (alwaysExpanded?.value) {
55
+ visualExpanded.value = expanded.value;
56
+ return;
57
+ }
58
+
59
+ const el = rootEl.value;
60
+ if (!el) return;
61
+
62
+ clearFadeTimer();
63
+ const id = ++transitionId;
64
+
65
+ // Capture current width before any changes
66
+ const from = el.getBoundingClientRect().width;
67
+
68
+ // Pin width so the layer swap can't cause a resize jump
69
+ suppressTransition.value = true;
70
+ transitionWidth.value = `${from}px`;
71
+
72
+ // Phase 1: fade out (visualExpanded still shows OLD layer)
73
+ isTransitioning.value = true;
74
+
75
+ // Phase 2: after fade completes, swap layer and animate width
76
+ fadeTimer = setTimeout(() => {
77
+ fadeTimer = null;
78
+ if (id !== transitionId) return;
79
+
80
+ // Swap layers while content is invisible
81
+ visualExpanded.value = expanded.value;
82
+
83
+ // Wait for Vue to flush the layer swap, then measure target width
84
+ nextTick(() => {
85
+ if (id !== transitionId) return;
86
+
87
+ // Release width to measure natural target (still no transitions)
88
+ transitionWidth.value = null;
89
+
90
+ nextTick(() => {
91
+ if (id !== transitionId) return;
92
+
93
+ const to = el.getBoundingClientRect().width;
94
+
95
+ // If from ≈ to, no animation needed
96
+ if (Math.abs(from - to) < 1) {
97
+ suppressTransition.value = false;
98
+ transitionWidth.value = null;
99
+ isTransitioning.value = false;
100
+ return;
101
+ }
102
+
103
+ // Pin back to old width (still suppressed)
104
+ transitionWidth.value = `${from}px`;
105
+
106
+ // Let Vue flush the pinned width, then enable transitions and animate
107
+ nextTick(() => {
108
+ if (id !== transitionId) return;
109
+
110
+ // Force the browser to commit the from-width
111
+ el.offsetWidth;
112
+
113
+ // Re-enable CSS transitions and set target width
114
+ suppressTransition.value = false;
115
+
116
+ // rAF ensures the browser has painted the from-width
117
+ // before we set the to-width, guaranteeing the transition fires
118
+ requestAnimationFrame(() => {
119
+ if (id !== transitionId) return;
120
+ transitionWidth.value = `${to}px`;
121
+ });
122
+ });
123
+ });
124
+ });
125
+ }, fadeMs);
126
+ });
127
+
128
+ /** Call from @transitionend on the root element. */
129
+ function onTransitionEnd(e: TransitionEvent) {
130
+ if (e.target !== rootEl.value) return;
131
+ if (e.propertyName === "width") {
132
+ transitionWidth.value = null;
133
+ isTransitioning.value = false;
134
+ }
135
+ }
136
+
137
+ onUnmounted(clearFadeTimer);
138
+
139
+ return {
140
+ visualExpanded,
141
+ isTransitioning,
142
+ transitionWidth,
143
+ suppressTransition,
144
+ onTransitionEnd,
145
+ };
146
+ }
@@ -0,0 +1,135 @@
1
+ import { ref, watch, nextTick, onUnmounted } from "vue";
2
+ import type { Ref } from "vue";
3
+
4
+ export interface UseLayerTransitionOptions {
5
+ /** The container element (must have `.dock-layer-grid` class) */
6
+ containerEl: Ref<HTMLElement | null>;
7
+ /** The currently active layer id */
8
+ activeLayer: Ref<string>;
9
+ }
10
+
11
+ export interface UseLayerTransitionReturn {
12
+ /** Returns class array + inert for a given layer id */
13
+ layerProps(id: string): { class: string[]; inert: true | undefined };
14
+ /** Attach to @transitionend on the container */
15
+ onTransitionEnd(e: TransitionEvent): void;
16
+ }
17
+
18
+ /**
19
+ * Coordinates simultaneous crossfade + FLIP width animation for
20
+ * grid-stacked layer containers. Reusable at any nesting level.
21
+ *
22
+ * Algorithm on activeLayer change:
23
+ * 1. Capture current container width
24
+ * 2. Pin container to that width
25
+ * 3. Swap classes: old layer → leaving (absolute, fading out),
26
+ * new layer → active (relative, fading in)
27
+ * 4. nextTick: measure new natural width, re-pin to old
28
+ * 5. Animate to new width via CSS transition
29
+ * 6. On transitionend(width), clear inline width
30
+ */
31
+ export function useLayerTransition(
32
+ options: UseLayerTransitionOptions,
33
+ ): UseLayerTransitionReturn {
34
+ const { containerEl, activeLayer } = options;
35
+
36
+ const currentLayer = ref(activeLayer.value);
37
+ const leavingLayer = ref<string | null>(null);
38
+ let transitionId = 0;
39
+ let cleanupTimer: ReturnType<typeof setTimeout> | null = null;
40
+
41
+ function clearCleanup() {
42
+ if (cleanupTimer) {
43
+ clearTimeout(cleanupTimer);
44
+ cleanupTimer = null;
45
+ }
46
+ }
47
+
48
+ watch(activeLayer, (newLayer, oldLayer) => {
49
+ if (newLayer === oldLayer) return;
50
+
51
+ const el = containerEl.value;
52
+ if (!el) {
53
+ currentLayer.value = newLayer;
54
+ leavingLayer.value = null;
55
+ return;
56
+ }
57
+
58
+ clearCleanup();
59
+ const id = ++transitionId;
60
+
61
+ // 1. Capture current width
62
+ const fromWidth = el.getBoundingClientRect().width;
63
+
64
+ // 2. Pin width (prevents snap during class swap)
65
+ el.style.width = `${fromWidth}px`;
66
+
67
+ // 3. Swap: mark old as leaving, new as active
68
+ leavingLayer.value = oldLayer;
69
+ currentLayer.value = newLayer;
70
+
71
+ // 4. Measure new natural width on next tick
72
+ nextTick(() => {
73
+ if (id !== transitionId) return;
74
+ if (!el) return;
75
+
76
+ // Temporarily unpin to measure
77
+ el.style.transition = "none";
78
+ el.style.width = "";
79
+ const toWidth = el.getBoundingClientRect().width;
80
+
81
+ // Re-pin to old width
82
+ el.style.width = `${fromWidth}px`;
83
+ // Force reflow so the browser registers the old width
84
+ void el.offsetWidth;
85
+ // Restore CSS transitions
86
+ el.style.transition = "";
87
+
88
+ // 5. Animate to new width
89
+ requestAnimationFrame(() => {
90
+ if (id !== transitionId) return;
91
+ el.style.width = `${toWidth}px`;
92
+ });
93
+
94
+ // Safety: clear inline width after max duration in case transitionend
95
+ // doesn't fire (e.g. width didn't actually change)
96
+ cleanupTimer = setTimeout(() => {
97
+ if (id !== transitionId) return;
98
+ el.style.width = "";
99
+ leavingLayer.value = null;
100
+ }, 400);
101
+ });
102
+ });
103
+
104
+ function onTransitionEnd(e: TransitionEvent) {
105
+ const el = containerEl.value;
106
+ if (!el) return;
107
+ if (e.target !== el) return;
108
+ if (e.propertyName !== "width") return;
109
+
110
+ clearCleanup();
111
+ el.style.width = "";
112
+ leavingLayer.value = null;
113
+ }
114
+
115
+ function layerProps(id: string): {
116
+ class: string[];
117
+ inert: true | undefined;
118
+ } {
119
+ const isActive = currentLayer.value === id;
120
+ const isLeaving = leavingLayer.value === id;
121
+
122
+ const classes = ["dock-layer-item"];
123
+ if (isActive) classes.push("dock-layer-active");
124
+ if (isLeaving) classes.push("dock-layer-leaving");
125
+
126
+ return {
127
+ class: classes,
128
+ inert: isActive ? undefined : true,
129
+ };
130
+ }
131
+
132
+ onUnmounted(clearCleanup);
133
+
134
+ return { layerProps, onTransitionEnd };
135
+ }
@@ -0,0 +1,83 @@
1
+ import { ref, computed, onUnmounted } from "vue";
2
+ import type { Ref, WritableComputedRef } from "vue";
3
+
4
+ export interface UsePopupMutexOptions {
5
+ /** Delay when swapping between popups to prevent jarring transitions (ms, default: 180) */
6
+ swapDelay?: number;
7
+ }
8
+
9
+ export interface UsePopupMutexReturn<K extends string> {
10
+ /** Currently open popup key, or null */
11
+ current: Ref<K | null>;
12
+ /** Whether any popup is open or mid-swap */
13
+ isAnyOpen: Ref<boolean>;
14
+ /** Create a writable computed get/set for a specific popup key */
15
+ popupModel(key: K): WritableComputedRef<boolean>;
16
+ }
17
+
18
+ /**
19
+ * Mutex for dock popups / dropdowns: only one open at a time,
20
+ * with a brief delay when swapping to prevent jarring transitions.
21
+ */
22
+ export function usePopupMutex<K extends string>(
23
+ options?: UsePopupMutexOptions,
24
+ ): UsePopupMutexReturn<K> {
25
+ const { swapDelay = 180 } = options ?? {};
26
+
27
+ const current = ref<K | null>(null) as Ref<K | null>;
28
+ const pending = ref<K | null>(null) as Ref<K | null>;
29
+ let swapTimer: ReturnType<typeof setTimeout> | null = null;
30
+
31
+ function clearSwapTimer() {
32
+ if (swapTimer) {
33
+ clearTimeout(swapTimer);
34
+ swapTimer = null;
35
+ }
36
+ }
37
+
38
+ function update(key: K, open: boolean) {
39
+ if (open) {
40
+ if (current.value === key) return;
41
+ clearSwapTimer();
42
+
43
+ // Swapping: close current, delay, then open new
44
+ if (current.value && current.value !== key) {
45
+ pending.value = key;
46
+ current.value = null;
47
+ swapTimer = setTimeout(() => {
48
+ current.value = pending.value;
49
+ pending.value = null;
50
+ swapTimer = null;
51
+ }, swapDelay);
52
+ return;
53
+ }
54
+
55
+ pending.value = null;
56
+ current.value = key;
57
+ return;
58
+ }
59
+
60
+ // Closing
61
+ if (pending.value === key) {
62
+ pending.value = null;
63
+ }
64
+ if (current.value === key) {
65
+ current.value = null;
66
+ }
67
+ }
68
+
69
+ const isAnyOpen = computed(
70
+ () => current.value !== null || pending.value !== null,
71
+ );
72
+
73
+ function popupModel(key: K): WritableComputedRef<boolean> {
74
+ return computed({
75
+ get: () => current.value === key,
76
+ set: (open: boolean) => update(key, open),
77
+ });
78
+ }
79
+
80
+ onUnmounted(clearSwapTimer);
81
+
82
+ return { current, isAnyOpen, popupModel };
83
+ }