diff --git a/.yarn/patches/@testing-library-user-event-npm-14.6.1-5da7e1d4e2.patch b/.yarn/patches/@testing-library-user-event-npm-14.6.1-5da7e1d4e2.patch
index fc4c114ded1..16bf2b3d7d1 100644
--- a/.yarn/patches/@testing-library-user-event-npm-14.6.1-5da7e1d4e2.patch
+++ b/.yarn/patches/@testing-library-user-event-npm-14.6.1-5da7e1d4e2.patch
@@ -24,6 +24,42 @@ index 55027cb256f66b808d17280dc01bc55a796a1032..993d5de5a838a711d7ae009344354772
focus.focusElement(dest);
if (selection.hasOwnSelection(dest)) {
UI.setUISelection(dest, {
+diff --git a/dist/cjs/keyboard/index.js b/dist/cjs/keyboard/index.js
+index 18f5b4b8eb995fe5ecb352230f71b25e90f2a8a0..e5e17d1d3089aa8ea5c3f55a06aa746eb3a8258a 100644
+--- a/dist/cjs/keyboard/index.js
++++ b/dist/cjs/keyboard/index.js
+@@ -19,7 +19,7 @@ async function keyboardAction(instance, { keyDef, releasePrevious, releaseSelf,
+ }
+ if (!releasePrevious) {
+ for(let i = 1; i <= repeat; i++){
+- await system.keyboard.keydown(instance, keyDef);
++ await system.keyboard.keydown(instance, keyDef, i > 1);
+ if (i < repeat) {
+ await wait.wait(instance.config);
+ }
+diff --git a/dist/cjs/system/keyboard.js b/dist/cjs/system/keyboard.js
+index 3272fd7e642e79a8f67052b4b521355314cbc496..e1b16e9213eb609e7cd2335ecee792c3c19d35b4 100644
+--- a/dist/cjs/system/keyboard.js
++++ b/dist/cjs/system/keyboard.js
+@@ -52,7 +52,7 @@ class KeyboardHost {
+ getPressedKeys() {
+ return this.pressed.values().map((p)=>p.keyDef);
+ }
+- /** Press a key */ async keydown(instance, keyDef) {
++ /** Press a key */ async keydown(instance, keyDef, repeat = false) {
+ const key = String(keyDef.key);
+ const code = String(keyDef.code);
+ const target = getActiveElement.getActiveElementOrBody(instance.config.document);
+@@ -63,7 +63,8 @@ class KeyboardHost {
+ }
+ const unprevented = instance.dispatchUIEvent(target, 'keydown', {
+ key,
+- code
++ code,
++ repeat
+ });
+ if (isModifierLock(key) && !this.modifiers[key]) {
+ this.modifiers[key] = true;
diff --git a/dist/cjs/utils/focus/getActiveElement.js b/dist/cjs/utils/focus/getActiveElement.js
index d25f3a8ef67e856e43614559f73012899c0b53d7..4ed9ee45565ed438ee9284d8d3043c0bd50463eb 100644
--- a/dist/cjs/utils/focus/getActiveElement.js
@@ -37,3 +73,39 @@ index d25f3a8ef67e856e43614559f73012899c0b53d7..4ed9ee45565ed438ee9284d8d3043c0b
} else {
// Browser does not yield disabled elements as document.activeElement - jsdom does
if (isDisabled.isDisabled(activeElement)) {
+diff --git a/dist/esm/keyboard/index.js b/dist/esm/keyboard/index.js
+index 9c9feb7b82aabeadd8acbd9ec8f4127e302a4634..1456a5cf406382ee08b4bb7184499632bd8d3bd3 100644
+--- a/dist/esm/keyboard/index.js
++++ b/dist/esm/keyboard/index.js
+@@ -17,7 +17,7 @@ async function keyboardAction(instance, { keyDef, releasePrevious, releaseSelf,
+ }
+ if (!releasePrevious) {
+ for(let i = 1; i <= repeat; i++){
+- await system.keyboard.keydown(instance, keyDef);
++ await system.keyboard.keydown(instance, keyDef, i > 1);
+ if (i < repeat) {
+ await wait(instance.config);
+ }
+diff --git a/dist/esm/system/keyboard.js b/dist/esm/system/keyboard.js
+index 43700ca3a873aa6eb1cb4553c0889b685ff90fff..9607372660dcb1737b26abcdc4bc71f35e4d43c3 100644
+--- a/dist/esm/system/keyboard.js
++++ b/dist/esm/system/keyboard.js
+@@ -50,7 +50,7 @@ class KeyboardHost {
+ getPressedKeys() {
+ return this.pressed.values().map((p)=>p.keyDef);
+ }
+- /** Press a key */ async keydown(instance, keyDef) {
++ /** Press a key */ async keydown(instance, keyDef, repeat = false) {
+ const key = String(keyDef.key);
+ const code = String(keyDef.code);
+ const target = getActiveElementOrBody(instance.config.document);
+@@ -61,7 +61,8 @@ class KeyboardHost {
+ }
+ const unprevented = instance.dispatchUIEvent(target, 'keydown', {
+ key,
+- code
++ code,
++ repeat
+ });
+ if (isModifierLock(key) && !this.modifiers[key]) {
+ this.modifiers[key] = true;
diff --git a/packages/@adobe/react-spectrum/test/actiongroup/ActionGroup.test.js b/packages/@adobe/react-spectrum/test/actiongroup/ActionGroup.test.js
index ead720c81e0..d7653d3f550 100644
--- a/packages/@adobe/react-spectrum/test/actiongroup/ActionGroup.test.js
+++ b/packages/@adobe/react-spectrum/test/actiongroup/ActionGroup.test.js
@@ -226,6 +226,28 @@ describe('ActionGroup', function () {
}
);
+ it('should support repeat keydown events when holding an arrow key', async function () {
+ // Arrow key navigation is handled by useActionGroup's useKeyboard (allowRepeats).
+ let tree = render(
+
+
+ - Click me 1
+ - Click me 2
+ - Click me 3
+
+
+ );
+
+ let buttons = tree.getAllByRole('button');
+ await user.tab();
+ expect(document.activeElement).toBe(buttons[0]);
+
+ // Hold the key down for two keydown events (the second is a repeat).
+ await user.keyboard('{ArrowRight>2/}');
+
+ expect(document.activeElement).toBe(buttons[2]);
+ });
+
it.each`
Name | props | disabledKeys | orders
${'middle disabled'} | ${{locale: 'de-DE'}} | ${['1']} | ${[{action: tab, result: () => ['0', '-1', '-1']}, {action: pressArrowRight, result: () => ['-1', '-1', '0']}, {action: pressArrowRight, result: () => ['0', '-1', '-1']}, {action: pressArrowLeft, result: () => ['-1', '-1', '0']}, {action: pressArrowLeft, result: () => ['0', '-1', '-1']}]}
diff --git a/packages/react-aria-components/test/Calendar.test.js b/packages/react-aria-components/test/Calendar.test.js
index 4966edcb9af..b1aa4e93c35 100644
--- a/packages/react-aria-components/test/Calendar.test.js
+++ b/packages/react-aria-components/test/Calendar.test.js
@@ -509,6 +509,18 @@ describe('Calendar', () => {
expect(calendar.getByLabelText(/selected/)).toBe(day16);
});
+ if (parseInt(React.version, 10) >= 18) {
+ it('should support repeat keydown events when holding an arrow key', async () => {
+ let {getByRole} = renderCalendar({defaultFocusedValue: new CalendarDate(2020, 3, 3)});
+ let cell = getByRole('button', {name: 'Tuesday, March 3, 2020'});
+
+ await user.click(cell);
+ await user.keyboard('{ArrowRight>2/}');
+
+ expect(document.activeElement).toHaveAttribute('aria-label', 'Thursday, March 5, 2020');
+ });
+ }
+
it('should not become focused just by setting the focused date', async () => {
let DatePicker = () => {
let state = useContext(CalendarStateContext);
diff --git a/packages/react-aria-components/test/ColorArea.test.js b/packages/react-aria-components/test/ColorArea.test.js
index 3e04b6b7515..cc50b55cfac 100644
--- a/packages/react-aria-components/test/ColorArea.test.js
+++ b/packages/react-aria-components/test/ColorArea.test.js
@@ -175,6 +175,19 @@ describe('ColorArea', () => {
expect(wrapper).toHaveClass('disabled');
});
+ it('should support repeat keydown events when holding Page Up/Page Down', async () => {
+ // Page Up/Down adjust the Y (green) channel; the arrow keys are handled by useMove instead. The page step is 17.
+ let {getAllByRole} = renderColorArea();
+ let greenSlider = getAllByRole('slider', {hidden: true})[1];
+
+ await user.tab();
+ await user.keyboard('{PageUp>3/}');
+ expect(greenSlider).toHaveValue('51');
+
+ await user.keyboard('{PageDown>3/}');
+ expect(greenSlider).toHaveValue('0');
+ });
+
it('should support form prop', () => {
let {getByRole} = renderColorArea({form: 'test'});
let input = getByRole('slider');
diff --git a/packages/react-aria-components/test/ColorWheel.test.js b/packages/react-aria-components/test/ColorWheel.test.js
index c2665d0931a..a4af85c3fc0 100644
--- a/packages/react-aria-components/test/ColorWheel.test.js
+++ b/packages/react-aria-components/test/ColorWheel.test.js
@@ -173,6 +173,19 @@ describe('ColorWheel', () => {
expect(wrapper).toHaveClass('disabled');
});
+ it('should support repeat keydown events when holding Page Up/Page Down', async () => {
+ // Page Up/Down change the hue by the page step (15); the arrow keys are handled by useMove instead.
+ let {getByRole} = renderColorWheel();
+ let slider = getByRole('slider');
+
+ await user.tab();
+ await user.keyboard('{PageUp>3/}');
+ expect(slider).toHaveValue('45');
+
+ await user.keyboard('{PageDown>3/}');
+ expect(slider).toHaveValue('0');
+ });
+
it('should support form prop', () => {
let {getByRole} = renderColorWheel({form: 'test'});
let input = getByRole('slider');
diff --git a/packages/react-aria-components/test/DateField.test.js b/packages/react-aria-components/test/DateField.test.js
index 43d0e94ff00..d6212bd3163 100644
--- a/packages/react-aria-components/test/DateField.test.js
+++ b/packages/react-aria-components/test/DateField.test.js
@@ -495,6 +495,38 @@ describe('DateField', () => {
expect(document.activeElement).toBe(segments[0]);
});
+ it('should support repeat keydown events when holding backspace across empty segments', async () => {
+ // Backspace on an empty (placeholder) segment moves focus to the previous segment.
+ let {getAllByRole} = render(
+
+
+ {segment => }
+
+ );
+
+ let segments = getAllByRole('spinbutton');
+ await user.click(segments[2]);
+ await user.keyboard('{Backspace>2/}');
+
+ expect(document.activeElement).toBe(segments[0]);
+ });
+
+ it('should support repeat keydown events when holding an arrow key to navigate segments', async () => {
+ // ArrowLeft/ArrowRight move between segments
+ let {getAllByRole} = render(
+
+
+ {segment => }
+
+ );
+
+ let segments = getAllByRole('spinbutton');
+ await user.click(segments[0]);
+ await user.keyboard('{ArrowRight>2/}');
+
+ expect(document.activeElement).toBe(segments[2]);
+ });
+
it('should do nothing when pressing enter', async () => {
let {getAllByRole} = render(
diff --git a/packages/react-aria-components/test/NumberField.test.js b/packages/react-aria-components/test/NumberField.test.js
index 959e08cb62a..0a3363652c6 100644
--- a/packages/react-aria-components/test/NumberField.test.js
+++ b/packages/react-aria-components/test/NumberField.test.js
@@ -331,6 +331,23 @@ describe('NumberField', () => {
expect(onChange).toHaveBeenLastCalledWith(2);
});
+ it('should support repeat keydown events when holding an arrow key', async () => {
+ let onChange = jest.fn();
+ let {getByRole} = render();
+ let input = getByRole('textbox');
+
+ await user.tab();
+ expect(input).toHaveFocus();
+
+ await user.keyboard('{ArrowUp>3/}');
+ expect(input).toHaveValue('1,027');
+ expect(onChange).toHaveBeenLastCalledWith(1027);
+
+ await user.keyboard('{ArrowDown>3/}');
+ expect(input).toHaveValue('1,024');
+ expect(onChange).toHaveBeenLastCalledWith(1024);
+ });
+
it('should not type the grouping characters when useGrouping is false', async () => {
let {getByRole} = render();
let input = getByRole('textbox');
diff --git a/packages/react-aria-components/test/RadioGroup.test.js b/packages/react-aria-components/test/RadioGroup.test.js
index b0eb5a61ab1..596497a88a3 100644
--- a/packages/react-aria-components/test/RadioGroup.test.js
+++ b/packages/react-aria-components/test/RadioGroup.test.js
@@ -10,7 +10,7 @@
* governing permissions and limitations under the License.
*/
-import {act, pointerMap, render, within} from '@react-spectrum/test-utils-internal';
+import {act, fireEvent, pointerMap, render, within} from '@react-spectrum/test-utils-internal';
import {Button} from '../src/Button';
import {Dialog, DialogTrigger} from '../src/Dialog';
import {FieldError} from '../src/FieldError';
@@ -380,6 +380,23 @@ describe.each(['RadioGroup', 'RadioField'])('%s', comp => {
expect(label).not.toHaveClass('selected');
});
+ it('should support repeat keydown events when holding an arrow key', async () => {
+ let onChange = jest.fn();
+ let {getAllByRole} = renderGroup({onChange});
+ let radios = getAllByRole('radio');
+
+ await user.tab();
+ expect(radios[0]).toHaveFocus();
+
+ // user-event implements its own radio-group arrow navigation, which bypasses our own code
+ fireEvent.keyDown(document.activeElement, {key: 'ArrowDown'});
+ fireEvent.keyDown(document.activeElement, {key: 'ArrowDown', repeat: true});
+ fireEvent.keyUp(document.activeElement, {key: 'ArrowDown'});
+
+ expect(radios[2]).toBeChecked();
+ expect(onChange).toHaveBeenLastCalledWith('c');
+ });
+
it('should support read only state', () => {
let className = ({isReadOnly}) => (isReadOnly ? 'readonly' : '');
let {getByRole, getAllByRole} = renderGroup({isReadOnly: true, className}, {className});
diff --git a/packages/react-aria-components/test/Select.test.js b/packages/react-aria-components/test/Select.test.js
index df4906037d2..44f81b57a20 100644
--- a/packages/react-aria-components/test/Select.test.js
+++ b/packages/react-aria-components/test/Select.test.js
@@ -381,6 +381,36 @@ describe('Select', () => {
expect(button).toHaveTextContent('0');
});
+ it('should support repeat keydown events when holding an arrow key', async () => {
+ let onSelectionChange = jest.fn();
+ let {getByRole} = render(
+
+ );
+
+ let button = getByRole('button');
+ await user.tab();
+ expect(button).toHaveFocus();
+
+ // Arrow key navigation while closed moves the selection.
+ await user.keyboard('{ArrowRight>2/}');
+
+ expect(onSelectionChange).toHaveBeenLastCalledWith(1);
+ expect(button).toHaveTextContent('1');
+ });
+
it('should support falsy (0) as a valid default value', async () => {
let {getByRole} = render(