@zendir/ui 0.4.1 → 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 +34 -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 -538
  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 +11 -5
  229. package/dist/react/core/navigation/SideNav.js +857 -602
  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,1106 +1,1368 @@
1
- import { jsxs, jsx, Fragment } from "react/jsx-runtime";
2
- import React, { useMemo, useState, useCallback, useRef, useEffect, memo } from "react";
3
1
  import { useTheme } from "../../theme/ThemeProvider.js";
4
- const REGION_COLORS = {
5
- primary: "rgba(96, 165, 250, 0.15)",
6
- secondary: "rgba(168, 85, 247, 0.15)",
7
- tertiary: "rgba(251, 191, 36, 0.15)",
8
- userData: "rgba(34, 197, 94, 0.15)",
9
- error: "rgba(255, 56, 56, 0.15)"
2
+ import React, { memo, useCallback, useEffect, useMemo, useRef, useState } from "react";
3
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
4
+ //#region src/react/core/widgets/HexViewer.tsx
5
+ /**
6
+ * @zendir/ui - HexViewer Component
7
+ *
8
+ * Displays raw binary data in a hex viewer format for telemetry packet analysis.
9
+ * Shows offset, hex bytes, and ASCII representation side by side.
10
+ *
11
+ * Features:
12
+ * - Hex + ASCII view with configurable bytes per row (8, 16, 32)
13
+ * - Byte highlighting with custom colors (for field boundaries, errors, etc.)
14
+ * - Click-to-select bytes and ranges
15
+ * - Search by hex pattern or ASCII string
16
+ * - Offset display in hex or decimal
17
+ * - Live data streaming support (auto-scroll)
18
+ * - Copy selection as hex, decimal, or ASCII
19
+ * - Byte grouping (1, 2, 4, 8)
20
+ * - Endianness toggle for multi-byte interpretation
21
+ * - Status bar showing cursor position, selection info, data interpretation
22
+ *
23
+ * Astro UX Compliance:
24
+ * - Dark theme with monospace font
25
+ * - Status colors for highlighting
26
+ * - Accessible keyboard navigation
27
+ *
28
+ * @example
29
+ * ```tsx
30
+ * <HexViewer
31
+ * data={new Uint8Array([0x48, 0x65, 0x6C, 0x6C, 0x6F])}
32
+ * bytesPerRow={16}
33
+ * highlights={[{ start: 0, end: 4, color: '#4CAF50', label: 'Header' }]}
34
+ * />
35
+ * ```
36
+ */
37
+ /** Pre-defined region color palette for space packet headers */
38
+ var REGION_COLORS = {
39
+ primary: "rgba(96, 165, 250, 0.15)",
40
+ secondary: "rgba(168, 85, 247, 0.15)",
41
+ tertiary: "rgba(251, 191, 36, 0.15)",
42
+ userData: "rgba(34, 197, 94, 0.15)",
43
+ error: "rgba(255, 56, 56, 0.15)"
10
44
  };
11
- const REGION_BORDER_COLORS = {
12
- primary: "rgba(96, 165, 250, 0.5)",
13
- secondary: "rgba(168, 85, 247, 0.5)",
14
- tertiary: "rgba(251, 191, 36, 0.5)",
15
- userData: "rgba(34, 197, 94, 0.5)",
16
- error: "rgba(255, 56, 56, 0.5)"
45
+ var REGION_BORDER_COLORS = {
46
+ primary: "rgba(96, 165, 250, 0.5)",
47
+ secondary: "rgba(168, 85, 247, 0.5)",
48
+ tertiary: "rgba(251, 191, 36, 0.5)",
49
+ userData: "rgba(34, 197, 94, 0.5)",
50
+ error: "rgba(255, 56, 56, 0.5)"
17
51
  };
18
52
  function toUint8Array(data) {
19
- return data instanceof Uint8Array ? data : new Uint8Array(data);
53
+ return data instanceof Uint8Array ? data : new Uint8Array(data);
20
54
  }
21
55
  function byteToHex(byte) {
22
- return byte.toString(16).padStart(2, "0").toUpperCase();
56
+ return byte.toString(16).padStart(2, "0").toUpperCase();
23
57
  }
24
58
  function byteToAscii(byte) {
25
- return byte >= 32 && byte <= 126 ? String.fromCharCode(byte) : ".";
59
+ return byte >= 32 && byte <= 126 ? String.fromCharCode(byte) : ".";
26
60
  }
27
61
  function formatOffset(offset, base, totalBytes) {
28
- if (base === "hex") {
29
- const width2 = Math.max(4, Math.ceil(Math.log2(Math.max(totalBytes, 1)) / 4) * 1);
30
- return offset.toString(16).padStart(Math.max(width2, 4), "0").toUpperCase();
31
- }
32
- const width = Math.max(4, totalBytes.toString().length);
33
- return offset.toString().padStart(width, " ");
62
+ if (base === "hex") {
63
+ const width = Math.max(4, Math.ceil(Math.log2(Math.max(totalBytes, 1)) / 4) * 1);
64
+ return offset.toString(16).padStart(Math.max(width, 4), "0").toUpperCase();
65
+ }
66
+ const width = Math.max(4, totalBytes.toString().length);
67
+ return offset.toString().padStart(width, " ");
34
68
  }
35
69
  function interpretBytes(data, offset, endianness) {
36
- const result = {
37
- u8: data[offset],
38
- i8: data[offset] > 127 ? data[offset] - 256 : data[offset]
39
- };
40
- if (offset + 1 < data.length) {
41
- const view = new DataView(data.buffer, data.byteOffset + offset, Math.min(8, data.length - offset));
42
- const be = endianness === "big";
43
- result.u16 = view.getUint16(0, !be);
44
- result.i16 = view.getInt16(0, !be);
45
- if (offset + 3 < data.length) {
46
- result.u32 = view.getUint32(0, !be);
47
- result.i32 = view.getInt32(0, !be);
48
- result.f32 = view.getFloat32(0, !be);
49
- }
50
- if (offset + 7 < data.length) {
51
- result.f64 = view.getFloat64(0, !be);
52
- }
53
- }
54
- return result;
70
+ const result = {
71
+ u8: data[offset],
72
+ i8: data[offset] > 127 ? data[offset] - 256 : data[offset]
73
+ };
74
+ if (offset + 1 < data.length) {
75
+ const view = new DataView(data.buffer, data.byteOffset + offset, Math.min(8, data.length - offset));
76
+ const be = endianness === "big";
77
+ result.u16 = view.getUint16(0, !be);
78
+ result.i16 = view.getInt16(0, !be);
79
+ if (offset + 3 < data.length) {
80
+ result.u32 = view.getUint32(0, !be);
81
+ result.i32 = view.getInt32(0, !be);
82
+ result.f32 = view.getFloat32(0, !be);
83
+ }
84
+ if (offset + 7 < data.length) result.f64 = view.getFloat64(0, !be);
85
+ }
86
+ return result;
55
87
  }
56
- const HEX_ROW_HEIGHT = 22;
57
- const HEX_OVERSCAN_ROWS = 16;
58
- const MAX_RENDERED_SEARCH_HIGHLIGHTS = 400;
59
- const HexRow = memo(function HexRow2({
60
- offset,
61
- bytes,
62
- bytesPerRow,
63
- byteGrouping,
64
- offsetBase,
65
- totalBytes,
66
- showAscii,
67
- showOffset,
68
- highlights,
69
- regions,
70
- effectiveHighlight,
71
- selectedStart,
72
- selectedEnd,
73
- cursorOffset,
74
- onByteMouseDown,
75
- onByteMouseEnter,
76
- onByteClick,
77
- handleByteHoverInternal,
78
- handleByteLeaveInternal,
79
- tokens
80
- }) {
81
- const hexCells = [];
82
- const asciiCells = [];
83
- for (let i = 0; i < bytesPerRow; i++) {
84
- const byteIdx = offset + i;
85
- const isValid = i < bytes.length;
86
- const byte = isValid ? bytes[i] : 0;
87
- let bgColor = "transparent";
88
- let fgColor = tokens.colors.text.primary;
89
- let borderBottom = "2px solid transparent";
90
- const isSelected = byteIdx >= Math.min(selectedStart, selectedEnd) && byteIdx <= Math.max(selectedStart, selectedEnd) && selectedStart >= 0;
91
- const isCursor = byteIdx === cursorOffset;
92
- let regionColor = null;
93
- for (let r = 0; r < regions.length; r++) {
94
- const region = regions[r];
95
- if (byteIdx < region.start) break;
96
- if (byteIdx >= region.start && byteIdx < region.end) {
97
- regionColor = region.color;
98
- break;
99
- }
100
- }
101
- if (regionColor) {
102
- bgColor = regionColor;
103
- }
104
- for (const h of highlights) {
105
- if (byteIdx >= h.start && byteIdx <= h.end) {
106
- bgColor = `${h.color}33`;
107
- fgColor = h.color;
108
- break;
109
- }
110
- }
111
- if (effectiveHighlight && byteIdx >= effectiveHighlight.start && byteIdx < effectiveHighlight.end) {
112
- bgColor = `${tokens.colors.status.caution}4D`;
113
- borderBottom = `2px solid ${tokens.colors.status.caution}`;
114
- }
115
- if (isSelected) {
116
- bgColor = `${tokens.colors.interactive.default}4D`;
117
- fgColor = tokens.colors.interactive.default;
118
- }
119
- const needsGroupSpace = i > 0 && i % byteGrouping === 0;
120
- hexCells.push(
121
- /* @__PURE__ */ jsx(
122
- "span",
123
- {
124
- role: "button",
125
- tabIndex: isValid ? 0 : -1,
126
- onMouseDown: () => isValid && onByteMouseDown(byteIdx),
127
- onMouseEnter: () => {
128
- isValid && onByteMouseEnter(byteIdx);
129
- isValid && handleByteHoverInternal(byteIdx);
130
- },
131
- onMouseLeave: () => isValid && handleByteLeaveInternal(),
132
- onClick: () => isValid && onByteClick(byteIdx),
133
- onKeyDown: (e) => {
134
- if (isValid && (e.key === "Enter" || e.key === " ")) {
135
- e.preventDefault();
136
- onByteClick(byteIdx);
137
- }
138
- },
139
- style: {
140
- padding: "0 2px",
141
- marginLeft: needsGroupSpace ? 6 : 0,
142
- background: bgColor,
143
- color: isValid ? fgColor : `${tokens.colors.text.muted}26`,
144
- cursor: isValid ? "pointer" : "default",
145
- borderBottom: isCursor ? `2px solid ${tokens.colors.status.normal}` : borderBottom,
146
- borderRadius: tokens.borderRadius.sm,
147
- userSelect: "none",
148
- transition: "background 0.1s ease"
149
- },
150
- children: isValid ? byteToHex(byte) : " "
151
- },
152
- `hex-${i}`
153
- )
154
- );
155
- if (showAscii) {
156
- asciiCells.push(
157
- /* @__PURE__ */ jsx(
158
- "span",
159
- {
160
- role: "button",
161
- tabIndex: isValid ? 0 : -1,
162
- onMouseDown: () => isValid && onByteMouseDown(byteIdx),
163
- onMouseEnter: () => {
164
- isValid && onByteMouseEnter(byteIdx);
165
- isValid && handleByteHoverInternal(byteIdx);
166
- },
167
- onMouseLeave: () => isValid && handleByteLeaveInternal(),
168
- onClick: () => isValid && onByteClick(byteIdx),
169
- onKeyDown: (e) => {
170
- if (isValid && (e.key === "Enter" || e.key === " ")) {
171
- e.preventDefault();
172
- onByteClick(byteIdx);
173
- }
174
- },
175
- style: {
176
- background: bgColor,
177
- color: isValid ? byte >= 32 && byte <= 126 ? fgColor : `${tokens.colors.text.muted}33` : "transparent",
178
- cursor: isValid ? "pointer" : "default",
179
- borderBottom: isCursor ? `2px solid ${tokens.colors.status.normal}` : borderBottom,
180
- borderRadius: tokens.borderRadius.sm,
181
- userSelect: "none",
182
- width: 8,
183
- textAlign: "center"
184
- },
185
- children: isValid ? byteToAscii(byte) : " "
186
- },
187
- `ascii-${i}`
188
- )
189
- );
190
- }
191
- }
192
- return /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", height: HEX_ROW_HEIGHT, lineHeight: `${HEX_ROW_HEIGHT}px` }, children: [
193
- showOffset && /* @__PURE__ */ jsx("span", { style: { color: tokens.colors.text.muted, marginRight: 12, minWidth: 60, textAlign: "right" }, children: formatOffset(offset, offsetBase, totalBytes) }),
194
- /* @__PURE__ */ jsx("span", { style: { display: "flex", gap: 1, marginRight: showAscii ? 16 : 0, letterSpacing: "0.5px" }, children: hexCells }),
195
- showAscii && /* @__PURE__ */ jsxs(Fragment, { children: [
196
- /* @__PURE__ */ jsx("span", { style: { color: `${tokens.colors.border.muted}`, marginRight: 8 }, children: "│" }),
197
- /* @__PURE__ */ jsx("span", { style: { display: "flex", letterSpacing: "1px" }, children: asciiCells })
198
- ] })
199
- ] });
88
+ var HEX_ROW_HEIGHT = 22;
89
+ var HEX_OVERSCAN_ROWS = 16;
90
+ var MAX_RENDERED_SEARCH_HIGHLIGHTS = 400;
91
+ var HexRow = memo(function HexRow({ offset, bytes, bytesPerRow, byteGrouping, offsetBase, totalBytes, showAscii, showOffset, highlights, regions, effectiveHighlight, selectedStart, selectedEnd, cursorOffset, onByteMouseDown, onByteMouseEnter, onByteClick, handleByteHoverInternal, handleByteLeaveInternal, tokens }) {
92
+ const hexCells = [];
93
+ const asciiCells = [];
94
+ for (let i = 0; i < bytesPerRow; i++) {
95
+ const byteIdx = offset + i;
96
+ const isValid = i < bytes.length;
97
+ const byte = isValid ? bytes[i] : 0;
98
+ let bgColor = "transparent";
99
+ let fgColor = tokens.colors.text.primary;
100
+ let borderBottom = "2px solid transparent";
101
+ const isSelected = byteIdx >= Math.min(selectedStart, selectedEnd) && byteIdx <= Math.max(selectedStart, selectedEnd) && selectedStart >= 0;
102
+ const isCursor = byteIdx === cursorOffset;
103
+ let regionColor = null;
104
+ for (let r = 0; r < regions.length; r++) {
105
+ const region = regions[r];
106
+ if (byteIdx < region.start) break;
107
+ if (byteIdx >= region.start && byteIdx < region.end) {
108
+ regionColor = region.color;
109
+ break;
110
+ }
111
+ }
112
+ if (regionColor) bgColor = regionColor;
113
+ for (const h of highlights) if (byteIdx >= h.start && byteIdx <= h.end) {
114
+ bgColor = `${h.color}33`;
115
+ fgColor = h.color;
116
+ break;
117
+ }
118
+ if (effectiveHighlight && byteIdx >= effectiveHighlight.start && byteIdx < effectiveHighlight.end) {
119
+ bgColor = `${tokens.colors.status.caution}4D`;
120
+ borderBottom = `2px solid ${tokens.colors.status.caution}`;
121
+ }
122
+ if (isSelected) {
123
+ bgColor = `${tokens.colors.interactive.default}4D`;
124
+ fgColor = tokens.colors.interactive.default;
125
+ }
126
+ const needsGroupSpace = i > 0 && i % byteGrouping === 0;
127
+ hexCells.push(/* @__PURE__ */ jsx("span", {
128
+ role: "button",
129
+ tabIndex: isValid ? 0 : -1,
130
+ onMouseDown: () => isValid && onByteMouseDown(byteIdx),
131
+ onMouseEnter: () => {
132
+ isValid && onByteMouseEnter(byteIdx);
133
+ isValid && handleByteHoverInternal(byteIdx);
134
+ },
135
+ onMouseLeave: () => isValid && handleByteLeaveInternal(),
136
+ onClick: () => isValid && onByteClick(byteIdx),
137
+ onKeyDown: (e) => {
138
+ if (isValid && (e.key === "Enter" || e.key === " ")) {
139
+ e.preventDefault();
140
+ onByteClick(byteIdx);
141
+ }
142
+ },
143
+ style: {
144
+ padding: "0 2px",
145
+ marginLeft: needsGroupSpace ? 6 : 0,
146
+ background: bgColor,
147
+ color: isValid ? fgColor : `${tokens.colors.text.muted}26`,
148
+ cursor: isValid ? "pointer" : "default",
149
+ borderBottom: isCursor ? `2px solid ${tokens.colors.status.normal}` : borderBottom,
150
+ borderRadius: tokens.borderRadius.sm,
151
+ userSelect: "none",
152
+ transition: "background 0.1s ease"
153
+ },
154
+ children: isValid ? byteToHex(byte) : " "
155
+ }, `hex-${i}`));
156
+ if (showAscii) asciiCells.push(/* @__PURE__ */ jsx("span", {
157
+ role: "button",
158
+ tabIndex: isValid ? 0 : -1,
159
+ onMouseDown: () => isValid && onByteMouseDown(byteIdx),
160
+ onMouseEnter: () => {
161
+ isValid && onByteMouseEnter(byteIdx);
162
+ isValid && handleByteHoverInternal(byteIdx);
163
+ },
164
+ onMouseLeave: () => isValid && handleByteLeaveInternal(),
165
+ onClick: () => isValid && onByteClick(byteIdx),
166
+ onKeyDown: (e) => {
167
+ if (isValid && (e.key === "Enter" || e.key === " ")) {
168
+ e.preventDefault();
169
+ onByteClick(byteIdx);
170
+ }
171
+ },
172
+ style: {
173
+ background: bgColor,
174
+ color: isValid ? byte >= 32 && byte <= 126 ? fgColor : `${tokens.colors.text.muted}33` : "transparent",
175
+ cursor: isValid ? "pointer" : "default",
176
+ borderBottom: isCursor ? `2px solid ${tokens.colors.status.normal}` : borderBottom,
177
+ borderRadius: tokens.borderRadius.sm,
178
+ userSelect: "none",
179
+ width: 8,
180
+ textAlign: "center"
181
+ },
182
+ children: isValid ? byteToAscii(byte) : " "
183
+ }, `ascii-${i}`));
184
+ }
185
+ return /* @__PURE__ */ jsxs("div", {
186
+ style: {
187
+ display: "flex",
188
+ alignItems: "center",
189
+ height: HEX_ROW_HEIGHT,
190
+ lineHeight: `${HEX_ROW_HEIGHT}px`
191
+ },
192
+ children: [
193
+ showOffset && /* @__PURE__ */ jsx("span", {
194
+ style: {
195
+ color: tokens.colors.text.muted,
196
+ marginRight: 12,
197
+ minWidth: 60,
198
+ textAlign: "right"
199
+ },
200
+ children: formatOffset(offset, offsetBase, totalBytes)
201
+ }),
202
+ /* @__PURE__ */ jsx("span", {
203
+ style: {
204
+ display: "flex",
205
+ gap: 1,
206
+ marginRight: showAscii ? 16 : 0,
207
+ letterSpacing: "0.5px"
208
+ },
209
+ children: hexCells
210
+ }),
211
+ showAscii && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", {
212
+ style: {
213
+ color: `${tokens.colors.border.muted}`,
214
+ marginRight: 8
215
+ },
216
+ children: "│"
217
+ }), /* @__PURE__ */ jsx("span", {
218
+ style: {
219
+ display: "flex",
220
+ letterSpacing: "1px"
221
+ },
222
+ children: asciiCells
223
+ })] })
224
+ ]
225
+ });
200
226
  });
