瀏覽代碼

Merge branch 'dev' into 1.1

杨奕 8 年之前
父節點
當前提交
d2f02b828f
共有 65 個文件被更改,包括 1745 次插入723 次删除
  1. 17 0
      CHANGELOG.en-US.md
  2. 16 0
      CHANGELOG.zh-CN.md
  3. 4 0
      build/cooking.demo.js
  4. 8 1
      examples/components/footer.vue
  5. 93 1
      examples/components/side-nav.vue
  6. 1 1
      examples/docs/en-US/custom-theme.md
  7. 8 1
      examples/docs/en-US/date-picker.md
  8. 8 0
      examples/docs/en-US/datetime-picker.md
  9. 33 0
      examples/docs/en-US/dropdown.md
  10. 4 1
      examples/docs/en-US/input.md
  11. 94 2
      examples/docs/en-US/table.md
  12. 56 9
      examples/docs/en-US/tabs.md
  13. 9 1
      examples/docs/en-US/time-picker.md
  14. 1 1
      examples/docs/zh-CN/custom-theme.md
  15. 7 0
      examples/docs/zh-CN/date-picker.md
  16. 7 0
      examples/docs/zh-CN/datetime-picker.md
  17. 32 0
      examples/docs/zh-CN/dropdown.md
  18. 1 1
      examples/docs/zh-CN/form.md
  19. 5 6
      examples/docs/zh-CN/input.md
  20. 82 3
      examples/docs/zh-CN/table.md
  21. 79 15
      examples/docs/zh-CN/tabs.md
  22. 9 0
      examples/docs/zh-CN/time-picker.md
  23. 6 0
      examples/i18n/component.json
  24. 3 0
      examples/versions.json
  25. 3 2
      package.json
  26. 9 9
      packages/checkbox/src/checkbox.vue
  27. 4 6
      packages/date-picker/src/panel/date-range.vue
  28. 1 1
      packages/date-picker/src/panel/date.vue
  29. 1 1
      packages/date-picker/src/panel/time-select.vue
  30. 1 1
      packages/date-picker/src/panel/time.vue
  31. 30 8
      packages/date-picker/src/picker.vue
  32. 4 0
      packages/date-picker/src/util/index.js
  33. 1 1
      packages/form/README.md
  34. 2 1
      packages/form/src/form.vue
  35. 52 82
      packages/input-number/src/input-number.vue
  36. 33 30
      packages/input/src/input.vue
  37. 8 0
      packages/menu/src/menu.vue
  38. 2 0
      packages/pagination/src/pagination.js
  39. 5 4
      packages/radio/src/radio.vue
  40. 0 5
      packages/select/src/option.vue
  41. 4 2
      packages/select/src/select.vue
  42. 18 4
      packages/table/src/table-body.js
  43. 55 2
      packages/table/src/table-column.js
  44. 9 10
      packages/table/src/table-header.js
  45. 55 1
      packages/table/src/table-store.js
  46. 12 2
      packages/table/src/table.vue
  47. 16 49
      packages/tabs/src/tab-pane.vue
  48. 88 79
      packages/tabs/src/tabs.vue
  49. 1 1
      packages/theme-default/package.json
  50. 78 67
      packages/theme-default/src/checkbox.css
  51. 23 8
      packages/theme-default/src/col.css
  52. 5 0
      packages/theme-default/src/input-number.css
  53. 1 0
      packages/theme-default/src/pagination.css
  54. 49 38
      packages/theme-default/src/radio.css
  55. 38 0
      packages/theme-default/src/table.css
  56. 0 4
      packages/theme-default/src/tabs.css
  57. 2 2
      packages/tree/src/tree-node.vue
  58. 1 1
      src/index.js
  59. 65 0
      test/unit/specs/date-picker.spec.js
  60. 46 28
      test/unit/specs/input-number.spec.js
  61. 54 0
      test/unit/specs/input.spec.js
  62. 12 0
      test/unit/specs/pagination.spec.js
  63. 135 13
      test/unit/specs/table.spec.js
  64. 5 5
      test/unit/specs/tabs.spec.js
  65. 234 213
      yarn.lock

+ 17 - 0
CHANGELOG.en-US.md

@@ -1,5 +1,22 @@
 ## Changelog
 
+### 1.0.9
+
+*2016-12-27*
+
+- Fixed DatePicker incorrectly triggering input event, #1834
+- Fixed Tree reporting `event is undefined` error in Firefox, #1945
+- Added `change` event for DatePicker, whose parameter is the formatted value, #1841
+- Added `header-align` attribute for Table, #1424
+- Fixed single select Table's highlight style not removing when data is removed, #1890
+- Fixed filterable Select lagging issue with more options, #1933
+- Fixed multiple disabled Select not disabling removing selected options issue, #2001
+- Fixed Col style not working in `xs`, #2011
+- Added `value` attribute for Tab, #2008
+- Fixed InputNumber `change` event incorrectly firing multiple times in some conditions, #1999
+- Added `clearable` attribute for DatePicker, #1994
+- Fixed Form always passing validation in async mode, #1936
+
 ### 1.0.8
 
 *2016-12-20*

+ 16 - 0
CHANGELOG.zh-CN.md

@@ -1,5 +1,21 @@
 ## 更新日志
 
+### 1.0.9
+*2016-12-27*
+
+- 修复 DatePicker 不能正确触发 input 事件的问题,现在只有当日期改变时才触发,#1834
+- 修复 Tree 在 Firefox 下会提示 event is undefined 的问题,#1945
+- 新增 DatePicker 的 `change` 事件,返回和输入框一致的格式化后的值,#1841
+- 新增 Table 的 `header-align` 属性,#1424
+- 修复单选的 Table 在数据移除时,高亮状态仍然存在的问题,#1890
+- 修复可搜索的 Select 在选项较多时的卡顿问题,#1933
+- 修复多选的 Select 在禁用状态下仍然能够手动删除选中项的问题,#2001
+- 修复 Col `xs` 分辨率下样式无效的问题,#2011
+- 新增 Tab 组件的 `value` 属性并支持 `v-model` 用法,#2008
+- 修复 Input Number 在某些条件下 change 事件被触发多次的问题,#1999
+- 新增 DatePicker 的 `clearable` 属性,#1994
+- 修复 Form 异步验证时某些条件下总是验证通过的问题,#1936
+
 ### 1.0.8
 *2016-12-20*
 

+ 4 - 0
build/cooking.demo.js

@@ -1,6 +1,7 @@
 var cooking = require('cooking');
 var config = require('./config');
 var md = require('markdown-it')();
+var CopyWebpackPlugin = require('copy-webpack-plugin');
 var striptags = require('./strip-tags');
 var slugify = require('transliteration').slugify;
 var isProd = process.env.NODE_ENV === 'production';
@@ -113,5 +114,8 @@ if (isProd) {
   cooking.add('externals.vue-router', 'VueRouter');
 }
 
+cooking.add('plugin.CopyWebpackPlugin', new CopyWebpackPlugin([
+  { from: 'examples/versions.json' }
+]));
 cooking.add('vue.preserveWhitespace', false);
 module.exports = cooking.resolve();

+ 8 - 1
examples/components/footer.vue

@@ -2,7 +2,7 @@
   <footer class="footer">
     <div class="container">
       <div class="footer-main">
-        <p class="footer-main-title">Element 1.0 Hydrogen</p>
+        <p class="footer-main-title">Element {{ version }} Hydrogen</p>
         <a href="https://github.com/ElemeFE/element/issues" class="footer-main-link" target="_blank">{{ langConfig.feedback }}</a>
         <a href="https://github.com/ElemeFE/element/blob/master/.github/CONTRIBUTING.md" class="footer-main-link" target="_blank">{{ langConfig.contribution }}</a>
       </div>
@@ -134,8 +134,15 @@
 
 <script type="text/babel">
   import compoLang from '../i18n/component.json';
