@proyecto-viviana/solid-stately 0.2.2 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (218) hide show
  1. package/dist/index.js +43 -43
  2. package/dist/index.js.map +1 -1
  3. package/package.json +7 -5
  4. package/src/autocomplete/createAutocompleteState.d.ts +46 -0
  5. package/src/autocomplete/createAutocompleteState.d.ts.map +1 -0
  6. package/src/autocomplete/createAutocompleteState.ts +90 -0
  7. package/src/autocomplete/index.d.ts +2 -0
  8. package/src/autocomplete/index.d.ts.map +1 -0
  9. package/src/autocomplete/index.ts +5 -0
  10. package/src/calendar/createCalendarState.d.ts +130 -0
  11. package/src/calendar/createCalendarState.d.ts.map +1 -0
  12. package/src/calendar/createCalendarState.ts +461 -0
  13. package/src/calendar/createDateFieldState.d.ts +110 -0
  14. package/src/calendar/createDateFieldState.d.ts.map +1 -0
  15. package/src/calendar/createDateFieldState.ts +562 -0
  16. package/src/calendar/createRangeCalendarState.d.ts +146 -0
  17. package/src/calendar/createRangeCalendarState.d.ts.map +1 -0
  18. package/src/calendar/createRangeCalendarState.ts +535 -0
  19. package/src/calendar/createTimeFieldState.d.ts +95 -0
  20. package/src/calendar/createTimeFieldState.d.ts.map +1 -0
  21. package/src/calendar/createTimeFieldState.ts +483 -0
  22. package/src/calendar/index.d.ts +7 -0
  23. package/src/calendar/index.d.ts.map +1 -0
  24. package/src/calendar/index.ts +81 -0
  25. package/src/checkbox/createCheckboxGroupState.d.ts +71 -0
  26. package/src/checkbox/createCheckboxGroupState.d.ts.map +1 -0
  27. package/src/checkbox/createCheckboxGroupState.ts +193 -0
  28. package/src/checkbox/index.d.ts +2 -0
  29. package/src/checkbox/index.d.ts.map +1 -0
  30. package/src/checkbox/index.ts +5 -0
  31. package/src/collections/ListCollection.d.ts +37 -0
  32. package/src/collections/ListCollection.d.ts.map +1 -0
  33. package/src/collections/ListCollection.ts +146 -0
  34. package/src/collections/createListState.d.ts +79 -0
  35. package/src/collections/createListState.d.ts.map +1 -0
  36. package/src/collections/createListState.ts +264 -0
  37. package/src/collections/createMenuState.d.ts +50 -0
  38. package/src/collections/createMenuState.d.ts.map +1 -0
  39. package/src/collections/createMenuState.ts +106 -0
  40. package/src/collections/createSelectionState.d.ts +76 -0
  41. package/src/collections/createSelectionState.d.ts.map +1 -0
  42. package/src/collections/createSelectionState.ts +336 -0
  43. package/src/collections/index.d.ts +6 -0
  44. package/src/collections/index.d.ts.map +1 -0
  45. package/src/collections/index.ts +46 -0
  46. package/src/collections/types.d.ts +147 -0
  47. package/src/collections/types.d.ts.map +1 -0
  48. package/src/collections/types.ts +169 -0
  49. package/src/color/Color.d.ts +28 -0
  50. package/src/color/Color.d.ts.map +1 -0
  51. package/src/color/Color.ts +951 -0
  52. package/src/color/createColorAreaState.d.ts +76 -0
  53. package/src/color/createColorAreaState.d.ts.map +1 -0
  54. package/src/color/createColorAreaState.ts +293 -0
  55. package/src/color/createColorFieldState.d.ts +55 -0
  56. package/src/color/createColorFieldState.d.ts.map +1 -0
  57. package/src/color/createColorFieldState.ts +292 -0
  58. package/src/color/createColorSliderState.d.ts +67 -0
  59. package/src/color/createColorSliderState.d.ts.map +1 -0
  60. package/src/color/createColorSliderState.ts +241 -0
  61. package/src/color/createColorWheelState.d.ts +51 -0
  62. package/src/color/createColorWheelState.d.ts.map +1 -0
  63. package/src/color/createColorWheelState.ts +211 -0
  64. package/src/color/index.d.ts +10 -0
  65. package/src/color/index.d.ts.map +1 -0
  66. package/src/color/index.ts +47 -0
  67. package/src/color/types.d.ts +106 -0
  68. package/src/color/types.d.ts.map +1 -0
  69. package/src/color/types.ts +127 -0
  70. package/src/combobox/createComboBoxState.d.ts +125 -0
  71. package/src/combobox/createComboBoxState.d.ts.map +1 -0
  72. package/src/combobox/createComboBoxState.ts +703 -0
  73. package/src/combobox/index.d.ts +5 -0
  74. package/src/combobox/index.d.ts.map +1 -0
  75. package/src/combobox/index.ts +13 -0
  76. package/src/disclosure/createDisclosureState.d.ts +64 -0
  77. package/src/disclosure/createDisclosureState.d.ts.map +1 -0
  78. package/src/disclosure/createDisclosureState.ts +193 -0
  79. package/src/disclosure/index.d.ts +2 -0
  80. package/src/disclosure/index.d.ts.map +1 -0
  81. package/src/disclosure/index.ts +9 -0
  82. package/src/dnd/createDragState.d.ts +59 -0
  83. package/src/dnd/createDragState.d.ts.map +1 -0
  84. package/src/dnd/createDragState.ts +153 -0
  85. package/src/dnd/createDraggableCollectionState.d.ts +57 -0
  86. package/src/dnd/createDraggableCollectionState.d.ts.map +1 -0
  87. package/src/dnd/createDraggableCollectionState.ts +165 -0
  88. package/src/dnd/createDropState.d.ts +61 -0
  89. package/src/dnd/createDropState.d.ts.map +1 -0
  90. package/src/dnd/createDropState.ts +212 -0
  91. package/src/dnd/createDroppableCollectionState.d.ts +78 -0
  92. package/src/dnd/createDroppableCollectionState.d.ts.map +1 -0
  93. package/src/dnd/createDroppableCollectionState.ts +357 -0
  94. package/src/dnd/index.d.ts +11 -0
  95. package/src/dnd/index.d.ts.map +1 -0
  96. package/src/dnd/index.ts +76 -0
  97. package/src/dnd/types.d.ts +264 -0
  98. package/src/dnd/types.d.ts.map +1 -0
  99. package/src/dnd/types.ts +317 -0
  100. package/src/form/createFormValidationState.d.ts +100 -0
  101. package/src/form/createFormValidationState.d.ts.map +1 -0
  102. package/src/form/createFormValidationState.ts +389 -0
  103. package/src/form/index.d.ts +2 -0
  104. package/src/form/index.d.ts.map +1 -0
  105. package/src/form/index.ts +15 -0
  106. package/src/grid/createGridState.d.ts +12 -0
  107. package/src/grid/createGridState.d.ts.map +1 -0
  108. package/src/grid/createGridState.ts +327 -0
  109. package/src/grid/index.d.ts +7 -0
  110. package/src/grid/index.d.ts.map +1 -0
  111. package/src/grid/index.ts +13 -0
  112. package/src/grid/types.d.ts +156 -0
  113. package/src/grid/types.d.ts.map +1 -0
  114. package/src/grid/types.ts +179 -0
  115. package/src/index.d.ts +26 -0
  116. package/src/index.d.ts.map +1 -0
  117. package/src/index.ts +383 -0
  118. package/src/numberfield/createNumberFieldState.d.ts +65 -0
  119. package/src/numberfield/createNumberFieldState.d.ts.map +1 -0
  120. package/src/numberfield/createNumberFieldState.ts +383 -0
  121. package/src/numberfield/index.d.ts +2 -0
  122. package/src/numberfield/index.d.ts.map +1 -0
  123. package/src/numberfield/index.ts +5 -0
  124. package/src/overlays/createOverlayTriggerState.d.ts +32 -0
  125. package/src/overlays/createOverlayTriggerState.d.ts.map +1 -0
  126. package/src/overlays/createOverlayTriggerState.ts +67 -0
  127. package/src/overlays/index.d.ts +2 -0
  128. package/src/overlays/index.d.ts.map +1 -0
  129. package/src/overlays/index.ts +5 -0
  130. package/src/radio/createRadioGroupState.d.ts +77 -0
  131. package/src/radio/createRadioGroupState.d.ts.map +1 -0
  132. package/src/radio/createRadioGroupState.ts +201 -0
  133. package/src/radio/index.d.ts +2 -0
  134. package/src/radio/index.d.ts.map +1 -0
  135. package/src/radio/index.ts +6 -0
  136. package/src/searchfield/createSearchFieldState.d.ts +25 -0
  137. package/src/searchfield/createSearchFieldState.d.ts.map +1 -0
  138. package/src/searchfield/createSearchFieldState.ts +62 -0
  139. package/src/searchfield/index.d.ts +3 -0
  140. package/src/searchfield/index.d.ts.map +1 -0
  141. package/src/searchfield/index.ts +5 -0
  142. package/src/select/createSelectState.d.ts +73 -0
  143. package/src/select/createSelectState.d.ts.map +1 -0
  144. package/src/select/createSelectState.ts +181 -0
  145. package/src/select/index.d.ts +2 -0
  146. package/src/select/index.d.ts.map +1 -0
  147. package/src/select/index.ts +5 -0
  148. package/src/slider/createSliderState.d.ts +72 -0
  149. package/src/slider/createSliderState.d.ts.map +1 -0
  150. package/src/slider/createSliderState.ts +211 -0
  151. package/src/slider/index.d.ts +3 -0
  152. package/src/slider/index.d.ts.map +1 -0
  153. package/src/slider/index.ts +6 -0
  154. package/src/ssr/index.d.ts +28 -0
  155. package/src/ssr/index.d.ts.map +1 -0
  156. package/src/ssr/index.ts +41 -0
  157. package/src/table/TableCollection.d.ts +52 -0
  158. package/src/table/TableCollection.d.ts.map +1 -0
  159. package/src/table/TableCollection.ts +388 -0
  160. package/src/table/createTableState.d.ts +12 -0
  161. package/src/table/createTableState.d.ts.map +1 -0
  162. package/src/table/createTableState.ts +127 -0
  163. package/src/table/index.d.ts +8 -0
  164. package/src/table/index.d.ts.map +1 -0
  165. package/src/table/index.ts +18 -0
  166. package/src/table/types.d.ts +139 -0
  167. package/src/table/types.d.ts.map +1 -0
  168. package/src/table/types.ts +150 -0
  169. package/src/tabs/createTabListState.d.ts +68 -0
  170. package/src/tabs/createTabListState.d.ts.map +1 -0
  171. package/src/tabs/createTabListState.ts +240 -0
  172. package/src/tabs/index.d.ts +2 -0
  173. package/src/tabs/index.d.ts.map +1 -0
  174. package/src/tabs/index.ts +7 -0
  175. package/src/textfield/createTextFieldState.d.ts +30 -0
  176. package/src/textfield/createTextFieldState.d.ts.map +1 -0
  177. package/src/textfield/createTextFieldState.ts +75 -0
  178. package/src/textfield/index.d.ts +2 -0
  179. package/src/textfield/index.d.ts.map +1 -0
  180. package/src/textfield/index.ts +5 -0
  181. package/src/toast/createToastState.d.ts +118 -0
  182. package/src/toast/createToastState.d.ts.map +1 -0
  183. package/src/toast/createToastState.ts +316 -0
  184. package/src/toast/index.d.ts +2 -0
  185. package/src/toast/index.d.ts.map +1 -0
  186. package/src/toast/index.ts +11 -0
  187. package/src/toggle/createToggleState.d.ts +34 -0
  188. package/src/toggle/createToggleState.d.ts.map +1 -0
  189. package/src/toggle/createToggleState.ts +94 -0
  190. package/src/toggle/index.d.ts +2 -0
  191. package/src/toggle/index.d.ts.map +1 -0
  192. package/src/toggle/index.ts +5 -0
  193. package/src/tooltip/createTooltipTriggerState.d.ts +39 -0
  194. package/src/tooltip/createTooltipTriggerState.d.ts.map +1 -0
  195. package/src/tooltip/createTooltipTriggerState.ts +183 -0
  196. package/src/tooltip/index.d.ts +2 -0
  197. package/src/tooltip/index.d.ts.map +1 -0
  198. package/src/tooltip/index.ts +6 -0
  199. package/src/tree/TreeCollection.d.ts +40 -0
  200. package/src/tree/TreeCollection.d.ts.map +1 -0
  201. package/src/tree/TreeCollection.ts +175 -0
  202. package/src/tree/createTreeState.d.ts +14 -0
  203. package/src/tree/createTreeState.d.ts.map +1 -0
  204. package/src/tree/createTreeState.ts +392 -0
  205. package/src/tree/index.d.ts +7 -0
  206. package/src/tree/index.d.ts.map +1 -0
  207. package/src/tree/index.ts +13 -0
  208. package/src/tree/types.d.ts +157 -0
  209. package/src/tree/types.d.ts.map +1 -0
  210. package/src/tree/types.ts +174 -0
  211. package/src/utils/index.d.ts +2 -0
  212. package/src/utils/index.d.ts.map +1 -0
  213. package/src/utils/index.ts +1 -0
  214. package/src/utils/reactivity.d.ts +28 -0
  215. package/src/utils/reactivity.d.ts.map +1 -0
  216. package/src/utils/reactivity.ts +36 -0
  217. package/dist/index.jsx +0 -6408
  218. package/dist/index.jsx.map +0 -7
