New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

@react-aria/gridlist

Package Overview
Dependencies
Maintainers
2
Versions
582
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@react-aria/gridlist - npm Package Compare versions

Comparing version

to
3.0.0-nightly-016590a4a-250131

dist/import.mjs

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,25 @@

import { AriaGridListProps } from "@react-types/list";
import { DOMAttributes, KeyboardDelegate, FocusableElement, Node } from "@react-types/shared";
import { AriaLabelingProps, CollectionBase, DisabledBehavior, DOMAttributes, DOMProps, FocusStrategy, 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 {
/** Whether to auto focus the gridlist or an option. */
autoFocus?: boolean | FocusStrategy;
/**
* 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 +33,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 +65,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 +89,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 +92,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-016590a4a-250131",
"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,21 +26,21 @@ "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-016590a4a-250131",
"@react-aria/grid": "3.0.0-nightly-016590a4a-250131",
"@react-aria/i18n": "3.0.0-nightly-016590a4a-250131",
"@react-aria/interactions": "3.0.0-nightly-016590a4a-250131",
"@react-aria/selection": "3.0.0-nightly-016590a4a-250131",
"@react-aria/utils": "3.0.0-nightly-016590a4a-250131",
"@react-stately/collections": "3.0.0-nightly-016590a4a-250131",
"@react-stately/list": "3.0.0-nightly-016590a4a-250131",
"@react-stately/tree": "3.0.0-nightly-016590a4a-250131",
"@react-types/shared": "3.0.0-nightly-016590a4a-250131",
"@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-rc.1",
"react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
},
"publishConfig": {
"access": "public"
},
"gitHead": "fe1ad32e50ebb524621113a4beb3678b6efc4ed0"
}
}
}

@@ -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,43 @@ /*

import {AriaGridListProps} from '@react-types/list';
import {DOMAttributes, KeyboardDelegate} from '@react-types/shared';
import {
AriaLabelingProps,
CollectionBase,
DisabledBehavior,
DOMAttributes,
DOMProps,
FocusStrategy,
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 {
/** Whether to auto focus the gridlist or an option. */
autoFocus?: boolean | FocusStrategy,
/**
* 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 +62,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 +97,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 +117,13 @@

ref,
keyboardDelegate: keyboardDelegate,
keyboardDelegate,
layoutDelegate,
isVirtualized,
selectOnFocus: state.selectionManager.selectionBehavior === 'replace'
selectOnFocus: state.selectionManager.selectionBehavior === 'replace',
shouldFocusWrap: props.shouldFocusWrap,
linkBehavior,
autoFocus: props.autoFocus
});
let id = useId();
listMap.set(state, {id, onAction});
let id = useId(props.id);
listMap.set(state, {id, onAction, linkBehavior, keyboardNavigationBehavior});

@@ -77,2 +135,6 @@ let descriptionProps = useHighlightSelectionDescription({

let hasTabbableChild = useHasTabbableChild(ref, {
isDisabled: state.collection.size !== 0
});
let domProps = filterDOMProps(props, {labelable: true});

@@ -86,3 +148,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 +152,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, Key, 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,18 @@ 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<Key | null>(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 (
ref.current !== null &&
((keyWhenFocused.current != null && node.key !== keyWhenFocused.current) ||
!ref.current?.contains(document.activeElement))
) {
focusSafely(ref.current);

@@ -70,2 +92,34 @@ }

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...
let children = state.collection.getChildren?.(node.key);
hasChildRows = [...(children ?? [])].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;
let setSize = 1;
if (node.level > 0 && node?.parentKey != null) {
let parent = state.collection.getItem(node.parentKey);
if (parent) {
// siblings must exist because our original node exists, same with lastItem
let siblings = state.collection.getChildren?.(parent.key)!;
setSize = getLastItem(siblings)!.index + 1;
}
} else {
setSize = ([...state.collection].filter(row => row.level === 0).at(-1)?.index ?? 0) + 1;
}
treeGridRowProps = {
'aria-expanded': isExpanded,
'aria-level': node.level + 1,
'aria-posinset': node?.index + 1,
'aria-setsize': setSize
};
}
let {itemProps, ...itemStates} = useSelectableItem({

@@ -77,8 +131,9 @@ 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
});
let onKeyDown = (e: ReactKeyboardEvent) => {
if (!e.currentTarget.contains(e.target as Element)) {
if (!e.currentTarget.contains(e.target as Element) || !ref.current || !document.activeElement) {
return;

@@ -90,24 +145,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 +191,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)});
}
}

@@ -151,3 +228,3 @@ }

e.preventDefault();
ref.current.parentElement.dispatchEvent(
ref.current.parentElement?.dispatchEvent(
new KeyboardEvent(e.nativeEvent.type, e.nativeEvent)

@@ -157,2 +234,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();
}
}
}
}

@@ -162,2 +251,3 @@ };

let onFocus = (e) => {
keyWhenFocused.current = node.key;
if (e.target !== ref.current) {

@@ -177,6 +267,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,

@@ -198,4 +301,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,

@@ -210,6 +314,6 @@ descriptionProps: {

function last(walker: TreeWalker) {
let next: FocusableElement;
let last: FocusableElement;
let next: FocusableElement | null = null;
let last: FocusableElement | null = null;
do {
last = walker.lastChild() as FocusableElement;
last = walker.lastChild() as FocusableElement | null;
if (last) {

@@ -216,0 +320,0 @@ next = last;

@@ -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'
}

@@ -28,3 +30,3 @@

export function getRowId<T>(state: ListState<T>, key: Key) {
let {id} = listMap.get(state);
let {id} = listMap.get(state) ?? {};
if (!id) {

@@ -31,0 +33,0 @@ throw new Error('Unknown list');

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