dbeaver/pro#9659 add dnd scroll container (#4476)

This commit is contained in:
alex
2026-07-22 14:24:53 +02:00
committed by GitHub
parent af42d7f50a
commit 81a9be17b8
4 changed files with 96 additions and 2 deletions
@@ -0,0 +1,81 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2026 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { forwardRef, useEffect, useRef } from 'react';
import { observer } from 'mobx-react-lite';
import { useMergeRefs, useMouse } from '@cloudbeaver/core-blocks';
const EDGE = 60;
const MAX_SPEED = 12;
export interface IDNDScrollContainerProps {
isDragging: boolean;
className?: string;
children: React.ReactNode;
}
export const DNDScrollContainer = observer(
forwardRef<HTMLDivElement, IDNDScrollContainerProps>(function DNDScrollContainer({ isDragging, className, children }, externalRef) {
const innerRef = useRef<HTMLDivElement>(null);
const speedRef = useRef(0);
const rafRef = useRef<number>(null);
const mouse = useMouse<HTMLDivElement>();
const el = innerRef.current;
if (isDragging && el && mouse.state.position) {
const y = mouse.state.position.y;
const h = el.getBoundingClientRect().height;
if (y < EDGE) {
speedRef.current = -MAX_SPEED * Math.min((EDGE - y) / EDGE, 1);
} else if (y > h - EDGE) {
speedRef.current = MAX_SPEED * Math.min((y - (h - EDGE)) / EDGE, 1);
} else {
speedRef.current = 0;
}
} else {
speedRef.current = 0;
}
useEffect(() => {
if (!isDragging) {
return;
}
// rAF loop for smooth scrolling
const loop = () => {
const node = innerRef.current;
if (node && speedRef.current !== 0) {
node.scrollTop += speedRef.current;
}
rafRef.current = requestAnimationFrame(loop);
};
rafRef.current = requestAnimationFrame(loop);
return () => {
if (rafRef.current) {
cancelAnimationFrame(rafRef.current);
}
};
}, [isDragging]);
const ref = useMergeRefs(innerRef, mouse.reference, externalRef);
return (
<div ref={ref} className={className}>
{children}
</div>
);
}),
);
@@ -0,0 +1,11 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2026 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { importLazyComponent } from '@cloudbeaver/core-blocks';
export const DNDScrollContainer = importLazyComponent(() => import('./DNDScrollContainer.js').then(m => m.DNDScrollContainer));
+1
View File
@@ -27,6 +27,7 @@ export * from './DragAndDrop/DNDPreviewLoader.js';
export * from './DragAndDrop/DNDProviderLoader.js';
export * from './DragAndDrop/useDNDBox.js';
export * from './DragAndDrop/useDNDData.js';
export * from './DragAndDrop/DNDScrollContainerLoader.js';
export * from './Form/Components/IBaseFormProps.js';
export * from './Form/Components/BaseFormLazy.js';
@@ -26,6 +26,7 @@ import {
import { useService } from '@cloudbeaver/core-di';
import { EventContext, EventStopPropagationFlag } from '@cloudbeaver/core-events';
import { EObjectFeature, type NavNode, NavNodeInfoResource, NavTreeResource, ROOT_NODE_PATH } from '@cloudbeaver/core-navigation-tree';
import { DNDScrollContainer } from '@cloudbeaver/core-ui';
import { useNavTreeDropBox } from '../useNavTreeDropBox.js';
import style from './ElementsTree.module.css';
@@ -172,7 +173,7 @@ export const ElementsTree = observer(
return (
<>
<ElementsTreeTools tree={tree} settingsElements={settingsElements} />
<div ref={treeMergedRef} className={s(styles, { treeBox: true })}>
<DNDScrollContainer ref={treeMergedRef} className={s(styles, { treeBox: true })} isDragging={!!dndBox.state.context}>
<ElementsTreeContext.Provider value={context}>
<div className={s(styles, { box: true }, className)}>
<FolderExplorer state={folderExplorer}>
@@ -210,7 +211,7 @@ export const ElementsTree = observer(
</FolderExplorer>
</div>
</ElementsTreeContext.Provider>
</div>
</DNDScrollContainer>
</>
);
}),