@zendir/ui 0.6.0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +419 -406
- package/dist/react/3d/CesiumCaptureSource.js.map +1 -1
- package/dist/react/3d/ZenSpace3D.js.map +1 -1
- package/dist/react/3d/ZenSpace3DCesium.js.map +1 -1
- package/dist/react/3d/ZenSpace3DShaders.js.map +1 -1
- package/dist/react/3d/ZenSpace3DUtils.js.map +1 -1
- package/dist/react/3d/capturePngAnalysis.js.map +1 -1
- package/dist/react/3d/threeLoader.js.map +1 -1
- package/dist/react/astro/ClassificationBanner.js.map +1 -1
- package/dist/react/astro/GlobalStatusBar.js.map +1 -1
- package/dist/react/astro/MissionClock.js.map +1 -1
- package/dist/react/astro/MonitoringIcon.js.map +1 -1
- package/dist/react/astro/Notification.js.map +1 -1
- package/dist/react/astro/Progress.js.map +1 -1
- package/dist/react/astro/SimulationControls.js.map +1 -1
- package/dist/react/astro/StatusIndicator.js.map +1 -1
- package/dist/react/astro/UnifiedTimeline.js.map +1 -1
- package/dist/react/cards/AccessCard.js.map +1 -1
- package/dist/react/cards/OrbitCard.js.map +1 -1
- package/dist/react/cards/SpacecraftCard.js.map +1 -1
- package/dist/react/cards/TelemetryCard.js.map +1 -1
- package/dist/react/cards/TelemetryStreamCard.js.map +1 -1
- package/dist/react/charts/GroundTrackMap.js.map +1 -1
- package/dist/react/charts/GroundTrackMapLeaflet.js.map +1 -1
- package/dist/react/charts/groundTrackMapLeafletTiles.js.map +1 -1
- package/dist/react/charts/groundTrackMapLeafletUtils.js.map +1 -1
- package/dist/react/charts/unified/AstroChart.js.map +1 -1
- package/dist/react/charts/unified/PowerOverviewChart.js.map +1 -1
- package/dist/react/charts/unified/domain.js.map +1 -1
- package/dist/react/charts/unified/generators.js.map +1 -1
- package/dist/react/charts/unified/presets.js.map +1 -1
- package/dist/react/charts/unified/sync.js.map +1 -1
- package/dist/react/charts/unified/theme.js.map +1 -1
- package/dist/react/charts/unified/useChartStream.js.map +1 -1
- package/dist/react/chatgpt/index.js.map +1 -1
- package/dist/react/context/CategoryContext.js.map +1 -1
- package/dist/react/context/DisplaySettingsContext.js.map +1 -1
- package/dist/react/context/SpatialSelectionContext.js.map +1 -1
- package/dist/react/core/data/DataTable.js.map +1 -1
- package/dist/react/core/data/DataValue.js.map +1 -1
- package/dist/react/core/data/propertyConfig.js.map +1 -1
- package/dist/react/core/display/AstroIcon.js.map +1 -1
- package/dist/react/core/display/Badge.js.map +1 -1
- package/dist/react/core/display/CardHeader.js.map +1 -1
- package/dist/react/core/display/Container.js.map +1 -1
- package/dist/react/core/display/CopyButton.js.map +1 -1
- package/dist/react/core/display/GlassCard.js.map +1 -1
- package/dist/react/core/display/HeaderIconWithStatus.js.map +1 -1
- package/dist/react/core/display/Icon.js.map +1 -1
- package/dist/react/core/display/Typography.js.map +1 -1
- package/dist/react/core/feedback/ConfirmDialog.js.map +1 -1
- package/dist/react/core/feedback/Dialog.js.map +1 -1
- package/dist/react/core/feedback/Toast.js.map +1 -1
- package/dist/react/core/inputs/Button.js.map +1 -1
- package/dist/react/core/inputs/Checkbox.js.map +1 -1
- package/dist/react/core/inputs/Input.js.map +1 -1
- package/dist/react/core/inputs/LimitsBar.js.map +1 -1
- package/dist/react/core/inputs/NumberInput.js.map +1 -1
- package/dist/react/core/inputs/PinInput.js.map +1 -1
- package/dist/react/core/inputs/Select.js.map +1 -1
- package/dist/react/core/inputs/Toggle.js.map +1 -1
- package/dist/react/core/layout/Box.js.map +1 -1
- package/dist/react/core/layout/Center.js.map +1 -1
- package/dist/react/core/layout/Divider.js.map +1 -1
- package/dist/react/core/layout/Flex.js.map +1 -1
- package/dist/react/core/layout/Grid.js.map +1 -1
- package/dist/react/core/layout/Spacer.js.map +1 -1
- package/dist/react/core/layout/Stack.js.map +1 -1
- package/dist/react/core/layout/responsive.js.map +1 -1
- package/dist/react/core/layout/useBreakpoint.js.map +1 -1
- package/dist/react/core/navigation/AppBar.js.map +1 -1
- package/dist/react/core/navigation/Pagination.js.map +1 -1
- package/dist/react/core/navigation/SideNav.js.map +1 -1
- package/dist/react/core/navigation/Tabs.js.map +1 -1
- package/dist/react/core/overlays/Popover.js.map +1 -1
- package/dist/react/core/overlays/SidePanel.js.map +1 -1
- package/dist/react/core/overlays/Tooltip.js.map +1 -1
- package/dist/react/core/widgets/ActivityPlanner.js.map +1 -1
- package/dist/react/core/widgets/Capture.js.map +1 -1
- package/dist/react/core/widgets/ChatPanel.js.map +1 -1
- package/dist/react/core/widgets/ColorPickerPanel.js.map +1 -1
- package/dist/react/core/widgets/CommandBuilder.js.map +1 -1
- package/dist/react/core/widgets/ConnectionForm.js.map +1 -1
- package/dist/react/core/widgets/FileExplorer.js.map +1 -1
- package/dist/react/core/widgets/HexViewer.js.map +1 -1
- package/dist/react/core/widgets/ImageFrame.js.map +1 -1
- package/dist/react/core/widgets/ImageGallery.js.map +1 -1
- package/dist/react/core/widgets/ImagePanel.js.map +1 -1
- package/dist/react/core/widgets/LogViewer.js.map +1 -1
- package/dist/react/core/widgets/MessageStream.js.map +1 -1
- package/dist/react/core/widgets/MissionCalendar.js.map +1 -1
- package/dist/react/core/widgets/PacketViewer.js.map +1 -1
- package/dist/react/hooks/useCompactMode.js.map +1 -1
- package/dist/react/hooks/useSimulationScene.js.map +1 -1
- package/dist/react/panels/LayerControlPanel.js.map +1 -1
- package/dist/react/panels/ObjectInventoryPanel.js.map +1 -1
- package/dist/react/shared/ErrorBoundary.js.map +1 -1
- package/dist/react/shared/Skeleton.js.map +1 -1
- package/dist/react/theme/ThemeProvider.js +2 -1
- package/dist/react/theme/ThemeProvider.js.map +1 -1
- package/dist/react/theme/cardAccent.js.map +1 -1
- package/dist/react/theme/config.js.map +1 -1
- package/dist/react/theme/types.js.map +1 -1
- package/dist/react/types.js.map +1 -1
- package/dist/react/utils/categoryPalette.js.map +1 -1
- package/dist/react/utils/index.js.map +1 -1
- package/dist/style.css +216 -216
- package/dist/tokens/core.js.map +1 -1
- package/dist/tokens/index.js.map +1 -1
- package/dist/tokens/palettes/astro.js.map +1 -1
- package/dist/tokens/palettes/index.js.map +1 -1
- package/dist/tokens/palettes/tailwind.js.map +1 -1
- package/dist/tokens/palettes/zendir.js.map +1 -1
- package/dist/tokens/themes.js.map +1 -1
- package/dist/tokens/tokens.css +533 -533
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ColorPickerPanel.js","names":[],"sources":["../../../../src/react/core/widgets/ColorPickerPanel.tsx"],"sourcesContent":["/**\n * @zendir/ui - ColorPickerPanel Component\n *\n * Compact color picker with preset colors and target selector.\n * Used in AppBar for selecting accent and glass tint colors.\n *\n * Features:\n * - Preset color swatches grid\n * - Target toggle: Both | Accent Only | Glass Only\n * - Popover design with smooth animations\n * - Keyboard accessible\n *\n * @example\n * ```tsx\n * <ColorPickerPanel\n * accentColor=\"#8b5cf6\"\n * glassTint=\"#8b5cf6\"\n * colorTarget=\"both\"\n * onAccentChange={setAccentColor}\n * onGlassTintChange={setGlassTint}\n * onTargetChange={setColorTarget}\n * />\n * ```\n */\n\nimport React, { memo, useState, useRef, useEffect } from \"react\";\nimport { useTheme } from \"../../theme\";\nimport {\n PRESET_COLORS,\n type ColorTarget,\n} from \"../../context/DisplaySettingsContext\";\nimport { AstroIcon } from \"../display/AstroIcon\";\n\n// =============================================================================\n// Types\n// =============================================================================\n\nexport interface ColorPickerPanelProps {\n /** Current accent color */\n accentColor: string;\n\n /** Current glass tint color */\n glassTint: string;\n\n /** Which color to affect: both, accent, or glass */\n colorTarget: ColorTarget;\n\n /** Callback when accent color changes */\n onAccentChange: (color: string) => void;\n\n /** Callback when glass tint changes */\n onGlassTintChange: (color: string) => void;\n\n /** Callback when target changes */\n onTargetChange: (target: ColorTarget) => void;\n\n /** Callback when any color is selected (respects target) */\n onColorSelect?: (color: string) => void;\n\n /** Compact mode - show only trigger button */\n compact?: boolean;\n\n /** Custom className */\n className?: string;\n}\n\n// =============================================================================\n// Color Swatches\n// =============================================================================\n\nconst COLOR_SWATCHES = Object.entries(PRESET_COLORS).map(([key, color]) => ({\n key,\n color,\n label: key.charAt(0).toUpperCase() + key.slice(1),\n}));\n\n// =============================================================================\n// Target Toggle Component\n// =============================================================================\n\ninterface TargetToggleProps {\n value: ColorTarget;\n onChange: (target: ColorTarget) => void;\n tokens: any;\n}\n\nconst TargetToggle = memo(function TargetToggle({\n value,\n onChange,\n tokens,\n}: TargetToggleProps) {\n const targets: { key: ColorTarget; label: string }[] = [\n { key: \"both\", label: \"Both\" },\n { key: \"accent\", label: \"Accent\" },\n { key: \"glass\", label: \"Glass\" },\n ];\n\n return (\n <div\n style={{\n display: \"flex\",\n gap: 2,\n padding: 2,\n backgroundColor: \"rgba(255,255,255,0.05)\",\n borderRadius: tokens.borderRadius.sm,\n }}\n >\n {targets.map(({ key, label }) => (\n <button\n key={key}\n onClick={() => onChange(key)}\n style={{\n flex: 1,\n padding: \"4px 8px\",\n border: \"none\",\n borderRadius: tokens.borderRadius.sm - 2,\n backgroundColor:\n value === key ? \"rgba(255,255,255,0.15)\" : \"transparent\",\n color:\n value === key\n ? tokens.colors.text.primary\n : tokens.colors.text.tertiary,\n fontSize: \"0.625rem\", // 10px in rem\n fontWeight: value === key ? 600 : 400,\n cursor: \"pointer\",\n transition: \"all 0.15s ease\",\n }}\n >\n {label}\n </button>\n ))}\n </div>\n );\n});\n\n// =============================================================================\n// Color Grid Component\n// =============================================================================\n\ninterface ColorGridProps {\n selectedColor: string;\n onSelect: (color: string) => void;\n tokens: any;\n}\n\nconst ColorGrid = memo(function ColorGrid({\n selectedColor,\n onSelect,\n tokens: _tokens,\n}: ColorGridProps) {\n return (\n <div\n style={{\n display: \"grid\",\n gridTemplateColumns: \"repeat(6, 1fr)\",\n gap: 4,\n }}\n >\n {COLOR_SWATCHES.map(({ key, color, label }) => {\n const isSelected = selectedColor.toLowerCase() === color.toLowerCase();\n\n return (\n <button\n key={key}\n onClick={() => onSelect(color)}\n aria-label={`Select ${label}`}\n title={label}\n style={{\n width: 24,\n height: 24,\n padding: 0,\n border: isSelected\n ? \"2px solid white\"\n : \"1px solid rgba(255,255,255,0.2)\",\n borderRadius: 4,\n backgroundColor: color,\n cursor: \"pointer\",\n transition: \"all 0.15s ease\",\n transform: isSelected ? \"scale(1.1)\" : \"scale(1)\",\n boxShadow: isSelected ? `0 0 8px ${color}80` : \"none\",\n }}\n onMouseEnter={(e) => {\n if (!isSelected) {\n e.currentTarget.style.transform = \"scale(1.15)\";\n e.currentTarget.style.borderColor = \"rgba(255,255,255,0.5)\";\n }\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.transform = isSelected\n ? \"scale(1.1)\"\n : \"scale(1)\";\n e.currentTarget.style.borderColor = isSelected\n ? \"white\"\n : \"rgba(255,255,255,0.2)\";\n }}\n />\n );\n })}\n </div>\n );\n});\n\n// =============================================================================\n// ColorPickerPanel Component\n// =============================================================================\n\nexport const ColorPickerPanel = memo(function ColorPickerPanel({\n accentColor,\n glassTint,\n colorTarget,\n onAccentChange,\n onGlassTintChange,\n onTargetChange,\n onColorSelect,\n compact = true,\n className = \"\",\n}: ColorPickerPanelProps): React.ReactElement {\n const { tokens, theme } = useTheme();\n const [isOpen, setIsOpen] = useState(false);\n const panelRef = useRef<HTMLDivElement>(null);\n const triggerRef = useRef<HTMLButtonElement>(null);\n\n const isTransparentTheme =\n theme === \"transparent\" ||\n theme === \"transparent-bold\" ||\n theme === \"transparent-minimal\";\n\n // Current display color based on target\n const displayColor = colorTarget === \"glass\" ? glassTint : accentColor;\n\n // Handle color selection\n const handleColorSelect = (color: string) => {\n if (onColorSelect) {\n onColorSelect(color);\n } else {\n switch (colorTarget) {\n case \"accent\":\n onAccentChange(color);\n break;\n case \"glass\":\n onGlassTintChange(color);\n break;\n case \"both\":\n default:\n onAccentChange(color);\n onGlassTintChange(color);\n break;\n }\n }\n };\n\n // Close panel when clicking outside\n useEffect(() => {\n const handleClickOutside = (event: MouseEvent) => {\n if (\n panelRef.current &&\n !panelRef.current.contains(event.target as Node) &&\n triggerRef.current &&\n !triggerRef.current.contains(event.target as Node)\n ) {\n setIsOpen(false);\n }\n };\n\n if (isOpen) {\n document.addEventListener(\"mousedown\", handleClickOutside);\n return () =>\n document.removeEventListener(\"mousedown\", handleClickOutside);\n }\n }, [isOpen]);\n\n // Close on escape key\n useEffect(() => {\n const handleEscape = (event: KeyboardEvent) => {\n if (event.key === \"Escape\") {\n setIsOpen(false);\n triggerRef.current?.focus();\n }\n };\n\n if (isOpen) {\n document.addEventListener(\"keydown\", handleEscape);\n return () => document.removeEventListener(\"keydown\", handleEscape);\n }\n }, [isOpen]);\n\n if (compact) {\n return (\n <div style={{ position: \"relative\" }} className={className}>\n {/* Trigger Button */}\n <button\n ref={triggerRef}\n onClick={() => setIsOpen(!isOpen)}\n aria-label=\"Open color picker\"\n aria-expanded={isOpen}\n style={{\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n width: 36,\n height: 36,\n padding: 0,\n border: \"2px solid rgba(255,255,255,0.3)\",\n borderRadius: tokens.borderRadius.md,\n backgroundColor: displayColor,\n cursor: \"pointer\",\n transition: \"all 0.15s ease\",\n boxShadow: isOpen ? `0 0 12px ${displayColor}60` : \"none\",\n }}\n onMouseEnter={(e) => {\n e.currentTarget.style.borderColor = \"rgba(255,255,255,0.5)\";\n e.currentTarget.style.transform = \"scale(1.05)\";\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.borderColor = \"rgba(255,255,255,0.3)\";\n e.currentTarget.style.transform = \"scale(1)\";\n }}\n >\n <AstroIcon name=\"palette\" size={16} color=\"white\" />\n </button>\n\n {/* Popover Panel */}\n {isOpen && (\n <div\n ref={panelRef}\n style={{\n position: \"absolute\",\n top: \"100%\",\n right: 0,\n marginTop: 8,\n padding: 12,\n backgroundColor: tokens.colors.background.elevated,\n border: `1px solid ${tokens.colors.border.default}`,\n borderRadius: tokens.borderRadius.lg,\n boxShadow: tokens.shadows.lg,\n zIndex: 1000,\n minWidth: 180,\n }}\n >\n {/* Target Toggle - only show for transparent themes */}\n {isTransparentTheme && (\n <div style={{ marginBottom: 12 }}>\n <div\n style={{\n fontSize: \"0.625rem\", // 10px in rem\n fontWeight: 500,\n color: tokens.colors.text.tertiary,\n marginBottom: 6,\n textTransform: \"uppercase\",\n letterSpacing: \"0.05em\",\n }}\n >\n Apply to\n </div>\n <TargetToggle\n value={colorTarget}\n onChange={onTargetChange}\n tokens={tokens}\n />\n </div>\n )}\n\n {/* Color Grid */}\n <div>\n <div\n style={{\n fontSize: \"0.625rem\", // 10px in rem\n fontWeight: 500,\n color: tokens.colors.text.tertiary,\n marginBottom: 6,\n textTransform: \"uppercase\",\n letterSpacing: \"0.05em\",\n }}\n >\n {colorTarget === \"glass\"\n ? \"Glass Tint\"\n : colorTarget === \"accent\"\n ? \"Accent Color\"\n : \"Color\"}\n </div>\n <ColorGrid\n selectedColor={displayColor}\n onSelect={handleColorSelect}\n tokens={tokens}\n />\n </div>\n\n {/* Current Colors Display */}\n {isTransparentTheme &&\n colorTarget === \"both\" &&\n accentColor !== glassTint && (\n <div\n style={{\n marginTop: 12,\n paddingTop: 12,\n borderTop: `1px solid ${tokens.colors.border.muted}`,\n display: \"flex\",\n gap: 8,\n fontSize: \"0.625rem\", // 10px in rem\n color: tokens.colors.text.tertiary,\n }}\n >\n <div\n style={{ display: \"flex\", alignItems: \"center\", gap: 4 }}\n >\n <span\n style={{\n width: 12,\n height: 12,\n borderRadius: 2,\n backgroundColor: accentColor,\n border: \"1px solid rgba(255,255,255,0.2)\",\n }}\n />\n Accent\n </div>\n <div\n style={{ display: \"flex\", alignItems: \"center\", gap: 4 }}\n >\n <span\n style={{\n width: 12,\n height: 12,\n borderRadius: 2,\n backgroundColor: glassTint,\n border: \"1px solid rgba(255,255,255,0.2)\",\n }}\n />\n Glass\n </div>\n </div>\n )}\n </div>\n )}\n </div>\n );\n }\n\n // Expanded/inline mode\n return (\n <div\n className={className}\n style={{ display: \"flex\", flexDirection: \"column\", gap: 12 }}\n >\n {isTransparentTheme && (\n <TargetToggle\n value={colorTarget}\n onChange={onTargetChange}\n tokens={tokens}\n />\n )}\n <ColorGrid\n selectedColor={displayColor}\n onSelect={handleColorSelect}\n tokens={tokens}\n />\n </div>\n );\n});\n\nexport default ColorPickerPanel;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsEA,IAAM,iBAAiB,OAAO,QAAQ,aAAa,CAAC,CAAC,KAAK,CAAC,KAAK,YAAY;CAC1E;CACA;CACA,OAAO,IAAI,OAAO,CAAC,CAAC,CAAC,YAAY,IAAI,IAAI,MAAM,CAAC;AAClD,EAAE;AAYF,IAAM,eAAe,KAAK,SAAS,aAAa,EAC9C,OACA,UACA,UACoB;CAOpB,OACE,oBAAC,OAAD;EACE,OAAO;GACL,SAAS;GACT,KAAK;GACL,SAAS;GACT,iBAAiB;GACjB,cAAc,OAAO,aAAa;EACpC;YAEC;GAfH;IAAE,KAAK;IAAQ,OAAO;GAAO;GAC7B;IAAE,KAAK;IAAU,OAAO;GAAS;GACjC;IAAE,KAAK;IAAS,OAAO;GAAQ;EAa5B,CAAA,CAAQ,KAAK,EAAE,KAAK,YACnB,oBAAC,UAAD;GAEE,eAAe,SAAS,GAAG;GAC3B,OAAO;IACL,MAAM;IACN,SAAS;IACT,QAAQ;IACR,cAAc,OAAO,aAAa,KAAK;IACvC,iBACE,UAAU,MAAM,2BAA2B;IAC7C,OACE,UAAU,MACN,OAAO,OAAO,KAAK,UACnB,OAAO,OAAO,KAAK;IACzB,UAAU;IACV,YAAY,UAAU,MAAM,MAAM;IAClC,QAAQ;IACR,YAAY;GACd;aAEC;EACK,GApBD,GAoBC,CACT;CACE,CAAA;AAET,CAAC;AAYD,IAAM,YAAY,KAAK,SAAS,UAAU,EACxC,eACA,UACA,QAAQ,WACS;CACjB,OACE,oBAAC,OAAD;EACE,OAAO;GACL,SAAS;GACT,qBAAqB;GACrB,KAAK;EACP;YAEC,eAAe,KAAK,EAAE,KAAK,OAAO,YAAY;GAC7C,MAAM,aAAa,cAAc,YAAY,MAAM,MAAM,YAAY;GAErE,OACE,oBAAC,UAAD;IAEE,eAAe,SAAS,KAAK;IAC7B,cAAY,UAAU;IACtB,OAAO;IACP,OAAO;KACL,OAAO;KACP,QAAQ;KACR,SAAS;KACT,QAAQ,aACJ,oBACA;KACJ,cAAc;KACd,iBAAiB;KACjB,QAAQ;KACR,YAAY;KACZ,WAAW,aAAa,eAAe;KACvC,WAAW,aAAa,WAAW,MAAM,MAAM;IACjD;IACA,eAAe,MAAM;KACnB,IAAI,CAAC,YAAY;MACf,EAAE,cAAc,MAAM,YAAY;MAClC,EAAE,cAAc,MAAM,cAAc;KACtC;IACF;IACA,eAAe,MAAM;KACnB,EAAE,cAAc,MAAM,YAAY,aAC9B,eACA;KACJ,EAAE,cAAc,MAAM,cAAc,aAChC,UACA;IACN;GACD,GAhCM,GAgCN;EAEL,CAAC;CACE,CAAA;AAET,CAAC;AAMD,IAAa,mBAAmB,KAAK,SAAS,iBAAiB,EAC7D,aACA,WACA,aACA,gBACA,mBACA,gBACA,eACA,UAAU,MACV,YAAY,MACgC;CAC5C,MAAM,EAAE,QAAQ,UAAU,SAAS;CACnC,MAAM,CAAC,QAAQ,aAAa,SAAS,KAAK;CAC1C,MAAM,WAAW,OAAuB,IAAI;CAC5C,MAAM,aAAa,OAA0B,IAAI;CAEjD,MAAM,qBACJ,UAAU,iBACV,UAAU,sBACV,UAAU;CAGZ,MAAM,eAAe,gBAAgB,UAAU,YAAY;CAG3D,MAAM,qBAAqB,UAAkB;EAC3C,IAAI,eACF,cAAc,KAAK;OAEnB,QAAQ,aAAR;GACE,KAAK;IACH,eAAe,KAAK;IACpB;GACF,KAAK;IACH,kBAAkB,KAAK;IACvB;GAEF;IACE,eAAe,KAAK;IACpB,kBAAkB,KAAK;IACvB;EACJ;CAEJ;CAGA,gBAAgB;EACd,MAAM,sBAAsB,UAAsB;GAChD,IACE,SAAS,WACT,CAAC,SAAS,QAAQ,SAAS,MAAM,MAAc,KAC/C,WAAW,WACX,CAAC,WAAW,QAAQ,SAAS,MAAM,MAAc,GAEjD,UAAU,KAAK;EAEnB;EAEA,IAAI,QAAQ;GACV,SAAS,iBAAiB,aAAa,kBAAkB;GACzD,aACE,SAAS,oBAAoB,aAAa,kBAAkB;EAChE;CACF,GAAG,CAAC,MAAM,CAAC;CAGX,gBAAgB;EACd,MAAM,gBAAgB,UAAyB;GAC7C,IAAI,MAAM,QAAQ,UAAU;IAC1B,UAAU,KAAK;IACf,WAAW,SAAS,MAAM;GAC5B;EACF;EAEA,IAAI,QAAQ;GACV,SAAS,iBAAiB,WAAW,YAAY;GACjD,aAAa,SAAS,oBAAoB,WAAW,YAAY;EACnE;CACF,GAAG,CAAC,MAAM,CAAC;CAEX,IAAI,SACF,OACE,qBAAC,OAAD;EAAK,OAAO,EAAE,UAAU,WAAW;EAAc;YAAjD,CAEE,oBAAC,UAAD;GACE,KAAK;GACL,eAAe,UAAU,CAAC,MAAM;GAChC,cAAW;GACX,iBAAe;GACf,OAAO;IACL,SAAS;IACT,YAAY;IACZ,gBAAgB;IAChB,OAAO;IACP,QAAQ;IACR,SAAS;IACT,QAAQ;IACR,cAAc,OAAO,aAAa;IAClC,iBAAiB;IACjB,QAAQ;IACR,YAAY;IACZ,WAAW,SAAS,YAAY,aAAa,MAAM;GACrD;GACA,eAAe,MAAM;IACnB,EAAE,cAAc,MAAM,cAAc;IACpC,EAAE,cAAc,MAAM,YAAY;GACpC;GACA,eAAe,MAAM;IACnB,EAAE,cAAc,MAAM,cAAc;IACpC,EAAE,cAAc,MAAM,YAAY;GACpC;aAEA,oBAAC,WAAD;IAAW,MAAK;IAAU,MAAM;IAAI,OAAM;GAAS,CAAA;EAC7C,CAAA,GAGP,UACC,qBAAC,OAAD;GACE,KAAK;GACL,OAAO;IACL,UAAU;IACV,KAAK;IACL,OAAO;IACP,WAAW;IACX,SAAS;IACT,iBAAiB,OAAO,OAAO,WAAW;IAC1C,QAAQ,aAAa,OAAO,OAAO,OAAO;IAC1C,cAAc,OAAO,aAAa;IAClC,WAAW,OAAO,QAAQ;IAC1B,QAAQ;IACR,UAAU;GACZ;aAdF;IAiBG,sBACC,qBAAC,OAAD;KAAK,OAAO,EAAE,cAAc,GAAG;eAA/B,CACE,oBAAC,OAAD;MACE,OAAO;OACL,UAAU;OACV,YAAY;OACZ,OAAO,OAAO,OAAO,KAAK;OAC1B,cAAc;OACd,eAAe;OACf,eAAe;MACjB;gBACD;KAEI,CAAA,GACL,oBAAC,cAAD;MACE,OAAO;MACP,UAAU;MACF;KACT,CAAA,CACE;;IAIP,qBAAC,OAAD,EAAA,UAAA,CACE,oBAAC,OAAD;KACE,OAAO;MACL,UAAU;MACV,YAAY;MACZ,OAAO,OAAO,OAAO,KAAK;MAC1B,cAAc;MACd,eAAe;MACf,eAAe;KACjB;eAEC,gBAAgB,UACb,eACA,gBAAgB,WACd,iBACA;IACH,CAAA,GACL,oBAAC,WAAD;KACE,eAAe;KACf,UAAU;KACF;IACT,CAAA,CACE,EAAA,CAAA;IAGJ,sBACC,gBAAgB,UAChB,gBAAgB,aACd,qBAAC,OAAD;KACE,OAAO;MACL,WAAW;MACX,YAAY;MACZ,WAAW,aAAa,OAAO,OAAO,OAAO;MAC7C,SAAS;MACT,KAAK;MACL,UAAU;MACV,OAAO,OAAO,OAAO,KAAK;KAC5B;eATF,CAWE,qBAAC,OAAD;MACE,OAAO;OAAE,SAAS;OAAQ,YAAY;OAAU,KAAK;MAAE;gBADzD,CAGE,oBAAC,QAAD,EACE,OAAO;OACL,OAAO;OACP,QAAQ;OACR,cAAc;OACd,iBAAiB;OACjB,QAAQ;MACV,EACD,CAAA,GAAC,QAEC;SACL,qBAAC,OAAD;MACE,OAAO;OAAE,SAAS;OAAQ,YAAY;OAAU,KAAK;MAAE;gBADzD,CAGE,oBAAC,QAAD,EACE,OAAO;OACL,OAAO;OACP,QAAQ;OACR,cAAc;OACd,iBAAiB;OACjB,QAAQ;MACV,EACD,CAAA,GAAC,OAEC;OACF;;GAEN;IAEJ;;CAKT,OACE,qBAAC,OAAD;EACa;EACX,OAAO;GAAE,SAAS;GAAQ,eAAe;GAAU,KAAK;EAAG;YAF7D,CAIG,sBACC,oBAAC,cAAD;GACE,OAAO;GACP,UAAU;GACF;EACT,CAAA,GAEH,oBAAC,WAAD;GACE,eAAe;GACf,UAAU;GACF;EACT,CAAA,CACE;;AAET,CAAC"}
|
|
1
|
+
{"version":3,"file":"ColorPickerPanel.js","names":[],"sources":["../../../../src/react/core/widgets/ColorPickerPanel.tsx"],"sourcesContent":["/**\r\n * @zendir/ui - ColorPickerPanel Component\r\n *\r\n * Compact color picker with preset colors and target selector.\r\n * Used in AppBar for selecting accent and glass tint colors.\r\n *\r\n * Features:\r\n * - Preset color swatches grid\r\n * - Target toggle: Both | Accent Only | Glass Only\r\n * - Popover design with smooth animations\r\n * - Keyboard accessible\r\n *\r\n * @example\r\n * ```tsx\r\n * <ColorPickerPanel\r\n * accentColor=\"#8b5cf6\"\r\n * glassTint=\"#8b5cf6\"\r\n * colorTarget=\"both\"\r\n * onAccentChange={setAccentColor}\r\n * onGlassTintChange={setGlassTint}\r\n * onTargetChange={setColorTarget}\r\n * />\r\n * ```\r\n */\r\n\r\nimport React, { memo, useState, useRef, useEffect } from \"react\";\r\nimport { useTheme } from \"../../theme\";\r\nimport {\r\n PRESET_COLORS,\r\n type ColorTarget,\r\n} from \"../../context/DisplaySettingsContext\";\r\nimport { AstroIcon } from \"../display/AstroIcon\";\r\n\r\n// =============================================================================\r\n// Types\r\n// =============================================================================\r\n\r\nexport interface ColorPickerPanelProps {\r\n /** Current accent color */\r\n accentColor: string;\r\n\r\n /** Current glass tint color */\r\n glassTint: string;\r\n\r\n /** Which color to affect: both, accent, or glass */\r\n colorTarget: ColorTarget;\r\n\r\n /** Callback when accent color changes */\r\n onAccentChange: (color: string) => void;\r\n\r\n /** Callback when glass tint changes */\r\n onGlassTintChange: (color: string) => void;\r\n\r\n /** Callback when target changes */\r\n onTargetChange: (target: ColorTarget) => void;\r\n\r\n /** Callback when any color is selected (respects target) */\r\n onColorSelect?: (color: string) => void;\r\n\r\n /** Compact mode - show only trigger button */\r\n compact?: boolean;\r\n\r\n /** Custom className */\r\n className?: string;\r\n}\r\n\r\n// =============================================================================\r\n// Color Swatches\r\n// =============================================================================\r\n\r\nconst COLOR_SWATCHES = Object.entries(PRESET_COLORS).map(([key, color]) => ({\r\n key,\r\n color,\r\n label: key.charAt(0).toUpperCase() + key.slice(1),\r\n}));\r\n\r\n// =============================================================================\r\n// Target Toggle Component\r\n// =============================================================================\r\n\r\ninterface TargetToggleProps {\r\n value: ColorTarget;\r\n onChange: (target: ColorTarget) => void;\r\n tokens: any;\r\n}\r\n\r\nconst TargetToggle = memo(function TargetToggle({\r\n value,\r\n onChange,\r\n tokens,\r\n}: TargetToggleProps) {\r\n const targets: { key: ColorTarget; label: string }[] = [\r\n { key: \"both\", label: \"Both\" },\r\n { key: \"accent\", label: \"Accent\" },\r\n { key: \"glass\", label: \"Glass\" },\r\n ];\r\n\r\n return (\r\n <div\r\n style={{\r\n display: \"flex\",\r\n gap: 2,\r\n padding: 2,\r\n backgroundColor: \"rgba(255,255,255,0.05)\",\r\n borderRadius: tokens.borderRadius.sm,\r\n }}\r\n >\r\n {targets.map(({ key, label }) => (\r\n <button\r\n key={key}\r\n onClick={() => onChange(key)}\r\n style={{\r\n flex: 1,\r\n padding: \"4px 8px\",\r\n border: \"none\",\r\n borderRadius: tokens.borderRadius.sm - 2,\r\n backgroundColor:\r\n value === key ? \"rgba(255,255,255,0.15)\" : \"transparent\",\r\n color:\r\n value === key\r\n ? tokens.colors.text.primary\r\n : tokens.colors.text.tertiary,\r\n fontSize: \"0.625rem\", // 10px in rem\r\n fontWeight: value === key ? 600 : 400,\r\n cursor: \"pointer\",\r\n transition: \"all 0.15s ease\",\r\n }}\r\n >\r\n {label}\r\n </button>\r\n ))}\r\n </div>\r\n );\r\n});\r\n\r\n// =============================================================================\r\n// Color Grid Component\r\n// =============================================================================\r\n\r\ninterface ColorGridProps {\r\n selectedColor: string;\r\n onSelect: (color: string) => void;\r\n tokens: any;\r\n}\r\n\r\nconst ColorGrid = memo(function ColorGrid({\r\n selectedColor,\r\n onSelect,\r\n tokens: _tokens,\r\n}: ColorGridProps) {\r\n return (\r\n <div\r\n style={{\r\n display: \"grid\",\r\n gridTemplateColumns: \"repeat(6, 1fr)\",\r\n gap: 4,\r\n }}\r\n >\r\n {COLOR_SWATCHES.map(({ key, color, label }) => {\r\n const isSelected = selectedColor.toLowerCase() === color.toLowerCase();\r\n\r\n return (\r\n <button\r\n key={key}\r\n onClick={() => onSelect(color)}\r\n aria-label={`Select ${label}`}\r\n title={label}\r\n style={{\r\n width: 24,\r\n height: 24,\r\n padding: 0,\r\n border: isSelected\r\n ? \"2px solid white\"\r\n : \"1px solid rgba(255,255,255,0.2)\",\r\n borderRadius: 4,\r\n backgroundColor: color,\r\n cursor: \"pointer\",\r\n transition: \"all 0.15s ease\",\r\n transform: isSelected ? \"scale(1.1)\" : \"scale(1)\",\r\n boxShadow: isSelected ? `0 0 8px ${color}80` : \"none\",\r\n }}\r\n onMouseEnter={(e) => {\r\n if (!isSelected) {\r\n e.currentTarget.style.transform = \"scale(1.15)\";\r\n e.currentTarget.style.borderColor = \"rgba(255,255,255,0.5)\";\r\n }\r\n }}\r\n onMouseLeave={(e) => {\r\n e.currentTarget.style.transform = isSelected\r\n ? \"scale(1.1)\"\r\n : \"scale(1)\";\r\n e.currentTarget.style.borderColor = isSelected\r\n ? \"white\"\r\n : \"rgba(255,255,255,0.2)\";\r\n }}\r\n />\r\n );\r\n })}\r\n </div>\r\n );\r\n});\r\n\r\n// =============================================================================\r\n// ColorPickerPanel Component\r\n// =============================================================================\r\n\r\nexport const ColorPickerPanel = memo(function ColorPickerPanel({\r\n accentColor,\r\n glassTint,\r\n colorTarget,\r\n onAccentChange,\r\n onGlassTintChange,\r\n onTargetChange,\r\n onColorSelect,\r\n compact = true,\r\n className = \"\",\r\n}: ColorPickerPanelProps): React.ReactElement {\r\n const { tokens, theme } = useTheme();\r\n const [isOpen, setIsOpen] = useState(false);\r\n const panelRef = useRef<HTMLDivElement>(null);\r\n const triggerRef = useRef<HTMLButtonElement>(null);\r\n\r\n const isTransparentTheme =\r\n theme === \"transparent\" ||\r\n theme === \"transparent-bold\" ||\r\n theme === \"transparent-minimal\";\r\n\r\n // Current display color based on target\r\n const displayColor = colorTarget === \"glass\" ? glassTint : accentColor;\r\n\r\n // Handle color selection\r\n const handleColorSelect = (color: string) => {\r\n if (onColorSelect) {\r\n onColorSelect(color);\r\n } else {\r\n switch (colorTarget) {\r\n case \"accent\":\r\n onAccentChange(color);\r\n break;\r\n case \"glass\":\r\n onGlassTintChange(color);\r\n break;\r\n case \"both\":\r\n default:\r\n onAccentChange(color);\r\n onGlassTintChange(color);\r\n break;\r\n }\r\n }\r\n };\r\n\r\n // Close panel when clicking outside\r\n useEffect(() => {\r\n const handleClickOutside = (event: MouseEvent) => {\r\n if (\r\n panelRef.current &&\r\n !panelRef.current.contains(event.target as Node) &&\r\n triggerRef.current &&\r\n !triggerRef.current.contains(event.target as Node)\r\n ) {\r\n setIsOpen(false);\r\n }\r\n };\r\n\r\n if (isOpen) {\r\n document.addEventListener(\"mousedown\", handleClickOutside);\r\n return () =>\r\n document.removeEventListener(\"mousedown\", handleClickOutside);\r\n }\r\n }, [isOpen]);\r\n\r\n // Close on escape key\r\n useEffect(() => {\r\n const handleEscape = (event: KeyboardEvent) => {\r\n if (event.key === \"Escape\") {\r\n setIsOpen(false);\r\n triggerRef.current?.focus();\r\n }\r\n };\r\n\r\n if (isOpen) {\r\n document.addEventListener(\"keydown\", handleEscape);\r\n return () => document.removeEventListener(\"keydown\", handleEscape);\r\n }\r\n }, [isOpen]);\r\n\r\n if (compact) {\r\n return (\r\n <div style={{ position: \"relative\" }} className={className}>\r\n {/* Trigger Button */}\r\n <button\r\n ref={triggerRef}\r\n onClick={() => setIsOpen(!isOpen)}\r\n aria-label=\"Open color picker\"\r\n aria-expanded={isOpen}\r\n style={{\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n justifyContent: \"center\",\r\n width: 36,\r\n height: 36,\r\n padding: 0,\r\n border: \"2px solid rgba(255,255,255,0.3)\",\r\n borderRadius: tokens.borderRadius.md,\r\n backgroundColor: displayColor,\r\n cursor: \"pointer\",\r\n transition: \"all 0.15s ease\",\r\n boxShadow: isOpen ? `0 0 12px ${displayColor}60` : \"none\",\r\n }}\r\n onMouseEnter={(e) => {\r\n e.currentTarget.style.borderColor = \"rgba(255,255,255,0.5)\";\r\n e.currentTarget.style.transform = \"scale(1.05)\";\r\n }}\r\n onMouseLeave={(e) => {\r\n e.currentTarget.style.borderColor = \"rgba(255,255,255,0.3)\";\r\n e.currentTarget.style.transform = \"scale(1)\";\r\n }}\r\n >\r\n <AstroIcon name=\"palette\" size={16} color=\"white\" />\r\n </button>\r\n\r\n {/* Popover Panel */}\r\n {isOpen && (\r\n <div\r\n ref={panelRef}\r\n style={{\r\n position: \"absolute\",\r\n top: \"100%\",\r\n right: 0,\r\n marginTop: 8,\r\n padding: 12,\r\n backgroundColor: tokens.colors.background.elevated,\r\n border: `1px solid ${tokens.colors.border.default}`,\r\n borderRadius: tokens.borderRadius.lg,\r\n boxShadow: tokens.shadows.lg,\r\n zIndex: 1000,\r\n minWidth: 180,\r\n }}\r\n >\r\n {/* Target Toggle - only show for transparent themes */}\r\n {isTransparentTheme && (\r\n <div style={{ marginBottom: 12 }}>\r\n <div\r\n style={{\r\n fontSize: \"0.625rem\", // 10px in rem\r\n fontWeight: 500,\r\n color: tokens.colors.text.tertiary,\r\n marginBottom: 6,\r\n textTransform: \"uppercase\",\r\n letterSpacing: \"0.05em\",\r\n }}\r\n >\r\n Apply to\r\n </div>\r\n <TargetToggle\r\n value={colorTarget}\r\n onChange={onTargetChange}\r\n tokens={tokens}\r\n />\r\n </div>\r\n )}\r\n\r\n {/* Color Grid */}\r\n <div>\r\n <div\r\n style={{\r\n fontSize: \"0.625rem\", // 10px in rem\r\n fontWeight: 500,\r\n color: tokens.colors.text.tertiary,\r\n marginBottom: 6,\r\n textTransform: \"uppercase\",\r\n letterSpacing: \"0.05em\",\r\n }}\r\n >\r\n {colorTarget === \"glass\"\r\n ? \"Glass Tint\"\r\n : colorTarget === \"accent\"\r\n ? \"Accent Color\"\r\n : \"Color\"}\r\n </div>\r\n <ColorGrid\r\n selectedColor={displayColor}\r\n onSelect={handleColorSelect}\r\n tokens={tokens}\r\n />\r\n </div>\r\n\r\n {/* Current Colors Display */}\r\n {isTransparentTheme &&\r\n colorTarget === \"both\" &&\r\n accentColor !== glassTint && (\r\n <div\r\n style={{\r\n marginTop: 12,\r\n paddingTop: 12,\r\n borderTop: `1px solid ${tokens.colors.border.muted}`,\r\n display: \"flex\",\r\n gap: 8,\r\n fontSize: \"0.625rem\", // 10px in rem\r\n color: tokens.colors.text.tertiary,\r\n }}\r\n >\r\n <div\r\n style={{ display: \"flex\", alignItems: \"center\", gap: 4 }}\r\n >\r\n <span\r\n style={{\r\n width: 12,\r\n height: 12,\r\n borderRadius: 2,\r\n backgroundColor: accentColor,\r\n border: \"1px solid rgba(255,255,255,0.2)\",\r\n }}\r\n />\r\n Accent\r\n </div>\r\n <div\r\n style={{ display: \"flex\", alignItems: \"center\", gap: 4 }}\r\n >\r\n <span\r\n style={{\r\n width: 12,\r\n height: 12,\r\n borderRadius: 2,\r\n backgroundColor: glassTint,\r\n border: \"1px solid rgba(255,255,255,0.2)\",\r\n }}\r\n />\r\n Glass\r\n </div>\r\n </div>\r\n )}\r\n </div>\r\n )}\r\n </div>\r\n );\r\n }\r\n\r\n // Expanded/inline mode\r\n return (\r\n <div\r\n className={className}\r\n style={{ display: \"flex\", flexDirection: \"column\", gap: 12 }}\r\n >\r\n {isTransparentTheme && (\r\n <TargetToggle\r\n value={colorTarget}\r\n onChange={onTargetChange}\r\n tokens={tokens}\r\n />\r\n )}\r\n <ColorGrid\r\n selectedColor={displayColor}\r\n onSelect={handleColorSelect}\r\n tokens={tokens}\r\n />\r\n </div>\r\n );\r\n});\r\n\r\nexport default ColorPickerPanel;\r\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CommandBuilder.js","names":[],"sources":["../../../../src/react/core/widgets/CommandBuilder.tsx"],"sourcesContent":["/**\n * @zendir/ui - CommandBuilder Component\n *\n * Dynamic command parameter form for spacecraft command construction and execution.\n * Builds a form from a command definition with type-appropriate inputs,\n * validation, hazardous warnings, and send/history functionality.\n *\n * Features:\n * - Auto-generated form from command parameter definitions\n * - Type-appropriate inputs (number, string, boolean, enum/state, hex)\n * - Validation with min/max/regex constraints\n * - Hazardous command warnings\n * - Command history with re-send\n * - Raw command preview\n * - Required/optional parameter indicators\n * - Default values\n *\n * @example\n * ```tsx\n * <CommandBuilder\n * target=\"INST\"\n * command=\"COLLECT\"\n * parameters={[\n * { name: 'TYPE', type: 'enum', options: ['NORMAL', 'SPECIAL'], required: true },\n * { name: 'DURATION', type: 'float', min: 0, max: 100, default: 1.0, units: 'sec' },\n * ]}\n * onSend={(params) => console.log('Send:', params)}\n * />\n * ```\n */\n\nimport React, { useState, useCallback, useMemo, memo } from \"react\";\nimport { useTheme } from \"../../theme\";\n\n// =============================================================================\n// Types\n// =============================================================================\n\nexport type CommandParamType =\n | \"int\"\n | \"uint\"\n | \"float\"\n | \"string\"\n | \"boolean\"\n | \"enum\"\n | \"hex\"\n | \"block\";\n\nexport interface CommandParameter {\n /** Parameter name */\n name: string;\n /** Parameter type */\n type: CommandParamType;\n /** Description */\n description?: string;\n /** Default value */\n default?: unknown;\n /** Whether parameter is required (default: true) */\n required?: boolean;\n /** Minimum value (for numeric types) */\n min?: number;\n /** Maximum value (for numeric types) */\n max?: number;\n /** Bit size */\n bitSize?: number;\n /** Enum options (for enum type) */\n options?: (string | number)[];\n /** Units label */\n units?: string;\n /** Validation regex pattern (for string type) */\n pattern?: string;\n /** Whether this is a hazardous parameter */\n hazardous?: boolean;\n /** Hazardous description */\n hazardousDescription?: string;\n}\n\nexport interface CommandHistoryEntry {\n id: string;\n timestamp: Date;\n target: string;\n command: string;\n parameters: Record<string, unknown>;\n status: \"sent\" | \"success\" | \"error\";\n error?: string;\n}\n\nexport interface CommandBuilderProps {\n /** Target name */\n target: string;\n /** Command name */\n command: string;\n /** Command description */\n description?: string;\n /** Whether this is a hazardous command */\n hazardous?: boolean;\n /** Hazardous warning text */\n hazardousDescription?: string;\n /** Command parameters */\n parameters: CommandParameter[];\n /** Called when command is sent */\n onSend: (params: Record<string, unknown>) => void | Promise<void>;\n /** Command history entries */\n history?: CommandHistoryEntry[];\n /** Show command history (default: true) */\n showHistory?: boolean;\n /** Show raw command preview (default: true) */\n showPreview?: boolean;\n /** Whether send is disabled */\n disabled?: boolean;\n /** Height (default: auto) */\n height?: number | string;\n /** CSS class */\n className?: string;\n /** Show outer container border (default: true). Set false when embedded inside a Container/card. */\n bordered?: boolean;\n /** Custom style overrides for the outer container */\n style?: React.CSSProperties;\n}\n\n// =============================================================================\n// Component\n// =============================================================================\n\nexport const CommandBuilder = memo(function CommandBuilder({\n target,\n command,\n description,\n hazardous = false,\n hazardousDescription,\n parameters,\n onSend,\n history = [],\n showHistory = true,\n showPreview = true,\n disabled = false,\n height,\n className = \"\",\n bordered = true,\n style: styleProp,\n}: CommandBuilderProps): React.ReactElement {\n const { tokens, theme } = useTheme();\n const isTransparentTheme =\n theme === \"transparent\" ||\n theme === \"transparent-bold\" ||\n theme === \"transparent-minimal\";\n const [values, setValues] = useState<Record<string, unknown>>(() => {\n const defaults: Record<string, unknown> = {};\n parameters.forEach((p) => {\n if (p.default !== undefined) defaults[p.name] = p.default;\n else if (p.type === \"boolean\") defaults[p.name] = false;\n else if (p.type === \"enum\" && p.options?.length)\n defaults[p.name] = p.options[0];\n else defaults[p.name] = \"\";\n });\n return defaults;\n });\n const [errors, setErrors] = useState<Record<string, string>>({});\n const [sending, setSending] = useState(false);\n const [showConfirm, setShowConfirm] = useState(false);\n const [showHistoryPanel, setShowHistoryPanel] = useState(false);\n\n const setValue = useCallback((name: string, value: unknown) => {\n setValues((prev) => ({ ...prev, [name]: value }));\n setErrors((prev) => {\n const next = { ...prev };\n delete next[name];\n return next;\n });\n }, []);\n\n const validate = useCallback((): boolean => {\n const errs: Record<string, string> = {};\n parameters.forEach((p) => {\n const val = values[p.name];\n const required = p.required !== false;\n if (required && (val === undefined || val === \"\" || val === null)) {\n errs[p.name] = \"Required\";\n return;\n }\n if (val === \"\" || val === undefined) return;\n if (\n (p.type === \"int\" || p.type === \"uint\" || p.type === \"float\") &&\n typeof val === \"string\"\n ) {\n const num = Number(val);\n if (isNaN(num)) {\n errs[p.name] = \"Must be a number\";\n return;\n }\n if (p.min !== undefined && num < p.min) {\n errs[p.name] = `Min: ${p.min}`;\n return;\n }\n if (p.max !== undefined && num > p.max) {\n errs[p.name] = `Max: ${p.max}`;\n return;\n }\n if (p.type === \"uint\" && num < 0) {\n errs[p.name] = \"Must be >= 0\";\n return;\n }\n if (p.type === \"int\" && !Number.isInteger(num)) {\n errs[p.name] = \"Must be integer\";\n return;\n }\n }\n if (p.type === \"string\" && p.pattern && typeof val === \"string\") {\n if (!new RegExp(p.pattern).test(val)) {\n errs[p.name] = \"Invalid format\";\n }\n }\n });\n setErrors(errs);\n return Object.keys(errs).length === 0;\n }, [parameters, values]);\n\n const handleSend = useCallback(async () => {\n if (!validate()) return;\n if (hazardous && !showConfirm) {\n setShowConfirm(true);\n return;\n }\n setShowConfirm(false);\n setSending(true);\n try {\n const processedValues: Record<string, unknown> = {};\n parameters.forEach((p) => {\n let val = values[p.name];\n if (\n (p.type === \"int\" || p.type === \"uint\" || p.type === \"float\") &&\n typeof val === \"string\"\n ) {\n val = Number(val);\n }\n processedValues[p.name] = val;\n });\n await onSend(processedValues);\n } finally {\n setSending(false);\n }\n }, [validate, hazardous, showConfirm, parameters, values, onSend]);\n\n const commandString = useMemo(() => {\n const params = parameters\n .map((p) => `${p.name}=${JSON.stringify(values[p.name] ?? \"\")}`)\n .join(\", \");\n return `cmd(\"${target} ${command}\" ${params ? `with ${params}` : \"\"})`;\n }, [target, command, parameters, values]);\n\n const resetToDefaults = useCallback(() => {\n const defaults: Record<string, unknown> = {};\n parameters.forEach((p) => {\n if (p.default !== undefined) defaults[p.name] = p.default;\n else if (p.type === \"boolean\") defaults[p.name] = false;\n else if (p.type === \"enum\" && p.options?.length)\n defaults[p.name] = p.options[0];\n else defaults[p.name] = \"\";\n });\n setValues(defaults);\n setErrors({});\n }, [parameters]);\n\n const inputStyle: React.CSSProperties = {\n background: tokens.colors.background.base,\n color: tokens.colors.text.primary,\n border: `1px solid ${tokens.colors.border.muted}`,\n borderRadius: tokens.borderRadius.md,\n padding: `${tokens.spacing.sm} ${tokens.spacing.smd}`,\n fontSize: tokens.typography.fontSize.xs,\n outline: \"none\",\n fontFamily: tokens.typography.fontFamily.mono,\n width: \"100%\",\n minHeight: 40,\n boxSizing: \"border-box\" as const,\n transition: \"border-color 150ms ease\",\n };\n\n return (\n <div\n className={`command-builder ${className}`}\n style={{\n height: typeof height === \"number\" ? height : undefined,\n background: tokens.colors.background.base,\n ...(bordered\n ? (tokens.colors.border.cardStyle ?? {\n border: `1px solid ${tokens.colors.border.muted}`,\n })\n : { border: \"none\" }),\n borderRadius: tokens.borderRadius.lg,\n overflow: \"hidden\",\n display: \"flex\",\n flexDirection: \"column\",\n fontFamily: tokens.typography.fontFamily.primary,\n color: tokens.colors.text.primary,\n backdropFilter: isTransparentTheme ? \"blur(10px)\" : undefined,\n WebkitBackdropFilter: isTransparentTheme ? \"blur(10px)\" : undefined,\n ...styleProp,\n }}\n >\n {/* Header */}\n <div\n style={{\n padding: `${tokens.spacing.sm} ${tokens.spacing.smd}`,\n borderBottom: `1px solid ${tokens.colors.border.muted}`,\n background: hazardous\n ? `${tokens.colors.status.critical}12`\n : tokens.colors.background.surface,\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"space-between\",\n }}\n >\n <div\n style={{\n display: \"flex\",\n alignItems: \"center\",\n gap: tokens.spacing.sm,\n }}\n >\n {hazardous && (\n <span\n role=\"img\"\n aria-label=\"Hazardous command\"\n style={{ fontSize: tokens.typography.fontSize.lg }}\n >\n ⚠\n </span>\n )}\n <span\n style={{\n background: `${tokens.colors.interactive.default}22`,\n color: tokens.colors.interactive.default,\n padding: `${tokens.spacing.xxs} ${tokens.spacing.sm}`,\n borderRadius: tokens.borderRadius.md,\n fontSize: tokens.typography.fontSize.xxs,\n fontWeight: tokens.typography.fontWeight.medium,\n }}\n >\n {target}\n </span>\n <span\n style={{\n fontWeight: tokens.typography.fontWeight.bold,\n fontSize: tokens.typography.fontSize.sm,\n }}\n >\n {command}\n </span>\n {hazardous && (\n <span\n style={{\n background: `${tokens.colors.status.critical}22`,\n color: tokens.colors.status.critical,\n padding: `${tokens.spacing.xxs} ${tokens.spacing.sm}`,\n borderRadius: tokens.borderRadius.sm,\n fontSize: tokens.typography.fontSize.micro,\n fontWeight: tokens.typography.fontWeight.bold,\n textTransform: \"uppercase\",\n letterSpacing: \"0.5px\",\n }}\n >\n Hazardous\n </span>\n )}\n </div>\n {showHistory && history.length > 0 && (\n <button\n onClick={() => setShowHistoryPanel((p) => !p)}\n aria-expanded={showHistoryPanel}\n aria-label={`Command history (${history.length} entries)`}\n style={{\n background: tokens.colors.background.base,\n color: tokens.colors.text.secondary,\n border: `1px solid ${tokens.colors.border.muted}`,\n borderRadius: tokens.borderRadius.md,\n padding: `${tokens.spacing.xxs} ${tokens.spacing.sm}`,\n fontSize: tokens.typography.fontSize.xxs,\n cursor: \"pointer\",\n transition: \"all 150ms ease\",\n }}\n >\n History ({history.length})\n </button>\n )}\n </div>\n\n {description && (\n <div\n style={{\n padding: `${tokens.spacing.sm} ${tokens.spacing.smd}`,\n fontSize: tokens.typography.fontSize.xs,\n color: tokens.colors.text.secondary,\n borderBottom: `1px solid ${tokens.colors.border.muted}`,\n lineHeight: tokens.typography.lineHeight.normal,\n }}\n >\n {description}\n </div>\n )}\n\n {/* Parameters form */}\n <div\n role=\"form\"\n aria-label={`${target} ${command} parameters`}\n style={{ flex: 1, overflow: \"auto\", padding: tokens.spacing.smd }}\n >\n {parameters.length === 0 ? (\n <div\n style={{\n color: tokens.colors.text.muted,\n fontSize: tokens.typography.fontSize.xs,\n textAlign: \"center\",\n padding: tokens.spacing.xl,\n }}\n >\n No parameters — command will be sent as-is\n </div>\n ) : (\n <div\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n gap: tokens.spacing.smd,\n }}\n >\n {parameters.map((p) => (\n <div key={p.name}>\n <div\n style={{\n display: \"flex\",\n alignItems: \"center\",\n gap: tokens.spacing.sm,\n marginBottom: tokens.spacing.xs,\n }}\n >\n <label\n htmlFor={`cmd-param-${p.name}`}\n style={{\n fontWeight: tokens.typography.fontWeight.medium,\n fontSize: tokens.typography.fontSize.xs,\n color: tokens.colors.text.primary,\n }}\n >\n {p.name}\n {p.required !== false && (\n <span\n aria-label=\"required\"\n style={{\n color: tokens.colors.status.critical,\n marginLeft: tokens.spacing.xxs,\n }}\n >\n *\n </span>\n )}\n </label>\n <span\n style={{\n fontSize: tokens.typography.fontSize.xxs,\n color: tokens.colors.text.muted,\n background: tokens.colors.background.surface,\n padding: `1px ${tokens.spacing.xs}`,\n borderRadius: tokens.borderRadius.sm,\n }}\n >\n {p.type}\n {p.bitSize ? ` (${p.bitSize}b)` : \"\"}\n </span>\n {p.units && (\n <span\n style={{\n fontSize: tokens.typography.fontSize.xxs,\n color: tokens.colors.interactive.default,\n }}\n >\n [{p.units}]\n </span>\n )}\n {p.hazardous && (\n <span\n aria-label=\"Hazardous parameter\"\n style={{\n fontSize: tokens.typography.fontSize.xxs,\n color: tokens.colors.status.critical,\n }}\n >\n ⚠\n </span>\n )}\n </div>\n {p.description && (\n <div\n id={`cmd-param-desc-${p.name}`}\n style={{\n fontSize: tokens.typography.fontSize.xxs,\n color: tokens.colors.text.muted,\n marginBottom: tokens.spacing.xs,\n lineHeight: tokens.typography.lineHeight.tight,\n }}\n >\n {p.description}\n </div>\n )}\n\n {/* Input by type */}\n {p.type === \"boolean\" ? (\n <label\n style={{\n display: \"flex\",\n alignItems: \"center\",\n gap: tokens.spacing.sm,\n fontSize: tokens.typography.fontSize.xs,\n cursor: \"pointer\",\n }}\n >\n <input\n id={`cmd-param-${p.name}`}\n type=\"checkbox\"\n checked={!!values[p.name]}\n onChange={(e) => setValue(p.name, e.target.checked)}\n aria-describedby={\n p.description ? `cmd-param-desc-${p.name}` : undefined\n }\n style={{ accentColor: tokens.colors.interactive.default }}\n />\n {values[p.name] ? \"TRUE\" : \"FALSE\"}\n </label>\n ) : p.type === \"enum\" ? (\n <select\n id={`cmd-param-${p.name}`}\n value={String(values[p.name] ?? \"\")}\n onChange={(e) => setValue(p.name, e.target.value)}\n aria-describedby={\n p.description ? `cmd-param-desc-${p.name}` : undefined\n }\n style={inputStyle}\n >\n {p.options?.map((opt) => (\n <option key={String(opt)} value={String(opt)}>\n {String(opt)}\n </option>\n ))}\n </select>\n ) : (\n <input\n id={`cmd-param-${p.name}`}\n type={\n p.type === \"int\" ||\n p.type === \"uint\" ||\n p.type === \"float\"\n ? \"number\"\n : \"text\"\n }\n value={String(values[p.name] ?? \"\")}\n onChange={(e) => setValue(p.name, e.target.value)}\n placeholder={\n p.default !== undefined\n ? `Default: ${p.default}`\n : undefined\n }\n min={p.min}\n max={p.max}\n step={p.type === \"float\" ? \"any\" : 1}\n aria-describedby={\n p.description ? `cmd-param-desc-${p.name}` : undefined\n }\n aria-invalid={!!errors[p.name]}\n style={{\n ...inputStyle,\n borderColor: errors[p.name]\n ? tokens.colors.status.critical\n : tokens.colors.border.muted,\n }}\n />\n )}\n {errors[p.name] && (\n <span\n role=\"alert\"\n style={{\n display: \"block\",\n fontSize: tokens.typography.fontSize.xxs,\n color: tokens.colors.status.critical,\n marginTop: tokens.spacing.xxs,\n }}\n >\n {errors[p.name]}\n </span>\n )}\n </div>\n ))}\n </div>\n )}\n </div>\n\n {/* Command preview */}\n {showPreview && (\n <div\n aria-label=\"Command preview\"\n style={{\n padding: `${tokens.spacing.sm} ${tokens.spacing.smd}`,\n borderTop: `1px solid ${tokens.colors.border.muted}`,\n background: tokens.colors.background.surface,\n fontFamily: tokens.typography.fontFamily.mono,\n fontSize: tokens.typography.fontSize.xxs,\n color: tokens.colors.text.muted,\n overflow: \"hidden\",\n textOverflow: \"ellipsis\",\n whiteSpace: \"nowrap\",\n }}\n >\n {commandString}\n </div>\n )}\n\n {/* Hazardous confirmation */}\n {showConfirm && (\n <div\n role=\"alertdialog\"\n aria-label=\"Confirm hazardous command\"\n style={{\n padding: tokens.spacing.smd,\n borderTop: `1px solid ${tokens.colors.status.critical}44`,\n background: `${tokens.colors.status.critical}12`,\n }}\n >\n <div\n style={{\n fontWeight: tokens.typography.fontWeight.bold,\n color: tokens.colors.status.critical,\n marginBottom: tokens.spacing.sm,\n fontSize: tokens.typography.fontSize.sm,\n }}\n >\n Confirm Hazardous Command\n </div>\n <p\n style={{\n fontSize: tokens.typography.fontSize.xs,\n color: tokens.colors.text.secondary,\n marginBottom: tokens.spacing.smd,\n lineHeight: tokens.typography.lineHeight.normal,\n }}\n >\n {hazardousDescription ||\n `Are you sure you want to send ${target} ${command}?`}\n </p>\n <div style={{ display: \"flex\", gap: tokens.spacing.sm }}>\n <button\n onClick={handleSend}\n style={{\n background: tokens.colors.status.critical,\n color: tokens.colors.text.inverse,\n border: \"none\",\n borderRadius: tokens.borderRadius.md,\n padding: `${tokens.spacing.sm} ${tokens.spacing.md}`,\n fontSize: tokens.typography.fontSize.xs,\n fontWeight: tokens.typography.fontWeight.medium,\n cursor: \"pointer\",\n transition: \"opacity 150ms ease\",\n }}\n >\n Confirm Send\n </button>\n <button\n onClick={() => setShowConfirm(false)}\n style={{\n background: tokens.colors.background.base,\n color: tokens.colors.text.secondary,\n border: `1px solid ${tokens.colors.border.muted}`,\n borderRadius: tokens.borderRadius.md,\n padding: `${tokens.spacing.sm} ${tokens.spacing.md}`,\n fontSize: tokens.typography.fontSize.xs,\n cursor: \"pointer\",\n transition: \"opacity 150ms ease\",\n }}\n >\n Cancel\n </button>\n </div>\n </div>\n )}\n\n {/* Send button bar */}\n {!showConfirm && (\n <div\n style={{\n padding: `${tokens.spacing.sm} ${tokens.spacing.smd}`,\n borderTop: `1px solid ${tokens.colors.border.muted}`,\n background: tokens.colors.background.surface,\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"space-between\",\n }}\n >\n <button\n onClick={resetToDefaults}\n aria-label=\"Reset all parameters to defaults\"\n style={{\n background: \"transparent\",\n color: tokens.colors.text.muted,\n border: `1px solid ${tokens.colors.border.muted}`,\n borderRadius: tokens.borderRadius.md,\n padding: `${tokens.spacing.xs} ${tokens.spacing.sm}`,\n fontSize: tokens.typography.fontSize.xxs,\n cursor: \"pointer\",\n transition: \"all 150ms ease\",\n }}\n >\n Reset Defaults\n </button>\n <button\n onClick={handleSend}\n disabled={disabled || sending}\n aria-busy={sending}\n style={{\n background: hazardous\n ? tokens.colors.status.critical\n : tokens.colors.interactive.default,\n color: tokens.colors.text.inverse,\n border: \"none\",\n borderRadius: tokens.borderRadius.md,\n padding: `${tokens.spacing.sm} ${tokens.spacing.lg}`,\n fontSize: tokens.typography.fontSize.sm,\n fontWeight: tokens.typography.fontWeight.medium,\n cursor: disabled || sending ? \"not-allowed\" : \"pointer\",\n opacity: disabled || sending ? 0.5 : 1,\n transition: \"opacity 150ms ease\",\n }}\n >\n {sending ? \"Sending...\" : \"Send Command\"}\n </button>\n </div>\n )}\n\n {/* History panel */}\n {showHistoryPanel && history.length > 0 && (\n <div\n role=\"list\"\n aria-label=\"Command history\"\n style={{\n borderTop: `1px solid ${tokens.colors.border.muted}`,\n background: tokens.colors.background.surface,\n maxHeight: 200,\n overflow: \"auto\",\n }}\n >\n <div\n style={{\n padding: `${tokens.spacing.sm} ${tokens.spacing.smd}`,\n fontSize: tokens.typography.fontSize.xxs,\n color: tokens.colors.text.muted,\n fontWeight: tokens.typography.fontWeight.medium,\n textTransform: \"uppercase\",\n letterSpacing: \"0.5px\",\n }}\n >\n Command History\n </div>\n {history\n .slice()\n .reverse()\n .map((h) => (\n <div\n role=\"listitem\"\n key={h.id}\n style={{\n borderBottom: `1px solid ${tokens.colors.border.muted}`,\n }}\n >\n <button\n type=\"button\"\n aria-label={`${h.status} at ${h.timestamp.toLocaleTimeString()}`}\n style={{\n width: \"100%\",\n textAlign: \"left\",\n padding: `${tokens.spacing.sm} ${tokens.spacing.smd}`,\n fontSize: tokens.typography.fontSize.xxs,\n display: \"flex\",\n alignItems: \"center\",\n gap: tokens.spacing.sm,\n cursor: \"pointer\",\n transition: \"background 150ms ease\",\n border: \"none\",\n background: \"transparent\",\n color: \"inherit\",\n }}\n onClick={() => setValues(h.parameters)}\n >\n {/* Astro UX status shape */}\n <span\n aria-hidden=\"true\"\n style={{\n width: 8,\n height: 8,\n flexShrink: 0,\n ...(h.status === \"success\"\n ? {\n borderRadius: tokens.borderRadius.full,\n background: tokens.colors.status.normal,\n }\n : h.status === \"error\"\n ? {\n borderRadius: 0,\n background: tokens.colors.status.critical,\n clipPath: \"polygon(50% 100%, 0% 0%, 100% 0%)\",\n }\n : {\n borderRadius: tokens.borderRadius.full,\n background: \"transparent\",\n border: `2px solid ${tokens.colors.status.standby}`,\n }),\n }}\n />\n <span\n style={{\n color: tokens.colors.text.muted,\n fontFamily: tokens.typography.fontFamily.mono,\n fontSize: tokens.typography.fontSize.xxs,\n flexShrink: 0,\n }}\n >\n {h.timestamp.toLocaleTimeString()}\n </span>\n <span\n style={{\n fontFamily: tokens.typography.fontFamily.mono,\n fontSize: tokens.typography.fontSize.xxs,\n flex: 1,\n overflow: \"hidden\",\n textOverflow: \"ellipsis\",\n whiteSpace: \"nowrap\",\n }}\n >\n {Object.entries(h.parameters)\n .map(([k, v]) => `${k}=${JSON.stringify(v)}`)\n .join(\", \")}\n </span>\n {h.error && (\n <span\n style={{\n fontSize: tokens.typography.fontSize.micro,\n color: tokens.colors.status.critical,\n }}\n >\n {h.error}\n </span>\n )}\n </button>\n </div>\n ))}\n </div>\n )}\n </div>\n );\n});\n\nexport default CommandBuilder;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CommandBuilder.js","names":[],"sources":["../../../../src/react/core/widgets/CommandBuilder.tsx"],"sourcesContent":["/**\r\n * @zendir/ui - CommandBuilder Component\r\n *\r\n * Dynamic command parameter form for spacecraft command construction and execution.\r\n * Builds a form from a command definition with type-appropriate inputs,\r\n * validation, hazardous warnings, and send/history functionality.\r\n *\r\n * Features:\r\n * - Auto-generated form from command parameter definitions\r\n * - Type-appropriate inputs (number, string, boolean, enum/state, hex)\r\n * - Validation with min/max/regex constraints\r\n * - Hazardous command warnings\r\n * - Command history with re-send\r\n * - Raw command preview\r\n * - Required/optional parameter indicators\r\n * - Default values\r\n *\r\n * @example\r\n * ```tsx\r\n * <CommandBuilder\r\n * target=\"INST\"\r\n * command=\"COLLECT\"\r\n * parameters={[\r\n * { name: 'TYPE', type: 'enum', options: ['NORMAL', 'SPECIAL'], required: true },\r\n * { name: 'DURATION', type: 'float', min: 0, max: 100, default: 1.0, units: 'sec' },\r\n * ]}\r\n * onSend={(params) => console.log('Send:', params)}\r\n * />\r\n * ```\r\n */\r\n\r\nimport React, { useState, useCallback, useMemo, memo } from \"react\";\r\nimport { useTheme } from \"../../theme\";\r\n\r\n// =============================================================================\r\n// Types\r\n// =============================================================================\r\n\r\nexport type CommandParamType =\r\n | \"int\"\r\n | \"uint\"\r\n | \"float\"\r\n | \"string\"\r\n | \"boolean\"\r\n | \"enum\"\r\n | \"hex\"\r\n | \"block\";\r\n\r\nexport interface CommandParameter {\r\n /** Parameter name */\r\n name: string;\r\n /** Parameter type */\r\n type: CommandParamType;\r\n /** Description */\r\n description?: string;\r\n /** Default value */\r\n default?: unknown;\r\n /** Whether parameter is required (default: true) */\r\n required?: boolean;\r\n /** Minimum value (for numeric types) */\r\n min?: number;\r\n /** Maximum value (for numeric types) */\r\n max?: number;\r\n /** Bit size */\r\n bitSize?: number;\r\n /** Enum options (for enum type) */\r\n options?: (string | number)[];\r\n /** Units label */\r\n units?: string;\r\n /** Validation regex pattern (for string type) */\r\n pattern?: string;\r\n /** Whether this is a hazardous parameter */\r\n hazardous?: boolean;\r\n /** Hazardous description */\r\n hazardousDescription?: string;\r\n}\r\n\r\nexport interface CommandHistoryEntry {\r\n id: string;\r\n timestamp: Date;\r\n target: string;\r\n command: string;\r\n parameters: Record<string, unknown>;\r\n status: \"sent\" | \"success\" | \"error\";\r\n error?: string;\r\n}\r\n\r\nexport interface CommandBuilderProps {\r\n /** Target name */\r\n target: string;\r\n /** Command name */\r\n command: string;\r\n /** Command description */\r\n description?: string;\r\n /** Whether this is a hazardous command */\r\n hazardous?: boolean;\r\n /** Hazardous warning text */\r\n hazardousDescription?: string;\r\n /** Command parameters */\r\n parameters: CommandParameter[];\r\n /** Called when command is sent */\r\n onSend: (params: Record<string, unknown>) => void | Promise<void>;\r\n /** Command history entries */\r\n history?: CommandHistoryEntry[];\r\n /** Show command history (default: true) */\r\n showHistory?: boolean;\r\n /** Show raw command preview (default: true) */\r\n showPreview?: boolean;\r\n /** Whether send is disabled */\r\n disabled?: boolean;\r\n /** Height (default: auto) */\r\n height?: number | string;\r\n /** CSS class */\r\n className?: string;\r\n /** Show outer container border (default: true). Set false when embedded inside a Container/card. */\r\n bordered?: boolean;\r\n /** Custom style overrides for the outer container */\r\n style?: React.CSSProperties;\r\n}\r\n\r\n// =============================================================================\r\n// Component\r\n// =============================================================================\r\n\r\nexport const CommandBuilder = memo(function CommandBuilder({\r\n target,\r\n command,\r\n description,\r\n hazardous = false,\r\n hazardousDescription,\r\n parameters,\r\n onSend,\r\n history = [],\r\n showHistory = true,\r\n showPreview = true,\r\n disabled = false,\r\n height,\r\n className = \"\",\r\n bordered = true,\r\n style: styleProp,\r\n}: CommandBuilderProps): React.ReactElement {\r\n const { tokens, theme } = useTheme();\r\n const isTransparentTheme =\r\n theme === \"transparent\" ||\r\n theme === \"transparent-bold\" ||\r\n theme === \"transparent-minimal\";\r\n const [values, setValues] = useState<Record<string, unknown>>(() => {\r\n const defaults: Record<string, unknown> = {};\r\n parameters.forEach((p) => {\r\n if (p.default !== undefined) defaults[p.name] = p.default;\r\n else if (p.type === \"boolean\") defaults[p.name] = false;\r\n else if (p.type === \"enum\" && p.options?.length)\r\n defaults[p.name] = p.options[0];\r\n else defaults[p.name] = \"\";\r\n });\r\n return defaults;\r\n });\r\n const [errors, setErrors] = useState<Record<string, string>>({});\r\n const [sending, setSending] = useState(false);\r\n const [showConfirm, setShowConfirm] = useState(false);\r\n const [showHistoryPanel, setShowHistoryPanel] = useState(false);\r\n\r\n const setValue = useCallback((name: string, value: unknown) => {\r\n setValues((prev) => ({ ...prev, [name]: value }));\r\n setErrors((prev) => {\r\n const next = { ...prev };\r\n delete next[name];\r\n return next;\r\n });\r\n }, []);\r\n\r\n const validate = useCallback((): boolean => {\r\n const errs: Record<string, string> = {};\r\n parameters.forEach((p) => {\r\n const val = values[p.name];\r\n const required = p.required !== false;\r\n if (required && (val === undefined || val === \"\" || val === null)) {\r\n errs[p.name] = \"Required\";\r\n return;\r\n }\r\n if (val === \"\" || val === undefined) return;\r\n if (\r\n (p.type === \"int\" || p.type === \"uint\" || p.type === \"float\") &&\r\n typeof val === \"string\"\r\n ) {\r\n const num = Number(val);\r\n if (isNaN(num)) {\r\n errs[p.name] = \"Must be a number\";\r\n return;\r\n }\r\n if (p.min !== undefined && num < p.min) {\r\n errs[p.name] = `Min: ${p.min}`;\r\n return;\r\n }\r\n if (p.max !== undefined && num > p.max) {\r\n errs[p.name] = `Max: ${p.max}`;\r\n return;\r\n }\r\n if (p.type === \"uint\" && num < 0) {\r\n errs[p.name] = \"Must be >= 0\";\r\n return;\r\n }\r\n if (p.type === \"int\" && !Number.isInteger(num)) {\r\n errs[p.name] = \"Must be integer\";\r\n return;\r\n }\r\n }\r\n if (p.type === \"string\" && p.pattern && typeof val === \"string\") {\r\n if (!new RegExp(p.pattern).test(val)) {\r\n errs[p.name] = \"Invalid format\";\r\n }\r\n }\r\n });\r\n setErrors(errs);\r\n return Object.keys(errs).length === 0;\r\n }, [parameters, values]);\r\n\r\n const handleSend = useCallback(async () => {\r\n if (!validate()) return;\r\n if (hazardous && !showConfirm) {\r\n setShowConfirm(true);\r\n return;\r\n }\r\n setShowConfirm(false);\r\n setSending(true);\r\n try {\r\n const processedValues: Record<string, unknown> = {};\r\n parameters.forEach((p) => {\r\n let val = values[p.name];\r\n if (\r\n (p.type === \"int\" || p.type === \"uint\" || p.type === \"float\") &&\r\n typeof val === \"string\"\r\n ) {\r\n val = Number(val);\r\n }\r\n processedValues[p.name] = val;\r\n });\r\n await onSend(processedValues);\r\n } finally {\r\n setSending(false);\r\n }\r\n }, [validate, hazardous, showConfirm, parameters, values, onSend]);\r\n\r\n const commandString = useMemo(() => {\r\n const params = parameters\r\n .map((p) => `${p.name}=${JSON.stringify(values[p.name] ?? \"\")}`)\r\n .join(\", \");\r\n return `cmd(\"${target} ${command}\" ${params ? `with ${params}` : \"\"})`;\r\n }, [target, command, parameters, values]);\r\n\r\n const resetToDefaults = useCallback(() => {\r\n const defaults: Record<string, unknown> = {};\r\n parameters.forEach((p) => {\r\n if (p.default !== undefined) defaults[p.name] = p.default;\r\n else if (p.type === \"boolean\") defaults[p.name] = false;\r\n else if (p.type === \"enum\" && p.options?.length)\r\n defaults[p.name] = p.options[0];\r\n else defaults[p.name] = \"\";\r\n });\r\n setValues(defaults);\r\n setErrors({});\r\n }, [parameters]);\r\n\r\n const inputStyle: React.CSSProperties = {\r\n background: tokens.colors.background.base,\r\n color: tokens.colors.text.primary,\r\n border: `1px solid ${tokens.colors.border.muted}`,\r\n borderRadius: tokens.borderRadius.md,\r\n padding: `${tokens.spacing.sm} ${tokens.spacing.smd}`,\r\n fontSize: tokens.typography.fontSize.xs,\r\n outline: \"none\",\r\n fontFamily: tokens.typography.fontFamily.mono,\r\n width: \"100%\",\r\n minHeight: 40,\r\n boxSizing: \"border-box\" as const,\r\n transition: \"border-color 150ms ease\",\r\n };\r\n\r\n return (\r\n <div\r\n className={`command-builder ${className}`}\r\n style={{\r\n height: typeof height === \"number\" ? height : undefined,\r\n background: tokens.colors.background.base,\r\n ...(bordered\r\n ? (tokens.colors.border.cardStyle ?? {\r\n border: `1px solid ${tokens.colors.border.muted}`,\r\n })\r\n : { border: \"none\" }),\r\n borderRadius: tokens.borderRadius.lg,\r\n overflow: \"hidden\",\r\n display: \"flex\",\r\n flexDirection: \"column\",\r\n fontFamily: tokens.typography.fontFamily.primary,\r\n color: tokens.colors.text.primary,\r\n backdropFilter: isTransparentTheme ? \"blur(10px)\" : undefined,\r\n WebkitBackdropFilter: isTransparentTheme ? \"blur(10px)\" : undefined,\r\n ...styleProp,\r\n }}\r\n >\r\n {/* Header */}\r\n <div\r\n style={{\r\n padding: `${tokens.spacing.sm} ${tokens.spacing.smd}`,\r\n borderBottom: `1px solid ${tokens.colors.border.muted}`,\r\n background: hazardous\r\n ? `${tokens.colors.status.critical}12`\r\n : tokens.colors.background.surface,\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n justifyContent: \"space-between\",\r\n }}\r\n >\r\n <div\r\n style={{\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n gap: tokens.spacing.sm,\r\n }}\r\n >\r\n {hazardous && (\r\n <span\r\n role=\"img\"\r\n aria-label=\"Hazardous command\"\r\n style={{ fontSize: tokens.typography.fontSize.lg }}\r\n >\r\n ⚠\r\n </span>\r\n )}\r\n <span\r\n style={{\r\n background: `${tokens.colors.interactive.default}22`,\r\n color: tokens.colors.interactive.default,\r\n padding: `${tokens.spacing.xxs} ${tokens.spacing.sm}`,\r\n borderRadius: tokens.borderRadius.md,\r\n fontSize: tokens.typography.fontSize.xxs,\r\n fontWeight: tokens.typography.fontWeight.medium,\r\n }}\r\n >\r\n {target}\r\n </span>\r\n <span\r\n style={{\r\n fontWeight: tokens.typography.fontWeight.bold,\r\n fontSize: tokens.typography.fontSize.sm,\r\n }}\r\n >\r\n {command}\r\n </span>\r\n {hazardous && (\r\n <span\r\n style={{\r\n background: `${tokens.colors.status.critical}22`,\r\n color: tokens.colors.status.critical,\r\n padding: `${tokens.spacing.xxs} ${tokens.spacing.sm}`,\r\n borderRadius: tokens.borderRadius.sm,\r\n fontSize: tokens.typography.fontSize.micro,\r\n fontWeight: tokens.typography.fontWeight.bold,\r\n textTransform: \"uppercase\",\r\n letterSpacing: \"0.5px\",\r\n }}\r\n >\r\n Hazardous\r\n </span>\r\n )}\r\n </div>\r\n {showHistory && history.length > 0 && (\r\n <button\r\n onClick={() => setShowHistoryPanel((p) => !p)}\r\n aria-expanded={showHistoryPanel}\r\n aria-label={`Command history (${history.length} entries)`}\r\n style={{\r\n background: tokens.colors.background.base,\r\n color: tokens.colors.text.secondary,\r\n border: `1px solid ${tokens.colors.border.muted}`,\r\n borderRadius: tokens.borderRadius.md,\r\n padding: `${tokens.spacing.xxs} ${tokens.spacing.sm}`,\r\n fontSize: tokens.typography.fontSize.xxs,\r\n cursor: \"pointer\",\r\n transition: \"all 150ms ease\",\r\n }}\r\n >\r\n History ({history.length})\r\n </button>\r\n )}\r\n </div>\r\n\r\n {description && (\r\n <div\r\n style={{\r\n padding: `${tokens.spacing.sm} ${tokens.spacing.smd}`,\r\n fontSize: tokens.typography.fontSize.xs,\r\n color: tokens.colors.text.secondary,\r\n borderBottom: `1px solid ${tokens.colors.border.muted}`,\r\n lineHeight: tokens.typography.lineHeight.normal,\r\n }}\r\n >\r\n {description}\r\n </div>\r\n )}\r\n\r\n {/* Parameters form */}\r\n <div\r\n role=\"form\"\r\n aria-label={`${target} ${command} parameters`}\r\n style={{ flex: 1, overflow: \"auto\", padding: tokens.spacing.smd }}\r\n >\r\n {parameters.length === 0 ? (\r\n <div\r\n style={{\r\n color: tokens.colors.text.muted,\r\n fontSize: tokens.typography.fontSize.xs,\r\n textAlign: \"center\",\r\n padding: tokens.spacing.xl,\r\n }}\r\n >\r\n No parameters — command will be sent as-is\r\n </div>\r\n ) : (\r\n <div\r\n style={{\r\n display: \"flex\",\r\n flexDirection: \"column\",\r\n gap: tokens.spacing.smd,\r\n }}\r\n >\r\n {parameters.map((p) => (\r\n <div key={p.name}>\r\n <div\r\n style={{\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n gap: tokens.spacing.sm,\r\n marginBottom: tokens.spacing.xs,\r\n }}\r\n >\r\n <label\r\n htmlFor={`cmd-param-${p.name}`}\r\n style={{\r\n fontWeight: tokens.typography.fontWeight.medium,\r\n fontSize: tokens.typography.fontSize.xs,\r\n color: tokens.colors.text.primary,\r\n }}\r\n >\r\n {p.name}\r\n {p.required !== false && (\r\n <span\r\n aria-label=\"required\"\r\n style={{\r\n color: tokens.colors.status.critical,\r\n marginLeft: tokens.spacing.xxs,\r\n }}\r\n >\r\n *\r\n </span>\r\n )}\r\n </label>\r\n <span\r\n style={{\r\n fontSize: tokens.typography.fontSize.xxs,\r\n color: tokens.colors.text.muted,\r\n background: tokens.colors.background.surface,\r\n padding: `1px ${tokens.spacing.xs}`,\r\n borderRadius: tokens.borderRadius.sm,\r\n }}\r\n >\r\n {p.type}\r\n {p.bitSize ? ` (${p.bitSize}b)` : \"\"}\r\n </span>\r\n {p.units && (\r\n <span\r\n style={{\r\n fontSize: tokens.typography.fontSize.xxs,\r\n color: tokens.colors.interactive.default,\r\n }}\r\n >\r\n [{p.units}]\r\n </span>\r\n )}\r\n {p.hazardous && (\r\n <span\r\n aria-label=\"Hazardous parameter\"\r\n style={{\r\n fontSize: tokens.typography.fontSize.xxs,\r\n color: tokens.colors.status.critical,\r\n }}\r\n >\r\n ⚠\r\n </span>\r\n )}\r\n </div>\r\n {p.description && (\r\n <div\r\n id={`cmd-param-desc-${p.name}`}\r\n style={{\r\n fontSize: tokens.typography.fontSize.xxs,\r\n color: tokens.colors.text.muted,\r\n marginBottom: tokens.spacing.xs,\r\n lineHeight: tokens.typography.lineHeight.tight,\r\n }}\r\n >\r\n {p.description}\r\n </div>\r\n )}\r\n\r\n {/* Input by type */}\r\n {p.type === \"boolean\" ? (\r\n <label\r\n style={{\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n gap: tokens.spacing.sm,\r\n fontSize: tokens.typography.fontSize.xs,\r\n cursor: \"pointer\",\r\n }}\r\n >\r\n <input\r\n id={`cmd-param-${p.name}`}\r\n type=\"checkbox\"\r\n checked={!!values[p.name]}\r\n onChange={(e) => setValue(p.name, e.target.checked)}\r\n aria-describedby={\r\n p.description ? `cmd-param-desc-${p.name}` : undefined\r\n }\r\n style={{ accentColor: tokens.colors.interactive.default }}\r\n />\r\n {values[p.name] ? \"TRUE\" : \"FALSE\"}\r\n </label>\r\n ) : p.type === \"enum\" ? (\r\n <select\r\n id={`cmd-param-${p.name}`}\r\n value={String(values[p.name] ?? \"\")}\r\n onChange={(e) => setValue(p.name, e.target.value)}\r\n aria-describedby={\r\n p.description ? `cmd-param-desc-${p.name}` : undefined\r\n }\r\n style={inputStyle}\r\n >\r\n {p.options?.map((opt) => (\r\n <option key={String(opt)} value={String(opt)}>\r\n {String(opt)}\r\n </option>\r\n ))}\r\n </select>\r\n ) : (\r\n <input\r\n id={`cmd-param-${p.name}`}\r\n type={\r\n p.type === \"int\" ||\r\n p.type === \"uint\" ||\r\n p.type === \"float\"\r\n ? \"number\"\r\n : \"text\"\r\n }\r\n value={String(values[p.name] ?? \"\")}\r\n onChange={(e) => setValue(p.name, e.target.value)}\r\n placeholder={\r\n p.default !== undefined\r\n ? `Default: ${p.default}`\r\n : undefined\r\n }\r\n min={p.min}\r\n max={p.max}\r\n step={p.type === \"float\" ? \"any\" : 1}\r\n aria-describedby={\r\n p.description ? `cmd-param-desc-${p.name}` : undefined\r\n }\r\n aria-invalid={!!errors[p.name]}\r\n style={{\r\n ...inputStyle,\r\n borderColor: errors[p.name]\r\n ? tokens.colors.status.critical\r\n : tokens.colors.border.muted,\r\n }}\r\n />\r\n )}\r\n {errors[p.name] && (\r\n <span\r\n role=\"alert\"\r\n style={{\r\n display: \"block\",\r\n fontSize: tokens.typography.fontSize.xxs,\r\n color: tokens.colors.status.critical,\r\n marginTop: tokens.spacing.xxs,\r\n }}\r\n >\r\n {errors[p.name]}\r\n </span>\r\n )}\r\n </div>\r\n ))}\r\n </div>\r\n )}\r\n </div>\r\n\r\n {/* Command preview */}\r\n {showPreview && (\r\n <div\r\n aria-label=\"Command preview\"\r\n style={{\r\n padding: `${tokens.spacing.sm} ${tokens.spacing.smd}`,\r\n borderTop: `1px solid ${tokens.colors.border.muted}`,\r\n background: tokens.colors.background.surface,\r\n fontFamily: tokens.typography.fontFamily.mono,\r\n fontSize: tokens.typography.fontSize.xxs,\r\n color: tokens.colors.text.muted,\r\n overflow: \"hidden\",\r\n textOverflow: \"ellipsis\",\r\n whiteSpace: \"nowrap\",\r\n }}\r\n >\r\n {commandString}\r\n </div>\r\n )}\r\n\r\n {/* Hazardous confirmation */}\r\n {showConfirm && (\r\n <div\r\n role=\"alertdialog\"\r\n aria-label=\"Confirm hazardous command\"\r\n style={{\r\n padding: tokens.spacing.smd,\r\n borderTop: `1px solid ${tokens.colors.status.critical}44`,\r\n background: `${tokens.colors.status.critical}12`,\r\n }}\r\n >\r\n <div\r\n style={{\r\n fontWeight: tokens.typography.fontWeight.bold,\r\n color: tokens.colors.status.critical,\r\n marginBottom: tokens.spacing.sm,\r\n fontSize: tokens.typography.fontSize.sm,\r\n }}\r\n >\r\n Confirm Hazardous Command\r\n </div>\r\n <p\r\n style={{\r\n fontSize: tokens.typography.fontSize.xs,\r\n color: tokens.colors.text.secondary,\r\n marginBottom: tokens.spacing.smd,\r\n lineHeight: tokens.typography.lineHeight.normal,\r\n }}\r\n >\r\n {hazardousDescription ||\r\n `Are you sure you want to send ${target} ${command}?`}\r\n </p>\r\n <div style={{ display: \"flex\", gap: tokens.spacing.sm }}>\r\n <button\r\n onClick={handleSend}\r\n style={{\r\n background: tokens.colors.status.critical,\r\n color: tokens.colors.text.inverse,\r\n border: \"none\",\r\n borderRadius: tokens.borderRadius.md,\r\n padding: `${tokens.spacing.sm} ${tokens.spacing.md}`,\r\n fontSize: tokens.typography.fontSize.xs,\r\n fontWeight: tokens.typography.fontWeight.medium,\r\n cursor: \"pointer\",\r\n transition: \"opacity 150ms ease\",\r\n }}\r\n >\r\n Confirm Send\r\n </button>\r\n <button\r\n onClick={() => setShowConfirm(false)}\r\n style={{\r\n background: tokens.colors.background.base,\r\n color: tokens.colors.text.secondary,\r\n border: `1px solid ${tokens.colors.border.muted}`,\r\n borderRadius: tokens.borderRadius.md,\r\n padding: `${tokens.spacing.sm} ${tokens.spacing.md}`,\r\n fontSize: tokens.typography.fontSize.xs,\r\n cursor: \"pointer\",\r\n transition: \"opacity 150ms ease\",\r\n }}\r\n >\r\n Cancel\r\n </button>\r\n </div>\r\n </div>\r\n )}\r\n\r\n {/* Send button bar */}\r\n {!showConfirm && (\r\n <div\r\n style={{\r\n padding: `${tokens.spacing.sm} ${tokens.spacing.smd}`,\r\n borderTop: `1px solid ${tokens.colors.border.muted}`,\r\n background: tokens.colors.background.surface,\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n justifyContent: \"space-between\",\r\n }}\r\n >\r\n <button\r\n onClick={resetToDefaults}\r\n aria-label=\"Reset all parameters to defaults\"\r\n style={{\r\n background: \"transparent\",\r\n color: tokens.colors.text.muted,\r\n border: `1px solid ${tokens.colors.border.muted}`,\r\n borderRadius: tokens.borderRadius.md,\r\n padding: `${tokens.spacing.xs} ${tokens.spacing.sm}`,\r\n fontSize: tokens.typography.fontSize.xxs,\r\n cursor: \"pointer\",\r\n transition: \"all 150ms ease\",\r\n }}\r\n >\r\n Reset Defaults\r\n </button>\r\n <button\r\n onClick={handleSend}\r\n disabled={disabled || sending}\r\n aria-busy={sending}\r\n style={{\r\n background: hazardous\r\n ? tokens.colors.status.critical\r\n : tokens.colors.interactive.default,\r\n color: tokens.colors.text.inverse,\r\n border: \"none\",\r\n borderRadius: tokens.borderRadius.md,\r\n padding: `${tokens.spacing.sm} ${tokens.spacing.lg}`,\r\n fontSize: tokens.typography.fontSize.sm,\r\n fontWeight: tokens.typography.fontWeight.medium,\r\n cursor: disabled || sending ? \"not-allowed\" : \"pointer\",\r\n opacity: disabled || sending ? 0.5 : 1,\r\n transition: \"opacity 150ms ease\",\r\n }}\r\n >\r\n {sending ? \"Sending...\" : \"Send Command\"}\r\n </button>\r\n </div>\r\n )}\r\n\r\n {/* History panel */}\r\n {showHistoryPanel && history.length > 0 && (\r\n <div\r\n role=\"list\"\r\n aria-label=\"Command history\"\r\n style={{\r\n borderTop: `1px solid ${tokens.colors.border.muted}`,\r\n background: tokens.colors.background.surface,\r\n maxHeight: 200,\r\n overflow: \"auto\",\r\n }}\r\n >\r\n <div\r\n style={{\r\n padding: `${tokens.spacing.sm} ${tokens.spacing.smd}`,\r\n fontSize: tokens.typography.fontSize.xxs,\r\n color: tokens.colors.text.muted,\r\n fontWeight: tokens.typography.fontWeight.medium,\r\n textTransform: \"uppercase\",\r\n letterSpacing: \"0.5px\",\r\n }}\r\n >\r\n Command History\r\n </div>\r\n {history\r\n .slice()\r\n .reverse()\r\n .map((h) => (\r\n <div\r\n role=\"listitem\"\r\n key={h.id}\r\n style={{\r\n borderBottom: `1px solid ${tokens.colors.border.muted}`,\r\n }}\r\n >\r\n <button\r\n type=\"button\"\r\n aria-label={`${h.status} at ${h.timestamp.toLocaleTimeString()}`}\r\n style={{\r\n width: \"100%\",\r\n textAlign: \"left\",\r\n padding: `${tokens.spacing.sm} ${tokens.spacing.smd}`,\r\n fontSize: tokens.typography.fontSize.xxs,\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n gap: tokens.spacing.sm,\r\n cursor: \"pointer\",\r\n transition: \"background 150ms ease\",\r\n border: \"none\",\r\n background: \"transparent\",\r\n color: \"inherit\",\r\n }}\r\n onClick={() => setValues(h.parameters)}\r\n >\r\n {/* Astro UX status shape */}\r\n <span\r\n aria-hidden=\"true\"\r\n style={{\r\n width: 8,\r\n height: 8,\r\n flexShrink: 0,\r\n ...(h.status === \"success\"\r\n ? {\r\n borderRadius: tokens.borderRadius.full,\r\n background: tokens.colors.status.normal,\r\n }\r\n : h.status === \"error\"\r\n ? {\r\n borderRadius: 0,\r\n background: tokens.colors.status.critical,\r\n clipPath: \"polygon(50% 100%, 0% 0%, 100% 0%)\",\r\n }\r\n : {\r\n borderRadius: tokens.borderRadius.full,\r\n background: \"transparent\",\r\n border: `2px solid ${tokens.colors.status.standby}`,\r\n }),\r\n }}\r\n />\r\n <span\r\n style={{\r\n color: tokens.colors.text.muted,\r\n fontFamily: tokens.typography.fontFamily.mono,\r\n fontSize: tokens.typography.fontSize.xxs,\r\n flexShrink: 0,\r\n }}\r\n >\r\n {h.timestamp.toLocaleTimeString()}\r\n </span>\r\n <span\r\n style={{\r\n fontFamily: tokens.typography.fontFamily.mono,\r\n fontSize: tokens.typography.fontSize.xxs,\r\n flex: 1,\r\n overflow: \"hidden\",\r\n textOverflow: \"ellipsis\",\r\n whiteSpace: \"nowrap\",\r\n }}\r\n >\r\n {Object.entries(h.parameters)\r\n .map(([k, v]) => `${k}=${JSON.stringify(v)}`)\r\n .join(\", \")}\r\n </span>\r\n {h.error && (\r\n <span\r\n style={{\r\n fontSize: tokens.typography.fontSize.micro,\r\n color: tokens.colors.status.critical,\r\n }}\r\n >\r\n {h.error}\r\n </span>\r\n )}\r\n </button>\r\n </div>\r\n ))}\r\n </div>\r\n )}\r\n </div>\r\n );\r\n});\r\n\r\nexport default CommandBuilder;\r\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ConnectionForm.js","names":[],"sources":["../../../../src/react/core/widgets/ConnectionForm.tsx"],"sourcesContent":["/**\n * @zendir/ui - ConnectionForm Component\n *\n * MQTT/WebSocket connection form with URL composition, recent connections,\n * URL parameter sharing, and persistence. Tailored for operator dashboards\n * that require broker configuration (host, port, protocol, credentials).\n *\n * Astro UX Compliance:\n * - Status indicators for connection state (connected/disconnected/error)\n * - Form validation with clear error states\n * - Keyboard shortcut for quick connect (Ctrl+Enter)\n *\n * @example\n * ```tsx\n * <ConnectionForm\n * onConnect={(config) => client.connect(config)}\n * onDisconnect={() => client.disconnect()}\n * connected={isConnected}\n * defaultProtocol=\"wss\"\n * showAdvanced\n * />\n * ```\n */\n\nimport React, {\n memo,\n useState,\n useCallback,\n useEffect,\n useMemo,\n useId,\n useRef,\n} from \"react\";\nimport { useTheme } from \"../../theme\";\nimport type { LabelPlacement } from \"../inputs/Input\";\nimport { useBreakpoint } from \"../layout/useBreakpoint\";\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\nexport interface ConnectionConfig {\n /** Host */\n host: string;\n /** Port */\n port: number;\n /** Protocol */\n protocol: \"ws\" | \"wss\" | \"mqtt\" | \"mqtts\" | \"tcp\";\n /** Path (e.g. /mqtt) */\n path?: string;\n /** Username */\n username?: string;\n /** Password */\n password?: string;\n /** Client ID */\n clientId?: string;\n}\n\nexport interface ConnectionFormProps {\n /** Connect handler */\n onConnect: (config: ConnectionConfig) => void;\n /** Disconnect handler */\n onDisconnect?: () => void;\n /** Current connection state */\n connected?: boolean;\n /** Connection in progress */\n connecting?: boolean;\n /** Default protocol */\n defaultProtocol?: ConnectionConfig[\"protocol\"];\n /** Default host */\n defaultHost?: string;\n /** Default port */\n defaultPort?: number;\n /** Default path */\n defaultPath?: string;\n /** Show advanced fields (clientId, credentials) */\n showAdvanced?: boolean;\n /** Persist to localStorage (key name) */\n persistKey?: string;\n /** Parse URL params on mount */\n parseUrlParams?: boolean;\n /** Error message */\n error?: string | null;\n /** Compact layout */\n compact?: boolean;\n /** Custom style */\n style?: React.CSSProperties;\n /**\n * Label placement style for form fields.\n * - `'outlined'` — label sits on the input border (notched/Material-style). **Default.**\n * - `'above'` — label sits above the input with a gap (classic stack).\n */\n labelPlacement?: LabelPlacement;\n}\n\nconst PROTOCOLS: ConnectionConfig[\"protocol\"][] = [\n \"wss\",\n \"ws\",\n \"mqtts\",\n \"mqtt\",\n \"tcp\",\n];\nconst DEFAULT_PORTS: Record<string, number> = {\n ws: 80,\n wss: 443,\n mqtt: 1883,\n mqtts: 8883,\n tcp: 1883,\n};\n\nexport const ConnectionForm = memo(function ConnectionForm({\n onConnect,\n onDisconnect,\n connected = false,\n connecting = false,\n defaultProtocol = \"wss\",\n defaultHost = \"localhost\",\n defaultPort,\n defaultPath = \"/mqtt\",\n showAdvanced = false,\n persistKey,\n parseUrlParams = false,\n error,\n compact = false,\n style,\n labelPlacement = \"outlined\",\n}: ConnectionFormProps): React.ReactElement {\n const { tokens } = useTheme();\n const { isMobile } = useBreakpoint();\n const isOutlined = labelPlacement === \"outlined\";\n\n const initialConfig: ConnectionConfig = useMemo(() => {\n // Try localStorage\n if (persistKey) {\n try {\n const saved = localStorage.getItem(persistKey);\n if (saved) return JSON.parse(saved);\n } catch {\n /* ignore */\n }\n }\n\n // Try URL params\n if (parseUrlParams && typeof window !== \"undefined\") {\n const params = new URLSearchParams(window.location.search);\n if (params.has(\"host\")) {\n return {\n host: params.get(\"host\") || defaultHost,\n port: parseInt(\n params.get(\"port\") ||\n String(defaultPort || DEFAULT_PORTS[defaultProtocol]),\n ),\n protocol: (params.get(\"protocol\") ||\n defaultProtocol) as ConnectionConfig[\"protocol\"],\n path: params.get(\"path\") || defaultPath,\n username: params.get(\"username\") || \"\",\n password: params.get(\"password\") || \"\",\n clientId: params.get(\"clientId\") || \"\",\n };\n }\n }\n\n return {\n host: defaultHost,\n port: defaultPort || DEFAULT_PORTS[defaultProtocol],\n protocol: defaultProtocol,\n path: defaultPath,\n username: \"\",\n password: \"\",\n clientId: \"\",\n };\n }, []);\n\n const baseId = useId();\n const passwordInputRef = useRef<HTMLInputElement>(null);\n const usernameInputRef = useRef<HTMLInputElement>(null);\n const [config, setConfig] = useState<ConnectionConfig>(initialConfig);\n const [showAdv, setShowAdv] = useState(showAdvanced);\n const [showPassword, setShowPassword] = useState(false);\n\n // Persist on change\n useEffect(() => {\n if (persistKey) {\n try {\n localStorage.setItem(persistKey, JSON.stringify(config));\n } catch {\n /* ignore */\n }\n }\n }, [config, persistKey]);\n\n const updateField = useCallback(\n <K extends keyof ConnectionConfig>(key: K, value: ConnectionConfig[K]) => {\n setConfig((prev) => ({ ...prev, [key]: value }));\n },\n [],\n );\n\n const handleProtocolChange = useCallback(\n (protocol: ConnectionConfig[\"protocol\"]) => {\n setConfig((prev) => ({\n ...prev,\n protocol,\n port:\n prev.port === DEFAULT_PORTS[prev.protocol]\n ? DEFAULT_PORTS[protocol]\n : prev.port,\n }));\n },\n [],\n );\n\n const handleSubmit = useCallback(\n (e: React.FormEvent) => {\n e.preventDefault();\n if (!connected) onConnect(config);\n else onDisconnect?.();\n },\n [config, connected, onConnect, onDisconnect],\n );\n\n const getShareUrl = useCallback(() => {\n const base = window.location.origin + window.location.pathname;\n const params = new URLSearchParams({\n host: config.host,\n port: String(config.port),\n protocol: config.protocol,\n ...(config.path ? { path: config.path } : {}),\n });\n return `${base}?${params.toString()}`;\n }, [config]);\n\n const fullUrl = `${config.protocol}://${config.host}:${config.port}${config.path || \"\"}`;\n\n const inputStyle: React.CSSProperties = {\n width: \"100%\",\n minHeight: compact ? 32 : 40,\n padding: compact\n ? `6px ${tokens.spacing.sm}`\n : `${tokens.spacing.sm} ${tokens.spacing.md}`,\n fontSize: tokens.typography.fontSize.sm,\n fontFamily: tokens.typography.fontFamily.primary,\n color: tokens.colors.text.primary,\n backgroundColor: tokens.colors.background.base,\n border: `1px solid ${tokens.colors.border.default}`,\n borderRadius: tokens.borderRadius.md,\n outline: \"none\",\n transition: tokens.animation.fast,\n boxSizing: \"border-box\",\n };\n\n const labelStyle: React.CSSProperties = isOutlined\n ? {\n position: \"absolute\",\n top: 0,\n left: \"10px\",\n transform: \"translateY(-50%)\",\n backgroundColor: tokens.colors.background.base,\n padding: \"0 5px\",\n fontSize: tokens.typography.fontSize.xxs,\n fontWeight: tokens.typography.fontWeight.medium,\n color: tokens.colors.text.tertiary,\n textTransform: \"uppercase\",\n letterSpacing: \"0.04em\",\n zIndex: 1,\n pointerEvents: \"none\",\n lineHeight: 1.2,\n whiteSpace: \"nowrap\",\n }\n : {\n display: \"block\",\n fontSize: tokens.typography.fontSize.xxs,\n fontWeight: tokens.typography.fontWeight.medium,\n color: tokens.colors.text.tertiary,\n marginBottom: \"4px\",\n textTransform: \"uppercase\",\n letterSpacing: \"0.04em\",\n };\n\n // Wrapper for outlined fields (position: relative so the label can sit on the border)\n const fieldWrapperStyle: React.CSSProperties = isOutlined\n ? { position: \"relative\" }\n : {};\n\n // Status dot\n const statusColor = connected\n ? tokens.colors.status.normal\n : error\n ? tokens.colors.status.critical\n : tokens.colors.status.off;\n\n return (\n <form\n onSubmit={handleSubmit}\n aria-label=\"Connection configuration\"\n style={{\n fontFamily: tokens.typography.fontFamily.primary,\n ...style,\n }}\n >\n {/* Status bar */}\n <div\n aria-live=\"polite\"\n style={{\n display: \"flex\",\n alignItems: \"center\",\n gap: tokens.spacing.sm,\n flexWrap: \"wrap\",\n marginBottom: tokens.spacing.md,\n }}\n >\n <div\n style={{\n width: 8,\n height: 8,\n borderRadius: \"50%\",\n backgroundColor: statusColor,\n boxShadow: connected ? `0 0 6px ${statusColor}` : \"none\",\n transition: tokens.animation.fast,\n }}\n />\n <span\n style={{\n fontSize: tokens.typography.fontSize.xs,\n color: tokens.colors.text.secondary,\n fontWeight: tokens.typography.fontWeight.medium,\n }}\n >\n {connecting\n ? \"Connecting...\"\n : connected\n ? \"Connected\"\n : \"Disconnected\"}\n </span>\n <div style={{ flex: 1 }} />\n <span\n style={{\n fontSize: tokens.typography.fontSize.xxs,\n color: tokens.colors.text.tertiary,\n fontFamily: tokens.typography.fontFamily.mono,\n overflow: \"hidden\",\n textOverflow: \"ellipsis\",\n maxWidth: isMobile ? \"100%\" : 280,\n whiteSpace: isMobile ? \"normal\" : \"nowrap\",\n wordBreak: isMobile ? \"break-all\" : \"normal\",\n }}\n >\n {fullUrl}\n </span>\n </div>\n\n {/* Main fields */}\n <div\n style={{\n display: \"grid\",\n gridTemplateColumns: isMobile\n ? \"1fr\"\n : \"minmax(80px, 100px) 1fr minmax(60px, 80px)\",\n gap: tokens.spacing.sm,\n }}\n >\n {/* Protocol */}\n <div style={fieldWrapperStyle}>\n <label htmlFor={`${baseId}-protocol`} style={labelStyle}>\n Protocol\n </label>\n <select\n id={`${baseId}-protocol`}\n value={config.protocol}\n onChange={(e) =>\n handleProtocolChange(\n e.target.value as ConnectionConfig[\"protocol\"],\n )\n }\n disabled={connected || connecting}\n style={{\n ...inputStyle,\n cursor: \"pointer\",\n appearance: \"auto\",\n }}\n >\n {PROTOCOLS.map((p) => (\n <option key={p} value={p}>\n {p.toUpperCase()}\n </option>\n ))}\n </select>\n </div>\n\n {/* Host */}\n <div style={fieldWrapperStyle}>\n <label htmlFor={`${baseId}-host`} style={labelStyle}>\n Host\n </label>\n <input\n id={`${baseId}-host`}\n type=\"text\"\n value={config.host}\n onChange={(e) => updateField(\"host\", e.target.value)}\n disabled={connected || connecting}\n placeholder=\"broker.example.com\"\n style={inputStyle}\n />\n </div>\n\n {/* Port */}\n <div style={fieldWrapperStyle}>\n <label htmlFor={`${baseId}-port`} style={labelStyle}>\n Port\n </label>\n <input\n id={`${baseId}-port`}\n type=\"number\"\n value={config.port}\n min={1}\n max={65535}\n onChange={(e) => {\n const v = parseInt(e.target.value) || 0;\n updateField(\"port\", Math.max(0, Math.min(65535, v)));\n }}\n disabled={connected || connecting}\n style={{\n ...inputStyle,\n fontFamily: tokens.typography.fontFamily.mono,\n }}\n />\n </div>\n </div>\n\n {/* Path */}\n <div style={{ marginTop: tokens.spacing.sm, ...fieldWrapperStyle }}>\n <label htmlFor={`${baseId}-path`} style={labelStyle}>\n Path\n </label>\n <input\n id={`${baseId}-path`}\n type=\"text\"\n value={config.path || \"\"}\n onChange={(e) => updateField(\"path\", e.target.value)}\n disabled={connected || connecting}\n placeholder=\"/mqtt\"\n style={inputStyle}\n />\n </div>\n\n {/* Advanced toggle */}\n <button\n type=\"button\"\n onClick={() => setShowAdv((p) => !p)}\n style={{\n display: \"flex\",\n alignItems: \"center\",\n gap: \"4px\",\n fontSize: tokens.typography.fontSize.xxs,\n color: tokens.colors.text.tertiary,\n backgroundColor: \"transparent\",\n border: \"none\",\n cursor: \"pointer\",\n marginTop: tokens.spacing.sm,\n padding: 0,\n fontFamily: tokens.typography.fontFamily.primary,\n }}\n >\n <svg\n width=\"12\"\n height=\"12\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n style={{\n transition: tokens.animation.fast,\n transform: showAdv ? \"rotate(90deg)\" : \"rotate(0)\",\n }}\n >\n <polyline points=\"9 18 15 12 9 6\" />\n </svg>\n Advanced\n </button>\n\n {/* Advanced fields */}\n {showAdv && (\n <div\n style={{\n marginTop: tokens.spacing.sm,\n display: \"grid\",\n gridTemplateColumns: isMobile ? \"1fr\" : \"1fr 1fr\",\n gap: tokens.spacing.sm,\n }}\n >\n <div style={fieldWrapperStyle}>\n <label htmlFor={`${baseId}-username`} style={labelStyle}>\n Username\n </label>\n <input\n ref={usernameInputRef}\n id={`${baseId}-username`}\n type=\"text\"\n value={config.username || \"\"}\n onChange={(e) => updateField(\"username\", e.target.value)}\n onPaste={(e) => {\n const pasted = e.clipboardData.getData(\"text\");\n const trimmed = pasted.trim();\n const input = usernameInputRef.current;\n if (input) {\n const cur = config.username || \"\";\n const start = input.selectionStart ?? cur.length;\n const end = input.selectionEnd ?? cur.length;\n const newValue =\n cur.slice(0, start) + trimmed + cur.slice(end);\n if (trimmed !== pasted || newValue !== cur) {\n e.preventDefault();\n updateField(\"username\", newValue);\n requestAnimationFrame(() => {\n input.setSelectionRange(\n start + trimmed.length,\n start + trimmed.length,\n );\n });\n }\n } else if (trimmed !== pasted) {\n e.preventDefault();\n updateField(\"username\", trimmed);\n }\n }}\n onBlur={() => {\n const cur = config.username ?? \"\";\n const trimmed = cur.trim();\n if (trimmed !== cur) updateField(\"username\", trimmed);\n }}\n disabled={connected || connecting}\n autoComplete=\"username\"\n style={inputStyle}\n />\n </div>\n <div style={fieldWrapperStyle}>\n <label htmlFor={`${baseId}-password`} style={labelStyle}>\n Password\n </label>\n <div style={{ position: \"relative\" }}>\n <input\n ref={passwordInputRef}\n id={`${baseId}-password`}\n type={showPassword ? \"text\" : \"password\"}\n value={config.password || \"\"}\n onChange={(e) => updateField(\"password\", e.target.value)}\n onPaste={(e) => {\n const pasted = e.clipboardData.getData(\"text\");\n const trimmed = pasted.trim();\n const input = passwordInputRef.current;\n if (input) {\n const cur = config.password || \"\";\n const start = input.selectionStart ?? cur.length;\n const end = input.selectionEnd ?? cur.length;\n const newValue =\n cur.slice(0, start) + trimmed + cur.slice(end);\n if (trimmed !== pasted || newValue !== cur) {\n e.preventDefault();\n updateField(\"password\", newValue);\n requestAnimationFrame(() => {\n const pos = start + trimmed.length;\n input.setSelectionRange(pos, pos);\n });\n }\n } else if (trimmed !== pasted) {\n e.preventDefault();\n updateField(\"password\", trimmed);\n }\n }}\n onBlur={() => {\n const cur = config.password ?? \"\";\n const trimmed = cur.trim();\n if (trimmed !== cur) updateField(\"password\", trimmed);\n }}\n disabled={connected || connecting}\n autoComplete=\"current-password\"\n style={{ ...inputStyle, paddingRight: 36 }}\n />\n <button\n type=\"button\"\n onClick={() => setShowPassword((p) => !p)}\n style={{\n position: \"absolute\",\n right: 8,\n top: \"50%\",\n transform: \"translateY(-50%)\",\n background: \"none\",\n border: \"none\",\n cursor: \"pointer\",\n color: tokens.colors.text.tertiary,\n fontSize: tokens.typography.fontSize.xxs,\n minHeight: 32,\n padding: `${tokens.spacing.xs} ${tokens.spacing.sm}`,\n }}\n >\n {showPassword ? \"Hide\" : \"Show\"}\n </button>\n </div>\n </div>\n <div style={{ gridColumn: \"1 / -1\", ...fieldWrapperStyle }}>\n <label htmlFor={`${baseId}-clientId`} style={labelStyle}>\n Client ID\n </label>\n <input\n id={`${baseId}-clientId`}\n type=\"text\"\n value={config.clientId || \"\"}\n onChange={(e) => updateField(\"clientId\", e.target.value)}\n disabled={connected || connecting}\n placeholder=\"auto-generated if empty\"\n style={inputStyle}\n />\n </div>\n </div>\n )}\n\n {/* Error */}\n {error && (\n <div\n style={{\n marginTop: tokens.spacing.sm,\n padding: `${tokens.spacing.xs} ${tokens.spacing.sm}`,\n fontSize: tokens.typography.fontSize.xs,\n color: tokens.colors.status.critical,\n backgroundColor: `${tokens.colors.status.critical}10`,\n border: `1px solid ${tokens.colors.status.critical}20`,\n borderRadius: tokens.borderRadius.sm,\n }}\n >\n {error}\n </div>\n )}\n\n {/* Actions */}\n <div\n style={{\n display: \"flex\",\n alignItems: \"center\",\n gap: tokens.spacing.sm,\n flexDirection: isMobile ? \"column\" : \"row\",\n marginTop: tokens.spacing.md,\n }}\n >\n <button\n type=\"submit\"\n disabled={connecting || !config.host}\n style={{\n flex: 1,\n padding: compact\n ? `8px ${tokens.spacing.md}`\n : `${tokens.spacing.sm} ${tokens.spacing.lg}`,\n minHeight: 44,\n fontSize: tokens.typography.fontSize.sm,\n fontWeight: tokens.typography.fontWeight.semibold,\n fontFamily: tokens.typography.fontFamily.primary,\n color: tokens.colors.text.inverse || \"#fff\",\n backgroundColor: connected\n ? tokens.colors.status.critical\n : tokens.colors.accent.primary,\n border: \"none\",\n borderRadius: tokens.borderRadius.md,\n cursor: connecting || !config.host ? \"not-allowed\" : \"pointer\",\n opacity: connecting || !config.host ? 0.6 : 1,\n transition: tokens.animation.fast,\n }}\n >\n {connecting ? \"Connecting...\" : connected ? \"Disconnect\" : \"Connect\"}\n </button>\n\n {parseUrlParams && !connected && (\n <button\n type=\"button\"\n title=\"Copy share URL\"\n onClick={() => {\n navigator.clipboard?.writeText(getShareUrl());\n }}\n style={{\n padding: compact ? \"8px\" : tokens.spacing.sm,\n minHeight: 44,\n width: isMobile ? \"100%\" : \"auto\",\n fontSize: tokens.typography.fontSize.xs,\n color: tokens.colors.text.tertiary,\n backgroundColor: \"transparent\",\n border: `1px solid ${tokens.colors.border.muted}`,\n borderRadius: tokens.borderRadius.md,\n cursor: \"pointer\",\n fontFamily: tokens.typography.fontFamily.primary,\n }}\n >\n Share\n </button>\n )}\n </div>\n </form>\n );\n});\n\nexport default ConnectionForm;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ConnectionForm.js","names":[],"sources":["../../../../src/react/core/widgets/ConnectionForm.tsx"],"sourcesContent":["/**\r\n * @zendir/ui - ConnectionForm Component\r\n *\r\n * MQTT/WebSocket connection form with URL composition, recent connections,\r\n * URL parameter sharing, and persistence. Tailored for operator dashboards\r\n * that require broker configuration (host, port, protocol, credentials).\r\n *\r\n * Astro UX Compliance:\r\n * - Status indicators for connection state (connected/disconnected/error)\r\n * - Form validation with clear error states\r\n * - Keyboard shortcut for quick connect (Ctrl+Enter)\r\n *\r\n * @example\r\n * ```tsx\r\n * <ConnectionForm\r\n * onConnect={(config) => client.connect(config)}\r\n * onDisconnect={() => client.disconnect()}\r\n * connected={isConnected}\r\n * defaultProtocol=\"wss\"\r\n * showAdvanced\r\n * />\r\n * ```\r\n */\r\n\r\nimport React, {\r\n memo,\r\n useState,\r\n useCallback,\r\n useEffect,\r\n useMemo,\r\n useId,\r\n useRef,\r\n} from \"react\";\r\nimport { useTheme } from \"../../theme\";\r\nimport type { LabelPlacement } from \"../inputs/Input\";\r\nimport { useBreakpoint } from \"../layout/useBreakpoint\";\r\n\r\n// ─── Types ───────────────────────────────────────────────────────────────────\r\n\r\nexport interface ConnectionConfig {\r\n /** Host */\r\n host: string;\r\n /** Port */\r\n port: number;\r\n /** Protocol */\r\n protocol: \"ws\" | \"wss\" | \"mqtt\" | \"mqtts\" | \"tcp\";\r\n /** Path (e.g. /mqtt) */\r\n path?: string;\r\n /** Username */\r\n username?: string;\r\n /** Password */\r\n password?: string;\r\n /** Client ID */\r\n clientId?: string;\r\n}\r\n\r\nexport interface ConnectionFormProps {\r\n /** Connect handler */\r\n onConnect: (config: ConnectionConfig) => void;\r\n /** Disconnect handler */\r\n onDisconnect?: () => void;\r\n /** Current connection state */\r\n connected?: boolean;\r\n /** Connection in progress */\r\n connecting?: boolean;\r\n /** Default protocol */\r\n defaultProtocol?: ConnectionConfig[\"protocol\"];\r\n /** Default host */\r\n defaultHost?: string;\r\n /** Default port */\r\n defaultPort?: number;\r\n /** Default path */\r\n defaultPath?: string;\r\n /** Show advanced fields (clientId, credentials) */\r\n showAdvanced?: boolean;\r\n /** Persist to localStorage (key name) */\r\n persistKey?: string;\r\n /** Parse URL params on mount */\r\n parseUrlParams?: boolean;\r\n /** Error message */\r\n error?: string | null;\r\n /** Compact layout */\r\n compact?: boolean;\r\n /** Custom style */\r\n style?: React.CSSProperties;\r\n /**\r\n * Label placement style for form fields.\r\n * - `'outlined'` — label sits on the input border (notched/Material-style). **Default.**\r\n * - `'above'` — label sits above the input with a gap (classic stack).\r\n */\r\n labelPlacement?: LabelPlacement;\r\n}\r\n\r\nconst PROTOCOLS: ConnectionConfig[\"protocol\"][] = [\r\n \"wss\",\r\n \"ws\",\r\n \"mqtts\",\r\n \"mqtt\",\r\n \"tcp\",\r\n];\r\nconst DEFAULT_PORTS: Record<string, number> = {\r\n ws: 80,\r\n wss: 443,\r\n mqtt: 1883,\r\n mqtts: 8883,\r\n tcp: 1883,\r\n};\r\n\r\nexport const ConnectionForm = memo(function ConnectionForm({\r\n onConnect,\r\n onDisconnect,\r\n connected = false,\r\n connecting = false,\r\n defaultProtocol = \"wss\",\r\n defaultHost = \"localhost\",\r\n defaultPort,\r\n defaultPath = \"/mqtt\",\r\n showAdvanced = false,\r\n persistKey,\r\n parseUrlParams = false,\r\n error,\r\n compact = false,\r\n style,\r\n labelPlacement = \"outlined\",\r\n}: ConnectionFormProps): React.ReactElement {\r\n const { tokens } = useTheme();\r\n const { isMobile } = useBreakpoint();\r\n const isOutlined = labelPlacement === \"outlined\";\r\n\r\n const initialConfig: ConnectionConfig = useMemo(() => {\r\n // Try localStorage\r\n if (persistKey) {\r\n try {\r\n const saved = localStorage.getItem(persistKey);\r\n if (saved) return JSON.parse(saved);\r\n } catch {\r\n /* ignore */\r\n }\r\n }\r\n\r\n // Try URL params\r\n if (parseUrlParams && typeof window !== \"undefined\") {\r\n const params = new URLSearchParams(window.location.search);\r\n if (params.has(\"host\")) {\r\n return {\r\n host: params.get(\"host\") || defaultHost,\r\n port: parseInt(\r\n params.get(\"port\") ||\r\n String(defaultPort || DEFAULT_PORTS[defaultProtocol]),\r\n ),\r\n protocol: (params.get(\"protocol\") ||\r\n defaultProtocol) as ConnectionConfig[\"protocol\"],\r\n path: params.get(\"path\") || defaultPath,\r\n username: params.get(\"username\") || \"\",\r\n password: params.get(\"password\") || \"\",\r\n clientId: params.get(\"clientId\") || \"\",\r\n };\r\n }\r\n }\r\n\r\n return {\r\n host: defaultHost,\r\n port: defaultPort || DEFAULT_PORTS[defaultProtocol],\r\n protocol: defaultProtocol,\r\n path: defaultPath,\r\n username: \"\",\r\n password: \"\",\r\n clientId: \"\",\r\n };\r\n }, []);\r\n\r\n const baseId = useId();\r\n const passwordInputRef = useRef<HTMLInputElement>(null);\r\n const usernameInputRef = useRef<HTMLInputElement>(null);\r\n const [config, setConfig] = useState<ConnectionConfig>(initialConfig);\r\n const [showAdv, setShowAdv] = useState(showAdvanced);\r\n const [showPassword, setShowPassword] = useState(false);\r\n\r\n // Persist on change\r\n useEffect(() => {\r\n if (persistKey) {\r\n try {\r\n localStorage.setItem(persistKey, JSON.stringify(config));\r\n } catch {\r\n /* ignore */\r\n }\r\n }\r\n }, [config, persistKey]);\r\n\r\n const updateField = useCallback(\r\n <K extends keyof ConnectionConfig>(key: K, value: ConnectionConfig[K]) => {\r\n setConfig((prev) => ({ ...prev, [key]: value }));\r\n },\r\n [],\r\n );\r\n\r\n const handleProtocolChange = useCallback(\r\n (protocol: ConnectionConfig[\"protocol\"]) => {\r\n setConfig((prev) => ({\r\n ...prev,\r\n protocol,\r\n port:\r\n prev.port === DEFAULT_PORTS[prev.protocol]\r\n ? DEFAULT_PORTS[protocol]\r\n : prev.port,\r\n }));\r\n },\r\n [],\r\n );\r\n\r\n const handleSubmit = useCallback(\r\n (e: React.FormEvent) => {\r\n e.preventDefault();\r\n if (!connected) onConnect(config);\r\n else onDisconnect?.();\r\n },\r\n [config, connected, onConnect, onDisconnect],\r\n );\r\n\r\n const getShareUrl = useCallback(() => {\r\n const base = window.location.origin + window.location.pathname;\r\n const params = new URLSearchParams({\r\n host: config.host,\r\n port: String(config.port),\r\n protocol: config.protocol,\r\n ...(config.path ? { path: config.path } : {}),\r\n });\r\n return `${base}?${params.toString()}`;\r\n }, [config]);\r\n\r\n const fullUrl = `${config.protocol}://${config.host}:${config.port}${config.path || \"\"}`;\r\n\r\n const inputStyle: React.CSSProperties = {\r\n width: \"100%\",\r\n minHeight: compact ? 32 : 40,\r\n padding: compact\r\n ? `6px ${tokens.spacing.sm}`\r\n : `${tokens.spacing.sm} ${tokens.spacing.md}`,\r\n fontSize: tokens.typography.fontSize.sm,\r\n fontFamily: tokens.typography.fontFamily.primary,\r\n color: tokens.colors.text.primary,\r\n backgroundColor: tokens.colors.background.base,\r\n border: `1px solid ${tokens.colors.border.default}`,\r\n borderRadius: tokens.borderRadius.md,\r\n outline: \"none\",\r\n transition: tokens.animation.fast,\r\n boxSizing: \"border-box\",\r\n };\r\n\r\n const labelStyle: React.CSSProperties = isOutlined\r\n ? {\r\n position: \"absolute\",\r\n top: 0,\r\n left: \"10px\",\r\n transform: \"translateY(-50%)\",\r\n backgroundColor: tokens.colors.background.base,\r\n padding: \"0 5px\",\r\n fontSize: tokens.typography.fontSize.xxs,\r\n fontWeight: tokens.typography.fontWeight.medium,\r\n color: tokens.colors.text.tertiary,\r\n textTransform: \"uppercase\",\r\n letterSpacing: \"0.04em\",\r\n zIndex: 1,\r\n pointerEvents: \"none\",\r\n lineHeight: 1.2,\r\n whiteSpace: \"nowrap\",\r\n }\r\n : {\r\n display: \"block\",\r\n fontSize: tokens.typography.fontSize.xxs,\r\n fontWeight: tokens.typography.fontWeight.medium,\r\n color: tokens.colors.text.tertiary,\r\n marginBottom: \"4px\",\r\n textTransform: \"uppercase\",\r\n letterSpacing: \"0.04em\",\r\n };\r\n\r\n // Wrapper for outlined fields (position: relative so the label can sit on the border)\r\n const fieldWrapperStyle: React.CSSProperties = isOutlined\r\n ? { position: \"relative\" }\r\n : {};\r\n\r\n // Status dot\r\n const statusColor = connected\r\n ? tokens.colors.status.normal\r\n : error\r\n ? tokens.colors.status.critical\r\n : tokens.colors.status.off;\r\n\r\n return (\r\n <form\r\n onSubmit={handleSubmit}\r\n aria-label=\"Connection configuration\"\r\n style={{\r\n fontFamily: tokens.typography.fontFamily.primary,\r\n ...style,\r\n }}\r\n >\r\n {/* Status bar */}\r\n <div\r\n aria-live=\"polite\"\r\n style={{\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n gap: tokens.spacing.sm,\r\n flexWrap: \"wrap\",\r\n marginBottom: tokens.spacing.md,\r\n }}\r\n >\r\n <div\r\n style={{\r\n width: 8,\r\n height: 8,\r\n borderRadius: \"50%\",\r\n backgroundColor: statusColor,\r\n boxShadow: connected ? `0 0 6px ${statusColor}` : \"none\",\r\n transition: tokens.animation.fast,\r\n }}\r\n />\r\n <span\r\n style={{\r\n fontSize: tokens.typography.fontSize.xs,\r\n color: tokens.colors.text.secondary,\r\n fontWeight: tokens.typography.fontWeight.medium,\r\n }}\r\n >\r\n {connecting\r\n ? \"Connecting...\"\r\n : connected\r\n ? \"Connected\"\r\n : \"Disconnected\"}\r\n </span>\r\n <div style={{ flex: 1 }} />\r\n <span\r\n style={{\r\n fontSize: tokens.typography.fontSize.xxs,\r\n color: tokens.colors.text.tertiary,\r\n fontFamily: tokens.typography.fontFamily.mono,\r\n overflow: \"hidden\",\r\n textOverflow: \"ellipsis\",\r\n maxWidth: isMobile ? \"100%\" : 280,\r\n whiteSpace: isMobile ? \"normal\" : \"nowrap\",\r\n wordBreak: isMobile ? \"break-all\" : \"normal\",\r\n }}\r\n >\r\n {fullUrl}\r\n </span>\r\n </div>\r\n\r\n {/* Main fields */}\r\n <div\r\n style={{\r\n display: \"grid\",\r\n gridTemplateColumns: isMobile\r\n ? \"1fr\"\r\n : \"minmax(80px, 100px) 1fr minmax(60px, 80px)\",\r\n gap: tokens.spacing.sm,\r\n }}\r\n >\r\n {/* Protocol */}\r\n <div style={fieldWrapperStyle}>\r\n <label htmlFor={`${baseId}-protocol`} style={labelStyle}>\r\n Protocol\r\n </label>\r\n <select\r\n id={`${baseId}-protocol`}\r\n value={config.protocol}\r\n onChange={(e) =>\r\n handleProtocolChange(\r\n e.target.value as ConnectionConfig[\"protocol\"],\r\n )\r\n }\r\n disabled={connected || connecting}\r\n style={{\r\n ...inputStyle,\r\n cursor: \"pointer\",\r\n appearance: \"auto\",\r\n }}\r\n >\r\n {PROTOCOLS.map((p) => (\r\n <option key={p} value={p}>\r\n {p.toUpperCase()}\r\n </option>\r\n ))}\r\n </select>\r\n </div>\r\n\r\n {/* Host */}\r\n <div style={fieldWrapperStyle}>\r\n <label htmlFor={`${baseId}-host`} style={labelStyle}>\r\n Host\r\n </label>\r\n <input\r\n id={`${baseId}-host`}\r\n type=\"text\"\r\n value={config.host}\r\n onChange={(e) => updateField(\"host\", e.target.value)}\r\n disabled={connected || connecting}\r\n placeholder=\"broker.example.com\"\r\n style={inputStyle}\r\n />\r\n </div>\r\n\r\n {/* Port */}\r\n <div style={fieldWrapperStyle}>\r\n <label htmlFor={`${baseId}-port`} style={labelStyle}>\r\n Port\r\n </label>\r\n <input\r\n id={`${baseId}-port`}\r\n type=\"number\"\r\n value={config.port}\r\n min={1}\r\n max={65535}\r\n onChange={(e) => {\r\n const v = parseInt(e.target.value) || 0;\r\n updateField(\"port\", Math.max(0, Math.min(65535, v)));\r\n }}\r\n disabled={connected || connecting}\r\n style={{\r\n ...inputStyle,\r\n fontFamily: tokens.typography.fontFamily.mono,\r\n }}\r\n />\r\n </div>\r\n </div>\r\n\r\n {/* Path */}\r\n <div style={{ marginTop: tokens.spacing.sm, ...fieldWrapperStyle }}>\r\n <label htmlFor={`${baseId}-path`} style={labelStyle}>\r\n Path\r\n </label>\r\n <input\r\n id={`${baseId}-path`}\r\n type=\"text\"\r\n value={config.path || \"\"}\r\n onChange={(e) => updateField(\"path\", e.target.value)}\r\n disabled={connected || connecting}\r\n placeholder=\"/mqtt\"\r\n style={inputStyle}\r\n />\r\n </div>\r\n\r\n {/* Advanced toggle */}\r\n <button\r\n type=\"button\"\r\n onClick={() => setShowAdv((p) => !p)}\r\n style={{\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n gap: \"4px\",\r\n fontSize: tokens.typography.fontSize.xxs,\r\n color: tokens.colors.text.tertiary,\r\n backgroundColor: \"transparent\",\r\n border: \"none\",\r\n cursor: \"pointer\",\r\n marginTop: tokens.spacing.sm,\r\n padding: 0,\r\n fontFamily: tokens.typography.fontFamily.primary,\r\n }}\r\n >\r\n <svg\r\n width=\"12\"\r\n height=\"12\"\r\n viewBox=\"0 0 24 24\"\r\n fill=\"none\"\r\n stroke=\"currentColor\"\r\n strokeWidth=\"2\"\r\n style={{\r\n transition: tokens.animation.fast,\r\n transform: showAdv ? \"rotate(90deg)\" : \"rotate(0)\",\r\n }}\r\n >\r\n <polyline points=\"9 18 15 12 9 6\" />\r\n </svg>\r\n Advanced\r\n </button>\r\n\r\n {/* Advanced fields */}\r\n {showAdv && (\r\n <div\r\n style={{\r\n marginTop: tokens.spacing.sm,\r\n display: \"grid\",\r\n gridTemplateColumns: isMobile ? \"1fr\" : \"1fr 1fr\",\r\n gap: tokens.spacing.sm,\r\n }}\r\n >\r\n <div style={fieldWrapperStyle}>\r\n <label htmlFor={`${baseId}-username`} style={labelStyle}>\r\n Username\r\n </label>\r\n <input\r\n ref={usernameInputRef}\r\n id={`${baseId}-username`}\r\n type=\"text\"\r\n value={config.username || \"\"}\r\n onChange={(e) => updateField(\"username\", e.target.value)}\r\n onPaste={(e) => {\r\n const pasted = e.clipboardData.getData(\"text\");\r\n const trimmed = pasted.trim();\r\n const input = usernameInputRef.current;\r\n if (input) {\r\n const cur = config.username || \"\";\r\n const start = input.selectionStart ?? cur.length;\r\n const end = input.selectionEnd ?? cur.length;\r\n const newValue =\r\n cur.slice(0, start) + trimmed + cur.slice(end);\r\n if (trimmed !== pasted || newValue !== cur) {\r\n e.preventDefault();\r\n updateField(\"username\", newValue);\r\n requestAnimationFrame(() => {\r\n input.setSelectionRange(\r\n start + trimmed.length,\r\n start + trimmed.length,\r\n );\r\n });\r\n }\r\n } else if (trimmed !== pasted) {\r\n e.preventDefault();\r\n updateField(\"username\", trimmed);\r\n }\r\n }}\r\n onBlur={() => {\r\n const cur = config.username ?? \"\";\r\n const trimmed = cur.trim();\r\n if (trimmed !== cur) updateField(\"username\", trimmed);\r\n }}\r\n disabled={connected || connecting}\r\n autoComplete=\"username\"\r\n style={inputStyle}\r\n />\r\n </div>\r\n <div style={fieldWrapperStyle}>\r\n <label htmlFor={`${baseId}-password`} style={labelStyle}>\r\n Password\r\n </label>\r\n <div style={{ position: \"relative\" }}>\r\n <input\r\n ref={passwordInputRef}\r\n id={`${baseId}-password`}\r\n type={showPassword ? \"text\" : \"password\"}\r\n value={config.password || \"\"}\r\n onChange={(e) => updateField(\"password\", e.target.value)}\r\n onPaste={(e) => {\r\n const pasted = e.clipboardData.getData(\"text\");\r\n const trimmed = pasted.trim();\r\n const input = passwordInputRef.current;\r\n if (input) {\r\n const cur = config.password || \"\";\r\n const start = input.selectionStart ?? cur.length;\r\n const end = input.selectionEnd ?? cur.length;\r\n const newValue =\r\n cur.slice(0, start) + trimmed + cur.slice(end);\r\n if (trimmed !== pasted || newValue !== cur) {\r\n e.preventDefault();\r\n updateField(\"password\", newValue);\r\n requestAnimationFrame(() => {\r\n const pos = start + trimmed.length;\r\n input.setSelectionRange(pos, pos);\r\n });\r\n }\r\n } else if (trimmed !== pasted) {\r\n e.preventDefault();\r\n updateField(\"password\", trimmed);\r\n }\r\n }}\r\n onBlur={() => {\r\n const cur = config.password ?? \"\";\r\n const trimmed = cur.trim();\r\n if (trimmed !== cur) updateField(\"password\", trimmed);\r\n }}\r\n disabled={connected || connecting}\r\n autoComplete=\"current-password\"\r\n style={{ ...inputStyle, paddingRight: 36 }}\r\n />\r\n <button\r\n type=\"button\"\r\n onClick={() => setShowPassword((p) => !p)}\r\n style={{\r\n position: \"absolute\",\r\n right: 8,\r\n top: \"50%\",\r\n transform: \"translateY(-50%)\",\r\n background: \"none\",\r\n border: \"none\",\r\n cursor: \"pointer\",\r\n color: tokens.colors.text.tertiary,\r\n fontSize: tokens.typography.fontSize.xxs,\r\n minHeight: 32,\r\n padding: `${tokens.spacing.xs} ${tokens.spacing.sm}`,\r\n }}\r\n >\r\n {showPassword ? \"Hide\" : \"Show\"}\r\n </button>\r\n </div>\r\n </div>\r\n <div style={{ gridColumn: \"1 / -1\", ...fieldWrapperStyle }}>\r\n <label htmlFor={`${baseId}-clientId`} style={labelStyle}>\r\n Client ID\r\n </label>\r\n <input\r\n id={`${baseId}-clientId`}\r\n type=\"text\"\r\n value={config.clientId || \"\"}\r\n onChange={(e) => updateField(\"clientId\", e.target.value)}\r\n disabled={connected || connecting}\r\n placeholder=\"auto-generated if empty\"\r\n style={inputStyle}\r\n />\r\n </div>\r\n </div>\r\n )}\r\n\r\n {/* Error */}\r\n {error && (\r\n <div\r\n style={{\r\n marginTop: tokens.spacing.sm,\r\n padding: `${tokens.spacing.xs} ${tokens.spacing.sm}`,\r\n fontSize: tokens.typography.fontSize.xs,\r\n color: tokens.colors.status.critical,\r\n backgroundColor: `${tokens.colors.status.critical}10`,\r\n border: `1px solid ${tokens.colors.status.critical}20`,\r\n borderRadius: tokens.borderRadius.sm,\r\n }}\r\n >\r\n {error}\r\n </div>\r\n )}\r\n\r\n {/* Actions */}\r\n <div\r\n style={{\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n gap: tokens.spacing.sm,\r\n flexDirection: isMobile ? \"column\" : \"row\",\r\n marginTop: tokens.spacing.md,\r\n }}\r\n >\r\n <button\r\n type=\"submit\"\r\n disabled={connecting || !config.host}\r\n style={{\r\n flex: 1,\r\n padding: compact\r\n ? `8px ${tokens.spacing.md}`\r\n : `${tokens.spacing.sm} ${tokens.spacing.lg}`,\r\n minHeight: 44,\r\n fontSize: tokens.typography.fontSize.sm,\r\n fontWeight: tokens.typography.fontWeight.semibold,\r\n fontFamily: tokens.typography.fontFamily.primary,\r\n color: tokens.colors.text.inverse || \"#fff\",\r\n backgroundColor: connected\r\n ? tokens.colors.status.critical\r\n : tokens.colors.accent.primary,\r\n border: \"none\",\r\n borderRadius: tokens.borderRadius.md,\r\n cursor: connecting || !config.host ? \"not-allowed\" : \"pointer\",\r\n opacity: connecting || !config.host ? 0.6 : 1,\r\n transition: tokens.animation.fast,\r\n }}\r\n >\r\n {connecting ? \"Connecting...\" : connected ? \"Disconnect\" : \"Connect\"}\r\n </button>\r\n\r\n {parseUrlParams && !connected && (\r\n <button\r\n type=\"button\"\r\n title=\"Copy share URL\"\r\n onClick={() => {\r\n navigator.clipboard?.writeText(getShareUrl());\r\n }}\r\n style={{\r\n padding: compact ? \"8px\" : tokens.spacing.sm,\r\n minHeight: 44,\r\n width: isMobile ? \"100%\" : \"auto\",\r\n fontSize: tokens.typography.fontSize.xs,\r\n color: tokens.colors.text.tertiary,\r\n backgroundColor: \"transparent\",\r\n border: `1px solid ${tokens.colors.border.muted}`,\r\n borderRadius: tokens.borderRadius.md,\r\n cursor: \"pointer\",\r\n fontFamily: tokens.typography.fontFamily.primary,\r\n }}\r\n >\r\n Share\r\n </button>\r\n )}\r\n </div>\r\n </form>\r\n );\r\n});\r\n\r\nexport default ConnectionForm;\r\n"],"mappings":"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"}
|