@selvajs/ui 6.6.0-beta.2 → 6.6.0-beta.20

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 (401) hide show
  1. package/README.md +0 -3
  2. package/dist/components/ErrorScreen.svelte +7 -3
  3. package/dist/components/compute/AppLayout.svelte +307 -123
  4. package/dist/components/compute/AppLayout.svelte.d.ts +28 -0
  5. package/dist/components/compute/CollapsedPanelStrip.svelte +4 -2
  6. package/dist/components/compute/ComputeApp.svelte +46 -22
  7. package/dist/components/compute/ComputeApp.svelte.d.ts +20 -3
  8. package/dist/components/compute/ComputeMessagesDialog.svelte +31 -41
  9. package/dist/components/compute/ParameterPresetManager.svelte +16 -65
  10. package/dist/components/compute/ParameterPresetManager.svelte.d.ts +4 -5
  11. package/dist/components/compute/SolveMessages.svelte +128 -121
  12. package/dist/components/compute/SolveMessages.svelte.d.ts +12 -12
  13. package/dist/components/layout/AppShell.svelte +10 -6
  14. package/dist/components/layout/FooterItemRenderer.svelte +2 -2
  15. package/dist/components/layout/FooterItemRenderer.svelte.d.ts +1 -1
  16. package/dist/components/layout/PageContent.svelte +3 -2
  17. package/dist/components/layout/PageFooter.svelte +26 -14
  18. package/dist/components/layout/PageHeader.svelte +14 -10
  19. package/dist/components/layout/SectionHeader.svelte +4 -2
  20. package/dist/components/layout/SideNav.svelte +57 -8
  21. package/dist/components/layout/SubNav.svelte +2 -2
  22. package/dist/components/preview/ChartOutput.svelte +131 -65
  23. package/dist/components/preview/Group.svelte +42 -22
  24. package/dist/components/preview/HelpButton.svelte +5 -3
  25. package/dist/components/preview/ImageOutput.svelte +435 -71
  26. package/dist/components/preview/InputControl.svelte +125 -51
  27. package/dist/components/preview/InputControl.svelte.d.ts +5 -1
  28. package/dist/components/preview/OutputDisplay.svelte +42 -28
  29. package/dist/components/preview/TabBar.svelte +8 -6
  30. package/dist/components/preview/TabBar.svelte.d.ts +2 -0
  31. package/dist/components/preview/TabContent.svelte +66 -31
  32. package/dist/components/preview/TabContent.svelte.d.ts +5 -1
  33. package/dist/components/preview/TabLayout.svelte +29 -8
  34. package/dist/components/preview/TabLayout.svelte.d.ts +5 -0
  35. package/dist/components/preview/inputs/CheckboxInput.svelte +1 -1
  36. package/dist/components/preview/inputs/ChecklistInput.svelte +11 -6
  37. package/dist/components/preview/inputs/ChecklistInput.svelte.d.ts +2 -1
  38. package/dist/components/preview/inputs/ColorInput.svelte +357 -13
  39. package/dist/components/preview/inputs/ColorInput.svelte.d.ts +2 -0
  40. package/dist/components/preview/inputs/DropdownInput.svelte +20 -5
  41. package/dist/components/preview/inputs/DropdownInput.svelte.d.ts +2 -1
  42. package/dist/components/preview/inputs/FileInput.svelte +115 -103
  43. package/dist/components/preview/inputs/GeometryInput.svelte +128 -0
  44. package/dist/components/preview/inputs/GeometryInput.svelte.d.ts +21 -0
  45. package/dist/components/preview/inputs/NumberInput.svelte +98 -16
  46. package/dist/components/preview/inputs/NumberInput.svelte.d.ts +2 -0
  47. package/dist/components/preview/inputs/OptionGlyph.svelte +29 -0
  48. package/dist/components/preview/inputs/OptionGlyph.svelte.d.ts +8 -0
  49. package/dist/components/preview/inputs/SegmentedInput.svelte +45 -0
  50. package/dist/components/preview/inputs/SegmentedInput.svelte.d.ts +12 -0
  51. package/dist/components/preview/inputs/TextInput.svelte +11 -6
  52. package/dist/components/preview/inputs/TileInput.svelte +83 -0
  53. package/dist/components/preview/inputs/TileInput.svelte.d.ts +12 -0
  54. package/dist/components/preview/inputs/index.d.ts +3 -0
  55. package/dist/components/preview/inputs/index.js +3 -0
  56. package/dist/components/primitives/CalculateButton.svelte +56 -24
  57. package/dist/components/primitives/CalculateButton.svelte.d.ts +3 -0
  58. package/dist/components/primitives/Callout.svelte +8 -6
  59. package/dist/components/primitives/Callout.svelte.d.ts +4 -4
  60. package/dist/components/primitives/DataTable.svelte +38 -4
  61. package/dist/components/primitives/DataTable.svelte.d.ts +2 -0
  62. package/dist/components/primitives/FilterableDropdown.svelte +13 -1
  63. package/dist/components/primitives/FilterableDropdown.svelte.d.ts +5 -0
  64. package/dist/components/primitives/ImageUploadField.svelte +1 -1
  65. package/dist/components/primitives/SolvingIndicator.svelte +245 -15
  66. package/dist/components/primitives/SolvingIndicator.svelte.d.ts +15 -0
  67. package/dist/components/primitives/alert-dialog/alert-dialog-content.svelte +1 -1
  68. package/dist/components/primitives/badge/badge.svelte +4 -4
  69. package/dist/components/primitives/badge/badge.svelte.d.ts +10 -10
  70. package/dist/components/primitives/button/button.svelte +10 -8
  71. package/dist/components/primitives/button/button.svelte.d.ts +20 -17
  72. package/dist/components/primitives/card/card.svelte +1 -1
  73. package/dist/components/primitives/card/card.svelte.d.ts +1 -1
  74. package/dist/components/primitives/checkbox/checkbox.svelte +1 -1
  75. package/dist/components/primitives/command/command-input.svelte +1 -1
  76. package/dist/components/primitives/command/command-item.svelte +1 -1
  77. package/dist/components/primitives/command/command-link-item.svelte +1 -1
  78. package/dist/components/primitives/command/command.svelte +1 -1
  79. package/dist/components/primitives/context-menu/context-menu-checkbox-item.svelte +1 -1
  80. package/dist/components/primitives/context-menu/context-menu-content.svelte +1 -1
  81. package/dist/components/primitives/context-menu/context-menu-item.svelte +1 -1
  82. package/dist/components/primitives/context-menu/context-menu-radio-item.svelte +1 -1
  83. package/dist/components/primitives/context-menu/context-menu-sub-content.svelte +1 -1
  84. package/dist/components/primitives/context-menu/context-menu-sub-trigger.svelte +1 -1
  85. package/dist/components/primitives/dialog/dialog-content.svelte +7 -3
  86. package/dist/components/primitives/drawer/Drawer.svelte +1 -1
  87. package/dist/components/primitives/dropdown-menu/dropdown-menu-checkbox-item.svelte +31 -11
  88. package/dist/components/primitives/dropdown-menu/dropdown-menu-checkbox-item.svelte.d.ts +2 -0
  89. package/dist/components/primitives/dropdown-menu/dropdown-menu-content.svelte +5 -1
  90. package/dist/components/primitives/dropdown-menu/dropdown-menu-content.svelte.d.ts +2 -0
  91. package/dist/components/primitives/dropdown-menu/dropdown-menu-item.svelte +4 -2
  92. package/dist/components/primitives/dropdown-menu/dropdown-menu-item.svelte.d.ts +2 -1
  93. package/dist/components/primitives/dropdown-menu/dropdown-menu-radio-item.svelte +1 -1
  94. package/dist/components/primitives/dropdown-menu/dropdown-menu-shortcut.svelte +1 -1
  95. package/dist/components/primitives/dropdown-menu/dropdown-menu-sub-content.svelte +1 -1
  96. package/dist/components/primitives/dropdown-menu/dropdown-menu-sub-trigger.svelte +1 -1
  97. package/dist/components/primitives/index.d.ts +1 -1
  98. package/dist/components/primitives/index.js +1 -1
  99. package/dist/components/primitives/input/input.svelte +4 -4
  100. package/dist/components/primitives/input-group/input-group-input.svelte.d.ts +1 -1
  101. package/dist/components/primitives/input-group/input-group.svelte +1 -1
  102. package/dist/components/primitives/mode-toggle/mode-toggle.svelte +5 -2
  103. package/dist/components/primitives/popover/popover-content.svelte +1 -1
  104. package/dist/components/primitives/resizable/resizable-handle.svelte +2 -2
  105. package/dist/components/primitives/scroll-area/scroll-area.svelte +1 -1
  106. package/dist/components/primitives/search/search.svelte +2 -2
  107. package/dist/components/primitives/select/select-content.svelte +1 -1
  108. package/dist/components/primitives/select/select-item.svelte +1 -1
  109. package/dist/components/primitives/select/select-trigger.svelte +1 -1
  110. package/dist/components/primitives/slider/slider.svelte +8 -3
  111. package/dist/components/primitives/slider/slider.svelte.d.ts +5 -1
  112. package/dist/components/primitives/switch/switch.svelte +1 -1
  113. package/dist/components/primitives/tabs/tabs-list.svelte +2 -2
  114. package/dist/components/primitives/tabs/tabs-list.svelte.d.ts +6 -6
  115. package/dist/components/primitives/tabs/tabs-trigger.svelte +3 -3
  116. package/dist/components/primitives/tabs/tabs-trigger.svelte.d.ts +7 -7
  117. package/dist/components/primitives/textarea/textarea.svelte +1 -1
  118. package/dist/components/primitives/toggle/toggle.svelte +11 -3
  119. package/dist/components/primitives/toggle/toggle.svelte.d.ts +13 -4
  120. package/dist/components/primitives/toggle-group/toggle-group-item.svelte +1 -1
  121. package/dist/components/primitives/toggle-group/toggle-group.svelte +1 -1
  122. package/dist/components/primitives/tooltip/index.d.ts +6 -0
  123. package/dist/components/primitives/tooltip/index.js +8 -0
  124. package/dist/components/primitives/tooltip/tooltip-content.svelte +34 -0
  125. package/dist/components/primitives/tooltip/tooltip-content.svelte.d.ts +10 -0
  126. package/dist/components/primitives/tooltip/tooltip-portal.svelte +7 -0
  127. package/dist/components/primitives/tooltip/tooltip-portal.svelte.d.ts +4 -0
  128. package/dist/components/primitives/tooltip/tooltip-provider.svelte +7 -0
  129. package/dist/components/primitives/tooltip/tooltip-provider.svelte.d.ts +4 -0
  130. package/dist/components/primitives/tooltip/tooltip-trigger.svelte +7 -0
  131. package/dist/components/primitives/tooltip/tooltip-trigger.svelte.d.ts +4 -0
  132. package/dist/components/primitives/tooltip/tooltip.svelte +7 -0
  133. package/dist/components/primitives/tooltip/tooltip.svelte.d.ts +4 -0
  134. package/dist/components/viewer/MeshMetadataPanel.svelte +119 -0
  135. package/dist/components/viewer/MeshMetadataPanel.svelte.d.ts +9 -0
  136. package/dist/components/viewer/SceneManager.svelte +32 -21
  137. package/dist/components/viewer/SceneManager.svelte.d.ts +3 -1
  138. package/dist/components/viewer/Viewer.svelte +195 -207
  139. package/dist/components/viewer/Viewer.svelte.d.ts +7 -2
  140. package/dist/components/viewer/ViewerDock.svelte +404 -0
  141. package/dist/components/viewer/ViewerDock.svelte.d.ts +42 -0
  142. package/dist/composables/useFooterItem.svelte.d.ts +1 -1
  143. package/dist/composables/useFooterItem.svelte.js +1 -1
  144. package/dist/compute/liveProgress.svelte.d.ts +16 -0
  145. package/dist/compute/liveProgress.svelte.js +90 -0
  146. package/dist/compute/run-panel-action.d.ts +8 -0
  147. package/dist/compute/run-panel-action.js +12 -0
  148. package/dist/compute/solving.svelte.d.ts +11 -7
  149. package/dist/compute/solving.svelte.js +64 -45
  150. package/dist/compute/useSolveSession.svelte.js +3 -0
  151. package/dist/constants.d.ts +6 -8
  152. package/dist/constants.js +8 -11
  153. package/dist/contexts/hostContext.svelte.d.ts +39 -0
  154. package/dist/contexts/hostContext.svelte.js +9 -0
  155. package/dist/features/chart/plotly-compat.d.ts +15 -0
  156. package/dist/features/chart/plotly-compat.js +124 -0
  157. package/dist/i18n/localeContext.svelte.d.ts +10 -3
  158. package/dist/i18n/localeContext.svelte.js +27 -5
  159. package/dist/i18n/messages.d.ts +164 -2
  160. package/dist/i18n/messages.js +281 -11
  161. package/dist/index.d.ts +6 -1
  162. package/dist/index.js +5 -0
  163. package/dist/public.d.ts +2 -1
  164. package/dist/public.js +1 -0
  165. package/dist/schema/host-support.d.ts +29 -0
  166. package/dist/schema/host-support.js +97 -0
  167. package/dist/schema/option-display.d.ts +12 -0
  168. package/dist/schema/option-display.js +29 -0
  169. package/dist/schema/param-exporter.d.ts +4 -1
  170. package/dist/schema/param-exporter.js +10 -6
  171. package/dist/schema/visibility-rules.d.ts +7 -1
  172. package/dist/schema/visibility-rules.js +38 -0
  173. package/dist/styles/base.css +116 -83
  174. package/dist/types/actionButton.d.ts +17 -3
  175. package/dist/types/presetLabels.d.ts +3 -2
  176. package/dist/types/presetLabels.js +9 -8
  177. package/dist/utils/color-space.d.ts +21 -0
  178. package/dist/utils/color-space.js +90 -0
  179. package/dist/utils/file-download.js +10 -22
  180. package/dist/utils/host-files.d.ts +12 -0
  181. package/dist/utils/host-files.js +46 -0
  182. package/dist/utils/loadScript.d.ts +2 -0
  183. package/dist/utils/loadScript.js +2 -0
  184. package/dist/utils/webp-to-png.d.ts +10 -0
  185. package/dist/utils/webp-to-png.js +41 -0
  186. package/package.json +30 -22
  187. package/src/lib/components/ErrorScreen.svelte +7 -3
  188. package/src/lib/components/compute/AppLayout.svelte +307 -123
  189. package/src/lib/components/compute/CollapsedPanelStrip.svelte +4 -2
  190. package/src/lib/components/compute/ComputeApp.svelte +46 -22
  191. package/src/lib/components/compute/ComputeMessagesDialog.svelte +32 -42
  192. package/src/lib/components/compute/ParameterPresetManager.svelte +16 -65
  193. package/src/lib/components/compute/SolveMessages.svelte +130 -123
  194. package/src/lib/components/layout/AppShell.svelte +10 -6
  195. package/src/lib/components/layout/FooterItemRenderer.svelte +2 -2
  196. package/src/lib/components/layout/PageContent.svelte +3 -2
  197. package/src/lib/components/layout/PageFooter.svelte +26 -14
  198. package/src/lib/components/layout/PageHeader.svelte +14 -10
  199. package/src/lib/components/layout/SectionHeader.svelte +4 -2
  200. package/src/lib/components/layout/SideNav.svelte +57 -8
  201. package/src/lib/components/layout/SubNav.svelte +2 -2
  202. package/src/lib/components/preview/ChartOutput.svelte +131 -65
  203. package/src/lib/components/preview/Group.svelte +42 -22
  204. package/src/lib/components/preview/HelpButton.svelte +5 -3
  205. package/src/lib/components/preview/ImageOutput.svelte +435 -71
  206. package/src/lib/components/preview/InputControl.svelte +125 -51
  207. package/src/lib/components/preview/OutputDisplay.svelte +42 -28
  208. package/src/lib/components/preview/TabBar.svelte +8 -6
  209. package/src/lib/components/preview/TabContent.svelte +66 -31
  210. package/src/lib/components/preview/TabLayout.svelte +29 -8
  211. package/src/lib/components/preview/inputs/CheckboxInput.svelte +1 -1
  212. package/src/lib/components/preview/inputs/ChecklistInput.svelte +11 -6
  213. package/src/lib/components/preview/inputs/ColorInput.svelte +357 -13
  214. package/src/lib/components/preview/inputs/DropdownInput.svelte +20 -5
  215. package/src/lib/components/preview/inputs/FileInput.svelte +115 -103
  216. package/src/lib/components/preview/inputs/GeometryInput.svelte +128 -0
  217. package/src/lib/components/preview/inputs/NumberInput.svelte +98 -16
  218. package/src/lib/components/preview/inputs/OptionGlyph.svelte +29 -0
  219. package/src/lib/components/preview/inputs/SegmentedInput.svelte +45 -0
  220. package/src/lib/components/preview/inputs/TextInput.svelte +11 -6
  221. package/src/lib/components/preview/inputs/TileInput.svelte +83 -0
  222. package/src/lib/components/preview/inputs/index.ts +3 -0
  223. package/src/lib/components/primitives/CalculateButton.svelte +56 -24
  224. package/src/lib/components/primitives/Callout.svelte +9 -7
  225. package/src/lib/components/primitives/DataTable.svelte +38 -4
  226. package/src/lib/components/primitives/FilterableDropdown.svelte +14 -2
  227. package/src/lib/components/primitives/ImageUploadField.svelte +1 -1
  228. package/src/lib/components/primitives/IssueList.svelte +1 -1
  229. package/src/lib/components/primitives/SeverityIcon.svelte +1 -1
  230. package/src/lib/components/primitives/SolvingIndicator.svelte +245 -15
  231. package/src/lib/components/primitives/StateDisplay.svelte +1 -1
  232. package/src/lib/components/primitives/alert-dialog/alert-dialog-action.svelte +2 -2
  233. package/src/lib/components/primitives/alert-dialog/alert-dialog-cancel.svelte +2 -2
  234. package/src/lib/components/primitives/alert-dialog/alert-dialog-content.svelte +2 -2
  235. package/src/lib/components/primitives/alert-dialog/alert-dialog-description.svelte +1 -1
  236. package/src/lib/components/primitives/alert-dialog/alert-dialog-footer.svelte +1 -1
  237. package/src/lib/components/primitives/alert-dialog/alert-dialog-header.svelte +1 -1
  238. package/src/lib/components/primitives/alert-dialog/alert-dialog-overlay.svelte +1 -1
  239. package/src/lib/components/primitives/alert-dialog/alert-dialog-title.svelte +1 -1
  240. package/src/lib/components/primitives/badge/badge.svelte +5 -5
  241. package/src/lib/components/primitives/button/button.svelte +10 -8
  242. package/src/lib/components/primitives/button-group/button-group-separator.svelte +2 -2
  243. package/src/lib/components/primitives/button-group/button-group-text.svelte +1 -1
  244. package/src/lib/components/primitives/button-group/button-group.svelte +1 -1
  245. package/src/lib/components/primitives/card/card-content.svelte +1 -1
  246. package/src/lib/components/primitives/card/card-description.svelte +1 -1
  247. package/src/lib/components/primitives/card/card-footer.svelte +1 -1
  248. package/src/lib/components/primitives/card/card-header.svelte +1 -1
  249. package/src/lib/components/primitives/card/card-title.svelte +1 -1
  250. package/src/lib/components/primitives/card/card.svelte +2 -2
  251. package/src/lib/components/primitives/checkbox/checkbox.svelte +2 -2
  252. package/src/lib/components/primitives/command/command-dialog.svelte +2 -2
  253. package/src/lib/components/primitives/command/command-empty.svelte +1 -1
  254. package/src/lib/components/primitives/command/command-group.svelte +1 -1
  255. package/src/lib/components/primitives/command/command-input.svelte +3 -3
  256. package/src/lib/components/primitives/command/command-item.svelte +2 -2
  257. package/src/lib/components/primitives/command/command-link-item.svelte +2 -2
  258. package/src/lib/components/primitives/command/command-list.svelte +1 -1
  259. package/src/lib/components/primitives/command/command-separator.svelte +1 -1
  260. package/src/lib/components/primitives/command/command-shortcut.svelte +1 -1
  261. package/src/lib/components/primitives/command/command.svelte +2 -2
  262. package/src/lib/components/primitives/context-menu/context-menu-checkbox-item.svelte +2 -2
  263. package/src/lib/components/primitives/context-menu/context-menu-content.svelte +2 -2
  264. package/src/lib/components/primitives/context-menu/context-menu-group-heading.svelte +1 -1
  265. package/src/lib/components/primitives/context-menu/context-menu-item.svelte +2 -2
  266. package/src/lib/components/primitives/context-menu/context-menu-label.svelte +1 -1
  267. package/src/lib/components/primitives/context-menu/context-menu-radio-item.svelte +2 -2
  268. package/src/lib/components/primitives/context-menu/context-menu-separator.svelte +1 -1
  269. package/src/lib/components/primitives/context-menu/context-menu-shortcut.svelte +1 -1
  270. package/src/lib/components/primitives/context-menu/context-menu-sub-content.svelte +2 -2
  271. package/src/lib/components/primitives/context-menu/context-menu-sub-trigger.svelte +2 -2
  272. package/src/lib/components/primitives/dialog/dialog-content.svelte +8 -4
  273. package/src/lib/components/primitives/dialog/dialog-description.svelte +1 -1
  274. package/src/lib/components/primitives/dialog/dialog-footer.svelte +1 -1
  275. package/src/lib/components/primitives/dialog/dialog-header.svelte +1 -1
  276. package/src/lib/components/primitives/dialog/dialog-overlay.svelte +1 -1
  277. package/src/lib/components/primitives/dialog/dialog-title.svelte +1 -1
  278. package/src/lib/components/primitives/drawer/Drawer.svelte +1 -1
  279. package/src/lib/components/primitives/dropdown-menu/dropdown-menu-checkbox-item.svelte +32 -12
  280. package/src/lib/components/primitives/dropdown-menu/dropdown-menu-content.svelte +6 -2
  281. package/src/lib/components/primitives/dropdown-menu/dropdown-menu-group-heading.svelte +1 -1
  282. package/src/lib/components/primitives/dropdown-menu/dropdown-menu-item.svelte +5 -3
  283. package/src/lib/components/primitives/dropdown-menu/dropdown-menu-label.svelte +1 -1
  284. package/src/lib/components/primitives/dropdown-menu/dropdown-menu-radio-item.svelte +2 -2
  285. package/src/lib/components/primitives/dropdown-menu/dropdown-menu-separator.svelte +1 -1
  286. package/src/lib/components/primitives/dropdown-menu/dropdown-menu-shortcut.svelte +2 -2
  287. package/src/lib/components/primitives/dropdown-menu/dropdown-menu-sub-content.svelte +2 -2
  288. package/src/lib/components/primitives/dropdown-menu/dropdown-menu-sub-trigger.svelte +2 -2
  289. package/src/lib/components/primitives/empty/empty-content.svelte +1 -1
  290. package/src/lib/components/primitives/empty/empty-description.svelte +1 -1
  291. package/src/lib/components/primitives/empty/empty-header.svelte +1 -1
  292. package/src/lib/components/primitives/empty/empty-media.svelte +1 -1
  293. package/src/lib/components/primitives/empty/empty-title.svelte +1 -1
  294. package/src/lib/components/primitives/empty/empty.svelte +1 -1
  295. package/src/lib/components/primitives/field/field-content.svelte +1 -1
  296. package/src/lib/components/primitives/field/field-description.svelte +1 -1
  297. package/src/lib/components/primitives/field/field-error.svelte +1 -1
  298. package/src/lib/components/primitives/field/field-group.svelte +1 -1
  299. package/src/lib/components/primitives/field/field-label.svelte +2 -2
  300. package/src/lib/components/primitives/field/field-legend.svelte +1 -1
  301. package/src/lib/components/primitives/field/field-separator.svelte +2 -2
  302. package/src/lib/components/primitives/field/field-set.svelte +1 -1
  303. package/src/lib/components/primitives/field/field-title.svelte +1 -1
  304. package/src/lib/components/primitives/field/field.svelte +1 -1
  305. package/src/lib/components/primitives/index.ts +1 -1
  306. package/src/lib/components/primitives/input/input.svelte +5 -5
  307. package/src/lib/components/primitives/input-group/input-group-addon.svelte +1 -1
  308. package/src/lib/components/primitives/input-group/input-group-button.svelte +2 -2
  309. package/src/lib/components/primitives/input-group/input-group-input.svelte +2 -2
  310. package/src/lib/components/primitives/input-group/input-group-text.svelte +1 -1
  311. package/src/lib/components/primitives/input-group/input-group-textarea.svelte +2 -2
  312. package/src/lib/components/primitives/input-group/input-group.svelte +2 -2
  313. package/src/lib/components/primitives/label/label.svelte +1 -1
  314. package/src/lib/components/primitives/mode-toggle/mode-toggle.svelte +5 -2
  315. package/src/lib/components/primitives/popover/popover-content.svelte +2 -2
  316. package/src/lib/components/primitives/popover/popover-description.svelte +1 -1
  317. package/src/lib/components/primitives/popover/popover-header.svelte +1 -1
  318. package/src/lib/components/primitives/popover/popover-title.svelte +1 -1
  319. package/src/lib/components/primitives/popover/popover-trigger.svelte +1 -1
  320. package/src/lib/components/primitives/resizable/resizable-handle.svelte +3 -3
  321. package/src/lib/components/primitives/resizable/resizable-pane-group.svelte +1 -1
  322. package/src/lib/components/primitives/scroll-area/scroll-area-scrollbar.svelte +1 -1
  323. package/src/lib/components/primitives/scroll-area/scroll-area.svelte +2 -2
  324. package/src/lib/components/primitives/search/search.svelte +3 -3
  325. package/src/lib/components/primitives/select/select-content.svelte +2 -2
  326. package/src/lib/components/primitives/select/select-group-heading.svelte +1 -1
  327. package/src/lib/components/primitives/select/select-item.svelte +2 -2
  328. package/src/lib/components/primitives/select/select-label.svelte +1 -1
  329. package/src/lib/components/primitives/select/select-scroll-down-button.svelte +1 -1
  330. package/src/lib/components/primitives/select/select-scroll-up-button.svelte +1 -1
  331. package/src/lib/components/primitives/select/select-separator.svelte +2 -2
  332. package/src/lib/components/primitives/select/select-trigger.svelte +2 -2
  333. package/src/lib/components/primitives/separator/separator.svelte +1 -1
  334. package/src/lib/components/primitives/slider/slider.svelte +9 -4
  335. package/src/lib/components/primitives/spinner/spinner.svelte +1 -1
  336. package/src/lib/components/primitives/switch/switch.svelte +2 -2
  337. package/src/lib/components/primitives/tabs/tabs-content.svelte +1 -1
  338. package/src/lib/components/primitives/tabs/tabs-list.svelte +3 -3
  339. package/src/lib/components/primitives/tabs/tabs-trigger.svelte +4 -4
  340. package/src/lib/components/primitives/tabs/tabs.svelte +1 -1
  341. package/src/lib/components/primitives/textarea/textarea.svelte +2 -2
  342. package/src/lib/components/primitives/toggle/toggle.svelte +12 -4
  343. package/src/lib/components/primitives/toggle-group/toggle-group-item.svelte +3 -3
  344. package/src/lib/components/primitives/toggle-group/toggle-group.svelte +3 -3
  345. package/src/lib/components/primitives/tooltip/index.ts +19 -0
  346. package/src/lib/components/primitives/tooltip/tooltip-content.svelte +34 -0
  347. package/src/lib/components/primitives/tooltip/tooltip-portal.svelte +7 -0
  348. package/src/lib/components/primitives/tooltip/tooltip-provider.svelte +7 -0
  349. package/src/lib/components/primitives/tooltip/tooltip-trigger.svelte +7 -0
  350. package/src/lib/components/primitives/tooltip/tooltip.svelte +7 -0
  351. package/src/lib/components/viewer/MeshMetadataPanel.svelte +119 -0
  352. package/src/lib/components/viewer/SceneManager.svelte +32 -21
  353. package/src/lib/components/viewer/Viewer.svelte +196 -208
  354. package/src/lib/components/viewer/ViewerDock.svelte +404 -0
  355. package/src/lib/composables/useFooterItem.svelte.ts +1 -1
  356. package/src/lib/compute/liveProgress.svelte.ts +103 -0
  357. package/src/lib/compute/run-panel-action.ts +16 -0
  358. package/src/lib/compute/solving.svelte.ts +66 -50
  359. package/src/lib/compute/useSolveSession.svelte.ts +3 -0
  360. package/src/lib/constants.ts +8 -11
  361. package/src/lib/contexts/hostContext.svelte.ts +51 -0
  362. package/src/lib/features/chart/plotly-compat.ts +141 -0
  363. package/src/lib/i18n/localeContext.svelte.ts +40 -5
  364. package/src/lib/i18n/messages.ts +487 -13
  365. package/src/lib/index.ts +23 -0
  366. package/src/lib/public.ts +2 -0
  367. package/src/lib/schema/host-support.ts +129 -0
  368. package/src/lib/schema/option-display.ts +44 -0
  369. package/src/lib/schema/param-exporter.ts +10 -7
  370. package/src/lib/schema/visibility-rules.ts +50 -1
  371. package/src/lib/styles/base.css +116 -83
  372. package/src/lib/types/actionButton.ts +16 -3
  373. package/src/lib/types/presetLabels.ts +13 -11
  374. package/src/lib/utils/color-space.ts +107 -0
  375. package/src/lib/utils/file-download.ts +11 -21
  376. package/src/lib/utils/host-files.ts +61 -0
  377. package/src/lib/utils/loadScript.ts +3 -0
  378. package/src/lib/utils/webp-to-png.ts +48 -0
  379. package/dist/components/primitives/theme-switcher/ThemeSwitcher.svelte +0 -39
  380. package/dist/components/primitives/theme-switcher/ThemeSwitcher.svelte.d.ts +0 -3
  381. package/dist/components/primitives/theme-switcher/index.d.ts +0 -1
  382. package/dist/components/primitives/theme-switcher/index.js +0 -1
  383. package/dist/components/viewer/MeshMetadataDialog.svelte +0 -126
  384. package/dist/components/viewer/MeshMetadataDialog.svelte.d.ts +0 -10
  385. package/dist/styles/themes/cyberpunk.css +0 -98
  386. package/dist/styles/themes/neutral.css +0 -72
  387. package/dist/styles/themes/ocean.css +0 -75
  388. package/dist/styles/themes/selva.css +0 -105
  389. package/dist/theme/themeStore.svelte.d.ts +0 -12
  390. package/dist/theme/themeStore.svelte.js +0 -40
  391. package/dist/theme/themes.d.ts +0 -5
  392. package/dist/theme/themes.js +0 -13
  393. package/src/lib/components/primitives/theme-switcher/ThemeSwitcher.svelte +0 -39
  394. package/src/lib/components/primitives/theme-switcher/index.ts +0 -1
  395. package/src/lib/components/viewer/MeshMetadataDialog.svelte +0 -126
  396. package/src/lib/styles/themes/cyberpunk.css +0 -98
  397. package/src/lib/styles/themes/neutral.css +0 -72
  398. package/src/lib/styles/themes/ocean.css +0 -75
  399. package/src/lib/styles/themes/selva.css +0 -105
  400. package/src/lib/theme/themeStore.svelte.ts +0 -50
  401. package/src/lib/theme/themes.ts +0 -18
