@proyecto-viviana/solid-stately 0.3.0 → 0.4.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.
Files changed (63) hide show
  1. package/dist/calendar/IncompleteDate.d.ts +61 -0
  2. package/dist/calendar/createCalendarState.d.ts +20 -10
  3. package/dist/calendar/createDateFieldState.d.ts +10 -3
  4. package/dist/calendar/createRangeCalendarState.d.ts +10 -2
  5. package/dist/calendar/index.d.ts +1 -1
  6. package/dist/collections/createListState.d.ts +4 -1
  7. package/dist/collections/createSelectionState.d.ts +17 -5
  8. package/dist/collections/getChildNodes.d.ts +11 -0
  9. package/dist/collections/index.d.ts +2 -1
  10. package/dist/combobox/createComboBoxState.d.ts +7 -0
  11. package/dist/dnd/index.d.ts +1 -1
  12. package/dist/dnd/types.d.ts +3 -1
  13. package/dist/grid/types.d.ts +17 -3
  14. package/dist/index.d.ts +6 -6
  15. package/dist/index.js +1570 -425
  16. package/dist/index.js.map +1 -1
  17. package/dist/select/createSelectState.d.ts +6 -0
  18. package/dist/selection/Selection.d.ts +16 -0
  19. package/dist/selection/SelectionManager.d.ts +110 -0
  20. package/dist/selection/createMultipleSelectionState.d.ts +59 -0
  21. package/dist/selection/index.d.ts +1 -0
  22. package/dist/table/TableCollection.d.ts +30 -0
  23. package/dist/table/createTreeGridState.d.ts +15 -0
  24. package/dist/table/index.d.ts +2 -1
  25. package/dist/table/types.d.ts +58 -0
  26. package/dist/toast/createToastState.d.ts +16 -0
  27. package/dist/toast/index.d.ts +1 -1
  28. package/dist/tree/types.d.ts +14 -6
  29. package/package.json +1 -1
  30. package/src/calendar/IncompleteDate.ts +430 -0
  31. package/src/calendar/createCalendarState.ts +104 -37
  32. package/src/calendar/createDateFieldState.ts +262 -261
  33. package/src/calendar/createRangeCalendarState.ts +131 -19
  34. package/src/calendar/index.ts +2 -0
  35. package/src/collections/createListState.ts +50 -19
  36. package/src/collections/createMenuState.ts +3 -0
  37. package/src/collections/createSelectionState.ts +38 -28
  38. package/src/collections/getChildNodes.ts +98 -0
  39. package/src/collections/index.ts +9 -0
  40. package/src/combobox/createComboBoxState.ts +8 -0
  41. package/src/dnd/index.d.ts +1 -0
  42. package/src/dnd/index.ts +1 -0
  43. package/src/dnd/types.d.ts +3 -1
  44. package/src/dnd/types.ts +4 -1
  45. package/src/grid/createGridState.ts +36 -2
  46. package/src/grid/types.ts +17 -3
  47. package/src/index.d.ts +1 -0
  48. package/src/index.ts +11 -0
  49. package/src/select/createSelectState.ts +15 -2
  50. package/src/selection/Selection.ts +28 -0
  51. package/src/selection/SelectionManager.ts +526 -0
  52. package/src/selection/createMultipleSelectionState.ts +247 -0
  53. package/src/selection/index.ts +6 -0
  54. package/src/table/TableCollection.ts +213 -43
  55. package/src/table/createTableState.ts +20 -0
  56. package/src/table/createTreeGridState.ts +146 -0
  57. package/src/table/index.ts +3 -0
  58. package/src/table/types.ts +66 -0
  59. package/src/tabs/createTabListState.ts +11 -9
  60. package/src/toast/createToastState.ts +43 -10
  61. package/src/toast/index.ts +1 -0
  62. package/src/tree/createTreeState.ts +37 -6
  63. package/src/tree/types.ts +14 -6
