Browse Source

Merge pull request #335 from QingWei-Li/unittest

Unittest
杨奕 8 years ago
parent
commit
22fd5f1f02
77 changed files with 909 additions and 583 deletions
  1. 1 0
      .gitignore
  2. 17 2
      .travis.yml
  3. 15 1
      CHANGELOG.md
  4. 24 0
      build/config.js
  5. 1 23
      build/cooking.demo.js
  6. 25 0
      build/cooking.test.js
  7. 14 9
      examples/docs/zh-cn/badge.md
  8. 11 5
      examples/docs/zh-cn/checkbox.md
  9. 2 0
      examples/docs/zh-cn/date-picker.md
  10. 5 1
      examples/docs/zh-cn/development.md
  11. 2 1
      examples/docs/zh-cn/dropdown.md
  12. 1 1
      examples/docs/zh-cn/form.md
  13. 1 1
      examples/docs/zh-cn/input.md
  14. 7 7
      examples/docs/zh-cn/slider.md
  15. 22 4
      package.json
  16. 2 2
      packages/autocomplete/src/autocomplete.vue
  17. 0 22
      packages/button/src/button-group.js
  18. 16 0
      packages/button/src/button-group.vue
  19. 0 65
      packages/button/src/button.js
  20. 54 0
      packages/button/src/button.vue
  21. 32 14
      packages/checkbox/src/checkbox.vue
  22. 4 2
      packages/date-picker/src/basic/date-table.vue
  23. 3 1
      packages/date-picker/src/basic/month-table.vue
  24. 13 1
      packages/date-picker/src/css/date-picker.css
  25. 14 0
      packages/date-picker/src/css/date-range-picker.css
  26. 1 0
      packages/date-picker/src/css/picker-panel.css
  27. 1 1
      packages/date-picker/src/css/time-range-picker.css
  28. 36 8
      packages/date-picker/src/panel/date-range.vue
  29. 34 10
      packages/date-picker/src/panel/date.vue
  30. 14 7
      packages/date-picker/src/panel/time-range.vue
  31. 5 3
      packages/date-picker/src/panel/time-select.vue
  32. 16 5
      packages/date-picker/src/panel/time.vue
  33. 27 42
      packages/date-picker/src/picker.vue
  34. 9 1
      packages/date-picker/src/util/i18n.js
  35. 5 1
      packages/date-picker/src/util/index.js
  36. 19 23
      packages/dropdown/src/dropdown-menu.vue
  37. 16 11
      packages/dropdown/src/dropdown.vue
  38. 19 15
      packages/form/src/form-item.vue
  39. 1 1
      packages/icon/index.js
  40. 0 19
      packages/icon/src/icon.js
  41. 13 0
      packages/icon/src/icon.vue
  42. 4 3
      packages/input/src/input.vue
  43. 3 1
      packages/loading/package.json
  44. 4 2
      packages/loading/src/spinner.js
  45. 16 21
      packages/menu/src/menu.vue
  46. 1 0
      packages/message-box/package.json
  47. 4 3
      packages/message-box/src/main.vue
  48. 4 10
      packages/pagination/src/pagination.js
  49. 43 49
      packages/popover/src/main.vue
  50. 3 1
      packages/rate/package.json
  51. 4 2
      packages/rate/src/main.vue
  52. 34 47
      packages/select-dropdown/src/select-dropdown.vue
  53. 2 1
      packages/select/package.json
  54. 19 14
      packages/select/src/select.vue
  55. 4 3
      packages/slider/src/main.vue
  56. 14 13
      packages/steps/src/step.vue
  57. 0 5
      packages/table/src/table-body.js
  58. 0 1
      packages/table/src/table-column.js
  59. 0 0
      packages/theme-default/src/dropdown-menu.css
  60. 1 1
      packages/theme-default/src/icon.css
  61. 1 0
      packages/theme-default/src/index.css
  62. 39 1
      packages/theme-default/src/select-dropdown.css
  63. 0 42
      packages/theme-default/src/select.css
  64. 5 5
      packages/theme-default/src/step.css
  65. 1 2
      packages/theme-default/src/time-select.css
  66. 4 4
      packages/tree/src/tree-node.vue
  67. 4 8
      packages/upload/src/index.vue
  68. 2 0
      packages/upload/src/upload.vue
  69. 1 1
      src/index.js
  70. 1 1
      src/mixins/emitter.js
  71. 8 3
      src/utils/clickoutside.js
  72. 27 30
      src/utils/vue-popper.js
  73. 9 0
      test/unit/.eslintrc
  74. 14 0
      test/unit/index.js
  75. 31 0
      test/unit/karma.conf.js
  76. 60 0
      test/unit/specs/time-select.spec.js
  77. 40 0
      test/unit/util.js

+ 1 - 0
.gitignore

@@ -9,3 +9,4 @@ lib
 examples/element-ui
 fe.element/element-ui
 .npmrc
+coverage

+ 17 - 2
.travis.yml

@@ -1,5 +1,20 @@
 sudo: false
 language: node_js
 node_js:
-  - "5"
-script: make dist
+  - 5
+cache:
+  bundler: true
+  directories:
+    - node_modules # NPM packages
+    - travis_phantomjs
+before_install:
+  # Upgrade PhantomJS to v2.1.1.
+  - "export PHANTOMJS_VERSION=2.1.1"
+  - "export PATH=$PWD/travis_phantomjs/phantomjs-$PHANTOMJS_VERSION-linux-x86_64/bin:$PATH"
+  - "if [ $(phantomjs --version) != $PHANTOMJS_VERSION ]; then rm -rf $PWD/travis_phantomjs; mkdir -p $PWD/travis_phantomjs; fi"
+  - "if [ $(phantomjs --version) != $PHANTOMJS_VERSION ]; then wget https://github.com/Medium/phantomjs/releases/download/v$PHANTOMJS_VERSION/phantomjs-$PHANTOMJS_VERSION-linux-x86_64.tar.bz2 -O $PWD/travis_phantomjs/phantomjs-$PHANTOMJS_VERSION-linux-x86_64.tar.bz2; fi"
+  - "if [ $(phantomjs --version) != $PHANTOMJS_VERSION ]; then tar -xvf $PWD/travis_phantomjs/phantomjs-$PHANTOMJS_VERSION-linux-x86_64.tar.bz2 -C $PWD/travis_phantomjs; fi"
+  - "phantomjs --version"
+script:
+  - "make dist"
+  - "npm t"

+ 15 - 1
CHANGELOG.md

@@ -1,9 +1,20 @@
 ## 更新日志
 
-### 1.0.0(待发布)
+### 1.0.0-rc.7
 
 *2016-XX-XX*
 
+- Upload 新增 Data 属性支持额外数据的传输
+- DatePicker 修复 `$t` 报错
+- Popper 重构 vue-popper
+- Pagination 修复输入后再点击切换,输入框的值不更新
+- Step: 修复自定义 icon 的样式
+- 修复 Tree 组件 checkbox 点击失效的问题
+
+### 1.0.0-rc.6
+
+*2016-10-11*
+
 - 修复 Tabs 切换后 Tab-panel 被销毁的问题
 - 修复 TimePicker 错误的隐藏面板
 - 修复 Table Cell 的样式, #204
@@ -17,6 +28,9 @@
 - 修复 多选可搜索的 Select 下拉选项自动展开的问题
 - 为 Dialog 添加 top 属性
 - 修复 Menu 组件垂直模式下开启 router 属性会立刻跳转的问题 #295
+- Checkbox change 事件现在只能被人为的交互操作所触发
+- 新增 Checkbox checked 属性
+- 修复 Select 远程搜索时使用键盘选择选项无法更新 v-model 的问题
 
 #### 非兼容性更新
 

+ 24 - 0
build/config.js

@@ -28,3 +28,27 @@ exports.alias = {
 };
 
 exports.jsexclude = /node_modules|utils\/popper\.js|utils\/date.\js/;
+
+exports.postcss = function(webapck) {
+  return [
+    require('postcss-salad')({
+      browser: ['ie > 8', 'last 2 version'],
+      features: {
+        'partialImport': {
+          addDependencyTo: webapck
+        },
+        'bem': {
+          'shortcuts': {
+            'component': 'b',
+            'modifier': 'm',
+            'descendent': 'e'
+          },
+          'separators': {
+            'descendent': '__',
+            'modifier': '--'
+          }
+        }
+      }
+    })
+  ];
+};

+ 1 - 23
build/cooking.demo.js

