From 3e1a4a9e95079e08942f4404c498b75f98f61848 Mon Sep 17 00:00:00 2001 From: Dan Sumption Date: Mon, 2 Oct 2017 08:05:36 +0100 Subject: [PATCH 01/22] Add draggable property --- example/app.js | 1 + lib/react-ui-tree.js | 8 +++++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/example/app.js b/example/app.js index 121b8e6d..1d7a2b66 100644 --- a/example/app.js +++ b/example/app.js @@ -39,6 +39,7 @@ class App extends Component {
From c3dfb11355ca3c4bb8f15c5fb69f02d273988636 Mon Sep 17 00:00:00 2001 From: Dan Sumption Date: Mon, 2 Oct 2017 09:54:25 +0100 Subject: [PATCH 02/22] reset draggable to true in example app --- example/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/app.js b/example/app.js index 1d7a2b66..19bac4fc 100644 --- a/example/app.js +++ b/example/app.js @@ -39,7 +39,7 @@ class App extends Component {
Date: Fri, 1 Jun 2018 15:58:14 -0700 Subject: [PATCH 03/22] Scoped package v3.2.0 --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index c2823db2..02e74aba 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { - "name": "react-ui-tree", - "version": "3.1.0", + "name": "@robertlong/react-ui-tree", + "version": "3.2.0", "description": "React tree component", "main": "index.js", "scripts": { From d6284b5727a801ce33bf0707fe1c0512e837dea5 Mon Sep 17 00:00:00 2001 From: Robert Long Date: Fri, 1 Jun 2018 16:16:09 -0700 Subject: [PATCH 04/22] Add changed node and parent node to the onChange callback. --- lib/react-ui-tree.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/react-ui-tree.js b/lib/react-ui-tree.js index fd5f1a80..b60c9973 100644 --- a/lib/react-ui-tree.js +++ b/lib/react-ui-tree.js @@ -206,6 +206,9 @@ class UITree extends Component { }; dragEnd = () => { + const index = this.state.tree.getIndex(this.state.dragging.id); + const parent = this.state.tree.get(index.parent); + this.setState({ dragging: { id: null, @@ -216,14 +219,14 @@ class UITree extends Component { } }); - this.change(this.state.tree); + this.change(this.state.tree, parent, index.node); window.removeEventListener('mousemove', this.drag); window.removeEventListener('mouseup', this.dragEnd); }; - change = tree => { + change = (tree, parent, node) => { this._updated = true; - if (this.props.onChange) this.props.onChange(tree.obj); + if (this.props.onChange) this.props.onChange(tree.obj, parent, node); }; toggleCollapse = nodeId => { @@ -237,7 +240,7 @@ class UITree extends Component { tree: tree }); - this.change(tree); + this.change(tree, null, null); }; } From 07609a4f29e0efc9251c8558696a73ee1bc6d9a9 Mon Sep 17 00:00:00 2001 From: Robert Long Date: Fri, 1 Jun 2018 16:16:32 -0700 Subject: [PATCH 05/22] v3.3.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 02e74aba..98c8eaea 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@robertlong/react-ui-tree", - "version": "3.2.0", + "version": "3.3.0", "description": "React tree component", "main": "index.js", "scripts": { From 5d3a88823a0ff097b24307b98e76452d2f0f2cc0 Mon Sep 17 00:00:00 2001 From: Robert Long Date: Fri, 1 Jun 2018 16:37:26 -0700 Subject: [PATCH 06/22] Disable dragging on the root node. --- lib/node.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/node.js b/lib/node.js index a327187b..28e0e7ce 100644 --- a/lib/node.js +++ b/lib/node.js @@ -91,7 +91,7 @@ class UITreeNode extends Component { const nodeId = this.props.index.id; const dom = this.innerRef.current; - if (this.props.onDragStart) { + if (this.props.onDragStart && this.props.index !== 1) { this.props.onDragStart(nodeId, dom, e); } }; From effa11899953ea80dbe34898d644e92503c187fb Mon Sep 17 00:00:00 2001 From: Robert Long Date: Fri, 1 Jun 2018 17:03:58 -0700 Subject: [PATCH 07/22] Actually disable dragging root node. --- lib/node.js | 2 +- lib/react-ui-tree.js | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/node.js b/lib/node.js index 28e0e7ce..a327187b 100644 --- a/lib/node.js +++ b/lib/node.js @@ -91,7 +91,7 @@ class UITreeNode extends Component { const nodeId = this.props.index.id; const dom = this.innerRef.current; - if (this.props.onDragStart && this.props.index !== 1) { + if (this.props.onDragStart) { this.props.onDragStart(nodeId, dom, e); } }; diff --git a/lib/react-ui-tree.js b/lib/react-ui-tree.js index b60c9973..521a97e5 100644 --- a/lib/react-ui-tree.js +++ b/lib/react-ui-tree.js @@ -94,7 +94,8 @@ class UITree extends Component { } dragStart = (id, dom, e) => { - if (e.button !== 0) return; + if (e.button !== 0 || id === 1) return; + this.dragging = { id: id, w: dom.offsetWidth, @@ -198,7 +199,7 @@ class UITree extends Component { newIndex.node.collapsed = collapsed; dragging.id = newIndex.id; } - + this.setState({ tree: tree, dragging: dragging From 4d2c9607a7aeb74ea37e625d62e38cd060486a6a Mon Sep 17 00:00:00 2001 From: Robert Long Date: Fri, 1 Jun 2018 17:04:17 -0700 Subject: [PATCH 08/22] v3.3.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 98c8eaea..31b8ceca 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@robertlong/react-ui-tree", - "version": "3.3.0", + "version": "3.3.1", "description": "React tree component", "main": "index.js", "scripts": { From 75d6c6f0bb663f49b862d446bf557f8eec20480c Mon Sep 17 00:00:00 2001 From: Robert Long Date: Sun, 3 Jun 2018 21:43:09 -0700 Subject: [PATCH 09/22] Move build script into package.json for windows support. --- Makefile | 7 ------- package.json | 4 ++-- 2 files changed, 2 insertions(+), 9 deletions(-) delete mode 100644 Makefile diff --git a/Makefile b/Makefile deleted file mode 100644 index 098d8f6b..00000000 --- a/Makefile +++ /dev/null @@ -1,7 +0,0 @@ -all: - ./node_modules/.bin/babel lib --out-dir dist - ./node_modules/.bin/lessc lib/react-ui-tree.less > dist/react-ui-tree.css - ./node_modules/.bin/webpack -p -clean: - rm dist/* - rm example/bundle* diff --git a/package.json b/package.json index 31b8ceca..1adcd6ec 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,11 @@ { "name": "@robertlong/react-ui-tree", - "version": "3.3.1", + "version": "3.4.0", "description": "React tree component", "main": "index.js", "scripts": { "start": "webpack-dev-server --port=8080", - "build": "make", + "build": "babel lib --out-dir dist && lessc lib/react-ui-tree.less dist/react-ui-tree.css && webpack -p", "deploy": "rm -rf .publish && npm run build && github-pages-deploy", "pretest": "npm run build", "prepublish": "npm test", From 1c86734540955c4909ebc227097e9fc0419ad6fd Mon Sep 17 00:00:00 2001 From: Robert Long Date: Sun, 3 Jun 2018 21:44:43 -0700 Subject: [PATCH 10/22] v3.4.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1adcd6ec..48b5eb0e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@robertlong/react-ui-tree", - "version": "3.4.0", + "version": "3.4.1", "description": "React tree component", "main": "index.js", "scripts": { From a42f328f95b86f2186149095e8c7b9ca913d4c6e Mon Sep 17 00:00:00 2001 From: Robert Long Date: Mon, 4 Jun 2018 15:46:37 -0700 Subject: [PATCH 11/22] v3.4.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 48b5eb0e..416f2677 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@robertlong/react-ui-tree", - "version": "3.4.1", + "version": "3.4.2", "description": "React tree component", "main": "index.js", "scripts": { From 1125242d695fa092719204b9b220d24e9802543e Mon Sep 17 00:00:00 2001 From: Robert Long Date: Thu, 7 Jun 2018 15:26:24 -0700 Subject: [PATCH 12/22] Store drag start info in state and fix null index bug. --- lib/react-ui-tree.js | 47 ++++++++++++++++++++------------------------ 1 file changed, 21 insertions(+), 26 deletions(-) diff --git a/lib/react-ui-tree.js b/lib/react-ui-tree.js index 11f99673..cee1f9b0 100644 --- a/lib/react-ui-tree.js +++ b/lib/react-ui-tree.js @@ -98,33 +98,27 @@ class UITree extends Component { dragStart = (id, dom, e) => { if (e.button !== 0 || id === 1) return; - this.dragging = { - id: id, - w: dom.offsetWidth, - h: dom.offsetHeight, - x: dom.offsetLeft, - y: dom.offsetTop - }; - - this._startX = dom.offsetLeft; - this._startY = dom.offsetTop; - this._offsetX = e.clientX; - this._offsetY = e.clientY; - this._start = true; + this.setState({ + dragging: { + id: id, + w: dom.offsetWidth, + h: dom.offsetHeight, + x: dom.offsetLeft, + y: dom.offsetTop, + }, + start: { + x: dom.offsetLeft, + y: dom.offsetTop, + offsetX: e.clientX, + offsetY: e.clientY + } + }); window.addEventListener('mousemove', this.drag); window.addEventListener('mouseup', this.dragEnd); }; - // oh drag = e => { - if (this._start) { - this.setState({ - dragging: this.dragging - }); - this._start = false; - } - const tree = this.state.tree; const dragging = this.state.dragging; const paddingLeft = this.props.paddingLeft; @@ -132,10 +126,10 @@ class UITree extends Component { let index = tree.getIndex(dragging.id); const collapsed = index.node.collapsed; - const _startX = this._startX; - const _startY = this._startY; - const _offsetX = this._offsetX; - const _offsetY = this._offsetY; + const _startX = this.state.start.x; + const _startY = this.state.start.y; + const _offsetX = this.state.start.offsetX; + const _offsetY = this.state.start.offsetY; const pos = { x: _startX + e.clientX - _offsetX, @@ -219,7 +213,8 @@ class UITree extends Component { y: null, w: null, h: null - } + }, + start: null }); this.change(this.state.tree, parent, index.node); From be3e1eda9c226f9c0c404df1190a782581cfaab2 Mon Sep 17 00:00:00 2001 From: Robert Long Date: Thu, 7 Jun 2018 15:27:15 -0700 Subject: [PATCH 13/22] v3.4.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 416f2677..911ee46e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@robertlong/react-ui-tree", - "version": "3.4.2", + "version": "3.4.3", "description": "React tree component", "main": "index.js", "scripts": { From c92381737d7925c26a5b52f74d8e6ed9f23471e6 Mon Sep 17 00:00:00 2001 From: Robert Long Date: Thu, 7 Jun 2018 16:38:57 -0700 Subject: [PATCH 14/22] Handle clicking node in drag/dragEnd --- lib/react-ui-tree.js | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/lib/react-ui-tree.js b/lib/react-ui-tree.js index cee1f9b0..a90f3c45 100644 --- a/lib/react-ui-tree.js +++ b/lib/react-ui-tree.js @@ -124,6 +124,9 @@ class UITree extends Component { const paddingLeft = this.props.paddingLeft; let newIndex = null; let index = tree.getIndex(dragging.id); + + if (index === undefined) return; + const collapsed = index.node.collapsed; const _startX = this.state.start.x; @@ -203,9 +206,6 @@ class UITree extends Component { }; dragEnd = () => { - const index = this.state.tree.getIndex(this.state.dragging.id); - const parent = this.state.tree.get(index.parent); - this.setState({ dragging: { id: null, @@ -217,9 +217,16 @@ class UITree extends Component { start: null }); - this.change(this.state.tree, parent, index.node); window.removeEventListener('mousemove', this.drag); window.removeEventListener('mouseup', this.dragEnd); + + const index = this.state.tree.getIndex(this.state.dragging.id); + + if (index === undefined) return; + + const parent = this.state.tree.get(index.parent); + + this.change(this.state.tree, parent, index.node); }; change = (tree, parent, node) => { From 75ed6fd8cd388eabb7bbf1ba39fa40bb7a2d9868 Mon Sep 17 00:00:00 2001 From: Robert Long Date: Thu, 7 Jun 2018 16:39:21 -0700 Subject: [PATCH 15/22] v3.4.4 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 911ee46e..7e1b1694 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@robertlong/react-ui-tree", - "version": "3.4.3", + "version": "3.4.4", "description": "React tree component", "main": "index.js", "scripts": { From ce28215d2f38a395b16ab68eb0c26a774ab25234 Mon Sep 17 00:00:00 2001 From: Brian Peiris Date: Wed, 27 Jun 2018 13:57:09 -0700 Subject: [PATCH 16/22] Fix dragEnd --- lib/react-ui-tree.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/react-ui-tree.js b/lib/react-ui-tree.js index a90f3c45..91eb8291 100644 --- a/lib/react-ui-tree.js +++ b/lib/react-ui-tree.js @@ -206,6 +206,8 @@ class UITree extends Component { }; dragEnd = () => { + const draggingId = this.state.dragging.id; + this.setState({ dragging: { id: null, @@ -220,7 +222,7 @@ class UITree extends Component { window.removeEventListener('mousemove', this.drag); window.removeEventListener('mouseup', this.dragEnd); - const index = this.state.tree.getIndex(this.state.dragging.id); + const index = this.state.tree.getIndex(draggingId); if (index === undefined) return; From 0b42d7711ee422302f5d7dbe087646ac423f5ec0 Mon Sep 17 00:00:00 2001 From: Brian Peiris Date: Wed, 27 Jun 2018 14:02:13 -0700 Subject: [PATCH 17/22] v3.4.5 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7e1b1694..854cd874 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@robertlong/react-ui-tree", - "version": "3.4.4", + "version": "3.4.5", "description": "React tree component", "main": "index.js", "scripts": { From b579d37e0aa4341620480169fb5cefc8cb87f9c7 Mon Sep 17 00:00:00 2001 From: Brian Peiris Date: Wed, 27 Jun 2018 15:59:27 -0700 Subject: [PATCH 18/22] Ignore new props while dragging --- lib/react-ui-tree.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/react-ui-tree.js b/lib/react-ui-tree.js index 91eb8291..f63dbf1b 100644 --- a/lib/react-ui-tree.js +++ b/lib/react-ui-tree.js @@ -23,7 +23,7 @@ class UITree extends Component { } componentWillReceiveProps(nextProps) { - if (!this._updated) { + if (!this._updated && this.state.dragging.id === null) { this.setState(this.init(nextProps)); } else { this._updated = false; From 19bb15750394186daf70119d982bc80be49c51ad Mon Sep 17 00:00:00 2001 From: Robert Long Date: Wed, 27 Jun 2018 16:09:30 -0700 Subject: [PATCH 19/22] v3.4.6 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 854cd874..208d0632 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@robertlong/react-ui-tree", - "version": "3.4.5", + "version": "3.4.6", "description": "React tree component", "main": "index.js", "scripts": { From 1f313d0f56f2ce0246cc952a1620e830772746c1 Mon Sep 17 00:00:00 2001 From: Brian Peiris Date: Wed, 25 Jul 2018 12:59:37 -0700 Subject: [PATCH 20/22] Add support for scrolling --- example/theme.less | 4 +-- example/tree.js | 6 ++++ lib/react-ui-tree.js | 74 ++++++++++++++++++++++++++++++++++++++---- lib/react-ui-tree.less | 6 ++-- 4 files changed, 78 insertions(+), 12 deletions(-) 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 { From 76c8e3b59ebd7c428a2d187afbc082659dd5177b Mon Sep 17 00:00:00 2001 From: Brian Peiris Date: Wed, 25 Jul 2018 14:02:33 -0700 Subject: [PATCH 21/22] Include dist --- .gitignore | 2 - dist/node.js | 138 ++++++++++++++++ dist/react-ui-tree.css | 49 ++++++ dist/react-ui-tree.js | 365 +++++++++++++++++++++++++++++++++++++++++ dist/tree.js | 66 ++++++++ 5 files changed, 618 insertions(+), 2 deletions(-) create mode 100644 dist/node.js create mode 100644 dist/react-ui-tree.css create mode 100644 dist/react-ui-tree.js create mode 100644 dist/tree.js diff --git a/.gitignore b/.gitignore index be85b3bd..45b166e7 100644 --- a/.gitignore +++ b/.gitignore @@ -28,5 +28,3 @@ node_modules example/bundle* .publish - -dist diff --git a/dist/node.js b/dist/node.js new file mode 100644 index 00000000..b5daf285 --- /dev/null +++ b/dist/node.js @@ -0,0 +1,138 @@ +'use strict'; + +var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + +var _classnames = require('classnames'); + +var _classnames2 = _interopRequireDefault(_classnames); + +var _react = require('react'); + +var _react2 = _interopRequireDefault(_react); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + +function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + +var UITreeNode = function (_Component) { + _inherits(UITreeNode, _Component); + + function UITreeNode(props) { + _classCallCheck(this, UITreeNode); + + var _this = _possibleConstructorReturn(this, (UITreeNode.__proto__ || Object.getPrototypeOf(UITreeNode)).call(this, props)); + + _this.renderCollapse = function () { + var index = _this.props.index; + + + if (index.children && index.children.length) { + var collapsed = index.node.collapsed; + + + return _react2.default.createElement('span', { + className: (0, _classnames2.default)('collapse', collapsed ? 'caret-right' : 'caret-down'), + onMouseDown: function onMouseDown(e) { + return e.stopPropagation(); + }, + onClick: _this.handleCollapse + }); + } + + return null; + }; + + _this.renderChildren = function () { + var _this$props = _this.props, + index = _this$props.index, + tree = _this$props.tree, + dragging = _this$props.dragging; + + + if (index.children && index.children.length) { + var childrenStyles = { + paddingLeft: _this.props.paddingLeft + }; + + return _react2.default.createElement( + 'div', + { className: 'children', style: childrenStyles }, + index.children.map(function (child) { + var childIndex = tree.getIndex(child); + + return _react2.default.createElement(UITreeNode, { + tree: tree, + index: childIndex, + key: childIndex.id, + dragging: dragging, + paddingLeft: _this.props.paddingLeft, + onCollapse: _this.props.onCollapse, + onDragStart: _this.props.onDragStart + }); + }) + ); + } + + return null; + }; + + _this.handleCollapse = function (e) { + e.stopPropagation(); + var nodeId = _this.props.index.id; + + if (_this.props.onCollapse) { + _this.props.onCollapse(nodeId); + } + }; + + _this.handleMouseDown = function (e) { + var nodeId = _this.props.index.id; + var dom = _this.innerRef.current; + + if (_this.props.onDragStart) { + _this.props.onDragStart(nodeId, dom, e); + } + }; + + _this.innerRef = _react2.default.createRef(); + return _this; + } + + _createClass(UITreeNode, [{ + key: 'render', + value: function render() { + var _props = this.props, + tree = _props.tree, + index = _props.index, + dragging = _props.dragging; + var node = index.node; + + var styles = {}; + + return _react2.default.createElement( + 'div', + { + className: (0, _classnames2.default)('m-node', { + placeholder: index.id === dragging + }), + style: styles + }, + _react2.default.createElement( + 'div', + { className: 'inner', ref: this.innerRef, onMouseDown: this.handleMouseDown }, + this.renderCollapse(), + tree.renderNode(node) + ), + node.collapsed ? null : this.renderChildren() + ); + } + }]); + + return UITreeNode; +}(_react.Component); + +module.exports = UITreeNode; \ No newline at end of file diff --git a/dist/react-ui-tree.css b/dist/react-ui-tree.css new file mode 100644 index 00000000..0a60363e --- /dev/null +++ b/dist/react-ui-tree.css @@ -0,0 +1,49 @@ +.f-no-select { + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} +.m-tree { + position: relative; + overflow-x: hidden; + overflow-y: auto; + height: 100%; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} +.m-draggable { + position: absolute; + opacity: 0.8; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} +.m-node.placeholder > * { + visibility: hidden; +} +.m-node.placeholder { + outline: 1px dashed #ccc; +} +.m-node .inner { + position: relative; + cursor: pointer; + padding-left: 10px; +} +.m-node .collapse { + position: absolute; + left: 0; + cursor: pointer; +} +.m-node .caret-right:before { + content: '\25B8'; +} +.m-node .caret-down:before { + content: '\25BE'; +} diff --git a/dist/react-ui-tree.js b/dist/react-ui-tree.js new file mode 100644 index 00000000..59eeb82a --- /dev/null +++ b/dist/react-ui-tree.js @@ -0,0 +1,365 @@ +'use strict'; + +var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + +var _react = require('react'); + +var _react2 = _interopRequireDefault(_react); + +var _propTypes = require('prop-types'); + +var _propTypes2 = _interopRequireDefault(_propTypes); + +var _tree = require('./tree'); + +var _tree2 = _interopRequireDefault(_tree); + +var _node = require('./node'); + +var _node2 = _interopRequireDefault(_node); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + +function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + +var UITree = function (_Component) { + _inherits(UITree, _Component); + + function UITree(props) { + _classCallCheck(this, UITree); + + var _this = _possibleConstructorReturn(this, (UITree.__proto__ || Object.getPrototypeOf(UITree)).call(this, props)); + + _initialiseProps.call(_this); + + _this.state = _this.init(props); + _this.treeEl = _react2.default.createRef(); + + _this.startScrollHeight = null; + _this.lastMousePos = { clientX: null, clientY: null }; + _this.scrollEnabled = false; + _this.currentScrollSpeed = 0; + _this.lastScrollTimestamp = null; + return _this; + } + + _createClass(UITree, [{ + key: 'componentWillReceiveProps', + value: function componentWillReceiveProps(nextProps) { + if (!this._updated && this.state.dragging.id === null) { + this.setState(this.init(nextProps)); + } else { + this._updated = false; + } + } + }, { + key: 'render', + value: function render() { + var tree = this.state.tree; + var dragging = this.state.dragging; + var draggingDom = this.getDraggingDom(); + + return _react2.default.createElement( + 'div', + { className: 'm-tree', ref: this.treeEl }, + draggingDom, + _react2.default.createElement(_node2.default, { + tree: tree, + index: tree.getIndex(1), + key: 1, + paddingLeft: this.props.paddingLeft, + onDragStart: this.props.draggable && this.dragStart, + onCollapse: this.toggleCollapse, + dragging: dragging && dragging.id + }) + ); + } + }]); + + return UITree; +}(_react.Component); + +UITree.propTypes = { + tree: _propTypes2.default.object.isRequired, + paddingLeft: _propTypes2.default.number, + scrollMargin: _propTypes2.default.number, + scrollSpeed: _propTypes2.default.number, + renderNode: _propTypes2.default.func.isRequired, + draggable: _propTypes2.default.bool +}; +UITree.defaultProps = { + paddingLeft: 20, + scrollMargin: 20, + scrollSpeed: 200, + draggable: true +}; + +var _initialiseProps = function _initialiseProps() { + var _this2 = this; + + this.init = function (props) { + var tree = new _tree2.default(props.tree); + tree.isNodeCollapsed = props.isNodeCollapsed; + tree.renderNode = props.renderNode; + tree.changeNodeCollapsed = props.changeNodeCollapsed; + tree.updateNodesPosition(); + + return { + tree: tree, + dragging: { + id: null, + x: null, + y: null, + w: null, + h: null + } + }; + }; + + this.getDraggingDom = function () { + var _state = _this2.state, + tree = _state.tree, + dragging = _state.dragging; + + + if (dragging && dragging.id) { + var draggingIndex = tree.getIndex(dragging.id); + var draggingStyles = { + top: dragging.y, + left: dragging.x, + width: dragging.w + }; + + return _react2.default.createElement( + 'div', + { className: 'm-draggable', style: draggingStyles }, + _react2.default.createElement(_node2.default, { + tree: tree, + index: draggingIndex, + paddingLeft: _this2.props.paddingLeft + }) + ); + } + + return null; + }; + + this.dragStart = function (id, dom, e) { + if (e.button !== 0 || id === 1) return; + + var _treeEl$current = _this2.treeEl.current, + scrollHeight = _treeEl$current.scrollHeight, + scrollTop = _treeEl$current.scrollTop; + + + _this2.startScrollHeight = scrollHeight; + + _this2.setState({ + dragging: { + id: id, + w: dom.offsetWidth, + h: dom.offsetHeight, + ph: dom.parentNode.offsetHeight, + x: dom.offsetLeft, + y: dom.offsetTop + }, + start: { + x: dom.offsetLeft, + y: dom.offsetTop, + offsetX: e.clientX, + offsetY: e.clientY + scrollTop + } + }); + + window.addEventListener('mousemove', _this2.drag); + window.addEventListener('mouseup', _this2.dragEnd); + + _this2.lastMousePos.clientX = e.clientX; + _this2.lastMousePos.clientY = e.clientY; + _this2.scrollEnabled = true; + requestAnimationFrame(_this2.scroll); + }; + + this.scroll = function (timestamp) { + if (!_this2.scrollEnabled) return; + + if (_this2.lastScrollTimestamp === null || _this2.currentScrollSpeed === 0) { + _this2.lastScrollTimestamp = timestamp; + requestAnimationFrame(_this2.scroll); + return; + } + + var delta = timestamp - _this2.lastScrollTimestamp; + _this2.treeEl.current.scrollTop += _this2.currentScrollSpeed * delta / 1000; + _this2.drag(_this2.lastMousePos); + + _this2.lastScrollTimestamp = timestamp; + requestAnimationFrame(_this2.scroll); + }; + + this.drag = function (e) { + if (e) { + _this2.lastMousePos.clientX = e.clientX; + _this2.lastMousePos.clientY = e.clientY; + } else { + e = _this2.lastMousePos; + } + var _e = e, + clientX = _e.clientX, + clientY = _e.clientY; + + + var tree = _this2.state.tree; + var dragging = _this2.state.dragging; + var _props = _this2.props, + paddingLeft = _props.paddingLeft, + scrollMargin = _props.scrollMargin, + scrollSpeed = _props.scrollSpeed; + + var newIndex = null; + var index = tree.getIndex(dragging.id); + + if (index === undefined) return; + + var collapsed = index.node.collapsed; + + var _startX = _this2.state.start.x; + var _startY = _this2.state.start.y; + var _offsetX = _this2.state.start.offsetX; + var _offsetY = _this2.state.start.offsetY; + + var _treeEl$current2 = _this2.treeEl.current, + scrollTop = _treeEl$current2.scrollTop, + clientHeight = _treeEl$current2.clientHeight; + + + var pos = { + x: _startX + clientX - _offsetX, + y: Math.min(_this2.startScrollHeight - dragging.ph, _startY + clientY + scrollTop - _offsetY) + }; + dragging.x = pos.x; + dragging.y = pos.y; + + var diffX = dragging.x - paddingLeft / 2 - (index.left - 2) * paddingLeft; + var diffY = dragging.y - dragging.h / 2 - (index.top - 2) * dragging.h; + + if (diffX < 0) { + // left + if (index.parent && !index.next) { + newIndex = tree.move(index.id, index.parent, 'after'); + } + } else if (diffX > paddingLeft) { + // right + if (index.prev) { + var prevNode = tree.getIndex(index.prev).node; + if (!prevNode.collapsed && !prevNode.leaf) { + newIndex = tree.move(index.id, index.prev, 'append'); + } + } + } + + if (newIndex) { + index = newIndex; + newIndex.node.collapsed = collapsed; + dragging.id = newIndex.id; + } + + if (diffY < 0) { + // up + var above = tree.getNodeByTop(index.top - 1); + newIndex = tree.move(index.id, above.id, 'before'); + } else if (diffY > dragging.h) { + // down + if (index.next) { + var below = tree.getIndex(index.next); + if (below.children && below.children.length && !below.node.collapsed) { + newIndex = tree.move(index.id, index.next, 'prepend'); + } else { + newIndex = tree.move(index.id, index.next, 'after'); + } + } else { + var _below = tree.getNodeByTop(index.top + index.height); + if (_below && _below.parent !== index.id) { + if (_below.children && _below.children.length && !_below.node.collapsed) { + newIndex = tree.move(index.id, _below.id, 'prepend'); + } else { + newIndex = tree.move(index.id, _below.id, 'after'); + } + } + } + } + + if (newIndex) { + newIndex.node.collapsed = collapsed; + dragging.id = newIndex.id; + } + + if (dragging.y + dragging.ph > scrollTop + clientHeight - scrollMargin) { + _this2.currentScrollSpeed = scrollSpeed; + } else if (dragging.y < scrollTop + scrollMargin) { + _this2.currentScrollSpeed = -scrollSpeed; + } else { + _this2.currentScrollSpeed = 0; + } + + _this2.setState({ + tree: tree, + dragging: dragging + }); + }; + + this.dragEnd = function () { + var draggingId = _this2.state.dragging.id; + + _this2.setState({ + dragging: { + id: null, + x: null, + y: null, + w: null, + h: null + }, + start: null + }); + + window.removeEventListener('mousemove', _this2.drag); + window.removeEventListener('mouseup', _this2.dragEnd); + + _this2.lastMousePos.clientX = null; + _this2.lastMousePos.clientY = null; + _this2.scrollEnabled = false; + + var index = _this2.state.tree.getIndex(draggingId); + + if (index === undefined) return; + + var parent = _this2.state.tree.get(index.parent); + + _this2.change(_this2.state.tree, parent, index.node); + }; + + this.change = function (tree, parent, node) { + _this2._updated = true; + if (_this2.props.onChange) _this2.props.onChange(tree.obj, parent, node); + }; + + this.toggleCollapse = function (nodeId) { + var tree = _this2.state.tree; + var index = tree.getIndex(nodeId); + var node = index.node; + node.collapsed = !node.collapsed; + tree.updateNodesPosition(); + + _this2.setState({ + tree: tree + }); + + _this2.change(tree, null, null); + }; +}; + +module.exports = UITree; \ No newline at end of file diff --git a/dist/tree.js b/dist/tree.js new file mode 100644 index 00000000..00141042 --- /dev/null +++ b/dist/tree.js @@ -0,0 +1,66 @@ +'use strict'; + +var Tree = require('js-tree'); +var proto = Tree.prototype; + +proto.updateNodesPosition = function () { + var top = 1; + var left = 1; + var root = this.getIndex(1); + var self = this; + + root.top = top++; + root.left = left++; + + if (root.children && root.children.length) { + walk(root.children, root, left, root.node.collapsed); + } + + function walk(children, parent, left, collapsed) { + var height = 1; + children.forEach(function (id) { + var node = self.getIndex(id); + if (collapsed) { + node.top = null; + node.left = null; + } else { + node.top = top++; + node.left = left; + } + + if (node.children && node.children.length) { + height += walk(node.children, node, left + 1, collapsed || node.node.collapsed); + } else { + node.height = 1; + height += 1; + } + }); + + if (parent.node.collapsed) parent.height = 1;else parent.height = height; + return parent.height; + } +}; + +proto.move = function (fromId, toId, placement) { + if (fromId === toId || toId === 1) return; + + var obj = this.remove(fromId); + var index = null; + + if (placement === 'before') index = this.insertBefore(obj, toId);else if (placement === 'after') index = this.insertAfter(obj, toId);else if (placement === 'prepend') index = this.prepend(obj, toId);else if (placement === 'append') index = this.append(obj, toId); + + // todo: perf + this.updateNodesPosition(); + return index; +}; + +proto.getNodeByTop = function (top) { + var indexes = this.indexes; + for (var id in indexes) { + if (indexes.hasOwnProperty(id)) { + if (indexes[id].top === top) return indexes[id]; + } + } +}; + +module.exports = Tree; \ No newline at end of file From 0076263850080ee5cd502eb2f5f9ce91ef3b25b9 Mon Sep 17 00:00:00 2001 From: Brian Peiris Date: Wed, 25 Jul 2018 16:14:10 -0700 Subject: [PATCH 22/22] Only drag after threshold is crossed --- lib/node.js | 34 ++++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/lib/node.js b/lib/node.js index a327187b..19c3c671 100644 --- a/lib/node.js +++ b/lib/node.js @@ -1,7 +1,16 @@ import cx from 'classnames'; import React, { Component } from 'react'; +import PropTypes from 'prop-types'; class UITreeNode extends Component { + static propTypes = { + dragThreshold: PropTypes.number + }; + + static defaultProps = { + dragThreshold: 8 + }; + constructor(props) { super(props); this.innerRef = React.createRef(); @@ -69,7 +78,11 @@ class UITreeNode extends Component { })} style={styles} > -
+
{this.renderCollapse()} {tree.renderNode(node)}
@@ -88,9 +101,26 @@ class UITreeNode extends Component { }; handleMouseDown = e => { + e.stopPropagation(); + this.startPos = { x: e.clientX, y: e.clientY }; + window.addEventListener("mousemove", this.handleMouseMove); + window.addEventListener("mouseup", () => { + window.removeEventListener("mousemove", this.handleMouseMove); + }); + }; + + handleMouseMove = e => { + if (!this.startPos) return; + + const { dragThreshold } = this.props; + const deltaX = Math.abs(e.clientX - this.startPos.x); + const deltaY = Math.abs(e.clientY - this.startPos.y); + if (deltaX < dragThreshold && deltaY < dragThreshold) return; + + this.startPos = null; + const nodeId = this.props.index.id; const dom = this.innerRef.current; - if (this.props.onDragStart) { this.props.onDragStart(nodeId, dom, e); }