@@ -0,0 +1,146 @@
1
+ /**
2
+ * Tree grid state management for expandable Table rows.
3
+ * Based on @react-stately/table/useTreeGridState (the `UNSTABLE_` tree-grid feature).
4
+ *
5
+ * Owns the expanded-keys signal (controlled or uncontrolled), rebuilds the table
6
+ * collection from it, and layers the expansion API on top of {@link createTableState}.
7
+ */
8
+
9
+ import { createMemo, createSignal, type Accessor } from "solid-js";
10
+ import { createTableState } from "./createTableState";
11
+ import { createTableCollection, TableCollection } from "./TableCollection";
12
+ import type { Key } from "../collections/types";
13
+ import type { TreeGridState, TreeGridStateOptions } from "./types";
14
+
15
+ /**
16
+ * Creates state management for a tree grid (a table with expandable rows). Handles the
17
+ * expanded rows in addition to the selection, sort and focus handled by the table state.
18
+ */
19
+ export function createTreeGridState<
20
+ T extends object,
21
+ C extends TableCollection<T> = TableCollection<T>,
22
+ >(options: Accessor<TreeGridStateOptions<T, C>>): TreeGridState<T, C> {
23
+ const getOptions = () => options();
24
+
25
+ // Expanded keys: controlled via `UNSTABLE_expandedKeys`, otherwise the internal signal
26
+ // seeded from `UNSTABLE_defaultExpandedKeys`.
27
+ const [internalExpandedKeys, setInternalExpandedKeys] = createSignal<"all" | Set<Key>>(
28
+ getInitialExpanded(getOptions().UNSTABLE_defaultExpandedKeys),
29
+ );
30
+
31
+ const expandedKeys = createMemo<"all" | Set<Key>>(() => {
32
+ const controlled = getOptions().UNSTABLE_expandedKeys;
33
+ if (controlled !== undefined) {
34
+ return convertExpanded(controlled);
35
+ }
36
+ return internalExpandedKeys();
37
+ });
38
+
39
+ // Rebuild the tree collection whenever the expanded keys (or the data) change.
40
+ const collection = createMemo(
41
+ () =>
42
+ createTableCollection<T>({
43
+ columns: getOptions().columns,
44
+ rows: getOptions().rows,
45
+ getKey: getOptions().getKey,
46
+ getTextValue: getOptions().getTextValue,
47
+ showSelectionCheckboxes: getOptions().showSelectionCheckboxes ?? false,
48
+ rowHeaderColumnKeys: getOptions().rowHeaderColumnKeys,
49
+ expandedKeys: expandedKeys(),
50
+ }) as unknown as C,
51
+ );
52
+
53
+ const tableState = createTableState<T, C>(() => ({
54
+ collection: collection(),
55
+ disabledKeys: getOptions().disabledKeys,
56
+ focusMode: getOptions().focusMode,
57
+ selectionMode: getOptions().selectionMode,
58
+ selectionBehavior: getOptions().selectionBehavior,
59
+ disallowEmptySelection: getOptions().disallowEmptySelection,
60
+ selectedKeys: getOptions().selectedKeys,
61
+ defaultSelectedKeys: getOptions().defaultSelectedKeys,
62
+ onSelectionChange: getOptions().onSelectionChange,
63
+ sortDescriptor: getOptions().sortDescriptor,
64
+ onSortChange: getOptions().onSortChange,
65
+ showSelectionCheckboxes: getOptions().showSelectionCheckboxes,
66
+ }));
67
+
68
+ const toggleKey = (key: Key) => {
69
+ const updated = toggleExpandedKey(expandedKeys(), key, collection());
70
+
71
+ // Only own the value when uncontrolled.
72
+ if (getOptions().UNSTABLE_expandedKeys === undefined) {
73
+ setInternalExpandedKeys(updated);
74
+ }
75
+ getOptions().UNSTABLE_onExpandedChange?.(updated);
76
+ };
77
+
78
+ // Layer the expansion API onto the table state object, preserving the reactive getters
79
+ // already defined on it (spreading would eagerly evaluate them and break reactivity).
80
+ const treeGridState = tableState as TreeGridState<T, C>;
81
+ Object.defineProperty(treeGridState, "expandedKeys", {
82
+ get: () => expandedKeys(),
83
+ configurable: true,
84
+ enumerable: true,
85
+ });
86
+ Object.defineProperty(treeGridState, "treeColumn", {
87
+ get: () => collection().treeColumn ?? null,
88
+ configurable: true,
89
+ enumerable: true,
90
+ });
91
+ Object.defineProperty(treeGridState, "keyMap", {
92
+ get: () => collection().keyMap,
93
+ configurable: true,
94
+ enumerable: true,
95
+ });
96
+ Object.defineProperty(treeGridState, "userColumnCount", {
97
+ get: () => collection().userColumnCount,
98
+ configurable: true,
99
+ enumerable: true,
100
+ });
101
+ treeGridState.toggleKey = toggleKey;
102
+
103
+ return treeGridState;
104
+ }
105
+
106
+ /**
107
+ * Computes the next expanded-key set when a row is toggled. Mirrors the upstream
108
+ * `toggleKey`: collapsing from `'all'` materialises every expandable row first.
109
+ */
110
+ function toggleExpandedKey<T>(
111
+ current: "all" | Set<Key>,
112
+ key: Key,
113
+ collection: TableCollection<T>,
114
+ ): Set<Key> {
115
+ let updated: Set<Key>;
116
+ if (current === "all") {
117
+ updated = new Set(
118
+ collection.rows
119
+ .filter((row) => row.type === "item" && row.isExpandable)
120
+ .map((row) => row.key),
121
+ );
122
+ updated.delete(key);
123
+ } else {
124
+ updated = new Set(current);
125
+ if (updated.has(key)) {
126
+ updated.delete(key);
127
+ } else {
128
+ updated.add(key);
129
+ }
130
+ }
131
+ return updated;
132
+ }
133
+
134
+ function getInitialExpanded(defaultExpanded?: "all" | Iterable<Key>): "all" | Set<Key> {
135
+ if (defaultExpanded === undefined) {
136
+ return new Set();
137
+ }
138
+ return convertExpanded(defaultExpanded);
139
+ }
140
+
141
+ function convertExpanded(expanded: "all" | Iterable<Key>): "all" | Set<Key> {
142
+ if (!expanded) {
143
+ return new Set<Key>();
144
+ }
145
+ return expanded === "all" ? "all" : new Set(expanded);
146
+ }
@@ -4,12 +4,15 @@
4
4
  */
