@selvajs/ui 6.6.0-beta.9 → 7.0.1-beta.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 (345) hide show
  1. package/README.md +0 -3
  2. package/dist/components/ErrorScreen.svelte +1 -1
  3. package/dist/components/compute/AppLayout.svelte +230 -85
  4. package/dist/components/compute/AppLayout.svelte.d.ts +14 -2
  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 +1 -1
  10. package/dist/components/compute/SolveMessages.svelte +79 -88
  11. package/dist/components/compute/SolveMessages.svelte.d.ts +5 -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 +9 -6
  17. package/dist/components/layout/PageHeader.svelte +3 -3
  18. package/dist/components/layout/SideNav.svelte +3 -3
  19. package/dist/components/layout/SubNav.svelte +2 -2
  20. package/dist/components/preview/ChartOutput.svelte +141 -50
  21. package/dist/components/preview/Group.svelte +9 -9
  22. package/dist/components/preview/HelpButton.svelte +1 -1
  23. package/dist/components/preview/ImageOutput.svelte +13 -10
  24. package/dist/components/preview/InputControl.svelte +13 -12
  25. package/dist/components/preview/OutputDisplay.svelte +26 -17
  26. package/dist/components/preview/TabBar.svelte +8 -6
  27. package/dist/components/preview/TabBar.svelte.d.ts +2 -0
  28. package/dist/components/preview/TabContent.svelte +8 -8
  29. package/dist/components/preview/TabLayout.svelte +6 -7
  30. package/dist/components/preview/inputs/CheckboxInput.svelte +1 -1
  31. package/dist/components/preview/inputs/ChecklistInput.svelte +5 -5
  32. package/dist/components/preview/inputs/ColorInput.svelte +356 -14
  33. package/dist/components/preview/inputs/ColorInput.svelte.d.ts +1 -0
  34. package/dist/components/preview/inputs/DropdownInput.svelte +3 -3
  35. package/dist/components/preview/inputs/FileInput.svelte +17 -12
  36. package/dist/components/preview/inputs/GeometryInput.svelte +4 -4
  37. package/dist/components/preview/inputs/GeometryInput.svelte.d.ts +1 -1
  38. package/dist/components/preview/inputs/NumberInput.svelte +8 -8
  39. package/dist/components/preview/inputs/OptionGlyph.svelte +1 -1
  40. package/dist/components/preview/inputs/OptionGlyph.svelte.d.ts +1 -1
  41. package/dist/components/preview/inputs/SegmentedInput.svelte +3 -2
  42. package/dist/components/preview/inputs/TextInput.svelte +5 -5
  43. package/dist/components/preview/inputs/TileInput.svelte +8 -5
  44. package/dist/components/primitives/CalculateButton.svelte +10 -4
  45. package/dist/components/primitives/Callout.svelte +8 -6
  46. package/dist/components/primitives/Callout.svelte.d.ts +4 -4
  47. package/dist/components/primitives/DataTable.svelte +1 -1
  48. package/dist/components/primitives/FilterableDropdown.svelte +13 -1
  49. package/dist/components/primitives/FilterableDropdown.svelte.d.ts +5 -0
  50. package/dist/components/primitives/ImageUploadField.svelte +1 -1
  51. package/dist/components/primitives/SolvingIndicator.svelte +242 -41
  52. package/dist/components/primitives/SolvingIndicator.svelte.d.ts +13 -5
  53. package/dist/components/primitives/alert-dialog/alert-dialog-content.svelte +1 -1
  54. package/dist/components/primitives/badge/badge.svelte +2 -2
  55. package/dist/components/primitives/badge/badge.svelte.d.ts +6 -6
  56. package/dist/components/primitives/button/button.svelte +7 -8
  57. package/dist/components/primitives/button/button.svelte.d.ts +19 -19
  58. package/dist/components/primitives/card/card.svelte +1 -1
  59. package/dist/components/primitives/card/card.svelte.d.ts +1 -1
  60. package/dist/components/primitives/checkbox/checkbox.svelte +1 -1
  61. package/dist/components/primitives/command/command-input.svelte +1 -1
  62. package/dist/components/primitives/command/command-item.svelte +1 -1
  63. package/dist/components/primitives/command/command-link-item.svelte +1 -1
  64. package/dist/components/primitives/command/command.svelte +1 -1
  65. package/dist/components/primitives/context-menu/context-menu-checkbox-item.svelte +1 -1
  66. package/dist/components/primitives/context-menu/context-menu-content.svelte +1 -1
  67. package/dist/components/primitives/context-menu/context-menu-item.svelte +1 -1
  68. package/dist/components/primitives/context-menu/context-menu-radio-item.svelte +1 -1
  69. package/dist/components/primitives/context-menu/context-menu-sub-content.svelte +1 -1
  70. package/dist/components/primitives/context-menu/context-menu-sub-trigger.svelte +1 -1
  71. package/dist/components/primitives/dialog/dialog-content.svelte +3 -3
  72. package/dist/components/primitives/drawer/Drawer.svelte +1 -1
  73. package/dist/components/primitives/dropdown-menu/dropdown-menu-checkbox-item.svelte +31 -11
  74. package/dist/components/primitives/dropdown-menu/dropdown-menu-checkbox-item.svelte.d.ts +2 -0
  75. package/dist/components/primitives/dropdown-menu/dropdown-menu-content.svelte +5 -1
  76. package/dist/components/primitives/dropdown-menu/dropdown-menu-content.svelte.d.ts +2 -0
  77. package/dist/components/primitives/dropdown-menu/dropdown-menu-item.svelte +4 -2
  78. package/dist/components/primitives/dropdown-menu/dropdown-menu-item.svelte.d.ts +2 -1
  79. package/dist/components/primitives/dropdown-menu/dropdown-menu-radio-item.svelte +1 -1
  80. package/dist/components/primitives/dropdown-menu/dropdown-menu-shortcut.svelte +1 -1
  81. package/dist/components/primitives/dropdown-menu/dropdown-menu-sub-content.svelte +1 -1
  82. package/dist/components/primitives/dropdown-menu/dropdown-menu-sub-trigger.svelte +1 -1
  83. package/dist/components/primitives/index.d.ts +1 -1
  84. package/dist/components/primitives/index.js +1 -1
  85. package/dist/components/primitives/input/input.svelte +2 -2
  86. package/dist/components/primitives/input-group/input-group-input.svelte.d.ts +1 -1
  87. package/dist/components/primitives/input-group/input-group.svelte +1 -1
  88. package/dist/components/primitives/mode-toggle/mode-toggle.svelte +2 -2
  89. package/dist/components/primitives/popover/popover-content.svelte +1 -1
  90. package/dist/components/primitives/resizable/resizable-handle.svelte +2 -2
  91. package/dist/components/primitives/search/search.svelte +1 -1
  92. package/dist/components/primitives/select/select-content.svelte +1 -1
  93. package/dist/components/primitives/select/select-item.svelte +1 -1
  94. package/dist/components/primitives/select/select-trigger.svelte +1 -1
  95. package/dist/components/primitives/slider/slider.svelte +1 -1
  96. package/dist/components/primitives/switch/switch.svelte +1 -1
  97. package/dist/components/primitives/tabs/tabs-list.svelte +2 -2
  98. package/dist/components/primitives/tabs/tabs-list.svelte.d.ts +6 -6
  99. package/dist/components/primitives/tabs/tabs-trigger.svelte +2 -2
  100. package/dist/components/primitives/tabs/tabs-trigger.svelte.d.ts +6 -6
  101. package/dist/components/primitives/textarea/textarea.svelte +1 -1
  102. package/dist/components/primitives/toggle/toggle.svelte +8 -2
  103. package/dist/components/primitives/toggle/toggle.svelte.d.ts +10 -4
  104. package/dist/components/primitives/toggle-group/toggle-group-item.svelte +1 -1
  105. package/dist/components/primitives/toggle-group/toggle-group.svelte +1 -1
  106. package/dist/components/primitives/tooltip/index.d.ts +6 -0
  107. package/dist/components/primitives/tooltip/index.js +8 -0
  108. package/dist/components/primitives/tooltip/tooltip-content.svelte +34 -0
  109. package/dist/components/primitives/tooltip/tooltip-content.svelte.d.ts +10 -0
  110. package/dist/components/primitives/tooltip/tooltip-portal.svelte +7 -0
  111. package/dist/components/primitives/tooltip/tooltip-portal.svelte.d.ts +4 -0
  112. package/dist/components/primitives/tooltip/tooltip-provider.svelte +7 -0
  113. package/dist/components/primitives/tooltip/tooltip-provider.svelte.d.ts +4 -0
  114. package/dist/components/primitives/tooltip/tooltip-trigger.svelte +7 -0
  115. package/dist/components/primitives/tooltip/tooltip-trigger.svelte.d.ts +4 -0
  116. package/dist/components/primitives/tooltip/tooltip.svelte +7 -0
  117. package/dist/components/primitives/tooltip/tooltip.svelte.d.ts +4 -0
  118. package/dist/components/viewer/MeshMetadataPanel.svelte +2 -2
  119. package/dist/components/viewer/SceneManager.svelte +32 -21
  120. package/dist/components/viewer/SceneManager.svelte.d.ts +3 -1
  121. package/dist/components/viewer/Viewer.svelte +128 -178
  122. package/dist/components/viewer/Viewer.svelte.d.ts +7 -2
  123. package/dist/components/viewer/ViewerDock.svelte +452 -0
  124. package/dist/components/viewer/ViewerDock.svelte.d.ts +42 -0
  125. package/dist/composables/useFooterItem.svelte.d.ts +1 -1
  126. package/dist/composables/useFooterItem.svelte.js +1 -1
  127. package/dist/compute/liveProgress.svelte.d.ts +16 -0
  128. package/dist/compute/liveProgress.svelte.js +90 -0
  129. package/dist/compute/solving.svelte.d.ts +7 -9
  130. package/dist/compute/solving.svelte.js +56 -48
  131. package/dist/constants.d.ts +6 -8
  132. package/dist/constants.js +8 -11
  133. package/dist/contexts/hostContext.svelte.d.ts +1 -1
  134. package/dist/features/chart/plotly-compat.d.ts +15 -0
  135. package/dist/features/chart/plotly-compat.js +124 -0
  136. package/dist/i18n/messages.d.ts +17 -0
  137. package/dist/i18n/messages.js +26 -0
  138. package/dist/index.d.ts +2 -0
  139. package/dist/index.js +2 -0
  140. package/dist/public.d.ts +1 -0
  141. package/dist/public.js +1 -0
  142. package/dist/schema/visibility-rules.d.ts +7 -1
  143. package/dist/schema/visibility-rules.js +17 -0
  144. package/dist/styles/base.css +100 -86
  145. package/dist/utils/color-space.d.ts +21 -0
  146. package/dist/utils/color-space.js +90 -0
  147. package/package.json +31 -23
  148. package/src/lib/components/ErrorScreen.svelte +1 -1
  149. package/src/lib/components/compute/AppLayout.svelte +231 -86
  150. package/src/lib/components/compute/CollapsedPanelStrip.svelte +4 -2
  151. package/src/lib/components/compute/ComputeApp.svelte +6 -2
  152. package/src/lib/components/compute/ComputeMessagesDialog.svelte +4 -4
  153. package/src/lib/components/compute/ParameterPresetManager.svelte +1 -1
  154. package/src/lib/components/compute/SolveMessages.svelte +81 -90
  155. package/src/lib/components/layout/AppShell.svelte +2 -2
  156. package/src/lib/components/layout/FooterItemRenderer.svelte +2 -2
  157. package/src/lib/components/layout/PageContent.svelte +3 -2
  158. package/src/lib/components/layout/PageFooter.svelte +9 -6
  159. package/src/lib/components/layout/PageHeader.svelte +3 -3
  160. package/src/lib/components/layout/SideNav.svelte +4 -4
  161. package/src/lib/components/layout/SubNav.svelte +2 -2
  162. package/src/lib/components/preview/ChartOutput.svelte +141 -50
  163. package/src/lib/components/preview/Group.svelte +9 -9
  164. package/src/lib/components/preview/HelpButton.svelte +1 -1
  165. package/src/lib/components/preview/ImageOutput.svelte +13 -10
  166. package/src/lib/components/preview/InputControl.svelte +13 -12
  167. package/src/lib/components/preview/OutputDisplay.svelte +26 -17
  168. package/src/lib/components/preview/TabBar.svelte +8 -6
  169. package/src/lib/components/preview/TabContent.svelte +8 -8
  170. package/src/lib/components/preview/TabLayout.svelte +6 -7
  171. package/src/lib/components/preview/inputs/CheckboxInput.svelte +1 -1
  172. package/src/lib/components/preview/inputs/ChecklistInput.svelte +5 -5
  173. package/src/lib/components/preview/inputs/ColorInput.svelte +356 -14
  174. package/src/lib/components/preview/inputs/DropdownInput.svelte +3 -3
  175. package/src/lib/components/preview/inputs/FileInput.svelte +17 -12
  176. package/src/lib/components/preview/inputs/GeometryInput.svelte +4 -4
  177. package/src/lib/components/preview/inputs/NumberInput.svelte +8 -8
  178. package/src/lib/components/preview/inputs/OptionGlyph.svelte +2 -2
  179. package/src/lib/components/preview/inputs/SegmentedInput.svelte +3 -2
  180. package/src/lib/components/preview/inputs/TextInput.svelte +5 -5
  181. package/src/lib/components/preview/inputs/TileInput.svelte +8 -5
  182. package/src/lib/components/primitives/CalculateButton.svelte +10 -4
  183. package/src/lib/components/primitives/Callout.svelte +9 -7
  184. package/src/lib/components/primitives/DataTable.svelte +1 -1
  185. package/src/lib/components/primitives/FilterableDropdown.svelte +14 -2
  186. package/src/lib/components/primitives/ImageUploadField.svelte +1 -1
  187. package/src/lib/components/primitives/IssueList.svelte +1 -1
  188. package/src/lib/components/primitives/SeverityIcon.svelte +1 -1
  189. package/src/lib/components/primitives/SolvingIndicator.svelte +242 -41
  190. package/src/lib/components/primitives/StateDisplay.svelte +1 -1
  191. package/src/lib/components/primitives/alert-dialog/alert-dialog-action.svelte +2 -2
  192. package/src/lib/components/primitives/alert-dialog/alert-dialog-cancel.svelte +2 -2
  193. package/src/lib/components/primitives/alert-dialog/alert-dialog-content.svelte +2 -2
  194. package/src/lib/components/primitives/alert-dialog/alert-dialog-description.svelte +1 -1
  195. package/src/lib/components/primitives/alert-dialog/alert-dialog-footer.svelte +1 -1
  196. package/src/lib/components/primitives/alert-dialog/alert-dialog-header.svelte +1 -1
  197. package/src/lib/components/primitives/alert-dialog/alert-dialog-overlay.svelte +1 -1
  198. package/src/lib/components/primitives/alert-dialog/alert-dialog-title.svelte +1 -1
  199. package/src/lib/components/primitives/badge/badge.svelte +3 -3
  200. package/src/lib/components/primitives/button/button.svelte +7 -8
  201. package/src/lib/components/primitives/button-group/button-group-separator.svelte +2 -2
  202. package/src/lib/components/primitives/button-group/button-group-text.svelte +1 -1
  203. package/src/lib/components/primitives/button-group/button-group.svelte +1 -1
  204. package/src/lib/components/primitives/card/card-content.svelte +1 -1
  205. package/src/lib/components/primitives/card/card-description.svelte +1 -1
  206. package/src/lib/components/primitives/card/card-footer.svelte +1 -1
  207. package/src/lib/components/primitives/card/card-header.svelte +1 -1
  208. package/src/lib/components/primitives/card/card-title.svelte +1 -1
  209. package/src/lib/components/primitives/card/card.svelte +2 -2
  210. package/src/lib/components/primitives/checkbox/checkbox.svelte +2 -2
  211. package/src/lib/components/primitives/command/command-dialog.svelte +2 -2
  212. package/src/lib/components/primitives/command/command-empty.svelte +1 -1
  213. package/src/lib/components/primitives/command/command-group.svelte +1 -1
  214. package/src/lib/components/primitives/command/command-input.svelte +3 -3
  215. package/src/lib/components/primitives/command/command-item.svelte +2 -2
  216. package/src/lib/components/primitives/command/command-link-item.svelte +2 -2
  217. package/src/lib/components/primitives/command/command-list.svelte +1 -1
  218. package/src/lib/components/primitives/command/command-separator.svelte +1 -1
  219. package/src/lib/components/primitives/command/command-shortcut.svelte +1 -1
  220. package/src/lib/components/primitives/command/command.svelte +2 -2
  221. package/src/lib/components/primitives/context-menu/context-menu-checkbox-item.svelte +2 -2
  222. package/src/lib/components/primitives/context-menu/context-menu-content.svelte +2 -2
  223. package/src/lib/components/primitives/context-menu/context-menu-group-heading.svelte +1 -1
  224. package/src/lib/components/primitives/context-menu/context-menu-item.svelte +2 -2
  225. package/src/lib/components/primitives/context-menu/context-menu-label.svelte +1 -1
  226. package/src/lib/components/primitives/context-menu/context-menu-radio-item.svelte +2 -2
  227. package/src/lib/components/primitives/context-menu/context-menu-separator.svelte +1 -1
  228. package/src/lib/components/primitives/context-menu/context-menu-shortcut.svelte +1 -1
  229. package/src/lib/components/primitives/context-menu/context-menu-sub-content.svelte +2 -2
  230. package/src/lib/components/primitives/context-menu/context-menu-sub-trigger.svelte +2 -2
  231. package/src/lib/components/primitives/dialog/dialog-content.svelte +4 -4
  232. package/src/lib/components/primitives/dialog/dialog-description.svelte +1 -1
  233. package/src/lib/components/primitives/dialog/dialog-footer.svelte +1 -1
  234. package/src/lib/components/primitives/dialog/dialog-header.svelte +1 -1
  235. package/src/lib/components/primitives/dialog/dialog-overlay.svelte +1 -1
  236. package/src/lib/components/primitives/dialog/dialog-title.svelte +1 -1
  237. package/src/lib/components/primitives/drawer/Drawer.svelte +1 -1
  238. package/src/lib/components/primitives/dropdown-menu/dropdown-menu-checkbox-item.svelte +32 -12
  239. package/src/lib/components/primitives/dropdown-menu/dropdown-menu-content.svelte +6 -2
  240. package/src/lib/components/primitives/dropdown-menu/dropdown-menu-group-heading.svelte +1 -1
  241. package/src/lib/components/primitives/dropdown-menu/dropdown-menu-item.svelte +5 -3
  242. package/src/lib/components/primitives/dropdown-menu/dropdown-menu-label.svelte +1 -1
  243. package/src/lib/components/primitives/dropdown-menu/dropdown-menu-radio-item.svelte +2 -2
  244. package/src/lib/components/primitives/dropdown-menu/dropdown-menu-separator.svelte +1 -1
  245. package/src/lib/components/primitives/dropdown-menu/dropdown-menu-shortcut.svelte +2 -2
  246. package/src/lib/components/primitives/dropdown-menu/dropdown-menu-sub-content.svelte +2 -2
  247. package/src/lib/components/primitives/dropdown-menu/dropdown-menu-sub-trigger.svelte +2 -2
  248. package/src/lib/components/primitives/empty/empty-content.svelte +1 -1
  249. package/src/lib/components/primitives/empty/empty-description.svelte +1 -1
  250. package/src/lib/components/primitives/empty/empty-header.svelte +1 -1
  251. package/src/lib/components/primitives/empty/empty-media.svelte +1 -1
  252. package/src/lib/components/primitives/empty/empty-title.svelte +1 -1
  253. package/src/lib/components/primitives/empty/empty.svelte +1 -1
  254. package/src/lib/components/primitives/field/field-content.svelte +1 -1
  255. package/src/lib/components/primitives/field/field-description.svelte +1 -1
  256. package/src/lib/components/primitives/field/field-error.svelte +1 -1
  257. package/src/lib/components/primitives/field/field-group.svelte +1 -1
  258. package/src/lib/components/primitives/field/field-label.svelte +2 -2
  259. package/src/lib/components/primitives/field/field-legend.svelte +1 -1
  260. package/src/lib/components/primitives/field/field-separator.svelte +2 -2
  261. package/src/lib/components/primitives/field/field-set.svelte +1 -1
  262. package/src/lib/components/primitives/field/field-title.svelte +1 -1
  263. package/src/lib/components/primitives/field/field.svelte +1 -1
  264. package/src/lib/components/primitives/index.ts +1 -1
  265. package/src/lib/components/primitives/input/input.svelte +3 -3
  266. package/src/lib/components/primitives/input-group/input-group-addon.svelte +1 -1
  267. package/src/lib/components/primitives/input-group/input-group-button.svelte +2 -2
  268. package/src/lib/components/primitives/input-group/input-group-input.svelte +2 -2
  269. package/src/lib/components/primitives/input-group/input-group-text.svelte +1 -1
  270. package/src/lib/components/primitives/input-group/input-group-textarea.svelte +2 -2
  271. package/src/lib/components/primitives/input-group/input-group.svelte +2 -2
  272. package/src/lib/components/primitives/label/label.svelte +1 -1
  273. package/src/lib/components/primitives/mode-toggle/mode-toggle.svelte +2 -2
  274. package/src/lib/components/primitives/popover/popover-content.svelte +2 -2
  275. package/src/lib/components/primitives/popover/popover-description.svelte +1 -1
  276. package/src/lib/components/primitives/popover/popover-header.svelte +1 -1
  277. package/src/lib/components/primitives/popover/popover-title.svelte +1 -1
  278. package/src/lib/components/primitives/popover/popover-trigger.svelte +1 -1
  279. package/src/lib/components/primitives/resizable/resizable-handle.svelte +3 -3
  280. package/src/lib/components/primitives/resizable/resizable-pane-group.svelte +1 -1
  281. package/src/lib/components/primitives/scroll-area/scroll-area-scrollbar.svelte +1 -1
  282. package/src/lib/components/primitives/scroll-area/scroll-area.svelte +1 -1
  283. package/src/lib/components/primitives/search/search.svelte +2 -2
  284. package/src/lib/components/primitives/select/select-content.svelte +2 -2
  285. package/src/lib/components/primitives/select/select-group-heading.svelte +1 -1
  286. package/src/lib/components/primitives/select/select-item.svelte +2 -2
  287. package/src/lib/components/primitives/select/select-label.svelte +1 -1
  288. package/src/lib/components/primitives/select/select-scroll-down-button.svelte +1 -1
  289. package/src/lib/components/primitives/select/select-scroll-up-button.svelte +1 -1
  290. package/src/lib/components/primitives/select/select-separator.svelte +2 -2
  291. package/src/lib/components/primitives/select/select-trigger.svelte +2 -2
  292. package/src/lib/components/primitives/separator/separator.svelte +1 -1
  293. package/src/lib/components/primitives/slider/slider.svelte +2 -2
  294. package/src/lib/components/primitives/spinner/spinner.svelte +1 -1
  295. package/src/lib/components/primitives/switch/switch.svelte +2 -2
  296. package/src/lib/components/primitives/tabs/tabs-content.svelte +1 -1
  297. package/src/lib/components/primitives/tabs/tabs-list.svelte +3 -3
  298. package/src/lib/components/primitives/tabs/tabs-trigger.svelte +3 -3
  299. package/src/lib/components/primitives/tabs/tabs.svelte +1 -1
  300. package/src/lib/components/primitives/textarea/textarea.svelte +2 -2
  301. package/src/lib/components/primitives/toggle/toggle.svelte +9 -3
  302. package/src/lib/components/primitives/toggle-group/toggle-group-item.svelte +3 -3
  303. package/src/lib/components/primitives/toggle-group/toggle-group.svelte +3 -3
  304. package/src/lib/components/primitives/tooltip/index.ts +19 -0
  305. package/src/lib/components/primitives/tooltip/tooltip-content.svelte +34 -0
  306. package/src/lib/components/primitives/tooltip/tooltip-portal.svelte +7 -0
  307. package/src/lib/components/primitives/tooltip/tooltip-provider.svelte +7 -0
  308. package/src/lib/components/primitives/tooltip/tooltip-trigger.svelte +7 -0
  309. package/src/lib/components/primitives/tooltip/tooltip.svelte +7 -0
  310. package/src/lib/components/viewer/MeshMetadataPanel.svelte +2 -2
  311. package/src/lib/components/viewer/SceneManager.svelte +32 -21
  312. package/src/lib/components/viewer/Viewer.svelte +129 -179
  313. package/src/lib/components/viewer/ViewerDock.svelte +452 -0
  314. package/src/lib/composables/useFooterItem.svelte.ts +1 -1
  315. package/src/lib/compute/liveProgress.svelte.ts +103 -0
  316. package/src/lib/compute/solving.svelte.ts +56 -55
  317. package/src/lib/constants.ts +8 -11
  318. package/src/lib/contexts/hostContext.svelte.ts +1 -1
  319. package/src/lib/features/chart/plotly-compat.ts +141 -0
  320. package/src/lib/i18n/messages.ts +43 -0
  321. package/src/lib/index.ts +2 -0
  322. package/src/lib/public.ts +1 -0
  323. package/src/lib/schema/visibility-rules.ts +27 -1
  324. package/src/lib/styles/base.css +100 -86
  325. package/src/lib/utils/color-space.ts +107 -0
  326. package/dist/components/primitives/theme-switcher/ThemeSwitcher.svelte +0 -39
  327. package/dist/components/primitives/theme-switcher/ThemeSwitcher.svelte.d.ts +0 -3
  328. package/dist/components/primitives/theme-switcher/index.d.ts +0 -1
  329. package/dist/components/primitives/theme-switcher/index.js +0 -1
  330. package/dist/styles/themes/cyberpunk.css +0 -98
  331. package/dist/styles/themes/neutral.css +0 -72
  332. package/dist/styles/themes/ocean.css +0 -75
  333. package/dist/styles/themes/selva.css +0 -105
  334. package/dist/theme/themeStore.svelte.d.ts +0 -12
  335. package/dist/theme/themeStore.svelte.js +0 -40
  336. package/dist/theme/themes.d.ts +0 -5
  337. package/dist/theme/themes.js +0 -13
  338. package/src/lib/components/primitives/theme-switcher/ThemeSwitcher.svelte +0 -39
  339. package/src/lib/components/primitives/theme-switcher/index.ts +0 -1
  340. package/src/lib/styles/themes/cyberpunk.css +0 -98
  341. package/src/lib/styles/themes/neutral.css +0 -72
  342. package/src/lib/styles/themes/ocean.css +0 -75
  343. package/src/lib/styles/themes/selva.css +0 -105
  344. package/src/lib/theme/themeStore.svelte.ts +0 -50
  345. package/src/lib/theme/themes.ts +0 -18
