Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 5 additions & 8 deletions .github/ISSUE_TEMPLATE/Bug_Report.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ body:
attributes:
label: 😯 Current Behavior
description: |
Tell us what happens instead of the expected behavior. Please include a link to the storybook if possible.
Tell us what happens instead of the expected behavior. Please include a link to our docs, our storybook, or a StackBlitz reproduction if possible.
If you are seeing an error, please include the full error message and stack trace.
validations:
required: true
Expand All @@ -44,19 +44,16 @@ body:
Providing context helps us come up with a solution that is most useful in the real world.
How has this issue affected you? What are you trying to accomplish?
validations:
required: false
required: true
- type: textarea
id: base-reproduction
attributes:
label: 🖥️ Steps to Reproduce
description: |
Provide a minimal test case that can reproduce the problem. Include a CodeSandBox, link to sample repos, or specific doc examples to help us test your issue more easily.
Provide a minimal test case that can reproduce the problem. Include a StackBlitz, link to sample repos, or specific doc examples to help us test your issue more easily.

To get started, you can use the following CodeSandBox templates:
For React Spectrum: https://codesandbox.io/s/react-spectrum-template-syueo
For React Aria: https://codesandbox.io/s/react-aria-template-389r79
For React Aria Components: https://codesandbox.io/s/react-aria-components-template-g7wmmk
For React Spectrum S2 with Style Macros: https://codesandbox.io/p/devbox/react-spectrum-s2-style-macro-template-h6fpsq
To get a StackBlitz template, go to an example in the docs and select the "Open in StackBlitz" option in the menu next to the example.
Be sure to fork/save the StackBlitz project before sharing the link.
validations:
required: true
- type: markdown
Expand Down
5 changes: 2 additions & 3 deletions packages/@adobe/react-spectrum/src/actionbar/ActionBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -110,9 +110,8 @@ function ActionBarInner<T>(props: ActionBarInnerProps<T>, ref: Ref<HTMLDivElemen
}

