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
5 changes: 3 additions & 2 deletions packages/@adobe/react-spectrum/src/actionbar/ActionBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,9 @@ function ActionBarInner<T>(props: ActionBarInnerProps<T>, ref: Ref<HTMLDivElemen
}

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

let {keyboardProps} = useKeyboard({
shortcuts: {
Escape: () => {
onKeyDown(e) {
if (e.key === 'Escape') {
e.preventDefault();
onClearSelection?.();
} else {
e.continuePropagation();
}
}
});
Expand Down
37 changes: 0 additions & 37 deletions packages/react-aria-components/test/ListBox.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2396,40 +2396,3 @@ 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: 22 additions & 18 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,30 +91,34 @@ export function useActionGroup<T>(
let {direction} = useLocale();
let focusManager = createFocusManager(ref);
let flipDirection = direction === 'rtl' && orientation === 'horizontal';
let {keyboardProps} = useKeyboard({
shortcuts: {
ArrowRight: () => {
if (flipDirection) {
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) {
focusManager.focusPrevious({wrap: true});
} else {
focusManager.focusNext({wrap: true});
}
},
ArrowDown: () => {
focusManager.focusNext({wrap: true});
},
ArrowLeft: () => {
if (flipDirection) {
break;
case 'ArrowLeft':
case 'ArrowUp':
e.preventDefault();
e.stopPropagation();
if (e.key === 'ArrowLeft' && flipDirection) {
focusManager.focusNext({wrap: true});
} else {
focusManager.focusPrevious({wrap: true});
}
},
ArrowUp: () => {
focusManager.focusPrevious({wrap: true});
}
break;
}
});
};

let role: string | undefined = BUTTON_GROUP_ROLES[state.selectionManager.selectionMode];
if (isInToolbar && role === 'toolbar') {
Expand All @@ -126,7 +130,7 @@ export function useActionGroup<T>(
role,
'aria-orientation': role === 'toolbar' ? orientation : undefined,
'aria-disabled': isDisabled,
...keyboardProps
onKeyDown
}
};
}
84 changes: 46 additions & 38 deletions packages/react-aria/src/calendar/useCalendarGrid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,12 @@ 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 @@ -79,61 +78,70 @@ export function useCalendarGrid(

let {direction} = useLocale();

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

let visibleRangeDescription = useVisibleRangeDescription(
startDate,
Expand Down Expand Up @@ -174,7 +182,7 @@ export function useCalendarGrid(
'aria-disabled': state.isDisabled || undefined,
'aria-multiselectable':
'highlightedRange' in state || state.selectionMode === 'multiple' || undefined,
...keyboardProps,
onKeyDown,
onFocus: () => state.setFocused(true),
onBlur: () => state.setFocused(false)
}),
Expand Down
88 changes: 39 additions & 49 deletions packages/react-aria/src/color/useColorArea.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,56 +111,46 @@ 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);
};

let {keyboardProps} = useKeyboard({
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'
);
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);
}
}
});
Expand Down
Loading
Loading