@dxos/react-ui-list 0.8.4-main.c85a9c8dae → 0.8.4-main.d05673bc65
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 +116 -105
- package/dist/lib/browser/index.mjs.map +3 -3
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +116 -105
- 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 +4 -4
- package/dist/types/src/components/List/List.d.ts.map +1 -1
- package/dist/types/src/components/List/ListItem.d.ts +6 -4
- package/dist/types/src/components/List/ListItem.d.ts.map +1 -1
- package/dist/types/src/components/Tree/TreeContext.d.ts +4 -0
- package/dist/types/src/components/Tree/TreeContext.d.ts.map +1 -1
- package/dist/types/src/components/Tree/TreeItem.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +15 -14
- package/src/components/List/List.stories.tsx +2 -2
- package/src/components/List/List.tsx +2 -2
- package/src/components/List/ListItem.tsx +42 -24
- package/src/components/Tree/TreeContext.tsx +4 -0
- package/src/components/Tree/TreeItem.tsx +82 -60
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dxos/react-ui-list",
|
|
3
|
-
"version": "0.8.4-main.
|
|
3
|
+
"version": "0.8.4-main.d05673bc65",
|
|
4
4
|
"description": "A list component.",
|
|
5
5
|
"homepage": "https://dxos.org",
|
|
6
6
|
"bugs": "https://github.com/dxos/dxos/issues",
|
|
@@ -33,15 +33,16 @@
|
|
|
33
33
|
"@effect-atom/atom-react": "^0.5.0",
|
|
34
34
|
"@radix-ui/react-accordion": "1.2.3",
|
|
35
35
|
"@radix-ui/react-context": "1.1.1",
|
|
36
|
-
"@
|
|
37
|
-
"@dxos/
|
|
38
|
-
"@dxos/echo": "0.8.4-main.
|
|
39
|
-
"@dxos/
|
|
40
|
-
"@dxos/react-ui": "0.8.4-main.
|
|
41
|
-
"@dxos/
|
|
42
|
-
"@dxos/
|
|
43
|
-
"@dxos/ui-
|
|
44
|
-
"@dxos/util": "0.8.4-main.
|
|
36
|
+
"@radix-ui/react-slot": "1.1.2",
|
|
37
|
+
"@dxos/debug": "0.8.4-main.d05673bc65",
|
|
38
|
+
"@dxos/echo": "0.8.4-main.d05673bc65",
|
|
39
|
+
"@dxos/invariant": "0.8.4-main.d05673bc65",
|
|
40
|
+
"@dxos/react-ui": "0.8.4-main.d05673bc65",
|
|
41
|
+
"@dxos/ui-theme": "0.8.4-main.d05673bc65",
|
|
42
|
+
"@dxos/log": "0.8.4-main.d05673bc65",
|
|
43
|
+
"@dxos/react-ui-text-tooltip": "0.8.4-main.d05673bc65",
|
|
44
|
+
"@dxos/util": "0.8.4-main.d05673bc65",
|
|
45
|
+
"@dxos/ui-types": "0.8.4-main.d05673bc65"
|
|
45
46
|
},
|
|
46
47
|
"devDependencies": {
|
|
47
48
|
"@types/react": "~19.2.7",
|
|
@@ -50,15 +51,15 @@
|
|
|
50
51
|
"react": "~19.2.3",
|
|
51
52
|
"react-dom": "~19.2.3",
|
|
52
53
|
"vite": "^7.1.11",
|
|
53
|
-
"@dxos/random": "0.8.4-main.
|
|
54
|
-
"@dxos/storybook-utils": "0.8.4-main.
|
|
54
|
+
"@dxos/random": "0.8.4-main.d05673bc65",
|
|
55
|
+
"@dxos/storybook-utils": "0.8.4-main.d05673bc65"
|
|
55
56
|
},
|
|
56
57
|
"peerDependencies": {
|
|
57
58
|
"effect": "3.19.16",
|
|
58
59
|
"react": "~19.2.3",
|
|
59
60
|
"react-dom": "~19.2.3",
|
|
60
|
-
"@dxos/react-ui": "0.8.4-main.
|
|
61
|
-
"@dxos/ui-theme": "0.8.4-main.
|
|
61
|
+
"@dxos/react-ui": "0.8.4-main.d05673bc65",
|
|
62
|
+
"@dxos/ui-theme": "0.8.4-main.d05673bc65"
|
|
62
63
|
},
|
|
63
64
|
"publishConfig": {
|
|
64
65
|
"access": "public"
|
|
@@ -53,7 +53,7 @@ const DefaultStory = (props: Omit<ListRootProps<TestItemType>, 'items'>) => {
|
|
|
53
53
|
|
|
54
54
|
<div role='list' className='w-full h-full overflow-auto'>
|
|
55
55
|
{items?.map((item) => (
|
|
56
|
-
<List.Item<TestItemType> key={item.id} item={item} classNames={mx(grid
|
|
56
|
+
<List.Item<TestItemType> key={item.id} item={item} classNames={mx(grid)}>
|
|
57
57
|
<List.ItemDragHandle />
|
|
58
58
|
<List.ItemTitle onClick={() => handleSelect(item)}>{item.name}</List.ItemTitle>
|
|
59
59
|
<List.ItemDeleteButton onClick={() => handleDelete(item)} />
|
|
@@ -91,7 +91,7 @@ const SimpleStory = (props: Omit<ListRootProps<TestItemType>, 'items'>) => {
|
|
|
91
91
|
{({ items }) => (
|
|
92
92
|
<div role='list' className='w-full h-full overflow-auto'>
|
|
93
93
|
{items?.map((item) => (
|
|
94
|
-
<List.Item<TestItemType> key={item.id} item={item} classNames={mx(grid
|
|
94
|
+
<List.Item<TestItemType> key={item.id} item={item} classNames={mx(grid)}>
|
|
95
95
|
<List.ItemDragHandle />
|
|
96
96
|
<List.ItemTitle>{item.name}</List.ItemTitle>
|
|
97
97
|
<List.ItemDeleteButton />
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import {
|
|
6
6
|
ListItem,
|
|
7
|
-
|
|
7
|
+
ListItemIconButton,
|
|
8
8
|
ListItemDeleteButton,
|
|
9
9
|
ListItemDragHandle,
|
|
10
10
|
ListItemDragPreview,
|
|
@@ -33,8 +33,8 @@ export const List = {
|
|
|
33
33
|
ItemDragPreview: ListItemDragPreview,
|
|
34
34
|
ItemWrapper: ListItemWrapper,
|
|
35
35
|
ItemDragHandle: ListItemDragHandle,
|
|
36
|
+
ItemIconButton: ListItemIconButton,
|
|
36
37
|
ItemDeleteButton: ListItemDeleteButton,
|
|
37
|
-
ItemButton: ListItemButton,
|
|
38
38
|
ItemTitle: ListItemTitle,
|
|
39
39
|
};
|
|
40
40
|
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
extractClosestEdge,
|
|
12
12
|
} from '@atlaskit/pragmatic-drag-and-drop-hitbox/closest-edge';
|
|
13
13
|
import { createContext } from '@radix-ui/react-context';
|
|
14
|
+
import { Slot } from '@radix-ui/react-slot';
|
|
14
15
|
import React, {
|
|
15
16
|
type ComponentProps,
|
|
16
17
|
type HTMLAttributes,
|
|
@@ -80,6 +81,8 @@ export type ListItemProps<T extends ListItemRecord> = ThemedClassName<
|
|
|
80
81
|
PropsWithChildren<
|
|
81
82
|
{
|
|
82
83
|
item: T;
|
|
84
|
+
asChild?: boolean;
|
|
85
|
+
selected?: boolean;
|
|
83
86
|
} & HTMLAttributes<HTMLDivElement>
|
|
84
87
|
>
|
|
85
88
|
>;
|
|
@@ -87,7 +90,15 @@ export type ListItemProps<T extends ListItemRecord> = ThemedClassName<
|
|
|
87
90
|
/**
|
|
88
91
|
* Draggable list item.
|
|
89
92
|
*/
|
|
90
|
-
export const ListItem = <T extends ListItemRecord>({
|
|
93
|
+
export const ListItem = <T extends ListItemRecord>({
|
|
94
|
+
children,
|
|
95
|
+
classNames,
|
|
96
|
+
item,
|
|
97
|
+
asChild,
|
|
98
|
+
selected,
|
|
99
|
+
...props
|
|
100
|
+
}: ListItemProps<T>) => {
|
|
101
|
+
const Comp = asChild ? Slot : 'div';
|
|
91
102
|
const { isItem, readonly, dragPreview, setState: setRootState } = useListContext(LIST_ITEM_NAME);
|
|
92
103
|
const ref = useRef<HTMLDivElement | null>(null);
|
|
93
104
|
const dragHandleRef = useRef<HTMLElement | null>(null);
|
|
@@ -170,12 +181,18 @@ export const ListItem = <T extends ListItemRecord>({ children, classNames, item,
|
|
|
170
181
|
|
|
171
182
|
return (
|
|
172
183
|
<ListItemProvider item={item} dragHandleRef={dragHandleRef}>
|
|
173
|
-
<
|
|
184
|
+
<Comp
|
|
185
|
+
ref={ref}
|
|
186
|
+
role='listitem'
|
|
187
|
+
aria-selected={selected}
|
|
188
|
+
className={mx('relative dx-selected', classNames, stateStyles[state.type])}
|
|
189
|
+
{...props}
|
|
190
|
+
>
|
|
174
191
|
{children}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
192
|
+
</Comp>
|
|
193
|
+
{state.type === 'w-dragging-over' && state.closestEdge && (
|
|
194
|
+
<NaturalListItem.DropIndicator edge={state.closestEdge} />
|
|
195
|
+
)}
|
|
179
196
|
</ListItemProvider>
|
|
180
197
|
);
|
|
181
198
|
};
|
|
@@ -184,47 +201,48 @@ export const ListItem = <T extends ListItemRecord>({ children, classNames, item,
|
|
|
184
201
|
// List item components
|
|
185
202
|
//
|
|
186
203
|
|
|
187
|
-
export const
|
|
204
|
+
export const ListItemIconButton = ({
|
|
188
205
|
autoHide = true,
|
|
206
|
+
iconOnly = true,
|
|
207
|
+
variant = 'ghost',
|
|
189
208
|
classNames,
|
|
190
209
|
disabled,
|
|
191
|
-
icon = 'ph--x--regular',
|
|
192
|
-
label,
|
|
193
210
|
...props
|
|
194
|
-
}:
|
|
195
|
-
|
|
196
|
-
const { state } = useListContext('DELETE_BUTTON');
|
|
211
|
+
}: IconButtonProps & { autoHide?: boolean }) => {
|
|
212
|
+
const { state } = useListContext('ITEM_BUTTON');
|
|
197
213
|
const isDisabled = state.type !== 'idle' || disabled;
|
|
198
|
-
const { t } = useTranslation(osTranslations);
|
|
199
214
|
return (
|
|
200
215
|
<IconButton
|
|
201
|
-
iconOnly
|
|
202
|
-
variant='ghost'
|
|
203
216
|
{...props}
|
|
204
|
-
icon={icon}
|
|
205
217
|
disabled={isDisabled}
|
|
206
|
-
|
|
218
|
+
iconOnly={iconOnly}
|
|
219
|
+
variant={variant}
|
|
207
220
|
classNames={[classNames, autoHide && disabled && 'hidden']}
|
|
208
221
|
/>
|
|
209
222
|
);
|
|
210
223
|
};
|
|
211
224
|
|
|
212
|
-
|
|
225
|
+
// TODO(burdon): Generalize to action button.
|
|
226
|
+
export const ListItemDeleteButton = ({
|
|
213
227
|
autoHide = true,
|
|
214
|
-
iconOnly = true,
|
|
215
|
-
variant = 'ghost',
|
|
216
228
|
classNames,
|
|
217
229
|
disabled,
|
|
230
|
+
icon = 'ph--x--regular',
|
|
231
|
+
label,
|
|
218
232
|
...props
|
|
219
|
-
}: IconButtonProps &
|
|
220
|
-
|
|
233
|
+
}: Partial<Pick<IconButtonProps, 'icon'>> &
|
|
234
|
+
Omit<IconButtonProps, 'icon' | 'label'> & { autoHide?: boolean; label?: string }) => {
|
|
235
|
+
const { state } = useListContext('DELETE_BUTTON');
|
|
221
236
|
const isDisabled = state.type !== 'idle' || disabled;
|
|
237
|
+
const { t } = useTranslation(osTranslations);
|
|
222
238
|
return (
|
|
223
239
|
<IconButton
|
|
240
|
+
iconOnly
|
|
241
|
+
variant='ghost'
|
|
224
242
|
{...props}
|
|
243
|
+
icon={icon}
|
|
225
244
|
disabled={isDisabled}
|
|
226
|
-
|
|
227
|
-
variant={variant}
|
|
245
|
+
label={label ?? t('delete label')}
|
|
228
246
|
classNames={[classNames, autoHide && disabled && 'hidden']}
|
|
229
247
|
/>
|
|
230
248
|
);
|
|
@@ -12,6 +12,10 @@ export type TreeItemDataProps = {
|
|
|
12
12
|
id: string;
|
|
13
13
|
label: Label;
|
|
14
14
|
parentOf?: string[];
|
|
15
|
+
/** When `false`, the item cannot be dragged (overrides tree-level `draggable`). */
|
|
16
|
+
draggable?: boolean;
|
|
17
|
+
/** When `false`, the item does not participate as a drop target. */
|
|
18
|
+
droppable?: boolean;
|
|
15
19
|
className?: string;
|
|
16
20
|
headingClassName?: string;
|
|
17
21
|
icon?: string;
|
|
@@ -107,9 +107,19 @@ const RawTreeItem = <T extends { id: string } = any>({
|
|
|
107
107
|
} = useTree();
|
|
108
108
|
const path = useMemo(() => [...pathProp, item.id], [pathProp, item.id]);
|
|
109
109
|
|
|
110
|
-
const {
|
|
111
|
-
|
|
112
|
-
|
|
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));
|
|
113
123
|
const childIds = useAtomValue(childIdsAtom(item.id));
|
|
114
124
|
const open = useAtomValue(itemOpenAtom(path));
|
|
115
125
|
const current = useAtomValue(itemCurrentAtom(path));
|
|
@@ -127,6 +137,9 @@ const RawTreeItem = <T extends { id: string } = any>({
|
|
|
127
137
|
}
|
|
128
138
|
}, []);
|
|
129
139
|
|
|
140
|
+
const isItemDraggable = draggableProp && itemDraggable !== false;
|
|
141
|
+
const isItemDroppable = itemDroppable !== false;
|
|
142
|
+
|
|
130
143
|
useEffect(() => {
|
|
131
144
|
if (!draggableProp) {
|
|
132
145
|
return;
|
|
@@ -134,10 +147,9 @@ const RawTreeItem = <T extends { id: string } = any>({
|
|
|
134
147
|
|
|
135
148
|
invariant(buttonRef.current);
|
|
136
149
|
|
|
137
|
-
|
|
138
|
-
return combine(
|
|
150
|
+
const makeDraggable = () =>
|
|
139
151
|
draggable({
|
|
140
|
-
element: buttonRef.current
|
|
152
|
+
element: buttonRef.current!,
|
|
141
153
|
getInitialData: () => data,
|
|
142
154
|
onDragStart: () => {
|
|
143
155
|
setState('dragging');
|
|
@@ -152,62 +164,72 @@ const RawTreeItem = <T extends { id: string } = any>({
|
|
|
152
164
|
onOpenChange?.({ item, path, open: true });
|
|
153
165
|
}
|
|
154
166
|
},
|
|
155
|
-
})
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
setInstruction(instruction);
|
|
193
|
-
} else if (instruction?.type === 'reparent') {
|
|
194
|
-
// TODO(wittjosiah): This is not occurring in the current implementation.
|
|
195
|
-
setInstruction(instruction);
|
|
196
|
-
} else {
|
|
197
|
-
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);
|
|
198
202
|
}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
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 {
|
|
206
213
|
setInstruction(null);
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
|
|
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]);
|
|
211
233
|
|
|
212
234
|
// Cancel expand on unmount.
|
|
213
235
|
useEffect(() => () => cancelExpand(), [cancelExpand]);
|