react-arborist 3.5.0 → 3.6.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/README.md CHANGED
@@ -310,6 +310,7 @@ interface TreeProps<T> {
310
310
  openByDefault?: boolean;
311
311
  selectionFollowsFocus?: boolean;
312
312
  disableMultiSelection?: boolean;
313
+ disableSelect?: string | boolean | BoolFunc<T>;
313
314
  disableEdit?: string | boolean | BoolFunc<T>;
314
315
  disableDrag?: string | boolean | BoolFunc<T>;
315
316
  disableDrop?:
@@ -26,6 +26,7 @@ export declare class NodeApi<T = any> {
26
26
  get isOpen(): boolean;
27
27
  get isClosed(): boolean;
28
28
  get isEditable(): boolean;
29
+ get isSelectable(): boolean;
29
30
  get isEditing(): boolean;
30
31
  get isSelected(): boolean;
31
32
  get isOnlySelection(): boolean;
@@ -43,6 +43,9 @@ class NodeApi {
43
43
  get isEditable() {
44
44
  return this.tree.isEditable(this.data);
45
45
  }
46
+ get isSelectable() {
47
+ return this.tree.isSelectable(this.data);
48
+ }
46
49
  get isEditing() {
47
50
  return this.tree.editingId === this.id;
48
51
  }
@@ -136,7 +136,7 @@ export declare class TreeApi<T> {
136
136
  get(id: string | null): NodeApi<T> | null;
137
137
  at(index: number): NodeApi<T> | null;
138
138
  nodesBetween(startId: string | null, endId: string | null): NodeApi<T>[];
139
- indexOf(id: string | null | IdObj): number | null;
139
+ indexOf(id: Identity): number | null;
140
140
  get editingId(): string | null;
141
141
  createInternal(): Promise<void>;
142
142
  createLeaf(): Promise<void>;
@@ -145,11 +145,11 @@ export declare class TreeApi<T> {
145
145
  parentId?: null | string;
146
146
  index?: null | number;
147
147
  }): Promise<void>;
148
- delete(node: string | IdObj | null | string[] | IdObj[]): Promise<void>;
148
+ delete(node: Identity | string[] | IdObj[]): Promise<void>;
149
149
  edit(node: string | IdObj): Promise<EditResult>;
150
150
  submit(identity: Identity, value: string): Promise<void>;
151
151
  reset(): void;
152
- activate(id: string | IdObj | null): void;
152
+ activate(id: Identity): void;
153
153
  private resolveEdit;
154
154
  get selectedIds(): Set<string>;
155
155
  get selectedNodes(): NodeApi<T>[];
@@ -170,10 +170,11 @@ export declare class TreeApi<T> {
170
170
  selectContiguous(identity: Identity): void;
171
171
  deselectAll(): void;
172
172
  selectAll(): void;
173
+ private filterSelectableNodes;
173
174
  setSelection(args: {
174
175
  ids: (IdObj | string)[] | null;
175
- anchor: IdObj | string | null;
176
- mostRecent: IdObj | string | null;
176
+ anchor: Identity;
177
+ mostRecent: Identity;
177
178
  }): void;
178
179
  get cursorParentId(): string | null;
179
180
  get cursorOverFolder(): boolean;
@@ -202,10 +203,12 @@ export declare class TreeApi<T> {
202
203
  isOpen(id?: string): boolean;
203
204
  isEditable(data: T): boolean;
204
205
  isDraggable(data: T): boolean;
205
- isDragging(node: string | IdObj | null): boolean;
206
+ isSelectable(data: T): boolean;
207
+ private isActionPossible;
208
+ isDragging(node: Identity): boolean;
206
209
  isFocused(id: string): boolean;
207
210
  isMatch(node: NodeApi<T>): boolean;
208
- willReceiveDrop(node: string | IdObj | null): boolean;
211
+ willReceiveDrop(node: Identity): boolean;
209
212
  onFocus(): void;
210
213
  onBlur(): void;
211
214
  onItemsRendered(args: ListOnItemsRenderedProps): void;
@@ -327,20 +327,24 @@ class TreeApi {
327
327
  this.focus(this.at(index));
328
328
  }
329
329
  select(node, opts = {}) {
330
+ var _a;
330
331
  if (!node)
331
332
  return;
332
333
  const changeFocus = opts.focus !== false;
333
334
  const id = identify(node);
334
335
  if (changeFocus)
335
336
  this.dispatch((0, focus_slice_1.focus)(id));
336
- this.dispatch(selection_slice_1.actions.only(id));
337
- this.dispatch(selection_slice_1.actions.anchor(id));
338
- this.dispatch(selection_slice_1.actions.mostRecent(id));
337
+ if ((_a = this.get(id)) === null || _a === void 0 ? void 0 : _a.isSelectable) {
338
+ this.setSelection({
339
+ ids: [id],
340
+ anchor: id,
341
+ mostRecent: id,
342
+ });
343
+ }
339
344
  this.scrollTo(id, opts.align);
340
345
  if (this.focusedNode && changeFocus) {
341
346
  safeRun(this.props.onFocus, this.focusedNode);
342
347
  }
343
- safeRun(this.props.onSelect, this.selectedNodes);
344
348
  }
345
349
  deselect(node) {
346
350
  if (!node)
@@ -356,9 +360,11 @@ class TreeApi {
356
360
  const changeFocus = opts.focus !== false;
357
361
  if (changeFocus)
358
362
  this.dispatch((0, focus_slice_1.focus)(node.id));
359
- this.dispatch(selection_slice_1.actions.add(node.id));
360
- this.dispatch(selection_slice_1.actions.anchor(node.id));
361
- this.dispatch(selection_slice_1.actions.mostRecent(node.id));
363
+ if (node.isSelectable) {
364
+ this.dispatch(selection_slice_1.actions.add(node.id));
365
+ this.dispatch(selection_slice_1.actions.anchor(node.id));
366
+ this.dispatch(selection_slice_1.actions.mostRecent(node.id));
367
+ }
362
368
  this.scrollTo(node, opts.align);
363
369
  if (this.focusedNode && changeFocus) {
364
370
  safeRun(this.props.onFocus, this.focusedNode);
@@ -366,14 +372,18 @@ class TreeApi {
366
372
  safeRun(this.props.onSelect, this.selectedNodes);
367
373
  }
368
374
  selectContiguous(identity) {
375
+ var _a;
369
376
  if (!identity)
370
377
  return;
371
378
  const id = identify(identity);
372
- const { anchor, mostRecent } = this.state.nodes.selection;
373
379
  this.dispatch((0, focus_slice_1.focus)(id));
374
- this.dispatch(selection_slice_1.actions.remove(this.nodesBetween(anchor, mostRecent)));
375
- this.dispatch(selection_slice_1.actions.add(this.nodesBetween(anchor, identifyNull(id))));
376
- this.dispatch(selection_slice_1.actions.mostRecent(id));
380
+ if ((_a = this.get(id)) === null || _a === void 0 ? void 0 : _a.isSelectable) {
381
+ const { anchor, mostRecent } = this.state.nodes.selection;
382
+ const selectableNodes = this.filterSelectableNodes(this.nodesBetween(anchor, identifyNull(id)));
383
+ this.dispatch(selection_slice_1.actions.remove(this.nodesBetween(anchor, mostRecent)));
384
+ this.dispatch(selection_slice_1.actions.add(selectableNodes));
385
+ this.dispatch(selection_slice_1.actions.mostRecent(id));
386
+ }
377
387
  this.scrollTo(id);
378
388
  if (this.focusedNode)
379
389
  safeRun(this.props.onFocus, this.focusedNode);
@@ -384,17 +394,23 @@ class TreeApi {
384
394
  safeRun(this.props.onSelect, this.selectedNodes);
385
395
  }
386
396
  selectAll() {
387
- var _a;
397
+ var _a, _b, _c;
398
+ const allSelectableNodes = this.filterSelectableNodes(Object.keys(this.idToIndex));
388
399
  this.setSelection({
389
- ids: Object.keys(this.idToIndex),
390
- anchor: this.firstNode,
391
- mostRecent: this.lastNode,
400
+ ids: allSelectableNodes,
401
+ anchor: (_a = allSelectableNodes[0]) !== null && _a !== void 0 ? _a : null,
402
+ mostRecent: (_b = allSelectableNodes[allSelectableNodes.length - 1]) !== null && _b !== void 0 ? _b : null,
392
403
  });
393
- this.dispatch((0, focus_slice_1.focus)((_a = this.lastNode) === null || _a === void 0 ? void 0 : _a.id));
404
+ this.dispatch((0, focus_slice_1.focus)((_c = this.lastNode) === null || _c === void 0 ? void 0 : _c.id));
394
405
  if (this.focusedNode)
395
406
  safeRun(this.props.onFocus, this.focusedNode);
396
407
  safeRun(this.props.onSelect, this.selectedNodes);
397
408
  }
409
+ filterSelectableNodes(nodes) {
410
+ return nodes
411
+ .map((n) => this.get(identify(n)))
412
+ .filter((n) => !!n && n.isSelectable);
413
+ }
398
414
  setSelection(args) {
399
415
  var _a;
400
416
  const ids = new Set((_a = args.ids) === null || _a === void 0 ? void 0 : _a.map(identify));
@@ -592,12 +608,16 @@ class TreeApi {
592
608
  }
593
609
  }
594
610
  isEditable(data) {
595
- const check = this.props.disableEdit || (() => false);
596
- return !utils.access(data, check);
611
+ return this.isActionPossible(data, this.props.disableEdit);
597
612
  }
598
613
  isDraggable(data) {
599
- const check = this.props.disableDrag || (() => false);
600
- return !utils.access(data, check);
614
+ return this.isActionPossible(data, this.props.disableDrag);
615
+ }
616
+ isSelectable(data) {
617
+ return this.isActionPossible(data, this.props.disableSelect);
618
+ }
619
+ isActionPossible(data, disabler = () => false) {
620
+ return !utils.access(data, disabler);
601
621
  }
602
622
  isDragging(node) {
603
623
  const id = identifyNull(node);
@@ -31,6 +31,7 @@ export interface TreeProps<T> {
31
31
  openByDefault?: boolean;
32
32
  selectionFollowsFocus?: boolean;
33
33
  disableMultiSelection?: boolean;
34
+ disableSelect?: string | boolean | BoolFunc<T>;
34
35
  disableEdit?: string | boolean | BoolFunc<T>;
35
36
  disableDrag?: string | boolean | BoolFunc<T>;
36
37
  disableDrop?: string | boolean | ((args: {
@@ -26,6 +26,7 @@ export declare class NodeApi<T = any> {
26
26
  get isOpen(): boolean;
27
27
  get isClosed(): boolean;
28
28
  get isEditable(): boolean;
29
+ get isSelectable(): boolean;
29
30
  get isEditing(): boolean;
30
31
  get isSelected(): boolean;
31
32
  get isOnlySelection(): boolean;
@@ -40,6 +40,9 @@ export class NodeApi {
40
40
  get isEditable() {
41
41
  return this.tree.isEditable(this.data);
42
42
  }
43
+ get isSelectable() {
44
+ return this.tree.isSelectable(this.data);
45
+ }
43
46
  get isEditing() {
44
47
  return this.tree.editingId === this.id;
45
48
  }
@@ -136,7 +136,7 @@ export declare class TreeApi<T> {
136
136
  get(id: string | null): NodeApi<T> | null;
137
137
  at(index: number): NodeApi<T> | null;
138
138
  nodesBetween(startId: string | null, endId: string | null): NodeApi<T>[];
139
- indexOf(id: string | null | IdObj): number | null;
139
+ indexOf(id: Identity): number | null;
140
140
  get editingId(): string | null;
141
141
  createInternal(): Promise<void>;
142
142
  createLeaf(): Promise<void>;
@@ -145,11 +145,11 @@ export declare class TreeApi<T> {
145
145
  parentId?: null | string;
146
146
  index?: null | number;
147
147
  }): Promise<void>;
148
- delete(node: string | IdObj | null | string[] | IdObj[]): Promise<void>;
148
+ delete(node: Identity | string[] | IdObj[]): Promise<void>;
149
149
  edit(node: string | IdObj): Promise<EditResult>;
150
150
  submit(identity: Identity, value: string): Promise<void>;
151
151
  reset(): void;
152
- activate(id: string | IdObj | null): void;
152
+ activate(id: Identity): void;
153
153
  private resolveEdit;
154
154
  get selectedIds(): Set<string>;
155
155
  get selectedNodes(): NodeApi<T>[];
@@ -170,10 +170,11 @@ export declare class TreeApi<T> {
170
170
  selectContiguous(identity: Identity): void;
171
171
  deselectAll(): void;
172
172
  selectAll(): void;
173
+ private filterSelectableNodes;
173
174
  setSelection(args: {
174
175
  ids: (IdObj | string)[] | null;
175
- anchor: IdObj | string | null;
176
- mostRecent: IdObj | string | null;
176
+ anchor: Identity;
177
+ mostRecent: Identity;
177
178
  }): void;
178
179
  get cursorParentId(): string | null;
179
180
  get cursorOverFolder(): boolean;
@@ -202,10 +203,12 @@ export declare class TreeApi<T> {
202
203
  isOpen(id?: string): boolean;
203
204
  isEditable(data: T): boolean;
204
205
  isDraggable(data: T): boolean;
205
- isDragging(node: string | IdObj | null): boolean;
206
+ isSelectable(data: T): boolean;
207
+ private isActionPossible;
208
+ isDragging(node: Identity): boolean;
206
209
  isFocused(id: string): boolean;
207
210
  isMatch(node: NodeApi<T>): boolean;
208
- willReceiveDrop(node: string | IdObj | null): boolean;
211
+ willReceiveDrop(node: Identity): boolean;
209
212
  onFocus(): void;
210
213
  onBlur(): void;
211
214
  onItemsRendered(args: ListOnItemsRenderedProps): void;
@@ -301,20 +301,24 @@ export class TreeApi {
301
301
  this.focus(this.at(index));
302
302
  }
303
303
  select(node, opts = {}) {
304
+ var _a;
304
305
  if (!node)
305
306
  return;
306
307
  const changeFocus = opts.focus !== false;
307
308
  const id = identify(node);
308
309
  if (changeFocus)
309
310
  this.dispatch(focus(id));
310
- this.dispatch(selection.only(id));
311
- this.dispatch(selection.anchor(id));
312
- this.dispatch(selection.mostRecent(id));
311
+ if ((_a = this.get(id)) === null || _a === void 0 ? void 0 : _a.isSelectable) {
312
+ this.setSelection({
313
+ ids: [id],
314
+ anchor: id,
315
+ mostRecent: id,
316
+ });
317
+ }
313
318
  this.scrollTo(id, opts.align);
314
319
  if (this.focusedNode && changeFocus) {
315
320
  safeRun(this.props.onFocus, this.focusedNode);
316
321
  }
317
- safeRun(this.props.onSelect, this.selectedNodes);
318
322
  }
319
323
  deselect(node) {
320
324
  if (!node)
@@ -330,9 +334,11 @@ export class TreeApi {
330
334
  const changeFocus = opts.focus !== false;
331
335
  if (changeFocus)
332
336
  this.dispatch(focus(node.id));
333
- this.dispatch(selection.add(node.id));
334
- this.dispatch(selection.anchor(node.id));
335
- this.dispatch(selection.mostRecent(node.id));
337
+ if (node.isSelectable) {
338
+ this.dispatch(selection.add(node.id));
339
+ this.dispatch(selection.anchor(node.id));
340
+ this.dispatch(selection.mostRecent(node.id));
341
+ }
336
342
  this.scrollTo(node, opts.align);
337
343
  if (this.focusedNode && changeFocus) {
338
344
  safeRun(this.props.onFocus, this.focusedNode);
@@ -340,14 +346,18 @@ export class TreeApi {
340
346
  safeRun(this.props.onSelect, this.selectedNodes);
341
347
  }
342
348
  selectContiguous(identity) {
349
+ var _a;
343
350
  if (!identity)
344
351
  return;
345
352
  const id = identify(identity);
346
- const { anchor, mostRecent } = this.state.nodes.selection;
347
353
  this.dispatch(focus(id));
348
- this.dispatch(selection.remove(this.nodesBetween(anchor, mostRecent)));
349
- this.dispatch(selection.add(this.nodesBetween(anchor, identifyNull(id))));
350
- this.dispatch(selection.mostRecent(id));
354
+ if ((_a = this.get(id)) === null || _a === void 0 ? void 0 : _a.isSelectable) {
355
+ const { anchor, mostRecent } = this.state.nodes.selection;
356
+ const selectableNodes = this.filterSelectableNodes(this.nodesBetween(anchor, identifyNull(id)));
357
+ this.dispatch(selection.remove(this.nodesBetween(anchor, mostRecent)));
358
+ this.dispatch(selection.add(selectableNodes));
359
+ this.dispatch(selection.mostRecent(id));
360
+ }
351
361
  this.scrollTo(id);
352
362
  if (this.focusedNode)
353
363
  safeRun(this.props.onFocus, this.focusedNode);
@@ -358,17 +368,23 @@ export class TreeApi {
358
368
  safeRun(this.props.onSelect, this.selectedNodes);
359
369
  }
360
370
  selectAll() {
361
- var _a;
371
+ var _a, _b, _c;
372
+ const allSelectableNodes = this.filterSelectableNodes(Object.keys(this.idToIndex));
362
373
  this.setSelection({
363
- ids: Object.keys(this.idToIndex),
364
- anchor: this.firstNode,
365
- mostRecent: this.lastNode,
374
+ ids: allSelectableNodes,
375
+ anchor: (_a = allSelectableNodes[0]) !== null && _a !== void 0 ? _a : null,
376
+ mostRecent: (_b = allSelectableNodes[allSelectableNodes.length - 1]) !== null && _b !== void 0 ? _b : null,
366
377
  });
367
- this.dispatch(focus((_a = this.lastNode) === null || _a === void 0 ? void 0 : _a.id));
378
+ this.dispatch(focus((_c = this.lastNode) === null || _c === void 0 ? void 0 : _c.id));
368
379
  if (this.focusedNode)
369
380
  safeRun(this.props.onFocus, this.focusedNode);
370
381
  safeRun(this.props.onSelect, this.selectedNodes);
371
382
  }
383
+ filterSelectableNodes(nodes) {
384
+ return nodes
385
+ .map((n) => this.get(identify(n)))
386
+ .filter((n) => !!n && n.isSelectable);
387
+ }
372
388
  setSelection(args) {
373
389
  var _a;
374
390
  const ids = new Set((_a = args.ids) === null || _a === void 0 ? void 0 : _a.map(identify));
@@ -566,12 +582,16 @@ export class TreeApi {
566
582
  }
567
583
  }
568
584
  isEditable(data) {
569
- const check = this.props.disableEdit || (() => false);
570
- return !utils.access(data, check);
585
+ return this.isActionPossible(data, this.props.disableEdit);
571
586
  }
572
587
  isDraggable(data) {
573
- const check = this.props.disableDrag || (() => false);
574
- return !utils.access(data, check);
588
+ return this.isActionPossible(data, this.props.disableDrag);
589
+ }
590
+ isSelectable(data) {
591
+ return this.isActionPossible(data, this.props.disableSelect);
592
+ }
593
+ isActionPossible(data, disabler = () => false) {
594
+ return !utils.access(data, disabler);
575
595
  }
576
596
  isDragging(node) {
577
597
  const id = identifyNull(node);
@@ -31,6 +31,7 @@ export interface TreeProps<T> {
31
31
  openByDefault?: boolean;
32
32
  selectionFollowsFocus?: boolean;
33
33
  disableMultiSelection?: boolean;
34
+ disableSelect?: string | boolean | BoolFunc<T>;
34
35
  disableEdit?: string | boolean | BoolFunc<T>;
35
36
  disableDrag?: string | boolean | BoolFunc<T>;
36
37
  disableDrop?: string | boolean | ((args: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-arborist",
3
- "version": "3.5.0",
3
+ "version": "3.6.0",
4
4
  "license": "MIT",
5
5
  "source": "src/index.ts",
6
6
  "main": "dist/main/index.js",
@@ -59,6 +59,10 @@ export class NodeApi<T = any> {
59
59
  return this.tree.isEditable(this.data);
60
60
  }
61
61
 
62
+ get isSelectable() {
63
+ return this.tree.isSelectable(this.data);
64
+ }
65
+
62
66
  get isEditing() {
63
67
  return this.tree.editingId === this.id;
64
68
  }
@@ -1,5 +1,5 @@
1
1
  import { EditResult } from "../types/handlers";
2
- import { Identity, IdObj } from "../types/utils";
2
+ import { BoolFunc, Identity, IdObj } from "../types/utils";
3
3
  import { TreeProps } from "../types/tree-props";
4
4
  import { MutableRefObject } from "react";
5
5
  import { Align, FixedSizeList, ListOnItemsRenderedProps } from "react-window";
@@ -169,7 +169,7 @@ export class TreeApi<T> {
169
169
  return this.visibleNodes.slice(start, end + 1);
170
170
  }
171
171
 
172
- indexOf(id: string | null | IdObj) {
172
+ indexOf(id: Identity) {
173
173
  const key = utils.identifyNull(id);
174
174
  if (!key) return null;
175
175
  return this.idToIndex[key];
@@ -219,7 +219,7 @@ export class TreeApi<T> {
219
219
  }
220
220
  }
221
221
 
222
- async delete(node: string | IdObj | null | string[] | IdObj[]) {
222
+ async delete(node: Identity | string[] | IdObj[]) {
223
223
  if (!node) return;
224
224
  const idents = Array.isArray(node) ? node : [node];
225
225
  const ids = idents.map(identify);
@@ -256,7 +256,7 @@ export class TreeApi<T> {
256
256
  setTimeout(() => this.onFocus()); // Return focus to element;
257
257
  }
258
258
 
259
- activate(id: string | IdObj | null) {
259
+ activate(id: Identity) {
260
260
  const node = this.get(identifyNull(id));
261
261
  if (!node) return;
262
262
  safeRun(this.props.onActivate, node);
@@ -328,14 +328,17 @@ export class TreeApi<T> {
328
328
  const changeFocus = opts.focus !== false;
329
329
  const id = identify(node);
330
330
  if (changeFocus) this.dispatch(focus(id));
331
- this.dispatch(selection.only(id));
332
- this.dispatch(selection.anchor(id));
333
- this.dispatch(selection.mostRecent(id));
331
+ if (this.get(id)?.isSelectable) {
332
+ this.setSelection({
333
+ ids: [id],
334
+ anchor: id,
335
+ mostRecent: id,
336
+ });
337
+ }
334
338
  this.scrollTo(id, opts.align);
335
339
  if (this.focusedNode && changeFocus) {
336
340
  safeRun(this.props.onFocus, this.focusedNode);
337
341
  }
338
- safeRun(this.props.onSelect, this.selectedNodes);
339
342
  }
340
343
 
341
344
  deselect(node: Identity) {
@@ -350,9 +353,11 @@ export class TreeApi<T> {
350
353
  if (!node) return;
351
354
  const changeFocus = opts.focus !== false;
352
355
  if (changeFocus) this.dispatch(focus(node.id));
353
- this.dispatch(selection.add(node.id));
354
- this.dispatch(selection.anchor(node.id));
355
- this.dispatch(selection.mostRecent(node.id));
356
+ if (node.isSelectable) {
357
+ this.dispatch(selection.add(node.id));
358
+ this.dispatch(selection.anchor(node.id));
359
+ this.dispatch(selection.mostRecent(node.id));
360
+ }
356
361
  this.scrollTo(node, opts.align);
357
362
  if (this.focusedNode && changeFocus) {
358
363
  safeRun(this.props.onFocus, this.focusedNode);
@@ -363,11 +368,16 @@ export class TreeApi<T> {
363
368
  selectContiguous(identity: Identity) {
364
369
  if (!identity) return;
365
370
  const id = identify(identity);
366
- const { anchor, mostRecent } = this.state.nodes.selection;
367
371
  this.dispatch(focus(id));
368
- this.dispatch(selection.remove(this.nodesBetween(anchor, mostRecent)));
369
- this.dispatch(selection.add(this.nodesBetween(anchor, identifyNull(id))));
370
- this.dispatch(selection.mostRecent(id));
372
+ if (this.get(id)?.isSelectable) {
373
+ const { anchor, mostRecent } = this.state.nodes.selection;
374
+ const selectableNodes = this.filterSelectableNodes(
375
+ this.nodesBetween(anchor, identifyNull(id)),
376
+ );
377
+ this.dispatch(selection.remove(this.nodesBetween(anchor, mostRecent)));
378
+ this.dispatch(selection.add(selectableNodes));
379
+ this.dispatch(selection.mostRecent(id));
380
+ }
371
381
  this.scrollTo(id);
372
382
  if (this.focusedNode) safeRun(this.props.onFocus, this.focusedNode);
373
383
  safeRun(this.props.onSelect, this.selectedNodes);
@@ -379,20 +389,29 @@ export class TreeApi<T> {
379
389
  }
380
390
 
381
391
  selectAll() {
392
+ const allSelectableNodes = this.filterSelectableNodes(
393
+ Object.keys(this.idToIndex),
394
+ );
382
395
  this.setSelection({
383
- ids: Object.keys(this.idToIndex),
384
- anchor: this.firstNode,
385
- mostRecent: this.lastNode,
396
+ ids: allSelectableNodes,
397
+ anchor: allSelectableNodes[0] ?? null,
398
+ mostRecent: allSelectableNodes[allSelectableNodes.length - 1] ?? null,
386
399
  });
387
400
  this.dispatch(focus(this.lastNode?.id));
388
401
  if (this.focusedNode) safeRun(this.props.onFocus, this.focusedNode);
389
402
  safeRun(this.props.onSelect, this.selectedNodes);
390
403
  }
391
404
 
405
+ private filterSelectableNodes(nodes: (IdObj | string)[]) {
406
+ return nodes
407
+ .map((n) => this.get(identify(n)))
408
+ .filter((n): n is NodeApi<T> => !!n && n.isSelectable);
409
+ }
410
+
392
411
  setSelection(args: {
393
412
  ids: (IdObj | string)[] | null;
394
- anchor: IdObj | string | null;
395
- mostRecent: IdObj | string | null;
413
+ anchor: Identity;
414
+ mostRecent: Identity;
396
415
  }) {
397
416
  const ids = new Set(args.ids?.map(identify));
398
417
  const anchor = identifyNull(args.anchor);
@@ -596,16 +615,25 @@ export class TreeApi<T> {
596
615
  }
597
616
 
598
617
  isEditable(data: T) {
599
- const check = this.props.disableEdit || (() => false);
600
- return !utils.access(data, check);
618
+ return this.isActionPossible(data, this.props.disableEdit);
601
619
  }
602
620
 
603
621
  isDraggable(data: T) {
604
- const check = this.props.disableDrag || (() => false);
605
- return !utils.access(data, check);
622
+ return this.isActionPossible(data, this.props.disableDrag);
623
+ }
624
+
625
+ isSelectable(data: T) {
626
+ return this.isActionPossible(data, this.props.disableSelect);
627
+ }
628
+
629
+ private isActionPossible(
630
+ data: T,
631
+ disabler: string | boolean | BoolFunc<T> = () => false,
632
+ ) {
633
+ return !utils.access(data, disabler);
606
634
  }
607
635
 
608
- isDragging(node: string | IdObj | null) {
636
+ isDragging(node: Identity) {
609
637
  const id = identifyNull(node);
610
638
  if (!id) return false;
611
639
  return this.state.nodes.drag.id === id;
@@ -619,7 +647,7 @@ export class TreeApi<T> {
619
647
  return this.matchFn(node);
620
648
  }
621
649
 
622
- willReceiveDrop(node: string | IdObj | null) {
650
+ willReceiveDrop(node: Identity) {
623
651
  const id = identifyNull(node);
624
652
  if (!id) return false;
625
653
  const { destinationParentId, destinationIndex } = this.state.nodes.drag;
@@ -41,6 +41,7 @@ export interface TreeProps<T> {
41
41
  openByDefault?: boolean;
42
42
  selectionFollowsFocus?: boolean;
43
43
  disableMultiSelection?: boolean;
44
+ disableSelect?: string | boolean | BoolFunc<T>;
44
45
  disableEdit?: string | boolean | BoolFunc<T>;
45
46
  disableDrag?: string | boolean | BoolFunc<T>;
46
47
  disableDrop?: