@selvajs/ui 6.5.1-beta.1 → 6.6.0-beta.3

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 (348) hide show
  1. package/dist/components/ConfirmDialog.svelte +2 -10
  2. package/dist/components/ErrorScreen.svelte +16 -26
  3. package/dist/components/compute/AppLayout.svelte +7 -13
  4. package/dist/components/compute/ComputeApp.svelte +3 -3
  5. package/dist/components/compute/ComputeMessagesDialog.svelte +23 -49
  6. package/dist/components/compute/ParameterPresetManager.svelte +24 -68
  7. package/dist/components/compute/SolveMessages.svelte +23 -31
  8. package/dist/components/layout/Pagination.svelte +2 -4
  9. package/dist/components/layout/SideNav.svelte +1 -1
  10. package/dist/components/preview/ChartOutput.svelte +1 -5
  11. package/dist/components/preview/Group.svelte +71 -102
  12. package/dist/components/preview/Group.svelte.d.ts +1 -1
  13. package/dist/components/preview/HelpButton.svelte +34 -0
  14. package/dist/components/preview/HelpButton.svelte.d.ts +8 -0
  15. package/dist/components/preview/ImageOutput.svelte +2 -4
  16. package/dist/components/preview/InputControl.svelte +45 -45
  17. package/dist/components/preview/OutputDisplay.svelte +12 -22
  18. package/dist/components/preview/TabBar.svelte +106 -22
  19. package/dist/components/preview/TabBar.svelte.d.ts +1 -0
  20. package/dist/components/preview/TabContent.svelte +27 -56
  21. package/dist/components/preview/TabLayout.svelte +3 -3
  22. package/dist/components/preview/inputs/CheckboxInput.svelte +7 -7
  23. package/dist/components/preview/inputs/CheckboxInput.svelte.d.ts +3 -0
  24. package/dist/components/preview/inputs/ColorInput.svelte +1 -1
  25. package/dist/components/preview/inputs/FileInput.svelte +10 -13
  26. package/dist/components/preview/inputs/NumberInput.svelte +37 -57
  27. package/dist/components/preview/inputs/TextInput.svelte +0 -1
  28. package/dist/components/primitives/CalculateButton.svelte +1 -1
  29. package/dist/components/primitives/Callout.svelte +11 -18
  30. package/dist/components/primitives/DataTable.svelte +3 -4
  31. package/dist/components/primitives/FilterableDropdown.svelte +42 -129
  32. package/dist/components/primitives/FilterableDropdown.svelte.d.ts +1 -1
  33. package/dist/components/primitives/IssueList.svelte +56 -0
  34. package/dist/components/primitives/IssueList.svelte.d.ts +20 -0
  35. package/dist/components/primitives/SeverityIcon.svelte +15 -0
  36. package/dist/components/primitives/SeverityIcon.svelte.d.ts +8 -0
  37. package/dist/components/primitives/SolvingIndicator.svelte +1 -29
  38. package/dist/components/primitives/StateDisplay.svelte +19 -79
  39. package/dist/components/primitives/ViewToggle.svelte +18 -33
  40. package/dist/components/primitives/alert-dialog/alert-dialog-action.svelte +9 -3
  41. package/dist/components/primitives/alert-dialog/alert-dialog-action.svelte.d.ts +6 -1
  42. package/dist/components/primitives/alert-dialog/alert-dialog-content.svelte +1 -1
  43. package/dist/components/primitives/alert-dialog/alert-dialog-overlay.svelte +1 -1
  44. package/dist/components/primitives/badge/badge.svelte +2 -2
  45. package/dist/components/primitives/badge/badge.svelte.d.ts +4 -4
  46. package/dist/components/primitives/button/button.svelte +3 -2
  47. package/dist/components/primitives/button/button.svelte.d.ts +7 -4
  48. package/dist/components/primitives/card/card-header.svelte +1 -1
  49. package/dist/components/primitives/card/card.svelte +21 -5
  50. package/dist/components/primitives/card/card.svelte.d.ts +25 -1
  51. package/dist/components/primitives/card/index.d.ts +1 -2
  52. package/dist/components/primitives/card/index.js +2 -3
  53. package/dist/components/primitives/checkbox/checkbox.svelte +1 -1
  54. package/dist/components/primitives/command/command-dialog.svelte +42 -0
  55. package/dist/components/primitives/command/command-dialog.svelte.d.ts +14 -0
  56. package/dist/components/primitives/command/command-empty.svelte +17 -0
  57. package/dist/components/primitives/command/command-empty.svelte.d.ts +4 -0
  58. package/dist/components/primitives/command/command-group.svelte +33 -0
  59. package/dist/components/primitives/command/command-group.svelte.d.ts +7 -0
  60. package/dist/components/primitives/command/command-input.svelte +36 -0
  61. package/dist/components/primitives/command/command-input.svelte.d.ts +4 -0
  62. package/dist/components/primitives/command/command-item.svelte +28 -0
  63. package/dist/components/primitives/command/command-item.svelte.d.ts +4 -0
  64. package/dist/components/primitives/command/command-link-item.svelte +21 -0
  65. package/dist/components/primitives/command/command-link-item.svelte.d.ts +4 -0
  66. package/dist/components/primitives/command/command-list.svelte +20 -0
  67. package/dist/components/primitives/command/command-list.svelte.d.ts +4 -0
  68. package/dist/components/primitives/command/command-loading.svelte +7 -0
  69. package/dist/components/primitives/command/command-loading.svelte.d.ts +4 -0
  70. package/dist/components/primitives/command/command-separator.svelte +17 -0
  71. package/dist/components/primitives/command/command-separator.svelte.d.ts +4 -0
  72. package/dist/components/primitives/command/command-shortcut.svelte +23 -0
  73. package/dist/components/primitives/command/command-shortcut.svelte.d.ts +5 -0
  74. package/dist/components/primitives/command/command.svelte +28 -0
  75. package/dist/components/primitives/command/command.svelte.d.ts +8 -0
  76. package/dist/components/primitives/command/index.d.ts +12 -0
  77. package/dist/components/primitives/command/index.js +14 -0
  78. package/dist/components/primitives/context-menu/context-menu-content.svelte +1 -1
  79. package/dist/components/primitives/context-menu/context-menu-sub-content.svelte +1 -1
  80. package/dist/components/primitives/dialog/dialog-content.svelte +5 -1
  81. package/dist/components/primitives/dialog/dialog-content.svelte.d.ts +2 -0
  82. package/dist/components/primitives/dialog/dialog-overlay.svelte +1 -1
  83. package/dist/components/primitives/dialog/dialog-title.svelte +6 -2
  84. package/dist/components/primitives/dialog/dialog-title.svelte.d.ts +5 -1
  85. package/dist/components/primitives/drawer/Drawer.svelte +1 -1
  86. package/dist/components/primitives/dropdown-menu/dropdown-menu-checkbox-group.svelte +16 -0
  87. package/dist/components/primitives/dropdown-menu/dropdown-menu-checkbox-group.svelte.d.ts +4 -0
  88. package/dist/components/primitives/dropdown-menu/dropdown-menu-checkbox-item.svelte +46 -0
  89. package/dist/components/primitives/dropdown-menu/dropdown-menu-checkbox-item.svelte.d.ts +9 -0
  90. package/dist/components/primitives/dropdown-menu/dropdown-menu-content.svelte +31 -0
  91. package/dist/components/primitives/dropdown-menu/dropdown-menu-content.svelte.d.ts +10 -0
  92. package/dist/components/primitives/dropdown-menu/dropdown-menu-group-heading.svelte +22 -0
  93. package/dist/components/primitives/dropdown-menu/dropdown-menu-group-heading.svelte.d.ts +8 -0
  94. package/dist/components/primitives/dropdown-menu/dropdown-menu-group.svelte +7 -0
  95. package/dist/components/primitives/dropdown-menu/dropdown-menu-group.svelte.d.ts +4 -0
  96. package/dist/components/primitives/dropdown-menu/dropdown-menu-item.svelte +27 -0
  97. package/dist/components/primitives/dropdown-menu/dropdown-menu-item.svelte.d.ts +8 -0
  98. package/dist/components/primitives/dropdown-menu/dropdown-menu-label.svelte +24 -0
  99. package/dist/components/primitives/dropdown-menu/dropdown-menu-label.svelte.d.ts +8 -0
  100. package/dist/components/primitives/dropdown-menu/dropdown-menu-portal.svelte +7 -0
  101. package/dist/components/primitives/dropdown-menu/dropdown-menu-portal.svelte.d.ts +3 -0
  102. package/dist/components/primitives/dropdown-menu/dropdown-menu-radio-group.svelte +16 -0
  103. package/dist/components/primitives/dropdown-menu/dropdown-menu-radio-group.svelte.d.ts +4 -0
  104. package/dist/components/primitives/dropdown-menu/dropdown-menu-radio-item.svelte +36 -0
  105. package/dist/components/primitives/dropdown-menu/dropdown-menu-radio-item.svelte.d.ts +4 -0
  106. package/dist/components/primitives/dropdown-menu/dropdown-menu-separator.svelte +17 -0
  107. package/dist/components/primitives/dropdown-menu/dropdown-menu-separator.svelte.d.ts +4 -0
  108. package/dist/components/primitives/dropdown-menu/dropdown-menu-shortcut.svelte +23 -0
  109. package/dist/components/primitives/dropdown-menu/dropdown-menu-shortcut.svelte.d.ts +5 -0
  110. package/dist/components/primitives/dropdown-menu/dropdown-menu-sub-content.svelte +31 -0
  111. package/dist/components/primitives/dropdown-menu/dropdown-menu-sub-content.svelte.d.ts +10 -0
  112. package/dist/components/primitives/dropdown-menu/dropdown-menu-sub-trigger.svelte +29 -0
  113. package/dist/components/primitives/dropdown-menu/dropdown-menu-sub-trigger.svelte.d.ts +7 -0
  114. package/dist/components/primitives/dropdown-menu/dropdown-menu-sub.svelte +7 -0
  115. package/dist/components/primitives/dropdown-menu/dropdown-menu-sub.svelte.d.ts +3 -0
  116. package/dist/components/primitives/dropdown-menu/dropdown-menu-trigger.svelte +7 -0
  117. package/dist/components/primitives/dropdown-menu/dropdown-menu-trigger.svelte.d.ts +4 -0
  118. package/dist/components/primitives/dropdown-menu/dropdown-menu.svelte +7 -0
  119. package/dist/components/primitives/dropdown-menu/dropdown-menu.svelte.d.ts +3 -0
  120. package/dist/components/primitives/dropdown-menu/index.d.ts +18 -0
  121. package/dist/components/primitives/dropdown-menu/index.js +18 -0
  122. package/dist/components/primitives/{alert/alert-description.svelte → empty/empty-content.svelte} +3 -3
  123. package/dist/components/primitives/empty/empty-content.svelte.d.ts +5 -0
  124. package/dist/components/primitives/empty/empty-description.svelte +23 -0
  125. package/dist/components/primitives/{alert/alert-description.svelte.d.ts → empty/empty-description.svelte.d.ts} +4 -4
  126. package/dist/components/primitives/{alert/alert-title.svelte → empty/empty-header.svelte} +3 -3
  127. package/dist/components/primitives/empty/empty-header.svelte.d.ts +5 -0
  128. package/dist/components/primitives/empty/empty-media.svelte +41 -0
  129. package/dist/components/primitives/empty/empty-media.svelte.d.ts +26 -0
  130. package/dist/components/primitives/{card/card-action.svelte → empty/empty-title.svelte} +2 -2
  131. package/dist/components/primitives/{card/card-action.svelte.d.ts → empty/empty-title.svelte.d.ts} +3 -3
  132. package/dist/components/primitives/empty/empty.svelte +23 -0
  133. package/dist/components/primitives/empty/empty.svelte.d.ts +5 -0
  134. package/dist/components/primitives/empty/index.d.ts +7 -0
  135. package/dist/components/primitives/empty/index.js +9 -0
  136. package/dist/components/primitives/index.d.ts +10 -1
  137. package/dist/components/primitives/index.js +10 -1
  138. package/dist/components/primitives/input/input.svelte +4 -4
  139. package/dist/components/primitives/input-group/index.d.ts +7 -0
  140. package/dist/components/primitives/input-group/index.js +9 -0
  141. package/dist/components/primitives/input-group/input-group-addon.svelte +53 -0
  142. package/dist/components/primitives/input-group/input-group-addon.svelte.d.ts +32 -0
  143. package/dist/components/primitives/input-group/input-group-button.svelte +49 -0
  144. package/dist/components/primitives/input-group/input-group-button.svelte.d.ts +32 -0
  145. package/dist/components/primitives/input-group/input-group-input.svelte +25 -0
  146. package/dist/components/primitives/input-group/input-group-input.svelte.d.ts +11 -0
  147. package/dist/components/primitives/input-group/input-group-text.svelte +22 -0
  148. package/dist/components/primitives/input-group/input-group-text.svelte.d.ts +5 -0
  149. package/dist/components/primitives/input-group/input-group-textarea.svelte +23 -0
  150. package/dist/components/primitives/input-group/input-group-textarea.svelte.d.ts +3 -0
  151. package/dist/components/primitives/input-group/input-group.svelte +24 -0
  152. package/dist/components/primitives/{alert/alert-title.svelte.d.ts → input-group/input-group.svelte.d.ts} +4 -4
  153. package/dist/components/primitives/mode-toggle/mode-toggle.svelte +2 -6
  154. package/dist/components/primitives/popover/index.d.ts +9 -0
  155. package/dist/components/primitives/popover/index.js +11 -0
  156. package/dist/components/primitives/popover/popover-close.svelte +7 -0
  157. package/dist/components/primitives/popover/popover-close.svelte.d.ts +4 -0
  158. package/dist/components/primitives/popover/popover-content.svelte +31 -0
  159. package/dist/components/primitives/popover/popover-content.svelte.d.ts +10 -0
  160. package/dist/components/primitives/popover/popover-description.svelte +20 -0
  161. package/dist/components/primitives/popover/popover-description.svelte.d.ts +5 -0
  162. package/dist/components/primitives/popover/popover-header.svelte +20 -0
  163. package/dist/components/primitives/popover/popover-header.svelte.d.ts +5 -0
  164. package/dist/components/primitives/popover/popover-portal.svelte +7 -0
  165. package/dist/components/primitives/popover/popover-portal.svelte.d.ts +3 -0
  166. package/dist/components/primitives/popover/popover-title.svelte +15 -0
  167. package/dist/components/primitives/popover/popover-title.svelte.d.ts +5 -0
  168. package/dist/components/primitives/popover/popover-trigger.svelte +17 -0
  169. package/dist/components/primitives/popover/popover-trigger.svelte.d.ts +4 -0
  170. package/dist/components/primitives/popover/popover.svelte +7 -0
  171. package/dist/components/primitives/popover/popover.svelte.d.ts +3 -0
  172. package/dist/components/primitives/resizable/resizable-handle.svelte +6 -2
  173. package/dist/components/primitives/resizable/resizable-handle.svelte.d.ts +2 -0
  174. package/dist/components/primitives/resizable/resizable-pane-group.svelte +1 -4
  175. package/dist/components/primitives/scroll-area/scroll-area.svelte +1 -1
  176. package/dist/components/primitives/search/search.svelte +3 -3
  177. package/dist/components/primitives/select/select-content.svelte +1 -1
  178. package/dist/components/primitives/select/select-trigger.svelte +1 -1
  179. package/dist/components/primitives/severity.d.ts +7 -0
  180. package/dist/components/primitives/severity.js +19 -0
  181. package/dist/components/primitives/slider/slider.svelte +2 -2
  182. package/dist/components/primitives/sonner/sonner.svelte +1 -1
  183. package/dist/components/primitives/spinner/index.d.ts +1 -0
  184. package/dist/components/primitives/spinner/index.js +1 -0
  185. package/dist/components/primitives/spinner/spinner.svelte +29 -0
  186. package/dist/components/primitives/spinner/spinner.svelte.d.ts +4 -0
  187. package/dist/components/primitives/switch/switch.svelte +2 -2
  188. package/dist/components/primitives/tabs/tabs-list.svelte +27 -5
  189. package/dist/components/primitives/tabs/tabs-list.svelte.d.ts +22 -1
  190. package/dist/components/primitives/tabs/tabs-trigger.svelte +23 -5
  191. package/dist/components/primitives/tabs/tabs-trigger.svelte.d.ts +22 -1
  192. package/dist/components/primitives/tabs/tabs.svelte +6 -2
  193. package/dist/components/primitives/tabs/tabs.svelte.d.ts +5 -1
  194. package/dist/components/primitives/textarea/textarea.svelte +1 -1
  195. package/dist/components/primitives/theme-switcher/ThemeSwitcher.svelte +1 -1
  196. package/dist/components/primitives/toggle/index.d.ts +3 -0
  197. package/dist/components/primitives/toggle/index.js +5 -0
  198. package/dist/components/primitives/toggle/toggle.svelte +52 -0
  199. package/dist/components/primitives/toggle/toggle.svelte.d.ts +43 -0
  200. package/dist/components/primitives/toggle-group/index.d.ts +3 -0
  201. package/dist/components/primitives/toggle-group/index.js +5 -0
  202. package/dist/components/primitives/toggle-group/toggle-group-item.svelte +35 -0
  203. package/dist/components/primitives/toggle-group/toggle-group-item.svelte.d.ts +6 -0
  204. package/dist/components/primitives/toggle-group/toggle-group.svelte +75 -0
  205. package/dist/components/primitives/toggle-group/toggle-group.svelte.d.ts +17 -0
  206. package/dist/components/viewer/MeshMetadataDialog.svelte +3 -3
  207. package/dist/components/viewer/SceneManager.svelte +14 -5
  208. package/dist/components/viewer/Viewer.svelte +120 -171
  209. package/dist/i18n/messages.d.ts +2 -0
  210. package/dist/i18n/messages.js +4 -0
  211. package/dist/styles/base.css +43 -0
  212. package/package.json +9 -9
  213. package/src/lib/components/ConfirmDialog.svelte +2 -10
  214. package/src/lib/components/ErrorScreen.svelte +16 -26
  215. package/src/lib/components/compute/AppLayout.svelte +7 -13
  216. package/src/lib/components/compute/ComputeApp.svelte +3 -3
  217. package/src/lib/components/compute/ComputeMessagesDialog.svelte +23 -49
  218. package/src/lib/components/compute/ParameterPresetManager.svelte +24 -68
  219. package/src/lib/components/compute/SolveMessages.svelte +23 -31
  220. package/src/lib/components/layout/Pagination.svelte +2 -4
  221. package/src/lib/components/layout/SideNav.svelte +1 -1
  222. package/src/lib/components/preview/ChartOutput.svelte +1 -5
  223. package/src/lib/components/preview/Group.svelte +71 -102
  224. package/src/lib/components/preview/HelpButton.svelte +34 -0
  225. package/src/lib/components/preview/ImageOutput.svelte +2 -4
  226. package/src/lib/components/preview/InputControl.svelte +45 -45
  227. package/src/lib/components/preview/OutputDisplay.svelte +12 -22
  228. package/src/lib/components/preview/TabBar.svelte +106 -22
  229. package/src/lib/components/preview/TabContent.svelte +27 -56
  230. package/src/lib/components/preview/TabLayout.svelte +3 -3
  231. package/src/lib/components/preview/inputs/CheckboxInput.svelte +7 -7
  232. package/src/lib/components/preview/inputs/ColorInput.svelte +1 -1
  233. package/src/lib/components/preview/inputs/FileInput.svelte +10 -13
  234. package/src/lib/components/preview/inputs/NumberInput.svelte +37 -57
  235. package/src/lib/components/preview/inputs/TextInput.svelte +0 -1
  236. package/src/lib/components/primitives/CalculateButton.svelte +1 -1
  237. package/src/lib/components/primitives/Callout.svelte +11 -18
  238. package/src/lib/components/primitives/DataTable.svelte +3 -4
  239. package/src/lib/components/primitives/FilterableDropdown.svelte +42 -129
  240. package/src/lib/components/primitives/IssueList.svelte +56 -0
  241. package/src/lib/components/primitives/SeverityIcon.svelte +15 -0
  242. package/src/lib/components/primitives/SolvingIndicator.svelte +1 -29
  243. package/src/lib/components/primitives/StateDisplay.svelte +19 -79
  244. package/src/lib/components/primitives/ViewToggle.svelte +18 -33
  245. package/src/lib/components/primitives/alert-dialog/alert-dialog-action.svelte +9 -3
  246. package/src/lib/components/primitives/alert-dialog/alert-dialog-content.svelte +1 -1
  247. package/src/lib/components/primitives/alert-dialog/alert-dialog-overlay.svelte +1 -1
  248. package/src/lib/components/primitives/badge/badge.svelte +2 -2
  249. package/src/lib/components/primitives/button/button.svelte +3 -2
  250. package/src/lib/components/primitives/card/card-header.svelte +1 -1
  251. package/src/lib/components/primitives/card/card.svelte +21 -5
  252. package/src/lib/components/primitives/card/index.ts +1 -4
  253. package/src/lib/components/primitives/checkbox/checkbox.svelte +1 -1
  254. package/src/lib/components/primitives/command/command-dialog.svelte +42 -0
  255. package/src/lib/components/primitives/command/command-empty.svelte +17 -0
  256. package/src/lib/components/primitives/command/command-group.svelte +33 -0
  257. package/src/lib/components/primitives/command/command-input.svelte +36 -0
  258. package/src/lib/components/primitives/command/command-item.svelte +28 -0
  259. package/src/lib/components/primitives/command/command-link-item.svelte +21 -0
  260. package/src/lib/components/primitives/command/command-list.svelte +20 -0
  261. package/src/lib/components/primitives/command/command-loading.svelte +7 -0
  262. package/src/lib/components/primitives/command/command-separator.svelte +17 -0
  263. package/src/lib/components/primitives/command/command-shortcut.svelte +23 -0
  264. package/src/lib/components/primitives/command/command.svelte +28 -0
  265. package/src/lib/components/primitives/command/index.ts +37 -0
  266. package/src/lib/components/primitives/context-menu/context-menu-content.svelte +1 -1
  267. package/src/lib/components/primitives/context-menu/context-menu-sub-content.svelte +1 -1
  268. package/src/lib/components/primitives/dialog/dialog-content.svelte +5 -1
  269. package/src/lib/components/primitives/dialog/dialog-overlay.svelte +1 -1
  270. package/src/lib/components/primitives/dialog/dialog-title.svelte +6 -2
  271. package/src/lib/components/primitives/drawer/Drawer.svelte +1 -1
  272. package/src/lib/components/primitives/dropdown-menu/dropdown-menu-checkbox-group.svelte +16 -0
  273. package/src/lib/components/primitives/dropdown-menu/dropdown-menu-checkbox-item.svelte +46 -0
  274. package/src/lib/components/primitives/dropdown-menu/dropdown-menu-content.svelte +31 -0
  275. package/src/lib/components/primitives/dropdown-menu/dropdown-menu-group-heading.svelte +22 -0
  276. package/src/lib/components/primitives/dropdown-menu/dropdown-menu-group.svelte +7 -0
  277. package/src/lib/components/primitives/dropdown-menu/dropdown-menu-item.svelte +27 -0
  278. package/src/lib/components/primitives/dropdown-menu/dropdown-menu-label.svelte +24 -0
  279. package/src/lib/components/primitives/dropdown-menu/dropdown-menu-portal.svelte +7 -0
  280. package/src/lib/components/primitives/dropdown-menu/dropdown-menu-radio-group.svelte +16 -0
  281. package/src/lib/components/primitives/dropdown-menu/dropdown-menu-radio-item.svelte +36 -0
  282. package/src/lib/components/primitives/dropdown-menu/dropdown-menu-separator.svelte +17 -0
  283. package/src/lib/components/primitives/dropdown-menu/dropdown-menu-shortcut.svelte +23 -0
  284. package/src/lib/components/primitives/dropdown-menu/dropdown-menu-sub-content.svelte +31 -0
  285. package/src/lib/components/primitives/dropdown-menu/dropdown-menu-sub-trigger.svelte +29 -0
  286. package/src/lib/components/primitives/dropdown-menu/dropdown-menu-sub.svelte +7 -0
  287. package/src/lib/components/primitives/dropdown-menu/dropdown-menu-trigger.svelte +7 -0
  288. package/src/lib/components/primitives/dropdown-menu/dropdown-menu.svelte +7 -0
  289. package/src/lib/components/primitives/dropdown-menu/index.ts +54 -0
  290. package/src/lib/components/primitives/{alert/alert-description.svelte → empty/empty-content.svelte} +3 -3
  291. package/src/lib/components/primitives/empty/empty-description.svelte +23 -0
  292. package/src/lib/components/primitives/{alert/alert-title.svelte → empty/empty-header.svelte} +3 -3
  293. package/src/lib/components/primitives/empty/empty-media.svelte +41 -0
  294. package/src/lib/components/primitives/{card/card-action.svelte → empty/empty-title.svelte} +2 -2
  295. package/src/lib/components/primitives/empty/empty.svelte +23 -0
  296. package/src/lib/components/primitives/empty/index.ts +22 -0
  297. package/src/lib/components/primitives/index.ts +10 -1
  298. package/src/lib/components/primitives/input/input.svelte +4 -4
  299. package/src/lib/components/primitives/input-group/index.ts +22 -0
  300. package/src/lib/components/primitives/input-group/input-group-addon.svelte +53 -0
  301. package/src/lib/components/primitives/input-group/input-group-button.svelte +49 -0
  302. package/src/lib/components/primitives/input-group/input-group-input.svelte +25 -0
  303. package/src/lib/components/primitives/input-group/input-group-text.svelte +22 -0
  304. package/src/lib/components/primitives/input-group/input-group-textarea.svelte +23 -0
  305. package/src/lib/components/primitives/input-group/input-group.svelte +24 -0
  306. package/src/lib/components/primitives/mode-toggle/mode-toggle.svelte +2 -6
  307. package/src/lib/components/primitives/popover/index.ts +28 -0
  308. package/src/lib/components/primitives/popover/popover-close.svelte +7 -0
  309. package/src/lib/components/primitives/popover/popover-content.svelte +31 -0
  310. package/src/lib/components/primitives/popover/popover-description.svelte +20 -0
  311. package/src/lib/components/primitives/popover/popover-header.svelte +20 -0
  312. package/src/lib/components/primitives/popover/popover-portal.svelte +7 -0
  313. package/src/lib/components/primitives/popover/popover-title.svelte +15 -0
  314. package/src/lib/components/primitives/popover/popover-trigger.svelte +17 -0
  315. package/src/lib/components/primitives/popover/popover.svelte +7 -0
  316. package/src/lib/components/primitives/resizable/resizable-handle.svelte +6 -2
  317. package/src/lib/components/primitives/resizable/resizable-pane-group.svelte +1 -4
  318. package/src/lib/components/primitives/scroll-area/scroll-area.svelte +1 -1
  319. package/src/lib/components/primitives/search/search.svelte +3 -3
  320. package/src/lib/components/primitives/select/select-content.svelte +1 -1
  321. package/src/lib/components/primitives/select/select-trigger.svelte +1 -1
  322. package/src/lib/components/primitives/severity.ts +26 -0
  323. package/src/lib/components/primitives/slider/slider.svelte +2 -2
  324. package/src/lib/components/primitives/sonner/sonner.svelte +1 -1
  325. package/src/lib/components/primitives/spinner/index.ts +1 -0
  326. package/src/lib/components/primitives/spinner/spinner.svelte +29 -0
  327. package/src/lib/components/primitives/switch/switch.svelte +2 -2
  328. package/src/lib/components/primitives/tabs/tabs-list.svelte +27 -5
  329. package/src/lib/components/primitives/tabs/tabs-trigger.svelte +23 -5
  330. package/src/lib/components/primitives/tabs/tabs.svelte +6 -2
  331. package/src/lib/components/primitives/textarea/textarea.svelte +1 -1
  332. package/src/lib/components/primitives/theme-switcher/ThemeSwitcher.svelte +1 -1
  333. package/src/lib/components/primitives/toggle/index.ts +13 -0
  334. package/src/lib/components/primitives/toggle/toggle.svelte +52 -0
  335. package/src/lib/components/primitives/toggle-group/index.ts +10 -0
  336. package/src/lib/components/primitives/toggle-group/toggle-group-item.svelte +35 -0
  337. package/src/lib/components/primitives/toggle-group/toggle-group.svelte +75 -0
  338. package/src/lib/components/viewer/MeshMetadataDialog.svelte +3 -3
  339. package/src/lib/components/viewer/SceneManager.svelte +14 -5
  340. package/src/lib/components/viewer/Viewer.svelte +120 -171
  341. package/src/lib/i18n/messages.ts +6 -0
  342. package/src/lib/styles/base.css +43 -0
  343. package/dist/components/primitives/alert/alert.svelte +0 -44
  344. package/dist/components/primitives/alert/alert.svelte.d.ts +0 -26
  345. package/dist/components/primitives/alert/index.d.ts +0 -5
  346. package/dist/components/primitives/alert/index.js +0 -7
  347. package/src/lib/components/primitives/alert/alert.svelte +0 -44
  348. package/src/lib/components/primitives/alert/index.ts +0 -14
