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/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/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 diff --git a/example/app.js b/example/app.js index 121b8e6d..19bac4fc 100644 --- a/example/app.js +++ b/example/app.js @@ -39,6 +39,7 @@ class App extends Component {
-
+
{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); } diff --git a/lib/react-ui-tree.js b/lib/react-ui-tree.js index fd5f1a80..41956c6e 100644 --- a/lib/react-ui-tree.js +++ b/lib/react-ui-tree.js @@ -7,21 +7,34 @@ class UITree extends Component { static propTypes = { tree: PropTypes.object.isRequired, paddingLeft: PropTypes.number, - renderNode: PropTypes.func.isRequired + scrollMargin: PropTypes.number, + scrollSpeed: PropTypes.number, + renderNode: PropTypes.func.isRequired, + draggable: PropTypes.bool }; static defaultProps = { - paddingLeft: 20 + paddingLeft: 20, + scrollMargin: 20, + scrollSpeed: 200, + draggable: true }; constructor(props) { 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) { - if (!this._updated) { + if (!this._updated && this.state.dragging.id === null) { this.setState(this.init(nextProps)); } else { this._updated = false; @@ -78,14 +91,14 @@ class UITree extends Component { const draggingDom = this.getDraggingDom(); return ( -
+
{draggingDom} @@ -94,49 +107,84 @@ class UITree extends Component { } dragStart = (id, dom, e) => { - if (e.button !== 0) return; - this.dragging = { - id: id, - w: dom.offsetWidth, - h: dom.offsetHeight, - x: dom.offsetLeft, - y: dom.offsetTop - }; + if (e.button !== 0 || id === 1) return; + + const { scrollHeight, scrollTop } = this.treeEl.current; + + this.startScrollHeight = scrollHeight; - 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, + 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', this.drag); window.addEventListener('mouseup', this.dragEnd); + + this.lastMousePos.clientX = e.clientX; + this.lastMousePos.clientY = e.clientY; + this.scrollEnabled = true; + requestAnimationFrame(this.scroll); }; - // oh - drag = e => { - if (this._start) { - this.setState({ - dragging: this.dragging - }); - this._start = false; + 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) => { + 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); + + if (index === undefined) return; + 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 { 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; @@ -199,6 +247,14 @@ class UITree extends Component { 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 @@ -206,6 +262,8 @@ class UITree extends Component { }; dragEnd = () => { + const draggingId = this.state.dragging.id; + this.setState({ dragging: { id: null, @@ -213,17 +271,29 @@ class UITree extends Component { y: null, w: null, h: null - } + }, + start: null }); - this.change(this.state.tree); 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; + + const parent = this.state.tree.get(index.parent); + + this.change(this.state.tree, parent, index.node); }; - 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 +307,7 @@ class UITree extends Component { tree: tree }); - this.change(tree); + this.change(tree, null, null); }; } 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 { diff --git a/package.json b/package.json index c2823db2..208d0632 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,11 @@ { - "name": "react-ui-tree", - "version": "3.1.0", + "name": "@robertlong/react-ui-tree", + "version": "3.4.6", "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",