@selvajs/ui 6.5.1-beta.1 → 6.6.0-beta.11

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 (444) hide show
  1. package/dist/components/ConfirmDialog.svelte +2 -10
  2. package/dist/components/ErrorScreen.svelte +21 -27
  3. package/dist/components/compute/AppLayout.svelte +286 -116
  4. package/dist/components/compute/AppLayout.svelte.d.ts +22 -0
  5. package/dist/components/compute/ComputeApp.svelte +45 -24
  6. package/dist/components/compute/ComputeApp.svelte.d.ts +19 -2
  7. package/dist/components/compute/ComputeMessagesDialog.svelte +47 -83
  8. package/dist/components/compute/ParameterPresetManager.svelte +37 -130
  9. package/dist/components/compute/ParameterPresetManager.svelte.d.ts +4 -5
  10. package/dist/components/compute/SolveMessages.svelte +131 -77
  11. package/dist/components/compute/SolveMessages.svelte.d.ts +7 -12
  12. package/dist/components/layout/AppShell.svelte +10 -6
  13. package/dist/components/layout/PageFooter.svelte +23 -11
  14. package/dist/components/layout/PageHeader.svelte +11 -7
  15. package/dist/components/layout/Pagination.svelte +2 -4
  16. package/dist/components/layout/SectionHeader.svelte +4 -2
  17. package/dist/components/layout/SideNav.svelte +56 -7
  18. package/dist/components/preview/ChartOutput.svelte +35 -25
  19. package/dist/components/preview/Group.svelte +92 -103
  20. package/dist/components/preview/Group.svelte.d.ts +1 -1
  21. package/dist/components/preview/HelpButton.svelte +36 -0
  22. package/dist/components/preview/HelpButton.svelte.d.ts +8 -0
  23. package/dist/components/preview/ImageOutput.svelte +430 -71
  24. package/dist/components/preview/InputControl.svelte +163 -90
  25. package/dist/components/preview/InputControl.svelte.d.ts +5 -1
  26. package/dist/components/preview/OutputDisplay.svelte +28 -33
  27. package/dist/components/preview/TabBar.svelte +106 -22
  28. package/dist/components/preview/TabBar.svelte.d.ts +1 -0
  29. package/dist/components/preview/TabContent.svelte +63 -57
  30. package/dist/components/preview/TabContent.svelte.d.ts +5 -1
  31. package/dist/components/preview/TabLayout.svelte +27 -5
  32. package/dist/components/preview/TabLayout.svelte.d.ts +5 -0
  33. package/dist/components/preview/inputs/CheckboxInput.svelte +7 -7
  34. package/dist/components/preview/inputs/CheckboxInput.svelte.d.ts +3 -0
  35. package/dist/components/preview/inputs/ChecklistInput.svelte +7 -2
  36. package/dist/components/preview/inputs/ChecklistInput.svelte.d.ts +2 -1
  37. package/dist/components/preview/inputs/ColorInput.svelte +4 -2
  38. package/dist/components/preview/inputs/ColorInput.svelte.d.ts +1 -0
  39. package/dist/components/preview/inputs/DropdownInput.svelte +19 -4
  40. package/dist/components/preview/inputs/DropdownInput.svelte.d.ts +2 -1
  41. package/dist/components/preview/inputs/FileInput.svelte +112 -108
  42. package/dist/components/preview/inputs/GeometryInput.svelte +128 -0
  43. package/dist/components/preview/inputs/GeometryInput.svelte.d.ts +21 -0
  44. package/dist/components/preview/inputs/NumberInput.svelte +123 -61
  45. package/dist/components/preview/inputs/NumberInput.svelte.d.ts +2 -0
  46. package/dist/components/preview/inputs/OptionGlyph.svelte +29 -0
  47. package/dist/components/preview/inputs/OptionGlyph.svelte.d.ts +8 -0
  48. package/dist/components/preview/inputs/SegmentedInput.svelte +44 -0
  49. package/dist/components/preview/inputs/SegmentedInput.svelte.d.ts +12 -0
  50. package/dist/components/preview/inputs/TextInput.svelte +8 -4
  51. package/dist/components/preview/inputs/TileInput.svelte +80 -0
  52. package/dist/components/preview/inputs/TileInput.svelte.d.ts +12 -0
  53. package/dist/components/preview/inputs/index.d.ts +3 -0
  54. package/dist/components/preview/inputs/index.js +3 -0
  55. package/dist/components/primitives/CalculateButton.svelte +56 -24
  56. package/dist/components/primitives/CalculateButton.svelte.d.ts +3 -0
  57. package/dist/components/primitives/Callout.svelte +11 -18
  58. package/dist/components/primitives/DataTable.svelte +38 -5
  59. package/dist/components/primitives/DataTable.svelte.d.ts +2 -0
  60. package/dist/components/primitives/FilterableDropdown.svelte +42 -129
  61. package/dist/components/primitives/FilterableDropdown.svelte.d.ts +1 -1
  62. package/dist/components/primitives/IssueList.svelte +56 -0
  63. package/dist/components/primitives/IssueList.svelte.d.ts +20 -0
  64. package/dist/components/primitives/SeverityIcon.svelte +15 -0
  65. package/dist/components/primitives/SeverityIcon.svelte.d.ts +8 -0
  66. package/dist/components/primitives/SolvingIndicator.svelte +43 -42
  67. package/dist/components/primitives/SolvingIndicator.svelte.d.ts +6 -0
  68. package/dist/components/primitives/StateDisplay.svelte +19 -79
  69. package/dist/components/primitives/ViewToggle.svelte +18 -33
  70. package/dist/components/primitives/alert-dialog/alert-dialog-action.svelte +9 -3
  71. package/dist/components/primitives/alert-dialog/alert-dialog-action.svelte.d.ts +6 -1
  72. package/dist/components/primitives/alert-dialog/alert-dialog-content.svelte +1 -1
  73. package/dist/components/primitives/alert-dialog/alert-dialog-overlay.svelte +1 -1
  74. package/dist/components/primitives/badge/badge.svelte +2 -2
  75. package/dist/components/primitives/badge/badge.svelte.d.ts +4 -4
  76. package/dist/components/primitives/button/button.svelte +6 -2
  77. package/dist/components/primitives/button/button.svelte.d.ts +10 -4
  78. package/dist/components/primitives/card/card-header.svelte +1 -1
  79. package/dist/components/primitives/card/card.svelte +21 -5
  80. package/dist/components/primitives/card/card.svelte.d.ts +25 -1
  81. package/dist/components/primitives/card/index.d.ts +1 -2
  82. package/dist/components/primitives/card/index.js +2 -3
  83. package/dist/components/primitives/checkbox/checkbox.svelte +1 -1
  84. package/dist/components/primitives/command/command-dialog.svelte +42 -0
  85. package/dist/components/primitives/command/command-dialog.svelte.d.ts +14 -0
  86. package/dist/components/primitives/command/command-empty.svelte +17 -0
  87. package/dist/components/primitives/command/command-empty.svelte.d.ts +4 -0
  88. package/dist/components/primitives/command/command-group.svelte +33 -0
  89. package/dist/components/primitives/command/command-group.svelte.d.ts +7 -0
  90. package/dist/components/primitives/command/command-input.svelte +36 -0
  91. package/dist/components/primitives/command/command-input.svelte.d.ts +4 -0
  92. package/dist/components/primitives/command/command-item.svelte +28 -0
  93. package/dist/components/primitives/command/command-item.svelte.d.ts +4 -0
  94. package/dist/components/primitives/command/command-link-item.svelte +21 -0
  95. package/dist/components/primitives/command/command-link-item.svelte.d.ts +4 -0
  96. package/dist/components/primitives/command/command-list.svelte +20 -0
  97. package/dist/components/primitives/command/command-list.svelte.d.ts +4 -0
  98. package/dist/components/primitives/command/command-loading.svelte +7 -0
  99. package/dist/components/primitives/command/command-loading.svelte.d.ts +4 -0
  100. package/dist/components/primitives/command/command-separator.svelte +17 -0
  101. package/dist/components/primitives/command/command-separator.svelte.d.ts +4 -0
  102. package/dist/components/primitives/command/command-shortcut.svelte +23 -0
  103. package/dist/components/primitives/command/command-shortcut.svelte.d.ts +5 -0
  104. package/dist/components/primitives/command/command.svelte +28 -0
  105. package/dist/components/primitives/command/command.svelte.d.ts +8 -0
  106. package/dist/components/primitives/command/index.d.ts +12 -0
  107. package/dist/components/primitives/command/index.js +14 -0
  108. package/dist/components/primitives/context-menu/context-menu-content.svelte +1 -1
  109. package/dist/components/primitives/context-menu/context-menu-sub-content.svelte +1 -1
  110. package/dist/components/primitives/dialog/dialog-content.svelte +11 -3
  111. package/dist/components/primitives/dialog/dialog-content.svelte.d.ts +2 -0
  112. package/dist/components/primitives/dialog/dialog-overlay.svelte +1 -1
  113. package/dist/components/primitives/dialog/dialog-title.svelte +6 -2
  114. package/dist/components/primitives/dialog/dialog-title.svelte.d.ts +5 -1
  115. package/dist/components/primitives/drawer/Drawer.svelte +2 -2
  116. package/dist/components/primitives/dropdown-menu/dropdown-menu-checkbox-group.svelte +16 -0
  117. package/dist/components/primitives/dropdown-menu/dropdown-menu-checkbox-group.svelte.d.ts +4 -0
  118. package/dist/components/primitives/dropdown-menu/dropdown-menu-checkbox-item.svelte +46 -0
  119. package/dist/components/primitives/dropdown-menu/dropdown-menu-checkbox-item.svelte.d.ts +9 -0
  120. package/dist/components/primitives/dropdown-menu/dropdown-menu-content.svelte +31 -0
  121. package/dist/components/primitives/dropdown-menu/dropdown-menu-content.svelte.d.ts +10 -0
  122. package/dist/components/primitives/dropdown-menu/dropdown-menu-group-heading.svelte +22 -0
  123. package/dist/components/primitives/dropdown-menu/dropdown-menu-group-heading.svelte.d.ts +8 -0
  124. package/dist/components/primitives/dropdown-menu/dropdown-menu-group.svelte +7 -0
  125. package/dist/components/primitives/dropdown-menu/dropdown-menu-group.svelte.d.ts +4 -0
  126. package/dist/components/primitives/dropdown-menu/dropdown-menu-item.svelte +27 -0
  127. package/dist/components/primitives/dropdown-menu/dropdown-menu-item.svelte.d.ts +8 -0
  128. package/dist/components/primitives/dropdown-menu/dropdown-menu-label.svelte +24 -0
  129. package/dist/components/primitives/dropdown-menu/dropdown-menu-label.svelte.d.ts +8 -0
  130. package/dist/components/primitives/dropdown-menu/dropdown-menu-portal.svelte +7 -0
  131. package/dist/components/primitives/dropdown-menu/dropdown-menu-portal.svelte.d.ts +3 -0
  132. package/dist/components/primitives/dropdown-menu/dropdown-menu-radio-group.svelte +16 -0
  133. package/dist/components/primitives/dropdown-menu/dropdown-menu-radio-group.svelte.d.ts +4 -0
  134. package/dist/components/primitives/dropdown-menu/dropdown-menu-radio-item.svelte +36 -0
  135. package/dist/components/primitives/dropdown-menu/dropdown-menu-radio-item.svelte.d.ts +4 -0
  136. package/dist/components/primitives/dropdown-menu/dropdown-menu-separator.svelte +17 -0
  137. package/dist/components/primitives/dropdown-menu/dropdown-menu-separator.svelte.d.ts +4 -0
  138. package/dist/components/primitives/dropdown-menu/dropdown-menu-shortcut.svelte +23 -0
  139. package/dist/components/primitives/dropdown-menu/dropdown-menu-shortcut.svelte.d.ts +5 -0
  140. package/dist/components/primitives/dropdown-menu/dropdown-menu-sub-content.svelte +31 -0
  141. package/dist/components/primitives/dropdown-menu/dropdown-menu-sub-content.svelte.d.ts +10 -0
  142. package/dist/components/primitives/dropdown-menu/dropdown-menu-sub-trigger.svelte +29 -0
  143. package/dist/components/primitives/dropdown-menu/dropdown-menu-sub-trigger.svelte.d.ts +7 -0
  144. package/dist/components/primitives/dropdown-menu/dropdown-menu-sub.svelte +7 -0
  145. package/dist/components/primitives/dropdown-menu/dropdown-menu-sub.svelte.d.ts +3 -0
  146. package/dist/components/primitives/dropdown-menu/dropdown-menu-trigger.svelte +7 -0
  147. package/dist/components/primitives/dropdown-menu/dropdown-menu-trigger.svelte.d.ts +4 -0
  148. package/dist/components/primitives/dropdown-menu/dropdown-menu.svelte +7 -0
  149. package/dist/components/primitives/dropdown-menu/dropdown-menu.svelte.d.ts +3 -0
  150. package/dist/components/primitives/dropdown-menu/index.d.ts +18 -0
  151. package/dist/components/primitives/dropdown-menu/index.js +18 -0
  152. package/dist/components/primitives/{alert/alert-description.svelte → empty/empty-content.svelte} +3 -3
  153. package/dist/components/primitives/empty/empty-content.svelte.d.ts +5 -0
  154. package/dist/components/primitives/empty/empty-description.svelte +23 -0
  155. package/dist/components/primitives/{alert/alert-description.svelte.d.ts → empty/empty-description.svelte.d.ts} +4 -4
  156. package/dist/components/primitives/{alert/alert-title.svelte → empty/empty-header.svelte} +3 -3
  157. package/dist/components/primitives/empty/empty-header.svelte.d.ts +5 -0
  158. package/dist/components/primitives/empty/empty-media.svelte +41 -0
  159. package/dist/components/primitives/empty/empty-media.svelte.d.ts +26 -0
  160. package/dist/components/primitives/{card/card-action.svelte → empty/empty-title.svelte} +2 -2
  161. package/dist/components/primitives/{card/card-action.svelte.d.ts → empty/empty-title.svelte.d.ts} +3 -3
  162. package/dist/components/primitives/empty/empty.svelte +23 -0
  163. package/dist/components/primitives/empty/empty.svelte.d.ts +5 -0
  164. package/dist/components/primitives/empty/index.d.ts +7 -0
  165. package/dist/components/primitives/empty/index.js +9 -0
  166. package/dist/components/primitives/index.d.ts +10 -1
  167. package/dist/components/primitives/index.js +10 -1
  168. package/dist/components/primitives/input/input.svelte +4 -4
  169. package/dist/components/primitives/input-group/index.d.ts +7 -0
  170. package/dist/components/primitives/input-group/index.js +9 -0
  171. package/dist/components/primitives/input-group/input-group-addon.svelte +53 -0
  172. package/dist/components/primitives/input-group/input-group-addon.svelte.d.ts +32 -0
  173. package/dist/components/primitives/input-group/input-group-button.svelte +49 -0
  174. package/dist/components/primitives/input-group/input-group-button.svelte.d.ts +32 -0
  175. package/dist/components/primitives/input-group/input-group-input.svelte +25 -0
  176. package/dist/components/primitives/input-group/input-group-input.svelte.d.ts +11 -0
  177. package/dist/components/primitives/input-group/input-group-text.svelte +22 -0
  178. package/dist/components/primitives/input-group/input-group-text.svelte.d.ts +5 -0
  179. package/dist/components/primitives/input-group/input-group-textarea.svelte +23 -0
  180. package/dist/components/primitives/input-group/input-group-textarea.svelte.d.ts +3 -0
  181. package/dist/components/primitives/input-group/input-group.svelte +24 -0
  182. package/dist/components/primitives/{alert/alert-title.svelte.d.ts → input-group/input-group.svelte.d.ts} +4 -4
  183. package/dist/components/primitives/mode-toggle/mode-toggle.svelte +6 -7
  184. package/dist/components/primitives/popover/index.d.ts +9 -0
  185. package/dist/components/primitives/popover/index.js +11 -0
  186. package/dist/components/primitives/popover/popover-close.svelte +7 -0
  187. package/dist/components/primitives/popover/popover-close.svelte.d.ts +4 -0
  188. package/dist/components/primitives/popover/popover-content.svelte +31 -0
  189. package/dist/components/primitives/popover/popover-content.svelte.d.ts +10 -0
  190. package/dist/components/primitives/popover/popover-description.svelte +20 -0
  191. package/dist/components/primitives/popover/popover-description.svelte.d.ts +5 -0
  192. package/dist/components/primitives/popover/popover-header.svelte +20 -0
  193. package/dist/components/primitives/popover/popover-header.svelte.d.ts +5 -0
  194. package/dist/components/primitives/popover/popover-portal.svelte +7 -0
  195. package/dist/components/primitives/popover/popover-portal.svelte.d.ts +3 -0
  196. package/dist/components/primitives/popover/popover-title.svelte +15 -0
  197. package/dist/components/primitives/popover/popover-title.svelte.d.ts +5 -0
  198. package/dist/components/primitives/popover/popover-trigger.svelte +17 -0
  199. package/dist/components/primitives/popover/popover-trigger.svelte.d.ts +4 -0
  200. package/dist/components/primitives/popover/popover.svelte +7 -0
  201. package/dist/components/primitives/popover/popover.svelte.d.ts +3 -0
  202. package/dist/components/primitives/resizable/resizable-handle.svelte +6 -2
  203. package/dist/components/primitives/resizable/resizable-handle.svelte.d.ts +2 -0
  204. package/dist/components/primitives/resizable/resizable-pane-group.svelte +1 -4
  205. package/dist/components/primitives/scroll-area/scroll-area.svelte +1 -1
  206. package/dist/components/primitives/search/search.svelte +3 -3
  207. package/dist/components/primitives/select/select-content.svelte +1 -1
  208. package/dist/components/primitives/select/select-trigger.svelte +1 -1
  209. package/dist/components/primitives/severity.d.ts +7 -0
  210. package/dist/components/primitives/severity.js +19 -0
  211. package/dist/components/primitives/slider/slider.svelte +8 -3
  212. package/dist/components/primitives/slider/slider.svelte.d.ts +5 -1
  213. package/dist/components/primitives/sonner/sonner.svelte +1 -1
  214. package/dist/components/primitives/spinner/index.d.ts +1 -0
  215. package/dist/components/primitives/spinner/index.js +1 -0
  216. package/dist/components/primitives/spinner/spinner.svelte +29 -0
  217. package/dist/components/primitives/spinner/spinner.svelte.d.ts +4 -0
  218. package/dist/components/primitives/switch/switch.svelte +2 -2
  219. package/dist/components/primitives/tabs/tabs-list.svelte +27 -5
  220. package/dist/components/primitives/tabs/tabs-list.svelte.d.ts +22 -1
  221. package/dist/components/primitives/tabs/tabs-trigger.svelte +23 -5
  222. package/dist/components/primitives/tabs/tabs-trigger.svelte.d.ts +22 -1
  223. package/dist/components/primitives/tabs/tabs.svelte +6 -2
  224. package/dist/components/primitives/tabs/tabs.svelte.d.ts +5 -1
  225. package/dist/components/primitives/textarea/textarea.svelte +1 -1
  226. package/dist/components/primitives/theme-switcher/ThemeSwitcher.svelte +1 -1
  227. package/dist/components/primitives/toggle/index.d.ts +3 -0
  228. package/dist/components/primitives/toggle/index.js +5 -0
  229. package/dist/components/primitives/toggle/toggle.svelte +54 -0
  230. package/dist/components/primitives/toggle/toggle.svelte.d.ts +46 -0
  231. package/dist/components/primitives/toggle-group/index.d.ts +3 -0
  232. package/dist/components/primitives/toggle-group/index.js +5 -0
  233. package/dist/components/primitives/toggle-group/toggle-group-item.svelte +35 -0
  234. package/dist/components/primitives/toggle-group/toggle-group-item.svelte.d.ts +6 -0
  235. package/dist/components/primitives/toggle-group/toggle-group.svelte +75 -0
  236. package/dist/components/primitives/toggle-group/toggle-group.svelte.d.ts +17 -0
  237. package/dist/components/viewer/MeshMetadataPanel.svelte +119 -0
  238. package/dist/components/viewer/MeshMetadataPanel.svelte.d.ts +9 -0
  239. package/dist/components/viewer/SceneManager.svelte +14 -5
  240. package/dist/components/viewer/Viewer.svelte +243 -221
  241. package/dist/components/viewer/Viewer.svelte.d.ts +3 -0
  242. package/dist/compute/run-panel-action.d.ts +8 -0
  243. package/dist/compute/run-panel-action.js +12 -0
  244. package/dist/compute/solving.svelte.d.ts +11 -7
  245. package/dist/compute/solving.svelte.js +64 -45
  246. package/dist/compute/useSolveSession.svelte.js +3 -0
  247. package/dist/constants.d.ts +6 -8
  248. package/dist/constants.js +7 -9
  249. package/dist/contexts/hostContext.svelte.d.ts +39 -0
  250. package/dist/contexts/hostContext.svelte.js +9 -0
  251. package/dist/i18n/localeContext.svelte.d.ts +10 -3
  252. package/dist/i18n/localeContext.svelte.js +27 -5
  253. package/dist/i18n/messages.d.ts +150 -2
  254. package/dist/i18n/messages.js +261 -11
  255. package/dist/index.d.ts +4 -1
  256. package/dist/index.js +3 -0
  257. package/dist/public.d.ts +1 -1
  258. package/dist/schema/host-support.d.ts +29 -0
  259. package/dist/schema/host-support.js +97 -0
  260. package/dist/schema/option-display.d.ts +12 -0
  261. package/dist/schema/option-display.js +29 -0
  262. package/dist/schema/param-exporter.d.ts +4 -1
  263. package/dist/schema/param-exporter.js +10 -6
  264. package/dist/schema/visibility-rules.js +21 -0
  265. package/dist/styles/base.css +80 -10
  266. package/dist/types/actionButton.d.ts +17 -3
  267. package/dist/types/presetLabels.d.ts +3 -2
  268. package/dist/types/presetLabels.js +9 -8
  269. package/dist/utils/file-download.js +10 -22
  270. package/dist/utils/host-files.d.ts +12 -0
  271. package/dist/utils/host-files.js +46 -0
  272. package/dist/utils/loadScript.d.ts +2 -0
  273. package/dist/utils/loadScript.js +2 -0
  274. package/dist/utils/webp-to-png.d.ts +10 -0
  275. package/dist/utils/webp-to-png.js +41 -0
  276. package/package.json +14 -14
  277. package/src/lib/components/ConfirmDialog.svelte +2 -10
  278. package/src/lib/components/ErrorScreen.svelte +21 -27
  279. package/src/lib/components/compute/AppLayout.svelte +286 -116
  280. package/src/lib/components/compute/ComputeApp.svelte +45 -24
  281. package/src/lib/components/compute/ComputeMessagesDialog.svelte +47 -83
  282. package/src/lib/components/compute/ParameterPresetManager.svelte +37 -130
  283. package/src/lib/components/compute/SolveMessages.svelte +131 -77
  284. package/src/lib/components/layout/AppShell.svelte +10 -6
  285. package/src/lib/components/layout/PageFooter.svelte +23 -11
  286. package/src/lib/components/layout/PageHeader.svelte +11 -7
  287. package/src/lib/components/layout/Pagination.svelte +2 -4
  288. package/src/lib/components/layout/SectionHeader.svelte +4 -2
  289. package/src/lib/components/layout/SideNav.svelte +56 -7
  290. package/src/lib/components/preview/ChartOutput.svelte +35 -25
  291. package/src/lib/components/preview/Group.svelte +92 -103
  292. package/src/lib/components/preview/HelpButton.svelte +36 -0
  293. package/src/lib/components/preview/ImageOutput.svelte +430 -71
  294. package/src/lib/components/preview/InputControl.svelte +163 -90
  295. package/src/lib/components/preview/OutputDisplay.svelte +28 -33
  296. package/src/lib/components/preview/TabBar.svelte +106 -22
  297. package/src/lib/components/preview/TabContent.svelte +63 -57
  298. package/src/lib/components/preview/TabLayout.svelte +27 -5
  299. package/src/lib/components/preview/inputs/CheckboxInput.svelte +7 -7
  300. package/src/lib/components/preview/inputs/ChecklistInput.svelte +7 -2
  301. package/src/lib/components/preview/inputs/ColorInput.svelte +4 -2
  302. package/src/lib/components/preview/inputs/DropdownInput.svelte +19 -4
  303. package/src/lib/components/preview/inputs/FileInput.svelte +112 -108
  304. package/src/lib/components/preview/inputs/GeometryInput.svelte +128 -0
  305. package/src/lib/components/preview/inputs/NumberInput.svelte +123 -61
  306. package/src/lib/components/preview/inputs/OptionGlyph.svelte +29 -0
  307. package/src/lib/components/preview/inputs/SegmentedInput.svelte +44 -0
  308. package/src/lib/components/preview/inputs/TextInput.svelte +8 -4
  309. package/src/lib/components/preview/inputs/TileInput.svelte +80 -0
  310. package/src/lib/components/preview/inputs/index.ts +3 -0
  311. package/src/lib/components/primitives/CalculateButton.svelte +56 -24
  312. package/src/lib/components/primitives/Callout.svelte +11 -18
  313. package/src/lib/components/primitives/DataTable.svelte +38 -5
  314. package/src/lib/components/primitives/FilterableDropdown.svelte +42 -129
  315. package/src/lib/components/primitives/IssueList.svelte +56 -0
  316. package/src/lib/components/primitives/SeverityIcon.svelte +15 -0
  317. package/src/lib/components/primitives/SolvingIndicator.svelte +43 -42
  318. package/src/lib/components/primitives/StateDisplay.svelte +19 -79
  319. package/src/lib/components/primitives/ViewToggle.svelte +18 -33
  320. package/src/lib/components/primitives/alert-dialog/alert-dialog-action.svelte +9 -3
  321. package/src/lib/components/primitives/alert-dialog/alert-dialog-content.svelte +1 -1
  322. package/src/lib/components/primitives/alert-dialog/alert-dialog-overlay.svelte +1 -1
  323. package/src/lib/components/primitives/badge/badge.svelte +2 -2
  324. package/src/lib/components/primitives/button/button.svelte +6 -2
  325. package/src/lib/components/primitives/card/card-header.svelte +1 -1
  326. package/src/lib/components/primitives/card/card.svelte +21 -5
  327. package/src/lib/components/primitives/card/index.ts +1 -4
  328. package/src/lib/components/primitives/checkbox/checkbox.svelte +1 -1
  329. package/src/lib/components/primitives/command/command-dialog.svelte +42 -0
  330. package/src/lib/components/primitives/command/command-empty.svelte +17 -0
  331. package/src/lib/components/primitives/command/command-group.svelte +33 -0
  332. package/src/lib/components/primitives/command/command-input.svelte +36 -0
  333. package/src/lib/components/primitives/command/command-item.svelte +28 -0
  334. package/src/lib/components/primitives/command/command-link-item.svelte +21 -0
  335. package/src/lib/components/primitives/command/command-list.svelte +20 -0
  336. package/src/lib/components/primitives/command/command-loading.svelte +7 -0
  337. package/src/lib/components/primitives/command/command-separator.svelte +17 -0
  338. package/src/lib/components/primitives/command/command-shortcut.svelte +23 -0
  339. package/src/lib/components/primitives/command/command.svelte +28 -0
  340. package/src/lib/components/primitives/command/index.ts +37 -0
  341. package/src/lib/components/primitives/context-menu/context-menu-content.svelte +1 -1
  342. package/src/lib/components/primitives/context-menu/context-menu-sub-content.svelte +1 -1
  343. package/src/lib/components/primitives/dialog/dialog-content.svelte +11 -3
  344. package/src/lib/components/primitives/dialog/dialog-overlay.svelte +1 -1
  345. package/src/lib/components/primitives/dialog/dialog-title.svelte +6 -2
  346. package/src/lib/components/primitives/drawer/Drawer.svelte +2 -2
  347. package/src/lib/components/primitives/dropdown-menu/dropdown-menu-checkbox-group.svelte +16 -0
  348. package/src/lib/components/primitives/dropdown-menu/dropdown-menu-checkbox-item.svelte +46 -0
  349. package/src/lib/components/primitives/dropdown-menu/dropdown-menu-content.svelte +31 -0
  350. package/src/lib/components/primitives/dropdown-menu/dropdown-menu-group-heading.svelte +22 -0
  351. package/src/lib/components/primitives/dropdown-menu/dropdown-menu-group.svelte +7 -0
  352. package/src/lib/components/primitives/dropdown-menu/dropdown-menu-item.svelte +27 -0
  353. package/src/lib/components/primitives/dropdown-menu/dropdown-menu-label.svelte +24 -0
  354. package/src/lib/components/primitives/dropdown-menu/dropdown-menu-portal.svelte +7 -0
  355. package/src/lib/components/primitives/dropdown-menu/dropdown-menu-radio-group.svelte +16 -0
  356. package/src/lib/components/primitives/dropdown-menu/dropdown-menu-radio-item.svelte +36 -0
  357. package/src/lib/components/primitives/dropdown-menu/dropdown-menu-separator.svelte +17 -0
  358. package/src/lib/components/primitives/dropdown-menu/dropdown-menu-shortcut.svelte +23 -0
  359. package/src/lib/components/primitives/dropdown-menu/dropdown-menu-sub-content.svelte +31 -0
  360. package/src/lib/components/primitives/dropdown-menu/dropdown-menu-sub-trigger.svelte +29 -0
  361. package/src/lib/components/primitives/dropdown-menu/dropdown-menu-sub.svelte +7 -0
  362. package/src/lib/components/primitives/dropdown-menu/dropdown-menu-trigger.svelte +7 -0
  363. package/src/lib/components/primitives/dropdown-menu/dropdown-menu.svelte +7 -0
  364. package/src/lib/components/primitives/dropdown-menu/index.ts +54 -0
  365. package/src/lib/components/primitives/{alert/alert-description.svelte → empty/empty-content.svelte} +3 -3
  366. package/src/lib/components/primitives/empty/empty-description.svelte +23 -0
  367. package/src/lib/components/primitives/{alert/alert-title.svelte → empty/empty-header.svelte} +3 -3
  368. package/src/lib/components/primitives/empty/empty-media.svelte +41 -0
  369. package/src/lib/components/primitives/{card/card-action.svelte → empty/empty-title.svelte} +2 -2
  370. package/src/lib/components/primitives/empty/empty.svelte +23 -0
  371. package/src/lib/components/primitives/empty/index.ts +22 -0
  372. package/src/lib/components/primitives/index.ts +10 -1
  373. package/src/lib/components/primitives/input/input.svelte +4 -4
  374. package/src/lib/components/primitives/input-group/index.ts +22 -0
  375. package/src/lib/components/primitives/input-group/input-group-addon.svelte +53 -0
  376. package/src/lib/components/primitives/input-group/input-group-button.svelte +49 -0
  377. package/src/lib/components/primitives/input-group/input-group-input.svelte +25 -0
  378. package/src/lib/components/primitives/input-group/input-group-text.svelte +22 -0
  379. package/src/lib/components/primitives/input-group/input-group-textarea.svelte +23 -0
  380. package/src/lib/components/primitives/input-group/input-group.svelte +24 -0
  381. package/src/lib/components/primitives/mode-toggle/mode-toggle.svelte +6 -7
  382. package/src/lib/components/primitives/popover/index.ts +28 -0
  383. package/src/lib/components/primitives/popover/popover-close.svelte +7 -0
  384. package/src/lib/components/primitives/popover/popover-content.svelte +31 -0
  385. package/src/lib/components/primitives/popover/popover-description.svelte +20 -0
  386. package/src/lib/components/primitives/popover/popover-header.svelte +20 -0
  387. package/src/lib/components/primitives/popover/popover-portal.svelte +7 -0
  388. package/src/lib/components/primitives/popover/popover-title.svelte +15 -0
  389. package/src/lib/components/primitives/popover/popover-trigger.svelte +17 -0
  390. package/src/lib/components/primitives/popover/popover.svelte +7 -0
  391. package/src/lib/components/primitives/resizable/resizable-handle.svelte +6 -2
  392. package/src/lib/components/primitives/resizable/resizable-pane-group.svelte +1 -4
  393. package/src/lib/components/primitives/scroll-area/scroll-area.svelte +1 -1
  394. package/src/lib/components/primitives/search/search.svelte +3 -3
  395. package/src/lib/components/primitives/select/select-content.svelte +1 -1
  396. package/src/lib/components/primitives/select/select-trigger.svelte +1 -1
  397. package/src/lib/components/primitives/severity.ts +26 -0
  398. package/src/lib/components/primitives/slider/slider.svelte +8 -3
  399. package/src/lib/components/primitives/sonner/sonner.svelte +1 -1
  400. package/src/lib/components/primitives/spinner/index.ts +1 -0
  401. package/src/lib/components/primitives/spinner/spinner.svelte +29 -0
  402. package/src/lib/components/primitives/switch/switch.svelte +2 -2
  403. package/src/lib/components/primitives/tabs/tabs-list.svelte +27 -5
  404. package/src/lib/components/primitives/tabs/tabs-trigger.svelte +23 -5
  405. package/src/lib/components/primitives/tabs/tabs.svelte +6 -2
  406. package/src/lib/components/primitives/textarea/textarea.svelte +1 -1
  407. package/src/lib/components/primitives/theme-switcher/ThemeSwitcher.svelte +1 -1
  408. package/src/lib/components/primitives/toggle/index.ts +13 -0
  409. package/src/lib/components/primitives/toggle/toggle.svelte +54 -0
  410. package/src/lib/components/primitives/toggle-group/index.ts +10 -0
  411. package/src/lib/components/primitives/toggle-group/toggle-group-item.svelte +35 -0
  412. package/src/lib/components/primitives/toggle-group/toggle-group.svelte +75 -0
  413. package/src/lib/components/viewer/MeshMetadataPanel.svelte +119 -0
  414. package/src/lib/components/viewer/SceneManager.svelte +14 -5
  415. package/src/lib/components/viewer/Viewer.svelte +243 -221
  416. package/src/lib/compute/run-panel-action.ts +16 -0
  417. package/src/lib/compute/solving.svelte.ts +66 -50
  418. package/src/lib/compute/useSolveSession.svelte.ts +3 -0
  419. package/src/lib/constants.ts +7 -9
  420. package/src/lib/contexts/hostContext.svelte.ts +51 -0
  421. package/src/lib/i18n/localeContext.svelte.ts +40 -5
  422. package/src/lib/i18n/messages.ts +453 -13
  423. package/src/lib/index.ts +21 -0
  424. package/src/lib/public.ts +1 -0
  425. package/src/lib/schema/host-support.ts +129 -0
  426. package/src/lib/schema/option-display.ts +44 -0
  427. package/src/lib/schema/param-exporter.ts +10 -7
  428. package/src/lib/schema/visibility-rules.ts +23 -0
  429. package/src/lib/styles/base.css +80 -10
  430. package/src/lib/types/actionButton.ts +16 -3
  431. package/src/lib/types/presetLabels.ts +13 -11
  432. package/src/lib/utils/file-download.ts +11 -21
  433. package/src/lib/utils/host-files.ts +61 -0
  434. package/src/lib/utils/loadScript.ts +3 -0
  435. package/src/lib/utils/webp-to-png.ts +48 -0
  436. package/dist/components/primitives/alert/alert.svelte +0 -44
  437. package/dist/components/primitives/alert/alert.svelte.d.ts +0 -26
  438. package/dist/components/primitives/alert/index.d.ts +0 -5
  439. package/dist/components/primitives/alert/index.js +0 -7
  440. package/dist/components/viewer/MeshMetadataDialog.svelte +0 -126
  441. package/dist/components/viewer/MeshMetadataDialog.svelte.d.ts +0 -10
  442. package/src/lib/components/primitives/alert/alert.svelte +0 -44
  443. package/src/lib/components/primitives/alert/index.ts +0 -14
  444. package/src/lib/components/viewer/MeshMetadataDialog.svelte +0 -126
