feat: replace react-dnd to dnd-kit (#2028)

Co-authored-by: Himself65 <himself65@outlook.com>
This commit is contained in:
Qi
2023-04-21 12:27:32 +08:00
committed by GitHub
parent 4a473f5518
commit a5a6203a95
13 changed files with 334 additions and 252 deletions

View File

@@ -4,7 +4,6 @@
import 'fake-indexeddb/auto'; import 'fake-indexeddb/auto';
import { rootCurrentWorkspaceIdAtom } from '@affine/workspace/atom'; import { rootCurrentWorkspaceIdAtom } from '@affine/workspace/atom';
import type { PageMeta } from '@blocksuite/store';
import matchers from '@testing-library/jest-dom/matchers'; import matchers from '@testing-library/jest-dom/matchers';
import type { RenderResult } from '@testing-library/react'; import type { RenderResult } from '@testing-library/react';
import { render, renderHook } from '@testing-library/react'; import { render, renderHook } from '@testing-library/react';
@@ -95,7 +94,6 @@ const initPinBoard = async () => {
const app = render( const app = render(
<App <App
blockSuiteWorkspace={blockSuiteWorkspace as BlockSuiteWorkspace} blockSuiteWorkspace={blockSuiteWorkspace as BlockSuiteWorkspace}
allMetas={blockSuiteWorkspace.meta.pageMetas as PageMeta[]}
openPage={() => {}} openPage={() => {}}
/> />
); );

View File

@@ -8,7 +8,7 @@ import {
export const StyledCollapsedButton = styled('button')<{ export const StyledCollapsedButton = styled('button')<{
collapse: boolean; collapse: boolean;
show?: boolean; show?: boolean;
}>(({ collapse, show = true, theme }) => { }>(({ collapse, show = true }) => {
return { return {
width: '16px', width: '16px',
height: '100%', height: '100%',
@@ -43,7 +43,6 @@ export const StyledPinboard = styled('div')<{
disableCollapse, disableCollapse,
disable = false, disable = false,
active = false, active = false,
theme,
isOver, isOver,
textWrap = false, textWrap = false,
}) => { }) => {
@@ -66,7 +65,7 @@ export const StyledPinboard = styled('div')<{
userSelect: 'none', userSelect: 'none',
...(textWrap ...(textWrap
? { ? {
wordBreak: 'break-all', wordBreak: 'break-word',
whiteSpace: 'pre-wrap', whiteSpace: 'pre-wrap',
} }
: {}), : {}),
@@ -106,7 +105,7 @@ export const StyledOperationButton = styled(IconButton, {
}; };
}); });
export const StyledSearchContainer = styled('div')(({ theme }) => { export const StyledSearchContainer = styled('div')(() => {
return { return {
width: 'calc(100% - 24px)', width: 'calc(100% - 24px)',
margin: '0 auto', margin: '0 auto',
@@ -125,7 +124,7 @@ export const StyledMenuContent = styled('div')(() => {
overflow: 'auto', overflow: 'auto',
}; };
}); });
export const StyledMenuSubTitle = styled('div')(({ theme }) => { export const StyledMenuSubTitle = styled('div')(() => {
return { return {
color: 'var(--affine-text-secondary-color)', color: 'var(--affine-text-secondary-color)',
lineHeight: '36px', lineHeight: '36px',
@@ -133,7 +132,7 @@ export const StyledMenuSubTitle = styled('div')(({ theme }) => {
}; };
}); });
export const StyledMenuFooter = styled('div')(({ theme }) => { export const StyledMenuFooter = styled('div')(() => {
return { return {
width: 'calc(100% - 24px)', width: 'calc(100% - 24px)',
margin: '0 auto', margin: '0 auto',

View File

@@ -9,7 +9,7 @@ import {
export const StyledListItem = styled('div')<{ export const StyledListItem = styled('div')<{
active?: boolean; active?: boolean;
disabled?: boolean; disabled?: boolean;
}>(({ theme, active, disabled }) => { }>(({ active, disabled }) => {
return { return {
height: '32px', height: '32px',
color: active color: active
@@ -46,7 +46,7 @@ export const StyledListItem = styled('div')<{
export const StyledCollapseButton = styled('button')<{ export const StyledCollapseButton = styled('button')<{
collapse: boolean; collapse: boolean;
show?: boolean; show?: boolean;
}>(({ collapse, show = true, theme }) => { }>(({ collapse, show = true }) => {
return { return {
width: '16px', width: '16px',
height: '100%', height: '100%',
@@ -75,7 +75,7 @@ export const StyledCollapseItem = styled('div')<{
active?: boolean; active?: boolean;
isOver?: boolean; isOver?: boolean;
textWrap?: boolean; textWrap?: boolean;
}>(({ disable = false, active = false, theme, isOver, textWrap = false }) => { }>(({ disable = false, active = false, isOver, textWrap = false }) => {
return { return {
width: '100%', width: '100%',
lineHeight: '1.5', lineHeight: '1.5',
@@ -94,7 +94,7 @@ export const StyledCollapseItem = styled('div')<{
userSelect: 'none', userSelect: 'none',
...(textWrap ...(textWrap
? { ? {
wordBreak: 'break-all', wordBreak: 'break-word',
whiteSpace: 'pre-wrap', whiteSpace: 'pre-wrap',
} }
: {}), : {}),
@@ -174,7 +174,7 @@ const slideOut2 = keyframes({
export const StyledChangeLog = styled('div')<{ export const StyledChangeLog = styled('div')<{
isClose?: boolean; isClose?: boolean;
}>(({ theme, isClose }) => { }>(({ isClose }) => {
return { return {
width: '110%', width: '110%',
height: '32px', height: '32px',

View File

@@ -41,8 +41,6 @@
"lit": "^2.7.2", "lit": "^2.7.2",
"lottie-web": "^5.11.0", "lottie-web": "^5.11.0",
"react": "^18.2.0", "react": "^18.2.0",
"react-dnd": "^16.0.1",
"react-dnd-html5-backend": "^16.0.1",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-error-boundary": "^4.0.3", "react-error-boundary": "^4.0.3",
"react-is": "^18.2.0" "react-is": "^18.2.0"

View File

@@ -1,147 +1,43 @@
import { useEffect } from 'react'; import { useDraggable } from '@dnd-kit/core';
import { useDrag, useDrop } from 'react-dnd'; import { useMemo } from 'react';
import { import {
StyledCollapse, StyledCollapse,
StyledNodeLine,
StyledTreeNodeContainer, StyledTreeNodeContainer,
StyledTreeNodeWrapper, StyledTreeNodeWrapper,
} from './styles'; } from './styles';
import type { import { NodeLine, TreeNodeItem, TreeNodeItemWithDnd } from './TreeNodeInner';
Node, import type { TreeNodeProps } from './types';
NodeLIneProps,
TreeNodeItemProps,
TreeNodeProps,
} from './types';
const NodeLine = <RenderProps,>({
node,
onDrop,
allowDrop = true,
isTop = false,
}: NodeLIneProps<RenderProps>) => {
const [{ isOver }, drop] = useDrop(
() => ({
accept: 'node',
drop: (item: Node<RenderProps>, monitor) => {
const didDrop = monitor.didDrop();
if (didDrop) {
return;
}
onDrop?.(item.id, node.id, {
internal: false,
topLine: isTop,
bottomLine: !isTop,
});
},
collect: monitor => ({
isOver: monitor.isOver(),
canDrop: monitor.canDrop(),
}),
}),
[onDrop]
);
return <StyledNodeLine ref={drop} show={isOver && allowDrop} isTop={isTop} />;
};
const TreeNodeItemWithDnd = <RenderProps,>({
node,
allowDrop,
setCollapsed,
...otherProps
}: TreeNodeItemProps<RenderProps>) => {
const { onAdd, onDelete, onDrop } = otherProps;
const [{ canDrop, isOver }, drop] = useDrop(
() => ({
accept: 'node',
drop: (item: Node<RenderProps>, monitor) => {
const didDrop = monitor.didDrop();
if (didDrop || item.id === node.id || !allowDrop) {
return;
}
onDrop?.(item.id, node.id, {
internal: true,
topLine: false,
bottomLine: false,
});
},
collect: monitor => ({
isOver: monitor.isOver(),
canDrop: monitor.canDrop() && allowDrop,
}),
}),
[onDrop, allowDrop]
);
useEffect(() => {
if (isOver && canDrop) {
setCollapsed(node.id, false);
}
}, [isOver, canDrop, setCollapsed, node.id]);
return (
<TreeNodeItem
dropRef={drop}
onAdd={onAdd}
onDelete={onDelete}
node={node}
allowDrop={allowDrop}
setCollapsed={setCollapsed}
isOver={isOver}
canDrop={canDrop}
{...otherProps}
/>
);
};
const TreeNodeItem = <RenderProps,>({
node,
collapsed,
setCollapsed,
selectedId,
isOver = false,
canDrop = false,
onAdd,
onDelete,
dropRef,
disableCollapse,
}: TreeNodeItemProps<RenderProps>) => {
return (
<div ref={dropRef}>
{node.render?.(node, {
isOver: isOver && canDrop,
onAdd: () => onAdd?.(node.id),
onDelete: () => onDelete?.(node.id),
collapsed,
setCollapsed,
isSelected: selectedId === node.id,
disableCollapse,
})}
</div>
);
};
export const TreeNodeWithDnd = <RenderProps,>( export const TreeNodeWithDnd = <RenderProps,>(
props: TreeNodeProps<RenderProps> props: TreeNodeProps<RenderProps>
) => { ) => {
const [{ isDragging }, drag] = useDrag(() => ({ const { draggingId, node, allowDrop } = props;
type: 'node', const { attributes, listeners, setNodeRef } = useDraggable({
item: props.node, id: props.node.id,
collect: monitor => ({ });
isDragging: monitor.isDragging(), const isDragging = useMemo(
}), () => draggingId === node.id,
})); [draggingId, node.id]
);
return <TreeNode dragRef={drag} isDragging={isDragging} {...props} />; return (
<StyledTreeNodeContainer
ref={setNodeRef}
isDragging={isDragging}
{...listeners}
{...attributes}
>
<TreeNode
{...props}
allowDrop={allowDrop === false ? allowDrop : !isDragging}
/>
</StyledTreeNodeContainer>
);
}; };
export const TreeNode = <RenderProps,>({ export const TreeNode = <RenderProps,>({
node, node,
index, index,
isDragging = false,
allowDrop = true, allowDrop = true,
dragRef,
...otherProps ...otherProps
}: TreeNodeProps<RenderProps>) => { }: TreeNodeProps<RenderProps>) => {
const { indent, enableDnd, collapsedIds } = otherProps; const { indent, enableDnd, collapsedIds } = otherProps;
@@ -149,13 +45,13 @@ export const TreeNode = <RenderProps,>({
const { renderTopLine = true, renderBottomLine = true } = node; const { renderTopLine = true, renderBottomLine = true } = node;
return ( return (
<StyledTreeNodeContainer ref={dragRef} isDragging={isDragging}> <>
<StyledTreeNodeWrapper> <StyledTreeNodeWrapper>
{enableDnd && renderTopLine && index === 0 && ( {enableDnd && renderTopLine && index === 0 && (
<NodeLine <NodeLine
node={node} node={node}
{...otherProps} {...otherProps}
allowDrop={!isDragging && allowDrop} allowDrop={allowDrop}
isTop={true} isTop={true}
/> />
)} )}
@@ -180,11 +76,7 @@ export const TreeNode = <RenderProps,>({
{enableDnd && {enableDnd &&
renderBottomLine && renderBottomLine &&
(!node.children?.length || collapsed) && ( (!node.children?.length || collapsed) && (
<NodeLine <NodeLine node={node} {...otherProps} allowDrop={allowDrop} />
node={node}
{...otherProps}
allowDrop={!isDragging && allowDrop}
/>
)} )}
</StyledTreeNodeWrapper> </StyledTreeNodeWrapper>
<StyledCollapse in={!collapsed} indent={indent}> <StyledCollapse in={!collapsed} indent={indent}>
@@ -195,8 +87,8 @@ export const TreeNode = <RenderProps,>({
key={childNode.id} key={childNode.id}
node={childNode} node={childNode}
index={index} index={index}
allowDrop={isDragging ? false : allowDrop}
{...otherProps} {...otherProps}
allowDrop={allowDrop}
/> />
) : ( ) : (
<TreeNode <TreeNode
@@ -209,6 +101,6 @@ export const TreeNode = <RenderProps,>({
) )
)} )}
</StyledCollapse> </StyledCollapse>
</StyledTreeNodeContainer> </>
); );
}; };

View File

@@ -0,0 +1,92 @@
import { useDroppable } from '@dnd-kit/core';
import { StyledNodeLine } from './styles';
import type { NodeLIneProps, TreeNodeItemProps } from './types';
export const NodeLine = <RenderProps,>({
node,
allowDrop = true,
isTop = false,
}: NodeLIneProps<RenderProps>) => {
const { isOver, setNodeRef } = useDroppable({
id: `${node.id}-${isTop ? 'top' : 'bottom'}-line`,
disabled: !allowDrop,
data: {
node,
position: {
topLine: isTop,
bottomLine: !isTop,
internal: false,
},
},
});
return (
<StyledNodeLine
ref={setNodeRef}
isOver={isOver && allowDrop}
isTop={isTop}
/>
);
};
export const TreeNodeItemWithDnd = <RenderProps,>({
node,
allowDrop,
setCollapsed,
...otherProps
}: TreeNodeItemProps<RenderProps>) => {
const { onAdd, onDelete } = otherProps;
const { isOver, setNodeRef } = useDroppable({
id: node.id,
disabled: !allowDrop,
data: {
node,
position: {
topLine: false,
bottomLine: false,
internal: true,
},
},
});
return (
<div ref={setNodeRef}>
<TreeNodeItem
onAdd={onAdd}
onDelete={onDelete}
node={node}
allowDrop={allowDrop}
setCollapsed={setCollapsed}
isOver={isOver}
{...otherProps}
/>
</div>
);
};
export const TreeNodeItem = <RenderProps,>({
node,
collapsed,
setCollapsed,
selectedId,
isOver = false,
onAdd,
onDelete,
disableCollapse,
allowDrop = true,
}: TreeNodeItemProps<RenderProps>) => {
return (
<>
{node.render?.(node, {
isOver: isOver && allowDrop,
onAdd: () => onAdd?.(node.id),
onDelete: () => onDelete?.(node.id),
collapsed,
setCollapsed,
isSelected: selectedId === node.id,
disableCollapse,
})}
</>
);
};

View File

@@ -1,11 +1,20 @@
import { useEffect, useState } from 'react'; import type {
import { DndProvider } from 'react-dnd'; DragEndEvent} from '@dnd-kit/core';
import { HTML5Backend } from 'react-dnd-html5-backend'; import {
closestCenter,
DndContext,
DragOverlay,
PointerSensor,
useSensor,
useSensors
} from '@dnd-kit/core';
import { useCallback, useState } from 'react';
import useCollapsed from './hooks/useCollapsed';
import useSelectWithKeyboard from './hooks/useSelectWithKeyboard';
import { TreeNode, TreeNodeWithDnd } from './TreeNode'; import { TreeNode, TreeNodeWithDnd } from './TreeNode';
import type { TreeNodeProps, TreeViewProps } from './types'; import type { TreeViewProps } from './types';
import { flattenIds } from './utils'; import { findNode } from './utils';
export const TreeView = <RenderProps,>({ export const TreeView = <RenderProps,>({
data, data,
enableKeyboardSelection, enableKeyboardSelection,
@@ -13,69 +22,51 @@ export const TreeView = <RenderProps,>({
enableDnd = true, enableDnd = true,
initialCollapsedIds = [], initialCollapsedIds = [],
disableCollapse, disableCollapse,
onDrop,
...otherProps ...otherProps
}: TreeViewProps<RenderProps>) => { }: TreeViewProps<RenderProps>) => {
const [selectedId, setSelectedId] = useState<string>(); const sensors = useSensors(
// TODO: should record collapsedIds in localStorage useSensor(PointerSensor, {
const [collapsedIds, setCollapsedIds] = activationConstraint: {
useState<string[]>(initialCollapsedIds); distance: 8,
},
})
);
const { selectedId } = useSelectWithKeyboard({
data,
onSelect,
enableKeyboardSelection,
});
useEffect(() => { const { collapsedIds, setCollapsed } = useCollapsed({ disableCollapse });
if (!enableKeyboardSelection) {
const [draggingId, setDraggingId] = useState<string>();
const onDragEnd = useCallback(
(e: DragEndEvent) => {
const { active, over } = e;
const position = over?.data.current?.position;
const dropId = over?.data.current?.node.id;
if (!over || !active || !position) {
return; return;
} }
const flattenedIds = flattenIds<RenderProps>(data); onDrop?.(active.id as string, dropId, position);
},
const handleDirectionKeyDown = (e: KeyboardEvent) => { [onDrop]
if (e.key !== 'ArrowDown' && e.key !== 'ArrowUp') { );
return; const onDragMove = useCallback((e: DragEndEvent) => {
} setDraggingId(e.active.id as string);
if (selectedId === undefined) { }, []);
setSelectedId(flattenedIds[0]);
return;
}
let selectedIndex = flattenedIds.indexOf(selectedId);
if (e.key === 'ArrowDown') {
selectedIndex < flattenedIds.length - 1 && selectedIndex++;
}
if (e.key === 'ArrowUp') {
selectedIndex > 0 && selectedIndex--;
}
setSelectedId(flattenedIds[selectedIndex]);
};
const handleEnterKeyDown = (e: KeyboardEvent) => {
if (e.key !== 'Enter') {
return;
}
selectedId && onSelect?.(selectedId);
};
document.addEventListener('keydown', handleDirectionKeyDown);
document.addEventListener('keydown', handleEnterKeyDown);
return () => {
document.removeEventListener('keydown', handleDirectionKeyDown);
document.removeEventListener('keydown', handleEnterKeyDown);
};
}, [data, enableKeyboardSelection, onSelect, selectedId]);
const setCollapsed: TreeNodeProps['setCollapsed'] = (id, collapsed) => {
if (disableCollapse) {
return;
}
if (collapsed) {
setCollapsedIds(ids => [...ids, id]);
} else {
setCollapsedIds(ids => ids.filter(i => i !== id));
}
};
if (enableDnd) { if (enableDnd) {
return ( return (
<DndProvider backend={HTML5Backend}> <DndContext
sensors={sensors}
collisionDetection={closestCenter}
onDragMove={onDragMove}
onDragEnd={onDragEnd}
>
{data.map((node, index) => ( {data.map((node, index) => (
<TreeNodeWithDnd <TreeNodeWithDnd
key={node.id} key={node.id}
@@ -86,10 +77,24 @@ export const TreeView = <RenderProps,>({
selectedId={selectedId} selectedId={selectedId}
enableDnd={enableDnd} enableDnd={enableDnd}
disableCollapse={disableCollapse} disableCollapse={disableCollapse}
draggingId={draggingId}
{...otherProps} {...otherProps}
/> />
))} ))}
</DndProvider>
<DragOverlay>
{draggingId && (
<TreeNode
node={findNode(draggingId, data)!}
index={0}
allowDrop={false}
collapsedIds={collapsedIds}
setCollapsed={() => {}}
{...otherProps}
/>
)}
</DragOverlay>
</DndContext>
); );
} }

View File

@@ -0,0 +1,32 @@
import { useState } from 'react';
import type { TreeNodeProps } from '../types';
export const useCollapsed = <RenderProps>({
initialCollapsedIds = [],
disableCollapse = false,
}: {
disableCollapse?: boolean;
initialCollapsedIds?: string[];
}) => {
// TODO: should record collapsedIds in localStorage
const [collapsedIds, setCollapsedIds] =
useState<string[]>(initialCollapsedIds);
const setCollapsed: TreeNodeProps['setCollapsed'] = (id, collapsed) => {
if (disableCollapse) {
return;
}
if (collapsed) {
setCollapsedIds(ids => [...ids, id]);
} else {
setCollapsedIds(ids => ids.filter(i => i !== id));
}
};
return {
collapsedIds,
setCollapsed,
};
};
export default useCollapsed;

View File

@@ -0,0 +1,63 @@
import { useEffect, useState } from 'react';
import type { TreeViewProps } from '../types';
import { flattenIds } from '../utils';
export const useSelectWithKeyboard = <RenderProps>({
data,
enableKeyboardSelection,
onSelect,
}: Pick<
TreeViewProps<RenderProps>,
'data' | 'enableKeyboardSelection' | 'onSelect'
>) => {
const [selectedId, setSelectedId] = useState<string>();
// TODO: should record collapsedIds in localStorage
useEffect(() => {
if (!enableKeyboardSelection) {
return;
}
const flattenedIds = flattenIds<RenderProps>(data);
const handleDirectionKeyDown = (e: KeyboardEvent) => {
if (e.key !== 'ArrowDown' && e.key !== 'ArrowUp') {
return;
}
if (selectedId === undefined) {
setSelectedId(flattenedIds[0]);
return;
}
let selectedIndex = flattenedIds.indexOf(selectedId);
if (e.key === 'ArrowDown') {
selectedIndex < flattenedIds.length - 1 && selectedIndex++;
}
if (e.key === 'ArrowUp') {
selectedIndex > 0 && selectedIndex--;
}
setSelectedId(flattenedIds[selectedIndex]);
};
const handleEnterKeyDown = (e: KeyboardEvent) => {
if (e.key !== 'Enter') {
return;
}
selectedId && onSelect?.(selectedId);
};
document.addEventListener('keydown', handleDirectionKeyDown);
document.addEventListener('keydown', handleEnterKeyDown);
return () => {
document.removeEventListener('keydown', handleDirectionKeyDown);
document.removeEventListener('keydown', handleEnterKeyDown);
};
}, [data, enableKeyboardSelection, onSelect, selectedId]);
return {
selectedId,
};
};
export default useSelectWithKeyboard;

View File

@@ -16,16 +16,17 @@ export const StyledTreeNodeWrapper = styled('div')(() => {
}; };
}); });
export const StyledTreeNodeContainer = styled('div')<{ isDragging?: boolean }>( export const StyledTreeNodeContainer = styled('div')<{ isDragging?: boolean }>(
({ isDragging = false, theme }) => { ({ isDragging = false }) => {
return { return {
background: isDragging ? 'var(--affine-hover-color)' : '', background: isDragging ? 'var(--affine-hover-color)' : '',
// opacity: isDragging ? 0.4 : 1,
}; };
} }
); );
export const StyledNodeLine = styled('div')<{ show: boolean; isTop?: boolean }>( export const StyledNodeLine = styled('div')<{
({ show, isTop = false, theme }) => { isOver: boolean;
isTop?: boolean;
}>(({ isOver, isTop = false }) => {
return { return {
position: 'absolute', position: 'absolute',
left: '0', left: '0',
@@ -33,11 +34,10 @@ export const StyledNodeLine = styled('div')<{ show: boolean; isTop?: boolean }>(
width: '100%', width: '100%',
paddingTop: '2x', paddingTop: '2x',
borderTop: '2px solid', borderTop: '2px solid',
borderColor: show ? 'var(--affine-primary-color)' : 'transparent', borderColor: isOver ? 'var(--affine-primary-color)' : 'transparent',
boxShadow: show boxShadow: isOver
? `0px 0px 8px ${alpha(lightTheme.primaryColor, 0.35)}` ? `0px 0px 8px ${alpha(lightTheme.primaryColor, 0.35)}`
: 'none', : 'none',
zIndex: 1, zIndex: 1,
}; };
} });
);

View File

@@ -1,4 +1,4 @@
import type { CSSProperties, ReactNode, Ref } from 'react'; import type { CSSProperties, ReactNode } from 'react';
export type DropPosition = { export type DropPosition = {
topLine: boolean; topLine: boolean;
@@ -50,8 +50,7 @@ export type TreeNodeProps<RenderProps = unknown> = {
setCollapsed: (id: string, collapsed: boolean) => void; setCollapsed: (id: string, collapsed: boolean) => void;
allowDrop?: boolean; allowDrop?: boolean;
selectedId?: string; selectedId?: string;
isDragging?: boolean; draggingId?: string;
dragRef?: Ref<HTMLDivElement>;
} & CommonProps<RenderProps>; } & CommonProps<RenderProps>;
export type TreeNodeItemProps<RenderProps = unknown> = { export type TreeNodeItemProps<RenderProps = unknown> = {
@@ -59,9 +58,6 @@ export type TreeNodeItemProps<RenderProps = unknown> = {
setCollapsed: (id: string, collapsed: boolean) => void; setCollapsed: (id: string, collapsed: boolean) => void;
isOver?: boolean; isOver?: boolean;
canDrop?: boolean;
dropRef?: Ref<HTMLDivElement>;
} & TreeNodeProps<RenderProps>; } & TreeNodeProps<RenderProps>;
export type TreeViewProps<RenderProps = unknown> = { export type TreeViewProps<RenderProps = unknown> = {
@@ -73,4 +69,4 @@ export type TreeViewProps<RenderProps = unknown> = {
export type NodeLIneProps<RenderProps = unknown> = { export type NodeLIneProps<RenderProps = unknown> = {
allowDrop: boolean; allowDrop: boolean;
isTop?: boolean; isTop?: boolean;
} & Pick<TreeNodeProps<RenderProps>, 'node' | 'onDrop'>; } & Pick<TreeNodeProps<RenderProps>, 'node'>;

View File

@@ -1,4 +1,4 @@
import type { Node } from '@affine/component'; import type { Node } from './types';
export function flattenIds<RenderProps>(arr: Node<RenderProps>[]): string[] { export function flattenIds<RenderProps>(arr: Node<RenderProps>[]): string[] {
const result: string[] = []; const result: string[] = [];
@@ -16,3 +16,21 @@ export function flattenIds<RenderProps>(arr: Node<RenderProps>[]): string[] {
flatten(arr); flatten(arr);
return result; return result;
} }
export function findNode<RenderProps>(
id: string,
nodes: Node<RenderProps>[]
): Node<RenderProps> | undefined {
for (let i = 0, len = nodes.length; i < len; i++) {
const node = nodes[i];
if (node.id === id) {
return node;
}
if (node.children) {
const result = findNode(id, node.children);
if (result) {
return result;
}
}
}
}

View File

@@ -80,8 +80,6 @@ __metadata:
lit: ^2.7.2 lit: ^2.7.2
lottie-web: ^5.11.0 lottie-web: ^5.11.0
react: ^18.2.0 react: ^18.2.0
react-dnd: ^16.0.1
react-dnd-html5-backend: ^16.0.1
react-dom: ^18.2.0 react-dom: ^18.2.0
react-error-boundary: ^4.0.3 react-error-boundary: ^4.0.3
react-is: ^18.2.0 react-is: ^18.2.0
@@ -19643,16 +19641,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"react-dnd-html5-backend@npm:^16.0.1": "react-dnd@npm:*":
version: 16.0.1
resolution: "react-dnd-html5-backend@npm:16.0.1"
dependencies:
dnd-core: ^16.0.1
checksum: e2368bf85d5632a5cd867b743feb54c9052d909ea5331608860fa455edf3c633ac791f5b338e3db29b19ea8670c0ba5fb43c9c1c2510760bea030811d726cdfa
languageName: node
linkType: hard
"react-dnd@npm:*, react-dnd@npm:^16.0.1":
version: 16.0.1 version: 16.0.1
resolution: "react-dnd@npm:16.0.1" resolution: "react-dnd@npm:16.0.1"
dependencies: dependencies: