@react-aria/gridlist
Advanced tools
Comparing version 3.0.0-nightly.3434 to 3.0.0-nightly-07431f4b1-241030
227
dist/main.js
@@ -1,8 +0,6 @@ | ||
var $dSFus$reactariautils = require("@react-aria/utils"); | ||
var $dSFus$reactariagrid = require("@react-aria/grid"); | ||
var $dSFus$reactariaselection = require("@react-aria/selection"); | ||
var $dSFus$reactariafocus = require("@react-aria/focus"); | ||
var $dSFus$reactariainteractions = require("@react-aria/interactions"); | ||
var $dSFus$reactariai18n = require("@react-aria/i18n"); | ||
var $acf209ae814f1c93$exports = require("./useGridList.main.js"); | ||
var $f7116f5928c03f32$exports = require("./useGridListItem.main.js"); | ||
var $43131ea217bc2ad3$exports = require("./useGridListSelectionCheckbox.main.js"); | ||
function $parcel$export(e, n, v, s) { | ||
@@ -12,209 +10,20 @@ Object.defineProperty(e, n, {get: v, set: s, enumerable: true, configurable: true}); | ||
$parcel$export(module.exports, "useGridList", () => $acf209ae814f1c93$export$664f9155035607eb); | ||
$parcel$export(module.exports, "useGridListItem", () => $f7116f5928c03f32$export$9610e69494fadfd2); | ||
$parcel$export(module.exports, "useGridListSelectionCheckbox", () => $43131ea217bc2ad3$export$e29f2573fabbf7b9); | ||
$parcel$export(module.exports, "useGridList", () => $acf209ae814f1c93$exports.useGridList); | ||
$parcel$export(module.exports, "useGridListItem", () => $f7116f5928c03f32$exports.useGridListItem); | ||
$parcel$export(module.exports, "useGridListSelectionCheckbox", () => $43131ea217bc2ad3$exports.useGridListSelectionCheckbox); | ||
/* | ||
* Copyright 2022 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. | ||
*/ | ||
const $7db02799adae605d$export$5b9bb410392e3991 = new WeakMap(); | ||
function $7db02799adae605d$export$f45c25170b9a99c2(state, key) { | ||
let { id: id } = $7db02799adae605d$export$5b9bb410392e3991.get(state); | ||
if (!id) throw new Error('Unknown list'); | ||
return `${id}-${$7db02799adae605d$export$e0c709538cb8ae18(key)}`; | ||
} | ||
function $7db02799adae605d$export$e0c709538cb8ae18(key) { | ||
if (typeof key === 'string') return key.replace(/\s*/g, ''); | ||
return '' + key; | ||
} | ||
function $acf209ae814f1c93$export$664f9155035607eb(props, state, ref) { | ||
let { isVirtualized: isVirtualized , keyboardDelegate: keyboardDelegate , onAction: onAction } = props; | ||
if (!props['aria-label'] && !props['aria-labelledby']) console.warn('An aria-label or aria-labelledby prop is required for accessibility.'); | ||
let { listProps: listProps } = $dSFus$reactariaselection.useSelectableList({ | ||
selectionManager: state.selectionManager, | ||
collection: state.collection, | ||
disabledKeys: state.disabledKeys, | ||
ref: ref, | ||
keyboardDelegate: keyboardDelegate, | ||
isVirtualized: isVirtualized, | ||
selectOnFocus: state.selectionManager.selectionBehavior === 'replace' | ||
}); | ||
let id = $dSFus$reactariautils.useId(); | ||
$7db02799adae605d$export$5b9bb410392e3991.set(state, { | ||
id: id, | ||
onAction: onAction | ||
}); | ||
let descriptionProps = $dSFus$reactariagrid.useHighlightSelectionDescription({ | ||
selectionManager: state.selectionManager, | ||
hasItemActions: !!onAction | ||
}); | ||
let domProps = $dSFus$reactariautils.filterDOMProps(props, { | ||
labelable: true | ||
}); | ||
let gridProps = $dSFus$reactariautils.mergeProps(domProps, { | ||
role: 'grid', | ||
id: id, | ||
'aria-multiselectable': state.selectionManager.selectionMode === 'multiple' ? 'true' : undefined | ||
}, listProps, descriptionProps); | ||
if (isVirtualized) { | ||
gridProps['aria-rowcount'] = state.collection.size; | ||
gridProps['aria-colcount'] = 1; | ||
} | ||
$dSFus$reactariagrid.useGridSelectionAnnouncement({ | ||
}, state); | ||
return { | ||
gridProps: gridProps | ||
}; | ||
} | ||
function $f7116f5928c03f32$export$9610e69494fadfd2(props, state, ref) { | ||
// Copied from useGridCell + some modifications to make it not so grid specific | ||
let { node: node , isVirtualized: isVirtualized , shouldSelectOnPressUp: shouldSelectOnPressUp } = props; | ||
let { direction: direction } = $dSFus$reactariai18n.useLocale(); | ||
let { onAction: onAction } = $7db02799adae605d$export$5b9bb410392e3991.get(state); | ||
let descriptionId = $dSFus$reactariautils.useSlotId(); | ||
let focus = ()=>{ | ||
// Don't shift focus to the row if the active element is a element within the row already | ||
// (e.g. clicking on a row button) | ||
if (!ref.current.contains(document.activeElement)) $dSFus$reactariafocus.focusSafely(ref.current); | ||
}; | ||
let { itemProps: itemProps , ...itemStates } = $dSFus$reactariaselection.useSelectableItem({ | ||
selectionManager: state.selectionManager, | ||
key: node.key, | ||
ref: ref, | ||
isVirtualized: isVirtualized, | ||
shouldSelectOnPressUp: shouldSelectOnPressUp, | ||
onAction: onAction ? ()=>onAction(node.key) | ||
: undefined, | ||
focus: focus | ||
}); | ||
let onKeyDown = (e)=>{ | ||
if (!e.currentTarget.contains(e.target)) return; | ||
let walker = $dSFus$reactariafocus.getFocusableTreeWalker(ref.current); | ||
walker.currentNode = document.activeElement; | ||
switch(e.key){ | ||
case 'ArrowLeft': | ||
{ | ||
// Find the next focusable element within the row. | ||
let focusable = direction === 'rtl' ? walker.nextNode() : walker.previousNode(); | ||
if (focusable) { | ||
e.preventDefault(); | ||
e.stopPropagation(); | ||
$dSFus$reactariafocus.focusSafely(focusable); | ||
} else { | ||
// If there is no next focusable child, then return focus back to the row | ||
e.preventDefault(); | ||
e.stopPropagation(); | ||
if (direction === 'rtl') $dSFus$reactariafocus.focusSafely(ref.current); | ||
else { | ||
walker.currentNode = ref.current; | ||
let lastElement = $f7116f5928c03f32$var$last(walker); | ||
if (lastElement) $dSFus$reactariafocus.focusSafely(lastElement); | ||
} | ||
} | ||
break; | ||
} | ||
case 'ArrowRight': | ||
{ | ||
let focusable = direction === 'rtl' ? walker.previousNode() : walker.nextNode(); | ||
if (focusable) { | ||
e.preventDefault(); | ||
e.stopPropagation(); | ||
$dSFus$reactariafocus.focusSafely(focusable); | ||
} else { | ||
e.preventDefault(); | ||
e.stopPropagation(); | ||
if (direction === 'ltr') $dSFus$reactariafocus.focusSafely(ref.current); | ||
else { | ||
walker.currentNode = ref.current; | ||
let lastElement = $f7116f5928c03f32$var$last(walker); | ||
if (lastElement) $dSFus$reactariafocus.focusSafely(lastElement); | ||
} | ||
} | ||
break; | ||
} | ||
case 'ArrowUp': | ||
case 'ArrowDown': | ||
// Prevent this event from reaching row children, e.g. menu buttons. We want arrow keys to navigate | ||
// to the row 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; | ||
} | ||
}; | ||
let onFocus = (e)=>{ | ||
if (e.target !== ref.current) { | ||
// useSelectableItem only handles setting the focused key when | ||
// the focused element is the row itself. We also want to | ||
// set the focused key when a child element receives focus. | ||
// If focus is currently visible (e.g. the user is navigating with the keyboard), | ||
// then skip this. We want to restore focus to the previously focused row | ||
// in that case since the list should act like a single tab stop. | ||
if (!$dSFus$reactariainteractions.isFocusVisible()) state.selectionManager.setFocusedKey(node.key); | ||
return; | ||
} | ||
}; | ||
let rowProps = $dSFus$reactariautils.mergeProps(itemProps, { | ||
role: 'row', | ||
onKeyDownCapture: onKeyDown, | ||
onFocus: onFocus, | ||
'aria-label': node.textValue || undefined, | ||
'aria-selected': state.selectionManager.canSelectItem(node.key) ? state.selectionManager.isSelected(node.key) : undefined, | ||
'aria-disabled': state.selectionManager.isDisabled(node.key) || undefined, | ||
'aria-labelledby': descriptionId && node.textValue ? `${$7db02799adae605d$export$f45c25170b9a99c2(state, node.key)} ${descriptionId}` : undefined, | ||
id: $7db02799adae605d$export$f45c25170b9a99c2(state, node.key) | ||
}); | ||
if (isVirtualized) rowProps['aria-rowindex'] = node.index + 1; | ||
let gridCellProps = { | ||
role: 'gridcell', | ||
'aria-colindex': 1 | ||
}; | ||
return { | ||
rowProps: rowProps, | ||
gridCellProps: gridCellProps, | ||
descriptionProps: { | ||
id: descriptionId | ||
}, | ||
...itemStates | ||
}; | ||
} | ||
function $f7116f5928c03f32$var$last(walker) { | ||
let next; | ||
let last; | ||
do { | ||
last = walker.lastChild(); | ||
if (last) next = last; | ||
}while (last) | ||
return next; | ||
} | ||
function $43131ea217bc2ad3$export$e29f2573fabbf7b9(props, state) { | ||
let { key: key } = props; | ||
const { checkboxProps: checkboxProps } = $dSFus$reactariagrid.useGridSelectionCheckbox(props, state); | ||
return { | ||
checkboxProps: { | ||
...checkboxProps, | ||
'aria-labelledby': `${checkboxProps.id} ${$7db02799adae605d$export$f45c25170b9a99c2(state, key)}` | ||
} | ||
}; | ||
} | ||
//# sourceMappingURL=main.js.map |
@@ -1,213 +0,21 @@ | ||
import {useId as $13Gtr$useId, filterDOMProps as $13Gtr$filterDOMProps, mergeProps as $13Gtr$mergeProps, useSlotId as $13Gtr$useSlotId} from "@react-aria/utils"; | ||
import {useHighlightSelectionDescription as $13Gtr$useHighlightSelectionDescription, useGridSelectionAnnouncement as $13Gtr$useGridSelectionAnnouncement, useGridSelectionCheckbox as $13Gtr$useGridSelectionCheckbox} from "@react-aria/grid"; | ||
import {useSelectableList as $13Gtr$useSelectableList, useSelectableItem as $13Gtr$useSelectableItem} from "@react-aria/selection"; | ||
import {focusSafely as $13Gtr$focusSafely, getFocusableTreeWalker as $13Gtr$getFocusableTreeWalker} from "@react-aria/focus"; | ||
import {isFocusVisible as $13Gtr$isFocusVisible} from "@react-aria/interactions"; | ||
import {useLocale as $13Gtr$useLocale} from "@react-aria/i18n"; | ||
import {useGridList as $f47efb0c3a859cf2$export$664f9155035607eb} from "./useGridList.module.js"; | ||
import {useGridListItem as $4e8b0456ef72939f$export$9610e69494fadfd2} from "./useGridListItem.module.js"; | ||
import {useGridListSelectionCheckbox as $e52ffc04a4adbd52$export$e29f2573fabbf7b9} from "./useGridListSelectionCheckbox.module.js"; | ||
/* | ||
* Copyright 2022 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. | ||
*/ | ||
const $ce9b18daab526bbd$export$5b9bb410392e3991 = new WeakMap(); | ||
function $ce9b18daab526bbd$export$f45c25170b9a99c2(state, key) { | ||
let { id: id } = $ce9b18daab526bbd$export$5b9bb410392e3991.get(state); | ||
if (!id) throw new Error('Unknown list'); | ||
return `${id}-${$ce9b18daab526bbd$export$e0c709538cb8ae18(key)}`; | ||
} | ||
function $ce9b18daab526bbd$export$e0c709538cb8ae18(key) { | ||
if (typeof key === 'string') return key.replace(/\s*/g, ''); | ||
return '' + key; | ||
} | ||
function $f47efb0c3a859cf2$export$664f9155035607eb(props, state, ref) { | ||
let { isVirtualized: isVirtualized , keyboardDelegate: keyboardDelegate , onAction: onAction } = props; | ||
if (!props['aria-label'] && !props['aria-labelledby']) console.warn('An aria-label or aria-labelledby prop is required for accessibility.'); | ||
let { listProps: listProps } = $13Gtr$useSelectableList({ | ||
selectionManager: state.selectionManager, | ||
collection: state.collection, | ||
disabledKeys: state.disabledKeys, | ||
ref: ref, | ||
keyboardDelegate: keyboardDelegate, | ||
isVirtualized: isVirtualized, | ||
selectOnFocus: state.selectionManager.selectionBehavior === 'replace' | ||
}); | ||
let id = $13Gtr$useId(); | ||
$ce9b18daab526bbd$export$5b9bb410392e3991.set(state, { | ||
id: id, | ||
onAction: onAction | ||
}); | ||
let descriptionProps = $13Gtr$useHighlightSelectionDescription({ | ||
selectionManager: state.selectionManager, | ||
hasItemActions: !!onAction | ||
}); | ||
let domProps = $13Gtr$filterDOMProps(props, { | ||
labelable: true | ||
}); | ||
let gridProps = $13Gtr$mergeProps(domProps, { | ||
role: 'grid', | ||
id: id, | ||
'aria-multiselectable': state.selectionManager.selectionMode === 'multiple' ? 'true' : undefined | ||
}, listProps, descriptionProps); | ||
if (isVirtualized) { | ||
gridProps['aria-rowcount'] = state.collection.size; | ||
gridProps['aria-colcount'] = 1; | ||
} | ||
$13Gtr$useGridSelectionAnnouncement({ | ||
}, state); | ||
return { | ||
gridProps: gridProps | ||
}; | ||
} | ||
function $4e8b0456ef72939f$export$9610e69494fadfd2(props, state, ref) { | ||
// Copied from useGridCell + some modifications to make it not so grid specific | ||
let { node: node , isVirtualized: isVirtualized , shouldSelectOnPressUp: shouldSelectOnPressUp } = props; | ||
let { direction: direction } = $13Gtr$useLocale(); | ||
let { onAction: onAction } = $ce9b18daab526bbd$export$5b9bb410392e3991.get(state); | ||
let descriptionId = $13Gtr$useSlotId(); | ||
let focus = ()=>{ | ||
// Don't shift focus to the row if the active element is a element within the row already | ||
// (e.g. clicking on a row button) | ||
if (!ref.current.contains(document.activeElement)) $13Gtr$focusSafely(ref.current); | ||
}; | ||
let { itemProps: itemProps , ...itemStates } = $13Gtr$useSelectableItem({ | ||
selectionManager: state.selectionManager, | ||
key: node.key, | ||
ref: ref, | ||
isVirtualized: isVirtualized, | ||
shouldSelectOnPressUp: shouldSelectOnPressUp, | ||
onAction: onAction ? ()=>onAction(node.key) | ||
: undefined, | ||
focus: focus | ||
}); | ||
let onKeyDown = (e)=>{ | ||
if (!e.currentTarget.contains(e.target)) return; | ||
let walker = $13Gtr$getFocusableTreeWalker(ref.current); | ||
walker.currentNode = document.activeElement; | ||
switch(e.key){ | ||
case 'ArrowLeft': | ||
{ | ||
// Find the next focusable element within the row. | ||
let focusable = direction === 'rtl' ? walker.nextNode() : walker.previousNode(); | ||
if (focusable) { | ||
e.preventDefault(); | ||
e.stopPropagation(); | ||
$13Gtr$focusSafely(focusable); | ||
} else { | ||
// If there is no next focusable child, then return focus back to the row | ||
e.preventDefault(); | ||
e.stopPropagation(); | ||
if (direction === 'rtl') $13Gtr$focusSafely(ref.current); | ||
else { | ||
walker.currentNode = ref.current; | ||
let lastElement = $4e8b0456ef72939f$var$last(walker); | ||
if (lastElement) $13Gtr$focusSafely(lastElement); | ||
} | ||
} | ||
break; | ||
} | ||
case 'ArrowRight': | ||
{ | ||
let focusable = direction === 'rtl' ? walker.previousNode() : walker.nextNode(); | ||
if (focusable) { | ||
e.preventDefault(); | ||
e.stopPropagation(); | ||
$13Gtr$focusSafely(focusable); | ||
} else { | ||
e.preventDefault(); | ||
e.stopPropagation(); | ||
if (direction === 'ltr') $13Gtr$focusSafely(ref.current); | ||
else { | ||
walker.currentNode = ref.current; | ||
let lastElement = $4e8b0456ef72939f$var$last(walker); | ||
if (lastElement) $13Gtr$focusSafely(lastElement); | ||
} | ||
} | ||
break; | ||
} | ||
case 'ArrowUp': | ||
case 'ArrowDown': | ||
// Prevent this event from reaching row children, e.g. menu buttons. We want arrow keys to navigate | ||
// to the row 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; | ||
} | ||
}; | ||
let onFocus = (e)=>{ | ||
if (e.target !== ref.current) { | ||
// useSelectableItem only handles setting the focused key when | ||
// the focused element is the row itself. We also want to | ||
// set the focused key when a child element receives focus. | ||
// If focus is currently visible (e.g. the user is navigating with the keyboard), | ||
// then skip this. We want to restore focus to the previously focused row | ||
// in that case since the list should act like a single tab stop. | ||
if (!$13Gtr$isFocusVisible()) state.selectionManager.setFocusedKey(node.key); | ||
return; | ||
} | ||
}; | ||
let rowProps = $13Gtr$mergeProps(itemProps, { | ||
role: 'row', | ||
onKeyDownCapture: onKeyDown, | ||
onFocus: onFocus, | ||
'aria-label': node.textValue || undefined, | ||
'aria-selected': state.selectionManager.canSelectItem(node.key) ? state.selectionManager.isSelected(node.key) : undefined, | ||
'aria-disabled': state.selectionManager.isDisabled(node.key) || undefined, | ||
'aria-labelledby': descriptionId && node.textValue ? `${$ce9b18daab526bbd$export$f45c25170b9a99c2(state, node.key)} ${descriptionId}` : undefined, | ||
id: $ce9b18daab526bbd$export$f45c25170b9a99c2(state, node.key) | ||
}); | ||
if (isVirtualized) rowProps['aria-rowindex'] = node.index + 1; | ||
let gridCellProps = { | ||
role: 'gridcell', | ||
'aria-colindex': 1 | ||
}; | ||
return { | ||
rowProps: rowProps, | ||
gridCellProps: gridCellProps, | ||
descriptionProps: { | ||
id: descriptionId | ||
}, | ||
...itemStates | ||
}; | ||
} | ||
function $4e8b0456ef72939f$var$last(walker) { | ||
let next; | ||
let last; | ||
do { | ||
last = walker.lastChild(); | ||
if (last) next = last; | ||
}while (last) | ||
return next; | ||
} | ||
function $e52ffc04a4adbd52$export$e29f2573fabbf7b9(props, state) { | ||
let { key: key } = props; | ||
const { checkboxProps: checkboxProps } = $13Gtr$useGridSelectionCheckbox(props, state); | ||
return { | ||
checkboxProps: { | ||
...checkboxProps, | ||
'aria-labelledby': `${checkboxProps.id} ${$ce9b18daab526bbd$export$f45c25170b9a99c2(state, key)}` | ||
} | ||
}; | ||
} | ||
export {$f47efb0c3a859cf2$export$664f9155035607eb as useGridList, $4e8b0456ef72939f$export$9610e69494fadfd2 as useGridListItem, $e52ffc04a4adbd52$export$e29f2573fabbf7b9 as useGridListSelectionCheckbox}; | ||
//# sourceMappingURL=module.js.map |
@@ -1,7 +0,23 @@ | ||
import { AriaGridListProps } from "@react-types/list"; | ||
import { DOMAttributes, KeyboardDelegate, FocusableElement, Node } from "@react-types/shared"; | ||
import { AriaLabelingProps, CollectionBase, DisabledBehavior, DOMAttributes, DOMProps, Key, KeyboardDelegate, LayoutDelegate, MultipleSelection, RefObject, FocusableElement, Node } from "@react-types/shared"; | ||
import { ListState } from "@react-stately/list"; | ||
import { RefObject } from "react"; | ||
import { SelectableItemStates } from "@react-aria/selection"; | ||
import { TreeState } from "@react-stately/tree"; | ||
import { AriaGridSelectionCheckboxProps, GridSelectionCheckboxAria } from "@react-aria/grid"; | ||
export interface GridListProps<T> extends CollectionBase<T>, MultipleSelection { | ||
/** | ||
* Handler that is called when a user performs an action on an item. The exact user event depends on | ||
* the collection's `selectionBehavior` prop and the interaction modality. | ||
*/ | ||
onAction?: (key: Key) => void; | ||
/** Whether `disabledKeys` applies to all interactions, or only selection. */ | ||
disabledBehavior?: DisabledBehavior; | ||
} | ||
export interface AriaGridListProps<T> extends GridListProps<T>, DOMProps, AriaLabelingProps { | ||
/** | ||
* Whether keyboard navigation to focusable elements within grid list items is | ||
* via the left/right arrow keys or the tab key. | ||
* @default 'arrow' | ||
*/ | ||
keyboardNavigationBehavior?: 'arrow' | 'tab'; | ||
} | ||
export interface AriaGridListOptions<T> extends Omit<AriaGridListProps<T>, 'children'> { | ||
@@ -15,2 +31,21 @@ /** Whether the list uses virtual scrolling. */ | ||
keyboardDelegate?: KeyboardDelegate; | ||
/** | ||
* A delegate object that provides layout information for items in the collection. | ||
* By default this uses the DOM, but this can be overridden to implement things like | ||
* virtualized scrolling. | ||
*/ | ||
layoutDelegate?: LayoutDelegate; | ||
/** | ||
* Whether focus should wrap around when the end/start is reached. | ||
* @default false | ||
*/ | ||
shouldFocusWrap?: boolean; | ||
/** | ||
* The behavior of links in the collection. | ||
* - 'action': link behaves like onAction. | ||
* - 'selection': link follows selection interactions (e.g. if URL drives selection). | ||
* - 'override': links override all other interactions (link items are not selectable). | ||
* @default 'action' | ||
*/ | ||
linkBehavior?: 'action' | 'selection' | 'override'; | ||
} | ||
@@ -28,3 +63,3 @@ export interface GridListAria { | ||
*/ | ||
export function useGridList<T>(props: AriaGridListOptions<T>, state: ListState<T>, ref: RefObject<HTMLElement>): GridListAria; | ||
export function useGridList<T>(props: AriaGridListOptions<T>, state: ListState<T>, ref: RefObject<HTMLElement | null>): GridListAria; | ||
export interface AriaGridListItemOptions { | ||
@@ -52,3 +87,3 @@ /** An object representing the list item. Contains all the relevant information that makes up the list row. */ | ||
*/ | ||
export function useGridListItem<T>(props: AriaGridListItemOptions, state: ListState<T>, ref: RefObject<FocusableElement>): GridListItemAria; | ||
export function useGridListItem<T>(props: AriaGridListItemOptions, state: ListState<T> | TreeState<T>, ref: RefObject<FocusableElement | null>): GridListItemAria; | ||
/** | ||
@@ -55,0 +90,0 @@ * Provides the behavior and accessibility implementation for a selection checkbox in a grid list. |
{ | ||
"name": "@react-aria/gridlist", | ||
"version": "3.0.0-nightly.3434+fe1ad32e5", | ||
"version": "3.0.0-nightly-07431f4b1-241030", | ||
"description": "Spectrum UI components in React", | ||
@@ -8,2 +8,7 @@ "license": "Apache-2.0", | ||
"module": "dist/module.js", | ||
"exports": { | ||
"types": "./dist/types.d.ts", | ||
"import": "./dist/import.mjs", | ||
"require": "./dist/main.js" | ||
}, | ||
"types": "dist/types.d.ts", | ||
@@ -21,16 +26,17 @@ "source": "src/index.ts", | ||
"dependencies": { | ||
"@babel/runtime": "^7.6.2", | ||
"@react-aria/focus": "3.0.0-nightly.1734+fe1ad32e5", | ||
"@react-aria/grid": "3.4.1-nightly.3434+fe1ad32e5", | ||
"@react-aria/i18n": "3.0.0-nightly.1734+fe1ad32e5", | ||
"@react-aria/interactions": "3.0.0-nightly.1734+fe1ad32e5", | ||
"@react-aria/selection": "3.0.0-nightly.1734+fe1ad32e5", | ||
"@react-aria/utils": "3.0.0-nightly.1734+fe1ad32e5", | ||
"@react-stately/list": "3.5.3-nightly.3434+fe1ad32e5", | ||
"@react-types/checkbox": "3.0.0-nightly.1734+fe1ad32e5", | ||
"@react-types/list": "3.0.0-nightly.3434+fe1ad32e5", | ||
"@react-types/shared": "3.0.0-nightly.1734+fe1ad32e5" | ||
"@react-aria/focus": "^3.0.0-nightly-07431f4b1-241030", | ||
"@react-aria/grid": "^3.0.0-nightly-07431f4b1-241030", | ||
"@react-aria/i18n": "^3.0.0-nightly-07431f4b1-241030", | ||
"@react-aria/interactions": "^3.0.0-nightly-07431f4b1-241030", | ||
"@react-aria/selection": "^3.0.0-nightly-07431f4b1-241030", | ||
"@react-aria/utils": "^3.0.0-nightly-07431f4b1-241030", | ||
"@react-stately/collections": "^3.0.0-nightly-07431f4b1-241030", | ||
"@react-stately/list": "^3.0.0-nightly-07431f4b1-241030", | ||
"@react-stately/tree": "^3.0.0-nightly-07431f4b1-241030", | ||
"@react-types/shared": "^3.0.0-nightly-07431f4b1-241030", | ||
"@swc/helpers": "^0.5.0" | ||
}, | ||
"peerDependencies": { | ||
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" | ||
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0", | ||
"react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0" | ||
}, | ||
@@ -40,3 +46,3 @@ "publishConfig": { | ||
}, | ||
"gitHead": "fe1ad32e50ebb524621113a4beb3678b6efc4ed0" | ||
} | ||
"stableVersion": "3.9.5" | ||
} |
@@ -17,4 +17,4 @@ /* | ||
export type {AriaGridListOptions, GridListAria} from './useGridList'; | ||
export type {AriaGridListOptions, AriaGridListProps, GridListAria, GridListProps} from './useGridList'; | ||
export type {AriaGridListItemOptions, GridListItemAria} from './useGridListItem'; | ||
export type {AriaGridSelectionCheckboxProps, GridSelectionCheckboxAria} from '@react-aria/grid'; |
@@ -13,11 +13,40 @@ /* | ||
import {AriaGridListProps} from '@react-types/list'; | ||
import {DOMAttributes, KeyboardDelegate} from '@react-types/shared'; | ||
import { | ||
AriaLabelingProps, | ||
CollectionBase, | ||
DisabledBehavior, | ||
DOMAttributes, | ||
DOMProps, | ||
Key, | ||
KeyboardDelegate, | ||
LayoutDelegate, | ||
MultipleSelection, | ||
RefObject | ||
} from '@react-types/shared'; | ||
import {filterDOMProps, mergeProps, useId} from '@react-aria/utils'; | ||
import {listMap} from './utils'; | ||
import {ListState} from '@react-stately/list'; | ||
import {RefObject} from 'react'; | ||
import {useGridSelectionAnnouncement, useHighlightSelectionDescription} from '@react-aria/grid'; | ||
import {useHasTabbableChild} from '@react-aria/focus'; | ||
import {useSelectableList} from '@react-aria/selection'; | ||
export interface GridListProps<T> extends CollectionBase<T>, MultipleSelection { | ||
/** | ||
* Handler that is called when a user performs an action on an item. The exact user event depends on | ||
* the collection's `selectionBehavior` prop and the interaction modality. | ||
*/ | ||
onAction?: (key: Key) => void, | ||
/** Whether `disabledKeys` applies to all interactions, or only selection. */ | ||
disabledBehavior?: DisabledBehavior | ||
} | ||
export interface AriaGridListProps<T> extends GridListProps<T>, DOMProps, AriaLabelingProps { | ||
/** | ||
* Whether keyboard navigation to focusable elements within grid list items is | ||
* via the left/right arrow keys or the tab key. | ||
* @default 'arrow' | ||
*/ | ||
keyboardNavigationBehavior?: 'arrow' | 'tab' | ||
} | ||
export interface AriaGridListOptions<T> extends Omit<AriaGridListProps<T>, 'children'> { | ||
@@ -30,3 +59,22 @@ /** Whether the list uses virtual scrolling. */ | ||
*/ | ||
keyboardDelegate?: KeyboardDelegate | ||
keyboardDelegate?: KeyboardDelegate, | ||
/** | ||
* A delegate object that provides layout information for items in the collection. | ||
* By default this uses the DOM, but this can be overridden to implement things like | ||
* virtualized scrolling. | ||
*/ | ||
layoutDelegate?: LayoutDelegate, | ||
/** | ||
* Whether focus should wrap around when the end/start is reached. | ||
* @default false | ||
*/ | ||
shouldFocusWrap?: boolean, | ||
/** | ||
* The behavior of links in the collection. | ||
* - 'action': link behaves like onAction. | ||
* - 'selection': link follows selection interactions (e.g. if URL drives selection). | ||
* - 'override': links override all other interactions (link items are not selectable). | ||
* @default 'action' | ||
*/ | ||
linkBehavior?: 'action' | 'selection' | 'override' | ||
} | ||
@@ -46,7 +94,10 @@ | ||
*/ | ||
export function useGridList<T>(props: AriaGridListOptions<T>, state: ListState<T>, ref: RefObject<HTMLElement>): GridListAria { | ||
export function useGridList<T>(props: AriaGridListOptions<T>, state: ListState<T>, ref: RefObject<HTMLElement | null>): GridListAria { | ||
let { | ||
isVirtualized, | ||
keyboardDelegate, | ||
onAction | ||
layoutDelegate, | ||
onAction, | ||
linkBehavior = 'action', | ||
keyboardNavigationBehavior = 'arrow' | ||
} = props; | ||
@@ -63,9 +114,12 @@ | ||
ref, | ||
keyboardDelegate: keyboardDelegate, | ||
keyboardDelegate, | ||
layoutDelegate, | ||
isVirtualized, | ||
selectOnFocus: state.selectionManager.selectionBehavior === 'replace' | ||
selectOnFocus: state.selectionManager.selectionBehavior === 'replace', | ||
shouldFocusWrap: props.shouldFocusWrap, | ||
linkBehavior | ||
}); | ||
let id = useId(); | ||
listMap.set(state, {id, onAction}); | ||
let id = useId(props.id); | ||
listMap.set(state, {id, onAction, linkBehavior, keyboardNavigationBehavior}); | ||
@@ -77,2 +131,6 @@ let descriptionProps = useHighlightSelectionDescription({ | ||
let hasTabbableChild = useHasTabbableChild(ref, { | ||
isDisabled: state.collection.size !== 0 | ||
}); | ||
let domProps = filterDOMProps(props, {labelable: true}); | ||
@@ -86,3 +144,4 @@ let gridProps: DOMAttributes = mergeProps( | ||
}, | ||
listProps, | ||
// If collection is empty, make sure the grid is tabbable unless there is a child tabbable element. | ||
state.collection.size === 0 ? {tabIndex: hasTabbableChild ? -1 : 0} : listProps, | ||
descriptionProps | ||
@@ -89,0 +148,0 @@ ); |
@@ -13,10 +13,12 @@ /* | ||
import {DOMAttributes, FocusableElement, Node as RSNode} from '@react-types/shared'; | ||
import {chain, getScrollParent, mergeProps, scrollIntoViewport, useSlotId, useSyntheticLinkProps} from '@react-aria/utils'; | ||
import {DOMAttributes, FocusableElement, RefObject, Node as RSNode} from '@react-types/shared'; | ||
import {focusSafely, getFocusableTreeWalker} from '@react-aria/focus'; | ||
import {getLastItem} from '@react-stately/collections'; | ||
import {getRowId, listMap} from './utils'; | ||
import {HTMLAttributes, KeyboardEvent as ReactKeyboardEvent, useRef} from 'react'; | ||
import {isFocusVisible} from '@react-aria/interactions'; | ||
import type {ListState} from '@react-stately/list'; | ||
import {mergeProps, useSlotId} from '@react-aria/utils'; | ||
import {KeyboardEvent as ReactKeyboardEvent, RefObject} from 'react'; | ||
import {SelectableItemStates, useSelectableItem} from '@react-aria/selection'; | ||
import type {TreeState} from '@react-stately/tree'; | ||
import {useLocale} from '@react-aria/i18n'; | ||
@@ -42,2 +44,13 @@ | ||
const EXPANSION_KEYS = { | ||
'expand': { | ||
ltr: 'ArrowRight', | ||
rtl: 'ArrowLeft' | ||
}, | ||
'collapse': { | ||
ltr: 'ArrowLeft', | ||
rtl: 'ArrowRight' | ||
} | ||
}; | ||
/** | ||
@@ -49,3 +62,3 @@ * Provides the behavior and accessibility implementation for a row in a grid list. | ||
*/ | ||
export function useGridListItem<T>(props: AriaGridListItemOptions, state: ListState<T>, ref: RefObject<FocusableElement>): GridListItemAria { | ||
export function useGridListItem<T>(props: AriaGridListItemOptions, state: ListState<T> | TreeState<T>, ref: RefObject<FocusableElement | null>): GridListItemAria { | ||
// Copied from useGridCell + some modifications to make it not so grid specific | ||
@@ -58,9 +71,17 @@ let { | ||
// let stringFormatter = useLocalizedStringFormatter(intlMessages, '@react-aria/gridlist'); | ||
let {direction} = useLocale(); | ||
let {onAction} = listMap.get(state); | ||
let {onAction, linkBehavior, keyboardNavigationBehavior} = listMap.get(state); | ||
let descriptionId = useSlotId(); | ||
// We need to track the key of the item at the time it was last focused so that we force | ||
// focus to go to the item when the DOM node is reused for a different item in a virtualizer. | ||
let keyWhenFocused = useRef(null); | ||
let focus = () => { | ||
// Don't shift focus to the row if the active element is a element within the row already | ||
// (e.g. clicking on a row button) | ||
if (!ref.current.contains(document.activeElement)) { | ||
if ( | ||
(keyWhenFocused.current != null && node.key !== keyWhenFocused.current) || | ||
!ref.current?.contains(document.activeElement) | ||
) { | ||
focusSafely(ref.current); | ||
@@ -70,2 +91,24 @@ } | ||
let treeGridRowProps: HTMLAttributes<HTMLElement> = {}; | ||
let hasChildRows; | ||
let hasLink = state.selectionManager.isLink(node.key); | ||
if (node != null && 'expandedKeys' in state) { | ||
// TODO: ideally node.hasChildNodes would be a way to tell if a row has child nodes, but the row's contents make it so that value is always | ||
// true... | ||
hasChildRows = [...state.collection.getChildren(node.key)].length > 1; | ||
if (onAction == null && !hasLink && state.selectionManager.selectionMode === 'none' && hasChildRows) { | ||
onAction = () => state.toggleKey(node.key); | ||
} | ||
let isExpanded = hasChildRows ? state.expandedKeys.has(node.key) : undefined; | ||
treeGridRowProps = { | ||
'aria-expanded': isExpanded, | ||
'aria-level': node.level + 1, | ||
'aria-posinset': node?.index + 1, | ||
'aria-setsize': node.level > 0 ? | ||
(getLastItem(state.collection.getChildren(node?.parentKey))).index + 1 : | ||
[...state.collection].filter(row => row.level === 0).at(-1).index + 1 | ||
}; | ||
} | ||
let {itemProps, ...itemStates} = useSelectableItem({ | ||
@@ -77,4 +120,5 @@ selectionManager: state.selectionManager, | ||
shouldSelectOnPressUp, | ||
onAction: onAction ? () => onAction(node.key) : undefined, | ||
focus | ||
onAction: onAction || node.props?.onAction ? chain(node.props?.onAction, onAction ? () => onAction(node.key) : undefined) : undefined, | ||
focus, | ||
linkBehavior | ||
}); | ||
@@ -90,24 +134,41 @@ | ||
if ('expandedKeys' in state && document.activeElement === ref.current) { | ||
if ((e.key === EXPANSION_KEYS['expand'][direction]) && state.selectionManager.focusedKey === node.key && hasChildRows && !state.expandedKeys.has(node.key)) { | ||
state.toggleKey(node.key); | ||
e.stopPropagation(); | ||
return; | ||
} else if ((e.key === EXPANSION_KEYS['collapse'][direction]) && state.selectionManager.focusedKey === node.key && hasChildRows && state.expandedKeys.has(node.key)) { | ||
state.toggleKey(node.key); | ||
e.stopPropagation(); | ||
return; | ||
} | ||
} | ||
switch (e.key) { | ||
case 'ArrowLeft': { | ||
// Find the next focusable element within the row. | ||
let focusable = direction === 'rtl' | ||
? walker.nextNode() as FocusableElement | ||
: walker.previousNode() as FocusableElement; | ||
if (keyboardNavigationBehavior === 'arrow') { | ||
// Find the next focusable element within the row. | ||
let focusable = direction === 'rtl' | ||
? walker.nextNode() as FocusableElement | ||
: walker.previousNode() as FocusableElement; | ||
if (focusable) { | ||
e.preventDefault(); | ||
e.stopPropagation(); | ||
focusSafely(focusable); | ||
} else { | ||
// If there is no next focusable child, then return focus back to the row | ||
e.preventDefault(); | ||
e.stopPropagation(); | ||
if (direction === 'rtl') { | ||
focusSafely(ref.current); | ||
if (focusable) { | ||
e.preventDefault(); | ||
e.stopPropagation(); | ||
focusSafely(focusable); | ||
scrollIntoViewport(focusable, {containingElement: getScrollParent(ref.current)}); | ||
} else { | ||
walker.currentNode = ref.current; | ||
let lastElement = last(walker); | ||
if (lastElement) { | ||
focusSafely(lastElement); | ||
// If there is no next focusable child, then return focus back to the row | ||
e.preventDefault(); | ||
e.stopPropagation(); | ||
if (direction === 'rtl') { | ||
focusSafely(ref.current); | ||
scrollIntoViewport(ref.current, {containingElement: getScrollParent(ref.current)}); | ||
} else { | ||
walker.currentNode = ref.current; | ||
let lastElement = last(walker); | ||
if (lastElement) { | ||
focusSafely(lastElement); | ||
scrollIntoViewport(lastElement, {containingElement: getScrollParent(ref.current)}); | ||
} | ||
} | ||
@@ -119,20 +180,25 @@ } | ||
case 'ArrowRight': { | ||
let focusable = direction === 'rtl' | ||
? walker.previousNode() as FocusableElement | ||
: walker.nextNode() as FocusableElement; | ||
if (keyboardNavigationBehavior === 'arrow') { | ||
let focusable = direction === 'rtl' | ||
? walker.previousNode() as FocusableElement | ||
: walker.nextNode() as FocusableElement; | ||
if (focusable) { | ||
e.preventDefault(); | ||
e.stopPropagation(); | ||
focusSafely(focusable); | ||
} else { | ||
e.preventDefault(); | ||
e.stopPropagation(); | ||
if (direction === 'ltr') { | ||
focusSafely(ref.current); | ||
if (focusable) { | ||
e.preventDefault(); | ||
e.stopPropagation(); | ||
focusSafely(focusable); | ||
scrollIntoViewport(focusable, {containingElement: getScrollParent(ref.current)}); | ||
} else { | ||
walker.currentNode = ref.current; | ||
let lastElement = last(walker); | ||
if (lastElement) { | ||
focusSafely(lastElement); | ||
e.preventDefault(); | ||
e.stopPropagation(); | ||
if (direction === 'ltr') { | ||
focusSafely(ref.current); | ||
scrollIntoViewport(ref.current, {containingElement: getScrollParent(ref.current)}); | ||
} else { | ||
walker.currentNode = ref.current; | ||
let lastElement = last(walker); | ||
if (lastElement) { | ||
focusSafely(lastElement); | ||
scrollIntoViewport(lastElement, {containingElement: getScrollParent(ref.current)}); | ||
} | ||
} | ||
@@ -156,2 +222,14 @@ } | ||
break; | ||
case 'Tab': { | ||
if (keyboardNavigationBehavior === 'tab') { | ||
// If there is another focusable element within this item, stop propagation so the tab key | ||
// is handled by the browser and not by useSelectableCollection (which would take us out of the list). | ||
let walker = getFocusableTreeWalker(ref.current, {tabbable: true}); | ||
walker.currentNode = document.activeElement; | ||
let next = e.shiftKey ? walker.previousNode() : walker.nextNode(); | ||
if (next) { | ||
e.stopPropagation(); | ||
} | ||
} | ||
} | ||
} | ||
@@ -161,2 +239,3 @@ }; | ||
let onFocus = (e) => { | ||
keyWhenFocused.current = node.key; | ||
if (e.target !== ref.current) { | ||
@@ -176,6 +255,19 @@ // useSelectableItem only handles setting the focused key when | ||
let rowProps: DOMAttributes = mergeProps(itemProps, { | ||
let syntheticLinkProps = useSyntheticLinkProps(node.props); | ||
let linkProps = itemStates.hasAction ? syntheticLinkProps : {}; | ||
// TODO: re-add when we get translations and fix this for iOS VO | ||
// let rowAnnouncement; | ||
// if (onAction) { | ||
// rowAnnouncement = stringFormatter.format('hasActionAnnouncement'); | ||
// } else if (hasLink) { | ||
// rowAnnouncement = stringFormatter.format('hasLinkAnnouncement', { | ||
// link: node.props.href | ||
// }); | ||
// } | ||
let rowProps: DOMAttributes = mergeProps(itemProps, linkProps, { | ||
role: 'row', | ||
onKeyDownCapture: onKeyDown, | ||
onFocus, | ||
// 'aria-label': [(node.textValue || undefined), rowAnnouncement].filter(Boolean).join(', '), | ||
'aria-label': node.textValue || undefined, | ||
@@ -197,4 +289,5 @@ 'aria-selected': state.selectionManager.canSelectItem(node.key) ? state.selectionManager.isSelected(node.key) : undefined, | ||
// TODO: should isExpanded and hasChildRows be a item state that gets returned by the hook? | ||
return { | ||
rowProps, | ||
rowProps: {...mergeProps(rowProps, treeGridRowProps)}, | ||
gridCellProps, | ||
@@ -201,0 +294,0 @@ descriptionProps: { |
@@ -13,3 +13,3 @@ /* | ||
import {Key} from 'react'; | ||
import {Key} from '@react-types/shared'; | ||
import type {ListState} from '@react-stately/list'; | ||
@@ -19,3 +19,5 @@ | ||
id: string, | ||
onAction: (key: Key) => void | ||
onAction: (key: Key) => void, | ||
linkBehavior?: 'action' | 'selection' | 'override', | ||
keyboardNavigationBehavior: 'arrow' | 'tab' | ||
} | ||
@@ -22,0 +24,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
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Unidentified License
License(Experimental) Something that seems like a license was found, but its contents could not be matched with a known license.
Found 6 instances 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
152740
34
1727
13
6
60
+ Added@react-stately/collections@^3.0.0-nightly-07431f4b1-241030
+ Added@swc/helpers@^0.5.0
+ Added@formatjs/ecma402-abstract@2.2.4(transitive)
+ Added@formatjs/fast-memoize@2.2.3(transitive)
+ Added@formatjs/icu-messageformat-parser@2.9.4(transitive)
+ Added@formatjs/icu-skeleton-parser@1.8.8(transitive)
+ Added@formatjs/intl-localematcher@0.5.8(transitive)
+ Added@internationalized/date@3.6.0(transitive)
+ Added@internationalized/message@3.1.6(transitive)
+ Added@internationalized/number@3.6.0(transitive)
+ Added@internationalized/string@3.2.5(transitive)
+ Added@react-aria/focus@3.19.0(transitive)
+ Added@react-aria/grid@3.11.0(transitive)
+ Added@react-aria/i18n@3.12.4(transitive)
+ Added@react-aria/interactions@3.22.5(transitive)
+ Added@react-aria/live-announcer@3.4.1(transitive)
+ Added@react-aria/selection@3.21.0(transitive)
+ Added@react-aria/ssr@3.9.7(transitive)
+ Added@react-aria/utils@3.26.0(transitive)
+ Added@react-stately/collections@3.12.0(transitive)
+ Added@react-stately/grid@3.10.0(transitive)
+ Added@react-stately/list@3.11.1(transitive)
+ Added@react-stately/selection@3.18.0(transitive)
+ Added@react-stately/tree@3.8.6(transitive)
+ Added@react-stately/utils@3.10.5(transitive)
+ Added@react-types/checkbox@3.9.0(transitive)
+ Added@react-types/grid@3.2.10(transitive)
+ Added@react-types/shared@3.26.0(transitive)
+ Added@swc/helpers@0.5.15(transitive)
+ Addedclsx@2.1.1(transitive)
+ Addedintl-messageformat@10.7.7(transitive)
+ Addedreact-dom@18.3.1(transitive)
+ Addedscheduler@0.23.2(transitive)
+ Addedtslib@2.8.1(transitive)
- Removed@babel/runtime@^7.6.2
- Removed@babel/runtime@7.26.0(transitive)
- Removedregenerator-runtime@0.14.1(transitive)
Updated@react-aria/interactions@^3.0.0-nightly-07431f4b1-241030
Updated@react-aria/selection@^3.0.0-nightly-07431f4b1-241030