UNPKG

react-aria

Version:
364 lines (344 loc) • 19.2 kB
var $2f95486cfdaa743c$exports = require("../utils/chain.cjs"); var $4a053a4bf25e52fb$exports = require("../interactions/focusSafely.cjs"); var $da02ee888921bc9e$exports = require("../utils/shadowdom/DOMFunctions.cjs"); var $9fb4ac1cc58342cc$exports = require("../focus/FocusScope.cjs"); var $daad9bac4699131f$exports = require("./utils.cjs"); var $d865e4ff74ef4a73$exports = require("../utils/getScrollParent.cjs"); var $d0df89f3abe2c2ca$exports = require("../interactions/useFocusVisible.cjs"); var $48f566b6becd50da$exports = require("../utils/isFocusable.cjs"); var $89b39774f3b79dbb$exports = require("../utils/mergeProps.cjs"); var $9a1324d6ffd8bbb0$exports = require("../utils/scrollIntoView.cjs"); var $f38c7e3583533f40$exports = require("../selection/useSelectableItem.cjs"); var $2522e612fa919664$exports = require("../i18n/I18nProvider.cjs"); var $7ac82d1fee77eb8a$exports = require("../utils/useId.cjs"); var $75bd88aab025820b$exports = require("../utils/openLink.cjs"); var $aj3Ek$react = require("react"); function $parcel$export(e, n, v, s) { Object.defineProperty(e, n, {get: v, set: s, enumerable: true, configurable: true}); } $parcel$export(module.exports, "useGridListItem", function () { return $d07dc266c3dc14c0$export$9610e69494fadfd2; }); /* * 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 $d07dc266c3dc14c0$var$EXPANSION_KEYS = { expand: { ltr: 'ArrowRight', rtl: 'ArrowLeft' }, collapse: { ltr: 'ArrowLeft', rtl: 'ArrowRight' } }; function $d07dc266c3dc14c0$export$9610e69494fadfd2(props, state, ref) { // Copied from useGridCell + some modifications to make it not so grid specific let { node: node, isVirtualized: isVirtualized } = props; // let stringFormatter = useLocalizedStringFormatter(intlMessages, '@react-aria/gridlist'); let { direction: direction } = (0, $2522e612fa919664$exports.useLocale)(); let { onAction: onAction, linkBehavior: linkBehavior, keyboardNavigationBehavior: keyboardNavigationBehavior, shouldSelectOnPressUp: shouldSelectOnPressUp } = (0, $daad9bac4699131f$exports.listMap).get(state); let descriptionId = (0, $7ac82d1fee77eb8a$exports.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 = (0, $aj3Ek$react.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 !== null && (keyWhenFocused.current != null && node.key !== keyWhenFocused.current || !(0, $da02ee888921bc9e$exports.isFocusWithin)(ref.current))) (0, $4a053a4bf25e52fb$exports.focusSafely)(ref.current); }; let treeGridRowProps = {}; let hasChildRows = props.hasChildItems; 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 = 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; let index = node.index; if (node.level >= 0 && node?.parentKey != null) { let parent = state.collection.getItem(node.parentKey); if (parent) { // siblings must exist because our original node exists let siblings = $d07dc266c3dc14c0$var$getDirectChildren(parent, state.collection); setSize = [ ...siblings ].filter((row)=>row.type === 'item').length; if (index > 0 && siblings[0].type !== 'item') index -= 1; // subtract one for the parent item's content node } } else setSize = [ ...state.collection ].filter((row)=>row.level === 0 && row.type === 'item').length; treeGridRowProps = { 'aria-expanded': isExpanded, 'aria-level': node.level + 1, 'aria-posinset': index + 1, 'aria-setsize': setSize }; } let { itemProps: itemProps, ...itemStates } = (0, $f38c7e3583533f40$exports.useSelectableItem)({ selectionManager: state.selectionManager, key: node.key, ref: ref, isVirtualized: isVirtualized, shouldSelectOnPressUp: props.shouldSelectOnPressUp || shouldSelectOnPressUp, onAction: onAction || node.props?.onAction ? (0, $2f95486cfdaa743c$exports.chain)(node.props?.onAction, onAction ? ()=>onAction(node.key) : undefined) : undefined, focus: focus, linkBehavior: linkBehavior }); let onKeyDownCapture = (e)=>{ let activeElement = (0, $da02ee888921bc9e$exports.getActiveElement)(); if (!(0, $da02ee888921bc9e$exports.nodeContains)(e.currentTarget, (0, $da02ee888921bc9e$exports.getEventTarget)(e)) || !ref.current || !activeElement) return; let walker = (0, $9fb4ac1cc58342cc$exports.getFocusableTreeWalker)(ref.current); walker.currentNode = activeElement; if ($d07dc266c3dc14c0$var$handleTreeExpansionKeys(e, state, node, hasChildRows, direction, activeElement, ref.current)) return; switch(e.key){ case 'ArrowLeft': if (keyboardNavigationBehavior === 'arrow') { // Find the next focusable element within the row. let focusable = direction === 'rtl' ? walker.nextNode() : walker.previousNode(); if (focusable) { e.preventDefault(); e.stopPropagation(); (0, $4a053a4bf25e52fb$exports.focusSafely)(focusable); (0, $9a1324d6ffd8bbb0$exports.scrollIntoViewport)(focusable, { containingElement: (0, $d865e4ff74ef4a73$exports.getScrollParent)(ref.current) }); } else { // If there is no next focusable child, then return focus back to the row e.preventDefault(); e.stopPropagation(); if (direction === 'rtl') { (0, $4a053a4bf25e52fb$exports.focusSafely)(ref.current); (0, $9a1324d6ffd8bbb0$exports.scrollIntoViewport)(ref.current, { containingElement: (0, $d865e4ff74ef4a73$exports.getScrollParent)(ref.current) }); } else { walker.currentNode = ref.current; let lastElement = $d07dc266c3dc14c0$var$last(walker); // oxlint-disable-next-line max-depth if (lastElement) { (0, $4a053a4bf25e52fb$exports.focusSafely)(lastElement); (0, $9a1324d6ffd8bbb0$exports.scrollIntoViewport)(lastElement, { containingElement: (0, $d865e4ff74ef4a73$exports.getScrollParent)(ref.current) }); } } } } break; case 'ArrowRight': if (keyboardNavigationBehavior === 'arrow') { let focusable = direction === 'rtl' ? walker.previousNode() : walker.nextNode(); if (focusable) { e.preventDefault(); e.stopPropagation(); (0, $4a053a4bf25e52fb$exports.focusSafely)(focusable); (0, $9a1324d6ffd8bbb0$exports.scrollIntoViewport)(focusable, { containingElement: (0, $d865e4ff74ef4a73$exports.getScrollParent)(ref.current) }); } else { e.preventDefault(); e.stopPropagation(); if (direction === 'ltr') { (0, $4a053a4bf25e52fb$exports.focusSafely)(ref.current); (0, $9a1324d6ffd8bbb0$exports.scrollIntoViewport)(ref.current, { containingElement: (0, $d865e4ff74ef4a73$exports.getScrollParent)(ref.current) }); } else { walker.currentNode = ref.current; let lastElement = $d07dc266c3dc14c0$var$last(walker); // oxlint-disable-next-line max-depth if (lastElement) { (0, $4a053a4bf25e52fb$exports.focusSafely)(lastElement); (0, $9a1324d6ffd8bbb0$exports.scrollIntoViewport)(lastElement, { containingElement: (0, $d865e4ff74ef4a73$exports.getScrollParent)(ref.current) }); } } } } 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 && (0, $da02ee888921bc9e$exports.nodeContains)(ref.current, (0, $da02ee888921bc9e$exports.getEventTarget)(e))) { e.stopPropagation(); e.preventDefault(); ref.current.parentElement?.dispatchEvent(new KeyboardEvent(e.nativeEvent.type, e.nativeEvent)); } break; } }; let onFocus = (e)=>{ keyWhenFocused.current = node.key; if ((0, $da02ee888921bc9e$exports.getEventTarget)(e) !== 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 (!(0, $d0df89f3abe2c2ca$exports.isFocusVisible)()) state.selectionManager.setFocusedKey(node.key); return; } }; let onKeyDown = (e)=>{ let activeElement = (0, $da02ee888921bc9e$exports.getActiveElement)(); if (!(0, $da02ee888921bc9e$exports.nodeContains)(e.currentTarget, (0, $da02ee888921bc9e$exports.getEventTarget)(e)) || !ref.current || !activeElement) return; if (keyboardNavigationBehavior === 'tab') { // Stop propagation for all events that originate from the children of the gridlist item since we don't want to trigger // grid level interactions (row navigation/typeselect/etc) // exception made for Tab since that needs to propagate to useSelectableCollection to tab out of the gridlist, might be others? if ((0, $da02ee888921bc9e$exports.getEventTarget)(e) !== ref.current && e.key !== 'Tab') { e.stopPropagation(); return; } if ($d07dc266c3dc14c0$var$handleTreeExpansionKeys(e, state, node, hasChildRows, direction, activeElement, ref.current)) return; } switch(e.key){ 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 = (0, $9fb4ac1cc58342cc$exports.getFocusableTreeWalker)(ref.current, { tabbable: true }); walker.currentNode = activeElement; let next = e.shiftKey ? walker.previousNode() : walker.nextNode(); if (next) e.stopPropagation(); } } }; let syntheticLinkProps = (0, $75bd88aab025820b$exports.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 = (0, $89b39774f3b79dbb$exports.mergeProps)(itemProps, linkProps, { role: 'row', onKeyDownCapture: keyboardNavigationBehavior === 'arrow' ? onKeyDownCapture : undefined, onFocus: onFocus, // 'aria-label': [(node.textValue || undefined), rowAnnouncement].filter(Boolean).join(', '), 'aria-label': node['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['aria-label'] || node.textValue) ? `${(0, $daad9bac4699131f$exports.getRowId)(state, node.key)} ${descriptionId}` : undefined, id: (0, $daad9bac4699131f$exports.getRowId)(state, node.key) }); // we need to guard against space/enter triggering selection/row link via usePress (from itemProps) so check if propagation // is stopped. this also fixes space not working in a textfield in a tree parent row let baseOnKeyDown = rowProps.onKeyDown; rowProps.onKeyDown = (e)=>{ onKeyDown(e); if (!e.isPropagationStopped()) baseOnKeyDown?.(e); }; // guard against presses triggering row selecition when they happen on elements within the row // am currently assuming if it is tabbable it is interactive, but maybe can use a different kind of check let baseOnPointerDown = rowProps.onPointerDown; rowProps.onPointerDown = (e)=>{ let target = (0, $da02ee888921bc9e$exports.getEventTarget)(e); if (target && target !== ref.current && (0, $48f566b6becd50da$exports.isTabbable)(target)) { e.stopPropagation(); return; } baseOnPointerDown?.(e); }; let baseOnMouseDown = rowProps.onMouseDown; rowProps.onMouseDown = (e)=>{ let target = (0, $da02ee888921bc9e$exports.getEventTarget)(e); if (target && target !== ref.current && (0, $48f566b6becd50da$exports.isTabbable)(target)) { e.stopPropagation(); return; } baseOnMouseDown?.(e); }; if (isVirtualized) { let { collection: collection } = state; let nodes = [ ...collection ]; // TODO: refactor ListCollection to store an absolute index of a node's position? rowProps['aria-rowindex'] = nodes.find((node)=>node.type === 'section') ? [ ...collection.getKeys() ].filter((key)=>collection.getItem(key)?.type !== 'section').findIndex((key)=>key === node.key) + 1 : node.index + 1; } let gridCellProps = { role: 'gridcell', 'aria-colindex': 1 }; // TODO: should isExpanded and hasChildRows be a item state that gets returned by the hook? return { rowProps: { ...(0, $89b39774f3b79dbb$exports.mergeProps)(rowProps, treeGridRowProps) }, gridCellProps: gridCellProps, descriptionProps: { id: descriptionId }, ...itemStates }; } function $d07dc266c3dc14c0$var$handleTreeExpansionKeys(e, state, node, hasChildRows, direction, activeElement, rowRef) { if (!('expandedKeys' in state) || activeElement !== rowRef) return false; if (e.key === $d07dc266c3dc14c0$var$EXPANSION_KEYS['expand'][direction] && state.selectionManager.focusedKey === node.key && hasChildRows && !state.expandedKeys.has(node.key)) { state.toggleKey(node.key); e.stopPropagation(); return true; } else if (e.key === $d07dc266c3dc14c0$var$EXPANSION_KEYS['collapse'][direction] && state.selectionManager.focusedKey === node.key) { // If item is collapsible, collapse it; else move to parent if (hasChildRows && state.expandedKeys.has(node.key)) { state.toggleKey(node.key); e.stopPropagation(); return true; } else if (!state.expandedKeys.has(node.key) && node.parentKey && state.collection.getItem(node.parentKey)?.type === 'item') { // Item is a leaf or already collapsed, move focus to parent state.selectionManager.setFocusedKey(node.parentKey); e.stopPropagation(); return true; } } return false; } function $d07dc266c3dc14c0$var$last(walker) { let next = null; let last = null; do { last = walker.lastChild(); if (last) next = last; }while (last); return next; } function $d07dc266c3dc14c0$var$getDirectChildren(parent, collection) { // We can't assume that we can use firstChildKey because if a person builds a tree using hooks, they would not have access to that property (using type Node vs CollectionNode) // Instead, get all children and start at the first node (rather than just using firstChildKey) and only look at its siblings let children = collection.getChildren?.(parent.key); let childArray = children ? Array.from(children) : []; let node = childArray.length > 0 ? childArray[0] : null; let siblings = []; while(node){ siblings.push(node); node = node.nextKey != null ? collection.getItem(node.nextKey) : null; } return siblings; } //# sourceMappingURL=useGridListItem.cjs.map