@@ -1,10 +1,19 @@
1
1
  <script lang="ts">
2
- import { Input } from '$lib/components/primitives/input';
3
- import { Button } from '$lib/components/primitives/button';
4
- import { Label } from '$lib/components/primitives/label';
5
- import { toast } from '$lib/components/primitives/sonner';
2
+ import { Input } from '#lib/components/primitives/input/index.js';
3
+ import { Button } from '#lib/components/primitives/button/index.js';
4
+ import { Label } from '#lib/components/primitives/label/index.js';
5
+ import { toast } from '#lib/components/primitives/sonner/index.js';
6
6
  import { FileUp, Link, CircleAlert, CircleCheck } from '@lucide/svelte';
7
- import { APP_DEFAULTS } from '$lib/constants';
7
+ import { APP_DEFAULTS } from '#lib/constants.js';
8
+ import { getLocaleContext } from '#lib/i18n/localeContext.svelte.js';
9
+ import { formatMessage } from '#lib/i18n/messages.js';
10
+ import { pickFile } from '#lib/utils/host-files.js';
11
+ import {
12
+ acceptsFileEnding,
13
+ isWebp,
14
+ shouldConvertWebp,
15
+ webpToPng
16
+ } from '#lib/utils/webp-to-png.js';
8
17
 
