@react-aria/selection
Advanced tools
Comparing version
@@ -0,2 +1,4 @@ | ||
var $ee0bdf4faa47f2a8$exports = require("./utils.main.js"); | ||
function $parcel$export(e, n, v, s) { | ||
@@ -17,6 +19,8 @@ Object.defineProperty(e, n, {get: v, set: s, enumerable: true, configurable: true}); | ||
* governing permissions and limitations under the License. | ||
*/ class $2ac4508142683dcb$export$8f5ed9ff9f511381 { | ||
*/ | ||
class $2ac4508142683dcb$export$8f5ed9ff9f511381 { | ||
getItemRect(key) { | ||
let container = this.ref.current; | ||
let item = key != null ? container.querySelector(`[data-key="${CSS.escape(key.toString())}"]`) : null; | ||
if (!container) return null; | ||
let item = key != null ? (0, $ee0bdf4faa47f2a8$exports.getItemElement)(this.ref, key) : null; | ||
if (!item) return null; | ||
@@ -34,5 +38,6 @@ let containerRect = container.getBoundingClientRect(); | ||
let container = this.ref.current; | ||
var _container_scrollWidth, _container_scrollHeight; | ||
return { | ||
width: container.scrollWidth, | ||
height: container.scrollHeight | ||
width: (_container_scrollWidth = container === null || container === void 0 ? void 0 : container.scrollWidth) !== null && _container_scrollWidth !== void 0 ? _container_scrollWidth : 0, | ||
height: (_container_scrollHeight = container === null || container === void 0 ? void 0 : container.scrollHeight) !== null && _container_scrollHeight !== void 0 ? _container_scrollHeight : 0 | ||
}; | ||
@@ -42,7 +47,8 @@ } | ||
let container = this.ref.current; | ||
var _container_scrollLeft, _container_scrollTop, _container_offsetWidth, _container_offsetHeight; | ||
return { | ||
x: container.scrollLeft, | ||
y: container.scrollTop, | ||
width: container.offsetWidth, | ||
height: container.offsetHeight | ||
x: (_container_scrollLeft = container === null || container === void 0 ? void 0 : container.scrollLeft) !== null && _container_scrollLeft !== void 0 ? _container_scrollLeft : 0, | ||
y: (_container_scrollTop = container === null || container === void 0 ? void 0 : container.scrollTop) !== null && _container_scrollTop !== void 0 ? _container_scrollTop : 0, | ||
width: (_container_offsetWidth = container === null || container === void 0 ? void 0 : container.offsetWidth) !== null && _container_offsetWidth !== void 0 ? _container_offsetWidth : 0, | ||
height: (_container_offsetHeight = container === null || container === void 0 ? void 0 : container.offsetHeight) !== null && _container_offsetHeight !== void 0 ? _container_offsetHeight : 0 | ||
}; | ||
@@ -49,0 +55,0 @@ } |
@@ -0,1 +1,3 @@ | ||
import {getItemElement as $feb5ffebff200149$export$c3d8340acf92597f} from "./utils.module.js"; | ||
/* | ||
@@ -11,6 +13,8 @@ * Copyright 2024 Adobe. All rights reserved. | ||
* governing permissions and limitations under the License. | ||
*/ class $657e4dc4a6e88df0$export$8f5ed9ff9f511381 { | ||
*/ | ||
class $657e4dc4a6e88df0$export$8f5ed9ff9f511381 { | ||
getItemRect(key) { | ||
let container = this.ref.current; | ||
let item = key != null ? container.querySelector(`[data-key="${CSS.escape(key.toString())}"]`) : null; | ||
if (!container) return null; | ||
let item = key != null ? (0, $feb5ffebff200149$export$c3d8340acf92597f)(this.ref, key) : null; | ||
if (!item) return null; | ||
@@ -28,5 +32,6 @@ let containerRect = container.getBoundingClientRect(); | ||
let container = this.ref.current; | ||
var _container_scrollWidth, _container_scrollHeight; | ||
return { | ||
width: container.scrollWidth, | ||
height: container.scrollHeight | ||
width: (_container_scrollWidth = container === null || container === void 0 ? void 0 : container.scrollWidth) !== null && _container_scrollWidth !== void 0 ? _container_scrollWidth : 0, | ||
height: (_container_scrollHeight = container === null || container === void 0 ? void 0 : container.scrollHeight) !== null && _container_scrollHeight !== void 0 ? _container_scrollHeight : 0 | ||
}; | ||
@@ -36,7 +41,8 @@ } | ||
let container = this.ref.current; | ||
var _container_scrollLeft, _container_scrollTop, _container_offsetWidth, _container_offsetHeight; | ||
return { | ||
x: container.scrollLeft, | ||
y: container.scrollTop, | ||
width: container.offsetWidth, | ||
height: container.offsetHeight | ||
x: (_container_scrollLeft = container === null || container === void 0 ? void 0 : container.scrollLeft) !== null && _container_scrollLeft !== void 0 ? _container_scrollLeft : 0, | ||
y: (_container_scrollTop = container === null || container === void 0 ? void 0 : container.scrollTop) !== null && _container_scrollTop !== void 0 ? _container_scrollTop : 0, | ||
width: (_container_offsetWidth = container === null || container === void 0 ? void 0 : container.offsetWidth) !== null && _container_offsetWidth !== void 0 ? _container_offsetWidth : 0, | ||
height: (_container_offsetHeight = container === null || container === void 0 ? void 0 : container.offsetHeight) !== null && _container_offsetHeight !== void 0 ? _container_offsetHeight : 0 | ||
}; | ||
@@ -43,0 +49,0 @@ } |
@@ -28,6 +28,7 @@ var $2ac4508142683dcb$exports = require("./DOMLayoutDelegate.main.js"); | ||
findNextNonDisabled(key, getNext) { | ||
while(key != null){ | ||
let item = this.collection.getItem(key); | ||
if ((item === null || item === void 0 ? void 0 : item.type) === 'item' && !this.isDisabled(item)) return key; | ||
key = getNext(key); | ||
let nextKey = key; | ||
while(nextKey != null){ | ||
let item = this.collection.getItem(nextKey); | ||
if ((item === null || item === void 0 ? void 0 : item.type) === 'item' && !this.isDisabled(item)) return nextKey; | ||
nextKey = getNext(nextKey); | ||
} | ||
@@ -37,19 +38,23 @@ return null; | ||
getNextKey(key) { | ||
key = this.collection.getKeyAfter(key); | ||
return this.findNextNonDisabled(key, (key)=>this.collection.getKeyAfter(key)); | ||
let nextKey = key; | ||
nextKey = this.collection.getKeyAfter(nextKey); | ||
return this.findNextNonDisabled(nextKey, (key)=>this.collection.getKeyAfter(key)); | ||
} | ||
getPreviousKey(key) { | ||
key = this.collection.getKeyBefore(key); | ||
return this.findNextNonDisabled(key, (key)=>this.collection.getKeyBefore(key)); | ||
let nextKey = key; | ||
nextKey = this.collection.getKeyBefore(nextKey); | ||
return this.findNextNonDisabled(nextKey, (key)=>this.collection.getKeyBefore(key)); | ||
} | ||
findKey(key, nextKey, shouldSkip) { | ||
let itemRect = this.layoutDelegate.getItemRect(key); | ||
if (!itemRect) return null; | ||
let tempKey = key; | ||
let itemRect = this.layoutDelegate.getItemRect(tempKey); | ||
if (!itemRect || tempKey == null) return null; | ||
// Find the item above or below in the same column. | ||
let prevRect = itemRect; | ||
do { | ||
key = nextKey(key); | ||
itemRect = this.layoutDelegate.getItemRect(key); | ||
}while (itemRect && shouldSkip(prevRect, itemRect)); | ||
return key; | ||
tempKey = nextKey(tempKey); | ||
if (tempKey == null) break; | ||
itemRect = this.layoutDelegate.getItemRect(tempKey); | ||
}while (itemRect && shouldSkip(prevRect, itemRect) && tempKey != null); | ||
return tempKey; | ||
} | ||
@@ -111,17 +116,18 @@ isSameRow(prevRect, itemRect) { | ||
if (!itemRect) return null; | ||
if (!(0, $doKEG$reactariautils.isScrollable)(menu)) return this.getFirstKey(); | ||
if (menu && !(0, $doKEG$reactariautils.isScrollable)(menu)) return this.getFirstKey(); | ||
let nextKey = key; | ||
if (this.orientation === 'horizontal') { | ||
let pageX = Math.max(0, itemRect.x + itemRect.width - this.layoutDelegate.getVisibleRect().width); | ||
while(itemRect && itemRect.x > pageX){ | ||
key = this.getKeyAbove(key); | ||
itemRect = key == null ? null : this.layoutDelegate.getItemRect(key); | ||
while(itemRect && itemRect.x > pageX && nextKey != null){ | ||
nextKey = this.getKeyAbove(nextKey); | ||
itemRect = nextKey == null ? null : this.layoutDelegate.getItemRect(nextKey); | ||
} | ||
} else { | ||
let pageY = Math.max(0, itemRect.y + itemRect.height - this.layoutDelegate.getVisibleRect().height); | ||
while(itemRect && itemRect.y > pageY){ | ||
key = this.getKeyAbove(key); | ||
itemRect = key == null ? null : this.layoutDelegate.getItemRect(key); | ||
while(itemRect && itemRect.y > pageY && nextKey != null){ | ||
nextKey = this.getKeyAbove(nextKey); | ||
itemRect = nextKey == null ? null : this.layoutDelegate.getItemRect(nextKey); | ||
} | ||
} | ||
return key !== null && key !== void 0 ? key : this.getFirstKey(); | ||
return nextKey !== null && nextKey !== void 0 ? nextKey : this.getFirstKey(); | ||
} | ||
@@ -132,17 +138,18 @@ getKeyPageBelow(key) { | ||
if (!itemRect) return null; | ||
if (!(0, $doKEG$reactariautils.isScrollable)(menu)) return this.getLastKey(); | ||
if (menu && !(0, $doKEG$reactariautils.isScrollable)(menu)) return this.getLastKey(); | ||
let nextKey = key; | ||
if (this.orientation === 'horizontal') { | ||
let pageX = Math.min(this.layoutDelegate.getContentSize().width, itemRect.y - itemRect.width + this.layoutDelegate.getVisibleRect().width); | ||
while(itemRect && itemRect.x < pageX){ | ||
key = this.getKeyBelow(key); | ||
itemRect = key == null ? null : this.layoutDelegate.getItemRect(key); | ||
while(itemRect && itemRect.x < pageX && nextKey != null){ | ||
nextKey = this.getKeyBelow(nextKey); | ||
itemRect = nextKey == null ? null : this.layoutDelegate.getItemRect(nextKey); | ||
} | ||
} else { | ||
let pageY = Math.min(this.layoutDelegate.getContentSize().height, itemRect.y - itemRect.height + this.layoutDelegate.getVisibleRect().height); | ||
while(itemRect && itemRect.y < pageY){ | ||
key = this.getKeyBelow(key); | ||
itemRect = key == null ? null : this.layoutDelegate.getItemRect(key); | ||
while(itemRect && itemRect.y < pageY && nextKey != null){ | ||
nextKey = this.getKeyBelow(nextKey); | ||
itemRect = nextKey == null ? null : this.layoutDelegate.getItemRect(nextKey); | ||
} | ||
} | ||
return key !== null && key !== void 0 ? key : this.getLastKey(); | ||
return nextKey !== null && nextKey !== void 0 ? nextKey : this.getLastKey(); | ||
} | ||
@@ -155,2 +162,3 @@ getKeyForSearch(search, fromKey) { | ||
let item = collection.getItem(key); | ||
if (!item) return null; | ||
let substring = item.textValue.slice(0, search.length); | ||
@@ -157,0 +165,0 @@ if (item.textValue && this.collator.compare(substring, search) === 0) return key; |
@@ -22,6 +22,7 @@ import {DOMLayoutDelegate as $657e4dc4a6e88df0$export$8f5ed9ff9f511381} from "./DOMLayoutDelegate.module.js"; | ||
findNextNonDisabled(key, getNext) { | ||
while(key != null){ | ||
let item = this.collection.getItem(key); | ||
if ((item === null || item === void 0 ? void 0 : item.type) === 'item' && !this.isDisabled(item)) return key; | ||
key = getNext(key); | ||
let nextKey = key; | ||
while(nextKey != null){ | ||
let item = this.collection.getItem(nextKey); | ||
if ((item === null || item === void 0 ? void 0 : item.type) === 'item' && !this.isDisabled(item)) return nextKey; | ||
nextKey = getNext(nextKey); | ||
} | ||
@@ -31,19 +32,23 @@ return null; | ||
getNextKey(key) { | ||
key = this.collection.getKeyAfter(key); | ||
return this.findNextNonDisabled(key, (key)=>this.collection.getKeyAfter(key)); | ||
let nextKey = key; | ||
nextKey = this.collection.getKeyAfter(nextKey); | ||
return this.findNextNonDisabled(nextKey, (key)=>this.collection.getKeyAfter(key)); | ||
} | ||
getPreviousKey(key) { | ||
key = this.collection.getKeyBefore(key); | ||
return this.findNextNonDisabled(key, (key)=>this.collection.getKeyBefore(key)); | ||
let nextKey = key; | ||
nextKey = this.collection.getKeyBefore(nextKey); | ||
return this.findNextNonDisabled(nextKey, (key)=>this.collection.getKeyBefore(key)); | ||
} | ||
findKey(key, nextKey, shouldSkip) { | ||
let itemRect = this.layoutDelegate.getItemRect(key); | ||
if (!itemRect) return null; | ||
let tempKey = key; | ||
let itemRect = this.layoutDelegate.getItemRect(tempKey); | ||
if (!itemRect || tempKey == null) return null; | ||
// Find the item above or below in the same column. | ||
let prevRect = itemRect; | ||
do { | ||
key = nextKey(key); | ||
itemRect = this.layoutDelegate.getItemRect(key); | ||
}while (itemRect && shouldSkip(prevRect, itemRect)); | ||
return key; | ||
tempKey = nextKey(tempKey); | ||
if (tempKey == null) break; | ||
itemRect = this.layoutDelegate.getItemRect(tempKey); | ||
}while (itemRect && shouldSkip(prevRect, itemRect) && tempKey != null); | ||
return tempKey; | ||
} | ||
@@ -105,17 +110,18 @@ isSameRow(prevRect, itemRect) { | ||
if (!itemRect) return null; | ||
if (!(0, $eak97$isScrollable)(menu)) return this.getFirstKey(); | ||
if (menu && !(0, $eak97$isScrollable)(menu)) return this.getFirstKey(); | ||
let nextKey = key; | ||
if (this.orientation === 'horizontal') { | ||
let pageX = Math.max(0, itemRect.x + itemRect.width - this.layoutDelegate.getVisibleRect().width); | ||
while(itemRect && itemRect.x > pageX){ | ||
key = this.getKeyAbove(key); | ||
itemRect = key == null ? null : this.layoutDelegate.getItemRect(key); | ||
while(itemRect && itemRect.x > pageX && nextKey != null){ | ||
nextKey = this.getKeyAbove(nextKey); | ||
itemRect = nextKey == null ? null : this.layoutDelegate.getItemRect(nextKey); | ||
} | ||
} else { | ||
let pageY = Math.max(0, itemRect.y + itemRect.height - this.layoutDelegate.getVisibleRect().height); | ||
while(itemRect && itemRect.y > pageY){ | ||
key = this.getKeyAbove(key); | ||
itemRect = key == null ? null : this.layoutDelegate.getItemRect(key); | ||
while(itemRect && itemRect.y > pageY && nextKey != null){ | ||
nextKey = this.getKeyAbove(nextKey); | ||
itemRect = nextKey == null ? null : this.layoutDelegate.getItemRect(nextKey); | ||
} | ||
} | ||
return key !== null && key !== void 0 ? key : this.getFirstKey(); | ||
return nextKey !== null && nextKey !== void 0 ? nextKey : this.getFirstKey(); | ||
} | ||
@@ -126,17 +132,18 @@ getKeyPageBelow(key) { | ||
if (!itemRect) return null; | ||
if (!(0, $eak97$isScrollable)(menu)) return this.getLastKey(); | ||
if (menu && !(0, $eak97$isScrollable)(menu)) return this.getLastKey(); | ||
let nextKey = key; | ||
if (this.orientation === 'horizontal') { | ||
let pageX = Math.min(this.layoutDelegate.getContentSize().width, itemRect.y - itemRect.width + this.layoutDelegate.getVisibleRect().width); | ||
while(itemRect && itemRect.x < pageX){ | ||
key = this.getKeyBelow(key); | ||
itemRect = key == null ? null : this.layoutDelegate.getItemRect(key); | ||
while(itemRect && itemRect.x < pageX && nextKey != null){ | ||
nextKey = this.getKeyBelow(nextKey); | ||
itemRect = nextKey == null ? null : this.layoutDelegate.getItemRect(nextKey); | ||
} | ||
} else { | ||
let pageY = Math.min(this.layoutDelegate.getContentSize().height, itemRect.y - itemRect.height + this.layoutDelegate.getVisibleRect().height); | ||
while(itemRect && itemRect.y < pageY){ | ||
key = this.getKeyBelow(key); | ||
itemRect = key == null ? null : this.layoutDelegate.getItemRect(key); | ||
while(itemRect && itemRect.y < pageY && nextKey != null){ | ||
nextKey = this.getKeyBelow(nextKey); | ||
itemRect = nextKey == null ? null : this.layoutDelegate.getItemRect(nextKey); | ||
} | ||
} | ||
return key !== null && key !== void 0 ? key : this.getLastKey(); | ||
return nextKey !== null && nextKey !== void 0 ? nextKey : this.getLastKey(); | ||
} | ||
@@ -149,2 +156,3 @@ getKeyForSearch(search, fromKey) { | ||
let item = collection.getItem(key); | ||
if (!item) return null; | ||
let substring = item.textValue.slice(0, search.length); | ||
@@ -151,0 +159,0 @@ if (item.textValue && this.collator.compare(substring, search) === 0) return key; |
@@ -1,2 +0,2 @@ | ||
import { DOMAttributes, Key, KeyboardDelegate, FocusStrategy, RefObject, FocusableElement, LayoutDelegate, Rect, Size, Collection, Direction, DisabledBehavior, Node, Orientation } from "@react-types/shared"; | ||
import { DOMAttributes, Key, KeyboardDelegate, FocusStrategy, RefObject, DOMProps, FocusableElement, LayoutDelegate, Rect, Size, Collection, Direction, DisabledBehavior, Node, Orientation } from "@react-types/shared"; | ||
import { MultipleSelectionManager } from "@react-stately/selection"; | ||
@@ -61,2 +61,7 @@ export interface AriaTypeSelectOptions { | ||
/** | ||
* Whether pressing the Escape should clear selection in the collection or not. | ||
* @default 'clearSelection' | ||
*/ | ||
escapeKeyBehavior?: 'clearSelection' | 'none'; | ||
/** | ||
* Whether selection should occur automatically on focus. | ||
@@ -105,3 +110,3 @@ * @default false | ||
export function useSelectableCollection(options: AriaSelectableCollectionOptions): SelectableCollectionAria; | ||
export interface SelectableItemOptions { | ||
export interface SelectableItemOptions extends DOMProps { | ||
/** | ||
@@ -193,3 +198,3 @@ * An interface for reading and updating multiple selection state. | ||
export class DOMLayoutDelegate implements LayoutDelegate { | ||
constructor(ref: RefObject<HTMLElement>); | ||
constructor(ref: RefObject<HTMLElement | null>); | ||
getItemRect(key: Key): Rect | null; | ||
@@ -213,13 +218,13 @@ getContentSize(): Size; | ||
constructor(options: ListKeyboardDelegateOptions<T>); | ||
getNextKey(key: Key): Key; | ||
getPreviousKey(key: Key): Key; | ||
getKeyBelow(key: Key): Key; | ||
getKeyAbove(key: Key): Key; | ||
getKeyRightOf(key: Key): Key; | ||
getKeyLeftOf(key: Key): Key; | ||
getFirstKey(): Key; | ||
getLastKey(): Key; | ||
getKeyPageAbove(key: Key): Key; | ||
getKeyPageBelow(key: Key): Key; | ||
getKeyForSearch(search: string, fromKey?: Key): Key; | ||
getNextKey(key: Key): Key | null; | ||
getPreviousKey(key: Key): Key | null; | ||
getKeyBelow(key: Key): Key | null; | ||
getKeyAbove(key: Key): Key | null; | ||
getKeyRightOf?(key: Key): Key | null; | ||
getKeyLeftOf?(key: Key): Key | null; | ||
getFirstKey(): Key | null; | ||
getLastKey(): Key | null; | ||
getKeyPageAbove(key: Key): Key | null; | ||
getKeyPageBelow(key: Key): Key | null; | ||
getKeyForSearch(search: string, fromKey?: Key): Key | null; | ||
} | ||
@@ -226,0 +231,0 @@ export interface AriaSelectableListOptions extends Omit<AriaSelectableCollectionOptions, 'keyboardDelegate'> { |
var $ee0bdf4faa47f2a8$exports = require("./utils.main.js"); | ||
var $a1189052f36475e8$exports = require("./useTypeSelect.main.js"); | ||
var $bT8Bh$reactariautils = require("@react-aria/utils"); | ||
var $bT8Bh$reactdom = require("react-dom"); | ||
var $bT8Bh$react = require("react"); | ||
var $bT8Bh$reactariainteractions = require("@react-aria/interactions"); | ||
var $bT8Bh$reactariafocus = require("@react-aria/focus"); | ||
var $bT8Bh$reactariautils = require("@react-aria/utils"); | ||
var $bT8Bh$reactariainteractions = require("@react-aria/interactions"); | ||
var $bT8Bh$reactariai18n = require("@react-aria/i18n"); | ||
@@ -35,3 +35,3 @@ | ||
function $b6837c2f80a3c32f$export$d6daf82dcd84e87c(options) { | ||
let { selectionManager: manager, keyboardDelegate: delegate, ref: ref, autoFocus: autoFocus = false, shouldFocusWrap: shouldFocusWrap = false, disallowEmptySelection: disallowEmptySelection = false, disallowSelectAll: disallowSelectAll = false, selectOnFocus: selectOnFocus = manager.selectionBehavior === 'replace', disallowTypeAhead: disallowTypeAhead = false, shouldUseVirtualFocus: shouldUseVirtualFocus, allowsTabNavigation: allowsTabNavigation = false, isVirtualized: isVirtualized, scrollRef: // If no scrollRef is provided, assume the collection ref is the scrollable region | ||
let { selectionManager: manager, keyboardDelegate: delegate, ref: ref, autoFocus: autoFocus = false, shouldFocusWrap: shouldFocusWrap = false, disallowEmptySelection: disallowEmptySelection = false, disallowSelectAll: disallowSelectAll = false, escapeKeyBehavior: escapeKeyBehavior = 'clearSelection', selectOnFocus: selectOnFocus = manager.selectionBehavior === 'replace', disallowTypeAhead: disallowTypeAhead = false, shouldUseVirtualFocus: shouldUseVirtualFocus, allowsTabNavigation: allowsTabNavigation = false, isVirtualized: isVirtualized, scrollRef: // If no scrollRef is provided, assume the collection ref is the scrollable region | ||
scrollRef = ref, linkBehavior: linkBehavior = 'action' } = options; | ||
@@ -41,2 +41,3 @@ let { direction: direction } = (0, $bT8Bh$reactariai18n.useLocale)(); | ||
let onKeyDown = (e)=>{ | ||
var _ref_current; | ||
// Prevent option + tab from doing anything since it doesn't move focus to the cells, only buttons/checkboxes | ||
@@ -46,3 +47,3 @@ if (e.altKey && e.key === 'Tab') e.preventDefault(); | ||
// for elements outside the collection (e.g. menus). | ||
if (!ref.current.contains(e.target)) return; | ||
if (!((_ref_current = ref.current) === null || _ref_current === void 0 ? void 0 : _ref_current.contains(e.target))) return; | ||
const navigateToKey = (key, childFocus)=>{ | ||
@@ -55,5 +56,5 @@ if (key != null) { | ||
}); | ||
let item = scrollRef.current.querySelector(`[data-key="${CSS.escape(key.toString())}"]`); | ||
let item = (0, $ee0bdf4faa47f2a8$exports.getItemElement)(ref, key); | ||
let itemProps = manager.getItemProps(key); | ||
router.open(item, e, itemProps.href, itemProps.routerOptions); | ||
if (item) router.open(item, e, itemProps.href, itemProps.routerOptions); | ||
return; | ||
@@ -93,3 +94,3 @@ } | ||
var _delegate_getKeyLeftOf, _delegate_getFirstKey2, _delegate_getLastKey2; | ||
let nextKey = (_delegate_getKeyLeftOf = delegate.getKeyLeftOf) === null || _delegate_getKeyLeftOf === void 0 ? void 0 : _delegate_getKeyLeftOf.call(delegate, manager.focusedKey); | ||
let nextKey = manager.focusedKey != null ? (_delegate_getKeyLeftOf = delegate.getKeyLeftOf) === null || _delegate_getKeyLeftOf === void 0 ? void 0 : _delegate_getKeyLeftOf.call(delegate, manager.focusedKey) : null; | ||
if (nextKey == null && shouldFocusWrap) nextKey = direction === 'rtl' ? (_delegate_getFirstKey2 = delegate.getFirstKey) === null || _delegate_getFirstKey2 === void 0 ? void 0 : _delegate_getFirstKey2.call(delegate, manager.focusedKey) : (_delegate_getLastKey2 = delegate.getLastKey) === null || _delegate_getLastKey2 === void 0 ? void 0 : _delegate_getLastKey2.call(delegate, manager.focusedKey); | ||
@@ -105,3 +106,3 @@ if (nextKey != null) { | ||
var _delegate_getKeyRightOf, _delegate_getLastKey3, _delegate_getFirstKey3; | ||
let nextKey = (_delegate_getKeyRightOf = delegate.getKeyRightOf) === null || _delegate_getKeyRightOf === void 0 ? void 0 : _delegate_getKeyRightOf.call(delegate, manager.focusedKey); | ||
let nextKey = manager.focusedKey != null ? (_delegate_getKeyRightOf = delegate.getKeyRightOf) === null || _delegate_getKeyRightOf === void 0 ? void 0 : _delegate_getKeyRightOf.call(delegate, manager.focusedKey) : null; | ||
if (nextKey == null && shouldFocusWrap) nextKey = direction === 'rtl' ? (_delegate_getLastKey3 = delegate.getLastKey) === null || _delegate_getLastKey3 === void 0 ? void 0 : _delegate_getLastKey3.call(delegate, manager.focusedKey) : (_delegate_getFirstKey3 = delegate.getFirstKey) === null || _delegate_getFirstKey3 === void 0 ? void 0 : _delegate_getFirstKey3.call(delegate, manager.focusedKey); | ||
@@ -116,7 +117,10 @@ if (nextKey != null) { | ||
if (delegate.getFirstKey) { | ||
if (manager.focusedKey === null && e.shiftKey) return; | ||
e.preventDefault(); | ||
let firstKey = delegate.getFirstKey(manager.focusedKey, (0, $ee0bdf4faa47f2a8$exports.isCtrlKeyPressed)(e)); | ||
let firstKey = delegate.getFirstKey(manager.focusedKey, (0, $bT8Bh$reactariautils.isCtrlKeyPressed)(e)); | ||
manager.setFocusedKey(firstKey); | ||
if ((0, $ee0bdf4faa47f2a8$exports.isCtrlKeyPressed)(e) && e.shiftKey && manager.selectionMode === 'multiple') manager.extendSelection(firstKey); | ||
else if (selectOnFocus) manager.replaceSelection(firstKey); | ||
if (firstKey != null) { | ||
if ((0, $bT8Bh$reactariautils.isCtrlKeyPressed)(e) && e.shiftKey && manager.selectionMode === 'multiple') manager.extendSelection(firstKey); | ||
else if (selectOnFocus) manager.replaceSelection(firstKey); | ||
} | ||
} | ||
@@ -126,11 +130,14 @@ break; | ||
if (delegate.getLastKey) { | ||
if (manager.focusedKey === null && e.shiftKey) return; | ||
e.preventDefault(); | ||
let lastKey = delegate.getLastKey(manager.focusedKey, (0, $ee0bdf4faa47f2a8$exports.isCtrlKeyPressed)(e)); | ||
let lastKey = delegate.getLastKey(manager.focusedKey, (0, $bT8Bh$reactariautils.isCtrlKeyPressed)(e)); | ||
manager.setFocusedKey(lastKey); | ||
if ((0, $ee0bdf4faa47f2a8$exports.isCtrlKeyPressed)(e) && e.shiftKey && manager.selectionMode === 'multiple') manager.extendSelection(lastKey); | ||
else if (selectOnFocus) manager.replaceSelection(lastKey); | ||
if (lastKey != null) { | ||
if ((0, $bT8Bh$reactariautils.isCtrlKeyPressed)(e) && e.shiftKey && manager.selectionMode === 'multiple') manager.extendSelection(lastKey); | ||
else if (selectOnFocus) manager.replaceSelection(lastKey); | ||
} | ||
} | ||
break; | ||
case 'PageDown': | ||
if (delegate.getKeyPageBelow) { | ||
if (delegate.getKeyPageBelow && manager.focusedKey != null) { | ||
let nextKey = delegate.getKeyPageBelow(manager.focusedKey); | ||
@@ -144,3 +151,3 @@ if (nextKey != null) { | ||
case 'PageUp': | ||
if (delegate.getKeyPageAbove) { | ||
if (delegate.getKeyPageAbove && manager.focusedKey != null) { | ||
let nextKey = delegate.getKeyPageAbove(manager.focusedKey); | ||
@@ -154,3 +161,3 @@ if (nextKey != null) { | ||
case 'a': | ||
if ((0, $ee0bdf4faa47f2a8$exports.isCtrlKeyPressed)(e) && manager.selectionMode === 'multiple' && disallowSelectAll !== true) { | ||
if ((0, $bT8Bh$reactariautils.isCtrlKeyPressed)(e) && manager.selectionMode === 'multiple' && disallowSelectAll !== true) { | ||
e.preventDefault(); | ||
@@ -161,3 +168,3 @@ manager.selectAll(); | ||
case 'Escape': | ||
if (!disallowEmptySelection && manager.selectedKeys.size !== 0) { | ||
if (escapeKeyBehavior === 'clearSelection' && !disallowEmptySelection && manager.selectedKeys.size !== 0) { | ||
e.stopPropagation(); | ||
@@ -181,3 +188,3 @@ e.preventDefault(); | ||
}); | ||
let next; | ||
let next = undefined; | ||
let last; | ||
@@ -200,6 +207,8 @@ do { | ||
}); | ||
(0, $bT8Bh$reactariautils.useEvent)(scrollRef, 'scroll', isVirtualized ? null : ()=>{ | ||
(0, $bT8Bh$reactariautils.useEvent)(scrollRef, 'scroll', isVirtualized ? undefined : ()=>{ | ||
var _scrollRef_current, _scrollRef_current1; | ||
var _scrollRef_current_scrollTop, _scrollRef_current_scrollLeft; | ||
scrollPos.current = { | ||
top: scrollRef.current.scrollTop, | ||
left: scrollRef.current.scrollLeft | ||
top: (_scrollRef_current_scrollTop = (_scrollRef_current = scrollRef.current) === null || _scrollRef_current === void 0 ? void 0 : _scrollRef_current.scrollTop) !== null && _scrollRef_current_scrollTop !== void 0 ? _scrollRef_current_scrollTop : 0, | ||
left: (_scrollRef_current_scrollLeft = (_scrollRef_current1 = scrollRef.current) === null || _scrollRef_current1 === void 0 ? void 0 : _scrollRef_current1.scrollLeft) !== null && _scrollRef_current_scrollLeft !== void 0 ? _scrollRef_current_scrollLeft : 0 | ||
}; | ||
@@ -217,6 +226,7 @@ }); | ||
if (manager.focusedKey == null) { | ||
let navigateToFirstKey = (key)=>{ | ||
var _delegate_getLastKey, _delegate_getFirstKey; | ||
let navigateToKey = (key)=>{ | ||
if (key != null) { | ||
manager.setFocusedKey(key); | ||
if (selectOnFocus) manager.replaceSelection(key); | ||
if (selectOnFocus && !manager.isSelected(key)) manager.replaceSelection(key); | ||
} | ||
@@ -229,5 +239,5 @@ }; | ||
var _manager_lastSelectedKey, _manager_firstSelectedKey; | ||
if (relatedTarget && e.currentTarget.compareDocumentPosition(relatedTarget) & Node.DOCUMENT_POSITION_FOLLOWING) navigateToFirstKey((_manager_lastSelectedKey = manager.lastSelectedKey) !== null && _manager_lastSelectedKey !== void 0 ? _manager_lastSelectedKey : delegate.getLastKey()); | ||
else navigateToFirstKey((_manager_firstSelectedKey = manager.firstSelectedKey) !== null && _manager_firstSelectedKey !== void 0 ? _manager_firstSelectedKey : delegate.getFirstKey()); | ||
} else if (!isVirtualized) { | ||
if (relatedTarget && e.currentTarget.compareDocumentPosition(relatedTarget) & Node.DOCUMENT_POSITION_FOLLOWING) navigateToKey((_manager_lastSelectedKey = manager.lastSelectedKey) !== null && _manager_lastSelectedKey !== void 0 ? _manager_lastSelectedKey : (_delegate_getLastKey = delegate.getLastKey) === null || _delegate_getLastKey === void 0 ? void 0 : _delegate_getLastKey.call(delegate)); | ||
else navigateToKey((_manager_firstSelectedKey = manager.firstSelectedKey) !== null && _manager_firstSelectedKey !== void 0 ? _manager_firstSelectedKey : (_delegate_getFirstKey = delegate.getFirstKey) === null || _delegate_getFirstKey === void 0 ? void 0 : _delegate_getFirstKey.call(delegate)); | ||
} else if (!isVirtualized && scrollRef.current) { | ||
// Restore the scroll position to what it was before. | ||
@@ -237,8 +247,8 @@ scrollRef.current.scrollTop = scrollPos.current.top; | ||
} | ||
if (manager.focusedKey != null) { | ||
if (manager.focusedKey != null && scrollRef.current) { | ||
// Refocus and scroll the focused item into view if it exists within the scrollable region. | ||
let element = scrollRef.current.querySelector(`[data-key="${CSS.escape(manager.focusedKey.toString())}"]`); | ||
if (element) { | ||
let element = (0, $ee0bdf4faa47f2a8$exports.getItemElement)(ref, manager.focusedKey); | ||
if (element instanceof HTMLElement) { | ||
// This prevents a flash of focus on the first/last element in the collection, or the collection itself. | ||
if (!element.contains(document.activeElement)) (0, $bT8Bh$reactariautils.focusWithoutScrolling)(element); | ||
if (!element.contains(document.activeElement) && !shouldUseVirtualFocus) (0, $bT8Bh$reactariautils.focusWithoutScrolling)(element); | ||
let modality = (0, $bT8Bh$reactariainteractions.getInteractionModality)(); | ||
@@ -255,9 +265,69 @@ if (modality === 'keyboard') (0, $bT8Bh$reactariautils.scrollIntoViewport)(element, { | ||
}; | ||
// Ref to track whether the first item in the collection should be automatically focused. Specifically used for autocomplete when user types | ||
// to focus the first key AFTER the collection updates. | ||
// TODO: potentially expand the usage of this | ||
let shouldVirtualFocusFirst = (0, $bT8Bh$react.useRef)(false); | ||
// Add event listeners for custom virtual events. These handle updating the focused key in response to various keyboard events | ||
// at the autocomplete level | ||
// TODO: fix type later | ||
(0, $bT8Bh$reactariautils.useEvent)(ref, (0, $bT8Bh$reactariautils.FOCUS_EVENT), !shouldUseVirtualFocus ? undefined : (e)=>{ | ||
let { detail: detail } = e; | ||
e.stopPropagation(); | ||
manager.setFocused(true); | ||
// If the user is typing forwards, autofocus the first option in the list. | ||
if ((detail === null || detail === void 0 ? void 0 : detail.focusStrategy) === 'first') shouldVirtualFocusFirst.current = true; | ||
}); | ||
let updateActiveDescendant = (0, $bT8Bh$reactariautils.useEffectEvent)(()=>{ | ||
var _delegate_getFirstKey; | ||
var _delegate_getFirstKey1; | ||
let keyToFocus = (_delegate_getFirstKey1 = (_delegate_getFirstKey = delegate.getFirstKey) === null || _delegate_getFirstKey === void 0 ? void 0 : _delegate_getFirstKey.call(delegate)) !== null && _delegate_getFirstKey1 !== void 0 ? _delegate_getFirstKey1 : null; | ||
// If no focusable items exist in the list, make sure to clear any activedescendant that may still exist | ||
if (keyToFocus == null) { | ||
(0, $bT8Bh$reactariafocus.moveVirtualFocus)(ref.current); | ||
// If there wasn't a focusable key but the collection had items, then that means we aren't in an intermediate load state and all keys are disabled. | ||
// Reset shouldVirtualFocusFirst so that we don't erronously autofocus an item when the collection is filtered again. | ||
if (manager.collection.size > 0) shouldVirtualFocusFirst.current = false; | ||
} else { | ||
manager.setFocusedKey(keyToFocus); | ||
// Only set shouldVirtualFocusFirst to false if we've successfully set the first key as the focused key | ||
// If there wasn't a key to focus, we might be in a temporary loading state so we'll want to still focus the first key | ||
// after the collection updates after load | ||
shouldVirtualFocusFirst.current = false; | ||
} | ||
}); | ||
(0, $bT8Bh$reactariautils.useUpdateLayoutEffect)(()=>{ | ||
if (shouldVirtualFocusFirst.current) updateActiveDescendant(); | ||
}, [ | ||
manager.collection, | ||
updateActiveDescendant | ||
]); | ||
let resetFocusFirstFlag = (0, $bT8Bh$reactariautils.useEffectEvent)(()=>{ | ||
// If user causes the focused key to change in any other way, clear shouldVirtualFocusFirst so we don't | ||
// accidentally move focus from under them. Skip this if the collection was empty because we might be in a load | ||
// state and will still want to focus the first item after load | ||
if (manager.collection.size > 0) shouldVirtualFocusFirst.current = false; | ||
}); | ||
(0, $bT8Bh$reactariautils.useUpdateLayoutEffect)(()=>{ | ||
resetFocusFirstFlag(); | ||
}, [ | ||
manager.focusedKey, | ||
resetFocusFirstFlag | ||
]); | ||
(0, $bT8Bh$reactariautils.useEvent)(ref, (0, $bT8Bh$reactariautils.CLEAR_FOCUS_EVENT), !shouldUseVirtualFocus ? undefined : (e)=>{ | ||
var _e_detail; | ||
e.stopPropagation(); | ||
manager.setFocused(false); | ||
if ((_e_detail = e.detail) === null || _e_detail === void 0 ? void 0 : _e_detail.clearFocusKey) manager.setFocusedKey(null); | ||
}); | ||
const autoFocusRef = (0, $bT8Bh$react.useRef)(autoFocus); | ||
const didAutoFocusRef = (0, $bT8Bh$react.useRef)(false); | ||
(0, $bT8Bh$react.useEffect)(()=>{ | ||
if (autoFocusRef.current) { | ||
var _delegate_getFirstKey, _delegate_getLastKey; | ||
let focusedKey = null; | ||
var _delegate_getFirstKey1; | ||
// Check focus strategy to determine which item to focus | ||
if (autoFocus === 'first') focusedKey = delegate.getFirstKey(); | ||
if (autoFocus === 'last') focusedKey = delegate.getLastKey(); | ||
if (autoFocus === 'first') focusedKey = (_delegate_getFirstKey1 = (_delegate_getFirstKey = delegate.getFirstKey) === null || _delegate_getFirstKey === void 0 ? void 0 : _delegate_getFirstKey.call(delegate)) !== null && _delegate_getFirstKey1 !== void 0 ? _delegate_getFirstKey1 : null; | ||
var _delegate_getLastKey1; | ||
if (autoFocus === 'last') focusedKey = (_delegate_getLastKey1 = (_delegate_getLastKey = delegate.getLastKey) === null || _delegate_getLastKey === void 0 ? void 0 : _delegate_getLastKey.call(delegate)) !== null && _delegate_getLastKey1 !== void 0 ? _delegate_getLastKey1 : null; | ||
// If there are any selected keys, make the first one the new focus target | ||
@@ -274,17 +344,25 @@ let selectedKeys = manager.selectedKeys; | ||
// If no default focus key is selected, focus the collection itself. | ||
if (focusedKey == null && !shouldUseVirtualFocus) (0, $bT8Bh$reactariafocus.focusSafely)(ref.current); | ||
if (focusedKey == null && !shouldUseVirtualFocus && ref.current) (0, $bT8Bh$reactariainteractions.focusSafely)(ref.current); | ||
// Wait until the collection has items to autofocus. | ||
if (manager.collection.size > 0) { | ||
autoFocusRef.current = false; | ||
didAutoFocusRef.current = true; | ||
} | ||
} | ||
// eslint-disable-next-line react-hooks/exhaustive-deps | ||
}, []); | ||
}); | ||
// Scroll the focused element into view when the focusedKey changes. | ||
let lastFocusedKey = (0, $bT8Bh$react.useRef)(manager.focusedKey); | ||
let raf = (0, $bT8Bh$react.useRef)(null); | ||
(0, $bT8Bh$react.useEffect)(()=>{ | ||
if (manager.isFocused && manager.focusedKey != null && (manager.focusedKey !== lastFocusedKey.current || autoFocusRef.current) && (scrollRef === null || scrollRef === void 0 ? void 0 : scrollRef.current)) { | ||
if (manager.isFocused && manager.focusedKey != null && (manager.focusedKey !== lastFocusedKey.current || didAutoFocusRef.current) && scrollRef.current && ref.current) { | ||
let modality = (0, $bT8Bh$reactariainteractions.getInteractionModality)(); | ||
let element = ref.current.querySelector(`[data-key="${CSS.escape(manager.focusedKey.toString())}"]`); | ||
if (!element) // If item element wasn't found, return early (don't update autoFocusRef and lastFocusedKey). | ||
let element = (0, $ee0bdf4faa47f2a8$exports.getItemElement)(ref, manager.focusedKey); | ||
if (!(element instanceof HTMLElement)) // If item element wasn't found, return early (don't update autoFocusRef and lastFocusedKey). | ||
// The collection may initially be empty (e.g. virtualizer), so wait until the element exists. | ||
return; | ||
if (modality === 'keyboard' || autoFocusRef.current) { | ||
(0, $bT8Bh$reactariautils.scrollIntoView)(scrollRef.current, element); | ||
if (modality === 'keyboard' || didAutoFocusRef.current) { | ||
if (raf.current) cancelAnimationFrame(raf.current); | ||
raf.current = requestAnimationFrame(()=>{ | ||
if (scrollRef.current) (0, $bT8Bh$reactariautils.scrollIntoView)(scrollRef.current, element); | ||
}); | ||
// Avoid scroll in iOS VO, since it may cause overlay to close (i.e. RAC submenu) | ||
@@ -297,6 +375,11 @@ if (modality !== 'virtual') (0, $bT8Bh$reactariautils.scrollIntoViewport)(element, { | ||
// If the focused key becomes null (e.g. the last item is deleted), focus the whole collection. | ||
if (!shouldUseVirtualFocus && manager.isFocused && manager.focusedKey == null && lastFocusedKey.current != null) (0, $bT8Bh$reactariafocus.focusSafely)(ref.current); | ||
if (!shouldUseVirtualFocus && manager.isFocused && manager.focusedKey == null && lastFocusedKey.current != null && ref.current) (0, $bT8Bh$reactariainteractions.focusSafely)(ref.current); | ||
lastFocusedKey.current = manager.focusedKey; | ||
autoFocusRef.current = false; | ||
didAutoFocusRef.current = false; | ||
}); | ||
(0, $bT8Bh$react.useEffect)(()=>{ | ||
return ()=>{ | ||
if (raf.current) cancelAnimationFrame(raf.current); | ||
}; | ||
}, []); | ||
// Intercept FocusScope restoration since virtualized collections can reuse DOM nodes. | ||
@@ -324,11 +407,10 @@ (0, $bT8Bh$reactariautils.useEvent)(ref, 'react-aria-focus-scope-restore', (e)=>{ | ||
// This will be marshalled to either the first or last item depending on where focus came from. | ||
// If using virtual focus, don't set a tabIndex at all so that VoiceOver on iOS 14 doesn't try | ||
// to move real DOM focus to the element anyway. | ||
let tabIndex; | ||
let tabIndex = undefined; | ||
if (!shouldUseVirtualFocus) tabIndex = manager.focusedKey == null ? 0 : -1; | ||
let collectionId = (0, $ee0bdf4faa47f2a8$exports.useCollectionId)(manager.collection); | ||
return { | ||
collectionProps: { | ||
...handlers, | ||
tabIndex: tabIndex | ||
} | ||
collectionProps: (0, $bT8Bh$reactariautils.mergeProps)(handlers, { | ||
tabIndex: tabIndex, | ||
'data-collection': collectionId | ||
}) | ||
}; | ||
@@ -335,0 +417,0 @@ } |
@@ -1,8 +0,8 @@ | ||
import {isCtrlKeyPressed as $feb5ffebff200149$export$16792effe837dba3, isNonContiguousSelectionModifier as $feb5ffebff200149$export$d3e3bd3e26688c04} from "./utils.module.js"; | ||
import {getItemElement as $feb5ffebff200149$export$c3d8340acf92597f, isNonContiguousSelectionModifier as $feb5ffebff200149$export$d3e3bd3e26688c04, useCollectionId as $feb5ffebff200149$export$881eb0d9f3605d9d} from "./utils.module.js"; | ||
import {useTypeSelect as $fb3050f43d946246$export$e32c88dfddc6e1d8} from "./useTypeSelect.module.js"; | ||
import {useRouter as $3H3GQ$useRouter, isCtrlKeyPressed as $3H3GQ$isCtrlKeyPressed, focusWithoutScrolling as $3H3GQ$focusWithoutScrolling, useEvent as $3H3GQ$useEvent, scrollIntoViewport as $3H3GQ$scrollIntoViewport, FOCUS_EVENT as $3H3GQ$FOCUS_EVENT, useEffectEvent as $3H3GQ$useEffectEvent, useUpdateLayoutEffect as $3H3GQ$useUpdateLayoutEffect, CLEAR_FOCUS_EVENT as $3H3GQ$CLEAR_FOCUS_EVENT, scrollIntoView as $3H3GQ$scrollIntoView, mergeProps as $3H3GQ$mergeProps} from "@react-aria/utils"; | ||
import {flushSync as $3H3GQ$flushSync} from "react-dom"; | ||
import {useRef as $3H3GQ$useRef, useEffect as $3H3GQ$useEffect} from "react"; | ||
import {getFocusableTreeWalker as $3H3GQ$getFocusableTreeWalker, focusSafely as $3H3GQ$focusSafely} from "@react-aria/focus"; | ||
import {useRouter as $3H3GQ$useRouter, focusWithoutScrolling as $3H3GQ$focusWithoutScrolling, useEvent as $3H3GQ$useEvent, scrollIntoViewport as $3H3GQ$scrollIntoViewport, scrollIntoView as $3H3GQ$scrollIntoView, mergeProps as $3H3GQ$mergeProps} from "@react-aria/utils"; | ||
import {getInteractionModality as $3H3GQ$getInteractionModality} from "@react-aria/interactions"; | ||
import {getInteractionModality as $3H3GQ$getInteractionModality, focusSafely as $3H3GQ$focusSafely} from "@react-aria/interactions"; | ||
import {getFocusableTreeWalker as $3H3GQ$getFocusableTreeWalker, moveVirtualFocus as $3H3GQ$moveVirtualFocus} from "@react-aria/focus"; | ||
import {useLocale as $3H3GQ$useLocale} from "@react-aria/i18n"; | ||
@@ -29,3 +29,3 @@ | ||
function $ae20dd8cbca75726$export$d6daf82dcd84e87c(options) { | ||
let { selectionManager: manager, keyboardDelegate: delegate, ref: ref, autoFocus: autoFocus = false, shouldFocusWrap: shouldFocusWrap = false, disallowEmptySelection: disallowEmptySelection = false, disallowSelectAll: disallowSelectAll = false, selectOnFocus: selectOnFocus = manager.selectionBehavior === 'replace', disallowTypeAhead: disallowTypeAhead = false, shouldUseVirtualFocus: shouldUseVirtualFocus, allowsTabNavigation: allowsTabNavigation = false, isVirtualized: isVirtualized, scrollRef: // If no scrollRef is provided, assume the collection ref is the scrollable region | ||
let { selectionManager: manager, keyboardDelegate: delegate, ref: ref, autoFocus: autoFocus = false, shouldFocusWrap: shouldFocusWrap = false, disallowEmptySelection: disallowEmptySelection = false, disallowSelectAll: disallowSelectAll = false, escapeKeyBehavior: escapeKeyBehavior = 'clearSelection', selectOnFocus: selectOnFocus = manager.selectionBehavior === 'replace', disallowTypeAhead: disallowTypeAhead = false, shouldUseVirtualFocus: shouldUseVirtualFocus, allowsTabNavigation: allowsTabNavigation = false, isVirtualized: isVirtualized, scrollRef: // If no scrollRef is provided, assume the collection ref is the scrollable region | ||
scrollRef = ref, linkBehavior: linkBehavior = 'action' } = options; | ||
@@ -35,2 +35,3 @@ let { direction: direction } = (0, $3H3GQ$useLocale)(); | ||
let onKeyDown = (e)=>{ | ||
var _ref_current; | ||
// Prevent option + tab from doing anything since it doesn't move focus to the cells, only buttons/checkboxes | ||
@@ -40,3 +41,3 @@ if (e.altKey && e.key === 'Tab') e.preventDefault(); | ||
// for elements outside the collection (e.g. menus). | ||
if (!ref.current.contains(e.target)) return; | ||
if (!((_ref_current = ref.current) === null || _ref_current === void 0 ? void 0 : _ref_current.contains(e.target))) return; | ||
const navigateToKey = (key, childFocus)=>{ | ||
@@ -49,5 +50,5 @@ if (key != null) { | ||
}); | ||
let item = scrollRef.current.querySelector(`[data-key="${CSS.escape(key.toString())}"]`); | ||
let item = (0, $feb5ffebff200149$export$c3d8340acf92597f)(ref, key); | ||
let itemProps = manager.getItemProps(key); | ||
router.open(item, e, itemProps.href, itemProps.routerOptions); | ||
if (item) router.open(item, e, itemProps.href, itemProps.routerOptions); | ||
return; | ||
@@ -87,3 +88,3 @@ } | ||
var _delegate_getKeyLeftOf, _delegate_getFirstKey2, _delegate_getLastKey2; | ||
let nextKey = (_delegate_getKeyLeftOf = delegate.getKeyLeftOf) === null || _delegate_getKeyLeftOf === void 0 ? void 0 : _delegate_getKeyLeftOf.call(delegate, manager.focusedKey); | ||
let nextKey = manager.focusedKey != null ? (_delegate_getKeyLeftOf = delegate.getKeyLeftOf) === null || _delegate_getKeyLeftOf === void 0 ? void 0 : _delegate_getKeyLeftOf.call(delegate, manager.focusedKey) : null; | ||
if (nextKey == null && shouldFocusWrap) nextKey = direction === 'rtl' ? (_delegate_getFirstKey2 = delegate.getFirstKey) === null || _delegate_getFirstKey2 === void 0 ? void 0 : _delegate_getFirstKey2.call(delegate, manager.focusedKey) : (_delegate_getLastKey2 = delegate.getLastKey) === null || _delegate_getLastKey2 === void 0 ? void 0 : _delegate_getLastKey2.call(delegate, manager.focusedKey); | ||
@@ -99,3 +100,3 @@ if (nextKey != null) { | ||
var _delegate_getKeyRightOf, _delegate_getLastKey3, _delegate_getFirstKey3; | ||
let nextKey = (_delegate_getKeyRightOf = delegate.getKeyRightOf) === null || _delegate_getKeyRightOf === void 0 ? void 0 : _delegate_getKeyRightOf.call(delegate, manager.focusedKey); | ||
let nextKey = manager.focusedKey != null ? (_delegate_getKeyRightOf = delegate.getKeyRightOf) === null || _delegate_getKeyRightOf === void 0 ? void 0 : _delegate_getKeyRightOf.call(delegate, manager.focusedKey) : null; | ||
if (nextKey == null && shouldFocusWrap) nextKey = direction === 'rtl' ? (_delegate_getLastKey3 = delegate.getLastKey) === null || _delegate_getLastKey3 === void 0 ? void 0 : _delegate_getLastKey3.call(delegate, manager.focusedKey) : (_delegate_getFirstKey3 = delegate.getFirstKey) === null || _delegate_getFirstKey3 === void 0 ? void 0 : _delegate_getFirstKey3.call(delegate, manager.focusedKey); | ||
@@ -110,7 +111,10 @@ if (nextKey != null) { | ||
if (delegate.getFirstKey) { | ||
if (manager.focusedKey === null && e.shiftKey) return; | ||
e.preventDefault(); | ||
let firstKey = delegate.getFirstKey(manager.focusedKey, (0, $feb5ffebff200149$export$16792effe837dba3)(e)); | ||
let firstKey = delegate.getFirstKey(manager.focusedKey, (0, $3H3GQ$isCtrlKeyPressed)(e)); | ||
manager.setFocusedKey(firstKey); | ||
if ((0, $feb5ffebff200149$export$16792effe837dba3)(e) && e.shiftKey && manager.selectionMode === 'multiple') manager.extendSelection(firstKey); | ||
else if (selectOnFocus) manager.replaceSelection(firstKey); | ||
if (firstKey != null) { | ||
if ((0, $3H3GQ$isCtrlKeyPressed)(e) && e.shiftKey && manager.selectionMode === 'multiple') manager.extendSelection(firstKey); | ||
else if (selectOnFocus) manager.replaceSelection(firstKey); | ||
} | ||
} | ||
@@ -120,11 +124,14 @@ break; | ||
if (delegate.getLastKey) { | ||
if (manager.focusedKey === null && e.shiftKey) return; | ||
e.preventDefault(); | ||
let lastKey = delegate.getLastKey(manager.focusedKey, (0, $feb5ffebff200149$export$16792effe837dba3)(e)); | ||
let lastKey = delegate.getLastKey(manager.focusedKey, (0, $3H3GQ$isCtrlKeyPressed)(e)); | ||
manager.setFocusedKey(lastKey); | ||
if ((0, $feb5ffebff200149$export$16792effe837dba3)(e) && e.shiftKey && manager.selectionMode === 'multiple') manager.extendSelection(lastKey); | ||
else if (selectOnFocus) manager.replaceSelection(lastKey); | ||
if (lastKey != null) { | ||
if ((0, $3H3GQ$isCtrlKeyPressed)(e) && e.shiftKey && manager.selectionMode === 'multiple') manager.extendSelection(lastKey); | ||
else if (selectOnFocus) manager.replaceSelection(lastKey); | ||
} | ||
} | ||
break; | ||
case 'PageDown': | ||
if (delegate.getKeyPageBelow) { | ||
if (delegate.getKeyPageBelow && manager.focusedKey != null) { | ||
let nextKey = delegate.getKeyPageBelow(manager.focusedKey); | ||
@@ -138,3 +145,3 @@ if (nextKey != null) { | ||
case 'PageUp': | ||
if (delegate.getKeyPageAbove) { | ||
if (delegate.getKeyPageAbove && manager.focusedKey != null) { | ||
let nextKey = delegate.getKeyPageAbove(manager.focusedKey); | ||
@@ -148,3 +155,3 @@ if (nextKey != null) { | ||
case 'a': | ||
if ((0, $feb5ffebff200149$export$16792effe837dba3)(e) && manager.selectionMode === 'multiple' && disallowSelectAll !== true) { | ||
if ((0, $3H3GQ$isCtrlKeyPressed)(e) && manager.selectionMode === 'multiple' && disallowSelectAll !== true) { | ||
e.preventDefault(); | ||
@@ -155,3 +162,3 @@ manager.selectAll(); | ||
case 'Escape': | ||
if (!disallowEmptySelection && manager.selectedKeys.size !== 0) { | ||
if (escapeKeyBehavior === 'clearSelection' && !disallowEmptySelection && manager.selectedKeys.size !== 0) { | ||
e.stopPropagation(); | ||
@@ -175,3 +182,3 @@ e.preventDefault(); | ||
}); | ||
let next; | ||
let next = undefined; | ||
let last; | ||
@@ -194,6 +201,8 @@ do { | ||
}); | ||
(0, $3H3GQ$useEvent)(scrollRef, 'scroll', isVirtualized ? null : ()=>{ | ||
(0, $3H3GQ$useEvent)(scrollRef, 'scroll', isVirtualized ? undefined : ()=>{ | ||
var _scrollRef_current, _scrollRef_current1; | ||
var _scrollRef_current_scrollTop, _scrollRef_current_scrollLeft; | ||
scrollPos.current = { | ||
top: scrollRef.current.scrollTop, | ||
left: scrollRef.current.scrollLeft | ||
top: (_scrollRef_current_scrollTop = (_scrollRef_current = scrollRef.current) === null || _scrollRef_current === void 0 ? void 0 : _scrollRef_current.scrollTop) !== null && _scrollRef_current_scrollTop !== void 0 ? _scrollRef_current_scrollTop : 0, | ||
left: (_scrollRef_current_scrollLeft = (_scrollRef_current1 = scrollRef.current) === null || _scrollRef_current1 === void 0 ? void 0 : _scrollRef_current1.scrollLeft) !== null && _scrollRef_current_scrollLeft !== void 0 ? _scrollRef_current_scrollLeft : 0 | ||
}; | ||
@@ -211,6 +220,7 @@ }); | ||
if (manager.focusedKey == null) { | ||
let navigateToFirstKey = (key)=>{ | ||
var _delegate_getLastKey, _delegate_getFirstKey; | ||
let navigateToKey = (key)=>{ | ||
if (key != null) { | ||
manager.setFocusedKey(key); | ||
if (selectOnFocus) manager.replaceSelection(key); | ||
if (selectOnFocus && !manager.isSelected(key)) manager.replaceSelection(key); | ||
} | ||
@@ -223,5 +233,5 @@ }; | ||
var _manager_lastSelectedKey, _manager_firstSelectedKey; | ||
if (relatedTarget && e.currentTarget.compareDocumentPosition(relatedTarget) & Node.DOCUMENT_POSITION_FOLLOWING) navigateToFirstKey((_manager_lastSelectedKey = manager.lastSelectedKey) !== null && _manager_lastSelectedKey !== void 0 ? _manager_lastSelectedKey : delegate.getLastKey()); | ||
else navigateToFirstKey((_manager_firstSelectedKey = manager.firstSelectedKey) !== null && _manager_firstSelectedKey !== void 0 ? _manager_firstSelectedKey : delegate.getFirstKey()); | ||
} else if (!isVirtualized) { | ||
if (relatedTarget && e.currentTarget.compareDocumentPosition(relatedTarget) & Node.DOCUMENT_POSITION_FOLLOWING) navigateToKey((_manager_lastSelectedKey = manager.lastSelectedKey) !== null && _manager_lastSelectedKey !== void 0 ? _manager_lastSelectedKey : (_delegate_getLastKey = delegate.getLastKey) === null || _delegate_getLastKey === void 0 ? void 0 : _delegate_getLastKey.call(delegate)); | ||
else navigateToKey((_manager_firstSelectedKey = manager.firstSelectedKey) !== null && _manager_firstSelectedKey !== void 0 ? _manager_firstSelectedKey : (_delegate_getFirstKey = delegate.getFirstKey) === null || _delegate_getFirstKey === void 0 ? void 0 : _delegate_getFirstKey.call(delegate)); | ||
} else if (!isVirtualized && scrollRef.current) { | ||
// Restore the scroll position to what it was before. | ||
@@ -231,8 +241,8 @@ scrollRef.current.scrollTop = scrollPos.current.top; | ||
} | ||
if (manager.focusedKey != null) { | ||
if (manager.focusedKey != null && scrollRef.current) { | ||
// Refocus and scroll the focused item into view if it exists within the scrollable region. | ||
let element = scrollRef.current.querySelector(`[data-key="${CSS.escape(manager.focusedKey.toString())}"]`); | ||
if (element) { | ||
let element = (0, $feb5ffebff200149$export$c3d8340acf92597f)(ref, manager.focusedKey); | ||
if (element instanceof HTMLElement) { | ||
// This prevents a flash of focus on the first/last element in the collection, or the collection itself. | ||
if (!element.contains(document.activeElement)) (0, $3H3GQ$focusWithoutScrolling)(element); | ||
if (!element.contains(document.activeElement) && !shouldUseVirtualFocus) (0, $3H3GQ$focusWithoutScrolling)(element); | ||
let modality = (0, $3H3GQ$getInteractionModality)(); | ||
@@ -249,9 +259,69 @@ if (modality === 'keyboard') (0, $3H3GQ$scrollIntoViewport)(element, { | ||
}; | ||
// Ref to track whether the first item in the collection should be automatically focused. Specifically used for autocomplete when user types | ||
// to focus the first key AFTER the collection updates. | ||
// TODO: potentially expand the usage of this | ||
let shouldVirtualFocusFirst = (0, $3H3GQ$useRef)(false); | ||
// Add event listeners for custom virtual events. These handle updating the focused key in response to various keyboard events | ||
// at the autocomplete level | ||
// TODO: fix type later | ||
(0, $3H3GQ$useEvent)(ref, (0, $3H3GQ$FOCUS_EVENT), !shouldUseVirtualFocus ? undefined : (e)=>{ | ||
let { detail: detail } = e; | ||
e.stopPropagation(); | ||
manager.setFocused(true); | ||
// If the user is typing forwards, autofocus the first option in the list. | ||
if ((detail === null || detail === void 0 ? void 0 : detail.focusStrategy) === 'first') shouldVirtualFocusFirst.current = true; | ||
}); | ||
let updateActiveDescendant = (0, $3H3GQ$useEffectEvent)(()=>{ | ||
var _delegate_getFirstKey; | ||
var _delegate_getFirstKey1; | ||
let keyToFocus = (_delegate_getFirstKey1 = (_delegate_getFirstKey = delegate.getFirstKey) === null || _delegate_getFirstKey === void 0 ? void 0 : _delegate_getFirstKey.call(delegate)) !== null && _delegate_getFirstKey1 !== void 0 ? _delegate_getFirstKey1 : null; | ||
// If no focusable items exist in the list, make sure to clear any activedescendant that may still exist | ||
if (keyToFocus == null) { | ||
(0, $3H3GQ$moveVirtualFocus)(ref.current); | ||
// If there wasn't a focusable key but the collection had items, then that means we aren't in an intermediate load state and all keys are disabled. | ||
// Reset shouldVirtualFocusFirst so that we don't erronously autofocus an item when the collection is filtered again. | ||
if (manager.collection.size > 0) shouldVirtualFocusFirst.current = false; | ||
} else { | ||
manager.setFocusedKey(keyToFocus); | ||
// Only set shouldVirtualFocusFirst to false if we've successfully set the first key as the focused key | ||
// If there wasn't a key to focus, we might be in a temporary loading state so we'll want to still focus the first key | ||
// after the collection updates after load | ||
shouldVirtualFocusFirst.current = false; | ||
} | ||
}); | ||
(0, $3H3GQ$useUpdateLayoutEffect)(()=>{ | ||
if (shouldVirtualFocusFirst.current) updateActiveDescendant(); | ||
}, [ | ||
manager.collection, | ||
updateActiveDescendant | ||
]); | ||
let resetFocusFirstFlag = (0, $3H3GQ$useEffectEvent)(()=>{ | ||
// If user causes the focused key to change in any other way, clear shouldVirtualFocusFirst so we don't | ||
// accidentally move focus from under them. Skip this if the collection was empty because we might be in a load | ||
// state and will still want to focus the first item after load | ||
if (manager.collection.size > 0) shouldVirtualFocusFirst.current = false; | ||
}); | ||
(0, $3H3GQ$useUpdateLayoutEffect)(()=>{ | ||
resetFocusFirstFlag(); | ||
}, [ | ||
manager.focusedKey, | ||
resetFocusFirstFlag | ||
]); | ||
(0, $3H3GQ$useEvent)(ref, (0, $3H3GQ$CLEAR_FOCUS_EVENT), !shouldUseVirtualFocus ? undefined : (e)=>{ | ||
var _e_detail; | ||
e.stopPropagation(); | ||
manager.setFocused(false); | ||
if ((_e_detail = e.detail) === null || _e_detail === void 0 ? void 0 : _e_detail.clearFocusKey) manager.setFocusedKey(null); | ||
}); | ||
const autoFocusRef = (0, $3H3GQ$useRef)(autoFocus); | ||
const didAutoFocusRef = (0, $3H3GQ$useRef)(false); | ||
(0, $3H3GQ$useEffect)(()=>{ | ||
if (autoFocusRef.current) { | ||
var _delegate_getFirstKey, _delegate_getLastKey; | ||
let focusedKey = null; | ||
var _delegate_getFirstKey1; | ||
// Check focus strategy to determine which item to focus | ||
if (autoFocus === 'first') focusedKey = delegate.getFirstKey(); | ||
if (autoFocus === 'last') focusedKey = delegate.getLastKey(); | ||
if (autoFocus === 'first') focusedKey = (_delegate_getFirstKey1 = (_delegate_getFirstKey = delegate.getFirstKey) === null || _delegate_getFirstKey === void 0 ? void 0 : _delegate_getFirstKey.call(delegate)) !== null && _delegate_getFirstKey1 !== void 0 ? _delegate_getFirstKey1 : null; | ||
var _delegate_getLastKey1; | ||
if (autoFocus === 'last') focusedKey = (_delegate_getLastKey1 = (_delegate_getLastKey = delegate.getLastKey) === null || _delegate_getLastKey === void 0 ? void 0 : _delegate_getLastKey.call(delegate)) !== null && _delegate_getLastKey1 !== void 0 ? _delegate_getLastKey1 : null; | ||
// If there are any selected keys, make the first one the new focus target | ||
@@ -268,17 +338,25 @@ let selectedKeys = manager.selectedKeys; | ||
// If no default focus key is selected, focus the collection itself. | ||
if (focusedKey == null && !shouldUseVirtualFocus) (0, $3H3GQ$focusSafely)(ref.current); | ||
if (focusedKey == null && !shouldUseVirtualFocus && ref.current) (0, $3H3GQ$focusSafely)(ref.current); | ||
// Wait until the collection has items to autofocus. | ||
if (manager.collection.size > 0) { | ||
autoFocusRef.current = false; | ||
didAutoFocusRef.current = true; | ||
} | ||
} | ||
// eslint-disable-next-line react-hooks/exhaustive-deps | ||
}, []); | ||
}); | ||
// Scroll the focused element into view when the focusedKey changes. | ||
let lastFocusedKey = (0, $3H3GQ$useRef)(manager.focusedKey); | ||
let raf = (0, $3H3GQ$useRef)(null); | ||
(0, $3H3GQ$useEffect)(()=>{ | ||
if (manager.isFocused && manager.focusedKey != null && (manager.focusedKey !== lastFocusedKey.current || autoFocusRef.current) && (scrollRef === null || scrollRef === void 0 ? void 0 : scrollRef.current)) { | ||
if (manager.isFocused && manager.focusedKey != null && (manager.focusedKey !== lastFocusedKey.current || didAutoFocusRef.current) && scrollRef.current && ref.current) { | ||
let modality = (0, $3H3GQ$getInteractionModality)(); | ||
let element = ref.current.querySelector(`[data-key="${CSS.escape(manager.focusedKey.toString())}"]`); | ||
if (!element) // If item element wasn't found, return early (don't update autoFocusRef and lastFocusedKey). | ||
let element = (0, $feb5ffebff200149$export$c3d8340acf92597f)(ref, manager.focusedKey); | ||
if (!(element instanceof HTMLElement)) // If item element wasn't found, return early (don't update autoFocusRef and lastFocusedKey). | ||
// The collection may initially be empty (e.g. virtualizer), so wait until the element exists. | ||
return; | ||
if (modality === 'keyboard' || autoFocusRef.current) { | ||
(0, $3H3GQ$scrollIntoView)(scrollRef.current, element); | ||
if (modality === 'keyboard' || didAutoFocusRef.current) { | ||
if (raf.current) cancelAnimationFrame(raf.current); | ||
raf.current = requestAnimationFrame(()=>{ | ||
if (scrollRef.current) (0, $3H3GQ$scrollIntoView)(scrollRef.current, element); | ||
}); | ||
// Avoid scroll in iOS VO, since it may cause overlay to close (i.e. RAC submenu) | ||
@@ -291,6 +369,11 @@ if (modality !== 'virtual') (0, $3H3GQ$scrollIntoViewport)(element, { | ||
// If the focused key becomes null (e.g. the last item is deleted), focus the whole collection. | ||
if (!shouldUseVirtualFocus && manager.isFocused && manager.focusedKey == null && lastFocusedKey.current != null) (0, $3H3GQ$focusSafely)(ref.current); | ||
if (!shouldUseVirtualFocus && manager.isFocused && manager.focusedKey == null && lastFocusedKey.current != null && ref.current) (0, $3H3GQ$focusSafely)(ref.current); | ||
lastFocusedKey.current = manager.focusedKey; | ||
autoFocusRef.current = false; | ||
didAutoFocusRef.current = false; | ||
}); | ||
(0, $3H3GQ$useEffect)(()=>{ | ||
return ()=>{ | ||
if (raf.current) cancelAnimationFrame(raf.current); | ||
}; | ||
}, []); | ||
// Intercept FocusScope restoration since virtualized collections can reuse DOM nodes. | ||
@@ -318,11 +401,10 @@ (0, $3H3GQ$useEvent)(ref, 'react-aria-focus-scope-restore', (e)=>{ | ||
// This will be marshalled to either the first or last item depending on where focus came from. | ||
// If using virtual focus, don't set a tabIndex at all so that VoiceOver on iOS 14 doesn't try | ||
// to move real DOM focus to the element anyway. | ||
let tabIndex; | ||
let tabIndex = undefined; | ||
if (!shouldUseVirtualFocus) tabIndex = manager.focusedKey == null ? 0 : -1; | ||
let collectionId = (0, $feb5ffebff200149$export$881eb0d9f3605d9d)(manager.collection); | ||
return { | ||
collectionProps: { | ||
...handlers, | ||
tabIndex: tabIndex | ||
} | ||
collectionProps: (0, $3H3GQ$mergeProps)(handlers, { | ||
tabIndex: tabIndex, | ||
'data-collection': collectionId | ||
}) | ||
}; | ||
@@ -329,0 +411,0 @@ } |
var $ee0bdf4faa47f2a8$exports = require("./utils.main.js"); | ||
var $i4XHw$reactariainteractions = require("@react-aria/interactions"); | ||
var $i4XHw$reactariautils = require("@react-aria/utils"); | ||
var $i4XHw$reactariafocus = require("@react-aria/focus"); | ||
var $i4XHw$reactariautils = require("@react-aria/utils"); | ||
var $i4XHw$reactariainteractions = require("@react-aria/interactions"); | ||
var $i4XHw$react = require("react"); | ||
@@ -29,4 +29,5 @@ | ||
function $433b1145b0781e10$export$ecf600387e221c37(options) { | ||
let { selectionManager: manager, key: key, ref: ref, shouldSelectOnPressUp: shouldSelectOnPressUp, shouldUseVirtualFocus: shouldUseVirtualFocus, focus: focus, isDisabled: isDisabled, onAction: onAction, allowsDifferentPressOrigin: allowsDifferentPressOrigin, linkBehavior: linkBehavior = 'action' } = options; | ||
let { id: id, selectionManager: manager, key: key, ref: ref, shouldSelectOnPressUp: shouldSelectOnPressUp, shouldUseVirtualFocus: shouldUseVirtualFocus, focus: focus, isDisabled: isDisabled, onAction: onAction, allowsDifferentPressOrigin: allowsDifferentPressOrigin, linkBehavior: linkBehavior = 'action' } = options; | ||
let router = (0, $i4XHw$reactariautils.useRouter)(); | ||
id = (0, $i4XHw$reactariautils.useId)(id); | ||
let onSelect = (e)=>{ | ||
@@ -37,3 +38,3 @@ if (e.pointerType === 'keyboard' && (0, $ee0bdf4faa47f2a8$exports.isNonContiguousSelectionModifier)(e)) manager.toggleSelection(key); | ||
if (manager.isLink(key)) { | ||
if (linkBehavior === 'selection') { | ||
if (linkBehavior === 'selection' && ref.current) { | ||
let itemProps = manager.getItemProps(key); | ||
@@ -50,3 +51,3 @@ router.open(ref.current, e, itemProps.href, itemProps.routerOptions); | ||
} else if (e && e.shiftKey) manager.extendSelection(key); | ||
else if (manager.selectionBehavior === 'toggle' || e && ((0, $ee0bdf4faa47f2a8$exports.isCtrlKeyPressed)(e) || e.pointerType === 'touch' || e.pointerType === 'virtual')) // if touch or virtual (VO) then we just want to toggle, otherwise it's impossible to multi select because they don't have modifier keys | ||
else if (manager.selectionBehavior === 'toggle' || e && ((0, $i4XHw$reactariautils.isCtrlKeyPressed)(e) || e.pointerType === 'touch' || e.pointerType === 'virtual')) // if touch or virtual (VO) then we just want to toggle, otherwise it's impossible to multi select because they don't have modifier keys | ||
manager.toggleSelection(key); | ||
@@ -57,7 +58,12 @@ else manager.replaceSelection(key); | ||
// Focus the associated DOM node when this item becomes the focusedKey | ||
// TODO: can't make this useLayoutEffect bacause it breaks menus inside dialogs | ||
// However, if this is a useEffect, it runs twice and dispatches two blur events and immediately sets | ||
// aria-activeDescendant in useAutocomplete... I've worked around this for now | ||
(0, $i4XHw$react.useEffect)(()=>{ | ||
let isFocused = key === manager.focusedKey; | ||
if (isFocused && manager.isFocused && !shouldUseVirtualFocus) { | ||
if (focus) focus(); | ||
else if (document.activeElement !== ref.current) (0, $i4XHw$reactariafocus.focusSafely)(ref.current); | ||
if (isFocused && manager.isFocused) { | ||
if (!shouldUseVirtualFocus) { | ||
if (focus) focus(); | ||
else if (document.activeElement !== ref.current && ref.current) (0, $i4XHw$reactariainteractions.focusSafely)(ref.current); | ||
} else (0, $i4XHw$reactariafocus.moveVirtualFocus)(ref.current); | ||
} | ||
@@ -105,3 +111,3 @@ // eslint-disable-next-line react-hooks/exhaustive-deps | ||
if (onAction) onAction(); | ||
if (hasLinkAction) { | ||
if (hasLinkAction && ref.current) { | ||
let itemProps = manager.getItemProps(key); | ||
@@ -118,3 +124,5 @@ router.open(ref.current, e, itemProps.href, itemProps.routerOptions); | ||
// For keyboard events, selection still occurs on key down. | ||
let itemPressProps = {}; | ||
let itemPressProps = { | ||
ref: ref | ||
}; | ||
if (shouldSelectOnPressUp) { | ||
@@ -126,3 +134,3 @@ itemPressProps.onPressStart = (e)=>{ | ||
}; | ||
// If allowsDifferentPressOrigin, make selection happen on pressUp (e.g. open menu on press down, selection on menu item happens on press up.) | ||
// If allowsDifferentPressOrigin and interacting with mouse, make selection happen on pressUp (e.g. open menu on press down, selection on menu item happens on press up.) | ||
// Otherwise, have selection happen onPress (prevents listview row selection when clicking on interactable elements in the row) | ||
@@ -136,6 +144,8 @@ if (!allowsDifferentPressOrigin) itemPressProps.onPress = (e)=>{ | ||
else { | ||
itemPressProps.onPressUp = hasPrimaryAction ? null : (e)=>{ | ||
if (e.pointerType !== 'keyboard' && allowsSelection) onSelect(e); | ||
itemPressProps.onPressUp = hasPrimaryAction ? undefined : (e)=>{ | ||
if (e.pointerType === 'mouse' && allowsSelection) onSelect(e); | ||
}; | ||
itemPressProps.onPress = hasPrimaryAction ? performAction : null; | ||
itemPressProps.onPress = hasPrimaryAction ? performAction : (e)=>{ | ||
if (e.pointerType !== 'keyboard' && e.pointerType !== 'mouse' && allowsSelection) onSelect(e); | ||
}; | ||
} | ||
@@ -162,4 +172,20 @@ } else { | ||
} | ||
itemProps['data-collection'] = (0, $ee0bdf4faa47f2a8$exports.getCollectionId)(manager.collection); | ||
itemProps['data-key'] = key; | ||
itemPressProps.preventFocusOnPress = shouldUseVirtualFocus; | ||
// When using virtual focus, make sure the focused key gets updated on press. | ||
if (shouldUseVirtualFocus) itemPressProps = (0, $i4XHw$reactariautils.mergeProps)(itemPressProps, { | ||
onPressStart (e) { | ||
if (e.pointerType !== 'touch') { | ||
manager.setFocused(true); | ||
manager.setFocusedKey(key); | ||
} | ||
}, | ||
onPress (e) { | ||
if (e.pointerType === 'touch') { | ||
manager.setFocused(true); | ||
manager.setFocusedKey(key); | ||
} | ||
} | ||
}); | ||
let { pressProps: pressProps, isPressed: isPressed } = (0, $i4XHw$reactariainteractions.usePress)(itemPressProps); | ||
@@ -199,7 +225,11 @@ // Double clicking with a mouse with selectionBehavior = 'replace' performs an action. | ||
return { | ||
itemProps: (0, $i4XHw$reactariautils.mergeProps)(itemProps, allowsSelection || hasPrimaryAction ? pressProps : {}, longPressEnabled ? longPressProps : {}, { | ||
itemProps: (0, $i4XHw$reactariautils.mergeProps)(itemProps, allowsSelection || hasPrimaryAction || shouldUseVirtualFocus ? pressProps : {}, longPressEnabled ? longPressProps : {}, { | ||
onDoubleClick: onDoubleClick, | ||
onDragStartCapture: onDragStartCapture, | ||
onClick: onClick | ||
}), | ||
onClick: onClick, | ||
id: id | ||
}, // Prevent DOM focus from moving on mouse down when using virtual focus | ||
shouldUseVirtualFocus ? { | ||
onMouseDown: (e)=>e.preventDefault() | ||
} : undefined), | ||
isPressed: isPressed, | ||
@@ -206,0 +236,0 @@ isSelected: manager.isSelected(key), |
@@ -1,5 +0,5 @@ | ||
import {isCtrlKeyPressed as $feb5ffebff200149$export$16792effe837dba3, isNonContiguousSelectionModifier as $feb5ffebff200149$export$d3e3bd3e26688c04} from "./utils.module.js"; | ||
import {focusSafely as $581M0$focusSafely} from "@react-aria/focus"; | ||
import {useRouter as $581M0$useRouter, openLink as $581M0$openLink, mergeProps as $581M0$mergeProps} from "@react-aria/utils"; | ||
import {usePress as $581M0$usePress, useLongPress as $581M0$useLongPress} from "@react-aria/interactions"; | ||
import {getCollectionId as $feb5ffebff200149$export$6aeb1680a0ae8741, isNonContiguousSelectionModifier as $feb5ffebff200149$export$d3e3bd3e26688c04} from "./utils.module.js"; | ||
import {focusSafely as $581M0$focusSafely, usePress as $581M0$usePress, useLongPress as $581M0$useLongPress} from "@react-aria/interactions"; | ||
import {useRouter as $581M0$useRouter, useId as $581M0$useId, isCtrlKeyPressed as $581M0$isCtrlKeyPressed, mergeProps as $581M0$mergeProps, openLink as $581M0$openLink} from "@react-aria/utils"; | ||
import {moveVirtualFocus as $581M0$moveVirtualFocus} from "@react-aria/focus"; | ||
import {useEffect as $581M0$useEffect, useRef as $581M0$useRef} from "react"; | ||
@@ -23,4 +23,5 @@ | ||
function $880e95eb8b93ba9a$export$ecf600387e221c37(options) { | ||
let { selectionManager: manager, key: key, ref: ref, shouldSelectOnPressUp: shouldSelectOnPressUp, shouldUseVirtualFocus: shouldUseVirtualFocus, focus: focus, isDisabled: isDisabled, onAction: onAction, allowsDifferentPressOrigin: allowsDifferentPressOrigin, linkBehavior: linkBehavior = 'action' } = options; | ||
let { id: id, selectionManager: manager, key: key, ref: ref, shouldSelectOnPressUp: shouldSelectOnPressUp, shouldUseVirtualFocus: shouldUseVirtualFocus, focus: focus, isDisabled: isDisabled, onAction: onAction, allowsDifferentPressOrigin: allowsDifferentPressOrigin, linkBehavior: linkBehavior = 'action' } = options; | ||
let router = (0, $581M0$useRouter)(); | ||
id = (0, $581M0$useId)(id); | ||
let onSelect = (e)=>{ | ||
@@ -31,3 +32,3 @@ if (e.pointerType === 'keyboard' && (0, $feb5ffebff200149$export$d3e3bd3e26688c04)(e)) manager.toggleSelection(key); | ||
if (manager.isLink(key)) { | ||
if (linkBehavior === 'selection') { | ||
if (linkBehavior === 'selection' && ref.current) { | ||
let itemProps = manager.getItemProps(key); | ||
@@ -44,3 +45,3 @@ router.open(ref.current, e, itemProps.href, itemProps.routerOptions); | ||
} else if (e && e.shiftKey) manager.extendSelection(key); | ||
else if (manager.selectionBehavior === 'toggle' || e && ((0, $feb5ffebff200149$export$16792effe837dba3)(e) || e.pointerType === 'touch' || e.pointerType === 'virtual')) // if touch or virtual (VO) then we just want to toggle, otherwise it's impossible to multi select because they don't have modifier keys | ||
else if (manager.selectionBehavior === 'toggle' || e && ((0, $581M0$isCtrlKeyPressed)(e) || e.pointerType === 'touch' || e.pointerType === 'virtual')) // if touch or virtual (VO) then we just want to toggle, otherwise it's impossible to multi select because they don't have modifier keys | ||
manager.toggleSelection(key); | ||
@@ -51,7 +52,12 @@ else manager.replaceSelection(key); | ||
// Focus the associated DOM node when this item becomes the focusedKey | ||
// TODO: can't make this useLayoutEffect bacause it breaks menus inside dialogs | ||
// However, if this is a useEffect, it runs twice and dispatches two blur events and immediately sets | ||
// aria-activeDescendant in useAutocomplete... I've worked around this for now | ||
(0, $581M0$useEffect)(()=>{ | ||
let isFocused = key === manager.focusedKey; | ||
if (isFocused && manager.isFocused && !shouldUseVirtualFocus) { | ||
if (focus) focus(); | ||
else if (document.activeElement !== ref.current) (0, $581M0$focusSafely)(ref.current); | ||
if (isFocused && manager.isFocused) { | ||
if (!shouldUseVirtualFocus) { | ||
if (focus) focus(); | ||
else if (document.activeElement !== ref.current && ref.current) (0, $581M0$focusSafely)(ref.current); | ||
} else (0, $581M0$moveVirtualFocus)(ref.current); | ||
} | ||
@@ -99,3 +105,3 @@ // eslint-disable-next-line react-hooks/exhaustive-deps | ||
if (onAction) onAction(); | ||
if (hasLinkAction) { | ||
if (hasLinkAction && ref.current) { | ||
let itemProps = manager.getItemProps(key); | ||
@@ -112,3 +118,5 @@ router.open(ref.current, e, itemProps.href, itemProps.routerOptions); | ||
// For keyboard events, selection still occurs on key down. | ||
let itemPressProps = {}; | ||
let itemPressProps = { | ||
ref: ref | ||
}; | ||
if (shouldSelectOnPressUp) { | ||
@@ -120,3 +128,3 @@ itemPressProps.onPressStart = (e)=>{ | ||
}; | ||
// If allowsDifferentPressOrigin, make selection happen on pressUp (e.g. open menu on press down, selection on menu item happens on press up.) | ||
// If allowsDifferentPressOrigin and interacting with mouse, make selection happen on pressUp (e.g. open menu on press down, selection on menu item happens on press up.) | ||
// Otherwise, have selection happen onPress (prevents listview row selection when clicking on interactable elements in the row) | ||
@@ -130,6 +138,8 @@ if (!allowsDifferentPressOrigin) itemPressProps.onPress = (e)=>{ | ||
else { | ||
itemPressProps.onPressUp = hasPrimaryAction ? null : (e)=>{ | ||
if (e.pointerType !== 'keyboard' && allowsSelection) onSelect(e); | ||
itemPressProps.onPressUp = hasPrimaryAction ? undefined : (e)=>{ | ||
if (e.pointerType === 'mouse' && allowsSelection) onSelect(e); | ||
}; | ||
itemPressProps.onPress = hasPrimaryAction ? performAction : null; | ||
itemPressProps.onPress = hasPrimaryAction ? performAction : (e)=>{ | ||
if (e.pointerType !== 'keyboard' && e.pointerType !== 'mouse' && allowsSelection) onSelect(e); | ||
}; | ||
} | ||
@@ -156,4 +166,20 @@ } else { | ||
} | ||
itemProps['data-collection'] = (0, $feb5ffebff200149$export$6aeb1680a0ae8741)(manager.collection); | ||
itemProps['data-key'] = key; | ||
itemPressProps.preventFocusOnPress = shouldUseVirtualFocus; | ||
// When using virtual focus, make sure the focused key gets updated on press. | ||
if (shouldUseVirtualFocus) itemPressProps = (0, $581M0$mergeProps)(itemPressProps, { | ||
onPressStart (e) { | ||
if (e.pointerType !== 'touch') { | ||
manager.setFocused(true); | ||
manager.setFocusedKey(key); | ||
} | ||
}, | ||
onPress (e) { | ||
if (e.pointerType === 'touch') { | ||
manager.setFocused(true); | ||
manager.setFocusedKey(key); | ||
} | ||
} | ||
}); | ||
let { pressProps: pressProps, isPressed: isPressed } = (0, $581M0$usePress)(itemPressProps); | ||
@@ -193,7 +219,11 @@ // Double clicking with a mouse with selectionBehavior = 'replace' performs an action. | ||
return { | ||
itemProps: (0, $581M0$mergeProps)(itemProps, allowsSelection || hasPrimaryAction ? pressProps : {}, longPressEnabled ? longPressProps : {}, { | ||
itemProps: (0, $581M0$mergeProps)(itemProps, allowsSelection || hasPrimaryAction || shouldUseVirtualFocus ? pressProps : {}, longPressEnabled ? longPressProps : {}, { | ||
onDoubleClick: onDoubleClick, | ||
onDragStartCapture: onDragStartCapture, | ||
onClick: onClick | ||
}), | ||
onClick: onClick, | ||
id: id | ||
}, // Prevent DOM focus from moving on mouse down when using virtual focus | ||
shouldUseVirtualFocus ? { | ||
onMouseDown: (e)=>e.preventDefault() | ||
} : undefined), | ||
isPressed: isPressed, | ||
@@ -200,0 +230,0 @@ isSelected: manager.isSelected(key), |
@@ -27,3 +27,3 @@ var $3XJlT$react = require("react"); | ||
search: '', | ||
timeout: null | ||
timeout: undefined | ||
}).current; | ||
@@ -42,10 +42,12 @@ let onKeyDown = (e)=>{ | ||
state.search += character; | ||
// Use the delegate to find a key to focus. | ||
// Prioritize items after the currently focused item, falling back to searching the whole list. | ||
let key = keyboardDelegate.getKeyForSearch(state.search, selectionManager.focusedKey); | ||
// If no key found, search from the top. | ||
if (key == null) key = keyboardDelegate.getKeyForSearch(state.search); | ||
if (key != null) { | ||
selectionManager.setFocusedKey(key); | ||
if (onTypeSelect) onTypeSelect(key); | ||
if (keyboardDelegate.getKeyForSearch != null) { | ||
// Use the delegate to find a key to focus. | ||
// Prioritize items after the currently focused item, falling back to searching the whole list. | ||
let key = keyboardDelegate.getKeyForSearch(state.search, selectionManager.focusedKey); | ||
// If no key found, search from the top. | ||
if (key == null) key = keyboardDelegate.getKeyForSearch(state.search); | ||
if (key != null) { | ||
selectionManager.setFocusedKey(key); | ||
if (onTypeSelect) onTypeSelect(key); | ||
} | ||
} | ||
@@ -61,3 +63,3 @@ clearTimeout(state.timeout); | ||
// other hooks in order to handle the Spacebar event. | ||
onKeyDownCapture: keyboardDelegate.getKeyForSearch ? onKeyDown : null | ||
onKeyDownCapture: keyboardDelegate.getKeyForSearch ? onKeyDown : undefined | ||
} | ||
@@ -64,0 +66,0 @@ }; |
@@ -21,3 +21,3 @@ import {useRef as $dAE4Y$useRef} from "react"; | ||
search: '', | ||
timeout: null | ||
timeout: undefined | ||
}).current; | ||
@@ -36,10 +36,12 @@ let onKeyDown = (e)=>{ | ||
state.search += character; | ||
// Use the delegate to find a key to focus. | ||
// Prioritize items after the currently focused item, falling back to searching the whole list. | ||
let key = keyboardDelegate.getKeyForSearch(state.search, selectionManager.focusedKey); | ||
// If no key found, search from the top. | ||
if (key == null) key = keyboardDelegate.getKeyForSearch(state.search); | ||
if (key != null) { | ||
selectionManager.setFocusedKey(key); | ||
if (onTypeSelect) onTypeSelect(key); | ||
if (keyboardDelegate.getKeyForSearch != null) { | ||
// Use the delegate to find a key to focus. | ||
// Prioritize items after the currently focused item, falling back to searching the whole list. | ||
let key = keyboardDelegate.getKeyForSearch(state.search, selectionManager.focusedKey); | ||
// If no key found, search from the top. | ||
if (key == null) key = keyboardDelegate.getKeyForSearch(state.search); | ||
if (key != null) { | ||
selectionManager.setFocusedKey(key); | ||
if (onTypeSelect) onTypeSelect(key); | ||
} | ||
} | ||
@@ -55,3 +57,3 @@ clearTimeout(state.timeout); | ||
// other hooks in order to handle the Spacebar event. | ||
onKeyDownCapture: keyboardDelegate.getKeyForSearch ? onKeyDown : null | ||
onKeyDownCapture: keyboardDelegate.getKeyForSearch ? onKeyDown : undefined | ||
} | ||
@@ -58,0 +60,0 @@ }; |
@@ -9,3 +9,5 @@ var $gP8Dl$reactariautils = require("@react-aria/utils"); | ||
$parcel$export(module.exports, "isNonContiguousSelectionModifier", () => $ee0bdf4faa47f2a8$export$d3e3bd3e26688c04); | ||
$parcel$export(module.exports, "isCtrlKeyPressed", () => $ee0bdf4faa47f2a8$export$16792effe837dba3); | ||
$parcel$export(module.exports, "getItemElement", () => $ee0bdf4faa47f2a8$export$c3d8340acf92597f); | ||
$parcel$export(module.exports, "useCollectionId", () => $ee0bdf4faa47f2a8$export$881eb0d9f3605d9d); | ||
$parcel$export(module.exports, "getCollectionId", () => $ee0bdf4faa47f2a8$export$6aeb1680a0ae8741); | ||
/* | ||
@@ -27,8 +29,20 @@ * Copyright 2020 Adobe. All rights reserved. | ||
} | ||
function $ee0bdf4faa47f2a8$export$16792effe837dba3(e) { | ||
if ((0, $gP8Dl$reactariautils.isMac)()) return e.metaKey; | ||
return e.ctrlKey; | ||
function $ee0bdf4faa47f2a8$export$c3d8340acf92597f(collectionRef, key) { | ||
var _collectionRef_current, _collectionRef_current1; | ||
let selector = `[data-key="${CSS.escape(String(key))}"]`; | ||
let collection = (_collectionRef_current = collectionRef.current) === null || _collectionRef_current === void 0 ? void 0 : _collectionRef_current.dataset.collection; | ||
if (collection) selector = `[data-collection="${CSS.escape(collection)}"]${selector}`; | ||
return (_collectionRef_current1 = collectionRef.current) === null || _collectionRef_current1 === void 0 ? void 0 : _collectionRef_current1.querySelector(selector); | ||
} | ||
const $ee0bdf4faa47f2a8$var$collectionMap = new WeakMap(); | ||
function $ee0bdf4faa47f2a8$export$881eb0d9f3605d9d(collection) { | ||
let id = (0, $gP8Dl$reactariautils.useId)(); | ||
$ee0bdf4faa47f2a8$var$collectionMap.set(collection, id); | ||
return id; | ||
} | ||
function $ee0bdf4faa47f2a8$export$6aeb1680a0ae8741(collection) { | ||
return $ee0bdf4faa47f2a8$var$collectionMap.get(collection); | ||
} | ||
//# sourceMappingURL=utils.main.js.map |
@@ -1,2 +0,2 @@ | ||
import {isAppleDevice as $jUnAJ$isAppleDevice, isMac as $jUnAJ$isMac} from "@react-aria/utils"; | ||
import {isAppleDevice as $jUnAJ$isAppleDevice, useId as $jUnAJ$useId} from "@react-aria/utils"; | ||
@@ -19,9 +19,21 @@ /* | ||
} | ||
function $feb5ffebff200149$export$16792effe837dba3(e) { | ||
if ((0, $jUnAJ$isMac)()) return e.metaKey; | ||
return e.ctrlKey; | ||
function $feb5ffebff200149$export$c3d8340acf92597f(collectionRef, key) { | ||
var _collectionRef_current, _collectionRef_current1; | ||
let selector = `[data-key="${CSS.escape(String(key))}"]`; | ||
let collection = (_collectionRef_current = collectionRef.current) === null || _collectionRef_current === void 0 ? void 0 : _collectionRef_current.dataset.collection; | ||
if (collection) selector = `[data-collection="${CSS.escape(collection)}"]${selector}`; | ||
return (_collectionRef_current1 = collectionRef.current) === null || _collectionRef_current1 === void 0 ? void 0 : _collectionRef_current1.querySelector(selector); | ||
} | ||
const $feb5ffebff200149$var$collectionMap = new WeakMap(); | ||
function $feb5ffebff200149$export$881eb0d9f3605d9d(collection) { | ||
let id = (0, $jUnAJ$useId)(); | ||
$feb5ffebff200149$var$collectionMap.set(collection, id); | ||
return id; | ||
} | ||
function $feb5ffebff200149$export$6aeb1680a0ae8741(collection) { | ||
return $feb5ffebff200149$var$collectionMap.get(collection); | ||
} | ||
export {$feb5ffebff200149$export$d3e3bd3e26688c04 as isNonContiguousSelectionModifier, $feb5ffebff200149$export$16792effe837dba3 as isCtrlKeyPressed}; | ||
export {$feb5ffebff200149$export$d3e3bd3e26688c04 as isNonContiguousSelectionModifier, $feb5ffebff200149$export$c3d8340acf92597f as getItemElement, $feb5ffebff200149$export$881eb0d9f3605d9d as useCollectionId, $feb5ffebff200149$export$6aeb1680a0ae8741 as getCollectionId}; | ||
//# sourceMappingURL=utils.module.js.map |
{ | ||
"name": "@react-aria/selection", | ||
"version": "3.0.0-nightly-0ddbe6f95-241021", | ||
"version": "3.0.0-nightly-0ef9421d8-250514", | ||
"description": "Spectrum UI components in React", | ||
@@ -25,18 +25,17 @@ "license": "Apache-2.0", | ||
"dependencies": { | ||
"@react-aria/focus": "^3.0.0-nightly-0ddbe6f95-241021", | ||
"@react-aria/i18n": "^3.0.0-nightly-0ddbe6f95-241021", | ||
"@react-aria/interactions": "^3.0.0-nightly-0ddbe6f95-241021", | ||
"@react-aria/utils": "^3.0.0-nightly-0ddbe6f95-241021", | ||
"@react-stately/selection": "^3.0.0-nightly-0ddbe6f95-241021", | ||
"@react-types/shared": "^3.0.0-nightly-0ddbe6f95-241021", | ||
"@react-aria/focus": "3.0.0-nightly-0ef9421d8-250514", | ||
"@react-aria/i18n": "3.0.0-nightly-0ef9421d8-250514", | ||
"@react-aria/interactions": "3.0.0-nightly-0ef9421d8-250514", | ||
"@react-aria/utils": "3.0.0-nightly-0ef9421d8-250514", | ||
"@react-stately/selection": "3.0.0-nightly-0ef9421d8-250514", | ||
"@react-types/shared": "3.0.0-nightly-0ef9421d8-250514", | ||
"@swc/helpers": "^0.5.0" | ||
}, | ||
"peerDependencies": { | ||
"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" | ||
"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" | ||
}, | ||
"stableVersion": "3.20.1" | ||
} | ||
} |
@@ -13,8 +13,9 @@ /* | ||
import {getItemElement} from './utils'; | ||
import {Key, LayoutDelegate, Rect, RefObject, Size} from '@react-types/shared'; | ||
export class DOMLayoutDelegate implements LayoutDelegate { | ||
private ref: RefObject<HTMLElement>; | ||
private ref: RefObject<HTMLElement | null>; | ||
constructor(ref: RefObject<HTMLElement>) { | ||
constructor(ref: RefObject<HTMLElement | null>) { | ||
this.ref = ref; | ||
@@ -25,3 +26,6 @@ } | ||
let container = this.ref.current; | ||
let item = key != null ? container.querySelector(`[data-key="${CSS.escape(key.toString())}"]`) : null; | ||
if (!container) { | ||
return null; | ||
} | ||
let item = key != null ? getItemElement(this.ref, key) : null; | ||
if (!item) { | ||
@@ -45,4 +49,4 @@ return null; | ||
return { | ||
width: container.scrollWidth, | ||
height: container.scrollHeight | ||
width: container?.scrollWidth ?? 0, | ||
height: container?.scrollHeight ?? 0 | ||
}; | ||
@@ -54,8 +58,8 @@ } | ||
return { | ||
x: container.scrollLeft, | ||
y: container.scrollTop, | ||
width: container.offsetWidth, | ||
height: container.offsetHeight | ||
x: container?.scrollLeft ?? 0, | ||
y: container?.scrollTop ?? 0, | ||
width: container?.offsetWidth ?? 0, | ||
height: container?.offsetHeight ?? 0 | ||
}; | ||
} | ||
} |
@@ -77,10 +77,11 @@ /* | ||
private findNextNonDisabled(key: Key, getNext: (key: Key) => Key | null): Key | null { | ||
while (key != null) { | ||
let item = this.collection.getItem(key); | ||
private findNextNonDisabled(key: Key | null, getNext: (key: Key) => Key | null): Key | null { | ||
let nextKey = key; | ||
while (nextKey != null) { | ||
let item = this.collection.getItem(nextKey); | ||
if (item?.type === 'item' && !this.isDisabled(item)) { | ||
return key; | ||
return nextKey; | ||
} | ||
key = getNext(key); | ||
nextKey = getNext(nextKey); | ||
} | ||
@@ -91,10 +92,12 @@ | ||
getNextKey(key: Key) { | ||
key = this.collection.getKeyAfter(key); | ||
return this.findNextNonDisabled(key, key => this.collection.getKeyAfter(key)); | ||
getNextKey(key: Key): Key | null { | ||
let nextKey: Key | null = key; | ||
nextKey = this.collection.getKeyAfter(nextKey); | ||
return this.findNextNonDisabled(nextKey, key => this.collection.getKeyAfter(key)); | ||
} | ||
getPreviousKey(key: Key) { | ||
key = this.collection.getKeyBefore(key); | ||
return this.findNextNonDisabled(key, key => this.collection.getKeyBefore(key)); | ||
getPreviousKey(key: Key): Key | null { | ||
let nextKey: Key | null = key; | ||
nextKey = this.collection.getKeyBefore(nextKey); | ||
return this.findNextNonDisabled(nextKey, key => this.collection.getKeyBefore(key)); | ||
} | ||
@@ -104,7 +107,8 @@ | ||
key: Key, | ||
nextKey: (key: Key) => Key, | ||
nextKey: (key: Key) => Key | null, | ||
shouldSkip: (prevRect: Rect, itemRect: Rect) => boolean | ||
) { | ||
let itemRect = this.layoutDelegate.getItemRect(key); | ||
if (!itemRect) { | ||
let tempKey: Key | null = key; | ||
let itemRect = this.layoutDelegate.getItemRect(tempKey); | ||
if (!itemRect || tempKey == null) { | ||
return null; | ||
@@ -116,7 +120,10 @@ } | ||
do { | ||
key = nextKey(key); | ||
itemRect = this.layoutDelegate.getItemRect(key); | ||
} while (itemRect && shouldSkip(prevRect, itemRect)); | ||
tempKey = nextKey(tempKey); | ||
if (tempKey == null) { | ||
break; | ||
} | ||
itemRect = this.layoutDelegate.getItemRect(tempKey); | ||
} while (itemRect && shouldSkip(prevRect, itemRect) && tempKey != null); | ||
return key; | ||
return tempKey; | ||
} | ||
@@ -132,3 +139,3 @@ | ||
getKeyBelow(key: Key) { | ||
getKeyBelow(key: Key): Key | null { | ||
if (this.layout === 'grid' && this.orientation === 'vertical') { | ||
@@ -141,3 +148,3 @@ return this.findKey(key, (key) => this.getNextKey(key), this.isSameRow); | ||
getKeyAbove(key: Key) { | ||
getKeyAbove(key: Key): Key | null { | ||
if (this.layout === 'grid' && this.orientation === 'vertical') { | ||
@@ -154,3 +161,3 @@ return this.findKey(key, (key) => this.getPreviousKey(key), this.isSameRow); | ||
getKeyRightOf(key: Key) { | ||
getKeyRightOf?(key: Key): Key | null { | ||
// This is a temporary solution for CardView until we refactor useSelectableCollection. | ||
@@ -177,3 +184,3 @@ // https://github.com/orgs/adobe/projects/19/views/32?pane=issue&itemId=77825042 | ||
getKeyLeftOf(key: Key) { | ||
getKeyLeftOf?(key: Key): Key | null { | ||
let layoutDelegateMethod = this.direction === 'ltr' ? 'getKeyLeftOf' : 'getKeyRightOf'; | ||
@@ -198,3 +205,3 @@ if (this.layoutDelegate[layoutDelegateMethod]) { | ||
getFirstKey() { | ||
getFirstKey(): Key | null { | ||
let key = this.collection.getFirstKey(); | ||
@@ -204,3 +211,3 @@ return this.findNextNonDisabled(key, key => this.collection.getKeyAfter(key)); | ||
getLastKey() { | ||
getLastKey(): Key | null { | ||
let key = this.collection.getLastKey(); | ||
@@ -210,3 +217,3 @@ return this.findNextNonDisabled(key, key => this.collection.getKeyBefore(key)); | ||
getKeyPageAbove(key: Key) { | ||
getKeyPageAbove(key: Key): Key | null { | ||
let menu = this.ref.current; | ||
@@ -218,12 +225,13 @@ let itemRect = this.layoutDelegate.getItemRect(key); | ||
if (!isScrollable(menu)) { | ||
if (menu && !isScrollable(menu)) { | ||
return this.getFirstKey(); | ||
} | ||
let nextKey: Key | null = key; | ||
if (this.orientation === 'horizontal') { | ||
let pageX = Math.max(0, itemRect.x + itemRect.width - this.layoutDelegate.getVisibleRect().width); | ||
while (itemRect && itemRect.x > pageX) { | ||
key = this.getKeyAbove(key); | ||
itemRect = key == null ? null : this.layoutDelegate.getItemRect(key); | ||
while (itemRect && itemRect.x > pageX && nextKey != null) { | ||
nextKey = this.getKeyAbove(nextKey); | ||
itemRect = nextKey == null ? null : this.layoutDelegate.getItemRect(nextKey); | ||
} | ||
@@ -233,12 +241,12 @@ } else { | ||
while (itemRect && itemRect.y > pageY) { | ||
key = this.getKeyAbove(key); | ||
itemRect = key == null ? null : this.layoutDelegate.getItemRect(key); | ||
while (itemRect && itemRect.y > pageY && nextKey != null) { | ||
nextKey = this.getKeyAbove(nextKey); | ||
itemRect = nextKey == null ? null : this.layoutDelegate.getItemRect(nextKey); | ||
} | ||
} | ||
return key ?? this.getFirstKey(); | ||
return nextKey ?? this.getFirstKey(); | ||
} | ||
getKeyPageBelow(key: Key) { | ||
getKeyPageBelow(key: Key): Key | null { | ||
let menu = this.ref.current; | ||
@@ -250,12 +258,13 @@ let itemRect = this.layoutDelegate.getItemRect(key); | ||
if (!isScrollable(menu)) { | ||
if (menu && !isScrollable(menu)) { | ||
return this.getLastKey(); | ||
} | ||
let nextKey: Key | null = key; | ||
if (this.orientation === 'horizontal') { | ||
let pageX = Math.min(this.layoutDelegate.getContentSize().width, itemRect.y - itemRect.width + this.layoutDelegate.getVisibleRect().width); | ||
while (itemRect && itemRect.x < pageX) { | ||
key = this.getKeyBelow(key); | ||
itemRect = key == null ? null : this.layoutDelegate.getItemRect(key); | ||
while (itemRect && itemRect.x < pageX && nextKey != null) { | ||
nextKey = this.getKeyBelow(nextKey); | ||
itemRect = nextKey == null ? null : this.layoutDelegate.getItemRect(nextKey); | ||
} | ||
@@ -265,12 +274,12 @@ } else { | ||
while (itemRect && itemRect.y < pageY) { | ||
key = this.getKeyBelow(key); | ||
itemRect = key == null ? null : this.layoutDelegate.getItemRect(key); | ||
while (itemRect && itemRect.y < pageY && nextKey != null) { | ||
nextKey = this.getKeyBelow(nextKey); | ||
itemRect = nextKey == null ? null : this.layoutDelegate.getItemRect(nextKey); | ||
} | ||
} | ||
return key ?? this.getLastKey(); | ||
return nextKey ?? this.getLastKey(); | ||
} | ||
getKeyForSearch(search: string, fromKey?: Key) { | ||
getKeyForSearch(search: string, fromKey?: Key): Key | null { | ||
if (!this.collator) { | ||
@@ -284,2 +293,5 @@ return null; | ||
let item = collection.getItem(key); | ||
if (!item) { | ||
return null; | ||
} | ||
let substring = item.textValue.slice(0, search.length); | ||
@@ -286,0 +298,0 @@ if (item.textValue && this.collator.compare(substring, search) === 0) { |
@@ -13,9 +13,9 @@ /* | ||
import {CLEAR_FOCUS_EVENT, FOCUS_EVENT, focusWithoutScrolling, isCtrlKeyPressed, mergeProps, scrollIntoView, scrollIntoViewport, useEffectEvent, useEvent, useRouter, useUpdateLayoutEffect} from '@react-aria/utils'; | ||
import {DOMAttributes, FocusableElement, FocusStrategy, Key, KeyboardDelegate, RefObject} from '@react-types/shared'; | ||
import {flushSync} from 'react-dom'; | ||
import {FocusEvent, KeyboardEvent, useEffect, useRef} from 'react'; | ||
import {focusSafely, getFocusableTreeWalker} from '@react-aria/focus'; | ||
import {focusWithoutScrolling, mergeProps, scrollIntoView, scrollIntoViewport, useEvent, useRouter} from '@react-aria/utils'; | ||
import {getInteractionModality} from '@react-aria/interactions'; | ||
import {isCtrlKeyPressed, isNonContiguousSelectionModifier} from './utils'; | ||
import {focusSafely, getInteractionModality} from '@react-aria/interactions'; | ||
import {getFocusableTreeWalker, moveVirtualFocus} from '@react-aria/focus'; | ||
import {getItemElement, isNonContiguousSelectionModifier, useCollectionId} from './utils'; | ||
import {MultipleSelectionManager} from '@react-stately/selection'; | ||
@@ -59,2 +59,7 @@ import {useLocale} from '@react-aria/i18n'; | ||
/** | ||
* Whether pressing the Escape should clear selection in the collection or not. | ||
* @default 'clearSelection' | ||
*/ | ||
escapeKeyBehavior?: 'clearSelection' | 'none', | ||
/** | ||
* Whether selection should occur automatically on focus. | ||
@@ -113,2 +118,3 @@ * @default false | ||
disallowSelectAll = false, | ||
escapeKeyBehavior = 'clearSelection', | ||
selectOnFocus = manager.selectionBehavior === 'replace', | ||
@@ -134,3 +140,3 @@ disallowTypeAhead = false, | ||
// for elements outside the collection (e.g. menus). | ||
if (!ref.current.contains(e.target as Element)) { | ||
if (!ref.current?.contains(e.target as Element)) { | ||
return; | ||
@@ -147,5 +153,7 @@ } | ||
let item = scrollRef.current.querySelector(`[data-key="${CSS.escape(key.toString())}"]`); | ||
let item = getItemElement(ref, key); | ||
let itemProps = manager.getItemProps(key); | ||
router.open(item, e, itemProps.href, itemProps.routerOptions); | ||
if (item) { | ||
router.open(item, e, itemProps.href, itemProps.routerOptions); | ||
} | ||
@@ -202,3 +210,3 @@ return; | ||
if (delegate.getKeyLeftOf) { | ||
let nextKey = delegate.getKeyLeftOf?.(manager.focusedKey); | ||
let nextKey: Key | undefined | null = manager.focusedKey != null ? delegate.getKeyLeftOf?.(manager.focusedKey) : null; | ||
if (nextKey == null && shouldFocusWrap) { | ||
@@ -216,3 +224,3 @@ nextKey = direction === 'rtl' ? delegate.getFirstKey?.(manager.focusedKey) : delegate.getLastKey?.(manager.focusedKey); | ||
if (delegate.getKeyRightOf) { | ||
let nextKey = delegate.getKeyRightOf?.(manager.focusedKey); | ||
let nextKey: Key | undefined | null = manager.focusedKey != null ? delegate.getKeyRightOf?.(manager.focusedKey) : null; | ||
if (nextKey == null && shouldFocusWrap) { | ||
@@ -230,9 +238,14 @@ nextKey = direction === 'rtl' ? delegate.getLastKey?.(manager.focusedKey) : delegate.getFirstKey?.(manager.focusedKey); | ||
if (delegate.getFirstKey) { | ||
if (manager.focusedKey === null && e.shiftKey) { | ||
return; | ||
} | ||
e.preventDefault(); | ||
let firstKey = delegate.getFirstKey(manager.focusedKey, isCtrlKeyPressed(e)); | ||
let firstKey: Key | null = delegate.getFirstKey(manager.focusedKey, isCtrlKeyPressed(e)); | ||
manager.setFocusedKey(firstKey); | ||
if (isCtrlKeyPressed(e) && e.shiftKey && manager.selectionMode === 'multiple') { | ||
manager.extendSelection(firstKey); | ||
} else if (selectOnFocus) { | ||
manager.replaceSelection(firstKey); | ||
if (firstKey != null) { | ||
if (isCtrlKeyPressed(e) && e.shiftKey && manager.selectionMode === 'multiple') { | ||
manager.extendSelection(firstKey); | ||
} else if (selectOnFocus) { | ||
manager.replaceSelection(firstKey); | ||
} | ||
} | ||
@@ -243,9 +256,14 @@ } | ||
if (delegate.getLastKey) { | ||
if (manager.focusedKey === null && e.shiftKey) { | ||
return; | ||
} | ||
e.preventDefault(); | ||
let lastKey = delegate.getLastKey(manager.focusedKey, isCtrlKeyPressed(e)); | ||
manager.setFocusedKey(lastKey); | ||
if (isCtrlKeyPressed(e) && e.shiftKey && manager.selectionMode === 'multiple') { | ||
manager.extendSelection(lastKey); | ||
} else if (selectOnFocus) { | ||
manager.replaceSelection(lastKey); | ||
if (lastKey != null) { | ||
if (isCtrlKeyPressed(e) && e.shiftKey && manager.selectionMode === 'multiple') { | ||
manager.extendSelection(lastKey); | ||
} else if (selectOnFocus) { | ||
manager.replaceSelection(lastKey); | ||
} | ||
} | ||
@@ -255,3 +273,3 @@ } | ||
case 'PageDown': | ||
if (delegate.getKeyPageBelow) { | ||
if (delegate.getKeyPageBelow && manager.focusedKey != null) { | ||
let nextKey = delegate.getKeyPageBelow(manager.focusedKey); | ||
@@ -265,3 +283,3 @@ if (nextKey != null) { | ||
case 'PageUp': | ||
if (delegate.getKeyPageAbove) { | ||
if (delegate.getKeyPageAbove && manager.focusedKey != null) { | ||
let nextKey = delegate.getKeyPageAbove(manager.focusedKey); | ||
@@ -281,3 +299,3 @@ if (nextKey != null) { | ||
case 'Escape': | ||
if (!disallowEmptySelection && manager.selectedKeys.size !== 0) { | ||
if (escapeKeyBehavior === 'clearSelection' && !disallowEmptySelection && manager.selectedKeys.size !== 0) { | ||
e.stopPropagation(); | ||
@@ -300,3 +318,3 @@ e.preventDefault(); | ||
let walker = getFocusableTreeWalker(ref.current, {tabbable: true}); | ||
let next: FocusableElement; | ||
let next: FocusableElement | undefined = undefined; | ||
let last: FocusableElement; | ||
@@ -323,6 +341,6 @@ do { | ||
let scrollPos = useRef({top: 0, left: 0}); | ||
useEvent(scrollRef, 'scroll', isVirtualized ? null : () => { | ||
useEvent(scrollRef, 'scroll', isVirtualized ? undefined : () => { | ||
scrollPos.current = { | ||
top: scrollRef.current.scrollTop, | ||
left: scrollRef.current.scrollLeft | ||
top: scrollRef.current?.scrollTop ?? 0, | ||
left: scrollRef.current?.scrollLeft ?? 0 | ||
}; | ||
@@ -347,8 +365,7 @@ }); | ||
manager.setFocused(true); | ||
if (manager.focusedKey == null) { | ||
let navigateToFirstKey = (key: Key | undefined) => { | ||
let navigateToKey = (key: Key | undefined | null) => { | ||
if (key != null) { | ||
manager.setFocusedKey(key); | ||
if (selectOnFocus) { | ||
if (selectOnFocus && !manager.isSelected(key)) { | ||
manager.replaceSelection(key); | ||
@@ -363,7 +380,7 @@ } | ||
if (relatedTarget && (e.currentTarget.compareDocumentPosition(relatedTarget) & Node.DOCUMENT_POSITION_FOLLOWING)) { | ||
navigateToFirstKey(manager.lastSelectedKey ?? delegate.getLastKey()); | ||
navigateToKey(manager.lastSelectedKey ?? delegate.getLastKey?.()); | ||
} else { | ||
navigateToFirstKey(manager.firstSelectedKey ?? delegate.getFirstKey()); | ||
navigateToKey(manager.firstSelectedKey ?? delegate.getFirstKey?.()); | ||
} | ||
} else if (!isVirtualized) { | ||
} else if (!isVirtualized && scrollRef.current) { | ||
// Restore the scroll position to what it was before. | ||
@@ -374,8 +391,8 @@ scrollRef.current.scrollTop = scrollPos.current.top; | ||
if (manager.focusedKey != null) { | ||
if (manager.focusedKey != null && scrollRef.current) { | ||
// Refocus and scroll the focused item into view if it exists within the scrollable region. | ||
let element = scrollRef.current.querySelector(`[data-key="${CSS.escape(manager.focusedKey.toString())}"]`) as HTMLElement; | ||
if (element) { | ||
let element = getItemElement(ref, manager.focusedKey); | ||
if (element instanceof HTMLElement) { | ||
// This prevents a flash of focus on the first/last element in the collection, or the collection itself. | ||
if (!element.contains(document.activeElement)) { | ||
if (!element.contains(document.activeElement) && !shouldUseVirtualFocus) { | ||
focusWithoutScrolling(element); | ||
@@ -399,12 +416,80 @@ } | ||
// Ref to track whether the first item in the collection should be automatically focused. Specifically used for autocomplete when user types | ||
// to focus the first key AFTER the collection updates. | ||
// TODO: potentially expand the usage of this | ||
let shouldVirtualFocusFirst = useRef(false); | ||
// Add event listeners for custom virtual events. These handle updating the focused key in response to various keyboard events | ||
// at the autocomplete level | ||
// TODO: fix type later | ||
useEvent(ref, FOCUS_EVENT, !shouldUseVirtualFocus ? undefined : (e: any) => { | ||
let {detail} = e; | ||
e.stopPropagation(); | ||
manager.setFocused(true); | ||
// If the user is typing forwards, autofocus the first option in the list. | ||
if (detail?.focusStrategy === 'first') { | ||
shouldVirtualFocusFirst.current = true; | ||
} | ||
}); | ||
let updateActiveDescendant = useEffectEvent(() => { | ||
let keyToFocus = delegate.getFirstKey?.() ?? null; | ||
// If no focusable items exist in the list, make sure to clear any activedescendant that may still exist | ||
if (keyToFocus == null) { | ||
moveVirtualFocus(ref.current); | ||
// If there wasn't a focusable key but the collection had items, then that means we aren't in an intermediate load state and all keys are disabled. | ||
// Reset shouldVirtualFocusFirst so that we don't erronously autofocus an item when the collection is filtered again. | ||
if (manager.collection.size > 0) { | ||
shouldVirtualFocusFirst.current = false; | ||
} | ||
} else { | ||
manager.setFocusedKey(keyToFocus); | ||
// Only set shouldVirtualFocusFirst to false if we've successfully set the first key as the focused key | ||
// If there wasn't a key to focus, we might be in a temporary loading state so we'll want to still focus the first key | ||
// after the collection updates after load | ||
shouldVirtualFocusFirst.current = false; | ||
} | ||
}); | ||
useUpdateLayoutEffect(() => { | ||
if (shouldVirtualFocusFirst.current) { | ||
updateActiveDescendant(); | ||
} | ||
}, [manager.collection, updateActiveDescendant]); | ||
let resetFocusFirstFlag = useEffectEvent(() => { | ||
// If user causes the focused key to change in any other way, clear shouldVirtualFocusFirst so we don't | ||
// accidentally move focus from under them. Skip this if the collection was empty because we might be in a load | ||
// state and will still want to focus the first item after load | ||
if (manager.collection.size > 0) { | ||
shouldVirtualFocusFirst.current = false; | ||
} | ||
}); | ||
useUpdateLayoutEffect(() => { | ||
resetFocusFirstFlag(); | ||
}, [manager.focusedKey, resetFocusFirstFlag]); | ||
useEvent(ref, CLEAR_FOCUS_EVENT, !shouldUseVirtualFocus ? undefined : (e: any) => { | ||
e.stopPropagation(); | ||
manager.setFocused(false); | ||
if (e.detail?.clearFocusKey) { | ||
manager.setFocusedKey(null); | ||
} | ||
}); | ||
const autoFocusRef = useRef(autoFocus); | ||
const didAutoFocusRef = useRef(false); | ||
useEffect(() => { | ||
if (autoFocusRef.current) { | ||
let focusedKey = null; | ||
let focusedKey: Key | null = null; | ||
// Check focus strategy to determine which item to focus | ||
if (autoFocus === 'first') { | ||
focusedKey = delegate.getFirstKey(); | ||
focusedKey = delegate.getFirstKey?.() ?? null; | ||
} if (autoFocus === 'last') { | ||
focusedKey = delegate.getLastKey(); | ||
focusedKey = delegate.getLastKey?.() ?? null; | ||
} | ||
@@ -427,16 +512,22 @@ | ||
// If no default focus key is selected, focus the collection itself. | ||
if (focusedKey == null && !shouldUseVirtualFocus) { | ||
if (focusedKey == null && !shouldUseVirtualFocus && ref.current) { | ||
focusSafely(ref.current); | ||
} | ||
// Wait until the collection has items to autofocus. | ||
if (manager.collection.size > 0) { | ||
autoFocusRef.current = false; | ||
didAutoFocusRef.current = true; | ||
} | ||
} | ||
// eslint-disable-next-line react-hooks/exhaustive-deps | ||
}, []); | ||
}); | ||
// Scroll the focused element into view when the focusedKey changes. | ||
let lastFocusedKey = useRef(manager.focusedKey); | ||
let raf = useRef<number | null>(null); | ||
useEffect(() => { | ||
if (manager.isFocused && manager.focusedKey != null && (manager.focusedKey !== lastFocusedKey.current || autoFocusRef.current) && scrollRef?.current) { | ||
if (manager.isFocused && manager.focusedKey != null && (manager.focusedKey !== lastFocusedKey.current || didAutoFocusRef.current) && scrollRef.current && ref.current) { | ||
let modality = getInteractionModality(); | ||
let element = ref.current.querySelector(`[data-key="${CSS.escape(manager.focusedKey.toString())}"]`) as HTMLElement; | ||
if (!element) { | ||
let element = getItemElement(ref, manager.focusedKey); | ||
if (!(element instanceof HTMLElement)) { | ||
// If item element wasn't found, return early (don't update autoFocusRef and lastFocusedKey). | ||
@@ -447,5 +538,13 @@ // The collection may initially be empty (e.g. virtualizer), so wait until the element exists. | ||
if (modality === 'keyboard' || autoFocusRef.current) { | ||
scrollIntoView(scrollRef.current, element); | ||
if (modality === 'keyboard' || didAutoFocusRef.current) { | ||
if (raf.current) { | ||
cancelAnimationFrame(raf.current); | ||
} | ||
raf.current = requestAnimationFrame(() => { | ||
if (scrollRef.current) { | ||
scrollIntoView(scrollRef.current, element); | ||
} | ||
}); | ||
// Avoid scroll in iOS VO, since it may cause overlay to close (i.e. RAC submenu) | ||
@@ -459,3 +558,3 @@ if (modality !== 'virtual') { | ||
// If the focused key becomes null (e.g. the last item is deleted), focus the whole collection. | ||
if (!shouldUseVirtualFocus && manager.isFocused && manager.focusedKey == null && lastFocusedKey.current != null) { | ||
if (!shouldUseVirtualFocus && manager.isFocused && manager.focusedKey == null && lastFocusedKey.current != null && ref.current) { | ||
focusSafely(ref.current); | ||
@@ -465,5 +564,13 @@ } | ||
lastFocusedKey.current = manager.focusedKey; | ||
autoFocusRef.current = false; | ||
didAutoFocusRef.current = false; | ||
}); | ||
useEffect(() => { | ||
return () => { | ||
if (raf.current) { | ||
cancelAnimationFrame(raf.current); | ||
} | ||
}; | ||
}, []); | ||
// Intercept FocusScope restoration since virtualized collections can reuse DOM nodes. | ||
@@ -499,5 +606,3 @@ useEvent(ref, 'react-aria-focus-scope-restore', e => { | ||
// This will be marshalled to either the first or last item depending on where focus came from. | ||
// If using virtual focus, don't set a tabIndex at all so that VoiceOver on iOS 14 doesn't try | ||
// to move real DOM focus to the element anyway. | ||
let tabIndex: number; | ||
let tabIndex: number | undefined = undefined; | ||
if (!shouldUseVirtualFocus) { | ||
@@ -507,8 +612,9 @@ tabIndex = manager.focusedKey == null ? 0 : -1; | ||
let collectionId = useCollectionId(manager.collection); | ||
return { | ||
collectionProps: { | ||
...handlers, | ||
tabIndex | ||
} | ||
collectionProps: mergeProps(handlers, { | ||
tabIndex, | ||
'data-collection': collectionId | ||
}) | ||
}; | ||
} |
@@ -13,11 +13,11 @@ /* | ||
import {DOMAttributes, FocusableElement, Key, LongPressEvent, PressEvent, RefObject} from '@react-types/shared'; | ||
import {focusSafely} from '@react-aria/focus'; | ||
import {isCtrlKeyPressed, isNonContiguousSelectionModifier} from './utils'; | ||
import {mergeProps, openLink, useRouter} from '@react-aria/utils'; | ||
import {DOMAttributes, DOMProps, FocusableElement, Key, LongPressEvent, PointerType, PressEvent, RefObject} from '@react-types/shared'; | ||
import {focusSafely, PressHookProps, useLongPress, usePress} from '@react-aria/interactions'; | ||
import {getCollectionId, isNonContiguousSelectionModifier} from './utils'; | ||
import {isCtrlKeyPressed, mergeProps, openLink, useId, useRouter} from '@react-aria/utils'; | ||
import {moveVirtualFocus} from '@react-aria/focus'; | ||
import {MultipleSelectionManager} from '@react-stately/selection'; | ||
import {PressProps, useLongPress, usePress} from '@react-aria/interactions'; | ||
import {useEffect, useRef} from 'react'; | ||
export interface SelectableItemOptions { | ||
export interface SelectableItemOptions extends DOMProps { | ||
/** | ||
@@ -112,2 +112,3 @@ * An interface for reading and updating multiple selection state. | ||
let { | ||
id, | ||
selectionManager: manager, | ||
@@ -125,3 +126,3 @@ key, | ||
let router = useRouter(); | ||
id = useId(id); | ||
let onSelect = (e: PressEvent | LongPressEvent | PointerEvent) => { | ||
@@ -136,3 +137,3 @@ if (e.pointerType === 'keyboard' && isNonContiguousSelectionModifier(e)) { | ||
if (manager.isLink(key)) { | ||
if (linkBehavior === 'selection') { | ||
if (linkBehavior === 'selection' && ref.current) { | ||
let itemProps = manager.getItemProps(key); | ||
@@ -166,9 +167,16 @@ router.open(ref.current, e, itemProps.href, itemProps.routerOptions); | ||
// Focus the associated DOM node when this item becomes the focusedKey | ||
// TODO: can't make this useLayoutEffect bacause it breaks menus inside dialogs | ||
// However, if this is a useEffect, it runs twice and dispatches two blur events and immediately sets | ||
// aria-activeDescendant in useAutocomplete... I've worked around this for now | ||
useEffect(() => { | ||
let isFocused = key === manager.focusedKey; | ||
if (isFocused && manager.isFocused && !shouldUseVirtualFocus) { | ||
if (focus) { | ||
focus(); | ||
} else if (document.activeElement !== ref.current) { | ||
focusSafely(ref.current); | ||
if (isFocused && manager.isFocused) { | ||
if (!shouldUseVirtualFocus) { | ||
if (focus) { | ||
focus(); | ||
} else if (document.activeElement !== ref.current && ref.current) { | ||
focusSafely(ref.current); | ||
} | ||
} else { | ||
moveVirtualFocus(ref.current); | ||
} | ||
@@ -215,3 +223,3 @@ } | ||
let hasAction = hasPrimaryAction || hasSecondaryAction; | ||
let modality = useRef(null); | ||
let modality = useRef<PointerType | null>(null); | ||
@@ -227,3 +235,3 @@ let longPressEnabled = hasAction && allowsSelection; | ||
if (hasLinkAction) { | ||
if (hasLinkAction && ref.current) { | ||
let itemProps = manager.getItemProps(key); | ||
@@ -241,3 +249,3 @@ router.open(ref.current, e, itemProps.href, itemProps.routerOptions); | ||
// For keyboard events, selection still occurs on key down. | ||
let itemPressProps: PressProps = {}; | ||
let itemPressProps: PressHookProps = {ref}; | ||
if (shouldSelectOnPressUp) { | ||
@@ -252,3 +260,3 @@ itemPressProps.onPressStart = (e) => { | ||
// If allowsDifferentPressOrigin, make selection happen on pressUp (e.g. open menu on press down, selection on menu item happens on press up.) | ||
// If allowsDifferentPressOrigin and interacting with mouse, make selection happen on pressUp (e.g. open menu on press down, selection on menu item happens on press up.) | ||
// Otherwise, have selection happen onPress (prevents listview row selection when clicking on interactable elements in the row) | ||
@@ -268,4 +276,4 @@ if (!allowsDifferentPressOrigin) { | ||
} else { | ||
itemPressProps.onPressUp = hasPrimaryAction ? null : (e) => { | ||
if (e.pointerType !== 'keyboard' && allowsSelection) { | ||
itemPressProps.onPressUp = hasPrimaryAction ? undefined : (e) => { | ||
if (e.pointerType === 'mouse' && allowsSelection) { | ||
onSelect(e); | ||
@@ -275,3 +283,7 @@ } | ||
itemPressProps.onPress = hasPrimaryAction ? performAction : null; | ||
itemPressProps.onPress = hasPrimaryAction ? performAction : (e) => { | ||
if (e.pointerType !== 'keyboard' && e.pointerType !== 'mouse' && allowsSelection) { | ||
onSelect(e); | ||
} | ||
}; | ||
} | ||
@@ -317,4 +329,24 @@ } else { | ||
itemProps['data-collection'] = getCollectionId(manager.collection); | ||
itemProps['data-key'] = key; | ||
itemPressProps.preventFocusOnPress = shouldUseVirtualFocus; | ||
// When using virtual focus, make sure the focused key gets updated on press. | ||
if (shouldUseVirtualFocus) { | ||
itemPressProps = mergeProps(itemPressProps, { | ||
onPressStart(e) { | ||
if (e.pointerType !== 'touch') { | ||
manager.setFocused(true); | ||
manager.setFocusedKey(key); | ||
} | ||
}, | ||
onPress(e) { | ||
if (e.pointerType === 'touch') { | ||
manager.setFocused(true); | ||
manager.setFocusedKey(key); | ||
} | ||
} | ||
}); | ||
} | ||
let {pressProps, isPressed} = usePress(itemPressProps); | ||
@@ -365,5 +397,7 @@ | ||
itemProps, | ||
allowsSelection || hasPrimaryAction ? pressProps : {}, | ||
allowsSelection || hasPrimaryAction || shouldUseVirtualFocus ? pressProps : {}, | ||
longPressEnabled ? longPressProps : {}, | ||
{onDoubleClick, onDragStartCapture, onClick} | ||
{onDoubleClick, onDragStartCapture, onClick, id}, | ||
// Prevent DOM focus from moving on mouse down when using virtual focus | ||
shouldUseVirtualFocus ? {onMouseDown: e => e.preventDefault()} : undefined | ||
), | ||
@@ -370,0 +404,0 @@ isPressed, |
@@ -49,5 +49,5 @@ /* | ||
let {keyboardDelegate, selectionManager, onTypeSelect} = options; | ||
let state = useRef({ | ||
let state = useRef<{search: string, timeout: ReturnType<typeof setTimeout> | undefined}>({ | ||
search: '', | ||
timeout: null | ||
timeout: undefined | ||
}).current; | ||
@@ -74,15 +74,17 @@ | ||
// Use the delegate to find a key to focus. | ||
// Prioritize items after the currently focused item, falling back to searching the whole list. | ||
let key = keyboardDelegate.getKeyForSearch(state.search, selectionManager.focusedKey); | ||
if (keyboardDelegate.getKeyForSearch != null) { | ||
// Use the delegate to find a key to focus. | ||
// Prioritize items after the currently focused item, falling back to searching the whole list. | ||
let key = keyboardDelegate.getKeyForSearch(state.search, selectionManager.focusedKey); | ||
// If no key found, search from the top. | ||
if (key == null) { | ||
key = keyboardDelegate.getKeyForSearch(state.search); | ||
} | ||
// If no key found, search from the top. | ||
if (key == null) { | ||
key = keyboardDelegate.getKeyForSearch(state.search); | ||
} | ||
if (key != null) { | ||
selectionManager.setFocusedKey(key); | ||
if (onTypeSelect) { | ||
onTypeSelect(key); | ||
if (key != null) { | ||
selectionManager.setFocusedKey(key); | ||
if (onTypeSelect) { | ||
onTypeSelect(key); | ||
} | ||
} | ||
@@ -101,3 +103,3 @@ } | ||
// other hooks in order to handle the Spacebar event. | ||
onKeyDownCapture: keyboardDelegate.getKeyForSearch ? onKeyDown : null | ||
onKeyDownCapture: keyboardDelegate.getKeyForSearch ? onKeyDown : undefined | ||
} | ||
@@ -104,0 +106,0 @@ }; |
@@ -13,3 +13,5 @@ /* | ||
import {isAppleDevice, isMac} from '@react-aria/utils'; | ||
import {Collection, Key} from '@react-types/shared'; | ||
import {isAppleDevice, useId} from '@react-aria/utils'; | ||
import {RefObject} from 'react'; | ||
@@ -22,3 +24,3 @@ interface Event { | ||
export function isNonContiguousSelectionModifier(e: Event) { | ||
export function isNonContiguousSelectionModifier(e: Event): boolean { | ||
// Ctrl + Arrow Up/Arrow Down has a system wide meaning on macOS, so use Alt instead. | ||
@@ -29,8 +31,20 @@ // On Windows and Ubuntu, Alt + Space has a system wide meaning. | ||
export function isCtrlKeyPressed(e: Event) { | ||
if (isMac()) { | ||
return e.metaKey; | ||
export function getItemElement(collectionRef: RefObject<HTMLElement | null>, key: Key): Element | null | undefined { | ||
let selector = `[data-key="${CSS.escape(String(key))}"]`; | ||
let collection = collectionRef.current?.dataset.collection; | ||
if (collection) { | ||
selector = `[data-collection="${CSS.escape(collection)}"]${selector}`; | ||
} | ||
return collectionRef.current?.querySelector(selector); | ||
} | ||
return e.ctrlKey; | ||
const collectionMap = new WeakMap<Collection<any>, string>(); | ||
export function useCollectionId(collection: Collection<any>): string { | ||
let id = useId(); | ||
collectionMap.set(collection, id); | ||
return id; | ||
} | ||
export function getCollectionId(collection: Collection<any>): string { | ||
return collectionMap.get(collection)!; | ||
} |
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
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
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
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
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
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
Sorry, the diff of this file is not supported yet
Unidentified License
License(Experimental) Something that seems like a license was found, but its contents could not be matched with a known license.
Found 1 instance in 1 package
437043
16.99%2
-33.33%4873
13.7%+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
Updated
Updated