@dxos/react-ui-list 0.7.1 → 0.7.2-main.f1adc9f
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 +149 -87
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node/index.cjs +180 -116
- package/dist/lib/node/index.cjs.map +4 -4
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +149 -87
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/types/src/components/List/DropIndicator.d.ts +5 -4
- package/dist/types/src/components/List/DropIndicator.d.ts.map +1 -1
- package/dist/types/src/components/Tree/DropIndicator.d.ts +2 -1
- package/dist/types/src/components/Tree/DropIndicator.d.ts.map +1 -1
- package/dist/types/src/components/Tree/Tree.d.ts +4 -19
- package/dist/types/src/components/Tree/Tree.d.ts.map +1 -1
- package/dist/types/src/components/Tree/Tree.stories.d.ts.map +1 -1
- package/dist/types/src/components/Tree/TreeContext.d.ts +20 -0
- package/dist/types/src/components/Tree/TreeContext.d.ts.map +1 -0
- package/dist/types/src/components/Tree/TreeItem.d.ts +26 -24
- package/dist/types/src/components/Tree/TreeItem.d.ts.map +1 -1
- package/dist/types/src/components/Tree/TreeItemHeading.d.ts +1 -1
- package/dist/types/src/components/Tree/TreeItemHeading.d.ts.map +1 -1
- package/dist/types/src/components/Tree/helpers.d.ts +0 -3
- package/dist/types/src/components/Tree/helpers.d.ts.map +1 -1
- package/dist/types/src/components/Tree/index.d.ts +1 -1
- package/dist/types/src/components/Tree/index.d.ts.map +1 -1
- package/dist/types/src/components/Tree/testing.d.ts +3 -6
- package/dist/types/src/components/Tree/testing.d.ts.map +1 -1
- package/package.json +16 -18
- package/src/components/List/DropIndicator.tsx +7 -2
- package/src/components/Tree/DropIndicator.tsx +7 -8
- package/src/components/Tree/Tree.stories.tsx +71 -70
- package/src/components/Tree/Tree.tsx +29 -24
- package/src/components/Tree/TreeContext.tsx +32 -0
- package/src/components/Tree/TreeItem.tsx +122 -83
- package/src/components/Tree/TreeItemHeading.tsx +14 -5
- package/src/components/Tree/helpers.ts +0 -16
- package/src/components/Tree/index.ts +1 -1
- package/src/components/Tree/testing.ts +4 -73
- package/dist/types/src/components/Tree/types.d.ts +0 -18
- package/dist/types/src/components/Tree/types.d.ts.map +0 -1
- package/src/components/Tree/types.ts +0 -34
|
@@ -3,7 +3,10 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { combine } from '@atlaskit/pragmatic-drag-and-drop/combine';
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
draggable as pragmaticDraggable,
|
|
8
|
+
dropTargetForElements,
|
|
9
|
+
} from '@atlaskit/pragmatic-drag-and-drop/element/adapter';
|
|
7
10
|
// https://github.com/atlassian/pragmatic-drag-and-drop/blob/main/packages/hitbox/constellation/index/about.mdx
|
|
8
11
|
import {
|
|
9
12
|
attachInstruction,
|
|
@@ -11,8 +14,9 @@ import {
|
|
|
11
14
|
type Instruction,
|
|
12
15
|
type ItemMode,
|
|
13
16
|
} from '@atlaskit/pragmatic-drag-and-drop-hitbox/tree-item';
|
|
14
|
-
import React, { memo, useCallback, useEffect, useRef, useState, type FC, type KeyboardEvent } from 'react';
|
|
17
|
+
import React, { memo, useCallback, useEffect, useMemo, useRef, useState, type FC, type KeyboardEvent } from 'react';
|
|
15
18
|
|
|
19
|
+
import { S } from '@dxos/echo-schema';
|
|
16
20
|
import { invariant } from '@dxos/invariant';
|
|
17
21
|
import { Treegrid } from '@dxos/react-ui';
|
|
18
22
|
import {
|
|
@@ -25,44 +29,57 @@ import {
|
|
|
25
29
|
} from '@dxos/react-ui-theme';
|
|
26
30
|
|
|
27
31
|
import { DropIndicator } from './DropIndicator';
|
|
32
|
+
import { useTree } from './TreeContext';
|
|
28
33
|
import { TreeItemHeading } from './TreeItemHeading';
|
|
29
34
|
import { TreeItemToggle } from './TreeItemToggle';
|
|
30
35
|
import { DEFAULT_INDENTATION, paddingIndendation } from './helpers';
|
|
31
|
-
import { type ItemType } from './types';
|
|
32
36
|
|
|
33
37
|
type TreeItemState = 'idle' | 'dragging' | 'preview' | 'parent-of-instruction';
|
|
34
38
|
|
|
35
39
|
const hoverableDescriptionIcons =
|
|
36
40
|
'[--icons-color:inherit] hover-hover:[--icons-color:var(--description-text)] hover-hover:hover:[--icons-color:inherit] focus-within:[--icons-color:inherit]';
|
|
37
41
|
|
|
38
|
-
|
|
39
|
-
|
|
42
|
+
export const TreeDataSchema = S.Struct({
|
|
43
|
+
id: S.String,
|
|
44
|
+
path: S.Array(S.String),
|
|
45
|
+
item: S.Any,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
export type TreeData = S.Schema.Type<typeof TreeDataSchema>;
|
|
49
|
+
|
|
50
|
+
export const isTreeData = (data: unknown): data is TreeData => S.is(TreeDataSchema)(data);
|
|
51
|
+
|
|
52
|
+
export type TreeItemProps<T = any> = {
|
|
40
53
|
item: T;
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
current: boolean;
|
|
54
|
+
path: string[];
|
|
55
|
+
last: boolean;
|
|
44
56
|
draggable?: boolean;
|
|
45
|
-
renderColumns?: FC<{ item: T; menuOpen: boolean; setMenuOpen: (open: boolean) => void }>;
|
|
46
|
-
canDrop?: (source:
|
|
47
|
-
onOpenChange?: (item: T
|
|
48
|
-
onSelect?: (item: T
|
|
57
|
+
renderColumns?: FC<{ item: T; path: string[]; menuOpen: boolean; setMenuOpen: (open: boolean) => void }>;
|
|
58
|
+
canDrop?: (source: TreeData, target: TreeData) => boolean;
|
|
59
|
+
onOpenChange?: (params: { item: T; path: string[]; open: boolean }) => void;
|
|
60
|
+
onSelect?: (params: { item: T; path: string[]; current: boolean; option: boolean }) => void;
|
|
49
61
|
};
|
|
50
62
|
|
|
51
|
-
|
|
52
|
-
export const RawTreeItem = <T extends ItemType = ItemType>({
|
|
63
|
+
export const RawTreeItem = <T = any,>({
|
|
53
64
|
item,
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
draggable: _draggable,
|
|
65
|
+
path: _path,
|
|
66
|
+
last,
|
|
67
|
+
draggable,
|
|
58
68
|
renderColumns: Columns,
|
|
59
69
|
canDrop,
|
|
60
70
|
onOpenChange,
|
|
61
71
|
onSelect,
|
|
62
72
|
}: TreeItemProps<T>) => {
|
|
63
|
-
const {
|
|
73
|
+
const { getItems, getProps, isOpen, isCurrent } = useTree();
|
|
74
|
+
const items = getItems(item);
|
|
75
|
+
const { id, label, parentOf, icon, disabled, className, headingClassName, testId } = getProps(item, _path);
|
|
76
|
+
const path = useMemo(() => [..._path, id], [_path, id]);
|
|
77
|
+
const open = isOpen(path, item);
|
|
78
|
+
const current = isCurrent(path, item);
|
|
64
79
|
const level = path.length - 2;
|
|
65
80
|
const isBranch = !!parentOf;
|
|
81
|
+
const mode: ItemMode = last ? 'last-in-group' : open ? 'expanded' : 'standard';
|
|
82
|
+
const data = useMemo(() => ({ id, path, item }) satisfies TreeData, [id, path, item]);
|
|
66
83
|
|
|
67
84
|
const rowRef = useRef<HTMLDivElement | null>(null);
|
|
68
85
|
const buttonRef = useRef<HTMLButtonElement | null>(null);
|
|
@@ -80,7 +97,7 @@ export const RawTreeItem = <T extends ItemType = ItemType>({
|
|
|
80
97
|
}, []);
|
|
81
98
|
|
|
82
99
|
useEffect(() => {
|
|
83
|
-
if (!
|
|
100
|
+
if (!draggable) {
|
|
84
101
|
return;
|
|
85
102
|
}
|
|
86
103
|
|
|
@@ -88,27 +105,27 @@ export const RawTreeItem = <T extends ItemType = ItemType>({
|
|
|
88
105
|
|
|
89
106
|
// https://atlassian.design/components/pragmatic-drag-and-drop/core-package/adapters/element/about
|
|
90
107
|
return combine(
|
|
91
|
-
|
|
108
|
+
pragmaticDraggable({
|
|
92
109
|
element: buttonRef.current,
|
|
93
|
-
getInitialData: () =>
|
|
110
|
+
getInitialData: () => data,
|
|
94
111
|
onDragStart: () => {
|
|
95
112
|
setState('dragging');
|
|
96
113
|
if (open) {
|
|
97
114
|
openRef.current = true;
|
|
98
|
-
onOpenChange?.(item, false);
|
|
115
|
+
onOpenChange?.({ item, path, open: false });
|
|
99
116
|
}
|
|
100
117
|
},
|
|
101
118
|
onDrop: () => {
|
|
102
119
|
setState('idle');
|
|
103
120
|
if (openRef.current) {
|
|
104
|
-
onOpenChange?.(item, true);
|
|
121
|
+
onOpenChange?.({ item, path, open: true });
|
|
105
122
|
}
|
|
106
123
|
},
|
|
107
124
|
}),
|
|
108
125
|
dropTargetForElements({
|
|
109
126
|
element: buttonRef.current,
|
|
110
127
|
getData: ({ input, element }) => {
|
|
111
|
-
return attachInstruction(
|
|
128
|
+
return attachInstruction(data, {
|
|
112
129
|
input,
|
|
113
130
|
element,
|
|
114
131
|
indentPerLevel: DEFAULT_INDENTATION,
|
|
@@ -119,16 +136,16 @@ export const RawTreeItem = <T extends ItemType = ItemType>({
|
|
|
119
136
|
},
|
|
120
137
|
canDrop: ({ source }) => {
|
|
121
138
|
const _canDrop = canDrop ?? (() => true);
|
|
122
|
-
return source.element !== buttonRef.current && _canDrop(source.data as
|
|
139
|
+
return source.element !== buttonRef.current && _canDrop(source.data as TreeData, data);
|
|
123
140
|
},
|
|
124
141
|
getIsSticky: () => true,
|
|
125
142
|
onDrag: ({ self, source }) => {
|
|
126
143
|
const instruction = extractInstruction(self.data);
|
|
127
144
|
|
|
128
|
-
if (source.data.id !==
|
|
145
|
+
if (source.data.id !== id) {
|
|
129
146
|
if (instruction?.type === 'make-child' && isBranch && !open && !cancelExpandRef.current) {
|
|
130
147
|
cancelExpandRef.current = setTimeout(() => {
|
|
131
|
-
onOpenChange?.(item, true);
|
|
148
|
+
onOpenChange?.({ item, path, open: true });
|
|
132
149
|
}, 500);
|
|
133
150
|
}
|
|
134
151
|
|
|
@@ -154,17 +171,23 @@ export const RawTreeItem = <T extends ItemType = ItemType>({
|
|
|
154
171
|
},
|
|
155
172
|
}),
|
|
156
173
|
);
|
|
157
|
-
}, [draggable, item, mode, open, canDrop]);
|
|
174
|
+
}, [draggable, item, id, mode, path, open, canDrop]);
|
|
158
175
|
|
|
159
176
|
// Cancel expand on unmount.
|
|
160
177
|
useEffect(() => () => cancelExpand(), [cancelExpand]);
|
|
161
178
|
|
|
162
|
-
const handleOpenChange = useCallback(
|
|
179
|
+
const handleOpenChange = useCallback(
|
|
180
|
+
() => onOpenChange?.({ item, path, open: !open }),
|
|
181
|
+
[onOpenChange, item, path, open],
|
|
182
|
+
);
|
|
163
183
|
|
|
164
|
-
const handleSelect = useCallback(
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
184
|
+
const handleSelect = useCallback(
|
|
185
|
+
(option = false) => {
|
|
186
|
+
rowRef.current?.focus();
|
|
187
|
+
onSelect?.({ item, path, current: !current, option });
|
|
188
|
+
},
|
|
189
|
+
[onSelect, item, path, current],
|
|
190
|
+
);
|
|
168
191
|
|
|
169
192
|
const handleKeyDown = useCallback(
|
|
170
193
|
(event: KeyboardEvent) => {
|
|
@@ -176,7 +199,7 @@ export const RawTreeItem = <T extends ItemType = ItemType>({
|
|
|
176
199
|
isBranch && open && handleOpenChange();
|
|
177
200
|
break;
|
|
178
201
|
case ' ':
|
|
179
|
-
handleSelect();
|
|
202
|
+
handleSelect(event.altKey);
|
|
180
203
|
break;
|
|
181
204
|
}
|
|
182
205
|
},
|
|
@@ -184,56 +207,72 @@ export const RawTreeItem = <T extends ItemType = ItemType>({
|
|
|
184
207
|
);
|
|
185
208
|
|
|
186
209
|
return (
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
event
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
<Treegrid.Cell
|
|
216
|
-
indent
|
|
217
|
-
classNames='relative grid grid-cols-subgrid col-[tree-row]'
|
|
218
|
-
style={paddingIndendation(level)}
|
|
210
|
+
<>
|
|
211
|
+
<Treegrid.Row
|
|
212
|
+
ref={rowRef}
|
|
213
|
+
key={id}
|
|
214
|
+
id={id}
|
|
215
|
+
aria-labelledby={`${id}__label`}
|
|
216
|
+
parentOf={parentOf?.join(Treegrid.PARENT_OF_SEPARATOR)}
|
|
217
|
+
classNames={mx(
|
|
218
|
+
'grid grid-cols-subgrid col-[tree-row] mt-[2px] aria-[current]:bg-input',
|
|
219
|
+
hoverableControls,
|
|
220
|
+
hoverableFocusedKeyboardControls,
|
|
221
|
+
hoverableFocusedWithinControls,
|
|
222
|
+
hoverableDescriptionIcons,
|
|
223
|
+
ghostHover,
|
|
224
|
+
focusRing,
|
|
225
|
+
className,
|
|
226
|
+
)}
|
|
227
|
+
data-itemid={id}
|
|
228
|
+
data-testid={testId}
|
|
229
|
+
// NOTE(thure): This is intentionally an empty string to for descendents to select by in the CSS
|
|
230
|
+
// without alerting the user (except for in the correct link element). See also:
|
|
231
|
+
// https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-current#description
|
|
232
|
+
aria-current={current ? ('' as 'page') : undefined}
|
|
233
|
+
onKeyDown={handleKeyDown}
|
|
234
|
+
onContextMenu={(event) => {
|
|
235
|
+
event.preventDefault();
|
|
236
|
+
setMenuOpen(true);
|
|
237
|
+
}}
|
|
219
238
|
>
|
|
220
|
-
<
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
239
|
+
<Treegrid.Cell
|
|
240
|
+
indent
|
|
241
|
+
classNames='relative grid grid-cols-subgrid col-[tree-row]'
|
|
242
|
+
style={paddingIndendation(level)}
|
|
243
|
+
>
|
|
244
|
+
<div role='none' className='flex items-center'>
|
|
245
|
+
<TreeItemToggle open={open} isBranch={isBranch} onToggle={handleOpenChange} />
|
|
246
|
+
<TreeItemHeading
|
|
247
|
+
ref={buttonRef}
|
|
248
|
+
label={label}
|
|
249
|
+
icon={icon}
|
|
250
|
+
className={headingClassName}
|
|
251
|
+
disabled={disabled}
|
|
252
|
+
current={current}
|
|
253
|
+
onSelect={handleSelect}
|
|
254
|
+
/>
|
|
255
|
+
</div>
|
|
256
|
+
{Columns && <Columns item={item} path={path} menuOpen={menuOpen} setMenuOpen={setMenuOpen} />}
|
|
257
|
+
{instruction && <DropIndicator instruction={instruction} gap={2} />}
|
|
258
|
+
</Treegrid.Cell>
|
|
259
|
+
</Treegrid.Row>
|
|
260
|
+
{open &&
|
|
261
|
+
items.map((item, index) => (
|
|
262
|
+
<TreeItem
|
|
263
|
+
key={item.id}
|
|
264
|
+
item={item}
|
|
265
|
+
path={path}
|
|
266
|
+
last={index === items.length - 1}
|
|
267
|
+
draggable={draggable}
|
|
268
|
+
renderColumns={Columns}
|
|
269
|
+
canDrop={canDrop}
|
|
270
|
+
onOpenChange={onOpenChange}
|
|
271
|
+
onSelect={onSelect}
|
|
230
272
|
/>
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
{instruction && <DropIndicator instruction={instruction} />}
|
|
234
|
-
</Treegrid.Cell>
|
|
235
|
-
</Treegrid.Row>
|
|
273
|
+
))}
|
|
274
|
+
</>
|
|
236
275
|
);
|
|
237
276
|
};
|
|
238
277
|
|
|
239
|
-
export const TreeItem = memo(RawTreeItem) as <
|
|
278
|
+
export const TreeItem = memo(RawTreeItem) as FC<TreeItemProps>;
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import React, { type KeyboardEvent, forwardRef, memo, useCallback } from 'react';
|
|
5
|
+
import React, { type KeyboardEvent, type MouseEvent, forwardRef, memo, useCallback } from 'react';
|
|
6
6
|
|
|
7
7
|
import { Button, Icon, toLocalizedString, useTranslation, type Label } from '@dxos/react-ui';
|
|
8
8
|
import { TextTooltip } from '@dxos/react-ui-text-tooltip';
|
|
@@ -16,7 +16,7 @@ export type NavTreeItemHeadingProps = {
|
|
|
16
16
|
className?: string;
|
|
17
17
|
disabled?: boolean;
|
|
18
18
|
current?: boolean;
|
|
19
|
-
onSelect?: () => void;
|
|
19
|
+
onSelect?: (option: boolean) => void;
|
|
20
20
|
};
|
|
21
21
|
|
|
22
22
|
export const TreeItemHeading = memo(
|
|
@@ -24,12 +24,19 @@ export const TreeItemHeading = memo(
|
|
|
24
24
|
({ label, icon, className, disabled, current, onSelect }, forwardedRef) => {
|
|
25
25
|
const { t } = useTranslation();
|
|
26
26
|
|
|
27
|
+
const handleSelect = useCallback(
|
|
28
|
+
(event: MouseEvent) => {
|
|
29
|
+
onSelect?.(event.altKey);
|
|
30
|
+
},
|
|
31
|
+
[onSelect],
|
|
32
|
+
);
|
|
33
|
+
|
|
27
34
|
const handleButtonKeydown = useCallback(
|
|
28
35
|
(event: KeyboardEvent) => {
|
|
29
36
|
if (event.key === ' ' || event.key === 'Enter') {
|
|
30
37
|
event.preventDefault();
|
|
31
38
|
event.stopPropagation();
|
|
32
|
-
onSelect?.();
|
|
39
|
+
onSelect?.(event.altKey);
|
|
33
40
|
}
|
|
34
41
|
},
|
|
35
42
|
[onSelect],
|
|
@@ -55,12 +62,14 @@ export const TreeItemHeading = memo(
|
|
|
55
62
|
className,
|
|
56
63
|
)}
|
|
57
64
|
disabled={disabled}
|
|
58
|
-
onClick={
|
|
65
|
+
onClick={handleSelect}
|
|
59
66
|
onKeyDown={handleButtonKeydown}
|
|
60
67
|
{...(current && { 'aria-current': 'location' })}
|
|
61
68
|
>
|
|
62
69
|
{icon && <Icon icon={icon ?? 'ph--placeholder--regular'} size={4} classNames='is-[1em] bs-[1em] mlb-1' />}
|
|
63
|
-
<span className='flex-1 is-0 truncate text-start text-sm font-normal'
|
|
70
|
+
<span className='flex-1 is-0 truncate text-start text-sm font-normal' data-tooltip>
|
|
71
|
+
{toLocalizedString(label, t)}
|
|
72
|
+
</span>
|
|
64
73
|
</Button>
|
|
65
74
|
</TextTooltip>
|
|
66
75
|
);
|
|
@@ -2,24 +2,8 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { type ItemMode } from '@atlaskit/pragmatic-drag-and-drop-hitbox/tree-item';
|
|
6
|
-
|
|
7
|
-
import { type ItemType } from './types';
|
|
8
|
-
|
|
9
5
|
export const DEFAULT_INDENTATION = 8;
|
|
10
6
|
|
|
11
7
|
export const paddingIndendation = (level: number, indentation = DEFAULT_INDENTATION) => ({
|
|
12
8
|
paddingInlineStart: `${(level - 1) * indentation}px`,
|
|
13
9
|
});
|
|
14
|
-
|
|
15
|
-
export const getMode = (items: ItemType[], index: number): ItemMode => {
|
|
16
|
-
const item = items[index];
|
|
17
|
-
const next = items[index + 1];
|
|
18
|
-
if (!next || item.path.length > next.path.length) {
|
|
19
|
-
return 'last-in-group';
|
|
20
|
-
} else if (item.path.length < next.path.length) {
|
|
21
|
-
return 'expanded';
|
|
22
|
-
} else {
|
|
23
|
-
return 'standard';
|
|
24
|
-
}
|
|
25
|
-
};
|
|
@@ -7,9 +7,8 @@ import { type Instruction } from '@atlaskit/pragmatic-drag-and-drop-hitbox/tree-
|
|
|
7
7
|
import { S } from '@dxos/echo-schema';
|
|
8
8
|
import { log } from '@dxos/log';
|
|
9
9
|
import { faker } from '@dxos/random';
|
|
10
|
-
import { Path } from '@dxos/react-ui-mosaic';
|
|
11
10
|
|
|
12
|
-
import { type
|
|
11
|
+
import { type TreeData } from './TreeItem';
|
|
13
12
|
|
|
14
13
|
export type TestItem = {
|
|
15
14
|
id: string;
|
|
@@ -40,69 +39,7 @@ export const createTree = (n = 4, d = 4): TestItem => ({
|
|
|
40
39
|
items: d > 0 ? faker.helpers.multiple(() => createTree(n, d - 1), { count: n }) : [],
|
|
41
40
|
});
|
|
42
41
|
|
|
43
|
-
|
|
44
|
-
testItem,
|
|
45
|
-
getItem,
|
|
46
|
-
isOpen,
|
|
47
|
-
}: {
|
|
48
|
-
testItem: TestItem;
|
|
49
|
-
getItem: (testItem: TestItem, parent?: readonly string[]) => ItemType;
|
|
50
|
-
isOpen?: (testItem: ItemType) => boolean;
|
|
51
|
-
}): Generator<ItemType> {
|
|
52
|
-
const stack: [TestItem, ItemType][] = [[testItem, getItem(testItem)]];
|
|
53
|
-
while (stack.length > 0) {
|
|
54
|
-
const [testItem, item] = stack.pop()!;
|
|
55
|
-
if (item.path.length > 1) {
|
|
56
|
-
yield item;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const children = Array.from(testItem.items ?? []);
|
|
60
|
-
if (item.path.length === 1 || isOpen?.(item)) {
|
|
61
|
-
for (let i = children.length - 1; i >= 0; i--) {
|
|
62
|
-
const child = children[i];
|
|
63
|
-
stack.push([child, getItem(child, item.path)]);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export const flattenTree = (tree: TestItem, open: string[], getItem: (tree: TestItem) => ItemType): ItemType[] => {
|
|
70
|
-
return Array.from(
|
|
71
|
-
visitor({
|
|
72
|
-
testItem: tree,
|
|
73
|
-
getItem,
|
|
74
|
-
isOpen: ({ path }) => open.includes(Path.create(...path)),
|
|
75
|
-
}),
|
|
76
|
-
);
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
// Ensures that the same item is not created multiple times, causing the tree to be re-rendered.
|
|
80
|
-
const itemsCache: Record<string, ItemType> = {};
|
|
81
|
-
|
|
82
|
-
export const getItem = (testItem: TestItem, parent?: string[]): ItemType => {
|
|
83
|
-
const cachedItem = itemsCache[testItem.id];
|
|
84
|
-
if (cachedItem) {
|
|
85
|
-
return cachedItem;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
const item = {
|
|
89
|
-
id: testItem.id,
|
|
90
|
-
label: testItem.name,
|
|
91
|
-
icon: testItem.icon,
|
|
92
|
-
path: parent ? [...parent, testItem.id] : [testItem.id],
|
|
93
|
-
...((testItem.items?.length ?? 0) > 0 && {
|
|
94
|
-
parentOf: testItem.items!.map(({ id }) => id),
|
|
95
|
-
}),
|
|
96
|
-
};
|
|
97
|
-
itemsCache[testItem.id] = item;
|
|
98
|
-
return item;
|
|
99
|
-
};
|
|
100
|
-
|
|
101
|
-
export const invalidateCache = (id: string) => {
|
|
102
|
-
delete itemsCache[id];
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
const removeItem = (tree: TestItem, source: ItemType) => {
|
|
42
|
+
const removeItem = (tree: TestItem, source: TreeData) => {
|
|
106
43
|
const parent = getTestItem(tree, source.path.slice(1, -1));
|
|
107
44
|
const index = parent.items!.findIndex(({ id }) => id === source.id);
|
|
108
45
|
const item = parent.items[index];
|
|
@@ -127,35 +64,29 @@ export const updateState = ({
|
|
|
127
64
|
}: {
|
|
128
65
|
state: TestItem;
|
|
129
66
|
instruction: Instruction;
|
|
130
|
-
source:
|
|
131
|
-
target:
|
|
67
|
+
source: TreeData;
|
|
68
|
+
target: TreeData;
|
|
132
69
|
}) => {
|
|
133
70
|
switch (instruction.type) {
|
|
134
71
|
case 'reorder-above': {
|
|
135
|
-
invalidateCache(source.id);
|
|
136
72
|
const item = removeItem(state, source);
|
|
137
73
|
const parent = getTestItem(state, target.path.slice(1, -1));
|
|
138
74
|
const index = parent.items!.findIndex(({ id }) => id === target.id);
|
|
139
|
-
invalidateCache(target.id);
|
|
140
75
|
parent.items!.splice(index, 0, item);
|
|
141
76
|
break;
|
|
142
77
|
}
|
|
143
78
|
|
|
144
79
|
case 'reorder-below': {
|
|
145
|
-
invalidateCache(source.id);
|
|
146
80
|
const item = removeItem(state, source);
|
|
147
81
|
const parent = getTestItem(state, target.path.slice(1, -1));
|
|
148
82
|
const index = parent.items!.findIndex(({ id }) => id === target.id);
|
|
149
|
-
invalidateCache(target.id);
|
|
150
83
|
parent.items!.splice(index + 1, 0, item);
|
|
151
84
|
break;
|
|
152
85
|
}
|
|
153
86
|
|
|
154
87
|
case 'make-child': {
|
|
155
|
-
invalidateCache(source.id);
|
|
156
88
|
const item = removeItem(state, source);
|
|
157
89
|
const parent = getTestItem(state, target.path.slice(1));
|
|
158
|
-
invalidateCache(target.id);
|
|
159
90
|
parent.items!.push(item);
|
|
160
91
|
break;
|
|
161
92
|
}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { S } from '@dxos/echo-schema';
|
|
2
|
-
export declare const ItemSchema: S.mutable<S.Struct<{
|
|
3
|
-
id: typeof S.String;
|
|
4
|
-
label: S.mutable<S.Union<[typeof S.String, S.Tuple<[typeof S.String, S.Struct<{
|
|
5
|
-
ns: typeof S.String;
|
|
6
|
-
count: S.optional<typeof S.Number>;
|
|
7
|
-
}>]>]>>;
|
|
8
|
-
icon: S.optional<typeof S.String>;
|
|
9
|
-
disabled: S.optional<typeof S.Boolean>;
|
|
10
|
-
className: S.optional<typeof S.String>;
|
|
11
|
-
headingClassName: S.optional<typeof S.String>;
|
|
12
|
-
testId: S.optional<typeof S.String>;
|
|
13
|
-
path: S.Array$<typeof S.String>;
|
|
14
|
-
parentOf: S.optional<S.Array$<typeof S.String>>;
|
|
15
|
-
}>>;
|
|
16
|
-
export type ItemType = S.Schema.Type<typeof ItemSchema>;
|
|
17
|
-
export declare const isItem: (item: unknown) => boolean;
|
|
18
|
-
//# sourceMappingURL=types.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../../src/components/Tree/types.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,CAAC,EAAE,MAAM,mBAAmB,CAAC;AAatC,eAAO,MAAM,UAAU;;;;;;;;;;;;;GAYtB,CAAC;AAEF,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,UAAU,CAAC,CAAC;AAExD,eAAO,MAAM,MAAM,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,OAA0B,CAAC"}
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
//
|
|
2
|
-
// Copyright 2024 DXOS.org
|
|
3
|
-
//
|
|
4
|
-
|
|
5
|
-
import { S } from '@dxos/echo-schema';
|
|
6
|
-
|
|
7
|
-
const LabelSchema = S.Union(
|
|
8
|
-
S.String,
|
|
9
|
-
S.Tuple(
|
|
10
|
-
S.String,
|
|
11
|
-
S.Struct({
|
|
12
|
-
ns: S.String,
|
|
13
|
-
count: S.optional(S.Number),
|
|
14
|
-
}),
|
|
15
|
-
),
|
|
16
|
-
);
|
|
17
|
-
|
|
18
|
-
export const ItemSchema = S.mutable(
|
|
19
|
-
S.Struct({
|
|
20
|
-
id: S.String,
|
|
21
|
-
label: S.mutable(LabelSchema),
|
|
22
|
-
icon: S.optional(S.String),
|
|
23
|
-
disabled: S.optional(S.Boolean),
|
|
24
|
-
className: S.optional(S.String),
|
|
25
|
-
headingClassName: S.optional(S.String),
|
|
26
|
-
testId: S.optional(S.String),
|
|
27
|
-
path: S.Array(S.String),
|
|
28
|
-
parentOf: S.optional(S.Array(S.String)),
|
|
29
|
-
}),
|
|
30
|
-
);
|
|
31
|
-
|
|
32
|
-
export type ItemType = S.Schema.Type<typeof ItemSchema>;
|
|
33
|
-
|
|
34
|
-
export const isItem: (item: unknown) => boolean = S.is(ItemSchema);
|