@react-aria/grid
Advanced tools
Comparing version 3.0.0-nightly.2585 to 3.0.0-nightly.2655
216
dist/main.js
@@ -181,2 +181,4 @@ var { | ||
} | ||
return this.direction === 'rtl' ? this.getFirstKey(key) : this.getLastKey(key); | ||
} | ||
@@ -213,2 +215,4 @@ } | ||
} | ||
return this.direction === 'rtl' ? this.getLastKey(key) : this.getFirstKey(key); | ||
} | ||
@@ -236,5 +240,5 @@ } | ||
key = this.findNextKey(); // If global flag is set, focus the first cell in the first row. | ||
key = this.findNextKey(); // If global flag is set (or if focus mode is cell), focus the first cell in the first row. | ||
if (key != null && item && this.isCell(item) && global) { | ||
if (key != null && item && this.isCell(item) && global || this.focusMode === 'cell') { | ||
let item = this.collection.getItem(key); | ||
@@ -245,5 +249,3 @@ key = [...item.childNodes][0].key; | ||
if (this.focusMode === 'row') { | ||
return key; | ||
} | ||
return key; | ||
} | ||
@@ -271,5 +273,5 @@ | ||
key = this.findPreviousKey(); // If global flag is set, focus the last cell in the last row. | ||
key = this.findPreviousKey(); // If global flag is set (or if focus mode is cell), focus the last cell in the last row. | ||
if (key != null && item && this.isCell(item) && global) { | ||
if (key != null && item && this.isCell(item) && global || this.focusMode === 'cell') { | ||
let item = this.collection.getItem(key); | ||
@@ -281,5 +283,3 @@ let children = [...item.childNodes]; | ||
if (this.focusMode === 'row') { | ||
return key; | ||
} | ||
return key; | ||
} | ||
@@ -390,2 +390,6 @@ | ||
if (this.collator.compare(substring, search) === 0) { | ||
if (this.isRow(item) && this.focusMode === 'cell') { | ||
return [...item.childNodes][0].key; | ||
} | ||
return item.key; | ||
@@ -395,3 +399,3 @@ } | ||
key = this.getKeyBelow(key); // Wrap around when reaching the end of the collection | ||
key = this.findNextKey(key); // Wrap around when reaching the end of the collection | ||
@@ -410,4 +414,17 @@ if (key == null && !hasWrapped) { | ||
exports.GridKeyboardDelegate = GridKeyboardDelegate; | ||
const $df311f3c863e8abccff59a8ae65a46d3$var$gridIds = new WeakMap(); | ||
/* | ||
* Copyright 2020 Adobe. All rights reserved. | ||
* This file is licensed to you under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. You may obtain a copy | ||
* of the License at http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software distributed under | ||
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS | ||
* OF ANY KIND, either express or implied. See the License for the specific language | ||
* governing permissions and limitations under the License. | ||
*/ | ||
// Used to share keyboard delegate between useGrid and useGridCell | ||
const $e6fd609a4e1bd40f6df88a69c08b6746$export$gridKeyboardDelegates = new WeakMap(); | ||
function useGrid(props, state) { | ||
@@ -417,3 +434,4 @@ let { | ||
isVirtualized, | ||
keyboardDelegate | ||
keyboardDelegate, | ||
focusMode | ||
} = props; | ||
@@ -439,4 +457,5 @@ | ||
direction, | ||
collator | ||
}), [keyboardDelegate, state.collection, state.disabledKeys, ref, direction, collator]); | ||
collator, | ||
focusMode | ||
}), [keyboardDelegate, state.collection, state.disabledKeys, ref, direction, collator, focusMode]); | ||
let { | ||
@@ -450,3 +469,3 @@ collectionProps | ||
let id = useId(); | ||
$df311f3c863e8abccff59a8ae65a46d3$var$gridIds.set(state, id); | ||
$e6fd609a4e1bd40f6df88a69c08b6746$export$gridKeyboardDelegates.set(state, delegate); | ||
let domProps = filterDOMProps(props, { | ||
@@ -473,13 +492,2 @@ labelable: true | ||
/* | ||
* Copyright 2020 Adobe. All rights reserved. | ||
* This file is licensed to you under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. You may obtain a copy | ||
* of the License at http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software distributed under | ||
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS | ||
* OF ANY KIND, either express or implied. See the License for the specific language | ||
* governing permissions and limitations under the License. | ||
*/ | ||
function useGridRowGroup() { | ||
@@ -501,3 +509,4 @@ return { | ||
isSelected, | ||
isDisabled | ||
isDisabled, | ||
shouldSelectOnPressUp | ||
} = props; | ||
@@ -510,3 +519,4 @@ let { | ||
ref, | ||
isVirtualized | ||
isVirtualized, | ||
shouldSelectOnPressUp | ||
}); // TODO: move into useSelectableItem? | ||
@@ -541,4 +551,10 @@ | ||
isVirtualized, | ||
isDisabled | ||
} = props; // Handles focusing the cell. If there is a focusable child, | ||
isDisabled, | ||
focusMode = 'child', | ||
shouldSelectOnPressUp | ||
} = props; | ||
let { | ||
direction | ||
} = useLocale(); | ||
let keyboardDelegate = $e6fd609a4e1bd40f6df88a69c08b6746$export$gridKeyboardDelegates.get(state); // Handles focusing the cell. If there is a focusable child, | ||
// it is focused, otherwise the cell itself is focused. | ||
@@ -548,7 +564,13 @@ | ||
let treeWalker = getFocusableTreeWalker(ref.current); | ||
let focusable = treeWalker.firstChild(); | ||
if (focusable) { | ||
focusSafely(focusable); | ||
} else { | ||
if (focusMode === 'child') { | ||
let focusable = state.selectionManager.childFocusStrategy === 'last' ? $f9bcfab6f4b3fd6f2359f40ca9cd9cf4$var$last(treeWalker) : treeWalker.firstChild(); | ||
if (focusable) { | ||
focusSafely(focusable); | ||
return; | ||
} | ||
} | ||
if (!ref.current.contains(document.activeElement)) { | ||
focusSafely(ref.current); | ||
@@ -565,3 +587,4 @@ } | ||
isVirtualized, | ||
focus | ||
focus, | ||
shouldSelectOnPressUp | ||
}); // TODO: move into useSelectableItem? | ||
@@ -573,5 +596,106 @@ | ||
isDisabled | ||
})); // Grid cells can have focusable elements inside them. In this case, focus should | ||
})); | ||
let onKeyDown = e => { | ||
let walker = getFocusableTreeWalker(ref.current); | ||
walker.currentNode = document.activeElement; | ||
switch (e.key) { | ||
case 'ArrowLeft': | ||
{ | ||
// Find the next focusable element within the cell. | ||
let focusable = direction === 'rtl' ? walker.nextNode() : walker.previousNode(); // Don't focus the cell itself if focusMode is "child" | ||
if (focusMode === 'child' && focusable === ref.current) { | ||
focusable = null; | ||
} | ||
if (focusable) { | ||
e.preventDefault(); | ||
e.stopPropagation(); | ||
focusSafely(focusable); | ||
} else { | ||
// If there is no next focusable child, then move to the next cell to the left of this one. | ||
// This will be handled by useSelectableCollection. However, if there is no cell to the left | ||
// of this one, only one column, and the grid doesn't focus rows, then the next key will be the | ||
// same as this one. In that case we need to handle focusing either the cell or the first/last | ||
// child, depending on the focus mode. | ||
let prev = keyboardDelegate.getKeyLeftOf(node.key); | ||
if (prev !== node.key) { | ||
break; | ||
} | ||
e.preventDefault(); | ||
e.stopPropagation(); | ||
if (focusMode === 'cell' && direction === 'rtl') { | ||
focusSafely(ref.current); | ||
} else { | ||
walker.currentNode = ref.current; | ||
focusable = direction === 'rtl' ? walker.firstChild() : $f9bcfab6f4b3fd6f2359f40ca9cd9cf4$var$last(walker); | ||
if (focusable) { | ||
focusSafely(focusable); | ||
} | ||
} | ||
} | ||
break; | ||
} | ||
case 'ArrowRight': | ||
{ | ||
let focusable = direction === 'rtl' ? walker.previousNode() : walker.nextNode(); | ||
if (focusMode === 'child' && focusable === ref.current) { | ||
focusable = null; | ||
} | ||
if (focusable) { | ||
e.preventDefault(); | ||
e.stopPropagation(); | ||
focusSafely(focusable); | ||
} else { | ||
let next = keyboardDelegate.getKeyRightOf(node.key); | ||
if (next !== node.key) { | ||
break; | ||
} | ||
e.preventDefault(); | ||
e.stopPropagation(); | ||
if (focusMode === 'cell' && direction === 'ltr') { | ||
focusSafely(ref.current); | ||
} else { | ||
walker.currentNode = ref.current; | ||
focusable = direction === 'rtl' ? $f9bcfab6f4b3fd6f2359f40ca9cd9cf4$var$last(walker) : walker.firstChild(); | ||
if (focusable) { | ||
focusSafely(focusable); | ||
} | ||
} | ||
} | ||
break; | ||
} | ||
case 'ArrowUp': | ||
case 'ArrowDown': | ||
// Prevent this event from reaching cell children, e.g. menu buttons. We want arrow keys to navigate | ||
// to the cell above/below instead. We need to re-dispatch the event from a higher parent so it still | ||
// bubbles and gets handled by useSelectableCollection. | ||
if (!e.altKey && ref.current.contains(e.target)) { | ||
e.stopPropagation(); | ||
e.preventDefault(); | ||
ref.current.parentElement.dispatchEvent(new KeyboardEvent(e.nativeEvent.type, e.nativeEvent)); | ||
} | ||
break; | ||
} | ||
}; // Grid cells can have focusable elements inside them. In this case, focus should | ||
// be marshalled to that element rather than focusing the cell itself. | ||
let onFocus = e => { | ||
@@ -595,3 +719,3 @@ if (e.target !== ref.current) { | ||
requestAnimationFrame(() => { | ||
if (document.activeElement === ref.current) { | ||
if (focusMode === 'child' && document.activeElement === ref.current) { | ||
focus(); | ||
@@ -604,2 +728,3 @@ } | ||
role: 'gridcell', | ||
onKeyDownCapture: onKeyDown, | ||
onFocus | ||
@@ -618,2 +743,17 @@ }); | ||
exports.useGridCell = useGridCell; | ||
function $f9bcfab6f4b3fd6f2359f40ca9cd9cf4$var$last(walker) { | ||
let next; | ||
let last; | ||
do { | ||
last = walker.lastChild(); | ||
if (last) { | ||
next = last; | ||
} | ||
} while (last); | ||
return next; | ||
} | ||
//# sourceMappingURL=main.js.map |
@@ -149,2 +149,4 @@ import { focusSafely, getFocusableTreeWalker } from "@react-aria/focus"; | ||
} | ||
return this.direction === 'rtl' ? this.getFirstKey(key) : this.getLastKey(key); | ||
} | ||
@@ -181,2 +183,4 @@ } | ||
} | ||
return this.direction === 'rtl' ? this.getLastKey(key) : this.getFirstKey(key); | ||
} | ||
@@ -204,5 +208,5 @@ } | ||
key = this.findNextKey(); // If global flag is set, focus the first cell in the first row. | ||
key = this.findNextKey(); // If global flag is set (or if focus mode is cell), focus the first cell in the first row. | ||
if (key != null && item && this.isCell(item) && global) { | ||
if (key != null && item && this.isCell(item) && global || this.focusMode === 'cell') { | ||
let item = this.collection.getItem(key); | ||
@@ -213,5 +217,3 @@ key = [...item.childNodes][0].key; | ||
if (this.focusMode === 'row') { | ||
return key; | ||
} | ||
return key; | ||
} | ||
@@ -239,5 +241,5 @@ | ||
key = this.findPreviousKey(); // If global flag is set, focus the last cell in the last row. | ||
key = this.findPreviousKey(); // If global flag is set (or if focus mode is cell), focus the last cell in the last row. | ||
if (key != null && item && this.isCell(item) && global) { | ||
if (key != null && item && this.isCell(item) && global || this.focusMode === 'cell') { | ||
let item = this.collection.getItem(key); | ||
@@ -249,5 +251,3 @@ let children = [...item.childNodes]; | ||
if (this.focusMode === 'row') { | ||
return key; | ||
} | ||
return key; | ||
} | ||
@@ -358,2 +358,6 @@ | ||
if (this.collator.compare(substring, search) === 0) { | ||
if (this.isRow(item) && this.focusMode === 'cell') { | ||
return [...item.childNodes][0].key; | ||
} | ||
return item.key; | ||
@@ -363,3 +367,3 @@ } | ||
key = this.getKeyBelow(key); // Wrap around when reaching the end of the collection | ||
key = this.findNextKey(key); // Wrap around when reaching the end of the collection | ||
@@ -376,3 +380,16 @@ if (key == null && !hasWrapped) { | ||
} | ||
const $e3c4cce891c32069b0b54eddd405e76$var$gridIds = new WeakMap(); | ||
/* | ||
* Copyright 2020 Adobe. All rights reserved. | ||
* This file is licensed to you under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. You may obtain a copy | ||
* of the License at http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software distributed under | ||
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS | ||
* OF ANY KIND, either express or implied. See the License for the specific language | ||
* governing permissions and limitations under the License. | ||
*/ | ||
// Used to share keyboard delegate between useGrid and useGridCell | ||
const $c7c16c1fc27e25a205a277212c8e7cfd$export$gridKeyboardDelegates = new WeakMap(); | ||
export function useGrid(props, state) { | ||
@@ -382,3 +399,4 @@ let { | ||
isVirtualized, | ||
keyboardDelegate | ||
keyboardDelegate, | ||
focusMode | ||
} = props; | ||
@@ -404,4 +422,5 @@ | ||
direction, | ||
collator | ||
}), [keyboardDelegate, state.collection, state.disabledKeys, ref, direction, collator]); | ||
collator, | ||
focusMode | ||
}), [keyboardDelegate, state.collection, state.disabledKeys, ref, direction, collator, focusMode]); | ||
let { | ||
@@ -415,3 +434,3 @@ collectionProps | ||
let id = useId(); | ||
$e3c4cce891c32069b0b54eddd405e76$var$gridIds.set(state, id); | ||
$c7c16c1fc27e25a205a277212c8e7cfd$export$gridKeyboardDelegates.set(state, delegate); | ||
let domProps = filterDOMProps(props, { | ||
@@ -435,14 +454,2 @@ labelable: true | ||
} | ||
/* | ||
* Copyright 2020 Adobe. All rights reserved. | ||
* This file is licensed to you under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. You may obtain a copy | ||
* of the License at http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software distributed under | ||
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS | ||
* OF ANY KIND, either express or implied. See the License for the specific language | ||
* governing permissions and limitations under the License. | ||
*/ | ||
export function useGridRowGroup() { | ||
@@ -461,3 +468,4 @@ return { | ||
isSelected, | ||
isDisabled | ||
isDisabled, | ||
shouldSelectOnPressUp | ||
} = props; | ||
@@ -470,3 +478,4 @@ let { | ||
ref, | ||
isVirtualized | ||
isVirtualized, | ||
shouldSelectOnPressUp | ||
}); // TODO: move into useSelectableItem? | ||
@@ -498,4 +507,10 @@ | ||
isVirtualized, | ||
isDisabled | ||
} = props; // Handles focusing the cell. If there is a focusable child, | ||
isDisabled, | ||
focusMode = 'child', | ||
shouldSelectOnPressUp | ||
} = props; | ||
let { | ||
direction | ||
} = useLocale(); | ||
let keyboardDelegate = $c7c16c1fc27e25a205a277212c8e7cfd$export$gridKeyboardDelegates.get(state); // Handles focusing the cell. If there is a focusable child, | ||
// it is focused, otherwise the cell itself is focused. | ||
@@ -505,7 +520,13 @@ | ||
let treeWalker = getFocusableTreeWalker(ref.current); | ||
let focusable = treeWalker.firstChild(); | ||
if (focusable) { | ||
focusSafely(focusable); | ||
} else { | ||
if (focusMode === 'child') { | ||
let focusable = state.selectionManager.childFocusStrategy === 'last' ? $c46764dc2253b1b174632a30143c6340$var$last(treeWalker) : treeWalker.firstChild(); | ||
if (focusable) { | ||
focusSafely(focusable); | ||
return; | ||
} | ||
} | ||
if (!ref.current.contains(document.activeElement)) { | ||
focusSafely(ref.current); | ||
@@ -522,3 +543,4 @@ } | ||
isVirtualized, | ||
focus | ||
focus, | ||
shouldSelectOnPressUp | ||
}); // TODO: move into useSelectableItem? | ||
@@ -530,5 +552,106 @@ | ||
isDisabled | ||
})); // Grid cells can have focusable elements inside them. In this case, focus should | ||
})); | ||
let onKeyDown = e => { | ||
let walker = getFocusableTreeWalker(ref.current); | ||
walker.currentNode = document.activeElement; | ||
switch (e.key) { | ||
case 'ArrowLeft': | ||
{ | ||
// Find the next focusable element within the cell. | ||
let focusable = direction === 'rtl' ? walker.nextNode() : walker.previousNode(); // Don't focus the cell itself if focusMode is "child" | ||
if (focusMode === 'child' && focusable === ref.current) { | ||
focusable = null; | ||
} | ||
if (focusable) { | ||
e.preventDefault(); | ||
e.stopPropagation(); | ||
focusSafely(focusable); | ||
} else { | ||
// If there is no next focusable child, then move to the next cell to the left of this one. | ||
// This will be handled by useSelectableCollection. However, if there is no cell to the left | ||
// of this one, only one column, and the grid doesn't focus rows, then the next key will be the | ||
// same as this one. In that case we need to handle focusing either the cell or the first/last | ||
// child, depending on the focus mode. | ||
let prev = keyboardDelegate.getKeyLeftOf(node.key); | ||
if (prev !== node.key) { | ||
break; | ||
} | ||
e.preventDefault(); | ||
e.stopPropagation(); | ||
if (focusMode === 'cell' && direction === 'rtl') { | ||
focusSafely(ref.current); | ||
} else { | ||
walker.currentNode = ref.current; | ||
focusable = direction === 'rtl' ? walker.firstChild() : $c46764dc2253b1b174632a30143c6340$var$last(walker); | ||
if (focusable) { | ||
focusSafely(focusable); | ||
} | ||
} | ||
} | ||
break; | ||
} | ||
case 'ArrowRight': | ||
{ | ||
let focusable = direction === 'rtl' ? walker.previousNode() : walker.nextNode(); | ||
if (focusMode === 'child' && focusable === ref.current) { | ||
focusable = null; | ||
} | ||
if (focusable) { | ||
e.preventDefault(); | ||
e.stopPropagation(); | ||
focusSafely(focusable); | ||
} else { | ||
let next = keyboardDelegate.getKeyRightOf(node.key); | ||
if (next !== node.key) { | ||
break; | ||
} | ||
e.preventDefault(); | ||
e.stopPropagation(); | ||
if (focusMode === 'cell' && direction === 'ltr') { | ||
focusSafely(ref.current); | ||
} else { | ||
walker.currentNode = ref.current; | ||
focusable = direction === 'rtl' ? $c46764dc2253b1b174632a30143c6340$var$last(walker) : walker.firstChild(); | ||
if (focusable) { | ||
focusSafely(focusable); | ||
} | ||
} | ||
} | ||
break; | ||
} | ||
case 'ArrowUp': | ||
case 'ArrowDown': | ||
// Prevent this event from reaching cell children, e.g. menu buttons. We want arrow keys to navigate | ||
// to the cell above/below instead. We need to re-dispatch the event from a higher parent so it still | ||
// bubbles and gets handled by useSelectableCollection. | ||
if (!e.altKey && ref.current.contains(e.target)) { | ||
e.stopPropagation(); | ||
e.preventDefault(); | ||
ref.current.parentElement.dispatchEvent(new KeyboardEvent(e.nativeEvent.type, e.nativeEvent)); | ||
} | ||
break; | ||
} | ||
}; // Grid cells can have focusable elements inside them. In this case, focus should | ||
// be marshalled to that element rather than focusing the cell itself. | ||
let onFocus = e => { | ||
@@ -552,3 +675,3 @@ if (e.target !== ref.current) { | ||
requestAnimationFrame(() => { | ||
if (document.activeElement === ref.current) { | ||
if (focusMode === 'child' && document.activeElement === ref.current) { | ||
focus(); | ||
@@ -561,2 +684,3 @@ } | ||
role: 'gridcell', | ||
onKeyDownCapture: onKeyDown, | ||
onFocus | ||
@@ -573,2 +697,17 @@ }); | ||
} | ||
function $c46764dc2253b1b174632a30143c6340$var$last(walker) { | ||
let next; | ||
let last; | ||
do { | ||
last = walker.lastChild(); | ||
if (last) { | ||
next = last; | ||
} | ||
} while (last); | ||
return next; | ||
} | ||
//# sourceMappingURL=module.js.map |
@@ -38,8 +38,9 @@ import { Direction, KeyboardDelegate, Node, AriaLabelingProps, DOMProps } from "@react-types/shared"; | ||
} | ||
interface GridProps extends DOMProps, AriaLabelingProps { | ||
export interface GridProps extends DOMProps, AriaLabelingProps { | ||
ref: RefObject<HTMLElement>; | ||
isVirtualized?: boolean; | ||
keyboardDelegate?: KeyboardDelegate; | ||
focusMode?: 'row' | 'cell'; | ||
} | ||
interface GridAria { | ||
export interface GridAria { | ||
gridProps: HTMLAttributes<HTMLElement>; | ||
@@ -58,2 +59,3 @@ } | ||
isDisabled?: boolean; | ||
shouldSelectOnPressUp?: boolean; | ||
} | ||
@@ -69,2 +71,4 @@ export interface GridRowAria { | ||
isDisabled?: boolean; | ||
focusMode?: 'child' | 'cell'; | ||
shouldSelectOnPressUp?: boolean; | ||
} | ||
@@ -71,0 +75,0 @@ interface GridCellAria { |
{ | ||
"name": "@react-aria/grid", | ||
"version": "3.0.0-nightly.2585+e213843e", | ||
"version": "3.0.0-nightly.2655+f0afbae7", | ||
"description": "Spectrum UI components in React", | ||
@@ -21,11 +21,11 @@ "license": "Apache-2.0", | ||
"@babel/runtime": "^7.6.2", | ||
"@react-aria/focus": "3.0.0-nightly.907+e213843e", | ||
"@react-aria/i18n": "3.0.0-nightly.907+e213843e", | ||
"@react-aria/interactions": "3.0.0-nightly.907+e213843e", | ||
"@react-aria/selection": "3.0.0-nightly.907+e213843e", | ||
"@react-aria/utils": "3.0.0-nightly.907+e213843e", | ||
"@react-stately/grid": "3.0.0-nightly.2585+e213843e", | ||
"@react-stately/virtualizer": "3.1.3-nightly.2585+e213843e", | ||
"@react-types/grid": "3.0.0-nightly.2585+e213843e", | ||
"@react-types/shared": "3.0.0-nightly.907+e213843e" | ||
"@react-aria/focus": "3.0.0-nightly.975+f0afbae7", | ||
"@react-aria/i18n": "3.0.0-nightly.975+f0afbae7", | ||
"@react-aria/interactions": "3.0.0-nightly.975+f0afbae7", | ||
"@react-aria/selection": "3.0.0-nightly.975+f0afbae7", | ||
"@react-aria/utils": "3.0.0-nightly.975+f0afbae7", | ||
"@react-stately/grid": "3.0.0-nightly.2655+f0afbae7", | ||
"@react-stately/virtualizer": "3.1.4-nightly.2655+f0afbae7", | ||
"@react-types/grid": "3.0.0-nightly.2655+f0afbae7", | ||
"@react-types/shared": "3.0.0-nightly.975+f0afbae7" | ||
}, | ||
@@ -38,3 +38,3 @@ "peerDependencies": { | ||
}, | ||
"gitHead": "e213843ee295bda8ba7f80336a1e179f6e287d9e" | ||
"gitHead": "f0afbae764727b3062170852006ea38689a34f97" | ||
} |
@@ -170,2 +170,4 @@ /* | ||
} | ||
return this.direction === 'rtl' ? this.getFirstKey(key) : this.getLastKey(key); | ||
} | ||
@@ -205,2 +207,4 @@ } | ||
} | ||
return this.direction === 'rtl' ? this.getLastKey(key) : this.getFirstKey(key); | ||
} | ||
@@ -228,4 +232,4 @@ } | ||
// If global flag is set, focus the first cell in the first row. | ||
if (key != null && item && this.isCell(item) && global) { | ||
// If global flag is set (or if focus mode is cell), focus the first cell in the first row. | ||
if ((key != null && item && this.isCell(item) && global) || this.focusMode === 'cell') { | ||
let item = this.collection.getItem(key); | ||
@@ -236,5 +240,3 @@ key = [...item.childNodes][0].key; | ||
// Otherwise, focus the row itself. | ||
if (this.focusMode === 'row') { | ||
return key; | ||
} | ||
return key; | ||
} | ||
@@ -262,4 +264,4 @@ | ||
// If global flag is set, focus the last cell in the last row. | ||
if (key != null && item && this.isCell(item) && global) { | ||
// If global flag is set (or if focus mode is cell), focus the last cell in the last row. | ||
if ((key != null && item && this.isCell(item) && global) || this.focusMode === 'cell') { | ||
let item = this.collection.getItem(key); | ||
@@ -271,5 +273,3 @@ let children = [...item.childNodes]; | ||
// Otherwise, focus the row itself. | ||
if (this.focusMode === 'row') { | ||
return key; | ||
} | ||
return key; | ||
} | ||
@@ -370,2 +370,6 @@ | ||
if (this.collator.compare(substring, search) === 0) { | ||
if (this.isRow(item) && this.focusMode === 'cell') { | ||
return [...item.childNodes][0].key; | ||
} | ||
return item.key; | ||
@@ -375,3 +379,3 @@ } | ||
key = this.getKeyBelow(key); | ||
key = this.findNextKey(key); | ||
@@ -388,2 +392,2 @@ // Wrap around when reaching the end of the collection | ||
} | ||
@@ -17,2 +17,3 @@ /* | ||
import {GridKeyboardDelegate} from './GridKeyboardDelegate'; | ||
import {gridKeyboardDelegates} from './utils'; | ||
import {GridState} from '@react-stately/grid'; | ||
@@ -23,11 +24,10 @@ import {HTMLAttributes, RefObject, useMemo} from 'react'; | ||
const gridIds = new WeakMap<GridState<unknown, GridCollection<unknown>>, string>(); | ||
interface GridProps extends DOMProps, AriaLabelingProps { | ||
export interface GridProps extends DOMProps, AriaLabelingProps { | ||
ref: RefObject<HTMLElement>, | ||
isVirtualized?: boolean, | ||
keyboardDelegate?: KeyboardDelegate | ||
keyboardDelegate?: KeyboardDelegate, | ||
focusMode?: 'row' | 'cell' | ||
} | ||
interface GridAria { | ||
export interface GridAria { | ||
gridProps: HTMLAttributes<HTMLElement> | ||
@@ -40,3 +40,4 @@ } | ||
isVirtualized, | ||
keyboardDelegate | ||
keyboardDelegate, | ||
focusMode | ||
} = props; | ||
@@ -57,4 +58,5 @@ | ||
direction, | ||
collator | ||
}), [keyboardDelegate, state.collection, state.disabledKeys, ref, direction, collator]); | ||
collator, | ||
focusMode | ||
}), [keyboardDelegate, state.collection, state.disabledKeys, ref, direction, collator, focusMode]); | ||
let {collectionProps} = useSelectableCollection({ | ||
@@ -67,3 +69,3 @@ ref, | ||
let id = useId(); | ||
gridIds.set(state, id); | ||
gridKeyboardDelegates.set(state, delegate); | ||
@@ -70,0 +72,0 @@ let domProps = filterDOMProps(props, {labelable: true}); |
@@ -15,14 +15,20 @@ /* | ||
import {GridCollection} from '@react-types/grid'; | ||
import {gridKeyboardDelegates} from './utils'; | ||
import {GridState} from '@react-stately/grid'; | ||
import {HTMLAttributes, RefObject} from 'react'; | ||
import {HTMLAttributes, KeyboardEvent as ReactKeyboardEvent, RefObject} from 'react'; | ||
import {isFocusVisible, usePress} from '@react-aria/interactions'; | ||
import {mergeProps} from '@react-aria/utils'; | ||
import {Node} from '@react-types/shared'; | ||
import {Node as RSNode} from '@react-types/shared'; | ||
import {useLocale} from '@react-aria/i18n'; | ||
import {useSelectableItem} from '@react-aria/selection'; | ||
interface GridCellProps { | ||
node: Node<unknown>, | ||
node: RSNode<unknown>, | ||
ref: RefObject<HTMLElement>, | ||
isVirtualized?: boolean, | ||
isDisabled?: boolean | ||
isDisabled?: boolean, | ||
/* when a cell is focused, should the cell or it's first focusable item be focused */ | ||
focusMode?: 'child' | 'cell', | ||
shouldSelectOnPressUp?: boolean | ||
} | ||
@@ -39,5 +45,10 @@ | ||
isVirtualized, | ||
isDisabled | ||
isDisabled, | ||
focusMode = 'child', | ||
shouldSelectOnPressUp | ||
} = props; | ||
let {direction} = useLocale(); | ||
let keyboardDelegate = gridKeyboardDelegates.get(state); | ||
// Handles focusing the cell. If there is a focusable child, | ||
@@ -47,6 +58,13 @@ // it is focused, otherwise the cell itself is focused. | ||
let treeWalker = getFocusableTreeWalker(ref.current); | ||
let focusable = treeWalker.firstChild() as HTMLElement; | ||
if (focusable) { | ||
focusSafely(focusable); | ||
} else { | ||
if (focusMode === 'child') { | ||
let focusable = state.selectionManager.childFocusStrategy === 'last' | ||
? last(treeWalker) | ||
: treeWalker.firstChild() as HTMLElement; | ||
if (focusable) { | ||
focusSafely(focusable); | ||
return; | ||
} | ||
} | ||
if (!ref.current.contains(document.activeElement)) { | ||
focusSafely(ref.current); | ||
@@ -61,3 +79,4 @@ } | ||
isVirtualized, | ||
focus | ||
focus, | ||
shouldSelectOnPressUp | ||
}); | ||
@@ -68,2 +87,100 @@ | ||
let onKeyDown = (e: ReactKeyboardEvent) => { | ||
let walker = getFocusableTreeWalker(ref.current); | ||
walker.currentNode = document.activeElement; | ||
switch (e.key) { | ||
case 'ArrowLeft': { | ||
// Find the next focusable element within the cell. | ||
let focusable = direction === 'rtl' | ||
? walker.nextNode() as HTMLElement | ||
: walker.previousNode() as HTMLElement; | ||
// Don't focus the cell itself if focusMode is "child" | ||
if (focusMode === 'child' && focusable === ref.current) { | ||
focusable = null; | ||
} | ||
if (focusable) { | ||
e.preventDefault(); | ||
e.stopPropagation(); | ||
focusSafely(focusable); | ||
} else { | ||
// If there is no next focusable child, then move to the next cell to the left of this one. | ||
// This will be handled by useSelectableCollection. However, if there is no cell to the left | ||
// of this one, only one column, and the grid doesn't focus rows, then the next key will be the | ||
// same as this one. In that case we need to handle focusing either the cell or the first/last | ||
// child, depending on the focus mode. | ||
let prev = keyboardDelegate.getKeyLeftOf(node.key); | ||
if (prev !== node.key) { | ||
break; | ||
} | ||
e.preventDefault(); | ||
e.stopPropagation(); | ||
if (focusMode === 'cell' && direction === 'rtl') { | ||
focusSafely(ref.current); | ||
} else { | ||
walker.currentNode = ref.current; | ||
focusable = direction === 'rtl' | ||
? walker.firstChild() as HTMLElement | ||
: last(walker); | ||
if (focusable) { | ||
focusSafely(focusable); | ||
} | ||
} | ||
} | ||
break; | ||
} | ||
case 'ArrowRight': { | ||
let focusable = direction === 'rtl' | ||
? walker.previousNode() as HTMLElement | ||
: walker.nextNode() as HTMLElement; | ||
if (focusMode === 'child' && focusable === ref.current) { | ||
focusable = null; | ||
} | ||
if (focusable) { | ||
e.preventDefault(); | ||
e.stopPropagation(); | ||
focusSafely(focusable); | ||
} else { | ||
let next = keyboardDelegate.getKeyRightOf(node.key); | ||
if (next !== node.key) { | ||
break; | ||
} | ||
e.preventDefault(); | ||
e.stopPropagation(); | ||
if (focusMode === 'cell' && direction === 'ltr') { | ||
focusSafely(ref.current); | ||
} else { | ||
walker.currentNode = ref.current; | ||
focusable = direction === 'rtl' | ||
? last(walker) | ||
: walker.firstChild() as HTMLElement; | ||
if (focusable) { | ||
focusSafely(focusable); | ||
} | ||
} | ||
} | ||
break; | ||
} | ||
case 'ArrowUp': | ||
case 'ArrowDown': | ||
// Prevent this event from reaching cell children, e.g. menu buttons. We want arrow keys to navigate | ||
// to the cell above/below instead. We need to re-dispatch the event from a higher parent so it still | ||
// bubbles and gets handled by useSelectableCollection. | ||
if (!e.altKey && ref.current.contains(e.target as HTMLElement)) { | ||
e.stopPropagation(); | ||
e.preventDefault(); | ||
ref.current.parentElement.dispatchEvent( | ||
new KeyboardEvent(e.nativeEvent.type, e.nativeEvent) | ||
); | ||
} | ||
break; | ||
} | ||
}; | ||
// Grid cells can have focusable elements inside them. In this case, focus should | ||
@@ -88,3 +205,3 @@ // be marshalled to that element rather than focusing the cell itself. | ||
requestAnimationFrame(() => { | ||
if (document.activeElement === ref.current) { | ||
if (focusMode === 'child' && document.activeElement === ref.current) { | ||
focus(); | ||
@@ -97,2 +214,3 @@ } | ||
role: 'gridcell', | ||
onKeyDownCapture: onKeyDown, | ||
onFocus | ||
@@ -109,1 +227,13 @@ }); | ||
} | ||
function last(walker: TreeWalker) { | ||
let next: HTMLElement; | ||
let last: HTMLElement; | ||
do { | ||
last = walker.lastChild() as HTMLElement; | ||
if (last) { | ||
next = last; | ||
} | ||
} while (last); | ||
return next; | ||
} |
@@ -25,3 +25,4 @@ /* | ||
isSelected?: boolean, | ||
isDisabled?: boolean | ||
isDisabled?: boolean, | ||
shouldSelectOnPressUp?: boolean | ||
} | ||
@@ -39,3 +40,4 @@ | ||
isSelected, | ||
isDisabled | ||
isDisabled, | ||
shouldSelectOnPressUp | ||
} = props; | ||
@@ -47,3 +49,4 @@ | ||
ref, | ||
isVirtualized | ||
isVirtualized, | ||
shouldSelectOnPressUp | ||
}); | ||
@@ -50,0 +53,0 @@ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Manifest confusion
Supply chain riskThis package has inconsistent metadata. This could be malicious or caused by an error when publishing the package.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Manifest confusion
Supply chain riskThis package has inconsistent metadata. This could be malicious or caused by an error when publishing the package.
Found 1 instance in 1 package
174538
16
1891