{"mappings":"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","sources":["packages/react-aria/src/spinbutton/useSpinButton.ts"],"sourcesContent":["/*\n * Copyright 2020 Adobe. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\nimport {announce, clearAnnouncer} from '../live-announcer/LiveAnnouncer';\n\nimport {AriaButtonProps} from '../button/useButton';\nimport {DOMAttributes, InputBase, RangeInputBase, Validation, ValueBase} from '@react-types/shared';\n// @ts-ignore\nimport intlMessages from '../../intl/spinbutton/*.json';\nimport {useCallback, useEffect, useRef, useState} from 'react';\nimport {useEffectEvent} from '../utils/useEffectEvent';\nimport {useGlobalListeners} from '../utils/useGlobalListeners';\nimport {useLocalizedStringFormatter} from '../i18n/useLocalizedStringFormatter';\n\nconst noop = () => {};\n\nexport interface SpinButtonProps\n  extends InputBase, Validation<number>, ValueBase<number>, RangeInputBase<number> {\n  textValue?: string;\n  onIncrement?: () => void;\n  onIncrementPage?: () => void;\n  onDecrement?: () => void;\n  onDecrementPage?: () => void;\n  onDecrementToMin?: () => void;\n  onIncrementToMax?: () => void;\n}\n\nexport interface SpinbuttonAria {\n  spinButtonProps: DOMAttributes;\n  incrementButtonProps: AriaButtonProps;\n  decrementButtonProps: AriaButtonProps;\n}\n\nexport function useSpinButton(props: SpinButtonProps): SpinbuttonAria {\n  const _async = useRef<number>(undefined);\n  let {\n    value,\n    textValue,\n    minValue,\n    maxValue,\n    isDisabled,\n    isReadOnly,\n    isRequired,\n    onIncrement,\n    onIncrementPage,\n    onDecrement,\n    onDecrementPage,\n    onDecrementToMin,\n    onIncrementToMax\n  } = props;\n  const stringFormatter = useLocalizedStringFormatter(intlMessages, '@react-aria/spinbutton');\n\n  let isSpinning = useRef(false);\n  const clearAsync = useCallback(() => {\n    clearTimeout(_async.current);\n    isSpinning.current = false;\n  }, []);\n  const clearAsyncEvent = useEffectEvent(() => {\n    clearAsync();\n  });\n\n  useEffect(() => {\n    return () => clearAsyncEvent();\n  }, []);\n\n  let onKeyDown = e => {\n    if (\n      e.ctrlKey ||\n      e.metaKey ||\n      e.shiftKey ||\n      e.altKey ||\n      isReadOnly ||\n      e.nativeEvent.isComposing\n    ) {\n      return;\n    }\n\n    switch (e.key) {\n      case 'PageUp':\n        if (onIncrementPage) {\n          e.preventDefault();\n          onIncrementPage?.();\n          break;\n        }\n      // fallthrough!\n      case 'ArrowUp':\n      case 'Up':\n        if (onIncrement) {\n          e.preventDefault();\n          onIncrement?.();\n        }\n        break;\n      case 'PageDown':\n        if (onDecrementPage) {\n          e.preventDefault();\n          onDecrementPage?.();\n          break;\n        }\n      // fallthrough\n      case 'ArrowDown':\n      case 'Down':\n        if (onDecrement) {\n          e.preventDefault();\n          onDecrement?.();\n        }\n        break;\n      case 'Home':\n        if (onDecrementToMin) {\n          e.preventDefault();\n          onDecrementToMin?.();\n        }\n        break;\n      case 'End':\n        if (onIncrementToMax) {\n          e.preventDefault();\n          onIncrementToMax?.();\n        }\n        break;\n    }\n  };\n\n  let isFocused = useRef(false);\n  let onFocus = () => {\n    isFocused.current = true;\n  };\n\n  let onBlur = () => {\n    isFocused.current = false;\n  };\n\n  // Replace Unicode hyphen-minus (U+002D) with minus sign (U+2212).\n  // This ensures that macOS VoiceOver announces it as \"minus\" even with other characters between the minus sign\n  // and the number (e.g. currency symbol). Otherwise it announces nothing because it assumes the character is a hyphen.\n  // In addition, replace the empty string with the word \"Empty\" so that iOS VoiceOver does not read \"50%\" for an empty field.\n  let ariaTextValue =\n    textValue === ''\n      ? stringFormatter.format('Empty')\n      : (textValue || `${value}`).replace('-', '\\u2212');\n\n  useEffect(() => {\n    if (isFocused.current) {\n      clearAnnouncer('assertive');\n      announce(ariaTextValue, 'assertive');\n    }\n  }, [ariaTextValue]);\n\n  // For touch users, if they move their finger like they're scrolling, we don't want to trigger a spin.\n  let onPointerCancel = useCallback(() => {\n    clearAsync();\n  }, [clearAsync]);\n\n  const onIncrementEvent = useEffectEvent(onIncrement ?? noop);\n  const onDecrementEvent = useEffectEvent(onDecrement ?? noop);\n\n  const stepUpEvent = useEffectEvent(() => {\n    if (\n      maxValue === undefined ||\n      isNaN(maxValue) ||\n      value === undefined ||\n      isNaN(value) ||\n      value < maxValue\n    ) {\n      onIncrementEvent();\n      onIncrementPressStartEvent(60);\n    }\n  });\n\n  const onIncrementPressStartEvent = useEffectEvent((initialStepDelay: number) => {\n    clearAsyncEvent();\n    isSpinning.current = true;\n    // Start spinning after initial delay\n    _async.current = window.setTimeout(stepUpEvent, initialStepDelay);\n  });\n\n  const stepDownEvent = useEffectEvent(() => {\n    if (\n      minValue === undefined ||\n      isNaN(minValue) ||\n      value === undefined ||\n      isNaN(value) ||\n      value > minValue\n    ) {\n      onDecrementEvent();\n      onDecrementPressStartEvent(60);\n    }\n  });\n\n  const onDecrementPressStartEvent = useEffectEvent((initialStepDelay: number) => {\n    clearAsyncEvent();\n    isSpinning.current = true;\n    // Start spinning after initial delay\n    _async.current = window.setTimeout(stepDownEvent, initialStepDelay);\n  });\n\n  let cancelContextMenu = e => {\n    e.preventDefault();\n  };\n\n  let {addGlobalListener, removeAllGlobalListeners} = useGlobalListeners();\n\n  // Tracks in touch if the press end event was preceded by a press up.\n  // If it wasn't, then we know the finger left the button while still in contact with the screen.\n  // This means that the user is trying to scroll or interact in some way that shouldn't trigger\n  // an increment or decrement.\n  let isUp = useRef(false);\n\n  let [isIncrementPressed, setIsIncrementPressed] = useState<'touch' | 'mouse' | null>(null);\n  useEffect(() => {\n    if (isIncrementPressed === 'touch') {\n      onIncrementPressStartEvent(600);\n    } else if (isIncrementPressed) {\n      onIncrementPressStartEvent(400);\n    } else if (!isIncrementPressed) {\n      clearAsyncEvent();\n    }\n  }, [isIncrementPressed]);\n\n  let [isDecrementPressed, setIsDecrementPressed] = useState<'touch' | 'mouse' | null>(null);\n  useEffect(() => {\n    if (isDecrementPressed === 'touch') {\n      onDecrementPressStartEvent(600);\n    } else if (isDecrementPressed) {\n      onDecrementPressStartEvent(400);\n    } else if (!isDecrementPressed) {\n      clearAsyncEvent();\n    }\n  }, [isDecrementPressed]);\n\n  return {\n    spinButtonProps: {\n      role: 'spinbutton',\n      'aria-valuenow': value !== undefined && !isNaN(value) ? value : undefined,\n      'aria-valuetext': ariaTextValue,\n      'aria-valuemin': minValue,\n      'aria-valuemax': maxValue,\n      'aria-disabled': isDisabled || undefined,\n      'aria-readonly': isReadOnly || undefined,\n      'aria-required': isRequired || undefined,\n      onKeyDown,\n      onFocus,\n      onBlur\n    },\n    incrementButtonProps: {\n      onPressStart: e => {\n        clearAsync();\n        if (e.pointerType !== 'touch') {\n          onIncrement?.();\n          setIsIncrementPressed('mouse');\n        } else {\n          addGlobalListener(window, 'pointercancel', onPointerCancel, {capture: true});\n          isUp.current = false;\n          // For touch users, don't trigger a decrement on press start, we'll wait for the press end to trigger it if\n          // the control isn't spinning.\n          setIsIncrementPressed('touch');\n        }\n        addGlobalListener(window, 'contextmenu', cancelContextMenu);\n      },\n      onPressUp: e => {\n        clearAsync();\n        if (e.pointerType === 'touch') {\n          isUp.current = true;\n        }\n        removeAllGlobalListeners();\n        setIsIncrementPressed(null);\n      },\n      onPressEnd: e => {\n        clearAsync();\n        if (e.pointerType === 'touch') {\n          if (!isSpinning.current && isUp.current) {\n            onIncrement?.();\n          }\n        }\n        isUp.current = false;\n        setIsIncrementPressed(null);\n      },\n      onFocus,\n      onBlur\n    },\n    decrementButtonProps: {\n      onPressStart: e => {\n        clearAsync();\n        if (e.pointerType !== 'touch') {\n          onDecrement?.();\n          setIsDecrementPressed('mouse');\n        } else {\n          addGlobalListener(window, 'pointercancel', onPointerCancel, {capture: true});\n          isUp.current = false;\n          // For touch users, don't trigger a decrement on press start, we'll wait for the press end to trigger it if\n          // the control isn't spinning.\n          setIsDecrementPressed('touch');\n        }\n      },\n      onPressUp: e => {\n        clearAsync();\n        if (e.pointerType === 'touch') {\n          isUp.current = true;\n        }\n        removeAllGlobalListeners();\n        setIsDecrementPressed(null);\n      },\n      onPressEnd: e => {\n        clearAsync();\n        if (e.pointerType === 'touch') {\n          if (!isSpinning.current && isUp.current) {\n            onDecrement?.();\n          }\n        }\n        isUp.current = false;\n        setIsDecrementPressed(null);\n      },\n      onFocus,\n      onBlur\n    }\n  };\n}\n"],"names":[],"version":3,"file":"useSpinButton.mjs.map"}