@react-aria/selection 3.0.0-nightly.2962 → 3.0.0-nightly.2971

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.
@@ -10,32 +10,35 @@
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} from '@react-types/shared';
14
+ import {DOMLayoutDelegate} from './DOMLayoutDelegate';
14
15
  import {isScrollable} from '@react-aria/utils';
15
16
  import {RefObject} from 'react';
16
17
 
17
18
  interface ListKeyboardDelegateOptions<T> {
18
19
  collection: Collection<Node<T>>,
19
- ref: RefObject<HTMLElement>,
20
+ ref: RefObject<HTMLElement | null>,
20
21
  collator?: Intl.Collator,
21
22
  layout?: 'stack' | 'grid',
22
23
  orientation?: Orientation,
23
24
  direction?: Direction,
24
25
  disabledKeys?: Set<Key>,
25
- disabledBehavior?: DisabledBehavior
26
+ disabledBehavior?: DisabledBehavior,
27
+ layoutDelegate?: LayoutDelegate
26
28
  }
27
29
 
28
30
  export class ListKeyboardDelegate<T> implements KeyboardDelegate {
29
31
  private collection: Collection<Node<T>>;
30
32
  private disabledKeys: Set<Key>;
31
33
  private disabledBehavior: DisabledBehavior;
32
- private ref: RefObject<HTMLElement>;
34
+ private ref: RefObject<HTMLElement | null>;
33
35
  private collator: Intl.Collator | undefined;
34
36
  private layout: 'stack' | 'grid';
35
37
  private orientation?: Orientation;
36
38
  private direction?: Direction;
39
+ private layoutDelegate: LayoutDelegate;
37
40
 
38
- constructor(collection: Collection<Node<T>>, disabledKeys: Set<Key>, ref: RefObject<HTMLElement>, collator?: Intl.Collator);
41
+ constructor(collection: Collection<Node<T>>, disabledKeys: Set<Key>, ref: RefObject<HTMLElement | null>, collator?: Intl.Collator);
39
42
  constructor(options: ListKeyboardDelegateOptions<T>);
40
43
  constructor(...args: any[]) {
41
44
  if (args.length === 1) {
@@ -48,6 +51,7 @@ export class ListKeyboardDelegate<T> implements KeyboardDelegate {
48
51
  this.orientation = opts.orientation || 'vertical';
49
52
  this.direction = opts.direction;
50
53
  this.layout = opts.layout || 'stack';
54
+ this.layoutDelegate = opts.layoutDelegate || new DOMLayoutDelegate(opts.ref);
51
55
  } else {
52
56
  this.collection = args[0];
53
57
  this.disabledKeys = args[1];
@@ -56,6 +60,7 @@ export class ListKeyboardDelegate<T> implements KeyboardDelegate {
56
60
  this.layout = 'stack';
57
61
  this.orientation = 'vertical';
58
62
  this.disabledBehavior = 'all';
63
+ this.layoutDelegate = new DOMLayoutDelegate(this.ref);
59
64
  }
60
65
 
61
66
  // If this is a vertical stack, remove the left/right methods completely
@@ -101,29 +106,29 @@ export class ListKeyboardDelegate<T> implements KeyboardDelegate {
101
106
  private findKey(
102
107
  key: Key,
103
108
  nextKey: (key: Key) => Key,
104
- shouldSkip: (prevRect: DOMRect, itemRect: DOMRect) => boolean
109
+ shouldSkip: (prevRect: Rect, itemRect: Rect) => boolean
105
110
  ) {
106
- let item = this.getItem(key);
107
- if (!item) {
111
+ let itemRect = this.layoutDelegate.getItemRect(key);
112
+ if (!itemRect) {
108
113
  return null;
109
114
  }
110
115
 
111
116
  // Find the item above or below in the same column.
112
- let prevRect = item.getBoundingClientRect();
117
+ let prevRect = itemRect;
113
118
  do {
114
119
  key = nextKey(key);
115
- item = this.getItem(key);
116
- } while (item && shouldSkip(prevRect, item.getBoundingClientRect()));
120
+ itemRect = this.layoutDelegate.getItemRect(key);
121
+ } while (itemRect && shouldSkip(prevRect, itemRect));
117
122
 
118
123
  return key;
119
124
  }
120
125
 
121
- private isSameRow(prevRect: DOMRect, itemRect: DOMRect) {
122
- return prevRect.top === itemRect.top || prevRect.left !== itemRect.left;
126
+ private isSameRow(prevRect: Rect, itemRect: Rect) {
127
+ return prevRect.y === itemRect.y || prevRect.x !== itemRect.x;
123
128
  }
124
129
 
125
- private isSameColumn(prevRect: DOMRect, itemRect: DOMRect) {
126
- return prevRect.left === itemRect.left || prevRect.top !== itemRect.top;
130
+ private isSameColumn(prevRect: Rect, itemRect: Rect) {
131
+ return prevRect.x === itemRect.x || prevRect.y !== itemRect.y;
127
132
  }
128
133
 
129
134
  getKeyBelow(key: Key) {
@@ -202,14 +207,10 @@ export class ListKeyboardDelegate<T> implements KeyboardDelegate {
202
207
  return null;
203
208
  }
204
209
 
205
- private getItem(key: Key): HTMLElement {
206
- return key !== null ? this.ref.current.querySelector(`[data-key="${CSS.escape(key.toString())}"]`) : null;
207
- }
208
-
209
210
  getKeyPageAbove(key: Key) {
210
211
  let menu = this.ref.current;
211
- let item = this.getItem(key);
212
- if (!item) {
212
+ let itemRect = this.layoutDelegate.getItemRect(key);
213
+ if (!itemRect) {
213
214
  return null;
214
215
  }
215
216
 
@@ -217,25 +218,19 @@ export class ListKeyboardDelegate<T> implements KeyboardDelegate {
217
218
  return this.getFirstKey();
218
219
  }
219
220
 
220
- let containerRect = menu.getBoundingClientRect();
221
- let itemRect = item.getBoundingClientRect();
222
221
  if (this.orientation === 'horizontal') {
223
- let containerX = containerRect.x - menu.scrollLeft;
224
- let pageX = Math.max(0, (itemRect.x - containerX) + itemRect.width - containerRect.width);
222
+ let pageX = Math.max(0, itemRect.x + itemRect.width - this.layoutDelegate.getVisibleRect().width);
225
223
 
226
- while (item && (itemRect.x - containerX) > pageX) {
224
+ while (itemRect && itemRect.x > pageX) {
227
225
  key = this.getKeyAbove(key);
228
- item = key == null ? null : this.getItem(key);
229
- itemRect = item?.getBoundingClientRect();
226
+ itemRect = key == null ? null : this.layoutDelegate.getItemRect(key);
230
227
  }
231
228
  } else {
232
- let containerY = containerRect.y - menu.scrollTop;
233
- let pageY = Math.max(0, (itemRect.y - containerY) + itemRect.height - containerRect.height);
229
+ let pageY = Math.max(0, itemRect.y + itemRect.height - this.layoutDelegate.getVisibleRect().height);
234
230
 
235
- while (item && (itemRect.y - containerY) > pageY) {
231
+ while (itemRect && itemRect.y > pageY) {
236
232
  key = this.getKeyAbove(key);
237
- item = key == null ? null : this.getItem(key);
238
- itemRect = item?.getBoundingClientRect();
233
+ itemRect = key == null ? null : this.layoutDelegate.getItemRect(key);
239
234
  }
240
235
  }
241
236
 
@@ -244,8 +239,8 @@ export class ListKeyboardDelegate<T> implements KeyboardDelegate {
244
239
 
245
240
  getKeyPageBelow(key: Key) {
246
241
  let menu = this.ref.current;
247
- let item = this.getItem(key);
248
- if (!item) {
242
+ let itemRect = this.layoutDelegate.getItemRect(key);
243
+ if (!itemRect) {
249
244
  return null;
250
245
  }
251
246
 
@@ -253,25 +248,19 @@ export class ListKeyboardDelegate<T> implements KeyboardDelegate {
253
248
  return this.getLastKey();
254
249
  }
255
250
 
256
- let containerRect = menu.getBoundingClientRect();
257
- let itemRect = item.getBoundingClientRect();
258
251
  if (this.orientation === 'horizontal') {
259
- let containerX = containerRect.x - menu.scrollLeft;
260
- let pageX = Math.min(menu.scrollWidth, (itemRect.x - containerX) - itemRect.width + containerRect.width);
252
+ let pageX = Math.min(this.layoutDelegate.getContentSize().width, itemRect.y - itemRect.width + this.layoutDelegate.getVisibleRect().width);
261
253
 
262
- while (item && (itemRect.x - containerX) < pageX) {
254
+ while (itemRect && itemRect.x < pageX) {
263
255
  key = this.getKeyBelow(key);
264
- item = key == null ? null : this.getItem(key);
265
- itemRect = item?.getBoundingClientRect();
256
+ itemRect = key == null ? null : this.layoutDelegate.getItemRect(key);
266
257
  }
267
258
  } else {
268
- let containerY = containerRect.y - menu.scrollTop;
269
- let pageY = Math.min(menu.scrollHeight, (itemRect.y - containerY) - itemRect.height + containerRect.height);
259
+ let pageY = Math.min(this.layoutDelegate.getContentSize().height, itemRect.y - itemRect.height + this.layoutDelegate.getVisibleRect().height);
270
260
 
271
- while (item && (itemRect.y - containerY) < pageY) {
261
+ while (itemRect && itemRect.y < pageY) {
272
262
  key = this.getKeyBelow(key);
273
- item = key == null ? null : this.getItem(key);
274
- itemRect = item?.getBoundingClientRect();
263
+ itemRect = key == null ? null : this.layoutDelegate.getItemRect(key);
275
264
  }
276
265
  }
277
266
 
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';
@@ -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.
@@ -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).
@@ -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,