commit d53c451d4d77c6fc5c3547875c8f61a3cacc2c5b Author: ahua52 <1468492018@qq.com> Date: Fri Dec 30 15:43:08 2016 +0800 bee-tree diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..25052a1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,38 @@ +# Logs +logs +*.log +npm-debug.log* + +# Runtime data +pids +*.pid +*.seed + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# node-waf configuration +.lock-wscript + + +# Dependency directories +node_modules +jspm_packages + +# Optional npm cache directory +.npm + +# Optional REPL history +.node_repl_history + +dist +coverage diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..b6fadc1 --- /dev/null +++ b/.npmignore @@ -0,0 +1,28 @@ +bower_components/ +*.cfg +node_modules/ +nohup.out +*.iml +.idea/ +.ipr +.iws +*~ +~* +*.diff +*.log +*.patch +*.bak +.DS_Store +Thumbs.db +.project +.*proj +.svn/ +*.swp +out/ +.build +node_modules +_site +sea-modules +spm_modules +.cache +coverage diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..e5a6d34 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,22 @@ +sudo: false + +language: node_js + +node_js: + - "6" + +service_name: travis-ci +repo_token: add + +env: + global: + - NODE_ENV=travisci + - NPM_CONFIG_PROGRESS="false" + +before_install: + - npm install -g bee-tools + +script: npm test + +after_script: + - npm run coveralls diff --git a/HISTORY.md b/HISTORY.md new file mode 100644 index 0000000..e69de29 diff --git a/README.md b/README.md new file mode 100644 index 0000000..5829a6d --- /dev/null +++ b/README.md @@ -0,0 +1,43 @@ +# bee-tree + +[![npm version](https://img.shields.io/npm/v/bee-tree.svg)](https://www.npmjs.com/package/bee-tree) +[![Build Status](https://img.shields.io/travis/tinper-bee/bee-tree/master.svg)](https://travis-ci.org/tinper-bee/bee-tree) +[![Coverage Status](https://coveralls.io/repos/github/tinper-bee/bee-tree/badge.svg?branch=master)](https://coveralls.io/github/tinper-bee/bee-tree?branch=master) +[![devDependency Status](https://img.shields.io/david/dev/tinper-bee/bee-tree.svg)](https://david-dm.org/tinper-bee/bee-tree#info=devDependencies) +[![NPM downloads](http://img.shields.io/npm/dm/bee-tree.svg?style=flat)](https://npmjs.org/package/bee-tree) +[![Average time to resolve an issue](http://isitmaintained.com/badge/resolution/tinper-bee/bee-tree.svg)](http://isitmaintained.com/project/tinper-bee/bee-tree "Average time to resolve an issue") +[![Percentage of issues still open](http://isitmaintained.com/badge/open/tinper-bee/bee-tree.svg)](http://isitmaintained.com/project/tinper-bee/bee-tree "Percentage of issues still open") + +## Browser Support + +|![IE](https://raw.github.com/alrra/browser-logos/master/internet-explorer/internet-explorer_48x48.png) | ![Chrome](https://raw.github.com/alrra/browser-logos/master/chrome/chrome_48x48.png) | ![Firefox](https://raw.github.com/alrra/browser-logos/master/firefox/firefox_48x48.png) | ![Opera](https://raw.github.com/alrra/browser-logos/master/opera/opera_48x48.png) | ![Safari](https://raw.github.com/alrra/browser-logos/master/safari/safari_48x48.png)| +| --- | --- | --- | --- | --- | +| IE 9+ ✔ | Chrome 31.0+ ✔ | Firefox 31.0+ ✔ | Opera 30.0+ ✔ | Safari 7.0+ ✔ | + + +react bee-tree component for tinper-bee + +some description... + +## 使用方法 + +```js + +``` + + + +## API + +|参数|说明|类型|默认值| +|:--|:---:|:--:|---:| + +#### 开发调试 + +```sh +$ npm install -g bee-tools +$ git clone https://github.com/tinper-bee/bee-tree +$ cd bee-tree +$ npm install +$ npm run dev +``` diff --git a/README_EN.md b/README_EN.md new file mode 100644 index 0000000..08ea79e --- /dev/null +++ b/README_EN.md @@ -0,0 +1,43 @@ +# bee-dropdown + +[![npm version](https://img.shields.io/npm/v/bee-dropdown.svg)](https://www.npmjs.com/package/bee-dropdown) +[![Build Status](https://img.shields.io/travis/tinper-bee/bee-dropdown/master.svg)](https://travis-ci.org/tinper-bee/bee-dropdown) +[![Coverage Status](https://coveralls.io/repos/github/tinper-bee/bee-dropdown/badge.svg?branch=master)](https://coveralls.io/github/tinper-bee/bee-dropdown?branch=master) +[![devDependency Status](https://img.shields.io/david/dev/tinper-bee/bee-dropdown.svg)](https://david-dm.org/tinper-bee/bee-dropdown#info=devDependencies) +[![NPM downloads](http://img.shields.io/npm/dm/<%= packageName%>.svg?style=flat)](https://npmjs.org/package/<%= packageName%>) +[![Average time to resolve an issue](http://isitmaintained.com/badge/resolution/tinper-bee/<%= packageName%>.svg)](http://isitmaintained.com/project/tinper-bee/<%= packageName%> "Average time to resolve an issue") +[![Percentage of issues still open](http://isitmaintained.com/badge/open/tinper-bee/<%= packageName%>.svg)](http://isitmaintained.com/project/tinper-bee/<%= packageName%> "Percentage of issues still open") + +## Browser Support + +|![IE](https://raw.github.com/alrra/browser-logos/master/internet-explorer/internet-explorer_48x48.png) | ![Chrome](https://raw.github.com/alrra/browser-logos/master/chrome/chrome_48x48.png) | ![Firefox](https://raw.github.com/alrra/browser-logos/master/firefox/firefox_48x48.png) | ![Opera](https://raw.github.com/alrra/browser-logos/master/opera/opera_48x48.png) | ![Safari](https://raw.github.com/alrra/browser-logos/master/safari/safari_48x48.png)| +| --- | --- | --- | --- | --- | +| IE 9+ ✔ | Chrome 31.0+ ✔ | Firefox 31.0+ ✔ | Opera 30.0+ ✔ | Safari 7.0+ ✔ | + + +react bee-dropdown component for tinper-bee + + +## Usage + +```js + + +``` + + + +## API + +|参数|说明|类型|默认值| +|:--|:---:|:--:|---:| + +#### develop + +```sh +$ npm install -g bee-tools +$ git clone https://github.com/tinper-bee/bee-dropdown +$ cd bee-dropdown +$ npm install +$ npm run dev +``` diff --git a/demo/TreeDemo.js b/demo/TreeDemo.js new file mode 100644 index 0000000..3d65aef --- /dev/null +++ b/demo/TreeDemo.js @@ -0,0 +1,5 @@ +import Tree from '../src/index'; +import React, { Component } from 'react'; +import ReactDOM from 'react-dom'; +class Demo extends Component {render(){return( )}} +export default Demo; \ No newline at end of file diff --git a/demo/TreeDemo.scss b/demo/TreeDemo.scss new file mode 100644 index 0000000..86e7870 --- /dev/null +++ b/demo/TreeDemo.scss @@ -0,0 +1,11 @@ +@import "../node_modules/tinper-bee-core/scss/index.scss"; +@import "../src/Tree.scss"; +@import "../node_modules/bee-panel/src/Panel.scss"; +@import "../node_modules/bee-layout/src/Layout.scss"; +@import "../node_modules/bee-button/src/Button.scss"; +@import "../node_modules/bee-transition/src/Transition.scss"; +@import "../node_modules/bee-form-control/src/FormControl.scss"; +.ant-tree-searchable-filter { + color: #f50; + transition: all .3s ease; +} \ No newline at end of file diff --git a/demo/atom-one-dark.css b/demo/atom-one-dark.css new file mode 100644 index 0000000..1616aaf --- /dev/null +++ b/demo/atom-one-dark.css @@ -0,0 +1,96 @@ +/* + +Atom One Dark by Daniel Gamage +Original One Dark Syntax theme from https://github.com/atom/one-dark-syntax + +base: #282c34 +mono-1: #abb2bf +mono-2: #818896 +mono-3: #5c6370 +hue-1: #56b6c2 +hue-2: #61aeee +hue-3: #c678dd +hue-4: #98c379 +hue-5: #e06c75 +hue-5-2: #be5046 +hue-6: #d19a66 +hue-6-2: #e6c07b + +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + color: #abb2bf; + background: #282c34; +} + +.hljs-comment, +.hljs-quote { + color: #5c6370; + font-style: italic; +} + +.hljs-doctag, +.hljs-keyword, +.hljs-formula { + color: #c678dd; +} + +.hljs-section, +.hljs-name, +.hljs-selector-tag, +.hljs-deletion, +.hljs-subst { + color: #e06c75; +} + +.hljs-literal { + color: #56b6c2; +} + +.hljs-string, +.hljs-regexp, +.hljs-addition, +.hljs-attribute, +.hljs-meta-string { + color: #98c379; +} + +.hljs-built_in, +.hljs-class .hljs-title { + color: #e6c07b; +} + +.hljs-attr, +.hljs-variable, +.hljs-template-variable, +.hljs-type, +.hljs-selector-class, +.hljs-selector-attr, +.hljs-selector-pseudo, +.hljs-number { + color: #d19a66; +} + +.hljs-symbol, +.hljs-bullet, +.hljs-link, +.hljs-meta, +.hljs-selector-id, +.hljs-title { + color: #61aeee; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} + +.hljs-link { + text-decoration: underline; +} diff --git a/demo/demolist/Demo1.js b/demo/demolist/Demo1.js new file mode 100644 index 0000000..7eb44b9 --- /dev/null +++ b/demo/demolist/Demo1.js @@ -0,0 +1,48 @@ +/** +* +* @title Tree基本使用事例 +* @description 事例涵盖 checkbox如何选择,disable状态和部分选择状态。 +* +*/ +const defaultProps = { + keys: ['0-0-0', '0-0-1'] +} +class Demo1 extends Component { + constructor(props) { + super(props); + const keys = this.props.keys; + this.state = { + defaultExpandedKeys: keys, + defaultSelectedKeys: keys, + defaultCheckedKeys: keys, + }; + } + onSelect(info) { + console.log('selected', info); + } + onCheck(info) { + console.log('onCheck', info); + } + render() { + return ( + + + + + + + + sss} key="0-0-1-0" /> + + + + ); + } +} + +Demo1.defaultProps = defaultProps; \ No newline at end of file diff --git a/demo/demolist/Demo2.js b/demo/demolist/Demo2.js new file mode 100644 index 0000000..77b8f3d --- /dev/null +++ b/demo/demolist/Demo2.js @@ -0,0 +1,91 @@ +/** +* +* @title Tree数据可控事例 +* @description +* +*/ +/* +const x = 3; +const y = 2; +const z = 1; +const gData = []; + +const generateData = (_level, _preKey, _tns) => { + const preKey = _preKey || '0'; + const tns = _tns || gData; + + const children = []; + for (let i = 0; i < x; i++) { + const key = `${preKey}-${i}`; + tns.push({ title: key, key }); + if (i < y) { + children.push(key); + } + } + if (_level < 0) { + return tns; + } + const level = _level - 1; + children.forEach((key, index) => { + tns[index].children = []; + return generateData(level, key, tns[index].children); + }); +}; +generateData(z); +*/ +class Demo2 extends Component{ + constructor(props) { + super(props); + this.state = { + expandedKeys: ['0-0-0', '0-0-1'], + autoExpandParent: true, + checkedKeys: ['0-0-0'], + selectedKeys: [], + }; + this.onExpand = this.onExpand.bind(this); + this.onCheck = this.onCheck.bind(this); + this.onSelect = this.onSelect.bind(this); + } + onExpand(expandedKeys) { + console.log('onExpand', arguments); + // if not set autoExpandParent to false, if children expanded, parent can not collapse. + // or, you can remove all expanded children keys. + this.setState({ + expandedKeys, + autoExpandParent: false, + }); + } + onCheck(checkedKeys) { + this.setState({ + checkedKeys, + selectedKeys: ['0-3', '0-4'], + }); + } + onSelect(selectedKeys, info) { + console.log('onSelect', info); + this.setState({ selectedKeys }); + } + render() { + const loop = data => data.map((item) => { + if (item.children) { + return ( + + {loop(item.children)} + + ); + } + return ; + }); + return ( + + {loop(gData)} + + ); + } +}; \ No newline at end of file diff --git a/demo/demolist/Demo3.js b/demo/demolist/Demo3.js new file mode 100644 index 0000000..cb5d6bf --- /dev/null +++ b/demo/demolist/Demo3.js @@ -0,0 +1,83 @@ +/** +* +* @title Tree 拖拽使用事例 +* @description 拖动结点插入到另一个结点后面或者其他的父节点里面。 +* +*/ + +class Demo3 extends Component{ + constructor(props) { + super(props); + this.state = { + gData, + expandedKeys: ['0-0', '0-0-0', '0-0-0-0'], + }; + this.onDragEnter = this.onDragEnter.bind(this); + this.onDrop = this.onDrop.bind(this); + } + onDragEnter(info) { + console.log(info); + // expandedKeys 需要受控时设置 + // this.setState({ + // expandedKeys: info.expandedKeys, + // }); + } + onDrop(info) { + console.log(info); + const dropKey = info.node.props.eventKey; + const dragKey = info.dragNode.props.eventKey; + // const dragNodesKeys = info.dragNodesKeys; + const loop = (data, key, callback) => { + data.forEach((item, index, arr) => { + if (item.key === key) { + return callback(item, index, arr); + } + if (item.children) { + return loop(item.children, key, callback); + } + }); + }; + const data = [...this.state.gData]; + let dragObj; + loop(data, dragKey, (item, index, arr) => { + arr.splice(index, 1); + dragObj = item; + }); + if (info.dropToGap) { + let ar; + let i; + loop(data, dropKey, (item, index, arr) => { + ar = arr; + i = index; + }); + ar.splice(i, 0, dragObj); + } else { + loop(data, dropKey, (item) => { + item.children = item.children || []; + // where to insert 示例添加到尾部,可以是随意位置 + item.children.push(dragObj); + }); + } + this.setState({ + gData: data, + }); + } + render() { + const loop = data => data.map((item) => { + if (item.children && item.children.length) { + return {loop(item.children)}; + } + return ; + }); + return ( + + {loop(this.state.gData)} + + ); + } +}; \ No newline at end of file diff --git a/demo/demolist/Demo4.js b/demo/demolist/Demo4.js new file mode 100644 index 0000000..7511e5d --- /dev/null +++ b/demo/demolist/Demo4.js @@ -0,0 +1,110 @@ +/** +* +* @title Tree可搜索事例 +* @description +* +*/ +const dataList = []; +const generateList = (data) => { + for (let i = 0; i < data.length; i++) { + const node = data[i]; + const key = node.key; + dataList.push({ key, title: key }); + if (node.children) { + generateList(node.children, node.key); + } + } +}; +generateList(gData); + +const getParentKey = (key, tree) => { + let parentKey; + for (let i = 0; i < tree.length; i++) { + const node = tree[i]; + if (node.children) { + if (node.children.some(item => item.key === key)) { + parentKey = node.key; + } else if (getParentKey(key, node.children)) { + parentKey = getParentKey(key, node.children); + } + } + } + return parentKey; +}; + + +class Demo4 extends Component { + constructor(props) { + super(props); + this.state = { + expandedKeys: [], + searchValue: '', + autoExpandParent: true, + } + } + onExpand = (expandedKeys) => { + this.setState({ + expandedKeys, + autoExpandParent: false, + }); + } + onChange = (e) => { + const value = e.target.value; + const expandedKeys = []; + dataList.forEach((item) => { + if (item.key.indexOf(value) > -1) { + expandedKeys.push(getParentKey(item.key, gData)); + } + }); + const uniqueExpandedKeys = []; + expandedKeys.forEach((item) => { + if (item && uniqueExpandedKeys.indexOf(item) === -1) { + uniqueExpandedKeys.push(item); + } + }); + this.setState({ + expandedKeys: uniqueExpandedKeys, + searchValue: value, + autoExpandParent: true, + }); + } + render() { + const { searchValue, expandedKeys, autoExpandParent } = this.state; + const loop = data => data.map((item) => { + const index = item.key.search(searchValue); + const beforeStr = item.key.substr(0, index); + const afterStr = item.key.substr(index + searchValue.length); + const title = index > -1 ? ( + + {beforeStr} + {searchValue} + {afterStr} + + ) : {item.key}; + if (item.children) { + return ( + + {loop(item.children)} + + ); + } + return ; + }); + return ( +
+ + + {loop(gData)} + +
+ ); + } +} \ No newline at end of file diff --git a/demo/demolist/Demo5.js b/demo/demolist/Demo5.js new file mode 100644 index 0000000..99c5d61 --- /dev/null +++ b/demo/demolist/Demo5.js @@ -0,0 +1,98 @@ +/** +* +* @title Tree异步数据加载 +* @description 当点击展开,异步获取子节点数据 +* +*/ +function generateTreeNodes(treeNode) { + const arr = []; + const key = treeNode.props.eventKey; + for (let i = 0; i < 3; i++) { + arr.push({ name: `leaf ${key}-${i}`, key: `${key}-${i}` }); + } + return arr; +} + +function setLeaf(treeData, curKey, level) { + const loopLeaf = (data, lev) => { + const l = lev - 1; + data.forEach((item) => { + if ((item.key.length > curKey.length) ? item.key.indexOf(curKey) !== 0 : + curKey.indexOf(item.key) !== 0) { + return; + } + if (item.children) { + loopLeaf(item.children, l); + } else if (l < 1) { + item.isLeaf = true; + } + }); + }; + loopLeaf(treeData, level + 1); +} + +function getNewTreeData(treeData, curKey, child, level) { + const loop = (data) => { + if (level < 1 || curKey.length - 3 > level * 2) return; + data.forEach((item) => { + if (curKey.indexOf(item.key) === 0) { + if (item.children) { + loop(item.children); + } else { + item.children = child; + } + } + }); + }; + loop(treeData); + setLeaf(treeData, curKey, level); +} + +class Demo5 extends Component{ + constructor(props) { + super(props); + this.state = { + treeData: [], + }; + this.onSelect = this.onSelect.bind(this); + this.onLoadData = this.onLoadData.bind(this); + } + componentDidMount() { + setTimeout(() => { + this.setState({ + treeData: [ + { name: 'pNode 01', key: '0-0' }, + { name: 'pNode 02', key: '0-1' }, + { name: 'pNode 03', key: '0-2', isLeaf: true }, + ], + }); + }, 100); + } + onSelect(info) { + console.log('selected', info); + } + onLoadData(treeNode) { + return new Promise((resolve) => { + setTimeout(() => { + const treeData = [...this.state.treeData]; + getNewTreeData(treeData, treeNode.props.eventKey, generateTreeNodes(treeNode), 2); + this.setState({ treeData }); + resolve(); + }, 1000); + }); + } + render() { + const loop = data => data.map((item) => { + if (item.children) { + return {loop(item.children)}; + } + return ; + }); + const treeNodes = loop(this.state.treeData); + return ( + + {treeNodes} + + ); + } +}; \ No newline at end of file diff --git a/demo/index-demo-base.js b/demo/index-demo-base.js new file mode 100644 index 0000000..03b9817 --- /dev/null +++ b/demo/index-demo-base.js @@ -0,0 +1,113 @@ + +import { Con, Row, Col } from 'bee-layout'; +import { Panel } from 'bee-panel'; +import Button from 'bee-button'; +import React, { Component } from 'react'; +import ReactDOM from 'react-dom'; +import FormControl from 'bee-form-control'; +import Tree from '../src'; + +const x = 3; +const y = 2; +const z = 1; +const gData = []; + +const generateData = (_level, _preKey, _tns) => { + const preKey = _preKey || '0'; + const tns = _tns || gData; + + const children = []; + for (let i = 0; i < x; i++) { + const key = `${preKey}-${i}`; + tns.push({ title: key, key }); + if (i < y) { + children.push(key); + } + } + if (_level < 0) { + return tns; + } + const level = _level - 1; + children.forEach((key, index) => { + tns[index].children = []; + return generateData(level, key, tns[index].children); + }); +}; +generateData(z); + +const TreeNode = Tree.TreeNode; + +const CARET = ; + +const CARETUP = ; + + +{demolist} + +class Demo extends Component { + constructor(props){ + super(props); + this.state = { + open: false + } + this.handleClick = this.handleClick.bind(this); + } + handleClick() { + this.setState({ open: !this.state.open }) + } + + render () { + const { title, example, code, desc } = this.props; + let caret = this.state.open ? CARETUP : CARET; + let text = this.state.open ? "隐藏代码" : "查看代码"; + + const footer = ( + + ); + const header = ( + + + { example } + + + + + + ); + return ( + +

{ title }

+

{ desc }

+ +
{ code }
+
+ + ) + } +} + +class DemoGroup extends Component { + constructor(props){ + super(props) + } + render () { + return ( + + {DemoArray.map((child,index) => { + + return ( + + ) + + })} + + ) + } +} + +ReactDOM.render(, document.getElementById('tinperBeeDemo')); diff --git a/demo/index.js b/demo/index.js new file mode 100644 index 0000000..939d688 --- /dev/null +++ b/demo/index.js @@ -0,0 +1,539 @@ + +import { Con, Row, Col } from 'bee-layout'; +import { Panel } from 'bee-panel'; +import Button from 'bee-button'; +import React, { Component } from 'react'; +import ReactDOM from 'react-dom'; +import FormControl from 'bee-form-control'; +import Tree from '../src'; + +const x = 3; +const y = 2; +const z = 1; +const gData = []; + +const generateData = (_level, _preKey, _tns) => { + const preKey = _preKey || '0'; + const tns = _tns || gData; + + const children = []; + for (let i = 0; i < x; i++) { + const key = `${preKey}-${i}`; + tns.push({ title: key, key }); + if (i < y) { + children.push(key); + } + } + if (_level < 0) { + return tns; + } + const level = _level - 1; + children.forEach((key, index) => { + tns[index].children = []; + return generateData(level, key, tns[index].children); + }); +}; +generateData(z); + +const TreeNode = Tree.TreeNode; + +const CARET = ; + +const CARETUP = ; + + +/** +* +* @title Tree基本使用事例 +* @description 事例涵盖 checkbox如何选择,disable状态和部分选择状态。 +* +*/ +const defaultProps = { + keys: ['0-0-0', '0-0-1'] +} +class Demo1 extends Component { + constructor(props) { + super(props); + const keys = this.props.keys; + this.state = { + defaultExpandedKeys: keys, + defaultSelectedKeys: keys, + defaultCheckedKeys: keys, + }; + } + onSelect(info) { + console.log('selected', info); + } + onCheck(info) { + console.log('onCheck', info); + } + render() { + return ( + + + + + + + + sss} key="0-0-1-0" /> + + + + ); + } +} + +Demo1.defaultProps = defaultProps;/** +* +* @title Tree数据可控事例 +* @description +* +*/ +/* +const x = 3; +const y = 2; +const z = 1; +const gData = []; + +const generateData = (_level, _preKey, _tns) => { + const preKey = _preKey || '0'; + const tns = _tns || gData; + + const children = []; + for (let i = 0; i < x; i++) { + const key = `${preKey}-${i}`; + tns.push({ title: key, key }); + if (i < y) { + children.push(key); + } + } + if (_level < 0) { + return tns; + } + const level = _level - 1; + children.forEach((key, index) => { + tns[index].children = []; + return generateData(level, key, tns[index].children); + }); +}; +generateData(z); +*/ +class Demo2 extends Component{ + constructor(props) { + super(props); + this.state = { + expandedKeys: ['0-0-0', '0-0-1'], + autoExpandParent: true, + checkedKeys: ['0-0-0'], + selectedKeys: [], + }; + this.onExpand = this.onExpand.bind(this); + this.onCheck = this.onCheck.bind(this); + this.onSelect = this.onSelect.bind(this); + } + onExpand(expandedKeys) { + console.log('onExpand', arguments); + // if not set autoExpandParent to false, if children expanded, parent can not collapse. + // or, you can remove all expanded children keys. + this.setState({ + expandedKeys, + autoExpandParent: false, + }); + } + onCheck(checkedKeys) { + this.setState({ + checkedKeys, + selectedKeys: ['0-3', '0-4'], + }); + } + onSelect(selectedKeys, info) { + console.log('onSelect', info); + this.setState({ selectedKeys }); + } + render() { + const loop = data => data.map((item) => { + if (item.children) { + return ( + + {loop(item.children)} + + ); + } + return ; + }); + return ( + + {loop(gData)} + + ); + } +};/** +* +* @title Tree 拖拽使用事例 +* @description 拖动结点插入到另一个结点后面或者其他的父节点里面。 +* +*/ + +class Demo3 extends Component{ + constructor(props) { + super(props); + this.state = { + gData, + expandedKeys: ['0-0', '0-0-0', '0-0-0-0'], + }; + this.onDragEnter = this.onDragEnter.bind(this); + this.onDrop = this.onDrop.bind(this); + } + onDragEnter(info) { + console.log(info); + // expandedKeys 需要受控时设置 + // this.setState({ + // expandedKeys: info.expandedKeys, + // }); + } + onDrop(info) { + console.log(info); + const dropKey = info.node.props.eventKey; + const dragKey = info.dragNode.props.eventKey; + // const dragNodesKeys = info.dragNodesKeys; + const loop = (data, key, callback) => { + data.forEach((item, index, arr) => { + if (item.key === key) { + return callback(item, index, arr); + } + if (item.children) { + return loop(item.children, key, callback); + } + }); + }; + const data = [...this.state.gData]; + let dragObj; + loop(data, dragKey, (item, index, arr) => { + arr.splice(index, 1); + dragObj = item; + }); + if (info.dropToGap) { + let ar; + let i; + loop(data, dropKey, (item, index, arr) => { + ar = arr; + i = index; + }); + ar.splice(i, 0, dragObj); + } else { + loop(data, dropKey, (item) => { + item.children = item.children || []; + // where to insert 示例添加到尾部,可以是随意位置 + item.children.push(dragObj); + }); + } + this.setState({ + gData: data, + }); + } + render() { + const loop = data => data.map((item) => { + if (item.children && item.children.length) { + return {loop(item.children)}; + } + return ; + }); + return ( + + {loop(this.state.gData)} + + ); + } +};/** +* +* @title Tree可搜索事例 +* @description +* +*/ +const dataList = []; +const generateList = (data) => { + for (let i = 0; i < data.length; i++) { + const node = data[i]; + const key = node.key; + dataList.push({ key, title: key }); + if (node.children) { + generateList(node.children, node.key); + } + } +}; +generateList(gData); + +const getParentKey = (key, tree) => { + let parentKey; + for (let i = 0; i < tree.length; i++) { + const node = tree[i]; + if (node.children) { + if (node.children.some(item => item.key === key)) { + parentKey = node.key; + } else if (getParentKey(key, node.children)) { + parentKey = getParentKey(key, node.children); + } + } + } + return parentKey; +}; + + +class Demo4 extends Component { + constructor(props) { + super(props); + this.state = { + expandedKeys: [], + searchValue: '', + autoExpandParent: true, + } + } + onExpand = (expandedKeys) => { + this.setState({ + expandedKeys, + autoExpandParent: false, + }); + } + onChange = (e) => { + const value = e.target.value; + const expandedKeys = []; + dataList.forEach((item) => { + if (item.key.indexOf(value) > -1) { + expandedKeys.push(getParentKey(item.key, gData)); + } + }); + const uniqueExpandedKeys = []; + expandedKeys.forEach((item) => { + if (item && uniqueExpandedKeys.indexOf(item) === -1) { + uniqueExpandedKeys.push(item); + } + }); + this.setState({ + expandedKeys: uniqueExpandedKeys, + searchValue: value, + autoExpandParent: true, + }); + } + render() { + const { searchValue, expandedKeys, autoExpandParent } = this.state; + const loop = data => data.map((item) => { + const index = item.key.search(searchValue); + const beforeStr = item.key.substr(0, index); + const afterStr = item.key.substr(index + searchValue.length); + const title = index > -1 ? ( + + {beforeStr} + {searchValue} + {afterStr} + + ) : {item.key}; + if (item.children) { + return ( + + {loop(item.children)} + + ); + } + return ; + }); + return ( +
+ + + {loop(gData)} + +
+ ); + } +}/** +* +* @title Tree异步数据加载 +* @description 当点击展开,异步获取子节点数据 +* +*/ +function generateTreeNodes(treeNode) { + const arr = []; + const key = treeNode.props.eventKey; + for (let i = 0; i < 3; i++) { + arr.push({ name: `leaf ${key}-${i}`, key: `${key}-${i}` }); + } + return arr; +} + +function setLeaf(treeData, curKey, level) { + const loopLeaf = (data, lev) => { + const l = lev - 1; + data.forEach((item) => { + if ((item.key.length > curKey.length) ? item.key.indexOf(curKey) !== 0 : + curKey.indexOf(item.key) !== 0) { + return; + } + if (item.children) { + loopLeaf(item.children, l); + } else if (l < 1) { + item.isLeaf = true; + } + }); + }; + loopLeaf(treeData, level + 1); +} + +function getNewTreeData(treeData, curKey, child, level) { + const loop = (data) => { + if (level < 1 || curKey.length - 3 > level * 2) return; + data.forEach((item) => { + if (curKey.indexOf(item.key) === 0) { + if (item.children) { + loop(item.children); + } else { + item.children = child; + } + } + }); + }; + loop(treeData); + setLeaf(treeData, curKey, level); +} + +class Demo5 extends Component{ + constructor(props) { + super(props); + this.state = { + treeData: [], + }; + this.onSelect = this.onSelect.bind(this); + this.onLoadData = this.onLoadData.bind(this); + } + componentDidMount() { + setTimeout(() => { + this.setState({ + treeData: [ + { name: 'pNode 01', key: '0-0' }, + { name: 'pNode 02', key: '0-1' }, + { name: 'pNode 03', key: '0-2', isLeaf: true }, + ], + }); + }, 100); + } + onSelect(info) { + console.log('selected', info); + } + onLoadData(treeNode) { + return new Promise((resolve) => { + setTimeout(() => { + const treeData = [...this.state.treeData]; + getNewTreeData(treeData, treeNode.props.eventKey, generateTreeNodes(treeNode), 2); + this.setState({ treeData }); + resolve(); + }, 1000); + }); + } + render() { + const loop = data => data.map((item) => { + if (item.children) { + return {loop(item.children)}; + } + return ; + }); + const treeNodes = loop(this.state.treeData); + return ( + + {treeNodes} + + ); + } +};var DemoArray = [{"example":,"title":" Tree基本使用事例","code":"/**\n*\n* @title Tree基本使用事例\n* @description 事例涵盖 checkbox如何选择,disable状态和部分选择状态。\n*\n*/\nconst defaultProps = {\n\tkeys: ['0-0-0', '0-0-1']\n}\nclass Demo1 extends Component {\n\tconstructor(props) {\n\t\tsuper(props);\n\t const keys = this.props.keys;\n\t this.state = {\n\t defaultExpandedKeys: keys,\n\t defaultSelectedKeys: keys,\n\t defaultCheckedKeys: keys,\n\t };\n\t}\n\tonSelect(info) {\n\t console.log('selected', info);\n\t}\n\tonCheck(info) {\n\t console.log('onCheck', info);\n\t}\n\trender() {\n\t return (\n\t \n\t \n\t \n\t \n\t \n\t \n\t \n\t sss} key=\"0-0-1-0\" />\n\t \n\t \n\t \n\t );\n\t}\n}\n\nDemo1.defaultProps = defaultProps;","desc":" 事例涵盖 checkbox如何选择,disable状态和部分选择状态。"},{"example":,"title":" Tree数据可控事例","code":"/**\n*\n* @title Tree数据可控事例\n* @description\n*\n*/\n/*\nconst x = 3;\nconst y = 2;\nconst z = 1;\nconst gData = [];\n\nconst generateData = (_level, _preKey, _tns) => {\n const preKey = _preKey || '0';\n const tns = _tns || gData;\n\n const children = [];\n for (let i = 0; i < x; i++) {\n const key = `${preKey}-${i}`;\n tns.push({ title: key, key });\n if (i < y) {\n children.push(key);\n }\n }\n if (_level < 0) {\n return tns;\n }\n const level = _level - 1;\n children.forEach((key, index) => {\n tns[index].children = [];\n return generateData(level, key, tns[index].children);\n });\n};\ngenerateData(z);\n*/\nclass Demo2 extends Component{\n constructor(props) {\n \tsuper(props);\n this.state = {\n expandedKeys: ['0-0-0', '0-0-1'],\n autoExpandParent: true,\n checkedKeys: ['0-0-0'],\n selectedKeys: [],\n };\n this.onExpand = this.onExpand.bind(this);\n this.onCheck = this.onCheck.bind(this);\n this.onSelect = this.onSelect.bind(this);\n }\n onExpand(expandedKeys) {\n console.log('onExpand', arguments);\n // if not set autoExpandParent to false, if children expanded, parent can not collapse.\n // or, you can remove all expanded children keys.\n this.setState({\n expandedKeys,\n autoExpandParent: false,\n });\n }\n onCheck(checkedKeys) {\n this.setState({\n checkedKeys,\n selectedKeys: ['0-3', '0-4'],\n });\n }\n onSelect(selectedKeys, info) {\n console.log('onSelect', info);\n this.setState({ selectedKeys });\n }\n render() {\n const loop = data => data.map((item) => {\n if (item.children) {\n return (\n \n {loop(item.children)}\n \n );\n }\n return ;\n });\n return (\n \n {loop(gData)}\n
\n );\n }\n};","desc":""},{"example":,"title":" Tree 拖拽使用事例","code":"/**\n*\n* @title Tree 拖拽使用事例\n* @description 拖动结点插入到另一个结点后面或者其他的父节点里面。\n*\n*/\n\nclass Demo3 extends Component{\n constructor(props) {\n super(props);\n this.state = {\n gData,\n expandedKeys: ['0-0', '0-0-0', '0-0-0-0'],\n };\n this.onDragEnter = this.onDragEnter.bind(this);\n this.onDrop = this.onDrop.bind(this);\n }\n onDragEnter(info) {\n console.log(info);\n // expandedKeys 需要受控时设置\n // this.setState({\n // expandedKeys: info.expandedKeys,\n // });\n }\n onDrop(info) {\n console.log(info);\n const dropKey = info.node.props.eventKey;\n const dragKey = info.dragNode.props.eventKey;\n // const dragNodesKeys = info.dragNodesKeys;\n const loop = (data, key, callback) => {\n data.forEach((item, index, arr) => {\n if (item.key === key) {\n return callback(item, index, arr);\n }\n if (item.children) {\n return loop(item.children, key, callback);\n }\n });\n };\n const data = [...this.state.gData];\n let dragObj;\n loop(data, dragKey, (item, index, arr) => {\n arr.splice(index, 1);\n dragObj = item;\n });\n if (info.dropToGap) {\n let ar;\n let i;\n loop(data, dropKey, (item, index, arr) => {\n ar = arr;\n i = index;\n });\n ar.splice(i, 0, dragObj);\n } else {\n loop(data, dropKey, (item) => {\n item.children = item.children || [];\n // where to insert 示例添加到尾部,可以是随意位置\n item.children.push(dragObj);\n });\n }\n this.setState({\n gData: data,\n });\n }\n render() {\n const loop = data => data.map((item) => {\n if (item.children && item.children.length) {\n return {loop(item.children)};\n }\n return ;\n });\n return (\n \n {loop(this.state.gData)}\n \n );\n }\n};","desc":" 拖动结点插入到另一个结点后面或者其他的父节点里面。"},{"example":,"title":" Tree可搜索事例","code":"/**\n*\n* @title Tree可搜索事例\n* @description\n*\n*/\nconst dataList = [];\nconst generateList = (data) => {\n for (let i = 0; i < data.length; i++) {\n const node = data[i];\n const key = node.key;\n dataList.push({ key, title: key });\n if (node.children) {\n generateList(node.children, node.key);\n }\n }\n};\ngenerateList(gData);\n\nconst getParentKey = (key, tree) => {\n let parentKey;\n for (let i = 0; i < tree.length; i++) {\n const node = tree[i];\n if (node.children) {\n if (node.children.some(item => item.key === key)) {\n parentKey = node.key;\n } else if (getParentKey(key, node.children)) {\n parentKey = getParentKey(key, node.children);\n }\n }\n }\n return parentKey;\n};\n\n\nclass Demo4 extends Component {\n constructor(props) {\n super(props);\n this.state = {\n expandedKeys: [],\n searchValue: '',\n autoExpandParent: true,\n }\n }\n onExpand = (expandedKeys) => {\n this.setState({\n expandedKeys,\n autoExpandParent: false,\n });\n }\n onChange = (e) => {\n const value = e.target.value;\n const expandedKeys = [];\n dataList.forEach((item) => {\n if (item.key.indexOf(value) > -1) {\n expandedKeys.push(getParentKey(item.key, gData));\n }\n });\n const uniqueExpandedKeys = [];\n expandedKeys.forEach((item) => {\n if (item && uniqueExpandedKeys.indexOf(item) === -1) {\n uniqueExpandedKeys.push(item);\n }\n });\n this.setState({\n expandedKeys: uniqueExpandedKeys,\n searchValue: value,\n autoExpandParent: true,\n });\n }\n render() {\n const { searchValue, expandedKeys, autoExpandParent } = this.state;\n const loop = data => data.map((item) => {\n const index = item.key.search(searchValue);\n const beforeStr = item.key.substr(0, index);\n const afterStr = item.key.substr(index + searchValue.length);\n const title = index > -1 ? (\n \n {beforeStr}\n {searchValue}\n {afterStr}\n \n ) : {item.key};\n if (item.children) {\n return (\n \n {loop(item.children)}\n \n );\n }\n return ;\n });\n return (\n
\n \n \n {loop(gData)}\n \n
\n );\n }\n}","desc":""},{"example":,"title":" Tree异步数据加载","code":"/**\n*\n* @title Tree异步数据加载\n* @description 当点击展开,异步获取子节点数据\n*\n*/\nfunction generateTreeNodes(treeNode) {\n const arr = [];\n const key = treeNode.props.eventKey;\n for (let i = 0; i < 3; i++) {\n arr.push({ name: `leaf ${key}-${i}`, key: `${key}-${i}` });\n }\n return arr;\n}\n\nfunction setLeaf(treeData, curKey, level) {\n const loopLeaf = (data, lev) => {\n const l = lev - 1;\n data.forEach((item) => {\n if ((item.key.length > curKey.length) ? item.key.indexOf(curKey) !== 0 :\n curKey.indexOf(item.key) !== 0) {\n return;\n }\n if (item.children) {\n loopLeaf(item.children, l);\n } else if (l < 1) {\n item.isLeaf = true;\n }\n });\n };\n loopLeaf(treeData, level + 1);\n}\n\nfunction getNewTreeData(treeData, curKey, child, level) {\n const loop = (data) => {\n if (level < 1 || curKey.length - 3 > level * 2) return;\n data.forEach((item) => {\n if (curKey.indexOf(item.key) === 0) {\n if (item.children) {\n loop(item.children);\n } else {\n item.children = child;\n }\n }\n });\n };\n loop(treeData);\n setLeaf(treeData, curKey, level);\n}\n\nclass Demo5 extends Component{\n constructor(props) {\n super(props);\n this.state = {\n treeData: [],\n };\n this.onSelect = this.onSelect.bind(this);\n this.onLoadData = this.onLoadData.bind(this);\n }\n componentDidMount() {\n setTimeout(() => {\n this.setState({\n treeData: [\n { name: 'pNode 01', key: '0-0' },\n { name: 'pNode 02', key: '0-1' },\n { name: 'pNode 03', key: '0-2', isLeaf: true },\n ],\n });\n }, 100);\n }\n onSelect(info) {\n console.log('selected', info);\n }\n onLoadData(treeNode) {\n return new Promise((resolve) => {\n setTimeout(() => {\n const treeData = [...this.state.treeData];\n getNewTreeData(treeData, treeNode.props.eventKey, generateTreeNodes(treeNode), 2);\n this.setState({ treeData });\n resolve();\n }, 1000);\n });\n }\n render() {\n const loop = data => data.map((item) => {\n if (item.children) {\n return {loop(item.children)};\n }\n return ;\n });\n const treeNodes = loop(this.state.treeData);\n return (\n \n {treeNodes}\n \n );\n }\n};","desc":" 当点击展开,异步获取子节点数据"}] + + +class Demo extends Component { + constructor(props){ + super(props); + this.state = { + open: false + } + this.handleClick = this.handleClick.bind(this); + } + handleClick() { + this.setState({ open: !this.state.open }) + } + + render () { + const { title, example, code, desc } = this.props; + let caret = this.state.open ? CARETUP : CARET; + let text = this.state.open ? "隐藏代码" : "查看代码"; + + const footer = ( + + ); + const header = ( + + + { example } + + + + + + ); + return ( + +

