@selvajs/ui 6.6.0-beta.8 → 7.0.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 (373) hide show
  1. package/README.md +0 -3
  2. package/dist/components/ErrorScreen.svelte +1 -1
  3. package/dist/components/compute/AppLayout.svelte +95 -37
  4. package/dist/components/compute/AppLayout.svelte.d.ts +18 -1
  5. package/dist/components/compute/CollapsedPanelStrip.svelte +4 -2
  6. package/dist/components/compute/ComputeApp.svelte +6 -2
  7. package/dist/components/compute/ComputeApp.svelte.d.ts +1 -1
  8. package/dist/components/compute/ComputeMessagesDialog.svelte +3 -3
  9. package/dist/components/compute/ParameterPresetManager.svelte +9 -21
  10. package/dist/components/compute/SolveMessages.svelte +101 -87
  11. package/dist/components/compute/SolveMessages.svelte.d.ts +12 -0
  12. package/dist/components/layout/AppShell.svelte +2 -2
  13. package/dist/components/layout/FooterItemRenderer.svelte +2 -2
  14. package/dist/components/layout/FooterItemRenderer.svelte.d.ts +1 -1
  15. package/dist/components/layout/PageContent.svelte +3 -2
  16. package/dist/components/layout/PageFooter.svelte +8 -6
  17. package/dist/components/layout/PageHeader.svelte +3 -3
  18. package/dist/components/layout/SideNav.svelte +56 -15
  19. package/dist/components/layout/SubNav.svelte +2 -2
  20. package/dist/components/preview/ChartOutput.svelte +158 -62
  21. package/dist/components/preview/Group.svelte +14 -9
  22. package/dist/components/preview/HelpButton.svelte +1 -1
  23. package/dist/components/preview/ImageOutput.svelte +16 -11
  24. package/dist/components/preview/InputControl.svelte +115 -50
  25. package/dist/components/preview/InputControl.svelte.d.ts +5 -1
  26. package/dist/components/preview/OutputDisplay.svelte +26 -17
  27. package/dist/components/preview/TabBar.svelte +8 -6
  28. package/dist/components/preview/TabBar.svelte.d.ts +2 -0
  29. package/dist/components/preview/TabContent.svelte +65 -33
  30. package/dist/components/preview/TabContent.svelte.d.ts +5 -1
  31. package/dist/components/preview/TabLayout.svelte +29 -8
  32. package/dist/components/preview/TabLayout.svelte.d.ts +5 -0
  33. package/dist/components/preview/inputs/CheckboxInput.svelte +1 -1
  34. package/dist/components/preview/inputs/ChecklistInput.svelte +11 -6
  35. package/dist/components/preview/inputs/ChecklistInput.svelte.d.ts +2 -1
  36. package/dist/components/preview/inputs/ColorInput.svelte +356 -14
  37. package/dist/components/preview/inputs/ColorInput.svelte.d.ts +1 -0
  38. package/dist/components/preview/inputs/DropdownInput.svelte +18 -6
  39. package/dist/components/preview/inputs/DropdownInput.svelte.d.ts +2 -1
  40. package/dist/components/preview/inputs/FileInput.svelte +71 -75
  41. package/dist/components/preview/inputs/GeometryInput.svelte +128 -0
  42. package/dist/components/preview/inputs/GeometryInput.svelte.d.ts +21 -0
  43. package/dist/components/preview/inputs/NumberInput.svelte +8 -8
  44. package/dist/components/preview/inputs/OptionGlyph.svelte +29 -0
  45. package/dist/components/preview/inputs/OptionGlyph.svelte.d.ts +8 -0
  46. package/dist/components/preview/inputs/SegmentedInput.svelte +45 -0
  47. package/dist/components/preview/inputs/SegmentedInput.svelte.d.ts +12 -0
  48. package/dist/components/preview/inputs/TextInput.svelte +5 -5
  49. package/dist/components/preview/inputs/TileInput.svelte +83 -0
  50. package/dist/components/preview/inputs/TileInput.svelte.d.ts +12 -0
  51. package/dist/components/preview/inputs/index.d.ts +3 -0
  52. package/dist/components/preview/inputs/index.js +3 -0
  53. package/dist/components/primitives/CalculateButton.svelte +10 -4
  54. package/dist/components/primitives/Callout.svelte +8 -6
  55. package/dist/components/primitives/Callout.svelte.d.ts +4 -4
  56. package/dist/components/primitives/DataTable.svelte +1 -1
  57. package/dist/components/primitives/FilterableDropdown.svelte +13 -1
  58. package/dist/components/primitives/FilterableDropdown.svelte.d.ts +5 -0
  59. package/dist/components/primitives/ImageUploadField.svelte +1 -1
  60. package/dist/components/primitives/SolvingIndicator.svelte +245 -19
  61. package/dist/components/primitives/SolvingIndicator.svelte.d.ts +15 -0
  62. package/dist/components/primitives/alert-dialog/alert-dialog-content.svelte +1 -1
  63. package/dist/components/primitives/badge/badge.svelte +2 -2
  64. package/dist/components/primitives/badge/badge.svelte.d.ts +6 -6
  65. package/dist/components/primitives/button/button.svelte +9 -7
  66. package/dist/components/primitives/button/button.svelte.d.ts +19 -16
  67. package/dist/components/primitives/card/card.svelte +1 -1
  68. package/dist/components/primitives/card/card.svelte.d.ts +1 -1
  69. package/dist/components/primitives/checkbox/checkbox.svelte +1 -1
  70. package/dist/components/primitives/command/command-input.svelte +1 -1
  71. package/dist/components/primitives/command/command-item.svelte +1 -1
  72. package/dist/components/primitives/command/command-link-item.svelte +1 -1
  73. package/dist/components/primitives/command/command.svelte +1 -1
  74. package/dist/components/primitives/context-menu/context-menu-checkbox-item.svelte +1 -1
  75. package/dist/components/primitives/context-menu/context-menu-content.svelte +1 -1
  76. package/dist/components/primitives/context-menu/context-menu-item.svelte +1 -1
  77. package/dist/components/primitives/context-menu/context-menu-radio-item.svelte +1 -1
  78. package/dist/components/primitives/context-menu/context-menu-sub-content.svelte +1 -1
  79. package/dist/components/primitives/context-menu/context-menu-sub-trigger.svelte +1 -1
  80. package/dist/components/primitives/dialog/dialog-content.svelte +3 -3
  81. package/dist/components/primitives/drawer/Drawer.svelte +1 -1
  82. package/dist/components/primitives/dropdown-menu/dropdown-menu-checkbox-item.svelte +31 -11
  83. package/dist/components/primitives/dropdown-menu/dropdown-menu-checkbox-item.svelte.d.ts +2 -0
  84. package/dist/components/primitives/dropdown-menu/dropdown-menu-content.svelte +5 -1
  85. package/dist/components/primitives/dropdown-menu/dropdown-menu-content.svelte.d.ts +2 -0
  86. package/dist/components/primitives/dropdown-menu/dropdown-menu-item.svelte +4 -2
  87. package/dist/components/primitives/dropdown-menu/dropdown-menu-item.svelte.d.ts +2 -1
  88. package/dist/components/primitives/dropdown-menu/dropdown-menu-radio-item.svelte +1 -1
  89. package/dist/components/primitives/dropdown-menu/dropdown-menu-shortcut.svelte +1 -1
  90. package/dist/components/primitives/dropdown-menu/dropdown-menu-sub-content.svelte +1 -1
  91. package/dist/components/primitives/dropdown-menu/dropdown-menu-sub-trigger.svelte +1 -1
  92. package/dist/components/primitives/index.d.ts +1 -1
  93. package/dist/components/primitives/index.js +1 -1
  94. package/dist/components/primitives/input/input.svelte +2 -2
  95. package/dist/components/primitives/input-group/input-group-input.svelte.d.ts +1 -1
  96. package/dist/components/primitives/input-group/input-group.svelte +1 -1
  97. package/dist/components/primitives/mode-toggle/mode-toggle.svelte +2 -2
  98. package/dist/components/primitives/popover/popover-content.svelte +1 -1
  99. package/dist/components/primitives/resizable/resizable-handle.svelte +2 -2
  100. package/dist/components/primitives/search/search.svelte +1 -1
  101. package/dist/components/primitives/select/select-content.svelte +1 -1
  102. package/dist/components/primitives/select/select-item.svelte +1 -1
  103. package/dist/components/primitives/select/select-trigger.svelte +1 -1
  104. package/dist/components/primitives/slider/slider.svelte +1 -1
  105. package/dist/components/primitives/switch/switch.svelte +1 -1
  106. package/dist/components/primitives/tabs/tabs-list.svelte +2 -2
  107. package/dist/components/primitives/tabs/tabs-list.svelte.d.ts +6 -6
  108. package/dist/components/primitives/tabs/tabs-trigger.svelte +2 -2
  109. package/dist/components/primitives/tabs/tabs-trigger.svelte.d.ts +6 -6
  110. package/dist/components/primitives/textarea/textarea.svelte +1 -1
  111. package/dist/components/primitives/toggle/toggle.svelte +11 -3
  112. package/dist/components/primitives/toggle/toggle.svelte.d.ts +13 -4
  113. package/dist/components/primitives/toggle-group/toggle-group-item.svelte +1 -1
  114. package/dist/components/primitives/toggle-group/toggle-group.svelte +1 -1
  115. package/dist/components/primitives/tooltip/index.d.ts +6 -0
  116. package/dist/components/primitives/tooltip/index.js +8 -0
  117. package/dist/components/primitives/tooltip/tooltip-content.svelte +34 -0
  118. package/dist/components/primitives/tooltip/tooltip-content.svelte.d.ts +10 -0
  119. package/dist/components/primitives/tooltip/tooltip-portal.svelte +7 -0
  120. package/dist/components/primitives/tooltip/tooltip-portal.svelte.d.ts +4 -0
  121. package/dist/components/primitives/tooltip/tooltip-provider.svelte +7 -0
  122. package/dist/components/primitives/tooltip/tooltip-provider.svelte.d.ts +4 -0
  123. package/dist/components/primitives/tooltip/tooltip-trigger.svelte +7 -0
  124. package/dist/components/primitives/tooltip/tooltip-trigger.svelte.d.ts +4 -0
  125. package/dist/components/primitives/tooltip/tooltip.svelte +7 -0
  126. package/dist/components/primitives/tooltip/tooltip.svelte.d.ts +4 -0
  127. package/dist/components/viewer/MeshMetadataPanel.svelte +2 -2
  128. package/dist/components/viewer/SceneManager.svelte +32 -21
  129. package/dist/components/viewer/SceneManager.svelte.d.ts +3 -1
  130. package/dist/components/viewer/Viewer.svelte +135 -185
  131. package/dist/components/viewer/Viewer.svelte.d.ts +7 -2
  132. package/dist/components/viewer/ViewerDock.svelte +404 -0
  133. package/dist/components/viewer/ViewerDock.svelte.d.ts +42 -0
  134. package/dist/composables/useFooterItem.svelte.d.ts +1 -1
  135. package/dist/composables/useFooterItem.svelte.js +1 -1
  136. package/dist/compute/liveProgress.svelte.d.ts +16 -0
  137. package/dist/compute/liveProgress.svelte.js +90 -0
  138. package/dist/compute/solving.svelte.d.ts +11 -7
  139. package/dist/compute/solving.svelte.js +64 -45
  140. package/dist/constants.d.ts +6 -8
  141. package/dist/constants.js +8 -11
  142. package/dist/contexts/hostContext.svelte.d.ts +39 -0
  143. package/dist/contexts/hostContext.svelte.js +9 -0
  144. package/dist/features/chart/plotly-compat.d.ts +15 -0
  145. package/dist/features/chart/plotly-compat.js +124 -0
  146. package/dist/i18n/messages.d.ts +50 -0
  147. package/dist/i18n/messages.js +84 -0
  148. package/dist/index.d.ts +5 -0
  149. package/dist/index.js +5 -0
  150. package/dist/public.d.ts +1 -0
  151. package/dist/public.js +1 -0
  152. package/dist/schema/host-support.d.ts +29 -0
  153. package/dist/schema/host-support.js +97 -0
  154. package/dist/schema/option-display.d.ts +12 -0
  155. package/dist/schema/option-display.js +29 -0
  156. package/dist/schema/param-exporter.d.ts +4 -1
  157. package/dist/schema/param-exporter.js +10 -6
  158. package/dist/schema/visibility-rules.d.ts +7 -1
  159. package/dist/schema/visibility-rules.js +17 -0
  160. package/dist/styles/base.css +94 -84
  161. package/dist/utils/color-space.d.ts +21 -0
  162. package/dist/utils/color-space.js +90 -0
  163. package/dist/utils/file-download.js +6 -10
  164. package/dist/utils/host-files.d.ts +12 -0
  165. package/dist/utils/host-files.js +46 -0
  166. package/dist/utils/webp-to-png.d.ts +10 -0
  167. package/dist/utils/webp-to-png.js +41 -0
  168. package/package.json +31 -23
  169. package/src/lib/components/ErrorScreen.svelte +1 -1
  170. package/src/lib/components/compute/AppLayout.svelte +96 -38
  171. package/src/lib/components/compute/CollapsedPanelStrip.svelte +4 -2
  172. package/src/lib/components/compute/ComputeApp.svelte +6 -2
  173. package/src/lib/components/compute/ComputeMessagesDialog.svelte +4 -4
  174. package/src/lib/components/compute/ParameterPresetManager.svelte +9 -21
  175. package/src/lib/components/compute/SolveMessages.svelte +103 -89
  176. package/src/lib/components/layout/AppShell.svelte +2 -2
  177. package/src/lib/components/layout/FooterItemRenderer.svelte +2 -2
  178. package/src/lib/components/layout/PageContent.svelte +3 -2
  179. package/src/lib/components/layout/PageFooter.svelte +8 -6
  180. package/src/lib/components/layout/PageHeader.svelte +3 -3
  181. package/src/lib/components/layout/SideNav.svelte +56 -15
  182. package/src/lib/components/layout/SubNav.svelte +2 -2
  183. package/src/lib/components/preview/ChartOutput.svelte +158 -62
  184. package/src/lib/components/preview/Group.svelte +14 -9
  185. package/src/lib/components/preview/HelpButton.svelte +1 -1
  186. package/src/lib/components/preview/ImageOutput.svelte +16 -11
  187. package/src/lib/components/preview/InputControl.svelte +115 -50
  188. package/src/lib/components/preview/OutputDisplay.svelte +26 -17
  189. package/src/lib/components/preview/TabBar.svelte +8 -6
  190. package/src/lib/components/preview/TabContent.svelte +65 -33
  191. package/src/lib/components/preview/TabLayout.svelte +29 -8
  192. package/src/lib/components/preview/inputs/CheckboxInput.svelte +1 -1
  193. package/src/lib/components/preview/inputs/ChecklistInput.svelte +11 -6
  194. package/src/lib/components/preview/inputs/ColorInput.svelte +356 -14
  195. package/src/lib/components/preview/inputs/DropdownInput.svelte +18 -6
  196. package/src/lib/components/preview/inputs/FileInput.svelte +71 -75
  197. package/src/lib/components/preview/inputs/GeometryInput.svelte +128 -0
  198. package/src/lib/components/preview/inputs/NumberInput.svelte +8 -8
  199. package/src/lib/components/preview/inputs/OptionGlyph.svelte +29 -0
  200. package/src/lib/components/preview/inputs/SegmentedInput.svelte +45 -0
  201. package/src/lib/components/preview/inputs/TextInput.svelte +5 -5
  202. package/src/lib/components/preview/inputs/TileInput.svelte +83 -0
  203. package/src/lib/components/preview/inputs/index.ts +3 -0
  204. package/src/lib/components/primitives/CalculateButton.svelte +10 -4
  205. package/src/lib/components/primitives/Callout.svelte +9 -7
  206. package/src/lib/components/primitives/DataTable.svelte +1 -1
  207. package/src/lib/components/primitives/FilterableDropdown.svelte +14 -2
  208. package/src/lib/components/primitives/ImageUploadField.svelte +1 -1
  209. package/src/lib/components/primitives/IssueList.svelte +1 -1
  210. package/src/lib/components/primitives/SeverityIcon.svelte +1 -1
  211. package/src/lib/components/primitives/SolvingIndicator.svelte +245 -19
  212. package/src/lib/components/primitives/StateDisplay.svelte +1 -1
  213. package/src/lib/components/primitives/alert-dialog/alert-dialog-action.svelte +2 -2
  214. package/src/lib/components/primitives/alert-dialog/alert-dialog-cancel.svelte +2 -2
  215. package/src/lib/components/primitives/alert-dialog/alert-dialog-content.svelte +2 -2
  216. package/src/lib/components/primitives/alert-dialog/alert-dialog-description.svelte +1 -1
  217. package/src/lib/components/primitives/alert-dialog/alert-dialog-footer.svelte +1 -1
  218. package/src/lib/components/primitives/alert-dialog/alert-dialog-header.svelte +1 -1
  219. package/src/lib/components/primitives/alert-dialog/alert-dialog-overlay.svelte +1 -1
  220. package/src/lib/components/primitives/alert-dialog/alert-dialog-title.svelte +1 -1
  221. package/src/lib/components/primitives/badge/badge.svelte +3 -3
  222. package/src/lib/components/primitives/button/button.svelte +9 -7
  223. package/src/lib/components/primitives/button-group/button-group-separator.svelte +2 -2
  224. package/src/lib/components/primitives/button-group/button-group-text.svelte +1 -1
  225. package/src/lib/components/primitives/button-group/button-group.svelte +1 -1
  226. package/src/lib/components/primitives/card/card-content.svelte +1 -1
  227. package/src/lib/components/primitives/card/card-description.svelte +1 -1
  228. package/src/lib/components/primitives/card/card-footer.svelte +1 -1
  229. package/src/lib/components/primitives/card/card-header.svelte +1 -1
  230. package/src/lib/components/primitives/card/card-title.svelte +1 -1
  231. package/src/lib/components/primitives/card/card.svelte +2 -2
  232. package/src/lib/components/primitives/checkbox/checkbox.svelte +2 -2
  233. package/src/lib/components/primitives/command/command-dialog.svelte +2 -2
  234. package/src/lib/components/primitives/command/command-empty.svelte +1 -1
  235. package/src/lib/components/primitives/command/command-group.svelte +1 -1
  236. package/src/lib/components/primitives/command/command-input.svelte +3 -3
  237. package/src/lib/components/primitives/command/command-item.svelte +2 -2
  238. package/src/lib/components/primitives/command/command-link-item.svelte +2 -2
  239. package/src/lib/components/primitives/command/command-list.svelte +1 -1
  240. package/src/lib/components/primitives/command/command-separator.svelte +1 -1
  241. package/src/lib/components/primitives/command/command-shortcut.svelte +1 -1
  242. package/src/lib/components/primitives/command/command.svelte +2 -2
  243. package/src/lib/components/primitives/context-menu/context-menu-checkbox-item.svelte +2 -2
  244. package/src/lib/components/primitives/context-menu/context-menu-content.svelte +2 -2
  245. package/src/lib/components/primitives/context-menu/context-menu-group-heading.svelte +1 -1
  246. package/src/lib/components/primitives/context-menu/context-menu-item.svelte +2 -2
  247. package/src/lib/components/primitives/context-menu/context-menu-label.svelte +1 -1
  248. package/src/lib/components/primitives/context-menu/context-menu-radio-item.svelte +2 -2
  249. package/src/lib/components/primitives/context-menu/context-menu-separator.svelte +1 -1
  250. package/src/lib/components/primitives/context-menu/context-menu-shortcut.svelte +1 -1
  251. package/src/lib/components/primitives/context-menu/context-menu-sub-content.svelte +2 -2
  252. package/src/lib/components/primitives/context-menu/context-menu-sub-trigger.svelte +2 -2
  253. package/src/lib/components/primitives/dialog/dialog-content.svelte +4 -4
  254. package/src/lib/components/primitives/dialog/dialog-description.svelte +1 -1
  255. package/src/lib/components/primitives/dialog/dialog-footer.svelte +1 -1
  256. package/src/lib/components/primitives/dialog/dialog-header.svelte +1 -1
  257. package/src/lib/components/primitives/dialog/dialog-overlay.svelte +1 -1
  258. package/src/lib/components/primitives/dialog/dialog-title.svelte +1 -1
  259. package/src/lib/components/primitives/drawer/Drawer.svelte +1 -1
  260. package/src/lib/components/primitives/dropdown-menu/dropdown-menu-checkbox-item.svelte +32 -12
  261. package/src/lib/components/primitives/dropdown-menu/dropdown-menu-content.svelte +6 -2
  262. package/src/lib/components/primitives/dropdown-menu/dropdown-menu-group-heading.svelte +1 -1
  263. package/src/lib/components/primitives/dropdown-menu/dropdown-menu-item.svelte +5 -3
  264. package/src/lib/components/primitives/dropdown-menu/dropdown-menu-label.svelte +1 -1
  265. package/src/lib/components/primitives/dropdown-menu/dropdown-menu-radio-item.svelte +2 -2
  266. package/src/lib/components/primitives/dropdown-menu/dropdown-menu-separator.svelte +1 -1
  267. package/src/lib/components/primitives/dropdown-menu/dropdown-menu-shortcut.svelte +2 -2
  268. package/src/lib/components/primitives/dropdown-menu/dropdown-menu-sub-content.svelte +2 -2
  269. package/src/lib/components/primitives/dropdown-menu/dropdown-menu-sub-trigger.svelte +2 -2
  270. package/src/lib/components/primitives/empty/empty-content.svelte +1 -1
  271. package/src/lib/components/primitives/empty/empty-description.svelte +1 -1
  272. package/src/lib/components/primitives/empty/empty-header.svelte +1 -1
  273. package/src/lib/components/primitives/empty/empty-media.svelte +1 -1
  274. package/src/lib/components/primitives/empty/empty-title.svelte +1 -1
  275. package/src/lib/components/primitives/empty/empty.svelte +1 -1
  276. package/src/lib/components/primitives/field/field-content.svelte +1 -1
  277. package/src/lib/components/primitives/field/field-description.svelte +1 -1
  278. package/src/lib/components/primitives/field/field-error.svelte +1 -1
  279. package/src/lib/components/primitives/field/field-group.svelte +1 -1
  280. package/src/lib/components/primitives/field/field-label.svelte +2 -2
  281. package/src/lib/components/primitives/field/field-legend.svelte +1 -1
  282. package/src/lib/components/primitives/field/field-separator.svelte +2 -2
  283. package/src/lib/components/primitives/field/field-set.svelte +1 -1
  284. package/src/lib/components/primitives/field/field-title.svelte +1 -1
  285. package/src/lib/components/primitives/field/field.svelte +1 -1
  286. package/src/lib/components/primitives/index.ts +1 -1
  287. package/src/lib/components/primitives/input/input.svelte +3 -3
  288. package/src/lib/components/primitives/input-group/input-group-addon.svelte +1 -1
  289. package/src/lib/components/primitives/input-group/input-group-button.svelte +2 -2
  290. package/src/lib/components/primitives/input-group/input-group-input.svelte +2 -2
  291. package/src/lib/components/primitives/input-group/input-group-text.svelte +1 -1
  292. package/src/lib/components/primitives/input-group/input-group-textarea.svelte +2 -2
  293. package/src/lib/components/primitives/input-group/input-group.svelte +2 -2
  294. package/src/lib/components/primitives/label/label.svelte +1 -1
  295. package/src/lib/components/primitives/mode-toggle/mode-toggle.svelte +2 -2
  296. package/src/lib/components/primitives/popover/popover-content.svelte +2 -2
  297. package/src/lib/components/primitives/popover/popover-description.svelte +1 -1
  298. package/src/lib/components/primitives/popover/popover-header.svelte +1 -1
  299. package/src/lib/components/primitives/popover/popover-title.svelte +1 -1
  300. package/src/lib/components/primitives/popover/popover-trigger.svelte +1 -1
  301. package/src/lib/components/primitives/resizable/resizable-handle.svelte +3 -3
  302. package/src/lib/components/primitives/resizable/resizable-pane-group.svelte +1 -1
  303. package/src/lib/components/primitives/scroll-area/scroll-area-scrollbar.svelte +1 -1
  304. package/src/lib/components/primitives/scroll-area/scroll-area.svelte +1 -1
  305. package/src/lib/components/primitives/search/search.svelte +2 -2
  306. package/src/lib/components/primitives/select/select-content.svelte +2 -2
  307. package/src/lib/components/primitives/select/select-group-heading.svelte +1 -1
  308. package/src/lib/components/primitives/select/select-item.svelte +2 -2
  309. package/src/lib/components/primitives/select/select-label.svelte +1 -1
  310. package/src/lib/components/primitives/select/select-scroll-down-button.svelte +1 -1
  311. package/src/lib/components/primitives/select/select-scroll-up-button.svelte +1 -1
  312. package/src/lib/components/primitives/select/select-separator.svelte +2 -2
  313. package/src/lib/components/primitives/select/select-trigger.svelte +2 -2
  314. package/src/lib/components/primitives/separator/separator.svelte +1 -1
  315. package/src/lib/components/primitives/slider/slider.svelte +2 -2
  316. package/src/lib/components/primitives/spinner/spinner.svelte +1 -1
  317. package/src/lib/components/primitives/switch/switch.svelte +2 -2
  318. package/src/lib/components/primitives/tabs/tabs-content.svelte +1 -1
  319. package/src/lib/components/primitives/tabs/tabs-list.svelte +3 -3
  320. package/src/lib/components/primitives/tabs/tabs-trigger.svelte +3 -3
  321. package/src/lib/components/primitives/tabs/tabs.svelte +1 -1
  322. package/src/lib/components/primitives/textarea/textarea.svelte +2 -2
  323. package/src/lib/components/primitives/toggle/toggle.svelte +12 -4
  324. package/src/lib/components/primitives/toggle-group/toggle-group-item.svelte +3 -3
  325. package/src/lib/components/primitives/toggle-group/toggle-group.svelte +3 -3
  326. package/src/lib/components/primitives/tooltip/index.ts +19 -0
  327. package/src/lib/components/primitives/tooltip/tooltip-content.svelte +34 -0
  328. package/src/lib/components/primitives/tooltip/tooltip-portal.svelte +7 -0
  329. package/src/lib/components/primitives/tooltip/tooltip-provider.svelte +7 -0
  330. package/src/lib/components/primitives/tooltip/tooltip-trigger.svelte +7 -0
  331. package/src/lib/components/primitives/tooltip/tooltip.svelte +7 -0
  332. package/src/lib/components/viewer/MeshMetadataPanel.svelte +2 -2
  333. package/src/lib/components/viewer/SceneManager.svelte +32 -21
  334. package/src/lib/components/viewer/Viewer.svelte +136 -186
  335. package/src/lib/components/viewer/ViewerDock.svelte +404 -0
  336. package/src/lib/composables/useFooterItem.svelte.ts +1 -1
  337. package/src/lib/compute/liveProgress.svelte.ts +103 -0
  338. package/src/lib/compute/solving.svelte.ts +66 -50
  339. package/src/lib/constants.ts +8 -11
  340. package/src/lib/contexts/hostContext.svelte.ts +51 -0
  341. package/src/lib/features/chart/plotly-compat.ts +141 -0
  342. package/src/lib/i18n/messages.ts +139 -0
  343. package/src/lib/index.ts +22 -0
  344. package/src/lib/public.ts +1 -0
  345. package/src/lib/schema/host-support.ts +129 -0
  346. package/src/lib/schema/option-display.ts +44 -0
  347. package/src/lib/schema/param-exporter.ts +10 -7
  348. package/src/lib/schema/visibility-rules.ts +27 -1
  349. package/src/lib/styles/base.css +94 -84
  350. package/src/lib/utils/color-space.ts +107 -0
  351. package/src/lib/utils/file-download.ts +7 -11
  352. package/src/lib/utils/host-files.ts +61 -0
  353. package/src/lib/utils/webp-to-png.ts +48 -0
  354. package/dist/components/primitives/theme-switcher/ThemeSwitcher.svelte +0 -39
  355. package/dist/components/primitives/theme-switcher/ThemeSwitcher.svelte.d.ts +0 -3
  356. package/dist/components/primitives/theme-switcher/index.d.ts +0 -1
  357. package/dist/components/primitives/theme-switcher/index.js +0 -1
  358. package/dist/styles/themes/cyberpunk.css +0 -98
  359. package/dist/styles/themes/neutral.css +0 -72
  360. package/dist/styles/themes/ocean.css +0 -75
  361. package/dist/styles/themes/selva.css +0 -105
  362. package/dist/theme/themeStore.svelte.d.ts +0 -12
  363. package/dist/theme/themeStore.svelte.js +0 -40
  364. package/dist/theme/themes.d.ts +0 -5
  365. package/dist/theme/themes.js +0 -13
  366. package/src/lib/components/primitives/theme-switcher/ThemeSwitcher.svelte +0 -39
  367. package/src/lib/components/primitives/theme-switcher/index.ts +0 -1
  368. package/src/lib/styles/themes/cyberpunk.css +0 -98
  369. package/src/lib/styles/themes/neutral.css +0 -72
  370. package/src/lib/styles/themes/ocean.css +0 -75
  371. package/src/lib/styles/themes/selva.css +0 -105
  372. package/src/lib/theme/themeStore.svelte.ts +0 -50
  373. package/src/lib/theme/themes.ts +0 -18
