@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,12 +1,19 @@
1
1
  <script lang="ts">
2
- import { Input } from '../../primitives/input';
3
- import { Button } from '../../primitives/button';
4
- import { Label } from '../../primitives/label';
5
- import { toast } from '../../primitives/sonner';
2
+ import { Input } from '../../primitives/input/index.js';
3
+ import { Button } from '../../primitives/button/index.js';
4
+ import { Label } from '../../primitives/label/index.js';
5
+ import { toast } from '../../primitives/sonner/index.js';
6
6
  import { FileUp, Link, CircleAlert, CircleCheck } from '@lucide/svelte';
7
- import { APP_DEFAULTS } from '../../../constants';
8
- import { getLocaleContext } from '../../../i18n/localeContext.svelte';
9
- import { formatMessage } from '../../../i18n/messages';
7
+ import { APP_DEFAULTS } from '../../../constants.js';
8
+ import { getLocaleContext } from '../../../i18n/localeContext.svelte.js';
9
+ import { formatMessage } from '../../../i18n/messages.js';
10
+ import { pickFile } from '../../../utils/host-files.js';
11
+ import {
12
+ acceptsFileEnding,
13
+ isWebp,
14
+ shouldConvertWebp,
15
+ webpToPng
16
+ } from '../../../utils/webp-to-png.js';
10
17
 
11
18
  interface Props {
12
19
  value?: string;
@@ -40,7 +47,6 @@
40
47
  activeMode = effectiveModes.includes(preferred) ? preferred : effectiveModes[0];
41
48
  });
42
49
 
43
- let fileInput: HTMLInputElement | null = $state(null);
44
50
  let uploadedFileName = $state('');
45
51
  let urlInput = $state('');
46
52
  let isDragging = $state(false);
@@ -94,9 +100,27 @@
94
100
  return ext.toLowerCase();
95
101
  }
96
102
 
97
- function isValidFileExtension(fileEnding: string): boolean {
98
- if (acceptedFormats.length === 0) return true;
99
- 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
+ });
100
124
  }
101
125
 
102
126
  function isCorsError(error: unknown): boolean {
@@ -138,14 +162,16 @@
138
162
  return;
139
163
  }
140
164
 
141
- const blob = await response.blob();
142
- const fileEnding = getFileExtension(urlInput);
165
+ const fetched = await response.blob();
166
+ const requestedEnding = (await isWebp(fetched)) ? '.webp' : getFileExtension(urlInput);
143
167
 
