@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.
- package/README.md +39 -272
- package/dist/ActionBar.d.ts +21 -13
- package/dist/ActionBar.d.ts.map +1 -1
- package/dist/ActionGroup.d.ts +8 -8
- package/dist/ActionGroup.d.ts.map +1 -1
- package/dist/Alert.d.ts +5 -5
- package/dist/Alert.d.ts.map +1 -1
- package/dist/Autocomplete.d.ts +5 -5
- package/dist/Autocomplete.d.ts.map +1 -1
- package/dist/Breadcrumbs.d.ts +18 -7
- package/dist/Breadcrumbs.d.ts.map +1 -1
- package/dist/Button.d.ts +24 -5
- package/dist/Button.d.ts.map +1 -1
- package/dist/Calendar.d.ts +38 -7
- package/dist/Calendar.d.ts.map +1 -1
- package/dist/Checkbox.d.ts +32 -7
- package/dist/Checkbox.d.ts.map +1 -1
- package/dist/Collection.d.ts +19 -14
- package/dist/Collection.d.ts.map +1 -1
- package/dist/Color.d.ts +103 -14
- package/dist/Color.d.ts.map +1 -1
- package/dist/ColorEditor.d.ts +6 -6
- package/dist/ColorEditor.d.ts.map +1 -1
- package/dist/ComboBox.d.ts +85 -19
- package/dist/ComboBox.d.ts.map +1 -1
- package/dist/ContextualHelpTrigger.d.ts +2 -2
- package/dist/ContextualHelpTrigger.d.ts.map +1 -1
- package/dist/DateField.d.ts +8 -6
- package/dist/DateField.d.ts.map +1 -1
- package/dist/DatePicker.d.ts +53 -22
- package/dist/DatePicker.d.ts.map +1 -1
- package/dist/DateRangePickerContext.d.ts +30 -0
- package/dist/DateRangePickerContext.d.ts.map +1 -0
- package/dist/Dialog.d.ts +5 -5
- package/dist/Dialog.d.ts.map +1 -1
- package/dist/Disclosure.d.ts +23 -5
- package/dist/Disclosure.d.ts.map +1 -1
- package/dist/DragAndDrop.d.ts +6 -6
- package/dist/DragAndDrop.d.ts.map +1 -1
- package/dist/DragPreview.d.ts +2 -2
- package/dist/DragPreview.d.ts.map +1 -1
- package/dist/DropZone.d.ts +4 -4
- package/dist/DropZone.d.ts.map +1 -1
- package/dist/FieldError.d.ts +9 -5
- package/dist/FieldError.d.ts.map +1 -1
- package/dist/FileTrigger.d.ts +3 -3
- package/dist/FileTrigger.d.ts.map +1 -1
- package/dist/Focusable.d.ts +2 -2
- package/dist/Focusable.d.ts.map +1 -1
- package/dist/Form.d.ts +18 -4
- package/dist/Form.d.ts.map +1 -1
- package/dist/GridList.d.ts +32 -12
- package/dist/GridList.d.ts.map +1 -1
- package/dist/HiddenDateInput.d.ts +26 -0
- package/dist/HiddenDateInput.d.ts.map +1 -0
- package/dist/HiddenTimeInput.d.ts +25 -0
- package/dist/HiddenTimeInput.d.ts.map +1 -0
- package/dist/Icon.d.ts +5 -5
- package/dist/Icon.d.ts.map +1 -1
- package/dist/Keyboard.d.ts +1 -1
- package/dist/Landmark.d.ts +3 -3
- package/dist/Landmark.d.ts.map +1 -1
- package/dist/Link.d.ts +10 -4
- package/dist/Link.d.ts.map +1 -1
- package/dist/ListBox.d.ts +32 -12
- package/dist/ListBox.d.ts.map +1 -1
- package/dist/ListDropTargetDelegate.d.ts +6 -6
- package/dist/ListDropTargetDelegate.d.ts.map +1 -1
- package/dist/Menu.d.ts +65 -14
- package/dist/Menu.d.ts.map +1 -1
- package/dist/Meter.d.ts +3 -3
- package/dist/Meter.d.ts.map +1 -1
- package/dist/Modal.d.ts +5 -5
- package/dist/Modal.d.ts.map +1 -1
- package/dist/NumberField.d.ts +8 -12
- package/dist/NumberField.d.ts.map +1 -1
- package/dist/Popover.d.ts +28 -5
- package/dist/Popover.d.ts.map +1 -1
- package/dist/Pressable.d.ts +2 -2
- package/dist/Pressable.d.ts.map +1 -1
- package/dist/ProgressBar.d.ts +5 -3
- package/dist/ProgressBar.d.ts.map +1 -1
- package/dist/RadioGroup.d.ts +43 -9
- package/dist/RadioGroup.d.ts.map +1 -1
- package/dist/RangeCalendar.d.ts +34 -7
- package/dist/RangeCalendar.d.ts.map +1 -1
- package/dist/RouterProvider.d.ts +2 -2
- package/dist/RouterProvider.d.ts.map +1 -1
- package/dist/SearchField.d.ts +23 -20
- package/dist/SearchField.d.ts.map +1 -1
- package/dist/Select.d.ts +41 -11
- package/dist/Select.d.ts.map +1 -1
- package/dist/SelectionIndicator.d.ts +3 -3
- package/dist/SelectionIndicator.d.ts.map +1 -1
- package/dist/Separator.d.ts +9 -3
- package/dist/Separator.d.ts.map +1 -1
- package/dist/SharedElementTransition.d.ts +6 -4
- package/dist/SharedElementTransition.d.ts.map +1 -1
- package/dist/Slider.d.ts +12 -8
- package/dist/Slider.d.ts.map +1 -1
- package/dist/StepList.d.ts +90 -0
- package/dist/StepList.d.ts.map +1 -0
- package/dist/Switch.d.ts +11 -5
- package/dist/Switch.d.ts.map +1 -1
- package/dist/Table.d.ts +187 -23
- package/dist/Table.d.ts.map +1 -1
- package/dist/Tabs.d.ts +45 -9
- package/dist/Tabs.d.ts.map +1 -1
- package/dist/TagGroup.d.ts +12 -10
- package/dist/TagGroup.d.ts.map +1 -1
- package/dist/Text.d.ts +2 -2
- package/dist/TextField.d.ts +15 -11
- package/dist/TextField.d.ts.map +1 -1
- package/dist/TimeField.d.ts +6 -6
- package/dist/TimeField.d.ts.map +1 -1
- package/dist/Toast.d.ts +29 -14
- package/dist/Toast.d.ts.map +1 -1
- package/dist/ToggleButton.d.ts +11 -5
- package/dist/ToggleButton.d.ts.map +1 -1
- package/dist/ToggleButtonGroup.d.ts +7 -7
- package/dist/ToggleButtonGroup.d.ts.map +1 -1
- package/dist/Toolbar.d.ts +7 -3
- package/dist/Toolbar.d.ts.map +1 -1
- package/dist/Tooltip.d.ts +50 -8
- package/dist/Tooltip.d.ts.map +1 -1
- package/dist/Tree.d.ts +66 -17
- package/dist/Tree.d.ts.map +1 -1
- package/dist/Virtualizer.d.ts +12 -12
- package/dist/Virtualizer.d.ts.map +1 -1
- package/dist/VirtualizerLayouts.d.ts +2 -2
- package/dist/VirtualizerLayouts.d.ts.map +1 -1
- package/dist/VisuallyHidden.d.ts +1 -1
- package/dist/VisuallyHidden.d.ts.map +1 -1
- package/dist/contexts.d.ts +5 -1
- package/dist/contexts.d.ts.map +1 -1
- package/dist/index.d.ts +73 -71
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +23247 -18564
- package/dist/index.js.map +1 -1
- package/dist/index.jsx +18110 -0
- package/dist/index.jsx.map +1 -0
- package/dist/useDragAndDrop.d.ts +13 -13
- package/dist/useDragAndDrop.d.ts.map +1 -1
- package/dist/utils.d.ts +2 -2
- package/dist/utils.d.ts.map +1 -1
- package/dist/virtualizer/Layout.d.ts +1 -1
- package/dist/virtualizer/Layout.d.ts.map +1 -1
- package/package.json +31 -32
- package/src/ActionBar.tsx +75 -72
- package/src/ActionGroup.tsx +53 -61
- package/src/Alert.tsx +17 -42
- package/src/Autocomplete.tsx +39 -44
- package/src/Breadcrumbs.tsx +149 -80
- package/src/Button.tsx +267 -70
- package/src/Calendar.tsx +218 -138
- package/src/Checkbox.tsx +413 -121
- package/src/Collection.tsx +67 -58
- package/src/Color.tsx +803 -380
- package/src/ColorEditor.tsx +131 -149
- package/src/ComboBox.tsx +414 -249
- package/src/ContextualHelpTrigger.tsx +86 -74
- package/src/DateField.tsx +185 -91
- package/src/DatePicker.tsx +524 -213
- package/src/DateRangePickerContext.tsx +44 -0
- package/src/Dialog.tsx +156 -118
- package/src/Disclosure.tsx +127 -80
- package/src/DragAndDrop.tsx +60 -54
- package/src/DragPreview.tsx +13 -11
- package/src/DropZone.tsx +42 -22
- package/src/FieldError.tsx +45 -23
- package/src/FileTrigger.tsx +19 -19
- package/src/Focusable.tsx +21 -24
- package/src/Form.tsx +71 -16
- package/src/GridList.tsx +273 -197
- package/src/HiddenDateInput.tsx +153 -0
- package/src/HiddenTimeInput.tsx +133 -0
- package/src/Icon.tsx +22 -43
- package/src/Keyboard.tsx +3 -3
- package/src/Landmark.tsx +37 -63
- package/src/Link.tsx +125 -75
- package/src/ListBox.tsx +332 -233
- package/src/ListDropTargetDelegate.ts +81 -80
- package/src/Menu.tsx +1023 -274
- package/src/Meter.tsx +38 -56
- package/src/Modal.tsx +243 -175
- package/src/NumberField.tsx +139 -143
- package/src/Popover.tsx +386 -233
- package/src/Pressable.tsx +21 -21
- package/src/ProgressBar.tsx +48 -57
- package/src/RadioGroup.tsx +524 -122
- package/src/RangeCalendar.tsx +157 -90
- package/src/RouterProvider.tsx +30 -47
- package/src/SearchField.tsx +362 -143
- package/src/Select.tsx +656 -233
- package/src/SelectionIndicator.tsx +18 -15
- package/src/Separator.tsx +47 -49
- package/src/SharedElementTransition.tsx +103 -97
- package/src/Slider.tsx +138 -98
- package/src/StepList.tsx +272 -0
- package/src/Switch.tsx +93 -46
- package/src/Table.tsx +1308 -342
- package/src/Tabs.tsx +324 -103
- package/src/TagGroup.tsx +139 -126
- package/src/Text.tsx +3 -3
- package/src/TextField.tsx +389 -79
- package/src/TimeField.tsx +136 -76
- package/src/Toast.tsx +209 -157
- package/src/ToggleButton.tsx +47 -37
- package/src/ToggleButtonGroup.tsx +39 -34
- package/src/Toolbar.tsx +54 -69
- package/src/Tooltip.tsx +387 -119
- package/src/Tree.tsx +651 -368
- package/src/Virtualizer.tsx +208 -180
- package/src/VirtualizerLayouts.ts +45 -30
- package/src/VisuallyHidden.tsx +19 -19
- package/src/contexts.ts +29 -37
- package/src/index.ts +110 -195
- package/src/useDragAndDrop.ts +87 -71
- package/src/utils.tsx +40 -55
- package/src/virtualizer/Layout.ts +14 -22
- package/dist/index.ssr.js +0 -16996
- package/dist/index.ssr.js.map +0 -1
package/src/Tree.tsx
CHANGED
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
useContext,
|
|
20
20
|
For,
|
|
21
21
|
Show,
|
|
22
|
-
} from
|
|
22
|
+
} from "solid-js";
|
|
23
23
|
import {
|
|
24
24
|
createTree,
|
|
25
25
|
createTreeItem,
|
|
@@ -28,7 +28,7 @@ import {
|
|
|
28
28
|
createHover,
|
|
29
29
|
mergeProps,
|
|
30
30
|
type AriaTreeProps,
|
|
31
|
-
} from
|
|
31
|
+
} from "@proyecto-viviana/solidaria";
|
|
32
32
|
import {
|
|
33
33
|
createTreeState,
|
|
34
34
|
createTreeCollection,
|
|
@@ -39,7 +39,7 @@ import {
|
|
|
39
39
|
type Key,
|
|
40
40
|
type DropTarget,
|
|
41
41
|
type ItemDropTarget,
|
|
42
|
-
} from
|
|
42
|
+
} from "@proyecto-viviana/solid-stately";
|
|
43
43
|
import {
|
|
44
44
|
type RenderChildren,
|
|
45
45
|
type ClassNameOrFunction,
|
|
@@ -47,15 +47,15 @@ import {
|
|
|
47
47
|
type SlotProps,
|
|
48
48
|
useRenderProps,
|
|
49
49
|
filterDOMProps,
|
|
50
|
-
} from
|
|
51
|
-
import { SharedElementTransition } from
|
|
52
|
-
import { type DragAndDropHooks } from
|
|
50
|
+
} from "./utils";
|
|
51
|
+
import { SharedElementTransition } from "./SharedElementTransition";
|
|
52
|
+
import { type DragAndDropHooks } from "./useDragAndDrop";
|
|
53
53
|
import {
|
|
54
54
|
getNormalizedDropTargetKey,
|
|
55
55
|
mergePersistedKeysIntoVirtualRange,
|
|
56
56
|
useDndPersistedKeys,
|
|
57
57
|
useRenderDropIndicator,
|
|
58
|
-
} from
|
|
58
|
+
} from "./DragAndDrop";
|
|
59
59
|
import {
|
|
60
60
|
CollectionRendererContext,
|
|
61
61
|
flattenCollectionEntries,
|
|
@@ -67,12 +67,26 @@ import {
|
|
|
67
67
|
type SectionProps,
|
|
68
68
|
type HeaderProps,
|
|
69
69
|
useCollectionRenderer,
|
|
70
|
-
} from
|
|
71
|
-
import { useVirtualizerContext } from
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
70
|
+
} from "./Collection";
|
|
71
|
+
import { useVirtualizerContext } from "./Virtualizer";
|
|
72
|
+
import {
|
|
73
|
+
handleLinkClick,
|
|
74
|
+
type LinkDOMProps,
|
|
75
|
+
type RouterOptions,
|
|
76
|
+
useLinkProps,
|
|
77
|
+
useRouter,
|
|
78
|
+
} from "./RouterProvider";
|
|
79
|
+
|
|
80
|
+
type RefLike<T> = ((el: T) => void) | { current?: T | null } | undefined;
|
|
81
|
+
|
|
82
|
+
function assignRef<T>(ref: RefLike<T>, el: T): void {
|
|
83
|
+
if (!ref) return;
|
|
84
|
+
if (typeof ref === "function") {
|
|
85
|
+
ref(el);
|
|
86
|
+
} else {
|
|
87
|
+
ref.current = el;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
76
90
|
|
|
77
91
|
export interface TreeRenderProps {
|
|
78
92
|
/** Whether the tree has focus. */
|
|
@@ -85,23 +99,23 @@ export interface TreeRenderProps {
|
|
|
85
99
|
isEmpty: boolean;
|
|
86
100
|
}
|
|
87
101
|
|
|
88
|
-
export interface TreeProps<T extends object> extends Omit<AriaTreeProps,
|
|
102
|
+
export interface TreeProps<T extends object> extends Omit<AriaTreeProps, "children">, SlotProps {
|
|
89
103
|
/** The hierarchical items to render in the tree. */
|
|
90
|
-
items
|
|
104
|
+
items?: CollectionEntry<TreeItemData<T>>[];
|
|
91
105
|
/** The selection mode. */
|
|
92
|
-
selectionMode?:
|
|
106
|
+
selectionMode?: "none" | "single" | "multiple";
|
|
93
107
|
/** The selection behavior (toggle vs replace). */
|
|
94
|
-
selectionBehavior?:
|
|
108
|
+
selectionBehavior?: "toggle" | "replace";
|
|
95
109
|
/** Whether disabled items can still receive focus. */
|
|
96
|
-
disabledBehavior?:
|
|
110
|
+
disabledBehavior?: "selection" | "all";
|
|
97
111
|
/** Keys of disabled items. */
|
|
98
112
|
disabledKeys?: Iterable<Key>;
|
|
99
113
|
/** Currently selected keys (controlled). */
|
|
100
|
-
selectedKeys?:
|
|
114
|
+
selectedKeys?: "all" | Iterable<Key>;
|
|
101
115
|
/** Default selected keys (uncontrolled). */
|
|
102
|
-
defaultSelectedKeys?:
|
|
116
|
+
defaultSelectedKeys?: "all" | Iterable<Key>;
|
|
103
117
|
/** Handler called when selection changes. */
|
|
104
|
-
onSelectionChange?: (keys:
|
|
118
|
+
onSelectionChange?: (keys: "all" | Set<Key>) => void;
|
|
105
119
|
/** Currently expanded keys (controlled). */
|
|
106
120
|
expandedKeys?: Iterable<Key>;
|
|
107
121
|
/** Default expanded keys (uncontrolled). */
|
|
@@ -120,10 +134,20 @@ export interface TreeProps<T extends object> extends Omit<AriaTreeProps, 'childr
|
|
|
120
134
|
hasMore?: boolean;
|
|
121
135
|
/** Whether additional items are currently loading. */
|
|
122
136
|
isLoading?: boolean;
|
|
137
|
+
/** Loading state for async collection parity. */
|
|
138
|
+
loadingState?: "idle" | "loading" | "loadingMore" | "sorting" | "filtering" | "error";
|
|
123
139
|
/** Called when the load more sentinel becomes visible. */
|
|
124
140
|
onLoadMore?: () => void | Promise<void>;
|
|
141
|
+
/** Renders the load-more sentinel content. */
|
|
142
|
+
renderLoadMoreItem?: (state: { isLoading: boolean }) => JSX.Element;
|
|
143
|
+
/** CSS className for the load-more sentinel. */
|
|
144
|
+
loadMoreClass?: ClassNameOrFunction<{ isLoading: boolean }>;
|
|
145
|
+
/** Inline style for the load-more sentinel. */
|
|
146
|
+
loadMoreStyle?: StyleOrFunction<{ isLoading: boolean }>;
|
|
125
147
|
/** Drag and drop hooks from `useDragAndDrop`. */
|
|
126
148
|
dragAndDropHooks?: DragAndDropHooks<T>;
|
|
149
|
+
/** Ref for the rendered tree element. */
|
|
150
|
+
ref?: RefLike<HTMLDivElement>;
|
|
127
151
|
}
|
|
128
152
|
|
|
129
153
|
export interface TreeRenderItemState {
|
|
@@ -154,9 +178,16 @@ export interface TreeItemRenderProps {
|
|
|
154
178
|
isExpandable: boolean;
|
|
155
179
|
/** The nesting level (0 = root). */
|
|
156
180
|
level: number;
|
|
181
|
+
/** The selection mode active on the tree. */
|
|
182
|
+
selectionMode: "none" | "single" | "multiple";
|
|
183
|
+
/** The selection behavior active on the tree. */
|
|
184
|
+
selectionBehavior: "toggle" | "replace";
|
|
157
185
|
}
|
|
158
186
|
|
|
159
|
-
export interface TreeItemProps<T extends object>
|
|
187
|
+
export interface TreeItemProps<T extends object>
|
|
188
|
+
extends
|
|
189
|
+
SlotProps,
|
|
190
|
+
Omit<JSX.HTMLAttributes<HTMLElement>, "class" | "style" | "children" | "id" | "ref"> {
|
|
160
191
|
/** The unique key for the item. */
|
|
161
192
|
id: Key;
|
|
162
193
|
/** The item value. */
|
|
@@ -171,6 +202,21 @@ export interface TreeItemProps<T extends object> extends SlotProps, Omit<JSX.HTM
|
|
|
171
202
|
textValue?: string;
|
|
172
203
|
/** Handler called when the item is activated. */
|
|
173
204
|
onAction?: () => void;
|
|
205
|
+
/** Whether this item has children that may not be loaded yet. */
|
|
206
|
+
hasChildItems?: boolean;
|
|
207
|
+
/** Whether this item is disabled. */
|
|
208
|
+
isDisabled?: boolean;
|
|
209
|
+
/** Link target metadata. */
|
|
210
|
+
href?: string;
|
|
211
|
+
target?: LinkDOMProps["target"];
|
|
212
|
+
download?: LinkDOMProps["download"];
|
|
213
|
+
rel?: LinkDOMProps["rel"];
|
|
214
|
+
hrefLang?: string;
|
|
215
|
+
ping?: LinkDOMProps["ping"];
|
|
216
|
+
referrerPolicy?: LinkDOMProps["referrerPolicy"];
|
|
217
|
+
routerOptions?: RouterOptions;
|
|
218
|
+
/** Ref for the rendered row element. */
|
|
219
|
+
ref?: RefLike<HTMLElement>;
|
|
174
220
|
}
|
|
175
221
|
|
|
176
222
|
export interface TreeExpandButtonProps {
|
|
@@ -180,11 +226,16 @@ export interface TreeExpandButtonProps {
|
|
|
180
226
|
style?: JSX.CSSProperties;
|
|
181
227
|
/** Children to render inside the button. */
|
|
182
228
|
children?: JSX.Element | ((props: { isExpanded: boolean }) => JSX.Element);
|
|
229
|
+
[key: `data-${string}`]: string | undefined;
|
|
183
230
|
}
|
|
184
231
|
|
|
185
232
|
export interface TreeLoadMoreItemProps extends SlotProps {
|
|
186
233
|
onLoadMore: () => void | Promise<void>;
|
|
187
234
|
isLoading?: boolean;
|
|
235
|
+
loadingState?: "idle" | "loading" | "loadingMore" | "sorting" | "filtering" | "error";
|
|
236
|
+
level?: number;
|
|
237
|
+
/** Scroll offset multiplier for early loading trigger (default: 1 = 100% of viewport height). */
|
|
238
|
+
scrollOffset?: number;
|
|
188
239
|
children?: JSX.Element;
|
|
189
240
|
class?: ClassNameOrFunction<{ isLoading: boolean }>;
|
|
190
241
|
style?: StyleOrFunction<{ isLoading: boolean }>;
|
|
@@ -193,10 +244,6 @@ export interface TreeLoadMoreItemProps extends SlotProps {
|
|
|
193
244
|
export interface TreeSectionProps extends SectionProps {}
|
|
194
245
|
export interface TreeHeaderProps extends HeaderProps {}
|
|
195
246
|
|
|
196
|
-
// ============================================
|
|
197
|
-
// CONTEXT
|
|
198
|
-
// ============================================
|
|
199
|
-
|
|
200
247
|
interface TreeContextValue<T extends object> {
|
|
201
248
|
state: TreeState<T, TreeCollection<T>>;
|
|
202
249
|
collection: TreeCollection<T>;
|
|
@@ -211,25 +258,25 @@ interface TreeDropTargetDelegate {
|
|
|
211
258
|
getDropTargetFromPoint: (
|
|
212
259
|
x: number,
|
|
213
260
|
y: number,
|
|
214
|
-
isValidDropTarget: (target: DropTarget) => boolean
|
|
261
|
+
isValidDropTarget: (target: DropTarget) => boolean,
|
|
215
262
|
) => DropTarget | null;
|
|
216
263
|
getKeyboardNavigationTarget?: (
|
|
217
264
|
target: DropTarget | null,
|
|
218
|
-
direction:
|
|
219
|
-
isValidDropTarget: (target: DropTarget) => boolean
|
|
265
|
+
direction: "next" | "previous",
|
|
266
|
+
isValidDropTarget: (target: DropTarget) => boolean,
|
|
220
267
|
) => DropTarget | null;
|
|
221
268
|
getKeyboardPageNavigationTarget?: (
|
|
222
269
|
target: DropTarget | null,
|
|
223
|
-
direction:
|
|
224
|
-
isValidDropTarget: (target: DropTarget) => boolean
|
|
270
|
+
direction: "next" | "previous",
|
|
271
|
+
isValidDropTarget: (target: DropTarget) => boolean,
|
|
225
272
|
) => DropTarget | null;
|
|
226
273
|
}
|
|
227
274
|
|
|
228
275
|
interface PointerTrackingState {
|
|
229
276
|
lastY: number;
|
|
230
277
|
lastX: number;
|
|
231
|
-
yDirection:
|
|
232
|
-
xDirection:
|
|
278
|
+
yDirection: "up" | "down" | null;
|
|
279
|
+
xDirection: "left" | "right" | null;
|
|
233
280
|
boundaryContext: {
|
|
234
281
|
parentKey: Key;
|
|
235
282
|
lastSwitchY: number;
|
|
@@ -241,36 +288,36 @@ interface PointerTrackingState {
|
|
|
241
288
|
const X_SWITCH_THRESHOLD = 10;
|
|
242
289
|
const Y_SWITCH_THRESHOLD = 5;
|
|
243
290
|
const EXPANSION_KEYS = {
|
|
244
|
-
expand: { ltr:
|
|
245
|
-
collapse: { ltr:
|
|
291
|
+
expand: { ltr: "ArrowRight", rtl: "ArrowLeft" },
|
|
292
|
+
collapse: { ltr: "ArrowLeft", rtl: "ArrowRight" },
|
|
246
293
|
} as const;
|
|
247
294
|
|
|
248
|
-
function resolveTreeDirection(element: HTMLElement | null):
|
|
295
|
+
function resolveTreeDirection(element: HTMLElement | null): "ltr" | "rtl" {
|
|
249
296
|
if (element) {
|
|
250
|
-
const dir = element.closest(
|
|
251
|
-
if (dir ===
|
|
252
|
-
if (dir ===
|
|
253
|
-
if (typeof window !==
|
|
297
|
+
const dir = element.closest("[dir]")?.getAttribute("dir");
|
|
298
|
+
if (dir === "rtl") return "rtl";
|
|
299
|
+
if (dir === "ltr") return "ltr";
|
|
300
|
+
if (typeof window !== "undefined" && typeof window.getComputedStyle === "function") {
|
|
254
301
|
const computedDirection = window.getComputedStyle(element).direction;
|
|
255
|
-
if (computedDirection ===
|
|
256
|
-
if (computedDirection ===
|
|
302
|
+
if (computedDirection === "rtl") return "rtl";
|
|
303
|
+
if (computedDirection === "ltr") return "ltr";
|
|
257
304
|
}
|
|
258
305
|
}
|
|
259
|
-
if (typeof document !==
|
|
260
|
-
return document.dir ===
|
|
306
|
+
if (typeof document !== "undefined") {
|
|
307
|
+
return document.dir === "rtl" ? "rtl" : "ltr";
|
|
261
308
|
}
|
|
262
|
-
return
|
|
309
|
+
return "ltr";
|
|
263
310
|
}
|
|
264
311
|
|
|
265
312
|
function createTreeDropTargetDelegate<T extends object>(
|
|
266
313
|
delegate: TreeDropTargetDelegate,
|
|
267
314
|
state: TreeState<T, TreeCollection<T>>,
|
|
268
|
-
direction:
|
|
315
|
+
direction: "ltr" | "rtl",
|
|
269
316
|
baseKeyboardNav?: (
|
|
270
317
|
target: DropTarget | null,
|
|
271
|
-
direction:
|
|
272
|
-
isValidDropTarget: (target: DropTarget) => boolean
|
|
273
|
-
) => DropTarget | null
|
|
318
|
+
direction: "next" | "previous",
|
|
319
|
+
isValidDropTarget: (target: DropTarget) => boolean,
|
|
320
|
+
) => DropTarget | null,
|
|
274
321
|
): TreeDropTargetDelegate {
|
|
275
322
|
const pointerTracking: PointerTrackingState = {
|
|
276
323
|
lastY: 0,
|
|
@@ -282,9 +329,9 @@ function createTreeDropTargetDelegate<T extends object>(
|
|
|
282
329
|
|
|
283
330
|
const getPotentialTargets = (
|
|
284
331
|
originalTarget: ItemDropTarget,
|
|
285
|
-
isValidDropTarget: (target: DropTarget) => boolean
|
|
332
|
+
isValidDropTarget: (target: DropTarget) => boolean,
|
|
286
333
|
): ItemDropTarget[] => {
|
|
287
|
-
if (originalTarget.dropPosition ===
|
|
334
|
+
if (originalTarget.dropPosition === "on") return [originalTarget];
|
|
288
335
|
|
|
289
336
|
const collection = state.collection;
|
|
290
337
|
const getNodeNextKey = (node: TreeNode<T> | null | undefined): Key | null => {
|
|
@@ -294,7 +341,7 @@ function createTreeDropTargetDelegate<T extends object>(
|
|
|
294
341
|
};
|
|
295
342
|
const target: ItemDropTarget = { ...originalTarget };
|
|
296
343
|
let currentItem = collection.getItem(target.key);
|
|
297
|
-
while (currentItem && currentItem.type !==
|
|
344
|
+
while (currentItem && currentItem.type !== "item") {
|
|
298
345
|
const nextKey = getNodeNextKey(currentItem);
|
|
299
346
|
if (nextKey == null) break;
|
|
300
347
|
target.key = nextKey;
|
|
@@ -307,11 +354,11 @@ function createTreeDropTargetDelegate<T extends object>(
|
|
|
307
354
|
currentItem &&
|
|
308
355
|
currentItem.hasChildNodes &&
|
|
309
356
|
state.expandedKeys.has(currentItem.key) &&
|
|
310
|
-
target.dropPosition ===
|
|
357
|
+
target.dropPosition === "after"
|
|
311
358
|
) {
|
|
312
359
|
let firstChildItemNode: TreeNode<T> | null = null;
|
|
313
360
|
for (const child of collection.getChildren(currentItem.key)) {
|
|
314
|
-
if (child.type ===
|
|
361
|
+
if (child.type === "item") {
|
|
315
362
|
firstChildItemNode = child;
|
|
316
363
|
break;
|
|
317
364
|
}
|
|
@@ -319,9 +366,9 @@ function createTreeDropTargetDelegate<T extends object>(
|
|
|
319
366
|
|
|
320
367
|
if (firstChildItemNode) {
|
|
321
368
|
const beforeFirstChildTarget: ItemDropTarget = {
|
|
322
|
-
type:
|
|
369
|
+
type: "item",
|
|
323
370
|
key: firstChildItemNode.key,
|
|
324
|
-
dropPosition:
|
|
371
|
+
dropPosition: "before",
|
|
325
372
|
};
|
|
326
373
|
|
|
327
374
|
if (isValidDropTarget(beforeFirstChildTarget)) {
|
|
@@ -345,9 +392,9 @@ function createTreeDropTargetDelegate<T extends object>(
|
|
|
345
392
|
|
|
346
393
|
if (isLastChildAtLevel) {
|
|
347
394
|
const afterParentTarget: ItemDropTarget = {
|
|
348
|
-
type:
|
|
395
|
+
type: "item",
|
|
349
396
|
key: parentKey,
|
|
350
|
-
dropPosition:
|
|
397
|
+
dropPosition: "after",
|
|
351
398
|
};
|
|
352
399
|
|
|
353
400
|
if (isValidDropTarget(afterParentTarget)) {
|
|
@@ -375,9 +422,9 @@ function createTreeDropTargetDelegate<T extends object>(
|
|
|
375
422
|
nextNode.level > currentItem.level
|
|
376
423
|
) {
|
|
377
424
|
const beforeTarget: ItemDropTarget = {
|
|
378
|
-
type:
|
|
425
|
+
type: "item",
|
|
379
426
|
key: nextKey,
|
|
380
|
-
dropPosition:
|
|
427
|
+
dropPosition: "before",
|
|
381
428
|
};
|
|
382
429
|
if (isValidDropTarget(beforeTarget)) return [beforeTarget];
|
|
383
430
|
}
|
|
@@ -391,8 +438,8 @@ function createTreeDropTargetDelegate<T extends object>(
|
|
|
391
438
|
originalTarget: ItemDropTarget,
|
|
392
439
|
x: number,
|
|
393
440
|
y: number,
|
|
394
|
-
currentYMovement:
|
|
395
|
-
currentXMovement:
|
|
441
|
+
currentYMovement: "up" | "down" | null,
|
|
442
|
+
currentXMovement: "left" | "right" | null,
|
|
396
443
|
): ItemDropTarget => {
|
|
397
444
|
if (potentialTargets.length < 2) return potentialTargets[0];
|
|
398
445
|
|
|
@@ -400,8 +447,12 @@ function createTreeDropTargetDelegate<T extends object>(
|
|
|
400
447
|
const parentKey = currentItem?.parentKey;
|
|
401
448
|
if (parentKey == null) return potentialTargets[0];
|
|
402
449
|
|
|
403
|
-
if (
|
|
404
|
-
|
|
450
|
+
if (
|
|
451
|
+
!pointerTracking.boundaryContext ||
|
|
452
|
+
pointerTracking.boundaryContext.parentKey !== parentKey
|
|
453
|
+
) {
|
|
454
|
+
const initialTargetIndex =
|
|
455
|
+
pointerTracking.yDirection === "up" ? potentialTargets.length - 1 : 0;
|
|
405
456
|
pointerTracking.boundaryContext = {
|
|
406
457
|
parentKey,
|
|
407
458
|
preferredTargetIndex: initialTargetIndex,
|
|
@@ -416,9 +467,9 @@ function createTreeDropTargetDelegate<T extends object>(
|
|
|
416
467
|
|
|
417
468
|
if (distanceFromLastYSwitch > Y_SWITCH_THRESHOLD && currentYMovement) {
|
|
418
469
|
const currentIndex = boundaryContext.preferredTargetIndex ?? 0;
|
|
419
|
-
if (currentYMovement ===
|
|
470
|
+
if (currentYMovement === "down" && currentIndex === 0) {
|
|
420
471
|
boundaryContext.preferredTargetIndex = potentialTargets.length - 1;
|
|
421
|
-
} else if (currentYMovement ===
|
|
472
|
+
} else if (currentYMovement === "up" && currentIndex === potentialTargets.length - 1) {
|
|
422
473
|
boundaryContext.preferredTargetIndex = 0;
|
|
423
474
|
}
|
|
424
475
|
pointerTracking.xDirection = null;
|
|
@@ -427,8 +478,8 @@ function createTreeDropTargetDelegate<T extends object>(
|
|
|
427
478
|
if (distanceFromLastXSwitch > X_SWITCH_THRESHOLD && currentXMovement) {
|
|
428
479
|
const currentTargetIndex = boundaryContext.preferredTargetIndex ?? 0;
|
|
429
480
|
|
|
430
|
-
if (currentXMovement ===
|
|
431
|
-
if (direction ===
|
|
481
|
+
if (currentXMovement === "left") {
|
|
482
|
+
if (direction === "ltr") {
|
|
432
483
|
if (currentTargetIndex < potentialTargets.length - 1) {
|
|
433
484
|
boundaryContext.preferredTargetIndex = currentTargetIndex + 1;
|
|
434
485
|
boundaryContext.lastSwitchX = x;
|
|
@@ -437,8 +488,8 @@ function createTreeDropTargetDelegate<T extends object>(
|
|
|
437
488
|
boundaryContext.preferredTargetIndex = currentTargetIndex - 1;
|
|
438
489
|
boundaryContext.lastSwitchX = x;
|
|
439
490
|
}
|
|
440
|
-
} else if (currentXMovement ===
|
|
441
|
-
if (direction ===
|
|
491
|
+
} else if (currentXMovement === "right") {
|
|
492
|
+
if (direction === "ltr") {
|
|
442
493
|
if (currentTargetIndex > 0) {
|
|
443
494
|
boundaryContext.preferredTargetIndex = currentTargetIndex - 1;
|
|
444
495
|
boundaryContext.lastSwitchX = x;
|
|
@@ -454,7 +505,7 @@ function createTreeDropTargetDelegate<T extends object>(
|
|
|
454
505
|
|
|
455
506
|
const targetIndex = Math.max(
|
|
456
507
|
0,
|
|
457
|
-
Math.min(boundaryContext.preferredTargetIndex ?? 0, potentialTargets.length - 1)
|
|
508
|
+
Math.min(boundaryContext.preferredTargetIndex ?? 0, potentialTargets.length - 1),
|
|
458
509
|
);
|
|
459
510
|
return potentialTargets[targetIndex];
|
|
460
511
|
};
|
|
@@ -462,25 +513,25 @@ function createTreeDropTargetDelegate<T extends object>(
|
|
|
462
513
|
// --- Tree-aware keyboard DnD navigation (RAC parity) ---
|
|
463
514
|
const getKeyboardNavigationTarget = (
|
|
464
515
|
target: DropTarget | null,
|
|
465
|
-
dir:
|
|
466
|
-
isValidDropTarget: (target: DropTarget) => boolean
|
|
516
|
+
dir: "next" | "previous",
|
|
517
|
+
isValidDropTarget: (target: DropTarget) => boolean,
|
|
467
518
|
): DropTarget | null => {
|
|
468
519
|
const collection = state.collection;
|
|
469
520
|
|
|
470
521
|
// If the target key is not a visible row (e.g. collapsed/hidden child node),
|
|
471
522
|
// fall back to the base (non-override) index-based navigation to avoid infinite recursion.
|
|
472
523
|
// The collection keyMap contains ALL nodes (even collapsed), so check visible rows instead.
|
|
473
|
-
if (target && target.type ===
|
|
524
|
+
if (target && target.type === "item") {
|
|
474
525
|
const node = collection.getItem(target.key);
|
|
475
|
-
const isVisibleRow =
|
|
526
|
+
const isVisibleRow =
|
|
527
|
+
node != null && (node as TreeNode<T> & { rowIndex?: number }).rowIndex != null;
|
|
476
528
|
if (!isVisibleRow) {
|
|
477
529
|
return baseKeyboardNav?.(target, dir, isValidDropTarget) ?? null;
|
|
478
530
|
}
|
|
479
531
|
}
|
|
480
532
|
|
|
481
533
|
// Helpers
|
|
482
|
-
const tryValid = (t: DropTarget): DropTarget | null =>
|
|
483
|
-
isValidDropTarget(t) ? t : null;
|
|
534
|
+
const tryValid = (t: DropTarget): DropTarget | null => (isValidDropTarget(t) ? t : null);
|
|
484
535
|
|
|
485
536
|
const getNodeNextKey = (node: TreeNode<T> | null | undefined): Key | null => {
|
|
486
537
|
if (!node) return null;
|
|
@@ -495,7 +546,7 @@ function createTreeDropTargetDelegate<T extends object>(
|
|
|
495
546
|
|
|
496
547
|
const getFirstChildItemKey = (key: Key): Key | null => {
|
|
497
548
|
for (const child of collection.getChildren(key)) {
|
|
498
|
-
if (child.type ===
|
|
549
|
+
if (child.type === "item") return child.key;
|
|
499
550
|
}
|
|
500
551
|
return null;
|
|
501
552
|
};
|
|
@@ -503,7 +554,7 @@ function createTreeDropTargetDelegate<T extends object>(
|
|
|
503
554
|
const getLastChildItemKey = (key: Key): Key | null => {
|
|
504
555
|
let lastKey: Key | null = null;
|
|
505
556
|
for (const child of collection.getChildren(key)) {
|
|
506
|
-
if (child.type ===
|
|
557
|
+
if (child.type === "item") lastKey = child.key;
|
|
507
558
|
}
|
|
508
559
|
return lastKey;
|
|
509
560
|
};
|
|
@@ -519,50 +570,64 @@ function createTreeDropTargetDelegate<T extends object>(
|
|
|
519
570
|
return current;
|
|
520
571
|
};
|
|
521
572
|
|
|
522
|
-
if (dir ===
|
|
573
|
+
if (dir === "next") {
|
|
523
574
|
// From null → root
|
|
524
575
|
if (!target) {
|
|
525
|
-
return tryValid({ type:
|
|
576
|
+
return tryValid({ type: "root" });
|
|
526
577
|
}
|
|
527
578
|
// From root → first item 'before'
|
|
528
|
-
if (target.type ===
|
|
579
|
+
if (target.type === "root") {
|
|
529
580
|
const firstKey = collection.getFirstKey();
|
|
530
581
|
if (firstKey != null) {
|
|
531
|
-
return tryValid({ type:
|
|
582
|
+
return tryValid({ type: "item", key: firstKey, dropPosition: "before" });
|
|
532
583
|
}
|
|
533
584
|
return null;
|
|
534
585
|
}
|
|
535
|
-
if (target.type ===
|
|
586
|
+
if (target.type === "item") {
|
|
536
587
|
switch (target.dropPosition) {
|
|
537
|
-
case
|
|
538
|
-
return
|
|
539
|
-
|
|
540
|
-
|
|
588
|
+
case "before":
|
|
589
|
+
return (
|
|
590
|
+
tryValid({ type: "item", key: target.key, dropPosition: "on" }) ??
|
|
591
|
+
tryValid({ type: "item", key: target.key, dropPosition: "after" })
|
|
592
|
+
);
|
|
593
|
+
case "on": {
|
|
541
594
|
// If item is expanded and has children, go to first child 'before'
|
|
542
595
|
if (isExpanded(target.key)) {
|
|
543
596
|
const firstChild = getFirstChildItemKey(target.key);
|
|
544
597
|
if (firstChild != null) {
|
|
545
|
-
return
|
|
546
|
-
|
|
598
|
+
return (
|
|
599
|
+
tryValid({ type: "item", key: firstChild, dropPosition: "before" }) ??
|
|
600
|
+
tryValid({ type: "item", key: firstChild, dropPosition: "on" })
|
|
601
|
+
);
|
|
547
602
|
}
|
|
548
603
|
}
|
|
549
604
|
// Otherwise, next item in collection or 'after'
|
|
550
605
|
const nextKey = collection.getKeyAfter(target.key);
|
|
551
606
|
const targetNode = collection.getItem(target.key);
|
|
552
607
|
const nextNode = nextKey != null ? collection.getItem(nextKey) : null;
|
|
553
|
-
if (
|
|
554
|
-
|
|
555
|
-
|
|
608
|
+
if (
|
|
609
|
+
targetNode &&
|
|
610
|
+
nextNode &&
|
|
611
|
+
nextNode.level != null &&
|
|
612
|
+
targetNode.level != null &&
|
|
613
|
+
nextNode.level >= targetNode.level
|
|
614
|
+
) {
|
|
615
|
+
return (
|
|
616
|
+
tryValid({ type: "item", key: nextNode.key, dropPosition: "before" }) ??
|
|
617
|
+
tryValid({ type: "item", key: target.key, dropPosition: "after" })
|
|
618
|
+
);
|
|
556
619
|
}
|
|
557
|
-
return tryValid({ type:
|
|
620
|
+
return tryValid({ type: "item", key: target.key, dropPosition: "after" });
|
|
558
621
|
}
|
|
559
|
-
case
|
|
622
|
+
case "after": {
|
|
560
623
|
// If item is expanded (and we're at 'after'), first child
|
|
561
624
|
if (isExpanded(target.key)) {
|
|
562
625
|
const firstChild = getFirstChildItemKey(target.key);
|
|
563
626
|
if (firstChild != null) {
|
|
564
|
-
return
|
|
565
|
-
|
|
627
|
+
return (
|
|
628
|
+
tryValid({ type: "item", key: firstChild, dropPosition: "before" }) ??
|
|
629
|
+
tryValid({ type: "item", key: firstChild, dropPosition: "on" })
|
|
630
|
+
);
|
|
566
631
|
}
|
|
567
632
|
}
|
|
568
633
|
// Check if this is the last sibling at its level
|
|
@@ -570,40 +635,43 @@ function createTreeDropTargetDelegate<T extends object>(
|
|
|
570
635
|
const nextSiblingKey = getNodeNextKey(targetNode);
|
|
571
636
|
if (nextSiblingKey != null) {
|
|
572
637
|
const nextSibling = collection.getItem(nextSiblingKey);
|
|
573
|
-
if (nextSibling?.type ===
|
|
574
|
-
return
|
|
575
|
-
|
|
638
|
+
if (nextSibling?.type === "item") {
|
|
639
|
+
return (
|
|
640
|
+
tryValid({ type: "item", key: nextSibling.key, dropPosition: "before" }) ??
|
|
641
|
+
tryValid({ type: "item", key: nextSibling.key, dropPosition: "on" })
|
|
642
|
+
);
|
|
576
643
|
}
|
|
577
644
|
}
|
|
578
645
|
// Traverse up to parent when at last sibling
|
|
579
646
|
if (targetNode?.parentKey != null) {
|
|
580
647
|
const parentNode = collection.getItem(targetNode.parentKey);
|
|
581
648
|
const parentNextKey = getNodeNextKey(parentNode);
|
|
582
|
-
const parentNextNode =
|
|
583
|
-
|
|
584
|
-
|
|
649
|
+
const parentNextNode =
|
|
650
|
+
parentNextKey != null ? collection.getItem(parentNextKey) : null;
|
|
651
|
+
if (parentNextNode?.type === "item") {
|
|
652
|
+
return tryValid({ type: "item", key: parentNextNode.key, dropPosition: "before" });
|
|
585
653
|
}
|
|
586
|
-
if (parentNode?.type ===
|
|
587
|
-
return tryValid({ type:
|
|
654
|
+
if (parentNode?.type === "item") {
|
|
655
|
+
return tryValid({ type: "item", key: parentNode.key, dropPosition: "after" });
|
|
588
656
|
}
|
|
589
657
|
}
|
|
590
658
|
// Reached end — try next item in flat collection
|
|
591
659
|
const nextKey = collection.getKeyAfter(target.key);
|
|
592
660
|
if (nextKey != null) {
|
|
593
|
-
return
|
|
594
|
-
|
|
661
|
+
return (
|
|
662
|
+
tryValid({ type: "item", key: nextKey, dropPosition: "before" }) ??
|
|
663
|
+
tryValid({ type: "item", key: nextKey, dropPosition: "on" })
|
|
664
|
+
);
|
|
595
665
|
}
|
|
596
|
-
|
|
597
|
-
return tryValid({ type: 'root' });
|
|
666
|
+
return tryValid({ type: "root" });
|
|
598
667
|
}
|
|
599
668
|
}
|
|
600
669
|
}
|
|
601
670
|
return null;
|
|
602
671
|
}
|
|
603
672
|
|
|
604
|
-
// dir === 'previous'
|
|
605
673
|
// From null or root → last root-level item 'after'
|
|
606
|
-
if (!target || target.type ===
|
|
674
|
+
if (!target || target.type === "root") {
|
|
607
675
|
const lastKey = collection.getLastKey();
|
|
608
676
|
if (lastKey != null) {
|
|
609
677
|
// Find root-level ancestor of last key
|
|
@@ -613,34 +681,38 @@ function createTreeDropTargetDelegate<T extends object>(
|
|
|
613
681
|
rootKey = node.parentKey;
|
|
614
682
|
node = collection.getItem(rootKey);
|
|
615
683
|
}
|
|
616
|
-
return tryValid({ type:
|
|
684
|
+
return tryValid({ type: "item", key: rootKey, dropPosition: "after" });
|
|
617
685
|
}
|
|
618
686
|
return null;
|
|
619
687
|
}
|
|
620
688
|
|
|
621
|
-
if (target.type ===
|
|
689
|
+
if (target.type === "item") {
|
|
622
690
|
switch (target.dropPosition) {
|
|
623
|
-
case
|
|
691
|
+
case "after": {
|
|
624
692
|
// If expanded with children, go to deepest last child 'after'
|
|
625
693
|
const deepest = getDeepestLastChild(target.key);
|
|
626
694
|
if (deepest !== target.key) {
|
|
627
|
-
return
|
|
628
|
-
|
|
695
|
+
return (
|
|
696
|
+
tryValid({ type: "item", key: deepest, dropPosition: "after" }) ??
|
|
697
|
+
tryValid({ type: "item", key: target.key, dropPosition: "on" })
|
|
698
|
+
);
|
|
629
699
|
}
|
|
630
|
-
return tryValid({ type:
|
|
700
|
+
return tryValid({ type: "item", key: target.key, dropPosition: "on" });
|
|
631
701
|
}
|
|
632
|
-
case
|
|
633
|
-
return tryValid({ type:
|
|
634
|
-
case
|
|
702
|
+
case "on":
|
|
703
|
+
return tryValid({ type: "item", key: target.key, dropPosition: "before" });
|
|
704
|
+
case "before": {
|
|
635
705
|
// Move to the previous sibling's deepest last child 'after'
|
|
636
706
|
const prevKey = collection.getKeyBefore(target.key);
|
|
637
707
|
if (prevKey != null) {
|
|
638
708
|
const deepest = getDeepestLastChild(prevKey);
|
|
639
|
-
return
|
|
640
|
-
|
|
709
|
+
return (
|
|
710
|
+
tryValid({ type: "item", key: deepest, dropPosition: "after" }) ??
|
|
711
|
+
tryValid({ type: "item", key: prevKey, dropPosition: "on" })
|
|
712
|
+
);
|
|
641
713
|
}
|
|
642
714
|
// No previous — go to root
|
|
643
|
-
return tryValid({ type:
|
|
715
|
+
return tryValid({ type: "root" });
|
|
644
716
|
}
|
|
645
717
|
}
|
|
646
718
|
}
|
|
@@ -651,40 +723,40 @@ function createTreeDropTargetDelegate<T extends object>(
|
|
|
651
723
|
return {
|
|
652
724
|
getDropTargetFromPoint(x, y, isValidDropTarget) {
|
|
653
725
|
const baseTarget = delegate.getDropTargetFromPoint(x, y, isValidDropTarget);
|
|
654
|
-
if (!baseTarget || baseTarget.type ===
|
|
726
|
+
if (!baseTarget || baseTarget.type === "root") return baseTarget;
|
|
655
727
|
|
|
656
728
|
const deltaY = y - pointerTracking.lastY;
|
|
657
729
|
const deltaX = x - pointerTracking.lastX;
|
|
658
|
-
let currentYMovement:
|
|
659
|
-
let currentXMovement:
|
|
730
|
+
let currentYMovement: "up" | "down" | null = pointerTracking.yDirection;
|
|
731
|
+
let currentXMovement: "left" | "right" | null = pointerTracking.xDirection;
|
|
660
732
|
|
|
661
733
|
if (Math.abs(deltaY) > Y_SWITCH_THRESHOLD) {
|
|
662
|
-
currentYMovement = deltaY > 0 ?
|
|
734
|
+
currentYMovement = deltaY > 0 ? "down" : "up";
|
|
663
735
|
pointerTracking.yDirection = currentYMovement;
|
|
664
736
|
pointerTracking.lastY = y;
|
|
665
737
|
}
|
|
666
738
|
|
|
667
739
|
if (Math.abs(deltaX) > X_SWITCH_THRESHOLD) {
|
|
668
|
-
currentXMovement = deltaX > 0 ?
|
|
740
|
+
currentXMovement = deltaX > 0 ? "right" : "left";
|
|
669
741
|
pointerTracking.xDirection = currentXMovement;
|
|
670
742
|
pointerTracking.lastX = x;
|
|
671
743
|
}
|
|
672
744
|
|
|
673
745
|
let target: ItemDropTarget = baseTarget;
|
|
674
|
-
if (target.dropPosition ===
|
|
746
|
+
if (target.dropPosition === "before") {
|
|
675
747
|
const keyBefore = state.collection.getKeyBefore(target.key);
|
|
676
748
|
if (keyBefore != null) {
|
|
677
749
|
const normalized: ItemDropTarget = {
|
|
678
|
-
type:
|
|
750
|
+
type: "item",
|
|
679
751
|
key: keyBefore,
|
|
680
|
-
dropPosition:
|
|
752
|
+
dropPosition: "after",
|
|
681
753
|
};
|
|
682
754
|
if (isValidDropTarget(normalized)) target = normalized;
|
|
683
755
|
}
|
|
684
756
|
}
|
|
685
757
|
|
|
686
758
|
const potentialTargets = getPotentialTargets(target, isValidDropTarget);
|
|
687
|
-
if (potentialTargets.length === 0) return { type:
|
|
759
|
+
if (potentialTargets.length === 0) return { type: "root" };
|
|
688
760
|
|
|
689
761
|
if (potentialTargets.length > 1) {
|
|
690
762
|
return selectTarget(potentialTargets, target, x, y, currentYMovement, currentXMovement);
|
|
@@ -706,12 +778,33 @@ interface TreeItemContextValue<T extends object> {
|
|
|
706
778
|
}
|
|
707
779
|
|
|
708
780
|
export const TreeContext = createContext<TreeContextValue<object> | null>(null);
|
|
709
|
-
export const TreeStateContext = createContext<TreeState<object, TreeCollection<object>> | null>(
|
|
781
|
+
export const TreeStateContext = createContext<TreeState<object, TreeCollection<object>> | null>(
|
|
782
|
+
null,
|
|
783
|
+
);
|
|
710
784
|
export const TreeItemContext = createContext<TreeItemContextValue<object> | null>(null);
|
|
785
|
+
const TreeItemContentContext = createContext<TreeItemRenderProps | null>(null);
|
|
786
|
+
|
|
787
|
+
function isTreeItemRecord(value: unknown): value is Record<PropertyKey, unknown> {
|
|
788
|
+
return typeof value === "object" && value !== null;
|
|
789
|
+
}
|
|
711
790
|
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
791
|
+
function treeItemDataFromNode<T extends object>(node: TreeNode<T>): TreeItemData<T> {
|
|
792
|
+
const value = node.value;
|
|
793
|
+
const item: Record<PropertyKey, unknown> = isTreeItemRecord(value) ? value : {};
|
|
794
|
+
return {
|
|
795
|
+
...item,
|
|
796
|
+
key: node.key,
|
|
797
|
+
id: (item.id as Key | undefined) ?? node.key,
|
|
798
|
+
value: (value ?? undefined) as T | undefined,
|
|
799
|
+
textValue: node.textValue,
|
|
800
|
+
isDisabled: node.isDisabled,
|
|
801
|
+
hasChildItems: node.hasChildNodes,
|
|
802
|
+
children:
|
|
803
|
+
node.childNodes.length > 0
|
|
804
|
+
? node.childNodes.map((child) => treeItemDataFromNode(child))
|
|
805
|
+
: undefined,
|
|
806
|
+
};
|
|
807
|
+
}
|
|
715
808
|
|
|
716
809
|
/**
|
|
717
810
|
* A tree displays hierarchical data with expandable/collapsible nodes,
|
|
@@ -720,28 +813,44 @@ export const TreeItemContext = createContext<TreeItemContextValue<object> | null
|
|
|
720
813
|
export function Tree<T extends object>(props: TreeProps<T>): JSX.Element {
|
|
721
814
|
const [local, stateProps, ariaProps] = splitProps(
|
|
722
815
|
props,
|
|
723
|
-
['class', 'style', 'slot', 'renderEmptyState', 'hasMore', 'isLoading', 'onLoadMore', 'dragAndDropHooks'],
|
|
724
816
|
[
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
817
|
+
"class",
|
|
818
|
+
"style",
|
|
819
|
+
"slot",
|
|
820
|
+
"renderEmptyState",
|
|
821
|
+
"hasMore",
|
|
822
|
+
"isLoading",
|
|
823
|
+
"loadingState",
|
|
824
|
+
"onLoadMore",
|
|
825
|
+
"renderLoadMoreItem",
|
|
826
|
+
"loadMoreClass",
|
|
827
|
+
"loadMoreStyle",
|
|
828
|
+
"dragAndDropHooks",
|
|
829
|
+
"ref",
|
|
830
|
+
],
|
|
831
|
+
[
|
|
832
|
+
"items",
|
|
833
|
+
"disabledKeys",
|
|
834
|
+
"disabledBehavior",
|
|
835
|
+
"selectionMode",
|
|
836
|
+
"selectionBehavior",
|
|
837
|
+
"selectedKeys",
|
|
838
|
+
"defaultSelectedKeys",
|
|
839
|
+
"onSelectionChange",
|
|
840
|
+
"expandedKeys",
|
|
841
|
+
"defaultExpandedKeys",
|
|
842
|
+
"onExpandedChange",
|
|
843
|
+
],
|
|
737
844
|
);
|
|
738
845
|
|
|
739
|
-
// Create ref signal
|
|
740
846
|
const [ref, setRef] = createSignal<HTMLDivElement | null>(null);
|
|
741
|
-
const flatItems = createMemo<TreeItemData<T>[]>(() =>
|
|
742
|
-
|
|
847
|
+
const flatItems = createMemo<TreeItemData<T>[]>(() =>
|
|
848
|
+
flattenCollectionEntries(stateProps.items ?? []),
|
|
849
|
+
);
|
|
850
|
+
const hasSections = createMemo(() =>
|
|
851
|
+
(stateProps.items ?? []).some((entry) => isCollectionSection(entry)),
|
|
852
|
+
);
|
|
743
853
|
|
|
744
|
-
// Create tree state
|
|
745
854
|
const state = createTreeState<T, TreeCollection<T>>(() => ({
|
|
746
855
|
collectionFactory: (expandedKeys) =>
|
|
747
856
|
createTreeCollection(flatItems(), expandedKeys) as TreeCollection<T>,
|
|
@@ -773,26 +882,23 @@ export function Tree<T extends object>(props: TreeProps<T>): JSX.Element {
|
|
|
773
882
|
// Resolve writing direction for keyboard expand/collapse parity
|
|
774
883
|
const treeDirection = createMemo(() => ariaProps.direction ?? resolveTreeDirection(ref()));
|
|
775
884
|
|
|
776
|
-
// Create tree aria props
|
|
777
885
|
const { treeProps } = createTree<T, TreeCollection<T>>(
|
|
778
886
|
() => ({
|
|
779
887
|
id: ariaProps.id,
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
888
|
+
"aria-label": ariaProps["aria-label"],
|
|
889
|
+
"aria-labelledby": ariaProps["aria-labelledby"],
|
|
890
|
+
"aria-describedby": ariaProps["aria-describedby"],
|
|
783
891
|
isVirtualized: ariaProps.isVirtualized,
|
|
784
892
|
onAction: ariaProps.onAction,
|
|
785
893
|
isDisabled: ariaProps.isDisabled,
|
|
786
894
|
direction: treeDirection(),
|
|
787
895
|
}),
|
|
788
896
|
() => state,
|
|
789
|
-
ref
|
|
897
|
+
ref,
|
|
790
898
|
);
|
|
791
899
|
|
|
792
|
-
// Create focus ring
|
|
793
900
|
const { isFocused, isFocusVisible, focusProps } = createFocusRing();
|
|
794
901
|
|
|
795
|
-
// Render props values
|
|
796
902
|
const renderValues = createMemo<TreeRenderProps>(() => ({
|
|
797
903
|
isFocused: state.isFocused || isFocused(),
|
|
798
904
|
isFocusVisible: isFocusVisible(),
|
|
@@ -800,23 +906,20 @@ export function Tree<T extends object>(props: TreeProps<T>): JSX.Element {
|
|
|
800
906
|
isEmpty: flatItems().length === 0,
|
|
801
907
|
}));
|
|
802
908
|
|
|
803
|
-
// Resolve render props
|
|
804
909
|
const renderProps = useRenderProps(
|
|
805
910
|
{
|
|
806
911
|
class: local.class,
|
|
807
912
|
style: local.style,
|
|
808
|
-
defaultClassName:
|
|
913
|
+
defaultClassName: "solidaria-Tree",
|
|
809
914
|
},
|
|
810
|
-
renderValues
|
|
915
|
+
renderValues,
|
|
811
916
|
);
|
|
812
917
|
|
|
813
|
-
// Filter DOM props
|
|
814
918
|
const domProps = createMemo(() => {
|
|
815
919
|
const filtered = filterDOMProps(ariaProps as Record<string, unknown>, { global: true });
|
|
816
920
|
return filtered;
|
|
817
921
|
});
|
|
818
922
|
|
|
819
|
-
// Remove ref from spread props
|
|
820
923
|
const cleanTreeProps = () => {
|
|
821
924
|
const { ref: _ref1, ...rest } = treeProps as Record<string, unknown>;
|
|
822
925
|
return rest;
|
|
@@ -828,24 +931,28 @@ export function Tree<T extends object>(props: TreeProps<T>): JSX.Element {
|
|
|
828
931
|
|
|
829
932
|
const isEmpty = () => flatItems().length === 0;
|
|
830
933
|
|
|
831
|
-
// Render visible rows (flat list based on expansion state)
|
|
832
934
|
const visibleRows = createMemo(() => {
|
|
833
935
|
collectionVersion();
|
|
834
936
|
return state.collection.rows;
|
|
835
937
|
});
|
|
836
938
|
const virtualizer = useVirtualizerContext();
|
|
837
939
|
const parentCollectionRenderer = useCollectionRenderer<TreeItemData<T>>();
|
|
838
|
-
const getDropTargetByIndex = (
|
|
940
|
+
const getDropTargetByIndex = (
|
|
941
|
+
index: number,
|
|
942
|
+
position: "before" | "after" | "on",
|
|
943
|
+
): DropTarget | null => {
|
|
839
944
|
const node = visibleRows()[index];
|
|
840
945
|
if (!node) return null;
|
|
841
|
-
return { type:
|
|
946
|
+
return { type: "item", key: node.key, dropPosition: position };
|
|
842
947
|
};
|
|
843
948
|
const hasDroppableDnd = createMemo(() => {
|
|
844
949
|
const hooks = local.dragAndDropHooks;
|
|
845
950
|
return Boolean(
|
|
846
951
|
hooks?.useDroppableCollectionState &&
|
|
847
952
|
hooks.useDroppableCollection &&
|
|
848
|
-
(hooks.dropTargetDelegate ||
|
|
953
|
+
(hooks.dropTargetDelegate ||
|
|
954
|
+
parentCollectionRenderer?.dropTargetDelegate ||
|
|
955
|
+
hooks.ListDropTargetDelegate),
|
|
849
956
|
);
|
|
850
957
|
});
|
|
851
958
|
const hasDraggableDnd = createMemo(() => {
|
|
@@ -869,9 +976,9 @@ export function Tree<T extends object>(props: TreeProps<T>): JSX.Element {
|
|
|
869
976
|
|
|
870
977
|
activeDropState.getDropOperation = (target, types, allowedOperations) => {
|
|
871
978
|
const currentDraggingKeys = dragState()?.draggingKeys ?? new Set<string | number>();
|
|
872
|
-
if (target.type ===
|
|
873
|
-
if (currentDraggingKeys.has(target.key) && target.dropPosition ===
|
|
874
|
-
return
|
|
979
|
+
if (target.type === "item" && currentDraggingKeys.size > 0) {
|
|
980
|
+
if (currentDraggingKeys.has(target.key) && target.dropPosition === "on") {
|
|
981
|
+
return "cancel";
|
|
875
982
|
}
|
|
876
983
|
|
|
877
984
|
let currentKey: Key | null = target.key;
|
|
@@ -879,7 +986,7 @@ export function Tree<T extends object>(props: TreeProps<T>): JSX.Element {
|
|
|
879
986
|
const item = state.collection.getItem(currentKey);
|
|
880
987
|
const parentKey = item?.parentKey;
|
|
881
988
|
if (parentKey != null && currentDraggingKeys.has(parentKey)) {
|
|
882
|
-
return
|
|
989
|
+
return "cancel";
|
|
883
990
|
}
|
|
884
991
|
currentKey = parentKey ?? null;
|
|
885
992
|
}
|
|
@@ -914,21 +1021,22 @@ export function Tree<T extends object>(props: TreeProps<T>): JSX.Element {
|
|
|
914
1021
|
const activeDropState = dropState();
|
|
915
1022
|
if (!hooks?.useDroppableCollection || !activeDropState) return undefined;
|
|
916
1023
|
const direction = resolveTreeDirection(ref());
|
|
917
|
-
const baseDropTargetDelegate =
|
|
918
|
-
??
|
|
919
|
-
??
|
|
1024
|
+
const baseDropTargetDelegate =
|
|
1025
|
+
hooks.dropTargetDelegate ??
|
|
1026
|
+
parentCollectionRenderer?.dropTargetDelegate ??
|
|
1027
|
+
(hooks.ListDropTargetDelegate
|
|
920
1028
|
? new hooks.ListDropTargetDelegate(
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
1029
|
+
() => state.collection,
|
|
1030
|
+
() => ref(),
|
|
1031
|
+
{ layout: "stack", orientation: "vertical", direction },
|
|
1032
|
+
)
|
|
925
1033
|
: undefined);
|
|
926
1034
|
if (!baseDropTargetDelegate) return undefined;
|
|
927
1035
|
const dropTargetDelegate = createTreeDropTargetDelegate(
|
|
928
1036
|
baseDropTargetDelegate as TreeDropTargetDelegate,
|
|
929
1037
|
state,
|
|
930
1038
|
direction,
|
|
931
|
-
virtualizer?.getBaseKeyboardNavigationTarget
|
|
1039
|
+
virtualizer?.getBaseKeyboardNavigationTarget,
|
|
932
1040
|
);
|
|
933
1041
|
return hooks.useDroppableCollection(
|
|
934
1042
|
{
|
|
@@ -941,8 +1049,22 @@ export function Tree<T extends object>(props: TreeProps<T>): JSX.Element {
|
|
|
941
1049
|
getKeyPageBelow: (key) => state.collection.getKeyAfter(key),
|
|
942
1050
|
getKeyPageAbove: (key) => state.collection.getKeyBefore(key),
|
|
943
1051
|
},
|
|
1052
|
+
get collection() {
|
|
1053
|
+
return state.collection;
|
|
1054
|
+
},
|
|
1055
|
+
get selectedKeys() {
|
|
1056
|
+
return state.selectedKeys;
|
|
1057
|
+
},
|
|
1058
|
+
setSelectedKeys: (keys: Set<Key>) => {
|
|
1059
|
+
if (state.selectionMode === "none") return;
|
|
1060
|
+
state.clearSelection();
|
|
1061
|
+
for (const key of keys) {
|
|
1062
|
+
state.toggleSelection(key);
|
|
1063
|
+
}
|
|
1064
|
+
},
|
|
1065
|
+
setFocusedKey: (key) => state.setFocusedKey(key),
|
|
944
1066
|
onDropActivate: (event) => {
|
|
945
|
-
if (event.target.type !==
|
|
1067
|
+
if (event.target.type !== "item") return;
|
|
946
1068
|
const key = event.target.key;
|
|
947
1069
|
const item = state.collection.getItem(key);
|
|
948
1070
|
const isExpanded = state.isExpanded(key);
|
|
@@ -952,11 +1074,12 @@ export function Tree<T extends object>(props: TreeProps<T>): JSX.Element {
|
|
|
952
1074
|
},
|
|
953
1075
|
onKeyDown: (event) => {
|
|
954
1076
|
const target = activeDropState.target;
|
|
955
|
-
if (!target || target.type !==
|
|
1077
|
+
if (!target || target.type !== "item" || target.dropPosition !== "on") return;
|
|
956
1078
|
const item = state.collection.getItem(target.key);
|
|
957
1079
|
if (!item?.hasChildNodes) return;
|
|
958
|
-
const
|
|
959
|
-
const
|
|
1080
|
+
const currentDirection = ariaProps.direction ?? resolveTreeDirection(ref());
|
|
1081
|
+
const expandKey = EXPANSION_KEYS.expand[currentDirection];
|
|
1082
|
+
const collapseKey = EXPANSION_KEYS.collapse[currentDirection];
|
|
960
1083
|
if (event.key === expandKey && !state.isExpanded(target.key)) {
|
|
961
1084
|
state.toggleKey(target.key);
|
|
962
1085
|
} else if (event.key === collapseKey && state.isExpanded(target.key)) {
|
|
@@ -965,23 +1088,25 @@ export function Tree<T extends object>(props: TreeProps<T>): JSX.Element {
|
|
|
965
1088
|
},
|
|
966
1089
|
},
|
|
967
1090
|
activeDropState,
|
|
968
|
-
() => ref()
|
|
1091
|
+
() => ref(),
|
|
969
1092
|
);
|
|
970
1093
|
});
|
|
971
1094
|
const isRootDropTarget = createMemo(() => {
|
|
972
|
-
return Boolean(dropState()?.target?.type ===
|
|
1095
|
+
return Boolean(dropState()?.target?.type === "root");
|
|
973
1096
|
});
|
|
974
|
-
const dndRenderDropIndicator = createMemo(() =>
|
|
975
|
-
|
|
1097
|
+
const dndRenderDropIndicator = createMemo(() =>
|
|
1098
|
+
useRenderDropIndicator(local.dragAndDropHooks, dropState()),
|
|
1099
|
+
);
|
|
1100
|
+
const dndDropIndicator = (index: number, position: "before" | "after" | "on") => {
|
|
976
1101
|
const target = getDropTargetByIndex(index, position);
|
|
977
|
-
if (!target || target.type !==
|
|
1102
|
+
if (!target || target.type !== "item") return undefined;
|
|
978
1103
|
return dndRenderDropIndicator()?.(target);
|
|
979
1104
|
};
|
|
980
1105
|
const persistedKeys = useDndPersistedKeys(
|
|
981
1106
|
{ focusedKey: () => state.focusedKey },
|
|
982
1107
|
local.dragAndDropHooks,
|
|
983
1108
|
dropState(),
|
|
984
|
-
state.collection
|
|
1109
|
+
state.collection,
|
|
985
1110
|
);
|
|
986
1111
|
const virtualRange = createMemo(() => {
|
|
987
1112
|
if (!virtualizer || !parentCollectionRenderer?.isVirtualized) return null;
|
|
@@ -993,16 +1118,24 @@ export function Tree<T extends object>(props: TreeProps<T>): JSX.Element {
|
|
|
993
1118
|
const dropTarget = dropState()?.target;
|
|
994
1119
|
const normalizedDropKey = getNormalizedDropTargetKey(dropTarget, state.collection);
|
|
995
1120
|
const focusedKey = state.focusedKey;
|
|
996
|
-
const focusedIndex =
|
|
1121
|
+
const focusedIndex =
|
|
1122
|
+
focusedKey != null ? rows.findIndex((node) => node.key === focusedKey) : -1;
|
|
997
1123
|
const forceIncludeIndexes = [
|
|
998
|
-
dropTarget?.type ===
|
|
1124
|
+
dropTarget?.type === "item" ? rows.findIndex((node) => node.key === dropTarget.key) : -1,
|
|
999
1125
|
normalizedDropKey != null ? rows.findIndex((node) => node.key === normalizedDropKey) : -1,
|
|
1000
|
-
dropTarget?.type ===
|
|
1126
|
+
dropTarget?.type === "item" ? -1 : focusedIndex,
|
|
1001
1127
|
].filter((index) => index >= 0);
|
|
1002
|
-
return mergePersistedKeysIntoVirtualRange(
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1128
|
+
return mergePersistedKeysIntoVirtualRange(
|
|
1129
|
+
baseRange,
|
|
1130
|
+
persistedIndexes,
|
|
1131
|
+
rows.length,
|
|
1132
|
+
virtualizer,
|
|
1133
|
+
80,
|
|
1134
|
+
{
|
|
1135
|
+
forceIncludeIndexes,
|
|
1136
|
+
forceIncludeMaxSpan: 320,
|
|
1137
|
+
},
|
|
1138
|
+
);
|
|
1006
1139
|
});
|
|
1007
1140
|
const virtualizedVisibleRows = createMemo(() => {
|
|
1008
1141
|
const range = virtualRange();
|
|
@@ -1022,14 +1155,14 @@ export function Tree<T extends object>(props: TreeProps<T>): JSX.Element {
|
|
|
1022
1155
|
if (!node) return target;
|
|
1023
1156
|
return {
|
|
1024
1157
|
...target,
|
|
1025
|
-
key: typeof node.key ===
|
|
1158
|
+
key: typeof node.key === "string" || typeof node.key === "number" ? node.key : undefined,
|
|
1026
1159
|
parentKey:
|
|
1027
|
-
typeof node.parentKey ===
|
|
1160
|
+
typeof node.parentKey === "string" || typeof node.parentKey === "number"
|
|
1028
1161
|
? node.parentKey
|
|
1029
1162
|
: node.parentKey == null
|
|
1030
1163
|
? null
|
|
1031
1164
|
: undefined,
|
|
1032
|
-
level: typeof node.level ===
|
|
1165
|
+
level: typeof node.level === "number" ? node.level : undefined,
|
|
1033
1166
|
};
|
|
1034
1167
|
});
|
|
1035
1168
|
onCleanup(() => {
|
|
@@ -1048,7 +1181,7 @@ export function Tree<T extends object>(props: TreeProps<T>): JSX.Element {
|
|
|
1048
1181
|
});
|
|
1049
1182
|
const getAfterIndicatorIndexes = (
|
|
1050
1183
|
absoluteIndex: number,
|
|
1051
|
-
renderRange?: { start: number; end: number } | null
|
|
1184
|
+
renderRange?: { start: number; end: number } | null,
|
|
1052
1185
|
): number[] => {
|
|
1053
1186
|
const rows = visibleRows();
|
|
1054
1187
|
const current = rows[absoluteIndex];
|
|
@@ -1083,19 +1216,24 @@ export function Tree<T extends object>(props: TreeProps<T>): JSX.Element {
|
|
|
1083
1216
|
if (!virtualizer) return;
|
|
1084
1217
|
const direction = resolveTreeDirection(ref());
|
|
1085
1218
|
const parentDelegate: TreeDropTargetDelegate = {
|
|
1086
|
-
getDropTargetFromPoint:
|
|
1087
|
-
??
|
|
1088
|
-
|
|
1089
|
-
|
|
1219
|
+
getDropTargetFromPoint:
|
|
1220
|
+
parentCollectionRenderer?.dropTargetDelegate?.getDropTargetFromPoint ??
|
|
1221
|
+
((_x, _y, _v) => null),
|
|
1222
|
+
getKeyboardNavigationTarget:
|
|
1223
|
+
parentCollectionRenderer?.dropTargetDelegate?.getKeyboardNavigationTarget,
|
|
1224
|
+
getKeyboardPageNavigationTarget:
|
|
1225
|
+
parentCollectionRenderer?.dropTargetDelegate?.getKeyboardPageNavigationTarget,
|
|
1090
1226
|
};
|
|
1091
1227
|
const treeDelegate = createTreeDropTargetDelegate(
|
|
1092
|
-
parentDelegate,
|
|
1093
|
-
|
|
1228
|
+
parentDelegate,
|
|
1229
|
+
state,
|
|
1230
|
+
direction,
|
|
1231
|
+
virtualizer.getBaseKeyboardNavigationTarget,
|
|
1094
1232
|
);
|
|
1095
1233
|
virtualizer.setKeyboardNavigationOverride(
|
|
1096
1234
|
treeDelegate.getKeyboardNavigationTarget
|
|
1097
1235
|
? (target, dir, isValid) => treeDelegate.getKeyboardNavigationTarget!(target, dir, isValid)
|
|
1098
|
-
: undefined
|
|
1236
|
+
: undefined,
|
|
1099
1237
|
);
|
|
1100
1238
|
onCleanup(() => {
|
|
1101
1239
|
virtualizer.setKeyboardNavigationOverride(undefined);
|
|
@@ -1104,8 +1242,9 @@ export function Tree<T extends object>(props: TreeProps<T>): JSX.Element {
|
|
|
1104
1242
|
const collectionRenderer = createMemo<CollectionRendererContextValue<unknown>>(() => ({
|
|
1105
1243
|
...parentCollectionRenderer,
|
|
1106
1244
|
renderItem: (item) => item as JSX.Element,
|
|
1107
|
-
renderDropIndicator: (index: number, position:
|
|
1108
|
-
dndDropIndicator(index, position) ??
|
|
1245
|
+
renderDropIndicator: (index: number, position: "before" | "after" | "on") =>
|
|
1246
|
+
dndDropIndicator(index, position) ??
|
|
1247
|
+
parentCollectionRenderer?.renderDropIndicator?.(index, position),
|
|
1109
1248
|
}));
|
|
1110
1249
|
const rootKeyByNodeKey = createMemo(() => {
|
|
1111
1250
|
const rootMap = new Map<Key, Key>();
|
|
@@ -1136,11 +1275,11 @@ export function Tree<T extends object>(props: TreeProps<T>): JSX.Element {
|
|
|
1136
1275
|
if (!hasSections()) return null;
|
|
1137
1276
|
const rootMap = rootKeyByNodeKey();
|
|
1138
1277
|
const rows = renderableRows();
|
|
1139
|
-
return stateProps.items.map((entry) => {
|
|
1278
|
+
return (stateProps.items ?? []).map((entry) => {
|
|
1140
1279
|
if (!isCollectionSection(entry)) {
|
|
1141
1280
|
const matching = rows.filter((row) => rootMap.get(row.node.key) === entry.key);
|
|
1142
1281
|
return {
|
|
1143
|
-
type:
|
|
1282
|
+
type: "single" as const,
|
|
1144
1283
|
item: entry,
|
|
1145
1284
|
rows: matching,
|
|
1146
1285
|
};
|
|
@@ -1151,29 +1290,17 @@ export function Tree<T extends object>(props: TreeProps<T>): JSX.Element {
|
|
|
1151
1290
|
return rootKey != null && sectionRootKeys.has(rootKey);
|
|
1152
1291
|
});
|
|
1153
1292
|
return {
|
|
1154
|
-
type:
|
|
1293
|
+
type: "section" as const,
|
|
1155
1294
|
section: entry,
|
|
1156
1295
|
rows: sectionRows,
|
|
1157
1296
|
};
|
|
1158
1297
|
});
|
|
1159
1298
|
});
|
|
1160
1299
|
const renderTreeRow = (node: TreeNode<T>, itemIndex: number) => {
|
|
1161
|
-
const beforeIndicator = () => collectionRenderer().renderDropIndicator?.(itemIndex,
|
|
1162
|
-
const onIndicator = () => collectionRenderer().renderDropIndicator?.(itemIndex,
|
|
1300
|
+
const beforeIndicator = () => collectionRenderer().renderDropIndicator?.(itemIndex, "before");
|
|
1301
|
+
const onIndicator = () => collectionRenderer().renderDropIndicator?.(itemIndex, "on");
|
|
1163
1302
|
const afterIndicatorIndexes = () => getAfterIndicatorIndexes(itemIndex, renderRange());
|
|
1164
|
-
|
|
1165
|
-
const itemData: TreeItemData<T> = {
|
|
1166
|
-
key: node.key,
|
|
1167
|
-
value: node.value as T,
|
|
1168
|
-
textValue: node.textValue,
|
|
1169
|
-
children: node.hasChildNodes
|
|
1170
|
-
? node.childNodes.map((child) => ({
|
|
1171
|
-
key: child.key,
|
|
1172
|
-
value: child.value as T,
|
|
1173
|
-
textValue: child.textValue,
|
|
1174
|
-
}))
|
|
1175
|
-
: undefined,
|
|
1176
|
-
};
|
|
1303
|
+
const itemData = treeItemDataFromNode(node);
|
|
1177
1304
|
const itemState: TreeRenderItemState = {
|
|
1178
1305
|
isExpanded: node.isExpanded ?? false,
|
|
1179
1306
|
isExpandable: node.isExpandable ?? false,
|
|
@@ -1185,7 +1312,7 @@ export function Tree<T extends object>(props: TreeProps<T>): JSX.Element {
|
|
|
1185
1312
|
{onIndicator()}
|
|
1186
1313
|
{props.children(itemData, itemState)}
|
|
1187
1314
|
<For each={afterIndicatorIndexes()}>
|
|
1188
|
-
{(afterIndex) => collectionRenderer().renderDropIndicator?.(afterIndex,
|
|
1315
|
+
{(afterIndex) => collectionRenderer().renderDropIndicator?.(afterIndex, "after")}
|
|
1189
1316
|
</For>
|
|
1190
1317
|
</>
|
|
1191
1318
|
);
|
|
@@ -1193,15 +1320,20 @@ export function Tree<T extends object>(props: TreeProps<T>): JSX.Element {
|
|
|
1193
1320
|
|
|
1194
1321
|
return (
|
|
1195
1322
|
<TreeContext.Provider value={contextValue() as unknown as TreeContextValue<object>}>
|
|
1196
|
-
<TreeStateContext.Provider
|
|
1323
|
+
<TreeStateContext.Provider
|
|
1324
|
+
value={state as unknown as TreeState<object, TreeCollection<object>>}
|
|
1325
|
+
>
|
|
1197
1326
|
<CollectionRendererContext.Provider value={collectionRenderer()}>
|
|
1198
1327
|
<div
|
|
1199
|
-
ref={
|
|
1328
|
+
ref={(element) => {
|
|
1329
|
+
setRef(element);
|
|
1330
|
+
assignRef(local.ref, element);
|
|
1331
|
+
}}
|
|
1200
1332
|
{...mergeProps(
|
|
1201
1333
|
domProps(),
|
|
1202
1334
|
cleanTreeProps(),
|
|
1203
1335
|
cleanFocusProps(),
|
|
1204
|
-
(droppableCollection()?.collectionProps as Record<string, unknown> | undefined) ?? {}
|
|
1336
|
+
(droppableCollection()?.collectionProps as Record<string, unknown> | undefined) ?? {},
|
|
1205
1337
|
)}
|
|
1206
1338
|
class={renderProps.class()}
|
|
1207
1339
|
style={renderProps.style()}
|
|
@@ -1210,57 +1342,86 @@ export function Tree<T extends object>(props: TreeProps<T>): JSX.Element {
|
|
|
1210
1342
|
data-disabled={ariaProps.isDisabled || undefined}
|
|
1211
1343
|
data-empty={isEmpty() || undefined}
|
|
1212
1344
|
data-drop-target={isRootDropTarget() || undefined}
|
|
1213
|
-
data-selection-mode={
|
|
1345
|
+
data-selection-mode={
|
|
1346
|
+
stateProps.selectionMode !== "none" ? stateProps.selectionMode : undefined
|
|
1347
|
+
}
|
|
1214
1348
|
data-allows-dragging={hasDraggableDnd() || undefined}
|
|
1215
1349
|
>
|
|
1216
1350
|
<SharedElementTransition>
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1351
|
+
{isEmpty() && local.renderEmptyState ? (
|
|
1352
|
+
<div role="row" aria-level={1} style={{ display: "contents" }}>
|
|
1353
|
+
<div role="gridcell" style={{ display: "contents" }}>
|
|
1354
|
+
{local.renderEmptyState()}
|
|
1355
|
+
</div>
|
|
1356
|
+
</div>
|
|
1357
|
+
) : (
|
|
1358
|
+
<>
|
|
1359
|
+
{virtualRange()?.offsetTop ? (
|
|
1360
|
+
<div
|
|
1361
|
+
role="presentation"
|
|
1362
|
+
aria-hidden="true"
|
|
1363
|
+
style={{ height: `${virtualRange()!.offsetTop}px` }}
|
|
1364
|
+
data-virtualizer-spacer="top"
|
|
1365
|
+
/>
|
|
1366
|
+
) : null}
|
|
1367
|
+
<Show
|
|
1368
|
+
when={hasSections()}
|
|
1369
|
+
fallback={
|
|
1370
|
+
<For each={renderableRows()}>
|
|
1371
|
+
{(row) => renderTreeRow(row.node, row.globalIndex)}
|
|
1372
|
+
</For>
|
|
1373
|
+
}
|
|
1374
|
+
>
|
|
1375
|
+
<For each={sectionedRenderableRows() ?? []}>
|
|
1376
|
+
{(entry) => (
|
|
1377
|
+
<Show when={entry.rows.length > 0}>
|
|
1378
|
+
<Show
|
|
1379
|
+
when={entry.type === "section"}
|
|
1380
|
+
fallback={
|
|
1381
|
+
<For each={entry.rows}>
|
|
1382
|
+
{(row) => renderTreeRow(row.node, row.globalIndex)}
|
|
1383
|
+
</For>
|
|
1384
|
+
}
|
|
1385
|
+
>
|
|
1386
|
+
<TreeSection>
|
|
1387
|
+
{entry.type === "section" && entry.section.title ? (
|
|
1388
|
+
<TreeHeader>{entry.section.title}</TreeHeader>
|
|
1389
|
+
) : null}
|
|
1390
|
+
<For each={entry.rows}>
|
|
1391
|
+
{(row) => renderTreeRow(row.node, row.globalIndex)}
|
|
1392
|
+
</For>
|
|
1393
|
+
</TreeSection>
|
|
1394
|
+
</Show>
|
|
1251
1395
|
</Show>
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
</
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1396
|
+
)}
|
|
1397
|
+
</For>
|
|
1398
|
+
</Show>
|
|
1399
|
+
{virtualRange()?.offsetBottom ? (
|
|
1400
|
+
<div
|
|
1401
|
+
role="presentation"
|
|
1402
|
+
aria-hidden="true"
|
|
1403
|
+
style={{ height: `${virtualRange()!.offsetBottom}px` }}
|
|
1404
|
+
data-virtualizer-spacer="bottom"
|
|
1405
|
+
/>
|
|
1406
|
+
) : null}
|
|
1407
|
+
</>
|
|
1408
|
+
)}
|
|
1261
1409
|
</SharedElementTransition>
|
|
1262
1410
|
{local.hasMore && local.onLoadMore && (
|
|
1263
|
-
<TreeLoadMoreItem
|
|
1411
|
+
<TreeLoadMoreItem
|
|
1412
|
+
onLoadMore={local.onLoadMore}
|
|
1413
|
+
isLoading={local.isLoading}
|
|
1414
|
+
loadingState={local.loadingState}
|
|
1415
|
+
class={local.loadMoreClass}
|
|
1416
|
+
style={local.loadMoreStyle}
|
|
1417
|
+
>
|
|
1418
|
+
{local.renderLoadMoreItem?.({
|
|
1419
|
+
isLoading:
|
|
1420
|
+
!!local.isLoading ||
|
|
1421
|
+
local.loadingState === "loading" ||
|
|
1422
|
+
local.loadingState === "loadingMore",
|
|
1423
|
+
})}
|
|
1424
|
+
</TreeLoadMoreItem>
|
|
1264
1425
|
)}
|
|
1265
1426
|
</div>
|
|
1266
1427
|
</CollectionRendererContext.Provider>
|
|
@@ -1274,57 +1435,81 @@ export function Tree<T extends object>(props: TreeProps<T>): JSX.Element {
|
|
|
1274
1435
|
*/
|
|
1275
1436
|
export function TreeItem<T extends object>(props: TreeItemProps<T>): JSX.Element {
|
|
1276
1437
|
const [local, domProps] = splitProps(props, [
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1438
|
+
"class",
|
|
1439
|
+
"style",
|
|
1440
|
+
"slot",
|
|
1441
|
+
"id",
|
|
1442
|
+
"item",
|
|
1443
|
+
"textValue",
|
|
1444
|
+
"onAction",
|
|
1445
|
+
"hasChildItems",
|
|
1446
|
+
"isDisabled",
|
|
1447
|
+
"href",
|
|
1448
|
+
"target",
|
|
1449
|
+
"download",
|
|
1450
|
+
"rel",
|
|
1451
|
+
"hrefLang",
|
|
1452
|
+
"ping",
|
|
1453
|
+
"referrerPolicy",
|
|
1454
|
+
"routerOptions",
|
|
1455
|
+
"ref",
|
|
1456
|
+
"children",
|
|
1285
1457
|
]);
|
|
1286
1458
|
|
|
1287
|
-
// Get state from context
|
|
1288
1459
|
const context = useContext(TreeStateContext);
|
|
1289
1460
|
if (!context) {
|
|
1290
|
-
throw new Error(
|
|
1461
|
+
throw new Error("TreeItem must be used within a Tree");
|
|
1291
1462
|
}
|
|
1292
1463
|
const state = context as TreeState<T, TreeCollection<T>>;
|
|
1293
1464
|
const treeContext = useContext(TreeContext) as TreeContextValue<T> | null;
|
|
1465
|
+
const router = useRouter();
|
|
1466
|
+
const linkProps = createMemo(() =>
|
|
1467
|
+
useLinkProps({
|
|
1468
|
+
href: local.href,
|
|
1469
|
+
target: local.target,
|
|
1470
|
+
rel: local.rel,
|
|
1471
|
+
download: local.download,
|
|
1472
|
+
ping: local.ping,
|
|
1473
|
+
referrerPolicy: local.referrerPolicy,
|
|
1474
|
+
}),
|
|
1475
|
+
);
|
|
1294
1476
|
|
|
1295
|
-
|
|
1296
|
-
const
|
|
1477
|
+
const [ref, setRef] = createSignal<HTMLElement | null>(null);
|
|
1478
|
+
const setItemRef = (element: HTMLElement) => {
|
|
1479
|
+
setRef(element);
|
|
1480
|
+
assignRef(local.ref, element);
|
|
1481
|
+
};
|
|
1297
1482
|
|
|
1298
|
-
// Find the item node
|
|
1299
1483
|
const itemNode = createMemo(() => {
|
|
1300
1484
|
const node = state.collection.getItem(local.id);
|
|
1301
1485
|
if (!node) {
|
|
1302
|
-
// Create a simple node for the item
|
|
1303
1486
|
return {
|
|
1304
|
-
type:
|
|
1487
|
+
type: "item" as const,
|
|
1305
1488
|
key: local.id,
|
|
1306
1489
|
value: local.item?.value ?? null,
|
|
1307
1490
|
textValue: local.textValue ?? String(local.id),
|
|
1308
1491
|
level: 0,
|
|
1309
1492
|
index: 0,
|
|
1310
|
-
hasChildNodes:
|
|
1493
|
+
hasChildNodes: !!local.hasChildItems,
|
|
1311
1494
|
childNodes: [],
|
|
1312
|
-
|
|
1495
|
+
isDisabled: local.isDisabled,
|
|
1496
|
+
isExpandable: !!local.hasChildItems,
|
|
1313
1497
|
isExpanded: false,
|
|
1314
1498
|
} as TreeNode<T>;
|
|
1315
1499
|
}
|
|
1316
1500
|
return node;
|
|
1317
1501
|
});
|
|
1318
1502
|
|
|
1319
|
-
// Create item aria props
|
|
1320
1503
|
const treeItemAria = createTreeItem<T, TreeCollection<T>>(
|
|
1321
1504
|
() => ({
|
|
1322
1505
|
node: itemNode(),
|
|
1506
|
+
selectionBehavior: state.selectionBehavior,
|
|
1323
1507
|
onAction: local.onAction,
|
|
1508
|
+
isDisabled: local.isDisabled,
|
|
1324
1509
|
textValue: local.textValue,
|
|
1325
1510
|
}),
|
|
1326
1511
|
() => state,
|
|
1327
|
-
ref
|
|
1512
|
+
ref,
|
|
1328
1513
|
);
|
|
1329
1514
|
const isSelected = () => treeItemAria.isSelected;
|
|
1330
1515
|
const isDisabled = () => treeItemAria.isDisabled;
|
|
@@ -1333,39 +1518,37 @@ export function TreeItem<T extends object>(props: TreeItemProps<T>): JSX.Element
|
|
|
1333
1518
|
const isExpandable = () => treeItemAria.isExpandable;
|
|
1334
1519
|
const level = () => treeItemAria.level;
|
|
1335
1520
|
|
|
1336
|
-
// Create hover
|
|
1337
1521
|
const { isHovered, hoverProps } = createHover({
|
|
1338
1522
|
get isDisabled() {
|
|
1339
1523
|
return isDisabled();
|
|
1340
1524
|
},
|
|
1341
1525
|
});
|
|
1342
1526
|
|
|
1343
|
-
// Create focus ring
|
|
1344
1527
|
const { isFocusVisible, focusProps } = createFocusRing();
|
|
1345
1528
|
|
|
1346
|
-
// Check if focused
|
|
1347
1529
|
const isFocused = createMemo(() => state.focusedKey === local.id);
|
|
1348
1530
|
const draggableItem = createMemo(() => {
|
|
1349
|
-
if (!treeContext?.dragAndDropHooks?.useDraggableItem || !treeContext.dragState)
|
|
1531
|
+
if (!treeContext?.dragAndDropHooks?.useDraggableItem || !treeContext.dragState)
|
|
1532
|
+
return undefined;
|
|
1350
1533
|
return treeContext.dragAndDropHooks.useDraggableItem(
|
|
1351
1534
|
{
|
|
1352
1535
|
key: local.id as string | number,
|
|
1353
1536
|
},
|
|
1354
|
-
treeContext.dragState as Parameters<NonNullable<DragAndDropHooks<T>[
|
|
1537
|
+
treeContext.dragState as Parameters<NonNullable<DragAndDropHooks<T>["useDraggableItem"]>>[1],
|
|
1355
1538
|
);
|
|
1356
1539
|
});
|
|
1357
1540
|
const droppableItem = createMemo(() => {
|
|
1358
|
-
if (!treeContext?.dragAndDropHooks?.useDroppableItem || !treeContext.dropState)
|
|
1541
|
+
if (!treeContext?.dragAndDropHooks?.useDroppableItem || !treeContext.dropState)
|
|
1542
|
+
return undefined;
|
|
1359
1543
|
return treeContext.dragAndDropHooks.useDroppableItem(
|
|
1360
1544
|
{
|
|
1361
1545
|
key: local.id as string | number,
|
|
1362
1546
|
},
|
|
1363
|
-
treeContext.dropState as Parameters<NonNullable<DragAndDropHooks<T>[
|
|
1364
|
-
() => ref()
|
|
1547
|
+
treeContext.dropState as Parameters<NonNullable<DragAndDropHooks<T>["useDroppableItem"]>>[1],
|
|
1548
|
+
() => ref(),
|
|
1365
1549
|
);
|
|
1366
1550
|
});
|
|
1367
1551
|
|
|
1368
|
-
// Render props values
|
|
1369
1552
|
const renderValues = createMemo<TreeItemRenderProps>(() => ({
|
|
1370
1553
|
isSelected: isSelected(),
|
|
1371
1554
|
isFocused: isFocused(),
|
|
@@ -1376,20 +1559,20 @@ export function TreeItem<T extends object>(props: TreeItemProps<T>): JSX.Element
|
|
|
1376
1559
|
isExpanded: isExpanded(),
|
|
1377
1560
|
isExpandable: isExpandable(),
|
|
1378
1561
|
level: level(),
|
|
1562
|
+
selectionMode: state.selectionMode,
|
|
1563
|
+
selectionBehavior: state.selectionBehavior,
|
|
1379
1564
|
}));
|
|
1380
1565
|
|
|
1381
|
-
// Resolve render props
|
|
1382
1566
|
const renderProps = useRenderProps(
|
|
1383
1567
|
{
|
|
1384
1568
|
children: props.children,
|
|
1385
1569
|
class: local.class,
|
|
1386
1570
|
style: local.style,
|
|
1387
|
-
defaultClassName:
|
|
1571
|
+
defaultClassName: "solidaria-Tree-item",
|
|
1388
1572
|
},
|
|
1389
|
-
renderValues
|
|
1573
|
+
renderValues,
|
|
1390
1574
|
);
|
|
1391
1575
|
|
|
1392
|
-
// Remove ref from spread props
|
|
1393
1576
|
const cleanRowProps = () => {
|
|
1394
1577
|
const { ref: _ref1, ...rest } = treeItemAria.rowProps as Record<string, unknown>;
|
|
1395
1578
|
return rest;
|
|
@@ -1403,7 +1586,6 @@ export function TreeItem<T extends object>(props: TreeItemProps<T>): JSX.Element
|
|
|
1403
1586
|
return rest;
|
|
1404
1587
|
};
|
|
1405
1588
|
|
|
1406
|
-
// Item context for nested components
|
|
1407
1589
|
const itemContextValue = createMemo<TreeItemContextValue<T>>(() => ({
|
|
1408
1590
|
node: itemNode(),
|
|
1409
1591
|
isExpanded: isExpanded(),
|
|
@@ -1411,20 +1593,66 @@ export function TreeItem<T extends object>(props: TreeItemProps<T>): JSX.Element
|
|
|
1411
1593
|
level: level(),
|
|
1412
1594
|
}));
|
|
1413
1595
|
|
|
1596
|
+
const rowStyle = () => ({
|
|
1597
|
+
"--tree-item-level": String(level()),
|
|
1598
|
+
...((typeof renderProps.style() === "object" ? renderProps.style() : {}) as Record<
|
|
1599
|
+
string,
|
|
1600
|
+
string
|
|
1601
|
+
>),
|
|
1602
|
+
});
|
|
1603
|
+
|
|
1604
|
+
const rowContent = () => (
|
|
1605
|
+
<TreeItemContentContext.Provider value={renderValues()}>
|
|
1606
|
+
<div {...treeItemAria.gridCellProps} class="solidaria-Tree-item-content">
|
|
1607
|
+
{renderProps.renderChildren()}
|
|
1608
|
+
</div>
|
|
1609
|
+
</TreeItemContentContext.Provider>
|
|
1610
|
+
);
|
|
1611
|
+
|
|
1612
|
+
const mergedRowProps = () =>
|
|
1613
|
+
mergeProps(
|
|
1614
|
+
cleanRowProps(),
|
|
1615
|
+
cleanHoverProps(),
|
|
1616
|
+
cleanFocusProps(),
|
|
1617
|
+
(draggableItem()?.dragProps as Record<string, unknown> | undefined) ?? {},
|
|
1618
|
+
(droppableItem()?.dropProps as Record<string, unknown> | undefined) ?? {},
|
|
1619
|
+
);
|
|
1620
|
+
|
|
1621
|
+
const onLinkedRowClick = (event: MouseEvent) => {
|
|
1622
|
+
const onClick = (mergedRowProps() as { onClick?: (event: MouseEvent) => void }).onClick;
|
|
1623
|
+
onClick?.(event);
|
|
1624
|
+
handleLinkClick(event, router, local.href, local.routerOptions);
|
|
1625
|
+
};
|
|
1626
|
+
|
|
1627
|
+
const downloadAttr = () => {
|
|
1628
|
+
const download = linkProps().download;
|
|
1629
|
+
return typeof download === "boolean" ? (download ? "" : undefined) : download;
|
|
1630
|
+
};
|
|
1631
|
+
|
|
1632
|
+
const referrerPolicyAttr = () => linkProps().referrerPolicy || undefined;
|
|
1633
|
+
const linkedRowDomProps = () =>
|
|
1634
|
+
local.href
|
|
1635
|
+
? {
|
|
1636
|
+
onClick: onLinkedRowClick,
|
|
1637
|
+
"data-href": linkProps().href,
|
|
1638
|
+
"data-target": linkProps().target,
|
|
1639
|
+
"data-download": downloadAttr(),
|
|
1640
|
+
"data-rel": linkProps().rel,
|
|
1641
|
+
"data-hreflang": local.hrefLang,
|
|
1642
|
+
"data-ping": linkProps().ping,
|
|
1643
|
+
"data-referrer-policy": referrerPolicyAttr(),
|
|
1644
|
+
}
|
|
1645
|
+
: {};
|
|
1646
|
+
|
|
1414
1647
|
return (
|
|
1415
1648
|
<TreeItemContext.Provider value={itemContextValue() as unknown as TreeItemContextValue<object>}>
|
|
1416
1649
|
<div
|
|
1417
|
-
ref={
|
|
1650
|
+
ref={setItemRef}
|
|
1418
1651
|
{...domProps}
|
|
1419
|
-
{...
|
|
1420
|
-
|
|
1421
|
-
cleanHoverProps(),
|
|
1422
|
-
cleanFocusProps(),
|
|
1423
|
-
(draggableItem()?.dragProps as Record<string, unknown> | undefined) ?? {},
|
|
1424
|
-
(droppableItem()?.dropProps as Record<string, unknown> | undefined) ?? {}
|
|
1425
|
-
)}
|
|
1652
|
+
{...mergedRowProps()}
|
|
1653
|
+
{...linkedRowDomProps()}
|
|
1426
1654
|
class={renderProps.class()}
|
|
1427
|
-
style={
|
|
1655
|
+
style={rowStyle()}
|
|
1428
1656
|
data-selected={isSelected() || undefined}
|
|
1429
1657
|
data-focused={isFocused() || undefined}
|
|
1430
1658
|
data-focus-visible={(isFocusVisible() && isFocused()) || undefined}
|
|
@@ -1435,13 +1663,13 @@ export function TreeItem<T extends object>(props: TreeItemProps<T>): JSX.Element
|
|
|
1435
1663
|
data-expandable={isExpandable() || undefined}
|
|
1436
1664
|
data-has-child-items={isExpandable() || undefined}
|
|
1437
1665
|
data-level={level()}
|
|
1438
|
-
data-selection-mode={
|
|
1666
|
+
data-selection-mode={
|
|
1667
|
+
treeContext?.state.selectionMode !== "none" ? treeContext?.state.selectionMode : undefined
|
|
1668
|
+
}
|
|
1439
1669
|
data-dragging={draggableItem()?.isDragging || undefined}
|
|
1440
1670
|
data-drop-target={droppableItem()?.isDropTarget || undefined}
|
|
1441
1671
|
>
|
|
1442
|
-
|
|
1443
|
-
{renderProps.renderChildren()}
|
|
1444
|
-
</div>
|
|
1672
|
+
{rowContent()}
|
|
1445
1673
|
</div>
|
|
1446
1674
|
</TreeItemContext.Provider>
|
|
1447
1675
|
);
|
|
@@ -1451,38 +1679,42 @@ export function TreeItem<T extends object>(props: TreeItemProps<T>): JSX.Element
|
|
|
1451
1679
|
* A button to expand/collapse a tree item.
|
|
1452
1680
|
*/
|
|
1453
1681
|
export function TreeExpandButton(props: TreeExpandButtonProps): JSX.Element {
|
|
1454
|
-
// Get item context
|
|
1455
1682
|
const itemContext = useContext(TreeItemContext);
|
|
1456
1683
|
if (!itemContext) {
|
|
1457
|
-
throw new Error(
|
|
1684
|
+
throw new Error("TreeExpandButton must be used within a Tree");
|
|
1458
1685
|
}
|
|
1459
1686
|
|
|
1460
|
-
// Get state context
|
|
1461
1687
|
const stateContext = useContext(TreeStateContext);
|
|
1462
1688
|
if (!stateContext) {
|
|
1463
|
-
throw new Error(
|
|
1689
|
+
throw new Error("TreeExpandButton must be used within a Tree");
|
|
1464
1690
|
}
|
|
1465
1691
|
|
|
1466
1692
|
const state = stateContext as TreeState<object, TreeCollection<object>>;
|
|
1467
1693
|
|
|
1468
|
-
// Create expand button props
|
|
1469
1694
|
const treeItemAria = createTreeItem(
|
|
1470
1695
|
() => ({ node: itemContext.node }),
|
|
1471
1696
|
() => state,
|
|
1472
|
-
() => null
|
|
1697
|
+
() => null,
|
|
1473
1698
|
);
|
|
1474
1699
|
|
|
1475
|
-
// Remove ref and add custom handling
|
|
1476
1700
|
const cleanExpandProps = () => {
|
|
1477
1701
|
const { ref: _ref, ...rest } = treeItemAria.expandButtonProps as Record<string, unknown>;
|
|
1478
1702
|
return rest;
|
|
1479
1703
|
};
|
|
1704
|
+
const dataProps = () => {
|
|
1705
|
+
const result: Record<string, string | undefined> = {};
|
|
1706
|
+
for (const key in props) {
|
|
1707
|
+
if (key.startsWith("data-")) {
|
|
1708
|
+
result[key] = props[key as `data-${string}`];
|
|
1709
|
+
}
|
|
1710
|
+
}
|
|
1711
|
+
return result;
|
|
1712
|
+
};
|
|
1480
1713
|
|
|
1481
1714
|
const isExpanded = createMemo(() => state.isExpanded(itemContext.node.key));
|
|
1482
1715
|
|
|
1483
|
-
// Render children
|
|
1484
1716
|
const renderChildren = () => {
|
|
1485
|
-
if (typeof props.children ===
|
|
1717
|
+
if (typeof props.children === "function") {
|
|
1486
1718
|
return props.children({ isExpanded: isExpanded() });
|
|
1487
1719
|
}
|
|
1488
1720
|
return props.children;
|
|
@@ -1492,7 +1724,8 @@ export function TreeExpandButton(props: TreeExpandButtonProps): JSX.Element {
|
|
|
1492
1724
|
<Show when={itemContext.isExpandable}>
|
|
1493
1725
|
<button
|
|
1494
1726
|
{...cleanExpandProps()}
|
|
1495
|
-
|
|
1727
|
+
{...dataProps()}
|
|
1728
|
+
class={props.class ?? "solidaria-Tree-expand-button"}
|
|
1496
1729
|
style={props.style}
|
|
1497
1730
|
data-expanded={isExpanded() || undefined}
|
|
1498
1731
|
>
|
|
@@ -1505,26 +1738,44 @@ export function TreeExpandButton(props: TreeExpandButtonProps): JSX.Element {
|
|
|
1505
1738
|
/**
|
|
1506
1739
|
* A checkbox for item selection in a tree.
|
|
1507
1740
|
*/
|
|
1508
|
-
export function TreeSelectionCheckbox(props: {
|
|
1741
|
+
export function TreeSelectionCheckbox(props: {
|
|
1742
|
+
itemKey: Key;
|
|
1743
|
+
class?: string;
|
|
1744
|
+
style?: JSX.CSSProperties;
|
|
1745
|
+
excludeFromTabOrder?: boolean;
|
|
1746
|
+
"aria-label"?: string;
|
|
1747
|
+
}): JSX.Element {
|
|
1509
1748
|
const context = useContext(TreeStateContext);
|
|
1510
1749
|
if (!context) {
|
|
1511
|
-
throw new Error(
|
|
1750
|
+
throw new Error("TreeSelectionCheckbox must be used within a Tree");
|
|
1512
1751
|
}
|
|
1513
1752
|
|
|
1514
1753
|
const state = context as TreeState<object, TreeCollection<object>>;
|
|
1515
1754
|
|
|
1516
1755
|
const treeSelectionCheckboxAria = createTreeSelectionCheckbox<object, TreeCollection<object>>(
|
|
1517
1756
|
() => ({ key: props.itemKey }),
|
|
1518
|
-
() => state
|
|
1757
|
+
() => state,
|
|
1519
1758
|
);
|
|
1520
1759
|
|
|
1521
|
-
return
|
|
1760
|
+
return (
|
|
1761
|
+
<input
|
|
1762
|
+
{...treeSelectionCheckboxAria.checkboxProps}
|
|
1763
|
+
class={props.class ?? "solidaria-Tree-checkbox"}
|
|
1764
|
+
style={props.style}
|
|
1765
|
+
tabIndex={props.excludeFromTabOrder ? -1 : undefined}
|
|
1766
|
+
aria-label={props["aria-label"] ?? treeSelectionCheckboxAria.checkboxProps["aria-label"]}
|
|
1767
|
+
/>
|
|
1768
|
+
);
|
|
1522
1769
|
}
|
|
1523
1770
|
|
|
1524
1771
|
export function TreeLoadMoreItem(props: TreeLoadMoreItemProps): JSX.Element {
|
|
1525
|
-
let
|
|
1772
|
+
let sentinelRef: HTMLDivElement | undefined;
|
|
1526
1773
|
const [isPending, setIsPending] = createSignal(false);
|
|
1527
|
-
const isLoading = () =>
|
|
1774
|
+
const isLoading = () =>
|
|
1775
|
+
!!props.isLoading ||
|
|
1776
|
+
props.loadingState === "loading" ||
|
|
1777
|
+
props.loadingState === "loadingMore" ||
|
|
1778
|
+
isPending();
|
|
1528
1779
|
|
|
1529
1780
|
const triggerLoadMore = async () => {
|
|
1530
1781
|
if (isLoading()) return;
|
|
@@ -1537,49 +1788,82 @@ export function TreeLoadMoreItem(props: TreeLoadMoreItemProps): JSX.Element {
|
|
|
1537
1788
|
};
|
|
1538
1789
|
|
|
1539
1790
|
createEffect(() => {
|
|
1540
|
-
if (!
|
|
1541
|
-
const
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1791
|
+
if (!sentinelRef || typeof IntersectionObserver !== "function") return;
|
|
1792
|
+
const offset = props.scrollOffset ?? 1;
|
|
1793
|
+
const margin = `0px 0px ${100 * offset}% 0px`;
|
|
1794
|
+
const observer = new IntersectionObserver(
|
|
1795
|
+
(entries) => {
|
|
1796
|
+
if (entries[0]?.isIntersecting) {
|
|
1797
|
+
void triggerLoadMore();
|
|
1798
|
+
}
|
|
1799
|
+
},
|
|
1800
|
+
{ rootMargin: margin },
|
|
1801
|
+
);
|
|
1802
|
+
observer.observe(sentinelRef);
|
|
1547
1803
|
return () => observer.disconnect();
|
|
1548
1804
|
});
|
|
1549
1805
|
|
|
1550
1806
|
const renderProps = useRenderProps(
|
|
1551
1807
|
{
|
|
1552
|
-
children: props.children ?? (() => (isLoading() ?
|
|
1808
|
+
children: props.children ?? (() => (isLoading() ? "Loading more..." : "Load more")),
|
|
1553
1809
|
class: props.class,
|
|
1554
1810
|
style: props.style,
|
|
1555
|
-
defaultClassName:
|
|
1811
|
+
defaultClassName: "solidaria-Tree-loadMore",
|
|
1556
1812
|
},
|
|
1557
|
-
() => ({ isLoading: isLoading() })
|
|
1813
|
+
() => ({ isLoading: isLoading() }),
|
|
1558
1814
|
);
|
|
1559
1815
|
|
|
1560
1816
|
return (
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1817
|
+
<>
|
|
1818
|
+
<div style={{ position: "relative", width: 0, height: 0, overflow: "hidden" }} inert>
|
|
1819
|
+
<div ref={sentinelRef} style={{ position: "absolute", height: "1px", width: "1px" }} />
|
|
1820
|
+
</div>
|
|
1821
|
+
<div
|
|
1822
|
+
role="row"
|
|
1823
|
+
aria-level={props.level ?? 1}
|
|
1824
|
+
onFocus={() => {
|
|
1825
|
+
void triggerLoadMore();
|
|
1826
|
+
}}
|
|
1827
|
+
onFocusIn={() => {
|
|
1828
|
+
void triggerLoadMore();
|
|
1829
|
+
}}
|
|
1830
|
+
class={renderProps.class()}
|
|
1831
|
+
style={renderProps.style()}
|
|
1832
|
+
data-loading={isLoading() || undefined}
|
|
1833
|
+
data-level={props.level ?? 1}
|
|
1834
|
+
>
|
|
1835
|
+
<div
|
|
1836
|
+
role="gridcell"
|
|
1837
|
+
onFocus={() => {
|
|
1838
|
+
void triggerLoadMore();
|
|
1839
|
+
}}
|
|
1840
|
+
>
|
|
1841
|
+
{renderProps.renderChildren()}
|
|
1842
|
+
</div>
|
|
1843
|
+
</div>
|
|
1844
|
+
</>
|
|
1575
1845
|
);
|
|
1576
1846
|
}
|
|
1577
1847
|
|
|
1578
|
-
export interface TreeItemContentProps
|
|
1848
|
+
export interface TreeItemContentProps {
|
|
1849
|
+
children?: RenderChildren<TreeItemContentRenderProps>;
|
|
1850
|
+
}
|
|
1579
1851
|
export type TreeItemContentRenderProps = TreeItemRenderProps;
|
|
1580
1852
|
|
|
1581
|
-
export function TreeItemContent
|
|
1582
|
-
|
|
1853
|
+
export function TreeItemContent(props: TreeItemContentProps): JSX.Element {
|
|
1854
|
+
const context = useContext(TreeItemContentContext);
|
|
1855
|
+
if (!context) {
|
|
1856
|
+
throw new Error("TreeItemContent must be used within a TreeItem");
|
|
1857
|
+
}
|
|
1858
|
+
|
|
1859
|
+
const renderProps = useRenderProps(
|
|
1860
|
+
{
|
|
1861
|
+
children: props.children,
|
|
1862
|
+
},
|
|
1863
|
+
() => context,
|
|
1864
|
+
);
|
|
1865
|
+
|
|
1866
|
+
return <>{renderProps.renderChildren()}</>;
|
|
1583
1867
|
}
|
|
1584
1868
|
|
|
1585
1869
|
export function TreeSection(props: TreeSectionProps): JSX.Element {
|
|
@@ -1590,7 +1874,6 @@ export function TreeHeader(props: TreeHeaderProps): JSX.Element {
|
|
|
1590
1874
|
return <Header {...props} />;
|
|
1591
1875
|
}
|
|
1592
1876
|
|
|
1593
|
-
// Attach static properties
|
|
1594
1877
|
Tree.Item = TreeItem;
|
|
1595
1878
|
Tree.ExpandButton = TreeExpandButton;
|
|
1596
1879
|
Tree.SelectionCheckbox = TreeSelectionCheckbox;
|