@react-stately/selection 3.16.2 → 3.18.0

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.
@@ -15,6 +15,7 @@ import {
15
15
  FocusStrategy,
16
16
  Selection as ISelection,
17
17
  Key,
18
+ LayoutDelegate,
18
19
  LongPressEvent,
19
20
  Node,
20
21
  PressEvent,
@@ -26,23 +27,26 @@ import {MultipleSelectionManager, MultipleSelectionState} from './types';
26
27
  import {Selection} from './Selection';
27
28
 
28
29
  interface SelectionManagerOptions {
29
- allowsCellSelection?: boolean
30
+ allowsCellSelection?: boolean,
31
+ layoutDelegate?: LayoutDelegate
30
32
  }
31
33
 
32
34
  /**
33
35
  * An interface for reading and updating multiple selection state.
34
36
  */
35
37
  export class SelectionManager implements MultipleSelectionManager {
36
- private collection: Collection<Node<unknown>>;
38
+ collection: Collection<Node<unknown>>;
37
39
  private state: MultipleSelectionState;
38
40
  private allowsCellSelection: boolean;
39
- private _isSelectAll: boolean;
41
+ private _isSelectAll: boolean | null;
42
+ private layoutDelegate: LayoutDelegate | null;
40
43
 
41
44
  constructor(collection: Collection<Node<unknown>>, state: MultipleSelectionState, options?: SelectionManagerOptions) {
42
45
  this.collection = collection;
43
46
  this.state = state;
44
47
  this.allowsCellSelection = options?.allowsCellSelection ?? false;
45
48
  this._isSelectAll = null;
49
+ this.layoutDelegate = options?.layoutDelegate || null;
46
50
  }
47
51
 
48
52
  /**
@@ -90,12 +94,12 @@ export class SelectionManager implements MultipleSelectionManager {
90
94
  /**
91
95
  * The current focused key in the collection.
92
96
  */
93
- get focusedKey(): Key {
97
+ get focusedKey(): Key | null {
94
98
  return this.state.focusedKey;
95
99
  }
96
100
 
97
101
  /** Whether the first or last child of the focused key should receive focus. */
98
- get childFocusStrategy(): FocusStrategy {
102
+ get childFocusStrategy(): FocusStrategy | null {
99
103
  return this.state.childFocusStrategy;
100
104
  }
101
105
 
@@ -133,10 +137,13 @@ export class SelectionManager implements MultipleSelectionManager {
133
137
  return false;
134
138
  }
135
139
 
136
- key = this.getKey(key);
140
+ let mappedKey = this.getKey(key);
141
+ if (mappedKey == null) {
142
+ return false;
143
+ }
137
144
  return this.state.selectedKeys === 'all'
138
- ? this.canSelectItem(key)
139
- : this.state.selectedKeys.has(key);
145
+ ? this.canSelectItem(mappedKey)
146
+ : this.state.selectedKeys.has(mappedKey);
140
147
  }
141
148
 
142
149
  /**
@@ -177,7 +184,7 @@ export class SelectionManager implements MultipleSelectionManager {
177
184
  }
178
185
  }
179
186
 
180
- return first?.key;
187
+ return first?.key ?? null;
181
188
  }
182
189
 
183
190
  get lastSelectedKey(): Key | null {
@@ -189,7 +196,7 @@ export class SelectionManager implements MultipleSelectionManager {
189
196
  }
190
197
  }
191
198
 
192
- return last?.key;
199
+ return last?.key ?? null;
193
200
  }
194
201
 
195
202
  get disabledKeys(): Set<Key> {
@@ -213,22 +220,25 @@ export class SelectionManager implements MultipleSelectionManager {
213
220
  return;
214
221
  }
215
222
 
216
- toKey = this.getKey(toKey);
223
+ let mappedToKey = this.getKey(toKey);
224
+ if (mappedToKey == null) {
225
+ return;
226
+ }
217
227
 
218
228
  let selection: Selection;
219
229
 
220
230
  // Only select the one key if coming from a select all.
221
231
  if (this.state.selectedKeys === 'all') {
222
- selection = new Selection([toKey], toKey, toKey);
232
+ selection = new Selection([mappedToKey], mappedToKey, mappedToKey);
223
233
  } else {
224
234
  let selectedKeys = this.state.selectedKeys as Selection;
225
- let anchorKey = selectedKeys.anchorKey || toKey;
226
- selection = new Selection(selectedKeys, anchorKey, toKey);
227
- for (let key of this.getKeyRange(anchorKey, selectedKeys.currentKey || toKey)) {
235
+ let anchorKey = selectedKeys.anchorKey ?? mappedToKey;
236
+ selection = new Selection(selectedKeys, anchorKey, mappedToKey);
237
+ for (let key of this.getKeyRange(anchorKey, selectedKeys.currentKey ?? mappedToKey)) {
228
238
  selection.delete(key);
229
239
  }
230
240
 
231
- for (let key of this.getKeyRange(toKey, anchorKey)) {
241
+ for (let key of this.getKeyRange(mappedToKey, anchorKey)) {
232
242
  if (this.canSelectItem(key)) {
233
243
  selection.add(key);
234
244
  }
@@ -253,11 +263,15 @@ export class SelectionManager implements MultipleSelectionManager {
253
263
  }
254
264
 
255
265
  private getKeyRangeInternal(from: Key, to: Key) {
266
+ if (this.layoutDelegate?.getKeyRange) {
267
+ return this.layoutDelegate.getKeyRange(from, to);
268
+ }
269
+
256
270
  let keys: Key[] = [];
257
- let key = from;
258
- while (key) {
271
+ let key: Key | null = from;
272
+ while (key != null) {
259
273
  let item = this.collection.getItem(key);
260
- if (item && item.type === 'item' || (item.type === 'cell' && this.allowsCellSelection)) {
274
+ if (item && (item.type === 'item' || (item.type === 'cell' && this.allowsCellSelection))) {
261
275
  keys.push(key);
262
276
  }
263
277
 
@@ -284,7 +298,7 @@ export class SelectionManager implements MultipleSelectionManager {
284
298
  }
285
299
 
286
300
  // Find a parent item to select
287
- while (item.type !== 'item' && item.parentKey != null) {
301
+ while (item && item.type !== 'item' && item.parentKey != null) {
288
302
  item = this.collection.getItem(item.parentKey);
289
303
  }
290
304
 
@@ -308,20 +322,20 @@ export class SelectionManager implements MultipleSelectionManager {
308
322
  return;
309
323
  }
310
324
 
311
- key = this.getKey(key);
312
- if (key == null) {
325
+ let mappedKey = this.getKey(key);
326
+ if (mappedKey == null) {
313
327
  return;
314
328
  }
315
329
 
316
330
  let keys = new Selection(this.state.selectedKeys === 'all' ? this.getSelectAllKeys() : this.state.selectedKeys);
317
- if (keys.has(key)) {
318
- keys.delete(key);
331
+ if (keys.has(mappedKey)) {
332
+ keys.delete(mappedKey);
319
333
  // TODO: move anchor to last selected key...
320
334
  // Does `current` need to move here too?
321
- } else if (this.canSelectItem(key)) {
322
- keys.add(key);
323
- keys.anchorKey = key;
324
- keys.currentKey = key;
335
+ } else if (this.canSelectItem(mappedKey)) {
336
+ keys.add(mappedKey);
337
+ keys.anchorKey = mappedKey;
338
+ keys.currentKey = mappedKey;
325
339
  }
326
340
 
327
341
  if (this.disallowEmptySelection && keys.size === 0) {
@@ -339,13 +353,13 @@ export class SelectionManager implements MultipleSelectionManager {
339
353
  return;
340
354
  }
341
355
 
342
- key = this.getKey(key);
343
- if (key == null) {
356
+ let mappedKey = this.getKey(key);
357
+ if (mappedKey == null) {
344
358
  return;
345
359
  }
346
360
 
347
- let selection = this.canSelectItem(key)
348
- ? new Selection([key], key, key)
361
+ let selection = this.canSelectItem(mappedKey)
362
+ ? new Selection([mappedKey], mappedKey, mappedKey)
349
363
  : new Selection();
350
364
 
351
365
  this.state.setSelectedKeys(selection);
@@ -361,9 +375,9 @@ export class SelectionManager implements MultipleSelectionManager {
361
375
 
362
376
  let selection = new Selection();
363
377
  for (let key of keys) {
364
- key = this.getKey(key);
365
- if (key != null) {
366
- selection.add(key);
378
+ let mappedKey = this.getKey(key);
379
+ if (mappedKey != null) {
380
+ selection.add(mappedKey);
367
381
  if (this.selectionMode === 'single') {
368
382
  break;
369
383
  }
@@ -375,17 +389,17 @@ export class SelectionManager implements MultipleSelectionManager {
375
389
 
376
390
  private getSelectAllKeys() {
377
391
  let keys: Key[] = [];
378
- let addKeys = (key: Key) => {
392
+ let addKeys = (key: Key | null) => {
379
393
  while (key != null) {
380
394
  if (this.canSelectItem(key)) {
381
395
  let item = this.collection.getItem(key);
382
- if (item.type === 'item') {
396
+ if (item?.type === 'item') {
383
397
  keys.push(key);
384
398
  }
385
399
 
386
400
  // Add child keys. If cell selection is allowed, then include item children too.
387
- if (item.hasChildNodes && (this.allowsCellSelection || item.type !== 'item')) {
388
- addKeys(getFirstItem(getChildNodes(item, this.collection)).key);
401
+ if (item?.hasChildNodes && (this.allowsCellSelection || item.type !== 'item')) {
402
+ addKeys(getFirstItem(getChildNodes(item, this.collection))?.key ?? null);
389
403
  }
390
404
  }
391
405
 
package/src/types.ts CHANGED
@@ -19,11 +19,11 @@ export interface FocusState {
19
19
  /** Sets whether the collection is focused. */
20
20
  setFocused(isFocused: boolean): void,
21
21
  /** The current focused key in the collection. */
22
- readonly focusedKey: Key,
22
+ readonly focusedKey: Key | null,
23
23
  /** Whether the first or last child of the focused key should receive focus. */
24
- readonly childFocusStrategy: FocusStrategy,
24
+ readonly childFocusStrategy: FocusStrategy | null,
25
25
  /** Sets the focused key, and optionally, whether the first or last child of that key should receive focus. */
26
- setFocusedKey(key: Key, child?: FocusStrategy): void
26
+ setFocusedKey(key: Key | null, child?: FocusStrategy): void
27
27
  }
28
28
 
29
29
  export interface SingleSelectionState extends FocusState {
@@ -10,7 +10,7 @@
10
10
  * governing permissions and limitations under the License.
11
11
  */
12
12
 
13
- import {DisabledBehavior, Key, MultipleSelection, SelectionBehavior, SelectionMode} from '@react-types/shared';
13
+ import {DisabledBehavior, FocusStrategy, Key, MultipleSelection, SelectionBehavior, SelectionMode} from '@react-types/shared';
14
14
  import {MultipleSelectionState} from './types';
15
15
  import {Selection} from './Selection';
16
16
  import {useControlledState} from '@react-stately/utils';
@@ -45,7 +45,7 @@ export interface MultipleSelectionStateProps extends MultipleSelection {
45
45
  export function useMultipleSelectionState(props: MultipleSelectionStateProps): MultipleSelectionState {
46
46
  let {
47
47
  selectionMode = 'none' as SelectionMode,
48
- disallowEmptySelection,
48
+ disallowEmptySelection = false,
49
49
  allowDuplicateSelectionEvents,
50
50
  selectionBehavior: selectionBehaviorProp = 'toggle',
51
51
  disabledBehavior = 'all'
@@ -55,14 +55,14 @@ export function useMultipleSelectionState(props: MultipleSelectionStateProps): M
55
55
  // But we also need to trigger a react re-render. So, we have both a ref (sync) and state (async).
56
56
  let isFocusedRef = useRef(false);
57
57
  let [, setFocused] = useState(false);
58
- let focusedKeyRef = useRef(null);
59
- let childFocusStrategyRef = useRef(null);
60
- let [, setFocusedKey] = useState(null);
58
+ let focusedKeyRef = useRef<Key | null>(null);
59
+ let childFocusStrategyRef = useRef<FocusStrategy | null>(null);
60
+ let [, setFocusedKey] = useState<Key | null>(null);
61
61
  let selectedKeysProp = useMemo(() => convertSelection(props.selectedKeys), [props.selectedKeys]);
62
62
  let defaultSelectedKeys = useMemo(() => convertSelection(props.defaultSelectedKeys, new Selection()), [props.defaultSelectedKeys]);
63
63
  let [selectedKeys, setSelectedKeys] = useControlledState(
64
64
  selectedKeysProp,
65
- defaultSelectedKeys,
65
+ defaultSelectedKeys!,
66
66
  props.onSelectionChange
67
67
  );
68
68
  let disabledKeysProp = useMemo(() =>
@@ -119,7 +119,7 @@ export function useMultipleSelectionState(props: MultipleSelectionStateProps): M
119
119
  };
120
120
  }
121
121
 
122
- function convertSelection(selection: 'all' | Iterable<Key>, defaultValue?: Selection): 'all' | Set<Key> {
122
+ function convertSelection(selection: 'all' | Iterable<Key> | null | undefined, defaultValue?: Selection): 'all' | Set<Key> | undefined {
123
123
  if (!selection) {
124
124
  return defaultValue;
125
125
  }