9
18
  interface Props {
10
19
  value?: string;
@@ -22,6 +31,9 @@
22
31
  onChange
23
32
  }: Props = $props();
24
33
 
34
+ const locale = getLocaleContext();
35
+ const t = $derived(locale.messages);
36
+
25
37
  let effectiveModes = $derived(
26
38
  allowedInputModes && allowedInputModes.length > 0
27
39
  ? allowedInputModes
@@ -35,7 +47,6 @@
35
47
  activeMode = effectiveModes.includes(preferred) ? preferred : effectiveModes[0];
36
48
  });
37
49
 
38
- let fileInput: HTMLInputElement | null = $state(null);
39
50
  let uploadedFileName = $state('');
40
51
  let urlInput = $state('');
41
52
  let isDragging = $state(false);
@@ -52,9 +63,12 @@
52
63
  if (parsed.type === 'base64') {
53
64
  if (parsed._isMetadata && parsed._fileSize) {
54
65
  const sizeMB = (parsed._fileSize / 1024 / 1024).toFixed(2);
55
- uploadedFileName = `Uploaded file (${parsed.fileEnding}, ${sizeMB}MB)`;
66
+ uploadedFileName = formatMessage(t.uploadedFileSize, {
67
+ ext: parsed.fileEnding,
68
+ size: sizeMB
69
+ });
56
70
  } else {
57
- uploadedFileName = `Uploaded file (${parsed.fileEnding})`;
71
+ uploadedFileName = formatMessage(t.uploadedFile, { ext: parsed.fileEnding });
58
72
  }
59
73
  } else if (parsed.type === 'url') {
60
74
  urlInput = parsed.file;
@@ -65,6 +79,20 @@
65
79
  }
