@proyecto-viviana/solid-stately 0.2.7 → 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.
- package/README.md +30 -283
- package/dist/autocomplete/createAutocompleteState.d.ts +2 -2
- package/dist/autocomplete/index.d.ts +1 -1
- package/dist/calendar/IncompleteDate.d.ts +61 -0
- package/dist/calendar/createCalendarState.d.ts +41 -16
- package/dist/calendar/createDateFieldState.d.ts +39 -9
- package/dist/calendar/createDatePickerState.d.ts +60 -0
- package/dist/calendar/createRangeCalendarState.d.ts +37 -10
- package/dist/calendar/createTimeFieldState.d.ts +34 -7
- package/dist/calendar/index.d.ts +7 -6
- package/dist/checkbox/createCheckboxGroupState.d.ts +6 -6
- package/dist/checkbox/index.d.ts +1 -1
- package/dist/collections/ListCollection.d.ts +1 -1
- package/dist/collections/createListState.d.ts +11 -8
- package/dist/collections/createMenuState.d.ts +7 -7
- package/dist/collections/createSelectionState.d.ts +22 -10
- package/dist/collections/getChildNodes.d.ts +11 -0
- package/dist/collections/index.d.ts +6 -5
- package/dist/collections/types.d.ts +12 -12
- package/dist/color/Color.d.ts +1 -1
- package/dist/color/createColorAreaState.d.ts +4 -2
- package/dist/color/createColorFieldState.d.ts +38 -12
- package/dist/color/createColorSliderState.d.ts +9 -2
- package/dist/color/createColorWheelState.d.ts +13 -2
- package/dist/color/getColorChannels.d.ts +1 -1
- package/dist/color/index.d.ts +6 -6
- package/dist/color/types.d.ts +4 -4
- package/dist/combobox/createComboBoxState.d.ts +34 -13
- package/dist/combobox/index.d.ts +1 -1
- package/dist/data/createAsyncList.d.ts +4 -4
- package/dist/data/createListData.d.ts +2 -2
- package/dist/data/createTreeData.d.ts +6 -2
- package/dist/data/index.d.ts +3 -3
- package/dist/datepicker/index.d.ts +2 -4
- package/dist/disclosure/createDisclosureState.d.ts +2 -2
- package/dist/disclosure/index.d.ts +1 -1
- package/dist/dnd/createDragState.d.ts +2 -2
- package/dist/dnd/createDraggableCollectionState.d.ts +2 -2
- package/dist/dnd/createDropState.d.ts +2 -2
- package/dist/dnd/createDroppableCollectionState.d.ts +3 -3
- package/dist/dnd/index.d.ts +5 -5
- package/dist/dnd/types.d.ts +19 -17
- package/dist/form/createFormValidationState.d.ts +3 -3
- package/dist/form/index.d.ts +1 -1
- package/dist/grid/createGridState.d.ts +2 -2
- package/dist/grid/index.d.ts +2 -2
- package/dist/grid/types.d.ts +27 -11
- package/dist/index.d.ts +31 -30
- package/dist/index.js +9077 -7246
- package/dist/index.js.map +1 -1
- package/dist/menu/index.d.ts +2 -2
- package/dist/numberfield/createNumberFieldState.d.ts +2 -2
- package/dist/numberfield/index.d.ts +1 -1
- package/dist/overlays/createOverlayTriggerState.d.ts +2 -2
- package/dist/overlays/index.d.ts +1 -1
- package/dist/radio/createRadioGroupState.d.ts +7 -7
- package/dist/radio/index.d.ts +1 -1
- package/dist/searchfield/createSearchFieldState.d.ts +2 -2
- package/dist/searchfield/index.d.ts +2 -2
- package/dist/select/createSelectState.d.ts +14 -8
- package/dist/select/index.d.ts +1 -1
- package/dist/selection/Selection.d.ts +16 -0
- package/dist/selection/SelectionManager.d.ts +110 -0
- package/dist/selection/createMultipleSelectionState.d.ts +59 -0
- package/dist/selection/index.d.ts +6 -5
- package/dist/slider/createSliderState.d.ts +3 -3
- package/dist/slider/index.d.ts +2 -2
- package/dist/steplist/createStepListState.d.ts +48 -0
- package/dist/steplist/index.d.ts +1 -0
- package/dist/table/TableCollection.d.ts +33 -3
- package/dist/table/createTableColumnResizeState.d.ts +54 -0
- package/dist/table/createTableState.d.ts +2 -2
- package/dist/table/createTreeGridState.d.ts +15 -0
- package/dist/table/index.d.ts +6 -3
- package/dist/table/types.d.ts +68 -10
- package/dist/tabs/createTabListState.d.ts +5 -5
- package/dist/tabs/index.d.ts +1 -1
- package/dist/textfield/createTextFieldState.d.ts +2 -2
- package/dist/textfield/index.d.ts +1 -1
- package/dist/toast/createToastState.d.ts +24 -2
- package/dist/toast/index.d.ts +1 -1
- package/dist/toggle/createToggleGroupState.d.ts +4 -4
- package/dist/toggle/createToggleState.d.ts +2 -2
- package/dist/toggle/index.d.ts +2 -2
- package/dist/tooltip/createTooltipTriggerState.d.ts +2 -2
- package/dist/tooltip/index.d.ts +1 -1
- package/dist/tree/TreeCollection.d.ts +2 -2
- package/dist/tree/createTreeState.d.ts +2 -2
- package/dist/tree/index.d.ts +3 -3
- package/dist/tree/types.d.ts +27 -13
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/reactivity.d.ts +1 -1
- package/package.json +29 -30
- package/src/autocomplete/createAutocompleteState.d.ts +19 -17
- package/src/autocomplete/createAutocompleteState.ts +24 -30
- package/src/autocomplete/index.d.ts +6 -2
- package/src/autocomplete/index.ts +1 -1
- package/src/calendar/IncompleteDate.ts +430 -0
- package/src/calendar/createCalendarState.d.ts +139 -117
- package/src/calendar/createCalendarState.ts +308 -94
- package/src/calendar/createDateFieldState.d.ts +130 -95
- package/src/calendar/createDateFieldState.ts +447 -334
- package/src/calendar/createDatePickerState.ts +347 -0
- package/src/calendar/createRangeCalendarState.d.ts +154 -129
- package/src/calendar/createRangeCalendarState.ts +411 -85
- package/src/calendar/createTimeFieldState.d.ts +81 -79
- package/src/calendar/createTimeFieldState.ts +187 -108
- package/src/calendar/index.d.ts +80 -7
- package/src/calendar/index.ts +23 -12
- package/src/checkbox/createCheckboxGroupState.d.ts +57 -55
- package/src/checkbox/createCheckboxGroupState.ts +20 -29
- package/src/checkbox/index.d.ts +6 -2
- package/src/checkbox/index.ts +1 -1
- package/src/collections/ListCollection.d.ts +25 -22
- package/src/collections/ListCollection.ts +6 -6
- package/src/collections/createListState.d.ts +73 -58
- package/src/collections/createListState.ts +67 -35
- package/src/collections/createMenuState.d.ts +36 -31
- package/src/collections/createMenuState.ts +21 -12
- package/src/collections/createSelectionState.d.ts +78 -65
- package/src/collections/createSelectionState.ts +66 -52
- package/src/collections/getChildNodes.ts +98 -0
- package/src/collections/index.d.ts +38 -6
- package/src/collections/index.ts +14 -10
- package/src/collections/types.d.ts +101 -101
- package/src/collections/types.ts +12 -12
- package/src/color/Color.d.ts +20 -5
- package/src/color/Color.ts +357 -254
- package/src/color/createColorAreaState.d.ts +67 -65
- package/src/color/createColorAreaState.ts +21 -13
- package/src/color/createColorFieldState.d.ts +46 -44
- package/src/color/createColorFieldState.ts +285 -149
- package/src/color/createColorSliderState.d.ts +58 -56
- package/src/color/createColorSliderState.ts +45 -14
- package/src/color/createColorWheelState.d.ts +42 -40
- package/src/color/createColorWheelState.ts +101 -64
- package/src/color/getColorChannels.ts +7 -5
- package/src/color/index.d.ts +36 -7
- package/src/color/index.ts +6 -9
- package/src/color/types.d.ts +68 -63
- package/src/color/types.ts +11 -19
- package/src/combobox/createComboBoxState.d.ts +107 -105
- package/src/combobox/createComboBoxState.ts +184 -89
- package/src/combobox/index.d.ts +10 -2
- package/src/combobox/index.ts +1 -1
- package/src/data/createAsyncList.ts +89 -63
- package/src/data/createListData.ts +67 -67
- package/src/data/createTreeData.ts +304 -91
- package/src/data/index.ts +3 -3
- package/src/datepicker/index.ts +6 -10
- package/src/disclosure/createDisclosureState.d.ts +47 -43
- package/src/disclosure/createDisclosureState.ts +7 -21
- package/src/disclosure/index.d.ts +10 -2
- package/src/disclosure/index.ts +1 -1
- package/src/dnd/createDragState.d.ts +51 -42
- package/src/dnd/createDragState.ts +12 -14
- package/src/dnd/createDraggableCollectionState.d.ts +52 -41
- package/src/dnd/createDraggableCollectionState.ts +14 -21
- package/src/dnd/createDropState.d.ts +64 -45
- package/src/dnd/createDropState.ts +22 -32
- package/src/dnd/createDroppableCollectionState.d.ts +88 -59
- package/src/dnd/createDroppableCollectionState.ts +40 -43
- package/src/dnd/index.d.ts +55 -6
- package/src/dnd/index.ts +6 -18
- package/src/dnd/types.d.ts +170 -157
- package/src/dnd/types.ts +24 -22
- package/src/form/createFormValidationState.d.ts +47 -45
- package/src/form/createFormValidationState.ts +30 -63
- package/src/form/index.d.ts +16 -2
- package/src/form/index.ts +1 -1
- package/src/grid/createGridState.d.ts +7 -4
- package/src/grid/createGridState.ts +78 -51
- package/src/grid/index.d.ts +3 -3
- package/src/grid/index.ts +2 -8
- package/src/grid/types.d.ts +136 -129
- package/src/grid/types.ts +34 -18
- package/src/index.d.ts +311 -26
- package/src/index.ts +59 -89
- package/src/menu/index.ts +2 -4
- package/src/numberfield/createNumberFieldState.d.ts +56 -54
- package/src/numberfield/createNumberFieldState.ts +26 -31
- package/src/numberfield/index.d.ts +6 -2
- package/src/numberfield/index.ts +1 -1
- package/src/overlays/createOverlayTriggerState.d.ts +22 -20
- package/src/overlays/createOverlayTriggerState.ts +3 -4
- package/src/overlays/index.d.ts +6 -2
- package/src/overlays/index.ts +1 -1
- package/src/radio/createRadioGroupState.d.ts +54 -52
- package/src/radio/createRadioGroupState.ts +27 -29
- package/src/radio/index.d.ts +7 -2
- package/src/radio/index.ts +1 -1
- package/src/searchfield/createSearchFieldState.d.ts +16 -14
- package/src/searchfield/createSearchFieldState.ts +5 -11
- package/src/searchfield/index.d.ts +3 -3
- package/src/searchfield/index.ts +2 -5
- package/src/select/createSelectState.d.ts +63 -61
- package/src/select/createSelectState.ts +42 -29
- package/src/select/index.d.ts +2 -2
- package/src/select/index.ts +1 -5
- package/src/selection/Selection.ts +28 -0
- package/src/selection/SelectionManager.ts +526 -0
- package/src/selection/createMultipleSelectionState.ts +247 -0
- package/src/selection/index.ts +12 -7
- package/src/slider/createSliderState.d.ts +60 -60
- package/src/slider/createSliderState.ts +55 -33
- package/src/slider/index.d.ts +3 -3
- package/src/slider/index.ts +2 -6
- package/src/ssr/index.d.ts +1 -1
- package/src/ssr/index.ts +2 -2
- package/src/steplist/createStepListState.ts +191 -0
- package/src/steplist/index.ts +5 -0
- package/src/table/TableCollection.d.ts +42 -40
- package/src/table/TableCollection.ts +266 -99
- package/src/table/createTableColumnResizeState.ts +242 -0
- package/src/table/createTableState.d.ts +7 -4
- package/src/table/createTableState.ts +30 -7
- package/src/table/createTreeGridState.ts +146 -0
- package/src/table/index.d.ts +14 -4
- package/src/table/index.ts +13 -3
- package/src/table/types.d.ts +98 -92
- package/src/table/types.ts +83 -13
- package/src/tabs/createTabListState.d.ts +59 -57
- package/src/tabs/createTabListState.ts +34 -39
- package/src/tabs/index.d.ts +8 -2
- package/src/tabs/index.ts +1 -1
- package/src/textfield/createTextFieldState.d.ts +16 -14
- package/src/textfield/createTextFieldState.ts +7 -18
- package/src/textfield/index.d.ts +6 -2
- package/src/textfield/index.ts +1 -1
- package/src/toast/createToastState.d.ts +92 -86
- package/src/toast/createToastState.ts +90 -52
- package/src/toast/index.d.ts +12 -2
- package/src/toast/index.ts +2 -1
- package/src/toggle/createToggleGroupState.ts +11 -21
- package/src/toggle/createToggleState.d.ts +19 -19
- package/src/toggle/createToggleState.ts +2 -16
- package/src/toggle/index.d.ts +2 -2
- package/src/toggle/index.ts +2 -6
- package/src/tooltip/createTooltipTriggerState.d.ts +17 -17
- package/src/tooltip/createTooltipTriggerState.ts +12 -11
- package/src/tooltip/index.d.ts +7 -2
- package/src/tooltip/index.ts +1 -1
- package/src/tree/TreeCollection.d.ts +26 -23
- package/src/tree/TreeCollection.ts +29 -22
- package/src/tree/createTreeState.d.ts +7 -4
- package/src/tree/createTreeState.ts +70 -41
- package/src/tree/index.d.ts +10 -4
- package/src/tree/index.ts +3 -3
- package/src/tree/types.d.ts +131 -126
- package/src/tree/types.ts +30 -13
- package/src/utils/index.d.ts +2 -2
- package/src/utils/index.ts +1 -1
- package/src/utils/reactivity.d.ts +2 -2
- package/src/utils/reactivity.ts +3 -3
|
@@ -6,26 +6,22 @@
|
|
|
6
6
|
* either type to filter options or select from a list.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import { createSignal, createMemo, createEffect, type Accessor } from
|
|
10
|
-
import { access, type MaybeAccessor } from
|
|
11
|
-
import { createListState } from
|
|
12
|
-
import { createOverlayTriggerState } from
|
|
13
|
-
import type { Key, CollectionNode, Collection, FocusStrategy } from
|
|
9
|
+
import { createSignal, createMemo, createEffect, type Accessor } from "solid-js";
|
|
10
|
+
import { access, type MaybeAccessor } from "../utils";
|
|
11
|
+
import { createListState } from "../collections/createListState";
|
|
12
|
+
import { createOverlayTriggerState } from "../overlays";
|
|
13
|
+
import type { Key, CollectionNode, Collection, FocusStrategy } from "../collections/types";
|
|
14
|
+
import type { SelectionManager } from "../selection/SelectionManager";
|
|
14
15
|
|
|
15
|
-
|
|
16
|
-
// TYPES
|
|
17
|
-
// ============================================
|
|
16
|
+
export type MenuTriggerAction = "focus" | "input" | "manual";
|
|
18
17
|
|
|
19
|
-
export type
|
|
20
|
-
|
|
21
|
-
// Re-export FocusStrategy for convenience
|
|
22
|
-
export type { FocusStrategy } from '../collections/types';
|
|
18
|
+
export type { FocusStrategy } from "../collections/types";
|
|
23
19
|
|
|
24
20
|
export type FilterFn = (textValue: string, inputValue: string) => boolean;
|
|
25
21
|
|
|
26
22
|
export interface ComboBoxStateProps<T = unknown> {
|
|
27
23
|
/** The items to display in the combobox dropdown. */
|
|
28
|
-
items
|
|
24
|
+
items?: T[];
|
|
29
25
|
/** Default items when uncontrolled. */
|
|
30
26
|
defaultItems?: T[];
|
|
31
27
|
/** Function to get the key for an item. */
|
|
@@ -36,12 +32,20 @@ export interface ComboBoxStateProps<T = unknown> {
|
|
|
36
32
|
getDisabled?: (item: T) => boolean;
|
|
37
33
|
/** Keys of disabled items. */
|
|
38
34
|
disabledKeys?: Iterable<Key>;
|
|
39
|
-
/** The
|
|
35
|
+
/** The selection mode for the combobox. */
|
|
36
|
+
selectionMode?: "single" | "multiple";
|
|
37
|
+
/** The currently selected key (controlled, single mode). */
|
|
40
38
|
selectedKey?: Key | null;
|
|
41
|
-
/** The default selected key (uncontrolled). */
|
|
39
|
+
/** The default selected key (uncontrolled, single mode). */
|
|
42
40
|
defaultSelectedKey?: Key | null;
|
|
43
|
-
/**
|
|
41
|
+
/** The currently selected keys (controlled, multiple mode). */
|
|
42
|
+
selectedKeys?: Iterable<Key>;
|
|
43
|
+
/** The default selected keys (uncontrolled, multiple mode). */
|
|
44
|
+
defaultSelectedKeys?: Iterable<Key>;
|
|
45
|
+
/** Handler called when the selection changes (single mode). */
|
|
44
46
|
onSelectionChange?: (key: Key | null) => void;
|
|
47
|
+
/** Handler called when the selection changes (multiple mode). */
|
|
48
|
+
onSelectionChangeMultiple?: (keys: Set<Key>) => void;
|
|
45
49
|
/** The current input value (controlled). */
|
|
46
50
|
inputValue?: string;
|
|
47
51
|
/** The default input value (uncontrolled). */
|
|
@@ -75,6 +79,12 @@ export interface ComboBoxStateProps<T = unknown> {
|
|
|
75
79
|
export interface ComboBoxState<T = unknown> {
|
|
76
80
|
/** The collection of items (may be filtered). */
|
|
77
81
|
readonly collection: Accessor<Collection<T>>;
|
|
82
|
+
/**
|
|
83
|
+
* The collection-aware selection manager. Mirrors upstream `useComboBoxState`,
|
|
84
|
+
* which returns the `useListState` selection manager bound to the unfiltered
|
|
85
|
+
* collection (selection spans all items; filtering is display-only).
|
|
86
|
+
*/
|
|
87
|
+
readonly selectionManager: SelectionManager<T>;
|
|
78
88
|
/** Whether the combobox dropdown is open. */
|
|
79
89
|
readonly isOpen: Accessor<boolean>;
|
|
80
90
|
/** Open the combobox dropdown. */
|
|
@@ -83,14 +93,20 @@ export interface ComboBoxState<T = unknown> {
|
|
|
83
93
|
close(): void;
|
|
84
94
|
/** Toggle the combobox dropdown. */
|
|
85
95
|
toggle(focusStrategy?: FocusStrategy | null, trigger?: MenuTriggerAction): void;
|
|
86
|
-
/** The currently selected key. */
|
|
96
|
+
/** The currently selected key (single mode). */
|
|
87
97
|
readonly selectedKey: Accessor<Key | null>;
|
|
88
98
|
/** The default selected key. */
|
|
89
99
|
readonly defaultSelectedKey: Key | null;
|
|
90
|
-
/** The currently selected item. */
|
|
100
|
+
/** The currently selected item (single mode). */
|
|
91
101
|
readonly selectedItem: Accessor<CollectionNode<T> | null>;
|
|
92
102
|
/** Set the selected key. */
|
|
93
103
|
setSelectedKey(key: Key | null): void;
|
|
104
|
+
/** The currently selected keys (multiple mode). */
|
|
105
|
+
readonly selectedKeys: Accessor<Set<Key>>;
|
|
106
|
+
/** The currently selected items (multiple mode). */
|
|
107
|
+
readonly selectedItems: Accessor<CollectionNode<T>[]>;
|
|
108
|
+
/** Remove a selected key (multiple mode). */
|
|
109
|
+
removeSelectedKey(key: Key): void;
|
|
94
110
|
/** The current input value. */
|
|
95
111
|
readonly inputValue: Accessor<string>;
|
|
96
112
|
/** The default input value. */
|
|
@@ -115,8 +131,8 @@ export interface ComboBoxState<T = unknown> {
|
|
|
115
131
|
isKeyDisabled(key: Key): boolean;
|
|
116
132
|
/** Select a key and close the menu (for ListState compatibility). */
|
|
117
133
|
select(key: Key): void;
|
|
118
|
-
/** The selection mode
|
|
119
|
-
readonly selectionMode: Accessor<
|
|
134
|
+
/** The selection mode. */
|
|
135
|
+
readonly selectionMode: Accessor<"single" | "multiple">;
|
|
120
136
|
/** Check if a key is selected. */
|
|
121
137
|
isSelected(key: Key): boolean;
|
|
122
138
|
/** Whether the combobox is disabled. */
|
|
@@ -127,10 +143,6 @@ export interface ComboBoxState<T = unknown> {
|
|
|
127
143
|
readonly isRequired: boolean;
|
|
128
144
|
}
|
|
129
145
|
|
|
130
|
-
// ============================================
|
|
131
|
-
// DEFAULT FILTER
|
|
132
|
-
// ============================================
|
|
133
|
-
|
|
134
146
|
/**
|
|
135
147
|
* Default filter function that does case-insensitive "contains" matching.
|
|
136
148
|
*/
|
|
@@ -138,24 +150,21 @@ export const defaultContainsFilter: FilterFn = (textValue, inputValue) => {
|
|
|
138
150
|
return textValue.toLowerCase().includes(inputValue.toLowerCase());
|
|
139
151
|
};
|
|
140
152
|
|
|
141
|
-
// ============================================
|
|
142
|
-
// IMPLEMENTATION
|
|
143
|
-
// ============================================
|
|
144
|
-
|
|
145
153
|
/**
|
|
146
154
|
* Creates state for a combobox component.
|
|
147
155
|
* Combines list state with input value management and filtering.
|
|
148
156
|
*/
|
|
149
157
|
export function createComboBoxState<T = unknown>(
|
|
150
|
-
props: MaybeAccessor<ComboBoxStateProps<T
|
|
158
|
+
props: MaybeAccessor<ComboBoxStateProps<T>>,
|
|
151
159
|
): ComboBoxState<T> {
|
|
152
160
|
const getProps = () => access(props);
|
|
153
161
|
|
|
154
162
|
// Extract options with defaults
|
|
155
|
-
const menuTrigger = () => getProps().menuTrigger ??
|
|
163
|
+
const menuTrigger = () => getProps().menuTrigger ?? "input";
|
|
156
164
|
const allowsEmptyCollection = () => getProps().allowsEmptyCollection ?? false;
|
|
157
165
|
const allowsCustomValue = () => getProps().allowsCustomValue ?? false;
|
|
158
166
|
const shouldCloseOnBlur = () => getProps().shouldCloseOnBlur ?? true;
|
|
167
|
+
const isMultiple = () => getProps().selectionMode === "multiple";
|
|
159
168
|
|
|
160
169
|
// Track focus strategy for list navigation
|
|
161
170
|
const [focusStrategy, setFocusStrategy] = createSignal<FocusStrategy | null>(null);
|
|
@@ -164,13 +173,32 @@ export function createComboBoxState<T = unknown>(
|
|
|
164
173
|
const [showAllItems, setShowAllItems] = createSignal(false);
|
|
165
174
|
|
|
166
175
|
// Track the menu open trigger
|
|
167
|
-
let menuOpenTrigger: MenuTriggerAction =
|
|
176
|
+
let menuOpenTrigger: MenuTriggerAction = "focus";
|
|
168
177
|
|
|
169
|
-
// ----
|
|
178
|
+
// ---- Multi-select State ----
|
|
179
|
+
const isMultiSelectionControlled = () => getProps().selectedKeys !== undefined;
|
|
180
|
+
const [internalSelectedKeys, setInternalSelectedKeys] = createSignal<Set<Key>>(
|
|
181
|
+
new Set(getProps().defaultSelectedKeys ?? []),
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
const selectedKeys: Accessor<Set<Key>> = () => {
|
|
185
|
+
return isMultiSelectionControlled()
|
|
186
|
+
? new Set(getProps().selectedKeys ?? [])
|
|
187
|
+
: internalSelectedKeys();
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
const setSelectedKeys = (keys: Set<Key>) => {
|
|
191
|
+
if (!isMultiSelectionControlled()) {
|
|
192
|
+
setInternalSelectedKeys(new Set(keys));
|
|
193
|
+
}
|
|
194
|
+
getProps().onSelectionChangeMultiple?.(keys);
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
// ---- Selection State (single mode) ----
|
|
170
198
|
// Note: Selection state is initialized first because input value may depend on it
|
|
171
199
|
const isSelectionControlled = () => getProps().selectedKey !== undefined;
|
|
172
200
|
const [internalSelectedKey, setInternalSelectedKey] = createSignal<Key | null>(
|
|
173
|
-
getProps().defaultSelectedKey ?? null
|
|
201
|
+
getProps().defaultSelectedKey ?? null,
|
|
174
202
|
);
|
|
175
203
|
|
|
176
204
|
// ---- Input Value State ----
|
|
@@ -179,13 +207,13 @@ export function createComboBoxState<T = unknown>(
|
|
|
179
207
|
|
|
180
208
|
// We'll set the proper initial value after collection is created
|
|
181
209
|
const [internalInputValue, setInternalInputValue] = createSignal(
|
|
182
|
-
getProps().defaultInputValue ??
|
|
210
|
+
getProps().defaultInputValue ?? "",
|
|
183
211
|
);
|
|
184
212
|
// Track if we've initialized input from selection
|
|
185
213
|
let inputInitialized = false;
|
|
186
214
|
|
|
187
215
|
const inputValue: Accessor<string> = () => {
|
|
188
|
-
return isInputControlled() ? (getProps().inputValue ??
|
|
216
|
+
return isInputControlled() ? (getProps().inputValue ?? "") : internalInputValue();
|
|
189
217
|
};
|
|
190
218
|
|
|
191
219
|
const setInputValue = (value: string) => {
|
|
@@ -240,14 +268,27 @@ export function createComboBoxState<T = unknown>(
|
|
|
240
268
|
get disabledKeys() {
|
|
241
269
|
return getProps().disabledKeys;
|
|
242
270
|
},
|
|
243
|
-
selectionMode
|
|
271
|
+
get selectionMode() {
|
|
272
|
+
return isMultiple() ? ("multiple" as const) : ("single" as const);
|
|
273
|
+
},
|
|
244
274
|
disallowEmptySelection: false,
|
|
245
275
|
get selectedKeys() {
|
|
276
|
+
if (isMultiple()) {
|
|
277
|
+
return Array.from(selectedKeys());
|
|
278
|
+
}
|
|
246
279
|
const key = selectedKey();
|
|
247
280
|
return key != null ? [key] : [];
|
|
248
281
|
},
|
|
249
282
|
onSelectionChange(keys) {
|
|
250
|
-
if (keys ===
|
|
283
|
+
if (keys === "all") return;
|
|
284
|
+
|
|
285
|
+
if (isMultiple()) {
|
|
286
|
+
// In multiple mode, toggle selections
|
|
287
|
+
const newKeys = new Set(keys);
|
|
288
|
+
setSelectedKeys(newKeys);
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
|
|
251
292
|
const key = keys.size > 0 ? Array.from(keys)[0] : null;
|
|
252
293
|
|
|
253
294
|
// If same key selected, just reset input and close
|
|
@@ -284,13 +325,24 @@ export function createComboBoxState<T = unknown>(
|
|
|
284
325
|
return showAllItems() ? originalCollection() : filteredCollection();
|
|
285
326
|
});
|
|
286
327
|
|
|
287
|
-
// ---- Selected Item ----
|
|
328
|
+
// ---- Selected Item(s) ----
|
|
288
329
|
const selectedItem: Accessor<CollectionNode<T> | null> = () => {
|
|
289
330
|
const key = selectedKey();
|
|
290
331
|
if (key == null) return null;
|
|
291
332
|
return originalCollection().getItem(key);
|
|
292
333
|
};
|
|
293
334
|
|
|
335
|
+
const selectedItems = createMemo<CollectionNode<T>[]>(() => {
|
|
336
|
+
const keys = selectedKeys();
|
|
337
|
+
const collection = originalCollection();
|
|
338
|
+
const items: CollectionNode<T>[] = [];
|
|
339
|
+
for (const key of keys) {
|
|
340
|
+
const item = collection.getItem(key);
|
|
341
|
+
if (item) items.push(item);
|
|
342
|
+
}
|
|
343
|
+
return items;
|
|
344
|
+
});
|
|
345
|
+
|
|
294
346
|
// Initialize input value from selected item if not already set
|
|
295
347
|
// This runs once on creation
|
|
296
348
|
if (!inputInitialized && !isInputControlled()) {
|
|
@@ -309,7 +361,7 @@ export function createComboBoxState<T = unknown>(
|
|
|
309
361
|
// ---- Helper Functions ----
|
|
310
362
|
const resetInputValue = () => {
|
|
311
363
|
const item = selectedItem();
|
|
312
|
-
const textValue = item?.textValue ??
|
|
364
|
+
const textValue = item?.textValue ?? "";
|
|
313
365
|
setLastValue(textValue);
|
|
314
366
|
setInputValue(textValue);
|
|
315
367
|
};
|
|
@@ -322,14 +374,13 @@ export function createComboBoxState<T = unknown>(
|
|
|
322
374
|
|
|
323
375
|
// ---- Open/Toggle Logic ----
|
|
324
376
|
const open = (strategy: FocusStrategy | null = null, trigger?: MenuTriggerAction) => {
|
|
325
|
-
const displayAll = trigger ===
|
|
377
|
+
const displayAll = trigger === "manual" || (trigger === "focus" && menuTrigger() === "focus");
|
|
326
378
|
|
|
327
379
|
// Check if we should open
|
|
328
380
|
const filtered = filteredCollection();
|
|
329
381
|
const original = originalCollection();
|
|
330
|
-
const canOpen =
|
|
331
|
-
|
|
332
|
-
(displayAll && original.size > 0);
|
|
382
|
+
const canOpen =
|
|
383
|
+
allowsEmptyCollection() || filtered.size > 0 || (displayAll && original.size > 0);
|
|
333
384
|
|
|
334
385
|
if (!canOpen) return;
|
|
335
386
|
|
|
@@ -337,20 +388,32 @@ export function createComboBoxState<T = unknown>(
|
|
|
337
388
|
setShowAllItems(true);
|
|
338
389
|
}
|
|
339
390
|
|
|
340
|
-
menuOpenTrigger = trigger ??
|
|
391
|
+
menuOpenTrigger = trigger ?? "focus";
|
|
341
392
|
setFocusStrategy(strategy);
|
|
393
|
+
if (!overlayState.isOpen()) {
|
|
394
|
+
const key = selectedKey();
|
|
395
|
+
const nextFocusedKey =
|
|
396
|
+
key != null && !listState.isDisabled(key)
|
|
397
|
+
? key
|
|
398
|
+
: strategy === "first"
|
|
399
|
+
? displayedCollection().getFirstKey()
|
|
400
|
+
: strategy === "last"
|
|
401
|
+
? displayedCollection().getLastKey()
|
|
402
|
+
: null;
|
|
403
|
+
listState.setFocused(true);
|
|
404
|
+
listState.setFocusedKey(nextFocusedKey);
|
|
405
|
+
}
|
|
342
406
|
overlayState.open();
|
|
343
407
|
};
|
|
344
408
|
|
|
345
409
|
const toggle = (strategy: FocusStrategy | null = null, trigger?: MenuTriggerAction) => {
|
|
346
|
-
const displayAll = trigger ===
|
|
410
|
+
const displayAll = trigger === "manual" || (trigger === "focus" && menuTrigger() === "focus");
|
|
347
411
|
|
|
348
412
|
// Check if we can open (if closed)
|
|
349
413
|
const filtered = filteredCollection();
|
|
350
414
|
const original = originalCollection();
|
|
351
|
-
const canOpen =
|
|
352
|
-
|
|
353
|
-
(displayAll && original.size > 0);
|
|
415
|
+
const canOpen =
|
|
416
|
+
allowsEmptyCollection() || filtered.size > 0 || (displayAll && original.size > 0);
|
|
354
417
|
|
|
355
418
|
if (!canOpen && !overlayState.isOpen()) return;
|
|
356
419
|
|
|
@@ -359,7 +422,7 @@ export function createComboBoxState<T = unknown>(
|
|
|
359
422
|
}
|
|
360
423
|
|
|
361
424
|
if (!overlayState.isOpen()) {
|
|
362
|
-
menuOpenTrigger = trigger ??
|
|
425
|
+
menuOpenTrigger = trigger ?? "focus";
|
|
363
426
|
}
|
|
364
427
|
|
|
365
428
|
setFocusStrategy(strategy);
|
|
@@ -377,7 +440,7 @@ export function createComboBoxState<T = unknown>(
|
|
|
377
440
|
if (isSelectionControlled() && isInputControlled()) {
|
|
378
441
|
getProps().onSelectionChange?.(selectedKey());
|
|
379
442
|
const item = selectedItem();
|
|
380
|
-
setLastValue(item?.textValue ??
|
|
443
|
+
setLastValue(item?.textValue ?? "");
|
|
381
444
|
closeMenu();
|
|
382
445
|
} else {
|
|
383
446
|
// Reset input to selected item's text
|
|
@@ -389,7 +452,7 @@ export function createComboBoxState<T = unknown>(
|
|
|
389
452
|
const commitValue = () => {
|
|
390
453
|
if (allowsCustomValue()) {
|
|
391
454
|
const item = selectedItem();
|
|
392
|
-
const itemText = item?.textValue ??
|
|
455
|
+
const itemText = item?.textValue ?? "";
|
|
393
456
|
if (inputValue() === itemText) {
|
|
394
457
|
commitSelection();
|
|
395
458
|
} else {
|
|
@@ -403,6 +466,14 @@ export function createComboBoxState<T = unknown>(
|
|
|
403
466
|
const commit = () => {
|
|
404
467
|
const focusedKey = listState.focusedKey();
|
|
405
468
|
|
|
469
|
+
if (isMultiple()) {
|
|
470
|
+
// In multiple mode, toggle the focused item without closing
|
|
471
|
+
if (overlayState.isOpen() && focusedKey != null) {
|
|
472
|
+
select(focusedKey);
|
|
473
|
+
}
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
|
|
406
477
|
if (overlayState.isOpen() && focusedKey != null) {
|
|
407
478
|
// If focused key is already selected, just commit
|
|
408
479
|
if (selectedKey() === focusedKey) {
|
|
@@ -410,6 +481,7 @@ export function createComboBoxState<T = unknown>(
|
|
|
410
481
|
} else {
|
|
411
482
|
// Select the focused item
|
|
412
483
|
setSelectedKey(focusedKey);
|
|
484
|
+
closeMenu();
|
|
413
485
|
}
|
|
414
486
|
} else {
|
|
415
487
|
commitValue();
|
|
@@ -426,13 +498,13 @@ export function createComboBoxState<T = unknown>(
|
|
|
426
498
|
|
|
427
499
|
// ---- Focus Handling ----
|
|
428
500
|
const [isFocused, setIsFocused] = createSignal(false);
|
|
429
|
-
let valueOnFocus =
|
|
501
|
+
let valueOnFocus = "";
|
|
430
502
|
|
|
431
503
|
const setFocused = (focused: boolean) => {
|
|
432
504
|
if (focused) {
|
|
433
505
|
valueOnFocus = inputValue();
|
|
434
|
-
if (menuTrigger() ===
|
|
435
|
-
open(null,
|
|
506
|
+
if (menuTrigger() === "focus" && !getProps().isReadOnly) {
|
|
507
|
+
open(null, "focus");
|
|
436
508
|
}
|
|
437
509
|
} else {
|
|
438
510
|
if (shouldCloseOnBlur()) {
|
|
@@ -456,18 +528,13 @@ export function createComboBoxState<T = unknown>(
|
|
|
456
528
|
(filtered.size > 0 || allowsEmptyCollection()) &&
|
|
457
529
|
!isOpen &&
|
|
458
530
|
input !== last &&
|
|
459
|
-
menuTrigger() !==
|
|
531
|
+
menuTrigger() !== "manual"
|
|
460
532
|
) {
|
|
461
|
-
open(null,
|
|
533
|
+
open(null, "input");
|
|
462
534
|
}
|
|
463
535
|
|
|
464
536
|
// Auto-close when empty (unless showing all)
|
|
465
|
-
if (
|
|
466
|
-
!showAllItems() &&
|
|
467
|
-
!allowsEmptyCollection() &&
|
|
468
|
-
isOpen &&
|
|
469
|
-
filtered.size === 0
|
|
470
|
-
) {
|
|
537
|
+
if (!showAllItems() && !allowsEmptyCollection() && isOpen && filtered.size === 0) {
|
|
471
538
|
closeMenu();
|
|
472
539
|
}
|
|
473
540
|
|
|
@@ -477,7 +544,7 @@ export function createComboBoxState<T = unknown>(
|
|
|
477
544
|
setShowAllItems(false);
|
|
478
545
|
|
|
479
546
|
// Clear selection when input is cleared (if not fully controlled)
|
|
480
|
-
if (input ===
|
|
547
|
+
if (input === "" && (!isInputControlled() || !isSelectionControlled())) {
|
|
481
548
|
setSelectedKey(null);
|
|
482
549
|
}
|
|
483
550
|
|
|
@@ -485,11 +552,12 @@ export function createComboBoxState<T = unknown>(
|
|
|
485
552
|
}
|
|
486
553
|
});
|
|
487
554
|
|
|
488
|
-
// Update input when selection changes
|
|
555
|
+
// Update input when selection changes (only in single mode)
|
|
489
556
|
createEffect(() => {
|
|
557
|
+
if (isMultiple()) return;
|
|
490
558
|
const key = selectedKey();
|
|
491
559
|
const item = key != null ? originalCollection().getItem(key) : null;
|
|
492
|
-
const textValue = item?.textValue ??
|
|
560
|
+
const textValue = item?.textValue ?? "";
|
|
493
561
|
|
|
494
562
|
// Only update if selection changed and not fully controlled
|
|
495
563
|
if (!isInputControlled() || !isSelectionControlled()) {
|
|
@@ -500,8 +568,9 @@ export function createComboBoxState<T = unknown>(
|
|
|
500
568
|
}
|
|
501
569
|
});
|
|
502
570
|
|
|
503
|
-
// Close when selection changes
|
|
571
|
+
// Close when selection changes (only in single mode)
|
|
504
572
|
createEffect((prevKey: Key | null | undefined) => {
|
|
573
|
+
if (isMultiple()) return undefined;
|
|
505
574
|
const key = selectedKey();
|
|
506
575
|
if (key != null && key !== prevKey) {
|
|
507
576
|
closeMenu();
|
|
@@ -512,12 +581,38 @@ export function createComboBoxState<T = unknown>(
|
|
|
512
581
|
// ---- Selection Methods for ListState compatibility ----
|
|
513
582
|
// These methods allow createOption to work with ComboBoxState
|
|
514
583
|
const select = (key: Key) => {
|
|
515
|
-
|
|
516
|
-
|
|
584
|
+
if (isMultiple()) {
|
|
585
|
+
// Toggle selection in multiple mode
|
|
586
|
+
const current = new Set(selectedKeys());
|
|
587
|
+
if (current.has(key)) {
|
|
588
|
+
current.delete(key);
|
|
589
|
+
} else {
|
|
590
|
+
current.add(key);
|
|
591
|
+
}
|
|
592
|
+
setSelectedKeys(current);
|
|
593
|
+
// Don't close menu and don't reset input in multi-select mode
|
|
594
|
+
} else {
|
|
595
|
+
setSelectedKey(key);
|
|
596
|
+
closeMenu();
|
|
597
|
+
}
|
|
598
|
+
};
|
|
599
|
+
|
|
600
|
+
const removeSelectedKey = (key: Key) => {
|
|
601
|
+
if (isMultiple()) {
|
|
602
|
+
const current = new Set(selectedKeys());
|
|
603
|
+
current.delete(key);
|
|
604
|
+
setSelectedKeys(current);
|
|
605
|
+
}
|
|
517
606
|
};
|
|
518
607
|
|
|
519
|
-
const selectionMode: Accessor<
|
|
520
|
-
|
|
608
|
+
const selectionMode: Accessor<"single" | "multiple"> = () => getProps().selectionMode ?? "single";
|
|
609
|
+
|
|
610
|
+
const isSelected = (key: Key) => {
|
|
611
|
+
if (isMultiple()) {
|
|
612
|
+
return selectedKeys().has(key);
|
|
613
|
+
}
|
|
614
|
+
return selectedKey() === key;
|
|
615
|
+
};
|
|
521
616
|
|
|
522
617
|
// ---- Return State ----
|
|
523
618
|
return {
|
|
@@ -530,9 +625,13 @@ export function createComboBoxState<T = unknown>(
|
|
|
530
625
|
defaultSelectedKey: getProps().defaultSelectedKey ?? null,
|
|
531
626
|
selectedItem,
|
|
532
627
|
setSelectedKey,
|
|
628
|
+
selectedKeys,
|
|
629
|
+
selectedItems,
|
|
630
|
+
removeSelectedKey,
|
|
533
631
|
inputValue,
|
|
534
|
-
defaultInputValue: getProps().defaultInputValue ??
|
|
632
|
+
defaultInputValue: getProps().defaultInputValue ?? "",
|
|
535
633
|
setInputValue,
|
|
634
|
+
selectionManager: listState.selectionManager,
|
|
536
635
|
focusedKey: listState.focusedKey,
|
|
537
636
|
setFocusedKey: listState.setFocusedKey,
|
|
538
637
|
isFocused,
|
|
@@ -557,17 +656,13 @@ export function createComboBoxState<T = unknown>(
|
|
|
557
656
|
};
|
|
558
657
|
}
|
|
559
658
|
|
|
560
|
-
// ============================================
|
|
561
|
-
// COLLECTION FILTERING
|
|
562
|
-
// ============================================
|
|
563
|
-
|
|
564
659
|
/**
|
|
565
660
|
* Filter a collection based on input value.
|
|
566
661
|
*/
|
|
567
662
|
function filterCollection<T>(
|
|
568
663
|
collection: Collection<T>,
|
|
569
664
|
inputValue: string,
|
|
570
|
-
filter: FilterFn
|
|
665
|
+
filter: FilterFn,
|
|
571
666
|
): Collection<T> {
|
|
572
667
|
if (!inputValue) {
|
|
573
668
|
return collection;
|
|
@@ -576,12 +671,12 @@ function filterCollection<T>(
|
|
|
576
671
|
const filteredItems: CollectionNode<T>[] = [];
|
|
577
672
|
|
|
578
673
|
for (const item of collection) {
|
|
579
|
-
if (item.type ===
|
|
674
|
+
if (item.type === "section") {
|
|
580
675
|
// Filter section children
|
|
581
676
|
const filteredChildren: CollectionNode<T>[] = [];
|
|
582
677
|
if (item.childNodes) {
|
|
583
678
|
for (const child of item.childNodes) {
|
|
584
|
-
if (child.type ===
|
|
679
|
+
if (child.type === "item" && filter(child.textValue, inputValue)) {
|
|
585
680
|
filteredChildren.push(child);
|
|
586
681
|
}
|
|
587
682
|
}
|
|
@@ -593,7 +688,7 @@ function filterCollection<T>(
|
|
|
593
688
|
childNodes: filteredChildren,
|
|
594
689
|
});
|
|
595
690
|
}
|
|
596
|
-
} else if (item.type ===
|
|
691
|
+
} else if (item.type === "item") {
|
|
597
692
|
if (filter(item.textValue, inputValue)) {
|
|
598
693
|
filteredItems.push(item);
|
|
599
694
|
}
|
|
@@ -609,7 +704,7 @@ function filterCollection<T>(
|
|
|
609
704
|
*/
|
|
610
705
|
function createFilteredCollection<T>(
|
|
611
706
|
items: CollectionNode<T>[],
|
|
612
|
-
original: Collection<T
|
|
707
|
+
original: Collection<T>,
|
|
613
708
|
): Collection<T> {
|
|
614
709
|
const itemMap = new Map<Key, CollectionNode<T>>();
|
|
615
710
|
|
|
@@ -626,10 +721,10 @@ function createFilteredCollection<T>(
|
|
|
626
721
|
get size() {
|
|
627
722
|
let count = 0;
|
|
628
723
|
for (const item of items) {
|
|
629
|
-
if (item.type ===
|
|
724
|
+
if (item.type === "item") {
|
|
630
725
|
count++;
|
|
631
726
|
} else if (item.childNodes) {
|
|
632
|
-
count += Array.from(item.childNodes).filter(c => c.type ===
|
|
727
|
+
count += Array.from(item.childNodes).filter((c) => c.type === "item").length;
|
|
633
728
|
}
|
|
634
729
|
}
|
|
635
730
|
return count;
|
|
@@ -642,10 +737,10 @@ function createFilteredCollection<T>(
|
|
|
642
737
|
},
|
|
643
738
|
getFirstKey() {
|
|
644
739
|
for (const item of items) {
|
|
645
|
-
if (item.type ===
|
|
740
|
+
if (item.type === "item") return item.key;
|
|
646
741
|
if (item.childNodes) {
|
|
647
742
|
for (const child of item.childNodes) {
|
|
648
|
-
if (child.type ===
|
|
743
|
+
if (child.type === "item") return child.key;
|
|
649
744
|
}
|
|
650
745
|
}
|
|
651
746
|
}
|
|
@@ -654,11 +749,11 @@ function createFilteredCollection<T>(
|
|
|
654
749
|
getLastKey() {
|
|
655
750
|
for (let i = items.length - 1; i >= 0; i--) {
|
|
656
751
|
const item = items[i];
|
|
657
|
-
if (item.type ===
|
|
752
|
+
if (item.type === "item") return item.key;
|
|
658
753
|
if (item.childNodes) {
|
|
659
754
|
const children = Array.from(item.childNodes);
|
|
660
755
|
for (let j = children.length - 1; j >= 0; j--) {
|
|
661
|
-
if (children[j].type ===
|
|
756
|
+
if (children[j].type === "item") return children[j].key;
|
|
662
757
|
}
|
|
663
758
|
}
|
|
664
759
|
}
|
|
@@ -674,12 +769,12 @@ function createFilteredCollection<T>(
|
|
|
674
769
|
// Flatten items for indexing
|
|
675
770
|
let currentIndex = 0;
|
|
676
771
|
for (const item of items) {
|
|
677
|
-
if (item.type ===
|
|
772
|
+
if (item.type === "item") {
|
|
678
773
|
if (currentIndex === index) return item;
|
|
679
774
|
currentIndex++;
|
|
680
775
|
} else if (item.childNodes) {
|
|
681
776
|
for (const child of item.childNodes) {
|
|
682
|
-
if (child.type ===
|
|
777
|
+
if (child.type === "item") {
|
|
683
778
|
if (currentIndex === index) return child;
|
|
684
779
|
currentIndex++;
|
|
685
780
|
}
|
|
@@ -694,7 +789,7 @@ function createFilteredCollection<T>(
|
|
|
694
789
|
},
|
|
695
790
|
getTextValue(key: Key) {
|
|
696
791
|
const item = itemMap.get(key);
|
|
697
|
-
return item?.textValue ??
|
|
792
|
+
return item?.textValue ?? "";
|
|
698
793
|
},
|
|
699
794
|
[Symbol.iterator]() {
|
|
700
795
|
return items[Symbol.iterator]();
|
package/src/combobox/index.d.ts
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* ComboBox state management for Solid Stately.
|
|
3
3
|
*/
|
|
4
|
-
export {
|
|
5
|
-
|
|
4
|
+
export {
|
|
5
|
+
createComboBoxState,
|
|
6
|
+
defaultContainsFilter,
|
|
7
|
+
type ComboBoxState,
|
|
8
|
+
type ComboBoxStateProps,
|
|
9
|
+
type FilterFn,
|
|
10
|
+
type MenuTriggerAction,
|
|
11
|
+
type FocusStrategy,
|
|
12
|
+
} from "./createComboBoxState";
|
|
13
|
+
//# sourceMappingURL=index.d.ts.map
|
package/src/combobox/index.ts
CHANGED