+  import { version } from 'main/index.js';
 
   export default {
+    data() {
+      return {
+        version
+      };
+    },
+
     computed: {
       lang() {
         return this.$route.path.split('/')[1];

+ 93 - 1
examples/components/side-nav.vue

@@ -12,6 +12,41 @@
       margin: 0;
       overflow: hidden;
     }
+    
+    .nav-dropdown {
+      margin-bottom: 6px;
+      width: 100%;
+      span {
+        display: block;
+        width: 100%;
+        font-size: 16px;
+        color: #5e6d82;
+        line-height: 40px;
+        transition: .2s;
+        border-bottom: 1px solid #eaeefb;
+        &:hover {
+          cursor: pointer;
+        }
+      }
+      i {
+        transition: .2s;
+        font-size: 12px;
+        color: #d3dce6;
+      }
+      @when active {
+        span, i {
+          color: #20a0ff;
+        }
+        i {
+          transform: rotateZ(180deg) translateY(2px);
+        }
+      }
+      &:hover {
+        span, i {
+          color: #20a0ff;
+        }
+      }
+    }
 
     .nav-item {
       a {
@@ -53,9 +88,37 @@
       margin-top: 10px;
     }
   }
+  .nav-dropdown-list {
+    width: 120px;
+    margin-top: -8px;
+    li {
+      font-size: 14px;
+    }
+  }
 </style>
 <template>
   <div class="side-nav" :style="navStyle">
+    <el-dropdown
+      v-show="isComponentPage"
+      trigger="click"
+      class="nav-dropdown"
+      :class="{ 'is-active': dropdownVisible }">
+      <span>
+        {{ langConfig.dropdown }}{{ version }}
+        <i class="el-icon-caret-bottom el-icon--right"></i>
+      </span>
+      <el-dropdown-menu
+        slot="dropdown"
+        :offset="-80"
+        class="nav-dropdown-list"
+        @input="handleDropdownToggle">
+        <el-dropdown-item
+          v-for="item in Object.keys(versions)"
+          @click.native="switchVersion(item)">
+          {{ item }}
+        </el-dropdown-item>
+      </el-dropdown-menu>
+    </el-dropdown>
     <ul>
       <li class="nav-item" v-for="item in data">
         <a v-if="!item.path" @click="expandMenu">{{item.name}}</a>
@@ -99,6 +162,9 @@
   </div>
 </template>
 <script>
+  import compoLang from '../i18n/component.json';
+  import { version } from 'main/index.js';
+
   export default {
     props: {
       data: Array,
@@ -111,7 +177,10 @@
       return {
         highlights: [],
         navState: [],
-        isSmallScreen: false
+        isSmallScreen: false,
+        versions: [],
+        version,
+        dropdownVisible: false
       };
     },
     watch: {
@@ -122,9 +191,19 @@
     computed: {
       navStyle() {
         return this.isSmallScreen ? { 'padding-bottom': '60px' } : {};
+      },
+      isComponentPage() {
+        return /^component-/.test(this.$route.name);
+      },
+      langConfig() {
+        return compoLang.filter(config => config.lang === this.$route.meta.lang)[0]['nav'];
       }
     },
     methods: {
+      switchVersion(version) {
+        if (version === this.version) return;
+        location.href = `${ location.origin }/${ this.versions[version] }/${ location.hash } `;
+      },
       handleResize() {
         this.isSmallScreen = document.documentElement.clientWidth < 768;
         this.handlePathChange();
@@ -160,8 +239,21 @@
         if (!target.nextElementSibling || target.nextElementSibling.tagName !== 'UL') return;
         this.hideAllMenu();
         event.currentTarget.nextElementSibling.style.height = 'auto';
+      },
+      handleDropdownToggle(visible) {
+        this.dropdownVisible = visible;
       }
     },
+    created() {
+      const xhr = new XMLHttpRequest();
+      xhr.onreadystatechange = _ => {
+        if (xhr.readyState === 4 && xhr.status === 200) {
+          this.versions = JSON.parse(xhr.responseText);
+        }
+      };
+      xhr.open('GET', '/versions.json');
+      xhr.send();
+    },
     mounted() {
       this.handleResize();
       window.addEventListener('resize', this.handleResize);

+ 1 - 1
examples/docs/en-US/custom-theme.md

@@ -51,7 +51,7 @@ Just edit `element-variables.css`, e.g. changing the theme color to red:
 ```
 
 ### Build theme
-After saving the variable file, use `et` to build your theme. You can activate `watch` mode by adding a parameter `-w`:
+After saving the variable file, use `et` to build your theme. You can activate `watch` mode by adding a parameter `-w`. And if you customized the variable file's output, you need to add a parameter `-c` and variable file's name:
 ```shell
 et
 

+ 8 - 1
examples/docs/en-US/date-picker.md

@@ -256,6 +256,7 @@ Picking a date range is supported.
 | disabled | whether DatePicker is disabled | boolean | - | false |
 |size | size of Input | string | large/small/mini | — |
 | editable | whether the input is editable | boolean | - | true |
+| clearable | Whether to show clear button | boolean | - | true |
 | placeholder | placeholder | string | — | — |
 | type | type of the picker | string | year/month/date/datetime/ week/datetimerange/daterange | date |
 | format | format of the picker | string | year `yyyy` month `MM` day `dd`, hour `HH`, minute `mm`, second `ss` | yyyy-MM-dd |
@@ -274,4 +275,10 @@ Picking a date range is supported.
 | Attribute      | Description          | Type      | Accepted Values       | Default  |
 |---------- |-------------- |---------- |--------------------------------  |-------- |
 | text | title of the shortcut | string | — | — |
-| onClick | callback function, triggers when the shortcut is clicked, with the `vm` as its parameter. You can change the picker value by emitting the `pick` event. Example: `vm.$emit('pick', new Date())`| function | — | — |
+| onClick | callback function, triggers when the shortcut is clicked, with the `vm` as its parameter. You can change the picker value by emitting the `pick` event. Example: `vm.$emit('pick', new Date())`| function | — | — |
+
+
+### Events
+| Event Name | Description | Parameters |
+|---------|--------|---------|
+| change | triggers when input value changes | formatted value |

+ 8 - 0
examples/docs/en-US/datetime-picker.md

@@ -210,6 +210,7 @@ Select date and time in one picker.
 | readonly | whether DatePicker is read only | boolean | — | false |
 | disabled | whether DatePicker is disabled | boolean | - | false |
 | editable | whether the input is editable | boolean | - | true |
+| clearable | Whether to show clear button | boolean | - | true |
 |size | size of Input | string | large/small/mini | — |
 | placeholder | placeholder | string | — | — |
 | type | type of the picker | string | year/month/date/datetime/ week/datetimerange/daterange | date |
@@ -229,3 +230,10 @@ Select date and time in one picker.
 |---------- |-------------- |---------- |--------------------------------  |-------- |
 | text | title of the shortcut | string | — | — |
 | onClick | callback function, triggers when the shortcut is clicked, with the `vm` as its parameter. You can change the picker value by emitting the `pick` event. Example: `vm.$emit('pick', new Date())`| function | — | — |
+
+
+### Events
+| Event Name | Description | Parameters |
+|---------|--------|---------|
+| change | triggers when input value changes | formatted value |
+

+ 33 - 0
examples/docs/en-US/dropdown.md

@@ -3,6 +3,9 @@
     methods: {
       handleClick() {
         alert('button click');
+      },
+      handleCommand(command) {
+        this.$message('click on item ' + command);
       }
     }
   }
@@ -126,6 +129,36 @@ Use `hide-on-click` to define if menu closes on clicking.
 ```
 :::
 
+### Command event
+
+Clicking each dropdown item fires an event whose parameter is assigned by each item.
+
+:::demo
+```html
+<el-dropdown @command="handleCommand">
+  <span class="el-dropdown-link">
+    Dropdown List<i class="el-icon-caret-bottom el-icon--right"></i>
+  </span>
+  <el-dropdown-menu slot="dropdown">
+    <el-dropdown-item command="a">Action 1</el-dropdown-item>
+    <el-dropdown-item command="b">Action 2</el-dropdown-item>
+    <el-dropdown-item command="c">Action 3</el-dropdown-item>
+    <el-dropdown-item command="d" disabled>Action 4</el-dropdown-item>
+    <el-dropdown-item command="e" divided>Action 5</el-dropdown-item>
+  </el-dropdown-menu>
+</el-dropdown>
+<script>
+  export default {
+    methods: {
+      handleCommand(command) {
+        this.$message('click on item ' + command);
+      }
+    }
+  }
+</script>
+```
+:::
+
 
 ### Dropdown Attributes
 | Attribute      | Description          | Type      | Accepted Values       | Default  |

+ 4 - 1
examples/docs/en-US/input.md

@@ -591,7 +591,10 @@ Search data from server-side.
 
 | Event Name | Description | Parameters |
 |----| ----| ----|
-|click | triggers when the icon inside Input is clicked | event object |
+|click | triggers when the icon inside Input is clicked | (event: Event) |
+| blur | triggers when the icon inside Input is blur | (event: Event) |
+| focus | triggers when the icon inside Input is focus | (event: Event) |
+| change | triggers when the icon inside Input value change | (value: string \| number) |
 
 ### Autocomplete Attributes
 

+ 94 - 2
examples/docs/en-US/table.md

@@ -1346,6 +1346,94 @@ Customize table column so it can be integrated with other components.
 ```
 :::
 
+### Expandable row
+
+When the row content is too long and you do not want to display the horizontal scroll bar, you can use the expandable row feature.
+:::demo Activate expandable row by adding type="expand" and `inline-template` attribute,The template for `el-table-column` will be rendered as the contents of the expanded row, you can access the same attributes as the` inline-template`。
+```html
+<template>
+  <el-table
+    :data="tableData3"
+    style="width: 100%">
+    <el-table-column type="expand" inline-template>
+      <div>
+      <p>State: {{ row.state }}</p>
+      <p>City: {{ row.city }}</p>
+      <p>Address: {{ row.address }}</p>
+      <p>Zip: {{ row.zip }}</p>
+      </div>
+    </el-table-column>
+    <el-table-column
+      label="Date"
+      prop="date">
+    </el-table-column>
+    <el-table-column
+      label="Name"
+      prop="name">
+    </el-table-column>
+  </el-table>
+</template>
+
+<script>
+  export default {
+    data() {
+      return {
+        tableData3: [{
+          date: '2016-05-03',
+          name: 'Tom',
+          state: 'California',
+          city: 'Los Angeles',
+          address: 'No. 189, Grove St, Los Angeles',
+          zip: 'CA 90036'
+        }, {
+          date: '2016-05-02',
+          name: 'Tom',
+          state: 'California',
+          city: 'Los Angeles',
+          address: 'No. 189, Grove St, Los Angeles',
+          zip: 'CA 90036'
+        }, {
+          date: '2016-05-04',
+          name: 'Tom',
+          state: 'California',
+          city: 'Los Angeles',
+          address: 'No. 189, Grove St, Los Angeles',
+          zip: 'CA 90036'
+        }, {
+          date: '2016-05-01',
+          name: 'Tom',
+          state: 'California',
+          city: 'Los Angeles',
+          address: 'No. 189, Grove St, Los Angeles',
+          zip: 'CA 90036'
+        }, {
+          date: '2016-05-08',
+          name: 'Tom',
+          state: 'California',
+          city: 'Los Angeles',
+          address: 'No. 189, Grove St, Los Angeles',
+          zip: 'CA 90036'
+        }, {
+          date: '2016-05-06',
+          name: 'Tom',
+          state: 'California',
+          city: 'Los Angeles',
+          address: 'No. 189, Grove St, Los Angeles',
+          zip: 'CA 90036'
+        }, {
+          date: '2016-05-07',
+          name: 'Tom',
+          state: 'California',
+          city: 'Los Angeles',
+          address: 'No. 189, Grove St, Los Angeles',
+          zip: 'CA 90036'
+        }]
+    }
+  }
+</script>
+```
+:::
+
 ### Table Attributes
 | Attribute      | Description          | Type      | Accepted Values       | Default  |
 |---------- |-------------- |---------- |--------------------------------  |-------- |
@@ -1361,7 +1449,9 @@ Customize table column so it can be integrated with other components.
 | row-style | function that returns custom style for a row,  or a string assigning custom style for every row | Function(row, index)/Object | — | — |
 | row-key | key of row data, used for optimizing rendering. Required if `reserve-selection` is on | Function(row)/String | — | — |
 | context | context of Table, e.g. `_self` refers to the current context, `$parent` parent context, `$root` root context, can be overridden by `context` in `el-table-column` | Object | - | current context where Table lies |
-| empty-text | Displayed text when data is empty. You can customize this area with `slot="empty"` | String | | - | No Data |
+| empty-text | Displayed text when data is empty. You can customize this area with `slot="empty"` | String | - | No Data |
+| default-expand-all | whether expand all rows by default, only works when the table has a column type="expand" | Boolean | - | false |
+| expand-row-keys | set expanded rows by this prop, prop's value is the keys of expand rows, you should set row-key before using this prop | Array | - | |
 | virtual-scrollbar | Enable virtual scrollbar | Boolean | - | false |
 
 ### Table Events
@@ -1380,6 +1470,7 @@ Customize table column so it can be integrated with other components.
 | sort-change | triggers when Table's sorting changes | { column, prop, order } |
 | filter-change | column's key. If you need to use the filter-change event, this attribute is mandatory to identify which column is being filtered | filters |
 | current-change | triggers when current row changes | currentRow, oldCurrentRow |
+| expand | triggers when user expands or collapses a row | row, expanded |
 
 ### Table Methods
 | Method | Description | Parameters |
@@ -1390,7 +1481,7 @@ Customize table column so it can be integrated with other components.
 ### Table-column Attributes
 | Attribute      | Description          | Type      | Accepted Values       | Default  |
 |---------- |-------------- |---------- |--------------------------------  |-------- |
-| type | type of the column. If set to `selection`, the column will display checkbox. If set to `index`, the column will display index of the row (staring from 1)  | string | selection/index | — |
+| type | type of the column. If set to `selection`, the column will display checkbox. If set to `index`, the column will display index of the row (staring from 1). If set to `expand`, the column will display expand icon.  | string | selection/index/expand | — |
 | label | column label | string | — | — |
 | column-key | column's key. If you need to use the filter-change event, you need this attribute to identify which column is being filtered | string | string | - | - |
 | prop |  field name. You can also use its alias: `property` | string | — | — |
@@ -1404,6 +1495,7 @@ Customize table column so it can be integrated with other components.
 | formatter | function that formats content | Function(row, column) | — | — |
 | show-overflow-tooltip | whether to hide extra content and show them in a tooltip when hovering on the cell | boolean | — | false |
 | align | alignment | string | left/center/right | left |
+| header-align | alignment of the table header. If omitted, the value of the above `align` attribute will be applied | String | left/center/right | — |
 | class-name | class name of cells in the column | string | — | — |
 | selectable | function that determines if a certain row can be selected, works when `type` is 'selection' | Function(row, index) | — | — |
 | reserve-selection | whether to reserve selection after data refreshing, works when `type` is 'selection' | boolean | — | false |

+ 56 - 9
examples/docs/en-US/tabs.md

@@ -2,7 +2,18 @@
   export default {
     data() {
       return {
-        activeName: 'first'
+        activeName: 'first',
+        activeName2: 'first',
+        tabs: [{
+          title: 'Tab 1',
+          name: '1',
+          content: 'Tab 1 content'
+        }, {
+          title: 'Tab 2',
+          name: '2',
+          content: 'Tab 2 content'
+        }],
+        tabIndex: 2
       }
     },
     methods: {
@@ -11,6 +22,9 @@
       },
       handleClick(tab, event) {
         console.log(tab, event);
+      },
+      renderTab(h, tab) {
+        return <span><i class="el-icon-date"></i> {tab.label}</span>;
       }
     }
   }
@@ -24,11 +38,11 @@ Divide data collections which are related yet belong to different types.
 
 Basic and concise tabs.
 
-:::demo Tabs provide a selective card functionality and it can be achieved by just using `el-tabs` and child element `el-tab-pane`. In these two elements, we provide a list of attributes. The `label` in `el-tab-pane` determines the label of selective cards, and you can write content in the label. In this example, we add a `active-name` attribute indicating the active card in `el-tabs`, which can take a `String` value. In the `el-tab-pane` you can set corresponding `name` attribute, and if there is no `name`, the default sequence is `1`/`2`/`3`/`4`. In this example, the selected card is card 2. If `name` is omitted, setting `active-name` to `2` can reach the same goal.
+:::demo Tabs provide a selective card functionality. By default the first tab is selected as active, and you can activate any tab by setting the `value` attribute.
 
 ```html
 <template>
-  <el-tabs :active-name="activeName">
+  <el-tabs v-model="activeName" @tab-click="handleClick">
     <el-tab-pane label="User" name="first">User</el-tab-pane>
     <el-tab-pane label="Config" name="second">Config</el-tab-pane>
     <el-tab-pane label="Role" name="third">Role</el-tab-pane>
@@ -41,6 +55,11 @@ Basic and concise tabs.
       return {
         activeName: 'first'
       };
+    },
+    methods: {
+      handleClick(tab, event) {
+        console.log(tab, event);
+      }
     }
   };
 </script>
@@ -55,7 +74,7 @@ Tabs styled as cards.
 
 ```html
 <template>
-  <el-tabs type="card" @tab-click="handleClick" @tab-remove="handleRemove">
+  <el-tabs type="card" @tab-click="handleClick">
     <el-tab-pane label="User">User</el-tab-pane>
     <el-tab-pane label="Config">Config</el-tab-pane>
     <el-tab-pane label="Role">Role</el-tab-pane>
@@ -64,10 +83,12 @@ Tabs styled as cards.
 </template>
 <script>
   export default {
+    data() {
+      return {
+        activeName: 'first'
+      };
+    },
     methods: {
-      handleRemove(tab) {
-        console.log(tab);
-      },
       handleClick(tab, event) {
         console.log(tab, event);
       }
@@ -81,7 +102,7 @@ Tabs styled as cards.
 
 Closable tabs.
 
-:::demo You can set `closable` attribute in `el-tabs`. It accept `Boolean` and Tab will be closable when the boolean is `true`.
+:::demo You can set the closable attribute in el-tabs to make all tabs closable. Also, closable can be set in a tab panel to make that specific tab closable.
 
 ```html
 <template>
@@ -125,12 +146,37 @@ Border card tabs.
 
 :::
 
+### Custom Tab
+
+You can use `label-content` property to customize the tab
+
+:::demo `label-content` is a render function,which return the vnode of the tab.
+```html
+<el-tabs type="border-card">
+  <el-tab-pane label="Route" :label-content="renderTab">Route</el-tab-pane>
+  <el-tab-pane label="Config">Config</el-tab-pane>
+  <el-tab-pane label="Role">Role</el-tab-pane>
+  <el-tab-pane label="Task">Task</el-tab-pane>
+</el-tabs>
+<script>
+  export default {
+    methods: {
+      renderTab(h, tab) {
+        return <span><i class="el-icon-date"></i> {tab.label}</span>;
+      }
+    }
+  }
+</script>
+```
+:::
+
 ### Tabs Attributes
 | Attribute      | Description          | Type      | Accepted Values       | Default  |
 |---------- |-------- |---------- |-------------  |-------- |
 | type     | type of Tab | string   | card/border-card  |     —    |
 | closable  | whether Tab is closable | boolean   | — |  false  |
-| active-name  | name of the selected tab  | string   |  —  |  name of first tab |
+| active-name(deprecated)  | name of the selected tab  | string   |  —  |  name of first tab |
+| value  | name of the selected tab  | string   |  —  |  name of first tab |
 
 ### Tabs Events
 | Event Name | Description | Parameters |
@@ -145,3 +191,4 @@ Border card tabs.
 | label-content | render function for tab title | Function(h, tab:vueInstance) | - | - |
 | disabled | whether Tab is disabled | boolean | - | false |
 | name      | identifier corresponding to the activeName of Tabs, representing the alias of the tab-pane | string | — | ordinal number of the tab-pane in the sequence, i.e. the first tab-pane is '1' |
+| closable  | whether Tab is closable | boolean   | — |  false  |

+ 9 - 1
examples/docs/en-US/time-picker.md

@@ -146,7 +146,8 @@ Can pick an arbitrary time range.
 | readonly | whether DatePicker is read only | boolean | — | false |
 | disabled | whether DatePicker is disabled | boolean | - | false |
 | editable | whether the input is editable | boolean | - | true |
-|size | size of Input | string | large/small/mini | — |
+| clearable | Whether to show clear button | boolean | - | true |
+| size | size of Input | string | large/small/mini | — |
 | placeholder | placeholder | string | — | — |
 | format | format of the picker | string | hour `HH`, minute `mm`, second `ss` | HH:mm:ss |
 | value | value of the picker | date for Time Picker, and string for Time Select | hour `HH`, minute `mm`, second `ss` | HH:mm:ss |
@@ -167,3 +168,10 @@ Can pick an arbitrary time range.
 | Attribute      | Description          | Type      | Accepted Values       | Default  |
 |---------- |-------------- |---------- |--------------------------------  |-------- |
 | selectableRange | available time range, e.g.`'18:30:00 - 20:30:00'`or`['09:30:00 - 12:00:00', '14:30:00 - 18:30:00']` | string/array | — | — |
+
+
+### Events
+| Event Name | Description | Parameters |
+|---------|--------|---------|
+| change | triggers when input value changes | formatted value |
+

+ 1 - 1
examples/docs/zh-CN/custom-theme.md

@@ -51,7 +51,7 @@ et -i [可以自定义变量文件]
 ```
 
 ### 编译主题
-保存文件后,到命令行里执行 `et` 编译主题,如果你想启用 `watch` 模式,实时编译主题,增加 `-w` 参数
+保存文件后,到命令行里执行 `et` 编译主题,如果你想启用 `watch` 模式,实时编译主题,增加 `-w` 参数;如果你在初始化时指定了自定义变量文件,则需要增加 `-c` 参数,并带上你的变量文件名
 ```shell
 et
 

+ 7 - 0
examples/docs/zh-CN/date-picker.md

@@ -289,6 +289,7 @@
 | readonly | 完全只读 | boolean | — | false |
 | disabled | 禁用 | boolean | - | false |
 | editable | 文本框可输入 | boolean | - | true |
+| clearable | 是否显示清除按钮 | boolean | - | true |
 | size          | 输入框尺寸     | string          | large, small, mini  | — |
 | placeholder | 占位内容 | string | — | — |
 | type | 显示类型 | string | year/month/date/week/ datetime/datetimerange/daterange | date |
@@ -309,3 +310,9 @@
 |---------- |-------------- |---------- |--------------------------------  |-------- |
 | text | 标题文本 | string | — | — |
 | onClick | 选中后的回调函数,参数是 vm,可通过触发 'pick' 事件设置选择器的值。例如 vm.$emit('pick', new Date()) | function | — | — |
+
+### Events
+| Event Name | Description | Parameters |
+|---------|--------|---------|
+| change | 当 input 的值改变时触发,返回值和文本框一致 | formatted value |
+

+ 7 - 0
examples/docs/zh-CN/datetime-picker.md

@@ -233,6 +233,7 @@
 | readonly | 完全只读 | boolean | — | false |
 | disabled | 禁用 | boolean | - | false |
 | editable | 文本框可输入 | boolean | - | true |
+| clearable | 是否显示清除按钮 | boolean | - | true |
 | size          | 输入框尺寸     | string          | large, small, mini  | — |
 | placeholder | 占位内容 | string | — | — |
 | type | 显示类型 | string | year/month/date/week/ datetime/datetimerange/daterange | date |
@@ -254,3 +255,9 @@
 | onClick | 选中后的回调函数,参数是 vm,可通过触发 'pick' 事件设置选择器的值。例如 vm.$emit('pick', new Date()) | function | — | — |
 
 
+### Events
+| Event Name | Description | Parameters |
+|---------|--------|---------|
+| change | 当 input 的值改变时触发,返回值和文本框一致 | formatted value |
+
+

+ 32 - 0
examples/docs/zh-CN/dropdown.md

@@ -43,6 +43,9 @@
     methods: {
       handleClick() {
         alert('button click');
+      },
+      handleCommand(command) {
+        this.$message('click on item ' + command);
       }
     }
   }
@@ -168,6 +171,35 @@
 ```
 :::
 
+### 指令事件
+
+点击菜单项后会触发事件,用户可以通过相应的菜单项 key 进行不同的操作
+
+:::demo
+```html
+<el-dropdown @command="handleCommand">
+  <span class="el-dropdown-link">
+    下拉菜单<i class="el-icon-caret-bottom el-icon--right"></i>
+  </span>
+  <el-dropdown-menu slot="dropdown">
+    <el-dropdown-item command="a">黄金糕</el-dropdown-item>
+    <el-dropdown-item command="b">狮子头</el-dropdown-item>
+    <el-dropdown-item command="c">螺蛳粉</el-dropdown-item>
+    <el-dropdown-item command="d" disabled>双皮奶</el-dropdown-item>
+    <el-dropdown-item command="e" divided>蚵仔煎</el-dropdown-item>
+  </el-dropdown-menu>
+</el-dropdown>
+<script>
+  export default {
+    methods: {
+      handleCommand(command) {
+        this.$message('click on item ' + command);
+      }
+    }
+  }
+</script>
+```
+:::
 
 ### Dropdown Attributes
 | 参数          | 说明            | 类型            | 可选值                 | 默认值   |

+ 1 - 1
examples/docs/zh-CN/form.md

@@ -803,7 +803,7 @@
 |---------- |-------------- |
 | validate(cb) | 对整个表单进行校验的方法 |
 | validateField(prop, cb) | 对部分表单字段进行校验的方法 |
-| resetFields | 对整个表单进行重置,将所有字段值重置为并移除校验结果 |
+| resetFields | 对整个表单进行重置,将所有字段值重置为初始值并移除校验结果 |
 
 ### Form-Item Attributes
 

+ 5 - 6
examples/docs/zh-CN/input.md

@@ -203,9 +203,7 @@
 
 ::: demo
 ```html
-<el-input
-  placeholder="请输入内容"
-  v-model="input">
+<el-input v-model="input" placeholder="请输入内容">
 </el-input>
 
 <script>
@@ -759,9 +757,10 @@ export default {
 ### Input Events
 | 事件名称 | 说明 | 回调参数 |
 |---------|--------|---------|
-| click | 点击 Input 内的图标时触发 | event |
-| blur | 在 Input 失去焦点时触发 | event |
-| focus | 在 Input 或得焦点时触发 | event |
+| click | 点击 Input 内的图标时触发 | (event: Event) |
+| blur | 在 Input 失去焦点时触发 | (event: Event) |
+| focus | 在 Input 或得焦点时触发 | (event: Event) |
+| change | 在 Input 值改变时触发 | (value: string \| number) |
 
 ### Autocomplete Attributes
 

+ 82 - 3
examples/docs/zh-CN/table.md

@@ -60,6 +60,7 @@
           province: '上海',
           city: '普陀区',
           address: '上海市普陀区金沙江路 1518 弄',
+          detailAddress: '金沙江路 1518 弄',
           zip: 200333
         }, {
           date: '2016-05-02',
@@ -67,6 +68,7 @@
           province: '上海',
           city: '普陀区',
           address: '上海市普陀区金沙江路 1518 弄',
+          detailAddress: '金沙江路 1518 弄',
           zip: 200333
         }, {
           date: '2016-05-04',
@@ -81,6 +83,7 @@
           province: '上海',
           city: '普陀区',
           address: '上海市普陀区金沙江路 1518 弄',
+          detailAddress: '金沙江路 1518 弄',
           zip: 200333
         }, {
           date: '2016-05-08',
@@ -88,6 +91,7 @@
           province: '上海',
           city: '普陀区',
           address: '上海市普陀区金沙江路 1518 弄',
+          detailAddress: '金沙江路 1518 弄',
           zip: 200333
         }, {
           date: '2016-05-06',
@@ -95,6 +99,7 @@
           province: '上海',
           city: '普陀区',
           address: '上海市普陀区金沙江路 1518 弄',
+          detailAddress: '金沙江路 1518 弄',
           zip: 200333
         }, {
           date: '2016-05-07',
@@ -102,6 +107,7 @@
           province: '上海',
           city: '普陀区',
           address: '上海市普陀区金沙江路 1518 弄',
+          detailAddress: '金沙江路 1518 弄',
           zip: 200333
         }],
         tableData4: [{
@@ -1354,6 +1360,74 @@
 ```
 :::
 
+### 展开行
+
+当行内容过多并且不想显示横向滚动条时,可以使用 Table 展开行功能。
+:::demo 通过设置 type="expand" 和 `inline-template` 属性可以开启展开行功能,`el-table-column` 的模板会被渲染成为展开行的内容,展开行可访问的属性与使用 `inline-template` 的时候相同。
+```html
+<template>
+  <el-table
+    :data="tableData3"
+    style="width: 100%">
+    <el-table-column type="expand" inline-template>
+      <div>
+      <p>省: {{ row.province }}</p>
+      <p>市: {{ row.city }}</p>
+      <p>住址: {{ row.detailAddress }}</p>
+      <p>邮编: {{ row.zip }}</p>
+      </div>
+    </el-table-column>
+    <el-table-column
+      label="日期"
+      prop="date">
+    </el-table-column>
+    <el-table-column
+      label="姓名"
+      prop="name">
+    </el-table-column>
+  </el-table>
+</template>
+
+<script>
+  export default {
+    data() {
+      return {
+        tableData3: [{
+          date: '2016-05-02',
+          name: '王小虎',
+          province: '上海',
+          city: '普陀区',
+          detailAddress: '金沙江路 1518 弄',
+          zip: 200333
+        }, {
+          date: '2016-05-04',
+          name: '王小虎',
+          province: '上海',
+          city: '普陀区',
+          detailAddress: '金沙江路 1518 弄',
+          zip: 200333
+        }, {
+          date: '2016-05-01',
+          name: '王小虎',
+          province: '上海',
+          city: '普陀区',
+          detailAddress: '金沙江路 1518 弄',
+          zip: 200333
+        }, {
+          date: '2016-05-03',
+          name: '王小虎',
+          province: '上海',
+          city: '普陀区',
+          detailAddress: '金沙江路 1518 弄',
+          zip: 200333
+        }]
+      }
+    }
+  }
+</script>
+```
+:::
+
 ### Table Attributes
 | 参数      | 说明          | 类型      | 可选值                           | 默认值  |
 |---------- |-------------- |---------- |--------------------------------  |-------- |
@@ -1368,9 +1442,12 @@
 | row-style | 行的 style 的回调方法,也可以使用一个固定的 Object 为所有行设置一样的 Style。 | Function(row, index)/Object | — | — |
 | row-key | 行数据的 Key,用来优化 Table 的渲染;在使用 reserve-selection 功能的情况下,该属性是必填的 | Function(row)/String | — | — |
 | context | 设置上下文环境,例如设置当前上下文就是 `_self`,父级就是 `$parent`,根组件 `$root`。优先读取 column 的 context 属性。 | Object | - | Table 所处上下文 |
-| empty-text | 空数据时显示的文本内容,也可以通过 `slot="empty"` 设置 | String | | - | 暂无数据 |
+| empty-text | 空数据时显示的文本内容,也可以通过 `slot="empty"` 设置 | String | - | 暂无数据 |
+| default-expand-all | 是否默认展开所有行,当 Table 中存在 type="expand" 的 Column 的时候有效 | Boolean | - | false |
+| expand-row-keys | 可以通过该属性设置 Table 目前的展开行,需要设置 row-key 属性才能使用,该属性为展开行的 keys 数组。| Array | - | |
 | virtual-scrollbar | 启用虚拟滚动条 | Boolean | - | false |
 
+
 ### Table Events
 | 事件名 | 说明 | 参数 |
 | ---- | ---- | ---- |
@@ -1387,6 +1464,7 @@
 | sort-change | 当表格的排序条件发生变化的时候会触发该事件 | { column, prop, order } |
 | filter-change | 当表格的筛选条件发生变化的时候会触发该事件,参数的值是一个对象,对象的 key 是 column 的 columnKey,对应的 value 为用户选择的筛选条件的数组。 | filters |
 | current-change | 当表格的当前行发生变化的时候会触发该事件,如果要高亮当前行,请打开表格的 highlight-current-row 属性 | currentRow, oldCurrentRow |
+| expand | 当用户对某一行展开或者关闭的上会触发该事件 | row, expanded |
 
 ### Table Methods
 | 方法名 | 说明 | 参数 |
@@ -1397,7 +1475,7 @@
 ### Table-column Attributes
 | 参数      | 说明          | 类型      | 可选值                           | 默认值  |
 |---------- |-------------- |---------- |--------------------------------  |-------- |
-| type | 对应列的类型。如果设置了 `selection` 则显示多选框如果设置了 `index` 则显示该行的索引(从 1 开始计算) | string | selection/index | — |
+| type | 对应列的类型。如果设置了 `selection` 则显示多选框如果设置了 `index` 则显示该行的索引(从 1 开始计算);如果设置了 expand 则显示为一个可展开的按钮 | string | selection/index/expand | — |
 | column-key | column 的 key,如果需要使用 filter-change 事件,则需要此属性标识是哪个 column 的筛选条件 | string | - | - |
 | label | 显示的标题 | string | — | — |
 | prop | 对应列内容的字段名,也可以使用 property 属性 | string | — | — |
@@ -1410,7 +1488,8 @@
 | resizable | 对应列是否可以通过拖动改变宽度(需要在 el-table 上设置 border 属性为真) | boolean | — | true |
 | formatter | 用来格式化内容 | Function(row, column) | — | — |
 | show-overflow-tooltip | 当内容过长被隐藏时显示 tooltip | Boolean | — | false |
-| align | 对齐方式 | String | left, center, right | left |
+| align | 对齐方式 | String | left/center/right | left |
+| header-align | 表头对齐方式,若不设置该项,则使用表格的对齐方式 | String | left/center/right | — |
 | class-name | 列的 className | string | — | — |
 | selectable | 仅对 type=selection 的列有效,类型为 Function,Function 的返回值用来决定这一行的 CheckBox 是否可以勾选 | Function(row, index) | — | — |
 | reserve-selection | 仅对 type=selection 的列有效,类型为 Boolean,为 true 则代表会保留之前数据的选项,需要配合 Table 的 clearSelection 方法使用。 | Boolean | — | false |

+ 79 - 15
examples/docs/zh-CN/tabs.md

@@ -2,7 +2,18 @@
   export default {
     data() {
       return {
-        activeName: 'first'
+        activeName: 'first',
+        activeName2: 'first',
+        tabs: [{
+          title: 'Tab 1',
+          name: '1',
+          content: 'Tab 1 content'
+        }, {
+          title: 'Tab 2',
+          name: '2',
+          content: 'Tab 2 content'
+        }],
+        tabIndex: 2
       }
     },
     methods: {
@@ -11,22 +22,27 @@
       },
       handleClick(tab, event) {
         console.log(tab, event);
+      },
+      renderTab(h, tab) {
+        return <span><i class="el-icon-date"></i> {tab.label}</span>;
       }
     }
   }
 </script>
+
 ## Tabs 标签页
+
 分隔内容上有关联但属于不同类别的数据集合。
 
 ### 基础用法
 
 基础的、简洁的标签页。
 
-:::demo Tabs 组件提供了选项卡功能,只需要使用`el-tabs`和子元素`el-tab-pane`即可,在两个元素中,我们分别提供了一系列的属性来方便使用,`el-tab-pane`中`label`决定了选项卡标题,标签内部写入内容即可。在下例中我们在`el-tabs`中设置了`active-name`属性,接受一个`String`值,表明选中的选项卡,在`el-tab-pane`中可以设置对应的`name`属性,如果没有设置`name`,则默认值为顺序的`1`/`2`/`3`/`4`。例子选中选项卡2,如果不设置`name`,将`active-name`设为`2`,可以达成相同效果
+:::demo Tabs 组件提供了选项卡功能,默认选中第一个标签页,你也可以通过 `value` 属性来指定当前选中的标签页
 
 ```html
 <template>
-  <el-tabs :active-name="activeName">
+  <el-tabs v-model="activeName" @tab-click="handleClick">
     <el-tab-pane label="用户管理" name="first">用户管理</el-tab-pane>
     <el-tab-pane label="配置管理" name="second">配置管理</el-tab-pane>
     <el-tab-pane label="角色管理" name="third">角色管理</el-tab-pane>
@@ -39,6 +55,11 @@
       return {
         activeName: 'first'
       };
+    },
+    methods: {
+      handleClick(tab, event) {
+        console.log(tab, event);
+      }
     }
   };
 </script>
@@ -49,23 +70,25 @@
 
 选项卡样式的标签页。
 
-:::demo 只需要设置`type`属性即可,如果需要标签风格,将其设置为`card`。
+:::demo 只需要设置 `type` 属性为 `card` 就可以使选项卡改变为标签风格
 
 ```html
 <template>
-  <el-tabs type="card" @tab-click="handleClick" @tab-remove="handleRemove">
-    <el-tab-pane label="用户管理">用户管理</el-tab-pane>
-    <el-tab-pane label="配置管理">配置管理</el-tab-pane>
-    <el-tab-pane label="角色管理">角色管理</el-tab-pane>
-    <el-tab-pane label="定时任务补偿">定时任务补偿</el-tab-pane>
+  <el-tabs v-model="activeName2" type="card" @tab-click="handleClick">
+    <el-tab-pane label="用户管理" name="first">用户管理</el-tab-pane>
+    <el-tab-pane label="配置管理" name="second">配置管理</el-tab-pane>
+    <el-tab-pane label="角色管理" name="third">角色管理</el-tab-pane>
+    <el-tab-pane label="定时任务补偿" name="fourth">定时任务补偿</el-tab-pane>
   </el-tabs>
 </template>
 <script>
   export default {
+    data() {
+      return {
+        activeName: 'first'
+      };
+    },
     methods: {
-      handleRemove(tab) {
-        console.log(tab);
-      },
       handleClick(tab, event) {
         console.log(tab, event);
       }
@@ -79,11 +102,11 @@
 
 可以关闭标签页。
 
-:::demo 在`el-tabs`中设置`closable`属性,接受一个`Boolean`,设置为`true`时为可关闭
+:::demo 通过设置 `closable` 属性来打开 `Tabs` 的可关闭标签效果, `closable` 也可以设置在 `Tab Panel` 中实现部分标签页的可关闭效果
 
 ```html
 <template>
-  <el-tabs type="card" :closable="true" @tab-click="handleClick" @tab-remove="handleRemove">
+  <el-tabs type="card" closable @tab-click="handleClick" @tab-remove="handleRemove">
     <el-tab-pane label="用户管理">用户管理</el-tab-pane>
     <el-tab-pane label="配置管理">配置管理</el-tab-pane>
     <el-tab-pane label="角色管理">角色管理</el-tab-pane>
@@ -120,12 +143,52 @@
 ```
 :::
 
+### 自定义标签页
+
+可以通过 `label-content` 属性来实现自定义标签页的内容
+
+:::demo `label-content` 是一个 render function,在这个方法里返回的 vnode 会被渲染到标签页中。
+```html
+<el-tabs type="border-card">
+  <el-tab-pane label="我的行程" :label-content="renderTab">我的行程</el-tab-pane>
+  <el-tab-pane label="消息中心">消息中心</el-tab-pane>
+  <el-tab-pane label="角色管理">角色管理</el-tab-pane>
+  <el-tab-pane label="定时任务补偿">定时任务补偿</el-tab-pane>
+</el-tabs>
+<script>
+  export default {
+    methods: {
+      renderTab(h, tab) {
+        return <span><i class="el-icon-date"></i> {tab.label}</span>;
+      }
+    }
+  }
+</script>
+```
+:::
+
+### 动态增加标签页
+
+展示如何通过触发器来动态增加标签页
+
+:::demo
+```html
+<div style="margin-bottom: 20px;">
+  <el-button size="small" @click="tabs.push({ name: 'Tab ' + ++tabIndex, title: 'new Tab', content: 'new Tab content' })">add tab</el-button>
+</div>
+<el-tabs type="card" closable>
+  <el-tab-pane v-for="(item, index) in tabs" :label="item.title" :name="item.name">{{item.content}}</el-tab-pane>
+</el-tabs>
+```
+:::
+
 ### Tabs Attributes
 | 参数       | 说明     | 类型      | 可选值       | 默认值   |
 |---------- |-------- |---------- |-------------  |-------- |
 | type     | 风格类型   | string   | card/border-card  |     —    |
 | closable  | 标签是否可关闭   | boolean   | — |  false  |
-| active-name  | 选中选项卡的 name  | string   |  —  |  第一个选项卡的 name |
+| active-name(deprecated)  | 选中选项卡的 name  | string   |  —  |  第一个选项卡的 name |
+| value  | 绑定值,选中选项卡的 name  | string   |  —  |  第一个选项卡的 name |
 
 ### Tabs Events
 | 事件名称 | 说明 | 回调参数 |
@@ -140,3 +203,4 @@
 | label-content | 选项卡的标题的渲染 Function | Function(h, tab:vueInstance) | - | - |
 | disabled | 是否禁用 | boolean | - | false |
 | name      | 与选项卡 activeName 对应的标识符,表示选项卡别名 | string | — | 该选项卡在选项卡列表中的顺序值,如第一个选项卡则为'1' |
+| closable  | 标签是否可关闭   | boolean   | — |  false  |

+ 9 - 0
examples/docs/zh-CN/time-picker.md

@@ -153,6 +153,7 @@
 | readonly | 完全只读 | boolean | — | false |
 | disabled | 禁用 | boolean | - | false |
 | editable | 文本框可输入 | boolean | - | true |
+| clearable | 是否显示清除按钮 | boolean | - | true |
 | size          | 输入框尺寸     | string          | large, small, mini  | — |
 | placeholder | 占位内容 | string | — | — |
 | format | 时间格式化(TimePicker) | string | 小时:`HH`,分:`mm`,秒:`ss` | 'HH:mm:ss' |
@@ -175,3 +176,11 @@
 |---------- |-------------- |---------- |--------------------------------  |-------- |
 | selectableRange | 可选时间段,例如`'18:30:00 - 20:30:00'`或者传入数组`['09:30:00 - 12:00:00', '14:30:00 - 18:30:00']` | string/array | — | — |
 
+
+### Events
+| Event Name | Description | Parameters |
+|---------|--------|---------|
+| change | 当 input 的值改变时触发,返回值和文本框一致 | formatted value |
+
+
+

+ 6 - 0
examples/i18n/component.json

@@ -16,6 +16,9 @@
       "guide": "指南",
       "components": "组件",
       "resource": "资源"
+    },
+    "nav": {
+      "dropdown": "版本:"
     }
   },
   {
@@ -35,6 +38,9 @@
       "guide": "Guide",
       "components": "Component",
       "resource": "Resource"
+    },
+    "nav": {
+      "dropdown": "Version: "
     }
   }
 ]

+ 3 - 0
examples/versions.json

@@ -0,0 +1,3 @@
+{
+  "1.0.9": "1.0"
+}

+ 3 - 2
package.json

@@ -1,6 +1,6 @@
 {
   "name": "element-ui",
-  "version": "1.0.8",
+  "version": "1.0.9",
   "description": "A Component Library for Vue.js.",
   "main": "lib/element-ui.common.js",
   "files": [
@@ -58,13 +58,14 @@
     "babel-loader": "^6.2.5",
     "babel-plugin-module-resolver": "^2.2.0",
     "babel-plugin-syntax-jsx": "^6.8.0",
-    "babel-plugin-transform-vue-jsx": "^3.1.0",
+    "babel-plugin-transform-vue-jsx": "^3.3.0",
     "babel-preset-es2015": "^6.14.0",
     "chai": "^3.5.0",
     "cheerio": "^0.18.0",
     "cooking": "^1.2.0",
     "cooking-lint": "^0.1.3",
     "cooking-vue2": "^0.3.0",
+    "copy-webpack-plugin": "^4.0.1",
     "coveralls": "^2.11.14",
     "cp-cli": "^1.0.2",
     "cross-env": "^3.1.3",

+ 9 - 9
packages/checkbox/src/checkbox.vue

@@ -1,14 +1,14 @@
 <template>
   <label class="el-checkbox">
-    <span class="el-checkbox__input">
-      <span class="el-checkbox__inner"
-        :class="{
-          'is-disabled': disabled,
-          'is-checked': isChecked,
-          'is-indeterminate': indeterminate,
-          'is-focus': focus
-        }"
-      ></span>
+    <span class="el-checkbox__input"
+      :class="{
+        'is-disabled': disabled,
+        'is-checked': isChecked,
+        'is-indeterminate': indeterminate,
+        'is-focus': focus
+      }"
+    >
+      <span class="el-checkbox__inner"></span>
       <input
         v-if="trueLabel || falseLabel"
         class="el-checkbox__original"

+ 4 - 6
packages/date-picker/src/panel/date-range.vue

@@ -293,7 +293,7 @@
       handleClear() {
         this.minDate = null;
         this.maxDate = null;
-        this.handleConfirm();
+        this.handleConfirm(false);
       },
 
       handleDateInput(event, type) {
@@ -376,10 +376,8 @@
         this.maxDate = val.maxDate;
         this.minDate = val.minDate;
 
-        if (!close) return;
-        if (!this.showTime) {
-          this.$emit('pick', [this.minDate, this.maxDate]);
-        }
+        if (!close || this.showTime) return;
+        this.handleConfirm();
       },
 
       changeToToday() {
@@ -456,7 +454,7 @@
         this.resetDate();
       },
 
-      handleConfirm(visible) {
+      handleConfirm(visible = false) {
         this.$emit('pick', [this.minDate, this.maxDate], visible);
       },
 

+ 1 - 1
packages/date-picker/src/panel/date.vue

@@ -190,7 +190,7 @@
     methods: {
       handleClear() {
         this.date = new Date();
-        this.$emit('pick', '');
+        this.$emit('pick');
       },
 
       resetDate() {

+ 1 - 1
packages/date-picker/src/panel/time-select.vue

@@ -92,7 +92,7 @@
       },
 
       handleClear() {
-        this.$emit('pick', '');
+        this.$emit('pick');
       }
     },
 

+ 1 - 1
packages/date-picker/src/panel/time.vue

@@ -104,7 +104,7 @@
 
     methods: {
       handleClear() {
-        this.$emit('pick', '');
+        this.$emit('pick');
       },
 
       handleCancel() {

+ 30 - 8
packages/date-picker/src/picker.vue

@@ -26,7 +26,7 @@
 <script>
 import Vue from 'vue';
 import Clickoutside from 'element-ui/src/utils/clickoutside';
-import { formatDate, parseDate, getWeekNumber } from './util';
+import { formatDate, parseDate, getWeekNumber, equalDate } from './util';
 import Popper from 'element-ui/src/utils/vue-popper';
 import Emitter from 'element-ui/src/mixins/emitter';
 import ElInput from 'element-ui/packages/input';
@@ -187,6 +187,10 @@ export default {
     readonly: Boolean,
     placeholder: String,
     disabled: Boolean,
+    clearable: {
+      type: Boolean,
+      default: true
+    },
     popperClass: String,
     editable: {
       type: Boolean,
@@ -305,9 +309,10 @@ export default {
           if (parsedValue && this.picker) {
             this.picker.value = parsedValue;
           }
-          return;
+        } else {
+          this.picker.value = value;
         }
-        this.picker.value = value;
+        this.$forceUpdate();
       }
     }
   },
@@ -324,19 +329,34 @@ export default {
   methods: {
     handleMouseEnterIcon() {
       if (this.readonly || this.disabled) return;
-      if (!this.valueIsEmpty) {
+      if (!this.valueIsEmpty && this.clearable) {
         this.showClose = true;
       }
     },
 
     handleClickIcon() {
       if (this.readonly || this.disabled) return;
-      if (this.valueIsEmpty) {
+      if (this.showClose) {
+        this.internalValue = '';
+      } else {
         this.pickerVisible = !this.pickerVisible;
+      }
+    },
+
+    dateIsUpdated(date) {
+      let updated = true;
+
+      if (Array.isArray(date)) {
+        if (equalDate(this.cacheDateMin, date[0]) &&
+          equalDate(this.cacheDateMax, date[1])) updated = false;
+        this.cacheDateMin = date[0];
+        this.cacheDateMax = date[1];
       } else {
-        this.internalValue = '';
-        this.$emit('input', '');
+        if (equalDate(this.cacheDate, date)) updated = false;
+        this.cacheDate = date;
       }
+
+      return updated;
     },
 
     handleClose() {
@@ -419,7 +439,9 @@ export default {
 
         this.picker.$on('dodestroy', this.doDestroy);
         this.picker.$on('pick', (date, visible = false) => {
-          this.$emit('input', date);
+          if (this.dateIsUpdated(date)) this.$emit('input', date);
+
+          this.$nextTick(() => this.$emit('change', this.visualValue));
           this.pickerVisible = this.picker.visible = visible;
           this.picker.resetView && this.picker.resetView();
         });

+ 4 - 0
packages/date-picker/src/util/index.js

@@ -8,6 +8,10 @@ const newArray = function(start, end) {
   return result;
 };
 
+export const equalDate = function(dateA, dateB) {
+  return new Date(dateA).getTime() === new Date(dateB).getTime();
+};
+
 export const toDate = function(date) {
   date = new Date(date);
   if (isNaN(date.getTime())) return null;

+ 1 - 1
packages/form/README.md

@@ -45,7 +45,7 @@ Vue.component('el-form-item', ElForm)
 |---------- |-------------- |
 | validate(cb) | 对整个表单进行校验的方法 |
 | validateField(prop, cb) | 对部分表单字段进行校验的方法 |
-| resetFields | 对整个表单进行重置,将所有字段值重置为并移除校验结果 |
+| resetFields | 对整个表单进行重置,将所有字段值重置为初始值并移除校验结果 |
 
 ### Form-Item Attributes
 

+ 2 - 1
packages/form/src/form.vue

@@ -54,12 +54,13 @@
       },
       validate(callback) {
         let valid = true;
+        let count = 0;
         this.fields.forEach((field, index) => {
           field.validate('', errors => {
             if (errors) {
               valid = false;
             }
-            if (typeof callback === 'function' && index === this.fields.length - 1) {
+            if (typeof callback === 'function' && ++count === this.fields.length) {
               callback(valid);
             }
           });

+ 52 - 82
packages/input-number/src/input-number.vue

@@ -6,31 +6,11 @@
       { 'is-without-controls': !controls}
     ]"
   >
-    <el-input
-      :value="currentValue"
-      @keydown.up.native="increase"
-      @keydown.down.native="decrease"
-      @blur="handleBlur"
-      @input="handleInput"
-      :disabled="disabled"
-      :size="size"
-      :class="{
-        'is-active': inputActive
-      }">
-        <template slot="prepend" v-if="$slots.prepend">
-          <slot name="prepend"></slot>
-        </template>
-        <template slot="append" v-if="$slots.append">
-          <slot name="append"></slot>
-        </template>
-    </el-input>
     <span
       v-if="controls"
       class="el-input-number__decrease el-icon-minus"
       :class="{'is-disabled': minDisabled}"
       v-repeat-click="decrease"
-      @mouseenter="activeInput(minDisabled)"
-      @mouseleave="inactiveInput(minDisabled)"
     >
     </span>
     <span
@@ -38,10 +18,24 @@
       class="el-input-number__increase el-icon-plus"
       :class="{'is-disabled': maxDisabled}"
       v-repeat-click="increase"
-      @mouseenter="activeInput(maxDisabled)"
-      @mouseleave="inactiveInput(maxDisabled)"
     >
     </span>
+    <el-input
+      v-model.number="currentValue"
+      @keydown.up.native="increase"
+      @keydown.down.native="decrease"
+      @blur="handleBlur"
+      :disabled="disabled"
+      :size="size"
+      ref="input"
+    >
+        <template slot="prepend" v-if="$slots.prepend">
+          <slot name="prepend"></slot>
+        </template>
+        <template slot="append" v-if="$slots.append">
+          <slot name="append"></slot>
+        </template> 
+    </el-input>
   </div>
 </template>
 <script>
@@ -50,29 +44,6 @@
 
   export default {
     name: 'ElInputNumber',
-    props: {
-      step: {
-        type: Number,
-        default: 1
-      },
-      max: {
-        type: Number,
-        default: Infinity
-      },
-      min: {
-        type: Number,
-        default: 0
-      },
-      value: {
-        default: 0
-      },
-      disabled: Boolean,
-      size: String,
-      controls: {
-        type: Boolean,
-        default: true
-      }
-    },
     directives: {
       repeatClick: {
         bind(el, binding, vnode) {
@@ -99,6 +70,29 @@
     components: {
       ElInput
     },
+    props: {
+      step: {
+        type: Number,
+        default: 1
+      },
+      max: {
+        type: Number,
+        default: Infinity
+      },
+      min: {
+        type: Number,
+        default: 0
+      },
+      value: {
+        default: 0
+      },
+      disabled: Boolean,
+      size: String,
+      controls: {
+        type: Boolean,
+        default: true
+      }
+    },
     data() {
       // correct the init value
       let value = this.value;
@@ -111,8 +105,7 @@
         value = this.max;
       }
       return {
-        currentValue: value,
-        inputActive: false
+        currentValue: value
       };
     },
     watch: {
@@ -121,19 +114,18 @@
       },
 
       currentValue(newVal, oldVal) {
-        let value = Number(newVal);
-        if (value <= this.max && value >= this.min) {
-          this.$emit('change', value, oldVal);
-          this.$emit('input', value);
+        if (newVal <= this.max && newVal >= this.min) {
+          this.$emit('change', newVal, oldVal);
+          this.$emit('input', newVal);
         }
       }
     },
     computed: {
       minDisabled() {
-        return this.value - this.step < this.min;
+        return this.accSub(this.value, this.step) < this.min;
       },
       maxDisabled() {
-        return this.value + this.step > this.max;
+        return this.accAdd(this.value, this.step) > this.max;
       }
     },
     methods: {
@@ -183,41 +175,19 @@
         return (arg1 + arg2) / m;
       },
       increase() {
+        if (this.maxDisabled) return;
         const value = this.value || 0;
-        if (value + this.step > this.max || this.disabled) return;
-        this.currentValue = this.accAdd(this.step, value);
-        if (this.maxDisabled) {
-          this.inputActive = false;
-        }
+        if (this.accAdd(value, this.step) > this.max || this.disabled) return;
+        this.currentValue = this.accAdd(value, this.step);
       },
       decrease() {
+        if (this.minDisabled) return;
         const value = this.value || 0;
-        if (value - this.step < this.min || this.disabled) return;
+        if (this.accSub(value, this.step) < this.min || this.disabled) return;
         this.currentValue = this.accSub(value, this.step);
-        if (this.minDisabled) {
-          this.inputActive = false;
-        }
-      },
-      activeInput(disabled) {
-        if (!this.disabled && !disabled) {
-          this.inputActive = true;
-        }
-      },
-      inactiveInput(disabled) {
-        if (!this.disabled && !disabled) {
-          this.inputActive = false;
-        }
-      },
-      handleBlur(event) {
-        let value = Number(this.currentValue);
-        if (isNaN(value) || value > this.max || value < this.min) {
-          this.currentValue = this.value;
-        } else {
-          this.currentValue = value;
-        }
       },
-      handleInput(value) {
-        this.currentValue = value;
+      handleBlur() {
+        this.$refs.input.setCurrentValue(this.currentValue);
       }
     }
   };

+ 33 - 30
packages/input/src/input.vue

@@ -33,7 +33,7 @@
         :min="min"
         :max="max"
         :form="form"
-        :value="value"
+        :value="currentValue"
         ref="input"
         @input="handleInput"
         @focus="handleFocus"
@@ -48,7 +48,8 @@
     <textarea
       v-else
       class="el-textarea__inner"
-      v-model="currentValue"
+      :value="currentValue"
+      @input="handleInput"
       ref="textarea"
       :name="name"
       :placeholder="placeholder"
@@ -76,6 +77,13 @@
 
     mixins: [emitter],
 
+    data() {
+      return {
+        currentValue: this.value,
+        textareaStyle: {}
+      };
+    },
+
     props: {
       value: [String, Number],
       placeholder: String,
@@ -108,6 +116,18 @@
       min: {}
     },
 
+    computed: {
+      validating() {
+        return this.$parent.validateState === 'validating';
+      }
+    },
+
+    watch: {
+      'value'(val, oldValue) {
+        this.setCurrentValue(val);
+      }
+    },
+
     methods: {
       handleBlur(event) {
         this.$emit('blur', event);
@@ -129,46 +149,29 @@
         this.$emit('focus', event);
       },
       handleInput(event) {
-        this.currentValue = event.target.value;
+        this.setCurrentValue(event.target.value);
       },
       handleIconClick(event) {
         this.$emit('click', event);
+      },
+      setCurrentValue(value) {
+        if (value === this.currentValue) return;
+        this.$nextTick(_ => {
+          this.resizeTextarea();
+        });
+        this.currentValue = value;
+        this.$emit('input', value);
+        this.$emit('change', value);
+        this.dispatch('ElFormItem', 'el.form.change', [value]);
       }
     },
 
-    data() {
-      return {
-        currentValue: this.value,
-        textareaStyle: {}
-      };
-    },
-
     created() {
       this.$on('inputSelect', this.inputSelect);
     },
 
     mounted() {
       this.resizeTextarea();
-    },
-
-    computed: {
-      validating() {
-        return this.$parent.validateState === 'validating';
-      }
-    },
-
-    watch: {
-      'value'(val, oldValue) {
-        this.currentValue = val;
-      },
-      'currentValue'(val) {
-        this.$nextTick(_ => {
-          this.resizeTextarea();
-        });
-        this.$emit('input', val);
-        this.$emit('change', val);
-        this.dispatch('ElFormItem', 'el.form.change', [val]);
-      }
     }
   };
 </script>

+ 8 - 0
packages/menu/src/menu.vue

@@ -107,6 +107,14 @@
       },
       openActiveItemMenus() {
         let index = this.activeIndex;
+        // 选中用户指定的路由对应的menu
+        if (this.router) {
+          const userSpecifiedIndexs = Object
+                                       .keys(this.menuItems)
+                                       .filter(k => this.menuItems[k].route)
+                                       .filter(k => this.menuItems[k].route.path === this.$route.path);
+          userSpecifiedIndexs.length && (index = this.activeIndex = userSpecifiedIndexs[0]);
+        }
         if (!this.menuItems[index]) return;
         if (index && this.mode === 'vertical') {
           let indexPath = this.menuItems[index].indexPath;

+ 2 - 0
packages/pagination/src/pagination.js

@@ -100,6 +100,7 @@ export default {
       render(h) {
         return (
           <button
+            type="button"
             class={['btn-prev', { disabled: this.$parent.internalCurrentPage <= 1 }]}
             on-click={ this.$parent.prev }>
             <i class="el-icon el-icon-arrow-left"></i>
@@ -112,6 +113,7 @@ export default {
       render(h) {
         return (
           <button
+            type="button"
             class={[
               'btn-next',
               { disabled: this.$parent.internalCurrentPage === this.$parent.internalPageCount || this.$parent.internalPageCount === 0 }

+ 5 - 4
packages/radio/src/radio.vue

@@ -1,12 +1,13 @@
 <template>
   <label class="el-radio">
-    <span class="el-radio__input">
-      <span class="el-radio__inner"
-        :class="{
+    <span class="el-radio__input"
+      :class="{
         'is-disabled': disabled,
         'is-checked': model === label,
         'is-focus': focus
-      }"></span>
+      }"
+    >
+      <span class="el-radio__inner"></span>
       <input
         class="el-radio__original"
         :value="label"

+ 0 - 5
packages/select/src/option.vue

@@ -92,11 +92,6 @@
       },
       value() {
         this.dispatch('ElSelect', 'setSelected');
-      },
-      visible() {
-        this.$nextTick(() => {
-          this.dispatch('ElSelectDropdown', 'updatePopper');
-        });
       }
     },
 

+ 4 - 2
packages/select/src/select.vue

@@ -233,7 +233,9 @@
       },
 
       query(val) {
-        this.broadcast('ElSelectDropdown', 'updatePopper');
+        this.$nextTick(() => {
+          this.broadcast('ElSelectDropdown', 'updatePopper');
+        });
         this.hoverIndex = -1;
         if (this.multiple && this.filterable) {
           this.resetInputHeight();
@@ -557,7 +559,7 @@
 
       deleteTag(event, tag) {
         let index = this.selected.indexOf(tag);
-        if (index > -1) {
+        if (index > -1 && !this.disabled) {
           this.value.splice(index, 1);
         }
         event.stopPropagation();

+ 18 - 4
packages/table/src/table-body.js

@@ -38,7 +38,7 @@ export default {
         <tbody>
           {
             this._l(this.data, (row, $index) =>
-              <tr
+              [<tr
                 style={ this.rowStyle ? this.getRowStyle(row, $index) : null }
                 key={ this.table.rowKey ? this.getKeyOfRow(row, $index) : $index }
                 on-dblclick={ ($event) => this.handleDoubleClick($event, row) }
@@ -46,7 +46,7 @@ export default {
                 on-contextmenu={ ($event) => this.handleContextMenu($event, row) }
                 on-mouseenter={ _ => this.handleMouseEnter($index) }
                 on-mouseleave={ _ => this.handleMouseLeave() }
-                class={ this.getRowClass(row, $index) }>
+                class={ [this.getRowClass(row, $index)] }>
                 {
                   this._l(this.columns, (column, cellIndex) =>
                     <td
@@ -62,7 +62,15 @@ export default {
                 {
                   !this.fixed && this.layout.scrollY && this.layout.gutterWidth ? <td class="gutter" /> : ''
                 }
-              </tr>
+              </tr>,
+                this.store.states.expandRows.indexOf(row) > -1
+                ? (<tr>
+                    <td colspan={ this.columns.length } class="el-table__expanded-cell">
+                      { this.$parent.renderExpanded ? this.$parent.renderExpanded.call(this._renderProxy, h, { row, $index, store: this.store, _self: this.$parent.$vnode.context }) : ''}
+                    </td>
+                  </tr>)
+                : ''
+              ]
             )
           }
         </tbody>
@@ -95,6 +103,8 @@ export default {
       const newRow = rows[data.indexOf(newVal)];
       if (oldRow) {
         oldRow.classList.remove('current-row');
+      } else if (rows) {
+        [].forEach.call(rows, row => row.classList.remove('current-row'));
       }
       if (newRow) {
         newRow.classList.add('current-row');
@@ -180,7 +190,7 @@ export default {
 
       if (cell) {
         const column = getColumnByCell(table, cell);
-        const hoverState = table.hoverState = { cell, column, row };
+        const hoverState = table.hoverState = {cell, column, row};
         table.$emit('cell-mouse-enter', hoverState.row, hoverState.column, hoverState.cell, event);
       }
 
@@ -230,6 +240,10 @@ export default {
       this.store.commit('setCurrentRow', row);
 
       table.$emit('row-click', row, event, column);
+    },
+
+    handleExpandClick(row) {
+      this.store.commit('toggleRowExpanded', row);
     }
   }
 };

+ 55 - 2
packages/table/src/table-column.js

@@ -16,6 +16,12 @@ const defaults = {
     order: '',
     className: 'el-table-column--selection'
   },
+  expand: {
+    width: 48,
+    minWidth: 48,
+    realWidth: 48,
+    order: ''
+  },
   index: {
     width: 48,
     minWidth: 48,
@@ -48,6 +54,21 @@ const forced = {
       return <div>{ $index + 1 }</div>;
     },
     sortable: false
+  },
+  expand: {
+    renderHeader: function(h, {}) {
+      return '';
+    },
+    renderCell: function(h, { row, store }, proxy) {
+      const expanded = store.states.expandRows.indexOf(row) > -1;
+      return <div class={ 'el-table__expand-icon ' + (expanded ? 'el-table__expand-icon--expanded' : '') }
+                  on-click={ () => proxy.handleExpandClick(row) }>
+        <i class='el-icon el-icon-arrow-right'></i>
+      </div>;
+    },
+    sortable: false,
+    resizable: false,
+    className: 'el-table__expand-column'
   }
 };
 
@@ -114,6 +135,7 @@ export default {
     context: {},
     columnKey: String,
     align: String,
+    headerAlign: String,
     showTooltipWhenOverflow: Boolean,
     showOverflowTooltip: Boolean,
     fixed: [Boolean, String],
@@ -199,6 +221,7 @@ export default {
       isColumnGroup,
       context: this.context,
       align: this.align ? 'is-' + this.align : null,
+      headerAlign: this.headerAlign ? 'is-' + this.headerAlign : (this.align ? 'is-' + this.align : null),
       sortable: this.sortable,
       sortMethod: this.sortMethod,
       resizable: this.resizable,
@@ -217,9 +240,35 @@ export default {
 
     objectAssign(column, forced[type] || {});
 
+    this.columnConfig = column;
+
     let renderCell = column.renderCell;
     let _self = this;
 
+    if (type === 'expand') {
+      owner.renderExpanded = function(h, data) {
+        if (_self.$vnode.data.inlineTemplate) {
+          data._self = _self.context || data._self;
+          if (Object.prototype.toString.call(data._self) === '[object Object]') {
+            for (let prop in data._self) {
+              if (!data.hasOwnProperty(prop)) {
+                data[prop] = data._self[prop];
+              }
+            }
+          }
+          data._staticTrees = _self._staticTrees;
+          data.$options.staticRenderFns = _self.$options.staticRenderFns;
+          return _self.customRender.call(data);
+        }
+      };
+
+      column.renderCell = function(h, data) {
+        return <div class="cell">{ renderCell(h, data, this._renderProxy) }</div>;
+      };
+
+      return;
+    }
+
     column.renderCell = function(h, data) {
       if (_self.$vnode.data.inlineTemplate) {
         renderCell = function() {
@@ -254,8 +303,6 @@ export default {
           </el-tooltip>
         : <div class="cell">{ renderCell(h, data) }</div>;
     };
-
-    this.columnConfig = column;
   },
 
   destroyed() {
@@ -300,6 +347,12 @@ export default {
       }
     },
 
+    headerAlign(newVal) {
+      if (this.columnConfig) {
+        this.columnConfig.headerAlign = newVal ? 'is-' + newVal : this.align;
+      }
+    },
+
     width(newVal) {
       if (this.columnConfig) {
         this.columnConfig.width = newVal;

+ 9 - 10
packages/table/src/table-header.js

@@ -103,7 +103,7 @@ export default {
                       on-mouseout={ this.handleMouseOut }
                       on-mousedown={ ($event) => this.handleMouseDown($event, column) }
                       on-click={ ($event) => this.handleClick($event, column) }
-                      class={ [column.id, column.order, column.align, column.className || '', rowIndex === 0 && this.isCellHidden(cellIndex) ? 'is-hidden' : '', !column.children ? 'is-leaf' : ''] }>
+                      class={ [column.id, column.order, column.headerAlign, column.className || '', rowIndex === 0 && this.isCellHidden(cellIndex) ? 'is-hidden' : '', !column.children ? 'is-leaf' : ''] }>
                       <div class={ ['cell', column.filteredValue && column.filteredValue.length > 0 ? 'highlight' : ''] }>
                       {
                         column.renderHeader
@@ -112,9 +112,9 @@ export default {
                       }
                       {
                         column.sortable
-                          ? <span class="caret-wrapper" on-click={ ($event) => this.handleHeaderClick($event, column) }>
-                              <i class="sort-caret ascending"></i>
-                              <i class="sort-caret descending"></i>
+                          ? <span class="caret-wrapper">
+                              <i class="sort-caret ascending" on-click={ ($event) => this.handleHeaderClick($event, column, 'ascending')}></i>
+                              <i class="sort-caret descending" on-click={ ($event) => this.handleHeaderClick($event, column, 'descending')}></i>
                             </span>
                           : ''
                        }
@@ -334,7 +334,7 @@ export default {
       document.body.style.cursor = '';
     },
 
-    handleHeaderClick(event, column) {
+    handleHeaderClick(event, column, order) {
       let target = event.target;
       while (target && target.tagName !== 'TH') {
         target = target.parentNode;
@@ -362,15 +362,14 @@ export default {
         sortProp = column.property;
       }
 
-      if (!column.order) {
-        sortOrder = column.order = 'ascending';
-      } else if (column.order === 'ascending') {
-        sortOrder = column.order = 'descending';
-      } else {
+      if (column.order === order) {
         sortOrder = column.order = null;
         states.sortingColumn = null;
         sortProp = null;
+      } else {
+        sortOrder = column.order = order;
       }
+
       states.sortProp = sortProp;
       states.sortOrder = sortOrder;
 

+ 55 - 1
packages/table/src/table-store.js

@@ -69,7 +69,9 @@ const TableStore = function(table, initialState = {}) {
     selectable: null,
     currentRow: null,
     hoverRow: null,
-    filters: {}
+    filters: {},
+    expandRows: [],
+    defaultExpandAll: false
   };
 
   for (let prop in initialState) {
@@ -85,6 +87,15 @@ TableStore.prototype.mutations = {
     states._data = data;
     states.data = sortData((data || []), states);
 
+    states.data.forEach((item) => {
+      if (!item.$extra) {
+        Object.defineProperty(item, '$extra', {
+          value: {},
+          enumerable: false
+        });
+      }
+    });
+
     this.updateCurrentRow();
 
     if (!states.reserveSelection) {
@@ -114,6 +125,11 @@ TableStore.prototype.mutations = {
       }
     }
 
+    const defaultExpandAll = states.defaultExpandAll;
+    if (defaultExpandAll) {
+      this.states.expandRows = (states.data || []).slice(0);
+    }
+
     Vue.nextTick(() => this.table.updateScrollY());
   },
 
@@ -218,6 +234,26 @@ TableStore.prototype.mutations = {
     this.updateAllSelected();
   },
 
+  toggleRowExpanded: function(states, row, expanded) {
+    const expandRows = states.expandRows;
+    if (typeof expanded !== 'undefined') {
+      const index = expandRows.indexOf(row);
+      if (expanded) {
+        if (index === -1) expandRows.push(row);
+      } else {
+        if (index !== -1) expandRows.splice(index, 1);
+      }
+    } else {
+      const index = expandRows.indexOf(row);
+      if (index === -1) {
+        expandRows.push(row);
+      } else {
+        expandRows.splice(index, 1);
+      }
+    }
+    this.table.$emit('expand', row, expandRows.indexOf(row) !== -1);
+  },
+
   toggleAllSelection: debounce(10, function(states) {
     const data = states.data || [];
     const value = !states.isAllSelected;
@@ -286,6 +322,22 @@ TableStore.prototype.clearSelection = function() {
   }
 };
 
+TableStore.prototype.setExpandRowKeys = function(rowKeys) {
+  const expandRows = [];
+  const data = this.states.data;
+  const rowKey = this.states.rowKey;
+  if (!rowKey) throw new Error('[Table] prop row-key should not be empty.');
+  const keysMap = getKeysMap(data, rowKey);
+  rowKeys.forEach((key) => {
+    const info = keysMap[key];
+    if (info) {
+      expandRows.push(info.row);
+    }
+  });
+
+  this.states.expandRows = expandRows;
+};
+
 TableStore.prototype.toggleRowSelection = function(row, selected) {
   const changed = toggleRowSelection(this.states, row, selected);
   if (changed) {
@@ -395,6 +447,8 @@ TableStore.prototype.commit = function(name, ...args) {
   const mutations = this.mutations;
   if (mutations[name]) {
     mutations[name].apply(this, [this.states].concat(args));
+  } else {
+    throw new Error(`Action not found: ${name}`);
   }
 };
 

+ 12 - 2
packages/table/src/table.vue

@@ -164,7 +164,11 @@
 
       highlightCurrentRow: Boolean,
 
-      emptyText: String
+      emptyText: String,
+
+      expandRowKeys: Array,
+
+      defaultExpandAll: Boolean
     },
 
     components: {
@@ -356,6 +360,10 @@
         handler(val) {
           this.store.commit('setData', val);
         }
+      },
+
+      expandRowKeys(newVal) {
+        this.store.setExpandRowKeys(newVal);
       }
     },
 
@@ -372,7 +380,8 @@
 
     data() {
       const store = new TableStore(this, {
-        rowKey: this.rowKey
+        rowKey: this.rowKey,
+        defaultExpandAll: this.defaultExpandAll
       });
       const layout = new TableLayout({
         store,
@@ -383,6 +392,7 @@
       return {
         store,
         layout,
+        renderExpanded: null,
         resizeProxyVisible: false
       };
     }

+ 16 - 49
packages/tabs/src/tab-pane.vue

@@ -1,3 +1,10 @@
+<template>
+  <div class="el-tab-pane">
+    <div class="el-tab-pane__content" v-show="active">
+      <slot></slot>
+    </div>
+  </div>
+</template>
 <script>
   module.exports = {
     name: 'el-tab-pane',
@@ -12,73 +19,33 @@
 
     data() {
       return {
-        counter: 0,
-        transition: '',
-        paneStyle: {
-          position: 'relative'
-        },
-        isClosable: null,
-        index: ''
+        index: null
       };
     },
 
-    created() {
-      const propsData = this.$options.propsData;
-      if (propsData && typeof propsData.closable !== 'undefined') {
-        this.isClosable = propsData.closable === '' || propsData.closable;
-      } else {
-        this.isClosable = this.$parent.closable;
-      }
-      if (!this.index) {
-        this.index = this.$parent.$children.indexOf(this) + 1 + '';
-      }
-      if (this.$parent.panes) {
-        this.$parent.panes.push(this);
+    computed: {
+      isClosable() {
+        return this.closable || this.$parent.closable;
+      },
+      active() {
+        return this.$parent.currentName === (this.name || this.index);
       }
     },
 
-    computed: {
-      show() {
-        return this.$parent.currentName === this.index;
-      }
+    created() {
+      this.$parent.$forceUpdate();
     },
 
     destroyed() {
       if (this.$el && this.$el.parentNode) {
         this.$el.parentNode.removeChild(this.$el);
       }
-      const panes = this.$parent.panes;
-      if (panes) {
-        panes.splice(this, panes.indexOf(this));
-      }
     },
 
     watch: {
-      name: {
-        immediate: true,
-        handler(val) {
-          this.index = val;
-        }
-      },
-      closable(val) {
-        this.isClosable = val;
-      },
-      '$parent.currentName'(newValue, oldValue) {
-        if (this.index === newValue) {
-          this.transition = newValue > oldValue ? 'slideInRight' : 'slideInLeft';
-        }
-        if (this.index === oldValue) {
-          this.transition = oldValue > newValue ? 'slideInRight' : 'slideInLeft';
-        }
-      },
       label() {
         this.$parent.$forceUpdate();
       }
     }
   };
 </script>
-<template>
-  <div class="el-tab-pane" v-show="show && $slots.default">
-    <slot></slot>
-  </div>
-</template>

+ 88 - 79
packages/tabs/src/tabs.vue

@@ -4,69 +4,84 @@
 
     props: {
       type: String,
-      tabPosition: String,
       activeName: String,
-      closable: false,
-      tabWidth: 0
+      closable: {
+        type: Boolean,
+        default: false
+      },
+      value: {}
     },
 
     data() {
       return {
         children: null,
-        activeTab: null,
-        currentName: 0,
-        panes: []
+        currentName: this.value || this.activeName
       };
     },
 
     watch: {
-      activeName: {
-        handler(val) {
-          this.currentName = val;
-        }
+      activeName(val) {
+        this.currentName = val;
+      },
+      value(val) {
+        this.currentName = val;
+      },
+      currentName(val) {
+        this.$emit('input', val);
       }
     },
 
     methods: {
       handleTabRemove(tab, event) {
         event.stopPropagation();
-        let tabs = this.$children;
+        const tabs = this.$children;
 
-        var index = tabs.indexOf(tab);
-        tab.$destroy(true);
-
-        if (tab.index === this.currentName) {
-          let nextChild = tabs[index];
-          let prevChild = tabs[index - 1];
-
-          while (prevChild && prevChild.disabled) {
-            prevChild = tabs[tabs.indexOf(prevChild) - 1];
-          }
+        let index = tabs.indexOf(tab);
+        tab.$destroy();
 
-          this.currentName = nextChild
-            ? nextChild.index
-            : prevChild
-            ? prevChild.index
-            : '-1';
-        }
         this.$emit('tab-remove', tab);
         this.$forceUpdate();
+
+        this.$nextTick(_ => {
+          if (tab.active) {
+            let nextChild = tabs[index];
+            let prevChild = tabs[index - 1];
+            let nextActiveTab = nextChild || prevChild || null;
+
+            if (nextActiveTab) {
+              this.currentName = nextActiveTab.name || nextActiveTab.index;
+            }
+          }
+        });
       },
-      handleTabClick(tab, event) {
+      handleTabClick(tab, tabName, event) {
         if (tab.disabled) return;
-        this.currentName = tab.index;
+        this.currentName = tabName;
         this.$emit('tab-click', tab, event);
-      },
-      calcBarStyle() {
+      }
+    },
+    mounted() {
+      this.$forceUpdate();
+    },
+    render(h) {
+      let {
+        type,
+        handleTabRemove,
+        handleTabClick,
+        currentName
+      } = this;
+
+      const getBarStyle = () => {
         if (this.type || !this.$refs.tabs) return {};
-        var style = {};
-        var offset = 0;
-        var tabWidth = 0;
+        let style = {};
+        let offset = 0;
+        let tabWidth = 0;
 
-        this.$children.every((panel, index) => {
+        this.$children.every((tab, index) => {
           let $el = this.$refs.tabs[index];
           if (!$el) { return false; }
-          if (panel.index !== this.currentName) {
+
+          if (!tab.active) {
             offset += $el.clientWidth;
             return true;
           } else {
@@ -79,51 +94,45 @@
         style.transform = `translateX(${offset}px)`;
 
         return style;
-      }
-    },
-    mounted() {
-      this.currentName = this.activeName || this.$children[0] && this.$children[0].index || '1';
-      this.$nextTick(() => {
-        this.$forceUpdate();
-      });
-    },
-    render(h) {
-      let {
-        type,
-        panes, // eslint-disable-line
-        handleTabRemove,
-        handleTabClick,
-        currentName
-      } = this;
-
-      const barStyle = this.calcBarStyle();
-      const activeBar = !type
-        ? <div class="el-tabs__active-bar" style={barStyle}></div>
-        : null;
+      };
 
       const tabs = this.$children.map((tab, index) => {
-        let btnClose = h('span', {
-          class: {
-            'el-icon-close': true
-          },
-          on: { click: (ev) => { handleTabRemove(tab, ev); } }
-        });
-        const _tab = h('div', {
-          class: {
-            'el-tabs__item': true,
-            'is-active': currentName === tab.index,
-            'is-disabled': tab.disabled,
-            'is-closable': tab.isClosable
-          },
-          ref: 'tabs',
-          refInFor: true,
-          on: { click: (ev) => { handleTabClick(tab, ev); } }
-        }, [
-          tab.labelContent ? tab.labelContent.call(this._renderProxy, h, tab) : tab.label,
-          tab.isClosable ? btnClose : null,
-          index === 0 ? activeBar : null
-        ]);
-        return _tab;
+        let tabName = tab.name || tab.index || index;
+        if (currentName === undefined && index === 0) {
+          this.currentName = tabName;
+        }
+
+        tab.index = index;
+
+        const activeBar = !type && index === 0
+          ? <div class="el-tabs__active-bar" style={getBarStyle()}></div>
+          : null;
+
+        const btnClose = tab.isClosable
+          ? <span class="el-icon-close" on-click={(ev) => { handleTabRemove(tab, ev); }}></span>
+          : null;
+
+        const tabLabelContent = tab.labelContent
+          ? tab.labelContent.call(this._renderProxy, h, tab)
+          : tab.label;
+
+        return (
+          <div
+            class={{
+              'el-tabs__item': true,
+              'is-active': tab.active,
+              'is-disabled': tab.disabled,
+              'is-closable': tab.isClosable
+            }}
+            ref="tabs"
+            refInFor
+            on-click={(ev) => { handleTabClick(tab, tabName, ev); }}
+          >
+            {tabLabelContent}
+            {btnClose}
+            {activeBar}
+          </div>
+        );
       });
       return (
         <div class={{

+ 1 - 1
packages/theme-default/package.json

@@ -1,6 +1,6 @@
 {
   "name": "element-theme-default",
-  "version": "1.0.8",
+  "version": "1.0.9",
   "description": "Element component default theme.",
   "main": "lib/index.css",
   "style": "lib/index.css",

+ 78 - 67
packages/theme-default/src/checkbox.css

@@ -19,6 +19,83 @@
       line-height: 1;
       position: relative;
       vertical-align: middle;
+
+      @when disabled {
+        .el-checkbox__inner {
+          background-color: var(--checkbox-disabled-input-fill);
+          border-color: var(--checkbox-disabled-input-border-color);
+          cursor: not-allowed;
+
+          &::after {
+            cursor: not-allowed;
+            border-color: var(--checkbox-disabled-icon-color);
+          }
+
+          & + .el-checkbox__label {
+            cursor: not-allowed;
+          }
+        }
+        &.is-checked {
+          .el-checkbox__inner {
+            background-color: var(--checkbox-disabled-checked-input-fill);
+            border-color: var(--checkbox-disabled-checked-input-border-color);
+
+            &::after {
+              border-color: var(--checkbox-disabled-checked-icon-color);
+            }
+          }
+        }
+        &.is-indeterminate {
+          .el-checkbox__inner {
+            background-color: var(--checkbox-disabled-checked-input-fill);
+            border-color: var(--checkbox-disabled-checked-input-border-color);
+
+            &::before {
+              border-color: var(--checkbox-disabled-checked-icon-color);
+            }
+          }
+        }
+        & + .el-checkbox__label {
+          color: var(--disabled-color-base);
+          cursor: not-allowed;
+        }
+      }
+      @when checked {
+        .el-checkbox__inner {
+          background-color: var(--checkbox-checked-input-fill);
+          border-color: var(--checkbox-checked-input-border-color);
+
+          &::after {
+            transform: rotate(45deg) scaleY(1);
+          }
+        }
+      }
+      @when focus {
+        .el-checkbox__inner {
+          border-color: var(--checkbox-input-border-color-hover);
+        }
+      }
+      @when indeterminate {
+        .el-checkbox__inner {
+          background-color: var(--checkbox-checked-input-fill);
+          border-color: var(--checkbox-checked-input-border-color);
+
+          &::before {
+            content: '';
+            position: absolute;
+            display: block;
+            border: 1px solid var(--checkbox-checked-icon-color);
+            margin-top: -1px;
+            left: 3px;
+            right: 3px;
+            top: 50%;
+          }
+
+          &::after {
+            display: none;
+          }
+        }
+      }
     }
     @e inner {
       display: inline-block;
@@ -31,7 +108,7 @@
       transition: border-color .25s cubic-bezier(.71,-.46,.29,1.46),
                   background-color .25s cubic-bezier(.71,-.46,.29,1.46);
 
-      &:not(.is-disabled):hover {
+      &:hover {
         border-color: var(--checkbox-input-border-color-hover);
       }
 
@@ -50,72 +127,6 @@
         transition: transform .15s cubic-bezier(.71,-.46,.88,.6) .05s;
         transform-origin: center;
       }
-
-      @when disabled {
-        background-color: var(--checkbox-disabled-input-fill);
-        border-color: var(--checkbox-disabled-input-border-color);
-        cursor: not-allowed;
-
-        &::after {
-          cursor: not-allowed;
-          border-color: var(--checkbox-disabled-icon-color);
-        }
-
-        & + .el-checkbox__label {
-          cursor: not-allowed;
-        }
-      }
-
-      @when checked {
-        background-color: var(--checkbox-checked-input-fill);
-        border-color: var(--checkbox-checked-input-border-color);
-
-        &::after {
-          transform: rotate(45deg) scaleY(1);
-        }
-      }
-
-      @when focus {
-        border-color: var(--checkbox-input-border-color-hover);
-      }
-
-      &.is-disabled.is-checked {
-        background-color: var(--checkbox-disabled-checked-input-fill);
-        border-color: var(--checkbox-disabled-checked-input-border-color);
-
-        &::after {
-          border-color: var(--checkbox-disabled-checked-icon-color);
-        }
-      }
-
-      @when indeterminate {
-        background-color: var(--checkbox-checked-input-fill);
-        border-color: var(--checkbox-checked-input-border-color);
-
-        &::before {
-          content: '';
-          position: absolute;
-          display: block;
-          border: 1px solid var(--checkbox-checked-icon-color);
-          margin-top: -1px;
-          left: 3px;
-          right: 3px;
-          top: 50%;
-        }
-
-        &::after {
-          display: none;
-        }
-      }
-
-      &.is-disabled.is-indeterminate {
-        background-color: var(--checkbox-disabled-checked-input-fill);
-        border-color: var(--checkbox-disabled-checked-input-border-color);
-
-        &::before {
-          border-color: var(--checkbox-disabled-checked-icon-color);
-        }
-      }
     }
 
     @e original {

+ 23 - 8
packages/theme-default/src/col.css

@@ -7,26 +7,41 @@
 }
 
 @for $i from 1 to 24 {
-  .el-col-$i,
-  .el-col-xs-$i {
+  .el-col-$i {
     width: calc(1 / 24 * $(i) * 100)%;
   }
-  .el-col-offset-$i,
-  .el-col-xs-offset-$i {
+  .el-col-offset-$i {
     margin-left: calc(1 / 24 * $(i) * 100)%;
   }
-  .el-col-pull-$i,
-  .el-col-xs-pull-$i {
+  .el-col-pull-$i {
     position: relative;
     right: calc(1 / 24 * $(i) * 100)%;
   }
-  .el-col-push-$i,
-  .el-col-xs-push-$i {
+  .el-col-push-$i {
     position: relative;
     left: calc(1 / 24 * $(i) * 100)%;
   }
 }
 
+@media (max-width: 768px) {
+  @for $i from 1 to 24 {
+    .el-col-xs-$i {
+      width: calc(1 / 24 * $(i) * 100)%;
+    }
+    .el-col-xs-offset-$i {
+      margin-left: calc(1 / 24 * $(i) * 100)%;
+    }
+    .el-col-xs-pull-$i {
+      position: relative;
+      right: calc(1 / 24 * $(i) * 100)%;
+    }
+    .el-col-xs-push-$i {
+      position: relative;
+      left: calc(1 / 24 * $(i) * 100)%;
+    }
+  }
+}
+
 @media (min-width: 768px) {
   @for $i from 1 to 24 {
     .el-col-sm-$i {

+ 5 - 0
packages/theme-default/src/input-number.css

@@ -23,9 +23,14 @@
       color: #99A9BF;
       cursor: pointer;
       position: absolute;
+      z-index: 1;
 
       &:hover {
         color: var(--color-primary);
+
+        &:not(.is-disabled) ~ .el-input .el-input__inner:not(.is-disabled) {
+          border-color: var(--input-focus-border);
+        }
       }
 
       @when disabled {

+ 1 - 0
packages/theme-default/src/pagination.css

@@ -7,6 +7,7 @@
     white-space: nowrap;
     padding: 2px 5px;
     color: var(--pagination-color);
+    @utils-clearfix;
 
     span,
     button {

+ 49 - 38
packages/theme-default/src/radio.css

@@ -24,6 +24,54 @@
       line-height: 1;
       position: relative;
       vertical-align: middle;
+
+      @when disabled {
+        .el-radio__inner {
+          background-color: var(--radio-disabled-input-fill);
+          border-color: var(--radio-disabled-input-border-color);
+          cursor: not-allowed;
+
+          &::after {
+            cursor: not-allowed;
+            background-color: var(--radio-disabled-icon-color);
+          }
+
+          & + .el-radio__label {
+            cursor: not-allowed;
+          }
+        }
+        &.is-checked {
+          .el-radio__inner {
+            background-color: var(--radio-disabled-checked-input-fill);
+            border-color: var(--radio-disabled-checked-input-border-color);
+
+            &::after {
+              background-color: var(--radio-disabled-checked-icon-color);
+            }
+          }
+        }
+        & + .el-radio__label {
+          color: var(--disabled-color-base);
+          cursor: not-allowed;
+        }
+      }
+
+      @when checked {
+        .el-radio__inner {
+          border-color: var(--radio-checked-input-border-color);
+          background: var(--radio-checked-icon-color);
+
+          &::after {
+            transform: translate(-50%, -50%) scale(1);
+          }
+        }
+      }
+
+      @when focus {
+        .el-radio__inner {
+          border-color: var(--radio-input-border-color-hover);
+        }
+      }
     }
     @e inner {
       border: var(--radio-input-border);
@@ -34,7 +82,7 @@
       display: inline-block;
       box-sizing: border-box;
 
-      &:not(.is-disabled):hover {
+      &:hover {
         border-color: var(--radio-input-border-color-hover);
       }
 
@@ -47,43 +95,6 @@
         transform: translate(-50%, -50%) scale(0);
         transition: transform .15s cubic-bezier(.71,-.46,.88,.6);
       }
-
-      @when disabled {
-        background-color: var(--radio-disabled-input-fill);
-        border-color: var(--radio-disabled-input-border-color);
-        cursor: not-allowed;
-
-        &::after {
-          cursor: not-allowed;
-          background-color: var(--radio-disabled-icon-color);
-        }
-
-        & + .el-radio__label {
-          cursor: not-allowed;
-        }
-      }
-
-      @when checked {
-        border-color: var(--radio-checked-input-border-color);
-        background: var(--radio-checked-icon-color);
-
-        &::after {
-          transform: translate(-50%, -50%) scale(1);
-        }
-      }
-
-      @when focus {
-        border-color: var(--radio-input-border-color-hover);
-      }
-      
-      &.is-disabled.is-checked {
-        background-color: var(--radio-disabled-checked-input-fill);
-        border-color: var(--radio-disabled-checked-input-border-color);
-
-        &::after {
-          background-color: var(--radio-disabled-checked-icon-color);
-        }
-      }
     }
 
     @e original {

+ 38 - 0
packages/theme-default/src/table.css

@@ -66,6 +66,44 @@
       color: #5e6d82;
     }
 
+    @e expand-column {
+      .cell {
+        padding: 0;
+        text-align: center;
+      }
+    }
+
+    @e expand-icon {
+      position: relative;
+      cursor: pointer;
+      color: #666;
+      font-size: 12px;
+      transition: transform 0.2s ease-in-out;
+      height: 40px;
+
+      @m expanded {
+        transform: rotate(90deg);
+      }
+
+      > .el-icon {
+        position: absolute;
+        left: 50%;
+        top: 50%;
+        margin-left: -5px;
+        margin-top: -5px;
+      }
+    }
+
+    @e expanded-cell {
+      padding: 20px 50px;
+      background-color: #f9fafc;
+      box-shadow: inset 0 2px 0 #f4f4f4;
+
+      &:hover {
+        background-color: #f9fafc !important;
+      }
+    }
+
     @modifier fit {
       border-right: 0;
       border-bottom: 0;

+ 0 - 4
packages/theme-default/src/tabs.css

@@ -147,10 +147,6 @@
       }
     }
   }
-  @b tab-pane {
-    width: 100%;
-    display: inline-block;
-  }
 }
 
 .slideInRight-transition,

+ 2 - 2
packages/tree/src/tree-node.vue

@@ -133,12 +133,12 @@
         this.tree.$emit('current-change', store.currentNode ? store.currentNode.data : null, store.currentNode);
         this.tree.currentNode = this;
         if (this.tree.expandOnClickNode) {
-          this.handleExpandIconClick(event);
+          this.handleExpandIconClick();
         }
         this.tree.$emit('node-click', this.node.data, this.node, this);
       },
 
-      handleExpandIconClick(event) {
+      handleExpandIconClick() {
         if (this.expanded) {
           this.node.collapse();
         } else {

+ 1 - 1
src/index.js

@@ -141,7 +141,7 @@ if (typeof window !== 'undefined' && window.Vue) {
 };
 
 module.exports = {
-  version: '1.0.8',
+  version: '1.0.9',
   locale: locale.use,
   install,
   Loading,

+ 65 - 0
test/unit/specs/date-picker.spec.js

@@ -91,6 +91,31 @@ describe('DatePicker', () => {
     }, DELAY);
   });
 
+  it('disabled clear value', done => {
+    vm = createVue({
+      template: `
+        <el-date-picker v-model="value" ref="compo" :clearable="false"></el-date-picker>
+      `,
+      data() {
+        return { value: '' };
+      }
+    }, true);
+    const input = vm.$el.querySelector('input');
+
+    input.focus();
+    setTimeout(_ => {
+      const $el = vm.$refs.compo.picker.$el;
+      $el.querySelector('td.available').click();
+      vm.$nextTick(_ => {
+        vm.$el.querySelector('.el-input__icon').click();
+        setTimeout(_ => {
+          expect(vm.value).to.be.exist;
+          done();
+        }, DELAY);
+      });
+    }, DELAY);
+  });
+
   it('reset', done => {
     vm = createVue({
       template: `
@@ -120,6 +145,46 @@ describe('DatePicker', () => {
     }, DELAY);
   });
 
+  it('change event', done => {
+    let inputValue;
+
+    vm = createVue({
+      template: `
+        <el-date-picker
+          ref="compo"
+          v-model="value"
+          format="yyyy-MM"
+          @change="handleChange" />`,
+
+      methods: {
+        handleChange(val) {
+          inputValue = val;
+        }
+      },
+
+      data() {
+        return { value: '' };
+      }
+    }, true);
+
+    const input = vm.$el.querySelector('input');
+
+    input.blur();
+    input.focus();
+
+    setTimeout(_ => {
+      const picker = vm.$refs.compo.picker;
+
+      picker.$el.querySelector('td.available').click();
+      vm.$nextTick(_ => {
+        const date = picker.date;
+
+        expect(inputValue).to.equal(`${date.getFullYear()}-${date.getMonth() + 1 }`);
+        done();
+      });
+    }, DELAY);
+  });
+
   describe('keydown', () => {
     let input;
     let keyDown = function(el, keyCode) {

+ 46 - 28
test/unit/specs/input-number.spec.js

@@ -38,22 +38,14 @@ describe('InputNumber', () => {
     let input = vm.$el.querySelector('input');
     let btnDecrease = vm.$el.querySelector('.el-input-number__decrease');
 
-    triggerEvent(btnDecrease, 'mouseenter');
     triggerEvent(btnDecrease, 'mousedown');
     triggerEvent(document, 'mouseup');
 
-    setTimeout(_ => {
-      expect(vm.$el.querySelector('.el-input.is-active')).to.exist;
+    vm.$nextTick(_ => {
       expect(vm.value).to.be.equal(4);
       expect(input.value).to.be.equal('4');
-
-      triggerEvent(btnDecrease, 'mouseleave');
-
-      vm.$nextTick(_ => {
-        expect(vm.$el.querySelector('.el-input.is-active')).to.not.exist;
-        done();
-      });
-    }, 300);
+      done();
+    });
   });
   it('increase', done => {
     vm = createVue({
@@ -74,11 +66,11 @@ describe('InputNumber', () => {
     triggerEvent(btnIncrease, 'mousedown');
     triggerEvent(document, 'mouseup');
 
-    setTimeout(_ => {
+    vm.$nextTick(_ => {
       expect(vm.value).to.be.equal(2.5);
       expect(input.value).to.be.equal('2.5');
       done();
-    }, 100);
+    });
   });
   it('disabled', done => {
     vm = createVue({
@@ -100,19 +92,19 @@ describe('InputNumber', () => {
     triggerEvent(btnDecrease, 'mousedown');
     triggerEvent(document, 'mouseup');
 
-    triggerEvent(btnIncrease, 'mousedown');
-    triggerEvent(document, 'mouseup');
-
-    setTimeout(_ => {
+    vm.$nextTick(_ => {
       expect(vm.value).to.be.equal(2);
       expect(input.value).to.be.equal('2');
 
-      setTimeout(_ => {
+      triggerEvent(btnIncrease, 'mousedown');
+      triggerEvent(document, 'mouseup');
+
+      vm.$nextTick(_ => {
         expect(vm.value).to.be.equal(2);
         expect(input.value).to.be.equal('2');
         done();
-      }, 100);
-    }, 100);
+      });
+    });
   });
   it('step', done => {
     vm = createVue({
@@ -134,19 +126,19 @@ describe('InputNumber', () => {
     triggerEvent(btnIncrease, 'mousedown');
     triggerEvent(document, 'mouseup');
 
-    setTimeout(_ => {
+    vm.$nextTick(_ => {
       expect(vm.value).to.be.equal(8.2);
       expect(input.value).to.be.equal('8.2');
 
       triggerEvent(btnDecrease, 'mousedown');
       triggerEvent(document, 'mouseup');
 
-      setTimeout(_ => {
+      vm.$nextTick(_ => {
         expect(vm.value).to.be.equal(5);
         expect(input.value).to.be.equal('5');
         done();
-      }, 100);
-    }, 100);
+      });
+    });
   });
   it('min', done => {
     vm = createVue({
@@ -181,11 +173,11 @@ describe('InputNumber', () => {
     triggerEvent(btnDecrease, 'mousedown');
     triggerEvent(document, 'mouseup');
 
-    setTimeout(_ => {
+    vm.$nextTick(_ => {
       expect(vm.value).to.be.equal(6);
       expect(input.value).to.be.equal('6');
       done();
-    }, 100);
+    });
   });
   it('max', done => {
     vm = createVue({
@@ -220,11 +212,11 @@ describe('InputNumber', () => {
     triggerEvent(btnIncrease, 'mousedown');
     triggerEvent(document, 'mouseup');
 
-    setTimeout(_ => {
+    vm.$nextTick(_ => {
       expect(vm.value).to.be.equal(8);
       expect(input.value).to.be.equal('8');
       done();
-    }, 100);
+    });
   });
   it('controls', () => {
     vm = createVue({
@@ -242,4 +234,30 @@ describe('InputNumber', () => {
     expect(vm.$el.querySelector('.el-input-number__decrease')).to.not.exist;
     expect(vm.$el.querySelector('.el-input-number__increase')).to.not.exist;
   });
+  it('event:change', done => {
+    vm = createVue({
+      template: `
+        <el-input-number v-model="value" ref="input">
+        </el-input-number>
+      `,
+      data() {
+        return {
+          value: 1.5
+        };
+      }
+    }, true);
+
+    let btnIncrease = vm.$el.querySelector('.el-input-number__increase');
+    const spy = sinon.spy();
+
+    vm.$refs.input.$on('change', spy);
+
+    triggerEvent(btnIncrease, 'mousedown');
+    triggerEvent(document, 'mouseup');
+
+    vm.$nextTick(_ => {
+      expect(spy.withArgs(2.5, 1.5).calledOnce).to.be.true;
+      done();
+    });
+  });
 });

+ 54 - 0
test/unit/specs/input.spec.js

@@ -104,6 +104,7 @@ describe('Input', () => {
     }, true);
     expect(vm.$el.querySelector('.el-textarea__inner').getAttribute('rows')).to.be.equal('3');
   });
+
   it('autosize', done => {
     vm = createVue({
       template: `
@@ -143,4 +144,57 @@ describe('Input', () => {
       done();
     }, 200);
   });
+
+  describe('Input Events', () => {
+    it('event:focus & blur', done => {
+      vm = createVue({
+        template: `
+          <el-input
+            ref="input"
+            placeholder="请输入内容"
+            value="input">
+          </el-input>
+        `
+      }, true);
+
+      const spyFocus = sinon.spy();
+      const spyBlur = sinon.spy();
+
+      vm.$refs.input.$on('focus', spyFocus);
+      vm.$refs.input.$on('blur', spyBlur);
+      vm.$el.querySelector('input').focus();
+      vm.$el.querySelector('input').blur();
+
+      vm.$nextTick(_ => {
+        expect(spyFocus.calledOnce).to.be.true;
+        expect(spyBlur.calledOnce).to.be.true;
+        done();
+      });
+    });
+    it('event:change', done => {
+      vm = createVue({
+        template: `
+          <el-input
+            ref="input"
+            placeholder="请输入内容"
+            :value="input">
+          </el-input>
+        `,
+        data() {
+          return {
+            input: 'a'
+          };
+        }
+      }, true);
+
+      const spy = sinon.spy();
+      vm.$refs.input.$on('change', spy);
+      vm.input = 'b';
+
+      vm.$nextTick(_ => {
+        expect(spy.withArgs('b').calledOnce).to.be.true;
+        done();
+      });
+    });
+  });
 });

+ 12 - 0
test/unit/specs/pagination.spec.js

@@ -40,6 +40,18 @@ describe('Pagination', () => {
     expect(elm.querySelector('.el-pagination__total')).to.not.exist;
   });
 
+  it('layout: all in right, need clear float', () => {
+    vm = createTest(Pagination, {
+      layout: '->, prev, pager, next',
+      total: 100
+    }, true);
+    const elm = vm.$el;
+    let right_div = elm.querySelector('.el-pagination__rightwrapper');
+    expect(elm.clientHeight > 0 && right_div.clientHeight > 0).to.equal(true);
+    // elm 将来 padding 可能会变化, 所以使用 >= 来判定
+    expect(elm.clientHeight >= right_div.clientHeight).to.equal(true);
+  });
+
   it('custom slot', () => {
     vm = createVue({
       template: `

+ 135 - 13
test/unit/specs/table.spec.js

@@ -8,11 +8,11 @@ const toArray = function(obj) {
 
 const getTestData = function() {
   return [
-    { name: 'Toy Story', release: '1995-11-22', director: 'John Lasseter', runtime: 80 },
-    { name: 'A Bug\'s Life', release: '1998-11-25', director: 'John Lasseter', runtime: 95 },
-    { name: 'Toy Story 2', release: '1999-11-24', director: 'John Lasseter', runtime: 92 },
-    { name: 'Monsters, Inc.', release: '2001-11-2', director: 'Peter Docter', runtime: 92 },
-    { name: 'Finding Nemo', release: '2003-5-30', director: 'Andrew Stanton', runtime: 100 }
+    { id: 1, name: 'Toy Story', release: '1995-11-22', director: 'John Lasseter', runtime: 80 },
+    { id: 2, name: 'A Bug\'s Life', release: '1998-11-25', director: 'John Lasseter', runtime: 95 },
+    { id: 3, name: 'Toy Story 2', release: '1999-11-24', director: 'John Lasseter', runtime: 92 },
+    { id: 4, name: 'Monsters, Inc.', release: '2001-11-2', director: 'Peter Docter', runtime: 92 },
+    { id: 5, name: 'Finding Nemo', release: '2003-5-30', director: 'Andrew Stanton', runtime: 100 }
   ];
 };
 
@@ -27,6 +27,7 @@ describe('Table', () => {
     const vm = createVue({
       template: `
         <el-table :data="testData">
+          <el-table-column prop="id" />
           <el-table-column prop="name" label="片名" />
           <el-table-column prop="release" label="发行日期" />
           <el-table-column prop="director" label="导演" />
@@ -843,12 +844,12 @@ describe('Table', () => {
         vm.$el.querySelectorAll('.el-checkbox')[1].click();
 
         setTimeout(_ => {
-          expect(vm.$el.querySelectorAll('.el-checkbox__inner.is-checked')).to.length(1);
+          expect(vm.$el.querySelectorAll('.el-checkbox__input.is-checked')).to.length(1);
           // go to second page
           vm.testData = getData(1);
           setTimeout(_ => {
              // expect no checked
-            expect(vm.$el.querySelectorAll('.el-checkbox__inner.is-checked')).to.length(0);
+            expect(vm.$el.querySelectorAll('.el-checkbox__input.is-checked')).to.length(0);
             // click first checkbox
             vm.$el.querySelectorAll('.el-checkbox')[1].click();
             vm.$el.querySelectorAll('.el-checkbox')[2].click();
@@ -856,11 +857,11 @@ describe('Table', () => {
               // back first page
               vm.testData = getData();
               setTimeout(_ => {
-                expect(vm.$el.querySelectorAll('.el-checkbox__inner.is-checked')).to.length(1);
+                expect(vm.$el.querySelectorAll('.el-checkbox__input.is-checked')).to.length(1);
                 // clear
                 vm.$refs.table.clearSelection();
                 setTimeout(_ => {
-                  expect(vm.$el.querySelectorAll('.el-checkbox__inner.is-checked')).to.length(0);
+                  expect(vm.$el.querySelectorAll('.el-checkbox__input.is-checked')).to.length(0);
                   destroyVM(vm);
                   done();
                 }, DELAY);
@@ -957,6 +958,90 @@ describe('Table', () => {
           }, DELAY);
         });
       });
+
+      describe('= expand', () => {
+        const createInstance = function(extra) {
+          extra = extra || '';
+          return createVue({
+            template: `
+            <el-table row-key="id" :data="testData" @expand="handleExpand" ${extra}>
+              <el-table-column type="expand" inline-template>
+                <div>{{row.name}}</div>
+              </el-table-column>
+              <el-table-column prop="release" label="release" />
+              <el-table-column prop="director" label="director" />
+              <el-table-column prop="runtime" label="runtime" />
+            </el-table>
+          `,
+
+            created() {
+              this.testData = getTestData();
+            },
+
+            data() {
+              return { expandCount: 0, expandRowKeys: [] };
+            },
+
+            methods: {
+              handleExpand() {
+                this.expandCount++;
+              }
+            }
+          }, true);
+        };
+
+        it('works', done => {
+          const vm = createInstance();
+          setTimeout(_ => {
+            expect(vm.$el.querySelectorAll('td.el-table__expand-column').length).to.equal(5);
+            destroyVM(vm);
+            done();
+          }, DELAY);
+        });
+
+        it('should expand when click icon', done => {
+          const vm = createInstance();
+          setTimeout(_ => {
+            vm.$el.querySelector('td.el-table__expand-column .el-table__expand-icon').click();
+            setTimeout(_ => {
+              expect(vm.$el.querySelectorAll('.el-table__expanded-cell').length).to.equal(1);
+              expect(vm.expandCount).to.equal(1);
+              vm.$el.querySelector('td.el-table__expand-column .el-table__expand-icon').click();
+              setTimeout(_ => {
+                expect(vm.$el.querySelectorAll('.el-table__expanded-cell').length).to.equal(0);
+                expect(vm.expandCount).to.equal(2);
+                destroyVM(vm);
+                done();
+              }, DELAY);
+            }, DELAY);
+          }, DELAY);
+        });
+
+        it('should set expanded rows using expandRowKeys', done => {
+          const vm = createInstance(':expand-row-keys="expandRowKeys"');
+          setTimeout(_ => {
+            vm.expandRowKeys = [1, 3];
+            setTimeout(_ => {
+              expect(vm.$el.querySelectorAll('.el-table__expanded-cell').length).to.equal(2);
+              vm.expandRowKeys = [2];
+              setTimeout(_ => {
+                expect(vm.$el.querySelectorAll('.el-table__expanded-cell').length).to.equal(1);
+                destroyVM(vm);
+                done();
+              }, DELAY);
+            }, DELAY);
+          }, DELAY);
+        });
+
+        it('should default-expand-all when default-expand-all is true', done => {
+          const vm = createInstance('default-expand-all');
+          setTimeout(_ => {
+            expect(vm.$el.querySelectorAll('.el-table__expanded-cell').length).to.equal(5);
+            destroyVM(vm);
+            done();
+          }, DELAY);
+        });
+      });
     });
 
     describe('sortable', () => {
@@ -981,7 +1066,7 @@ describe('Table', () => {
           });
 
         setTimeout(_ => {
-          const elm = vm.$el.querySelector('.caret-wrapper');
+          const elm = vm.$el.querySelector('.caret-wrapper > .ascending');
 
           elm.click();
           setTimeout(_ => {
@@ -1003,7 +1088,7 @@ describe('Table', () => {
           }
         }, '@sort-change="sortChange"');
         setTimeout(_ => {
-          const elm = vm.$el.querySelector('.caret-wrapper');
+          const elm = vm.$el.querySelector('.caret-wrapper > .ascending');
 
           elm.click();
           setTimeout(_ => {
@@ -1019,7 +1104,7 @@ describe('Table', () => {
       const vm = createTable('', '', '', 'sortable');
 
       it('ascending', done => {
-        const elm = vm.$el.querySelector('.caret-wrapper');
+        const elm = vm.$el.querySelector('.caret-wrapper > .ascending');
 
         elm.click();
         setTimeout(_ => {
@@ -1031,7 +1116,7 @@ describe('Table', () => {
       });
 
       it('descending', done => {
-        const elm = vm.$el.querySelector('.caret-wrapper');
+        const elm = vm.$el.querySelector('.caret-wrapper > .descending');
 
         elm.click();
         setTimeout(_ => {
@@ -1215,6 +1300,43 @@ describe('Table', () => {
       }, DELAY);
     });
 
+    it('header-align', (done) => {
+      const vm = createVue({
+        template: `
+           <el-table :data="testData">
+            <el-table-column prop="name" :align="align" :header-align="headerAlign"/>
+          </el-table>
+        `,
+
+        data() {
+          return {
+            align: 'left',
+            headerAlign: null
+          };
+        },
+
+        created() {
+          this.testData = getTestData();
+        }
+      }, true);
+
+      setTimeout(() => {
+        expect(vm.$el.querySelectorAll('.el-table__header th.is-left').length > 0).to.be.true;
+        expect(vm.$el.querySelectorAll('.el-table__header td.is-center').length === 0).to.be.true;
+        vm.align = 'right';
+        vm.$nextTick(() => {
+          expect(vm.$el.querySelectorAll('.el-table__header th.is-right').length > 0).to.be.true;
+          expect(vm.$el.querySelectorAll('.el-table__header td.is-center').length === 0).to.be.true;
+          vm.headerAlign = 'center';
+          vm.$nextTick(() => {
+            expect(vm.$el.querySelectorAll('.el-table__header th.is-right').length === 0).to.be.true;
+            expect(vm.$el.querySelectorAll('.el-table__header td.is-center').length > 0).to.be.true;
+          });
+        });
+        done();
+      }, DELAY);
+    });
+
     it('width', (done) => {
       const vm = createVue({
         template: `

+ 5 - 5
test/unit/specs/tabs.spec.js

@@ -188,7 +188,7 @@ describe('Tabs', () => {
   it('closable edge', done => {
     vm = createVue({
       template: `
-        <el-tabs type="card" :closable="true">
+        <el-tabs type="card" :closable="true" ref="tabs">
           <el-tab-pane label="用户管理">A</el-tab-pane>
           <el-tab-pane label="配置管理">B</el-tab-pane>
           <el-tab-pane label="角色管理">C</el-tab-pane>
@@ -199,7 +199,7 @@ describe('Tabs', () => {
 
     let tabList = vm.$el.querySelector('.el-tabs__header').children;
     let paneList = vm.$el.querySelector('.el-tabs__content').children;
-    setTimeout(_ => {
+    vm.$nextTick(_ => {
       tabList[0].querySelector('.el-icon-close').click();
       vm.$nextTick(_ => {
         expect(tabList.length).to.be.equal(3);
@@ -209,16 +209,16 @@ describe('Tabs', () => {
 
         tabList[2].click();
         tabList[2].querySelector('.el-icon-close').click();
-        vm.$nextTick(_ => {
+        setTimeout(_ => {
           expect(tabList.length).to.be.equal(2);
           expect(paneList.length).to.be.equal(2);
           expect(tabList[1].classList.contains('is-active')).to.be.true;
           expect(tabList[1].innerText.trim()).to.be.equal('角色管理');
           expect(paneList[1].innerText.trim()).to.be.equal('C');
           done();
-        });
+        }, 100);
       });
-    }, 100);
+    });
   });
   it('tab title render function', done => {
     vm = createVue({

File diff suppressed because it is too large
+ 234 - 213
yarn.lock


Some files were not shown because too many files changed in this diff