Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
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
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 @@ -112,9 +112,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 @@ -168,12 +168,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
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]);
});
});
});
51 changes: 30 additions & 21 deletions packages/react-aria/src/actiongroup/useActionGroup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,11 @@ import {
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 +92,42 @@ 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: e => {
if (!nodeContains(e.currentTarget, getEventTarget(e))) {
return false;
}
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: e => {
if (!nodeContains(e.currentTarget, getEventTarget(e))) {
return false;
}
focusManager.focusNext({wrap: true});
},
ArrowLeft: e => {
if (!nodeContains(e.currentTarget, getEventTarget(e))) {
return false;
}
if (flipDirection) {
focusManager.focusNext({wrap: true});
} else {
focusManager.focusPrevious({wrap: true});
}
break;
},
ArrowUp: e => {
if (!nodeContains(e.currentTarget, getEventTarget(e))) {
return false;
}
focusManager.focusPrevious({wrap: true});
}
}
};
});

let role: string | undefined = BUTTON_GROUP_ROLES[state.selectionManager.selectionMode];
if (isInToolbar && role === 'toolbar') {
Expand All @@ -130,7 +139,7 @@ export function useActionGroup<T>(
role,
'aria-orientation': role === 'toolbar' ? orientation : undefined,
'aria-disabled': isDisabled,
onKeyDown
...keyboardProps
}
};
}
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
89 changes: 50 additions & 39 deletions packages/react-aria/src/color/useColorArea.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,46 +112,57 @@ export function useColorArea(props: AriaColorAreaOptions, state: ColorAreaState)

let currentPosition = useRef<{x: number; y: number} | null>(null);

let keyboardUpdate = (cb, inputRef: RefObject<HTMLInputElement | null>, input: 'x' | 'y') => {
state.setDragging(true);
setValueChangedViaKeyboard(true);
cb();
state.setDragging(false);
focusInput(inputRef);
setFocusedInput(input);
return true;
};

let {keyboardProps} = useKeyboard({
onKeyDown(e) {
// these are the cases that useMove doesn't handle
if (!/^(PageUp|PageDown|Home|End)$/.test(e.key)) {
e.continuePropagation();
return;
}
// same handling as useMove, don't need to stop propagation, useKeyboard will do that for us
e.preventDefault();
// remember to set this and unset it so that onChangeEnd is fired
state.setDragging(true);
setValueChangedViaKeyboard(true);
let dir;
switch (e.key) {
case 'PageUp':
state.incrementY(state.yChannelPageStep);
dir = 'y';
break;
case 'PageDown':
state.decrementY(state.yChannelPageStep);
dir = 'y';
break;
case 'Home':
direction === 'rtl'
? state.incrementX(state.xChannelPageStep)
: state.decrementX(state.xChannelPageStep);
dir = 'x';
break;
case 'End':
direction === 'rtl'
? state.decrementX(state.xChannelPageStep)
: state.incrementX(state.xChannelPageStep);
dir = 'x';
break;
}
state.setDragging(false);
if (dir) {
let input = dir === 'x' ? inputXRef : inputYRef;
focusInput(input);
setFocusedInput(dir);
shortcuts: {
PageUp: () => {
return keyboardUpdate(
() => {
state.incrementY(state.yChannelPageStep);
},
inputYRef,
'y'
);
},
PageDown: () => {
return keyboardUpdate(
() => {
state.decrementY(state.yChannelPageStep);
},
inputYRef,
'y'
);
},
Home: () => {
return keyboardUpdate(
() => {
direction === 'rtl'
? state.incrementX(state.xChannelPageStep)
: state.decrementX(state.xChannelPageStep);
},
inputXRef,
'x'
);
},
End: () => {
return keyboardUpdate(
() => {
direction === 'rtl'
? state.decrementX(state.xChannelPageStep)
: state.incrementX(state.xChannelPageStep);
},
inputXRef,
'x'
);
}
}
});
Expand Down
Loading