@proyecto-viviana/solidaria-components 0.2.5 → 0.2.9

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 (194) hide show
  1. package/LICENSE +21 -0
  2. package/dist/ActionBar.d.ts +71 -0
  3. package/dist/ActionBar.d.ts.map +1 -0
  4. package/dist/ActionGroup.d.ts +74 -0
  5. package/dist/ActionGroup.d.ts.map +1 -0
  6. package/dist/Alert.d.ts +70 -0
  7. package/dist/Alert.d.ts.map +1 -0
  8. package/dist/Breadcrumbs.d.ts +10 -2
  9. package/dist/Breadcrumbs.d.ts.map +1 -1
  10. package/dist/Button.d.ts +4 -0
  11. package/dist/Button.d.ts.map +1 -1
  12. package/dist/Calendar.d.ts +13 -0
  13. package/dist/Calendar.d.ts.map +1 -1
  14. package/dist/Checkbox.d.ts +2 -2
  15. package/dist/Checkbox.d.ts.map +1 -1
  16. package/dist/Collection.d.ts +125 -0
  17. package/dist/Collection.d.ts.map +1 -0
  18. package/dist/Color.d.ts +114 -2
  19. package/dist/Color.d.ts.map +1 -1
  20. package/dist/ColorEditor.d.ts +42 -0
  21. package/dist/ColorEditor.d.ts.map +1 -0
  22. package/dist/ComboBox.d.ts +64 -0
  23. package/dist/ComboBox.d.ts.map +1 -1
  24. package/dist/ContextualHelpTrigger.d.ts +40 -0
  25. package/dist/ContextualHelpTrigger.d.ts.map +1 -0
  26. package/dist/DateField.d.ts +27 -2
  27. package/dist/DateField.d.ts.map +1 -1
  28. package/dist/DatePicker.d.ts +67 -2
  29. package/dist/DatePicker.d.ts.map +1 -1
  30. package/dist/Dialog.d.ts.map +1 -1
  31. package/dist/Disclosure.d.ts +2 -0
  32. package/dist/Disclosure.d.ts.map +1 -1
  33. package/dist/DragAndDrop.d.ts +80 -0
  34. package/dist/DragAndDrop.d.ts.map +1 -0
  35. package/dist/DragPreview.d.ts +14 -0
  36. package/dist/DragPreview.d.ts.map +1 -0
  37. package/dist/DropZone.d.ts +27 -0
  38. package/dist/DropZone.d.ts.map +1 -0
  39. package/dist/FieldError.d.ts +23 -0
  40. package/dist/FieldError.d.ts.map +1 -0
  41. package/dist/FileTrigger.d.ts +26 -0
  42. package/dist/FileTrigger.d.ts.map +1 -0
  43. package/dist/Focusable.d.ts +27 -0
  44. package/dist/Focusable.d.ts.map +1 -0
  45. package/dist/Form.d.ts +27 -0
  46. package/dist/Form.d.ts.map +1 -0
  47. package/dist/GridList.d.ts +40 -1
  48. package/dist/GridList.d.ts.map +1 -1
  49. package/dist/Icon.d.ts +57 -0
  50. package/dist/Icon.d.ts.map +1 -0
  51. package/dist/Keyboard.d.ts +13 -0
  52. package/dist/Keyboard.d.ts.map +1 -0
  53. package/dist/Link.d.ts.map +1 -1
  54. package/dist/ListBox.d.ts +43 -1
  55. package/dist/ListBox.d.ts.map +1 -1
  56. package/dist/ListDropTargetDelegate.d.ts +38 -0
  57. package/dist/ListDropTargetDelegate.d.ts.map +1 -0
  58. package/dist/Menu.d.ts +20 -2
  59. package/dist/Menu.d.ts.map +1 -1
  60. package/dist/Meter.d.ts +2 -2
  61. package/dist/Meter.d.ts.map +1 -1
  62. package/dist/Modal.d.ts +2 -0
  63. package/dist/Modal.d.ts.map +1 -1
  64. package/dist/NumberField.d.ts +2 -0
  65. package/dist/NumberField.d.ts.map +1 -1
  66. package/dist/Popover.d.ts +4 -2
  67. package/dist/Popover.d.ts.map +1 -1
  68. package/dist/Pressable.d.ts +27 -0
  69. package/dist/Pressable.d.ts.map +1 -0
  70. package/dist/ProgressBar.d.ts +2 -2
  71. package/dist/ProgressBar.d.ts.map +1 -1
  72. package/dist/RadioGroup.d.ts.map +1 -1
  73. package/dist/RangeCalendar.d.ts +5 -0
  74. package/dist/RangeCalendar.d.ts.map +1 -1
  75. package/dist/RouterProvider.d.ts +75 -0
  76. package/dist/RouterProvider.d.ts.map +1 -0
  77. package/dist/SearchField.d.ts +2 -3
  78. package/dist/SearchField.d.ts.map +1 -1
  79. package/dist/Select.d.ts +11 -0
  80. package/dist/Select.d.ts.map +1 -1
  81. package/dist/SelectionIndicator.d.ts +30 -0
  82. package/dist/SelectionIndicator.d.ts.map +1 -0
  83. package/dist/SharedElementTransition.d.ts +39 -0
  84. package/dist/SharedElementTransition.d.ts.map +1 -0
  85. package/dist/Slider.d.ts +6 -3
  86. package/dist/Slider.d.ts.map +1 -1
  87. package/dist/Table.d.ts +39 -0
  88. package/dist/Table.d.ts.map +1 -1
  89. package/dist/Tabs.d.ts +4 -3
  90. package/dist/Tabs.d.ts.map +1 -1
  91. package/dist/TagGroup.d.ts +12 -2
  92. package/dist/TagGroup.d.ts.map +1 -1
  93. package/dist/Text.d.ts +10 -0
  94. package/dist/Text.d.ts.map +1 -0
  95. package/dist/TextField.d.ts +4 -0
  96. package/dist/TextField.d.ts.map +1 -1
  97. package/dist/TimeField.d.ts +26 -1
  98. package/dist/TimeField.d.ts.map +1 -1
  99. package/dist/Toast.d.ts.map +1 -1
  100. package/dist/ToggleButton.d.ts +30 -0
  101. package/dist/ToggleButton.d.ts.map +1 -0
  102. package/dist/ToggleButtonGroup.d.ts +33 -0
  103. package/dist/ToggleButtonGroup.d.ts.map +1 -0
  104. package/dist/Toolbar.d.ts.map +1 -1
  105. package/dist/Tooltip.d.ts +9 -0
  106. package/dist/Tooltip.d.ts.map +1 -1
  107. package/dist/Tree.d.ts +44 -2
  108. package/dist/Tree.d.ts.map +1 -1
  109. package/dist/Virtualizer.d.ts +61 -0
  110. package/dist/Virtualizer.d.ts.map +1 -0
  111. package/dist/VirtualizerLayouts.d.ts +82 -0
  112. package/dist/VirtualizerLayouts.d.ts.map +1 -0
  113. package/dist/VisuallyHidden.d.ts +3 -1
  114. package/dist/VisuallyHidden.d.ts.map +1 -1
  115. package/dist/contexts.d.ts +1 -0
  116. package/dist/contexts.d.ts.map +1 -1
  117. package/dist/index.d.ts +57 -25
  118. package/dist/index.d.ts.map +1 -1
  119. package/dist/index.js +13961 -5946
  120. package/dist/index.js.map +1 -7
  121. package/dist/index.ssr.js +9612 -2401
  122. package/dist/index.ssr.js.map +1 -7
  123. package/dist/useDragAndDrop.d.ts +93 -0
  124. package/dist/useDragAndDrop.d.ts.map +1 -0
  125. package/dist/utils.d.ts +7 -1
  126. package/dist/utils.d.ts.map +1 -1
  127. package/dist/virtualizer/Layout.d.ts +79 -0
  128. package/dist/virtualizer/Layout.d.ts.map +1 -0
  129. package/package.json +8 -6
  130. package/src/ActionBar.tsx +248 -0
  131. package/src/ActionGroup.tsx +285 -0
  132. package/src/Alert.tsx +177 -0
  133. package/src/Autocomplete.tsx +1 -1
  134. package/src/Breadcrumbs.tsx +103 -17
  135. package/src/Button.tsx +65 -21
  136. package/src/Calendar.tsx +179 -53
  137. package/src/Checkbox.tsx +1 -2
  138. package/src/Collection.tsx +341 -0
  139. package/src/Color.tsx +652 -34
  140. package/src/ColorEditor.tsx +231 -0
  141. package/src/ComboBox.tsx +315 -81
  142. package/src/ContextualHelpTrigger.tsx +183 -0
  143. package/src/DateField.tsx +93 -19
  144. package/src/DatePicker.tsx +495 -25
  145. package/src/Dialog.tsx +40 -9
  146. package/src/Disclosure.tsx +33 -27
  147. package/src/DragAndDrop.tsx +334 -0
  148. package/src/DragPreview.tsx +45 -0
  149. package/src/DropZone.tsx +213 -0
  150. package/src/FieldError.tsx +67 -0
  151. package/src/FileTrigger.tsx +83 -0
  152. package/src/Focusable.tsx +106 -0
  153. package/src/Form.tsx +85 -0
  154. package/src/GridList.tsx +379 -41
  155. package/src/Icon.tsx +154 -0
  156. package/src/Keyboard.tsx +26 -0
  157. package/src/Link.tsx +14 -1
  158. package/src/ListBox.tsx +484 -33
  159. package/src/ListDropTargetDelegate.ts +282 -0
  160. package/src/Menu.tsx +388 -35
  161. package/src/Meter.tsx +7 -3
  162. package/src/Modal.tsx +32 -4
  163. package/src/NumberField.tsx +163 -43
  164. package/src/Popover.tsx +136 -180
  165. package/src/Pressable.tsx +108 -0
  166. package/src/ProgressBar.tsx +7 -3
  167. package/src/RadioGroup.tsx +35 -25
  168. package/src/RangeCalendar.tsx +100 -68
  169. package/src/RouterProvider.tsx +240 -0
  170. package/src/SearchField.tsx +142 -34
  171. package/src/Select.tsx +221 -73
  172. package/src/SelectionIndicator.tsx +105 -0
  173. package/src/SharedElementTransition.tsx +258 -0
  174. package/src/Slider.tsx +16 -6
  175. package/src/Table.tsx +417 -57
  176. package/src/Tabs.tsx +68 -35
  177. package/src/TagGroup.tsx +121 -36
  178. package/src/Text.tsx +18 -0
  179. package/src/TextField.tsx +25 -8
  180. package/src/TimeField.tsx +101 -151
  181. package/src/Toast.tsx +108 -14
  182. package/src/ToggleButton.tsx +159 -0
  183. package/src/ToggleButtonGroup.tsx +136 -0
  184. package/src/Toolbar.tsx +14 -8
  185. package/src/Tooltip.tsx +108 -19
  186. package/src/Tree.tsx +1143 -87
  187. package/src/Virtualizer.tsx +702 -0
  188. package/src/VirtualizerLayouts.ts +265 -0
  189. package/src/VisuallyHidden.tsx +15 -21
  190. package/src/contexts.ts +1 -0
  191. package/src/index.ts +1057 -620
  192. package/src/useDragAndDrop.ts +351 -0
  193. package/src/utils.tsx +37 -3
  194. package/src/virtualizer/Layout.ts +200 -0
