{"version":3,"file":"timeInput.mjs","names":[],"sources":["../../../../package/common/timeInput/timeInput.tsx"],"sourcesContent":["import React, { useState, useEffect, useRef } from 'react';\nimport cx from 'classnames';\nimport MaskedInput from '../maskedInput';\nimport type { BeforeMaskedStateChangeStates, MaskState } from '../maskedInput';\nimport { faClock } from '@fortawesome/free-solid-svg-icons';\nimport { FontAwesomeIcon } from '@fortawesome/react-fontawesome';\nimport Input from '../input';\nimport Modal from '../modal';\nimport Popover from '../popover';\nimport Button from '../button';\nimport Tooltip from '../tooltip';\nimport ClockDial from './components/clockDial';\nimport { PopoverAlign, PopoverPlacement } from '../popover/popover.types';\nimport { type TimeValue, parseTimeString, formatTime, snapMinute, clampToRange, clampHour } from './timeFunctions';\nimport type { TimeInputProps } from './timeInput.types';\n\nimport styles from './timeInput.module.pcss';\n\ntype ClockMode = 'hours' | 'minutes';\n\nexport default function TimeInput({\n  value,\n  selected,\n  onChange = () => null,\n  onSelect = () => null,\n  formValidator = null,\n  isInline = false,\n  isModal = false,\n  noInput = false,\n  noHeader = false,\n  showClearButton = false,\n  showNowButton = false,\n  minuteStep = 1,\n  use24Hour = true,\n  minTime = null,\n  maxTime = null,\n  label = '',\n  isValid = true,\n  errorMessage = '',\n  isRequired = false,\n  disabled = false,\n  className = '',\n  style,\n  classNames,\n  styles: slotStyles,\n  ...rest\n}: TimeInputProps) {\n  const getExternalString = (): string => {\n    if (formValidator) return (formValidator.value as string) || '';\n    return value ?? selected ?? '';\n  };\n\n  const [currentTimeString, setCurrentTimeString] = useState(getExternalString);\n  const [isPickerOpen, setIsPickerOpen] = useState(false);\n  const [mode, setMode] = useState<ClockMode>('hours');\n  const [isFocused, setIsFocused] = useState(false);\n  const [isHovered, setIsHovered] = useState(false);\n  const [, forceUpdate] = useState(0);\n\n  const externalString = getExternalString();\n  const [prevExternal, setPrevExternal] = useState(externalString);\n  const fvRef = useRef(formValidator);\n\n  // Sync when the controlled value changes externally.\n  if (!formValidator && prevExternal !== externalString) {\n    setPrevExternal(externalString);\n    setCurrentTimeString(externalString);\n  }\n\n  useEffect(() => {\n    fvRef.current = formValidator;\n    if (!formValidator) return;\n    const listener = () => {\n      setCurrentTimeString((formValidator.value as string) || '');\n      forceUpdate((n) => n + 1);\n    };\n    formValidator.registerOnUpdateListener(listener);\n    return () => formValidator.removeOnUpdateListener(listener);\n  }, [formValidator]);\n\n  const hasError = (): boolean => (formValidator ? formValidator.shouldShowError : !isValid || !!errorMessage);\n\n  const computedIsRequired = (): boolean =>\n    !!(isRequired || formValidator?.validators.find((v) => (v as { validatorId?: string }).validatorId === 'required'));\n\n  /** Push a value out to formValidator/onChange without touching the input string. */\n  const emit = (time: TimeValue | null) => {\n    const str = time ? formatTime(time) : null;\n    if (formValidator) formValidator.set(str);\n    onChange?.(str);\n  };\n\n  const applyTime = (time: TimeValue | null) => {\n    setCurrentTimeString(time ? formatTime(time) : '');\n    emit(time);\n  };\n\n  const parsed = parseTimeString(currentTimeString);\n  const dialTime: TimeValue = parsed ?? { hours: 12, minutes: 0 };\n\n  const handleDialChange = (time: TimeValue) => {\n    const snapped = { hours: time.hours, minutes: snapMinute(time.minutes, minuteStep) };\n    applyTime(clampToRange(snapped, minTime, maxTime));\n  };\n\n  const handleDialCommit = (time: TimeValue) => {\n    const snapped = { hours: time.hours, minutes: snapMinute(time.minutes, minuteStep) };\n    const next = clampToRange(snapped, minTime, maxTime);\n    applyTime(next);\n    if (mode === 'hours') {\n      setMode('minutes');\n      return;\n    }\n    onSelect?.(formatTime(next));\n    if (!isInline) setIsPickerOpen(false);\n  };\n\n  const openPicker = () => {\n    if (disabled) return;\n    setMode('hours');\n    setIsPickerOpen(true);\n  };\n\n  const beforeMaskedStateChange = ({ previousState, nextState }: BeforeMaskedStateChangeStates): MaskState => {\n    if (nextState.value === previousState.value) return nextState;\n    let v = nextState.value;\n    const hourStr = v.slice(0, 2);\n    if (/^\\d\\d$/.test(hourStr) && Number(hourStr) > 23) v = `23${v.slice(2)}`;\n    const minStr = v.slice(3, 5);\n    if (/^\\d\\d$/.test(minStr) && Number(minStr) > 59) v = `${v.slice(0, 3)}59`;\n    return v === nextState.value ? nextState : { ...nextState, value: v };\n  };\n\n  const handleInputChange = (v: string) => {\n    setCurrentTimeString(v);\n    const t = parseTimeString(v);\n    if (t) emit(t);\n  };\n\n  const handleBlur = () => {\n    if (fvRef.current) fvRef.current.touched = true;\n    setIsFocused(false);\n    emit(parseTimeString(currentTimeString));\n  };\n\n  const setMeridiem = (pm: boolean) => {\n    const isPm = dialTime.hours >= 12;\n    if (pm === isPm) return;\n    applyTime({ hours: clampHour(dialTime.hours + (pm ? 12 : -12)), minutes: dialTime.minutes });\n  };\n\n  const hasErr = hasError();\n  const displayHours = parsed\n    ? String(use24Hour ? parsed.hours : ((parsed.hours + 11) % 12) + 1).padStart(2, '0')\n    : '--';\n  const displayMinutes = parsed ? String(parsed.minutes).padStart(2, '0') : '--';\n\n  const header = noHeader ? null : (\n    <div className={cx(styles.timeInputClockHeader, classNames?.clockHeader)} style={slotStyles?.clockHeader}>\n      <button\n        type='button'\n        className={cx(\n          styles.timeInputHeaderSegment,\n          mode === 'hours' && styles.timeInputHeaderSegmentActive,\n          classNames?.headerSegment,\n        )}\n        style={slotStyles?.headerSegment}\n        onClick={() => setMode('hours')}\n      >\n        {displayHours}\n      </button>\n      <span className={styles.timeInputHeaderColon}>:</span>\n      <button\n        type='button'\n        className={cx(\n          styles.timeInputHeaderSegment,\n          mode === 'minutes' && styles.timeInputHeaderSegmentActive,\n          classNames?.headerSegment,\n        )}\n        style={slotStyles?.headerSegment}\n        onClick={() => setMode('minutes')}\n      >\n        {displayMinutes}\n      </button>\n      {!use24Hour && (\n        <div className={cx(styles.timeInputMeridiem, classNames?.meridiem)} style={slotStyles?.meridiem}>\n          <button\n            type='button'\n            className={cx(dialTime.hours < 12 && styles.timeInputMeridiemActive)}\n            onClick={() => setMeridiem(false)}\n          >\n            AM\n          </button>\n          <button\n            type='button'\n            className={cx(dialTime.hours >= 12 && styles.timeInputMeridiemActive)}\n            onClick={() => setMeridiem(true)}\n          >\n            PM\n          </button>\n        </div>\n      )}\n    </div>\n  );\n\n  const footer =\n    showClearButton || showNowButton ? (\n      <div className={cx(styles.timeInputFooter, classNames?.footer)} style={slotStyles?.footer}>\n        {showNowButton && (\n          <Button\n            onClick={() => {\n              const now = new Date();\n              handleDialChange({ hours: now.getHours(), minutes: now.getMinutes() });\n            }}\n          >\n            Now\n          </Button>\n        )}\n        {showClearButton && (\n          <Button\n            onClick={() => {\n              applyTime(null);\n              onSelect?.(null);\n            }}\n          >\n            Clear\n          </Button>\n        )}\n      </div>\n    ) : null;\n\n  const pickerContent = (\n    <div className={cx(styles.timeInputClock, classNames?.clock)} style={slotStyles?.clock}>\n      {header}\n      <ClockDial\n        time={dialTime}\n        mode={mode}\n        use24Hour={use24Hour}\n        minuteStep={minuteStep}\n        onChange={handleDialChange}\n        onCommit={handleDialCommit}\n        classNames={classNames}\n        styles={slotStyles}\n      />\n      {footer}\n    </div>\n  );\n\n  const input = noInput ? null : (\n    <Input\n      autoComplete='off'\n      label={`${label}${computedIsRequired() ? ' *' : ''}`}\n      value={currentTimeString}\n      icon={<FontAwesomeIcon icon={faClock} />}\n      onIconClick={openPicker}\n      onClick={openPicker}\n      readOnly={isModal}\n      disabled={disabled}\n      isValid={!hasErr}\n      customInput={<MaskedInput mask='99:99' beforeMaskedStateChange={beforeMaskedStateChange} />}\n      onChange={(v) => handleInputChange(v as string)}\n      onBlur={handleBlur}\n      onFocus={() => setIsFocused(true)}\n      onMouseEnter={() => setIsHovered(true)}\n      onMouseLeave={() => setIsHovered(false)}\n    />\n  );\n\n  const picker = (() => {\n    if (isInline) return pickerContent;\n    if (isModal) {\n      return (\n        <Modal hideHeader isOpen={isPickerOpen} onClose={() => setIsPickerOpen(false)}>\n          {pickerContent}\n        </Modal>\n      );\n    }\n    return (\n      <Popover\n        isOpen={isPickerOpen}\n        onOpenChange={setIsPickerOpen}\n        content={pickerContent}\n        placement={PopoverPlacement.Bottom}\n        align={PopoverAlign.Start}\n        // A click to place the caret in the field must not take the panel away.\n        // The focus stays there on its own: Popover only follows a keyboard open.\n        closeOnTriggerClick={false}\n        constrainWidth={false}\n      >\n        {input}\n      </Popover>\n    );\n  })();\n\n  return (\n    <div\n      className={cx(styles.timeInput, classNames?.root, className)}\n      style={{ ...slotStyles?.root, ...style }}\n      {...rest}\n    >\n      {(isModal || isInline) && input}\n      {picker}\n      <Tooltip\n        variant='error'\n        message={formValidator ? (formValidator.getCurrentErrorMessage() ?? '') : errorMessage || ''}\n        isVisible={hasErr && (isFocused || isHovered)}\n      />\n    </div>\n  );\n}\n"],"mappings":"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"}