144
- if (!isValidFileExtension(fileEnding)) {
145
- urlError = { message: formatNotAccepted(fileEnding), isCors: false };
168
+ if (!acceptsFileEnding(requestedEnding, acceptedFormats)) {
169
+ urlError = { message: formatNotAccepted(requestedEnding), isCors: false };
146
170
  return;
147
171
  }
148
172
 
173
+ const { blob, fileEnding } = await prepare(fetched, requestedEnding);
174
+
149
175
  const maxSize = APP_DEFAULTS.FILE_UPLOAD.MAX_SIZE_BYTES;
150
176
  if (blob.size > maxSize) {
151
177
  urlError = {
@@ -155,15 +181,7 @@
155
181
  return;
156
182
  }
157
183
 
158
- const base64Data = await new Promise<string>((resolve, reject) => {
159
- const reader = new FileReader();
160
- reader.onload = (e) => {
161
- const result = e.target?.result as string;
162
- resolve(result.includes(',') ? result.split(',')[1] : result);
163
- };
164
- reader.onerror = () => reject(new Error(t.fileReadFailed));
165
- reader.readAsDataURL(blob);
166
- });
184
+ const base64Data = await readBase64(blob);
167
185
  const data = JSON.stringify({ file: base64Data, type: 'base64', fileEnding });
168
186
  value = data;
169
187
  urlSuccess = formatMessage(t.fileLoaded, {
@@ -206,48 +224,39 @@
206
224
  }
207
225
  }
208
226
 
209
- function handleFileUpload(event: Event) {
210
- const target = event.target as HTMLInputElement;
211
- const file = target.files?.[0];
212
- if (!file) return;
213
-
214
- const fileEnding = getFileExtension(file.name);
215
-
216
- if (!isValidFileExtension(fileEnding)) {
217
- toast.error(formatNotAccepted(fileEnding));
218
- return;
219
- }
227
+ async function handleFile(file: File) {
228
+ const requestedEnding = getFileExtension(file.name);
220
229
 
221
- // The file is base64-embedded into the compute request body, so without a
222
- // client-side cap an oversize file comes back as an opaque 413. This cap is
223
- // currently LOOSER than the server's COMPUTE_REQUEST_MAX_BYTES (see the TEMP
224
- // note in constants.ts) — some files pass here and still 413. The URL import
225
- // path applies the same cap.
226
- if (file.size > APP_DEFAULTS.FILE_UPLOAD.MAX_SIZE_BYTES) {
227
- toast.error(formatTooLarge(file.size));
230
+ if (!acceptsFileEnding(requestedEnding, acceptedFormats)) {
231
+ toast.error(formatNotAccepted(requestedEnding));
228
232
  return;
229
233
  }
230
234
 
231
- uploadedFileName = file.name;
232
-
233
- const reader = new FileReader();
234
- reader.onload = (e) => {
235
- const base64 = e.target?.result as string;
236
- 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
+ }
237
247
 
238
- const data = JSON.stringify({
239
- file: base64Data,
240
- type: 'base64',
241
- fileEnding
242
- });
248
+ uploadedFileName = file.name;
249
+ const data = JSON.stringify({ file: await readBase64(blob), type: 'base64', fileEnding });
243
250
  value = data;
244
251
  onChange(data);
245
- };
246
- reader.readAsDataURL(file);
252
+ } catch (error) {
253
+ toast.error(error instanceof Error ? error.message : t.fileReadFailed);
254
+ }
247
255
  }
248
256
 
249
- function openFilePicker() {
250
- fileInput?.click();
257
+ async function openFilePicker() {
258
+ const file = await pickFile({ accept: acceptAttribute });
259
+ if (file) handleFile(file);
251
260
  }
252
261
 
253
262
  function handleDragOver(e: DragEvent) {
@@ -263,27 +272,14 @@
263
272
  e.preventDefault();
264
273
  isDragging = false;
265
274
 
266
- const files = e.dataTransfer?.files;
267
- if (files?.[0]) {
268
- const fakeEvent = new Event('change');
269
- const fakeTarget = { files: files } as HTMLInputElement;
270
- Object.defineProperty(fakeEvent, 'target', { value: fakeTarget });
271
- handleFileUpload(fakeEvent);
272
- }
275
+ const file = e.dataTransfer?.files?.[0];
276
+ if (file) handleFile(file);
273
277
  }
274
278
 
275
279
  const acceptAttribute = $derived(acceptedFormats.length > 0 ? acceptedFormats.join(',') : '*');
276
280
  </script>
277
281
 
278
282
  <div class="gap-2.5 flex w-full flex-col">
279
- <input
280
- bind:this={fileInput}
281
- type="file"
282
- accept={acceptAttribute}
283
- onchange={handleFileUpload}
284
- class="hidden"
285
- />
286
-
287
283
  {#if showToggle}
288
284
  <div class="p-0.5 text-xs flex rounded-md bg-muted">
289
285
  {#each effectiveModes as mode (mode)}
@@ -291,10 +287,10 @@
291
287
  type="button"
292
288
  onclick={() => (activeMode = mode)}
293
289
  aria-pressed={activeMode === mode}
294
- 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 ${
295
291
  activeMode === mode
296
- ? 'shadow-xs bg-background text-foreground'
297
- : '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'
298
294
  }`}
299
295
  >
300
296
  {mode === 'upload' ? t.fileModeUpload : t.fileModeUrl}
@@ -0,0 +1,128 @@
1
+ <script lang="ts">
2
+ import { Button } from '../../primitives/button/index.js';
3
+ import type { GeometryPickResult, PickKind } from '../../../contexts/hostContext.svelte.js';
4
+ import { getLocaleContext } from '../../../i18n/localeContext.svelte.js';
5
+ import type { ViewerMessages } from '../../../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>
@@ -0,0 +1,21 @@
1
+ import type { GeometryPickResult, PickKind } from '../../../contexts/hostContext.svelte.js';
2
+ interface Props {
3
+ inputId: string;
4
+ /** The kinds of object the input takes; one kind names the button after it. */
5
+ kinds?: readonly PickKind[];
6
+ /** 1 picks a single object. */
7
+ atMost?: number;
8
+ /** What the host reports the param holds, such as "2 curves"; absent until something is picked. */
9
+ summary?: string;
10
+ /** Absent where this page may not pick, such as a browser tab beside the Rhino panel. */
11
+ onPick?: () => Promise<GeometryPickResult>;
12
+ onClear?: () => void;
13
+ /** Subscribes to what the host holds after any page picks or clears; returns an unsubscribe. */
14
+ watchSummary?: (listener: (summary: string | null) => void) => () => void;
15
+ /** Shown when the pick fails; the host's own message. */
16
+ onError?: (message: string) => void;
17
+ disabled?: boolean;
18
+ }
19
+ declare const GeometryInput: import("svelte").Component<Props, {}, "">;
20
+ type GeometryInput = ReturnType<typeof GeometryInput>;
21
+ export default GeometryInput;
@@ -1,13 +1,13 @@
1
1
  <script lang="ts">
2
2
  import type { NumberWidgetConfig, SupportedTypes } from '@selvajs/schemas';
3
- import { debounce } from '../../../utils/debounce';
4
- import { Input } from '../../primitives/input';
5
- import { Slider } from '../../primitives/slider';
6
- import * as InputGroup from '../../primitives/input-group';
7
- import { Separator } from '../../primitives/separator';
8
- import * as Field from '../../primitives/field';
9
- import { getLocaleContext } from '../../../i18n/localeContext.svelte';
10
- import { formatMessage } from '../../../i18n/messages';
3
+ import { debounce } from '../../../utils/debounce.js';
4
+ import { Input } from '../../primitives/input/index.js';
5
+ import { Slider } from '../../primitives/slider/index.js';
6
+ import * as InputGroup from '../../primitives/input-group/index.js';
7
+ import { Separator } from '../../primitives/separator/index.js';
8
+ import * as Field from '../../primitives/field/index.js';
9
+ import { getLocaleContext } from '../../../i18n/localeContext.svelte.js';
10
+ import { formatMessage } from '../../../i18n/messages.js';
11
11
 
12
12
  interface Props {
13
13
  inputId: string;
@@ -0,0 +1,29 @@
1
+ <script lang="ts">
2
+ import type { GlyphSource } from '../../../schema/option-display.js';
3
+ import { cn } from '../../../utils.js';
4
+
5
+ interface Props {
6
+ glyph: GlyphSource;
7
+ class?: string;
8
+ }
9
+
10
+ let { glyph, class: className }: Props = $props();
11
+ </script>
12
+
13
+ {#if glyph.kind === 'icon'}
14
+ <!-- Lucide's stroke defaults; the path is bound as an attribute, never parsed as markup. -->
15
+ <svg
16
+ viewBox="0 0 24 24"
17
+ fill="none"
18
+ stroke="currentColor"
19
+ stroke-width="2"
20
+ stroke-linecap="round"
21
+ stroke-linejoin="round"
22
+ aria-hidden="true"
23
+ class={cn('size-4 shrink-0', className)}
24
+ >
25
+ <path d={glyph.d} />
26
+ </svg>
27
+ {:else}
28
+ <img src={glyph.src} alt="" class={cn('size-4 shrink-0 rounded-sm object-cover', className)} />
29
+ {/if}
@@ -0,0 +1,8 @@
1
+ import type { GlyphSource } from '../../../schema/option-display.js';
2
+ interface Props {
3
+ glyph: GlyphSource;
4
+ class?: string;
5
+ }
6
+ declare const OptionGlyph: import("svelte").Component<Props, {}, "">;
7
+ type OptionGlyph = ReturnType<typeof OptionGlyph>;
8
+ export default OptionGlyph;
@@ -0,0 +1,45 @@
1
+ <script lang="ts">
2
+ import type { DropdownWidgetConfig, SupportedTypes, UISchema } from '@selvajs/schemas';
3
+ import * as ToggleGroup from '../../primitives/toggle-group/index.js';
4
+ import { optionGlyph } from '../../../schema/option-display.js';
5
+ import OptionGlyph from './OptionGlyph.svelte';
6
+
7
+ interface Props {
8
+ labelId: string;
9
+ value?: string;
10
+ config: DropdownWidgetConfig;
11
+ assets?: UISchema['assets'];
12
+ onChange: (value: SupportedTypes) => void;
13
+ disabled?: boolean;
14
+ }
15
+
16
+ let { labelId, value, config, assets, onChange, disabled = false }: Props = $props();
17
+
18
+ const options = $derived(Object.entries(config.options ?? {}));
19
+ const hasIcons = $derived(
20
+ options.some(([name]) => optionGlyph(config.optionMedia?.[name], assets))
21
+ );
22
+ </script>
23
+
24
+ <ToggleGroup.Root
25
+ type="single"
26
+ variant="outline"
27
+ spacing={0}
28
+ size={hasIcons ? 'stacked' : 'default'}
29
+ class="w-full"
30
+ aria-labelledby={labelId}
31
+ value={value ?? ''}
32
+ onValueChange={(next) => {
33
+ // A single-select group emits '' when the active item is clicked again; keep the selection.
34
+ if (next) onChange(next);
35
+ }}
36
+ {disabled}
37
+ >
38
+ {#each options as [name, expr] (name)}
39
+ {@const glyph = optionGlyph(config.optionMedia?.[name], assets)}
40
+ <ToggleGroup.Item value={expr ?? name} class="min-w-0 flex-1" title={name}>
41
+ {#if glyph}<OptionGlyph {glyph} />{/if}
42
+ <span class="max-w-full truncate">{name}</span>
43
+ </ToggleGroup.Item>
44
+ {/each}
45
+ </ToggleGroup.Root>
@@ -0,0 +1,12 @@
1
+ import type { DropdownWidgetConfig, SupportedTypes, UISchema } from '@selvajs/schemas';
2
+ interface Props {
3
+ labelId: string;
4
+ value?: string;
5
+ config: DropdownWidgetConfig;
6
+ assets?: UISchema['assets'];
7
+ onChange: (value: SupportedTypes) => void;
8
+ disabled?: boolean;
9
+ }
10
+ declare const SegmentedInput: import("svelte").Component<Props, {}, "">;
11
+ type SegmentedInput = ReturnType<typeof SegmentedInput>;
12
+ export default SegmentedInput;
@@ -1,10 +1,10 @@
1
1
  <script lang="ts">
2
2
  import type { TextWidgetConfig, SupportedTypes } from '@selvajs/schemas';
3
- import { debounce } from '../../../utils/debounce';
4
- import { Input } from '../../primitives/input';
5
- import * as Field from '../../primitives/field';
6
- import { getLocaleContext } from '../../../i18n/localeContext.svelte';
7
- import { formatMessage } from '../../../i18n/messages';
3
+ import { debounce } from '../../../utils/debounce.js';
4
+ import { Input } from '../../primitives/input/index.js';
5
+ import * as Field from '../../primitives/field/index.js';
6
+ import { getLocaleContext } from '../../../i18n/localeContext.svelte.js';
7
+ import { formatMessage } from '../../../i18n/messages.js';
8
8
 
9
9
  interface Props {
10
10
  inputId: string;
@@ -0,0 +1,83 @@
1
+ <script lang="ts">
2
+ import type { DropdownWidgetConfig, SupportedTypes, UISchema } from '@selvajs/schemas';
3
+ import { optionGlyph } from '../../../schema/option-display.js';
4
+ import OptionGlyph from './OptionGlyph.svelte';
5
+
6
+ interface Props {
7
+ labelId: string;
8
+ value?: string;
9
+ config: DropdownWidgetConfig;
10
+ assets?: UISchema['assets'];
11
+ onChange: (value: SupportedTypes) => void;
12
+ disabled?: boolean;
13
+ }
14
+
15
+ let { labelId, value, config, assets, onChange, disabled = false }: Props = $props();
16
+
17
+ const options = $derived(
18
+ Object.entries(config.options ?? {}).map(([name, expr]) => ({ name, value: expr ?? name }))
19
+ );
20
+ // With nothing selected the first tile takes focus, so the group is still reachable by Tab.
21
+ const focusIndex = $derived(
22
+ Math.max(
23
+ 0,
24
+ options.findIndex((o) => o.value === value)
25
+ )
26
+ );
27
+
28
+ let tiles: HTMLButtonElement[] = $state([]);
29
+
30
+ function onKeydown(event: KeyboardEvent, index: number) {
31
+ const step = { ArrowRight: 1, ArrowDown: 1, ArrowLeft: -1, ArrowUp: -1 }[event.key];
32
+ if (!step) return;
33
+ event.preventDefault();
34
+ const next = (index + step + options.length) % options.length;
35
+ onChange(options[next].value);
36
+ tiles[next]?.focus();
37
+ }
38
+ </script>
39
+
40
+ <div role="radiogroup" aria-labelledby={labelId} class="gap-2 tile-grid grid">
41
+ {#each options as option, index (option.name)}
42
+ {@const glyph = optionGlyph(config.optionMedia?.[option.name], assets)}
43
+ {@const selected = option.value === value}
44
+ <button
45
+ bind:this={tiles[index]}
46
+ type="button"
47
+ role="radio"
48
+ title={option.name}
49
+ aria-checked={selected}
50
+ tabindex={index === focusIndex ? 0 : -1}
51
+ {disabled}
52
+ onclick={() => onChange(option.value)}
53
+ onkeydown={(e) => onKeydown(e, index)}
54
+ class="gap-1.5 p-1.5 min-w-0 flex flex-col rounded-lg border text-left transition-colors outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 {selected
55
+ ? 'border-primary bg-primary/10 ring-1 ring-primary'
56
+ : 'border-input hover:bg-hover'}"
57
+ >
58
+ <span
59
+ class="flex aspect-square w-full items-center justify-center overflow-hidden rounded-md bg-muted {selected
60
+ ? 'text-foreground'
61
+ : 'text-muted-foreground'}"
62
+ >
63
+ {#if glyph?.kind === 'image'}
64
+ <OptionGlyph {glyph} class="size-full" />
65
+ {:else if glyph}
66
+ <OptionGlyph {glyph} class="size-7" />
67
+ {:else}
68
+ <span class="text-lg font-medium" aria-hidden="true">{option.name.charAt(0)}</span>
69
+ {/if}
70
+ </span>
71
+ <span class="px-0.5 text-xs leading-tight line-clamp-2" class:font-medium={selected}>
72
+ {option.name}
73
+ </span>
74
+ </button>
75
+ {/each}
76
+ </div>
77
+
78
+ <style>
79
+ /* Two or three tiles a row as the panel narrows; a fixed count shrinks them to unreadable. */
80
+ .tile-grid {
81
+ grid-template-columns: repeat(auto-fill, minmax(5.5rem, 1fr));
82
+ }
83
+ </style>
@@ -0,0 +1,12 @@
1
+ import type { DropdownWidgetConfig, SupportedTypes, UISchema } from '@selvajs/schemas';
2
+ interface Props {
3
+ labelId: string;
4
+ value?: string;
5
+ config: DropdownWidgetConfig;
6
+ assets?: UISchema['assets'];
7
+ onChange: (value: SupportedTypes) => void;
8
+ disabled?: boolean;
9
+ }
10
+ declare const TileInput: import("svelte").Component<Props, {}, "">;
11
+ type TileInput = ReturnType<typeof TileInput>;
12
+ export default TileInput;
@@ -3,5 +3,8 @@ export { default as ChecklistInput } from './ChecklistInput.svelte';
3
3
  export { default as ColorInput } from './ColorInput.svelte';
4
4
  export { default as DropdownInput } from './DropdownInput.svelte';
5
5
  export { default as FileInput } from './FileInput.svelte';
6
+ export { default as GeometryInput } from './GeometryInput.svelte';
6
7
  export { default as NumberInput } from './NumberInput.svelte';
8
+ export { default as SegmentedInput } from './SegmentedInput.svelte';
7
9
  export { default as TextInput } from './TextInput.svelte';
10
+ export { default as TileInput } from './TileInput.svelte';
@@ -3,5 +3,8 @@ export { default as ChecklistInput } from './ChecklistInput.svelte';
3
3
  export { default as ColorInput } from './ColorInput.svelte';
4
4
  export { default as DropdownInput } from './DropdownInput.svelte';
5
5
  export { default as FileInput } from './FileInput.svelte';
6
+ export { default as GeometryInput } from './GeometryInput.svelte';
6
7
  export { default as NumberInput } from './NumberInput.svelte';
8
+ export { default as SegmentedInput } from './SegmentedInput.svelte';
7
9
  export { default as TextInput } from './TextInput.svelte';
10
+ export { default as TileInput } from './TileInput.svelte';
@@ -1,7 +1,7 @@
1
1
  <script lang="ts">
2
2
  import { untrack, type Snippet } from 'svelte';
3
3
  import { Button } from './button';
4
- import { Zap, Check, Loader, Play } from '@lucide/svelte';
4
+ import { Zap, LoaderCircle, Play } from '@lucide/svelte';
5
5
  import { getLocaleContext } from '../../i18n/localeContext.svelte';
6
6
 
7
7
  const locale = getLocaleContext();
@@ -38,7 +38,7 @@
38
38
  });
39
39
  </script>
40
40
 
41
- <div class="bottom-0 mt-3 pb-3 pt-2 backdrop-blur-sm px-2 sticky border-t border-border/50">
41
+ <div class="bottom-0 mt-3 pb-3 pt-2 backdrop-blur-sm px-2 sticky border-t border-border">
42
42
  <span class="sr-only" role="status" aria-live="polite">{announcement}</span>
43
43
  <div class="gap-2 flex">
44
44
  <Button
@@ -52,8 +52,14 @@
52
52
  {!hasPendingChanges && !isSolving ? 'text-muted-foreground' : ''}"
53
53
  >
54
54
  {#if isSolving}
55
- <Loader class="h-4 w-4 animate-spin" />
55
+ <LoaderCircle class="h-4 w-4 animate-spin" />
56
56
  {t.solvingEllipsis}
57
+ <kbd
58
+ class="px-1 rounded-sm border border-current/30 font-sans text-2xs opacity-70"
59
+ title={t.solveMessagesEscHint}
60
+ >
61
+ Esc
62
+ </kbd>
57
63
  {:else if hasNeverSolved}
58
64
  <Play class="h-4 w-4" />
59
65
  {t.pressToCalculate}
@@ -61,7 +67,7 @@
61
67
  <Zap class="h-4 w-4" />
62
68
  {t.calculate}
63
69
  {:else}
64
- <Check class="h-4 w-4" />
70
+ <span class="size-1.5 shrink-0 rounded-full bg-success" aria-hidden="true"></span>
65
71
  {t.upToDate}
66
72
  {/if}
67
73
  </Button>