@proyecto-viviana/solid-stately 0.3.0 → 0.4.1

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 (77) 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 +65 -20
  4. package/dist/calendar/createRangeCalendarState.d.ts +20 -2
  5. package/dist/calendar/createTimeFieldState.d.ts +38 -67
  6. package/dist/calendar/index.d.ts +1 -1
  7. package/dist/calendar/intl/index.d.ts +10 -0
  8. package/dist/collections/ListCollection.d.ts +8 -0
  9. package/dist/collections/createListState.d.ts +16 -1
  10. package/dist/collections/createSelectionState.d.ts +17 -5
  11. package/dist/collections/getChildNodes.d.ts +19 -0
  12. package/dist/collections/index.d.ts +3 -2
  13. package/dist/collections/types.d.ts +9 -0
  14. package/dist/color/intl/index.d.ts +14 -0
  15. package/dist/combobox/createComboBoxState.d.ts +7 -0
  16. package/dist/dnd/createDroppableCollectionState.d.ts +17 -2
  17. package/dist/dnd/index.d.ts +1 -1
  18. package/dist/dnd/types.d.ts +3 -1
  19. package/dist/grid/types.d.ts +17 -3
  20. package/dist/index.d.ts +6 -6
  21. package/dist/index.js +4372 -1087
  22. package/dist/index.js.map +1 -1
  23. package/dist/select/createSelectState.d.ts +6 -0
  24. package/dist/selection/Selection.d.ts +16 -0
  25. package/dist/selection/SelectionManager.d.ts +119 -0
  26. package/dist/selection/createMultipleSelectionState.d.ts +59 -0
  27. package/dist/selection/index.d.ts +1 -0
  28. package/dist/table/TableCollection.d.ts +30 -0
  29. package/dist/table/createTreeGridState.d.ts +15 -0
  30. package/dist/table/index.d.ts +2 -1
  31. package/dist/table/types.d.ts +58 -0
  32. package/dist/toast/createToastState.d.ts +16 -0
  33. package/dist/toast/index.d.ts +1 -1
  34. package/dist/tree/types.d.ts +14 -6
  35. package/package.json +1 -1
  36. package/src/calendar/IncompleteDate.ts +430 -0
  37. package/src/calendar/createCalendarState.ts +129 -40
  38. package/src/calendar/createDateFieldState.ts +633 -477
  39. package/src/calendar/createRangeCalendarState.ts +343 -30
  40. package/src/calendar/createTimeFieldState.ts +179 -467
  41. package/src/calendar/index.ts +2 -0
  42. package/src/calendar/intl/index.ts +364 -0
  43. package/src/collections/ListCollection.ts +31 -0
  44. package/src/collections/createListState.ts +139 -17
  45. package/src/collections/createMenuState.ts +3 -0
  46. package/src/collections/createSelectionState.ts +38 -28
  47. package/src/collections/getChildNodes.ts +129 -0
  48. package/src/collections/index.ts +12 -0
  49. package/src/collections/types.ts +10 -0
  50. package/src/color/Color.ts +104 -133
  51. package/src/color/intl/index.ts +1409 -0
  52. package/src/combobox/createComboBoxState.ts +38 -13
  53. package/src/dnd/createDroppableCollectionState.ts +125 -6
  54. package/src/dnd/index.d.ts +1 -0
  55. package/src/dnd/index.ts +1 -0
  56. package/src/dnd/types.d.ts +3 -1
  57. package/src/dnd/types.ts +4 -1
  58. package/src/grid/createGridState.ts +36 -2
  59. package/src/grid/types.ts +17 -3
  60. package/src/index.d.ts +1 -0
  61. package/src/index.ts +14 -0
  62. package/src/select/createSelectState.ts +15 -2
  63. package/src/selection/Selection.ts +28 -0
  64. package/src/selection/SelectionManager.ts +541 -0
  65. package/src/selection/createMultipleSelectionState.ts +247 -0
  66. package/src/selection/index.ts +6 -0
  67. package/src/steplist/createStepListState.ts +27 -4
  68. package/src/table/TableCollection.ts +213 -43
  69. package/src/table/createTableState.ts +20 -0
  70. package/src/table/createTreeGridState.ts +146 -0
  71. package/src/table/index.ts +3 -0
  72. package/src/table/types.ts +66 -0
  73. package/src/tabs/createTabListState.ts +11 -9
  74. package/src/toast/createToastState.ts +43 -10
  75. package/src/toast/index.ts +1 -0
  76. package/src/tree/createTreeState.ts +37 -6
  77. package/src/tree/types.ts +14 -6
