@zendir/ui 0.6.1 → 0.6.3

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 CHANGED
@@ -1,5 +1,25 @@
1
1
  # @zendir/ui
2
2
 
3
+ ## 0.6.3
4
+
5
+ ### Patch Changes
6
+
7
+ - **HexViewer**: partial final rows keep full column alignment — the
8
+ two-space pad cells no longer collapse, so the ASCII column lines up on
9
+ the last line of any byte length.
10
+ - **Checkbox**: unchecked boxes use `border.default` instead of
11
+ `border.muted`; themes may define the muted hairline equal to an elevated
12
+ surface colour (nebula does), which made unchecked boxes invisible inside
13
+ panels and dropdowns.
14
+
15
+ ## 0.6.2
16
+
17
+ ### Patch Changes
18
+
19
+ - **GroundTrackMap**: vector layers (tracks, footprints, terminator) render
20
+ to canvas instead of SVG, removing the DOM churn that destabilised Safari
21
+ on the map view.
22
+
3
23
  ## 0.6.1
4
24
 
5
25
  ### Patch Changes
@@ -252,6 +252,7 @@ function GroundTrackMapLeaflet({ allSatellites, groundStations, showTerminator =
252
252
  keyboard: true,
253
253
  dragging: true,
254
254
  attributionControl: true,
255
+ preferCanvas: true,
255
256
  maxBounds: [[-90, -540], [90, 540]],
256
257
  maxBoundsViscosity: 1
257
258
  });
