@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
@@ -0,0 +1,404 @@
1
+ <script lang="ts">
2
+ import { MediaQuery } from 'svelte/reactivity';
3
+ import {
4
+ Camera,
5
+ Grid3x3,
6
+ Layers,
7
+ LocateFixed,
8
+ Maximize,
9
+ Minimize,
10
+ Moon,
11
+ Ellipsis,
12
+ Palette,
13
+ Ruler,
14
+ Settings2,
15
+ Spline,
16
+ Cuboid
17
+ } from '@lucide/svelte';
18
+ import type { CameraProjection, Look, ViewPreset } from '@selvajs/visualization/render';
19
+ import * as DropdownMenu from '#lib/components/primitives/dropdown-menu/index.js';
20
+ import type { ViewerMessages } from '#lib/i18n/messages.js';
21
+
22
+ interface Props {
23
+ t: ViewerMessages;
24
+ /** Fades the dock out while a host drawer covers the viewer. */
25
+ hidden?: boolean;
26
+ showDisplay: boolean;
27
+ showGrid: boolean;
28
+ showSceneManager: boolean;
29
+ showScreenshot: boolean;
30
+ showFullscreen: boolean;
31
+ styleOptions: { look: Look; label: string }[];
32
+ viewPresets: { preset: ViewPreset; label: string }[];
33
+ renderStyle: Look;
34
+ nightOn: boolean;
35
+ edgesVisible: boolean;
36
+ measureActive: boolean;
37
+ gridVisible: boolean;
38
+ sceneManagerOpen: boolean;
39
+ projection: CameraProjection;
40
+ isFullscreen: boolean;
41
+ onSetLook: (look: Look) => void;
42
+ onToggleNight: () => void;
43
+ onToggleEdges: () => void;
44
+ onToggleMeasure: () => void;
45
+ onToggleGrid: () => void;
46
+ onToggleSceneManager: () => void;
47
+ onToggleProjection: () => void;
48
+ onSetView: (preset: ViewPreset) => void;
49
+ onFit: () => void;
50
+ onScreenshot: () => void;
51
+ onToggleFullscreen: () => void;
52
+ }
53
+
54
+ let {
55
+ t,
56
+ hidden = false,
57
+ showDisplay,
58
+ showGrid,
59
+ showSceneManager,
60
+ showScreenshot,
61
+ showFullscreen,
62
+ styleOptions,
63
+ viewPresets,
64
+ renderStyle,
65
+ nightOn,
66
+ edgesVisible,
67
+ measureActive,
68
+ gridVisible,
69
+ sceneManagerOpen,
70
+ projection,
71
+ isFullscreen,
72
+ onSetLook,
73
+ onToggleNight,
74
+ onToggleEdges,
75
+ onToggleMeasure,
76
+ onToggleGrid,
77
+ onToggleSceneManager,
78
+ onToggleProjection,
79
+ onSetView,
80
+ onFit,
81
+ onScreenshot,
82
+ onToggleFullscreen
83
+ }: Props = $props();
84
+
85
+ // A hairline pill at the bottom edge is the iOS home indicator on a phone, and swiping it leaves
86
+ // the app. Touch gets a visible round button instead.
87
+ const touch = new MediaQuery('(pointer: coarse)');
88
+ const reducedMotion = new MediaQuery('(prefers-reduced-motion: reduce)');
89
+
90
+ const COLLAPSED = $derived(touch.current ? { w: 44, h: 44 } : { w: 56, h: 5 });
91
+ const EXPANDED_H = $derived(touch.current ? 54 : 42);
92
+ const PAD = 4;
93
+
94
+ let dockEl: HTMLDivElement | undefined = $state();
95
+ let rowWidth = $state(0);
96
+ let engaged = $state(false);
97
+ // Set by an outside press: the dock was dismissed, so it shrinks faster.
98
+ let dismissed = $state(false);
99
+ let menusOpen = $state(0);
100
+ let focusWithin = $state(false);
101
+ let idleTimer: ReturnType<typeof setTimeout> | undefined;
102
+
103
+ const wantOpen = $derived(engaged || menusOpen > 0 || focusWithin);
104
+
105
+ // Opening leads: the pill grows first and the buttons pop in behind it. Closing mirrors it: the
106
+ // buttons fade first, then the pill shrinks. Without the delay on close the row is clipped mid-fade.
107
+ const EASE = 'cubic-bezier(0.32, 0.72, 0, 1)';
108
+ const containerStyle = $derived.by(() => {
109
+ const bottom = touch.current ? 'max(1rem,env(safe-area-inset-bottom))' : '0.75rem';
110
+ const geometry = wantOpen
111
+ ? `width:${rowWidth + PAD * 2 + 2}px;height:${EXPANDED_H}px;border-radius:12px;padding:${PAD}px`
112
+ : `width:${COLLAPSED.w}px;height:${COLLAPSED.h}px;border-radius:${touch.current ? 22 : 99}px;padding:0px`;
113
+ const size = `bottom:${bottom};${geometry}`;
114
+ if (reducedMotion.current) return size;
115
+ const ms = wantOpen ? 420 : dismissed ? 130 : 170;
116
+ const delay = wantOpen || dismissed ? 0 : 20;
117
+ const props = ['width', 'height', 'border-radius', 'padding'];
118
+ return `${size};transition:${props.map((p) => `${p} ${ms}ms ${EASE} ${delay}ms`).join(',')},opacity 200ms ease`;
119
+ });
120
+ const itemStyle = (index: number) => {
121
+ if (reducedMotion.current) return `opacity:${wantOpen ? 1 : 0}`;
122
+ const delay = 110 + index * 35;
123
+ return wantOpen
124
+ ? `opacity:1;transform:none;transition:opacity 240ms ease ${delay}ms,transform 420ms cubic-bezier(0.34,1.45,0.5,1) ${delay}ms`
125
+ : 'opacity:0;transform:translateY(6px) scale(0.85);transition:opacity 100ms ease,transform 100ms ease';
126
+ };
127
+ // The touch button's settings glyph hands over to the row.
128
+ const glyphStyle = $derived(
129
+ `opacity:${touch.current && !wantOpen ? 1 : 0};${reducedMotion.current ? '' : `transition:opacity ${wantOpen ? 100 : 100}ms ease ${wantOpen || dismissed ? 0 : 60}ms`}`
130
+ );
131
+
132
+ function scheduleCollapse(delay = touch.current ? 3500 : 150) {
133
+ clearTimeout(idleTimer);
134
+ idleTimer = setTimeout(() => (engaged = false), delay);
135
+ }
136
+
137
+ function engage() {
138
+ engaged = true;
139
+ dismissed = false;
140
+ clearTimeout(idleTimer);
141
+ }
142
+
143
+ function handlePointerMove(event: PointerEvent) {
144
+ if (!dockEl || touch.current || event.pointerType === 'touch') return;
145
+ // Orbiting or box-dragging with the button down should never pop the dock open.
146
+ if (event.buttons) return;
147
+ const host = dockEl.offsetParent;
148
+ if (!host || !(event.target instanceof Node) || !host.contains(event.target)) {
149
+ if (engaged) scheduleCollapse();
150
+ return;
151
+ }
152
+ const r = dockEl.getBoundingClientRect();
153
+ const near =
154
+ event.clientY > r.top - 36 && Math.abs(event.clientX - (r.left + r.width / 2)) < 110;
155
+ if (near) engage();
156
+ else if (engaged) scheduleCollapse();
157
+ }
158
+
159
+ function handleWindowPointerDown(event: PointerEvent) {
160
+ if (!engaged || !dockEl || !(event.target instanceof Node)) return;
161
+ if (!dockEl.contains(event.target)) {
162
+ clearTimeout(idleTimer);
163
+ dismissed = true;
164
+ engaged = false;
165
+ }
166
+ }
167
+
168
+ function openFromCollapsed() {
169
+ if (wantOpen) return;
170
+ engage();
171
+ if (touch.current) scheduleCollapse();
172
+ }
173
+
174
+ // Any use of the open dock on touch pushes the idle collapse back.
175
+ function keepAlive() {
176
+ if (touch.current && engaged) scheduleCollapse();
177
+ }
178
+
179
+ function trackMenu(open: boolean) {
180
+ menusOpen += open ? 1 : -1;
181
+ if (!open && !touch.current) scheduleCollapse(250);
182
+ }
183
+
184
+ // Buttons in the row, in order, so the stagger index stays right when some are hidden.
185
+ const buttons = $derived(
186
+ [
187
+ showDisplay && 'look',
188
+ showSceneManager && 'scene',
189
+ 'measure',
190
+ 'fit',
191
+ showFullscreen && 'full',
192
+ 'more'
193
+ ].filter(Boolean) as string[]
194
+ );
195
+ const idx = (id: string) => buttons.indexOf(id);
196
+
197
+ const BTN =
198
+ 'relative grid size-8.5 shrink-0 place-items-center rounded-lg text-foreground outline-none transition-colors hover:bg-hover hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring aria-pressed:text-foreground data-[state=open]:bg-selected data-[state=open]:text-foreground pointer-coarse:size-11';
199
+ const DOT =
200
+ 'aria-pressed:after:absolute aria-pressed:after:bottom-0.5 aria-pressed:after:size-1 aria-pressed:after:rounded-full aria-pressed:after:bg-primary';
201
+ </script>
202
+
203
+ <!-- eslint-disable shadcn/no-inline-styles -- the pill animates between measured pixel sizes and staggered per-item delays; no class can express those -->
204
+
205
+ <svelte:window onpointermove={handlePointerMove} onpointerdown={handleWindowPointerDown} />
206
+
207
+ <!-- Resting state is a pill; it grows into the row. Only the container is clickable while collapsed. -->
208
+ <div
209
+ bind:this={dockEl}
210
+ role="toolbar"
211
+ aria-label={t.toolsMenu}
212
+ tabindex="-1"
213
+ class="backdrop-blur-md absolute left-1/2 z-50 -translate-x-1/2 overflow-hidden transition-colors {hidden
214
+ ? 'pointer-events-none opacity-0'
215
+ : ''} {wantOpen
216
+ ? 'shadow-md border border-border-strong bg-card/75'
217
+ : touch.current
218
+ ? 'shadow-lg cursor-pointer border border-border bg-background/90'
219
+ : nightOn
220
+ ? 'border-white/25 bg-white/35 hover:bg-white/70 cursor-pointer border'
221
+ : 'shadow-sm cursor-pointer border border-border-strong bg-card/90 hover:bg-card'}"
222
+ style={containerStyle}
223
+ onclick={openFromCollapsed}
224
+ onkeydown={(e) => (e.key === 'Escape' ? (engaged = false) : undefined)}
225
+ onfocusin={() => (focusWithin = true)}
226
+ onfocusout={(e) => {
227
+ if (!dockEl?.contains(e.relatedTarget as Node | null)) focusWithin = false;
228
+ }}
229
+ onpointerdown={keepAlive}
230
+ >
231
+ {#if touch.current}
232
+ <span
233
+ class="inset-0 pointer-events-none absolute grid place-items-center text-foreground"
234
+ style={glyphStyle}
235
+ aria-hidden="true"
236
+ >
237
+ <Settings2 class="size-5" />
238
+ </span>
239
+ {/if}
240
+
241
+ <div
242
+ bind:offsetWidth={rowWidth}
243
+ class="gap-0.5 mx-auto flex w-max items-center"
244
+ inert={!wantOpen}
245
+ >
246
+ {#if showDisplay}
247
+ <div style={itemStyle(idx('look'))}>
248
+ <DropdownMenu.Root onOpenChange={trackMenu}>
249
+ <DropdownMenu.Trigger>
250
+ {#snippet child({ props })}
251
+ <button {...props} class={BTN} title={t.display} aria-label={t.display}>
252
+ <Palette class="size-4.5" />
253
+ </button>
254
+ {/snippet}
255
+ </DropdownMenu.Trigger>
256
+ <DropdownMenu.Content
257
+ side="top"
258
+ align="center"
259
+ sideOffset={12}
260
+ variant="glass"
261
+ class="min-w-52 z-10001 w-auto whitespace-nowrap"
262
+ >
263
+ <DropdownMenu.RadioGroup
264
+ value={renderStyle}
265
+ onValueChange={(look) => onSetLook(look as Look)}
266
+ >
267
+ {#each styleOptions as { look, label } (look)}
268
+ <DropdownMenu.RadioItem value={look} closeOnSelect={false}>
269
+ {label}
270
+ </DropdownMenu.RadioItem>
271
+ {/each}
272
+ </DropdownMenu.RadioGroup>
273
+ <DropdownMenu.Separator />
274
+ <DropdownMenu.CheckboxItem
275
+ switch
276
+ checked={edgesVisible}
277
+ onCheckedChange={onToggleEdges}
278
+ >
279
+ <Spline />
280
+ {t.edges}
281
+ <DropdownMenu.Shortcut class="pointer-coarse:hidden">E</DropdownMenu.Shortcut>
282
+ </DropdownMenu.CheckboxItem>
283
+ </DropdownMenu.Content>
284
+ </DropdownMenu.Root>
285
+ </div>
286
+ {/if}
287
+
288
+ {#if showSceneManager}
289
+ <div style={itemStyle(idx('scene'))}>
290
+ <button
291
+ class="{BTN} {DOT}"
292
+ title="{t.sceneManager} (S)"
293
+ aria-label={t.sceneManager}
294
+ aria-pressed={sceneManagerOpen}
295
+ onclick={onToggleSceneManager}
296
+ >
297
+ <Layers class="size-4.5" />
298
+ </button>
299
+ </div>
300
+ {/if}
301
+
302
+ <div style={itemStyle(idx('measure'))}>
303
+ <button
304
+ class="{BTN} {DOT}"
305
+ title="{t.measure} (M)"
306
+ aria-label={t.measure}
307
+ aria-pressed={measureActive}
308
+ onclick={onToggleMeasure}
309
+ >
310
+ <Ruler class="size-4.5" />
311
+ </button>
312
+ </div>
313
+
314
+ <div style={itemStyle(idx('fit'))}>
315
+ <button class={BTN} title="{t.fitToView} (F)" aria-label="{t.fitToView} (F)" onclick={onFit}>
316
+ <LocateFixed class="size-4.5" />
317
+ </button>
318
+ </div>
319
+
320
+ {#if showFullscreen}
321
+ <div style={itemStyle(idx('full'))}>
322
+ <button
323
+ class={BTN}
324
+ title={isFullscreen ? t.exitFullscreen : t.fullscreen}
325
+ aria-label={isFullscreen ? t.exitFullscreen : t.fullscreen}
326
+ onclick={onToggleFullscreen}
327
+ >
328
+ {#if isFullscreen}<Minimize class="size-4.5" />{:else}<Maximize class="size-4.5" />{/if}
329
+ </button>
330
+ </div>
331
+ {/if}
332
+
333
+ <div style={itemStyle(idx('more'))}>
334
+ <DropdownMenu.Root onOpenChange={trackMenu}>
335
+ <DropdownMenu.Trigger>
336
+ {#snippet child({ props })}
337
+ <button {...props} class={BTN} title={t.toolsMenu} aria-label={t.toolsMenu}>
338
+ <Ellipsis class="size-4.5" />
339
+ </button>
340
+ {/snippet}
341
+ </DropdownMenu.Trigger>
342
+ <DropdownMenu.Content
343
+ side="top"
344
+ align="end"
345
+ sideOffset={12}
346
+ variant="glass"
347
+ class="min-w-60 z-10001 w-auto whitespace-nowrap"
348
+ >
349
+ <DropdownMenu.Label>{t.views}</DropdownMenu.Label>
350
+ <div class="gap-1 px-1 pb-1 grid grid-cols-4">
351
+ {#each viewPresets as { preset, label } (preset)}
352
+ <DropdownMenu.Item
353
+ closeOnSelect={false}
354
+ onSelect={() => onSetView(preset)}
355
+ variant="chip"
356
+ >
357
+ {label}
358
+ </DropdownMenu.Item>
359
+ {/each}
360
+ </div>
361
+
362
+ <DropdownMenu.Separator />
363
+
364
+ <DropdownMenu.CheckboxItem
365
+ switch
366
+ checked={projection === 'orthographic'}
367
+ onCheckedChange={onToggleProjection}
368
+ >
369
+ <Cuboid />
370
+ {t.switchToOrthographic}
371
+ <DropdownMenu.Shortcut class="pointer-coarse:hidden">O</DropdownMenu.Shortcut>
372
+ </DropdownMenu.CheckboxItem>
373
+
374
+ {#if showGrid}
375
+ <DropdownMenu.CheckboxItem switch checked={gridVisible} onCheckedChange={onToggleGrid}>
376
+ <Grid3x3 />
377
+ {t.grid}
378
+ <DropdownMenu.Shortcut class="pointer-coarse:hidden">G</DropdownMenu.Shortcut>
379
+ </DropdownMenu.CheckboxItem>
380
+ {/if}
381
+
382
+ {#if showDisplay}
383
+ <DropdownMenu.CheckboxItem switch checked={nightOn} onCheckedChange={onToggleNight}>
384
+ <Moon />
385
+ {t.nightMode}
386
+ <DropdownMenu.Shortcut class="pointer-coarse:hidden">N</DropdownMenu.Shortcut>
387
+ </DropdownMenu.CheckboxItem>
388
+ {/if}
389
+
390
+ {#if showScreenshot}
391
+ <DropdownMenu.Separator />
392
+ {/if}
393
+
394
+ {#if showScreenshot}
395
+ <DropdownMenu.Item onSelect={onScreenshot}>
396
+ <Camera />
397
+ {t.screenshot}
398
+ </DropdownMenu.Item>
399
+ {/if}
400
+ </DropdownMenu.Content>
401
+ </DropdownMenu.Root>
402
+ </div>
403
+ </div>
404
+ </div>
@@ -3,7 +3,7 @@ import {
3
3
  type FooterStore,
4
4
  type FooterItem,
5
5
  FOOTER_CONTEXT_KEY
6
- } from '$lib/contexts/footerContext.svelte';
6
+ } from '#lib/contexts/footerContext.svelte.js';
7
7
 
8
8
  export type FooterItemConfig<P extends Record<string, unknown> = Record<string, unknown>> = Omit<
9
9
  FooterItem<P>,
@@ -0,0 +1,103 @@
1
+ import { untrack } from 'svelte';
2
+ import type { ProgressPayload } from '@selvajs/schemas';
3
+ import type { LiveSolveState } from '@selvajs/solve/client';
4
+
5
+ /** A progress bar's width (0–1) and how long its transition to that width runs. */
6
+ export interface ProgressBar {
7
+ width: number;
8
+ ms: number;
9
+ }
10
+
11
+ /**
12
+ * The live channel's progress reports, smoothed for display. `headline` is the first source's
13
+ * report, held long enough to read; `bars` is the width each source's bar should animate to.
14
+ */
15
+ export function createLiveProgress(getLive: () => LiveSolveState): {
16
+ readonly progress: ProgressPayload[];
17
+ readonly headline: ProgressPayload | undefined;
18
+ readonly bars: Readonly<Record<string, ProgressBar>>;
19
+ } {
20
+ const progress = $derived([...getLive().progress.values()]);
21
+
22
+ // A report only arrives when a step starts, so a bar that just shows `fraction` stands still
23
+ // through the step and then jumps. When the plugin knows how long the step took last time, the
24
+ // bar runs linearly to where the step will end, over that time. Steps are weighted by time, so
25
+ // linear gives one steady speed across the solve; any easing curve visibly speeds up and
26
+ // crawls within each step. A slower run waits at the step's end; a faster one snaps ahead.
27
+ let bars = $state.raw<Record<string, ProgressBar>>({});
28
+ // Any live event re-runs the effect below. Only a source with a new report may re-snap;
29
+ // the rest keep easing where they are.
30
+ // eslint-disable-next-line svelte/prefer-svelte-reactivity -- bookkeeping for the effect below, never rendered
31
+ let seen = new Map<string, unknown>();
32
+ let targets: Record<string, ProgressBar> = {};
33
+ $effect(() => {
34
+ const snapped: Record<string, ProgressBar> = {};
35
+ const eased: Record<string, ProgressBar> = {};
36
+ const current = untrack(() => bars);
37
+ // eslint-disable-next-line svelte/prefer-svelte-reactivity -- bookkeeping for this effect; reactive reads here would re-trigger it
38
+ const nextSeen = new Map<string, unknown>();
39
+ for (const p of progress) {
40
+ if (p.fraction === undefined) continue;
41
+ const key = p.source ?? '';
42
+ nextSeen.set(key, p);
43
+ if (seen.get(key) === p && current[key]) {
44
+ snapped[key] = current[key];
45
+ // This run's cleanup cancelled the frame that would have started the ease.
46
+ if (targets[key] && current[key] !== targets[key]) eased[key] = targets[key];
47
+ continue;
48
+ }
49
+ snapped[key] = { width: p.fraction, ms: 150 };
50
+ if (p.nextFraction !== undefined && p.stepMs !== undefined) {
51
+ eased[key] = { width: p.nextFraction, ms: p.stepMs };
52
+ }
53
+ }
54
+ seen = nextSeen;
55
+ targets = eased;
56
+ bars = snapped;
57
+ if (Object.keys(eased).length === 0) return;
58
+ // Two frames: the snapped width must be painted before the eased one is set, or the
59
+ // browser folds both into one style change and there is nothing to transition from.
60
+ let frame = requestAnimationFrame(() => {
61
+ frame = requestAnimationFrame(() => (bars = { ...snapped, ...eased }));
62
+ });
63
+ return () => cancelAnimationFrame(frame);
64
+ });
65
+
66
+ // Steps a few milliseconds long would swap the headline faster than it can be read. Each one
67
+ // stays up for at least this long; steps that end sooner are skipped in the label but still
68
+ // count in the bar. The label and its step count move together so they never disagree.
69
+ const HEADLINE_MIN_MS = 200;
70
+ let headline = $state.raw<ProgressPayload | undefined>(undefined);
71
+ let headlineAt = 0;
72
+ $effect(() => {
73
+ const next = progress[0];
74
+ const shown = untrack(() => headline);
75
+ if (next?.label === shown?.label && next?.done === shown?.done) {
76
+ headline = next;
77
+ return;
78
+ }
79
+ const show = () => {
80
+ headline = next;
81
+ headlineAt = performance.now();
82
+ };
83
+ const wait = HEADLINE_MIN_MS - (performance.now() - headlineAt);
84
+ if (!shown || wait <= 0) {
85
+ show();
86
+ return;
87
+ }
88
+ const timer = setTimeout(show, wait);
89
+ return () => clearTimeout(timer);
90
+ });
91
+
92
+ return {
93
+ get progress() {
94
+ return progress;
95
+ },
96
+ get headline() {
97
+ return headline;
98
+ },
99
+ get bars() {
100
+ return bars;
101
+ }
102
+ };
103
+ }
@@ -0,0 +1,16 @@
1
+ import type { SolveSettlement } from '@selvajs/solve/client';
2
+ import type { ActionButton } from '../types/actionButton';
3
+
4
+ /**
5
+ * Runs a footer-menu action. A `solvesFirst` action waits for the result for the current
6
+ * inputs and runs only on an applied one; a refused or failed solve has already shown the user
7
+ * why. A host that can't solve (`ensureSolved` undefined) runs it straight away.
8
+ */
9
+ export async function runPanelAction(
10
+ action: ActionButton,
11
+ ensureSolved: (() => Promise<SolveSettlement>) | undefined
12
+ ): Promise<void> {
13
+ if (!action.solvesFirst || !ensureSolved) return action.onclick();
14
+ const settled = await ensureSolved();
15
+ if (settled.status === 'applied') await action.onclick(settled);
16
+ }
@@ -2,76 +2,92 @@ import { untrack } from 'svelte';
2
2
  import { APP_DEFAULTS } from '../constants';
3
3
 
4
4
  /**
5
- * Adaptive solving indicator: measures solve durations and adjusts its
6
- * visibility delay from the running average.
5
+ * When to show that a solve is running. A solve that ends within `SHOW_AFTER_MS` shows nothing:
6
+ * the result updating is feedback enough, and a spinner that flashes for a frame reads as jank.
7
+ * Once shown, the indicator stays `MIN_VISIBLE_MS` for the same reason. `slow` turns on past
8
+ * `SLOW_AFTER_MS`, the case of a Rhino.Compute round trip or a heavy definition.
7
9
  *
8
- * - Average under FAST_THRESHOLD_MS: never shown, so quick solves don't flicker
9
- * - Average over SLOW_THRESHOLD_MS: shown immediately
10
- * - Between the two: shown after a proportional slice of ANIMATION_DELAY
11
- * - First solve: shown immediately, there is no history yet
10
+ * `freezesDuringSolve` is for a host that can't paint while it solves (an embedded WebView on
11
+ * the solver's thread). A delay can't run there, so the indicator shows on every solve, before
12
+ * the solve takes the thread: it is the only place that host can say Esc aborts.
12
13
  */
13
- export function createSolvingIndicator(isSolving: () => boolean): { readonly show: boolean } {
14
- const { FAST_THRESHOLD_MS, SLOW_THRESHOLD_MS, HISTORY_SIZE, ANIMATION_DELAY } =
15
- APP_DEFAULTS.SOLVING_INDICATOR;
14
+ export function createSolvingIndicator(
15
+ isSolving: () => boolean,
16
+ options: { freezesDuringSolve?: boolean } = {}
17
+ ): { readonly show: boolean; readonly slow: boolean } {
18
+ const { SHOW_AFTER_MS, MIN_VISIBLE_MS, SLOW_AFTER_MS } = APP_DEFAULTS.SOLVING_INDICATOR;
19
+
20
+ if (options.freezesDuringSolve) {
21
+ return {
22
+ // Read straight from `isSolving`, not through an effect: an effect would run a tick
23
+ // later, after the solve has already frozen the page.
24
+ get show() {
25
+ return isSolving();
26
+ },
27
+ slow: false
28
+ };
29
+ }
16
30
 
17
31
  let show = $state(false);
18
- let timeout: ReturnType<typeof setTimeout> | null = null;
19
- let solveStartTime: number | null = null;
20
- const solveHistory: number[] = [];
32
+ let slow = $state(false);
33
+ let shownAt = 0;
34
+ let showTimer: ReturnType<typeof setTimeout> | null = null;
35
+ let slowTimer: ReturnType<typeof setTimeout> | null = null;
36
+ let hideTimer: ReturnType<typeof setTimeout> | null = null;
37
+
38
+ function clear(timer: ReturnType<typeof setTimeout> | null) {
39
+ if (timer) clearTimeout(timer);
40
+ return null;
41
+ }
42
+
43
+ function hide() {
44
+ hideTimer = null;
45
+ show = false;
46
+ slow = false;
47
+ }
21
48
 
22
49
  $effect(() => {
23
50
  const solving = isSolving();
24
-
25
51
  untrack(() => {
26
52
  if (solving) {
27
- solveStartTime = performance.now();
28
-
29
- const avg =
30
- solveHistory.length === 0
31
- ? Infinity
32
- : solveHistory.reduce((a, b) => a + b, 0) / solveHistory.length;
33
-
34
- if (solveHistory.length > 0 && avg < FAST_THRESHOLD_MS) return;
35
-
36
- const delay =
37
- avg === Infinity || avg >= SLOW_THRESHOLD_MS
38
- ? 0
39
- : ((avg - FAST_THRESHOLD_MS) / (SLOW_THRESHOLD_MS - FAST_THRESHOLD_MS)) *
40
- ANIMATION_DELAY;
41
-
42
- if (!timeout) {
43
- if (delay === 0) {
53
+ // A solve that starts while the last one's indicator is held keeps it up.
54
+ hideTimer = clear(hideTimer);
55
+ if (!show) {
56
+ showTimer ??= setTimeout(() => {
57
+ showTimer = null;
44
58
  show = true;
45
- } else {
46
- timeout = setTimeout(() => {
47
- show = true;
48
- timeout = null;
49
- }, delay);
50
- }
59
+ shownAt = performance.now();
60
+ }, SHOW_AFTER_MS);
51
61
  }
62
+ slowTimer ??= setTimeout(() => {
63
+ slowTimer = null;
64
+ slow = true;
65
+ }, SLOW_AFTER_MS);
52
66
  } else {
53
- if (solveStartTime !== null) {
54
- solveHistory.push(performance.now() - solveStartTime);
55
- if (solveHistory.length > HISTORY_SIZE) solveHistory.shift();
56
- solveStartTime = null;
57
- }
58
-
59
- if (timeout) {
60
- clearTimeout(timeout);
61
- timeout = null;
62
- }
63
- show = false;
67
+ showTimer = clear(showTimer);
68
+ slowTimer = clear(slowTimer);
69
+ if (!show) return;
70
+ const remaining = MIN_VISIBLE_MS - (performance.now() - shownAt);
71
+ if (remaining > 0) hideTimer ??= setTimeout(hide, remaining);
72
+ else hide();
64
73
  }
65
74
  });
75
+ });
66
76
 
67
- return () => {
68
- if (timeout) clearTimeout(timeout);
69
- };
77
+ // Separate from the effect above: a cleanup there would run on every `isSolving` flip and
78
+ // cancel the timers it just set.
79
+ $effect(() => () => {
80
+ clear(showTimer);
81
+ clear(slowTimer);
82
+ clear(hideTimer);
70
83
  });
71
84
 
72
85
  return {
73
86
  get show() {
74
87
  return show;
88
+ },
89
+ get slow() {
90
+ return slow;
75
91
  }
76
92
  };
77
93
  }
@@ -86,10 +86,13 @@ export function useSolveSession(args: SolveSessionArgs): SolveSession {
86
86
  discard: () => session.discard(),
87
87
  setValue: (id, value, forceSolve) => session.setValue(id, value, forceSolve),
88
88
  solve: () => session.solve(),
89
+ ensureSolved: () => session.ensureSolved(),
90
+ onSettled: (listener) => session.onSettled(listener),
89
91
  loadValues: (incoming) => session.loadValues(incoming),
90
92
  rebuild: (schema, scopeKey) => session.rebuild(schema, scopeKey),
91
93
  report: (result) => session.report(result),
92
94
  reportError: (message) => session.reportError(message),
95
+ reportAborted: () => session.reportAborted(),
93
96
  subscribe: (listener) => session.subscribe(listener),
94
97
  notify: () => session.notify(),
95
98
  dispose: () => session.dispose()