@dxos/react-ui-list 0.8.4-main.69d29f4 → 0.8.4-main.6fa680abb7
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/dist/lib/browser/index.mjs +192 -166
- package/dist/lib/browser/index.mjs.map +3 -3
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +192 -166
- package/dist/lib/node-esm/index.mjs.map +3 -3
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/types/src/components/List/List.d.ts.map +1 -1
- package/dist/types/src/components/List/ListItem.d.ts +2 -2
- package/dist/types/src/components/List/ListItem.d.ts.map +1 -1
- package/dist/types/src/components/Tree/Tree.d.ts +6 -5
- package/dist/types/src/components/Tree/Tree.d.ts.map +1 -1
- package/dist/types/src/components/Tree/Tree.stories.d.ts +1 -1
- package/dist/types/src/components/Tree/Tree.stories.d.ts.map +1 -1
- package/dist/types/src/components/Tree/TreeContext.d.ts +21 -10
- package/dist/types/src/components/Tree/TreeContext.d.ts.map +1 -1
- package/dist/types/src/components/Tree/TreeItem.d.ts +8 -0
- package/dist/types/src/components/Tree/TreeItem.d.ts.map +1 -1
- package/dist/types/src/components/Tree/TreeItemHeading.d.ts.map +1 -1
- package/dist/types/src/components/Tree/index.d.ts +2 -0
- package/dist/types/src/components/Tree/index.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +18 -18
- package/src/components/Accordion/Accordion.stories.tsx +3 -3
- package/src/components/Accordion/AccordionItem.tsx +1 -1
- package/src/components/List/List.stories.tsx +7 -7
- package/src/components/List/ListItem.tsx +10 -10
- package/src/components/Tree/Tree.stories.tsx +102 -26
- package/src/components/Tree/Tree.tsx +30 -40
- package/src/components/Tree/TreeContext.tsx +18 -9
- package/src/components/Tree/TreeItem.tsx +166 -99
- package/src/components/Tree/TreeItemHeading.tsx +5 -3
- package/src/components/Tree/TreeItemToggle.tsx +1 -1
- package/src/components/Tree/index.ts +2 -0
|
@@ -10,8 +10,19 @@ import {
|
|
|
10
10
|
attachInstruction,
|
|
11
11
|
extractInstruction,
|
|
12
12
|
} from '@atlaskit/pragmatic-drag-and-drop-hitbox/tree-item';
|
|
13
|
+
import { useAtomValue } from '@effect-atom/atom-react';
|
|
13
14
|
import * as Schema from 'effect/Schema';
|
|
14
|
-
import React, {
|
|
15
|
+
import React, {
|
|
16
|
+
type FC,
|
|
17
|
+
type KeyboardEvent,
|
|
18
|
+
type MouseEvent,
|
|
19
|
+
memo,
|
|
20
|
+
useCallback,
|
|
21
|
+
useEffect,
|
|
22
|
+
useMemo,
|
|
23
|
+
useRef,
|
|
24
|
+
useState,
|
|
25
|
+
} from 'react';
|
|
15
26
|
|
|
16
27
|
import { invariant } from '@dxos/invariant';
|
|
17
28
|
import { TreeItem as NaturalTreeItem, Treegrid } from '@dxos/react-ui';
|
|
@@ -21,6 +32,7 @@ import {
|
|
|
21
32
|
hoverableControls,
|
|
22
33
|
hoverableFocusedKeyboardControls,
|
|
23
34
|
hoverableFocusedWithinControls,
|
|
35
|
+
mx,
|
|
24
36
|
} from '@dxos/ui-theme';
|
|
25
37
|
|
|
26
38
|
import { DEFAULT_INDENTATION, paddingIndentation } from './helpers';
|
|
@@ -31,7 +43,7 @@ import { TreeItemToggle } from './TreeItemToggle';
|
|
|
31
43
|
const hoverableDescriptionIcons =
|
|
32
44
|
'[--icons-color:inherit] hover-hover:[--icons-color:var(--description-text)] hover-hover:hover:[--icons-color:inherit] focus-within:[--icons-color:inherit]';
|
|
33
45
|
|
|
34
|
-
type
|
|
46
|
+
type TreeItemDragState = 'idle' | 'dragging' | 'preview' | 'parent-of-instruction';
|
|
35
47
|
|
|
36
48
|
export const TreeDataSchema = Schema.Struct({
|
|
37
49
|
id: Schema.String,
|
|
@@ -62,39 +74,61 @@ export type TreeItemProps<T extends { id: string } = any> = {
|
|
|
62
74
|
canSelect?: (params: { item: T; path: string[] }) => boolean;
|
|
63
75
|
onOpenChange?: (params: { item: T; path: string[]; open: boolean }) => void;
|
|
64
76
|
onSelect?: (params: { item: T; path: string[]; current: boolean; option: boolean }) => void;
|
|
77
|
+
onItemHover?: (params: { item: T }) => void;
|
|
65
78
|
};
|
|
66
79
|
|
|
67
80
|
const RawTreeItem = <T extends { id: string } = any>({
|
|
68
81
|
item,
|
|
69
|
-
path:
|
|
82
|
+
path: pathProp,
|
|
70
83
|
levelOffset = 2,
|
|
71
84
|
last,
|
|
72
|
-
draggable:
|
|
85
|
+
draggable: draggableProp,
|
|
73
86
|
renderColumns: Columns,
|
|
74
87
|
blockInstruction,
|
|
75
88
|
canDrop,
|
|
76
89
|
canSelect,
|
|
77
90
|
onOpenChange,
|
|
78
91
|
onSelect,
|
|
92
|
+
onItemHover,
|
|
79
93
|
}: TreeItemProps<T>) => {
|
|
80
94
|
const rowRef = useRef<HTMLDivElement | null>(null);
|
|
81
95
|
const buttonRef = useRef<HTMLButtonElement | null>(null);
|
|
82
96
|
const openRef = useRef(false);
|
|
83
97
|
const cancelExpandRef = useRef<NodeJS.Timeout | null>(null);
|
|
84
|
-
const [_state, setState] = useState<
|
|
98
|
+
const [_state, setState] = useState<TreeItemDragState>('idle');
|
|
85
99
|
const [instruction, setInstruction] = useState<Instruction | null>(null);
|
|
86
100
|
const [menuOpen, setMenuOpen] = useState(false);
|
|
87
101
|
|
|
88
|
-
const {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
102
|
+
const {
|
|
103
|
+
itemProps: itemPropsAtom,
|
|
104
|
+
childIds: childIdsAtom,
|
|
105
|
+
itemOpen: itemOpenAtom,
|
|
106
|
+
itemCurrent: itemCurrentAtom,
|
|
107
|
+
} = useTree();
|
|
108
|
+
const path = useMemo(() => [...pathProp, item.id], [pathProp, item.id]);
|
|
109
|
+
|
|
110
|
+
const {
|
|
111
|
+
id,
|
|
112
|
+
parentOf,
|
|
113
|
+
draggable: itemDraggable,
|
|
114
|
+
droppable: itemDroppable,
|
|
115
|
+
label,
|
|
116
|
+
className,
|
|
117
|
+
headingClassName,
|
|
118
|
+
icon,
|
|
119
|
+
iconHue,
|
|
120
|
+
disabled,
|
|
121
|
+
testId,
|
|
122
|
+
} = useAtomValue(itemPropsAtom(path));
|
|
123
|
+
const childIds = useAtomValue(childIdsAtom(item.id));
|
|
124
|
+
const open = useAtomValue(itemOpenAtom(path));
|
|
125
|
+
const current = useAtomValue(itemCurrentAtom(path));
|
|
126
|
+
|
|
94
127
|
const level = path.length - levelOffset;
|
|
95
128
|
const isBranch = !!parentOf;
|
|
96
129
|
const mode: ItemMode = last ? 'last-in-group' : open ? 'expanded' : 'standard';
|
|
97
130
|
const canSelectItem = canSelect?.({ item, path }) ?? true;
|
|
131
|
+
const data = { id, path, item } satisfies TreeData;
|
|
98
132
|
|
|
99
133
|
const cancelExpand = useCallback(() => {
|
|
100
134
|
if (cancelExpandRef.current) {
|
|
@@ -103,19 +137,19 @@ const RawTreeItem = <T extends { id: string } = any>({
|
|
|
103
137
|
}
|
|
104
138
|
}, []);
|
|
105
139
|
|
|
140
|
+
const isItemDraggable = draggableProp && itemDraggable !== false;
|
|
141
|
+
const isItemDroppable = itemDroppable !== false;
|
|
142
|
+
|
|
106
143
|
useEffect(() => {
|
|
107
|
-
if (!
|
|
144
|
+
if (!draggableProp) {
|
|
108
145
|
return;
|
|
109
146
|
}
|
|
110
147
|
|
|
111
148
|
invariant(buttonRef.current);
|
|
112
149
|
|
|
113
|
-
const
|
|
114
|
-
|
|
115
|
-
// https://atlassian.design/components/pragmatic-drag-and-drop/core-package/adapters/element/about
|
|
116
|
-
return combine(
|
|
150
|
+
const makeDraggable = () =>
|
|
117
151
|
draggable({
|
|
118
|
-
element: buttonRef.current
|
|
152
|
+
element: buttonRef.current!,
|
|
119
153
|
getInitialData: () => data,
|
|
120
154
|
onDragStart: () => {
|
|
121
155
|
setState('dragging');
|
|
@@ -130,62 +164,72 @@ const RawTreeItem = <T extends { id: string } = any>({
|
|
|
130
164
|
onOpenChange?.({ item, path, open: true });
|
|
131
165
|
}
|
|
132
166
|
},
|
|
133
|
-
})
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
setInstruction(instruction);
|
|
171
|
-
} else if (instruction?.type === 'reparent') {
|
|
172
|
-
// TODO(wittjosiah): This is not occurring in the current implementation.
|
|
173
|
-
setInstruction(instruction);
|
|
174
|
-
} else {
|
|
175
|
-
setInstruction(null);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
if (!isItemDroppable) {
|
|
170
|
+
return isItemDraggable ? makeDraggable() : undefined;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const dropTarget = dropTargetForElements({
|
|
174
|
+
element: buttonRef.current,
|
|
175
|
+
getData: ({ input, element }) => {
|
|
176
|
+
return attachInstruction(data, {
|
|
177
|
+
input,
|
|
178
|
+
element,
|
|
179
|
+
indentPerLevel: DEFAULT_INDENTATION,
|
|
180
|
+
currentLevel: level,
|
|
181
|
+
mode,
|
|
182
|
+
block: isBranch ? [] : ['make-child'],
|
|
183
|
+
});
|
|
184
|
+
},
|
|
185
|
+
canDrop: ({ source }) => {
|
|
186
|
+
const _canDrop = canDrop ?? (() => true);
|
|
187
|
+
return source.element !== buttonRef.current && _canDrop({ source: source.data as TreeData, target: data });
|
|
188
|
+
},
|
|
189
|
+
getIsSticky: () => true,
|
|
190
|
+
onDrag: ({ self, source }) => {
|
|
191
|
+
const desired = extractInstruction(self.data);
|
|
192
|
+
const block =
|
|
193
|
+
desired && blockInstruction?.({ instruction: desired, source: source.data as TreeData, target: data });
|
|
194
|
+
const instruction: Instruction | null =
|
|
195
|
+
block && desired.type !== 'instruction-blocked' ? { type: 'instruction-blocked', desired } : desired;
|
|
196
|
+
|
|
197
|
+
if (source.data.id !== id) {
|
|
198
|
+
if (instruction?.type === 'make-child' && isBranch && !open && !cancelExpandRef.current) {
|
|
199
|
+
cancelExpandRef.current = setTimeout(() => {
|
|
200
|
+
onOpenChange?.({ item, path, open: true });
|
|
201
|
+
}, 500);
|
|
176
202
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
203
|
+
|
|
204
|
+
if (instruction?.type !== 'make-child') {
|
|
205
|
+
cancelExpand();
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
setInstruction(instruction);
|
|
209
|
+
} else if (instruction?.type === 'reparent') {
|
|
210
|
+
// TODO(wittjosiah): This is not occurring in the current implementation.
|
|
211
|
+
setInstruction(instruction);
|
|
212
|
+
} else {
|
|
184
213
|
setInstruction(null);
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
|
|
214
|
+
}
|
|
215
|
+
},
|
|
216
|
+
onDragLeave: () => {
|
|
217
|
+
cancelExpand();
|
|
218
|
+
setInstruction(null);
|
|
219
|
+
},
|
|
220
|
+
onDrop: () => {
|
|
221
|
+
cancelExpand();
|
|
222
|
+
setInstruction(null);
|
|
223
|
+
},
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
if (!isItemDraggable) {
|
|
227
|
+
return dropTarget;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// https://atlassian.design/components/pragmatic-drag-and-drop/core-package/adapters/element/about
|
|
231
|
+
return combine(makeDraggable(), dropTarget);
|
|
232
|
+
}, [draggableProp, isItemDraggable, isItemDroppable, item, id, mode, path, open, blockInstruction, canDrop]);
|
|
189
233
|
|
|
190
234
|
// Cancel expand on unmount.
|
|
191
235
|
useEffect(() => () => cancelExpand(), [cancelExpand]);
|
|
@@ -223,6 +267,29 @@ const RawTreeItem = <T extends { id: string } = any>({
|
|
|
223
267
|
[isBranch, open, handleOpenToggle, handleSelect],
|
|
224
268
|
);
|
|
225
269
|
|
|
270
|
+
const handleItemHover = useCallback(() => {
|
|
271
|
+
onItemHover?.({ item });
|
|
272
|
+
}, [onItemHover, item]);
|
|
273
|
+
|
|
274
|
+
const handleContextMenu = useCallback(
|
|
275
|
+
(event: MouseEvent) => {
|
|
276
|
+
event.preventDefault();
|
|
277
|
+
setMenuOpen(true);
|
|
278
|
+
},
|
|
279
|
+
[setMenuOpen],
|
|
280
|
+
);
|
|
281
|
+
|
|
282
|
+
const childProps = {
|
|
283
|
+
draggable: draggableProp,
|
|
284
|
+
renderColumns: Columns,
|
|
285
|
+
blockInstruction,
|
|
286
|
+
canDrop,
|
|
287
|
+
canSelect,
|
|
288
|
+
onItemHover,
|
|
289
|
+
onOpenChange,
|
|
290
|
+
onSelect,
|
|
291
|
+
};
|
|
292
|
+
|
|
226
293
|
return (
|
|
227
294
|
<>
|
|
228
295
|
<Treegrid.Row
|
|
@@ -231,27 +298,25 @@ const RawTreeItem = <T extends { id: string } = any>({
|
|
|
231
298
|
id={id}
|
|
232
299
|
aria-labelledby={`${id}__label`}
|
|
233
300
|
parentOf={parentOf?.join(Treegrid.PARENT_OF_SEPARATOR)}
|
|
234
|
-
classNames={[
|
|
235
|
-
'grid grid-cols-subgrid col-[tree-row] mbs-0.5 aria-[current]:bg-activeSurface',
|
|
236
|
-
hoverableControls,
|
|
237
|
-
hoverableFocusedKeyboardControls,
|
|
238
|
-
hoverableFocusedWithinControls,
|
|
239
|
-
hoverableDescriptionIcons,
|
|
240
|
-
ghostHover,
|
|
241
|
-
ghostFocusWithin,
|
|
242
|
-
className,
|
|
243
|
-
]}
|
|
244
301
|
data-object-id={id}
|
|
245
302
|
data-testid={testId}
|
|
246
303
|
// NOTE(thure): This is intentionally an empty string to for descendents to select by in the CSS
|
|
247
304
|
// without alerting the user (except for in the correct link element). See also:
|
|
248
305
|
// https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-current#description
|
|
249
306
|
aria-current={current ? ('' as 'page') : undefined}
|
|
307
|
+
classNames={mx(
|
|
308
|
+
'grid grid-cols-subgrid col-[tree-row] mt-0.5 is-current:bg-active-surface',
|
|
309
|
+
hoverableControls,
|
|
310
|
+
hoverableFocusedKeyboardControls,
|
|
311
|
+
hoverableFocusedWithinControls,
|
|
312
|
+
hoverableDescriptionIcons,
|
|
313
|
+
ghostFocusWithin,
|
|
314
|
+
ghostHover,
|
|
315
|
+
className,
|
|
316
|
+
)}
|
|
250
317
|
onKeyDown={handleKeyDown}
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
setMenuOpen(true);
|
|
254
|
-
}}
|
|
318
|
+
onMouseEnter={handleItemHover}
|
|
319
|
+
onContextMenu={handleContextMenu}
|
|
255
320
|
>
|
|
256
321
|
<div
|
|
257
322
|
role='none'
|
|
@@ -276,23 +341,25 @@ const RawTreeItem = <T extends { id: string } = any>({
|
|
|
276
341
|
</div>
|
|
277
342
|
</Treegrid.Row>
|
|
278
343
|
{open &&
|
|
279
|
-
|
|
280
|
-
<
|
|
281
|
-
key={item.id}
|
|
282
|
-
item={item}
|
|
283
|
-
path={path}
|
|
284
|
-
last={index === items.length - 1}
|
|
285
|
-
draggable={_draggable}
|
|
286
|
-
renderColumns={Columns}
|
|
287
|
-
blockInstruction={blockInstruction}
|
|
288
|
-
canDrop={canDrop}
|
|
289
|
-
canSelect={canSelect}
|
|
290
|
-
onOpenChange={onOpenChange}
|
|
291
|
-
onSelect={onSelect}
|
|
292
|
-
/>
|
|
344
|
+
childIds.map((childId, index) => (
|
|
345
|
+
<TreeItemById key={childId} id={childId} path={path} last={index === childIds.length - 1} {...childProps} />
|
|
293
346
|
))}
|
|
294
347
|
</>
|
|
295
348
|
);
|
|
296
349
|
};
|
|
297
350
|
|
|
298
351
|
export const TreeItem = memo(RawTreeItem) as FC<TreeItemProps>;
|
|
352
|
+
|
|
353
|
+
/** Resolves a child ID to an item via the `item` atom and renders a TreeItem. */
|
|
354
|
+
export type TreeItemByIdProps = Omit<TreeItemProps, 'item'> & { id: string };
|
|
355
|
+
|
|
356
|
+
const RawTreeItemById = <T extends { id: string } = any>({ id, ...props }: TreeItemByIdProps) => {
|
|
357
|
+
const { item: itemAtom } = useTree();
|
|
358
|
+
const item = useAtomValue(itemAtom(id)) as T | undefined;
|
|
359
|
+
if (!item) {
|
|
360
|
+
return null;
|
|
361
|
+
}
|
|
362
|
+
return <TreeItem item={item} {...props} />;
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
export const TreeItemById = memo(RawTreeItemById) as FC<TreeItemByIdProps>;
|
|
@@ -58,7 +58,7 @@ export const TreeItemHeading = memo(
|
|
|
58
58
|
variant='ghost'
|
|
59
59
|
density='fine'
|
|
60
60
|
classNames={[
|
|
61
|
-
'grow gap-2
|
|
61
|
+
'grow gap-2 ps-0.5 hover:bg-transparent dark:hover:bg-transparent',
|
|
62
62
|
'disabled:cursor-default disabled:opacity-100',
|
|
63
63
|
className,
|
|
64
64
|
]}
|
|
@@ -67,8 +67,10 @@ export const TreeItemHeading = memo(
|
|
|
67
67
|
onKeyDown={handleButtonKeydown}
|
|
68
68
|
{...(current && { 'aria-current': 'location' })}
|
|
69
69
|
>
|
|
70
|
-
{icon &&
|
|
71
|
-
|
|
70
|
+
{icon && (
|
|
71
|
+
<Icon icon={icon ?? 'ph--placeholder--regular'} size={5} classNames={['my-1', styles?.surfaceText]} />
|
|
72
|
+
)}
|
|
73
|
+
<span className='flex-1 w-0 truncate text-start font-normal' data-tooltip>
|
|
72
74
|
{toLocalizedString(label, t)}
|
|
73
75
|
</span>
|
|
74
76
|
</Button>
|
|
@@ -23,7 +23,7 @@ export const TreeItemToggle = memo(
|
|
|
23
23
|
variant='ghost'
|
|
24
24
|
density='fine'
|
|
25
25
|
classNames={[
|
|
26
|
-
'
|
|
26
|
+
'h-full w-6 px-0',
|
|
27
27
|
'[&_svg]:transition-[transform] [&_svg]:duration-200',
|
|
28
28
|
open && '[&_svg]:rotate-90',
|
|
29
29
|
hidden ? 'hidden' : !isBranch && 'invisible',
|