5
5
 
6
6
  export { createTableState } from "./createTableState";
7
+ export { createTreeGridState } from "./createTreeGridState";
7
8
  export { createTableColumnResizeState } from "./createTableColumnResizeState";
8
9
  export { TableCollection, createTableCollection } from "./TableCollection";
9
10
  export type {
10
11
  TableState,
11
12
  TableStateOptions,
12
13
  TableCollection as ITableCollection,
14
+ TreeGridState,
15
+ TreeGridStateOptions,
13
16
  SortDescriptor,
14
17
  SortDirection,
15
18
  Sortable,
@@ -46,6 +46,8 @@ export interface TableCollection<T = unknown> extends GridCollection<T> {
46
46
  readonly head?: GridNode<T>;
47
47
  /** The node that makes up the body of the table. */
48
48
  readonly body: GridNode<T>;
49
+ /** The key of the column that renders the tree-grid expand/collapse affordance, if any. */
50
+ readonly treeColumn?: Key | null;
49
51
  }
50
52
 
51
53
  /**
@@ -64,6 +66,59 @@ export interface TableState<T, C extends TableCollection<T> = TableCollection<T>
64
66
  readonly sortDescriptor: SortDescriptor | null;
65
67
  /** Sort by the given column and direction. */
66
68
  sort(columnKey: Key, direction?: SortDirection): void;
69
+ /**
70
+ * The set of expanded row keys for tree grids. A plain table reports an empty set.
71
+ */
72
+ readonly expandedKeys: "all" | Set<Key>;
73
+ /**
74
+ * The key of the column that renders the expand/collapse affordance, or `null` for a
75
+ * plain (non-tree) table.
76
+ */
77
+ readonly treeColumn: Key | null;
78
+ /** Toggles the expanded state of a row by its key (no-op for a plain table). */
79
+ toggleKey(key: Key): void;
80
+ }
81
+
82
+ /**
83
+ * State for a tree grid (expandable rows). Mirrors `@react-stately/table`'s
84
+ * `UNSTABLE_useTreeGridState` return value.
85
+ */
86
+ export interface TreeGridState<
87
+ T,
88
+ C extends TableCollection<T> = TableCollection<T>,
89
+ > extends TableState<T, C> {
90
+ /** A set of keys for rows that are expanded. */
91
+ readonly expandedKeys: "all" | Set<Key>;
92
+ /** The key map containing nodes representing the collection's tree grid structure. */
93
+ readonly keyMap: Map<Key, GridNode<T>>;
94
+ /** The number of leaf columns provided by the user (excludes selection/drag columns). */
95
+ readonly userColumnCount: number;
96
+ }
97
+
98
+ /**
99
+ * Options for creating tree grid state. Mirrors `TreeGridStateProps` upstream; the
100
+ * expansion props keep the `UNSTABLE_` prefix while the feature is unstable.
101
+ */
102
+ export interface TreeGridStateOptions<
103
+ T,
104
+ C extends TableCollection<T> = TableCollection<T>,
105
+ > extends Omit<TableStateOptions<T, C>, "collection"> {
106
+ /** Column definitions. */
107
+ columns: ColumnDefinition<T>[];
108
+ /** Row definitions (may contain `childRows` for nested rows). */
109
+ rows: RowDefinition<T>[];
110
+ /** Function to get the key from a data item. */
111
+ getKey?: (item: T) => Key;
112
+ /** Function to get the text value from a data item. */
113
+ getTextValue?: (item: T, column: ColumnDefinition<T>) => string;
114
+ /** Set of column keys that serve as row headers. */
115
+ rowHeaderColumnKeys?: Set<Key>;
116
+ /** The currently expanded keys in the collection (controlled). */
117
+ UNSTABLE_expandedKeys?: "all" | Iterable<Key>;
118
+ /** The initial expanded keys in the collection (uncontrolled). */
119
+ UNSTABLE_defaultExpandedKeys?: "all" | Iterable<Key>;
120
+ /** Handler that is called when rows are expanded or collapsed. */
121
+ UNSTABLE_onExpandedChange?: (keys: Set<Key>) => void;
67
122
  }
