@zendir/ui 0.5.0 → 0.6.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 (391) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +640 -631
  3. package/dist/_virtual/_rolldown/runtime.js +13 -0
  4. package/dist/index.js +17 -221
  5. package/dist/react/3d/CesiumCaptureSource.d.ts +0 -1
  6. package/dist/react/3d/CesiumCaptureSource.js +364 -283
  7. package/dist/react/3d/CesiumCaptureSource.js.map +1 -1
  8. package/dist/react/3d/ZenSpace3D.d.ts +2 -3
  9. package/dist/react/3d/ZenSpace3D.js +1681 -1237
  10. package/dist/react/3d/ZenSpace3D.js.map +1 -1
  11. package/dist/react/3d/ZenSpace3DCesium.d.ts +0 -1
  12. package/dist/react/3d/ZenSpace3DCesium.js +513 -560
  13. package/dist/react/3d/ZenSpace3DCesium.js.map +1 -1
  14. package/dist/react/3d/ZenSpace3DShaders.js +61 -66
  15. package/dist/react/3d/ZenSpace3DShaders.js.map +1 -1
  16. package/dist/react/3d/ZenSpace3DTypes.d.ts +19 -20
  17. package/dist/react/3d/ZenSpace3DUtils.d.ts +0 -1
  18. package/dist/react/3d/ZenSpace3DUtils.js +37 -24
  19. package/dist/react/3d/ZenSpace3DUtils.js.map +1 -1
  20. package/dist/react/3d/capturePngAnalysis.js +42 -36
  21. package/dist/react/3d/capturePngAnalysis.js.map +1 -1
  22. package/dist/react/3d/index.d.ts +3 -3
  23. package/dist/react/3d/index.js +5 -53
  24. package/dist/react/3d/threeLoader.d.ts +1 -1
  25. package/dist/react/3d/threeLoader.js +23 -18
  26. package/dist/react/3d/threeLoader.js.map +1 -1
  27. package/dist/react/astro/ClassificationBanner.d.ts +2 -3
  28. package/dist/react/astro/ClassificationBanner.js +95 -79
  29. package/dist/react/astro/ClassificationBanner.js.map +1 -1
  30. package/dist/react/astro/GlobalStatusBar.d.ts +0 -1
  31. package/dist/react/astro/GlobalStatusBar.js +157 -163
  32. package/dist/react/astro/GlobalStatusBar.js.map +1 -1
  33. package/dist/react/astro/MissionClock.d.ts +7 -8
  34. package/dist/react/astro/MissionClock.js +377 -400
  35. package/dist/react/astro/MissionClock.js.map +1 -1
  36. package/dist/react/astro/MonitoringIcon.d.ts +1 -2
  37. package/dist/react/astro/MonitoringIcon.js +422 -358
  38. package/dist/react/astro/MonitoringIcon.js.map +1 -1
  39. package/dist/react/astro/Notification.d.ts +1 -2
  40. package/dist/react/astro/Notification.js +229 -126
  41. package/dist/react/astro/Notification.js.map +1 -1
  42. package/dist/react/astro/Progress.d.ts +0 -1
  43. package/dist/react/astro/Progress.js +199 -141
  44. package/dist/react/astro/Progress.js.map +1 -1
  45. package/dist/react/astro/SimulationControls.d.ts +3 -4
  46. package/dist/react/astro/SimulationControls.js +596 -650
  47. package/dist/react/astro/SimulationControls.js.map +1 -1
  48. package/dist/react/astro/StatusIndicator.d.ts +0 -1
  49. package/dist/react/astro/StatusIndicator.js +236 -170
  50. package/dist/react/astro/StatusIndicator.js.map +1 -1
  51. package/dist/react/astro/UnifiedTimeline.d.ts +9 -10
  52. package/dist/react/astro/UnifiedTimeline.js +2345 -2392
  53. package/dist/react/astro/UnifiedTimeline.js.map +1 -1
  54. package/dist/react/astro/index.d.ts +5 -5
  55. package/dist/react/astro/index.js +1 -25
  56. package/dist/react/cards/AccessCard.d.ts +0 -1
  57. package/dist/react/cards/AccessCard.js +471 -405
  58. package/dist/react/cards/AccessCard.js.map +1 -1
  59. package/dist/react/cards/OrbitCard.d.ts +0 -1
  60. package/dist/react/cards/OrbitCard.js +487 -364
  61. package/dist/react/cards/OrbitCard.js.map +1 -1
  62. package/dist/react/cards/SpacecraftCard.d.ts +0 -1
  63. package/dist/react/cards/SpacecraftCard.js +813 -925
  64. package/dist/react/cards/SpacecraftCard.js.map +1 -1
  65. package/dist/react/cards/TelemetryCard.d.ts +0 -1
  66. package/dist/react/cards/TelemetryCard.js +1055 -732
  67. package/dist/react/cards/TelemetryCard.js.map +1 -1
  68. package/dist/react/cards/TelemetryStreamCard.d.ts +0 -1
  69. package/dist/react/cards/TelemetryStreamCard.js +323 -297
  70. package/dist/react/cards/TelemetryStreamCard.js.map +1 -1
  71. package/dist/react/cards/index.d.ts +1 -1
  72. package/dist/react/cards/index.js +1 -8
  73. package/dist/react/charts/GroundTrackMap.d.ts +6 -7
  74. package/dist/react/charts/GroundTrackMap.js +1377 -1256
  75. package/dist/react/charts/GroundTrackMap.js.map +1 -1
  76. package/dist/react/charts/GroundTrackMapLeaflet.d.ts +1 -2
  77. package/dist/react/charts/GroundTrackMapLeaflet.js +1065 -987
  78. package/dist/react/charts/GroundTrackMapLeaflet.js.map +1 -1
  79. package/dist/react/charts/groundTrackMapLeafletTiles.js +34 -25
  80. package/dist/react/charts/groundTrackMapLeafletTiles.js.map +1 -1
  81. package/dist/react/charts/groundTrackMapLeafletUtils.js +70 -61
  82. package/dist/react/charts/groundTrackMapLeafletUtils.js.map +1 -1
  83. package/dist/react/charts/index.d.ts +1 -1
  84. package/dist/react/charts/index.js +7 -100
  85. package/dist/react/charts/unified/AstroChart.d.ts +1 -2
  86. package/dist/react/charts/unified/AstroChart.js +1331 -1411
  87. package/dist/react/charts/unified/AstroChart.js.map +1 -1
  88. package/dist/react/charts/unified/PowerOverviewChart.d.ts +3 -3
  89. package/dist/react/charts/unified/PowerOverviewChart.js +495 -478
  90. package/dist/react/charts/unified/PowerOverviewChart.js.map +1 -1
  91. package/dist/react/charts/unified/domain.d.ts +12 -13
  92. package/dist/react/charts/unified/domain.js +3999 -3145
  93. package/dist/react/charts/unified/domain.js.map +1 -1
  94. package/dist/react/charts/unified/generators.d.ts +3 -4
  95. package/dist/react/charts/unified/generators.js +561 -498
  96. package/dist/react/charts/unified/generators.js.map +1 -1
  97. package/dist/react/charts/unified/index.d.ts +1 -1
  98. package/dist/react/charts/unified/presets.d.ts +14 -15
  99. package/dist/react/charts/unified/presets.js +857 -994
  100. package/dist/react/charts/unified/presets.js.map +1 -1
  101. package/dist/react/charts/unified/sync.d.ts +2 -3
  102. package/dist/react/charts/unified/sync.js +229 -195
  103. package/dist/react/charts/unified/sync.js.map +1 -1
  104. package/dist/react/charts/unified/theme.d.ts +11 -12
  105. package/dist/react/charts/unified/theme.js +606 -642
  106. package/dist/react/charts/unified/theme.js.map +1 -1
  107. package/dist/react/charts/unified/types.d.ts +32 -33
  108. package/dist/react/charts/unified/useChartStream.d.ts +1 -2
  109. package/dist/react/charts/unified/useChartStream.js +203 -216
  110. package/dist/react/charts/unified/useChartStream.js.map +1 -1
  111. package/dist/react/chatgpt/AppCard.d.ts +1 -2
  112. package/dist/react/chatgpt/index.d.ts +7 -7
  113. package/dist/react/chatgpt/index.js +160 -139
  114. package/dist/react/chatgpt/index.js.map +1 -1
  115. package/dist/react/context/CategoryContext.d.ts +0 -1
  116. package/dist/react/context/CategoryContext.js +78 -29
  117. package/dist/react/context/CategoryContext.js.map +1 -1
  118. package/dist/react/context/DisplaySettingsContext.d.ts +1 -2
  119. package/dist/react/context/DisplaySettingsContext.js +205 -156
  120. package/dist/react/context/DisplaySettingsContext.js.map +1 -1
  121. package/dist/react/context/SpatialSelectionContext.d.ts +0 -1
  122. package/dist/react/context/SpatialSelectionContext.js +36 -7
  123. package/dist/react/context/SpatialSelectionContext.js.map +1 -1
  124. package/dist/react/core/data/DataTable.d.ts +3 -4
  125. package/dist/react/core/data/DataTable.js +473 -440
  126. package/dist/react/core/data/DataTable.js.map +1 -1
  127. package/dist/react/core/data/DataValue.d.ts +4 -5
  128. package/dist/react/core/data/DataValue.js +605 -571
  129. package/dist/react/core/data/DataValue.js.map +1 -1
  130. package/dist/react/core/data/propertyConfig.d.ts +1 -2
  131. package/dist/react/core/data/propertyConfig.js +446 -382
  132. package/dist/react/core/data/propertyConfig.js.map +1 -1
  133. package/dist/react/core/display/AstroIcon.d.ts +4 -5
  134. package/dist/react/core/display/AstroIcon.js +339 -429
  135. package/dist/react/core/display/AstroIcon.js.map +1 -1
  136. package/dist/react/core/display/Badge.d.ts +2 -3
  137. package/dist/react/core/display/Badge.js +131 -131
  138. package/dist/react/core/display/Badge.js.map +1 -1
  139. package/dist/react/core/display/CardHeader.d.ts +0 -1
  140. package/dist/react/core/display/CardHeader.js +398 -311
  141. package/dist/react/core/display/CardHeader.js.map +1 -1
  142. package/dist/react/core/display/Container.d.ts +2 -3
  143. package/dist/react/core/display/Container.js +640 -668
  144. package/dist/react/core/display/Container.js.map +1 -1
  145. package/dist/react/core/display/CopyButton.d.ts +1 -2
  146. package/dist/react/core/display/CopyButton.js +158 -101
  147. package/dist/react/core/display/CopyButton.js.map +1 -1
  148. package/dist/react/core/display/GlassCard.d.ts +3 -4
  149. package/dist/react/core/display/GlassCard.js +416 -369
  150. package/dist/react/core/display/GlassCard.js.map +1 -1
  151. package/dist/react/core/display/HeaderIconWithStatus.d.ts +0 -1
  152. package/dist/react/core/display/HeaderIconWithStatus.js +152 -152
  153. package/dist/react/core/display/HeaderIconWithStatus.js.map +1 -1
  154. package/dist/react/core/display/Icon.d.ts +2 -3
  155. package/dist/react/core/display/Icon.js +172 -143
  156. package/dist/react/core/display/Icon.js.map +1 -1
  157. package/dist/react/core/display/Typography.d.ts +24 -25
  158. package/dist/react/core/display/Typography.js +360 -178
  159. package/dist/react/core/display/Typography.js.map +1 -1
  160. package/dist/react/core/feedback/ConfirmDialog.d.ts +1 -2
  161. package/dist/react/core/feedback/ConfirmDialog.js +343 -304
  162. package/dist/react/core/feedback/ConfirmDialog.js.map +1 -1
  163. package/dist/react/core/feedback/Dialog.d.ts +2 -3
  164. package/dist/react/core/feedback/Dialog.js +189 -197
  165. package/dist/react/core/feedback/Dialog.js.map +1 -1
  166. package/dist/react/core/feedback/Toast.d.ts +2 -3
  167. package/dist/react/core/feedback/Toast.js +346 -207
  168. package/dist/react/core/feedback/Toast.js.map +1 -1
  169. package/dist/react/core/index.d.ts +26 -26
  170. package/dist/react/core/index.js +7 -119
  171. package/dist/react/core/inputs/Button.d.ts +2 -3
  172. package/dist/react/core/inputs/Button.js +281 -304
  173. package/dist/react/core/inputs/Button.js.map +1 -1
  174. package/dist/react/core/inputs/Checkbox.d.ts +2 -3
  175. package/dist/react/core/inputs/Checkbox.js +139 -127
  176. package/dist/react/core/inputs/Checkbox.js.map +1 -1
  177. package/dist/react/core/inputs/Input.d.ts +3 -4
  178. package/dist/react/core/inputs/Input.js +277 -285
  179. package/dist/react/core/inputs/Input.js.map +1 -1
  180. package/dist/react/core/inputs/LimitsBar.d.ts +1 -2
  181. package/dist/react/core/inputs/LimitsBar.js +271 -196
  182. package/dist/react/core/inputs/LimitsBar.js.map +1 -1
  183. package/dist/react/core/inputs/NumberInput.d.ts +2 -3
  184. package/dist/react/core/inputs/NumberInput.js +531 -503
  185. package/dist/react/core/inputs/NumberInput.js.map +1 -1
  186. package/dist/react/core/inputs/PinInput.d.ts +2 -3
  187. package/dist/react/core/inputs/PinInput.js +224 -207
  188. package/dist/react/core/inputs/PinInput.js.map +1 -1
  189. package/dist/react/core/inputs/Select.d.ts +1 -2
  190. package/dist/react/core/inputs/Select.js +298 -299
  191. package/dist/react/core/inputs/Select.js.map +1 -1
  192. package/dist/react/core/inputs/Toggle.d.ts +2 -3
  193. package/dist/react/core/inputs/Toggle.js +142 -148
  194. package/dist/react/core/inputs/Toggle.js.map +1 -1
  195. package/dist/react/core/layout/Box.d.ts +3 -4
  196. package/dist/react/core/layout/Box.js +118 -122
  197. package/dist/react/core/layout/Box.js.map +1 -1
  198. package/dist/react/core/layout/Center.d.ts +0 -1
  199. package/dist/react/core/layout/Center.js +45 -32
  200. package/dist/react/core/layout/Center.js.map +1 -1
  201. package/dist/react/core/layout/Divider.d.ts +1 -2
  202. package/dist/react/core/layout/Divider.js +112 -106
  203. package/dist/react/core/layout/Divider.js.map +1 -1
  204. package/dist/react/core/layout/Flex.d.ts +4 -5
  205. package/dist/react/core/layout/Flex.js +120 -124
  206. package/dist/react/core/layout/Flex.js.map +1 -1
  207. package/dist/react/core/layout/Grid.d.ts +5 -6
  208. package/dist/react/core/layout/Grid.js +131 -138
  209. package/dist/react/core/layout/Grid.js.map +1 -1
  210. package/dist/react/core/layout/Spacer.d.ts +0 -1
  211. package/dist/react/core/layout/Spacer.js +36 -29
  212. package/dist/react/core/layout/Spacer.js.map +1 -1
  213. package/dist/react/core/layout/Stack.d.ts +4 -5
  214. package/dist/react/core/layout/Stack.js +127 -70
  215. package/dist/react/core/layout/Stack.js.map +1 -1
  216. package/dist/react/core/layout/responsive.d.ts +1 -2
  217. package/dist/react/core/layout/responsive.js +25 -19
  218. package/dist/react/core/layout/responsive.js.map +1 -1
  219. package/dist/react/core/layout/useBreakpoint.d.ts +1 -1
  220. package/dist/react/core/layout/useBreakpoint.js +119 -66
  221. package/dist/react/core/layout/useBreakpoint.js.map +1 -1
  222. package/dist/react/core/navigation/AppBar.d.ts +1 -2
  223. package/dist/react/core/navigation/AppBar.js +827 -805
  224. package/dist/react/core/navigation/AppBar.js.map +1 -1
  225. package/dist/react/core/navigation/Pagination.d.ts +2 -3
  226. package/dist/react/core/navigation/Pagination.js +207 -187
  227. package/dist/react/core/navigation/Pagination.js.map +1 -1
  228. package/dist/react/core/navigation/SideNav.d.ts +5 -6
  229. package/dist/react/core/navigation/SideNav.js +855 -629
  230. package/dist/react/core/navigation/SideNav.js.map +1 -1
  231. package/dist/react/core/navigation/Tabs.d.ts +0 -1
  232. package/dist/react/core/navigation/Tabs.js +113 -119
  233. package/dist/react/core/navigation/Tabs.js.map +1 -1
  234. package/dist/react/core/overlays/Popover.d.ts +2 -3
  235. package/dist/react/core/overlays/Popover.js +303 -282
  236. package/dist/react/core/overlays/Popover.js.map +1 -1
  237. package/dist/react/core/overlays/SidePanel.d.ts +1 -2
  238. package/dist/react/core/overlays/SidePanel.js +217 -212
  239. package/dist/react/core/overlays/SidePanel.js.map +1 -1
  240. package/dist/react/core/overlays/Tooltip.d.ts +1 -2
  241. package/dist/react/core/overlays/Tooltip.js +170 -176
  242. package/dist/react/core/overlays/Tooltip.js.map +1 -1
  243. package/dist/react/core/widgets/ActivityPlanner.d.ts +0 -1
  244. package/dist/react/core/widgets/ActivityPlanner.js +855 -517
  245. package/dist/react/core/widgets/ActivityPlanner.js.map +1 -1
  246. package/dist/react/core/widgets/Capture.d.ts +2 -3
  247. package/dist/react/core/widgets/Capture.js +965 -779
  248. package/dist/react/core/widgets/Capture.js.map +1 -1
  249. package/dist/react/core/widgets/ChatPanel.d.ts +16 -17
  250. package/dist/react/core/widgets/ChatPanel.js +1734 -1034
  251. package/dist/react/core/widgets/ChatPanel.js.map +1 -1
  252. package/dist/react/core/widgets/ColorPickerPanel.d.ts +0 -1
  253. package/dist/react/core/widgets/ColorPickerPanel.js +298 -287
  254. package/dist/react/core/widgets/ColorPickerPanel.js.map +1 -1
  255. package/dist/react/core/widgets/CommandBuilder.d.ts +2 -3
  256. package/dist/react/core/widgets/CommandBuilder.js +624 -516
  257. package/dist/react/core/widgets/CommandBuilder.js.map +1 -1
  258. package/dist/react/core/widgets/ConnectionForm.d.ts +2 -3
  259. package/dist/react/core/widgets/ConnectionForm.js +528 -492
  260. package/dist/react/core/widgets/ConnectionForm.js.map +1 -1
  261. package/dist/react/core/widgets/FileExplorer.d.ts +3 -4
  262. package/dist/react/core/widgets/FileExplorer.js +617 -505
  263. package/dist/react/core/widgets/FileExplorer.js.map +1 -1
  264. package/dist/react/core/widgets/HexViewer.d.ts +2 -3
  265. package/dist/react/core/widgets/HexViewer.js +1353 -1091
  266. package/dist/react/core/widgets/HexViewer.js.map +1 -1
  267. package/dist/react/core/widgets/ImageFrame.d.ts +1 -2
  268. package/dist/react/core/widgets/ImageFrame.js +230 -193
  269. package/dist/react/core/widgets/ImageFrame.js.map +1 -1
  270. package/dist/react/core/widgets/ImageGallery.d.ts +2 -1
  271. package/dist/react/core/widgets/ImageGallery.js +380 -314
  272. package/dist/react/core/widgets/ImageGallery.js.map +1 -1
  273. package/dist/react/core/widgets/ImagePanel.d.ts +0 -1
  274. package/dist/react/core/widgets/ImagePanel.js +110 -101
  275. package/dist/react/core/widgets/ImagePanel.js.map +1 -1
  276. package/dist/react/core/widgets/LogViewer.d.ts +2 -3
  277. package/dist/react/core/widgets/LogViewer.js +711 -607
  278. package/dist/react/core/widgets/LogViewer.js.map +1 -1
  279. package/dist/react/core/widgets/MessageStream.d.ts +3 -4
  280. package/dist/react/core/widgets/MessageStream.js +514 -444
  281. package/dist/react/core/widgets/MessageStream.js.map +1 -1
  282. package/dist/react/core/widgets/MissionCalendar.d.ts +3 -4
  283. package/dist/react/core/widgets/MissionCalendar.js +1488 -1020
  284. package/dist/react/core/widgets/MissionCalendar.js.map +1 -1
  285. package/dist/react/core/widgets/PacketViewer.d.ts +2 -3
  286. package/dist/react/core/widgets/PacketViewer.js +550 -421
  287. package/dist/react/core/widgets/PacketViewer.js.map +1 -1
  288. package/dist/react/core/widgets/capture-placeholder.png.js +6 -5
  289. package/dist/react/core/widgets/capture-placeholder.png.js.map +1 -1
  290. package/dist/react/hooks/index.d.ts +5 -5
  291. package/dist/react/hooks/useAccessWindows.d.ts +0 -1
  292. package/dist/react/hooks/useCompactMode.d.ts +1 -1
  293. package/dist/react/hooks/useCompactMode.js +104 -58
  294. package/dist/react/hooks/useCompactMode.js.map +1 -1
  295. package/dist/react/hooks/useGroundTrackHistory.d.ts +0 -1
  296. package/dist/react/hooks/useSimulationScene.d.ts +1 -2
  297. package/dist/react/hooks/useSimulationScene.js +481 -374
  298. package/dist/react/hooks/useSimulationScene.js.map +1 -1
  299. package/dist/react/index.d.ts +3 -3
  300. package/dist/react/panels/LayerControlPanel.d.ts +1 -2
  301. package/dist/react/panels/LayerControlPanel.js +223 -180
  302. package/dist/react/panels/LayerControlPanel.js.map +1 -1
  303. package/dist/react/panels/ObjectInventoryPanel.d.ts +2 -3
  304. package/dist/react/panels/ObjectInventoryPanel.js +315 -250
  305. package/dist/react/panels/ObjectInventoryPanel.js.map +1 -1
  306. package/dist/react/panels/index.d.ts +1 -1
  307. package/dist/react/panels/index.js +1 -6
  308. package/dist/react/shared/ErrorBoundary.d.ts +1 -2
  309. package/dist/react/shared/ErrorBoundary.js +170 -139
  310. package/dist/react/shared/ErrorBoundary.js.map +1 -1
  311. package/dist/react/shared/Skeleton.d.ts +1 -2
  312. package/dist/react/shared/Skeleton.js +300 -301
  313. package/dist/react/shared/Skeleton.js.map +1 -1
  314. package/dist/react/theme/ThemeProvider.d.ts +2 -321
  315. package/dist/react/theme/ThemeProvider.js +312 -939
  316. package/dist/react/theme/ThemeProvider.js.map +1 -1
  317. package/dist/react/theme/cardAccent.d.ts +1 -2
  318. package/dist/react/theme/cardAccent.js +196 -126
  319. package/dist/react/theme/cardAccent.js.map +1 -1
  320. package/dist/react/theme/config.d.ts +0 -1
  321. package/dist/react/theme/config.js +47 -25
  322. package/dist/react/theme/config.js.map +1 -1
  323. package/dist/react/theme/index.d.ts +2 -2
  324. package/dist/react/theme/index.js +1 -16
  325. package/dist/react/theme/types.d.ts +332 -0
  326. package/dist/react/theme/types.js +39 -0
  327. package/dist/react/theme/types.js.map +1 -0
  328. package/dist/react/types.d.ts +8 -8
  329. package/dist/react/types.js +147 -47
  330. package/dist/react/types.js.map +1 -1
  331. package/dist/react/utils/categoryPalette.d.ts +0 -1
  332. package/dist/react/utils/categoryPalette.js +91 -102
  333. package/dist/react/utils/categoryPalette.js.map +1 -1
  334. package/dist/react/utils/index.d.ts +5 -5
  335. package/dist/react/utils/index.js +457 -371
  336. package/dist/react/utils/index.js.map +1 -1
  337. package/dist/react/visualizations/EclipseTimerCard.d.ts +0 -1
  338. package/dist/react/visualizations/LinkBudgetCard.d.ts +0 -1
  339. package/dist/react/visualizations/NavBallCard.d.ts +0 -1
  340. package/dist/react/visualizations/PropulsionCard.d.ts +1 -2
  341. package/dist/react/visualizations/SensorFootprintCard.d.ts +1 -2
  342. package/dist/react/visualizations/ThermalHeatmapCard.d.ts +0 -1
  343. package/dist/react/visualizations/index.d.ts +3 -3
  344. package/dist/react.d.ts +2 -1
  345. package/dist/react.js +17 -221
  346. package/dist/shaders/atmosphere.frag.js +6 -5
  347. package/dist/shaders/atmosphere.frag.js.map +1 -1
  348. package/dist/shaders/atmosphere.vert.js +6 -5
  349. package/dist/shaders/atmosphere.vert.js.map +1 -1
  350. package/dist/shaders/stars.frag.js +6 -5
  351. package/dist/shaders/stars.frag.js.map +1 -1
  352. package/dist/shaders/stars.vert.js +6 -5
  353. package/dist/shaders/stars.vert.js.map +1 -1
  354. package/dist/style.css +217 -194
  355. package/dist/tokens/core.d.ts +159 -0
  356. package/dist/tokens/core.js +144 -0
  357. package/dist/tokens/core.js.map +1 -0
  358. package/dist/tokens/css-vars.d.ts +213 -2
  359. package/dist/tokens/css-vars.js +590 -227
  360. package/dist/tokens/css-vars.js.map +1 -1
  361. package/dist/tokens/index.d.ts +31 -228
  362. package/dist/tokens/index.js +281 -366
  363. package/dist/tokens/index.js.map +1 -1
  364. package/dist/tokens/palettes/astro.d.ts +129 -0
  365. package/dist/tokens/palettes/astro.js +112 -0
  366. package/dist/tokens/palettes/astro.js.map +1 -0
  367. package/dist/tokens/palettes/index.d.ts +288 -0
  368. package/dist/tokens/palettes/index.js +35 -0
  369. package/dist/tokens/palettes/index.js.map +1 -0
  370. package/dist/tokens/palettes/tailwind.d.ts +324 -0
  371. package/dist/tokens/palettes/tailwind.js +201 -0
  372. package/dist/tokens/palettes/tailwind.js.map +1 -0
  373. package/dist/tokens/palettes/zendir.d.ts +126 -0
  374. package/dist/tokens/palettes/zendir.js +92 -0
  375. package/dist/tokens/palettes/zendir.js.map +1 -0
  376. package/dist/tokens/themes.d.ts +30 -0
  377. package/dist/tokens/themes.js +704 -0
  378. package/dist/tokens/themes.js.map +1 -0
  379. package/dist/tokens/tokens.css +419 -41
  380. package/dist/types/index.d.ts +2 -2
  381. package/package.json +310 -313
  382. package/dist/index.js.map +0 -1
  383. package/dist/react/3d/index.js.map +0 -1
  384. package/dist/react/astro/index.js.map +0 -1
  385. package/dist/react/cards/index.js.map +0 -1
  386. package/dist/react/charts/index.js.map +0 -1
  387. package/dist/react/core/index.js.map +0 -1
  388. package/dist/react/panels/index.js.map +0 -1
  389. package/dist/react/theme/astro-tokens.d.ts +0 -153
  390. package/dist/react/theme/index.js.map +0 -1
  391. package/dist/react.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"ImageGallery.js","sources":["../../../../src/react/core/widgets/ImageGallery.tsx"],"sourcesContent":["/**\r\n * @zendir/ui - ImageGallery Component\r\n * \r\n * Grid of image thumbnails with lightbox, download, delete, and pagination.\r\n * Designed for spacecraft image capture galleries in operator dashboards.\r\n * \r\n * Astro UX Compliance:\r\n * - Consistent card styling with GlassCard\r\n * - Status-based border colors\r\n * - Keyboard navigation (Escape to close lightbox)\r\n * - Focus management\r\n * \r\n * @example\r\n * ```tsx\r\n * <ImageGallery\r\n * images={[\r\n * { id: '1', name: 'capture_001.png', src: dataUrl, timestamp: Date.now() },\r\n * { id: '2', name: 'capture_002.png', src: dataUrl, timestamp: Date.now() },\r\n * ]}\r\n * columns={[2, 3, 4]}\r\n * onDownload={(img) => saveAs(img.src, img.name)}\r\n * onDelete={(img) => removeImage(img.id)}\r\n * pageSize={12}\r\n * />\r\n * ```\r\n */\r\n\r\nimport React, { memo, useState, useCallback, useEffect } from 'react';\r\nimport { useTheme } from '../../theme';\r\nimport { useBreakpoint } from '../layout/useBreakpoint';\r\nimport { resolveResponsiveValue } from '../layout/useBreakpoint';\r\nimport type { ResponsiveValue } from '../layout/useBreakpoint';\r\nimport { Pagination } from '../navigation/Pagination';\r\n\r\n// ─── Types ───────────────────────────────────────────────────────────────────\r\n\r\nexport interface GalleryImage {\r\n /** Unique identifier */\r\n id: string;\r\n /** Display name */\r\n name: string;\r\n /** Image source URL or data URL */\r\n src: string;\r\n /** Timestamp (ms since epoch) */\r\n timestamp?: number;\r\n /** Optional metadata */\r\n metadata?: Record<string, unknown>;\r\n}\r\n\r\nexport interface ImageGalleryProps {\r\n /** Array of images */\r\n images: GalleryImage[];\r\n /** Number of columns (responsive) */\r\n columns?: ResponsiveValue<number>;\r\n /** Images per page (0 = no pagination) */\r\n pageSize?: number;\r\n /** Download handler */\r\n onDownload?: (image: GalleryImage) => void;\r\n /** Delete handler (shows delete button when provided) */\r\n onDelete?: (image: GalleryImage) => void;\r\n /** Click handler */\r\n onClick?: (image: GalleryImage) => void;\r\n /** Show lightbox on click (default true) */\r\n lightbox?: boolean;\r\n /** Thumbnail height */\r\n thumbnailHeight?: number;\r\n /** Empty state message */\r\n emptyMessage?: string;\r\n /** New image badge count (shows badge on component) */\r\n newCount?: number;\r\n /** Custom style */\r\n style?: React.CSSProperties;\r\n}\r\n\r\nexport const ImageGallery = memo(function ImageGallery({\r\n images,\r\n columns = [2, 3, 4],\r\n pageSize = 12,\r\n onDownload,\r\n onDelete,\r\n onClick,\r\n lightbox = true,\r\n thumbnailHeight = 140,\r\n emptyMessage = 'No images captured',\r\n newCount: _newCount,\r\n style,\r\n}: ImageGalleryProps): React.ReactElement {\r\n const { tokens } = useTheme();\r\n const { breakpoint } = useBreakpoint();\r\n const [currentPage, setCurrentPage] = useState(0);\r\n const [selectedImage, setSelectedImage] = useState<GalleryImage | null>(null);\r\n const [hoveredId, setHoveredId] = useState<string | null>(null);\r\n \r\n const cols = Array.isArray(columns) ? resolveResponsiveValue(columns, breakpoint) : columns;\r\n const totalPages = pageSize > 0 ? Math.ceil(images.length / pageSize) : 1;\r\n const pageImages = pageSize > 0\r\n ? images.slice(currentPage * pageSize, (currentPage + 1) * pageSize)\r\n : images;\r\n \r\n // Reset page if images change\r\n useEffect(() => {\r\n if (currentPage >= totalPages && totalPages > 0) {\r\n setCurrentPage(totalPages - 1);\r\n }\r\n }, [images.length, totalPages, currentPage]);\r\n \r\n // Lightbox keyboard\r\n useEffect(() => {\r\n if (!selectedImage) return;\r\n const handler = (e: KeyboardEvent) => {\r\n if (e.key === 'Escape') setSelectedImage(null);\r\n };\r\n document.addEventListener('keydown', handler);\r\n return () => document.removeEventListener('keydown', handler);\r\n }, [selectedImage]);\r\n \r\n const handleImageClick = useCallback((img: GalleryImage) => {\r\n onClick?.(img);\r\n if (lightbox) setSelectedImage(img);\r\n }, [onClick, lightbox]);\r\n \r\n const formatTime = (ts?: number) => {\r\n if (!ts) return '';\r\n const d = new Date(ts);\r\n return d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' });\r\n };\r\n \r\n // Action button helper\r\n const ActionBtn = ({ icon, label, onClick: handler, color }: { icon: React.ReactNode; label: string; onClick: () => void; color?: string }) => (\r\n <button\r\n aria-label={label}\r\n onClick={(e) => { e.stopPropagation(); handler(); }}\r\n style={{\r\n display: 'flex', alignItems: 'center', justifyContent: 'center',\r\n width: 28, height: 28,\r\n border: `1px solid ${tokens.colors.border.muted}`,\r\n borderRadius: tokens.borderRadius.sm,\r\n backgroundColor: `${tokens.colors.background.elevated}e0`,\r\n color: color || tokens.colors.text.secondary,\r\n cursor: 'pointer', transition: tokens.animation.fast,\r\n backdropFilter: 'blur(4px)',\r\n }}\r\n >\r\n {icon}\r\n </button>\r\n );\r\n \r\n // Download icon\r\n const downloadIcon = <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\"><path d=\"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4\"/><polyline points=\"7 10 12 15 17 10\"/><line x1=\"12\" y1=\"15\" x2=\"12\" y2=\"3\"/></svg>;\r\n // Delete icon\r\n const deleteIcon = <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\"><polyline points=\"3 6 5 6 21 6\"/><path d=\"M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2\"/></svg>;\r\n \r\n return (\r\n <div style={{ fontFamily: tokens.typography.fontFamily.primary, ...style }}>\r\n {/* Empty state */}\r\n {images.length === 0 && (\r\n <div style={{\r\n display: 'flex', alignItems: 'center', justifyContent: 'center',\r\n height: 200, color: tokens.colors.text.tertiary,\r\n fontSize: tokens.typography.fontSize.sm,\r\n border: `1px dashed ${tokens.colors.border.muted}`,\r\n borderRadius: tokens.borderRadius.md,\r\n }}>\r\n {emptyMessage}\r\n </div>\r\n )}\r\n \r\n {/* Grid */}\r\n {pageImages.length > 0 && (\r\n <div style={{\r\n display: 'grid',\r\n gridTemplateColumns: `repeat(${cols}, 1fr)`,\r\n gap: tokens.spacing.sm,\r\n }}>\r\n {pageImages.map(img => {\r\n const isHovered = hoveredId === img.id;\r\n return (\r\n <div\r\n key={img.id}\r\n role=\"button\"\r\n tabIndex={0}\r\n aria-label={`View image ${img.name || img.id}`}\r\n onClick={() => handleImageClick(img)}\r\n onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); handleImageClick(img); } }}\r\n onMouseEnter={() => setHoveredId(img.id)}\r\n onMouseLeave={() => setHoveredId(null)}\r\n style={{\r\n position: 'relative',\r\n borderRadius: tokens.borderRadius.md,\r\n border: `1px solid ${tokens.colors.border.muted}`,\r\n overflow: 'hidden',\r\n cursor: 'pointer',\r\n transition: tokens.animation.fast,\r\n transform: isHovered ? 'translateY(-2px)' : 'none',\r\n boxShadow: isHovered ? tokens.shadows.md : 'none',\r\n backgroundColor: tokens.colors.background.surface,\r\n }}\r\n >\r\n {/* Thumbnail */}\r\n <div style={{\r\n height: thumbnailHeight,\r\n display: 'flex', alignItems: 'center', justifyContent: 'center',\r\n overflow: 'hidden',\r\n backgroundColor: tokens.colors.background.base,\r\n }}>\r\n <img\r\n src={img.src}\r\n alt={img.name}\r\n loading=\"lazy\"\r\n onError={(e) => {\r\n const target = e.target as HTMLImageElement;\r\n target.style.display = 'none';\r\n target.parentElement!.innerHTML = `<span style=\"color:${tokens.colors.text.tertiary};font-size:${tokens.typography.fontSize.xxs}\">Image unavailable</span>`;\r\n }}\r\n style={{\r\n maxWidth: '100%', maxHeight: '100%',\r\n objectFit: 'contain',\r\n }}\r\n />\r\n </div>\r\n \r\n {/* Info bar */}\r\n <div style={{\r\n padding: `${tokens.spacing.xs} ${tokens.spacing.sm}`,\r\n borderTop: `1px solid ${tokens.colors.border.muted}`,\r\n }}>\r\n <div style={{\r\n fontSize: tokens.typography.fontSize.xxs,\r\n fontWeight: tokens.typography.fontWeight.medium,\r\n color: tokens.colors.text.primary,\r\n whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis',\r\n }}>\r\n {img.name}\r\n </div>\r\n {img.timestamp && (\r\n <div style={{\r\n fontSize: tokens.typography.fontSize.micro,\r\n color: tokens.colors.text.tertiary,\r\n fontFamily: tokens.typography.fontFamily.mono,\r\n }}>\r\n {formatTime(img.timestamp)}\r\n </div>\r\n )}\r\n </div>\r\n \r\n {/* Hover actions */}\r\n {isHovered && (\r\n <div style={{\r\n position: 'absolute', top: tokens.spacing.xs, right: tokens.spacing.xs,\r\n display: 'flex', gap: '4px',\r\n }}>\r\n {onDownload && (\r\n <ActionBtn icon={downloadIcon} label=\"Download\" onClick={() => onDownload(img)} />\r\n )}\r\n {onDelete && (\r\n <ActionBtn icon={deleteIcon} label=\"Delete\" onClick={() => onDelete(img)} color={tokens.colors.status.critical} />\r\n )}\r\n </div>\r\n )}\r\n </div>\r\n );\r\n })}\r\n </div>\r\n )}\r\n \r\n {/* Pagination */}\r\n {totalPages > 1 && (\r\n <div style={{\r\n display: 'flex',\r\n justifyContent: 'center',\r\n alignItems: 'center',\r\n marginTop: tokens.spacing.md,\r\n }}>\r\n <Pagination\r\n currentPage={currentPage + 1}\r\n totalPages={totalPages}\r\n onChange={(page) => setCurrentPage(Math.max(0, page - 1))}\r\n showFirstLast={false}\r\n displayMode=\"text\"\r\n size=\"small\"\r\n />\r\n </div>\r\n )}\r\n \r\n {/* Lightbox */}\r\n {selectedImage && (\r\n <>\r\n <div\r\n aria-hidden=\"true\"\r\n onClick={() => setSelectedImage(null)}\r\n style={{\r\n position: 'fixed', inset: 0, zIndex: 9998,\r\n backgroundColor: `${tokens.colors.background.base}e0`,\r\n backdropFilter: 'blur(8px)',\r\n animation: `zendir-lb-fade 200ms ${tokens.animation.easing.default}`,\r\n }}\r\n />\r\n <div\r\n role=\"dialog\"\r\n aria-modal=\"true\"\r\n aria-label={`Image: ${selectedImage.name}`}\r\n onClick={() => setSelectedImage(null)}\r\n style={{\r\n position: 'fixed', inset: 0, zIndex: 9999,\r\n display: 'flex', flexDirection: 'column',\r\n alignItems: 'center', justifyContent: 'center',\r\n padding: tokens.spacing.xl,\r\n }}\r\n >\r\n <img\r\n src={selectedImage.src}\r\n alt={selectedImage.name}\r\n onClick={e => e.stopPropagation()}\r\n style={{\r\n maxWidth: '90vw', maxHeight: '80vh',\r\n objectFit: 'contain',\r\n borderRadius: tokens.borderRadius.md,\r\n boxShadow: tokens.shadows.xl,\r\n animation: `zendir-lb-scale 250ms ${tokens.animation.easing.default}`,\r\n }}\r\n />\r\n <div style={{\r\n display: 'flex', alignItems: 'center', gap: tokens.spacing.md,\r\n marginTop: tokens.spacing.md,\r\n }}\r\n onClick={e => e.stopPropagation()}\r\n >\r\n <span style={{\r\n color: tokens.colors.text.primary,\r\n fontSize: tokens.typography.fontSize.sm,\r\n fontWeight: tokens.typography.fontWeight.medium,\r\n }}>\r\n {selectedImage.name}\r\n </span>\r\n {onDownload && (\r\n <button\r\n onClick={() => onDownload(selectedImage)}\r\n style={{\r\n padding: '6px 16px', fontSize: tokens.typography.fontSize.xs,\r\n color: tokens.colors.text.primary, backgroundColor: `${tokens.colors.accent.primary}20`,\r\n border: `1px solid ${tokens.colors.accent.primary}40`,\r\n borderRadius: tokens.borderRadius.md, cursor: 'pointer',\r\n fontFamily: tokens.typography.fontFamily.primary,\r\n }}\r\n >\r\n Download\r\n </button>\r\n )}\r\n {onDelete && (\r\n <button\r\n onClick={() => { onDelete(selectedImage); setSelectedImage(null); }}\r\n style={{\r\n padding: '6px 16px', fontSize: tokens.typography.fontSize.xs,\r\n color: tokens.colors.status.critical, backgroundColor: `${tokens.colors.status.critical}15`,\r\n border: `1px solid ${tokens.colors.status.critical}30`,\r\n borderRadius: tokens.borderRadius.md, cursor: 'pointer',\r\n fontFamily: tokens.typography.fontFamily.primary,\r\n }}\r\n >\r\n Delete\r\n </button>\r\n )}\r\n </div>\r\n </div>\r\n <style>{`\r\n @keyframes zendir-lb-fade { from { opacity: 0; } to { opacity: 1; } }\r\n @keyframes zendir-lb-scale { from { opacity: 0; transform: scale(0.9); } to { opacity: 1; transform: scale(1); } }\r\n `}</style>\r\n </>\r\n )}\r\n </div>\r\n );\r\n});\r\n\r\nexport default ImageGallery;\r\n"],"names":["ImageGallery"],"mappings":"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"}