66
80
  });
67
81
 
82
+ function formatNotAccepted(fileEnding: string): string {
83
+ return formatMessage(t.fileFormatNotAccepted, {
84
+ ext: fileEnding,
85
+ formats: acceptedFormats.join(', ')
86
+ });
87
+ }
88
+
89
+ function formatTooLarge(bytes: number): string {
90
+ return formatMessage(t.fileTooLarge, {
91
+ size: (bytes / 1024 / 1024).toFixed(2),
92
+ max: APP_DEFAULTS.FILE_UPLOAD.MAX_SIZE_MB
93
+ });
94
+ }
95
+
68
96
  function getFileExtension(filename: string): string {
69
97
  // Strip query strings before extracting extension
70
98
  const cleanPath = filename.split('?')[0];
@@ -72,9 +100,27 @@
72
100
  return ext.toLowerCase();
73
101
  }
74
102
 
75
- function isValidFileExtension(fileEnding: string): boolean {
76
- if (acceptedFormats.length === 0) return true;
77
- return acceptedFormats.includes(fileEnding.toLowerCase());
103
+ // Runs before the size cap: the PNG is larger than the WebP it came from. A WebP already over
104
+ // the cap is passed through unconverted, so the cap rejects it without a full decode.
105
+ async function prepare(
106
+ blob: Blob,
107
+ fileEnding: string
108
+ ): Promise<{ blob: Blob; fileEnding: string }> {
109
+ if (blob.size > APP_DEFAULTS.FILE_UPLOAD.MAX_SIZE_BYTES) return { blob, fileEnding };
110
+ if (!shouldConvertWebp(fileEnding, acceptedFormats)) return { blob, fileEnding };
111
+ return { blob: await webpToPng(blob), fileEnding: '.png' };
112
+ }
113
+
114
+ function readBase64(blob: Blob): Promise<string> {
115
+ return new Promise<string>((resolve, reject) => {
116
+ const reader = new FileReader();
117
+ reader.onload = (e) => {
118
+ const result = e.target?.result as string;
119
+ resolve(result.includes(',') ? result.split(',')[1] : result);
120
+ };
121
+ reader.onerror = () => reject(new Error(t.fileReadFailed));
122
+ reader.readAsDataURL(blob);
123
+ });
78
124
  }
79
125
 
80
126
  function isCorsError(error: unknown): boolean {
@@ -96,56 +142,52 @@
96
142
  try {
97
143
  parsedUrl = new URL(urlInput);
98
144
  } catch {
99
- urlError = { message: 'Invalid URL — make sure it starts with https://', isCors: false };
145
+ urlError = { message: t.urlInvalid, isCors: false };
100
146
  return;
101
147
  }
102
148
 
103
149
  if (parsedUrl.protocol !== 'https:' && parsedUrl.protocol !== 'http:') {
104
- urlError = { message: 'Only http:// and https:// URLs are supported.', isCors: false };
150
+ urlError = { message: t.urlProtocolUnsupported, isCors: false };
105
151
  return;
106
152
  }
107
153
 
108
154
  const response = await fetch(urlInput);
109
155
  if (!response.ok) {
110
156
  urlError = {
111
- message: `Server returned ${response.status} ${response.statusText}. Check the URL is correct and publicly accessible.`,
157
+ message: formatMessage(t.urlServerError, {
158
+ status: `${response.status} ${response.statusText}`.trim()
159
+ }),
112
160
  isCors: false
113
161
  };
114
162
  return;
115
163
  }
116
164
 
117
- const blob = await response.blob();
118
- const fileEnding = getFileExtension(urlInput);
165
+ const fetched = await response.blob();
166
+ const requestedEnding = (await isWebp(fetched)) ? '.webp' : getFileExtension(urlInput);
119
167
 
120
- if (!isValidFileExtension(fileEnding)) {
121
- urlError = {
122
- message: `File format "${fileEnding}" is not accepted. Allowed: ${acceptedFormats.join(', ')}`,
123
- isCors: false
124
- };
168
+ if (!acceptsFileEnding(requestedEnding, acceptedFormats)) {
169
+ urlError = { message: formatNotAccepted(requestedEnding), isCors: false };
125
170
  return;
126
171
  }
127
172
 
173
+ const { blob, fileEnding } = await prepare(fetched, requestedEnding);
174
+
128
175
  const maxSize = APP_DEFAULTS.FILE_UPLOAD.MAX_SIZE_BYTES;
129
176
  if (blob.size > maxSize) {
130
177
  urlError = {
131
- message: `File too large: ${(blob.size / 1024 / 1024).toFixed(2)}MB (max ${APP_DEFAULTS.FILE_UPLOAD.MAX_SIZE_MB}MB). Download it and use Upload instead.`,
178
+ message: `${formatTooLarge(blob.size)} ${t.fileTooLargeUseUpload}`,
132
179
  isCors: false
133
180
  };
134
181
  return;
135
182
  }
136
183
 
137
- const base64Data = await new Promise<string>((resolve, reject) => {
138
- const reader = new FileReader();
139
- reader.onload = (e) => {
140
- const result = e.target?.result as string;
141
- resolve(result.includes(',') ? result.split(',')[1] : result);
142
- };
143
- reader.onerror = () => reject(new Error('Failed to read the downloaded file.'));
144
- reader.readAsDataURL(blob);
145
- });
184
+ const base64Data = await readBase64(blob);
146
185
  const data = JSON.stringify({ file: base64Data, type: 'base64', fileEnding });
147
186
  value = data;
148
- urlSuccess = `Loaded ${fileEnding} (${(blob.size / 1024).toFixed(0)} KB)`;
187
+ urlSuccess = formatMessage(t.fileLoaded, {
188
+ ext: fileEnding,
189
+ size: (blob.size / 1024).toFixed(0)
190
+ });
149
191
  onChange(data);
150
192
  } catch (error) {
151
193
  if (isCorsError(error)) {
@@ -158,26 +200,22 @@
158
200
  })();
159
201
  // The opaque TypeError hides CORS blocks and plain network failures (404, DNS)
160
202
  // alike, so the message has to cover both.
161
- let hint =
162
- 'Check that the URL is correct and the file is publicly accessible. If the server requires login, download the file and use the Upload option instead.';
203
+ let hint = t.urlUnreachableHint;
163
204
  if (host.includes('sharepoint.com') || host.includes('onedrive.com')) {
164
- hint =
165
- 'SharePoint/OneDrive blocks browser access. Open the file in SharePoint, use "Download" to save it locally, then upload it here.';
205
+ hint = t.urlHintSharePoint;
166
206
  } else if (host.includes('drive.google.com')) {
167
- hint =
168
- 'Google Drive blocks browser access. Use "Download" in Google Drive to save the file locally, then upload it here.';
207
+ hint = t.urlHintGoogleDrive;
169
208
  } else if (host.includes('dropbox.com')) {
170
- hint =
171
- 'Dropbox links may block browser access. Try changing "?dl=0" to "?dl=1" in the URL, or download and upload instead.';
209
+ hint = t.urlHintDropbox;
172
210
  }
173
211
  urlError = {
174
- message: `Could not reach "${host}" — the file may not exist, or the server does not allow browser access.`,
212
+ message: formatMessage(t.urlUnreachable, { host }),
175
213
  isCors: true,
176
214
  hint
177
215
  };
178
216
  } else {
179
217
  urlError = {
180
- message: error instanceof Error ? error.message : 'Unknown error',
218
+ message: error instanceof Error ? error.message : t.unknownError,
181
219
  isCors: false
182
220
  };
183
221
  }
@@ -186,50 +224,39 @@
186
224
  }
