{"mappings":";;;;;;;;;;;AAAA;;;;;;;;;;CAUC;;;;;;AAcD,MAAM,6BAAO,KAAO;AAkBb,SAAS,0CACd,KAAsB;IAEtB,MAAM,SAAS,CAAA,GAAA,aAAK,EAAU;IAC9B,IAAI,SACF,KAAK,aACL,SAAS,YACT,QAAQ,YACR,QAAQ,cACR,UAAU,cACV,UAAU,cACV,UAAU,eACV,WAAW,mBACX,eAAe,eACf,WAAW,mBACX,eAAe,oBACf,gBAAgB,oBAChB,gBAAgB,EACjB,GAAG;IACJ,MAAM,kBAAkB,CAAA,GAAA,yCAA0B,EAAE,CAAA,GAAA,+CAAW,GAAG;IAElE,IAAI,aAAa,CAAA,GAAA,aAAK,EAAE;IACxB,MAAM,aAAa,CAAA,GAAA,kBAAU,EAAE;QAC7B,aAAa,OAAO,OAAO;QAC3B,WAAW,OAAO,GAAG;IACvB,GAAG,EAAE;IACL,MAAM,kBAAkB,CAAA,GAAA,yCAAa,EAAE;QACrC;IACF;IAEA,CAAA,GAAA,gBAAQ,EAAE;QACR,OAAO,IAAM;IACf,GAAG,EAAE;IAEL,IAAI,YAAY,CAAC;QACf,IAAI,EAAE,OAAO,IAAI,EAAE,OAAO,IAAI,EAAE,QAAQ,IAAI,EAAE,MAAM,IAAI,cAAc,EAAE,WAAW,CAAC,WAAW,EAC7F;QAGF,OAAQ,EAAE,GAAG;YACX,KAAK;gBACH,IAAI,iBAAiB;oBACnB,EAAE,cAAc;oBAChB;oBACA;gBACF;YACF,eAAe;YACf,KAAK;YACL,KAAK;gBACH,IAAI,aAAa;oBACf,EAAE,cAAc;oBAChB;gBACF;gBACA;YACF,KAAK;gBACH,IAAI,iBAAiB;oBACnB,EAAE,cAAc;oBAChB;oBACA;gBACF;YACF,cAAc;YACd,KAAK;YACL,KAAK;gBACH,IAAI,aAAa;oBACf,EAAE,cAAc;oBAChB;gBACF;gBACA;YACF,KAAK;gBACH,IAAI,kBAAkB;oBACpB,EAAE,cAAc;oBAChB;gBACF;gBACA;YACF,KAAK;gBACH,IAAI,kBAAkB;oBACpB,EAAE,cAAc;oBAChB;gBACF;gBACA;QACJ;IACF;IAEA,IAAI,YAAY,CAAA,GAAA,aAAK,EAAE;IACvB,IAAI,UAAU;QACZ,UAAU,OAAO,GAAG;IACtB;IAEA,IAAI,SAAS;QACX,UAAU,OAAO,GAAG;IACtB;IAEA,kEAAkE;IAClE,8GAA8G;IAC9G,sHAAsH;IACtH,4HAA4H;IAC5H,IAAI,gBAAgB,cAAc,KAAK,gBAAgB,MAAM,CAAC,WAAW,AAAC,CAAA,aAAa,GAAG,OAAO,AAAD,EAAG,OAAO,CAAC,KAAK;IAEhH,CAAA,GAAA,gBAAQ,EAAE;QACR,IAAI,UAAU,OAAO,EAAE;YACrB,CAAA,GAAA,yCAAa,EAAE;YACf,CAAA,GAAA,yCAAO,EAAE,eAAe;QAC1B;IACF,GAAG;QAAC;KAAc;IAElB,sGAAsG;IACtG,IAAI,kBAAkB,CAAA,GAAA,kBAAU,EAAE;QAChC;IACF,GAAG;QAAC;KAAW;IAEf,MAAM,mBAAmB,CAAA,GAAA,yCAAa,EAAE,eAAe;IACvD,MAAM,mBAAmB,CAAA,GAAA,yCAAa,EAAE,eAAe;IAEvD,MAAM,cAAc,CAAA,GAAA,yCAAa,EAAE;QACjC,IAAI,aAAa,aAAa,MAAM,aAAa,UAAU,aAAa,MAAM,UAAU,QAAQ,UAAU;YACxG;YACA,2BAA2B;QAC7B;IACF;IAEA,MAAM,6BAA6B,CAAA,GAAA,yCAAa,EAAE,CAAC;QACjD;QACA,WAAW,OAAO,GAAG;QACrB,qCAAqC;QACrC,OAAO,OAAO,GAAG,OAAO,UAAU,CAAC,aAAa;IAClD;IAEA,MAAM,gBAAgB,CAAA,GAAA,yCAAa,EAAE;QACnC,IAAI,aAAa,aAAa,MAAM,aAAa,UAAU,aAAa,MAAM,UAAU,QAAQ,UAAU;YACxG;YACA,2BAA2B;QAC7B;IACF;IAEA,MAAM,6BAA6B,CAAA,GAAA,yCAAa,EAAE,CAAC;QACjD;QACA,WAAW,OAAO,GAAG;QACrB,qCAAqC;QACrC,OAAO,OAAO,GAAG,OAAO,UAAU,CAAC,eAAe;IACpD;IAEA,IAAI,oBAAoB,CAAC;QACvB,EAAE,cAAc;IAClB;IAEA,IAAI,qBAAC,iBAAiB,4BAAE,wBAAwB,EAAC,GAAG,CAAA,GAAA,yCAAiB;IAErE,qEAAqE;IACrE,gGAAgG;IAChG,8FAA8F;IAC9F,6BAA6B;IAC7B,IAAI,OAAO,CAAA,GAAA,aAAK,EAAE;IAElB,IAAI,CAAC,oBAAoB,sBAAsB,GAAG,CAAA,GAAA,eAAO,EAA4B;IACrF,CAAA,GAAA,gBAAQ,EAAE;QACR,IAAI,uBAAuB,SACzB,2BAA2B;aACtB,IAAI,oBACT,2BAA2B;IAE/B,GAAG;QAAC;KAAmB;IAEvB,IAAI,CAAC,oBAAoB,sBAAsB,GAAG,CAAA,GAAA,eAAO,EAA4B;IACrF,CAAA,GAAA,gBAAQ,EAAE;QACR,IAAI,uBAAuB,SACzB,2BAA2B;aACtB,IAAI,oBACT,2BAA2B;IAE/B,GAAG;QAAC;KAAmB;IAEvB,OAAO;QACL,iBAAiB;YACf,MAAM;YACN,iBAAiB,UAAU,aAAa,CAAC,MAAM,SAAS,QAAQ;YAChE,kBAAkB;YAClB,iBAAiB;YACjB,iBAAiB;YACjB,iBAAiB,cAAc;YAC/B,iBAAiB,cAAc;YAC/B,iBAAiB,cAAc;uBAC/B;qBACA;oBACA;QACF;QACA,sBAAsB;YACpB,cAAc,CAAC;gBACb;gBACA,IAAI,EAAE,WAAW,KAAK,SAAS;oBAC7B;oBACA,sBAAsB;gBACxB,OAAO;oBACL,kBAAkB,QAAQ,iBAAiB,iBAAiB;wBAAC,SAAS;oBAAI;oBAC1E,KAAK,OAAO,GAAG;oBACf,2GAA2G;oBAC3G,8BAA8B;oBAC9B,sBAAsB;gBACxB;gBACA,kBAAkB,QAAQ,eAAe;YAC3C;YACA,WAAW,CAAC;gBACV;gBACA,IAAI,EAAE,WAAW,KAAK,SACpB,KAAK,OAAO,GAAG;gBAEjB;gBACA,sBAAsB;YACxB;YACA,YAAY,CAAC;gBACX;gBACA,IAAI,EAAE,WAAW,KAAK,SACpB;oBAAA,IAAI,CAAC,WAAW,OAAO,IAAI,KAAK,OAAO,EACrC;gBACF;gBAEF,KAAK,OAAO,GAAG;gBACf,sBAAsB;YACxB;qBACA;oBACA;QACF;QACA,sBAAsB;YACpB,cAAc,CAAC;gBACb;gBACA,IAAI,EAAE,WAAW,KAAK,SAAS;oBAC7B;oBACA,sBAAsB;gBACxB,OAAO;oBACL,kBAAkB,QAAQ,iBAAiB,iBAAiB;wBAAC,SAAS;oBAAI;oBAC1E,KAAK,OAAO,GAAG;oBACf,2GAA2G;oBAC3G,8BAA8B;oBAC9B,sBAAsB;gBACxB;YACF;YACA,WAAW,CAAC;gBACV;gBACA,IAAI,EAAE,WAAW,KAAK,SACpB,KAAK,OAAO,GAAG;gBAEjB;gBACA,sBAAsB;YACxB;YACA,YAAY,CAAC;gBACX;gBACA,IAAI,EAAE,WAAW,KAAK,SACpB;oBAAA,IAAI,CAAC,WAAW,OAAO,IAAI,KAAK,OAAO,EACrC;gBACF;gBAEF,KAAK,OAAO,GAAG;gBACf,sBAAsB;YACxB;qBACA;oBACA;QACF;IACF;AACF","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\n\nconst noop = () => {};\n\nexport interface SpinButtonProps 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(\n  props: SpinButtonProps\n): 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 (e.ctrlKey || e.metaKey || e.shiftKey || e.altKey || isReadOnly || e.nativeEvent.isComposing) {\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 = textValue === '' ? stringFormatter.format('Empty') : (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 (maxValue === undefined || isNaN(maxValue) || value === undefined || isNaN(value) || value < maxValue) {\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 (minValue === undefined || isNaN(minValue) || value === undefined || isNaN(value) || value > minValue) {\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    }\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    }\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"}