diff --git a/example/theme.less b/example/theme.less index 5c7f9386..e9097932 100644 --- a/example/theme.less +++ b/example/theme.less @@ -4,14 +4,12 @@ left: 0; bottom: 0; width: 300px; - overflow-x: hidden; - overflow-y: auto; background-color: #21252B; } .m-node { &.placeholder { - border: 1px dashed #1385e5; + outline: 1px dashed #1385e5; } .inner { diff --git a/example/tree.js b/example/tree.js index 958dd15c..0562ea95 100644 --- a/example/tree.js +++ b/example/tree.js @@ -37,6 +37,9 @@ module.exports = { { module: 'index.html', leaf: true + }, + { + module: 'test-folder' } ] }, @@ -88,6 +91,9 @@ module.exports = { { module: 'webpack.config.js', leaf: true + }, + { + module: 'test-folder-2' } ] }; diff --git a/lib/react-ui-tree.js b/lib/react-ui-tree.js index f63dbf1b..41956c6e 100644 --- a/lib/react-ui-tree.js +++ b/lib/react-ui-tree.js @@ -7,12 +7,16 @@ class UITree extends Component { static propTypes = { tree: PropTypes.object.isRequired, paddingLeft: PropTypes.number, + scrollMargin: PropTypes.number, + scrollSpeed: PropTypes.number, renderNode: PropTypes.func.isRequired, draggable: PropTypes.bool }; static defaultProps = { paddingLeft: 20, + scrollMargin: 20, + scrollSpeed: 200, draggable: true }; @@ -20,6 +24,13 @@ class UITree extends Component { super(props); this.state = this.init(props); + this.treeEl = React.createRef(); + + this.startScrollHeight = null; + this.lastMousePos = { clientX: null, clientY: null }; + this.scrollEnabled = false; + this.currentScrollSpeed = 0; + this.lastScrollTimestamp = null; } componentWillReceiveProps(nextProps) { @@ -80,7 +91,7 @@ class UITree extends Component { const draggingDom = this.getDraggingDom(); return ( -
+
{draggingDom} { if (e.button !== 0 || id === 1) return; + const { scrollHeight, scrollTop } = this.treeEl.current; + + this.startScrollHeight = scrollHeight; + this.setState({ dragging: { id: id, w: dom.offsetWidth, h: dom.offsetHeight, + ph: dom.parentNode.offsetHeight, x: dom.offsetLeft, y: dom.offsetTop, }, @@ -110,18 +126,48 @@ class UITree extends Component { x: dom.offsetLeft, y: dom.offsetTop, offsetX: e.clientX, - offsetY: e.clientY + offsetY: e.clientY + scrollTop } }); window.addEventListener('mousemove', this.drag); window.addEventListener('mouseup', this.dragEnd); + + this.lastMousePos.clientX = e.clientX; + this.lastMousePos.clientY = e.clientY; + this.scrollEnabled = true; + requestAnimationFrame(this.scroll); + }; + + scroll = (timestamp) => { + if (!this.scrollEnabled) return; + + if (this.lastScrollTimestamp === null || this.currentScrollSpeed === 0){ + this.lastScrollTimestamp = timestamp; + requestAnimationFrame(this.scroll); + return; + } + + const delta = timestamp - this.lastScrollTimestamp; + this.treeEl.current.scrollTop += this.currentScrollSpeed * delta / 1000; + this.drag(this.lastMousePos); + + this.lastScrollTimestamp = timestamp; + requestAnimationFrame(this.scroll); }; - drag = e => { + drag = (e) => { + if (e) { + this.lastMousePos.clientX = e.clientX; + this.lastMousePos.clientY = e.clientY; + } else { + e = this.lastMousePos; + } + const { clientX, clientY } = e; + const tree = this.state.tree; const dragging = this.state.dragging; - const paddingLeft = this.props.paddingLeft; + const { paddingLeft, scrollMargin, scrollSpeed } = this.props; let newIndex = null; let index = tree.getIndex(dragging.id); @@ -134,9 +180,11 @@ class UITree extends Component { const _offsetX = this.state.start.offsetX; const _offsetY = this.state.start.offsetY; + const { scrollTop, clientHeight } = this.treeEl.current; + const pos = { - x: _startX + e.clientX - _offsetX, - y: _startY + e.clientY - _offsetY + x: _startX + clientX - _offsetX, + y: Math.min(this.startScrollHeight - dragging.ph, _startY + clientY + scrollTop - _offsetY) }; dragging.x = pos.x; dragging.y = pos.y; @@ -198,7 +246,15 @@ class UITree extends Component { newIndex.node.collapsed = collapsed; dragging.id = newIndex.id; } - + + if (dragging.y + dragging.ph > scrollTop + clientHeight - scrollMargin) { + this.currentScrollSpeed = scrollSpeed; + } else if (dragging.y < scrollTop + scrollMargin) { + this.currentScrollSpeed = -scrollSpeed; + } else { + this.currentScrollSpeed = 0; + } + this.setState({ tree: tree, dragging: dragging @@ -222,6 +278,10 @@ class UITree extends Component { window.removeEventListener('mousemove', this.drag); window.removeEventListener('mouseup', this.dragEnd); + this.lastMousePos.clientX = null; + this.lastMousePos.clientY = null; + this.scrollEnabled = false; + const index = this.state.tree.getIndex(draggingId); if (index === undefined) return; diff --git a/lib/react-ui-tree.less b/lib/react-ui-tree.less index 3c255719..9fd9e0d4 100644 --- a/lib/react-ui-tree.less +++ b/lib/react-ui-tree.less @@ -8,7 +8,9 @@ .m-tree { position: relative; - overflow: hidden; + overflow-x: hidden; + overflow-y: auto; + height: 100%; .f-no-select; } @@ -24,7 +26,7 @@ } &.placeholder { - border: 1px dashed #ccc; + outline: 1px dashed #ccc; } .inner {