@zendir/ui 0.6.0 → 0.6.1
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.
- package/CHANGELOG.md +419 -406
- package/dist/react/3d/CesiumCaptureSource.js.map +1 -1
- package/dist/react/3d/ZenSpace3D.js.map +1 -1
- package/dist/react/3d/ZenSpace3DCesium.js.map +1 -1
- package/dist/react/3d/ZenSpace3DShaders.js.map +1 -1
- package/dist/react/3d/ZenSpace3DUtils.js.map +1 -1
- package/dist/react/3d/capturePngAnalysis.js.map +1 -1
- package/dist/react/3d/threeLoader.js.map +1 -1
- package/dist/react/astro/ClassificationBanner.js.map +1 -1
- package/dist/react/astro/GlobalStatusBar.js.map +1 -1
- package/dist/react/astro/MissionClock.js.map +1 -1
- package/dist/react/astro/MonitoringIcon.js.map +1 -1
- package/dist/react/astro/Notification.js.map +1 -1
- package/dist/react/astro/Progress.js.map +1 -1
- package/dist/react/astro/SimulationControls.js.map +1 -1
- package/dist/react/astro/StatusIndicator.js.map +1 -1
- package/dist/react/astro/UnifiedTimeline.js.map +1 -1
- package/dist/react/cards/AccessCard.js.map +1 -1
- package/dist/react/cards/OrbitCard.js.map +1 -1
- package/dist/react/cards/SpacecraftCard.js.map +1 -1
- package/dist/react/cards/TelemetryCard.js.map +1 -1
- package/dist/react/cards/TelemetryStreamCard.js.map +1 -1
- package/dist/react/charts/GroundTrackMap.js.map +1 -1
- package/dist/react/charts/GroundTrackMapLeaflet.js.map +1 -1
- package/dist/react/charts/groundTrackMapLeafletTiles.js.map +1 -1
- package/dist/react/charts/groundTrackMapLeafletUtils.js.map +1 -1
- package/dist/react/charts/unified/AstroChart.js.map +1 -1
- package/dist/react/charts/unified/PowerOverviewChart.js.map +1 -1
- package/dist/react/charts/unified/domain.js.map +1 -1
- package/dist/react/charts/unified/generators.js.map +1 -1
- package/dist/react/charts/unified/presets.js.map +1 -1
- package/dist/react/charts/unified/sync.js.map +1 -1
- package/dist/react/charts/unified/theme.js.map +1 -1
- package/dist/react/charts/unified/useChartStream.js.map +1 -1
- package/dist/react/chatgpt/index.js.map +1 -1
- package/dist/react/context/CategoryContext.js.map +1 -1
- package/dist/react/context/DisplaySettingsContext.js.map +1 -1
- package/dist/react/context/SpatialSelectionContext.js.map +1 -1
- package/dist/react/core/data/DataTable.js.map +1 -1
- package/dist/react/core/data/DataValue.js.map +1 -1
- package/dist/react/core/data/propertyConfig.js.map +1 -1
- package/dist/react/core/display/AstroIcon.js.map +1 -1
- package/dist/react/core/display/Badge.js.map +1 -1
- package/dist/react/core/display/CardHeader.js.map +1 -1
- package/dist/react/core/display/Container.js.map +1 -1
- package/dist/react/core/display/CopyButton.js.map +1 -1
- package/dist/react/core/display/GlassCard.js.map +1 -1
- package/dist/react/core/display/HeaderIconWithStatus.js.map +1 -1
- package/dist/react/core/display/Icon.js.map +1 -1
- package/dist/react/core/display/Typography.js.map +1 -1
- package/dist/react/core/feedback/ConfirmDialog.js.map +1 -1
- package/dist/react/core/feedback/Dialog.js.map +1 -1
- package/dist/react/core/feedback/Toast.js.map +1 -1
- package/dist/react/core/inputs/Button.js.map +1 -1
- package/dist/react/core/inputs/Checkbox.js.map +1 -1
- package/dist/react/core/inputs/Input.js.map +1 -1
- package/dist/react/core/inputs/LimitsBar.js.map +1 -1
- package/dist/react/core/inputs/NumberInput.js.map +1 -1
- package/dist/react/core/inputs/PinInput.js.map +1 -1
- package/dist/react/core/inputs/Select.js.map +1 -1
- package/dist/react/core/inputs/Toggle.js.map +1 -1
- package/dist/react/core/layout/Box.js.map +1 -1
- package/dist/react/core/layout/Center.js.map +1 -1
- package/dist/react/core/layout/Divider.js.map +1 -1
- package/dist/react/core/layout/Flex.js.map +1 -1
- package/dist/react/core/layout/Grid.js.map +1 -1
- package/dist/react/core/layout/Spacer.js.map +1 -1
- package/dist/react/core/layout/Stack.js.map +1 -1
- package/dist/react/core/layout/responsive.js.map +1 -1
- package/dist/react/core/layout/useBreakpoint.js.map +1 -1
- package/dist/react/core/navigation/AppBar.js.map +1 -1
- package/dist/react/core/navigation/Pagination.js.map +1 -1
- package/dist/react/core/navigation/SideNav.js.map +1 -1
- package/dist/react/core/navigation/Tabs.js.map +1 -1
- package/dist/react/core/overlays/Popover.js.map +1 -1
- package/dist/react/core/overlays/SidePanel.js.map +1 -1
- package/dist/react/core/overlays/Tooltip.js.map +1 -1
- package/dist/react/core/widgets/ActivityPlanner.js.map +1 -1
- package/dist/react/core/widgets/Capture.js.map +1 -1
- package/dist/react/core/widgets/ChatPanel.js.map +1 -1
- package/dist/react/core/widgets/ColorPickerPanel.js.map +1 -1
- package/dist/react/core/widgets/CommandBuilder.js.map +1 -1
- package/dist/react/core/widgets/ConnectionForm.js.map +1 -1
- package/dist/react/core/widgets/FileExplorer.js.map +1 -1
- package/dist/react/core/widgets/HexViewer.js.map +1 -1
- package/dist/react/core/widgets/ImageFrame.js.map +1 -1
- package/dist/react/core/widgets/ImageGallery.js.map +1 -1
- package/dist/react/core/widgets/ImagePanel.js.map +1 -1
- package/dist/react/core/widgets/LogViewer.js.map +1 -1
- package/dist/react/core/widgets/MessageStream.js.map +1 -1
- package/dist/react/core/widgets/MissionCalendar.js.map +1 -1
- package/dist/react/core/widgets/PacketViewer.js.map +1 -1
- package/dist/react/hooks/useCompactMode.js.map +1 -1
- package/dist/react/hooks/useSimulationScene.js.map +1 -1
- package/dist/react/panels/LayerControlPanel.js.map +1 -1
- package/dist/react/panels/ObjectInventoryPanel.js.map +1 -1
- package/dist/react/shared/ErrorBoundary.js.map +1 -1
- package/dist/react/shared/Skeleton.js.map +1 -1
- package/dist/react/theme/ThemeProvider.js +2 -1
- package/dist/react/theme/ThemeProvider.js.map +1 -1
- package/dist/react/theme/cardAccent.js.map +1 -1
- package/dist/react/theme/config.js.map +1 -1
- package/dist/react/theme/types.js.map +1 -1
- package/dist/react/types.js.map +1 -1
- package/dist/react/utils/categoryPalette.js.map +1 -1
- package/dist/react/utils/index.js.map +1 -1
- package/dist/style.css +216 -216
- package/dist/tokens/core.js.map +1 -1
- package/dist/tokens/index.js.map +1 -1
- package/dist/tokens/palettes/astro.js.map +1 -1
- package/dist/tokens/palettes/index.js.map +1 -1
- package/dist/tokens/palettes/tailwind.js.map +1 -1
- package/dist/tokens/palettes/zendir.js.map +1 -1
- package/dist/tokens/themes.js.map +1 -1
- package/dist/tokens/tokens.css +533 -533
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HexViewer.js","names":[],"sources":["../../../../src/react/core/widgets/HexViewer.tsx"],"sourcesContent":["/**\n * @zendir/ui - HexViewer Component\n *\n * Displays raw binary data in a hex viewer format for telemetry packet analysis.\n * Shows offset, hex bytes, and ASCII representation side by side.\n *\n * Features:\n * - Hex + ASCII view with configurable bytes per row (8, 16, 32)\n * - Byte highlighting with custom colors (for field boundaries, errors, etc.)\n * - Click-to-select bytes and ranges\n * - Search by hex pattern or ASCII string\n * - Offset display in hex or decimal\n * - Live data streaming support (auto-scroll)\n * - Copy selection as hex, decimal, or ASCII\n * - Byte grouping (1, 2, 4, 8)\n * - Endianness toggle for multi-byte interpretation\n * - Status bar showing cursor position, selection info, data interpretation\n *\n * Astro UX Compliance:\n * - Dark theme with monospace font\n * - Status colors for highlighting\n * - Accessible keyboard navigation\n *\n * @example\n * ```tsx\n * <HexViewer\n * data={new Uint8Array([0x48, 0x65, 0x6C, 0x6C, 0x6F])}\n * bytesPerRow={16}\n * highlights={[{ start: 0, end: 4, color: '#4CAF50', label: 'Header' }]}\n * />\n * ```\n */\n\nimport React, {\n useState,\n useRef,\n useCallback,\n useMemo,\n useEffect,\n memo,\n} from \"react\";\nimport { useTheme } from \"../../theme\";\n\n// =============================================================================\n// Types\n// =============================================================================\n\nexport interface HexHighlight {\n /** Start byte offset (inclusive) */\n start: number;\n /** End byte offset (inclusive) */\n end: number;\n /** Highlight color (CSS color) */\n color: string;\n /** Optional label for the highlight region */\n label?: string;\n}\n\n/**\n * Named region for structural packet analysis (e.g., CCSDS Primary Header,\n * Secondary Header, User Data). Regions provide persistent background color\n * and a legend entry, unlike transient highlights.\n */\nexport interface HexRegion {\n /** Start byte offset (inclusive) */\n start: number;\n /** End byte offset (exclusive) */\n end: number;\n /** Region display name (e.g., \"Primary Header\") */\n label: string;\n /** Background fill color (CSS rgba recommended) */\n color: string;\n /** Border/accent color for the region */\n borderColor?: string;\n}\n\n/**\n * A decoded field within the packet payload for the fields table.\n * Enables cross-highlighting: hover a field row → highlight its bytes,\n * hover hex bytes → highlight the corresponding field row.\n */\nexport interface DecodedField {\n /** Field name */\n name: string;\n /** Decoded value (displayed via toString) */\n value: string | number | boolean | null;\n /** Engineering units (e.g., \"°C\", \"V\", \"rpm\") */\n unit?: string;\n /** Byte offset within the packet where this field starts */\n byteOffset?: number;\n /** Size in bits */\n sizeBits?: number;\n /** Optional description / tooltip */\n description?: string;\n}\n\n/**\n * Packet header key-value pairs shown in a collapsible summary panel.\n * Supports CCSDS-style primary + secondary header display.\n */\nexport interface PacketHeaderEntry {\n /** Label (e.g., \"APID\", \"Sequence Count\", \"Spacecraft ID\") */\n label: string;\n /** Value */\n value: string | number;\n /** Render value in monospace font */\n mono?: boolean;\n}\n\n/** Pre-defined region color palette for space packet headers */\nexport const REGION_COLORS = {\n primary: \"rgba(96, 165, 250, 0.15)\",\n secondary: \"rgba(168, 85, 247, 0.15)\",\n tertiary: \"rgba(251, 191, 36, 0.15)\",\n userData: \"rgba(34, 197, 94, 0.15)\",\n error: \"rgba(255, 56, 56, 0.15)\",\n} as const;\n\nexport const REGION_BORDER_COLORS = {\n primary: \"rgba(96, 165, 250, 0.5)\",\n secondary: \"rgba(168, 85, 247, 0.5)\",\n tertiary: \"rgba(251, 191, 36, 0.5)\",\n userData: \"rgba(34, 197, 94, 0.5)\",\n error: \"rgba(255, 56, 56, 0.5)\",\n} as const;\n\nexport type ByteGrouping = 1 | 2 | 4 | 8;\nexport type OffsetBase = \"hex\" | \"decimal\";\nexport type Endianness = \"big\" | \"little\";\n\nexport interface HexViewerProps {\n /** Binary data to display */\n data: Uint8Array | number[];\n /** Bytes per row (default: 16) */\n bytesPerRow?: 8 | 16 | 32;\n /** Byte grouping for hex display (default: 1) */\n byteGrouping?: ByteGrouping;\n /** Offset display base (default: 'hex') */\n offsetBase?: OffsetBase;\n /** Highlighted byte regions (transient, e.g., search results or field hover) */\n highlights?: HexHighlight[];\n /** Named structural regions (persistent background, e.g., Primary/Secondary Header) */\n regions?: HexRegion[];\n /** Decoded fields for the fields table (cross-highlights with hex bytes) */\n decodedFields?: DecodedField[];\n /** Packet header entries for the summary panel */\n packetHeaders?: PacketHeaderEntry[];\n /** External hover highlight range (for cross-component linking) */\n externalHighlight?: { start: number; end: number } | null;\n /** Whether to show the ASCII column (default: true) */\n showAscii?: boolean;\n /** Whether to show the offset column (default: true) */\n showOffset?: boolean;\n /** Whether to show the status/info bar (default: true) */\n showStatusBar?: boolean;\n /** Whether to show the search bar (default: true) */\n showSearch?: boolean;\n /** Whether to show the toolbar (default: true) */\n showToolbar?: boolean;\n /** Whether to show the decoded fields table (default: true if decodedFields provided) */\n showFieldsTable?: boolean;\n /** Whether to show the packet header summary (default: true if packetHeaders provided) */\n showHeaderSummary?: boolean;\n /**\n * Height of the viewer (default: 400).\n * When constrained, HexViewer enables outer scroll fallback so expanded\n * panels (headers/decoded fields) never trap lower sections.\n */\n height?: number | string;\n /** Title for the viewer */\n title?: string;\n /** Whether data is streaming (auto-scroll to bottom) */\n streaming?: boolean;\n /** Endianness for multi-byte interpretation (default: 'big') */\n endianness?: Endianness;\n /** Called when byte selection changes */\n onSelectionChange?: (start: number, end: number) => void;\n /** Called when a byte is clicked */\n onByteClick?: (offset: number, value: number) => void;\n /** Called when hovering a byte (for external cross-highlighting) */\n onByteHover?: (byteIndex: number) => void;\n /** Called when mouse leaves hex area */\n onByteLeave?: () => void;\n /** CSS class name */\n className?: string;\n /** Show outer container border (default: true). Set false when embedded inside a Container/card. */\n bordered?: boolean;\n /** Custom style overrides for the outer container */\n style?: React.CSSProperties;\n}\n\n// =============================================================================\n// Helpers\n// =============================================================================\n\nfunction toUint8Array(data: Uint8Array | number[]): Uint8Array {\n return data instanceof Uint8Array ? data : new Uint8Array(data);\n}\n\nfunction byteToHex(byte: number): string {\n return byte.toString(16).padStart(2, \"0\").toUpperCase();\n}\n\nfunction byteToAscii(byte: number): string {\n return byte >= 0x20 && byte <= 0x7e ? String.fromCharCode(byte) : \".\";\n}\n\nfunction formatOffset(\n offset: number,\n base: OffsetBase,\n totalBytes: number,\n): string {\n if (base === \"hex\") {\n const width = Math.max(\n 4,\n Math.ceil(Math.log2(Math.max(totalBytes, 1)) / 4) * 1,\n );\n return offset.toString(16).padStart(Math.max(width, 4), \"0\").toUpperCase();\n }\n const width = Math.max(4, totalBytes.toString().length);\n return offset.toString().padStart(width, \" \");\n}\n\nfunction interpretBytes(\n data: Uint8Array,\n offset: number,\n endianness: Endianness,\n): {\n u8: number;\n i8: number;\n u16?: number;\n i16?: number;\n u32?: number;\n i32?: number;\n f32?: number;\n f64?: number;\n} {\n const result: ReturnType<typeof interpretBytes> = {\n u8: data[offset],\n i8: data[offset] > 127 ? data[offset] - 256 : data[offset],\n };\n\n if (offset + 1 < data.length) {\n const view = new DataView(\n data.buffer,\n data.byteOffset + offset,\n Math.min(8, data.length - offset),\n );\n const be = endianness === \"big\";\n result.u16 = view.getUint16(0, !be);\n result.i16 = view.getInt16(0, !be);\n if (offset + 3 < data.length) {\n result.u32 = view.getUint32(0, !be);\n result.i32 = view.getInt32(0, !be);\n result.f32 = view.getFloat32(0, !be);\n }\n if (offset + 7 < data.length) {\n result.f64 = view.getFloat64(0, !be);\n }\n }\n return result;\n}\n\nconst HEX_ROW_HEIGHT = 22;\nconst HEX_OVERSCAN_ROWS = 16;\nconst MAX_RENDERED_SEARCH_HIGHLIGHTS = 400;\n\n// =============================================================================\n// Sub-components\n// =============================================================================\n\nconst HexRow = memo(function HexRow({\n offset,\n bytes,\n bytesPerRow,\n byteGrouping,\n offsetBase,\n totalBytes,\n showAscii,\n showOffset,\n highlights,\n regions,\n effectiveHighlight,\n selectedStart,\n selectedEnd,\n cursorOffset,\n onByteMouseDown,\n onByteMouseEnter,\n onByteClick,\n handleByteHoverInternal,\n handleByteLeaveInternal,\n tokens,\n}: {\n offset: number;\n bytes: Uint8Array;\n bytesPerRow: number;\n byteGrouping: ByteGrouping;\n offsetBase: OffsetBase;\n totalBytes: number;\n showAscii: boolean;\n showOffset: boolean;\n highlights: HexHighlight[];\n regions: HexRegion[];\n effectiveHighlight: { start: number; end: number } | null | undefined;\n selectedStart: number;\n selectedEnd: number;\n cursorOffset: number;\n onByteMouseDown: (idx: number) => void;\n onByteMouseEnter: (idx: number) => void;\n onByteClick: (idx: number) => void;\n handleByteHoverInternal: (idx: number) => void;\n handleByteLeaveInternal: () => void;\n tokens: ReturnType<typeof useTheme>[\"tokens\"];\n}) {\n const hexCells: React.ReactNode[] = [];\n const asciiCells: React.ReactNode[] = [];\n\n for (let i = 0; i < bytesPerRow; i++) {\n const byteIdx = offset + i;\n const isValid = i < bytes.length;\n const byte = isValid ? bytes[i] : 0;\n\n // Determine highlight color (priority: selection > cross-highlight > search highlight > region)\n let bgColor = \"transparent\";\n let fgColor = tokens.colors.text.primary;\n let borderBottom = \"2px solid transparent\";\n const isSelected =\n byteIdx >= Math.min(selectedStart, selectedEnd) &&\n byteIdx <= Math.max(selectedStart, selectedEnd) &&\n selectedStart >= 0;\n const isCursor = byteIdx === cursorOffset;\n\n // 1. Region background (lowest priority — always visible unless overridden)\n let regionColor: string | null = null;\n for (let r = 0; r < regions.length; r++) {\n const region = regions[r];\n if (byteIdx < region.start) break;\n if (byteIdx >= region.start && byteIdx < region.end) {\n regionColor = region.color;\n break;\n }\n }\n if (regionColor) {\n bgColor = regionColor;\n }\n\n // 2. Search highlights\n for (const h of highlights) {\n if (byteIdx >= h.start && byteIdx <= h.end) {\n bgColor = `${h.color}33`;\n fgColor = h.color;\n break;\n }\n }\n\n // 3. Cross-highlight from field hover or external\n if (\n effectiveHighlight &&\n byteIdx >= effectiveHighlight.start &&\n byteIdx < effectiveHighlight.end\n ) {\n bgColor = `${tokens.colors.status.caution}4D`;\n borderBottom = `2px solid ${tokens.colors.status.caution}`;\n }\n\n // 4. Selection (highest priority)\n if (isSelected) {\n bgColor = `${tokens.colors.interactive.default}4D`;\n fgColor = tokens.colors.interactive.default;\n }\n\n // Add grouping space\n const needsGroupSpace = i > 0 && i % byteGrouping === 0;\n\n hexCells.push(\n <span\n key={`hex-${i}`}\n role=\"button\"\n tabIndex={isValid ? 0 : -1}\n onMouseDown={() => isValid && onByteMouseDown(byteIdx)}\n onMouseEnter={() => {\n isValid && onByteMouseEnter(byteIdx);\n isValid && handleByteHoverInternal(byteIdx);\n }}\n onMouseLeave={() => isValid && handleByteLeaveInternal()}\n onClick={() => isValid && onByteClick(byteIdx)}\n onKeyDown={(e) => {\n if (isValid && (e.key === \"Enter\" || e.key === \" \")) {\n e.preventDefault();\n onByteClick(byteIdx);\n }\n }}\n style={{\n padding: \"0 2px\",\n marginLeft: needsGroupSpace ? 6 : 0,\n background: bgColor,\n color: isValid ? fgColor : `${tokens.colors.text.muted}26`,\n cursor: isValid ? \"pointer\" : \"default\",\n borderBottom: isCursor\n ? `2px solid ${tokens.colors.status.normal}`\n : borderBottom,\n borderRadius: tokens.borderRadius.sm,\n userSelect: \"none\",\n transition: \"background 0.1s ease\",\n }}\n >\n {isValid ? byteToHex(byte) : \" \"}\n </span>,\n );\n\n if (showAscii) {\n asciiCells.push(\n <span\n key={`ascii-${i}`}\n role=\"button\"\n tabIndex={isValid ? 0 : -1}\n onMouseDown={() => isValid && onByteMouseDown(byteIdx)}\n onMouseEnter={() => {\n isValid && onByteMouseEnter(byteIdx);\n isValid && handleByteHoverInternal(byteIdx);\n }}\n onMouseLeave={() => isValid && handleByteLeaveInternal()}\n onClick={() => isValid && onByteClick(byteIdx)}\n onKeyDown={(e) => {\n if (isValid && (e.key === \"Enter\" || e.key === \" \")) {\n e.preventDefault();\n onByteClick(byteIdx);\n }\n }}\n style={{\n background: bgColor,\n color: isValid\n ? byte >= 0x20 && byte <= 0x7e\n ? fgColor\n : `${tokens.colors.text.muted}33`\n : \"transparent\",\n cursor: isValid ? \"pointer\" : \"default\",\n borderBottom: isCursor\n ? `2px solid ${tokens.colors.status.normal}`\n : borderBottom,\n borderRadius: tokens.borderRadius.sm,\n userSelect: \"none\",\n width: 8,\n textAlign: \"center\" as const,\n }}\n >\n {isValid ? byteToAscii(byte) : \" \"}\n </span>,\n );\n }\n }\n\n return (\n <div\n style={{\n display: \"flex\",\n alignItems: \"center\",\n height: HEX_ROW_HEIGHT,\n lineHeight: `${HEX_ROW_HEIGHT}px`,\n }}\n >\n {showOffset && (\n <span\n style={{\n color: tokens.colors.text.muted,\n marginRight: 12,\n minWidth: 60,\n textAlign: \"right\",\n }}\n >\n {formatOffset(offset, offsetBase, totalBytes)}\n </span>\n )}\n <span\n style={{\n display: \"flex\",\n gap: 1,\n marginRight: showAscii ? 16 : 0,\n letterSpacing: \"0.5px\",\n }}\n >\n {hexCells}\n </span>\n {showAscii && (\n <>\n <span\n style={{ color: `${tokens.colors.border.muted}`, marginRight: 8 }}\n >\n │\n </span>\n <span style={{ display: \"flex\", letterSpacing: \"1px\" }}>\n {asciiCells}\n </span>\n </>\n )}\n </div>\n );\n});\n\n// =============================================================================\n// Main Component\n// =============================================================================\n\nexport function HexViewer({\n data: rawData,\n bytesPerRow = 16,\n byteGrouping = 1,\n offsetBase = \"hex\",\n highlights = [],\n regions = [],\n decodedFields,\n packetHeaders,\n externalHighlight,\n showAscii = true,\n showOffset = true,\n showStatusBar = true,\n showSearch = true,\n showToolbar = true,\n showFieldsTable,\n showHeaderSummary,\n height = 400,\n title,\n streaming = false,\n endianness = \"big\",\n onSelectionChange,\n onByteClick,\n onByteHover,\n onByteLeave,\n className = \"\",\n bordered = true,\n style: styleProp,\n}: HexViewerProps): React.ReactElement {\n const { tokens } = useTheme();\n const data = useMemo(() => toUint8Array(rawData), [rawData]);\n const [hoveredFieldIdx, setHoveredFieldIdx] = useState<number | null>(null);\n const [fieldHighlight, setFieldHighlight] = useState<{\n start: number;\n end: number;\n } | null>(null);\n const [showHeaders, setShowHeaders] = useState(true);\n const [showFields, setShowFields] = useState(true);\n\n const sortedRegions = useMemo(\n () => [...regions].sort((a, b) => a.start - b.start),\n [regions],\n );\n\n // Build field byte ranges for cross-highlighting\n const fieldByteRanges = useMemo(() => {\n if (!decodedFields) return [];\n return decodedFields.map((f) => ({\n start: f.byteOffset ?? 0,\n end: (f.byteOffset ?? 0) + Math.ceil((f.sizeBits ?? 0) / 8),\n name: f.name,\n }));\n }, [decodedFields]);\n\n // Cross-highlight: field hover → byte highlight\n const handleFieldHover = useCallback(\n (fieldIdx: number) => {\n setHoveredFieldIdx(fieldIdx);\n const range = fieldByteRanges[fieldIdx];\n if (range && range.end > range.start) setFieldHighlight(range);\n },\n [fieldByteRanges],\n );\n\n const handleFieldLeave = useCallback(() => {\n setHoveredFieldIdx(null);\n setFieldHighlight(null);\n }, []);\n\n // Cross-highlight: byte hover → field highlight\n const handleByteHoverInternal = useCallback(\n (byteIdx: number) => {\n onByteHover?.(byteIdx);\n const fi = fieldByteRanges.findIndex(\n (r) => byteIdx >= r.start && byteIdx < r.end,\n );\n if (fi >= 0) {\n setHoveredFieldIdx(fi);\n setFieldHighlight(fieldByteRanges[fi]);\n } else {\n setHoveredFieldIdx(null);\n setFieldHighlight({ start: byteIdx, end: byteIdx + 1 });\n }\n },\n [fieldByteRanges, onByteHover],\n );\n\n const handleByteLeaveInternal = useCallback(() => {\n onByteLeave?.();\n setHoveredFieldIdx(null);\n setFieldHighlight(null);\n }, [onByteLeave]);\n\n // Merge field highlight + external highlight into effective highlights\n const effectiveHighlight = externalHighlight || fieldHighlight;\n\n const shouldShowFieldsTable =\n showFieldsTable ?? (decodedFields && decodedFields.length > 0);\n const shouldShowHeaderSummary =\n showHeaderSummary ?? (packetHeaders && packetHeaders.length > 0);\n const scrollRef = useRef<HTMLDivElement>(null);\n const hasConstrainedHeight = useMemo(() => {\n if (height === undefined || height === null) return false;\n if (typeof height === \"string\")\n return height.trim().toLowerCase() !== \"auto\";\n return true;\n }, [height]);\n const decodedFieldsMaxHeight = useMemo(() => {\n if (typeof height === \"number\") {\n // Keep decoded fields useful but avoid starving the hex grid.\n return Math.min(220, Math.max(120, Math.floor(height * 0.32)));\n }\n return 200;\n }, [height]);\n\n const [cursorOffset, setCursorOffset] = useState(-1);\n const [selectStart, setSelectStart] = useState(-1);\n const [selectEnd, setSelectEnd] = useState(-1);\n const [isDragging, setIsDragging] = useState(false);\n const [searchQuery, setSearchQuery] = useState(\"\");\n const [searchType, setSearchType] = useState<\"hex\" | \"ascii\">(\"hex\");\n const [searchResults, setSearchResults] = useState<number[]>([]);\n const [currentSearchIdx, setCurrentSearchIdx] = useState(-1);\n const [localBytesPerRow, setLocalBytesPerRow] = useState(bytesPerRow);\n const [localGrouping, setLocalGrouping] = useState(byteGrouping);\n const [localOffsetBase, setLocalOffsetBase] = useState(offsetBase);\n const [localEndianness, setLocalEndianness] = useState(endianness);\n const [localShowAscii, setLocalShowAscii] = useState(showAscii);\n const [scrollTop, setScrollTop] = useState(0);\n const [viewportHeight, setViewportHeight] = useState(160);\n\n // Auto-scroll when streaming\n useEffect(() => {\n if (streaming && scrollRef.current) {\n scrollRef.current.scrollTop = scrollRef.current.scrollHeight;\n }\n }, [data, streaming]);\n\n useEffect(() => {\n const el = scrollRef.current;\n if (!el) return undefined;\n\n const updateViewportHeight = () => {\n setViewportHeight(Math.max(120, el.clientHeight || 160));\n };\n\n updateViewportHeight();\n if (typeof ResizeObserver !== \"undefined\") {\n const observer = new ResizeObserver(updateViewportHeight);\n observer.observe(el);\n return () => observer.disconnect();\n }\n\n window.addEventListener(\"resize\", updateViewportHeight);\n return () => window.removeEventListener(\"resize\", updateViewportHeight);\n }, []);\n\n const totalRows = Math.max(1, Math.ceil(data.length / localBytesPerRow));\n const startRow = Math.max(\n 0,\n Math.floor(scrollTop / HEX_ROW_HEIGHT) - HEX_OVERSCAN_ROWS,\n );\n const visibleRowCount =\n Math.ceil(viewportHeight / HEX_ROW_HEIGHT) + HEX_OVERSCAN_ROWS * 2;\n const endRow = Math.min(totalRows, startRow + visibleRowCount);\n const virtualRows = useMemo(() => {\n const result: { offset: number; bytes: Uint8Array }[] = [];\n for (let row = startRow; row < endRow; row++) {\n const offset = row * localBytesPerRow;\n const bytes = data.subarray(\n offset,\n Math.min(data.length, offset + localBytesPerRow),\n );\n result.push({ offset, bytes });\n }\n if (result.length === 0) {\n result.push({ offset: 0, bytes: new Uint8Array(0) });\n }\n return result;\n }, [data, localBytesPerRow, startRow, endRow]);\n\n // Search\n const doSearch = useCallback(() => {\n if (!searchQuery.trim()) {\n setSearchResults([]);\n setCurrentSearchIdx(-1);\n return;\n }\n\n const results: number[] = [];\n if (searchType === \"hex\") {\n const hexStr = searchQuery.replace(/\\s+/g, \"\");\n if (hexStr.length % 2 !== 0 || !/^[0-9a-fA-F]+$/.test(hexStr)) return;\n const searchBytes = new Uint8Array(hexStr.length / 2);\n for (let i = 0; i < searchBytes.length; i++) {\n searchBytes[i] = parseInt(hexStr.slice(i * 2, i * 2 + 2), 16);\n }\n for (let i = 0; i <= data.length - searchBytes.length; i++) {\n let match = true;\n for (let j = 0; j < searchBytes.length; j++) {\n if (data[i + j] !== searchBytes[j]) {\n match = false;\n break;\n }\n }\n if (match) results.push(i);\n }\n } else {\n const encoder = new TextEncoder();\n const searchBytes = encoder.encode(searchQuery);\n for (let i = 0; i <= data.length - searchBytes.length; i++) {\n let match = true;\n for (let j = 0; j < searchBytes.length; j++) {\n if (data[i + j] !== searchBytes[j]) {\n match = false;\n break;\n }\n }\n if (match) results.push(i);\n }\n }\n setSearchResults(results);\n setCurrentSearchIdx(results.length > 0 ? 0 : -1);\n if (results.length > 0) {\n setCursorOffset(results[0]);\n if (scrollRef.current) {\n const rowIdx = Math.floor(results[0] / localBytesPerRow);\n scrollRef.current.scrollTop = Math.max(\n 0,\n rowIdx * HEX_ROW_HEIGHT - 100,\n );\n }\n }\n }, [searchQuery, searchType, data, localBytesPerRow]);\n\n const scrollToOffset = useCallback(\n (offset: number) => {\n if (!scrollRef.current) return;\n const rowIdx = Math.floor(offset / localBytesPerRow);\n scrollRef.current.scrollTop = Math.max(0, rowIdx * HEX_ROW_HEIGHT - 100);\n },\n [localBytesPerRow],\n );\n\n const handleHexScroll = useCallback(\n (event: React.UIEvent<HTMLDivElement>) => {\n setScrollTop(event.currentTarget.scrollTop);\n },\n [],\n );\n\n const nextSearchResult = useCallback(() => {\n if (searchResults.length === 0) return;\n const nextIdx = (currentSearchIdx + 1) % searchResults.length;\n setCurrentSearchIdx(nextIdx);\n setCursorOffset(searchResults[nextIdx]);\n scrollToOffset(searchResults[nextIdx]);\n }, [searchResults, currentSearchIdx, scrollToOffset]);\n\n const prevSearchResult = useCallback(() => {\n if (searchResults.length === 0) return;\n const prevIdx =\n (currentSearchIdx - 1 + searchResults.length) % searchResults.length;\n setCurrentSearchIdx(prevIdx);\n setCursorOffset(searchResults[prevIdx]);\n scrollToOffset(searchResults[prevIdx]);\n }, [searchResults, currentSearchIdx, scrollToOffset]);\n\n // Selection handlers\n const handleByteMouseDown = useCallback((idx: number) => {\n setSelectStart(idx);\n setSelectEnd(idx);\n setCursorOffset(idx);\n setIsDragging(true);\n }, []);\n\n const handleByteMouseEnter = useCallback(\n (idx: number) => {\n if (isDragging) {\n setSelectEnd(idx);\n }\n },\n [isDragging],\n );\n\n const handleMouseUp = useCallback(() => {\n if (isDragging) {\n setIsDragging(false);\n if (onSelectionChange && selectStart >= 0 && selectEnd >= 0) {\n onSelectionChange(\n Math.min(selectStart, selectEnd),\n Math.max(selectStart, selectEnd),\n );\n }\n }\n }, [isDragging, selectStart, selectEnd, onSelectionChange]);\n\n useEffect(() => {\n window.addEventListener(\"mouseup\", handleMouseUp);\n return () => window.removeEventListener(\"mouseup\", handleMouseUp);\n }, [handleMouseUp]);\n\n const handleByteClickCb = useCallback(\n (idx: number) => {\n setCursorOffset(idx);\n if (onByteClick) {\n onByteClick(idx, data[idx]);\n }\n },\n [data, onByteClick],\n );\n\n // Copy selection\n const copySelection = useCallback(\n (format: \"hex\" | \"decimal\" | \"ascii\") => {\n if (selectStart < 0 || selectEnd < 0) return;\n const start = Math.min(selectStart, selectEnd);\n const end = Math.max(selectStart, selectEnd);\n const selected = data.slice(start, end + 1);\n\n let text: string;\n if (format === \"hex\") {\n text = Array.from(selected)\n .map((b) => byteToHex(b))\n .join(\" \");\n } else if (format === \"decimal\") {\n text = Array.from(selected).join(\", \");\n } else {\n text = Array.from(selected)\n .map((b) => byteToAscii(b))\n .join(\"\");\n }\n navigator.clipboard?.writeText(text);\n },\n [data, selectStart, selectEnd],\n );\n\n // Interpretation of cursor byte\n const interpretation = useMemo(() => {\n if (cursorOffset < 0 || cursorOffset >= data.length) return null;\n return interpretBytes(data, cursorOffset, localEndianness);\n }, [data, cursorOffset, localEndianness]);\n\n const renderedSearchResultEntries = useMemo(() => {\n if (searchResults.length <= MAX_RENDERED_SEARCH_HIGHLIGHTS) {\n return searchResults.map((pos, idx) => ({ pos, idx }));\n }\n\n const halfWindow = Math.floor(MAX_RENDERED_SEARCH_HIGHLIGHTS / 2);\n const anchor = currentSearchIdx >= 0 ? currentSearchIdx : 0;\n const start = Math.max(0, anchor - halfWindow);\n const end = Math.min(\n searchResults.length,\n start + MAX_RENDERED_SEARCH_HIGHLIGHTS,\n );\n const adjustedStart = Math.max(0, end - MAX_RENDERED_SEARCH_HIGHLIGHTS);\n\n return searchResults\n .slice(adjustedStart, end)\n .map((pos, i) => ({ pos, idx: adjustedStart + i }));\n }, [searchResults, currentSearchIdx]);\n\n // Search highlights\n const allHighlights = useMemo(() => {\n const h = [...highlights];\n const searchLen =\n searchType === \"hex\"\n ? searchQuery.replace(/\\s+/g, \"\").length / 2\n : searchQuery.length;\n renderedSearchResultEntries.forEach(({ pos, idx }) => {\n h.push({\n start: pos,\n end: pos + Math.max(0, searchLen - 1),\n color:\n idx === currentSearchIdx\n ? tokens.colors.status.serious\n : tokens.colors.status.caution,\n label: `Search match ${idx + 1}`,\n });\n });\n return h;\n }, [\n highlights,\n renderedSearchResultEntries,\n currentSearchIdx,\n searchQuery,\n searchType,\n tokens,\n ]);\n\n const containerStyle: React.CSSProperties = {\n fontFamily: tokens.typography.fontFamily.mono,\n fontSize: 13,\n lineHeight: \"22px\",\n background: tokens.colors.background.base,\n border: bordered ? `1px solid ${tokens.colors.border.muted}` : \"none\",\n borderRadius: tokens.borderRadius.lg,\n overflowX: \"hidden\",\n overflowY: hasConstrainedHeight ? \"auto\" : \"visible\",\n display: \"flex\",\n flexDirection: \"column\",\n minHeight: 0,\n height: hasConstrainedHeight ? height : undefined,\n color: tokens.colors.text.primary,\n ...styleProp,\n };\n\n return (\n <div className={`hex-viewer ${className}`} style={containerStyle}>\n {/* Sticky header: title bar + toolbar stay pinned while content scrolls */}\n <div\n style={{\n position: \"sticky\",\n top: 0,\n zIndex: 10,\n background: tokens.colors.background.surface,\n flexShrink: 0,\n }}\n >\n {/* Title bar */}\n {title && (\n <div\n style={{\n padding: `${tokens.spacing.sm} ${tokens.spacing.smd}`,\n borderBottom: `1px solid ${tokens.colors.border.muted}`,\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"space-between\",\n }}\n >\n <span\n style={{\n fontWeight: tokens.typography.fontWeight.medium,\n fontSize: tokens.typography.fontSize.base,\n fontFamily: tokens.typography.fontFamily.primary,\n }}\n >\n {title}\n </span>\n <span\n style={{\n fontSize: tokens.typography.fontSize.sm,\n color: tokens.colors.text.muted,\n }}\n >\n {data.length.toLocaleString()} bytes\n </span>\n </div>\n )}\n\n {/* Toolbar */}\n {showToolbar && (\n <div\n role=\"toolbar\"\n aria-label=\"Hex viewer controls\"\n style={{\n padding: `${tokens.spacing.sm} ${tokens.spacing.smd}`,\n borderBottom: `1px solid ${tokens.colors.border.muted}`,\n display: \"flex\",\n alignItems: \"center\",\n gap: tokens.spacing.sm,\n flexWrap: \"wrap\",\n fontSize: tokens.typography.fontSize.sm,\n }}\n >\n <label\n style={{\n display: \"flex\",\n alignItems: \"center\",\n gap: 4,\n color: tokens.colors.text.muted,\n }}\n >\n Bytes/Row:\n <select\n aria-label=\"Bytes per row\"\n value={localBytesPerRow}\n onChange={(e) =>\n setLocalBytesPerRow(Number(e.target.value) as 8 | 16 | 32)\n }\n style={{\n background: tokens.colors.background.base,\n color: tokens.colors.text.primary,\n border: `1px solid ${tokens.colors.border.muted}`,\n borderRadius: tokens.borderRadius.md,\n padding: \"2px 4px\",\n fontSize: 11,\n }}\n >\n <option value={8}>8</option>\n <option value={16}>16</option>\n <option value={32}>32</option>\n </select>\n </label>\n <label\n style={{\n display: \"flex\",\n alignItems: \"center\",\n gap: 4,\n color: tokens.colors.text.muted,\n }}\n >\n Group:\n <select\n aria-label=\"Byte grouping\"\n value={localGrouping}\n onChange={(e) =>\n setLocalGrouping(Number(e.target.value) as ByteGrouping)\n }\n style={{\n background: tokens.colors.background.base,\n color: tokens.colors.text.primary,\n border: `1px solid ${tokens.colors.border.muted}`,\n borderRadius: tokens.borderRadius.md,\n padding: \"2px 4px\",\n fontSize: 11,\n }}\n >\n <option value={1}>1</option>\n <option value={2}>2</option>\n <option value={4}>4</option>\n <option value={8}>8</option>\n </select>\n </label>\n <label\n style={{\n display: \"flex\",\n alignItems: \"center\",\n gap: 4,\n color: tokens.colors.text.muted,\n }}\n >\n Offset:\n <select\n aria-label=\"Offset format\"\n value={localOffsetBase}\n onChange={(e) =>\n setLocalOffsetBase(e.target.value as OffsetBase)\n }\n style={{\n background: tokens.colors.background.base,\n color: tokens.colors.text.primary,\n border: `1px solid ${tokens.colors.border.muted}`,\n borderRadius: tokens.borderRadius.md,\n padding: \"2px 4px\",\n fontSize: 11,\n }}\n >\n <option value=\"hex\">Hex</option>\n <option value=\"decimal\">Dec</option>\n </select>\n </label>\n <label\n style={{\n display: \"flex\",\n alignItems: \"center\",\n gap: 4,\n color: tokens.colors.text.muted,\n }}\n >\n Endian:\n <select\n aria-label=\"Endianness\"\n value={localEndianness}\n onChange={(e) =>\n setLocalEndianness(e.target.value as Endianness)\n }\n style={{\n background: tokens.colors.background.base,\n color: tokens.colors.text.primary,\n border: `1px solid ${tokens.colors.border.muted}`,\n borderRadius: tokens.borderRadius.md,\n padding: \"2px 4px\",\n fontSize: 11,\n }}\n >\n <option value=\"big\">Big</option>\n <option value=\"little\">Little</option>\n </select>\n </label>\n <label\n style={{\n display: \"flex\",\n alignItems: \"center\",\n gap: 4,\n color: tokens.colors.text.muted,\n cursor: \"pointer\",\n }}\n >\n <input\n type=\"checkbox\"\n checked={localShowAscii}\n onChange={(e) => setLocalShowAscii(e.target.checked)}\n style={{ accentColor: tokens.colors.interactive.default }}\n />\n ASCII\n </label>\n <div style={{ flex: 1 }} />\n {selectStart >= 0 && selectEnd >= 0 && (\n <div style={{ display: \"flex\", gap: 4 }}>\n {([\"hex\", \"decimal\", \"ascii\"] as const).map((fmt) => (\n <button\n key={fmt}\n onClick={() => copySelection(fmt)}\n style={{\n background: tokens.colors.background.base,\n color: tokens.colors.text.secondary,\n border: `1px solid ${tokens.colors.border.muted}`,\n borderRadius: tokens.borderRadius.md,\n padding: \"2px 8px\",\n fontSize: 10,\n cursor: \"pointer\",\n }}\n >\n Copy {fmt}\n </button>\n ))}\n </div>\n )}\n </div>\n )}\n\n {/* Search bar */}\n {showSearch && (\n <div\n role=\"search\"\n style={{\n padding: `${tokens.spacing.sm} ${tokens.spacing.smd}`,\n borderBottom: `1px solid ${tokens.colors.border.muted}`,\n display: \"flex\",\n alignItems: \"center\",\n gap: tokens.spacing.sm,\n }}\n >\n <label\n htmlFor=\"hex-search-type\"\n style={{\n position: \"absolute\",\n width: 1,\n height: 1,\n padding: 0,\n margin: -1,\n overflow: \"hidden\",\n clip: \"rect(0, 0, 0, 0)\",\n border: 0,\n }}\n >\n Search type\n </label>\n <select\n id=\"hex-search-type\"\n value={searchType}\n onChange={(e) => setSearchType(e.target.value as \"hex\" | \"ascii\")}\n style={{\n background: tokens.colors.background.base,\n color: tokens.colors.text.primary,\n border: `1px solid ${tokens.colors.border.muted}`,\n borderRadius: tokens.borderRadius.md,\n padding: `${tokens.spacing.xs} ${tokens.spacing.sm}`,\n fontSize: tokens.typography.fontSize.sm,\n }}\n >\n <option value=\"hex\">Hex</option>\n <option value=\"ascii\">ASCII</option>\n </select>\n <input\n type=\"text\"\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === \"Enter\") doSearch();\n }}\n placeholder={\n searchType === \"hex\" ? \"e.g. 48 65 6C 6C 6F\" : \"Search ASCII...\"\n }\n aria-label=\"Search hex data\"\n style={{\n flex: 1,\n background: tokens.colors.background.base,\n color: tokens.colors.text.primary,\n border: `1px solid ${tokens.colors.border.muted}`,\n borderRadius: tokens.borderRadius.md,\n padding: `${tokens.spacing.xs} ${tokens.spacing.sm}`,\n fontSize: tokens.typography.fontSize.sm,\n outline: \"none\",\n fontFamily: \"inherit\",\n }}\n />\n <button\n onClick={doSearch}\n aria-label=\"Find matches\"\n style={{\n background: tokens.colors.interactive.default,\n color: tokens.colors.text.inverse,\n border: \"none\",\n borderRadius: tokens.borderRadius.md,\n padding: `${tokens.spacing.xs} ${tokens.spacing.smd}`,\n fontSize: tokens.typography.fontSize.sm,\n cursor: \"pointer\",\n fontWeight: tokens.typography.fontWeight.medium,\n }}\n >\n Find\n </button>\n {searchResults.length > 0 && (\n <>\n <span\n style={{\n fontSize: tokens.typography.fontSize.sm,\n color: tokens.colors.text.muted,\n }}\n aria-live=\"polite\"\n >\n {currentSearchIdx + 1}/{searchResults.length}\n </span>\n <button\n onClick={prevSearchResult}\n aria-label=\"Previous match\"\n style={{\n background: \"none\",\n border: \"none\",\n color: tokens.colors.text.secondary,\n cursor: \"pointer\",\n fontSize: tokens.typography.fontSize.base,\n padding: tokens.spacing.xs,\n }}\n >\n ▲\n </button>\n <button\n onClick={nextSearchResult}\n aria-label=\"Next match\"\n style={{\n background: \"none\",\n border: \"none\",\n color: tokens.colors.text.secondary,\n cursor: \"pointer\",\n fontSize: tokens.typography.fontSize.base,\n padding: tokens.spacing.xs,\n }}\n >\n ▼\n </button>\n </>\n )}\n </div>\n )}\n </div>\n {/* end sticky header */}\n\n {/* Packet Header Summary (collapsible) */}\n {shouldShowHeaderSummary && packetHeaders && packetHeaders.length > 0 && (\n <div style={{ flexShrink: 0 }}>\n <div\n role=\"button\"\n tabIndex={0}\n aria-expanded={showHeaders}\n aria-label=\"Toggle packet headers\"\n onClick={() => setShowHeaders(!showHeaders)}\n onKeyDown={(e) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n setShowHeaders(!showHeaders);\n }\n }}\n style={{\n display: \"flex\",\n alignItems: \"center\",\n gap: tokens.spacing.sm,\n padding: `${tokens.spacing.sm} ${tokens.spacing.smd}`,\n background: tokens.colors.background.surface,\n cursor: \"pointer\",\n borderBottom: `1px solid ${tokens.colors.border.muted}`,\n userSelect: \"none\",\n }}\n >\n <span\n aria-hidden=\"true\"\n style={{\n fontSize: tokens.typography.fontSize.micro,\n color: tokens.colors.text.muted,\n transition: \"transform 150ms ease\",\n transform: showHeaders ? \"rotate(0deg)\" : \"rotate(-90deg)\",\n }}\n >\n {\"\\u25BC\"}\n </span>\n <span\n style={{\n fontSize: tokens.typography.fontSize.sm,\n color: tokens.colors.text.secondary,\n fontWeight: tokens.typography.fontWeight.medium,\n }}\n >\n Packet Headers\n </span>\n </div>\n {showHeaders && (\n <div\n style={{\n padding: `${tokens.spacing.sm} ${tokens.spacing.smd}`,\n borderBottom: `1px solid ${tokens.colors.border.muted}`,\n background: tokens.colors.background.base,\n }}\n >\n <div\n style={{\n display: \"grid\",\n gridTemplateColumns: \"auto 1fr\",\n gap: `${tokens.spacing.xxs} ${tokens.spacing.md}`,\n fontSize: tokens.typography.fontSize.sm,\n lineHeight: tokens.typography.lineHeight.tight,\n }}\n >\n {packetHeaders.map((entry, i) => (\n <React.Fragment key={i}>\n <span style={{ color: tokens.colors.text.muted }}>\n {entry.label}\n </span>\n <span\n style={{\n color: tokens.colors.text.primary,\n fontWeight: tokens.typography.fontWeight.medium,\n fontFamily: entry.mono\n ? tokens.typography.fontFamily.mono\n : \"inherit\",\n }}\n >\n {entry.value}\n </span>\n </React.Fragment>\n ))}\n </div>\n </div>\n )}\n </div>\n )}\n\n {/* Decoded Fields Table (collapsible) */}\n {shouldShowFieldsTable && decodedFields && decodedFields.length > 0 && (\n <div style={{ flexShrink: 0 }}>\n <div\n role=\"button\"\n tabIndex={0}\n aria-expanded={showFields}\n aria-label=\"Toggle decoded fields\"\n onClick={() => setShowFields(!showFields)}\n onKeyDown={(e) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n setShowFields(!showFields);\n }\n }}\n style={{\n display: \"flex\",\n alignItems: \"center\",\n gap: tokens.spacing.sm,\n padding: `${tokens.spacing.sm} ${tokens.spacing.smd}`,\n background: tokens.colors.background.surface,\n cursor: \"pointer\",\n borderBottom: `1px solid ${tokens.colors.border.muted}`,\n userSelect: \"none\",\n }}\n >\n <span\n aria-hidden=\"true\"\n style={{\n fontSize: tokens.typography.fontSize.micro,\n color: tokens.colors.text.muted,\n transition: \"transform 150ms ease\",\n transform: showFields ? \"rotate(0deg)\" : \"rotate(-90deg)\",\n }}\n >\n {\"\\u25BC\"}\n </span>\n <span\n style={{\n fontSize: tokens.typography.fontSize.sm,\n color: tokens.colors.text.secondary,\n fontWeight: tokens.typography.fontWeight.medium,\n }}\n >\n Decoded Fields ({decodedFields.length})\n </span>\n </div>\n {showFields && (\n <div\n role=\"table\"\n aria-label=\"Decoded packet fields\"\n style={{\n borderBottom: `1px solid ${tokens.colors.border.muted}`,\n maxHeight: decodedFieldsMaxHeight,\n overflowY: \"auto\",\n }}\n >\n {/* Fields table header */}\n <div\n role=\"row\"\n style={{\n display: \"flex\",\n alignItems: \"center\",\n padding: `${tokens.spacing.xs} ${tokens.spacing.smd}`,\n borderBottom: `1px solid ${tokens.colors.border.muted}`,\n gap: tokens.spacing.sm,\n position: \"sticky\",\n top: 0,\n background: tokens.colors.background.surface,\n zIndex: 1,\n }}\n >\n <span\n role=\"columnheader\"\n style={{\n flex: 2,\n fontSize: tokens.typography.fontSize.xxs,\n color: tokens.colors.text.muted,\n textTransform: \"uppercase\",\n letterSpacing: \"0.5px\",\n }}\n >\n Field\n </span>\n <span\n role=\"columnheader\"\n style={{\n flex: 2,\n fontSize: tokens.typography.fontSize.xxs,\n color: tokens.colors.text.muted,\n textTransform: \"uppercase\",\n letterSpacing: \"0.5px\",\n textAlign: \"right\",\n }}\n >\n Value\n </span>\n <span\n role=\"columnheader\"\n style={{\n flex: 1,\n fontSize: tokens.typography.fontSize.xxs,\n color: tokens.colors.text.muted,\n textTransform: \"uppercase\",\n letterSpacing: \"0.5px\",\n textAlign: \"right\",\n }}\n >\n Unit\n </span>\n <span\n role=\"columnheader\"\n style={{\n flex: 1,\n fontSize: tokens.typography.fontSize.xxs,\n color: tokens.colors.text.muted,\n textTransform: \"uppercase\",\n letterSpacing: \"0.5px\",\n textAlign: \"right\",\n }}\n >\n Offset\n </span>\n <span\n role=\"columnheader\"\n style={{\n flex: 1,\n fontSize: tokens.typography.fontSize.xxs,\n color: tokens.colors.text.muted,\n textTransform: \"uppercase\",\n letterSpacing: \"0.5px\",\n textAlign: \"right\",\n }}\n >\n Size\n </span>\n </div>\n {/* Field rows */}\n {decodedFields.map((field, fi) => {\n const isHovered = hoveredFieldIdx === fi;\n return (\n <div\n key={fi}\n onMouseEnter={() => handleFieldHover(fi)}\n onMouseLeave={handleFieldLeave}\n title={field.description}\n style={{\n display: \"flex\",\n alignItems: \"center\",\n padding: `3px ${tokens.spacing.smd}`,\n gap: tokens.spacing.sm,\n background: isHovered\n ? `${tokens.colors.status.caution}14`\n : fi % 2 === 0\n ? tokens.colors.background.base\n : \"transparent\",\n transition: \"background 0.15s ease\",\n cursor: \"default\",\n minHeight: tokens.elementSize.sm,\n }}\n >\n <span\n role=\"cell\"\n style={{\n flex: 2,\n fontSize: tokens.typography.fontSize.sm,\n color: tokens.colors.text.secondary,\n overflow: \"hidden\",\n textOverflow: \"ellipsis\",\n whiteSpace: \"nowrap\",\n }}\n >\n {field.name}\n </span>\n <span\n role=\"cell\"\n style={{\n flex: 2,\n fontSize: tokens.typography.fontSize.sm,\n color: tokens.colors.text.primary,\n fontWeight: tokens.typography.fontWeight.medium,\n fontFamily: tokens.typography.fontFamily.mono,\n textAlign: \"right\",\n }}\n >\n {field.value === null || field.value === undefined\n ? \"\\u2014\"\n : String(field.value)}\n </span>\n <span\n role=\"cell\"\n style={{\n flex: 1,\n fontSize: tokens.typography.fontSize.xxs,\n color: tokens.colors.text.muted,\n textAlign: \"right\",\n }}\n >\n {field.unit || \"\"}\n </span>\n <span\n role=\"cell\"\n style={{\n flex: 1,\n fontSize: tokens.typography.fontSize.xxs,\n color: tokens.colors.text.muted,\n textAlign: \"right\",\n fontFamily: tokens.typography.fontFamily.mono,\n }}\n >\n {field.byteOffset !== undefined ? field.byteOffset : \"\"}\n </span>\n <span\n role=\"cell\"\n style={{\n flex: 1,\n fontSize: tokens.typography.fontSize.xxs,\n color: tokens.colors.text.muted,\n textAlign: \"right\",\n fontFamily: tokens.typography.fontFamily.mono,\n }}\n >\n {field.sizeBits ? `${field.sizeBits}b` : \"\"}\n </span>\n </div>\n );\n })}\n </div>\n )}\n </div>\n )}\n\n {/* Region legend */}\n {regions.length > 0 && (\n <div\n role=\"group\"\n aria-label=\"Packet structure regions\"\n style={{\n padding: `${tokens.spacing.xs} ${tokens.spacing.smd}`,\n borderBottom: `1px solid ${tokens.colors.border.muted}`,\n display: \"flex\",\n alignItems: \"center\",\n gap: tokens.spacing.smd,\n flexWrap: \"wrap\",\n fontSize: tokens.typography.fontSize.xxs,\n background: tokens.colors.background.surface,\n }}\n >\n {regions.map((r, i) => (\n <span\n key={i}\n style={{\n display: \"inline-flex\",\n alignItems: \"center\",\n gap: tokens.spacing.xs,\n }}\n >\n <span\n aria-hidden=\"true\"\n style={{\n display: \"inline-block\",\n width: 10,\n height: 10,\n borderRadius: tokens.borderRadius.sm,\n background: r.color,\n border: `1px solid ${r.borderColor || \"transparent\"}`,\n }}\n />\n <span style={{ color: tokens.colors.text.muted }}>\n {r.label} ({r.end - r.start}B)\n </span>\n </span>\n ))}\n </div>\n )}\n\n {/* Column headers */}\n <div\n role=\"row\"\n aria-label=\"Column headers\"\n style={{\n padding: `${tokens.spacing.xs} ${tokens.spacing.smd}`,\n borderBottom: `1px solid ${tokens.colors.border.muted}`,\n display: \"flex\",\n alignItems: \"center\",\n fontSize: tokens.typography.fontSize.xxs,\n color: tokens.colors.text.muted,\n textTransform: \"uppercase\",\n letterSpacing: \"0.5px\",\n fontWeight: tokens.typography.fontWeight.medium,\n background: tokens.colors.background.surface,\n }}\n >\n {showOffset && (\n <span\n style={{\n marginRight: tokens.spacing.smd,\n minWidth: 60,\n textAlign: \"right\",\n }}\n >\n Offset\n </span>\n )}\n <span style={{ flex: 1 }}>Hex</span>\n {localShowAscii && (\n <>\n <span\n aria-hidden=\"true\"\n style={{\n color: `${tokens.colors.border.muted}`,\n marginRight: tokens.spacing.sm,\n }}\n >\n |\n </span>\n <span>ASCII</span>\n </>\n )}\n </div>\n\n {/* Hex content */}\n <div\n ref={scrollRef}\n role=\"grid\"\n aria-label=\"Hex data\"\n tabIndex={0}\n onScroll={handleHexScroll}\n style={{\n flex: \"1 1 160px\",\n minHeight: 160,\n overflow: \"auto\",\n padding: `${tokens.spacing.xs} ${tokens.spacing.smd}`,\n }}\n >\n <div\n style={{ height: totalRows * HEX_ROW_HEIGHT, position: \"relative\" }}\n >\n <div\n style={{\n position: \"absolute\",\n top: startRow * HEX_ROW_HEIGHT,\n left: 0,\n right: 0,\n }}\n >\n {virtualRows.map((row) => (\n <HexRow\n key={row.offset}\n offset={row.offset}\n bytes={row.bytes}\n bytesPerRow={localBytesPerRow}\n byteGrouping={localGrouping}\n offsetBase={localOffsetBase}\n totalBytes={data.length}\n showAscii={localShowAscii}\n showOffset={showOffset}\n highlights={allHighlights}\n regions={sortedRegions}\n effectiveHighlight={effectiveHighlight}\n selectedStart={selectStart}\n selectedEnd={selectEnd}\n cursorOffset={cursorOffset}\n onByteMouseDown={handleByteMouseDown}\n onByteMouseEnter={handleByteMouseEnter}\n onByteClick={handleByteClickCb}\n handleByteHoverInternal={handleByteHoverInternal}\n handleByteLeaveInternal={handleByteLeaveInternal}\n tokens={tokens}\n />\n ))}\n </div>\n </div>\n </div>\n\n {/* Highlight legend */}\n {highlights.length > 0 && (\n <div\n role=\"group\"\n aria-label=\"Search highlights\"\n style={{\n padding: `${tokens.spacing.xs} ${tokens.spacing.smd}`,\n borderTop: `1px solid ${tokens.colors.border.muted}`,\n display: \"flex\",\n alignItems: \"center\",\n gap: tokens.spacing.smd,\n flexWrap: \"wrap\",\n fontSize: tokens.typography.fontSize.xxs,\n background: tokens.colors.background.surface,\n }}\n >\n {highlights\n .filter((h) => h.label)\n .map((h, i) => (\n <span\n key={i}\n style={{\n display: \"flex\",\n alignItems: \"center\",\n gap: tokens.spacing.xs,\n }}\n >\n <span\n aria-hidden=\"true\"\n style={{\n width: 10,\n height: 10,\n borderRadius: tokens.borderRadius.sm,\n background: h.color,\n display: \"inline-block\",\n }}\n />\n <span style={{ color: tokens.colors.text.muted }}>\n {h.label} (0x{h.start.toString(16)}-0x{h.end.toString(16)})\n </span>\n </span>\n ))}\n </div>\n )}\n\n {/* Status bar */}\n {showStatusBar && (\n <div\n role=\"status\"\n aria-live=\"polite\"\n style={{\n padding: `${tokens.spacing.sm} ${tokens.spacing.smd}`,\n borderTop: `1px solid ${tokens.colors.border.muted}`,\n display: \"flex\",\n alignItems: \"center\",\n gap: tokens.spacing.md,\n fontSize: tokens.typography.fontSize.sm,\n background: tokens.colors.background.surface,\n color: tokens.colors.text.muted,\n flexWrap: \"wrap\",\n }}\n >\n <span>Size: {data.length.toLocaleString()} bytes</span>\n {cursorOffset >= 0 && cursorOffset < data.length && (\n <>\n <span>\n Offset: 0x{cursorOffset.toString(16).toUpperCase()} (\n {cursorOffset})\n </span>\n <span>\n Value: 0x{byteToHex(data[cursorOffset])} ({data[cursorOffset]})\n </span>\n </>\n )}\n {selectStart >= 0 && selectEnd >= 0 && selectStart !== selectEnd && (\n <span>\n Selection: {Math.abs(selectEnd - selectStart) + 1} bytes (0x\n {Math.min(selectStart, selectEnd).toString(16).toUpperCase()}-0x\n {Math.max(selectStart, selectEnd).toString(16).toUpperCase()})\n </span>\n )}\n {interpretation && (\n <span\n style={{\n display: \"flex\",\n gap: tokens.spacing.sm,\n color: tokens.colors.text.secondary,\n }}\n >\n <span>U8: {interpretation.u8}</span>\n <span>I8: {interpretation.i8}</span>\n {interpretation.u16 !== undefined && (\n <span>U16: {interpretation.u16}</span>\n )}\n {interpretation.u32 !== undefined && (\n <span>U32: {interpretation.u32}</span>\n )}\n {interpretation.f32 !== undefined && (\n <span>F32: {interpretation.f32.toFixed(6)}</span>\n )}\n </span>\n )}\n </div>\n )}\n </div>\n );\n}\n\nexport default HexViewer;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"HexViewer.js","names":[],"sources":["../../../../src/react/core/widgets/HexViewer.tsx"],"sourcesContent":["/**\r\n * @zendir/ui - HexViewer Component\r\n *\r\n * Displays raw binary data in a hex viewer format for telemetry packet analysis.\r\n * Shows offset, hex bytes, and ASCII representation side by side.\r\n *\r\n * Features:\r\n * - Hex + ASCII view with configurable bytes per row (8, 16, 32)\r\n * - Byte highlighting with custom colors (for field boundaries, errors, etc.)\r\n * - Click-to-select bytes and ranges\r\n * - Search by hex pattern or ASCII string\r\n * - Offset display in hex or decimal\r\n * - Live data streaming support (auto-scroll)\r\n * - Copy selection as hex, decimal, or ASCII\r\n * - Byte grouping (1, 2, 4, 8)\r\n * - Endianness toggle for multi-byte interpretation\r\n * - Status bar showing cursor position, selection info, data interpretation\r\n *\r\n * Astro UX Compliance:\r\n * - Dark theme with monospace font\r\n * - Status colors for highlighting\r\n * - Accessible keyboard navigation\r\n *\r\n * @example\r\n * ```tsx\r\n * <HexViewer\r\n * data={new Uint8Array([0x48, 0x65, 0x6C, 0x6C, 0x6F])}\r\n * bytesPerRow={16}\r\n * highlights={[{ start: 0, end: 4, color: '#4CAF50', label: 'Header' }]}\r\n * />\r\n * ```\r\n */\r\n\r\nimport React, {\r\n useState,\r\n useRef,\r\n useCallback,\r\n useMemo,\r\n useEffect,\r\n memo,\r\n} from \"react\";\r\nimport { useTheme } from \"../../theme\";\r\n\r\n// =============================================================================\r\n// Types\r\n// =============================================================================\r\n\r\nexport interface HexHighlight {\r\n /** Start byte offset (inclusive) */\r\n start: number;\r\n /** End byte offset (inclusive) */\r\n end: number;\r\n /** Highlight color (CSS color) */\r\n color: string;\r\n /** Optional label for the highlight region */\r\n label?: string;\r\n}\r\n\r\n/**\r\n * Named region for structural packet analysis (e.g., CCSDS Primary Header,\r\n * Secondary Header, User Data). Regions provide persistent background color\r\n * and a legend entry, unlike transient highlights.\r\n */\r\nexport interface HexRegion {\r\n /** Start byte offset (inclusive) */\r\n start: number;\r\n /** End byte offset (exclusive) */\r\n end: number;\r\n /** Region display name (e.g., \"Primary Header\") */\r\n label: string;\r\n /** Background fill color (CSS rgba recommended) */\r\n color: string;\r\n /** Border/accent color for the region */\r\n borderColor?: string;\r\n}\r\n\r\n/**\r\n * A decoded field within the packet payload for the fields table.\r\n * Enables cross-highlighting: hover a field row → highlight its bytes,\r\n * hover hex bytes → highlight the corresponding field row.\r\n */\r\nexport interface DecodedField {\r\n /** Field name */\r\n name: string;\r\n /** Decoded value (displayed via toString) */\r\n value: string | number | boolean | null;\r\n /** Engineering units (e.g., \"°C\", \"V\", \"rpm\") */\r\n unit?: string;\r\n /** Byte offset within the packet where this field starts */\r\n byteOffset?: number;\r\n /** Size in bits */\r\n sizeBits?: number;\r\n /** Optional description / tooltip */\r\n description?: string;\r\n}\r\n\r\n/**\r\n * Packet header key-value pairs shown in a collapsible summary panel.\r\n * Supports CCSDS-style primary + secondary header display.\r\n */\r\nexport interface PacketHeaderEntry {\r\n /** Label (e.g., \"APID\", \"Sequence Count\", \"Spacecraft ID\") */\r\n label: string;\r\n /** Value */\r\n value: string | number;\r\n /** Render value in monospace font */\r\n mono?: boolean;\r\n}\r\n\r\n/** Pre-defined region color palette for space packet headers */\r\nexport const REGION_COLORS = {\r\n primary: \"rgba(96, 165, 250, 0.15)\",\r\n secondary: \"rgba(168, 85, 247, 0.15)\",\r\n tertiary: \"rgba(251, 191, 36, 0.15)\",\r\n userData: \"rgba(34, 197, 94, 0.15)\",\r\n error: \"rgba(255, 56, 56, 0.15)\",\r\n} as const;\r\n\r\nexport const REGION_BORDER_COLORS = {\r\n primary: \"rgba(96, 165, 250, 0.5)\",\r\n secondary: \"rgba(168, 85, 247, 0.5)\",\r\n tertiary: \"rgba(251, 191, 36, 0.5)\",\r\n userData: \"rgba(34, 197, 94, 0.5)\",\r\n error: \"rgba(255, 56, 56, 0.5)\",\r\n} as const;\r\n\r\nexport type ByteGrouping = 1 | 2 | 4 | 8;\r\nexport type OffsetBase = \"hex\" | \"decimal\";\r\nexport type Endianness = \"big\" | \"little\";\r\n\r\nexport interface HexViewerProps {\r\n /** Binary data to display */\r\n data: Uint8Array | number[];\r\n /** Bytes per row (default: 16) */\r\n bytesPerRow?: 8 | 16 | 32;\r\n /** Byte grouping for hex display (default: 1) */\r\n byteGrouping?: ByteGrouping;\r\n /** Offset display base (default: 'hex') */\r\n offsetBase?: OffsetBase;\r\n /** Highlighted byte regions (transient, e.g., search results or field hover) */\r\n highlights?: HexHighlight[];\r\n /** Named structural regions (persistent background, e.g., Primary/Secondary Header) */\r\n regions?: HexRegion[];\r\n /** Decoded fields for the fields table (cross-highlights with hex bytes) */\r\n decodedFields?: DecodedField[];\r\n /** Packet header entries for the summary panel */\r\n packetHeaders?: PacketHeaderEntry[];\r\n /** External hover highlight range (for cross-component linking) */\r\n externalHighlight?: { start: number; end: number } | null;\r\n /** Whether to show the ASCII column (default: true) */\r\n showAscii?: boolean;\r\n /** Whether to show the offset column (default: true) */\r\n showOffset?: boolean;\r\n /** Whether to show the status/info bar (default: true) */\r\n showStatusBar?: boolean;\r\n /** Whether to show the search bar (default: true) */\r\n showSearch?: boolean;\r\n /** Whether to show the toolbar (default: true) */\r\n showToolbar?: boolean;\r\n /** Whether to show the decoded fields table (default: true if decodedFields provided) */\r\n showFieldsTable?: boolean;\r\n /** Whether to show the packet header summary (default: true if packetHeaders provided) */\r\n showHeaderSummary?: boolean;\r\n /**\r\n * Height of the viewer (default: 400).\r\n * When constrained, HexViewer enables outer scroll fallback so expanded\r\n * panels (headers/decoded fields) never trap lower sections.\r\n */\r\n height?: number | string;\r\n /** Title for the viewer */\r\n title?: string;\r\n /** Whether data is streaming (auto-scroll to bottom) */\r\n streaming?: boolean;\r\n /** Endianness for multi-byte interpretation (default: 'big') */\r\n endianness?: Endianness;\r\n /** Called when byte selection changes */\r\n onSelectionChange?: (start: number, end: number) => void;\r\n /** Called when a byte is clicked */\r\n onByteClick?: (offset: number, value: number) => void;\r\n /** Called when hovering a byte (for external cross-highlighting) */\r\n onByteHover?: (byteIndex: number) => void;\r\n /** Called when mouse leaves hex area */\r\n onByteLeave?: () => void;\r\n /** CSS class name */\r\n className?: string;\r\n /** Show outer container border (default: true). Set false when embedded inside a Container/card. */\r\n bordered?: boolean;\r\n /** Custom style overrides for the outer container */\r\n style?: React.CSSProperties;\r\n}\r\n\r\n// =============================================================================\r\n// Helpers\r\n// =============================================================================\r\n\r\nfunction toUint8Array(data: Uint8Array | number[]): Uint8Array {\r\n return data instanceof Uint8Array ? data : new Uint8Array(data);\r\n}\r\n\r\nfunction byteToHex(byte: number): string {\r\n return byte.toString(16).padStart(2, \"0\").toUpperCase();\r\n}\r\n\r\nfunction byteToAscii(byte: number): string {\r\n return byte >= 0x20 && byte <= 0x7e ? String.fromCharCode(byte) : \".\";\r\n}\r\n\r\nfunction formatOffset(\r\n offset: number,\r\n base: OffsetBase,\r\n totalBytes: number,\r\n): string {\r\n if (base === \"hex\") {\r\n const width = Math.max(\r\n 4,\r\n Math.ceil(Math.log2(Math.max(totalBytes, 1)) / 4) * 1,\r\n );\r\n return offset.toString(16).padStart(Math.max(width, 4), \"0\").toUpperCase();\r\n }\r\n const width = Math.max(4, totalBytes.toString().length);\r\n return offset.toString().padStart(width, \" \");\r\n}\r\n\r\nfunction interpretBytes(\r\n data: Uint8Array,\r\n offset: number,\r\n endianness: Endianness,\r\n): {\r\n u8: number;\r\n i8: number;\r\n u16?: number;\r\n i16?: number;\r\n u32?: number;\r\n i32?: number;\r\n f32?: number;\r\n f64?: number;\r\n} {\r\n const result: ReturnType<typeof interpretBytes> = {\r\n u8: data[offset],\r\n i8: data[offset] > 127 ? data[offset] - 256 : data[offset],\r\n };\r\n\r\n if (offset + 1 < data.length) {\r\n const view = new DataView(\r\n data.buffer,\r\n data.byteOffset + offset,\r\n Math.min(8, data.length - offset),\r\n );\r\n const be = endianness === \"big\";\r\n result.u16 = view.getUint16(0, !be);\r\n result.i16 = view.getInt16(0, !be);\r\n if (offset + 3 < data.length) {\r\n result.u32 = view.getUint32(0, !be);\r\n result.i32 = view.getInt32(0, !be);\r\n result.f32 = view.getFloat32(0, !be);\r\n }\r\n if (offset + 7 < data.length) {\r\n result.f64 = view.getFloat64(0, !be);\r\n }\r\n }\r\n return result;\r\n}\r\n\r\nconst HEX_ROW_HEIGHT = 22;\r\nconst HEX_OVERSCAN_ROWS = 16;\r\nconst MAX_RENDERED_SEARCH_HIGHLIGHTS = 400;\r\n\r\n// =============================================================================\r\n// Sub-components\r\n// =============================================================================\r\n\r\nconst HexRow = memo(function HexRow({\r\n offset,\r\n bytes,\r\n bytesPerRow,\r\n byteGrouping,\r\n offsetBase,\r\n totalBytes,\r\n showAscii,\r\n showOffset,\r\n highlights,\r\n regions,\r\n effectiveHighlight,\r\n selectedStart,\r\n selectedEnd,\r\n cursorOffset,\r\n onByteMouseDown,\r\n onByteMouseEnter,\r\n onByteClick,\r\n handleByteHoverInternal,\r\n handleByteLeaveInternal,\r\n tokens,\r\n}: {\r\n offset: number;\r\n bytes: Uint8Array;\r\n bytesPerRow: number;\r\n byteGrouping: ByteGrouping;\r\n offsetBase: OffsetBase;\r\n totalBytes: number;\r\n showAscii: boolean;\r\n showOffset: boolean;\r\n highlights: HexHighlight[];\r\n regions: HexRegion[];\r\n effectiveHighlight: { start: number; end: number } | null | undefined;\r\n selectedStart: number;\r\n selectedEnd: number;\r\n cursorOffset: number;\r\n onByteMouseDown: (idx: number) => void;\r\n onByteMouseEnter: (idx: number) => void;\r\n onByteClick: (idx: number) => void;\r\n handleByteHoverInternal: (idx: number) => void;\r\n handleByteLeaveInternal: () => void;\r\n tokens: ReturnType<typeof useTheme>[\"tokens\"];\r\n}) {\r\n const hexCells: React.ReactNode[] = [];\r\n const asciiCells: React.ReactNode[] = [];\r\n\r\n for (let i = 0; i < bytesPerRow; i++) {\r\n const byteIdx = offset + i;\r\n const isValid = i < bytes.length;\r\n const byte = isValid ? bytes[i] : 0;\r\n\r\n // Determine highlight color (priority: selection > cross-highlight > search highlight > region)\r\n let bgColor = \"transparent\";\r\n let fgColor = tokens.colors.text.primary;\r\n let borderBottom = \"2px solid transparent\";\r\n const isSelected =\r\n byteIdx >= Math.min(selectedStart, selectedEnd) &&\r\n byteIdx <= Math.max(selectedStart, selectedEnd) &&\r\n selectedStart >= 0;\r\n const isCursor = byteIdx === cursorOffset;\r\n\r\n // 1. Region background (lowest priority — always visible unless overridden)\r\n let regionColor: string | null = null;\r\n for (let r = 0; r < regions.length; r++) {\r\n const region = regions[r];\r\n if (byteIdx < region.start) break;\r\n if (byteIdx >= region.start && byteIdx < region.end) {\r\n regionColor = region.color;\r\n break;\r\n }\r\n }\r\n if (regionColor) {\r\n bgColor = regionColor;\r\n }\r\n\r\n // 2. Search highlights\r\n for (const h of highlights) {\r\n if (byteIdx >= h.start && byteIdx <= h.end) {\r\n bgColor = `${h.color}33`;\r\n fgColor = h.color;\r\n break;\r\n }\r\n }\r\n\r\n // 3. Cross-highlight from field hover or external\r\n if (\r\n effectiveHighlight &&\r\n byteIdx >= effectiveHighlight.start &&\r\n byteIdx < effectiveHighlight.end\r\n ) {\r\n bgColor = `${tokens.colors.status.caution}4D`;\r\n borderBottom = `2px solid ${tokens.colors.status.caution}`;\r\n }\r\n\r\n // 4. Selection (highest priority)\r\n if (isSelected) {\r\n bgColor = `${tokens.colors.interactive.default}4D`;\r\n fgColor = tokens.colors.interactive.default;\r\n }\r\n\r\n // Add grouping space\r\n const needsGroupSpace = i > 0 && i % byteGrouping === 0;\r\n\r\n hexCells.push(\r\n <span\r\n key={`hex-${i}`}\r\n role=\"button\"\r\n tabIndex={isValid ? 0 : -1}\r\n onMouseDown={() => isValid && onByteMouseDown(byteIdx)}\r\n onMouseEnter={() => {\r\n isValid && onByteMouseEnter(byteIdx);\r\n isValid && handleByteHoverInternal(byteIdx);\r\n }}\r\n onMouseLeave={() => isValid && handleByteLeaveInternal()}\r\n onClick={() => isValid && onByteClick(byteIdx)}\r\n onKeyDown={(e) => {\r\n if (isValid && (e.key === \"Enter\" || e.key === \" \")) {\r\n e.preventDefault();\r\n onByteClick(byteIdx);\r\n }\r\n }}\r\n style={{\r\n padding: \"0 2px\",\r\n marginLeft: needsGroupSpace ? 6 : 0,\r\n background: bgColor,\r\n color: isValid ? fgColor : `${tokens.colors.text.muted}26`,\r\n cursor: isValid ? \"pointer\" : \"default\",\r\n borderBottom: isCursor\r\n ? `2px solid ${tokens.colors.status.normal}`\r\n : borderBottom,\r\n borderRadius: tokens.borderRadius.sm,\r\n userSelect: \"none\",\r\n transition: \"background 0.1s ease\",\r\n }}\r\n >\r\n {isValid ? byteToHex(byte) : \" \"}\r\n </span>,\r\n );\r\n\r\n if (showAscii) {\r\n asciiCells.push(\r\n <span\r\n key={`ascii-${i}`}\r\n role=\"button\"\r\n tabIndex={isValid ? 0 : -1}\r\n onMouseDown={() => isValid && onByteMouseDown(byteIdx)}\r\n onMouseEnter={() => {\r\n isValid && onByteMouseEnter(byteIdx);\r\n isValid && handleByteHoverInternal(byteIdx);\r\n }}\r\n onMouseLeave={() => isValid && handleByteLeaveInternal()}\r\n onClick={() => isValid && onByteClick(byteIdx)}\r\n onKeyDown={(e) => {\r\n if (isValid && (e.key === \"Enter\" || e.key === \" \")) {\r\n e.preventDefault();\r\n onByteClick(byteIdx);\r\n }\r\n }}\r\n style={{\r\n background: bgColor,\r\n color: isValid\r\n ? byte >= 0x20 && byte <= 0x7e\r\n ? fgColor\r\n : `${tokens.colors.text.muted}33`\r\n : \"transparent\",\r\n cursor: isValid ? \"pointer\" : \"default\",\r\n borderBottom: isCursor\r\n ? `2px solid ${tokens.colors.status.normal}`\r\n : borderBottom,\r\n borderRadius: tokens.borderRadius.sm,\r\n userSelect: \"none\",\r\n width: 8,\r\n textAlign: \"center\" as const,\r\n }}\r\n >\r\n {isValid ? byteToAscii(byte) : \" \"}\r\n </span>,\r\n );\r\n }\r\n }\r\n\r\n return (\r\n <div\r\n style={{\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n height: HEX_ROW_HEIGHT,\r\n lineHeight: `${HEX_ROW_HEIGHT}px`,\r\n }}\r\n >\r\n {showOffset && (\r\n <span\r\n style={{\r\n color: tokens.colors.text.muted,\r\n marginRight: 12,\r\n minWidth: 60,\r\n textAlign: \"right\",\r\n }}\r\n >\r\n {formatOffset(offset, offsetBase, totalBytes)}\r\n </span>\r\n )}\r\n <span\r\n style={{\r\n display: \"flex\",\r\n gap: 1,\r\n marginRight: showAscii ? 16 : 0,\r\n letterSpacing: \"0.5px\",\r\n }}\r\n >\r\n {hexCells}\r\n </span>\r\n {showAscii && (\r\n <>\r\n <span\r\n style={{ color: `${tokens.colors.border.muted}`, marginRight: 8 }}\r\n >\r\n │\r\n </span>\r\n <span style={{ display: \"flex\", letterSpacing: \"1px\" }}>\r\n {asciiCells}\r\n </span>\r\n </>\r\n )}\r\n </div>\r\n );\r\n});\r\n\r\n// =============================================================================\r\n// Main Component\r\n// =============================================================================\r\n\r\nexport function HexViewer({\r\n data: rawData,\r\n bytesPerRow = 16,\r\n byteGrouping = 1,\r\n offsetBase = \"hex\",\r\n highlights = [],\r\n regions = [],\r\n decodedFields,\r\n packetHeaders,\r\n externalHighlight,\r\n showAscii = true,\r\n showOffset = true,\r\n showStatusBar = true,\r\n showSearch = true,\r\n showToolbar = true,\r\n showFieldsTable,\r\n showHeaderSummary,\r\n height = 400,\r\n title,\r\n streaming = false,\r\n endianness = \"big\",\r\n onSelectionChange,\r\n onByteClick,\r\n onByteHover,\r\n onByteLeave,\r\n className = \"\",\r\n bordered = true,\r\n style: styleProp,\r\n}: HexViewerProps): React.ReactElement {\r\n const { tokens } = useTheme();\r\n const data = useMemo(() => toUint8Array(rawData), [rawData]);\r\n const [hoveredFieldIdx, setHoveredFieldIdx] = useState<number | null>(null);\r\n const [fieldHighlight, setFieldHighlight] = useState<{\r\n start: number;\r\n end: number;\r\n } | null>(null);\r\n const [showHeaders, setShowHeaders] = useState(true);\r\n const [showFields, setShowFields] = useState(true);\r\n\r\n const sortedRegions = useMemo(\r\n () => [...regions].sort((a, b) => a.start - b.start),\r\n [regions],\r\n );\r\n\r\n // Build field byte ranges for cross-highlighting\r\n const fieldByteRanges = useMemo(() => {\r\n if (!decodedFields) return [];\r\n return decodedFields.map((f) => ({\r\n start: f.byteOffset ?? 0,\r\n end: (f.byteOffset ?? 0) + Math.ceil((f.sizeBits ?? 0) / 8),\r\n name: f.name,\r\n }));\r\n }, [decodedFields]);\r\n\r\n // Cross-highlight: field hover → byte highlight\r\n const handleFieldHover = useCallback(\r\n (fieldIdx: number) => {\r\n setHoveredFieldIdx(fieldIdx);\r\n const range = fieldByteRanges[fieldIdx];\r\n if (range && range.end > range.start) setFieldHighlight(range);\r\n },\r\n [fieldByteRanges],\r\n );\r\n\r\n const handleFieldLeave = useCallback(() => {\r\n setHoveredFieldIdx(null);\r\n setFieldHighlight(null);\r\n }, []);\r\n\r\n // Cross-highlight: byte hover → field highlight\r\n const handleByteHoverInternal = useCallback(\r\n (byteIdx: number) => {\r\n onByteHover?.(byteIdx);\r\n const fi = fieldByteRanges.findIndex(\r\n (r) => byteIdx >= r.start && byteIdx < r.end,\r\n );\r\n if (fi >= 0) {\r\n setHoveredFieldIdx(fi);\r\n setFieldHighlight(fieldByteRanges[fi]);\r\n } else {\r\n setHoveredFieldIdx(null);\r\n setFieldHighlight({ start: byteIdx, end: byteIdx + 1 });\r\n }\r\n },\r\n [fieldByteRanges, onByteHover],\r\n );\r\n\r\n const handleByteLeaveInternal = useCallback(() => {\r\n onByteLeave?.();\r\n setHoveredFieldIdx(null);\r\n setFieldHighlight(null);\r\n }, [onByteLeave]);\r\n\r\n // Merge field highlight + external highlight into effective highlights\r\n const effectiveHighlight = externalHighlight || fieldHighlight;\r\n\r\n const shouldShowFieldsTable =\r\n showFieldsTable ?? (decodedFields && decodedFields.length > 0);\r\n const shouldShowHeaderSummary =\r\n showHeaderSummary ?? (packetHeaders && packetHeaders.length > 0);\r\n const scrollRef = useRef<HTMLDivElement>(null);\r\n const hasConstrainedHeight = useMemo(() => {\r\n if (height === undefined || height === null) return false;\r\n if (typeof height === \"string\")\r\n return height.trim().toLowerCase() !== \"auto\";\r\n return true;\r\n }, [height]);\r\n const decodedFieldsMaxHeight = useMemo(() => {\r\n if (typeof height === \"number\") {\r\n // Keep decoded fields useful but avoid starving the hex grid.\r\n return Math.min(220, Math.max(120, Math.floor(height * 0.32)));\r\n }\r\n return 200;\r\n }, [height]);\r\n\r\n const [cursorOffset, setCursorOffset] = useState(-1);\r\n const [selectStart, setSelectStart] = useState(-1);\r\n const [selectEnd, setSelectEnd] = useState(-1);\r\n const [isDragging, setIsDragging] = useState(false);\r\n const [searchQuery, setSearchQuery] = useState(\"\");\r\n const [searchType, setSearchType] = useState<\"hex\" | \"ascii\">(\"hex\");\r\n const [searchResults, setSearchResults] = useState<number[]>([]);\r\n const [currentSearchIdx, setCurrentSearchIdx] = useState(-1);\r\n const [localBytesPerRow, setLocalBytesPerRow] = useState(bytesPerRow);\r\n const [localGrouping, setLocalGrouping] = useState(byteGrouping);\r\n const [localOffsetBase, setLocalOffsetBase] = useState(offsetBase);\r\n const [localEndianness, setLocalEndianness] = useState(endianness);\r\n const [localShowAscii, setLocalShowAscii] = useState(showAscii);\r\n const [scrollTop, setScrollTop] = useState(0);\r\n const [viewportHeight, setViewportHeight] = useState(160);\r\n\r\n // Auto-scroll when streaming\r\n useEffect(() => {\r\n if (streaming && scrollRef.current) {\r\n scrollRef.current.scrollTop = scrollRef.current.scrollHeight;\r\n }\r\n }, [data, streaming]);\r\n\r\n useEffect(() => {\r\n const el = scrollRef.current;\r\n if (!el) return undefined;\r\n\r\n const updateViewportHeight = () => {\r\n setViewportHeight(Math.max(120, el.clientHeight || 160));\r\n };\r\n\r\n updateViewportHeight();\r\n if (typeof ResizeObserver !== \"undefined\") {\r\n const observer = new ResizeObserver(updateViewportHeight);\r\n observer.observe(el);\r\n return () => observer.disconnect();\r\n }\r\n\r\n window.addEventListener(\"resize\", updateViewportHeight);\r\n return () => window.removeEventListener(\"resize\", updateViewportHeight);\r\n }, []);\r\n\r\n const totalRows = Math.max(1, Math.ceil(data.length / localBytesPerRow));\r\n const startRow = Math.max(\r\n 0,\r\n Math.floor(scrollTop / HEX_ROW_HEIGHT) - HEX_OVERSCAN_ROWS,\r\n );\r\n const visibleRowCount =\r\n Math.ceil(viewportHeight / HEX_ROW_HEIGHT) + HEX_OVERSCAN_ROWS * 2;\r\n const endRow = Math.min(totalRows, startRow + visibleRowCount);\r\n const virtualRows = useMemo(() => {\r\n const result: { offset: number; bytes: Uint8Array }[] = [];\r\n for (let row = startRow; row < endRow; row++) {\r\n const offset = row * localBytesPerRow;\r\n const bytes = data.subarray(\r\n offset,\r\n Math.min(data.length, offset + localBytesPerRow),\r\n );\r\n result.push({ offset, bytes });\r\n }\r\n if (result.length === 0) {\r\n result.push({ offset: 0, bytes: new Uint8Array(0) });\r\n }\r\n return result;\r\n }, [data, localBytesPerRow, startRow, endRow]);\r\n\r\n // Search\r\n const doSearch = useCallback(() => {\r\n if (!searchQuery.trim()) {\r\n setSearchResults([]);\r\n setCurrentSearchIdx(-1);\r\n return;\r\n }\r\n\r\n const results: number[] = [];\r\n if (searchType === \"hex\") {\r\n const hexStr = searchQuery.replace(/\\s+/g, \"\");\r\n if (hexStr.length % 2 !== 0 || !/^[0-9a-fA-F]+$/.test(hexStr)) return;\r\n const searchBytes = new Uint8Array(hexStr.length / 2);\r\n for (let i = 0; i < searchBytes.length; i++) {\r\n searchBytes[i] = parseInt(hexStr.slice(i * 2, i * 2 + 2), 16);\r\n }\r\n for (let i = 0; i <= data.length - searchBytes.length; i++) {\r\n let match = true;\r\n for (let j = 0; j < searchBytes.length; j++) {\r\n if (data[i + j] !== searchBytes[j]) {\r\n match = false;\r\n break;\r\n }\r\n }\r\n if (match) results.push(i);\r\n }\r\n } else {\r\n const encoder = new TextEncoder();\r\n const searchBytes = encoder.encode(searchQuery);\r\n for (let i = 0; i <= data.length - searchBytes.length; i++) {\r\n let match = true;\r\n for (let j = 0; j < searchBytes.length; j++) {\r\n if (data[i + j] !== searchBytes[j]) {\r\n match = false;\r\n break;\r\n }\r\n }\r\n if (match) results.push(i);\r\n }\r\n }\r\n setSearchResults(results);\r\n setCurrentSearchIdx(results.length > 0 ? 0 : -1);\r\n if (results.length > 0) {\r\n setCursorOffset(results[0]);\r\n if (scrollRef.current) {\r\n const rowIdx = Math.floor(results[0] / localBytesPerRow);\r\n scrollRef.current.scrollTop = Math.max(\r\n 0,\r\n rowIdx * HEX_ROW_HEIGHT - 100,\r\n );\r\n }\r\n }\r\n }, [searchQuery, searchType, data, localBytesPerRow]);\r\n\r\n const scrollToOffset = useCallback(\r\n (offset: number) => {\r\n if (!scrollRef.current) return;\r\n const rowIdx = Math.floor(offset / localBytesPerRow);\r\n scrollRef.current.scrollTop = Math.max(0, rowIdx * HEX_ROW_HEIGHT - 100);\r\n },\r\n [localBytesPerRow],\r\n );\r\n\r\n const handleHexScroll = useCallback(\r\n (event: React.UIEvent<HTMLDivElement>) => {\r\n setScrollTop(event.currentTarget.scrollTop);\r\n },\r\n [],\r\n );\r\n\r\n const nextSearchResult = useCallback(() => {\r\n if (searchResults.length === 0) return;\r\n const nextIdx = (currentSearchIdx + 1) % searchResults.length;\r\n setCurrentSearchIdx(nextIdx);\r\n setCursorOffset(searchResults[nextIdx]);\r\n scrollToOffset(searchResults[nextIdx]);\r\n }, [searchResults, currentSearchIdx, scrollToOffset]);\r\n\r\n const prevSearchResult = useCallback(() => {\r\n if (searchResults.length === 0) return;\r\n const prevIdx =\r\n (currentSearchIdx - 1 + searchResults.length) % searchResults.length;\r\n setCurrentSearchIdx(prevIdx);\r\n setCursorOffset(searchResults[prevIdx]);\r\n scrollToOffset(searchResults[prevIdx]);\r\n }, [searchResults, currentSearchIdx, scrollToOffset]);\r\n\r\n // Selection handlers\r\n const handleByteMouseDown = useCallback((idx: number) => {\r\n setSelectStart(idx);\r\n setSelectEnd(idx);\r\n setCursorOffset(idx);\r\n setIsDragging(true);\r\n }, []);\r\n\r\n const handleByteMouseEnter = useCallback(\r\n (idx: number) => {\r\n if (isDragging) {\r\n setSelectEnd(idx);\r\n }\r\n },\r\n [isDragging],\r\n );\r\n\r\n const handleMouseUp = useCallback(() => {\r\n if (isDragging) {\r\n setIsDragging(false);\r\n if (onSelectionChange && selectStart >= 0 && selectEnd >= 0) {\r\n onSelectionChange(\r\n Math.min(selectStart, selectEnd),\r\n Math.max(selectStart, selectEnd),\r\n );\r\n }\r\n }\r\n }, [isDragging, selectStart, selectEnd, onSelectionChange]);\r\n\r\n useEffect(() => {\r\n window.addEventListener(\"mouseup\", handleMouseUp);\r\n return () => window.removeEventListener(\"mouseup\", handleMouseUp);\r\n }, [handleMouseUp]);\r\n\r\n const handleByteClickCb = useCallback(\r\n (idx: number) => {\r\n setCursorOffset(idx);\r\n if (onByteClick) {\r\n onByteClick(idx, data[idx]);\r\n }\r\n },\r\n [data, onByteClick],\r\n );\r\n\r\n // Copy selection\r\n const copySelection = useCallback(\r\n (format: \"hex\" | \"decimal\" | \"ascii\") => {\r\n if (selectStart < 0 || selectEnd < 0) return;\r\n const start = Math.min(selectStart, selectEnd);\r\n const end = Math.max(selectStart, selectEnd);\r\n const selected = data.slice(start, end + 1);\r\n\r\n let text: string;\r\n if (format === \"hex\") {\r\n text = Array.from(selected)\r\n .map((b) => byteToHex(b))\r\n .join(\" \");\r\n } else if (format === \"decimal\") {\r\n text = Array.from(selected).join(\", \");\r\n } else {\r\n text = Array.from(selected)\r\n .map((b) => byteToAscii(b))\r\n .join(\"\");\r\n }\r\n navigator.clipboard?.writeText(text);\r\n },\r\n [data, selectStart, selectEnd],\r\n );\r\n\r\n // Interpretation of cursor byte\r\n const interpretation = useMemo(() => {\r\n if (cursorOffset < 0 || cursorOffset >= data.length) return null;\r\n return interpretBytes(data, cursorOffset, localEndianness);\r\n }, [data, cursorOffset, localEndianness]);\r\n\r\n const renderedSearchResultEntries = useMemo(() => {\r\n if (searchResults.length <= MAX_RENDERED_SEARCH_HIGHLIGHTS) {\r\n return searchResults.map((pos, idx) => ({ pos, idx }));\r\n }\r\n\r\n const halfWindow = Math.floor(MAX_RENDERED_SEARCH_HIGHLIGHTS / 2);\r\n const anchor = currentSearchIdx >= 0 ? currentSearchIdx : 0;\r\n const start = Math.max(0, anchor - halfWindow);\r\n const end = Math.min(\r\n searchResults.length,\r\n start + MAX_RENDERED_SEARCH_HIGHLIGHTS,\r\n );\r\n const adjustedStart = Math.max(0, end - MAX_RENDERED_SEARCH_HIGHLIGHTS);\r\n\r\n return searchResults\r\n .slice(adjustedStart, end)\r\n .map((pos, i) => ({ pos, idx: adjustedStart + i }));\r\n }, [searchResults, currentSearchIdx]);\r\n\r\n // Search highlights\r\n const allHighlights = useMemo(() => {\r\n const h = [...highlights];\r\n const searchLen =\r\n searchType === \"hex\"\r\n ? searchQuery.replace(/\\s+/g, \"\").length / 2\r\n : searchQuery.length;\r\n renderedSearchResultEntries.forEach(({ pos, idx }) => {\r\n h.push({\r\n start: pos,\r\n end: pos + Math.max(0, searchLen - 1),\r\n color:\r\n idx === currentSearchIdx\r\n ? tokens.colors.status.serious\r\n : tokens.colors.status.caution,\r\n label: `Search match ${idx + 1}`,\r\n });\r\n });\r\n return h;\r\n }, [\r\n highlights,\r\n renderedSearchResultEntries,\r\n currentSearchIdx,\r\n searchQuery,\r\n searchType,\r\n tokens,\r\n ]);\r\n\r\n const containerStyle: React.CSSProperties = {\r\n fontFamily: tokens.typography.fontFamily.mono,\r\n fontSize: 13,\r\n lineHeight: \"22px\",\r\n background: tokens.colors.background.base,\r\n border: bordered ? `1px solid ${tokens.colors.border.muted}` : \"none\",\r\n borderRadius: tokens.borderRadius.lg,\r\n overflowX: \"hidden\",\r\n overflowY: hasConstrainedHeight ? \"auto\" : \"visible\",\r\n display: \"flex\",\r\n flexDirection: \"column\",\r\n minHeight: 0,\r\n height: hasConstrainedHeight ? height : undefined,\r\n color: tokens.colors.text.primary,\r\n ...styleProp,\r\n };\r\n\r\n return (\r\n <div className={`hex-viewer ${className}`} style={containerStyle}>\r\n {/* Sticky header: title bar + toolbar stay pinned while content scrolls */}\r\n <div\r\n style={{\r\n position: \"sticky\",\r\n top: 0,\r\n zIndex: 10,\r\n background: tokens.colors.background.surface,\r\n flexShrink: 0,\r\n }}\r\n >\r\n {/* Title bar */}\r\n {title && (\r\n <div\r\n style={{\r\n padding: `${tokens.spacing.sm} ${tokens.spacing.smd}`,\r\n borderBottom: `1px solid ${tokens.colors.border.muted}`,\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n justifyContent: \"space-between\",\r\n }}\r\n >\r\n <span\r\n style={{\r\n fontWeight: tokens.typography.fontWeight.medium,\r\n fontSize: tokens.typography.fontSize.base,\r\n fontFamily: tokens.typography.fontFamily.primary,\r\n }}\r\n >\r\n {title}\r\n </span>\r\n <span\r\n style={{\r\n fontSize: tokens.typography.fontSize.sm,\r\n color: tokens.colors.text.muted,\r\n }}\r\n >\r\n {data.length.toLocaleString()} bytes\r\n </span>\r\n </div>\r\n )}\r\n\r\n {/* Toolbar */}\r\n {showToolbar && (\r\n <div\r\n role=\"toolbar\"\r\n aria-label=\"Hex viewer controls\"\r\n style={{\r\n padding: `${tokens.spacing.sm} ${tokens.spacing.smd}`,\r\n borderBottom: `1px solid ${tokens.colors.border.muted}`,\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n gap: tokens.spacing.sm,\r\n flexWrap: \"wrap\",\r\n fontSize: tokens.typography.fontSize.sm,\r\n }}\r\n >\r\n <label\r\n style={{\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n gap: 4,\r\n color: tokens.colors.text.muted,\r\n }}\r\n >\r\n Bytes/Row:\r\n <select\r\n aria-label=\"Bytes per row\"\r\n value={localBytesPerRow}\r\n onChange={(e) =>\r\n setLocalBytesPerRow(Number(e.target.value) as 8 | 16 | 32)\r\n }\r\n style={{\r\n background: tokens.colors.background.base,\r\n color: tokens.colors.text.primary,\r\n border: `1px solid ${tokens.colors.border.muted}`,\r\n borderRadius: tokens.borderRadius.md,\r\n padding: \"2px 4px\",\r\n fontSize: 11,\r\n }}\r\n >\r\n <option value={8}>8</option>\r\n <option value={16}>16</option>\r\n <option value={32}>32</option>\r\n </select>\r\n </label>\r\n <label\r\n style={{\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n gap: 4,\r\n color: tokens.colors.text.muted,\r\n }}\r\n >\r\n Group:\r\n <select\r\n aria-label=\"Byte grouping\"\r\n value={localGrouping}\r\n onChange={(e) =>\r\n setLocalGrouping(Number(e.target.value) as ByteGrouping)\r\n }\r\n style={{\r\n background: tokens.colors.background.base,\r\n color: tokens.colors.text.primary,\r\n border: `1px solid ${tokens.colors.border.muted}`,\r\n borderRadius: tokens.borderRadius.md,\r\n padding: \"2px 4px\",\r\n fontSize: 11,\r\n }}\r\n >\r\n <option value={1}>1</option>\r\n <option value={2}>2</option>\r\n <option value={4}>4</option>\r\n <option value={8}>8</option>\r\n </select>\r\n </label>\r\n <label\r\n style={{\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n gap: 4,\r\n color: tokens.colors.text.muted,\r\n }}\r\n >\r\n Offset:\r\n <select\r\n aria-label=\"Offset format\"\r\n value={localOffsetBase}\r\n onChange={(e) =>\r\n setLocalOffsetBase(e.target.value as OffsetBase)\r\n }\r\n style={{\r\n background: tokens.colors.background.base,\r\n color: tokens.colors.text.primary,\r\n border: `1px solid ${tokens.colors.border.muted}`,\r\n borderRadius: tokens.borderRadius.md,\r\n padding: \"2px 4px\",\r\n fontSize: 11,\r\n }}\r\n >\r\n <option value=\"hex\">Hex</option>\r\n <option value=\"decimal\">Dec</option>\r\n </select>\r\n </label>\r\n <label\r\n style={{\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n gap: 4,\r\n color: tokens.colors.text.muted,\r\n }}\r\n >\r\n Endian:\r\n <select\r\n aria-label=\"Endianness\"\r\n value={localEndianness}\r\n onChange={(e) =>\r\n setLocalEndianness(e.target.value as Endianness)\r\n }\r\n style={{\r\n background: tokens.colors.background.base,\r\n color: tokens.colors.text.primary,\r\n border: `1px solid ${tokens.colors.border.muted}`,\r\n borderRadius: tokens.borderRadius.md,\r\n padding: \"2px 4px\",\r\n fontSize: 11,\r\n }}\r\n >\r\n <option value=\"big\">Big</option>\r\n <option value=\"little\">Little</option>\r\n </select>\r\n </label>\r\n <label\r\n style={{\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n gap: 4,\r\n color: tokens.colors.text.muted,\r\n cursor: \"pointer\",\r\n }}\r\n >\r\n <input\r\n type=\"checkbox\"\r\n checked={localShowAscii}\r\n onChange={(e) => setLocalShowAscii(e.target.checked)}\r\n style={{ accentColor: tokens.colors.interactive.default }}\r\n />\r\n ASCII\r\n </label>\r\n <div style={{ flex: 1 }} />\r\n {selectStart >= 0 && selectEnd >= 0 && (\r\n <div style={{ display: \"flex\", gap: 4 }}>\r\n {([\"hex\", \"decimal\", \"ascii\"] as const).map((fmt) => (\r\n <button\r\n key={fmt}\r\n onClick={() => copySelection(fmt)}\r\n style={{\r\n background: tokens.colors.background.base,\r\n color: tokens.colors.text.secondary,\r\n border: `1px solid ${tokens.colors.border.muted}`,\r\n borderRadius: tokens.borderRadius.md,\r\n padding: \"2px 8px\",\r\n fontSize: 10,\r\n cursor: \"pointer\",\r\n }}\r\n >\r\n Copy {fmt}\r\n </button>\r\n ))}\r\n </div>\r\n )}\r\n </div>\r\n )}\r\n\r\n {/* Search bar */}\r\n {showSearch && (\r\n <div\r\n role=\"search\"\r\n style={{\r\n padding: `${tokens.spacing.sm} ${tokens.spacing.smd}`,\r\n borderBottom: `1px solid ${tokens.colors.border.muted}`,\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n gap: tokens.spacing.sm,\r\n }}\r\n >\r\n <label\r\n htmlFor=\"hex-search-type\"\r\n style={{\r\n position: \"absolute\",\r\n width: 1,\r\n height: 1,\r\n padding: 0,\r\n margin: -1,\r\n overflow: \"hidden\",\r\n clip: \"rect(0, 0, 0, 0)\",\r\n border: 0,\r\n }}\r\n >\r\n Search type\r\n </label>\r\n <select\r\n id=\"hex-search-type\"\r\n value={searchType}\r\n onChange={(e) => setSearchType(e.target.value as \"hex\" | \"ascii\")}\r\n style={{\r\n background: tokens.colors.background.base,\r\n color: tokens.colors.text.primary,\r\n border: `1px solid ${tokens.colors.border.muted}`,\r\n borderRadius: tokens.borderRadius.md,\r\n padding: `${tokens.spacing.xs} ${tokens.spacing.sm}`,\r\n fontSize: tokens.typography.fontSize.sm,\r\n }}\r\n >\r\n <option value=\"hex\">Hex</option>\r\n <option value=\"ascii\">ASCII</option>\r\n </select>\r\n <input\r\n type=\"text\"\r\n value={searchQuery}\r\n onChange={(e) => setSearchQuery(e.target.value)}\r\n onKeyDown={(e) => {\r\n if (e.key === \"Enter\") doSearch();\r\n }}\r\n placeholder={\r\n searchType === \"hex\" ? \"e.g. 48 65 6C 6C 6F\" : \"Search ASCII...\"\r\n }\r\n aria-label=\"Search hex data\"\r\n style={{\r\n flex: 1,\r\n background: tokens.colors.background.base,\r\n color: tokens.colors.text.primary,\r\n border: `1px solid ${tokens.colors.border.muted}`,\r\n borderRadius: tokens.borderRadius.md,\r\n padding: `${tokens.spacing.xs} ${tokens.spacing.sm}`,\r\n fontSize: tokens.typography.fontSize.sm,\r\n outline: \"none\",\r\n fontFamily: \"inherit\",\r\n }}\r\n />\r\n <button\r\n onClick={doSearch}\r\n aria-label=\"Find matches\"\r\n style={{\r\n background: tokens.colors.interactive.default,\r\n color: tokens.colors.text.inverse,\r\n border: \"none\",\r\n borderRadius: tokens.borderRadius.md,\r\n padding: `${tokens.spacing.xs} ${tokens.spacing.smd}`,\r\n fontSize: tokens.typography.fontSize.sm,\r\n cursor: \"pointer\",\r\n fontWeight: tokens.typography.fontWeight.medium,\r\n }}\r\n >\r\n Find\r\n </button>\r\n {searchResults.length > 0 && (\r\n <>\r\n <span\r\n style={{\r\n fontSize: tokens.typography.fontSize.sm,\r\n color: tokens.colors.text.muted,\r\n }}\r\n aria-live=\"polite\"\r\n >\r\n {currentSearchIdx + 1}/{searchResults.length}\r\n </span>\r\n <button\r\n onClick={prevSearchResult}\r\n aria-label=\"Previous match\"\r\n style={{\r\n background: \"none\",\r\n border: \"none\",\r\n color: tokens.colors.text.secondary,\r\n cursor: \"pointer\",\r\n fontSize: tokens.typography.fontSize.base,\r\n padding: tokens.spacing.xs,\r\n }}\r\n >\r\n ▲\r\n </button>\r\n <button\r\n onClick={nextSearchResult}\r\n aria-label=\"Next match\"\r\n style={{\r\n background: \"none\",\r\n border: \"none\",\r\n color: tokens.colors.text.secondary,\r\n cursor: \"pointer\",\r\n fontSize: tokens.typography.fontSize.base,\r\n padding: tokens.spacing.xs,\r\n }}\r\n >\r\n ▼\r\n </button>\r\n </>\r\n )}\r\n </div>\r\n )}\r\n </div>\r\n {/* end sticky header */}\r\n\r\n {/* Packet Header Summary (collapsible) */}\r\n {shouldShowHeaderSummary && packetHeaders && packetHeaders.length > 0 && (\r\n <div style={{ flexShrink: 0 }}>\r\n <div\r\n role=\"button\"\r\n tabIndex={0}\r\n aria-expanded={showHeaders}\r\n aria-label=\"Toggle packet headers\"\r\n onClick={() => setShowHeaders(!showHeaders)}\r\n onKeyDown={(e) => {\r\n if (e.key === \"Enter\" || e.key === \" \") {\r\n e.preventDefault();\r\n setShowHeaders(!showHeaders);\r\n }\r\n }}\r\n style={{\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n gap: tokens.spacing.sm,\r\n padding: `${tokens.spacing.sm} ${tokens.spacing.smd}`,\r\n background: tokens.colors.background.surface,\r\n cursor: \"pointer\",\r\n borderBottom: `1px solid ${tokens.colors.border.muted}`,\r\n userSelect: \"none\",\r\n }}\r\n >\r\n <span\r\n aria-hidden=\"true\"\r\n style={{\r\n fontSize: tokens.typography.fontSize.micro,\r\n color: tokens.colors.text.muted,\r\n transition: \"transform 150ms ease\",\r\n transform: showHeaders ? \"rotate(0deg)\" : \"rotate(-90deg)\",\r\n }}\r\n >\r\n {\"\\u25BC\"}\r\n </span>\r\n <span\r\n style={{\r\n fontSize: tokens.typography.fontSize.sm,\r\n color: tokens.colors.text.secondary,\r\n fontWeight: tokens.typography.fontWeight.medium,\r\n }}\r\n >\r\n Packet Headers\r\n </span>\r\n </div>\r\n {showHeaders && (\r\n <div\r\n style={{\r\n padding: `${tokens.spacing.sm} ${tokens.spacing.smd}`,\r\n borderBottom: `1px solid ${tokens.colors.border.muted}`,\r\n background: tokens.colors.background.base,\r\n }}\r\n >\r\n <div\r\n style={{\r\n display: \"grid\",\r\n gridTemplateColumns: \"auto 1fr\",\r\n gap: `${tokens.spacing.xxs} ${tokens.spacing.md}`,\r\n fontSize: tokens.typography.fontSize.sm,\r\n lineHeight: tokens.typography.lineHeight.tight,\r\n }}\r\n >\r\n {packetHeaders.map((entry, i) => (\r\n <React.Fragment key={i}>\r\n <span style={{ color: tokens.colors.text.muted }}>\r\n {entry.label}\r\n </span>\r\n <span\r\n style={{\r\n color: tokens.colors.text.primary,\r\n fontWeight: tokens.typography.fontWeight.medium,\r\n fontFamily: entry.mono\r\n ? tokens.typography.fontFamily.mono\r\n : \"inherit\",\r\n }}\r\n >\r\n {entry.value}\r\n </span>\r\n </React.Fragment>\r\n ))}\r\n </div>\r\n </div>\r\n )}\r\n </div>\r\n )}\r\n\r\n {/* Decoded Fields Table (collapsible) */}\r\n {shouldShowFieldsTable && decodedFields && decodedFields.length > 0 && (\r\n <div style={{ flexShrink: 0 }}>\r\n <div\r\n role=\"button\"\r\n tabIndex={0}\r\n aria-expanded={showFields}\r\n aria-label=\"Toggle decoded fields\"\r\n onClick={() => setShowFields(!showFields)}\r\n onKeyDown={(e) => {\r\n if (e.key === \"Enter\" || e.key === \" \") {\r\n e.preventDefault();\r\n setShowFields(!showFields);\r\n }\r\n }}\r\n style={{\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n gap: tokens.spacing.sm,\r\n padding: `${tokens.spacing.sm} ${tokens.spacing.smd}`,\r\n background: tokens.colors.background.surface,\r\n cursor: \"pointer\",\r\n borderBottom: `1px solid ${tokens.colors.border.muted}`,\r\n userSelect: \"none\",\r\n }}\r\n >\r\n <span\r\n aria-hidden=\"true\"\r\n style={{\r\n fontSize: tokens.typography.fontSize.micro,\r\n color: tokens.colors.text.muted,\r\n transition: \"transform 150ms ease\",\r\n transform: showFields ? \"rotate(0deg)\" : \"rotate(-90deg)\",\r\n }}\r\n >\r\n {\"\\u25BC\"}\r\n </span>\r\n <span\r\n style={{\r\n fontSize: tokens.typography.fontSize.sm,\r\n color: tokens.colors.text.secondary,\r\n fontWeight: tokens.typography.fontWeight.medium,\r\n }}\r\n >\r\n Decoded Fields ({decodedFields.length})\r\n </span>\r\n </div>\r\n {showFields && (\r\n <div\r\n role=\"table\"\r\n aria-label=\"Decoded packet fields\"\r\n style={{\r\n borderBottom: `1px solid ${tokens.colors.border.muted}`,\r\n maxHeight: decodedFieldsMaxHeight,\r\n overflowY: \"auto\",\r\n }}\r\n >\r\n {/* Fields table header */}\r\n <div\r\n role=\"row\"\r\n style={{\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n padding: `${tokens.spacing.xs} ${tokens.spacing.smd}`,\r\n borderBottom: `1px solid ${tokens.colors.border.muted}`,\r\n gap: tokens.spacing.sm,\r\n position: \"sticky\",\r\n top: 0,\r\n background: tokens.colors.background.surface,\r\n zIndex: 1,\r\n }}\r\n >\r\n <span\r\n role=\"columnheader\"\r\n style={{\r\n flex: 2,\r\n fontSize: tokens.typography.fontSize.xxs,\r\n color: tokens.colors.text.muted,\r\n textTransform: \"uppercase\",\r\n letterSpacing: \"0.5px\",\r\n }}\r\n >\r\n Field\r\n </span>\r\n <span\r\n role=\"columnheader\"\r\n style={{\r\n flex: 2,\r\n fontSize: tokens.typography.fontSize.xxs,\r\n color: tokens.colors.text.muted,\r\n textTransform: \"uppercase\",\r\n letterSpacing: \"0.5px\",\r\n textAlign: \"right\",\r\n }}\r\n >\r\n Value\r\n </span>\r\n <span\r\n role=\"columnheader\"\r\n style={{\r\n flex: 1,\r\n fontSize: tokens.typography.fontSize.xxs,\r\n color: tokens.colors.text.muted,\r\n textTransform: \"uppercase\",\r\n letterSpacing: \"0.5px\",\r\n textAlign: \"right\",\r\n }}\r\n >\r\n Unit\r\n </span>\r\n <span\r\n role=\"columnheader\"\r\n style={{\r\n flex: 1,\r\n fontSize: tokens.typography.fontSize.xxs,\r\n color: tokens.colors.text.muted,\r\n textTransform: \"uppercase\",\r\n letterSpacing: \"0.5px\",\r\n textAlign: \"right\",\r\n }}\r\n >\r\n Offset\r\n </span>\r\n <span\r\n role=\"columnheader\"\r\n style={{\r\n flex: 1,\r\n fontSize: tokens.typography.fontSize.xxs,\r\n color: tokens.colors.text.muted,\r\n textTransform: \"uppercase\",\r\n letterSpacing: \"0.5px\",\r\n textAlign: \"right\",\r\n }}\r\n >\r\n Size\r\n </span>\r\n </div>\r\n {/* Field rows */}\r\n {decodedFields.map((field, fi) => {\r\n const isHovered = hoveredFieldIdx === fi;\r\n return (\r\n <div\r\n key={fi}\r\n onMouseEnter={() => handleFieldHover(fi)}\r\n onMouseLeave={handleFieldLeave}\r\n title={field.description}\r\n style={{\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n padding: `3px ${tokens.spacing.smd}`,\r\n gap: tokens.spacing.sm,\r\n background: isHovered\r\n ? `${tokens.colors.status.caution}14`\r\n : fi % 2 === 0\r\n ? tokens.colors.background.base\r\n : \"transparent\",\r\n transition: \"background 0.15s ease\",\r\n cursor: \"default\",\r\n minHeight: tokens.elementSize.sm,\r\n }}\r\n >\r\n <span\r\n role=\"cell\"\r\n style={{\r\n flex: 2,\r\n fontSize: tokens.typography.fontSize.sm,\r\n color: tokens.colors.text.secondary,\r\n overflow: \"hidden\",\r\n textOverflow: \"ellipsis\",\r\n whiteSpace: \"nowrap\",\r\n }}\r\n >\r\n {field.name}\r\n </span>\r\n <span\r\n role=\"cell\"\r\n style={{\r\n flex: 2,\r\n fontSize: tokens.typography.fontSize.sm,\r\n color: tokens.colors.text.primary,\r\n fontWeight: tokens.typography.fontWeight.medium,\r\n fontFamily: tokens.typography.fontFamily.mono,\r\n textAlign: \"right\",\r\n }}\r\n >\r\n {field.value === null || field.value === undefined\r\n ? \"\\u2014\"\r\n : String(field.value)}\r\n </span>\r\n <span\r\n role=\"cell\"\r\n style={{\r\n flex: 1,\r\n fontSize: tokens.typography.fontSize.xxs,\r\n color: tokens.colors.text.muted,\r\n textAlign: \"right\",\r\n }}\r\n >\r\n {field.unit || \"\"}\r\n </span>\r\n <span\r\n role=\"cell\"\r\n style={{\r\n flex: 1,\r\n fontSize: tokens.typography.fontSize.xxs,\r\n color: tokens.colors.text.muted,\r\n textAlign: \"right\",\r\n fontFamily: tokens.typography.fontFamily.mono,\r\n }}\r\n >\r\n {field.byteOffset !== undefined ? field.byteOffset : \"\"}\r\n </span>\r\n <span\r\n role=\"cell\"\r\n style={{\r\n flex: 1,\r\n fontSize: tokens.typography.fontSize.xxs,\r\n color: tokens.colors.text.muted,\r\n textAlign: \"right\",\r\n fontFamily: tokens.typography.fontFamily.mono,\r\n }}\r\n >\r\n {field.sizeBits ? `${field.sizeBits}b` : \"\"}\r\n </span>\r\n </div>\r\n );\r\n })}\r\n </div>\r\n )}\r\n </div>\r\n )}\r\n\r\n {/* Region legend */}\r\n {regions.length > 0 && (\r\n <div\r\n role=\"group\"\r\n aria-label=\"Packet structure regions\"\r\n style={{\r\n padding: `${tokens.spacing.xs} ${tokens.spacing.smd}`,\r\n borderBottom: `1px solid ${tokens.colors.border.muted}`,\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n gap: tokens.spacing.smd,\r\n flexWrap: \"wrap\",\r\n fontSize: tokens.typography.fontSize.xxs,\r\n background: tokens.colors.background.surface,\r\n }}\r\n >\r\n {regions.map((r, i) => (\r\n <span\r\n key={i}\r\n style={{\r\n display: \"inline-flex\",\r\n alignItems: \"center\",\r\n gap: tokens.spacing.xs,\r\n }}\r\n >\r\n <span\r\n aria-hidden=\"true\"\r\n style={{\r\n display: \"inline-block\",\r\n width: 10,\r\n height: 10,\r\n borderRadius: tokens.borderRadius.sm,\r\n background: r.color,\r\n border: `1px solid ${r.borderColor || \"transparent\"}`,\r\n }}\r\n />\r\n <span style={{ color: tokens.colors.text.muted }}>\r\n {r.label} ({r.end - r.start}B)\r\n </span>\r\n </span>\r\n ))}\r\n </div>\r\n )}\r\n\r\n {/* Column headers */}\r\n <div\r\n role=\"row\"\r\n aria-label=\"Column headers\"\r\n style={{\r\n padding: `${tokens.spacing.xs} ${tokens.spacing.smd}`,\r\n borderBottom: `1px solid ${tokens.colors.border.muted}`,\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n fontSize: tokens.typography.fontSize.xxs,\r\n color: tokens.colors.text.muted,\r\n textTransform: \"uppercase\",\r\n letterSpacing: \"0.5px\",\r\n fontWeight: tokens.typography.fontWeight.medium,\r\n background: tokens.colors.background.surface,\r\n }}\r\n >\r\n {showOffset && (\r\n <span\r\n style={{\r\n marginRight: tokens.spacing.smd,\r\n minWidth: 60,\r\n textAlign: \"right\",\r\n }}\r\n >\r\n Offset\r\n </span>\r\n )}\r\n <span style={{ flex: 1 }}>Hex</span>\r\n {localShowAscii && (\r\n <>\r\n <span\r\n aria-hidden=\"true\"\r\n style={{\r\n color: `${tokens.colors.border.muted}`,\r\n marginRight: tokens.spacing.sm,\r\n }}\r\n >\r\n |\r\n </span>\r\n <span>ASCII</span>\r\n </>\r\n )}\r\n </div>\r\n\r\n {/* Hex content */}\r\n <div\r\n ref={scrollRef}\r\n role=\"grid\"\r\n aria-label=\"Hex data\"\r\n tabIndex={0}\r\n onScroll={handleHexScroll}\r\n style={{\r\n flex: \"1 1 160px\",\r\n minHeight: 160,\r\n overflow: \"auto\",\r\n padding: `${tokens.spacing.xs} ${tokens.spacing.smd}`,\r\n }}\r\n >\r\n <div\r\n style={{ height: totalRows * HEX_ROW_HEIGHT, position: \"relative\" }}\r\n >\r\n <div\r\n style={{\r\n position: \"absolute\",\r\n top: startRow * HEX_ROW_HEIGHT,\r\n left: 0,\r\n right: 0,\r\n }}\r\n >\r\n {virtualRows.map((row) => (\r\n <HexRow\r\n key={row.offset}\r\n offset={row.offset}\r\n bytes={row.bytes}\r\n bytesPerRow={localBytesPerRow}\r\n byteGrouping={localGrouping}\r\n offsetBase={localOffsetBase}\r\n totalBytes={data.length}\r\n showAscii={localShowAscii}\r\n showOffset={showOffset}\r\n highlights={allHighlights}\r\n regions={sortedRegions}\r\n effectiveHighlight={effectiveHighlight}\r\n selectedStart={selectStart}\r\n selectedEnd={selectEnd}\r\n cursorOffset={cursorOffset}\r\n onByteMouseDown={handleByteMouseDown}\r\n onByteMouseEnter={handleByteMouseEnter}\r\n onByteClick={handleByteClickCb}\r\n handleByteHoverInternal={handleByteHoverInternal}\r\n handleByteLeaveInternal={handleByteLeaveInternal}\r\n tokens={tokens}\r\n />\r\n ))}\r\n </div>\r\n </div>\r\n </div>\r\n\r\n {/* Highlight legend */}\r\n {highlights.length > 0 && (\r\n <div\r\n role=\"group\"\r\n aria-label=\"Search highlights\"\r\n style={{\r\n padding: `${tokens.spacing.xs} ${tokens.spacing.smd}`,\r\n borderTop: `1px solid ${tokens.colors.border.muted}`,\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n gap: tokens.spacing.smd,\r\n flexWrap: \"wrap\",\r\n fontSize: tokens.typography.fontSize.xxs,\r\n background: tokens.colors.background.surface,\r\n }}\r\n >\r\n {highlights\r\n .filter((h) => h.label)\r\n .map((h, i) => (\r\n <span\r\n key={i}\r\n style={{\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n gap: tokens.spacing.xs,\r\n }}\r\n >\r\n <span\r\n aria-hidden=\"true\"\r\n style={{\r\n width: 10,\r\n height: 10,\r\n borderRadius: tokens.borderRadius.sm,\r\n background: h.color,\r\n display: \"inline-block\",\r\n }}\r\n />\r\n <span style={{ color: tokens.colors.text.muted }}>\r\n {h.label} (0x{h.start.toString(16)}-0x{h.end.toString(16)})\r\n </span>\r\n </span>\r\n ))}\r\n </div>\r\n )}\r\n\r\n {/* Status bar */}\r\n {showStatusBar && (\r\n <div\r\n role=\"status\"\r\n aria-live=\"polite\"\r\n style={{\r\n padding: `${tokens.spacing.sm} ${tokens.spacing.smd}`,\r\n borderTop: `1px solid ${tokens.colors.border.muted}`,\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n gap: tokens.spacing.md,\r\n fontSize: tokens.typography.fontSize.sm,\r\n background: tokens.colors.background.surface,\r\n color: tokens.colors.text.muted,\r\n flexWrap: \"wrap\",\r\n }}\r\n >\r\n <span>Size: {data.length.toLocaleString()} bytes</span>\r\n {cursorOffset >= 0 && cursorOffset < data.length && (\r\n <>\r\n <span>\r\n Offset: 0x{cursorOffset.toString(16).toUpperCase()} (\r\n {cursorOffset})\r\n </span>\r\n <span>\r\n Value: 0x{byteToHex(data[cursorOffset])} ({data[cursorOffset]})\r\n </span>\r\n </>\r\n )}\r\n {selectStart >= 0 && selectEnd >= 0 && selectStart !== selectEnd && (\r\n <span>\r\n Selection: {Math.abs(selectEnd - selectStart) + 1} bytes (0x\r\n {Math.min(selectStart, selectEnd).toString(16).toUpperCase()}-0x\r\n {Math.max(selectStart, selectEnd).toString(16).toUpperCase()})\r\n </span>\r\n )}\r\n {interpretation && (\r\n <span\r\n style={{\r\n display: \"flex\",\r\n gap: tokens.spacing.sm,\r\n color: tokens.colors.text.secondary,\r\n }}\r\n >\r\n <span>U8: {interpretation.u8}</span>\r\n <span>I8: {interpretation.i8}</span>\r\n {interpretation.u16 !== undefined && (\r\n <span>U16: {interpretation.u16}</span>\r\n )}\r\n {interpretation.u32 !== undefined && (\r\n <span>U32: {interpretation.u32}</span>\r\n )}\r\n {interpretation.f32 !== undefined && (\r\n <span>F32: {interpretation.f32.toFixed(6)}</span>\r\n )}\r\n </span>\r\n )}\r\n </div>\r\n )}\r\n </div>\r\n );\r\n}\r\n\r\nexport default HexViewer;\r\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ImageFrame.js","names":[],"sources":["../../../../src/react/core/widgets/ImageFrame.tsx"],"sourcesContent":["/**\n * @zendir/ui - ImageFrame Component\n *\n * Renders a single-channel data frame (e.g. a CCD/sensor readout) as a\n * false-colour image on a <canvas>. Purely presentational: hand it an\n * array of samples and it paints them — it has no knowledge of where\n * the data came from (engine, file, websocket, console). This is the\n * reusable surface that callers \"dump\" a frame into.\n *\n * Pipeline per sample: normalise against a fixed ceiling (`maxADU`) to\n * an intensity in [0,1], run that through a colormap, pack RGBA. The\n * default `cividis` ramp (dark teal → yellow) reads well on dark UI.\n *\n * The canvas's native resolution is the frame's width×height, so each\n * sample is exactly one pixel; CSS scales it up with\n * `image-rendering: pixelated`, keeping small sensor frames crisp.\n *\n * Astro UX Compliance:\n * - Themed surface, border radius, and typography via `useTheme`.\n * - Loading and empty states with friendly messaging.\n * - Monospace raw-value grid for analysis.\n *\n * @example\n * ```tsx\n * <ImageFrame\n * data={ccdSamples} // number[] length = width*height\n * width={28}\n * maxADU={65535}\n * title=\"CCD capture\"\n * showValues\n * />\n * ```\n */\n\nimport React, { memo, useEffect, useMemo, useRef } from \"react\";\nimport { useTheme } from \"../../theme\";\nimport { classNames, lerp } from \"../../utils\";\n\n// =============================================================================\n// Types\n// =============================================================================\n\n/** Built-in colormap names. */\nexport type ImageColormap = \"cividis\" | \"gray\";\n\nexport interface ImageFrameProps {\n /** Flat, row-major single-channel samples. Length must equal width*height. */\n data?: number[];\n /**\n * Pre-decoded RGBA pixels (length = width*height*4). When provided this\n * is painted directly and `data` / `maxADU` / `colormap` are ignored.\n * Escape hatch for callers that already have an image buffer.\n */\n pixels?: Uint8ClampedArray;\n /** Frame width in pixels. */\n width: number;\n /** Frame height in pixels. Defaults to `width` (square). */\n height?: number;\n /** Saturation ceiling used to normalise `data` → [0,1]. Default 65535. */\n maxADU?: number;\n /** Colormap applied to normalised intensities. Default 'cividis'. */\n colormap?: ImageColormap;\n /** Show the raw sample values as a grid below the image. Default false. */\n showValues?: boolean;\n /** Optional title shown above the image. */\n title?: string;\n /** Loading state (skeleton). */\n loading?: boolean;\n /** Message shown when there is no frame to display. */\n emptyMessage?: string;\n /** Custom class name. */\n className?: string;\n}\n\n// =============================================================================\n// Pure helpers (exported for testing / reuse)\n// =============================================================================\n\nconst DEFAULT_MAX_ADU = 65535;\n\n/** Two-stop colormaps: intensity t in [0,1] → [r,g,b] (0..255). */\nconst COLORMAPS: Record<\n ImageColormap,\n (t: number) => [number, number, number]\n> = {\n // Dark teal → yellow; a cividis-style ramp that reads well on dark UI.\n cividis: (t) => [lerp(42, 237, t), lerp(123, 222, t), lerp(155, 83, t)],\n gray: (t) => {\n const v = lerp(0, 255, t);\n return [v, v, v];\n },\n};\n\n/**\n * decodeImage — pure transform: single-channel samples → canvas-ready\n * RGBA. Fixed-ceiling normalisation (`sample / maxADU`) so the output\n * reflects the real signal level relative to saturation rather than\n * rescaling per frame. Returns null on invalid input.\n */\nexport function decodeImage(opts: {\n data: number[];\n width: number;\n height?: number;\n maxADU?: number;\n colormap?: ImageColormap;\n}): { width: number; height: number; pixels: Uint8ClampedArray } | null {\n const {\n data,\n width,\n height,\n maxADU = DEFAULT_MAX_ADU,\n colormap = \"cividis\",\n } = opts;\n if (!Array.isArray(data)) return null;\n if (!Number.isFinite(width) || width <= 0) return null;\n const W = Math.floor(width);\n const H =\n Number.isFinite(height) && (height as number) > 0\n ? Math.floor(height as number)\n : W;\n if (data.length !== W * H) return null;\n\n const ramp = COLORMAPS[colormap] || COLORMAPS.cividis;\n const ceiling =\n Number.isFinite(maxADU) && maxADU > 0 ? maxADU : DEFAULT_MAX_ADU;\n\n const pixels = new Uint8ClampedArray(W * H * 4);\n for (let i = 0; i < W * H; i++) {\n const raw = data[i];\n // sample / ceiling (lerp clamps t to [0,1]); non-finite reads as 0.\n const t = Number.isFinite(raw) ? raw / ceiling : 0;\n const [r, g, b] = ramp(t);\n const off = i * 4;\n pixels[off + 0] = r;\n pixels[off + 1] = g;\n pixels[off + 2] = b;\n pixels[off + 3] = 255;\n }\n return { width: W, height: H, pixels };\n}\n\n/** Pretty-print samples as a width-padded grid for the values view. */\nfunction formatValues(\n data: number[],\n width: number,\n height: number,\n maxADU: number,\n): string {\n if (width * height !== data.length) return data.join(\", \");\n const colW = Math.max(3, String(Math.floor(maxADU) || 1).length);\n const rows: string[] = [];\n for (let y = 0; y < height; y++) {\n const cells: string[] = [];\n for (let x = 0; x < width; x++) {\n cells.push(String(data[y * width + x]).padStart(colW, \" \"));\n }\n rows.push(cells.join(\" \"));\n }\n return rows.join(\"\\n\");\n}\n\n// =============================================================================\n// Component\n// =============================================================================\n\nexport const ImageFrame = memo(function ImageFrame({\n data,\n pixels,\n width,\n height,\n maxADU = DEFAULT_MAX_ADU,\n colormap = \"cividis\",\n showValues = false,\n title,\n loading = false,\n emptyMessage = \"No frame to display.\",\n className = \"\",\n}: ImageFrameProps): React.ReactElement {\n const { tokens } = useTheme();\n const canvasRef = useRef<HTMLCanvasElement>(null);\n\n const H =\n Number.isFinite(height) && (height as number) > 0\n ? Math.floor(height as number)\n : Math.floor(width);\n const W = Math.floor(width);\n\n // Decode once per frame/option change. A pre-decoded `pixels` buffer\n // wins; otherwise decode `data` through the colormap.\n const decoded = useMemo(() => {\n if (pixels instanceof Uint8ClampedArray && pixels.length === W * H * 4) {\n return { width: W, height: H, pixels };\n }\n if (Array.isArray(data)) {\n return decodeImage({ data, width: W, height: H, maxADU, colormap });\n }\n return null;\n }, [data, pixels, W, H, maxADU, colormap]);\n\n useEffect(() => {\n if (!decoded) return;\n const canvas = canvasRef.current;\n if (!canvas) return;\n canvas.width = decoded.width;\n canvas.height = decoded.height;\n const ctx = canvas.getContext(\"2d\");\n if (!ctx) return;\n // Build via createImageData + set (rather than `new ImageData(buf,…)`)\n // to sidestep the lib.dom ArrayBuffer/ArrayBufferLike typing mismatch\n // on newer TypeScript, and it's the same cost.\n const img = ctx.createImageData(decoded.width, decoded.height);\n img.data.set(decoded.pixels);\n ctx.putImageData(img, 0, 0);\n }, [decoded]);\n\n const surfaceStyle: React.CSSProperties = {\n backgroundColor: tokens.colors.background.surface,\n borderRadius: tokens.borderRadius.lg,\n padding: tokens.spacing.md,\n color: tokens.colors.text.primary,\n fontFamily: tokens.typography.fontFamily.primary,\n display: \"flex\",\n flexDirection: \"column\",\n gap: tokens.spacing.sm,\n };\n\n // Loading state\n if (loading) {\n return (\n <div\n className={classNames(\"zendir-image-frame\", \"loading\", className)}\n role=\"img\"\n aria-busy=\"true\"\n aria-label=\"Loading image\"\n style={surfaceStyle}\n >\n {title && <FrameTitle tokens={tokens} title={title} />}\n <div\n style={{\n width: \"100%\",\n aspectRatio: `${W} / ${H}`,\n background: \"rgba(255,255,255,0.06)\",\n borderRadius: tokens.borderRadius.sm,\n }}\n />\n </div>\n );\n }\n\n // Empty state\n if (!decoded) {\n return (\n <div\n className={classNames(\"zendir-image-frame\", \"empty\", className)}\n role=\"img\"\n aria-label=\"No image\"\n style={{\n ...surfaceStyle,\n textAlign: \"center\",\n color: tokens.colors.text.tertiary,\n }}\n >\n {title && <FrameTitle tokens={tokens} title={title} />}\n <p style={{ margin: 0, fontSize: tokens.typography.fontSize.xs }}>\n {emptyMessage}\n </p>\n </div>\n );\n }\n\n return (\n <div\n className={classNames(\"zendir-image-frame\", className)}\n aria-label={title || \"Image frame\"}\n style={surfaceStyle}\n >\n {title && <FrameTitle tokens={tokens} title={title} />}\n\n <canvas\n ref={canvasRef}\n data-testid=\"image-frame-canvas\"\n style={{\n width: \"100%\",\n aspectRatio: `${decoded.width} / ${decoded.height}`,\n imageRendering: \"pixelated\",\n display: \"block\",\n background: \"rgba(0,0,0,0.4)\",\n borderRadius: tokens.borderRadius.sm,\n }}\n />\n\n {showValues && Array.isArray(data) && (\n <pre\n data-testid=\"image-frame-values\"\n style={{\n margin: 0,\n padding: tokens.spacing.sm,\n background: \"rgba(0,0,0,0.45)\",\n borderRadius: tokens.borderRadius.sm,\n color: tokens.colors.text.primary,\n fontSize: 10,\n lineHeight: 1.25,\n fontFamily: tokens.typography.fontFamily.mono,\n whiteSpace: \"pre\",\n overflow: \"auto\",\n maxHeight: 220,\n }}\n >\n {formatValues(data, decoded.width, decoded.height, maxADU)}\n </pre>\n )}\n </div>\n );\n});\n\n/** Small header row shared across states. */\nconst FrameTitle = ({\n tokens,\n title,\n}: {\n tokens: ReturnType<typeof useTheme>[\"tokens\"];\n title: string;\n}) => (\n <h3\n style={{\n margin: 0,\n fontSize: tokens.typography.fontSize.md,\n fontWeight: tokens.typography.fontWeight.semibold,\n color: tokens.colors.text.primary,\n }}\n >\n {title}\n </h3>\n);\n\nexport default ImageFrame;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ImageFrame.js","names":[],"sources":["../../../../src/react/core/widgets/ImageFrame.tsx"],"sourcesContent":["/**\r\n * @zendir/ui - ImageFrame Component\r\n *\r\n * Renders a single-channel data frame (e.g. a CCD/sensor readout) as a\r\n * false-colour image on a <canvas>. Purely presentational: hand it an\r\n * array of samples and it paints them — it has no knowledge of where\r\n * the data came from (engine, file, websocket, console). This is the\r\n * reusable surface that callers \"dump\" a frame into.\r\n *\r\n * Pipeline per sample: normalise against a fixed ceiling (`maxADU`) to\r\n * an intensity in [0,1], run that through a colormap, pack RGBA. The\r\n * default `cividis` ramp (dark teal → yellow) reads well on dark UI.\r\n *\r\n * The canvas's native resolution is the frame's width×height, so each\r\n * sample is exactly one pixel; CSS scales it up with\r\n * `image-rendering: pixelated`, keeping small sensor frames crisp.\r\n *\r\n * Astro UX Compliance:\r\n * - Themed surface, border radius, and typography via `useTheme`.\r\n * - Loading and empty states with friendly messaging.\r\n * - Monospace raw-value grid for analysis.\r\n *\r\n * @example\r\n * ```tsx\r\n * <ImageFrame\r\n * data={ccdSamples} // number[] length = width*height\r\n * width={28}\r\n * maxADU={65535}\r\n * title=\"CCD capture\"\r\n * showValues\r\n * />\r\n * ```\r\n */\r\n\r\nimport React, { memo, useEffect, useMemo, useRef } from \"react\";\r\nimport { useTheme } from \"../../theme\";\r\nimport { classNames, lerp } from \"../../utils\";\r\n\r\n// =============================================================================\r\n// Types\r\n// =============================================================================\r\n\r\n/** Built-in colormap names. */\r\nexport type ImageColormap = \"cividis\" | \"gray\";\r\n\r\nexport interface ImageFrameProps {\r\n /** Flat, row-major single-channel samples. Length must equal width*height. */\r\n data?: number[];\r\n /**\r\n * Pre-decoded RGBA pixels (length = width*height*4). When provided this\r\n * is painted directly and `data` / `maxADU` / `colormap` are ignored.\r\n * Escape hatch for callers that already have an image buffer.\r\n */\r\n pixels?: Uint8ClampedArray;\r\n /** Frame width in pixels. */\r\n width: number;\r\n /** Frame height in pixels. Defaults to `width` (square). */\r\n height?: number;\r\n /** Saturation ceiling used to normalise `data` → [0,1]. Default 65535. */\r\n maxADU?: number;\r\n /** Colormap applied to normalised intensities. Default 'cividis'. */\r\n colormap?: ImageColormap;\r\n /** Show the raw sample values as a grid below the image. Default false. */\r\n showValues?: boolean;\r\n /** Optional title shown above the image. */\r\n title?: string;\r\n /** Loading state (skeleton). */\r\n loading?: boolean;\r\n /** Message shown when there is no frame to display. */\r\n emptyMessage?: string;\r\n /** Custom class name. */\r\n className?: string;\r\n}\r\n\r\n// =============================================================================\r\n// Pure helpers (exported for testing / reuse)\r\n// =============================================================================\r\n\r\nconst DEFAULT_MAX_ADU = 65535;\r\n\r\n/** Two-stop colormaps: intensity t in [0,1] → [r,g,b] (0..255). */\r\nconst COLORMAPS: Record<\r\n ImageColormap,\r\n (t: number) => [number, number, number]\r\n> = {\r\n // Dark teal → yellow; a cividis-style ramp that reads well on dark UI.\r\n cividis: (t) => [lerp(42, 237, t), lerp(123, 222, t), lerp(155, 83, t)],\r\n gray: (t) => {\r\n const v = lerp(0, 255, t);\r\n return [v, v, v];\r\n },\r\n};\r\n\r\n/**\r\n * decodeImage — pure transform: single-channel samples → canvas-ready\r\n * RGBA. Fixed-ceiling normalisation (`sample / maxADU`) so the output\r\n * reflects the real signal level relative to saturation rather than\r\n * rescaling per frame. Returns null on invalid input.\r\n */\r\nexport function decodeImage(opts: {\r\n data: number[];\r\n width: number;\r\n height?: number;\r\n maxADU?: number;\r\n colormap?: ImageColormap;\r\n}): { width: number; height: number; pixels: Uint8ClampedArray } | null {\r\n const {\r\n data,\r\n width,\r\n height,\r\n maxADU = DEFAULT_MAX_ADU,\r\n colormap = \"cividis\",\r\n } = opts;\r\n if (!Array.isArray(data)) return null;\r\n if (!Number.isFinite(width) || width <= 0) return null;\r\n const W = Math.floor(width);\r\n const H =\r\n Number.isFinite(height) && (height as number) > 0\r\n ? Math.floor(height as number)\r\n : W;\r\n if (data.length !== W * H) return null;\r\n\r\n const ramp = COLORMAPS[colormap] || COLORMAPS.cividis;\r\n const ceiling =\r\n Number.isFinite(maxADU) && maxADU > 0 ? maxADU : DEFAULT_MAX_ADU;\r\n\r\n const pixels = new Uint8ClampedArray(W * H * 4);\r\n for (let i = 0; i < W * H; i++) {\r\n const raw = data[i];\r\n // sample / ceiling (lerp clamps t to [0,1]); non-finite reads as 0.\r\n const t = Number.isFinite(raw) ? raw / ceiling : 0;\r\n const [r, g, b] = ramp(t);\r\n const off = i * 4;\r\n pixels[off + 0] = r;\r\n pixels[off + 1] = g;\r\n pixels[off + 2] = b;\r\n pixels[off + 3] = 255;\r\n }\r\n return { width: W, height: H, pixels };\r\n}\r\n\r\n/** Pretty-print samples as a width-padded grid for the values view. */\r\nfunction formatValues(\r\n data: number[],\r\n width: number,\r\n height: number,\r\n maxADU: number,\r\n): string {\r\n if (width * height !== data.length) return data.join(\", \");\r\n const colW = Math.max(3, String(Math.floor(maxADU) || 1).length);\r\n const rows: string[] = [];\r\n for (let y = 0; y < height; y++) {\r\n const cells: string[] = [];\r\n for (let x = 0; x < width; x++) {\r\n cells.push(String(data[y * width + x]).padStart(colW, \" \"));\r\n }\r\n rows.push(cells.join(\" \"));\r\n }\r\n return rows.join(\"\\n\");\r\n}\r\n\r\n// =============================================================================\r\n// Component\r\n// =============================================================================\r\n\r\nexport const ImageFrame = memo(function ImageFrame({\r\n data,\r\n pixels,\r\n width,\r\n height,\r\n maxADU = DEFAULT_MAX_ADU,\r\n colormap = \"cividis\",\r\n showValues = false,\r\n title,\r\n loading = false,\r\n emptyMessage = \"No frame to display.\",\r\n className = \"\",\r\n}: ImageFrameProps): React.ReactElement {\r\n const { tokens } = useTheme();\r\n const canvasRef = useRef<HTMLCanvasElement>(null);\r\n\r\n const H =\r\n Number.isFinite(height) && (height as number) > 0\r\n ? Math.floor(height as number)\r\n : Math.floor(width);\r\n const W = Math.floor(width);\r\n\r\n // Decode once per frame/option change. A pre-decoded `pixels` buffer\r\n // wins; otherwise decode `data` through the colormap.\r\n const decoded = useMemo(() => {\r\n if (pixels instanceof Uint8ClampedArray && pixels.length === W * H * 4) {\r\n return { width: W, height: H, pixels };\r\n }\r\n if (Array.isArray(data)) {\r\n return decodeImage({ data, width: W, height: H, maxADU, colormap });\r\n }\r\n return null;\r\n }, [data, pixels, W, H, maxADU, colormap]);\r\n\r\n useEffect(() => {\r\n if (!decoded) return;\r\n const canvas = canvasRef.current;\r\n if (!canvas) return;\r\n canvas.width = decoded.width;\r\n canvas.height = decoded.height;\r\n const ctx = canvas.getContext(\"2d\");\r\n if (!ctx) return;\r\n // Build via createImageData + set (rather than `new ImageData(buf,…)`)\r\n // to sidestep the lib.dom ArrayBuffer/ArrayBufferLike typing mismatch\r\n // on newer TypeScript, and it's the same cost.\r\n const img = ctx.createImageData(decoded.width, decoded.height);\r\n img.data.set(decoded.pixels);\r\n ctx.putImageData(img, 0, 0);\r\n }, [decoded]);\r\n\r\n const surfaceStyle: React.CSSProperties = {\r\n backgroundColor: tokens.colors.background.surface,\r\n borderRadius: tokens.borderRadius.lg,\r\n padding: tokens.spacing.md,\r\n color: tokens.colors.text.primary,\r\n fontFamily: tokens.typography.fontFamily.primary,\r\n display: \"flex\",\r\n flexDirection: \"column\",\r\n gap: tokens.spacing.sm,\r\n };\r\n\r\n // Loading state\r\n if (loading) {\r\n return (\r\n <div\r\n className={classNames(\"zendir-image-frame\", \"loading\", className)}\r\n role=\"img\"\r\n aria-busy=\"true\"\r\n aria-label=\"Loading image\"\r\n style={surfaceStyle}\r\n >\r\n {title && <FrameTitle tokens={tokens} title={title} />}\r\n <div\r\n style={{\r\n width: \"100%\",\r\n aspectRatio: `${W} / ${H}`,\r\n background: \"rgba(255,255,255,0.06)\",\r\n borderRadius: tokens.borderRadius.sm,\r\n }}\r\n />\r\n </div>\r\n );\r\n }\r\n\r\n // Empty state\r\n if (!decoded) {\r\n return (\r\n <div\r\n className={classNames(\"zendir-image-frame\", \"empty\", className)}\r\n role=\"img\"\r\n aria-label=\"No image\"\r\n style={{\r\n ...surfaceStyle,\r\n textAlign: \"center\",\r\n color: tokens.colors.text.tertiary,\r\n }}\r\n >\r\n {title && <FrameTitle tokens={tokens} title={title} />}\r\n <p style={{ margin: 0, fontSize: tokens.typography.fontSize.xs }}>\r\n {emptyMessage}\r\n </p>\r\n </div>\r\n );\r\n }\r\n\r\n return (\r\n <div\r\n className={classNames(\"zendir-image-frame\", className)}\r\n aria-label={title || \"Image frame\"}\r\n style={surfaceStyle}\r\n >\r\n {title && <FrameTitle tokens={tokens} title={title} />}\r\n\r\n <canvas\r\n ref={canvasRef}\r\n data-testid=\"image-frame-canvas\"\r\n style={{\r\n width: \"100%\",\r\n aspectRatio: `${decoded.width} / ${decoded.height}`,\r\n imageRendering: \"pixelated\",\r\n display: \"block\",\r\n background: \"rgba(0,0,0,0.4)\",\r\n borderRadius: tokens.borderRadius.sm,\r\n }}\r\n />\r\n\r\n {showValues && Array.isArray(data) && (\r\n <pre\r\n data-testid=\"image-frame-values\"\r\n style={{\r\n margin: 0,\r\n padding: tokens.spacing.sm,\r\n background: \"rgba(0,0,0,0.45)\",\r\n borderRadius: tokens.borderRadius.sm,\r\n color: tokens.colors.text.primary,\r\n fontSize: 10,\r\n lineHeight: 1.25,\r\n fontFamily: tokens.typography.fontFamily.mono,\r\n whiteSpace: \"pre\",\r\n overflow: \"auto\",\r\n maxHeight: 220,\r\n }}\r\n >\r\n {formatValues(data, decoded.width, decoded.height, maxADU)}\r\n </pre>\r\n )}\r\n </div>\r\n );\r\n});\r\n\r\n/** Small header row shared across states. */\r\nconst FrameTitle = ({\r\n tokens,\r\n title,\r\n}: {\r\n tokens: ReturnType<typeof useTheme>[\"tokens\"];\r\n title: string;\r\n}) => (\r\n <h3\r\n style={{\r\n margin: 0,\r\n fontSize: tokens.typography.fontSize.md,\r\n fontWeight: tokens.typography.fontWeight.semibold,\r\n color: tokens.colors.text.primary,\r\n }}\r\n >\r\n {title}\r\n </h3>\r\n);\r\n\r\nexport default ImageFrame;\r\n"],"mappings":"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"}
|