@react-stately/selection 3.17.0 → 3.19.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.
package/src/Selection.ts CHANGED
@@ -17,17 +17,17 @@ import {Key} from '@react-types/shared';
17
17
  * and current selected key for use when range selecting.
18
18
  */
19
19
  export class Selection extends Set<Key> {
20
- anchorKey: Key;
21
- currentKey: Key;
20
+ anchorKey: Key | null;
21
+ currentKey: Key | null;
22
22
 
23
- constructor(keys?: Iterable<Key> | Selection, anchorKey?: Key, currentKey?: Key) {
23
+ constructor(keys?: Iterable<Key> | Selection, anchorKey?: Key | null, currentKey?: Key | null) {
24
24
  super(keys);
25
25
  if (keys instanceof Selection) {
26
26
  this.anchorKey = anchorKey ?? keys.anchorKey;
27
27
  this.currentKey = currentKey ?? keys.currentKey;
28
28
  } else {
29
- this.anchorKey = anchorKey;
30
- this.currentKey = currentKey;
29
+ this.anchorKey = anchorKey ?? null;
30
+ this.currentKey = currentKey ?? null;
31
31
  }
32
32
  }
33
33
  }
@@ -35,10 +35,10 @@ interface SelectionManagerOptions {
35
35
  * An interface for reading and updating multiple selection state.
36
36
  */
37
37
  export class SelectionManager implements MultipleSelectionManager {
38
- private collection: Collection<Node<unknown>>;
38
+ collection: Collection<Node<unknown>>;
39
39
  private state: MultipleSelectionState;
40
40
  private allowsCellSelection: boolean;
41
- private _isSelectAll: boolean;
41
+ private _isSelectAll: boolean | null;
42
42
  private layoutDelegate: LayoutDelegate | null;
43
43
 
44
44
  constructor(collection: Collection<Node<unknown>>, state: MultipleSelectionState, options?: SelectionManagerOptions) {
@@ -94,12 +94,12 @@ export class SelectionManager implements MultipleSelectionManager {
94
94
  /**
95
95
  * The current focused key in the collection.
96
96
  */
97
- get focusedKey(): Key {
97
+ get focusedKey(): Key | null {
98
98
  return this.state.focusedKey;
99
99
  }
100
100
 
101
101
  /** Whether the first or last child of the focused key should receive focus. */
102
- get childFocusStrategy(): FocusStrategy {
102
+ get childFocusStrategy(): FocusStrategy | null {
103
103
  return this.state.childFocusStrategy;
104
104
  }
105
105
 
@@ -137,10 +137,13 @@ export class SelectionManager implements MultipleSelectionManager {
137
137
  return false;
138
138
  }
139
139
 
140
- key = this.getKey(key);
140
+ let mappedKey = this.getKey(key);
141
+ if (mappedKey == null) {
142
+ return false;
143
+ }
141
144
  return this.state.selectedKeys === 'all'
142
- ? this.canSelectItem(key)
143
- : this.state.selectedKeys.has(key);
145
+ ? this.canSelectItem(mappedKey)
146
+ : this.state.selectedKeys.has(mappedKey);
144
147
  }
145
148
 
146
149
  /**
@@ -181,7 +184,7 @@ export class SelectionManager implements MultipleSelectionManager {
181
184
  }
182
185
  }
183
186
 
184
- return first?.key;
187
+ return first?.key ?? null;
185
188
  }
186
189
 
187
190
  get lastSelectedKey(): Key | null {
@@ -193,7 +196,7 @@ export class SelectionManager implements MultipleSelectionManager {
193
196
  }
194
197
  }
195
198
 
196
- return last?.key;
199
+ return last?.key ?? null;
197
200
  }
198
201
 
199
202
  get disabledKeys(): Set<Key> {
@@ -217,22 +220,25 @@ export class SelectionManager implements MultipleSelectionManager {
217
220
  return;
218
221
  }
219
222
 
220
- toKey = this.getKey(toKey);
223
+ let mappedToKey = this.getKey(toKey);
224
+ if (mappedToKey == null) {
225
+ return;
226
+ }
221
227
 
222
228
  let selection: Selection;
223
229
 
224
230
  // Only select the one key if coming from a select all.
225
231
  if (this.state.selectedKeys === 'all') {
226
- selection = new Selection([toKey], toKey, toKey);
232
+ selection = new Selection([mappedToKey], mappedToKey, mappedToKey);
227
233
  } else {
228
234
  let selectedKeys = this.state.selectedKeys as Selection;
229
- let anchorKey = selectedKeys.anchorKey ?? toKey;
230
- selection = new Selection(selectedKeys, anchorKey, toKey);
231
- 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)) {
232
238
  selection.delete(key);
233
239
  }
234
240
 
235
- for (let key of this.getKeyRange(toKey, anchorKey)) {
241
+ for (let key of this.getKeyRange(mappedToKey, anchorKey)) {
236
242
  if (this.canSelectItem(key)) {
237
243
  selection.add(key);
238
244
  }
@@ -262,10 +268,10 @@ export class SelectionManager implements MultipleSelectionManager {
262
268
  }
263
269
 
264
270
  let keys: Key[] = [];
265
- let key = from;
271
+ let key: Key | null = from;
266
272
  while (key != null) {
267
273
  let item = this.collection.getItem(key);
268
- if (item && item.type === 'item' || (item.type === 'cell' && this.allowsCellSelection)) {
274
+ if (item && (item.type === 'item' || (item.type === 'cell' && this.allowsCellSelection))) {
269
275
  keys.push(key);
270
276
  }
271
277
 
@@ -292,7 +298,7 @@ export class SelectionManager implements MultipleSelectionManager {
292
298
  }
293
299
 
294
300
  // Find a parent item to select
295
- while (item.type !== 'item' && item.parentKey != null) {
301
+ while (item && item.type !== 'item' && item.parentKey != null) {
296
302
  item = this.collection.getItem(item.parentKey);
297
303
  }
298
304
 
@@ -316,20 +322,20 @@ export class SelectionManager implements MultipleSelectionManager {
316
322
  return;
317
323
  }
318
324
 
319
- key = this.getKey(key);
320
- if (key == null) {
325
+ let mappedKey = this.getKey(key);
326
+ if (mappedKey == null) {
321
327
  return;
322
328
  }
323
329
 
324
330
  let keys = new Selection(this.state.selectedKeys === 'all' ? this.getSelectAllKeys() : this.state.selectedKeys);
325
- if (keys.has(key)) {
326
- keys.delete(key);
331
+ if (keys.has(mappedKey)) {
332
+ keys.delete(mappedKey);
327
333
  // TODO: move anchor to last selected key...
328
334
  // Does `current` need to move here too?
329
- } else if (this.canSelectItem(key)) {
330
- keys.add(key);
331
- keys.anchorKey = key;
332
- keys.currentKey = key;
335
+ } else if (this.canSelectItem(mappedKey)) {
336
+ keys.add(mappedKey);
337
+ keys.anchorKey = mappedKey;
338
+ keys.currentKey = mappedKey;
333
339
  }
334
340
 
335
341
  if (this.disallowEmptySelection && keys.size === 0) {
@@ -347,13 +353,13 @@ export class SelectionManager implements MultipleSelectionManager {
347
353
  return;
348
354
  }
349
355
 
350
- key = this.getKey(key);
351
- if (key == null) {
356
+ let mappedKey = this.getKey(key);
357
+ if (mappedKey == null) {
352
358
  return;
353
359
  }
354
360
 
355
- let selection = this.canSelectItem(key)
356
- ? new Selection([key], key, key)
361
+ let selection = this.canSelectItem(mappedKey)
362
+ ? new Selection([mappedKey], mappedKey, mappedKey)
357
363
  : new Selection();
358
364
 
359
365
  this.state.setSelectedKeys(selection);
@@ -369,9 +375,9 @@ export class SelectionManager implements MultipleSelectionManager {
369
375
 
370
376
  let selection = new Selection();
371
377
  for (let key of keys) {
372
- key = this.getKey(key);
373
- if (key != null) {
374
- selection.add(key);
378
+ let mappedKey = this.getKey(key);
379
+ if (mappedKey != null) {
380
+ selection.add(mappedKey);
375
381
  if (this.selectionMode === 'single') {
376
382
  break;
377
383
  }
@@ -383,17 +389,17 @@ export class SelectionManager implements MultipleSelectionManager {
383
389
 
384
390
  private getSelectAllKeys() {
385
391
  let keys: Key[] = [];
386
- let addKeys = (key: Key) => {
392
+ let addKeys = (key: Key | null) => {
387
393
  while (key != null) {
388
394
  if (this.canSelectItem(key)) {
389
395
  let item = this.collection.getItem(key);
390
- if (item.type === 'item') {
396
+ if (item?.type === 'item') {
391
397
  keys.push(key);
392
398
  }
393
399
 
394
400
  // Add child keys. If cell selection is allowed, then include item children too.
395
- if (item.hasChildNodes && (this.allowsCellSelection || item.type !== 'item')) {
396
- 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);
397
403
  }
398
404
  }
399
405
 
package/src/types.ts CHANGED
@@ -10,7 +10,7 @@
10
10
  * governing permissions and limitations under the License.
11
11
  */
12
12
 
13
- import {DisabledBehavior, FocusStrategy, Key, LongPressEvent, PressEvent, Selection, SelectionBehavior, SelectionMode} from '@react-types/shared';
13
+ import {Collection, DisabledBehavior, FocusStrategy, Key, LongPressEvent, Node, PressEvent, Selection, SelectionBehavior, SelectionMode} from '@react-types/shared';
14
14
 
15
15
 
16
16
  export interface FocusState {
@@ -19,9 +19,9 @@ 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
26
  setFocusedKey(key: Key | null, child?: FocusStrategy): void
27
27
  }
@@ -107,5 +107,7 @@ export interface MultipleSelectionManager extends FocusState {
107
107
  /** Returns whether the given key is a hyperlink. */
108
108
  isLink(key: Key): boolean,
109
109
  /** Returns the props for the given item. */
110
- getItemProps(key: Key): any
110
+ getItemProps(key: Key): any,
111
+ /** The collection of nodes that the selection manager handles. */
112
+ collection: Collection<Node<unknown>>
111
113
  }
@@ -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
  }