{"version":3,"sources":["../src/NumericInput/NumericInput.tsx"],"sourcesContent":["import * as React from 'react';\nimport { clsx } from 'clsx';\n\nexport type NumericInputState = 'default' | 'error' | 'success';\n\nexport interface NumericInputProps {\n  /**\n   * The label for the numeric input\n   */\n  label?: string;\n  /**\n   * The current value\n   */\n  value?: number;\n  /**\n   * Callback when value changes\n   */\n  onChange?: (value: number | undefined) => void;\n  /**\n   * Minimum allowed value\n   */\n  min?: number;\n  /**\n   * Maximum allowed value\n   */\n  max?: number;\n  /**\n   * Step increment/decrement value\n   * @default 1\n   */\n  step?: number;\n  /**\n   * The validation state\n   * @default 'default'\n   */\n  state?: NumericInputState;\n  /**\n   * Optional error message\n   */\n  error?: string;\n  /**\n   * Optional helper text\n   */\n  helperText?: string;\n  /**\n   * Whether the input is disabled\n   */\n  disabled?: boolean;\n  /**\n   * Custom className\n   */\n  className?: string;\n  /**\n   * Custom style\n   */\n  style?: React.CSSProperties;\n  /**\n   * Test ID for testing\n   */\n  'data-testid'?: string;\n}\n\nconst labelStyles: React.CSSProperties = {\n  display: 'block',\n  fontSize: '13px',\n  fontWeight: '600',\n  color: '#2f2f2f',\n  marginBottom: '4px',\n  fontFamily: \"'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif\",\n};\n\nconst helperTextStyles: React.CSSProperties = {\n  fontSize: '13px',\n  margin: '0',\n  marginTop: '2px',\n  color: '#595959',\n  fontFamily: \"'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif\",\n  lineHeight: '1.4',\n};\n\nconst errorTextStyles: React.CSSProperties = {\n  ...helperTextStyles,\n  color: '#a62323', // destructive-600\n  display: 'flex',\n  alignItems: 'center',\n  gap: '4px',\n};\n\nconst containerStyles = {\n  base: {\n    display: 'flex',\n    alignItems: 'center',\n    height: '46px',\n    fontFamily: \"'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif\",\n    backgroundColor: '#ffffff',\n    borderRadius: '8px',\n    transition: 'all 0.2s ease-in-out',\n    overflow: 'hidden',\n  },\n  states: {\n    default: {\n      border: '1px solid #d1d1d1', // grey-300\n    },\n    defaultFocus: {\n      borderColor: '#3cad51', // brand-500\n      outline: '3px solid rgba(60, 173, 81, 0.2)',\n    },\n    error: {\n      border: '1px solid #c93232', // destructive-500\n    },\n    errorFocus: {\n      borderColor: '#c93232',\n      outline: '3px solid rgba(201, 50, 50, 0.2)',\n    },\n    success: {\n      border: '1px solid #16a33d', // success-500\n    },\n    successFocus: {\n      borderColor: '#16a33d',\n      outline: '3px solid rgba(22, 163, 61, 0.2)',\n    },\n    disabled: {\n      backgroundColor: '#f8f8f8', // grey-050\n      borderColor: '#efefef', // grey-100\n      cursor: 'not-allowed',\n    },\n  },\n};\n\nconst buttonStyles: React.CSSProperties = {\n  width: '46px',\n  height: '100%',\n  border: 'none',\n  background: 'transparent',\n  cursor: 'pointer',\n  fontSize: '16px',\n  color: '#2f2f2f',\n  display: 'flex',\n  alignItems: 'center',\n  justifyContent: 'center',\n  transition: 'background-color 0.2s',\n  fontFamily: \"'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif\",\n  userSelect: 'none' as const,\n};\n\nconst inputStyles: React.CSSProperties = {\n  flex: 1,\n  border: 'none',\n  outline: 'none',\n  background: 'transparent',\n  textAlign: 'center',\n  fontSize: '13px',\n  color: '#2f2f2f',\n  fontFamily: \"'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif\",\n  padding: '0 8px',\n  appearance: 'textfield' as any,\n};\n\n/**\n * NumericInput component - Arbor Design System\n *\n * A number input with increment/decrement buttons.\n * Enforces numeric rules (min, max, step) and prevents non-numeric input.\n */\nexport const NumericInput = React.forwardRef<HTMLDivElement, NumericInputProps>(\n  (\n    {\n      label,\n      value,\n      onChange,\n      min,\n      max,\n      step = 1,\n      state = 'default',\n      error,\n      helperText,\n      disabled = false,\n      className,\n      style,\n      'data-testid': dataTestId,\n    },\n    ref\n  ) => {\n    const [isFocused, setIsFocused] = React.useState(false);\n    const inputRef = React.useRef<HTMLInputElement>(null);\n\n    const inputId = React.useId();\n    const helperTextId = React.useId();\n    const errorId = React.useId();\n\n    const clampValue = (val: number): number => {\n      let clamped = val;\n      if (min !== undefined && clamped < min) clamped = min;\n      if (max !== undefined && clamped > max) clamped = max;\n      return clamped;\n    };\n\n    const handleIncrement = () => {\n      if (disabled) return;\n      const currentValue = value ?? 0;\n      const newValue = currentValue + step;\n      const clampedValue = clampValue(newValue);\n      if (clampedValue !== currentValue) {\n        onChange?.(clampedValue);\n      }\n    };\n\n    const handleDecrement = () => {\n      if (disabled) return;\n      const currentValue = value ?? 0;\n      const newValue = currentValue - step;\n      const clampedValue = clampValue(newValue);\n      if (clampedValue !== currentValue) {\n        onChange?.(clampedValue);\n      }\n    };\n\n    const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n      const inputValue = e.target.value;\n\n      // Allow empty input\n      if (inputValue === '' || inputValue === '-') {\n        onChange?.(undefined);\n        return;\n      }\n\n      // Parse as number\n      const numValue = parseFloat(inputValue);\n\n      // Only update if it's a valid number\n      if (!isNaN(numValue)) {\n        const clampedValue = clampValue(numValue);\n        onChange?.(clampedValue);\n      }\n    };\n\n    const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {\n      // Allow: backspace, delete, tab, escape, enter, minus sign, decimal point\n      const allowedKeys = ['Backspace', 'Delete', 'Tab', 'Escape', 'Enter', 'ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', '-', '.'];\n\n      if (allowedKeys.includes(e.key)) {\n        // Handle arrow keys for increment/decrement\n        if (e.key === 'ArrowUp') {\n          e.preventDefault();\n          handleIncrement();\n        } else if (e.key === 'ArrowDown') {\n          e.preventDefault();\n          handleDecrement();\n        }\n        return;\n      }\n\n      // Only allow numbers\n      if (!/^[0-9]$/.test(e.key)) {\n        e.preventDefault();\n      }\n    };\n\n    const currentState = error ? 'error' : state;\n    const stateStyles = containerStyles.states[currentState];\n    const focusStyles =\n      currentState === 'error'\n        ? containerStyles.states.errorFocus\n        : currentState === 'success'\n        ? containerStyles.states.successFocus\n        : containerStyles.states.defaultFocus;\n\n    const containerStyle: React.CSSProperties = {\n      ...containerStyles.base,\n      ...stateStyles,\n      ...(isFocused && !disabled && focusStyles),\n      ...(disabled && containerStyles.states.disabled),\n    };\n\n    const isDecrementDisabled = disabled || (min !== undefined && value !== undefined && value <= min);\n    const isIncrementDisabled = disabled || (max !== undefined && value !== undefined && value >= max);\n\n    // Button styles for success state (green outlined circles)\n    const getButtonStyle = (isDisabled: boolean) => {\n      const baseStyle: React.CSSProperties = {\n        ...buttonStyles,\n        cursor: isDisabled ? 'not-allowed' : 'pointer',\n        color: isDisabled ? '#7e7e7e' : '#2f2f2f',\n      };\n\n      // Add green circle border for success state\n      if (currentState === 'success' && !isDisabled) {\n        return {\n          ...baseStyle,\n          color: '#16a33d', // success-500\n          border: '2px solid #16a33d',\n          borderRadius: '50%',\n          width: '32px',\n          height: '32px',\n        };\n      }\n\n      return baseStyle;\n    };\n\n    return (\n      <div className={clsx('arbor-numeric-input-wrapper', className)} style={style} ref={ref} data-testid={dataTestId}>\n        {label && (\n          <label htmlFor={inputId} style={labelStyles}>\n            {label}\n          </label>\n        )}\n\n        <div style={containerStyle}>\n          <button\n            type=\"button\"\n            onClick={handleDecrement}\n            disabled={isDecrementDisabled}\n            style={getButtonStyle(isDecrementDisabled)}\n            onMouseEnter={(e) => {\n              if (!isDecrementDisabled) {\n                e.currentTarget.style.backgroundColor = '#f8f8f8';\n                e.currentTarget.style.borderRadius = '99px';\n              }\n            }}\n            onMouseLeave={(e) => {\n              e.currentTarget.style.backgroundColor = 'transparent';\n              e.currentTarget.style.borderRadius = currentState === 'success' ? '50%' : '0';\n            }}\n            aria-label=\"Decrement\"\n          >\n            −\n          </button>\n\n          <input\n            ref={inputRef}\n            id={inputId}\n            type=\"text\"\n            inputMode=\"numeric\"\n            value={value !== undefined ? value : ''}\n            onChange={handleInputChange}\n            onKeyDown={handleKeyDown}\n            onFocus={() => setIsFocused(true)}\n            onBlur={() => setIsFocused(false)}\n            disabled={disabled}\n            aria-invalid={error ? 'true' : 'false'}\n            aria-describedby={error ? errorId : helperText ? helperTextId : undefined}\n            style={{\n              ...inputStyles,\n              color: disabled ? '#7e7e7e' : '#2f2f2f',\n              cursor: disabled ? 'not-allowed' : 'text',\n            }}\n          />\n\n          <button\n            type=\"button\"\n            onClick={handleIncrement}\n            disabled={isIncrementDisabled}\n            style={getButtonStyle(isIncrementDisabled)}\n            onMouseEnter={(e) => {\n              if (!isIncrementDisabled) {\n                e.currentTarget.style.backgroundColor = '#f8f8f8';\n                e.currentTarget.style.borderRadius = '99px';\n              }\n            }}\n            onMouseLeave={(e) => {\n              e.currentTarget.style.backgroundColor = 'transparent';\n              e.currentTarget.style.borderRadius = currentState === 'success' ? '50%' : '0';\n            }}\n            aria-label=\"Increment\"\n          >\n            +\n          </button>\n        </div>\n\n        {error && (\n          <p id={errorId} style={errorTextStyles}>\n            {error}\n          </p>\n        )}\n\n        {helperText && !error && (\n          <p id={helperTextId} style={helperTextStyles}>\n            {helperText}\n          </p>\n        )}\n      </div>\n    );\n  }\n);\n\nNumericInput.displayName = 'NumericInput';\n"],"mappings":"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","names":[]}