@zendir/ui 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -0
- package/dist/index.js +67 -62
- package/dist/index.js.map +1 -1
- package/dist/react/3d/ZenSpace3D.js +3 -2
- package/dist/react/3d/ZenSpace3DShaders.js +478 -0
- package/dist/react/3d/ZenSpace3DShaders.js.map +1 -0
- package/dist/react/3d/capturePngAnalysis.js +42 -0
- package/dist/react/3d/capturePngAnalysis.js.map +1 -0
- package/dist/react/3d/index.js +57 -0
- package/dist/react/3d/index.js.map +1 -0
- package/dist/react/3d/threeLoader.js +4 -0
- package/dist/react/3d/threeLoader.js.map +1 -1
- package/dist/react/astro/ClassificationBanner.js +1 -1
- package/dist/react/astro/GlobalStatusBar.js +1 -1
- package/dist/react/astro/MissionClock.js +8 -5
- package/dist/react/astro/MissionClock.js.map +1 -1
- package/dist/react/astro/MonitoringIcon.js +1 -1
- package/dist/react/astro/Notification.js +1 -1
- package/dist/react/astro/Progress.js +1 -1
- package/dist/react/astro/SimulationControls.js +1 -1
- package/dist/react/astro/StatusIndicator.js +1 -1
- package/dist/react/astro/UnifiedTimeline.js +1 -1
- package/dist/react/astro/index.js +34 -0
- package/dist/react/astro/index.js.map +1 -0
- package/dist/react/cards/AccessCard.js +410 -0
- package/dist/react/cards/AccessCard.js.map +1 -0
- package/dist/react/cards/OrbitCard.js +372 -0
- package/dist/react/cards/OrbitCard.js.map +1 -0
- package/dist/react/cards/SpacecraftCard.js +941 -0
- package/dist/react/cards/SpacecraftCard.js.map +1 -0
- package/dist/react/cards/TelemetryCard.js +742 -0
- package/dist/react/cards/TelemetryCard.js.map +1 -0
- package/dist/react/cards/TelemetryStreamCard.js +309 -0
- package/dist/react/cards/TelemetryStreamCard.js.map +1 -0
- package/dist/react/cards/index.js +13 -0
- package/dist/react/cards/index.js.map +1 -0
- package/dist/react/charts/GroundTrackMap.js +1 -1
- package/dist/react/charts/GroundTrackMapLeaflet.js +1 -1
- package/dist/react/charts/index.js +104 -0
- package/dist/react/charts/index.js.map +1 -0
- package/dist/react/charts/unified/AstroChart.js +1 -1
- package/dist/react/charts/unified/PowerOverviewChart.js +488 -0
- package/dist/react/charts/unified/PowerOverviewChart.js.map +1 -0
- package/dist/react/charts/unified/domain.js +3168 -0
- package/dist/react/charts/unified/domain.js.map +1 -0
- package/dist/react/charts/unified/generators.js +518 -0
- package/dist/react/charts/unified/generators.js.map +1 -0
- package/dist/react/charts/unified/presets.js +1000 -0
- package/dist/react/charts/unified/presets.js.map +1 -0
- package/dist/react/charts/unified/sync.js +219 -0
- package/dist/react/charts/unified/sync.js.map +1 -0
- package/dist/react/charts/unified/theme.js +331 -4
- package/dist/react/charts/unified/theme.js.map +1 -1
- package/dist/react/charts/unified/useChartStream.js +226 -0
- package/dist/react/charts/unified/useChartStream.js.map +1 -0
- package/dist/react/chatgpt/index.js +163 -0
- package/dist/react/chatgpt/index.js.map +1 -0
- package/dist/react/core/data/DataTable.js +1 -1
- package/dist/react/core/data/DataValue.js +1 -1
- package/dist/react/core/display/AstroIcon.js +1 -1
- package/dist/react/core/display/Badge.js +1 -1
- package/dist/react/core/display/CardHeader.js +1 -1
- package/dist/react/core/display/Container.js +1 -1
- package/dist/react/core/display/GlassCard.js +1 -1
- package/dist/react/core/display/HeaderIconWithStatus.js +1 -1
- package/dist/react/core/feedback/Dialog.js +24 -21
- package/dist/react/core/feedback/Dialog.js.map +1 -1
- package/dist/react/core/feedback/Toast.js +1 -1
- package/dist/react/core/index.d.ts +4 -0
- package/dist/react/core/index.js +166 -0
- package/dist/react/core/index.js.map +1 -0
- package/dist/react/core/inputs/Button.js +1 -1
- package/dist/react/core/inputs/Checkbox.js +1 -1
- package/dist/react/core/inputs/Input.js +1 -1
- package/dist/react/core/inputs/NumberInput.js +1 -1
- package/dist/react/core/inputs/PinInput.js +1 -1
- package/dist/react/core/inputs/Select.js +1 -1
- package/dist/react/core/inputs/Toggle.js +1 -1
- package/dist/react/core/layout/Box.js +1 -1
- package/dist/react/core/layout/Flex.js +1 -1
- package/dist/react/core/layout/Grid.js +1 -1
- package/dist/react/core/layout/Stack.js +1 -1
- package/dist/react/core/navigation/AppBar.js +1 -1
- package/dist/react/core/navigation/Pagination.js +1 -1
- package/dist/react/core/navigation/SideNav.js +1 -1
- package/dist/react/core/navigation/SideNav.js.map +1 -1
- package/dist/react/core/navigation/Tabs.js +1 -1
- package/dist/react/core/overlays/SidePanel.js +1 -1
- package/dist/react/core/overlays/Tooltip.js +1 -1
- package/dist/react/core/widgets/Capture.js +1 -1
- package/dist/react/core/widgets/ChatPanel.js +1 -1
- package/dist/react/core/widgets/ColorPickerPanel.js +1 -1
- package/dist/react/core/widgets/ConnectionForm.js +1 -1
- package/dist/react/core/widgets/FileExplorer.js +1 -1
- package/dist/react/core/widgets/ImageFrame.d.ts +51 -0
- package/dist/react/core/widgets/ImageFrame.js +201 -0
- package/dist/react/core/widgets/ImageFrame.js.map +1 -0
- package/dist/react/core/widgets/ImageGallery.js +1 -1
- package/dist/react/core/widgets/ImagePanel.d.ts +43 -0
- package/dist/react/core/widgets/ImagePanel.js +105 -0
- package/dist/react/core/widgets/ImagePanel.js.map +1 -0
- package/dist/react/core/widgets/MessageStream.js +1 -1
- package/dist/react/core/widgets/MissionCalendar.js +1 -1
- package/dist/react/hooks/useCompactMode.js +1 -1
- package/dist/react/index.d.ts +2 -2
- package/dist/react/panels/index.js +8 -0
- package/dist/react/panels/index.js.map +1 -0
- package/dist/react/theme/config.js +30 -0
- package/dist/react/theme/config.js.map +1 -0
- package/dist/react/theme/index.js +19 -0
- package/dist/react/theme/index.js.map +1 -0
- package/dist/react/utils/index.js +8 -0
- package/dist/react/utils/index.js.map +1 -1
- package/dist/react.js +67 -62
- package/dist/react.js.map +1 -1
- package/package.json +46 -38
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ImageFrame.js","sources":["../../../../src/react/core/widgets/ImageFrame.tsx"],"sourcesContent":["/**\r\n * @zendir/ui - ImageFrame Component\r\n *\r\n * Renders a single-channel data frame (e.g. a CCD/sensor readout) as a\r\n * false-colour image on a <canvas>. Purely presentational: hand it an\r\n * array of samples and it paints them — it has no knowledge of where\r\n * the data came from (engine, file, websocket, console). This is the\r\n * reusable surface that callers \"dump\" a frame into.\r\n *\r\n * Pipeline per sample: normalise against a fixed ceiling (`maxADU`) to\r\n * an intensity in [0,1], run that through a colormap, pack RGBA. The\r\n * default `cividis` ramp (dark teal → yellow) reads well on dark UI.\r\n *\r\n * The canvas's native resolution is the frame's width×height, so each\r\n * sample is exactly one pixel; CSS scales it up with\r\n * `image-rendering: pixelated`, keeping small sensor frames crisp.\r\n *\r\n * Astro UX Compliance:\r\n * - Themed surface, border radius, and typography via `useTheme`.\r\n * - Loading and empty states with friendly messaging.\r\n * - Monospace raw-value grid for analysis.\r\n *\r\n * @example\r\n * ```tsx\r\n * <ImageFrame\r\n * data={ccdSamples} // number[] length = width*height\r\n * width={28}\r\n * maxADU={65535}\r\n * title=\"CCD capture\"\r\n * showValues\r\n * />\r\n * ```\r\n */\r\n\r\nimport React, { memo, useEffect, useMemo, useRef } from 'react';\r\nimport { useTheme } from '../../theme';\r\nimport { classNames, lerp } from '../../utils';\r\n\r\n// =============================================================================\r\n// Types\r\n// =============================================================================\r\n\r\n/** Built-in colormap names. */\r\nexport type ImageColormap = 'cividis' | 'gray';\r\n\r\nexport interface ImageFrameProps {\r\n /** Flat, row-major single-channel samples. Length must equal width*height. */\r\n data?: number[];\r\n /**\r\n * Pre-decoded RGBA pixels (length = width*height*4). When provided this\r\n * is painted directly and `data` / `maxADU` / `colormap` are ignored.\r\n * Escape hatch for callers that already have an image buffer.\r\n */\r\n pixels?: Uint8ClampedArray;\r\n /** Frame width in pixels. */\r\n width: number;\r\n /** Frame height in pixels. Defaults to `width` (square). */\r\n height?: number;\r\n /** Saturation ceiling used to normalise `data` → [0,1]. Default 65535. */\r\n maxADU?: number;\r\n /** Colormap applied to normalised intensities. Default 'cividis'. */\r\n colormap?: ImageColormap;\r\n /** Show the raw sample values as a grid below the image. Default false. */\r\n showValues?: boolean;\r\n /** Optional title shown above the image. */\r\n title?: string;\r\n /** Loading state (skeleton). */\r\n loading?: boolean;\r\n /** Message shown when there is no frame to display. */\r\n emptyMessage?: string;\r\n /** Custom class name. */\r\n className?: string;\r\n}\r\n\r\n// =============================================================================\r\n// Pure helpers (exported for testing / reuse)\r\n// =============================================================================\r\n\r\nconst DEFAULT_MAX_ADU = 65535;\r\n\r\n/** Two-stop colormaps: intensity t in [0,1] → [r,g,b] (0..255). */\r\nconst COLORMAPS: Record<ImageColormap, (t: number) => [number, number, number]> = {\r\n // Dark teal → yellow; a cividis-style ramp that reads well on dark UI.\r\n cividis: (t) => [lerp(42, 237, t), lerp(123, 222, t), lerp(155, 83, t)],\r\n gray: (t) => {\r\n const v = lerp(0, 255, t);\r\n return [v, v, v];\r\n },\r\n};\r\n\r\n/**\r\n * decodeImage — pure transform: single-channel samples → canvas-ready\r\n * RGBA. Fixed-ceiling normalisation (`sample / maxADU`) so the output\r\n * reflects the real signal level relative to saturation rather than\r\n * rescaling per frame. Returns null on invalid input.\r\n */\r\nexport function decodeImage(opts: {\r\n data: number[];\r\n width: number;\r\n height?: number;\r\n maxADU?: number;\r\n colormap?: ImageColormap;\r\n}): { width: number; height: number; pixels: Uint8ClampedArray } | null {\r\n const { data, width, height, maxADU = DEFAULT_MAX_ADU, colormap = 'cividis' } = opts;\r\n if (!Array.isArray(data)) return null;\r\n if (!Number.isFinite(width) || width <= 0) return null;\r\n const W = Math.floor(width);\r\n const H = Number.isFinite(height) && (height as number) > 0 ? Math.floor(height as number) : W;\r\n if (data.length !== W * H) return null;\r\n\r\n const ramp = COLORMAPS[colormap] || COLORMAPS.cividis;\r\n const ceiling = Number.isFinite(maxADU) && maxADU > 0 ? maxADU : DEFAULT_MAX_ADU;\r\n\r\n const pixels = new Uint8ClampedArray(W * H * 4);\r\n for (let i = 0; i < W * H; i++) {\r\n const raw = data[i];\r\n // sample / ceiling (lerp clamps t to [0,1]); non-finite reads as 0.\r\n const t = Number.isFinite(raw) ? raw / ceiling : 0;\r\n const [r, g, b] = ramp(t);\r\n const off = i * 4;\r\n pixels[off + 0] = r;\r\n pixels[off + 1] = g;\r\n pixels[off + 2] = b;\r\n pixels[off + 3] = 255;\r\n }\r\n return { width: W, height: H, pixels };\r\n}\r\n\r\n/** Pretty-print samples as a width-padded grid for the values view. */\r\nfunction formatValues(data: number[], width: number, height: number, maxADU: number): string {\r\n if (width * height !== data.length) return data.join(', ');\r\n const colW = Math.max(3, String(Math.floor(maxADU) || 1).length);\r\n const rows: string[] = [];\r\n for (let y = 0; y < height; y++) {\r\n const cells: string[] = [];\r\n for (let x = 0; x < width; x++) {\r\n cells.push(String(data[y * width + x]).padStart(colW, ' '));\r\n }\r\n rows.push(cells.join(' '));\r\n }\r\n return rows.join('\\n');\r\n}\r\n\r\n// =============================================================================\r\n// Component\r\n// =============================================================================\r\n\r\nexport const ImageFrame = memo(function ImageFrame({\r\n data,\r\n pixels,\r\n width,\r\n height,\r\n maxADU = DEFAULT_MAX_ADU,\r\n colormap = 'cividis',\r\n showValues = false,\r\n title,\r\n loading = false,\r\n emptyMessage = 'No frame to display.',\r\n className = '',\r\n}: ImageFrameProps): React.ReactElement {\r\n const { tokens } = useTheme();\r\n const canvasRef = useRef<HTMLCanvasElement>(null);\r\n\r\n const H = Number.isFinite(height) && (height as number) > 0 ? Math.floor(height as number) : Math.floor(width);\r\n const W = Math.floor(width);\r\n\r\n // Decode once per frame/option change. A pre-decoded `pixels` buffer\r\n // wins; otherwise decode `data` through the colormap.\r\n const decoded = useMemo(() => {\r\n if (pixels instanceof Uint8ClampedArray && pixels.length === W * H * 4) {\r\n return { width: W, height: H, pixels };\r\n }\r\n if (Array.isArray(data)) {\r\n return decodeImage({ data, width: W, height: H, maxADU, colormap });\r\n }\r\n return null;\r\n }, [data, pixels, W, H, maxADU, colormap]);\r\n\r\n useEffect(() => {\r\n if (!decoded) return;\r\n const canvas = canvasRef.current;\r\n if (!canvas) return;\r\n canvas.width = decoded.width;\r\n canvas.height = decoded.height;\r\n const ctx = canvas.getContext('2d');\r\n if (!ctx) return;\r\n // Build via createImageData + set (rather than `new ImageData(buf,…)`)\r\n // to sidestep the lib.dom ArrayBuffer/ArrayBufferLike typing mismatch\r\n // on newer TypeScript, and it's the same cost.\r\n const img = ctx.createImageData(decoded.width, decoded.height);\r\n img.data.set(decoded.pixels);\r\n ctx.putImageData(img, 0, 0);\r\n }, [decoded]);\r\n\r\n const surfaceStyle: React.CSSProperties = {\r\n backgroundColor: tokens.colors.background.surface,\r\n borderRadius: tokens.borderRadius.lg,\r\n padding: tokens.spacing.md,\r\n color: tokens.colors.text.primary,\r\n fontFamily: tokens.typography.fontFamily.primary,\r\n display: 'flex',\r\n flexDirection: 'column',\r\n gap: tokens.spacing.sm,\r\n };\r\n\r\n // Loading state\r\n if (loading) {\r\n return (\r\n <div\r\n className={classNames('zendir-image-frame', 'loading', className)}\r\n role=\"img\"\r\n aria-busy=\"true\"\r\n aria-label=\"Loading image\"\r\n style={surfaceStyle}\r\n >\r\n {title && <FrameTitle tokens={tokens} title={title} />}\r\n <div\r\n style={{\r\n width: '100%',\r\n aspectRatio: `${W} / ${H}`,\r\n background: 'rgba(255,255,255,0.06)',\r\n borderRadius: tokens.borderRadius.sm,\r\n }}\r\n />\r\n </div>\r\n );\r\n }\r\n\r\n // Empty state\r\n if (!decoded) {\r\n return (\r\n <div\r\n className={classNames('zendir-image-frame', 'empty', className)}\r\n role=\"img\"\r\n aria-label=\"No image\"\r\n style={{ ...surfaceStyle, textAlign: 'center', color: tokens.colors.text.tertiary }}\r\n >\r\n {title && <FrameTitle tokens={tokens} title={title} />}\r\n <p style={{ margin: 0, fontSize: tokens.typography.fontSize.xs }}>{emptyMessage}</p>\r\n </div>\r\n );\r\n }\r\n\r\n return (\r\n <div\r\n className={classNames('zendir-image-frame', className)}\r\n aria-label={title || 'Image frame'}\r\n style={surfaceStyle}\r\n >\r\n {title && <FrameTitle tokens={tokens} title={title} />}\r\n\r\n <canvas\r\n ref={canvasRef}\r\n data-testid=\"image-frame-canvas\"\r\n style={{\r\n width: '100%',\r\n aspectRatio: `${decoded.width} / ${decoded.height}`,\r\n imageRendering: 'pixelated',\r\n display: 'block',\r\n background: 'rgba(0,0,0,0.4)',\r\n borderRadius: tokens.borderRadius.sm,\r\n }}\r\n />\r\n\r\n {showValues && Array.isArray(data) && (\r\n <pre\r\n data-testid=\"image-frame-values\"\r\n style={{\r\n margin: 0,\r\n padding: tokens.spacing.sm,\r\n background: 'rgba(0,0,0,0.45)',\r\n borderRadius: tokens.borderRadius.sm,\r\n color: tokens.colors.text.primary,\r\n fontSize: 10,\r\n lineHeight: 1.25,\r\n fontFamily: tokens.typography.fontFamily.mono,\r\n whiteSpace: 'pre',\r\n overflow: 'auto',\r\n maxHeight: 220,\r\n }}\r\n >\r\n {formatValues(data, decoded.width, decoded.height, maxADU)}\r\n </pre>\r\n )}\r\n </div>\r\n );\r\n});\r\n\r\n/** Small header row shared across states. */\r\nconst FrameTitle = ({ tokens, title }: { tokens: ReturnType<typeof useTheme>['tokens']; title: string }) => (\r\n <h3\r\n style={{\r\n margin: 0,\r\n fontSize: tokens.typography.fontSize.md,\r\n fontWeight: tokens.typography.fontWeight.semibold,\r\n color: tokens.colors.text.primary,\r\n }}\r\n >\r\n {title}\r\n </h3>\r\n);\r\n\r\nexport default ImageFrame;\r\n"],"names":["ImageFrame"],"mappings":";;;;AA8EA,MAAM,kBAAkB;AAGxB,MAAM,YAA4E;AAAA;AAAA,EAEhF,SAAS,CAAC,MAAM,CAAC,KAAK,IAAI,KAAK,CAAC,GAAG,KAAK,KAAK,KAAK,CAAC,GAAG,KAAK,KAAK,IAAI,CAAC,CAAC;AAAA,EACtE,MAAM,CAAC,MAAM;AACX,UAAM,IAAI,KAAK,GAAG,KAAK,CAAC;AACxB,WAAO,CAAC,GAAG,GAAG,CAAC;AAAA,EACjB;AACF;AAQO,SAAS,YAAY,MAM4C;AACtE,QAAM,EAAE,MAAM,OAAO,QAAQ,SAAS,iBAAiB,WAAW,cAAc;AAChF,MAAI,CAAC,MAAM,QAAQ,IAAI,EAAG,QAAO;AACjC,MAAI,CAAC,OAAO,SAAS,KAAK,KAAK,SAAS,EAAG,QAAO;AAClD,QAAM,IAAI,KAAK,MAAM,KAAK;AAC1B,QAAM,IAAI,OAAO,SAAS,MAAM,KAAM,SAAoB,IAAI,KAAK,MAAM,MAAgB,IAAI;AAC7F,MAAI,KAAK,WAAW,IAAI,EAAG,QAAO;AAElC,QAAM,OAAO,UAAU,QAAQ,KAAK,UAAU;AAC9C,QAAM,UAAU,OAAO,SAAS,MAAM,KAAK,SAAS,IAAI,SAAS;AAEjE,QAAM,SAAS,IAAI,kBAAkB,IAAI,IAAI,CAAC;AAC9C,WAAS,IAAI,GAAG,IAAI,IAAI,GAAG,KAAK;AAC9B,UAAM,MAAM,KAAK,CAAC;AAElB,UAAM,IAAI,OAAO,SAAS,GAAG,IAAI,MAAM,UAAU;AACjD,UAAM,CAAC,GAAG,GAAG,CAAC,IAAI,KAAK,CAAC;AACxB,UAAM,MAAM,IAAI;AAChB,WAAO,MAAM,CAAC,IAAI;AAClB,WAAO,MAAM,CAAC,IAAI;AAClB,WAAO,MAAM,CAAC,IAAI;AAClB,WAAO,MAAM,CAAC,IAAI;AAAA,EACpB;AACA,SAAO,EAAE,OAAO,GAAG,QAAQ,GAAG,OAAA;AAChC;AAGA,SAAS,aAAa,MAAgB,OAAe,QAAgB,QAAwB;AAC3F,MAAI,QAAQ,WAAW,KAAK,OAAQ,QAAO,KAAK,KAAK,IAAI;AACzD,QAAM,OAAO,KAAK,IAAI,GAAG,OAAO,KAAK,MAAM,MAAM,KAAK,CAAC,EAAE,MAAM;AAC/D,QAAM,OAAiB,CAAA;AACvB,WAAS,IAAI,GAAG,IAAI,QAAQ,KAAK;AAC/B,UAAM,QAAkB,CAAA;AACxB,aAAS,IAAI,GAAG,IAAI,OAAO,KAAK;AAC9B,YAAM,KAAK,OAAO,KAAK,IAAI,QAAQ,CAAC,CAAC,EAAE,SAAS,MAAM,GAAG,CAAC;AAAA,IAC5D;AACA,SAAK,KAAK,MAAM,KAAK,GAAG,CAAC;AAAA,EAC3B;AACA,SAAO,KAAK,KAAK,IAAI;AACvB;AAMO,MAAM,aAAa,KAAK,SAASA,YAAW;AAAA,EACjD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,SAAS;AAAA,EACT,WAAW;AAAA,EACX,aAAa;AAAA,EACb;AAAA,EACA,UAAU;AAAA,EACV,eAAe;AAAA,EACf,YAAY;AACd,GAAwC;AACtC,QAAM,EAAE,OAAA,IAAW,SAAA;AACnB,QAAM,YAAY,OAA0B,IAAI;AAEhD,QAAM,IAAI,OAAO,SAAS,MAAM,KAAM,SAAoB,IAAI,KAAK,MAAM,MAAgB,IAAI,KAAK,MAAM,KAAK;AAC7G,QAAM,IAAI,KAAK,MAAM,KAAK;AAI1B,QAAM,UAAU,QAAQ,MAAM;AAC5B,QAAI,kBAAkB,qBAAqB,OAAO,WAAW,IAAI,IAAI,GAAG;AACtE,aAAO,EAAE,OAAO,GAAG,QAAQ,GAAG,OAAA;AAAA,IAChC;AACA,QAAI,MAAM,QAAQ,IAAI,GAAG;AACvB,aAAO,YAAY,EAAE,MAAM,OAAO,GAAG,QAAQ,GAAG,QAAQ,UAAU;AAAA,IACpE;AACA,WAAO;AAAA,EACT,GAAG,CAAC,MAAM,QAAQ,GAAG,GAAG,QAAQ,QAAQ,CAAC;AAEzC,YAAU,MAAM;AACd,QAAI,CAAC,QAAS;AACd,UAAM,SAAS,UAAU;AACzB,QAAI,CAAC,OAAQ;AACb,WAAO,QAAQ,QAAQ;AACvB,WAAO,SAAS,QAAQ;AACxB,UAAM,MAAM,OAAO,WAAW,IAAI;AAClC,QAAI,CAAC,IAAK;AAIV,UAAM,MAAM,IAAI,gBAAgB,QAAQ,OAAO,QAAQ,MAAM;AAC7D,QAAI,KAAK,IAAI,QAAQ,MAAM;AAC3B,QAAI,aAAa,KAAK,GAAG,CAAC;AAAA,EAC5B,GAAG,CAAC,OAAO,CAAC;AAEZ,QAAM,eAAoC;AAAA,IACxC,iBAAiB,OAAO,OAAO,WAAW;AAAA,IAC1C,cAAc,OAAO,aAAa;AAAA,IAClC,SAAS,OAAO,QAAQ;AAAA,IACxB,OAAO,OAAO,OAAO,KAAK;AAAA,IAC1B,YAAY,OAAO,WAAW,WAAW;AAAA,IACzC,SAAS;AAAA,IACT,eAAe;AAAA,IACf,KAAK,OAAO,QAAQ;AAAA,EAAA;AAItB,MAAI,SAAS;AACX,WACE;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,WAAW,WAAW,sBAAsB,WAAW,SAAS;AAAA,QAChE,MAAK;AAAA,QACL,aAAU;AAAA,QACV,cAAW;AAAA,QACX,OAAO;AAAA,QAEN,UAAA;AAAA,UAAA,SAAS,oBAAC,YAAA,EAAW,QAAgB,MAAA,CAAc;AAAA,UACpD;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,OAAO;AAAA,gBACL,OAAO;AAAA,gBACP,aAAa,GAAG,CAAC,MAAM,CAAC;AAAA,gBACxB,YAAY;AAAA,gBACZ,cAAc,OAAO,aAAa;AAAA,cAAA;AAAA,YACpC;AAAA,UAAA;AAAA,QACF;AAAA,MAAA;AAAA,IAAA;AAAA,EAGN;AAGA,MAAI,CAAC,SAAS;AACZ,WACE;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,WAAW,WAAW,sBAAsB,SAAS,SAAS;AAAA,QAC9D,MAAK;AAAA,QACL,cAAW;AAAA,QACX,OAAO,EAAE,GAAG,cAAc,WAAW,UAAU,OAAO,OAAO,OAAO,KAAK,SAAA;AAAA,QAExE,UAAA;AAAA,UAAA,SAAS,oBAAC,YAAA,EAAW,QAAgB,MAAA,CAAc;AAAA,UACpD,oBAAC,KAAA,EAAE,OAAO,EAAE,QAAQ,GAAG,UAAU,OAAO,WAAW,SAAS,GAAA,GAAO,UAAA,aAAA,CAAa;AAAA,QAAA;AAAA,MAAA;AAAA,IAAA;AAAA,EAGtF;AAEA,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,WAAW,WAAW,sBAAsB,SAAS;AAAA,MACrD,cAAY,SAAS;AAAA,MACrB,OAAO;AAAA,MAEN,UAAA;AAAA,QAAA,SAAS,oBAAC,YAAA,EAAW,QAAgB,MAAA,CAAc;AAAA,QAEpD;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,KAAK;AAAA,YACL,eAAY;AAAA,YACZ,OAAO;AAAA,cACL,OAAO;AAAA,cACP,aAAa,GAAG,QAAQ,KAAK,MAAM,QAAQ,MAAM;AAAA,cACjD,gBAAgB;AAAA,cAChB,SAAS;AAAA,cACT,YAAY;AAAA,cACZ,cAAc,OAAO,aAAa;AAAA,YAAA;AAAA,UACpC;AAAA,QAAA;AAAA,QAGD,cAAc,MAAM,QAAQ,IAAI,KAC/B;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,eAAY;AAAA,YACZ,OAAO;AAAA,cACL,QAAQ;AAAA,cACR,SAAS,OAAO,QAAQ;AAAA,cACxB,YAAY;AAAA,cACZ,cAAc,OAAO,aAAa;AAAA,cAClC,OAAO,OAAO,OAAO,KAAK;AAAA,cAC1B,UAAU;AAAA,cACV,YAAY;AAAA,cACZ,YAAY,OAAO,WAAW,WAAW;AAAA,cACzC,YAAY;AAAA,cACZ,UAAU;AAAA,cACV,WAAW;AAAA,YAAA;AAAA,YAGZ,uBAAa,MAAM,QAAQ,OAAO,QAAQ,QAAQ,MAAM;AAAA,UAAA;AAAA,QAAA;AAAA,MAC3D;AAAA,IAAA;AAAA,EAAA;AAIR,CAAC;AAGD,MAAM,aAAa,CAAC,EAAE,QAAQ,YAC5B;AAAA,EAAC;AAAA,EAAA;AAAA,IACC,OAAO;AAAA,MACL,QAAQ;AAAA,MACR,UAAU,OAAO,WAAW,SAAS;AAAA,MACrC,YAAY,OAAO,WAAW,WAAW;AAAA,MACzC,OAAO,OAAO,OAAO,KAAK;AAAA,IAAA;AAAA,IAG3B,UAAA;AAAA,EAAA;AACH;"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { jsxs, jsx, Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { memo, useState, useEffect, useCallback } from "react";
|
|
3
|
+
import { useTheme } from "../../theme/ThemeProvider.js";
|
|
3
4
|
import { useBreakpoint, resolveResponsiveValue } from "../layout/useBreakpoint.js";
|
|
4
5
|
import { Pagination } from "../navigation/Pagination.js";
|
|
5
|
-
import { useTheme } from "../../theme/ThemeProvider.js";
|
|
6
6
|
const ImageGallery = memo(function ImageGallery2({
|
|
7
7
|
images,
|
|
8
8
|
columns = [2, 3, 4],
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { default as React } from 'react';
|
|
2
|
+
import { ImageColormap } from './ImageFrame';
|
|
3
|
+
|
|
4
|
+
export interface ImagePanelProps {
|
|
5
|
+
/** Flat, row-major single-channel samples. Length must equal width*height. */
|
|
6
|
+
data?: number[];
|
|
7
|
+
/** Pre-decoded RGBA pixels — escape hatch for callers with their own buffer. */
|
|
8
|
+
pixels?: Uint8ClampedArray;
|
|
9
|
+
/** Frame width in pixels. */
|
|
10
|
+
width: number;
|
|
11
|
+
/** Frame height in pixels. Defaults to `width` (square). */
|
|
12
|
+
height?: number;
|
|
13
|
+
/** Saturation ceiling used to normalise `data` → [0,1]. Default 65535. */
|
|
14
|
+
maxADU?: number;
|
|
15
|
+
/** Colormap applied to normalised intensities. Default 'cividis'. */
|
|
16
|
+
colormap?: ImageColormap;
|
|
17
|
+
/** Show the raw sample values as a grid below the image. Default false. */
|
|
18
|
+
showValues?: boolean;
|
|
19
|
+
/** Message shown when no frame is available. */
|
|
20
|
+
emptyMessage?: string;
|
|
21
|
+
/** Title rendered above the frame. */
|
|
22
|
+
title?: string;
|
|
23
|
+
/** True while a capture is in flight. Drives the button label + skeleton. */
|
|
24
|
+
loading?: boolean;
|
|
25
|
+
/** Error from the upstream capture. Rendered as a red banner above the frame. */
|
|
26
|
+
error?: string | null;
|
|
27
|
+
/** Click handler for the refresh button. Omit to hide the button entirely. */
|
|
28
|
+
onRefresh?: () => void;
|
|
29
|
+
/** Label for the idle refresh button. Default 'Refresh'. */
|
|
30
|
+
refreshLabel?: string;
|
|
31
|
+
/** Label while `loading` is true. Default 'Capturing…'. */
|
|
32
|
+
refreshingLabel?: string;
|
|
33
|
+
/**
|
|
34
|
+
* Disable the refresh button even when not loading — e.g. when the
|
|
35
|
+
* caller has no data source bound (no CCD selected). The button is
|
|
36
|
+
* always disabled while `loading` is true regardless of this prop.
|
|
37
|
+
*/
|
|
38
|
+
refreshDisabled?: boolean;
|
|
39
|
+
/** Extra class name applied to the outer wrapper. */
|
|
40
|
+
className?: string;
|
|
41
|
+
}
|
|
42
|
+
export declare const ImagePanel: React.NamedExoticComponent<ImagePanelProps>;
|
|
43
|
+
export default ImagePanel;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { jsxs, jsx } from "react/jsx-runtime";
|
|
2
|
+
import { memo } from "react";
|
|
3
|
+
import { useTheme } from "../../theme/ThemeProvider.js";
|
|
4
|
+
import { classNames } from "../../utils/index.js";
|
|
5
|
+
import { ImageFrame } from "./ImageFrame.js";
|
|
6
|
+
const ImagePanel = memo(function ImagePanel2({
|
|
7
|
+
// frame
|
|
8
|
+
data,
|
|
9
|
+
pixels,
|
|
10
|
+
width,
|
|
11
|
+
height,
|
|
12
|
+
maxADU,
|
|
13
|
+
colormap,
|
|
14
|
+
showValues,
|
|
15
|
+
emptyMessage,
|
|
16
|
+
// chrome
|
|
17
|
+
title,
|
|
18
|
+
loading = false,
|
|
19
|
+
error = null,
|
|
20
|
+
onRefresh,
|
|
21
|
+
refreshLabel = "Refresh",
|
|
22
|
+
refreshingLabel = "Capturing…",
|
|
23
|
+
refreshDisabled = false,
|
|
24
|
+
className = ""
|
|
25
|
+
}) {
|
|
26
|
+
const { tokens } = useTheme();
|
|
27
|
+
const buttonDisabled = loading || refreshDisabled;
|
|
28
|
+
const hasFrame = Array.isArray(data) || pixels instanceof Uint8ClampedArray;
|
|
29
|
+
const frameLoading = loading && !hasFrame;
|
|
30
|
+
return /* @__PURE__ */ jsxs(
|
|
31
|
+
"div",
|
|
32
|
+
{
|
|
33
|
+
"data-testid": "image-panel",
|
|
34
|
+
className: classNames("zendir-image-panel", className),
|
|
35
|
+
style: {
|
|
36
|
+
display: "flex",
|
|
37
|
+
flexDirection: "column",
|
|
38
|
+
gap: tokens.spacing.sm,
|
|
39
|
+
fontFamily: tokens.typography.fontFamily.primary
|
|
40
|
+
},
|
|
41
|
+
children: [
|
|
42
|
+
onRefresh && /* @__PURE__ */ jsx(
|
|
43
|
+
"button",
|
|
44
|
+
{
|
|
45
|
+
type: "button",
|
|
46
|
+
"data-testid": "image-panel-refresh",
|
|
47
|
+
onClick: onRefresh,
|
|
48
|
+
disabled: buttonDisabled,
|
|
49
|
+
"aria-busy": loading ? "true" : "false",
|
|
50
|
+
style: {
|
|
51
|
+
alignSelf: "flex-start",
|
|
52
|
+
padding: `${tokens.spacing.xs}px ${tokens.spacing.sm}px`,
|
|
53
|
+
borderRadius: tokens.borderRadius.sm,
|
|
54
|
+
border: `1px solid ${tokens.colors.border.muted}`,
|
|
55
|
+
background: buttonDisabled ? "rgba(255,255,255,0.03)" : "rgba(255,255,255,0.08)",
|
|
56
|
+
color: buttonDisabled ? tokens.colors.text.tertiary : tokens.colors.text.primary,
|
|
57
|
+
cursor: buttonDisabled ? "default" : "pointer",
|
|
58
|
+
fontSize: tokens.typography.fontSize.xs,
|
|
59
|
+
fontFamily: tokens.typography.fontFamily.primary,
|
|
60
|
+
// Make the disabled state visually consistent across browsers.
|
|
61
|
+
opacity: buttonDisabled ? 0.85 : 1
|
|
62
|
+
},
|
|
63
|
+
children: loading ? refreshingLabel : refreshLabel
|
|
64
|
+
}
|
|
65
|
+
),
|
|
66
|
+
error && /* @__PURE__ */ jsx(
|
|
67
|
+
"div",
|
|
68
|
+
{
|
|
69
|
+
"data-testid": "image-panel-error",
|
|
70
|
+
role: "alert",
|
|
71
|
+
style: {
|
|
72
|
+
color: tokens.colors.status.critical,
|
|
73
|
+
fontSize: tokens.typography.fontSize.xs,
|
|
74
|
+
background: "rgba(255, 56, 56, 0.08)",
|
|
75
|
+
border: `1px solid ${tokens.colors.status.critical}33`,
|
|
76
|
+
borderRadius: tokens.borderRadius.sm,
|
|
77
|
+
padding: `${tokens.spacing.xs}px ${tokens.spacing.sm}px`,
|
|
78
|
+
wordBreak: "break-word"
|
|
79
|
+
},
|
|
80
|
+
children: error
|
|
81
|
+
}
|
|
82
|
+
),
|
|
83
|
+
/* @__PURE__ */ jsx(
|
|
84
|
+
ImageFrame,
|
|
85
|
+
{
|
|
86
|
+
data,
|
|
87
|
+
pixels,
|
|
88
|
+
width,
|
|
89
|
+
height,
|
|
90
|
+
maxADU,
|
|
91
|
+
colormap,
|
|
92
|
+
showValues,
|
|
93
|
+
title,
|
|
94
|
+
loading: frameLoading,
|
|
95
|
+
emptyMessage
|
|
96
|
+
}
|
|
97
|
+
)
|
|
98
|
+
]
|
|
99
|
+
}
|
|
100
|
+
);
|
|
101
|
+
});
|
|
102
|
+
export {
|
|
103
|
+
ImagePanel
|
|
104
|
+
};
|
|
105
|
+
//# sourceMappingURL=ImagePanel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ImagePanel.js","sources":["../../../../src/react/core/widgets/ImagePanel.tsx"],"sourcesContent":["/**\n * @zendir/ui - ImagePanel Component\n *\n * Reusable chrome around `<ImageFrame>` for sensor/CCD-style image\n * surfaces. Adds the bits every imaging panel needs but `<ImageFrame>`\n * intentionally doesn't:\n *\n * - **Refresh button** with disabled / busy states.\n * - **Error banner** when the upstream capture failed.\n * - **Title strip + frame** in a single bordered card.\n *\n * Like `<ImageFrame>`, this component is **purely presentational** —\n * it knows nothing about where the data came from. Callers wire up the\n * I/O (engine, file, websocket) and feed:\n *\n * - `data` / `pixels` / `width` / `maxADU` / `colormap` → forwarded\n * to `<ImageFrame>` unchanged.\n * - `onRefresh` → click handler for the button.\n * - `loading` → toggles the button's \"Capturing…\" label AND the\n * frame's skeleton state when there's no frame yet.\n * - `error` → renders a red strip above the frame.\n *\n * The component handles the common refresh-disabled-while-loading\n * pattern internally so callers don't have to compute it.\n *\n * @example\n * ```tsx\n * <ImagePanel\n * title=\"CCD capture\"\n * data={samples}\n * width={28}\n * maxADU={65535}\n * loading={busy}\n * error={err}\n * onRefresh={capture}\n * refreshDisabled={!ccd}\n * emptyMessage=\"No frame captured yet.\"\n * />\n * ```\n */\n\nimport React, { memo } from 'react';\nimport { useTheme } from '../../theme';\nimport { classNames } from '../../utils';\nimport { ImageFrame, type ImageColormap } from './ImageFrame';\n\n// =============================================================================\n// Types\n// =============================================================================\n\nexport interface ImagePanelProps {\n // -- Frame inputs (forwarded to <ImageFrame>) ----------------------------\n /** Flat, row-major single-channel samples. Length must equal width*height. */\n data?: number[];\n /** Pre-decoded RGBA pixels — escape hatch for callers with their own buffer. */\n pixels?: Uint8ClampedArray;\n /** Frame width in pixels. */\n width: number;\n /** Frame height in pixels. Defaults to `width` (square). */\n height?: number;\n /** Saturation ceiling used to normalise `data` → [0,1]. Default 65535. */\n maxADU?: number;\n /** Colormap applied to normalised intensities. Default 'cividis'. */\n colormap?: ImageColormap;\n /** Show the raw sample values as a grid below the image. Default false. */\n showValues?: boolean;\n /** Message shown when no frame is available. */\n emptyMessage?: string;\n\n // -- Panel chrome --------------------------------------------------------\n /** Title rendered above the frame. */\n title?: string;\n /** True while a capture is in flight. Drives the button label + skeleton. */\n loading?: boolean;\n /** Error from the upstream capture. Rendered as a red banner above the frame. */\n error?: string | null;\n /** Click handler for the refresh button. Omit to hide the button entirely. */\n onRefresh?: () => void;\n /** Label for the idle refresh button. Default 'Refresh'. */\n refreshLabel?: string;\n /** Label while `loading` is true. Default 'Capturing…'. */\n refreshingLabel?: string;\n /**\n * Disable the refresh button even when not loading — e.g. when the\n * caller has no data source bound (no CCD selected). The button is\n * always disabled while `loading` is true regardless of this prop.\n */\n refreshDisabled?: boolean;\n /** Extra class name applied to the outer wrapper. */\n className?: string;\n}\n\n// =============================================================================\n// Component\n// =============================================================================\n\nexport const ImagePanel = memo(function ImagePanel({\n // frame\n data,\n pixels,\n width,\n height,\n maxADU,\n colormap,\n showValues,\n emptyMessage,\n // chrome\n title,\n loading = false,\n error = null,\n onRefresh,\n refreshLabel = 'Refresh',\n refreshingLabel = 'Capturing…',\n refreshDisabled = false,\n className = '',\n}: ImagePanelProps): React.ReactElement {\n const { tokens } = useTheme();\n\n // Button is disabled while loading OR when caller explicitly disables it\n // (e.g. no source bound yet). Coalesced here so callers don't repeat the\n // `loading || refreshDisabled` pattern.\n const buttonDisabled = loading || refreshDisabled;\n\n // Skeleton state inside ImageFrame should fire only while loading AND\n // there's nothing to show yet — once we have data, repeat captures\n // refresh in-place without flashing the skeleton.\n const hasFrame = Array.isArray(data) || pixels instanceof Uint8ClampedArray;\n const frameLoading = loading && !hasFrame;\n\n return (\n <div\n data-testid=\"image-panel\"\n className={classNames('zendir-image-panel', className)}\n style={{\n display: 'flex',\n flexDirection: 'column',\n gap: tokens.spacing.sm,\n fontFamily: tokens.typography.fontFamily.primary,\n }}\n >\n {onRefresh && (\n <button\n type=\"button\"\n data-testid=\"image-panel-refresh\"\n onClick={onRefresh}\n disabled={buttonDisabled}\n aria-busy={loading ? 'true' : 'false'}\n style={{\n alignSelf: 'flex-start',\n padding: `${tokens.spacing.xs}px ${tokens.spacing.sm}px`,\n borderRadius: tokens.borderRadius.sm,\n border: `1px solid ${tokens.colors.border.muted}`,\n background: buttonDisabled\n ? 'rgba(255,255,255,0.03)'\n : 'rgba(255,255,255,0.08)',\n color: buttonDisabled ? tokens.colors.text.tertiary : tokens.colors.text.primary,\n cursor: buttonDisabled ? 'default' : 'pointer',\n fontSize: tokens.typography.fontSize.xs,\n fontFamily: tokens.typography.fontFamily.primary,\n // Make the disabled state visually consistent across browsers.\n opacity: buttonDisabled ? 0.85 : 1,\n }}\n >\n {loading ? refreshingLabel : refreshLabel}\n </button>\n )}\n\n {error && (\n <div\n data-testid=\"image-panel-error\"\n role=\"alert\"\n style={{\n color: tokens.colors.status.critical,\n fontSize: tokens.typography.fontSize.xs,\n background: 'rgba(255, 56, 56, 0.08)',\n border: `1px solid ${tokens.colors.status.critical}33`,\n borderRadius: tokens.borderRadius.sm,\n padding: `${tokens.spacing.xs}px ${tokens.spacing.sm}px`,\n wordBreak: 'break-word',\n }}\n >\n {error}\n </div>\n )}\n\n <ImageFrame\n data={data}\n pixels={pixels}\n width={width}\n height={height}\n maxADU={maxADU}\n colormap={colormap}\n showValues={showValues}\n title={title}\n loading={frameLoading}\n emptyMessage={emptyMessage}\n />\n </div>\n );\n});\n\nexport default ImagePanel;\n"],"names":["ImagePanel"],"mappings":";;;;;AAgGO,MAAM,aAAa,KAAK,SAASA,YAAW;AAAA;AAAA,EAEjD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA,EACA,UAAU;AAAA,EACV,QAAQ;AAAA,EACR;AAAA,EACA,eAAe;AAAA,EACf,kBAAkB;AAAA,EAClB,kBAAkB;AAAA,EAClB,YAAY;AACd,GAAwC;AACtC,QAAM,EAAE,OAAA,IAAW,SAAA;AAKnB,QAAM,iBAAiB,WAAW;AAKlC,QAAM,WAAW,MAAM,QAAQ,IAAI,KAAK,kBAAkB;AAC1D,QAAM,eAAe,WAAW,CAAC;AAEjC,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,eAAY;AAAA,MACZ,WAAW,WAAW,sBAAsB,SAAS;AAAA,MACrD,OAAO;AAAA,QACL,SAAS;AAAA,QACT,eAAe;AAAA,QACf,KAAK,OAAO,QAAQ;AAAA,QACpB,YAAY,OAAO,WAAW,WAAW;AAAA,MAAA;AAAA,MAG1C,UAAA;AAAA,QAAA,aACC;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,MAAK;AAAA,YACL,eAAY;AAAA,YACZ,SAAS;AAAA,YACT,UAAU;AAAA,YACV,aAAW,UAAU,SAAS;AAAA,YAC9B,OAAO;AAAA,cACL,WAAW;AAAA,cACX,SAAS,GAAG,OAAO,QAAQ,EAAE,MAAM,OAAO,QAAQ,EAAE;AAAA,cACpD,cAAc,OAAO,aAAa;AAAA,cAClC,QAAQ,aAAa,OAAO,OAAO,OAAO,KAAK;AAAA,cAC/C,YAAY,iBACR,2BACA;AAAA,cACJ,OAAO,iBAAiB,OAAO,OAAO,KAAK,WAAW,OAAO,OAAO,KAAK;AAAA,cACzE,QAAQ,iBAAiB,YAAY;AAAA,cACrC,UAAU,OAAO,WAAW,SAAS;AAAA,cACrC,YAAY,OAAO,WAAW,WAAW;AAAA;AAAA,cAEzC,SAAS,iBAAiB,OAAO;AAAA,YAAA;AAAA,YAGlC,oBAAU,kBAAkB;AAAA,UAAA;AAAA,QAAA;AAAA,QAIhC,SACC;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,eAAY;AAAA,YACZ,MAAK;AAAA,YACL,OAAO;AAAA,cACL,OAAO,OAAO,OAAO,OAAO;AAAA,cAC5B,UAAU,OAAO,WAAW,SAAS;AAAA,cACrC,YAAY;AAAA,cACZ,QAAQ,aAAa,OAAO,OAAO,OAAO,QAAQ;AAAA,cAClD,cAAc,OAAO,aAAa;AAAA,cAClC,SAAS,GAAG,OAAO,QAAQ,EAAE,MAAM,OAAO,QAAQ,EAAE;AAAA,cACpD,WAAW;AAAA,YAAA;AAAA,YAGZ,UAAA;AAAA,UAAA;AAAA,QAAA;AAAA,QAIL;AAAA,UAAC;AAAA,UAAA;AAAA,YACC;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA,SAAS;AAAA,YACT;AAAA,UAAA;AAAA,QAAA;AAAA,MACF;AAAA,IAAA;AAAA,EAAA;AAGN,CAAC;"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { jsxs, jsx, Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { memo, useMemo } from "react";
|
|
3
|
+
import { useTheme } from "../../theme/ThemeProvider.js";
|
|
3
4
|
import { classNames } from "../../utils/index.js";
|
|
4
5
|
import { Icon } from "../display/Icon.js";
|
|
5
|
-
import { useTheme } from "../../theme/ThemeProvider.js";
|
|
6
6
|
import { useCompactMode } from "../../hooks/useCompactMode.js";
|
|
7
7
|
const STATUS_SHAPES = {
|
|
8
8
|
off: (size, color) => /* @__PURE__ */ jsx("svg", { viewBox: "0 0 12 12", width: size, height: size, "aria-hidden": "true", children: /* @__PURE__ */ jsx("circle", { cx: "6", cy: "6", r: "5", fill: color }) }),
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsxs, jsx } from "react/jsx-runtime";
|
|
2
2
|
import { memo, useState, useRef, useMemo, useCallback } from "react";
|
|
3
|
-
import { AstroIcon } from "../display/AstroIcon.js";
|
|
4
3
|
import { useTheme } from "../../theme/ThemeProvider.js";
|
|
4
|
+
import { AstroIcon } from "../display/AstroIcon.js";
|
|
5
5
|
function getStatusColor(status, tokens) {
|
|
6
6
|
if (!status) return tokens.colors.text.muted ?? tokens.colors.text.secondary;
|
|
7
7
|
switch (status) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useState, useCallback, useMemo } from "react";
|
|
2
|
-
import { useDisplaySettingsOptional } from "../context/DisplaySettingsContext.js";
|
|
3
2
|
import { useTheme } from "../theme/ThemeProvider.js";
|
|
3
|
+
import { useDisplaySettingsOptional } from "../context/DisplaySettingsContext.js";
|
|
4
4
|
function useCompactMode(options = {}) {
|
|
5
5
|
const { compact: compactProp, defaultExpanded = false, onPinChange } = options;
|
|
6
6
|
const { prefersReducedMotion } = useTheme();
|
package/dist/react/index.d.ts
CHANGED
|
@@ -14,11 +14,11 @@
|
|
|
14
14
|
* Note: `zendir-ts` (the Zendir TypeScript SDK) is optional. All types are
|
|
15
15
|
* provided locally. Install `zendir-ts` for API client functionality.
|
|
16
16
|
*/
|
|
17
|
-
export { Icon, getIconNames, isValidIconName, Button, Input, Select, Toggle, Checkbox, Tooltip, Dialog, Badge, Container, Tabs, Pagination, GlassCard, GLASS_COLOR_OVERLAYS, DataValue, DataValueGroup, MessageStream, AppBar, ColorPickerPanel, getPropertyConfig, formatPropertyLabel, deriveStatus, deriveBatteryStatus, formatPropertyValue, createPropertyConfig, getPropertiesByCategory, PROPERTY_PRESETS, CATEGORY_ICONS, CATEGORY_LABELS, NumberInput, SideNav, SIDENAV_HEADER_LOGO_SLOT_HEIGHT_PX, ToastProvider, useToast, useToastManager, Popover, Menu, Box, Flex, Grid, Stack, HStack, VStack, Center, Spacer, Divider, useBreakpoint, BREAKPOINTS, resolveResponsive, resolveSpacing, ConfirmDialog, ConfirmProvider, useConfirm, PinInput, CopyButton, useCopyToClipboard, DataTable, DataTableRowDetail, ImageGallery, ChatPanel, parseChatResponse, createChatResponseParser, parseMcpToolResult, CHAT_RESPONSE_TOOL_SCHEMA, CHAT_RESPONSE_MCP_TOOL, CHAT_RESPONSE_JSON_PROMPT, CHAT_RESPONSE_YAML_PROMPT, CHAT_STATUS_RULES_PROMPT, ConnectionForm, SidePanel, HexViewer, REGION_COLORS, REGION_BORDER_COLORS, LimitsBar, LogViewer, PacketViewer, CommandBuilder, FileExplorer, MissionCalendar, ActivityPlanner, Typography, Display1, Display2, H1, H2, H3, H4, H5, H6, Body1, Body2, Body3, Compact, Micro, Mono, DataText, Label, FONT_FAMILY_PRIMARY, FONT_FAMILY_MONO, FONT_WEIGHTS, CardHeader, HeaderIconWithStatus, Capture, useCapture, loadCapturePlaceholderImage, } from './core';
|
|
17
|
+
export { Icon, getIconNames, isValidIconName, Button, Input, Select, Toggle, Checkbox, Tooltip, Dialog, Badge, Container, Tabs, Pagination, GlassCard, GLASS_COLOR_OVERLAYS, DataValue, DataValueGroup, MessageStream, AppBar, ColorPickerPanel, getPropertyConfig, formatPropertyLabel, deriveStatus, deriveBatteryStatus, formatPropertyValue, createPropertyConfig, getPropertiesByCategory, PROPERTY_PRESETS, CATEGORY_ICONS, CATEGORY_LABELS, NumberInput, SideNav, SIDENAV_HEADER_LOGO_SLOT_HEIGHT_PX, ToastProvider, useToast, useToastManager, Popover, Menu, Box, Flex, Grid, Stack, HStack, VStack, Center, Spacer, Divider, useBreakpoint, BREAKPOINTS, resolveResponsive, resolveSpacing, ConfirmDialog, ConfirmProvider, useConfirm, PinInput, CopyButton, useCopyToClipboard, DataTable, DataTableRowDetail, ImageGallery, ImageFrame, decodeImage, ImagePanel, ChatPanel, parseChatResponse, createChatResponseParser, parseMcpToolResult, CHAT_RESPONSE_TOOL_SCHEMA, CHAT_RESPONSE_MCP_TOOL, CHAT_RESPONSE_JSON_PROMPT, CHAT_RESPONSE_YAML_PROMPT, CHAT_STATUS_RULES_PROMPT, ConnectionForm, SidePanel, HexViewer, REGION_COLORS, REGION_BORDER_COLORS, LimitsBar, LogViewer, PacketViewer, CommandBuilder, FileExplorer, MissionCalendar, ActivityPlanner, Typography, Display1, Display2, H1, H2, H3, H4, H5, H6, Body1, Body2, Body3, Compact, Micro, Mono, DataText, Label, FONT_FAMILY_PRIMARY, FONT_FAMILY_MONO, FONT_WEIGHTS, CardHeader, HeaderIconWithStatus, Capture, useCapture, loadCapturePlaceholderImage, } from './core';
|
|
18
18
|
export { ecefMetersToSdkPosition, ecefEulerXyzToHpr } from './3d';
|
|
19
19
|
export { ZenSpace3D, useSimulationScene } from './3d';
|
|
20
20
|
export type { ZenSpace3DProps, ZenSpace3DHandle, UseSimulationSceneOptions, UseSimulationSceneResult, SimulationSceneClient, SimulationSceneObject, StructureDiag, PositionTickDiag, } from './3d';
|
|
21
|
-
export type { NumberInputProps, NumberInputSize, SliderStatus, StatusThresholds, SideNavProps, SideNavHeaderProps, SideNavItemProps, SideNavSectionProps, SideNavFooterProps, ToastOptions, ToastStatus, ToastPosition, ToastProviderProps, PopoverProps, PopoverPlacement, MenuProps, MenuItemProps, BoxProps, FlexProps, GridProps, StackProps, CenterProps, SpacerProps, DividerProps, Breakpoint, ResponsiveValue, SpacingToken, ConfirmDialogProps, ConfirmOptions, ConfirmStatus, PinInputProps, CopyButtonProps, UseCopyToClipboardReturn, DataTableProps, DataTableColumn, DataTableRowDetailProps, DataTableRowDetailField, ImageGalleryProps, GalleryImage, ChatPanelProps, ChatMessage, ChatBlock, ChatBlockEvent, ChatBlockAlert, ChatBlockTelemetry, ChatBlockProgress, ChatBlockTable, ChatBlockActions, ChatBlockChoice, ChatBlockConfirm, ChatBlockCommand, ChatBlockKV, ChatResponseFormat, ChatResponsePayload, ChatResponseParserOptions, McpToolContent, McpToolResult, ConnectionFormProps, ConnectionConfig, SidePanelProps, SidePanelPosition, HexViewerProps, HexHighlight, HexRegion, DecodedField, PacketHeaderEntry, ByteGrouping, OffsetBase, Endianness, LimitsBarProps, LimitsState, LogViewerProps, LogEntry, LogSeverity, PacketViewerProps, PacketItem, PacketItemLimits, PacketItemLimitsState, PacketViewMode, CommandBuilderProps, CommandParameter, CommandParamType, CommandHistoryEntry, FileExplorerProps, FileNode, FileViewMode, FileSortBy, MissionCalendarProps, CalendarEvent, CalendarTimeline, CalendarViewMode, ActivityStatus, ActivityType, ActivityPlannerProps, ActivityFormData, TypographyProps, TypographyVariant, TypographyElement, TypographyColor, CardHeaderProps, HeaderIconWithStatusProps, IconProps, IconName, ButtonProps, ButtonVariant, ButtonSize, InputProps, InputSize, LabelPlacement, SelectProps, SelectOption, ToggleProps, CheckboxProps, TooltipProps, TooltipPlacement, DialogProps, DialogActionsProps, DialogSize, BadgeProps, BadgeVariant, BadgeSize, ContainerProps, ContainerVariant, ContainerPadding, TabsProps, TabsListProps, TabProps, TabsPanelProps, PaginationProps, GlassCardProps, GlassColorOverlay, DataValueProps, DataValueGroupProps, DataValueVariant, DataValueSize, MessageStreamProps, StreamMessage, AppBarProps, AppBarBranding, ColorPickerPanelProps, PropertyConfig, PropertyKey, PropertyCategory, GlassAccentPosition, CaptureProps, CaptureHandle, CaptureRequest, CaptureArgs, CapturedImage, UseCaptureOptions, UseCaptureResult, } from './core';
|
|
21
|
+
export type { NumberInputProps, NumberInputSize, SliderStatus, StatusThresholds, SideNavProps, SideNavHeaderProps, SideNavItemProps, SideNavSectionProps, SideNavFooterProps, ToastOptions, ToastStatus, ToastPosition, ToastProviderProps, PopoverProps, PopoverPlacement, MenuProps, MenuItemProps, BoxProps, FlexProps, GridProps, StackProps, CenterProps, SpacerProps, DividerProps, Breakpoint, ResponsiveValue, SpacingToken, ConfirmDialogProps, ConfirmOptions, ConfirmStatus, PinInputProps, CopyButtonProps, UseCopyToClipboardReturn, DataTableProps, DataTableColumn, DataTableRowDetailProps, DataTableRowDetailField, ImageGalleryProps, GalleryImage, ImageFrameProps, ImageColormap, ImagePanelProps, ChatPanelProps, ChatMessage, ChatBlock, ChatBlockEvent, ChatBlockAlert, ChatBlockTelemetry, ChatBlockProgress, ChatBlockTable, ChatBlockActions, ChatBlockChoice, ChatBlockConfirm, ChatBlockCommand, ChatBlockKV, ChatResponseFormat, ChatResponsePayload, ChatResponseParserOptions, McpToolContent, McpToolResult, ConnectionFormProps, ConnectionConfig, SidePanelProps, SidePanelPosition, HexViewerProps, HexHighlight, HexRegion, DecodedField, PacketHeaderEntry, ByteGrouping, OffsetBase, Endianness, LimitsBarProps, LimitsState, LogViewerProps, LogEntry, LogSeverity, PacketViewerProps, PacketItem, PacketItemLimits, PacketItemLimitsState, PacketViewMode, CommandBuilderProps, CommandParameter, CommandParamType, CommandHistoryEntry, FileExplorerProps, FileNode, FileViewMode, FileSortBy, MissionCalendarProps, CalendarEvent, CalendarTimeline, CalendarViewMode, ActivityStatus, ActivityType, ActivityPlannerProps, ActivityFormData, TypographyProps, TypographyVariant, TypographyElement, TypographyColor, CardHeaderProps, HeaderIconWithStatusProps, IconProps, IconName, ButtonProps, ButtonVariant, ButtonSize, InputProps, InputSize, LabelPlacement, SelectProps, SelectOption, ToggleProps, CheckboxProps, TooltipProps, TooltipPlacement, DialogProps, DialogActionsProps, DialogSize, BadgeProps, BadgeVariant, BadgeSize, ContainerProps, ContainerVariant, ContainerPadding, TabsProps, TabsListProps, TabProps, TabsPanelProps, PaginationProps, GlassCardProps, GlassColorOverlay, DataValueProps, DataValueGroupProps, DataValueVariant, DataValueSize, MessageStreamProps, StreamMessage, AppBarProps, AppBarBranding, ColorPickerPanelProps, PropertyConfig, PropertyKey, PropertyCategory, GlassAccentPosition, CaptureProps, CaptureHandle, CaptureRequest, CaptureArgs, CapturedImage, UseCaptureOptions, UseCaptureResult, } from './core';
|
|
22
22
|
export { ThemeProvider, useTheme, useThemeTokens, useScrollbarStyles, CardAccentProvider, useCardAccent, getSystemAccentColor, getAccentColorOptions, CARD_ACCENT_COLORS, SPACE_SYSTEM_COLORS, } from './theme';
|
|
23
23
|
export type { ThemeProviderProps, ThemeContextValue, ThemeVariant, ThemeMode, ThemeTokens, ThemeColors, ThemeAnimation, ThemeFocus, LayoutTokens, BorderTokens, CardAccentKey, CardAccentColor, CardAccentContextValue, CardAccentProviderProps, } from './theme';
|
|
24
24
|
export { DisplaySettingsProvider, useDisplaySettings, useDisplaySettingsOptional, getEffectiveCompactMode, PRESET_COLORS, GLASS_TINTS, } from './context';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { ObjectInventoryPanel, recommendedFlyDistance } from "./ObjectInventoryPanel.js";
|
|
2
|
+
import { LayerControlPanel } from "./LayerControlPanel.js";
|
|
3
|
+
export {
|
|
4
|
+
LayerControlPanel,
|
|
5
|
+
ObjectInventoryPanel,
|
|
6
|
+
recommendedFlyDistance
|
|
7
|
+
};
|
|
8
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
const DEFAULT_THEME_CONFIG = {
|
|
2
|
+
defaultTheme: "hybrid",
|
|
3
|
+
// Zen (Hybrid) is the default
|
|
4
|
+
defaultMode: "dark",
|
|
5
|
+
persist: true
|
|
6
|
+
};
|
|
7
|
+
function getThemeConfig() {
|
|
8
|
+
if (typeof window !== "undefined") {
|
|
9
|
+
const envTheme = window.__ZENDIR_UI_THEME__;
|
|
10
|
+
if (envTheme === "astro" || envTheme === "purple-hue" || envTheme === "hybrid") {
|
|
11
|
+
return {
|
|
12
|
+
...DEFAULT_THEME_CONFIG,
|
|
13
|
+
defaultTheme: envTheme
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return DEFAULT_THEME_CONFIG;
|
|
18
|
+
}
|
|
19
|
+
function setDefaultTheme(theme) {
|
|
20
|
+
if (typeof window !== "undefined") {
|
|
21
|
+
window.__ZENDIR_UI_THEME__ = theme;
|
|
22
|
+
localStorage.setItem("zendir-ui-theme", theme);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
export {
|
|
26
|
+
DEFAULT_THEME_CONFIG,
|
|
27
|
+
getThemeConfig,
|
|
28
|
+
setDefaultTheme
|
|
29
|
+
};
|
|
30
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sources":["../../../src/react/theme/config.ts"],"sourcesContent":["/**\n * @zendir/ui - Theme Configuration\n * \n * Easy configuration for default theme settings.\n * Import and use in your app root to set defaults.\n */\n\nimport type { ThemeVariant, ThemeMode } from './ThemeProvider';\n\nexport interface ThemeConfig {\n /** Default theme variant */\n defaultTheme?: ThemeVariant;\n /** Default theme mode */\n defaultMode?: ThemeMode;\n /** Persist theme selection to localStorage */\n persist?: boolean;\n}\n\n/**\n * Default theme configuration\n * \n * Available themes:\n * - 'hybrid' - Zen (Hybrid): default; Astro status colors with purple accents\n * - 'astro' - Astro UX (U.S. Space Force) compliant theme\n * - 'purple-hue' - Zen (Purple Hue) theme with modern aesthetics\n * - 'transparent' / 'transparent-bold' / 'transparent-minimal' - Glassmorphic themes\n */\nexport const DEFAULT_THEME_CONFIG: ThemeConfig = {\n defaultTheme: 'hybrid', // Zen (Hybrid) is the default\n defaultMode: 'dark',\n persist: true,\n};\n\n/**\n * Get theme configuration from environment or use defaults\n */\nexport function getThemeConfig(): ThemeConfig {\n // Allow override via environment variable or build-time config\n if (typeof window !== 'undefined') {\n const envTheme = (window as any).__ZENDIR_UI_THEME__;\n if (envTheme === 'astro' || envTheme === 'purple-hue' || envTheme === 'hybrid') {\n return {\n ...DEFAULT_THEME_CONFIG,\n defaultTheme: envTheme,\n };\n }\n }\n \n return DEFAULT_THEME_CONFIG;\n}\n\n/**\n * Set default theme at build/runtime\n * \n * @example\n * ```tsx\n * // In your app root\n * import { setDefaultTheme } from '@zendir/ui/react/theme/config';\n * \n * // Zen (Hybrid) is the default; override if needed:\n * setDefaultTheme('hybrid');\n * setDefaultTheme('purple-hue');\n * setDefaultTheme('astro');\n * ```\n */\nexport function setDefaultTheme(theme: ThemeVariant): void {\n if (typeof window !== 'undefined') {\n (window as any).__ZENDIR_UI_THEME__ = theme;\n // Also update localStorage if persist is enabled\n localStorage.setItem('zendir-ui-theme', theme);\n }\n}\n"],"names":[],"mappings":"AA2BO,MAAM,uBAAoC;AAAA,EAC/C,cAAc;AAAA;AAAA,EACd,aAAa;AAAA,EACb,SAAS;AACX;AAKO,SAAS,iBAA8B;AAE5C,MAAI,OAAO,WAAW,aAAa;AACjC,UAAM,WAAY,OAAe;AACjC,QAAI,aAAa,WAAW,aAAa,gBAAgB,aAAa,UAAU;AAC9E,aAAO;AAAA,QACL,GAAG;AAAA,QACH,cAAc;AAAA,MAAA;AAAA,IAElB;AAAA,EACF;AAEA,SAAO;AACT;AAgBO,SAAS,gBAAgB,OAA2B;AACzD,MAAI,OAAO,WAAW,aAAa;AAChC,WAAe,sBAAsB;AAEtC,iBAAa,QAAQ,mBAAmB,KAAK;AAAA,EAC/C;AACF;"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { ThemeProvider, useScrollbarStyles, useTheme, useThemeTokens } from "./ThemeProvider.js";
|
|
2
|
+
import { DEFAULT_THEME_CONFIG, getThemeConfig, setDefaultTheme } from "./config.js";
|
|
3
|
+
import { CARD_ACCENT_COLORS, CardAccentProvider, SPACE_SYSTEM_COLORS, getAccentColorOptions, getSystemAccentColor, useCardAccent } from "./cardAccent.js";
|
|
4
|
+
export {
|
|
5
|
+
CARD_ACCENT_COLORS,
|
|
6
|
+
CardAccentProvider,
|
|
7
|
+
DEFAULT_THEME_CONFIG,
|
|
8
|
+
SPACE_SYSTEM_COLORS,
|
|
9
|
+
ThemeProvider,
|
|
10
|
+
getAccentColorOptions,
|
|
11
|
+
getSystemAccentColor,
|
|
12
|
+
getThemeConfig,
|
|
13
|
+
setDefaultTheme,
|
|
14
|
+
useCardAccent,
|
|
15
|
+
useScrollbarStyles,
|
|
16
|
+
useTheme,
|
|
17
|
+
useThemeTokens
|
|
18
|
+
};
|
|
19
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;"}
|
|
@@ -230,6 +230,13 @@ const STATUS_COLORS = {
|
|
|
230
230
|
function getStatusColor(status) {
|
|
231
231
|
return STATUS_COLORS[status ?? "off"];
|
|
232
232
|
}
|
|
233
|
+
function deriveBatteryStatus(level) {
|
|
234
|
+
if (level === void 0 || level === null) return "off";
|
|
235
|
+
if (level <= 10) return "critical";
|
|
236
|
+
if (level <= 20) return "serious";
|
|
237
|
+
if (level <= 30) return "caution";
|
|
238
|
+
return "normal";
|
|
239
|
+
}
|
|
233
240
|
function getStatusFromValue(value, thresholds, higherIsBetter = true) {
|
|
234
241
|
if (value === null || value === void 0 || !Number.isFinite(value)) {
|
|
235
242
|
return "off";
|
|
@@ -378,6 +385,7 @@ export {
|
|
|
378
385
|
addAlpha,
|
|
379
386
|
clamp,
|
|
380
387
|
classNames,
|
|
388
|
+
deriveBatteryStatus,
|
|
381
389
|
focusRingStyle,
|
|
382
390
|
formatAltitude,
|
|
383
391
|
formatCoordinate,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../../../src/react/utils/index.ts"],"sourcesContent":["/**\n * @zendir/ui - Enterprise Utility Functions\n * \n * Shared utilities for null-safety, formatting, and defensive coding.\n * These utilities ensure components never crash due to undefined data.\n */\n\n// ============================================================================\n// NULL SAFETY UTILITIES\n// ============================================================================\n\n/**\n * Safely access a value with a fallback for null/undefined\n * @example withNullSafety(data?.temperature, 0) // Returns 0 if undefined\n */\nexport function withNullSafety<T>(value: T | null | undefined, fallback: T): T {\n return value ?? fallback;\n}\n\n/**\n * Safely format a number, returning '--' if undefined\n * @example safeNumber(data?.value, 2) // \"123.45\" or \"--\"\n */\nexport function safeNumber(\n value: number | null | undefined,\n decimals: number = 2,\n fallback: string = '--'\n): string {\n if (value === null || value === undefined || !Number.isFinite(value)) {\n return fallback;\n }\n return value.toFixed(decimals);\n}\n\n/**\n * Check if value is a valid finite number\n */\nexport function isValidNumber(value: unknown): value is number {\n return typeof value === 'number' && Number.isFinite(value);\n}\n\n// ============================================================================\n// NUMBER FORMATTING\n// ============================================================================\n\nexport interface FormatNumberOptions {\n decimals?: number;\n locale?: string;\n notation?: 'standard' | 'scientific' | 'engineering' | 'compact';\n unit?: string;\n signDisplay?: 'auto' | 'never' | 'always' | 'exceptZero';\n}\n\n/**\n * Format a number with locale-aware formatting and optional unit\n * @example formatNumber(1234567, { notation: 'compact' }) // \"1.2M\"\n */\nexport function formatNumber(\n value: number | null | undefined,\n options: FormatNumberOptions = {}\n): string {\n if (value === null || value === undefined || !Number.isFinite(value)) {\n return '--';\n }\n\n const {\n decimals = 2,\n locale = 'en-US',\n notation = 'standard',\n unit,\n signDisplay = 'auto',\n } = options;\n\n try {\n const formatted = new Intl.NumberFormat(locale, {\n notation,\n minimumFractionDigits: notation === 'compact' ? 0 : decimals,\n maximumFractionDigits: decimals,\n signDisplay,\n }).format(value);\n\n return unit ? `${formatted} ${unit}` : formatted;\n } catch {\n return value.toFixed(decimals);\n }\n}\n\n/**\n * Format a number with tabular (monospace) digits for alignment\n * Uses font-feature-settings: 'tnum' 1\n */\nexport function formatTabular(\n value: number | null | undefined,\n decimals: number = 2\n): string {\n return safeNumber(value, decimals);\n}\n\n// ============================================================================\n// UNIT-AWARE FORMATTING\n// ============================================================================\n\n/**\n * Format temperature with unit conversion\n * @example formatTemperature(25) // \"25.0°C\"\n * @example formatTemperature(25, 'fahrenheit') // \"77.0°F\"\n */\nexport function formatTemperature(\n celsius: number | null | undefined,\n unit: 'celsius' | 'fahrenheit' | 'kelvin' = 'celsius',\n decimals: number = 1\n): string {\n if (celsius === null || celsius === undefined || !Number.isFinite(celsius)) {\n return '--°C';\n }\n\n switch (unit) {\n case 'fahrenheit':\n return `${((celsius * 9) / 5 + 32).toFixed(decimals)}°F`;\n case 'kelvin':\n return `${(celsius + 273.15).toFixed(decimals)}K`;\n default:\n return `${celsius.toFixed(decimals)}°C`;\n }\n}\n\n/**\n * Format data rate with automatic unit scaling\n * @example formatDataRate(1500000) // \"1.50 Mbps\"\n */\nexport function formatDataRate(bitsPerSecond: number | null | undefined): string {\n if (bitsPerSecond === null || bitsPerSecond === undefined || !Number.isFinite(bitsPerSecond)) {\n return '-- bps';\n }\n\n const units = ['bps', 'Kbps', 'Mbps', 'Gbps', 'Tbps'];\n let unitIndex = 0;\n let value = bitsPerSecond;\n\n while (value >= 1000 && unitIndex < units.length - 1) {\n value /= 1000;\n unitIndex++;\n }\n\n return `${value.toFixed(2)} ${units[unitIndex]}`;\n}\n\n/**\n * Format distance with automatic unit scaling\n * @example formatDistance(1500) // \"1.50 km\"\n */\nexport function formatDistance(meters: number | null | undefined): string {\n if (meters === null || meters === undefined || !Number.isFinite(meters)) {\n return '-- m';\n }\n\n if (meters >= 1_000_000) {\n return `${(meters / 1_000_000).toFixed(2)} Mm`;\n } else if (meters >= 1_000) {\n return `${(meters / 1_000).toFixed(2)} km`;\n } else if (meters < 1) {\n return `${(meters * 100).toFixed(1)} cm`;\n }\n return `${meters.toFixed(1)} m`;\n}\n\n/**\n * Format altitude (always in km for space ops)\n * @example formatAltitude(418.2) // \"418.2 km\"\n */\nexport function formatAltitude(km: number | null | undefined): string {\n if (km === null || km === undefined || !Number.isFinite(km)) {\n return '-- km';\n }\n return `${km.toFixed(1)} km`;\n}\n\n/**\n * Format velocity\n * @example formatVelocity(7.66) // \"7.66 km/s\"\n */\nexport function formatVelocity(kmPerSec: number | null | undefined): string {\n if (kmPerSec === null || kmPerSec === undefined || !Number.isFinite(kmPerSec)) {\n return '-- km/s';\n }\n return `${kmPerSec.toFixed(2)} km/s`;\n}\n\n/**\n * Format percentage with bounds checking\n * @example formatPercentage(0.856) // \"85.6%\"\n * @example formatPercentage(85.6, false) // \"85.6%\" (already percentage)\n */\nexport function formatPercentage(\n value: number | null | undefined,\n isDecimal: boolean = false,\n decimals: number = 1\n): string {\n if (value === null || value === undefined || !Number.isFinite(value)) {\n return '--%';\n }\n\n const percentage = isDecimal ? value * 100 : value;\n return `${percentage.toFixed(decimals)}%`;\n}\n\n/**\n * Format power (watts) with auto-scaling\n * @example formatPower(1500) // \"1.50 kW\"\n */\nexport function formatPower(watts: number | null | undefined): string {\n if (watts === null || watts === undefined || !Number.isFinite(watts)) {\n return '-- W';\n }\n\n if (Math.abs(watts) >= 1_000_000) {\n return `${(watts / 1_000_000).toFixed(2)} MW`;\n } else if (Math.abs(watts) >= 1_000) {\n return `${(watts / 1_000).toFixed(2)} kW`;\n } else if (Math.abs(watts) < 1) {\n return `${(watts * 1000).toFixed(1)} mW`;\n }\n return `${watts.toFixed(1)} W`;\n}\n\n/**\n * Format frequency (Hz) with auto-scaling\n * @example formatFrequency(2400000000) // \"2.40 GHz\"\n */\nexport function formatFrequency(hz: number | null | undefined): string {\n if (hz === null || hz === undefined || !Number.isFinite(hz)) {\n return '-- Hz';\n }\n\n if (hz >= 1_000_000_000) {\n return `${(hz / 1_000_000_000).toFixed(2)} GHz`;\n } else if (hz >= 1_000_000) {\n return `${(hz / 1_000_000).toFixed(2)} MHz`;\n } else if (hz >= 1_000) {\n return `${(hz / 1_000).toFixed(2)} kHz`;\n }\n return `${hz.toFixed(0)} Hz`;\n}\n\n// ============================================================================\n// TIME FORMATTING\n// ============================================================================\n\n/**\n * Format duration in human-readable form\n * @example formatDuration(3661) // \"1h 1m 1s\"\n */\nexport function formatDuration(seconds: number | null | undefined): string {\n if (seconds === null || seconds === undefined || !Number.isFinite(seconds)) {\n return '--:--';\n }\n\n const absSeconds = Math.abs(seconds);\n const sign = seconds < 0 ? '-' : '';\n\n if (absSeconds < 60) {\n return `${sign}${absSeconds.toFixed(0)}s`;\n } else if (absSeconds < 3600) {\n const mins = Math.floor(absSeconds / 60);\n const secs = Math.floor(absSeconds % 60);\n return `${sign}${mins}m ${secs}s`;\n } else if (absSeconds < 86400) {\n const hours = Math.floor(absSeconds / 3600);\n const mins = Math.floor((absSeconds % 3600) / 60);\n return `${sign}${hours}h ${mins}m`;\n } else {\n const days = Math.floor(absSeconds / 86400);\n const hours = Math.floor((absSeconds % 86400) / 3600);\n return `${sign}${days}d ${hours}h`;\n }\n}\n\n/**\n * Format countdown timer (supports negative values for past events)\n * @example formatCountdown(125) // \"T-02:05\"\n * @example formatCountdown(-60) // \"T+01:00\"\n */\nexport function formatCountdown(seconds: number | null | undefined): string {\n if (seconds === null || seconds === undefined || !Number.isFinite(seconds)) {\n return 'T--:--';\n }\n\n const prefix = seconds >= 0 ? 'T-' : 'T+';\n const absSeconds = Math.abs(seconds);\n\n if (absSeconds >= 3600) {\n const hours = Math.floor(absSeconds / 3600);\n const mins = Math.floor((absSeconds % 3600) / 60);\n const secs = Math.floor(absSeconds % 60);\n return `${prefix}${hours}:${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;\n }\n\n const mins = Math.floor(absSeconds / 60);\n const secs = Math.floor(absSeconds % 60);\n return `${prefix}${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;\n}\n\n/**\n * Format UTC timestamp\n * @example formatUTC(new Date()) // \"2026-01-27 14:30:00Z\"\n */\nexport function formatUTC(date: Date | string | null | undefined): string {\n if (!date) return '--:--:-- UTC';\n\n try {\n const d = typeof date === 'string' ? new Date(date) : date;\n if (isNaN(d.getTime())) return '--:--:-- UTC';\n\n return d.toISOString().replace('T', ' ').slice(0, 19) + 'Z';\n } catch {\n return '--:--:-- UTC';\n }\n}\n\n/**\n * Format time only (HH:MM:SS)\n * @example formatTime(new Date()) // \"14:30:00\"\n */\nexport function formatTime(date: Date | string | null | undefined, includeSeconds = true): string {\n if (!date) return includeSeconds ? '--:--:--' : '--:--';\n\n try {\n const d = typeof date === 'string' ? new Date(date) : date;\n if (isNaN(d.getTime())) return includeSeconds ? '--:--:--' : '--:--';\n\n const hours = d.getUTCHours().toString().padStart(2, '0');\n const mins = d.getUTCMinutes().toString().padStart(2, '0');\n const secs = d.getUTCSeconds().toString().padStart(2, '0');\n\n return includeSeconds ? `${hours}:${mins}:${secs}` : `${hours}:${mins}`;\n } catch {\n return includeSeconds ? '--:--:--' : '--:--';\n }\n}\n\n// ============================================================================\n// COORDINATE FORMATTING\n// ============================================================================\n\n/**\n * Format latitude/longitude\n * @example formatCoordinate(32.4, 'lat') // \"32.40° N\"\n * @example formatCoordinate(-117.2, 'lon') // \"117.20° W\"\n */\nexport function formatCoordinate(\n value: number | null | undefined,\n type: 'lat' | 'lon'\n): string {\n if (value === null || value === undefined || !Number.isFinite(value)) {\n return '--°';\n }\n\n const absValue = Math.abs(value);\n let direction: string;\n\n if (type === 'lat') {\n direction = value >= 0 ? 'N' : 'S';\n } else {\n direction = value >= 0 ? 'E' : 'W';\n }\n\n return `${absValue.toFixed(2)}° ${direction}`;\n}\n\n/**\n * Format lat/lon pair\n * @example formatLatLon(32.4, -117.2) // \"32.40° N, 117.20° W\"\n */\nexport function formatLatLon(\n lat: number | null | undefined,\n lon: number | null | undefined\n): string {\n return `${formatCoordinate(lat, 'lat')}, ${formatCoordinate(lon, 'lon')}`;\n}\n\n// ============================================================================\n// ANGLE FORMATTING\n// ============================================================================\n\n/**\n * Format angle in degrees\n * @example formatDegrees(45.5) // \"45.5°\"\n */\nexport function formatDegrees(\n value: number | null | undefined,\n decimals: number = 1\n): string {\n if (value === null || value === undefined || !Number.isFinite(value)) {\n return '--°';\n }\n return `${value.toFixed(decimals)}°`;\n}\n\n/**\n * Format decibels\n * @example formatDecibels(3.5) // \"3.5 dB\"\n */\nexport function formatDecibels(\n value: number | null | undefined,\n decimals: number = 1\n): string {\n if (value === null || value === undefined || !Number.isFinite(value)) {\n return '-- dB';\n }\n return `${value.toFixed(decimals)} dB`;\n}\n\n// ============================================================================\n// VALUE UTILITIES\n// ============================================================================\n\n/**\n * Clamp a value between min and max\n * @example clamp(150, 0, 100) // 100\n */\nexport function clamp(value: number, min: number, max: number): number {\n return Math.min(Math.max(value, min), max);\n}\n\n/**\n * Linear interpolation\n * @example lerp(0, 100, 0.5) // 50\n */\nexport function lerp(start: number, end: number, t: number): number {\n return start + (end - start) * clamp(t, 0, 1);\n}\n\n/**\n * Map a value from one range to another\n * @example mapRange(50, 0, 100, 0, 1) // 0.5\n */\nexport function mapRange(\n value: number,\n inMin: number,\n inMax: number,\n outMin: number,\n outMax: number\n): number {\n const t = (value - inMin) / (inMax - inMin);\n return lerp(outMin, outMax, t);\n}\n\n// ============================================================================\n// STATUS UTILITIES\n// ============================================================================\n\nexport type StatusLevel = 'off' | 'standby' | 'normal' | 'caution' | 'serious' | 'critical';\n\n/**\n * Astro UX Design System status colors\n * These match the official Astro status semantics\n */\nexport const STATUS_COLORS: Record<StatusLevel, string> = {\n off: '#a4abb6',\n standby: '#2dccff',\n normal: '#56f000',\n caution: '#fce83a',\n serious: '#ffb302',\n critical: '#ff3838',\n};\n\n/**\n * Get status color from level\n * @example getStatusColor('normal') // '#56f000'\n */\nexport function getStatusColor(status: StatusLevel | null | undefined): string {\n return STATUS_COLORS[status ?? 'off'];\n}\n\n/**\n * Derive status for battery specifically (low is bad)\n * @example deriveBatteryStatus(25) // 'caution'\n */\nexport function deriveBatteryStatus(level: number | undefined | null): StatusLevel {\n if (level === undefined || level === null) return 'off';\n if (level <= 10) return 'critical';\n if (level <= 20) return 'serious';\n if (level <= 30) return 'caution';\n return 'normal';\n}\n\n/**\n * Determine status level from a value and thresholds\n * @example getStatusFromValue(85, { critical: 20, serious: 40, caution: 60, normal: 80 }) // 'normal'\n */\nexport function getStatusFromValue(\n value: number | null | undefined,\n thresholds: {\n critical?: number;\n serious?: number;\n caution?: number;\n normal?: number;\n },\n higherIsBetter: boolean = true\n): StatusLevel {\n if (value === null || value === undefined || !Number.isFinite(value)) {\n return 'off';\n }\n\n const { critical = 10, serious = 25, caution = 50, normal = 75 } = thresholds;\n\n if (higherIsBetter) {\n if (value <= critical) return 'critical';\n if (value <= serious) return 'serious';\n if (value <= caution) return 'caution';\n if (value <= normal) return 'standby';\n return 'normal';\n } else {\n if (value >= critical) return 'critical';\n if (value >= serious) return 'serious';\n if (value >= caution) return 'caution';\n if (value >= normal) return 'standby';\n return 'normal';\n }\n}\n\n// ============================================================================\n// STATUS NORMALIZATION\n// ============================================================================\n\n/**\n * Domain status terms that map to UI StatusLevel\n * This handles common backend/sensor terminology\n */\nconst STATUS_MAPPING: Record<string, StatusLevel> = {\n // → 'off'\n 'off': 'off',\n 'disabled': 'off',\n 'inactive': 'off',\n 'offline': 'off',\n 'unknown': 'off',\n 'null': 'off',\n 'none': 'off',\n \n // → 'standby'\n 'standby': 'standby',\n 'idle': 'standby',\n 'waiting': 'standby',\n 'ready': 'standby',\n 'pending': 'standby',\n 'queued': 'standby',\n \n // → 'normal'\n 'normal': 'normal',\n 'nominal': 'normal',\n 'healthy': 'normal',\n 'ok': 'normal',\n 'good': 'normal',\n 'success': 'normal',\n 'operational': 'normal',\n 'active': 'normal',\n 'online': 'normal',\n 'connected': 'normal',\n 'stable': 'normal',\n 'transmitting': 'normal',\n 'receiving': 'normal',\n \n // → 'caution'\n 'caution': 'caution',\n 'warning': 'caution',\n 'degraded': 'caution',\n 'elevated': 'caution',\n 'alert': 'caution',\n 'attention': 'caution',\n 'limited': 'caution',\n \n // → 'serious'\n 'serious': 'serious',\n 'unstable': 'serious',\n 'danger': 'serious',\n 'severe': 'serious',\n \n // → 'critical'\n 'critical': 'critical',\n 'error': 'critical',\n 'failed': 'critical',\n 'failure': 'critical',\n 'emergency': 'critical',\n 'fault': 'critical',\n 'alarm': 'critical',\n};\n\n/**\n * Normalize any status string to the 6-level StatusLevel system\n * \n * @param status - Any status string from domain/backend\n * @param defaultStatus - Fallback if status is not recognized (default: 'off')\n * @returns StatusLevel\n * \n * @example\n * ```typescript\n * normalizeStatus('degraded') // 'caution'\n * normalizeStatus('nominal') // 'normal'\n * normalizeStatus('transmitting') // 'normal'\n * normalizeStatus('warning') // 'caution'\n * normalizeStatus('error') // 'critical'\n * normalizeStatus(undefined) // 'off'\n * ```\n */\nexport function normalizeStatus(\n status: string | undefined | null,\n defaultStatus: StatusLevel = 'off'\n): StatusLevel {\n if (!status) return defaultStatus;\n \n const normalized = status.toLowerCase().trim();\n return STATUS_MAPPING[normalized] ?? defaultStatus;\n}\n\n/**\n * Check if a string is a valid StatusLevel\n */\nexport function isStatusLevel(value: string): value is StatusLevel {\n return ['off', 'standby', 'normal', 'caution', 'serious', 'critical'].includes(value);\n}\n\n/**\n * Get the severity order of a status (higher = more severe)\n * Useful for sorting or finding worst status\n */\nexport function getStatusSeverity(status: StatusLevel): number {\n const order: Record<StatusLevel, number> = {\n off: 0,\n standby: 1,\n normal: 2,\n caution: 3,\n serious: 4,\n critical: 5,\n };\n return order[status];\n}\n\n/**\n * Get the worst (most severe) status from an array\n * \n * @example\n * ```typescript\n * getWorstStatus(['normal', 'caution', 'normal']) // 'caution'\n * getWorstStatus(['normal', 'critical', 'caution']) // 'critical'\n * ```\n */\nexport function getWorstStatus(statuses: StatusLevel[]): StatusLevel {\n if (statuses.length === 0) return 'off';\n return statuses.reduce((worst, current) => \n getStatusSeverity(current) > getStatusSeverity(worst) ? current : worst\n );\n}\n\n// ============================================================================\n// COLOR UTILITIES\n// ============================================================================\n\n/**\n * Safely add alpha to any CSS color string.\n * Handles hex (#RRGGBB → #RRGGBBAA), rgb(), and rgba() formats.\n *\n * Avoids the common bug of appending a hex alpha string to an rgba() color,\n * which produces invalid CSS (e.g. \"rgba(15, 20, 35, 0.85)80\").\n *\n * @param color - CSS color string (hex, rgb, or rgba)\n * @param alpha - Alpha value 0–1\n * @returns Valid CSS color string with alpha applied\n *\n * @example\n * addAlpha('#1b2d3e', 0.5) // '#1b2d3e80'\n * addAlpha('rgba(15, 20, 35, 0.85)', 0.5) // 'rgba(15, 20, 35, 0.5)'\n * addAlpha('rgb(15, 20, 35)', 0.031) // 'rgba(15, 20, 35, 0.031)'\n */\nexport function addAlpha(color: string, alpha: number): string {\n if (!color) return color;\n const a = Math.max(0, Math.min(1, alpha));\n if (color.startsWith('rgba(')) {\n return color.replace(/,\\s*[\\d.]+\\)\\s*$/, `, ${a})`);\n }\n if (color.startsWith('rgb(')) {\n return color.replace(/^rgb\\(/, 'rgba(').replace(/\\)\\s*$/, `, ${a})`);\n }\n if (color.startsWith('#')) {\n const hexA = Math.round(a * 255).toString(16).padStart(2, '0');\n return `${color}${hexA}`;\n }\n return color;\n}\n\n// ============================================================================\n// COMPONENT STYLE UTILITIES\n// ============================================================================\n\n/**\n * Merge class names, filtering out falsy values\n * @example classNames('base', isActive && 'active', className) // \"base active custom\"\n */\nexport function classNames(...classes: (string | boolean | undefined | null)[]): string {\n return classes.filter(Boolean).join(' ');\n}\n\n/**\n * Generate CSS for tabular numbers (monospace digits)\n */\nexport const tabularNumsStyle: React.CSSProperties = {\n fontVariantNumeric: 'tabular-nums',\n fontFeatureSettings: '\"tnum\" 1',\n};\n\n/**\n * CSS transition presets\n */\nexport const transitions = {\n fast: 'all 150ms ease-out',\n normal: 'all 250ms ease-out',\n slow: 'all 400ms ease-out',\n spring: 'all 300ms cubic-bezier(0.34, 1.56, 0.64, 1)',\n} as const;\n\n/**\n * Focus ring styles for accessibility\n */\nexport const focusRingStyle: React.CSSProperties = {\n outline: '2px solid #4dc3ff',\n outlineOffset: '2px',\n};\n\n/**\n * Compute a WCAG AA safe version of an accent color for use as foreground text\n * on dark backgrounds. If the accent already passes 4.5:1 contrast on typical\n * dark surfaces (L ≈ 0.01), returns it unchanged. Otherwise lightens toward\n * white until the minimum contrast is met.\n *\n * @example safeAccentText('#8b5cf6') // '#a885f8' — lightened to pass 4.5:1\n */\nexport function safeAccentText(accent: string): string {\n const hex = accent.replace('#', '');\n if (hex.length < 6) return accent;\n\n const r = parseInt(hex.slice(0, 2), 16) / 255;\n const g = parseInt(hex.slice(2, 4), 16) / 255;\n const b = parseInt(hex.slice(4, 6), 16) / 255;\n const toLinear = (c: number) =>\n c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);\n const L = 0.2126 * toLinear(r) + 0.7152 * toLinear(g) + 0.0722 * toLinear(b);\n\n // Threshold: on a dark surface with L≈0.01 we need text L ≥ 0.22 for 4.5:1\n if (L >= 0.22) return accent;\n\n // Lighten by mixing 25% toward white — reaches ~5.5:1 on typical dark bgs\n const mix = 0.25;\n const lr = Math.min(255, Math.round((r + (1 - r) * mix) * 255));\n const lg = Math.min(255, Math.round((g + (1 - g) * mix) * 255));\n const lb = Math.min(255, Math.round((b + (1 - b) * mix) * 255));\n return `#${lr.toString(16).padStart(2, '0')}${lg.toString(16).padStart(2, '0')}${lb.toString(16).padStart(2, '0')}`;\n}\n\n// =============================================================================\n// Category Palette\n// =============================================================================\n\nexport { CategoryPalette } from './categoryPalette';\n"],"names":["mins","secs"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../../../src/react/utils/index.ts"],"sourcesContent":["/**\n * @zendir/ui - Enterprise Utility Functions\n * \n * Shared utilities for null-safety, formatting, and defensive coding.\n * These utilities ensure components never crash due to undefined data.\n */\n\n// ============================================================================\n// NULL SAFETY UTILITIES\n// ============================================================================\n\n/**\n * Safely access a value with a fallback for null/undefined\n * @example withNullSafety(data?.temperature, 0) // Returns 0 if undefined\n */\nexport function withNullSafety<T>(value: T | null | undefined, fallback: T): T {\n return value ?? fallback;\n}\n\n/**\n * Safely format a number, returning '--' if undefined\n * @example safeNumber(data?.value, 2) // \"123.45\" or \"--\"\n */\nexport function safeNumber(\n value: number | null | undefined,\n decimals: number = 2,\n fallback: string = '--'\n): string {\n if (value === null || value === undefined || !Number.isFinite(value)) {\n return fallback;\n }\n return value.toFixed(decimals);\n}\n\n/**\n * Check if value is a valid finite number\n */\nexport function isValidNumber(value: unknown): value is number {\n return typeof value === 'number' && Number.isFinite(value);\n}\n\n// ============================================================================\n// NUMBER FORMATTING\n// ============================================================================\n\nexport interface FormatNumberOptions {\n decimals?: number;\n locale?: string;\n notation?: 'standard' | 'scientific' | 'engineering' | 'compact';\n unit?: string;\n signDisplay?: 'auto' | 'never' | 'always' | 'exceptZero';\n}\n\n/**\n * Format a number with locale-aware formatting and optional unit\n * @example formatNumber(1234567, { notation: 'compact' }) // \"1.2M\"\n */\nexport function formatNumber(\n value: number | null | undefined,\n options: FormatNumberOptions = {}\n): string {\n if (value === null || value === undefined || !Number.isFinite(value)) {\n return '--';\n }\n\n const {\n decimals = 2,\n locale = 'en-US',\n notation = 'standard',\n unit,\n signDisplay = 'auto',\n } = options;\n\n try {\n const formatted = new Intl.NumberFormat(locale, {\n notation,\n minimumFractionDigits: notation === 'compact' ? 0 : decimals,\n maximumFractionDigits: decimals,\n signDisplay,\n }).format(value);\n\n return unit ? `${formatted} ${unit}` : formatted;\n } catch {\n return value.toFixed(decimals);\n }\n}\n\n/**\n * Format a number with tabular (monospace) digits for alignment\n * Uses font-feature-settings: 'tnum' 1\n */\nexport function formatTabular(\n value: number | null | undefined,\n decimals: number = 2\n): string {\n return safeNumber(value, decimals);\n}\n\n// ============================================================================\n// UNIT-AWARE FORMATTING\n// ============================================================================\n\n/**\n * Format temperature with unit conversion\n * @example formatTemperature(25) // \"25.0°C\"\n * @example formatTemperature(25, 'fahrenheit') // \"77.0°F\"\n */\nexport function formatTemperature(\n celsius: number | null | undefined,\n unit: 'celsius' | 'fahrenheit' | 'kelvin' = 'celsius',\n decimals: number = 1\n): string {\n if (celsius === null || celsius === undefined || !Number.isFinite(celsius)) {\n return '--°C';\n }\n\n switch (unit) {\n case 'fahrenheit':\n return `${((celsius * 9) / 5 + 32).toFixed(decimals)}°F`;\n case 'kelvin':\n return `${(celsius + 273.15).toFixed(decimals)}K`;\n default:\n return `${celsius.toFixed(decimals)}°C`;\n }\n}\n\n/**\n * Format data rate with automatic unit scaling\n * @example formatDataRate(1500000) // \"1.50 Mbps\"\n */\nexport function formatDataRate(bitsPerSecond: number | null | undefined): string {\n if (bitsPerSecond === null || bitsPerSecond === undefined || !Number.isFinite(bitsPerSecond)) {\n return '-- bps';\n }\n\n const units = ['bps', 'Kbps', 'Mbps', 'Gbps', 'Tbps'];\n let unitIndex = 0;\n let value = bitsPerSecond;\n\n while (value >= 1000 && unitIndex < units.length - 1) {\n value /= 1000;\n unitIndex++;\n }\n\n return `${value.toFixed(2)} ${units[unitIndex]}`;\n}\n\n/**\n * Format distance with automatic unit scaling\n * @example formatDistance(1500) // \"1.50 km\"\n */\nexport function formatDistance(meters: number | null | undefined): string {\n if (meters === null || meters === undefined || !Number.isFinite(meters)) {\n return '-- m';\n }\n\n if (meters >= 1_000_000) {\n return `${(meters / 1_000_000).toFixed(2)} Mm`;\n } else if (meters >= 1_000) {\n return `${(meters / 1_000).toFixed(2)} km`;\n } else if (meters < 1) {\n return `${(meters * 100).toFixed(1)} cm`;\n }\n return `${meters.toFixed(1)} m`;\n}\n\n/**\n * Format altitude (always in km for space ops)\n * @example formatAltitude(418.2) // \"418.2 km\"\n */\nexport function formatAltitude(km: number | null | undefined): string {\n if (km === null || km === undefined || !Number.isFinite(km)) {\n return '-- km';\n }\n return `${km.toFixed(1)} km`;\n}\n\n/**\n * Format velocity\n * @example formatVelocity(7.66) // \"7.66 km/s\"\n */\nexport function formatVelocity(kmPerSec: number | null | undefined): string {\n if (kmPerSec === null || kmPerSec === undefined || !Number.isFinite(kmPerSec)) {\n return '-- km/s';\n }\n return `${kmPerSec.toFixed(2)} km/s`;\n}\n\n/**\n * Format percentage with bounds checking\n * @example formatPercentage(0.856) // \"85.6%\"\n * @example formatPercentage(85.6, false) // \"85.6%\" (already percentage)\n */\nexport function formatPercentage(\n value: number | null | undefined,\n isDecimal: boolean = false,\n decimals: number = 1\n): string {\n if (value === null || value === undefined || !Number.isFinite(value)) {\n return '--%';\n }\n\n const percentage = isDecimal ? value * 100 : value;\n return `${percentage.toFixed(decimals)}%`;\n}\n\n/**\n * Format power (watts) with auto-scaling\n * @example formatPower(1500) // \"1.50 kW\"\n */\nexport function formatPower(watts: number | null | undefined): string {\n if (watts === null || watts === undefined || !Number.isFinite(watts)) {\n return '-- W';\n }\n\n if (Math.abs(watts) >= 1_000_000) {\n return `${(watts / 1_000_000).toFixed(2)} MW`;\n } else if (Math.abs(watts) >= 1_000) {\n return `${(watts / 1_000).toFixed(2)} kW`;\n } else if (Math.abs(watts) < 1) {\n return `${(watts * 1000).toFixed(1)} mW`;\n }\n return `${watts.toFixed(1)} W`;\n}\n\n/**\n * Format frequency (Hz) with auto-scaling\n * @example formatFrequency(2400000000) // \"2.40 GHz\"\n */\nexport function formatFrequency(hz: number | null | undefined): string {\n if (hz === null || hz === undefined || !Number.isFinite(hz)) {\n return '-- Hz';\n }\n\n if (hz >= 1_000_000_000) {\n return `${(hz / 1_000_000_000).toFixed(2)} GHz`;\n } else if (hz >= 1_000_000) {\n return `${(hz / 1_000_000).toFixed(2)} MHz`;\n } else if (hz >= 1_000) {\n return `${(hz / 1_000).toFixed(2)} kHz`;\n }\n return `${hz.toFixed(0)} Hz`;\n}\n\n// ============================================================================\n// TIME FORMATTING\n// ============================================================================\n\n/**\n * Format duration in human-readable form\n * @example formatDuration(3661) // \"1h 1m 1s\"\n */\nexport function formatDuration(seconds: number | null | undefined): string {\n if (seconds === null || seconds === undefined || !Number.isFinite(seconds)) {\n return '--:--';\n }\n\n const absSeconds = Math.abs(seconds);\n const sign = seconds < 0 ? '-' : '';\n\n if (absSeconds < 60) {\n return `${sign}${absSeconds.toFixed(0)}s`;\n } else if (absSeconds < 3600) {\n const mins = Math.floor(absSeconds / 60);\n const secs = Math.floor(absSeconds % 60);\n return `${sign}${mins}m ${secs}s`;\n } else if (absSeconds < 86400) {\n const hours = Math.floor(absSeconds / 3600);\n const mins = Math.floor((absSeconds % 3600) / 60);\n return `${sign}${hours}h ${mins}m`;\n } else {\n const days = Math.floor(absSeconds / 86400);\n const hours = Math.floor((absSeconds % 86400) / 3600);\n return `${sign}${days}d ${hours}h`;\n }\n}\n\n/**\n * Format countdown timer (supports negative values for past events)\n * @example formatCountdown(125) // \"T-02:05\"\n * @example formatCountdown(-60) // \"T+01:00\"\n */\nexport function formatCountdown(seconds: number | null | undefined): string {\n if (seconds === null || seconds === undefined || !Number.isFinite(seconds)) {\n return 'T--:--';\n }\n\n const prefix = seconds >= 0 ? 'T-' : 'T+';\n const absSeconds = Math.abs(seconds);\n\n if (absSeconds >= 3600) {\n const hours = Math.floor(absSeconds / 3600);\n const mins = Math.floor((absSeconds % 3600) / 60);\n const secs = Math.floor(absSeconds % 60);\n return `${prefix}${hours}:${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;\n }\n\n const mins = Math.floor(absSeconds / 60);\n const secs = Math.floor(absSeconds % 60);\n return `${prefix}${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;\n}\n\n/**\n * Format UTC timestamp\n * @example formatUTC(new Date()) // \"2026-01-27 14:30:00Z\"\n */\nexport function formatUTC(date: Date | string | null | undefined): string {\n if (!date) return '--:--:-- UTC';\n\n try {\n const d = typeof date === 'string' ? new Date(date) : date;\n if (isNaN(d.getTime())) return '--:--:-- UTC';\n\n return d.toISOString().replace('T', ' ').slice(0, 19) + 'Z';\n } catch {\n return '--:--:-- UTC';\n }\n}\n\n/**\n * Format time only (HH:MM:SS)\n * @example formatTime(new Date()) // \"14:30:00\"\n */\nexport function formatTime(date: Date | string | null | undefined, includeSeconds = true): string {\n if (!date) return includeSeconds ? '--:--:--' : '--:--';\n\n try {\n const d = typeof date === 'string' ? new Date(date) : date;\n if (isNaN(d.getTime())) return includeSeconds ? '--:--:--' : '--:--';\n\n const hours = d.getUTCHours().toString().padStart(2, '0');\n const mins = d.getUTCMinutes().toString().padStart(2, '0');\n const secs = d.getUTCSeconds().toString().padStart(2, '0');\n\n return includeSeconds ? `${hours}:${mins}:${secs}` : `${hours}:${mins}`;\n } catch {\n return includeSeconds ? '--:--:--' : '--:--';\n }\n}\n\n// ============================================================================\n// COORDINATE FORMATTING\n// ============================================================================\n\n/**\n * Format latitude/longitude\n * @example formatCoordinate(32.4, 'lat') // \"32.40° N\"\n * @example formatCoordinate(-117.2, 'lon') // \"117.20° W\"\n */\nexport function formatCoordinate(\n value: number | null | undefined,\n type: 'lat' | 'lon'\n): string {\n if (value === null || value === undefined || !Number.isFinite(value)) {\n return '--°';\n }\n\n const absValue = Math.abs(value);\n let direction: string;\n\n if (type === 'lat') {\n direction = value >= 0 ? 'N' : 'S';\n } else {\n direction = value >= 0 ? 'E' : 'W';\n }\n\n return `${absValue.toFixed(2)}° ${direction}`;\n}\n\n/**\n * Format lat/lon pair\n * @example formatLatLon(32.4, -117.2) // \"32.40° N, 117.20° W\"\n */\nexport function formatLatLon(\n lat: number | null | undefined,\n lon: number | null | undefined\n): string {\n return `${formatCoordinate(lat, 'lat')}, ${formatCoordinate(lon, 'lon')}`;\n}\n\n// ============================================================================\n// ANGLE FORMATTING\n// ============================================================================\n\n/**\n * Format angle in degrees\n * @example formatDegrees(45.5) // \"45.5°\"\n */\nexport function formatDegrees(\n value: number | null | undefined,\n decimals: number = 1\n): string {\n if (value === null || value === undefined || !Number.isFinite(value)) {\n return '--°';\n }\n return `${value.toFixed(decimals)}°`;\n}\n\n/**\n * Format decibels\n * @example formatDecibels(3.5) // \"3.5 dB\"\n */\nexport function formatDecibels(\n value: number | null | undefined,\n decimals: number = 1\n): string {\n if (value === null || value === undefined || !Number.isFinite(value)) {\n return '-- dB';\n }\n return `${value.toFixed(decimals)} dB`;\n}\n\n// ============================================================================\n// VALUE UTILITIES\n// ============================================================================\n\n/**\n * Clamp a value between min and max\n * @example clamp(150, 0, 100) // 100\n */\nexport function clamp(value: number, min: number, max: number): number {\n return Math.min(Math.max(value, min), max);\n}\n\n/**\n * Linear interpolation\n * @example lerp(0, 100, 0.5) // 50\n */\nexport function lerp(start: number, end: number, t: number): number {\n return start + (end - start) * clamp(t, 0, 1);\n}\n\n/**\n * Map a value from one range to another\n * @example mapRange(50, 0, 100, 0, 1) // 0.5\n */\nexport function mapRange(\n value: number,\n inMin: number,\n inMax: number,\n outMin: number,\n outMax: number\n): number {\n const t = (value - inMin) / (inMax - inMin);\n return lerp(outMin, outMax, t);\n}\n\n// ============================================================================\n// STATUS UTILITIES\n// ============================================================================\n\nexport type StatusLevel = 'off' | 'standby' | 'normal' | 'caution' | 'serious' | 'critical';\n\n/**\n * Astro UX Design System status colors\n * These match the official Astro status semantics\n */\nexport const STATUS_COLORS: Record<StatusLevel, string> = {\n off: '#a4abb6',\n standby: '#2dccff',\n normal: '#56f000',\n caution: '#fce83a',\n serious: '#ffb302',\n critical: '#ff3838',\n};\n\n/**\n * Get status color from level\n * @example getStatusColor('normal') // '#56f000'\n */\nexport function getStatusColor(status: StatusLevel | null | undefined): string {\n return STATUS_COLORS[status ?? 'off'];\n}\n\n/**\n * Derive status for battery specifically (low is bad)\n * @example deriveBatteryStatus(25) // 'caution'\n */\nexport function deriveBatteryStatus(level: number | undefined | null): StatusLevel {\n if (level === undefined || level === null) return 'off';\n if (level <= 10) return 'critical';\n if (level <= 20) return 'serious';\n if (level <= 30) return 'caution';\n return 'normal';\n}\n\n/**\n * Determine status level from a value and thresholds\n * @example getStatusFromValue(85, { critical: 20, serious: 40, caution: 60, normal: 80 }) // 'normal'\n */\nexport function getStatusFromValue(\n value: number | null | undefined,\n thresholds: {\n critical?: number;\n serious?: number;\n caution?: number;\n normal?: number;\n },\n higherIsBetter: boolean = true\n): StatusLevel {\n if (value === null || value === undefined || !Number.isFinite(value)) {\n return 'off';\n }\n\n const { critical = 10, serious = 25, caution = 50, normal = 75 } = thresholds;\n\n if (higherIsBetter) {\n if (value <= critical) return 'critical';\n if (value <= serious) return 'serious';\n if (value <= caution) return 'caution';\n if (value <= normal) return 'standby';\n return 'normal';\n } else {\n if (value >= critical) return 'critical';\n if (value >= serious) return 'serious';\n if (value >= caution) return 'caution';\n if (value >= normal) return 'standby';\n return 'normal';\n }\n}\n\n// ============================================================================\n// STATUS NORMALIZATION\n// ============================================================================\n\n/**\n * Domain status terms that map to UI StatusLevel\n * This handles common backend/sensor terminology\n */\nconst STATUS_MAPPING: Record<string, StatusLevel> = {\n // → 'off'\n 'off': 'off',\n 'disabled': 'off',\n 'inactive': 'off',\n 'offline': 'off',\n 'unknown': 'off',\n 'null': 'off',\n 'none': 'off',\n \n // → 'standby'\n 'standby': 'standby',\n 'idle': 'standby',\n 'waiting': 'standby',\n 'ready': 'standby',\n 'pending': 'standby',\n 'queued': 'standby',\n \n // → 'normal'\n 'normal': 'normal',\n 'nominal': 'normal',\n 'healthy': 'normal',\n 'ok': 'normal',\n 'good': 'normal',\n 'success': 'normal',\n 'operational': 'normal',\n 'active': 'normal',\n 'online': 'normal',\n 'connected': 'normal',\n 'stable': 'normal',\n 'transmitting': 'normal',\n 'receiving': 'normal',\n \n // → 'caution'\n 'caution': 'caution',\n 'warning': 'caution',\n 'degraded': 'caution',\n 'elevated': 'caution',\n 'alert': 'caution',\n 'attention': 'caution',\n 'limited': 'caution',\n \n // → 'serious'\n 'serious': 'serious',\n 'unstable': 'serious',\n 'danger': 'serious',\n 'severe': 'serious',\n \n // → 'critical'\n 'critical': 'critical',\n 'error': 'critical',\n 'failed': 'critical',\n 'failure': 'critical',\n 'emergency': 'critical',\n 'fault': 'critical',\n 'alarm': 'critical',\n};\n\n/**\n * Normalize any status string to the 6-level StatusLevel system\n * \n * @param status - Any status string from domain/backend\n * @param defaultStatus - Fallback if status is not recognized (default: 'off')\n * @returns StatusLevel\n * \n * @example\n * ```typescript\n * normalizeStatus('degraded') // 'caution'\n * normalizeStatus('nominal') // 'normal'\n * normalizeStatus('transmitting') // 'normal'\n * normalizeStatus('warning') // 'caution'\n * normalizeStatus('error') // 'critical'\n * normalizeStatus(undefined) // 'off'\n * ```\n */\nexport function normalizeStatus(\n status: string | undefined | null,\n defaultStatus: StatusLevel = 'off'\n): StatusLevel {\n if (!status) return defaultStatus;\n \n const normalized = status.toLowerCase().trim();\n return STATUS_MAPPING[normalized] ?? defaultStatus;\n}\n\n/**\n * Check if a string is a valid StatusLevel\n */\nexport function isStatusLevel(value: string): value is StatusLevel {\n return ['off', 'standby', 'normal', 'caution', 'serious', 'critical'].includes(value);\n}\n\n/**\n * Get the severity order of a status (higher = more severe)\n * Useful for sorting or finding worst status\n */\nexport function getStatusSeverity(status: StatusLevel): number {\n const order: Record<StatusLevel, number> = {\n off: 0,\n standby: 1,\n normal: 2,\n caution: 3,\n serious: 4,\n critical: 5,\n };\n return order[status];\n}\n\n/**\n * Get the worst (most severe) status from an array\n * \n * @example\n * ```typescript\n * getWorstStatus(['normal', 'caution', 'normal']) // 'caution'\n * getWorstStatus(['normal', 'critical', 'caution']) // 'critical'\n * ```\n */\nexport function getWorstStatus(statuses: StatusLevel[]): StatusLevel {\n if (statuses.length === 0) return 'off';\n return statuses.reduce((worst, current) => \n getStatusSeverity(current) > getStatusSeverity(worst) ? current : worst\n );\n}\n\n// ============================================================================\n// COLOR UTILITIES\n// ============================================================================\n\n/**\n * Safely add alpha to any CSS color string.\n * Handles hex (#RRGGBB → #RRGGBBAA), rgb(), and rgba() formats.\n *\n * Avoids the common bug of appending a hex alpha string to an rgba() color,\n * which produces invalid CSS (e.g. \"rgba(15, 20, 35, 0.85)80\").\n *\n * @param color - CSS color string (hex, rgb, or rgba)\n * @param alpha - Alpha value 0–1\n * @returns Valid CSS color string with alpha applied\n *\n * @example\n * addAlpha('#1b2d3e', 0.5) // '#1b2d3e80'\n * addAlpha('rgba(15, 20, 35, 0.85)', 0.5) // 'rgba(15, 20, 35, 0.5)'\n * addAlpha('rgb(15, 20, 35)', 0.031) // 'rgba(15, 20, 35, 0.031)'\n */\nexport function addAlpha(color: string, alpha: number): string {\n if (!color) return color;\n const a = Math.max(0, Math.min(1, alpha));\n if (color.startsWith('rgba(')) {\n return color.replace(/,\\s*[\\d.]+\\)\\s*$/, `, ${a})`);\n }\n if (color.startsWith('rgb(')) {\n return color.replace(/^rgb\\(/, 'rgba(').replace(/\\)\\s*$/, `, ${a})`);\n }\n if (color.startsWith('#')) {\n const hexA = Math.round(a * 255).toString(16).padStart(2, '0');\n return `${color}${hexA}`;\n }\n return color;\n}\n\n// ============================================================================\n// COMPONENT STYLE UTILITIES\n// ============================================================================\n\n/**\n * Merge class names, filtering out falsy values\n * @example classNames('base', isActive && 'active', className) // \"base active custom\"\n */\nexport function classNames(...classes: (string | boolean | undefined | null)[]): string {\n return classes.filter(Boolean).join(' ');\n}\n\n/**\n * Generate CSS for tabular numbers (monospace digits)\n */\nexport const tabularNumsStyle: React.CSSProperties = {\n fontVariantNumeric: 'tabular-nums',\n fontFeatureSettings: '\"tnum\" 1',\n};\n\n/**\n * CSS transition presets\n */\nexport const transitions = {\n fast: 'all 150ms ease-out',\n normal: 'all 250ms ease-out',\n slow: 'all 400ms ease-out',\n spring: 'all 300ms cubic-bezier(0.34, 1.56, 0.64, 1)',\n} as const;\n\n/**\n * Focus ring styles for accessibility\n */\nexport const focusRingStyle: React.CSSProperties = {\n outline: '2px solid #4dc3ff',\n outlineOffset: '2px',\n};\n\n/**\n * Compute a WCAG AA safe version of an accent color for use as foreground text\n * on dark backgrounds. If the accent already passes 4.5:1 contrast on typical\n * dark surfaces (L ≈ 0.01), returns it unchanged. Otherwise lightens toward\n * white until the minimum contrast is met.\n *\n * @example safeAccentText('#8b5cf6') // '#a885f8' — lightened to pass 4.5:1\n */\nexport function safeAccentText(accent: string): string {\n const hex = accent.replace('#', '');\n if (hex.length < 6) return accent;\n\n const r = parseInt(hex.slice(0, 2), 16) / 255;\n const g = parseInt(hex.slice(2, 4), 16) / 255;\n const b = parseInt(hex.slice(4, 6), 16) / 255;\n const toLinear = (c: number) =>\n c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);\n const L = 0.2126 * toLinear(r) + 0.7152 * toLinear(g) + 0.0722 * toLinear(b);\n\n // Threshold: on a dark surface with L≈0.01 we need text L ≥ 0.22 for 4.5:1\n if (L >= 0.22) return accent;\n\n // Lighten by mixing 25% toward white — reaches ~5.5:1 on typical dark bgs\n const mix = 0.25;\n const lr = Math.min(255, Math.round((r + (1 - r) * mix) * 255));\n const lg = Math.min(255, Math.round((g + (1 - g) * mix) * 255));\n const lb = Math.min(255, Math.round((b + (1 - b) * mix) * 255));\n return `#${lr.toString(16).padStart(2, '0')}${lg.toString(16).padStart(2, '0')}${lb.toString(16).padStart(2, '0')}`;\n}\n\n// =============================================================================\n// Category Palette\n// =============================================================================\n\nexport { CategoryPalette } from './categoryPalette';\n"],"names":["mins","secs"],"mappings":"AAeO,SAAS,eAAkB,OAA6B,UAAgB;AAC7E,SAAO,SAAS;AAClB;AAMO,SAAS,WACd,OACA,WAAmB,GACnB,WAAmB,MACX;AACR,MAAI,UAAU,QAAQ,UAAU,UAAa,CAAC,OAAO,SAAS,KAAK,GAAG;AACpE,WAAO;AAAA,EACT;AACA,SAAO,MAAM,QAAQ,QAAQ;AAC/B;AAKO,SAAS,cAAc,OAAiC;AAC7D,SAAO,OAAO,UAAU,YAAY,OAAO,SAAS,KAAK;AAC3D;AAkBO,SAAS,aACd,OACA,UAA+B,IACvB;AACR,MAAI,UAAU,QAAQ,UAAU,UAAa,CAAC,OAAO,SAAS,KAAK,GAAG;AACpE,WAAO;AAAA,EACT;AAEA,QAAM;AAAA,IACJ,WAAW;AAAA,IACX,SAAS;AAAA,IACT,WAAW;AAAA,IACX;AAAA,IACA,cAAc;AAAA,EAAA,IACZ;AAEJ,MAAI;AACF,UAAM,YAAY,IAAI,KAAK,aAAa,QAAQ;AAAA,MAC9C;AAAA,MACA,uBAAuB,aAAa,YAAY,IAAI;AAAA,MACpD,uBAAuB;AAAA,MACvB;AAAA,IAAA,CACD,EAAE,OAAO,KAAK;AAEf,WAAO,OAAO,GAAG,SAAS,IAAI,IAAI,KAAK;AAAA,EACzC,QAAQ;AACN,WAAO,MAAM,QAAQ,QAAQ;AAAA,EAC/B;AACF;AAMO,SAAS,cACd,OACA,WAAmB,GACX;AACR,SAAO,WAAW,OAAO,QAAQ;AACnC;AAWO,SAAS,kBACd,SACA,OAA4C,WAC5C,WAAmB,GACX;AACR,MAAI,YAAY,QAAQ,YAAY,UAAa,CAAC,OAAO,SAAS,OAAO,GAAG;AAC1E,WAAO;AAAA,EACT;AAEA,UAAQ,MAAA;AAAA,IACN,KAAK;AACH,aAAO,IAAK,UAAU,IAAK,IAAI,IAAI,QAAQ,QAAQ,CAAC;AAAA,IACtD,KAAK;AACH,aAAO,IAAI,UAAU,QAAQ,QAAQ,QAAQ,CAAC;AAAA,IAChD;AACE,aAAO,GAAG,QAAQ,QAAQ,QAAQ,CAAC;AAAA,EAAA;AAEzC;AAMO,SAAS,eAAe,eAAkD;AAC/E,MAAI,kBAAkB,QAAQ,kBAAkB,UAAa,CAAC,OAAO,SAAS,aAAa,GAAG;AAC5F,WAAO;AAAA,EACT;AAEA,QAAM,QAAQ,CAAC,OAAO,QAAQ,QAAQ,QAAQ,MAAM;AACpD,MAAI,YAAY;AAChB,MAAI,QAAQ;AAEZ,SAAO,SAAS,OAAQ,YAAY,MAAM,SAAS,GAAG;AACpD,aAAS;AACT;AAAA,EACF;AAEA,SAAO,GAAG,MAAM,QAAQ,CAAC,CAAC,IAAI,MAAM,SAAS,CAAC;AAChD;AAMO,SAAS,eAAe,QAA2C;AACxE,MAAI,WAAW,QAAQ,WAAW,UAAa,CAAC,OAAO,SAAS,MAAM,GAAG;AACvE,WAAO;AAAA,EACT;AAEA,MAAI,UAAU,KAAW;AACvB,WAAO,IAAI,SAAS,KAAW,QAAQ,CAAC,CAAC;AAAA,EAC3C,WAAW,UAAU,KAAO;AAC1B,WAAO,IAAI,SAAS,KAAO,QAAQ,CAAC,CAAC;AAAA,EACvC,WAAW,SAAS,GAAG;AACrB,WAAO,IAAI,SAAS,KAAK,QAAQ,CAAC,CAAC;AAAA,EACrC;AACA,SAAO,GAAG,OAAO,QAAQ,CAAC,CAAC;AAC7B;AAMO,SAAS,eAAe,IAAuC;AACpE,MAAI,OAAO,QAAQ,OAAO,UAAa,CAAC,OAAO,SAAS,EAAE,GAAG;AAC3D,WAAO;AAAA,EACT;AACA,SAAO,GAAG,GAAG,QAAQ,CAAC,CAAC;AACzB;AAMO,SAAS,eAAe,UAA6C;AAC1E,MAAI,aAAa,QAAQ,aAAa,UAAa,CAAC,OAAO,SAAS,QAAQ,GAAG;AAC7E,WAAO;AAAA,EACT;AACA,SAAO,GAAG,SAAS,QAAQ,CAAC,CAAC;AAC/B;AAOO,SAAS,iBACd,OACA,YAAqB,OACrB,WAAmB,GACX;AACR,MAAI,UAAU,QAAQ,UAAU,UAAa,CAAC,OAAO,SAAS,KAAK,GAAG;AACpE,WAAO;AAAA,EACT;AAEA,QAAM,aAAa,YAAY,QAAQ,MAAM;AAC7C,SAAO,GAAG,WAAW,QAAQ,QAAQ,CAAC;AACxC;AAMO,SAAS,YAAY,OAA0C;AACpE,MAAI,UAAU,QAAQ,UAAU,UAAa,CAAC,OAAO,SAAS,KAAK,GAAG;AACpE,WAAO;AAAA,EACT;AAEA,MAAI,KAAK,IAAI,KAAK,KAAK,KAAW;AAChC,WAAO,IAAI,QAAQ,KAAW,QAAQ,CAAC,CAAC;AAAA,EAC1C,WAAW,KAAK,IAAI,KAAK,KAAK,KAAO;AACnC,WAAO,IAAI,QAAQ,KAAO,QAAQ,CAAC,CAAC;AAAA,EACtC,WAAW,KAAK,IAAI,KAAK,IAAI,GAAG;AAC9B,WAAO,IAAI,QAAQ,KAAM,QAAQ,CAAC,CAAC;AAAA,EACrC;AACA,SAAO,GAAG,MAAM,QAAQ,CAAC,CAAC;AAC5B;AAMO,SAAS,gBAAgB,IAAuC;AACrE,MAAI,OAAO,QAAQ,OAAO,UAAa,CAAC,OAAO,SAAS,EAAE,GAAG;AAC3D,WAAO;AAAA,EACT;AAEA,MAAI,MAAM,KAAe;AACvB,WAAO,IAAI,KAAK,KAAe,QAAQ,CAAC,CAAC;AAAA,EAC3C,WAAW,MAAM,KAAW;AAC1B,WAAO,IAAI,KAAK,KAAW,QAAQ,CAAC,CAAC;AAAA,EACvC,WAAW,MAAM,KAAO;AACtB,WAAO,IAAI,KAAK,KAAO,QAAQ,CAAC,CAAC;AAAA,EACnC;AACA,SAAO,GAAG,GAAG,QAAQ,CAAC,CAAC;AACzB;AAUO,SAAS,eAAe,SAA4C;AACzE,MAAI,YAAY,QAAQ,YAAY,UAAa,CAAC,OAAO,SAAS,OAAO,GAAG;AAC1E,WAAO;AAAA,EACT;AAEA,QAAM,aAAa,KAAK,IAAI,OAAO;AACnC,QAAM,OAAO,UAAU,IAAI,MAAM;AAEjC,MAAI,aAAa,IAAI;AACnB,WAAO,GAAG,IAAI,GAAG,WAAW,QAAQ,CAAC,CAAC;AAAA,EACxC,WAAW,aAAa,MAAM;AAC5B,UAAM,OAAO,KAAK,MAAM,aAAa,EAAE;AACvC,UAAM,OAAO,KAAK,MAAM,aAAa,EAAE;AACvC,WAAO,GAAG,IAAI,GAAG,IAAI,KAAK,IAAI;AAAA,EAChC,WAAW,aAAa,OAAO;AAC7B,UAAM,QAAQ,KAAK,MAAM,aAAa,IAAI;AAC1C,UAAM,OAAO,KAAK,MAAO,aAAa,OAAQ,EAAE;AAChD,WAAO,GAAG,IAAI,GAAG,KAAK,KAAK,IAAI;AAAA,EACjC,OAAO;AACL,UAAM,OAAO,KAAK,MAAM,aAAa,KAAK;AAC1C,UAAM,QAAQ,KAAK,MAAO,aAAa,QAAS,IAAI;AACpD,WAAO,GAAG,IAAI,GAAG,IAAI,KAAK,KAAK;AAAA,EACjC;AACF;AAOO,SAAS,gBAAgB,SAA4C;AAC1E,MAAI,YAAY,QAAQ,YAAY,UAAa,CAAC,OAAO,SAAS,OAAO,GAAG;AAC1E,WAAO;AAAA,EACT;AAEA,QAAM,SAAS,WAAW,IAAI,OAAO;AACrC,QAAM,aAAa,KAAK,IAAI,OAAO;AAEnC,MAAI,cAAc,MAAM;AACtB,UAAM,QAAQ,KAAK,MAAM,aAAa,IAAI;AAC1C,UAAMA,QAAO,KAAK,MAAO,aAAa,OAAQ,EAAE;AAChD,UAAMC,QAAO,KAAK,MAAM,aAAa,EAAE;AACvC,WAAO,GAAG,MAAM,GAAG,KAAK,IAAID,MAAK,WAAW,SAAS,GAAG,GAAG,CAAC,IAAIC,MAAK,SAAA,EAAW,SAAS,GAAG,GAAG,CAAC;AAAA,EAClG;AAEA,QAAM,OAAO,KAAK,MAAM,aAAa,EAAE;AACvC,QAAM,OAAO,KAAK,MAAM,aAAa,EAAE;AACvC,SAAO,GAAG,MAAM,GAAG,KAAK,SAAA,EAAW,SAAS,GAAG,GAAG,CAAC,IAAI,KAAK,SAAA,EAAW,SAAS,GAAG,GAAG,CAAC;AACzF;AAMO,SAAS,UAAU,MAAgD;AACxE,MAAI,CAAC,KAAM,QAAO;AAElB,MAAI;AACF,UAAM,IAAI,OAAO,SAAS,WAAW,IAAI,KAAK,IAAI,IAAI;AACtD,QAAI,MAAM,EAAE,QAAA,CAAS,EAAG,QAAO;AAE/B,WAAO,EAAE,cAAc,QAAQ,KAAK,GAAG,EAAE,MAAM,GAAG,EAAE,IAAI;AAAA,EAC1D,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAMO,SAAS,WAAW,MAAwC,iBAAiB,MAAc;AAChG,MAAI,CAAC,KAAM,QAAO,iBAAiB,aAAa;AAEhD,MAAI;AACF,UAAM,IAAI,OAAO,SAAS,WAAW,IAAI,KAAK,IAAI,IAAI;AACtD,QAAI,MAAM,EAAE,QAAA,CAAS,EAAG,QAAO,iBAAiB,aAAa;AAE7D,UAAM,QAAQ,EAAE,YAAA,EAAc,WAAW,SAAS,GAAG,GAAG;AACxD,UAAM,OAAO,EAAE,cAAA,EAAgB,WAAW,SAAS,GAAG,GAAG;AACzD,UAAM,OAAO,EAAE,cAAA,EAAgB,WAAW,SAAS,GAAG,GAAG;AAEzD,WAAO,iBAAiB,GAAG,KAAK,IAAI,IAAI,IAAI,IAAI,KAAK,GAAG,KAAK,IAAI,IAAI;AAAA,EACvE,QAAQ;AACN,WAAO,iBAAiB,aAAa;AAAA,EACvC;AACF;AAWO,SAAS,iBACd,OACA,MACQ;AACR,MAAI,UAAU,QAAQ,UAAU,UAAa,CAAC,OAAO,SAAS,KAAK,GAAG;AACpE,WAAO;AAAA,EACT;AAEA,QAAM,WAAW,KAAK,IAAI,KAAK;AAC/B,MAAI;AAEJ,MAAI,SAAS,OAAO;AAClB,gBAAY,SAAS,IAAI,MAAM;AAAA,EACjC,OAAO;AACL,gBAAY,SAAS,IAAI,MAAM;AAAA,EACjC;AAEA,SAAO,GAAG,SAAS,QAAQ,CAAC,CAAC,KAAK,SAAS;AAC7C;AAMO,SAAS,aACd,KACA,KACQ;AACR,SAAO,GAAG,iBAAiB,KAAK,KAAK,CAAC,KAAK,iBAAiB,KAAK,KAAK,CAAC;AACzE;AAUO,SAAS,cACd,OACA,WAAmB,GACX;AACR,MAAI,UAAU,QAAQ,UAAU,UAAa,CAAC,OAAO,SAAS,KAAK,GAAG;AACpE,WAAO;AAAA,EACT;AACA,SAAO,GAAG,MAAM,QAAQ,QAAQ,CAAC;AACnC;AAMO,SAAS,eACd,OACA,WAAmB,GACX;AACR,MAAI,UAAU,QAAQ,UAAU,UAAa,CAAC,OAAO,SAAS,KAAK,GAAG;AACpE,WAAO;AAAA,EACT;AACA,SAAO,GAAG,MAAM,QAAQ,QAAQ,CAAC;AACnC;AAUO,SAAS,MAAM,OAAe,KAAa,KAAqB;AACrE,SAAO,KAAK,IAAI,KAAK,IAAI,OAAO,GAAG,GAAG,GAAG;AAC3C;AAMO,SAAS,KAAK,OAAe,KAAa,GAAmB;AAClE,SAAO,SAAS,MAAM,SAAS,MAAM,GAAG,GAAG,CAAC;AAC9C;AAMO,SAAS,SACd,OACA,OACA,OACA,QACA,QACQ;AACR,QAAM,KAAK,QAAQ,UAAU,QAAQ;AACrC,SAAO,KAAK,QAAQ,QAAQ,CAAC;AAC/B;AAYO,MAAM,gBAA6C;AAAA,EACxD,KAAK;AAAA,EACL,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,SAAS;AAAA,EACT,UAAU;AACZ;AAMO,SAAS,eAAe,QAAgD;AAC7E,SAAO,cAAc,UAAU,KAAK;AACtC;AAMO,SAAS,oBAAoB,OAA+C;AACjF,MAAI,UAAU,UAAa,UAAU,KAAM,QAAO;AAClD,MAAI,SAAS,GAAI,QAAO;AACxB,MAAI,SAAS,GAAI,QAAO;AACxB,MAAI,SAAS,GAAI,QAAO;AACxB,SAAO;AACT;AAMO,SAAS,mBACd,OACA,YAMA,iBAA0B,MACb;AACb,MAAI,UAAU,QAAQ,UAAU,UAAa,CAAC,OAAO,SAAS,KAAK,GAAG;AACpE,WAAO;AAAA,EACT;AAEA,QAAM,EAAE,WAAW,IAAI,UAAU,IAAI,UAAU,IAAI,SAAS,GAAA,IAAO;AAEnE,MAAI,gBAAgB;AAClB,QAAI,SAAS,SAAU,QAAO;AAC9B,QAAI,SAAS,QAAS,QAAO;AAC7B,QAAI,SAAS,QAAS,QAAO;AAC7B,QAAI,SAAS,OAAQ,QAAO;AAC5B,WAAO;AAAA,EACT,OAAO;AACL,QAAI,SAAS,SAAU,QAAO;AAC9B,QAAI,SAAS,QAAS,QAAO;AAC7B,QAAI,SAAS,QAAS,QAAO;AAC7B,QAAI,SAAS,OAAQ,QAAO;AAC5B,WAAO;AAAA,EACT;AACF;AAUA,MAAM,iBAA8C;AAAA;AAAA,EAElD,OAAO;AAAA,EACP,YAAY;AAAA,EACZ,YAAY;AAAA,EACZ,WAAW;AAAA,EACX,WAAW;AAAA,EACX,QAAQ;AAAA,EACR,QAAQ;AAAA;AAAA,EAGR,WAAW;AAAA,EACX,QAAQ;AAAA,EACR,WAAW;AAAA,EACX,SAAS;AAAA,EACT,WAAW;AAAA,EACX,UAAU;AAAA;AAAA,EAGV,UAAU;AAAA,EACV,WAAW;AAAA,EACX,WAAW;AAAA,EACX,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,WAAW;AAAA,EACX,eAAe;AAAA,EACf,UAAU;AAAA,EACV,UAAU;AAAA,EACV,aAAa;AAAA,EACb,UAAU;AAAA,EACV,gBAAgB;AAAA,EAChB,aAAa;AAAA;AAAA,EAGb,WAAW;AAAA,EACX,WAAW;AAAA,EACX,YAAY;AAAA,EACZ,YAAY;AAAA,EACZ,SAAS;AAAA,EACT,aAAa;AAAA,EACb,WAAW;AAAA;AAAA,EAGX,WAAW;AAAA,EACX,YAAY;AAAA,EACZ,UAAU;AAAA,EACV,UAAU;AAAA;AAAA,EAGV,YAAY;AAAA,EACZ,SAAS;AAAA,EACT,UAAU;AAAA,EACV,WAAW;AAAA,EACX,aAAa;AAAA,EACb,SAAS;AAAA,EACT,SAAS;AACX;AAmBO,SAAS,gBACd,QACA,gBAA6B,OAChB;AACb,MAAI,CAAC,OAAQ,QAAO;AAEpB,QAAM,aAAa,OAAO,YAAA,EAAc,KAAA;AACxC,SAAO,eAAe,UAAU,KAAK;AACvC;AAKO,SAAS,cAAc,OAAqC;AACjE,SAAO,CAAC,OAAO,WAAW,UAAU,WAAW,WAAW,UAAU,EAAE,SAAS,KAAK;AACtF;AAMO,SAAS,kBAAkB,QAA6B;AAC7D,QAAM,QAAqC;AAAA,IACzC,KAAK;AAAA,IACL,SAAS;AAAA,IACT,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,SAAS;AAAA,IACT,UAAU;AAAA,EAAA;AAEZ,SAAO,MAAM,MAAM;AACrB;AAWO,SAAS,eAAe,UAAsC;AACnE,MAAI,SAAS,WAAW,EAAG,QAAO;AAClC,SAAO,SAAS;AAAA,IAAO,CAAC,OAAO,YAC7B,kBAAkB,OAAO,IAAI,kBAAkB,KAAK,IAAI,UAAU;AAAA,EAAA;AAEtE;AAsBO,SAAS,SAAS,OAAe,OAAuB;AAC7D,MAAI,CAAC,MAAO,QAAO;AACnB,QAAM,IAAI,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,KAAK,CAAC;AACxC,MAAI,MAAM,WAAW,OAAO,GAAG;AAC7B,WAAO,MAAM,QAAQ,oBAAoB,KAAK,CAAC,GAAG;AAAA,EACpD;AACA,MAAI,MAAM,WAAW,MAAM,GAAG;AAC5B,WAAO,MAAM,QAAQ,UAAU,OAAO,EAAE,QAAQ,UAAU,KAAK,CAAC,GAAG;AAAA,EACrE;AACA,MAAI,MAAM,WAAW,GAAG,GAAG;AACzB,UAAM,OAAO,KAAK,MAAM,IAAI,GAAG,EAAE,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG;AAC7D,WAAO,GAAG,KAAK,GAAG,IAAI;AAAA,EACxB;AACA,SAAO;AACT;AAUO,SAAS,cAAc,SAA0D;AACtF,SAAO,QAAQ,OAAO,OAAO,EAAE,KAAK,GAAG;AACzC;AAKO,MAAM,mBAAwC;AAAA,EACnD,oBAAoB;AAAA,EACpB,qBAAqB;AACvB;AAKO,MAAM,cAAc;AAAA,EACzB,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,QAAQ;AACV;AAKO,MAAM,iBAAsC;AAAA,EACjD,SAAS;AAAA,EACT,eAAe;AACjB;AAUO,SAAS,eAAe,QAAwB;AACrD,QAAM,MAAM,OAAO,QAAQ,KAAK,EAAE;AAClC,MAAI,IAAI,SAAS,EAAG,QAAO;AAE3B,QAAM,IAAI,SAAS,IAAI,MAAM,GAAG,CAAC,GAAG,EAAE,IAAI;AAC1C,QAAM,IAAI,SAAS,IAAI,MAAM,GAAG,CAAC,GAAG,EAAE,IAAI;AAC1C,QAAM,IAAI,SAAS,IAAI,MAAM,GAAG,CAAC,GAAG,EAAE,IAAI;AAC1C,QAAM,WAAW,CAAC,MAChB,KAAK,UAAU,IAAI,QAAQ,KAAK,KAAK,IAAI,SAAS,OAAO,GAAG;AAC9D,QAAM,IAAI,SAAS,SAAS,CAAC,IAAI,SAAS,SAAS,CAAC,IAAI,SAAS,SAAS,CAAC;AAG3E,MAAI,KAAK,KAAM,QAAO;AAGtB,QAAM,MAAM;AACZ,QAAM,KAAK,KAAK,IAAI,KAAK,KAAK,OAAO,KAAK,IAAI,KAAK,OAAO,GAAG,CAAC;AAC9D,QAAM,KAAK,KAAK,IAAI,KAAK,KAAK,OAAO,KAAK,IAAI,KAAK,OAAO,GAAG,CAAC;AAC9D,QAAM,KAAK,KAAK,IAAI,KAAK,KAAK,OAAO,KAAK,IAAI,KAAK,OAAO,GAAG,CAAC;AAC9D,SAAO,IAAI,GAAG,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG,CAAC,GAAG,GAAG,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG,CAAC,GAAG,GAAG,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG,CAAC;AACnH;"}
|