@@ -1 +1 @@
1
- {"version":3,"file":"GroundTrackMapLeaflet.js","names":[],"sources":["../../../src/react/charts/GroundTrackMapLeaflet.tsx"],"sourcesContent":["/**\r\n * @zendir/ui - GroundTrackMap Leaflet Provider\r\n *\r\n * Leaflet-based 2D world map for ground tracks, stations, and terminator.\r\n * Uses Zendir hybrid theme; all interactions enabled (zoom, scroll, drag, etc.).\r\n * Import leaflet-zendir.css when using this component.\r\n */\r\n\r\nimport React, {\r\n useRef,\r\n useEffect,\r\n useCallback,\r\n useMemo,\r\n useState,\r\n} from \"react\";\r\nimport { useTheme } from \"../theme\";\r\nimport type { GroundStation } from \"../types\";\r\nimport type {\r\n SatelliteTrack,\r\n GroundStationEnhanced,\r\n SROGroundStation,\r\n MapPin,\r\n LightSource,\r\n} from \"./GroundTrackMap\";\r\nimport type { MapLayerDef } from \"./GroundTrackMap\";\r\n\r\n// Optional Leaflet — consumer must install leaflet when using mapProvider=\"leaflet\"\r\nimport L from \"leaflet\";\r\nimport \"leaflet/dist/leaflet.css\";\r\nimport \"./leaflet-zendir.css\";\r\nimport {\r\n splitPolylineAtAntimeridian,\r\n segmentsWithWorldCopies,\r\n geodesicCirclePointsContinuous,\r\n} from \"./groundTrackMapLeafletUtils\";\r\nimport {\r\n DEFAULT_TILE,\r\n FALLBACK_TILE,\r\n TILE_ERROR_THRESHOLD,\r\n OSM_ATTRIBUTION,\r\n TILE_PRESETS,\r\n getBasemapAttribution,\r\n} from \"./groundTrackMapLeafletTiles\";\r\n\r\nconst STATUS_COLOR_MAP: Record<string, string> = {\r\n normal: \"#56f000\",\r\n standby: \"#2dccff\",\r\n caution: \"#fce83a\",\r\n serious: \"#ffb302\",\r\n critical: \"#ff3838\",\r\n off: \"#a4abb6\",\r\n};\r\n\r\nconst DEFAULT_TRACK_COLORS = [\r\n \"#2dccff\",\r\n \"#3E3CFF\",\r\n \"#9D70FF\",\r\n \"#56f000\",\r\n \"#fce83a\",\r\n \"#ff7849\",\r\n \"#2dd4bf\",\r\n \"#ff3838\",\r\n];\r\n\r\n// =============================================================================\r\n// Icon helpers — produce L.DivIcon with inline SVG (no external assets needed)\r\n// =============================================================================\r\n\r\n/** Satellite spacecraft icon with solar panels, matching Astro UXDS style. */\r\nfunction createSatDivIcon(statusColor: string, trackColor: string): L.DivIcon {\r\n // Status badge shape (Astro UXDS spec)\r\n const badge =\r\n statusColor === \"#56f000\"\r\n ? `<circle cx=\"7\" cy=\"7\" r=\"3\" fill=\"${statusColor}\"/>` // normal: filled dot\r\n : statusColor === \"#2dccff\"\r\n ? `<circle cx=\"7\" cy=\"7\" r=\"2.5\" fill=\"none\" stroke=\"${statusColor}\" stroke-width=\"1.5\"/>` // standby: ring\r\n : statusColor === \"#fce83a\"\r\n ? `<rect x=\"4.5\" y=\"4.5\" width=\"5\" height=\"5\" fill=\"${statusColor}\"/>` // caution: square\r\n : statusColor === \"#ffb302\"\r\n ? `<polygon points=\"7,3.5 10.5,7 7,10.5 3.5,7\" fill=\"${statusColor}\"/>` // serious: diamond (Astro UXDS)\r\n : statusColor === \"#ff3838\"\r\n ? `<polygon points=\"7,10 4,4 10,4\" fill=\"${statusColor}\"/>` // critical: triangle down\r\n : `<circle cx=\"7\" cy=\"7\" r=\"2\" fill=\"${statusColor}\"/>`; // off: small dot\r\n\r\n const svg = `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"34\" height=\"34\" viewBox=\"0 0 34 34\">\r\n <!-- Glow ring -->\r\n <circle cx=\"17\" cy=\"17\" r=\"15\" fill=\"${statusColor}\" fill-opacity=\"0.12\"/>\r\n <!-- Main body circle -->\r\n <circle cx=\"17\" cy=\"17\" r=\"11\" fill=\"#0d1323\" stroke=\"${trackColor}\" stroke-width=\"1.5\"/>\r\n <!-- Left solar panel -->\r\n <rect x=\"2\" y=\"15\" width=\"9\" height=\"4\" rx=\"0.5\" fill=\"${trackColor}\" fill-opacity=\"0.75\"/>\r\n <line x1=\"5.5\" y1=\"15\" x2=\"5.5\" y2=\"19\" stroke=\"${trackColor}\" stroke-width=\"0.5\" stroke-opacity=\"0.45\"/>\r\n <!-- Connector left -->\r\n <line x1=\"11\" y1=\"17\" x2=\"13\" y2=\"17\" stroke=\"${trackColor}\" stroke-width=\"1.2\"/>\r\n <!-- Satellite bus body -->\r\n <rect x=\"13\" y=\"14.5\" width=\"8\" height=\"5\" rx=\"1\" fill=\"${statusColor}\"/>\r\n <!-- Connector right -->\r\n <line x1=\"21\" y1=\"17\" x2=\"23\" y2=\"17\" stroke=\"${trackColor}\" stroke-width=\"1.2\"/>\r\n <!-- Right solar panel -->\r\n <rect x=\"23\" y=\"15\" width=\"9\" height=\"4\" rx=\"0.5\" fill=\"${trackColor}\" fill-opacity=\"0.75\"/>\r\n <line x1=\"26.5\" y1=\"15\" x2=\"26.5\" y2=\"19\" stroke=\"${trackColor}\" stroke-width=\"0.5\" stroke-opacity=\"0.45\"/>\r\n <!-- Status badge (top-left) -->\r\n <circle cx=\"7\" cy=\"7\" r=\"5.5\" fill=\"#0d1323\"/>\r\n ${badge}\r\n </svg>`;\r\n\r\n return L.divIcon({\r\n html: svg,\r\n className: \"zendir-sat-icon\",\r\n iconSize: [34, 34] as unknown as L.PointExpression,\r\n iconAnchor: [17, 17] as unknown as L.PointExpression,\r\n tooltipAnchor: [0, -18] as unknown as L.PointExpression,\r\n });\r\n}\r\n\r\n/** Ground station icon — dish/antenna shape, matching Astro UXDS style. */\r\nfunction createStationDivIcon(\r\n statusColor: string,\r\n type: string,\r\n status: string,\r\n): L.DivIcon {\r\n // Status badge\r\n const badge =\r\n status === \"normal\"\r\n ? `<circle cx=\"5\" cy=\"5\" r=\"2.5\" fill=\"${statusColor}\"/>`\r\n : status === \"standby\"\r\n ? `<circle cx=\"5\" cy=\"5\" r=\"2\" fill=\"none\" stroke=\"${statusColor}\" stroke-width=\"1.2\"/>`\r\n : status === \"caution\"\r\n ? `<rect x=\"3\" y=\"3\" width=\"4\" height=\"4\" fill=\"${statusColor}\"/>`\r\n : status === \"serious\"\r\n ? `<polygon points=\"5,2.5 7.5,5 5,7.5 2.5,5\" fill=\"${statusColor}\"/>`\r\n : status === \"critical\"\r\n ? `<polygon points=\"5,7.5 2.5,2.5 7.5,2.5\" fill=\"${statusColor}\"/>`\r\n : `<circle cx=\"5\" cy=\"5\" r=\"1.5\" fill=\"${statusColor}\"/>`;\r\n\r\n let iconInner = \"\";\r\n if (type === \"phased-array\") {\r\n // Grid pattern (phased array)\r\n iconInner = `\r\n <rect x=\"9\" y=\"7\" width=\"10\" height=\"8\" rx=\"0.5\" fill=\"none\" stroke=\"${statusColor}\" stroke-width=\"1.2\"/>\r\n <line x1=\"12\" y1=\"7\" x2=\"12\" y2=\"15\" stroke=\"${statusColor}\" stroke-width=\"0.7\"/>\r\n <line x1=\"15\" y1=\"7\" x2=\"15\" y2=\"15\" stroke=\"${statusColor}\" stroke-width=\"0.7\"/>\r\n <line x1=\"9\" y1=\"10\" x2=\"19\" y2=\"10\" stroke=\"${statusColor}\" stroke-width=\"0.7\"/>\r\n <line x1=\"9\" y1=\"13\" x2=\"19\" y2=\"13\" stroke=\"${statusColor}\" stroke-width=\"0.7\"/>\r\n <line x1=\"14\" y1=\"15\" x2=\"14\" y2=\"20\" stroke=\"${statusColor}\" stroke-width=\"1.2\"/>\r\n <line x1=\"11\" y1=\"20\" x2=\"17\" y2=\"20\" stroke=\"${statusColor}\" stroke-width=\"1.2\"/>`;\r\n } else if (type === \"relay\") {\r\n // Relay dish with signal arcs\r\n iconInner = `\r\n <path d=\"M 8 16 a 7 7 0 0 1 7 -7\" fill=\"none\" stroke=\"${statusColor}\" stroke-width=\"1.5\" stroke-linecap=\"round\"/>\r\n <path d=\"M 8 16 m 2 -2 a 5 5 0 0 1 5 -5\" fill=\"none\" stroke=\"${statusColor}\" stroke-width=\"1.2\" stroke-linecap=\"round\"/>\r\n <line x1=\"14\" y1=\"16\" x2=\"14\" y2=\"21\" stroke=\"${statusColor}\" stroke-width=\"1.2\"/>\r\n <line x1=\"11\" y1=\"21\" x2=\"17\" y2=\"21\" stroke=\"${statusColor}\" stroke-width=\"1.2\"/>\r\n <path d=\"M 16 8 a 3 3 0 0 1 0 4\" fill=\"none\" stroke=\"${statusColor}\" stroke-width=\"1.2\" stroke-linecap=\"round\"/>\r\n <path d=\"M 17.5 6.5 a 5 5 0 0 1 0 7\" fill=\"none\" stroke=\"${statusColor}\" stroke-width=\"1\" stroke-linecap=\"round\"/>`;\r\n } else {\r\n // Default: dish antenna with concentric arcs (Astro UXDS antenna pattern)\r\n iconInner = `\r\n <path d=\"M 14 14 m -7 0 a 7 7 0 0 1 7 -7\" fill=\"none\" stroke=\"${statusColor}\" stroke-width=\"1.5\" stroke-linecap=\"round\"/>\r\n <path d=\"M 14 14 m -5 0 a 5 5 0 0 1 5 -5\" fill=\"none\" stroke=\"${statusColor}\" stroke-width=\"1.5\" stroke-linecap=\"round\"/>\r\n <path d=\"M 14 14 m -3 0 a 3 3 0 0 1 3 -3\" fill=\"none\" stroke=\"${statusColor}\" stroke-width=\"1.5\" stroke-linecap=\"round\"/>\r\n <circle cx=\"14\" cy=\"14\" r=\"1.5\" fill=\"${statusColor}\"/>\r\n <line x1=\"14\" y1=\"15\" x2=\"14\" y2=\"21\" stroke=\"${statusColor}\" stroke-width=\"1.2\"/>\r\n <line x1=\"11\" y1=\"21\" x2=\"17\" y2=\"21\" stroke=\"${statusColor}\" stroke-width=\"1.2\"/>`;\r\n }\r\n\r\n const svg = `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"28\" height=\"28\" viewBox=\"0 0 28 28\">\r\n <!-- Glow ring -->\r\n <circle cx=\"14\" cy=\"14\" r=\"13\" fill=\"${statusColor}\" fill-opacity=\"0.1\"/>\r\n <!-- Background -->\r\n <circle cx=\"14\" cy=\"14\" r=\"11\" fill=\"#0d1323\" stroke=\"${statusColor}\" stroke-width=\"1.2\" stroke-opacity=\"0.7\"/>\r\n ${iconInner}\r\n <!-- Status badge (top-left) -->\r\n <circle cx=\"5\" cy=\"5\" r=\"5\" fill=\"#0d1323\"/>\r\n ${badge}\r\n </svg>`;\r\n\r\n return L.divIcon({\r\n html: svg,\r\n className: \"zendir-station-icon\",\r\n iconSize: [28, 28] as unknown as L.PointExpression,\r\n iconAnchor: [14, 14] as unknown as L.PointExpression,\r\n tooltipAnchor: [0, -14] as unknown as L.PointExpression,\r\n });\r\n}\r\n\r\n/**\r\n * Calculate the sub-solar point (latitude, longitude) for a given UTC Date.\r\n * Returns the geographic location where the sun is directly overhead.\r\n */\r\nfunction calculateSubSolarPoint(date: Date): [number, number] {\r\n const dayOfYear = Math.floor(\r\n (date.getTime() - Date.UTC(date.getUTCFullYear(), 0, 0)) / 86400000,\r\n );\r\n const declination =\r\n -23.44 * Math.cos(((2 * Math.PI) / 365) * (dayOfYear + 10));\r\n const B = ((2 * Math.PI) / 365) * (dayOfYear - 81);\r\n const eotMinutes =\r\n 9.87 * Math.sin(2 * B) - 7.53 * Math.cos(B) - 1.5 * Math.sin(B);\r\n const utcHours =\r\n date.getUTCHours() +\r\n date.getUTCMinutes() / 60 +\r\n date.getUTCSeconds() / 3600;\r\n const solarHours = utcHours + eotMinutes / 60;\r\n const lon = -(solarHours - 12) * 15;\r\n return [declination, ((lon + 540) % 360) - 180];\r\n}\r\n\r\n/**\r\n * Calculate the approximate sub-lunar point for a given UTC Date.\r\n * Uses a simplified low-precision algorithm sufficient for map overlay rendering.\r\n */\r\nfunction calculateSubLunarPoint(date: Date): [number, number] {\r\n const J2000 = Date.UTC(2000, 0, 1, 12, 0, 0);\r\n const d = (date.getTime() - J2000) / 86400000;\r\n const DEG = Math.PI / 180;\r\n const L = (218.316 + 13.176396 * d) % 360;\r\n const M = (134.963 + 13.064993 * d) % 360;\r\n const F = (93.272 + 13.22935 * d) % 360;\r\n const lon_ecl = L + 6.289 * Math.sin(M * DEG);\r\n const lat_ecl = 5.128 * Math.sin(F * DEG);\r\n const obliquity = 23.439 - 0.00000036 * d;\r\n const sinRA =\r\n Math.sin(lon_ecl * DEG) * Math.cos(obliquity * DEG) -\r\n Math.tan(lat_ecl * DEG) * Math.sin(obliquity * DEG);\r\n const cosRA = Math.cos(lon_ecl * DEG);\r\n let RA = Math.atan2(sinRA, cosRA) / DEG;\r\n if (RA < 0) RA += 360;\r\n const dec =\r\n Math.asin(\r\n Math.sin(lat_ecl * DEG) * Math.cos(obliquity * DEG) +\r\n Math.cos(lat_ecl * DEG) *\r\n Math.sin(obliquity * DEG) *\r\n Math.sin(lon_ecl * DEG),\r\n ) / DEG;\r\n const GMST = (280.46061837 + 360.98564736629 * d) % 360;\r\n let geoLon = RA - GMST;\r\n geoLon = ((geoLon + 540) % 360) - 180;\r\n return [dec, geoLon];\r\n}\r\n\r\n/**\r\n * Compute a terminator line with CONTINUOUS (unwrapped) longitudes.\r\n *\r\n * `depressionDeg` offsets the solar zenith angle to produce twilight\r\n * boundaries instead of the geometric sunset line:\r\n * - 0° = geometric sunset/sunrise (day/night boundary)\r\n * - 6° = civil twilight (horizon still visible, outdoor activities possible)\r\n * - 12° = nautical twilight (horizon barely visible, stars appearing)\r\n * - 18° = astronomical twilight (sky fully dark for observation)\r\n *\r\n * These thresholds follow IAU/USNO standard definitions used in celestial\r\n * navigation, Astro UX space ops dashboards, and STK/GMAT mission tools.\r\n */\r\nfunction calculateTerminatorContinuous(\r\n date: Date,\r\n depressionDeg: number = 0,\r\n numPoints: number = 360,\r\n): { sunset: Array<[number, number]>; sunrise: Array<[number, number]> } {\r\n const dayOfYear = Math.floor(\r\n (date.getTime() - Date.UTC(date.getUTCFullYear(), 0, 0)) / 86400000,\r\n );\r\n const declination =\r\n -23.44 * Math.cos(((2 * Math.PI) / 365) * (dayOfYear + 10));\r\n const decRad = (declination * Math.PI) / 180;\r\n\r\n // Equation of Time correction (minutes) for more accurate solar position\r\n const B = ((2 * Math.PI) / 365) * (dayOfYear - 81);\r\n const eotMinutes =\r\n 9.87 * Math.sin(2 * B) - 7.53 * Math.cos(B) - 1.5 * Math.sin(B);\r\n\r\n // Sub-solar longitude: the geographic longitude where the sun is directly overhead.\r\n // At noon UTC (solarHours=12) the sun is over Greenwich (0°).\r\n // At midnight UTC (solarHours=0) the sun is over the dateline (180°).\r\n const utcHours =\r\n date.getUTCHours() +\r\n date.getUTCMinutes() / 60 +\r\n date.getUTCSeconds() / 3600;\r\n const solarHours = utcHours + eotMinutes / 60;\r\n const subSolarLon = -(solarHours - 12) * 15;\r\n const depRad = (depressionDeg * Math.PI) / 180;\r\n\r\n const sunset: Array<[number, number]> = [];\r\n const sunrise: Array<[number, number]> = [];\r\n\r\n for (let i = 0; i <= numPoints; i++) {\r\n // Avoid exact +/- 90 to prevent longitude singularities at the poles\r\n const lat = -89.999 + (i / numPoints) * 179.998;\r\n const latRad = lat * (Math.PI / 180);\r\n const cosH =\r\n -(Math.sin(depRad) + Math.sin(latRad) * Math.sin(decRad)) /\r\n (Math.cos(latRad) * Math.cos(decRad));\r\n\r\n if (cosH < -1) {\r\n // All day — midnight sun at this latitude. No night boundary points.\r\n } else if (cosH > 1) {\r\n // All night — polar night at this latitude. Night spans all longitudes.\r\n sunset.push([lat, subSolarLon]);\r\n sunrise.push([lat, subSolarLon + 360]);\r\n } else {\r\n const H = (Math.acos(cosH) * 180) / Math.PI;\r\n // Sunset boundary: H degrees EAST of the sub-solar point (afternoon side).\r\n // Observers here see the sun setting in the west — entering night.\r\n sunset.push([lat, subSolarLon + H]);\r\n // Sunrise boundary: H degrees WEST of the sub-solar point (morning side),\r\n // expressed as subSolarLon - H + 360 to keep values > sunset for a continuous polygon.\r\n sunrise.push([lat, subSolarLon + 360 - H]);\r\n }\r\n }\r\n\r\n return { sunset, sunrise };\r\n}\r\n\r\n/**\r\n * Build the full night polygon, properly closing the loops around the poles.\r\n * Ensures longitudes are strictly continuous so the map projection doesn't\r\n * draw antimeridian lines across the day side.\r\n */\r\nfunction buildNightPolygon(terminator: {\r\n sunset: Array<[number, number]>;\r\n sunrise: Array<[number, number]>;\r\n}): [number, number][] {\r\n const { sunset, sunrise } = terminator;\r\n if (!sunset.length || !sunrise.length) return [];\r\n\r\n const poly: [number, number][] = [];\r\n\r\n // Left edge of night (sunset): South to North\r\n poly.push(...sunset);\r\n\r\n // Right edge of night (sunrise): North to South\r\n poly.push(...[...sunrise].reverse());\r\n\r\n // Leaflet will automatically close the polygon by connecting the last point\r\n // (South end of sunrise) back to the first point (South end of sunset).\r\n // Because longitudes are continuous and bounded (sunrise.lon - sunset.lon <= 360),\r\n // this closing line will simply span the bottom or top of the map, perfectly\r\n // enclosing the night area without crossing the day side!\r\n\r\n return poly;\r\n}\r\n\r\nexport type GroundStationMapItem =\r\n | GroundStation\r\n | GroundStationEnhanced\r\n | SROGroundStation;\r\n\r\nexport interface GroundTrackMapLeafletProps {\r\n allSatellites: SatelliteTrack[];\r\n groundStations: GroundStationMapItem[];\r\n showTerminator?: boolean;\r\n /** Override the time used for the terminator calculation (defaults to wall-clock now). */\r\n terminatorTime?: Date;\r\n showGrid?: boolean;\r\n showLegend?: boolean;\r\n showEquator?: boolean;\r\n showRecenterButton?: boolean;\r\n /** Show a toggle button to switch between Dark and Satellite tile styles. Default true. */\r\n showMapStyleToggle?: boolean;\r\n defaultCenter?: [number, number];\r\n defaultZoom?: number;\r\n height?: number | string;\r\n width?: number | string;\r\n minHeight?: string;\r\n emptyMessage?: string;\r\n tileUrl?: string;\r\n /**\r\n * URL template for a \"night\" tile layer (e.g. NASA Black Marble, VIIRS city lights).\r\n * Rendered beneath the terminator overlay so it only appears on the dark side.\r\n * Requires `showTerminator`. Falls back to dark overlay when not provided.\r\n */\r\n nightTileUrl?: string;\r\n /**\r\n * Point light sources rendered on the night side (masked by the terminator).\r\n * Each light fades in through twilight and reaches full brightness at night.\r\n */\r\n lightSources?: LightSource[];\r\n className?: string;\r\n onSatelliteClick?: (id: string) => void;\r\n onStationClick?: (id: string) => void;\r\n /** Points-of-interest pins */\r\n pins?: MapPin[];\r\n /** Allow adding/editing pins via map clicks */\r\n pinsEditable?: boolean;\r\n onPinAdd?: (pin: Omit<MapPin, \"id\">) => void;\r\n onPinUpdate?: (pin: MapPin) => void;\r\n onPinRemove?: (pinId: string) => void;\r\n /** Custom overlay layers for the Layers panel */\r\n customLayers?: MapLayerDef[];\r\n /** Called when any layer is toggled */\r\n onLayerChange?: (layerId: string, enabled: boolean) => void;\r\n /**\r\n * Show sun and moon position markers on the map.\r\n * Requires `showTerminator`. Default: true when terminator is enabled.\r\n */\r\n showCelestialMarkers?: boolean;\r\n}\r\n\r\nexport function GroundTrackMapLeaflet({\r\n allSatellites,\r\n groundStations,\r\n showTerminator = true,\r\n terminatorTime,\r\n showGrid = false,\r\n showLegend = true,\r\n showEquator = false,\r\n showRecenterButton = true,\r\n showMapStyleToggle = true,\r\n defaultCenter = [20, 0],\r\n defaultZoom = 2,\r\n height = \"100%\",\r\n width = \"100%\",\r\n minHeight = \"400px\",\r\n emptyMessage = \"No orbital data available\",\r\n tileUrl,\r\n nightTileUrl,\r\n lightSources,\r\n className = \"\",\r\n onSatelliteClick,\r\n onStationClick,\r\n pins,\r\n pinsEditable = false,\r\n onPinAdd,\r\n onPinUpdate,\r\n onPinRemove,\r\n customLayers,\r\n onLayerChange,\r\n showCelestialMarkers,\r\n}: GroundTrackMapLeafletProps): React.ReactElement {\r\n const { tokens } = useTheme();\r\n const containerRef = useRef<HTMLDivElement>(null);\r\n const mapRef = useRef<L.Map | null>(null);\r\n const tileLayerRef = useRef<L.TileLayer | null>(null);\r\n const overlayGroupRef = useRef<L.LayerGroup | null>(null);\r\n const controlsRef = useRef<L.Control[]>([]);\r\n /** Separate layer group so pin updates don't clear satellite/station layers */\r\n const pinsGroupRef = useRef<L.LayerGroup | null>(null);\r\n const [ready, setReady] = useState(false);\r\n\r\n // ── Layers panel open/close ──\r\n const [layersPanelOpen, setLayersPanelOpen] = useState(false);\r\n const layersPanelRef = useRef<HTMLDivElement>(null);\r\n\r\n // Close layers panel when clicking outside\r\n useEffect(() => {\r\n if (!layersPanelOpen) return;\r\n const handleClickOutside = (e: MouseEvent) => {\r\n if (\r\n layersPanelRef.current &&\r\n !layersPanelRef.current.contains(e.target as Node)\r\n ) {\r\n setLayersPanelOpen(false);\r\n }\r\n };\r\n document.addEventListener(\"mousedown\", handleClickOutside);\r\n return () => document.removeEventListener(\"mousedown\", handleClickOutside);\r\n }, [layersPanelOpen]);\r\n\r\n // ── Tile style with localStorage persistence ──\r\n const TILE_STORAGE_KEY = \"zendir-map-tile-style\";\r\n const isExplicitTileUrl = tileUrl !== undefined;\r\n const [tileStyle, setTileStyle] = useState<\"dark\" | \"satellite\">(() => {\r\n if (isExplicitTileUrl) return \"dark\";\r\n try {\r\n const saved = localStorage.getItem(TILE_STORAGE_KEY);\r\n if (saved === \"dark\" || saved === \"satellite\") return saved;\r\n } catch {\r\n /* SSR / storage blocked */\r\n }\r\n return \"dark\";\r\n });\r\n\r\n const handleTileStyleChange = useCallback((style: \"dark\" | \"satellite\") => {\r\n setTileStyle(style);\r\n try {\r\n localStorage.setItem(TILE_STORAGE_KEY, style);\r\n } catch {\r\n /* noop */\r\n }\r\n }, []);\r\n\r\n const effectiveTileUrl = isExplicitTileUrl\r\n ? tileUrl\r\n : TILE_PRESETS[tileStyle];\r\n\r\n // ── Internal overlay toggle state (built-in layers: terminator, grid) ──\r\n const [internalTerminator, setInternalTerminator] = useState(showTerminator);\r\n const [internalGrid, setInternalGrid] = useState(showGrid);\r\n\r\n // Sync with prop changes (props are the initial source of truth)\r\n useEffect(() => {\r\n setInternalTerminator(showTerminator);\r\n }, [showTerminator]);\r\n useEffect(() => {\r\n setInternalGrid(showGrid);\r\n }, [showGrid]);\r\n\r\n // ── Custom layer toggle state ──\r\n const [customLayerState, setCustomLayerState] = useState<\r\n Record<string, boolean>\r\n >(() => {\r\n const state: Record<string, boolean> = {};\r\n for (const l of customLayers ?? []) {\r\n state[l.id] = l.defaultEnabled ?? true;\r\n }\r\n return state;\r\n });\r\n\r\n // Sync when customLayers prop changes (add new layers with defaults)\r\n useEffect(() => {\r\n setCustomLayerState((prev) => {\r\n const next = { ...prev };\r\n for (const l of customLayers ?? []) {\r\n if (!(l.id in next)) next[l.id] = l.defaultEnabled ?? true;\r\n }\r\n return next;\r\n });\r\n }, [customLayers]);\r\n\r\n const handleLayerToggle = useCallback(\r\n (layerId: string, enabled: boolean) => {\r\n if (layerId === \"terminator\") {\r\n setInternalTerminator(enabled);\r\n } else if (layerId === \"grid\") {\r\n setInternalGrid(enabled);\r\n } else {\r\n setCustomLayerState((prev) => ({ ...prev, [layerId]: enabled }));\r\n }\r\n onLayerChange?.(layerId, enabled);\r\n },\r\n [onLayerChange],\r\n );\r\n\r\n const clearLayers = useCallback(() => {\r\n const map = mapRef.current;\r\n if (!map) return;\r\n overlayGroupRef.current?.clearLayers();\r\n controlsRef.current.forEach((ctrl) => map.removeControl(ctrl));\r\n controlsRef.current = [];\r\n }, []);\r\n\r\n const addLayer = useCallback((layer: L.Layer) => {\r\n overlayGroupRef.current?.addLayer(layer);\r\n }, []);\r\n\r\n // Create map once on mount (or when tileUrl changes). Intentionally not depending on\r\n // defaultCenter/defaultZoom so animated updates do not recreate the map (no flicker/zoom reset).\r\n useEffect(() => {\r\n // eslint-disable-next-line react-hooks/exhaustive-deps -- defaultCenter/defaultZoom used only for initial view\r\n const el = containerRef.current;\r\n if (!el) return;\r\n\r\n const center: L.LatLngExpression = defaultCenter ?? [20, 0];\r\n const zoom = defaultZoom ?? 2;\r\n\r\n const map = L.map(el, {\r\n center,\r\n zoom,\r\n zoomControl: false,\r\n scrollWheelZoom: true,\r\n doubleClickZoom: true,\r\n touchZoom: true,\r\n boxZoom: true,\r\n keyboard: true,\r\n dragging: true,\r\n attributionControl: true,\r\n maxBounds: [\r\n [-90, -540],\r\n [90, 540],\r\n ],\r\n maxBoundsViscosity: 1.0,\r\n });\r\n\r\n map.attributionControl.setPrefix(\"\");\r\n\r\n const overlayGroup = L.layerGroup();\r\n overlayGroup.addTo(map);\r\n overlayGroupRef.current = overlayGroup;\r\n\r\n const pinsGroup = L.layerGroup();\r\n pinsGroup.addTo(map);\r\n pinsGroupRef.current = pinsGroup;\r\n\r\n mapRef.current = map;\r\n setReady(true);\r\n\r\n const onSize = () => {\r\n map.invalidateSize({ animate: false });\r\n };\r\n // Immediate RAF + two deferred calls cover: first paint, CSS transitions, flex layout settle\r\n const raf = requestAnimationFrame(onSize);\r\n const t1 = setTimeout(onSize, 150);\r\n const t2 = setTimeout(onSize, 500);\r\n\r\n return () => {\r\n clearTimeout(t1);\r\n clearTimeout(t2);\r\n cancelAnimationFrame(raf);\r\n clearLayers();\r\n pinsGroupRef.current?.clearLayers();\r\n pinsGroupRef.current = null;\r\n tileLayerRef.current = null;\r\n map.remove();\r\n mapRef.current = null;\r\n overlayGroupRef.current = null;\r\n };\r\n // Mount-only: tile layer is swapped by a dedicated effect below.\r\n // eslint-disable-next-line react-hooks/exhaustive-deps\r\n }, [clearLayers]);\r\n\r\n // Swap tile layer without recreating the entire map (avoids flash on style toggle).\r\n useEffect(() => {\r\n const map = mapRef.current;\r\n if (!map || !ready) return;\r\n\r\n // Remove existing tile layer (Leaflet drops that layer's attribution with it)\r\n if (tileLayerRef.current) {\r\n tileLayerRef.current.remove();\r\n tileLayerRef.current = null;\r\n }\r\n\r\n const isCartoTiles = effectiveTileUrl.includes(\"cartocdn\");\r\n const basemapAttribution = getBasemapAttribution(effectiveTileUrl);\r\n\r\n const tile = L.tileLayer(effectiveTileUrl, {\r\n maxZoom: 19,\r\n subdomains: isCartoTiles ? \"abcd\" : \"abc\",\r\n crossOrigin: true,\r\n attribution: basemapAttribution || undefined,\r\n });\r\n tile.addTo(map);\r\n tile.bringToBack();\r\n tileLayerRef.current = tile;\r\n\r\n // Fallback on tile error\r\n let hasSwitchedToFallback = false;\r\n const onTileError = () => {\r\n if (hasSwitchedToFallback) return;\r\n hasSwitchedToFallback = true;\r\n tile.off(\"tileerror\", onTileError);\r\n tile.remove();\r\n const fallback = L.tileLayer(FALLBACK_TILE, {\r\n maxZoom: 19,\r\n subdomains: \"abc\",\r\n crossOrigin: true,\r\n attribution: OSM_ATTRIBUTION,\r\n });\r\n fallback.addTo(map);\r\n fallback.bringToBack();\r\n tileLayerRef.current = fallback;\r\n };\r\n tile.on(\"tileerror\", onTileError);\r\n }, [effectiveTileUrl, ready]);\r\n\r\n useEffect(() => {\r\n if (!ready || !mapRef.current) return;\r\n const map = mapRef.current;\r\n clearLayers();\r\n\r\n // === Night Tile Layer (placed beneath terminator so night imagery appears on dark side) ===\r\n if (nightTileUrl && internalTerminator) {\r\n const nightTile = L.tileLayer(nightTileUrl, {\r\n maxZoom: 19,\r\n crossOrigin: true,\r\n opacity: 0.7,\r\n className: \"zendir-night-tiles\",\r\n });\r\n addLayer(nightTile);\r\n }\r\n\r\n if (internalTerminator) {\r\n const now = terminatorTime ?? new Date();\r\n\r\n // Graduated terminator shade. Stronger on satellite imagery for contrast,\r\n // lighter on dark tiles where subtlety reads better.\r\n const isSatelliteTile = tileStyle === \"satellite\";\r\n const BAND_STEP = 1.5;\r\n const MAX_DEP = 30;\r\n const NIGHT_OPACITY = isSatelliteTile ? 0.75 : 0.38;\r\n const NIGHT_COLOR = isSatelliteTile ? \"#030810\" : \"#2a3a52\";\r\n const bandSteps: Array<{\r\n depression: number;\r\n opacity: number;\r\n color: string;\r\n }> = [];\r\n for (let d = 0; d <= MAX_DEP; d += BAND_STEP) {\r\n const t = Math.min(d / 18, 1);\r\n const opacity =\r\n Math.pow(t, 1.6) * (NIGHT_OPACITY * 0.85) +\r\n (d > 18 ? ((d - 18) / 12) * (NIGHT_OPACITY * 0.15) : 0);\r\n bandSteps.push({\r\n depression: d,\r\n opacity: Math.min(opacity, NIGHT_OPACITY),\r\n color: NIGHT_COLOR,\r\n });\r\n }\r\n bandSteps.push({\r\n depression: 90,\r\n opacity: NIGHT_OPACITY,\r\n color: NIGHT_COLOR,\r\n });\r\n\r\n let prevOpacity = 0;\r\n\r\n // Draw each band as a cumulative overlay — each adds incremental darkening\r\n // By stacking full night polygons with small opacities, we avoid drawing\r\n // complex band-difference polygons, which is much more robust at the poles.\r\n for (const b of bandSteps) {\r\n const terminator = calculateTerminatorContinuous(\r\n now,\r\n Math.min(b.depression, 89),\r\n );\r\n const bandOpacity = b.opacity - prevOpacity;\r\n if (bandOpacity < 0.002 || terminator.sunset.length === 0) {\r\n prevOpacity = b.opacity;\r\n continue;\r\n }\r\n\r\n const poly = buildNightPolygon(terminator);\r\n if (poly.length < 3) continue;\r\n\r\n [0, 360, -360].forEach((offset) => {\r\n const shifted = poly.map(\r\n ([lat, lon]) => [lat, lon + offset] as [number, number],\r\n );\r\n addLayer(\r\n L.polygon(shifted, {\r\n color: \"transparent\",\r\n fillColor: b.color,\r\n fillOpacity: bandOpacity,\r\n interactive: false,\r\n }),\r\n );\r\n });\r\n\r\n prevOpacity = b.opacity;\r\n }\r\n\r\n // Draw a smooth terminator boundary line (0° depression)\r\n // — a thin, continuous stroke showing the day/night geometric edge.\r\n // Styling adapts to tile style: brighter on satellite for visibility.\r\n const terminatorEdge = calculateTerminatorContinuous(now, 0);\r\n const glowColor = isSatelliteTile ? \"#a0a0a0\" : \"#909090\";\r\n const glowOpacity = isSatelliteTile ? 0.15 : 0.08;\r\n const coreColor = isSatelliteTile ? \"#c0c0c0\" : \"#a8a8a8\";\r\n const coreOpacity = isSatelliteTile ? 0.45 : 0.3;\r\n if (terminatorEdge.sunset.length > 2) {\r\n const sunsetLine = terminatorEdge.sunset.map(\r\n ([lat, lon]) => [lat, lon] as [number, number],\r\n );\r\n const sunriseLine = terminatorEdge.sunrise.map(\r\n ([lat, lon]) => [lat, lon] as [number, number],\r\n );\r\n [0, 360, -360].forEach((offset) => {\r\n addLayer(\r\n L.polyline(\r\n sunsetLine.map(\r\n ([lat, lon]) => [lat, lon + offset] as [number, number],\r\n ),\r\n {\r\n color: glowColor,\r\n weight: 3,\r\n opacity: glowOpacity,\r\n interactive: false,\r\n smoothFactor: 1.5,\r\n },\r\n ),\r\n );\r\n addLayer(\r\n L.polyline(\r\n sunriseLine.map(\r\n ([lat, lon]) => [lat, lon + offset] as [number, number],\r\n ),\r\n {\r\n color: glowColor,\r\n weight: 3,\r\n opacity: glowOpacity,\r\n interactive: false,\r\n smoothFactor: 1.5,\r\n },\r\n ),\r\n );\r\n addLayer(\r\n L.polyline(\r\n sunsetLine.map(\r\n ([lat, lon]) => [lat, lon + offset] as [number, number],\r\n ),\r\n {\r\n color: coreColor,\r\n weight: 1,\r\n opacity: coreOpacity,\r\n interactive: false,\r\n smoothFactor: 1.5,\r\n },\r\n ),\r\n );\r\n addLayer(\r\n L.polyline(\r\n sunriseLine.map(\r\n ([lat, lon]) => [lat, lon + offset] as [number, number],\r\n ),\r\n {\r\n color: coreColor,\r\n weight: 1,\r\n opacity: coreOpacity,\r\n interactive: false,\r\n smoothFactor: 1.5,\r\n },\r\n ),\r\n );\r\n });\r\n }\r\n\r\n // TODO: Re-enable sun/moon celestial markers once visual design is finalized.\r\n // Calculations (calculateSubSolarPoint, calculateSubLunarPoint) are correct.\r\n // Uncomment and set back to prop-driven to restore:\r\n // const celestialEnabled = showCelestialMarkers !== undefined ? showCelestialMarkers : true;\r\n const celestialEnabled = false;\r\n if (celestialEnabled) {\r\n const [sunLat, sunLon] = calculateSubSolarPoint(now);\r\n const sunIcon = L.divIcon({\r\n className: \"\",\r\n iconSize: [28, 28] as unknown as L.PointExpression,\r\n iconAnchor: [14, 14] as unknown as L.PointExpression,\r\n html: `<svg width=\"28\" height=\"28\" viewBox=\"0 0 28 28\" xmlns=\"http://www.w3.org/2000/svg\">\r\n <defs><filter id=\"sun-glow\"><feGaussianBlur stdDeviation=\"1.5\" result=\"blur\"/><feMerge><feMergeNode in=\"blur\"/><feMergeNode in=\"SourceGraphic\"/></feMerge></filter></defs>\r\n <circle cx=\"14\" cy=\"14\" r=\"6\" fill=\"#FFB300\" stroke=\"#FF8F00\" stroke-width=\"1.2\" filter=\"url(#sun-glow)\"/>\r\n ${[0, 45, 90, 135, 180, 225, 270, 315]\r\n .map((a) => {\r\n const r1 = 8,\r\n r2 = 12,\r\n rad = (a * Math.PI) / 180;\r\n return `<line x1=\"${14 + r1 * Math.cos(rad)}\" y1=\"${14 + r1 * Math.sin(rad)}\" x2=\"${14 + r2 * Math.cos(rad)}\" y2=\"${14 + r2 * Math.sin(rad)}\" stroke=\"#FFB300\" stroke-width=\"1.4\" stroke-linecap=\"round\" opacity=\"0.85\"/>`;\r\n })\r\n .join(\"\")}\r\n </svg>`,\r\n });\r\n const sunMarker = L.marker([sunLat, sunLon], {\r\n icon: sunIcon,\r\n interactive: true,\r\n zIndexOffset: 800,\r\n });\r\n sunMarker.bindTooltip(\r\n `<b>Sub-Solar Point</b><br/>Sun's zenith position<br/>Lat ${sunLat.toFixed(2)}° Lon ${sunLon.toFixed(2)}°`,\r\n {\r\n direction: \"top\",\r\n offset: [0, -14] as unknown as L.PointExpression,\r\n className: \"zendir-celestial-tooltip\",\r\n },\r\n );\r\n addLayer(sunMarker);\r\n\r\n // Sub-lunar point marker\r\n const [moonLat, moonLon] = calculateSubLunarPoint(now);\r\n const moonIcon = L.divIcon({\r\n className: \"\",\r\n iconSize: [22, 22] as unknown as L.PointExpression,\r\n iconAnchor: [11, 11] as unknown as L.PointExpression,\r\n html: `<svg width=\"22\" height=\"22\" viewBox=\"0 0 22 22\" xmlns=\"http://www.w3.org/2000/svg\">\r\n <defs><filter id=\"moon-glow\"><feGaussianBlur stdDeviation=\"1\" result=\"blur\"/><feMerge><feMergeNode in=\"blur\"/><feMergeNode in=\"SourceGraphic\"/></feMerge></filter></defs>\r\n <circle cx=\"11\" cy=\"11\" r=\"6.5\" fill=\"#e0e0e0\" stroke=\"#9e9e9e\" stroke-width=\"0.8\" filter=\"url(#moon-glow)\" opacity=\"0.9\"/>\r\n <circle cx=\"9\" cy=\"9.5\" r=\"1.6\" fill=\"#bdbdbd\" opacity=\"0.5\"/>\r\n <circle cx=\"13\" cy=\"12\" r=\"1.0\" fill=\"#bdbdbd\" opacity=\"0.4\"/>\r\n <circle cx=\"10.5\" cy=\"13.5\" r=\"0.7\" fill=\"#bdbdbd\" opacity=\"0.35\"/>\r\n </svg>`,\r\n });\r\n const moonMarker = L.marker([moonLat, moonLon], {\r\n icon: moonIcon,\r\n interactive: true,\r\n zIndexOffset: 790,\r\n });\r\n moonMarker.bindTooltip(\r\n `<b>Sub-Lunar Point</b><br/>Moon's zenith position<br/>Lat ${moonLat.toFixed(2)}° Lon ${moonLon.toFixed(2)}°`,\r\n {\r\n direction: \"top\",\r\n offset: [0, -11] as unknown as L.PointExpression,\r\n className: \"zendir-celestial-tooltip\",\r\n },\r\n );\r\n addLayer(moonMarker);\r\n } // end celestialEnabled\r\n }\r\n\r\n // === Light Sources (rendered as glowing markers on the night side) ===\r\n if (lightSources && lightSources.length > 0 && internalTerminator) {\r\n const now = terminatorTime ?? new Date();\r\n const dayOfYear = Math.floor(\r\n (now.getTime() - Date.UTC(now.getUTCFullYear(), 0, 0)) / 86400000,\r\n );\r\n const declination =\r\n -23.44 * Math.cos(((2 * Math.PI) / 365) * (dayOfYear + 10));\r\n const decRad = (declination * Math.PI) / 180;\r\n const lsSubSolarLon =\r\n -(now.getUTCHours() + now.getUTCMinutes() / 60 - 12) * 15;\r\n\r\n for (const light of lightSources) {\r\n const latRad = (light.latitude * Math.PI) / 180;\r\n const sinAlt =\r\n Math.sin(latRad) * Math.sin(decRad) +\r\n Math.cos(latRad) *\r\n Math.cos(decRad) *\r\n Math.cos(((light.longitude - lsSubSolarLon) * Math.PI) / 180);\r\n const solarAltDeg = (Math.asin(sinAlt) * 180) / Math.PI;\r\n\r\n let nightFactor: number;\r\n if (solarAltDeg >= 0) nightFactor = 0;\r\n else if (solarAltDeg <= -18) nightFactor = 1;\r\n else nightFactor = Math.min(1, -solarAltDeg / 18);\r\n\r\n if (nightFactor < 0.01) continue;\r\n\r\n const r = light.radius ?? 4;\r\n const intensity = light.intensity ?? 0.8;\r\n const color = light.color ?? \"#ffeedd\";\r\n const alpha = intensity * nightFactor;\r\n\r\n // Outer glow circle\r\n const glowMarker = L.circleMarker([light.latitude, light.longitude], {\r\n radius: r * 2,\r\n fillColor: color,\r\n fillOpacity: alpha * 0.4,\r\n color: color,\r\n weight: 0,\r\n interactive: !!light.label,\r\n });\r\n addLayer(glowMarker);\r\n\r\n // Bright core\r\n const coreMarker = L.circleMarker([light.latitude, light.longitude], {\r\n radius: Math.max(1, r * 0.5),\r\n fillColor: color,\r\n fillOpacity: Math.min(1, alpha * 1.2),\r\n color: \"transparent\",\r\n weight: 0,\r\n interactive: false,\r\n });\r\n addLayer(coreMarker);\r\n\r\n if (light.label) {\r\n glowMarker.bindTooltip(light.label, {\r\n permanent: false,\r\n direction: \"top\",\r\n className: \"zendir-leaflet-tooltip\",\r\n });\r\n }\r\n }\r\n }\r\n\r\n if (internalGrid) {\r\n const gridStyle = {\r\n color: \"rgba(157, 112, 255, 0.12)\",\r\n weight: 0.5,\r\n interactive: false,\r\n };\r\n [-360, 0, 360].forEach((offset) => {\r\n for (let lon = -180; lon <= 180; lon += 30) {\r\n const l = lon + offset;\r\n addLayer(\r\n L.polyline(\r\n [\r\n [90, l],\r\n [-90, l],\r\n ],\r\n gridStyle,\r\n ),\r\n );\r\n }\r\n for (let lat = -90; lat <= 90; lat += 30) {\r\n addLayer(\r\n L.polyline(\r\n [\r\n [lat, -180 + offset],\r\n [lat, 180 + offset],\r\n ],\r\n gridStyle,\r\n ),\r\n );\r\n }\r\n });\r\n }\r\n\r\n if (showEquator) {\r\n [-360, 0, 360].forEach((offset) => {\r\n addLayer(\r\n L.polyline(\r\n [\r\n [0, -180 + offset],\r\n [0, 180 + offset],\r\n ],\r\n {\r\n color: \"rgba(88, 166, 255, 0.25)\",\r\n weight: 1,\r\n dashArray: \"6, 4\",\r\n interactive: false,\r\n },\r\n ),\r\n );\r\n });\r\n }\r\n\r\n groundStations.forEach((gs) => {\r\n const status = (\"status\" in gs ? gs.status : undefined) ?? \"standby\";\r\n const statusColor = STATUS_COLOR_MAP[status] || STATUS_COLOR_MAP.standby;\r\n const stationType =\r\n (\"type\" in gs ? (gs as { type?: string }).type : undefined) ?? \"dish\";\r\n const radius = \"coverageRadius\" in gs ? gs.coverageRadius : undefined;\r\n const showCoverage = \"showCoverage\" in gs ? gs.showCoverage : false;\r\n if (radius != null && radius > 0 && showCoverage) {\r\n const ring = geodesicCirclePointsContinuous(\r\n gs.latitude,\r\n gs.longitude,\r\n radius,\r\n 64,\r\n );\r\n [0, 360, -360].forEach((offset) => {\r\n addLayer(\r\n L.polygon(\r\n ring.map(([lat, lon]) => [lat, lon + offset] as [number, number]),\r\n {\r\n color: `${statusColor}55`,\r\n fillColor: statusColor,\r\n fillOpacity: 0.1,\r\n weight: 1,\r\n dashArray: \"4, 3\",\r\n interactive: false,\r\n },\r\n ),\r\n );\r\n });\r\n }\r\n\r\n const gsIcon = createStationDivIcon(statusColor, stationType, status);\r\n const marker = L.marker([gs.latitude, gs.longitude], { icon: gsIcon });\r\n addLayer(marker);\r\n const statusLabel = status !== \"standby\" ? ` · ${status}` : \"\";\r\n marker.bindTooltip(\r\n `<strong>${gs.name}</strong>${statusLabel}${\"network\" in gs && gs.network ? `<br/><span style=\"opacity:0.7\">${gs.network}</span>` : \"\"}`,\r\n {\r\n permanent: false,\r\n direction: \"auto\",\r\n className: \"zendir-leaflet-tooltip\",\r\n },\r\n );\r\n marker.on(\"click\", () => {\r\n onStationClick?.(\"id\" in gs ? String(gs.id) : gs.name);\r\n });\r\n });\r\n\r\n allSatellites.forEach((sat, satIdx) => {\r\n const track = sat.groundTrack;\r\n if (!track || track.length === 0) return;\r\n\r\n const color =\r\n sat.color || DEFAULT_TRACK_COLORS[satIdx % DEFAULT_TRACK_COLORS.length];\r\n const futureIdx = sat.futureTrackIndex ?? track.length;\r\n\r\n const pastLatLngs = track\r\n .slice(0, futureIdx)\r\n .map((p) => [p.latitude, p.longitude] as [number, number]);\r\n segmentsWithWorldCopies(splitPolylineAtAntimeridian(pastLatLngs)).forEach(\r\n (seg) => {\r\n if (seg.length >= 2) {\r\n addLayer(L.polyline(seg, { color, weight: 2.5, opacity: 1 }));\r\n }\r\n },\r\n );\r\n\r\n if (futureIdx < track.length) {\r\n const futureLatLngs = track\r\n .slice(futureIdx)\r\n .map((p) => [p.latitude, p.longitude] as [number, number]);\r\n segmentsWithWorldCopies(\r\n splitPolylineAtAntimeridian(futureLatLngs),\r\n ).forEach((seg) => {\r\n if (seg.length >= 2) {\r\n addLayer(\r\n L.polyline(seg, {\r\n color,\r\n weight: 1.5,\r\n opacity: 0.5,\r\n dashArray: \"6, 4\",\r\n }),\r\n );\r\n }\r\n });\r\n }\r\n\r\n // Access mask segments (contact passes) — solid green overlay on track\r\n if (sat.accessMask && sat.accessMask.some(Boolean)) {\r\n let segment: [number, number][] = [];\r\n for (let i = 0; i < track.length; i++) {\r\n if (sat.accessMask[i]) {\r\n segment.push([track[i].latitude, track[i].longitude]);\r\n } else if (segment.length >= 2) {\r\n segmentsWithWorldCopies(\r\n splitPolylineAtAntimeridian(segment),\r\n ).forEach((seg) => {\r\n if (seg.length >= 2) {\r\n addLayer(\r\n L.polyline(seg, {\r\n color: STATUS_COLOR_MAP.normal,\r\n weight: 4,\r\n opacity: 0.9,\r\n }),\r\n );\r\n }\r\n });\r\n segment = [];\r\n } else {\r\n segment = [];\r\n }\r\n }\r\n if (segment.length >= 2) {\r\n segmentsWithWorldCopies(splitPolylineAtAntimeridian(segment)).forEach(\r\n (seg) => {\r\n if (seg.length >= 2) {\r\n addLayer(\r\n L.polyline(seg, {\r\n color: STATUS_COLOR_MAP.normal,\r\n weight: 4,\r\n opacity: 0.9,\r\n }),\r\n );\r\n }\r\n },\r\n );\r\n }\r\n }\r\n\r\n if (sat.passMarkers && sat.passMarkers.length > 0) {\r\n sat.passMarkers.forEach((pm) => {\r\n const isAos = pm.type === \"aos\";\r\n const pmColor = isAos ? \"#56f000\" : \"#ff3838\";\r\n const pmMarker = L.circleMarker([pm.latitude, pm.longitude], {\r\n radius: 5,\r\n fillColor: pmColor,\r\n color: `${pmColor}cc`,\r\n weight: 1.5,\r\n fillOpacity: 0.9,\r\n });\r\n addLayer(pmMarker);\r\n pmMarker.bindTooltip(\r\n `${pm.type.toUpperCase()}${pm.label ? ` – ${pm.label}` : \"\"}`,\r\n {\r\n permanent: false,\r\n direction: \"auto\",\r\n className: \"zendir-leaflet-tooltip\",\r\n },\r\n );\r\n });\r\n }\r\n\r\n if (sat.showFootprint && sat.footprintRadius) {\r\n const lastIdx =\r\n futureIdx > 0\r\n ? Math.min(futureIdx - 1, track.length - 1)\r\n : track.length - 1;\r\n const last = track[lastIdx];\r\n const ring = geodesicCirclePointsContinuous(\r\n last.latitude,\r\n last.longitude,\r\n sat.footprintRadius,\r\n 64,\r\n );\r\n [0, 360, -360].forEach((offset) => {\r\n addLayer(\r\n L.polygon(\r\n ring.map(([lat, lon]) => [lat, lon + offset] as [number, number]),\r\n {\r\n color: `${color}50`,\r\n fillColor: color,\r\n fillOpacity: 0.03,\r\n weight: 1,\r\n dashArray: \"4, 4\",\r\n interactive: false,\r\n },\r\n ),\r\n );\r\n });\r\n }\r\n\r\n const currentIdx =\r\n futureIdx > 0\r\n ? Math.min(futureIdx - 1, track.length - 1)\r\n : track.length - 1;\r\n const current = track[currentIdx];\r\n const statusColor =\r\n STATUS_COLOR_MAP[sat.status || \"normal\"] || STATUS_COLOR_MAP.normal;\r\n\r\n const satIcon = createSatDivIcon(statusColor, color);\r\n const satMarker = L.marker([current.latitude, current.longitude], {\r\n icon: satIcon,\r\n });\r\n addLayer(satMarker);\r\n satMarker.bindTooltip(\r\n `<strong style=\"color:${color}\">${sat.name}</strong><br/>` +\r\n `Lat ${current.latitude.toFixed(3)}° &nbsp; Lon ${current.longitude.toFixed(3)}°` +\r\n (current.altitude != null\r\n ? `<br/>Alt ${current.altitude.toFixed(1)} km`\r\n : \"\") +\r\n (sat.status\r\n ? `<br/><span style=\"color:${statusColor}\">${sat.status.toUpperCase()}</span>`\r\n : \"\"),\r\n {\r\n permanent: false,\r\n direction: \"auto\",\r\n className: \"zendir-leaflet-tooltip\",\r\n },\r\n );\r\n satMarker.on(\"click\", () => onSatelliteClick?.(sat.id));\r\n });\r\n\r\n if (showLegend && (allSatellites.length > 0 || groundStations.length > 0)) {\r\n const Legend = L.Control.extend({\r\n onAdd() {\r\n const div = L.DomUtil.create(\"div\", \"leaflet-legend-zendir\");\r\n div.style.cssText = `\r\n padding: 8px 12px; background: ${tokens.colors.background.surface}; border: 1px solid ${tokens.colors.border.default};\r\n border-radius: 8px; color: ${tokens.colors.text.primary}; font-size: 11px; font-family: Roboto, sans-serif;\r\n `;\r\n allSatellites.forEach((s, i) => {\r\n const c =\r\n s.color || DEFAULT_TRACK_COLORS[i % DEFAULT_TRACK_COLORS.length];\r\n const row = document.createElement(\"div\");\r\n row.style.cssText =\r\n \"display: flex; align-items: center; gap: 6px; margin: 2px 0;\";\r\n const swatch = document.createElement(\"span\");\r\n swatch.style.cssText = `width:8px;height:8px;border-radius:50%;background:${c};`;\r\n const label = document.createElement(\"span\");\r\n label.textContent = s.name;\r\n row.appendChild(swatch);\r\n row.appendChild(label);\r\n div.appendChild(row);\r\n });\r\n if (groundStations.length > 0) {\r\n const row = document.createElement(\"div\");\r\n row.style.cssText =\r\n \"display: flex; align-items: center; gap: 6px; margin: 2px 0; margin-top: 6px;\";\r\n const swatch = document.createElement(\"span\");\r\n swatch.style.cssText = `width:8px;height:8px;border-radius:50%;background:${STATUS_COLOR_MAP.standby};`;\r\n const label = document.createElement(\"span\");\r\n label.textContent = `${groundStations.length} Ground Station${groundStations.length > 1 ? \"s\" : \"\"}`;\r\n row.appendChild(swatch);\r\n row.appendChild(label);\r\n div.appendChild(row);\r\n }\r\n return div;\r\n },\r\n });\r\n const legend = new Legend({ position: \"topleft\" });\r\n legend.addTo(map);\r\n controlsRef.current.push(legend);\r\n }\r\n }, [\r\n ready,\r\n allSatellites,\r\n groundStations,\r\n internalTerminator,\r\n terminatorTime,\r\n nightTileUrl,\r\n lightSources,\r\n internalGrid,\r\n showEquator,\r\n showLegend,\r\n tileStyle,\r\n tokens.colors.text.primary,\r\n tokens.colors.background.surface,\r\n tokens.colors.border.default,\r\n addLayer,\r\n clearLayers,\r\n onSatelliteClick,\r\n onStationClick,\r\n ]);\r\n\r\n // ==========================================================================\r\n // Pins layer — separate from overlayGroup so pin updates don't rebuild tracks\r\n // ==========================================================================\r\n\r\n useEffect(() => {\r\n if (!ready || !mapRef.current) return;\r\n const group = pinsGroupRef.current;\r\n if (!group) return;\r\n group.clearLayers();\r\n\r\n if (!pins || pins.length === 0) return;\r\n const accentColor = tokens.colors.accent.primary;\r\n\r\n pins.forEach((pin) => {\r\n const pinColor = pin.color || accentColor;\r\n\r\n const svg = `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"36\" viewBox=\"0 0 24 36\">\r\n <defs>\r\n <filter id=\"pin-shadow-${pin.id}\" x=\"-30%\" y=\"-10%\" width=\"160%\" height=\"140%\">\r\n <feDropShadow dx=\"0\" dy=\"2\" stdDeviation=\"2\" flood-color=\"#000\" flood-opacity=\"0.35\"/>\r\n </filter>\r\n </defs>\r\n <g filter=\"url(#pin-shadow-${pin.id})\">\r\n <path d=\"M12 2 C6.5 2 2 6.5 2 12 C2 19 12 34 12 34 C12 34 22 19 22 12 C22 6.5 17.5 2 12 2Z\"\r\n fill=\"${pinColor}\" stroke=\"rgba(255,255,255,0.5)\" stroke-width=\"1\"/>\r\n <circle cx=\"12\" cy=\"12\" r=\"4\" fill=\"#0d1323\" stroke=\"rgba(255,255,255,0.7)\" stroke-width=\"0.8\"/>\r\n </g>\r\n </svg>`;\r\n\r\n const icon = L.divIcon({\r\n html: svg,\r\n className: \"zendir-pin-icon\",\r\n iconSize: [24, 36] as unknown as L.PointExpression,\r\n iconAnchor: [12, 36] as unknown as L.PointExpression,\r\n tooltipAnchor: [0, -36] as unknown as L.PointExpression,\r\n });\r\n\r\n const marker = L.marker([pin.latitude, pin.longitude], {\r\n icon,\r\n draggable: pinsEditable,\r\n title: pin.label ?? \"\",\r\n });\r\n\r\n // Rich tooltip\r\n const tooltipLines = [\r\n pin.label ? `<strong>${pin.label}</strong>` : \"\",\r\n pin.description\r\n ? `<div style=\"opacity:0.7;font-size:11px\">${pin.description}</div>`\r\n : \"\",\r\n `<div style=\"font-size:10px;opacity:0.5;font-family:monospace\">${pin.latitude.toFixed(4)}°, ${pin.longitude.toFixed(4)}°</div>`,\r\n pin.createdBy\r\n ? `<div style=\"font-size:10px;opacity:0.4;margin-top:2px\">by ${pin.createdBy}</div>`\r\n : \"\",\r\n ]\r\n .filter(Boolean)\r\n .join(\"\");\r\n marker.bindTooltip(tooltipLines, {\r\n className: \"zendir-pin-tooltip\",\r\n direction: \"top\",\r\n offset: [0, -4],\r\n });\r\n\r\n if (pinsEditable) {\r\n // Drag to reposition\r\n marker.on(\"dragend\", () => {\r\n const latlng = marker.getLatLng();\r\n onPinUpdate?.({\r\n ...pin,\r\n latitude: latlng.lat,\r\n longitude: latlng.lng,\r\n });\r\n });\r\n\r\n // Right-click to delete\r\n marker.on(\"contextmenu\", (e: L.LeafletEvent) => {\r\n L.DomEvent.stopPropagation(e as unknown as Event);\r\n onPinRemove?.(pin.id);\r\n });\r\n }\r\n\r\n group.addLayer(marker);\r\n });\r\n }, [\r\n ready,\r\n pins,\r\n pinsEditable,\r\n onPinUpdate,\r\n onPinRemove,\r\n tokens.colors.accent.primary,\r\n ]);\r\n\r\n // Click-to-add: listen for clicks on the map background when pinsEditable is true\r\n useEffect(() => {\r\n if (!ready || !mapRef.current || !pinsEditable || !onPinAdd) return;\r\n const map = mapRef.current;\r\n\r\n const handleClick = (e: L.LeafletMouseEvent) => {\r\n // Only fire on map background clicks — ignore clicks that bubbled from markers\r\n if (\r\n (e.originalEvent?.target as HTMLElement)?.closest?.(\r\n \".leaflet-marker-icon\",\r\n )\r\n )\r\n return;\r\n onPinAdd({\r\n latitude: e.latlng.lat,\r\n longitude: e.latlng.lng,\r\n label: \"\",\r\n color: tokens.colors.accent.primary,\r\n });\r\n };\r\n\r\n map.on(\"click\", handleClick);\r\n return () => {\r\n map.off(\"click\", handleClick);\r\n };\r\n }, [ready, pinsEditable, onPinAdd, tokens.colors.accent.primary]);\r\n\r\n const handleRecenter = useCallback(() => {\r\n const map = mapRef.current;\r\n if (!map) return;\r\n const points: L.LatLngExpression[] = [];\r\n allSatellites.forEach((s) => {\r\n s.groundTrack.forEach((p) => points.push([p.latitude, p.longitude]));\r\n });\r\n groundStations.forEach((gs) => points.push([gs.latitude, gs.longitude]));\r\n if (points.length > 0) {\r\n const bounds = L.latLngBounds(points);\r\n map.fitBounds(bounds, { padding: [40, 40], maxZoom: 8 });\r\n } else {\r\n map.setView(defaultCenter, defaultZoom);\r\n }\r\n }, [allSatellites, groundStations, defaultCenter, defaultZoom]);\r\n\r\n const handleZoomIn = useCallback(() => {\r\n mapRef.current?.zoomIn();\r\n }, []);\r\n const handleZoomOut = useCallback(() => {\r\n mapRef.current?.zoomOut();\r\n }, []);\r\n\r\n const resolvedMinHeight =\r\n minHeight || (typeof height === \"number\" ? `${height}px` : \"400px\");\r\n\r\n const isEmpty =\r\n allSatellites.length === 0 &&\r\n groundStations.length === 0 &&\r\n (!pins || pins.length === 0);\r\n\r\n // ── Shared control button style ──\r\n const ctrlBtnBase: React.CSSProperties = {\r\n background: \"rgba(20, 24, 38, 0.92)\",\r\n backdropFilter: \"blur(8px)\",\r\n WebkitBackdropFilter: \"blur(8px)\",\r\n border: \"1px solid rgba(120, 100, 180, 0.18)\",\r\n color: \"#c8c0d8\",\r\n cursor: \"pointer\",\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n justifyContent: \"center\",\r\n transition: \"background 0.15s, border-color 0.15s\",\r\n };\r\n\r\n const ctrlHover = (e: React.MouseEvent<HTMLButtonElement>) => {\r\n e.currentTarget.style.background = \"rgba(30, 34, 52, 0.95)\";\r\n e.currentTarget.style.borderColor = \"rgba(157, 112, 255, 0.4)\";\r\n };\r\n const ctrlLeave = (e: React.MouseEvent<HTMLButtonElement>) => {\r\n e.currentTarget.style.background = \"rgba(20, 24, 38, 0.92)\";\r\n e.currentTarget.style.borderColor = \"rgba(120, 100, 180, 0.18)\";\r\n };\r\n\r\n // Overlay items shown in the Layers panel\r\n const overlayItems: Array<{ id: string; label: string; enabled: boolean }> =\r\n [];\r\n overlayItems.push({\r\n id: \"terminator\",\r\n label: \"Day / Night\",\r\n enabled: internalTerminator,\r\n });\r\n overlayItems.push({ id: \"grid\", label: \"Grid Lines\", enabled: internalGrid });\r\n for (const l of customLayers ?? []) {\r\n overlayItems.push({\r\n id: l.id,\r\n label: l.label,\r\n enabled: customLayerState[l.id] ?? l.defaultEnabled ?? true,\r\n });\r\n }\r\n\r\n return (\r\n <div\r\n className={`zendir-ground-track-map zendir-ground-track-map-leaflet${pinsEditable ? \" zendir-pins-editable\" : \"\"} ${className}`}\r\n data-map-backend=\"leaflet\"\r\n style={{\r\n width,\r\n height,\r\n minHeight: resolvedMinHeight,\r\n backgroundColor: tokens.colors.background.base,\r\n borderRadius: 8,\r\n overflow: \"hidden\",\r\n position: \"relative\",\r\n }}\r\n >\r\n <div\r\n ref={containerRef}\r\n style={{ width: \"100%\", height: \"100%\", minHeight: resolvedMinHeight }}\r\n />\r\n {isEmpty && (\r\n <div\r\n style={{\r\n position: \"absolute\",\r\n left: \"50%\",\r\n top: \"50%\",\r\n transform: \"translate(-50%, -50%)\",\r\n color: tokens.colors.text.secondary,\r\n fontSize: 14,\r\n pointerEvents: \"none\",\r\n }}\r\n >\r\n {emptyMessage}\r\n </div>\r\n )}\r\n {/* ── Unified Map Controls — top-right control bar ── */}\r\n <div\r\n style={{\r\n position: \"absolute\",\r\n top: 10,\r\n right: 10,\r\n zIndex: 1000,\r\n display: \"flex\",\r\n flexDirection: \"row\",\r\n gap: 4,\r\n alignItems: \"flex-start\",\r\n pointerEvents: \"none\",\r\n }}\r\n >\r\n {/* Recenter */}\r\n {showRecenterButton && (\r\n <button\r\n type=\"button\"\r\n onClick={handleRecenter}\r\n title=\"Recenter map to fit all assets and ground stations\"\r\n aria-label=\"Recenter map\"\r\n style={{\r\n ...ctrlBtnBase,\r\n borderRadius: 4,\r\n padding: \"6px 8px\",\r\n gap: 4,\r\n fontSize: 10,\r\n fontWeight: 500,\r\n letterSpacing: \"0.03em\",\r\n pointerEvents: \"auto\",\r\n }}\r\n onMouseEnter={ctrlHover}\r\n onMouseLeave={ctrlLeave}\r\n >\r\n <svg\r\n width=\"13\"\r\n height=\"13\"\r\n viewBox=\"0 0 24 24\"\r\n fill=\"none\"\r\n stroke=\"currentColor\"\r\n strokeWidth=\"2.2\"\r\n >\r\n <circle cx=\"12\" cy=\"12\" r=\"3\" />\r\n <path d=\"M12 2v4M12 18v4M2 12h4M18 12h4\" />\r\n </svg>\r\n </button>\r\n )}\r\n\r\n {/* Layers panel */}\r\n <div\r\n ref={layersPanelRef}\r\n style={{ position: \"relative\", pointerEvents: \"auto\" }}\r\n >\r\n <button\r\n type=\"button\"\r\n onClick={() => setLayersPanelOpen((o) => !o)}\r\n title=\"Map layers\"\r\n aria-label=\"Toggle map layers panel\"\r\n aria-expanded={layersPanelOpen}\r\n style={{\r\n ...ctrlBtnBase,\r\n borderRadius: 4,\r\n padding: \"6px 8px\",\r\n pointerEvents: \"auto\",\r\n borderColor: layersPanelOpen\r\n ? \"rgba(157, 112, 255, 0.4)\"\r\n : ctrlBtnBase.border\r\n ? undefined\r\n : \"rgba(120, 100, 180, 0.18)\",\r\n background: layersPanelOpen\r\n ? \"rgba(30, 34, 52, 0.95)\"\r\n : ctrlBtnBase.background,\r\n }}\r\n onMouseEnter={ctrlHover}\r\n onMouseLeave={ctrlLeave}\r\n >\r\n {/* Layers icon — stacked diamonds */}\r\n <svg\r\n width=\"14\"\r\n height=\"14\"\r\n viewBox=\"0 0 24 24\"\r\n fill=\"none\"\r\n stroke=\"currentColor\"\r\n strokeWidth=\"1.8\"\r\n strokeLinejoin=\"round\"\r\n >\r\n <path d=\"M12 2L2 7l10 5 10-5-10-5z\" />\r\n <path d=\"M2 17l10 5 10-5\" />\r\n <path d=\"M2 12l10 5 10-5\" />\r\n </svg>\r\n </button>\r\n\r\n {/* Dropdown panel */}\r\n {layersPanelOpen && (\r\n <div\r\n style={{\r\n position: \"absolute\",\r\n top: \"calc(100% + 6px)\",\r\n right: 0,\r\n minWidth: 180,\r\n background: \"rgba(16, 18, 30, 0.96)\",\r\n backdropFilter: \"blur(16px)\",\r\n WebkitBackdropFilter: \"blur(16px)\",\r\n border: \"1px solid rgba(120, 100, 180, 0.18)\",\r\n borderRadius: 6,\r\n padding: \"8px 0\",\r\n boxShadow: \"0 8px 32px rgba(0,0,0,0.5)\",\r\n }}\r\n >\r\n {/* Base Map section (only when no explicit tileUrl) */}\r\n {!isExplicitTileUrl && (\r\n <>\r\n <div\r\n style={{\r\n padding: \"4px 12px 6px\",\r\n fontSize: 9,\r\n fontWeight: 600,\r\n letterSpacing: \"0.08em\",\r\n textTransform: \"uppercase\" as const,\r\n color: \"#7a748e\",\r\n }}\r\n >\r\n Base Map\r\n </div>\r\n <div\r\n style={{ display: \"flex\", gap: 2, padding: \"0 8px 8px\" }}\r\n >\r\n {([\"dark\", \"satellite\"] as const).map((style) => (\r\n <button\r\n key={style}\r\n type=\"button\"\r\n onClick={() => handleTileStyleChange(style)}\r\n aria-pressed={tileStyle === style}\r\n style={{\r\n flex: 1,\r\n background:\r\n tileStyle === style\r\n ? \"rgba(157, 112, 255, 0.18)\"\r\n : \"rgba(255,255,255,0.03)\",\r\n border: `1px solid ${tileStyle === style ? \"rgba(157, 112, 255, 0.35)\" : \"rgba(120, 100, 180, 0.1)\"}`,\r\n borderRadius: 4,\r\n color: tileStyle === style ? \"#d0c4ee\" : \"#7a748e\",\r\n cursor: \"pointer\",\r\n padding: \"5px 6px\",\r\n fontSize: 10,\r\n fontWeight: tileStyle === style ? 600 : 400,\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n justifyContent: \"center\",\r\n gap: 4,\r\n transition: \"all 0.15s ease\",\r\n }}\r\n >\r\n {style === \"dark\" ? (\r\n <svg\r\n width=\"10\"\r\n height=\"10\"\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n stroke=\"none\"\r\n >\r\n <path d=\"M21 12.79A9 9 0 1111.21 3 7 7 0 0021 12.79z\" />\r\n </svg>\r\n ) : (\r\n <svg\r\n width=\"10\"\r\n height=\"10\"\r\n viewBox=\"0 0 24 24\"\r\n fill=\"none\"\r\n stroke=\"currentColor\"\r\n strokeWidth=\"2\"\r\n >\r\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\r\n <path d=\"M2 12h20M12 2a15.3 15.3 0 014 10 15.3 15.3 0 01-4 10 15.3 15.3 0 01-4-10 15.3 15.3 0 014-10z\" />\r\n </svg>\r\n )}\r\n {style === \"dark\" ? \"Dark\" : \"Satellite\"}\r\n </button>\r\n ))}\r\n </div>\r\n <div\r\n style={{\r\n height: 1,\r\n background: \"rgba(120, 100, 180, 0.1)\",\r\n margin: \"0 8px\",\r\n }}\r\n />\r\n </>\r\n )}\r\n\r\n {/* Overlays section */}\r\n <div\r\n style={{\r\n padding: `${isExplicitTileUrl ? \"4px\" : \"8px\"} 12px 4px`,\r\n fontSize: 9,\r\n fontWeight: 600,\r\n letterSpacing: \"0.08em\",\r\n textTransform: \"uppercase\" as const,\r\n color: \"#7a748e\",\r\n }}\r\n >\r\n Overlays\r\n </div>\r\n {overlayItems.map((item) => (\r\n <button\r\n key={item.id}\r\n type=\"button\"\r\n onClick={() => handleLayerToggle(item.id, !item.enabled)}\r\n style={{\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n gap: 8,\r\n width: \"100%\",\r\n padding: \"5px 12px\",\r\n background: \"transparent\",\r\n border: \"none\",\r\n color: item.enabled ? \"#c8c0d8\" : \"#5a5470\",\r\n cursor: \"pointer\",\r\n fontSize: 11,\r\n textAlign: \"left\" as const,\r\n transition: \"background 0.1s\",\r\n }}\r\n onMouseEnter={(e) => {\r\n e.currentTarget.style.background =\r\n \"rgba(157, 112, 255, 0.08)\";\r\n }}\r\n onMouseLeave={(e) => {\r\n e.currentTarget.style.background = \"transparent\";\r\n }}\r\n >\r\n {/* Toggle indicator */}\r\n <span\r\n style={{\r\n width: 14,\r\n height: 14,\r\n borderRadius: 3,\r\n border: `1.5px solid ${item.enabled ? \"rgba(157, 112, 255, 0.6)\" : \"rgba(120, 100, 180, 0.25)\"}`,\r\n background: item.enabled\r\n ? \"rgba(157, 112, 255, 0.22)\"\r\n : \"transparent\",\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n justifyContent: \"center\",\r\n flexShrink: 0,\r\n transition: \"all 0.15s ease\",\r\n }}\r\n >\r\n {item.enabled && (\r\n <svg\r\n width=\"9\"\r\n height=\"9\"\r\n viewBox=\"0 0 12 12\"\r\n fill=\"none\"\r\n stroke=\"#d0c4ee\"\r\n strokeWidth=\"2\"\r\n >\r\n <path d=\"M2 6l3 3 5-5\" />\r\n </svg>\r\n )}\r\n </span>\r\n {item.label}\r\n </button>\r\n ))}\r\n </div>\r\n )}\r\n </div>\r\n\r\n {/* Zoom controls */}\r\n <div\r\n style={{\r\n display: \"flex\",\r\n flexDirection: \"column\",\r\n pointerEvents: \"auto\",\r\n }}\r\n >\r\n <button\r\n type=\"button\"\r\n onClick={handleZoomIn}\r\n title=\"Zoom in\"\r\n aria-label=\"Zoom in\"\r\n style={{\r\n ...ctrlBtnBase,\r\n borderRadius: \"4px 4px 0 0\",\r\n borderBottom: \"none\",\r\n width: 30,\r\n height: 28,\r\n fontSize: 16,\r\n fontWeight: 300,\r\n pointerEvents: \"auto\",\r\n }}\r\n onMouseEnter={ctrlHover}\r\n onMouseLeave={ctrlLeave}\r\n >\r\n +\r\n </button>\r\n <button\r\n type=\"button\"\r\n onClick={handleZoomOut}\r\n title=\"Zoom out\"\r\n aria-label=\"Zoom out\"\r\n style={{\r\n ...ctrlBtnBase,\r\n borderRadius: \"0 0 4px 4px\",\r\n width: 30,\r\n height: 28,\r\n fontSize: 16,\r\n fontWeight: 300,\r\n pointerEvents: \"auto\",\r\n }}\r\n onMouseEnter={ctrlHover}\r\n onMouseLeave={ctrlLeave}\r\n >\r\n −\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n );\r\n}\r\n\r\nexport default GroundTrackMapLeaflet;\r\n"],"mappings":"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"}
1
+ {"version":3,"file":"GroundTrackMapLeaflet.js","names":[],"sources":["../../../src/react/charts/GroundTrackMapLeaflet.tsx"],"sourcesContent":["/**\r\n * @zendir/ui - GroundTrackMap Leaflet Provider\r\n *\r\n * Leaflet-based 2D world map for ground tracks, stations, and terminator.\r\n * Uses Zendir hybrid theme; all interactions enabled (zoom, scroll, drag, etc.).\r\n * Import leaflet-zendir.css when using this component.\r\n */\r\n\r\nimport React, {\r\n useRef,\r\n useEffect,\r\n useCallback,\r\n useMemo,\r\n useState,\r\n} from \"react\";\r\nimport { useTheme } from \"../theme\";\r\nimport type { GroundStation } from \"../types\";\r\nimport type {\r\n SatelliteTrack,\r\n GroundStationEnhanced,\r\n SROGroundStation,\r\n MapPin,\r\n LightSource,\r\n} from \"./GroundTrackMap\";\r\nimport type { MapLayerDef } from \"./GroundTrackMap\";\r\n\r\n// Optional Leaflet — consumer must install leaflet when using mapProvider=\"leaflet\"\r\nimport L from \"leaflet\";\r\nimport \"leaflet/dist/leaflet.css\";\r\nimport \"./leaflet-zendir.css\";\r\nimport {\r\n splitPolylineAtAntimeridian,\r\n segmentsWithWorldCopies,\r\n geodesicCirclePointsContinuous,\r\n} from \"./groundTrackMapLeafletUtils\";\r\nimport {\r\n DEFAULT_TILE,\r\n FALLBACK_TILE,\r\n TILE_ERROR_THRESHOLD,\r\n OSM_ATTRIBUTION,\r\n TILE_PRESETS,\r\n getBasemapAttribution,\r\n} from \"./groundTrackMapLeafletTiles\";\r\n\r\nconst STATUS_COLOR_MAP: Record<string, string> = {\r\n normal: \"#56f000\",\r\n standby: \"#2dccff\",\r\n caution: \"#fce83a\",\r\n serious: \"#ffb302\",\r\n critical: \"#ff3838\",\r\n off: \"#a4abb6\",\r\n};\r\n\r\nconst DEFAULT_TRACK_COLORS = [\r\n \"#2dccff\",\r\n \"#3E3CFF\",\r\n \"#9D70FF\",\r\n \"#56f000\",\r\n \"#fce83a\",\r\n \"#ff7849\",\r\n \"#2dd4bf\",\r\n \"#ff3838\",\r\n];\r\n\r\n// =============================================================================\r\n// Icon helpers — produce L.DivIcon with inline SVG (no external assets needed)\r\n// =============================================================================\r\n\r\n/** Satellite spacecraft icon with solar panels, matching Astro UXDS style. */\r\nfunction createSatDivIcon(statusColor: string, trackColor: string): L.DivIcon {\r\n // Status badge shape (Astro UXDS spec)\r\n const badge =\r\n statusColor === \"#56f000\"\r\n ? `<circle cx=\"7\" cy=\"7\" r=\"3\" fill=\"${statusColor}\"/>` // normal: filled dot\r\n : statusColor === \"#2dccff\"\r\n ? `<circle cx=\"7\" cy=\"7\" r=\"2.5\" fill=\"none\" stroke=\"${statusColor}\" stroke-width=\"1.5\"/>` // standby: ring\r\n : statusColor === \"#fce83a\"\r\n ? `<rect x=\"4.5\" y=\"4.5\" width=\"5\" height=\"5\" fill=\"${statusColor}\"/>` // caution: square\r\n : statusColor === \"#ffb302\"\r\n ? `<polygon points=\"7,3.5 10.5,7 7,10.5 3.5,7\" fill=\"${statusColor}\"/>` // serious: diamond (Astro UXDS)\r\n : statusColor === \"#ff3838\"\r\n ? `<polygon points=\"7,10 4,4 10,4\" fill=\"${statusColor}\"/>` // critical: triangle down\r\n : `<circle cx=\"7\" cy=\"7\" r=\"2\" fill=\"${statusColor}\"/>`; // off: small dot\r\n\r\n const svg = `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"34\" height=\"34\" viewBox=\"0 0 34 34\">\r\n <!-- Glow ring -->\r\n <circle cx=\"17\" cy=\"17\" r=\"15\" fill=\"${statusColor}\" fill-opacity=\"0.12\"/>\r\n <!-- Main body circle -->\r\n <circle cx=\"17\" cy=\"17\" r=\"11\" fill=\"#0d1323\" stroke=\"${trackColor}\" stroke-width=\"1.5\"/>\r\n <!-- Left solar panel -->\r\n <rect x=\"2\" y=\"15\" width=\"9\" height=\"4\" rx=\"0.5\" fill=\"${trackColor}\" fill-opacity=\"0.75\"/>\r\n <line x1=\"5.5\" y1=\"15\" x2=\"5.5\" y2=\"19\" stroke=\"${trackColor}\" stroke-width=\"0.5\" stroke-opacity=\"0.45\"/>\r\n <!-- Connector left -->\r\n <line x1=\"11\" y1=\"17\" x2=\"13\" y2=\"17\" stroke=\"${trackColor}\" stroke-width=\"1.2\"/>\r\n <!-- Satellite bus body -->\r\n <rect x=\"13\" y=\"14.5\" width=\"8\" height=\"5\" rx=\"1\" fill=\"${statusColor}\"/>\r\n <!-- Connector right -->\r\n <line x1=\"21\" y1=\"17\" x2=\"23\" y2=\"17\" stroke=\"${trackColor}\" stroke-width=\"1.2\"/>\r\n <!-- Right solar panel -->\r\n <rect x=\"23\" y=\"15\" width=\"9\" height=\"4\" rx=\"0.5\" fill=\"${trackColor}\" fill-opacity=\"0.75\"/>\r\n <line x1=\"26.5\" y1=\"15\" x2=\"26.5\" y2=\"19\" stroke=\"${trackColor}\" stroke-width=\"0.5\" stroke-opacity=\"0.45\"/>\r\n <!-- Status badge (top-left) -->\r\n <circle cx=\"7\" cy=\"7\" r=\"5.5\" fill=\"#0d1323\"/>\r\n ${badge}\r\n </svg>`;\r\n\r\n return L.divIcon({\r\n html: svg,\r\n className: \"zendir-sat-icon\",\r\n iconSize: [34, 34] as unknown as L.PointExpression,\r\n iconAnchor: [17, 17] as unknown as L.PointExpression,\r\n tooltipAnchor: [0, -18] as unknown as L.PointExpression,\r\n });\r\n}\r\n\r\n/** Ground station icon — dish/antenna shape, matching Astro UXDS style. */\r\nfunction createStationDivIcon(\r\n statusColor: string,\r\n type: string,\r\n status: string,\r\n): L.DivIcon {\r\n // Status badge\r\n const badge =\r\n status === \"normal\"\r\n ? `<circle cx=\"5\" cy=\"5\" r=\"2.5\" fill=\"${statusColor}\"/>`\r\n : status === \"standby\"\r\n ? `<circle cx=\"5\" cy=\"5\" r=\"2\" fill=\"none\" stroke=\"${statusColor}\" stroke-width=\"1.2\"/>`\r\n : status === \"caution\"\r\n ? `<rect x=\"3\" y=\"3\" width=\"4\" height=\"4\" fill=\"${statusColor}\"/>`\r\n : status === \"serious\"\r\n ? `<polygon points=\"5,2.5 7.5,5 5,7.5 2.5,5\" fill=\"${statusColor}\"/>`\r\n : status === \"critical\"\r\n ? `<polygon points=\"5,7.5 2.5,2.5 7.5,2.5\" fill=\"${statusColor}\"/>`\r\n : `<circle cx=\"5\" cy=\"5\" r=\"1.5\" fill=\"${statusColor}\"/>`;\r\n\r\n let iconInner = \"\";\r\n if (type === \"phased-array\") {\r\n // Grid pattern (phased array)\r\n iconInner = `\r\n <rect x=\"9\" y=\"7\" width=\"10\" height=\"8\" rx=\"0.5\" fill=\"none\" stroke=\"${statusColor}\" stroke-width=\"1.2\"/>\r\n <line x1=\"12\" y1=\"7\" x2=\"12\" y2=\"15\" stroke=\"${statusColor}\" stroke-width=\"0.7\"/>\r\n <line x1=\"15\" y1=\"7\" x2=\"15\" y2=\"15\" stroke=\"${statusColor}\" stroke-width=\"0.7\"/>\r\n <line x1=\"9\" y1=\"10\" x2=\"19\" y2=\"10\" stroke=\"${statusColor}\" stroke-width=\"0.7\"/>\r\n <line x1=\"9\" y1=\"13\" x2=\"19\" y2=\"13\" stroke=\"${statusColor}\" stroke-width=\"0.7\"/>\r\n <line x1=\"14\" y1=\"15\" x2=\"14\" y2=\"20\" stroke=\"${statusColor}\" stroke-width=\"1.2\"/>\r\n <line x1=\"11\" y1=\"20\" x2=\"17\" y2=\"20\" stroke=\"${statusColor}\" stroke-width=\"1.2\"/>`;\r\n } else if (type === \"relay\") {\r\n // Relay dish with signal arcs\r\n iconInner = `\r\n <path d=\"M 8 16 a 7 7 0 0 1 7 -7\" fill=\"none\" stroke=\"${statusColor}\" stroke-width=\"1.5\" stroke-linecap=\"round\"/>\r\n <path d=\"M 8 16 m 2 -2 a 5 5 0 0 1 5 -5\" fill=\"none\" stroke=\"${statusColor}\" stroke-width=\"1.2\" stroke-linecap=\"round\"/>\r\n <line x1=\"14\" y1=\"16\" x2=\"14\" y2=\"21\" stroke=\"${statusColor}\" stroke-width=\"1.2\"/>\r\n <line x1=\"11\" y1=\"21\" x2=\"17\" y2=\"21\" stroke=\"${statusColor}\" stroke-width=\"1.2\"/>\r\n <path d=\"M 16 8 a 3 3 0 0 1 0 4\" fill=\"none\" stroke=\"${statusColor}\" stroke-width=\"1.2\" stroke-linecap=\"round\"/>\r\n <path d=\"M 17.5 6.5 a 5 5 0 0 1 0 7\" fill=\"none\" stroke=\"${statusColor}\" stroke-width=\"1\" stroke-linecap=\"round\"/>`;\r\n } else {\r\n // Default: dish antenna with concentric arcs (Astro UXDS antenna pattern)\r\n iconInner = `\r\n <path d=\"M 14 14 m -7 0 a 7 7 0 0 1 7 -7\" fill=\"none\" stroke=\"${statusColor}\" stroke-width=\"1.5\" stroke-linecap=\"round\"/>\r\n <path d=\"M 14 14 m -5 0 a 5 5 0 0 1 5 -5\" fill=\"none\" stroke=\"${statusColor}\" stroke-width=\"1.5\" stroke-linecap=\"round\"/>\r\n <path d=\"M 14 14 m -3 0 a 3 3 0 0 1 3 -3\" fill=\"none\" stroke=\"${statusColor}\" stroke-width=\"1.5\" stroke-linecap=\"round\"/>\r\n <circle cx=\"14\" cy=\"14\" r=\"1.5\" fill=\"${statusColor}\"/>\r\n <line x1=\"14\" y1=\"15\" x2=\"14\" y2=\"21\" stroke=\"${statusColor}\" stroke-width=\"1.2\"/>\r\n <line x1=\"11\" y1=\"21\" x2=\"17\" y2=\"21\" stroke=\"${statusColor}\" stroke-width=\"1.2\"/>`;\r\n }\r\n\r\n const svg = `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"28\" height=\"28\" viewBox=\"0 0 28 28\">\r\n <!-- Glow ring -->\r\n <circle cx=\"14\" cy=\"14\" r=\"13\" fill=\"${statusColor}\" fill-opacity=\"0.1\"/>\r\n <!-- Background -->\r\n <circle cx=\"14\" cy=\"14\" r=\"11\" fill=\"#0d1323\" stroke=\"${statusColor}\" stroke-width=\"1.2\" stroke-opacity=\"0.7\"/>\r\n ${iconInner}\r\n <!-- Status badge (top-left) -->\r\n <circle cx=\"5\" cy=\"5\" r=\"5\" fill=\"#0d1323\"/>\r\n ${badge}\r\n </svg>`;\r\n\r\n return L.divIcon({\r\n html: svg,\r\n className: \"zendir-station-icon\",\r\n iconSize: [28, 28] as unknown as L.PointExpression,\r\n iconAnchor: [14, 14] as unknown as L.PointExpression,\r\n tooltipAnchor: [0, -14] as unknown as L.PointExpression,\r\n });\r\n}\r\n\r\n/**\r\n * Calculate the sub-solar point (latitude, longitude) for a given UTC Date.\r\n * Returns the geographic location where the sun is directly overhead.\r\n */\r\nfunction calculateSubSolarPoint(date: Date): [number, number] {\r\n const dayOfYear = Math.floor(\r\n (date.getTime() - Date.UTC(date.getUTCFullYear(), 0, 0)) / 86400000,\r\n );\r\n const declination =\r\n -23.44 * Math.cos(((2 * Math.PI) / 365) * (dayOfYear + 10));\r\n const B = ((2 * Math.PI) / 365) * (dayOfYear - 81);\r\n const eotMinutes =\r\n 9.87 * Math.sin(2 * B) - 7.53 * Math.cos(B) - 1.5 * Math.sin(B);\r\n const utcHours =\r\n date.getUTCHours() +\r\n date.getUTCMinutes() / 60 +\r\n date.getUTCSeconds() / 3600;\r\n const solarHours = utcHours + eotMinutes / 60;\r\n const lon = -(solarHours - 12) * 15;\r\n return [declination, ((lon + 540) % 360) - 180];\r\n}\r\n\r\n/**\r\n * Calculate the approximate sub-lunar point for a given UTC Date.\r\n * Uses a simplified low-precision algorithm sufficient for map overlay rendering.\r\n */\r\nfunction calculateSubLunarPoint(date: Date): [number, number] {\r\n const J2000 = Date.UTC(2000, 0, 1, 12, 0, 0);\r\n const d = (date.getTime() - J2000) / 86400000;\r\n const DEG = Math.PI / 180;\r\n const L = (218.316 + 13.176396 * d) % 360;\r\n const M = (134.963 + 13.064993 * d) % 360;\r\n const F = (93.272 + 13.22935 * d) % 360;\r\n const lon_ecl = L + 6.289 * Math.sin(M * DEG);\r\n const lat_ecl = 5.128 * Math.sin(F * DEG);\r\n const obliquity = 23.439 - 0.00000036 * d;\r\n const sinRA =\r\n Math.sin(lon_ecl * DEG) * Math.cos(obliquity * DEG) -\r\n Math.tan(lat_ecl * DEG) * Math.sin(obliquity * DEG);\r\n const cosRA = Math.cos(lon_ecl * DEG);\r\n let RA = Math.atan2(sinRA, cosRA) / DEG;\r\n if (RA < 0) RA += 360;\r\n const dec =\r\n Math.asin(\r\n Math.sin(lat_ecl * DEG) * Math.cos(obliquity * DEG) +\r\n Math.cos(lat_ecl * DEG) *\r\n Math.sin(obliquity * DEG) *\r\n Math.sin(lon_ecl * DEG),\r\n ) / DEG;\r\n const GMST = (280.46061837 + 360.98564736629 * d) % 360;\r\n let geoLon = RA - GMST;\r\n geoLon = ((geoLon + 540) % 360) - 180;\r\n return [dec, geoLon];\r\n}\r\n\r\n/**\r\n * Compute a terminator line with CONTINUOUS (unwrapped) longitudes.\r\n *\r\n * `depressionDeg` offsets the solar zenith angle to produce twilight\r\n * boundaries instead of the geometric sunset line:\r\n * - 0° = geometric sunset/sunrise (day/night boundary)\r\n * - 6° = civil twilight (horizon still visible, outdoor activities possible)\r\n * - 12° = nautical twilight (horizon barely visible, stars appearing)\r\n * - 18° = astronomical twilight (sky fully dark for observation)\r\n *\r\n * These thresholds follow IAU/USNO standard definitions used in celestial\r\n * navigation, Astro UX space ops dashboards, and STK/GMAT mission tools.\r\n */\r\nfunction calculateTerminatorContinuous(\r\n date: Date,\r\n depressionDeg: number = 0,\r\n numPoints: number = 360,\r\n): { sunset: Array<[number, number]>; sunrise: Array<[number, number]> } {\r\n const dayOfYear = Math.floor(\r\n (date.getTime() - Date.UTC(date.getUTCFullYear(), 0, 0)) / 86400000,\r\n );\r\n const declination =\r\n -23.44 * Math.cos(((2 * Math.PI) / 365) * (dayOfYear + 10));\r\n const decRad = (declination * Math.PI) / 180;\r\n\r\n // Equation of Time correction (minutes) for more accurate solar position\r\n const B = ((2 * Math.PI) / 365) * (dayOfYear - 81);\r\n const eotMinutes =\r\n 9.87 * Math.sin(2 * B) - 7.53 * Math.cos(B) - 1.5 * Math.sin(B);\r\n\r\n // Sub-solar longitude: the geographic longitude where the sun is directly overhead.\r\n // At noon UTC (solarHours=12) the sun is over Greenwich (0°).\r\n // At midnight UTC (solarHours=0) the sun is over the dateline (180°).\r\n const utcHours =\r\n date.getUTCHours() +\r\n date.getUTCMinutes() / 60 +\r\n date.getUTCSeconds() / 3600;\r\n const solarHours = utcHours + eotMinutes / 60;\r\n const subSolarLon = -(solarHours - 12) * 15;\r\n const depRad = (depressionDeg * Math.PI) / 180;\r\n\r\n const sunset: Array<[number, number]> = [];\r\n const sunrise: Array<[number, number]> = [];\r\n\r\n for (let i = 0; i <= numPoints; i++) {\r\n // Avoid exact +/- 90 to prevent longitude singularities at the poles\r\n const lat = -89.999 + (i / numPoints) * 179.998;\r\n const latRad = lat * (Math.PI / 180);\r\n const cosH =\r\n -(Math.sin(depRad) + Math.sin(latRad) * Math.sin(decRad)) /\r\n (Math.cos(latRad) * Math.cos(decRad));\r\n\r\n if (cosH < -1) {\r\n // All day — midnight sun at this latitude. No night boundary points.\r\n } else if (cosH > 1) {\r\n // All night — polar night at this latitude. Night spans all longitudes.\r\n sunset.push([lat, subSolarLon]);\r\n sunrise.push([lat, subSolarLon + 360]);\r\n } else {\r\n const H = (Math.acos(cosH) * 180) / Math.PI;\r\n // Sunset boundary: H degrees EAST of the sub-solar point (afternoon side).\r\n // Observers here see the sun setting in the west — entering night.\r\n sunset.push([lat, subSolarLon + H]);\r\n // Sunrise boundary: H degrees WEST of the sub-solar point (morning side),\r\n // expressed as subSolarLon - H + 360 to keep values > sunset for a continuous polygon.\r\n sunrise.push([lat, subSolarLon + 360 - H]);\r\n }\r\n }\r\n\r\n return { sunset, sunrise };\r\n}\r\n\r\n/**\r\n * Build the full night polygon, properly closing the loops around the poles.\r\n * Ensures longitudes are strictly continuous so the map projection doesn't\r\n * draw antimeridian lines across the day side.\r\n */\r\nfunction buildNightPolygon(terminator: {\r\n sunset: Array<[number, number]>;\r\n sunrise: Array<[number, number]>;\r\n}): [number, number][] {\r\n const { sunset, sunrise } = terminator;\r\n if (!sunset.length || !sunrise.length) return [];\r\n\r\n const poly: [number, number][] = [];\r\n\r\n // Left edge of night (sunset): South to North\r\n poly.push(...sunset);\r\n\r\n // Right edge of night (sunrise): North to South\r\n poly.push(...[...sunrise].reverse());\r\n\r\n // Leaflet will automatically close the polygon by connecting the last point\r\n // (South end of sunrise) back to the first point (South end of sunset).\r\n // Because longitudes are continuous and bounded (sunrise.lon - sunset.lon <= 360),\r\n // this closing line will simply span the bottom or top of the map, perfectly\r\n // enclosing the night area without crossing the day side!\r\n\r\n return poly;\r\n}\r\n\r\nexport type GroundStationMapItem =\r\n | GroundStation\r\n | GroundStationEnhanced\r\n | SROGroundStation;\r\n\r\nexport interface GroundTrackMapLeafletProps {\r\n allSatellites: SatelliteTrack[];\r\n groundStations: GroundStationMapItem[];\r\n showTerminator?: boolean;\r\n /** Override the time used for the terminator calculation (defaults to wall-clock now). */\r\n terminatorTime?: Date;\r\n showGrid?: boolean;\r\n showLegend?: boolean;\r\n showEquator?: boolean;\r\n showRecenterButton?: boolean;\r\n /** Show a toggle button to switch between Dark and Satellite tile styles. Default true. */\r\n showMapStyleToggle?: boolean;\r\n defaultCenter?: [number, number];\r\n defaultZoom?: number;\r\n height?: number | string;\r\n width?: number | string;\r\n minHeight?: string;\r\n emptyMessage?: string;\r\n tileUrl?: string;\r\n /**\r\n * URL template for a \"night\" tile layer (e.g. NASA Black Marble, VIIRS city lights).\r\n * Rendered beneath the terminator overlay so it only appears on the dark side.\r\n * Requires `showTerminator`. Falls back to dark overlay when not provided.\r\n */\r\n nightTileUrl?: string;\r\n /**\r\n * Point light sources rendered on the night side (masked by the terminator).\r\n * Each light fades in through twilight and reaches full brightness at night.\r\n */\r\n lightSources?: LightSource[];\r\n className?: string;\r\n onSatelliteClick?: (id: string) => void;\r\n onStationClick?: (id: string) => void;\r\n /** Points-of-interest pins */\r\n pins?: MapPin[];\r\n /** Allow adding/editing pins via map clicks */\r\n pinsEditable?: boolean;\r\n onPinAdd?: (pin: Omit<MapPin, \"id\">) => void;\r\n onPinUpdate?: (pin: MapPin) => void;\r\n onPinRemove?: (pinId: string) => void;\r\n /** Custom overlay layers for the Layers panel */\r\n customLayers?: MapLayerDef[];\r\n /** Called when any layer is toggled */\r\n onLayerChange?: (layerId: string, enabled: boolean) => void;\r\n /**\r\n * Show sun and moon position markers on the map.\r\n * Requires `showTerminator`. Default: true when terminator is enabled.\r\n */\r\n showCelestialMarkers?: boolean;\r\n}\r\n\r\nexport function GroundTrackMapLeaflet({\r\n allSatellites,\r\n groundStations,\r\n showTerminator = true,\r\n terminatorTime,\r\n showGrid = false,\r\n showLegend = true,\r\n showEquator = false,\r\n showRecenterButton = true,\r\n showMapStyleToggle = true,\r\n defaultCenter = [20, 0],\r\n defaultZoom = 2,\r\n height = \"100%\",\r\n width = \"100%\",\r\n minHeight = \"400px\",\r\n emptyMessage = \"No orbital data available\",\r\n tileUrl,\r\n nightTileUrl,\r\n lightSources,\r\n className = \"\",\r\n onSatelliteClick,\r\n onStationClick,\r\n pins,\r\n pinsEditable = false,\r\n onPinAdd,\r\n onPinUpdate,\r\n onPinRemove,\r\n customLayers,\r\n onLayerChange,\r\n showCelestialMarkers,\r\n}: GroundTrackMapLeafletProps): React.ReactElement {\r\n const { tokens } = useTheme();\r\n const containerRef = useRef<HTMLDivElement>(null);\r\n const mapRef = useRef<L.Map | null>(null);\r\n const tileLayerRef = useRef<L.TileLayer | null>(null);\r\n const overlayGroupRef = useRef<L.LayerGroup | null>(null);\r\n const controlsRef = useRef<L.Control[]>([]);\r\n /** Separate layer group so pin updates don't clear satellite/station layers */\r\n const pinsGroupRef = useRef<L.LayerGroup | null>(null);\r\n const [ready, setReady] = useState(false);\r\n\r\n // ── Layers panel open/close ──\r\n const [layersPanelOpen, setLayersPanelOpen] = useState(false);\r\n const layersPanelRef = useRef<HTMLDivElement>(null);\r\n\r\n // Close layers panel when clicking outside\r\n useEffect(() => {\r\n if (!layersPanelOpen) return;\r\n const handleClickOutside = (e: MouseEvent) => {\r\n if (\r\n layersPanelRef.current &&\r\n !layersPanelRef.current.contains(e.target as Node)\r\n ) {\r\n setLayersPanelOpen(false);\r\n }\r\n };\r\n document.addEventListener(\"mousedown\", handleClickOutside);\r\n return () => document.removeEventListener(\"mousedown\", handleClickOutside);\r\n }, [layersPanelOpen]);\r\n\r\n // ── Tile style with localStorage persistence ──\r\n const TILE_STORAGE_KEY = \"zendir-map-tile-style\";\r\n const isExplicitTileUrl = tileUrl !== undefined;\r\n const [tileStyle, setTileStyle] = useState<\"dark\" | \"satellite\">(() => {\r\n if (isExplicitTileUrl) return \"dark\";\r\n try {\r\n const saved = localStorage.getItem(TILE_STORAGE_KEY);\r\n if (saved === \"dark\" || saved === \"satellite\") return saved;\r\n } catch {\r\n /* SSR / storage blocked */\r\n }\r\n return \"dark\";\r\n });\r\n\r\n const handleTileStyleChange = useCallback((style: \"dark\" | \"satellite\") => {\r\n setTileStyle(style);\r\n try {\r\n localStorage.setItem(TILE_STORAGE_KEY, style);\r\n } catch {\r\n /* noop */\r\n }\r\n }, []);\r\n\r\n const effectiveTileUrl = isExplicitTileUrl\r\n ? tileUrl\r\n : TILE_PRESETS[tileStyle];\r\n\r\n // ── Internal overlay toggle state (built-in layers: terminator, grid) ──\r\n const [internalTerminator, setInternalTerminator] = useState(showTerminator);\r\n const [internalGrid, setInternalGrid] = useState(showGrid);\r\n\r\n // Sync with prop changes (props are the initial source of truth)\r\n useEffect(() => {\r\n setInternalTerminator(showTerminator);\r\n }, [showTerminator]);\r\n useEffect(() => {\r\n setInternalGrid(showGrid);\r\n }, [showGrid]);\r\n\r\n // ── Custom layer toggle state ──\r\n const [customLayerState, setCustomLayerState] = useState<\r\n Record<string, boolean>\r\n >(() => {\r\n const state: Record<string, boolean> = {};\r\n for (const l of customLayers ?? []) {\r\n state[l.id] = l.defaultEnabled ?? true;\r\n }\r\n return state;\r\n });\r\n\r\n // Sync when customLayers prop changes (add new layers with defaults)\r\n useEffect(() => {\r\n setCustomLayerState((prev) => {\r\n const next = { ...prev };\r\n for (const l of customLayers ?? []) {\r\n if (!(l.id in next)) next[l.id] = l.defaultEnabled ?? true;\r\n }\r\n return next;\r\n });\r\n }, [customLayers]);\r\n\r\n const handleLayerToggle = useCallback(\r\n (layerId: string, enabled: boolean) => {\r\n if (layerId === \"terminator\") {\r\n setInternalTerminator(enabled);\r\n } else if (layerId === \"grid\") {\r\n setInternalGrid(enabled);\r\n } else {\r\n setCustomLayerState((prev) => ({ ...prev, [layerId]: enabled }));\r\n }\r\n onLayerChange?.(layerId, enabled);\r\n },\r\n [onLayerChange],\r\n );\r\n\r\n const clearLayers = useCallback(() => {\r\n const map = mapRef.current;\r\n if (!map) return;\r\n overlayGroupRef.current?.clearLayers();\r\n controlsRef.current.forEach((ctrl) => map.removeControl(ctrl));\r\n controlsRef.current = [];\r\n }, []);\r\n\r\n const addLayer = useCallback((layer: L.Layer) => {\r\n overlayGroupRef.current?.addLayer(layer);\r\n }, []);\r\n\r\n // Create map once on mount (or when tileUrl changes). Intentionally not depending on\r\n // defaultCenter/defaultZoom so animated updates do not recreate the map (no flicker/zoom reset).\r\n useEffect(() => {\r\n // eslint-disable-next-line react-hooks/exhaustive-deps -- defaultCenter/defaultZoom used only for initial view\r\n const el = containerRef.current;\r\n if (!el) return;\r\n\r\n const center: L.LatLngExpression = defaultCenter ?? [20, 0];\r\n const zoom = defaultZoom ?? 2;\r\n\r\n const map = L.map(el, {\r\n center,\r\n zoom,\r\n zoomControl: false,\r\n scrollWheelZoom: true,\r\n doubleClickZoom: true,\r\n touchZoom: true,\r\n boxZoom: true,\r\n keyboard: true,\r\n dragging: true,\r\n attributionControl: true,\r\n // Canvas renderer for vector layers (tracks, footprints, terminator).\r\n // The overlay set is torn down and rebuilt whenever track data or the\r\n // terminator time changes; as SVG that churn creates/destroys hundreds\r\n // of DOM nodes per rebuild, which Safari's compositor handles poorly —\r\n // iPad tabs run out of memory and desktop Safari freezes on\r\n // click-zoom. Canvas draws sidestep the DOM entirely.\r\n preferCanvas: true,\r\n maxBounds: [\r\n [-90, -540],\r\n [90, 540],\r\n ],\r\n maxBoundsViscosity: 1.0,\r\n });\r\n\r\n map.attributionControl.setPrefix(\"\");\r\n\r\n const overlayGroup = L.layerGroup();\r\n overlayGroup.addTo(map);\r\n overlayGroupRef.current = overlayGroup;\r\n\r\n const pinsGroup = L.layerGroup();\r\n pinsGroup.addTo(map);\r\n pinsGroupRef.current = pinsGroup;\r\n\r\n mapRef.current = map;\r\n setReady(true);\r\n\r\n const onSize = () => {\r\n map.invalidateSize({ animate: false });\r\n };\r\n // Immediate RAF + two deferred calls cover: first paint, CSS transitions, flex layout settle\r\n const raf = requestAnimationFrame(onSize);\r\n const t1 = setTimeout(onSize, 150);\r\n const t2 = setTimeout(onSize, 500);\r\n\r\n return () => {\r\n clearTimeout(t1);\r\n clearTimeout(t2);\r\n cancelAnimationFrame(raf);\r\n clearLayers();\r\n pinsGroupRef.current?.clearLayers();\r\n pinsGroupRef.current = null;\r\n tileLayerRef.current = null;\r\n map.remove();\r\n mapRef.current = null;\r\n overlayGroupRef.current = null;\r\n };\r\n // Mount-only: tile layer is swapped by a dedicated effect below.\r\n // eslint-disable-next-line react-hooks/exhaustive-deps\r\n }, [clearLayers]);\r\n\r\n // Swap tile layer without recreating the entire map (avoids flash on style toggle).\r\n useEffect(() => {\r\n const map = mapRef.current;\r\n if (!map || !ready) return;\r\n\r\n // Remove existing tile layer (Leaflet drops that layer's attribution with it)\r\n if (tileLayerRef.current) {\r\n tileLayerRef.current.remove();\r\n tileLayerRef.current = null;\r\n }\r\n\r\n const isCartoTiles = effectiveTileUrl.includes(\"cartocdn\");\r\n const basemapAttribution = getBasemapAttribution(effectiveTileUrl);\r\n\r\n const tile = L.tileLayer(effectiveTileUrl, {\r\n maxZoom: 19,\r\n subdomains: isCartoTiles ? \"abcd\" : \"abc\",\r\n crossOrigin: true,\r\n attribution: basemapAttribution || undefined,\r\n });\r\n tile.addTo(map);\r\n tile.bringToBack();\r\n tileLayerRef.current = tile;\r\n\r\n // Fallback on tile error\r\n let hasSwitchedToFallback = false;\r\n const onTileError = () => {\r\n if (hasSwitchedToFallback) return;\r\n hasSwitchedToFallback = true;\r\n tile.off(\"tileerror\", onTileError);\r\n tile.remove();\r\n const fallback = L.tileLayer(FALLBACK_TILE, {\r\n maxZoom: 19,\r\n subdomains: \"abc\",\r\n crossOrigin: true,\r\n attribution: OSM_ATTRIBUTION,\r\n });\r\n fallback.addTo(map);\r\n fallback.bringToBack();\r\n tileLayerRef.current = fallback;\r\n };\r\n tile.on(\"tileerror\", onTileError);\r\n }, [effectiveTileUrl, ready]);\r\n\r\n useEffect(() => {\r\n if (!ready || !mapRef.current) return;\r\n const map = mapRef.current;\r\n clearLayers();\r\n\r\n // === Night Tile Layer (placed beneath terminator so night imagery appears on dark side) ===\r\n if (nightTileUrl && internalTerminator) {\r\n const nightTile = L.tileLayer(nightTileUrl, {\r\n maxZoom: 19,\r\n crossOrigin: true,\r\n opacity: 0.7,\r\n className: \"zendir-night-tiles\",\r\n });\r\n addLayer(nightTile);\r\n }\r\n\r\n if (internalTerminator) {\r\n const now = terminatorTime ?? new Date();\r\n\r\n // Graduated terminator shade. Stronger on satellite imagery for contrast,\r\n // lighter on dark tiles where subtlety reads better.\r\n const isSatelliteTile = tileStyle === \"satellite\";\r\n const BAND_STEP = 1.5;\r\n const MAX_DEP = 30;\r\n const NIGHT_OPACITY = isSatelliteTile ? 0.75 : 0.38;\r\n const NIGHT_COLOR = isSatelliteTile ? \"#030810\" : \"#2a3a52\";\r\n const bandSteps: Array<{\r\n depression: number;\r\n opacity: number;\r\n color: string;\r\n }> = [];\r\n for (let d = 0; d <= MAX_DEP; d += BAND_STEP) {\r\n const t = Math.min(d / 18, 1);\r\n const opacity =\r\n Math.pow(t, 1.6) * (NIGHT_OPACITY * 0.85) +\r\n (d > 18 ? ((d - 18) / 12) * (NIGHT_OPACITY * 0.15) : 0);\r\n bandSteps.push({\r\n depression: d,\r\n opacity: Math.min(opacity, NIGHT_OPACITY),\r\n color: NIGHT_COLOR,\r\n });\r\n }\r\n bandSteps.push({\r\n depression: 90,\r\n opacity: NIGHT_OPACITY,\r\n color: NIGHT_COLOR,\r\n });\r\n\r\n let prevOpacity = 0;\r\n\r\n // Draw each band as a cumulative overlay — each adds incremental darkening\r\n // By stacking full night polygons with small opacities, we avoid drawing\r\n // complex band-difference polygons, which is much more robust at the poles.\r\n for (const b of bandSteps) {\r\n const terminator = calculateTerminatorContinuous(\r\n now,\r\n Math.min(b.depression, 89),\r\n );\r\n const bandOpacity = b.opacity - prevOpacity;\r\n if (bandOpacity < 0.002 || terminator.sunset.length === 0) {\r\n prevOpacity = b.opacity;\r\n continue;\r\n }\r\n\r\n const poly = buildNightPolygon(terminator);\r\n if (poly.length < 3) continue;\r\n\r\n [0, 360, -360].forEach((offset) => {\r\n const shifted = poly.map(\r\n ([lat, lon]) => [lat, lon + offset] as [number, number],\r\n );\r\n addLayer(\r\n L.polygon(shifted, {\r\n color: \"transparent\",\r\n fillColor: b.color,\r\n fillOpacity: bandOpacity,\r\n interactive: false,\r\n }),\r\n );\r\n });\r\n\r\n prevOpacity = b.opacity;\r\n }\r\n\r\n // Draw a smooth terminator boundary line (0° depression)\r\n // — a thin, continuous stroke showing the day/night geometric edge.\r\n // Styling adapts to tile style: brighter on satellite for visibility.\r\n const terminatorEdge = calculateTerminatorContinuous(now, 0);\r\n const glowColor = isSatelliteTile ? \"#a0a0a0\" : \"#909090\";\r\n const glowOpacity = isSatelliteTile ? 0.15 : 0.08;\r\n const coreColor = isSatelliteTile ? \"#c0c0c0\" : \"#a8a8a8\";\r\n const coreOpacity = isSatelliteTile ? 0.45 : 0.3;\r\n if (terminatorEdge.sunset.length > 2) {\r\n const sunsetLine = terminatorEdge.sunset.map(\r\n ([lat, lon]) => [lat, lon] as [number, number],\r\n );\r\n const sunriseLine = terminatorEdge.sunrise.map(\r\n ([lat, lon]) => [lat, lon] as [number, number],\r\n );\r\n [0, 360, -360].forEach((offset) => {\r\n addLayer(\r\n L.polyline(\r\n sunsetLine.map(\r\n ([lat, lon]) => [lat, lon + offset] as [number, number],\r\n ),\r\n {\r\n color: glowColor,\r\n weight: 3,\r\n opacity: glowOpacity,\r\n interactive: false,\r\n smoothFactor: 1.5,\r\n },\r\n ),\r\n );\r\n addLayer(\r\n L.polyline(\r\n sunriseLine.map(\r\n ([lat, lon]) => [lat, lon + offset] as [number, number],\r\n ),\r\n {\r\n color: glowColor,\r\n weight: 3,\r\n opacity: glowOpacity,\r\n interactive: false,\r\n smoothFactor: 1.5,\r\n },\r\n ),\r\n );\r\n addLayer(\r\n L.polyline(\r\n sunsetLine.map(\r\n ([lat, lon]) => [lat, lon + offset] as [number, number],\r\n ),\r\n {\r\n color: coreColor,\r\n weight: 1,\r\n opacity: coreOpacity,\r\n interactive: false,\r\n smoothFactor: 1.5,\r\n },\r\n ),\r\n );\r\n addLayer(\r\n L.polyline(\r\n sunriseLine.map(\r\n ([lat, lon]) => [lat, lon + offset] as [number, number],\r\n ),\r\n {\r\n color: coreColor,\r\n weight: 1,\r\n opacity: coreOpacity,\r\n interactive: false,\r\n smoothFactor: 1.5,\r\n },\r\n ),\r\n );\r\n });\r\n }\r\n\r\n // TODO: Re-enable sun/moon celestial markers once visual design is finalized.\r\n // Calculations (calculateSubSolarPoint, calculateSubLunarPoint) are correct.\r\n // Uncomment and set back to prop-driven to restore:\r\n // const celestialEnabled = showCelestialMarkers !== undefined ? showCelestialMarkers : true;\r\n const celestialEnabled = false;\r\n if (celestialEnabled) {\r\n const [sunLat, sunLon] = calculateSubSolarPoint(now);\r\n const sunIcon = L.divIcon({\r\n className: \"\",\r\n iconSize: [28, 28] as unknown as L.PointExpression,\r\n iconAnchor: [14, 14] as unknown as L.PointExpression,\r\n html: `<svg width=\"28\" height=\"28\" viewBox=\"0 0 28 28\" xmlns=\"http://www.w3.org/2000/svg\">\r\n <defs><filter id=\"sun-glow\"><feGaussianBlur stdDeviation=\"1.5\" result=\"blur\"/><feMerge><feMergeNode in=\"blur\"/><feMergeNode in=\"SourceGraphic\"/></feMerge></filter></defs>\r\n <circle cx=\"14\" cy=\"14\" r=\"6\" fill=\"#FFB300\" stroke=\"#FF8F00\" stroke-width=\"1.2\" filter=\"url(#sun-glow)\"/>\r\n ${[0, 45, 90, 135, 180, 225, 270, 315]\r\n .map((a) => {\r\n const r1 = 8,\r\n r2 = 12,\r\n rad = (a * Math.PI) / 180;\r\n return `<line x1=\"${14 + r1 * Math.cos(rad)}\" y1=\"${14 + r1 * Math.sin(rad)}\" x2=\"${14 + r2 * Math.cos(rad)}\" y2=\"${14 + r2 * Math.sin(rad)}\" stroke=\"#FFB300\" stroke-width=\"1.4\" stroke-linecap=\"round\" opacity=\"0.85\"/>`;\r\n })\r\n .join(\"\")}\r\n </svg>`,\r\n });\r\n const sunMarker = L.marker([sunLat, sunLon], {\r\n icon: sunIcon,\r\n interactive: true,\r\n zIndexOffset: 800,\r\n });\r\n sunMarker.bindTooltip(\r\n `<b>Sub-Solar Point</b><br/>Sun's zenith position<br/>Lat ${sunLat.toFixed(2)}° Lon ${sunLon.toFixed(2)}°`,\r\n {\r\n direction: \"top\",\r\n offset: [0, -14] as unknown as L.PointExpression,\r\n className: \"zendir-celestial-tooltip\",\r\n },\r\n );\r\n addLayer(sunMarker);\r\n\r\n // Sub-lunar point marker\r\n const [moonLat, moonLon] = calculateSubLunarPoint(now);\r\n const moonIcon = L.divIcon({\r\n className: \"\",\r\n iconSize: [22, 22] as unknown as L.PointExpression,\r\n iconAnchor: [11, 11] as unknown as L.PointExpression,\r\n html: `<svg width=\"22\" height=\"22\" viewBox=\"0 0 22 22\" xmlns=\"http://www.w3.org/2000/svg\">\r\n <defs><filter id=\"moon-glow\"><feGaussianBlur stdDeviation=\"1\" result=\"blur\"/><feMerge><feMergeNode in=\"blur\"/><feMergeNode in=\"SourceGraphic\"/></feMerge></filter></defs>\r\n <circle cx=\"11\" cy=\"11\" r=\"6.5\" fill=\"#e0e0e0\" stroke=\"#9e9e9e\" stroke-width=\"0.8\" filter=\"url(#moon-glow)\" opacity=\"0.9\"/>\r\n <circle cx=\"9\" cy=\"9.5\" r=\"1.6\" fill=\"#bdbdbd\" opacity=\"0.5\"/>\r\n <circle cx=\"13\" cy=\"12\" r=\"1.0\" fill=\"#bdbdbd\" opacity=\"0.4\"/>\r\n <circle cx=\"10.5\" cy=\"13.5\" r=\"0.7\" fill=\"#bdbdbd\" opacity=\"0.35\"/>\r\n </svg>`,\r\n });\r\n const moonMarker = L.marker([moonLat, moonLon], {\r\n icon: moonIcon,\r\n interactive: true,\r\n zIndexOffset: 790,\r\n });\r\n moonMarker.bindTooltip(\r\n `<b>Sub-Lunar Point</b><br/>Moon's zenith position<br/>Lat ${moonLat.toFixed(2)}° Lon ${moonLon.toFixed(2)}°`,\r\n {\r\n direction: \"top\",\r\n offset: [0, -11] as unknown as L.PointExpression,\r\n className: \"zendir-celestial-tooltip\",\r\n },\r\n );\r\n addLayer(moonMarker);\r\n } // end celestialEnabled\r\n }\r\n\r\n // === Light Sources (rendered as glowing markers on the night side) ===\r\n if (lightSources && lightSources.length > 0 && internalTerminator) {\r\n const now = terminatorTime ?? new Date();\r\n const dayOfYear = Math.floor(\r\n (now.getTime() - Date.UTC(now.getUTCFullYear(), 0, 0)) / 86400000,\r\n );\r\n const declination =\r\n -23.44 * Math.cos(((2 * Math.PI) / 365) * (dayOfYear + 10));\r\n const decRad = (declination * Math.PI) / 180;\r\n const lsSubSolarLon =\r\n -(now.getUTCHours() + now.getUTCMinutes() / 60 - 12) * 15;\r\n\r\n for (const light of lightSources) {\r\n const latRad = (light.latitude * Math.PI) / 180;\r\n const sinAlt =\r\n Math.sin(latRad) * Math.sin(decRad) +\r\n Math.cos(latRad) *\r\n Math.cos(decRad) *\r\n Math.cos(((light.longitude - lsSubSolarLon) * Math.PI) / 180);\r\n const solarAltDeg = (Math.asin(sinAlt) * 180) / Math.PI;\r\n\r\n let nightFactor: number;\r\n if (solarAltDeg >= 0) nightFactor = 0;\r\n else if (solarAltDeg <= -18) nightFactor = 1;\r\n else nightFactor = Math.min(1, -solarAltDeg / 18);\r\n\r\n if (nightFactor < 0.01) continue;\r\n\r\n const r = light.radius ?? 4;\r\n const intensity = light.intensity ?? 0.8;\r\n const color = light.color ?? \"#ffeedd\";\r\n const alpha = intensity * nightFactor;\r\n\r\n // Outer glow circle\r\n const glowMarker = L.circleMarker([light.latitude, light.longitude], {\r\n radius: r * 2,\r\n fillColor: color,\r\n fillOpacity: alpha * 0.4,\r\n color: color,\r\n weight: 0,\r\n interactive: !!light.label,\r\n });\r\n addLayer(glowMarker);\r\n\r\n // Bright core\r\n const coreMarker = L.circleMarker([light.latitude, light.longitude], {\r\n radius: Math.max(1, r * 0.5),\r\n fillColor: color,\r\n fillOpacity: Math.min(1, alpha * 1.2),\r\n color: \"transparent\",\r\n weight: 0,\r\n interactive: false,\r\n });\r\n addLayer(coreMarker);\r\n\r\n if (light.label) {\r\n glowMarker.bindTooltip(light.label, {\r\n permanent: false,\r\n direction: \"top\",\r\n className: \"zendir-leaflet-tooltip\",\r\n });\r\n }\r\n }\r\n }\r\n\r\n if (internalGrid) {\r\n const gridStyle = {\r\n color: \"rgba(157, 112, 255, 0.12)\",\r\n weight: 0.5,\r\n interactive: false,\r\n };\r\n [-360, 0, 360].forEach((offset) => {\r\n for (let lon = -180; lon <= 180; lon += 30) {\r\n const l = lon + offset;\r\n addLayer(\r\n L.polyline(\r\n [\r\n [90, l],\r\n [-90, l],\r\n ],\r\n gridStyle,\r\n ),\r\n );\r\n }\r\n for (let lat = -90; lat <= 90; lat += 30) {\r\n addLayer(\r\n L.polyline(\r\n [\r\n [lat, -180 + offset],\r\n [lat, 180 + offset],\r\n ],\r\n gridStyle,\r\n ),\r\n );\r\n }\r\n });\r\n }\r\n\r\n if (showEquator) {\r\n [-360, 0, 360].forEach((offset) => {\r\n addLayer(\r\n L.polyline(\r\n [\r\n [0, -180 + offset],\r\n [0, 180 + offset],\r\n ],\r\n {\r\n color: \"rgba(88, 166, 255, 0.25)\",\r\n weight: 1,\r\n dashArray: \"6, 4\",\r\n interactive: false,\r\n },\r\n ),\r\n );\r\n });\r\n }\r\n\r\n groundStations.forEach((gs) => {\r\n const status = (\"status\" in gs ? gs.status : undefined) ?? \"standby\";\r\n const statusColor = STATUS_COLOR_MAP[status] || STATUS_COLOR_MAP.standby;\r\n const stationType =\r\n (\"type\" in gs ? (gs as { type?: string }).type : undefined) ?? \"dish\";\r\n const radius = \"coverageRadius\" in gs ? gs.coverageRadius : undefined;\r\n const showCoverage = \"showCoverage\" in gs ? gs.showCoverage : false;\r\n if (radius != null && radius > 0 && showCoverage) {\r\n const ring = geodesicCirclePointsContinuous(\r\n gs.latitude,\r\n gs.longitude,\r\n radius,\r\n 64,\r\n );\r\n [0, 360, -360].forEach((offset) => {\r\n addLayer(\r\n L.polygon(\r\n ring.map(([lat, lon]) => [lat, lon + offset] as [number, number]),\r\n {\r\n color: `${statusColor}55`,\r\n fillColor: statusColor,\r\n fillOpacity: 0.1,\r\n weight: 1,\r\n dashArray: \"4, 3\",\r\n interactive: false,\r\n },\r\n ),\r\n );\r\n });\r\n }\r\n\r\n const gsIcon = createStationDivIcon(statusColor, stationType, status);\r\n const marker = L.marker([gs.latitude, gs.longitude], { icon: gsIcon });\r\n addLayer(marker);\r\n const statusLabel = status !== \"standby\" ? ` · ${status}` : \"\";\r\n marker.bindTooltip(\r\n `<strong>${gs.name}</strong>${statusLabel}${\"network\" in gs && gs.network ? `<br/><span style=\"opacity:0.7\">${gs.network}</span>` : \"\"}`,\r\n {\r\n permanent: false,\r\n direction: \"auto\",\r\n className: \"zendir-leaflet-tooltip\",\r\n },\r\n );\r\n marker.on(\"click\", () => {\r\n onStationClick?.(\"id\" in gs ? String(gs.id) : gs.name);\r\n });\r\n });\r\n\r\n allSatellites.forEach((sat, satIdx) => {\r\n const track = sat.groundTrack;\r\n if (!track || track.length === 0) return;\r\n\r\n const color =\r\n sat.color || DEFAULT_TRACK_COLORS[satIdx % DEFAULT_TRACK_COLORS.length];\r\n const futureIdx = sat.futureTrackIndex ?? track.length;\r\n\r\n const pastLatLngs = track\r\n .slice(0, futureIdx)\r\n .map((p) => [p.latitude, p.longitude] as [number, number]);\r\n segmentsWithWorldCopies(splitPolylineAtAntimeridian(pastLatLngs)).forEach(\r\n (seg) => {\r\n if (seg.length >= 2) {\r\n addLayer(L.polyline(seg, { color, weight: 2.5, opacity: 1 }));\r\n }\r\n },\r\n );\r\n\r\n if (futureIdx < track.length) {\r\n const futureLatLngs = track\r\n .slice(futureIdx)\r\n .map((p) => [p.latitude, p.longitude] as [number, number]);\r\n segmentsWithWorldCopies(\r\n splitPolylineAtAntimeridian(futureLatLngs),\r\n ).forEach((seg) => {\r\n if (seg.length >= 2) {\r\n addLayer(\r\n L.polyline(seg, {\r\n color,\r\n weight: 1.5,\r\n opacity: 0.5,\r\n dashArray: \"6, 4\",\r\n }),\r\n );\r\n }\r\n });\r\n }\r\n\r\n // Access mask segments (contact passes) — solid green overlay on track\r\n if (sat.accessMask && sat.accessMask.some(Boolean)) {\r\n let segment: [number, number][] = [];\r\n for (let i = 0; i < track.length; i++) {\r\n if (sat.accessMask[i]) {\r\n segment.push([track[i].latitude, track[i].longitude]);\r\n } else if (segment.length >= 2) {\r\n segmentsWithWorldCopies(\r\n splitPolylineAtAntimeridian(segment),\r\n ).forEach((seg) => {\r\n if (seg.length >= 2) {\r\n addLayer(\r\n L.polyline(seg, {\r\n color: STATUS_COLOR_MAP.normal,\r\n weight: 4,\r\n opacity: 0.9,\r\n }),\r\n );\r\n }\r\n });\r\n segment = [];\r\n } else {\r\n segment = [];\r\n }\r\n }\r\n if (segment.length >= 2) {\r\n segmentsWithWorldCopies(splitPolylineAtAntimeridian(segment)).forEach(\r\n (seg) => {\r\n if (seg.length >= 2) {\r\n addLayer(\r\n L.polyline(seg, {\r\n color: STATUS_COLOR_MAP.normal,\r\n weight: 4,\r\n opacity: 0.9,\r\n }),\r\n );\r\n }\r\n },\r\n );\r\n }\r\n }\r\n\r\n if (sat.passMarkers && sat.passMarkers.length > 0) {\r\n sat.passMarkers.forEach((pm) => {\r\n const isAos = pm.type === \"aos\";\r\n const pmColor = isAos ? \"#56f000\" : \"#ff3838\";\r\n const pmMarker = L.circleMarker([pm.latitude, pm.longitude], {\r\n radius: 5,\r\n fillColor: pmColor,\r\n color: `${pmColor}cc`,\r\n weight: 1.5,\r\n fillOpacity: 0.9,\r\n });\r\n addLayer(pmMarker);\r\n pmMarker.bindTooltip(\r\n `${pm.type.toUpperCase()}${pm.label ? ` – ${pm.label}` : \"\"}`,\r\n {\r\n permanent: false,\r\n direction: \"auto\",\r\n className: \"zendir-leaflet-tooltip\",\r\n },\r\n );\r\n });\r\n }\r\n\r\n if (sat.showFootprint && sat.footprintRadius) {\r\n const lastIdx =\r\n futureIdx > 0\r\n ? Math.min(futureIdx - 1, track.length - 1)\r\n : track.length - 1;\r\n const last = track[lastIdx];\r\n const ring = geodesicCirclePointsContinuous(\r\n last.latitude,\r\n last.longitude,\r\n sat.footprintRadius,\r\n 64,\r\n );\r\n [0, 360, -360].forEach((offset) => {\r\n addLayer(\r\n L.polygon(\r\n ring.map(([lat, lon]) => [lat, lon + offset] as [number, number]),\r\n {\r\n color: `${color}50`,\r\n fillColor: color,\r\n fillOpacity: 0.03,\r\n weight: 1,\r\n dashArray: \"4, 4\",\r\n interactive: false,\r\n },\r\n ),\r\n );\r\n });\r\n }\r\n\r\n const currentIdx =\r\n futureIdx > 0\r\n ? Math.min(futureIdx - 1, track.length - 1)\r\n : track.length - 1;\r\n const current = track[currentIdx];\r\n const statusColor =\r\n STATUS_COLOR_MAP[sat.status || \"normal\"] || STATUS_COLOR_MAP.normal;\r\n\r\n const satIcon = createSatDivIcon(statusColor, color);\r\n const satMarker = L.marker([current.latitude, current.longitude], {\r\n icon: satIcon,\r\n });\r\n addLayer(satMarker);\r\n satMarker.bindTooltip(\r\n `<strong style=\"color:${color}\">${sat.name}</strong><br/>` +\r\n `Lat ${current.latitude.toFixed(3)}° &nbsp; Lon ${current.longitude.toFixed(3)}°` +\r\n (current.altitude != null\r\n ? `<br/>Alt ${current.altitude.toFixed(1)} km`\r\n : \"\") +\r\n (sat.status\r\n ? `<br/><span style=\"color:${statusColor}\">${sat.status.toUpperCase()}</span>`\r\n : \"\"),\r\n {\r\n permanent: false,\r\n direction: \"auto\",\r\n className: \"zendir-leaflet-tooltip\",\r\n },\r\n );\r\n satMarker.on(\"click\", () => onSatelliteClick?.(sat.id));\r\n });\r\n\r\n if (showLegend && (allSatellites.length > 0 || groundStations.length > 0)) {\r\n const Legend = L.Control.extend({\r\n onAdd() {\r\n const div = L.DomUtil.create(\"div\", \"leaflet-legend-zendir\");\r\n div.style.cssText = `\r\n padding: 8px 12px; background: ${tokens.colors.background.surface}; border: 1px solid ${tokens.colors.border.default};\r\n border-radius: 8px; color: ${tokens.colors.text.primary}; font-size: 11px; font-family: Roboto, sans-serif;\r\n `;\r\n allSatellites.forEach((s, i) => {\r\n const c =\r\n s.color || DEFAULT_TRACK_COLORS[i % DEFAULT_TRACK_COLORS.length];\r\n const row = document.createElement(\"div\");\r\n row.style.cssText =\r\n \"display: flex; align-items: center; gap: 6px; margin: 2px 0;\";\r\n const swatch = document.createElement(\"span\");\r\n swatch.style.cssText = `width:8px;height:8px;border-radius:50%;background:${c};`;\r\n const label = document.createElement(\"span\");\r\n label.textContent = s.name;\r\n row.appendChild(swatch);\r\n row.appendChild(label);\r\n div.appendChild(row);\r\n });\r\n if (groundStations.length > 0) {\r\n const row = document.createElement(\"div\");\r\n row.style.cssText =\r\n \"display: flex; align-items: center; gap: 6px; margin: 2px 0; margin-top: 6px;\";\r\n const swatch = document.createElement(\"span\");\r\n swatch.style.cssText = `width:8px;height:8px;border-radius:50%;background:${STATUS_COLOR_MAP.standby};`;\r\n const label = document.createElement(\"span\");\r\n label.textContent = `${groundStations.length} Ground Station${groundStations.length > 1 ? \"s\" : \"\"}`;\r\n row.appendChild(swatch);\r\n row.appendChild(label);\r\n div.appendChild(row);\r\n }\r\n return div;\r\n },\r\n });\r\n const legend = new Legend({ position: \"topleft\" });\r\n legend.addTo(map);\r\n controlsRef.current.push(legend);\r\n }\r\n }, [\r\n ready,\r\n allSatellites,\r\n groundStations,\r\n internalTerminator,\r\n terminatorTime,\r\n nightTileUrl,\r\n lightSources,\r\n internalGrid,\r\n showEquator,\r\n showLegend,\r\n tileStyle,\r\n tokens.colors.text.primary,\r\n tokens.colors.background.surface,\r\n tokens.colors.border.default,\r\n addLayer,\r\n clearLayers,\r\n onSatelliteClick,\r\n onStationClick,\r\n ]);\r\n\r\n // ==========================================================================\r\n // Pins layer — separate from overlayGroup so pin updates don't rebuild tracks\r\n // ==========================================================================\r\n\r\n useEffect(() => {\r\n if (!ready || !mapRef.current) return;\r\n const group = pinsGroupRef.current;\r\n if (!group) return;\r\n group.clearLayers();\r\n\r\n if (!pins || pins.length === 0) return;\r\n const accentColor = tokens.colors.accent.primary;\r\n\r\n pins.forEach((pin) => {\r\n const pinColor = pin.color || accentColor;\r\n\r\n const svg = `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"36\" viewBox=\"0 0 24 36\">\r\n <defs>\r\n <filter id=\"pin-shadow-${pin.id}\" x=\"-30%\" y=\"-10%\" width=\"160%\" height=\"140%\">\r\n <feDropShadow dx=\"0\" dy=\"2\" stdDeviation=\"2\" flood-color=\"#000\" flood-opacity=\"0.35\"/>\r\n </filter>\r\n </defs>\r\n <g filter=\"url(#pin-shadow-${pin.id})\">\r\n <path d=\"M12 2 C6.5 2 2 6.5 2 12 C2 19 12 34 12 34 C12 34 22 19 22 12 C22 6.5 17.5 2 12 2Z\"\r\n fill=\"${pinColor}\" stroke=\"rgba(255,255,255,0.5)\" stroke-width=\"1\"/>\r\n <circle cx=\"12\" cy=\"12\" r=\"4\" fill=\"#0d1323\" stroke=\"rgba(255,255,255,0.7)\" stroke-width=\"0.8\"/>\r\n </g>\r\n </svg>`;\r\n\r\n const icon = L.divIcon({\r\n html: svg,\r\n className: \"zendir-pin-icon\",\r\n iconSize: [24, 36] as unknown as L.PointExpression,\r\n iconAnchor: [12, 36] as unknown as L.PointExpression,\r\n tooltipAnchor: [0, -36] as unknown as L.PointExpression,\r\n });\r\n\r\n const marker = L.marker([pin.latitude, pin.longitude], {\r\n icon,\r\n draggable: pinsEditable,\r\n title: pin.label ?? \"\",\r\n });\r\n\r\n // Rich tooltip\r\n const tooltipLines = [\r\n pin.label ? `<strong>${pin.label}</strong>` : \"\",\r\n pin.description\r\n ? `<div style=\"opacity:0.7;font-size:11px\">${pin.description}</div>`\r\n : \"\",\r\n `<div style=\"font-size:10px;opacity:0.5;font-family:monospace\">${pin.latitude.toFixed(4)}°, ${pin.longitude.toFixed(4)}°</div>`,\r\n pin.createdBy\r\n ? `<div style=\"font-size:10px;opacity:0.4;margin-top:2px\">by ${pin.createdBy}</div>`\r\n : \"\",\r\n ]\r\n .filter(Boolean)\r\n .join(\"\");\r\n marker.bindTooltip(tooltipLines, {\r\n className: \"zendir-pin-tooltip\",\r\n direction: \"top\",\r\n offset: [0, -4],\r\n });\r\n\r\n if (pinsEditable) {\r\n // Drag to reposition\r\n marker.on(\"dragend\", () => {\r\n const latlng = marker.getLatLng();\r\n onPinUpdate?.({\r\n ...pin,\r\n latitude: latlng.lat,\r\n longitude: latlng.lng,\r\n });\r\n });\r\n\r\n // Right-click to delete\r\n marker.on(\"contextmenu\", (e: L.LeafletEvent) => {\r\n L.DomEvent.stopPropagation(e as unknown as Event);\r\n onPinRemove?.(pin.id);\r\n });\r\n }\r\n\r\n group.addLayer(marker);\r\n });\r\n }, [\r\n ready,\r\n pins,\r\n pinsEditable,\r\n onPinUpdate,\r\n onPinRemove,\r\n tokens.colors.accent.primary,\r\n ]);\r\n\r\n // Click-to-add: listen for clicks on the map background when pinsEditable is true\r\n useEffect(() => {\r\n if (!ready || !mapRef.current || !pinsEditable || !onPinAdd) return;\r\n const map = mapRef.current;\r\n\r\n const handleClick = (e: L.LeafletMouseEvent) => {\r\n // Only fire on map background clicks — ignore clicks that bubbled from markers\r\n if (\r\n (e.originalEvent?.target as HTMLElement)?.closest?.(\r\n \".leaflet-marker-icon\",\r\n )\r\n )\r\n return;\r\n onPinAdd({\r\n latitude: e.latlng.lat,\r\n longitude: e.latlng.lng,\r\n label: \"\",\r\n color: tokens.colors.accent.primary,\r\n });\r\n };\r\n\r\n map.on(\"click\", handleClick);\r\n return () => {\r\n map.off(\"click\", handleClick);\r\n };\r\n }, [ready, pinsEditable, onPinAdd, tokens.colors.accent.primary]);\r\n\r\n const handleRecenter = useCallback(() => {\r\n const map = mapRef.current;\r\n if (!map) return;\r\n const points: L.LatLngExpression[] = [];\r\n allSatellites.forEach((s) => {\r\n s.groundTrack.forEach((p) => points.push([p.latitude, p.longitude]));\r\n });\r\n groundStations.forEach((gs) => points.push([gs.latitude, gs.longitude]));\r\n if (points.length > 0) {\r\n const bounds = L.latLngBounds(points);\r\n map.fitBounds(bounds, { padding: [40, 40], maxZoom: 8 });\r\n } else {\r\n map.setView(defaultCenter, defaultZoom);\r\n }\r\n }, [allSatellites, groundStations, defaultCenter, defaultZoom]);\r\n\r\n const handleZoomIn = useCallback(() => {\r\n mapRef.current?.zoomIn();\r\n }, []);\r\n const handleZoomOut = useCallback(() => {\r\n mapRef.current?.zoomOut();\r\n }, []);\r\n\r\n const resolvedMinHeight =\r\n minHeight || (typeof height === \"number\" ? `${height}px` : \"400px\");\r\n\r\n const isEmpty =\r\n allSatellites.length === 0 &&\r\n groundStations.length === 0 &&\r\n (!pins || pins.length === 0);\r\n\r\n // ── Shared control button style ──\r\n const ctrlBtnBase: React.CSSProperties = {\r\n background: \"rgba(20, 24, 38, 0.92)\",\r\n backdropFilter: \"blur(8px)\",\r\n WebkitBackdropFilter: \"blur(8px)\",\r\n border: \"1px solid rgba(120, 100, 180, 0.18)\",\r\n color: \"#c8c0d8\",\r\n cursor: \"pointer\",\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n justifyContent: \"center\",\r\n transition: \"background 0.15s, border-color 0.15s\",\r\n };\r\n\r\n const ctrlHover = (e: React.MouseEvent<HTMLButtonElement>) => {\r\n e.currentTarget.style.background = \"rgba(30, 34, 52, 0.95)\";\r\n e.currentTarget.style.borderColor = \"rgba(157, 112, 255, 0.4)\";\r\n };\r\n const ctrlLeave = (e: React.MouseEvent<HTMLButtonElement>) => {\r\n e.currentTarget.style.background = \"rgba(20, 24, 38, 0.92)\";\r\n e.currentTarget.style.borderColor = \"rgba(120, 100, 180, 0.18)\";\r\n };\r\n\r\n // Overlay items shown in the Layers panel\r\n const overlayItems: Array<{ id: string; label: string; enabled: boolean }> =\r\n [];\r\n overlayItems.push({\r\n id: \"terminator\",\r\n label: \"Day / Night\",\r\n enabled: internalTerminator,\r\n });\r\n overlayItems.push({ id: \"grid\", label: \"Grid Lines\", enabled: internalGrid });\r\n for (const l of customLayers ?? []) {\r\n overlayItems.push({\r\n id: l.id,\r\n label: l.label,\r\n enabled: customLayerState[l.id] ?? l.defaultEnabled ?? true,\r\n });\r\n }\r\n\r\n return (\r\n <div\r\n className={`zendir-ground-track-map zendir-ground-track-map-leaflet${pinsEditable ? \" zendir-pins-editable\" : \"\"} ${className}`}\r\n data-map-backend=\"leaflet\"\r\n style={{\r\n width,\r\n height,\r\n minHeight: resolvedMinHeight,\r\n backgroundColor: tokens.colors.background.base,\r\n borderRadius: 8,\r\n overflow: \"hidden\",\r\n position: \"relative\",\r\n }}\r\n >\r\n <div\r\n ref={containerRef}\r\n style={{ width: \"100%\", height: \"100%\", minHeight: resolvedMinHeight }}\r\n />\r\n {isEmpty && (\r\n <div\r\n style={{\r\n position: \"absolute\",\r\n left: \"50%\",\r\n top: \"50%\",\r\n transform: \"translate(-50%, -50%)\",\r\n color: tokens.colors.text.secondary,\r\n fontSize: 14,\r\n pointerEvents: \"none\",\r\n }}\r\n >\r\n {emptyMessage}\r\n </div>\r\n )}\r\n {/* ── Unified Map Controls — top-right control bar ── */}\r\n <div\r\n style={{\r\n position: \"absolute\",\r\n top: 10,\r\n right: 10,\r\n zIndex: 1000,\r\n display: \"flex\",\r\n flexDirection: \"row\",\r\n gap: 4,\r\n alignItems: \"flex-start\",\r\n pointerEvents: \"none\",\r\n }}\r\n >\r\n {/* Recenter */}\r\n {showRecenterButton && (\r\n <button\r\n type=\"button\"\r\n onClick={handleRecenter}\r\n title=\"Recenter map to fit all assets and ground stations\"\r\n aria-label=\"Recenter map\"\r\n style={{\r\n ...ctrlBtnBase,\r\n borderRadius: 4,\r\n padding: \"6px 8px\",\r\n gap: 4,\r\n fontSize: 10,\r\n fontWeight: 500,\r\n letterSpacing: \"0.03em\",\r\n pointerEvents: \"auto\",\r\n }}\r\n onMouseEnter={ctrlHover}\r\n onMouseLeave={ctrlLeave}\r\n >\r\n <svg\r\n width=\"13\"\r\n height=\"13\"\r\n viewBox=\"0 0 24 24\"\r\n fill=\"none\"\r\n stroke=\"currentColor\"\r\n strokeWidth=\"2.2\"\r\n >\r\n <circle cx=\"12\" cy=\"12\" r=\"3\" />\r\n <path d=\"M12 2v4M12 18v4M2 12h4M18 12h4\" />\r\n </svg>\r\n </button>\r\n )}\r\n\r\n {/* Layers panel */}\r\n <div\r\n ref={layersPanelRef}\r\n style={{ position: \"relative\", pointerEvents: \"auto\" }}\r\n >\r\n <button\r\n type=\"button\"\r\n onClick={() => setLayersPanelOpen((o) => !o)}\r\n title=\"Map layers\"\r\n aria-label=\"Toggle map layers panel\"\r\n aria-expanded={layersPanelOpen}\r\n style={{\r\n ...ctrlBtnBase,\r\n borderRadius: 4,\r\n padding: \"6px 8px\",\r\n pointerEvents: \"auto\",\r\n borderColor: layersPanelOpen\r\n ? \"rgba(157, 112, 255, 0.4)\"\r\n : ctrlBtnBase.border\r\n ? undefined\r\n : \"rgba(120, 100, 180, 0.18)\",\r\n background: layersPanelOpen\r\n ? \"rgba(30, 34, 52, 0.95)\"\r\n : ctrlBtnBase.background,\r\n }}\r\n onMouseEnter={ctrlHover}\r\n onMouseLeave={ctrlLeave}\r\n >\r\n {/* Layers icon — stacked diamonds */}\r\n <svg\r\n width=\"14\"\r\n height=\"14\"\r\n viewBox=\"0 0 24 24\"\r\n fill=\"none\"\r\n stroke=\"currentColor\"\r\n strokeWidth=\"1.8\"\r\n strokeLinejoin=\"round\"\r\n >\r\n <path d=\"M12 2L2 7l10 5 10-5-10-5z\" />\r\n <path d=\"M2 17l10 5 10-5\" />\r\n <path d=\"M2 12l10 5 10-5\" />\r\n </svg>\r\n </button>\r\n\r\n {/* Dropdown panel */}\r\n {layersPanelOpen && (\r\n <div\r\n style={{\r\n position: \"absolute\",\r\n top: \"calc(100% + 6px)\",\r\n right: 0,\r\n minWidth: 180,\r\n background: \"rgba(16, 18, 30, 0.96)\",\r\n backdropFilter: \"blur(16px)\",\r\n WebkitBackdropFilter: \"blur(16px)\",\r\n border: \"1px solid rgba(120, 100, 180, 0.18)\",\r\n borderRadius: 6,\r\n padding: \"8px 0\",\r\n boxShadow: \"0 8px 32px rgba(0,0,0,0.5)\",\r\n }}\r\n >\r\n {/* Base Map section (only when no explicit tileUrl) */}\r\n {!isExplicitTileUrl && (\r\n <>\r\n <div\r\n style={{\r\n padding: \"4px 12px 6px\",\r\n fontSize: 9,\r\n fontWeight: 600,\r\n letterSpacing: \"0.08em\",\r\n textTransform: \"uppercase\" as const,\r\n color: \"#7a748e\",\r\n }}\r\n >\r\n Base Map\r\n </div>\r\n <div\r\n style={{ display: \"flex\", gap: 2, padding: \"0 8px 8px\" }}\r\n >\r\n {([\"dark\", \"satellite\"] as const).map((style) => (\r\n <button\r\n key={style}\r\n type=\"button\"\r\n onClick={() => handleTileStyleChange(style)}\r\n aria-pressed={tileStyle === style}\r\n style={{\r\n flex: 1,\r\n background:\r\n tileStyle === style\r\n ? \"rgba(157, 112, 255, 0.18)\"\r\n : \"rgba(255,255,255,0.03)\",\r\n border: `1px solid ${tileStyle === style ? \"rgba(157, 112, 255, 0.35)\" : \"rgba(120, 100, 180, 0.1)\"}`,\r\n borderRadius: 4,\r\n color: tileStyle === style ? \"#d0c4ee\" : \"#7a748e\",\r\n cursor: \"pointer\",\r\n padding: \"5px 6px\",\r\n fontSize: 10,\r\n fontWeight: tileStyle === style ? 600 : 400,\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n justifyContent: \"center\",\r\n gap: 4,\r\n transition: \"all 0.15s ease\",\r\n }}\r\n >\r\n {style === \"dark\" ? (\r\n <svg\r\n width=\"10\"\r\n height=\"10\"\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n stroke=\"none\"\r\n >\r\n <path d=\"M21 12.79A9 9 0 1111.21 3 7 7 0 0021 12.79z\" />\r\n </svg>\r\n ) : (\r\n <svg\r\n width=\"10\"\r\n height=\"10\"\r\n viewBox=\"0 0 24 24\"\r\n fill=\"none\"\r\n stroke=\"currentColor\"\r\n strokeWidth=\"2\"\r\n >\r\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\r\n <path d=\"M2 12h20M12 2a15.3 15.3 0 014 10 15.3 15.3 0 01-4 10 15.3 15.3 0 01-4-10 15.3 15.3 0 014-10z\" />\r\n </svg>\r\n )}\r\n {style === \"dark\" ? \"Dark\" : \"Satellite\"}\r\n </button>\r\n ))}\r\n </div>\r\n <div\r\n style={{\r\n height: 1,\r\n background: \"rgba(120, 100, 180, 0.1)\",\r\n margin: \"0 8px\",\r\n }}\r\n />\r\n </>\r\n )}\r\n\r\n {/* Overlays section */}\r\n <div\r\n style={{\r\n padding: `${isExplicitTileUrl ? \"4px\" : \"8px\"} 12px 4px`,\r\n fontSize: 9,\r\n fontWeight: 600,\r\n letterSpacing: \"0.08em\",\r\n textTransform: \"uppercase\" as const,\r\n color: \"#7a748e\",\r\n }}\r\n >\r\n Overlays\r\n </div>\r\n {overlayItems.map((item) => (\r\n <button\r\n key={item.id}\r\n type=\"button\"\r\n onClick={() => handleLayerToggle(item.id, !item.enabled)}\r\n style={{\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n gap: 8,\r\n width: \"100%\",\r\n padding: \"5px 12px\",\r\n background: \"transparent\",\r\n border: \"none\",\r\n color: item.enabled ? \"#c8c0d8\" : \"#5a5470\",\r\n cursor: \"pointer\",\r\n fontSize: 11,\r\n textAlign: \"left\" as const,\r\n transition: \"background 0.1s\",\r\n }}\r\n onMouseEnter={(e) => {\r\n e.currentTarget.style.background =\r\n \"rgba(157, 112, 255, 0.08)\";\r\n }}\r\n onMouseLeave={(e) => {\r\n e.currentTarget.style.background = \"transparent\";\r\n }}\r\n >\r\n {/* Toggle indicator */}\r\n <span\r\n style={{\r\n width: 14,\r\n height: 14,\r\n borderRadius: 3,\r\n border: `1.5px solid ${item.enabled ? \"rgba(157, 112, 255, 0.6)\" : \"rgba(120, 100, 180, 0.25)\"}`,\r\n background: item.enabled\r\n ? \"rgba(157, 112, 255, 0.22)\"\r\n : \"transparent\",\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n justifyContent: \"center\",\r\n flexShrink: 0,\r\n transition: \"all 0.15s ease\",\r\n }}\r\n >\r\n {item.enabled && (\r\n <svg\r\n width=\"9\"\r\n height=\"9\"\r\n viewBox=\"0 0 12 12\"\r\n fill=\"none\"\r\n stroke=\"#d0c4ee\"\r\n strokeWidth=\"2\"\r\n >\r\n <path d=\"M2 6l3 3 5-5\" />\r\n </svg>\r\n )}\r\n </span>\r\n {item.label}\r\n </button>\r\n ))}\r\n </div>\r\n )}\r\n </div>\r\n\r\n {/* Zoom controls */}\r\n <div\r\n style={{\r\n display: \"flex\",\r\n flexDirection: \"column\",\r\n pointerEvents: \"auto\",\r\n }}\r\n >\r\n <button\r\n type=\"button\"\r\n onClick={handleZoomIn}\r\n title=\"Zoom in\"\r\n aria-label=\"Zoom in\"\r\n style={{\r\n ...ctrlBtnBase,\r\n borderRadius: \"4px 4px 0 0\",\r\n borderBottom: \"none\",\r\n width: 30,\r\n height: 28,\r\n fontSize: 16,\r\n fontWeight: 300,\r\n pointerEvents: \"auto\",\r\n }}\r\n onMouseEnter={ctrlHover}\r\n onMouseLeave={ctrlLeave}\r\n >\r\n +\r\n </button>\r\n <button\r\n type=\"button\"\r\n onClick={handleZoomOut}\r\n title=\"Zoom out\"\r\n aria-label=\"Zoom out\"\r\n style={{\r\n ...ctrlBtnBase,\r\n borderRadius: \"0 0 4px 4px\",\r\n width: 30,\r\n height: 28,\r\n fontSize: 16,\r\n fontWeight: 300,\r\n pointerEvents: \"auto\",\r\n }}\r\n onMouseEnter={ctrlHover}\r\n onMouseLeave={ctrlLeave}\r\n >\r\n −\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n );\r\n}\r\n\r\nexport default GroundTrackMapLeaflet;\r\n"],"mappings":"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"}
@@ -1,17 +1,4 @@
1
- /**
2
- * Power Overview Chart - Combined Timeline Visualization
3
- *
4
- * Award-winning UX combining:
5
- * - Power generation timeline
6
- * - State regions (Running, Safe, Shutdown)
7
- * - Mode indicators (Inertial, Sun, Nadir, Velocity, Ground)
8
- * - Eclipse periods
9
- * - Event markers with dots on timeline
10
- * - Interactive tooltips
11
- * - Status indicators
12
- *
13
- * @module @zendir/ui/charts/PowerOverviewChart
14
- */
1
+ import { default as React } from 'react';
15
2
  export type PowerState = "running" | "safe" | "shutdown";