@@ -10,6 +10,7 @@
10
10
  } from '../../schema/param-exporter';
11
11
  import { Button, Input, Label, Textarea, Dialog, Callout, IssueList, toast } from '../primitives';
12
12
  import { DEFAULT_PRESET_LABELS, type PresetLabels } from '../../types/presetLabels';
13
+ import { pickFile } from '../../utils/host-files';
13
14
 
14
15
  interface Props {
15
16
  schema: UISchema;
@@ -37,7 +38,6 @@
37
38
  let showLoadDialog = $state(false);
38
39
  let loadResult = $state<PresetLoadResult | null>(null);
39
40
  let loadingPresetName = $state('');
40
- let fileInputRef = $state<HTMLInputElement | null>(null);
41
41
 
42
42
  let listedStates = $state<ParameterPreset[]>([]);
43
43
  let isLoadingList = $state(false);
@@ -69,7 +69,7 @@
69
69
  });
70
70
 
71
71
  if (onSaveState) await onSaveState(state);
72
- else exportStateAsJson(state);
72
+ else await exportStateAsJson(state);
73
73
  showExportDialog = false;
74
74
  }
75
75
 
@@ -86,19 +86,16 @@
86
86
  }
87
87
  }
88
88
 
89
- async function handleImport(event: Event) {
90
- const input = event.target as HTMLInputElement;
91
- if (!input.files || input.files.length === 0) return;
89
+ async function importFromFile() {
90
+ // .sps is the pre-rename extension; files with it exist on users' drives, so keep accepting it.
91
+ const file = await pickFile({ accept: '.slvp,.sps' });
92
+ if (!file) return;
92
93
 
93
94
  try {
94
- const imported = await importStateFromJson(input.files[0]);
95
- tryLoad(imported);
95
+ tryLoad(await importStateFromJson(file));
96
96
  } catch (error) {
97
97
  toast.error(t.loadImportError + (error as Error).message);
98
98
  }
99
-
100
- // Reset so re-picking the same file fires `change` again.
101
- input.value = '';
102
99
  }
103
100
 
104
101
  function confirmLoad() {
@@ -132,8 +129,8 @@
132
129
  }
133
130
 
134
131
  function handleLoadClick() {
135
- fileInputRef?.click();
136
132
  showLoadDialog = false;
133
+ void importFromFile();
137
134
  }
138
135
 
139
136
  function selectListedState(preset: ParameterPreset) {
@@ -142,15 +139,6 @@
142
139
  }
143
140
  </script>
144
141
 
145
- <!-- .sps is the pre-rename extension; files with it exist on users' drives, so keep accepting it. -->
146
- <input
147
- bind:this={fileInputRef}
148
- type="file"
149
- accept=".slvp,.sps"
150
- onchange={handleImport}
151
- class="hidden"
152
- />
153
-
154
142
  <!-- Save Dialog -->
155
143
  <Dialog.Root bind:open={showExportDialog}>
156
144
  <Dialog.Content>
@@ -215,7 +203,7 @@
215
203
  <button
216
204
  type="button"
217
205
  onclick={() => selectListedState(preset)}
218
- class="p-3 w-full rounded-lg border text-left transition-colors hover:bg-muted"
206
+ class="p-3 w-full rounded-lg border text-left transition-colors hover:bg-hover"
219
207
  >
220
208
  <div class="text-sm font-medium">{preset.name}</div>
221
209
  {#if preset.description}
@@ -16,10 +16,11 @@
16
16
  import { LoaderCircle } from '@lucide/svelte';
17
17
  import type { SolveDiagnostic } from '@selvajs/solve/shared';
18
18
  import type { LiveSolveState, SolvePhase } from '@selvajs/solve/client';
19
- import { Button } from '$lib/components/primitives/button';
20
- import IssueList, { type Issue } from '$lib/components/primitives/IssueList.svelte';
21
- import SeverityIcon from '$lib/components/primitives/SeverityIcon.svelte';
22
- import { getLocaleContext } from '$lib/i18n/localeContext.svelte';
19
+ import { Button } from '#lib/components/primitives/button/index.js';
20
+ import IssueList, { type Issue } from '#lib/components/primitives/IssueList.svelte';
21
+ import SeverityIcon from '#lib/components/primitives/SeverityIcon.svelte';
22
+ import { getLocaleContext } from '#lib/i18n/localeContext.svelte.js';
23
+ import { createLiveProgress } from '#lib/compute/liveProgress.svelte.js';
23
24
 
24
25
  interface Props {
25
26
  /** What the running (or most recent) solve has said over the live channel. */
@@ -31,6 +32,18 @@
31
32
  onabort: () => void;
32
33
  onconfirm: () => void;
33
34
  ondiscard: () => void;
35
+ /**
36
+ * See `createSolvingIndicator`. The live panel would only paint once the solve is over, so
37
+ * an Esc hint stands in for it; the result's messages still show.
38
+ */
39
+ freezesDuringSolve?: boolean;
40
+ /** `floating` overlays the bottom centre; `inline` sits in the flow below the host's content. */
41
+ placement?: 'floating' | 'inline';
42
+ /**
43
+ * The host shows progress and Abort itself (`AppLayout`'s solve status), so the live view
44
+ * keeps only the messages that arrive mid-solve.
45
+ */
46
+ progressShownByHost?: boolean;
34
47
  }
35
48
 
36
49
  let {
@@ -39,7 +52,10 @@
39
52
  diagnostics = [],
40
53
  onabort,
41
54
  onconfirm,
42
- ondiscard
55
+ ondiscard,
56
+ freezesDuringSolve = false,
57
+ placement = 'floating',
58
+ progressShownByHost = false
43
59
  }: Props = $props();
44
60
 
45
61
  const locale = getLocaleContext();
@@ -47,50 +63,10 @@
47
63
 
48
64
  const REMARK_LINGER_MS = 6000;
49
65
 
50
- const progress = $derived([...live.progress.values()]);
51
-
52
- // A report only arrives when a step starts, so a bar that just shows `fraction` stands still
53
- // through the step and then jumps. When the plugin knows how long the step took last time, the
54
- // bar runs linearly to where the step will end, over that time. Steps are weighted by time, so
55
- // linear gives one steady speed across the solve; any easing curve visibly speeds up and
56
- // crawls within each step. A slower run waits at the step's end; a faster one snaps ahead.
57
- let bars = $state.raw<Record<string, { width: number; ms: number }>>({});
58
- // Any live event re-runs the effect below. Only a source with a new report may re-snap;
59
- // the rest keep easing where they are.
60
- let seen = new Map<string, unknown>();
61
- let targets: Record<string, { width: number; ms: number }> = {};
62
- $effect(() => {
63
- const snapped: Record<string, { width: number; ms: number }> = {};
64
- const eased: Record<string, { width: number; ms: number }> = {};
65
- const current = untrack(() => bars);
66
- // eslint-disable-next-line svelte/prefer-svelte-reactivity -- bookkeeping for this effect; reactive reads here would re-trigger it
67
- const nextSeen = new Map<string, unknown>();
68
- for (const p of progress) {
69
- if (p.fraction === undefined) continue;
70
- const key = p.source ?? '';
71
- nextSeen.set(key, p);
72
- if (seen.get(key) === p && current[key]) {
73
- snapped[key] = current[key];
74
- // This run's cleanup cancelled the frame that would have started the ease.
75
- if (targets[key] && current[key] !== targets[key]) eased[key] = targets[key];
76
- continue;
77
- }
78
- snapped[key] = { width: p.fraction, ms: 150 };
79
- if (p.nextFraction !== undefined && p.stepMs !== undefined) {
80
- eased[key] = { width: p.nextFraction, ms: p.stepMs };
81
- }
82
- }
83
- seen = nextSeen;
84
- targets = eased;
85
- bars = snapped;
86
- if (Object.keys(eased).length === 0) return;
87
- // Two frames: the snapped width must be painted before the eased one is set, or the
88
- // browser folds both into one style change and there is nothing to transition from.
89
- let frame = requestAnimationFrame(() => {
90
- frame = requestAnimationFrame(() => (bars = { ...snapped, ...eased }));
91
- });
92
- return () => cancelAnimationFrame(frame);
93
- });
66
+ const liveProgress = createLiveProgress(() => live);
67
+ const progress = $derived(liveProgress.progress);
68
+ const bars = $derived(liveProgress.bars);
69
+ const headline = $derived(liveProgress.headline);
94
70
  // Grasshopper's own complaints stay in the footer log; this panel shows what the author wrote.
95
71
  const authored = $derived(diagnostics.filter((d) => d.isGate));
96
72
 
@@ -117,16 +93,30 @@
117
93
  // Keyed on the diagnostics array's identity, which every report renews, rather than on a
118
94
  // solve id: only the live channel has one, and on the Compute path remarks arrive with the
119
95
  // result. Once per array, so remarks that sat behind a review do not reappear after Continue.
96
+ //
97
+ // A remark is shown once while it stays raised, not after every solve. Every report repeats
98
+ // it: Grasshopper keeps runtime messages on a Message component an input change did not
99
+ // reach, and Compute re-solves the whole definition. It shows again once a solve stops
100
+ // raising it and a later one raises it anew.
120
101
  let lingering = $state(false);
121
102
  let lingeredFor: SolveDiagnostic[] | null = null;
103
+ let shownRemarks: ReadonlySet<string> = new Set();
104
+ let freshRemarks = $state.raw<SolveDiagnostic[]>([]);
105
+ const remarkKey = (d: SolveDiagnostic) => `${d.source ?? ''}\u0000${d.message}`;
122
106
  $effect(() => {
123
107
  if (phase === 'live') {
124
108
  lingering = false;
125
109
  return;
126
110
  }
127
- if (authoredRemarks.length === 0 || lingeredFor === diagnostics) return;
111
+ if (lingeredFor === diagnostics) return;
128
112
  lingeredFor = diagnostics;
129
- if (phase !== 'idle') return;
113
+ const raised = authoredRemarks.map(remarkKey);
114
+ const stillShown = raised.filter((k) => shownRemarks.has(k));
115
+ const fresh = authoredRemarks.filter((d) => !shownRemarks.has(remarkKey(d)));
116
+ const showNow = phase === 'idle' && fresh.length > 0;
117
+ shownRemarks = new Set(showNow ? [...stillShown, ...fresh.map(remarkKey)] : stillShown);
118
+ if (!showNow) return;
119
+ freshRemarks = fresh;
130
120
  lingering = true;
131
121
  const timer = setTimeout(() => (lingering = false), REMARK_LINGER_MS);
132
122
  return () => clearTimeout(timer);
@@ -145,6 +135,7 @@
145
135
  $effect(() => {
146
136
  if (phase === 'live') {
147
137
  finishing = false;
138
+ if (freezesDuringSolve) return;
148
139
  const timer = setTimeout(() => {
149
140
  revealed = true;
150
141
  revealedAt = performance.now();
@@ -171,7 +162,7 @@
171
162
  // While solving, the live channel is all there is. Once the result lands it is authoritative
172
163
  // — it carries remarks too — so the live list is dropped entirely and nothing is shown twice.
173
164
  const rows = $derived<SolveDiagnostic[]>(
174
- view === 'live' ? [...live.diagnostics] : view === 'idle' ? authoredRemarks : authoredGating
165
+ view === 'live' ? [...live.diagnostics] : view === 'idle' ? freshRemarks : authoredGating
175
166
  );
176
167
 
177
168
  // A remark states something without asking anything, so it reads as a quiet note.
@@ -183,37 +174,11 @@
183
174
  }))
184
175
  );
185
176
 
186
- // Steps a few milliseconds long would swap the headline faster than it can be read. Each one
187
- // stays up for at least this long; steps that end sooner are skipped in the label but still
188
- // count in the bar. The label and its step count move together so they never disagree.
189
- const HEADLINE_MIN_MS = 200;
190
- let headline = $state.raw<(typeof progress)[number] | undefined>(undefined);
191
- let headlineAt = 0;
192
- $effect(() => {
193
- const next = progress[0];
194
- const shown = untrack(() => headline);
195
- if (next?.label === shown?.label && next?.done === shown?.done) {
196
- headline = next;
197
- return;
198
- }
199
- const show = () => {
200
- headline = next;
201
- headlineAt = performance.now();
202
- };
203
- const wait = HEADLINE_MIN_MS - (performance.now() - headlineAt);
204
- if (!shown || wait <= 0) {
205
- show();
206
- return;
207
- }
208
- const timer = setTimeout(show, wait);
209
- return () => clearTimeout(timer);
210
- });
211
-
212
177
  const visible = $derived(
213
178
  view === 'review' || view === 'blocked'
214
179
  ? true
215
180
  : view === 'live'
216
- ? revealed && (rows.length > 0 || progress.length > 0)
181
+ ? revealed && (rows.length > 0 || (!progressShownByHost && progress.length > 0))
217
182
  : rows.length > 0 && lingering
218
183
  );
219
184
 
@@ -235,10 +200,14 @@
235
200
  }
236
201
 
237
202
  // Escape resolves to the safe side — discard, keeping what is on screen — never to silently
238
- // applying a result the user has not accepted.
203
+ // applying a result the user has not accepted. A frozen host gets no keydown until the solve
204
+ // ends; there the plugin aborts on the key state, which only counts while Rhino is in front, so
205
+ // a browser tab (local or Compute) has to abort from the page.
239
206
  function handleKeydown(e: KeyboardEvent) {
240
207
  if (e.key !== 'Escape') return;
241
- if (phase === 'review') ondiscard();
208
+ if (phase === 'live') {
209
+ if (!freezesDuringSolve) onabort();
210
+ } else if (phase === 'review') ondiscard();
242
211
  else if (phase === 'blocked') dismiss();
243
212
  }
244
213
  </script>
@@ -247,17 +216,29 @@
247
216
 
248
217
  <div class="sr-only" role="status" aria-live="polite">{announcement}</div>
249
218
 
250
- {#if visible}
219
+ {#if freezesDuringSolve && view === 'live'}
220
+ <!-- Painted before the solve takes the thread; Abort could not be clicked until it ends. Inline,
221
+ the solving chip carries the hint instead: a line here would resize the footer mid-solve. -->
222
+ {#if placement === 'floating'}
223
+ <p
224
+ class="bottom-4 text-xs fixed left-1/2 z-50 -translate-x-1/2 text-muted-foreground"
225
+ data-testid="solve-esc-hint"
226
+ >
227
+ {t.solveMessagesEscHint}
228
+ </p>
229
+ {/if}
230
+ {:else if visible}
231
+ <!-- Inline, it is a row of the host's footer like the Calculate row under it, not a card: a
232
+ framed box inside an edge-to-edge column reads as a dialog glued into the page. -->
251
233
  <div
252
- class="bottom-4 p-3 shadow-lg w-lg fixed left-1/2 z-50 max-w-screen-inset -translate-x-1/2 rounded-lg border bg-background {view ===
253
- 'blocked'
254
- ? 'border-destructive/50'
255
- : ''}"
234
+ class={placement === 'inline'
235
+ ? `px-2 py-2 border-t ${view === 'blocked' ? 'border-destructive/40' : 'border-border'}`
236
+ : `p-3 bottom-4 shadow-lg w-lg fixed left-1/2 z-50 max-w-screen-inset -translate-x-1/2 rounded-lg border bg-background ${view === 'blocked' ? 'border-destructive/50' : ''}`}
256
237
  role={view === 'review' || view === 'blocked' ? 'alert' : undefined}
257
238
  data-testid="solve-messages"
258
239
  data-view={view}
259
240
  >
260
- {#if view === 'live'}
241
+ {#if view === 'live' && !progressShownByHost}
261
242
  {@const head = headline ?? progress[0]}
262
243
  <div>
263
244
  <div>
@@ -275,8 +256,13 @@
275
256
  class="-my-1 shrink-0"
276
257
  disabled={finishing}
277
258
  onclick={onabort}
259
+ aria-keyshortcuts="Escape"
260
+ title={t.solveMessagesEscHint}
278
261
  >
279
262
  {t.solveMessagesAbort}
263
+ <kbd class="px-1 rounded-sm border border-border font-sans text-2xs opacity-70">
264
+ Esc
265
+ </kbd>
280
266
  </Button>
281
267
  </div>
282
268
  {#each progress as p, i (p.source ?? '')}
@@ -315,6 +301,30 @@
315
301
  {/each}
316
302
  </div>
317
303
  </div>
304
+ {:else if (view === 'review' || view === 'blocked') && placement === 'inline'}
305
+ <!-- Each message carries its own icon, so the label goes without one; the buttons share
306
+ its row rather than adding one under a single line of text. -->
307
+ <div class="mb-1.5 gap-2 flex items-center">
308
+ <span
309
+ class="min-w-0 text-xs font-medium flex-1 truncate {view === 'blocked'
310
+ ? 'text-destructive'
311
+ : 'text-muted-foreground'}"
312
+ >
313
+ {view === 'blocked' ? t.solveBlockedTitle : t.solveMessagesTitle}
314
+ </span>
315
+ {#if view === 'blocked'}
316
+ <Button variant="ghost" size="xs" class="-my-1 shrink-0" onclick={dismiss}>
317
+ {t.solveMessagesDismiss}
318
+ </Button>
319
+ {:else}
320
+ <Button variant="ghost" size="xs" class="-my-1 shrink-0" onclick={ondiscard}>
321
+ {t.solveMessagesDiscard}
322
+ </Button>
323
+ <Button size="xs" class="-my-1 shrink-0" onclick={onconfirm}>
324
+ {t.solveMessagesConfirm}
325
+ </Button>
326
+ {/if}
327
+ </div>
318
328
  {:else if view === 'review' || view === 'blocked'}
319
329
  <div class="mb-2">
320
330
  <div class="gap-2 text-sm font-medium flex items-center">
@@ -333,12 +343,16 @@
333
343
  {/if}
334
344
 
335
345
  {#if rows.length > 0}
336
- <div class="max-h-40 overflow-y-auto {view === 'live' ? 'mt-3 pt-3 border-t' : ''}">
346
+ <div
347
+ class="max-h-40 overflow-y-auto {view === 'live' && !progressShownByHost
348
+ ? 'mt-3 pt-3 border-t'
349
+ : ''}"
350
+ >
337
351
  <IssueList {issues} variant="plain" />
338
352
  </div>
339
353
  {/if}
340
354
 
341
- {#if view === 'review' || view === 'blocked'}
355
+ {#if (view === 'review' || view === 'blocked') && placement === 'floating'}
342
356
  <div class="mt-3 gap-2 flex justify-end">
343
357
  {#if view === 'blocked'}
344
358
  <Button variant="outline" size="sm" onclick={dismiss}>{t.solveMessagesDismiss}</Button>
@@ -94,14 +94,14 @@
94
94
  const sidenavWrapClass = $derived(
95
95
  _mode === 'fixed'
96
96
  ? 'flex shrink-0'
97
- : 'flex shrink-0 sm:self-start sm:sticky sm:top-(--header-h) sm:max-h-[calc(100svh-var(--header-h))]'
97
+ : 'flex shrink-0 sm:self-start sm:sticky sm:top-(--header-h) sm:max-h-(--below-header-h)'
98
98
  );
99
99
 
100
100
  // The sticky wrapper is only as tall as its content, so in scroll mode the divider comes
101
101
  // from the main column's left edge — that one spans the full body height.
102
102
  const mainClass = $derived(
103
103
  _mode === 'fixed'
104
- ? 'min-h-0 flex-1 overflow-y-auto'
104
+ ? 'min-h-0 min-w-0 flex-1 overflow-y-auto'
105
105
  : 'min-w-0 flex-1 overflow-y-auto border-border sm:border-l'
106
106
  );
107
107
  </script>
@@ -1,5 +1,5 @@
1
1
  <script lang="ts">
2
- import type { FooterItem } from '$lib/contexts/footerContext.svelte';
2
+ import type { FooterItem } from '#lib/contexts/footerContext.svelte.js';
3
3
 
4
4
  let { item }: { item: FooterItem } = $props();
5
5
 
@@ -9,7 +9,7 @@
9
9
  {#if item.onClick}
10
10
  <button
11
11
  onclick={item.onClick}
12
- class="gap-1.5 px-2 py-0.5 rounded flex cursor-pointer items-center transition-colors hover:bg-muted"
12
+ class="gap-1.5 px-2 py-0.5 flex cursor-pointer items-center rounded-sm transition-colors hover:bg-hover"
13
13
  >
14
14
  <item.component {...itemProps} />
15
15
  </button>
@@ -7,6 +7,7 @@
7
7
  let { class: className = '', children }: PageContentProps = $props();
8
8
  </script>
9
9
 
10
- <main class={`px-(--page-px) py-(--page-py) ${className}`}>
10
+ <!-- One content width for every page, so moving between pages doesn't change the frame. -->
11
+ <div class={`max-w-6xl min-w-0 mx-auto w-full px-(--page-px) py-(--page-py) ${className}`}>
11
12
  {@render children?.()}
12
- </main>
13
+ </div>
@@ -2,11 +2,11 @@
2
2
  import type { Snippet } from 'svelte';
3
3
  import { CircleAlert, TriangleAlert, Info } from '@lucide/svelte';
4
4
  import type { SolveDiagnostic } from '@selvajs/solve/shared';
5
- import { useFooter } from '$lib/contexts/footerContext.svelte';
5
+ import { useFooter } from '#lib/contexts/footerContext.svelte.js';
6
6
  import ComputeMessagesDialog from '../compute/ComputeMessagesDialog.svelte';
7
7
  import FooterItemRenderer from './FooterItemRenderer.svelte';
8
- import { getLocaleContext } from '$lib/i18n/localeContext.svelte';
9
- import { formatCount, formatMessage } from '$lib/i18n/messages';
8
+ import { getLocaleContext } from '#lib/i18n/localeContext.svelte.js';
9
+ import { formatCount, formatMessage } from '#lib/i18n/messages.js';
10
10
 
11
11
  interface Props {
12
12
  errors?: string[];
@@ -88,12 +88,12 @@
88
88
  <ComputeMessagesDialog {errors} {warnings} {diagnostics}>
89
89
  {#snippet trigger()}
90
90
  <div
91
- class="gap-1.5 px-2 py-1 rounded flex cursor-pointer items-center transition-colors hover:bg-muted {errorCount >
91
+ class="gap-1.5 px-2 py-1 flex cursor-pointer items-center rounded-sm transition-colors hover:bg-hover {errorCount >
92
92
  0
93
93
  ? 'text-destructive hover:bg-destructive/10'
94
94
  : warningCount > 0
95
95
  ? 'text-warning hover:bg-warning/10'
96
- : 'hover:bg-muted'}"
96
+ : 'hover:bg-hover'}"
97
97
  title={formatCount(totalCount, t.lastSolveMessagesOne, t.lastSolveMessagesOther)}
98
98
  >
99
99
  {#if errorCount > 0}
@@ -136,6 +136,8 @@
136
136
  <FooterItemRenderer {item} />
137
137
  {/each}
138
138
 
139
- <p class="truncate">{copyrightLine}</p>
139
+ {#if copyrightLine}
140
+ <p class="truncate">{copyrightLine}</p>
141
+ {/if}
140
142
  </div>
141
143
  </footer>
@@ -1,6 +1,6 @@
1
1
  <script lang="ts">
2
2
  import type { Snippet } from 'svelte';
3
- import { ModeToggle } from '$lib/components/primitives/mode-toggle';
3
+ import { ModeToggle } from '#lib/components/primitives/mode-toggle/index.js';
4
4
 
5
5
  interface PageHeaderProps {
6
6
  homeUrl?: string;
@@ -34,7 +34,7 @@
34
34
  <div class="gap-2 sm:gap-5 flex h-(--header-h) items-center px-(--page-px)">
35
35
  <!-- Logo -->
36
36
  <a href={homeUrl} class="gap-2 flex shrink-0 items-center" aria-label={brandName}>
37
- <img src={logo} alt="" aria-hidden="true" class="h-5 w-5" />
37
+ <img src={logo} alt="" aria-hidden="true" class="h-7 w-7" />
38
38
  <!-- On a phone the page title needs the room more than the brand does. -->
39
39
  <span
40
40
  class="font-semibold text-sm tracking-tight {title !== undefined ? 'sm:inline hidden' : ''}"
@@ -66,7 +66,7 @@
66
66
  </div>
67
67
 
68
68
  {#if subnav}
69
- <div class="h-10 flex items-center border-t border-border/60 px-(--page-px)">
69
+ <div class="h-10 flex items-center border-t border-border px-(--page-px)">
70
70
  {@render subnav()}
71
71
  </div>
72
72
  {/if}
@@ -13,6 +13,9 @@
13
13
  <script lang="ts">
14
14
  import type { Snippet } from 'svelte';
15
15
  import { page } from '$app/state';
16
+ import ChevronsUpDown from '@lucide/svelte/icons/chevrons-up-down';
17
+ import Check from '@lucide/svelte/icons/check';
18
+ import * as DropdownMenu from '#lib/components/primitives/dropdown-menu/index.js';
16
19
 
17
20
  interface SideNavProps {
18
21
  items: SideNavItem[];
@@ -41,10 +44,12 @@
41
44
  if (item.match === 'prefix') return path.startsWith(item.href);
42
45
  return path === item.href;
43
46
  }
47
+
48
+ const activeItem = $derived(items.find(isActive));
44
49
  </script>
45
50
 
46
- <!-- On a phone a 240px column would leave the page ~150px, so the nav becomes a scrolling strip
47
- above the content and the eyebrow, header and footer drop out. -->
51
+ <!-- On a phone a 240px column would leave the page ~150px, so the nav collapses to one switcher
52
+ naming the current section; the header and footer drop out. -->
48
53
  <aside
49
54
  class={`sm:h-full sm:w-60 sm:overflow-y-auto sm:border-b-0 flex w-full shrink-0 flex-col border-b border-border bg-background ${
50
55
  border ? 'sm:border-r' : ''
@@ -64,31 +69,67 @@
64
69
  </div>
65
70
  {/if}
66
71
 
67
- <nav
68
- class="gap-1 py-2 sm:gap-0 sm:pt-0 sm:pb-4 sm:pr-2 sm:flex-1 sm:space-y-px sm:block sm:px-0 flex overflow-x-auto px-(--page-px)"
69
- >
72
+ <div class="py-2 sm:hidden px-(--page-px)">
73
+ <DropdownMenu.Root>
74
+ <DropdownMenu.Trigger>
75
+ {#snippet child({ props })}
76
+ <button
77
+ {...props}
78
+ class="min-h-11 gap-3 px-3 text-sm font-medium flex w-full items-center rounded-md border border-border bg-card text-left"
79
+ >
80
+ {#if activeItem?.icon}
81
+ {@const Icon = activeItem.icon}
82
+ <Icon class="size-4 shrink-0 opacity-70" />
83
+ {/if}
84
+ <span class="min-w-0 flex-1 truncate">
85
+ {#if eyebrow}<span class="text-muted-foreground">{eyebrow} /</span>{/if}
86
+ {activeItem?.label ?? ''}
87
+ </span>
88
+ <ChevronsUpDown class="size-4 shrink-0 opacity-60" />
89
+ </button>
90
+ {/snippet}
91
+ </DropdownMenu.Trigger>
92
+ <DropdownMenu.Content sideOffset={4}>
93
+ {#each items as item (item.href)}
94
+ {@const active = isActive(item)}
95
+ {@const Icon = item.icon}
96
+ <DropdownMenu.Item class="min-h-11">
97
+ {#snippet child({ props })}
98
+ <a href={item.href} aria-current={active ? 'page' : undefined} {...props}>
99
+ {#if Icon}<Icon class="size-4 opacity-70" />{/if}
100
+ <span class="flex-1">{item.label}</span>
101
+ {#if item.badge !== undefined}
102
+ <span class="font-mono text-2xs tabular-nums opacity-60">{item.badge}</span>
103
+ {/if}
104
+ {#if active}<Check class="size-4" aria-hidden="true" />{/if}
105
+ </a>
106
+ {/snippet}
107
+ </DropdownMenu.Item>
108
+ {/each}
109
+ </DropdownMenu.Content>
110
+ </DropdownMenu.Root>
111
+ </div>
112
+
113
+ <nav class="pb-4 pr-2 sm:block hidden flex-1 space-y-px">
70
114
  {#each items as item (item.href)}
71
115
  {@const active = isActive(item)}
72
116
  {@const Icon = item.icon}
73
117
  <a
74
118
  href={item.href}
75
- class={`group gap-2 py-2 pr-2.5 sm:py-1.5 sm:w-full relative flex shrink-0 items-center text-left transition-colors ${
119
+ aria-current={active ? 'page' : undefined}
120
+ class={`group gap-2 py-1.5 pr-2.5 relative flex w-full items-center text-left transition-colors ${
76
121
  active
77
- ? 'pl-2.5 sm:pl-4 sm:rounded-l-none sm:rounded-r-md rounded-md bg-accent text-accent-foreground'
78
- : 'sm:mx-2 pl-2.5 rounded-md text-muted-foreground hover:bg-muted/60 hover:text-foreground'
122
+ ? 'pl-4 rounded-r-md bg-selected text-foreground'
123
+ : 'mx-2 pl-2.5 rounded-md text-muted-foreground hover:bg-hover hover:text-foreground'
79
124
  }`}
80
125
  >
81
126
  {#if active}
82
- <span
83
- class="left-0 top-0 bottom-0 w-0.75 sm:block absolute hidden bg-primary"
84
- aria-hidden="true"
85
- ></span>
127
+ <span class="left-0 top-0 bottom-0 w-0.75 absolute bg-primary" aria-hidden="true"></span>
86
128
  {/if}
87
129
  {#if Icon}
88
130
  <Icon class="h-3.5 w-3.5 shrink-0 opacity-70" />
89
131
  {/if}
90
- <span class="text-sm font-medium sm:flex-1 sm:truncate whitespace-nowrap">{item.label}</span
91
- >
132
+ <span class="text-sm font-medium flex-1 truncate">{item.label}</span>
92
133
  {#if item.badge !== undefined}
93
134
  <span class="font-mono shrink-0 text-2xs tabular-nums opacity-60">{item.badge}</span>
94
135
  {/if}
@@ -97,7 +138,7 @@
97
138
  </nav>
98
139
 
99
140
  {#if footer}
100
- <div class="px-3 py-3 sm:block hidden border-t border-border/60">
141
+ <div class="px-3 py-3 sm:block hidden border-t border-border">
101
142
  {@render footer()}
102
143
  </div>
103
144
  {/if}
@@ -31,8 +31,8 @@
31
31
  class={[
32
32
  'h-7 px-3 gap-1.5 text-sm font-medium flex items-center rounded-md transition-colors',
33
33
  active
34
- ? 'bg-accent text-accent-foreground'
35
- : 'text-muted-foreground hover:bg-accent/50 hover:text-foreground'
34
+ ? 'bg-selected text-foreground'
35
+ : 'text-muted-foreground hover:bg-hover hover:text-foreground'
36
36
  ].join(' ')}
37
37
  >
38
38
  {#if item.icon}