package/src/Dialog.tsx CHANGED
@@ -8,6 +8,7 @@
8
8
  import {
9
9
  type JSX,
10
10
  createContext,
11
+ createEffect,
11
12
  createMemo,
12
13
  createUniqueId,
13
14
  splitProps,
@@ -109,10 +110,18 @@ export function DialogTrigger(props: DialogTriggerProps): JSX.Element {
109
110
  () => triggerRef
110
111
  )
111
112
 
113
+ const setTriggerRef = (el: HTMLElement | null) => {
114
+ if (!el) return
115
+ if (!triggerRef || !triggerRef.isConnected) {
116
+ triggerRef = el
117
+ }
118
+ }
119
+
112
120
  // Context value - memoized to avoid unnecessary re-renders
113
121
  const contextValue = createMemo(() => ({
114
122
  state,
115
123
  triggerRef: () => triggerRef,
124
+ setTriggerRef,
116
125
  triggerId,
117
126
  }))
118
127
 
@@ -154,8 +163,7 @@ export function Dialog(props: DialogProps): JSX.Element {
154
163
  return ariaProps['aria-label']
155
164
  },
156
165
  get 'aria-labelledby'() {
157
- // Use provided labelledby, or fall back to trigger id if no title
158
- return ariaProps['aria-labelledby'] ?? triggerContext?.triggerId
166
+ return ariaProps['aria-labelledby']
159
167
  },
160
168
  get 'aria-describedby'() {
161
169
  return ariaProps['aria-describedby']
@@ -172,7 +180,10 @@ export function Dialog(props: DialogProps): JSX.Element {
172
180
 
173
181
  const close = () => {
174
182
  local.onClose?.()
175
- overlayState?.close()
183
+ if (overlayState) {
184
+ overlayState.close()
185
+ return
186
+ }
176
187
  triggerContext?.state.close()
177
188
  }
178
189
 
@@ -233,26 +244,46 @@ export function Heading(props: HeadingProps): JSX.Element {
233
244
  const dialogContext = useContext(DialogContext)
234
245
  const level = () => props.level ?? 2
235
246
  const id = () => dialogContext?.titleId
247
+ let headingRef: HTMLHeadingElement | undefined
248
+
249
+ createEffect(() => {
250
+ const el = headingRef
251
+ if (!el) return
252
+
253
+ const contextId = id()
254
+ if (contextId) {
255
+ el.id = contextId
256
+ return
257
+ }
258
+
259
+ if (!el.id) {
260
+ const dialog = el.closest('[role="dialog"],[role="alertdialog"]')
261
+ const labelledBy = dialog?.getAttribute('aria-labelledby')
262
+ if (labelledBy && !el.ownerDocument.getElementById(labelledBy)) {
263
+ el.id = labelledBy
264
+ }
265
+ }
266
+ })
236
267
 
237
268
  return (
238
269
  <Switch>
239
270
  <Match when={level() === 1}>
240
- <h1 id={id()} class={props.class}>{props.children}</h1>
271
+ <h1 ref={headingRef} id={id()} class={props.class}>{props.children}</h1>
241
272
  </Match>
242
273
  <Match when={level() === 2}>
243
- <h2 id={id()} class={props.class}>{props.children}</h2>
274
+ <h2 ref={headingRef} id={id()} class={props.class}>{props.children}</h2>
244
275
  </Match>
245
276
  <Match when={level() === 3}>
246
- <h3 id={id()} class={props.class}>{props.children}</h3>
277
+ <h3 ref={headingRef} id={id()} class={props.class}>{props.children}</h3>
247
278
  </Match>
248
279
  <Match when={level() === 4}>
249
- <h4 id={id()} class={props.class}>{props.children}</h4>
280
+ <h4 ref={headingRef} id={id()} class={props.class}>{props.children}</h4>
250
281
  </Match>
251
282
  <Match when={level() === 5}>
252
- <h5 id={id()} class={props.class}>{props.children}</h5>
283
+ <h5 ref={headingRef} id={id()} class={props.class}>{props.children}</h5>
253
284
  </Match>
254
285
  <Match when={level() === 6}>
255
- <h6 id={id()} class={props.class}>{props.children}</h6>
286
+ <h6 ref={headingRef} id={id()} class={props.class}>{props.children}</h6>
256
287
  </Match>
257
288
  </Switch>
258
289
  )
@@ -107,6 +107,7 @@ interface DisclosureContextValue {
107
107
  }
108
108
 
109
109
  export const DisclosureContext = createContext<DisclosureContextValue | null>(null);
110
+ export const DisclosureStateContext = createContext<DisclosureState | null>(null);
110
111
 
111
112
  export function useDisclosureContext(): DisclosureContextValue | null {
112
113
  return useContext(DisclosureContext);
@@ -117,6 +118,7 @@ interface DisclosureGroupContextValue {
117
118
  }
118
119
 
119
120
  export const DisclosureGroupContext = createContext<DisclosureGroupContextValue | null>(null);
121
+ export const DisclosureGroupStateContext = createContext<DisclosureGroupState | null>(null);
120
122
 
121
123
  export function useDisclosureGroupContext(): DisclosureGroupContextValue | null {
122
124
  return useContext(DisclosureGroupContext);
@@ -160,17 +162,17 @@ export function DisclosureGroup(props: DisclosureGroupProps): JSX.Element {
160
162
  ]);
161
163
 
162
164
  // Create group state
163
- const state = createDisclosureGroupState({
165
+ const state = createDisclosureGroupState(() => ({
164
166
  allowsMultipleExpanded: local.allowsMultipleExpanded,
165
167
  isDisabled: local.isDisabled,
166
168
  expandedKeys: local.expandedKeys,
167
169
  defaultExpandedKeys: local.defaultExpandedKeys,
168
170
  onExpandedChange: local.onExpandedChange,
169
- });
171
+ }));
170
172
 
171
173
  // Create group accessibility props
172
174
  const { groupProps } = createDisclosureGroup(
173
- { isDisabled: local.isDisabled },
175
+ () => ({ isDisabled: local.isDisabled }),
174
176
  state
175
177
  );
176
178
 
@@ -199,17 +201,19 @@ export function DisclosureGroup(props: DisclosureGroupProps): JSX.Element {
199
201
  const { ref: _ref, ...cleanGroupProps } = groupProps as Record<string, unknown>;
200
202
 
201
203
  return (
202
- <DisclosureGroupContext.Provider value={contextValue}>
203
- <div
204
- {...domProps()}
205
- {...cleanGroupProps}
206
- class={renderProps.class()}
207
- style={renderProps.style()}
208
- data-disabled={dataAttr(state.isDisabled)}
209
- >
210
- {props.children}
211
- </div>
212
- </DisclosureGroupContext.Provider>
204
+ <DisclosureGroupStateContext.Provider value={state}>
205
+ <DisclosureGroupContext.Provider value={contextValue}>
206
+ <div
207
+ {...domProps()}
208
+ {...cleanGroupProps}
209
+ class={renderProps.class()}
210
+ style={renderProps.style()}
211
+ data-disabled={dataAttr(state.isDisabled)}
212
+ >
213
+ {props.children}
214
+ </div>
215
+ </DisclosureGroupContext.Provider>
216
+ </DisclosureGroupStateContext.Provider>
213
217
  );
214
218
  }
215
219
 
@@ -315,19 +319,21 @@ export function Disclosure(props: DisclosureProps): JSX.Element {
315
319
  };
316
320
 
317
321
  return (
318
- <DisclosureContext.Provider value={contextValue}>
319
- <DisclosurePanelRefContext.Provider value={setPanelRef}>
320
- <div
321
- {...domProps()}
322
- class={renderProps.class()}
323
- style={renderProps.style()}
324
- data-expanded={dataAttr(state.isExpanded())}
325
- data-disabled={dataAttr(isDisabled())}
326
- >
327
- {props.children}
328
- </div>
329
- </DisclosurePanelRefContext.Provider>
330
- </DisclosureContext.Provider>
322
+ <DisclosureStateContext.Provider value={state}>
323
+ <DisclosureContext.Provider value={contextValue}>
324
+ <DisclosurePanelRefContext.Provider value={setPanelRef}>
325
+ <div
326
+ {...domProps()}
327
+ class={renderProps.class()}
328
+ style={renderProps.style()}
329
+ data-expanded={dataAttr(state.isExpanded())}
330
+ data-disabled={dataAttr(isDisabled())}
331
+ >
332
+ {props.children}
333
+ </div>
334
+ </DisclosurePanelRefContext.Provider>
335
+ </DisclosureContext.Provider>
336
+ </DisclosureStateContext.Provider>
331
337
  );
332
338
  }
333
339
 
@@ -0,0 +1,334 @@
1
+ /**
2
+ * Drag and drop composition helpers for solidaria-components.
3
+ *
4
+ * Compatibility target: react-aria-components DragAndDrop exports.
5
+ */
6
+
7
+ import {
8
+ type JSX,
9
+ type Accessor,
10
+ createContext,
11
+ createMemo,
12
+ useContext,
13
+ } from 'solid-js';
14
+ import type {
15
+ DragTypes,
16
+ DropOperation,
17
+ DropTarget,
18
+ ItemDropTarget,
19
+ Key,
20
+ } from '@proyecto-viviana/solid-stately';
21
+ import type { DragAndDropHooks } from './useDragAndDrop';
22
+ import {
23
+ type ClassNameOrFunction,
24
+ type StyleOrFunction,
25
+ type SlotProps,
26
+ useRenderProps,
27
+ dataAttr,
28
+ } from './utils';
29
+
30
+ export interface DragAndDropContextValue {
31
+ dragAndDropHooks?: DragAndDropHooks<unknown>;
32
+ dragState?: unknown;
33
+ dropState?: {
34
+ target?: DropTarget | null;
35
+ isDropTarget?: (target: DropTarget) => boolean;
36
+ };
37
+ }
38
+
39
+ export const DragAndDropContext = createContext<DragAndDropContextValue>({});
40
+
41
+ export interface DropIndicatorRenderProps {
42
+ isDropTarget: boolean;
43
+ }
44
+
45
+ export interface DropIndicatorProps extends SlotProps {
46
+ target: ItemDropTarget;
47
+ children?: JSX.Element | ((props: DropIndicatorRenderProps) => JSX.Element);
48
+ class?: ClassNameOrFunction<DropIndicatorRenderProps>;
49
+ style?: StyleOrFunction<DropIndicatorRenderProps>;
50
+ }
51
+
52
+ interface DropIndicatorContextValue {
53
+ render: (props: DropIndicatorProps) => JSX.Element;
54
+ }
55
+
56
+ export const DropIndicatorContext = createContext<DropIndicatorContextValue | null>(null);
57
+
58
+ function DefaultDropIndicator(props: DropIndicatorProps): JSX.Element {
59
+ const dnd = useContext(DragAndDropContext);
60
+ const isDropTarget = createMemo(() => {
61
+ const target = props.target;
62
+ return dnd.dropState?.isDropTarget?.(target) ?? false;
63
+ });
64
+
65
+ const renderProps = useRenderProps(
66
+ {
67
+ children: props.children,
68
+ class: props.class,
69
+ style: props.style,
70
+ defaultClassName: 'solidaria-DropIndicator',
71
+ },
72
+ () => ({
73
+ isDropTarget: isDropTarget(),
74
+ })
75
+ );
76
+
77
+ return (
78
+ <div
79
+ role="option"
80
+ aria-disabled={true}
81
+ class={renderProps.class()}
82
+ style={renderProps.style()}
83
+ data-drop-target={dataAttr(isDropTarget())}
84
+ >
85
+ {renderProps.renderChildren()}
86
+ </div>
87
+ );
88
+ }
89
+
90
+ export function DropIndicator(props: DropIndicatorProps): JSX.Element {
91
+ const context = useContext(DropIndicatorContext);
92
+ if (context) return context.render(props);
93
+ return <DefaultDropIndicator {...props} />;
94
+ }
95
+
96
+ export function useRenderDropIndicator(
97
+ hooksOrDropState?:
98
+ | Pick<DragAndDropHooks<unknown>, 'renderDropIndicator' | 'isVirtualDragging' | 'useDropIndicator'>
99
+ | {
100
+ target?: DropTarget | null;
101
+ isDropTarget?: ((target: DropTarget) => boolean) | boolean;
102
+ },
103
+ maybeDropState?: {
104
+ target?: DropTarget | null;
105
+ isDropTarget?: ((target: DropTarget) => boolean) | boolean;
106
+ }
107
+ ): ((target: ItemDropTarget) => JSX.Element | undefined) | undefined {
108
+ const looksLikeDropState = (
109
+ value: unknown
110
+ ): value is {
111
+ target?: DropTarget | null;
112
+ isDropTarget?: ((target: DropTarget) => boolean) | boolean;
113
+ } => {
114
+ return Boolean(
115
+ value &&
116
+ typeof value === 'object' &&
117
+ ('isDropTarget' in (value as Record<string, unknown>) || 'target' in (value as Record<string, unknown>))
118
+ );
119
+ };
120
+
121
+ const dragAndDropHooks = looksLikeDropState(hooksOrDropState)
122
+ ? undefined
123
+ : hooksOrDropState;
124
+ const dropState = looksLikeDropState(hooksOrDropState) ? hooksOrDropState : maybeDropState;
125
+
126
+ // RAC only renders collection indicators when drop hooks are present.
127
+ if (dragAndDropHooks && !dragAndDropHooks.useDropIndicator) return undefined;
128
+ if (!dropState && !dragAndDropHooks?.renderDropIndicator) return undefined;
129
+
130
+ const targetsEqual = (a: DropTarget | null | undefined, b: DropTarget): boolean => {
131
+ if (!a) return false;
132
+ if (a.type !== b.type) return false;
133
+ if (a.type === 'root' && b.type === 'root') return true;
134
+ if (a.type !== 'item' || b.type !== 'item') return false;
135
+ return a.key === b.key && a.dropPosition === b.dropPosition;
136
+ };
137
+
138
+ return (target: ItemDropTarget) => {
139
+ const stateIsDropTarget = dropState?.isDropTarget;
140
+ const isTarget = typeof stateIsDropTarget === 'function'
141
+ ? stateIsDropTarget(target)
142
+ : stateIsDropTarget === true
143
+ ? targetsEqual(dropState?.target, target)
144
+ : false;
145
+ const isVirtualDragging = dragAndDropHooks?.isVirtualDragging?.() ?? false;
146
+ if (!isTarget && !isVirtualDragging) return undefined;
147
+ return dragAndDropHooks?.renderDropIndicator
148
+ ? dragAndDropHooks.renderDropIndicator(target)
149
+ : <DropIndicator target={target} />;
150
+ };
151
+ }
152
+
153
+ type KeyAccessor = Key | null | undefined | Accessor<Key | null | undefined>;
154
+
155
+ interface SelectionManagerLike {
156
+ focusedKey?: KeyAccessor;
157
+ }
158
+
159
+ interface CollectionLike {
160
+ getKeyAfter: (key: Key) => Key | null;
161
+ getItem: (key: Key) => { level?: number; type?: string } | null | undefined;
162
+ }
163
+
164
+ interface DroppableCollectionStateLike {
165
+ target?: DropTarget | null;
166
+ }
167
+
168
+ export interface VirtualRangeLike {
169
+ start: number;
170
+ end: number;
171
+ offsetTop: number;
172
+ offsetBottom: number;
173
+ }
174
+
175
+ export interface LayoutInfoProviderLike {
176
+ getLayoutInfo: (index: number) => { rect: { y: number; height: number } };
177
+ }
178
+
179
+ function resolveKey(value: KeyAccessor): Key | null | undefined {
180
+ if (typeof value === 'function') {
181
+ return (value as Accessor<Key | null | undefined>)();
182
+ }
183
+ return value;
184
+ }
185
+
186
+ function getAfterDropNormalizedKey(
187
+ target: ItemDropTarget,
188
+ collection?: CollectionLike
189
+ ): Key {
190
+ if (target.dropPosition !== 'after' || !collection) return target.key;
191
+
192
+ let nextKey = collection.getKeyAfter(target.key);
193
+ let lastDescendantKey: Key | null = null;
194
+
195
+ if (nextKey != null) {
196
+ const targetLevel = collection.getItem(target.key)?.level ?? 0;
197
+ while (nextKey != null) {
198
+ const node = collection.getItem(nextKey);
199
+ if (!node) break;
200
+ if (node.type && node.type !== 'item') {
201
+ nextKey = collection.getKeyAfter(nextKey);
202
+ continue;
203
+ }
204
+ if ((node.level ?? 0) <= targetLevel) break;
205
+ lastDescendantKey = nextKey;
206
+ nextKey = collection.getKeyAfter(nextKey);
207
+ }
208
+ }
209
+
210
+ return nextKey ?? lastDescendantKey ?? target.key;
211
+ }
212
+
213
+ export function getNormalizedDropTargetKey(
214
+ target: DropTarget | null | undefined,
215
+ collection?: CollectionLike
216
+ ): Key | null {
217
+ if (!target || target.type !== 'item') return null;
218
+ return getAfterDropNormalizedKey(target, collection);
219
+ }
220
+
221
+ export function useDndPersistedKeys(
222
+ selectionManager: SelectionManagerLike | null | undefined,
223
+ dragAndDropHooks?: Pick<DragAndDropHooks<unknown>, 'isVirtualDragging'>,
224
+ dropState?: DroppableCollectionStateLike,
225
+ collection?: CollectionLike
226
+ ): Accessor<Set<Key>> {
227
+ return createMemo(() => {
228
+ const focusedKey = resolveKey(selectionManager?.focusedKey);
229
+ let dropTargetKey: Key | null | undefined;
230
+
231
+ if (dragAndDropHooks?.isVirtualDragging?.() && dropState?.target?.type === 'item') {
232
+ dropTargetKey = getNormalizedDropTargetKey(dropState.target, collection) ?? undefined;
233
+ }
234
+
235
+ const keys = new Set<Key>();
236
+ if (focusedKey != null) keys.add(focusedKey);
237
+ if (dropTargetKey != null) keys.add(dropTargetKey);
238
+ return keys;
239
+ });
240
+ }
241
+
242
+ export function mergePersistedKeysIntoVirtualRange(
243
+ baseRange: VirtualRangeLike,
244
+ persistedIndexes: number[],
245
+ itemCount: number,
246
+ layoutInfoProvider: LayoutInfoProviderLike,
247
+ maxExtraItems = 60,
248
+ options?: {
249
+ forceIncludeIndexes?: number[];
250
+ forceIncludeMaxSpan?: number;
251
+ fallbackToForcedWindow?: boolean;
252
+ }
253
+ ): VirtualRangeLike {
254
+ const validPersistedIndexes = Array.from(
255
+ new Set(persistedIndexes.filter((index) => index >= 0 && index < itemCount))
256
+ ).sort((a, b) => a - b);
257
+ const forceIndexes = Array.from(
258
+ new Set((options?.forceIncludeIndexes ?? []).filter((index) => index >= 0 && index < itemCount))
259
+ );
260
+
261
+ if (itemCount <= 0) return baseRange;
262
+ if (validPersistedIndexes.length === 0 && forceIndexes.length === 0) return baseRange;
263
+
264
+ const baseSpan = Math.max(1, baseRange.end - baseRange.start);
265
+ const maxSpan = Math.max(baseSpan, baseSpan + maxExtraItems);
266
+
267
+ let start = baseRange.start;
268
+ let end = baseRange.end;
269
+
270
+ const distanceToBaseRange = (index: number): number => {
271
+ if (index < baseRange.start) return baseRange.start - index;
272
+ if (index >= baseRange.end) return index - (baseRange.end - 1);
273
+ return 0;
274
+ };
275
+
276
+ for (const index of validPersistedIndexes.sort((a, b) => distanceToBaseRange(a) - distanceToBaseRange(b))) {
277
+ const nextStart = Math.min(start, index);
278
+ const nextEnd = Math.max(end, index + 1);
279
+ if (nextEnd - nextStart <= maxSpan) {
280
+ start = nextStart;
281
+ end = nextEnd;
282
+ }
283
+ }
284
+
285
+ if (forceIndexes.length > 0) {
286
+ const forceMaxSpan = Math.max(maxSpan, options?.forceIncludeMaxSpan ?? Math.max(maxSpan, 300));
287
+ for (const index of forceIndexes) {
288
+ const nextStart = Math.min(start, index);
289
+ const nextEnd = Math.max(end, index + 1);
290
+ if (nextEnd - nextStart <= forceMaxSpan) {
291
+ start = nextStart;
292
+ end = nextEnd;
293
+ }
294
+ }
295
+
296
+ if (options?.fallbackToForcedWindow !== false) {
297
+ const missingForced = forceIndexes.filter((index) => index < start || index >= end);
298
+ if (missingForced.length > 0) {
299
+ const nearestForced = missingForced[0];
300
+ const forceMaxSpan = Math.max(maxSpan, options?.forceIncludeMaxSpan ?? Math.max(maxSpan, 300));
301
+ const windowSpan = Math.min(itemCount, Math.max(baseSpan, forceMaxSpan));
302
+ const centeredStart = Math.max(0, Math.min(itemCount - windowSpan, nearestForced - Math.floor(windowSpan / 2)));
303
+ start = centeredStart;
304
+ end = Math.min(itemCount, centeredStart + windowSpan);
305
+ }
306
+ }
307
+ }
308
+
309
+ if (start === baseRange.start && end === baseRange.end) return baseRange;
310
+
311
+ const startRect = start > 0 ? layoutInfoProvider.getLayoutInfo(start).rect : { y: 0, height: 0 };
312
+ const lastRect = layoutInfoProvider.getLayoutInfo(itemCount - 1).rect;
313
+ const endRect = end > 0 ? layoutInfoProvider.getLayoutInfo(end - 1).rect : { y: 0, height: 0 };
314
+
315
+ return {
316
+ start,
317
+ end,
318
+ offsetTop: Math.max(0, startRect.y),
319
+ offsetBottom: Math.max(0, (lastRect.y + lastRect.height) - (endRect.y + endRect.height)),
320
+ };
321
+ }
322
+
323
+ export type DropTargetDelegate = {
324
+ getDropTargetFromPoint: (
325
+ x: number,
326
+ y: number,
327
+ isValidDropTarget: (target: DropTarget) => boolean
328
+ ) => DropTarget | null;
329
+ getDropOperation: (
330
+ target: DropTarget,
331
+ types: DragTypes,
332
+ allowedOperations: DropOperation[]
333
+ ) => DropOperation;
334
+ };
@@ -0,0 +1,45 @@
1
+ import { onCleanup, type JSX } from 'solid-js';
2
+ import type { DragItem, DragPreviewRenderer } from '@proyecto-viviana/solid-stately';
3
+
4
+ export interface DragPreviewProps {
5
+ ref?: { current: DragPreviewRenderer | null };
6
+ children: (items: DragItem[]) => JSX.Element | { element: JSX.Element; x?: number; y?: number } | null | undefined;
7
+ }
8
+
9
+ export function DragPreview(props: DragPreviewProps): JSX.Element {
10
+ const hasDom = typeof HTMLElement !== 'undefined';
11
+ const isElementNode = (value: unknown): value is HTMLElement => {
12
+ if (!value) return false;
13
+ if (hasDom && value instanceof HTMLElement) return true;
14
+ return typeof value === 'object' && (value as { nodeType?: number }).nodeType === 1;
15
+ };
16
+
17
+ if (props.ref) {
18
+ const renderer: DragPreviewRenderer = (items, callback) => {
19
+ const rendered = props.children(items);
20
+ if (!rendered) {
21
+ callback(null);
22
+ return;
23
+ }
24
+ if (
25
+ typeof rendered === 'object' &&
26
+ rendered !== null &&
27
+ 'element' in rendered
28
+ ) {
29
+ const previewValue = rendered as { element: unknown; x?: number; y?: number };
30
+ callback(isElementNode(previewValue.element) ? previewValue.element : null, previewValue.x, previewValue.y);
31
+ return;
32
+ }
33
+ callback(isElementNode(rendered) ? rendered : null);
34
+ };
35
+
36
+ props.ref.current = renderer;
37
+ onCleanup(() => {
38
+ if (props.ref?.current === renderer) {
39
+ props.ref.current = null;
40
+ }
41
+ });
42
+ }
43
+
44
+ return null as unknown as JSX.Element;
45
+ }