16
3
  export type OperationalMode = "none" | "inertial" | "sun" | "nadir" | "velocity" | "ground";
17
4
  export type EventType = "scenario" | "operator" | "spacecraft" | "ground";
@@ -65,7 +52,7 @@ export interface PowerOverviewChartProps {
65
52
  /** Enable zoom controls */
66
53
  enableZoom?: boolean;
67
54
  }
68
- export declare function PowerOverviewChart(props: PowerOverviewChartProps): import("react/jsx-runtime").JSX.Element;
55
+ export declare function PowerOverviewChart(props: PowerOverviewChartProps): React.JSX.Element;
69
56
  export declare function generateSamplePowerData(durationSeconds?: number): {
70
57
  data: PowerTimelinePoint[];
71
58
  events: PowerEvent[];
@@ -19,7 +19,7 @@ export interface SpatialSelectionProviderProps {
19
19
  /** Called whenever the selection changes (uncontrolled or controlled). */
20
20
  onSelectionChange?: (id: string | null) => void;
21
21
  }
22
- export declare function SpatialSelectionProvider({ children, defaultSelectedId, selectedId: controlledId, onSelectionChange, }: SpatialSelectionProviderProps): import("react/jsx-runtime").JSX.Element;
22
+ export declare function SpatialSelectionProvider({ children, defaultSelectedId, selectedId: controlledId, onSelectionChange, }: SpatialSelectionProviderProps): import("react").JSX.Element;
23
23
  /**
24
24
  * Read the spatial-selection context. Returns `null` when used outside
25
25
  * a `<SpatialSelectionProvider>` — components MUST handle that case so
@@ -48,7 +48,7 @@ var Checkbox = memo(forwardRef(function Checkbox({ type = "checkbox", checked =
48
48
  const boxStyle = {
49
49
  width: config.boxSize,
50
50
  height: config.boxSize,
51
- border: `${tokens.borders.width.thick} solid ${checked ? tokens.colors.accent.primary : hovered && !disabled ? `${tokens.colors.accent.primary}60` : tokens.colors.border.muted}`,
51
+ border: `${tokens.borders.width.thick} solid ${checked ? tokens.colors.accent.primary : hovered && !disabled ? `${tokens.colors.accent.primary}60` : tokens.colors.border.default}`,
52
52
  borderRadius,
53
53
  backgroundColor: checked ? tokens.colors.accent.primary : isTransparentTheme && transparentDefault ? transparentDefault : "transparent",
54
54
  display: "flex",
@@ -1 +1 @@
1
- {"version":3,"file":"Checkbox.js","names":[],"sources":["../../../../src/react/core/inputs/Checkbox.tsx"],"sourcesContent":["/**\r\n * @zendir/ui - Checkbox Component\r\n *\r\n * Checkbox and Radio button following Astro UX Design System with Zendir purple accents.\r\n *\r\n * Astro UX Compliance:\r\n * - Clear visual states\r\n * - Accessible keyboard support\r\n * - Indeterminate state for checkbox\r\n *\r\n * Zendir Enhancements:\r\n * - Purple accent when checked\r\n * - Glow effect on focus\r\n * - Smooth spring animation\r\n *\r\n * @example\r\n * ```tsx\r\n * <Checkbox checked={agreed} onChange={setAgreed} label=\"I agree to terms\" />\r\n * <Checkbox type=\"radio\" name=\"priority\" value=\"high\" label=\"High Priority\" />\r\n * ```\r\n */\r\n\r\nimport React, { memo, forwardRef, useId, useState } from \"react\";\r\nimport { useTheme } from \"../../theme\";\r\nimport { classNames } from \"../../utils\";\r\n\r\nexport interface CheckboxProps {\r\n /** Checkbox or radio */\r\n type?: \"checkbox\" | \"radio\";\r\n /** Checked state */\r\n checked?: boolean;\r\n /** Change handler */\r\n onChange?: (checked: boolean) => void;\r\n /** Label text */\r\n label?: string;\r\n /** Value (for radio groups) */\r\n value?: string;\r\n /** Name (for radio groups) */\r\n name?: string;\r\n /** Disabled state */\r\n disabled?: boolean;\r\n /** Indeterminate state (checkbox only) */\r\n indeterminate?: boolean;\r\n /** Size variant */\r\n size?: \"small\" | \"medium\";\r\n /** Custom className */\r\n className?: string;\r\n}\r\n\r\nexport const Checkbox = memo(\r\n forwardRef<HTMLInputElement, CheckboxProps>(function Checkbox(\r\n {\r\n type = \"checkbox\",\r\n checked = false,\r\n onChange,\r\n label,\r\n value,\r\n name,\r\n disabled = false,\r\n indeterminate = false,\r\n size = \"medium\",\r\n className = \"\",\r\n },\r\n ref,\r\n ): React.ReactElement {\r\n const { tokens, theme } = useTheme();\r\n const isTransparentTheme =\r\n theme === \"transparent\" ||\r\n theme === \"transparent-bold\" ||\r\n theme === \"transparent-minimal\";\r\n const transparentDefault = tokens.colors.interactive.transparentDefault;\r\n const [focused, setFocused] = useState(false);\r\n const [hovered, setHovered] = useState(false);\r\n const id = useId();\r\n\r\n // WCAG 2.5.8: minimum 24×24 CSS px target size\r\n const sizeConfig = {\r\n small: { boxSize: 18, iconSize: 12, targetSize: 24 },\r\n medium: { boxSize: 20, iconSize: 14, targetSize: 24 },\r\n };\r\n const config = sizeConfig[size];\r\n\r\n const isRadio = type === \"radio\";\r\n const borderRadius = isRadio ? \"50%\" : tokens.borderRadius.md;\r\n\r\n const boxStyle: React.CSSProperties = {\r\n width: config.boxSize,\r\n height: config.boxSize,\r\n border: `${tokens.borders.width.thick} solid ${checked ? tokens.colors.accent.primary : hovered && !disabled ? `${tokens.colors.accent.primary}60` : tokens.colors.border.muted}`,\r\n borderRadius,\r\n backgroundColor: checked\r\n ? tokens.colors.accent.primary\r\n : isTransparentTheme && transparentDefault\r\n ? transparentDefault\r\n : \"transparent\",\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n justifyContent: \"center\",\r\n transition: `${tokens.animation.normal}, opacity ${tokens.animation.duration.normal}ms ${tokens.animation.easing.default}`,\r\n flexShrink: 0,\r\n boxShadow: checked\r\n ? `0 0 12px ${tokens.colors.accent.primary}40`\r\n : focused\r\n ? tokens.borders.focusRing.subtle\r\n : \"none\",\r\n transform: checked\r\n ? \"scale(1)\"\r\n : hovered && !disabled\r\n ? \"scale(1.05)\"\r\n : \"scale(1)\",\r\n };\r\n\r\n // Check mark for checkbox\r\n const checkIcon = (\r\n <svg\r\n width={config.iconSize}\r\n height={config.iconSize}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"none\"\r\n stroke=\"#ffffff\"\r\n strokeWidth=\"3\"\r\n strokeLinecap=\"round\"\r\n strokeLinejoin=\"round\"\r\n >\r\n {indeterminate ? (\r\n <line x1=\"5\" y1=\"12\" x2=\"19\" y2=\"12\" />\r\n ) : (\r\n <polyline points=\"20 6 9 17 4 12\" />\r\n )}\r\n </svg>\r\n );\r\n\r\n // Dot for radio\r\n const radioIcon = (\r\n <div\r\n style={{\r\n width: config.iconSize - 4,\r\n height: config.iconSize - 4,\r\n borderRadius: \"50%\",\r\n backgroundColor: \"#ffffff\",\r\n }}\r\n />\r\n );\r\n\r\n return (\r\n <label\r\n htmlFor={id}\r\n className={classNames(\"zendir-checkbox\", className)}\r\n onMouseEnter={() => setHovered(true)}\r\n onMouseLeave={() => setHovered(false)}\r\n style={{\r\n display: \"inline-flex\",\r\n alignItems: \"center\",\r\n gap: tokens.spacing.sm,\r\n cursor: disabled ? \"not-allowed\" : \"pointer\",\r\n opacity: disabled ? 0.5 : 1,\r\n }}\r\n >\r\n {/* Touch target wrapper — ensures WCAG 2.5.8 24px minimum */}\r\n <div\r\n style={{\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n justifyContent: \"center\",\r\n minWidth: config.targetSize,\r\n minHeight: config.targetSize,\r\n }}\r\n >\r\n <div style={boxStyle}>\r\n {checked && (isRadio ? radioIcon : checkIcon)}\r\n </div>\r\n </div>\r\n\r\n <input\r\n ref={ref}\r\n type={type}\r\n id={id}\r\n name={name}\r\n value={value}\r\n checked={checked}\r\n disabled={disabled}\r\n onChange={(e) => onChange?.(e.target.checked)}\r\n onFocus={() => setFocused(true)}\r\n onBlur={() => setFocused(false)}\r\n style={{\r\n position: \"absolute\",\r\n opacity: 0,\r\n width: 0,\r\n height: 0,\r\n }}\r\n />\r\n\r\n {label && (\r\n <span\r\n style={{\r\n fontSize: \"0.875rem\",\r\n color: tokens.colors.text.primary,\r\n transition: tokens.animation.fast,\r\n }}\r\n >\r\n {label}\r\n </span>\r\n )}\r\n </label>\r\n );\r\n }),\r\n);\r\n\r\nexport default Checkbox;\r\n"],"mappings":"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"}
1
+ {"version":3,"file":"Checkbox.js","names":[],"sources":["../../../../src/react/core/inputs/Checkbox.tsx"],"sourcesContent":["/**\r\n * @zendir/ui - Checkbox Component\r\n *\r\n * Checkbox and Radio button following Astro UX Design System with Zendir purple accents.\r\n *\r\n * Astro UX Compliance:\r\n * - Clear visual states\r\n * - Accessible keyboard support\r\n * - Indeterminate state for checkbox\r\n *\r\n * Zendir Enhancements:\r\n * - Purple accent when checked\r\n * - Glow effect on focus\r\n * - Smooth spring animation\r\n *\r\n * @example\r\n * ```tsx\r\n * <Checkbox checked={agreed} onChange={setAgreed} label=\"I agree to terms\" />\r\n * <Checkbox type=\"radio\" name=\"priority\" value=\"high\" label=\"High Priority\" />\r\n * ```\r\n */\r\n\r\nimport React, { memo, forwardRef, useId, useState } from \"react\";\r\nimport { useTheme } from \"../../theme\";\r\nimport { classNames } from \"../../utils\";\r\n\r\nexport interface CheckboxProps {\r\n /** Checkbox or radio */\r\n type?: \"checkbox\" | \"radio\";\r\n /** Checked state */\r\n checked?: boolean;\r\n /** Change handler */\r\n onChange?: (checked: boolean) => void;\r\n /** Label text */\r\n label?: string;\r\n /** Value (for radio groups) */\r\n value?: string;\r\n /** Name (for radio groups) */\r\n name?: string;\r\n /** Disabled state */\r\n disabled?: boolean;\r\n /** Indeterminate state (checkbox only) */\r\n indeterminate?: boolean;\r\n /** Size variant */\r\n size?: \"small\" | \"medium\";\r\n /** Custom className */\r\n className?: string;\r\n}\r\n\r\nexport const Checkbox = memo(\r\n forwardRef<HTMLInputElement, CheckboxProps>(function Checkbox(\r\n {\r\n type = \"checkbox\",\r\n checked = false,\r\n onChange,\r\n label,\r\n value,\r\n name,\r\n disabled = false,\r\n indeterminate = false,\r\n size = \"medium\",\r\n className = \"\",\r\n },\r\n ref,\r\n ): React.ReactElement {\r\n const { tokens, theme } = useTheme();\r\n const isTransparentTheme =\r\n theme === \"transparent\" ||\r\n theme === \"transparent-bold\" ||\r\n theme === \"transparent-minimal\";\r\n const transparentDefault = tokens.colors.interactive.transparentDefault;\r\n const [focused, setFocused] = useState(false);\r\n const [hovered, setHovered] = useState(false);\r\n const id = useId();\r\n\r\n // WCAG 2.5.8: minimum 24×24 CSS px target size\r\n const sizeConfig = {\r\n small: { boxSize: 18, iconSize: 12, targetSize: 24 },\r\n medium: { boxSize: 20, iconSize: 14, targetSize: 24 },\r\n };\r\n const config = sizeConfig[size];\r\n\r\n const isRadio = type === \"radio\";\r\n const borderRadius = isRadio ? \"50%\" : tokens.borderRadius.md;\r\n\r\n const boxStyle: React.CSSProperties = {\r\n width: config.boxSize,\r\n height: config.boxSize,\r\n // Unchecked state uses border.default, not border.muted: the muted\r\n // token is a hairline that themes may set equal to an elevated surface\r\n // colour (nebula does), which makes the box invisible inside panels.\r\n // A control outline is an affordance and needs the stronger token.\r\n border: `${tokens.borders.width.thick} solid ${checked ? tokens.colors.accent.primary : hovered && !disabled ? `${tokens.colors.accent.primary}60` : tokens.colors.border.default}`,\r\n borderRadius,\r\n backgroundColor: checked\r\n ? tokens.colors.accent.primary\r\n : isTransparentTheme && transparentDefault\r\n ? transparentDefault\r\n : \"transparent\",\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n justifyContent: \"center\",\r\n transition: `${tokens.animation.normal}, opacity ${tokens.animation.duration.normal}ms ${tokens.animation.easing.default}`,\r\n flexShrink: 0,\r\n boxShadow: checked\r\n ? `0 0 12px ${tokens.colors.accent.primary}40`\r\n : focused\r\n ? tokens.borders.focusRing.subtle\r\n : \"none\",\r\n transform: checked\r\n ? \"scale(1)\"\r\n : hovered && !disabled\r\n ? \"scale(1.05)\"\r\n : \"scale(1)\",\r\n };\r\n\r\n // Check mark for checkbox\r\n const checkIcon = (\r\n <svg\r\n width={config.iconSize}\r\n height={config.iconSize}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"none\"\r\n stroke=\"#ffffff\"\r\n strokeWidth=\"3\"\r\n strokeLinecap=\"round\"\r\n strokeLinejoin=\"round\"\r\n >\r\n {indeterminate ? (\r\n <line x1=\"5\" y1=\"12\" x2=\"19\" y2=\"12\" />\r\n ) : (\r\n <polyline points=\"20 6 9 17 4 12\" />\r\n )}\r\n </svg>\r\n );\r\n\r\n // Dot for radio\r\n const radioIcon = (\r\n <div\r\n style={{\r\n width: config.iconSize - 4,\r\n height: config.iconSize - 4,\r\n borderRadius: \"50%\",\r\n backgroundColor: \"#ffffff\",\r\n }}\r\n />\r\n );\r\n\r\n return (\r\n <label\r\n htmlFor={id}\r\n className={classNames(\"zendir-checkbox\", className)}\r\n onMouseEnter={() => setHovered(true)}\r\n onMouseLeave={() => setHovered(false)}\r\n style={{\r\n display: \"inline-flex\",\r\n alignItems: \"center\",\r\n gap: tokens.spacing.sm,\r\n cursor: disabled ? \"not-allowed\" : \"pointer\",\r\n opacity: disabled ? 0.5 : 1,\r\n }}\r\n >\r\n {/* Touch target wrapper — ensures WCAG 2.5.8 24px minimum */}\r\n <div\r\n style={{\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n justifyContent: \"center\",\r\n minWidth: config.targetSize,\r\n minHeight: config.targetSize,\r\n }}\r\n >\r\n <div style={boxStyle}>\r\n {checked && (isRadio ? radioIcon : checkIcon)}\r\n </div>\r\n </div>\r\n\r\n <input\r\n ref={ref}\r\n type={type}\r\n id={id}\r\n name={name}\r\n value={value}\r\n checked={checked}\r\n disabled={disabled}\r\n onChange={(e) => onChange?.(e.target.checked)}\r\n onFocus={() => setFocused(true)}\r\n onBlur={() => setFocused(false)}\r\n style={{\r\n position: \"absolute\",\r\n opacity: 0,\r\n width: 0,\r\n height: 0,\r\n }}\r\n />\r\n\r\n {label && (\r\n <span\r\n style={{\r\n fontSize: \"0.875rem\",\r\n color: tokens.colors.text.primary,\r\n transition: tokens.animation.fast,\r\n }}\r\n >\r\n {label}\r\n </span>\r\n )}\r\n </label>\r\n );\r\n }),\r\n);\r\n\r\nexport default Checkbox;\r\n"],"mappings":"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"}
@@ -149,7 +149,8 @@ var HexRow = memo(function HexRow({ offset, bytes, bytesPerRow, byteGrouping, of
149
149
  borderBottom: isCursor ? `2px solid ${tokens.colors.status.normal}` : borderBottom,
150
150
  borderRadius: tokens.borderRadius.sm,
151
151
  userSelect: "none",
152
- transition: "background 0.1s ease"
152
+ transition: "background 0.1s ease",
153
+ whiteSpace: "pre"
153
154
  },
154
155
  children: isValid ? byteToHex(byte) : " "
155
156
  }, `hex-${i}`));
