@react-aria/selection 3.18.1 → 3.19.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/DOMLayoutDelegate.main.js +53 -0
- package/dist/DOMLayoutDelegate.main.js.map +1 -0
- package/dist/DOMLayoutDelegate.mjs +48 -0
- package/dist/DOMLayoutDelegate.module.js +48 -0
- package/dist/DOMLayoutDelegate.module.js.map +1 -0
- package/dist/ListKeyboardDelegate.main.js +28 -39
- package/dist/ListKeyboardDelegate.main.js.map +1 -1
- package/dist/ListKeyboardDelegate.mjs +28 -39
- package/dist/ListKeyboardDelegate.module.js +28 -39
- package/dist/ListKeyboardDelegate.module.js.map +1 -1
- package/dist/import.mjs +3 -1
- package/dist/main.js +3 -0
- package/dist/main.js.map +1 -1
- package/dist/module.js +3 -1
- package/dist/module.js.map +1 -1
- package/dist/types.d.ts +19 -7
- package/dist/types.d.ts.map +1 -1
- package/dist/useSelectableCollection.main.js +17 -15
- package/dist/useSelectableCollection.main.js.map +1 -1
- package/dist/useSelectableCollection.mjs +17 -15
- package/dist/useSelectableCollection.module.js +17 -15
- package/dist/useSelectableCollection.module.js.map +1 -1
- package/dist/useSelectableItem.main.js.map +1 -1
- package/dist/useSelectableItem.module.js.map +1 -1
- package/dist/useSelectableList.main.js +4 -2
- package/dist/useSelectableList.main.js.map +1 -1
- package/dist/useSelectableList.mjs +4 -2
- package/dist/useSelectableList.module.js +4 -2
- package/dist/useSelectableList.module.js.map +1 -1
- package/package.json +10 -10
- package/src/DOMLayoutDelegate.ts +57 -0
- package/src/ListKeyboardDelegate.ts +37 -49
- package/src/index.ts +1 -0
- package/src/useSelectableCollection.ts +26 -15
- package/src/useSelectableItem.ts +3 -3
- package/src/useSelectableList.ts +12 -4
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2024 Adobe. All rights reserved.
|
|
3
|
+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*
|
|
7
|
+
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
* governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import {Key, LayoutDelegate, Rect, RefObject, Size} from '@react-types/shared';
|
|
14
|
+
|
|
15
|
+
export class DOMLayoutDelegate implements LayoutDelegate {
|
|
16
|
+
private ref: RefObject<HTMLElement>;
|
|
17
|
+
|
|
18
|
+
constructor(ref: RefObject<HTMLElement>) {
|
|
19
|
+
this.ref = ref;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
getItemRect(key: Key): Rect | null {
|
|
23
|
+
let container = this.ref.current;
|
|
24
|
+
let item = key != null ? container.querySelector(`[data-key="${CSS.escape(key.toString())}"]`) : null;
|
|
25
|
+
if (!item) {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
let containerRect = container.getBoundingClientRect();
|
|
30
|
+
let itemRect = item.getBoundingClientRect();
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
x: itemRect.left - containerRect.left + container.scrollLeft,
|
|
34
|
+
y: itemRect.top - containerRect.top + container.scrollTop,
|
|
35
|
+
width: itemRect.width,
|
|
36
|
+
height: itemRect.height
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
getContentSize(): Size {
|
|
41
|
+
let container = this.ref.current;
|
|
42
|
+
return {
|
|
43
|
+
width: container.scrollWidth,
|
|
44
|
+
height: container.scrollHeight
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
getVisibleRect(): Rect {
|
|
49
|
+
let container = this.ref.current;
|
|
50
|
+
return {
|
|
51
|
+
x: container.scrollLeft,
|
|
52
|
+
y: container.scrollTop,
|
|
53
|
+
width: container.offsetWidth,
|
|
54
|
+
height: container.offsetHeight
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -10,32 +10,34 @@
|
|
|
10
10
|
* governing permissions and limitations under the License.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import {Collection, Direction, DisabledBehavior, Key, KeyboardDelegate, Node, Orientation} from '@react-types/shared';
|
|
13
|
+
import {Collection, Direction, DisabledBehavior, Key, KeyboardDelegate, LayoutDelegate, Node, Orientation, Rect, RefObject} from '@react-types/shared';
|
|
14
|
+
import {DOMLayoutDelegate} from './DOMLayoutDelegate';
|
|
14
15
|
import {isScrollable} from '@react-aria/utils';
|
|
15
|
-
import {RefObject} from 'react';
|
|
16
16
|
|
|
17
17
|
interface ListKeyboardDelegateOptions<T> {
|
|
18
18
|
collection: Collection<Node<T>>,
|
|
19
|
-
ref: RefObject<HTMLElement>,
|
|
19
|
+
ref: RefObject<HTMLElement | null>,
|
|
20
20
|
collator?: Intl.Collator,
|
|
21
21
|
layout?: 'stack' | 'grid',
|
|
22
22
|
orientation?: Orientation,
|
|
23
23
|
direction?: Direction,
|
|
24
24
|
disabledKeys?: Set<Key>,
|
|
25
|
-
disabledBehavior?: DisabledBehavior
|
|
25
|
+
disabledBehavior?: DisabledBehavior,
|
|
26
|
+
layoutDelegate?: LayoutDelegate
|
|
26
27
|
}
|
|
27
28
|
|
|
28
29
|
export class ListKeyboardDelegate<T> implements KeyboardDelegate {
|
|
29
30
|
private collection: Collection<Node<T>>;
|
|
30
31
|
private disabledKeys: Set<Key>;
|
|
31
32
|
private disabledBehavior: DisabledBehavior;
|
|
32
|
-
private ref: RefObject<HTMLElement>;
|
|
33
|
+
private ref: RefObject<HTMLElement | null>;
|
|
33
34
|
private collator: Intl.Collator | undefined;
|
|
34
35
|
private layout: 'stack' | 'grid';
|
|
35
36
|
private orientation?: Orientation;
|
|
36
37
|
private direction?: Direction;
|
|
38
|
+
private layoutDelegate: LayoutDelegate;
|
|
37
39
|
|
|
38
|
-
constructor(collection: Collection<Node<T>>, disabledKeys: Set<Key>, ref: RefObject<HTMLElement>, collator?: Intl.Collator);
|
|
40
|
+
constructor(collection: Collection<Node<T>>, disabledKeys: Set<Key>, ref: RefObject<HTMLElement | null>, collator?: Intl.Collator);
|
|
39
41
|
constructor(options: ListKeyboardDelegateOptions<T>);
|
|
40
42
|
constructor(...args: any[]) {
|
|
41
43
|
if (args.length === 1) {
|
|
@@ -45,9 +47,10 @@ export class ListKeyboardDelegate<T> implements KeyboardDelegate {
|
|
|
45
47
|
this.collator = opts.collator;
|
|
46
48
|
this.disabledKeys = opts.disabledKeys || new Set();
|
|
47
49
|
this.disabledBehavior = opts.disabledBehavior || 'all';
|
|
48
|
-
this.orientation = opts.orientation;
|
|
50
|
+
this.orientation = opts.orientation || 'vertical';
|
|
49
51
|
this.direction = opts.direction;
|
|
50
52
|
this.layout = opts.layout || 'stack';
|
|
53
|
+
this.layoutDelegate = opts.layoutDelegate || new DOMLayoutDelegate(opts.ref);
|
|
51
54
|
} else {
|
|
52
55
|
this.collection = args[0];
|
|
53
56
|
this.disabledKeys = args[1];
|
|
@@ -56,6 +59,7 @@ export class ListKeyboardDelegate<T> implements KeyboardDelegate {
|
|
|
56
59
|
this.layout = 'stack';
|
|
57
60
|
this.orientation = 'vertical';
|
|
58
61
|
this.disabledBehavior = 'all';
|
|
62
|
+
this.layoutDelegate = new DOMLayoutDelegate(this.ref);
|
|
59
63
|
}
|
|
60
64
|
|
|
61
65
|
// If this is a vertical stack, remove the left/right methods completely
|
|
@@ -101,29 +105,29 @@ export class ListKeyboardDelegate<T> implements KeyboardDelegate {
|
|
|
101
105
|
private findKey(
|
|
102
106
|
key: Key,
|
|
103
107
|
nextKey: (key: Key) => Key,
|
|
104
|
-
shouldSkip: (prevRect:
|
|
108
|
+
shouldSkip: (prevRect: Rect, itemRect: Rect) => boolean
|
|
105
109
|
) {
|
|
106
|
-
let
|
|
107
|
-
if (!
|
|
110
|
+
let itemRect = this.layoutDelegate.getItemRect(key);
|
|
111
|
+
if (!itemRect) {
|
|
108
112
|
return null;
|
|
109
113
|
}
|
|
110
114
|
|
|
111
115
|
// Find the item above or below in the same column.
|
|
112
|
-
let prevRect =
|
|
116
|
+
let prevRect = itemRect;
|
|
113
117
|
do {
|
|
114
118
|
key = nextKey(key);
|
|
115
|
-
|
|
116
|
-
} while (
|
|
119
|
+
itemRect = this.layoutDelegate.getItemRect(key);
|
|
120
|
+
} while (itemRect && shouldSkip(prevRect, itemRect));
|
|
117
121
|
|
|
118
122
|
return key;
|
|
119
123
|
}
|
|
120
124
|
|
|
121
|
-
private isSameRow(prevRect:
|
|
122
|
-
return prevRect.
|
|
125
|
+
private isSameRow(prevRect: Rect, itemRect: Rect) {
|
|
126
|
+
return prevRect.y === itemRect.y || prevRect.x !== itemRect.x;
|
|
123
127
|
}
|
|
124
128
|
|
|
125
|
-
private isSameColumn(prevRect:
|
|
126
|
-
return prevRect.
|
|
129
|
+
private isSameColumn(prevRect: Rect, itemRect: Rect) {
|
|
130
|
+
return prevRect.x === itemRect.x || prevRect.y !== itemRect.y;
|
|
127
131
|
}
|
|
128
132
|
|
|
129
133
|
getKeyBelow(key: Key) {
|
|
@@ -202,14 +206,10 @@ export class ListKeyboardDelegate<T> implements KeyboardDelegate {
|
|
|
202
206
|
return null;
|
|
203
207
|
}
|
|
204
208
|
|
|
205
|
-
private getItem(key: Key): HTMLElement {
|
|
206
|
-
return key !== null ? this.ref.current.querySelector(`[data-key="${CSS.escape(key.toString())}"]`) : null;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
209
|
getKeyPageAbove(key: Key) {
|
|
210
210
|
let menu = this.ref.current;
|
|
211
|
-
let
|
|
212
|
-
if (!
|
|
211
|
+
let itemRect = this.layoutDelegate.getItemRect(key);
|
|
212
|
+
if (!itemRect) {
|
|
213
213
|
return null;
|
|
214
214
|
}
|
|
215
215
|
|
|
@@ -217,25 +217,19 @@ export class ListKeyboardDelegate<T> implements KeyboardDelegate {
|
|
|
217
217
|
return this.getFirstKey();
|
|
218
218
|
}
|
|
219
219
|
|
|
220
|
-
let containerRect = menu.getBoundingClientRect();
|
|
221
|
-
let itemRect = item.getBoundingClientRect();
|
|
222
220
|
if (this.orientation === 'horizontal') {
|
|
223
|
-
let
|
|
224
|
-
let pageX = Math.max(0, (itemRect.x - containerX) + itemRect.width - containerRect.width);
|
|
221
|
+
let pageX = Math.max(0, itemRect.x + itemRect.width - this.layoutDelegate.getVisibleRect().width);
|
|
225
222
|
|
|
226
|
-
while (
|
|
223
|
+
while (itemRect && itemRect.x > pageX) {
|
|
227
224
|
key = this.getKeyAbove(key);
|
|
228
|
-
|
|
229
|
-
itemRect = item?.getBoundingClientRect();
|
|
225
|
+
itemRect = key == null ? null : this.layoutDelegate.getItemRect(key);
|
|
230
226
|
}
|
|
231
227
|
} else {
|
|
232
|
-
let
|
|
233
|
-
let pageY = Math.max(0, (itemRect.y - containerY) + itemRect.height - containerRect.height);
|
|
228
|
+
let pageY = Math.max(0, itemRect.y + itemRect.height - this.layoutDelegate.getVisibleRect().height);
|
|
234
229
|
|
|
235
|
-
while (
|
|
230
|
+
while (itemRect && itemRect.y > pageY) {
|
|
236
231
|
key = this.getKeyAbove(key);
|
|
237
|
-
|
|
238
|
-
itemRect = item?.getBoundingClientRect();
|
|
232
|
+
itemRect = key == null ? null : this.layoutDelegate.getItemRect(key);
|
|
239
233
|
}
|
|
240
234
|
}
|
|
241
235
|
|
|
@@ -244,8 +238,8 @@ export class ListKeyboardDelegate<T> implements KeyboardDelegate {
|
|
|
244
238
|
|
|
245
239
|
getKeyPageBelow(key: Key) {
|
|
246
240
|
let menu = this.ref.current;
|
|
247
|
-
let
|
|
248
|
-
if (!
|
|
241
|
+
let itemRect = this.layoutDelegate.getItemRect(key);
|
|
242
|
+
if (!itemRect) {
|
|
249
243
|
return null;
|
|
250
244
|
}
|
|
251
245
|
|
|
@@ -253,25 +247,19 @@ export class ListKeyboardDelegate<T> implements KeyboardDelegate {
|
|
|
253
247
|
return this.getLastKey();
|
|
254
248
|
}
|
|
255
249
|
|
|
256
|
-
let containerRect = menu.getBoundingClientRect();
|
|
257
|
-
let itemRect = item.getBoundingClientRect();
|
|
258
250
|
if (this.orientation === 'horizontal') {
|
|
259
|
-
let
|
|
260
|
-
let pageX = Math.min(menu.scrollWidth, (itemRect.x - containerX) - itemRect.width + containerRect.width);
|
|
251
|
+
let pageX = Math.min(this.layoutDelegate.getContentSize().width, itemRect.y - itemRect.width + this.layoutDelegate.getVisibleRect().width);
|
|
261
252
|
|
|
262
|
-
while (
|
|
253
|
+
while (itemRect && itemRect.x < pageX) {
|
|
263
254
|
key = this.getKeyBelow(key);
|
|
264
|
-
|
|
265
|
-
itemRect = item?.getBoundingClientRect();
|
|
255
|
+
itemRect = key == null ? null : this.layoutDelegate.getItemRect(key);
|
|
266
256
|
}
|
|
267
257
|
} else {
|
|
268
|
-
let
|
|
269
|
-
let pageY = Math.min(menu.scrollHeight, (itemRect.y - containerY) - itemRect.height + containerRect.height);
|
|
258
|
+
let pageY = Math.min(this.layoutDelegate.getContentSize().height, itemRect.y - itemRect.height + this.layoutDelegate.getVisibleRect().height);
|
|
270
259
|
|
|
271
|
-
while (
|
|
260
|
+
while (itemRect && itemRect.y < pageY) {
|
|
272
261
|
key = this.getKeyBelow(key);
|
|
273
|
-
|
|
274
|
-
itemRect = item?.getBoundingClientRect();
|
|
262
|
+
itemRect = key == null ? null : this.layoutDelegate.getItemRect(key);
|
|
275
263
|
}
|
|
276
264
|
}
|
|
277
265
|
|
package/src/index.ts
CHANGED
|
@@ -14,6 +14,7 @@ export {useSelectableCollection} from './useSelectableCollection';
|
|
|
14
14
|
export {useSelectableItem} from './useSelectableItem';
|
|
15
15
|
export {useSelectableList} from './useSelectableList';
|
|
16
16
|
export {ListKeyboardDelegate} from './ListKeyboardDelegate';
|
|
17
|
+
export {DOMLayoutDelegate} from './DOMLayoutDelegate';
|
|
17
18
|
export {useTypeSelect} from './useTypeSelect';
|
|
18
19
|
|
|
19
20
|
export type {AriaSelectableCollectionOptions, SelectableCollectionAria} from './useSelectableCollection';
|
|
@@ -10,9 +10,9 @@
|
|
|
10
10
|
* governing permissions and limitations under the License.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import {DOMAttributes, FocusableElement, FocusStrategy, Key, KeyboardDelegate} from '@react-types/shared';
|
|
13
|
+
import {DOMAttributes, FocusableElement, FocusStrategy, Key, KeyboardDelegate, RefObject} from '@react-types/shared';
|
|
14
14
|
import {flushSync} from 'react-dom';
|
|
15
|
-
import {FocusEvent, KeyboardEvent,
|
|
15
|
+
import {FocusEvent, KeyboardEvent, useEffect, useRef} from 'react';
|
|
16
16
|
import {focusSafely, getFocusableTreeWalker} from '@react-aria/focus';
|
|
17
17
|
import {focusWithoutScrolling, mergeProps, scrollIntoView, scrollIntoViewport, useEvent, useRouter} from '@react-aria/utils';
|
|
18
18
|
import {getInteractionModality} from '@react-aria/interactions';
|
|
@@ -33,7 +33,7 @@ export interface AriaSelectableCollectionOptions {
|
|
|
33
33
|
/**
|
|
34
34
|
* The ref attached to the element representing the collection.
|
|
35
35
|
*/
|
|
36
|
-
ref: RefObject<HTMLElement>,
|
|
36
|
+
ref: RefObject<HTMLElement | null>,
|
|
37
37
|
/**
|
|
38
38
|
* Whether the collection or one of its items should be automatically focused upon render.
|
|
39
39
|
* @default false
|
|
@@ -80,7 +80,7 @@ export interface AriaSelectableCollectionOptions {
|
|
|
80
80
|
* The ref attached to the scrollable body. Used to provide automatic scrolling on item focus for non-virtualized collections.
|
|
81
81
|
* If not provided, defaults to the collection ref.
|
|
82
82
|
*/
|
|
83
|
-
scrollRef?: RefObject<HTMLElement>,
|
|
83
|
+
scrollRef?: RefObject<HTMLElement | null>,
|
|
84
84
|
/**
|
|
85
85
|
* The behavior of links in the collection.
|
|
86
86
|
* - 'action': link behaves like onAction.
|
|
@@ -293,6 +293,7 @@ export function useSelectableCollection(options: AriaSelectableCollectionOptions
|
|
|
293
293
|
};
|
|
294
294
|
|
|
295
295
|
// Store the scroll position so we can restore it later.
|
|
296
|
+
/// TODO: should this happen all the time??
|
|
296
297
|
let scrollPos = useRef({top: 0, left: 0});
|
|
297
298
|
useEvent(scrollRef, 'scroll', isVirtualized ? null : () => {
|
|
298
299
|
scrollPos.current = {
|
|
@@ -342,7 +343,7 @@ export function useSelectableCollection(options: AriaSelectableCollectionOptions
|
|
|
342
343
|
scrollRef.current.scrollLeft = scrollPos.current.left;
|
|
343
344
|
}
|
|
344
345
|
|
|
345
|
-
if (
|
|
346
|
+
if (manager.focusedKey != null) {
|
|
346
347
|
// Refocus and scroll the focused item into view if it exists within the scrollable region.
|
|
347
348
|
let element = scrollRef.current.querySelector(`[data-key="${CSS.escape(manager.focusedKey.toString())}"]`) as HTMLElement;
|
|
348
349
|
if (element) {
|
|
@@ -400,17 +401,21 @@ export function useSelectableCollection(options: AriaSelectableCollectionOptions
|
|
|
400
401
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
401
402
|
}, []);
|
|
402
403
|
|
|
403
|
-
//
|
|
404
|
-
// When virtualized, Virtualizer handles this internally.
|
|
404
|
+
// Scroll the focused element into view when the focusedKey changes.
|
|
405
405
|
let lastFocusedKey = useRef(manager.focusedKey);
|
|
406
406
|
useEffect(() => {
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
let element =
|
|
410
|
-
if (element
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
407
|
+
if (manager.isFocused && manager.focusedKey != null && (manager.focusedKey !== lastFocusedKey.current || autoFocusRef.current) && scrollRef?.current) {
|
|
408
|
+
let modality = getInteractionModality();
|
|
409
|
+
let element = ref.current.querySelector(`[data-key="${CSS.escape(manager.focusedKey.toString())}"]`) as HTMLElement;
|
|
410
|
+
if (!element) {
|
|
411
|
+
// If item element wasn't found, return early (don't update autoFocusRef and lastFocusedKey).
|
|
412
|
+
// The collection may initially be empty (e.g. virtualizer), so wait until the element exists.
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
if (modality === 'keyboard' || autoFocusRef.current) {
|
|
417
|
+
scrollIntoView(scrollRef.current, element);
|
|
418
|
+
|
|
414
419
|
// Avoid scroll in iOS VO, since it may cause overlay to close (i.e. RAC submenu)
|
|
415
420
|
if (modality !== 'virtual') {
|
|
416
421
|
scrollIntoViewport(element, {containingElement: ref.current});
|
|
@@ -425,7 +430,13 @@ export function useSelectableCollection(options: AriaSelectableCollectionOptions
|
|
|
425
430
|
|
|
426
431
|
lastFocusedKey.current = manager.focusedKey;
|
|
427
432
|
autoFocusRef.current = false;
|
|
428
|
-
}
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
// Intercept FocusScope restoration since virtualized collections can reuse DOM nodes.
|
|
436
|
+
useEvent(ref, 'react-aria-focus-scope-restore', e => {
|
|
437
|
+
e.preventDefault();
|
|
438
|
+
manager.setFocused(true);
|
|
439
|
+
});
|
|
429
440
|
|
|
430
441
|
let handlers = {
|
|
431
442
|
onKeyDown,
|
package/src/useSelectableItem.ts
CHANGED
|
@@ -10,13 +10,13 @@
|
|
|
10
10
|
* governing permissions and limitations under the License.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import {DOMAttributes, FocusableElement, Key, LongPressEvent, PressEvent} from '@react-types/shared';
|
|
13
|
+
import {DOMAttributes, FocusableElement, Key, LongPressEvent, PressEvent, RefObject} from '@react-types/shared';
|
|
14
14
|
import {focusSafely} from '@react-aria/focus';
|
|
15
15
|
import {isCtrlKeyPressed, isNonContiguousSelectionModifier} from './utils';
|
|
16
16
|
import {mergeProps, openLink, useRouter} from '@react-aria/utils';
|
|
17
17
|
import {MultipleSelectionManager} from '@react-stately/selection';
|
|
18
18
|
import {PressProps, useLongPress, usePress} from '@react-aria/interactions';
|
|
19
|
-
import {
|
|
19
|
+
import {useEffect, useRef} from 'react';
|
|
20
20
|
|
|
21
21
|
export interface SelectableItemOptions {
|
|
22
22
|
/**
|
|
@@ -30,7 +30,7 @@ export interface SelectableItemOptions {
|
|
|
30
30
|
/**
|
|
31
31
|
* Ref to the item.
|
|
32
32
|
*/
|
|
33
|
-
ref: RefObject<FocusableElement>,
|
|
33
|
+
ref: RefObject<FocusableElement | null>,
|
|
34
34
|
/**
|
|
35
35
|
* By default, selection occurs on pointer down. This can be strange if selecting an
|
|
36
36
|
* item causes the UI to disappear immediately (e.g. menus).
|
package/src/useSelectableList.ts
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import {AriaSelectableCollectionOptions, useSelectableCollection} from './useSelectableCollection';
|
|
14
|
-
import {Collection, DOMAttributes, Key, KeyboardDelegate, Node} from '@react-types/shared';
|
|
14
|
+
import {Collection, DOMAttributes, Key, KeyboardDelegate, LayoutDelegate, Node} from '@react-types/shared';
|
|
15
15
|
import {ListKeyboardDelegate} from './ListKeyboardDelegate';
|
|
16
16
|
import {useCollator} from '@react-aria/i18n';
|
|
17
17
|
import {useMemo} from 'react';
|
|
@@ -25,6 +25,12 @@ export interface AriaSelectableListOptions extends Omit<AriaSelectableCollection
|
|
|
25
25
|
* A delegate object that implements behavior for keyboard focus movement.
|
|
26
26
|
*/
|
|
27
27
|
keyboardDelegate?: KeyboardDelegate,
|
|
28
|
+
/**
|
|
29
|
+
* A delegate object that provides layout information for items in the collection.
|
|
30
|
+
* By default this uses the DOM, but this can be overridden to implement things like
|
|
31
|
+
* virtualized scrolling.
|
|
32
|
+
*/
|
|
33
|
+
layoutDelegate?: LayoutDelegate,
|
|
28
34
|
/**
|
|
29
35
|
* The item keys that are disabled. These items cannot be selected, focused, or otherwise interacted with.
|
|
30
36
|
*/
|
|
@@ -47,7 +53,8 @@ export function useSelectableList(props: AriaSelectableListOptions): SelectableL
|
|
|
47
53
|
collection,
|
|
48
54
|
disabledKeys,
|
|
49
55
|
ref,
|
|
50
|
-
keyboardDelegate
|
|
56
|
+
keyboardDelegate,
|
|
57
|
+
layoutDelegate
|
|
51
58
|
} = props;
|
|
52
59
|
|
|
53
60
|
// By default, a KeyboardDelegate is provided which uses the DOM to query layout information (e.g. for page up/page down).
|
|
@@ -60,9 +67,10 @@ export function useSelectableList(props: AriaSelectableListOptions): SelectableL
|
|
|
60
67
|
disabledKeys,
|
|
61
68
|
disabledBehavior,
|
|
62
69
|
ref,
|
|
63
|
-
collator
|
|
70
|
+
collator,
|
|
71
|
+
layoutDelegate
|
|
64
72
|
})
|
|
65
|
-
), [keyboardDelegate, collection, disabledKeys, ref, collator, disabledBehavior]);
|
|
73
|
+
), [keyboardDelegate, layoutDelegate, collection, disabledKeys, ref, collator, disabledBehavior]);
|
|
66
74
|
|
|
67
75
|
let {collectionProps} = useSelectableCollection({
|
|
68
76
|
...props,
|