@react-aria/spinbutton 3.6.18 → 3.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"mappings":";;AAsBA,gCAAiC,SAAQ,SAAS,EAAE,WAAW,MAAM,CAAC,EAAE,UAAU,MAAM,CAAC,EAAE,eAAe,MAAM,CAAC;IAC/G,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;IACzB,eAAe,CAAC,EAAE,MAAM,IAAI,CAAC;IAC7B,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;IACzB,eAAe,CAAC,EAAE,MAAM,IAAI,CAAC;IAC7B,gBAAgB,CAAC,EAAE,MAAM,IAAI,CAAC;IAC9B,gBAAgB,CAAC,EAAE,MAAM,IAAI,CAAA;CAC9B;AAED;IACE,eAAe,EAAE,aAAa,CAAC;IAC/B,oBAAoB,EAAE,eAAe,CAAC;IACtC,oBAAoB,EAAE,eAAe,CAAA;CACtC;AAED,8BACE,KAAK,EAAE,eAAe,GACrB,cAAc,CA8KhB","sources":["packages/@react-aria/spinbutton/src/packages/@react-aria/spinbutton/src/useSpinButton.ts","packages/@react-aria/spinbutton/src/packages/@react-aria/spinbutton/src/index.ts","packages/@react-aria/spinbutton/src/index.ts"],"sourcesContent":[null,null,"/*\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 */\nexport type {SpinButtonProps, SpinbuttonAria} from './useSpinButton';\nexport {useSpinButton} from './useSpinButton';\n"],"names":[],"version":3,"file":"types.d.ts.map"}
1
+ {"mappings":";;AAsBA,gCAAiC,SAAQ,SAAS,EAAE,WAAW,MAAM,CAAC,EAAE,UAAU,MAAM,CAAC,EAAE,eAAe,MAAM,CAAC;IAC/G,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;IACzB,eAAe,CAAC,EAAE,MAAM,IAAI,CAAC;IAC7B,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;IACzB,eAAe,CAAC,EAAE,MAAM,IAAI,CAAC;IAC7B,gBAAgB,CAAC,EAAE,MAAM,IAAI,CAAC;IAC9B,gBAAgB,CAAC,EAAE,MAAM,IAAI,CAAA;CAC9B;AAED;IACE,eAAe,EAAE,aAAa,CAAC;IAC/B,oBAAoB,EAAE,eAAe,CAAC;IACtC,oBAAoB,EAAE,eAAe,CAAA;CACtC;AAED,8BACE,KAAK,EAAE,eAAe,GACrB,cAAc,CA0QhB","sources":["packages/@react-aria/spinbutton/src/packages/@react-aria/spinbutton/src/useSpinButton.ts","packages/@react-aria/spinbutton/src/packages/@react-aria/spinbutton/src/index.ts","packages/@react-aria/spinbutton/src/index.ts"],"sourcesContent":[null,null,"/*\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 */\nexport type {SpinButtonProps, SpinbuttonAria} from './useSpinButton';\nexport {useSpinButton} from './useSpinButton';\n"],"names":[],"version":3,"file":"types.d.ts.map"}
@@ -33,9 +33,16 @@ function $37bbd4c129023f61$export$e908e06f4b8e3402(props) {
33
33
  const _async = (0, $2pZbw$react.useRef)(undefined);
34
34
  let { value: value, textValue: textValue, minValue: minValue, maxValue: maxValue, isDisabled: isDisabled, isReadOnly: isReadOnly, isRequired: isRequired, onIncrement: onIncrement, onIncrementPage: onIncrementPage, onDecrement: onDecrement, onDecrementPage: onDecrementPage, onDecrementToMin: onDecrementToMin, onIncrementToMax: onIncrementToMax } = props;
35
35
  const stringFormatter = (0, $2pZbw$reactariai18n.useLocalizedStringFormatter)((0, ($parcel$interopDefault($cb4b786159079747$exports))), '@react-aria/spinbutton');
36
- const clearAsync = ()=>clearTimeout(_async.current);
36
+ let isSpinning = (0, $2pZbw$react.useRef)(false);
37
+ const clearAsync = (0, $2pZbw$react.useCallback)(()=>{
38
+ clearTimeout(_async.current);
39
+ isSpinning.current = false;
40
+ }, []);
41
+ const clearAsyncEvent = (0, $2pZbw$reactariautils.useEffectEvent)(()=>{
42
+ clearAsync();
43
+ });
37
44
  (0, $2pZbw$react.useEffect)(()=>{
38
- return ()=>clearAsync();
45
+ return ()=>clearAsyncEvent();
39
46
  }, []);
40
47
  let onKeyDown = (e)=>{
41
48
  if (e.ctrlKey || e.metaKey || e.shiftKey || e.altKey || isReadOnly || e.nativeEvent.isComposing) return;
@@ -102,8 +109,15 @@ function $37bbd4c129023f61$export$e908e06f4b8e3402(props) {
102
109
  }, [
103
110
  ariaTextValue
104
111
  ]);
105
- const onIncrementPressStart = (0, $2pZbw$reactariautils.useEffectEvent)((initialStepDelay)=>{
112
+ // For touch users, if they move their finger like they're scrolling, we don't want to trigger a spin.
113
+ let onPointerCancel = (0, $2pZbw$react.useCallback)(()=>{
106
114
  clearAsync();
115
+ }, [
116
+ clearAsync
117
+ ]);
118
+ const onIncrementPressStart = (0, $2pZbw$reactariautils.useEffectEvent)((initialStepDelay)=>{
119
+ clearAsyncEvent();
120
+ isSpinning.current = true;
107
121
  onIncrement === null || onIncrement === void 0 ? void 0 : onIncrement();
108
122
  // Start spinning after initial delay
109
123
  _async.current = window.setTimeout(()=>{
@@ -111,7 +125,8 @@ function $37bbd4c129023f61$export$e908e06f4b8e3402(props) {
111
125
  }, initialStepDelay);
112
126
  });
113
127
  const onDecrementPressStart = (0, $2pZbw$reactariautils.useEffectEvent)((initialStepDelay)=>{
114
- clearAsync();
128
+ clearAsyncEvent();
129
+ isSpinning.current = true;
115
130
  onDecrement === null || onDecrement === void 0 ? void 0 : onDecrement();
116
131
  // Start spinning after initial delay
117
132
  _async.current = window.setTimeout(()=>{
@@ -122,6 +137,25 @@ function $37bbd4c129023f61$export$e908e06f4b8e3402(props) {
122
137
  e.preventDefault();
123
138
  };
124
139
  let { addGlobalListener: addGlobalListener, removeAllGlobalListeners: removeAllGlobalListeners } = (0, $2pZbw$reactariautils.useGlobalListeners)();
140
+ // Tracks in touch if the press end event was preceded by a press up.
141
+ // If it wasn't, then we know the finger left the button while still in contact with the screen.
142
+ // This means that the user is trying to scroll or interact in some way that shouldn't trigger
143
+ // an increment or decrement.
144
+ let isUp = (0, $2pZbw$react.useRef)(false);
145
+ let [isIncrementPressed, setIsIncrementPressed] = (0, $2pZbw$react.useState)(null);
146
+ (0, $2pZbw$react.useEffect)(()=>{
147
+ if (isIncrementPressed === 'touch') onIncrementPressStart(60);
148
+ else if (isIncrementPressed) onIncrementPressStart(400);
149
+ }, [
150
+ isIncrementPressed
151
+ ]);
152
+ let [isDecrementPressed, setIsDecrementPressed] = (0, $2pZbw$react.useState)(null);
153
+ (0, $2pZbw$react.useEffect)(()=>{
154
+ if (isDecrementPressed === 'touch') onDecrementPressStart(60);
155
+ else if (isDecrementPressed) onDecrementPressStart(400);
156
+ }, [
157
+ isDecrementPressed
158
+ ]);
125
159
  return {
126
160
  spinButtonProps: {
127
161
  role: 'spinbutton',
@@ -137,25 +171,68 @@ function $37bbd4c129023f61$export$e908e06f4b8e3402(props) {
137
171
  onBlur: onBlur
138
172
  },
139
173
  incrementButtonProps: {
140
- onPressStart: ()=>{
141
- onIncrementPressStart(400);
174
+ onPressStart: (e)=>{
175
+ if (e.pointerType !== 'touch') setIsIncrementPressed('mouse');
176
+ else {
177
+ if (_async.current) clearAsync();
178
+ addGlobalListener(window, 'pointercancel', onPointerCancel, {
179
+ capture: true
180
+ });
181
+ isUp.current = false;
182
+ // For touch users, don't trigger a decrement on press start, we'll wait for the press end to trigger it if
183
+ // the control isn't spinning.
184
+ _async.current = window.setTimeout(()=>{
185
+ setIsIncrementPressed('touch');
186
+ }, 600);
187
+ }
142
188
  addGlobalListener(window, 'contextmenu', cancelContextMenu);
143
189
  },
144
- onPressEnd: ()=>{
190
+ onPressUp: (e)=>{
191
+ if (e.pointerType === 'touch') isUp.current = true;
145
192
  clearAsync();
146
193
  removeAllGlobalListeners();
194
+ setIsIncrementPressed(null);
195
+ },
196
+ onPressEnd: (e)=>{
197
+ if (e.pointerType === 'touch') {
198
+ if (!isSpinning.current && isUp.current) onIncrement === null || onIncrement === void 0 ? void 0 : onIncrement();
199
+ }
200
+ clearAsync();
201
+ isUp.current = false;
202
+ setIsIncrementPressed(null);
147
203
  },
148
204
  onFocus: onFocus,
149
205
  onBlur: onBlur
150
206
  },
151
207
  decrementButtonProps: {
152
- onPressStart: ()=>{
153
- onDecrementPressStart(400);
154
- addGlobalListener(window, 'contextmenu', cancelContextMenu);
208
+ onPressStart: (e)=>{
209
+ if (e.pointerType !== 'touch') setIsDecrementPressed('mouse');
210
+ else {
211
+ if (_async.current) clearAsync();
212
+ addGlobalListener(window, 'pointercancel', onPointerCancel, {
213
+ capture: true
214
+ });
215
+ isUp.current = false;
216
+ // For touch users, don't trigger a decrement on press start, we'll wait for the press end to trigger it if
217
+ // the control isn't spinning.
218
+ _async.current = window.setTimeout(()=>{
219
+ setIsDecrementPressed('touch');
220
+ }, 600);
221
+ }
155
222
  },
156
- onPressEnd: ()=>{
223
+ onPressUp: (e)=>{
224
+ if (e.pointerType === 'touch') isUp.current = true;
157
225
  clearAsync();
158
226
  removeAllGlobalListeners();
227
+ setIsDecrementPressed(null);
228
+ },
229
+ onPressEnd: (e)=>{
230
+ if (e.pointerType === 'touch') {
231
+ if (!isSpinning.current && isUp.current) onDecrement === null || onDecrement === void 0 ? void 0 : onDecrement();
232
+ }
233
+ clearAsync();
234
+ isUp.current = false;
235
+ setIsDecrementPressed(null);
159
236
  },
160
237
  onFocus: onFocus,
161
238
  onBlur: onBlur
@@ -1 +1 @@
1
- {"mappings":"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","sources":["packages/@react-aria/spinbutton/src/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 '@react-aria/live-announcer';\nimport {AriaButtonProps} from '@react-types/button';\nimport {DOMAttributes, InputBase, RangeInputBase, Validation, ValueBase} from '@react-types/shared';\n// @ts-ignore\nimport intlMessages from '../intl/*.json';\nimport {useEffect, useRef} from 'react';\nimport {useEffectEvent, useGlobalListeners} from '@react-aria/utils';\nimport {useLocalizedStringFormatter} from '@react-aria/i18n';\n\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 const clearAsync = () => clearTimeout(_async.current);\n\n\n useEffect(() => {\n return () => clearAsync();\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 const onIncrementPressStart = useEffectEvent(\n (initialStepDelay: number) => {\n clearAsync();\n onIncrement?.();\n // Start spinning after initial delay\n _async.current = window.setTimeout(\n () => {\n if ((maxValue === undefined || isNaN(maxValue)) || (value === undefined || isNaN(value)) || value < maxValue) {\n onIncrementPressStart(60);\n }\n },\n initialStepDelay\n );\n }\n );\n\n const onDecrementPressStart = useEffectEvent(\n (initialStepDelay: number) => {\n clearAsync();\n onDecrement?.();\n // Start spinning after initial delay\n _async.current = window.setTimeout(\n () => {\n if ((minValue === undefined || isNaN(minValue)) || (value === undefined || isNaN(value)) || value > minValue) {\n onDecrementPressStart(60);\n }\n },\n initialStepDelay\n );\n }\n );\n\n let cancelContextMenu = (e) => {\n e.preventDefault();\n };\n\n let {addGlobalListener, removeAllGlobalListeners} = useGlobalListeners();\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: () => {\n onIncrementPressStart(400);\n addGlobalListener(window, 'contextmenu', cancelContextMenu);\n },\n onPressEnd: () => {\n clearAsync();\n removeAllGlobalListeners();\n },\n onFocus,\n onBlur\n },\n decrementButtonProps: {\n onPressStart: () => {\n onDecrementPressStart(400);\n addGlobalListener(window, 'contextmenu', cancelContextMenu);\n },\n onPressEnd: () => {\n clearAsync();\n removeAllGlobalListeners();\n },\n onFocus,\n onBlur\n }\n };\n}\n"],"names":[],"version":3,"file":"useSpinButton.main.js.map"}
1
+ {"mappings":"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","sources":["packages/@react-aria/spinbutton/src/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 '@react-aria/live-announcer';\nimport {AriaButtonProps} from '@react-types/button';\nimport {DOMAttributes, InputBase, RangeInputBase, Validation, ValueBase} from '@react-types/shared';\n// @ts-ignore\nimport intlMessages from '../intl/*.json';\nimport {useCallback, useEffect, useRef, useState} from 'react';\nimport {useEffectEvent, useGlobalListeners} from '@react-aria/utils';\nimport {useLocalizedStringFormatter} from '@react-aria/i18n';\n\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 onIncrementPressStart = useEffectEvent(\n (initialStepDelay: number) => {\n clearAsyncEvent();\n isSpinning.current = true;\n onIncrement?.();\n // Start spinning after initial delay\n _async.current = window.setTimeout(\n () => {\n if ((maxValue === undefined || isNaN(maxValue)) || (value === undefined || isNaN(value)) || value < maxValue) {\n onIncrementPressStart(60);\n }\n },\n initialStepDelay\n );\n }\n );\n\n const onDecrementPressStart = useEffectEvent(\n (initialStepDelay: number) => {\n clearAsyncEvent();\n isSpinning.current = true;\n onDecrement?.();\n // Start spinning after initial delay\n _async.current = window.setTimeout(\n () => {\n if ((minValue === undefined || isNaN(minValue)) || (value === undefined || isNaN(value)) || value > minValue) {\n onDecrementPressStart(60);\n }\n },\n initialStepDelay\n );\n }\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 onIncrementPressStart(60);\n } else if (isIncrementPressed) {\n onIncrementPressStart(400);\n }\n }, [isIncrementPressed]);\n\n let [isDecrementPressed, setIsDecrementPressed] = useState<'touch' | 'mouse' | null>(null);\n useEffect(() => {\n if (isDecrementPressed === 'touch') {\n onDecrementPressStart(60);\n } else if (isDecrementPressed) {\n onDecrementPressStart(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 if (e.pointerType !== 'touch') {\n setIsIncrementPressed('mouse');\n } else {\n if (_async.current) {\n clearAsync();\n }\n\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 _async.current = window.setTimeout(() => {\n setIsIncrementPressed('touch');\n }, 600);\n }\n addGlobalListener(window, 'contextmenu', cancelContextMenu);\n },\n onPressUp: (e) => {\n if (e.pointerType === 'touch') {\n isUp.current = true;\n }\n clearAsync();\n removeAllGlobalListeners();\n setIsIncrementPressed(null);\n },\n onPressEnd: (e) => {\n if (e.pointerType === 'touch') {\n if (!isSpinning.current && isUp.current) {\n onIncrement?.();\n }\n }\n clearAsync();\n isUp.current = false;\n setIsIncrementPressed(null);\n },\n onFocus,\n onBlur\n },\n decrementButtonProps: {\n onPressStart: (e) => {\n if (e.pointerType !== 'touch') {\n setIsDecrementPressed('mouse');\n } else {\n if (_async.current) {\n clearAsync();\n }\n\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 _async.current = window.setTimeout(() => {\n setIsDecrementPressed('touch');\n }, 600);\n }\n },\n onPressUp: (e) => {\n if (e.pointerType === 'touch') {\n isUp.current = true;\n }\n clearAsync();\n removeAllGlobalListeners();\n setIsDecrementPressed(null);\n },\n onPressEnd: (e) => {\n if (e.pointerType === 'touch') {\n if (!isSpinning.current && isUp.current) {\n onDecrement?.();\n }\n }\n clearAsync();\n isUp.current = false;\n setIsDecrementPressed(null);\n },\n onFocus,\n onBlur\n }\n };\n}\n"],"names":[],"version":3,"file":"useSpinButton.main.js.map"}
@@ -1,6 +1,6 @@
1
1
  import $5rwhf$intlStringsmodulejs from "./intlStrings.mjs";
2
2
  import {clearAnnouncer as $5rwhf$clearAnnouncer, announce as $5rwhf$announce} from "@react-aria/live-announcer";
3
- import {useRef as $5rwhf$useRef, useEffect as $5rwhf$useEffect} from "react";
3
+ import {useRef as $5rwhf$useRef, useCallback as $5rwhf$useCallback, useEffect as $5rwhf$useEffect, useState as $5rwhf$useState} from "react";
4
4
  import {useEffectEvent as $5rwhf$useEffectEvent, useGlobalListeners as $5rwhf$useGlobalListeners} from "@react-aria/utils";
5
5
  import {useLocalizedStringFormatter as $5rwhf$useLocalizedStringFormatter} from "@react-aria/i18n";
6
6
 
@@ -27,9 +27,16 @@ function $d2e8511e6f209edf$export$e908e06f4b8e3402(props) {
27
27
  const _async = (0, $5rwhf$useRef)(undefined);
28
28
  let { value: value, textValue: textValue, minValue: minValue, maxValue: maxValue, isDisabled: isDisabled, isReadOnly: isReadOnly, isRequired: isRequired, onIncrement: onIncrement, onIncrementPage: onIncrementPage, onDecrement: onDecrement, onDecrementPage: onDecrementPage, onDecrementToMin: onDecrementToMin, onIncrementToMax: onIncrementToMax } = props;
29
29
  const stringFormatter = (0, $5rwhf$useLocalizedStringFormatter)((0, ($parcel$interopDefault($5rwhf$intlStringsmodulejs))), '@react-aria/spinbutton');
30
- const clearAsync = ()=>clearTimeout(_async.current);
30
+ let isSpinning = (0, $5rwhf$useRef)(false);
31
+ const clearAsync = (0, $5rwhf$useCallback)(()=>{
32
+ clearTimeout(_async.current);
33
+ isSpinning.current = false;
34
+ }, []);
35
+ const clearAsyncEvent = (0, $5rwhf$useEffectEvent)(()=>{
36
+ clearAsync();
37
+ });
31
38
  (0, $5rwhf$useEffect)(()=>{
32
- return ()=>clearAsync();
39
+ return ()=>clearAsyncEvent();
33
40
  }, []);
34
41
  let onKeyDown = (e)=>{
35
42
  if (e.ctrlKey || e.metaKey || e.shiftKey || e.altKey || isReadOnly || e.nativeEvent.isComposing) return;
@@ -96,8 +103,15 @@ function $d2e8511e6f209edf$export$e908e06f4b8e3402(props) {
96
103
  }, [
97
104
  ariaTextValue
98
105
  ]);
99
- const onIncrementPressStart = (0, $5rwhf$useEffectEvent)((initialStepDelay)=>{
106
+ // For touch users, if they move their finger like they're scrolling, we don't want to trigger a spin.
107
+ let onPointerCancel = (0, $5rwhf$useCallback)(()=>{
100
108
  clearAsync();
109
+ }, [
110
+ clearAsync
111
+ ]);
112
+ const onIncrementPressStart = (0, $5rwhf$useEffectEvent)((initialStepDelay)=>{
113
+ clearAsyncEvent();
114
+ isSpinning.current = true;
101
115
  onIncrement === null || onIncrement === void 0 ? void 0 : onIncrement();
102
116
  // Start spinning after initial delay
103
117
  _async.current = window.setTimeout(()=>{
@@ -105,7 +119,8 @@ function $d2e8511e6f209edf$export$e908e06f4b8e3402(props) {
105
119
  }, initialStepDelay);
106
120
  });
107
121
  const onDecrementPressStart = (0, $5rwhf$useEffectEvent)((initialStepDelay)=>{
108
- clearAsync();
122
+ clearAsyncEvent();
123
+ isSpinning.current = true;
109
124
  onDecrement === null || onDecrement === void 0 ? void 0 : onDecrement();
110
125
  // Start spinning after initial delay
111
126
  _async.current = window.setTimeout(()=>{
@@ -116,6 +131,25 @@ function $d2e8511e6f209edf$export$e908e06f4b8e3402(props) {
116
131
  e.preventDefault();
117
132
  };
118
133
  let { addGlobalListener: addGlobalListener, removeAllGlobalListeners: removeAllGlobalListeners } = (0, $5rwhf$useGlobalListeners)();
134
+ // Tracks in touch if the press end event was preceded by a press up.
135
+ // If it wasn't, then we know the finger left the button while still in contact with the screen.
136
+ // This means that the user is trying to scroll or interact in some way that shouldn't trigger
137
+ // an increment or decrement.
138
+ let isUp = (0, $5rwhf$useRef)(false);
139
+ let [isIncrementPressed, setIsIncrementPressed] = (0, $5rwhf$useState)(null);
140
+ (0, $5rwhf$useEffect)(()=>{
141
+ if (isIncrementPressed === 'touch') onIncrementPressStart(60);
142
+ else if (isIncrementPressed) onIncrementPressStart(400);
143
+ }, [
144
+ isIncrementPressed
145
+ ]);
146
+ let [isDecrementPressed, setIsDecrementPressed] = (0, $5rwhf$useState)(null);
147
+ (0, $5rwhf$useEffect)(()=>{
148
+ if (isDecrementPressed === 'touch') onDecrementPressStart(60);
149
+ else if (isDecrementPressed) onDecrementPressStart(400);
150
+ }, [
151
+ isDecrementPressed
152
+ ]);
119
153
  return {
120
154
  spinButtonProps: {
121
155
  role: 'spinbutton',
@@ -131,25 +165,68 @@ function $d2e8511e6f209edf$export$e908e06f4b8e3402(props) {
131
165
  onBlur: onBlur
132
166
  },
133
167
  incrementButtonProps: {
134
- onPressStart: ()=>{
135
- onIncrementPressStart(400);
168
+ onPressStart: (e)=>{
169
+ if (e.pointerType !== 'touch') setIsIncrementPressed('mouse');
170
+ else {
171
+ if (_async.current) clearAsync();
172
+ addGlobalListener(window, 'pointercancel', onPointerCancel, {
173
+ capture: true
174
+ });
175
+ isUp.current = false;
176
+ // For touch users, don't trigger a decrement on press start, we'll wait for the press end to trigger it if
177
+ // the control isn't spinning.
178
+ _async.current = window.setTimeout(()=>{
179
+ setIsIncrementPressed('touch');
180
+ }, 600);
181
+ }
136
182
  addGlobalListener(window, 'contextmenu', cancelContextMenu);
137
183
  },
138
- onPressEnd: ()=>{
184
+ onPressUp: (e)=>{
185
+ if (e.pointerType === 'touch') isUp.current = true;
139
186
  clearAsync();
140
187
  removeAllGlobalListeners();
188
+ setIsIncrementPressed(null);
189
+ },
190
+ onPressEnd: (e)=>{
191
+ if (e.pointerType === 'touch') {
192
+ if (!isSpinning.current && isUp.current) onIncrement === null || onIncrement === void 0 ? void 0 : onIncrement();
193
+ }
194
+ clearAsync();
195
+ isUp.current = false;
196
+ setIsIncrementPressed(null);
141
197
  },
142
198
  onFocus: onFocus,
143
199
  onBlur: onBlur
144
200
  },
145
201
  decrementButtonProps: {
146
- onPressStart: ()=>{
147
- onDecrementPressStart(400);
148
- addGlobalListener(window, 'contextmenu', cancelContextMenu);
202
+ onPressStart: (e)=>{
203
+ if (e.pointerType !== 'touch') setIsDecrementPressed('mouse');
204
+ else {
205
+ if (_async.current) clearAsync();
206
+ addGlobalListener(window, 'pointercancel', onPointerCancel, {
207
+ capture: true
208
+ });
209
+ isUp.current = false;
210
+ // For touch users, don't trigger a decrement on press start, we'll wait for the press end to trigger it if
211
+ // the control isn't spinning.
212
+ _async.current = window.setTimeout(()=>{
213
+ setIsDecrementPressed('touch');
214
+ }, 600);
215
+ }
149
216
  },
150
- onPressEnd: ()=>{
217
+ onPressUp: (e)=>{
218
+ if (e.pointerType === 'touch') isUp.current = true;
151
219
  clearAsync();
152
220
  removeAllGlobalListeners();
221
+ setIsDecrementPressed(null);
222
+ },
223
+ onPressEnd: (e)=>{
224
+ if (e.pointerType === 'touch') {
225
+ if (!isSpinning.current && isUp.current) onDecrement === null || onDecrement === void 0 ? void 0 : onDecrement();
226
+ }
227
+ clearAsync();
228
+ isUp.current = false;
229
+ setIsDecrementPressed(null);
153
230
  },
154
231
  onFocus: onFocus,
155
232
  onBlur: onBlur
@@ -1,6 +1,6 @@
1
1
  import $5rwhf$intlStringsmodulejs from "./intlStrings.module.js";
2
2
  import {clearAnnouncer as $5rwhf$clearAnnouncer, announce as $5rwhf$announce} from "@react-aria/live-announcer";
3
- import {useRef as $5rwhf$useRef, useEffect as $5rwhf$useEffect} from "react";
3
+ import {useRef as $5rwhf$useRef, useCallback as $5rwhf$useCallback, useEffect as $5rwhf$useEffect, useState as $5rwhf$useState} from "react";
4
4
  import {useEffectEvent as $5rwhf$useEffectEvent, useGlobalListeners as $5rwhf$useGlobalListeners} from "@react-aria/utils";
5
5
  import {useLocalizedStringFormatter as $5rwhf$useLocalizedStringFormatter} from "@react-aria/i18n";
6
6
 
@@ -27,9 +27,16 @@ function $d2e8511e6f209edf$export$e908e06f4b8e3402(props) {
27
27
  const _async = (0, $5rwhf$useRef)(undefined);
28
28
  let { value: value, textValue: textValue, minValue: minValue, maxValue: maxValue, isDisabled: isDisabled, isReadOnly: isReadOnly, isRequired: isRequired, onIncrement: onIncrement, onIncrementPage: onIncrementPage, onDecrement: onDecrement, onDecrementPage: onDecrementPage, onDecrementToMin: onDecrementToMin, onIncrementToMax: onIncrementToMax } = props;
29
29
  const stringFormatter = (0, $5rwhf$useLocalizedStringFormatter)((0, ($parcel$interopDefault($5rwhf$intlStringsmodulejs))), '@react-aria/spinbutton');
30
- const clearAsync = ()=>clearTimeout(_async.current);
30
+ let isSpinning = (0, $5rwhf$useRef)(false);
31
+ const clearAsync = (0, $5rwhf$useCallback)(()=>{
32
+ clearTimeout(_async.current);
33
+ isSpinning.current = false;
34
+ }, []);
35
+ const clearAsyncEvent = (0, $5rwhf$useEffectEvent)(()=>{
36
+ clearAsync();
37
+ });
31
38
  (0, $5rwhf$useEffect)(()=>{
32
- return ()=>clearAsync();
39
+ return ()=>clearAsyncEvent();
33
40
  }, []);
34
41
  let onKeyDown = (e)=>{
35
42
  if (e.ctrlKey || e.metaKey || e.shiftKey || e.altKey || isReadOnly || e.nativeEvent.isComposing) return;
@@ -96,8 +103,15 @@ function $d2e8511e6f209edf$export$e908e06f4b8e3402(props) {
96
103
  }, [
97
104
  ariaTextValue
98
105
  ]);
99
- const onIncrementPressStart = (0, $5rwhf$useEffectEvent)((initialStepDelay)=>{
106
+ // For touch users, if they move their finger like they're scrolling, we don't want to trigger a spin.
107
+ let onPointerCancel = (0, $5rwhf$useCallback)(()=>{
100
108
  clearAsync();
109
+ }, [
110
+ clearAsync
111
+ ]);
112
+ const onIncrementPressStart = (0, $5rwhf$useEffectEvent)((initialStepDelay)=>{
113
+ clearAsyncEvent();
114
+ isSpinning.current = true;
101
115
  onIncrement === null || onIncrement === void 0 ? void 0 : onIncrement();
102
116
  // Start spinning after initial delay
103
117
  _async.current = window.setTimeout(()=>{
@@ -105,7 +119,8 @@ function $d2e8511e6f209edf$export$e908e06f4b8e3402(props) {
105
119
  }, initialStepDelay);
106
120
  });
107
121
  const onDecrementPressStart = (0, $5rwhf$useEffectEvent)((initialStepDelay)=>{
108
- clearAsync();
122
+ clearAsyncEvent();
123
+ isSpinning.current = true;
109
124
  onDecrement === null || onDecrement === void 0 ? void 0 : onDecrement();
110
125
  // Start spinning after initial delay
111
126
  _async.current = window.setTimeout(()=>{
@@ -116,6 +131,25 @@ function $d2e8511e6f209edf$export$e908e06f4b8e3402(props) {
116
131
  e.preventDefault();
117
132
  };
118
133
  let { addGlobalListener: addGlobalListener, removeAllGlobalListeners: removeAllGlobalListeners } = (0, $5rwhf$useGlobalListeners)();
134
+ // Tracks in touch if the press end event was preceded by a press up.
135
+ // If it wasn't, then we know the finger left the button while still in contact with the screen.
136
+ // This means that the user is trying to scroll or interact in some way that shouldn't trigger
137
+ // an increment or decrement.
138
+ let isUp = (0, $5rwhf$useRef)(false);
139
+ let [isIncrementPressed, setIsIncrementPressed] = (0, $5rwhf$useState)(null);
140
+ (0, $5rwhf$useEffect)(()=>{
141
+ if (isIncrementPressed === 'touch') onIncrementPressStart(60);
142
+ else if (isIncrementPressed) onIncrementPressStart(400);
143
+ }, [
144
+ isIncrementPressed
145
+ ]);
146
+ let [isDecrementPressed, setIsDecrementPressed] = (0, $5rwhf$useState)(null);
147
+ (0, $5rwhf$useEffect)(()=>{
148
+ if (isDecrementPressed === 'touch') onDecrementPressStart(60);
149
+ else if (isDecrementPressed) onDecrementPressStart(400);
150
+ }, [
151
+ isDecrementPressed
152
+ ]);
119
153
  return {
120
154
  spinButtonProps: {
121
155
  role: 'spinbutton',
@@ -131,25 +165,68 @@ function $d2e8511e6f209edf$export$e908e06f4b8e3402(props) {
131
165
  onBlur: onBlur
132
166
  },
133
167
  incrementButtonProps: {
134
- onPressStart: ()=>{
135
- onIncrementPressStart(400);
168
+ onPressStart: (e)=>{
169
+ if (e.pointerType !== 'touch') setIsIncrementPressed('mouse');
170
+ else {
171
+ if (_async.current) clearAsync();
172
+ addGlobalListener(window, 'pointercancel', onPointerCancel, {
173
+ capture: true
174
+ });
175
+ isUp.current = false;
176
+ // For touch users, don't trigger a decrement on press start, we'll wait for the press end to trigger it if
177
+ // the control isn't spinning.
178
+ _async.current = window.setTimeout(()=>{
179
+ setIsIncrementPressed('touch');
180
+ }, 600);
181
+ }
136
182
  addGlobalListener(window, 'contextmenu', cancelContextMenu);
137
183
  },
138
- onPressEnd: ()=>{
184
+ onPressUp: (e)=>{
185
+ if (e.pointerType === 'touch') isUp.current = true;
139
186
  clearAsync();
140
187
  removeAllGlobalListeners();
188
+ setIsIncrementPressed(null);
189
+ },
190
+ onPressEnd: (e)=>{
191
+ if (e.pointerType === 'touch') {
192
+ if (!isSpinning.current && isUp.current) onIncrement === null || onIncrement === void 0 ? void 0 : onIncrement();
193
+ }
194
+ clearAsync();
195
+ isUp.current = false;
196
+ setIsIncrementPressed(null);
141
197
  },
142
198
  onFocus: onFocus,
143
199
  onBlur: onBlur
144
200
  },
145
201
  decrementButtonProps: {
146
- onPressStart: ()=>{
147
- onDecrementPressStart(400);
148
- addGlobalListener(window, 'contextmenu', cancelContextMenu);
202
+ onPressStart: (e)=>{
203
+ if (e.pointerType !== 'touch') setIsDecrementPressed('mouse');
204
+ else {
205
+ if (_async.current) clearAsync();
206
+ addGlobalListener(window, 'pointercancel', onPointerCancel, {
207
+ capture: true
208
+ });
209
+ isUp.current = false;
210
+ // For touch users, don't trigger a decrement on press start, we'll wait for the press end to trigger it if
211
+ // the control isn't spinning.
212
+ _async.current = window.setTimeout(()=>{
213
+ setIsDecrementPressed('touch');
214
+ }, 600);
215
+ }
149
216
  },
150
- onPressEnd: ()=>{
217
+ onPressUp: (e)=>{
218
+ if (e.pointerType === 'touch') isUp.current = true;
151
219
  clearAsync();
152
220
  removeAllGlobalListeners();
221
+ setIsDecrementPressed(null);
222
+ },
223
+ onPressEnd: (e)=>{
224
+ if (e.pointerType === 'touch') {
225
+ if (!isSpinning.current && isUp.current) onDecrement === null || onDecrement === void 0 ? void 0 : onDecrement();
226
+ }
227
+ clearAsync();
228
+ isUp.current = false;
229
+ setIsDecrementPressed(null);
153
230
  },
154
231
  onFocus: onFocus,
155
232
  onBlur: onBlur
@@ -1 +1 @@
1
- {"mappings":"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","sources":["packages/@react-aria/spinbutton/src/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 '@react-aria/live-announcer';\nimport {AriaButtonProps} from '@react-types/button';\nimport {DOMAttributes, InputBase, RangeInputBase, Validation, ValueBase} from '@react-types/shared';\n// @ts-ignore\nimport intlMessages from '../intl/*.json';\nimport {useEffect, useRef} from 'react';\nimport {useEffectEvent, useGlobalListeners} from '@react-aria/utils';\nimport {useLocalizedStringFormatter} from '@react-aria/i18n';\n\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 const clearAsync = () => clearTimeout(_async.current);\n\n\n useEffect(() => {\n return () => clearAsync();\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 const onIncrementPressStart = useEffectEvent(\n (initialStepDelay: number) => {\n clearAsync();\n onIncrement?.();\n // Start spinning after initial delay\n _async.current = window.setTimeout(\n () => {\n if ((maxValue === undefined || isNaN(maxValue)) || (value === undefined || isNaN(value)) || value < maxValue) {\n onIncrementPressStart(60);\n }\n },\n initialStepDelay\n );\n }\n );\n\n const onDecrementPressStart = useEffectEvent(\n (initialStepDelay: number) => {\n clearAsync();\n onDecrement?.();\n // Start spinning after initial delay\n _async.current = window.setTimeout(\n () => {\n if ((minValue === undefined || isNaN(minValue)) || (value === undefined || isNaN(value)) || value > minValue) {\n onDecrementPressStart(60);\n }\n },\n initialStepDelay\n );\n }\n );\n\n let cancelContextMenu = (e) => {\n e.preventDefault();\n };\n\n let {addGlobalListener, removeAllGlobalListeners} = useGlobalListeners();\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: () => {\n onIncrementPressStart(400);\n addGlobalListener(window, 'contextmenu', cancelContextMenu);\n },\n onPressEnd: () => {\n clearAsync();\n removeAllGlobalListeners();\n },\n onFocus,\n onBlur\n },\n decrementButtonProps: {\n onPressStart: () => {\n onDecrementPressStart(400);\n addGlobalListener(window, 'contextmenu', cancelContextMenu);\n },\n onPressEnd: () => {\n clearAsync();\n removeAllGlobalListeners();\n },\n onFocus,\n onBlur\n }\n };\n}\n"],"names":[],"version":3,"file":"useSpinButton.module.js.map"}
1
+ {"mappings":"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","sources":["packages/@react-aria/spinbutton/src/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 '@react-aria/live-announcer';\nimport {AriaButtonProps} from '@react-types/button';\nimport {DOMAttributes, InputBase, RangeInputBase, Validation, ValueBase} from '@react-types/shared';\n// @ts-ignore\nimport intlMessages from '../intl/*.json';\nimport {useCallback, useEffect, useRef, useState} from 'react';\nimport {useEffectEvent, useGlobalListeners} from '@react-aria/utils';\nimport {useLocalizedStringFormatter} from '@react-aria/i18n';\n\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 onIncrementPressStart = useEffectEvent(\n (initialStepDelay: number) => {\n clearAsyncEvent();\n isSpinning.current = true;\n onIncrement?.();\n // Start spinning after initial delay\n _async.current = window.setTimeout(\n () => {\n if ((maxValue === undefined || isNaN(maxValue)) || (value === undefined || isNaN(value)) || value < maxValue) {\n onIncrementPressStart(60);\n }\n },\n initialStepDelay\n );\n }\n );\n\n const onDecrementPressStart = useEffectEvent(\n (initialStepDelay: number) => {\n clearAsyncEvent();\n isSpinning.current = true;\n onDecrement?.();\n // Start spinning after initial delay\n _async.current = window.setTimeout(\n () => {\n if ((minValue === undefined || isNaN(minValue)) || (value === undefined || isNaN(value)) || value > minValue) {\n onDecrementPressStart(60);\n }\n },\n initialStepDelay\n );\n }\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 onIncrementPressStart(60);\n } else if (isIncrementPressed) {\n onIncrementPressStart(400);\n }\n }, [isIncrementPressed]);\n\n let [isDecrementPressed, setIsDecrementPressed] = useState<'touch' | 'mouse' | null>(null);\n useEffect(() => {\n if (isDecrementPressed === 'touch') {\n onDecrementPressStart(60);\n } else if (isDecrementPressed) {\n onDecrementPressStart(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 if (e.pointerType !== 'touch') {\n setIsIncrementPressed('mouse');\n } else {\n if (_async.current) {\n clearAsync();\n }\n\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 _async.current = window.setTimeout(() => {\n setIsIncrementPressed('touch');\n }, 600);\n }\n addGlobalListener(window, 'contextmenu', cancelContextMenu);\n },\n onPressUp: (e) => {\n if (e.pointerType === 'touch') {\n isUp.current = true;\n }\n clearAsync();\n removeAllGlobalListeners();\n setIsIncrementPressed(null);\n },\n onPressEnd: (e) => {\n if (e.pointerType === 'touch') {\n if (!isSpinning.current && isUp.current) {\n onIncrement?.();\n }\n }\n clearAsync();\n isUp.current = false;\n setIsIncrementPressed(null);\n },\n onFocus,\n onBlur\n },\n decrementButtonProps: {\n onPressStart: (e) => {\n if (e.pointerType !== 'touch') {\n setIsDecrementPressed('mouse');\n } else {\n if (_async.current) {\n clearAsync();\n }\n\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 _async.current = window.setTimeout(() => {\n setIsDecrementPressed('touch');\n }, 600);\n }\n },\n onPressUp: (e) => {\n if (e.pointerType === 'touch') {\n isUp.current = true;\n }\n clearAsync();\n removeAllGlobalListeners();\n setIsDecrementPressed(null);\n },\n onPressEnd: (e) => {\n if (e.pointerType === 'touch') {\n if (!isSpinning.current && isUp.current) {\n onDecrement?.();\n }\n }\n clearAsync();\n isUp.current = false;\n setIsDecrementPressed(null);\n },\n onFocus,\n onBlur\n }\n };\n}\n"],"names":[],"version":3,"file":"useSpinButton.module.js.map"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@react-aria/spinbutton",
3
- "version": "3.6.18",
3
+ "version": "3.7.0",
4
4
  "description": "Spectrum UI components in React",
5
5
  "license": "Apache-2.0",
6
6
  "main": "dist/main.js",
@@ -26,11 +26,11 @@
26
26
  "url": "https://github.com/adobe/react-spectrum"
27
27
  },
28
28
  "dependencies": {
29
- "@react-aria/i18n": "^3.12.12",
29
+ "@react-aria/i18n": "^3.12.14",
30
30
  "@react-aria/live-announcer": "^3.4.4",
31
- "@react-aria/utils": "^3.30.1",
32
- "@react-types/button": "^3.14.0",
33
- "@react-types/shared": "^3.32.0",
31
+ "@react-aria/utils": "^3.32.0",
32
+ "@react-types/button": "^3.14.1",
33
+ "@react-types/shared": "^3.32.1",
34
34
  "@swc/helpers": "^0.5.0"
35
35
  },
36
36
  "peerDependencies": {
@@ -40,5 +40,5 @@
40
40
  "publishConfig": {
41
41
  "access": "public"
42
42
  },
43
- "gitHead": "2c58ed3ddd9be9100af9b1d0cfd137fcdc95ba2d"
43
+ "gitHead": "4d838da5bfe36abb35aed166995a9ef63825370f"
44
44
  }
@@ -15,7 +15,7 @@ import {AriaButtonProps} from '@react-types/button';
15
15
  import {DOMAttributes, InputBase, RangeInputBase, Validation, ValueBase} from '@react-types/shared';
16
16
  // @ts-ignore
17
17
  import intlMessages from '../intl/*.json';
18
- import {useEffect, useRef} from 'react';
18
+ import {useCallback, useEffect, useRef, useState} from 'react';
19
19
  import {useEffectEvent, useGlobalListeners} from '@react-aria/utils';
20
20
  import {useLocalizedStringFormatter} from '@react-aria/i18n';
21
21
 
@@ -57,11 +57,17 @@ export function useSpinButton(
57
57
  } = props;
58
58
  const stringFormatter = useLocalizedStringFormatter(intlMessages, '@react-aria/spinbutton');
59
59
 
60
- const clearAsync = () => clearTimeout(_async.current);
61
-
60
+ let isSpinning = useRef(false);
61
+ const clearAsync = useCallback(() => {
62
+ clearTimeout(_async.current);
63
+ isSpinning.current = false;
64
+ }, []);
65
+ const clearAsyncEvent = useEffectEvent(() => {
66
+ clearAsync();
67
+ });
62
68
 
63
69
  useEffect(() => {
64
- return () => clearAsync();
70
+ return () => clearAsyncEvent();
65
71
  }, []);
66
72
 
67
73
  let onKeyDown = (e) => {
@@ -135,9 +141,15 @@ export function useSpinButton(
135
141
  }
136
142
  }, [ariaTextValue]);
137
143
 
144
+ // For touch users, if they move their finger like they're scrolling, we don't want to trigger a spin.
145
+ let onPointerCancel = useCallback(() => {
146
+ clearAsync();
147
+ }, [clearAsync]);
148
+
138
149
  const onIncrementPressStart = useEffectEvent(
139
150
  (initialStepDelay: number) => {
140
- clearAsync();
151
+ clearAsyncEvent();
152
+ isSpinning.current = true;
141
153
  onIncrement?.();
142
154
  // Start spinning after initial delay
143
155
  _async.current = window.setTimeout(
@@ -153,7 +165,8 @@ export function useSpinButton(
153
165
 
154
166
  const onDecrementPressStart = useEffectEvent(
155
167
  (initialStepDelay: number) => {
156
- clearAsync();
168
+ clearAsyncEvent();
169
+ isSpinning.current = true;
157
170
  onDecrement?.();
158
171
  // Start spinning after initial delay
159
172
  _async.current = window.setTimeout(
@@ -173,6 +186,30 @@ export function useSpinButton(
173
186
 
174
187
  let {addGlobalListener, removeAllGlobalListeners} = useGlobalListeners();
175
188
 
189
+ // Tracks in touch if the press end event was preceded by a press up.
190
+ // If it wasn't, then we know the finger left the button while still in contact with the screen.
191
+ // This means that the user is trying to scroll or interact in some way that shouldn't trigger
192
+ // an increment or decrement.
193
+ let isUp = useRef(false);
194
+
195
+ let [isIncrementPressed, setIsIncrementPressed] = useState<'touch' | 'mouse' | null>(null);
196
+ useEffect(() => {
197
+ if (isIncrementPressed === 'touch') {
198
+ onIncrementPressStart(60);
199
+ } else if (isIncrementPressed) {
200
+ onIncrementPressStart(400);
201
+ }
202
+ }, [isIncrementPressed]);
203
+
204
+ let [isDecrementPressed, setIsDecrementPressed] = useState<'touch' | 'mouse' | null>(null);
205
+ useEffect(() => {
206
+ if (isDecrementPressed === 'touch') {
207
+ onDecrementPressStart(60);
208
+ } else if (isDecrementPressed) {
209
+ onDecrementPressStart(400);
210
+ }
211
+ }, [isDecrementPressed]);
212
+
176
213
  return {
177
214
  spinButtonProps: {
178
215
  role: 'spinbutton',
@@ -188,25 +225,80 @@ export function useSpinButton(
188
225
  onBlur
189
226
  },
190
227
  incrementButtonProps: {
191
- onPressStart: () => {
192
- onIncrementPressStart(400);
228
+ onPressStart: (e) => {
229
+ if (e.pointerType !== 'touch') {
230
+ setIsIncrementPressed('mouse');
231
+ } else {
232
+ if (_async.current) {
233
+ clearAsync();
234
+ }
235
+
236
+ addGlobalListener(window, 'pointercancel', onPointerCancel, {capture: true});
237
+ isUp.current = false;
238
+ // For touch users, don't trigger a decrement on press start, we'll wait for the press end to trigger it if
239
+ // the control isn't spinning.
240
+ _async.current = window.setTimeout(() => {
241
+ setIsIncrementPressed('touch');
242
+ }, 600);
243
+ }
193
244
  addGlobalListener(window, 'contextmenu', cancelContextMenu);
194
245
  },
195
- onPressEnd: () => {
246
+ onPressUp: (e) => {
247
+ if (e.pointerType === 'touch') {
248
+ isUp.current = true;
249
+ }
196
250
  clearAsync();
197
251
  removeAllGlobalListeners();
252
+ setIsIncrementPressed(null);
253
+ },
254
+ onPressEnd: (e) => {
255
+ if (e.pointerType === 'touch') {
256
+ if (!isSpinning.current && isUp.current) {
257
+ onIncrement?.();
258
+ }
259
+ }
260
+ clearAsync();
261
+ isUp.current = false;
262
+ setIsIncrementPressed(null);
198
263
  },
199
264
  onFocus,
200
265
  onBlur
201
266
  },
202
267
  decrementButtonProps: {
203
- onPressStart: () => {
204
- onDecrementPressStart(400);
205
- addGlobalListener(window, 'contextmenu', cancelContextMenu);
268
+ onPressStart: (e) => {
269
+ if (e.pointerType !== 'touch') {
270
+ setIsDecrementPressed('mouse');
271
+ } else {
272
+ if (_async.current) {
273
+ clearAsync();
274
+ }
275
+
276
+ addGlobalListener(window, 'pointercancel', onPointerCancel, {capture: true});
277
+ isUp.current = false;
278
+ // For touch users, don't trigger a decrement on press start, we'll wait for the press end to trigger it if
279
+ // the control isn't spinning.
280
+ _async.current = window.setTimeout(() => {
281
+ setIsDecrementPressed('touch');
282
+ }, 600);
283
+ }
206
284
  },
207
- onPressEnd: () => {
285
+ onPressUp: (e) => {
286
+ if (e.pointerType === 'touch') {
287
+ isUp.current = true;
288
+ }
208
289
  clearAsync();
209
290
  removeAllGlobalListeners();
291
+ setIsDecrementPressed(null);
292
+ },
293
+ onPressEnd: (e) => {
294
+ if (e.pointerType === 'touch') {
295
+ if (!isSpinning.current && isUp.current) {
296
+ onDecrement?.();
297
+ }
298
+ }
299
+ clearAsync();
300
+ isUp.current = false;
301
+ setIsDecrementPressed(null);
210
302
  },
211
303
  onFocus,
212
304
  onBlur