Files
NapCatQQ/.pnpm-store/v3/files/e3/f9e21bc11e3a63194c112acfc1ec35aca237440b5dd3a4aa7c192b0f621a30f101bd1f519e789f8671bc82d3e408434ba0c99e7f90150b58e2211ae53cc3cb
T
TanTan e311ca3275
Build NapCat Artifacts / Type Check & Test (push) Waiting to run
docs: 仓库链接指向本镜像仓库
2026-11-13 09:25:58 +08:00

292 lines
9.8 KiB
Plaintext

/*
* Copyright 2020 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/
import {Collection, Direction, DisabledBehavior, Key, KeyboardDelegate, LayoutDelegate, Node, Orientation, Rect, RefObject} from '@react-types/shared';
import {DOMLayoutDelegate} from './DOMLayoutDelegate';
import {isScrollable} from '@react-aria/utils';
interface ListKeyboardDelegateOptions<T> {
collection: Collection<Node<T>>,
ref: RefObject<HTMLElement | null>,
collator?: Intl.Collator,
layout?: 'stack' | 'grid',
orientation?: Orientation,
direction?: Direction,
disabledKeys?: Set<Key>,
disabledBehavior?: DisabledBehavior,
layoutDelegate?: LayoutDelegate
}
export class ListKeyboardDelegate<T> implements KeyboardDelegate {
private collection: Collection<Node<T>>;
private disabledKeys: Set<Key>;
private disabledBehavior: DisabledBehavior;
private ref: RefObject<HTMLElement | null>;
private collator: Intl.Collator | undefined;
private layout: 'stack' | 'grid';
private orientation?: Orientation;
private direction?: Direction;
private layoutDelegate: LayoutDelegate;
constructor(collection: Collection<Node<T>>, disabledKeys: Set<Key>, ref: RefObject<HTMLElement | null>, collator?: Intl.Collator);
constructor(options: ListKeyboardDelegateOptions<T>);
constructor(...args: any[]) {
if (args.length === 1) {
let opts = args[0] as ListKeyboardDelegateOptions<T>;
this.collection = opts.collection;
this.ref = opts.ref;
this.collator = opts.collator;
this.disabledKeys = opts.disabledKeys || new Set();
this.disabledBehavior = opts.disabledBehavior || 'all';
this.orientation = opts.orientation || 'vertical';
this.direction = opts.direction;
this.layout = opts.layout || 'stack';
this.layoutDelegate = opts.layoutDelegate || new DOMLayoutDelegate(opts.ref);
} else {
this.collection = args[0];
this.disabledKeys = args[1];
this.ref = args[2];
this.collator = args[3];
this.layout = 'stack';
this.orientation = 'vertical';
this.disabledBehavior = 'all';
this.layoutDelegate = new DOMLayoutDelegate(this.ref);
}
// If this is a vertical stack, remove the left/right methods completely
// so they aren't called by useDroppableCollection.
if (this.layout === 'stack' && this.orientation === 'vertical') {
this.getKeyLeftOf = undefined;
this.getKeyRightOf = undefined;
}
}
private isDisabled(item: Node<unknown>) {
return this.disabledBehavior === 'all' && (item.props?.isDisabled || this.disabledKeys.has(item.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 nextKey;
}
nextKey = getNext(nextKey);
}
return null;
}
getNextKey(key: Key) {
let nextKey: Key | null = key;
nextKey = this.collection.getKeyAfter(nextKey);
return this.findNextNonDisabled(nextKey, key => this.collection.getKeyAfter(key));
}
getPreviousKey(key: Key) {
let nextKey: Key | null = key;
nextKey = this.collection.getKeyBefore(nextKey);
return this.findNextNonDisabled(nextKey, key => this.collection.getKeyBefore(key));
}
private findKey(
key: Key,
nextKey: (key: Key) => Key | null,
shouldSkip: (prevRect: Rect, itemRect: Rect) => boolean
) {
let tempKey: Key | null = 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 {
tempKey = nextKey(tempKey);
if (tempKey == null) {
break;
}
itemRect = this.layoutDelegate.getItemRect(tempKey);
} while (itemRect && shouldSkip(prevRect, itemRect) && tempKey != null);
return tempKey;
}
private isSameRow(prevRect: Rect, itemRect: Rect) {
return prevRect.y === itemRect.y || prevRect.x !== itemRect.x;
}
private isSameColumn(prevRect: Rect, itemRect: Rect) {
return prevRect.x === itemRect.x || prevRect.y !== itemRect.y;
}
getKeyBelow(key: Key) {
if (this.layout === 'grid' && this.orientation === 'vertical') {
return this.findKey(key, (key) => this.getNextKey(key), this.isSameRow);
} else {
return this.getNextKey(key);
}
}
getKeyAbove(key: Key) {
if (this.layout === 'grid' && this.orientation === 'vertical') {
return this.findKey(key, (key) => this.getPreviousKey(key), this.isSameRow);
} else {
return this.getPreviousKey(key);
}
}
private getNextColumn(key: Key, right: boolean) {
return right ? this.getPreviousKey(key) : this.getNextKey(key);
}
getKeyRightOf?(key: Key) {
// This is a temporary solution for CardView until we refactor useSelectableCollection.
// https://github.com/orgs/adobe/projects/19/views/32?pane=issue&itemId=77825042
let layoutDelegateMethod = this.direction === 'ltr' ? 'getKeyRightOf' : 'getKeyLeftOf';
if (this.layoutDelegate[layoutDelegateMethod]) {
key = this.layoutDelegate[layoutDelegateMethod](key);
return this.findNextNonDisabled(key, key => this.layoutDelegate[layoutDelegateMethod](key));
}
if (this.layout === 'grid') {
if (this.orientation === 'vertical') {
return this.getNextColumn(key, this.direction === 'rtl');
} else {
return this.findKey(key, (key) => this.getNextColumn(key, this.direction === 'rtl'), this.isSameColumn);
}
} else if (this.orientation === 'horizontal') {
return this.getNextColumn(key, this.direction === 'rtl');
}
return null;
}
getKeyLeftOf?(key: Key) {
let layoutDelegateMethod = this.direction === 'ltr' ? 'getKeyLeftOf' : 'getKeyRightOf';
if (this.layoutDelegate[layoutDelegateMethod]) {
key = this.layoutDelegate[layoutDelegateMethod](key);
return this.findNextNonDisabled(key, key => this.layoutDelegate[layoutDelegateMethod](key));
}
if (this.layout === 'grid') {
if (this.orientation === 'vertical') {
return this.getNextColumn(key, this.direction === 'ltr');
} else {
return this.findKey(key, (key) => this.getNextColumn(key, this.direction === 'ltr'), this.isSameColumn);
}
} else if (this.orientation === 'horizontal') {
return this.getNextColumn(key, this.direction === 'ltr');
}
return null;
}
getFirstKey() {
let key = this.collection.getFirstKey();
return this.findNextNonDisabled(key, key => this.collection.getKeyAfter(key));
}
getLastKey() {
let key = this.collection.getLastKey();
return this.findNextNonDisabled(key, key => this.collection.getKeyBefore(key));
}
getKeyPageAbove(key: Key) {
let menu = this.ref.current;
let itemRect = this.layoutDelegate.getItemRect(key);
if (!itemRect) {
return null;
}
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 && 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 && nextKey != null) {
nextKey = this.getKeyAbove(nextKey);
itemRect = nextKey == null ? null : this.layoutDelegate.getItemRect(nextKey);
}
}
return nextKey ?? this.getFirstKey();
}
getKeyPageBelow(key: Key) {
let menu = this.ref.current;
let itemRect = this.layoutDelegate.getItemRect(key);
if (!itemRect) {
return null;
}
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 && 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 && nextKey != null) {
nextKey = this.getKeyBelow(nextKey);
itemRect = nextKey == null ? null : this.layoutDelegate.getItemRect(nextKey);
}
}
return nextKey ?? this.getLastKey();
}
getKeyForSearch(search: string, fromKey?: Key) {
if (!this.collator) {
return null;
}
let collection = this.collection;
let key = fromKey || this.getFirstKey();
while (key != null) {
let item = collection.getItem(key);
if (!item) {
return null;
}
let substring = item.textValue.slice(0, search.length);
if (item.textValue && this.collator.compare(substring, search) === 0) {
return key;
}
key = this.getNextKey(key);
}
return null;
}
}