@@ -0,0 +1,703 @@
1
+ /**
2
+ * State management for ComboBox components.
3
+ * Based on @react-stately/combobox useComboBoxState.
4
+ *
5
+ * ComboBox combines a text input with a dropdown list, allowing users to
6
+ * either type to filter options or select from a list.
7
+ */
8
+
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
+
15
+ // ============================================
16
+ // TYPES
17
+ // ============================================
18
+
19
+ export type MenuTriggerAction = 'focus' | 'input' | 'manual';
20
+
21
+ // Re-export FocusStrategy for convenience
22
+ export type { FocusStrategy } from '../collections/types';
23
+
24
+ export type FilterFn = (textValue: string, inputValue: string) => boolean;
25
+
26
+ export interface ComboBoxStateProps<T = unknown> {
27
+ /** The items to display in the combobox dropdown. */
28
+ items: T[];
29
+ /** Default items when uncontrolled. */
30
+ defaultItems?: T[];
31
+ /** Function to get the key for an item. */
32
+ getKey?: (item: T) => Key;
33
+ /** Function to get the text value for an item. */
34
+ getTextValue?: (item: T) => string;
35
+ /** Function to check if an item is disabled. */
36
+ getDisabled?: (item: T) => boolean;
37
+ /** Keys of disabled items. */
38
+ disabledKeys?: Iterable<Key>;
39
+ /** The currently selected key (controlled). */
40
+ selectedKey?: Key | null;
41
+ /** The default selected key (uncontrolled). */
42
+ defaultSelectedKey?: Key | null;
43
+ /** Handler called when the selection changes. */
44
+ onSelectionChange?: (key: Key | null) => void;
45
+ /** The current input value (controlled). */
46
+ inputValue?: string;
47
+ /** The default input value (uncontrolled). */
48
+ defaultInputValue?: string;
49
+ /** Handler called when the input value changes. */
50
+ onInputChange?: (value: string) => void;
51
+ /** Whether the combobox is open (controlled). */
52
+ isOpen?: boolean;
53
+ /** Whether the combobox is open by default (uncontrolled). */
54
+ defaultOpen?: boolean;
55
+ /** Handler called when the open state changes. */
56
+ onOpenChange?: (isOpen: boolean, trigger?: MenuTriggerAction) => void;
57
+ /** Whether the combobox is disabled. */
58
+ isDisabled?: boolean;
59
+ /** Whether the combobox is read-only. */
60
+ isReadOnly?: boolean;
61
+ /** Whether the combobox is required. */
62
+ isRequired?: boolean;
63
+ /** The filter function to use when filtering items. */
64
+ defaultFilter?: FilterFn;
65
+ /** Whether to allow the menu to open when there are no items. */
66
+ allowsEmptyCollection?: boolean;
67
+ /** Whether to allow custom values that don't match any option. */
68
+ allowsCustomValue?: boolean;
69
+ /** What triggers the menu to open. */
70
+ menuTrigger?: MenuTriggerAction;
71
+ /** Whether to close the menu on blur. */
72
+ shouldCloseOnBlur?: boolean;
73
+ }
74
+
75
+ export interface ComboBoxState<T = unknown> {
76
+ /** The collection of items (may be filtered). */
77
+ readonly collection: Accessor<Collection<T>>;
78
+ /** Whether the combobox dropdown is open. */
79
+ readonly isOpen: Accessor<boolean>;
80
+ /** Open the combobox dropdown. */
81
+ open(focusStrategy?: FocusStrategy | null, trigger?: MenuTriggerAction): void;
82
+ /** Close the combobox dropdown. */
83
+ close(): void;
84
+ /** Toggle the combobox dropdown. */
85
+ toggle(focusStrategy?: FocusStrategy | null, trigger?: MenuTriggerAction): void;
86
+ /** The currently selected key. */
87
+ readonly selectedKey: Accessor<Key | null>;
88
+ /** The default selected key. */
89
+ readonly defaultSelectedKey: Key | null;
90
+ /** The currently selected item. */
91
+ readonly selectedItem: Accessor<CollectionNode<T> | null>;
92
+ /** Set the selected key. */
93
+ setSelectedKey(key: Key | null): void;
94
+ /** The current input value. */
95
+ readonly inputValue: Accessor<string>;
96
+ /** The default input value. */
97
+ readonly defaultInputValue: string;
98
+ /** Set the input value. */
99
+ setInputValue(value: string): void;
100
+ /** The currently focused key in the list. */
101
+ readonly focusedKey: Accessor<Key | null>;
102
+ /** Set the focused key. */
103
+ setFocusedKey(key: Key | null): void;
104
+ /** Whether the combobox input has focus. */
105
+ readonly isFocused: Accessor<boolean>;
106
+ /** Set whether the combobox has focus. */
107
+ setFocused(isFocused: boolean): void;
108
+ /** The focus strategy to use when opening. */
109
+ readonly focusStrategy: Accessor<FocusStrategy | null>;
110
+ /** Commit the current selection (select focused item or custom value). */
111
+ commit(): void;
112
+ /** Revert input to the selected item's text and close menu. */
113
+ revert(): void;
114
+ /** Whether a specific key is disabled. */
115
+ isKeyDisabled(key: Key): boolean;
116
+ /** Select a key and close the menu (for ListState compatibility). */
117
+ select(key: Key): void;
118
+ /** The selection mode (always 'single' for combobox). */
119
+ readonly selectionMode: Accessor<'single'>;
120
+ /** Check if a key is selected. */
121
+ isSelected(key: Key): boolean;
122
+ /** Whether the combobox is disabled. */
123
+ readonly isDisabled: boolean;
124
+ /** Whether the combobox is read-only. */
125
+ readonly isReadOnly: boolean;
126
+ /** Whether the combobox is required. */
127
+ readonly isRequired: boolean;
128
+ }
129
+
130
+ // ============================================
131
+ // DEFAULT FILTER
132
+ // ============================================
133
+
134
+ /**
135
+ * Default filter function that does case-insensitive "contains" matching.
136
+ */
137
+ export const defaultContainsFilter: FilterFn = (textValue, inputValue) => {
138
+ return textValue.toLowerCase().includes(inputValue.toLowerCase());
139
+ };
140
+
141
+ // ============================================
142
+ // IMPLEMENTATION
143
+ // ============================================
144
+
145
+ /**
146
+ * Creates state for a combobox component.
147
+ * Combines list state with input value management and filtering.
148
+ */
149
+ export function createComboBoxState<T = unknown>(
150
+ props: MaybeAccessor<ComboBoxStateProps<T>>
151
+ ): ComboBoxState<T> {
152
+ const getProps = () => access(props);
153
+
154
+ // Extract options with defaults
155
+ const menuTrigger = () => getProps().menuTrigger ?? 'input';
156
+ const allowsEmptyCollection = () => getProps().allowsEmptyCollection ?? false;
157
+ const allowsCustomValue = () => getProps().allowsCustomValue ?? false;
158
+ const shouldCloseOnBlur = () => getProps().shouldCloseOnBlur ?? true;
159
+
160
+ // Track focus strategy for list navigation
161
+ const [focusStrategy, setFocusStrategy] = createSignal<FocusStrategy | null>(null);
162
+
163
+ // Track whether we're showing all items (vs filtered)
164
+ const [showAllItems, setShowAllItems] = createSignal(false);
165
+
166
+ // Track the menu open trigger
167
+ let menuOpenTrigger: MenuTriggerAction = 'focus';
168
+
169
+ // ---- Selection State ----
170
+ // Note: Selection state is initialized first because input value may depend on it
171
+ const isSelectionControlled = () => getProps().selectedKey !== undefined;
172
+ const [internalSelectedKey, setInternalSelectedKey] = createSignal<Key | null>(
173
+ getProps().defaultSelectedKey ?? null
174
+ );
175
+
176
+ // ---- Input Value State ----
177
+ // Initialized after selection so we can derive from selected item if needed
178
+ const isInputControlled = () => getProps().inputValue !== undefined;
179
+
180
+ // We'll set the proper initial value after collection is created
181
+ const [internalInputValue, setInternalInputValue] = createSignal(
182
+ getProps().defaultInputValue ?? ''
183
+ );
184
+ // Track if we've initialized input from selection
185
+ let inputInitialized = false;
186
+
187
+ const inputValue: Accessor<string> = () => {
188
+ return isInputControlled() ? (getProps().inputValue ?? '') : internalInputValue();
189
+ };
190
+
191
+ const setInputValue = (value: string) => {
192
+ if (!isInputControlled()) {
193
+ setInternalInputValue(value);
194
+ }
195
+ getProps().onInputChange?.(value);
196
+ };
197
+
198
+ // Track last committed input value
199
+ const [lastValue, setLastValue] = createSignal(inputValue());
200
+
201
+ const selectedKey: Accessor<Key | null> = () => {
202
+ return isSelectionControlled() ? (getProps().selectedKey ?? null) : internalSelectedKey();
203
+ };
204
+
205
+ const setSelectedKey = (key: Key | null) => {
206
+ if (!isSelectionControlled()) {
207
+ setInternalSelectedKey(key);
208
+ }
209
+ getProps().onSelectionChange?.(key);
210
+ };
211
+
212
+ // ---- Overlay State ----
213
+ const overlayState = createOverlayTriggerState({
214
+ get isOpen() {
215
+ return getProps().isOpen;
216
+ },
217
+ get defaultOpen() {
218
+ return getProps().defaultOpen;
219
+ },
220
+ onOpenChange(isOpen: boolean) {
221
+ getProps().onOpenChange?.(isOpen, isOpen ? menuOpenTrigger : undefined);
222
+ },
223
+ });
224
+
225
+ // ---- List State (unfiltered collection) ----
226
+ const listState = createListState<T>({
227
+ get items() {
228
+ // Use items or defaultItems
229
+ return getProps().items ?? getProps().defaultItems ?? [];
230
+ },
231
+ get getKey() {
232
+ return getProps().getKey;
233
+ },
234
+ get getTextValue() {
235
+ return getProps().getTextValue;
236
+ },
237
+ get getDisabled() {
238
+ return getProps().getDisabled;
239
+ },
240
+ get disabledKeys() {
241
+ return getProps().disabledKeys;
242
+ },
243
+ selectionMode: 'single',
244
+ disallowEmptySelection: false,
245
+ get selectedKeys() {
246
+ const key = selectedKey();
247
+ return key != null ? [key] : [];
248
+ },
249
+ onSelectionChange(keys) {
250
+ if (keys === 'all') return;
251
+ const key = keys.size > 0 ? Array.from(keys)[0] : null;
252
+
253
+ // If same key selected, just reset input and close
254
+ if (key === selectedKey()) {
255
+ resetInputValue();
256
+ closeMenu();
257
+ return;
258
+ }
259
+
260
+ setSelectedKey(key);
261
+ },
262
+ });
263
+
264
+ // ---- Filtered Collection ----
265
+ const originalCollection = listState.collection;
266
+
267
+ const filteredCollection = createMemo<Collection<T>>(() => {
268
+ const collection = originalCollection();
269
+ const input = inputValue();
270
+ const filter = getProps().defaultFilter;
271
+
272
+ // If no filter function provided, return original collection
273
+ if (!filter) {
274
+ return collection;
275
+ }
276
+
277
+ // Filter the collection based on input value
278
+ return filterCollection(collection, input, filter);
279
+ });
280
+
281
+ // The displayed collection depends on showAllItems flag
282
+ // Always show filtered collection (or all items if showAllItems is true)
283
+ const displayedCollection = createMemo<Collection<T>>(() => {
284
+ return showAllItems() ? originalCollection() : filteredCollection();
285
+ });
286
+
287
+ // ---- Selected Item ----
288
+ const selectedItem: Accessor<CollectionNode<T> | null> = () => {
289
+ const key = selectedKey();
290
+ if (key == null) return null;
291
+ return originalCollection().getItem(key);
292
+ };
293
+
294
+ // Initialize input value from selected item if not already set
295
+ // This runs once on creation
296
+ if (!inputInitialized && !isInputControlled()) {
297
+ const defaultKey = getProps().defaultSelectedKey;
298
+ if (defaultKey != null && !getProps().defaultInputValue) {
299
+ // Get the text value from the collection for the default selected key
300
+ const item = originalCollection().getItem(defaultKey);
301
+ if (item) {
302
+ setInternalInputValue(item.textValue);
303
+ setLastValue(item.textValue);
304
+ }
305
+ }
306
+ inputInitialized = true;
307
+ }
308
+
309
+ // ---- Helper Functions ----
310
+ const resetInputValue = () => {
311
+ const item = selectedItem();
312
+ const textValue = item?.textValue ?? '';
313
+ setLastValue(textValue);
314
+ setInputValue(textValue);
315
+ };
316
+
317
+ const closeMenu = () => {
318
+ if (overlayState.isOpen()) {
319
+ overlayState.close();
320
+ }
321
+ };
322
+
323
+ // ---- Open/Toggle Logic ----
324
+ const open = (strategy: FocusStrategy | null = null, trigger?: MenuTriggerAction) => {
325
+ const displayAll = trigger === 'manual' || (trigger === 'focus' && menuTrigger() === 'focus');
326
+
327
+ // Check if we should open
328
+ const filtered = filteredCollection();
329
+ const original = originalCollection();
330
+ const canOpen = allowsEmptyCollection() ||
331
+ filtered.size > 0 ||
332
+ (displayAll && original.size > 0);
333
+
334
+ if (!canOpen) return;
335
+
336
+ if (displayAll && !overlayState.isOpen()) {
337
+ setShowAllItems(true);
338
+ }
339
+
340
+ menuOpenTrigger = trigger ?? 'focus';
341
+ setFocusStrategy(strategy);
342
+ overlayState.open();
343
+ };
344
+
345
+ const toggle = (strategy: FocusStrategy | null = null, trigger?: MenuTriggerAction) => {
346
+ const displayAll = trigger === 'manual' || (trigger === 'focus' && menuTrigger() === 'focus');
347
+
348
+ // Check if we can open (if closed)
349
+ const filtered = filteredCollection();
350
+ const original = originalCollection();
351
+ const canOpen = allowsEmptyCollection() ||
352
+ filtered.size > 0 ||
353
+ (displayAll && original.size > 0);
354
+
355
+ if (!canOpen && !overlayState.isOpen()) return;
356
+
357
+ if (displayAll && !overlayState.isOpen()) {
358
+ setShowAllItems(true);
359
+ }
360
+
361
+ if (!overlayState.isOpen()) {
362
+ menuOpenTrigger = trigger ?? 'focus';
363
+ }
364
+
365
+ setFocusStrategy(strategy);
366
+ overlayState.toggle();
367
+ };
368
+
369
+ // ---- Commit/Revert Logic ----
370
+ const commitCustomValue = () => {
371
+ setSelectedKey(null);
372
+ closeMenu();
373
+ };
374
+
375
+ const commitSelection = () => {
376
+ // If both are controlled, just call onSelectionChange
377
+ if (isSelectionControlled() && isInputControlled()) {
378
+ getProps().onSelectionChange?.(selectedKey());
379
+ const item = selectedItem();
380
+ setLastValue(item?.textValue ?? '');
381
+ closeMenu();
382
+ } else {
383
+ // Reset input to selected item's text
384
+ resetInputValue();
385
+ closeMenu();
386
+ }
387
+ };
388
+
389
+ const commitValue = () => {
390
+ if (allowsCustomValue()) {
391
+ const item = selectedItem();
392
+ const itemText = item?.textValue ?? '';
393
+ if (inputValue() === itemText) {
394
+ commitSelection();
395
+ } else {
396
+ commitCustomValue();
397
+ }
398
+ } else {
399
+ commitSelection();
400
+ }
401
+ };
402
+
403
+ const commit = () => {
404
+ const focusedKey = listState.focusedKey();
405
+
406
+ if (overlayState.isOpen() && focusedKey != null) {
407
+ // If focused key is already selected, just commit
408
+ if (selectedKey() === focusedKey) {
409
+ commitSelection();
410
+ } else {
411
+ // Select the focused item
412
+ setSelectedKey(focusedKey);
413
+ }
414
+ } else {
415
+ commitValue();
416
+ }
417
+ };
418
+
419
+ const revert = () => {
420
+ if (allowsCustomValue() && selectedKey() == null) {
421
+ commitCustomValue();
422
+ } else {
423
+ commitSelection();
424
+ }
425
+ };
426
+
427
+ // ---- Focus Handling ----
428
+ const [isFocused, setIsFocused] = createSignal(false);
429
+ let valueOnFocus = '';
430
+
431
+ const setFocused = (focused: boolean) => {
432
+ if (focused) {
433
+ valueOnFocus = inputValue();
434
+ if (menuTrigger() === 'focus' && !getProps().isReadOnly) {
435
+ open(null, 'focus');
436
+ }
437
+ } else {
438
+ if (shouldCloseOnBlur()) {
439
+ commitValue();
440
+ }
441
+ }
442
+ setIsFocused(focused);
443
+ };
444
+
445
+ // ---- Effects for Auto Open/Close ----
446
+ createEffect(() => {
447
+ const input = inputValue();
448
+ const filtered = filteredCollection();
449
+ const isOpen = overlayState.isOpen();
450
+ const last = lastValue();
451
+ const focused = isFocused();
452
+
453
+ // Auto-open when typing
454
+ if (
455
+ focused &&
456
+ (filtered.size > 0 || allowsEmptyCollection()) &&
457
+ !isOpen &&
458
+ input !== last &&
459
+ menuTrigger() !== 'manual'
460
+ ) {
461
+ open(null, 'input');
462
+ }
463
+
464
+ // Auto-close when empty (unless showing all)
465
+ if (
466
+ !showAllItems() &&
467
+ !allowsEmptyCollection() &&
468
+ isOpen &&
469
+ filtered.size === 0
470
+ ) {
471
+ closeMenu();
472
+ }
473
+
474
+ // Clear focused key when input changes
475
+ if (input !== last) {
476
+ listState.setFocusedKey(null);
477
+ setShowAllItems(false);
478
+
479
+ // Clear selection when input is cleared (if not fully controlled)
480
+ if (input === '' && (!isInputControlled() || !isSelectionControlled())) {
481
+ setSelectedKey(null);
482
+ }
483
+
484
+ setLastValue(input);
485
+ }
486
+ });
487
+
488
+ // Update input when selection changes
489
+ createEffect(() => {
490
+ const key = selectedKey();
491
+ const item = key != null ? originalCollection().getItem(key) : null;
492
+ const textValue = item?.textValue ?? '';
493
+
494
+ // Only update if selection changed and not fully controlled
495
+ if (!isInputControlled() || !isSelectionControlled()) {
496
+ if (key != null && textValue !== inputValue()) {
497
+ setInputValue(textValue);
498
+ setLastValue(textValue);
499
+ }
500
+ }
501
+ });
502
+
503
+ // Close when selection changes
504
+ createEffect((prevKey: Key | null | undefined) => {
505
+ const key = selectedKey();
506
+ if (key != null && key !== prevKey) {
507
+ closeMenu();
508
+ }
509
+ return key;
510
+ }, undefined);
511
+
512
+ // ---- Selection Methods for ListState compatibility ----
513
+ // These methods allow createOption to work with ComboBoxState
514
+ const select = (key: Key) => {
515
+ setSelectedKey(key);
516
+ closeMenu();
517
+ };
518
+
519
+ const selectionMode: Accessor<'single'> = () => 'single';
520
+ const isSelected = (key: Key) => selectedKey() === key;
521
+
522
+ // ---- Return State ----
523
+ return {
524
+ collection: displayedCollection,
525
+ isOpen: overlayState.isOpen,
526
+ open,
527
+ close: commitValue,
528
+ toggle,
529
+ selectedKey,
530
+ defaultSelectedKey: getProps().defaultSelectedKey ?? null,
531
+ selectedItem,
532
+ setSelectedKey,
533
+ inputValue,
534
+ defaultInputValue: getProps().defaultInputValue ?? '',
535
+ setInputValue,
536
+ focusedKey: listState.focusedKey,
537
+ setFocusedKey: listState.setFocusedKey,
538
+ isFocused,
539
+ setFocused,
540
+ focusStrategy,
541
+ commit,
542
+ revert,
543
+ // Selection state methods for ListState compatibility
544
+ select,
545
+ selectionMode,
546
+ isSelected,
547
+ isKeyDisabled: (key: Key) => listState.isDisabled(key),
548
+ get isDisabled() {
549
+ return getProps().isDisabled ?? false;
550
+ },
551
+ get isReadOnly() {
552
+ return getProps().isReadOnly ?? false;
553
+ },
554
+ get isRequired() {
555
+ return getProps().isRequired ?? false;
556
+ },
557
+ };
558
+ }
559
+
560
+ // ============================================
561
+ // COLLECTION FILTERING
562
+ // ============================================
563
+
564
+ /**
565
+ * Filter a collection based on input value.
566
+ */
567
+ function filterCollection<T>(
568
+ collection: Collection<T>,
569
+ inputValue: string,
570
+ filter: FilterFn
571
+ ): Collection<T> {
572
+ if (!inputValue) {
573
+ return collection;
574
+ }
575
+
576
+ const filteredItems: CollectionNode<T>[] = [];
577
+
578
+ for (const item of collection) {
579
+ if (item.type === 'section') {
580
+ // Filter section children
581
+ const filteredChildren: CollectionNode<T>[] = [];
582
+ if (item.childNodes) {
583
+ for (const child of item.childNodes) {
584
+ if (child.type === 'item' && filter(child.textValue, inputValue)) {
585
+ filteredChildren.push(child);
586
+ }
587
+ }
588
+ }
589
+ // Only include section if it has matching children
590
+ if (filteredChildren.length > 0) {
591
+ filteredItems.push({
592
+ ...item,
593
+ childNodes: filteredChildren,
594
+ });
595
+ }
596
+ } else if (item.type === 'item') {
597
+ if (filter(item.textValue, inputValue)) {
598
+ filteredItems.push(item);
599
+ }
600
+ }
601
+ }
602
+
603
+ // Create a new collection from filtered items
604
+ return createFilteredCollection(filteredItems, collection);
605
+ }
606
+
607
+ /**
608
+ * Create a filtered collection wrapper.
609
+ */
610
+ function createFilteredCollection<T>(
611
+ items: CollectionNode<T>[],
612
+ original: Collection<T>
613
+ ): Collection<T> {
614
+ const itemMap = new Map<Key, CollectionNode<T>>();
615
+
616
+ for (const item of items) {
617
+ itemMap.set(item.key, item);
618
+ if (item.childNodes) {
619
+ for (const child of item.childNodes) {
620
+ itemMap.set(child.key, child);
621
+ }
622
+ }
623
+ }
624
+
625
+ return {
626
+ get size() {
627
+ let count = 0;
628
+ for (const item of items) {
629
+ if (item.type === 'item') {
630
+ count++;
631
+ } else if (item.childNodes) {
632
+ count += Array.from(item.childNodes).filter(c => c.type === 'item').length;
633
+ }
634
+ }
635
+ return count;
636
+ },
637
+ getItem(key: Key) {
638
+ return itemMap.get(key) ?? null;
639
+ },
640
+ getKeys() {
641
+ return itemMap.keys();
642
+ },
643
+ getFirstKey() {
644
+ for (const item of items) {
645
+ if (item.type === 'item') return item.key;
646
+ if (item.childNodes) {
647
+ for (const child of item.childNodes) {
648
+ if (child.type === 'item') return child.key;
649
+ }
650
+ }
651
+ }
652
+ return null;
653
+ },
654
+ getLastKey() {
655
+ for (let i = items.length - 1; i >= 0; i--) {
656
+ const item = items[i];
657
+ if (item.type === 'item') return item.key;
658
+ if (item.childNodes) {
659
+ const children = Array.from(item.childNodes);
660
+ for (let j = children.length - 1; j >= 0; j--) {
661
+ if (children[j].type === 'item') return children[j].key;
662
+ }
663
+ }
664
+ }
665
+ return null;
666
+ },
667
+ getKeyBefore(key: Key) {
668
+ return original.getKeyBefore(key);
669
+ },
670
+ getKeyAfter(key: Key) {
671
+ return original.getKeyAfter(key);
672
+ },
673
+ at(index: number) {
674
+ // Flatten items for indexing
675
+ let currentIndex = 0;
676
+ for (const item of items) {
677
+ if (item.type === 'item') {
678
+ if (currentIndex === index) return item;
679
+ currentIndex++;
680
+ } else if (item.childNodes) {
681
+ for (const child of item.childNodes) {
682
+ if (child.type === 'item') {
683
+ if (currentIndex === index) return child;
684
+ currentIndex++;
685
+ }
686
+ }
687
+ }
688
+ }
689
+ return null;
690
+ },
691
+ getChildren(key: Key) {
692
+ const item = itemMap.get(key);
693
+ return item?.childNodes ?? [];
694
+ },
695
+ getTextValue(key: Key) {
696
+ const item = itemMap.get(key);
697
+ return item?.textValue ?? '';
698
+ },
699
+ [Symbol.iterator]() {
700
+ return items[Symbol.iterator]();
701
+ },
702
+ };
703
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * ComboBox state management for Solid Stately.
3
+ */
4
+ export { createComboBoxState, defaultContainsFilter, type ComboBoxState, type ComboBoxStateProps, type FilterFn, type MenuTriggerAction, type FocusStrategy, } from './createComboBoxState';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EACL,mBAAmB,EACnB,qBAAqB,EACrB,KAAK,aAAa,EAClB,KAAK,kBAAkB,EACvB,KAAK,QAAQ,EACb,KAAK,iBAAiB,EACtB,KAAK,aAAa,GACnB,MAAM,uBAAuB,CAAC"}