68
123
 
69
124
  /**
@@ -77,6 +132,8 @@ export interface TableStateOptions<
77
132
  collection: C;
78
133
  /** Keys of disabled rows. */
79
134
  disabledKeys?: Iterable<Key>;
135
+ /** Whether `disabledKeys` applies to all interactions, or only selection. @default "all" */
136
+ disabledBehavior?: "selection" | "all";
80
137
  /** Focus mode: 'row' or 'cell'. */
81
138
  focusMode?: "row" | "cell";
82
139
  /** Selection mode. */
@@ -151,4 +208,13 @@ export interface TableCollectionOptions<T = unknown> {
151
208
  showSelectionCheckboxes?: boolean;
152
209
  /** Set of column keys that serve as row headers. */
153
210
  rowHeaderColumnKeys?: Set<Key>;
211
+ /**
212
+ * The set of expanded row keys. Passing this (even an empty set) builds the collection
213
+ * in tree-grid mode: `childRows` are walked recursively, only rows whose ancestors are
214
+ * all expanded are flattened into the navigable body, and {@link TableCollection.treeColumn}
215
+ * resolves to a real column. Omit it for a plain (non-tree) table.
216
+ */
217
+ expandedKeys?: "all" | Set<Key>;
218
+ /** Explicit tree column key. Defaults to the first row-header column in tree-grid mode. */
219
+ treeColumn?: Key | null;
154
220
  }
