@proyecto-viviana/solidaria-components 0.2.9 → 0.3.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.
Files changed (222) hide show
  1. package/README.md +39 -272
  2. package/dist/ActionBar.d.ts +21 -13
  3. package/dist/ActionBar.d.ts.map +1 -1
  4. package/dist/ActionGroup.d.ts +8 -8
  5. package/dist/ActionGroup.d.ts.map +1 -1
  6. package/dist/Alert.d.ts +5 -5
  7. package/dist/Alert.d.ts.map +1 -1
  8. package/dist/Autocomplete.d.ts +5 -5
  9. package/dist/Autocomplete.d.ts.map +1 -1
  10. package/dist/Breadcrumbs.d.ts +18 -7
  11. package/dist/Breadcrumbs.d.ts.map +1 -1
  12. package/dist/Button.d.ts +24 -5
  13. package/dist/Button.d.ts.map +1 -1
  14. package/dist/Calendar.d.ts +38 -7
  15. package/dist/Calendar.d.ts.map +1 -1
  16. package/dist/Checkbox.d.ts +32 -7
  17. package/dist/Checkbox.d.ts.map +1 -1
  18. package/dist/Collection.d.ts +19 -14
  19. package/dist/Collection.d.ts.map +1 -1
  20. package/dist/Color.d.ts +103 -14
  21. package/dist/Color.d.ts.map +1 -1
  22. package/dist/ColorEditor.d.ts +6 -6
  23. package/dist/ColorEditor.d.ts.map +1 -1
  24. package/dist/ComboBox.d.ts +85 -19
  25. package/dist/ComboBox.d.ts.map +1 -1
  26. package/dist/ContextualHelpTrigger.d.ts +2 -2
  27. package/dist/ContextualHelpTrigger.d.ts.map +1 -1
  28. package/dist/DateField.d.ts +8 -6
  29. package/dist/DateField.d.ts.map +1 -1
  30. package/dist/DatePicker.d.ts +53 -22
  31. package/dist/DatePicker.d.ts.map +1 -1
  32. package/dist/DateRangePickerContext.d.ts +30 -0
  33. package/dist/DateRangePickerContext.d.ts.map +1 -0
  34. package/dist/Dialog.d.ts +5 -5
  35. package/dist/Dialog.d.ts.map +1 -1
  36. package/dist/Disclosure.d.ts +23 -5
  37. package/dist/Disclosure.d.ts.map +1 -1
  38. package/dist/DragAndDrop.d.ts +6 -6
  39. package/dist/DragAndDrop.d.ts.map +1 -1
  40. package/dist/DragPreview.d.ts +2 -2
  41. package/dist/DragPreview.d.ts.map +1 -1
  42. package/dist/DropZone.d.ts +4 -4
  43. package/dist/DropZone.d.ts.map +1 -1
  44. package/dist/FieldError.d.ts +9 -5
  45. package/dist/FieldError.d.ts.map +1 -1
  46. package/dist/FileTrigger.d.ts +3 -3
  47. package/dist/FileTrigger.d.ts.map +1 -1
  48. package/dist/Focusable.d.ts +2 -2
  49. package/dist/Focusable.d.ts.map +1 -1
  50. package/dist/Form.d.ts +18 -4
  51. package/dist/Form.d.ts.map +1 -1
  52. package/dist/GridList.d.ts +32 -12
  53. package/dist/GridList.d.ts.map +1 -1
  54. package/dist/HiddenDateInput.d.ts +26 -0
  55. package/dist/HiddenDateInput.d.ts.map +1 -0
  56. package/dist/HiddenTimeInput.d.ts +25 -0
  57. package/dist/HiddenTimeInput.d.ts.map +1 -0
  58. package/dist/Icon.d.ts +5 -5
  59. package/dist/Icon.d.ts.map +1 -1
  60. package/dist/Keyboard.d.ts +1 -1
  61. package/dist/Landmark.d.ts +3 -3
  62. package/dist/Landmark.d.ts.map +1 -1
  63. package/dist/Link.d.ts +10 -4
  64. package/dist/Link.d.ts.map +1 -1
  65. package/dist/ListBox.d.ts +32 -12
  66. package/dist/ListBox.d.ts.map +1 -1
  67. package/dist/ListDropTargetDelegate.d.ts +6 -6
  68. package/dist/ListDropTargetDelegate.d.ts.map +1 -1
  69. package/dist/Menu.d.ts +65 -14
  70. package/dist/Menu.d.ts.map +1 -1
  71. package/dist/Meter.d.ts +3 -3
  72. package/dist/Meter.d.ts.map +1 -1
  73. package/dist/Modal.d.ts +5 -5
  74. package/dist/Modal.d.ts.map +1 -1
  75. package/dist/NumberField.d.ts +8 -12
  76. package/dist/NumberField.d.ts.map +1 -1
  77. package/dist/Popover.d.ts +28 -5
  78. package/dist/Popover.d.ts.map +1 -1
  79. package/dist/Pressable.d.ts +2 -2
  80. package/dist/Pressable.d.ts.map +1 -1
  81. package/dist/ProgressBar.d.ts +5 -3
  82. package/dist/ProgressBar.d.ts.map +1 -1
  83. package/dist/RadioGroup.d.ts +43 -9
  84. package/dist/RadioGroup.d.ts.map +1 -1
  85. package/dist/RangeCalendar.d.ts +34 -7
  86. package/dist/RangeCalendar.d.ts.map +1 -1
  87. package/dist/RouterProvider.d.ts +2 -2
  88. package/dist/RouterProvider.d.ts.map +1 -1
  89. package/dist/SearchField.d.ts +23 -20
  90. package/dist/SearchField.d.ts.map +1 -1
  91. package/dist/Select.d.ts +41 -11
  92. package/dist/Select.d.ts.map +1 -1
  93. package/dist/SelectionIndicator.d.ts +3 -3
  94. package/dist/SelectionIndicator.d.ts.map +1 -1
  95. package/dist/Separator.d.ts +9 -3
  96. package/dist/Separator.d.ts.map +1 -1
  97. package/dist/SharedElementTransition.d.ts +6 -4
  98. package/dist/SharedElementTransition.d.ts.map +1 -1
  99. package/dist/Slider.d.ts +12 -8
  100. package/dist/Slider.d.ts.map +1 -1
  101. package/dist/StepList.d.ts +90 -0
  102. package/dist/StepList.d.ts.map +1 -0
  103. package/dist/Switch.d.ts +11 -5
  104. package/dist/Switch.d.ts.map +1 -1
  105. package/dist/Table.d.ts +187 -23
  106. package/dist/Table.d.ts.map +1 -1
  107. package/dist/Tabs.d.ts +45 -9
  108. package/dist/Tabs.d.ts.map +1 -1
  109. package/dist/TagGroup.d.ts +12 -10
  110. package/dist/TagGroup.d.ts.map +1 -1
  111. package/dist/Text.d.ts +2 -2
  112. package/dist/TextField.d.ts +15 -11
  113. package/dist/TextField.d.ts.map +1 -1
  114. package/dist/TimeField.d.ts +6 -6
  115. package/dist/TimeField.d.ts.map +1 -1
  116. package/dist/Toast.d.ts +29 -14
  117. package/dist/Toast.d.ts.map +1 -1
  118. package/dist/ToggleButton.d.ts +11 -5
  119. package/dist/ToggleButton.d.ts.map +1 -1
  120. package/dist/ToggleButtonGroup.d.ts +7 -7
  121. package/dist/ToggleButtonGroup.d.ts.map +1 -1
  122. package/dist/Toolbar.d.ts +7 -3
  123. package/dist/Toolbar.d.ts.map +1 -1
  124. package/dist/Tooltip.d.ts +50 -8
  125. package/dist/Tooltip.d.ts.map +1 -1
  126. package/dist/Tree.d.ts +66 -17
  127. package/dist/Tree.d.ts.map +1 -1
  128. package/dist/Virtualizer.d.ts +12 -12
  129. package/dist/Virtualizer.d.ts.map +1 -1
  130. package/dist/VirtualizerLayouts.d.ts +2 -2
  131. package/dist/VirtualizerLayouts.d.ts.map +1 -1
  132. package/dist/VisuallyHidden.d.ts +1 -1
  133. package/dist/VisuallyHidden.d.ts.map +1 -1
  134. package/dist/contexts.d.ts +5 -1
  135. package/dist/contexts.d.ts.map +1 -1
  136. package/dist/index.d.ts +73 -71
  137. package/dist/index.d.ts.map +1 -1
  138. package/dist/index.js +23247 -18564
  139. package/dist/index.js.map +1 -1
  140. package/dist/index.jsx +18110 -0
  141. package/dist/index.jsx.map +1 -0
  142. package/dist/useDragAndDrop.d.ts +13 -13
  143. package/dist/useDragAndDrop.d.ts.map +1 -1
  144. package/dist/utils.d.ts +2 -2
  145. package/dist/utils.d.ts.map +1 -1
  146. package/dist/virtualizer/Layout.d.ts +1 -1
  147. package/dist/virtualizer/Layout.d.ts.map +1 -1
  148. package/package.json +31 -32
  149. package/src/ActionBar.tsx +75 -72
  150. package/src/ActionGroup.tsx +53 -61
  151. package/src/Alert.tsx +17 -42
  152. package/src/Autocomplete.tsx +39 -44
  153. package/src/Breadcrumbs.tsx +149 -80
  154. package/src/Button.tsx +267 -70
  155. package/src/Calendar.tsx +218 -138
  156. package/src/Checkbox.tsx +413 -121
  157. package/src/Collection.tsx +67 -58
  158. package/src/Color.tsx +803 -380
  159. package/src/ColorEditor.tsx +131 -149
  160. package/src/ComboBox.tsx +414 -249
  161. package/src/ContextualHelpTrigger.tsx +86 -74
  162. package/src/DateField.tsx +185 -91
  163. package/src/DatePicker.tsx +524 -213
  164. package/src/DateRangePickerContext.tsx +44 -0
  165. package/src/Dialog.tsx +156 -118
  166. package/src/Disclosure.tsx +127 -80
  167. package/src/DragAndDrop.tsx +60 -54
  168. package/src/DragPreview.tsx +13 -11
  169. package/src/DropZone.tsx +42 -22
  170. package/src/FieldError.tsx +45 -23
  171. package/src/FileTrigger.tsx +19 -19
  172. package/src/Focusable.tsx +21 -24
  173. package/src/Form.tsx +71 -16
  174. package/src/GridList.tsx +273 -197
  175. package/src/HiddenDateInput.tsx +153 -0
  176. package/src/HiddenTimeInput.tsx +133 -0
  177. package/src/Icon.tsx +22 -43
  178. package/src/Keyboard.tsx +3 -3
  179. package/src/Landmark.tsx +37 -63
  180. package/src/Link.tsx +125 -75
  181. package/src/ListBox.tsx +332 -233
  182. package/src/ListDropTargetDelegate.ts +81 -80
  183. package/src/Menu.tsx +1023 -274
  184. package/src/Meter.tsx +38 -56
  185. package/src/Modal.tsx +243 -175
  186. package/src/NumberField.tsx +139 -143
  187. package/src/Popover.tsx +386 -233
  188. package/src/Pressable.tsx +21 -21
  189. package/src/ProgressBar.tsx +48 -57
  190. package/src/RadioGroup.tsx +524 -122
  191. package/src/RangeCalendar.tsx +157 -90
  192. package/src/RouterProvider.tsx +30 -47
  193. package/src/SearchField.tsx +362 -143
  194. package/src/Select.tsx +656 -233
  195. package/src/SelectionIndicator.tsx +18 -15
  196. package/src/Separator.tsx +47 -49
  197. package/src/SharedElementTransition.tsx +103 -97
  198. package/src/Slider.tsx +138 -98
  199. package/src/StepList.tsx +272 -0
  200. package/src/Switch.tsx +93 -46
  201. package/src/Table.tsx +1308 -342
  202. package/src/Tabs.tsx +324 -103
  203. package/src/TagGroup.tsx +139 -126
  204. package/src/Text.tsx +3 -3
  205. package/src/TextField.tsx +389 -79
  206. package/src/TimeField.tsx +136 -76
  207. package/src/Toast.tsx +209 -157
  208. package/src/ToggleButton.tsx +47 -37
  209. package/src/ToggleButtonGroup.tsx +39 -34
  210. package/src/Toolbar.tsx +54 -69
  211. package/src/Tooltip.tsx +387 -119
  212. package/src/Tree.tsx +651 -368
  213. package/src/Virtualizer.tsx +208 -180
  214. package/src/VirtualizerLayouts.ts +45 -30
  215. package/src/VisuallyHidden.tsx +19 -19
  216. package/src/contexts.ts +29 -37
  217. package/src/index.ts +110 -195
  218. package/src/useDragAndDrop.ts +87 -71
  219. package/src/utils.tsx +40 -55
  220. package/src/virtualizer/Layout.ts +14 -22
  221. package/dist/index.ssr.js +0 -16996
  222. package/dist/index.ssr.js.map +0 -1