1
+ {"version":3,"file":"ImageGallery.js","names":[],"sources":["../../../../src/react/core/widgets/ImageGallery.tsx"],"sourcesContent":["/**\n * @zendir/ui - ImageGallery Component\n *\n * Grid of image thumbnails with lightbox, download, delete, and pagination.\n * Designed for spacecraft image capture galleries in operator dashboards.\n *\n * Astro UX Compliance:\n * - Consistent card styling with GlassCard\n * - Status-based border colors\n * - Keyboard navigation (Escape to close lightbox)\n * - Focus management\n *\n * @example\n * ```tsx\n * <ImageGallery\n * images={[\n * { id: '1', name: 'capture_001.png', src: dataUrl, timestamp: Date.now() },\n * { id: '2', name: 'capture_002.png', src: dataUrl, timestamp: Date.now() },\n * ]}\n * columns={[2, 3, 4]}\n * onDownload={(img) => saveAs(img.src, img.name)}\n * onDelete={(img) => removeImage(img.id)}\n * pageSize={12}\n * />\n * ```\n */\n\nimport React, { memo, useState, useCallback, useEffect } from \"react\";\nimport { useTheme } from \"../../theme\";\nimport { useBreakpoint } from \"../layout/useBreakpoint\";\nimport { resolveResponsiveValue } from \"../layout/useBreakpoint\";\nimport type { ResponsiveValue } from \"../layout/useBreakpoint\";\nimport { Pagination } from \"../navigation/Pagination\";\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\nexport interface GalleryImage {\n /** Unique identifier */\n id: string;\n /** Display name */\n name: string;\n /** Image source URL or data URL */\n src: string;\n /** Timestamp (ms since epoch) */\n timestamp?: number;\n /** Optional short label shown as a pill over the thumbnail (e.g. \"NEW\"). */\n badge?: string;\n /** Optional metadata */\n metadata?: Record<string, unknown>;\n}\n\nexport interface ImageGalleryProps {\n /** Array of images */\n images: GalleryImage[];\n /** Number of columns (responsive) */\n columns?: ResponsiveValue<number>;\n /** Images per page (0 = no pagination) */\n pageSize?: number;\n /** Download handler */\n onDownload?: (image: GalleryImage) => void;\n /** Delete handler (shows delete button when provided) */\n onDelete?: (image: GalleryImage) => void;\n /** Click handler */\n onClick?: (image: GalleryImage) => void;\n /** Show lightbox on click (default true) */\n lightbox?: boolean;\n /** Thumbnail height */\n thumbnailHeight?: number;\n /** Empty state message */\n emptyMessage?: string;\n /** New image badge count (shows badge on component) */\n newCount?: number;\n /** Custom style */\n style?: React.CSSProperties;\n}\n\nexport const ImageGallery = memo(function ImageGallery({\n images,\n columns = [2, 3, 4],\n pageSize = 12,\n onDownload,\n onDelete,\n onClick,\n lightbox = true,\n thumbnailHeight = 140,\n emptyMessage = \"No images captured\",\n newCount: _newCount,\n style,\n}: ImageGalleryProps): React.ReactElement {\n const { tokens } = useTheme();\n const { breakpoint } = useBreakpoint();\n const [currentPage, setCurrentPage] = useState(0);\n const [selectedImage, setSelectedImage] = useState<GalleryImage | null>(null);\n const [hoveredId, setHoveredId] = useState<string | null>(null);\n\n const cols = Array.isArray(columns)\n ? resolveResponsiveValue(columns, breakpoint)\n : columns;\n const totalPages = pageSize > 0 ? Math.ceil(images.length / pageSize) : 1;\n const pageImages =\n pageSize > 0\n ? images.slice(currentPage * pageSize, (currentPage + 1) * pageSize)\n : images;\n\n // Reset page if images change\n useEffect(() => {\n if (currentPage >= totalPages && totalPages > 0) {\n setCurrentPage(totalPages - 1);\n }\n }, [images.length, totalPages, currentPage]);\n\n // Lightbox keyboard\n useEffect(() => {\n if (!selectedImage) return;\n const handler = (e: KeyboardEvent) => {\n if (e.key === \"Escape\") setSelectedImage(null);\n };\n document.addEventListener(\"keydown\", handler);\n return () => document.removeEventListener(\"keydown\", handler);\n }, [selectedImage]);\n\n const handleImageClick = useCallback(\n (img: GalleryImage) => {\n onClick?.(img);\n if (lightbox) setSelectedImage(img);\n },\n [onClick, lightbox],\n );\n\n const formatTime = (ts?: number) => {\n if (!ts) return \"\";\n const d = new Date(ts);\n return d.toLocaleTimeString([], {\n hour: \"2-digit\",\n minute: \"2-digit\",\n second: \"2-digit\",\n });\n };\n\n // Action button helper\n const ActionBtn = ({\n icon,\n label,\n onClick: handler,\n color,\n }: {\n icon: React.ReactNode;\n label: string;\n onClick: () => void;\n color?: string;\n }) => (\n <button\n aria-label={label}\n onClick={(e) => {\n e.stopPropagation();\n handler();\n }}\n style={{\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n width: 28,\n height: 28,\n border: `1px solid ${tokens.colors.border.muted}`,\n borderRadius: tokens.borderRadius.sm,\n backgroundColor: `${tokens.colors.background.elevated}e0`,\n color: color || tokens.colors.text.secondary,\n cursor: \"pointer\",\n transition: tokens.animation.fast,\n backdropFilter: \"blur(4px)\",\n }}\n >\n {icon}\n </button>\n );\n\n // Download icon\n const downloadIcon = (\n <svg\n width=\"14\"\n height=\"14\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n >\n <path d=\"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4\" />\n <polyline points=\"7 10 12 15 17 10\" />\n <line x1=\"12\" y1=\"15\" x2=\"12\" y2=\"3\" />\n </svg>\n );\n // Delete icon\n const deleteIcon = (\n <svg\n width=\"14\"\n height=\"14\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n >\n <polyline points=\"3 6 5 6 21 6\" />\n <path d=\"M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2\" />\n </svg>\n );\n\n return (\n <div style={{ fontFamily: tokens.typography.fontFamily.primary, ...style }}>\n {/* Empty state */}\n {images.length === 0 && (\n <div\n style={{\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n height: 200,\n color: tokens.colors.text.tertiary,\n fontSize: tokens.typography.fontSize.sm,\n border: `1px dashed ${tokens.colors.border.muted}`,\n borderRadius: tokens.borderRadius.md,\n }}\n >\n {emptyMessage}\n </div>\n )}\n\n {/* Grid */}\n {pageImages.length > 0 && (\n <div\n style={{\n display: \"grid\",\n gridTemplateColumns: `repeat(${cols}, 1fr)`,\n gap: tokens.spacing.sm,\n }}\n >\n {pageImages.map((img) => {\n const isHovered = hoveredId === img.id;\n return (\n <div\n key={img.id}\n role=\"button\"\n tabIndex={0}\n aria-label={`View image ${img.name || img.id}`}\n onClick={() => handleImageClick(img)}\n onKeyDown={(e) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n handleImageClick(img);\n }\n }}\n onMouseEnter={() => setHoveredId(img.id)}\n onMouseLeave={() => setHoveredId(null)}\n style={{\n position: \"relative\",\n borderRadius: tokens.borderRadius.md,\n border: `1px solid ${tokens.colors.border.muted}`,\n overflow: \"hidden\",\n cursor: \"pointer\",\n transition: tokens.animation.fast,\n transform: isHovered ? \"translateY(-2px)\" : \"none\",\n boxShadow: isHovered ? tokens.shadows.md : \"none\",\n backgroundColor: tokens.colors.background.surface,\n }}\n >\n {/* Per-image badge (e.g. newest capture) — overlays the thumbnail */}\n {img.badge ? (\n <span\n style={{\n position: \"absolute\",\n top: tokens.spacing.xs,\n left: tokens.spacing.xs,\n zIndex: 1,\n padding: `2px ${tokens.spacing.xs}`,\n borderRadius: tokens.borderRadius.sm,\n backgroundColor: tokens.colors.accent.primary,\n color: \"#fff\",\n fontSize: tokens.typography.fontSize.micro,\n fontWeight: tokens.typography.fontWeight.bold,\n letterSpacing: \"0.06em\",\n textTransform: \"uppercase\",\n lineHeight: 1.4,\n pointerEvents: \"none\",\n boxShadow: tokens.shadows.sm,\n }}\n >\n {img.badge}\n </span>\n ) : null}\n\n {/* Thumbnail */}\n <div\n style={{\n height: thumbnailHeight,\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n overflow: \"hidden\",\n backgroundColor: tokens.colors.background.base,\n }}\n >\n <img\n src={img.src}\n alt={img.name}\n loading=\"lazy\"\n onError={(e) => {\n const target = e.target as HTMLImageElement;\n target.style.display = \"none\";\n target.parentElement!.innerHTML = `<span style=\"color:${tokens.colors.text.tertiary};font-size:${tokens.typography.fontSize.xxs}\">Image unavailable</span>`;\n }}\n style={{\n maxWidth: \"100%\",\n maxHeight: \"100%\",\n objectFit: \"contain\",\n }}\n />\n </div>\n\n {/* Info bar */}\n <div\n style={{\n padding: `${tokens.spacing.xs} ${tokens.spacing.sm}`,\n borderTop: `1px solid ${tokens.colors.border.muted}`,\n }}\n >\n <div\n style={{\n fontSize: tokens.typography.fontSize.xxs,\n fontWeight: tokens.typography.fontWeight.medium,\n color: tokens.colors.text.primary,\n whiteSpace: \"nowrap\",\n overflow: \"hidden\",\n textOverflow: \"ellipsis\",\n }}\n >\n {img.name}\n </div>\n {img.timestamp && (\n <div\n style={{\n fontSize: tokens.typography.fontSize.micro,\n color: tokens.colors.text.tertiary,\n fontFamily: tokens.typography.fontFamily.mono,\n }}\n >\n {formatTime(img.timestamp)}\n </div>\n )}\n </div>\n\n {/* Hover actions */}\n {isHovered && (\n <div\n style={{\n position: \"absolute\",\n top: tokens.spacing.xs,\n right: tokens.spacing.xs,\n display: \"flex\",\n gap: \"4px\",\n }}\n >\n {onDownload && (\n <ActionBtn\n icon={downloadIcon}\n label=\"Download\"\n onClick={() => onDownload(img)}\n />\n )}\n {onDelete && (\n <ActionBtn\n icon={deleteIcon}\n label=\"Delete\"\n onClick={() => onDelete(img)}\n color={tokens.colors.status.critical}\n />\n )}\n </div>\n )}\n </div>\n );\n })}\n </div>\n )}\n\n {/* Pagination */}\n {totalPages > 1 && (\n <div\n style={{\n display: \"flex\",\n justifyContent: \"center\",\n alignItems: \"center\",\n marginTop: tokens.spacing.md,\n }}\n >\n <Pagination\n currentPage={currentPage + 1}\n totalPages={totalPages}\n onChange={(page) => setCurrentPage(Math.max(0, page - 1))}\n showFirstLast={false}\n displayMode=\"text\"\n size=\"small\"\n />\n </div>\n )}\n\n {/* Lightbox */}\n {selectedImage && (\n <>\n <div\n aria-hidden=\"true\"\n onClick={() => setSelectedImage(null)}\n style={{\n position: \"fixed\",\n inset: 0,\n zIndex: 9998,\n backgroundColor: `${tokens.colors.background.base}e0`,\n backdropFilter: \"blur(8px)\",\n animation: `zendir-lb-fade 200ms ${tokens.animation.easing.default}`,\n }}\n />\n <div\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label={`Image: ${selectedImage.name}`}\n onClick={() => setSelectedImage(null)}\n style={{\n position: \"fixed\",\n inset: 0,\n zIndex: 9999,\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n justifyContent: \"center\",\n padding: tokens.spacing.xl,\n }}\n >\n <img\n src={selectedImage.src}\n alt={selectedImage.name}\n onClick={(e) => e.stopPropagation()}\n style={{\n maxWidth: \"90vw\",\n maxHeight: \"80vh\",\n objectFit: \"contain\",\n borderRadius: tokens.borderRadius.md,\n boxShadow: tokens.shadows.xl,\n animation: `zendir-lb-scale 250ms ${tokens.animation.easing.default}`,\n }}\n />\n <div\n style={{\n display: \"flex\",\n alignItems: \"center\",\n gap: tokens.spacing.md,\n marginTop: tokens.spacing.md,\n }}\n onClick={(e) => e.stopPropagation()}\n >\n <span\n style={{\n color: tokens.colors.text.primary,\n fontSize: tokens.typography.fontSize.sm,\n fontWeight: tokens.typography.fontWeight.medium,\n }}\n >\n {selectedImage.name}\n </span>\n {onDownload && (\n <button\n onClick={() => onDownload(selectedImage)}\n style={{\n padding: \"6px 16px\",\n fontSize: tokens.typography.fontSize.xs,\n color: tokens.colors.text.primary,\n backgroundColor: `${tokens.colors.accent.primary}20`,\n border: `1px solid ${tokens.colors.accent.primary}40`,\n borderRadius: tokens.borderRadius.md,\n cursor: \"pointer\",\n fontFamily: tokens.typography.fontFamily.primary,\n }}\n >\n Download\n </button>\n )}\n {onDelete && (\n <button\n onClick={() => {\n onDelete(selectedImage);\n setSelectedImage(null);\n }}\n style={{\n padding: \"6px 16px\",\n fontSize: tokens.typography.fontSize.xs,\n color: tokens.colors.status.critical,\n backgroundColor: `${tokens.colors.status.critical}15`,\n border: `1px solid ${tokens.colors.status.critical}30`,\n borderRadius: tokens.borderRadius.md,\n cursor: \"pointer\",\n fontFamily: tokens.typography.fontFamily.primary,\n }}\n >\n Delete\n </button>\n )}\n </div>\n </div>\n <style>{`\n @keyframes zendir-lb-fade { from { opacity: 0; } to { opacity: 1; } }\n @keyframes zendir-lb-scale { from { opacity: 0; transform: scale(0.9); } to { opacity: 1; transform: scale(1); } }\n `}</style>\n </>\n )}\n </div>\n );\n});\n\nexport default ImageGallery;\n"],"mappings":"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"}
@@ -1,6 +1,5 @@
1
1
  import { default as React } from 'react';