@@ -1,8 +1,20 @@
1
1
  <script lang="ts">
2
2
  import type { OutputImageLayoutItem } from '@selvajs/schemas';
3
3
  import type { FileData } from '@selvajs/compute/core';
4
- import { Download, Maximize, Minimize } from '@lucide/svelte';
4
+ import {
5
+ ChevronLeft,
6
+ ChevronRight,
7
+ Download,
8
+ LoaderCircle,
9
+ Maximize,
10
+ Minimize,
11
+ Scan,
12
+ ZoomIn,
13
+ ZoomOut
14
+ } from '@lucide/svelte';
5
15
  import { downloadFiles, isFileData, MIME_BY_EXT } from '$lib/utils/file-download';
16
+ import { getLocaleContext } from '$lib/i18n/localeContext.svelte';
17
+ import { formatMessage } from '$lib/i18n/messages';
6
18
 
7
19
  interface Props {
8
20
  item: OutputImageLayoutItem;
@@ -11,135 +23,482 @@
11
23
 
12
24
  let { item, value }: Props = $props();
13
25
 
26
+ const locale = getLocaleContext();
27
+ const t = $derived(locale.messages);
28
+
14
29
  const SUPPORTED_EXTS = new Set(['.png', '.jpg', '.jpeg', '.webp', '.gif', '.svg']);
30
+ const ZOOM_STEP = 1.5;
31
+ const MAX_ZOOM_OVER_NATURAL = 32;
32
+ // Above this many screen pixels per image pixel, smoothing hides exactly the detail the user
33
+ // zoomed in to see, so raster images switch to nearest-neighbour.
34
+ const PIXELATED_FROM_SCALE = 2;
35
+
36
+ const extOf = (f: FileData) => (f.fileType ?? '').toLowerCase();
15
37
 
16
- const file = $derived.by<FileData | null>(() => {
17
- if (Array.isArray(value)) return value.find(isFileData) ?? null;
18
- return isFileData(value) ? value : null;
38
+ const files = $derived.by<FileData[]>(() => {
39
+ if (Array.isArray(value)) return value.filter(isFileData);
40
+ return isFileData(value) ? [value] : [];
19
41
  });
42
+ const images = $derived(files.filter((f) => SUPPORTED_EXTS.has(extOf(f))));
43
+ const unsupported = $derived(images.length === 0 ? files[0] : undefined);
20
44
 
21
- const ext = $derived(file ? (file.fileType ?? '').toLowerCase() : '');
22
- const isSupported = $derived(SUPPORTED_EXTS.has(ext));
45
+ let index = $state(0);
46
+ const currentIndex = $derived(Math.max(0, Math.min(index, images.length - 1)));
47
+ const current = $derived(images[currentIndex] ?? null);
48
+ const isSvg = $derived(current ? extOf(current) === '.svg' : false);
23
49
 
24
50
  // Binary formats (PNG/JPG/WEBP/GIF) arrive base64; SVG arrives as plain UTF-8 XML
25
51
  // and must be percent-encoded into the data URL.
26
52
  // Rendering through <img src> rather than inlining the SVG is a security choice:
27
53
  // browsers disable scripting in image-context SVGs, so untrusted SVG cannot
28
54
  // execute against the host. Don't switch to {@html} or an inline <svg>.
29
- const dataUrl = $derived.by(() => {
30
- if (!file || !isSupported) return null;
31
- const mime = MIME_BY_EXT[ext] ?? 'application/octet-stream';
32
- if (file.isBase64Encoded) {
33
- return `data:${mime};base64,${file.data}`;
34
- }
55
+ function toDataUrl(file: FileData): string {
56
+ const mime = MIME_BY_EXT[extOf(file)] ?? 'application/octet-stream';
57
+ if (file.isBase64Encoded) return `data:${mime};base64,${file.data}`;
35
58
  return `data:${mime};utf8,${encodeURIComponent(file.data)}`;
36
- });
59
+ }
60
+
61
+ const dataUrl = $derived(current ? toDataUrl(current) : null);
37
62
 
38
63
  const cfg = $derived(item.config ?? {});
39
64
  const allowDownload = $derived(cfg.allowDownload ?? true);
40
- const allowFullscreen = $derived(cfg.allowFullscreen ?? true);
65
+ // Some embedded WebViews (WKWebView in the Rhino panel) ship without the Fullscreen API at all.
66
+ const fullscreenSupported = typeof document !== 'undefined' && !!document.fullscreenEnabled;
67
+ const allowFullscreen = $derived((cfg.allowFullscreen ?? true) && fullscreenSupported);
41
68
  const backgroundColor = $derived(cfg.backgroundColor);
42
69
 
43
70
  let wrapperEl = $state<HTMLDivElement | null>(null);
71
+ let stageEl = $state<HTMLDivElement | null>(null);
44
72
  let isFullscreen = $state(false);
45
73
  let downloading = $state(false);
46
74
  let error = $state<string | null>(null);
75
+ let decodeFailed = $state(false);
76
+
77
+ // View state. `scale` is screen px per image px; (x, y) is the image's top-left in stage px.
78
+ // The image is sized with width/height rather than a CSS transform so the browser re-rasters
79
+ // SVGs and resamples bitmaps at the displayed size instead of stretching a cached layer.
80
+ let stageW = $state(0);
81
+ let stageH = $state(0);
82
+ let naturalW = $state(0);
83
+ let naturalH = $state(0);
84
+ let scale = $state(1);
85
+ let x = $state(0);
86
+ let y = $state(0);
87
+ let fitted = $state(true);
88
+
89
+ const ready = $derived(naturalW > 0 && naturalH > 0 && stageW > 0 && stageH > 0);
90
+ const fitScale = $derived(ready ? Math.min(stageW / naturalW, stageH / naturalH) : 1);
91
+ const minScale = $derived(Math.min(fitScale, 1));
92
+ const maxScale = $derived(Math.max(fitScale, 1) * MAX_ZOOM_OVER_NATURAL);
93
+ const zoomPercent = $derived(Math.round(scale * 100));
94
+ const pannable = $derived(naturalW * scale > stageW + 1 || naturalH * scale > stageH + 1);
95
+ const pixelated = $derived(!isSvg && scale >= PIXELATED_FROM_SCALE);
96
+
97
+ function clampAxis(pos: number, size: number, stage: number) {
98
+ if (size <= stage) return (stage - size) / 2;
99
+ return Math.min(0, Math.max(stage - size, pos));
100
+ }
101
+
102
+ function applyView(nextScale: number, nextX: number, nextY: number) {
103
+ scale = nextScale;
104
+ x = clampAxis(nextX, naturalW * nextScale, stageW);
105
+ y = clampAxis(nextY, naturalH * nextScale, stageH);
106
+ }
107
+
108
+ function fit() {
109
+ fitted = true;
110
+ applyView(fitScale, 0, 0);
111
+ }
112
+
113
+ function zoomTo(target: number, px = stageW / 2, py = stageH / 2) {
114
+ const next = Math.min(maxScale, Math.max(minScale, target));
115
+ const ratio = next / scale;
116
+ fitted = Math.abs(next - fitScale) < 1e-3;
117
+ applyView(next, px - (px - x) * ratio, py - (py - y) * ratio);
118
+ }
119
+
120
+ // Refit on stage resize (fullscreen, layout changes) unless the user has zoomed in.
121
+ $effect(() => {
122
+ if (!stageEl) return;
123
+ const ro = new ResizeObserver(([entry]) => {
124
+ stageW = entry.contentRect.width;
125
+ stageH = entry.contentRect.height;
126
+ if (fitted) fit();
127
+ else applyView(Math.max(scale, minScale), x, y);
128
+ });
129
+ ro.observe(stageEl);
130
+ return () => ro.disconnect();
131
+ });
132
+
133
+ function onImageLoad(e: Event) {
134
+ const img = e.currentTarget as HTMLImageElement;
135
+ decodeFailed = false;
136
+ // A viewBox-only SVG can report 0×0; fall back to the browser's default replaced-element size.
137
+ const w = img.naturalWidth || 300;
138
+ const h = img.naturalHeight || 150;
139
+ // Keep the zoom across solves when the new image has the same size: the user is usually
140
+ // watching one detail change as they move a slider.
141
+ const sameSize = w === naturalW && h === naturalH;
142
+ naturalW = w;
143
+ naturalH = h;
144
+ if (!sameSize || fitted) fit();
145
+ }
146
+
147
+ // ---- pointer: drag to pan, two fingers to pinch -------------------------------------------
148
+ // Not reactive state: only read inside handlers.
149
+ const pointers: Record<number, { x: number; y: number }> = {};
150
+ const pointerList = () => Object.values(pointers);
151
+ let dragging = $state(false);
152
+ let pinchDistance = 0;
153
+
154
+ function stagePoint(e: { clientX: number; clientY: number }) {
155
+ const r = stageEl!.getBoundingClientRect();
156
+ return { x: e.clientX - r.left, y: e.clientY - r.top };
157
+ }
158
+
159
+ function onPointerDown(e: PointerEvent) {
160
+ if (e.button !== 0 || (e.target as Element).closest('button')) return;
161
+ stageEl?.setPointerCapture(e.pointerId);
162
+ pointers[e.pointerId] = stagePoint(e);
163
+ const count = pointerList().length;
164
+ dragging = count === 1 && pannable;
165
+ if (count === 2) {
166
+ const [a, b] = pointerList();
167
+ pinchDistance = Math.hypot(a.x - b.x, a.y - b.y);
168
+ }
169
+ }
170
+
171
+ function onPointerMove(e: PointerEvent) {
172
+ const prev = pointers[e.pointerId];
173
+ if (!prev) return;
174
+ const p = stagePoint(e);
175
+ pointers[e.pointerId] = p;
176
+
177
+ if (pointerList().length === 2) {
178
+ const [a, b] = pointerList();
179
+ const distance = Math.hypot(a.x - b.x, a.y - b.y);
180
+ if (pinchDistance > 0)
181
+ zoomTo(scale * (distance / pinchDistance), (a.x + b.x) / 2, (a.y + b.y) / 2);
182
+ pinchDistance = distance;
183
+ } else if (dragging) {
184
+ fitted = false;
185
+ applyView(scale, x + p.x - prev.x, y + p.y - prev.y);
186
+ }
187
+ }
188
+
189
+ function onPointerUp(e: PointerEvent) {
190
+ delete pointers[e.pointerId];
191
+ const count = pointerList().length;
192
+ if (count < 2) pinchDistance = 0;
193
+ if (count === 0) dragging = false;
194
+ }
195
+
196
+ function onDoubleClick(e: MouseEvent) {
197
+ if (!fitted) return fit();
198
+ const p = stagePoint(e);
199
+ // Large images go to 1:1 (true pixels); small ones are already ≥1:1 when fitted, so magnify.
200
+ zoomTo(fitScale < 0.8 ? 1 : fitScale * 3, p.x, p.y);
201
+ }
202
+
203
+ // ---- wheel: zoom on Ctrl/⌘ (and trackpad pinch, which sends ctrlKey) or in fullscreen -------
204
+ let showWheelHint = $state(false);
205
+ let wheelHintTimeout: ReturnType<typeof setTimeout>;
206
+
207
+ // Registered by hand: preventDefault needs a non-passive listener.
208
+ $effect(() => {
209
+ if (!stageEl) return;
210
+ const el = stageEl;
211
+ function onWheel(e: WheelEvent) {
212
+ if (!ready) return;
213
+ if (!(e.ctrlKey || e.metaKey || isFullscreen)) {
214
+ // Let the page scroll, but say how to zoom.
215
+ showWheelHint = true;
216
+ clearTimeout(wheelHintTimeout);
217
+ wheelHintTimeout = setTimeout(() => (showWheelHint = false), 1200);
218
+ return;
219
+ }
220
+ e.preventDefault();
221
+ showWheelHint = false;
222
+ const delta = e.deltaMode === 1 ? e.deltaY * 16 : e.deltaY;
223
+ const p = stagePoint(e);
224
+ zoomTo(scale * Math.exp(-delta * 0.002), p.x, p.y);
225
+ }
226
+ el.addEventListener('wheel', onWheel, { passive: false });
227
+ return () => {
228
+ el.removeEventListener('wheel', onWheel);
229
+ clearTimeout(wheelHintTimeout);
230
+ };
231
+ });
232
+
233
+ function onKeyDown(e: KeyboardEvent) {
234
+ const actions: Record<string, () => void> = {
235
+ '+': () => zoomTo(scale * ZOOM_STEP),
236
+ '=': () => zoomTo(scale * ZOOM_STEP),
237
+ '-': () => zoomTo(scale / ZOOM_STEP),
238
+ '0': fit,
239
+ '1': () => zoomTo(1),
240
+ ArrowLeft: () => step(-1),
241
+ ArrowRight: () => step(1)
242
+ };
243
+ if (allowFullscreen) actions.f = toggleFullscreen;
244
+ const action = actions[e.key];
245
+ if (!action) return;
246
+ e.preventDefault();
247
+ action();
248
+ }
249
+
250
+ function step(delta: number) {
251
+ if (images.length < 2) return;
252
+ select((currentIndex + delta + images.length) % images.length);
253
+ }
254
+
255
+ function select(i: number) {
256
+ if (i === currentIndex) return;
257
+ index = i;
258
+ // A different image in the set: start fitted rather than carrying over the old zoom.
259
+ naturalW = 0;
260
+ naturalH = 0;
261
+ fitted = true;
262
+ }
47
263
 
48
264
  function toggleFullscreen() {
49
265
  if (!wrapperEl) return;
50
266
  if (!document.fullscreenElement) {
51
267
  wrapperEl.requestFullscreen().catch((err) => {
52
- error = `Fullscreen unavailable: ${err.message}`;
268
+ error = formatMessage(t.fullscreenUnavailable, { error: err.message });
53
269
  });
54
270
  } else {
55
271
  document.exitFullscreen().catch((err) => {
56
- error = `Exit fullscreen failed: ${err.message}`;
272
+ error = formatMessage(t.exitFullscreenFailed, { error: err.message });
57
273
  });
58
274
  }
59
275
  }
60
276
 
61
277
  $effect(() => {
62
278
  function onFullscreenChange() {
63
- isFullscreen = !!document.fullscreenElement;
279
+ isFullscreen = document.fullscreenElement === wrapperEl;
64
280
  }
65
281
  document.addEventListener('fullscreenchange', onFullscreenChange);
66
282
  return () => document.removeEventListener('fullscreenchange', onFullscreenChange);
67
283
  });
68
284
 
69
285
  async function handleDownload() {
70
- if (!file) return;
286
+ if (!current) return;
71
287
  downloading = true;
72
288
  error = null;
73
289
  try {
74
- await downloadFiles(file, file.fileName);
290
+ await downloadFiles(current, current.fileName);
75
291
  } catch (e) {
76
- error = e instanceof Error ? e.message : 'Download failed';
292
+ error = e instanceof Error ? e.message : t.downloadFailed;
77
293
  } finally {
78
294
  downloading = false;
79
295
  }
80
296
  }
297
+
298
+ const toolButton =
299
+ 'flex h-7 min-w-7 items-center justify-center rounded px-1.5 text-muted-foreground transition-colors hover:bg-accent hover:text-foreground disabled:pointer-events-none disabled:opacity-40';
81
300
  </script>
82
301
 
83
- <div
84
- bind:this={wrapperEl}
85
- class="group rounded relative overflow-hidden border border-border {isFullscreen
86
- ? 'bg-background'
87
- : ''}"
88
- >
89
- {#if error}
90
- <div class="px-4 py-3 text-sm text-destructive">{error}</div>
91
- {:else if !file}
92
- <div class="px-4 py-8 text-sm text-center text-muted-foreground">Waiting for image...</div>
93
- {:else if !isSupported}
94
- <div class="px-4 py-3 text-sm text-destructive">
95
- Unsupported image format: <span class="font-mono">{ext || '(unknown)'}</span>. Supported:
96
- .png, .jpg, .jpeg, .webp, .gif, .svg
97
- </div>
98
- {:else}
99
- <div
100
- class="w-full"
101
- style="height: {isFullscreen ? '100vh' : '380px'}; {backgroundColor
102
- ? `background-color: ${backgroundColor};`
103
- : ''}"
104
- >
105
- <img
106
- src={dataUrl}
107
- alt={item.displayName ?? file.fileName}
108
- class="h-full w-full object-contain"
109
- />
110
- </div>
111
-
112
- {#if allowDownload || allowFullscreen}
302
+ <!-- The layout hides this output until a solve returns a file, so there is no empty state here. -->
303
+ {#if error || unsupported || current}
304
+ <div
305
+ bind:this={wrapperEl}
306
+ class="rounded flex flex-col overflow-hidden border border-border bg-background {isFullscreen
307
+ ? 'h-dvh'
308
+ : ''}"
309
+ >
310
+ {#if error}
311
+ <div class="px-4 py-3 text-sm text-destructive">{error}</div>
312
+ {:else if unsupported}
313
+ <div class="px-4 py-3 text-sm text-destructive">
314
+ {formatMessage(t.imageUnsupported, {
315
+ ext: extOf(unsupported) || t.imageUnknownFormat,
316
+ formats: [...SUPPORTED_EXTS].join(', ')
317
+ })}
318
+ </div>
319
+ {:else if current}
320
+ <!-- The stage is a pan/zoom surface with its own keyboard shortcuts; no native element fits. -->
321
+ <!-- svelte-ignore a11y_no_noninteractive_tabindex, a11y_no_noninteractive_element_interactions -->
113
322
  <div
114
- class="gap-1 right-2 top-2 absolute z-10 flex items-center opacity-0 transition-opacity group-hover:opacity-100 focus-within:opacity-100 {isFullscreen
115
- ? 'opacity-100'
116
- : ''}"
323
+ bind:this={stageEl}
324
+ role="application"
325
+ aria-roledescription={t.imageViewer}
326
+ aria-label={item.displayName ?? current.fileName}
327
+ tabindex="0"
328
+ class="relative w-full overflow-hidden outline-none select-none focus-visible:ring-2 focus-visible:ring-ring
329
+ {isFullscreen ? 'flex-1' : 'h-95'}
330
+ {dragging ? 'cursor-grabbing' : pannable ? 'cursor-grab' : 'cursor-zoom-in'}
331
+ {fitted ? 'touch-pan-y' : 'touch-none'}
332
+ {backgroundColor ? 'bg-(--image-bg)' : 'checkerboard'}"
333
+ style:--image-bg={backgroundColor}
334
+ onpointerdown={onPointerDown}
335
+ onpointermove={onPointerMove}
336
+ onpointerup={onPointerUp}
337
+ onpointercancel={onPointerUp}
338
+ ondblclick={onDoubleClick}
339
+ onkeydown={onKeyDown}
117
340
  >
118
- {#if allowDownload && file}
341
+ {#if decodeFailed}
342
+ <div class="inset-0 text-sm absolute flex items-center justify-center text-destructive">
343
+ {formatMessage(t.imageDecodeFailed, { file: `${current.fileName}${current.fileType}` })}
344
+ </div>
345
+ {:else}
346
+ {#if !ready}
347
+ <div
348
+ class="inset-0 gap-2 text-sm absolute flex items-center justify-center text-muted-foreground"
349
+ >
350
+ <LoaderCircle size={16} class="animate-spin" />
351
+ {t.imageLoading}
352
+ </div>
353
+ {/if}
354
+ <img
355
+ src={dataUrl}
356
+ alt=""
357
+ draggable="false"
358
+ onload={onImageLoad}
359
+ onerror={() => (decodeFailed = true)}
360
+ class="absolute top-(--img-y) left-(--img-x) h-(--img-h) w-(--img-w) max-w-none
361
+ {pixelated ? 'pixelated' : ''}
362
+ {ready ? '' : 'invisible'}"
363
+ style:--img-x="{x}px"
364
+ style:--img-y="{y}px"
365
+ style:--img-w="{naturalW * scale}px"
366
+ style:--img-h="{naturalH * scale}px"
367
+ />
368
+ {/if}
369
+
370
+ {#if images.length > 1}
119
371
  <button
120
- onclick={handleDownload}
121
- disabled={downloading}
122
- title="Download image"
123
- class="rounded p-1.5 backdrop-blur-sm flex items-center border border-border bg-background/80 text-foreground transition-colors hover:bg-background disabled:cursor-not-allowed disabled:opacity-50"
372
+ onclick={() => step(-1)}
373
+ title="{t.previousImage} (←)"
374
+ aria-label={t.previousImage}
375
+ class="left-2 p-1.5 backdrop-blur-sm absolute top-1/2 -translate-y-1/2 rounded-full border border-border bg-background/80 text-foreground hover:bg-background"
124
376
  >
125
- <Download size={14} />
377
+ <ChevronLeft size={16} />
378
+ </button>
379
+ <button
380
+ onclick={() => step(1)}
381
+ title="{t.nextImage} (→)"
382
+ aria-label={t.nextImage}
383
+ class="right-2 p-1.5 backdrop-blur-sm absolute top-1/2 -translate-y-1/2 rounded-full border border-border bg-background/80 text-foreground hover:bg-background"
384
+ >
385
+ <ChevronRight size={16} />
126
386
  </button>
127
387
  {/if}
128
388
 
129
- {#if allowFullscreen}
389
+ <div
390
+ class="px-2.5 py-1 text-xs bottom-2 rounded pointer-events-none absolute left-1/2 -translate-x-1/2 bg-foreground/80 text-background transition-opacity {showWheelHint
391
+ ? 'opacity-100'
392
+ : 'opacity-0'}"
393
+ >
394
+ {t.zoomHint}
395
+ </div>
396
+ </div>
397
+
398
+ {#if images.length > 1}
399
+ <div class="gap-1.5 p-1.5 flex overflow-x-auto border-t border-border">
400
+ {#each images as image, i (i)}
401
+ <button
402
+ onclick={() => select(i)}
403
+ title="{image.fileName}{image.fileType}"
404
+ class="h-12 w-16 rounded shrink-0 overflow-hidden border-2 bg-muted transition-colors {i ===
405
+ currentIndex
406
+ ? 'border-primary'
407
+ : 'border-transparent opacity-70 hover:opacity-100'}"
408
+ >
409
+ <img src={toDataUrl(image)} alt="" class="h-full w-full object-contain" />
410
+ </button>
411
+ {/each}
412
+ </div>
413
+ {/if}
414
+
415
+ <div class="gap-2 px-2 py-1 text-xs flex items-center border-t border-border">
416
+ <div class="min-w-0 gap-1.5 flex flex-1 items-center text-muted-foreground">
417
+ <span class="truncate text-foreground">{current.fileName}{current.fileType}</span>
418
+ {#if ready}
419
+ <span class="shrink-0 tabular-nums">{naturalW}×{naturalH}</span>
420
+ {/if}
421
+ </div>
422
+
423
+ <div class="gap-0.5 flex shrink-0 items-center">
130
424
  <button
131
- onclick={toggleFullscreen}
132
- title={isFullscreen ? 'Exit fullscreen' : 'Fullscreen'}
133
- class="rounded p-1.5 backdrop-blur-sm flex items-center border border-border bg-background/80 text-foreground transition-colors hover:bg-background"
425
+ class={toolButton}
426
+ onclick={() => zoomTo(scale / ZOOM_STEP)}
427
+ disabled={!ready || scale <= minScale + 1e-3}
428
+ title="{t.zoomOut} (−)"
429
+ aria-label={t.zoomOut}
134
430
  >
135
- {#if isFullscreen}
136
- <Minimize size={14} />
137
- {:else}
138
- <Maximize size={14} />
139
- {/if}
431
+ <ZoomOut size={14} />
140
432
  </button>
141
- {/if}
433
+ <span class="w-11 text-center text-muted-foreground tabular-nums">{zoomPercent}%</span>
434
+ <button
435
+ class={toolButton}
436
+ onclick={() => zoomTo(scale * ZOOM_STEP)}
437
+ disabled={!ready || scale >= maxScale - 1e-3}
438
+ title="{t.zoomIn} (+)"
439
+ aria-label={t.zoomIn}
440
+ >
441
+ <ZoomIn size={14} />
442
+ </button>
443
+ <button
444
+ class={toolButton}
445
+ onclick={fit}
446
+ disabled={!ready || fitted}
447
+ title="{t.zoomFit} (0)"
448
+ aria-label={t.zoomFit}
449
+ >
450
+ <Scan size={14} />
451
+ </button>
452
+ <button
453
+ class="{toolButton} font-medium tabular-nums"
454
+ onclick={() => zoomTo(1)}
455
+ disabled={!ready || Math.abs(scale - 1) < 1e-3}
456
+ title="{t.zoomActual} (1)"
457
+ aria-label={t.zoomActual}
458
+ >
459
+ 1:1
460
+ </button>
461
+
462
+ {#if allowDownload || allowFullscreen}
463
+ <div class="mx-1 h-4 w-px bg-border"></div>
464
+ {/if}
465
+ {#if allowDownload}
466
+ <button
467
+ class={toolButton}
468
+ onclick={handleDownload}
469
+ disabled={downloading}
470
+ title={t.downloadImage}
471
+ aria-label={t.downloadImage}
472
+ >
473
+ <Download size={14} />
474
+ </button>
475
+ {/if}
476
+ {#if allowFullscreen}
477
+ <button
478
+ class={toolButton}
479
+ onclick={toggleFullscreen}
480
+ title="{isFullscreen ? t.exitFullscreen : t.fullscreen} (F)"
481
+ aria-label={isFullscreen ? t.exitFullscreen : t.fullscreen}
482
+ >
483
+ {#if isFullscreen}
484
+ <Minimize size={14} />
485
+ {:else}
486
+ <Maximize size={14} />
487
+ {/if}
488
+ </button>
489
+ {/if}
490
+ </div>
142
491
  </div>
143
492
  {/if}
144
- {/if}
145
- </div>
493
+ </div>
494
+ {/if}
495
+
496
+ <style>
497
+ /* Shows transparency in PNG/WebP/SVG output instead of blending it into the page. */
498
+ .checkerboard {
499
+ background: repeating-conic-gradient(var(--muted) 0 25%, transparent 0 50%) 0 0 / 16px 16px;
500
+ }
501
+ .pixelated {
502
+ image-rendering: pixelated;
503
+ }
504
+ </style>