@@ -0,0 +1,541 @@
1
+ /**
2
+ * An interface for reading and updating multiple selection state.
3
+ *
4
+ * The upper of the two selection layers ported from upstream
5
+ * (`@react-stately/selection`'s `SelectionManager`): the collection-aware
6
+ * operations built on top of the raw {@link MultipleSelectionState}.
7
+ *
8
+ * React → Solid adaptations, all faithful to upstream's observable contract:
9
+ * - Upstream re-instantiates `SelectionManager` every render to capture a
10
+ * fresh `collection`. Here the manager is long-lived, so it captures a
11
+ * reactive `collection` accessor and reads it live via `get collection()`.
12
+ * - Upstream caches `_isSelectAll` for the lifetime of one render. A
13
+ * long-lived manager would let that cache go stale, so it is dropped and
14
+ * `isSelectAll` recomputes (it is consulted rarely — only by select-all UI).
15
+ * - Our `CollectionNode` has no `'cell'` type (grids/trees use a separate
16
+ * state engine), so the cell-selection branches are omitted.
17
+ *
18
+ * Based on @react-stately/selection.
19
+ */
20
+
21
+ import { compareNodeOrder, getChildNodes, getFirstItem } from "../collections/getChildNodes";
22
+ import type { SelectionPressEvent } from "../collections/createSelectionState";
23
+ import type {
24
+ Collection,
25
+ CollectionNode,
26
+ DisabledBehavior,
27
+ FocusStrategy,
28
+ Key,
29
+ Selection as SelectionValue,
30
+ SelectionBehavior,
31
+ SelectionMode,
32
+ } from "../collections/types";
33
+ import type { MultipleSelectionState } from "./createMultipleSelectionState";
34
+ import { Selection } from "./Selection";
35
+
36
+ /** A delegate that can resolve a key range without walking the collection (e.g. a virtualized layout). */
37
+ export interface LayoutDelegate {
38
+ getKeyRange?(from: Key, to: Key): Key[];
39
+ }
40
+
41
+ export interface SelectionManagerOptions<T = unknown> {
42
+ layoutDelegate?: LayoutDelegate;
43
+ /**
44
+ * The full (unfiltered) collection, used when materializing the 'all'
45
+ * selection so that filtered-out items (e.g. by Autocomplete) are still
46
+ * included.
47
+ */
48
+ fullCollection?: Collection<T>;
49
+ }
50
+
51
+ /**
52
+ * An interface for reading and updating multiple selection state.
53
+ */
54
+ export class SelectionManager<T = unknown> {
55
+ private collectionAccessor: () => Collection<T>;
56
+ private state: MultipleSelectionState;
57
+ private layoutDelegate: LayoutDelegate | null;
58
+ private fullCollection: Collection<T> | null;
59
+
60
+ constructor(
61
+ collection: () => Collection<T>,
62
+ state: MultipleSelectionState,
63
+ options?: SelectionManagerOptions<T>,
64
+ ) {
65
+ this.collectionAccessor = collection;
66
+ this.state = state;
67
+ this.layoutDelegate = options?.layoutDelegate ?? null;
68
+ this.fullCollection = options?.fullCollection ?? null;
69
+ }
70
+
71
+ /** The collection the manager operates over (read live so it stays reactive). */
72
+ get collection(): Collection<T> {
73
+ return this.collectionAccessor();
74
+ }
75
+
76
+ /** The type of selection that is allowed in the collection. */
77
+ get selectionMode(): SelectionMode {
78
+ return this.state.selectionMode;
79
+ }
80
+
81
+ /** Whether the collection allows empty selection. */
82
+ get disallowEmptySelection(): boolean {
83
+ return this.state.disallowEmptySelection;
84
+ }
85
+
86
+ /** The selection behavior for the collection. */
87
+ get selectionBehavior(): SelectionBehavior {
88
+ return this.state.selectionBehavior;
89
+ }
90
+
91
+ /** Sets the selection behavior for the collection. */
92
+ setSelectionBehavior(selectionBehavior: SelectionBehavior): void {
93
+ this.state.setSelectionBehavior(selectionBehavior);
94
+ }
95
+
96
+ /** Whether the collection is currently focused. */
97
+ get isFocused(): boolean {
98
+ return this.state.isFocused;
99
+ }
100
+
101
+ /** Sets whether the collection is focused. */
102
+ setFocused(isFocused: boolean): void {
103
+ this.state.setFocused(isFocused);
104
+ }
105
+
106
+ /** The current focused key in the collection. */
107
+ get focusedKey(): Key | null {
108
+ return this.state.focusedKey;
109
+ }
110
+
111
+ /** Whether the first or last child of the focused key should receive focus. */
112
+ get childFocusStrategy(): FocusStrategy | null {
113
+ return this.state.childFocusStrategy;
114
+ }
115
+
116
+ /** Sets the focused key. */
117
+ setFocusedKey(key: Key | null, childFocusStrategy?: FocusStrategy): void {
118
+ if (key == null || this.collection.getItem(key)) {
119
+ this.state.setFocusedKey(key, childFocusStrategy);
120
+ }
121
+ }
122
+
123
+ /** The currently selected keys in the collection (materialized — 'all' is expanded). */
124
+ get selectedKeys(): Set<Key> {
125
+ const keys = this.state.selectedKeys;
126
+ return keys === "all" ? new Set(this.getSelectAllKeys()) : keys;
127
+ }
128
+
129
+ /** The raw selection value for the collection. Either 'all' for select all, or a set of keys. */
130
+ get rawSelection(): SelectionValue {
131
+ return this.state.selectedKeys;
132
+ }
133
+
134
+ /** Last selection payload emitted through onSelectionChange. @internal */
135
+ get lastSelectionEvent(): SelectionValue | null {
136
+ return this.state.lastSelectionEvent;
137
+ }
138
+
139
+ /** Re-emits a selection payload without recomputing selection. @internal */
140
+ emitDuplicateSelectionEvent(keys: SelectionValue | null = this.state.lastSelectionEvent): void {
141
+ if (keys == null || this.selectionMode === "none") {
142
+ return;
143
+ }
144
+
145
+ this.state.emitDuplicateSelectionEvent(keys);
146
+ }
147
+
148
+ /** Returns whether a key is selected. */
149
+ isSelected(key: Key): boolean {
150
+ if (this.state.selectionMode === "none") {
151
+ return false;
152
+ }
153
+
154
+ const mappedKey = this.getKey(key);
155
+ if (mappedKey == null) {
156
+ return false;
157
+ }
158
+ const selectedKeys = this.state.selectedKeys;
159
+ return selectedKeys === "all" ? this.canSelectItem(mappedKey) : selectedKeys.has(mappedKey);
160
+ }
161
+
162
+ /** Whether the selection is empty. */
163
+ get isEmpty(): boolean {
164
+ return this.state.selectedKeys !== "all" && this.state.selectedKeys.size === 0;
165
+ }
166
+
167
+ /** Whether all items in the collection are selected. */
168
+ get isSelectAll(): boolean {
169
+ if (this.isEmpty) {
170
+ return false;
171
+ }
172
+
173
+ const selectedKeys = this.state.selectedKeys;
174
+ if (selectedKeys === "all") {
175
+ return true;
176
+ }
177
+
178
+ const allKeys = this.getSelectAllKeys();
179
+ return allKeys.every((k) => selectedKeys.has(k));
180
+ }
181
+
182
+ get firstSelectedKey(): Key | null {
183
+ let first: CollectionNode<T> | null = null;
184
+ for (const key of this.state.selectedKeys) {
185
+ const item = this.collection.getItem(key);
186
+ if (!first || (item && compareNodeOrder(this.collection, item, first) < 0)) {
187
+ first = item;
188
+ }
189
+ }
190
+
191
+ return first?.key ?? null;
192
+ }
193
+
194
+ get lastSelectedKey(): Key | null {
195
+ let last: CollectionNode<T> | null = null;
196
+ for (const key of this.state.selectedKeys) {
197
+ const item = this.collection.getItem(key);
198
+ if (!last || (item && compareNodeOrder(this.collection, item, last) > 0)) {
199
+ last = item;
200
+ }
201
+ }
202
+
203
+ return last?.key ?? null;
204
+ }
205
+
206
+ get disabledKeys(): Set<Key> {
207
+ return this.state.disabledKeys;
208
+ }
209
+
210
+ get disabledBehavior(): DisabledBehavior {
211
+ return this.state.disabledBehavior;
212
+ }
213
+
214
+ /** Extends the selection to the given key. */
215
+ extendSelection(toKey: Key): void {
216
+ if (this.selectionMode === "none") {
217
+ return;
218
+ }
219
+
220
+ if (this.selectionMode === "single") {
221
+ this.replaceSelection(toKey);
222
+ return;
223
+ }
224
+
225
+ const mappedToKey = this.getKey(toKey);
226
+ if (mappedToKey == null) {
227
+ return;
228
+ }
229
+
230
+ let selection: Selection;
231
+
232
+ // Only select the one key if coming from a select all.
233
+ const currentSelection = this.state.selectedKeys;
234
+ if (currentSelection === "all") {
235
+ selection = new Selection([mappedToKey], mappedToKey, mappedToKey);
236
+ } else {
237
+ const selectedKeys = currentSelection as Selection;
238
+ const anchorKey = selectedKeys.anchorKey ?? mappedToKey;
239
+ selection = new Selection(selectedKeys, anchorKey, mappedToKey);
240
+ for (const key of this.getKeyRange(anchorKey, selectedKeys.currentKey ?? mappedToKey)) {
241
+ selection.delete(key);
242
+ }
243
+
244
+ for (const key of this.getKeyRange(mappedToKey, anchorKey)) {
245
+ if (this.canSelectItem(key)) {
246
+ selection.add(key);
247
+ }
248
+ }
249
+ }
250
+
251
+ this.state.setSelectedKeys(selection);
252
+ }
253
+
254
+ private getKeyRange(from: Key, to: Key): Key[] {
255
+ const fromItem = this.collection.getItem(from);
256
+ const toItem = this.collection.getItem(to);
257
+ if (fromItem && toItem) {
258
+ if (compareNodeOrder(this.collection, fromItem, toItem) <= 0) {
259
+ return this.getKeyRangeInternal(from, to);
260
+ }
261
+
262
+ return this.getKeyRangeInternal(to, from);
263
+ }
264
+
265
+ return [];
266
+ }
267
+
268
+ private getKeyRangeInternal(from: Key, to: Key): Key[] {
269
+ if (this.layoutDelegate?.getKeyRange) {
270
+ return this.layoutDelegate.getKeyRange(from, to);
271
+ }
272
+
273
+ const keys: Key[] = [];
274
+ let key: Key | null = from;
275
+ while (key != null) {
276
+ const item = this.collection.getItem(key);
277
+ if (item && item.type === "item") {
278
+ keys.push(key);
279
+ }
280
+
281
+ if (key === to) {
282
+ return keys;
283
+ }
284
+
285
+ key = this.collection.getKeyAfter(key);
286
+ }
287
+
288
+ return [];
289
+ }
290
+
291
+ private getKey(key: Key): Key | null {
292
+ let item = this.collection.getItem(key);
293
+ if (!item) {
294
+ // ¯\_(ツ)_/¯
295
+ return key;
296
+ }
297
+
298
+ // Find a parent item to select.
299
+ while (item && item.type !== "item" && item.parentKey != null) {
300
+ item = this.collection.getItem(item.parentKey);
301
+ }
302
+
303
+ if (!item || item.type !== "item") {
304
+ return null;
305
+ }
306
+
307
+ return item.key;
308
+ }
309
+
310
+ /** Toggles whether the given key is selected. */
311
+ toggleSelection(key: Key): void {
312
+ if (this.selectionMode === "none") {
313
+ return;
314
+ }
315
+
316
+ if (this.selectionMode === "single" && !this.isSelected(key)) {
317
+ this.replaceSelection(key);
318
+ return;
319
+ }
320
+
321
+ const mappedKey = this.getKey(key);
322
+ if (mappedKey == null) {
323
+ return;
324
+ }
325
+
326
+ const keys = new Selection(
327
+ this.state.selectedKeys === "all" ? this.getSelectAllKeys() : this.state.selectedKeys,
328
+ );
329
+ if (keys.has(mappedKey)) {
330
+ keys.delete(mappedKey);
331
+ // TODO: move anchor to last selected key...
332
+ } else if (this.canSelectItem(mappedKey)) {
333
+ keys.add(mappedKey);
334
+ keys.anchorKey = mappedKey;
335
+ keys.currentKey = mappedKey;
336
+ }
337
+
338
+ if (this.disallowEmptySelection && keys.size === 0) {
339
+ return;
340
+ }
341
+
342
+ this.state.setSelectedKeys(keys);
343
+ }
344
+
345
+ /** Replaces the selection with only the given key. */
346
+ replaceSelection(key: Key): void {
347
+ if (this.selectionMode === "none") {
348
+ return;
349
+ }
350
+
351
+ const mappedKey = this.getKey(key);
352
+ if (mappedKey == null) {
353
+ return;
354
+ }
355
+
356
+ const selection = this.canSelectItem(mappedKey)
357
+ ? new Selection([mappedKey], mappedKey, mappedKey)
358
+ : new Selection();
359
+
360
+ this.state.setSelectedKeys(selection);
361
+ }
362
+
363
+ /** Replaces the selection with the given keys. */
364
+ setSelectedKeys(keys: Iterable<Key>): void {
365
+ if (this.selectionMode === "none") {
366
+ return;
367
+ }
368
+
369
+ const selection = new Selection();
370
+ for (const key of keys) {
371
+ const mappedKey = this.getKey(key);
372
+ if (mappedKey != null) {
373
+ selection.add(mappedKey);
374
+ if (this.selectionMode === "single") {
375
+ break;
376
+ }
377
+ }
378
+ }
379
+
380
+ this.state.setSelectedKeys(selection);
381
+ }
382
+
383
+ private getSelectAllKeys(): Key[] {
384
+ // Use the full (unfiltered) collection when available so that materializing
385
+ // the 'all' selection includes items that are currently filtered out.
386
+ const collection = this.fullCollection ?? this.collection;
387
+ const keys: Key[] = [];
388
+ const addKeys = (key: Key | null) => {
389
+ while (key != null) {
390
+ if (this.canSelectItemIn(key, collection)) {
391
+ const item = collection.getItem(key);
392
+ if (item?.type === "item") {
393
+ keys.push(key);
394
+ }
395
+
396
+ // Add child keys for sections.
397
+ if (item?.hasChildNodes && item.type !== "item") {
398
+ addKeys(getFirstItem(getChildNodes(item, collection))?.key ?? null);
399
+ }
400
+ }
401
+
402
+ key = collection.getKeyAfter(key);
403
+ }
404
+ };
405
+
406
+ addKeys(collection.getFirstKey());
407
+ return keys;
408
+ }
409
+
410
+ /** Selects all items in the collection. */
411
+ selectAll(): void {
412
+ if (!this.isSelectAll && this.selectionMode === "multiple") {
413
+ this.state.setSelectedKeys("all");
414
+ }
415
+ }
416
+
417
+ /** Removes all keys from the selection. */
418
+ clearSelection(): void {
419
+ if (
420
+ !this.disallowEmptySelection &&
421
+ (this.state.selectedKeys === "all" || this.state.selectedKeys.size > 0)
422
+ ) {
423
+ this.state.setSelectedKeys(new Selection());
424
+ }
425
+ }
426
+
427
+ /** Toggles between select all and an empty selection. */
428
+ toggleSelectAll(): void {
429
+ if (this.isSelectAll) {
430
+ this.clearSelection();
431
+ } else {
432
+ this.selectAll();
433
+ }
434
+ }
435
+
436
+ select(key: Key, e?: SelectionPressEvent): void {
437
+ if (this.selectionMode === "none") {
438
+ return;
439
+ }
440
+
441
+ if (this.selectionMode === "single") {
442
+ if (this.isSelected(key) && !this.disallowEmptySelection) {
443
+ this.toggleSelection(key);
444
+ } else {
445
+ this.replaceSelection(key);
446
+ }
447
+ } else if (
448
+ this.selectionBehavior === "toggle" ||
449
+ (e && (e.pointerType === "touch" || e.pointerType === "virtual"))
450
+ ) {
451
+ // touch / virtual (VoiceOver) have no modifier keys, so multi-select is
452
+ // only reachable by toggling.
453
+ this.toggleSelection(key);
454
+ } else {
455
+ this.replaceSelection(key);
456
+ }
457
+ }
458
+
459
+ /** Returns whether the current selection is equal to the given selection. */
460
+ isSelectionEqual(selection: Set<Key>): boolean {
461
+ if (selection === this.state.selectedKeys) {
462
+ return true;
463
+ }
464
+
465
+ // Check if the set of keys match.
466
+ const selectedKeys = this.selectedKeys;
467
+ if (selection.size !== selectedKeys.size) {
468
+ return false;
469
+ }
470
+
471
+ for (const key of selection) {
472
+ if (!selectedKeys.has(key)) {
473
+ return false;
474
+ }
475
+ }
476
+
477
+ for (const key of selectedKeys) {
478
+ if (!selection.has(key)) {
479
+ return false;
480
+ }
481
+ }
482
+
483
+ return true;
484
+ }
485
+
486
+ canSelectItem(key: Key): boolean {
487
+ return this.canSelectItemIn(key, this.collection);
488
+ }
489
+
490
+ private canSelectItemIn(key: Key, collection: Collection<T>): boolean {
491
+ if (this.state.selectionMode === "none" || this.state.disabledKeys.has(key)) {
492
+ return false;
493
+ }
494
+
495
+ const item = collection.getItem(key);
496
+ if (!item || item.isDisabled || item.props?.isDisabled) {
497
+ return false;
498
+ }
499
+
500
+ return true;
501
+ }
502
+
503
+ isDisabled(key: Key): boolean {
504
+ const item = this.collection.getItem(key);
505
+ return (
506
+ this.state.disabledBehavior === "all" &&
507
+ (this.state.disabledKeys.has(key) || !!item?.props?.isDisabled) &&
508
+ item?.props?.disabledBehavior !== "selection"
509
+ );
510
+ }
511
+
512
+ isLink(key: Key): boolean {
513
+ return !!this.collection.getItem(key)?.props?.href;
514
+ }
515
+
516
+ getItemProps(key: Key): Record<string, unknown> | undefined {
517
+ return this.collection.getItem(key)?.props;
518
+ }
519
+
520
+ withCollection(collection: Collection<T>): SelectionManager<T> {
521
+ return new SelectionManager<T>(() => collection, this.state, {
522
+ layoutDelegate: this.layoutDelegate ?? undefined,
523
+ fullCollection: this.fullCollection ?? this.collection,
524
+ });
525
+ }
526
+
527
+ /**
528
+ * Like {@link withCollection}, but the collection is supplied as a live
529
+ * accessor so the returned manager stays reactive as the collection recomputes
530
+ * (e.g. an Autocomplete filter narrowing the list as the user types). The
531
+ * shared underlying selection/focus state is preserved, and the original
532
+ * (unfiltered) collection is retained as `fullCollection` so a select-all
533
+ * still materializes the filtered-out keys.
534
+ */
535
+ withCollectionAccessor(collection: () => Collection<T>): SelectionManager<T> {
536
+ return new SelectionManager<T>(collection, this.state, {
537
+ layoutDelegate: this.layoutDelegate ?? undefined,
538
+ fullCollection: this.fullCollection ?? this.collection,
539
+ });
540
+ }
541
+ }