@proyecto-viviana/solid-stately 0.2.4 → 0.2.7
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/LICENSE +21 -0
- package/dist/autocomplete/createAutocompleteState.d.ts +2 -1
- package/dist/checkbox/createCheckboxGroupState.d.ts +10 -1
- package/dist/collections/types.d.ts +11 -0
- package/dist/color/getColorChannels.d.ts +20 -0
- package/dist/data/createAsyncList.d.ts +111 -0
- package/dist/data/createListData.d.ts +65 -0
- package/dist/data/createTreeData.d.ts +61 -0
- package/dist/data/index.d.ts +3 -0
- package/dist/datepicker/index.d.ts +10 -0
- package/dist/grid/types.d.ts +5 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.js +3737 -2697
- package/dist/index.js.map +1 -7
- package/dist/menu/index.d.ts +8 -0
- package/dist/radio/createRadioGroupState.d.ts +10 -1
- package/dist/select/createSelectState.d.ts +17 -0
- package/dist/selection/index.d.ts +11 -0
- package/dist/toast/createToastState.d.ts +7 -1
- package/dist/toggle/createToggleGroupState.d.ts +45 -0
- package/dist/toggle/index.d.ts +1 -0
- package/dist/tree/TreeCollection.d.ts +3 -2
- package/package.json +6 -5
- package/src/autocomplete/createAutocompleteState.ts +10 -11
- package/src/calendar/createDateFieldState.ts +24 -1
- package/src/checkbox/createCheckboxGroupState.ts +42 -6
- package/src/collections/ListCollection.ts +152 -146
- package/src/collections/createListState.ts +266 -264
- package/src/collections/createMenuState.ts +106 -106
- package/src/collections/createSelectionState.ts +336 -336
- package/src/collections/index.ts +46 -46
- package/src/collections/types.ts +181 -169
- package/src/color/Color.ts +951 -951
- package/src/color/createColorAreaState.ts +293 -293
- package/src/color/createColorFieldState.ts +292 -292
- package/src/color/createColorSliderState.ts +241 -241
- package/src/color/createColorWheelState.ts +211 -211
- package/src/color/getColorChannels.ts +34 -0
- package/src/color/index.ts +47 -47
- package/src/color/types.ts +127 -127
- package/src/combobox/createComboBoxState.ts +703 -703
- package/src/combobox/index.ts +13 -13
- package/src/data/createAsyncList.ts +377 -0
- package/src/data/createListData.ts +298 -0
- package/src/data/createTreeData.ts +433 -0
- package/src/data/index.ts +25 -0
- package/src/datepicker/index.ts +36 -0
- package/src/disclosure/createDisclosureState.ts +4 -4
- package/src/dnd/createDragState.ts +153 -153
- package/src/dnd/createDraggableCollectionState.ts +165 -165
- package/src/dnd/createDropState.ts +212 -212
- package/src/dnd/createDroppableCollectionState.ts +357 -357
- package/src/dnd/index.ts +76 -76
- package/src/dnd/types.ts +317 -317
- package/src/form/createFormValidationState.ts +389 -389
- package/src/form/index.ts +15 -15
- package/src/grid/types.ts +5 -0
- package/src/index.ts +49 -0
- package/src/menu/index.ts +19 -0
- package/src/numberfield/createNumberFieldState.ts +427 -383
- package/src/numberfield/index.ts +5 -5
- package/src/overlays/createOverlayTriggerState.ts +67 -67
- package/src/overlays/index.ts +5 -5
- package/src/radio/createRadioGroupState.ts +44 -6
- package/src/searchfield/createSearchFieldState.ts +62 -62
- package/src/searchfield/index.ts +5 -5
- package/src/select/createSelectState.ts +290 -181
- package/src/select/index.ts +5 -5
- package/src/selection/index.ts +28 -0
- package/src/slider/createSliderState.ts +211 -211
- package/src/slider/index.ts +6 -6
- package/src/tabs/createTabListState.ts +37 -11
- package/src/toast/createToastState.d.ts +6 -1
- package/src/toast/createToastState.ts +8 -1
- package/src/toggle/createToggleGroupState.ts +127 -0
- package/src/toggle/index.ts +6 -0
- package/src/tooltip/createTooltipTriggerState.ts +183 -183
- package/src/tooltip/index.ts +6 -6
- package/src/tree/TreeCollection.ts +208 -175
- package/src/tree/createTreeState.ts +392 -392
- package/src/tree/index.ts +13 -13
- package/src/tree/types.ts +174 -174
|
@@ -1,336 +1,336 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Selection state management for collections.
|
|
3
|
-
* Based on @react-stately/selection.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { createSignal, type Accessor } from 'solid-js';
|
|
7
|
-
import { access, type MaybeAccessor } from '../utils';
|
|
8
|
-
import type {
|
|
9
|
-
Collection,
|
|
10
|
-
DisabledBehavior,
|
|
11
|
-
FocusStrategy,
|
|
12
|
-
Key,
|
|
13
|
-
Selection,
|
|
14
|
-
SelectionBehavior,
|
|
15
|
-
SelectionMode,
|
|
16
|
-
} from './types';
|
|
17
|
-
|
|
18
|
-
export interface SelectionStateProps {
|
|
19
|
-
/** The selection mode. */
|
|
20
|
-
selectionMode?: SelectionMode;
|
|
21
|
-
/** How selection behaves on interaction. */
|
|
22
|
-
selectionBehavior?: SelectionBehavior;
|
|
23
|
-
/** Whether empty selection is disallowed. */
|
|
24
|
-
disallowEmptySelection?: boolean;
|
|
25
|
-
/** Currently selected keys (controlled). */
|
|
26
|
-
selectedKeys?: 'all' | Iterable<Key>;
|
|
27
|
-
/** Default selected keys (uncontrolled). */
|
|
28
|
-
defaultSelectedKeys?: 'all' | Iterable<Key>;
|
|
29
|
-
/** Handler for selection changes. */
|
|
30
|
-
onSelectionChange?: (keys: Selection) => void;
|
|
31
|
-
/** Keys of disabled items. */
|
|
32
|
-
disabledKeys?: Iterable<Key>;
|
|
33
|
-
/** How disabled keys behave. */
|
|
34
|
-
disabledBehavior?: DisabledBehavior;
|
|
35
|
-
/** Whether to allow duplicate selection events. */
|
|
36
|
-
allowDuplicateSelectionEvents?: boolean;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export interface SelectionState {
|
|
40
|
-
/** The selection mode. */
|
|
41
|
-
readonly selectionMode: Accessor<SelectionMode>;
|
|
42
|
-
/** The selection behavior. */
|
|
43
|
-
readonly selectionBehavior: Accessor<SelectionBehavior>;
|
|
44
|
-
/** Whether empty selection is disallowed. */
|
|
45
|
-
readonly disallowEmptySelection: Accessor<boolean>;
|
|
46
|
-
/** The currently selected keys. */
|
|
47
|
-
readonly selectedKeys: Accessor<Selection>;
|
|
48
|
-
/** Set of disabled keys. */
|
|
49
|
-
readonly disabledKeys: Accessor<Set<Key>>;
|
|
50
|
-
/** How disabled keys behave. */
|
|
51
|
-
readonly disabledBehavior: Accessor<DisabledBehavior>;
|
|
52
|
-
/** Whether the selection is empty. */
|
|
53
|
-
readonly isEmpty: Accessor<boolean>;
|
|
54
|
-
/** Whether all items are selected. */
|
|
55
|
-
readonly isSelectAll: Accessor<boolean>;
|
|
56
|
-
/** Check if a key is selected. */
|
|
57
|
-
isSelected(key: Key): boolean;
|
|
58
|
-
/** Check if a key is disabled. */
|
|
59
|
-
isDisabled(key: Key): boolean;
|
|
60
|
-
/** Set the selection behavior. */
|
|
61
|
-
setSelectionBehavior(behavior: SelectionBehavior): void;
|
|
62
|
-
/** Toggle selection for a key. */
|
|
63
|
-
toggleSelection(key: Key): void;
|
|
64
|
-
/** Replace selection with a single key. */
|
|
65
|
-
replaceSelection(key: Key): void;
|
|
66
|
-
/** Set multiple selected keys. */
|
|
67
|
-
setSelectedKeys(keys: Iterable<Key>): void;
|
|
68
|
-
/** Select all items. */
|
|
69
|
-
selectAll(): void;
|
|
70
|
-
/** Clear all selection. */
|
|
71
|
-
clearSelection(): void;
|
|
72
|
-
/** Toggle between select all and clear. */
|
|
73
|
-
toggleSelectAll(): void;
|
|
74
|
-
/** Extend selection to a key (for shift-click). */
|
|
75
|
-
extendSelection(toKey: Key, collection: Collection): void;
|
|
76
|
-
/** Select a key based on interaction. */
|
|
77
|
-
select(key: Key, e?: { shiftKey?: boolean; ctrlKey?: boolean; metaKey?: boolean }, collection?: Collection): void;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Creates selection state for a collection.
|
|
82
|
-
*/
|
|
83
|
-
export function createSelectionState(
|
|
84
|
-
props: MaybeAccessor<SelectionStateProps> = {}
|
|
85
|
-
): SelectionState {
|
|
86
|
-
const getProps = () => access(props);
|
|
87
|
-
|
|
88
|
-
// Selection behavior state
|
|
89
|
-
const [internalBehavior, setInternalBehavior] = createSignal<SelectionBehavior>('toggle');
|
|
90
|
-
|
|
91
|
-
// Internal selection state
|
|
92
|
-
const [internalSelectedKeys, setInternalSelectedKeys] = createSignal<Selection>(
|
|
93
|
-
getInitialSelection(getProps().defaultSelectedKeys)
|
|
94
|
-
);
|
|
95
|
-
|
|
96
|
-
// Track anchor for range selection
|
|
97
|
-
const [anchorKey, setAnchorKey] = createSignal<Key | null>(null);
|
|
98
|
-
|
|
99
|
-
// Computed values
|
|
100
|
-
const selectionMode: Accessor<SelectionMode> = () => getProps().selectionMode ?? 'none';
|
|
101
|
-
|
|
102
|
-
const selectionBehavior: Accessor<SelectionBehavior> = () => {
|
|
103
|
-
return getProps().selectionBehavior ?? internalBehavior();
|
|
104
|
-
};
|
|
105
|
-
|
|
106
|
-
const disallowEmptySelection: Accessor<boolean> = () => {
|
|
107
|
-
return getProps().disallowEmptySelection ?? false;
|
|
108
|
-
};
|
|
109
|
-
|
|
110
|
-
const selectedKeys: Accessor<Selection> = () => {
|
|
111
|
-
const p = getProps();
|
|
112
|
-
if (p.selectedKeys !== undefined) {
|
|
113
|
-
return normalizeSelection(p.selectedKeys);
|
|
114
|
-
}
|
|
115
|
-
return internalSelectedKeys();
|
|
116
|
-
};
|
|
117
|
-
|
|
118
|
-
const disabledKeys: Accessor<Set<Key>> = () => {
|
|
119
|
-
const keys = getProps().disabledKeys;
|
|
120
|
-
return keys ? new Set(keys) : new Set();
|
|
121
|
-
};
|
|
122
|
-
|
|
123
|
-
const disabledBehavior: Accessor<DisabledBehavior> = () => {
|
|
124
|
-
return getProps().disabledBehavior ?? 'all';
|
|
125
|
-
};
|
|
126
|
-
|
|
127
|
-
const isEmpty: Accessor<boolean> = () => {
|
|
128
|
-
const keys = selectedKeys();
|
|
129
|
-
return keys !== 'all' && keys.size === 0;
|
|
130
|
-
};
|
|
131
|
-
|
|
132
|
-
const isSelectAll: Accessor<boolean> = () => {
|
|
133
|
-
return selectedKeys() === 'all';
|
|
134
|
-
};
|
|
135
|
-
|
|
136
|
-
// Methods
|
|
137
|
-
const isSelected = (key: Key): boolean => {
|
|
138
|
-
const keys = selectedKeys();
|
|
139
|
-
if (keys === 'all') return true;
|
|
140
|
-
return keys.has(key);
|
|
141
|
-
};
|
|
142
|
-
|
|
143
|
-
const isDisabled = (key: Key): boolean => {
|
|
144
|
-
return disabledKeys().has(key);
|
|
145
|
-
};
|
|
146
|
-
|
|
147
|
-
const updateSelection = (newSelection: Selection) => {
|
|
148
|
-
const p = getProps();
|
|
149
|
-
|
|
150
|
-
// Controlled mode
|
|
151
|
-
if (p.selectedKeys !== undefined) {
|
|
152
|
-
p.onSelectionChange?.(newSelection);
|
|
153
|
-
return;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// Uncontrolled mode
|
|
157
|
-
const current = internalSelectedKeys();
|
|
158
|
-
const isDifferent =
|
|
159
|
-
current === 'all' ||
|
|
160
|
-
newSelection === 'all' ||
|
|
161
|
-
current.size !== (newSelection as Set<Key>).size ||
|
|
162
|
-
![...current].every((k) => (newSelection as Set<Key>).has(k));
|
|
163
|
-
|
|
164
|
-
if (isDifferent || p.allowDuplicateSelectionEvents) {
|
|
165
|
-
setInternalSelectedKeys(newSelection);
|
|
166
|
-
p.onSelectionChange?.(newSelection);
|
|
167
|
-
}
|
|
168
|
-
};
|
|
169
|
-
|
|
170
|
-
const toggleSelection = (key: Key) => {
|
|
171
|
-
if (isDisabled(key)) return;
|
|
172
|
-
if (selectionMode() === 'none') return;
|
|
173
|
-
|
|
174
|
-
const current = selectedKeys();
|
|
175
|
-
|
|
176
|
-
if (selectionMode() === 'single') {
|
|
177
|
-
if (isSelected(key) && !disallowEmptySelection()) {
|
|
178
|
-
updateSelection(new Set());
|
|
179
|
-
} else {
|
|
180
|
-
updateSelection(new Set([key]));
|
|
181
|
-
}
|
|
182
|
-
return;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
// Multiple selection
|
|
186
|
-
if (current === 'all') {
|
|
187
|
-
// Can't toggle when all selected without collection
|
|
188
|
-
return;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
const newSelection = new Set(current);
|
|
192
|
-
if (newSelection.has(key)) {
|
|
193
|
-
if (newSelection.size > 1 || !disallowEmptySelection()) {
|
|
194
|
-
newSelection.delete(key);
|
|
195
|
-
}
|
|
196
|
-
} else {
|
|
197
|
-
newSelection.add(key);
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
updateSelection(newSelection);
|
|
201
|
-
setAnchorKey(key);
|
|
202
|
-
};
|
|
203
|
-
|
|
204
|
-
const replaceSelection = (key: Key) => {
|
|
205
|
-
if (isDisabled(key)) return;
|
|
206
|
-
if (selectionMode() === 'none') return;
|
|
207
|
-
|
|
208
|
-
updateSelection(new Set([key]));
|
|
209
|
-
setAnchorKey(key);
|
|
210
|
-
};
|
|
211
|
-
|
|
212
|
-
const setSelectedKeys = (keys: Iterable<Key>) => {
|
|
213
|
-
const filtered = [...keys].filter((k) => !isDisabled(k));
|
|
214
|
-
updateSelection(new Set(filtered));
|
|
215
|
-
};
|
|
216
|
-
|
|
217
|
-
const selectAll = () => {
|
|
218
|
-
if (selectionMode() !== 'multiple') return;
|
|
219
|
-
updateSelection('all');
|
|
220
|
-
};
|
|
221
|
-
|
|
222
|
-
const clearSelection = () => {
|
|
223
|
-
if (disallowEmptySelection()) return;
|
|
224
|
-
updateSelection(new Set());
|
|
225
|
-
};
|
|
226
|
-
|
|
227
|
-
const toggleSelectAll = () => {
|
|
228
|
-
if (selectionMode() !== 'multiple') return;
|
|
229
|
-
|
|
230
|
-
if (isSelectAll()) {
|
|
231
|
-
clearSelection();
|
|
232
|
-
} else {
|
|
233
|
-
selectAll();
|
|
234
|
-
}
|
|
235
|
-
};
|
|
236
|
-
|
|
237
|
-
const extendSelection = (toKey: Key, collection: Collection) => {
|
|
238
|
-
if (isDisabled(toKey)) return;
|
|
239
|
-
if (selectionMode() !== 'multiple') {
|
|
240
|
-
replaceSelection(toKey);
|
|
241
|
-
return;
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
const anchor = anchorKey();
|
|
245
|
-
if (!anchor) {
|
|
246
|
-
replaceSelection(toKey);
|
|
247
|
-
return;
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
// Get keys between anchor and toKey
|
|
251
|
-
const keys = [...collection.getKeys()];
|
|
252
|
-
const anchorIndex = keys.indexOf(anchor);
|
|
253
|
-
const toIndex = keys.indexOf(toKey);
|
|
254
|
-
|
|
255
|
-
if (anchorIndex === -1 || toIndex === -1) {
|
|
256
|
-
replaceSelection(toKey);
|
|
257
|
-
return;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
const start = Math.min(anchorIndex, toIndex);
|
|
261
|
-
const end = Math.max(anchorIndex, toIndex);
|
|
262
|
-
const rangeKeys = keys.slice(start, end + 1).filter((k) => !isDisabled(k));
|
|
263
|
-
|
|
264
|
-
updateSelection(new Set(rangeKeys));
|
|
265
|
-
};
|
|
266
|
-
|
|
267
|
-
const select = (
|
|
268
|
-
key: Key,
|
|
269
|
-
e?: { shiftKey?: boolean; ctrlKey?: boolean; metaKey?: boolean },
|
|
270
|
-
collection?: Collection
|
|
271
|
-
) => {
|
|
272
|
-
if (isDisabled(key)) return;
|
|
273
|
-
if (selectionMode() === 'none') return;
|
|
274
|
-
|
|
275
|
-
const mode = selectionMode();
|
|
276
|
-
const behavior = selectionBehavior();
|
|
277
|
-
|
|
278
|
-
if (mode === 'single') {
|
|
279
|
-
if (behavior === 'replace' || !isSelected(key)) {
|
|
280
|
-
replaceSelection(key);
|
|
281
|
-
} else if (!disallowEmptySelection()) {
|
|
282
|
-
clearSelection();
|
|
283
|
-
}
|
|
284
|
-
return;
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
// Multiple selection
|
|
288
|
-
if (e?.shiftKey && collection) {
|
|
289
|
-
extendSelection(key, collection);
|
|
290
|
-
return;
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
if (e?.ctrlKey || e?.metaKey || behavior === 'toggle') {
|
|
294
|
-
toggleSelection(key);
|
|
295
|
-
} else {
|
|
296
|
-
replaceSelection(key);
|
|
297
|
-
}
|
|
298
|
-
};
|
|
299
|
-
|
|
300
|
-
const setSelectionBehavior = (behavior: SelectionBehavior) => {
|
|
301
|
-
setInternalBehavior(behavior);
|
|
302
|
-
};
|
|
303
|
-
|
|
304
|
-
return {
|
|
305
|
-
selectionMode,
|
|
306
|
-
selectionBehavior,
|
|
307
|
-
disallowEmptySelection,
|
|
308
|
-
selectedKeys,
|
|
309
|
-
disabledKeys,
|
|
310
|
-
disabledBehavior,
|
|
311
|
-
isEmpty,
|
|
312
|
-
isSelectAll,
|
|
313
|
-
isSelected,
|
|
314
|
-
isDisabled,
|
|
315
|
-
setSelectionBehavior,
|
|
316
|
-
toggleSelection,
|
|
317
|
-
replaceSelection,
|
|
318
|
-
setSelectedKeys,
|
|
319
|
-
selectAll,
|
|
320
|
-
clearSelection,
|
|
321
|
-
toggleSelectAll,
|
|
322
|
-
extendSelection,
|
|
323
|
-
select,
|
|
324
|
-
};
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
// Helper functions
|
|
328
|
-
function getInitialSelection(defaultKeys?: 'all' | Iterable<Key>): Selection {
|
|
329
|
-
if (defaultKeys === undefined) return new Set();
|
|
330
|
-
return normalizeSelection(defaultKeys);
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
function normalizeSelection(keys: 'all' | Iterable<Key>): Selection {
|
|
334
|
-
if (keys === 'all') return 'all';
|
|
335
|
-
return new Set(keys);
|
|
336
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Selection state management for collections.
|
|
3
|
+
* Based on @react-stately/selection.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { createSignal, createMemo, type Accessor } from 'solid-js';
|
|
7
|
+
import { access, type MaybeAccessor } from '../utils';
|
|
8
|
+
import type {
|
|
9
|
+
Collection,
|
|
10
|
+
DisabledBehavior,
|
|
11
|
+
FocusStrategy,
|
|
12
|
+
Key,
|
|
13
|
+
Selection,
|
|
14
|
+
SelectionBehavior,
|
|
15
|
+
SelectionMode,
|
|
16
|
+
} from './types';
|
|
17
|
+
|
|
18
|
+
export interface SelectionStateProps {
|
|
19
|
+
/** The selection mode. */
|
|
20
|
+
selectionMode?: SelectionMode;
|
|
21
|
+
/** How selection behaves on interaction. */
|
|
22
|
+
selectionBehavior?: SelectionBehavior;
|
|
23
|
+
/** Whether empty selection is disallowed. */
|
|
24
|
+
disallowEmptySelection?: boolean;
|
|
25
|
+
/** Currently selected keys (controlled). */
|
|
26
|
+
selectedKeys?: 'all' | Iterable<Key>;
|
|
27
|
+
/** Default selected keys (uncontrolled). */
|
|
28
|
+
defaultSelectedKeys?: 'all' | Iterable<Key>;
|
|
29
|
+
/** Handler for selection changes. */
|
|
30
|
+
onSelectionChange?: (keys: Selection) => void;
|
|
31
|
+
/** Keys of disabled items. */
|
|
32
|
+
disabledKeys?: Iterable<Key>;
|
|
33
|
+
/** How disabled keys behave. */
|
|
34
|
+
disabledBehavior?: DisabledBehavior;
|
|
35
|
+
/** Whether to allow duplicate selection events. */
|
|
36
|
+
allowDuplicateSelectionEvents?: boolean;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface SelectionState {
|
|
40
|
+
/** The selection mode. */
|
|
41
|
+
readonly selectionMode: Accessor<SelectionMode>;
|
|
42
|
+
/** The selection behavior. */
|
|
43
|
+
readonly selectionBehavior: Accessor<SelectionBehavior>;
|
|
44
|
+
/** Whether empty selection is disallowed. */
|
|
45
|
+
readonly disallowEmptySelection: Accessor<boolean>;
|
|
46
|
+
/** The currently selected keys. */
|
|
47
|
+
readonly selectedKeys: Accessor<Selection>;
|
|
48
|
+
/** Set of disabled keys. */
|
|
49
|
+
readonly disabledKeys: Accessor<Set<Key>>;
|
|
50
|
+
/** How disabled keys behave. */
|
|
51
|
+
readonly disabledBehavior: Accessor<DisabledBehavior>;
|
|
52
|
+
/** Whether the selection is empty. */
|
|
53
|
+
readonly isEmpty: Accessor<boolean>;
|
|
54
|
+
/** Whether all items are selected. */
|
|
55
|
+
readonly isSelectAll: Accessor<boolean>;
|
|
56
|
+
/** Check if a key is selected. */
|
|
57
|
+
isSelected(key: Key): boolean;
|
|
58
|
+
/** Check if a key is disabled. */
|
|
59
|
+
isDisabled(key: Key): boolean;
|
|
60
|
+
/** Set the selection behavior. */
|
|
61
|
+
setSelectionBehavior(behavior: SelectionBehavior): void;
|
|
62
|
+
/** Toggle selection for a key. */
|
|
63
|
+
toggleSelection(key: Key): void;
|
|
64
|
+
/** Replace selection with a single key. */
|
|
65
|
+
replaceSelection(key: Key): void;
|
|
66
|
+
/** Set multiple selected keys. */
|
|
67
|
+
setSelectedKeys(keys: Iterable<Key>): void;
|
|
68
|
+
/** Select all items. */
|
|
69
|
+
selectAll(): void;
|
|
70
|
+
/** Clear all selection. */
|
|
71
|
+
clearSelection(): void;
|
|
72
|
+
/** Toggle between select all and clear. */
|
|
73
|
+
toggleSelectAll(): void;
|
|
74
|
+
/** Extend selection to a key (for shift-click). */
|
|
75
|
+
extendSelection(toKey: Key, collection: Collection): void;
|
|
76
|
+
/** Select a key based on interaction. */
|
|
77
|
+
select(key: Key, e?: { shiftKey?: boolean; ctrlKey?: boolean; metaKey?: boolean }, collection?: Collection): void;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Creates selection state for a collection.
|
|
82
|
+
*/
|
|
83
|
+
export function createSelectionState(
|
|
84
|
+
props: MaybeAccessor<SelectionStateProps> = {}
|
|
85
|
+
): SelectionState {
|
|
86
|
+
const getProps = () => access(props);
|
|
87
|
+
|
|
88
|
+
// Selection behavior state
|
|
89
|
+
const [internalBehavior, setInternalBehavior] = createSignal<SelectionBehavior>('toggle');
|
|
90
|
+
|
|
91
|
+
// Internal selection state
|
|
92
|
+
const [internalSelectedKeys, setInternalSelectedKeys] = createSignal<Selection>(
|
|
93
|
+
getInitialSelection(getProps().defaultSelectedKeys)
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
// Track anchor for range selection
|
|
97
|
+
const [anchorKey, setAnchorKey] = createSignal<Key | null>(null);
|
|
98
|
+
|
|
99
|
+
// Computed values
|
|
100
|
+
const selectionMode: Accessor<SelectionMode> = () => getProps().selectionMode ?? 'none';
|
|
101
|
+
|
|
102
|
+
const selectionBehavior: Accessor<SelectionBehavior> = () => {
|
|
103
|
+
return getProps().selectionBehavior ?? internalBehavior();
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const disallowEmptySelection: Accessor<boolean> = () => {
|
|
107
|
+
return getProps().disallowEmptySelection ?? false;
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const selectedKeys: Accessor<Selection> = createMemo(() => {
|
|
111
|
+
const p = getProps();
|
|
112
|
+
if (p.selectedKeys !== undefined) {
|
|
113
|
+
return normalizeSelection(p.selectedKeys);
|
|
114
|
+
}
|
|
115
|
+
return internalSelectedKeys();
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
const disabledKeys: Accessor<Set<Key>> = createMemo(() => {
|
|
119
|
+
const keys = getProps().disabledKeys;
|
|
120
|
+
return keys ? new Set<Key>(keys) : new Set<Key>();
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
const disabledBehavior: Accessor<DisabledBehavior> = () => {
|
|
124
|
+
return getProps().disabledBehavior ?? 'all';
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const isEmpty: Accessor<boolean> = () => {
|
|
128
|
+
const keys = selectedKeys();
|
|
129
|
+
return keys !== 'all' && keys.size === 0;
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
const isSelectAll: Accessor<boolean> = () => {
|
|
133
|
+
return selectedKeys() === 'all';
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
// Methods
|
|
137
|
+
const isSelected = (key: Key): boolean => {
|
|
138
|
+
const keys = selectedKeys();
|
|
139
|
+
if (keys === 'all') return true;
|
|
140
|
+
return keys.has(key);
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
const isDisabled = (key: Key): boolean => {
|
|
144
|
+
return disabledKeys().has(key);
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
const updateSelection = (newSelection: Selection) => {
|
|
148
|
+
const p = getProps();
|
|
149
|
+
|
|
150
|
+
// Controlled mode
|
|
151
|
+
if (p.selectedKeys !== undefined) {
|
|
152
|
+
p.onSelectionChange?.(newSelection);
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Uncontrolled mode
|
|
157
|
+
const current = internalSelectedKeys();
|
|
158
|
+
const isDifferent =
|
|
159
|
+
current === 'all' ||
|
|
160
|
+
newSelection === 'all' ||
|
|
161
|
+
current.size !== (newSelection as Set<Key>).size ||
|
|
162
|
+
![...current].every((k) => (newSelection as Set<Key>).has(k));
|
|
163
|
+
|
|
164
|
+
if (isDifferent || p.allowDuplicateSelectionEvents) {
|
|
165
|
+
setInternalSelectedKeys(newSelection);
|
|
166
|
+
p.onSelectionChange?.(newSelection);
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
const toggleSelection = (key: Key) => {
|
|
171
|
+
if (isDisabled(key)) return;
|
|
172
|
+
if (selectionMode() === 'none') return;
|
|
173
|
+
|
|
174
|
+
const current = selectedKeys();
|
|
175
|
+
|
|
176
|
+
if (selectionMode() === 'single') {
|
|
177
|
+
if (isSelected(key) && !disallowEmptySelection()) {
|
|
178
|
+
updateSelection(new Set());
|
|
179
|
+
} else {
|
|
180
|
+
updateSelection(new Set([key]));
|
|
181
|
+
}
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Multiple selection
|
|
186
|
+
if (current === 'all') {
|
|
187
|
+
// Can't toggle when all selected without collection
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const newSelection = new Set(current);
|
|
192
|
+
if (newSelection.has(key)) {
|
|
193
|
+
if (newSelection.size > 1 || !disallowEmptySelection()) {
|
|
194
|
+
newSelection.delete(key);
|
|
195
|
+
}
|
|
196
|
+
} else {
|
|
197
|
+
newSelection.add(key);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
updateSelection(newSelection);
|
|
201
|
+
setAnchorKey(key);
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
const replaceSelection = (key: Key) => {
|
|
205
|
+
if (isDisabled(key)) return;
|
|
206
|
+
if (selectionMode() === 'none') return;
|
|
207
|
+
|
|
208
|
+
updateSelection(new Set([key]));
|
|
209
|
+
setAnchorKey(key);
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
const setSelectedKeys = (keys: Iterable<Key>) => {
|
|
213
|
+
const filtered = [...keys].filter((k) => !isDisabled(k));
|
|
214
|
+
updateSelection(new Set(filtered));
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
const selectAll = () => {
|
|
218
|
+
if (selectionMode() !== 'multiple') return;
|
|
219
|
+
updateSelection('all');
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
const clearSelection = () => {
|
|
223
|
+
if (disallowEmptySelection()) return;
|
|
224
|
+
updateSelection(new Set());
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
const toggleSelectAll = () => {
|
|
228
|
+
if (selectionMode() !== 'multiple') return;
|
|
229
|
+
|
|
230
|
+
if (isSelectAll()) {
|
|
231
|
+
clearSelection();
|
|
232
|
+
} else {
|
|
233
|
+
selectAll();
|
|
234
|
+
}
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
const extendSelection = (toKey: Key, collection: Collection) => {
|
|
238
|
+
if (isDisabled(toKey)) return;
|
|
239
|
+
if (selectionMode() !== 'multiple') {
|
|
240
|
+
replaceSelection(toKey);
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const anchor = anchorKey();
|
|
245
|
+
if (!anchor) {
|
|
246
|
+
replaceSelection(toKey);
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Get keys between anchor and toKey
|
|
251
|
+
const keys = [...collection.getKeys()];
|
|
252
|
+
const anchorIndex = keys.indexOf(anchor);
|
|
253
|
+
const toIndex = keys.indexOf(toKey);
|
|
254
|
+
|
|
255
|
+
if (anchorIndex === -1 || toIndex === -1) {
|
|
256
|
+
replaceSelection(toKey);
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const start = Math.min(anchorIndex, toIndex);
|
|
261
|
+
const end = Math.max(anchorIndex, toIndex);
|
|
262
|
+
const rangeKeys = keys.slice(start, end + 1).filter((k) => !isDisabled(k));
|
|
263
|
+
|
|
264
|
+
updateSelection(new Set(rangeKeys));
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
const select = (
|
|
268
|
+
key: Key,
|
|
269
|
+
e?: { shiftKey?: boolean; ctrlKey?: boolean; metaKey?: boolean },
|
|
270
|
+
collection?: Collection
|
|
271
|
+
) => {
|
|
272
|
+
if (isDisabled(key)) return;
|
|
273
|
+
if (selectionMode() === 'none') return;
|
|
274
|
+
|
|
275
|
+
const mode = selectionMode();
|
|
276
|
+
const behavior = selectionBehavior();
|
|
277
|
+
|
|
278
|
+
if (mode === 'single') {
|
|
279
|
+
if (behavior === 'replace' || !isSelected(key)) {
|
|
280
|
+
replaceSelection(key);
|
|
281
|
+
} else if (!disallowEmptySelection()) {
|
|
282
|
+
clearSelection();
|
|
283
|
+
}
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Multiple selection
|
|
288
|
+
if (e?.shiftKey && collection) {
|
|
289
|
+
extendSelection(key, collection);
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (e?.ctrlKey || e?.metaKey || behavior === 'toggle') {
|
|
294
|
+
toggleSelection(key);
|
|
295
|
+
} else {
|
|
296
|
+
replaceSelection(key);
|
|
297
|
+
}
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
const setSelectionBehavior = (behavior: SelectionBehavior) => {
|
|
301
|
+
setInternalBehavior(behavior);
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
return {
|
|
305
|
+
selectionMode,
|
|
306
|
+
selectionBehavior,
|
|
307
|
+
disallowEmptySelection,
|
|
308
|
+
selectedKeys,
|
|
309
|
+
disabledKeys,
|
|
310
|
+
disabledBehavior,
|
|
311
|
+
isEmpty,
|
|
312
|
+
isSelectAll,
|
|
313
|
+
isSelected,
|
|
314
|
+
isDisabled,
|
|
315
|
+
setSelectionBehavior,
|
|
316
|
+
toggleSelection,
|
|
317
|
+
replaceSelection,
|
|
318
|
+
setSelectedKeys,
|
|
319
|
+
selectAll,
|
|
320
|
+
clearSelection,
|
|
321
|
+
toggleSelectAll,
|
|
322
|
+
extendSelection,
|
|
323
|
+
select,
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Helper functions
|
|
328
|
+
function getInitialSelection(defaultKeys?: 'all' | Iterable<Key>): Selection {
|
|
329
|
+
if (defaultKeys === undefined) return new Set();
|
|
330
|
+
return normalizeSelection(defaultKeys);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function normalizeSelection(keys: 'all' | Iterable<Key>): Selection {
|
|
334
|
+
if (keys === 'all') return 'all';
|
|
335
|
+
return new Set(keys);
|
|
336
|
+
}
|