let {keyboardProps} = useKeyboard({
onKeyDown(e) {
if (e.key === 'Escape') {
e.preventDefault();
shortcuts: {
Escape: () => {
onClearSelection();
}
}
Expand Down
7 changes: 2 additions & 5 deletions packages/@react-spectrum/s2/src/ActionBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -167,12 +167,9 @@ const ActionBarInner = forwardRef(function ActionBarInner(
});

let {keyboardProps} = useKeyboard({
onKeyDown(e) {
if (e.key === 'Escape') {
e.preventDefault();
shortcuts: {
Escape: () => {
onClearSelection?.();
} else {
e.continuePropagation();
}
}
});
Expand Down
2 changes: 1 addition & 1 deletion packages/@react-spectrum/s2/src/TableView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -690,7 +690,7 @@ function CellFocusRing() {
className={style({
...cellFocus,
position: 'absolute',
top: 'var(--topFocusRing)',
top: 'var(--topFocusRing, 0)',
bottom: 0,
insetStart: 0,
insetEnd: 0,
Expand Down
37 changes: 37 additions & 0 deletions packages/react-aria-components/test/ListBox.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2396,3 +2396,40 @@ describe('ListBox', () => {
});
}
});

describe('keyboard modifier keys', () => {
let user;
let platformMock;
beforeAll(() => {
user = userEvent.setup({delay: null, pointerMap});
});
// selectionMode: 'none', 'single', 'multiple'
// selectionBehavior: 'toggle', 'replace'
// platform: 'mac', 'windows'

// modifier key: 'alt', 'ctrl', 'meta', 'shift'
// key: 'arrow-up', 'arrow-down', 'arrow-left', 'arrow-right', 'home', 'end', 'page-up', 'page-down', 'enter', 'space', 'tab'
// expected behavior: 'navigate', 'select', 'toggle', 'replace'
describe('mac', () => {
beforeAll(() => {
platformMock = jest.spyOn(navigator, 'platform', 'get').mockImplementation(() => 'Mac');
});
afterAll(() => {
platformMock.mockRestore();
});
it('should not navigate when using unsupported modifier keys', async () => {
let {getByRole} = renderListbox({selectionMode: 'none'});
await user.tab();
let listbox = getByRole('listbox');
let options = within(listbox).getAllByRole('option');
await user.keyboard('{ArrowDown}');
expect(document.activeElement).toBe(options[1]);
await user.keyboard('{Meta>}{ArrowDown}{/Meta}');
expect(document.activeElement).toBe(options[1]);
await user.keyboard('{Meta>}{ArrowUp}{/Meta}');
expect(document.activeElement).toBe(options[1]);
await user.keyboard('{Control>}{Home}{/Control}');
expect(document.activeElement).toBe(options[1]);
});
});
});
40 changes: 18 additions & 22 deletions packages/react-aria/src/actiongroup/useActionGroup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,11 @@ import {
} from '@react-types/shared';
import {createFocusManager} from '../focus/FocusScope';
import {filterDOMProps} from '../utils/filterDOMProps';
import {getEventTarget, nodeContains} from '../utils/shadowdom/DOMFunctions';
import {KeyboardEventHandler, useState} from 'react';
import {ListState} from 'react-stately/useListState';
import {useKeyboard} from '../interactions/useKeyboard';
import {useLayoutEffect} from '../utils/useLayoutEffect';
import {useLocale} from '../i18n/I18nProvider';
import {useState} from 'react';

const BUTTON_GROUP_ROLES = {
none: 'toolbar',
Expand Down Expand Up @@ -91,34 +91,30 @@ export function useActionGroup<T>(
let {direction} = useLocale();
let focusManager = createFocusManager(ref);
let flipDirection = direction === 'rtl' && orientation === 'horizontal';
let onKeyDown: KeyboardEventHandler = e => {
if (!nodeContains(e.currentTarget, getEventTarget(e))) {
return;
}

switch (e.key) {
case 'ArrowRight':
case 'ArrowDown':
e.preventDefault();
e.stopPropagation();
if (e.key === 'ArrowRight' && flipDirection) {
let {keyboardProps} = useKeyboard({
shortcuts: {
ArrowRight: () => {
if (flipDirection) {
focusManager.focusPrevious({wrap: true});
} else {
focusManager.focusNext({wrap: true});
}
break;
case 'ArrowLeft':
case 'ArrowUp':
e.preventDefault();
e.stopPropagation();
if (e.key === 'ArrowLeft' && flipDirection) {
},
ArrowDown: () => {
focusManager.focusNext({wrap: true});
},
ArrowLeft: () => {
if (flipDirection) {
focusManager.focusNext({wrap: true});
} else {
focusManager.focusPrevious({wrap: true});
}
break;
},
ArrowUp: () => {
focusManager.focusPrevious({wrap: true});
}
}
};
});

let role: string | undefined = BUTTON_GROUP_ROLES[state.selectionManager.selectionMode];
if (isInToolbar && role === 'toolbar') {
Expand All @@ -130,7 +126,7 @@ export function useActionGroup<T>(
role,
'aria-orientation': role === 'toolbar' ? orientation : undefined,
'aria-disabled': isDisabled,
onKeyDown
...keyboardProps
}
};
}
9 changes: 8 additions & 1 deletion packages/react-aria/src/autocomplete/useAutocomplete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,14 @@ export function useAutocomplete<T>(
let onChange = (value: string) => {
// Tell wrapped collection to focus the first element in the list when typing forward and to clear focused key when modifying the text via
// copy paste/backspacing/undo/redo for screen reader announcements
if (lastInputType.current === 'insertText' && !disableAutoFocusFirst) {
if (
(lastInputType.current === 'insertText' ||
// IME composition (e.g. CJK input) reports 'insertCompositionText'/'insertFromComposition'
// instead of 'insertText'. Treat these as forward typing so the first item gets virtual focus.
lastInputType.current === 'insertCompositionText' ||
lastInputType.current === 'insertFromComposition') &&
!disableAutoFocusFirst
) {
focusFirstItem();
} else if (
lastInputType.current &&
Expand Down
84 changes: 38 additions & 46 deletions packages/react-aria/src/calendar/useCalendarGrid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,13 @@ import {CalendarDate, startOfWeek, today} from '@internationalized/date';
import {CalendarSelectionMode, CalendarState} from 'react-stately/useCalendarState';
import {DOMAttributes} from '@react-types/shared';
import {hookData, useVisibleRangeDescription} from './utils';
import {KeyboardEvent, useMemo} from 'react';
import {mergeProps} from '../utils/mergeProps';
import {RangeCalendarState} from 'react-stately/useRangeCalendarState';
import {useDateFormatter} from '../i18n/useDateFormatter';
import {useKeyboard} from '../interactions/useKeyboard';
import {useLabels} from '../utils/useLabels';
import {useLocale} from '../i18n/I18nProvider';
import {useMemo} from 'react';

export interface AriaCalendarGridProps {
/**
Expand Down Expand Up @@ -78,70 +79,61 @@ export function useCalendarGrid(

let {direction} = useLocale();

let onKeyDown = (e: KeyboardEvent) => {
switch (e.key) {
case 'Enter':
case ' ':
e.preventDefault();
let {keyboardProps} = useKeyboard({
shortcuts: {
Enter: () => {
state.selectFocusedDate();
break;
case 'PageUp':
e.preventDefault();
e.stopPropagation();
state.focusPreviousSection(e.shiftKey);
break;
case 'PageDown':
e.preventDefault();
e.stopPropagation();
state.focusNextSection(e.shiftKey);
break;
case 'End':
e.preventDefault();
e.stopPropagation();
},
' ': () => {
state.selectFocusedDate();
},
PageUp: () => {
state.focusPreviousSection();
},
'Shift+PageUp': () => {
state.focusPreviousSection(true);
},
PageDown: () => {
state.focusNextSection();
},
'Shift+PageDown': () => {
state.focusNextSection(true);
},
End: () => {
state.focusSectionEnd();
break;
case 'Home':
e.preventDefault();
e.stopPropagation();
},
Home: () => {
state.focusSectionStart();
break;
case 'ArrowLeft':
e.preventDefault();
e.stopPropagation();
},
ArrowLeft: () => {
if (direction === 'rtl') {
state.focusNextDay();
} else {
state.focusPreviousDay();
}
break;
case 'ArrowUp':
e.preventDefault();
e.stopPropagation();
},
ArrowUp: () => {
state.focusPreviousRow();
break;
case 'ArrowRight':
e.preventDefault();
e.stopPropagation();
},
ArrowRight: () => {
if (direction === 'rtl') {
state.focusPreviousDay();
} else {
state.focusNextDay();
}
break;
case 'ArrowDown':
e.preventDefault();
e.stopPropagation();
},
ArrowDown: () => {
state.focusNextRow();
break;
case 'Escape':
},
Escape: () => {
// Cancel the selection.
if ('setAnchorDate' in state) {
e.preventDefault();
state.setAnchorDate(null);
}
break;
return false; // TODO: is this really correct? or should it return true when we cancel and only propagate if there's nothing to do
}
}
};
});

let visibleRangeDescription = useVisibleRangeDescription(
startDate,
Expand Down Expand Up @@ -182,7 +174,7 @@ export function useCalendarGrid(
'aria-disabled': state.isDisabled || undefined,
'aria-multiselectable':
'highlightedRange' in state || state.selectionMode === 'multiple' || undefined,
onKeyDown,
...keyboardProps,
onFocus: () => state.setFocused(true),
onBlur: () => state.setFocused(false)
}),
Expand Down
Loading
Loading