2
2
  import { ImageColormap } from './ImageFrame';
3
-
4
3
  export interface ImagePanelProps {
5
4
  /** Flat, row-major single-channel samples. Length must equal width*height. */
6
5
  data?: number[];
@@ -1,105 +1,114 @@
1
- import { jsxs, jsx } from "react/jsx-runtime";
2
- import { memo } from "react";
3
1
  import { useTheme } from "../../theme/ThemeProvider.js";
4
2
  import { classNames } from "../../utils/index.js";
5
3
  import { ImageFrame } from "./ImageFrame.js";
6
- const ImagePanel = memo(function ImagePanel2({
7
- // frame
8
- data,
9
- pixels,
10
- width,
11
- height,
12
- maxADU,
13
- colormap,
14
- showValues,
15
- emptyMessage,
16
- // chrome
17
- title,
18
- loading = false,
19
- error = null,
20
- onRefresh,
21
- refreshLabel = "Refresh",
22
- refreshingLabel = "Capturing…",
23
- refreshDisabled = false,
24
- className = ""
25
- }) {
26
- const { tokens } = useTheme();
27
- const buttonDisabled = loading || refreshDisabled;
28
- const hasFrame = Array.isArray(data) || pixels instanceof Uint8ClampedArray;
29
- const frameLoading = loading && !hasFrame;
30
- return /* @__PURE__ */ jsxs(
31
- "div",
32
- {
33
- "data-testid": "image-panel",
34
- className: classNames("zendir-image-panel", className),
35
- style: {
36
- display: "flex",
37
- flexDirection: "column",
38
- gap: tokens.spacing.sm,
39
- fontFamily: tokens.typography.fontFamily.primary
40
- },
41
- children: [
42
- onRefresh && /* @__PURE__ */ jsx(
43
- "button",
44
- {
45
- type: "button",
46
- "data-testid": "image-panel-refresh",
47
- onClick: onRefresh,
48
- disabled: buttonDisabled,
49
- "aria-busy": loading ? "true" : "false",
50
- style: {
51
- alignSelf: "flex-start",
52
- padding: `${tokens.spacing.xs}px ${tokens.spacing.sm}px`,
53
- borderRadius: tokens.borderRadius.sm,
54
- border: `1px solid ${tokens.colors.border.muted}`,
55
- background: buttonDisabled ? "rgba(255,255,255,0.03)" : "rgba(255,255,255,0.08)",
56
- color: buttonDisabled ? tokens.colors.text.tertiary : tokens.colors.text.primary,
57
- cursor: buttonDisabled ? "default" : "pointer",
58
- fontSize: tokens.typography.fontSize.xs,
59
- fontFamily: tokens.typography.fontFamily.primary,
60
- // Make the disabled state visually consistent across browsers.
61
- opacity: buttonDisabled ? 0.85 : 1
62
- },
63
- children: loading ? refreshingLabel : refreshLabel
64
- }
65
- ),
66
- error && /* @__PURE__ */ jsx(
67
- "div",
68
- {
69
- "data-testid": "image-panel-error",
70
- role: "alert",
71
- style: {
72
- color: tokens.colors.status.critical,
73
- fontSize: tokens.typography.fontSize.xs,
74
- background: "rgba(255, 56, 56, 0.08)",
75
- border: `1px solid ${tokens.colors.status.critical}33`,
76
- borderRadius: tokens.borderRadius.sm,
77
- padding: `${tokens.spacing.xs}px ${tokens.spacing.sm}px`,
78
- wordBreak: "break-word"
79
- },
80
- children: error
81
- }
82
- ),
83
- /* @__PURE__ */ jsx(
84
- ImageFrame,
85
- {
86
- data,
87
- pixels,
88
- width,
89
- height,
90
- maxADU,
91
- colormap,
92
- showValues,
93
- title,
94
- loading: frameLoading,
95
- emptyMessage
96
- }
97
- )
98
- ]
99
- }
100
- );
4
+ import { memo } from "react";
5
+ import { jsx, jsxs } from "react/jsx-runtime";
6
+ //#region src/react/core/widgets/ImagePanel.tsx
7
+ /**
8
+ * @zendir/ui - ImagePanel Component
9
+ *
10
+ * Reusable chrome around `<ImageFrame>` for sensor/CCD-style image
11
+ * surfaces. Adds the bits every imaging panel needs but `<ImageFrame>`
12
+ * intentionally doesn't:
13
+ *
14
+ * - **Refresh button** with disabled / busy states.
15
+ * - **Error banner** when the upstream capture failed.
16
+ * - **Title strip + frame** in a single bordered card.
17
+ *
18
+ * Like `<ImageFrame>`, this component is **purely presentational** —
19
+ * it knows nothing about where the data came from. Callers wire up the
20
+ * I/O (engine, file, websocket) and feed:
21
+ *
22
+ * - `data` / `pixels` / `width` / `maxADU` / `colormap` → forwarded
23
+ * to `<ImageFrame>` unchanged.
24
+ * - `onRefresh` click handler for the button.
25
+ * - `loading` toggles the button's "Capturing…" label AND the
26
+ * frame's skeleton state when there's no frame yet.
27
+ * - `error` renders a red strip above the frame.
28
+ *
29
+ * The component handles the common refresh-disabled-while-loading
30
+ * pattern internally so callers don't have to compute it.
31
+ *
32
+ * @example
33
+ * ```tsx
34
+ * <ImagePanel
35
+ * title="CCD capture"
36
+ * data={samples}
37
+ * width={28}
38
+ * maxADU={65535}
39
+ * loading={busy}
40
+ * error={err}
41
+ * onRefresh={capture}
42
+ * refreshDisabled={!ccd}
43
+ * emptyMessage="No frame captured yet."
44
+ * />
45
+ * ```
46
+ */
47
+ var ImagePanel = memo(function ImagePanel({ data, pixels, width, height, maxADU, colormap, showValues, emptyMessage, title, loading = false, error = null, onRefresh, refreshLabel = "Refresh", refreshingLabel = "Capturing…", refreshDisabled = false, className = "" }) {
48
+ const { tokens } = useTheme();
49
+ const buttonDisabled = loading || refreshDisabled;
50
+ const hasFrame = Array.isArray(data) || pixels instanceof Uint8ClampedArray;
51
+ const frameLoading = loading && !hasFrame;
52
+ return /* @__PURE__ */ jsxs("div", {
53
+ "data-testid": "image-panel",
54
+ className: classNames("zendir-image-panel", className),
55
+ style: {
56
+ display: "flex",
57
+ flexDirection: "column",
58
+ gap: tokens.spacing.sm,
59
+ fontFamily: tokens.typography.fontFamily.primary
60
+ },
61
+ children: [
62
+ onRefresh && /* @__PURE__ */ jsx("button", {
63
+ type: "button",
64
+ "data-testid": "image-panel-refresh",
65
+ onClick: onRefresh,
66
+ disabled: buttonDisabled,
67
+ "aria-busy": loading ? "true" : "false",
68
+ style: {
69
+ alignSelf: "flex-start",
70
+ padding: `${tokens.spacing.xs}px ${tokens.spacing.sm}px`,
71
+ borderRadius: tokens.borderRadius.sm,
72
+ border: `1px solid ${tokens.colors.border.muted}`,
73
+ background: buttonDisabled ? "rgba(255,255,255,0.03)" : "rgba(255,255,255,0.08)",
74
+ color: buttonDisabled ? tokens.colors.text.tertiary : tokens.colors.text.primary,
75
+ cursor: buttonDisabled ? "default" : "pointer",
76
+ fontSize: tokens.typography.fontSize.xs,
77
+ fontFamily: tokens.typography.fontFamily.primary,
78
+ opacity: buttonDisabled ? .85 : 1
79
+ },
80
+ children: loading ? refreshingLabel : refreshLabel
81
+ }),
82
+ error && /* @__PURE__ */ jsx("div", {
83
+ "data-testid": "image-panel-error",
84
+ role: "alert",
85
+ style: {
86
+ color: tokens.colors.status.critical,
87
+ fontSize: tokens.typography.fontSize.xs,
88
+ background: `${tokens.colors.status.critical}14`,
89
+ border: `1px solid ${tokens.colors.status.critical}33`,
90
+ borderRadius: tokens.borderRadius.sm,
91
+ padding: `${tokens.spacing.xs}px ${tokens.spacing.sm}px`,
92
+ wordBreak: "break-word"
93
+ },
94
+ children: error
95
+ }),
96
+ /* @__PURE__ */ jsx(ImageFrame, {
97
+ data,
98
+ pixels,
99
+ width,
100
+ height,
101
+ maxADU,
102
+ colormap,
103
+ showValues,
104
+ title,
105
+ loading: frameLoading,
106
+ emptyMessage
107
+ })
108
+ ]
109
+ });
101
110
  });
102
- export {
103
- ImagePanel
104
- };
105
- //# sourceMappingURL=ImagePanel.js.map
111
+ //#endregion
112
+ export { ImagePanel, ImagePanel as default };
113
+
114
+ //# sourceMappingURL=ImagePanel.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ImagePanel.js","sources":["../../../../src/react/core/widgets/ImagePanel.tsx"],"sourcesContent":["/**\n * @zendir/ui - ImagePanel Component\n *\n * Reusable chrome around `<ImageFrame>` for sensor/CCD-style image\n * surfaces. Adds the bits every imaging panel needs but `<ImageFrame>`\n * intentionally doesn't:\n *\n * - **Refresh button** with disabled / busy states.\n * - **Error banner** when the upstream capture failed.\n * - **Title strip + frame** in a single bordered card.\n *\n * Like `<ImageFrame>`, this component is **purely presentational** —\n * it knows nothing about where the data came from. Callers wire up the\n * I/O (engine, file, websocket) and feed:\n *\n * - `data` / `pixels` / `width` / `maxADU` / `colormap` → forwarded\n * to `<ImageFrame>` unchanged.\n * - `onRefresh` → click handler for the button.\n * - `loading` → toggles the button's \"Capturing…\" label AND the\n * frame's skeleton state when there's no frame yet.\n * - `error` → renders a red strip above the frame.\n *\n * The component handles the common refresh-disabled-while-loading\n * pattern internally so callers don't have to compute it.\n *\n * @example\n * ```tsx\n * <ImagePanel\n * title=\"CCD capture\"\n * data={samples}\n * width={28}\n * maxADU={65535}\n * loading={busy}\n * error={err}\n * onRefresh={capture}\n * refreshDisabled={!ccd}\n * emptyMessage=\"No frame captured yet.\"\n * />\n * ```\n */\n\nimport React, { memo } from 'react';\nimport { useTheme } from '../../theme';\nimport { classNames } from '../../utils';\nimport { ImageFrame, type ImageColormap } from './ImageFrame';\n\n// =============================================================================\n// Types\n// =============================================================================\n\nexport interface ImagePanelProps {\n // -- Frame inputs (forwarded to <ImageFrame>) ----------------------------\n /** Flat, row-major single-channel samples. Length must equal width*height. */\n data?: number[];\n /** Pre-decoded RGBA pixels — escape hatch for callers with their own buffer. */\n pixels?: Uint8ClampedArray;\n /** Frame width in pixels. */\n width: number;\n /** Frame height in pixels. Defaults to `width` (square). */\n height?: number;\n /** Saturation ceiling used to normalise `data` → [0,1]. Default 65535. */\n maxADU?: number;\n /** Colormap applied to normalised intensities. Default 'cividis'. */\n colormap?: ImageColormap;\n /** Show the raw sample values as a grid below the image. Default false. */\n showValues?: boolean;\n /** Message shown when no frame is available. */\n emptyMessage?: string;\n\n // -- Panel chrome --------------------------------------------------------\n /** Title rendered above the frame. */\n title?: string;\n /** True while a capture is in flight. Drives the button label + skeleton. */\n loading?: boolean;\n /** Error from the upstream capture. Rendered as a red banner above the frame. */\n error?: string | null;\n /** Click handler for the refresh button. Omit to hide the button entirely. */\n onRefresh?: () => void;\n /** Label for the idle refresh button. Default 'Refresh'. */\n refreshLabel?: string;\n /** Label while `loading` is true. Default 'Capturing…'. */\n refreshingLabel?: string;\n /**\n * Disable the refresh button even when not loading — e.g. when the\n * caller has no data source bound (no CCD selected). The button is\n * always disabled while `loading` is true regardless of this prop.\n */\n refreshDisabled?: boolean;\n /** Extra class name applied to the outer wrapper. */\n className?: string;\n}\n\n// =============================================================================\n// Component\n// =============================================================================\n\nexport const ImagePanel = memo(function ImagePanel({\n // frame\n data,\n pixels,\n width,\n height,\n maxADU,\n colormap,\n showValues,\n emptyMessage,\n // chrome\n title,\n loading = false,\n error = null,\n onRefresh,\n refreshLabel = 'Refresh',\n refreshingLabel = 'Capturing…',\n refreshDisabled = false,\n className = '',\n}: ImagePanelProps): React.ReactElement {\n const { tokens } = useTheme();\n\n // Button is disabled while loading OR when caller explicitly disables it\n // (e.g. no source bound yet). Coalesced here so callers don't repeat the\n // `loading || refreshDisabled` pattern.\n const buttonDisabled = loading || refreshDisabled;\n\n // Skeleton state inside ImageFrame should fire only while loading AND\n // there's nothing to show yet — once we have data, repeat captures\n // refresh in-place without flashing the skeleton.\n const hasFrame = Array.isArray(data) || pixels instanceof Uint8ClampedArray;\n const frameLoading = loading && !hasFrame;\n\n return (\n <div\n data-testid=\"image-panel\"\n className={classNames('zendir-image-panel', className)}\n style={{\n display: 'flex',\n flexDirection: 'column',\n gap: tokens.spacing.sm,\n fontFamily: tokens.typography.fontFamily.primary,\n }}\n >\n {onRefresh && (\n <button\n type=\"button\"\n data-testid=\"image-panel-refresh\"\n onClick={onRefresh}\n disabled={buttonDisabled}\n aria-busy={loading ? 'true' : 'false'}\n style={{\n alignSelf: 'flex-start',\n padding: `${tokens.spacing.xs}px ${tokens.spacing.sm}px`,\n borderRadius: tokens.borderRadius.sm,\n border: `1px solid ${tokens.colors.border.muted}`,\n background: buttonDisabled\n ? 'rgba(255,255,255,0.03)'\n : 'rgba(255,255,255,0.08)',\n color: buttonDisabled ? tokens.colors.text.tertiary : tokens.colors.text.primary,\n cursor: buttonDisabled ? 'default' : 'pointer',\n fontSize: tokens.typography.fontSize.xs,\n fontFamily: tokens.typography.fontFamily.primary,\n // Make the disabled state visually consistent across browsers.\n opacity: buttonDisabled ? 0.85 : 1,\n }}\n >\n {loading ? refreshingLabel : refreshLabel}\n </button>\n )}\n\n {error && (\n <div\n data-testid=\"image-panel-error\"\n role=\"alert\"\n style={{\n color: tokens.colors.status.critical,\n fontSize: tokens.typography.fontSize.xs,\n background: 'rgba(255, 56, 56, 0.08)',\n border: `1px solid ${tokens.colors.status.critical}33`,\n borderRadius: tokens.borderRadius.sm,\n padding: `${tokens.spacing.xs}px ${tokens.spacing.sm}px`,\n wordBreak: 'break-word',\n }}\n >\n {error}\n </div>\n )}\n\n <ImageFrame\n data={data}\n pixels={pixels}\n width={width}\n height={height}\n maxADU={maxADU}\n colormap={colormap}\n showValues={showValues}\n title={title}\n loading={frameLoading}\n emptyMessage={emptyMessage}\n />\n </div>\n );\n});\n\nexport default ImagePanel;\n"],"names":["ImagePanel"],"mappings":";;;;;AAgGO,MAAM,aAAa,KAAK,SAASA,YAAW;AAAA;AAAA,EAEjD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA,EACA,UAAU;AAAA,EACV,QAAQ;AAAA,EACR;AAAA,EACA,eAAe;AAAA,EACf,kBAAkB;AAAA,EAClB,kBAAkB;AAAA,EAClB,YAAY;AACd,GAAwC;AACtC,QAAM,EAAE,OAAA,IAAW,SAAA;AAKnB,QAAM,iBAAiB,WAAW;AAKlC,QAAM,WAAW,MAAM,QAAQ,IAAI,KAAK,kBAAkB;AAC1D,QAAM,eAAe,WAAW,CAAC;AAEjC,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,eAAY;AAAA,MACZ,WAAW,WAAW,sBAAsB,SAAS;AAAA,MACrD,OAAO;AAAA,QACL,SAAS;AAAA,QACT,eAAe;AAAA,QACf,KAAK,OAAO,QAAQ;AAAA,QACpB,YAAY,OAAO,WAAW,WAAW;AAAA,MAAA;AAAA,MAG1C,UAAA;AAAA,QAAA,aACC;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,MAAK;AAAA,YACL,eAAY;AAAA,YACZ,SAAS;AAAA,YACT,UAAU;AAAA,YACV,aAAW,UAAU,SAAS;AAAA,YAC9B,OAAO;AAAA,cACL,WAAW;AAAA,cACX,SAAS,GAAG,OAAO,QAAQ,EAAE,MAAM,OAAO,QAAQ,EAAE;AAAA,cACpD,cAAc,OAAO,aAAa;AAAA,cAClC,QAAQ,aAAa,OAAO,OAAO,OAAO,KAAK;AAAA,cAC/C,YAAY,iBACR,2BACA;AAAA,cACJ,OAAO,iBAAiB,OAAO,OAAO,KAAK,WAAW,OAAO,OAAO,KAAK;AAAA,cACzE,QAAQ,iBAAiB,YAAY;AAAA,cACrC,UAAU,OAAO,WAAW,SAAS;AAAA,cACrC,YAAY,OAAO,WAAW,WAAW;AAAA;AAAA,cAEzC,SAAS,iBAAiB,OAAO;AAAA,YAAA;AAAA,YAGlC,oBAAU,kBAAkB;AAAA,UAAA;AAAA,QAAA;AAAA,QAIhC,SACC;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,eAAY;AAAA,YACZ,MAAK;AAAA,YACL,OAAO;AAAA,cACL,OAAO,OAAO,OAAO,OAAO;AAAA,cAC5B,UAAU,OAAO,WAAW,SAAS;AAAA,cACrC,YAAY;AAAA,cACZ,QAAQ,aAAa,OAAO,OAAO,OAAO,QAAQ;AAAA,cAClD,cAAc,OAAO,aAAa;AAAA,cAClC,SAAS,GAAG,OAAO,QAAQ,EAAE,MAAM,OAAO,QAAQ,EAAE;AAAA,cACpD,WAAW;AAAA,YAAA;AAAA,YAGZ,UAAA;AAAA,UAAA;AAAA,QAAA;AAAA,QAIL;AAAA,UAAC;AAAA,UAAA;AAAA,YACC;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA,SAAS;AAAA,YACT;AAAA,UAAA;AAAA,QAAA;AAAA,MACF;AAAA,IAAA;AAAA,EAAA;AAGN,CAAC;"}
1
+ {"version":3,"file":"ImagePanel.js","names":[],"sources":["../../../../src/react/core/widgets/ImagePanel.tsx"],"sourcesContent":["/**\n * @zendir/ui - ImagePanel Component\n *\n * Reusable chrome around `<ImageFrame>` for sensor/CCD-style image\n * surfaces. Adds the bits every imaging panel needs but `<ImageFrame>`\n * intentionally doesn't:\n *\n * - **Refresh button** with disabled / busy states.\n * - **Error banner** when the upstream capture failed.\n * - **Title strip + frame** in a single bordered card.\n *\n * Like `<ImageFrame>`, this component is **purely presentational** —\n * it knows nothing about where the data came from. Callers wire up the\n * I/O (engine, file, websocket) and feed:\n *\n * - `data` / `pixels` / `width` / `maxADU` / `colormap` → forwarded\n * to `<ImageFrame>` unchanged.\n * - `onRefresh` → click handler for the button.\n * - `loading` → toggles the button's \"Capturing…\" label AND the\n * frame's skeleton state when there's no frame yet.\n * - `error` → renders a red strip above the frame.\n *\n * The component handles the common refresh-disabled-while-loading\n * pattern internally so callers don't have to compute it.\n *\n * @example\n * ```tsx\n * <ImagePanel\n * title=\"CCD capture\"\n * data={samples}\n * width={28}\n * maxADU={65535}\n * loading={busy}\n * error={err}\n * onRefresh={capture}\n * refreshDisabled={!ccd}\n * emptyMessage=\"No frame captured yet.\"\n * />\n * ```\n */\n\nimport React, { memo } from \"react\";\nimport { useTheme } from \"../../theme\";\nimport { classNames } from \"../../utils\";\nimport { ImageFrame, type ImageColormap } from \"./ImageFrame\";\n\n// =============================================================================\n// Types\n// =============================================================================\n\nexport interface ImagePanelProps {\n // -- Frame inputs (forwarded to <ImageFrame>) ----------------------------\n /** Flat, row-major single-channel samples. Length must equal width*height. */\n data?: number[];\n /** Pre-decoded RGBA pixels — escape hatch for callers with their own buffer. */\n pixels?: Uint8ClampedArray;\n /** Frame width in pixels. */\n width: number;\n /** Frame height in pixels. Defaults to `width` (square). */\n height?: number;\n /** Saturation ceiling used to normalise `data` → [0,1]. Default 65535. */\n maxADU?: number;\n /** Colormap applied to normalised intensities. Default 'cividis'. */\n colormap?: ImageColormap;\n /** Show the raw sample values as a grid below the image. Default false. */\n showValues?: boolean;\n /** Message shown when no frame is available. */\n emptyMessage?: string;\n\n // -- Panel chrome --------------------------------------------------------\n /** Title rendered above the frame. */\n title?: string;\n /** True while a capture is in flight. Drives the button label + skeleton. */\n loading?: boolean;\n /** Error from the upstream capture. Rendered as a red banner above the frame. */\n error?: string | null;\n /** Click handler for the refresh button. Omit to hide the button entirely. */\n onRefresh?: () => void;\n /** Label for the idle refresh button. Default 'Refresh'. */\n refreshLabel?: string;\n /** Label while `loading` is true. Default 'Capturing…'. */\n refreshingLabel?: string;\n /**\n * Disable the refresh button even when not loading — e.g. when the\n * caller has no data source bound (no CCD selected). The button is\n * always disabled while `loading` is true regardless of this prop.\n */\n refreshDisabled?: boolean;\n /** Extra class name applied to the outer wrapper. */\n className?: string;\n}\n\n// =============================================================================\n// Component\n// =============================================================================\n\nexport const ImagePanel = memo(function ImagePanel({\n // frame\n data,\n pixels,\n width,\n height,\n maxADU,\n colormap,\n showValues,\n emptyMessage,\n // chrome\n title,\n loading = false,\n error = null,\n onRefresh,\n refreshLabel = \"Refresh\",\n refreshingLabel = \"Capturing…\",\n refreshDisabled = false,\n className = \"\",\n}: ImagePanelProps): React.ReactElement {\n const { tokens } = useTheme();\n\n // Button is disabled while loading OR when caller explicitly disables it\n // (e.g. no source bound yet). Coalesced here so callers don't repeat the\n // `loading || refreshDisabled` pattern.\n const buttonDisabled = loading || refreshDisabled;\n\n // Skeleton state inside ImageFrame should fire only while loading AND\n // there's nothing to show yet — once we have data, repeat captures\n // refresh in-place without flashing the skeleton.\n const hasFrame = Array.isArray(data) || pixels instanceof Uint8ClampedArray;\n const frameLoading = loading && !hasFrame;\n\n return (\n <div\n data-testid=\"image-panel\"\n className={classNames(\"zendir-image-panel\", className)}\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n gap: tokens.spacing.sm,\n fontFamily: tokens.typography.fontFamily.primary,\n }}\n >\n {onRefresh && (\n <button\n type=\"button\"\n data-testid=\"image-panel-refresh\"\n onClick={onRefresh}\n disabled={buttonDisabled}\n aria-busy={loading ? \"true\" : \"false\"}\n style={{\n alignSelf: \"flex-start\",\n padding: `${tokens.spacing.xs}px ${tokens.spacing.sm}px`,\n borderRadius: tokens.borderRadius.sm,\n border: `1px solid ${tokens.colors.border.muted}`,\n background: buttonDisabled\n ? \"rgba(255,255,255,0.03)\"\n : \"rgba(255,255,255,0.08)\",\n color: buttonDisabled\n ? tokens.colors.text.tertiary\n : tokens.colors.text.primary,\n cursor: buttonDisabled ? \"default\" : \"pointer\",\n fontSize: tokens.typography.fontSize.xs,\n fontFamily: tokens.typography.fontFamily.primary,\n // Make the disabled state visually consistent across browsers.\n opacity: buttonDisabled ? 0.85 : 1,\n }}\n >\n {loading ? refreshingLabel : refreshLabel}\n </button>\n )}\n\n {error && (\n <div\n data-testid=\"image-panel-error\"\n role=\"alert\"\n style={{\n color: tokens.colors.status.critical,\n fontSize: tokens.typography.fontSize.xs,\n background: `${tokens.colors.status.critical}14`,\n border: `1px solid ${tokens.colors.status.critical}33`,\n borderRadius: tokens.borderRadius.sm,\n padding: `${tokens.spacing.xs}px ${tokens.spacing.sm}px`,\n wordBreak: \"break-word\",\n }}\n >\n {error}\n </div>\n )}\n\n <ImageFrame\n data={data}\n pixels={pixels}\n width={width}\n height={height}\n maxADU={maxADU}\n colormap={colormap}\n showValues={showValues}\n title={title}\n loading={frameLoading}\n emptyMessage={emptyMessage}\n />\n </div>\n );\n});\n\nexport default ImagePanel;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgGA,IAAa,aAAa,KAAK,SAAS,WAAW,EAEjD,MACA,QACA,OACA,QACA,QACA,UACA,YACA,cAEA,OACA,UAAU,OACV,QAAQ,MACR,WACA,eAAe,WACf,kBAAkB,cAClB,kBAAkB,OAClB,YAAY,MAC0B;CACtC,MAAM,EAAE,WAAW,SAAS;CAK5B,MAAM,iBAAiB,WAAW;CAKlC,MAAM,WAAW,MAAM,QAAQ,IAAI,KAAK,kBAAkB;CAC1D,MAAM,eAAe,WAAW,CAAC;CAEjC,OACE,qBAAC,OAAD;EACE,eAAY;EACZ,WAAW,WAAW,sBAAsB,SAAS;EACrD,OAAO;GACL,SAAS;GACT,eAAe;GACf,KAAK,OAAO,QAAQ;GACpB,YAAY,OAAO,WAAW,WAAW;EAC3C;YARF;GAUG,aACC,oBAAC,UAAD;IACE,MAAK;IACL,eAAY;IACZ,SAAS;IACT,UAAU;IACV,aAAW,UAAU,SAAS;IAC9B,OAAO;KACL,WAAW;KACX,SAAS,GAAG,OAAO,QAAQ,GAAG,KAAK,OAAO,QAAQ,GAAG;KACrD,cAAc,OAAO,aAAa;KAClC,QAAQ,aAAa,OAAO,OAAO,OAAO;KAC1C,YAAY,iBACR,2BACA;KACJ,OAAO,iBACH,OAAO,OAAO,KAAK,WACnB,OAAO,OAAO,KAAK;KACvB,QAAQ,iBAAiB,YAAY;KACrC,UAAU,OAAO,WAAW,SAAS;KACrC,YAAY,OAAO,WAAW,WAAW;KAEzC,SAAS,iBAAiB,MAAO;IACnC;cAEC,UAAU,kBAAkB;GACvB,CAAA;GAGT,SACC,oBAAC,OAAD;IACE,eAAY;IACZ,MAAK;IACL,OAAO;KACL,OAAO,OAAO,OAAO,OAAO;KAC5B,UAAU,OAAO,WAAW,SAAS;KACrC,YAAY,GAAG,OAAO,OAAO,OAAO,SAAS;KAC7C,QAAQ,aAAa,OAAO,OAAO,OAAO,SAAS;KACnD,cAAc,OAAO,aAAa;KAClC,SAAS,GAAG,OAAO,QAAQ,GAAG,KAAK,OAAO,QAAQ,GAAG;KACrD,WAAW;IACb;cAEC;GACE,CAAA;GAGP,oBAAC,YAAD;IACQ;IACE;IACD;IACC;IACA;IACE;IACE;IACL;IACP,SAAS;IACK;GACf,CAAA;EACE;;AAET,CAAC"}
@@ -1,6 +1,5 @@
1
1
  import { default as React } from 'react';
2
-
3
- export type LogSeverity = 'debug' | 'info' | 'warn' | 'error' | 'critical';
2
+ export type LogSeverity = "debug" | "info" | "warn" | "error" | "critical";
4
3
  export interface LogEntry {
5
4
  /** Unique ID */
6
5
  id: string | number;
@@ -45,7 +44,7 @@ export interface LogViewerProps {
45
44
  * (falls back to wall-clock formatting if simTime is absent on an entry).
46
45
  * Default: 'time'.
47
46
  */
48
- timestampFormat?: 'time' | 'datetime' | 'relative' | 'sim-elapsed';
47
+ timestampFormat?: "time" | "datetime" | "relative" | "sim-elapsed";
49
48
  /** Called when an entry is clicked */
50
49
  onEntryClick?: (entry: LogEntry) => void;
51
50
  /** CSS class */