{ title }

+

{ desc }

+ +
{ code }
+
+ + ) + } +} + +class DemoGroup extends Component { + constructor(props){ + super(props) + } + render () { + return ( + + {DemoArray.map((child,index) => { + + return ( + + ) + + })} + + ) + } +} + +ReactDOM.render(, document.getElementById('tinperBeeDemo')); diff --git a/docs/api.md b/docs/api.md new file mode 100644 index 0000000..8f4181e --- /dev/null +++ b/docs/api.md @@ -0,0 +1,42 @@ +# Tree + +## 代码演示 + +## API + +## Tree +|参数|说明|类型|默认值| +|:---|:-----|:----|:------| +multiple|是否允许选择多个树节点|bool|false +checkable|是否支持添加在树节点前添加Checkbox|bool|false +defaultExpandAll|默认是否展开所有节点|bool|false +defaultExpandedKeys|默认展开指定的节点|String[]|[] +expandedKeys|指定展开的节点(controlled)String[]|[] +autoExpandParent|是否自定展开父节点|bool|true +defaultCheckedKeys|指定默认被选中的节点key|String[]|[] +checkedKeys|指定被选中的节点(controlled)(PS:当指定的是父节点,所有的子节点也会被指定;当指定的是子节点,父节点也会被选中。当checkable和checkStrictly都为true,子节点与父节点的选择情况都不会影响到对方|String[]/{checked:Array,halfChecked:Array}|[] +checkStrictly|精细的检查每个节点|bool|false +defaultSelectedKeys|指定选中的节点key|String[]|[] +selectedKeys|指定选中的节点keys(controlled)|String[]|- +onExpand|当打开或关闭树节点触发的方法|function(expandedKeys, {expanded: bool, node})|- +onCheck|当选择事件发生触发的方法|function(checkedKeys, e:{checked: bool, checkedNodes, node, event})|- +onSelect|当用户选择树节点触发的回调函数|function(selectedKeys, e:{selected: bool, selectedNodes, node, event})|- +filterTreeNode|过滤树节点的方法(highlight),当返回true,相关联的节点会高亮|function(node)|- +loadData|异步加载数据|function(node)|- +onRightClick|当用户点击右键触发的回调函数|function({event,node})|- +draggable|树是否可拖拽(IE>8| bool|false +onDragStart|当树节点刚开始拖拽所触发的放方法|function({event,node})|- +onDragEnter|当拖拽进入触发的方法|function({event,node,expandedKeys})|- +onDragOver|当拖拽经过触发的方法|function({event,node})|- +onDragLeave|当拖拽离开触发的方法|function({event,node})|- +onDragEnd当拖拽结束触发的方法|function({event,node})|- +onDrop|当节点放下触发方法function({event, node, dragNode, dragNodesKeys})|- + +## TreeNode +|参数|说明|类型|默认值| +|:---|:-----|:----|:------| +disabled|节点是否不可用|bool|false +disableCheckbox|节点的checkbox是否不可用|bool|false +title|名称标题|String/element |-- +key|节点key,和(default)ExpandedKeys / (default)CheckedKeys / (default)SelectedKeys一起用,必须是唯一的|String|- +isLeaf|是否是叶子节点|bool|false diff --git a/docs/api_en.md b/docs/api_en.md new file mode 100644 index 0000000..4424a74 --- /dev/null +++ b/docs/api_en.md @@ -0,0 +1,5 @@ +## Tree +## Code display +## API +|Property|Description|Type|Default| +|:---|:-----|:----|:------| \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..dfc6777 --- /dev/null +++ b/index.html @@ -0,0 +1,22 @@ + + + + + tinper-bee demo + + + + +
+ + + + + + + + + + diff --git a/package.json b/package.json new file mode 100644 index 0000000..913dac8 --- /dev/null +++ b/package.json @@ -0,0 +1,55 @@ +{ + "name": "bee-tree", + "version": "0.0.1", + "description": "Tree ui component for react", + "keywords": [ + "react", + "react-component", + "bee-tree", + "iuap-design", + "tinper-bee", + "Tree" + ], + "engines": { + "node": ">=4.0.0" + }, + "homepage": "https://github.com/tinper-bee/bee-tree.git", + "author": "Yonyou FED", + "repository": "http://github.com/tinper-bee/bee-tree", + "bugs": "https://github.com/tinper-bee/bee-tree.git/issues", + "license": "MIT", + "main": "./build/index.js", + "config": { + "port": 3000 + }, + "scripts": { + "dev": "bee-tools run start", + "build": "bee-tools run build", + "lint": "bee-tools run lint", + "test": "bee-tools run test", + "chrome": "bee-tools run chrome", + "coveralls": "bee-tools run coverage", + "browsers": "bee-tools run browsers", + "pub": "bee-tools run pub" + }, + "dependencies": { + "bee-animate": "latest", + "bee-checkbox": "^0.1.4", + "bee-form-control": "^0.1.5", + "classnames": "^2.2.5", + "object-assign": "latest", + "tinper-bee-core": "latest" + }, + "devDependencies": { + "chai": "^3.5.0", + "enzyme": "^2.4.1", + "react": "~0.14.0", + "react-addons-test-utils": "15.3.2", + "react-dom": "~0.14.0", + "console-polyfill": "~0.2.1", + "es5-shim": "~4.1.10", + "bee-panel": "latest", + "bee-layout": "latest", + "bee-button": "latest" + } +} diff --git a/src copy/Tree.js b/src copy/Tree.js new file mode 100644 index 0000000..4b82601 --- /dev/null +++ b/src copy/Tree.js @@ -0,0 +1,109 @@ +import React from 'react'; +import RcTree, { TreeNode } from './src/index'; +import animation from './openAnimation'; + +export interface AntTreeNodeProps { + disabled?: boolean; + disableCheckbox?: boolean; + title?: string | React.ReactNode; + key?: string; + isLeaf?: boolean; +} + +export class AntTreeNode extends React.Component { + render() { + return ; + } +} + +export interface AntTreeNodeEvent { + event: 'check' | 'select'; + node: AntTreeNode; + checked?: boolean; + checkedNodes?: Array; + selected?: boolean; + selectedNodes?: Array; +} + +export interface AntTreeNodeMouseEvent { + node: AntTreeNode; + event: React.MouseEventHandler; +} + +export interface TreeProps { + showLine?: boolean; + className?: string; + /** 是否支持多选 */ + multiple?: boolean; + /** 是否自动展开父节点 */ + autoExpandParent?: boolean; + /** checkable状态下节点选择完全受控(父子节点选中状态不再关联)*/ + checkStrictly?: boolean; + /** 是否支持选中 */ + checkable?: boolean; + /** 默认展开所有树节点 */ + defaultExpandAll?: boolean; + /** 默认展开指定的树节点 */ + defaultExpandedKeys?: Array; + /** (受控)展开指定的树节点 */ + expandedKeys?: Array; + /** (受控)选中复选框的树节点 */ + checkedKeys?: Array | { checked: Array, halfChecked: Array }; + /** 默认选中复选框的树节点 */ + defaultCheckedKeys?: Array; + /** (受控)设置选中的树节点 */ + selectedKeys?: Array; + /** 默认选中的树节点 */ + defaultSelectedKeys?: Array; + /** 展开/收起节点时触发 */ + onExpand?: (expandedKeys: Array, info: { node: AntTreeNode, expanded: boolean }) => void | PromiseLike; + /** 点击复选框触发 */ + onCheck?: (checkedKeys: Array, e: AntTreeNodeEvent) => void; + /** 点击树节点触发 */ + onSelect?: (selectedKeys: Array, e: AntTreeNodeEvent) => void; + /** filter some AntTreeNodes as you need. it should return true */ + filterAntTreeNode?: (node: AntTreeNode) => boolean; + /** 异步加载数据 */ + loadData?: (node: AntTreeNode) => PromiseLike; + /** 响应右键点击 */ + onRightClick?: (options: AntTreeNodeMouseEvent) => void; + /** 设置节点可拖拽(IE>8)*/ + draggable?: boolean; + /** 开始拖拽时调用 */ + onDragStart?: (options: AntTreeNodeMouseEvent) => void; + /** dragenter 触发时调用 */ + onDragEnter?: (options: AntTreeNodeMouseEvent) => void; + /** dragover 触发时调用 */ + onDragOver?: (options: AntTreeNodeMouseEvent) => void; + /** dragleave 触发时调用 */ + onDragLeave?: (options: AntTreeNodeMouseEvent) => void; + /** drop 触发时调用 */ + onDrop?: (options: AntTreeNodeMouseEvent) => void; + style?: React.CSSProperties; + prefixCls?: string; + filterTreeNode?: (node: AntTreeNode) => boolean; +} + +export default class Tree extends React.Component { + static TreeNode = TreeNode; + + static defaultProps = { + prefixCls: 'ant-tree', + checkable: false, + showIcon: false, + openAnimation: animation, + }; + + render() { + const props = this.props; + let checkable = props.checkable; + return ( + ) : checkable } + > + {this.props.children} + + ); + } +} diff --git a/src copy/Tree.scss b/src copy/Tree.scss new file mode 100644 index 0000000..ad7f06e --- /dev/null +++ b/src copy/Tree.scss @@ -0,0 +1,313 @@ +@import "../node_modules/tinper-bee-core/scss/minxin-variables"; +@import "../node_modules/tinper-bee-core/scss/minxin-mixins"; +@import "../node_modules/bee-checkbox/src/Checkbox"; + + +//css 分割线 +.ant-tree li span.ant-tree-checkbox { + margin: 2px 4px 0 0; +} +.ant-tree-checkbox { + white-space: nowrap; + cursor: pointer; + outline: none; + display: inline-block; + line-height: 1; + position: relative; + vertical-align: middle; +} +.ant-tree-checkbox-checked .ant-tree-checkbox-inner, .ant-tree-checkbox-indeterminate .ant-tree-checkbox-inner { + background-color: #108ee9; + border-color: #108ee9; +} +.ant-tree-checkbox-inner { + position: relative; + top: 0; + left: 0; + display: inline-block; + width: 14px; + height: 14px; + border: 1px solid #d9d9d9; + border-radius: 3px; + background-color: #fff; + -webkit-transition: all .3s; + transition: all .3s; +} +.ant-tree-checkbox-checked .ant-tree-checkbox-inner, .ant-tree-checkbox-indeterminate .ant-tree-checkbox-inner { + background-color: #108ee9; + border-color: #108ee9; +} +ant-tree-checkbox-disabled .ant-tree-checkbox-inner { + border-color: #d9d9d9 !important; + background-color: #f3f3f3; +} +.ant-tree-checkbox-indeterminate .ant-tree-checkbox-inner:after { + content: ' '; + -webkit-transform: scale(1); + -ms-transform: scale(1); + transform: scale(1); + position: absolute; + left: 2px; + top: 5px; + width: 8px; + height: 1px; +} +.ant-tree-checkbox-disabled .ant-tree-checkbox-inner { + border-color: #d9d9d9 !important; + background-color: #f3f3f3; +} +.ant-tree-checkbox-disabled.ant-tree-checkbox-checked .ant-tree-checkbox-inner:after { + -webkit-animation-name: none; + animation-name: none; + border-color: #ccc; +} +.ant-tree-checkbox-disabled .ant-tree-checkbox-inner:after { + -webkit-animation-name: none; + animation-name: none; + border-color: #f3f3f3; +} +.ant-tree-checkbox-checked .ant-tree-checkbox-inner:after { + -webkit-transform: rotate(45deg) scale(1); + -ms-transform: rotate(45deg) scale(1); + transform: rotate(45deg) scale(1); + position: absolute; + left: 4px; + top: 1px; + display: table; + width: 5px; + height: 8px; + border: 2px solid #fff; + border-top: 0; + border-left: 0; + content: ' '; + -webkit-transition: all 0.2s cubic-bezier(0.12, 0.4, 0.29, 1.46) 0.1s; + transition: all 0.2s cubic-bezier(0.12, 0.4, 0.29, 1.46) 0.1s; +} +.ant-tree-checkbox-inner:after { + -webkit-transform: rotate(45deg) scale(0); + -ms-transform: rotate(45deg) scale(0); + transform: rotate(45deg) scale(0); + position: absolute; + left: 4px; + top: 1px; + display: table; + width: 5px; + height: 8px; + border: 2px solid #fff; + border-top: 0; + border-left: 0; + content: ' '; + -webkit-transition: all 0.1s cubic-bezier(0.71, -0.46, 0.88, 0.6); + transition: all 0.1s cubic-bezier(0.71, -0.46, 0.88, 0.6); +} +.ant-tree { + margin: 0; + padding: 5px; + font-size: 12px; +} +.ant-tree li { + padding: 0; + margin: 7px 0; + list-style: none; + white-space: nowrap; + outline: 0; +} +.ant-tree li a[draggable], +.ant-tree li a[draggable="true"] { + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + /* Required to make elements draggable in old WebKit */ + -khtml-user-drag: element; + -webkit-user-drag: element; +} +.ant-tree li.drag-over > a[draggable] { + background-color: #108ee9; + color: white; + opacity: 0.8; +} +.ant-tree li.drag-over-gap-top > a[draggable] { + border-top: 2px #108ee9 solid; +} +.ant-tree li.drag-over-gap-bottom > a[draggable] { + border-bottom: 2px #108ee9 solid; +} +.ant-tree li.filter-node > a { + color: #f50 !important; + font-weight: bold!important; +} +.ant-tree li ul { + margin: 0; + padding: 0 0 0 18px; +} +.ant-tree li a { + display: inline-block; + padding: 1px 5px; + border-radius: 2px; + margin: 0; + cursor: pointer; + text-decoration: none; + vertical-align: top; + color: #666; + -webkit-transition: all 0.3s ease; + transition: all 0.3s ease; +} +.ant-tree li a:hover { + background-color: #e7f4fd; +} +.ant-tree li a.ant-tree-node-selected { + background-color: #cfe8fb; +} +.ant-tree li span.u-checkbox { + margin: 2px 4px 0 0; +} +.ant-tree li span.ant-tree-switcher, +.ant-tree li span.ant-tree-iconEle { + margin: 0; + width: 16px; + height: 16px; + line-height: 16px; + display: inline-block; + vertical-align: middle; + border: 0 none; + cursor: pointer; + outline: none; +} +.ant-tree li span.ant-tree-icon_loading:after { + display: inline-block; + font-family: 'uf'; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + content: "\E6AE"; + -webkit-animation: loadingCircle 1s infinite linear; + animation: loadingCircle 1s infinite linear; + color: #108ee9; +} +.ant-tree li span.ant-tree-switcher.ant-tree-switcher-noop { + cursor: auto; +} +.ant-tree li span.ant-tree-switcher.ant-tree-roots_open, +.ant-tree li span.ant-tree-switcher.ant-tree-center_open, +.ant-tree li span.ant-tree-switcher.ant-tree-bottom_open, +.ant-tree li span.ant-tree-switcher.ant-tree-noline_open { + position: relative; +} +.ant-tree li span.ant-tree-switcher.ant-tree-roots_open:after, +.ant-tree li span.ant-tree-switcher.ant-tree-center_open:after, +.ant-tree li span.ant-tree-switcher.ant-tree-bottom_open:after, +.ant-tree li span.ant-tree-switcher.ant-tree-noline_open:after { + font-size: 12px; + font-size: 7px \9; + -webkit-transform: scale(0.58333333) rotate(0deg); + -ms-transform: scale(0.58333333) rotate(0deg); + transform: scale(0.58333333) rotate(0deg); + /* IE6-IE8 */ + -ms-filter: "progid:DXImageTransform.Microsoft.Matrix(sizingMethod='auto expand', M11=1, M12=0, M21=0, M22=1)"; + zoom: 1; + display: inline-block; + font-family: 'uf'; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + content: "\e639"; + font-weight: bold; + position: absolute; + top: 0; + right: 4px; + color: #666; + -webkit-transition: -webkit-transform .3s ease; + transition: -webkit-transform .3s ease; + transition: transform .3s ease; + transition: transform .3s ease, -webkit-transform .3s ease; +} +:root .ant-tree li span.ant-tree-switcher.ant-tree-roots_open:after, +:root .ant-tree li span.ant-tree-switcher.ant-tree-center_open:after, +:root .ant-tree li span.ant-tree-switcher.ant-tree-bottom_open:after, +:root .ant-tree li span.ant-tree-switcher.ant-tree-noline_open:after { + -webkit-filter: none; + filter: none; +} +:root .ant-tree li span.ant-tree-switcher.ant-tree-roots_open:after, +:root .ant-tree li span.ant-tree-switcher.ant-tree-center_open:after, +:root .ant-tree li span.ant-tree-switcher.ant-tree-bottom_open:after, +:root .ant-tree li span.ant-tree-switcher.ant-tree-noline_open:after { + font-size: 12px; +} +.ant-tree li span.ant-tree-switcher.ant-tree-roots_close, +.ant-tree li span.ant-tree-switcher.ant-tree-center_close, +.ant-tree li span.ant-tree-switcher.ant-tree-bottom_close, +.ant-tree li span.ant-tree-switcher.ant-tree-noline_close { + position: relative; + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=3)"; +} +.ant-tree li span.ant-tree-switcher.ant-tree-roots_close:after, +.ant-tree li span.ant-tree-switcher.ant-tree-center_close:after, +.ant-tree li span.ant-tree-switcher.ant-tree-bottom_close:after, +.ant-tree li span.ant-tree-switcher.ant-tree-noline_close:after { + font-size: 12px; + font-size: 7px \9; + -webkit-transform: scale(0.58333333) rotate(0deg); + -ms-transform: scale(0.58333333) rotate(0deg); + transform: scale(0.58333333) rotate(0deg); + /* IE6-IE8 */ + -ms-filter: "progid:DXImageTransform.Microsoft.Matrix(sizingMethod='auto expand', M11=1, M12=0, M21=0, M22=1)"; + zoom: 1; + display: inline-block; + font-family: 'uf'; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + content: "\e639"; + font-weight: bold; + position: absolute; + top: 0; + right: 4px; + color: #666; + -webkit-transition: -webkit-transform .3s ease; + transition: -webkit-transform .3s ease; + transition: transform .3s ease; + transition: transform .3s ease, -webkit-transform .3s ease; +} +:root .ant-tree li span.ant-tree-switcher.ant-tree-roots_close:after, +:root .ant-tree li span.ant-tree-switcher.ant-tree-center_close:after, +:root .ant-tree li span.ant-tree-switcher.ant-tree-bottom_close:after, +:root .ant-tree li span.ant-tree-switcher.ant-tree-noline_close:after { + -webkit-filter: none; + filter: none; +} +:root .ant-tree li span.ant-tree-switcher.ant-tree-roots_close:after, +:root .ant-tree li span.ant-tree-switcher.ant-tree-center_close:after, +:root .ant-tree li span.ant-tree-switcher.ant-tree-bottom_close:after, +:root .ant-tree li span.ant-tree-switcher.ant-tree-noline_close:after { + font-size: 12px; +} +.ant-tree li span.ant-tree-switcher.ant-tree-roots_close:after, +.ant-tree li span.ant-tree-switcher.ant-tree-center_close:after, +.ant-tree li span.ant-tree-switcher.ant-tree-bottom_close:after, +.ant-tree li span.ant-tree-switcher.ant-tree-noline_close:after { + -webkit-transform: rotate(270deg) scale(0.6); + -ms-transform: rotate(270deg) scale(0.6); + transform: rotate(270deg) scale(0.6); +} +.ant-tree-child-tree { + display: none; +} +.ant-tree-child-tree-open { + display: block; +} +.ant-tree-treenode-disabled > span, +.ant-tree-treenode-disabled > a, +.ant-tree-treenode-disabled > a span { + color: #ccc; + cursor: not-allowed; +} +.ant-tree-icon__open { + margin-right: 2px; + vertical-align: top; +} +.ant-tree-icon__close { + margin-right: 2px; + vertical-align: top; +} diff --git a/src copy/TreeNode.js b/src copy/TreeNode.js new file mode 100644 index 0000000..069fa00 --- /dev/null +++ b/src copy/TreeNode.js @@ -0,0 +1,389 @@ +import React, { PropTypes } from 'react'; +import assign from 'object-assign'; +import classNames from 'classnames'; +import Animate from 'rc-animate'; +import Checkbox from 'bee-checkbox'; +import { browser } from './util'; + +const browserUa = typeof window !== 'undefined' ? browser(window.navigator) : ''; +const ieOrEdge = /.*(IE|Edge).+/.test(browserUa); +// const uaArray = browserUa.split(' '); +// const gtIE8 = uaArray.length !== 2 || uaArray[0].indexOf('IE') === -1 || Number(uaArray[1]) > 8; + +const defaultTitle = '---'; + +class TreeNode extends React.Component { + constructor(props) { + super(props); + [ + 'onExpand', + 'onCheck', + 'onContextMenu', + 'onMouseEnter', + 'onMouseLeave', + 'onDragStart', + 'onDragEnter', + 'onDragOver', + 'onDragLeave', + 'onDrop', + 'onDragEnd', + ].forEach((m) => { + this[m] = this[m].bind(this); + }); + this.state = { + dataLoading: false, + dragNodeHighlight: false, + }; + } + + componentDidMount() { + if (!this.props.root._treeNodeInstances) { + this.props.root._treeNodeInstances = []; + } + this.props.root._treeNodeInstances.push(this); + } + // shouldComponentUpdate(nextProps) { + // if (!nextProps.expanded) { + // return false; + // } + // return true; + // } + + onCheck() { + this.props.root.onCheck(this); + } + + onSelect() { + this.props.root.onSelect(this); + } + + onMouseEnter(e) { + e.preventDefault(); + this.props.root.onMouseEnter(e, this); + } + + onMouseLeave(e) { + e.preventDefault(); + this.props.root.onMouseLeave(e, this); + } + + onContextMenu(e) { + e.preventDefault(); + this.props.root.onContextMenu(e, this); + } + + onDragStart(e) { + // console.log('dragstart', this.props.eventKey, e); + // e.preventDefault(); + e.stopPropagation(); + this.setState({ + dragNodeHighlight: true, + }); + this.props.root.onDragStart(e, this); + try { + // ie throw error + // firefox-need-it + e.dataTransfer.setData('text/plain', ''); + } finally { + // empty + } + } + + onDragEnter(e) { + e.preventDefault(); + e.stopPropagation(); + this.props.root.onDragEnter(e, this); + } + + onDragOver(e) { + // todo disabled + e.preventDefault(); + e.stopPropagation(); + this.props.root.onDragOver(e, this); + return false; + } + + onDragLeave(e) { + e.stopPropagation(); + this.props.root.onDragLeave(e, this); + } + + onDrop(e) { + e.preventDefault(); + e.stopPropagation(); + this.setState({ + dragNodeHighlight: false, + }); + this.props.root.onDrop(e, this); + } + + onDragEnd(e) { + e.stopPropagation(); + this.setState({ + dragNodeHighlight: false, + }); + this.props.root.onDragEnd(e, this); + } + + onExpand() { + const callbackPromise = this.props.root.onExpand(this); + if (callbackPromise && typeof callbackPromise === 'object') { + const setLoading = (dataLoading) => { + this.setState({ dataLoading }); + }; + setLoading(true); + callbackPromise.then(() => { + setLoading(false); + }, () => { + setLoading(false); + }); + } + } + + // keyboard event support + onKeyDown(e) { + e.preventDefault(); + } + + renderSwitcher(props, expandedState) { + const prefixCls = props.prefixCls; + const switcherCls = { + [`${prefixCls}-switcher`]: true, + }; + if (!props.showLine) { + switcherCls[`${prefixCls}-noline_${expandedState}`] = true; + } else if (props.pos === '0-0') { + switcherCls[`${prefixCls}-roots_${expandedState}`] = true; + } else { + switcherCls[`${prefixCls}-center_${expandedState}`] = !props.last; + switcherCls[`${prefixCls}-bottom_${expandedState}`] = props.last; + } + if (props.disabled) { + switcherCls[`${prefixCls}-switcher-disabled`] = true; + return ; + } + return ; + } + + renderCheckbox(props) { + const prefixCls = props.prefixCls; + const checkboxCls = { + [`${prefixCls}-checkbox`]: true, + }; + if (props.checked) { + checkboxCls[`${prefixCls}-checkbox-checked`] = true; + } else if (props.halfChecked) { + checkboxCls[`${prefixCls}-checkbox-indeterminate`] = true; + } + let customEle = null; + if (typeof props.checkable !== 'boolean') { + customEle = props.checkable; + } + if (props.disabled || props.disableCheckbox) { + checkboxCls[`${prefixCls}-checkbox-disabled`] = true; + return {customEle}; + } + return ( + {customEle} + + ); + /*return ( + + )*/ + } + + renderChildren(props) { + const renderFirst = this.renderFirst; + this.renderFirst = 1; + let transitionAppear = true; + if (!renderFirst && props.expanded) { + transitionAppear = false; + } + const children = props.children; + let newChildren = children; + if (children && + (children.type === TreeNode || + Array.isArray(children) && + children.every((item) => { + return item.type === TreeNode; + }))) { + const cls = { + [`${props.prefixCls}-child-tree`]: true, + [`${props.prefixCls}-child-tree-open`]: props.expanded, + }; + if (props.showLine) { + cls[`${props.prefixCls}-line`] = !props.last; + } + const animProps = {}; + if (props.openTransitionName) { + animProps.transitionName = props.openTransitionName; + } else if (typeof props.openAnimation === 'object') { + animProps.animation = assign({}, props.openAnimation); + if (!transitionAppear) { + delete animProps.animation.appear; + } + } + newChildren = ( + + {!props.expanded ? null :
    + {React.Children.map(children, (item, index) => { + return props.root.renderTreeNode(item, index, props.pos); + }, props.root)} +
} +
+ ); + } + return newChildren; + } + + render() { + const props = this.props; + const prefixCls = props.prefixCls; + const expandedState = props.expanded ? 'open' : 'close'; + let iconState = expandedState; + + let canRenderSwitcher = true; + const content = props.title; + let newChildren = this.renderChildren(props); + if (!newChildren || newChildren === props.children) { + // content = newChildren; + newChildren = null; + if (!props.loadData || props.isLeaf) { + canRenderSwitcher = false; + iconState = 'docu'; + } + } + // For performance, does't render children into dom when `!props.expanded` (move to Animate) + // if (!props.expanded) { + // newChildren = null; + // } + + const iconEleCls = { + [`${prefixCls}-iconEle`]: true, + [`${prefixCls}-icon_loading`]: this.state.dataLoading, + [`${prefixCls}-icon__${iconState}`]: true, + }; + + const selectHandle = () => { + const icon = (props.showIcon || props.loadData && this.state.dataLoading) ? + : null; + const title = {content}; + const wrap = `${prefixCls}-node-content-wrapper`; + const domProps = { + className: `${wrap} ${wrap}-${iconState === expandedState ? iconState : 'normal'}`, + }; + if (!props.disabled) { + if (props.selected || !props._dropTrigger && this.state.dragNodeHighlight) { + domProps.className += ` ${prefixCls}-node-selected`; + } + domProps.onClick = (e) => { + e.preventDefault(); + if (props.selectable) { + this.onSelect(); + } + // not fire check event + // if (props.checkable) { + // this.onCheck(); + // } + }; + if (props.onRightClick) { + domProps.onContextMenu = this.onContextMenu; + } + if (props.onMouseEnter) { + domProps.onMouseEnter = this.onMouseEnter; + } + if (props.onMouseLeave) { + domProps.onMouseLeave = this.onMouseLeave; + } + if (props.draggable) { + domProps.className += ' draggable'; + if (ieOrEdge) { + // ie bug! + domProps.href = '#'; + } + domProps.draggable = true; + domProps['aria-grabbed'] = true; + domProps.onDragStart = this.onDragStart; + } + } + return ( + + {icon}{title} + + ); + }; + + const liProps = {}; + if (props.draggable) { + liProps.onDragEnter = this.onDragEnter; + liProps.onDragOver = this.onDragOver; + liProps.onDragLeave = this.onDragLeave; + liProps.onDrop = this.onDrop; + liProps.onDragEnd = this.onDragEnd; + } + + let disabledCls = ''; + let dragOverCls = ''; + if (props.disabled) { + disabledCls = `${prefixCls}-treenode-disabled`; + } else if (props.dragOver) { + dragOverCls = 'drag-over'; + } else if (props.dragOverGapTop) { + dragOverCls = 'drag-over-gap-top'; + } else if (props.dragOverGapBottom) { + dragOverCls = 'drag-over-gap-bottom'; + } + + const filterCls = props.filterTreeNode(this) ? 'filter-node' : ''; + + const noopSwitcher = () => { + const cls = { + [`${prefixCls}-switcher`]: true, + [`${prefixCls}-switcher-noop`]: true, + }; + if (props.showLine) { + cls[`${prefixCls}-center_docu`] = !props.last; + cls[`${prefixCls}-bottom_docu`] = props.last; + } else { + cls[`${prefixCls}-noline_docu`] = true; + } + return ; + }; + + return ( +
  • + {canRenderSwitcher ? this.renderSwitcher(props, expandedState) : noopSwitcher()} + {props.checkable ? this.renderCheckbox(props) : null} + {selectHandle()} + {newChildren} +
  • + ); + } +} + +TreeNode.isTreeNode = 1; + +TreeNode.propTypes = { + prefixCls: PropTypes.string, + disabled: PropTypes.bool, + disableCheckbox: PropTypes.bool, + expanded: PropTypes.bool, + isLeaf: PropTypes.bool, + root: PropTypes.object, + onSelect: PropTypes.func, +}; + +TreeNode.defaultProps = { + title: defaultTitle, +}; + +export default TreeNode; diff --git a/src copy/Treecopy.scss b/src copy/Treecopy.scss new file mode 100644 index 0000000..885fc4f --- /dev/null +++ b/src copy/Treecopy.scss @@ -0,0 +1,363 @@ + +$treePrefixCls: 'ant-tree'; +.#{$treePrefixCls} { + margin: 0; + padding: 5px; + li { + padding: 0; + margin: 0; + list-style: none; + white-space: nowrap; + outline: 0; + .draggable { + color: #333; + -moz-user-select: none; + -khtml-user-select: none; + -webkit-user-select: none; + user-select: none; + /* Required to make elements draggable in old WebKit */ + -khtml-user-drag: element; + -webkit-user-drag: element; + } + &.drag-over { + > .draggable { + background-color: #316ac5; + color: white; + border: 1px #316ac5 solid; + opacity: 0.8; + } + } + &.drag-over-gap-top { + > .draggable { + border-top: 2px blue solid; + } + } + &.drag-over-gap-bottom { + > .draggable { + border-bottom: 2px blue solid; + } + } + &.filter-node { + > .#{$treePrefixCls}-node-content-wrapper { + color: #a60000!important; + font-weight: bold!important; + } + } + ul { + margin: 0; + padding: 0 0 0 18px; + &.#{$treePrefixCls}-line { + background: url("https://t.alipayobjects.com/images/T13BtfXl0mXXXXXXXX.gif") 0 0 repeat-y; + } + } + .#{$treePrefixCls}-node-content-wrapper { + display: inline-block; + padding: 1px 3px 0 0; + margin: 0; + cursor: pointer; + height: 17px; + text-decoration: none; + vertical-align: top; + } + span { + &.#{$treePrefixCls}-switcher, + &.#{$treePrefixCls}-checkbox, + &.#{$treePrefixCls}-iconEle { + line-height: 16px; + margin-right: 2px; + width: 16px; + height: 16px; + display: inline-block; + vertical-align: middle; + border: 0 none; + cursor: pointer; + outline: none; + background-color: transparent; + background-repeat: no-repeat; + background-attachment: scroll; + background-image: url("https://t.alipayobjects.com/images/T1.ANfXhXtXXXXXXXX.png"); + } + &.#{$treePrefixCls}-icon_loading { + margin-right: 2px; + vertical-align: top; + background: url(https://t.alipayobjects.com/images/rmsweb/T1YxhiXgJbXXXXXXXX.gif) no-repeat scroll 0 0 transparent; + } + &.#{$treePrefixCls}-switcher { + &.#{$treePrefixCls}-switcher-noop { + cursor: auto; + } + &.#{$treePrefixCls}-roots_open { + background-position: -93px -56px; + } + &.#{$treePrefixCls}-roots_close { + background-position: -75px -56px; + } + &.#{$treePrefixCls}-center_open { + background-position: -92px -18px; + } + &.#{$treePrefixCls}-center_close { + background-position: -74px -18px; + } + &.#{$treePrefixCls}-bottom_open { + background-position: -92px -36px; + } + &.#{$treePrefixCls}-bottom_close { + background-position: -74px -36px; + } + &.#{$treePrefixCls}-noline_open { + background-position: -92px -72px; + } + &.#{$treePrefixCls}-noline_close { + background-position: -74px -72px; + } + &.#{$treePrefixCls}-center_docu { + background-position: -56px -18px; + } + &.#{$treePrefixCls}-bottom_docu { + background-position: -56px -36px; + } + &.#{$treePrefixCls}-noline_docu { + background: none; + } + } + &.#{$treePrefixCls}-checkbox { + width: 13px; + height: 13px; + margin: 0 3px; + background-position: 0 0; + &-checked { + background-position: -14px 0; + } + &-indeterminate { + background-position: -14px -28px; + } + &-disabled { + background-position: 0 -56px; + } + &.#{$treePrefixCls}-checkbox-checked.#{$treePrefixCls}-checkbox-disabled { + background-position: -14px -56px; + } + &.#{$treePrefixCls}-checkbox-indeterminate.#{$treePrefixCls}-checkbox-disabled { + position: relative; + background: #ccc; + border-radius: 3px; + &::after { + content: ' '; + -webkit-transform: scale(1); + transform: scale(1); + position: absolute; + left: 3px; + top: 5px; + width: 5px; + height: 0; + border: 2px solid #fff; + border-top: 0; + border-left: 0; + } + } + } + } + } + &-child-tree { + display: none; + &-open { + display: block; + } + } + &-treenode-disabled { + >span, + >a, + >a span { + color: #ccc; + cursor: not-allowed; + } + } + &-node-selected { + background-color: #ffe6b0; + border: 1px #ffb951 solid; + opacity: 0.8; + } + &-icon__open { + margin-right: 2px; + background-position: -110px -16px; + vertical-align: top; + } + &-icon__close { + margin-right: 2px; + background-position: -110px 0; + vertical-align: top; + } + &-icon__docu { + margin-right: 2px; + background-position: -110px -32px; + vertical-align: top; + } +} + +$tree-prefix-cls: "ant-tree"; +$primary-color: '#108ee9'; +$highlight-color: '#f50'; +$font-size-base: '12px'; +//.antCheckboxFn($checkbox-prefix-cls: "ant-tree-checkbox"); + +.#{$tree-prefix-cls} { + margin: 0; + padding: 5px; + font-size: $font-size-base; + li { + padding: 0; + margin: 7px 0; + list-style: none; + white-space: nowrap; + outline: 0; + a[draggable], + a[draggable="true"] { + user-select: none; + /* Required to make elements draggable in old WebKit */ + -khtml-user-drag: element; + -webkit-user-drag: element; + } + &.drag-over { + > a[draggable] { + background-color: $primary-color; + color: white; + opacity: 0.8; + } + } + &.drag-over-gap-top { + > a[draggable] { + border-top: 2px $primary-color solid; + } + } + &.drag-over-gap-bottom { + > a[draggable] { + border-bottom: 2px $primary-color solid; + } + } + &.filter-node { + > a { + color: $highlight-color!important; + font-weight: bold!important; + } + } + ul { + margin: 0; + padding: 0 0 0 18px; + } + a { + display: inline-block; + padding: 1px 5px; + border-radius: 2px; + margin: 0; + cursor: pointer; + text-decoration: none; + vertical-align: top; + color: $text-color; + transition: all 0.3s ease; + &:hover { + background-color: tint($primary-color, 90%); + } + &.#{$tree-prefix-cls}-node-selected { + background-color: tint($primary-color, 80%); + } + } + span { + &.#{$tree-prefix-cls}-checkbox { + margin: 2px 4px 0 0; + } + &.#{$tree-prefix-cls}-switcher, + &.#{$tree-prefix-cls}-iconEle { + margin: 0; + width: 16px; + height: 16px; + line-height: 16px; + display: inline-block; + vertical-align: middle; + border: 0 none; + cursor: pointer; + outline: none; + } + &.#{$tree-prefix-cls}-icon_loading { + &:after { + display: inline-block; + animation: loadingCircle 1s infinite linear; + color: $primary-color; + font-family: 'uf'; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + content: "\e6ae"; + } + } + &.#{$tree-prefix-cls}-switcher { + &.#{$tree-prefix-cls}-switcher-noop { + cursor: auto; + } + &.#{$tree-prefix-cls}-roots_open, + &.#{$tree-prefix-cls}-center_open, + &.#{$tree-prefix-cls}-bottom_open, + &.#{$tree-prefix-cls}-noline_open { + //@include antTreeSwitcherIcon; + } + &.#{$tree-prefix-cls}-roots_close, + &.#{$tree-prefix-cls}-center_close, + &.#{$tree-prefix-cls}-bottom_close, + &.#{$tree-prefix-cls}-noline_close { + //@include antTreeSwitcherIcon; + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=3)"; + &:after { + transform: rotate(270deg) scale(0.6); + } + } + } + } + } + &-child-tree { + display: none; + &-open { + display: block; + } + } + &-treenode-disabled { + >span, + >a, + >a span { + color: #ccc; + cursor: not-allowed; + } + } + &-icon__open { + margin-right: 2px; + vertical-align: top; + } + &-icon__close { + margin-right: 2px; + vertical-align: top; + } +} +@mixin antTreeSwitcherIcon { + position: relative; + &:after { + font-size: 12px; + -webkit-transform: scale(0.58333333) rotate(0deg); + -ms-transform: scale(0.58333333) rotate(0deg); + transform: scale(0.58333333) rotate(0deg); + -ms-filter: "progid:DXImageTransform.Microsoft.Matrix(sizingMethod='auto expand', M11=1, M12=0, M21=0, M22=1)"; + zoom: 1; + display: inline-block; + font-family: 'anticon'; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + content: "\E606"; + font-weight: bold; + position: absolute; + top: 0; + right: 4px; + color: #666; + -webkit-transition: -webkit-transform .3s ease; + transition: -webkit-transform .3s ease; + transition: transform .3s ease; + transition: transform .3s ease, -webkit-transform .3s ease; + } +} \ No newline at end of file diff --git a/src copy/index.js b/src copy/index.js new file mode 100644 index 0000000..9a52664 --- /dev/null +++ b/src copy/index.js @@ -0,0 +1,2 @@ +import Tree from './Tree'; +export default Tree; \ No newline at end of file diff --git a/src copy/openAnimation.js b/src copy/openAnimation.js new file mode 100644 index 0000000..bb05a41 --- /dev/null +++ b/src copy/openAnimation.js @@ -0,0 +1,36 @@ +import cssAnimation from 'rc-animate'; + +function animate(node, show, done) { + let height; + return cssAnimation(node, 'ant-motion-collapse', { + start() { + if (!show) { + node.style.height = `${node.offsetHeight}px`; + } else { + height = node.offsetHeight; + node.style.height = 0; + } + }, + active() { + node.style.height = `${show ? height : 0}px`; + }, + end() { + node.style.height = ''; + done(); + }, + }); +} + +const animation = { + enter(node, done) { + return animate(node, true, done); + }, + leave(node, done) { + return animate(node, false, done); + }, + appear(node, done) { + return animate(node, true, done); + }, +}; + +export default animation; diff --git a/src copy/rcTree.js b/src copy/rcTree.js new file mode 100644 index 0000000..6b454cd --- /dev/null +++ b/src copy/rcTree.js @@ -0,0 +1,641 @@ +/* eslint no-console:0 */ +import React, { PropTypes } from 'react'; +import assign from 'object-assign'; +import classNames from 'classnames'; +import { + loopAllChildren, isInclude, getOffset, + filterParentPosition, handleCheckState, getCheck, + getStrictlyValue, arraysEqual, +} from './util'; + +function noop() { +} + +class Tree extends React.Component { + constructor(props) { + super(props); + ['onKeyDown', 'onCheck'].forEach((m) => { + this[m] = this[m].bind(this); + }); + this.contextmenuKeys = []; + this.checkedKeysChange = true; + + this.state = { + expandedKeys: this.getDefaultExpandedKeys(props), + checkedKeys: this.getDefaultCheckedKeys(props), + selectedKeys: this.getDefaultSelectedKeys(props), + dragNodesKeys: '', + dragOverNodeKey: '', + dropNodeKey: '', + }; + } + + componentWillReceiveProps(nextProps) { + const expandedKeys = this.getDefaultExpandedKeys(nextProps, true); + const checkedKeys = this.getDefaultCheckedKeys(nextProps, true); + const selectedKeys = this.getDefaultSelectedKeys(nextProps, true); + const st = {}; + if (expandedKeys) { + st.expandedKeys = expandedKeys; + } + if (checkedKeys) { + if (nextProps.checkedKeys === this.props.checkedKeys) { + this.checkedKeysChange = false; + } else { + this.checkedKeysChange = true; + } + st.checkedKeys = checkedKeys; + } + if (selectedKeys) { + st.selectedKeys = selectedKeys; + } + this.setState(st); + } + + onDragStart(e, treeNode) { + this.dragNode = treeNode; + this.dragNodesKeys = this.getDragNodes(treeNode); + const st = { + dragNodesKeys: this.dragNodesKeys, + }; + const expandedKeys = this.getExpandedKeys(treeNode, false); + if (expandedKeys) { + // Controlled expand, save and then reset + this.getRawExpandedKeys(); + st.expandedKeys = expandedKeys; + } + this.setState(st); + this.props.onDragStart({ + event: e, + node: treeNode, + }); + this._dropTrigger = false; + } + + onDragEnterGap(e, treeNode) { + const offsetTop = (0, getOffset)(treeNode.refs.selectHandle).top; + const offsetHeight = treeNode.refs.selectHandle.offsetHeight; + const pageY = e.pageY; + const gapHeight = 2; + if (pageY > offsetTop + offsetHeight - gapHeight) { + this.dropPosition = 1; + return 1; + } + if (pageY < offsetTop + gapHeight) { + this.dropPosition = -1; + return -1; + } + this.dropPosition = 0; + return 0; + } + + onDragEnter(e, treeNode) { + const enterGap = this.onDragEnterGap(e, treeNode); + if (this.dragNode.props.eventKey === treeNode.props.eventKey && enterGap === 0) { + this.setState({ + dragOverNodeKey: '', + }); + return; + } + const st = { + dragOverNodeKey: treeNode.props.eventKey, + }; + const expandedKeys = this.getExpandedKeys(treeNode, true); + if (expandedKeys) { + this.getRawExpandedKeys(); + st.expandedKeys = expandedKeys; + } + this.setState(st); + this.props.onDragEnter({ + event: e, + node: treeNode, + expandedKeys: expandedKeys && [...expandedKeys] || [...this.state.expandedKeys], + }); + } + + onDragOver(e, treeNode) { + this.props.onDragOver({ event: e, node: treeNode }); + } + + onDragLeave(e, treeNode) { + this.props.onDragLeave({ event: e, node: treeNode }); + } + + onDrop(e, treeNode) { + const key = treeNode.props.eventKey; + this.setState({ + dragOverNodeKey: '', + dropNodeKey: key, + }); + if (this.dragNodesKeys.indexOf(key) > -1) { + if (console.warn) { + console.warn('can not drop to dragNode(include it\'s children node)'); + } + return false; + } + + const posArr = treeNode.props.pos.split('-'); + const res = { + event: e, + node: treeNode, + dragNode: this.dragNode, + dragNodesKeys: [...this.dragNodesKeys], + dropPosition: this.dropPosition + Number(posArr[posArr.length - 1]), + }; + if (this.dropPosition !== 0) { + res.dropToGap = true; + } + if ('expandedKeys' in this.props) { + res.rawExpandedKeys = [...this._rawExpandedKeys] || [...this.state.expandedKeys]; + } + this.props.onDrop(res); + this._dropTrigger = true; + } + + onDragEnd(e, treeNode) { + this.setState({ + dragOverNodeKey: '', + }); + this.props.onDragEnd({ event: e, node: treeNode }); + } + + onExpand(treeNode) { + const expanded = !treeNode.props.expanded; + const controlled = 'expandedKeys' in this.props; + const expandedKeys = [...this.state.expandedKeys]; + const index = expandedKeys.indexOf(treeNode.props.eventKey); + if (expanded && index === -1) { + expandedKeys.push(treeNode.props.eventKey); + } else if (!expanded && index > -1) { + expandedKeys.splice(index, 1); + } + if (!controlled) { + this.setState({ expandedKeys }); + } + this.props.onExpand(expandedKeys, { node: treeNode, expanded }); + + // after data loaded, need set new expandedKeys + if (expanded && this.props.loadData) { + return this.props.loadData(treeNode).then(() => { + if (!controlled) { + this.setState({ expandedKeys }); + } + }); + } + } + + onCheck(treeNode) { + let checked = !treeNode.props.checked; + if (treeNode.props.halfChecked) { + checked = true; + } + const key = treeNode.props.eventKey; + let checkedKeys = [...this.state.checkedKeys]; + const index = checkedKeys.indexOf(key); + + const newSt = { + event: 'check', + node: treeNode, + checked, + }; + + if (this.props.checkStrictly && ('checkedKeys' in this.props)) { + if (checked && index === -1) { + checkedKeys.push(key); + } + if (!checked && index > -1) { + checkedKeys.splice(index, 1); + } + newSt.checkedNodes = []; + loopAllChildren(this.props.children, (item, ind, pos, keyOrPos) => { + if (checkedKeys.indexOf(keyOrPos) !== -1) { + newSt.checkedNodes.push(item); + } + }); + this.props.onCheck(getStrictlyValue(checkedKeys, this.props.checkedKeys.halfChecked), newSt); + } else { + if (checked && index === -1) { + this.treeNodesStates[treeNode.props.pos].checked = true; + const checkedPositions = []; + Object.keys(this.treeNodesStates).forEach(i => { + if (this.treeNodesStates[i].checked) { + checkedPositions.push(i); + } + }); + handleCheckState(this.treeNodesStates, filterParentPosition(checkedPositions), true); + } + if (!checked) { + this.treeNodesStates[treeNode.props.pos].checked = false; + this.treeNodesStates[treeNode.props.pos].halfChecked = false; + handleCheckState(this.treeNodesStates, [treeNode.props.pos], false); + } + const checkKeys = getCheck(this.treeNodesStates); + newSt.checkedNodes = checkKeys.checkedNodes; + newSt.checkedNodesPositions = checkKeys.checkedNodesPositions; + newSt.halfCheckedKeys = checkKeys.halfCheckedKeys; + this.checkKeys = checkKeys; + + this._checkedKeys = checkedKeys = checkKeys.checkedKeys; + if (!('checkedKeys' in this.props)) { + this.setState({ + checkedKeys, + }); + } + this.props.onCheck(checkedKeys, newSt); + } + } + + onSelect(treeNode) { + const props = this.props; + const selectedKeys = [...this.state.selectedKeys]; + const eventKey = treeNode.props.eventKey; + const index = selectedKeys.indexOf(eventKey); + let selected; + if (index !== -1) { + selected = false; + selectedKeys.splice(index, 1); + } else { + selected = true; + if (!props.multiple) { + selectedKeys.length = 0; + } + selectedKeys.push(eventKey); + } + const selectedNodes = []; + if (selectedKeys.length) { + loopAllChildren(this.props.children, (item) => { + if (selectedKeys.indexOf(item.key) !== -1) { + selectedNodes.push(item); + } + }); + } + const newSt = { + event: 'select', + node: treeNode, + selected, + selectedNodes, + }; + if (!('selectedKeys' in this.props)) { + this.setState({ + selectedKeys, + }); + } + props.onSelect(selectedKeys, newSt); + } + + onMouseEnter(e, treeNode) { + this.props.onMouseEnter({ event: e, node: treeNode }); + } + + onMouseLeave(e, treeNode) { + this.props.onMouseLeave({ event: e, node: treeNode }); + } + + onContextMenu(e, treeNode) { + const selectedKeys = [...this.state.selectedKeys]; + const eventKey = treeNode.props.eventKey; + if (this.contextmenuKeys.indexOf(eventKey) === -1) { + this.contextmenuKeys.push(eventKey); + } + this.contextmenuKeys.forEach((key) => { + const index = selectedKeys.indexOf(key); + if (index !== -1) { + selectedKeys.splice(index, 1); + } + }); + if (selectedKeys.indexOf(eventKey) === -1) { + selectedKeys.push(eventKey); + } + this.setState({ + selectedKeys, + }); + this.props.onRightClick({ event: e, node: treeNode }); + } + + // all keyboard events callbacks run from here at first + onKeyDown(e) { + e.preventDefault(); + } + + getFilterExpandedKeys(props, expandKeyProp, expandAll) { + const keys = props[expandKeyProp]; + if (!expandAll && !props.autoExpandParent) { + return keys || []; + } + const expandedPositionArr = []; + if (props.autoExpandParent) { + loopAllChildren(props.children, (item, index, pos, newKey) => { + if (keys.indexOf(newKey) > -1) { + expandedPositionArr.push(pos); + } + }); + } + const filterExpandedKeys = []; + loopAllChildren(props.children, (item, index, pos, newKey) => { + if (expandAll) { + filterExpandedKeys.push(newKey); + } else if (props.autoExpandParent) { + expandedPositionArr.forEach(p => { + if ((p.split('-').length > pos.split('-').length + && isInclude(pos.split('-'), p.split('-')) || pos === p) + && filterExpandedKeys.indexOf(newKey) === -1) { + filterExpandedKeys.push(newKey); + } + }); + } + }); + return filterExpandedKeys.length ? filterExpandedKeys : keys; + } + + getDefaultExpandedKeys(props, willReceiveProps) { + let expandedKeys = willReceiveProps ? undefined : + this.getFilterExpandedKeys(props, 'defaultExpandedKeys', + props.defaultExpandedKeys.length ? false : props.defaultExpandAll); + if ('expandedKeys' in props) { + expandedKeys = (props.autoExpandParent ? + this.getFilterExpandedKeys(props, 'expandedKeys', false) : + props.expandedKeys) || []; + } + return expandedKeys; + } + + getDefaultCheckedKeys(props, willReceiveProps) { + let checkedKeys = willReceiveProps ? undefined : props.defaultCheckedKeys; + if ('checkedKeys' in props) { + checkedKeys = props.checkedKeys || []; + if (props.checkStrictly) { + if (props.checkedKeys.checked) { + checkedKeys = props.checkedKeys.checked; + } else if (!Array.isArray(props.checkedKeys)) { + checkedKeys = []; + } + } + } + return checkedKeys; + } + + getDefaultSelectedKeys(props, willReceiveProps) { + const getKeys = (keys) => { + if (props.multiple) { + return [...keys]; + } + if (keys.length) { + return [keys[0]]; + } + return keys; + }; + let selectedKeys = willReceiveProps ? undefined : getKeys(props.defaultSelectedKeys); + if ('selectedKeys' in props) { + selectedKeys = getKeys(props.selectedKeys); + } + return selectedKeys; + } + + getRawExpandedKeys() { + if (!this._rawExpandedKeys && ('expandedKeys' in this.props)) { + this._rawExpandedKeys = [...this.state.expandedKeys]; + } + } + + getOpenTransitionName() { + const props = this.props; + let transitionName = props.openTransitionName; + const animationName = props.openAnimation; + if (!transitionName && typeof animationName === 'string') { + transitionName = `${props.prefixCls}-open-${animationName}`; + } + return transitionName; + } + + getDragNodes(treeNode) { + const dragNodesKeys = []; + const tPArr = treeNode.props.pos.split('-'); + loopAllChildren(this.props.children, (item, index, pos, newKey) => { + const pArr = pos.split('-'); + if (treeNode.props.pos === pos || tPArr.length < pArr.length && isInclude(tPArr, pArr)) { + dragNodesKeys.push(newKey); + } + }); + return dragNodesKeys; + } + + getExpandedKeys(treeNode, expand) { + const key = treeNode.props.eventKey; + const expandedKeys = this.state.expandedKeys; + const expandedIndex = expandedKeys.indexOf(key); + let exKeys; + if (expandedIndex > -1 && !expand) { + exKeys = [...expandedKeys]; + exKeys.splice(expandedIndex, 1); + return exKeys; + } + if (expand && expandedKeys.indexOf(key) === -1) { + return expandedKeys.concat([key]); + } + } + + filterTreeNode(treeNode) { + const filterTreeNode = this.props.filterTreeNode; + if (typeof filterTreeNode !== 'function' || treeNode.props.disabled) { + return false; + } + return filterTreeNode.call(this, treeNode); + } + + renderTreeNode(child, index, level = 0) { + const pos = `${level}-${index}`; + const key = child.key || pos; + const state = this.state; + const props = this.props; + + // prefer to child's own selectable property if passed + let selectable = props.selectable; + if (child.props.hasOwnProperty('selectable')) { + selectable = child.props.selectable; + } + + const cloneProps = { + ref: `treeNode-${key}`, + root: this, + eventKey: key, + pos, + selectable, + loadData: props.loadData, + onMouseEnter: props.onMouseEnter, + onMouseLeave: props.onMouseLeave, + onRightClick: props.onRightClick, + prefixCls: props.prefixCls, + showLine: props.showLine, + showIcon: props.showIcon, + draggable: props.draggable, + dragOver: state.dragOverNodeKey === key && this.dropPosition === 0, + dragOverGapTop: state.dragOverNodeKey === key && this.dropPosition === -1, + dragOverGapBottom: state.dragOverNodeKey === key && this.dropPosition === 1, + _dropTrigger: this._dropTrigger, + expanded: state.expandedKeys.indexOf(key) !== -1, + selected: state.selectedKeys.indexOf(key) !== -1, + openTransitionName: this.getOpenTransitionName(), + openAnimation: props.openAnimation, + filterTreeNode: this.filterTreeNode.bind(this), + }; + if (props.checkable) { + cloneProps.checkable = props.checkable; + if (props.checkStrictly) { + if (state.checkedKeys) { + cloneProps.checked = state.checkedKeys.indexOf(key) !== -1 || false; + } + if (props.checkedKeys.halfChecked) { + cloneProps.halfChecked = props.checkedKeys.halfChecked.indexOf(key) !== -1 || false; + } else { + cloneProps.halfChecked = false; + } + } else { + if (this.checkedKeys) { + cloneProps.checked = this.checkedKeys.indexOf(key) !== -1 || false; + } + cloneProps.halfChecked = this.halfCheckedKeys.indexOf(key) !== -1; + } + } + if (this.treeNodesStates && this.treeNodesStates[pos]) { + assign(cloneProps, this.treeNodesStates[pos].siblingPosition); + } + return React.cloneElement(child, cloneProps); + } + + render() { + const props = this.props; + const domProps = { + className: classNames(props.className, props.prefixCls), + role: 'tree-node', + }; + if (props.focusable) { + domProps.tabIndex = '0'; + domProps.onKeyDown = this.onKeyDown; + } + const getTreeNodesStates = () => { + this.treeNodesStates = {}; + loopAllChildren(props.children, (item, index, pos, keyOrPos, siblingPosition) => { + this.treeNodesStates[pos] = { + siblingPosition, + }; + }); + }; + if (props.showLine && !props.checkable) { + getTreeNodesStates(); + } + if (props.checkable && (this.checkedKeysChange || props.loadData)) { + if (props.checkStrictly) { + getTreeNodesStates(); + } else if (props._treeNodesStates) { + this.treeNodesStates = props._treeNodesStates.treeNodesStates; + this.halfCheckedKeys = props._treeNodesStates.halfCheckedKeys; + this.checkedKeys = props._treeNodesStates.checkedKeys; + } else { + const checkedKeys = this.state.checkedKeys; + let checkKeys; + if (!props.loadData && this.checkKeys && this._checkedKeys && + arraysEqual(this._checkedKeys, checkedKeys)) { + // if checkedKeys the same as _checkedKeys from onCheck, use _checkedKeys. + checkKeys = this.checkKeys; + } else { + const checkedPositions = []; + this.treeNodesStates = {}; + loopAllChildren(props.children, (item, index, pos, keyOrPos, siblingPosition) => { + this.treeNodesStates[pos] = { + node: item, + key: keyOrPos, + checked: false, + halfChecked: false, + siblingPosition, + }; + if (checkedKeys.indexOf(keyOrPos) !== -1) { + this.treeNodesStates[pos].checked = true; + checkedPositions.push(pos); + } + }); + // if the parent node's key exists, it all children node will be checked + handleCheckState(this.treeNodesStates, filterParentPosition(checkedPositions), true); + checkKeys = getCheck(this.treeNodesStates); + } + this.halfCheckedKeys = checkKeys.halfCheckedKeys; + this.checkedKeys = checkKeys.checkedKeys; + } + } + + return ( +
      + {React.Children.map(props.children, this.renderTreeNode, this)} +
    + ); + } +} + +Tree.propTypes = { + prefixCls: PropTypes.string, + children: PropTypes.any, + showLine: PropTypes.bool, + showIcon: PropTypes.bool, + selectable: PropTypes.bool, + multiple: PropTypes.bool, + checkable: PropTypes.oneOfType([ + PropTypes.bool, + PropTypes.node, + ]), + _treeNodesStates: PropTypes.object, + checkStrictly: PropTypes.bool, + draggable: PropTypes.bool, + autoExpandParent: PropTypes.bool, + defaultExpandAll: PropTypes.bool, + defaultExpandedKeys: PropTypes.arrayOf(PropTypes.string), + expandedKeys: PropTypes.arrayOf(PropTypes.string), + defaultCheckedKeys: PropTypes.arrayOf(PropTypes.string), + checkedKeys: PropTypes.oneOfType([ + PropTypes.arrayOf(PropTypes.string), + PropTypes.object, + ]), + defaultSelectedKeys: PropTypes.arrayOf(PropTypes.string), + selectedKeys: PropTypes.arrayOf(PropTypes.string), + onExpand: PropTypes.func, + onCheck: PropTypes.func, + onSelect: PropTypes.func, + loadData: PropTypes.func, + onMouseEnter: PropTypes.func, + onMouseLeave: PropTypes.func, + onRightClick: PropTypes.func, + onDragStart: PropTypes.func, + onDragEnter: PropTypes.func, + onDragOver: PropTypes.func, + onDragLeave: PropTypes.func, + onDrop: PropTypes.func, + onDragEnd: PropTypes.func, + filterTreeNode: PropTypes.func, + openTransitionName: PropTypes.string, + openAnimation: PropTypes.oneOfType([PropTypes.string, PropTypes.object]), +}; + +Tree.defaultProps = { + prefixCls: 'rc-tree', + showLine: false, + showIcon: true, + selectable: true, + multiple: false, + checkable: false, + checkStrictly: false, + draggable: false, + autoExpandParent: true, + defaultExpandAll: false, + defaultExpandedKeys: [], + defaultCheckedKeys: [], + defaultSelectedKeys: [], + onExpand: noop, + onCheck: noop, + onSelect: noop, + onDragStart: noop, + onDragEnter: noop, + onDragOver: noop, + onDragLeave: noop, + onDrop: noop, + onDragEnd: noop, +}; + +export default Tree; diff --git a/src copy/rcindex.js b/src copy/rcindex.js new file mode 100644 index 0000000..97537ef --- /dev/null +++ b/src copy/rcindex.js @@ -0,0 +1,5 @@ +import Tree from './rcTree'; +import TreeNode from './TreeNode'; +Tree.TreeNode = TreeNode; + +export default Tree; diff --git a/src copy/src/Tree.jsx b/src copy/src/Tree.jsx new file mode 100644 index 0000000..6b454cd --- /dev/null +++ b/src copy/src/Tree.jsx @@ -0,0 +1,641 @@ +/* eslint no-console:0 */ +import React, { PropTypes } from 'react'; +import assign from 'object-assign'; +import classNames from 'classnames'; +import { + loopAllChildren, isInclude, getOffset, + filterParentPosition, handleCheckState, getCheck, + getStrictlyValue, arraysEqual, +} from './util'; + +function noop() { +} + +class Tree extends React.Component { + constructor(props) { + super(props); + ['onKeyDown', 'onCheck'].forEach((m) => { + this[m] = this[m].bind(this); + }); + this.contextmenuKeys = []; + this.checkedKeysChange = true; + + this.state = { + expandedKeys: this.getDefaultExpandedKeys(props), + checkedKeys: this.getDefaultCheckedKeys(props), + selectedKeys: this.getDefaultSelectedKeys(props), + dragNodesKeys: '', + dragOverNodeKey: '', + dropNodeKey: '', + }; + } + + componentWillReceiveProps(nextProps) { + const expandedKeys = this.getDefaultExpandedKeys(nextProps, true); + const checkedKeys = this.getDefaultCheckedKeys(nextProps, true); + const selectedKeys = this.getDefaultSelectedKeys(nextProps, true); + const st = {}; + if (expandedKeys) { + st.expandedKeys = expandedKeys; + } + if (checkedKeys) { + if (nextProps.checkedKeys === this.props.checkedKeys) { + this.checkedKeysChange = false; + } else { + this.checkedKeysChange = true; + } + st.checkedKeys = checkedKeys; + } + if (selectedKeys) { + st.selectedKeys = selectedKeys; + } + this.setState(st); + } + + onDragStart(e, treeNode) { + this.dragNode = treeNode; + this.dragNodesKeys = this.getDragNodes(treeNode); + const st = { + dragNodesKeys: this.dragNodesKeys, + }; + const expandedKeys = this.getExpandedKeys(treeNode, false); + if (expandedKeys) { + // Controlled expand, save and then reset + this.getRawExpandedKeys(); + st.expandedKeys = expandedKeys; + } + this.setState(st); + this.props.onDragStart({ + event: e, + node: treeNode, + }); + this._dropTrigger = false; + } + + onDragEnterGap(e, treeNode) { + const offsetTop = (0, getOffset)(treeNode.refs.selectHandle).top; + const offsetHeight = treeNode.refs.selectHandle.offsetHeight; + const pageY = e.pageY; + const gapHeight = 2; + if (pageY > offsetTop + offsetHeight - gapHeight) { + this.dropPosition = 1; + return 1; + } + if (pageY < offsetTop + gapHeight) { + this.dropPosition = -1; + return -1; + } + this.dropPosition = 0; + return 0; + } + + onDragEnter(e, treeNode) { + const enterGap = this.onDragEnterGap(e, treeNode); + if (this.dragNode.props.eventKey === treeNode.props.eventKey && enterGap === 0) { + this.setState({ + dragOverNodeKey: '', + }); + return; + } + const st = { + dragOverNodeKey: treeNode.props.eventKey, + }; + const expandedKeys = this.getExpandedKeys(treeNode, true); + if (expandedKeys) { + this.getRawExpandedKeys(); + st.expandedKeys = expandedKeys; + } + this.setState(st); + this.props.onDragEnter({ + event: e, + node: treeNode, + expandedKeys: expandedKeys && [...expandedKeys] || [...this.state.expandedKeys], + }); + } + + onDragOver(e, treeNode) { + this.props.onDragOver({ event: e, node: treeNode }); + } + + onDragLeave(e, treeNode) { + this.props.onDragLeave({ event: e, node: treeNode }); + } + + onDrop(e, treeNode) { + const key = treeNode.props.eventKey; + this.setState({ + dragOverNodeKey: '', + dropNodeKey: key, + }); + if (this.dragNodesKeys.indexOf(key) > -1) { + if (console.warn) { + console.warn('can not drop to dragNode(include it\'s children node)'); + } + return false; + } + + const posArr = treeNode.props.pos.split('-'); + const res = { + event: e, + node: treeNode, + dragNode: this.dragNode, + dragNodesKeys: [...this.dragNodesKeys], + dropPosition: this.dropPosition + Number(posArr[posArr.length - 1]), + }; + if (this.dropPosition !== 0) { + res.dropToGap = true; + } + if ('expandedKeys' in this.props) { + res.rawExpandedKeys = [...this._rawExpandedKeys] || [...this.state.expandedKeys]; + } + this.props.onDrop(res); + this._dropTrigger = true; + } + + onDragEnd(e, treeNode) { + this.setState({ + dragOverNodeKey: '', + }); + this.props.onDragEnd({ event: e, node: treeNode }); + } + + onExpand(treeNode) { + const expanded = !treeNode.props.expanded; + const controlled = 'expandedKeys' in this.props; + const expandedKeys = [...this.state.expandedKeys]; + const index = expandedKeys.indexOf(treeNode.props.eventKey); + if (expanded && index === -1) { + expandedKeys.push(treeNode.props.eventKey); + } else if (!expanded && index > -1) { + expandedKeys.splice(index, 1); + } + if (!controlled) { + this.setState({ expandedKeys }); + } + this.props.onExpand(expandedKeys, { node: treeNode, expanded }); + + // after data loaded, need set new expandedKeys + if (expanded && this.props.loadData) { + return this.props.loadData(treeNode).then(() => { + if (!controlled) { + this.setState({ expandedKeys }); + } + }); + } + } + + onCheck(treeNode) { + let checked = !treeNode.props.checked; + if (treeNode.props.halfChecked) { + checked = true; + } + const key = treeNode.props.eventKey; + let checkedKeys = [...this.state.checkedKeys]; + const index = checkedKeys.indexOf(key); + + const newSt = { + event: 'check', + node: treeNode, + checked, + }; + + if (this.props.checkStrictly && ('checkedKeys' in this.props)) { + if (checked && index === -1) { + checkedKeys.push(key); + } + if (!checked && index > -1) { + checkedKeys.splice(index, 1); + } + newSt.checkedNodes = []; + loopAllChildren(this.props.children, (item, ind, pos, keyOrPos) => { + if (checkedKeys.indexOf(keyOrPos) !== -1) { + newSt.checkedNodes.push(item); + } + }); + this.props.onCheck(getStrictlyValue(checkedKeys, this.props.checkedKeys.halfChecked), newSt); + } else { + if (checked && index === -1) { + this.treeNodesStates[treeNode.props.pos].checked = true; + const checkedPositions = []; + Object.keys(this.treeNodesStates).forEach(i => { + if (this.treeNodesStates[i].checked) { + checkedPositions.push(i); + } + }); + handleCheckState(this.treeNodesStates, filterParentPosition(checkedPositions), true); + } + if (!checked) { + this.treeNodesStates[treeNode.props.pos].checked = false; + this.treeNodesStates[treeNode.props.pos].halfChecked = false; + handleCheckState(this.treeNodesStates, [treeNode.props.pos], false); + } + const checkKeys = getCheck(this.treeNodesStates); + newSt.checkedNodes = checkKeys.checkedNodes; + newSt.checkedNodesPositions = checkKeys.checkedNodesPositions; + newSt.halfCheckedKeys = checkKeys.halfCheckedKeys; + this.checkKeys = checkKeys; + + this._checkedKeys = checkedKeys = checkKeys.checkedKeys; + if (!('checkedKeys' in this.props)) { + this.setState({ + checkedKeys, + }); + } + this.props.onCheck(checkedKeys, newSt); + } + } + + onSelect(treeNode) { + const props = this.props; + const selectedKeys = [...this.state.selectedKeys]; + const eventKey = treeNode.props.eventKey; + const index = selectedKeys.indexOf(eventKey); + let selected; + if (index !== -1) { + selected = false; + selectedKeys.splice(index, 1); + } else { + selected = true; + if (!props.multiple) { + selectedKeys.length = 0; + } + selectedKeys.push(eventKey); + } + const selectedNodes = []; + if (selectedKeys.length) { + loopAllChildren(this.props.children, (item) => { + if (selectedKeys.indexOf(item.key) !== -1) { + selectedNodes.push(item); + } + }); + } + const newSt = { + event: 'select', + node: treeNode, + selected, + selectedNodes, + }; + if (!('selectedKeys' in this.props)) { + this.setState({ + selectedKeys, + }); + } + props.onSelect(selectedKeys, newSt); + } + + onMouseEnter(e, treeNode) { + this.props.onMouseEnter({ event: e, node: treeNode }); + } + + onMouseLeave(e, treeNode) { + this.props.onMouseLeave({ event: e, node: treeNode }); + } + + onContextMenu(e, treeNode) { + const selectedKeys = [...this.state.selectedKeys]; + const eventKey = treeNode.props.eventKey; + if (this.contextmenuKeys.indexOf(eventKey) === -1) { + this.contextmenuKeys.push(eventKey); + } + this.contextmenuKeys.forEach((key) => { + const index = selectedKeys.indexOf(key); + if (index !== -1) { + selectedKeys.splice(index, 1); + } + }); + if (selectedKeys.indexOf(eventKey) === -1) { + selectedKeys.push(eventKey); + } + this.setState({ + selectedKeys, + }); + this.props.onRightClick({ event: e, node: treeNode }); + } + + // all keyboard events callbacks run from here at first + onKeyDown(e) { + e.preventDefault(); + } + + getFilterExpandedKeys(props, expandKeyProp, expandAll) { + const keys = props[expandKeyProp]; + if (!expandAll && !props.autoExpandParent) { + return keys || []; + } + const expandedPositionArr = []; + if (props.autoExpandParent) { + loopAllChildren(props.children, (item, index, pos, newKey) => { + if (keys.indexOf(newKey) > -1) { + expandedPositionArr.push(pos); + } + }); + } + const filterExpandedKeys = []; + loopAllChildren(props.children, (item, index, pos, newKey) => { + if (expandAll) { + filterExpandedKeys.push(newKey); + } else if (props.autoExpandParent) { + expandedPositionArr.forEach(p => { + if ((p.split('-').length > pos.split('-').length + && isInclude(pos.split('-'), p.split('-')) || pos === p) + && filterExpandedKeys.indexOf(newKey) === -1) { + filterExpandedKeys.push(newKey); + } + }); + } + }); + return filterExpandedKeys.length ? filterExpandedKeys : keys; + } + + getDefaultExpandedKeys(props, willReceiveProps) { + let expandedKeys = willReceiveProps ? undefined : + this.getFilterExpandedKeys(props, 'defaultExpandedKeys', + props.defaultExpandedKeys.length ? false : props.defaultExpandAll); + if ('expandedKeys' in props) { + expandedKeys = (props.autoExpandParent ? + this.getFilterExpandedKeys(props, 'expandedKeys', false) : + props.expandedKeys) || []; + } + return expandedKeys; + } + + getDefaultCheckedKeys(props, willReceiveProps) { + let checkedKeys = willReceiveProps ? undefined : props.defaultCheckedKeys; + if ('checkedKeys' in props) { + checkedKeys = props.checkedKeys || []; + if (props.checkStrictly) { + if (props.checkedKeys.checked) { + checkedKeys = props.checkedKeys.checked; + } else if (!Array.isArray(props.checkedKeys)) { + checkedKeys = []; + } + } + } + return checkedKeys; + } + + getDefaultSelectedKeys(props, willReceiveProps) { + const getKeys = (keys) => { + if (props.multiple) { + return [...keys]; + } + if (keys.length) { + return [keys[0]]; + } + return keys; + }; + let selectedKeys = willReceiveProps ? undefined : getKeys(props.defaultSelectedKeys); + if ('selectedKeys' in props) { + selectedKeys = getKeys(props.selectedKeys); + } + return selectedKeys; + } + + getRawExpandedKeys() { + if (!this._rawExpandedKeys && ('expandedKeys' in this.props)) { + this._rawExpandedKeys = [...this.state.expandedKeys]; + } + } + + getOpenTransitionName() { + const props = this.props; + let transitionName = props.openTransitionName; + const animationName = props.openAnimation; + if (!transitionName && typeof animationName === 'string') { + transitionName = `${props.prefixCls}-open-${animationName}`; + } + return transitionName; + } + + getDragNodes(treeNode) { + const dragNodesKeys = []; + const tPArr = treeNode.props.pos.split('-'); + loopAllChildren(this.props.children, (item, index, pos, newKey) => { + const pArr = pos.split('-'); + if (treeNode.props.pos === pos || tPArr.length < pArr.length && isInclude(tPArr, pArr)) { + dragNodesKeys.push(newKey); + } + }); + return dragNodesKeys; + } + + getExpandedKeys(treeNode, expand) { + const key = treeNode.props.eventKey; + const expandedKeys = this.state.expandedKeys; + const expandedIndex = expandedKeys.indexOf(key); + let exKeys; + if (expandedIndex > -1 && !expand) { + exKeys = [...expandedKeys]; + exKeys.splice(expandedIndex, 1); + return exKeys; + } + if (expand && expandedKeys.indexOf(key) === -1) { + return expandedKeys.concat([key]); + } + } + + filterTreeNode(treeNode) { + const filterTreeNode = this.props.filterTreeNode; + if (typeof filterTreeNode !== 'function' || treeNode.props.disabled) { + return false; + } + return filterTreeNode.call(this, treeNode); + } + + renderTreeNode(child, index, level = 0) { + const pos = `${level}-${index}`; + const key = child.key || pos; + const state = this.state; + const props = this.props; + + // prefer to child's own selectable property if passed + let selectable = props.selectable; + if (child.props.hasOwnProperty('selectable')) { + selectable = child.props.selectable; + } + + const cloneProps = { + ref: `treeNode-${key}`, + root: this, + eventKey: key, + pos, + selectable, + loadData: props.loadData, + onMouseEnter: props.onMouseEnter, + onMouseLeave: props.onMouseLeave, + onRightClick: props.onRightClick, + prefixCls: props.prefixCls, + showLine: props.showLine, + showIcon: props.showIcon, + draggable: props.draggable, + dragOver: state.dragOverNodeKey === key && this.dropPosition === 0, + dragOverGapTop: state.dragOverNodeKey === key && this.dropPosition === -1, + dragOverGapBottom: state.dragOverNodeKey === key && this.dropPosition === 1, + _dropTrigger: this._dropTrigger, + expanded: state.expandedKeys.indexOf(key) !== -1, + selected: state.selectedKeys.indexOf(key) !== -1, + openTransitionName: this.getOpenTransitionName(), + openAnimation: props.openAnimation, + filterTreeNode: this.filterTreeNode.bind(this), + }; + if (props.checkable) { + cloneProps.checkable = props.checkable; + if (props.checkStrictly) { + if (state.checkedKeys) { + cloneProps.checked = state.checkedKeys.indexOf(key) !== -1 || false; + } + if (props.checkedKeys.halfChecked) { + cloneProps.halfChecked = props.checkedKeys.halfChecked.indexOf(key) !== -1 || false; + } else { + cloneProps.halfChecked = false; + } + } else { + if (this.checkedKeys) { + cloneProps.checked = this.checkedKeys.indexOf(key) !== -1 || false; + } + cloneProps.halfChecked = this.halfCheckedKeys.indexOf(key) !== -1; + } + } + if (this.treeNodesStates && this.treeNodesStates[pos]) { + assign(cloneProps, this.treeNodesStates[pos].siblingPosition); + } + return React.cloneElement(child, cloneProps); + } + + render() { + const props = this.props; + const domProps = { + className: classNames(props.className, props.prefixCls), + role: 'tree-node', + }; + if (props.focusable) { + domProps.tabIndex = '0'; + domProps.onKeyDown = this.onKeyDown; + } + const getTreeNodesStates = () => { + this.treeNodesStates = {}; + loopAllChildren(props.children, (item, index, pos, keyOrPos, siblingPosition) => { + this.treeNodesStates[pos] = { + siblingPosition, + }; + }); + }; + if (props.showLine && !props.checkable) { + getTreeNodesStates(); + } + if (props.checkable && (this.checkedKeysChange || props.loadData)) { + if (props.checkStrictly) { + getTreeNodesStates(); + } else if (props._treeNodesStates) { + this.treeNodesStates = props._treeNodesStates.treeNodesStates; + this.halfCheckedKeys = props._treeNodesStates.halfCheckedKeys; + this.checkedKeys = props._treeNodesStates.checkedKeys; + } else { + const checkedKeys = this.state.checkedKeys; + let checkKeys; + if (!props.loadData && this.checkKeys && this._checkedKeys && + arraysEqual(this._checkedKeys, checkedKeys)) { + // if checkedKeys the same as _checkedKeys from onCheck, use _checkedKeys. + checkKeys = this.checkKeys; + } else { + const checkedPositions = []; + this.treeNodesStates = {}; + loopAllChildren(props.children, (item, index, pos, keyOrPos, siblingPosition) => { + this.treeNodesStates[pos] = { + node: item, + key: keyOrPos, + checked: false, + halfChecked: false, + siblingPosition, + }; + if (checkedKeys.indexOf(keyOrPos) !== -1) { + this.treeNodesStates[pos].checked = true; + checkedPositions.push(pos); + } + }); + // if the parent node's key exists, it all children node will be checked + handleCheckState(this.treeNodesStates, filterParentPosition(checkedPositions), true); + checkKeys = getCheck(this.treeNodesStates); + } + this.halfCheckedKeys = checkKeys.halfCheckedKeys; + this.checkedKeys = checkKeys.checkedKeys; + } + } + + return ( +
      + {React.Children.map(props.children, this.renderTreeNode, this)} +
    + ); + } +} + +Tree.propTypes = { + prefixCls: PropTypes.string, + children: PropTypes.any, + showLine: PropTypes.bool, + showIcon: PropTypes.bool, + selectable: PropTypes.bool, + multiple: PropTypes.bool, + checkable: PropTypes.oneOfType([ + PropTypes.bool, + PropTypes.node, + ]), + _treeNodesStates: PropTypes.object, + checkStrictly: PropTypes.bool, + draggable: PropTypes.bool, + autoExpandParent: PropTypes.bool, + defaultExpandAll: PropTypes.bool, + defaultExpandedKeys: PropTypes.arrayOf(PropTypes.string), + expandedKeys: PropTypes.arrayOf(PropTypes.string), + defaultCheckedKeys: PropTypes.arrayOf(PropTypes.string), + checkedKeys: PropTypes.oneOfType([ + PropTypes.arrayOf(PropTypes.string), + PropTypes.object, + ]), + defaultSelectedKeys: PropTypes.arrayOf(PropTypes.string), + selectedKeys: PropTypes.arrayOf(PropTypes.string), + onExpand: PropTypes.func, + onCheck: PropTypes.func, + onSelect: PropTypes.func, + loadData: PropTypes.func, + onMouseEnter: PropTypes.func, + onMouseLeave: PropTypes.func, + onRightClick: PropTypes.func, + onDragStart: PropTypes.func, + onDragEnter: PropTypes.func, + onDragOver: PropTypes.func, + onDragLeave: PropTypes.func, + onDrop: PropTypes.func, + onDragEnd: PropTypes.func, + filterTreeNode: PropTypes.func, + openTransitionName: PropTypes.string, + openAnimation: PropTypes.oneOfType([PropTypes.string, PropTypes.object]), +}; + +Tree.defaultProps = { + prefixCls: 'rc-tree', + showLine: false, + showIcon: true, + selectable: true, + multiple: false, + checkable: false, + checkStrictly: false, + draggable: false, + autoExpandParent: true, + defaultExpandAll: false, + defaultExpandedKeys: [], + defaultCheckedKeys: [], + defaultSelectedKeys: [], + onExpand: noop, + onCheck: noop, + onSelect: noop, + onDragStart: noop, + onDragEnter: noop, + onDragOver: noop, + onDragLeave: noop, + onDrop: noop, + onDragEnd: noop, +}; + +export default Tree; diff --git a/src copy/src/TreeNode.jsx b/src copy/src/TreeNode.jsx new file mode 100644 index 0000000..aa140cf --- /dev/null +++ b/src copy/src/TreeNode.jsx @@ -0,0 +1,383 @@ +import React, { PropTypes } from 'react'; +import assign from 'object-assign'; +import classNames from 'classnames'; +import Animate from 'rc-animate'; +import { browser } from './util'; + +const browserUa = typeof window !== 'undefined' ? browser(window.navigator) : ''; +const ieOrEdge = /.*(IE|Edge).+/.test(browserUa); +// const uaArray = browserUa.split(' '); +// const gtIE8 = uaArray.length !== 2 || uaArray[0].indexOf('IE') === -1 || Number(uaArray[1]) > 8; + +const defaultTitle = '---'; + +class TreeNode extends React.Component { + constructor(props) { + super(props); + [ + 'onExpand', + 'onCheck', + 'onContextMenu', + 'onMouseEnter', + 'onMouseLeave', + 'onDragStart', + 'onDragEnter', + 'onDragOver', + 'onDragLeave', + 'onDrop', + 'onDragEnd', + ].forEach((m) => { + this[m] = this[m].bind(this); + }); + this.state = { + dataLoading: false, + dragNodeHighlight: false, + }; + } + + componentDidMount() { + if (!this.props.root._treeNodeInstances) { + this.props.root._treeNodeInstances = []; + } + this.props.root._treeNodeInstances.push(this); + } + // shouldComponentUpdate(nextProps) { + // if (!nextProps.expanded) { + // return false; + // } + // return true; + // } + + onCheck() { + this.props.root.onCheck(this); + } + + onSelect() { + this.props.root.onSelect(this); + } + + onMouseEnter(e) { + e.preventDefault(); + this.props.root.onMouseEnter(e, this); + } + + onMouseLeave(e) { + e.preventDefault(); + this.props.root.onMouseLeave(e, this); + } + + onContextMenu(e) { + e.preventDefault(); + this.props.root.onContextMenu(e, this); + } + + onDragStart(e) { + // console.log('dragstart', this.props.eventKey, e); + // e.preventDefault(); + e.stopPropagation(); + this.setState({ + dragNodeHighlight: true, + }); + this.props.root.onDragStart(e, this); + try { + // ie throw error + // firefox-need-it + e.dataTransfer.setData('text/plain', ''); + } finally { + // empty + } + } + + onDragEnter(e) { + e.preventDefault(); + e.stopPropagation(); + this.props.root.onDragEnter(e, this); + } + + onDragOver(e) { + // todo disabled + e.preventDefault(); + e.stopPropagation(); + this.props.root.onDragOver(e, this); + return false; + } + + onDragLeave(e) { + e.stopPropagation(); + this.props.root.onDragLeave(e, this); + } + + onDrop(e) { + e.preventDefault(); + e.stopPropagation(); + this.setState({ + dragNodeHighlight: false, + }); + this.props.root.onDrop(e, this); + } + + onDragEnd(e) { + e.stopPropagation(); + this.setState({ + dragNodeHighlight: false, + }); + this.props.root.onDragEnd(e, this); + } + + onExpand() { + const callbackPromise = this.props.root.onExpand(this); + if (callbackPromise && typeof callbackPromise === 'object') { + const setLoading = (dataLoading) => { + this.setState({ dataLoading }); + }; + setLoading(true); + callbackPromise.then(() => { + setLoading(false); + }, () => { + setLoading(false); + }); + } + } + + // keyboard event support + onKeyDown(e) { + e.preventDefault(); + } + + renderSwitcher(props, expandedState) { + const prefixCls = props.prefixCls; + const switcherCls = { + [`${prefixCls}-switcher`]: true, + }; + if (!props.showLine) { + switcherCls[`${prefixCls}-noline_${expandedState}`] = true; + } else if (props.pos === '0-0') { + switcherCls[`${prefixCls}-roots_${expandedState}`] = true; + } else { + switcherCls[`${prefixCls}-center_${expandedState}`] = !props.last; + switcherCls[`${prefixCls}-bottom_${expandedState}`] = props.last; + } + if (props.disabled) { + switcherCls[`${prefixCls}-switcher-disabled`] = true; + return ; + } + return ; + } + + renderCheckbox(props) { + const prefixCls = props.prefixCls; + const checkboxCls = { + [`${prefixCls}-checkbox`]: true, + }; + if (props.checked) { + checkboxCls[`${prefixCls}-checkbox-checked`] = true; + } else if (props.halfChecked) { + checkboxCls[`${prefixCls}-checkbox-indeterminate`] = true; + } + let customEle = null; + if (typeof props.checkable !== 'boolean') { + customEle = props.checkable; + } + if (props.disabled || props.disableCheckbox) { + checkboxCls[`${prefixCls}-checkbox-disabled`] = true; + return {customEle}; + } + return ( + {customEle}); + } + + renderChildren(props) { + const renderFirst = this.renderFirst; + this.renderFirst = 1; + let transitionAppear = true; + if (!renderFirst && props.expanded) { + transitionAppear = false; + } + const children = props.children; + let newChildren = children; + if (children && + (children.type === TreeNode || + Array.isArray(children) && + children.every((item) => { + return item.type === TreeNode; + }))) { + const cls = { + [`${props.prefixCls}-child-tree`]: true, + [`${props.prefixCls}-child-tree-open`]: props.expanded, + }; + if (props.showLine) { + cls[`${props.prefixCls}-line`] = !props.last; + } + const animProps = {}; + if (props.openTransitionName) { + animProps.transitionName = props.openTransitionName; + } else if (typeof props.openAnimation === 'object') { + animProps.animation = assign({}, props.openAnimation); + if (!transitionAppear) { + delete animProps.animation.appear; + } + } + newChildren = ( + + {!props.expanded ? null :
      + {React.Children.map(children, (item, index) => { + return props.root.renderTreeNode(item, index, props.pos); + }, props.root)} +
    } +
    + ); + } + return newChildren; + } + + render() { + const props = this.props; + const prefixCls = props.prefixCls; + const expandedState = props.expanded ? 'open' : 'close'; + let iconState = expandedState; + + let canRenderSwitcher = true; + const content = props.title; + let newChildren = this.renderChildren(props); + if (!newChildren || newChildren === props.children) { + // content = newChildren; + newChildren = null; + if (!props.loadData || props.isLeaf) { + canRenderSwitcher = false; + iconState = 'docu'; + } + } + // For performance, does't render children into dom when `!props.expanded` (move to Animate) + // if (!props.expanded) { + // newChildren = null; + // } + + const iconEleCls = { + [`${prefixCls}-iconEle`]: true, + [`${prefixCls}-icon_loading`]: this.state.dataLoading, + [`${prefixCls}-icon__${iconState}`]: true, + }; + + const selectHandle = () => { + const icon = (props.showIcon || props.loadData && this.state.dataLoading) ? + : null; + const title = {content}; + const wrap = `${prefixCls}-node-content-wrapper`; + const domProps = { + className: `${wrap} ${wrap}-${iconState === expandedState ? iconState : 'normal'}`, + }; + if (!props.disabled) { + if (props.selected || !props._dropTrigger && this.state.dragNodeHighlight) { + domProps.className += ` ${prefixCls}-node-selected`; + } + domProps.onClick = (e) => { + e.preventDefault(); + if (props.selectable) { + this.onSelect(); + } + // not fire check event + // if (props.checkable) { + // this.onCheck(); + // } + }; + if (props.onRightClick) { + domProps.onContextMenu = this.onContextMenu; + } + if (props.onMouseEnter) { + domProps.onMouseEnter = this.onMouseEnter; + } + if (props.onMouseLeave) { + domProps.onMouseLeave = this.onMouseLeave; + } + if (props.draggable) { + domProps.className += ' draggable'; + if (ieOrEdge) { + // ie bug! + domProps.href = '#'; + } + domProps.draggable = true; + domProps['aria-grabbed'] = true; + domProps.onDragStart = this.onDragStart; + } + } + return ( + + {icon}{title} + + ); + }; + + const liProps = {}; + if (props.draggable) { + liProps.onDragEnter = this.onDragEnter; + liProps.onDragOver = this.onDragOver; + liProps.onDragLeave = this.onDragLeave; + liProps.onDrop = this.onDrop; + liProps.onDragEnd = this.onDragEnd; + } + + let disabledCls = ''; + let dragOverCls = ''; + if (props.disabled) { + disabledCls = `${prefixCls}-treenode-disabled`; + } else if (props.dragOver) { + dragOverCls = 'drag-over'; + } else if (props.dragOverGapTop) { + dragOverCls = 'drag-over-gap-top'; + } else if (props.dragOverGapBottom) { + dragOverCls = 'drag-over-gap-bottom'; + } + + const filterCls = props.filterTreeNode(this) ? 'filter-node' : ''; + + const noopSwitcher = () => { + const cls = { + [`${prefixCls}-switcher`]: true, + [`${prefixCls}-switcher-noop`]: true, + }; + if (props.showLine) { + cls[`${prefixCls}-center_docu`] = !props.last; + cls[`${prefixCls}-bottom_docu`] = props.last; + } else { + cls[`${prefixCls}-noline_docu`] = true; + } + return ; + }; + + return ( +
  • + {canRenderSwitcher ? this.renderSwitcher(props, expandedState) : noopSwitcher()} + {props.checkable ? this.renderCheckbox(props) : null} + {selectHandle()} + {newChildren} +
  • + ); + } +} + +TreeNode.isTreeNode = 1; + +TreeNode.propTypes = { + prefixCls: PropTypes.string, + disabled: PropTypes.bool, + disableCheckbox: PropTypes.bool, + expanded: PropTypes.bool, + isLeaf: PropTypes.bool, + root: PropTypes.object, + onSelect: PropTypes.func, +}; + +TreeNode.defaultProps = { + title: defaultTitle, +}; + +export default TreeNode; diff --git a/src copy/src/index.js b/src copy/src/index.js new file mode 100644 index 0000000..a37c0d2 --- /dev/null +++ b/src copy/src/index.js @@ -0,0 +1,5 @@ +import Tree from './Tree'; +import TreeNode from './TreeNode'; +Tree.TreeNode = TreeNode; + +export default Tree; diff --git a/src copy/src/util.js b/src copy/src/util.js new file mode 100644 index 0000000..3d0d84a --- /dev/null +++ b/src copy/src/util.js @@ -0,0 +1,286 @@ +/* eslint no-loop-func: 0*/ + +import React from 'react'; + +export function browser(navigator) { + let tem; + const ua = navigator.userAgent; + let M = ua.match(/(opera|chrome|safari|firefox|msie|trident(?=\/))\/?\s*(\d+)/i) || []; + if (/trident/i.test(M[1])) { + tem = /\brv[ :]+(\d+)/g.exec(ua) || []; + return `IE ${tem[1] || ''}`; + } + if (M[1] === 'Chrome') { + tem = ua.match(/\b(OPR|Edge)\/(\d+)/); + if (tem) return tem.slice(1).join(' ').replace('OPR', 'Opera'); + } + M = M[2] ? [M[1], M[2]] : [navigator.appName, navigator.appVersion, '-?']; + tem = ua.match(/version\/(\d+)/i); + if (tem) { + M.splice(1, 1, tem[1]); + } + return M.join(' '); +} + +// export function getOffset(el) { +// const obj = el.getBoundingClientRect(); +// return { +// left: obj.left + document.body.scrollLeft, +// top: obj.top + document.body.scrollTop, +// width: obj.width, +// height: obj.height +// }; +// } + +// // iscroll offset +// offset = function (el) { +// var left = -el.offsetLeft, +// top = -el.offsetTop; + +// // jshint -W084 +// while (el = el.offsetParent) { +// left -= el.offsetLeft; +// top -= el.offsetTop; +// } +// // jshint +W084 + +// return { +// left: left, +// top: top +// }; +// } + +/* eslint-disable */ +export function getOffset(ele) { + let doc, win, docElem, rect; + + if (!ele.getClientRects().length) { + return { top: 0, left: 0 }; + } + + rect = ele.getBoundingClientRect(); + + if (rect.width || rect.height) { + doc = ele.ownerDocument; + win = doc.defaultView; + docElem = doc.documentElement; + + return { + top: rect.top + win.pageYOffset - docElem.clientTop, + left: rect.left + win.pageXOffset - docElem.clientLeft + }; + } + + return rect; +} +/* eslint-enable */ + +function getChildrenlength(children) { + let len = 1; + if (Array.isArray(children)) { + len = children.length; + } + return len; +} + +function getSiblingPosition(index, len, siblingPosition) { + if (len === 1) { + siblingPosition.first = true; + siblingPosition.last = true; + } else { + siblingPosition.first = index === 0; + siblingPosition.last = index === len - 1; + } + return siblingPosition; +} + +export function loopAllChildren(childs, callback, parent) { + const loop = (children, level, _parent) => { + const len = getChildrenlength(children); + React.Children.forEach(children, (item, index) => { + const pos = `${level}-${index}`; + if (item.props.children && item.type && item.type.isTreeNode) { + loop(item.props.children, pos, { node: item, pos }); + } + callback(item, index, pos, item.key || pos, getSiblingPosition(index, len, {}), _parent); + }); + }; + loop(childs, 0, parent); +} + +export function isInclude(smallArray, bigArray) { + return smallArray.every((ii, i) => { + return ii === bigArray[i]; + }); +} +// console.log(isInclude(['0', '1'], ['0', '10', '1'])); + + +// arr.length === 628, use time: ~20ms +export function filterParentPosition(arr) { + const levelObj = {}; + arr.forEach((item) => { + const posLen = item.split('-').length; + if (!levelObj[posLen]) { + levelObj[posLen] = []; + } + levelObj[posLen].push(item); + }); + const levelArr = Object.keys(levelObj).sort(); + for (let i = 0; i < levelArr.length; i++) { + if (levelArr[i + 1]) { + levelObj[levelArr[i]].forEach(ii => { + for (let j = i + 1; j < levelArr.length; j++) { + levelObj[levelArr[j]].forEach((_i, index) => { + if (isInclude(ii.split('-'), _i.split('-'))) { + levelObj[levelArr[j]][index] = null; + } + }); + levelObj[levelArr[j]] = levelObj[levelArr[j]].filter(p => p); + } + }); + } + } + let nArr = []; + levelArr.forEach(i => { + nArr = nArr.concat(levelObj[i]); + }); + return nArr; +} +// console.log(filterParentPosition( +// ['0-2', '0-3-3', '0-10', '0-10-0', '0-0-1', '0-0', '0-1-1', '0-1'] +// )); + + +function stripTail(str) { + const arr = str.match(/(.+)(-[^-]+)$/); + let st = ''; + if (arr && arr.length === 3) { + st = arr[1]; + } + return st; +} +function splitPosition(pos) { + return pos.split('-'); +} + +export function handleCheckState(obj, checkedPositionArr, checkIt) { + // console.log(stripTail('0-101-000')); + let objKeys = Object.keys(obj); + // let s = Date.now(); + objKeys.forEach((i, index) => { + const iArr = splitPosition(i); + let saved = false; + checkedPositionArr.forEach((_pos) => { + // 设置子节点,全选或全不选 + const _posArr = splitPosition(_pos); + if (iArr.length > _posArr.length && isInclude(_posArr, iArr)) { + obj[i].halfChecked = false; + obj[i].checked = checkIt; + objKeys[index] = null; + } + if (iArr[0] === _posArr[0] && iArr[1] === _posArr[1]) { + // 如果 + saved = true; + } + }); + if (!saved) { + objKeys[index] = null; + } + }); + // TODO: 循环 2470000 次耗时约 1400 ms。 性能瓶颈! + // console.log(Date.now()-s, checkedPositionArr.length * objKeys.length); + objKeys = objKeys.filter(i => i); // filter non null; + + for (let pIndex = 0; pIndex < checkedPositionArr.length; pIndex++) { + // 循环设置父节点的 选中 或 半选状态 + const loop = (__pos) => { + const _posLen = splitPosition(__pos).length; + if (_posLen <= 2) { // e.g. '0-0', '0-1' + return; + } + let sibling = 0; + let siblingChecked = 0; + const parentPosition = stripTail(__pos); + objKeys.forEach((i /* , index*/) => { + const iArr = splitPosition(i); + if (iArr.length === _posLen && isInclude(splitPosition(parentPosition), iArr)) { + sibling++; + if (obj[i].checked) { + siblingChecked++; + const _i = checkedPositionArr.indexOf(i); + if (_i > -1) { + checkedPositionArr.splice(_i, 1); + if (_i <= pIndex) { + pIndex--; + } + } + } else if (obj[i].halfChecked) { + siblingChecked += 0.5; + } + // objKeys[index] = null; + } + }); + // objKeys = objKeys.filter(i => i); // filter non null; + const parent = obj[parentPosition]; + // sibling 不会等于0 + // 全不选 - 全选 - 半选 + if (siblingChecked === 0) { + parent.checked = false; + parent.halfChecked = false; + } else if (siblingChecked === sibling) { + parent.checked = true; + parent.halfChecked = false; + } else { + parent.halfChecked = true; + parent.checked = false; + } + loop(parentPosition); + }; + loop(checkedPositionArr[pIndex], pIndex); + } + // console.log(Date.now()-s, objKeys.length, checkIt); +} + +export function getCheck(treeNodesStates) { + const halfCheckedKeys = []; + const checkedKeys = []; + const checkedNodes = []; + const checkedNodesPositions = []; + Object.keys(treeNodesStates).forEach((item) => { + const itemObj = treeNodesStates[item]; + if (itemObj.checked) { + checkedKeys.push(itemObj.key); + checkedNodes.push(itemObj.node); + checkedNodesPositions.push({ node: itemObj.node, pos: item }); + } else if (itemObj.halfChecked) { + halfCheckedKeys.push(itemObj.key); + } + }); + return { + halfCheckedKeys, checkedKeys, checkedNodes, checkedNodesPositions, treeNodesStates, + }; +} + +export function getStrictlyValue(checkedKeys, halfChecked) { + if (halfChecked) { + return { checked: checkedKeys, halfChecked }; + } + return checkedKeys; +} + +export function arraysEqual(a, b) { + if (a === b) return true; + if (a === null || typeof a === 'undefined' || b === null || typeof b === 'undefined') { + return false; + } + if (a.length !== b.length) return false; + + // If you don't care about the order of the elements inside + // the array, you should sort both arrays here. + + for (let i = 0; i < a.length; ++i) { + if (a[i] !== b[i]) return false; + } + return true; +} diff --git a/src copy/util.js b/src copy/util.js new file mode 100644 index 0000000..3d0d84a --- /dev/null +++ b/src copy/util.js @@ -0,0 +1,286 @@ +/* eslint no-loop-func: 0*/ + +import React from 'react'; + +export function browser(navigator) { + let tem; + const ua = navigator.userAgent; + let M = ua.match(/(opera|chrome|safari|firefox|msie|trident(?=\/))\/?\s*(\d+)/i) || []; + if (/trident/i.test(M[1])) { + tem = /\brv[ :]+(\d+)/g.exec(ua) || []; + return `IE ${tem[1] || ''}`; + } + if (M[1] === 'Chrome') { + tem = ua.match(/\b(OPR|Edge)\/(\d+)/); + if (tem) return tem.slice(1).join(' ').replace('OPR', 'Opera'); + } + M = M[2] ? [M[1], M[2]] : [navigator.appName, navigator.appVersion, '-?']; + tem = ua.match(/version\/(\d+)/i); + if (tem) { + M.splice(1, 1, tem[1]); + } + return M.join(' '); +} + +// export function getOffset(el) { +// const obj = el.getBoundingClientRect(); +// return { +// left: obj.left + document.body.scrollLeft, +// top: obj.top + document.body.scrollTop, +// width: obj.width, +// height: obj.height +// }; +// } + +// // iscroll offset +// offset = function (el) { +// var left = -el.offsetLeft, +// top = -el.offsetTop; + +// // jshint -W084 +// while (el = el.offsetParent) { +// left -= el.offsetLeft; +// top -= el.offsetTop; +// } +// // jshint +W084 + +// return { +// left: left, +// top: top +// }; +// } + +/* eslint-disable */ +export function getOffset(ele) { + let doc, win, docElem, rect; + + if (!ele.getClientRects().length) { + return { top: 0, left: 0 }; + } + + rect = ele.getBoundingClientRect(); + + if (rect.width || rect.height) { + doc = ele.ownerDocument; + win = doc.defaultView; + docElem = doc.documentElement; + + return { + top: rect.top + win.pageYOffset - docElem.clientTop, + left: rect.left + win.pageXOffset - docElem.clientLeft + }; + } + + return rect; +} +/* eslint-enable */ + +function getChildrenlength(children) { + let len = 1; + if (Array.isArray(children)) { + len = children.length; + } + return len; +} + +function getSiblingPosition(index, len, siblingPosition) { + if (len === 1) { + siblingPosition.first = true; + siblingPosition.last = true; + } else { + siblingPosition.first = index === 0; + siblingPosition.last = index === len - 1; + } + return siblingPosition; +} + +export function loopAllChildren(childs, callback, parent) { + const loop = (children, level, _parent) => { + const len = getChildrenlength(children); + React.Children.forEach(children, (item, index) => { + const pos = `${level}-${index}`; + if (item.props.children && item.type && item.type.isTreeNode) { + loop(item.props.children, pos, { node: item, pos }); + } + callback(item, index, pos, item.key || pos, getSiblingPosition(index, len, {}), _parent); + }); + }; + loop(childs, 0, parent); +} + +export function isInclude(smallArray, bigArray) { + return smallArray.every((ii, i) => { + return ii === bigArray[i]; + }); +} +// console.log(isInclude(['0', '1'], ['0', '10', '1'])); + + +// arr.length === 628, use time: ~20ms +export function filterParentPosition(arr) { + const levelObj = {}; + arr.forEach((item) => { + const posLen = item.split('-').length; + if (!levelObj[posLen]) { + levelObj[posLen] = []; + } + levelObj[posLen].push(item); + }); + const levelArr = Object.keys(levelObj).sort(); + for (let i = 0; i < levelArr.length; i++) { + if (levelArr[i + 1]) { + levelObj[levelArr[i]].forEach(ii => { + for (let j = i + 1; j < levelArr.length; j++) { + levelObj[levelArr[j]].forEach((_i, index) => { + if (isInclude(ii.split('-'), _i.split('-'))) { + levelObj[levelArr[j]][index] = null; + } + }); + levelObj[levelArr[j]] = levelObj[levelArr[j]].filter(p => p); + } + }); + } + } + let nArr = []; + levelArr.forEach(i => { + nArr = nArr.concat(levelObj[i]); + }); + return nArr; +} +// console.log(filterParentPosition( +// ['0-2', '0-3-3', '0-10', '0-10-0', '0-0-1', '0-0', '0-1-1', '0-1'] +// )); + + +function stripTail(str) { + const arr = str.match(/(.+)(-[^-]+)$/); + let st = ''; + if (arr && arr.length === 3) { + st = arr[1]; + } + return st; +} +function splitPosition(pos) { + return pos.split('-'); +} + +export function handleCheckState(obj, checkedPositionArr, checkIt) { + // console.log(stripTail('0-101-000')); + let objKeys = Object.keys(obj); + // let s = Date.now(); + objKeys.forEach((i, index) => { + const iArr = splitPosition(i); + let saved = false; + checkedPositionArr.forEach((_pos) => { + // 设置子节点,全选或全不选 + const _posArr = splitPosition(_pos); + if (iArr.length > _posArr.length && isInclude(_posArr, iArr)) { + obj[i].halfChecked = false; + obj[i].checked = checkIt; + objKeys[index] = null; + } + if (iArr[0] === _posArr[0] && iArr[1] === _posArr[1]) { + // 如果 + saved = true; + } + }); + if (!saved) { + objKeys[index] = null; + } + }); + // TODO: 循环 2470000 次耗时约 1400 ms。 性能瓶颈! + // console.log(Date.now()-s, checkedPositionArr.length * objKeys.length); + objKeys = objKeys.filter(i => i); // filter non null; + + for (let pIndex = 0; pIndex < checkedPositionArr.length; pIndex++) { + // 循环设置父节点的 选中 或 半选状态 + const loop = (__pos) => { + const _posLen = splitPosition(__pos).length; + if (_posLen <= 2) { // e.g. '0-0', '0-1' + return; + } + let sibling = 0; + let siblingChecked = 0; + const parentPosition = stripTail(__pos); + objKeys.forEach((i /* , index*/) => { + const iArr = splitPosition(i); + if (iArr.length === _posLen && isInclude(splitPosition(parentPosition), iArr)) { + sibling++; + if (obj[i].checked) { + siblingChecked++; + const _i = checkedPositionArr.indexOf(i); + if (_i > -1) { + checkedPositionArr.splice(_i, 1); + if (_i <= pIndex) { + pIndex--; + } + } + } else if (obj[i].halfChecked) { + siblingChecked += 0.5; + } + // objKeys[index] = null; + } + }); + // objKeys = objKeys.filter(i => i); // filter non null; + const parent = obj[parentPosition]; + // sibling 不会等于0 + // 全不选 - 全选 - 半选 + if (siblingChecked === 0) { + parent.checked = false; + parent.halfChecked = false; + } else if (siblingChecked === sibling) { + parent.checked = true; + parent.halfChecked = false; + } else { + parent.halfChecked = true; + parent.checked = false; + } + loop(parentPosition); + }; + loop(checkedPositionArr[pIndex], pIndex); + } + // console.log(Date.now()-s, objKeys.length, checkIt); +} + +export function getCheck(treeNodesStates) { + const halfCheckedKeys = []; + const checkedKeys = []; + const checkedNodes = []; + const checkedNodesPositions = []; + Object.keys(treeNodesStates).forEach((item) => { + const itemObj = treeNodesStates[item]; + if (itemObj.checked) { + checkedKeys.push(itemObj.key); + checkedNodes.push(itemObj.node); + checkedNodesPositions.push({ node: itemObj.node, pos: item }); + } else if (itemObj.halfChecked) { + halfCheckedKeys.push(itemObj.key); + } + }); + return { + halfCheckedKeys, checkedKeys, checkedNodes, checkedNodesPositions, treeNodesStates, + }; +} + +export function getStrictlyValue(checkedKeys, halfChecked) { + if (halfChecked) { + return { checked: checkedKeys, halfChecked }; + } + return checkedKeys; +} + +export function arraysEqual(a, b) { + if (a === b) return true; + if (a === null || typeof a === 'undefined' || b === null || typeof b === 'undefined') { + return false; + } + if (a.length !== b.length) return false; + + // If you don't care about the order of the elements inside + // the array, you should sort both arrays here. + + for (let i = 0; i < a.length; ++i) { + if (a[i] !== b[i]) return false; + } + return true; +} diff --git a/src/Tree.js b/src/Tree.js new file mode 100644 index 0000000..6b454cd --- /dev/null +++ b/src/Tree.js @@ -0,0 +1,641 @@ +/* eslint no-console:0 */ +import React, { PropTypes } from 'react'; +import assign from 'object-assign'; +import classNames from 'classnames'; +import { + loopAllChildren, isInclude, getOffset, + filterParentPosition, handleCheckState, getCheck, + getStrictlyValue, arraysEqual, +} from './util'; + +function noop() { +} + +class Tree extends React.Component { + constructor(props) { + super(props); + ['onKeyDown', 'onCheck'].forEach((m) => { + this[m] = this[m].bind(this); + }); + this.contextmenuKeys = []; + this.checkedKeysChange = true; + + this.state = { + expandedKeys: this.getDefaultExpandedKeys(props), + checkedKeys: this.getDefaultCheckedKeys(props), + selectedKeys: this.getDefaultSelectedKeys(props), + dragNodesKeys: '', + dragOverNodeKey: '', + dropNodeKey: '', + }; + } + + componentWillReceiveProps(nextProps) { + const expandedKeys = this.getDefaultExpandedKeys(nextProps, true); + const checkedKeys = this.getDefaultCheckedKeys(nextProps, true); + const selectedKeys = this.getDefaultSelectedKeys(nextProps, true); + const st = {}; + if (expandedKeys) { + st.expandedKeys = expandedKeys; + } + if (checkedKeys) { + if (nextProps.checkedKeys === this.props.checkedKeys) { + this.checkedKeysChange = false; + } else { + this.checkedKeysChange = true; + } + st.checkedKeys = checkedKeys; + } + if (selectedKeys) { + st.selectedKeys = selectedKeys; + } + this.setState(st); + } + + onDragStart(e, treeNode) { + this.dragNode = treeNode; + this.dragNodesKeys = this.getDragNodes(treeNode); + const st = { + dragNodesKeys: this.dragNodesKeys, + }; + const expandedKeys = this.getExpandedKeys(treeNode, false); + if (expandedKeys) { + // Controlled expand, save and then reset + this.getRawExpandedKeys(); + st.expandedKeys = expandedKeys; + } + this.setState(st); + this.props.onDragStart({ + event: e, + node: treeNode, + }); + this._dropTrigger = false; + } + + onDragEnterGap(e, treeNode) { + const offsetTop = (0, getOffset)(treeNode.refs.selectHandle).top; + const offsetHeight = treeNode.refs.selectHandle.offsetHeight; + const pageY = e.pageY; + const gapHeight = 2; + if (pageY > offsetTop + offsetHeight - gapHeight) { + this.dropPosition = 1; + return 1; + } + if (pageY < offsetTop + gapHeight) { + this.dropPosition = -1; + return -1; + } + this.dropPosition = 0; + return 0; + } + + onDragEnter(e, treeNode) { + const enterGap = this.onDragEnterGap(e, treeNode); + if (this.dragNode.props.eventKey === treeNode.props.eventKey && enterGap === 0) { + this.setState({ + dragOverNodeKey: '', + }); + return; + } + const st = { + dragOverNodeKey: treeNode.props.eventKey, + }; + const expandedKeys = this.getExpandedKeys(treeNode, true); + if (expandedKeys) { + this.getRawExpandedKeys(); + st.expandedKeys = expandedKeys; + } + this.setState(st); + this.props.onDragEnter({ + event: e, + node: treeNode, + expandedKeys: expandedKeys && [...expandedKeys] || [...this.state.expandedKeys], + }); + } + + onDragOver(e, treeNode) { + this.props.onDragOver({ event: e, node: treeNode }); + } + + onDragLeave(e, treeNode) { + this.props.onDragLeave({ event: e, node: treeNode }); + } + + onDrop(e, treeNode) { + const key = treeNode.props.eventKey; + this.setState({ + dragOverNodeKey: '', + dropNodeKey: key, + }); + if (this.dragNodesKeys.indexOf(key) > -1) { + if (console.warn) { + console.warn('can not drop to dragNode(include it\'s children node)'); + } + return false; + } + + const posArr = treeNode.props.pos.split('-'); + const res = { + event: e, + node: treeNode, + dragNode: this.dragNode, + dragNodesKeys: [...this.dragNodesKeys], + dropPosition: this.dropPosition + Number(posArr[posArr.length - 1]), + }; + if (this.dropPosition !== 0) { + res.dropToGap = true; + } + if ('expandedKeys' in this.props) { + res.rawExpandedKeys = [...this._rawExpandedKeys] || [...this.state.expandedKeys]; + } + this.props.onDrop(res); + this._dropTrigger = true; + } + + onDragEnd(e, treeNode) { + this.setState({ + dragOverNodeKey: '', + }); + this.props.onDragEnd({ event: e, node: treeNode }); + } + + onExpand(treeNode) { + const expanded = !treeNode.props.expanded; + const controlled = 'expandedKeys' in this.props; + const expandedKeys = [...this.state.expandedKeys]; + const index = expandedKeys.indexOf(treeNode.props.eventKey); + if (expanded && index === -1) { + expandedKeys.push(treeNode.props.eventKey); + } else if (!expanded && index > -1) { + expandedKeys.splice(index, 1); + } + if (!controlled) { + this.setState({ expandedKeys }); + } + this.props.onExpand(expandedKeys, { node: treeNode, expanded }); + + // after data loaded, need set new expandedKeys + if (expanded && this.props.loadData) { + return this.props.loadData(treeNode).then(() => { + if (!controlled) { + this.setState({ expandedKeys }); + } + }); + } + } + + onCheck(treeNode) { + let checked = !treeNode.props.checked; + if (treeNode.props.halfChecked) { + checked = true; + } + const key = treeNode.props.eventKey; + let checkedKeys = [...this.state.checkedKeys]; + const index = checkedKeys.indexOf(key); + + const newSt = { + event: 'check', + node: treeNode, + checked, + }; + + if (this.props.checkStrictly && ('checkedKeys' in this.props)) { + if (checked && index === -1) { + checkedKeys.push(key); + } + if (!checked && index > -1) { + checkedKeys.splice(index, 1); + } + newSt.checkedNodes = []; + loopAllChildren(this.props.children, (item, ind, pos, keyOrPos) => { + if (checkedKeys.indexOf(keyOrPos) !== -1) { + newSt.checkedNodes.push(item); + } + }); + this.props.onCheck(getStrictlyValue(checkedKeys, this.props.checkedKeys.halfChecked), newSt); + } else { + if (checked && index === -1) { + this.treeNodesStates[treeNode.props.pos].checked = true; + const checkedPositions = []; + Object.keys(this.treeNodesStates).forEach(i => { + if (this.treeNodesStates[i].checked) { + checkedPositions.push(i); + } + }); + handleCheckState(this.treeNodesStates, filterParentPosition(checkedPositions), true); + } + if (!checked) { + this.treeNodesStates[treeNode.props.pos].checked = false; + this.treeNodesStates[treeNode.props.pos].halfChecked = false; + handleCheckState(this.treeNodesStates, [treeNode.props.pos], false); + } + const checkKeys = getCheck(this.treeNodesStates); + newSt.checkedNodes = checkKeys.checkedNodes; + newSt.checkedNodesPositions = checkKeys.checkedNodesPositions; + newSt.halfCheckedKeys = checkKeys.halfCheckedKeys; + this.checkKeys = checkKeys; + + this._checkedKeys = checkedKeys = checkKeys.checkedKeys; + if (!('checkedKeys' in this.props)) { + this.setState({ + checkedKeys, + }); + } + this.props.onCheck(checkedKeys, newSt); + } + } + + onSelect(treeNode) { + const props = this.props; + const selectedKeys = [...this.state.selectedKeys]; + const eventKey = treeNode.props.eventKey; + const index = selectedKeys.indexOf(eventKey); + let selected; + if (index !== -1) { + selected = false; + selectedKeys.splice(index, 1); + } else { + selected = true; + if (!props.multiple) { + selectedKeys.length = 0; + } + selectedKeys.push(eventKey); + } + const selectedNodes = []; + if (selectedKeys.length) { + loopAllChildren(this.props.children, (item) => { + if (selectedKeys.indexOf(item.key) !== -1) { + selectedNodes.push(item); + } + }); + } + const newSt = { + event: 'select', + node: treeNode, + selected, + selectedNodes, + }; + if (!('selectedKeys' in this.props)) { + this.setState({ + selectedKeys, + }); + } + props.onSelect(selectedKeys, newSt); + } + + onMouseEnter(e, treeNode) { + this.props.onMouseEnter({ event: e, node: treeNode }); + } + + onMouseLeave(e, treeNode) { + this.props.onMouseLeave({ event: e, node: treeNode }); + } + + onContextMenu(e, treeNode) { + const selectedKeys = [...this.state.selectedKeys]; + const eventKey = treeNode.props.eventKey; + if (this.contextmenuKeys.indexOf(eventKey) === -1) { + this.contextmenuKeys.push(eventKey); + } + this.contextmenuKeys.forEach((key) => { + const index = selectedKeys.indexOf(key); + if (index !== -1) { + selectedKeys.splice(index, 1); + } + }); + if (selectedKeys.indexOf(eventKey) === -1) { + selectedKeys.push(eventKey); + } + this.setState({ + selectedKeys, + }); + this.props.onRightClick({ event: e, node: treeNode }); + } + + // all keyboard events callbacks run from here at first + onKeyDown(e) { + e.preventDefault(); + } + + getFilterExpandedKeys(props, expandKeyProp, expandAll) { + const keys = props[expandKeyProp]; + if (!expandAll && !props.autoExpandParent) { + return keys || []; + } + const expandedPositionArr = []; + if (props.autoExpandParent) { + loopAllChildren(props.children, (item, index, pos, newKey) => { + if (keys.indexOf(newKey) > -1) { + expandedPositionArr.push(pos); + } + }); + } + const filterExpandedKeys = []; + loopAllChildren(props.children, (item, index, pos, newKey) => { + if (expandAll) { + filterExpandedKeys.push(newKey); + } else if (props.autoExpandParent) { + expandedPositionArr.forEach(p => { + if ((p.split('-').length > pos.split('-').length + && isInclude(pos.split('-'), p.split('-')) || pos === p) + && filterExpandedKeys.indexOf(newKey) === -1) { + filterExpandedKeys.push(newKey); + } + }); + } + }); + return filterExpandedKeys.length ? filterExpandedKeys : keys; + } + + getDefaultExpandedKeys(props, willReceiveProps) { + let expandedKeys = willReceiveProps ? undefined : + this.getFilterExpandedKeys(props, 'defaultExpandedKeys', + props.defaultExpandedKeys.length ? false : props.defaultExpandAll); + if ('expandedKeys' in props) { + expandedKeys = (props.autoExpandParent ? + this.getFilterExpandedKeys(props, 'expandedKeys', false) : + props.expandedKeys) || []; + } + return expandedKeys; + } + + getDefaultCheckedKeys(props, willReceiveProps) { + let checkedKeys = willReceiveProps ? undefined : props.defaultCheckedKeys; + if ('checkedKeys' in props) { + checkedKeys = props.checkedKeys || []; + if (props.checkStrictly) { + if (props.checkedKeys.checked) { + checkedKeys = props.checkedKeys.checked; + } else if (!Array.isArray(props.checkedKeys)) { + checkedKeys = []; + } + } + } + return checkedKeys; + } + + getDefaultSelectedKeys(props, willReceiveProps) { + const getKeys = (keys) => { + if (props.multiple) { + return [...keys]; + } + if (keys.length) { + return [keys[0]]; + } + return keys; + }; + let selectedKeys = willReceiveProps ? undefined : getKeys(props.defaultSelectedKeys); + if ('selectedKeys' in props) { + selectedKeys = getKeys(props.selectedKeys); + } + return selectedKeys; + } + + getRawExpandedKeys() { + if (!this._rawExpandedKeys && ('expandedKeys' in this.props)) { + this._rawExpandedKeys = [...this.state.expandedKeys]; + } + } + + getOpenTransitionName() { + const props = this.props; + let transitionName = props.openTransitionName; + const animationName = props.openAnimation; + if (!transitionName && typeof animationName === 'string') { + transitionName = `${props.prefixCls}-open-${animationName}`; + } + return transitionName; + } + + getDragNodes(treeNode) { + const dragNodesKeys = []; + const tPArr = treeNode.props.pos.split('-'); + loopAllChildren(this.props.children, (item, index, pos, newKey) => { + const pArr = pos.split('-'); + if (treeNode.props.pos === pos || tPArr.length < pArr.length && isInclude(tPArr, pArr)) { + dragNodesKeys.push(newKey); + } + }); + return dragNodesKeys; + } + + getExpandedKeys(treeNode, expand) { + const key = treeNode.props.eventKey; + const expandedKeys = this.state.expandedKeys; + const expandedIndex = expandedKeys.indexOf(key); + let exKeys; + if (expandedIndex > -1 && !expand) { + exKeys = [...expandedKeys]; + exKeys.splice(expandedIndex, 1); + return exKeys; + } + if (expand && expandedKeys.indexOf(key) === -1) { + return expandedKeys.concat([key]); + } + } + + filterTreeNode(treeNode) { + const filterTreeNode = this.props.filterTreeNode; + if (typeof filterTreeNode !== 'function' || treeNode.props.disabled) { + return false; + } + return filterTreeNode.call(this, treeNode); + } + + renderTreeNode(child, index, level = 0) { + const pos = `${level}-${index}`; + const key = child.key || pos; + const state = this.state; + const props = this.props; + + // prefer to child's own selectable property if passed + let selectable = props.selectable; + if (child.props.hasOwnProperty('selectable')) { + selectable = child.props.selectable; + } + + const cloneProps = { + ref: `treeNode-${key}`, + root: this, + eventKey: key, + pos, + selectable, + loadData: props.loadData, + onMouseEnter: props.onMouseEnter, + onMouseLeave: props.onMouseLeave, + onRightClick: props.onRightClick, + prefixCls: props.prefixCls, + showLine: props.showLine, + showIcon: props.showIcon, + draggable: props.draggable, + dragOver: state.dragOverNodeKey === key && this.dropPosition === 0, + dragOverGapTop: state.dragOverNodeKey === key && this.dropPosition === -1, + dragOverGapBottom: state.dragOverNodeKey === key && this.dropPosition === 1, + _dropTrigger: this._dropTrigger, + expanded: state.expandedKeys.indexOf(key) !== -1, + selected: state.selectedKeys.indexOf(key) !== -1, + openTransitionName: this.getOpenTransitionName(), + openAnimation: props.openAnimation, + filterTreeNode: this.filterTreeNode.bind(this), + }; + if (props.checkable) { + cloneProps.checkable = props.checkable; + if (props.checkStrictly) { + if (state.checkedKeys) { + cloneProps.checked = state.checkedKeys.indexOf(key) !== -1 || false; + } + if (props.checkedKeys.halfChecked) { + cloneProps.halfChecked = props.checkedKeys.halfChecked.indexOf(key) !== -1 || false; + } else { + cloneProps.halfChecked = false; + } + } else { + if (this.checkedKeys) { + cloneProps.checked = this.checkedKeys.indexOf(key) !== -1 || false; + } + cloneProps.halfChecked = this.halfCheckedKeys.indexOf(key) !== -1; + } + } + if (this.treeNodesStates && this.treeNodesStates[pos]) { + assign(cloneProps, this.treeNodesStates[pos].siblingPosition); + } + return React.cloneElement(child, cloneProps); + } + + render() { + const props = this.props; + const domProps = { + className: classNames(props.className, props.prefixCls), + role: 'tree-node', + }; + if (props.focusable) { + domProps.tabIndex = '0'; + domProps.onKeyDown = this.onKeyDown; + } + const getTreeNodesStates = () => { + this.treeNodesStates = {}; + loopAllChildren(props.children, (item, index, pos, keyOrPos, siblingPosition) => { + this.treeNodesStates[pos] = { + siblingPosition, + }; + }); + }; + if (props.showLine && !props.checkable) { + getTreeNodesStates(); + } + if (props.checkable && (this.checkedKeysChange || props.loadData)) { + if (props.checkStrictly) { + getTreeNodesStates(); + } else if (props._treeNodesStates) { + this.treeNodesStates = props._treeNodesStates.treeNodesStates; + this.halfCheckedKeys = props._treeNodesStates.halfCheckedKeys; + this.checkedKeys = props._treeNodesStates.checkedKeys; + } else { + const checkedKeys = this.state.checkedKeys; + let checkKeys; + if (!props.loadData && this.checkKeys && this._checkedKeys && + arraysEqual(this._checkedKeys, checkedKeys)) { + // if checkedKeys the same as _checkedKeys from onCheck, use _checkedKeys. + checkKeys = this.checkKeys; + } else { + const checkedPositions = []; + this.treeNodesStates = {}; + loopAllChildren(props.children, (item, index, pos, keyOrPos, siblingPosition) => { + this.treeNodesStates[pos] = { + node: item, + key: keyOrPos, + checked: false, + halfChecked: false, + siblingPosition, + }; + if (checkedKeys.indexOf(keyOrPos) !== -1) { + this.treeNodesStates[pos].checked = true; + checkedPositions.push(pos); + } + }); + // if the parent node's key exists, it all children node will be checked + handleCheckState(this.treeNodesStates, filterParentPosition(checkedPositions), true); + checkKeys = getCheck(this.treeNodesStates); + } + this.halfCheckedKeys = checkKeys.halfCheckedKeys; + this.checkedKeys = checkKeys.checkedKeys; + } + } + + return ( +
      + {React.Children.map(props.children, this.renderTreeNode, this)} +
    + ); + } +} + +Tree.propTypes = { + prefixCls: PropTypes.string, + children: PropTypes.any, + showLine: PropTypes.bool, + showIcon: PropTypes.bool, + selectable: PropTypes.bool, + multiple: PropTypes.bool, + checkable: PropTypes.oneOfType([ + PropTypes.bool, + PropTypes.node, + ]), + _treeNodesStates: PropTypes.object, + checkStrictly: PropTypes.bool, + draggable: PropTypes.bool, + autoExpandParent: PropTypes.bool, + defaultExpandAll: PropTypes.bool, + defaultExpandedKeys: PropTypes.arrayOf(PropTypes.string), + expandedKeys: PropTypes.arrayOf(PropTypes.string), + defaultCheckedKeys: PropTypes.arrayOf(PropTypes.string), + checkedKeys: PropTypes.oneOfType([ + PropTypes.arrayOf(PropTypes.string), + PropTypes.object, + ]), + defaultSelectedKeys: PropTypes.arrayOf(PropTypes.string), + selectedKeys: PropTypes.arrayOf(PropTypes.string), + onExpand: PropTypes.func, + onCheck: PropTypes.func, + onSelect: PropTypes.func, + loadData: PropTypes.func, + onMouseEnter: PropTypes.func, + onMouseLeave: PropTypes.func, + onRightClick: PropTypes.func, + onDragStart: PropTypes.func, + onDragEnter: PropTypes.func, + onDragOver: PropTypes.func, + onDragLeave: PropTypes.func, + onDrop: PropTypes.func, + onDragEnd: PropTypes.func, + filterTreeNode: PropTypes.func, + openTransitionName: PropTypes.string, + openAnimation: PropTypes.oneOfType([PropTypes.string, PropTypes.object]), +}; + +Tree.defaultProps = { + prefixCls: 'rc-tree', + showLine: false, + showIcon: true, + selectable: true, + multiple: false, + checkable: false, + checkStrictly: false, + draggable: false, + autoExpandParent: true, + defaultExpandAll: false, + defaultExpandedKeys: [], + defaultCheckedKeys: [], + defaultSelectedKeys: [], + onExpand: noop, + onCheck: noop, + onSelect: noop, + onDragStart: noop, + onDragEnter: noop, + onDragOver: noop, + onDragLeave: noop, + onDrop: noop, + onDragEnd: noop, +}; + +export default Tree; diff --git a/src/Tree.scss b/src/Tree.scss new file mode 100644 index 0000000..141d664 --- /dev/null +++ b/src/Tree.scss @@ -0,0 +1,320 @@ +@import "../node_modules/tinper-bee-core/scss/minxin-variables"; +@import "../node_modules/tinper-bee-core/scss/minxin-mixins"; +@import "../node_modules/bee-checkbox/src/Checkbox"; + + +//css 分割线 +.u-tree li span.u-tree-checkbox { + margin: 2px 4px 0 0; +} +.u-tree-checkbox { + white-space: nowrap; + cursor: pointer; + outline: none; + display: inline-block; + line-height: 1; + position: relative; + vertical-align: middle; +} +.u-tree-checkbox-checked .u-tree-checkbox-inner, .u-tree-checkbox-indeterminate .u-tree-checkbox-inner { + background-color: #108ee9; + border-color: #108ee9; +} +.u-tree-checkbox-inner { + position: relative; + top: 0; + left: 0; + display: inline-block; + width: 14px; + height: 14px; + border: 1px solid #d9d9d9; + border-radius: 3px; + background-color: #fff; + -webkit-transition: all .3s; + transition: all .3s; +} +.u-tree-checkbox-checked .u-tree-checkbox-inner, .u-tree-checkbox-indeterminate .u-tree-checkbox-inner { + background-color: #108ee9; + border-color: #108ee9; +} +u-tree-checkbox-disabled .u-tree-checkbox-inner { + border-color: #d9d9d9 !important; + background-color: #f3f3f3; +} +.u-tree-checkbox-indeterminate .u-tree-checkbox-inner:after { + content: ' '; + -webkit-transform: scale(1); + -ms-transform: scale(1); + transform: scale(1); + position: absolute; + left: 2px; + top: 5px; + width: 8px; + height: 1px; +} +.u-tree-checkbox-disabled .u-tree-checkbox-inner { + border-color: #d9d9d9 !important; + background-color: #f3f3f3; +} +.u-tree-checkbox-disabled.u-tree-checkbox-checked .u-tree-checkbox-inner:after { + -webkit-animation-name: none; + animation-name: none; + border-color: #ccc; +} +.u-tree-checkbox-disabled .u-tree-checkbox-inner:after { + -webkit-animation-name: none; + animation-name: none; + border-color: #f3f3f3; +} +.u-tree-checkbox-checked .u-tree-checkbox-inner:after { + -webkit-transform: rotate(45deg) scale(1); + -ms-transform: rotate(45deg) scale(1); + transform: rotate(45deg) scale(1); + position: absolute; + left: 4px; + top: 1px; + display: table; + width: 5px; + height: 8px; + border: 2px solid #fff; + border-top: 0; + border-left: 0; + content: ' '; + -webkit-transition: all 0.2s cubic-bezier(0.12, 0.4, 0.29, 1.46) 0.1s; + transition: all 0.2s cubic-bezier(0.12, 0.4, 0.29, 1.46) 0.1s; +} +.u-tree-checkbox-inner:after { + -webkit-transform: rotate(45deg) scale(0); + -ms-transform: rotate(45deg) scale(0); + transform: rotate(45deg) scale(0); + position: absolute; + left: 4px; + top: 1px; + display: table; + width: 5px; + height: 8px; + border: 2px solid #fff; + border-top: 0; + border-left: 0; + content: ' '; + -webkit-transition: all 0.1s cubic-bezier(0.71, -0.46, 0.88, 0.6); + transition: all 0.1s cubic-bezier(0.71, -0.46, 0.88, 0.6); +} +.u-tree { + margin: 0; + padding: 5px; + font-size: 12px; +} +.u-tree li { + padding: 0; + margin: 7px 0; + list-style: none; + white-space: nowrap; + outline: 0; +} +.u-tree li a[draggable], +.u-tree li a[draggable="true"] { + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + /* Required to make elements draggable in old WebKit */ + -khtml-user-drag: element; + -webkit-user-drag: element; +} +.u-tree li.drag-over > a[draggable] { + background-color: #108ee9; + color: white; + opacity: 0.8; +} +.u-tree li.drag-over-gap-top > a[draggable] { + border-top: 2px #108ee9 solid; +} +.u-tree li.drag-over-gap-bottom > a[draggable] { + border-bottom: 2px #108ee9 solid; +} +.u-tree li.filter-node > a { + color: #f50 !important; + font-weight: bold!important; +} +.u-tree li ul { + margin: 0; + padding: 0 0 0 18px; +} +.u-tree li a { + display: inline-block; + padding: 1px 5px; + border-radius: 2px; + margin: 0; + cursor: pointer; + text-decoration: none; + vertical-align: top; + color: #666; + -webkit-transition: all 0.3s ease; + transition: all 0.3s ease; +} +.u-tree li a:hover { + background-color: #e7f4fd; +} +.u-tree li a.u-tree-node-selected { + background-color: #cfe8fb; +} +.u-tree li span.u-checkbox { + margin: 2px 4px 0 0; +} +.u-tree li span.u-tree-switcher, +.u-tree li span.u-tree-iconEle { + margin: 0; + width: 16px; + height: 16px; + line-height: 16px; + display: inline-block; + vertical-align: middle; + border: 0 none; + cursor: pointer; + outline: none; +} +.u-tree li span.u-tree-icon_loading:after { + display: inline-block; + font-family: 'uf'; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + content: "\E6AE"; + -webkit-animation: loadingCircle 1s infinite linear; + animation: loadingCircle 1s infinite linear; + color: #108ee9; +} +.u-tree li span.u-tree-switcher.u-tree-switcher-noop { + cursor: auto; +} +.u-tree li span.u-tree-switcher.u-tree-roots_open, +.u-tree li span.u-tree-switcher.u-tree-center_open, +.u-tree li span.u-tree-switcher.u-tree-bottom_open, +.u-tree li span.u-tree-switcher.u-tree-noline_open { + position: relative; +} +.u-tree li span.u-tree-switcher.u-tree-roots_open:after, +.u-tree li span.u-tree-switcher.u-tree-center_open:after, +.u-tree li span.u-tree-switcher.u-tree-bottom_open:after, +.u-tree li span.u-tree-switcher.u-tree-noline_open:after { + font-size: 18px; + font-size: 7px \9; + -webkit-transform: scale(0.58333333) rotate(0deg); + -ms-transform: scale(0.58333333) rotate(0deg); + transform: scale(0.58333333) rotate(0deg); + /* IE6-IE8 */ + -ms-filter: "progid:DXImageTransform.Microsoft.Matrix(sizingMethod='auto expand', M11=1, M12=0, M21=0, M22=1)"; + zoom: 1; + display: inline-block; + font-family: 'uf'; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + content: "\e639"; + font-weight: bold; + position: absolute; + top: 0; + right: 4px; + color: #666; + -webkit-transition: -webkit-transform .3s ease; + transition: -webkit-transform .3s ease; + transition: transform .3s ease; + transition: transform .3s ease, -webkit-transform .3s ease; +} +:root .u-tree li span.u-tree-switcher.u-tree-roots_open:after, +:root .u-tree li span.u-tree-switcher.u-tree-center_open:after, +:root .u-tree li span.u-tree-switcher.u-tree-bottom_open:after, +:root .u-tree li span.u-tree-switcher.u-tree-noline_open:after { + -webkit-filter: none; + filter: none; +} +:root .u-tree li span.u-tree-switcher.u-tree-roots_open:after, +:root .u-tree li span.u-tree-switcher.u-tree-center_open:after, +:root .u-tree li span.u-tree-switcher.u-tree-bottom_open:after, +:root .u-tree li span.u-tree-switcher.u-tree-noline_open:after { + font-size: 18px; +} +.u-tree li span.u-tree-switcher.u-tree-roots_close, +.u-tree li span.u-tree-switcher.u-tree-center_close, +.u-tree li span.u-tree-switcher.u-tree-bottom_close, +.u-tree li span.u-tree-switcher.u-tree-noline_close { + position: relative; + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=3)"; +} +.u-tree li span.u-tree-switcher.u-tree-roots_close:after, +.u-tree li span.u-tree-switcher.u-tree-center_close:after, +.u-tree li span.u-tree-switcher.u-tree-bottom_close:after, +.u-tree li span.u-tree-switcher.u-tree-noline_close:after { + font-size: 18px; + font-size: 7px \9; + -webkit-transform: scale(0.58333333) rotate(0deg); + -ms-transform: scale(0.58333333) rotate(0deg); + transform: scale(0.58333333) rotate(0deg); + /* IE6-IE8 */ + -ms-filter: "progid:DXImageTransform.Microsoft.Matrix(sizingMethod='auto expand', M11=1, M12=0, M21=0, M22=1)"; + zoom: 1; + display: inline-block; + font-family: 'uf'; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + content: "\e639"; + font-weight: bold; + position: absolute; + top: 0; + right: 4px; + color: #666; + -webkit-transition: -webkit-transform .3s ease; + transition: -webkit-transform .3s ease; + transition: transform .3s ease; + transition: transform .3s ease, -webkit-transform .3s ease; +} +:root .u-tree li span.u-tree-switcher.u-tree-roots_close:after, +:root .u-tree li span.u-tree-switcher.u-tree-center_close:after, +:root .u-tree li span.u-tree-switcher.u-tree-bottom_close:after, +:root .u-tree li span.u-tree-switcher.u-tree-noline_close:after { + -webkit-filter: none; + filter: none; +} +:root .u-tree li span.u-tree-switcher.u-tree-roots_close:after, +:root .u-tree li span.u-tree-switcher.u-tree-center_close:after, +:root .u-tree li span.u-tree-switcher.u-tree-bottom_close:after, +:root .u-tree li span.u-tree-switcher.u-tree-noline_close:after { + font-size: 18px; +} +.u-tree li span.u-tree-switcher.u-tree-roots_close:after, +.u-tree li span.u-tree-switcher.u-tree-center_close:after, +.u-tree li span.u-tree-switcher.u-tree-bottom_close:after, +.u-tree li span.u-tree-switcher.u-tree-noline_close:after { + -webkit-transform: rotate(270deg) scale(0.6); + -ms-transform: rotate(270deg) scale(0.6); + transform: rotate(270deg) scale(0.6); +} +.u-tree-child-tree { + display: none; +} +.u-tree-child-tree-open { + display: block; +} +.u-tree-treenode-disabled > span, +.u-tree-treenode-disabled > a, +.u-tree-treenode-disabled > a span { + color: #ccc; + cursor: not-allowed; +} +.u-tree-icon__open { + margin-right: 2px; + vertical-align: top; +} +.u-tree-icon__close { + margin-right: 2px; + vertical-align: top; +} +.u-motion-collapse { + overflow: hidden; +} +.u-motion-collapse-active { + -webkit-transition: height 0.2s cubic-bezier(0.215, 0.61, 0.355, 1); + transition: height 0.2s cubic-bezier(0.215, 0.61, 0.355, 1); +} diff --git a/src/TreeNode.js b/src/TreeNode.js new file mode 100644 index 0000000..aa140cf --- /dev/null +++ b/src/TreeNode.js @@ -0,0 +1,383 @@ +import React, { PropTypes } from 'react'; +import assign from 'object-assign'; +import classNames from 'classnames'; +import Animate from 'rc-animate'; +import { browser } from './util'; + +const browserUa = typeof window !== 'undefined' ? browser(window.navigator) : ''; +const ieOrEdge = /.*(IE|Edge).+/.test(browserUa); +// const uaArray = browserUa.split(' '); +// const gtIE8 = uaArray.length !== 2 || uaArray[0].indexOf('IE') === -1 || Number(uaArray[1]) > 8; + +const defaultTitle = '---'; + +class TreeNode extends React.Component { + constructor(props) { + super(props); + [ + 'onExpand', + 'onCheck', + 'onContextMenu', + 'onMouseEnter', + 'onMouseLeave', + 'onDragStart', + 'onDragEnter', + 'onDragOver', + 'onDragLeave', + 'onDrop', + 'onDragEnd', + ].forEach((m) => { + this[m] = this[m].bind(this); + }); + this.state = { + dataLoading: false, + dragNodeHighlight: false, + }; + } + + componentDidMount() { + if (!this.props.root._treeNodeInstances) { + this.props.root._treeNodeInstances = []; + } + this.props.root._treeNodeInstances.push(this); + } + // shouldComponentUpdate(nextProps) { + // if (!nextProps.expanded) { + // return false; + // } + // return true; + // } + + onCheck() { + this.props.root.onCheck(this); + } + + onSelect() { + this.props.root.onSelect(this); + } + + onMouseEnter(e) { + e.preventDefault(); + this.props.root.onMouseEnter(e, this); + } + + onMouseLeave(e) { + e.preventDefault(); + this.props.root.onMouseLeave(e, this); + } + + onContextMenu(e) { + e.preventDefault(); + this.props.root.onContextMenu(e, this); + } + + onDragStart(e) { + // console.log('dragstart', this.props.eventKey, e); + // e.preventDefault(); + e.stopPropagation(); + this.setState({ + dragNodeHighlight: true, + }); + this.props.root.onDragStart(e, this); + try { + // ie throw error + // firefox-need-it + e.dataTransfer.setData('text/plain', ''); + } finally { + // empty + } + } + + onDragEnter(e) { + e.preventDefault(); + e.stopPropagation(); + this.props.root.onDragEnter(e, this); + } + + onDragOver(e) { + // todo disabled + e.preventDefault(); + e.stopPropagation(); + this.props.root.onDragOver(e, this); + return false; + } + + onDragLeave(e) { + e.stopPropagation(); + this.props.root.onDragLeave(e, this); + } + + onDrop(e) { + e.preventDefault(); + e.stopPropagation(); + this.setState({ + dragNodeHighlight: false, + }); + this.props.root.onDrop(e, this); + } + + onDragEnd(e) { + e.stopPropagation(); + this.setState({ + dragNodeHighlight: false, + }); + this.props.root.onDragEnd(e, this); + } + + onExpand() { + const callbackPromise = this.props.root.onExpand(this); + if (callbackPromise && typeof callbackPromise === 'object') { + const setLoading = (dataLoading) => { + this.setState({ dataLoading }); + }; + setLoading(true); + callbackPromise.then(() => { + setLoading(false); + }, () => { + setLoading(false); + }); + } + } + + // keyboard event support + onKeyDown(e) { + e.preventDefault(); + } + + renderSwitcher(props, expandedState) { + const prefixCls = props.prefixCls; + const switcherCls = { + [`${prefixCls}-switcher`]: true, + }; + if (!props.showLine) { + switcherCls[`${prefixCls}-noline_${expandedState}`] = true; + } else if (props.pos === '0-0') { + switcherCls[`${prefixCls}-roots_${expandedState}`] = true; + } else { + switcherCls[`${prefixCls}-center_${expandedState}`] = !props.last; + switcherCls[`${prefixCls}-bottom_${expandedState}`] = props.last; + } + if (props.disabled) { + switcherCls[`${prefixCls}-switcher-disabled`] = true; + return ; + } + return ; + } + + renderCheckbox(props) { + const prefixCls = props.prefixCls; + const checkboxCls = { + [`${prefixCls}-checkbox`]: true, + }; + if (props.checked) { + checkboxCls[`${prefixCls}-checkbox-checked`] = true; + } else if (props.halfChecked) { + checkboxCls[`${prefixCls}-checkbox-indeterminate`] = true; + } + let customEle = null; + if (typeof props.checkable !== 'boolean') { + customEle = props.checkable; + } + if (props.disabled || props.disableCheckbox) { + checkboxCls[`${prefixCls}-checkbox-disabled`] = true; + return {customEle}; + } + return ( + {customEle}); + } + + renderChildren(props) { + const renderFirst = this.renderFirst; + this.renderFirst = 1; + let transitionAppear = true; + if (!renderFirst && props.expanded) { + transitionAppear = false; + } + const children = props.children; + let newChildren = children; + if (children && + (children.type === TreeNode || + Array.isArray(children) && + children.every((item) => { + return item.type === TreeNode; + }))) { + const cls = { + [`${props.prefixCls}-child-tree`]: true, + [`${props.prefixCls}-child-tree-open`]: props.expanded, + }; + if (props.showLine) { + cls[`${props.prefixCls}-line`] = !props.last; + } + const animProps = {}; + if (props.openTransitionName) { + animProps.transitionName = props.openTransitionName; + } else if (typeof props.openAnimation === 'object') { + animProps.animation = assign({}, props.openAnimation); + if (!transitionAppear) { + delete animProps.animation.appear; + } + } + newChildren = ( + + {!props.expanded ? null :
      + {React.Children.map(children, (item, index) => { + return props.root.renderTreeNode(item, index, props.pos); + }, props.root)} +
    } +
    + ); + } + return newChildren; + } + + render() { + const props = this.props; + const prefixCls = props.prefixCls; + const expandedState = props.expanded ? 'open' : 'close'; + let iconState = expandedState; + + let canRenderSwitcher = true; + const content = props.title; + let newChildren = this.renderChildren(props); + if (!newChildren || newChildren === props.children) { + // content = newChildren; + newChildren = null; + if (!props.loadData || props.isLeaf) { + canRenderSwitcher = false; + iconState = 'docu'; + } + } + // For performance, does't render children into dom when `!props.expanded` (move to Animate) + // if (!props.expanded) { + // newChildren = null; + // } + + const iconEleCls = { + [`${prefixCls}-iconEle`]: true, + [`${prefixCls}-icon_loading`]: this.state.dataLoading, + [`${prefixCls}-icon__${iconState}`]: true, + }; + + const selectHandle = () => { + const icon = (props.showIcon || props.loadData && this.state.dataLoading) ? + : null; + const title = {content}; + const wrap = `${prefixCls}-node-content-wrapper`; + const domProps = { + className: `${wrap} ${wrap}-${iconState === expandedState ? iconState : 'normal'}`, + }; + if (!props.disabled) { + if (props.selected || !props._dropTrigger && this.state.dragNodeHighlight) { + domProps.className += ` ${prefixCls}-node-selected`; + } + domProps.onClick = (e) => { + e.preventDefault(); + if (props.selectable) { + this.onSelect(); + } + // not fire check event + // if (props.checkable) { + // this.onCheck(); + // } + }; + if (props.onRightClick) { + domProps.onContextMenu = this.onContextMenu; + } + if (props.onMouseEnter) { + domProps.onMouseEnter = this.onMouseEnter; + } + if (props.onMouseLeave) { + domProps.onMouseLeave = this.onMouseLeave; + } + if (props.draggable) { + domProps.className += ' draggable'; + if (ieOrEdge) { + // ie bug! + domProps.href = '#'; + } + domProps.draggable = true; + domProps['aria-grabbed'] = true; + domProps.onDragStart = this.onDragStart; + } + } + return ( + + {icon}{title} + + ); + }; + + const liProps = {}; + if (props.draggable) { + liProps.onDragEnter = this.onDragEnter; + liProps.onDragOver = this.onDragOver; + liProps.onDragLeave = this.onDragLeave; + liProps.onDrop = this.onDrop; + liProps.onDragEnd = this.onDragEnd; + } + + let disabledCls = ''; + let dragOverCls = ''; + if (props.disabled) { + disabledCls = `${prefixCls}-treenode-disabled`; + } else if (props.dragOver) { + dragOverCls = 'drag-over'; + } else if (props.dragOverGapTop) { + dragOverCls = 'drag-over-gap-top'; + } else if (props.dragOverGapBottom) { + dragOverCls = 'drag-over-gap-bottom'; + } + + const filterCls = props.filterTreeNode(this) ? 'filter-node' : ''; + + const noopSwitcher = () => { + const cls = { + [`${prefixCls}-switcher`]: true, + [`${prefixCls}-switcher-noop`]: true, + }; + if (props.showLine) { + cls[`${prefixCls}-center_docu`] = !props.last; + cls[`${prefixCls}-bottom_docu`] = props.last; + } else { + cls[`${prefixCls}-noline_docu`] = true; + } + return ; + }; + + return ( +
  • + {canRenderSwitcher ? this.renderSwitcher(props, expandedState) : noopSwitcher()} + {props.checkable ? this.renderCheckbox(props) : null} + {selectHandle()} + {newChildren} +
  • + ); + } +} + +TreeNode.isTreeNode = 1; + +TreeNode.propTypes = { + prefixCls: PropTypes.string, + disabled: PropTypes.bool, + disableCheckbox: PropTypes.bool, + expanded: PropTypes.bool, + isLeaf: PropTypes.bool, + root: PropTypes.object, + onSelect: PropTypes.func, +}; + +TreeNode.defaultProps = { + title: defaultTitle, +}; + +export default TreeNode; diff --git a/src/Treecopy.scss b/src/Treecopy.scss new file mode 100644 index 0000000..4a2d36b --- /dev/null +++ b/src/Treecopy.scss @@ -0,0 +1,363 @@ + +$treePrefixCls: 'u-tree'; +.#{$treePrefixCls} { + margin: 0; + padding: 5px; + li { + padding: 0; + margin: 0; + list-style: none; + white-space: nowrap; + outline: 0; + .draggable { + color: #333; + -moz-user-select: none; + -khtml-user-select: none; + -webkit-user-select: none; + user-select: none; + /* Required to make elements draggable in old WebKit */ + -khtml-user-drag: element; + -webkit-user-drag: element; + } + &.drag-over { + > .draggable { + background-color: #316ac5; + color: white; + border: 1px #316ac5 solid; + opacity: 0.8; + } + } + &.drag-over-gap-top { + > .draggable { + border-top: 2px blue solid; + } + } + &.drag-over-gap-bottom { + > .draggable { + border-bottom: 2px blue solid; + } + } + &.filter-node { + > .#{$treePrefixCls}-node-content-wrapper { + color: #a60000!important; + font-weight: bold!important; + } + } + ul { + margin: 0; + padding: 0 0 0 18px; + &.#{$treePrefixCls}-line { + background: url("https://t.alipayobjects.com/images/T13BtfXl0mXXXXXXXX.gif") 0 0 repeat-y; + } + } + .#{$treePrefixCls}-node-content-wrapper { + display: inline-block; + padding: 1px 3px 0 0; + margin: 0; + cursor: pointer; + height: 17px; + text-decoration: none; + vertical-align: top; + } + span { + &.#{$treePrefixCls}-switcher, + &.#{$treePrefixCls}-checkbox, + &.#{$treePrefixCls}-iconEle { + line-height: 16px; + margin-right: 2px; + width: 16px; + height: 16px; + display: inline-block; + vertical-align: middle; + border: 0 none; + cursor: pointer; + outline: none; + background-color: transparent; + background-repeat: no-repeat; + background-attachment: scroll; + background-image: url("https://t.alipayobjects.com/images/T1.ANfXhXtXXXXXXXX.png"); + } + &.#{$treePrefixCls}-icon_loading { + margin-right: 2px; + vertical-align: top; + background: url(https://t.alipayobjects.com/images/rmsweb/T1YxhiXgJbXXXXXXXX.gif) no-repeat scroll 0 0 transparent; + } + &.#{$treePrefixCls}-switcher { + &.#{$treePrefixCls}-switcher-noop { + cursor: auto; + } + &.#{$treePrefixCls}-roots_open { + background-position: -93px -56px; + } + &.#{$treePrefixCls}-roots_close { + background-position: -75px -56px; + } + &.#{$treePrefixCls}-center_open { + background-position: -92px -18px; + } + &.#{$treePrefixCls}-center_close { + background-position: -74px -18px; + } + &.#{$treePrefixCls}-bottom_open { + background-position: -92px -36px; + } + &.#{$treePrefixCls}-bottom_close { + background-position: -74px -36px; + } + &.#{$treePrefixCls}-noline_open { + background-position: -92px -72px; + } + &.#{$treePrefixCls}-noline_close { + background-position: -74px -72px; + } + &.#{$treePrefixCls}-center_docu { + background-position: -56px -18px; + } + &.#{$treePrefixCls}-bottom_docu { + background-position: -56px -36px; + } + &.#{$treePrefixCls}-noline_docu { + background: none; + } + } + &.#{$treePrefixCls}-checkbox { + width: 13px; + height: 13px; + margin: 0 3px; + background-position: 0 0; + &-checked { + background-position: -14px 0; + } + &-indeterminate { + background-position: -14px -28px; + } + &-disabled { + background-position: 0 -56px; + } + &.#{$treePrefixCls}-checkbox-checked.#{$treePrefixCls}-checkbox-disabled { + background-position: -14px -56px; + } + &.#{$treePrefixCls}-checkbox-indeterminate.#{$treePrefixCls}-checkbox-disabled { + position: relative; + background: #ccc; + border-radius: 3px; + &::after { + content: ' '; + -webkit-transform: scale(1); + transform: scale(1); + position: absolute; + left: 3px; + top: 5px; + width: 5px; + height: 0; + border: 2px solid #fff; + border-top: 0; + border-left: 0; + } + } + } + } + } + &-child-tree { + display: none; + &-open { + display: block; + } + } + &-treenode-disabled { + >span, + >a, + >a span { + color: #ccc; + cursor: not-allowed; + } + } + &-node-selected { + background-color: #ffe6b0; + border: 1px #ffb951 solid; + opacity: 0.8; + } + &-icon__open { + margin-right: 2px; + background-position: -110px -16px; + vertical-align: top; + } + &-icon__close { + margin-right: 2px; + background-position: -110px 0; + vertical-align: top; + } + &-icon__docu { + margin-right: 2px; + background-position: -110px -32px; + vertical-align: top; + } +} + +$tree-prefix-cls: "ant-tree"; +$primary-color: '#108ee9'; +$highlight-color: '#f50'; +$font-size-base: '12px'; +//.antCheckboxFn($checkbox-prefix-cls: "ant-tree-checkbox"); + +.#{$tree-prefix-cls} { + margin: 0; + padding: 5px; + font-size: $font-size-base; + li { + padding: 0; + margin: 7px 0; + list-style: none; + white-space: nowrap; + outline: 0; + a[draggable], + a[draggable="true"] { + user-select: none; + /* Required to make elements draggable in old WebKit */ + -khtml-user-drag: element; + -webkit-user-drag: element; + } + &.drag-over { + > a[draggable] { + background-color: $primary-color; + color: white; + opacity: 0.8; + } + } + &.drag-over-gap-top { + > a[draggable] { + border-top: 2px $primary-color solid; + } + } + &.drag-over-gap-bottom { + > a[draggable] { + border-bottom: 2px $primary-color solid; + } + } + &.filter-node { + > a { + color: $highlight-color!important; + font-weight: bold!important; + } + } + ul { + margin: 0; + padding: 0 0 0 18px; + } + a { + display: inline-block; + padding: 1px 5px; + border-radius: 2px; + margin: 0; + cursor: pointer; + text-decoration: none; + vertical-align: top; + color: $text-color; + transition: all 0.3s ease; + &:hover { + background-color: tint($primary-color, 90%); + } + &.#{$tree-prefix-cls}-node-selected { + background-color: tint($primary-color, 80%); + } + } + span { + &.#{$tree-prefix-cls}-checkbox { + margin: 2px 4px 0 0; + } + &.#{$tree-prefix-cls}-switcher, + &.#{$tree-prefix-cls}-iconEle { + margin: 0; + width: 16px; + height: 16px; + line-height: 16px; + display: inline-block; + vertical-align: middle; + border: 0 none; + cursor: pointer; + outline: none; + } + &.#{$tree-prefix-cls}-icon_loading { + &:after { + display: inline-block; + animation: loadingCircle 1s infinite linear; + color: $primary-color; + font-family: 'uf'; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + content: "\e6ae"; + } + } + &.#{$tree-prefix-cls}-switcher { + &.#{$tree-prefix-cls}-switcher-noop { + cursor: auto; + } + &.#{$tree-prefix-cls}-roots_open, + &.#{$tree-prefix-cls}-center_open, + &.#{$tree-prefix-cls}-bottom_open, + &.#{$tree-prefix-cls}-noline_open { + //@include antTreeSwitcherIcon; + } + &.#{$tree-prefix-cls}-roots_close, + &.#{$tree-prefix-cls}-center_close, + &.#{$tree-prefix-cls}-bottom_close, + &.#{$tree-prefix-cls}-noline_close { + //@include antTreeSwitcherIcon; + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=3)"; + &:after { + transform: rotate(270deg) scale(0.6); + } + } + } + } + } + &-child-tree { + display: none; + &-open { + display: block; + } + } + &-treenode-disabled { + >span, + >a, + >a span { + color: #ccc; + cursor: not-allowed; + } + } + &-icon__open { + margin-right: 2px; + vertical-align: top; + } + &-icon__close { + margin-right: 2px; + vertical-align: top; + } +} +@mixin antTreeSwitcherIcon { + position: relative; + &:after { + font-size: 12px; + -webkit-transform: scale(0.58333333) rotate(0deg); + -ms-transform: scale(0.58333333) rotate(0deg); + transform: scale(0.58333333) rotate(0deg); + -ms-filter: "progid:DXImageTransform.Microsoft.Matrix(sizingMethod='auto expand', M11=1, M12=0, M21=0, M22=1)"; + zoom: 1; + display: inline-block; + font-family: 'anticon'; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + content: "\E606"; + font-weight: bold; + position: absolute; + top: 0; + right: 4px; + color: #666; + -webkit-transition: -webkit-transform .3s ease; + transition: -webkit-transform .3s ease; + transition: transform .3s ease; + transition: transform .3s ease, -webkit-transform .3s ease; + } +} \ No newline at end of file diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..ee7c36a --- /dev/null +++ b/src/index.js @@ -0,0 +1,108 @@ +import React,{PropTypes,Component} from 'react'; +import RcTree from './Tree'; +import TreeNode from './TreeNode'; +import animation from './openAnimation'; + +const AntTreeNodeProps ={ + disabled: PropTypes.bool, + disableCheckbox: PropTypes.bool, + title: PropTypes.oneOfType([ + PropTypes.string, + PropTypes.node + ]), + key: PropTypes.string, + isLeaf: PropTypes.bool +} + +export class AntTreeNode extends Component { + render() { + return ; + } +} + +AntTreeNode.AntTreeNodeProps = AntTreeNodeProps; + +const TreeProps ={ + showLine: PropTypes.bool, + className: PropTypes.string, + /** 是否支持多选 */ + multiple: PropTypes.bool, + /** 是否自动展开父节点 */ + autoExpandParent: PropTypes.bool, + /** checkable状态下节点选择完全受控(父子节点选中状态不再关联)*/ + checkStrictly: PropTypes.bool, + /** 是否支持选中 */ + checkable: PropTypes.bool, + /** 默认展开所有树节点 */ + defaultExpandAll: PropTypes.bool, + /** 默认展开指定的树节点 */ + defaultExpandedKeys: PropTypes.array, + /** (受控)展开指定的树节点 */ + expandedKeys: PropTypes.array, + /** (受控)选中复选框的树节点 */ + checkedKeys: PropTypes.oneOfType([ + PropTypes.array, + PropTypes.object + ]), + /** 默认选中复选框的树节点 */ + defaultCheckedKeys: PropTypes.array, + /** (受控)设置选中的树节点 */ + selectedKeys: PropTypes.array, + /** 默认选中的树节点 */ + defaultSelectedKeys: PropTypes.array, + /** 展开/收起节点时触发 */ + onExpand: PropTypes.func, + /** 点击复选框触发 */ + onCheck: PropTypes.func, + /** 点击树节点触发 */ + onSelect: PropTypes.func, + /** filter some AntTreeNodes as you need. it should return true */ + filterAntTreeNode: PropTypes.func, + /** 异步加载数据 */ + loadData: PropTypes.func, + /** 响应右键点击 */ + onRightClick: PropTypes.func, + /** 设置节点可拖拽(IE>8)*/ + draggable: PropTypes.bool, + /** 开始拖拽时调用 */ + onDragStart: PropTypes.func, + /** dragenter 触发时调用 */ + onDragEnter: PropTypes.func, + /** dragover 触发时调用 */ + onDragOver: PropTypes.func, + /** dragleave 触发时调用 */ + onDragLeave: PropTypes.func, + /** drop 触发时调用 */ + onDrop: PropTypes.func, + style: React.CSSProperties, + prefixCls: PropTypes.string, + filterTreeNode: PropTypes.func +} + +const defaultProps = { + prefixCls: 'u-tree', + checkable: false, + showIcon: false, + openAnimation: animation, +} + +class Tree extends Component{ + + render() { + const props = this.props; + let checkable = props.checkable; + return ( + ) : checkable } + > + {this.props.children} + + ); + } +} + +Tree.TreeNode = TreeNode; +Tree.TreeProps = TreeProps; +Tree.defaultProps = defaultProps; +export default Tree; diff --git a/src/openAnimation.js b/src/openAnimation.js new file mode 100644 index 0000000..15f9aa5 --- /dev/null +++ b/src/openAnimation.js @@ -0,0 +1,36 @@ +import cssAnimation from 'css-animation'; + +function animate(node, show, done) { + let height; + return cssAnimation(node, 'u-motion-collapse', { + start() { + if (!show) { + node.style.height = `${node.offsetHeight}px`; + } else { + height = node.offsetHeight; + node.style.height = 0; + } + }, + active() { + node.style.height = `${show ? height : 0}px`; + }, + end() { + node.style.height = ''; + done(); + }, + }); +} + +const animation = { + enter(node, done) { + return animate(node, true, done); + }, + leave(node, done) { + return animate(node, false, done); + }, + appear(node, done) { + return animate(node, true, done); + }, +}; + +export default animation; diff --git a/src/util.js b/src/util.js new file mode 100644 index 0000000..3d0d84a --- /dev/null +++ b/src/util.js @@ -0,0 +1,286 @@ +/* eslint no-loop-func: 0*/ + +import React from 'react'; + +export function browser(navigator) { + let tem; + const ua = navigator.userAgent; + let M = ua.match(/(opera|chrome|safari|firefox|msie|trident(?=\/))\/?\s*(\d+)/i) || []; + if (/trident/i.test(M[1])) { + tem = /\brv[ :]+(\d+)/g.exec(ua) || []; + return `IE ${tem[1] || ''}`; + } + if (M[1] === 'Chrome') { + tem = ua.match(/\b(OPR|Edge)\/(\d+)/); + if (tem) return tem.slice(1).join(' ').replace('OPR', 'Opera'); + } + M = M[2] ? [M[1], M[2]] : [navigator.appName, navigator.appVersion, '-?']; + tem = ua.match(/version\/(\d+)/i); + if (tem) { + M.splice(1, 1, tem[1]); + } + return M.join(' '); +} + +// export function getOffset(el) { +// const obj = el.getBoundingClientRect(); +// return { +// left: obj.left + document.body.scrollLeft, +// top: obj.top + document.body.scrollTop, +// width: obj.width, +// height: obj.height +// }; +// } + +// // iscroll offset +// offset = function (el) { +// var left = -el.offsetLeft, +// top = -el.offsetTop; + +// // jshint -W084 +// while (el = el.offsetParent) { +// left -= el.offsetLeft; +// top -= el.offsetTop; +// } +// // jshint +W084 + +// return { +// left: left, +// top: top +// }; +// } + +/* eslint-disable */ +export function getOffset(ele) { + let doc, win, docElem, rect; + + if (!ele.getClientRects().length) { + return { top: 0, left: 0 }; + } + + rect = ele.getBoundingClientRect(); + + if (rect.width || rect.height) { + doc = ele.ownerDocument; + win = doc.defaultView; + docElem = doc.documentElement; + + return { + top: rect.top + win.pageYOffset - docElem.clientTop, + left: rect.left + win.pageXOffset - docElem.clientLeft + }; + } + + return rect; +} +/* eslint-enable */ + +function getChildrenlength(children) { + let len = 1; + if (Array.isArray(children)) { + len = children.length; + } + return len; +} + +function getSiblingPosition(index, len, siblingPosition) { + if (len === 1) { + siblingPosition.first = true; + siblingPosition.last = true; + } else { + siblingPosition.first = index === 0; + siblingPosition.last = index === len - 1; + } + return siblingPosition; +} + +export function loopAllChildren(childs, callback, parent) { + const loop = (children, level, _parent) => { + const len = getChildrenlength(children); + React.Children.forEach(children, (item, index) => { + const pos = `${level}-${index}`; + if (item.props.children && item.type && item.type.isTreeNode) { + loop(item.props.children, pos, { node: item, pos }); + } + callback(item, index, pos, item.key || pos, getSiblingPosition(index, len, {}), _parent); + }); + }; + loop(childs, 0, parent); +} + +export function isInclude(smallArray, bigArray) { + return smallArray.every((ii, i) => { + return ii === bigArray[i]; + }); +} +// console.log(isInclude(['0', '1'], ['0', '10', '1'])); + + +// arr.length === 628, use time: ~20ms +export function filterParentPosition(arr) { + const levelObj = {}; + arr.forEach((item) => { + const posLen = item.split('-').length; + if (!levelObj[posLen]) { + levelObj[posLen] = []; + } + levelObj[posLen].push(item); + }); + const levelArr = Object.keys(levelObj).sort(); + for (let i = 0; i < levelArr.length; i++) { + if (levelArr[i + 1]) { + levelObj[levelArr[i]].forEach(ii => { + for (let j = i + 1; j < levelArr.length; j++) { + levelObj[levelArr[j]].forEach((_i, index) => { + if (isInclude(ii.split('-'), _i.split('-'))) { + levelObj[levelArr[j]][index] = null; + } + }); + levelObj[levelArr[j]] = levelObj[levelArr[j]].filter(p => p); + } + }); + } + } + let nArr = []; + levelArr.forEach(i => { + nArr = nArr.concat(levelObj[i]); + }); + return nArr; +} +// console.log(filterParentPosition( +// ['0-2', '0-3-3', '0-10', '0-10-0', '0-0-1', '0-0', '0-1-1', '0-1'] +// )); + + +function stripTail(str) { + const arr = str.match(/(.+)(-[^-]+)$/); + let st = ''; + if (arr && arr.length === 3) { + st = arr[1]; + } + return st; +} +function splitPosition(pos) { + return pos.split('-'); +} + +export function handleCheckState(obj, checkedPositionArr, checkIt) { + // console.log(stripTail('0-101-000')); + let objKeys = Object.keys(obj); + // let s = Date.now(); + objKeys.forEach((i, index) => { + const iArr = splitPosition(i); + let saved = false; + checkedPositionArr.forEach((_pos) => { + // 设置子节点,全选或全不选 + const _posArr = splitPosition(_pos); + if (iArr.length > _posArr.length && isInclude(_posArr, iArr)) { + obj[i].halfChecked = false; + obj[i].checked = checkIt; + objKeys[index] = null; + } + if (iArr[0] === _posArr[0] && iArr[1] === _posArr[1]) { + // 如果 + saved = true; + } + }); + if (!saved) { + objKeys[index] = null; + } + }); + // TODO: 循环 2470000 次耗时约 1400 ms。 性能瓶颈! + // console.log(Date.now()-s, checkedPositionArr.length * objKeys.length); + objKeys = objKeys.filter(i => i); // filter non null; + + for (let pIndex = 0; pIndex < checkedPositionArr.length; pIndex++) { + // 循环设置父节点的 选中 或 半选状态 + const loop = (__pos) => { + const _posLen = splitPosition(__pos).length; + if (_posLen <= 2) { // e.g. '0-0', '0-1' + return; + } + let sibling = 0; + let siblingChecked = 0; + const parentPosition = stripTail(__pos); + objKeys.forEach((i /* , index*/) => { + const iArr = splitPosition(i); + if (iArr.length === _posLen && isInclude(splitPosition(parentPosition), iArr)) { + sibling++; + if (obj[i].checked) { + siblingChecked++; + const _i = checkedPositionArr.indexOf(i); + if (_i > -1) { + checkedPositionArr.splice(_i, 1); + if (_i <= pIndex) { + pIndex--; + } + } + } else if (obj[i].halfChecked) { + siblingChecked += 0.5; + } + // objKeys[index] = null; + } + }); + // objKeys = objKeys.filter(i => i); // filter non null; + const parent = obj[parentPosition]; + // sibling 不会等于0 + // 全不选 - 全选 - 半选 + if (siblingChecked === 0) { + parent.checked = false; + parent.halfChecked = false; + } else if (siblingChecked === sibling) { + parent.checked = true; + parent.halfChecked = false; + } else { + parent.halfChecked = true; + parent.checked = false; + } + loop(parentPosition); + }; + loop(checkedPositionArr[pIndex], pIndex); + } + // console.log(Date.now()-s, objKeys.length, checkIt); +} + +export function getCheck(treeNodesStates) { + const halfCheckedKeys = []; + const checkedKeys = []; + const checkedNodes = []; + const checkedNodesPositions = []; + Object.keys(treeNodesStates).forEach((item) => { + const itemObj = treeNodesStates[item]; + if (itemObj.checked) { + checkedKeys.push(itemObj.key); + checkedNodes.push(itemObj.node); + checkedNodesPositions.push({ node: itemObj.node, pos: item }); + } else if (itemObj.halfChecked) { + halfCheckedKeys.push(itemObj.key); + } + }); + return { + halfCheckedKeys, checkedKeys, checkedNodes, checkedNodesPositions, treeNodesStates, + }; +} + +export function getStrictlyValue(checkedKeys, halfChecked) { + if (halfChecked) { + return { checked: checkedKeys, halfChecked }; + } + return checkedKeys; +} + +export function arraysEqual(a, b) { + if (a === b) return true; + if (a === null || typeof a === 'undefined' || b === null || typeof b === 'undefined') { + return false; + } + if (a.length !== b.length) return false; + + // If you don't care about the order of the elements inside + // the array, you should sort both arrays here. + + for (let i = 0; i < a.length; ++i) { + if (a[i] !== b[i]) return false; + } + return true; +} diff --git a/test/index.test.js b/test/index.test.js new file mode 100644 index 0000000..93a09f9 --- /dev/null +++ b/test/index.test.js @@ -0,0 +1,4 @@ +import React from 'react'; +import {shallow, mount, render} from 'enzyme'; +import {expect} from 'chai'; +import Tree from '../src/index'; \ No newline at end of file