Skip to content

Commit

Permalink
Merge branch 'main' of github.com:adobe/react-spectrum into react_18
Browse files Browse the repository at this point in the history
# Conflicts:
#	package.json
#	packages/@react-spectrum/list/src/ListView.tsx
#	yarn.lock
  • Loading branch information
devongovett committed May 20, 2022
2 parents 0e2e740 + d750b23 commit e8372e6
Show file tree
Hide file tree
Showing 26 changed files with 765 additions and 504 deletions.
3 changes: 1 addition & 2 deletions .chromatic/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ module.exports = {
addons: [
'@storybook/addon-actions',
'@storybook/addon-links',
'@storybook/addon-a11y',
'@storybook/addon-knobs'
'@storybook/addon-a11y'
],
typescript: {
check: false,
Expand Down
2 changes: 1 addition & 1 deletion .storybook/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ module.exports = {
'@storybook/addon-actions',
'@storybook/addon-links',
'@storybook/addon-a11y',
'@storybook/addon-knobs',
'@storybook/addon-controls',
'storybook-dark-mode',
'./custom-addons/provider/register',
'./custom-addons/descriptions/register',
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@
"@spectrum-css/vars": "^2.3.0",
"@storybook/addon-a11y": "^6.5.3",
"@storybook/addon-actions": "^6.5.3",
"@storybook/addon-knobs": "^6.4.0",
"@storybook/addon-controls": "^6.5.3",
"@storybook/addon-links": "^6.5.3",
"@storybook/addons": "^6.5.3",
"@storybook/api": "^6.5.3",
Expand Down
8 changes: 4 additions & 4 deletions packages/@react-aria/dnd/test/dnd.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2073,8 +2073,8 @@ describe('useDrag and useDrop', function () {
expect(document.getElementById(draggable.getAttribute('aria-describedby'))).toHaveTextContent('Dragging. Click to cancel drag.');

// Android Talkback fires with click event of detail = 1, test that our onPointerDown listener detects that it is a virtual click
fireEvent(draggable, pointerEvent('pointerdown', {pointerId: 1, width: 1, height: 1, pressure: 0, detail: 0}));
fireEvent(draggable, pointerEvent('pointerup', {pointerId: 1, width: 1, height: 1, pressure: 0, detail: 0}));
fireEvent(draggable, pointerEvent('pointerdown', {pointerId: 1, width: 1, height: 1, pressure: 0, detail: 0, pointerType: 'mouse'}));
fireEvent(draggable, pointerEvent('pointerup', {pointerId: 1, width: 1, height: 1, pressure: 0, detail: 0, pointerType: 'mouse'}));
fireEvent.click(draggable, {detail: 1});
expect(draggable).toHaveAttribute('data-dragging', 'false');
expect(draggable).toHaveAttribute('aria-describedby');
Expand Down Expand Up @@ -2103,8 +2103,8 @@ describe('useDrag and useDrop', function () {

// Android Talkback fires with click event of detail = 1, test that our onPointerDown listener detects that it is a virtual click
fireEvent.focus(droppable);
fireEvent(droppable, pointerEvent('pointerdown', {pointerId: 1, width: 1, height: 1, pressure: 0, detail: 0}));
fireEvent(droppable, pointerEvent('pointerup', {pointerId: 1, width: 1, height: 1, pressure: 0, detail: 0}));
fireEvent(droppable, pointerEvent('pointerdown', {pointerId: 1, width: 1, height: 1, pressure: 0, detail: 0, pointerType: 'mouse'}));
fireEvent(droppable, pointerEvent('pointerup', {pointerId: 1, width: 1, height: 1, pressure: 0, detail: 0, pointerType: 'mouse'}));
fireEvent.click(droppable, {detail: 1});
expect(draggable).toHaveAttribute('data-dragging', 'false');
expect(draggable).toHaveAttribute('aria-describedby');
Expand Down
4 changes: 2 additions & 2 deletions packages/@react-aria/grid/src/useGridSelectionCheckbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export function useGridSelectionCheckbox<T, C extends GridCollection<T>>(props:

let manager = state.selectionManager;
let checkboxId = useId();
let isDisabled = state.disabledKeys.has(key);
let isDisabled = !state.selectionManager.canSelectItem(key);
let isSelected = state.selectionManager.isSelected(key);

let onChange = () => manager.select(key);
Expand All @@ -40,7 +40,7 @@ export function useGridSelectionCheckbox<T, C extends GridCollection<T>>(props:
id: checkboxId,
'aria-label': formatMessage('select'),
isSelected,
isDisabled: isDisabled || manager.selectionMode === 'none',
isDisabled,
onChange
}
};
Expand Down
6 changes: 4 additions & 2 deletions packages/@react-aria/interactions/src/usePress.ts
Original file line number Diff line number Diff line change
Expand Up @@ -778,13 +778,15 @@ function isVirtualPointerEvent(event: PointerEvent) {
// Android TalkBack double tap will sometimes return a event with width and height of 1
// and pointerType === 'mouse' so we need to check for a specific combination of event attributes.
// Cannot use "event.pressure === 0" as the sole check due to Safari pointer events always returning pressure === 0
// instead of .5, see https://bugs.webkit.org/show_bug.cgi?id=206216
// instead of .5, see https://bugs.webkit.org/show_bug.cgi?id=206216. event.pointerType === 'mouse' is to distingush
// Talkback double tap from Windows Firefox touch screen press
return (
(event.width === 0 && event.height === 0) ||
(event.width === 1 &&
event.height === 1 &&
event.pressure === 0 &&
event.detail === 0
event.detail === 0 &&
event.pointerType === 'mouse'
)
);
}
4 changes: 2 additions & 2 deletions packages/@react-aria/interactions/test/usePress.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -607,8 +607,8 @@ describe('usePress', function () {
let el = res.getByText('test');
// Android TalkBack will occasionally fire a pointer down event with "width: 1, height: 1" instead of "width: 0, height: 0".
// Make sure we can still determine that this is a virtual event by checking the pressure, detail, and height/width.
fireEvent(el, pointerEvent('pointerdown', {pointerId: 1, width: 1, height: 1, pressure: 0, detail: 0}));
fireEvent(el, pointerEvent('pointerup', {pointerId: 1, width: 1, height: 1, pressure: 0, detail: 0}));
fireEvent(el, pointerEvent('pointerdown', {pointerId: 1, width: 1, height: 1, pressure: 0, detail: 0, pointerType: 'mouse'}));
fireEvent(el, pointerEvent('pointerup', {pointerId: 1, width: 1, height: 1, pressure: 0, detail: 0, pointerType: 'mouse'}));
expect(events).toEqual([]);

// Virtual pointer event sets pointerType and onClick handles the rest
Expand Down
15 changes: 7 additions & 8 deletions packages/@react-aria/list/src/useListItem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ import {isFocusVisible} from '@react-aria/interactions';
import type {ListState} from '@react-stately/list';
import {mergeProps} from '@react-aria/utils';
import {Node as RSNode} from '@react-types/shared';
import {SelectableItemStates, useSelectableItem} from '@react-aria/selection';
import {useLocale} from '@react-aria/i18n';
import {useSelectableItem} from '@react-aria/selection';

export interface AriaListItemOptions {
/** An object representing the list item. Contains all the relevant information that makes up the list row. */
Expand All @@ -29,13 +29,11 @@ export interface AriaListItemOptions {
shouldSelectOnPressUp?: boolean
}

export interface ListItemAria {
export interface ListItemAria extends SelectableItemStates {
/** Props for the list row element. */
rowProps: HTMLAttributes<HTMLElement>,
/** Props for the grid cell element within the list row. */
gridCellProps: HTMLAttributes<HTMLElement>,
/** Whether the row is currently pressed. */
isPressed: boolean
gridCellProps: HTMLAttributes<HTMLElement>
}

/**
Expand All @@ -62,7 +60,7 @@ export function useListItem<T>(props: AriaListItemOptions, state: ListState<T>,
}
};

let {itemProps, isPressed} = useSelectableItem({
let {itemProps, ...itemStates} = useSelectableItem({
selectionManager: state.selectionManager,
key: node.key,
ref,
Expand Down Expand Up @@ -167,7 +165,8 @@ export function useListItem<T>(props: AriaListItemOptions, state: ListState<T>,
onKeyDownCapture: onKeyDown,
onFocus,
'aria-label': node.textValue,
'aria-selected': state.selectionManager.selectionMode !== 'none' ? state.selectionManager.isSelected(node.key) : undefined,
'aria-selected': state.selectionManager.canSelectItem(node.key) ? state.selectionManager.isSelected(node.key) : undefined,
'aria-disabled': state.selectionManager.isDisabled(node.key) || undefined,
id: getRowId(state, node.key)
});

Expand All @@ -183,7 +182,7 @@ export function useListItem<T>(props: AriaListItemOptions, state: ListState<T>,
return {
rowProps,
gridCellProps,
isPressed
...itemStates
};
}

Expand Down
51 changes: 41 additions & 10 deletions packages/@react-aria/selection/src/useSelectableItem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {mergeProps} from '@react-aria/utils';
import {MultipleSelectionManager} from '@react-stately/selection';
import {PressProps, useLongPress, usePress} from '@react-aria/interactions';

interface SelectableItemOptions {
export interface SelectableItemOptions {
/**
* An interface for reading and updating multiple selection state.
*/
Expand Down Expand Up @@ -56,19 +56,40 @@ interface SelectableItemOptions {
/** Whether the item is disabled. */
isDisabled?: boolean,
/**
* Handler that is called when a user performs an action on the cell. The exact user event depends on
* Handler that is called when a user performs an action on the item. The exact user event depends on
* the collection's `selectionBehavior` prop and the interaction modality.
*/
onAction?: () => void
}

interface SelectableItemAria {
export interface SelectableItemStates {
/** Whether the item is currently in a pressed state. */
isPressed: boolean,
/** Whether the item is currently selected. */
isSelected: boolean,
/**
* Whether the item is non-interactive, i.e. both selection and actions are disabled and the item may
* not be focused. Dependent on `disabledKeys` and `disabledBehavior`.
*/
isDisabled: boolean,
/**
* Whether the item may be selected, dependent on `selectionMode`, `disabledKeys`, and `disabledBehavior`.
*/
allowsSelection: boolean,
/**
* Whether the item has an action, dependent on `onAction`, `disabledKeys`,
* and `disabledBehavior. It may also change depending on the current selection state
* of the list (e.g. when selection is primary). This can be used to enable or disable hover
* styles or other visual indications of interactivity.
*/
hasAction: boolean
}

export interface SelectableItemAria extends SelectableItemStates {
/**
* Props to be spread on the item root node.
*/
itemProps: HTMLAttributes<HTMLElement>,
/** Whether the item is currently in a pressed state. */
isPressed: boolean
itemProps: HTMLAttributes<HTMLElement>
}

/**
Expand Down Expand Up @@ -145,10 +166,16 @@ export function useSelectableItem(options: SelectableItemOptions): SelectableIte
// Clicking the checkbox enters selection mode, after which clicking anywhere on any row toggles selection for that row.
// With highlight selection, onAction is secondary, and occurs on double click. Single click selects the row.
// With touch, onAction occurs on single tap, and long press enters selection mode.
let hasPrimaryAction = onAction && (manager.selectionMode === 'none' || (manager.selectionBehavior !== 'replace' && manager.isEmpty));
let hasSecondaryAction = onAction && manager.selectionMode !== 'none' && manager.selectionBehavior === 'replace';
let hasAction = hasPrimaryAction || hasSecondaryAction;
isDisabled = isDisabled || manager.isDisabled(key);
let allowsSelection = !isDisabled && manager.canSelectItem(key);
let allowsActions = onAction && !isDisabled;
let hasPrimaryAction = allowsActions && (
manager.selectionBehavior === 'replace'
? !allowsSelection
: manager.isEmpty
);
let hasSecondaryAction = allowsActions && allowsSelection && manager.selectionBehavior === 'replace';
let hasAction = hasPrimaryAction || hasSecondaryAction;
let modality = useRef(null);

let longPressEnabled = hasAction && allowsSelection;
Expand Down Expand Up @@ -276,7 +303,11 @@ export function useSelectableItem(options: SelectableItemOptions): SelectableIte
longPressEnabled ? longPressProps : {},
{onDoubleClick, onDragStart}
),
isPressed
isPressed,
isSelected: manager.isSelected(key),
isDisabled,
allowsSelection,
hasAction
};
}

Expand Down
3 changes: 3 additions & 0 deletions packages/@react-aria/sidenav/test/useSideNavItem.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ describe('useSideNavItem', function () {
},
canSelectItem() {
return true;
},
isDisabled() {
return false;
}
},
disabledKeys: new Set()
Expand Down
10 changes: 5 additions & 5 deletions packages/@react-spectrum/list/src/ListView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/

import {classNames, useDOMRef, useStyleProps} from '@react-spectrum/utils';
import {DOMRef, LoadingState} from '@react-types/shared';
import type {DraggableCollectionState, DroppableCollectionState} from '@react-stately/dnd';
Expand Down Expand Up @@ -65,7 +64,7 @@ const ROW_HEIGHTS = {
}
};

function useListLayout<T>(state: ListState<T>, density: SpectrumListProps<T>['density']) {
function useListLayout<T>(state: ListState<T>, density: SpectrumListProps<T>['density'], allowDisabledKeyFocus: boolean) {
let {scale} = useProvider();
let collator = useCollator({usage: 'search', sensitivity: 'base'});
let isEmpty = state.collection.size === 0;
Expand All @@ -75,9 +74,9 @@ function useListLayout<T>(state: ListState<T>, density: SpectrumListProps<T>['de
padding: 0,
collator,
loaderHeight: isEmpty ? null : ROW_HEIGHTS[density][scale],
allowDisabledKeyFocus: true
allowDisabledKeyFocus
})
, [collator, scale, density, isEmpty]);
, [collator, scale, density, isEmpty, allowDisabledKeyFocus]);

layout.collection = state.collection;
layout.disabledKeys = state.disabledKeys;
Expand Down Expand Up @@ -116,7 +115,8 @@ function ListView<T extends object>(props: SpectrumListProps<T>, ref: DOMRef<HTM
let isLoading = loadingState === 'loading' || loadingState === 'loadingMore';

let {styleProps} = useStyleProps(props);
let layout = useListLayout(state, props.density || 'regular');
let {locale} = useLocale();
let layout = useListLayout(state, props.density || 'regular', state.selectionManager.disabledBehavior === 'selection');
let dragState: DraggableCollectionState;
let preview = useRef(null);
if (isListDraggable) {
Expand Down

0 comments on commit e8372e6

Please sign in to comment.