@@ -0,0 +1,452 @@
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 bottom = $derived(touch.current ? 'max(1rem,env(safe-area-inset-bottom))' : '0.75rem');
109
+ const containerStyle = $derived.by(() => {
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
+ // Touch has no hover to reopen it, so there the dock stays open until a press lands outside it.
133
+ function scheduleCollapse(delay = 150) {
134
+ clearTimeout(idleTimer);
135
+ idleTimer = setTimeout(() => (engaged = false), delay);
136
+ }
137
+
138
+ function engage() {
139
+ engaged = true;
140
+ dismissed = false;
141
+ clearTimeout(idleTimer);
142
+ }
143
+
144
+ function handlePointerMove(event: PointerEvent) {
145
+ if (!dockEl || touch.current || event.pointerType === 'touch') return;
146
+ // Orbiting or box-dragging with the button down should never pop the dock open.
147
+ if (event.buttons) return;
148
+ const host = dockEl.offsetParent;
149
+ if (!host || !(event.target instanceof Node) || !host.contains(event.target)) {
150
+ if (engaged) scheduleCollapse();
151
+ return;
152
+ }
153
+ const r = dockEl.getBoundingClientRect();
154
+ const near =
155
+ event.clientY > r.top - 36 && Math.abs(event.clientX - (r.left + r.width / 2)) < 110;
156
+ if (near) engage();
157
+ else if (engaged) scheduleCollapse();
158
+ }
159
+
160
+ function handleWindowPointerDown(event: PointerEvent) {
161
+ if (!engaged || !dockEl || !(event.target instanceof Node)) return;
162
+ if (!dockEl.contains(event.target)) {
163
+ clearTimeout(idleTimer);
164
+ dismissed = true;
165
+ engaged = false;
166
+ }
167
+ }
168
+
169
+ function openFromCollapsed() {
170
+ if (wantOpen) return;
171
+ engage();
172
+ }
173
+
174
+ // ==========================================================================
175
+ // Long-press labels
176
+ // ==========================================================================
177
+
178
+ // The icons carry no text and touch has no tooltip, so holding a button names it instead of
179
+ // pressing it.
180
+ const LONG_PRESS_MS = 450;
181
+ const HINT_LINGER_MS = 1200;
182
+ let hint = $state<string | null>(null);
183
+ let pressTimer: ReturnType<typeof setTimeout> | undefined;
184
+ let hintTimer: ReturnType<typeof setTimeout> | undefined;
185
+ let swallowClick = false;
186
+
187
+ function startPress(event: PointerEvent) {
188
+ if (event.pointerType !== 'touch' || !wantOpen || !(event.target instanceof Element)) return;
189
+ const button = event.target.closest('button[aria-label]');
190
+ if (!button) return;
191
+ clearTimeout(pressTimer);
192
+ pressTimer = setTimeout(() => {
193
+ // Keyboard shortcuts in the label mean nothing on a phone.
194
+ hint = (button.getAttribute('aria-label') ?? '').replace(/\s*\([A-Z]\)$/, '');
195
+ swallowClick = true;
196
+ }, LONG_PRESS_MS);
197
+ }
198
+
199
+ function endPress() {
200
+ clearTimeout(pressTimer);
201
+ if (!hint) return;
202
+ clearTimeout(hintTimer);
203
+ hintTimer = setTimeout(() => (hint = null), HINT_LINGER_MS);
204
+ }
205
+
206
+ function swallowLongPressClick(event: MouseEvent) {
207
+ if (!swallowClick) return;
208
+ swallowClick = false;
209
+ event.preventDefault();
210
+ event.stopPropagation();
211
+ }
212
+
213
+ function trackMenu(open: boolean) {
214
+ menusOpen += open ? 1 : -1;
215
+ if (!open && !touch.current) scheduleCollapse(250);
216
+ }
217
+
218
+ // Buttons in the row, in order, so the stagger index stays right when some are hidden.
219
+ const buttons = $derived(
220
+ [
221
+ showDisplay && 'look',
222
+ showSceneManager && 'scene',
223
+ 'measure',
224
+ 'fit',
225
+ showFullscreen && 'full',
226
+ 'more'
227
+ ].filter(Boolean) as string[]
228
+ );
229
+ const idx = (id: string) => buttons.indexOf(id);
230
+
231
+ const BTN =
232
+ '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';
233
+ const DOT =
234
+ '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';
235
+ </script>
236
+
237
+ <!-- eslint-disable shadcn/no-inline-styles -- the pill animates between measured pixel sizes and staggered per-item delays; no class can express those -->
238
+
239
+ <svelte:window onpointermove={handlePointerMove} onpointerdown={handleWindowPointerDown} />
240
+
241
+ <!-- Resting state is a pill; it grows into the row. Only the container is clickable while collapsed. -->
242
+ <div
243
+ bind:this={dockEl}
244
+ role="toolbar"
245
+ aria-label={t.toolsMenu}
246
+ tabindex="-1"
247
+ class="backdrop-blur-md absolute left-1/2 z-50 -translate-x-1/2 overflow-hidden transition-colors select-none {hidden
248
+ ? 'pointer-events-none opacity-0'
249
+ : ''} {wantOpen
250
+ ? 'shadow-md border border-border-strong bg-card/75'
251
+ : touch.current
252
+ ? 'shadow-lg cursor-pointer border border-border bg-background/90'
253
+ : nightOn
254
+ ? 'border-white/25 bg-white/35 hover:bg-white/70 cursor-pointer border'
255
+ : 'shadow-sm cursor-pointer border border-border-strong bg-card/90 hover:bg-card'}"
256
+ style="{containerStyle};-webkit-touch-callout:none"
257
+ onclick={openFromCollapsed}
258
+ onkeydown={(e) => (e.key === 'Escape' ? (engaged = false) : undefined)}
259
+ onfocusin={() => (focusWithin = true)}
260
+ onfocusout={(e) => {
261
+ if (!dockEl?.contains(e.relatedTarget as Node | null)) focusWithin = false;
262
+ }}
263
+ onpointerdown={startPress}
264
+ onpointerup={endPress}
265
+ onpointercancel={endPress}
266
+ onclickcapture={swallowLongPressClick}
267
+ oncontextmenu={(e) => touch.current && e.preventDefault()}
268
+ >
269
+ {#if touch.current}
270
+ <span
271
+ class="inset-0 pointer-events-none absolute grid place-items-center text-foreground"
272
+ style={glyphStyle}
273
+ aria-hidden="true"
274
+ >
275
+ <Settings2 class="size-5" />
276
+ </span>
277
+ {/if}
278
+
279
+ <div
280
+ bind:offsetWidth={rowWidth}
281
+ class="gap-0.5 mx-auto flex w-max items-center"
282
+ inert={!wantOpen}
283
+ >
284
+ {#if showDisplay}
285
+ <div style={itemStyle(idx('look'))}>
286
+ <DropdownMenu.Root onOpenChange={trackMenu}>
287
+ <DropdownMenu.Trigger>
288
+ {#snippet child({ props })}
289
+ <button {...props} class={BTN} title={t.display} aria-label={t.display}>
290
+ <Palette class="size-4.5" />
291
+ </button>
292
+ {/snippet}
293
+ </DropdownMenu.Trigger>
294
+ <DropdownMenu.Content
295
+ side="top"
296
+ align="center"
297
+ sideOffset={12}
298
+ variant="glass"
299
+ class="min-w-52 z-10001 w-auto whitespace-nowrap"
300
+ >
301
+ <DropdownMenu.RadioGroup
302
+ value={renderStyle}
303
+ onValueChange={(look) => onSetLook(look as Look)}
304
+ >
305
+ {#each styleOptions as { look, label } (look)}
306
+ <DropdownMenu.RadioItem value={look} closeOnSelect={false}>
307
+ {label}
308
+ </DropdownMenu.RadioItem>
309
+ {/each}
310
+ </DropdownMenu.RadioGroup>
311
+ <DropdownMenu.Separator />
312
+ <DropdownMenu.CheckboxItem
313
+ switch
314
+ checked={edgesVisible}
315
+ onCheckedChange={onToggleEdges}
316
+ >
317
+ <Spline />
318
+ {t.edges}
319
+ <DropdownMenu.Shortcut class="pointer-coarse:hidden">E</DropdownMenu.Shortcut>
320
+ </DropdownMenu.CheckboxItem>
321
+ </DropdownMenu.Content>
322
+ </DropdownMenu.Root>
323
+ </div>
324
+ {/if}
325
+
326
+ {#if showSceneManager}
327
+ <div style={itemStyle(idx('scene'))}>
328
+ <button
329
+ class="{BTN} {DOT}"
330
+ title="{t.sceneManager} (S)"
331
+ aria-label={t.sceneManager}
332
+ aria-pressed={sceneManagerOpen}
333
+ onclick={onToggleSceneManager}
334
+ >
335
+ <Layers class="size-4.5" />
336
+ </button>
337
+ </div>
338
+ {/if}
339
+
340
+ <div style={itemStyle(idx('measure'))}>
341
+ <button
342
+ class="{BTN} {DOT}"
343
+ title="{t.measure} (M)"
344
+ aria-label={t.measure}
345
+ aria-pressed={measureActive}
346
+ onclick={onToggleMeasure}
347
+ >
348
+ <Ruler class="size-4.5" />
349
+ </button>
350
+ </div>
351
+
352
+ <div style={itemStyle(idx('fit'))}>
353
+ <button class={BTN} title="{t.fitToView} (F)" aria-label="{t.fitToView} (F)" onclick={onFit}>
354
+ <LocateFixed class="size-4.5" />
355
+ </button>
356
+ </div>
357
+
358
+ {#if showFullscreen}
359
+ <div style={itemStyle(idx('full'))}>
360
+ <button
361
+ class={BTN}
362
+ title={isFullscreen ? t.exitFullscreen : t.fullscreen}
363
+ aria-label={isFullscreen ? t.exitFullscreen : t.fullscreen}
364
+ onclick={onToggleFullscreen}
365
+ >
366
+ {#if isFullscreen}<Minimize class="size-4.5" />{:else}<Maximize class="size-4.5" />{/if}
367
+ </button>
368
+ </div>
369
+ {/if}
370
+
371
+ <div style={itemStyle(idx('more'))}>
372
+ <DropdownMenu.Root onOpenChange={trackMenu}>
373
+ <DropdownMenu.Trigger>
374
+ {#snippet child({ props })}
375
+ <button {...props} class={BTN} title={t.toolsMenu} aria-label={t.toolsMenu}>
376
+ <Ellipsis class="size-4.5" />
377
+ </button>
378
+ {/snippet}
379
+ </DropdownMenu.Trigger>
380
+ <DropdownMenu.Content
381
+ side="top"
382
+ align="end"
383
+ sideOffset={12}
384
+ variant="glass"
385
+ class="min-w-60 z-10001 w-auto whitespace-nowrap"
386
+ >
387
+ <DropdownMenu.Label>{t.views}</DropdownMenu.Label>
388
+ <div class="gap-1 px-1 pb-1 grid grid-cols-4">
389
+ {#each viewPresets as { preset, label } (preset)}
390
+ <DropdownMenu.Item
391
+ closeOnSelect={false}
392
+ onSelect={() => onSetView(preset)}
393
+ variant="chip"
394
+ >
395
+ {label}
396
+ </DropdownMenu.Item>
397
+ {/each}
398
+ </div>
399
+
400
+ <DropdownMenu.Separator />
401
+
402
+ <DropdownMenu.CheckboxItem
403
+ switch
404
+ checked={projection === 'orthographic'}
405
+ onCheckedChange={onToggleProjection}
406
+ >
407
+ <Cuboid />
408
+ {t.switchToOrthographic}
409
+ <DropdownMenu.Shortcut class="pointer-coarse:hidden">O</DropdownMenu.Shortcut>
410
+ </DropdownMenu.CheckboxItem>
411
+
412
+ {#if showGrid}
413
+ <DropdownMenu.CheckboxItem switch checked={gridVisible} onCheckedChange={onToggleGrid}>
414
+ <Grid3x3 />
415
+ {t.grid}
416
+ <DropdownMenu.Shortcut class="pointer-coarse:hidden">G</DropdownMenu.Shortcut>
417
+ </DropdownMenu.CheckboxItem>
418
+ {/if}
419
+
420
+ {#if showDisplay}
421
+ <DropdownMenu.CheckboxItem switch checked={nightOn} onCheckedChange={onToggleNight}>
422
+ <Moon />
423
+ {t.nightMode}
424
+ <DropdownMenu.Shortcut class="pointer-coarse:hidden">N</DropdownMenu.Shortcut>
425
+ </DropdownMenu.CheckboxItem>
426
+ {/if}
427
+
428
+ {#if showScreenshot}
429
+ <DropdownMenu.Separator />
430
+ {/if}
431
+
432
+ {#if showScreenshot}
433
+ <DropdownMenu.Item onSelect={onScreenshot}>
434
+ <Camera />
435
+ {t.screenshot}
436
+ </DropdownMenu.Item>
437
+ {/if}
438
+ </DropdownMenu.Content>
439
+ </DropdownMenu.Root>
440
+ </div>
441
+ </div>
442
+ </div>
443
+
444
+ {#if hint}
445
+ <div
446
+ role="status"
447
+ class="text-xs font-medium px-2.5 py-1 shadow-md pointer-events-none absolute left-1/2 z-50 -translate-x-1/2 rounded-md border border-border-strong bg-card text-foreground"
448
+ style="bottom:calc({bottom} + {EXPANDED_H + 8}px)"
449
+ >
450
+ {hint}
451
+ </div>
452
+ {/if}
@@ -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
+ }