@imperosoft/cris-webui-components 0.1.1-beta.4 → 0.1.1-beta.5

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/dist/index.js CHANGED
@@ -64,6 +64,7 @@ function getIconFilter(active) {
64
64
 
65
65
  // src/components/CrisButton.tsx
66
66
  var import_jsx_runtime = require("react/jsx-runtime");
67
+ var globalTouchActive = false;
67
68
  function CrisButton({
68
69
  join,
69
70
  joinFeedback,
@@ -138,37 +139,46 @@ function CrisButton({
138
139
  }
139
140
  };
140
141
  const handleTouchStart = () => {
142
+ globalTouchActive = true;
141
143
  touchingRef.current = true;
142
144
  touchStartedHereRef.current = true;
143
145
  handlePress();
144
146
  };
145
147
  const handleTouchEnd = () => {
148
+ globalTouchActive = true;
146
149
  touchingRef.current = true;
147
150
  if (touchStartedHereRef.current) {
148
151
  touchStartedHereRef.current = false;
149
152
  handleRelease();
150
153
  }
154
+ setTimeout(() => {
155
+ globalTouchActive = false;
156
+ }, 300);
151
157
  };
152
158
  const handleTouchCancel = () => {
159
+ globalTouchActive = true;
153
160
  touchingRef.current = true;
154
161
  touchStartedHereRef.current = false;
155
162
  handleRelease();
163
+ setTimeout(() => {
164
+ globalTouchActive = false;
165
+ }, 300);
156
166
  };
157
167
  const handleMouseDown = () => {
158
- if (touchingRef.current) return;
168
+ if (globalTouchActive || touchingRef.current) return;
159
169
  handlePress();
160
170
  };
161
171
  const handleMouseUp = () => {
162
- if (touchingRef.current) return;
172
+ if (globalTouchActive || touchingRef.current) return;
163
173
  handleRelease();
164
174
  };
165
175
  const handleMouseEnter = () => {
166
- if (touchingRef.current) return;
176
+ if (globalTouchActive || touchingRef.current) return;
167
177
  if (suppressKeyClicks) return;
168
178
  setHovering(true);
169
179
  };
170
180
  const handleMouseLeave = () => {
171
- if (touchingRef.current) return;
181
+ if (globalTouchActive || touchingRef.current) return;
172
182
  setHovering(false);
173
183
  handleRelease();
174
184
  };
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/components/CrisButton.tsx","../src/utils/icons.ts","../src/components/CrisText.tsx","../src/components/CrisSlider.tsx","../src/components/CrisGauge.tsx","../src/components/CrisSpinner.tsx"],"sourcesContent":["/**\r\n * @imperosoft/cris-webui-components\r\n *\r\n * CRIS - Crestron React Impero Soft WebUI components library\r\n *\r\n * Provides reusable UI components for Crestron touch panel interfaces.\r\n */\r\n\r\n// Components\r\nexport { CrisButton } from './components/CrisButton';\r\nexport type { CrisButtonProps } from './components/CrisButton';\r\n\r\nexport { CrisText } from './components/CrisText';\r\nexport type { CrisTextProps } from './components/CrisText';\r\n\r\nexport { CrisSlider } from './components/CrisSlider';\r\nexport type { CrisSliderProps } from './components/CrisSlider';\r\n\r\nexport { CrisGauge } from './components/CrisGauge';\r\nexport type { CrisGaugeProps } from './components/CrisGauge';\r\n\r\nexport { CrisSpinner } from './components/CrisSpinner';\r\nexport type { CrisSpinnerProps, SpinnerSpeed } from './components/CrisSpinner';\r\n\r\n// Utilities\r\nexport { configureIcons, getIconUrl, getIconFilter, getIconConfig } from './utils/icons';\r\nexport type { IconConfig } from './utils/icons';\r\n","import { useState, useRef, ReactNode } from 'react';\r\nimport { useDigital, useJoinsStore } from '@imperosoft/cris-webui-core';\r\nimport { getIconUrl, getIconFilter } from '../utils/icons';\r\n\r\nexport interface CrisButtonProps {\r\n /** Digital join for press action */\r\n join?: number;\r\n /** Digital join for feedback (defaults to join) */\r\n joinFeedback?: number;\r\n /** Digital join for enable state */\r\n joinEnable?: number;\r\n /** Digital join for visibility */\r\n joinVisible?: number;\r\n\r\n /** Button text */\r\n text?: string;\r\n /** Text when pressed (local feedback) */\r\n textPressed?: string;\r\n /** Text when selected (controller feedback) */\r\n textSelected?: string;\r\n\r\n /** Icon as ReactNode (for custom SVG components) */\r\n icon?: ReactNode;\r\n /** Icon name (loads SVG from configured path, e.g., 'motor-stop') */\r\n iconName?: string;\r\n /** Icon CSS class (for CSS-based icons, e.g., 'ico-motor-stop') */\r\n iconClass?: string;\r\n /** Icon size - number (px), string ('50%', '2rem'), or preset ('xs'|'sm'|'md'|'lg'|'xl') */\r\n iconSize?: number | string | 'xs' | 'sm' | 'md' | 'lg' | 'xl';\r\n /** Icon container size as percentage (default: 80% for top/bottom, 20% for left/right) */\r\n iconContainerSize?: string;\r\n /** Icon inline style */\r\n iconStyle?: React.CSSProperties;\r\n /** Icon position relative to text */\r\n iconPosition?: 'left' | 'right' | 'top' | 'bottom';\r\n\r\n /** Icon name when active (overrides iconName when button is active) */\r\n iconNameActive?: string;\r\n /** Icon CSS class when active (overrides iconClass when button is active) */\r\n iconClassActive?: string;\r\n /** Icon inline style when active (merged with iconStyle when button is active) */\r\n iconStyleActive?: React.CSSProperties;\r\n\r\n /** Show controller feedback styling */\r\n showControlFeedback?: boolean;\r\n /** Show local press feedback styling */\r\n showLocalFeedback?: boolean;\r\n /** Suppress click actions (display only) */\r\n suppressKeyClicks?: boolean;\r\n\r\n /** Smart object ID (for smarts instead of joins) */\r\n smartId?: number;\r\n\r\n /** Custom class names */\r\n className?: string;\r\n /** Class when active (controller feedback) */\r\n classActive?: string;\r\n /** Class when pressed (local feedback) */\r\n classPressed?: string;\r\n /** Class when disabled */\r\n classDisabled?: string;\r\n\r\n /** Children content */\r\n children?: ReactNode;\r\n\r\n /** Custom click handler (called on press) */\r\n onPress?: () => void;\r\n /** Custom release handler */\r\n onRelease?: () => void;\r\n}\r\n\r\nexport function CrisButton({\r\n join,\r\n joinFeedback,\r\n joinEnable,\r\n joinVisible,\r\n text,\r\n textPressed,\r\n textSelected,\r\n icon,\r\n iconName,\r\n iconClass,\r\n iconSize,\r\n iconContainerSize,\r\n iconStyle,\r\n iconPosition = 'top',\r\n iconNameActive,\r\n iconClassActive,\r\n iconStyleActive,\r\n showControlFeedback = true,\r\n showLocalFeedback = true,\r\n suppressKeyClicks = false,\r\n smartId,\r\n className = '',\r\n classActive = '',\r\n classPressed = '',\r\n classDisabled = '',\r\n children,\r\n onPress,\r\n onRelease,\r\n}: CrisButtonProps) {\r\n const [pressed, setPressed] = useState(false);\r\n const [hovering, setHovering] = useState(false);\r\n const pressedRef = useRef(false);\r\n const touchingRef = useRef(false);\r\n const touchStartedHereRef = useRef(false);\r\n\r\n // Get join values reactively\r\n const feedbackJoin = joinFeedback ?? join;\r\n const feedback = useDigital(feedbackJoin ?? 0);\r\n const enabled = useDigital(joinEnable ?? 0);\r\n const visible = useDigital(joinVisible ?? 0);\r\n\r\n // Get action method\r\n const dSet = useJoinsStore((state) => state.dSet);\r\n\r\n // Determine if button is enabled\r\n const isEnabled = joinEnable == null ? true : enabled;\r\n\r\n // Determine if button is visible\r\n const isVisible = joinVisible == null ? true : visible;\r\n\r\n // Determine current feedback state\r\n const hasControlFeedback = showControlFeedback && feedbackJoin != null && feedback;\r\n const hasPressedFeedback = showLocalFeedback && pressed && isEnabled;\r\n const isActive = hasControlFeedback || hasPressedFeedback;\r\n\r\n // Determine current text\r\n let currentText = text ?? '';\r\n if (hasPressedFeedback && textPressed != null) {\r\n currentText = textPressed;\r\n } else if (hasControlFeedback && textSelected != null) {\r\n currentText = textSelected;\r\n }\r\n\r\n // Event handlers\r\n const handlePress = () => {\r\n if (suppressKeyClicks) return;\r\n if (pressedRef.current) return;\r\n\r\n pressedRef.current = true;\r\n setPressed(true);\r\n\r\n if (!isEnabled) return;\r\n\r\n // Custom handler\r\n onPress?.();\r\n\r\n // Send to controller\r\n if (join != null && smartId == null) {\r\n dSet(join, true);\r\n }\r\n // TODO: Add smartId support when smarts are implemented in core\r\n };\r\n\r\n const handleRelease = () => {\r\n if (suppressKeyClicks) return;\r\n if (!pressedRef.current) return;\r\n\r\n pressedRef.current = false;\r\n setPressed(false);\r\n\r\n if (!isEnabled) return;\r\n\r\n // Custom handler\r\n onRelease?.();\r\n\r\n // Send to controller\r\n if (join != null && smartId == null) {\r\n dSet(join, false);\r\n }\r\n // TODO: Add smartId support when smarts are implemented in core\r\n };\r\n\r\n // Touch event handlers - set touching flag to prevent mouse events from firing\r\n // Also track if touch started on THIS button to prevent phantom clicks\r\n // when buttons swap visibility (e.g., back button hides, power button appears)\r\n const handleTouchStart = () => {\r\n touchingRef.current = true;\r\n touchStartedHereRef.current = true;\r\n handlePress();\r\n };\r\n\r\n const handleTouchEnd = () => {\r\n touchingRef.current = true;\r\n // Only fire release if touch actually started on this button\r\n if (touchStartedHereRef.current) {\r\n touchStartedHereRef.current = false;\r\n handleRelease();\r\n }\r\n };\r\n\r\n const handleTouchCancel = () => {\r\n touchingRef.current = true;\r\n touchStartedHereRef.current = false;\r\n handleRelease();\r\n };\r\n\r\n // Mouse event handlers - skip if touch event already handled\r\n const handleMouseDown = () => {\r\n if (touchingRef.current) return;\r\n handlePress();\r\n };\r\n\r\n const handleMouseUp = () => {\r\n if (touchingRef.current) return;\r\n handleRelease();\r\n };\r\n\r\n const handleMouseEnter = () => {\r\n if (touchingRef.current) return;\r\n if (suppressKeyClicks) return;\r\n setHovering(true);\r\n };\r\n\r\n const handleMouseLeave = () => {\r\n if (touchingRef.current) return;\r\n setHovering(false);\r\n handleRelease();\r\n };\r\n\r\n // Don't render if not visible\r\n if (!isVisible) return null;\r\n\r\n // Build class names\r\n const classes = [\r\n 'cris-button',\r\n className,\r\n hasControlFeedback && classActive,\r\n hasPressedFeedback && classPressed,\r\n !isEnabled && classDisabled,\r\n hasControlFeedback && 'active',\r\n hasPressedFeedback && 'pressed',\r\n !isEnabled && 'disabled',\r\n suppressKeyClicks && 'no-hover',\r\n ]\r\n .filter(Boolean)\r\n .join(' ');\r\n\r\n // Flex direction based on icon position\r\n const flexDirection =\r\n iconPosition === 'top'\r\n ? 'flex-col'\r\n : iconPosition === 'bottom'\r\n ? 'flex-col-reverse'\r\n : iconPosition === 'left'\r\n ? 'flex-row'\r\n : 'flex-row-reverse';\r\n\r\n // Determine if we have any icon to show\r\n const hasIcon = icon != null || iconName != null || iconClass != null || iconNameActive != null || iconClassActive != null;\r\n\r\n // Determine current icon properties based on active state\r\n const currentIconName = isActive && iconNameActive != null ? iconNameActive : iconName;\r\n const currentIconClass = isActive && iconClassActive != null ? iconClassActive : iconClass;\r\n const currentIconStyle = isActive && iconStyleActive != null ? { ...iconStyle, ...iconStyleActive } : iconStyle;\r\n\r\n // Size presets mapping\r\n const sizePresets: Record<string, string> = {\r\n xs: '16px',\r\n sm: '24px',\r\n md: '32px',\r\n lg: '48px',\r\n xl: '64px',\r\n };\r\n\r\n // Calculate icon size value\r\n const getIconSizeValue = (): string | undefined => {\r\n if (iconSize == null) return undefined;\r\n if (typeof iconSize === 'number') return `${iconSize}px`;\r\n if (iconSize in sizePresets) return sizePresets[iconSize];\r\n return iconSize; // Already a string like '50%' or '2rem'\r\n };\r\n\r\n const iconSizeValue = getIconSizeValue();\r\n\r\n // Build icon classes\r\n const iconClasses = [\r\n 'cris-button-icon',\r\n currentIconClass,\r\n hasControlFeedback && 'active',\r\n hasPressedFeedback && 'pressed',\r\n ]\r\n .filter(Boolean)\r\n .join(' ');\r\n\r\n // Build icon style with size and filter support\r\n const computedIconStyle: React.CSSProperties = {\r\n ...currentIconStyle,\r\n };\r\n\r\n // Apply size\r\n if (iconSizeValue) {\r\n computedIconStyle.width = iconSizeValue;\r\n computedIconStyle.height = iconSizeValue;\r\n }\r\n\r\n // Apply filter from config if using iconName (only if not overridden by iconStyle/iconStyleActive)\r\n if (currentIconName && !currentIconStyle?.filter) {\r\n const filter = getIconFilter(isActive);\r\n if (filter) {\r\n computedIconStyle.filter = filter;\r\n }\r\n }\r\n\r\n // Render icon element\r\n const renderIconElement = () => {\r\n if (icon) {\r\n // ReactNode icon (custom component)\r\n return <span className={iconClasses} style={computedIconStyle}>{icon}</span>;\r\n }\r\n\r\n if (currentIconName) {\r\n // Icon by name (loads from configured path)\r\n return (\r\n <img\r\n className={iconClasses}\r\n style={computedIconStyle}\r\n src={getIconUrl(currentIconName)}\r\n alt=\"\"\r\n />\r\n );\r\n }\r\n\r\n if (currentIconClass) {\r\n // CSS-based icon (like Angular version)\r\n return <img className={iconClasses} style={computedIconStyle} alt=\"\" />;\r\n }\r\n\r\n return null;\r\n };\r\n\r\n // Calculate icon container size (default: 100% if no text, otherwise 80% for top/bottom, 20% for left/right)\r\n const isVertical = iconPosition === 'top' || iconPosition === 'bottom';\r\n const hasText = currentText !== '';\r\n const defaultContainerSize = hasText ? (isVertical ? '80%' : '20%') : '100%';\r\n const containerSize = iconContainerSize ?? defaultContainerSize;\r\n\r\n // Calculate text container size (remaining space)\r\n const textContainerSize = `calc(100% - ${containerSize})`;\r\n\r\n // Container style for icon area\r\n const iconContainerStyle: React.CSSProperties = isVertical\r\n ? { height: containerSize, width: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center' }\r\n : { width: containerSize, height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center' };\r\n\r\n // Container style for text area\r\n const textContainerStyle: React.CSSProperties = isVertical\r\n ? { height: textContainerSize, width: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center' }\r\n : { width: textContainerSize, height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center' };\r\n\r\n // Check if button has 'free' class (absolute positioning)\r\n const isFreePositioned = className.includes('free');\r\n\r\n return (\r\n <div\r\n className={`${classes} flex items-center justify-center ${flexDirection}`}\r\n style={{\r\n cursor: suppressKeyClicks ? 'default' : 'pointer',\r\n position: isFreePositioned ? undefined : 'relative',\r\n overflow: isFreePositioned ? undefined : 'hidden',\r\n }}\r\n onMouseDown={handleMouseDown}\r\n onMouseUp={handleMouseUp}\r\n onMouseEnter={handleMouseEnter}\r\n onMouseLeave={handleMouseLeave}\r\n onTouchStart={handleTouchStart}\r\n onTouchEnd={handleTouchEnd}\r\n onTouchCancel={handleTouchCancel}\r\n >\r\n {/* Hover overlay - inline styles for touch-safe behavior */}\r\n <div\r\n className=\"cris-button-hover-overlay\"\r\n style={{\r\n position: 'absolute',\r\n inset: 0,\r\n backgroundColor: 'black',\r\n opacity: hovering ? 0.1 : 0,\r\n transition: 'opacity 0.2s',\r\n pointerEvents: 'none',\r\n borderRadius: 'inherit',\r\n }}\r\n />\r\n {children}\r\n {hasIcon && (\r\n <div className=\"cris-button-icon-container\" style={iconContainerStyle}>\r\n {renderIconElement()}\r\n </div>\r\n )}\r\n {currentText && (\r\n <div className=\"cris-button-text-container\" style={textContainerStyle}>\r\n <span className=\"cris-button-text\">{currentText}</span>\r\n </div>\r\n )}\r\n </div>\r\n );\r\n}\r\n","/**\r\n * Icon configuration and utilities for CRIS components\r\n *\r\n * Supports different base paths for dev vs production (Crestron controller)\r\n */\r\n\r\nexport interface IconConfig {\r\n /** Base path for icon assets (e.g., '/assets/icons/' or 'http://controller/icons/') */\r\n basePath: string;\r\n /** File extension (default: 'svg') */\r\n extension: string;\r\n /** Default filter for inactive state */\r\n defaultFilter?: string;\r\n /** Default filter for active state */\r\n activeFilter?: string;\r\n}\r\n\r\n// Default configuration\r\nlet iconConfig: IconConfig = {\r\n basePath: '/assets/icons/',\r\n extension: 'svg',\r\n defaultFilter: undefined,\r\n activeFilter: undefined,\r\n};\r\n\r\n/**\r\n * Configure icon paths and defaults\r\n * Call this once at app startup\r\n *\r\n * @example\r\n * // Development\r\n * configureIcons({ basePath: '/assets/icons/' });\r\n *\r\n * // Production on Crestron controller\r\n * configureIcons({ basePath: '/html/icons/' });\r\n *\r\n * // With color filters\r\n * configureIcons({\r\n * basePath: '/assets/icons/',\r\n * defaultFilter: 'brightness(0) invert(1)', // white\r\n * activeFilter: 'brightness(0) invert(0.5) sepia(1) saturate(5) hue-rotate(0deg)', // red\r\n * });\r\n */\r\nexport function configureIcons(config: Partial<IconConfig>): void {\r\n iconConfig = { ...iconConfig, ...config };\r\n}\r\n\r\n/**\r\n * Get current icon configuration\r\n */\r\nexport function getIconConfig(): IconConfig {\r\n return iconConfig;\r\n}\r\n\r\n/**\r\n * Get full URL for an icon\r\n *\r\n * @param name - Icon name (without path or extension), e.g., 'motor-stop'\r\n * @returns Full URL to the icon\r\n */\r\nexport function getIconUrl(name: string): string {\r\n if (!name) return '';\r\n\r\n // If already a full URL or path, return as-is\r\n if (name.startsWith('http') || name.startsWith('/') || name.startsWith('.')) {\r\n return name;\r\n }\r\n\r\n const { basePath, extension } = iconConfig;\r\n const normalizedBase = basePath.endsWith('/') ? basePath : `${basePath}/`;\r\n return `${normalizedBase}${name}.${extension}`;\r\n}\r\n\r\n/**\r\n * Get CSS filter for icon state\r\n */\r\nexport function getIconFilter(active: boolean): string | undefined {\r\n return active ? iconConfig.activeFilter : iconConfig.defaultFilter;\r\n}\r\n","import { ReactNode } from 'react';\r\nimport { useDigital, useSerial, useCipDecode } from '@imperosoft/cris-webui-core';\r\n\r\nexport interface CrisTextProps {\r\n /** Serial join for indirect text */\r\n joinIndirectText?: number;\r\n /** Digital join for enable state */\r\n joinEnable?: number;\r\n /** Digital join for visibility */\r\n joinVisible?: number;\r\n /** Static text (used if no joinIndirectText) */\r\n text?: string;\r\n /** Container CSS class */\r\n className?: string;\r\n /** Container inline style */\r\n style?: React.CSSProperties;\r\n /** Text element CSS class */\r\n textClassName?: string;\r\n /** Text element inline style */\r\n textStyle?: React.CSSProperties;\r\n /** Class when disabled */\r\n classDisabled?: string;\r\n /** Children content */\r\n children?: ReactNode;\r\n}\r\n\r\nexport function CrisText({\r\n joinIndirectText,\r\n joinEnable,\r\n joinVisible,\r\n text,\r\n className = '',\r\n style,\r\n textClassName = '',\r\n textStyle,\r\n classDisabled = '',\r\n children,\r\n}: CrisTextProps) {\r\n // Get join values reactively\r\n const indirectText = useSerial(joinIndirectText ?? 0);\r\n const enabled = useDigital(joinEnable ?? 0);\r\n const visible = useDigital(joinVisible ?? 0);\r\n\r\n // Determine if text is enabled\r\n const isEnabled = joinEnable == null ? true : enabled;\r\n\r\n // Determine if text is visible\r\n const isVisible = joinVisible == null ? true : visible;\r\n\r\n // Get the raw text - indirect text or static text\r\n const rawText = joinIndirectText != null ? indirectText : (text ?? '');\r\n\r\n // Decode CIP patterns in the text\r\n const currentText = useCipDecode(rawText);\r\n\r\n // Don't render if not visible\r\n if (!isVisible) return null;\r\n\r\n // Build class names\r\n const containerClasses = [\r\n 'cris-text',\r\n className,\r\n !isEnabled && classDisabled,\r\n !isEnabled && 'disabled',\r\n ]\r\n .filter(Boolean)\r\n .join(' ');\r\n\r\n return (\r\n <div className={containerClasses} style={style}>\r\n {children}\r\n {currentText && (\r\n <span className={`cris-text-text ${textClassName}`} style={textStyle}>\r\n {currentText}\r\n </span>\r\n )}\r\n </div>\r\n );\r\n}\r\n","import { useState, useRef, useEffect, useCallback } from 'react';\r\nimport { useDigital, useAnalog, useJoinsStore } from '@imperosoft/cris-webui-core';\r\n\r\nexport interface CrisSliderProps {\r\n /** Analog join for value (shared with digital if joinDigital not set) */\r\n join?: number;\r\n /** Digital join for press/release feedback */\r\n joinDigital?: number;\r\n /** Analog join for value (overrides join) */\r\n joinAnalog?: number;\r\n /** Digital join for enable state */\r\n joinEnable?: number;\r\n /** Digital join for visibility */\r\n joinVisible?: number;\r\n\r\n /** Minimum value (default 0) */\r\n minValue?: number;\r\n /** Maximum value (default 65535) */\r\n maxValue?: number;\r\n /** Horizontal orientation (default false = vertical) */\r\n horizontal?: boolean;\r\n /** Hide fill bar */\r\n fillHidden?: boolean;\r\n /** Track size as percentage of container (default 20) */\r\n trackSizePercent?: number;\r\n /** Thumb size as percentage of container (default 4) */\r\n thumbSizePercent?: number;\r\n /** Delay in ms after drag before updating from feedback (default 1000) */\r\n delayMsAfterDragUpdateFeedback?: number;\r\n\r\n /** Container CSS class */\r\n className?: string;\r\n /** Container inline style */\r\n style?: React.CSSProperties;\r\n /** Bar/track CSS class */\r\n barClassName?: string;\r\n /** Bar/track inline style */\r\n barStyle?: React.CSSProperties;\r\n /** Fill CSS class */\r\n fillClassName?: string;\r\n /** Fill inline style */\r\n fillStyle?: React.CSSProperties;\r\n /** Thumb CSS class */\r\n thumbClassName?: string;\r\n /** Thumb inline style */\r\n thumbStyle?: React.CSSProperties;\r\n /** Class when disabled */\r\n classDisabled?: string;\r\n}\r\n\r\nexport function CrisSlider({\r\n join,\r\n joinDigital,\r\n joinAnalog,\r\n joinEnable,\r\n joinVisible,\r\n minValue = 0,\r\n maxValue = 65535,\r\n horizontal = false,\r\n fillHidden = false,\r\n trackSizePercent = 20,\r\n thumbSizePercent = 4,\r\n delayMsAfterDragUpdateFeedback = 1000,\r\n className = '',\r\n style,\r\n barClassName = '',\r\n barStyle,\r\n fillClassName = '',\r\n fillStyle,\r\n thumbClassName = '',\r\n thumbStyle,\r\n classDisabled = '',\r\n}: CrisSliderProps) {\r\n // Effective joins\r\n const effectiveAnalogJoin = joinAnalog ?? join;\r\n const effectiveDigitalJoin = joinDigital ?? join;\r\n\r\n // Get join values reactively\r\n const analogValue = useAnalog(effectiveAnalogJoin ?? 0);\r\n const enabled = useDigital(joinEnable ?? 0);\r\n const visible = useDigital(joinVisible ?? 0);\r\n\r\n // Action methods\r\n const dSet = useJoinsStore((state) => state.dSet);\r\n const aSet = useJoinsStore((state) => state.aSet);\r\n\r\n // Local state\r\n const [ratioCurrent, setRatioCurrent] = useState(0);\r\n const [isDragging, setIsDragging] = useState(false);\r\n const isDraggingOrJustAfterRef = useRef(false);\r\n const ratioBeforeDragRef = useRef(0);\r\n const afterDragTimeoutRef = useRef<number | null>(null);\r\n const touchingRef = useRef(false);\r\n\r\n // Determine if enabled and visible\r\n const isEnabled = joinEnable == null ? true : enabled;\r\n const isVisible = joinVisible == null ? true : visible;\r\n\r\n // Convert analog value to ratio\r\n const analogToRatio = useCallback(\r\n (value: number) => {\r\n const range = maxValue - minValue;\r\n if (range <= 0) return 0;\r\n return Math.max(0, Math.min(1, (value - minValue) / range));\r\n },\r\n [minValue, maxValue]\r\n );\r\n\r\n // Convert ratio to analog value\r\n const ratioToAnalog = useCallback(\r\n (ratio: number) => {\r\n const range = maxValue - minValue;\r\n return Math.round(minValue + ratio * range);\r\n },\r\n [minValue, maxValue]\r\n );\r\n\r\n // Update ratio from controller feedback\r\n const updateFromFeedback = useCallback(() => {\r\n if (!isDraggingOrJustAfterRef.current && effectiveAnalogJoin != null) {\r\n setRatioCurrent(analogToRatio(analogValue));\r\n }\r\n }, [analogValue, analogToRatio, effectiveAnalogJoin]);\r\n\r\n // Update from feedback when analog value changes\r\n useEffect(() => {\r\n updateFromFeedback();\r\n }, [updateFromFeedback]);\r\n\r\n // Handle drag start\r\n const handleDragStart = useCallback(() => {\r\n if (!isEnabled) return;\r\n\r\n setIsDragging(true);\r\n\r\n // Send digital press\r\n if (effectiveDigitalJoin != null) {\r\n dSet(effectiveDigitalJoin, true);\r\n }\r\n\r\n // Save ratio before drag\r\n if (!isDraggingOrJustAfterRef.current) {\r\n ratioBeforeDragRef.current = ratioCurrent;\r\n }\r\n isDraggingOrJustAfterRef.current = true;\r\n\r\n // Clear any pending timeout\r\n if (afterDragTimeoutRef.current !== null) {\r\n window.clearTimeout(afterDragTimeoutRef.current);\r\n afterDragTimeoutRef.current = null;\r\n }\r\n }, [isEnabled, effectiveDigitalJoin, dSet, ratioCurrent]);\r\n\r\n // Handle drag end\r\n const handleDragEnd = useCallback(() => {\r\n if (!isDragging) return;\r\n\r\n setIsDragging(false);\r\n\r\n // Send digital release\r\n if (effectiveDigitalJoin != null) {\r\n dSet(effectiveDigitalJoin, false);\r\n }\r\n\r\n // Schedule feedback update after delay\r\n if (delayMsAfterDragUpdateFeedback > 0) {\r\n afterDragTimeoutRef.current = window.setTimeout(() => {\r\n isDraggingOrJustAfterRef.current = false;\r\n updateFromFeedback();\r\n afterDragTimeoutRef.current = null;\r\n }, delayMsAfterDragUpdateFeedback);\r\n } else {\r\n isDraggingOrJustAfterRef.current = false;\r\n updateFromFeedback();\r\n }\r\n }, [isDragging, effectiveDigitalJoin, dSet, delayMsAfterDragUpdateFeedback, updateFromFeedback]);\r\n\r\n // Handle move/drag\r\n const handleMove = useCallback(\r\n (clientX: number, clientY: number, bounds: DOMRect) => {\r\n if (!isDragging) return;\r\n\r\n let newRatio: number;\r\n if (horizontal) {\r\n newRatio = (clientX - bounds.left) / bounds.width;\r\n } else {\r\n newRatio = 1 - (clientY - bounds.top) / bounds.height;\r\n }\r\n newRatio = Math.max(0, Math.min(1, newRatio));\r\n\r\n setRatioCurrent(newRatio);\r\n\r\n // Send analog value\r\n if (effectiveAnalogJoin != null) {\r\n aSet(effectiveAnalogJoin, ratioToAnalog(newRatio));\r\n }\r\n },\r\n [isDragging, horizontal, effectiveAnalogJoin, aSet, ratioToAnalog]\r\n );\r\n\r\n // Mouse handlers\r\n const handleMouseDown = (event: React.MouseEvent<HTMLDivElement>) => {\r\n if (touchingRef.current) return;\r\n if (event.button !== 0) return;\r\n\r\n handleDragStart();\r\n const bounds = event.currentTarget.getBoundingClientRect();\r\n handleMove(event.clientX, event.clientY, bounds);\r\n };\r\n\r\n const handleMouseMove = (event: React.MouseEvent<HTMLDivElement>) => {\r\n if (!isDragging) return;\r\n const bounds = event.currentTarget.getBoundingClientRect();\r\n handleMove(event.clientX, event.clientY, bounds);\r\n };\r\n\r\n const handleMouseUp = () => {\r\n handleDragEnd();\r\n };\r\n\r\n const handleMouseLeave = (event: React.MouseEvent<HTMLDivElement>) => {\r\n if (isDragging) {\r\n const bounds = event.currentTarget.getBoundingClientRect();\r\n handleMove(event.clientX, event.clientY, bounds);\r\n }\r\n handleDragEnd();\r\n };\r\n\r\n // Touch handlers\r\n const handleTouchStart = (event: React.TouchEvent<HTMLDivElement>) => {\r\n touchingRef.current = true;\r\n handleDragStart();\r\n const bounds = event.currentTarget.getBoundingClientRect();\r\n const touch = event.touches[0];\r\n handleMove(touch.clientX, touch.clientY, bounds);\r\n };\r\n\r\n const handleTouchMove = (event: React.TouchEvent<HTMLDivElement>) => {\r\n if (!isDragging) return;\r\n const bounds = event.currentTarget.getBoundingClientRect();\r\n const touch = event.touches[0];\r\n handleMove(touch.clientX, touch.clientY, bounds);\r\n };\r\n\r\n const handleTouchEnd = () => {\r\n handleDragEnd();\r\n };\r\n\r\n const handleTouchCancel = (event: React.TouchEvent<HTMLDivElement>) => {\r\n if (isDragging && event.touches.length > 0) {\r\n const bounds = event.currentTarget.getBoundingClientRect();\r\n const touch = event.touches[0];\r\n handleMove(touch.clientX, touch.clientY, bounds);\r\n }\r\n handleDragEnd();\r\n };\r\n\r\n // Don't render if not visible\r\n if (!isVisible) return null;\r\n\r\n // Build class names\r\n const containerClasses = [\r\n 'cris-slider',\r\n className,\r\n !isEnabled && classDisabled,\r\n !isEnabled && 'disabled',\r\n ]\r\n .filter(Boolean)\r\n .join(' ');\r\n\r\n // Calculate bar style\r\n const computedBarStyle: React.CSSProperties = {\r\n ...barStyle,\r\n position: 'absolute',\r\n };\r\n\r\n if (horizontal) {\r\n const width = 100 - thumbSizePercent;\r\n const left = (100 - width) / 2;\r\n computedBarStyle.height = `${trackSizePercent}%`;\r\n computedBarStyle.top = `${(100 - trackSizePercent) / 2}%`;\r\n computedBarStyle.width = `${width}%`;\r\n computedBarStyle.left = `${left}%`;\r\n } else {\r\n const height = 100 - thumbSizePercent;\r\n const top = (100 - height) / 2;\r\n computedBarStyle.width = `${trackSizePercent}%`;\r\n computedBarStyle.left = `${(100 - trackSizePercent) / 2}%`;\r\n computedBarStyle.height = `${height}%`;\r\n computedBarStyle.top = `${top}%`;\r\n }\r\n\r\n // Calculate fill style\r\n const computedFillStyle: React.CSSProperties = {\r\n ...fillStyle,\r\n position: 'absolute',\r\n };\r\n\r\n if (horizontal) {\r\n const range = 100 - thumbSizePercent;\r\n const left = thumbSizePercent / 2;\r\n const width = range * ratioCurrent;\r\n computedFillStyle.height = `${trackSizePercent}%`;\r\n computedFillStyle.top = `${(100 - trackSizePercent) / 2}%`;\r\n computedFillStyle.width = `${width}%`;\r\n computedFillStyle.left = `${left}%`;\r\n } else {\r\n const range = 100 - thumbSizePercent;\r\n const top = 100 - (range * ratioCurrent + thumbSizePercent / 2);\r\n const height = range + thumbSizePercent / 2 - top;\r\n computedFillStyle.width = `${trackSizePercent}%`;\r\n computedFillStyle.left = `${(100 - trackSizePercent) / 2}%`;\r\n computedFillStyle.height = `${height}%`;\r\n computedFillStyle.top = `${top}%`;\r\n }\r\n\r\n // Calculate thumb style\r\n const computedThumbStyle: React.CSSProperties = {\r\n ...thumbStyle,\r\n position: 'absolute',\r\n };\r\n\r\n if (horizontal) {\r\n const maxLeft = 100 - thumbSizePercent;\r\n const left = maxLeft * ratioCurrent;\r\n computedThumbStyle.width = `${thumbSizePercent}%`;\r\n computedThumbStyle.height = '100%';\r\n computedThumbStyle.left = `${left}%`;\r\n } else {\r\n const maxTop = 100 - thumbSizePercent;\r\n const top = maxTop - maxTop * ratioCurrent;\r\n computedThumbStyle.width = '100%';\r\n computedThumbStyle.height = `${thumbSizePercent}%`;\r\n computedThumbStyle.top = `${top}%`;\r\n }\r\n\r\n return (\r\n <div\r\n className={containerClasses}\r\n style={{\r\n ...style,\r\n position: 'relative',\r\n cursor: isEnabled ? 'pointer' : 'default',\r\n touchAction: 'none',\r\n userSelect: 'none',\r\n }}\r\n onMouseDown={handleMouseDown}\r\n onMouseMove={handleMouseMove}\r\n onMouseUp={handleMouseUp}\r\n onMouseLeave={handleMouseLeave}\r\n onTouchStart={handleTouchStart}\r\n onTouchMove={handleTouchMove}\r\n onTouchEnd={handleTouchEnd}\r\n onTouchCancel={handleTouchCancel}\r\n >\r\n {/* Bar/Track */}\r\n <div className={`cris-slider-bar ${barClassName}`} style={computedBarStyle} />\r\n\r\n {/* Fill */}\r\n {!fillHidden && (\r\n <div className={`cris-slider-fill ${fillClassName}`} style={computedFillStyle} />\r\n )}\r\n\r\n {/* Thumb */}\r\n <div className={`cris-slider-thumb ${thumbClassName}`} style={computedThumbStyle} />\r\n </div>\r\n );\r\n}\r\n","import { useAnalog, useDigital } from '@imperosoft/cris-webui-core';\r\n\r\nexport interface CrisGaugeProps {\r\n /** Static value (used if no join) */\r\n value?: number;\r\n /** Analog join for value */\r\n join?: number;\r\n /** Digital join for enable state */\r\n joinEnable?: number;\r\n /** Digital join for visibility */\r\n joinVisible?: number;\r\n\r\n /** Minimum value (default 0) */\r\n minValue?: number;\r\n /** Maximum value (default 65535) */\r\n maxValue?: number;\r\n /** Number of segments (default 20) */\r\n segments?: number;\r\n /** Invert direction (default false) */\r\n inverted?: boolean;\r\n /** Orientation (default vertical) */\r\n orientation?: 'vertical' | 'horizontal';\r\n\r\n /** Container CSS class */\r\n className?: string;\r\n /** Container inline style */\r\n style?: React.CSSProperties;\r\n\r\n /** Inactive segment CSS class */\r\n inactiveSegmentClassName?: string;\r\n /** Inactive segment inline style */\r\n inactiveSegmentStyle?: React.CSSProperties;\r\n\r\n /** Active segment CSS class (all levels) */\r\n segmentClassName?: string;\r\n /** Active segment inline style (all levels) */\r\n segmentStyle?: React.CSSProperties;\r\n\r\n /** Low level segment CSS class (0-60%) */\r\n lowSegmentClassName?: string;\r\n /** Low level segment inline style */\r\n lowSegmentStyle?: React.CSSProperties;\r\n\r\n /** Medium level segment CSS class (60-80%) */\r\n mediumSegmentClassName?: string;\r\n /** Medium level segment inline style */\r\n mediumSegmentStyle?: React.CSSProperties;\r\n\r\n /** High level segment CSS class (80-100%) */\r\n highSegmentClassName?: string;\r\n /** High level segment inline style */\r\n highSegmentStyle?: React.CSSProperties;\r\n\r\n /** Class when disabled */\r\n classDisabled?: string;\r\n}\r\n\r\nexport function CrisGauge({\r\n value,\r\n join,\r\n joinEnable,\r\n joinVisible,\r\n minValue = 0,\r\n maxValue = 65535,\r\n segments = 20,\r\n inverted = false,\r\n orientation = 'vertical',\r\n className = '',\r\n style,\r\n inactiveSegmentClassName = '',\r\n inactiveSegmentStyle,\r\n segmentClassName = '',\r\n segmentStyle,\r\n lowSegmentClassName = '',\r\n lowSegmentStyle,\r\n mediumSegmentClassName = '',\r\n mediumSegmentStyle,\r\n highSegmentClassName = '',\r\n highSegmentStyle,\r\n classDisabled = '',\r\n}: CrisGaugeProps) {\r\n // Get join values reactively\r\n const joinValue = useAnalog(join ?? 0);\r\n const enabled = useDigital(joinEnable ?? 0);\r\n const visible = useDigital(joinVisible ?? 0);\r\n\r\n // Determine if enabled and visible\r\n const isEnabled = joinEnable == null ? true : enabled;\r\n const isVisible = joinVisible == null ? true : visible;\r\n\r\n // Get current value\r\n const currentValue = join != null ? joinValue : (value ?? 0);\r\n\r\n // Don't render if not visible\r\n if (!isVisible) return null;\r\n\r\n // Determine segment type (low, medium, high)\r\n const getSegmentType = (segment: number): 'low' | 'medium' | 'high' => {\r\n const lowMax = segments * 0.6;\r\n const mediumMax = segments * 0.8;\r\n\r\n const seg =\r\n (inverted && orientation === 'vertical') || (!inverted && orientation === 'horizontal')\r\n ? segment + 1\r\n : segments - segment;\r\n\r\n if (seg <= lowMax) return 'low';\r\n if (seg <= mediumMax) return 'medium';\r\n return 'high';\r\n };\r\n\r\n // Check if segment is active\r\n const isSegmentActive = (segment: number): boolean => {\r\n if (currentValue < minValue) return false;\r\n if (currentValue > maxValue) return true;\r\n\r\n const ratio = currentValue / (maxValue - minValue);\r\n let segMax = Math.round(segments * ratio);\r\n if (ratio !== 0 && segMax === 0) segMax = 1;\r\n\r\n const seg =\r\n (inverted && orientation === 'vertical') || (!inverted && orientation === 'horizontal')\r\n ? segment + 1\r\n : segments - segment;\r\n\r\n return seg <= segMax;\r\n };\r\n\r\n // Get segment class\r\n const getSegmentClass = (segment: number): string => {\r\n const type = getSegmentType(segment);\r\n const active = isSegmentActive(segment);\r\n\r\n const classes = ['cris-gauge-segment', type, orientation];\r\n\r\n if (active) {\r\n classes.push('active');\r\n // Add type-specific or general active class\r\n if (type === 'low' && (lowSegmentClassName || segmentClassName)) {\r\n classes.push(lowSegmentClassName || segmentClassName);\r\n } else if (type === 'medium' && (mediumSegmentClassName || segmentClassName)) {\r\n classes.push(mediumSegmentClassName || segmentClassName);\r\n } else if (type === 'high' && (highSegmentClassName || segmentClassName)) {\r\n classes.push(highSegmentClassName || segmentClassName);\r\n }\r\n } else {\r\n if (inactiveSegmentClassName) {\r\n classes.push(inactiveSegmentClassName);\r\n }\r\n }\r\n\r\n return classes.filter(Boolean).join(' ');\r\n };\r\n\r\n // Get segment style\r\n const getSegmentStyle = (segment: number): React.CSSProperties | undefined => {\r\n const type = getSegmentType(segment);\r\n const active = isSegmentActive(segment);\r\n\r\n if (active) {\r\n if (type === 'low' && (lowSegmentStyle || segmentStyle)) {\r\n return lowSegmentStyle || segmentStyle;\r\n } else if (type === 'medium' && (mediumSegmentStyle || segmentStyle)) {\r\n return mediumSegmentStyle || segmentStyle;\r\n } else if (type === 'high' && (highSegmentStyle || segmentStyle)) {\r\n return highSegmentStyle || segmentStyle;\r\n }\r\n } else {\r\n return inactiveSegmentStyle;\r\n }\r\n\r\n return undefined;\r\n };\r\n\r\n // Build container classes\r\n const containerClasses = [\r\n 'cris-gauge',\r\n className,\r\n !isEnabled && classDisabled,\r\n !isEnabled && 'disabled',\r\n ]\r\n .filter(Boolean)\r\n .join(' ');\r\n\r\n // Container flex style\r\n const containerStyle: React.CSSProperties = {\r\n ...style,\r\n display: 'flex',\r\n flexDirection: orientation === 'horizontal' ? 'row' : 'column',\r\n justifyContent: 'center',\r\n alignItems: 'center',\r\n };\r\n\r\n return (\r\n <div className={containerClasses} style={containerStyle}>\r\n {Array.from({ length: segments }, (_, segment) => (\r\n <div\r\n key={segment}\r\n className={getSegmentClass(segment)}\r\n style={getSegmentStyle(segment)}\r\n />\r\n ))}\r\n </div>\r\n );\r\n}\r\n","import { useAnalog, useDigital } from '@imperosoft/cris-webui-core';\r\n\r\nexport type SpinnerSpeed = 'slow' | 'medium' | 'fast';\r\n\r\nexport interface CrisSpinnerProps {\r\n /** Analog join for position (0-65535 maps to 0-360 degrees) */\r\n joinPosition?: number;\r\n /** Digital join for visibility */\r\n joinVisible?: number;\r\n\r\n /** Static position value (0-65535, used if no joinPosition) */\r\n position?: number;\r\n\r\n /** Endless rotation mode (default: false) */\r\n endless?: boolean;\r\n /** Speed for endless rotation (default: 'medium') */\r\n endlessSpeed?: SpinnerSpeed;\r\n\r\n /** Spinner color (default: '#bf2b23') */\r\n color?: string;\r\n /** Background/track color (default: 'rgba(0, 0, 0, 0.1)') */\r\n trackColor?: string;\r\n /** Spinner line width in pixels (default: 3) */\r\n lineWidth?: number;\r\n\r\n /** Container CSS class */\r\n className?: string;\r\n /** Container inline style */\r\n style?: React.CSSProperties;\r\n}\r\n\r\nconst SPEED_DURATIONS: Record<SpinnerSpeed, string> = {\r\n slow: '1.5s',\r\n medium: '0.8s',\r\n fast: '0.4s',\r\n};\r\n\r\nexport function CrisSpinner({\r\n joinPosition,\r\n joinVisible,\r\n position,\r\n endless = false,\r\n endlessSpeed = 'medium',\r\n color = '#bf2b23',\r\n trackColor = 'rgba(0, 0, 0, 0.1)',\r\n lineWidth = 3,\r\n className = '',\r\n style,\r\n}: CrisSpinnerProps) {\r\n // Get join values reactively\r\n const joinPositionValue = useAnalog(joinPosition ?? 0);\r\n const visible = useDigital(joinVisible ?? 0);\r\n\r\n // Determine visibility\r\n const isVisible = joinVisible == null ? true : visible;\r\n\r\n // Don't render if not visible\r\n if (!isVisible) return null;\r\n\r\n // Get current position (0-65535 -> 0-360 degrees)\r\n const currentPosition = joinPosition != null ? joinPositionValue : (position ?? 0);\r\n const rotationDegrees = (currentPosition / 65535) * 360;\r\n\r\n // Build container classes\r\n const containerClasses = ['cris-spinner', className].filter(Boolean).join(' ');\r\n\r\n // Spinner styles\r\n const spinnerStyle: React.CSSProperties = {\r\n width: '100%',\r\n height: '100%',\r\n borderRadius: '50%',\r\n border: `${lineWidth}px solid ${trackColor}`,\r\n borderTopColor: color,\r\n boxSizing: 'border-box',\r\n };\r\n\r\n if (endless) {\r\n // Endless rotation animation\r\n spinnerStyle.animation = `cris-spinner-rotate ${SPEED_DURATIONS[endlessSpeed]} linear infinite`;\r\n } else {\r\n // Position-based rotation\r\n spinnerStyle.transform = `rotate(${rotationDegrees}deg)`;\r\n spinnerStyle.transition = 'transform 0.1s linear';\r\n }\r\n\r\n return (\r\n <div className={containerClasses} style={style}>\r\n <div style={spinnerStyle} />\r\n <style>{`\r\n @keyframes cris-spinner-rotate {\r\n to { transform: rotate(360deg); }\r\n }\r\n `}</style>\r\n </div>\r\n );\r\n}\r\n"],"mappings":"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","names":["import_cris_webui_core","import_jsx_runtime","import_react","import_cris_webui_core","import_jsx_runtime","import_cris_webui_core","import_jsx_runtime","import_cris_webui_core","import_jsx_runtime"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/components/CrisButton.tsx","../src/utils/icons.ts","../src/components/CrisText.tsx","../src/components/CrisSlider.tsx","../src/components/CrisGauge.tsx","../src/components/CrisSpinner.tsx"],"sourcesContent":["/**\r\n * @imperosoft/cris-webui-components\r\n *\r\n * CRIS - Crestron React Impero Soft WebUI components library\r\n *\r\n * Provides reusable UI components for Crestron touch panel interfaces.\r\n */\r\n\r\n// Components\r\nexport { CrisButton } from './components/CrisButton';\r\nexport type { CrisButtonProps } from './components/CrisButton';\r\n\r\nexport { CrisText } from './components/CrisText';\r\nexport type { CrisTextProps } from './components/CrisText';\r\n\r\nexport { CrisSlider } from './components/CrisSlider';\r\nexport type { CrisSliderProps } from './components/CrisSlider';\r\n\r\nexport { CrisGauge } from './components/CrisGauge';\r\nexport type { CrisGaugeProps } from './components/CrisGauge';\r\n\r\nexport { CrisSpinner } from './components/CrisSpinner';\r\nexport type { CrisSpinnerProps, SpinnerSpeed } from './components/CrisSpinner';\r\n\r\n// Utilities\r\nexport { configureIcons, getIconUrl, getIconFilter, getIconConfig } from './utils/icons';\r\nexport type { IconConfig } from './utils/icons';\r\n","import { useState, useRef, ReactNode } from 'react';\r\nimport { useDigital, useJoinsStore } from '@imperosoft/cris-webui-core';\r\nimport { getIconUrl, getIconFilter } from '../utils/icons';\r\n\r\n// Global touch state - shared across all CrisButton instances\r\n// This prevents phantom clicks when buttons swap visibility during touch\r\nlet globalTouchActive = false;\r\n\r\nexport interface CrisButtonProps {\r\n /** Digital join for press action */\r\n join?: number;\r\n /** Digital join for feedback (defaults to join) */\r\n joinFeedback?: number;\r\n /** Digital join for enable state */\r\n joinEnable?: number;\r\n /** Digital join for visibility */\r\n joinVisible?: number;\r\n\r\n /** Button text */\r\n text?: string;\r\n /** Text when pressed (local feedback) */\r\n textPressed?: string;\r\n /** Text when selected (controller feedback) */\r\n textSelected?: string;\r\n\r\n /** Icon as ReactNode (for custom SVG components) */\r\n icon?: ReactNode;\r\n /** Icon name (loads SVG from configured path, e.g., 'motor-stop') */\r\n iconName?: string;\r\n /** Icon CSS class (for CSS-based icons, e.g., 'ico-motor-stop') */\r\n iconClass?: string;\r\n /** Icon size - number (px), string ('50%', '2rem'), or preset ('xs'|'sm'|'md'|'lg'|'xl') */\r\n iconSize?: number | string | 'xs' | 'sm' | 'md' | 'lg' | 'xl';\r\n /** Icon container size as percentage (default: 80% for top/bottom, 20% for left/right) */\r\n iconContainerSize?: string;\r\n /** Icon inline style */\r\n iconStyle?: React.CSSProperties;\r\n /** Icon position relative to text */\r\n iconPosition?: 'left' | 'right' | 'top' | 'bottom';\r\n\r\n /** Icon name when active (overrides iconName when button is active) */\r\n iconNameActive?: string;\r\n /** Icon CSS class when active (overrides iconClass when button is active) */\r\n iconClassActive?: string;\r\n /** Icon inline style when active (merged with iconStyle when button is active) */\r\n iconStyleActive?: React.CSSProperties;\r\n\r\n /** Show controller feedback styling */\r\n showControlFeedback?: boolean;\r\n /** Show local press feedback styling */\r\n showLocalFeedback?: boolean;\r\n /** Suppress click actions (display only) */\r\n suppressKeyClicks?: boolean;\r\n\r\n /** Smart object ID (for smarts instead of joins) */\r\n smartId?: number;\r\n\r\n /** Custom class names */\r\n className?: string;\r\n /** Class when active (controller feedback) */\r\n classActive?: string;\r\n /** Class when pressed (local feedback) */\r\n classPressed?: string;\r\n /** Class when disabled */\r\n classDisabled?: string;\r\n\r\n /** Children content */\r\n children?: ReactNode;\r\n\r\n /** Custom click handler (called on press) */\r\n onPress?: () => void;\r\n /** Custom release handler */\r\n onRelease?: () => void;\r\n}\r\n\r\nexport function CrisButton({\r\n join,\r\n joinFeedback,\r\n joinEnable,\r\n joinVisible,\r\n text,\r\n textPressed,\r\n textSelected,\r\n icon,\r\n iconName,\r\n iconClass,\r\n iconSize,\r\n iconContainerSize,\r\n iconStyle,\r\n iconPosition = 'top',\r\n iconNameActive,\r\n iconClassActive,\r\n iconStyleActive,\r\n showControlFeedback = true,\r\n showLocalFeedback = true,\r\n suppressKeyClicks = false,\r\n smartId,\r\n className = '',\r\n classActive = '',\r\n classPressed = '',\r\n classDisabled = '',\r\n children,\r\n onPress,\r\n onRelease,\r\n}: CrisButtonProps) {\r\n const [pressed, setPressed] = useState(false);\r\n const [hovering, setHovering] = useState(false);\r\n const pressedRef = useRef(false);\r\n const touchingRef = useRef(false);\r\n const touchStartedHereRef = useRef(false);\r\n\r\n // Get join values reactively\r\n const feedbackJoin = joinFeedback ?? join;\r\n const feedback = useDigital(feedbackJoin ?? 0);\r\n const enabled = useDigital(joinEnable ?? 0);\r\n const visible = useDigital(joinVisible ?? 0);\r\n\r\n // Get action method\r\n const dSet = useJoinsStore((state) => state.dSet);\r\n\r\n // Determine if button is enabled\r\n const isEnabled = joinEnable == null ? true : enabled;\r\n\r\n // Determine if button is visible\r\n const isVisible = joinVisible == null ? true : visible;\r\n\r\n // Determine current feedback state\r\n const hasControlFeedback = showControlFeedback && feedbackJoin != null && feedback;\r\n const hasPressedFeedback = showLocalFeedback && pressed && isEnabled;\r\n const isActive = hasControlFeedback || hasPressedFeedback;\r\n\r\n // Determine current text\r\n let currentText = text ?? '';\r\n if (hasPressedFeedback && textPressed != null) {\r\n currentText = textPressed;\r\n } else if (hasControlFeedback && textSelected != null) {\r\n currentText = textSelected;\r\n }\r\n\r\n // Event handlers\r\n const handlePress = () => {\r\n if (suppressKeyClicks) return;\r\n if (pressedRef.current) return;\r\n\r\n pressedRef.current = true;\r\n setPressed(true);\r\n\r\n if (!isEnabled) return;\r\n\r\n // Custom handler\r\n onPress?.();\r\n\r\n // Send to controller\r\n if (join != null && smartId == null) {\r\n dSet(join, true);\r\n }\r\n // TODO: Add smartId support when smarts are implemented in core\r\n };\r\n\r\n const handleRelease = () => {\r\n if (suppressKeyClicks) return;\r\n if (!pressedRef.current) return;\r\n\r\n pressedRef.current = false;\r\n setPressed(false);\r\n\r\n if (!isEnabled) return;\r\n\r\n // Custom handler\r\n onRelease?.();\r\n\r\n // Send to controller\r\n if (join != null && smartId == null) {\r\n dSet(join, false);\r\n }\r\n // TODO: Add smartId support when smarts are implemented in core\r\n };\r\n\r\n // Touch event handlers - set global touching flag to prevent mouse events from firing\r\n // Also track if touch started on THIS button to prevent phantom clicks\r\n // when buttons swap visibility (e.g., back button hides, power button appears)\r\n const handleTouchStart = () => {\r\n globalTouchActive = true;\r\n touchingRef.current = true;\r\n touchStartedHereRef.current = true;\r\n handlePress();\r\n };\r\n\r\n const handleTouchEnd = () => {\r\n globalTouchActive = true;\r\n touchingRef.current = true;\r\n // Only fire release if touch actually started on this button\r\n if (touchStartedHereRef.current) {\r\n touchStartedHereRef.current = false;\r\n handleRelease();\r\n }\r\n // Reset global touch flag after a short delay to block synthetic click\r\n setTimeout(() => {\r\n globalTouchActive = false;\r\n }, 300);\r\n };\r\n\r\n const handleTouchCancel = () => {\r\n globalTouchActive = true;\r\n touchingRef.current = true;\r\n touchStartedHereRef.current = false;\r\n handleRelease();\r\n // Reset global touch flag after a short delay\r\n setTimeout(() => {\r\n globalTouchActive = false;\r\n }, 300);\r\n };\r\n\r\n // Mouse event handlers - skip if any touch event is active (global check)\r\n const handleMouseDown = () => {\r\n if (globalTouchActive || touchingRef.current) return;\r\n handlePress();\r\n };\r\n\r\n const handleMouseUp = () => {\r\n if (globalTouchActive || touchingRef.current) return;\r\n handleRelease();\r\n };\r\n\r\n const handleMouseEnter = () => {\r\n if (globalTouchActive || touchingRef.current) return;\r\n if (suppressKeyClicks) return;\r\n setHovering(true);\r\n };\r\n\r\n const handleMouseLeave = () => {\r\n if (globalTouchActive || touchingRef.current) return;\r\n setHovering(false);\r\n handleRelease();\r\n };\r\n\r\n // Don't render if not visible\r\n if (!isVisible) return null;\r\n\r\n // Build class names\r\n const classes = [\r\n 'cris-button',\r\n className,\r\n hasControlFeedback && classActive,\r\n hasPressedFeedback && classPressed,\r\n !isEnabled && classDisabled,\r\n hasControlFeedback && 'active',\r\n hasPressedFeedback && 'pressed',\r\n !isEnabled && 'disabled',\r\n suppressKeyClicks && 'no-hover',\r\n ]\r\n .filter(Boolean)\r\n .join(' ');\r\n\r\n // Flex direction based on icon position\r\n const flexDirection =\r\n iconPosition === 'top'\r\n ? 'flex-col'\r\n : iconPosition === 'bottom'\r\n ? 'flex-col-reverse'\r\n : iconPosition === 'left'\r\n ? 'flex-row'\r\n : 'flex-row-reverse';\r\n\r\n // Determine if we have any icon to show\r\n const hasIcon = icon != null || iconName != null || iconClass != null || iconNameActive != null || iconClassActive != null;\r\n\r\n // Determine current icon properties based on active state\r\n const currentIconName = isActive && iconNameActive != null ? iconNameActive : iconName;\r\n const currentIconClass = isActive && iconClassActive != null ? iconClassActive : iconClass;\r\n const currentIconStyle = isActive && iconStyleActive != null ? { ...iconStyle, ...iconStyleActive } : iconStyle;\r\n\r\n // Size presets mapping\r\n const sizePresets: Record<string, string> = {\r\n xs: '16px',\r\n sm: '24px',\r\n md: '32px',\r\n lg: '48px',\r\n xl: '64px',\r\n };\r\n\r\n // Calculate icon size value\r\n const getIconSizeValue = (): string | undefined => {\r\n if (iconSize == null) return undefined;\r\n if (typeof iconSize === 'number') return `${iconSize}px`;\r\n if (iconSize in sizePresets) return sizePresets[iconSize];\r\n return iconSize; // Already a string like '50%' or '2rem'\r\n };\r\n\r\n const iconSizeValue = getIconSizeValue();\r\n\r\n // Build icon classes\r\n const iconClasses = [\r\n 'cris-button-icon',\r\n currentIconClass,\r\n hasControlFeedback && 'active',\r\n hasPressedFeedback && 'pressed',\r\n ]\r\n .filter(Boolean)\r\n .join(' ');\r\n\r\n // Build icon style with size and filter support\r\n const computedIconStyle: React.CSSProperties = {\r\n ...currentIconStyle,\r\n };\r\n\r\n // Apply size\r\n if (iconSizeValue) {\r\n computedIconStyle.width = iconSizeValue;\r\n computedIconStyle.height = iconSizeValue;\r\n }\r\n\r\n // Apply filter from config if using iconName (only if not overridden by iconStyle/iconStyleActive)\r\n if (currentIconName && !currentIconStyle?.filter) {\r\n const filter = getIconFilter(isActive);\r\n if (filter) {\r\n computedIconStyle.filter = filter;\r\n }\r\n }\r\n\r\n // Render icon element\r\n const renderIconElement = () => {\r\n if (icon) {\r\n // ReactNode icon (custom component)\r\n return <span className={iconClasses} style={computedIconStyle}>{icon}</span>;\r\n }\r\n\r\n if (currentIconName) {\r\n // Icon by name (loads from configured path)\r\n return (\r\n <img\r\n className={iconClasses}\r\n style={computedIconStyle}\r\n src={getIconUrl(currentIconName)}\r\n alt=\"\"\r\n />\r\n );\r\n }\r\n\r\n if (currentIconClass) {\r\n // CSS-based icon (like Angular version)\r\n return <img className={iconClasses} style={computedIconStyle} alt=\"\" />;\r\n }\r\n\r\n return null;\r\n };\r\n\r\n // Calculate icon container size (default: 100% if no text, otherwise 80% for top/bottom, 20% for left/right)\r\n const isVertical = iconPosition === 'top' || iconPosition === 'bottom';\r\n const hasText = currentText !== '';\r\n const defaultContainerSize = hasText ? (isVertical ? '80%' : '20%') : '100%';\r\n const containerSize = iconContainerSize ?? defaultContainerSize;\r\n\r\n // Calculate text container size (remaining space)\r\n const textContainerSize = `calc(100% - ${containerSize})`;\r\n\r\n // Container style for icon area\r\n const iconContainerStyle: React.CSSProperties = isVertical\r\n ? { height: containerSize, width: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center' }\r\n : { width: containerSize, height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center' };\r\n\r\n // Container style for text area\r\n const textContainerStyle: React.CSSProperties = isVertical\r\n ? { height: textContainerSize, width: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center' }\r\n : { width: textContainerSize, height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center' };\r\n\r\n // Check if button has 'free' class (absolute positioning)\r\n const isFreePositioned = className.includes('free');\r\n\r\n return (\r\n <div\r\n className={`${classes} flex items-center justify-center ${flexDirection}`}\r\n style={{\r\n cursor: suppressKeyClicks ? 'default' : 'pointer',\r\n position: isFreePositioned ? undefined : 'relative',\r\n overflow: isFreePositioned ? undefined : 'hidden',\r\n }}\r\n onMouseDown={handleMouseDown}\r\n onMouseUp={handleMouseUp}\r\n onMouseEnter={handleMouseEnter}\r\n onMouseLeave={handleMouseLeave}\r\n onTouchStart={handleTouchStart}\r\n onTouchEnd={handleTouchEnd}\r\n onTouchCancel={handleTouchCancel}\r\n >\r\n {/* Hover overlay - inline styles for touch-safe behavior */}\r\n <div\r\n className=\"cris-button-hover-overlay\"\r\n style={{\r\n position: 'absolute',\r\n inset: 0,\r\n backgroundColor: 'black',\r\n opacity: hovering ? 0.1 : 0,\r\n transition: 'opacity 0.2s',\r\n pointerEvents: 'none',\r\n borderRadius: 'inherit',\r\n }}\r\n />\r\n {children}\r\n {hasIcon && (\r\n <div className=\"cris-button-icon-container\" style={iconContainerStyle}>\r\n {renderIconElement()}\r\n </div>\r\n )}\r\n {currentText && (\r\n <div className=\"cris-button-text-container\" style={textContainerStyle}>\r\n <span className=\"cris-button-text\">{currentText}</span>\r\n </div>\r\n )}\r\n </div>\r\n );\r\n}\r\n","/**\r\n * Icon configuration and utilities for CRIS components\r\n *\r\n * Supports different base paths for dev vs production (Crestron controller)\r\n */\r\n\r\nexport interface IconConfig {\r\n /** Base path for icon assets (e.g., '/assets/icons/' or 'http://controller/icons/') */\r\n basePath: string;\r\n /** File extension (default: 'svg') */\r\n extension: string;\r\n /** Default filter for inactive state */\r\n defaultFilter?: string;\r\n /** Default filter for active state */\r\n activeFilter?: string;\r\n}\r\n\r\n// Default configuration\r\nlet iconConfig: IconConfig = {\r\n basePath: '/assets/icons/',\r\n extension: 'svg',\r\n defaultFilter: undefined,\r\n activeFilter: undefined,\r\n};\r\n\r\n/**\r\n * Configure icon paths and defaults\r\n * Call this once at app startup\r\n *\r\n * @example\r\n * // Development\r\n * configureIcons({ basePath: '/assets/icons/' });\r\n *\r\n * // Production on Crestron controller\r\n * configureIcons({ basePath: '/html/icons/' });\r\n *\r\n * // With color filters\r\n * configureIcons({\r\n * basePath: '/assets/icons/',\r\n * defaultFilter: 'brightness(0) invert(1)', // white\r\n * activeFilter: 'brightness(0) invert(0.5) sepia(1) saturate(5) hue-rotate(0deg)', // red\r\n * });\r\n */\r\nexport function configureIcons(config: Partial<IconConfig>): void {\r\n iconConfig = { ...iconConfig, ...config };\r\n}\r\n\r\n/**\r\n * Get current icon configuration\r\n */\r\nexport function getIconConfig(): IconConfig {\r\n return iconConfig;\r\n}\r\n\r\n/**\r\n * Get full URL for an icon\r\n *\r\n * @param name - Icon name (without path or extension), e.g., 'motor-stop'\r\n * @returns Full URL to the icon\r\n */\r\nexport function getIconUrl(name: string): string {\r\n if (!name) return '';\r\n\r\n // If already a full URL or path, return as-is\r\n if (name.startsWith('http') || name.startsWith('/') || name.startsWith('.')) {\r\n return name;\r\n }\r\n\r\n const { basePath, extension } = iconConfig;\r\n const normalizedBase = basePath.endsWith('/') ? basePath : `${basePath}/`;\r\n return `${normalizedBase}${name}.${extension}`;\r\n}\r\n\r\n/**\r\n * Get CSS filter for icon state\r\n */\r\nexport function getIconFilter(active: boolean): string | undefined {\r\n return active ? iconConfig.activeFilter : iconConfig.defaultFilter;\r\n}\r\n","import { ReactNode } from 'react';\r\nimport { useDigital, useSerial, useCipDecode } from '@imperosoft/cris-webui-core';\r\n\r\nexport interface CrisTextProps {\r\n /** Serial join for indirect text */\r\n joinIndirectText?: number;\r\n /** Digital join for enable state */\r\n joinEnable?: number;\r\n /** Digital join for visibility */\r\n joinVisible?: number;\r\n /** Static text (used if no joinIndirectText) */\r\n text?: string;\r\n /** Container CSS class */\r\n className?: string;\r\n /** Container inline style */\r\n style?: React.CSSProperties;\r\n /** Text element CSS class */\r\n textClassName?: string;\r\n /** Text element inline style */\r\n textStyle?: React.CSSProperties;\r\n /** Class when disabled */\r\n classDisabled?: string;\r\n /** Children content */\r\n children?: ReactNode;\r\n}\r\n\r\nexport function CrisText({\r\n joinIndirectText,\r\n joinEnable,\r\n joinVisible,\r\n text,\r\n className = '',\r\n style,\r\n textClassName = '',\r\n textStyle,\r\n classDisabled = '',\r\n children,\r\n}: CrisTextProps) {\r\n // Get join values reactively\r\n const indirectText = useSerial(joinIndirectText ?? 0);\r\n const enabled = useDigital(joinEnable ?? 0);\r\n const visible = useDigital(joinVisible ?? 0);\r\n\r\n // Determine if text is enabled\r\n const isEnabled = joinEnable == null ? true : enabled;\r\n\r\n // Determine if text is visible\r\n const isVisible = joinVisible == null ? true : visible;\r\n\r\n // Get the raw text - indirect text or static text\r\n const rawText = joinIndirectText != null ? indirectText : (text ?? '');\r\n\r\n // Decode CIP patterns in the text\r\n const currentText = useCipDecode(rawText);\r\n\r\n // Don't render if not visible\r\n if (!isVisible) return null;\r\n\r\n // Build class names\r\n const containerClasses = [\r\n 'cris-text',\r\n className,\r\n !isEnabled && classDisabled,\r\n !isEnabled && 'disabled',\r\n ]\r\n .filter(Boolean)\r\n .join(' ');\r\n\r\n return (\r\n <div className={containerClasses} style={style}>\r\n {children}\r\n {currentText && (\r\n <span className={`cris-text-text ${textClassName}`} style={textStyle}>\r\n {currentText}\r\n </span>\r\n )}\r\n </div>\r\n );\r\n}\r\n","import { useState, useRef, useEffect, useCallback } from 'react';\r\nimport { useDigital, useAnalog, useJoinsStore } from '@imperosoft/cris-webui-core';\r\n\r\nexport interface CrisSliderProps {\r\n /** Analog join for value (shared with digital if joinDigital not set) */\r\n join?: number;\r\n /** Digital join for press/release feedback */\r\n joinDigital?: number;\r\n /** Analog join for value (overrides join) */\r\n joinAnalog?: number;\r\n /** Digital join for enable state */\r\n joinEnable?: number;\r\n /** Digital join for visibility */\r\n joinVisible?: number;\r\n\r\n /** Minimum value (default 0) */\r\n minValue?: number;\r\n /** Maximum value (default 65535) */\r\n maxValue?: number;\r\n /** Horizontal orientation (default false = vertical) */\r\n horizontal?: boolean;\r\n /** Hide fill bar */\r\n fillHidden?: boolean;\r\n /** Track size as percentage of container (default 20) */\r\n trackSizePercent?: number;\r\n /** Thumb size as percentage of container (default 4) */\r\n thumbSizePercent?: number;\r\n /** Delay in ms after drag before updating from feedback (default 1000) */\r\n delayMsAfterDragUpdateFeedback?: number;\r\n\r\n /** Container CSS class */\r\n className?: string;\r\n /** Container inline style */\r\n style?: React.CSSProperties;\r\n /** Bar/track CSS class */\r\n barClassName?: string;\r\n /** Bar/track inline style */\r\n barStyle?: React.CSSProperties;\r\n /** Fill CSS class */\r\n fillClassName?: string;\r\n /** Fill inline style */\r\n fillStyle?: React.CSSProperties;\r\n /** Thumb CSS class */\r\n thumbClassName?: string;\r\n /** Thumb inline style */\r\n thumbStyle?: React.CSSProperties;\r\n /** Class when disabled */\r\n classDisabled?: string;\r\n}\r\n\r\nexport function CrisSlider({\r\n join,\r\n joinDigital,\r\n joinAnalog,\r\n joinEnable,\r\n joinVisible,\r\n minValue = 0,\r\n maxValue = 65535,\r\n horizontal = false,\r\n fillHidden = false,\r\n trackSizePercent = 20,\r\n thumbSizePercent = 4,\r\n delayMsAfterDragUpdateFeedback = 1000,\r\n className = '',\r\n style,\r\n barClassName = '',\r\n barStyle,\r\n fillClassName = '',\r\n fillStyle,\r\n thumbClassName = '',\r\n thumbStyle,\r\n classDisabled = '',\r\n}: CrisSliderProps) {\r\n // Effective joins\r\n const effectiveAnalogJoin = joinAnalog ?? join;\r\n const effectiveDigitalJoin = joinDigital ?? join;\r\n\r\n // Get join values reactively\r\n const analogValue = useAnalog(effectiveAnalogJoin ?? 0);\r\n const enabled = useDigital(joinEnable ?? 0);\r\n const visible = useDigital(joinVisible ?? 0);\r\n\r\n // Action methods\r\n const dSet = useJoinsStore((state) => state.dSet);\r\n const aSet = useJoinsStore((state) => state.aSet);\r\n\r\n // Local state\r\n const [ratioCurrent, setRatioCurrent] = useState(0);\r\n const [isDragging, setIsDragging] = useState(false);\r\n const isDraggingOrJustAfterRef = useRef(false);\r\n const ratioBeforeDragRef = useRef(0);\r\n const afterDragTimeoutRef = useRef<number | null>(null);\r\n const touchingRef = useRef(false);\r\n\r\n // Determine if enabled and visible\r\n const isEnabled = joinEnable == null ? true : enabled;\r\n const isVisible = joinVisible == null ? true : visible;\r\n\r\n // Convert analog value to ratio\r\n const analogToRatio = useCallback(\r\n (value: number) => {\r\n const range = maxValue - minValue;\r\n if (range <= 0) return 0;\r\n return Math.max(0, Math.min(1, (value - minValue) / range));\r\n },\r\n [minValue, maxValue]\r\n );\r\n\r\n // Convert ratio to analog value\r\n const ratioToAnalog = useCallback(\r\n (ratio: number) => {\r\n const range = maxValue - minValue;\r\n return Math.round(minValue + ratio * range);\r\n },\r\n [minValue, maxValue]\r\n );\r\n\r\n // Update ratio from controller feedback\r\n const updateFromFeedback = useCallback(() => {\r\n if (!isDraggingOrJustAfterRef.current && effectiveAnalogJoin != null) {\r\n setRatioCurrent(analogToRatio(analogValue));\r\n }\r\n }, [analogValue, analogToRatio, effectiveAnalogJoin]);\r\n\r\n // Update from feedback when analog value changes\r\n useEffect(() => {\r\n updateFromFeedback();\r\n }, [updateFromFeedback]);\r\n\r\n // Handle drag start\r\n const handleDragStart = useCallback(() => {\r\n if (!isEnabled) return;\r\n\r\n setIsDragging(true);\r\n\r\n // Send digital press\r\n if (effectiveDigitalJoin != null) {\r\n dSet(effectiveDigitalJoin, true);\r\n }\r\n\r\n // Save ratio before drag\r\n if (!isDraggingOrJustAfterRef.current) {\r\n ratioBeforeDragRef.current = ratioCurrent;\r\n }\r\n isDraggingOrJustAfterRef.current = true;\r\n\r\n // Clear any pending timeout\r\n if (afterDragTimeoutRef.current !== null) {\r\n window.clearTimeout(afterDragTimeoutRef.current);\r\n afterDragTimeoutRef.current = null;\r\n }\r\n }, [isEnabled, effectiveDigitalJoin, dSet, ratioCurrent]);\r\n\r\n // Handle drag end\r\n const handleDragEnd = useCallback(() => {\r\n if (!isDragging) return;\r\n\r\n setIsDragging(false);\r\n\r\n // Send digital release\r\n if (effectiveDigitalJoin != null) {\r\n dSet(effectiveDigitalJoin, false);\r\n }\r\n\r\n // Schedule feedback update after delay\r\n if (delayMsAfterDragUpdateFeedback > 0) {\r\n afterDragTimeoutRef.current = window.setTimeout(() => {\r\n isDraggingOrJustAfterRef.current = false;\r\n updateFromFeedback();\r\n afterDragTimeoutRef.current = null;\r\n }, delayMsAfterDragUpdateFeedback);\r\n } else {\r\n isDraggingOrJustAfterRef.current = false;\r\n updateFromFeedback();\r\n }\r\n }, [isDragging, effectiveDigitalJoin, dSet, delayMsAfterDragUpdateFeedback, updateFromFeedback]);\r\n\r\n // Handle move/drag\r\n const handleMove = useCallback(\r\n (clientX: number, clientY: number, bounds: DOMRect) => {\r\n if (!isDragging) return;\r\n\r\n let newRatio: number;\r\n if (horizontal) {\r\n newRatio = (clientX - bounds.left) / bounds.width;\r\n } else {\r\n newRatio = 1 - (clientY - bounds.top) / bounds.height;\r\n }\r\n newRatio = Math.max(0, Math.min(1, newRatio));\r\n\r\n setRatioCurrent(newRatio);\r\n\r\n // Send analog value\r\n if (effectiveAnalogJoin != null) {\r\n aSet(effectiveAnalogJoin, ratioToAnalog(newRatio));\r\n }\r\n },\r\n [isDragging, horizontal, effectiveAnalogJoin, aSet, ratioToAnalog]\r\n );\r\n\r\n // Mouse handlers\r\n const handleMouseDown = (event: React.MouseEvent<HTMLDivElement>) => {\r\n if (touchingRef.current) return;\r\n if (event.button !== 0) return;\r\n\r\n handleDragStart();\r\n const bounds = event.currentTarget.getBoundingClientRect();\r\n handleMove(event.clientX, event.clientY, bounds);\r\n };\r\n\r\n const handleMouseMove = (event: React.MouseEvent<HTMLDivElement>) => {\r\n if (!isDragging) return;\r\n const bounds = event.currentTarget.getBoundingClientRect();\r\n handleMove(event.clientX, event.clientY, bounds);\r\n };\r\n\r\n const handleMouseUp = () => {\r\n handleDragEnd();\r\n };\r\n\r\n const handleMouseLeave = (event: React.MouseEvent<HTMLDivElement>) => {\r\n if (isDragging) {\r\n const bounds = event.currentTarget.getBoundingClientRect();\r\n handleMove(event.clientX, event.clientY, bounds);\r\n }\r\n handleDragEnd();\r\n };\r\n\r\n // Touch handlers\r\n const handleTouchStart = (event: React.TouchEvent<HTMLDivElement>) => {\r\n touchingRef.current = true;\r\n handleDragStart();\r\n const bounds = event.currentTarget.getBoundingClientRect();\r\n const touch = event.touches[0];\r\n handleMove(touch.clientX, touch.clientY, bounds);\r\n };\r\n\r\n const handleTouchMove = (event: React.TouchEvent<HTMLDivElement>) => {\r\n if (!isDragging) return;\r\n const bounds = event.currentTarget.getBoundingClientRect();\r\n const touch = event.touches[0];\r\n handleMove(touch.clientX, touch.clientY, bounds);\r\n };\r\n\r\n const handleTouchEnd = () => {\r\n handleDragEnd();\r\n };\r\n\r\n const handleTouchCancel = (event: React.TouchEvent<HTMLDivElement>) => {\r\n if (isDragging && event.touches.length > 0) {\r\n const bounds = event.currentTarget.getBoundingClientRect();\r\n const touch = event.touches[0];\r\n handleMove(touch.clientX, touch.clientY, bounds);\r\n }\r\n handleDragEnd();\r\n };\r\n\r\n // Don't render if not visible\r\n if (!isVisible) return null;\r\n\r\n // Build class names\r\n const containerClasses = [\r\n 'cris-slider',\r\n className,\r\n !isEnabled && classDisabled,\r\n !isEnabled && 'disabled',\r\n ]\r\n .filter(Boolean)\r\n .join(' ');\r\n\r\n // Calculate bar style\r\n const computedBarStyle: React.CSSProperties = {\r\n ...barStyle,\r\n position: 'absolute',\r\n };\r\n\r\n if (horizontal) {\r\n const width = 100 - thumbSizePercent;\r\n const left = (100 - width) / 2;\r\n computedBarStyle.height = `${trackSizePercent}%`;\r\n computedBarStyle.top = `${(100 - trackSizePercent) / 2}%`;\r\n computedBarStyle.width = `${width}%`;\r\n computedBarStyle.left = `${left}%`;\r\n } else {\r\n const height = 100 - thumbSizePercent;\r\n const top = (100 - height) / 2;\r\n computedBarStyle.width = `${trackSizePercent}%`;\r\n computedBarStyle.left = `${(100 - trackSizePercent) / 2}%`;\r\n computedBarStyle.height = `${height}%`;\r\n computedBarStyle.top = `${top}%`;\r\n }\r\n\r\n // Calculate fill style\r\n const computedFillStyle: React.CSSProperties = {\r\n ...fillStyle,\r\n position: 'absolute',\r\n };\r\n\r\n if (horizontal) {\r\n const range = 100 - thumbSizePercent;\r\n const left = thumbSizePercent / 2;\r\n const width = range * ratioCurrent;\r\n computedFillStyle.height = `${trackSizePercent}%`;\r\n computedFillStyle.top = `${(100 - trackSizePercent) / 2}%`;\r\n computedFillStyle.width = `${width}%`;\r\n computedFillStyle.left = `${left}%`;\r\n } else {\r\n const range = 100 - thumbSizePercent;\r\n const top = 100 - (range * ratioCurrent + thumbSizePercent / 2);\r\n const height = range + thumbSizePercent / 2 - top;\r\n computedFillStyle.width = `${trackSizePercent}%`;\r\n computedFillStyle.left = `${(100 - trackSizePercent) / 2}%`;\r\n computedFillStyle.height = `${height}%`;\r\n computedFillStyle.top = `${top}%`;\r\n }\r\n\r\n // Calculate thumb style\r\n const computedThumbStyle: React.CSSProperties = {\r\n ...thumbStyle,\r\n position: 'absolute',\r\n };\r\n\r\n if (horizontal) {\r\n const maxLeft = 100 - thumbSizePercent;\r\n const left = maxLeft * ratioCurrent;\r\n computedThumbStyle.width = `${thumbSizePercent}%`;\r\n computedThumbStyle.height = '100%';\r\n computedThumbStyle.left = `${left}%`;\r\n } else {\r\n const maxTop = 100 - thumbSizePercent;\r\n const top = maxTop - maxTop * ratioCurrent;\r\n computedThumbStyle.width = '100%';\r\n computedThumbStyle.height = `${thumbSizePercent}%`;\r\n computedThumbStyle.top = `${top}%`;\r\n }\r\n\r\n return (\r\n <div\r\n className={containerClasses}\r\n style={{\r\n ...style,\r\n position: 'relative',\r\n cursor: isEnabled ? 'pointer' : 'default',\r\n touchAction: 'none',\r\n userSelect: 'none',\r\n }}\r\n onMouseDown={handleMouseDown}\r\n onMouseMove={handleMouseMove}\r\n onMouseUp={handleMouseUp}\r\n onMouseLeave={handleMouseLeave}\r\n onTouchStart={handleTouchStart}\r\n onTouchMove={handleTouchMove}\r\n onTouchEnd={handleTouchEnd}\r\n onTouchCancel={handleTouchCancel}\r\n >\r\n {/* Bar/Track */}\r\n <div className={`cris-slider-bar ${barClassName}`} style={computedBarStyle} />\r\n\r\n {/* Fill */}\r\n {!fillHidden && (\r\n <div className={`cris-slider-fill ${fillClassName}`} style={computedFillStyle} />\r\n )}\r\n\r\n {/* Thumb */}\r\n <div className={`cris-slider-thumb ${thumbClassName}`} style={computedThumbStyle} />\r\n </div>\r\n );\r\n}\r\n","import { useAnalog, useDigital } from '@imperosoft/cris-webui-core';\r\n\r\nexport interface CrisGaugeProps {\r\n /** Static value (used if no join) */\r\n value?: number;\r\n /** Analog join for value */\r\n join?: number;\r\n /** Digital join for enable state */\r\n joinEnable?: number;\r\n /** Digital join for visibility */\r\n joinVisible?: number;\r\n\r\n /** Minimum value (default 0) */\r\n minValue?: number;\r\n /** Maximum value (default 65535) */\r\n maxValue?: number;\r\n /** Number of segments (default 20) */\r\n segments?: number;\r\n /** Invert direction (default false) */\r\n inverted?: boolean;\r\n /** Orientation (default vertical) */\r\n orientation?: 'vertical' | 'horizontal';\r\n\r\n /** Container CSS class */\r\n className?: string;\r\n /** Container inline style */\r\n style?: React.CSSProperties;\r\n\r\n /** Inactive segment CSS class */\r\n inactiveSegmentClassName?: string;\r\n /** Inactive segment inline style */\r\n inactiveSegmentStyle?: React.CSSProperties;\r\n\r\n /** Active segment CSS class (all levels) */\r\n segmentClassName?: string;\r\n /** Active segment inline style (all levels) */\r\n segmentStyle?: React.CSSProperties;\r\n\r\n /** Low level segment CSS class (0-60%) */\r\n lowSegmentClassName?: string;\r\n /** Low level segment inline style */\r\n lowSegmentStyle?: React.CSSProperties;\r\n\r\n /** Medium level segment CSS class (60-80%) */\r\n mediumSegmentClassName?: string;\r\n /** Medium level segment inline style */\r\n mediumSegmentStyle?: React.CSSProperties;\r\n\r\n /** High level segment CSS class (80-100%) */\r\n highSegmentClassName?: string;\r\n /** High level segment inline style */\r\n highSegmentStyle?: React.CSSProperties;\r\n\r\n /** Class when disabled */\r\n classDisabled?: string;\r\n}\r\n\r\nexport function CrisGauge({\r\n value,\r\n join,\r\n joinEnable,\r\n joinVisible,\r\n minValue = 0,\r\n maxValue = 65535,\r\n segments = 20,\r\n inverted = false,\r\n orientation = 'vertical',\r\n className = '',\r\n style,\r\n inactiveSegmentClassName = '',\r\n inactiveSegmentStyle,\r\n segmentClassName = '',\r\n segmentStyle,\r\n lowSegmentClassName = '',\r\n lowSegmentStyle,\r\n mediumSegmentClassName = '',\r\n mediumSegmentStyle,\r\n highSegmentClassName = '',\r\n highSegmentStyle,\r\n classDisabled = '',\r\n}: CrisGaugeProps) {\r\n // Get join values reactively\r\n const joinValue = useAnalog(join ?? 0);\r\n const enabled = useDigital(joinEnable ?? 0);\r\n const visible = useDigital(joinVisible ?? 0);\r\n\r\n // Determine if enabled and visible\r\n const isEnabled = joinEnable == null ? true : enabled;\r\n const isVisible = joinVisible == null ? true : visible;\r\n\r\n // Get current value\r\n const currentValue = join != null ? joinValue : (value ?? 0);\r\n\r\n // Don't render if not visible\r\n if (!isVisible) return null;\r\n\r\n // Determine segment type (low, medium, high)\r\n const getSegmentType = (segment: number): 'low' | 'medium' | 'high' => {\r\n const lowMax = segments * 0.6;\r\n const mediumMax = segments * 0.8;\r\n\r\n const seg =\r\n (inverted && orientation === 'vertical') || (!inverted && orientation === 'horizontal')\r\n ? segment + 1\r\n : segments - segment;\r\n\r\n if (seg <= lowMax) return 'low';\r\n if (seg <= mediumMax) return 'medium';\r\n return 'high';\r\n };\r\n\r\n // Check if segment is active\r\n const isSegmentActive = (segment: number): boolean => {\r\n if (currentValue < minValue) return false;\r\n if (currentValue > maxValue) return true;\r\n\r\n const ratio = currentValue / (maxValue - minValue);\r\n let segMax = Math.round(segments * ratio);\r\n if (ratio !== 0 && segMax === 0) segMax = 1;\r\n\r\n const seg =\r\n (inverted && orientation === 'vertical') || (!inverted && orientation === 'horizontal')\r\n ? segment + 1\r\n : segments - segment;\r\n\r\n return seg <= segMax;\r\n };\r\n\r\n // Get segment class\r\n const getSegmentClass = (segment: number): string => {\r\n const type = getSegmentType(segment);\r\n const active = isSegmentActive(segment);\r\n\r\n const classes = ['cris-gauge-segment', type, orientation];\r\n\r\n if (active) {\r\n classes.push('active');\r\n // Add type-specific or general active class\r\n if (type === 'low' && (lowSegmentClassName || segmentClassName)) {\r\n classes.push(lowSegmentClassName || segmentClassName);\r\n } else if (type === 'medium' && (mediumSegmentClassName || segmentClassName)) {\r\n classes.push(mediumSegmentClassName || segmentClassName);\r\n } else if (type === 'high' && (highSegmentClassName || segmentClassName)) {\r\n classes.push(highSegmentClassName || segmentClassName);\r\n }\r\n } else {\r\n if (inactiveSegmentClassName) {\r\n classes.push(inactiveSegmentClassName);\r\n }\r\n }\r\n\r\n return classes.filter(Boolean).join(' ');\r\n };\r\n\r\n // Get segment style\r\n const getSegmentStyle = (segment: number): React.CSSProperties | undefined => {\r\n const type = getSegmentType(segment);\r\n const active = isSegmentActive(segment);\r\n\r\n if (active) {\r\n if (type === 'low' && (lowSegmentStyle || segmentStyle)) {\r\n return lowSegmentStyle || segmentStyle;\r\n } else if (type === 'medium' && (mediumSegmentStyle || segmentStyle)) {\r\n return mediumSegmentStyle || segmentStyle;\r\n } else if (type === 'high' && (highSegmentStyle || segmentStyle)) {\r\n return highSegmentStyle || segmentStyle;\r\n }\r\n } else {\r\n return inactiveSegmentStyle;\r\n }\r\n\r\n return undefined;\r\n };\r\n\r\n // Build container classes\r\n const containerClasses = [\r\n 'cris-gauge',\r\n className,\r\n !isEnabled && classDisabled,\r\n !isEnabled && 'disabled',\r\n ]\r\n .filter(Boolean)\r\n .join(' ');\r\n\r\n // Container flex style\r\n const containerStyle: React.CSSProperties = {\r\n ...style,\r\n display: 'flex',\r\n flexDirection: orientation === 'horizontal' ? 'row' : 'column',\r\n justifyContent: 'center',\r\n alignItems: 'center',\r\n };\r\n\r\n return (\r\n <div className={containerClasses} style={containerStyle}>\r\n {Array.from({ length: segments }, (_, segment) => (\r\n <div\r\n key={segment}\r\n className={getSegmentClass(segment)}\r\n style={getSegmentStyle(segment)}\r\n />\r\n ))}\r\n </div>\r\n );\r\n}\r\n","import { useAnalog, useDigital } from '@imperosoft/cris-webui-core';\r\n\r\nexport type SpinnerSpeed = 'slow' | 'medium' | 'fast';\r\n\r\nexport interface CrisSpinnerProps {\r\n /** Analog join for position (0-65535 maps to 0-360 degrees) */\r\n joinPosition?: number;\r\n /** Digital join for visibility */\r\n joinVisible?: number;\r\n\r\n /** Static position value (0-65535, used if no joinPosition) */\r\n position?: number;\r\n\r\n /** Endless rotation mode (default: false) */\r\n endless?: boolean;\r\n /** Speed for endless rotation (default: 'medium') */\r\n endlessSpeed?: SpinnerSpeed;\r\n\r\n /** Spinner color (default: '#bf2b23') */\r\n color?: string;\r\n /** Background/track color (default: 'rgba(0, 0, 0, 0.1)') */\r\n trackColor?: string;\r\n /** Spinner line width in pixels (default: 3) */\r\n lineWidth?: number;\r\n\r\n /** Container CSS class */\r\n className?: string;\r\n /** Container inline style */\r\n style?: React.CSSProperties;\r\n}\r\n\r\nconst SPEED_DURATIONS: Record<SpinnerSpeed, string> = {\r\n slow: '1.5s',\r\n medium: '0.8s',\r\n fast: '0.4s',\r\n};\r\n\r\nexport function CrisSpinner({\r\n joinPosition,\r\n joinVisible,\r\n position,\r\n endless = false,\r\n endlessSpeed = 'medium',\r\n color = '#bf2b23',\r\n trackColor = 'rgba(0, 0, 0, 0.1)',\r\n lineWidth = 3,\r\n className = '',\r\n style,\r\n}: CrisSpinnerProps) {\r\n // Get join values reactively\r\n const joinPositionValue = useAnalog(joinPosition ?? 0);\r\n const visible = useDigital(joinVisible ?? 0);\r\n\r\n // Determine visibility\r\n const isVisible = joinVisible == null ? true : visible;\r\n\r\n // Don't render if not visible\r\n if (!isVisible) return null;\r\n\r\n // Get current position (0-65535 -> 0-360 degrees)\r\n const currentPosition = joinPosition != null ? joinPositionValue : (position ?? 0);\r\n const rotationDegrees = (currentPosition / 65535) * 360;\r\n\r\n // Build container classes\r\n const containerClasses = ['cris-spinner', className].filter(Boolean).join(' ');\r\n\r\n // Spinner styles\r\n const spinnerStyle: React.CSSProperties = {\r\n width: '100%',\r\n height: '100%',\r\n borderRadius: '50%',\r\n border: `${lineWidth}px solid ${trackColor}`,\r\n borderTopColor: color,\r\n boxSizing: 'border-box',\r\n };\r\n\r\n if (endless) {\r\n // Endless rotation animation\r\n spinnerStyle.animation = `cris-spinner-rotate ${SPEED_DURATIONS[endlessSpeed]} linear infinite`;\r\n } else {\r\n // Position-based rotation\r\n spinnerStyle.transform = `rotate(${rotationDegrees}deg)`;\r\n spinnerStyle.transition = 'transform 0.1s linear';\r\n }\r\n\r\n return (\r\n <div className={containerClasses} style={style}>\r\n <div style={spinnerStyle} />\r\n <style>{`\r\n @keyframes cris-spinner-rotate {\r\n to { transform: rotate(360deg); }\r\n }\r\n `}</style>\r\n </div>\r\n );\r\n}\r\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,mBAA4C;AAC5C,6BAA0C;;;ACiB1C,IAAI,aAAyB;AAAA,EAC3B,UAAU;AAAA,EACV,WAAW;AAAA,EACX,eAAe;AAAA,EACf,cAAc;AAChB;AAoBO,SAAS,eAAe,QAAmC;AAChE,eAAa,EAAE,GAAG,YAAY,GAAG,OAAO;AAC1C;AAKO,SAAS,gBAA4B;AAC1C,SAAO;AACT;AAQO,SAAS,WAAW,MAAsB;AAC/C,MAAI,CAAC,KAAM,QAAO;AAGlB,MAAI,KAAK,WAAW,MAAM,KAAK,KAAK,WAAW,GAAG,KAAK,KAAK,WAAW,GAAG,GAAG;AAC3E,WAAO;AAAA,EACT;AAEA,QAAM,EAAE,UAAU,UAAU,IAAI;AAChC,QAAM,iBAAiB,SAAS,SAAS,GAAG,IAAI,WAAW,GAAG,QAAQ;AACtE,SAAO,GAAG,cAAc,GAAG,IAAI,IAAI,SAAS;AAC9C;AAKO,SAAS,cAAc,QAAqC;AACjE,SAAO,SAAS,WAAW,eAAe,WAAW;AACvD;;;ADsPa;AA9Tb,IAAI,oBAAoB;AAqEjB,SAAS,WAAW;AAAA,EACzB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,eAAe;AAAA,EACf;AAAA,EACA;AAAA,EACA;AAAA,EACA,sBAAsB;AAAA,EACtB,oBAAoB;AAAA,EACpB,oBAAoB;AAAA,EACpB;AAAA,EACA,YAAY;AAAA,EACZ,cAAc;AAAA,EACd,eAAe;AAAA,EACf,gBAAgB;AAAA,EAChB;AAAA,EACA;AAAA,EACA;AACF,GAAoB;AAClB,QAAM,CAAC,SAAS,UAAU,QAAI,uBAAS,KAAK;AAC5C,QAAM,CAAC,UAAU,WAAW,QAAI,uBAAS,KAAK;AAC9C,QAAM,iBAAa,qBAAO,KAAK;AAC/B,QAAM,kBAAc,qBAAO,KAAK;AAChC,QAAM,0BAAsB,qBAAO,KAAK;AAGxC,QAAM,eAAe,gBAAgB;AACrC,QAAM,eAAW,mCAAW,gBAAgB,CAAC;AAC7C,QAAM,cAAU,mCAAW,cAAc,CAAC;AAC1C,QAAM,cAAU,mCAAW,eAAe,CAAC;AAG3C,QAAM,WAAO,sCAAc,CAAC,UAAU,MAAM,IAAI;AAGhD,QAAM,YAAY,cAAc,OAAO,OAAO;AAG9C,QAAM,YAAY,eAAe,OAAO,OAAO;AAG/C,QAAM,qBAAqB,uBAAuB,gBAAgB,QAAQ;AAC1E,QAAM,qBAAqB,qBAAqB,WAAW;AAC3D,QAAM,WAAW,sBAAsB;AAGvC,MAAI,cAAc,QAAQ;AAC1B,MAAI,sBAAsB,eAAe,MAAM;AAC7C,kBAAc;AAAA,EAChB,WAAW,sBAAsB,gBAAgB,MAAM;AACrD,kBAAc;AAAA,EAChB;AAGA,QAAM,cAAc,MAAM;AACxB,QAAI,kBAAmB;AACvB,QAAI,WAAW,QAAS;AAExB,eAAW,UAAU;AACrB,eAAW,IAAI;AAEf,QAAI,CAAC,UAAW;AAGhB,cAAU;AAGV,QAAI,QAAQ,QAAQ,WAAW,MAAM;AACnC,WAAK,MAAM,IAAI;AAAA,IACjB;AAAA,EAEF;AAEA,QAAM,gBAAgB,MAAM;AAC1B,QAAI,kBAAmB;AACvB,QAAI,CAAC,WAAW,QAAS;AAEzB,eAAW,UAAU;AACrB,eAAW,KAAK;AAEhB,QAAI,CAAC,UAAW;AAGhB,gBAAY;AAGZ,QAAI,QAAQ,QAAQ,WAAW,MAAM;AACnC,WAAK,MAAM,KAAK;AAAA,IAClB;AAAA,EAEF;AAKA,QAAM,mBAAmB,MAAM;AAC7B,wBAAoB;AACpB,gBAAY,UAAU;AACtB,wBAAoB,UAAU;AAC9B,gBAAY;AAAA,EACd;AAEA,QAAM,iBAAiB,MAAM;AAC3B,wBAAoB;AACpB,gBAAY,UAAU;AAEtB,QAAI,oBAAoB,SAAS;AAC/B,0BAAoB,UAAU;AAC9B,oBAAc;AAAA,IAChB;AAEA,eAAW,MAAM;AACf,0BAAoB;AAAA,IACtB,GAAG,GAAG;AAAA,EACR;AAEA,QAAM,oBAAoB,MAAM;AAC9B,wBAAoB;AACpB,gBAAY,UAAU;AACtB,wBAAoB,UAAU;AAC9B,kBAAc;AAEd,eAAW,MAAM;AACf,0BAAoB;AAAA,IACtB,GAAG,GAAG;AAAA,EACR;AAGA,QAAM,kBAAkB,MAAM;AAC5B,QAAI,qBAAqB,YAAY,QAAS;AAC9C,gBAAY;AAAA,EACd;AAEA,QAAM,gBAAgB,MAAM;AAC1B,QAAI,qBAAqB,YAAY,QAAS;AAC9C,kBAAc;AAAA,EAChB;AAEA,QAAM,mBAAmB,MAAM;AAC7B,QAAI,qBAAqB,YAAY,QAAS;AAC9C,QAAI,kBAAmB;AACvB,gBAAY,IAAI;AAAA,EAClB;AAEA,QAAM,mBAAmB,MAAM;AAC7B,QAAI,qBAAqB,YAAY,QAAS;AAC9C,gBAAY,KAAK;AACjB,kBAAc;AAAA,EAChB;AAGA,MAAI,CAAC,UAAW,QAAO;AAGvB,QAAM,UAAU;AAAA,IACd;AAAA,IACA;AAAA,IACA,sBAAsB;AAAA,IACtB,sBAAsB;AAAA,IACtB,CAAC,aAAa;AAAA,IACd,sBAAsB;AAAA,IACtB,sBAAsB;AAAA,IACtB,CAAC,aAAa;AAAA,IACd,qBAAqB;AAAA,EACvB,EACG,OAAO,OAAO,EACd,KAAK,GAAG;AAGX,QAAM,gBACJ,iBAAiB,QACb,aACA,iBAAiB,WACf,qBACA,iBAAiB,SACf,aACA;AAGV,QAAM,UAAU,QAAQ,QAAQ,YAAY,QAAQ,aAAa,QAAQ,kBAAkB,QAAQ,mBAAmB;AAGtH,QAAM,kBAAkB,YAAY,kBAAkB,OAAO,iBAAiB;AAC9E,QAAM,mBAAmB,YAAY,mBAAmB,OAAO,kBAAkB;AACjF,QAAM,mBAAmB,YAAY,mBAAmB,OAAO,EAAE,GAAG,WAAW,GAAG,gBAAgB,IAAI;AAGtG,QAAM,cAAsC;AAAA,IAC1C,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,EACN;AAGA,QAAM,mBAAmB,MAA0B;AACjD,QAAI,YAAY,KAAM,QAAO;AAC7B,QAAI,OAAO,aAAa,SAAU,QAAO,GAAG,QAAQ;AACpD,QAAI,YAAY,YAAa,QAAO,YAAY,QAAQ;AACxD,WAAO;AAAA,EACT;AAEA,QAAM,gBAAgB,iBAAiB;AAGvC,QAAM,cAAc;AAAA,IAClB;AAAA,IACA;AAAA,IACA,sBAAsB;AAAA,IACtB,sBAAsB;AAAA,EACxB,EACG,OAAO,OAAO,EACd,KAAK,GAAG;AAGX,QAAM,oBAAyC;AAAA,IAC7C,GAAG;AAAA,EACL;AAGA,MAAI,eAAe;AACjB,sBAAkB,QAAQ;AAC1B,sBAAkB,SAAS;AAAA,EAC7B;AAGA,MAAI,mBAAmB,CAAC,kBAAkB,QAAQ;AAChD,UAAM,SAAS,cAAc,QAAQ;AACrC,QAAI,QAAQ;AACV,wBAAkB,SAAS;AAAA,IAC7B;AAAA,EACF;AAGA,QAAM,oBAAoB,MAAM;AAC9B,QAAI,MAAM;AAER,aAAO,4CAAC,UAAK,WAAW,aAAa,OAAO,mBAAoB,gBAAK;AAAA,IACvE;AAEA,QAAI,iBAAiB;AAEnB,aACE;AAAA,QAAC;AAAA;AAAA,UACC,WAAW;AAAA,UACX,OAAO;AAAA,UACP,KAAK,WAAW,eAAe;AAAA,UAC/B,KAAI;AAAA;AAAA,MACN;AAAA,IAEJ;AAEA,QAAI,kBAAkB;AAEpB,aAAO,4CAAC,SAAI,WAAW,aAAa,OAAO,mBAAmB,KAAI,IAAG;AAAA,IACvE;AAEA,WAAO;AAAA,EACT;AAGA,QAAM,aAAa,iBAAiB,SAAS,iBAAiB;AAC9D,QAAM,UAAU,gBAAgB;AAChC,QAAM,uBAAuB,UAAW,aAAa,QAAQ,QAAS;AACtE,QAAM,gBAAgB,qBAAqB;AAG3C,QAAM,oBAAoB,eAAe,aAAa;AAGtD,QAAM,qBAA0C,aAC5C,EAAE,QAAQ,eAAe,OAAO,QAAQ,SAAS,QAAQ,YAAY,UAAU,gBAAgB,SAAS,IACxG,EAAE,OAAO,eAAe,QAAQ,QAAQ,SAAS,QAAQ,YAAY,UAAU,gBAAgB,SAAS;AAG5G,QAAM,qBAA0C,aAC5C,EAAE,QAAQ,mBAAmB,OAAO,QAAQ,SAAS,QAAQ,YAAY,UAAU,gBAAgB,SAAS,IAC5G,EAAE,OAAO,mBAAmB,QAAQ,QAAQ,SAAS,QAAQ,YAAY,UAAU,gBAAgB,SAAS;AAGhH,QAAM,mBAAmB,UAAU,SAAS,MAAM;AAElD,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAW,GAAG,OAAO,qCAAqC,aAAa;AAAA,MACvE,OAAO;AAAA,QACL,QAAQ,oBAAoB,YAAY;AAAA,QACxC,UAAU,mBAAmB,SAAY;AAAA,QACzC,UAAU,mBAAmB,SAAY;AAAA,MAC3C;AAAA,MACA,aAAa;AAAA,MACb,WAAW;AAAA,MACX,cAAc;AAAA,MACd,cAAc;AAAA,MACd,cAAc;AAAA,MACd,YAAY;AAAA,MACZ,eAAe;AAAA,MAGf;AAAA;AAAA,UAAC;AAAA;AAAA,YACC,WAAU;AAAA,YACV,OAAO;AAAA,cACL,UAAU;AAAA,cACV,OAAO;AAAA,cACP,iBAAiB;AAAA,cACjB,SAAS,WAAW,MAAM;AAAA,cAC1B,YAAY;AAAA,cACZ,eAAe;AAAA,cACf,cAAc;AAAA,YAChB;AAAA;AAAA,QACF;AAAA,QACC;AAAA,QACA,WACC,4CAAC,SAAI,WAAU,8BAA6B,OAAO,oBAChD,4BAAkB,GACrB;AAAA,QAED,eACC,4CAAC,SAAI,WAAU,8BAA6B,OAAO,oBACjD,sDAAC,UAAK,WAAU,oBAAoB,uBAAY,GAClD;AAAA;AAAA;AAAA,EAEJ;AAEJ;;;AE1ZA,IAAAA,0BAAoD;AAoEhD,IAAAC,sBAAA;AA3CG,SAAS,SAAS;AAAA,EACvB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,YAAY;AAAA,EACZ;AAAA,EACA,gBAAgB;AAAA,EAChB;AAAA,EACA,gBAAgB;AAAA,EAChB;AACF,GAAkB;AAEhB,QAAM,mBAAe,mCAAU,oBAAoB,CAAC;AACpD,QAAM,cAAU,oCAAW,cAAc,CAAC;AAC1C,QAAM,cAAU,oCAAW,eAAe,CAAC;AAG3C,QAAM,YAAY,cAAc,OAAO,OAAO;AAG9C,QAAM,YAAY,eAAe,OAAO,OAAO;AAG/C,QAAM,UAAU,oBAAoB,OAAO,eAAgB,QAAQ;AAGnE,QAAM,kBAAc,sCAAa,OAAO;AAGxC,MAAI,CAAC,UAAW,QAAO;AAGvB,QAAM,mBAAmB;AAAA,IACvB;AAAA,IACA;AAAA,IACA,CAAC,aAAa;AAAA,IACd,CAAC,aAAa;AAAA,EAChB,EACG,OAAO,OAAO,EACd,KAAK,GAAG;AAEX,SACE,8CAAC,SAAI,WAAW,kBAAkB,OAC/B;AAAA;AAAA,IACA,eACC,6CAAC,UAAK,WAAW,kBAAkB,aAAa,IAAI,OAAO,WACxD,uBACH;AAAA,KAEJ;AAEJ;;;AC9EA,IAAAC,gBAAyD;AACzD,IAAAC,0BAAqD;AAgVjD,IAAAC,sBAAA;AA/RG,SAAS,WAAW;AAAA,EACzB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,WAAW;AAAA,EACX,WAAW;AAAA,EACX,aAAa;AAAA,EACb,aAAa;AAAA,EACb,mBAAmB;AAAA,EACnB,mBAAmB;AAAA,EACnB,iCAAiC;AAAA,EACjC,YAAY;AAAA,EACZ;AAAA,EACA,eAAe;AAAA,EACf;AAAA,EACA,gBAAgB;AAAA,EAChB;AAAA,EACA,iBAAiB;AAAA,EACjB;AAAA,EACA,gBAAgB;AAClB,GAAoB;AAElB,QAAM,sBAAsB,cAAc;AAC1C,QAAM,uBAAuB,eAAe;AAG5C,QAAM,kBAAc,mCAAU,uBAAuB,CAAC;AACtD,QAAM,cAAU,oCAAW,cAAc,CAAC;AAC1C,QAAM,cAAU,oCAAW,eAAe,CAAC;AAG3C,QAAM,WAAO,uCAAc,CAAC,UAAU,MAAM,IAAI;AAChD,QAAM,WAAO,uCAAc,CAAC,UAAU,MAAM,IAAI;AAGhD,QAAM,CAAC,cAAc,eAAe,QAAI,wBAAS,CAAC;AAClD,QAAM,CAAC,YAAY,aAAa,QAAI,wBAAS,KAAK;AAClD,QAAM,+BAA2B,sBAAO,KAAK;AAC7C,QAAM,yBAAqB,sBAAO,CAAC;AACnC,QAAM,0BAAsB,sBAAsB,IAAI;AACtD,QAAM,kBAAc,sBAAO,KAAK;AAGhC,QAAM,YAAY,cAAc,OAAO,OAAO;AAC9C,QAAM,YAAY,eAAe,OAAO,OAAO;AAG/C,QAAM,oBAAgB;AAAA,IACpB,CAAC,UAAkB;AACjB,YAAM,QAAQ,WAAW;AACzB,UAAI,SAAS,EAAG,QAAO;AACvB,aAAO,KAAK,IAAI,GAAG,KAAK,IAAI,IAAI,QAAQ,YAAY,KAAK,CAAC;AAAA,IAC5D;AAAA,IACA,CAAC,UAAU,QAAQ;AAAA,EACrB;AAGA,QAAM,oBAAgB;AAAA,IACpB,CAAC,UAAkB;AACjB,YAAM,QAAQ,WAAW;AACzB,aAAO,KAAK,MAAM,WAAW,QAAQ,KAAK;AAAA,IAC5C;AAAA,IACA,CAAC,UAAU,QAAQ;AAAA,EACrB;AAGA,QAAM,yBAAqB,2BAAY,MAAM;AAC3C,QAAI,CAAC,yBAAyB,WAAW,uBAAuB,MAAM;AACpE,sBAAgB,cAAc,WAAW,CAAC;AAAA,IAC5C;AAAA,EACF,GAAG,CAAC,aAAa,eAAe,mBAAmB,CAAC;AAGpD,+BAAU,MAAM;AACd,uBAAmB;AAAA,EACrB,GAAG,CAAC,kBAAkB,CAAC;AAGvB,QAAM,sBAAkB,2BAAY,MAAM;AACxC,QAAI,CAAC,UAAW;AAEhB,kBAAc,IAAI;AAGlB,QAAI,wBAAwB,MAAM;AAChC,WAAK,sBAAsB,IAAI;AAAA,IACjC;AAGA,QAAI,CAAC,yBAAyB,SAAS;AACrC,yBAAmB,UAAU;AAAA,IAC/B;AACA,6BAAyB,UAAU;AAGnC,QAAI,oBAAoB,YAAY,MAAM;AACxC,aAAO,aAAa,oBAAoB,OAAO;AAC/C,0BAAoB,UAAU;AAAA,IAChC;AAAA,EACF,GAAG,CAAC,WAAW,sBAAsB,MAAM,YAAY,CAAC;AAGxD,QAAM,oBAAgB,2BAAY,MAAM;AACtC,QAAI,CAAC,WAAY;AAEjB,kBAAc,KAAK;AAGnB,QAAI,wBAAwB,MAAM;AAChC,WAAK,sBAAsB,KAAK;AAAA,IAClC;AAGA,QAAI,iCAAiC,GAAG;AACtC,0BAAoB,UAAU,OAAO,WAAW,MAAM;AACpD,iCAAyB,UAAU;AACnC,2BAAmB;AACnB,4BAAoB,UAAU;AAAA,MAChC,GAAG,8BAA8B;AAAA,IACnC,OAAO;AACL,+BAAyB,UAAU;AACnC,yBAAmB;AAAA,IACrB;AAAA,EACF,GAAG,CAAC,YAAY,sBAAsB,MAAM,gCAAgC,kBAAkB,CAAC;AAG/F,QAAM,iBAAa;AAAA,IACjB,CAAC,SAAiB,SAAiB,WAAoB;AACrD,UAAI,CAAC,WAAY;AAEjB,UAAI;AACJ,UAAI,YAAY;AACd,oBAAY,UAAU,OAAO,QAAQ,OAAO;AAAA,MAC9C,OAAO;AACL,mBAAW,KAAK,UAAU,OAAO,OAAO,OAAO;AAAA,MACjD;AACA,iBAAW,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,QAAQ,CAAC;AAE5C,sBAAgB,QAAQ;AAGxB,UAAI,uBAAuB,MAAM;AAC/B,aAAK,qBAAqB,cAAc,QAAQ,CAAC;AAAA,MACnD;AAAA,IACF;AAAA,IACA,CAAC,YAAY,YAAY,qBAAqB,MAAM,aAAa;AAAA,EACnE;AAGA,QAAM,kBAAkB,CAAC,UAA4C;AACnE,QAAI,YAAY,QAAS;AACzB,QAAI,MAAM,WAAW,EAAG;AAExB,oBAAgB;AAChB,UAAM,SAAS,MAAM,cAAc,sBAAsB;AACzD,eAAW,MAAM,SAAS,MAAM,SAAS,MAAM;AAAA,EACjD;AAEA,QAAM,kBAAkB,CAAC,UAA4C;AACnE,QAAI,CAAC,WAAY;AACjB,UAAM,SAAS,MAAM,cAAc,sBAAsB;AACzD,eAAW,MAAM,SAAS,MAAM,SAAS,MAAM;AAAA,EACjD;AAEA,QAAM,gBAAgB,MAAM;AAC1B,kBAAc;AAAA,EAChB;AAEA,QAAM,mBAAmB,CAAC,UAA4C;AACpE,QAAI,YAAY;AACd,YAAM,SAAS,MAAM,cAAc,sBAAsB;AACzD,iBAAW,MAAM,SAAS,MAAM,SAAS,MAAM;AAAA,IACjD;AACA,kBAAc;AAAA,EAChB;AAGA,QAAM,mBAAmB,CAAC,UAA4C;AACpE,gBAAY,UAAU;AACtB,oBAAgB;AAChB,UAAM,SAAS,MAAM,cAAc,sBAAsB;AACzD,UAAM,QAAQ,MAAM,QAAQ,CAAC;AAC7B,eAAW,MAAM,SAAS,MAAM,SAAS,MAAM;AAAA,EACjD;AAEA,QAAM,kBAAkB,CAAC,UAA4C;AACnE,QAAI,CAAC,WAAY;AACjB,UAAM,SAAS,MAAM,cAAc,sBAAsB;AACzD,UAAM,QAAQ,MAAM,QAAQ,CAAC;AAC7B,eAAW,MAAM,SAAS,MAAM,SAAS,MAAM;AAAA,EACjD;AAEA,QAAM,iBAAiB,MAAM;AAC3B,kBAAc;AAAA,EAChB;AAEA,QAAM,oBAAoB,CAAC,UAA4C;AACrE,QAAI,cAAc,MAAM,QAAQ,SAAS,GAAG;AAC1C,YAAM,SAAS,MAAM,cAAc,sBAAsB;AACzD,YAAM,QAAQ,MAAM,QAAQ,CAAC;AAC7B,iBAAW,MAAM,SAAS,MAAM,SAAS,MAAM;AAAA,IACjD;AACA,kBAAc;AAAA,EAChB;AAGA,MAAI,CAAC,UAAW,QAAO;AAGvB,QAAM,mBAAmB;AAAA,IACvB;AAAA,IACA;AAAA,IACA,CAAC,aAAa;AAAA,IACd,CAAC,aAAa;AAAA,EAChB,EACG,OAAO,OAAO,EACd,KAAK,GAAG;AAGX,QAAM,mBAAwC;AAAA,IAC5C,GAAG;AAAA,IACH,UAAU;AAAA,EACZ;AAEA,MAAI,YAAY;AACd,UAAM,QAAQ,MAAM;AACpB,UAAM,QAAQ,MAAM,SAAS;AAC7B,qBAAiB,SAAS,GAAG,gBAAgB;AAC7C,qBAAiB,MAAM,IAAI,MAAM,oBAAoB,CAAC;AACtD,qBAAiB,QAAQ,GAAG,KAAK;AACjC,qBAAiB,OAAO,GAAG,IAAI;AAAA,EACjC,OAAO;AACL,UAAM,SAAS,MAAM;AACrB,UAAM,OAAO,MAAM,UAAU;AAC7B,qBAAiB,QAAQ,GAAG,gBAAgB;AAC5C,qBAAiB,OAAO,IAAI,MAAM,oBAAoB,CAAC;AACvD,qBAAiB,SAAS,GAAG,MAAM;AACnC,qBAAiB,MAAM,GAAG,GAAG;AAAA,EAC/B;AAGA,QAAM,oBAAyC;AAAA,IAC7C,GAAG;AAAA,IACH,UAAU;AAAA,EACZ;AAEA,MAAI,YAAY;AACd,UAAM,QAAQ,MAAM;AACpB,UAAM,OAAO,mBAAmB;AAChC,UAAM,QAAQ,QAAQ;AACtB,sBAAkB,SAAS,GAAG,gBAAgB;AAC9C,sBAAkB,MAAM,IAAI,MAAM,oBAAoB,CAAC;AACvD,sBAAkB,QAAQ,GAAG,KAAK;AAClC,sBAAkB,OAAO,GAAG,IAAI;AAAA,EAClC,OAAO;AACL,UAAM,QAAQ,MAAM;AACpB,UAAM,MAAM,OAAO,QAAQ,eAAe,mBAAmB;AAC7D,UAAM,SAAS,QAAQ,mBAAmB,IAAI;AAC9C,sBAAkB,QAAQ,GAAG,gBAAgB;AAC7C,sBAAkB,OAAO,IAAI,MAAM,oBAAoB,CAAC;AACxD,sBAAkB,SAAS,GAAG,MAAM;AACpC,sBAAkB,MAAM,GAAG,GAAG;AAAA,EAChC;AAGA,QAAM,qBAA0C;AAAA,IAC9C,GAAG;AAAA,IACH,UAAU;AAAA,EACZ;AAEA,MAAI,YAAY;AACd,UAAM,UAAU,MAAM;AACtB,UAAM,OAAO,UAAU;AACvB,uBAAmB,QAAQ,GAAG,gBAAgB;AAC9C,uBAAmB,SAAS;AAC5B,uBAAmB,OAAO,GAAG,IAAI;AAAA,EACnC,OAAO;AACL,UAAM,SAAS,MAAM;AACrB,UAAM,MAAM,SAAS,SAAS;AAC9B,uBAAmB,QAAQ;AAC3B,uBAAmB,SAAS,GAAG,gBAAgB;AAC/C,uBAAmB,MAAM,GAAG,GAAG;AAAA,EACjC;AAEA,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAW;AAAA,MACX,OAAO;AAAA,QACL,GAAG;AAAA,QACH,UAAU;AAAA,QACV,QAAQ,YAAY,YAAY;AAAA,QAChC,aAAa;AAAA,QACb,YAAY;AAAA,MACd;AAAA,MACA,aAAa;AAAA,MACb,aAAa;AAAA,MACb,WAAW;AAAA,MACX,cAAc;AAAA,MACd,cAAc;AAAA,MACd,aAAa;AAAA,MACb,YAAY;AAAA,MACZ,eAAe;AAAA,MAGf;AAAA,qDAAC,SAAI,WAAW,mBAAmB,YAAY,IAAI,OAAO,kBAAkB;AAAA,QAG3E,CAAC,cACA,6CAAC,SAAI,WAAW,oBAAoB,aAAa,IAAI,OAAO,mBAAmB;AAAA,QAIjF,6CAAC,SAAI,WAAW,qBAAqB,cAAc,IAAI,OAAO,oBAAoB;AAAA;AAAA;AAAA,EACpF;AAEJ;;;AC/WA,IAAAC,0BAAsC;AAoM9B,IAAAC,sBAAA;AA3ID,SAAS,UAAU;AAAA,EACxB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,WAAW;AAAA,EACX,WAAW;AAAA,EACX,WAAW;AAAA,EACX,WAAW;AAAA,EACX,cAAc;AAAA,EACd,YAAY;AAAA,EACZ;AAAA,EACA,2BAA2B;AAAA,EAC3B;AAAA,EACA,mBAAmB;AAAA,EACnB;AAAA,EACA,sBAAsB;AAAA,EACtB;AAAA,EACA,yBAAyB;AAAA,EACzB;AAAA,EACA,uBAAuB;AAAA,EACvB;AAAA,EACA,gBAAgB;AAClB,GAAmB;AAEjB,QAAM,gBAAY,mCAAU,QAAQ,CAAC;AACrC,QAAM,cAAU,oCAAW,cAAc,CAAC;AAC1C,QAAM,cAAU,oCAAW,eAAe,CAAC;AAG3C,QAAM,YAAY,cAAc,OAAO,OAAO;AAC9C,QAAM,YAAY,eAAe,OAAO,OAAO;AAG/C,QAAM,eAAe,QAAQ,OAAO,YAAa,SAAS;AAG1D,MAAI,CAAC,UAAW,QAAO;AAGvB,QAAM,iBAAiB,CAAC,YAA+C;AACrE,UAAM,SAAS,WAAW;AAC1B,UAAM,YAAY,WAAW;AAE7B,UAAM,MACH,YAAY,gBAAgB,cAAgB,CAAC,YAAY,gBAAgB,eACtE,UAAU,IACV,WAAW;AAEjB,QAAI,OAAO,OAAQ,QAAO;AAC1B,QAAI,OAAO,UAAW,QAAO;AAC7B,WAAO;AAAA,EACT;AAGA,QAAM,kBAAkB,CAAC,YAA6B;AACpD,QAAI,eAAe,SAAU,QAAO;AACpC,QAAI,eAAe,SAAU,QAAO;AAEpC,UAAM,QAAQ,gBAAgB,WAAW;AACzC,QAAI,SAAS,KAAK,MAAM,WAAW,KAAK;AACxC,QAAI,UAAU,KAAK,WAAW,EAAG,UAAS;AAE1C,UAAM,MACH,YAAY,gBAAgB,cAAgB,CAAC,YAAY,gBAAgB,eACtE,UAAU,IACV,WAAW;AAEjB,WAAO,OAAO;AAAA,EAChB;AAGA,QAAM,kBAAkB,CAAC,YAA4B;AACnD,UAAM,OAAO,eAAe,OAAO;AACnC,UAAM,SAAS,gBAAgB,OAAO;AAEtC,UAAM,UAAU,CAAC,sBAAsB,MAAM,WAAW;AAExD,QAAI,QAAQ;AACV,cAAQ,KAAK,QAAQ;AAErB,UAAI,SAAS,UAAU,uBAAuB,mBAAmB;AAC/D,gBAAQ,KAAK,uBAAuB,gBAAgB;AAAA,MACtD,WAAW,SAAS,aAAa,0BAA0B,mBAAmB;AAC5E,gBAAQ,KAAK,0BAA0B,gBAAgB;AAAA,MACzD,WAAW,SAAS,WAAW,wBAAwB,mBAAmB;AACxE,gBAAQ,KAAK,wBAAwB,gBAAgB;AAAA,MACvD;AAAA,IACF,OAAO;AACL,UAAI,0BAA0B;AAC5B,gBAAQ,KAAK,wBAAwB;AAAA,MACvC;AAAA,IACF;AAEA,WAAO,QAAQ,OAAO,OAAO,EAAE,KAAK,GAAG;AAAA,EACzC;AAGA,QAAM,kBAAkB,CAAC,YAAqD;AAC5E,UAAM,OAAO,eAAe,OAAO;AACnC,UAAM,SAAS,gBAAgB,OAAO;AAEtC,QAAI,QAAQ;AACV,UAAI,SAAS,UAAU,mBAAmB,eAAe;AACvD,eAAO,mBAAmB;AAAA,MAC5B,WAAW,SAAS,aAAa,sBAAsB,eAAe;AACpE,eAAO,sBAAsB;AAAA,MAC/B,WAAW,SAAS,WAAW,oBAAoB,eAAe;AAChE,eAAO,oBAAoB;AAAA,MAC7B;AAAA,IACF,OAAO;AACL,aAAO;AAAA,IACT;AAEA,WAAO;AAAA,EACT;AAGA,QAAM,mBAAmB;AAAA,IACvB;AAAA,IACA;AAAA,IACA,CAAC,aAAa;AAAA,IACd,CAAC,aAAa;AAAA,EAChB,EACG,OAAO,OAAO,EACd,KAAK,GAAG;AAGX,QAAM,iBAAsC;AAAA,IAC1C,GAAG;AAAA,IACH,SAAS;AAAA,IACT,eAAe,gBAAgB,eAAe,QAAQ;AAAA,IACtD,gBAAgB;AAAA,IAChB,YAAY;AAAA,EACd;AAEA,SACE,6CAAC,SAAI,WAAW,kBAAkB,OAAO,gBACtC,gBAAM,KAAK,EAAE,QAAQ,SAAS,GAAG,CAAC,GAAG,YACpC;AAAA,IAAC;AAAA;AAAA,MAEC,WAAW,gBAAgB,OAAO;AAAA,MAClC,OAAO,gBAAgB,OAAO;AAAA;AAAA,IAFzB;AAAA,EAGP,CACD,GACH;AAEJ;;;AC5MA,IAAAC,0BAAsC;AAsFlC,IAAAC,sBAAA;AAvDJ,IAAM,kBAAgD;AAAA,EACpD,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,MAAM;AACR;AAEO,SAAS,YAAY;AAAA,EAC1B;AAAA,EACA;AAAA,EACA;AAAA,EACA,UAAU;AAAA,EACV,eAAe;AAAA,EACf,QAAQ;AAAA,EACR,aAAa;AAAA,EACb,YAAY;AAAA,EACZ,YAAY;AAAA,EACZ;AACF,GAAqB;AAEnB,QAAM,wBAAoB,mCAAU,gBAAgB,CAAC;AACrD,QAAM,cAAU,oCAAW,eAAe,CAAC;AAG3C,QAAM,YAAY,eAAe,OAAO,OAAO;AAG/C,MAAI,CAAC,UAAW,QAAO;AAGvB,QAAM,kBAAkB,gBAAgB,OAAO,oBAAqB,YAAY;AAChF,QAAM,kBAAmB,kBAAkB,QAAS;AAGpD,QAAM,mBAAmB,CAAC,gBAAgB,SAAS,EAAE,OAAO,OAAO,EAAE,KAAK,GAAG;AAG7E,QAAM,eAAoC;AAAA,IACxC,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,cAAc;AAAA,IACd,QAAQ,GAAG,SAAS,YAAY,UAAU;AAAA,IAC1C,gBAAgB;AAAA,IAChB,WAAW;AAAA,EACb;AAEA,MAAI,SAAS;AAEX,iBAAa,YAAY,uBAAuB,gBAAgB,YAAY,CAAC;AAAA,EAC/E,OAAO;AAEL,iBAAa,YAAY,UAAU,eAAe;AAClD,iBAAa,aAAa;AAAA,EAC5B;AAEA,SACE,8CAAC,SAAI,WAAW,kBAAkB,OAChC;AAAA,iDAAC,SAAI,OAAO,cAAc;AAAA,IAC1B,6CAAC,WAAO;AAAA;AAAA;AAAA;AAAA,SAIN;AAAA,KACJ;AAEJ;","names":["import_cris_webui_core","import_jsx_runtime","import_react","import_cris_webui_core","import_jsx_runtime","import_cris_webui_core","import_jsx_runtime","import_cris_webui_core","import_jsx_runtime"]}
package/dist/index.mjs CHANGED
@@ -30,6 +30,7 @@ function getIconFilter(active) {
30
30
 
31
31
  // src/components/CrisButton.tsx
32
32
  import { jsx, jsxs } from "react/jsx-runtime";
33
+ var globalTouchActive = false;
33
34
  function CrisButton({
34
35
  join,
35
36
  joinFeedback,
@@ -104,37 +105,46 @@ function CrisButton({
104
105
  }
105
106
  };
106
107
  const handleTouchStart = () => {
108
+ globalTouchActive = true;
107
109
  touchingRef.current = true;
108
110
  touchStartedHereRef.current = true;
109
111
  handlePress();
110
112
  };
111
113
  const handleTouchEnd = () => {
114
+ globalTouchActive = true;
112
115
  touchingRef.current = true;
113
116
  if (touchStartedHereRef.current) {
114
117
  touchStartedHereRef.current = false;
115
118
  handleRelease();
116
119
  }
120
+ setTimeout(() => {
121
+ globalTouchActive = false;
122
+ }, 300);
117
123
  };
118
124
  const handleTouchCancel = () => {
125
+ globalTouchActive = true;
119
126
  touchingRef.current = true;
120
127
  touchStartedHereRef.current = false;
121
128
  handleRelease();
129
+ setTimeout(() => {
130
+ globalTouchActive = false;
131
+ }, 300);
122
132
  };
123
133
  const handleMouseDown = () => {
124
- if (touchingRef.current) return;
134
+ if (globalTouchActive || touchingRef.current) return;
125
135
  handlePress();
126
136
  };
127
137
  const handleMouseUp = () => {
128
- if (touchingRef.current) return;
138
+ if (globalTouchActive || touchingRef.current) return;
129
139
  handleRelease();
130
140
  };
131
141
  const handleMouseEnter = () => {
132
- if (touchingRef.current) return;
142
+ if (globalTouchActive || touchingRef.current) return;
133
143
  if (suppressKeyClicks) return;
134
144
  setHovering(true);
135
145
  };
136
146
  const handleMouseLeave = () => {
137
- if (touchingRef.current) return;
147
+ if (globalTouchActive || touchingRef.current) return;
138
148
  setHovering(false);
139
149
  handleRelease();
140
150
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/components/CrisButton.tsx","../src/utils/icons.ts","../src/components/CrisText.tsx","../src/components/CrisSlider.tsx","../src/components/CrisGauge.tsx","../src/components/CrisSpinner.tsx"],"sourcesContent":["import { useState, useRef, ReactNode } from 'react';\r\nimport { useDigital, useJoinsStore } from '@imperosoft/cris-webui-core';\r\nimport { getIconUrl, getIconFilter } from '../utils/icons';\r\n\r\nexport interface CrisButtonProps {\r\n /** Digital join for press action */\r\n join?: number;\r\n /** Digital join for feedback (defaults to join) */\r\n joinFeedback?: number;\r\n /** Digital join for enable state */\r\n joinEnable?: number;\r\n /** Digital join for visibility */\r\n joinVisible?: number;\r\n\r\n /** Button text */\r\n text?: string;\r\n /** Text when pressed (local feedback) */\r\n textPressed?: string;\r\n /** Text when selected (controller feedback) */\r\n textSelected?: string;\r\n\r\n /** Icon as ReactNode (for custom SVG components) */\r\n icon?: ReactNode;\r\n /** Icon name (loads SVG from configured path, e.g., 'motor-stop') */\r\n iconName?: string;\r\n /** Icon CSS class (for CSS-based icons, e.g., 'ico-motor-stop') */\r\n iconClass?: string;\r\n /** Icon size - number (px), string ('50%', '2rem'), or preset ('xs'|'sm'|'md'|'lg'|'xl') */\r\n iconSize?: number | string | 'xs' | 'sm' | 'md' | 'lg' | 'xl';\r\n /** Icon container size as percentage (default: 80% for top/bottom, 20% for left/right) */\r\n iconContainerSize?: string;\r\n /** Icon inline style */\r\n iconStyle?: React.CSSProperties;\r\n /** Icon position relative to text */\r\n iconPosition?: 'left' | 'right' | 'top' | 'bottom';\r\n\r\n /** Icon name when active (overrides iconName when button is active) */\r\n iconNameActive?: string;\r\n /** Icon CSS class when active (overrides iconClass when button is active) */\r\n iconClassActive?: string;\r\n /** Icon inline style when active (merged with iconStyle when button is active) */\r\n iconStyleActive?: React.CSSProperties;\r\n\r\n /** Show controller feedback styling */\r\n showControlFeedback?: boolean;\r\n /** Show local press feedback styling */\r\n showLocalFeedback?: boolean;\r\n /** Suppress click actions (display only) */\r\n suppressKeyClicks?: boolean;\r\n\r\n /** Smart object ID (for smarts instead of joins) */\r\n smartId?: number;\r\n\r\n /** Custom class names */\r\n className?: string;\r\n /** Class when active (controller feedback) */\r\n classActive?: string;\r\n /** Class when pressed (local feedback) */\r\n classPressed?: string;\r\n /** Class when disabled */\r\n classDisabled?: string;\r\n\r\n /** Children content */\r\n children?: ReactNode;\r\n\r\n /** Custom click handler (called on press) */\r\n onPress?: () => void;\r\n /** Custom release handler */\r\n onRelease?: () => void;\r\n}\r\n\r\nexport function CrisButton({\r\n join,\r\n joinFeedback,\r\n joinEnable,\r\n joinVisible,\r\n text,\r\n textPressed,\r\n textSelected,\r\n icon,\r\n iconName,\r\n iconClass,\r\n iconSize,\r\n iconContainerSize,\r\n iconStyle,\r\n iconPosition = 'top',\r\n iconNameActive,\r\n iconClassActive,\r\n iconStyleActive,\r\n showControlFeedback = true,\r\n showLocalFeedback = true,\r\n suppressKeyClicks = false,\r\n smartId,\r\n className = '',\r\n classActive = '',\r\n classPressed = '',\r\n classDisabled = '',\r\n children,\r\n onPress,\r\n onRelease,\r\n}: CrisButtonProps) {\r\n const [pressed, setPressed] = useState(false);\r\n const [hovering, setHovering] = useState(false);\r\n const pressedRef = useRef(false);\r\n const touchingRef = useRef(false);\r\n const touchStartedHereRef = useRef(false);\r\n\r\n // Get join values reactively\r\n const feedbackJoin = joinFeedback ?? join;\r\n const feedback = useDigital(feedbackJoin ?? 0);\r\n const enabled = useDigital(joinEnable ?? 0);\r\n const visible = useDigital(joinVisible ?? 0);\r\n\r\n // Get action method\r\n const dSet = useJoinsStore((state) => state.dSet);\r\n\r\n // Determine if button is enabled\r\n const isEnabled = joinEnable == null ? true : enabled;\r\n\r\n // Determine if button is visible\r\n const isVisible = joinVisible == null ? true : visible;\r\n\r\n // Determine current feedback state\r\n const hasControlFeedback = showControlFeedback && feedbackJoin != null && feedback;\r\n const hasPressedFeedback = showLocalFeedback && pressed && isEnabled;\r\n const isActive = hasControlFeedback || hasPressedFeedback;\r\n\r\n // Determine current text\r\n let currentText = text ?? '';\r\n if (hasPressedFeedback && textPressed != null) {\r\n currentText = textPressed;\r\n } else if (hasControlFeedback && textSelected != null) {\r\n currentText = textSelected;\r\n }\r\n\r\n // Event handlers\r\n const handlePress = () => {\r\n if (suppressKeyClicks) return;\r\n if (pressedRef.current) return;\r\n\r\n pressedRef.current = true;\r\n setPressed(true);\r\n\r\n if (!isEnabled) return;\r\n\r\n // Custom handler\r\n onPress?.();\r\n\r\n // Send to controller\r\n if (join != null && smartId == null) {\r\n dSet(join, true);\r\n }\r\n // TODO: Add smartId support when smarts are implemented in core\r\n };\r\n\r\n const handleRelease = () => {\r\n if (suppressKeyClicks) return;\r\n if (!pressedRef.current) return;\r\n\r\n pressedRef.current = false;\r\n setPressed(false);\r\n\r\n if (!isEnabled) return;\r\n\r\n // Custom handler\r\n onRelease?.();\r\n\r\n // Send to controller\r\n if (join != null && smartId == null) {\r\n dSet(join, false);\r\n }\r\n // TODO: Add smartId support when smarts are implemented in core\r\n };\r\n\r\n // Touch event handlers - set touching flag to prevent mouse events from firing\r\n // Also track if touch started on THIS button to prevent phantom clicks\r\n // when buttons swap visibility (e.g., back button hides, power button appears)\r\n const handleTouchStart = () => {\r\n touchingRef.current = true;\r\n touchStartedHereRef.current = true;\r\n handlePress();\r\n };\r\n\r\n const handleTouchEnd = () => {\r\n touchingRef.current = true;\r\n // Only fire release if touch actually started on this button\r\n if (touchStartedHereRef.current) {\r\n touchStartedHereRef.current = false;\r\n handleRelease();\r\n }\r\n };\r\n\r\n const handleTouchCancel = () => {\r\n touchingRef.current = true;\r\n touchStartedHereRef.current = false;\r\n handleRelease();\r\n };\r\n\r\n // Mouse event handlers - skip if touch event already handled\r\n const handleMouseDown = () => {\r\n if (touchingRef.current) return;\r\n handlePress();\r\n };\r\n\r\n const handleMouseUp = () => {\r\n if (touchingRef.current) return;\r\n handleRelease();\r\n };\r\n\r\n const handleMouseEnter = () => {\r\n if (touchingRef.current) return;\r\n if (suppressKeyClicks) return;\r\n setHovering(true);\r\n };\r\n\r\n const handleMouseLeave = () => {\r\n if (touchingRef.current) return;\r\n setHovering(false);\r\n handleRelease();\r\n };\r\n\r\n // Don't render if not visible\r\n if (!isVisible) return null;\r\n\r\n // Build class names\r\n const classes = [\r\n 'cris-button',\r\n className,\r\n hasControlFeedback && classActive,\r\n hasPressedFeedback && classPressed,\r\n !isEnabled && classDisabled,\r\n hasControlFeedback && 'active',\r\n hasPressedFeedback && 'pressed',\r\n !isEnabled && 'disabled',\r\n suppressKeyClicks && 'no-hover',\r\n ]\r\n .filter(Boolean)\r\n .join(' ');\r\n\r\n // Flex direction based on icon position\r\n const flexDirection =\r\n iconPosition === 'top'\r\n ? 'flex-col'\r\n : iconPosition === 'bottom'\r\n ? 'flex-col-reverse'\r\n : iconPosition === 'left'\r\n ? 'flex-row'\r\n : 'flex-row-reverse';\r\n\r\n // Determine if we have any icon to show\r\n const hasIcon = icon != null || iconName != null || iconClass != null || iconNameActive != null || iconClassActive != null;\r\n\r\n // Determine current icon properties based on active state\r\n const currentIconName = isActive && iconNameActive != null ? iconNameActive : iconName;\r\n const currentIconClass = isActive && iconClassActive != null ? iconClassActive : iconClass;\r\n const currentIconStyle = isActive && iconStyleActive != null ? { ...iconStyle, ...iconStyleActive } : iconStyle;\r\n\r\n // Size presets mapping\r\n const sizePresets: Record<string, string> = {\r\n xs: '16px',\r\n sm: '24px',\r\n md: '32px',\r\n lg: '48px',\r\n xl: '64px',\r\n };\r\n\r\n // Calculate icon size value\r\n const getIconSizeValue = (): string | undefined => {\r\n if (iconSize == null) return undefined;\r\n if (typeof iconSize === 'number') return `${iconSize}px`;\r\n if (iconSize in sizePresets) return sizePresets[iconSize];\r\n return iconSize; // Already a string like '50%' or '2rem'\r\n };\r\n\r\n const iconSizeValue = getIconSizeValue();\r\n\r\n // Build icon classes\r\n const iconClasses = [\r\n 'cris-button-icon',\r\n currentIconClass,\r\n hasControlFeedback && 'active',\r\n hasPressedFeedback && 'pressed',\r\n ]\r\n .filter(Boolean)\r\n .join(' ');\r\n\r\n // Build icon style with size and filter support\r\n const computedIconStyle: React.CSSProperties = {\r\n ...currentIconStyle,\r\n };\r\n\r\n // Apply size\r\n if (iconSizeValue) {\r\n computedIconStyle.width = iconSizeValue;\r\n computedIconStyle.height = iconSizeValue;\r\n }\r\n\r\n // Apply filter from config if using iconName (only if not overridden by iconStyle/iconStyleActive)\r\n if (currentIconName && !currentIconStyle?.filter) {\r\n const filter = getIconFilter(isActive);\r\n if (filter) {\r\n computedIconStyle.filter = filter;\r\n }\r\n }\r\n\r\n // Render icon element\r\n const renderIconElement = () => {\r\n if (icon) {\r\n // ReactNode icon (custom component)\r\n return <span className={iconClasses} style={computedIconStyle}>{icon}</span>;\r\n }\r\n\r\n if (currentIconName) {\r\n // Icon by name (loads from configured path)\r\n return (\r\n <img\r\n className={iconClasses}\r\n style={computedIconStyle}\r\n src={getIconUrl(currentIconName)}\r\n alt=\"\"\r\n />\r\n );\r\n }\r\n\r\n if (currentIconClass) {\r\n // CSS-based icon (like Angular version)\r\n return <img className={iconClasses} style={computedIconStyle} alt=\"\" />;\r\n }\r\n\r\n return null;\r\n };\r\n\r\n // Calculate icon container size (default: 100% if no text, otherwise 80% for top/bottom, 20% for left/right)\r\n const isVertical = iconPosition === 'top' || iconPosition === 'bottom';\r\n const hasText = currentText !== '';\r\n const defaultContainerSize = hasText ? (isVertical ? '80%' : '20%') : '100%';\r\n const containerSize = iconContainerSize ?? defaultContainerSize;\r\n\r\n // Calculate text container size (remaining space)\r\n const textContainerSize = `calc(100% - ${containerSize})`;\r\n\r\n // Container style for icon area\r\n const iconContainerStyle: React.CSSProperties = isVertical\r\n ? { height: containerSize, width: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center' }\r\n : { width: containerSize, height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center' };\r\n\r\n // Container style for text area\r\n const textContainerStyle: React.CSSProperties = isVertical\r\n ? { height: textContainerSize, width: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center' }\r\n : { width: textContainerSize, height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center' };\r\n\r\n // Check if button has 'free' class (absolute positioning)\r\n const isFreePositioned = className.includes('free');\r\n\r\n return (\r\n <div\r\n className={`${classes} flex items-center justify-center ${flexDirection}`}\r\n style={{\r\n cursor: suppressKeyClicks ? 'default' : 'pointer',\r\n position: isFreePositioned ? undefined : 'relative',\r\n overflow: isFreePositioned ? undefined : 'hidden',\r\n }}\r\n onMouseDown={handleMouseDown}\r\n onMouseUp={handleMouseUp}\r\n onMouseEnter={handleMouseEnter}\r\n onMouseLeave={handleMouseLeave}\r\n onTouchStart={handleTouchStart}\r\n onTouchEnd={handleTouchEnd}\r\n onTouchCancel={handleTouchCancel}\r\n >\r\n {/* Hover overlay - inline styles for touch-safe behavior */}\r\n <div\r\n className=\"cris-button-hover-overlay\"\r\n style={{\r\n position: 'absolute',\r\n inset: 0,\r\n backgroundColor: 'black',\r\n opacity: hovering ? 0.1 : 0,\r\n transition: 'opacity 0.2s',\r\n pointerEvents: 'none',\r\n borderRadius: 'inherit',\r\n }}\r\n />\r\n {children}\r\n {hasIcon && (\r\n <div className=\"cris-button-icon-container\" style={iconContainerStyle}>\r\n {renderIconElement()}\r\n </div>\r\n )}\r\n {currentText && (\r\n <div className=\"cris-button-text-container\" style={textContainerStyle}>\r\n <span className=\"cris-button-text\">{currentText}</span>\r\n </div>\r\n )}\r\n </div>\r\n );\r\n}\r\n","/**\r\n * Icon configuration and utilities for CRIS components\r\n *\r\n * Supports different base paths for dev vs production (Crestron controller)\r\n */\r\n\r\nexport interface IconConfig {\r\n /** Base path for icon assets (e.g., '/assets/icons/' or 'http://controller/icons/') */\r\n basePath: string;\r\n /** File extension (default: 'svg') */\r\n extension: string;\r\n /** Default filter for inactive state */\r\n defaultFilter?: string;\r\n /** Default filter for active state */\r\n activeFilter?: string;\r\n}\r\n\r\n// Default configuration\r\nlet iconConfig: IconConfig = {\r\n basePath: '/assets/icons/',\r\n extension: 'svg',\r\n defaultFilter: undefined,\r\n activeFilter: undefined,\r\n};\r\n\r\n/**\r\n * Configure icon paths and defaults\r\n * Call this once at app startup\r\n *\r\n * @example\r\n * // Development\r\n * configureIcons({ basePath: '/assets/icons/' });\r\n *\r\n * // Production on Crestron controller\r\n * configureIcons({ basePath: '/html/icons/' });\r\n *\r\n * // With color filters\r\n * configureIcons({\r\n * basePath: '/assets/icons/',\r\n * defaultFilter: 'brightness(0) invert(1)', // white\r\n * activeFilter: 'brightness(0) invert(0.5) sepia(1) saturate(5) hue-rotate(0deg)', // red\r\n * });\r\n */\r\nexport function configureIcons(config: Partial<IconConfig>): void {\r\n iconConfig = { ...iconConfig, ...config };\r\n}\r\n\r\n/**\r\n * Get current icon configuration\r\n */\r\nexport function getIconConfig(): IconConfig {\r\n return iconConfig;\r\n}\r\n\r\n/**\r\n * Get full URL for an icon\r\n *\r\n * @param name - Icon name (without path or extension), e.g., 'motor-stop'\r\n * @returns Full URL to the icon\r\n */\r\nexport function getIconUrl(name: string): string {\r\n if (!name) return '';\r\n\r\n // If already a full URL or path, return as-is\r\n if (name.startsWith('http') || name.startsWith('/') || name.startsWith('.')) {\r\n return name;\r\n }\r\n\r\n const { basePath, extension } = iconConfig;\r\n const normalizedBase = basePath.endsWith('/') ? basePath : `${basePath}/`;\r\n return `${normalizedBase}${name}.${extension}`;\r\n}\r\n\r\n/**\r\n * Get CSS filter for icon state\r\n */\r\nexport function getIconFilter(active: boolean): string | undefined {\r\n return active ? iconConfig.activeFilter : iconConfig.defaultFilter;\r\n}\r\n","import { ReactNode } from 'react';\r\nimport { useDigital, useSerial, useCipDecode } from '@imperosoft/cris-webui-core';\r\n\r\nexport interface CrisTextProps {\r\n /** Serial join for indirect text */\r\n joinIndirectText?: number;\r\n /** Digital join for enable state */\r\n joinEnable?: number;\r\n /** Digital join for visibility */\r\n joinVisible?: number;\r\n /** Static text (used if no joinIndirectText) */\r\n text?: string;\r\n /** Container CSS class */\r\n className?: string;\r\n /** Container inline style */\r\n style?: React.CSSProperties;\r\n /** Text element CSS class */\r\n textClassName?: string;\r\n /** Text element inline style */\r\n textStyle?: React.CSSProperties;\r\n /** Class when disabled */\r\n classDisabled?: string;\r\n /** Children content */\r\n children?: ReactNode;\r\n}\r\n\r\nexport function CrisText({\r\n joinIndirectText,\r\n joinEnable,\r\n joinVisible,\r\n text,\r\n className = '',\r\n style,\r\n textClassName = '',\r\n textStyle,\r\n classDisabled = '',\r\n children,\r\n}: CrisTextProps) {\r\n // Get join values reactively\r\n const indirectText = useSerial(joinIndirectText ?? 0);\r\n const enabled = useDigital(joinEnable ?? 0);\r\n const visible = useDigital(joinVisible ?? 0);\r\n\r\n // Determine if text is enabled\r\n const isEnabled = joinEnable == null ? true : enabled;\r\n\r\n // Determine if text is visible\r\n const isVisible = joinVisible == null ? true : visible;\r\n\r\n // Get the raw text - indirect text or static text\r\n const rawText = joinIndirectText != null ? indirectText : (text ?? '');\r\n\r\n // Decode CIP patterns in the text\r\n const currentText = useCipDecode(rawText);\r\n\r\n // Don't render if not visible\r\n if (!isVisible) return null;\r\n\r\n // Build class names\r\n const containerClasses = [\r\n 'cris-text',\r\n className,\r\n !isEnabled && classDisabled,\r\n !isEnabled && 'disabled',\r\n ]\r\n .filter(Boolean)\r\n .join(' ');\r\n\r\n return (\r\n <div className={containerClasses} style={style}>\r\n {children}\r\n {currentText && (\r\n <span className={`cris-text-text ${textClassName}`} style={textStyle}>\r\n {currentText}\r\n </span>\r\n )}\r\n </div>\r\n );\r\n}\r\n","import { useState, useRef, useEffect, useCallback } from 'react';\r\nimport { useDigital, useAnalog, useJoinsStore } from '@imperosoft/cris-webui-core';\r\n\r\nexport interface CrisSliderProps {\r\n /** Analog join for value (shared with digital if joinDigital not set) */\r\n join?: number;\r\n /** Digital join for press/release feedback */\r\n joinDigital?: number;\r\n /** Analog join for value (overrides join) */\r\n joinAnalog?: number;\r\n /** Digital join for enable state */\r\n joinEnable?: number;\r\n /** Digital join for visibility */\r\n joinVisible?: number;\r\n\r\n /** Minimum value (default 0) */\r\n minValue?: number;\r\n /** Maximum value (default 65535) */\r\n maxValue?: number;\r\n /** Horizontal orientation (default false = vertical) */\r\n horizontal?: boolean;\r\n /** Hide fill bar */\r\n fillHidden?: boolean;\r\n /** Track size as percentage of container (default 20) */\r\n trackSizePercent?: number;\r\n /** Thumb size as percentage of container (default 4) */\r\n thumbSizePercent?: number;\r\n /** Delay in ms after drag before updating from feedback (default 1000) */\r\n delayMsAfterDragUpdateFeedback?: number;\r\n\r\n /** Container CSS class */\r\n className?: string;\r\n /** Container inline style */\r\n style?: React.CSSProperties;\r\n /** Bar/track CSS class */\r\n barClassName?: string;\r\n /** Bar/track inline style */\r\n barStyle?: React.CSSProperties;\r\n /** Fill CSS class */\r\n fillClassName?: string;\r\n /** Fill inline style */\r\n fillStyle?: React.CSSProperties;\r\n /** Thumb CSS class */\r\n thumbClassName?: string;\r\n /** Thumb inline style */\r\n thumbStyle?: React.CSSProperties;\r\n /** Class when disabled */\r\n classDisabled?: string;\r\n}\r\n\r\nexport function CrisSlider({\r\n join,\r\n joinDigital,\r\n joinAnalog,\r\n joinEnable,\r\n joinVisible,\r\n minValue = 0,\r\n maxValue = 65535,\r\n horizontal = false,\r\n fillHidden = false,\r\n trackSizePercent = 20,\r\n thumbSizePercent = 4,\r\n delayMsAfterDragUpdateFeedback = 1000,\r\n className = '',\r\n style,\r\n barClassName = '',\r\n barStyle,\r\n fillClassName = '',\r\n fillStyle,\r\n thumbClassName = '',\r\n thumbStyle,\r\n classDisabled = '',\r\n}: CrisSliderProps) {\r\n // Effective joins\r\n const effectiveAnalogJoin = joinAnalog ?? join;\r\n const effectiveDigitalJoin = joinDigital ?? join;\r\n\r\n // Get join values reactively\r\n const analogValue = useAnalog(effectiveAnalogJoin ?? 0);\r\n const enabled = useDigital(joinEnable ?? 0);\r\n const visible = useDigital(joinVisible ?? 0);\r\n\r\n // Action methods\r\n const dSet = useJoinsStore((state) => state.dSet);\r\n const aSet = useJoinsStore((state) => state.aSet);\r\n\r\n // Local state\r\n const [ratioCurrent, setRatioCurrent] = useState(0);\r\n const [isDragging, setIsDragging] = useState(false);\r\n const isDraggingOrJustAfterRef = useRef(false);\r\n const ratioBeforeDragRef = useRef(0);\r\n const afterDragTimeoutRef = useRef<number | null>(null);\r\n const touchingRef = useRef(false);\r\n\r\n // Determine if enabled and visible\r\n const isEnabled = joinEnable == null ? true : enabled;\r\n const isVisible = joinVisible == null ? true : visible;\r\n\r\n // Convert analog value to ratio\r\n const analogToRatio = useCallback(\r\n (value: number) => {\r\n const range = maxValue - minValue;\r\n if (range <= 0) return 0;\r\n return Math.max(0, Math.min(1, (value - minValue) / range));\r\n },\r\n [minValue, maxValue]\r\n );\r\n\r\n // Convert ratio to analog value\r\n const ratioToAnalog = useCallback(\r\n (ratio: number) => {\r\n const range = maxValue - minValue;\r\n return Math.round(minValue + ratio * range);\r\n },\r\n [minValue, maxValue]\r\n );\r\n\r\n // Update ratio from controller feedback\r\n const updateFromFeedback = useCallback(() => {\r\n if (!isDraggingOrJustAfterRef.current && effectiveAnalogJoin != null) {\r\n setRatioCurrent(analogToRatio(analogValue));\r\n }\r\n }, [analogValue, analogToRatio, effectiveAnalogJoin]);\r\n\r\n // Update from feedback when analog value changes\r\n useEffect(() => {\r\n updateFromFeedback();\r\n }, [updateFromFeedback]);\r\n\r\n // Handle drag start\r\n const handleDragStart = useCallback(() => {\r\n if (!isEnabled) return;\r\n\r\n setIsDragging(true);\r\n\r\n // Send digital press\r\n if (effectiveDigitalJoin != null) {\r\n dSet(effectiveDigitalJoin, true);\r\n }\r\n\r\n // Save ratio before drag\r\n if (!isDraggingOrJustAfterRef.current) {\r\n ratioBeforeDragRef.current = ratioCurrent;\r\n }\r\n isDraggingOrJustAfterRef.current = true;\r\n\r\n // Clear any pending timeout\r\n if (afterDragTimeoutRef.current !== null) {\r\n window.clearTimeout(afterDragTimeoutRef.current);\r\n afterDragTimeoutRef.current = null;\r\n }\r\n }, [isEnabled, effectiveDigitalJoin, dSet, ratioCurrent]);\r\n\r\n // Handle drag end\r\n const handleDragEnd = useCallback(() => {\r\n if (!isDragging) return;\r\n\r\n setIsDragging(false);\r\n\r\n // Send digital release\r\n if (effectiveDigitalJoin != null) {\r\n dSet(effectiveDigitalJoin, false);\r\n }\r\n\r\n // Schedule feedback update after delay\r\n if (delayMsAfterDragUpdateFeedback > 0) {\r\n afterDragTimeoutRef.current = window.setTimeout(() => {\r\n isDraggingOrJustAfterRef.current = false;\r\n updateFromFeedback();\r\n afterDragTimeoutRef.current = null;\r\n }, delayMsAfterDragUpdateFeedback);\r\n } else {\r\n isDraggingOrJustAfterRef.current = false;\r\n updateFromFeedback();\r\n }\r\n }, [isDragging, effectiveDigitalJoin, dSet, delayMsAfterDragUpdateFeedback, updateFromFeedback]);\r\n\r\n // Handle move/drag\r\n const handleMove = useCallback(\r\n (clientX: number, clientY: number, bounds: DOMRect) => {\r\n if (!isDragging) return;\r\n\r\n let newRatio: number;\r\n if (horizontal) {\r\n newRatio = (clientX - bounds.left) / bounds.width;\r\n } else {\r\n newRatio = 1 - (clientY - bounds.top) / bounds.height;\r\n }\r\n newRatio = Math.max(0, Math.min(1, newRatio));\r\n\r\n setRatioCurrent(newRatio);\r\n\r\n // Send analog value\r\n if (effectiveAnalogJoin != null) {\r\n aSet(effectiveAnalogJoin, ratioToAnalog(newRatio));\r\n }\r\n },\r\n [isDragging, horizontal, effectiveAnalogJoin, aSet, ratioToAnalog]\r\n );\r\n\r\n // Mouse handlers\r\n const handleMouseDown = (event: React.MouseEvent<HTMLDivElement>) => {\r\n if (touchingRef.current) return;\r\n if (event.button !== 0) return;\r\n\r\n handleDragStart();\r\n const bounds = event.currentTarget.getBoundingClientRect();\r\n handleMove(event.clientX, event.clientY, bounds);\r\n };\r\n\r\n const handleMouseMove = (event: React.MouseEvent<HTMLDivElement>) => {\r\n if (!isDragging) return;\r\n const bounds = event.currentTarget.getBoundingClientRect();\r\n handleMove(event.clientX, event.clientY, bounds);\r\n };\r\n\r\n const handleMouseUp = () => {\r\n handleDragEnd();\r\n };\r\n\r\n const handleMouseLeave = (event: React.MouseEvent<HTMLDivElement>) => {\r\n if (isDragging) {\r\n const bounds = event.currentTarget.getBoundingClientRect();\r\n handleMove(event.clientX, event.clientY, bounds);\r\n }\r\n handleDragEnd();\r\n };\r\n\r\n // Touch handlers\r\n const handleTouchStart = (event: React.TouchEvent<HTMLDivElement>) => {\r\n touchingRef.current = true;\r\n handleDragStart();\r\n const bounds = event.currentTarget.getBoundingClientRect();\r\n const touch = event.touches[0];\r\n handleMove(touch.clientX, touch.clientY, bounds);\r\n };\r\n\r\n const handleTouchMove = (event: React.TouchEvent<HTMLDivElement>) => {\r\n if (!isDragging) return;\r\n const bounds = event.currentTarget.getBoundingClientRect();\r\n const touch = event.touches[0];\r\n handleMove(touch.clientX, touch.clientY, bounds);\r\n };\r\n\r\n const handleTouchEnd = () => {\r\n handleDragEnd();\r\n };\r\n\r\n const handleTouchCancel = (event: React.TouchEvent<HTMLDivElement>) => {\r\n if (isDragging && event.touches.length > 0) {\r\n const bounds = event.currentTarget.getBoundingClientRect();\r\n const touch = event.touches[0];\r\n handleMove(touch.clientX, touch.clientY, bounds);\r\n }\r\n handleDragEnd();\r\n };\r\n\r\n // Don't render if not visible\r\n if (!isVisible) return null;\r\n\r\n // Build class names\r\n const containerClasses = [\r\n 'cris-slider',\r\n className,\r\n !isEnabled && classDisabled,\r\n !isEnabled && 'disabled',\r\n ]\r\n .filter(Boolean)\r\n .join(' ');\r\n\r\n // Calculate bar style\r\n const computedBarStyle: React.CSSProperties = {\r\n ...barStyle,\r\n position: 'absolute',\r\n };\r\n\r\n if (horizontal) {\r\n const width = 100 - thumbSizePercent;\r\n const left = (100 - width) / 2;\r\n computedBarStyle.height = `${trackSizePercent}%`;\r\n computedBarStyle.top = `${(100 - trackSizePercent) / 2}%`;\r\n computedBarStyle.width = `${width}%`;\r\n computedBarStyle.left = `${left}%`;\r\n } else {\r\n const height = 100 - thumbSizePercent;\r\n const top = (100 - height) / 2;\r\n computedBarStyle.width = `${trackSizePercent}%`;\r\n computedBarStyle.left = `${(100 - trackSizePercent) / 2}%`;\r\n computedBarStyle.height = `${height}%`;\r\n computedBarStyle.top = `${top}%`;\r\n }\r\n\r\n // Calculate fill style\r\n const computedFillStyle: React.CSSProperties = {\r\n ...fillStyle,\r\n position: 'absolute',\r\n };\r\n\r\n if (horizontal) {\r\n const range = 100 - thumbSizePercent;\r\n const left = thumbSizePercent / 2;\r\n const width = range * ratioCurrent;\r\n computedFillStyle.height = `${trackSizePercent}%`;\r\n computedFillStyle.top = `${(100 - trackSizePercent) / 2}%`;\r\n computedFillStyle.width = `${width}%`;\r\n computedFillStyle.left = `${left}%`;\r\n } else {\r\n const range = 100 - thumbSizePercent;\r\n const top = 100 - (range * ratioCurrent + thumbSizePercent / 2);\r\n const height = range + thumbSizePercent / 2 - top;\r\n computedFillStyle.width = `${trackSizePercent}%`;\r\n computedFillStyle.left = `${(100 - trackSizePercent) / 2}%`;\r\n computedFillStyle.height = `${height}%`;\r\n computedFillStyle.top = `${top}%`;\r\n }\r\n\r\n // Calculate thumb style\r\n const computedThumbStyle: React.CSSProperties = {\r\n ...thumbStyle,\r\n position: 'absolute',\r\n };\r\n\r\n if (horizontal) {\r\n const maxLeft = 100 - thumbSizePercent;\r\n const left = maxLeft * ratioCurrent;\r\n computedThumbStyle.width = `${thumbSizePercent}%`;\r\n computedThumbStyle.height = '100%';\r\n computedThumbStyle.left = `${left}%`;\r\n } else {\r\n const maxTop = 100 - thumbSizePercent;\r\n const top = maxTop - maxTop * ratioCurrent;\r\n computedThumbStyle.width = '100%';\r\n computedThumbStyle.height = `${thumbSizePercent}%`;\r\n computedThumbStyle.top = `${top}%`;\r\n }\r\n\r\n return (\r\n <div\r\n className={containerClasses}\r\n style={{\r\n ...style,\r\n position: 'relative',\r\n cursor: isEnabled ? 'pointer' : 'default',\r\n touchAction: 'none',\r\n userSelect: 'none',\r\n }}\r\n onMouseDown={handleMouseDown}\r\n onMouseMove={handleMouseMove}\r\n onMouseUp={handleMouseUp}\r\n onMouseLeave={handleMouseLeave}\r\n onTouchStart={handleTouchStart}\r\n onTouchMove={handleTouchMove}\r\n onTouchEnd={handleTouchEnd}\r\n onTouchCancel={handleTouchCancel}\r\n >\r\n {/* Bar/Track */}\r\n <div className={`cris-slider-bar ${barClassName}`} style={computedBarStyle} />\r\n\r\n {/* Fill */}\r\n {!fillHidden && (\r\n <div className={`cris-slider-fill ${fillClassName}`} style={computedFillStyle} />\r\n )}\r\n\r\n {/* Thumb */}\r\n <div className={`cris-slider-thumb ${thumbClassName}`} style={computedThumbStyle} />\r\n </div>\r\n );\r\n}\r\n","import { useAnalog, useDigital } from '@imperosoft/cris-webui-core';\r\n\r\nexport interface CrisGaugeProps {\r\n /** Static value (used if no join) */\r\n value?: number;\r\n /** Analog join for value */\r\n join?: number;\r\n /** Digital join for enable state */\r\n joinEnable?: number;\r\n /** Digital join for visibility */\r\n joinVisible?: number;\r\n\r\n /** Minimum value (default 0) */\r\n minValue?: number;\r\n /** Maximum value (default 65535) */\r\n maxValue?: number;\r\n /** Number of segments (default 20) */\r\n segments?: number;\r\n /** Invert direction (default false) */\r\n inverted?: boolean;\r\n /** Orientation (default vertical) */\r\n orientation?: 'vertical' | 'horizontal';\r\n\r\n /** Container CSS class */\r\n className?: string;\r\n /** Container inline style */\r\n style?: React.CSSProperties;\r\n\r\n /** Inactive segment CSS class */\r\n inactiveSegmentClassName?: string;\r\n /** Inactive segment inline style */\r\n inactiveSegmentStyle?: React.CSSProperties;\r\n\r\n /** Active segment CSS class (all levels) */\r\n segmentClassName?: string;\r\n /** Active segment inline style (all levels) */\r\n segmentStyle?: React.CSSProperties;\r\n\r\n /** Low level segment CSS class (0-60%) */\r\n lowSegmentClassName?: string;\r\n /** Low level segment inline style */\r\n lowSegmentStyle?: React.CSSProperties;\r\n\r\n /** Medium level segment CSS class (60-80%) */\r\n mediumSegmentClassName?: string;\r\n /** Medium level segment inline style */\r\n mediumSegmentStyle?: React.CSSProperties;\r\n\r\n /** High level segment CSS class (80-100%) */\r\n highSegmentClassName?: string;\r\n /** High level segment inline style */\r\n highSegmentStyle?: React.CSSProperties;\r\n\r\n /** Class when disabled */\r\n classDisabled?: string;\r\n}\r\n\r\nexport function CrisGauge({\r\n value,\r\n join,\r\n joinEnable,\r\n joinVisible,\r\n minValue = 0,\r\n maxValue = 65535,\r\n segments = 20,\r\n inverted = false,\r\n orientation = 'vertical',\r\n className = '',\r\n style,\r\n inactiveSegmentClassName = '',\r\n inactiveSegmentStyle,\r\n segmentClassName = '',\r\n segmentStyle,\r\n lowSegmentClassName = '',\r\n lowSegmentStyle,\r\n mediumSegmentClassName = '',\r\n mediumSegmentStyle,\r\n highSegmentClassName = '',\r\n highSegmentStyle,\r\n classDisabled = '',\r\n}: CrisGaugeProps) {\r\n // Get join values reactively\r\n const joinValue = useAnalog(join ?? 0);\r\n const enabled = useDigital(joinEnable ?? 0);\r\n const visible = useDigital(joinVisible ?? 0);\r\n\r\n // Determine if enabled and visible\r\n const isEnabled = joinEnable == null ? true : enabled;\r\n const isVisible = joinVisible == null ? true : visible;\r\n\r\n // Get current value\r\n const currentValue = join != null ? joinValue : (value ?? 0);\r\n\r\n // Don't render if not visible\r\n if (!isVisible) return null;\r\n\r\n // Determine segment type (low, medium, high)\r\n const getSegmentType = (segment: number): 'low' | 'medium' | 'high' => {\r\n const lowMax = segments * 0.6;\r\n const mediumMax = segments * 0.8;\r\n\r\n const seg =\r\n (inverted && orientation === 'vertical') || (!inverted && orientation === 'horizontal')\r\n ? segment + 1\r\n : segments - segment;\r\n\r\n if (seg <= lowMax) return 'low';\r\n if (seg <= mediumMax) return 'medium';\r\n return 'high';\r\n };\r\n\r\n // Check if segment is active\r\n const isSegmentActive = (segment: number): boolean => {\r\n if (currentValue < minValue) return false;\r\n if (currentValue > maxValue) return true;\r\n\r\n const ratio = currentValue / (maxValue - minValue);\r\n let segMax = Math.round(segments * ratio);\r\n if (ratio !== 0 && segMax === 0) segMax = 1;\r\n\r\n const seg =\r\n (inverted && orientation === 'vertical') || (!inverted && orientation === 'horizontal')\r\n ? segment + 1\r\n : segments - segment;\r\n\r\n return seg <= segMax;\r\n };\r\n\r\n // Get segment class\r\n const getSegmentClass = (segment: number): string => {\r\n const type = getSegmentType(segment);\r\n const active = isSegmentActive(segment);\r\n\r\n const classes = ['cris-gauge-segment', type, orientation];\r\n\r\n if (active) {\r\n classes.push('active');\r\n // Add type-specific or general active class\r\n if (type === 'low' && (lowSegmentClassName || segmentClassName)) {\r\n classes.push(lowSegmentClassName || segmentClassName);\r\n } else if (type === 'medium' && (mediumSegmentClassName || segmentClassName)) {\r\n classes.push(mediumSegmentClassName || segmentClassName);\r\n } else if (type === 'high' && (highSegmentClassName || segmentClassName)) {\r\n classes.push(highSegmentClassName || segmentClassName);\r\n }\r\n } else {\r\n if (inactiveSegmentClassName) {\r\n classes.push(inactiveSegmentClassName);\r\n }\r\n }\r\n\r\n return classes.filter(Boolean).join(' ');\r\n };\r\n\r\n // Get segment style\r\n const getSegmentStyle = (segment: number): React.CSSProperties | undefined => {\r\n const type = getSegmentType(segment);\r\n const active = isSegmentActive(segment);\r\n\r\n if (active) {\r\n if (type === 'low' && (lowSegmentStyle || segmentStyle)) {\r\n return lowSegmentStyle || segmentStyle;\r\n } else if (type === 'medium' && (mediumSegmentStyle || segmentStyle)) {\r\n return mediumSegmentStyle || segmentStyle;\r\n } else if (type === 'high' && (highSegmentStyle || segmentStyle)) {\r\n return highSegmentStyle || segmentStyle;\r\n }\r\n } else {\r\n return inactiveSegmentStyle;\r\n }\r\n\r\n return undefined;\r\n };\r\n\r\n // Build container classes\r\n const containerClasses = [\r\n 'cris-gauge',\r\n className,\r\n !isEnabled && classDisabled,\r\n !isEnabled && 'disabled',\r\n ]\r\n .filter(Boolean)\r\n .join(' ');\r\n\r\n // Container flex style\r\n const containerStyle: React.CSSProperties = {\r\n ...style,\r\n display: 'flex',\r\n flexDirection: orientation === 'horizontal' ? 'row' : 'column',\r\n justifyContent: 'center',\r\n alignItems: 'center',\r\n };\r\n\r\n return (\r\n <div className={containerClasses} style={containerStyle}>\r\n {Array.from({ length: segments }, (_, segment) => (\r\n <div\r\n key={segment}\r\n className={getSegmentClass(segment)}\r\n style={getSegmentStyle(segment)}\r\n />\r\n ))}\r\n </div>\r\n );\r\n}\r\n","import { useAnalog, useDigital } from '@imperosoft/cris-webui-core';\r\n\r\nexport type SpinnerSpeed = 'slow' | 'medium' | 'fast';\r\n\r\nexport interface CrisSpinnerProps {\r\n /** Analog join for position (0-65535 maps to 0-360 degrees) */\r\n joinPosition?: number;\r\n /** Digital join for visibility */\r\n joinVisible?: number;\r\n\r\n /** Static position value (0-65535, used if no joinPosition) */\r\n position?: number;\r\n\r\n /** Endless rotation mode (default: false) */\r\n endless?: boolean;\r\n /** Speed for endless rotation (default: 'medium') */\r\n endlessSpeed?: SpinnerSpeed;\r\n\r\n /** Spinner color (default: '#bf2b23') */\r\n color?: string;\r\n /** Background/track color (default: 'rgba(0, 0, 0, 0.1)') */\r\n trackColor?: string;\r\n /** Spinner line width in pixels (default: 3) */\r\n lineWidth?: number;\r\n\r\n /** Container CSS class */\r\n className?: string;\r\n /** Container inline style */\r\n style?: React.CSSProperties;\r\n}\r\n\r\nconst SPEED_DURATIONS: Record<SpinnerSpeed, string> = {\r\n slow: '1.5s',\r\n medium: '0.8s',\r\n fast: '0.4s',\r\n};\r\n\r\nexport function CrisSpinner({\r\n joinPosition,\r\n joinVisible,\r\n position,\r\n endless = false,\r\n endlessSpeed = 'medium',\r\n color = '#bf2b23',\r\n trackColor = 'rgba(0, 0, 0, 0.1)',\r\n lineWidth = 3,\r\n className = '',\r\n style,\r\n}: CrisSpinnerProps) {\r\n // Get join values reactively\r\n const joinPositionValue = useAnalog(joinPosition ?? 0);\r\n const visible = useDigital(joinVisible ?? 0);\r\n\r\n // Determine visibility\r\n const isVisible = joinVisible == null ? true : visible;\r\n\r\n // Don't render if not visible\r\n if (!isVisible) return null;\r\n\r\n // Get current position (0-65535 -> 0-360 degrees)\r\n const currentPosition = joinPosition != null ? joinPositionValue : (position ?? 0);\r\n const rotationDegrees = (currentPosition / 65535) * 360;\r\n\r\n // Build container classes\r\n const containerClasses = ['cris-spinner', className].filter(Boolean).join(' ');\r\n\r\n // Spinner styles\r\n const spinnerStyle: React.CSSProperties = {\r\n width: '100%',\r\n height: '100%',\r\n borderRadius: '50%',\r\n border: `${lineWidth}px solid ${trackColor}`,\r\n borderTopColor: color,\r\n boxSizing: 'border-box',\r\n };\r\n\r\n if (endless) {\r\n // Endless rotation animation\r\n spinnerStyle.animation = `cris-spinner-rotate ${SPEED_DURATIONS[endlessSpeed]} linear infinite`;\r\n } else {\r\n // Position-based rotation\r\n spinnerStyle.transform = `rotate(${rotationDegrees}deg)`;\r\n spinnerStyle.transition = 'transform 0.1s linear';\r\n }\r\n\r\n return (\r\n <div className={containerClasses} style={style}>\r\n <div style={spinnerStyle} />\r\n <style>{`\r\n @keyframes cris-spinner-rotate {\r\n to { transform: rotate(360deg); }\r\n }\r\n `}</style>\r\n </div>\r\n );\r\n}\r\n"],"mappings":";AAAA,SAAS,UAAU,cAAyB;AAC5C,SAAS,YAAY,qBAAqB;;;ACiB1C,IAAI,aAAyB;AAAA,EAC3B,UAAU;AAAA,EACV,WAAW;AAAA,EACX,eAAe;AAAA,EACf,cAAc;AAChB;AAoBO,SAAS,eAAe,QAAmC;AAChE,eAAa,EAAE,GAAG,YAAY,GAAG,OAAO;AAC1C;AAKO,SAAS,gBAA4B;AAC1C,SAAO;AACT;AAQO,SAAS,WAAW,MAAsB;AAC/C,MAAI,CAAC,KAAM,QAAO;AAGlB,MAAI,KAAK,WAAW,MAAM,KAAK,KAAK,WAAW,GAAG,KAAK,KAAK,WAAW,GAAG,GAAG;AAC3E,WAAO;AAAA,EACT;AAEA,QAAM,EAAE,UAAU,UAAU,IAAI;AAChC,QAAM,iBAAiB,SAAS,SAAS,GAAG,IAAI,WAAW,GAAG,QAAQ;AACtE,SAAO,GAAG,cAAc,GAAG,IAAI,IAAI,SAAS;AAC9C;AAKO,SAAS,cAAc,QAAqC;AACjE,SAAO,SAAS,WAAW,eAAe,WAAW;AACvD;;;ADuOa,cA8CT,YA9CS;AA9ON,SAAS,WAAW;AAAA,EACzB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,eAAe;AAAA,EACf;AAAA,EACA;AAAA,EACA;AAAA,EACA,sBAAsB;AAAA,EACtB,oBAAoB;AAAA,EACpB,oBAAoB;AAAA,EACpB;AAAA,EACA,YAAY;AAAA,EACZ,cAAc;AAAA,EACd,eAAe;AAAA,EACf,gBAAgB;AAAA,EAChB;AAAA,EACA;AAAA,EACA;AACF,GAAoB;AAClB,QAAM,CAAC,SAAS,UAAU,IAAI,SAAS,KAAK;AAC5C,QAAM,CAAC,UAAU,WAAW,IAAI,SAAS,KAAK;AAC9C,QAAM,aAAa,OAAO,KAAK;AAC/B,QAAM,cAAc,OAAO,KAAK;AAChC,QAAM,sBAAsB,OAAO,KAAK;AAGxC,QAAM,eAAe,gBAAgB;AACrC,QAAM,WAAW,WAAW,gBAAgB,CAAC;AAC7C,QAAM,UAAU,WAAW,cAAc,CAAC;AAC1C,QAAM,UAAU,WAAW,eAAe,CAAC;AAG3C,QAAM,OAAO,cAAc,CAAC,UAAU,MAAM,IAAI;AAGhD,QAAM,YAAY,cAAc,OAAO,OAAO;AAG9C,QAAM,YAAY,eAAe,OAAO,OAAO;AAG/C,QAAM,qBAAqB,uBAAuB,gBAAgB,QAAQ;AAC1E,QAAM,qBAAqB,qBAAqB,WAAW;AAC3D,QAAM,WAAW,sBAAsB;AAGvC,MAAI,cAAc,QAAQ;AAC1B,MAAI,sBAAsB,eAAe,MAAM;AAC7C,kBAAc;AAAA,EAChB,WAAW,sBAAsB,gBAAgB,MAAM;AACrD,kBAAc;AAAA,EAChB;AAGA,QAAM,cAAc,MAAM;AACxB,QAAI,kBAAmB;AACvB,QAAI,WAAW,QAAS;AAExB,eAAW,UAAU;AACrB,eAAW,IAAI;AAEf,QAAI,CAAC,UAAW;AAGhB,cAAU;AAGV,QAAI,QAAQ,QAAQ,WAAW,MAAM;AACnC,WAAK,MAAM,IAAI;AAAA,IACjB;AAAA,EAEF;AAEA,QAAM,gBAAgB,MAAM;AAC1B,QAAI,kBAAmB;AACvB,QAAI,CAAC,WAAW,QAAS;AAEzB,eAAW,UAAU;AACrB,eAAW,KAAK;AAEhB,QAAI,CAAC,UAAW;AAGhB,gBAAY;AAGZ,QAAI,QAAQ,QAAQ,WAAW,MAAM;AACnC,WAAK,MAAM,KAAK;AAAA,IAClB;AAAA,EAEF;AAKA,QAAM,mBAAmB,MAAM;AAC7B,gBAAY,UAAU;AACtB,wBAAoB,UAAU;AAC9B,gBAAY;AAAA,EACd;AAEA,QAAM,iBAAiB,MAAM;AAC3B,gBAAY,UAAU;AAEtB,QAAI,oBAAoB,SAAS;AAC/B,0BAAoB,UAAU;AAC9B,oBAAc;AAAA,IAChB;AAAA,EACF;AAEA,QAAM,oBAAoB,MAAM;AAC9B,gBAAY,UAAU;AACtB,wBAAoB,UAAU;AAC9B,kBAAc;AAAA,EAChB;AAGA,QAAM,kBAAkB,MAAM;AAC5B,QAAI,YAAY,QAAS;AACzB,gBAAY;AAAA,EACd;AAEA,QAAM,gBAAgB,MAAM;AAC1B,QAAI,YAAY,QAAS;AACzB,kBAAc;AAAA,EAChB;AAEA,QAAM,mBAAmB,MAAM;AAC7B,QAAI,YAAY,QAAS;AACzB,QAAI,kBAAmB;AACvB,gBAAY,IAAI;AAAA,EAClB;AAEA,QAAM,mBAAmB,MAAM;AAC7B,QAAI,YAAY,QAAS;AACzB,gBAAY,KAAK;AACjB,kBAAc;AAAA,EAChB;AAGA,MAAI,CAAC,UAAW,QAAO;AAGvB,QAAM,UAAU;AAAA,IACd;AAAA,IACA;AAAA,IACA,sBAAsB;AAAA,IACtB,sBAAsB;AAAA,IACtB,CAAC,aAAa;AAAA,IACd,sBAAsB;AAAA,IACtB,sBAAsB;AAAA,IACtB,CAAC,aAAa;AAAA,IACd,qBAAqB;AAAA,EACvB,EACG,OAAO,OAAO,EACd,KAAK,GAAG;AAGX,QAAM,gBACJ,iBAAiB,QACb,aACA,iBAAiB,WACf,qBACA,iBAAiB,SACf,aACA;AAGV,QAAM,UAAU,QAAQ,QAAQ,YAAY,QAAQ,aAAa,QAAQ,kBAAkB,QAAQ,mBAAmB;AAGtH,QAAM,kBAAkB,YAAY,kBAAkB,OAAO,iBAAiB;AAC9E,QAAM,mBAAmB,YAAY,mBAAmB,OAAO,kBAAkB;AACjF,QAAM,mBAAmB,YAAY,mBAAmB,OAAO,EAAE,GAAG,WAAW,GAAG,gBAAgB,IAAI;AAGtG,QAAM,cAAsC;AAAA,IAC1C,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,EACN;AAGA,QAAM,mBAAmB,MAA0B;AACjD,QAAI,YAAY,KAAM,QAAO;AAC7B,QAAI,OAAO,aAAa,SAAU,QAAO,GAAG,QAAQ;AACpD,QAAI,YAAY,YAAa,QAAO,YAAY,QAAQ;AACxD,WAAO;AAAA,EACT;AAEA,QAAM,gBAAgB,iBAAiB;AAGvC,QAAM,cAAc;AAAA,IAClB;AAAA,IACA;AAAA,IACA,sBAAsB;AAAA,IACtB,sBAAsB;AAAA,EACxB,EACG,OAAO,OAAO,EACd,KAAK,GAAG;AAGX,QAAM,oBAAyC;AAAA,IAC7C,GAAG;AAAA,EACL;AAGA,MAAI,eAAe;AACjB,sBAAkB,QAAQ;AAC1B,sBAAkB,SAAS;AAAA,EAC7B;AAGA,MAAI,mBAAmB,CAAC,kBAAkB,QAAQ;AAChD,UAAM,SAAS,cAAc,QAAQ;AACrC,QAAI,QAAQ;AACV,wBAAkB,SAAS;AAAA,IAC7B;AAAA,EACF;AAGA,QAAM,oBAAoB,MAAM;AAC9B,QAAI,MAAM;AAER,aAAO,oBAAC,UAAK,WAAW,aAAa,OAAO,mBAAoB,gBAAK;AAAA,IACvE;AAEA,QAAI,iBAAiB;AAEnB,aACE;AAAA,QAAC;AAAA;AAAA,UACC,WAAW;AAAA,UACX,OAAO;AAAA,UACP,KAAK,WAAW,eAAe;AAAA,UAC/B,KAAI;AAAA;AAAA,MACN;AAAA,IAEJ;AAEA,QAAI,kBAAkB;AAEpB,aAAO,oBAAC,SAAI,WAAW,aAAa,OAAO,mBAAmB,KAAI,IAAG;AAAA,IACvE;AAEA,WAAO;AAAA,EACT;AAGA,QAAM,aAAa,iBAAiB,SAAS,iBAAiB;AAC9D,QAAM,UAAU,gBAAgB;AAChC,QAAM,uBAAuB,UAAW,aAAa,QAAQ,QAAS;AACtE,QAAM,gBAAgB,qBAAqB;AAG3C,QAAM,oBAAoB,eAAe,aAAa;AAGtD,QAAM,qBAA0C,aAC5C,EAAE,QAAQ,eAAe,OAAO,QAAQ,SAAS,QAAQ,YAAY,UAAU,gBAAgB,SAAS,IACxG,EAAE,OAAO,eAAe,QAAQ,QAAQ,SAAS,QAAQ,YAAY,UAAU,gBAAgB,SAAS;AAG5G,QAAM,qBAA0C,aAC5C,EAAE,QAAQ,mBAAmB,OAAO,QAAQ,SAAS,QAAQ,YAAY,UAAU,gBAAgB,SAAS,IAC5G,EAAE,OAAO,mBAAmB,QAAQ,QAAQ,SAAS,QAAQ,YAAY,UAAU,gBAAgB,SAAS;AAGhH,QAAM,mBAAmB,UAAU,SAAS,MAAM;AAElD,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAW,GAAG,OAAO,qCAAqC,aAAa;AAAA,MACvE,OAAO;AAAA,QACL,QAAQ,oBAAoB,YAAY;AAAA,QACxC,UAAU,mBAAmB,SAAY;AAAA,QACzC,UAAU,mBAAmB,SAAY;AAAA,MAC3C;AAAA,MACA,aAAa;AAAA,MACb,WAAW;AAAA,MACX,cAAc;AAAA,MACd,cAAc;AAAA,MACd,cAAc;AAAA,MACd,YAAY;AAAA,MACZ,eAAe;AAAA,MAGf;AAAA;AAAA,UAAC;AAAA;AAAA,YACC,WAAU;AAAA,YACV,OAAO;AAAA,cACL,UAAU;AAAA,cACV,OAAO;AAAA,cACP,iBAAiB;AAAA,cACjB,SAAS,WAAW,MAAM;AAAA,cAC1B,YAAY;AAAA,cACZ,eAAe;AAAA,cACf,cAAc;AAAA,YAChB;AAAA;AAAA,QACF;AAAA,QACC;AAAA,QACA,WACC,oBAAC,SAAI,WAAU,8BAA6B,OAAO,oBAChD,4BAAkB,GACrB;AAAA,QAED,eACC,oBAAC,SAAI,WAAU,8BAA6B,OAAO,oBACjD,8BAAC,UAAK,WAAU,oBAAoB,uBAAY,GAClD;AAAA;AAAA;AAAA,EAEJ;AAEJ;;;AE3YA,SAAS,cAAAA,aAAY,WAAW,oBAAoB;AAoEhD,SAGI,OAAAC,MAHJ,QAAAC,aAAA;AA3CG,SAAS,SAAS;AAAA,EACvB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,YAAY;AAAA,EACZ;AAAA,EACA,gBAAgB;AAAA,EAChB;AAAA,EACA,gBAAgB;AAAA,EAChB;AACF,GAAkB;AAEhB,QAAM,eAAe,UAAU,oBAAoB,CAAC;AACpD,QAAM,UAAUF,YAAW,cAAc,CAAC;AAC1C,QAAM,UAAUA,YAAW,eAAe,CAAC;AAG3C,QAAM,YAAY,cAAc,OAAO,OAAO;AAG9C,QAAM,YAAY,eAAe,OAAO,OAAO;AAG/C,QAAM,UAAU,oBAAoB,OAAO,eAAgB,QAAQ;AAGnE,QAAM,cAAc,aAAa,OAAO;AAGxC,MAAI,CAAC,UAAW,QAAO;AAGvB,QAAM,mBAAmB;AAAA,IACvB;AAAA,IACA;AAAA,IACA,CAAC,aAAa;AAAA,IACd,CAAC,aAAa;AAAA,EAChB,EACG,OAAO,OAAO,EACd,KAAK,GAAG;AAEX,SACE,gBAAAE,MAAC,SAAI,WAAW,kBAAkB,OAC/B;AAAA;AAAA,IACA,eACC,gBAAAD,KAAC,UAAK,WAAW,kBAAkB,aAAa,IAAI,OAAO,WACxD,uBACH;AAAA,KAEJ;AAEJ;;;AC9EA,SAAS,YAAAE,WAAU,UAAAC,SAAQ,WAAW,mBAAmB;AACzD,SAAS,cAAAC,aAAY,WAAW,iBAAAC,sBAAqB;AAgVjD,SAmBE,OAAAC,MAnBF,QAAAC,aAAA;AA/RG,SAAS,WAAW;AAAA,EACzB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,WAAW;AAAA,EACX,WAAW;AAAA,EACX,aAAa;AAAA,EACb,aAAa;AAAA,EACb,mBAAmB;AAAA,EACnB,mBAAmB;AAAA,EACnB,iCAAiC;AAAA,EACjC,YAAY;AAAA,EACZ;AAAA,EACA,eAAe;AAAA,EACf;AAAA,EACA,gBAAgB;AAAA,EAChB;AAAA,EACA,iBAAiB;AAAA,EACjB;AAAA,EACA,gBAAgB;AAClB,GAAoB;AAElB,QAAM,sBAAsB,cAAc;AAC1C,QAAM,uBAAuB,eAAe;AAG5C,QAAM,cAAc,UAAU,uBAAuB,CAAC;AACtD,QAAM,UAAUH,YAAW,cAAc,CAAC;AAC1C,QAAM,UAAUA,YAAW,eAAe,CAAC;AAG3C,QAAM,OAAOC,eAAc,CAAC,UAAU,MAAM,IAAI;AAChD,QAAM,OAAOA,eAAc,CAAC,UAAU,MAAM,IAAI;AAGhD,QAAM,CAAC,cAAc,eAAe,IAAIH,UAAS,CAAC;AAClD,QAAM,CAAC,YAAY,aAAa,IAAIA,UAAS,KAAK;AAClD,QAAM,2BAA2BC,QAAO,KAAK;AAC7C,QAAM,qBAAqBA,QAAO,CAAC;AACnC,QAAM,sBAAsBA,QAAsB,IAAI;AACtD,QAAM,cAAcA,QAAO,KAAK;AAGhC,QAAM,YAAY,cAAc,OAAO,OAAO;AAC9C,QAAM,YAAY,eAAe,OAAO,OAAO;AAG/C,QAAM,gBAAgB;AAAA,IACpB,CAAC,UAAkB;AACjB,YAAM,QAAQ,WAAW;AACzB,UAAI,SAAS,EAAG,QAAO;AACvB,aAAO,KAAK,IAAI,GAAG,KAAK,IAAI,IAAI,QAAQ,YAAY,KAAK,CAAC;AAAA,IAC5D;AAAA,IACA,CAAC,UAAU,QAAQ;AAAA,EACrB;AAGA,QAAM,gBAAgB;AAAA,IACpB,CAAC,UAAkB;AACjB,YAAM,QAAQ,WAAW;AACzB,aAAO,KAAK,MAAM,WAAW,QAAQ,KAAK;AAAA,IAC5C;AAAA,IACA,CAAC,UAAU,QAAQ;AAAA,EACrB;AAGA,QAAM,qBAAqB,YAAY,MAAM;AAC3C,QAAI,CAAC,yBAAyB,WAAW,uBAAuB,MAAM;AACpE,sBAAgB,cAAc,WAAW,CAAC;AAAA,IAC5C;AAAA,EACF,GAAG,CAAC,aAAa,eAAe,mBAAmB,CAAC;AAGpD,YAAU,MAAM;AACd,uBAAmB;AAAA,EACrB,GAAG,CAAC,kBAAkB,CAAC;AAGvB,QAAM,kBAAkB,YAAY,MAAM;AACxC,QAAI,CAAC,UAAW;AAEhB,kBAAc,IAAI;AAGlB,QAAI,wBAAwB,MAAM;AAChC,WAAK,sBAAsB,IAAI;AAAA,IACjC;AAGA,QAAI,CAAC,yBAAyB,SAAS;AACrC,yBAAmB,UAAU;AAAA,IAC/B;AACA,6BAAyB,UAAU;AAGnC,QAAI,oBAAoB,YAAY,MAAM;AACxC,aAAO,aAAa,oBAAoB,OAAO;AAC/C,0BAAoB,UAAU;AAAA,IAChC;AAAA,EACF,GAAG,CAAC,WAAW,sBAAsB,MAAM,YAAY,CAAC;AAGxD,QAAM,gBAAgB,YAAY,MAAM;AACtC,QAAI,CAAC,WAAY;AAEjB,kBAAc,KAAK;AAGnB,QAAI,wBAAwB,MAAM;AAChC,WAAK,sBAAsB,KAAK;AAAA,IAClC;AAGA,QAAI,iCAAiC,GAAG;AACtC,0BAAoB,UAAU,OAAO,WAAW,MAAM;AACpD,iCAAyB,UAAU;AACnC,2BAAmB;AACnB,4BAAoB,UAAU;AAAA,MAChC,GAAG,8BAA8B;AAAA,IACnC,OAAO;AACL,+BAAyB,UAAU;AACnC,yBAAmB;AAAA,IACrB;AAAA,EACF,GAAG,CAAC,YAAY,sBAAsB,MAAM,gCAAgC,kBAAkB,CAAC;AAG/F,QAAM,aAAa;AAAA,IACjB,CAAC,SAAiB,SAAiB,WAAoB;AACrD,UAAI,CAAC,WAAY;AAEjB,UAAI;AACJ,UAAI,YAAY;AACd,oBAAY,UAAU,OAAO,QAAQ,OAAO;AAAA,MAC9C,OAAO;AACL,mBAAW,KAAK,UAAU,OAAO,OAAO,OAAO;AAAA,MACjD;AACA,iBAAW,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,QAAQ,CAAC;AAE5C,sBAAgB,QAAQ;AAGxB,UAAI,uBAAuB,MAAM;AAC/B,aAAK,qBAAqB,cAAc,QAAQ,CAAC;AAAA,MACnD;AAAA,IACF;AAAA,IACA,CAAC,YAAY,YAAY,qBAAqB,MAAM,aAAa;AAAA,EACnE;AAGA,QAAM,kBAAkB,CAAC,UAA4C;AACnE,QAAI,YAAY,QAAS;AACzB,QAAI,MAAM,WAAW,EAAG;AAExB,oBAAgB;AAChB,UAAM,SAAS,MAAM,cAAc,sBAAsB;AACzD,eAAW,MAAM,SAAS,MAAM,SAAS,MAAM;AAAA,EACjD;AAEA,QAAM,kBAAkB,CAAC,UAA4C;AACnE,QAAI,CAAC,WAAY;AACjB,UAAM,SAAS,MAAM,cAAc,sBAAsB;AACzD,eAAW,MAAM,SAAS,MAAM,SAAS,MAAM;AAAA,EACjD;AAEA,QAAM,gBAAgB,MAAM;AAC1B,kBAAc;AAAA,EAChB;AAEA,QAAM,mBAAmB,CAAC,UAA4C;AACpE,QAAI,YAAY;AACd,YAAM,SAAS,MAAM,cAAc,sBAAsB;AACzD,iBAAW,MAAM,SAAS,MAAM,SAAS,MAAM;AAAA,IACjD;AACA,kBAAc;AAAA,EAChB;AAGA,QAAM,mBAAmB,CAAC,UAA4C;AACpE,gBAAY,UAAU;AACtB,oBAAgB;AAChB,UAAM,SAAS,MAAM,cAAc,sBAAsB;AACzD,UAAM,QAAQ,MAAM,QAAQ,CAAC;AAC7B,eAAW,MAAM,SAAS,MAAM,SAAS,MAAM;AAAA,EACjD;AAEA,QAAM,kBAAkB,CAAC,UAA4C;AACnE,QAAI,CAAC,WAAY;AACjB,UAAM,SAAS,MAAM,cAAc,sBAAsB;AACzD,UAAM,QAAQ,MAAM,QAAQ,CAAC;AAC7B,eAAW,MAAM,SAAS,MAAM,SAAS,MAAM;AAAA,EACjD;AAEA,QAAM,iBAAiB,MAAM;AAC3B,kBAAc;AAAA,EAChB;AAEA,QAAM,oBAAoB,CAAC,UAA4C;AACrE,QAAI,cAAc,MAAM,QAAQ,SAAS,GAAG;AAC1C,YAAM,SAAS,MAAM,cAAc,sBAAsB;AACzD,YAAM,QAAQ,MAAM,QAAQ,CAAC;AAC7B,iBAAW,MAAM,SAAS,MAAM,SAAS,MAAM;AAAA,IACjD;AACA,kBAAc;AAAA,EAChB;AAGA,MAAI,CAAC,UAAW,QAAO;AAGvB,QAAM,mBAAmB;AAAA,IACvB;AAAA,IACA;AAAA,IACA,CAAC,aAAa;AAAA,IACd,CAAC,aAAa;AAAA,EAChB,EACG,OAAO,OAAO,EACd,KAAK,GAAG;AAGX,QAAM,mBAAwC;AAAA,IAC5C,GAAG;AAAA,IACH,UAAU;AAAA,EACZ;AAEA,MAAI,YAAY;AACd,UAAM,QAAQ,MAAM;AACpB,UAAM,QAAQ,MAAM,SAAS;AAC7B,qBAAiB,SAAS,GAAG,gBAAgB;AAC7C,qBAAiB,MAAM,IAAI,MAAM,oBAAoB,CAAC;AACtD,qBAAiB,QAAQ,GAAG,KAAK;AACjC,qBAAiB,OAAO,GAAG,IAAI;AAAA,EACjC,OAAO;AACL,UAAM,SAAS,MAAM;AACrB,UAAM,OAAO,MAAM,UAAU;AAC7B,qBAAiB,QAAQ,GAAG,gBAAgB;AAC5C,qBAAiB,OAAO,IAAI,MAAM,oBAAoB,CAAC;AACvD,qBAAiB,SAAS,GAAG,MAAM;AACnC,qBAAiB,MAAM,GAAG,GAAG;AAAA,EAC/B;AAGA,QAAM,oBAAyC;AAAA,IAC7C,GAAG;AAAA,IACH,UAAU;AAAA,EACZ;AAEA,MAAI,YAAY;AACd,UAAM,QAAQ,MAAM;AACpB,UAAM,OAAO,mBAAmB;AAChC,UAAM,QAAQ,QAAQ;AACtB,sBAAkB,SAAS,GAAG,gBAAgB;AAC9C,sBAAkB,MAAM,IAAI,MAAM,oBAAoB,CAAC;AACvD,sBAAkB,QAAQ,GAAG,KAAK;AAClC,sBAAkB,OAAO,GAAG,IAAI;AAAA,EAClC,OAAO;AACL,UAAM,QAAQ,MAAM;AACpB,UAAM,MAAM,OAAO,QAAQ,eAAe,mBAAmB;AAC7D,UAAM,SAAS,QAAQ,mBAAmB,IAAI;AAC9C,sBAAkB,QAAQ,GAAG,gBAAgB;AAC7C,sBAAkB,OAAO,IAAI,MAAM,oBAAoB,CAAC;AACxD,sBAAkB,SAAS,GAAG,MAAM;AACpC,sBAAkB,MAAM,GAAG,GAAG;AAAA,EAChC;AAGA,QAAM,qBAA0C;AAAA,IAC9C,GAAG;AAAA,IACH,UAAU;AAAA,EACZ;AAEA,MAAI,YAAY;AACd,UAAM,UAAU,MAAM;AACtB,UAAM,OAAO,UAAU;AACvB,uBAAmB,QAAQ,GAAG,gBAAgB;AAC9C,uBAAmB,SAAS;AAC5B,uBAAmB,OAAO,GAAG,IAAI;AAAA,EACnC,OAAO;AACL,UAAM,SAAS,MAAM;AACrB,UAAM,MAAM,SAAS,SAAS;AAC9B,uBAAmB,QAAQ;AAC3B,uBAAmB,SAAS,GAAG,gBAAgB;AAC/C,uBAAmB,MAAM,GAAG,GAAG;AAAA,EACjC;AAEA,SACE,gBAAAI;AAAA,IAAC;AAAA;AAAA,MACC,WAAW;AAAA,MACX,OAAO;AAAA,QACL,GAAG;AAAA,QACH,UAAU;AAAA,QACV,QAAQ,YAAY,YAAY;AAAA,QAChC,aAAa;AAAA,QACb,YAAY;AAAA,MACd;AAAA,MACA,aAAa;AAAA,MACb,aAAa;AAAA,MACb,WAAW;AAAA,MACX,cAAc;AAAA,MACd,cAAc;AAAA,MACd,aAAa;AAAA,MACb,YAAY;AAAA,MACZ,eAAe;AAAA,MAGf;AAAA,wBAAAD,KAAC,SAAI,WAAW,mBAAmB,YAAY,IAAI,OAAO,kBAAkB;AAAA,QAG3E,CAAC,cACA,gBAAAA,KAAC,SAAI,WAAW,oBAAoB,aAAa,IAAI,OAAO,mBAAmB;AAAA,QAIjF,gBAAAA,KAAC,SAAI,WAAW,qBAAqB,cAAc,IAAI,OAAO,oBAAoB;AAAA;AAAA;AAAA,EACpF;AAEJ;;;AC/WA,SAAS,aAAAE,YAAW,cAAAC,mBAAkB;AAoM9B,gBAAAC,YAAA;AA3ID,SAAS,UAAU;AAAA,EACxB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,WAAW;AAAA,EACX,WAAW;AAAA,EACX,WAAW;AAAA,EACX,WAAW;AAAA,EACX,cAAc;AAAA,EACd,YAAY;AAAA,EACZ;AAAA,EACA,2BAA2B;AAAA,EAC3B;AAAA,EACA,mBAAmB;AAAA,EACnB;AAAA,EACA,sBAAsB;AAAA,EACtB;AAAA,EACA,yBAAyB;AAAA,EACzB;AAAA,EACA,uBAAuB;AAAA,EACvB;AAAA,EACA,gBAAgB;AAClB,GAAmB;AAEjB,QAAM,YAAYF,WAAU,QAAQ,CAAC;AACrC,QAAM,UAAUC,YAAW,cAAc,CAAC;AAC1C,QAAM,UAAUA,YAAW,eAAe,CAAC;AAG3C,QAAM,YAAY,cAAc,OAAO,OAAO;AAC9C,QAAM,YAAY,eAAe,OAAO,OAAO;AAG/C,QAAM,eAAe,QAAQ,OAAO,YAAa,SAAS;AAG1D,MAAI,CAAC,UAAW,QAAO;AAGvB,QAAM,iBAAiB,CAAC,YAA+C;AACrE,UAAM,SAAS,WAAW;AAC1B,UAAM,YAAY,WAAW;AAE7B,UAAM,MACH,YAAY,gBAAgB,cAAgB,CAAC,YAAY,gBAAgB,eACtE,UAAU,IACV,WAAW;AAEjB,QAAI,OAAO,OAAQ,QAAO;AAC1B,QAAI,OAAO,UAAW,QAAO;AAC7B,WAAO;AAAA,EACT;AAGA,QAAM,kBAAkB,CAAC,YAA6B;AACpD,QAAI,eAAe,SAAU,QAAO;AACpC,QAAI,eAAe,SAAU,QAAO;AAEpC,UAAM,QAAQ,gBAAgB,WAAW;AACzC,QAAI,SAAS,KAAK,MAAM,WAAW,KAAK;AACxC,QAAI,UAAU,KAAK,WAAW,EAAG,UAAS;AAE1C,UAAM,MACH,YAAY,gBAAgB,cAAgB,CAAC,YAAY,gBAAgB,eACtE,UAAU,IACV,WAAW;AAEjB,WAAO,OAAO;AAAA,EAChB;AAGA,QAAM,kBAAkB,CAAC,YAA4B;AACnD,UAAM,OAAO,eAAe,OAAO;AACnC,UAAM,SAAS,gBAAgB,OAAO;AAEtC,UAAM,UAAU,CAAC,sBAAsB,MAAM,WAAW;AAExD,QAAI,QAAQ;AACV,cAAQ,KAAK,QAAQ;AAErB,UAAI,SAAS,UAAU,uBAAuB,mBAAmB;AAC/D,gBAAQ,KAAK,uBAAuB,gBAAgB;AAAA,MACtD,WAAW,SAAS,aAAa,0BAA0B,mBAAmB;AAC5E,gBAAQ,KAAK,0BAA0B,gBAAgB;AAAA,MACzD,WAAW,SAAS,WAAW,wBAAwB,mBAAmB;AACxE,gBAAQ,KAAK,wBAAwB,gBAAgB;AAAA,MACvD;AAAA,IACF,OAAO;AACL,UAAI,0BAA0B;AAC5B,gBAAQ,KAAK,wBAAwB;AAAA,MACvC;AAAA,IACF;AAEA,WAAO,QAAQ,OAAO,OAAO,EAAE,KAAK,GAAG;AAAA,EACzC;AAGA,QAAM,kBAAkB,CAAC,YAAqD;AAC5E,UAAM,OAAO,eAAe,OAAO;AACnC,UAAM,SAAS,gBAAgB,OAAO;AAEtC,QAAI,QAAQ;AACV,UAAI,SAAS,UAAU,mBAAmB,eAAe;AACvD,eAAO,mBAAmB;AAAA,MAC5B,WAAW,SAAS,aAAa,sBAAsB,eAAe;AACpE,eAAO,sBAAsB;AAAA,MAC/B,WAAW,SAAS,WAAW,oBAAoB,eAAe;AAChE,eAAO,oBAAoB;AAAA,MAC7B;AAAA,IACF,OAAO;AACL,aAAO;AAAA,IACT;AAEA,WAAO;AAAA,EACT;AAGA,QAAM,mBAAmB;AAAA,IACvB;AAAA,IACA;AAAA,IACA,CAAC,aAAa;AAAA,IACd,CAAC,aAAa;AAAA,EAChB,EACG,OAAO,OAAO,EACd,KAAK,GAAG;AAGX,QAAM,iBAAsC;AAAA,IAC1C,GAAG;AAAA,IACH,SAAS;AAAA,IACT,eAAe,gBAAgB,eAAe,QAAQ;AAAA,IACtD,gBAAgB;AAAA,IAChB,YAAY;AAAA,EACd;AAEA,SACE,gBAAAC,KAAC,SAAI,WAAW,kBAAkB,OAAO,gBACtC,gBAAM,KAAK,EAAE,QAAQ,SAAS,GAAG,CAAC,GAAG,YACpC,gBAAAA;AAAA,IAAC;AAAA;AAAA,MAEC,WAAW,gBAAgB,OAAO;AAAA,MAClC,OAAO,gBAAgB,OAAO;AAAA;AAAA,IAFzB;AAAA,EAGP,CACD,GACH;AAEJ;;;AC5MA,SAAS,aAAAC,YAAW,cAAAC,mBAAkB;AAsFlC,SACE,OAAAC,MADF,QAAAC,aAAA;AAvDJ,IAAM,kBAAgD;AAAA,EACpD,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,MAAM;AACR;AAEO,SAAS,YAAY;AAAA,EAC1B;AAAA,EACA;AAAA,EACA;AAAA,EACA,UAAU;AAAA,EACV,eAAe;AAAA,EACf,QAAQ;AAAA,EACR,aAAa;AAAA,EACb,YAAY;AAAA,EACZ,YAAY;AAAA,EACZ;AACF,GAAqB;AAEnB,QAAM,oBAAoBH,WAAU,gBAAgB,CAAC;AACrD,QAAM,UAAUC,YAAW,eAAe,CAAC;AAG3C,QAAM,YAAY,eAAe,OAAO,OAAO;AAG/C,MAAI,CAAC,UAAW,QAAO;AAGvB,QAAM,kBAAkB,gBAAgB,OAAO,oBAAqB,YAAY;AAChF,QAAM,kBAAmB,kBAAkB,QAAS;AAGpD,QAAM,mBAAmB,CAAC,gBAAgB,SAAS,EAAE,OAAO,OAAO,EAAE,KAAK,GAAG;AAG7E,QAAM,eAAoC;AAAA,IACxC,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,cAAc;AAAA,IACd,QAAQ,GAAG,SAAS,YAAY,UAAU;AAAA,IAC1C,gBAAgB;AAAA,IAChB,WAAW;AAAA,EACb;AAEA,MAAI,SAAS;AAEX,iBAAa,YAAY,uBAAuB,gBAAgB,YAAY,CAAC;AAAA,EAC/E,OAAO;AAEL,iBAAa,YAAY,UAAU,eAAe;AAClD,iBAAa,aAAa;AAAA,EAC5B;AAEA,SACE,gBAAAE,MAAC,SAAI,WAAW,kBAAkB,OAChC;AAAA,oBAAAD,KAAC,SAAI,OAAO,cAAc;AAAA,IAC1B,gBAAAA,KAAC,WAAO;AAAA;AAAA;AAAA;AAAA,SAIN;AAAA,KACJ;AAEJ;","names":["useDigital","jsx","jsxs","useState","useRef","useDigital","useJoinsStore","jsx","jsxs","useAnalog","useDigital","jsx","useAnalog","useDigital","jsx","jsxs"]}
1
+ {"version":3,"sources":["../src/components/CrisButton.tsx","../src/utils/icons.ts","../src/components/CrisText.tsx","../src/components/CrisSlider.tsx","../src/components/CrisGauge.tsx","../src/components/CrisSpinner.tsx"],"sourcesContent":["import { useState, useRef, ReactNode } from 'react';\r\nimport { useDigital, useJoinsStore } from '@imperosoft/cris-webui-core';\r\nimport { getIconUrl, getIconFilter } from '../utils/icons';\r\n\r\n// Global touch state - shared across all CrisButton instances\r\n// This prevents phantom clicks when buttons swap visibility during touch\r\nlet globalTouchActive = false;\r\n\r\nexport interface CrisButtonProps {\r\n /** Digital join for press action */\r\n join?: number;\r\n /** Digital join for feedback (defaults to join) */\r\n joinFeedback?: number;\r\n /** Digital join for enable state */\r\n joinEnable?: number;\r\n /** Digital join for visibility */\r\n joinVisible?: number;\r\n\r\n /** Button text */\r\n text?: string;\r\n /** Text when pressed (local feedback) */\r\n textPressed?: string;\r\n /** Text when selected (controller feedback) */\r\n textSelected?: string;\r\n\r\n /** Icon as ReactNode (for custom SVG components) */\r\n icon?: ReactNode;\r\n /** Icon name (loads SVG from configured path, e.g., 'motor-stop') */\r\n iconName?: string;\r\n /** Icon CSS class (for CSS-based icons, e.g., 'ico-motor-stop') */\r\n iconClass?: string;\r\n /** Icon size - number (px), string ('50%', '2rem'), or preset ('xs'|'sm'|'md'|'lg'|'xl') */\r\n iconSize?: number | string | 'xs' | 'sm' | 'md' | 'lg' | 'xl';\r\n /** Icon container size as percentage (default: 80% for top/bottom, 20% for left/right) */\r\n iconContainerSize?: string;\r\n /** Icon inline style */\r\n iconStyle?: React.CSSProperties;\r\n /** Icon position relative to text */\r\n iconPosition?: 'left' | 'right' | 'top' | 'bottom';\r\n\r\n /** Icon name when active (overrides iconName when button is active) */\r\n iconNameActive?: string;\r\n /** Icon CSS class when active (overrides iconClass when button is active) */\r\n iconClassActive?: string;\r\n /** Icon inline style when active (merged with iconStyle when button is active) */\r\n iconStyleActive?: React.CSSProperties;\r\n\r\n /** Show controller feedback styling */\r\n showControlFeedback?: boolean;\r\n /** Show local press feedback styling */\r\n showLocalFeedback?: boolean;\r\n /** Suppress click actions (display only) */\r\n suppressKeyClicks?: boolean;\r\n\r\n /** Smart object ID (for smarts instead of joins) */\r\n smartId?: number;\r\n\r\n /** Custom class names */\r\n className?: string;\r\n /** Class when active (controller feedback) */\r\n classActive?: string;\r\n /** Class when pressed (local feedback) */\r\n classPressed?: string;\r\n /** Class when disabled */\r\n classDisabled?: string;\r\n\r\n /** Children content */\r\n children?: ReactNode;\r\n\r\n /** Custom click handler (called on press) */\r\n onPress?: () => void;\r\n /** Custom release handler */\r\n onRelease?: () => void;\r\n}\r\n\r\nexport function CrisButton({\r\n join,\r\n joinFeedback,\r\n joinEnable,\r\n joinVisible,\r\n text,\r\n textPressed,\r\n textSelected,\r\n icon,\r\n iconName,\r\n iconClass,\r\n iconSize,\r\n iconContainerSize,\r\n iconStyle,\r\n iconPosition = 'top',\r\n iconNameActive,\r\n iconClassActive,\r\n iconStyleActive,\r\n showControlFeedback = true,\r\n showLocalFeedback = true,\r\n suppressKeyClicks = false,\r\n smartId,\r\n className = '',\r\n classActive = '',\r\n classPressed = '',\r\n classDisabled = '',\r\n children,\r\n onPress,\r\n onRelease,\r\n}: CrisButtonProps) {\r\n const [pressed, setPressed] = useState(false);\r\n const [hovering, setHovering] = useState(false);\r\n const pressedRef = useRef(false);\r\n const touchingRef = useRef(false);\r\n const touchStartedHereRef = useRef(false);\r\n\r\n // Get join values reactively\r\n const feedbackJoin = joinFeedback ?? join;\r\n const feedback = useDigital(feedbackJoin ?? 0);\r\n const enabled = useDigital(joinEnable ?? 0);\r\n const visible = useDigital(joinVisible ?? 0);\r\n\r\n // Get action method\r\n const dSet = useJoinsStore((state) => state.dSet);\r\n\r\n // Determine if button is enabled\r\n const isEnabled = joinEnable == null ? true : enabled;\r\n\r\n // Determine if button is visible\r\n const isVisible = joinVisible == null ? true : visible;\r\n\r\n // Determine current feedback state\r\n const hasControlFeedback = showControlFeedback && feedbackJoin != null && feedback;\r\n const hasPressedFeedback = showLocalFeedback && pressed && isEnabled;\r\n const isActive = hasControlFeedback || hasPressedFeedback;\r\n\r\n // Determine current text\r\n let currentText = text ?? '';\r\n if (hasPressedFeedback && textPressed != null) {\r\n currentText = textPressed;\r\n } else if (hasControlFeedback && textSelected != null) {\r\n currentText = textSelected;\r\n }\r\n\r\n // Event handlers\r\n const handlePress = () => {\r\n if (suppressKeyClicks) return;\r\n if (pressedRef.current) return;\r\n\r\n pressedRef.current = true;\r\n setPressed(true);\r\n\r\n if (!isEnabled) return;\r\n\r\n // Custom handler\r\n onPress?.();\r\n\r\n // Send to controller\r\n if (join != null && smartId == null) {\r\n dSet(join, true);\r\n }\r\n // TODO: Add smartId support when smarts are implemented in core\r\n };\r\n\r\n const handleRelease = () => {\r\n if (suppressKeyClicks) return;\r\n if (!pressedRef.current) return;\r\n\r\n pressedRef.current = false;\r\n setPressed(false);\r\n\r\n if (!isEnabled) return;\r\n\r\n // Custom handler\r\n onRelease?.();\r\n\r\n // Send to controller\r\n if (join != null && smartId == null) {\r\n dSet(join, false);\r\n }\r\n // TODO: Add smartId support when smarts are implemented in core\r\n };\r\n\r\n // Touch event handlers - set global touching flag to prevent mouse events from firing\r\n // Also track if touch started on THIS button to prevent phantom clicks\r\n // when buttons swap visibility (e.g., back button hides, power button appears)\r\n const handleTouchStart = () => {\r\n globalTouchActive = true;\r\n touchingRef.current = true;\r\n touchStartedHereRef.current = true;\r\n handlePress();\r\n };\r\n\r\n const handleTouchEnd = () => {\r\n globalTouchActive = true;\r\n touchingRef.current = true;\r\n // Only fire release if touch actually started on this button\r\n if (touchStartedHereRef.current) {\r\n touchStartedHereRef.current = false;\r\n handleRelease();\r\n }\r\n // Reset global touch flag after a short delay to block synthetic click\r\n setTimeout(() => {\r\n globalTouchActive = false;\r\n }, 300);\r\n };\r\n\r\n const handleTouchCancel = () => {\r\n globalTouchActive = true;\r\n touchingRef.current = true;\r\n touchStartedHereRef.current = false;\r\n handleRelease();\r\n // Reset global touch flag after a short delay\r\n setTimeout(() => {\r\n globalTouchActive = false;\r\n }, 300);\r\n };\r\n\r\n // Mouse event handlers - skip if any touch event is active (global check)\r\n const handleMouseDown = () => {\r\n if (globalTouchActive || touchingRef.current) return;\r\n handlePress();\r\n };\r\n\r\n const handleMouseUp = () => {\r\n if (globalTouchActive || touchingRef.current) return;\r\n handleRelease();\r\n };\r\n\r\n const handleMouseEnter = () => {\r\n if (globalTouchActive || touchingRef.current) return;\r\n if (suppressKeyClicks) return;\r\n setHovering(true);\r\n };\r\n\r\n const handleMouseLeave = () => {\r\n if (globalTouchActive || touchingRef.current) return;\r\n setHovering(false);\r\n handleRelease();\r\n };\r\n\r\n // Don't render if not visible\r\n if (!isVisible) return null;\r\n\r\n // Build class names\r\n const classes = [\r\n 'cris-button',\r\n className,\r\n hasControlFeedback && classActive,\r\n hasPressedFeedback && classPressed,\r\n !isEnabled && classDisabled,\r\n hasControlFeedback && 'active',\r\n hasPressedFeedback && 'pressed',\r\n !isEnabled && 'disabled',\r\n suppressKeyClicks && 'no-hover',\r\n ]\r\n .filter(Boolean)\r\n .join(' ');\r\n\r\n // Flex direction based on icon position\r\n const flexDirection =\r\n iconPosition === 'top'\r\n ? 'flex-col'\r\n : iconPosition === 'bottom'\r\n ? 'flex-col-reverse'\r\n : iconPosition === 'left'\r\n ? 'flex-row'\r\n : 'flex-row-reverse';\r\n\r\n // Determine if we have any icon to show\r\n const hasIcon = icon != null || iconName != null || iconClass != null || iconNameActive != null || iconClassActive != null;\r\n\r\n // Determine current icon properties based on active state\r\n const currentIconName = isActive && iconNameActive != null ? iconNameActive : iconName;\r\n const currentIconClass = isActive && iconClassActive != null ? iconClassActive : iconClass;\r\n const currentIconStyle = isActive && iconStyleActive != null ? { ...iconStyle, ...iconStyleActive } : iconStyle;\r\n\r\n // Size presets mapping\r\n const sizePresets: Record<string, string> = {\r\n xs: '16px',\r\n sm: '24px',\r\n md: '32px',\r\n lg: '48px',\r\n xl: '64px',\r\n };\r\n\r\n // Calculate icon size value\r\n const getIconSizeValue = (): string | undefined => {\r\n if (iconSize == null) return undefined;\r\n if (typeof iconSize === 'number') return `${iconSize}px`;\r\n if (iconSize in sizePresets) return sizePresets[iconSize];\r\n return iconSize; // Already a string like '50%' or '2rem'\r\n };\r\n\r\n const iconSizeValue = getIconSizeValue();\r\n\r\n // Build icon classes\r\n const iconClasses = [\r\n 'cris-button-icon',\r\n currentIconClass,\r\n hasControlFeedback && 'active',\r\n hasPressedFeedback && 'pressed',\r\n ]\r\n .filter(Boolean)\r\n .join(' ');\r\n\r\n // Build icon style with size and filter support\r\n const computedIconStyle: React.CSSProperties = {\r\n ...currentIconStyle,\r\n };\r\n\r\n // Apply size\r\n if (iconSizeValue) {\r\n computedIconStyle.width = iconSizeValue;\r\n computedIconStyle.height = iconSizeValue;\r\n }\r\n\r\n // Apply filter from config if using iconName (only if not overridden by iconStyle/iconStyleActive)\r\n if (currentIconName && !currentIconStyle?.filter) {\r\n const filter = getIconFilter(isActive);\r\n if (filter) {\r\n computedIconStyle.filter = filter;\r\n }\r\n }\r\n\r\n // Render icon element\r\n const renderIconElement = () => {\r\n if (icon) {\r\n // ReactNode icon (custom component)\r\n return <span className={iconClasses} style={computedIconStyle}>{icon}</span>;\r\n }\r\n\r\n if (currentIconName) {\r\n // Icon by name (loads from configured path)\r\n return (\r\n <img\r\n className={iconClasses}\r\n style={computedIconStyle}\r\n src={getIconUrl(currentIconName)}\r\n alt=\"\"\r\n />\r\n );\r\n }\r\n\r\n if (currentIconClass) {\r\n // CSS-based icon (like Angular version)\r\n return <img className={iconClasses} style={computedIconStyle} alt=\"\" />;\r\n }\r\n\r\n return null;\r\n };\r\n\r\n // Calculate icon container size (default: 100% if no text, otherwise 80% for top/bottom, 20% for left/right)\r\n const isVertical = iconPosition === 'top' || iconPosition === 'bottom';\r\n const hasText = currentText !== '';\r\n const defaultContainerSize = hasText ? (isVertical ? '80%' : '20%') : '100%';\r\n const containerSize = iconContainerSize ?? defaultContainerSize;\r\n\r\n // Calculate text container size (remaining space)\r\n const textContainerSize = `calc(100% - ${containerSize})`;\r\n\r\n // Container style for icon area\r\n const iconContainerStyle: React.CSSProperties = isVertical\r\n ? { height: containerSize, width: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center' }\r\n : { width: containerSize, height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center' };\r\n\r\n // Container style for text area\r\n const textContainerStyle: React.CSSProperties = isVertical\r\n ? { height: textContainerSize, width: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center' }\r\n : { width: textContainerSize, height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center' };\r\n\r\n // Check if button has 'free' class (absolute positioning)\r\n const isFreePositioned = className.includes('free');\r\n\r\n return (\r\n <div\r\n className={`${classes} flex items-center justify-center ${flexDirection}`}\r\n style={{\r\n cursor: suppressKeyClicks ? 'default' : 'pointer',\r\n position: isFreePositioned ? undefined : 'relative',\r\n overflow: isFreePositioned ? undefined : 'hidden',\r\n }}\r\n onMouseDown={handleMouseDown}\r\n onMouseUp={handleMouseUp}\r\n onMouseEnter={handleMouseEnter}\r\n onMouseLeave={handleMouseLeave}\r\n onTouchStart={handleTouchStart}\r\n onTouchEnd={handleTouchEnd}\r\n onTouchCancel={handleTouchCancel}\r\n >\r\n {/* Hover overlay - inline styles for touch-safe behavior */}\r\n <div\r\n className=\"cris-button-hover-overlay\"\r\n style={{\r\n position: 'absolute',\r\n inset: 0,\r\n backgroundColor: 'black',\r\n opacity: hovering ? 0.1 : 0,\r\n transition: 'opacity 0.2s',\r\n pointerEvents: 'none',\r\n borderRadius: 'inherit',\r\n }}\r\n />\r\n {children}\r\n {hasIcon && (\r\n <div className=\"cris-button-icon-container\" style={iconContainerStyle}>\r\n {renderIconElement()}\r\n </div>\r\n )}\r\n {currentText && (\r\n <div className=\"cris-button-text-container\" style={textContainerStyle}>\r\n <span className=\"cris-button-text\">{currentText}</span>\r\n </div>\r\n )}\r\n </div>\r\n );\r\n}\r\n","/**\r\n * Icon configuration and utilities for CRIS components\r\n *\r\n * Supports different base paths for dev vs production (Crestron controller)\r\n */\r\n\r\nexport interface IconConfig {\r\n /** Base path for icon assets (e.g., '/assets/icons/' or 'http://controller/icons/') */\r\n basePath: string;\r\n /** File extension (default: 'svg') */\r\n extension: string;\r\n /** Default filter for inactive state */\r\n defaultFilter?: string;\r\n /** Default filter for active state */\r\n activeFilter?: string;\r\n}\r\n\r\n// Default configuration\r\nlet iconConfig: IconConfig = {\r\n basePath: '/assets/icons/',\r\n extension: 'svg',\r\n defaultFilter: undefined,\r\n activeFilter: undefined,\r\n};\r\n\r\n/**\r\n * Configure icon paths and defaults\r\n * Call this once at app startup\r\n *\r\n * @example\r\n * // Development\r\n * configureIcons({ basePath: '/assets/icons/' });\r\n *\r\n * // Production on Crestron controller\r\n * configureIcons({ basePath: '/html/icons/' });\r\n *\r\n * // With color filters\r\n * configureIcons({\r\n * basePath: '/assets/icons/',\r\n * defaultFilter: 'brightness(0) invert(1)', // white\r\n * activeFilter: 'brightness(0) invert(0.5) sepia(1) saturate(5) hue-rotate(0deg)', // red\r\n * });\r\n */\r\nexport function configureIcons(config: Partial<IconConfig>): void {\r\n iconConfig = { ...iconConfig, ...config };\r\n}\r\n\r\n/**\r\n * Get current icon configuration\r\n */\r\nexport function getIconConfig(): IconConfig {\r\n return iconConfig;\r\n}\r\n\r\n/**\r\n * Get full URL for an icon\r\n *\r\n * @param name - Icon name (without path or extension), e.g., 'motor-stop'\r\n * @returns Full URL to the icon\r\n */\r\nexport function getIconUrl(name: string): string {\r\n if (!name) return '';\r\n\r\n // If already a full URL or path, return as-is\r\n if (name.startsWith('http') || name.startsWith('/') || name.startsWith('.')) {\r\n return name;\r\n }\r\n\r\n const { basePath, extension } = iconConfig;\r\n const normalizedBase = basePath.endsWith('/') ? basePath : `${basePath}/`;\r\n return `${normalizedBase}${name}.${extension}`;\r\n}\r\n\r\n/**\r\n * Get CSS filter for icon state\r\n */\r\nexport function getIconFilter(active: boolean): string | undefined {\r\n return active ? iconConfig.activeFilter : iconConfig.defaultFilter;\r\n}\r\n","import { ReactNode } from 'react';\r\nimport { useDigital, useSerial, useCipDecode } from '@imperosoft/cris-webui-core';\r\n\r\nexport interface CrisTextProps {\r\n /** Serial join for indirect text */\r\n joinIndirectText?: number;\r\n /** Digital join for enable state */\r\n joinEnable?: number;\r\n /** Digital join for visibility */\r\n joinVisible?: number;\r\n /** Static text (used if no joinIndirectText) */\r\n text?: string;\r\n /** Container CSS class */\r\n className?: string;\r\n /** Container inline style */\r\n style?: React.CSSProperties;\r\n /** Text element CSS class */\r\n textClassName?: string;\r\n /** Text element inline style */\r\n textStyle?: React.CSSProperties;\r\n /** Class when disabled */\r\n classDisabled?: string;\r\n /** Children content */\r\n children?: ReactNode;\r\n}\r\n\r\nexport function CrisText({\r\n joinIndirectText,\r\n joinEnable,\r\n joinVisible,\r\n text,\r\n className = '',\r\n style,\r\n textClassName = '',\r\n textStyle,\r\n classDisabled = '',\r\n children,\r\n}: CrisTextProps) {\r\n // Get join values reactively\r\n const indirectText = useSerial(joinIndirectText ?? 0);\r\n const enabled = useDigital(joinEnable ?? 0);\r\n const visible = useDigital(joinVisible ?? 0);\r\n\r\n // Determine if text is enabled\r\n const isEnabled = joinEnable == null ? true : enabled;\r\n\r\n // Determine if text is visible\r\n const isVisible = joinVisible == null ? true : visible;\r\n\r\n // Get the raw text - indirect text or static text\r\n const rawText = joinIndirectText != null ? indirectText : (text ?? '');\r\n\r\n // Decode CIP patterns in the text\r\n const currentText = useCipDecode(rawText);\r\n\r\n // Don't render if not visible\r\n if (!isVisible) return null;\r\n\r\n // Build class names\r\n const containerClasses = [\r\n 'cris-text',\r\n className,\r\n !isEnabled && classDisabled,\r\n !isEnabled && 'disabled',\r\n ]\r\n .filter(Boolean)\r\n .join(' ');\r\n\r\n return (\r\n <div className={containerClasses} style={style}>\r\n {children}\r\n {currentText && (\r\n <span className={`cris-text-text ${textClassName}`} style={textStyle}>\r\n {currentText}\r\n </span>\r\n )}\r\n </div>\r\n );\r\n}\r\n","import { useState, useRef, useEffect, useCallback } from 'react';\r\nimport { useDigital, useAnalog, useJoinsStore } from '@imperosoft/cris-webui-core';\r\n\r\nexport interface CrisSliderProps {\r\n /** Analog join for value (shared with digital if joinDigital not set) */\r\n join?: number;\r\n /** Digital join for press/release feedback */\r\n joinDigital?: number;\r\n /** Analog join for value (overrides join) */\r\n joinAnalog?: number;\r\n /** Digital join for enable state */\r\n joinEnable?: number;\r\n /** Digital join for visibility */\r\n joinVisible?: number;\r\n\r\n /** Minimum value (default 0) */\r\n minValue?: number;\r\n /** Maximum value (default 65535) */\r\n maxValue?: number;\r\n /** Horizontal orientation (default false = vertical) */\r\n horizontal?: boolean;\r\n /** Hide fill bar */\r\n fillHidden?: boolean;\r\n /** Track size as percentage of container (default 20) */\r\n trackSizePercent?: number;\r\n /** Thumb size as percentage of container (default 4) */\r\n thumbSizePercent?: number;\r\n /** Delay in ms after drag before updating from feedback (default 1000) */\r\n delayMsAfterDragUpdateFeedback?: number;\r\n\r\n /** Container CSS class */\r\n className?: string;\r\n /** Container inline style */\r\n style?: React.CSSProperties;\r\n /** Bar/track CSS class */\r\n barClassName?: string;\r\n /** Bar/track inline style */\r\n barStyle?: React.CSSProperties;\r\n /** Fill CSS class */\r\n fillClassName?: string;\r\n /** Fill inline style */\r\n fillStyle?: React.CSSProperties;\r\n /** Thumb CSS class */\r\n thumbClassName?: string;\r\n /** Thumb inline style */\r\n thumbStyle?: React.CSSProperties;\r\n /** Class when disabled */\r\n classDisabled?: string;\r\n}\r\n\r\nexport function CrisSlider({\r\n join,\r\n joinDigital,\r\n joinAnalog,\r\n joinEnable,\r\n joinVisible,\r\n minValue = 0,\r\n maxValue = 65535,\r\n horizontal = false,\r\n fillHidden = false,\r\n trackSizePercent = 20,\r\n thumbSizePercent = 4,\r\n delayMsAfterDragUpdateFeedback = 1000,\r\n className = '',\r\n style,\r\n barClassName = '',\r\n barStyle,\r\n fillClassName = '',\r\n fillStyle,\r\n thumbClassName = '',\r\n thumbStyle,\r\n classDisabled = '',\r\n}: CrisSliderProps) {\r\n // Effective joins\r\n const effectiveAnalogJoin = joinAnalog ?? join;\r\n const effectiveDigitalJoin = joinDigital ?? join;\r\n\r\n // Get join values reactively\r\n const analogValue = useAnalog(effectiveAnalogJoin ?? 0);\r\n const enabled = useDigital(joinEnable ?? 0);\r\n const visible = useDigital(joinVisible ?? 0);\r\n\r\n // Action methods\r\n const dSet = useJoinsStore((state) => state.dSet);\r\n const aSet = useJoinsStore((state) => state.aSet);\r\n\r\n // Local state\r\n const [ratioCurrent, setRatioCurrent] = useState(0);\r\n const [isDragging, setIsDragging] = useState(false);\r\n const isDraggingOrJustAfterRef = useRef(false);\r\n const ratioBeforeDragRef = useRef(0);\r\n const afterDragTimeoutRef = useRef<number | null>(null);\r\n const touchingRef = useRef(false);\r\n\r\n // Determine if enabled and visible\r\n const isEnabled = joinEnable == null ? true : enabled;\r\n const isVisible = joinVisible == null ? true : visible;\r\n\r\n // Convert analog value to ratio\r\n const analogToRatio = useCallback(\r\n (value: number) => {\r\n const range = maxValue - minValue;\r\n if (range <= 0) return 0;\r\n return Math.max(0, Math.min(1, (value - minValue) / range));\r\n },\r\n [minValue, maxValue]\r\n );\r\n\r\n // Convert ratio to analog value\r\n const ratioToAnalog = useCallback(\r\n (ratio: number) => {\r\n const range = maxValue - minValue;\r\n return Math.round(minValue + ratio * range);\r\n },\r\n [minValue, maxValue]\r\n );\r\n\r\n // Update ratio from controller feedback\r\n const updateFromFeedback = useCallback(() => {\r\n if (!isDraggingOrJustAfterRef.current && effectiveAnalogJoin != null) {\r\n setRatioCurrent(analogToRatio(analogValue));\r\n }\r\n }, [analogValue, analogToRatio, effectiveAnalogJoin]);\r\n\r\n // Update from feedback when analog value changes\r\n useEffect(() => {\r\n updateFromFeedback();\r\n }, [updateFromFeedback]);\r\n\r\n // Handle drag start\r\n const handleDragStart = useCallback(() => {\r\n if (!isEnabled) return;\r\n\r\n setIsDragging(true);\r\n\r\n // Send digital press\r\n if (effectiveDigitalJoin != null) {\r\n dSet(effectiveDigitalJoin, true);\r\n }\r\n\r\n // Save ratio before drag\r\n if (!isDraggingOrJustAfterRef.current) {\r\n ratioBeforeDragRef.current = ratioCurrent;\r\n }\r\n isDraggingOrJustAfterRef.current = true;\r\n\r\n // Clear any pending timeout\r\n if (afterDragTimeoutRef.current !== null) {\r\n window.clearTimeout(afterDragTimeoutRef.current);\r\n afterDragTimeoutRef.current = null;\r\n }\r\n }, [isEnabled, effectiveDigitalJoin, dSet, ratioCurrent]);\r\n\r\n // Handle drag end\r\n const handleDragEnd = useCallback(() => {\r\n if (!isDragging) return;\r\n\r\n setIsDragging(false);\r\n\r\n // Send digital release\r\n if (effectiveDigitalJoin != null) {\r\n dSet(effectiveDigitalJoin, false);\r\n }\r\n\r\n // Schedule feedback update after delay\r\n if (delayMsAfterDragUpdateFeedback > 0) {\r\n afterDragTimeoutRef.current = window.setTimeout(() => {\r\n isDraggingOrJustAfterRef.current = false;\r\n updateFromFeedback();\r\n afterDragTimeoutRef.current = null;\r\n }, delayMsAfterDragUpdateFeedback);\r\n } else {\r\n isDraggingOrJustAfterRef.current = false;\r\n updateFromFeedback();\r\n }\r\n }, [isDragging, effectiveDigitalJoin, dSet, delayMsAfterDragUpdateFeedback, updateFromFeedback]);\r\n\r\n // Handle move/drag\r\n const handleMove = useCallback(\r\n (clientX: number, clientY: number, bounds: DOMRect) => {\r\n if (!isDragging) return;\r\n\r\n let newRatio: number;\r\n if (horizontal) {\r\n newRatio = (clientX - bounds.left) / bounds.width;\r\n } else {\r\n newRatio = 1 - (clientY - bounds.top) / bounds.height;\r\n }\r\n newRatio = Math.max(0, Math.min(1, newRatio));\r\n\r\n setRatioCurrent(newRatio);\r\n\r\n // Send analog value\r\n if (effectiveAnalogJoin != null) {\r\n aSet(effectiveAnalogJoin, ratioToAnalog(newRatio));\r\n }\r\n },\r\n [isDragging, horizontal, effectiveAnalogJoin, aSet, ratioToAnalog]\r\n );\r\n\r\n // Mouse handlers\r\n const handleMouseDown = (event: React.MouseEvent<HTMLDivElement>) => {\r\n if (touchingRef.current) return;\r\n if (event.button !== 0) return;\r\n\r\n handleDragStart();\r\n const bounds = event.currentTarget.getBoundingClientRect();\r\n handleMove(event.clientX, event.clientY, bounds);\r\n };\r\n\r\n const handleMouseMove = (event: React.MouseEvent<HTMLDivElement>) => {\r\n if (!isDragging) return;\r\n const bounds = event.currentTarget.getBoundingClientRect();\r\n handleMove(event.clientX, event.clientY, bounds);\r\n };\r\n\r\n const handleMouseUp = () => {\r\n handleDragEnd();\r\n };\r\n\r\n const handleMouseLeave = (event: React.MouseEvent<HTMLDivElement>) => {\r\n if (isDragging) {\r\n const bounds = event.currentTarget.getBoundingClientRect();\r\n handleMove(event.clientX, event.clientY, bounds);\r\n }\r\n handleDragEnd();\r\n };\r\n\r\n // Touch handlers\r\n const handleTouchStart = (event: React.TouchEvent<HTMLDivElement>) => {\r\n touchingRef.current = true;\r\n handleDragStart();\r\n const bounds = event.currentTarget.getBoundingClientRect();\r\n const touch = event.touches[0];\r\n handleMove(touch.clientX, touch.clientY, bounds);\r\n };\r\n\r\n const handleTouchMove = (event: React.TouchEvent<HTMLDivElement>) => {\r\n if (!isDragging) return;\r\n const bounds = event.currentTarget.getBoundingClientRect();\r\n const touch = event.touches[0];\r\n handleMove(touch.clientX, touch.clientY, bounds);\r\n };\r\n\r\n const handleTouchEnd = () => {\r\n handleDragEnd();\r\n };\r\n\r\n const handleTouchCancel = (event: React.TouchEvent<HTMLDivElement>) => {\r\n if (isDragging && event.touches.length > 0) {\r\n const bounds = event.currentTarget.getBoundingClientRect();\r\n const touch = event.touches[0];\r\n handleMove(touch.clientX, touch.clientY, bounds);\r\n }\r\n handleDragEnd();\r\n };\r\n\r\n // Don't render if not visible\r\n if (!isVisible) return null;\r\n\r\n // Build class names\r\n const containerClasses = [\r\n 'cris-slider',\r\n className,\r\n !isEnabled && classDisabled,\r\n !isEnabled && 'disabled',\r\n ]\r\n .filter(Boolean)\r\n .join(' ');\r\n\r\n // Calculate bar style\r\n const computedBarStyle: React.CSSProperties = {\r\n ...barStyle,\r\n position: 'absolute',\r\n };\r\n\r\n if (horizontal) {\r\n const width = 100 - thumbSizePercent;\r\n const left = (100 - width) / 2;\r\n computedBarStyle.height = `${trackSizePercent}%`;\r\n computedBarStyle.top = `${(100 - trackSizePercent) / 2}%`;\r\n computedBarStyle.width = `${width}%`;\r\n computedBarStyle.left = `${left}%`;\r\n } else {\r\n const height = 100 - thumbSizePercent;\r\n const top = (100 - height) / 2;\r\n computedBarStyle.width = `${trackSizePercent}%`;\r\n computedBarStyle.left = `${(100 - trackSizePercent) / 2}%`;\r\n computedBarStyle.height = `${height}%`;\r\n computedBarStyle.top = `${top}%`;\r\n }\r\n\r\n // Calculate fill style\r\n const computedFillStyle: React.CSSProperties = {\r\n ...fillStyle,\r\n position: 'absolute',\r\n };\r\n\r\n if (horizontal) {\r\n const range = 100 - thumbSizePercent;\r\n const left = thumbSizePercent / 2;\r\n const width = range * ratioCurrent;\r\n computedFillStyle.height = `${trackSizePercent}%`;\r\n computedFillStyle.top = `${(100 - trackSizePercent) / 2}%`;\r\n computedFillStyle.width = `${width}%`;\r\n computedFillStyle.left = `${left}%`;\r\n } else {\r\n const range = 100 - thumbSizePercent;\r\n const top = 100 - (range * ratioCurrent + thumbSizePercent / 2);\r\n const height = range + thumbSizePercent / 2 - top;\r\n computedFillStyle.width = `${trackSizePercent}%`;\r\n computedFillStyle.left = `${(100 - trackSizePercent) / 2}%`;\r\n computedFillStyle.height = `${height}%`;\r\n computedFillStyle.top = `${top}%`;\r\n }\r\n\r\n // Calculate thumb style\r\n const computedThumbStyle: React.CSSProperties = {\r\n ...thumbStyle,\r\n position: 'absolute',\r\n };\r\n\r\n if (horizontal) {\r\n const maxLeft = 100 - thumbSizePercent;\r\n const left = maxLeft * ratioCurrent;\r\n computedThumbStyle.width = `${thumbSizePercent}%`;\r\n computedThumbStyle.height = '100%';\r\n computedThumbStyle.left = `${left}%`;\r\n } else {\r\n const maxTop = 100 - thumbSizePercent;\r\n const top = maxTop - maxTop * ratioCurrent;\r\n computedThumbStyle.width = '100%';\r\n computedThumbStyle.height = `${thumbSizePercent}%`;\r\n computedThumbStyle.top = `${top}%`;\r\n }\r\n\r\n return (\r\n <div\r\n className={containerClasses}\r\n style={{\r\n ...style,\r\n position: 'relative',\r\n cursor: isEnabled ? 'pointer' : 'default',\r\n touchAction: 'none',\r\n userSelect: 'none',\r\n }}\r\n onMouseDown={handleMouseDown}\r\n onMouseMove={handleMouseMove}\r\n onMouseUp={handleMouseUp}\r\n onMouseLeave={handleMouseLeave}\r\n onTouchStart={handleTouchStart}\r\n onTouchMove={handleTouchMove}\r\n onTouchEnd={handleTouchEnd}\r\n onTouchCancel={handleTouchCancel}\r\n >\r\n {/* Bar/Track */}\r\n <div className={`cris-slider-bar ${barClassName}`} style={computedBarStyle} />\r\n\r\n {/* Fill */}\r\n {!fillHidden && (\r\n <div className={`cris-slider-fill ${fillClassName}`} style={computedFillStyle} />\r\n )}\r\n\r\n {/* Thumb */}\r\n <div className={`cris-slider-thumb ${thumbClassName}`} style={computedThumbStyle} />\r\n </div>\r\n );\r\n}\r\n","import { useAnalog, useDigital } from '@imperosoft/cris-webui-core';\r\n\r\nexport interface CrisGaugeProps {\r\n /** Static value (used if no join) */\r\n value?: number;\r\n /** Analog join for value */\r\n join?: number;\r\n /** Digital join for enable state */\r\n joinEnable?: number;\r\n /** Digital join for visibility */\r\n joinVisible?: number;\r\n\r\n /** Minimum value (default 0) */\r\n minValue?: number;\r\n /** Maximum value (default 65535) */\r\n maxValue?: number;\r\n /** Number of segments (default 20) */\r\n segments?: number;\r\n /** Invert direction (default false) */\r\n inverted?: boolean;\r\n /** Orientation (default vertical) */\r\n orientation?: 'vertical' | 'horizontal';\r\n\r\n /** Container CSS class */\r\n className?: string;\r\n /** Container inline style */\r\n style?: React.CSSProperties;\r\n\r\n /** Inactive segment CSS class */\r\n inactiveSegmentClassName?: string;\r\n /** Inactive segment inline style */\r\n inactiveSegmentStyle?: React.CSSProperties;\r\n\r\n /** Active segment CSS class (all levels) */\r\n segmentClassName?: string;\r\n /** Active segment inline style (all levels) */\r\n segmentStyle?: React.CSSProperties;\r\n\r\n /** Low level segment CSS class (0-60%) */\r\n lowSegmentClassName?: string;\r\n /** Low level segment inline style */\r\n lowSegmentStyle?: React.CSSProperties;\r\n\r\n /** Medium level segment CSS class (60-80%) */\r\n mediumSegmentClassName?: string;\r\n /** Medium level segment inline style */\r\n mediumSegmentStyle?: React.CSSProperties;\r\n\r\n /** High level segment CSS class (80-100%) */\r\n highSegmentClassName?: string;\r\n /** High level segment inline style */\r\n highSegmentStyle?: React.CSSProperties;\r\n\r\n /** Class when disabled */\r\n classDisabled?: string;\r\n}\r\n\r\nexport function CrisGauge({\r\n value,\r\n join,\r\n joinEnable,\r\n joinVisible,\r\n minValue = 0,\r\n maxValue = 65535,\r\n segments = 20,\r\n inverted = false,\r\n orientation = 'vertical',\r\n className = '',\r\n style,\r\n inactiveSegmentClassName = '',\r\n inactiveSegmentStyle,\r\n segmentClassName = '',\r\n segmentStyle,\r\n lowSegmentClassName = '',\r\n lowSegmentStyle,\r\n mediumSegmentClassName = '',\r\n mediumSegmentStyle,\r\n highSegmentClassName = '',\r\n highSegmentStyle,\r\n classDisabled = '',\r\n}: CrisGaugeProps) {\r\n // Get join values reactively\r\n const joinValue = useAnalog(join ?? 0);\r\n const enabled = useDigital(joinEnable ?? 0);\r\n const visible = useDigital(joinVisible ?? 0);\r\n\r\n // Determine if enabled and visible\r\n const isEnabled = joinEnable == null ? true : enabled;\r\n const isVisible = joinVisible == null ? true : visible;\r\n\r\n // Get current value\r\n const currentValue = join != null ? joinValue : (value ?? 0);\r\n\r\n // Don't render if not visible\r\n if (!isVisible) return null;\r\n\r\n // Determine segment type (low, medium, high)\r\n const getSegmentType = (segment: number): 'low' | 'medium' | 'high' => {\r\n const lowMax = segments * 0.6;\r\n const mediumMax = segments * 0.8;\r\n\r\n const seg =\r\n (inverted && orientation === 'vertical') || (!inverted && orientation === 'horizontal')\r\n ? segment + 1\r\n : segments - segment;\r\n\r\n if (seg <= lowMax) return 'low';\r\n if (seg <= mediumMax) return 'medium';\r\n return 'high';\r\n };\r\n\r\n // Check if segment is active\r\n const isSegmentActive = (segment: number): boolean => {\r\n if (currentValue < minValue) return false;\r\n if (currentValue > maxValue) return true;\r\n\r\n const ratio = currentValue / (maxValue - minValue);\r\n let segMax = Math.round(segments * ratio);\r\n if (ratio !== 0 && segMax === 0) segMax = 1;\r\n\r\n const seg =\r\n (inverted && orientation === 'vertical') || (!inverted && orientation === 'horizontal')\r\n ? segment + 1\r\n : segments - segment;\r\n\r\n return seg <= segMax;\r\n };\r\n\r\n // Get segment class\r\n const getSegmentClass = (segment: number): string => {\r\n const type = getSegmentType(segment);\r\n const active = isSegmentActive(segment);\r\n\r\n const classes = ['cris-gauge-segment', type, orientation];\r\n\r\n if (active) {\r\n classes.push('active');\r\n // Add type-specific or general active class\r\n if (type === 'low' && (lowSegmentClassName || segmentClassName)) {\r\n classes.push(lowSegmentClassName || segmentClassName);\r\n } else if (type === 'medium' && (mediumSegmentClassName || segmentClassName)) {\r\n classes.push(mediumSegmentClassName || segmentClassName);\r\n } else if (type === 'high' && (highSegmentClassName || segmentClassName)) {\r\n classes.push(highSegmentClassName || segmentClassName);\r\n }\r\n } else {\r\n if (inactiveSegmentClassName) {\r\n classes.push(inactiveSegmentClassName);\r\n }\r\n }\r\n\r\n return classes.filter(Boolean).join(' ');\r\n };\r\n\r\n // Get segment style\r\n const getSegmentStyle = (segment: number): React.CSSProperties | undefined => {\r\n const type = getSegmentType(segment);\r\n const active = isSegmentActive(segment);\r\n\r\n if (active) {\r\n if (type === 'low' && (lowSegmentStyle || segmentStyle)) {\r\n return lowSegmentStyle || segmentStyle;\r\n } else if (type === 'medium' && (mediumSegmentStyle || segmentStyle)) {\r\n return mediumSegmentStyle || segmentStyle;\r\n } else if (type === 'high' && (highSegmentStyle || segmentStyle)) {\r\n return highSegmentStyle || segmentStyle;\r\n }\r\n } else {\r\n return inactiveSegmentStyle;\r\n }\r\n\r\n return undefined;\r\n };\r\n\r\n // Build container classes\r\n const containerClasses = [\r\n 'cris-gauge',\r\n className,\r\n !isEnabled && classDisabled,\r\n !isEnabled && 'disabled',\r\n ]\r\n .filter(Boolean)\r\n .join(' ');\r\n\r\n // Container flex style\r\n const containerStyle: React.CSSProperties = {\r\n ...style,\r\n display: 'flex',\r\n flexDirection: orientation === 'horizontal' ? 'row' : 'column',\r\n justifyContent: 'center',\r\n alignItems: 'center',\r\n };\r\n\r\n return (\r\n <div className={containerClasses} style={containerStyle}>\r\n {Array.from({ length: segments }, (_, segment) => (\r\n <div\r\n key={segment}\r\n className={getSegmentClass(segment)}\r\n style={getSegmentStyle(segment)}\r\n />\r\n ))}\r\n </div>\r\n );\r\n}\r\n","import { useAnalog, useDigital } from '@imperosoft/cris-webui-core';\r\n\r\nexport type SpinnerSpeed = 'slow' | 'medium' | 'fast';\r\n\r\nexport interface CrisSpinnerProps {\r\n /** Analog join for position (0-65535 maps to 0-360 degrees) */\r\n joinPosition?: number;\r\n /** Digital join for visibility */\r\n joinVisible?: number;\r\n\r\n /** Static position value (0-65535, used if no joinPosition) */\r\n position?: number;\r\n\r\n /** Endless rotation mode (default: false) */\r\n endless?: boolean;\r\n /** Speed for endless rotation (default: 'medium') */\r\n endlessSpeed?: SpinnerSpeed;\r\n\r\n /** Spinner color (default: '#bf2b23') */\r\n color?: string;\r\n /** Background/track color (default: 'rgba(0, 0, 0, 0.1)') */\r\n trackColor?: string;\r\n /** Spinner line width in pixels (default: 3) */\r\n lineWidth?: number;\r\n\r\n /** Container CSS class */\r\n className?: string;\r\n /** Container inline style */\r\n style?: React.CSSProperties;\r\n}\r\n\r\nconst SPEED_DURATIONS: Record<SpinnerSpeed, string> = {\r\n slow: '1.5s',\r\n medium: '0.8s',\r\n fast: '0.4s',\r\n};\r\n\r\nexport function CrisSpinner({\r\n joinPosition,\r\n joinVisible,\r\n position,\r\n endless = false,\r\n endlessSpeed = 'medium',\r\n color = '#bf2b23',\r\n trackColor = 'rgba(0, 0, 0, 0.1)',\r\n lineWidth = 3,\r\n className = '',\r\n style,\r\n}: CrisSpinnerProps) {\r\n // Get join values reactively\r\n const joinPositionValue = useAnalog(joinPosition ?? 0);\r\n const visible = useDigital(joinVisible ?? 0);\r\n\r\n // Determine visibility\r\n const isVisible = joinVisible == null ? true : visible;\r\n\r\n // Don't render if not visible\r\n if (!isVisible) return null;\r\n\r\n // Get current position (0-65535 -> 0-360 degrees)\r\n const currentPosition = joinPosition != null ? joinPositionValue : (position ?? 0);\r\n const rotationDegrees = (currentPosition / 65535) * 360;\r\n\r\n // Build container classes\r\n const containerClasses = ['cris-spinner', className].filter(Boolean).join(' ');\r\n\r\n // Spinner styles\r\n const spinnerStyle: React.CSSProperties = {\r\n width: '100%',\r\n height: '100%',\r\n borderRadius: '50%',\r\n border: `${lineWidth}px solid ${trackColor}`,\r\n borderTopColor: color,\r\n boxSizing: 'border-box',\r\n };\r\n\r\n if (endless) {\r\n // Endless rotation animation\r\n spinnerStyle.animation = `cris-spinner-rotate ${SPEED_DURATIONS[endlessSpeed]} linear infinite`;\r\n } else {\r\n // Position-based rotation\r\n spinnerStyle.transform = `rotate(${rotationDegrees}deg)`;\r\n spinnerStyle.transition = 'transform 0.1s linear';\r\n }\r\n\r\n return (\r\n <div className={containerClasses} style={style}>\r\n <div style={spinnerStyle} />\r\n <style>{`\r\n @keyframes cris-spinner-rotate {\r\n to { transform: rotate(360deg); }\r\n }\r\n `}</style>\r\n </div>\r\n );\r\n}\r\n"],"mappings":"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","names":["useDigital","jsx","jsxs","useState","useRef","useDigital","useJoinsStore","jsx","jsxs","useAnalog","useDigital","jsx","useAnalog","useDigital","jsx","jsxs"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@imperosoft/cris-webui-components",
3
- "version": "0.1.1-beta.4",
3
+ "version": "0.1.1-beta.5",
4
4
  "description": "CRIS - Crestron React Impero Soft WebUI components library",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",