diff --git a/components/_util/props-util.js b/components/_util/props-util.js
index d483cbd80..8aec27a94 100644
--- a/components/_util/props-util.js
+++ b/components/_util/props-util.js
@@ -166,10 +166,6 @@ export function getComponentName (opts) {
return opts && (opts.Ctor.options.name || opts.tag)
}
-export function isValidElement (ele) {
- return !!ele.tag
-}
-
export function isEmptyElement (ele) {
return !(ele.tag || ele.text.trim() !== '')
}
@@ -206,6 +202,11 @@ export function mergeProps () {
return props
}
+function isValidElement (element) {
+ const name = element.constructor.name
+ return element.tag && (name === 'VNode' || name === 'VueComponent')
+}
+
export {
hasProp,
filterProps,
@@ -219,5 +220,6 @@ export {
getValueByProp,
parseStyleText,
initDefaultProps,
+ isValidElement,
}
export default hasProp
diff --git a/components/_util/store/PropTypes.js b/components/_util/store/PropTypes.js
new file mode 100644
index 000000000..603ffe6bb
--- /dev/null
+++ b/components/_util/store/PropTypes.js
@@ -0,0 +1,7 @@
+import PropTypes from '../vue-types'
+
+export const storeShape = PropTypes.shape({
+ subscribe: PropTypes.func.isRequired,
+ setState: PropTypes.func.isRequired,
+ getState: PropTypes.func.isRequired,
+})
diff --git a/components/_util/store/Provider.jsx b/components/_util/store/Provider.jsx
new file mode 100644
index 000000000..0b01a3397
--- /dev/null
+++ b/components/_util/store/Provider.jsx
@@ -0,0 +1,16 @@
+
+import { storeShape } from './PropTypes'
+export default {
+ name: 'StoreProvider',
+ props: {
+ store: storeShape.isRequired,
+ },
+ provide () {
+ return {
+ _store: this.$props,
+ }
+ },
+ render () {
+ return this.$slots.default[0]
+ },
+}
diff --git a/components/_util/store/connect.jsx b/components/_util/store/connect.jsx
new file mode 100644
index 000000000..40296f406
--- /dev/null
+++ b/components/_util/store/connect.jsx
@@ -0,0 +1,85 @@
+import shallowEqual from 'shallowequal'
+import omit from 'omit.js'
+import { getOptionProps } from '../props-util'
+import PropTypes from '../vue-types'
+
+function getDisplayName (WrappedComponent) {
+ return WrappedComponent.name || 'Component'
+}
+
+const defaultMapStateToProps = () => ({})
+export default function connect (mapStateToProps) {
+ const shouldSubscribe = !!mapStateToProps
+ const finnalMapStateToProps = mapStateToProps || defaultMapStateToProps
+ return function wrapWithConnect (WrappedComponent) {
+ const tempProps = omit(WrappedComponent.props || {}, ['store'])
+ const props = {}
+ Object.keys(tempProps).forEach(k => { props[k] = PropTypes.any })
+ const Connect = {
+ name: `Connect_${getDisplayName(WrappedComponent)}`,
+ props,
+ inject: {
+ _store: { default: {}},
+ },
+ data () {
+ this.store = this._store.store
+ return {
+ subscribed: finnalMapStateToProps(this.store.getState(), this.$props),
+ }
+ },
+ mounted () {
+ this.trySubscribe()
+ },
+
+ beforeDestroy () {
+ this.tryUnsubscribe()
+ },
+ methods: {
+ handleChange () {
+ if (!this.unsubscribe) {
+ return
+ }
+
+ const nextState = finnalMapStateToProps(this.store.getState(), this.$props)
+ if (!shallowEqual(this.nextState, nextState)) {
+ this.nextState = nextState
+ this.subscribed = nextState
+ }
+ },
+
+ trySubscribe () {
+ if (shouldSubscribe) {
+ this.unsubscribe = this.store.subscribe(this.handleChange)
+ this.handleChange()
+ }
+ },
+
+ tryUnsubscribe () {
+ if (this.unsubscribe) {
+ this.unsubscribe()
+ this.unsubscribe = null
+ }
+ },
+ },
+ render () {
+ const { $listeners, $slots, $attrs, $scopedSlots, subscribed, store } = this
+ const props = getOptionProps(this)
+ const wrapProps = {
+ props: {
+ ...props,
+ ...subscribed,
+ store,
+ },
+ on: $listeners,
+ attrs: $attrs,
+ slots: $slots,
+ scopedSlots: $scopedSlots,
+ }
+ return (
+
+ )
+ },
+ }
+ return Connect
+ }
+}
diff --git a/components/_util/store/create.js b/components/_util/store/create.js
new file mode 100644
index 000000000..57ce3e083
--- /dev/null
+++ b/components/_util/store/create.js
@@ -0,0 +1,30 @@
+export default function create (initialState) {
+ let state = initialState
+ const listeners = []
+
+ function setState (partial) {
+ state = { ...state, ...partial }
+ for (let i = 0; i < listeners.length; i++) {
+ listeners[i]()
+ }
+ }
+
+ function getState () {
+ return state
+ }
+
+ function subscribe (listener) {
+ listeners.push(listener)
+
+ return function unsubscribe () {
+ const index = listeners.indexOf(listener)
+ listeners.splice(index, 1)
+ }
+ }
+
+ return {
+ setState,
+ getState,
+ subscribe,
+ }
+}
diff --git a/components/_util/store/index.js b/components/_util/store/index.js
new file mode 100644
index 000000000..62d58a9a3
--- /dev/null
+++ b/components/_util/store/index.js
@@ -0,0 +1,5 @@
+export { default as Provider } from './Provider'
+
+export { default as connect } from './connect'
+
+export { default as create } from './create'
diff --git a/components/checkbox/Checkbox.jsx b/components/checkbox/Checkbox.jsx
index 722ed86e9..3056fc5c1 100644
--- a/components/checkbox/Checkbox.jsx
+++ b/components/checkbox/Checkbox.jsx
@@ -20,7 +20,6 @@ export default {
},
inject: {
checkboxGroupContext: { default: null },
- test: { default: null },
},
data () {
const { checkboxGroupContext, checked, defaultChecked, value } = this
diff --git a/components/dropdown/index.en-US.md b/components/dropdown/index.en-US.md
index 4c872bd8c..e0f7c5b56 100644
--- a/components/dropdown/index.en-US.md
+++ b/components/dropdown/index.en-US.md
@@ -8,7 +8,7 @@
| getPopupContainer | to set the container of the dropdown menu. The default is to create a `div` element in `body`, you can reset it to the scrolling area and make a relative reposition. [example](https://codepen.io/afc163/pen/zEjNOy?editors=0010) | Function(triggerNode) | `() => document.body` |
| overlay(slot) | the dropdown menu | [Menu](#/us/components/menu) | - |
| placement | placement of pop menu: `bottomLeft` `bottomCenter` `bottomRight` `topLeft` `topCenter` `topRight` | String | `bottomLeft` |
-| trigger | the trigger mode which executes the drop-down action | Array<`click`\|`hover`\|`contextMenu`> | `['hover']` |
+| trigger | the trigger mode which executes the drop-down action | Array<`click`\|`hover`\|`contextmenu`> | `['hover']` |
| visible(v-model) | whether the dropdown menu is visible | boolean | - |
### events
@@ -30,7 +30,7 @@ You should use [Menu](#/us/components/menu/) as `overlay`. The menu items and di
| overlay(slot) | the dropdown menu | [Menu](#/us/components/menu) | - |
| placement | placement of pop menu: `bottomLeft` `bottomCenter` `bottomRight` `topLeft` `topCenter` `topRight` | String | `bottomLeft` |
| size | size of the button, the same as [Button](#/us/components/button) | string | `default` |
-| trigger | the trigger mode which executes the drop-down action | Array<`click`\|`hover`\|`contextMenu`> | `['hover']` |
+| trigger | the trigger mode which executes the drop-down action | Array<`click`\|`hover`\|`contextmenu`> | `['hover']` |
| type | type of the button, the same as [Button](#/us/components/button) | string | `default` |
| visible | whether the dropdown menu is visible | boolean | - |
diff --git a/components/dropdown/index.zh-CN.md b/components/dropdown/index.zh-CN.md
index 8c4f839ff..b65b5cf1d 100644
--- a/components/dropdown/index.zh-CN.md
+++ b/components/dropdown/index.zh-CN.md
@@ -8,7 +8,7 @@
| getPopupContainer | 菜单渲染父节点。默认渲染到 body 上,如果你遇到菜单滚动定位问题,试试修改为滚动的区域,并相对其定位。 | Function(triggerNode) | `() => document.body` |
| overlay(slot) | 菜单 | [Menu](#/cn/components/menu) | - |
| placement | 菜单弹出位置:`bottomLeft` `bottomCenter` `bottomRight` `topLeft` `topCenter` `topRight` | String | `bottomLeft` |
-| trigger | 触发下拉的行为 | Array<`click`\|`hover`\|`contextMenu`> | `['hover']` |
+| trigger | 触发下拉的行为 | Array<`click`\|`hover`\|`contextmenu`> | `['hover']` |
| visible(v-model) | 菜单是否显示 | boolean | - |
`overlay` 菜单使用 [Menu](#/cn/components/menu/),还包括菜单项 `Menu.Item`,分割线 `Menu.Divider`。
@@ -31,7 +31,7 @@
| overlay(slot) | 菜单 | [Menu](#/cn/components/menu/) | - |
| placement | 菜单弹出位置:`bottomLeft` `bottomCenter` `bottomRight` `topLeft` `topCenter` `topRight` | String | `bottomLeft` |
| size | 按钮大小,和 [Button](#/cn/components/button/) 一致 | string | 'default' |
-| trigger | 触发下拉的行为 | Array<`click`\|`hover`\|`contextMenu`> | `['hover']` |
+| trigger | 触发下拉的行为 | Array<`click`\|`hover`\|`contextmenu`> | `['hover']` |
| type | 按钮类型,和 [Button](#/cn/components/button/) 一致 | string | 'default' |
| visible(v-model) | 菜单是否显示 | boolean | - |
diff --git a/components/trigger/index.md b/components/trigger/index.md
index 69f9cb643..bae1a0f2a 100644
--- a/components/trigger/index.md
+++ b/components/trigger/index.md
@@ -40,7 +40,7 @@
action |
string[] |
['hover'] |
- which actions cause popup shown. enum of 'hover','click','focus','contextMenu' |
+ which actions cause popup shown. enum of 'hover','click','focus','contextmenu' |
mouseEnterDelay |
diff --git a/components/vc-table/assets/animation.less b/components/vc-table/assets/animation.less
new file mode 100644
index 000000000..a6d4e9281
--- /dev/null
+++ b/components/vc-table/assets/animation.less
@@ -0,0 +1,59 @@
+
+.move-enter, .move-appear {
+ opacity: 0;
+ animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
+ animation-duration: 2.5s;
+ animation-fill-mode: both;
+ animation-play-state: paused;
+}
+
+.move-leave {
+ animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19);
+ animation-duration: .5s;
+ animation-fill-mode: both;
+ animation-play-state: paused;
+}
+
+.move-enter.move-enter-active, .move-appear.move-enter-active {
+ animation-name: moveLeftIn;
+ animation-play-state: running;
+}
+
+.move-leave.move-leave-active {
+ animation-name: moveRightOut;
+ animation-play-state: running;
+}
+
+@keyframes moveLeftIn {
+ 0% {
+ transform-origin: 0 0;
+ transform: translateX(30px);
+ opacity: 0;
+ background: #fff6de;
+ }
+ 20% {
+ transform-origin: 0 0;
+ transform: translateX(0);
+ opacity: 1;
+ }
+ 80%{
+ background: #fff6de;
+ }
+ 100%{
+ background: transparent;
+ opacity: 1;
+ }
+}
+
+@keyframes moveRightOut {
+ 0% {
+ transform-origin: 0 0;
+ transform: translateX(0);
+ opacity: 1;
+ }
+ 100% {
+ transform-origin: 0 0;
+ transform: translateX(-30px);
+ opacity: 0;
+ }
+}
diff --git a/components/vc-table/assets/bordered.less b/components/vc-table/assets/bordered.less
new file mode 100644
index 000000000..f3ad2e84a
--- /dev/null
+++ b/components/vc-table/assets/bordered.less
@@ -0,0 +1,11 @@
+@tablePrefixCls: rc-table;
+@table-border-color: #e9e9e9;
+
+.@{tablePrefixCls}.bordered {
+ table {
+ border-collapse: collapse;
+ }
+ th, td {
+ border: 1px solid @table-border-color;
+ }
+}
diff --git a/components/vc-table/assets/index.less b/components/vc-table/assets/index.less
new file mode 100644
index 000000000..f6c6faa4a
--- /dev/null
+++ b/components/vc-table/assets/index.less
@@ -0,0 +1,227 @@
+@import 'normalize.css';
+
+@tablePrefixCls: rc-table;
+@text-color : #666;
+@font-size-base : 12px;
+@line-height: 1.5;
+@table-border-color: #e9e9e9;
+@table-head-background-color: #f7f7f7;
+@vertical-padding: 16px;
+@horizontal-padding: 8px;
+
+.@{tablePrefixCls} {
+ font-size: @font-size-base;
+ color: @text-color;
+ transition: opacity 0.3s ease;
+ position: relative;
+ line-height: @line-height;
+ overflow: hidden;
+
+ .@{tablePrefixCls}-scroll {
+ overflow: auto;
+ table {
+ width: auto;
+ min-width: 100%;
+ }
+ }
+
+ .@{tablePrefixCls}-header {
+ overflow: hidden;
+ background: @table-head-background-color;
+ }
+
+ &-fixed-header &-body {
+ background: #fff;
+ position: relative;
+ }
+
+ &-fixed-header &-body-inner {
+ height: 100%;
+ overflow: scroll;
+ }
+
+ &-fixed-header &-scroll &-header {
+ overflow-x: scroll;
+ padding-bottom: 20px;
+ margin-bottom: -20px;
+ overflow-y: scroll;
+ box-sizing: border-box;
+ }
+
+ .@{tablePrefixCls}-title {
+ padding: @vertical-padding @horizontal-padding;
+ border-top: 1px solid @table-border-color;
+ }
+
+ .@{tablePrefixCls}-content {
+ position: relative;
+ }
+
+ .@{tablePrefixCls}-footer {
+ padding: @vertical-padding @horizontal-padding;
+ border-bottom: 1px solid @table-border-color;
+ }
+
+ .@{tablePrefixCls}-placeholder {
+ padding: 16px 8px;
+ background: #fff;
+ border-bottom: 1px solid @table-border-color;
+ text-align: center;
+ position: relative;
+ &-fixed-columns {
+ position: absolute;
+ bottom: 0;
+ width: 100%;
+ background: transparent;
+ pointer-events: none;
+ }
+ }
+
+ table {
+ width: 100%;
+ border-collapse: separate;
+ text-align: left;
+ }
+
+ th {
+ background: @table-head-background-color;
+ font-weight: bold;
+ transition: background .3s ease;
+ }
+
+ td {
+ border-bottom: 1px solid @table-border-color;
+ &:empty:after {
+ content: '.'; // empty cell placeholder
+ visibility: hidden;
+ }
+ }
+
+ tr {
+ transition: all .3s ease;
+ &:hover {
+ background: #eaf8fe;
+ }
+ &.@{tablePrefixCls}-row-hover {
+ background: #eaf8fe;
+ }
+ }
+
+ th, td {
+ padding: @vertical-padding @horizontal-padding;
+ white-space: nowrap;
+ }
+}
+
+.@{tablePrefixCls} {
+ &-expand-icon-col {
+ width: 34px;
+ }
+ &-row, &-expanded-row {
+ &-expand-icon {
+ cursor: pointer;
+ display: inline-block;
+ width: 16px;
+ height: 16px;
+ text-align: center;
+ line-height: 16px;
+ border: 1px solid @table-border-color;
+ user-select: none;
+ background: #fff;
+ }
+ &-spaced {
+ visibility: hidden;
+ }
+ &-spaced:after {
+ content: '.'
+ }
+
+ &-expanded:after {
+ content: '-'
+ }
+
+ &-collapsed:after {
+ content: '+'
+ }
+ }
+ tr&-expanded-row {
+ background: #f7f7f7;
+ &:hover {
+ background: #f7f7f7;
+ }
+ }
+ &-column-hidden {
+ display: none;
+ }
+ &-prev-columns-page,
+ &-next-columns-page {
+ cursor: pointer;
+ color: #666;
+ z-index: 1;
+ &:hover {
+ color: #2db7f5;
+ }
+ &-disabled {
+ cursor: not-allowed;
+ color: #999;
+ &:hover {
+ color: #999;
+ }
+ }
+ }
+ &-prev-columns-page {
+ margin-right: 8px;
+ &:before {
+ content: '<';
+ }
+ }
+ &-next-columns-page {
+ float: right;
+ &:before {
+ content: '>';
+ }
+ }
+
+ &-fixed-left,
+ &-fixed-right {
+ position: absolute;
+ top: 0;
+ overflow: hidden;
+ table {
+ width: auto;
+ background: #fff;
+ }
+ }
+
+ &-fixed-left {
+ left: 0;
+ box-shadow: 4px 0 4px rgba(100, 100, 100, 0.1);
+ & .@{tablePrefixCls}-body-inner {
+ margin-right: -20px;
+ padding-right: 20px;
+ }
+ .@{tablePrefixCls}-fixed-header & .@{tablePrefixCls}-body-inner {
+ padding-right: 0;
+ }
+ }
+
+ &-fixed-right {
+ right: 0;
+ box-shadow: -4px 0 4px rgba(100, 100, 100, 0.1);
+
+ // hide expand row content in right fixed Table
+ // https://github.com/ant-design/ant-design/issues/1898
+ .@{tablePrefixCls}-expanded-row {
+ color: transparent;
+ pointer-events: none;
+ }
+ }
+
+ &&-scroll-position-left &-fixed-left {
+ box-shadow: none;
+ }
+
+ &&-scroll-position-right &-fixed-right {
+ box-shadow: none;
+ }
+}
diff --git a/components/vc-table/assets/normalize.css b/components/vc-table/assets/normalize.css
new file mode 100644
index 000000000..a5b29a0c0
--- /dev/null
+++ b/components/vc-table/assets/normalize.css
@@ -0,0 +1,220 @@
+/*! normalize.css v3.0.1 | MIT License | git.io/normalize */
+
+html {
+ font-family: sans-serif;
+ -ms-text-size-adjust: 100%;
+ -webkit-text-size-adjust: 100%
+}
+
+body {
+ margin: 0
+}
+
+article,
+aside,
+details,
+figcaption,
+figure,
+footer,
+header,
+hgroup,
+main,
+nav,
+section,
+summary {
+ display: block
+}
+
+audio,
+canvas,
+progress,
+video {
+ display: inline-block;
+ vertical-align: baseline
+}
+
+audio:not([controls]) {
+ display: none;
+ height: 0
+}
+
+[hidden],
+template {
+ display: none
+}
+
+a {
+ background: 0 0
+}
+
+a:active,
+a:hover {
+ outline: 0
+}
+
+abbr[title] {
+ border-bottom: 1px dotted
+}
+
+b,
+strong {
+ font-weight: 700
+}
+
+dfn {
+ font-style: italic
+}
+
+h1 {
+ font-size: 2em;
+ margin: .67em 0
+}
+
+mark {
+ background: #ff0;
+ color: #000
+}
+
+small {
+ font-size: 80%
+}
+
+sub,
+sup {
+ font-size: 75%;
+ line-height: 0;
+ position: relative;
+ vertical-align: baseline
+}
+
+sup {
+ top: -.5em
+}
+
+sub {
+ bottom: -.25em
+}
+
+img {
+ border: 0
+}
+
+svg:not(:root) {
+ overflow: hidden
+}
+
+figure {
+ margin: 1em 40px
+}
+
+hr {
+ -moz-box-sizing: content-box;
+ box-sizing: content-box;
+ height: 0
+}
+
+pre {
+ overflow: auto
+}
+
+code,
+kbd,
+pre,
+samp {
+ font-family: monospace, monospace;
+ font-size: 1em
+}
+
+button,
+input,
+optgroup,
+select,
+textarea {
+ color: inherit;
+ font: inherit;
+ margin: 0
+}
+
+button {
+ overflow: visible
+}
+
+button,
+select {
+ text-transform: none
+}
+
+button,
+html input[type=button],
+input[type=reset],
+input[type=submit] {
+ -webkit-appearance: button;
+ cursor: pointer
+}
+
+button[disabled],
+html input[disabled] {
+ cursor: default
+}
+
+button::-moz-focus-inner,
+input::-moz-focus-inner {
+ border: 0;
+ padding: 0
+}
+
+input {
+ line-height: normal
+}
+
+input[type=checkbox],
+input[type=radio] {
+ box-sizing: border-box;
+ padding: 0
+}
+
+input[type=number]::-webkit-inner-spin-button,
+input[type=number]::-webkit-outer-spin-button {
+ height: auto
+}
+
+input[type=search] {
+ -webkit-appearance: textfield;
+ -moz-box-sizing: content-box;
+ -webkit-box-sizing: content-box;
+ box-sizing: content-box
+}
+
+input[type=search]::-webkit-search-cancel-button,
+input[type=search]::-webkit-search-decoration {
+ -webkit-appearance: none
+}
+
+fieldset {
+ border: 1px solid silver;
+ margin: 0 2px;
+ padding: .35em .625em .75em
+}
+
+legend {
+ border: 0;
+ padding: 0
+}
+
+textarea {
+ overflow: auto
+}
+
+optgroup {
+ font-weight: 700
+}
+
+table {
+ border-collapse: collapse;
+ border-spacing: 0
+}
+
+td,
+th {
+ padding: 0
+}
\ No newline at end of file
diff --git a/components/vc-table/demo/childrenIndent.js b/components/vc-table/demo/childrenIndent.js
new file mode 100644
index 000000000..f436835d7
--- /dev/null
+++ b/components/vc-table/demo/childrenIndent.js
@@ -0,0 +1,93 @@
+/* eslint-disable no-console,func-names,react/no-multi-comp */
+import Table from '../index'
+import '../assets/index.less'
+
+const columns = [{
+ title: 'Name',
+ dataIndex: 'name',
+ key: 'name',
+ width: 400,
+}, {
+ title: 'Age',
+ dataIndex: 'age',
+ key: 'age',
+ width: 100,
+}, {
+ title: 'Address',
+ dataIndex: 'address',
+ key: 'address',
+ width: 200,
+}, {
+ title: 'Operations',
+ dataIndex: 'operation',
+ key: 'x',
+ width: 150,
+}]
+
+const data = [{
+ key: 1,
+ name: 'a',
+ age: 32,
+ address: 'I am a',
+ children: [{
+ key: 11,
+ name: 'aa',
+ age: 33,
+ address: 'I am aa',
+ }, {
+ key: 12,
+ name: 'ab',
+ age: 33,
+ address: 'I am ab',
+ children: [{
+ key: 121,
+ name: 'aba',
+ age: 33,
+ address: 'I am aba',
+ }],
+ }, {
+ key: 13,
+ name: 'ac',
+ age: 33,
+ address: 'I am ac',
+ children: [{
+ key: 131,
+ name: 'aca',
+ age: 33,
+ address: 'I am aca',
+ children: [{
+ key: 1311,
+ name: 'acaa',
+ age: 33,
+ address: 'I am acaa',
+ }, {
+ key: 1312,
+ name: 'acab',
+ age: 33,
+ address: 'I am acab',
+ }],
+ }],
+ }],
+}, {
+ key: 2,
+ name: 'b',
+ age: 32,
+ address: 'I am b',
+}]
+
+function onExpand (expanded, record) {
+ console.log('onExpand', expanded, record)
+}
+export default {
+ render () {
+ return (
+
+ )
+ },
+}
+
diff --git a/components/vc-table/demo/className.js b/components/vc-table/demo/className.js
new file mode 100644
index 000000000..9413ea545
--- /dev/null
+++ b/components/vc-table/demo/className.js
@@ -0,0 +1,45 @@
+/* eslint-disable no-console,func-names,react/no-multi-comp */
+import Table from '../index'
+import '../assets/index.less'
+
+const data = [
+ { a: '123', key: '1' },
+ { a: 'cdd', b: 'edd', key: '2' },
+ { a: '1333', c: 'eee', d: 2, key: '3' },
+]
+export default {
+ render () {
+ const columns = [
+ { title: 'title1', dataIndex: 'a',
+ className: 'a',
+ key: 'a', width: 100 },
+ { id: '123', title: 'title2', dataIndex: 'b',
+ className: 'b',
+ key: 'b', width: 100 },
+ { title: 'title3', dataIndex: 'c',
+ className: 'c',
+ key: 'c', width: 200 },
+ {
+ title: 'Operations', dataIndex: '',
+ className: 'd',
+ key: 'd', render (h) {
+ return Operations
+ },
+ },
+ ]
+ return (
+
+
rowClassName and className
+
`row-${i}`}
+ expandedRowRender={record => extra: {record.a}
}
+ expandedRowClassName={(record, i) => `ex-row-${i}`}
+ data={data}
+ class='table'
+ />
+
+ )
+ },
+}
+
diff --git a/components/vc-table/index.js b/components/vc-table/index.js
new file mode 100644
index 000000000..c4a842cdc
--- /dev/null
+++ b/components/vc-table/index.js
@@ -0,0 +1,9 @@
+import Table from './src/Table'
+import Column from './src/Column'
+import ColumnGroup from './src/ColumnGroup'
+
+Table.Column = Column
+Table.ColumnGroup = ColumnGroup
+
+export default Table
+export { Column, ColumnGroup }
diff --git a/components/vc-table/src/BaseTable.jsx b/components/vc-table/src/BaseTable.jsx
new file mode 100644
index 000000000..93a624224
--- /dev/null
+++ b/components/vc-table/src/BaseTable.jsx
@@ -0,0 +1,191 @@
+
+import PropTypes from '../../_util/vue-types'
+import ColGroup from './ColGroup'
+import TableHeader from './TableHeader'
+import TableRow from './TableRow'
+import ExpandableRow from './ExpandableRow'
+import { mergeProps } from '../../_util/props-util'
+import { connect } from '../../_util/store'
+function noop () {}
+const BaseTable = {
+ name: 'BaseTable',
+ props: {
+ fixed: PropTypes.oneOfType([
+ PropTypes.string,
+ PropTypes.bool,
+ ]),
+ columns: PropTypes.array.isRequired,
+ tableClassName: PropTypes.string.isRequired,
+ hasHead: PropTypes.bool.isRequired,
+ hasBody: PropTypes.bool.isRequired,
+ store: PropTypes.object.isRequired,
+ expander: PropTypes.object.isRequired,
+ getRowKey: PropTypes.func,
+ isAnyColumnsFixed: PropTypes.bool,
+ },
+ inject: {
+ table: { default: {}},
+ },
+ methods: {
+ handleRowHover (isHover, key) {
+ this.props.store.setState({
+ currentHoverKey: isHover ? key : null,
+ })
+ },
+
+ renderRows (renderData, indent, ancestorKeys = []) {
+ const {
+ columnManager, sComponents: components,
+ prefixCls,
+ childrenColumnName,
+ rowClassName,
+ // rowRef,
+ $listeners: {
+ rowClick: onRowClick = noop,
+ rowDoubleclick: onRowDoubleClick = noop,
+ rowContextmenu: onRowContextMenu = noop,
+ rowMouseenter: onRowMouseEnter = noop,
+ rowMouseleave: onRowMouseLeave = noop,
+ row: onRow = noop,
+ },
+ } = this.table
+ const { getRowKey, fixed, expander, isAnyColumnsFixed } = this
+
+ const rows = []
+
+ for (let i = 0; i < renderData.length; i++) {
+ const record = renderData[i]
+ const key = getRowKey(record, i)
+ const className = typeof rowClassName === 'string'
+ ? rowClassName
+ : rowClassName(record, i, indent)
+
+ const onHoverProps = {}
+ if (columnManager.isAnyColumnsFixed()) {
+ onHoverProps.hover = this.handleRowHover
+ }
+
+ let leafColumns
+ if (fixed === 'left') {
+ leafColumns = columnManager.leftLeafColumns()
+ } else if (fixed === 'right') {
+ leafColumns = columnManager.rightLeafColumns()
+ } else {
+ leafColumns = columnManager.leafColumns()
+ }
+
+ const rowPrefixCls = `${prefixCls}-row`
+ const expandableRowProps = {
+ props: {
+ ...expander.props,
+ fixed,
+ index: i,
+ prefixCls: rowPrefixCls,
+ record,
+ rowKey: key,
+ needIndentSpaced: expander.needIndentSpaced,
+ },
+ key,
+ on: {
+ // ...expander.on,
+ rowClick: onRowClick,
+ expandedChange: expander.handleExpandChange,
+ },
+ scopedSlots: {
+ default: (expandableRow) => {
+ const tableRowProps = mergeProps({
+ props: {
+ fixed,
+ indent,
+ record,
+ index: i,
+ prefixCls: rowPrefixCls,
+ childrenColumnName: childrenColumnName,
+ columns: leafColumns,
+ rowKey: key,
+ ancestorKeys,
+ components,
+ isAnyColumnsFixed,
+ },
+ on: {
+ row: onRow,
+ rowDoubleclick: onRowDoubleClick,
+ rowContextmenu: onRowContextMenu,
+ rowMouseenter: onRowMouseEnter,
+ rowMouseleave: onRowMouseLeave,
+ ...onHoverProps,
+ },
+ class: className,
+ ref: `row_${i}_${indent}`,
+ }, expandableRow)
+ return (
+
+ )
+ },
+ },
+ }
+ const row = (
+
+ )
+
+ rows.push(row)
+
+ expander.renderRows(
+ this.renderRows,
+ rows,
+ record,
+ i,
+ indent,
+ fixed,
+ key,
+ ancestorKeys
+ )
+ }
+ return rows
+ },
+ },
+
+ render () {
+ const { sComponents: components, prefixCls, scroll, data, getBodyWrapper } = this.table
+ const { expander, tableClassName, hasHead, hasBody, fixed, columns } = this
+ const tableStyle = {}
+
+ if (!fixed && scroll.x) {
+ // not set width, then use content fixed width
+ if (scroll.x === true) {
+ tableStyle.tableLayout = 'fixed'
+ } else {
+ tableStyle.width = scroll.x
+ }
+ }
+
+ const Table = hasBody ? components.table : 'table'
+ const BodyWrapper = components.body.wrapper
+
+ let body
+ if (hasBody) {
+ body = (
+
+ {this.renderRows(data, 0)}
+
+ )
+ if (getBodyWrapper) {
+ body = getBodyWrapper(body)
+ }
+ }
+
+ return (
+
+
+ {hasHead && }
+ {body}
+
+ )
+ },
+}
+
+export default connect()(BaseTable)
diff --git a/components/vc-table/src/BodyTable.jsx b/components/vc-table/src/BodyTable.jsx
new file mode 100644
index 000000000..6cb8d69be
--- /dev/null
+++ b/components/vc-table/src/BodyTable.jsx
@@ -0,0 +1,117 @@
+import PropTypes from '../../_util/vue-types'
+import { measureScrollbar } from './utils'
+import BaseTable from './BaseTable'
+
+export default {
+ name: 'BodyTable',
+ props: {
+ fixed: PropTypes.oneOfType([
+ PropTypes.string,
+ PropTypes.bool,
+ ]),
+ columns: PropTypes.array.isRequired,
+ tableClassName: PropTypes.string.isRequired,
+ handleBodyScroll: PropTypes.func.isRequired,
+ getRowKey: PropTypes.func.isRequired,
+ expander: PropTypes.object.isRequired,
+ isAnyColumnsFixed: PropTypes.bool,
+ },
+ inject: {
+ table: { default: {}},
+ },
+ render () {
+ const { prefixCls, scroll } = this.table
+ const {
+ columns,
+ fixed,
+ tableClassName,
+ getRowKey,
+ handleBodyScroll,
+ expander,
+ isAnyColumnsFixed,
+ } = this
+ let { useFixedHeader } = this.table
+ const bodyStyle = { ...this.table.bodyStyle }
+ const innerBodyStyle = {}
+
+ if (scroll.x || fixed) {
+ bodyStyle.overflowX = bodyStyle.overflowX || 'auto'
+ // Fix weired webkit render bug
+ // https://github.com/ant-design/ant-design/issues/7783
+ bodyStyle.WebkitTransform = 'translate3d (0, 0, 0)'
+ }
+
+ if (scroll.y) {
+ // maxHeight will make fixed-Table scrolling not working
+ // so we only set maxHeight to body-Table here
+ if (fixed) {
+ innerBodyStyle.maxHeight = bodyStyle.maxHeight || scroll.y
+ innerBodyStyle.overflowY = bodyStyle.overflowY || 'scroll'
+ } else {
+ bodyStyle.maxHeight = bodyStyle.maxHeight || scroll.y
+ }
+ bodyStyle.overflowY = bodyStyle.overflowY || 'scroll'
+ useFixedHeader = true
+
+ // Add negative margin bottom for scroll bar overflow bug
+ const scrollbarWidth = measureScrollbar()
+ if (scrollbarWidth > 0 && fixed) {
+ bodyStyle.marginBottom = `-${scrollbarWidth}px`
+ bodyStyle.paddingBottom = '0px'
+ }
+ }
+
+ const baseTable = (
+
+ )
+
+ if (fixed && columns.length) {
+ let refName
+ if (columns[0].fixed === 'left' || columns[0].fixed === true) {
+ refName = 'fixedColumnsBodyLeft'
+ } else if (columns[0].fixed === 'right') {
+ refName = 'fixedColumnsBodyRight'
+ }
+ delete bodyStyle.overflowX
+ delete bodyStyle.overflowY
+ return (
+
+ )
+ }
+ return (
+
+ {baseTable}
+
+ )
+ },
+
+}
+
diff --git a/components/vc-table/src/ColGroup.jsx b/components/vc-table/src/ColGroup.jsx
new file mode 100644
index 000000000..9f169e57a
--- /dev/null
+++ b/components/vc-table/src/ColGroup.jsx
@@ -0,0 +1,55 @@
+import PropTypes from '../../_util/vue-types'
+
+export default {
+ name: 'ColGroup',
+ props: {
+ fixed: PropTypes.string,
+ columns: PropTypes.array,
+ },
+ inject: {
+ table: { default: {}},
+ },
+ render () {
+ const { fixed, table } = this
+ const { prefixCls, expandIconAsCell, columnManager } = table
+
+ let cols = []
+
+ if (expandIconAsCell && fixed !== 'right') {
+ cols.push(
+
+ )
+ }
+
+ let leafColumns
+
+ if (fixed === 'left') {
+ leafColumns = columnManager.leftLeafColumns()
+ } else if (fixed === 'right') {
+ leafColumns = columnManager.rightLeafColumns()
+ } else {
+ leafColumns = columnManager.leafColumns()
+ }
+ cols = cols.concat(
+ leafColumns.map(c => {
+ const width = typeof c.width === 'number' ? `${c.width}px` : c.width
+ return (
+
+ )
+ })
+ )
+ return (
+
+ {cols}
+
+ )
+ },
+
+}
+
diff --git a/components/vc-table/src/Column.jsx b/components/vc-table/src/Column.jsx
new file mode 100644
index 000000000..8ad12648c
--- /dev/null
+++ b/components/vc-table/src/Column.jsx
@@ -0,0 +1,23 @@
+import PropTypes from '../../_util/vue-types'
+
+export default {
+ name: 'Column',
+ props: {
+ colSpan: PropTypes.number,
+ title: PropTypes.any,
+ dataIndex: PropTypes.string,
+ width: PropTypes.oneOfType([
+ PropTypes.number,
+ PropTypes.string,
+ ]),
+ fixed: PropTypes.oneOf([
+ true,
+ 'left',
+ 'right',
+ ]),
+ render: PropTypes.func,
+ // onCellClick: PropTypes.func,
+ // onCell: PropTypes.func,
+ // onHeaderCell: PropTypes.func,
+ },
+}
diff --git a/components/vc-table/src/ColumnGroup.jsx b/components/vc-table/src/ColumnGroup.jsx
new file mode 100644
index 000000000..ad329cd15
--- /dev/null
+++ b/components/vc-table/src/ColumnGroup.jsx
@@ -0,0 +1,9 @@
+import PropTypes from '../../_util/vue-types'
+
+export default {
+ name: 'ColumnGroup',
+ props: {
+ title: PropTypes.any,
+ },
+ isTableColumnGroup: true,
+}
diff --git a/components/vc-table/src/ColumnManager.jsx b/components/vc-table/src/ColumnManager.jsx
new file mode 100644
index 000000000..b0696baaf
--- /dev/null
+++ b/components/vc-table/src/ColumnManager.jsx
@@ -0,0 +1,149 @@
+export default class ColumnManager {
+ constructor (columns, elements) {
+ this.columns = columns || this.normalize(elements)
+ this._cached = {}
+ }
+
+ isAnyColumnsFixed () {
+ return this._cache('isAnyColumnsFixed', () => {
+ return this.columns.some(column => !!column.fixed)
+ })
+ }
+
+ isAnyColumnsLeftFixed () {
+ return this._cache('isAnyColumnsLeftFixed', () => {
+ return this.columns.some(
+ column => column.fixed === 'left' || column.fixed === true
+ )
+ })
+ }
+
+ isAnyColumnsRightFixed () {
+ return this._cache('isAnyColumnsRightFixed', () => {
+ return this.columns.some(
+ column => column.fixed === 'right'
+ )
+ })
+ }
+
+ leftColumns () {
+ return this._cache('leftColumns', () => {
+ return this.groupedColumns().filter(
+ column => column.fixed === 'left' || column.fixed === true
+ )
+ })
+ }
+
+ rightColumns () {
+ return this._cache('rightColumns', () => {
+ return this.groupedColumns().filter(
+ column => column.fixed === 'right'
+ )
+ })
+ }
+
+ leafColumns () {
+ return this._cache('leafColumns', () =>
+ this._leafColumns(this.columns)
+ )
+ }
+
+ leftLeafColumns () {
+ return this._cache('leftLeafColumns', () =>
+ this._leafColumns(this.leftColumns())
+ )
+ }
+
+ rightLeafColumns () {
+ return this._cache('rightLeafColumns', () =>
+ this._leafColumns(this.rightColumns())
+ )
+ }
+
+ // add appropriate rowspan and colspan to column
+ groupedColumns () {
+ return this._cache('groupedColumns', () => {
+ const _groupColumns = (columns, currentRow = 0, parentColumn = {}, rows = []) => {
+ // track how many rows we got
+ rows[currentRow] = rows[currentRow] || []
+ const grouped = []
+ const setRowSpan = column => {
+ const rowSpan = rows.length - currentRow
+ if (column &&
+ !column.children && // parent columns are supposed to be one row
+ rowSpan > 1 &&
+ (!column.rowSpan || column.rowSpan < rowSpan)
+ ) {
+ column.rowSpan = rowSpan
+ }
+ }
+ columns.forEach((column, index) => {
+ const newColumn = { ...column }
+ rows[currentRow].push(newColumn)
+ parentColumn.colSpan = parentColumn.colSpan || 0
+ if (newColumn.children && newColumn.children.length > 0) {
+ newColumn.children = _groupColumns(newColumn.children, currentRow + 1, newColumn, rows)
+ parentColumn.colSpan = parentColumn.colSpan + newColumn.colSpan
+ } else {
+ parentColumn.colSpan++
+ }
+ // update rowspan to all same row columns
+ for (let i = 0; i < rows[currentRow].length - 1; ++i) {
+ setRowSpan(rows[currentRow][i])
+ }
+ // last column, update rowspan immediately
+ if (index + 1 === columns.length) {
+ setRowSpan(newColumn)
+ }
+ grouped.push(newColumn)
+ })
+ return grouped
+ }
+ return _groupColumns(this.columns)
+ })
+ }
+
+ normalize (elements) {
+ const columns = []
+ elements.forEach(element => {
+ if (!element.tag) {
+ return
+ }
+ debugger
+ const column = { ...element.props }
+ if (element.key) {
+ column.key = element.key
+ }
+ if (element.type.isTableColumnGroup) {
+ column.children = this.normalize(column.children)
+ }
+ columns.push(column)
+ })
+ return columns
+ }
+
+ reset (columns, elements) {
+ this.columns = columns || this.normalize(elements)
+ this._cached = {}
+ }
+
+ _cache (name, fn) {
+ if (name in this._cached) {
+ return this._cached[name]
+ }
+ this._cached[name] = fn()
+ return this._cached[name]
+ }
+
+ _leafColumns (columns) {
+ const leafColumns = []
+ columns.forEach(column => {
+ if (!column.children) {
+ leafColumns.push(column)
+ } else {
+ leafColumns.push(...this._leafColumns(column.children))
+ }
+ })
+ return leafColumns
+ }
+}
diff --git a/components/vc-table/src/ExpandIcon.jsx b/components/vc-table/src/ExpandIcon.jsx
new file mode 100644
index 000000000..490386d43
--- /dev/null
+++ b/components/vc-table/src/ExpandIcon.jsx
@@ -0,0 +1,34 @@
+import PropTypes from '../../_util/vue-types'
+import BaseMixin from '../../_util/BaseMixin'
+export default {
+ mixins: [BaseMixin],
+ name: 'ExpandIcon',
+ props: {
+ record: PropTypes.object,
+ prefixCls: PropTypes.string,
+ expandable: PropTypes.any,
+ expanded: PropTypes.bool,
+ needIndentSpaced: PropTypes.bool,
+ },
+ methods: {
+ onExpand (e) {
+ this.__emit('expand', this.record, e)
+ },
+ },
+
+ render () {
+ const { expandable, prefixCls, onExpand, needIndentSpaced, expanded } = this
+ if (expandable) {
+ const expandClassName = expanded ? 'expanded' : 'collapsed'
+ return (
+
+ )
+ } else if (needIndentSpaced) {
+ return
+ }
+ return null
+ },
+}
diff --git a/components/vc-table/src/ExpandableRow.jsx b/components/vc-table/src/ExpandableRow.jsx
new file mode 100644
index 000000000..cfd431197
--- /dev/null
+++ b/components/vc-table/src/ExpandableRow.jsx
@@ -0,0 +1,128 @@
+import PropTypes from '../../_util/vue-types'
+import ExpandIcon from './ExpandIcon'
+import BaseMixin from '../../_util/BaseMixin'
+import { connect } from '../../_util/store'
+
+const ExpandableRow = {
+ mixins: [BaseMixin],
+ name: 'ExpandableRow',
+ props: {
+ prefixCls: PropTypes.string.isRequired,
+ rowKey: PropTypes.oneOfType([
+ PropTypes.string,
+ PropTypes.number,
+ ]).isRequired,
+ fixed: PropTypes.oneOfType([
+ PropTypes.string,
+ PropTypes.bool,
+ ]),
+ record: PropTypes.object.isRequired,
+ indentSize: PropTypes.number,
+ needIndentSpaced: PropTypes.bool.isRequired,
+ expandRowByClick: PropTypes.bool,
+ expanded: PropTypes.bool.isRequired,
+ expandIconAsCell: PropTypes.bool,
+ expandIconColumnIndex: PropTypes.number,
+ childrenColumnName: PropTypes.string,
+ expandedRowRender: PropTypes.func,
+ // onExpandedChange: PropTypes.func.isRequired,
+ // onRowClick: PropTypes.func,
+ // children: PropTypes.func.isRequired,
+ },
+
+ beforeDestroy () {
+ this.handleDestroy()
+ },
+ methods: {
+ hasExpandIcon (columnIndex) {
+ const { expandRowByClick } = this
+ return !this.expandIconAsCell &&
+ !expandRowByClick &&
+ columnIndex === this.expandIconColumnIndex
+ },
+
+ handleExpandChange (record, event) {
+ const { expanded, rowKey } = this
+ this.__emit('expandedChange', !expanded, record, event, rowKey)
+ },
+
+ handleDestroy () {
+ const { rowKey, record } = this
+ this.__emit('expandedChange', false, record, null, rowKey, true)
+ },
+
+ handleRowClick (record, index, event) {
+ const { expandRowByClick } = this
+ if (expandRowByClick) {
+ this.handleExpandChange(record, event)
+ }
+ this.__emit('rowClick', record, index, event)
+ },
+
+ renderExpandIcon () {
+ const { prefixCls, expanded, record, needIndentSpaced } = this
+
+ return (
+
+ )
+ },
+
+ renderExpandIconCell (cells) {
+ if (!this.expandIconAsCell) {
+ return
+ }
+ const { prefixCls } = this
+
+ cells.push(
+
+ {this.renderExpandIcon()}
+ |
+ )
+ },
+ },
+
+ render () {
+ const {
+ childrenColumnName,
+ expandedRowRender,
+ indentSize,
+ record,
+ fixed,
+ $scopedSlots,
+ } = this
+
+ this.expandIconAsCell = fixed !== 'right' ? this.expandIconAsCell : false
+ this.expandIconColumnIndex = fixed !== 'right' ? this.expandIconColumnIndex : -1
+ const childrenData = record[childrenColumnName]
+ this.expandable = !!(childrenData || expandedRowRender)
+ const expandableRowProps = {
+ props: {
+ indentSize,
+ hasExpandIcon: this.hasExpandIcon,
+ renderExpandIcon: this.renderExpandIcon,
+ renderExpandIconCell: this.renderExpandIconCell,
+ },
+
+ on: {
+ rowClick: this.handleRowClick,
+ },
+
+ }
+
+ return $scopedSlots.default && $scopedSlots.default(expandableRowProps)
+ },
+}
+
+export default connect(({ expandedRowKeys }, { rowKey }) => ({
+ expanded: !!~expandedRowKeys.indexOf(rowKey),
+}))(ExpandableRow)
diff --git a/components/vc-table/src/ExpandableTable.jsx b/components/vc-table/src/ExpandableTable.jsx
new file mode 100644
index 000000000..fc8402b3c
--- /dev/null
+++ b/components/vc-table/src/ExpandableTable.jsx
@@ -0,0 +1,223 @@
+import PropTypes from '../../_util/vue-types'
+import BaseMixin from '../../_util/BaseMixin'
+import { connect } from '../../_util/store'
+import TableRow from './TableRow'
+import { remove } from './utils'
+import { initDefaultProps, getOptionProps } from '../../_util/props-util'
+
+export const ExpandableTableProps = () => ({
+ expandIconAsCell: PropTypes.bool,
+ expandedRowKeys: PropTypes.array,
+ expandedRowClassName: PropTypes.func,
+ defaultExpandAllRows: PropTypes.bool,
+ defaultExpandedRowKeys: PropTypes.array,
+ expandIconColumnIndex: PropTypes.number,
+ expandedRowRender: PropTypes.func,
+ childrenColumnName: PropTypes.string,
+ indentSize: PropTypes.number,
+ // onExpand: PropTypes.func,
+ // onExpandedRowsChange: PropTypes.func,
+ columnManager: PropTypes.object.isRequired,
+ store: PropTypes.object.isRequired,
+ prefixCls: PropTypes.string.isRequired,
+ data: PropTypes.array,
+ getRowKey: PropTypes.func,
+})
+
+const ExpandableTable = {
+ name: 'ExpandableTable',
+ mixins: [BaseMixin],
+ props: initDefaultProps(ExpandableTableProps(), {
+ expandIconAsCell: false,
+ expandedRowClassName: () => '',
+ expandIconColumnIndex: 0,
+ defaultExpandAllRows: false,
+ defaultExpandedRowKeys: [],
+ childrenColumnName: 'children',
+ indentSize: 15,
+ }),
+
+ data () {
+ const {
+ data,
+ childrenColumnName,
+ defaultExpandAllRows,
+ expandedRowKeys,
+ defaultExpandedRowKeys,
+ getRowKey,
+ } = this
+
+ let finnalExpandedRowKeys = []
+ let rows = [...data]
+
+ if (defaultExpandAllRows) {
+ for (let i = 0; i < rows.length; i++) {
+ const row = rows[i]
+ finnalExpandedRowKeys.push(getRowKey(row, i))
+ rows = rows.concat(row[childrenColumnName] || [])
+ }
+ } else {
+ finnalExpandedRowKeys = expandedRowKeys || defaultExpandedRowKeys
+ }
+
+ // this.columnManager = props.columnManager
+ // this.store = props.store
+
+ this.store.setState({
+ expandedRowsHeight: {},
+ expandedRowKeys: finnalExpandedRowKeys,
+ })
+ return {}
+ },
+ watch: {
+ expandedRowKeys (val) {
+ this.store.setState({
+ expandedRowKeys: val,
+ })
+ },
+ },
+ methods: {
+ handleExpandChange (expanded, record, event, rowKey, destroy = false) {
+ if (event) {
+ event.preventDefault()
+ event.stopPropagation()
+ }
+
+ let { expandedRowKeys } = this.store.getState()
+
+ if (expanded) {
+ // row was expaned
+ expandedRowKeys = [...expandedRowKeys, rowKey]
+ } else {
+ // row was collapse
+ const expandedRowIndex = expandedRowKeys.indexOf(rowKey)
+ if (expandedRowIndex !== -1) {
+ expandedRowKeys = remove(expandedRowKeys, rowKey)
+ }
+ }
+
+ if (!this.expandedRowKeys) {
+ this.store.setState({ expandedRowKeys })
+ }
+ this.__emit('expandedRowsChange', expandedRowKeys)
+ if (!destroy) {
+ this.__emit('expand', expanded, record)
+ }
+ },
+
+ renderExpandIndentCell (rows, fixed) {
+ const { prefixCls, expandIconAsCell } = this
+ if (!expandIconAsCell || fixed === 'right' || !rows.length) {
+ return
+ }
+
+ const iconColumn = {
+ key: 'rc-table-expand-icon-cell',
+ className: `${prefixCls}-expand-icon-th`,
+ title: '',
+ rowSpan: rows.length,
+ }
+
+ rows[0].unshift({ ...iconColumn, column: iconColumn })
+ },
+
+ renderExpandedRow (record, index, render, className, ancestorKeys, indent, fixed) {
+ const { prefixCls, expandIconAsCell, indentSize } = this
+ let colCount
+ if (fixed === 'left') {
+ colCount = this.columnManager.leftLeafColumns().length
+ } else if (fixed === 'right') {
+ colCount = this.columnManager.rightLeafColumns().length
+ } else {
+ colCount = this.columnManager.leafColumns().length
+ }
+ const columns = [{
+ key: 'extra-row',
+ render: () => ({
+ props: {
+ colSpan: colCount,
+ },
+ children: fixed !== 'right' ? render(record, index, indent) : ' ',
+ }),
+ }]
+ if (expandIconAsCell && fixed !== 'right') {
+ columns.unshift({
+ key: 'expand-icon-placeholder',
+ render: () => null,
+ })
+ }
+ const parentKey = ancestorKeys[ancestorKeys.length - 1]
+ const rowKey = `${parentKey}-extra-row`
+ const components = {
+ body: {
+ row: 'tr',
+ cell: 'td',
+ },
+ }
+ return (
+ {}}
+ />
+ )
+ },
+
+ renderRows (renderRows, rows, record, index, indent, fixed, parentKey, ancestorKeys) {
+ const { expandedRowClassName, expandedRowRender, childrenColumnName } = this
+ const childrenData = record[childrenColumnName]
+ const nextAncestorKeys = [...ancestorKeys, parentKey]
+ const nextIndent = indent + 1
+
+ if (expandedRowRender) {
+ rows.push(
+ this.renderExpandedRow(
+ record,
+ index,
+ expandedRowRender,
+ expandedRowClassName(record, index, indent),
+ nextAncestorKeys,
+ nextIndent,
+ fixed,
+ ),
+ )
+ }
+
+ if (childrenData) {
+ rows.push(
+ ...renderRows(
+ childrenData,
+ nextIndent,
+ nextAncestorKeys,
+ )
+ )
+ }
+ },
+ },
+
+ render () {
+ const { data, childrenColumnName, $scopedSlots, $listeners } = this
+ const props = getOptionProps(this)
+ const needIndentSpaced = data.some(record => record[childrenColumnName])
+
+ return $scopedSlots.default && $scopedSlots.default({
+ props,
+ on: $listeners,
+ needIndentSpaced,
+ renderRows: this.renderRows,
+ handleExpandChange: this.handleExpandChange,
+ renderExpandIndentCell: this.renderExpandIndentCell,
+ })
+ },
+}
+
+export default connect()(ExpandableTable)
diff --git a/components/vc-table/src/HeadTable.jsx b/components/vc-table/src/HeadTable.jsx
new file mode 100644
index 000000000..99c6468f0
--- /dev/null
+++ b/components/vc-table/src/HeadTable.jsx
@@ -0,0 +1,59 @@
+import PropTypes from '../../_util/vue-types'
+import { measureScrollbar } from './utils'
+import BaseTable from './BaseTable'
+
+export default {
+ name: 'HeadTable',
+ props: {
+ fixed: PropTypes.oneOfType([
+ PropTypes.string,
+ PropTypes.bool,
+ ]),
+ columns: PropTypes.array.isRequired,
+ tableClassName: PropTypes.string.isRequired,
+ handleBodyScrollLeft: PropTypes.func.isRequired,
+ expander: PropTypes.object.isRequired,
+ },
+ inject: {
+ table: { default: {}},
+ },
+ render () {
+ const { columns, fixed, tableClassName, handleBodyScrollLeft, expander, table } = this
+ const { prefixCls, scroll, showHeader } = table
+ let { useFixedHeader } = table
+ const headStyle = {}
+
+ if (scroll.y) {
+ useFixedHeader = true
+ // Add negative margin bottom for scroll bar overflow bug
+ const scrollbarWidth = measureScrollbar('horizontal')
+ if (scrollbarWidth > 0 && !fixed) {
+ headStyle.marginBottom = `-${scrollbarWidth}px`
+ headStyle.paddingBottom = '0px'
+ }
+ }
+
+ if (!useFixedHeader || !showHeader) {
+ return null
+ }
+ return (
+
+ )
+ },
+
+}
diff --git a/components/vc-table/src/Table.jsx b/components/vc-table/src/Table.jsx
new file mode 100644
index 000000000..84217873a
--- /dev/null
+++ b/components/vc-table/src/Table.jsx
@@ -0,0 +1,507 @@
+
+import PropTypes from '../../_util/vue-types'
+import { debounce, warningOnce } from './utils'
+import shallowequal from 'shallowequal'
+import addEventListener from '../../_util/Dom/addEventListener'
+import { Provider, create } from '../../_util/store'
+import merge from 'lodash/merge'
+import ColumnManager from './ColumnManager'
+import classes from 'component-classes'
+import HeadTable from './HeadTable'
+import BodyTable from './BodyTable'
+import ExpandableTable from './ExpandableTable'
+import { initDefaultProps, getOptionProps } from '../../_util/props-util'
+import BaseMixin from '../../_util/BaseMixin'
+
+export default {
+ name: 'Table',
+ mixins: [BaseMixin],
+ props: initDefaultProps({
+ data: PropTypes.array,
+ useFixedHeader: PropTypes.bool,
+ columns: PropTypes.array,
+ prefixCls: PropTypes.string,
+ bodyStyle: PropTypes.object,
+ rowKey: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
+ rowClassName: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
+ // onRow: PropTypes.func,
+ // onHeaderRow: PropTypes.func,
+ // onRowClick: PropTypes.func,
+ // onRowDoubleClick: PropTypes.func,
+ // onRowContextMenu: PropTypes.func,
+ // onRowMouseEnter: PropTypes.func,
+ // onRowMouseLeave: PropTypes.func,
+ showHeader: PropTypes.bool,
+ title: PropTypes.func,
+ id: PropTypes.string,
+ footer: PropTypes.func,
+ emptyText: PropTypes.any,
+ scroll: PropTypes.object,
+ rowRef: PropTypes.func,
+ getBodyWrapper: PropTypes.func,
+ components: PropTypes.shape({
+ table: PropTypes.any,
+ header: PropTypes.shape({
+ wrapper: PropTypes.any,
+ row: PropTypes.any,
+ cell: PropTypes.any,
+ }),
+ body: PropTypes.shape({
+ wrapper: PropTypes.any,
+ row: PropTypes.any,
+ cell: PropTypes.any,
+ }),
+ }),
+ expandIconAsCell: PropTypes.bool,
+ expandedRowKeys: PropTypes.array,
+ expandedRowClassName: PropTypes.func,
+ defaultExpandAllRows: PropTypes.bool,
+ defaultExpandedRowKeys: PropTypes.array,
+ expandIconColumnIndex: PropTypes.number,
+ expandedRowRender: PropTypes.func,
+ childrenColumnName: PropTypes.string,
+ indentSize: PropTypes.number,
+ }, {
+ data: [],
+ useFixedHeader: false,
+ rowKey: 'key',
+ rowClassName: () => '',
+ prefixCls: 'rc-table',
+ bodyStyle: {},
+ showHeader: true,
+ scroll: {},
+ rowRef: () => null,
+ emptyText: () => 'No Data',
+ }),
+
+ // static childContextTypes = {
+ // table: PropTypes.any,
+ // components: PropTypes.any,
+ // },
+
+ created () {
+ [
+ 'rowClick',
+ 'rowDoubleclick',
+ 'rowContextmenu',
+ 'rowMouseenter',
+ 'rowMouseleave',
+ ].forEach(name => {
+ warningOnce(
+ this.$listeners[name] === undefined,
+ `${name} is deprecated, please use onRow instead.`,
+ )
+ })
+
+ warningOnce(
+ this.getBodyWrapper === undefined,
+ 'getBodyWrapper is deprecated, please use custom components instead.',
+ )
+
+ // this.columnManager = new ColumnManager(this.columns, this.$slots.default)
+
+ this.store = create({
+ currentHoverKey: null,
+ fixedColumnsHeadRowsHeight: [],
+ fixedColumnsBodyRowsHeight: [],
+ })
+
+ this.setScrollPosition('left')
+
+ this.debouncedWindowResize = debounce(this.handleWindowResize, 150)
+ },
+ data () {
+ this.preData = [...this.data]
+ return {
+ columnManager: new ColumnManager(this.columns, this.$slots.default),
+ sComponents: merge({
+ table: 'table',
+ header: {
+ wrapper: 'thead',
+ row: 'tr',
+ cell: 'th',
+ },
+ body: {
+ wrapper: 'tbody',
+ row: 'tr',
+ cell: 'td',
+ },
+ }, this.components),
+ }
+ },
+ provide () {
+ return {
+ table: this,
+ }
+ },
+ watch: {
+ components (val) {
+ this._components = merge({
+ table: 'table',
+ header: {
+ wrapper: 'thead',
+ row: 'tr',
+ cell: 'th',
+ },
+ body: {
+ wrapper: 'tbody',
+ row: 'tr',
+ cell: 'td',
+ },
+ }, this.components)
+ },
+ columns (val) {
+ if (val) {
+ this.columnManager.reset(val)
+ }
+ },
+ data (val) {
+ if (val.length === 0 && this.hasScrollX()) {
+ this.$nextTick(() => {
+ this.resetScrollX()
+ })
+ }
+ },
+ },
+
+ mounted () {
+ this.$nextTick(() => {
+ if (this.columnManager.isAnyColumnsFixed()) {
+ this.handleWindowResize()
+ this.resizeEvent = addEventListener(
+ window, 'resize', this.debouncedWindowResize
+ )
+ }
+ })
+ },
+
+ componentWillReceiveProps (nextProps) {
+ if (nextProps.columns && nextProps.columns !== this.props.columns) {
+ this.columnManager.reset(nextProps.columns)
+ } else if (nextProps.children !== this.props.children) {
+ this.columnManager.reset(null, nextProps.children)
+ }
+ },
+
+ updated (prevProps) {
+ if (this.columnManager.isAnyColumnsFixed()) {
+ this.handleWindowResize()
+ if (!this.resizeEvent) {
+ this.resizeEvent = addEventListener(
+ window, 'resize', this.debouncedWindowResize
+ )
+ }
+ }
+ },
+
+ beforeDestroy () {
+ if (this.resizeEvent) {
+ this.resizeEvent.remove()
+ }
+ if (this.debouncedWindowResize) {
+ this.debouncedWindowResize.cancel()
+ }
+ },
+ methods: {
+ getRowKey (record, index) {
+ const rowKey = this.rowKey
+ const key = (typeof rowKey === 'function')
+ ? rowKey(record, index) : record[rowKey]
+ warningOnce(
+ key !== undefined,
+ 'Each record in table should have a unique `key` prop,' +
+ 'or set `rowKey` to an unique primary key.'
+ )
+ return key === undefined ? index : key
+ },
+
+ setScrollPosition (position) {
+ this.scrollPosition = position
+ if (this.tableNode) {
+ const { prefixCls } = this
+ if (position === 'both') {
+ classes(this.tableNode)
+ .remove(new RegExp(`^${prefixCls}-scroll-position-.+$`))
+ .add(`${prefixCls}-scroll-position-left`)
+ .add(`${prefixCls}-scroll-position-right`)
+ } else {
+ classes(this.tableNode)
+ .remove(new RegExp(`^${prefixCls}-scroll-position-.+$`))
+ .add(`${prefixCls}-scroll-position-${position}`)
+ }
+ }
+ },
+
+ setScrollPositionClassName () {
+ const node = this.bodyTable
+ const scrollToLeft = node.scrollLeft === 0
+ const scrollToRight = node.scrollLeft + 1 >=
+ node.children[0].getBoundingClientRect().width -
+ node.getBoundingClientRect().width
+ if (scrollToLeft && scrollToRight) {
+ this.setScrollPosition('both')
+ } else if (scrollToLeft) {
+ this.setScrollPosition('left')
+ } else if (scrollToRight) {
+ this.setScrollPosition('right')
+ } else if (this.scrollPosition !== 'middle') {
+ this.setScrollPosition('middle')
+ }
+ },
+
+ handleWindowResize () {
+ this.syncFixedTableRowHeight()
+ this.setScrollPositionClassName()
+ },
+
+ syncFixedTableRowHeight () {
+ const tableRect = this.tableNode.getBoundingClientRect()
+ // If tableNode's height less than 0, suppose it is hidden and don't recalculate rowHeight.
+ // see: https://github.com/ant-design/ant-design/issues/4836
+ if (tableRect.height !== undefined && tableRect.height <= 0) {
+ return
+ }
+ const { prefixCls } = this.props
+ const headRows = this.headTable
+ ? this.headTable.querySelectorAll('thead')
+ : this.bodyTable.querySelectorAll('thead')
+ const bodyRows = this.bodyTable.querySelectorAll(`.${prefixCls}-row`) || []
+ const fixedColumnsHeadRowsHeight = [].map.call(
+ headRows, row => row.getBoundingClientRect().height || 'auto'
+ )
+ const fixedColumnsBodyRowsHeight = [].map.call(
+ bodyRows, row => row.getBoundingClientRect().height || 'auto'
+ )
+ const state = this.store.getState()
+ if (shallowequal(state.fixedColumnsHeadRowsHeight, fixedColumnsHeadRowsHeight) &&
+ shallowequal(state.fixedColumnsBodyRowsHeight, fixedColumnsBodyRowsHeight)) {
+ return
+ }
+
+ this.store.setState({
+ fixedColumnsHeadRowsHeight,
+ fixedColumnsBodyRowsHeight,
+ })
+ },
+
+ resetScrollX () {
+ if (this.headTable) {
+ this.headTable.scrollLeft = 0
+ }
+ if (this.bodyTable) {
+ this.bodyTable.scrollLeft = 0
+ }
+ },
+
+ hasScrollX () {
+ const { scroll = {}} = this
+ return 'x' in scroll
+ },
+
+ handleBodyScrollLeft (e) {
+ // Fix https://github.com/ant-design/ant-design/issues/7635
+ if (e.currentTarget !== e.target) {
+ return
+ }
+ const target = e.target
+ const { scroll = {}} = this
+ const { headTable, bodyTable } = this
+ if (target.scrollLeft !== this.lastScrollLeft && scroll.x) {
+ if (target === bodyTable && headTable) {
+ headTable.scrollLeft = target.scrollLeft
+ } else if (target === headTable && bodyTable) {
+ bodyTable.scrollLeft = target.scrollLeft
+ }
+ this.setScrollPositionClassName()
+ }
+ // Remember last scrollLeft for scroll direction detecting.
+ this.lastScrollLeft = target.scrollLeft
+ },
+
+ handleBodyScrollTop (e) {
+ const target = e.target
+ const { scroll = {}} = this
+ const { headTable, bodyTable, fixedColumnsBodyLeft, fixedColumnsBodyRight } = this
+ if (target.scrollTop !== this.lastScrollTop && scroll.y && target !== headTable) {
+ const scrollTop = target.scrollTop
+ if (fixedColumnsBodyLeft && target !== fixedColumnsBodyLeft) {
+ fixedColumnsBodyLeft.scrollTop = scrollTop
+ }
+ if (fixedColumnsBodyRight && target !== fixedColumnsBodyRight) {
+ fixedColumnsBodyRight.scrollTop = scrollTop
+ }
+ if (bodyTable && target !== bodyTable) {
+ bodyTable.scrollTop = scrollTop
+ }
+ }
+ // Remember last scrollTop for scroll direction detecting.
+ this.lastScrollTop = target.scrollTop
+ },
+
+ handleBodyScroll (e) {
+ this.handleBodyScrollLeft(e)
+ this.handleBodyScrollTop(e)
+ },
+
+ renderMainTable () {
+ const { scroll, prefixCls } = this
+ const isAnyColumnsFixed = this.columnManager.isAnyColumnsFixed()
+ const scrollable = isAnyColumnsFixed || scroll.x || scroll.y
+
+ const table = [
+ this.renderTable({
+ columns: this.columnManager.groupedColumns(),
+ isAnyColumnsFixed,
+ }),
+ this.renderEmptyText(),
+ this.renderFooter(),
+ ]
+
+ return scrollable ? (
+ {table}
+ ) : table
+ },
+
+ renderLeftFixedTable () {
+ const { prefixCls } = this
+
+ return (
+
+ {this.renderTable({
+ columns: this.columnManager.leftColumns(),
+ fixed: 'left',
+ })}
+
+ )
+ },
+ renderRightFixedTable () {
+ const { prefixCls } = this
+
+ return (
+
+ {this.renderTable({
+ columns: this.columnManager.rightColumns(),
+ fixed: 'right',
+ })}
+
+ )
+ },
+
+ renderTable (options) {
+ const { columns, fixed, isAnyColumnsFixed } = options
+ const { prefixCls, scroll = {}} = this
+ const tableClassName = (scroll.x || fixed) ? `${prefixCls}-fixed` : ''
+
+ const headTable = (
+
+ )
+
+ const bodyTable = (
+
+ )
+
+ return [headTable, bodyTable]
+ },
+
+ renderTitle () {
+ const { title, prefixCls } = this
+ return title ? (
+
+ {title(this.props.data)}
+
+ ) : null
+ },
+
+ renderFooter () {
+ const { footer, prefixCls } = this
+ return footer ? (
+
+ ) : null
+ },
+
+ renderEmptyText () {
+ const { emptyText, prefixCls, data } = this
+ if (data.length) {
+ return null
+ }
+ const emptyClassName = `${prefixCls}-placeholder`
+ return (
+
+ {(typeof emptyText === 'function') ? emptyText() : emptyText}
+
+ )
+ },
+ },
+
+ render () {
+ const props = getOptionProps(this)
+ const { $listeners, columnManager, getRowKey } = this
+ const prefixCls = props.prefixCls
+
+ let className = props.prefixCls
+ if (props.useFixedHeader || (props.scroll && props.scroll.y)) {
+ className += ` ${prefixCls}-fixed-header`
+ }
+ if (this.scrollPosition === 'both') {
+ className += ` ${prefixCls}-scroll-position-left ${prefixCls}-scroll-position-right`
+ } else {
+ className += ` ${prefixCls}-scroll-position-${this.scrollPosition}`
+ }
+ const hasLeftFixed = columnManager.isAnyColumnsLeftFixed()
+ const hasRightFixed = columnManager.isAnyColumnsRightFixed()
+
+ const expandableTableProps = {
+ props: {
+ ...props,
+ columnManager,
+ getRowKey,
+ },
+ on: { ...$listeners },
+ scopedSlots: {
+ default: (expander) => {
+ this.expander = expander
+ return (
+
+ {this.renderTitle()}
+
+ {this.renderMainTable()}
+ {hasLeftFixed && this.renderLeftFixedTable()}
+ {hasRightFixed && this.renderRightFixedTable()}
+
+
+ )
+ },
+ },
+ }
+ return (
+
+
+
+ )
+ },
+}
diff --git a/components/vc-table/src/TableCell.jsx b/components/vc-table/src/TableCell.jsx
new file mode 100644
index 000000000..8ae309633
--- /dev/null
+++ b/components/vc-table/src/TableCell.jsx
@@ -0,0 +1,110 @@
+import PropTypes from '../../_util/vue-types'
+import get from 'lodash/get'
+import { isValidElement } from '../../_util/props-util'
+
+export default {
+ name: 'TableCell',
+ props: {
+ record: PropTypes.object,
+ prefixCls: PropTypes.string,
+ index: PropTypes.number,
+ indent: PropTypes.number,
+ indentSize: PropTypes.number,
+ column: PropTypes.object,
+ expandIcon: PropTypes.any,
+ component: PropTypes.any,
+ },
+ methods: {
+ isInvalidRenderCellText (text) {
+ // debugger
+ return text && !isValidElement(text) &&
+ Object.prototype.toString.call(text) === '[object Object]'
+ },
+
+ handleClick (e) {
+ const { record, column: { onCellClick }} = this
+ if (onCellClick) {
+ onCellClick(record, e)
+ }
+ },
+ },
+
+ render (h) {
+ const {
+ record,
+ indentSize,
+ prefixCls,
+ indent,
+ index,
+ expandIcon,
+ column,
+ component: BodyCell,
+ } = this
+ const { dataIndex, render, className = '' } = column
+ const cls = column.class || className
+ // We should return undefined if no dataIndex is specified, but in order to
+ // be compatible with object-path's behavior, we return the record object instead.
+ let text
+ if (typeof dataIndex === 'number') {
+ text = get(record, dataIndex)
+ } else if (!dataIndex || dataIndex.length === 0) {
+ text = record
+ } else {
+ text = get(record, dataIndex)
+ }
+ const tdProps = {
+ props: {},
+ attrs: {},
+ class: cls,
+ on: {
+ click: this.handleClick,
+ },
+ }
+ let colSpan
+ let rowSpan
+
+ if (render) {
+ text = render(h, text, record, index)
+ if (this.isInvalidRenderCellText(text)) {
+ tdProps.attrs = text.attrs || text.props || {}
+ colSpan = tdProps.attrs.colSpan
+ rowSpan = tdProps.attrs.rowSpan
+ text = text.children
+ }
+ }
+
+ if (column.onCell) {
+ tdProps.attrs = { ...tdProps.attrs, ...column.onCell(record) }
+ }
+
+ // Fix https://github.com/ant-design/ant-design/issues/1202
+ if (this.isInvalidRenderCellText(text)) {
+ text = null
+ }
+
+ const indentText = expandIcon ? (
+
+ ) : null
+
+ if (rowSpan === 0 || colSpan === 0) {
+ return null
+ }
+
+ if (column.align) {
+ tdProps.style = { textAlign: column.align }
+ }
+
+ return (
+
+ {indentText}
+ {expandIcon}
+ {text}
+
+ )
+ },
+}
diff --git a/components/vc-table/src/TableHeader.jsx b/components/vc-table/src/TableHeader.jsx
new file mode 100644
index 000000000..9e6271b8e
--- /dev/null
+++ b/components/vc-table/src/TableHeader.jsx
@@ -0,0 +1,88 @@
+import PropTypes from '../../_util/vue-types'
+import TableHeaderRow from './TableHeaderRow'
+
+function getHeaderRows (columns, currentRow = 0, rows) {
+ rows = rows || []
+ rows[currentRow] = rows[currentRow] || []
+
+ columns.forEach(column => {
+ if (column.rowSpan && rows.length < column.rowSpan) {
+ while (rows.length < column.rowSpan) {
+ rows.push([])
+ }
+ }
+ const cell = {
+ key: column.key,
+ className: column.className || '',
+ children: column.title,
+ column,
+ }
+ if (column.children) {
+ getHeaderRows(column.children, currentRow + 1, rows)
+ }
+ if ('colSpan' in column) {
+ cell.colSpan = column.colSpan
+ }
+ if ('rowSpan' in column) {
+ cell.rowSpan = column.rowSpan
+ }
+ if (cell.colSpan !== 0) {
+ rows[currentRow].push(cell)
+ }
+ })
+ return rows.filter(row => row.length > 0)
+}
+
+export default {
+ name: 'TableHeader',
+ props: {
+ fixed: PropTypes.string,
+ columns: PropTypes.array.isRequired,
+ expander: PropTypes.object.isRequired,
+
+ },
+ inject: {
+ table: { default: {}},
+ },
+ methods: {
+ onHeaderRow () {
+ this.table.__emit('headerRow', ...arguments)
+ },
+ },
+
+ render () {
+ const { sComponents: components, prefixCls, showHeader } = this.table
+ const { expander, columns, fixed, onHeaderRow } = this
+
+ if (!showHeader) {
+ return null
+ }
+
+ const rows = getHeaderRows(columns)
+
+ expander.renderExpandIndentCell(rows, fixed)
+
+ const HeaderWrapper = components.header.wrapper
+
+ return (
+
+ {
+ rows.map((row, index) => (
+
+ ))
+ }
+
+ )
+ },
+
+}
+
diff --git a/components/vc-table/src/TableHeaderRow.jsx b/components/vc-table/src/TableHeaderRow.jsx
new file mode 100644
index 000000000..e87532b51
--- /dev/null
+++ b/components/vc-table/src/TableHeaderRow.jsx
@@ -0,0 +1,78 @@
+import PropTypes from '../../_util/vue-types'
+import { connect } from '../../_util/store'
+import { mergeProps } from '../../_util/props-util'
+
+const TableHeaderRow = {
+ props: {
+ index: PropTypes.number,
+ fixed: PropTypes.string,
+ columns: PropTypes.array,
+ rows: PropTypes.array,
+ row: PropTypes.array,
+ components: PropTypes.object,
+ height: PropTypes.any,
+ },
+ name: 'TableHeaderRow',
+ render () {
+ const { row, index, height, components, $listeners = {}} = this
+ const onHeaderRow = $listeners.headerRow
+ const HeaderRow = components.header.row
+ const HeaderCell = components.header.cell
+ const rowProps = onHeaderRow(row.map(cell => cell.column), index)
+ const customStyle = rowProps ? rowProps.style : {}
+ const style = { height, ...customStyle }
+
+ return (
+
+ {row.map((cell, i) => {
+ const { column, children, className, ...cellProps } = cell
+ const cls = cell.class || className
+ const customProps = column.onHeaderCell ? column.onHeaderCell(column) : {}
+ if (column.align) {
+ cellProps.style = { textAlign: column.align }
+ }
+ const headerCellProps = mergeProps({
+ attrs: {
+ ...cellProps,
+ },
+ class: cls,
+ }, {
+ ...customProps,
+ key: column.key || column.dataIndex || i,
+ })
+ return (
+
+ {children}
+
+ )
+ })}
+
+ )
+ },
+}
+
+function getRowHeight (state, props) {
+ const { fixedColumnsHeadRowsHeight } = state
+ const { columns, rows, fixed } = props
+ const headerHeight = fixedColumnsHeadRowsHeight[0]
+
+ if (!fixed) {
+ return null
+ }
+
+ if (headerHeight && columns) {
+ if (headerHeight === 'auto') {
+ return 'auto'
+ }
+ return `${headerHeight / rows.length}px`
+ }
+ return null
+}
+
+export default connect((state, props) => {
+ return {
+ height: getRowHeight(state, props),
+ }
+})(TableHeaderRow)
diff --git a/components/vc-table/src/TableRow.jsx b/components/vc-table/src/TableRow.jsx
new file mode 100644
index 000000000..a64570267
--- /dev/null
+++ b/components/vc-table/src/TableRow.jsx
@@ -0,0 +1,298 @@
+import PropTypes from '../../_util/vue-types'
+import { connect } from '../../_util/store'
+import TableCell from './TableCell'
+import { warningOnce } from './utils'
+import { initDefaultProps, mergeProps } from '../../_util/props-util'
+import BaseMixin from '../../_util/BaseMixin'
+function noop () {}
+const TableRow = {
+ name: 'TableRow',
+ mixins: [BaseMixin],
+ props: initDefaultProps({
+ // onRow: PropTypes.func,
+ // onRowClick: PropTypes.func,
+ // onRowDoubleClick: PropTypes.func,
+ // onRowContextMenu: PropTypes.func,
+ // onRowMouseEnter: PropTypes.func,
+ // onRowMouseLeave: PropTypes.func,
+ record: PropTypes.object,
+ prefixCls: PropTypes.string,
+ // onHover: PropTypes.func,
+ columns: PropTypes.array,
+ height: PropTypes.oneOfType([
+ PropTypes.string,
+ PropTypes.number,
+ ]),
+ index: PropTypes.number,
+ rowKey: PropTypes.oneOfType([
+ PropTypes.string,
+ PropTypes.number,
+ ]).isRequired,
+ className: PropTypes.string,
+ indent: PropTypes.number,
+ indentSize: PropTypes.number,
+ hasExpandIcon: PropTypes.func.isRequired,
+ hovered: PropTypes.bool.isRequired,
+ visible: PropTypes.bool.isRequired,
+ store: PropTypes.object.isRequired,
+ fixed: PropTypes.oneOfType([
+ PropTypes.string,
+ PropTypes.bool,
+ ]),
+ renderExpandIcon: PropTypes.func,
+ renderExpandIconCell: PropTypes.func,
+ components: PropTypes.any,
+ expandedRow: PropTypes.bool,
+ isAnyColumnsFixed: PropTypes.bool,
+ ancestorKeys: PropTypes.array.isRequired,
+ expandIconColumnIndex: PropTypes.number,
+ expandRowByClick: PropTypes.bool,
+ // visible: PropTypes.bool,
+ // hovered: PropTypes.bool,
+ // height: PropTypes.any,
+ }, {
+ expandIconColumnIndex: 0,
+ expandRowByClick: false,
+ hasExpandIcon () {},
+ renderExpandIcon () {},
+ renderExpandIconCell () {},
+ }),
+
+ data () {
+ this.shouldRender = this.visible
+ return {}
+ },
+
+ mounted () {
+ if (this.shouldRender) {
+ this.$nextTick(() => {
+ this.saveRowRef()
+ })
+ }
+ },
+ watch: {
+ visible (val) {
+ if (val) {
+ this.shouldRender = true
+ }
+ },
+ },
+
+ componentWillReceiveProps (nextProps) {
+ if (this.props.visible || (!this.props.visible && nextProps.visible)) {
+ this.shouldRender = true
+ }
+ },
+
+ shouldComponentUpdate (nextProps) {
+ return !!(this.props.visible || nextProps.visible)
+ },
+
+ updated () {
+ if (this.shouldRender && !this.rowRef) {
+ this.$nextTick(() => {
+ this.saveRowRef()
+ })
+ }
+ },
+ methods: {
+ onRowClick (event) {
+ const { record, index } = this
+ this.__emit('rowClick', record, index, event)
+ },
+
+ onRowDoubleClick (event) {
+ const { record, index } = this
+ this.__emit('rowDoubleClick', record, index, event)
+ },
+
+ onContextMenu (event) {
+ const { record, index } = this
+ this.__emit('rowContextmenu', record, index, event)
+ },
+
+ onMouseEnter (event) {
+ const { record, index, rowKey } = this
+ this.__emit('hover', true, rowKey)
+ this.__emit('rowMouseenter', record, index, event)
+ },
+
+ onMouseLeave (event) {
+ const { record, index, rowKey } = this
+ this.__emit('hover', false, rowKey)
+ this.__emit('rowMouseleave', record, index, event)
+ },
+
+ setExpanedRowHeight () {
+ const { store, rowKey } = this
+ let { expandedRowsHeight } = store.getState()
+ const height = this.rowRef.getBoundingClientRect().height
+ expandedRowsHeight = {
+ ...expandedRowsHeight,
+ [rowKey]: height,
+ }
+ store.setState({ expandedRowsHeight })
+ },
+
+ setRowHeight () {
+ const { store, index } = this
+ const fixedColumnsBodyRowsHeight = store.getState().fixedColumnsBodyRowsHeight.slice()
+ const height = this.rowRef.getBoundingClientRect().height
+ fixedColumnsBodyRowsHeight[index] = height
+ store.setState({ fixedColumnsBodyRowsHeight })
+ },
+
+ getStyle () {
+ const { height, visible } = this
+
+ if (height && height !== this.style.height) {
+ this.style = { ...this.style, height }
+ }
+
+ if (!visible && !this.style.display) {
+ this.style = { ...this.style, display: 'none' }
+ }
+
+ return this.style
+ },
+
+ saveRowRef () {
+ this.rowRef = this.$el
+
+ const { isAnyColumnsFixed, fixed, expandedRow, ancestorKeys } = this
+
+ if (!isAnyColumnsFixed) {
+ return
+ }
+
+ if (!fixed && expandedRow) {
+ this.setExpanedRowHeight()
+ }
+
+ if (!fixed && ancestorKeys.length >= 0) {
+ this.setRowHeight()
+ }
+ },
+ },
+
+ render () {
+ if (!this.shouldRender) {
+ return null
+ }
+
+ const {
+ prefixCls,
+ columns,
+ record,
+ index,
+ // onRow,
+ indent,
+ indentSize,
+ hovered,
+ height,
+ visible,
+ components,
+ hasExpandIcon,
+ renderExpandIcon,
+ renderExpandIconCell,
+ $listeners,
+ } = this
+ const { row: onRow = noop } = $listeners
+ const BodyRow = components.body.row
+ const BodyCell = components.body.cell
+
+ let className = ''
+
+ if (hovered) {
+ className += ` ${prefixCls}-hover`
+ }
+
+ const cells = []
+
+ renderExpandIconCell(cells)
+
+ for (let i = 0; i < columns.length; i++) {
+ const column = columns[i]
+
+ warningOnce(
+ column.onCellClick === undefined,
+ 'column[onCellClick] is deprecated, please use column[onCell] instead.',
+ )
+
+ cells.push(
+
+ )
+ }
+
+ const rowClassName =
+ `${prefixCls} ${className} ${prefixCls}-level-${indent}`.trim()
+
+ const rowProps = onRow(record, index)
+ const customStyle = rowProps ? rowProps.style : {}
+ let style = { height }
+
+ if (!visible) {
+ style.display = 'none'
+ }
+
+ style = { ...style, ...customStyle }
+ const bodyRowProps = mergeProps({
+ on: {
+ click: this.onRowClick,
+ dblclick: this.onRowDoubleClick,
+ mouseenter: this.onMouseEnter,
+ mouseleave: this.onMouseLeave,
+ contextmenu: this.onContextMenu,
+ },
+ class: rowClassName,
+ }, { ...rowProps, style })
+ return (
+
+ {cells}
+
+ )
+ },
+}
+
+function getRowHeight (state, props) {
+ const { expandedRowsHeight, fixedColumnsBodyRowsHeight } = state
+ const { fixed, index, rowKey } = props
+
+ if (!fixed) {
+ return null
+ }
+
+ if (expandedRowsHeight[rowKey]) {
+ return expandedRowsHeight[rowKey]
+ }
+
+ if (fixedColumnsBodyRowsHeight[index]) {
+ return fixedColumnsBodyRowsHeight[index]
+ }
+
+ return null
+}
+
+export default connect((state, props) => {
+ const { currentHoverKey, expandedRowKeys } = state
+ const { rowKey, ancestorKeys } = props
+ const visible = ancestorKeys.length === 0 || ancestorKeys.every(k => ~expandedRowKeys.indexOf(k))
+
+ return ({
+ visible,
+ hovered: currentHoverKey === rowKey,
+ height: getRowHeight(state, props),
+ })
+})(TableRow)
diff --git a/components/vc-table/src/utils.js b/components/vc-table/src/utils.js
new file mode 100644
index 000000000..5d3ef7c37
--- /dev/null
+++ b/components/vc-table/src/utils.js
@@ -0,0 +1,84 @@
+import warning from 'warning'
+
+let scrollbarSize
+
+// Measure scrollbar width for padding body during modal show/hide
+const scrollbarMeasure = {
+ position: 'absolute',
+ top: '-9999px',
+ width: '50px',
+ height: '50px',
+ overflow: 'scroll',
+}
+
+export function measureScrollbar (direction = 'vertical') {
+ if (typeof document === 'undefined' || typeof window === 'undefined') {
+ return 0
+ }
+ if (scrollbarSize) {
+ return scrollbarSize
+ }
+ const scrollDiv = document.createElement('div')
+ for (const scrollProp in scrollbarMeasure) {
+ if (scrollbarMeasure.hasOwnProperty(scrollProp)) {
+ scrollDiv.style[scrollProp] = scrollbarMeasure[scrollProp]
+ }
+ }
+ document.body.appendChild(scrollDiv)
+ let size = 0
+ if (direction === 'vertical') {
+ size = scrollDiv.offsetWidth - scrollDiv.clientWidth
+ } else if (direction === 'horizontal') {
+ size = scrollDiv.offsetHeight - scrollDiv.clientHeight
+ }
+
+ document.body.removeChild(scrollDiv)
+ scrollbarSize = size
+ return scrollbarSize
+}
+
+export function debounce (func, wait, immediate) {
+ let timeout
+ function debounceFunc () {
+ const context = this
+ const args = arguments
+ // https://fb.me/react-event-pooling
+ if (args[0] && args[0].persist) {
+ args[0].persist()
+ }
+ const later = () => {
+ timeout = null
+ if (!immediate) {
+ func.apply(context, args)
+ }
+ }
+ const callNow = immediate && !timeout
+ clearTimeout(timeout)
+ timeout = setTimeout(later, wait)
+ if (callNow) {
+ func.apply(context, args)
+ }
+ }
+ debounceFunc.cancel = function cancel () {
+ if (timeout) {
+ clearTimeout(timeout)
+ timeout = null
+ }
+ }
+ return debounceFunc
+}
+
+const warned = {}
+export function warningOnce (condition, format, args) {
+ if (!warned[format]) {
+ warning(condition, format, args)
+ warned[format] = !condition
+ }
+}
+
+export function remove (array, item) {
+ const index = array.indexOf(item)
+ const front = array.slice(0, index)
+ const last = array.slice(index + 1, array.length)
+ return front.concat(last)
+}
diff --git a/components/vc-tree/assets/icons.png b/components/vc-tree/assets/icons.png
new file mode 100644
index 000000000..ffda01ef1
Binary files /dev/null and b/components/vc-tree/assets/icons.png differ
diff --git a/components/vc-tree/assets/index.less b/components/vc-tree/assets/index.less
new file mode 100644
index 000000000..cde71e949
--- /dev/null
+++ b/components/vc-tree/assets/index.less
@@ -0,0 +1,192 @@
+@treePrefixCls: rc-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}-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('data:image/png;base64,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');
+
+ &.@{treePrefixCls}-icon__customize {
+ background-image: none;
+ }
+ }
+ &.@{treePrefixCls}-icon_loading {
+ margin-right: 2px;
+ vertical-align: top;
+ background: url('data:image/gif;base64,R0lGODlhEAAQAKIGAMLY8YSx5HOm4Mjc88/g9Ofw+v///wAAACH/C05FVFNDQVBFMi4wAwEAAAAh+QQFCgAGACwAAAAAEAAQAAADMGi6RbUwGjKIXCAA016PgRBElAVlG/RdLOO0X9nK61W39qvqiwz5Ls/rRqrggsdkAgAh+QQFCgAGACwCAAAABwAFAAADD2hqELAmiFBIYY4MAutdCQAh+QQFCgAGACwGAAAABwAFAAADD1hU1kaDOKMYCGAGEeYFCQAh+QQFCgAGACwKAAIABQAHAAADEFhUZjSkKdZqBQG0IELDQAIAIfkEBQoABgAsCgAGAAUABwAAAxBoVlRKgyjmlAIBqCDCzUoCACH5BAUKAAYALAYACgAHAAUAAAMPaGpFtYYMAgJgLogA610JACH5BAUKAAYALAIACgAHAAUAAAMPCAHWFiI4o1ghZZJB5i0JACH5BAUKAAYALAAABgAFAAcAAAMQCAFmIaEp1motpDQySMNFAgA7') no-repeat scroll 0 0 transparent;
+ }
+ &.@{treePrefixCls}-switcher {
+ &.@{treePrefixCls}-switcher-noop {
+ cursor: auto;
+ }
+ &.@{treePrefixCls}-switcher_open {
+ background-position: -93px -56px;
+ }
+ &.@{treePrefixCls}-switcher_close {
+ background-position: -75px -56px;
+ }
+ }
+ &.@{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;
+ }
+ }
+ }
+ }
+ }
+ &:not(.@{treePrefixCls}-show-line) {
+ .@{treePrefixCls}-switcher-noop {
+ background: none;
+ }
+ }
+ &.@{treePrefixCls}-show-line {
+ li:not(:last-child) {
+ > ul {
+ background: url('data:image/gif;base64,R0lGODlhCQACAIAAAMzMzP///yH5BAEAAAEALAAAAAAJAAIAAAIEjI9pUAA7') 0 0 repeat-y;
+ }
+ > .@{treePrefixCls}-switcher-noop {
+ background-position: -56px -18px;
+ }
+ }
+ li:last-child {
+ > .@{treePrefixCls}-switcher-noop {
+ background-position: -56px -36px;
+ }
+ }
+ }
+ &-child-tree {
+ display: none;
+ &-open {
+ display: block;
+ }
+ }
+ &-treenode-disabled {
+ >span:not(.@{treePrefixCls}-switcher),
+ >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;
+ }
+ &-icon__customize {
+ margin-right: 2px;
+ vertical-align: top;
+ }
+}
diff --git a/components/vc-tree/assets/line.gif b/components/vc-tree/assets/line.gif
new file mode 100644
index 000000000..d561d36a9
Binary files /dev/null and b/components/vc-tree/assets/line.gif differ
diff --git a/components/vc-tree/assets/loading.gif b/components/vc-tree/assets/loading.gif
new file mode 100644
index 000000000..e8c289293
Binary files /dev/null and b/components/vc-tree/assets/loading.gif differ
diff --git a/components/vc-tree/demo/basic.jsx b/components/vc-tree/demo/basic.jsx
new file mode 100644
index 000000000..322d26d96
--- /dev/null
+++ b/components/vc-tree/demo/basic.jsx
@@ -0,0 +1,110 @@
+/* eslint no-console:0 */
+/* eslint no-alert:0 */
+import PropTypes from '../../_util/vue-types'
+import Tree, { TreeNode } from '../index'
+import '../assets/index.less'
+import './basic.less'
+
+export default {
+ props: {
+ keys: PropTypes.array.def(['0-0-0-0']),
+ },
+ data () {
+ const keys = this.keys
+ return {
+ defaultExpandedKeys: keys,
+ defaultSelectedKeys: keys,
+ defaultCheckedKeys: keys,
+ switchIt: true,
+ showMore: false,
+ }
+ },
+ methods: {
+ onExpand (expandedKeys) {
+ console.log('onExpand', expandedKeys, arguments)
+ },
+ onSelect (selectedKeys, info) {
+ console.log('selected', selectedKeys, info)
+ this.selKey = info.node.$options.propsData.eventKey
+ },
+ onCheck (checkedKeys, info) {
+ console.log('onCheck', checkedKeys, info)
+ },
+ onEdit () {
+ setTimeout(() => {
+ console.log('current key: ', this.selKey)
+ }, 0)
+ },
+ onDel (e) {
+ if (!window.confirm('sure to delete?')) {
+ return
+ }
+ e.stopPropagation()
+ },
+ toggleChildren () {
+ this.showMore = !this.showMore
+ },
+ },
+
+ render () {
+ const customLabel = (
+ operations:
+ Edit
+
+ Delete
+ )
+ return (
+
simple
+ {/*
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ */}
+
+ Check on Click TreeNode
+
+
+
+
+
+
+
+ {this.showMore ?
+
+
+ : null}
+
+
+ )
+ },
+}
+
diff --git a/components/vc-tree/demo/basic.less b/components/vc-tree/demo/basic.less
new file mode 100644
index 000000000..cca0a1860
--- /dev/null
+++ b/components/vc-tree/demo/basic.less
@@ -0,0 +1,6 @@
+.rc-tree li a.rc-tree-node-selected{
+ .cus-label {
+ background-color: white;
+ border: none;
+ }
+}
diff --git a/components/vc-tree/index.js b/components/vc-tree/index.js
new file mode 100644
index 000000000..8f31b413f
--- /dev/null
+++ b/components/vc-tree/index.js
@@ -0,0 +1,3 @@
+'use strict'
+
+module.exports = require('./src/')
diff --git a/components/vc-tree/src/Tree.jsx b/components/vc-tree/src/Tree.jsx
new file mode 100644
index 000000000..6c042a530
--- /dev/null
+++ b/components/vc-tree/src/Tree.jsx
@@ -0,0 +1,602 @@
+import PropTypes from '../../_util/vue-types'
+import classNames from 'classnames'
+import warning from 'warning'
+import { initDefaultProps, getOptionProps } from '../../_util/props-util'
+import { cloneElement } from '../../_util/vnode'
+import BaseMixin from '../../_util/BaseMixin'
+import {
+ traverseTreeNodes, getStrictlyValue,
+ getFullKeyList, getPosition, getDragNodesKeys,
+ calcExpandedKeys, calcSelectedKeys,
+ calcCheckedKeys, calcDropPosition,
+ arrAdd, arrDel, posToArr,
+} from './util'
+
+/**
+ * Thought we still use `cloneElement` to pass `key`,
+ * other props can pass with context for future refactor.
+ */
+export const contextTypes = {
+ rcTree: PropTypes.shape({
+ root: PropTypes.object,
+
+ prefixCls: PropTypes.string,
+ selectable: PropTypes.bool,
+ showIcon: PropTypes.bool,
+ draggable: PropTypes.bool,
+ checkable: PropTypes.oneOfType([
+ PropTypes.bool,
+ PropTypes.object,
+ ]),
+ checkStrictly: PropTypes.bool,
+ disabled: PropTypes.bool,
+ openTransitionName: PropTypes.string,
+ openAnimation: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
+
+ loadData: PropTypes.func,
+ filterTreeNode: PropTypes.func,
+ renderTreeNode: PropTypes.func,
+
+ isKeyChecked: PropTypes.func,
+
+ // onNodeExpand: PropTypes.func,
+ // onNodeSelect: PropTypes.func,
+ // onNodeMouseEnter: PropTypes.func,
+ // onNodeMouseLeave: PropTypes.func,
+ // onNodeContextMenu: PropTypes.func,
+ // onNodeDragStart: PropTypes.func,
+ // onNodeDragEnter: PropTypes.func,
+ // onNodeDragOver: PropTypes.func,
+ // onNodeDragLeave: PropTypes.func,
+ // onNodeDragEnd: PropTypes.func,
+ // onNodeDrop: PropTypes.func,
+ // onBatchNodeCheck: PropTypes.func,
+ // onCheckConductFinished: PropTypes.func,
+ }),
+}
+
+const Tree = {
+ name: 'Tree',
+ mixins: [BaseMixin],
+ props: initDefaultProps({
+ prefixCls: PropTypes.string,
+ showLine: PropTypes.bool,
+ showIcon: PropTypes.bool,
+ focusable: PropTypes.bool,
+ selectable: PropTypes.bool,
+ disabled: PropTypes.bool,
+ multiple: PropTypes.bool,
+ checkable: PropTypes.oneOfType([
+ PropTypes.bool,
+ PropTypes.node,
+ ]),
+ 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,
+ // onDragEnd: PropTypes.func,
+ // onDrop: PropTypes.func,
+ filterTreeNode: PropTypes.func,
+ openTransitionName: PropTypes.string,
+ openAnimation: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
+ children: PropTypes.any,
+ }, {
+ prefixCls: 'rc-tree',
+ showLine: false,
+ showIcon: true,
+ selectable: true,
+ multiple: false,
+ checkable: false,
+ disabled: false,
+ checkStrictly: false,
+ draggable: false,
+ autoExpandParent: true,
+ defaultExpandAll: false,
+ defaultExpandedKeys: [],
+ defaultCheckedKeys: [],
+ defaultSelectedKeys: [],
+ }),
+
+ // static childContextTypes = contextTypes;
+
+ data () {
+ const props = getOptionProps(this)
+ const {
+ defaultExpandAll,
+ defaultExpandedKeys,
+ defaultCheckedKeys,
+ defaultSelectedKeys,
+ } = props
+ const children = this.$slots.default
+ // Sync state with props
+ const { checkedKeys = [], halfCheckedKeys = [] } =
+ calcCheckedKeys(defaultCheckedKeys, props, children) || {}
+
+ // Cache for check status to optimize
+ this.checkedBatch = null
+ this.propsToStateMap = {
+ expandedKeys: 'sExpandedKeys',
+ selectedKeys: 'sSelectedKeys',
+ checkedKeys: 'sCheckedKeys',
+ halfCheckedKeys: 'sHalfCheckedKeys',
+ }
+ return {
+ sExpandedKeys: defaultExpandAll
+ ? getFullKeyList(children)
+ : calcExpandedKeys(defaultExpandedKeys, props, children),
+ sSelectedKeys: calcSelectedKeys(defaultSelectedKeys, props, children),
+ sCheckedKeys: checkedKeys,
+ sHalfCheckedKeys: halfCheckedKeys,
+
+ ...(this.getSyncProps(props) || {}),
+ dragOverNodeKey: '',
+ dropPosition: null,
+ }
+ },
+ provide () {
+ return {
+ vcTree: this,
+ }
+ },
+
+ watch: {
+ children (val) {
+ const { checkedKeys = [], halfCheckedKeys = [] } = calcCheckedKeys(this.checkedKeys || this.sCheckedKeys, this.$props, this.$slots.default) || {}
+ this.sCheckedKeys = checkedKeys
+ this.sHalfCheckedKeys = halfCheckedKeys
+ },
+ expandedKeys (val) {
+ this.sExpandedKeys = calcExpandedKeys(this.expandedKeys, this.$props, this.$slots.default)
+ },
+ selectedKeys (val) {
+ this.sSelectedKeys = calcSelectedKeys(this.selectedKeys, this.$props, this.$slots.default)
+ },
+ checkedKeys (val) {
+ const { checkedKeys = [], halfCheckedKeys = [] } = calcCheckedKeys(this.checkedKeys, this.$props, this.$slots.default) || {}
+ this.sCheckedKeys = checkedKeys
+ this.sHalfCheckedKeys = halfCheckedKeys
+ },
+ },
+
+ // componentWillReceiveProps (nextProps) {
+ // // React 16 will not trigger update if new state is null
+ // this.setState(this.getSyncProps(nextProps, this.props))
+ // },
+
+ methods: {
+ onNodeDragStart (event, node) {
+ const { sExpandedKeys } = this
+ const { eventKey, children } = node.props
+
+ this.dragNode = node
+
+ this.setState({
+ dragNodesKeys: getDragNodesKeys(children, node),
+ sExpandedKeys: arrDel(sExpandedKeys, eventKey),
+ })
+ this.__emit('dragstart', { event, node })
+ },
+
+ /**
+ * [Legacy] Select handler is less small than node,
+ * so that this will trigger when drag enter node or select handler.
+ * This is a little tricky if customize css without padding.
+ * Better for use mouse move event to refresh drag state.
+ * But let's just keep it to avoid event trigger logic change.
+ */
+ onNodeDragEnter (event, node) {
+ const { sExpandedKeys } = this
+ const { pos, eventKey } = node.props
+
+ const dropPosition = calcDropPosition(event, node)
+
+ // Skip if drag node is self
+ if (
+ this.dragNode.props.eventKey === eventKey &&
+ dropPosition === 0
+ ) {
+ this.setState({
+ dragOverNodeKey: '',
+ dropPosition: null,
+ })
+ return
+ }
+
+ // Ref: https://github.com/react-component/tree/issues/132
+ // Add timeout to let onDragLevel fire before onDragEnter,
+ // so that we can clean drag props for onDragLeave node.
+ // Macro task for this:
+ // https://html.spec.whatwg.org/multipage/webappapis.html#clean-up-after-running-script
+ setTimeout(() => {
+ // Update drag over node
+ this.setState({
+ dragOverNodeKey: eventKey,
+ dropPosition,
+ })
+
+ // Side effect for delay drag
+ if (!this.delayedDragEnterLogic) {
+ this.delayedDragEnterLogic = {}
+ }
+ Object.keys(this.delayedDragEnterLogic).forEach((key) => {
+ clearTimeout(this.delayedDragEnterLogic[key])
+ })
+ this.delayedDragEnterLogic[pos] = setTimeout(() => {
+ const newExpandedKeys = arrAdd(sExpandedKeys, eventKey)
+ this.setState({
+ sExpandedKeys: newExpandedKeys,
+ })
+ this.__emit('dragenter', { event, node, expandedKeys: newExpandedKeys })
+ }, 400)
+ }, 0)
+ },
+ onNodeDragOver (event, node) {
+ this.__emit('dragover', { event, node })
+ },
+ onNodeDragLeave (event, node) {
+ this.setState({
+ dragOverNodeKey: '',
+ })
+ this.__emit('dragleave', { event, node })
+ },
+ onNodeDragEnd (event, node) {
+ this.setState({
+ dragOverNodeKey: '',
+ })
+ this.__emit('dragend', { event, node })
+ },
+ onNodeDrop (event, node) {
+ const { dragNodesKeys, dropPosition } = this
+
+ const { eventKey, pos } = node.props
+
+ this.setState({
+ dragOverNodeKey: '',
+ dropNodeKey: eventKey,
+ })
+
+ if (dragNodesKeys.indexOf(eventKey) !== -1) {
+ warning(false, 'Can not drop to dragNode(include it\'s children node)')
+ return
+ }
+
+ const posArr = posToArr(pos)
+
+ const dropResult = {
+ event,
+ node,
+ dragNode: this.dragNode,
+ dragNodesKeys: dragNodesKeys.slice(),
+ dropPosition: dropPosition + Number(posArr[posArr.length - 1]),
+ }
+
+ if (dropPosition !== 0) {
+ dropResult.dropToGap = true
+ }
+ this.__emit('drop', dropResult)
+ },
+
+ onNodeSelect (e, treeNode) {
+ const { sSelectedKeys, multiple, $slots: { default: children }} = this
+ const { selected, eventKey } = getOptionProps(treeNode)
+ const targetSelected = !selected
+ let selectedKeys = sSelectedKeys
+ // Update selected keys
+ if (!targetSelected) {
+ selectedKeys = arrDel(selectedKeys, eventKey)
+ } else if (!multiple) {
+ selectedKeys = [eventKey]
+ } else {
+ selectedKeys = arrAdd(selectedKeys, eventKey)
+ }
+
+ // [Legacy] Not found related usage in doc or upper libs
+ // [Legacy] TODO: add optimize prop to skip node process
+ const selectedNodes = []
+ if (selectedKeys.length) {
+ traverseTreeNodes(children, ({ node, key }) => {
+ if (selectedKeys.indexOf(key) !== -1) {
+ selectedNodes.push(node)
+ }
+ })
+ }
+
+ this.setUncontrolledState({ selectedKeys })
+
+ const eventObj = {
+ event: 'select',
+ selected: targetSelected,
+ node: treeNode,
+ selectedNodes,
+ }
+ this.__emit('select', selectedKeys, eventObj)
+ },
+
+ /**
+ * This will cache node check status to optimize update process.
+ * When Tree get trigger `onCheckConductFinished` will flush all the update.
+ */
+ onBatchNodeCheck (key, checked, halfChecked, startNode) {
+ if (startNode) {
+ this.checkedBatch = {
+ treeNode: startNode,
+ checked,
+ list: [],
+ }
+ }
+
+ // This code should never called
+ if (!this.checkedBatch) {
+ this.checkedBatch = {
+ list: [],
+ }
+ warning(
+ false,
+ 'Checked batch not init. This should be a bug. Please fire a issue.'
+ )
+ }
+
+ this.checkedBatch.list.push({ key, checked, halfChecked })
+ },
+
+ /**
+ * When top `onCheckConductFinished` called, will execute all batch update.
+ * And trigger `onCheck` event.
+ */
+ onCheckConductFinished () {
+ const { sCheckedKeys, sHalfCheckedKeys, checkStrictly, $slots: { default: children }} = this
+
+ // Use map to optimize update speed
+ const checkedKeySet = {}
+ const halfCheckedKeySet = {}
+
+ sCheckedKeys.forEach(key => {
+ checkedKeySet[key] = true
+ })
+ sHalfCheckedKeys.forEach(key => {
+ halfCheckedKeySet[key] = true
+ })
+
+ // Batch process
+ this.checkedBatch.list.forEach(({ key, checked, halfChecked }) => {
+ checkedKeySet[key] = checked
+ halfCheckedKeySet[key] = halfChecked
+ })
+ const newCheckedKeys = Object.keys(checkedKeySet).filter(key => checkedKeySet[key])
+ const newHalfCheckedKeys = Object.keys(halfCheckedKeySet).filter(key => halfCheckedKeySet[key])
+
+ // Trigger onChecked
+ let selectedObj
+
+ const eventObj = {
+ event: 'check',
+ node: this.checkedBatch.treeNode,
+ checked: this.checkedBatch.checked,
+ }
+
+ if (checkStrictly) {
+ selectedObj = getStrictlyValue(newCheckedKeys, newHalfCheckedKeys)
+
+ // [Legacy] TODO: add optimize prop to skip node process
+ eventObj.checkedNodes = []
+ traverseTreeNodes(children, ({ node, key }) => {
+ if (checkedKeySet[key]) {
+ eventObj.checkedNodes.push(node)
+ }
+ })
+
+ this.setUncontrolledState({ checkedKeys: newCheckedKeys })
+ } else {
+ selectedObj = newCheckedKeys
+
+ // [Legacy] TODO: add optimize prop to skip node process
+ eventObj.checkedNodes = []
+ eventObj.checkedNodesPositions = [] // [Legacy] TODO: not in API
+ eventObj.halfCheckedKeys = newHalfCheckedKeys // [Legacy] TODO: not in API
+ traverseTreeNodes(children, ({ node, pos, key }) => {
+ if (checkedKeySet[key]) {
+ eventObj.checkedNodes.push(node)
+ eventObj.checkedNodesPositions.push({ node, pos })
+ }
+ })
+
+ this.setUncontrolledState({
+ checkedKeys: newCheckedKeys,
+ halfCheckedKeys: newHalfCheckedKeys,
+ })
+ }
+ this.__emit('check', selectedObj, eventObj)
+
+ // Clean up
+ this.checkedBatch = null
+ },
+
+ onNodeExpand (e, treeNode) {
+ const { sExpandedKeys, loadData } = this
+ let expandedKeys = [...sExpandedKeys]
+ const { eventKey, expanded } = getOptionProps(treeNode)
+
+ // Update selected keys
+ const index = expandedKeys.indexOf(eventKey)
+ const targetExpanded = !expanded
+
+ warning(
+ (expanded && index !== -1) || (!expanded && index === -1)
+ , 'Expand state not sync with index check')
+
+ if (targetExpanded) {
+ expandedKeys = arrAdd(expandedKeys, eventKey)
+ } else {
+ expandedKeys = arrDel(expandedKeys, eventKey)
+ }
+
+ this.setUncontrolledState({ expandedKeys })
+ this.__emit('expand', expandedKeys, { node: treeNode, expanded: targetExpanded })
+
+ // Async Load data
+ if (targetExpanded && loadData) {
+ return loadData(treeNode).then(() => {
+ // [Legacy] Refresh logic
+ this.setUncontrolledState({ expandedKeys })
+ })
+ }
+
+ return null
+ },
+
+ onNodeMouseEnter (event, node) {
+ this.__emit('mouseenter', { event, node })
+ },
+
+ onNodeMouseLeave (event, node) {
+ this.__emit('mouseleave', { event, node })
+ },
+
+ onNodeContextMenu (event, node) {
+ event.preventDefault()
+ this.__emit('rightClick', { event, node })
+ },
+
+ /**
+ * Sync state with props if needed
+ */
+ getSyncProps (props = {}, prevProps) {
+ let needSync = false
+ const newState = {}
+ const myPrevProps = prevProps || {}
+ const children = this.$slots.default
+ function checkSync (name) {
+ if (props[name] !== myPrevProps[name]) {
+ needSync = true
+ return true
+ }
+ return false
+ }
+
+ // Children change will affect check box status.
+ // And no need to check when prev props not provided
+ if (prevProps && checkSync('children')) {
+ const { checkedKeys = [], halfCheckedKeys = [] } =
+ calcCheckedKeys(props.checkedKeys || this.sCheckedKeys, props, children) || {}
+ newState.sCheckedKeys = checkedKeys
+ newState.sHalfCheckedKeys = halfCheckedKeys
+ }
+
+ if (checkSync('expandedKeys')) {
+ newState.sExpandedKeys = calcExpandedKeys(props.expandedKeys, props, children)
+ }
+
+ if (checkSync('selectedKeys')) {
+ newState.sSelectedKeys = calcSelectedKeys(props.selectedKeys, props, children)
+ }
+
+ if (checkSync('checkedKeys')) {
+ const { checkedKeys = [], halfCheckedKeys = [] } =
+ calcCheckedKeys(props.checkedKeys, props, children) || {}
+ newState.sCheckedKeys = checkedKeys
+ newState.sHalfCheckedKeys = halfCheckedKeys
+ }
+
+ return needSync ? newState : null
+ },
+
+ /**
+ * Only update the value which is not in props
+ */
+ setUncontrolledState (state) {
+ let needSync = false
+ const newState = {}
+ const props = getOptionProps(this)
+ Object.keys(state).forEach(name => {
+ if (name in props) return
+
+ needSync = true
+ const key = this.propsToStateMap[name]
+ newState[key] = state[name]
+ })
+
+ this.setState(needSync ? newState : null)
+ },
+
+ isKeyChecked (key) {
+ const { sCheckedKeys = [] } = this
+ return sCheckedKeys.indexOf(key) !== -1
+ },
+
+ /**
+ * [Legacy] Original logic use `key` as tracking clue.
+ * We have to use `cloneElement` to pass `key`.
+ */
+ renderTreeNode (child, index, level = 0) {
+ const {
+ sExpandedKeys = [], sSelectedKeys = [], sHalfCheckedKeys = [],
+ dragOverNodeKey, dropPosition,
+ } = this
+ const pos = getPosition(level, index)
+ const key = child.key || pos
+
+ return cloneElement(child, {
+ props: {
+ eventKey: key,
+ expanded: sExpandedKeys.indexOf(key) !== -1,
+ selected: sSelectedKeys.indexOf(key) !== -1,
+ checked: this.isKeyChecked(key),
+ halfChecked: sHalfCheckedKeys.indexOf(key) !== -1,
+ pos,
+
+ // [Legacy] Drag props
+ dragOver: dragOverNodeKey === key && dropPosition === 0,
+ dragOverGapTop: dragOverNodeKey === key && dropPosition === -1,
+ dragOverGapBottom: dragOverNodeKey === key && dropPosition === 1,
+ },
+
+ })
+ },
+ },
+
+ render () {
+ const {
+ prefixCls, focusable,
+ showLine,
+ $slots: { default: children = [] },
+ } = this
+ const domProps = {}
+
+ return (
+ {}}
+ >
+ {children.map(this.renderTreeNode)}
+
+ )
+ },
+}
+
+export default Tree
diff --git a/components/vc-tree/src/TreeNode.jsx b/components/vc-tree/src/TreeNode.jsx
new file mode 100644
index 000000000..fb5ce8a86
--- /dev/null
+++ b/components/vc-tree/src/TreeNode.jsx
@@ -0,0 +1,589 @@
+import PropTypes from '../../_util/vue-types'
+import classNames from 'classnames'
+import warning from 'warning'
+import { contextTypes } from './Tree'
+import { getPosition, getNodeChildren, isCheckDisabled, traverseTreeNodes } from './util'
+import { initDefaultProps, getOptionProps, filterEmpty } from '../../_util/props-util'
+import BaseMixin from '../../_util/BaseMixin'
+import getTransitionProps from '../../_util/getTransitionProps'
+
+const ICON_OPEN = 'open'
+const ICON_CLOSE = 'close'
+
+const LOAD_STATUS_NONE = 0
+const LOAD_STATUS_LOADING = 1
+const LOAD_STATUS_LOADED = 2
+const LOAD_STATUS_FAILED = 0 // Action align, let's make failed same as init.
+
+const defaultTitle = '---'
+
+let onlyTreeNodeWarned = false // Only accept TreeNode
+
+export const nodeContextTypes = {
+ ...contextTypes,
+ vcTreeNode: PropTypes.shape({
+ onUpCheckConduct: PropTypes.func,
+ }),
+}
+
+const TreeNode = {
+ name: 'TreeNode',
+ mixins: [BaseMixin],
+ props: initDefaultProps({
+ eventKey: PropTypes.string, // Pass by parent `cloneElement`
+ prefixCls: PropTypes.string,
+ // className: PropTypes.string,
+ root: PropTypes.object,
+ // onSelect: PropTypes.func,
+
+ // By parent
+ expanded: PropTypes.bool,
+ selected: PropTypes.bool,
+ checked: PropTypes.bool,
+ halfChecked: PropTypes.bool,
+ title: PropTypes.any,
+ pos: PropTypes.string,
+ dragOver: PropTypes.bool,
+ dragOverGapTop: PropTypes.bool,
+ dragOverGapBottom: PropTypes.bool,
+
+ // By user
+ isLeaf: PropTypes.bool,
+ selectable: PropTypes.bool,
+ disabled: PropTypes.bool,
+ disableCheckbox: PropTypes.bool,
+ icon: PropTypes.any,
+ }, {
+ title: defaultTitle,
+ }),
+
+ data () {
+ return {
+ loadStatus: LOAD_STATUS_NONE,
+ dragNodeHighlight: false,
+ }
+ },
+ inject: {
+ vcTree: { default: {}},
+ },
+ provide () {
+ return {
+ vcTree: this.vcTree,
+ vcTreeNode: this,
+ }
+ },
+
+ // Isomorphic needn't load data in server side
+ mounted () {
+ this.syncLoadData(this.$props)
+ },
+ watch: {
+ expanded (val) {
+ this.syncLoadData({ expanded: val })
+ },
+ },
+
+ methods: {
+ onUpCheckConduct (treeNode, nodeChecked, nodeHalfChecked) {
+ const { pos: nodePos } = getOptionProps(treeNode)
+ const { eventKey, pos, checked, halfChecked } = this
+ const {
+ vcTree: { checkStrictly, isKeyChecked, onBatchNodeCheck, onCheckConductFinished },
+ vcTreeNode: { onUpCheckConduct } = {},
+ } = this
+
+ // Stop conduct when current node is disabled
+ if (isCheckDisabled(this)) {
+ onCheckConductFinished()
+ return
+ }
+
+ const children = this.getNodeChildren()
+
+ let checkedCount = nodeChecked ? 1 : 0
+
+ // Statistic checked count
+ children.forEach((node, index) => {
+ const childPos = getPosition(pos, index)
+
+ if (nodePos === childPos || isCheckDisabled(node)) {
+ return
+ }
+ if (isKeyChecked(node.key || childPos)) {
+ checkedCount += 1
+ }
+ })
+
+ // Static enabled children count
+ const enabledChildrenCount = children
+ .filter(node => !isCheckDisabled(node))
+ .length
+
+ // checkStrictly will not conduct check status
+ const nextChecked = checkStrictly ? checked : enabledChildrenCount === checkedCount
+ const nextHalfChecked = checkStrictly // propagated or child checked
+ ? halfChecked : (nodeHalfChecked || (checkedCount > 0 && !nextChecked))
+
+ // Add into batch update
+ if (checked !== nextChecked || halfChecked !== nextHalfChecked) {
+ onBatchNodeCheck(eventKey, nextChecked, nextHalfChecked)
+
+ if (onUpCheckConduct) {
+ onUpCheckConduct(this, nextChecked, nextHalfChecked)
+ } else {
+ // Flush all the update
+ onCheckConductFinished()
+ }
+ } else {
+ // Flush all the update
+ onCheckConductFinished()
+ }
+ },
+
+ onDownCheckConduct (nodeChecked) {
+ const { $slots } = this
+ const children = $slots.default || []
+ const { vcTree: { checkStrictly, isKeyChecked, onBatchNodeCheck }} = this
+ if (checkStrictly) return
+
+ traverseTreeNodes(children, ({ node, key }) => {
+ if (isCheckDisabled(node)) return false
+
+ if (nodeChecked !== isKeyChecked(key)) {
+ onBatchNodeCheck(key, nodeChecked, false)
+ }
+ })
+ },
+
+ onSelectorClick (e) {
+ if (this.isSelectable()) {
+ this.onSelect(e)
+ } else {
+ this.onCheck(e)
+ }
+ },
+
+ onSelect (e) {
+ if (this.isDisabled()) return
+
+ const { vcTree: { onNodeSelect }} = this
+ e.preventDefault()
+ onNodeSelect(e, this)
+ },
+
+ onCheck (e) {
+ if (this.isDisabled()) return
+
+ const { disableCheckbox, checked, eventKey } = this
+ const {
+ vcTree: { checkable, onBatchNodeCheck, onCheckConductFinished },
+ vcTreeNode: { onUpCheckConduct } = {},
+ } = this
+
+ if (!checkable || disableCheckbox) return
+
+ e.preventDefault()
+ const targetChecked = !checked
+ onBatchNodeCheck(eventKey, targetChecked, false, this)
+
+ // Children conduct
+ this.onDownCheckConduct(targetChecked)
+
+ // Parent conduct
+ if (onUpCheckConduct) {
+ onUpCheckConduct(this, targetChecked, false)
+ } else {
+ onCheckConductFinished()
+ }
+ },
+
+ onMouseEnter (e) {
+ const { vcTree: { onNodeMouseEnter }} = this
+ onNodeMouseEnter(e, this)
+ },
+
+ onMouseLeave (e) {
+ const { vcTree: { onNodeMouseLeave }} = this
+ onNodeMouseLeave(e, this)
+ },
+
+ onContextMenu (e) {
+ const { vcTree: { onNodeContextMenu }} = this
+ onNodeContextMenu(e, this)
+ },
+
+ onDragStart (e) {
+ const { vcTree: { onNodeDragStart }} = this
+
+ e.stopPropagation()
+ this.setState({
+ dragNodeHighlight: true,
+ })
+ onNodeDragStart(e, this)
+
+ try {
+ // ie throw error
+ // firefox-need-it
+ e.dataTransfer.setData('text/plain', '')
+ } catch (error) {
+ // empty
+ }
+ },
+
+ onDragEnter (e) {
+ const { vcTree: { onNodeDragEnter }} = this
+
+ e.preventDefault()
+ e.stopPropagation()
+ onNodeDragEnter(e, this)
+ },
+
+ onDragOver (e) {
+ const { vcTree: { onNodeDragOver }} = this
+
+ e.preventDefault()
+ e.stopPropagation()
+ onNodeDragOver(e, this)
+ },
+
+ onDragLeave (e) {
+ const { vcTree: { onNodeDragLeave }} = this
+
+ e.stopPropagation()
+ onNodeDragLeave(e, this)
+ },
+
+ onDragEnd (e) {
+ const { vcTree: { onNodeDragEnd }} = this
+
+ e.stopPropagation()
+ this.setState({
+ dragNodeHighlight: false,
+ })
+ onNodeDragEnd(e, this)
+ },
+
+ onDrop (e) {
+ const { vcTree: { onNodeDrop }} = this
+
+ e.preventDefault()
+ e.stopPropagation()
+ this.setState({
+ dragNodeHighlight: false,
+ })
+ onNodeDrop(e, this)
+ },
+
+ // Disabled item still can be switch
+ onExpand (e) {
+ const { vcTree: { onNodeExpand }} = this
+ const callbackPromise = onNodeExpand(e, this)
+
+ // Promise like
+ if (callbackPromise && callbackPromise.then) {
+ this.setState({ loadStatus: LOAD_STATUS_LOADING })
+
+ callbackPromise.then(() => {
+ this.setState({ loadStatus: LOAD_STATUS_LOADED })
+ }).catch(() => {
+ this.setState({ loadStatus: LOAD_STATUS_FAILED })
+ })
+ }
+ },
+
+ // Drag usage
+ setSelectHandle (node) {
+ this.selectHandle = node
+ },
+
+ getNodeChildren () {
+ const { $slots: { default: children }} = this
+ const originList = filterEmpty(children)
+ const targetList = getNodeChildren(originList)
+
+ if (originList.length !== targetList.length && !onlyTreeNodeWarned) {
+ onlyTreeNodeWarned = true
+ warning(false, 'Tree only accept TreeNode as children.')
+ }
+
+ return targetList
+ },
+
+ getNodeState () {
+ const { expanded } = this
+
+ if (this.isLeaf2()) {
+ return null
+ }
+
+ return expanded ? ICON_OPEN : ICON_CLOSE
+ },
+
+ isLeaf2 () {
+ const { isLeaf, loadStatus } = this
+ const { vcTree: { loadData }} = this
+
+ const hasChildren = this.getNodeChildren().length !== 0
+
+ return (
+ isLeaf ||
+ (!loadData && !hasChildren) ||
+ (loadData && loadStatus === LOAD_STATUS_LOADED && !hasChildren)
+ )
+ },
+
+ isDisabled () {
+ const { disabled } = this
+ const { vcTree: { disabled: treeDisabled }} = this
+
+ // Follow the logic of Selectable
+ if (disabled === false) {
+ return false
+ }
+
+ return !!(treeDisabled || disabled)
+ },
+
+ isSelectable () {
+ const { selectable } = this
+ const { vcTree: { selectable: treeSelectable }} = this
+
+ // Ignore when selectable is undefined or null
+ if (typeof selectable === 'boolean') {
+ return selectable
+ }
+
+ return treeSelectable
+ },
+
+ // Load data to avoid default expanded tree without data
+ syncLoadData (props) {
+ const { loadStatus } = this
+ const { expanded } = props
+ const { vcTree: { loadData }} = this
+
+ if (loadData && loadStatus === LOAD_STATUS_NONE && expanded && !this.isLeaf2()) {
+ this.setState({ loadStatus: LOAD_STATUS_LOADING })
+
+ loadData(this).then(() => {
+ this.setState({ loadStatus: LOAD_STATUS_LOADED })
+ }).catch(() => {
+ this.setState({ loadStatus: LOAD_STATUS_FAILED })
+ })
+ }
+ },
+
+ // Switcher
+ renderSwitcher () {
+ const { expanded } = this
+ const { vcTree: { prefixCls }} = this
+
+ if (this.isLeaf2()) {
+ return
+ }
+
+ return (
+
+ )
+ },
+
+ // Checkbox
+ renderCheckbox () {
+ const { checked, halfChecked, disableCheckbox } = this
+ const { vcTree: { prefixCls, checkable }} = this
+ const disabled = this.isDisabled()
+
+ if (!checkable) return null
+
+ // [Legacy] Custom element should be separate with `checkable` in future
+ const $custom = typeof checkable !== 'boolean' ? checkable : null
+
+ return (
+
+ {$custom}
+
+ )
+ },
+
+ renderIcon () {
+ const { loadStatus } = this
+ const { vcTree: { prefixCls }} = this
+
+ return (
+
+ )
+ },
+
+ // Icon + Title
+ renderSelector () {
+ const { title, selected, icon, loadStatus, dragNodeHighlight } = this
+ const { vcTree: { prefixCls, showIcon, draggable, loadData }} = this
+ const disabled = this.isDisabled()
+
+ const wrapClass = `${prefixCls}-node-content-wrapper`
+
+ // Icon - Still show loading icon when loading without showIcon
+ let $icon
+
+ if (showIcon) {
+ $icon = icon ? (
+
+ {typeof icon === 'function'
+ ? icon(this.$props) : icon}
+
+ ) : this.renderIcon()
+ } else if (loadData && loadStatus === LOAD_STATUS_LOADING) {
+ $icon = this.renderIcon()
+ }
+
+ // Title
+ const $title = {title}
+
+ return (
+
+ {$icon}{$title}
+
+ )
+ },
+
+ // Children list wrapped with `Animation`
+ renderChildren () {
+ const { expanded, pos } = this
+ const { vcTree: {
+ prefixCls,
+ openTransitionName, openAnimation,
+ renderTreeNode,
+ }} = this
+
+ // [Legacy] Animation control
+ const renderFirst = this.renderFirst
+ this.renderFirst = 1
+ let transitionAppear = true
+ if (!renderFirst && expanded) {
+ transitionAppear = false
+ }
+
+ let animProps = {}
+ if (openTransitionName) {
+ animProps = getTransitionProps(openTransitionName, { appear: transitionAppear })
+ } else if (typeof openAnimation === 'object') {
+ animProps = { ...openAnimation }
+ if (!transitionAppear) {
+ delete animProps.props.appear
+ }
+ }
+
+ // Children TreeNode
+ const nodeList = this.getNodeChildren()
+
+ if (nodeList.length === 0) {
+ return null
+ }
+
+ let $children
+ if (expanded) {
+ $children = (
+
+ {nodeList.map((node, index) => (
+ renderTreeNode(node, index, pos)
+ ))}
+
+ )
+ }
+
+ return (
+
+ {$children}
+
+ )
+ },
+ },
+
+ render () {
+ const {
+ dragOver, dragOverGapTop, dragOverGapBottom,
+ } = this
+ const { vcTree: {
+ prefixCls,
+ filterTreeNode,
+ }} = this
+ const disabled = this.isDisabled()
+
+ return (
+
+ {this.renderSwitcher()}
+ {this.renderCheckbox()}
+ {this.renderSelector()}
+ {this.renderChildren()}
+
+ )
+ },
+}
+
+TreeNode.isTreeNode = 1
+
+export default TreeNode
diff --git a/components/vc-tree/src/index.js b/components/vc-tree/src/index.js
new file mode 100644
index 000000000..e8c0e81b5
--- /dev/null
+++ b/components/vc-tree/src/index.js
@@ -0,0 +1,25 @@
+import { getOptionProps } from '../../_util/props-util'
+import Tree from './Tree'
+import TreeNode from './TreeNode'
+Tree.TreeNode = TreeNode
+
+//
+const NewTree = {
+ TreeNode: TreeNode,
+ props: Tree.props,
+ render () {
+ const { $listeners, $slots } = this
+ const treeProps = {
+ props: {
+ ...getOptionProps(this),
+ children: $slots.default,
+ },
+ on: $listeners,
+ }
+ return (
+ {$slots.default}
+ )
+ },
+}
+export { TreeNode }
+export default NewTree
diff --git a/components/vc-tree/src/util.js b/components/vc-tree/src/util.js
new file mode 100644
index 000000000..712853249
--- /dev/null
+++ b/components/vc-tree/src/util.js
@@ -0,0 +1,398 @@
+/* eslint no-loop-func: 0*/
+import warning from 'warning'
+import { getSlotOptions, getOptionProps } from '../../_util/props-util'
+
+export function arrDel (list, value) {
+ const clone = list.slice()
+ const index = clone.indexOf(value)
+ if (index >= 0) {
+ clone.splice(index, 1)
+ }
+ return clone
+}
+
+export function arrAdd (list, value) {
+ const clone = list.slice()
+ if (clone.indexOf(value) === -1) {
+ clone.push(value)
+ }
+ return clone
+}
+
+export function posToArr (pos) {
+ return pos.split('-')
+}
+
+// Only used when drag, not affect SSR.
+export function getOffset (ele) {
+ if (!ele.getClientRects().length) {
+ return { top: 0, left: 0 }
+ }
+
+ const rect = ele.getBoundingClientRect()
+ if (rect.width || rect.height) {
+ const doc = ele.ownerDocument
+ const win = doc.defaultView
+ const docElem = doc.documentElement
+
+ return {
+ top: rect.top + win.pageYOffset - docElem.clientTop,
+ left: rect.left + win.pageXOffset - docElem.clientLeft,
+ }
+ }
+
+ return rect
+}
+
+export function getPosition (level, index) {
+ return `${level}-${index}`
+}
+
+export function getNodeChildren (children = []) {
+ return children
+ .filter(child => getSlotOptions(child).isTreeNode)
+}
+
+export function isCheckDisabled (node) {
+ const { disabled, disableCheckbox } = getOptionProps(node) || {}
+ return !!(disabled || disableCheckbox)
+}
+
+export function traverseTreeNodes (treeNodes, subTreeData, callback) {
+ if (typeof subTreeData === 'function') {
+ callback = subTreeData
+ subTreeData = false
+ }
+
+ function processNode (node, index, parent) {
+ const children = node ? node.componentOptions.children : treeNodes
+ const pos = node ? getPosition(parent.pos, index) : 0
+
+ // Filter children
+ const childList = getNodeChildren(children)
+
+ // Process node if is not root
+ if (node) {
+ const data = {
+ node,
+ index,
+ pos,
+ key: node.key || pos,
+ parentPos: parent.node ? parent.pos : null,
+ }
+
+ // Children data is not must have
+ if (subTreeData) {
+ // Statistic children
+ const subNodes = []
+ childList.forEach((subNode, subIndex) => {
+ // Provide limit snapshot
+ const subPos = getPosition(pos, index)
+ subNodes.push({
+ node: subNode,
+ key: subNode.key || subPos,
+ pos: subPos,
+ index: subIndex,
+ })
+ })
+ data.subNodes = subNodes
+ }
+
+ // Can break traverse by return false
+ if (callback(data) === false) {
+ return
+ }
+ }
+
+ // Process children node
+ childList.forEach((subNode, subIndex) => {
+ processNode(subNode, subIndex, { node, pos })
+ })
+ }
+
+ processNode(null)
+}
+
+/**
+ * [Legacy] Return halfChecked when it has value.
+ * @param checkedKeys
+ * @param halfChecked
+ * @returns {*}
+ */
+export function getStrictlyValue (checkedKeys, halfChecked) {
+ if (halfChecked) {
+ return { checked: checkedKeys, halfChecked }
+ }
+ return checkedKeys
+}
+
+export function getFullKeyList (treeNodes) {
+ const keyList = []
+ traverseTreeNodes(treeNodes, ({ key }) => {
+ keyList.push(key)
+ })
+ return keyList
+}
+
+/**
+ * Check position relation.
+ * @param parentPos
+ * @param childPos
+ * @param directly only directly parent can be true
+ * @returns {boolean}
+ */
+export function isParent (parentPos, childPos, directly = false) {
+ if (!parentPos || !childPos || parentPos.length > childPos.length) return false
+
+ const parentPath = posToArr(parentPos)
+ const childPath = posToArr(childPos)
+
+ // Directly check
+ if (directly && parentPath.length !== childPath.length - 1) return false
+
+ const len = parentPath.length
+ for (let i = 0; i < len; i += 1) {
+ if (parentPath[i] !== childPath[i]) return false
+ }
+
+ return true
+}
+
+/**
+ * Statistic TreeNodes info
+ * @param treeNodes
+ * @returns {{}}
+ */
+export function getNodesStatistic (treeNodes) {
+ const statistic = {
+ keyNodes: {},
+ posNodes: {},
+ nodeList: [],
+ }
+
+ traverseTreeNodes(treeNodes, true, ({ node, index, pos, key, subNodes, parentPos }) => {
+ const data = { node, index, pos, key, subNodes, parentPos }
+ statistic.keyNodes[key] = data
+ statistic.posNodes[pos] = data
+ statistic.nodeList.push(data)
+ })
+
+ return statistic
+}
+
+export function getDragNodesKeys (treeNodes, node) {
+ const { eventKey, pos } = getOptionProps(node)
+ const dragNodesKeys = []
+
+ traverseTreeNodes(treeNodes, ({ pos: nodePos, key }) => {
+ if (isParent(pos, nodePos)) {
+ dragNodesKeys.push(key)
+ }
+ })
+ dragNodesKeys.push(eventKey || pos)
+ return dragNodesKeys
+}
+
+export function calcDropPosition (event, treeNode) {
+ const offsetTop = getOffset(treeNode.selectHandle).top
+ const offsetHeight = treeNode.selectHandle.offsetHeight
+ const pageY = event.pageY
+ const gapHeight = 2 // [Legacy] TODO: remove hard code
+ if (pageY > offsetTop + offsetHeight - gapHeight) {
+ return 1
+ }
+ if (pageY < offsetTop + gapHeight) {
+ return -1
+ }
+ return 0
+}
+
+/**
+ * Auto expand all related node when sub node is expanded
+ * @param keyList
+ * @param props
+ * @returns [string]
+ */
+export function calcExpandedKeys (keyList, props, children = []) {
+ if (!keyList) {
+ return []
+ }
+
+ const { autoExpandParent } = props
+
+ // Do nothing if not auto expand parent
+ if (!autoExpandParent) {
+ return keyList
+ }
+
+ // Fill parent expanded keys
+ const { keyNodes, nodeList } = getNodesStatistic(children)
+ const needExpandKeys = {}
+ const needExpandPathList = []
+
+ // Fill expanded nodes
+ keyList.forEach((key) => {
+ const node = keyNodes[key]
+ if (node) {
+ needExpandKeys[key] = true
+ needExpandPathList.push(node.pos)
+ }
+ })
+
+ // Match parent by path
+ nodeList.forEach(({ pos, key }) => {
+ if (needExpandPathList.some(childPos => isParent(pos, childPos))) {
+ needExpandKeys[key] = true
+ }
+ })
+
+ const calcExpandedKeyList = Object.keys(needExpandKeys)
+
+ // [Legacy] Return origin keyList if calc list is empty
+ return calcExpandedKeyList.length ? calcExpandedKeyList : keyList
+}
+
+/**
+ * Return selectedKeys according with multiple prop
+ * @param selectedKeys
+ * @param props
+ * @returns [string]
+ */
+export function calcSelectedKeys (selectedKeys, props) {
+ if (!selectedKeys) {
+ return undefined
+ }
+
+ const { multiple } = props
+ if (multiple) {
+ return selectedKeys.slice()
+ }
+
+ if (selectedKeys.length) {
+ return [selectedKeys[0]]
+ }
+ return selectedKeys
+}
+
+/**
+ * Check conduct is by key level. It pass though up & down.
+ * When conduct target node is check means already conducted will be skip.
+ * @param treeNodes
+ * @param checkedKeys
+ * @returns {{checkedKeys: Array, halfCheckedKeys: Array}}
+ */
+export function calcCheckStateConduct (treeNodes, checkedKeys) {
+ const { keyNodes, posNodes } = getNodesStatistic(treeNodes)
+
+ const tgtCheckedKeys = {}
+ const tgtHalfCheckedKeys = {}
+
+ // Conduct up
+ function conductUp (key, halfChecked) {
+ if (tgtCheckedKeys[key]) return
+
+ const { subNodes = [], parentPos, node } = keyNodes[key]
+ if (isCheckDisabled(node)) return
+
+ const allSubChecked = !halfChecked && subNodes
+ .filter(sub => !isCheckDisabled(sub.node))
+ .every(sub => tgtCheckedKeys[sub.key])
+
+ if (allSubChecked) {
+ tgtCheckedKeys[key] = true
+ } else {
+ tgtHalfCheckedKeys[key] = true
+ }
+
+ if (parentPos !== null) {
+ conductUp(posNodes[parentPos].key, !allSubChecked)
+ }
+ }
+
+ // Conduct down
+ function conductDown (key) {
+ if (tgtCheckedKeys[key]) return
+ const { subNodes = [], node } = keyNodes[key]
+
+ if (isCheckDisabled(node)) return
+
+ tgtCheckedKeys[key] = true
+
+ subNodes.forEach((sub) => {
+ conductDown(sub.key)
+ })
+ }
+
+ function conduct (key) {
+ if (!keyNodes[key]) {
+ warning(false, `'${key}' does not exist in the tree.`)
+ return
+ }
+
+ const { subNodes = [], parentPos, node } = keyNodes[key]
+ if (isCheckDisabled(node)) return
+
+ tgtCheckedKeys[key] = true
+
+ // Conduct down
+ subNodes
+ .filter(sub => !isCheckDisabled(sub.node))
+ .forEach((sub) => {
+ conductDown(sub.key)
+ })
+
+ // Conduct up
+ if (parentPos !== null) {
+ conductUp(posNodes[parentPos].key)
+ }
+ }
+
+ checkedKeys.forEach((key) => {
+ conduct(key)
+ })
+
+ return {
+ checkedKeys: Object.keys(tgtCheckedKeys),
+ halfCheckedKeys: Object.keys(tgtHalfCheckedKeys)
+ .filter(key => !tgtCheckedKeys[key]),
+ }
+}
+
+/**
+ * Calculate the value of checked and halfChecked keys.
+ * This should be only run in init or props changed.
+ */
+export function calcCheckedKeys (keys, props, children = []) {
+ const { checkable, checkStrictly } = props
+
+ if (!checkable || !keys) {
+ return null
+ }
+
+ // Convert keys to object format
+ let keyProps
+ if (Array.isArray(keys)) {
+ // [Legacy] Follow the api doc
+ keyProps = {
+ checkedKeys: keys,
+ halfCheckedKeys: undefined,
+ }
+ } else if (typeof keys === 'object') {
+ keyProps = {
+ checkedKeys: keys.checked || undefined,
+ halfCheckedKeys: keys.halfChecked || undefined,
+ }
+ } else {
+ warning(false, '`CheckedKeys` is not an array or an object')
+ return null
+ }
+
+ // Do nothing if is checkStrictly mode
+ if (checkStrictly) {
+ return keyProps
+ }
+
+ // Conduct calculate the check status
+ const { checkedKeys = [] } = keyProps
+ return calcCheckStateConduct(children, checkedKeys)
+}
diff --git a/package-lock.json b/package-lock.json
index be35897c4..b2cdd9e77 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
{
"name": "vue-antd-ui",
- "version": "0.1.0",
+ "version": "0.1.1",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -206,7 +206,7 @@
},
"abab": {
"version": "1.0.4",
- "resolved": "https://registry.npmjs.org/abab/-/abab-1.0.4.tgz",
+ "resolved": "http://registry.npm.taobao.org/abab/download/abab-1.0.4.tgz",
"integrity": "sha1-X6rZwsB/YN12dw9xzwJbYqY8/U4=",
"dev": true,
"optional": true
@@ -251,7 +251,7 @@
},
"acorn-globals": {
"version": "1.0.9",
- "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-1.0.9.tgz",
+ "resolved": "http://registry.npm.taobao.org/acorn-globals/download/acorn-globals-1.0.9.tgz",
"integrity": "sha1-VbtemGkVB7dFedBRNBMhfDgMVM8=",
"dev": true,
"optional": true,
@@ -261,7 +261,7 @@
"dependencies": {
"acorn": {
"version": "2.7.0",
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-2.7.0.tgz",
+ "resolved": "http://registry.npm.taobao.org/acorn/download/acorn-2.7.0.tgz",
"integrity": "sha1-q259nYhqrKiwhbwzEreaGYQz8Oc=",
"dev": true,
"optional": true
@@ -2491,7 +2491,7 @@
},
"component-classes": {
"version": "1.2.6",
- "resolved": "http://registry.npm.taobao.org/component-classes/download/component-classes-1.2.6.tgz",
+ "resolved": "https://registry.npmjs.org/component-classes/-/component-classes-1.2.6.tgz",
"integrity": "sha1-xkI5TDYYpNiwuJGe/Mu9kw5c1pE=",
"requires": {
"component-indexof": "0.0.3"
@@ -3086,13 +3086,13 @@
},
"cssom": {
"version": "0.3.2",
- "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.2.tgz",
+ "resolved": "http://registry.npm.taobao.org/cssom/download/cssom-0.3.2.tgz",
"integrity": "sha1-uANhcMefB6kP8vFuIihAJ6JDhIs=",
"dev": true
},
"cssstyle": {
"version": "0.2.37",
- "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-0.2.37.tgz",
+ "resolved": "http://registry.npm.taobao.org/cssstyle/download/cssstyle-0.2.37.tgz",
"integrity": "sha1-VBCXI0yyUTyDzu06zdwn/yeYfVQ=",
"dev": true,
"optional": true,
@@ -8076,7 +8076,7 @@
},
"jsdom": {
"version": "7.2.2",
- "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-7.2.2.tgz",
+ "resolved": "http://registry.npm.taobao.org/jsdom/download/jsdom-7.2.2.tgz",
"integrity": "sha1-QLQCdwwr2iNGkJa+6Rq2deOx/G4=",
"dev": true,
"optional": true,
@@ -8100,14 +8100,14 @@
"dependencies": {
"acorn": {
"version": "2.7.0",
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-2.7.0.tgz",
+ "resolved": "http://registry.npm.taobao.org/acorn/download/acorn-2.7.0.tgz",
"integrity": "sha1-q259nYhqrKiwhbwzEreaGYQz8Oc=",
"dev": true,
"optional": true
},
"parse5": {
"version": "1.5.1",
- "resolved": "https://registry.npmjs.org/parse5/-/parse5-1.5.1.tgz",
+ "resolved": "http://registry.npm.taobao.org/parse5/download/parse5-1.5.1.tgz",
"integrity": "sha1-m387DeMr543CQBsXVzzK8Pb1nZQ=",
"dev": true,
"optional": true
@@ -8897,11 +8897,6 @@
"integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=",
"dev": true
},
- "lodash.clonedeep": {
- "version": "4.5.0",
- "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
- "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8="
- },
"lodash.create": {
"version": "3.1.1",
"resolved": "https://registry.npm.taobao.org/lodash.create/download/lodash.create-3.1.1.tgz",
@@ -8913,11 +8908,6 @@
"lodash._isiterateecall": "3.0.9"
}
},
- "lodash.debounce": {
- "version": "4.0.8",
- "resolved": "https://registry.npm.taobao.org/lodash.debounce/download/lodash.debounce-4.0.8.tgz",
- "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168="
- },
"lodash.escape": {
"version": "3.2.0",
"resolved": "http://registry.npm.taobao.org/lodash.escape/download/lodash.escape-3.2.0.tgz",
@@ -8945,11 +8935,6 @@
"integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=",
"dev": true
},
- "lodash.isequal": {
- "version": "4.5.0",
- "resolved": "https://registry.npm.taobao.org/lodash.isequal/download/lodash.isequal-4.5.0.tgz",
- "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA="
- },
"lodash.isplainobject": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
@@ -15976,7 +15961,7 @@
},
"symbol-tree": {
"version": "3.2.2",
- "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.2.tgz",
+ "resolved": "http://registry.npm.taobao.org/symbol-tree/download/symbol-tree-3.2.2.tgz",
"integrity": "sha1-rifbOPZgp64uHDt9G8KQgZuFGeY=",
"dev": true,
"optional": true
@@ -16267,7 +16252,7 @@
},
"tr46": {
"version": "0.0.3",
- "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
+ "resolved": "http://registry.npm.taobao.org/tr46/download/tr46-0.0.3.tgz",
"integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=",
"dev": true,
"optional": true
@@ -17031,7 +17016,7 @@
"dependencies": {
"cheerio": {
"version": "0.20.0",
- "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-0.20.0.tgz",
+ "resolved": "http://registry.npm.taobao.org/cheerio/download/cheerio-0.20.0.tgz",
"integrity": "sha1-XHEPK6uVZTJyhCugHG6mGzVF7DU=",
"dev": true,
"requires": {
@@ -17045,7 +17030,7 @@
},
"domhandler": {
"version": "2.3.0",
- "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.3.0.tgz",
+ "resolved": "http://registry.npm.taobao.org/domhandler/download/domhandler-2.3.0.tgz",
"integrity": "sha1-LeWaCCLVAn+r/28DLCsloqir5zg=",
"dev": true,
"requires": {
@@ -17054,7 +17039,7 @@
},
"domutils": {
"version": "1.5.1",
- "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz",
+ "resolved": "http://registry.npm.taobao.org/domutils/download/domutils-1.5.1.tgz",
"integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=",
"dev": true,
"requires": {
@@ -17064,7 +17049,7 @@
},
"htmlparser2": {
"version": "3.8.3",
- "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.8.3.tgz",
+ "resolved": "http://registry.npm.taobao.org/htmlparser2/download/htmlparser2-3.8.3.tgz",
"integrity": "sha1-mWwosZFRaovoZQGn15dX5ccMEGg=",
"dev": true,
"requires": {
@@ -17077,7 +17062,7 @@
"dependencies": {
"entities": {
"version": "1.0.0",
- "resolved": "https://registry.npmjs.org/entities/-/entities-1.0.0.tgz",
+ "resolved": "http://registry.npm.taobao.org/entities/download/entities-1.0.0.tgz",
"integrity": "sha1-sph6o4ITR/zeZCsk/fyeT7cSvyY=",
"dev": true
}
@@ -17085,13 +17070,13 @@
},
"isarray": {
"version": "0.0.1",
- "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
+ "resolved": "http://registry.npm.taobao.org/isarray/download/isarray-0.0.1.tgz",
"integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=",
"dev": true
},
"loader-utils": {
"version": "0.2.17",
- "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-0.2.17.tgz",
+ "resolved": "http://registry.npm.taobao.org/loader-utils/download/loader-utils-0.2.17.tgz",
"integrity": "sha1-+G5jdNQyBabmxg6RlvF8Apm/s0g=",
"dev": true,
"requires": {
@@ -17103,7 +17088,7 @@
},
"readable-stream": {
"version": "1.1.14",
- "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
+ "resolved": "http://registry.npm.taobao.org/readable-stream/download/readable-stream-1.1.14.tgz",
"integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=",
"dev": true,
"requires": {
@@ -17115,7 +17100,7 @@
},
"string_decoder": {
"version": "0.10.31",
- "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
+ "resolved": "http://registry.npm.taobao.org/string_decoder/download/string_decoder-0.10.31.tgz",
"integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=",
"dev": true
}
@@ -17319,7 +17304,7 @@
},
"webidl-conversions": {
"version": "2.0.1",
- "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-2.0.1.tgz",
+ "resolved": "http://registry.npm.taobao.org/webidl-conversions/download/webidl-conversions-2.0.1.tgz",
"integrity": "sha1-O/glj30xjHRDw28uFpQCoaZwNQY=",
"dev": true,
"optional": true
@@ -18038,7 +18023,7 @@
},
"whatwg-url-compat": {
"version": "0.6.5",
- "resolved": "https://registry.npmjs.org/whatwg-url-compat/-/whatwg-url-compat-0.6.5.tgz",
+ "resolved": "http://registry.npm.taobao.org/whatwg-url-compat/download/whatwg-url-compat-0.6.5.tgz",
"integrity": "sha1-AImBEa9om7CXVBzVpFymyHmERb8=",
"dev": true,
"optional": true,
@@ -18168,7 +18153,7 @@
},
"xml-name-validator": {
"version": "2.0.1",
- "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-2.0.1.tgz",
+ "resolved": "http://registry.npm.taobao.org/xml-name-validator/download/xml-name-validator-2.0.1.tgz",
"integrity": "sha1-TYuPHszTQZqjYgYb7O9RXh5VljU=",
"dev": true,
"optional": true
@@ -18250,4 +18235,4 @@
"dev": true
}
}
-}
\ No newline at end of file
+}