@@ -83,60 +83,31 @@
83
83
  {/if}
84
84
  {/snippet}
85
85
 
86
- <Tabs.Content value={tab.id} class="min-h-0 p-0 flex-1">
87
- <ScrollArea class="h-full" orientation="vertical">
88
- <div class="p-4 tab-content-container">
89
- {#if visibleGroups.length === 0}
90
- <StateDisplay type="empty" size="medium" message={t.tabNoGroups} />
91
- {:else}
92
- <div class="gap-8 flex flex-col">
93
- {#each visibleGroups as group (group.id)}
94
- <Group
95
- label={group.label}
96
- description={group.description}
97
- items={group.items}
98
- columns={group.columns ?? 1}
99
- collapsed={collapsedGroups[group.id] ?? false}
100
- {values}
101
- onToggle={() => onToggleGroup(group.id)}
102
- inputSnippet={renderInput}
103
- outputSnippet={renderOutput}
104
- flat={visibleGroups.length === 1}
105
- />
106
- {/each}
107
- </div>
108
- {/if}
109
- </div>
110
- </ScrollArea>
86
+ <Tabs.Content value={tab.id} class="min-h-0">
87
+ <div class="h-full bg-muted dark:bg-background">
88
+ <ScrollArea class="h-full" orientation="vertical">
89
+ <div class="px-3">
90
+ {#if visibleGroups.length === 0}
91
+ <StateDisplay type="empty" size="medium" message={t.tabNoGroups} />
92
+ {:else}
93
+ <div class="gap-3 py-3 flex flex-col">
94
+ {#each visibleGroups as group (group.id)}
95
+ <Group
96
+ label={group.label}
97
+ description={group.description}
98
+ items={group.items}
99
+ columns={group.columns ?? 1}
100
+ collapsed={collapsedGroups[group.id] ?? false}
101
+ {values}
102
+ onToggle={() => onToggleGroup(group.id)}
103
+ inputSnippet={renderInput}
104
+ outputSnippet={renderOutput}
105
+ flat={visibleGroups.length === 1}
106
+ />
107
+ {/each}
108
+ </div>
109
+ {/if}
110
+ </div>
111
+ </ScrollArea>
112
+ </div>
111
113
  </Tabs.Content>
112
-
113
- <style>
114
- .tab-content-container {
115
- container-type: inline-size;
116
- }
117
-
118
- @container (max-width: 320px) {
119
- :global(.schema-grid) {
120
- grid-template-columns: 1fr;
121
- }
122
- }
123
- @container (min-width: 321px) and (max-width: 560px) {
124
- :global(.schema-grid) {
125
- grid-template-columns: repeat(min(2, var(--schema-cols)), minmax(0, 1fr));
126
- }
127
- }
128
-
129
- /* Fallback for browsers without container query support */
130
- @supports not (container-type: inline-size) {
131
- @media (max-width: 639px) {
132
- :global(.schema-grid) {
133
- grid-template-columns: 1fr;
134
- }
135
- }
136
- @media (min-width: 640px) and (max-width: 1023px) {
137
- :global(.schema-grid) {
138
- grid-template-columns: repeat(min(2, var(--schema-cols)), minmax(0, 1fr));
139
- }
140
- }
141
- }
142
- </style>
@@ -66,10 +66,10 @@
66
66
  }
67
67
  </script>
68
68
 
69
- <Card.Root class="pt-1 min-h-0 mx-1 flex flex-1 flex-col overflow-hidden">
70
- <Tabs.Root value={activeTabId} class="gap-0 min-h-0 flex flex-1 flex-col">
69
+ <Card.Root variant="flush" class="min-h-0 mx-1 flex-1">
70
+ <Tabs.Root value={activeTabId} variant="line" class="min-h-0 flex-1">
71
71
  {#if showTabBar}
72
- <TabBar tabs={visibleTabs} onTabChange={handleTabChange} />
72
+ <TabBar tabs={visibleTabs} {activeTabId} onTabChange={handleTabChange} />
73
73
  {/if}
74
74
 
75
75
  {#each visibleTabs as tab (tab.id)}
@@ -1,19 +1,21 @@
1
1
  <script lang="ts">
2
+ import type { Snippet } from 'svelte';
2
3
  import type { SupportedTypes } from '@selvajs/schemas';
3
4
  import { Checkbox } from '../../primitives/checkbox';
4
- import * as Field from '../../primitives/field';
5
5
 
6
6
  interface Props {
7
7
  inputId: string;
8
8
  value?: boolean;
9
9
  onChange: (value: SupportedTypes) => void;
10
10
  disabled?: boolean;
11
+ /** Rendered beside the box; must contain a label pointing at `inputId`. */
12
+ children?: Snippet;
11
13
  }
12
14
 
13
- let { inputId, value = $bindable(), onChange, disabled = false }: Props = $props();
15
+ let { inputId, value = $bindable(), onChange, disabled = false, children }: Props = $props();
14
16
  </script>
15
17
 
16
- <Field.Field orientation="horizontal">
18
+ <div class="gap-2.5 min-h-6 min-w-0 flex items-center">
17
19
  <Checkbox
18
20
  id={inputId}
19
21
  checked={typeof value === 'boolean' ? value : false}
@@ -23,7 +25,5 @@
23
25
  }}
24
26
  {disabled}
25
27
  />
26
- <Field.Label for={inputId} class="font-normal cursor-pointer text-muted-foreground">
27
- Enabled
28
- </Field.Label>
29
- </Field.Field>
28
+ {@render children?.()}
29
+ </div>
@@ -1,9 +1,12 @@
1
+ import type { Snippet } from 'svelte';
1
2
  import type { SupportedTypes } from '@selvajs/schemas';
2
3
  interface Props {
3
4
  inputId: string;
4
5
  value?: boolean;
5
6
  onChange: (value: SupportedTypes) => void;
6
7
  disabled?: boolean;
8
+ /** Rendered beside the box; must contain a label pointing at `inputId`. */
9
+ children?: Snippet;
7
10
  }
8
11
  declare const CheckboxInput: import("svelte").Component<Props, {}, "value">;
9
12
  type CheckboxInput = ReturnType<typeof CheckboxInput>;
@@ -11,7 +11,7 @@
11
11
  <input
12
12
  type="color"
13
13
  value={value || '#000000'}
14
- class="h-9 w-12 rounded p-1 cursor-pointer border border-input bg-background"
14
+ class="h-9 w-12 rounded p-1 cursor-pointer border border-input bg-background dark:bg-input/30"
15
15
  onchange={(e) => {
16
16
  const newValue = (e.currentTarget as HTMLInputElement).value;
17
17
  onChange(newValue);
@@ -257,7 +257,7 @@
257
257
  const acceptAttribute = $derived(acceptedFormats.length > 0 ? acceptedFormats.join(',') : '*');
258
258
  </script>
259
259
 
260
- <div class="gap-4 flex w-full flex-col">
260
+ <div class="gap-2.5 flex w-full flex-col">
261
261
  <input
262
262
  bind:this={fileInput}
263
263
  type="file"
@@ -267,15 +267,16 @@
267
267
  />
268
268
 
269
269
  {#if showToggle}
270
- <div class="rounded text-xs flex overflow-hidden border">
270
+ <div class="p-0.5 text-xs flex rounded-md bg-muted">
271
271
  {#each effectiveModes as mode (mode)}
272
272
  <button
273
273
  type="button"
274
274
  onclick={() => (activeMode = mode)}
275
- class={`px-3 py-1 flex-1 transition-colors ${
275
+ aria-pressed={activeMode === mode}
276
+ class={`px-3 py-1 font-medium flex-1 rounded-sm transition-colors ${
276
277
  activeMode === mode
277
- ? 'bg-primary text-primary-foreground'
278
- : 'text-muted-foreground hover:bg-accent'
278
+ ? 'shadow-xs bg-background text-foreground'
279
+ : 'text-muted-foreground hover:text-foreground'
279
280
  }`}
280
281
  >
281
282
  {mode === 'upload' ? 'Upload' : 'URL'}
@@ -286,7 +287,7 @@
286
287
 
287
288
  {#if activeMode === 'url'}
288
289
  <div class="gap-2 flex flex-col">
289
- <Label for="url-input" class="gap-2 flex items-center">
290
+ <Label for="url-input">
290
291
  <Link size={16} />
291
292
  File URL
292
293
  </Label>
@@ -297,7 +298,7 @@
297
298
  bind:value={urlInput}
298
299
  placeholder="https://example.com/model.3dm"
299
300
  disabled={isLoading}
300
- class={urlError ? 'border-destructive focus-visible:ring-destructive' : ''}
301
+ aria-invalid={urlError ? true : undefined}
301
302
  oninput={() => {
302
303
  urlError = null;
303
304
  urlSuccess = '';
@@ -342,14 +343,10 @@
342
343
  </div>
343
344
  {:else}
344
345
  <div class="gap-2 flex flex-col">
345
- <Label for="file-upload" class="gap-2 flex items-center">
346
- <FileUp size={16} />
347
- Upload File
348
- </Label>
349
346
  <Button
350
347
  type="button"
351
- variant="outline"
352
- class="gap-2 w-full {isDragging ? 'border-primary' : ''}"
348
+ variant={isDragging ? 'secondary' : 'outline'}
349
+ class="w-full"
353
350
  onclick={openFilePicker}
354
351
  ondragover={handleDragOver}
355
352
  ondragleave={handleDragLeave}
@@ -3,6 +3,8 @@
3
3
  import { debounce } from '../../../utils/debounce';
4
4
  import { Input } from '../../primitives/input';
5
5
  import { Slider } from '../../primitives/slider';
6
+ import * as InputGroup from '../../primitives/input-group';
7
+ import { Separator } from '../../primitives/separator';
6
8
  import * as Field from '../../primitives/field';
7
9
 
8
10
  interface Props {
@@ -16,8 +18,6 @@
16
18
  let { inputId, value = $bindable(), config, onChange, disabled = false }: Props = $props();
17
19
 
18
20
  let validationError = $state<string | null>(null);
19
- let sliderEditing = $state(false);
20
- let sliderInputValue = $state('');
21
21
 
22
22
  const min = $derived(config?.minimum ?? 0);
23
23
  const max = $derived(config?.maximum ?? 100);
@@ -49,72 +49,53 @@
49
49
  commitSliderDebounced(newValue);
50
50
  }
51
51
 
52
- function focusSliderInput(node: HTMLInputElement) {
53
- node.focus();
54
- node.select();
55
- }
52
+ const decimals = $derived(Math.max(0, -Math.floor(Math.log10(step))));
56
53
 
57
- function decimalPlaces(stepSize: number): number {
58
- return Math.max(0, -Math.floor(Math.log10(stepSize)));
54
+ function commitTypedValue(input: HTMLInputElement) {
55
+ const parsed = parseFloat(input.value);
56
+ if (isNaN(parsed)) {
57
+ input.value = numValue.toFixed(decimals);
58
+ return;
59
+ }
60
+ const clamped = Math.min(max, Math.max(min, parsed));
61
+ input.value = clamped.toFixed(decimals);
62
+ if (clamped === value) return;
63
+ value = clamped;
64
+ onChange(clamped);
59
65
  }
60
66
  </script>
61
67
 
62
68
  {#if config?.renderAsSlider}
63
- {@const dp = decimalPlaces(step)}
64
- <div class="gap-4 flex items-center">
65
- <Slider
66
- type="single"
67
- value={numValue}
69
+ <!-- Value and track share one box, the height of a number input, so mixed rows line up and
70
+ a value can't be read as belonging to the neighbouring slider. -->
71
+ <InputGroup.Root>
72
+ <InputGroup.Input
73
+ id={inputId}
74
+ type="number"
75
+ class="w-18 flex-none"
76
+ value={numValue.toFixed(decimals)}
68
77
  {min}
69
78
  {max}
70
79
  {step}
71
- class="flex-1"
72
- onValueChange={handleSliderChange}
73
80
  {disabled}
81
+ onchange={(e) => commitTypedValue(e.currentTarget)}
82
+ onkeydown={(e) => {
83
+ if (e.key === 'Enter') e.currentTarget.blur();
84
+ }}
74
85
  />
75
- {#if sliderEditing}
76
- <input
77
- use:focusSliderInput
78
- type="number"
86
+ <Separator orientation="vertical" />
87
+ <div class="px-3.5 min-w-0 flex flex-1 items-center">
88
+ <Slider
89
+ type="single"
90
+ value={numValue}
91
+ {min}
92
+ {max}
79
93
  {step}
80
- class="min-w-12 w-16 text-sm border-b border-border bg-transparent text-right outline-none focus:border-foreground"
81
- bind:value={sliderInputValue}
82
- onblur={() => {
83
- const parsed = parseFloat(sliderInputValue);
84
- if (!isNaN(parsed)) handleSliderChange(Math.min(max, Math.max(min, parsed)));
85
- sliderEditing = false;
86
- }}
87
- onkeydown={(e) => {
88
- if (e.key === 'Enter') {
89
- const parsed = parseFloat(sliderInputValue);
90
- if (!isNaN(parsed)) handleSliderChange(Math.min(max, Math.max(min, parsed)));
91
- sliderEditing = false;
92
- } else if (e.key === 'Escape') {
93
- sliderEditing = false;
94
- }
95
- }}
94
+ onValueChange={handleSliderChange}
95
+ {disabled}
96
96
  />
97
- {:else}
98
- <span
99
- role="button"
100
- tabindex={0}
101
- class="min-w-12 text-sm cursor-text text-right text-muted-foreground select-none"
102
- title="Double-click to edit"
103
- ondblclick={() => {
104
- sliderInputValue = numValue.toFixed(dp);
105
- sliderEditing = true;
106
- }}
107
- onkeydown={(e) => {
108
- if (e.key === 'Enter' || e.key === ' ') {
109
- sliderInputValue = numValue.toFixed(dp);
110
- sliderEditing = true;
111
- }
112
- }}
113
- >
114
- {numValue.toFixed(dp)}
115
- </span>
116
- {/if}
117
- </div>
97
+ </div>
98
+ </InputGroup.Root>
118
99
  {:else}
119
100
  <Input
120
101
  id={inputId}
@@ -126,7 +107,6 @@
126
107
  placeholder={config?.placeholder}
127
108
  data-invalid={validationError ? true : undefined}
128
109
  aria-invalid={validationError ? true : undefined}
129
- class={validationError ? 'border-destructive' : ''}
130
110
  {disabled}
131
111
  oninput={() => (validationError = null)}
132
112
  onchange={(e) => {
@@ -51,7 +51,6 @@
51
51
  maxlength={config?.maxLength}
52
52
  data-invalid={validationError ? true : undefined}
53
53
  aria-invalid={validationError ? true : undefined}
54
- class={validationError ? 'border-destructive' : ''}
55
54
  {disabled}
56
55
  oninput={() => {
57
56
  validationError = null;
@@ -24,7 +24,7 @@
24
24
  disabled={!hasPendingChanges && !isSolving}
25
25
  aria-busy={isSolving}
26
26
  class="gap-2 font-medium w-full transition-all duration-200
27
- {hasPendingChanges && !isSolving ? 'selva-pending-pulse shadow-md' : ''}
27
+ {hasPendingChanges && !isSolving ? 'shadow-md' : ''}
28
28
  {!hasPendingChanges && !isSolving ? 'text-muted-foreground' : ''}"
29
29
  >
30
30
  {#if isSolving}
@@ -1,7 +1,7 @@
1
1
  <script lang="ts" module>
2
2
  import { type VariantProps, tv } from 'tailwind-variants';
3
- import { Info, TriangleAlert, CircleCheck, CircleX, Lightbulb } from '@lucide/svelte';
4
3
  import type { Component } from 'svelte';
4
+ import { SEVERITY_ICON, SEVERITY_TEXT, type Severity } from './severity';
5
5
 
6
6
  export const calloutVariants = tv({
7
7
  base: 'flex items-start gap-2.5 rounded-lg border px-3.5 py-3 text-sm',
@@ -19,20 +19,12 @@
19
19
 
20
20
  export type CalloutTone = NonNullable<VariantProps<typeof calloutVariants>['tone']>;
21
21
 
22
- const TONE_ICON: Record<CalloutTone, Component> = {
23
- info: Info,
24
- tip: Lightbulb,
25
- success: CircleCheck,
26
- warning: TriangleAlert,
27
- danger: CircleX
28
- };
29
-
30
- const TONE_ACCENT: Record<CalloutTone, string> = {
31
- info: 'text-info',
32
- tip: 'text-muted-foreground',
33
- success: 'text-success',
34
- warning: 'text-warning',
35
- danger: 'text-destructive'
22
+ const TONE_SEVERITY: Record<CalloutTone, Severity> = {
23
+ info: 'info',
24
+ tip: 'note',
25
+ success: 'success',
26
+ warning: 'warning',
27
+ danger: 'error'
36
28
  };
37
29
  </script>
38
30
 
@@ -51,7 +43,8 @@
51
43
 
52
44
  let { tone = 'info', title, icon, class: className, children, ...restProps }: Props = $props();
53
45
 
54
- const Icon = $derived(icon === undefined ? TONE_ICON[tone] : icon);
46
+ const Icon = $derived(icon === undefined ? SEVERITY_ICON[TONE_SEVERITY[tone]] : icon);
47
+ const accent = $derived(SEVERITY_TEXT[TONE_SEVERITY[tone]]);
55
48
  </script>
56
49
 
57
50
  <div
@@ -60,11 +53,11 @@
60
53
  {...restProps}
61
54
  >
62
55
  {#if Icon}
63
- <Icon class={cn('mt-0.5 h-4 w-4 shrink-0', TONE_ACCENT[tone])} />
56
+ <Icon class={cn('mt-0.5 h-4 w-4 shrink-0', accent)} />
64
57
  {/if}
65
58
  <div class="min-w-0 space-y-1">
66
59
  {#if title}
67
- <p class={cn('leading-tight font-medium', TONE_ACCENT[tone])}>{title}</p>
60
+ <p class={cn('leading-tight font-medium', accent)}>{title}</p>
68
61
  {/if}
69
62
  <div class="leading-relaxed text-muted-foreground [&_a]:underline [&_strong]:text-foreground">
70
63
  {@render children()}
@@ -22,17 +22,16 @@
22
22
  const gridTemplate = $derived(columns.map((c) => c.width ?? '1fr').join(' '));
23
23
  </script>
24
24
 
25
- <div class={`divide-y rounded-lg border ${className}`}>
25
+ <div class={`divide-y rounded-lg border ${className}`} style:--table-cols={gridTemplate}>
26
26
  <div
27
- class="gap-4 px-4 py-2 text-xs font-medium tracking-wide grid bg-muted/40 text-muted-foreground uppercase"
28
- style:grid-template-columns={gridTemplate}
27
+ class="gap-4 px-4 py-2 text-xs font-medium tracking-wide grid grid-cols-(--table-cols) bg-muted/40 text-muted-foreground uppercase"
29
28
  >
30
29
  {#each columns as col (col.label)}
31
30
  <span class={col.align === 'right' ? 'text-right' : ''}>{col.label}</span>
32
31
  {/each}
33
32
  </div>
34
33
  {#each rows as item (getKey(item))}
35
- <div class="gap-4 px-4 py-3 grid items-center" style:grid-template-columns={gridTemplate}>
34
+ <div class="gap-4 px-4 py-3 grid grid-cols-(--table-cols) items-center">
36
35
  {@render row(item)}
37
36
  </div>
38
37
  {/each}
@@ -1,14 +1,16 @@
1
1
  <script lang="ts">
2
2
  import type { Component } from 'svelte';
3
- import { Search, ChevronDown, Check } from '@lucide/svelte';
3
+ import { ChevronDown } from '@lucide/svelte';
4
4
  import { cn } from '../../utils.js';
5
+ import { buttonVariants } from './button';
6
+ import * as Popover from './popover';
7
+ import * as Command from './command';
5
8
 
6
9
  export interface FilterableDropdownItem {
7
10
  id: string;
8
11
  label: string;
9
12
  icon?: Component;
10
13
  iconClass?: string;
11
- iconStyle?: string;
12
14
  }
13
15
 
14
16
  interface Props {
@@ -19,6 +21,7 @@
19
21
  placeholder?: string;
20
22
  triggerIcon?: Component;
21
23
  searchPlaceholder?: string;
24
+ /** Above this many items the list gets a filter box. */
22
25
  searchThreshold?: number;
23
26
  disabled?: boolean;
24
27
  class?: string;
@@ -38,149 +41,59 @@
38
41
  }: Props = $props();
39
42
 
40
43
  let open = $state(false);
41
- let query = $state('');
42
- let highlighted = $state(0);
43
- let inputRef = $state<HTMLInputElement>();
44
- let containerRef = $state<HTMLDivElement>();
45
44
 
46
45
  const selected = $derived(items.find((item) => item.id === value) ?? null);
47
- const showSearch = $derived(items.length > searchThreshold);
48
-
49
- const matches = $derived.by(() => {
50
- const q = query.trim().toLowerCase();
51
- if (!q) return items;
52
- return items.filter((item) => item.label.toLowerCase().includes(q));
53
- });
54
-
55
- $effect(() => {
56
- void matches;
57
- highlighted = 0;
58
- });
59
-
60
- $effect(() => {
61
- if (open) {
62
- queueMicrotask(() => inputRef?.focus());
63
- } else {
64
- query = '';
65
- }
66
- });
46
+ const SelectedIcon = $derived(selected?.icon ?? TriggerIcon);
67
47
 
68
48
  function pick(item: FilterableDropdownItem) {
69
49
  onChange(item.id);
70
50
  open = false;
71
51
  }
72
-
73
- function handleKey(e: KeyboardEvent) {
74
- if (e.key === 'ArrowDown') {
75
- e.preventDefault();
76
- highlighted = Math.min(highlighted + 1, matches.length - 1);
77
- } else if (e.key === 'ArrowUp') {
78
- e.preventDefault();
79
- highlighted = Math.max(highlighted - 1, 0);
80
- } else if (e.key === 'Enter') {
81
- e.preventDefault();
82
- const m = matches[highlighted];
83
- if (m) pick(m);
84
- } else if (e.key === 'Escape') {
85
- e.preventDefault();
86
- open = false;
87
- }
88
- }
89
-
90
- function handleDocClick(e: MouseEvent) {
91
- if (!open) return;
92
- if (containerRef && !containerRef.contains(e.target as Node)) open = false;
93
- }
94
-
95
- $effect(() => {
96
- if (!open) return;
97
- document.addEventListener('mousedown', handleDocClick);
98
- return () => document.removeEventListener('mousedown', handleDocClick);
99
- });
100
-
101
- const SelectedIcon = $derived(selected?.icon);
102
52
  </script>
103
53
 
104
- <div bind:this={containerRef} class={cn('relative', className)}>
105
- <button
106
- type="button"
54
+ <Popover.Root bind:open>
55
+ <Popover.Trigger
107
56
  {id}
108
57
  {disabled}
109
- onclick={() => (open = !open)}
58
+ role="combobox"
59
+ aria-expanded={open}
110
60
  class={cn(
111
- 'h-10 gap-2 px-3 text-sm flex w-full items-center rounded-md border bg-background text-left transition-colors disabled:cursor-not-allowed disabled:opacity-50',
112
- open ? 'border-ring ring-2 ring-ring/30' : 'border-input hover:bg-muted/40'
61
+ buttonVariants({ variant: 'outline' }),
62
+ 'font-normal w-full justify-start',
63
+ className
113
64
  )}
114
65
  >
115
66
  {#if SelectedIcon}
116
- <SelectedIcon
117
- class={cn('h-3.5 w-3.5 shrink-0', selected?.iconClass ?? 'text-muted-foreground')}
118
- style={selected?.iconStyle}
119
- />
120
- {:else if TriggerIcon}
121
- <TriggerIcon class="h-3.5 w-3.5 shrink-0 text-muted-foreground" />
67
+ <SelectedIcon class={cn('size-3.5', selected?.iconClass ?? 'text-muted-foreground')} />
122
68
  {/if}
123
- <span class={cn('min-w-0 flex-1 truncate', selected ? '' : 'text-muted-foreground')}>
69
+ <span class={cn('min-w-0 flex-1 truncate text-left', !selected && 'text-muted-foreground')}>
124
70
  {selected ? selected.label : placeholder}
125
71
  </span>
126
- <ChevronDown
127
- class={cn(
128
- 'h-3.5 w-3.5 shrink-0 text-muted-foreground transition-transform',
129
- open ? 'rotate-180' : ''
130
- )}
131
- />
132
- </button>
133
-
134
- {#if open}
135
- <div
136
- class="right-0 left-0 mt-1 shadow-md absolute top-full z-50 overflow-hidden rounded-md border border-border bg-popover"
137
- >
138
- {#if showSearch}
139
- <div class="relative border-b border-border">
140
- <Search
141
- class="left-3 h-3.5 w-3.5 pointer-events-none absolute top-1/2 -translate-y-1/2 text-muted-foreground"
142
- />
143
- <input
144
- bind:this={inputRef}
145
- bind:value={query}
146
- onkeydown={handleKey}
147
- placeholder={searchPlaceholder}
148
- class="h-8 pr-3 pl-9 text-sm w-full bg-transparent outline-none placeholder:text-muted-foreground"
149
- />
150
- </div>
72
+ <ChevronDown class="size-3.5 text-muted-foreground" />
73
+ </Popover.Trigger>
74
+ <Popover.Content align="start" class="p-0 w-(--bits-popover-anchor-width)">
75
+ <Command.Root>
76
+ {#if items.length > searchThreshold}
77
+ <Command.Input placeholder={searchPlaceholder} />
151
78
  {/if}
152
-
153
- <div class="max-h-60 overflow-y-auto">
154
- {#if matches.length === 0}
155
- <p class="px-3 py-3 text-xs text-center text-muted-foreground">
156
- No matches for "{query}"
157
- </p>
158
- {:else}
159
- {#each matches as item, i (item.id)}
160
- {@const ItemIcon = item.icon}
161
- <button
162
- type="button"
163
- onclick={() => pick(item)}
164
- onmouseenter={() => (highlighted = i)}
165
- class={cn(
166
- 'gap-2 px-3 py-1.5 text-sm flex w-full items-center text-left transition-colors',
167
- highlighted === i ? 'bg-accent text-accent-foreground' : 'hover:bg-muted/60'
168
- )}
169
- >
170
- {#if ItemIcon}
171
- <ItemIcon
172
- class={cn('h-3.5 w-3.5 shrink-0', item.iconClass ?? 'text-muted-foreground')}
173
- style={item.iconStyle}
174
- />
175
- {/if}
176
- <span class="flex-1 truncate">{item.label}</span>
177
- {#if item.id === value}
178
- <Check class="h-3.5 w-3.5 shrink-0 text-primary" />
179
- {/if}
180
- </button>
181
- {/each}
182
- {/if}
183
- </div>
184
- </div>
185
- {/if}
186
- </div>
79
+ <Command.List>
80
+ <Command.Empty>No matches.</Command.Empty>
81
+ {#each items as item (item.id)}
82
+ {@const ItemIcon = item.icon}
83
+ <!-- Filter on the label; the id is only the key. -->
84
+ <Command.Item
85
+ value={item.id}
86
+ keywords={[item.label]}
87
+ data-checked={item.id === value}
88
+ onSelect={() => pick(item)}
89
+ >
90
+ {#if ItemIcon}
91
+ <ItemIcon class={cn('size-3.5', item.iconClass ?? 'text-muted-foreground')} />
92
+ {/if}
93
+ <span class="truncate">{item.label}</span>
94
+ </Command.Item>
95
+ {/each}
96
+ </Command.List>
97
+ </Command.Root>
98
+ </Popover.Content>
99
+ </Popover.Root>