187
225
  }
188
226
 
189
- function handleFileUpload(event: Event) {
190
- const target = event.target as HTMLInputElement;
191
- const file = target.files?.[0];
192
- if (!file) return;
193
-
194
- const fileEnding = getFileExtension(file.name);
227
+ async function handleFile(file: File) {
228
+ const requestedEnding = getFileExtension(file.name);
195
229
 
196
- if (!isValidFileExtension(fileEnding)) {
197
- toast.error(`File format not accepted: ${fileEnding}`);
230
+ if (!acceptsFileEnding(requestedEnding, acceptedFormats)) {
231
+ toast.error(formatNotAccepted(requestedEnding));
198
232
  return;
199
233
  }
200
234
 
201
- // The file is base64-embedded into the compute request body, so without a
202
- // client-side cap an oversize file comes back as an opaque 413. This cap is
203
- // currently LOOSER than the server's COMPUTE_REQUEST_MAX_BYTES (see the TEMP
204
- // note in constants.ts) — some files pass here and still 413. The URL import
205
- // path applies the same cap.
206
- if (file.size > APP_DEFAULTS.FILE_UPLOAD.MAX_SIZE_BYTES) {
207
- toast.error(
208
- `File too large: ${(file.size / 1024 / 1024).toFixed(2)}MB (max ${APP_DEFAULTS.FILE_UPLOAD.MAX_SIZE_MB}MB).`
209
- );
210
- return;
211
- }
212
-
213
- uploadedFileName = file.name;
214
-
215
- const reader = new FileReader();
216
- reader.onload = (e) => {
217
- const base64 = e.target?.result as string;
218
- const base64Data = base64.includes(',') ? base64.split(',')[1] : base64;
235
+ try {
236
+ const { blob, fileEnding } = await prepare(file, requestedEnding);
237
+
238
+ // The file is base64-embedded into the compute request body, so without a
239
+ // client-side cap an oversize file comes back as an opaque 413. This cap is
240
+ // currently LOOSER than the server's COMPUTE_REQUEST_MAX_BYTES (see the TEMP
241
+ // note in constants.ts) — some files pass here and still 413. The URL import
242
+ // path applies the same cap.
243
+ if (blob.size > APP_DEFAULTS.FILE_UPLOAD.MAX_SIZE_BYTES) {
244
+ toast.error(formatTooLarge(blob.size));
245
+ return;
246
+ }
219
247
 
220
- const data = JSON.stringify({
221
- file: base64Data,
222
- type: 'base64',
223
- fileEnding
224
- });
248
+ uploadedFileName = file.name;
249
+ const data = JSON.stringify({ file: await readBase64(blob), type: 'base64', fileEnding });
225
250
  value = data;
226
251
  onChange(data);
227
- };
228
- reader.readAsDataURL(file);
252
+ } catch (error) {
253
+ toast.error(error instanceof Error ? error.message : t.fileReadFailed);
254
+ }
229
255
  }
230
256
 
231
- function openFilePicker() {
232
- fileInput?.click();
257
+ async function openFilePicker() {
258
+ const file = await pickFile({ accept: acceptAttribute });
259
+ if (file) handleFile(file);
233
260
  }
234
261
 
235
262
  function handleDragOver(e: DragEvent) {
@@ -245,27 +272,14 @@
245
272
  e.preventDefault();
246
273
  isDragging = false;
247
274
 
248
- const files = e.dataTransfer?.files;
249
- if (files?.[0]) {
250
- const fakeEvent = new Event('change');
251
- const fakeTarget = { files: files } as HTMLInputElement;
252
- Object.defineProperty(fakeEvent, 'target', { value: fakeTarget });
253
- handleFileUpload(fakeEvent);
254
- }
275
+ const file = e.dataTransfer?.files?.[0];
276
+ if (file) handleFile(file);
255
277
  }
256
278
 
257
279
  const acceptAttribute = $derived(acceptedFormats.length > 0 ? acceptedFormats.join(',') : '*');
258
280
  </script>
259
281
 
260
282
  <div class="gap-2.5 flex w-full flex-col">
261
- <input
262
- bind:this={fileInput}
263
- type="file"
264
- accept={acceptAttribute}
265
- onchange={handleFileUpload}
266
- class="hidden"
267
- />
268
-
269
283
  {#if showToggle}
270
284
  <div class="p-0.5 text-xs flex rounded-md bg-muted">
271
285
  {#each effectiveModes as mode (mode)}
@@ -273,13 +287,13 @@
273
287
  type="button"
274
288
  onclick={() => (activeMode = mode)}
275
289
  aria-pressed={activeMode === mode}
276
- class={`px-3 py-1 font-medium flex-1 rounded-sm transition-colors ${
290
+ class={`px-3 py-1 font-medium flex-1 rounded-sm border transition-colors ${
277
291
  activeMode === mode
278
- ? 'shadow-xs bg-background text-foreground'
279
- : 'text-muted-foreground hover:text-foreground'
292
+ ? 'shadow-xs border-border-strong bg-raised text-foreground'
293
+ : 'border-transparent text-muted-foreground hover:text-foreground'
280
294
  }`}
281
295
  >
282
- {mode === 'upload' ? 'Upload' : 'URL'}
296
+ {mode === 'upload' ? t.fileModeUpload : t.fileModeUrl}
283
297
  </button>
284
298
  {/each}
285
299
  </div>
@@ -289,7 +303,7 @@
289
303
  <div class="gap-2 flex flex-col">
290
304
  <Label for="url-input">
291
305
  <Link size={16} />
292
- File URL
306
+ {t.fileUrlLabel}
293
307
  </Label>
294
308
  <div class="gap-2 flex">
295
309
  <Input
@@ -310,7 +324,7 @@
310
324
  disabled={isLoading || !urlInput.trim()}
311
325
  onclick={handleUrlChange}
312
326
  >
313
- {isLoading ? 'Fetching...' : 'Fetch'}
327
+ {isLoading ? t.fetchingFile : t.fetchFile}
314
328
  </Button>
315
329
  </div>
316
330
 
@@ -336,9 +350,7 @@
336
350
  {/if}
337
351
 
338
352
  {#if !urlError && !urlSuccess}
339
- <p class="text-xs text-muted-foreground">
340
- URL must be publicly accessible (no login required).
341
- </p>
353
+ <p class="text-xs text-muted-foreground">{t.fileUrlPublic}</p>
342
354
  {/if}
343
355
  </div>
344
356
  {:else}
@@ -353,11 +365,11 @@
353
365
  ondrop={handleDrop}
354
366
  >
355
367
  <FileUp size={16} />
356
- {uploadedFileName || 'Choose File or Drag & Drop'}
368
+ {uploadedFileName || t.chooseFile}
357
369
  </Button>
358
370
  {#if acceptedFormats.length > 0}
359
371
  <p class="text-xs text-muted-foreground">
360
- Accepted formats: {acceptedFormats.join(', ')}
372
+ {formatMessage(t.acceptedFormats, { formats: acceptedFormats.join(', ') })}
361
373
  </p>
362
374
  {/if}
363
375
  </div>
@@ -0,0 +1,128 @@
1
+ <script lang="ts">
2
+ import { Button } from '#lib/components/primitives/button/index.js';
3
+ import type { GeometryPickResult, PickKind } from '#lib/contexts/hostContext.svelte.js';
4
+ import { getLocaleContext } from '#lib/i18n/localeContext.svelte.js';
5
+ import type { ViewerMessages } from '#lib/i18n/messages.js';
6
+
7
+ interface Props {
8
+ inputId: string;
9
+ /** The kinds of object the input takes; one kind names the button after it. */
10
+ kinds?: readonly PickKind[];
11
+ /** 1 picks a single object. */
12
+ atMost?: number;
13
+ /** What the host reports the param holds, such as "2 curves"; absent until something is picked. */
14
+ summary?: string;
15
+ /** Absent where this page may not pick, such as a browser tab beside the Rhino panel. */
16
+ onPick?: () => Promise<GeometryPickResult>;
17
+ onClear?: () => void;
18
+ /** Subscribes to what the host holds after any page picks or clears; returns an unsubscribe. */
19
+ watchSummary?: (listener: (summary: string | null) => void) => () => void;
20
+ /** Shown when the pick fails; the host's own message. */
21
+ onError?: (message: string) => void;
22
+ disabled?: boolean;
23
+ }
24
+
25
+ let {
26
+ inputId,
27
+ kinds,
28
+ atMost,
29
+ summary,
30
+ onPick,
31
+ onClear,
32
+ watchSummary,
33
+ onError,
34
+ disabled = false
35
+ }: Props = $props();
36
+
37
+ const locale = getLocaleContext();
38
+ const t = $derived(locale.messages);
39
+
40
+ type PickLabels = readonly [one: keyof ViewerMessages, other: keyof ViewerMessages];
41
+
42
+ const ANY_LABELS: PickLabels = ['geometryPickAnyOne', 'geometryPickAnyOther'];
43
+ const KIND_LABELS: Record<PickKind, PickLabels> = {
44
+ point: ['geometryPickPointOne', 'geometryPickPointOther'],
45
+ pointCloud: ['geometryPickPointCloudOne', 'geometryPickPointCloudOther'],
46
+ curve: ['geometryPickCurveOne', 'geometryPickCurveOther'],
47
+ line: ['geometryPickLineOne', 'geometryPickLineOther'],
48
+ plane: ['geometryPickPlaneOne', 'geometryPickPlaneOther'],
49
+ surface: ['geometryPickSurfaceOne', 'geometryPickSurfaceOther'],
50
+ polysurface: ['geometryPickPolysurfaceOne', 'geometryPickPolysurfaceOther'],
51
+ extrusion: ['geometryPickExtrusionOne', 'geometryPickExtrusionOther'],
52
+ mesh: ['geometryPickMeshOne', 'geometryPickMeshOther'],
53
+ subd: ['geometryPickSubdOne', 'geometryPickSubdOther'],
54
+ block: ['geometryPickBlockOne', 'geometryPickBlockOther']
55
+ };
56
+
57
+ const pickLabel = $derived.by(() => {
58
+ const [one, other] = kinds?.length === 1 ? KIND_LABELS[kinds[0]] : ANY_LABELS;
59
+ return t[atMost === 1 ? one : other];
60
+ });
61
+
62
+ // Rhino's command line prompts during the pick; the page keeps the button from being pressed
63
+ // twice and says where to look.
64
+ let picking = $state(false);
65
+ // The last pick or clear wins over what the host reported when the page loaded.
66
+ let local = $state<string | null | undefined>(undefined);
67
+ const shown = $derived(local !== undefined ? local : (summary ?? null));
68
+
69
+ $effect(() =>
70
+ watchSummary?.((held) => {
71
+ local = held;
72
+ })
73
+ );
74
+
75
+ async function pick(onPick: () => Promise<GeometryPickResult>) {
76
+ picking = true;
77
+ try {
78
+ const result = await onPick();
79
+ if (result.outcome === 'picked') local = result.summary;
80
+ else if (result.outcome === 'failed') onError?.(result.error);
81
+ } finally {
82
+ picking = false;
83
+ }
84
+ }
85
+
86
+ // The field label points at inputId: the button where there is one, else an <output>, which
87
+ // unlike a <span> can be labelled.
88
+ const readout = $derived(onPick ? 'span' : 'output');
89
+
90
+ function clear(onClear: () => void) {
91
+ local = null;
92
+ onClear();
93
+ }
94
+ </script>
95
+
96
+ <div class="gap-2 min-w-0 flex flex-wrap items-center">
97
+ {#if onPick}
98
+ <Button
99
+ id={inputId}
100
+ variant="outline"
101
+ size="sm"
102
+ onclick={() => pick(onPick)}
103
+ disabled={disabled || picking}
104
+ >
105
+ {shown ? t.geometryPickAgain : pickLabel}
106
+ </Button>
107
+ {/if}
108
+ {#if picking}
109
+ <span class="text-sm min-w-0 text-muted-foreground" role="status">{t.geometryPicking}</span>
110
+ {:else if shown}
111
+ <svelte:element
112
+ this={readout}
113
+ id={onPick ? undefined : inputId}
114
+ class="text-sm min-w-0 truncate text-foreground"
115
+ >
116
+ {shown}
117
+ </svelte:element>
118
+ {#if onClear}
119
+ <Button variant="ghost" size="sm" onclick={() => clear(onClear)} {disabled}>
120
+ {t.geometryClear}
121
+ </Button>
122
+ {/if}
123
+ {:else if !onPick}
124
+ <output id={inputId} class="text-sm min-w-0 text-muted-foreground"
125
+ >{t.geometryPickInPanel}</output
126
+ >
127
+ {/if}
128
+ </div>
@@ -1,21 +1,35 @@
1
1
  <script lang="ts">
2
2
  import type { NumberWidgetConfig, SupportedTypes } from '@selvajs/schemas';
3
- import { debounce } from '$lib/utils/debounce';
4
- import { Input } from '$lib/components/primitives/input';
5
- import { Slider } from '$lib/components/primitives/slider';
6
- import * as InputGroup from '$lib/components/primitives/input-group';
7
- import { Separator } from '$lib/components/primitives/separator';
8
- import * as Field from '$lib/components/primitives/field';
3
+ import { debounce } from '#lib/utils/debounce.js';
4
+ import { Input } from '#lib/components/primitives/input/index.js';
5
+ import { Slider } from '#lib/components/primitives/slider/index.js';
6
+ import * as InputGroup from '#lib/components/primitives/input-group/index.js';
7
+ import { Separator } from '#lib/components/primitives/separator/index.js';
8
+ import * as Field from '#lib/components/primitives/field/index.js';
9
+ import { getLocaleContext } from '#lib/i18n/localeContext.svelte.js';
10
+ import { formatMessage } from '#lib/i18n/messages.js';
9
11
 
10
12
  interface Props {
11
13
  inputId: string;
14
+ /** Id of the field's label, which also names the slider thumb. */
15
+ labelId?: string;
12
16
  value?: number;
13
17
  config?: NumberWidgetConfig;
14
18
  onChange: (value: SupportedTypes) => void;
15
19
  disabled?: boolean;
16
20
  }
17
21
 
18
- let { inputId, value = $bindable(), config, onChange, disabled = false }: Props = $props();
22
+ let {
23
+ inputId,
24
+ labelId,
25
+ value = $bindable(),
26
+ config,
27
+ onChange,
28
+ disabled = false
29
+ }: Props = $props();
30
+
31
+ const locale = getLocaleContext();
32
+ const t = $derived(locale.messages);
19
33
 
20
34
  let validationError = $state<string | null>(null);
21
35
 
@@ -29,9 +43,9 @@
29
43
 
30
44
  function validateNumber(val: number): string | null {
31
45
  if (config?.minimum !== undefined && val < config.minimum)
32
- return `Minimum value is ${config.minimum}`;
46
+ return formatMessage(t.minValue, { min: config.minimum });
33
47
  if (config?.maximum !== undefined && val > config.maximum)
34
- return `Maximum value is ${config.maximum}`;
48
+ return formatMessage(t.maxValue, { max: config.maximum });
35
49
  return null;
36
50
  }
37
51
 
@@ -44,11 +58,69 @@
44
58
  }
45
59
  }
46
60
 
47
- function handleSliderChange(newValue: number) {
61
+ // bits-ui builds an array of every step and rebuilds a tick object per step on each value
62
+ // change, so a fine-grained range freezes the drag. Past this many steps the track drives a
63
+ // coarse proxy scale instead; the number box next to it still sets any exact value.
64
+ const MAX_TRACK_STEPS = 1000;
65
+ const FINE_DRAG_FACTOR = 10;
66
+ const stepCount = $derived((max - min) / step);
67
+ const useProxyTrack = $derived(
68
+ step > 0 && Number.isFinite(stepCount) && stepCount > MAX_TRACK_STEPS
69
+ );
70
+ const trackMin = $derived(useProxyTrack ? 0 : min);
71
+ const trackMax = $derived(useProxyTrack ? MAX_TRACK_STEPS : max);
72
+ const stepPrecision = $derived((String(step).split('.')[1] ?? '').length);
73
+
74
+ let dragging = false;
75
+ let shiftHeld = false;
76
+ let lastTrack: number | null = null;
77
+ let fineAnchor: { track: number; value: number } | null = null;
78
+
79
+ function snapToStep(v: number): number {
80
+ const clamped = Math.min(max, Math.max(min, v));
81
+ if (!(step > 0)) return clamped;
82
+ const snapped = min + Math.round((clamped - min) / step) * step;
83
+ return Math.min(max, Number(snapped.toFixed(stepPrecision)));
84
+ }
85
+
86
+ function toTrack(v: number): number {
87
+ return useProxyTrack ? Math.round(((v - min) / (max - min)) * MAX_TRACK_STEPS) : v;
88
+ }
89
+
90
+ function trackSpanToValue(trackDelta: number): number {
91
+ return (trackDelta / (trackMax - trackMin)) * (max - min);
92
+ }
93
+
94
+ // bits-ui maps the pointer to an absolute track position, so fine drag works in deltas: the
95
+ // value moves FINE_DRAG_FACTOR times slower than the pointer from where Shift took effect.
96
+ function setFromTrack(track: number) {
97
+ if (max <= min) return;
98
+ let newValue: number;
99
+ if (dragging && shiftHeld) {
100
+ fineAnchor ??= { track: lastTrack ?? track, value: numValue };
101
+ newValue = snapToStep(
102
+ fineAnchor.value + trackSpanToValue(track - fineAnchor.track) / FINE_DRAG_FACTOR
103
+ );
104
+ } else {
105
+ fineAnchor = null;
106
+ newValue = snapToStep(min + trackSpanToValue(track - trackMin));
107
+ }
108
+ lastTrack = track;
48
109
  value = newValue;
49
110
  commitSliderDebounced(newValue);
50
111
  }
51
112
 
113
+ function startDrag(e: PointerEvent) {
114
+ dragging = true;
115
+ shiftHeld = e.shiftKey;
116
+ lastTrack = null;
117
+ fineAnchor = null;
118
+ }
119
+
120
+ function setShift(e: KeyboardEvent) {
121
+ if (e.key === 'Shift') shiftHeld = e.type === 'keydown';
122
+ }
123
+
52
124
  const decimals = $derived(Math.max(0, -Math.floor(Math.log10(step))));
53
125
 
54
126
  function commitTypedValue(input: HTMLInputElement) {
@@ -65,6 +137,13 @@
65
137
  }
66
138
  </script>
67
139
 
140
+ <svelte:window
141
+ onkeydown={setShift}
142
+ onkeyup={setShift}
143
+ onpointerup={() => (dragging = false)}
144
+ onblur={() => (shiftHeld = dragging = false)}
145
+ />
146
+
68
147
  {#if config?.renderAsSlider}
69
148
  <!-- Value and track share one box, the height of a number input, so mixed rows line up and
70
149
  a value can't be read as belonging to the neighbouring slider. -->
@@ -84,14 +163,17 @@
84
163
  }}
85
164
  />
86
165
  <Separator orientation="vertical" />
87
- <div class="px-3.5 min-w-0 flex flex-1 items-center">
166
+ <div class="px-3.5 min-w-0 flex flex-1 items-center" onpointerdowncapture={startDrag}>
167
+ <!-- A function binding, not value + onValueChange: when a fine drag leaves the value
168
+ unchanged, the thumb must stay put instead of following the pointer. -->
88
169
  <Slider
89
170
  type="single"
90
- value={numValue}
91
- {min}
92
- {max}
93
- {step}
94
- onValueChange={handleSliderChange}
171
+ bind:value={() => toTrack(numValue), setFromTrack}
172
+ min={trackMin}
173
+ max={trackMax}
174
+ step={useProxyTrack ? 1 : step}
175
+ title={t.fineDragHint}
176
+ thumbLabelledby={labelId}
95
177
  {disabled}
96
178
  />
97
179
  </div>