react-aria
Version:
Spectrum UI components in React
1 lines • 14.1 kB
Source Map (JSON)
{"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';\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"}