package/src/ListBox.tsx CHANGED
@@ -16,22 +16,21 @@ import {
16
16
  useContext,
17
17
  For,
18
18
  Show,
19
- } from 'solid-js';
19
+ } from "solid-js";
20
20
  import {
21
21
  createListBox,
22
22
  createOption,
23
23
  createFocusRing,
24
- createHover,
25
24
  mergeProps,
26
25
  type AriaListBoxProps,
27
26
  type AriaOptionProps,
28
- } from '@proyecto-viviana/solidaria';
27
+ } from "@proyecto-viviana/solidaria";
29
28
  import {
30
29
  createListState,
31
30
  type ListState,
32
31
  type Key,
33
32
  type DropTarget,
34
- } from '@proyecto-viviana/solid-stately';
33
+ } from "@proyecto-viviana/solid-stately";
35
34
  import {
36
35
  type RenderChildren,
37
36
  type ClassNameOrFunction,
@@ -39,20 +38,20 @@ import {
39
38
  type SlotProps,
40
39
  useRenderProps,
41
40
  filterDOMProps,
42
- } from './utils';
43
- import { SharedElementTransition } from './SharedElementTransition';
41
+ } from "./utils";
42
+ import { SharedElementTransition } from "./SharedElementTransition";
44
43
  import {
45
44
  SelectionIndicatorContext,
46
45
  type SelectionIndicatorContextValue,
47
- } from './SelectionIndicator';
48
- import { useVirtualizerContext } from './Virtualizer';
49
- import { type DragAndDropHooks } from './useDragAndDrop';
46
+ } from "./SelectionIndicator";
47
+ import { useVirtualizerContext } from "./Virtualizer";
48
+ import { type DragAndDropHooks } from "./useDragAndDrop";
50
49
  import {
51
50
  getNormalizedDropTargetKey,
52
51
  mergePersistedKeysIntoVirtualRange,
53
52
  useDndPersistedKeys,
54
53
  useRenderDropIndicator,
55
- } from './DragAndDrop';
54
+ } from "./DragAndDrop";
56
55
  import {
57
56
  CollectionRendererContext,
58
57
  Section,
@@ -64,11 +63,7 @@ import {
64
63
  useCollectionRenderer,
65
64
  isCollectionSection,
66
65
  flattenCollectionEntries,
67
- } from './Collection';
68
-
69
- // ============================================
70
- // TYPES
71
- // ============================================
66
+ } from "./Collection";
72
67
 
