@proyecto-viviana/solid-stately 0.6.0-rc.0 → 0.6.0-rc.2
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 +59 -29
- package/dist/calendar/createCalendarState.d.ts +3 -1
- package/dist/calendar/createCalendarState.d.ts.map +1 -1
- package/dist/calendar/createDatePickerState.d.ts +2 -0
- package/dist/calendar/createDatePickerState.d.ts.map +1 -1
- package/dist/calendar/createRangeCalendarState.d.ts +2 -0
- package/dist/calendar/createRangeCalendarState.d.ts.map +1 -1
- package/dist/calendar/createTimeFieldState.d.ts +1 -1
- package/dist/calendar/createTimeFieldState.d.ts.map +1 -1
- package/dist/checkbox/createCheckboxGroupState.d.ts +2 -0
- package/dist/checkbox/createCheckboxGroupState.d.ts.map +1 -1
- package/dist/collections/createListState.d.ts.map +1 -1
- package/dist/collections/createMenuState.d.ts +46 -2
- package/dist/collections/createMenuState.d.ts.map +1 -1
- package/dist/collections/createSelectionState.d.ts.map +1 -1
- package/dist/collections/index.d.ts +1 -1
- package/dist/collections/index.d.ts.map +1 -1
- package/dist/color/createColorAreaState.d.ts +4 -0
- package/dist/color/createColorAreaState.d.ts.map +1 -1
- package/dist/color/createColorFieldState.d.ts +2 -0
- package/dist/color/createColorFieldState.d.ts.map +1 -1
- package/dist/color/createColorSliderState.d.ts +2 -0
- package/dist/color/createColorSliderState.d.ts.map +1 -1
- package/dist/color/createColorWheelState.d.ts.map +1 -1
- package/dist/combobox/createComboBoxState.d.ts +90 -23
- package/dist/combobox/createComboBoxState.d.ts.map +1 -1
- package/dist/combobox/index.d.ts +1 -1
- package/dist/combobox/index.d.ts.map +1 -1
- package/dist/data/createAsyncList.d.ts.map +1 -1
- package/dist/data/createListData.d.ts.map +1 -1
- package/dist/data/createTreeData.d.ts.map +1 -1
- package/dist/dnd/createDraggableCollectionState.d.ts +5 -1
- package/dist/dnd/createDraggableCollectionState.d.ts.map +1 -1
- package/dist/dnd/createDroppableCollectionState.d.ts +6 -0
- package/dist/dnd/createDroppableCollectionState.d.ts.map +1 -1
- package/dist/form/createFormValidationState.d.ts.map +1 -1
- package/dist/grid/createGridState.d.ts +2 -2
- package/dist/grid/createGridState.d.ts.map +1 -1
- package/dist/index.d.ts +5 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1044 -478
- package/dist/index.js.map +1 -1
- package/dist/menu/index.d.ts +2 -1
- package/dist/menu/index.d.ts.map +1 -1
- package/dist/numberfield/createNumberFieldState.d.ts +6 -3
- package/dist/numberfield/createNumberFieldState.d.ts.map +1 -1
- package/dist/private/flags/flags.d.ts.map +1 -0
- package/dist/private/flags/flags.js +1 -1
- package/dist/private/flags/flags.js.map +1 -1
- package/dist/radio/createRadioGroupState.d.ts.map +1 -1
- package/dist/select/createSelectState.d.ts +85 -29
- package/dist/select/createSelectState.d.ts.map +1 -1
- package/dist/select/index.d.ts +1 -1
- package/dist/select/index.d.ts.map +1 -1
- package/dist/selection/SelectionManager.d.ts.map +1 -1
- package/dist/selection/createMultipleSelectionState.d.ts.map +1 -1
- package/dist/slider/createSliderState.d.ts +35 -9
- package/dist/slider/createSliderState.d.ts.map +1 -1
- package/dist/ssr/index.d.ts +0 -6
- package/dist/ssr/index.d.ts.map +1 -1
- package/dist/steplist/createStepListState.d.ts +8 -13
- package/dist/steplist/createStepListState.d.ts.map +1 -1
- package/dist/table/createTableColumnResizeState.d.ts.map +1 -1
- package/dist/table/createTableState.d.ts +2 -2
- package/dist/table/createTableState.d.ts.map +1 -1
- package/dist/table/createTreeGridState.d.ts +2 -2
- package/dist/table/createTreeGridState.d.ts.map +1 -1
- package/dist/table/types.d.ts +2 -2
- package/dist/table/types.d.ts.map +1 -1
- package/dist/tabs/createTabListState.d.ts.map +1 -1
- package/dist/textfield/createTextFieldState.d.ts.map +1 -1
- package/dist/toggle/createToggleGroupState.d.ts +0 -8
- package/dist/toggle/createToggleGroupState.d.ts.map +1 -1
- package/dist/toggle/createToggleState.d.ts.map +1 -1
- package/dist/tokenfield/TokenFieldValue.d.ts +28 -4
- package/dist/tokenfield/TokenFieldValue.d.ts.map +1 -1
- package/dist/tokenfield/index.d.ts +1 -1
- package/dist/tokenfield/index.d.ts.map +1 -1
- package/dist/tree/createTreeState.d.ts +2 -2
- package/dist/tree/createTreeState.d.ts.map +1 -1
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/reactivity.d.ts +5 -0
- package/dist/utils/reactivity.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/calendar/createCalendarState.ts +139 -58
- package/src/calendar/createDatePickerState.ts +14 -11
- package/src/calendar/createRangeCalendarState.ts +43 -17
- package/src/calendar/createTimeFieldState.ts +1 -1
- package/src/checkbox/createCheckboxGroupState.ts +16 -0
- package/src/collections/createListState.ts +4 -4
- package/src/collections/createMenuState.ts +134 -4
- package/src/collections/createSelectionState.ts +2 -2
- package/src/collections/index.ts +4 -0
- package/src/color/createColorAreaState.ts +19 -0
- package/src/color/createColorFieldState.ts +76 -26
- package/src/color/createColorSliderState.ts +25 -2
- package/src/color/createColorWheelState.ts +9 -5
- package/src/combobox/createComboBoxState.ts +371 -121
- package/src/combobox/index.ts +4 -0
- package/src/data/createAsyncList.ts +19 -4
- package/src/data/createListData.ts +8 -2
- package/src/data/createTreeData.ts +11 -3
- package/src/dnd/createDraggableCollectionState.ts +8 -1
- package/src/dnd/createDroppableCollectionState.ts +10 -0
- package/src/form/createFormValidationState.ts +23 -5
- package/src/grid/createGridState.ts +15 -8
- package/src/index.ts +18 -2
- package/src/menu/index.ts +5 -0
- package/src/numberfield/createNumberFieldState.ts +97 -35
- package/src/radio/createRadioGroupState.ts +4 -1
- package/src/select/createSelectState.ts +253 -131
- package/src/select/index.ts +8 -1
- package/src/selection/SelectionManager.ts +3 -1
- package/src/selection/createMultipleSelectionState.ts +2 -2
- package/src/slider/createSliderState.ts +227 -103
- package/src/ssr/index.ts +11 -4
- package/src/steplist/createStepListState.ts +156 -123
- package/src/table/createTableColumnResizeState.ts +51 -11
- package/src/table/createTableState.ts +5 -4
- package/src/table/createTreeGridState.ts +3 -4
- package/src/table/types.ts +2 -2
- package/src/tabs/createTabListState.ts +8 -31
- package/src/textfield/createTextFieldState.ts +6 -3
- package/src/toggle/createToggleGroupState.ts +4 -2
- package/src/toggle/createToggleState.ts +5 -4
- package/src/tokenfield/TokenFieldValue.ts +75 -16
- package/src/tokenfield/index.ts +1 -0
- package/src/tree/createTreeState.ts +15 -8
- package/src/utils/index.ts +1 -0
- package/src/utils/reactivity.ts +8 -0
- package/dist/flags/flags.d.ts.map +0 -1
- /package/dist/{flags → private/flags}/flags.d.ts +0 -0
- /package/src/{flags → private/flags}/flags.ts +0 -0
|
@@ -17,13 +17,12 @@
|
|
|
17
17
|
* Based on @react-stately/select useSelectState.
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
|
-
import { createMemo } from "solid-js";
|
|
20
|
+
import { createMemo, untrack } from "solid-js";
|
|
21
21
|
import type { Accessor } from "solid-js";
|
|
22
|
-
import { createInternalSignal,
|
|
22
|
+
import { createInternalSignal, propsAccessor, type MaybeAccessor } from "../utils";
|
|
23
23
|
import { createListState } from "../collections/createListState";
|
|
24
24
|
import { createOverlayTriggerState } from "../overlays";
|
|
25
|
-
import type { Key, CollectionNode, Collection } from "../collections/types";
|
|
26
|
-
import type { SelectionMode, Selection } from "../collections/types";
|
|
25
|
+
import type { Key, CollectionNode, Collection, FocusStrategy } from "../collections/types";
|
|
27
26
|
import type { SelectionManager } from "../selection/SelectionManager";
|
|
28
27
|
import {
|
|
29
28
|
createFormValidationState,
|
|
@@ -31,7 +30,22 @@ import {
|
|
|
31
30
|
type ValidationFunction,
|
|
32
31
|
} from "../form";
|
|
33
32
|
|
|
34
|
-
|
|
33
|
+
/** Select never uses the collection `"none"` mode. */
|
|
34
|
+
export type SelectSelectionMode = "single" | "multiple";
|
|
35
|
+
|
|
36
|
+
/** Single mode is `Key | null`. Multiple mode is `readonly Key[]`. */
|
|
37
|
+
export type SelectValueType<M extends SelectSelectionMode> = M extends "single"
|
|
38
|
+
? Key | null
|
|
39
|
+
: readonly Key[];
|
|
40
|
+
|
|
41
|
+
/** `onChange` receives a mutable `Key[]` in multiple mode. */
|
|
42
|
+
export type SelectChangeValueType<M extends SelectSelectionMode> = M extends "single"
|
|
43
|
+
? Key | null
|
|
44
|
+
: Key[];
|
|
45
|
+
|
|
46
|
+
type SelectValidationType<M extends SelectSelectionMode> = M extends "single" ? Key : Key[];
|
|
47
|
+
|
|
48
|
+
export interface SelectStateProps<T = unknown, M extends SelectSelectionMode = "single"> {
|
|
35
49
|
/** The items to display in the select. */
|
|
36
50
|
items: T[];
|
|
37
51
|
/** Function to get the key for an item. */
|
|
@@ -42,26 +56,48 @@ export interface SelectStateProps<T = unknown> {
|
|
|
42
56
|
getDisabled?: (item: T) => boolean;
|
|
43
57
|
/** Keys of disabled items. */
|
|
44
58
|
disabledKeys?: Iterable<Key>;
|
|
45
|
-
/**
|
|
59
|
+
/**
|
|
60
|
+
* Whether single or multiple selection is enabled.
|
|
61
|
+
* @default 'single'
|
|
62
|
+
*/
|
|
63
|
+
selectionMode?: M;
|
|
64
|
+
/**
|
|
65
|
+
* The current value (controlled).
|
|
66
|
+
* Single mode is `Key | null`. Multiple mode is `readonly Key[]`.
|
|
67
|
+
*/
|
|
68
|
+
value?: SelectValueType<M>;
|
|
69
|
+
/** The default value (uncontrolled). */
|
|
70
|
+
defaultValue?: SelectValueType<M>;
|
|
71
|
+
/** Handler called when the value changes. */
|
|
72
|
+
onChange?: (value: SelectChangeValueType<M>) => void;
|
|
73
|
+
/**
|
|
74
|
+
* The currently selected key in the collection (controlled).
|
|
75
|
+
* @deprecated Use `value`.
|
|
76
|
+
*/
|
|
46
77
|
selectedKey?: Key | null;
|
|
47
|
-
/**
|
|
78
|
+
/**
|
|
79
|
+
* The initial selected key in the collection (uncontrolled).
|
|
80
|
+
* @deprecated Use `defaultValue`.
|
|
81
|
+
*/
|
|
48
82
|
defaultSelectedKey?: Key | null;
|
|
49
|
-
/**
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
/** Handler called when the selection changes. */
|
|
83
|
+
/**
|
|
84
|
+
* Handler that is called when the selection changes.
|
|
85
|
+
* @deprecated Use `onChange`.
|
|
86
|
+
*/
|
|
54
87
|
onSelectionChange?: (key: Key | null) => void;
|
|
55
|
-
/** Handler called when selected keys change. */
|
|
56
|
-
onSelectionChangeKeys?: (keys: Selection) => void;
|
|
57
|
-
/** Selection mode for the select. */
|
|
58
|
-
selectionMode?: Extract<SelectionMode, "single" | "multiple">;
|
|
59
88
|
/** Whether the select is open (controlled). */
|
|
60
89
|
isOpen?: boolean;
|
|
61
90
|
/** Whether the select is open by default (uncontrolled). */
|
|
62
91
|
defaultOpen?: boolean;
|
|
63
92
|
/** Handler called when the open state changes. */
|
|
64
93
|
onOpenChange?: (isOpen: boolean) => void;
|
|
94
|
+
/**
|
|
95
|
+
* Whether the Select should close when an item is selected. Defaults to true
|
|
96
|
+
* when selectionMode is single, false otherwise.
|
|
97
|
+
*/
|
|
98
|
+
shouldCloseOnSelect?: boolean;
|
|
99
|
+
/** Whether the select should be allowed to be open when the collection is empty. */
|
|
100
|
+
allowsEmptyCollection?: boolean;
|
|
65
101
|
/** Whether the select is disabled. */
|
|
66
102
|
isDisabled?: boolean;
|
|
67
103
|
/** Whether the select is required. */
|
|
@@ -71,7 +107,7 @@ export interface SelectStateProps<T = unknown> {
|
|
|
71
107
|
/** @deprecated Use isInvalid instead. */
|
|
72
108
|
validationState?: "valid" | "invalid";
|
|
73
109
|
/** Custom validation function. */
|
|
74
|
-
validate?: ValidationFunction<
|
|
110
|
+
validate?: ValidationFunction<SelectValidationType<M> | null>;
|
|
75
111
|
/**
|
|
76
112
|
* Whether to use native HTML form validation or ARIA validation semantics.
|
|
77
113
|
* @default "native"
|
|
@@ -81,7 +117,10 @@ export interface SelectStateProps<T = unknown> {
|
|
|
81
117
|
name?: string | string[];
|
|
82
118
|
}
|
|
83
119
|
|
|
84
|
-
export interface SelectState<
|
|
120
|
+
export interface SelectState<
|
|
121
|
+
T = unknown,
|
|
122
|
+
M extends SelectSelectionMode = "single",
|
|
123
|
+
> extends FormValidationState {
|
|
85
124
|
/** The collection of items. */
|
|
86
125
|
readonly collection: Accessor<Collection<T>>;
|
|
87
126
|
/**
|
|
@@ -92,23 +131,48 @@ export interface SelectState<T = unknown> extends FormValidationState {
|
|
|
92
131
|
/** Whether the select dropdown is open. */
|
|
93
132
|
readonly isOpen: Accessor<boolean>;
|
|
94
133
|
/** Open the select dropdown. */
|
|
95
|
-
open(): void;
|
|
134
|
+
open(focusStrategy?: FocusStrategy | null): void;
|
|
96
135
|
/** Close the select dropdown. */
|
|
97
136
|
close(): void;
|
|
98
137
|
/** Toggle the select dropdown. */
|
|
99
|
-
toggle(): void;
|
|
100
|
-
/**
|
|
138
|
+
toggle(focusStrategy?: FocusStrategy | null): void;
|
|
139
|
+
/** Controls which item will be auto focused when the menu opens. */
|
|
140
|
+
readonly focusStrategy: Accessor<FocusStrategy | null>;
|
|
141
|
+
/**
|
|
142
|
+
* The key for the first selected item.
|
|
143
|
+
* @deprecated Use `value`.
|
|
144
|
+
*/
|
|
101
145
|
readonly selectedKey: Accessor<Key | null>;
|
|
102
|
-
/**
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
146
|
+
/**
|
|
147
|
+
* The default selected key.
|
|
148
|
+
* @deprecated Use `defaultValue`.
|
|
149
|
+
*/
|
|
150
|
+
readonly defaultSelectedKey: Key | null;
|
|
151
|
+
/**
|
|
152
|
+
* Sets the selected key.
|
|
153
|
+
* @deprecated Use `setValue`.
|
|
154
|
+
*/
|
|
109
155
|
setSelectedKey(key: Key | null): void;
|
|
110
|
-
/**
|
|
156
|
+
/** The current select value. */
|
|
157
|
+
readonly value: Accessor<SelectValueType<M>>;
|
|
158
|
+
/** The default select value. */
|
|
159
|
+
readonly defaultValue: SelectValueType<M>;
|
|
160
|
+
/** Sets the select value. */
|
|
161
|
+
setValue(value: Key | readonly Key[] | null): void;
|
|
162
|
+
/**
|
|
163
|
+
* Selected keys derived from `value`.
|
|
164
|
+
* Kept for list and hidden-select adapters. Never `"all"`.
|
|
165
|
+
*/
|
|
166
|
+
readonly selectedKeys: Accessor<Set<Key>>;
|
|
167
|
+
/** Replace the selected keys. */
|
|
111
168
|
setSelectedKeys(keys: Iterable<Key>): void;
|
|
169
|
+
/**
|
|
170
|
+
* The value of the first selected item.
|
|
171
|
+
* @deprecated Use `selectedItems`.
|
|
172
|
+
*/
|
|
173
|
+
readonly selectedItem: Accessor<CollectionNode<T> | null>;
|
|
174
|
+
/** The value of the selected items. */
|
|
175
|
+
readonly selectedItems: Accessor<CollectionNode<T>[]>;
|
|
112
176
|
/** The currently focused key. */
|
|
113
177
|
readonly focusedKey: Accessor<Key | null>;
|
|
114
178
|
/** Set the focused key. */
|
|
@@ -124,20 +188,29 @@ export interface SelectState<T = unknown> extends FormValidationState {
|
|
|
124
188
|
/** Whether the select is required. */
|
|
125
189
|
readonly isRequired: boolean;
|
|
126
190
|
/** The selection mode. */
|
|
127
|
-
readonly selectionMode: Accessor<
|
|
191
|
+
readonly selectionMode: Accessor<M>;
|
|
192
|
+
/** Whether selecting an item closes the menu. */
|
|
193
|
+
readonly shouldCloseOnSelect: Accessor<boolean>;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function convertValue(value: Key | readonly Key[] | null | undefined): readonly Key[] | undefined {
|
|
197
|
+
if (value === undefined) return undefined;
|
|
198
|
+
if (value === null || typeof value === "string" || typeof value === "number") {
|
|
199
|
+
return value == null ? [] : [value];
|
|
200
|
+
}
|
|
201
|
+
return value;
|
|
128
202
|
}
|
|
129
203
|
|
|
130
204
|
/**
|
|
131
205
|
* Creates state for a select component.
|
|
132
206
|
* Combines list state with overlay trigger state for dropdown behavior.
|
|
133
207
|
*/
|
|
134
|
-
export function createSelectState<T = unknown>(
|
|
135
|
-
props: MaybeAccessor<SelectStateProps<T>>,
|
|
136
|
-
): SelectState<T> {
|
|
137
|
-
const getProps = (
|
|
138
|
-
const selectionMode: Accessor<
|
|
208
|
+
export function createSelectState<T = unknown, M extends SelectSelectionMode = "single">(
|
|
209
|
+
props: MaybeAccessor<SelectStateProps<T, M>>,
|
|
210
|
+
): SelectState<T, M> {
|
|
211
|
+
const getProps = propsAccessor(props);
|
|
212
|
+
const selectionMode: Accessor<M> = () => (getProps().selectionMode ?? "single") as M;
|
|
139
213
|
|
|
140
|
-
// Overlay trigger state for open/close
|
|
141
214
|
const overlayState = createOverlayTriggerState({
|
|
142
215
|
get isOpen() {
|
|
143
216
|
return getProps().isOpen;
|
|
@@ -150,85 +223,137 @@ export function createSelectState<T = unknown>(
|
|
|
150
223
|
},
|
|
151
224
|
});
|
|
152
225
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
? "all"
|
|
162
|
-
: new Set(getProps().defaultSelectedKeys ?? []),
|
|
163
|
-
);
|
|
226
|
+
const resolvedDefault = untrack((): Key | readonly Key[] | null => {
|
|
227
|
+
const current = getProps();
|
|
228
|
+
if (current.defaultValue !== undefined) {
|
|
229
|
+
return current.defaultValue as Key | readonly Key[] | null;
|
|
230
|
+
}
|
|
231
|
+
const mode = current.selectionMode ?? "single";
|
|
232
|
+
return mode === "single" ? (current.defaultSelectedKey ?? null) : [];
|
|
233
|
+
});
|
|
164
234
|
|
|
165
|
-
const
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
return
|
|
235
|
+
const controlledProp = (): SelectValueType<M> | undefined => {
|
|
236
|
+
const current = getProps();
|
|
237
|
+
if (current.value !== undefined) return current.value;
|
|
238
|
+
if (selectionMode() === "single" && current.selectedKey !== undefined) {
|
|
239
|
+
return (current.selectedKey ?? null) as SelectValueType<M>;
|
|
170
240
|
}
|
|
241
|
+
return undefined;
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
const [internalValue, setInternalValue] = createInternalSignal<Key | readonly Key[] | null>(
|
|
245
|
+
resolvedDefault,
|
|
246
|
+
);
|
|
171
247
|
|
|
172
|
-
|
|
248
|
+
const readControlled = (): Key | readonly Key[] | null | undefined => {
|
|
249
|
+
const controlled = controlledProp();
|
|
250
|
+
return controlled === undefined ? undefined : (controlled as Key | readonly Key[] | null);
|
|
173
251
|
};
|
|
174
252
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
253
|
+
let valueRef: Key | readonly Key[] | null = untrack(() => {
|
|
254
|
+
const controlled = readControlled();
|
|
255
|
+
return controlled !== undefined ? controlled : internalValue();
|
|
256
|
+
});
|
|
257
|
+
let wroteThisTurn = false;
|
|
258
|
+
|
|
259
|
+
const syncValueRef = () => {
|
|
260
|
+
if (wroteThisTurn) return;
|
|
261
|
+
const controlled = readControlled();
|
|
262
|
+
valueRef = controlled !== undefined ? controlled : untrack(internalValue);
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
const displayValue = (): Key | readonly Key[] | null => {
|
|
266
|
+
const controlled = readControlled();
|
|
267
|
+
const raw = controlled !== undefined ? controlled : internalValue();
|
|
268
|
+
if (
|
|
269
|
+
selectionMode() === "single" &&
|
|
270
|
+
raw !== null &&
|
|
271
|
+
typeof raw !== "string" &&
|
|
272
|
+
typeof raw !== "number"
|
|
273
|
+
) {
|
|
274
|
+
return raw[0] ?? null;
|
|
275
|
+
}
|
|
276
|
+
return raw;
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
const setValue = (next: Key | readonly Key[] | null) => {
|
|
280
|
+
syncValueRef();
|
|
281
|
+
if (selectionMode() === "single") {
|
|
282
|
+
const key: Key | null =
|
|
283
|
+
typeof next === "string" || typeof next === "number"
|
|
284
|
+
? next
|
|
285
|
+
: next === null
|
|
286
|
+
? null
|
|
287
|
+
: (next[0] ?? null);
|
|
288
|
+
// Both callbacks follow this request, not a comparison with displayValue.
|
|
289
|
+
// A refused controlled value stays stale for the turn, so a second
|
|
290
|
+
// same-key call would notify the legacy callback again. React Stately
|
|
291
|
+
// still compares displayValue; this is the Solid same-turn adaptation.
|
|
292
|
+
// A later turn resyncs valueRef, and a different key is its own request.
|
|
293
|
+
if (!Object.is(valueRef, key)) {
|
|
294
|
+
valueRef = key;
|
|
295
|
+
wroteThisTurn = true;
|
|
296
|
+
queueMicrotask(() => {
|
|
297
|
+
wroteThisTurn = false;
|
|
298
|
+
});
|
|
299
|
+
if (controlledProp() === undefined) {
|
|
300
|
+
setInternalValue(key);
|
|
187
301
|
}
|
|
188
|
-
getProps().
|
|
302
|
+
getProps().onChange?.(key as SelectChangeValueType<M>);
|
|
189
303
|
getProps().onSelectionChange?.(key);
|
|
190
304
|
}
|
|
191
305
|
return;
|
|
192
306
|
}
|
|
193
307
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
if (
|
|
204
|
-
|
|
205
|
-
return keys === "all" ? "all" : new Set<Key>(keys ?? []);
|
|
308
|
+
// Keep the readonly list for state. `onChange` receives its own mutable copy.
|
|
309
|
+
const keys: readonly Key[] =
|
|
310
|
+
typeof next === "string" || typeof next === "number" ? [next] : next === null ? [] : next;
|
|
311
|
+
if (!Object.is(valueRef, keys)) {
|
|
312
|
+
valueRef = keys;
|
|
313
|
+
wroteThisTurn = true;
|
|
314
|
+
queueMicrotask(() => {
|
|
315
|
+
wroteThisTurn = false;
|
|
316
|
+
});
|
|
317
|
+
if (controlledProp() === undefined) {
|
|
318
|
+
setInternalValue(keys);
|
|
206
319
|
}
|
|
207
|
-
|
|
320
|
+
getProps().onChange?.([...keys] as SelectChangeValueType<M>);
|
|
208
321
|
}
|
|
209
|
-
|
|
210
|
-
const key = selectedKey();
|
|
211
|
-
return key != null ? new Set<Key>([key]) : new Set<Key>();
|
|
212
|
-
});
|
|
322
|
+
};
|
|
213
323
|
|
|
214
324
|
const setSelectedKeys = (keys: Iterable<Key>) => {
|
|
215
|
-
const next = new Set(keys);
|
|
216
325
|
if (selectionMode() === "multiple") {
|
|
217
|
-
|
|
218
|
-
setInternalSelectedKeys(next);
|
|
219
|
-
}
|
|
220
|
-
getProps().onSelectionChangeKeys?.(next);
|
|
221
|
-
getProps().onSelectionChange?.(next.size > 0 ? Array.from(next)[0] : null);
|
|
326
|
+
setValue([...keys]);
|
|
222
327
|
return;
|
|
223
328
|
}
|
|
224
|
-
|
|
225
|
-
const
|
|
226
|
-
|
|
329
|
+
let key: Key | null = null;
|
|
330
|
+
for (const item of keys) {
|
|
331
|
+
key = item;
|
|
332
|
+
break;
|
|
333
|
+
}
|
|
334
|
+
setValue(key);
|
|
227
335
|
};
|
|
228
336
|
|
|
337
|
+
const initialDisplay = untrack(displayValue);
|
|
338
|
+
const stateDefaultValue = (resolvedDefault ?? initialDisplay) as SelectValueType<M>;
|
|
339
|
+
const defaultSelectedKey =
|
|
340
|
+
untrack(() => getProps().defaultSelectedKey) ??
|
|
341
|
+
(untrack(() => getProps().selectionMode) === "single" ? (initialDisplay as Key) : null);
|
|
342
|
+
|
|
343
|
+
const shouldCloseOnSelect = () => getProps().shouldCloseOnSelect ?? selectionMode() === "single";
|
|
344
|
+
|
|
229
345
|
const validation = createFormValidationState({
|
|
230
346
|
get value() {
|
|
231
|
-
|
|
347
|
+
const display = displayValue();
|
|
348
|
+
if (
|
|
349
|
+
display !== null &&
|
|
350
|
+
typeof display !== "string" &&
|
|
351
|
+
typeof display !== "number" &&
|
|
352
|
+
display.length === 0
|
|
353
|
+
) {
|
|
354
|
+
return null;
|
|
355
|
+
}
|
|
356
|
+
return display;
|
|
232
357
|
},
|
|
233
358
|
get isInvalid() {
|
|
234
359
|
return getProps().isInvalid;
|
|
@@ -237,7 +362,7 @@ export function createSelectState<T = unknown>(
|
|
|
237
362
|
return getProps().validationState;
|
|
238
363
|
},
|
|
239
364
|
get validate() {
|
|
240
|
-
return getProps().validate;
|
|
365
|
+
return getProps().validate as ValidationFunction<unknown> | undefined;
|
|
241
366
|
},
|
|
242
367
|
get name() {
|
|
243
368
|
return getProps().name;
|
|
@@ -247,7 +372,6 @@ export function createSelectState<T = unknown>(
|
|
|
247
372
|
},
|
|
248
373
|
});
|
|
249
374
|
|
|
250
|
-
// Create list state with select selection mode
|
|
251
375
|
const listState = createListState<T>({
|
|
252
376
|
get items() {
|
|
253
377
|
return getProps().items;
|
|
@@ -274,39 +398,32 @@ export function createSelectState<T = unknown>(
|
|
|
274
398
|
},
|
|
275
399
|
allowDuplicateSelectionEvents: true,
|
|
276
400
|
get selectedKeys() {
|
|
277
|
-
|
|
278
|
-
if (keys === "all") return "all";
|
|
279
|
-
return keys;
|
|
401
|
+
return convertValue(displayValue()) ?? [];
|
|
280
402
|
},
|
|
281
403
|
onSelectionChange(keys) {
|
|
282
|
-
if (selectionMode() === "multiple") {
|
|
283
|
-
if (!isControlledMultiple()) {
|
|
284
|
-
setInternalSelectedKeys(keys);
|
|
285
|
-
}
|
|
286
|
-
getProps().onSelectionChangeKeys?.(keys);
|
|
287
|
-
if (keys !== "all") {
|
|
288
|
-
getProps().onSelectionChange?.(keys.size > 0 ? Array.from(keys)[0] : null);
|
|
289
|
-
}
|
|
290
|
-
validation.commitValidation();
|
|
291
|
-
return;
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
// Get the first (and only) selected key
|
|
295
404
|
if (keys === "all") return;
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
405
|
+
if (selectionMode() === "single") {
|
|
406
|
+
setValue(keys.values().next().value ?? null);
|
|
407
|
+
} else {
|
|
408
|
+
setValue([...keys]);
|
|
409
|
+
}
|
|
410
|
+
if (shouldCloseOnSelect()) {
|
|
411
|
+
overlayState.close();
|
|
412
|
+
}
|
|
299
413
|
validation.commitValidation();
|
|
300
414
|
},
|
|
301
415
|
});
|
|
302
416
|
|
|
417
|
+
const [focusStrategy, setFocusStrategy] = createInternalSignal<FocusStrategy | null>(null);
|
|
303
418
|
// The select's own focus state (the trigger), separate from the collection's
|
|
304
419
|
// focus-within state on the selection manager — upstream useSelectState keeps
|
|
305
420
|
// these apart with a dedicated useState. Sharing the manager's signal makes
|
|
306
421
|
// trigger focus re-arm the item roving-focus effect and steal focus back.
|
|
307
422
|
const [isFocused, setFocused] = createInternalSignal(false);
|
|
308
423
|
|
|
309
|
-
|
|
424
|
+
const selectedKey: Accessor<Key | null> = () => listState.selectionManager.firstSelectedKey;
|
|
425
|
+
const selectedKeys: Accessor<Set<Key>> = () => new Set(convertValue(displayValue()) ?? []);
|
|
426
|
+
|
|
310
427
|
const selectedItem: Accessor<CollectionNode<T> | null> = createMemo(() => {
|
|
311
428
|
const key = selectedKey();
|
|
312
429
|
if (key == null) return null;
|
|
@@ -314,48 +431,53 @@ export function createSelectState<T = unknown>(
|
|
|
314
431
|
});
|
|
315
432
|
|
|
316
433
|
const selectedItems: Accessor<CollectionNode<T>[]> = createMemo(() => {
|
|
317
|
-
const keys = selectedKeys();
|
|
318
|
-
if (keys === "all") {
|
|
319
|
-
return Array.from(listState.collection());
|
|
320
|
-
}
|
|
321
|
-
|
|
322
434
|
const items: CollectionNode<T>[] = [];
|
|
323
|
-
for (const key of
|
|
435
|
+
for (const key of listState.selectionManager.selectedKeys) {
|
|
324
436
|
const item = listState.collection().getItem(key);
|
|
325
437
|
if (item) items.push(item);
|
|
326
438
|
}
|
|
327
439
|
return items;
|
|
328
440
|
});
|
|
329
441
|
|
|
442
|
+
const canOpenMenu = () =>
|
|
443
|
+
listState.collection().size !== 0 || getProps().allowsEmptyCollection === true;
|
|
444
|
+
|
|
330
445
|
return {
|
|
331
446
|
realtimeValidation: validation.realtimeValidation,
|
|
332
447
|
displayValidation: validation.displayValidation,
|
|
333
448
|
updateValidation: validation.updateValidation,
|
|
334
449
|
resetValidation: validation.resetValidation,
|
|
335
450
|
commitValidation: validation.commitValidation,
|
|
336
|
-
// Collection
|
|
337
451
|
collection: listState.collection,
|
|
338
452
|
selectionManager: listState.selectionManager,
|
|
339
|
-
|
|
340
|
-
// Focus management
|
|
341
453
|
focusedKey: listState.focusedKey,
|
|
342
454
|
setFocusedKey: listState.setFocusedKey,
|
|
343
455
|
isFocused,
|
|
344
456
|
setFocused,
|
|
345
|
-
|
|
346
|
-
// Overlay state
|
|
347
457
|
isOpen: overlayState.isOpen,
|
|
348
|
-
open:
|
|
458
|
+
open(strategy: FocusStrategy | null = null) {
|
|
459
|
+
if (!canOpenMenu()) return;
|
|
460
|
+
setFocusStrategy(strategy);
|
|
461
|
+
overlayState.open();
|
|
462
|
+
},
|
|
349
463
|
close: overlayState.close,
|
|
350
|
-
toggle:
|
|
351
|
-
|
|
352
|
-
|
|
464
|
+
toggle(strategy: FocusStrategy | null = null) {
|
|
465
|
+
if (!canOpenMenu()) return;
|
|
466
|
+
setFocusStrategy(strategy);
|
|
467
|
+
overlayState.toggle();
|
|
468
|
+
},
|
|
469
|
+
focusStrategy,
|
|
353
470
|
selectionMode,
|
|
471
|
+
shouldCloseOnSelect,
|
|
472
|
+
value: () => displayValue() as SelectValueType<M>,
|
|
473
|
+
defaultValue: stateDefaultValue,
|
|
474
|
+
setValue,
|
|
354
475
|
selectedKey,
|
|
476
|
+
defaultSelectedKey,
|
|
477
|
+
setSelectedKey: setValue,
|
|
355
478
|
selectedKeys,
|
|
356
479
|
selectedItem,
|
|
357
480
|
selectedItems,
|
|
358
|
-
setSelectedKey,
|
|
359
481
|
setSelectedKeys,
|
|
360
482
|
isKeyDisabled: (key: Key) => listState.isDisabled(key),
|
|
361
483
|
get isDisabled() {
|
package/src/select/index.ts
CHANGED
|
@@ -1 +1,8 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export {
|
|
2
|
+
createSelectState,
|
|
3
|
+
type SelectStateProps,
|
|
4
|
+
type SelectState,
|
|
5
|
+
type SelectSelectionMode,
|
|
6
|
+
type SelectValueType,
|
|
7
|
+
type SelectChangeValueType,
|
|
8
|
+
} from "./createSelectState";
|
|
@@ -518,7 +518,9 @@ export class SelectionManager<T = unknown> {
|
|
|
518
518
|
const item = this.collection.getItem(key);
|
|
519
519
|
return (
|
|
520
520
|
this.state.disabledBehavior === "all" &&
|
|
521
|
-
(this.state.disabledKeys.has(key) ||
|
|
521
|
+
(this.state.disabledKeys.has(key) ||
|
|
522
|
+
!!item?.props?.isDisabled ||
|
|
523
|
+
item?.isDisabled === true) &&
|
|
522
524
|
item?.props?.disabledBehavior !== "selection"
|
|
523
525
|
);
|
|
524
526
|
}
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
|
|
26
26
|
import { createMemo, createEffect } from "solid-js";
|
|
27
27
|
import type { Accessor } from "solid-js";
|
|
28
|
-
import {
|
|
28
|
+
import { createInternalSignal, propsAccessor, readNow, type MaybeAccessor } from "../utils";
|
|
29
29
|
import type { SelectionStateProps } from "../collections/createSelectionState";
|
|
30
30
|
import type {
|
|
31
31
|
DisabledBehavior,
|
|
@@ -114,7 +114,7 @@ function convertSelection(
|
|
|
114
114
|
export function createMultipleSelectionState(
|
|
115
115
|
props: MaybeAccessor<SelectionStateProps> = {},
|
|
116
116
|
): MultipleSelectionState {
|
|
117
|
-
const getProps = (
|
|
117
|
+
const getProps = propsAccessor(props);
|
|
118
118
|
|
|
119
119
|
// Focus is a plain signal in Solid; upstream needs a ref + state pair only
|
|
120
120
|
// because React requires both a synchronous read and a re-render trigger.
|