@@ -1 +1 @@
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 &#x25B2;\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 &#x25BC;\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
+ {"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 // The two-space filler in a partial final row must keep its full\r\n // two-character width — collapsed whitespace shrinks the pad cells\r\n // and shifts the ASCII column out of alignment on the last line.\r\n whiteSpace: \"pre\",\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 &#x25B2;\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 &#x25BC;\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,5 +1,5 @@
1
1
  //#region src/shaders/atmosphere.frag
2
- var atmosphere_default = "uniform vec3 uSunDirection;\n\nvarying vec3 vNormal;\nvarying vec3 vPosition;\nvarying vec3 vWorldPosition;\n\nvoid main() {\n // Base atmospheric scattering\n float viewIntensity = pow(0.65 - dot(vNormal, vec3(0.0, 0.0, 1.0)), 2.0);\n \n // Sun-facing glow (brighter on the side facing the sun)\n vec3 worldNormal = normalize(vWorldPosition);\n float sunFacing = dot(worldNormal, normalize(uSunDirection));\n float sunGlow = max(0.0, sunFacing) * 0.6 + 0.4; // 0.4-1.0 range\n \n // Terminator enhancement (glow at day/night boundary)\n float terminator = 1.0 - abs(sunFacing);\n float terminatorGlow = pow(terminator, 3.0) * 0.3;\n \n // Combine effects\n float totalIntensity = viewIntensity * sunGlow + terminatorGlow;\n \n // Atmosphere color (blue with slight orange on sun side)\n vec3 dayColor = vec3(0.3, 0.6, 1.0);\n vec3 sunsetColor = vec3(1.0, 0.5, 0.2);\n vec3 atmosphereColor = mix(dayColor, sunsetColor, pow(max(0.0, terminator), 4.0) * 0.3);\n \n gl_FragColor = vec4(atmosphereColor, 1.0) * totalIntensity;\n}\n";
2
+ var atmosphere_default = "uniform vec3 uSunDirection;\r\n\r\nvarying vec3 vNormal;\r\nvarying vec3 vPosition;\r\nvarying vec3 vWorldPosition;\r\n\r\nvoid main() {\r\n // Base atmospheric scattering\r\n float viewIntensity = pow(0.65 - dot(vNormal, vec3(0.0, 0.0, 1.0)), 2.0);\r\n \r\n // Sun-facing glow (brighter on the side facing the sun)\r\n vec3 worldNormal = normalize(vWorldPosition);\r\n float sunFacing = dot(worldNormal, normalize(uSunDirection));\r\n float sunGlow = max(0.0, sunFacing) * 0.6 + 0.4; // 0.4-1.0 range\r\n \r\n // Terminator enhancement (glow at day/night boundary)\r\n float terminator = 1.0 - abs(sunFacing);\r\n float terminatorGlow = pow(terminator, 3.0) * 0.3;\r\n \r\n // Combine effects\r\n float totalIntensity = viewIntensity * sunGlow + terminatorGlow;\r\n \r\n // Atmosphere color (blue with slight orange on sun side)\r\n vec3 dayColor = vec3(0.3, 0.6, 1.0);\r\n vec3 sunsetColor = vec3(1.0, 0.5, 0.2);\r\n vec3 atmosphereColor = mix(dayColor, sunsetColor, pow(max(0.0, terminator), 4.0) * 0.3);\r\n \r\n gl_FragColor = vec4(atmosphereColor, 1.0) * totalIntensity;\r\n}\r\n";
3
3
  //#endregion
4
4
  export { atmosphere_default as default };
5
5
 
@@ -1 +1 @@
1
- {"version":3,"file":"atmosphere.frag.js","names":[],"sources":["../../src/shaders/atmosphere.frag"],"sourcesContent":["export default \"uniform vec3 uSunDirection;\\n\\nvarying vec3 vNormal;\\nvarying vec3 vPosition;\\nvarying vec3 vWorldPosition;\\n\\nvoid main() {\\n // Base atmospheric scattering\\n float viewIntensity = pow(0.65 - dot(vNormal, vec3(0.0, 0.0, 1.0)), 2.0);\\n \\n // Sun-facing glow (brighter on the side facing the sun)\\n vec3 worldNormal = normalize(vWorldPosition);\\n float sunFacing = dot(worldNormal, normalize(uSunDirection));\\n float sunGlow = max(0.0, sunFacing) * 0.6 + 0.4; // 0.4-1.0 range\\n \\n // Terminator enhancement (glow at day/night boundary)\\n float terminator = 1.0 - abs(sunFacing);\\n float terminatorGlow = pow(terminator, 3.0) * 0.3;\\n \\n // Combine effects\\n float totalIntensity = viewIntensity * sunGlow + terminatorGlow;\\n \\n // Atmosphere color (blue with slight orange on sun side)\\n vec3 dayColor = vec3(0.3, 0.6, 1.0);\\n vec3 sunsetColor = vec3(1.0, 0.5, 0.2);\\n vec3 atmosphereColor = mix(dayColor, sunsetColor, pow(max(0.0, terminator), 4.0) * 0.3);\\n \\n gl_FragColor = vec4(atmosphereColor, 1.0) * totalIntensity;\\n}\\n\";"],"mappings":";AAAA,IAAA,qBAAe"}
1
+ {"version":3,"file":"atmosphere.frag.js","names":[],"sources":["../../src/shaders/atmosphere.frag"],"sourcesContent":["export default \"uniform vec3 uSunDirection;\\r\\n\\r\\nvarying vec3 vNormal;\\r\\nvarying vec3 vPosition;\\r\\nvarying vec3 vWorldPosition;\\r\\n\\r\\nvoid main() {\\r\\n // Base atmospheric scattering\\r\\n float viewIntensity = pow(0.65 - dot(vNormal, vec3(0.0, 0.0, 1.0)), 2.0);\\r\\n \\r\\n // Sun-facing glow (brighter on the side facing the sun)\\r\\n vec3 worldNormal = normalize(vWorldPosition);\\r\\n float sunFacing = dot(worldNormal, normalize(uSunDirection));\\r\\n float sunGlow = max(0.0, sunFacing) * 0.6 + 0.4; // 0.4-1.0 range\\r\\n \\r\\n // Terminator enhancement (glow at day/night boundary)\\r\\n float terminator = 1.0 - abs(sunFacing);\\r\\n float terminatorGlow = pow(terminator, 3.0) * 0.3;\\r\\n \\r\\n // Combine effects\\r\\n float totalIntensity = viewIntensity * sunGlow + terminatorGlow;\\r\\n \\r\\n // Atmosphere color (blue with slight orange on sun side)\\r\\n vec3 dayColor = vec3(0.3, 0.6, 1.0);\\r\\n vec3 sunsetColor = vec3(1.0, 0.5, 0.2);\\r\\n vec3 atmosphereColor = mix(dayColor, sunsetColor, pow(max(0.0, terminator), 4.0) * 0.3);\\r\\n \\r\\n gl_FragColor = vec4(atmosphereColor, 1.0) * totalIntensity;\\r\\n}\\r\\n\";"],"mappings":";AAAA,IAAA,qBAAe"}
@@ -1,5 +1,5 @@
1
1
  //#region src/shaders/atmosphere.vert
2
- var atmosphere_default = "varying vec3 vNormal;\nvarying vec3 vPosition;\nvarying vec3 vWorldPosition;\n\nvoid main() {\n vNormal = normalize(mat3(modelViewMatrix) * normal);\n vPosition = position;\n vWorldPosition = (modelMatrix * vec4(position, 1.0)).xyz;\n gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\n}\n";
2
+ var atmosphere_default = "varying vec3 vNormal;\r\nvarying vec3 vPosition;\r\nvarying vec3 vWorldPosition;\r\n\r\nvoid main() {\r\n vNormal = normalize(mat3(modelViewMatrix) * normal);\r\n vPosition = position;\r\n vWorldPosition = (modelMatrix * vec4(position, 1.0)).xyz;\r\n gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\r\n}\r\n";
3
3
  //#endregion
4
4
  export { atmosphere_default as default };
5
5
 
@@ -1 +1 @@
1
- {"version":3,"file":"atmosphere.vert.js","names":[],"sources":["../../src/shaders/atmosphere.vert"],"sourcesContent":["export default \"varying vec3 vNormal;\\nvarying vec3 vPosition;\\nvarying vec3 vWorldPosition;\\n\\nvoid main() {\\n vNormal = normalize(mat3(modelViewMatrix) * normal);\\n vPosition = position;\\n vWorldPosition = (modelMatrix * vec4(position, 1.0)).xyz;\\n gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\\n}\\n\";"],"mappings":";AAAA,IAAA,qBAAe"}
1
+ {"version":3,"file":"atmosphere.vert.js","names":[],"sources":["../../src/shaders/atmosphere.vert"],"sourcesContent":["export default \"varying vec3 vNormal;\\r\\nvarying vec3 vPosition;\\r\\nvarying vec3 vWorldPosition;\\r\\n\\r\\nvoid main() {\\r\\n vNormal = normalize(mat3(modelViewMatrix) * normal);\\r\\n vPosition = position;\\r\\n vWorldPosition = (modelMatrix * vec4(position, 1.0)).xyz;\\r\\n gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\\r\\n}\\r\\n\";"],"mappings":";AAAA,IAAA,qBAAe"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zendir/ui",
3
- "version": "0.6.1",
3
+ "version": "0.6.3",
4
4
  "description": "React UI components for space operations, built on the Astro UX Design System",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",