@@ -199,17 +199,19 @@ export function createTabListState<T = unknown>(
199
199
  const setFocusedKey = (key: Key | null, childStrategy?: FocusStrategy) => {
200
200
  setFocusedKeyInternal(key);
201
201
  setChildFocusStrategy(childStrategy ?? null);
202
+ };
202
203
 
203
- // In automatic mode, selecting follows focus
204
- if (
205
- keyboardActivation() === "automatic" &&
206
- key !== null &&
207
- key !== selectedKey() &&
208
- !isKeyDisabled(key)
209
- ) {
210
- setSelectedKey(key);
204
+ // If the tab list doesn't have focus and the selected key changes, or if there
205
+ // isn't a focused key yet, move the focused key to the selected key so the
206
+ // roving tabIndex stays on the selected tab (mirrors useTabListState's effect).
207
+ let lastSelectedKey: Key | null = selectedKey();
208
+ createComputed(() => {
209
+ const sel = selectedKey();
210
+ if ((sel !== null && focusedKey() === null) || (!isFocused() && sel !== lastSelectedKey)) {
211
+ setFocusedKey(sel);
211
212
  }
212
- };
213
+ lastSelectedKey = sel;
214
+ });
213
215
 
214
216
  // Keep uncontrolled selection valid as items/disabled keys change.
215
217
  createComputed(() => {
@@ -31,11 +31,20 @@ export interface QueuedToast<T> {
31
31
  timeout?: number;
32
32
  }
33
33
 
34
+ /** The kind of queue mutation that triggered a notification, passed to `wrapUpdate`. */
35
+ export type ToastAction = "add" | "remove" | "clear";
36
+
34
37
  export interface ToastQueueOptions {
35
38
  /** The maximum number of toasts to display at once. */
36
39
  maxVisibleToasts?: number;
37
40
  /** Whether toasts should stack (true) or queue (false). */
38
41
  hasExitAnimation?: boolean;
42
+ /**
43
+ * Wraps each update to the visible toasts (e.g. in `document.startViewTransition()`),
44
+ * so the resulting re-render can be animated. Receives the action that triggered the
45
+ * update. Mirrors `@react-stately/toast`'s `wrapUpdate`.
46
+ */
47
+ wrapUpdate?: (fn: () => void, action: ToastAction) => void;
39
48
  }
40
49
 
41
50
  export interface ToastStateProps<T> {
@@ -125,10 +134,29 @@ export class ToastQueue<T> {
125
134
  private maxVisibleToasts: number;
126
135
  private hasExitAnimation: boolean;
127
136
  private keyCounter = 0;
137
+ private wrapUpdate?: (fn: () => void, action: ToastAction) => void;
128
138
 
129
139
  constructor(options: ToastQueueOptions = {}) {
130
140
  this.maxVisibleToasts = options.maxVisibleToasts ?? 5;
131
141
  this.hasExitAnimation = options.hasExitAnimation ?? false;
142
+ this.wrapUpdate = options.wrapUpdate;
143
+ }
144
+
145
+ /**
146
+ * Installs (or clears) the function that wraps visible-toast updates, so the
147
+ * resulting re-render can be animated. Used to attach `document.startViewTransition`
148
+ * to a shared queue (e.g. the global queue) after construction.
149
+ */
150
+ setWrapUpdate(wrapUpdate?: (fn: () => void, action: ToastAction) => void): void {
151
+ this.wrapUpdate = wrapUpdate;
152
+ }
153
+
154
+ private runWithWrapUpdate(fn: () => void, action: ToastAction): void {
155
+ if (this.wrapUpdate) {
156
+ this.wrapUpdate(fn, action);
157
+ } else {
158
+ fn();
159
+ }
132
160
  }
133
161
 
134
162
  /**
@@ -173,7 +201,7 @@ export class ToastQueue<T> {
173
201
  this.queue = [...this.queue.slice(0, low), toast, ...this.queue.slice(low)];
174
202
 
175
203
  // Start timer for visible toasts
176
- this.updateVisibility();
204
+ this.updateVisibility("add");
177
205
 
178
206
  return key;
179
207
  }
@@ -193,7 +221,7 @@ export class ToastQueue<T> {
193
221
  this.queue = this.queue.map((t) =>
194
222
  t.key === key ? { ...t, timer: null, animation: "exiting" } : t,
195
223
  );
196
- this.notify();
224
+ this.notify("remove");
197
225
  } else {
198
226
  // Remove immediately
199
227
  this.remove(key);
@@ -211,7 +239,7 @@ export class ToastQueue<T> {
211
239
  }
212
240
 
213
241
  this.queue = this.queue.filter((t) => t.key !== key);
214
- this.updateVisibility();
242
+ this.updateVisibility("remove");
215
243
  }
216
244
 
217
245
  /**
@@ -223,7 +251,7 @@ export class ToastQueue<T> {
223
251
  }
224
252
 
225
253
  this.queue = [];
226
- this.notify();
254
+ this.notify("clear");
227
255
  }
228
256
 
229
257
  /**
@@ -244,7 +272,7 @@ export class ToastQueue<T> {
244
272
  }
245
273
  }
246
274
 
247
- private updateVisibility(): void {
275
+ private updateVisibility(action: ToastAction): void {
248
276
  // Mark toasts as visible or queued based on maxVisibleToasts
249
277
  const visibleCount = this.queue.filter(
250
278
  (t) => t.animation !== "queued" && t.animation !== "exiting",
@@ -277,14 +305,19 @@ export class ToastQueue<T> {
277
305
  return nextToast;
278
306
  });
279
307
 
280
- this.notify();
308
+ this.notify(action);
281
309
  }
282
310
 
283
- private notify(): void {
311
+ private notify(action: ToastAction = "add"): void {
284
312
  const toasts = [...this.queue];
285
- for (const callback of this.subscriptions) {
286
- callback(toasts);
287
- }
313
+ // The new visible toasts are computed synchronously above; only the subscriber
314
+ // fan-out (which drives the re-render) is wrapped, so it can run inside a view
315
+ // transition. Mirrors @react-stately/toast's updateVisibleToasts.
316
+ this.runWithWrapUpdate(() => {
317
+ for (const callback of this.subscriptions) {
318
+ callback(toasts);
319
+ }
320
+ }, action);
288
321
  }
289
322
  }
290
323
 
@@ -6,6 +6,7 @@ export {
6
6
  type ToastOptions,
7
7
  type QueuedToast,
8
8
  type ToastQueueOptions,
9
+ type ToastAction,
9
10
  type ToastStateProps,
10
11
  type ToastState,
11
12
  } from "./createToastState";
@@ -6,8 +6,8 @@
6
6
  */
7
7
 
8
8
  import { createSignal, createEffect, createMemo, on, type Accessor } from "solid-js";
9
- import type { TreeState, TreeStateOptions, TreeCollection, TreeNode } from "./types";
10
- import type { Key, FocusStrategy } from "../collections/types";
9
+ import type { TreeState, TreeStateOptions, TreeCollection } from "./types";
10
+ import type { Key, FocusStrategy, Selection, SelectionBehavior } from "../collections/types";
11
11
 
12
12
  /**
13
13
  * Creates state management for a tree component.
@@ -66,10 +66,27 @@ export function createTreeState<T extends object, C extends TreeCollection<T> =
66
66
  });
67
67
 
68
68
  const selectionMode = createMemo(() => getOptions().selectionMode ?? "none");
69
- const selectionBehavior = createMemo(() => getOptions().selectionBehavior ?? "toggle");
69
+ const selectionBehaviorProp = (): SelectionBehavior => getOptions().selectionBehavior ?? "toggle";
70
+ const [selectionBehaviorState, setSelectionBehaviorState] =
71
+ createSignal<SelectionBehavior>(selectionBehaviorProp());
72
+ const selectionBehavior = createMemo(() => selectionBehaviorState());
70
73
  const disallowEmptySelection = createMemo(() => getOptions().disallowEmptySelection ?? false);
71
74
  const disabledBehavior = createMemo(() => getOptions().disabledBehavior ?? "all");
72
75
 
76
+ createEffect(on(selectionBehaviorProp, (behavior) => setSelectionBehaviorState(behavior)));
77
+
78
+ createEffect(() => {
79
+ const keys = selectedKeys();
80
+ if (
81
+ selectionBehaviorProp() === "replace" &&
82
+ selectionBehaviorState() === "toggle" &&
83
+ keys !== "all" &&
84
+ keys.size === 0
85
+ ) {
86
+ setSelectionBehaviorState("replace");
87
+ }
88
+ });
89
+
73
90
  // Set focused key
74
91
  const setFocusedKey = (key: Key | null, strategy: FocusStrategy = "first") => {
75
92
  setFocusedKeyInternal(key);
@@ -169,7 +186,9 @@ export function createTreeState<T extends object, C extends TreeCollection<T> =
169
186
  };
170
187
 
171
188
  const toggleSelection = (key: Key) => {
172
- if (isDisabled(key) && disabledBehavior() === "all") return;
189
+ // Disabled keys are never selectable, regardless of disabledBehavior
190
+ // (mirrors SelectionManager.canSelectItem, which ignores disabledBehavior).
191
+ if (isDisabled(key)) return;
173
192
  if (selectionMode() === "none") return;
174
193
 
175
194
  const current = selectedKeys();
@@ -203,15 +222,19 @@ export function createTreeState<T extends object, C extends TreeCollection<T> =
203
222
  };
204
223
 
205
224
  const replaceSelection = (key: Key) => {
206
- if (isDisabled(key) && disabledBehavior() === "all") return;
225
+ if (isDisabled(key)) return;
207
226
  if (selectionMode() === "none") return;
208
227
 
209
228
  updateSelection(new Set([key]));
210
229
  setAnchorKey(key);
211
230
  };
212
231
 
232
+ const setSelectedKeys = (keys: Selection | Iterable<Key>) => {
233
+ updateSelection(normalizeSelection(keys));
234
+ };
235
+
213
236
  const extendSelection = (toKey: Key) => {
214
- if (isDisabled(toKey) && disabledBehavior() === "all") return;
237
+ if (isDisabled(toKey)) return;
215
238
  if (selectionMode() !== "multiple") {
216
239
  replaceSelection(toKey);
217
240
  return;
@@ -330,6 +353,9 @@ export function createTreeState<T extends object, C extends TreeCollection<T> =
330
353
  get disabledKeys() {
331
354
  return disabledKeys();
332
355
  },
356
+ get disabledBehavior() {
357
+ return disabledBehavior();
358
+ },
333
359
  get expandedKeys() {
334
360
  return expandedKeys();
335
361
  },
@@ -351,6 +377,9 @@ export function createTreeState<T extends object, C extends TreeCollection<T> =
351
377
  get selectionBehavior() {
352
378
  return selectionBehavior();
353
379
  },
380
+ get disallowEmptySelection() {
381
+ return disallowEmptySelection();
382
+ },
354
383
  get selectedKeys() {
355
384
  return selectedKeys();
356
385
  },
@@ -361,6 +390,8 @@ export function createTreeState<T extends object, C extends TreeCollection<T> =
361
390
  setFocused: setIsFocused,
362
391
  toggleSelection,
363
392
  replaceSelection,
393
+ setSelectedKeys,
394
+ setSelectionBehavior: setSelectionBehaviorState,
364
395
  extendSelection,
365
396
  selectAll,
366
397
  clearSelection,
package/src/tree/types.ts CHANGED
@@ -3,7 +3,7 @@
3
3
  * Based on @react-stately/tree.
4
4
  */
5
5
 
6
- import type { Key, FocusStrategy } from "../collections/types";
6
+ import type { Key, FocusStrategy, Selection, SelectionBehavior } from "../collections/types";
7
7
  import type { GridNode, GridCollection } from "../grid/types";
8
8
 
9
9
  /**
@@ -81,6 +81,8 @@ export interface TreeState<T, C extends TreeCollection<T> = TreeCollection<T>> {
81
81
  readonly collection: C;
82
82
  /** Keys of disabled items. */
83
83
  readonly disabledKeys: Set<Key>;
84
+ /** Whether `disabledKeys` applies to all interactions, or only selection. */
85
+ readonly disabledBehavior: "selection" | "all";
84
86
  /** Keys of expanded items. */
85
87
  readonly expandedKeys: Set<Key>;
86
88
  /** Whether keyboard navigation is disabled. */
@@ -94,9 +96,11 @@ export interface TreeState<T, C extends TreeCollection<T> = TreeCollection<T>> {
94
96
  /** The selection mode. */
95
97
  readonly selectionMode: "none" | "single" | "multiple";
96
98
  /** How selection behaves when pressing an item. */
97
- readonly selectionBehavior: "toggle" | "replace";
99
+ readonly selectionBehavior: SelectionBehavior;
100
+ /** Whether empty selection is disallowed. */
101
+ readonly disallowEmptySelection: boolean;
98
102
  /** The currently selected keys. */
99
- readonly selectedKeys: "all" | Set<Key>;
103
+ readonly selectedKeys: Selection;
100
104
 
101
105
  /** Check if a key is selected. */
102
106
  isSelected(key: Key): boolean;
@@ -112,6 +116,10 @@ export interface TreeState<T, C extends TreeCollection<T> = TreeCollection<T>> {
112
116
  toggleSelection(key: Key): void;
113
117
  /** Replace selection with a key. */
114
118
  replaceSelection(key: Key): void;
119
+ /** Set the selected keys directly. */
120
+ setSelectedKeys(keys: Selection | Iterable<Key>): void;
121
+ /** Set how selection behaves when pressing an item. */
122
+ setSelectionBehavior(behavior: SelectionBehavior): void;
115
123
  /** Extend selection to a key (shift-click). */
116
124
  extendSelection(toKey: Key): void;
117
125
  /** Select all visible items. */
@@ -145,13 +153,13 @@ export interface TreeStateOptions<T, C extends TreeCollection<T> = TreeCollectio
145
153
  /** Selection mode. */
146
154
  selectionMode?: "none" | "single" | "multiple";
147
155
  /** Selection behavior. */
148
- selectionBehavior?: "toggle" | "replace";
156
+ selectionBehavior?: SelectionBehavior;
149
157
  /** Whether empty selection is disallowed. */
150
158
  disallowEmptySelection?: boolean;
151
159
  /** Currently selected keys (controlled). */
152
- selectedKeys?: "all" | Iterable<Key>;
160
+ selectedKeys?: Selection | Iterable<Key>;
153
161
  /** Default selected keys (uncontrolled). */
154
- defaultSelectedKeys?: "all" | Iterable<Key>;
162
+ defaultSelectedKeys?: Selection | Iterable<Key>;
155
163
  /** Handler for selection changes. */
156
164
  onSelectionChange?: (keys: "all" | Set<Key>) => void;
157
165
  /** Currently expanded keys (controlled). */