73
68
  export interface ListBoxRenderProps {
74
69
  /** Whether the listbox has focus. */
@@ -81,9 +76,15 @@ export interface ListBoxRenderProps {
81
76
  isEmpty: boolean;
82
77
  }
83
78
 
84
- export interface ListBoxProps<T>
85
- extends Omit<AriaListBoxProps, 'children'>,
86
- SlotProps {
79
+ type RefLike<T> = ((el: T) => void) | { current?: T | null } | undefined;
80
+
81
+ function assignRef<T>(ref: RefLike<T>, el: T): void {
82
+ if (!ref) return;
83
+ if (typeof ref === "function") ref(el);
84
+ else ref.current = el;
85
+ }
86
+
87
+ export interface ListBoxProps<T> extends Omit<AriaListBoxProps, "children">, SlotProps {
87
88
  /** The items to render in the listbox. */
88
89
  items: CollectionEntry<T>[];
89
90
  /** Function to get the key from an item. */
@@ -93,15 +94,19 @@ export interface ListBoxProps<T>
93
94
  /** Function to check if an item is disabled. */
94
95
  getDisabled?: (item: T) => boolean;
95
96
  /** The selection mode. */
96
- selectionMode?: 'none' | 'single' | 'multiple';
97
+ selectionMode?: "none" | "single" | "multiple";
98
+ /** The selection behavior (toggle vs replace). */
99
+ selectionBehavior?: "toggle" | "replace";
100
+ /** Whether disabled items can still receive focus. */
101
+ disabledBehavior?: "selection" | "all";
97
102
  /** Keys of disabled items. */
98
103
  disabledKeys?: Iterable<Key>;
99
104
  /** Currently selected keys (controlled). */
100
- selectedKeys?: 'all' | Iterable<Key>;
105
+ selectedKeys?: "all" | Iterable<Key>;
101
106
  /** Default selected keys (uncontrolled). */
102
- defaultSelectedKeys?: 'all' | Iterable<Key>;
107
+ defaultSelectedKeys?: "all" | Iterable<Key>;
103
108
  /** Handler called when selection changes. */
104
- onSelectionChange?: (keys: 'all' | Set<Key>) => void;
109
+ onSelectionChange?: (keys: "all" | Set<Key>) => void;
105
110
  /** The children of the component. A function may be provided to render each item. */
106
111
  children: (item: T) => JSX.Element;
107
112
  /** The CSS className for the element. */
@@ -116,8 +121,16 @@ export interface ListBoxProps<T>
116
121
  isLoading?: boolean;
117
122
  /** Called when the load more sentinel becomes visible. */
118
123
  onLoadMore?: () => void | Promise<void>;
124
+ /** Ref for the listbox element. */
125
+ ref?: RefLike<HTMLUListElement>;
119
126
  /** Drag and drop hooks from `useDragAndDrop`. */
120
127
  dragAndDropHooks?: DragAndDropHooks<T>;
128
+ /** Layout hint for styling parity. */
129
+ layout?: "stack" | "grid";
130
+ /** Orientation hint for styling parity. */
131
+ orientation?: "vertical" | "horizontal";
132
+ /** Slot definitions provided through ListBoxContext. */
133
+ slots?: Record<string, Partial<ListBoxProps<T>>>;
121
134
  }
122
135
 
123
136
  export interface ListBoxOptionRenderProps {
@@ -136,8 +149,7 @@ export interface ListBoxOptionRenderProps {
136
149
  }
137
150
 
138
151
  export interface ListBoxOptionProps<T>
139
- extends Omit<AriaOptionProps, 'children' | 'key'>,
140
- SlotProps {
152
+ extends Omit<AriaOptionProps, "children" | "key">, SlotProps {
141
153
  /** The unique key for the option. */
142
154
  id: Key;
143
155
  /** The item value. */
@@ -150,6 +162,8 @@ export interface ListBoxOptionProps<T>
150
162
  style?: StyleOrFunction<ListBoxOptionRenderProps>;
151
163
  /** The text value of the option (for typeahead). */
152
164
  textValue?: string;
165
+ /** Ref for the option element. */
166
+ ref?: RefLike<HTMLLIElement>;
153
167
  }
154
168
 
155
169
  export interface ListBoxLoadMoreItemProps extends SlotProps {
@@ -157,6 +171,8 @@ export interface ListBoxLoadMoreItemProps extends SlotProps {
157
171
  onLoadMore: () => void | Promise<void>;
158
172
  /** Whether additional items are currently loading. */
159
173
  isLoading?: boolean;
174
+ /** Scroll offset multiplier for early loading trigger (default: 1 = 100% of viewport height). */
175
+ scrollOffset?: number;
160
176
  /** Content for the load more row. */
161
177
  children?: JSX.Element;
162
178
  /** The CSS className for the element. */
@@ -167,34 +183,58 @@ export interface ListBoxLoadMoreItemProps extends SlotProps {
167
183
 
168
184
  export interface ListBoxSectionProps extends SectionProps {}
169
185
 
170
- // ============================================
171
- // CONTEXT
172
- // ============================================
173
-
174
186
  interface ListBoxContextValue<T> {
175
187
  state: ListState<T>;
176
188
  isDisabled: () => boolean;
177
189
  dragAndDropHooks?: DragAndDropHooks<unknown>;
178
190
  dragState?: unknown;
179
191
  dropState?: unknown;
192
+ slots?: Record<string, Partial<ListBoxProps<T>>>;
180
193
  }
181
194
 
182
195
  export const ListBoxContext = createContext<ListBoxContextValue<unknown> | null>(null);
183
196
  export const ListBoxStateContext = createContext<ListState<unknown> | null>(null);
184
197
  export const ListStateContext = ListBoxStateContext;
185
198
 
186
- // ============================================
187
- // COMPONENTS
188
- // ============================================
189
-
190
199
  /**
191
200
  * A listbox displays a list of options and allows a user to select one or more of them.
192
201
  */
193
202
  export function ListBox<T>(props: ListBoxProps<T>): JSX.Element {
203
+ const parentContext = useContext(ListBoxContext) as ListBoxContextValue<T> | null;
204
+ const contextSlotProps = parentContext?.slots?.[props.slot ?? "default"];
205
+ const mergedListBoxProps = contextSlotProps
206
+ ? (mergeProps(contextSlotProps, props) as ListBoxProps<T>)
207
+ : props;
194
208
  const [local, stateProps, ariaProps] = splitProps(
195
- props,
196
- ['children', 'class', 'style', 'slot', 'renderEmptyState', 'hasMore', 'isLoading', 'onLoadMore', 'dragAndDropHooks'],
197
- ['items', 'getKey', 'getTextValue', 'getDisabled', 'disabledKeys', 'selectionMode', 'selectedKeys', 'defaultSelectedKeys', 'onSelectionChange']
209
+ mergedListBoxProps,
210
+ [
211
+ "children",
212
+ "class",
213
+ "style",
214
+ "slot",
215
+ "renderEmptyState",
216
+ "hasMore",
217
+ "isLoading",
218
+ "onLoadMore",
219
+ "dragAndDropHooks",
220
+ "slots",
221
+ "ref",
222
+ ],
223
+ [
224
+ "items",
225
+ "getKey",
226
+ "getTextValue",
227
+ "getDisabled",
228
+ "disabledKeys",
229
+ "disabledBehavior",
230
+ "selectionMode",
231
+ "selectionBehavior",
232
+ "selectedKeys",
233
+ "defaultSelectedKeys",
234
+ "onSelectionChange",
235
+ "layout",
236
+ "orientation",
237
+ ],
198
238
  );
199
239
 
200
240
  const flatItems = createMemo<T[]>(() => {
@@ -203,7 +243,6 @@ export function ListBox<T>(props: ListBoxProps<T>): JSX.Element {
203
243
 
204
244
  const hasSections = createMemo(() => stateProps.items.some((item) => isCollectionSection(item)));
205
245
 
206
- // Create list state
207
246
  const state = createListState<T>({
208
247
  get items() {
209
248
  return flatItems();
@@ -223,6 +262,12 @@ export function ListBox<T>(props: ListBoxProps<T>): JSX.Element {
223
262
  get selectionMode() {
224
263
  return stateProps.selectionMode;
225
264
  },
265
+ get selectionBehavior() {
266
+ return stateProps.selectionBehavior;
267
+ },
268
+ get disabledBehavior() {
269
+ return stateProps.disabledBehavior;
270
+ },
226
271
  get selectedKeys() {
227
272
  return stateProps.selectedKeys;
228
273
  },
@@ -234,16 +279,14 @@ export function ListBox<T>(props: ListBoxProps<T>): JSX.Element {
234
279
  },
235
280
  });
236
281
 
237
- // Helper to resolve isDisabled
238
282
  const resolveDisabled = (): boolean => {
239
283
  const disabled = ariaProps.isDisabled;
240
- if (typeof disabled === 'function') {
284
+ if (typeof disabled === "function") {
241
285
  return (disabled as () => boolean)();
242
286
  }
243
287
  return !!disabled;
244
288
  };
245
289
 
246
- // Create listbox aria props
247
290
  const listBoxAria = createListBox(
248
291
  {
249
292
  ...ariaProps,
@@ -251,13 +294,11 @@ export function ListBox<T>(props: ListBoxProps<T>): JSX.Element {
251
294
  return resolveDisabled();
252
295
  },
253
296
  },
254
- state
297
+ state,
255
298
  );
256
299
 
257
- // Create focus ring
258
300
  const { isFocused, isFocusVisible, focusProps } = createFocusRing();
259
301
 
260
- // Render props values
261
302
  const renderValues = createMemo<ListBoxRenderProps>(() => ({
262
303
  isFocused: state.isFocused() || isFocused(),
263
304
  isFocusVisible: isFocusVisible(),
@@ -265,23 +306,20 @@ export function ListBox<T>(props: ListBoxProps<T>): JSX.Element {
265
306
  isEmpty: state.collection().size === 0,
266
307
  }));
267
308
 
268
- // Resolve render props
269
309
  const renderProps = useRenderProps(
270
310
  {
271
311
  class: local.class,
272
312
  style: local.style,
273
- defaultClassName: 'solidaria-ListBox',
313
+ defaultClassName: "solidaria-ListBox",
274
314
  },
275
- renderValues
315
+ renderValues,
276
316
  );
277
317
 
278
- // Filter DOM props
279
318
  const domProps = createMemo(() => {
280
319
  const filtered = filterDOMProps(ariaProps as Record<string, unknown>, { global: true });
281
320
  return filtered;
282
321
  });
283
322
 
284
- // Remove ref from spread props
285
323
  const cleanListBoxProps = () => {
286
324
  const { ref: _ref1, ...rest } = listBoxAria.listBoxProps as Record<string, unknown>;
287
325
  return rest;
@@ -298,18 +336,25 @@ export function ListBox<T>(props: ListBoxProps<T>): JSX.Element {
298
336
 
299
337
  const isEmpty = () => stateProps.items.length === 0;
300
338
  const parentCollectionRenderer = useCollectionRenderer<unknown>();
301
- const getItemNodes = createMemo(() => Array.from(state.collection()).filter((node) => node.type === 'item'));
302
- const getDropTargetByIndex = (index: number, position: 'before' | 'after' | 'on'): DropTarget | null => {
339
+ const getItemNodes = createMemo(() =>
340
+ Array.from(state.collection()).filter((node) => node.type === "item"),
341
+ );
342
+ const getDropTargetByIndex = (
343
+ index: number,
344
+ position: "before" | "after" | "on",
345
+ ): DropTarget | null => {
303
346
  const node = getItemNodes()[index];
304
347
  if (!node) return null;
305
- return { type: 'item', key: node.key, dropPosition: position };
348
+ return { type: "item", key: node.key, dropPosition: position };
306
349
  };
307
350
  const hasDroppableDnd = createMemo(() => {
308
351
  const hooks = local.dragAndDropHooks;
309
352
  return Boolean(
310
353
  hooks?.useDroppableCollectionState &&
311
354
  hooks.useDroppableCollection &&
312
- (hooks.dropTargetDelegate || parentCollectionRenderer?.dropTargetDelegate || hooks.ListDropTargetDelegate)
355
+ (hooks.dropTargetDelegate ||
356
+ parentCollectionRenderer?.dropTargetDelegate ||
357
+ hooks.ListDropTargetDelegate),
313
358
  );
314
359
  });
315
360
  const dropState = createMemo(() => {
@@ -338,22 +383,23 @@ export function ListBox<T>(props: ListBoxProps<T>): JSX.Element {
338
383
  const hooks = local.dragAndDropHooks;
339
384
  const activeDropState = dropState();
340
385
  if (!hooks?.useDroppableCollection || !activeDropState) return undefined;
341
- const resolveDirection = (): 'ltr' | 'rtl' => {
386
+ const resolveDirection = (): "ltr" | "rtl" => {
342
387
  const el = listRef();
343
- if (el && typeof window !== 'undefined' && typeof window.getComputedStyle === 'function') {
388
+ if (el && typeof window !== "undefined" && typeof window.getComputedStyle === "function") {
344
389
  const dir = window.getComputedStyle(el).direction;
345
- if (dir === 'rtl') return 'rtl';
390
+ if (dir === "rtl") return "rtl";
346
391
  }
347
- return typeof document !== 'undefined' && document.dir === 'rtl' ? 'rtl' : 'ltr';
392
+ return typeof document !== "undefined" && document.dir === "rtl" ? "rtl" : "ltr";
348
393
  };
349
- const dropTargetDelegate = hooks.dropTargetDelegate
350
- ?? parentCollectionRenderer?.dropTargetDelegate
351
- ?? (hooks.ListDropTargetDelegate
394
+ const dropTargetDelegate =
395
+ hooks.dropTargetDelegate ??
396
+ parentCollectionRenderer?.dropTargetDelegate ??
397
+ (hooks.ListDropTargetDelegate
352
398
  ? new hooks.ListDropTargetDelegate(
353
- () => state.collection(),
354
- () => listRef(),
355
- { layout: 'stack', orientation: 'vertical', direction: resolveDirection() }
356
- )
399
+ () => state.collection(),
400
+ () => listRef(),
401
+ { layout: "stack", orientation: "vertical", direction: resolveDirection() },
402
+ )
357
403
  : undefined);
358
404
  if (!dropTargetDelegate) return undefined;
359
405
  return hooks.useDroppableCollection(
@@ -369,16 +415,18 @@ export function ListBox<T>(props: ListBoxProps<T>): JSX.Element {
369
415
  },
370
416
  },
371
417
  activeDropState,
372
- () => listRef()
418
+ () => listRef(),
373
419
  );
374
420
  });
375
421
  const isRootDropTarget = createMemo(() => {
376
- return Boolean(dropState()?.target?.type === 'root');
422
+ return Boolean(dropState()?.target?.type === "root");
377
423
  });
378
- const dndRenderDropIndicator = createMemo(() => useRenderDropIndicator(local.dragAndDropHooks, dropState()));
379
- const dndDropIndicator = (index: number, position: 'before' | 'after' | 'on') => {
424
+ const dndRenderDropIndicator = createMemo(() =>
425
+ useRenderDropIndicator(local.dragAndDropHooks, dropState()),
426
+ );
427
+ const dndDropIndicator = (index: number, position: "before" | "after" | "on") => {
380
428
  const target = getDropTargetByIndex(index, position);
381
- if (!target || target.type !== 'item') return undefined;
429
+ if (!target || target.type !== "item") return undefined;
382
430
  return dndRenderDropIndicator()?.(target);
383
431
  };
384
432
  const virtualizer = useVirtualizerContext();
@@ -386,7 +434,7 @@ export function ListBox<T>(props: ListBoxProps<T>): JSX.Element {
386
434
  { focusedKey: state.focusedKey },
387
435
  local.dragAndDropHooks,
388
436
  dropState(),
389
- state.collection()
437
+ state.collection(),
390
438
  );
391
439
  const virtualRange = createMemo(() => {
392
440
  if (!virtualizer || !parentCollectionRenderer?.isVirtualized || hasSections()) return null;
@@ -398,20 +446,31 @@ export function ListBox<T>(props: ListBoxProps<T>): JSX.Element {
398
446
  const dropTarget = dropState()?.target;
399
447
  const normalizedDropKey = getNormalizedDropTargetKey(dropTarget, state.collection());
400
448
  const focusedKey = state.focusedKey();
401
- const focusedIndex = focusedKey != null ? itemNodes.findIndex((node) => node.key === focusedKey) : -1;
449
+ const focusedIndex =
450
+ focusedKey != null ? itemNodes.findIndex((node) => node.key === focusedKey) : -1;
402
451
  const forceIncludeIndexes = [
403
- dropTarget?.type === 'item' ? itemNodes.findIndex((node) => node.key === dropTarget.key) : -1,
404
- normalizedDropKey != null ? itemNodes.findIndex((node) => node.key === normalizedDropKey) : -1,
405
- dropTarget?.type === 'item' ? -1 : focusedIndex,
452
+ dropTarget?.type === "item" ? itemNodes.findIndex((node) => node.key === dropTarget.key) : -1,
453
+ normalizedDropKey != null
454
+ ? itemNodes.findIndex((node) => node.key === normalizedDropKey)
455
+ : -1,
456
+ dropTarget?.type === "item" ? -1 : focusedIndex,
406
457
  ].filter((index) => index >= 0);
407
- return mergePersistedKeysIntoVirtualRange(baseRange, persistedIndexes, stateProps.items.length, virtualizer, 80, {
408
- forceIncludeIndexes,
409
- forceIncludeMaxSpan: 320,
410
- });
458
+ return mergePersistedKeysIntoVirtualRange(
459
+ baseRange,
460
+ persistedIndexes,
461
+ stateProps.items.length,
462
+ virtualizer,
463
+ 80,
464
+ {
465
+ forceIncludeIndexes,
466
+ forceIncludeMaxSpan: 320,
467
+ },
468
+ );
411
469
  });
412
470
  createEffect(() => {
413
471
  if (!virtualizer || !parentCollectionRenderer?.isVirtualized) return;
414
- const getItemNodes = () => Array.from(state.collection()).filter((node) => node.type === 'item');
472
+ const getItemNodes = () =>
473
+ Array.from(state.collection()).filter((node) => node.type === "item");
415
474
  virtualizer.setDropTargetItemCountResolver(() => getItemNodes().length);
416
475
  virtualizer.setDropTargetIndexResolver((key) => {
417
476
  const index = getItemNodes().findIndex((node) => node.key === key);
@@ -422,7 +481,7 @@ export function ListBox<T>(props: ListBoxProps<T>): JSX.Element {
422
481
  if (!node) return target;
423
482
  return {
424
483
  ...target,
425
- key: typeof node.key === 'string' || typeof node.key === 'number' ? node.key : undefined,
484
+ key: typeof node.key === "string" || typeof node.key === "number" ? node.key : undefined,
426
485
  };
427
486
  });
428
487
  onCleanup(() => {
@@ -445,7 +504,7 @@ export function ListBox<T>(props: ListBoxProps<T>): JSX.Element {
445
504
  index: globalIndex++,
446
505
  }));
447
506
  return {
448
- type: 'section' as const,
507
+ type: "section" as const,
449
508
  section: entry,
450
509
  items: sectionItems,
451
510
  };
@@ -455,27 +514,31 @@ export function ListBox<T>(props: ListBoxProps<T>): JSX.Element {
455
514
  index: globalIndex++,
456
515
  };
457
516
  return {
458
- type: 'item' as const,
517
+ type: "item" as const,
459
518
  item: indexedItem,
460
519
  };
461
520
  });
462
521
  });
463
522
  const collectionRenderer = createMemo<CollectionRendererContextValue<unknown>>(() => ({
464
523
  ...parentCollectionRenderer,
465
- renderItem: (item) => props.children(item as T),
524
+ renderItem: (item) => local.children(item as T),
466
525
  renderDropIndicator: (index, position) =>
467
- dndDropIndicator(index, position) ?? parentCollectionRenderer?.renderDropIndicator?.(index, position),
526
+ dndDropIndicator(index, position) ??
527
+ parentCollectionRenderer?.renderDropIndicator?.(index, position),
468
528
  }));
469
529
 
470
530
  return (
471
531
  <ListBoxContext.Provider
472
- value={{
473
- state,
474
- isDisabled: resolveDisabled,
475
- dragAndDropHooks: local.dragAndDropHooks as DragAndDropHooks<unknown> | undefined,
476
- dragState: dragState(),
477
- dropState: dropState(),
478
- }}
532
+ value={
533
+ {
534
+ state,
535
+ isDisabled: resolveDisabled,
536
+ dragAndDropHooks: local.dragAndDropHooks as DragAndDropHooks<unknown> | undefined,
537
+ dragState: dragState(),
538
+ dropState: dropState(),
539
+ slots: local.slots,
540
+ } as ListBoxContextValue<unknown>
541
+ }
479
542
  >
480
543
  <ListBoxStateContext.Provider value={state}>
481
544
  <CollectionRendererContext.Provider value={collectionRenderer()}>
@@ -488,10 +551,12 @@ export function ListBox<T>(props: ListBoxProps<T>): JSX.Element {
488
551
  domProps(),
489
552
  cleanListBoxProps(),
490
553
  cleanFocusProps(),
491
- (droppableCollection()?.collectionProps as Record<string, unknown> | undefined) ?? {}
554
+ (droppableCollection()?.collectionProps as Record<string, unknown> | undefined) ??
555
+ {},
492
556
  )}
493
557
  ref={(el) => {
494
558
  setListRef(el);
559
+ assignRef(local.ref, el);
495
560
  }}
496
561
  class={renderProps.class()}
497
562
  style={renderProps.style()}
@@ -499,83 +564,104 @@ export function ListBox<T>(props: ListBoxProps<T>): JSX.Element {
499
564
  data-focus-visible={isFocusVisible() || undefined}
500
565
  data-disabled={resolveDisabled() || undefined}
501
566
  data-empty={isEmpty() || undefined}
567
+ data-layout={stateProps.layout}
568
+ data-orientation={stateProps.orientation}
502
569
  data-drop-target={isRootDropTarget() || undefined}
570
+ slot={local.slot}
503
571
  >
504
572
  <SharedElementTransition>
505
- {isEmpty() && local.renderEmptyState
506
- ? local.renderEmptyState()
507
- : hasSections()
508
- ? (
509
- <For each={sectionedRenderEntries()}>
510
- {(entry) =>
511
- entry.type === 'section'
512
- ? (
513
- <li role="presentation" data-section-wrapper>
514
- <Section class="solidaria-ListBox-section">
515
- {entry.section.title != null && (
516
- <Header class="solidaria-ListBox-sectionHeader">{entry.section.title}</Header>
517
- )}
518
- <Group class="solidaria-ListBox-sectionGroup">
519
- <ul role="group" aria-label={entry.section['aria-label']}>
520
- <For each={entry.items}>
521
- {(indexedItem) => (
522
- <>
523
- {collectionRenderer().renderDropIndicator?.(indexedItem.index, 'before')}
524
- {collectionRenderer().renderDropIndicator?.(indexedItem.index, 'on')}
525
- {props.children(indexedItem.item)}
526
- {collectionRenderer().renderDropIndicator?.(indexedItem.index, 'after')}
527
- </>
573
+ {isEmpty() && local.renderEmptyState ? (
574
+ <li role="option" style={{ display: "contents" }} data-empty-state>
575
+ {local.renderEmptyState()}
576
+ </li>
577
+ ) : hasSections() ? (
578
+ <For each={sectionedRenderEntries()}>
579
+ {(entry) =>
580
+ entry.type === "section" ? (
581
+ <li role="presentation" data-section-wrapper>
582
+ <Section class="solidaria-ListBox-section">
583
+ {entry.section.title != null && (
584
+ <Header class="solidaria-ListBox-sectionHeader">
585
+ {entry.section.title}
586
+ </Header>
587
+ )}
588
+ <Group class="solidaria-ListBox-sectionGroup">
589
+ <ul role="group" aria-label={entry.section["aria-label"]}>
590
+ <For each={entry.items}>
591
+ {(indexedItem) => (
592
+ <>
593
+ {collectionRenderer().renderDropIndicator?.(
594
+ indexedItem.index,
595
+ "before",
528
596
  )}
529
- </For>
530
- </ul>
531
- </Group>
532
- </Section>
533
- </li>
534
- )
535
- : (
536
- <>
537
- {collectionRenderer().renderDropIndicator?.(entry.item.index, 'before')}
538
- {collectionRenderer().renderDropIndicator?.(entry.item.index, 'on')}
539
- {props.children(entry.item.item)}
540
- {collectionRenderer().renderDropIndicator?.(entry.item.index, 'after')}
541
- </>
542
- )
543
- }
597
+ {collectionRenderer().renderDropIndicator?.(
598
+ indexedItem.index,
599
+ "on",
600
+ )}
601
+ {local.children(indexedItem.item)}
602
+ {collectionRenderer().renderDropIndicator?.(
603
+ indexedItem.index,
604
+ "after",
605
+ )}
606
+ </>
607
+ )}
608
+ </For>
609
+ </ul>
610
+ </Group>
611
+ </Section>
612
+ </li>
613
+ ) : (
614
+ <>
615
+ {collectionRenderer().renderDropIndicator?.(entry.item.index, "before")}
616
+ {collectionRenderer().renderDropIndicator?.(entry.item.index, "on")}
617
+ {local.children(entry.item.item)}
618
+ {collectionRenderer().renderDropIndicator?.(entry.item.index, "after")}
619
+ </>
620
+ )
621
+ }
622
+ </For>
623
+ ) : (
624
+ <>
625
+ {virtualRange()?.offsetTop ? (
626
+ <li
627
+ role="presentation"
628
+ aria-hidden="true"
629
+ style={{ height: `${virtualRange()!.offsetTop}px` }}
630
+ data-virtualizer-spacer="top"
631
+ />
632
+ ) : null}
633
+ <For each={visibleItems()}>
634
+ {(item, index) => {
635
+ const itemIndex = () => (virtualRange()?.start ?? 0) + index();
636
+ const beforeIndicator = () =>
637
+ collectionRenderer().renderDropIndicator?.(itemIndex(), "before");
638
+ const onIndicator = () =>
639
+ collectionRenderer().renderDropIndicator?.(itemIndex(), "on");
640
+ const afterIndicator = () =>
641
+ collectionRenderer().renderDropIndicator?.(itemIndex(), "after");
642
+ return (
643
+ <>
644
+ {beforeIndicator()}
645
+ {onIndicator()}
646
+ {local.children(item as T)}
647
+ {afterIndicator()}
648
+ </>
649
+ );
650
+ }}
544
651
  </For>
545
- )
546
- : (
547
- <>
548
- {virtualRange()?.offsetTop
549
- ? <li role="presentation" aria-hidden="true" style={{ height: `${virtualRange()!.offsetTop}px` }} data-virtualizer-spacer="top" />
550
- : null}
551
- <For each={visibleItems()}>
552
- {(item, index) => {
553
- const itemIndex = () => (virtualRange()?.start ?? 0) + index();
554
- const beforeIndicator = () => collectionRenderer().renderDropIndicator?.(itemIndex(), 'before');
555
- const onIndicator = () => collectionRenderer().renderDropIndicator?.(itemIndex(), 'on');
556
- const afterIndicator = () => collectionRenderer().renderDropIndicator?.(itemIndex(), 'after');
557
- return (
558
- <>
559
- {beforeIndicator()}
560
- {onIndicator()}
561
- {props.children(item as T)}
562
- {afterIndicator()}
563
- </>
564
- );
565
- }}
566
- </For>
567
- {virtualRange()?.offsetBottom
568
- ? <li role="presentation" aria-hidden="true" style={{ height: `${virtualRange()!.offsetBottom}px` }} data-virtualizer-spacer="bottom" />
569
- : null}
570
- </>
571
- )
572
- }
652
+ {virtualRange()?.offsetBottom ? (
653
+ <li
654
+ role="presentation"
655
+ aria-hidden="true"
656
+ style={{ height: `${virtualRange()!.offsetBottom}px` }}
657
+ data-virtualizer-spacer="bottom"
658
+ />
659
+ ) : null}
660
+ </>
661
+ )}
573
662
  </SharedElementTransition>
574
663
  {local.hasMore && local.onLoadMore && (
575
- <ListBoxLoadMoreItem
576
- onLoadMore={local.onLoadMore}
577
- isLoading={local.isLoading}
578
- />
664
+ <ListBoxLoadMoreItem onLoadMore={local.onLoadMore} isLoading={local.isLoading} />
579
665
  )}
580
666
  </ul>
581
667
  </>
@@ -590,120 +676,123 @@ export function ListBox<T>(props: ListBoxProps<T>): JSX.Element {
590
676
  */
591
677
  export function ListBoxOption<T>(props: ListBoxOptionProps<T>): JSX.Element {
592
678
  const [local, ariaProps] = splitProps(props, [
593
- 'class',
594
- 'style',
595
- 'slot',
596
- 'id',
597
- 'item',
598
- 'textValue',
679
+ "class",
680
+ "style",
681
+ "slot",
682
+ "id",
683
+ "item",
684
+ "textValue",
685
+ "ref",
599
686
  ]);
600
687
 
601
- // Get state from context
602
688
  const context = useContext(ListBoxStateContext);
603
689
  if (!context) {
604
- throw new Error('ListBoxOption must be used within a ListBox');
690
+ throw new Error("ListBoxOption must be used within a ListBox");
605
691
  }
606
692
  const state = context as ListState<T>;
607
693
  const listContext = useContext(ListBoxContext) as ListBoxContextValue<T> | null;
608
694
  const [ref, setRef] = createSignal<HTMLLIElement | null>(null);
609
695
 
610
- // Create option aria props
611
696
  const optionAria = createOption<T>(
612
697
  {
613
698
  key: local.id,
614
699
  get isDisabled() {
615
700
  return Boolean(ariaProps.isDisabled || listContext?.isDisabled());
616
701
  },
617
- get 'aria-label'() {
618
- return ariaProps['aria-label'] ?? local.textValue;
702
+ get "aria-label"() {
703
+ return ariaProps["aria-label"] ?? local.textValue;
619
704
  },
620
705
  get shouldSelectOnPressUp() {
621
706
  return ariaProps.shouldSelectOnPressUp;
622
707
  },
708
+ get shouldFocusOnHover() {
709
+ return ariaProps.shouldFocusOnHover;
710
+ },
711
+ get onHoverStart() {
712
+ return ariaProps.onHoverStart;
713
+ },
714
+ get onHoverEnd() {
715
+ return ariaProps.onHoverEnd;
716
+ },
717
+ get onHoverChange() {
718
+ return ariaProps.onHoverChange;
719
+ },
623
720
  },
624
- state
721
+ state,
625
722
  );
626
723
 
627
- // Create hover
628
- const { isHovered, hoverProps } = createHover({
629
- get isDisabled() {
630
- return optionAria.isDisabled();
631
- },
632
- });
633
-
634
- // Render props values
635
724
  const renderValues = createMemo<ListBoxOptionRenderProps>(() => ({
636
725
  isSelected: optionAria.isSelected(),
637
726
  isFocused: optionAria.isFocused(),
638
727
  isFocusVisible: optionAria.isFocusVisible(),
639
728
  isPressed: optionAria.isPressed(),
640
- isHovered: isHovered(),
729
+ isHovered: optionAria.isHovered(),
641
730
  isDisabled: optionAria.isDisabled(),
642
731
  }));
643
732
 
644
- // Resolve render props
645
733
  const renderProps = useRenderProps(
646
734
  {
647
735
  children: props.children,
648
736
  class: local.class,
649
737
  style: local.style,
650
- defaultClassName: 'solidaria-ListBox-option',
738
+ defaultClassName: "solidaria-ListBox-option",
651
739
  },
652
- renderValues
740
+ renderValues,
653
741
  );
654
742
  const hasPrimitiveLabel = () => {
655
- return typeof props.children === 'string' || typeof props.children === 'number';
743
+ return typeof props.children === "string" || typeof props.children === "number";
656
744
  };
657
745
 
658
746
  const selectionIndicatorContext = createMemo<SelectionIndicatorContextValue>(() => ({
659
747
  isSelected: optionAria.isSelected,
660
748
  }));
661
749
  const draggableItem = createMemo(() => {
662
- if (!listContext?.dragAndDropHooks?.useDraggableItem || !listContext.dragState) return undefined;
750
+ if (!listContext?.dragAndDropHooks?.useDraggableItem || !listContext.dragState)
751
+ return undefined;
663
752
  return listContext.dragAndDropHooks.useDraggableItem(
664
753
  {
665
754
  key: local.id as string | number,
666
755
  },
667
- listContext.dragState as Parameters<NonNullable<DragAndDropHooks<T>['useDraggableItem']>>[1]
756
+ listContext.dragState as Parameters<NonNullable<DragAndDropHooks<T>["useDraggableItem"]>>[1],
668
757
  );
669
758
  });
670
759
  const droppableItem = createMemo(() => {
671
- if (!listContext?.dragAndDropHooks?.useDroppableItem || !listContext.dropState) return undefined;
760
+ if (!listContext?.dragAndDropHooks?.useDroppableItem || !listContext.dropState)
761
+ return undefined;
672
762
  return listContext.dragAndDropHooks.useDroppableItem(
673
763
  {
674
764
  key: local.id as string | number,
675
765
  },
676
- listContext.dropState as Parameters<NonNullable<DragAndDropHooks<T>['useDroppableItem']>>[1],
677
- () => ref()
766
+ listContext.dropState as Parameters<NonNullable<DragAndDropHooks<T>["useDroppableItem"]>>[1],
767
+ () => ref(),
678
768
  );
679
769
  });
680
770
 
681
- // Remove ref from spread props
682
771
  const cleanOptionProps = () => {
683
772
  const {
684
773
  ref: _ref1,
685
- 'aria-describedby': _ariaDescribedby,
774
+ "aria-describedby": _ariaDescribedby,
686
775
  ...rest
687
776
  } = optionAria.optionProps as Record<string, unknown>;
688
- if (!hasPrimitiveLabel() && rest['aria-label'] == null) {
689
- delete rest['aria-labelledby'];
777
+ if (!hasPrimitiveLabel() && rest["aria-label"] == null) {
778
+ delete rest["aria-labelledby"];
690
779
  }
691
780
  return rest;
692
781
  };
693
- const cleanHoverProps = () => {
694
- const { ref: _ref2, ...rest } = hoverProps as Record<string, unknown>;
695
- return rest;
696
- };
782
+ const domProps = () => filterDOMProps(ariaProps as Record<string, unknown>, { global: true });
697
783
 
698
784
  return (
699
785
  <SelectionIndicatorContext.Provider value={selectionIndicatorContext()}>
700
786
  <li
701
- ref={setRef}
787
+ ref={(el) => {
788
+ setRef(el);
789
+ assignRef(local.ref, el);
790
+ }}
702
791
  {...mergeProps(
792
+ domProps(),
703
793
  cleanOptionProps(),
704
- cleanHoverProps(),
705
794
  (draggableItem()?.dragProps as Record<string, unknown> | undefined) ?? {},
706
- (droppableItem()?.dropProps as Record<string, unknown> | undefined) ?? {}
795
+ (droppableItem()?.dropProps as Record<string, unknown> | undefined) ?? {},
707
796
  )}
708
797
  class={renderProps.class()}
709
798
  style={renderProps.style()}
@@ -711,14 +800,17 @@ export function ListBoxOption<T>(props: ListBoxOptionProps<T>): JSX.Element {
711
800
  data-focused={optionAria.isFocused() || undefined}
712
801
  data-focus-visible={optionAria.isFocusVisible() || undefined}
713
802
  data-pressed={optionAria.isPressed() || undefined}
714
- data-hovered={isHovered() || undefined}
803
+ data-hovered={optionAria.isHovered() || undefined}
715
804
  data-disabled={optionAria.isDisabled() || undefined}
716
805
  data-dragging={draggableItem()?.isDragging || undefined}
717
806
  data-drop-target={droppableItem()?.isDropTarget || undefined}
807
+ slot={local.slot}
718
808
  >
719
- {hasPrimitiveLabel()
720
- ? <span {...optionAria.labelProps}>{renderProps.renderChildren()}</span>
721
- : renderProps.renderChildren()}
809
+ {hasPrimitiveLabel() ? (
810
+ <span {...optionAria.labelProps}>{renderProps.renderChildren()}</span>
811
+ ) : (
812
+ renderProps.renderChildren()
813
+ )}
722
814
  </li>
723
815
  </SelectionIndicatorContext.Provider>
724
816
  );
@@ -728,7 +820,7 @@ export function ListBoxOption<T>(props: ListBoxOptionProps<T>): JSX.Element {
728
820
  * Load more sentinel item for listbox collections.
729
821
  */
730
822
  export function ListBoxLoadMoreItem(props: ListBoxLoadMoreItemProps): JSX.Element {
731
- let ref: HTMLLIElement | undefined;
823
+ let sentinelRef: HTMLDivElement | undefined;
732
824
  const [isPending, setIsPending] = createSignal(false);
733
825
 
734
826
  const isLoading = () => !!props.isLoading || isPending();
@@ -744,44 +836,52 @@ export function ListBoxLoadMoreItem(props: ListBoxLoadMoreItemProps): JSX.Elemen
744
836
  };
745
837
 
746
838
  createEffect(() => {
747
- if (!ref || typeof IntersectionObserver !== 'function') return;
748
-
749
- const observer = new IntersectionObserver((entries) => {
750
- const entry = entries[0];
751
- if (entry?.isIntersecting) {
752
- void triggerLoadMore();
753
- }
754
- });
839
+ if (!sentinelRef || typeof IntersectionObserver !== "function") return;
840
+
841
+ const offset = props.scrollOffset ?? 1;
842
+ const margin = `0px 0px ${100 * offset}% 0px`;
843
+ const observer = new IntersectionObserver(
844
+ (entries) => {
845
+ if (entries[0]?.isIntersecting) {
846
+ void triggerLoadMore();
847
+ }
848
+ },
849
+ { rootMargin: margin },
850
+ );
755
851
 
756
- observer.observe(ref);
852
+ observer.observe(sentinelRef);
757
853
  return () => observer.disconnect();
758
854
  });
759
855
 
760
856
  const renderProps = useRenderProps(
761
857
  {
762
- children: props.children ?? (() => (isLoading() ? 'Loading more...' : 'Load more')),
858
+ children: props.children ?? (() => (isLoading() ? "Loading more..." : "Load more")),
763
859
  class: props.class,
764
860
  style: props.style,
765
- defaultClassName: 'solidaria-ListBox-loadMore',
861
+ defaultClassName: "solidaria-ListBox-loadMore",
766
862
  },
767
- () => ({ isLoading: isLoading() })
863
+ () => ({ isLoading: isLoading() }),
768
864
  );
769
865
 
770
866
  return (
771
- <li
772
- ref={ref}
773
- role="option"
774
- aria-disabled={true}
775
- tabIndex={0}
776
- onFocus={() => {
777
- void triggerLoadMore();
778
- }}
779
- class={renderProps.class()}
780
- style={renderProps.style()}
781
- data-loading={isLoading() || undefined}
782
- >
783
- {renderProps.renderChildren()}
784
- </li>
867
+ <>
868
+ <li style={{ position: "relative", width: 0, height: 0, overflow: "hidden" }} inert>
869
+ <div ref={sentinelRef} style={{ position: "absolute", height: "1px", width: "1px" }} />
870
+ </li>
871
+ <li
872
+ role="option"
873
+ aria-disabled={true}
874
+ tabIndex={0}
875
+ onFocus={() => {
876
+ void triggerLoadMore();
877
+ }}
878
+ class={renderProps.class()}
879
+ style={renderProps.style()}
880
+ data-loading={isLoading() || undefined}
881
+ >
882
+ {renderProps.renderChildren()}
883
+ </li>
884
+ </>
785
885
  );
786
886
  }
787
887
 
@@ -792,6 +892,5 @@ export function ListBoxSection(props: ListBoxSectionProps): JSX.Element {
792
892
  return <Section {...props} />;
793
893
  }
794
894
 
795
- // Attach Option as a static property
796
895
  ListBox.Option = ListBoxOption;
797
896
  ListBox.LoadMoreItem = ListBoxLoadMoreItem;