{"version":3,"sources":["../src/time/index.ts","../src/time/time-format.tsx","../src/time/format-time.ts","../src/time/parse-time.ts","../src/utils/input.ts","../src/time/use-time-format.ts","../src/set-native-value.ts"],"sourcesContent":["export { TimeFormat } from \"./time-format\";\nexport type { TimeFormatProps } from \"./time-format\";\nexport { useTimeFormat } from \"./use-time-format\";\nexport type { UseTimeFormatOptions } from \"./use-time-format\";\nexport { formatTime } from \"./format-time\";\nexport { parseTime } from \"./parse-time\";\nexport type { TimeFormatOptions, TimeFormatValues } from \"./types\";\n","import React, { forwardRef, useLayoutEffect, useReducer, useRef } from \"react\";\nimport type { TimeFormatOptions, TimeFormatValues } from \"./types\";\nimport { formatTime } from \"./format-time\";\nimport {\n  collapseTrailingSeparator,\n  parseTime,\n  digitsBeforeCaret,\n  resolveCaret,\n} from \"./parse-time\";\nimport { isInputFocused, setInputSelection } from \"../utils/input\";\n\nexport interface TimeFormatProps\n  extends TimeFormatOptions,\n    Omit<React.InputHTMLAttributes<HTMLInputElement>, \"value\" | \"onChange\"> {\n  value?: string;\n  onValueChange?: (values: TimeFormatValues) => void;\n}\n\nexport const TimeFormat = forwardRef<HTMLInputElement, TimeFormatProps>(function TimeFormat(\n  props,\n  forwardedRef\n) {\n  const { value = \"\", onValueChange, separator, ...domProps } = props;\n  const options: TimeFormatOptions = { separator };\n\n  const innerRef = useRef<HTMLInputElement | null>(null);\n  const caretRef = useRef<number | null>(null);\n  const lastValuesRef = useRef<TimeFormatValues | null>(null);\n\n  const [, forceUpdate] = useReducer((n: number) => n + 1, 0);\n\n  const last = lastValuesRef.current;\n  const formatted =\n    last !== null && (value === last.value || value === last.formattedValue)\n      ? last.formattedValue\n      : formatTime(value ?? \"\", options);\n\n  function handleChange(event: React.ChangeEvent<HTMLInputElement>): void {\n    const el = event.target;\n    const caret = el.selectionStart ?? el.value.length;\n    const raw = collapseTrailingSeparator(el.value, formatted, options.separator ?? \":\");\n    const digits = digitsBeforeCaret(raw, caret);\n    const values = parseTime(raw, options);\n    caretRef.current = resolveCaret(values, digits, options);\n    lastValuesRef.current = values;\n    forceUpdate();\n    onValueChange?.(values);\n  }\n\n  useLayoutEffect(() => {\n    const el = innerRef.current;\n    if (el && caretRef.current !== null && isInputFocused(el)) {\n      setInputSelection(el, caretRef.current, caretRef.current);\n      caretRef.current = null;\n    }\n  });\n\n  function setRef(el: HTMLInputElement | null): void {\n    innerRef.current = el;\n    if (typeof forwardedRef === \"function\") forwardedRef(el);\n    else if (forwardedRef)\n      (forwardedRef as React.MutableRefObject<HTMLInputElement | null>).current = el;\n  }\n\n  return <input {...domProps} ref={setRef} value={formatted} onChange={handleChange} />;\n});\n\nTimeFormat.displayName = \"TimeFormat\";\n","import type { TimeFormatOptions } from \"./types\";\n\nexport interface TimeParts {\n  hours: string;\n  minutes: string;\n}\n\nexport function tokenizeTime(raw: string | number): TimeParts {\n  const digits = String(raw ?? \"\").replace(/\\D/g, \"\").slice(0, 4);\n  let hours = digits.slice(0, 2);\n  let minutes = digits.slice(2, 4);\n  if (hours.length === 2) {\n    hours = String(Math.min(parseInt(hours, 10), 23)).padStart(2, \"0\");\n  }\n  if (minutes.length === 2) {\n    minutes = String(Math.min(parseInt(minutes, 10), 59)).padStart(2, \"0\");\n  }\n  return { hours, minutes };\n}\n\nexport function buildFormatted(parts: TimeParts, separator: string): string {\n  const { hours, minutes } = parts;\n  if (hours === \"\") return \"\";\n  if (hours.length < 2) return hours;\n  if (minutes === \"\") return hours + separator;\n  return hours + separator + minutes;\n}\n\nexport function formatTime(input: string | number, options?: TimeFormatOptions): string {\n  const separator = options?.separator ?? \":\";\n  return buildFormatted(tokenizeTime(input), separator);\n}\n","import type { TimeFormatOptions, TimeFormatValues } from \"./types\";\nimport { tokenizeTime, buildFormatted } from \"./format-time\";\n\nexport function collapseTrailingSeparator(\n  raw: string,\n  previous: string,\n  separator: string\n): string {\n  return previous.endsWith(separator) && raw === previous.slice(0, -separator.length)\n    ? raw.slice(0, -1)\n    : raw;\n}\n\nexport function parseTime(formatted: string, options?: TimeFormatOptions): TimeFormatValues {\n  const separator = options?.separator ?? \":\";\n  const parts = tokenizeTime(formatted);\n  const formattedValue = buildFormatted(parts, separator);\n  const hours = parts.hours.length === 2 ? parseInt(parts.hours, 10) : undefined;\n  const minutes = parts.minutes.length === 2 ? parseInt(parts.minutes, 10) : undefined;\n  const value =\n    hours !== undefined && minutes !== undefined ? `${parts.hours}:${parts.minutes}` : \"\";\n  return { value, formattedValue, hours, minutes };\n}\n\nexport function digitsBeforeCaret(value: string, caret: number): number {\n  let count = 0;\n  for (let i = 0; i < caret && i < value.length; i++) {\n    if (value[i] >= \"0\" && value[i] <= \"9\") count++;\n  }\n  return count;\n}\n\nfunction caretAfterDigits(formatted: string, digitsBefore: number): number {\n  if (digitsBefore <= 0) {\n    const first = formatted.search(/\\d/);\n    return first === -1 ? formatted.length : first;\n  }\n  let seen = 0;\n  for (let i = 0; i < formatted.length; i++) {\n    if (formatted[i] >= \"0\" && formatted[i] <= \"9\") {\n      seen++;\n      if (seen === digitsBefore) return i + 1;\n    }\n  }\n  return formatted.length;\n}\n\nexport function resolveCaret(\n  values: TimeFormatValues,\n  digitsBefore: number,\n  options?: TimeFormatOptions\n): number {\n  const separator = options?.separator ?? \":\";\n  const caret = caretAfterDigits(values.formattedValue, digitsBefore);\n  if (separator && values.formattedValue.slice(caret, caret + separator.length) === separator) {\n    return caret + separator.length;\n  }\n  return caret;\n}\n","import type { Selection } from \"../types\";\n\nexport function setInputSelection(\n  input: HTMLInputElement,\n  start: number,\n  end?: number\n): void {\n  if (end === undefined) {\n    end = start;\n  }\n  input.setSelectionRange(start, end);\n}\n\nexport function getInputSelection(input: HTMLInputElement): Required<Selection> {\n  const start = input.selectionStart;\n  const end = input.selectionEnd;\n\n  return {\n    start,\n    end,\n    length: (end ?? 0) - (start ?? 0)\n  };\n}\n\nexport function isInputFocused(input: HTMLInputElement): boolean {\n  const inputDocument = input.ownerDocument;\n  return inputDocument.hasFocus() && inputDocument.activeElement === input;\n}\n","import { useRef, useCallback } from \"react\";\nimport type React from \"react\";\nimport type { TimeFormatValues } from \"./types\";\nimport type { TimeFormatOptions } from \"./types\";\nimport { formatTime } from \"./format-time\";\nimport {\n  collapseTrailingSeparator,\n  parseTime,\n  digitsBeforeCaret,\n  resolveCaret,\n} from \"./parse-time\";\nimport { setNativeValue } from \"../set-native-value\";\nimport { isInputFocused, setInputSelection } from \"../utils/input\";\n\nexport interface UseTimeFormatOptions extends TimeFormatOptions {\n  onValueChange?: (values: TimeFormatValues) => void;\n}\n\nconst IS_JSDOM =\n  typeof navigator !== \"undefined\" && navigator.userAgent.includes(\"jsdom\");\n\nexport function useTimeFormat(\n  options: UseTimeFormatOptions\n): React.RefCallback<HTMLInputElement> {\n  const optionsRef = useRef(options);\n  optionsRef.current = options;\n\n  const inputRef = useRef<HTMLInputElement | null>(null);\n  const handlerRef = useRef<((event: Event) => void) | null>(null);\n\n  return useCallback((el: HTMLInputElement | null) => {\n    if (el) {\n      let previous = el.value;\n      const handler = (): void => {\n        const opts = optionsRef.current;\n        const caret = el.selectionStart ?? el.value.length;\n        const raw = collapseTrailingSeparator(el.value, previous, opts.separator ?? \":\");\n        const digits = digitsBeforeCaret(raw, caret);\n        const values = parseTime(raw, opts);\n        setNativeValue(el, values.formattedValue);\n        previous = values.formattedValue;\n        if (isInputFocused(el)) {\n          const next = resolveCaret(values, digits, opts);\n          setInputSelection(el, next, next);\n        }\n        // JSDOM-ONLY: resync @testing-library/user-event v14's shadow-value cache.\n        // setNativeValue above bypasses React's value tracker; under jsdom that\n        // leaves user-event's per-input shadow value stale, corrupting masking from\n        // ~the 4th keystroke. A plain (idempotent) reassignment resyncs it.\n        // MUST NOT run in a real browser: there, microtasks drain BETWEEN event\n        // listeners, so this write would fight React; real browsers read the live\n        // DOM and need no resync. onValueChange (below) is called directly, not via\n        // React's tracker, so it is unaffected in either environment.\n        if (IS_JSDOM) {\n          queueMicrotask(() => {\n            if (el.value !== values.formattedValue) return;\n            el.value = values.formattedValue;\n            if (isInputFocused(el)) {\n              const c = resolveCaret(values, digits, opts);\n              setInputSelection(el, c, c);\n            }\n          });\n        }\n        opts.onValueChange?.(values);\n      };\n      el.addEventListener(\"input\", handler);\n      handlerRef.current = handler;\n      inputRef.current = el;\n      if (el.value) {\n        setNativeValue(el, formatTime(el.value, optionsRef.current));\n        previous = formatTime(el.value, optionsRef.current);\n      }\n    } else {\n      if (inputRef.current && handlerRef.current) {\n        inputRef.current.removeEventListener(\"input\", handlerRef.current);\n      }\n      inputRef.current = null;\n      handlerRef.current = null;\n    }\n  }, []);\n}\n","// Write to input.value through the native prototype setter so React's\n// internal value tracker stays stale. The user's keystroke `input` event then\n// bubbles into React's root listener, which sees masked value != stale tracker\n// and fires the consumer's onChange with the masked value. Using the tracked\n// (React-wrapped) setter instead would suppress that onChange.\nexport function setNativeValue(input: HTMLInputElement, value: string): void {\n  const descriptor = Object.getOwnPropertyDescriptor(\n    HTMLInputElement.prototype,\n    \"value\"\n  );\n  const setter = descriptor && descriptor.set;\n  if (setter) {\n    setter.call(input, value);\n  } else {\n    input.value = value;\n  }\n}\n"],"mappings":"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","names":["TimeFormat","React","import_react"]}