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
4 changes: 2 additions & 2 deletions packages/@adobe/react-spectrum/src/card/WaterfallLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ export class WaterfallLayout<T> extends BaseLayout<T> implements KeyboardDelegat
let rect = new Rect(layoutInfo.rect.maxX + 1, layoutInfo.rect.y, layoutInfo.rect.width + this.horizontalSpacing, layoutInfo.rect.height);
key = this._findClosest(layoutInfo.rect, rect)?.key;

if (!key) {
if (key == null) {
rect = new Rect(layoutInfo.rect.maxX + 1, 0, layoutInfo.rect.width + this.horizontalSpacing, this.virtualizer.contentSize.height);
key = this._findClosest(layoutInfo.rect, rect)?.key;
}
Expand All @@ -218,7 +218,7 @@ export class WaterfallLayout<T> extends BaseLayout<T> implements KeyboardDelegat
let rect = new Rect(layoutInfo.rect.x - layoutInfo.rect.width - this.horizontalSpacing - 1, layoutInfo.rect.y, layoutInfo.rect.width + this.horizontalSpacing, layoutInfo.rect.height);
key = this._findClosest(layoutInfo.rect, rect)?.key;

if (!key) {
if (key == null) {
rect = new Rect(layoutInfo.rect.x - layoutInfo.rect.width - this.horizontalSpacing - 1, 0, layoutInfo.rect.width + this.horizontalSpacing, this.virtualizer.contentSize.height);
key = this._findClosest(layoutInfo.rect, rect)?.key;
}
Expand Down
6 changes: 3 additions & 3 deletions packages/@react-spectrum/s2/src/TreeView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -594,10 +594,10 @@ function isNextSelected(id: Key | undefined, state: TreeState<unknown>) {
let keyAfter = state.collection.getKeyAfter(id);

// We need to skip non-item nodes because the selection manager will map non-item nodes to their parent before checking selection
let node = keyAfter ? state.collection.getItem(keyAfter) : null;
while (node && node.type !== 'item' && keyAfter) {
let node = keyAfter != null ? state.collection.getItem(keyAfter) : null;
while (node && node.type !== 'item' && keyAfter != null) {
keyAfter = state.collection.getKeyAfter(keyAfter);
node = keyAfter ? state.collection.getItem(keyAfter) : null;
node = keyAfter != null ? state.collection.getItem(keyAfter) : null;
}

return keyAfter != null && state.selectionManager.isSelected(keyAfter);
Expand Down
2 changes: 1 addition & 1 deletion packages/react-aria-components/src/DragAndDrop.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ export function useDndPersistedKeys(selectionManager: MultipleSelectionManager,
if (nextKey != null) {
let targetLevel = dropState.collection.getItem(dropTargetKey)?.level ?? 0;
// Skip over any rows that are descendants of the target ("after" position should be after all children)
while (nextKey) {
while (nextKey != null) {
let node = dropState.collection.getItem(nextKey);
// eslint-disable-next-line max-depth
if (!node) {
Expand Down
4 changes: 2 additions & 2 deletions packages/react-aria-components/src/Table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ class TableCollection<T> extends BaseCollection<T> implements ITableCollection<T
if (lastChildKey != null) {
let lastCell = this.getItem(lastChildKey) as GridNode<T> | null;
while (lastCell && lastCell.type !== 'cell') {
lastCell = lastCell.prevKey ? this.getItem(lastCell.prevKey) as GridNode<T> | null : null;
lastCell = lastCell.prevKey != null ? this.getItem(lastCell.prevKey) as GridNode<T> | null : null;
}
if (lastCell) {
let numberOfCellsInRow = (lastCell.colIndex ?? lastCell.index) + (lastCell.colSpan ?? 1);
Expand Down Expand Up @@ -308,7 +308,7 @@ class TableCollection<T> extends BaseCollection<T> implements ITableCollection<T
while (node) {
yield node as Node<T>;
let key = self.getKeyAfter(node.key);
node = key ? self.getItem(key) : null;
node = key != null ? self.getItem(key) : null;
if (node && node.parentKey === item.parentKey) {
break;
}
Expand Down
4 changes: 2 additions & 2 deletions packages/react-aria-components/src/Tree.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -109,12 +109,12 @@ class TreeCollection<T> extends BaseCollection<T> {
while (node) {
yield node as Node<T>;
if (node.type === 'section') {
node = node.nextKey ? this.getItem(node.nextKey) : null;
node = node.nextKey != null ? this.getItem(node.nextKey) : null;
} else {
// This will include both item and content nodes
// We handle the content nodes in useCollectionRenderer and ListLayout
let key = this.getKeyAfter(node.key);
node = key ? this.getItem(key) : null;
node = key != null ? this.getItem(key) : null;
}
}
}
Expand Down
8 changes: 4 additions & 4 deletions packages/react-aria-components/src/TreeDropTargetDelegate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,9 +163,9 @@ export class TreeDropTargetDelegate<T> {
let parentKey = currentItem?.parentKey;
let ancestorTargets: ItemDropTarget[] = [];

while (parentKey) {
while (parentKey != null) {
let parentItem = collection.getItem(parentKey);
let nextItem = parentItem?.nextKey ? collection.getItem(parentItem.nextKey) : null;
let nextItem = parentItem?.nextKey != null ? collection.getItem(parentItem.nextKey) : null;
let isLastChildAtLevel = !nextItem || nextItem.parentKey !== parentKey;

if (isLastChildAtLevel) {
Expand Down Expand Up @@ -193,7 +193,7 @@ export class TreeDropTargetDelegate<T> {
// Handle converting "after" to "before next" for non-ambiguous cases
if (potentialTargets.length === 1) {
let nextKey = collection.getKeyAfter(target.key);
let nextNode = nextKey ? collection.getItem(nextKey) : null;
let nextNode = nextKey != null ? collection.getItem(nextKey) : null;
if (nextKey != null && nextNode && currentItem && nextNode.level != null && currentItem.level != null && nextNode.level > currentItem.level) {
let beforeTarget = {
type: 'item',
Expand Down Expand Up @@ -225,7 +225,7 @@ export class TreeDropTargetDelegate<T> {
let currentItem = this.state!.collection.getItem(originalTarget.key);
let parentKey = currentItem?.parentKey;

if (!parentKey) {
if (parentKey == null) {
return potentialTargets[0];
}

Expand Down
25 changes: 25 additions & 0 deletions packages/react-aria-components/test/Select.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,31 @@ describe('Select', () => {
expect(select).not.toHaveAttribute('data-invalid');
});

it('should support arrow key navigation to a falsy key', async () => {
let onSelectionChange = jest.fn();
let {getByRole} = render(
<Select onSelectionChange={onSelectionChange} aria-label="Pick a number">
<Button>
<SelectValue />
</Button>
<Popover>
<ListBox
items={Array.from({length: 3}).map((_, i) => ({id: i, label: `${i}`}))}>
{item => <ListBoxItem id={item.id} textValue={item.label}>{item.label}</ListBoxItem>}
</ListBox>
</Popover>
</Select>
);

let button = getByRole('button');
act(() => button.focus());

await user.keyboard('{ArrowRight}');
expect(onSelectionChange).toHaveBeenCalledTimes(1);
expect(onSelectionChange).toHaveBeenLastCalledWith(0);
expect(button).toHaveTextContent('0');
});

it('should support falsy (0) as a valid default value', async () => {
let {getByRole} = render(
<Select placeholder="pick a number">
Expand Down
35 changes: 34 additions & 1 deletion packages/react-aria-components/test/Table.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -468,6 +468,39 @@ describe('Table', () => {
expect(getAllByRole('row')).toHaveLength(5);
});

it('should support rows with a falsy key', () => {
let falsyKeyRows = [
{id: 1, name: 'One', date: '4/7/2021', type: 'File folder'},
{id: 0, name: 'Zero', date: '6/7/2020', type: 'File folder'},
{id: 2, name: 'Two', date: '11/20/2010', type: 'System file'}
];

let {getAllByRole} = render(
<Table aria-label="Files">
<MyTableHeader columns={columns}>
{column => (
<MyColumn isRowHeader={column.isRowHeader} childColumns={column.children}>
{column.name}
</MyColumn>
)}
</MyTableHeader>
<TableBody items={falsyKeyRows}>
{item => (
<MyRow columns={columns}>
{column => <Cell>{item[column.id]}</Cell>}
</MyRow>
)}
</TableBody>
</Table>
);

let rows = getAllByRole('row');
expect(rows).toHaveLength(4);
expect(rows[1]).toHaveTextContent('One');
expect(rows[2]).toHaveTextContent('Zero');
expect(rows[3]).toHaveTextContent('Two');
});

it('should support column hover when sorting is allowed', async () => {
let {getAllByRole} = renderTable({
columnProps: {allowsSorting: true, className: ({isHovered}) => isHovered ? 'hover' : ''}
Expand Down Expand Up @@ -3051,7 +3084,7 @@ describe('Table', () => {
)}
</Collection>
</TableBody>

))}
</Table>
);
Expand Down
41 changes: 41 additions & 0 deletions packages/react-aria-components/test/Tree.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,47 @@ describe('Tree', () => {
expect(rows[16]).toHaveAttribute('aria-setsize', '1');
});

it('should support items with falsy keys', async () => {
let falsyKeyRows = [
{id: 0, name: 'Zero', childItems: [
{id: '', name: 'Empty'},
{id: 'zero-child', name: 'Zero child'}
]},
{id: 1, name: 'One'}
];

let {getAllByRole} = render(
<Tree
defaultExpandedKeys={new Set([0])}
aria-label="falsy keys tree"
items={falsyKeyRows}>
{(item: any) => (
<DynamicTreeItem childItems={item.childItems} textValue={item.name}>
{item.name}
</DynamicTreeItem>
)}
</Tree>
);

let rows = getAllByRole('row');
expect(rows).toHaveLength(4);
expect(rows[0]).toHaveTextContent('Zero');
expect(rows[1]).toHaveTextContent('Empty');
expect(rows[2]).toHaveTextContent('Zero child');
expect(rows[3]).toHaveTextContent('One');

await user.tab();
expect(document.activeElement).toBe(rows[0]);
await user.keyboard('{ArrowDown}');
expect(document.activeElement).toBe(rows[1]);
await user.keyboard('{ArrowDown}');
expect(document.activeElement).toBe(rows[2]);
await user.keyboard('{ArrowDown}');
expect(document.activeElement).toBe(rows[3]);
await user.keyboard('{ArrowUp}');
expect(document.activeElement).toBe(rows[2]);
});

it.each(['Checkbox', 'CheckboxField'])('should render checkboxes for selection using %s', async (comp) => {
let {getByRole, getAllByRole} = render(<StaticTree treeProps={{selectionMode: 'single'}} rowProps={{href: 'https://google.com', checkboxComponent: comp}} />);
let tree = getByRole('treegrid');
Expand Down
4 changes: 2 additions & 2 deletions packages/react-aria/src/collections/BaseCollection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,7 @@ function filterChildren<T>(collection: BaseCollection<T>, newCollection: BaseCol
lastNode = newNode;
}

currentNode = currentNode.nextKey ? collection.getItem(currentNode.nextKey) : null;
currentNode = currentNode.nextKey != null ? collection.getItem(currentNode.nextKey) : null;
}

// TODO: this is pretty specific to dividers but doesn't feel like there is a good way to get around it since we only can know
Expand All @@ -341,7 +341,7 @@ function filterChildren<T>(collection: BaseCollection<T>, newCollection: BaseCol
let prevKey = lastNode.prevKey;
newCollection.removeNode(lastNode.key);

if (prevKey) {
if (prevKey != null) {
lastNode = newCollection.getItem(prevKey) as Mutable<CollectionNode<T>>;
lastNode.nextKey = null;
} else {
Expand Down
4 changes: 2 additions & 2 deletions packages/react-aria/src/select/useSelect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ export function useSelect<T, M extends SelectionMode = 'single'>(props: AriaSele
e.preventDefault();

let key = state.selectedKey != null ? delegate.getKeyAbove?.(state.selectedKey) : delegate.getFirstKey?.();
if (key) {
if (key != null) {
state.setSelectedKey(key);
}
break;
Expand All @@ -138,7 +138,7 @@ export function useSelect<T, M extends SelectionMode = 'single'>(props: AriaSele
e.preventDefault();

let key = state.selectedKey != null ? delegate.getKeyBelow?.(state.selectedKey) : delegate.getFirstKey?.();
if (key) {
if (key != null) {
state.setSelectedKey(key);
}
break;
Expand Down
2 changes: 1 addition & 1 deletion packages/react-aria/src/table/useTableRow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ export function useTableRow<T>(props: GridRowProps<T>, state: TableState<T> | Tr
if (hasChildRows && state.expandedKeys.has(treeNode.key)) {
state.toggleKey(treeNode.key);
e.stopPropagation();
} else if (!state.expandedKeys.has(treeNode.key) && treeNode.parentKey && treeNode.level > 0) {
} else if (!state.expandedKeys.has(treeNode.key) && treeNode.parentKey != null && treeNode.level > 0) {
// Item is a leaf or already collapsed, move focus to parent
state.selectionManager.setFocusedKey(treeNode.parentKey);
e.stopPropagation();
Expand Down
2 changes: 1 addition & 1 deletion packages/react-stately/src/table/TableCollection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export function buildHeaderRows<T>(keyMap: Map<Key, GridNode<T>>, columnNodes: G
let parentKey = column.parentKey;
let col = [column];

while (parentKey) {
while (parentKey != null) {
let parent: GridNode<T> | undefined = keyMap.get(parentKey);
if (!parent) {
break;
Expand Down
Loading