201
- function HexViewer({
202
- data: rawData,
203
- bytesPerRow = 16,
204
- byteGrouping = 1,
205
- offsetBase = "hex",
206
- highlights = [],
207
- regions = [],
208
- decodedFields,
209
- packetHeaders,
210
- externalHighlight,
211
- showAscii = true,
212
- showOffset = true,
213
- showStatusBar = true,
214
- showSearch = true,
215
- showToolbar = true,
216
- showFieldsTable,
217
- showHeaderSummary,
218
- height = 400,
219
- title,
220
- streaming = false,
221
- endianness = "big",
222
- onSelectionChange,
223
- onByteClick,
224
- onByteHover,
225
- onByteLeave,
226
- className = "",
227
- bordered = true,
228
- style: styleProp
229
- }) {
230
- const { tokens } = useTheme();
231
- const data = useMemo(() => toUint8Array(rawData), [rawData]);
232
- const [hoveredFieldIdx, setHoveredFieldIdx] = useState(null);
233
- const [fieldHighlight, setFieldHighlight] = useState(null);
234
- const [showHeaders, setShowHeaders] = useState(true);
235
- const [showFields, setShowFields] = useState(true);
236
- const sortedRegions = useMemo(
237
- () => [...regions].sort((a, b) => a.start - b.start),
238
- [regions]
239
- );
240
- const fieldByteRanges = useMemo(() => {
241
- if (!decodedFields) return [];
242
- return decodedFields.map((f) => ({
243
- start: f.byteOffset ?? 0,
244
- end: (f.byteOffset ?? 0) + Math.ceil((f.sizeBits ?? 0) / 8),
245
- name: f.name
246
- }));
247
- }, [decodedFields]);
248
- const handleFieldHover = useCallback((fieldIdx) => {
249
- setHoveredFieldIdx(fieldIdx);
250
- const range = fieldByteRanges[fieldIdx];
251
- if (range && range.end > range.start) setFieldHighlight(range);
252
- }, [fieldByteRanges]);
253
- const handleFieldLeave = useCallback(() => {
254
- setHoveredFieldIdx(null);
255
- setFieldHighlight(null);
256
- }, []);
257
- const handleByteHoverInternal = useCallback((byteIdx) => {
258
- onByteHover == null ? void 0 : onByteHover(byteIdx);
259
- const fi = fieldByteRanges.findIndex((r) => byteIdx >= r.start && byteIdx < r.end);
260
- if (fi >= 0) {
261
- setHoveredFieldIdx(fi);
262
- setFieldHighlight(fieldByteRanges[fi]);
263
- } else {
264
- setHoveredFieldIdx(null);
265
- setFieldHighlight({ start: byteIdx, end: byteIdx + 1 });
266
- }
267
- }, [fieldByteRanges, onByteHover]);
268
- const handleByteLeaveInternal = useCallback(() => {
269
- onByteLeave == null ? void 0 : onByteLeave();
270
- setHoveredFieldIdx(null);
271
- setFieldHighlight(null);
272
- }, [onByteLeave]);
273
- const effectiveHighlight = externalHighlight || fieldHighlight;
274
- const shouldShowFieldsTable = showFieldsTable ?? (decodedFields && decodedFields.length > 0);
275
- const shouldShowHeaderSummary = showHeaderSummary ?? (packetHeaders && packetHeaders.length > 0);
276
- const scrollRef = useRef(null);
277
- const hasConstrainedHeight = useMemo(() => {
278
- if (height === void 0 || height === null) return false;
279
- if (typeof height === "string") return height.trim().toLowerCase() !== "auto";
280
- return true;
281
- }, [height]);
282
- const decodedFieldsMaxHeight = useMemo(() => {
283
- if (typeof height === "number") {
284
- return Math.min(220, Math.max(120, Math.floor(height * 0.32)));
285
- }
286
- return 200;
287
- }, [height]);
288
- const [cursorOffset, setCursorOffset] = useState(-1);
289
- const [selectStart, setSelectStart] = useState(-1);
290
- const [selectEnd, setSelectEnd] = useState(-1);
291
- const [isDragging, setIsDragging] = useState(false);
292
- const [searchQuery, setSearchQuery] = useState("");
293
- const [searchType, setSearchType] = useState("hex");
294
- const [searchResults, setSearchResults] = useState([]);
295
- const [currentSearchIdx, setCurrentSearchIdx] = useState(-1);
296
- const [localBytesPerRow, setLocalBytesPerRow] = useState(bytesPerRow);
297
- const [localGrouping, setLocalGrouping] = useState(byteGrouping);
298
- const [localOffsetBase, setLocalOffsetBase] = useState(offsetBase);
299
- const [localEndianness, setLocalEndianness] = useState(endianness);
300
- const [localShowAscii, setLocalShowAscii] = useState(showAscii);
301
- const [scrollTop, setScrollTop] = useState(0);
302
- const [viewportHeight, setViewportHeight] = useState(160);
303
- useEffect(() => {
304
- if (streaming && scrollRef.current) {
305
- scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
306
- }
307
- }, [data, streaming]);
308
- useEffect(() => {
309
- const el = scrollRef.current;
310
- if (!el) return void 0;
311
- const updateViewportHeight = () => {
312
- setViewportHeight(Math.max(120, el.clientHeight || 160));
313
- };
314
- updateViewportHeight();
315
- if (typeof ResizeObserver !== "undefined") {
316
- const observer = new ResizeObserver(updateViewportHeight);
317
- observer.observe(el);
318
- return () => observer.disconnect();
319
- }
320
- window.addEventListener("resize", updateViewportHeight);
321
- return () => window.removeEventListener("resize", updateViewportHeight);
322
- }, []);
323
- const totalRows = Math.max(1, Math.ceil(data.length / localBytesPerRow));
324
- const startRow = Math.max(0, Math.floor(scrollTop / HEX_ROW_HEIGHT) - HEX_OVERSCAN_ROWS);
325
- const visibleRowCount = Math.ceil(viewportHeight / HEX_ROW_HEIGHT) + HEX_OVERSCAN_ROWS * 2;
326
- const endRow = Math.min(totalRows, startRow + visibleRowCount);
327
- const virtualRows = useMemo(() => {
328
- const result = [];
329
- for (let row = startRow; row < endRow; row++) {
330
- const offset = row * localBytesPerRow;
331
- const bytes = data.subarray(offset, Math.min(data.length, offset + localBytesPerRow));
332
- result.push({ offset, bytes });
333
- }
334
- if (result.length === 0) {
335
- result.push({ offset: 0, bytes: new Uint8Array(0) });
336
- }
337
- return result;
338
- }, [data, localBytesPerRow, startRow, endRow]);
339
- const doSearch = useCallback(() => {
340
- if (!searchQuery.trim()) {
341
- setSearchResults([]);
342
- setCurrentSearchIdx(-1);
343
- return;
344
- }
345
- const results = [];
346
- if (searchType === "hex") {
347
- const hexStr = searchQuery.replace(/\s+/g, "");
348
- if (hexStr.length % 2 !== 0 || !/^[0-9a-fA-F]+$/.test(hexStr)) return;
349
- const searchBytes = new Uint8Array(hexStr.length / 2);
350
- for (let i = 0; i < searchBytes.length; i++) {
351
- searchBytes[i] = parseInt(hexStr.slice(i * 2, i * 2 + 2), 16);
352
- }
353
- for (let i = 0; i <= data.length - searchBytes.length; i++) {
354
- let match = true;
355
- for (let j = 0; j < searchBytes.length; j++) {
356
- if (data[i + j] !== searchBytes[j]) {
357
- match = false;
358
- break;
359
- }
360
- }
361
- if (match) results.push(i);
362
- }
363
- } else {
364
- const encoder = new TextEncoder();
365
- const searchBytes = encoder.encode(searchQuery);
366
- for (let i = 0; i <= data.length - searchBytes.length; i++) {
367
- let match = true;
368
- for (let j = 0; j < searchBytes.length; j++) {
369
- if (data[i + j] !== searchBytes[j]) {
370
- match = false;
371
- break;
372
- }
373
- }
374
- if (match) results.push(i);
375
- }
376
- }
377
- setSearchResults(results);
378
- setCurrentSearchIdx(results.length > 0 ? 0 : -1);
379
- if (results.length > 0) {
380
- setCursorOffset(results[0]);
381
- if (scrollRef.current) {
382
- const rowIdx = Math.floor(results[0] / localBytesPerRow);
383
- scrollRef.current.scrollTop = Math.max(0, rowIdx * HEX_ROW_HEIGHT - 100);
384
- }
385
- }
386
- }, [searchQuery, searchType, data, localBytesPerRow]);
387
- const scrollToOffset = useCallback((offset) => {
388
- if (!scrollRef.current) return;
389
- const rowIdx = Math.floor(offset / localBytesPerRow);
390
- scrollRef.current.scrollTop = Math.max(0, rowIdx * HEX_ROW_HEIGHT - 100);
391
- }, [localBytesPerRow]);
392
- const handleHexScroll = useCallback((event) => {
393
- setScrollTop(event.currentTarget.scrollTop);
394
- }, []);
395
- const nextSearchResult = useCallback(() => {
396
- if (searchResults.length === 0) return;
397
- const nextIdx = (currentSearchIdx + 1) % searchResults.length;
398
- setCurrentSearchIdx(nextIdx);
399
- setCursorOffset(searchResults[nextIdx]);
400
- scrollToOffset(searchResults[nextIdx]);
401
- }, [searchResults, currentSearchIdx, scrollToOffset]);
402
- const prevSearchResult = useCallback(() => {
403
- if (searchResults.length === 0) return;
404
- const prevIdx = (currentSearchIdx - 1 + searchResults.length) % searchResults.length;
405
- setCurrentSearchIdx(prevIdx);
406
- setCursorOffset(searchResults[prevIdx]);
407
- scrollToOffset(searchResults[prevIdx]);
408
- }, [searchResults, currentSearchIdx, scrollToOffset]);
409
- const handleByteMouseDown = useCallback((idx) => {
410
- setSelectStart(idx);
411
- setSelectEnd(idx);
412
- setCursorOffset(idx);
413
- setIsDragging(true);
414
- }, []);
415
- const handleByteMouseEnter = useCallback((idx) => {
416
- if (isDragging) {
417
- setSelectEnd(idx);
418
- }
419
- }, [isDragging]);
420
- const handleMouseUp = useCallback(() => {
421
- if (isDragging) {
422
- setIsDragging(false);
423
- if (onSelectionChange && selectStart >= 0 && selectEnd >= 0) {
424
- onSelectionChange(Math.min(selectStart, selectEnd), Math.max(selectStart, selectEnd));
425
- }
426
- }
427
- }, [isDragging, selectStart, selectEnd, onSelectionChange]);
428
- useEffect(() => {
429
- window.addEventListener("mouseup", handleMouseUp);
430
- return () => window.removeEventListener("mouseup", handleMouseUp);
431
- }, [handleMouseUp]);
432
- const handleByteClickCb = useCallback((idx) => {
433
- setCursorOffset(idx);
434
- if (onByteClick) {
435
- onByteClick(idx, data[idx]);
436
- }
437
- }, [data, onByteClick]);
438
- const copySelection = useCallback((format) => {
439
- var _a;
440
- if (selectStart < 0 || selectEnd < 0) return;
441
- const start = Math.min(selectStart, selectEnd);
442
- const end = Math.max(selectStart, selectEnd);
443
- const selected = data.slice(start, end + 1);
444
- let text;
445
- if (format === "hex") {
446
- text = Array.from(selected).map((b) => byteToHex(b)).join(" ");
447
- } else if (format === "decimal") {
448
- text = Array.from(selected).join(", ");
449
- } else {
450
- text = Array.from(selected).map((b) => byteToAscii(b)).join("");
451
- }
452
- (_a = navigator.clipboard) == null ? void 0 : _a.writeText(text);
453
- }, [data, selectStart, selectEnd]);
454
- const interpretation = useMemo(() => {
455
- if (cursorOffset < 0 || cursorOffset >= data.length) return null;
456
- return interpretBytes(data, cursorOffset, localEndianness);
457
- }, [data, cursorOffset, localEndianness]);
458
- const renderedSearchResultEntries = useMemo(() => {
459
- if (searchResults.length <= MAX_RENDERED_SEARCH_HIGHLIGHTS) {
460
- return searchResults.map((pos, idx) => ({ pos, idx }));
461
- }
462
- const halfWindow = Math.floor(MAX_RENDERED_SEARCH_HIGHLIGHTS / 2);
463
- const anchor = currentSearchIdx >= 0 ? currentSearchIdx : 0;
464
- const start = Math.max(0, anchor - halfWindow);
465
- const end = Math.min(searchResults.length, start + MAX_RENDERED_SEARCH_HIGHLIGHTS);
466
- const adjustedStart = Math.max(0, end - MAX_RENDERED_SEARCH_HIGHLIGHTS);
467
- return searchResults.slice(adjustedStart, end).map((pos, i) => ({ pos, idx: adjustedStart + i }));
468
- }, [searchResults, currentSearchIdx]);
469
- const allHighlights = useMemo(() => {
470
- const h = [...highlights];
471
- const searchLen = searchType === "hex" ? searchQuery.replace(/\s+/g, "").length / 2 : searchQuery.length;
472
- renderedSearchResultEntries.forEach(({ pos, idx }) => {
473
- h.push({
474
- start: pos,
475
- end: pos + Math.max(0, searchLen - 1),
476
- color: idx === currentSearchIdx ? tokens.colors.status.serious : tokens.colors.status.caution,
477
- label: `Search match ${idx + 1}`
478
- });
479
- });
480
- return h;
481
- }, [highlights, renderedSearchResultEntries, currentSearchIdx, searchQuery, searchType, tokens]);
482
- const containerStyle = {
483
- fontFamily: tokens.typography.fontFamily.mono,
484
- fontSize: 13,
485
- lineHeight: "22px",
486
- background: tokens.colors.background.base,
487
- border: bordered ? `1px solid ${tokens.colors.border.muted}` : "none",
488
- borderRadius: tokens.borderRadius.lg,
489
- overflowX: "hidden",
490
- overflowY: hasConstrainedHeight ? "auto" : "visible",
491
- display: "flex",
492
- flexDirection: "column",
493
- minHeight: 0,
494
- height: hasConstrainedHeight ? height : void 0,
495
- color: tokens.colors.text.primary,
496
- ...styleProp
497
- };
498
- return /* @__PURE__ */ jsxs(
499
- "div",
500
- {
501
- className: `hex-viewer ${className}`,
502
- style: containerStyle,
503
- children: [
504
- /* @__PURE__ */ jsxs("div", { style: {
505
- position: "sticky",
506
- top: 0,
507
- zIndex: 10,
508
- background: tokens.colors.background.surface,
509
- flexShrink: 0
510
- }, children: [
511
- title && /* @__PURE__ */ jsxs("div", { style: {
512
- padding: `${tokens.spacing.sm} ${tokens.spacing.smd}`,
513
- borderBottom: `1px solid ${tokens.colors.border.muted}`,
514
- display: "flex",
515
- alignItems: "center",
516
- justifyContent: "space-between"
517
- }, children: [
518
- /* @__PURE__ */ jsx("span", { style: { fontWeight: tokens.typography.fontWeight.medium, fontSize: tokens.typography.fontSize.base, fontFamily: tokens.typography.fontFamily.primary }, children: title }),
519
- /* @__PURE__ */ jsxs("span", { style: { fontSize: tokens.typography.fontSize.sm, color: tokens.colors.text.muted }, children: [
520
- data.length.toLocaleString(),
521
- " bytes"
522
- ] })
523
- ] }),
524
- showToolbar && /* @__PURE__ */ jsxs("div", { role: "toolbar", "aria-label": "Hex viewer controls", style: {
525
- padding: `${tokens.spacing.sm} ${tokens.spacing.smd}`,
526
- borderBottom: `1px solid ${tokens.colors.border.muted}`,
527
- display: "flex",
528
- alignItems: "center",
529
- gap: tokens.spacing.sm,
530
- flexWrap: "wrap",
531
- fontSize: tokens.typography.fontSize.sm
532
- }, children: [
533
- /* @__PURE__ */ jsxs("label", { style: { display: "flex", alignItems: "center", gap: 4, color: tokens.colors.text.muted }, children: [
534
- "Bytes/Row:",
535
- /* @__PURE__ */ jsxs(
536
- "select",
537
- {
538
- "aria-label": "Bytes per row",
539
- value: localBytesPerRow,
540
- onChange: (e) => setLocalBytesPerRow(Number(e.target.value)),
541
- style: {
542
- background: tokens.colors.background.base,
543
- color: tokens.colors.text.primary,
544
- border: `1px solid ${tokens.colors.border.muted}`,
545
- borderRadius: tokens.borderRadius.md,
546
- padding: "2px 4px",
547
- fontSize: 11
548
- },
549
- children: [
550
- /* @__PURE__ */ jsx("option", { value: 8, children: "8" }),
551
- /* @__PURE__ */ jsx("option", { value: 16, children: "16" }),
552
- /* @__PURE__ */ jsx("option", { value: 32, children: "32" })
553
- ]
554
- }
555
- )
556
- ] }),
557
- /* @__PURE__ */ jsxs("label", { style: { display: "flex", alignItems: "center", gap: 4, color: tokens.colors.text.muted }, children: [
558
- "Group:",
559
- /* @__PURE__ */ jsxs(
560
- "select",
561
- {
562
- "aria-label": "Byte grouping",
563
- value: localGrouping,
564
- onChange: (e) => setLocalGrouping(Number(e.target.value)),
565
- style: {
566
- background: tokens.colors.background.base,
567
- color: tokens.colors.text.primary,
568
- border: `1px solid ${tokens.colors.border.muted}`,
569
- borderRadius: tokens.borderRadius.md,
570
- padding: "2px 4px",
571
- fontSize: 11
572
- },
573
- children: [
574
- /* @__PURE__ */ jsx("option", { value: 1, children: "1" }),
575
- /* @__PURE__ */ jsx("option", { value: 2, children: "2" }),
576
- /* @__PURE__ */ jsx("option", { value: 4, children: "4" }),
577
- /* @__PURE__ */ jsx("option", { value: 8, children: "8" })
578
- ]
579
- }
580
- )
581
- ] }),
582
- /* @__PURE__ */ jsxs("label", { style: { display: "flex", alignItems: "center", gap: 4, color: tokens.colors.text.muted }, children: [
583
- "Offset:",
584
- /* @__PURE__ */ jsxs(
585
- "select",
586
- {
587
- "aria-label": "Offset format",
588
- value: localOffsetBase,
589
- onChange: (e) => setLocalOffsetBase(e.target.value),
590
- style: {
591
- background: tokens.colors.background.base,
592
- color: tokens.colors.text.primary,
593
- border: `1px solid ${tokens.colors.border.muted}`,
594
- borderRadius: tokens.borderRadius.md,
595
- padding: "2px 4px",
596
- fontSize: 11
597
- },
598
- children: [
599
- /* @__PURE__ */ jsx("option", { value: "hex", children: "Hex" }),
600
- /* @__PURE__ */ jsx("option", { value: "decimal", children: "Dec" })
601
- ]
602
- }
603
- )
604
- ] }),
605
- /* @__PURE__ */ jsxs("label", { style: { display: "flex", alignItems: "center", gap: 4, color: tokens.colors.text.muted }, children: [
606
- "Endian:",
607
- /* @__PURE__ */ jsxs(
608
- "select",
609
- {
610
- "aria-label": "Endianness",
611
- value: localEndianness,
612
- onChange: (e) => setLocalEndianness(e.target.value),
613
- style: {
614
- background: tokens.colors.background.base,
615
- color: tokens.colors.text.primary,
616
- border: `1px solid ${tokens.colors.border.muted}`,
617
- borderRadius: tokens.borderRadius.md,
618
- padding: "2px 4px",
619
- fontSize: 11
620
- },
621
- children: [
622
- /* @__PURE__ */ jsx("option", { value: "big", children: "Big" }),
623
- /* @__PURE__ */ jsx("option", { value: "little", children: "Little" })
624
- ]
625
- }
626
- )
627
- ] }),
628
- /* @__PURE__ */ jsxs("label", { style: { display: "flex", alignItems: "center", gap: 4, color: tokens.colors.text.muted, cursor: "pointer" }, children: [
629
- /* @__PURE__ */ jsx(
630
- "input",
631
- {
632
- type: "checkbox",
633
- checked: localShowAscii,
634
- onChange: (e) => setLocalShowAscii(e.target.checked),
635
- style: { accentColor: tokens.colors.interactive.default }
636
- }
637
- ),
638
- "ASCII"
639
- ] }),
640
- /* @__PURE__ */ jsx("div", { style: { flex: 1 } }),
641
- selectStart >= 0 && selectEnd >= 0 && /* @__PURE__ */ jsx("div", { style: { display: "flex", gap: 4 }, children: ["hex", "decimal", "ascii"].map((fmt) => /* @__PURE__ */ jsxs(
642
- "button",
643
- {
644
- onClick: () => copySelection(fmt),
645
- style: {
646
- background: tokens.colors.background.base,
647
- color: tokens.colors.text.secondary,
648
- border: `1px solid ${tokens.colors.border.muted}`,
649
- borderRadius: tokens.borderRadius.md,
650
- padding: "2px 8px",
651
- fontSize: 10,
652
- cursor: "pointer"
653
- },
654
- children: [
655
- "Copy ",
656
- fmt
657
- ]
658
- },
659
- fmt
660
- )) })
661
- ] }),
662
- showSearch && /* @__PURE__ */ jsxs("div", { role: "search", style: {
663
- padding: `${tokens.spacing.sm} ${tokens.spacing.smd}`,
664
- borderBottom: `1px solid ${tokens.colors.border.muted}`,
665
- display: "flex",
666
- alignItems: "center",
667
- gap: tokens.spacing.sm
668
- }, children: [
669
- /* @__PURE__ */ jsx(
670
- "label",
671
- {
672
- htmlFor: "hex-search-type",
673
- style: { position: "absolute", width: 1, height: 1, padding: 0, margin: -1, overflow: "hidden", clip: "rect(0, 0, 0, 0)", border: 0 },
674
- children: "Search type"
675
- }
676
- ),
677
- /* @__PURE__ */ jsxs(
678
- "select",
679
- {
680
- id: "hex-search-type",
681
- value: searchType,
682
- onChange: (e) => setSearchType(e.target.value),
683
- style: {
684
- background: tokens.colors.background.base,
685
- color: tokens.colors.text.primary,
686
- border: `1px solid ${tokens.colors.border.muted}`,
687
- borderRadius: tokens.borderRadius.md,
688
- padding: `${tokens.spacing.xs} ${tokens.spacing.sm}`,
689
- fontSize: tokens.typography.fontSize.sm
690
- },
691
- children: [
692
- /* @__PURE__ */ jsx("option", { value: "hex", children: "Hex" }),
693
- /* @__PURE__ */ jsx("option", { value: "ascii", children: "ASCII" })
694
- ]
695
- }
696
- ),
697
- /* @__PURE__ */ jsx(
698
- "input",
699
- {
700
- type: "text",
701
- value: searchQuery,
702
- onChange: (e) => setSearchQuery(e.target.value),
703
- onKeyDown: (e) => {
704
- if (e.key === "Enter") doSearch();
705
- },
706
- placeholder: searchType === "hex" ? "e.g. 48 65 6C 6C 6F" : "Search ASCII...",
707
- "aria-label": "Search hex data",
708
- style: {
709
- flex: 1,
710
- background: tokens.colors.background.base,
711
- color: tokens.colors.text.primary,
712
- border: `1px solid ${tokens.colors.border.muted}`,
713
- borderRadius: tokens.borderRadius.md,
714
- padding: `${tokens.spacing.xs} ${tokens.spacing.sm}`,
715
- fontSize: tokens.typography.fontSize.sm,
716
- outline: "none",
717
- fontFamily: "inherit"
718
- }
719
- }
720
- ),
721
- /* @__PURE__ */ jsx("button", { onClick: doSearch, "aria-label": "Find matches", style: {
722
- background: tokens.colors.interactive.default,
723
- color: tokens.colors.text.inverse,
724
- border: "none",
725
- borderRadius: tokens.borderRadius.md,
726
- padding: `${tokens.spacing.xs} ${tokens.spacing.smd}`,
727
- fontSize: tokens.typography.fontSize.sm,
728
- cursor: "pointer",
729
- fontWeight: tokens.typography.fontWeight.medium
730
- }, children: "Find" }),
731
- searchResults.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
732
- /* @__PURE__ */ jsxs("span", { style: { fontSize: tokens.typography.fontSize.sm, color: tokens.colors.text.muted }, "aria-live": "polite", children: [
733
- currentSearchIdx + 1,
734
- "/",
735
- searchResults.length
736
- ] }),
737
- /* @__PURE__ */ jsx("button", { onClick: prevSearchResult, "aria-label": "Previous match", style: { background: "none", border: "none", color: tokens.colors.text.secondary, cursor: "pointer", fontSize: tokens.typography.fontSize.base, padding: tokens.spacing.xs }, children: "▲" }),
738
- /* @__PURE__ */ jsx("button", { onClick: nextSearchResult, "aria-label": "Next match", style: { background: "none", border: "none", color: tokens.colors.text.secondary, cursor: "pointer", fontSize: tokens.typography.fontSize.base, padding: tokens.spacing.xs }, children: "▼" })
739
- ] })
740
- ] })
741
- ] }),
742
- shouldShowHeaderSummary && packetHeaders && packetHeaders.length > 0 && /* @__PURE__ */ jsxs("div", { style: { flexShrink: 0 }, children: [
743
- /* @__PURE__ */ jsxs(
744
- "div",
745
- {
746
- role: "button",
747
- tabIndex: 0,
748
- "aria-expanded": showHeaders,
749
- "aria-label": "Toggle packet headers",
750
- onClick: () => setShowHeaders(!showHeaders),
751
- onKeyDown: (e) => {
752
- if (e.key === "Enter" || e.key === " ") {
753
- e.preventDefault();
754
- setShowHeaders(!showHeaders);
755
- }
756
- },
757
- style: {
758
- display: "flex",
759
- alignItems: "center",
760
- gap: tokens.spacing.sm,
761
- padding: `${tokens.spacing.sm} ${tokens.spacing.smd}`,
762
- background: tokens.colors.background.surface,
763
- cursor: "pointer",
764
- borderBottom: `1px solid ${tokens.colors.border.muted}`,
765
- userSelect: "none"
766
- },
767
- children: [
768
- /* @__PURE__ */ jsx("span", { "aria-hidden": "true", style: { fontSize: tokens.typography.fontSize.micro, color: tokens.colors.text.muted, transition: "transform 150ms ease", transform: showHeaders ? "rotate(0deg)" : "rotate(-90deg)" }, children: "▼" }),
769
- /* @__PURE__ */ jsx("span", { style: { fontSize: tokens.typography.fontSize.sm, color: tokens.colors.text.secondary, fontWeight: tokens.typography.fontWeight.medium }, children: "Packet Headers" })
770
- ]
771
- }
772
- ),
773
- showHeaders && /* @__PURE__ */ jsx("div", { style: {
774
- padding: `${tokens.spacing.sm} ${tokens.spacing.smd}`,
775
- borderBottom: `1px solid ${tokens.colors.border.muted}`,
776
- background: tokens.colors.background.base
777
- }, children: /* @__PURE__ */ jsx("div", { style: {
778
- display: "grid",
779
- gridTemplateColumns: "auto 1fr",
780
- gap: `${tokens.spacing.xxs} ${tokens.spacing.md}`,
781
- fontSize: tokens.typography.fontSize.sm,
782
- lineHeight: tokens.typography.lineHeight.tight
783
- }, children: packetHeaders.map((entry, i) => /* @__PURE__ */ jsxs(React.Fragment, { children: [
784
- /* @__PURE__ */ jsx("span", { style: { color: tokens.colors.text.muted }, children: entry.label }),
785
- /* @__PURE__ */ jsx("span", { style: {
786
- color: tokens.colors.text.primary,
787
- fontWeight: tokens.typography.fontWeight.medium,
788
- fontFamily: entry.mono ? tokens.typography.fontFamily.mono : "inherit"
789
- }, children: entry.value })
790
- ] }, i)) }) })
791
- ] }),
792
- shouldShowFieldsTable && decodedFields && decodedFields.length > 0 && /* @__PURE__ */ jsxs("div", { style: { flexShrink: 0 }, children: [
793
- /* @__PURE__ */ jsxs(
794
- "div",
795
- {
796
- role: "button",
797
- tabIndex: 0,
798
- "aria-expanded": showFields,
799
- "aria-label": "Toggle decoded fields",
800
- onClick: () => setShowFields(!showFields),
801
- onKeyDown: (e) => {
802
- if (e.key === "Enter" || e.key === " ") {
803
- e.preventDefault();
804
- setShowFields(!showFields);
805
- }
806
- },
807
- style: {
808
- display: "flex",
809
- alignItems: "center",
810
- gap: tokens.spacing.sm,
811
- padding: `${tokens.spacing.sm} ${tokens.spacing.smd}`,
812
- background: tokens.colors.background.surface,
813
- cursor: "pointer",
814
- borderBottom: `1px solid ${tokens.colors.border.muted}`,
815
- userSelect: "none"
816
- },
817
- children: [
818
- /* @__PURE__ */ jsx("span", { "aria-hidden": "true", style: { fontSize: tokens.typography.fontSize.micro, color: tokens.colors.text.muted, transition: "transform 150ms ease", transform: showFields ? "rotate(0deg)" : "rotate(-90deg)" }, children: "▼" }),
819
- /* @__PURE__ */ jsxs("span", { style: { fontSize: tokens.typography.fontSize.sm, color: tokens.colors.text.secondary, fontWeight: tokens.typography.fontWeight.medium }, children: [
820
- "Decoded Fields (",
821
- decodedFields.length,
822
- ")"
823
- ] })
824
- ]
825
- }
826
- ),
827
- showFields && /* @__PURE__ */ jsxs(
828
- "div",
829
- {
830
- role: "table",
831
- "aria-label": "Decoded packet fields",
832
- style: {
833
- borderBottom: `1px solid ${tokens.colors.border.muted}`,
834
- maxHeight: decodedFieldsMaxHeight,
835
- overflowY: "auto"
836
- },
837
- children: [
838
- /* @__PURE__ */ jsxs("div", { role: "row", style: {
839
- display: "flex",
840
- alignItems: "center",
841
- padding: `${tokens.spacing.xs} ${tokens.spacing.smd}`,
842
- borderBottom: `1px solid ${tokens.colors.border.muted}`,
843
- gap: tokens.spacing.sm,
844
- position: "sticky",
845
- top: 0,
846
- background: tokens.colors.background.surface,
847
- zIndex: 1
848
- }, children: [
849
- /* @__PURE__ */ jsx("span", { role: "columnheader", style: { flex: 2, fontSize: tokens.typography.fontSize.xxs, color: tokens.colors.text.muted, textTransform: "uppercase", letterSpacing: "0.5px" }, children: "Field" }),
850
- /* @__PURE__ */ jsx("span", { role: "columnheader", style: { flex: 2, fontSize: tokens.typography.fontSize.xxs, color: tokens.colors.text.muted, textTransform: "uppercase", letterSpacing: "0.5px", textAlign: "right" }, children: "Value" }),
851
- /* @__PURE__ */ jsx("span", { role: "columnheader", style: { flex: 1, fontSize: tokens.typography.fontSize.xxs, color: tokens.colors.text.muted, textTransform: "uppercase", letterSpacing: "0.5px", textAlign: "right" }, children: "Unit" }),
852
- /* @__PURE__ */ jsx("span", { role: "columnheader", style: { flex: 1, fontSize: tokens.typography.fontSize.xxs, color: tokens.colors.text.muted, textTransform: "uppercase", letterSpacing: "0.5px", textAlign: "right" }, children: "Offset" }),
853
- /* @__PURE__ */ jsx("span", { role: "columnheader", style: { flex: 1, fontSize: tokens.typography.fontSize.xxs, color: tokens.colors.text.muted, textTransform: "uppercase", letterSpacing: "0.5px", textAlign: "right" }, children: "Size" })
854
- ] }),
855
- decodedFields.map((field, fi) => {
856
- const isHovered = hoveredFieldIdx === fi;
857
- return /* @__PURE__ */ jsxs(
858
- "div",
859
- {
860
- onMouseEnter: () => handleFieldHover(fi),
861
- onMouseLeave: handleFieldLeave,
862
- title: field.description,
863
- style: {
864
- display: "flex",
865
- alignItems: "center",
866
- padding: `3px ${tokens.spacing.smd}`,
867
- gap: tokens.spacing.sm,
868
- background: isHovered ? `${tokens.colors.status.caution}14` : fi % 2 === 0 ? tokens.colors.background.base : "transparent",
869
- transition: "background 0.15s ease",
870
- cursor: "default",
871
- minHeight: tokens.elementSize.sm
872
- },
873
- children: [
874
- /* @__PURE__ */ jsx("span", { role: "cell", style: {
875
- flex: 2,
876
- fontSize: tokens.typography.fontSize.sm,
877
- color: tokens.colors.text.secondary,
878
- overflow: "hidden",
879
- textOverflow: "ellipsis",
880
- whiteSpace: "nowrap"
881
- }, children: field.name }),
882
- /* @__PURE__ */ jsx("span", { role: "cell", style: {
883
- flex: 2,
884
- fontSize: tokens.typography.fontSize.sm,
885
- color: tokens.colors.text.primary,
886
- fontWeight: tokens.typography.fontWeight.medium,
887
- fontFamily: tokens.typography.fontFamily.mono,
888
- textAlign: "right"
889
- }, children: field.value === null || field.value === void 0 ? "—" : String(field.value) }),
890
- /* @__PURE__ */ jsx("span", { role: "cell", style: {
891
- flex: 1,
892
- fontSize: tokens.typography.fontSize.xxs,
893
- color: tokens.colors.text.muted,
894
- textAlign: "right"
895
- }, children: field.unit || "" }),
896
- /* @__PURE__ */ jsx("span", { role: "cell", style: {
897
- flex: 1,
898
- fontSize: tokens.typography.fontSize.xxs,
899
- color: tokens.colors.text.muted,
900
- textAlign: "right",
901
- fontFamily: tokens.typography.fontFamily.mono
902
- }, children: field.byteOffset !== void 0 ? field.byteOffset : "" }),
903
- /* @__PURE__ */ jsx("span", { role: "cell", style: {
904
- flex: 1,
905
- fontSize: tokens.typography.fontSize.xxs,
906
- color: tokens.colors.text.muted,
907
- textAlign: "right",
908
- fontFamily: tokens.typography.fontFamily.mono
909
- }, children: field.sizeBits ? `${field.sizeBits}b` : "" })
910
- ]
911
- },
912
- fi
913
- );
914
- })
915
- ]
916
- }
917
- )
918
- ] }),
919
- regions.length > 0 && /* @__PURE__ */ jsx(
920
- "div",
921
- {
922
- role: "group",
923
- "aria-label": "Packet structure regions",
924
- style: {
925
- padding: `${tokens.spacing.xs} ${tokens.spacing.smd}`,
926
- borderBottom: `1px solid ${tokens.colors.border.muted}`,
927
- display: "flex",
928
- alignItems: "center",
929
- gap: tokens.spacing.smd,
930
- flexWrap: "wrap",
931
- fontSize: tokens.typography.fontSize.xxs,
932
- background: tokens.colors.background.surface
933
- },
934
- children: regions.map((r, i) => /* @__PURE__ */ jsxs("span", { style: { display: "inline-flex", alignItems: "center", gap: tokens.spacing.xs }, children: [
935
- /* @__PURE__ */ jsx("span", { "aria-hidden": "true", style: {
936
- display: "inline-block",
937
- width: 10,
938
- height: 10,
939
- borderRadius: tokens.borderRadius.sm,
940
- background: r.color,
941
- border: `1px solid ${r.borderColor || "transparent"}`
942
- } }),
943
- /* @__PURE__ */ jsxs("span", { style: { color: tokens.colors.text.muted }, children: [
944
- r.label,
945
- " (",
946
- r.end - r.start,
947
- "B)"
948
- ] })
949
- ] }, i))
950
- }
951
- ),
952
- /* @__PURE__ */ jsxs("div", { role: "row", "aria-label": "Column headers", style: {
953
- padding: `${tokens.spacing.xs} ${tokens.spacing.smd}`,
954
- borderBottom: `1px solid ${tokens.colors.border.muted}`,
955
- display: "flex",
956
- alignItems: "center",
957
- fontSize: tokens.typography.fontSize.xxs,
958
- color: tokens.colors.text.muted,
959
- textTransform: "uppercase",
960
- letterSpacing: "0.5px",
961
- fontWeight: tokens.typography.fontWeight.medium,
962
- background: tokens.colors.background.surface
963
- }, children: [
964
- showOffset && /* @__PURE__ */ jsx("span", { style: { marginRight: tokens.spacing.smd, minWidth: 60, textAlign: "right" }, children: "Offset" }),
965
- /* @__PURE__ */ jsx("span", { style: { flex: 1 }, children: "Hex" }),
966
- localShowAscii && /* @__PURE__ */ jsxs(Fragment, { children: [
967
- /* @__PURE__ */ jsx("span", { "aria-hidden": "true", style: { color: `${tokens.colors.border.muted}`, marginRight: tokens.spacing.sm }, children: "|" }),
968
- /* @__PURE__ */ jsx("span", { children: "ASCII" })
969
- ] })
970
- ] }),
971
- /* @__PURE__ */ jsx(
972
- "div",
973
- {
974
- ref: scrollRef,
975
- role: "grid",
976
- "aria-label": "Hex data",
977
- tabIndex: 0,
978
- onScroll: handleHexScroll,
979
- style: {
980
- flex: "1 1 160px",
981
- minHeight: 160,
982
- overflow: "auto",
983
- padding: `${tokens.spacing.xs} ${tokens.spacing.smd}`
984
- },
985
- children: /* @__PURE__ */ jsx("div", { style: { height: totalRows * HEX_ROW_HEIGHT, position: "relative" }, children: /* @__PURE__ */ jsx("div", { style: { position: "absolute", top: startRow * HEX_ROW_HEIGHT, left: 0, right: 0 }, children: virtualRows.map((row) => /* @__PURE__ */ jsx(
986
- HexRow,
987
- {
988
- offset: row.offset,
989
- bytes: row.bytes,
990
- bytesPerRow: localBytesPerRow,
991
- byteGrouping: localGrouping,
992
- offsetBase: localOffsetBase,
993
- totalBytes: data.length,
994
- showAscii: localShowAscii,
995
- showOffset,
996
- highlights: allHighlights,
997
- regions: sortedRegions,
998
- effectiveHighlight,
999
- selectedStart: selectStart,
1000
- selectedEnd: selectEnd,
1001
- cursorOffset,
1002
- onByteMouseDown: handleByteMouseDown,
1003
- onByteMouseEnter: handleByteMouseEnter,
1004
- onByteClick: handleByteClickCb,
1005
- handleByteHoverInternal,
1006
- handleByteLeaveInternal,
1007
- tokens
1008
- },
1009
- row.offset
1010
- )) }) })
1011
- }
1012
- ),
1013
- highlights.length > 0 && /* @__PURE__ */ jsx("div", { role: "group", "aria-label": "Search highlights", style: {
1014
- padding: `${tokens.spacing.xs} ${tokens.spacing.smd}`,
1015
- borderTop: `1px solid ${tokens.colors.border.muted}`,
1016
- display: "flex",
1017
- alignItems: "center",
1018
- gap: tokens.spacing.smd,
1019
- flexWrap: "wrap",
1020
- fontSize: tokens.typography.fontSize.xxs,
1021
- background: tokens.colors.background.surface
1022
- }, children: highlights.filter((h) => h.label).map((h, i) => /* @__PURE__ */ jsxs("span", { style: { display: "flex", alignItems: "center", gap: tokens.spacing.xs }, children: [
1023
- /* @__PURE__ */ jsx("span", { "aria-hidden": "true", style: { width: 10, height: 10, borderRadius: tokens.borderRadius.sm, background: h.color, display: "inline-block" } }),
1024
- /* @__PURE__ */ jsxs("span", { style: { color: tokens.colors.text.muted }, children: [
1025
- h.label,
1026
- " (0x",
1027
- h.start.toString(16),
1028
- "-0x",
1029
- h.end.toString(16),
1030
- ")"
1031
- ] })
1032
- ] }, i)) }),
1033
- showStatusBar && /* @__PURE__ */ jsxs("div", { role: "status", "aria-live": "polite", style: {
1034
- padding: `${tokens.spacing.sm} ${tokens.spacing.smd}`,
1035
- borderTop: `1px solid ${tokens.colors.border.muted}`,
1036
- display: "flex",
1037
- alignItems: "center",
1038
- gap: tokens.spacing.md,
1039
- fontSize: tokens.typography.fontSize.sm,
1040
- background: tokens.colors.background.surface,
1041
- color: tokens.colors.text.muted,
1042
- flexWrap: "wrap"
1043
- }, children: [
1044
- /* @__PURE__ */ jsxs("span", { children: [
1045
- "Size: ",
1046
- data.length.toLocaleString(),
1047
- " bytes"
1048
- ] }),
1049
- cursorOffset >= 0 && cursorOffset < data.length && /* @__PURE__ */ jsxs(Fragment, { children: [
1050
- /* @__PURE__ */ jsxs("span", { children: [
1051
- "Offset: 0x",
1052
- cursorOffset.toString(16).toUpperCase(),
1053
- " (",
1054
- cursorOffset,
1055
- ")"
1056
- ] }),
1057
- /* @__PURE__ */ jsxs("span", { children: [
1058
- "Value: 0x",
1059
- byteToHex(data[cursorOffset]),
1060
- " (",
1061
- data[cursorOffset],
1062
- ")"
1063
- ] })
1064
- ] }),
1065
- selectStart >= 0 && selectEnd >= 0 && selectStart !== selectEnd && /* @__PURE__ */ jsxs("span", { children: [
1066
- "Selection: ",
1067
- Math.abs(selectEnd - selectStart) + 1,
1068
- " bytes (0x",
1069
- Math.min(selectStart, selectEnd).toString(16).toUpperCase(),
1070
- "-0x",
1071
- Math.max(selectStart, selectEnd).toString(16).toUpperCase(),
1072
- ")"
1073
- ] }),
1074
- interpretation && /* @__PURE__ */ jsxs("span", { style: { display: "flex", gap: tokens.spacing.sm, color: tokens.colors.text.secondary }, children: [
1075
- /* @__PURE__ */ jsxs("span", { children: [
1076
- "U8: ",
1077
- interpretation.u8
1078
- ] }),
1079
- /* @__PURE__ */ jsxs("span", { children: [
1080
- "I8: ",
1081
- interpretation.i8
1082
- ] }),
1083
- interpretation.u16 !== void 0 && /* @__PURE__ */ jsxs("span", { children: [
1084
- "U16: ",
1085
- interpretation.u16
1086
- ] }),
1087
- interpretation.u32 !== void 0 && /* @__PURE__ */ jsxs("span", { children: [
1088
- "U32: ",
1089
- interpretation.u32
1090
- ] }),
1091
- interpretation.f32 !== void 0 && /* @__PURE__ */ jsxs("span", { children: [
1092
- "F32: ",
1093
- interpretation.f32.toFixed(6)
1094
- ] })
1095
- ] })
1096
- ] })
1097
- ]
1098
- }
1099
- );
227
+ function HexViewer({ data: rawData, bytesPerRow = 16, byteGrouping = 1, offsetBase = "hex", highlights = [], regions = [], decodedFields, packetHeaders, externalHighlight, showAscii = true, showOffset = true, showStatusBar = true, showSearch = true, showToolbar = true, showFieldsTable, showHeaderSummary, height = 400, title, streaming = false, endianness = "big", onSelectionChange, onByteClick, onByteHover, onByteLeave, className = "", bordered = true, style: styleProp }) {
228
+ const { tokens } = useTheme();
229
+ const data = useMemo(() => toUint8Array(rawData), [rawData]);
230
+ const [hoveredFieldIdx, setHoveredFieldIdx] = useState(null);
231
+ const [fieldHighlight, setFieldHighlight] = useState(null);
232
+ const [showHeaders, setShowHeaders] = useState(true);
233
+ const [showFields, setShowFields] = useState(true);
234
+ const sortedRegions = useMemo(() => [...regions].sort((a, b) => a.start - b.start), [regions]);
235
+ const fieldByteRanges = useMemo(() => {
236
+ if (!decodedFields) return [];
237
+ return decodedFields.map((f) => ({
238
+ start: f.byteOffset ?? 0,
239
+ end: (f.byteOffset ?? 0) + Math.ceil((f.sizeBits ?? 0) / 8),
240
+ name: f.name
241
+ }));
242
+ }, [decodedFields]);
243
+ const handleFieldHover = useCallback((fieldIdx) => {
244
+ setHoveredFieldIdx(fieldIdx);
245
+ const range = fieldByteRanges[fieldIdx];
246
+ if (range && range.end > range.start) setFieldHighlight(range);
247
+ }, [fieldByteRanges]);
248
+ const handleFieldLeave = useCallback(() => {
249
+ setHoveredFieldIdx(null);
250
+ setFieldHighlight(null);
251
+ }, []);
252
+ const handleByteHoverInternal = useCallback((byteIdx) => {
253
+ onByteHover?.(byteIdx);
254
+ const fi = fieldByteRanges.findIndex((r) => byteIdx >= r.start && byteIdx < r.end);
255
+ if (fi >= 0) {
256
+ setHoveredFieldIdx(fi);
257
+ setFieldHighlight(fieldByteRanges[fi]);
258
+ } else {
259
+ setHoveredFieldIdx(null);
260
+ setFieldHighlight({
261
+ start: byteIdx,
262
+ end: byteIdx + 1
263
+ });
264
+ }
265
+ }, [fieldByteRanges, onByteHover]);
266
+ const handleByteLeaveInternal = useCallback(() => {
267
+ onByteLeave?.();
268
+ setHoveredFieldIdx(null);
269
+ setFieldHighlight(null);
270
+ }, [onByteLeave]);
271
+ const effectiveHighlight = externalHighlight || fieldHighlight;
272
+ const shouldShowFieldsTable = showFieldsTable ?? (decodedFields && decodedFields.length > 0);
273
+ const shouldShowHeaderSummary = showHeaderSummary ?? (packetHeaders && packetHeaders.length > 0);
274
+ const scrollRef = useRef(null);
275
+ const hasConstrainedHeight = useMemo(() => {
276
+ if (height === void 0 || height === null) return false;
277
+ if (typeof height === "string") return height.trim().toLowerCase() !== "auto";
278
+ return true;
279
+ }, [height]);
280
+ const decodedFieldsMaxHeight = useMemo(() => {
281
+ if (typeof height === "number") return Math.min(220, Math.max(120, Math.floor(height * .32)));
282
+ return 200;
283
+ }, [height]);
284
+ const [cursorOffset, setCursorOffset] = useState(-1);
285
+ const [selectStart, setSelectStart] = useState(-1);
286
+ const [selectEnd, setSelectEnd] = useState(-1);
287
+ const [isDragging, setIsDragging] = useState(false);
288
+ const [searchQuery, setSearchQuery] = useState("");
289
+ const [searchType, setSearchType] = useState("hex");
290
+ const [searchResults, setSearchResults] = useState([]);
291
+ const [currentSearchIdx, setCurrentSearchIdx] = useState(-1);
292
+ const [localBytesPerRow, setLocalBytesPerRow] = useState(bytesPerRow);
293
+ const [localGrouping, setLocalGrouping] = useState(byteGrouping);
294
+ const [localOffsetBase, setLocalOffsetBase] = useState(offsetBase);
295
+ const [localEndianness, setLocalEndianness] = useState(endianness);
296
+ const [localShowAscii, setLocalShowAscii] = useState(showAscii);
297
+ const [scrollTop, setScrollTop] = useState(0);
298
+ const [viewportHeight, setViewportHeight] = useState(160);
299
+ useEffect(() => {
300
+ if (streaming && scrollRef.current) scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
301
+ }, [data, streaming]);
302
+ useEffect(() => {
303
+ const el = scrollRef.current;
304
+ if (!el) return void 0;
305
+ const updateViewportHeight = () => {
306
+ setViewportHeight(Math.max(120, el.clientHeight || 160));
307
+ };
308
+ updateViewportHeight();
309
+ if (typeof ResizeObserver !== "undefined") {
310
+ const observer = new ResizeObserver(updateViewportHeight);
311
+ observer.observe(el);
312
+ return () => observer.disconnect();
313
+ }
314
+ window.addEventListener("resize", updateViewportHeight);
315
+ return () => window.removeEventListener("resize", updateViewportHeight);
316
+ }, []);
317
+ const totalRows = Math.max(1, Math.ceil(data.length / localBytesPerRow));
318
+ const startRow = Math.max(0, Math.floor(scrollTop / HEX_ROW_HEIGHT) - HEX_OVERSCAN_ROWS);
319
+ const visibleRowCount = Math.ceil(viewportHeight / HEX_ROW_HEIGHT) + HEX_OVERSCAN_ROWS * 2;
320
+ const endRow = Math.min(totalRows, startRow + visibleRowCount);
321
+ const virtualRows = useMemo(() => {
322
+ const result = [];
323
+ for (let row = startRow; row < endRow; row++) {
324
+ const offset = row * localBytesPerRow;
325
+ const bytes = data.subarray(offset, Math.min(data.length, offset + localBytesPerRow));
326
+ result.push({
327
+ offset,
328
+ bytes
329
+ });
330
+ }
331
+ if (result.length === 0) result.push({
332
+ offset: 0,
333
+ bytes: /* @__PURE__ */ new Uint8Array(0)
334
+ });
335
+ return result;
336
+ }, [
337
+ data,
338
+ localBytesPerRow,
339
+ startRow,
340
+ endRow
341
+ ]);
342
+ const doSearch = useCallback(() => {
343
+ if (!searchQuery.trim()) {
344
+ setSearchResults([]);
345
+ setCurrentSearchIdx(-1);
346
+ return;
347
+ }
348
+ const results = [];
349
+ if (searchType === "hex") {
350
+ const hexStr = searchQuery.replace(/\s+/g, "");
351
+ if (hexStr.length % 2 !== 0 || !/^[0-9a-fA-F]+$/.test(hexStr)) return;
352
+ const searchBytes = new Uint8Array(hexStr.length / 2);
353
+ for (let i = 0; i < searchBytes.length; i++) searchBytes[i] = parseInt(hexStr.slice(i * 2, i * 2 + 2), 16);
354
+ for (let i = 0; i <= data.length - searchBytes.length; i++) {
355
+ let match = true;
356
+ for (let j = 0; j < searchBytes.length; j++) if (data[i + j] !== searchBytes[j]) {
357
+ match = false;
358
+ break;
359
+ }
360
+ if (match) results.push(i);
361
+ }
362
+ } else {
363
+ const searchBytes = new TextEncoder().encode(searchQuery);
364
+ for (let i = 0; i <= data.length - searchBytes.length; i++) {
365
+ let match = true;
366
+ for (let j = 0; j < searchBytes.length; j++) if (data[i + j] !== searchBytes[j]) {
367
+ match = false;
368
+ break;
369
+ }
370
+ if (match) results.push(i);
371
+ }
372
+ }
373
+ setSearchResults(results);
374
+ setCurrentSearchIdx(results.length > 0 ? 0 : -1);
375
+ if (results.length > 0) {
376
+ setCursorOffset(results[0]);
377
+ if (scrollRef.current) {
378
+ const rowIdx = Math.floor(results[0] / localBytesPerRow);
379
+ scrollRef.current.scrollTop = Math.max(0, rowIdx * HEX_ROW_HEIGHT - 100);
380
+ }
381
+ }
382
+ }, [
383
+ searchQuery,
384
+ searchType,
385
+ data,
386
+ localBytesPerRow
387
+ ]);
388
+ const scrollToOffset = useCallback((offset) => {
389
+ if (!scrollRef.current) return;
390
+ const rowIdx = Math.floor(offset / localBytesPerRow);
391
+ scrollRef.current.scrollTop = Math.max(0, rowIdx * HEX_ROW_HEIGHT - 100);
392
+ }, [localBytesPerRow]);
393
+ const handleHexScroll = useCallback((event) => {
394
+ setScrollTop(event.currentTarget.scrollTop);
395
+ }, []);
396
+ const nextSearchResult = useCallback(() => {
397
+ if (searchResults.length === 0) return;
398
+ const nextIdx = (currentSearchIdx + 1) % searchResults.length;
399
+ setCurrentSearchIdx(nextIdx);
400
+ setCursorOffset(searchResults[nextIdx]);
401
+ scrollToOffset(searchResults[nextIdx]);
402
+ }, [
403
+ searchResults,
404
+ currentSearchIdx,
405
+ scrollToOffset
406
+ ]);
407
+ const prevSearchResult = useCallback(() => {
408
+ if (searchResults.length === 0) return;
409
+ const prevIdx = (currentSearchIdx - 1 + searchResults.length) % searchResults.length;
410
+ setCurrentSearchIdx(prevIdx);
411
+ setCursorOffset(searchResults[prevIdx]);
412
+ scrollToOffset(searchResults[prevIdx]);
413
+ }, [
414
+ searchResults,
415
+ currentSearchIdx,
416
+ scrollToOffset
417
+ ]);
418
+ const handleByteMouseDown = useCallback((idx) => {
419
+ setSelectStart(idx);
420
+ setSelectEnd(idx);
421
+ setCursorOffset(idx);
422
+ setIsDragging(true);
423
+ }, []);
424
+ const handleByteMouseEnter = useCallback((idx) => {
425
+ if (isDragging) setSelectEnd(idx);
426
+ }, [isDragging]);
427
+ const handleMouseUp = useCallback(() => {
428
+ if (isDragging) {
429
+ setIsDragging(false);
430
+ if (onSelectionChange && selectStart >= 0 && selectEnd >= 0) onSelectionChange(Math.min(selectStart, selectEnd), Math.max(selectStart, selectEnd));
431
+ }
432
+ }, [
433
+ isDragging,
434
+ selectStart,
435
+ selectEnd,
436
+ onSelectionChange
437
+ ]);
438
+ useEffect(() => {
439
+ window.addEventListener("mouseup", handleMouseUp);
440
+ return () => window.removeEventListener("mouseup", handleMouseUp);
441
+ }, [handleMouseUp]);
442
+ const handleByteClickCb = useCallback((idx) => {
443
+ setCursorOffset(idx);
444
+ if (onByteClick) onByteClick(idx, data[idx]);
445
+ }, [data, onByteClick]);
446
+ const copySelection = useCallback((format) => {
447
+ if (selectStart < 0 || selectEnd < 0) return;
448
+ const start = Math.min(selectStart, selectEnd);
449
+ const end = Math.max(selectStart, selectEnd);
450
+ const selected = data.slice(start, end + 1);
451
+ let text;
452
+ if (format === "hex") text = Array.from(selected).map((b) => byteToHex(b)).join(" ");
453
+ else if (format === "decimal") text = Array.from(selected).join(", ");
454
+ else text = Array.from(selected).map((b) => byteToAscii(b)).join("");
455
+ navigator.clipboard?.writeText(text);
456
+ }, [
457
+ data,
458
+ selectStart,
459
+ selectEnd
460
+ ]);
461
+ const interpretation = useMemo(() => {
462
+ if (cursorOffset < 0 || cursorOffset >= data.length) return null;
463
+ return interpretBytes(data, cursorOffset, localEndianness);
464
+ }, [
465
+ data,
466
+ cursorOffset,
467
+ localEndianness
468
+ ]);
469
+ const renderedSearchResultEntries = useMemo(() => {
470
+ if (searchResults.length <= MAX_RENDERED_SEARCH_HIGHLIGHTS) return searchResults.map((pos, idx) => ({
471
+ pos,
472
+ idx
473
+ }));
474
+ const start = Math.max(0, (currentSearchIdx >= 0 ? currentSearchIdx : 0) - Math.floor(MAX_RENDERED_SEARCH_HIGHLIGHTS / 2));
475
+ const end = Math.min(searchResults.length, start + MAX_RENDERED_SEARCH_HIGHLIGHTS);
476
+ const adjustedStart = Math.max(0, end - MAX_RENDERED_SEARCH_HIGHLIGHTS);
477
+ return searchResults.slice(adjustedStart, end).map((pos, i) => ({
478
+ pos,
479
+ idx: adjustedStart + i
480
+ }));
481
+ }, [searchResults, currentSearchIdx]);
482
+ const allHighlights = useMemo(() => {
483
+ const h = [...highlights];
484
+ const searchLen = searchType === "hex" ? searchQuery.replace(/\s+/g, "").length / 2 : searchQuery.length;
485
+ renderedSearchResultEntries.forEach(({ pos, idx }) => {
486
+ h.push({
487
+ start: pos,
488
+ end: pos + Math.max(0, searchLen - 1),
489
+ color: idx === currentSearchIdx ? tokens.colors.status.serious : tokens.colors.status.caution,
490
+ label: `Search match ${idx + 1}`
491
+ });
492
+ });
493
+ return h;
494
+ }, [
495
+ highlights,
496
+ renderedSearchResultEntries,
497
+ currentSearchIdx,
498
+ searchQuery,
499
+ searchType,
500
+ tokens
501
+ ]);
502
+ const containerStyle = {
503
+ fontFamily: tokens.typography.fontFamily.mono,
504
+ fontSize: 13,
505
+ lineHeight: "22px",
506
+ background: tokens.colors.background.base,
507
+ border: bordered ? `1px solid ${tokens.colors.border.muted}` : "none",
508
+ borderRadius: tokens.borderRadius.lg,
509
+ overflowX: "hidden",
510
+ overflowY: hasConstrainedHeight ? "auto" : "visible",
511
+ display: "flex",
512
+ flexDirection: "column",
513
+ minHeight: 0,
514
+ height: hasConstrainedHeight ? height : void 0,
515
+ color: tokens.colors.text.primary,
516
+ ...styleProp
517
+ };
518
+ return /* @__PURE__ */ jsxs("div", {
519
+ className: `hex-viewer ${className}`,
520
+ style: containerStyle,
521
+ children: [
522
+ /* @__PURE__ */ jsxs("div", {
523
+ style: {
524
+ position: "sticky",
525
+ top: 0,
526
+ zIndex: 10,
527
+ background: tokens.colors.background.surface,
528
+ flexShrink: 0
529
+ },
530
+ children: [
531
+ title && /* @__PURE__ */ jsxs("div", {
532
+ style: {
533
+ padding: `${tokens.spacing.sm} ${tokens.spacing.smd}`,
534
+ borderBottom: `1px solid ${tokens.colors.border.muted}`,
535
+ display: "flex",
536
+ alignItems: "center",
537
+ justifyContent: "space-between"
538
+ },
539
+ children: [/* @__PURE__ */ jsx("span", {
540
+ style: {
541
+ fontWeight: tokens.typography.fontWeight.medium,
542
+ fontSize: tokens.typography.fontSize.base,
543
+ fontFamily: tokens.typography.fontFamily.primary
544
+ },
545
+ children: title
546
+ }), /* @__PURE__ */ jsxs("span", {
547
+ style: {
548
+ fontSize: tokens.typography.fontSize.sm,
549
+ color: tokens.colors.text.muted
550
+ },
551
+ children: [data.length.toLocaleString(), " bytes"]
552
+ })]
553
+ }),
554
+ showToolbar && /* @__PURE__ */ jsxs("div", {
555
+ role: "toolbar",
556
+ "aria-label": "Hex viewer controls",
557
+ style: {
558
+ padding: `${tokens.spacing.sm} ${tokens.spacing.smd}`,
559
+ borderBottom: `1px solid ${tokens.colors.border.muted}`,
560
+ display: "flex",
561
+ alignItems: "center",
562
+ gap: tokens.spacing.sm,
563
+ flexWrap: "wrap",
564
+ fontSize: tokens.typography.fontSize.sm
565
+ },
566
+ children: [
567
+ /* @__PURE__ */ jsxs("label", {
568
+ style: {
569
+ display: "flex",
570
+ alignItems: "center",
571
+ gap: 4,
572
+ color: tokens.colors.text.muted
573
+ },
574
+ children: ["Bytes/Row:", /* @__PURE__ */ jsxs("select", {
575
+ "aria-label": "Bytes per row",
576
+ value: localBytesPerRow,
577
+ onChange: (e) => setLocalBytesPerRow(Number(e.target.value)),
578
+ style: {
579
+ background: tokens.colors.background.base,
580
+ color: tokens.colors.text.primary,
581
+ border: `1px solid ${tokens.colors.border.muted}`,
582
+ borderRadius: tokens.borderRadius.md,
583
+ padding: "2px 4px",
584
+ fontSize: 11
585
+ },
586
+ children: [
587
+ /* @__PURE__ */ jsx("option", {
588
+ value: 8,
589
+ children: "8"
590
+ }),
591
+ /* @__PURE__ */ jsx("option", {
592
+ value: 16,
593
+ children: "16"
594
+ }),
595
+ /* @__PURE__ */ jsx("option", {
596
+ value: 32,
597
+ children: "32"
598
+ })
599
+ ]
600
+ })]
601
+ }),
602
+ /* @__PURE__ */ jsxs("label", {
603
+ style: {
604
+ display: "flex",
605
+ alignItems: "center",
606
+ gap: 4,
607
+ color: tokens.colors.text.muted
608
+ },
609
+ children: ["Group:", /* @__PURE__ */ jsxs("select", {
610
+ "aria-label": "Byte grouping",
611
+ value: localGrouping,
612
+ onChange: (e) => setLocalGrouping(Number(e.target.value)),
613
+ style: {
614
+ background: tokens.colors.background.base,
615
+ color: tokens.colors.text.primary,
616
+ border: `1px solid ${tokens.colors.border.muted}`,
617
+ borderRadius: tokens.borderRadius.md,
618
+ padding: "2px 4px",
619
+ fontSize: 11
620
+ },
621
+ children: [
622
+ /* @__PURE__ */ jsx("option", {
623
+ value: 1,
624
+ children: "1"
625
+ }),
626
+ /* @__PURE__ */ jsx("option", {
627
+ value: 2,
628
+ children: "2"
629
+ }),
630
+ /* @__PURE__ */ jsx("option", {
631
+ value: 4,
632
+ children: "4"
633
+ }),
634
+ /* @__PURE__ */ jsx("option", {
635
+ value: 8,
636
+ children: "8"
637
+ })
638
+ ]
639
+ })]
640
+ }),
641
+ /* @__PURE__ */ jsxs("label", {
642
+ style: {
643
+ display: "flex",
644
+ alignItems: "center",
645
+ gap: 4,
646
+ color: tokens.colors.text.muted
647
+ },
648
+ children: ["Offset:", /* @__PURE__ */ jsxs("select", {
649
+ "aria-label": "Offset format",
650
+ value: localOffsetBase,
651
+ onChange: (e) => setLocalOffsetBase(e.target.value),
652
+ style: {
653
+ background: tokens.colors.background.base,
654
+ color: tokens.colors.text.primary,
655
+ border: `1px solid ${tokens.colors.border.muted}`,
656
+ borderRadius: tokens.borderRadius.md,
657
+ padding: "2px 4px",
658
+ fontSize: 11
659
+ },
660
+ children: [/* @__PURE__ */ jsx("option", {
661
+ value: "hex",
662
+ children: "Hex"
663
+ }), /* @__PURE__ */ jsx("option", {
664
+ value: "decimal",
665
+ children: "Dec"
666
+ })]
667
+ })]
668
+ }),
669
+ /* @__PURE__ */ jsxs("label", {
670
+ style: {
671
+ display: "flex",
672
+ alignItems: "center",
673
+ gap: 4,
674
+ color: tokens.colors.text.muted
675
+ },
676
+ children: ["Endian:", /* @__PURE__ */ jsxs("select", {
677
+ "aria-label": "Endianness",
678
+ value: localEndianness,
679
+ onChange: (e) => setLocalEndianness(e.target.value),
680
+ style: {
681
+ background: tokens.colors.background.base,
682
+ color: tokens.colors.text.primary,
683
+ border: `1px solid ${tokens.colors.border.muted}`,
684
+ borderRadius: tokens.borderRadius.md,
685
+ padding: "2px 4px",
686
+ fontSize: 11
687
+ },
688
+ children: [/* @__PURE__ */ jsx("option", {
689
+ value: "big",
690
+ children: "Big"
691
+ }), /* @__PURE__ */ jsx("option", {
692
+ value: "little",
693
+ children: "Little"
694
+ })]
695
+ })]
696
+ }),
697
+ /* @__PURE__ */ jsxs("label", {
698
+ style: {
699
+ display: "flex",
700
+ alignItems: "center",
701
+ gap: 4,
702
+ color: tokens.colors.text.muted,
703
+ cursor: "pointer"
704
+ },
705
+ children: [/* @__PURE__ */ jsx("input", {
706
+ type: "checkbox",
707
+ checked: localShowAscii,
708
+ onChange: (e) => setLocalShowAscii(e.target.checked),
709
+ style: { accentColor: tokens.colors.interactive.default }
710
+ }), "ASCII"]
711
+ }),
712
+ /* @__PURE__ */ jsx("div", { style: { flex: 1 } }),
713
+ selectStart >= 0 && selectEnd >= 0 && /* @__PURE__ */ jsx("div", {
714
+ style: {
715
+ display: "flex",
716
+ gap: 4
717
+ },
718
+ children: [
719
+ "hex",
720
+ "decimal",
721
+ "ascii"
722
+ ].map((fmt) => /* @__PURE__ */ jsxs("button", {
723
+ onClick: () => copySelection(fmt),
724
+ style: {
725
+ background: tokens.colors.background.base,
726
+ color: tokens.colors.text.secondary,
727
+ border: `1px solid ${tokens.colors.border.muted}`,
728
+ borderRadius: tokens.borderRadius.md,
729
+ padding: "2px 8px",
730
+ fontSize: 10,
731
+ cursor: "pointer"
732
+ },
733
+ children: ["Copy ", fmt]
734
+ }, fmt))
735
+ })
736
+ ]
737
+ }),
738
+ showSearch && /* @__PURE__ */ jsxs("div", {
739
+ role: "search",
740
+ style: {
741
+ padding: `${tokens.spacing.sm} ${tokens.spacing.smd}`,
742
+ borderBottom: `1px solid ${tokens.colors.border.muted}`,
743
+ display: "flex",
744
+ alignItems: "center",
745
+ gap: tokens.spacing.sm
746
+ },
747
+ children: [
748
+ /* @__PURE__ */ jsx("label", {
749
+ htmlFor: "hex-search-type",
750
+ style: {
751
+ position: "absolute",
752
+ width: 1,
753
+ height: 1,
754
+ padding: 0,
755
+ margin: -1,
756
+ overflow: "hidden",
757
+ clip: "rect(0, 0, 0, 0)",
758
+ border: 0
759
+ },
760
+ children: "Search type"
761
+ }),
762
+ /* @__PURE__ */ jsxs("select", {
763
+ id: "hex-search-type",
764
+ value: searchType,
765
+ onChange: (e) => setSearchType(e.target.value),
766
+ style: {
767
+ background: tokens.colors.background.base,
768
+ color: tokens.colors.text.primary,
769
+ border: `1px solid ${tokens.colors.border.muted}`,
770
+ borderRadius: tokens.borderRadius.md,
771
+ padding: `${tokens.spacing.xs} ${tokens.spacing.sm}`,
772
+ fontSize: tokens.typography.fontSize.sm
773
+ },
774
+ children: [/* @__PURE__ */ jsx("option", {
775
+ value: "hex",
776
+ children: "Hex"
777
+ }), /* @__PURE__ */ jsx("option", {
778
+ value: "ascii",
779
+ children: "ASCII"
780
+ })]
781
+ }),
782
+ /* @__PURE__ */ jsx("input", {
783
+ type: "text",
784
+ value: searchQuery,
785
+ onChange: (e) => setSearchQuery(e.target.value),
786
+ onKeyDown: (e) => {
787
+ if (e.key === "Enter") doSearch();
788
+ },
789
+ placeholder: searchType === "hex" ? "e.g. 48 65 6C 6C 6F" : "Search ASCII...",
790
+ "aria-label": "Search hex data",
791
+ style: {
792
+ flex: 1,
793
+ background: tokens.colors.background.base,
794
+ color: tokens.colors.text.primary,
795
+ border: `1px solid ${tokens.colors.border.muted}`,
796
+ borderRadius: tokens.borderRadius.md,
797
+ padding: `${tokens.spacing.xs} ${tokens.spacing.sm}`,
798
+ fontSize: tokens.typography.fontSize.sm,
799
+ outline: "none",
800
+ fontFamily: "inherit"
801
+ }
802
+ }),
803
+ /* @__PURE__ */ jsx("button", {
804
+ onClick: doSearch,
805
+ "aria-label": "Find matches",
806
+ style: {
807
+ background: tokens.colors.interactive.default,
808
+ color: tokens.colors.text.inverse,
809
+ border: "none",
810
+ borderRadius: tokens.borderRadius.md,
811
+ padding: `${tokens.spacing.xs} ${tokens.spacing.smd}`,
812
+ fontSize: tokens.typography.fontSize.sm,
813
+ cursor: "pointer",
814
+ fontWeight: tokens.typography.fontWeight.medium
815
+ },
816
+ children: "Find"
817
+ }),
818
+ searchResults.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
819
+ /* @__PURE__ */ jsxs("span", {
820
+ style: {
821
+ fontSize: tokens.typography.fontSize.sm,
822
+ color: tokens.colors.text.muted
823
+ },
824
+ "aria-live": "polite",
825
+ children: [
826
+ currentSearchIdx + 1,
827
+ "/",
828
+ searchResults.length
829
+ ]
830
+ }),
831
+ /* @__PURE__ */ jsx("button", {
832
+ onClick: prevSearchResult,
833
+ "aria-label": "Previous match",
834
+ style: {
835
+ background: "none",
836
+ border: "none",
837
+ color: tokens.colors.text.secondary,
838
+ cursor: "pointer",
839
+ fontSize: tokens.typography.fontSize.base,
840
+ padding: tokens.spacing.xs
841
+ },
842
+ children: "▲"
843
+ }),
844
+ /* @__PURE__ */ jsx("button", {
845
+ onClick: nextSearchResult,
846
+ "aria-label": "Next match",
847
+ style: {
848
+ background: "none",
849
+ border: "none",
850
+ color: tokens.colors.text.secondary,
851
+ cursor: "pointer",
852
+ fontSize: tokens.typography.fontSize.base,
853
+ padding: tokens.spacing.xs
854
+ },
855
+ children: "▼"
856
+ })
857
+ ] })
858
+ ]
859
+ })
860
+ ]
861
+ }),
862
+ shouldShowHeaderSummary && packetHeaders && packetHeaders.length > 0 && /* @__PURE__ */ jsxs("div", {
863
+ style: { flexShrink: 0 },
864
+ children: [/* @__PURE__ */ jsxs("div", {
865
+ role: "button",
866
+ tabIndex: 0,
867
+ "aria-expanded": showHeaders,
868
+ "aria-label": "Toggle packet headers",
869
+ onClick: () => setShowHeaders(!showHeaders),
870
+ onKeyDown: (e) => {
871
+ if (e.key === "Enter" || e.key === " ") {
872
+ e.preventDefault();
873
+ setShowHeaders(!showHeaders);
874
+ }
875
+ },
876
+ style: {
877
+ display: "flex",
878
+ alignItems: "center",
879
+ gap: tokens.spacing.sm,
880
+ padding: `${tokens.spacing.sm} ${tokens.spacing.smd}`,
881
+ background: tokens.colors.background.surface,
882
+ cursor: "pointer",
883
+ borderBottom: `1px solid ${tokens.colors.border.muted}`,
884
+ userSelect: "none"
885
+ },
886
+ children: [/* @__PURE__ */ jsx("span", {
887
+ "aria-hidden": "true",
888
+ style: {
889
+ fontSize: tokens.typography.fontSize.micro,
890
+ color: tokens.colors.text.muted,
891
+ transition: "transform 150ms ease",
892
+ transform: showHeaders ? "rotate(0deg)" : "rotate(-90deg)"
893
+ },
894
+ children: ""
895
+ }), /* @__PURE__ */ jsx("span", {
896
+ style: {
897
+ fontSize: tokens.typography.fontSize.sm,
898
+ color: tokens.colors.text.secondary,
899
+ fontWeight: tokens.typography.fontWeight.medium
900
+ },
901
+ children: "Packet Headers"
902
+ })]
903
+ }), showHeaders && /* @__PURE__ */ jsx("div", {
904
+ style: {
905
+ padding: `${tokens.spacing.sm} ${tokens.spacing.smd}`,
906
+ borderBottom: `1px solid ${tokens.colors.border.muted}`,
907
+ background: tokens.colors.background.base
908
+ },
909
+ children: /* @__PURE__ */ jsx("div", {
910
+ style: {
911
+ display: "grid",
912
+ gridTemplateColumns: "auto 1fr",
913
+ gap: `${tokens.spacing.xxs} ${tokens.spacing.md}`,
914
+ fontSize: tokens.typography.fontSize.sm,
915
+ lineHeight: tokens.typography.lineHeight.tight
916
+ },
917
+ children: packetHeaders.map((entry, i) => /* @__PURE__ */ jsxs(React.Fragment, { children: [/* @__PURE__ */ jsx("span", {
918
+ style: { color: tokens.colors.text.muted },
919
+ children: entry.label
920
+ }), /* @__PURE__ */ jsx("span", {
921
+ style: {
922
+ color: tokens.colors.text.primary,
923
+ fontWeight: tokens.typography.fontWeight.medium,
924
+ fontFamily: entry.mono ? tokens.typography.fontFamily.mono : "inherit"
925
+ },
926
+ children: entry.value
927
+ })] }, i))
928
+ })
929
+ })]
930
+ }),
931
+ shouldShowFieldsTable && decodedFields && decodedFields.length > 0 && /* @__PURE__ */ jsxs("div", {
932
+ style: { flexShrink: 0 },
933
+ children: [/* @__PURE__ */ jsxs("div", {
934
+ role: "button",
935
+ tabIndex: 0,
936
+ "aria-expanded": showFields,
937
+ "aria-label": "Toggle decoded fields",
938
+ onClick: () => setShowFields(!showFields),
939
+ onKeyDown: (e) => {
940
+ if (e.key === "Enter" || e.key === " ") {
941
+ e.preventDefault();
942
+ setShowFields(!showFields);
943
+ }
944
+ },
945
+ style: {
946
+ display: "flex",
947
+ alignItems: "center",
948
+ gap: tokens.spacing.sm,
949
+ padding: `${tokens.spacing.sm} ${tokens.spacing.smd}`,
950
+ background: tokens.colors.background.surface,
951
+ cursor: "pointer",
952
+ borderBottom: `1px solid ${tokens.colors.border.muted}`,
953
+ userSelect: "none"
954
+ },
955
+ children: [/* @__PURE__ */ jsx("span", {
956
+ "aria-hidden": "true",
957
+ style: {
958
+ fontSize: tokens.typography.fontSize.micro,
959
+ color: tokens.colors.text.muted,
960
+ transition: "transform 150ms ease",
961
+ transform: showFields ? "rotate(0deg)" : "rotate(-90deg)"
962
+ },
963
+ children: "▼"
964
+ }), /* @__PURE__ */ jsxs("span", {
965
+ style: {
966
+ fontSize: tokens.typography.fontSize.sm,
967
+ color: tokens.colors.text.secondary,
968
+ fontWeight: tokens.typography.fontWeight.medium
969
+ },
970
+ children: [
971
+ "Decoded Fields (",
972
+ decodedFields.length,
973
+ ")"
974
+ ]
975
+ })]
976
+ }), showFields && /* @__PURE__ */ jsxs("div", {
977
+ role: "table",
978
+ "aria-label": "Decoded packet fields",
979
+ style: {
980
+ borderBottom: `1px solid ${tokens.colors.border.muted}`,
981
+ maxHeight: decodedFieldsMaxHeight,
982
+ overflowY: "auto"
983
+ },
984
+ children: [/* @__PURE__ */ jsxs("div", {
985
+ role: "row",
986
+ style: {
987
+ display: "flex",
988
+ alignItems: "center",
989
+ padding: `${tokens.spacing.xs} ${tokens.spacing.smd}`,
990
+ borderBottom: `1px solid ${tokens.colors.border.muted}`,
991
+ gap: tokens.spacing.sm,
992
+ position: "sticky",
993
+ top: 0,
994
+ background: tokens.colors.background.surface,
995
+ zIndex: 1
996
+ },
997
+ children: [
998
+ /* @__PURE__ */ jsx("span", {
999
+ role: "columnheader",
1000
+ style: {
1001
+ flex: 2,
1002
+ fontSize: tokens.typography.fontSize.xxs,
1003
+ color: tokens.colors.text.muted,
1004
+ textTransform: "uppercase",
1005
+ letterSpacing: "0.5px"
1006
+ },
1007
+ children: "Field"
1008
+ }),
1009
+ /* @__PURE__ */ jsx("span", {
1010
+ role: "columnheader",
1011
+ style: {
1012
+ flex: 2,
1013
+ fontSize: tokens.typography.fontSize.xxs,
1014
+ color: tokens.colors.text.muted,
1015
+ textTransform: "uppercase",
1016
+ letterSpacing: "0.5px",
1017
+ textAlign: "right"
1018
+ },
1019
+ children: "Value"
1020
+ }),
1021
+ /* @__PURE__ */ jsx("span", {
1022
+ role: "columnheader",
1023
+ style: {
1024
+ flex: 1,
1025
+ fontSize: tokens.typography.fontSize.xxs,
1026
+ color: tokens.colors.text.muted,
1027
+ textTransform: "uppercase",
1028
+ letterSpacing: "0.5px",
1029
+ textAlign: "right"
1030
+ },
1031
+ children: "Unit"
1032
+ }),
1033
+ /* @__PURE__ */ jsx("span", {
1034
+ role: "columnheader",
1035
+ style: {
1036
+ flex: 1,
1037
+ fontSize: tokens.typography.fontSize.xxs,
1038
+ color: tokens.colors.text.muted,
1039
+ textTransform: "uppercase",
1040
+ letterSpacing: "0.5px",
1041
+ textAlign: "right"
1042
+ },
1043
+ children: "Offset"
1044
+ }),
1045
+ /* @__PURE__ */ jsx("span", {
1046
+ role: "columnheader",
1047
+ style: {
1048
+ flex: 1,
1049
+ fontSize: tokens.typography.fontSize.xxs,
1050
+ color: tokens.colors.text.muted,
1051
+ textTransform: "uppercase",
1052
+ letterSpacing: "0.5px",
1053
+ textAlign: "right"
1054
+ },
1055
+ children: "Size"
1056
+ })
1057
+ ]
1058
+ }), decodedFields.map((field, fi) => {
1059
+ const isHovered = hoveredFieldIdx === fi;
1060
+ return /* @__PURE__ */ jsxs("div", {
1061
+ onMouseEnter: () => handleFieldHover(fi),
1062
+ onMouseLeave: handleFieldLeave,
1063
+ title: field.description,
1064
+ style: {
1065
+ display: "flex",
1066
+ alignItems: "center",
1067
+ padding: `3px ${tokens.spacing.smd}`,
1068
+ gap: tokens.spacing.sm,
1069
+ background: isHovered ? `${tokens.colors.status.caution}14` : fi % 2 === 0 ? tokens.colors.background.base : "transparent",
1070
+ transition: "background 0.15s ease",
1071
+ cursor: "default",
1072
+ minHeight: tokens.elementSize.sm
1073
+ },
1074
+ children: [
1075
+ /* @__PURE__ */ jsx("span", {
1076
+ role: "cell",
1077
+ style: {
1078
+ flex: 2,
1079
+ fontSize: tokens.typography.fontSize.sm,
1080
+ color: tokens.colors.text.secondary,
1081
+ overflow: "hidden",
1082
+ textOverflow: "ellipsis",
1083
+ whiteSpace: "nowrap"
1084
+ },
1085
+ children: field.name
1086
+ }),
1087
+ /* @__PURE__ */ jsx("span", {
1088
+ role: "cell",
1089
+ style: {
1090
+ flex: 2,
1091
+ fontSize: tokens.typography.fontSize.sm,
1092
+ color: tokens.colors.text.primary,
1093
+ fontWeight: tokens.typography.fontWeight.medium,
1094
+ fontFamily: tokens.typography.fontFamily.mono,
1095
+ textAlign: "right"
1096
+ },
1097
+ children: field.value === null || field.value === void 0 ? "—" : String(field.value)
1098
+ }),
1099
+ /* @__PURE__ */ jsx("span", {
1100
+ role: "cell",
1101
+ style: {
1102
+ flex: 1,
1103
+ fontSize: tokens.typography.fontSize.xxs,
1104
+ color: tokens.colors.text.muted,
1105
+ textAlign: "right"
1106
+ },
1107
+ children: field.unit || ""
1108
+ }),
1109
+ /* @__PURE__ */ jsx("span", {
1110
+ role: "cell",
1111
+ style: {
1112
+ flex: 1,
1113
+ fontSize: tokens.typography.fontSize.xxs,
1114
+ color: tokens.colors.text.muted,
1115
+ textAlign: "right",
1116
+ fontFamily: tokens.typography.fontFamily.mono
1117
+ },
1118
+ children: field.byteOffset !== void 0 ? field.byteOffset : ""
1119
+ }),
1120
+ /* @__PURE__ */ jsx("span", {
1121
+ role: "cell",
1122
+ style: {
1123
+ flex: 1,
1124
+ fontSize: tokens.typography.fontSize.xxs,
1125
+ color: tokens.colors.text.muted,
1126
+ textAlign: "right",
1127
+ fontFamily: tokens.typography.fontFamily.mono
1128
+ },
1129
+ children: field.sizeBits ? `${field.sizeBits}b` : ""
1130
+ })
1131
+ ]
1132
+ }, fi);
1133
+ })]
1134
+ })]
1135
+ }),
1136
+ regions.length > 0 && /* @__PURE__ */ jsx("div", {
1137
+ role: "group",
1138
+ "aria-label": "Packet structure regions",
1139
+ style: {
1140
+ padding: `${tokens.spacing.xs} ${tokens.spacing.smd}`,
1141
+ borderBottom: `1px solid ${tokens.colors.border.muted}`,
1142
+ display: "flex",
1143
+ alignItems: "center",
1144
+ gap: tokens.spacing.smd,
1145
+ flexWrap: "wrap",
1146
+ fontSize: tokens.typography.fontSize.xxs,
1147
+ background: tokens.colors.background.surface
1148
+ },
1149
+ children: regions.map((r, i) => /* @__PURE__ */ jsxs("span", {
1150
+ style: {
1151
+ display: "inline-flex",
1152
+ alignItems: "center",
1153
+ gap: tokens.spacing.xs
1154
+ },
1155
+ children: [/* @__PURE__ */ jsx("span", {
1156
+ "aria-hidden": "true",
1157
+ style: {
1158
+ display: "inline-block",
1159
+ width: 10,
1160
+ height: 10,
1161
+ borderRadius: tokens.borderRadius.sm,
1162
+ background: r.color,
1163
+ border: `1px solid ${r.borderColor || "transparent"}`
1164
+ }
1165
+ }), /* @__PURE__ */ jsxs("span", {
1166
+ style: { color: tokens.colors.text.muted },
1167
+ children: [
1168
+ r.label,
1169
+ " (",
1170
+ r.end - r.start,
1171
+ "B)"
1172
+ ]
1173
+ })]
1174
+ }, i))
1175
+ }),
1176
+ /* @__PURE__ */ jsxs("div", {
1177
+ role: "row",
1178
+ "aria-label": "Column headers",
1179
+ style: {
1180
+ padding: `${tokens.spacing.xs} ${tokens.spacing.smd}`,
1181
+ borderBottom: `1px solid ${tokens.colors.border.muted}`,
1182
+ display: "flex",
1183
+ alignItems: "center",
1184
+ fontSize: tokens.typography.fontSize.xxs,
1185
+ color: tokens.colors.text.muted,
1186
+ textTransform: "uppercase",
1187
+ letterSpacing: "0.5px",
1188
+ fontWeight: tokens.typography.fontWeight.medium,
1189
+ background: tokens.colors.background.surface
1190
+ },
1191
+ children: [
1192
+ showOffset && /* @__PURE__ */ jsx("span", {
1193
+ style: {
1194
+ marginRight: tokens.spacing.smd,
1195
+ minWidth: 60,
1196
+ textAlign: "right"
1197
+ },
1198
+ children: "Offset"
1199
+ }),
1200
+ /* @__PURE__ */ jsx("span", {
1201
+ style: { flex: 1 },
1202
+ children: "Hex"
1203
+ }),
1204
+ localShowAscii && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", {
1205
+ "aria-hidden": "true",
1206
+ style: {
1207
+ color: `${tokens.colors.border.muted}`,
1208
+ marginRight: tokens.spacing.sm
1209
+ },
1210
+ children: "|"
1211
+ }), /* @__PURE__ */ jsx("span", { children: "ASCII" })] })
1212
+ ]
1213
+ }),
1214
+ /* @__PURE__ */ jsx("div", {
1215
+ ref: scrollRef,
1216
+ role: "grid",
1217
+ "aria-label": "Hex data",
1218
+ tabIndex: 0,
1219
+ onScroll: handleHexScroll,
1220
+ style: {
1221
+ flex: "1 1 160px",
1222
+ minHeight: 160,
1223
+ overflow: "auto",
1224
+ padding: `${tokens.spacing.xs} ${tokens.spacing.smd}`
1225
+ },
1226
+ children: /* @__PURE__ */ jsx("div", {
1227
+ style: {
1228
+ height: totalRows * HEX_ROW_HEIGHT,
1229
+ position: "relative"
1230
+ },
1231
+ children: /* @__PURE__ */ jsx("div", {
1232
+ style: {
1233
+ position: "absolute",
1234
+ top: startRow * HEX_ROW_HEIGHT,
1235
+ left: 0,
1236
+ right: 0
1237
+ },
1238
+ children: virtualRows.map((row) => /* @__PURE__ */ jsx(HexRow, {
1239
+ offset: row.offset,
1240
+ bytes: row.bytes,
1241
+ bytesPerRow: localBytesPerRow,
1242
+ byteGrouping: localGrouping,
1243
+ offsetBase: localOffsetBase,
1244
+ totalBytes: data.length,
1245
+ showAscii: localShowAscii,
1246
+ showOffset,
1247
+ highlights: allHighlights,
1248
+ regions: sortedRegions,
1249
+ effectiveHighlight,
1250
+ selectedStart: selectStart,
1251
+ selectedEnd: selectEnd,
1252
+ cursorOffset,
1253
+ onByteMouseDown: handleByteMouseDown,
1254
+ onByteMouseEnter: handleByteMouseEnter,
1255
+ onByteClick: handleByteClickCb,
1256
+ handleByteHoverInternal,
1257
+ handleByteLeaveInternal,
1258
+ tokens
1259
+ }, row.offset))
1260
+ })
1261
+ })
1262
+ }),
1263
+ highlights.length > 0 && /* @__PURE__ */ jsx("div", {
1264
+ role: "group",
1265
+ "aria-label": "Search highlights",
1266
+ style: {
1267
+ padding: `${tokens.spacing.xs} ${tokens.spacing.smd}`,
1268
+ borderTop: `1px solid ${tokens.colors.border.muted}`,
1269
+ display: "flex",
1270
+ alignItems: "center",
1271
+ gap: tokens.spacing.smd,
1272
+ flexWrap: "wrap",
1273
+ fontSize: tokens.typography.fontSize.xxs,
1274
+ background: tokens.colors.background.surface
1275
+ },
1276
+ children: highlights.filter((h) => h.label).map((h, i) => /* @__PURE__ */ jsxs("span", {
1277
+ style: {
1278
+ display: "flex",
1279
+ alignItems: "center",
1280
+ gap: tokens.spacing.xs
1281
+ },
1282
+ children: [/* @__PURE__ */ jsx("span", {
1283
+ "aria-hidden": "true",
1284
+ style: {
1285
+ width: 10,
1286
+ height: 10,
1287
+ borderRadius: tokens.borderRadius.sm,
1288
+ background: h.color,
1289
+ display: "inline-block"
1290
+ }
1291
+ }), /* @__PURE__ */ jsxs("span", {
1292
+ style: { color: tokens.colors.text.muted },
1293
+ children: [
1294
+ h.label,
1295
+ " (0x",
1296
+ h.start.toString(16),
1297
+ "-0x",
1298
+ h.end.toString(16),
1299
+ ")"
1300
+ ]
1301
+ })]
1302
+ }, i))
1303
+ }),
1304
+ showStatusBar && /* @__PURE__ */ jsxs("div", {
1305
+ role: "status",
1306
+ "aria-live": "polite",
1307
+ style: {
1308
+ padding: `${tokens.spacing.sm} ${tokens.spacing.smd}`,
1309
+ borderTop: `1px solid ${tokens.colors.border.muted}`,
1310
+ display: "flex",
1311
+ alignItems: "center",
1312
+ gap: tokens.spacing.md,
1313
+ fontSize: tokens.typography.fontSize.sm,
1314
+ background: tokens.colors.background.surface,
1315
+ color: tokens.colors.text.muted,
1316
+ flexWrap: "wrap"
1317
+ },
1318
+ children: [
1319
+ /* @__PURE__ */ jsxs("span", { children: [
1320
+ "Size: ",
1321
+ data.length.toLocaleString(),
1322
+ " bytes"
1323
+ ] }),
1324
+ cursorOffset >= 0 && cursorOffset < data.length && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("span", { children: [
1325
+ "Offset: 0x",
1326
+ cursorOffset.toString(16).toUpperCase(),
1327
+ " (",
1328
+ cursorOffset,
1329
+ ")"
1330
+ ] }), /* @__PURE__ */ jsxs("span", { children: [
1331
+ "Value: 0x",
1332
+ byteToHex(data[cursorOffset]),
1333
+ " (",
1334
+ data[cursorOffset],
1335
+ ")"
1336
+ ] })] }),
1337
+ selectStart >= 0 && selectEnd >= 0 && selectStart !== selectEnd && /* @__PURE__ */ jsxs("span", { children: [
1338
+ "Selection: ",
1339
+ Math.abs(selectEnd - selectStart) + 1,
1340
+ " bytes (0x",
1341
+ Math.min(selectStart, selectEnd).toString(16).toUpperCase(),
1342
+ "-0x",
1343
+ Math.max(selectStart, selectEnd).toString(16).toUpperCase(),
1344
+ ")"
1345
+ ] }),
1346
+ interpretation && /* @__PURE__ */ jsxs("span", {
1347
+ style: {
1348
+ display: "flex",
1349
+ gap: tokens.spacing.sm,
1350
+ color: tokens.colors.text.secondary
1351
+ },
1352
+ children: [
1353
+ /* @__PURE__ */ jsxs("span", { children: ["U8: ", interpretation.u8] }),
1354
+ /* @__PURE__ */ jsxs("span", { children: ["I8: ", interpretation.i8] }),
1355
+ interpretation.u16 !== void 0 && /* @__PURE__ */ jsxs("span", { children: ["U16: ", interpretation.u16] }),
1356
+ interpretation.u32 !== void 0 && /* @__PURE__ */ jsxs("span", { children: ["U32: ", interpretation.u32] }),
1357
+ interpretation.f32 !== void 0 && /* @__PURE__ */ jsxs("span", { children: ["F32: ", interpretation.f32.toFixed(6)] })
1358
+ ]
1359
+ })
1360
+ ]
1361
+ })
1362
+ ]
1363
+ });
1100
1364
  }
1101
- export {
1102
- HexViewer,
1103
- REGION_BORDER_COLORS,
1104
- REGION_COLORS
1105
- };
1106
- //# sourceMappingURL=HexViewer.js.map
1365
+ //#endregion
1366
+ export { HexViewer, HexViewer as default, REGION_BORDER_COLORS, REGION_COLORS };
1367
+
1368
+ //# sourceMappingURL=HexViewer.js.map