@dxos/react-ui-list 0.8.4-main.a4bbb77 → 0.8.4-main.abd8ff62ef
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 +1349 -718
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +1349 -718
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/types/src/components/Accordion/Accordion.d.ts +1 -1
- package/dist/types/src/components/Accordion/Accordion.d.ts.map +1 -1
- package/dist/types/src/components/Accordion/Accordion.stories.d.ts +0 -3
- package/dist/types/src/components/Accordion/Accordion.stories.d.ts.map +1 -1
- package/dist/types/src/components/Accordion/AccordionItem.d.ts.map +1 -1
- package/dist/types/src/components/Accordion/AccordionRoot.d.ts +1 -1
- package/dist/types/src/components/Accordion/AccordionRoot.d.ts.map +1 -1
- package/dist/types/src/components/Combobox/Combobox.d.ts +105 -0
- package/dist/types/src/components/Combobox/Combobox.d.ts.map +1 -0
- package/dist/types/src/components/Combobox/Combobox.stories.d.ts +12 -0
- package/dist/types/src/components/Combobox/Combobox.stories.d.ts.map +1 -0
- package/dist/types/src/components/Combobox/index.d.ts +2 -0
- package/dist/types/src/components/Combobox/index.d.ts.map +1 -0
- package/dist/types/src/components/List/List.d.ts +19 -8
- package/dist/types/src/components/List/List.d.ts.map +1 -1
- package/dist/types/src/components/List/List.stories.d.ts +2 -2
- package/dist/types/src/components/List/List.stories.d.ts.map +1 -1
- package/dist/types/src/components/List/ListItem.d.ts +10 -8
- package/dist/types/src/components/List/ListItem.d.ts.map +1 -1
- package/dist/types/src/components/List/ListRoot.d.ts +2 -2
- package/dist/types/src/components/List/ListRoot.d.ts.map +1 -1
- package/dist/types/src/components/List/testing.d.ts +1 -1
- package/dist/types/src/components/List/testing.d.ts.map +1 -1
- package/dist/types/src/components/Listbox/Listbox.d.ts +27 -0
- package/dist/types/src/components/Listbox/Listbox.d.ts.map +1 -0
- package/dist/types/src/components/Listbox/Listbox.stories.d.ts +12 -0
- package/dist/types/src/components/Listbox/Listbox.stories.d.ts.map +1 -0
- package/dist/types/src/components/Listbox/index.d.ts +2 -0
- package/dist/types/src/components/Listbox/index.d.ts.map +1 -0
- package/dist/types/src/components/Picker/Picker.d.ts +49 -0
- package/dist/types/src/components/Picker/Picker.d.ts.map +1 -0
- package/dist/types/src/components/Picker/Picker.stories.d.ts +28 -0
- package/dist/types/src/components/Picker/Picker.stories.d.ts.map +1 -0
- package/dist/types/src/components/Picker/context.d.ts +29 -0
- package/dist/types/src/components/Picker/context.d.ts.map +1 -0
- package/dist/types/src/components/Picker/index.d.ts +3 -0
- package/dist/types/src/components/Picker/index.d.ts.map +1 -0
- package/dist/types/src/components/RowList/RowList.d.ts +61 -0
- package/dist/types/src/components/RowList/RowList.d.ts.map +1 -0
- package/dist/types/src/components/RowList/RowList.stories.d.ts +35 -0
- package/dist/types/src/components/RowList/RowList.stories.d.ts.map +1 -0
- package/dist/types/src/components/RowList/index.d.ts +3 -0
- package/dist/types/src/components/RowList/index.d.ts.map +1 -0
- package/dist/types/src/components/Tree/Tree.d.ts +10 -6
- package/dist/types/src/components/Tree/Tree.d.ts.map +1 -1
- package/dist/types/src/components/Tree/Tree.stories.d.ts +9 -28
- package/dist/types/src/components/Tree/Tree.stories.d.ts.map +1 -1
- package/dist/types/src/components/Tree/TreeContext.d.ts +24 -10
- package/dist/types/src/components/Tree/TreeContext.d.ts.map +1 -1
- package/dist/types/src/components/Tree/TreeItem.d.ts +25 -4
- package/dist/types/src/components/Tree/TreeItem.d.ts.map +1 -1
- package/dist/types/src/components/Tree/TreeItemHeading.d.ts +4 -3
- package/dist/types/src/components/Tree/TreeItemHeading.d.ts.map +1 -1
- package/dist/types/src/components/Tree/TreeItemToggle.d.ts +3 -3
- package/dist/types/src/components/Tree/TreeItemToggle.d.ts.map +1 -1
- package/dist/types/src/components/Tree/helpers.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/src/components/Tree/testing.d.ts +3 -3
- package/dist/types/src/components/Tree/testing.d.ts.map +1 -1
- package/dist/types/src/components/index.d.ts +4 -0
- package/dist/types/src/components/index.d.ts.map +1 -1
- package/dist/types/src/util/path.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +34 -31
- package/src/components/Accordion/Accordion.stories.tsx +5 -8
- package/src/components/Accordion/AccordionItem.tsx +3 -4
- package/src/components/Accordion/AccordionRoot.tsx +1 -1
- package/src/components/Combobox/Combobox.stories.tsx +60 -0
- package/src/components/Combobox/Combobox.tsx +387 -0
- package/src/components/Combobox/index.ts +5 -0
- package/src/components/List/List.stories.tsx +34 -22
- package/src/components/List/List.tsx +14 -10
- package/src/components/List/ListItem.tsx +60 -40
- package/src/components/List/ListRoot.tsx +3 -3
- package/src/components/List/testing.ts +7 -7
- package/src/components/Listbox/Listbox.stories.tsx +48 -0
- package/src/components/Listbox/Listbox.tsx +201 -0
- package/src/components/Listbox/index.ts +5 -0
- package/src/components/Picker/Picker.stories.tsx +131 -0
- package/src/components/Picker/Picker.tsx +439 -0
- package/src/components/Picker/context.ts +43 -0
- package/src/components/Picker/index.ts +6 -0
- package/src/components/RowList/RowList.stories.tsx +163 -0
- package/src/components/RowList/RowList.tsx +353 -0
- package/src/components/RowList/index.ts +6 -0
- package/src/components/Tree/Tree.stories.tsx +153 -64
- package/src/components/Tree/Tree.tsx +43 -40
- package/src/components/Tree/TreeContext.tsx +21 -9
- package/src/components/Tree/TreeItem.tsx +214 -127
- package/src/components/Tree/TreeItemHeading.tsx +10 -8
- package/src/components/Tree/TreeItemToggle.tsx +29 -18
- package/src/components/Tree/index.ts +2 -0
- package/src/components/Tree/testing.ts +10 -9
- package/src/components/index.ts +4 -0
|
@@ -1,68 +1,71 @@
|
|
|
1
1
|
//
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
|
-
//
|
|
4
3
|
|
|
4
|
+
import { useAtomValue } from '@effect-atom/atom-react';
|
|
5
5
|
import React, { useMemo } from 'react';
|
|
6
6
|
|
|
7
|
-
import { type HasId } from '@dxos/echo-schema';
|
|
8
7
|
import { Treegrid, type TreegridRootProps } from '@dxos/react-ui';
|
|
9
8
|
|
|
10
|
-
import { type
|
|
11
|
-
import {
|
|
9
|
+
import { type TreeModel, TreeProvider } from './TreeContext';
|
|
10
|
+
import { TreeItemById, type TreeItemByIdProps, type TreeItemProps } from './TreeItem';
|
|
12
11
|
|
|
13
|
-
export type TreeProps<T extends
|
|
14
|
-
|
|
12
|
+
export type TreeProps<T extends { id: string } = any> = {
|
|
13
|
+
model: TreeModel<T>;
|
|
14
|
+
rootId?: string;
|
|
15
15
|
path?: string[];
|
|
16
16
|
id: string;
|
|
17
|
-
} &
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
} & Partial<Pick<TreegridRootProps, 'gridTemplateColumns' | 'classNames'>> &
|
|
18
|
+
Pick<
|
|
19
|
+
TreeItemProps<T>,
|
|
20
|
+
| 'draggable'
|
|
21
|
+
| 'renderColumns'
|
|
22
|
+
| 'blockInstruction'
|
|
23
|
+
| 'canDrop'
|
|
24
|
+
| 'canSelect'
|
|
25
|
+
| 'onOpenChange'
|
|
26
|
+
| 'onSelect'
|
|
27
|
+
| 'onItemHover'
|
|
28
|
+
| 'levelOffset'
|
|
29
|
+
>;
|
|
20
30
|
|
|
21
|
-
export const Tree = <T extends
|
|
22
|
-
|
|
31
|
+
export const Tree = <T extends { id: string } = any>({
|
|
32
|
+
model,
|
|
33
|
+
rootId,
|
|
23
34
|
path,
|
|
24
35
|
id,
|
|
25
|
-
useItems,
|
|
26
|
-
getProps,
|
|
27
|
-
isOpen,
|
|
28
|
-
isCurrent,
|
|
29
36
|
draggable = false,
|
|
30
37
|
gridTemplateColumns = '[tree-row-start] 1fr min-content [tree-row-end]',
|
|
31
38
|
classNames,
|
|
32
39
|
levelOffset,
|
|
33
40
|
renderColumns,
|
|
41
|
+
blockInstruction,
|
|
34
42
|
canDrop,
|
|
43
|
+
canSelect,
|
|
35
44
|
onOpenChange,
|
|
36
45
|
onSelect,
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
useItems,
|
|
41
|
-
getProps,
|
|
42
|
-
isOpen,
|
|
43
|
-
isCurrent,
|
|
44
|
-
}),
|
|
45
|
-
[useItems, getProps, isOpen, isCurrent],
|
|
46
|
-
);
|
|
47
|
-
const items = useItems(root);
|
|
46
|
+
onItemHover,
|
|
47
|
+
}: TreeProps<T>) => {
|
|
48
|
+
const childIds = useAtomValue(model.childIds(rootId));
|
|
48
49
|
const treePath = useMemo(() => (path ? [...path, id] : [id]), [id, path]);
|
|
49
50
|
|
|
51
|
+
const childProps: Omit<TreeItemByIdProps, 'id' | 'last'> = {
|
|
52
|
+
path: treePath,
|
|
53
|
+
levelOffset,
|
|
54
|
+
draggable,
|
|
55
|
+
renderColumns,
|
|
56
|
+
blockInstruction,
|
|
57
|
+
canDrop,
|
|
58
|
+
canSelect,
|
|
59
|
+
onOpenChange,
|
|
60
|
+
onSelect,
|
|
61
|
+
onItemHover,
|
|
62
|
+
};
|
|
63
|
+
|
|
50
64
|
return (
|
|
51
65
|
<Treegrid.Root gridTemplateColumns={gridTemplateColumns} classNames={classNames}>
|
|
52
|
-
<TreeProvider value={
|
|
53
|
-
{
|
|
54
|
-
<
|
|
55
|
-
key={item.id}
|
|
56
|
-
item={item}
|
|
57
|
-
last={index === items.length - 1}
|
|
58
|
-
path={treePath}
|
|
59
|
-
levelOffset={levelOffset}
|
|
60
|
-
draggable={draggable}
|
|
61
|
-
renderColumns={renderColumns}
|
|
62
|
-
canDrop={canDrop}
|
|
63
|
-
onOpenChange={onOpenChange}
|
|
64
|
-
onSelect={onSelect}
|
|
65
|
-
/>
|
|
66
|
+
<TreeProvider value={model}>
|
|
67
|
+
{childIds.map((childId, index) => (
|
|
68
|
+
<TreeItemById key={childId} id={childId} last={index === childIds.length - 1} {...childProps} />
|
|
66
69
|
))}
|
|
67
70
|
</TreeProvider>
|
|
68
71
|
</Treegrid.Root>
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
+
import { type Atom } from '@effect-atom/atom-react';
|
|
5
6
|
import { createContext, useContext } from 'react';
|
|
6
7
|
|
|
7
8
|
import { raise } from '@dxos/debug';
|
|
@@ -11,21 +12,32 @@ export type TreeItemDataProps = {
|
|
|
11
12
|
id: string;
|
|
12
13
|
label: Label;
|
|
13
14
|
parentOf?: string[];
|
|
14
|
-
|
|
15
|
-
|
|
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;
|
|
16
19
|
className?: string;
|
|
17
20
|
headingClassName?: string;
|
|
21
|
+
icon?: string;
|
|
22
|
+
iconHue?: string;
|
|
23
|
+
disabled?: boolean;
|
|
18
24
|
testId?: string;
|
|
19
25
|
};
|
|
20
26
|
|
|
21
|
-
export
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
+
export interface TreeModel<T extends { id: string } = any> {
|
|
28
|
+
/** Atom family: resolve item by ID (content). */
|
|
29
|
+
item: (id: string) => Atom.Atom<T | undefined>;
|
|
30
|
+
/** Atom family: open state keyed by path. */
|
|
31
|
+
itemOpen: (path: string[]) => Atom.Atom<boolean>;
|
|
32
|
+
/** Atom family: current (selected) state keyed by path. */
|
|
33
|
+
itemCurrent: (path: string[]) => Atom.Atom<boolean>;
|
|
34
|
+
/** Atom family: display props for an item at a given path (path includes item's own ID at end). */
|
|
35
|
+
itemProps: (path: string[]) => Atom.Atom<TreeItemDataProps>;
|
|
36
|
+
/** Atom family: outbound child IDs for a parent ID (topology). Undefined = root. */
|
|
37
|
+
childIds: (parentId?: string) => Atom.Atom<string[]>;
|
|
38
|
+
}
|
|
27
39
|
|
|
28
|
-
const TreeContext = createContext<
|
|
40
|
+
const TreeContext = createContext<TreeModel | null>(null);
|
|
29
41
|
|
|
30
42
|
export const TreeProvider = TreeContext.Provider;
|
|
31
43
|
|
|
@@ -2,37 +2,49 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { combine } from '@atlaskit/pragmatic-drag-and-drop/combine';
|
|
6
|
-
import { draggable, dropTargetForElements } from '@atlaskit/pragmatic-drag-and-drop/element/adapter';
|
|
7
5
|
import {
|
|
8
6
|
type Instruction,
|
|
9
7
|
type ItemMode,
|
|
10
8
|
attachInstruction,
|
|
11
9
|
extractInstruction,
|
|
12
10
|
} from '@atlaskit/pragmatic-drag-and-drop-hitbox/tree-item';
|
|
13
|
-
import {
|
|
14
|
-
import
|
|
11
|
+
import { combine } from '@atlaskit/pragmatic-drag-and-drop/combine';
|
|
12
|
+
import { draggable, dropTargetForElements } from '@atlaskit/pragmatic-drag-and-drop/element/adapter';
|
|
13
|
+
import { useAtomValue } from '@effect-atom/atom-react';
|
|
14
|
+
import * as Schema from 'effect/Schema';
|
|
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
|
-
import { type HasId } from '@dxos/echo-schema';
|
|
17
27
|
import { invariant } from '@dxos/invariant';
|
|
18
|
-
import { TreeItem as NaturalTreeItem, Treegrid } from '@dxos/react-ui';
|
|
28
|
+
import { TreeItem as NaturalTreeItem, Treegrid, TREEGRID_PARENT_OF_SEPARATOR } from '@dxos/react-ui';
|
|
19
29
|
import {
|
|
30
|
+
ghostFocusWithin,
|
|
20
31
|
ghostHover,
|
|
21
32
|
hoverableControls,
|
|
22
33
|
hoverableFocusedKeyboardControls,
|
|
23
34
|
hoverableFocusedWithinControls,
|
|
24
|
-
|
|
35
|
+
mx,
|
|
36
|
+
} from '@dxos/ui-theme';
|
|
25
37
|
|
|
26
38
|
import { DEFAULT_INDENTATION, paddingIndentation } from './helpers';
|
|
27
39
|
import { useTree } from './TreeContext';
|
|
28
40
|
import { TreeItemHeading } from './TreeItemHeading';
|
|
29
41
|
import { TreeItemToggle } from './TreeItemToggle';
|
|
30
42
|
|
|
31
|
-
type TreeItemState = 'idle' | 'dragging' | 'preview' | 'parent-of-instruction';
|
|
32
|
-
|
|
33
43
|
const hoverableDescriptionIcons =
|
|
34
44
|
'[--icons-color:inherit] hover-hover:[--icons-color:var(--description-text)] hover-hover:hover:[--icons-color:inherit] focus-within:[--icons-color:inherit]';
|
|
35
45
|
|
|
46
|
+
type TreeItemDragState = 'idle' | 'dragging' | 'preview' | 'parent-of-instruction';
|
|
47
|
+
|
|
36
48
|
export const TreeDataSchema = Schema.Struct({
|
|
37
49
|
id: Schema.String,
|
|
38
50
|
path: Schema.Array(Schema.String),
|
|
@@ -42,7 +54,7 @@ export const TreeDataSchema = Schema.Struct({
|
|
|
42
54
|
export type TreeData = Schema.Schema.Type<typeof TreeDataSchema>;
|
|
43
55
|
export const isTreeData = (data: unknown): data is TreeData => Schema.is(TreeDataSchema)(data);
|
|
44
56
|
|
|
45
|
-
export type ColumnRenderer<T extends
|
|
57
|
+
export type ColumnRenderer<T extends { id: string } = any> = FC<{
|
|
46
58
|
item: T;
|
|
47
59
|
path: string[];
|
|
48
60
|
open: boolean;
|
|
@@ -50,46 +62,74 @@ export type ColumnRenderer<T extends HasId = any> = FC<{
|
|
|
50
62
|
setMenuOpen: (open: boolean) => void;
|
|
51
63
|
}>;
|
|
52
64
|
|
|
53
|
-
export type TreeItemProps<T extends
|
|
65
|
+
export type TreeItemProps<T extends { id: string } = any> = {
|
|
54
66
|
item: T;
|
|
55
67
|
path: string[];
|
|
56
68
|
levelOffset?: number;
|
|
57
69
|
last: boolean;
|
|
58
70
|
draggable?: boolean;
|
|
59
71
|
renderColumns?: ColumnRenderer<T>;
|
|
72
|
+
blockInstruction?: (params: { instruction: Instruction; source: TreeData; target: TreeData }) => boolean;
|
|
60
73
|
canDrop?: (params: { source: TreeData; target: TreeData }) => boolean;
|
|
74
|
+
canSelect?: (params: { item: T; path: string[] }) => boolean;
|
|
61
75
|
onOpenChange?: (params: { item: T; path: string[]; open: boolean }) => void;
|
|
62
76
|
onSelect?: (params: { item: T; path: string[]; current: boolean; option: boolean }) => void;
|
|
77
|
+
onItemHover?: (params: { item: T }) => void;
|
|
63
78
|
};
|
|
64
79
|
|
|
65
|
-
const RawTreeItem = <T extends
|
|
80
|
+
const RawTreeItem = <T extends { id: string } = any>({
|
|
66
81
|
item,
|
|
67
|
-
path:
|
|
82
|
+
path: pathProp,
|
|
83
|
+
levelOffset = 2,
|
|
68
84
|
last,
|
|
69
|
-
draggable:
|
|
85
|
+
draggable: draggableProp,
|
|
70
86
|
renderColumns: Columns,
|
|
87
|
+
blockInstruction,
|
|
71
88
|
canDrop,
|
|
89
|
+
canSelect,
|
|
72
90
|
onOpenChange,
|
|
73
91
|
onSelect,
|
|
74
|
-
|
|
92
|
+
onItemHover,
|
|
75
93
|
}: TreeItemProps<T>) => {
|
|
76
94
|
const rowRef = useRef<HTMLDivElement | null>(null);
|
|
77
95
|
const buttonRef = useRef<HTMLButtonElement | null>(null);
|
|
78
96
|
const openRef = useRef(false);
|
|
79
97
|
const cancelExpandRef = useRef<NodeJS.Timeout | null>(null);
|
|
80
|
-
const [_state, setState] = useState<
|
|
98
|
+
const [_state, setState] = useState<TreeItemDragState>('idle');
|
|
81
99
|
const [instruction, setInstruction] = useState<Instruction | null>(null);
|
|
82
100
|
const [menuOpen, setMenuOpen] = useState(false);
|
|
83
101
|
|
|
84
|
-
const {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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
|
+
|
|
90
127
|
const level = path.length - levelOffset;
|
|
91
128
|
const isBranch = !!parentOf;
|
|
92
129
|
const mode: ItemMode = last ? 'last-in-group' : open ? 'expanded' : 'standard';
|
|
130
|
+
const canSelectItem = canSelect?.({ item, path }) ?? true;
|
|
131
|
+
const data = { id, path, item } satisfies TreeData;
|
|
132
|
+
const shouldSeedNativeDragData = typeof document !== 'undefined' && document.body.hasAttribute('data-platform');
|
|
93
133
|
|
|
94
134
|
const cancelExpand = useCallback(() => {
|
|
95
135
|
if (cancelExpandRef.current) {
|
|
@@ -98,20 +138,27 @@ const RawTreeItem = <T extends HasId = any>({
|
|
|
98
138
|
}
|
|
99
139
|
}, []);
|
|
100
140
|
|
|
141
|
+
const isItemDraggable = draggableProp && itemDraggable !== false;
|
|
142
|
+
const isItemDroppable = itemDroppable !== false;
|
|
143
|
+
const nativeDragText = id;
|
|
144
|
+
|
|
101
145
|
useEffect(() => {
|
|
102
|
-
if (!
|
|
146
|
+
if (!draggableProp) {
|
|
103
147
|
return;
|
|
104
148
|
}
|
|
105
149
|
|
|
106
150
|
invariant(buttonRef.current);
|
|
107
151
|
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
// https://atlassian.design/components/pragmatic-drag-and-drop/core-package/adapters/element/about
|
|
111
|
-
return combine(
|
|
152
|
+
const makeDraggable = () =>
|
|
112
153
|
draggable({
|
|
113
|
-
element: buttonRef.current
|
|
154
|
+
element: buttonRef.current!,
|
|
114
155
|
getInitialData: () => data,
|
|
156
|
+
getInitialDataForExternal: () => {
|
|
157
|
+
if (!shouldSeedNativeDragData) {
|
|
158
|
+
return {};
|
|
159
|
+
}
|
|
160
|
+
return { 'text/plain': nativeDragText };
|
|
161
|
+
},
|
|
115
162
|
onDragStart: () => {
|
|
116
163
|
setState('dragging');
|
|
117
164
|
if (open) {
|
|
@@ -125,96 +172,132 @@ const RawTreeItem = <T extends HasId = any>({
|
|
|
125
172
|
onOpenChange?.({ item, path, open: true });
|
|
126
173
|
}
|
|
127
174
|
},
|
|
128
|
-
})
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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
|
-
// TODO(wittjosiah): This is not occurring in the current implementation.
|
|
164
|
-
setInstruction(instruction);
|
|
165
|
-
} else {
|
|
166
|
-
setInstruction(null);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
if (!isItemDroppable) {
|
|
178
|
+
return isItemDraggable ? makeDraggable() : undefined;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const dropTarget = dropTargetForElements({
|
|
182
|
+
element: buttonRef.current,
|
|
183
|
+
getData: ({ input, element }) => {
|
|
184
|
+
return attachInstruction(data, {
|
|
185
|
+
input,
|
|
186
|
+
element,
|
|
187
|
+
indentPerLevel: DEFAULT_INDENTATION,
|
|
188
|
+
currentLevel: level,
|
|
189
|
+
mode,
|
|
190
|
+
block: isBranch ? [] : ['make-child'],
|
|
191
|
+
});
|
|
192
|
+
},
|
|
193
|
+
canDrop: ({ source }) => {
|
|
194
|
+
const _canDrop = canDrop ?? (() => true);
|
|
195
|
+
return source.element !== buttonRef.current && _canDrop({ source: source.data as TreeData, target: data });
|
|
196
|
+
},
|
|
197
|
+
getIsSticky: () => true,
|
|
198
|
+
onDrag: ({ self, source }) => {
|
|
199
|
+
const desired = extractInstruction(self.data);
|
|
200
|
+
const block =
|
|
201
|
+
desired && blockInstruction?.({ instruction: desired, source: source.data as TreeData, target: data });
|
|
202
|
+
const instruction: Instruction | null =
|
|
203
|
+
block && desired.type !== 'instruction-blocked' ? { type: 'instruction-blocked', desired } : desired;
|
|
204
|
+
|
|
205
|
+
if (source.data.id !== id) {
|
|
206
|
+
if (instruction?.type === 'make-child' && isBranch && !open && !cancelExpandRef.current) {
|
|
207
|
+
cancelExpandRef.current = setTimeout(() => {
|
|
208
|
+
onOpenChange?.({ item, path, open: true });
|
|
209
|
+
}, 500);
|
|
167
210
|
}
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
211
|
+
|
|
212
|
+
if (instruction?.type !== 'make-child') {
|
|
213
|
+
cancelExpand();
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
setInstruction(instruction);
|
|
217
|
+
} else if (instruction?.type === 'reparent') {
|
|
218
|
+
// TODO(wittjosiah): This is not occurring in the current implementation.
|
|
219
|
+
setInstruction(instruction);
|
|
220
|
+
} else {
|
|
175
221
|
setInstruction(null);
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
|
|
222
|
+
}
|
|
223
|
+
},
|
|
224
|
+
onDragLeave: () => {
|
|
225
|
+
cancelExpand();
|
|
226
|
+
setInstruction(null);
|
|
227
|
+
},
|
|
228
|
+
onDrop: () => {
|
|
229
|
+
cancelExpand();
|
|
230
|
+
setInstruction(null);
|
|
231
|
+
},
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
if (!isItemDraggable) {
|
|
235
|
+
return dropTarget;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// https://atlassian.design/components/pragmatic-drag-and-drop/core-package/adapters/element/about
|
|
239
|
+
return combine(makeDraggable(), dropTarget);
|
|
240
|
+
}, [draggableProp, isItemDraggable, isItemDroppable, item, id, mode, path, open, blockInstruction, canDrop]);
|
|
180
241
|
|
|
181
242
|
// Cancel expand on unmount.
|
|
182
243
|
useEffect(() => () => cancelExpand(), [cancelExpand]);
|
|
183
244
|
|
|
184
|
-
const
|
|
245
|
+
const handleOpenToggle = useCallback(
|
|
185
246
|
() => onOpenChange?.({ item, path, open: !open }),
|
|
186
247
|
[onOpenChange, item, path, open],
|
|
187
248
|
);
|
|
188
249
|
|
|
189
250
|
const handleSelect = useCallback(
|
|
190
251
|
(option = false) => {
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
252
|
+
// If the item is a branch, toggle it if:
|
|
253
|
+
// - also holding down the option key
|
|
254
|
+
// - or the item is currently selected
|
|
255
|
+
if (isBranch && (option || current)) {
|
|
256
|
+
handleOpenToggle();
|
|
257
|
+
} else if (canSelectItem) {
|
|
258
|
+
canSelect?.({ item, path });
|
|
194
259
|
rowRef.current?.focus();
|
|
195
260
|
onSelect?.({ item, path, current: !current, option });
|
|
196
261
|
}
|
|
197
262
|
},
|
|
198
|
-
[item, path, current, isBranch,
|
|
263
|
+
[item, path, current, isBranch, canSelectItem, handleOpenToggle, onSelect],
|
|
199
264
|
);
|
|
200
265
|
|
|
201
266
|
const handleKeyDown = useCallback(
|
|
202
267
|
(event: KeyboardEvent) => {
|
|
203
268
|
switch (event.key) {
|
|
204
269
|
case 'ArrowRight':
|
|
205
|
-
isBranch && !open && handleOpenChange();
|
|
206
|
-
break;
|
|
207
270
|
case 'ArrowLeft':
|
|
208
|
-
isBranch &&
|
|
209
|
-
break;
|
|
210
|
-
case ' ':
|
|
211
|
-
handleSelect(event.altKey);
|
|
271
|
+
isBranch && handleOpenToggle();
|
|
212
272
|
break;
|
|
213
273
|
}
|
|
214
274
|
},
|
|
215
|
-
[isBranch, open,
|
|
275
|
+
[isBranch, open, handleOpenToggle, handleSelect],
|
|
276
|
+
);
|
|
277
|
+
|
|
278
|
+
const handleItemHover = useCallback(() => {
|
|
279
|
+
onItemHover?.({ item });
|
|
280
|
+
}, [onItemHover, item]);
|
|
281
|
+
|
|
282
|
+
const handleContextMenu = useCallback(
|
|
283
|
+
(event: MouseEvent) => {
|
|
284
|
+
event.preventDefault();
|
|
285
|
+
setMenuOpen(true);
|
|
286
|
+
},
|
|
287
|
+
[setMenuOpen],
|
|
216
288
|
);
|
|
217
289
|
|
|
290
|
+
const childProps = {
|
|
291
|
+
draggable: draggableProp,
|
|
292
|
+
renderColumns: Columns,
|
|
293
|
+
blockInstruction,
|
|
294
|
+
canDrop,
|
|
295
|
+
canSelect,
|
|
296
|
+
onItemHover,
|
|
297
|
+
onOpenChange,
|
|
298
|
+
onSelect,
|
|
299
|
+
};
|
|
300
|
+
|
|
218
301
|
return (
|
|
219
302
|
<>
|
|
220
303
|
<Treegrid.Row
|
|
@@ -222,65 +305,69 @@ const RawTreeItem = <T extends HasId = any>({
|
|
|
222
305
|
key={id}
|
|
223
306
|
id={id}
|
|
224
307
|
aria-labelledby={`${id}__label`}
|
|
225
|
-
parentOf={parentOf?.join(
|
|
226
|
-
|
|
227
|
-
|
|
308
|
+
parentOf={parentOf?.join(TREEGRID_PARENT_OF_SEPARATOR)}
|
|
309
|
+
data-object-id={id}
|
|
310
|
+
data-testid={testId}
|
|
311
|
+
// NOTE(thure): This is intentionally an empty string to for descendents to select by in the CSS
|
|
312
|
+
// without alerting the user (except for in the correct link element). See also:
|
|
313
|
+
// https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-current#description
|
|
314
|
+
aria-current={current ? ('' as 'page') : undefined}
|
|
315
|
+
classNames={mx(
|
|
316
|
+
'grid grid-cols-subgrid col-[tree-row] mt-0.5 is-current:bg-active-surface',
|
|
228
317
|
hoverableControls,
|
|
229
318
|
hoverableFocusedKeyboardControls,
|
|
230
319
|
hoverableFocusedWithinControls,
|
|
231
320
|
hoverableDescriptionIcons,
|
|
321
|
+
ghostFocusWithin,
|
|
232
322
|
ghostHover,
|
|
233
323
|
className,
|
|
234
|
-
|
|
235
|
-
data-itemid={id}
|
|
236
|
-
data-testid={testId}
|
|
237
|
-
// NOTE(thure): This is intentionally an empty string to for descendents to select by in the CSS
|
|
238
|
-
// without alerting the user (except for in the correct link element). See also:
|
|
239
|
-
// https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-current#description
|
|
240
|
-
aria-current={current ? ('' as 'page') : undefined}
|
|
324
|
+
)}
|
|
241
325
|
onKeyDown={handleKeyDown}
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
setMenuOpen(true);
|
|
245
|
-
}}
|
|
326
|
+
onMouseEnter={handleItemHover}
|
|
327
|
+
onContextMenu={handleContextMenu}
|
|
246
328
|
>
|
|
247
|
-
<
|
|
248
|
-
|
|
249
|
-
|
|
329
|
+
<div
|
|
330
|
+
role='none'
|
|
331
|
+
className='indent relative grid grid-cols-subgrid col-[tree-row]'
|
|
250
332
|
style={paddingIndentation(level)}
|
|
251
333
|
>
|
|
252
|
-
<
|
|
253
|
-
<TreeItemToggle isBranch={isBranch} open={open}
|
|
334
|
+
<Treegrid.Cell classNames='flex items-center'>
|
|
335
|
+
<TreeItemToggle isBranch={isBranch} open={open} onClick={handleOpenToggle} />
|
|
254
336
|
<TreeItemHeading
|
|
255
|
-
ref={buttonRef}
|
|
256
|
-
label={label}
|
|
257
|
-
icon={icon}
|
|
258
|
-
className={headingClassName}
|
|
259
337
|
disabled={disabled}
|
|
260
338
|
current={current}
|
|
339
|
+
label={label}
|
|
340
|
+
className={headingClassName}
|
|
341
|
+
icon={icon}
|
|
342
|
+
iconHue={iconHue}
|
|
261
343
|
onSelect={handleSelect}
|
|
344
|
+
ref={buttonRef}
|
|
262
345
|
/>
|
|
263
|
-
</
|
|
346
|
+
</Treegrid.Cell>
|
|
264
347
|
{Columns && <Columns item={item} path={path} open={open} menuOpen={menuOpen} setMenuOpen={setMenuOpen} />}
|
|
265
348
|
{instruction && <NaturalTreeItem.DropIndicator instruction={instruction} gap={2} />}
|
|
266
|
-
</
|
|
349
|
+
</div>
|
|
267
350
|
</Treegrid.Row>
|
|
268
351
|
{open &&
|
|
269
|
-
|
|
270
|
-
<
|
|
271
|
-
key={item.id}
|
|
272
|
-
item={item}
|
|
273
|
-
path={path}
|
|
274
|
-
last={index === items.length - 1}
|
|
275
|
-
draggable={_draggable}
|
|
276
|
-
renderColumns={Columns}
|
|
277
|
-
canDrop={canDrop}
|
|
278
|
-
onOpenChange={onOpenChange}
|
|
279
|
-
onSelect={onSelect}
|
|
280
|
-
/>
|
|
352
|
+
childIds.map((childId, index) => (
|
|
353
|
+
<TreeItemById key={childId} id={childId} path={path} last={index === childIds.length - 1} {...childProps} />
|
|
281
354
|
))}
|
|
282
355
|
</>
|
|
283
356
|
);
|
|
284
357
|
};
|
|
285
358
|
|
|
286
359
|
export const TreeItem = memo(RawTreeItem) as FC<TreeItemProps>;
|
|
360
|
+
|
|
361
|
+
/** Resolves a child ID to an item via the `item` atom and renders a TreeItem. */
|
|
362
|
+
export type TreeItemByIdProps = Omit<TreeItemProps, 'item'> & { id: string };
|
|
363
|
+
|
|
364
|
+
const RawTreeItemById = <T extends { id: string } = any>({ id, ...props }: TreeItemByIdProps) => {
|
|
365
|
+
const { item: itemAtom } = useTree();
|
|
366
|
+
const item = useAtomValue(itemAtom(id)) as T | undefined;
|
|
367
|
+
if (!item) {
|
|
368
|
+
return null;
|
|
369
|
+
}
|
|
370
|
+
return <TreeItem item={item} {...props} />;
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
export const TreeItemById = memo(RawTreeItemById) as FC<TreeItemByIdProps>;
|