@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
@@ -1,5 +1,7 @@
1
1
  import { randomId } from '../utils/randomId';
2
+ import { saveBlob } from '../utils/host-files';
2
3
  import { getGroups } from '@selvajs/schemas';
4
+ import { isValuelessInput, valuelessInputIds } from './host-support';
3
5
  import type {
4
6
  UISchema,
5
7
  ParameterPreset,
@@ -24,6 +26,7 @@ export function createSavedState(
24
26
  if (item.type !== 'input') continue;
25
27
  const value = currentValues[item.paramId];
26
28
  const inputDef = schema.inputs.find((i) => i.id === item.paramId);
29
+ if (isValuelessInput(item, inputDef)) continue;
27
30
  if (value !== undefined && inputDef) {
28
31
  parameters.push({
29
32
  paramId: item.paramId,
@@ -110,15 +113,18 @@ export function validateSavedState(
110
113
  * Params the current schema no longer has are dropped. That's the same condition
111
114
  * `validateSavedState` flags as an error, but the rule is re-derived from the schema here
112
115
  * rather than read back off the issue list — nothing couples to how sentinel ids are encoded.
116
+ * Valueless inputs are dropped too: a preset saved before an input became a button or a
117
+ * geometry input still holds a value for it.
113
118
  */
114
119
  export function extractLoadableValues(
115
120
  savedState: ParameterPreset,
116
121
  currentSchema: UISchema
117
122
  ): Record<string, unknown> {
118
123
  const values: Record<string, unknown> = {};
124
+ const valueless = valuelessInputIds(currentSchema);
119
125
  for (const paramState of savedState.parameters) {
120
126
  const exists = currentSchema.inputs.some((i) => i.id === paramState.paramId);
121
- if (exists) {
127
+ if (exists && !valueless.has(paramState.paramId)) {
122
128
  values[paramState.paramId] = paramState.value;
123
129
  }
124
130
  }
@@ -147,18 +153,15 @@ export function loadPreset(savedState: ParameterPreset, currentSchema: UISchema)
147
153
  };
148
154
  }
149
155
 
150
- export function exportStateAsJson(savedState: ParameterPreset): void {
156
+ /** Resolves false when the user cancelled the save. */
157
+ export function exportStateAsJson(savedState: ParameterPreset): Promise<boolean> {
151
158
  const blob = new Blob([JSON.stringify(savedState, null, 2)], { type: 'application/json' });
152
159
  const safeName = savedState.name.replace(/[^a-z0-9]/gi, '_');
153
160
  const date = savedState.timestamp.split('T')[0].replace(/-/g, '_');
154
161
 
155
- const link = document.createElement('a');
156
- link.href = URL.createObjectURL(blob);
157
162
  // .slvp = Selva parameter preset. Import still accepts the pre-rename .sps — same JSON,
158
163
  // only the writer's extension changed.
159
- link.download = `${safeName}_${date}.slvp`;
160
- link.click();
161
- URL.revokeObjectURL(link.href);
164
+ return saveBlob(blob, `${safeName}_${date}.slvp`);
162
165
  }
163
166
 
164
167
  export async function importStateFromJson(file: File): Promise<ParameterPreset> {
@@ -1,10 +1,12 @@
1
1
  import type {
2
+ InputLayoutItem,
2
3
  LayoutItem,
3
4
  OutputLayoutItem,
4
5
  VisibilityRule,
5
6
  VisibilityCondition,
6
7
  GroupVisibilityCondition,
7
- TabConfig
8
+ TabConfig,
9
+ UISchema
8
10
  } from '@selvajs/schemas';
9
11
  import { isFileData } from '../utils/file-download';
10
12
 
@@ -148,6 +150,30 @@ export function isTabVisible(tab: TabConfig, values: Record<string, unknown>): b
148
150
  );
149
151
  }
150
152
 
153
+ /**
154
+ * The input items the form shows for these values: each passes its own condition, inside a
155
+ * shown group and, when tabbed, a shown tab. For a host that repeats the form back, such as a
156
+ * summary before a commit, without re-deriving the renderer's rules.
157
+ */
158
+ export function visibleInputItems(
159
+ schema: UISchema,
160
+ values: Record<string, unknown>
161
+ ): InputLayoutItem[] {
162
+ const layout = schema?.layout;
163
+ const groups =
164
+ layout?.type === 'tabbed'
165
+ ? (layout.tabs ?? []).filter((t) => isTabVisible(t, values)).flatMap((t) => t.groups ?? [])
166
+ : layout?.type === 'flat'
167
+ ? (layout.groups ?? [])
168
+ : [];
169
+ return groups
170
+ .filter((g) => evaluateGroupVisibility(g, values))
171
+ .flatMap((g) => g.items ?? [])
172
+ .filter(
173
+ (i): i is InputLayoutItem => i.type === 'input' && evaluateVisibility(i, values).visible
174
+ );
175
+ }
176
+
151
177
  /**
152
178
  * A linebreak is always "visible" but renders as a divider only, so a group left with
153
179
  * nothing but separators counts as empty.
@@ -1,77 +1,95 @@
1
+ @import '@fontsource-variable/hanken-grotesk';
2
+
1
3
  @custom-variant dark (&:where(.dark, .dark *));
2
4
 
5
+ /* Surfaces, edges and states share one set of names in both modes; each mode only changes the
6
+ values. Ratios are WCAG 2.x. The roles and when to use each: docs/contributing/design-system.md. */
3
7
  :root {
4
- --radius: 0.625rem;
5
8
  --header-h: 3.5rem;
9
+ --below-header-h: calc(100svh - var(--header-h));
6
10
  --page-px: 1rem;
7
11
  --page-py: 1.75rem;
8
- --background: oklch(0.99 0 0);
9
- --foreground: oklch(0.22 0 0);
10
- --card: oklch(0.985 0 0);
11
- --card-foreground: oklch(0.22 0 0);
12
- --popover: oklch(0.99 0 0);
13
- --popover-foreground: oklch(0.22 0 0);
14
- --primary: oklch(0.57 0.07 135);
15
- --primary-foreground: oklch(0.985 0 0);
16
- --secondary: oklch(0.94 0 0);
17
- --secondary-foreground: oklch(0.28 0 0);
18
- --muted: oklch(0.95 0 0);
12
+ /* Canvas, then surface (cards, panels), then raised (rows, menus, popovers). Light can't go
13
+ lighter than white, so raised is white too and lifts with a border and a shadow instead.
14
+ Values follow stock shadcn neutral. */
15
+ --background: oklch(0.985 0 0);
16
+ --card: oklch(1 0 0);
17
+ --raised: oklch(1 0 0);
18
+ /* Recessed fill for trays, table heads, code and skeletons: darker than the surface around it. */
19
+ --muted: oklch(0.97 0 0);
20
+ /* Fill of anything you type into, on a card: recessed, the same step as muted, in both modes.
21
+ The edge (`--input`) carries the boundary. */
22
+ --field: var(--muted);
23
+ --foreground: oklch(0.145 0 0);
24
+ --card-foreground: oklch(0.145 0 0);
25
+ --popover-foreground: oklch(0.145 0 0);
26
+ /* 4.5:1 on every surface. */
19
27
  --muted-foreground: oklch(0.5 0 0);
20
- --accent: oklch(0.93 0 0);
21
- --accent-foreground: oklch(0.3 0 0);
22
- /* 4.5:1 as small text on background, card and muted, and under white text. */
23
- --destructive: oklch(0.55 0.19 27);
24
- --destructive-foreground: oklch(0.985 0 0);
25
- --border: oklch(0.86 0 0);
26
- /* Field borders and unchecked boxes: at least 3:1 against background, card and muted, the
27
- WCAG minimum for a control's boundary. Same for the focus ring, which is drawn opaque. */
28
- --input: oklch(0.62 0 0);
29
- --ring: oklch(0.55 0 0);
30
- --success: oklch(0.66 0.14 145);
28
+ /* Icons, placeholders, disabled text: 3:1 on every surface. */
29
+ --faint-foreground: oklch(0.6 0 0);
30
+ /* State overlays, laid over whatever surface is underneath. */
31
+ --hover: oklch(0 0 0 / 4%);
32
+ --selected: oklch(0 0 0 / 8%);
33
+ /* Rest fill of a neutral filled control; its hover is `selected`. */
34
+ --secondary: oklch(0.97 0 0);
35
+ --secondary-foreground: oklch(0.205 0 0);
36
+ /* Outlines and separators. */
37
+ --border: oklch(0 0 0 / 8%);
38
+ /* Popovers, outline buttons, dashed drop zones. */
39
+ --border-strong: oklch(0 0 0 / 14%);
40
+ /* Edge of anything you type into or toggle, slider tracks included. Softer than the 3:1 WCAG
41
+ boundary minimum in both modes: the recessed field fill carries the rest of the boundary. */
42
+ --input: oklch(0 0 0 / 10%);
43
+ --ring: oklch(0.708 0 0);
44
+ --elevation-raised: 0 1px 2px oklch(0 0 0 / 5%);
45
+ --elevation-popover: 0 8px 24px oklch(0 0 0 / 10%);
46
+ /* 4.5:1 as text on a card and under its foreground; 3:1 as a fill on every surface. */
47
+ --primary: oklch(0.205 0 0);
48
+ --primary-foreground: oklch(0.985 0 0);
49
+ --destructive: #c9302d;
50
+ --destructive-foreground: #fbfbfb;
51
+ /* 3:1 on every surface, as icons and dots. */
52
+ --success: #51a556;
31
53
  --success-foreground: oklch(0.99 0 0);
32
- --warning: oklch(0.78 0.12 75);
54
+ --warning: #bd8630;
33
55
  --warning-foreground: oklch(0.25 0.04 75);
34
- --info: oklch(0.68 0.11 245);
56
+ --info: #5399d1;
35
57
  --info-foreground: oklch(0.99 0 0);
36
58
  --chart-1: oklch(0.65 0.14 135);
37
59
  --chart-2: oklch(0.7 0.11 200);
38
60
  --chart-3: oklch(0.6 0.13 260);
39
61
  --chart-4: oklch(0.78 0.13 80);
40
62
  --chart-5: oklch(0.68 0.15 30);
41
- --sidebar: oklch(0.96 0 0);
42
- --sidebar-foreground: oklch(0.25 0 0);
43
- --sidebar-primary: oklch(0.57 0.07 135);
44
- --sidebar-primary-foreground: oklch(0.985 0 0);
45
- --sidebar-accent: oklch(0.93 0 0);
46
- --sidebar-accent-foreground: oklch(0.3 0 0);
47
- --sidebar-border: oklch(0.88 0 0);
48
- --sidebar-ring: oklch(0.55 0 0);
49
- --inputparam: oklch(1 0 0);
50
- --outputparam: oklch(1 0 0);
51
63
  /* Scrim behind modal dialogs and drawers. */
52
64
  --overlay: oklch(0 0 0 / 50%);
53
65
  }
54
66
 
55
67
  .dark {
56
- --background: oklch(0.17 0 0);
57
- --foreground: oklch(0.96 0 0);
58
- --card: oklch(0.23 0 0);
59
- --card-foreground: oklch(0.96 0 0);
60
- --popover: oklch(0.23 0 0);
61
- --popover-foreground: oklch(0.96 0 0);
62
- --primary: oklch(0.74 0.07 135);
63
- --primary-foreground: oklch(0.18 0 0);
64
- --secondary: oklch(0.3 0 0);
65
- --secondary-foreground: oklch(0.96 0 0);
66
- --muted: oklch(0.27 0 0);
67
- --muted-foreground: oklch(0.72 0 0);
68
- --accent: oklch(0.36 0 0);
69
- --accent-foreground: oklch(0.92 0 0);
68
+ --background: oklch(0.145 0 0);
69
+ --card: oklch(0.205 0 0);
70
+ --raised: oklch(0.24 0 0);
71
+ --muted: oklch(0.269 0 0);
72
+ --foreground: oklch(0.985 0 0);
73
+ --card-foreground: oklch(0.985 0 0);
74
+ --popover-foreground: oklch(0.985 0 0);
75
+ --muted-foreground: oklch(0.708 0 0);
76
+ --faint-foreground: oklch(0.556 0 0);
77
+ --hover: oklch(1 0 0 / 6%);
78
+ --selected: oklch(1 0 0 / 10%);
79
+ --secondary: oklch(0.269 0 0);
80
+ --secondary-foreground: oklch(0.985 0 0);
81
+ --border: oklch(1 0 0 / 10%);
82
+ --border-strong: oklch(1 0 0 / 18%);
83
+ --input: oklch(1 0 0 / 15%);
84
+ --ring: oklch(0.556 0 0);
85
+ /* Shadows barely show on a dark canvas, so raised things lean on the border instead. */
86
+ --elevation-raised: none;
87
+ --elevation-popover: 0 8px 24px oklch(0 0 0 / 50%);
88
+ --primary: oklch(0.922 0 0);
89
+ --primary-foreground: oklch(0.205 0 0);
90
+ /* White text can't reach 4.5:1 on a red light enough to also be text on the dark card. */
70
91
  --destructive: oklch(0.7 0.17 22);
71
- --destructive-foreground: oklch(0.985 0 0);
72
- --border: oklch(1 0 0 / 14%);
73
- --input: oklch(1 0 0 / 36%);
74
- --ring: oklch(0.74 0 0);
92
+ --destructive-foreground: oklch(0.18 0 0);
75
93
  --success: oklch(0.74 0.15 145);
76
94
  --success-foreground: oklch(0.18 0.03 145);
77
95
  --warning: oklch(0.8 0.14 75);
@@ -83,41 +101,39 @@
83
101
  --chart-3: oklch(0.68 0.14 260);
84
102
  --chart-4: oklch(0.78 0.14 80);
85
103
  --chart-5: oklch(0.7 0.16 30);
86
- --sidebar: oklch(0.21 0 0);
87
- --sidebar-foreground: oklch(0.96 0 0);
88
- --sidebar-primary: oklch(0.74 0.07 135);
89
- --sidebar-primary-foreground: oklch(0.18 0 0);
90
- --sidebar-accent: oklch(0.32 0 0);
91
- --sidebar-accent-foreground: oklch(0.92 0 0);
92
- --sidebar-border: oklch(1 0 0 / 10%);
93
- --sidebar-ring: oklch(0.74 0 0);
94
- --inputparam: oklch(0.15 0 0);
95
- --outputparam: oklch(0.25 0 0);
96
104
  }
97
105
 
98
106
  @theme inline {
99
- --radius-sm: calc(var(--radius) - 4px);
100
- --radius-md: calc(var(--radius) - 2px);
101
- --radius-lg: var(--radius);
102
- --radius-xl: calc(var(--radius) + 4px);
107
+ /* Concentric: an item inside a padded container takes the container's radius minus the
108
+ padding. A 12px menu with 4px padding holds 8px items; a 10px control with 2px padding
109
+ holds 8px segments. */
110
+ --radius-sm: 6px;
111
+ --radius-inner: 8px;
112
+ --radius-md: 10px;
113
+ --radius-lg: 12px;
114
+ --radius-xl: 16px;
103
115
  --color-background: var(--background);
104
116
  --color-foreground: var(--foreground);
105
117
  --color-card: var(--card);
106
118
  --color-card-foreground: var(--card-foreground);
107
- --color-popover: var(--popover);
119
+ --color-raised: var(--raised);
120
+ --color-popover: var(--raised);
108
121
  --color-popover-foreground: var(--popover-foreground);
109
- --color-primary: var(--primary);
110
- --color-primary-foreground: var(--primary-foreground);
111
- --color-secondary: var(--secondary);
112
- --color-secondary-foreground: var(--secondary-foreground);
122
+ --color-field: var(--field);
113
123
  --color-muted: var(--muted);
114
124
  --color-muted-foreground: var(--muted-foreground);
115
- --color-accent: var(--accent);
116
- --color-accent-foreground: var(--accent-foreground);
125
+ --color-faint-foreground: var(--faint-foreground);
126
+ --color-hover: var(--hover);
127
+ --color-selected: var(--selected);
128
+ --color-secondary: var(--secondary);
129
+ --color-secondary-foreground: var(--secondary-foreground);
130
+ --color-primary: var(--primary);
131
+ --color-primary-foreground: var(--primary-foreground);
117
132
  --color-destructive: var(--destructive);
118
133
  --color-destructive-foreground: var(--destructive-foreground);
119
134
  --color-overlay: var(--overlay);
120
135
  --color-border: var(--border);
136
+ --color-border-strong: var(--border-strong);
121
137
  --color-input: var(--input);
122
138
  --color-ring: var(--ring);
123
139
  --color-success: var(--success);
@@ -131,21 +147,15 @@
131
147
  --color-chart-3: var(--chart-3);
132
148
  --color-chart-4: var(--chart-4);
133
149
  --color-chart-5: var(--chart-5);
134
- --color-sidebar: var(--sidebar);
135
- --color-sidebar-foreground: var(--sidebar-foreground);
136
- --color-sidebar-primary: var(--sidebar-primary);
137
- --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
138
- --color-sidebar-accent: var(--sidebar-accent);
139
- --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
140
- --color-sidebar-border: var(--sidebar-border);
141
- --color-sidebar-ring: var(--sidebar-ring);
142
- --color-inputparam: var(--inputparam);
143
- --color-outputparam: var(--outputparam);
150
+ --shadow-raised: var(--elevation-raised);
151
+ --shadow-popover: var(--elevation-popover);
144
152
  }
145
153
 
146
154
  /* Design tokens beyond Tailwind's defaults. Reach for these before an arbitrary value: the
147
155
  design-system lint rejects `text-[10px]`, `max-h-[85dvh]` and the like. */
148
156
  @theme {
157
+ /* Hanken Grotesk (OFL), self-hosted via Fontsource: the plugin UI runs offline from the .gha. */
158
+ --font-sans: 'Hanken Grotesk Variable', ui-sans-serif, system-ui, sans-serif;
149
159
  /* Type tags, counters and other dense labels. */
150
160
  --text-2xs: 0.625rem;
151
161
  --text-2xs--line-height: calc(0.875 / 0.625);
@@ -0,0 +1,107 @@
1
+ export interface Rgb {
2
+ r: number;
3
+ g: number;
4
+ b: number;
5
+ }
6
+
7
+ /** h in [0, 360), s and v in [0, 1]. */
8
+ export interface Hsv {
9
+ h: number;
10
+ s: number;
11
+ v: number;
12
+ }
13
+
14
+ const clamp = (n: number, min: number, max: number) => Math.min(max, Math.max(min, n));
15
+
16
+ /** Accepts `#rgb`, `#rrggbb` and the same without `#`. Returns lowercase `#rrggbb`, or null. */
17
+ export function normalizeHex(input: string): string | null {
18
+ const raw = input.trim().replace(/^#/, '');
19
+ if (/^[0-9a-f]{3}$/i.test(raw)) {
20
+ return `#${raw
21
+ .split('')
22
+ .map((c) => c + c)
23
+ .join('')
24
+ .toLowerCase()}`;
25
+ }
26
+ if (/^[0-9a-f]{6}$/i.test(raw)) return `#${raw.toLowerCase()}`;
27
+ return null;
28
+ }
29
+
30
+ export function hexToRgb(hex: string): Rgb | null {
31
+ const normalized = normalizeHex(hex);
32
+ if (!normalized) return null;
33
+ return {
34
+ r: parseInt(normalized.slice(1, 3), 16),
35
+ g: parseInt(normalized.slice(3, 5), 16),
36
+ b: parseInt(normalized.slice(5, 7), 16)
37
+ };
38
+ }
39
+
40
+ export function rgbToHex({ r, g, b }: Rgb): string {
41
+ const channel = (n: number) => clamp(Math.round(n), 0, 255).toString(16).padStart(2, '0');
42
+ return `#${channel(r)}${channel(g)}${channel(b)}`;
43
+ }
44
+
45
+ export function rgbToHsv({ r, g, b }: Rgb): Hsv {
46
+ const rn = r / 255;
47
+ const gn = g / 255;
48
+ const bn = b / 255;
49
+ const max = Math.max(rn, gn, bn);
50
+ const delta = max - Math.min(rn, gn, bn);
51
+
52
+ let h = 0;
53
+ if (delta !== 0) {
54
+ if (max === rn) h = ((gn - bn) / delta) % 6;
55
+ else if (max === gn) h = (bn - rn) / delta + 2;
56
+ else h = (rn - gn) / delta + 4;
57
+ h *= 60;
58
+ if (h < 0) h += 360;
59
+ }
60
+ return { h, s: max === 0 ? 0 : delta / max, v: max };
61
+ }
62
+
63
+ export function hsvToRgb({ h, s, v }: Hsv): Rgb {
64
+ const c = v * s;
65
+ const hp = (((h % 360) + 360) % 360) / 60;
66
+ const x = c * (1 - Math.abs((hp % 2) - 1));
67
+ const [r1, g1, b1] =
68
+ hp < 1
69
+ ? [c, x, 0]
70
+ : hp < 2
71
+ ? [x, c, 0]
72
+ : hp < 3
73
+ ? [0, c, x]
74
+ : hp < 4
75
+ ? [0, x, c]
76
+ : hp < 5
77
+ ? [x, 0, c]
78
+ : [c, 0, x];
79
+ const m = v - c;
80
+ return { r: (r1 + m) * 255, g: (g1 + m) * 255, b: (b1 + m) * 255 };
81
+ }
82
+
83
+ export const hexToHsv = (hex: string): Hsv | null => {
84
+ const rgb = hexToRgb(hex);
85
+ return rgb ? rgbToHsv(rgb) : null;
86
+ };
87
+
88
+ export const hsvToHex = (hsv: Hsv): string => rgbToHex(hsvToRgb(hsv));
89
+
90
+ /** WCAG relative luminance of an sRGB color. */
91
+ function luminance({ r, g, b }: Rgb): number {
92
+ const lin = (c: number) => {
93
+ const n = c / 255;
94
+ return n <= 0.04045 ? n / 12.92 : Math.pow((n + 0.055) / 1.055, 2.4);
95
+ };
96
+ return 0.2126 * lin(r) + 0.7152 * lin(g) + 0.0722 * lin(b);
97
+ }
98
+
99
+ /** WCAG contrast ratio between two hex colors, 1 to 21. Null if either isn't a hex color. */
100
+ export function contrastRatio(a: string, b: string): number | null {
101
+ const ra = hexToRgb(a);
102
+ const rb = hexToRgb(b);
103
+ if (!ra || !rb) return null;
104
+ const la = luminance(ra);
105
+ const lb = luminance(rb);
106
+ return (Math.max(la, lb) + 0.05) / (Math.min(la, lb) + 0.05);
107
+ }
@@ -1,6 +1,7 @@
1
1
  import { decodeBase64ToBinary, downloadFileDataByRoot, type FileData } from '@selvajs/compute/core';
2
2
  import { SvelteMap } from 'svelte/reactivity';
3
3
  import { APP_DEFAULTS } from '../constants';
4
+ import { saveBlob } from './host-files';
4
5
 
5
6
  export const MIME_BY_EXT: Record<string, string> = {
6
7
  '.png': 'image/png',
@@ -19,7 +20,7 @@ export const MIME_BY_EXT: Record<string, string> = {
19
20
  '.stl': 'model/stl'
20
21
  };
21
22
 
22
- function saveSingleFile(file: FileData): void {
23
+ function saveSingleFile(file: FileData): Promise<boolean> {
23
24
  const ext = (file.fileType ?? '').toLowerCase();
24
25
  const mime = MIME_BY_EXT[ext] ?? 'application/octet-stream';
25
26
 
@@ -27,12 +28,7 @@ function saveSingleFile(file: FileData): void {
27
28
  // decodeBase64ToBinary returns an ArrayBuffer-backed copy, never a SharedArrayBuffer view.
28
29
  const blob = new Blob([content as BlobPart], { type: mime });
29
30
 
30
- const fullName = `${file.fileName}${file.fileType ?? ''}`;
31
- const a = document.createElement('a');
32
- a.href = URL.createObjectURL(blob);
33
- a.download = fullName;
34
- a.click();
35
- URL.revokeObjectURL(a.href);
31
+ return saveBlob(blob, `${file.fileName}${file.fileType ?? ''}`);
36
32
  }
37
33
 
38
34
  export async function downloadFiles(
@@ -47,14 +43,14 @@ export async function downloadFiles(
47
43
  return;
48
44
  }
49
45
 
50
- // A lone file is saved as itself rather than zipped — the one case that needs the DOM
51
- // directly. Everything else, including the per-root archive split, belongs to compute.
46
+ // A lone file is saved as itself rather than zipped. Everything else, including the
47
+ // per-root archive split, belongs to compute.
52
48
  if (filesArray.length === 1) {
53
- saveSingleFile(filesArray[0]);
49
+ await saveSingleFile(filesArray[0]);
54
50
  return;
55
51
  }
56
52
 
57
- await downloadFileDataByRoot(filesArray, fileName);
53
+ await downloadFileDataByRoot(filesArray, fileName, null, { saveBlob });
58
54
  } catch (error) {
59
55
  console.error('[FileDownload] Error downloading files:', error);
60
56
  throw error;
@@ -0,0 +1,61 @@
1
+ // Where a file the page offers goes, and where a file the page asks for comes from. A browser
2
+ // gets an <a download> click and a hidden <input type=file>. A host whose WebView can't do
3
+ // either safely (the Rhino panel) installs its own once at start-up, before the first save or pick.
4
+
5
+ /** Resolves false when the user cancelled. An anchor download always resolves true: it can't tell. */
6
+ export type BlobSaver = (blob: Blob, fileName: string) => Promise<boolean>;
7
+
8
+ /** `accept` is what `<input accept>` takes: `.slvp,.sps`, `image/*`. Resolves null on cancel. */
9
+ export type FilePicker = (options: { accept: string }) => Promise<File | null>;
10
+
11
+ let saver: BlobSaver = saveWithAnchor;
12
+ let picker: FilePicker = pickWithInput;
13
+
14
+ export function setBlobSaver(next: BlobSaver): void {
15
+ saver = next;
16
+ }
17
+
18
+ export function setFilePicker(next: FilePicker): void {
19
+ picker = next;
20
+ }
21
+
22
+ export function saveBlob(blob: Blob, fileName: string): Promise<boolean> {
23
+ return saver(blob, fileName);
24
+ }
25
+
26
+ export function pickFile(options: { accept: string }): Promise<File | null> {
27
+ return picker(options);
28
+ }
29
+
30
+ async function saveWithAnchor(blob: Blob, fileName: string): Promise<boolean> {
31
+ const url = URL.createObjectURL(blob);
32
+ const a = document.createElement('a');
33
+ a.href = url;
34
+ a.download = fileName;
35
+ // Firefox only downloads from an anchor that is in the document.
36
+ document.body.appendChild(a);
37
+ a.click();
38
+ a.remove();
39
+ // Revoking synchronously can abort the download: the browser pins the blob only once it starts.
40
+ setTimeout(() => URL.revokeObjectURL(url), 10_000);
41
+ return true;
42
+ }
43
+
44
+ function pickWithInput({ accept }: { accept: string }): Promise<File | null> {
45
+ return new Promise((resolve) => {
46
+ const input = document.createElement('input');
47
+ input.type = 'file';
48
+ input.accept = accept;
49
+ input.style.display = 'none';
50
+
51
+ const settle = (file: File | null) => {
52
+ input.remove();
53
+ resolve(file);
54
+ };
55
+ input.addEventListener('change', () => settle(input.files?.[0] ?? null), { once: true });
56
+ input.addEventListener('cancel', () => settle(null), { once: true });
57
+
58
+ document.body.appendChild(input);
59
+ input.click();
60
+ });
61
+ }
@@ -0,0 +1,48 @@
1
+ // Grasshopper's PDF export can't read WebP: the plugin's PDF library has no WebP decoder, and
2
+ // nothing on Windows or Rhino.Compute decodes it reliably. The browser can, so a WebP picked for
3
+ // an input that also takes PNG is re-encoded here. PNG rather than JPEG keeps transparency.
4
+
5
+ /** True when a WebP should go out as PNG: the input takes PNG, so a PNG is a valid value. */
6
+ export function shouldConvertWebp(fileEnding: string, acceptedFormats: readonly string[]): boolean {
7
+ return fileEnding === '.webp' && acceptedFormats.includes('.png');
8
+ }
9
+
10
+ /** Whether the input takes a file with this ending, counting a WebP it would get as PNG. */
11
+ export function acceptsFileEnding(fileEnding: string, acceptedFormats: readonly string[]): boolean {
12
+ const ending = fileEnding.toLowerCase();
13
+ if (acceptedFormats.length === 0 || acceptedFormats.includes(ending)) return true;
14
+ return shouldConvertWebp(ending, acceptedFormats);
15
+ }
16
+
17
+ const ascii = (bytes: Uint8Array, start: number, end: number) =>
18
+ String.fromCharCode(...bytes.subarray(start, end));
19
+
20
+ /**
21
+ * By content, not name: image CDNs serve WebP from URLs with no extension or one naming another
22
+ * format. A WebP file starts `RIFF`, a 4-byte size, then `WEBP`.
23
+ */
24
+ export async function isWebp(blob: Blob): Promise<boolean> {
25
+ if (blob.type === 'image/webp') return true;
26
+ const head = new Uint8Array(await blob.slice(0, 12).arrayBuffer());
27
+ return head.length === 12 && ascii(head, 0, 4) === 'RIFF' && ascii(head, 8, 12) === 'WEBP';
28
+ }
29
+
30
+ export async function webpToPng(blob: Blob): Promise<Blob> {
31
+ const bitmap = await createImageBitmap(blob);
32
+ try {
33
+ const canvas = document.createElement('canvas');
34
+ canvas.width = bitmap.width;
35
+ canvas.height = bitmap.height;
36
+ const context = canvas.getContext('2d');
37
+ if (!context) throw new Error('Canvas 2D context unavailable');
38
+ context.drawImage(bitmap, 0, 0);
39
+ return await new Promise<Blob>((resolve, reject) =>
40
+ canvas.toBlob(
41
+ (png) => (png ? resolve(png) : reject(new Error('PNG encoding failed'))),
42
+ 'image/png'
43
+ )
44
+ );
45
+ } finally {
46
+ bitmap.close();
47
+ }
48
+ }
@@ -1,39 +0,0 @@
1
- <script lang="ts">
2
- import { themeStore, type Theme } from '../../../theme/themeStore.svelte';
3
- import * as Select from '../select';
4
- import { Palette } from '@lucide/svelte';
5
-
6
- const themes: { value: Theme; label: string; description: string }[] = [
7
- { value: 'neutral', label: 'Neutral', description: 'Classic grayscale' },
8
- { value: 'selva', label: 'Selva', description: 'Forest green' },
9
- { value: 'ocean', label: 'Ocean', description: 'Calm blue tones' },
10
- { value: 'cyberpunk', label: 'Cyberpunk', description: 'Vibrant neon colors' }
11
- ];
12
-
13
- let value = $state(themeStore.current);
14
-
15
- $effect(() => {
16
- if (value) {
17
- themeStore.set(value as Theme);
18
- }
19
- });
20
-
21
- const triggerContent = $derived(themes.find((t) => t.value === value)?.label ?? 'Select theme');
22
- </script>
23
-
24
- <Select.Root type="single" bind:value>
25
- <Select.Trigger class="gap-2 w-35">
26
- <Palette size={14} />
27
- <span>{triggerContent}</span>
28
- </Select.Trigger>
29
- <Select.Content>
30
- {#each themes as theme (theme.value)}
31
- <Select.Item value={theme.value} label={theme.label}>
32
- <div class="flex flex-col items-start">
33
- <span class="font-medium text-sm">{theme.label}</span>
34
- <span class="text-xs text-muted-foreground">{theme.description}</span>
35
- </div>
36
- </Select.Item>
37
- {/each}
38
- </Select.Content>
39
- </Select.Root>
@@ -1,3 +0,0 @@
1
- declare const ThemeSwitcher: import("svelte").Component<Record<string, never>, {}, "">;
2
- type ThemeSwitcher = ReturnType<typeof ThemeSwitcher>;
3
- export default ThemeSwitcher;
@@ -1 +0,0 @@
1
- export { default as ThemeSwitcher } from './ThemeSwitcher.svelte';
@@ -1 +0,0 @@
1
- export { default as ThemeSwitcher } from './ThemeSwitcher.svelte';