@@ -28,29 +28,7 @@ cooking.set({
   sourceMap: true,
   alias: config.alias,
   extends: ['vue2', 'lint'],
-  postcss: function(webapck) {
-    return [
-      require('postcss-salad')({
-        browser: ['ie > 8', 'last 2 version'],
-        features: {
-          'partialImport': {
-            addDependencyTo: webapck
-          },
-          'bem': {
-            'shortcuts': {
-              'component': 'b',
-              'modifier': 'm',
-              'descendent': 'e'
-            },
-            'separators': {
-              'descendent': '__',
-              'modifier': '--'
-            }
-          }
-        }
-      })
-    ];
-  }
+  postcss: config.postcss
 });
 
 cooking.add('loader.md', {

+ 25 - 0
build/cooking.test.js

@@ -0,0 +1,25 @@
+var path = require('path');
+var cooking = require('cooking');
+var config = require('./config');
+var projectRoot = path.resolve(__dirname, '../');
+var ProgressBarPlugin = require('progress-bar-webpack-plugin');
+
+cooking.set({
+  entry: './src/index.js',
+  extends: ['vue2'],
+  minimize: false,
+  alias: config.alias,
+  postcss: config.postcss,
+  sourceMap: '#inline-source-map'
+});
+
+cooking.add('vue.loaders.js', 'isparta');
+cooking.add('loader.js.exclude', config.jsexclude);
+cooking.add('preLoader.js', {
+  test: /\.js$/,
+  loader: 'isparta-loader',
+  include: path.resolve(projectRoot, 'src')
+});
+
+cooking.add('plugins.process', new ProgressBarPlugin());
+module.exports = cooking.resolve();

+ 14 - 9
examples/docs/zh-cn/badge.md

@@ -21,15 +21,20 @@
   <el-button size="small">回复</el-button>
 </el-badge>
 
-<el-dropdown text="点我查看" type="text" :icon-separate="false" trigger="click">
-  <el-dropdown-item class="clearfix">
-    评论
-    <el-badge class="mark" :value="12" />
-  </el-dropdown-item>
-  <el-dropdown-item class="clearfix">
-    回复
-    <el-badge class="mark" :value="3" />
-  </el-dropdown-item>
+<el-dropdown trigger="click">
+  <span class="el-dropdown-link">
+    点我查看<i class="el-icon-caret-bottom el-icon-right"></i>
+  </span>
+  <el-dropdown-menu slot="dropdown">
+    <el-dropdown-item class="clearfix">
+      评论
+      <el-badge class="mark" :value="12" />
+    </el-dropdown-item>
+    <el-dropdown-item class="clearfix">
+      回复
+      <el-badge class="mark" :value="3" />
+    </el-dropdown-item>
+  </el-dropdown-menu>
 </el-dropdown>
 ```
 :::

+ 11 - 5
examples/docs/zh-cn/checkbox.md

@@ -4,16 +4,20 @@
       return {
         checkList: ['选中且禁用','复选框 A'],
         // checkList2: ['复选框 A'],
-        checked: true,
+        checked: false,
         checked1: false,
         checked2: true,
         isValid: '可用'
       };
+    },
+    methods: {
+      handleChange(ev) {
+        console.log(ev);
+      }
     }
   };
 </script>
 
-
 <style>
   .demo-box.demo-checkbox {
     .checkbox {
@@ -37,7 +41,7 @@
 ```html
 <template>
   <!-- `checked` 为 true 或 false -->
-  <el-checkbox class="checkbox" v-model="checked">备选项</el-checkbox>
+  <el-checkbox class="checkbox" v-model="checked" checked>备选项</el-checkbox>
 </template>
 <script>
   export default {
@@ -59,7 +63,7 @@
 
 ```html
 <template>
-  <el-checkbox class="checkbox" v-model="checked1" disabled>备选项</el-checkbox>
+  <el-checkbox class="checkbox" v-model="checked1" disabled>备选项1</el-checkbox>
   <el-checkbox class="checkbox" v-model="checked2" disabled>备选项</el-checkbox>
 </template>
 <script>
@@ -139,8 +143,10 @@
 | true-label | 选中时的值   | string, number    |       —        |     —    |
 | false-label | 没有选中时的值   | string, number    |      —         |     —    |
 | disabled  | 按钮禁用    | boolean   |  — | false   |
+| checked  | 当前是否勾选    | boolean   |  — | false   |
+| indeterminate  | 设置 indeterminate 状态,只负责样式控制    | boolean   |  — | false   |
 
 ### Checkbox-group Events
 | 事件名称      | 说明    | 回调参数      |
 |---------- |-------- |---------- |
-| change  | 当绑定值变化时触发的事件 | 选中的 Checkbox Label 值 |
+| change  | 当绑定值变化时触发的事件 | event 事件对象 |

+ 2 - 0
examples/docs/zh-cn/date-picker.md

@@ -119,6 +119,7 @@
     <span class="demonstration">带快捷选项</span>
     <el-date-picker
       v-model="value2"
+      align="right"
       type="date"
       placeholder="选择日期"
       :picker-options="pickerOptions1">
@@ -193,6 +194,7 @@
   <span class="demonstration">年</span>
   <el-date-picker
     v-model="value5"
+    align="right"
     type="year"
     placeholder="选择年">
   </el-date-picker>

+ 5 - 1
examples/docs/zh-cn/development.md

@@ -25,7 +25,11 @@ npm run bootstrap
 registry=https://registry.npm.taobao.org
 ```
 
-然后再运行 `npm run bootstrap` 安装依赖。
+然后再运行
+
+```shell
+PHANTOMJS_CDNURL=http://npm.taobao.org/mirrors/phantomjs npm run bootstrap
+```
 
 ### 启动开发环境
 

+ 2 - 1
examples/docs/zh-cn/dropdown.md

@@ -152,7 +152,8 @@
 ### Attributes
 | 参数          | 说明            | 类型            | 可选值                 | 默认值   |
 |-------------  |---------------- |---------------- |---------------------- |-------- |
-| type          | 菜单按钮类型,同 Button 组件   | string  |          —             |    —     |
+| type          | 菜单按钮类型,同 Button 组件(只在`split-button`为 true 的情况下有效)   | string  |          —             |    —     |
+| size          | 菜单按钮尺寸,同 Button 组件(只在`split-button`为 true 的情况下有效)     | string          | hover, click  | hover |
 | split-button  | 下拉触发元素呈现为按钮组    | boolean  |    —  |  false |
 | menu-align    | 菜单水平对齐方向     | string          | start, end  | end |
 | trigger       | 触发下拉的行为     | string          | hover, click  | hover |

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

@@ -514,7 +514,7 @@
       <el-option label="区域二" value="beijing"></el-option>
     </el-select>
   </el-form-item>
-  <el-form-item label="活动时间">
+  <el-form-item label="活动时间" required>
     <el-col :span="11">
       <el-form-item prop="date1">
         <el-date-picker type="date" placeholder="选择日期" v-model="ruleForm.date1" style="width: 100%;"></el-date-picker>

+ 1 - 1
examples/docs/zh-cn/input.md

@@ -627,7 +627,7 @@
 
 | 参数          | 说明            | 类型            | 可选值                 | 默认值   |
 |-------------  |---------------- |---------------- |---------------------- |-------- |
-| type         | 同原生的 input 的 type 属性,另外提供 type="textarea"   | string  | — | — |
+| type         | 类型   | string  | text/textarea | text |
 | value         | 绑定值           | string, number  | — | — |
 | maxlength     | 最大输入长度      | number          |  —  | — |
 | minlength     | 最小输入长度      | number          | — | — |

+ 7 - 7
examples/docs/zh-cn/slider.md

@@ -16,22 +16,22 @@
   .demo-box.demo-slider .source {
     padding: 0;
   }
-  
+
   .demo-box.demo-slider .block {
     padding: 30px 24px;
     overflow: hidden;
     border-bottom: solid 1px #EFF2F6;
     &:last-child {
-      border-bottom: none;      
+      border-bottom: none;
     }
   }
-  
+
   .demo-box.demo-slider .demonstration {
     font-size: 14px;
     color: #8492a6;
     line-height: 44px;
   }
-  
+
   .demo-box.demo-slider .demonstration + .el-slider {
     float: right;
     width: 70%;
@@ -52,7 +52,7 @@
 <template>
   <div class="block">
     <span class="demonstration">默认</span>
-    <el-slider v-model="value1"></el-slider>  
+    <el-slider v-model="value1"></el-slider>
   </div>
   <div class="block">
     <span class="demonstration">自定义初始值</span>
@@ -85,7 +85,7 @@
     <el-slider
       v-model="value3"
       :step="10">
-    </el-slider>  
+    </el-slider>
   </div>
   <div class="block">
     <span class="demonstration">显示间断点</span>
@@ -121,7 +121,7 @@
     <el-slider
       v-model="value5"
       show-input>
-    </el-slider>  
+    </el-slider>
   </div>
 </template>
 

+ 22 - 4
package.json

@@ -1,6 +1,6 @@
 {
   "name": "element-ui",
-  "version": "1.0.0-rc.5",
+  "version": "1.0.0-rc.6",
   "description": "A Component Library for Vue.js.",
   "main": "lib/element-ui.common.js",
   "files": [
@@ -20,7 +20,9 @@
     "pub:all": "npm run dist:all && lerna publish",
     "build:utils": "babel src/utils --out-dir lib/utils",
     "clean": "rimraf lib && rimraf packages/*/lib",
-    "lint": "eslint src/**/*.js packages/**/*.{js,vue} build/**/*.js --quiet"
+    "lint": "eslint src/**/*.js test/**/*.js packages/**/*.{js,vue} build/**/*.js --quiet",
+    "test:watch": "karma start test/unit/karma.conf.js",
+    "test": "karma start test/unit/karma.conf.js --single-run"
   },
   "repository": {
     "type": "git",
@@ -43,6 +45,7 @@
     "babel-plugin-syntax-jsx": "^6.8.0",
     "babel-plugin-transform-vue-jsx": "^3.1.0",
     "babel-preset-es2015": "^6.14.0",
+    "chai": "^3.5.0",
     "cheerio": "^0.18.0",
     "cooking": "^1.1.0",
     "cooking-lint": "^0.1.3",
@@ -57,24 +60,39 @@
     "highlight.js": "^9.3.0",
     "html-loader": "^0.4.3",
     "html-webpack-plugin": "^2.22.0",
+    "inject-loader": "^3.0.0-beta2",
+    "isparta-loader": "^2.0.0",
     "json-loader": "^0.5.4",
     "json-templater": "^1.0.4",
+    "karma": "^1.3.0",
+    "karma-coverage": "^1.1.1",
+    "karma-mocha": "^1.2.0",
+    "karma-phantomjs-launcher": "^1.0.2",
+    "karma-sinon-chai": "^1.2.4",
+    "karma-sourcemap-loader": "^0.3.7",
+    "karma-spec-reporter": "0.0.26",
+    "karma-webpack": "^1.8.0",
     "lerna": "2.0.0-beta.18",
+    "lolex": "^1.5.1",
     "markdown-it": "^6.1.1",
     "markdown-it-container": "^2.0.0",
+    "mocha": "^3.1.1",
     "object-assign": "^4.1.0",
+    "phantomjs-prebuilt": "^2.1.13",
     "postcss": "^5.1.2",
     "postcss-loader": "^0.11.1",
     "postcss-salad": "^1.0.5",
     "rimraf": "^2.5.4",
+    "sinon": "^1.17.6",
+    "sinon-chai": "^2.8.0",
     "style-loader": "^0.13.1",
     "theaterjs": "^3.0.0",
     "uppercamelcase": "^1.1.0",
     "url-loader": "^0.5.7",
-    "vue": "^2.0.1",
+    "vue": "^2.0.2",
     "vue-loader": "^9.5.1",
     "vue-markdown-loader": "^0.5.1",
-    "vue-popup": "^0.2.6",
+    "vue-popup": "^0.2.8",
     "vue-router": "^2.0.0",
     "webpack": "^1.13.2",
     "webpack-dev-server": "^1.15.1",

+ 2 - 2
packages/autocomplete/src/autocomplete.vue

@@ -5,8 +5,8 @@
       :disabled="disabled"
       :placeholder="placeholder"
       :name='name'
-      @onchange="handleChange"
-      @onfocus="handleFocus"
+      @change="handleChange"
+      @focus="handleFocus"
       @keydown.up.native="highlight(highlightedIndex - 1)"
       @keydown.down.native="highlight(highlightedIndex + 1)"
       @keydown.enter.native="select(highlightedIndex)"

+ 0 - 22
packages/button/src/button-group.js

@@ -1,22 +0,0 @@
-/**
- * button
- * @module components/basic/menu
- * @desc 用于按钮组
- * @param {string} label - 名称
- */
-export default {
-  name: 'ElButtonGroup',
-
-  functional: true,
-
-  render(h, { slots, data }) {
-    return (
-      <div
-        class="el-button-group"
-        { ...data }
-        { ...{ on: data.nativeOn } }>
-        { slots().default }
-      </div>
-    );
-  }
-};

+ 16 - 0
packages/button/src/button-group.vue

@@ -0,0 +1,16 @@
+<template>
+  <div class="el-button-group">
+    <slot></slot>
+  </div>
+</template>
+<script>
+  /**
+   * button
+   * @module components/basic/menu
+   * @desc 用于按钮组
+   * @param {string} label - 名称
+   */
+  export default {
+    name: 'ElButtonGroup'
+  };
+</script>

+ 0 - 65
packages/button/src/button.js

@@ -1,65 +0,0 @@
-export default {
-  name: 'ElButton',
-
-  functional: true,
-
-  props: {
-    type: {
-      type: String,
-      default: 'default'
-    },
-    size: String,
-    icon: {
-      type: String,
-      default: ''
-    },
-    nativeType: {
-      type: String,
-      default: 'button'
-    },
-    loading: {
-      type: Boolean,
-      default: false
-    },
-    disabled: {
-      type: Boolean,
-      default: false
-    },
-    plain: {
-      type: Boolean,
-      default: false
-    }
-  },
-
-  render(h, { props, slots, data }) {
-    return (
-      <button
-        disabled={ props.disabled }
-        type={ props.nativeType }
-        class={[
-          'el-button',
-          props.type ? 'el-button-' + props.type : '',
-          props.size ? 'el-button-' + props.size : '',
-          {
-            'is-disabled': props.disabled,
-            'is-loading': props.loading,
-            'is-plain': props.plain
-          }
-        ]}
-        { ...data }
-        { ...{ on: data.nativeOn } }>
-        {
-          [
-            props.loading
-              ? <i class="el-icon-loading"></i>
-              : {},
-            props.icon && !props.loading
-              ? <i class={ 'el-icon-' + props.icon }></i>
-              : {}
-          ]
-        }
-        { slots().default }
-      </button>
-    );
-  }
-};

+ 54 - 0
packages/button/src/button.vue

@@ -0,0 +1,54 @@
+<template>
+  <button :disabled="disabled" class="el-button"
+    :type="nativeType"
+    :class="[
+      type ? 'el-button-' + type : '',
+      size ? 'el-button-' + size : '',
+      {
+        'is-disabled': disabled,
+        'is-loading': loading,
+        'is-plain': plain
+      }
+    ]"
+  >
+    <i class="el-icon-loading" v-if="loading"></i>
+    <i :class="'el-icon-' + icon" v-if="icon && !loading"></i>
+    <slot></slot>
+  </button>
+</template>
+<script>
+  /**
+   * button
+   */
+  export default {
+    name: 'ElButton',
+
+    props: {
+      type: {
+        type: String,
+        default: 'default'
+      },
+      size: String,
+      icon: {
+        type: String,
+        default: ''
+      },
+      nativeType: {
+        type: String,
+        default: 'button'
+      },
+      loading: {
+        type: Boolean,
+        default: false
+      },
+      disabled: {
+        type: Boolean,
+        default: false
+      },
+      plain: {
+        type: Boolean,
+        default: false
+      }
+    }
+  };
+</script>

+ 32 - 14
packages/checkbox/src/checkbox.vue

@@ -4,7 +4,7 @@
       <span class="el-checkbox__inner"
         :class="{
           'is-disabled': disabled,
-          'is-checked': checked,
+          'is-checked': isChecked,
           'is-indeterminate': indeterminate,
           'is-focus': focus
         }"
@@ -12,23 +12,25 @@
       <input
         v-if="trueLabel || falseLabel"
         class="el-checkbox__original"
+        type="checkbox"
+        :disabled="disabled"
         :true-value="trueLabel"
         :false-value="falseLabel"
         v-model="_value"
-        type="checkbox"
         @focus="focus = true"
         @blur="focus = false"
-        :disabled="disabled"
+        @change="handleChange"
         ref="checkbox">
       <input
         v-else
         class="el-checkbox__original"
+        type="checkbox"
+        :disabled="disabled"
         :value="label"
         v-model="_value"
         @focus="focus = true"
         @blur="focus = false"
-        type="checkbox"
-        :disabled="disabled">
+        @change="handleChange">
     </span>
     <span class="el-checkbox__label" v-if="$slots.default || label">
       <slot></slot>
@@ -46,11 +48,10 @@
 
     props: {
       value: {},
-      label: {
-        type: String
-      },
+      label: String,
       indeterminate: Boolean,
       disabled: Boolean,
+      checked: Boolean,
       trueLabel: [String, Number],
       falseLabel: [String, Number]
     },
@@ -58,17 +59,17 @@
     computed: {
       _value: {
         get() {
-          return this.value !== undefined ? this.value : this.$parent.value;
+          return !this.wrapInGroup ? this.value : this.$parent.value;
         },
         set(newValue) {
-          if (this.value !== undefined) {
+          if (!this.wrapInGroup) {
             this.$emit('input', newValue);
           } else {
             this.$parent.$emit('input', newValue);
           }
         }
       },
-      checked() {
+      isChecked() {
         var type = Object.prototype.toString.call(this._value);
 
         if (type === '[object Boolean]') {
@@ -83,13 +84,30 @@
 
     data() {
       return {
-        focus: false
+        focus: false,
+        wrapInGroup: this.$parent.$options._componentTag === 'el-checkbox-group'
       };
     },
 
     watch: {
-      checked(sure) {
-        this.$emit('change', sure);
+      checked: {
+        immediate: true,
+        handler(value) {
+          if (value) {
+            let type = Object.prototype.toString.call(this._value);
+            if (type !== '[object Array]') {
+              this._value = this.trueLabel || true;
+            } else {
+              this._value.push(this.label);
+            }
+          }
+        }
+      }
+    },
+
+    methods: {
+      handleChange(ev) {
+        this.$emit('change', ev);
       }
     }
   };

+ 4 - 2
packages/date-picker/src/basic/date-table.vue

@@ -24,7 +24,7 @@
       <td
         v-for="cell in row"
         :class="getCellClasses(cell)"
-        v-text="cell.type === 'today' ? '今天' : cell.text"></td>
+        v-text="cell.type === 'today' ? $t('datepicker.today') : cell.text"></td>
     </tr>
     </tbody>
   </table>
@@ -214,7 +214,9 @@
     },
 
     methods: {
-      $t: $t,
+      $t(...args) {
+        return $t.apply(this, args);
+      },
 
       getCellClasses(cell) {
         const selectionMode = this.selectionMode;

+ 3 - 1
packages/date-picker/src/basic/month-table.vue

@@ -58,7 +58,9 @@
     },
 
     methods: {
-      $t: $t,
+      $t(...args) {
+        return $t.apply(this, args);
+      },
 
       handleMonthTableClick(event) {
         const target = event.target;

+ 13 - 1
packages/date-picker/src/css/date-picker.css

@@ -3,7 +3,19 @@
 
 @component-namespace el {
   @b date-picker {
-    min-width: 300px;
+    min-width: 254px;
+
+    &.has-sidebar.has-time {
+      min-width: 434px;
+    }
+
+    &.has-sidebar {
+      min-width: 370px;
+    }
+
+    &.has-time {
+      min-width: 324px;
+    }
 
     .el-picker-panel__content {
       min-width: 224px;

+ 14 - 0
packages/date-picker/src/css/date-range-picker.css

@@ -2,6 +2,20 @@
 
 @component-namespace el {
   @b date-range-picker {
+    min-width: 520px;
+
+    &.has-sidebar.has-time {
+      min-width: 766px;
+    }
+
+    &.has-sidebar {
+      min-width: 620px;
+    }
+
+    &.has-time {
+      min-width: 660px;
+    }
+
     table {
       table-layout: fixed;
       width: 100%;

+ 1 - 0
packages/date-picker/src/css/picker-panel.css

@@ -63,6 +63,7 @@
       cursor: pointer;
       background-color: transparent;
       outline: none;
+      font-size: 12px;
 
       &[disabled] {
         color: #cccccc;

+ 1 - 1
packages/date-picker/src/css/time-range-picker.css

@@ -2,7 +2,7 @@
 
 @component-namespace el {
   @b time-range-picker {
-    width: 354px;
+    min-width: 354px;
     overflow: visible;
 
     @e content {

+ 36 - 8
packages/date-picker/src/panel/date-range.vue

@@ -1,8 +1,13 @@
 <template>
-  <transition name="md-fade-bottom">
+  <transition name="md-fade-bottom" @after-leave="$emit('dodestroy')">
     <div
       v-show="visible"
-      class="el-picker-panel el-date-range-picker">
+      :style="{ width: width + 'px' }"
+      class="el-picker-panel el-date-range-picker"
+      :class="{
+        'has-sidebar': $slots.sidebar || shortcuts,
+        'has-time': showTime
+      }">
       <div class="el-picker-panel__body-wrapper">
         <slot name="sidebar" class="el-picker-panel__sidebar"></slot>
         <div class="el-picker-panel__sidebar" v-if="shortcuts">
@@ -31,6 +36,7 @@
                   @focus="leftTimePickerVisible = true"
                   @change="handleTimeChange($event, 'min')"/>
                 <time-picker
+                  :picker-width="leftPickerWidth"
                   ref="lefttimepicker"
                   :date="minDate"
                   @pick="handleLeftTimePick"
@@ -41,6 +47,7 @@
             <span class="el-icon-arrow-right"></span>
             <span class="el-date-range-picker__editors-wrap is-right">
               <input
+                ref="leftInput"
                 placeholder="结束日期"
                 class="el-date-range-picker__editor"
                 v-model="rightVisibleDate"
@@ -51,6 +58,7 @@
                 class="el-date-range-picker__time-picker-wrap"
                 v-clickoutside="closeRightTimePicker">
                 <input
+                  ref="rightInput"
                   placeholder="结束时间"
                   class="el-date-range-picker__editor"
                   v-model="rightVisibleTime"
@@ -58,6 +66,7 @@
                   :readonly="!minDate"
                   @change="handleTimeChange($event, 'max')" />
                 <time-picker
+                  :picker-width="rightPickerWidth"
                   ref="righttimepicker"
                   :date="maxDate"
                   @pick="handleRightTimePick"
@@ -119,12 +128,12 @@
       <div class="el-picker-panel__footer" v-if="showTime">
         <!-- <a
           class="el-picker-panel__link-btn"
-          @click="changeToToday">{{ $t('datepicker.today') }}</a> -->
+          @click="changeToToday">{{ $t('datepicker.now') }}</a> -->
         <button
           type="button"
           class="el-picker-panel__btn"
           @click="handleConfirm"
-          :disabled="btnDisabled">确定</button>
+          :disabled="btnDisabled">{{ $t('datepicker.confirm') }}</button>
       </div>
     </div>
   </transition>
@@ -140,11 +149,11 @@
       },
 
       leftLabel() {
-        return this.date.getFullYear() + '年 ' + (this.date.getMonth() + 1) + '月';
+        return this.date.getFullYear() + ' ' + this.$t('datepicker.month') + ' ' + (this.date.getMonth() + 1) + ' ' + this.$t('datepicker.month');
       },
 
       rightLabel() {
-        return this.rightDate.getFullYear() + '年 ' + (this.rightDate.getMonth() + 1) + '月';
+        return this.rightDate.getFullYear() + ' ' + this.$t('datepicker.month') + ' ' + (this.rightDate.getMonth() + 1) + ' ' + this.$t('datepicker.month');
       },
 
       leftYear() {
@@ -254,6 +263,8 @@
 
     data() {
       return {
+        leftPickerWidth: 0,
+        rightPickerWidth: 0,
         date: new Date(),
         minDate: '',
         maxDate: '',
@@ -269,11 +280,26 @@
         visible: '',
         disabledDate: '',
         leftTimePickerVisible: false,
-        rightTimePickerVisible: false
+        rightTimePickerVisible: false,
+        width: 0
       };
     },
 
     watch: {
+      showTime(val) {
+        if (!val) return;
+        this.$nextTick(_ => {
+          const leftInputElm = this.$refs.leftInput;
+          const rightInputElm = this.$refs.rightInput;
+          if (leftInputElm) {
+            this.leftPickerWidth = leftInputElm.getBoundingClientRect().width + 10;
+          }
+          if (rightInputElm) {
+            this.rightPickerWidth = rightInputElm.getBoundingClientRect().width + 10;
+          }
+        });
+      },
+
       minDate() {
         this.$nextTick(() => {
           if (this.maxDate && this.maxDate < this.minDate) {
@@ -302,7 +328,9 @@
     },
 
     methods: {
-      $t,
+      $t(...args) {
+        return $t.apply(this, args);
+      },
 
       closeLeftTimePicker() {
         this.leftTimePickerVisible = false;

+ 34 - 10
packages/date-picker/src/panel/date.vue

@@ -1,8 +1,15 @@
 <template>
-  <transition name="md-fade-bottom">
+  <transition name="md-fade-bottom" @after-leave="$emit('dodestroy')">
     <div
       v-show="visible"
-      class="el-picker-panel el-date-picker">
+      :style="{
+        width: width + 'px'
+      }"
+      class="el-picker-panel el-date-picker"
+      :class="{
+        'has-sidebar': $slots.sidebar || shortcuts,
+        'has-time': showTime
+      }">
       <div class="el-picker-panel__body-wrapper">
         <slot name="sidebar" class="el-picker-panel__sidebar"></slot>
         <div class="el-picker-panel__sidebar" v-if="shortcuts">
@@ -15,20 +22,22 @@
         <div class="el-picker-panel__body">
          <div class="el-date-picker__time-header" v-if="showTime">
             <input
-              placehoder="选择日期"
+              :placehoder="$t('datepicker.selectDate')"
               type="text"
               v-model="visibleDate"
               class="el-date-picker__editor">
             <span style="position: relative" v-clickoutside="closeTimePicker">
               <input
+                ref="input"
                 @focus="timePickerVisible = true"
                 v-model="visibleTime"
-                placehoder="选择时间"
+                :placehoder="$t('datepicker.selectTime')"
                 type="text"
                 class="el-date-picker__editor">
               <time-picker
                 ref="timepicker"
                 :date="date"
+                :picker-width="pickerWidth"
                 @pick="handleTimePick"
                 :visible="timePickerVisible">
               </time-picker>
@@ -53,7 +62,7 @@
               @click="showMonthPicker"
               v-show="currentView === 'date'"
               class="el-date-picker__header-label"
-              :class="{ active: currentView === 'month' }">{{ month + 1 }}</span>
+              :class="{ active: currentView === 'month' }">{{ month + 1 }} {{$t('datepicker.month')}}</span>
             <button
               type="button"
               @click="nextYear"
@@ -100,7 +109,7 @@
         <a
           href="JavaScript:"
           class="el-picker-panel__link-btn"
-          @click="changeToToday">{{ $t('datepicker.today') }}</a>
+          @click="changeToToday">{{ $t('datepicker.now') }}</a>
         <button
           type="button"
           class="el-picker-panel__btn"
@@ -115,6 +124,16 @@
 
   export default {
     watch: {
+      showTime(val) {
+        if (!val) return;
+        this.$nextTick(_ => {
+          const inputElm = this.$refs.input;
+          if (inputElm) {
+            this.pickerWidth = inputElm.getBoundingClientRect().width + 10;
+          }
+        });
+      },
+
       value(newVal) {
         if (this.selectionMode === 'day' && newVal instanceof Date) {
           this.date = newVal;
@@ -148,7 +167,9 @@
     },
 
     methods: {
-      $t: $t,
+      $t(...args) {
+        return $t.apply(this, args);
+      },
 
       resetDate() {
         this.date = new Date(this.date);
@@ -326,6 +347,7 @@
 
     data() {
       return {
+        pickerWidth: 0,
         date: new Date(),
         value: '',
         showTime: false,
@@ -337,7 +359,8 @@
         year: null,
         month: null,
         week: null,
-        timePickerVisible: false
+        timePickerVisible: false,
+        width: 0
       };
     },
 
@@ -383,11 +406,12 @@
       yearLabel() {
         const year = this.year;
         if (!year) return '';
+        const yearTranslation = this.$t('datepicker.year');
         if (this.currentView === 'year') {
           const startYear = Math.floor(year / 10) * 10;
-          return startYear + '年' + '-' + (startYear + 9) + '年';
+          return startYear + ' ' + yearTranslation + '-' + (startYear + 9) + ' ' + yearTranslation;
         }
-        return this.year + '年';
+        return this.year + ' ' + yearTranslation;
       },
 
       hours: {

+ 14 - 7
packages/date-picker/src/panel/time-range.vue

@@ -1,11 +1,12 @@
 <template>
-  <transition name="md-fade-bottom">
+  <transition name="md-fade-bottom" @after-leave="$emit('dodestroy')">
     <div
       v-show="visible"
+      :style="{ width: width + 'px' }"
       class="el-time-range-picker el-picker-panel">
       <div class="el-time-range-picker__content">
         <div class="el-time-range-picker__cell">
-          <div class="el-time-range-picker__header">开始时间</div>
+          <div class="el-time-range-picker__header">{{ $t('datepicker.startTime') }}</div>
           <div class="el-time-range-picker__body el-time-panel__content">
             <time-spinner
               ref="minSpinner"
@@ -19,7 +20,7 @@
           </div>
         </div>
         <div class="el-time-range-picker__cell">
-          <div class="el-time-range-picker__header">结束时间</div>
+          <div class="el-time-range-picker__header">{{ $t('datepicker.endTime') }}</div>
           <div class="el-time-range-picker__body el-time-panel__content">
             <time-spinner
               ref="maxSpinner"
@@ -37,19 +38,20 @@
         <button
           type="button"
           class="el-time-panel__btn cancel"
-          @click="handleCancel()">取消</button>
+          @click="handleCancel()">{{ $t('datepicker.cancel') }}</button>
         <button
           type="button"
           class="el-time-panel__btn confirm"
           @click="handleConfirm()"
-          :disabled="btnDisabled">确定</button>
+          :disabled="btnDisabled">{{ $t('datepicker.confirm') }}</button>
       </div>
     </div>
   </transition>
 </template>
 
-<script type="text/ecmascript-6">
+<script type="text/babel">
   import { parseDate, limitRange } from '../util';
+  import { $t } from '../util';
 
   const MIN_TIME = parseDate('00:00:00', 'HH:mm:ss');
   const MAX_TIME = parseDate('23:59:59', 'HH:mm:ss');
@@ -90,11 +92,16 @@
         minMinutes: minTime.getMinutes(),
         minSeconds: minTime.getSeconds(),
         format: 'HH:mm:ss',
-        visible: false
+        visible: false,
+        width: 0
       };
     },
 
     methods: {
+      $t(...args) {
+        return $t.apply(this, args);
+      },
+
       handleCancel() {
         this.$emit('pick');
       },

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

@@ -1,7 +1,8 @@
 <template>
-  <transition name="md-fade-bottom">
+  <transition name="md-fade-bottom" @after-leave="$emit('dodestroy')">
     <div
       v-show="visible"
+      :style="{ width: width + 'px' }"
       class="el-picker-panel time-select">
       <div class="el-picker-panel__content">
         <div class="time-select-item"
@@ -16,7 +17,7 @@
   </transition>
 </template>
 
-<script type="text/ecmascript-6">
+<script type="text/babel">
   const parseTime = function(time) {
     const values = ('' || time).split(':');
     if (values.length >= 2) {
@@ -91,7 +92,8 @@
         step: '00:30',
         value: '',
         visible: false,
-        minTime: ''
+        minTime: '',
+        width: 0
       };
     },
 

+ 16 - 5
packages/date-picker/src/panel/time.vue

@@ -1,7 +1,8 @@
 <template>
-  <transition name="md-fade-bottom">
+  <transition name="md-fade-bottom" @after-leave="$emit('dodestroy')">
     <div
       v-show="currentVisible"
+      :style="{width: width + 'px'}"
       class="el-time-panel">
       <div class="el-time-panel__content">
         <time-spinner
@@ -18,11 +19,11 @@
         <button
           type="button"
           class="el-time-panel__btn cancel"
-          @click="handleCancel()">取消</button>
+          @click="handleCancel()">{{ $t('datepicker.cancel') }}</button>
         <button
           type="button"
           class="el-time-panel__btn confirm"
-          @click="handleConfirm()">确定</button>
+          @click="handleConfirm()">{{ $t('datepicker.confirm') }}</button>
       </div>
     </div>
   </transition>
@@ -31,6 +32,7 @@
 <script type="text/babel">
   import { limitRange } from '../util';
   import Vue from 'vue';
+  import { $t } from '../util';
 
   export default {
     components: {
@@ -38,12 +40,12 @@
     },
 
     props: {
+      pickerWidth: {},
       date: {
         default() {
           return new Date();
         }
       },
-
       visible: Boolean
     },
 
@@ -52,6 +54,10 @@
         this.currentVisible = val;
       },
 
+      pickerWidth(val) {
+        this.width = val;
+      },
+
       value(newVal) {
         let date;
         if (newVal instanceof Date) {
@@ -80,7 +86,8 @@
         seconds: 0,
         selectableRange: [],
         currentDate: this.$options.defaultValue || this.date,
-        currentVisible: this.visible
+        currentVisible: this.visible,
+        width: this.pickerWidth || 0
       };
     },
 
@@ -91,6 +98,10 @@
     },
 
     methods: {
+      $t(...args) {
+        return $t.apply(this, args);
+      },
+
       handleCancel() {
         this.$emit('pick', null);
       },

+ 27 - 42
packages/date-picker/src/picker.vue

@@ -11,7 +11,7 @@
     <input
       class="el-date-editor__editor"
       :readonly="readonly"
-      :type="editorType"
+      type="text"
       :placeholder="placeholder"
       @focus="handleFocus"
       @blur="handleBlur"
@@ -33,9 +33,20 @@
 import Vue from 'vue';
 import Clickoutside from 'main/utils/clickoutside';
 import { merge, formatDate, parseDate, getWeekNumber } from './util';
-import Popper from 'main/utils/popper';
+import Popper from 'main/utils/vue-popper';
 import emitter from 'main/mixins/emitter';
 
+const newPopper = {
+  props: {
+    appendToBody: Popper.props.appendToBody,
+    offset: Popper.props.offset,
+    boundariesPadding: Popper.props.boundariesPadding
+  },
+  methods: Popper.methods,
+  data: Popper.data,
+  beforeDestroy: Popper.beforeDestroy
+};
+
 const FUNCTION_KEYS = [13, 16, 17, 18, 19, 20, 27, 33, 34, 35, 36, 37, 38, 39, 40];
 const RANGE_SEPARATOR = ' - ';
 const DEFAULT_FORMATS = {
@@ -184,7 +195,7 @@ const PLACEMENT_MAP = {
 };
 
 export default {
-  mixins: [emitter],
+  mixins: [emitter, newPopper],
 
   props: {
     format: String,
@@ -274,13 +285,18 @@ export default {
         }
         this.$emit('input', value);
       }
-    },
-
-    editorType() {
-      return 'text';
     }
   },
 
+  created() {
+    // vue-popper
+    this.options = {
+      boundariesPadding: 0,
+      gpuAcceleration: false
+    };
+    this.placement = PLACEMENT_MAP[this.align] || PLACEMENT_MAP.left;
+  },
+
   methods: {
     handleClose() {
       this.pickerVisible = false;
@@ -386,16 +402,6 @@ export default {
       !this.pickerVisible ? this.showPicker() : this.hidePicker();
     },
 
-    destroyPopper() {
-      if (this.popper) {
-        this.resetTransformOrigin(this.popper);
-        setTimeout(() => {
-          this.popper && this.popper.destroy();
-          this.popper = null;
-        }, 300);
-      }
-    },
-
     hidePicker() {
       if (this.picker) {
         this.picker.resetView && this.picker.resetView();
@@ -410,6 +416,8 @@ export default {
         this.picker = new Vue(merge({
           el: document.createElement('div')
         }, this.panel));
+        this.popperElm = this.picker.$el;
+        this.picker.width = this.$refs.reference.getBoundingClientRect().width;
         this.picker.showTime = this.type === 'datetime' || this.type === 'datetimerange';
         this.picker.selectionMode = this.selectionMode;
         if (this.format) {
@@ -445,6 +453,7 @@ export default {
         this.pickerVisible = this.picker.visible = true;
         this.picker.resetView && this.picker.resetView();
 
+        this.picker.$on('dodestroy', this.doDestroy);
         this.picker.$on('pick', (date, visible = false) => {
           this.$emit('input', date);
 
@@ -464,20 +473,7 @@ export default {
         this.pickerVisible = this.picker.visible = true;
       }
 
-      this.$nextTick(() => {
-        if (this.popper) return;
-
-        this.popper = new Popper(this.$refs.reference, this.picker.$el, {
-          gpuAcceleration: false,
-          placement: PLACEMENT_MAP[this.align] || PLACEMENT_MAP.left,
-          boundariesPadding: 0,
-          forceAbsolute: true
-        });
-
-        this.popper.onCreate(popper => {
-          this.resetTransformOrigin(popper);
-        });
-      });
+      this.updatePopper();
 
       if (this.value instanceof Date) {
         this.picker.date = new Date(this.value.getTime());
@@ -492,18 +488,7 @@ export default {
         }
         this.picker.ajustScrollTop && this.picker.ajustScrollTop();
       });
-    },
-
-    resetTransformOrigin(popper) {
-      let placementMap = { top: 'bottom', bottom: 'top' };
-      let placement = popper._popper.getAttribute('x-placement').split('-')[0];
-      let origin = placementMap[placement];
-      popper._popper.style.transformOrigin = `center ${ origin }`;
     }
-  },
-
-  beforeDestroy() {
-    this.popper && this.popper.destroy();
   }
 };
 </script>

+ 9 - 1
packages/date-picker/src/util/i18n.js

@@ -1,8 +1,16 @@
 export default {
   datepicker: {
-    today: '此刻',
+    now: '此刻',
+    today: '今天',
+    cancel: '取消',
     clear: '清空',
     confirm: '确定',
+    selectDate: '选择日期',
+    selectTime: '选择时间',
+    startTime: '开始时间',
+    endTime: '结束时间',
+    year: '年',
+    month: '月',
     week: '周次',
     weeks: {
       sun: '日',

+ 5 - 1
packages/date-picker/src/util/index.js

@@ -176,7 +176,11 @@ export const limitRange = function(date, ranges) {
 
 import i18n from './i18n';
 
-export const $t = function(path) {
+export const $t = function(path, options) {
+  const vuei18n = Object.getPrototypeOf(this).$t;
+  if (typeof vuei18n === 'function') {
+    return vuei18n.apply(this, [path, options]);
+  }
   const array = path.split('.');
   let current = i18n;
   for (var i = 0, j = array.length; i < j; i++) {

+ 19 - 23
packages/dropdown/src/dropdown-menu.vue

@@ -1,39 +1,35 @@
 <template>
-  <ul class="el-dropdown__menu">
+  <transition name="md-fade-bottom" @after-leave="doDestroy">
+  <ul class="el-dropdown__menu" v-show="showPopper">
     <slot></slot>
   </ul>
+  </transition>
 </template>
 <script>
-  import Popper from 'main/utils/popper';
+  import Popper from 'main/utils/vue-popper';
 
   export default {
     name: 'ElDropdownMenu',
 
-    props: {
-      visible: Boolean
-    },
-    data() {
-      return {
-        popper: null
-      };
-    },
-    computed: {
-      menuAlign() {
-        return this.$parent.menuAlign;
-      }
-    },
-    mounted() {
-      document.body.appendChild(this.$el);
+    componentName: 'ElDropdownMenu',
 
-      this.$nextTick(() => {
-        this.popper = new Popper(this.$parent.$el, this.$el, { gpuAcceleration: false, placement: `bottom-${this.menuAlign}` });
+    mixins: [Popper],
+
+    created() {
+      this.$on('visible', val => {
+        this.showPopper = val;
       });
     },
 
-    destroyed() {
-      setTimeout(() => {
-        this.popper.destroy();
-      }, 300);
+    mounted() {
+      this.$parent.popperElm = this.popperElm = this.$el;
+      this.referenceElm = this.$parent.$el;
+    },
+
+    computed: {
+      placement() {
+        return `bottom-${this.$parent.menuAlign}`;
+      }
     }
   };
 </script>

+ 16 - 11
packages/dropdown/src/dropdown.vue

@@ -1,9 +1,12 @@
 <script>
   import Clickoutside from 'main/utils/clickoutside';
+  import emitter from 'main/mixins/emitter';
 
   export default {
     name: 'ElDropdown',
 
+    mixins: [emitter],
+
     directives: { Clickoutside },
 
     props: {
@@ -15,9 +18,8 @@
         type: String,
         default: 'end'
       },
-      type: {
-        type: String
-      },
+      type: String,
+      size: String,
       splitButton: Boolean
     },
 
@@ -32,6 +34,12 @@
       this.initEvent();
     },
 
+    watch: {
+      visible(val) {
+        this.broadcast('ElDropdownMenu', 'visible', val);
+      }
+    },
+
     methods: {
       show() {
         clearTimeout(this.timeout);
@@ -51,7 +59,7 @@
       initEvent() {
         let { trigger, show, hide, handleClick, splitButton } = this;
         let triggerElm = splitButton
-          ? this.$refs.trigger
+          ? this.$refs.trigger.$el
           : this.$slots.default[0].elm;
 
         if (trigger === 'hover') {
@@ -74,8 +82,7 @@
     },
 
     render(h) {
-      let { hide, splitButton, visible, type } = this;
-      let dropdownElm = visible ? this.$slots.dropdown : null;
+      let { hide, splitButton, type, size } = this;
 
       var handleClick = _ => {
         this.$emit('click');
@@ -84,10 +91,10 @@
       let triggerElm = !splitButton
         ? this.$slots.default
         : (<el-button-group>
-            <el-button type={type} nativeOn-click={handleClick}>
+            <el-button type={type} size={size} nativeOn-click={handleClick}>
               {this.$slots.default}
             </el-button>
-            <el-button ref="trigger" type={type} class="el-dropdown__icon-button">
+            <el-button ref="trigger" type={type} size={size} class="el-dropdown__icon-button">
               <i class="el-dropdown__icon el-icon-caret-bottom"></i>
             </el-button>
           </el-button-group>);
@@ -95,9 +102,7 @@
       return (
         <div class="el-dropdown" v-clickoutside={hide}>
           {triggerElm}
-          <transition name="md-fade-bottom">
-            {dropdownElm}
-          </transition>
+          {this.$slots.dropdown}
         </div>
       );
     }

+ 19 - 15
packages/form/src/form-item.vue

@@ -127,9 +127,12 @@
         }
       },
       getRules() {
-        if (!this.prop) { return []; }
-        var rules = this.rules || (this.form.rules ? this.form.rules[this.prop] : []);
-        return Array.isArray(rules) ? rules : [rules];
+        var formRules = this.form.rules;
+        var selfRuels = this.rules;
+
+        formRules = formRules ? formRules[this.prop] : [];
+
+        return [].concat(selfRuels || formRules || []);
       },
       getFilteredRule(trigger) {
         var rules = this.getRules();
@@ -151,21 +154,22 @@
       }
     },
     mounted() {
-      var rules = this.getRules();
-
-      rules.every(rule => {
-        if (rule.required) {
-          this.isRequired = true;
-          return false;
-        }
-      });
-
       if (this.prop) {
         this.dispatch('form', 'el.form.addField', [this]);
-      }
 
-      this.$on('el.form.blur', this.onFieldBlur);
-      this.$on('el.form.change', this.onFieldChange);
+        let rules = this.getRules();
+
+        if (rules.length) {
+          rules.every(rule => {
+            if (rule.required) {
+              this.isRequired = true;
+              return false;
+            }
+          });
+          this.$on('el.form.blur', this.onFieldBlur);
+          this.$on('el.form.change', this.onFieldChange);
+        }
+      }
     },
     beforeDestroy() {
       this.dispatch('form', 'el.form.removeField', [this]);

+ 1 - 1
packages/icon/index.js

@@ -1 +1 @@
-module.exports = require('./src/icon');
+module.exports = require('./src/icon.vue');

+ 0 - 19
packages/icon/src/icon.js

@@ -1,19 +0,0 @@
-export default {
-  name: 'ElIcon',
-
-  functional: true,
-
-  props: {
-    name: String
-  },
-
-  render(h, { props, data }) {
-    return (
-      <i
-        class={ 'el-icon' + props.name }
-        { ...data }
-        { ...{ on: data.nativeOn } }>
-      </i>
-    );
-  }
-};

+ 13 - 0
packages/icon/src/icon.vue

@@ -0,0 +1,13 @@
+<template>
+  <i :class="'el-icon-' + name"></i>
+</template>
+
+<script>
+  export default {
+    name: 'ElIcon',
+
+    props: {
+      name: String
+    }
+  };
+</script>

+ 4 - 3
packages/input/src/input.vue

@@ -15,7 +15,7 @@
       <input
         class="el-input__inner"
         v-model="currentValue"
-        :type="type"
+        type="text"
         :name="name"
         :placeholder="placeholder"
         :disabled="disabled"
@@ -25,7 +25,7 @@
         :minlength="minlength"
         :autocomplete="autoComplete"
         ref="input"
-        @focus="$emit('onfocus', currentValue)"
+        @focus="$emit('focus', currentValue)"
         @blur="handleBlur"
       >
       <!-- input 图标 -->
@@ -49,7 +49,7 @@
       :rows="rows"
       :maxlength="maxlength"
       :minlength="minlength"
-      @focus="$emit('onfocus', currentValue)"
+      @focus="$emit('focus', currentValue)"
       @blur="handleBlur">
     </textarea>
   </div>
@@ -162,6 +162,7 @@
 
       'currentValue'(val) {
         this.$emit('input', val);
+        this.$emit('change', val);
         this.dispatch('form-item', 'el.form.change', [val]);
       }
     }

+ 3 - 1
packages/loading/package.json

@@ -11,5 +11,7 @@
   "repository": "https://github.com/element-component/element/tree/master/packages/loading",
   "author": "elemefe",
   "license": "MIT",
-  "dependencies": {}
+  "dependencies": {
+    "wind-dom": "0.0.3"
+  }
 }

+ 4 - 2
packages/loading/src/spinner.js

@@ -1,10 +1,12 @@
+import { addClass } from 'wind-dom/src/class';
+
 class Spinner {
   constructor() {
     let spinner = document.createElement('div');
-    spinner.classList.add('el-loading-spinner');
+    addClass(spinner, 'el-loading-spinner');
     [1, 2, 3].forEach(index => {
       let bubble = document.createElement('div');
-      bubble.classList.add('el-loading-bubble', `bubble${ index }`);
+      addClass(bubble, `el-loading-bubble bubble${ index }`);
       spinner.appendChild(bubble);
     });
     this.el = spinner;

+ 16 - 21
packages/menu/src/menu.vue

@@ -27,12 +27,7 @@
         type: String,
         default: ''
       },
-      defaultOpeneds: {
-        type: Array,
-        default() {
-          return [];
-        }
-      },
+      defaultOpeneds: Array,
       theme: {
         type: String,
         default: 'light'
@@ -43,7 +38,7 @@
     data() {
       return {
         activeIndex: this.defaultActive,
-        openedMenus: this.defaultOpeneds.slice(0),
+        openedMenus: this.defaultOpeneds ? this.defaultOpeneds.slice(0) : [],
         menuItems: {},
         submenus: {}
       };
@@ -63,6 +58,7 @@
       openMenu(index, indexPath) {
         let openedMenus = this.openedMenus;
         if (openedMenus.indexOf(index) !== -1) return;
+        // 将不在该菜单路径下的其余菜单收起
         if (this.uniqueOpened) {
           this.openedMenus = openedMenus.filter(index => {
             return indexPath.indexOf(index) !== -1;
@@ -92,29 +88,28 @@
           this.broadcast('submenu', 'item-select', [index, indexPath]);
           this.openedMenus = [];
         } else {
+          this.openActiveItemMenus();
+        }
+
+        if (this.router && route) {
+          this.$router.push(route);
+        }
+      },
+      openActiveItemMenus() {
+        let index = this.activeIndex;
+        if (index && this.mode === 'vertical') {
+          let indexPath = this.menuItems[index].indexPath;
+
           // 展开该菜单项的路径上所有子菜单
           indexPath.forEach(index => {
             let submenu = this.submenus[index];
             submenu && this.openMenu(index, submenu.indexPath);
           });
         }
-
-        if (this.router && route) {
-          this.$router.push(route);
-        }
       }
     },
     mounted() {
-      let index = this.activeIndex;
-      if (index && this.mode === 'vertical') {
-        let indexPath = this.menuItems[index].indexPath;
-
-        // 展开该菜单项的路径上所有子菜单
-        indexPath.forEach(index => {
-          let submenu = this.submenus[index];
-          submenu && this.openMenu(index, submenu.indexPath);
-        });
-      }
+      this.openActiveItemMenus();
     }
   };
 </script>

+ 1 - 0
packages/message-box/package.json

@@ -12,5 +12,6 @@
   "author": "elemefe",
   "license": "MIT",
   "dependencies": {
+    "wind-dom": "0.0.3"
   }
 }

+ 4 - 3
packages/message-box/src/main.vue

@@ -35,6 +35,7 @@
 
   import Popup from 'vue-popup';
   import ElInput from 'packages/input/index.js';
+  import { addClass, removeClass } from 'wind-dom/src/class';
 
   export default {
     mixins: [ Popup ],
@@ -113,7 +114,7 @@
           var inputPattern = this.inputPattern;
           if (inputPattern && !inputPattern.test(this.inputValue || '')) {
             this.editorErrorMessage = this.inputErrorMessage || '输入的数据不合法!';
-            this.$refs.input.$el.querySelector('input').classList.add('invalid');
+            addClass(this.$refs.input.$el.querySelector('input'), 'invalid');
             return false;
           }
           var inputValidator = this.inputValidator;
@@ -121,7 +122,7 @@
             var validateResult = inputValidator(this.inputValue);
             if (validateResult === false) {
               this.editorErrorMessage = this.inputErrorMessage || '输入的数据不合法!';
-              this.$refs.input.$el.querySelector('input').classList.add('invalid');
+              addClass(this.$refs.input.$el.querySelector('input'), 'invalid');
               return false;
             }
             if (typeof validateResult === 'string') {
@@ -131,7 +132,7 @@
           }
         }
         this.editorErrorMessage = '';
-        this.$refs.input.$el.querySelector('input').classList.remove('invalid');
+        removeClass(this.$refs.input.$el.querySelector('input'), 'invalid');
         return true;
       }
     },

+ 4 - 10
packages/pagination/src/pagination.js

@@ -168,14 +168,9 @@ export default {
           this.oldValue = event.target.value;
         },
 
-        handleChange(event) {
-          const target = event.target;
+        handleChange({ target }) {
           this.$parent.internalCurrentPage = this.$parent.getValidCurrentPage(target.value);
-
-          if (target.value !== this.oldValue && Number(target.value) === this.$parent.internalCurrentPage) {
-            this.$parent.$emit('currentchange', this.$parent.internalCurrentPage);
-          }
-
+          this.$parent.$emit('currentchange', this.$parent.internalCurrentPage);
           this.oldValue = null;
         }
       },
@@ -189,12 +184,11 @@ export default {
               type="number"
               min={ 1 }
               max={ this.pageCount }
-              value={ this.$parent.internalCurrentPage }
+              domProps-value={ this.$parent.internalCurrentPage }
               on-change={ this.handleChange }
               on-focus={ this.handleFocus }
               style={{ width: '30px' }}
-              number
-              lazy/>
+              number/>
           </span>
         );

+ 43 - 49
packages/popover/src/main.vue

@@ -46,65 +46,59 @@ export default {
     transition: {
       type: String,
       default: 'fade-in-linear'
-    },
-    options: {
-      default() {
-        return {
-          gpuAcceleration: false
-        };
-      }
     }
   },
 
   mounted() {
-    setTimeout(() => {
-      let _timer;
-      const reference = this.reference || this.$refs.reference || this.$slots.reference[0].elm;
+    let _timer;
+    const reference = this.reference || this.$refs.reference || this.$slots.reference[0].elm;
+    const popper = this.popper || this.$refs.popper;
 
-      this.$nextTick(() => {
-        if (this.trigger === 'click') {
-          on(reference, 'click', () => { this.showPopper = !this.showPopper; });
-          on(document, 'click', (e) => {
-            if (!this.$el ||
-                !reference ||
-                this.$el.contains(e.target) ||
-                reference.contains(e.target)) return;
+    this.$nextTick(() => {
+      if (this.trigger === 'click') {
+        on(reference, 'click', () => { this.showPopper = !this.showPopper; });
+        on(document, 'click', (e) => {
+          if (!this.$el ||
+              !reference ||
+              this.$el.contains(e.target) ||
+              reference.contains(e.target) ||
+              !popper ||
+              popper.contains(e.target)) return;
+          this.showPopper = false;
+        });
+      } else if (this.trigger === 'hover') {
+        on(reference, 'mouseenter', () => {
+          this.showPopper = true;
+          clearTimeout(_timer);
+        });
+        on(reference, 'mouseleave', () => {
+          _timer = setTimeout(() => {
             this.showPopper = false;
-          });
-        } else if (this.trigger === 'hover') {
-          on(reference, 'mouseenter', () => {
-            this.showPopper = true;
-            clearTimeout(_timer);
-          });
-          on(reference, 'mouseleave', () => {
-            _timer = setTimeout(() => {
-              this.showPopper = false;
-            }, 200);
-          });
-        } else {
-          if ([].slice.call(reference.children).length) {
-            const children = reference.childNodes;
+          }, 200);
+        });
+      } else {
+        if ([].slice.call(reference.children).length) {
+          const children = reference.childNodes;
 
-            for (let i = 0; i < children.length; i++) {
-              if (children[i].nodeName === 'INPUT') {
-                on(children[i], 'focus', () => { this.showPopper = true; });
-                on(children[i], 'blur', () => { this.showPopper = false; });
-                break;
-              }
+          for (let i = 0; i < children.length; i++) {
+            if (children[i].nodeName === 'INPUT') {
+              on(children[i], 'focus', () => { this.showPopper = true; });
+              on(children[i], 'blur', () => { this.showPopper = false; });
+              break;
             }
-          } else if (
-              reference.nodeName === 'INPUT' ||
-              reference.nodeName === 'TEXTAREA'
-          ) {
-            on(reference, 'focus', () => { this.showPopper = true; });
-            on(reference, 'blur', () => { this.showPopper = false; });
-          } else {
-            on(reference, 'mousedown', () => { this.showPopper = true; });
-            on(reference, 'mouseup', () => { this.showPopper = false; });
           }
+        } else if (
+            reference.nodeName === 'INPUT' ||
+            reference.nodeName === 'TEXTAREA'
+        ) {
+          on(reference, 'focus', () => { this.showPopper = true; });
+          on(reference, 'blur', () => { this.showPopper = false; });
+        } else {
+          on(reference, 'mousedown', () => { this.showPopper = true; });
+          on(reference, 'mouseup', () => { this.showPopper = false; });
         }
-      });
-    }, 100);
+      }
+    });
   },
 
   destroyed() {

+ 3 - 1
packages/rate/package.json

@@ -11,5 +11,7 @@
   "repository": "https://github.com/element-component/element/tree/master/packages/rate",
   "author": "elemefe",
   "license": "MIT",
-  "dependencies": {}
+  "dependencies": {
+    "wind-dom": "0.0.3"
+  }
 }

+ 4 - 2
packages/rate/src/main.vue

@@ -24,6 +24,8 @@
 </template>
 
 <script type="text/babel">
+  import { hasClass } from 'wind-dom/src/class';
+
   export default {
     name: 'el-rate',
 
@@ -223,10 +225,10 @@
         }
         if (this.allowHalf) {
           let target = event.target;
-          if (target.classList.contains('el-rate__item')) {
+          if (hasClass(target, 'el-rate__item')) {
             target = target.querySelector('.el-rate__icon');
           }
-          if (target.classList.contains('el-rate__decimal')) {
+          if (hasClass(target, 'el-rate__decimal')) {
             target = target.parentNode;
           }
           this.pointerAtLeftHalf = event.offsetX * 2 <= target.clientWidth;

+ 34 - 47
packages/select-dropdown/src/select-dropdown.vue

@@ -1,71 +1,58 @@
 <template>
-  <div class="el-select-dropdown">
+  <div
+    class="el-select-dropdown"
+    :class="{ 'is-multiple': $parent.multiple }"
+    :style="{ minWidth: minWidth }">
     <slot></slot>
   </div>
 </template>
 
 <script type="text/babel">
-  import Popper from 'main/utils/popper';
+  import Popper from 'main/utils/vue-popper';
 
   export default {
     name: 'el-select-dropdown',
 
     componentName: 'select-dropdown',
 
-    data() {
-      return {
-        popper: null
-      };
-    },
-
-    created() {
-      this.$on('updatePopper', this.updatePopper);
-      this.$on('destroyPopper', this.destroyPopper);
-    },
+    mixins: [Popper],
 
-    methods: {
-      updatePopper() {
-        if (this.popper) {
-          this.$nextTick(() => {
-            this.popper.update();
-          });
-        } else {
-          this.$nextTick(() => {
-            this.popper = new Popper(this.$parent.$refs.reference.$el, this.$el, {
-              gpuAcceleration: false,
-              placement: 'bottom-start',
-              boundariesPadding: 0,
-              forceAbsolute: true
-            });
-            this.popper.onCreate(popper => {
-              this.resetTransformOrigin(popper);
-            });
-          });
-        }
+    props: {
+      placement: {
+        default: 'bottom-start'
       },
 
-      destroyPopper() {
-        if (this.popper) {
-          this.resetTransformOrigin(this.popper);
-          setTimeout(() => {
-            this.popper.destroy();
-            this.popper = null;
-          }, 300);
-        }
+      boundariesPadding: {
+        default: 0
       },
 
-      resetTransformOrigin(popper) {
-        let placementMap = { top: 'bottom', bottom: 'top' };
-        let placement = popper._popper.getAttribute('x-placement').split('-')[0];
-        let origin = placementMap[placement];
-        popper._popper.style.transformOrigin = `center ${ origin }`;
+      options: {
+        default() {
+          return {
+            forceAbsolute: true,
+            gpuAcceleration: false
+          };
+        }
       }
     },
 
-    beforeDestroy() {
-      if (this.popper) {
-        this.popper.destroy();
+    data() {
+      return {
+        minWidth: ''
+      };
+    },
+
+    watch: {
+      '$parent.inputWidth'() {
+        this.minWidth = this.$parent.$el.getBoundingClientRect().width + 'px';
       }
+    },
+
+    mounted() {
+      this.referenceElm = this.$parent.$refs.reference.$el;
+      this.$parent.popperElm = this.popperElm = this.$el;
+      this.$on('updatePopper', this.updatePopper);
+      this.$on('destroyPopper', this.destroyPopper);
     }
   };
 </script>

+ 2 - 1
packages/select/package.json

@@ -13,6 +13,7 @@
   "repository": "https://github.com/element-component/element/tree/master/packages/select",
   "devDependencies": {
     "throttle-debounce": "^1.0.1",
-    "vue-clickoutside": "0.0.4"
+    "vue-clickoutside": "0.0.4",
+    "wind-dom": "0.0.3"
   }
 }

+ 19 - 14
packages/select/src/select.vue

@@ -1,5 +1,8 @@
 <template>
-  <div class="el-select" :class="{ 'is-multiple': multiple, 'is-small': size === 'small' }">
+  <div
+    class="el-select"
+    v-clickoutside="handleClose"
+    :class="{ 'is-multiple': multiple, 'is-small': size === 'small' }">
     <div class="el-select__tags" v-if="multiple" @click.stop="toggleMenu" ref="tags" :style="{ 'max-width': inputWidth - 32 + 'px' }">
       <transition-group @after-leave="resetInputHeight">
         <el-tag
@@ -25,7 +28,7 @@
         v-model="query"
         :debounce="remote ? 300 : 0"
         v-if="filterable"
-        :style="{ width: inputLength + 'px' }"
+        :style="{ width: inputLength + 'px', 'max-width': inputWidth - 42 + 'px' }"
         ref="input">
     </div>
     <el-input
@@ -45,10 +48,9 @@
       @keydown.native.tab="visible = false"
       @mouseenter.native="inputHovering = true"
       @mouseleave.native="inputHovering = false"
-      :icon="iconClass"
-      v-element-clickoutside="handleClose">
+      :icon="iconClass">
     </el-input>
-    <transition name="md-fade-bottom">
+    <transition name="md-fade-bottom" @after-leave="doDestroy">
       <el-select-menu
         ref="popper"
         v-show="visible && nodataText !== false">
@@ -68,6 +70,7 @@
   import ElTag from 'packages/tag/index.js';
   import debounce from 'throttle-debounce/debounce';
   import Clickoutside from 'main/utils/clickoutside';
+  import { addClass, removeClass } from 'wind-dom/src/class';
 
   export default {
     mixins: [emitter],
@@ -93,10 +96,10 @@
         if (icon) {
           if (criteria) {
             icon.addEventListener('click', this.deleteSelected);
-            icon.classList.add('is-show-close');
+            addClass(icon, 'is-show-close');
           } else {
             icon.removeEventListener('click', this.deleteSelected);
-            icon.classList.remove('is-show-close');
+            removeClass(icon, 'is-show-close');
           }
         }
         return criteria;
@@ -127,9 +130,7 @@
       ElTag
     },
 
-    directives: {
-      ElementClickoutside: Clickoutside
-    },
+    directives: { Clickoutside },
 
     props: {
       name: String,
@@ -270,7 +271,7 @@
         if (!val) {
           this.$refs.reference.$el.querySelector('input').blur();
           if (this.$el.querySelector('.el-input__icon')) {
-            this.$el.querySelector('.el-input__icon').classList.remove('is-reverse');
+            removeClass(this.$el.querySelector('.el-input__icon'), 'is-reverse');
           }
           this.broadcast('select-dropdown', 'destroyPopper');
           if (this.$refs.input) {
@@ -287,7 +288,7 @@
           }
         } else {
           if (this.$el.querySelector('.el-input__icon')) {
-            this.$el.querySelector('.el-input__icon').classList.add('is-reverse');
+            addClass(this.$el.querySelector('.el-input__icon'), 'is-reverse');
           }
           this.broadcast('select-dropdown', 'updatePopper');
           if (this.filterable) {
@@ -316,6 +317,10 @@
     },
 
     methods: {
+      doDestroy() {
+        this.$refs.popper.doDestroy();
+      },
+
       handleClose() {
         this.visible = false;
       },
@@ -362,7 +367,7 @@
 
       resetInputState(e) {
         if (e.keyCode !== 8) this.toggleLastOptionHitState(false);
-        this.inputLength = this.$refs.input.value.length * 12 + 20;
+        this.inputLength = this.$refs.input.value.length * 15 + 20;
       },
 
       resetInputHeight() {
@@ -486,7 +491,7 @@
       },
 
       onInputChange() {
-        if (this.filterable) {
+        if (this.filterable && this.selectedLabel !== this.value) {
           this.query = this.selectedLabel;
         }
       },

+ 4 - 3
packages/slider/src/main.vue

@@ -26,10 +26,11 @@
   </div>
 </template>
 
-<script type="text/ecmascript-6">
+<script type="text/babel">
   import Popper from 'main/utils/popper';
   import ElInputNumber from 'packages/input-number/index.js';
   import { getStyle } from 'wind-dom/src/style';
+  import { addClass, removeClass } from 'wind-dom/src/class';
 
   export default {
     name: 'ElSlider',
@@ -125,8 +126,8 @@
         let placementMap = { top: 'bottom', bottom: 'top' };
         let placement = this.popper._popper.getAttribute('x-placement').split('-')[0];
         let origin = placementMap[placement];
-        this.popper._popper.classList.add(placement);
-        this.popper._popper.classList.remove(placementMap[placement]);
+        addClass(this.popper._popper, placement);
+        removeClass(this.popper._popper, placementMap[placement]);
         this.popper._popper.style.transformOrigin = `center ${ origin }`;
       },
 

+ 14 - 13
packages/steps/src/step.vue

@@ -12,17 +12,18 @@
         <i class="el-step__line-inner" :style="lineStyle"></i>
       </div>
 
-      <slot
-        v-if="currentStatus !== 'success' && currentStatus !== 'error'"
-        name="icon">
-        <i v-if="icon" :class="['el-step__icon', 'el-icon-' + icon]"></i>
-        <div v-else>{{ index + 1 }}</div>
-      </slot>
-      <i
-        v-else
-        class="el-step__icon"
-        :class="['el-icon-' + (currentStatus === 'success' ? 'check' : 'close')]">
-      </i>
+      <span class="el-step__icon">
+        <slot
+          v-if="currentStatus !== 'success' && currentStatus !== 'error'"
+          name="icon">
+          <i v-if="icon" :class="['el-icon-' + icon]"></i>
+          <div v-else>{{ index + 1 }}</div>
+        </slot>
+        <i
+          v-else
+          :class="['el-icon-' + (currentStatus === 'success' ? 'check' : 'close')]">
+        </i>
+      </span>
     </div>
     <div
       class="el-step__main"
@@ -59,8 +60,8 @@ export default {
   data() {
     return {
       index: -1,
-      style: { width: 0, height: 0 },
-      lineStyle: { width: 0, height: 0 },
+      style: { width: '', height: '' },
+      lineStyle: { width: '', height: '' },
       mainOffset: 0,
       currentStatus: this.status
     };

+ 0 - 5
packages/table/src/table-body.js

@@ -155,11 +155,6 @@ export default {
       grid.$emit('rowclick', row, event);
     },
 
-    handleCreate(vm) {
-      document.body.appendChild(vm.$refs.popper);
-      vm.updatePopper();
-    },
-
     $getPropertyText(row, property, columnId) {
       let grid = this.$parent;
       const column = getColumnById(grid, columnId);

+ 0 - 1
packages/table/src/table-column.js

@@ -190,7 +190,6 @@ export default {
 
       return _self.showTooltipWhenOverflow
         ? <el-tooltip
-            on-created={ this.handleCreate }
             effect={ this.effect }
             placement="top"
             disabled={ this.tooltipDisabled }>

+ 0 - 0
packages/theme-default/src/dropdown-menu.css


+ 1 - 1
packages/theme-default/src/icon.css

@@ -17,7 +17,7 @@
     font-variant: normal;
     text-transform: none;
     line-height: 1;
-    vertical-align: bottom;
+    vertical-align: baseline;
     display: inline-block;
 
     /* Better Font Rendering =========== */

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

@@ -9,6 +9,7 @@
 @import "./radio.css";
 @import "./switch.css";
 @import "./dropdown.css";
+@import "./dropdown-menu.css";
 @import "./loading.css";
 @import "./dialog.css";
 @import "./table.css";

+ 39 - 1
packages/theme-default/src/select-dropdown.css

@@ -4,7 +4,6 @@
 @component-namespace el {
 
   @b select-dropdown {
-    width: 100%;
     position: absolute;
     z-index: 1001;
     border: solid 1px #d3dce6;
@@ -12,5 +11,44 @@
     background-color: #fff;
     box-shadow: 0 2px 4px rgba(0, 0, 0, .12), 0 0 6px rgba(0, 0, 0, .04);
     box-sizing: border-box;
+    margin: 5px 0;
+
+    @when multiple {
+      & .el-select-dropdown__item.selected {
+        color: #20A0FF;
+        background-color: #fff;
+
+        &.hover {
+          background-color: #E5E9F2;
+        }
+
+        &::after {
+          position: absolute;
+          right: 10px;
+          font-family: 'element-icons';
+          content: "\E608";
+          font-size: 11px;
+          -webkit-font-smoothing: antialiased;
+          -moz-osx-font-smoothing: grayscale;
+        }
+      }
+    }
+  }
+
+  @b select-dropdown__nodata {
+    padding: 10px 0;
+    margin: 0;
+    text-align: center;
+    color: #999;
+  }
+
+  @b select-dropdown__list {
+    list-style: none;
+    padding: 6px 0;
+    margin: 0;
+    width: 100%;
+    max-height: 274px;
+    box-sizing: border-box;
+    overflow-y: auto;
   }
 }

+ 0 - 42
packages/theme-default/src/select.css

@@ -25,27 +25,6 @@
       }
     }
 
-    & .el-select-dropdown {
-      margin: 5px 0;
-
-      & p.el-select-dropdown__nodata {
-        padding: 10px 0;
-        margin: 0;
-        text-align: center;
-        color: #999;
-      }
-    }
-
-    & .el-select-dropdown__list {
-      list-style: none;
-      padding: 6px 0;
-      margin: 0;
-      width: 100%;
-      max-height: 274px;
-      box-sizing: border-box;
-      overflow-y: auto;
-    }
-
     & .el-input {
       & .el-input__icon {
         color: #c0ccda;
@@ -95,27 +74,6 @@
       }
     }
 
-    @when multiple {
-      & .el-select-dropdown__item.selected {
-        color: #20A0FF;
-        background-color: #fff;
-
-        &.hover {
-          background-color: #E5E9F2;
-        }
-
-        &::after {
-          position: absolute;
-          right: 10px;
-          font-family: 'element-icons';
-          content: "\E608";
-          font-size: 11px;
-          -webkit-font-smoothing: antialiased;
-          -moz-osx-font-smoothing: grayscale;
-        }
-      }
-    }
-
     @e input {
       border: none;
       outline: none;

+ 5 - 5
packages/theme-default/src/step.css

@@ -19,10 +19,6 @@
       & .el-step__main {
         padding-left: 10px;
       }
-
-      & .el-step__description {
-        width: 300px;
-      }
     }
 
     @e line {
@@ -69,6 +65,10 @@
 
     @e icon {
       line-height: 28px;
+
+      > * {
+        line-height: inherit;
+      }
     }
 
     @e head {
@@ -149,7 +149,7 @@
 
     @e title {
       font-size: 14px;
-      margin-top: 5px;
+      line-height: 32px;
       display: inline-block;
 
       @when process {

+ 1 - 2
packages/theme-default/src/time-select.css

@@ -3,9 +3,8 @@
 @import "../../date-picker/src/css/vars.css";
 
 .time-select {
-  min-width: 200px;
   margin: 5px 0;
-  width: 100%;
+  min-width: 0;
 }
 
 .time-select .el-picker-panel__content {

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

@@ -10,9 +10,9 @@
       <span
         v-if="node.loading"
         class="el-tree-node__icon el-icon-loading"
-      >  
+      >
       </span>
-      <span class="el-tree-node__label">{{ node.label }}</span>
+      <span class="el-tree-node__label" v-html="node.label"></span>
     </div>
     <collapse-transition>
       <div class="el-tree-node__children"
@@ -94,9 +94,9 @@
         }
       },
 
-      handleCheckChange(checked) {
+      handleCheckChange(ev) {
         if (!this.node.indeterminate) {
-          this.node.setChecked(checked, true);
+          this.node.setChecked(ev.target.checked, true);
         }
       }
     },

+ 4 - 8
packages/upload/src/index.vue

@@ -31,18 +31,13 @@ export default {
         };
       }
     },
-    multiple: {
-      type: Boolean,
-      default: false
-    },
+    data: Object,
+    multiple: Boolean,
     name: {
       type: String,
       default: 'file'
     },
-    withCredentials: {
-      type: Boolean,
-      default: false
-    },
+    withCredentials: Boolean,
     thumbnailMode: Boolean,
     showUploadList: {
       type: Boolean,
@@ -179,6 +174,7 @@ export default {
         'with-credentials': this.withCredentials,
         headers: this.headers,
         name: this.name,
+        data: this.data,
         accept: this.thumbnailMode ? 'image/*' : this.accept,
         'on-start': this.handleStart,
         'on-progress': this.handleProgress,

+ 2 - 0
packages/upload/src/upload.vue

@@ -34,6 +34,7 @@ export default {
       type: String,
       default: 'file'
     },
+    data: Object,
     headers: Object,
     withCredentials: Boolean,
     multiple: Boolean,
@@ -133,6 +134,7 @@ export default {
         headers: this.headers,
         withCredentials: this.withCredentials,
         file: file,
+        data: this.data,
         filename: this.name,
         onProgress: e => {
           this.onProgress(e, file);

+ 1 - 1
src/index.js

@@ -127,7 +127,7 @@ if (typeof window !== 'undefined' && window.Vue) {
 };
 
 module.exports = {
-  version: '1.0.0-rc.5',
+  version: '1.0.0-rc.6',
   install,
   SelectDropdown,
   Pagination,

+ 1 - 1
src/mixins/emitter.js

@@ -12,7 +12,7 @@ function broadcast(componentName, eventName, params) {
 export default {
   methods: {
     dispatch(componentName, eventName, params) {
-      var parent = this.$parent;
+      var parent = this.$parent || this.$root;
       var name = parent.$options.componentName;
 
       while (parent && (!name || name !== componentName)) {

+ 8 - 3
src/utils/clickoutside.js

@@ -1,3 +1,5 @@
+import { on, off } from 'wind-dom/src/event';
+
 /**
  * v-clickoutside
  * @desc 点击元素外面才会触发的事件
@@ -11,7 +13,10 @@ const clickoutsideContext = '@@clickoutsideContext';
 export default {
   bind(el, binding, vnode) {
     const documentHandler = function(e) {
-      if (!vnode.context || el.contains(e.target)) return;
+      if (!vnode.context ||
+        el.contains(e.target) ||
+        !vnode.context.popperElm ||
+        vnode.context.popperElm.contains(e.target)) return;
       if (binding.expression) {
         vnode.context[el[clickoutsideContext].methodName]();
       } else {
@@ -23,7 +28,7 @@ export default {
       methodName: binding.expression,
       bindingFn: binding.value
     };
-    document.addEventListener('click', documentHandler);
+    on(document, 'click', documentHandler);
   },
 
   update(el, binding) {
@@ -32,7 +37,7 @@ export default {
   },
 
   unbind(el) {
-    document.removeEventListener('click', el[clickoutsideContext].documentHandler);
+    off(document, 'click', el[clickoutsideContext].documentHandler);
   },
 
   install(Vue) {

+ 27 - 30
src/utils/vue-popper.js

@@ -1,4 +1,5 @@
 import PopperJS from './popper';
+import { PopupManager } from 'vue-popup';
 
 /**
  * @param {HTMLElement} [reference=$refs.reference] - The reference element used to position the popper.
@@ -26,10 +27,16 @@ export default {
     value: Boolean,
     visibleArrow: Boolean,
     transition: String,
+    appendToBody: {
+      type: Boolean,
+      default: true
+    },
     options: {
       type: Object,
       default() {
-        return {};
+        return {
+          gpuAcceleration: false
+        };
       }
     }
   },
@@ -62,59 +69,48 @@ export default {
       }
 
       const options = this.options;
-      const popper = this.popper || this.$refs.popper;
-      const reference = this.reference || this.$refs.reference || this.$slots.reference[0].elm;
+      const popper = this.popperElm = this.popperElm || this.popper || this.$refs.popper;
+      const reference = this.referenceElm = this.referenceElm || this.reference || this.$refs.reference || this.$slots.reference[0].elm;
 
       if (!popper || !reference) return;
-      if (this.visibleArrow) {
-        this.appendArrow(popper);
-      }
 
+      if (this.visibleArrow) this.appendArrow(popper);
+      if (this.appendToBody) document.body.appendChild(this.popperElm);
       if (this.popperJS && this.popperJS.hasOwnProperty('destroy')) {
         this.popperJS.destroy();
       }
 
       options.placement = this.placement;
       options.offset = this.offset;
-
-      this.$nextTick(() => {
-        this.popperJS = new PopperJS(
-          reference,
-          popper,
-          options
-        );
-        this.popperJS.onCreate(popper => {
-          this.resetTransformOrigin(popper);
-          this.$emit('created', this);
-        });
+      this.popperJS = new PopperJS(reference, popper, options);
+      this.popperJS.onCreate(_ => {
+        this.$emit('created', this);
+        this.resetTransformOrigin();
       });
+      this.popperJS._popper.style.zIndex = PopupManager.nextZIndex();
     },
 
     updatePopper() {
-      if (this.popperJS) {
-        this.popperJS.update();
-      } else {
-        this.createPopper();
-      }
+      this.popperJS ? this.popperJS.update() : this.createPopper();
     },
 
     doDestroy() {
-      if (this.showPopper) return;
+      if (this.showPopper || !this.popperJS) return;
       this.popperJS.destroy();
       this.popperJS = null;
     },
 
     destroyPopper() {
       if (this.popperJS) {
-        this.resetTransformOrigin(this.popperJS);
+        this.resetTransformOrigin();
       }
     },
 
-    resetTransformOrigin(popper) {
+    resetTransformOrigin() {
       let placementMap = { top: 'bottom', bottom: 'top', left: 'right', right: 'left' };
-      let placement = popper._popper.getAttribute('x-placement').split('-')[0];
+      let placement = this.popperJS._popper.getAttribute('x-placement').split('-')[0];
       let origin = placementMap[placement];
-      popper._popper.style.transformOrigin = ['top', 'bottom'].indexOf(placement) > -1 ? `center ${ origin }` : `${ origin } center`;
+      this.popperJS._popper.style.transformOrigin = ['top', 'bottom'].indexOf(placement) > -1 ? `center ${ origin }` : `${ origin } center`;
     },
 
     appendArrow(element) {
@@ -144,8 +140,9 @@ export default {
   },
 
   beforeDestroy() {
-    if (this.popperJS) {
-      this.popperJS.destroy();
-    }
+    this.doDestroy();
+    this.popperElm &&
+    document.body.contains(this.popperElm) &&
+    document.body.removeChild(this.popperElm);
   }
 };

+ 9 - 0
test/unit/.eslintrc

@@ -0,0 +1,9 @@
+{
+  "env": {
+    "mocha": true
+  },
+  "globals": {
+    "expect": true,
+    "sinon": true
+  }
+}

+ 14 - 0
test/unit/index.js

@@ -0,0 +1,14 @@
+// Polyfill fn.bind() for PhantomJS
+/* eslint-disable no-extend-native */
+Function.prototype.bind = require('function-bind');
+require('packages/theme-default/src/index.css');
+
+// require all test files (files that ends with .spec.js)
+const testsContext = require.context('./specs', true, /\.spec$/);
+testsContext.keys().forEach(testsContext);
+
+// require all src files except main.js for coverage.
+// you can also change this to match only the subset of files that
+// you want coverage for.
+const srcContext = require.context('../../src', true, /^\.\/(?!main(\.js)?$)/);
+srcContext.keys().forEach(srcContext);

+ 31 - 0
test/unit/karma.conf.js

@@ -0,0 +1,31 @@
+var webpackConfig = require('../../build/cooking.test');
+
+// no need for app entry during tests
+delete webpackConfig.entry;
+
+module.exports = function(config) {
+  config.set({
+    // to run in additional browsers:
+    // 1. install corresponding karma launcher
+    //    http://karma-runner.github.io/0.13/config/browsers.html
+    // 2. add it to the `browsers` array below.
+    browsers: ['PhantomJS'],
+    frameworks: ['mocha', 'sinon-chai'],
+    reporters: ['spec', 'coverage'],
+    files: ['./index.js'],
+    preprocessors: {
+      './index.js': ['webpack', 'sourcemap']
+    },
+    webpack: webpackConfig,
+    webpackMiddleware: {
+      noInfo: true
+    },
+    coverageReporter: {
+      dir: './coverage',
+      reporters: [
+        { type: 'lcov', subdir: '.' },
+        { type: 'text-summary' }
+      ]
+    }
+  });
+};

+ 60 - 0
test/unit/specs/time-select.spec.js

@@ -0,0 +1,60 @@
+import { createTest, createVue } from '../util';
+import TimeSelect from 'packages/time-select';
+import Vue from 'vue';
+
+describe('TimeSelect', () => {
+  it('should render correct contents', done => {
+    const vm = createTest(TimeSelect, {
+      pickerOptions: {
+        start: '08:30',
+        step: '00:15',
+        end: '18:30'
+      },
+      placeholder: 'test'
+    }, true);
+    vm.$el.querySelector('input').blur();
+    vm.$el.querySelector('input').focus();
+    vm.$el.querySelector('input').blur();
+
+    Vue.nextTick(_ => {
+      expect(vm.picker.start).to.equal('08:30');
+      expect(vm.picker.end).to.equal('18:30');
+      expect(vm.picker.step).to.equal('00:15');
+      expect(vm.$el.querySelector('input').getAttribute('placeholder')).to.equal('test');
+      done();
+    });
+  });
+
+  it('click time', done => {
+    const vm = createVue({
+      template: `
+        <div>
+          <el-time-select ref="compo" v-model="value">
+          </el-time-select>
+        </div>
+      `,
+
+      data() {
+        return {
+          value: ''
+        };
+      }
+    }, true);
+
+    vm.$el.querySelector('input').blur();
+    vm.$el.querySelector('input').focus();
+    vm.$el.querySelector('input').blur();
+
+    Vue.nextTick(_ => {
+      const items = vm.$refs.compo.picker.$el.querySelectorAll('.time-select-item');
+      const target = items[4];
+      const time = target.textContent.trim();
+
+      target.click();
+      Vue.nextTick(_ => {
+        expect(vm.value).to.equal(time);
+        done();
+      });
+    });
+  });
+});

+ 40 - 0
test/unit/util.js

@@ -0,0 +1,40 @@
+import Vue from 'vue/dist/vue';
+import Element from 'main/index.js';
+
+Vue.use(Element);
+
+let id = 0;
+
+const createElm = function() {
+  const elm = document.createElement('div');
+
+  elm.id = 'app' + ++id;
+  document.body.appendChild(elm);
+
+  return elm;
+};
+
+/**
+ * 创建一个 Vue 的实例对象
+ * @param  {Object}  Compo   组件配置
+ * @param  {Boolean=false} mounted 是否添加到 DOM 上
+ * @return {Object} vm
+ */
+exports.createVue = function(Compo, mounted = false) {
+  const elm = createElm();
+  return new Vue(Compo).$mount(mounted === false ? null : elm);
+};
+
+/**
+ * 创建一个测试组件实例
+ * @link http://vuejs.org/guide/unit-testing.html#Writing-Testable-Components
+ * @param  {Object}  Compo          - 组件对象
+ * @param  {Object}  propsData      - props 数据
+ * @param  {Boolean=false} mounted  - 是否添加到 DOM 上
+ * @return {Object} vm
+ */
+exports.createTest = function(Compo, propsData = {}, mounted = false) {
+  const elm = createElm();
+  const Ctor = Vue.extend(Compo);
+  return new Ctor({ propsData }).$mount(mounted === false ? null : elm);
+};