Browse Source

Merge pull request #1982 from QingWei-Li/feat/ssr

SSR feature
baiyaaaaa 8 years ago
parent
commit
769ab5aa8d
49 changed files with 737 additions and 76 deletions
  1. 1 1
      build/deploy-ci.sh
  2. 2 2
      examples/docs/en-US/button.md
  3. 2 2
      examples/docs/en-US/pagination.md
  4. 1 1
      examples/docs/zh-CN/button.md
  5. 1 1
      examples/docs/zh-CN/pagination.md
  6. 1 3
      package.json
  7. 0 1
      packages/date-picker/package.json
  8. 1 1
      packages/date-picker/src/basic/date-table.vue
  9. 1 1
      packages/date-picker/src/basic/month-table.vue
  10. 1 1
      packages/date-picker/src/basic/year-table.vue
  11. 1 0
      packages/date-picker/src/picker.vue
  12. 1 1
      packages/dialog/src/component.vue
  13. 0 1
      packages/input-number/package.json
  14. 6 11
      packages/input-number/src/input-number.vue
  15. 2 3
      packages/input/src/input.vue
  16. 0 1
      packages/loading/package.json
  17. 2 1
      packages/loading/src/directive.js
  18. 1 0
      packages/loading/src/index.js
  19. 0 1
      packages/menu/package.json
  20. 0 1
      packages/message-box/package.json
  21. 1 0
      packages/message-box/src/main.js
  22. 2 2
      packages/message-box/src/main.vue
  23. 2 1
      packages/message/src/main.js
  24. 2 1
      packages/notification/src/main.js
  25. 0 1
      packages/popover/package.json
  26. 1 1
      packages/popover/src/main.vue
  27. 0 1
      packages/rate/package.json
  28. 1 1
      packages/rate/src/main.vue
  29. 1 2
      packages/select/package.json
  30. 2 1
      packages/select/src/select.vue
  31. 0 1
      packages/slider/package.json
  32. 1 1
      packages/slider/src/main.vue
  33. 2 1
      packages/table/src/dropdown.js
  34. 3 1
      packages/table/src/table-header.js
  35. 1 1
      packages/table/src/util.js
  36. 1 3
      packages/theme-default/package.json
  37. 33 0
      packages/theme-default/src/common/popup.css
  38. 1 1
      packages/theme-default/src/dialog.css
  39. 1 1
      packages/theme-default/src/message-box.css
  40. 2 2
      packages/upload/src/iframe-upload.vue
  41. 1 1
      packages/upload/src/index.vue
  42. 3 2
      src/utils/clickoutside.js
  43. 178 0
      src/utils/dom.js
  44. 297 0
      src/utils/popup/index.js
  45. 165 0
      src/utils/popup/popup-manager.js
  46. 7 3
      src/utils/resize-event.js
  47. 4 2
      src/utils/vue-popper.js
  48. 1 1
      test/unit/specs/mixin.vue-popup.spec.js
  49. 0 10
      yarn.lock

+ 1 - 1
build/deploy-ci.sh

@@ -42,7 +42,7 @@ if [ "$TRAVIS_TAG" ]; then
   export SUB_FOLDER=$(echo "$TRAVIS_TAG" | grep -o -E "\d+\.\d+")
   echo $SUB_FOLDER
 
-  SUB_FOLDER='1.0'
+  SUB_FOLDER='1.1'
   mkdir $SUB_FOLDER
   rm -rf *.js *.css *.map static
   rm -rf $SUB_FOLDER/**

+ 2 - 2
examples/docs/en-US/button.md

@@ -1,5 +1,5 @@
 <script>
-  import { addClass } from 'wind-dom/src/class';
+  import { addClass } from 'element-ui/src/utils/dom';
   export default {
     data() {
       return {
@@ -78,7 +78,7 @@ Different colors represent different meanings.
 ```
 :::
 
-### Icon Button 
+### Icon Button
 
 Use icons to add more meaning to Button. You can use icon alone to save some space, or with text together.
 

+ 2 - 2
examples/docs/en-US/pagination.md

@@ -1,4 +1,4 @@
-## Pagination 
+## Pagination
 
 If you have too much data to display in one page, use pagination.
 
@@ -117,7 +117,7 @@ Add more modules based on your scenario.
 ```
 :::
 <script>
-  import { addClass } from 'wind-dom/src/class';
+  import { addClass } from 'element-ui/src/utils/dom';
   export default {
     data() {
       return {

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

@@ -1,5 +1,5 @@
 <script>
-  import { addClass } from 'wind-dom/src/class';
+  import { addClass } from 'element-ui/src/utils/dom';
   export default {
     data() {
       return {

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

@@ -117,7 +117,7 @@
 ```
 :::
 <script>
-  import { addClass } from 'wind-dom/src/class';
+  import { addClass } from 'element-ui/src/utils/dom';
   export default {
     methods: {
       handleSizeChange(val) {

+ 1 - 3
package.json

@@ -47,9 +47,7 @@
     "async-validator": "^1.6.6",
     "babel-helper-vue-jsx-merge-props": "^2.0.0",
     "deepmerge": "^1.2.0",
-    "throttle-debounce": "^1.0.1",
-    "vue-popup": "^0.2.14",
-    "wind-dom": "0.0.3"
+    "throttle-debounce": "^1.0.1"
   },
   "peerDependencies": {
     "vue": "^2.1.6"

+ 0 - 1
packages/date-picker/package.json

@@ -12,6 +12,5 @@
   "author": "long.zhang@ele.me",
   "license": "MIT",
   "dependencies": {
-    "wind-dom": "0.0.3"
   }
 }

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

@@ -32,7 +32,7 @@
 
 <script>
   import { getFirstDayOfMonth, getDayCountOfMonth, getWeekNumber, getStartDateOfMonth, DAY_DURATION } from '../util';
-  import { hasClass } from 'wind-dom/src/class';
+  import { hasClass } from 'element-ui/src/utils/dom';
   import Locale from 'element-ui/src/mixins/locale';
 
   const clearHours = function(time) {

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

@@ -49,7 +49,7 @@
 
 <script type="text/babel">
   import Locale from 'element-ui/src/mixins/locale';
-  import { hasClass } from 'wind-dom/src/class';
+  import { hasClass } from 'element-ui/src/utils/dom';
 
   export default {
     props: {

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

@@ -44,7 +44,7 @@
 </template>
 
 <script type="text/babel">
-  import { hasClass } from 'wind-dom/src/class';
+  import { hasClass } from 'element-ui/src/utils/dom';
 
   export default {
     props: {

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

@@ -375,6 +375,7 @@ export default {
     },
 
     showPicker() {
+      if (this.$isServer) return;
       if (!this.picker) {
         this.panel.defaultValue = this.internalValue;
         this.picker = new Vue(this.panel).$mount(document.createElement('div'));

+ 1 - 1
packages/dialog/src/component.vue

@@ -22,7 +22,7 @@
 </template>
 
 <script>
-  import Popup from 'vue-popup';
+  import Popup from 'element-ui/src/utils/popup';
 
   export default {
     name: 'el-dialog',

+ 0 - 1
packages/input-number/package.json

@@ -13,6 +13,5 @@
   "license": "MIT",
   "repository": "https://github.com/ElemeFE/element/tree/master/packages/input-number",
   "dependencies": {
-    "wind-dom": "0.0.3"
   }
 }

+ 6 - 11
packages/input-number/src/input-number.vue

@@ -22,7 +22,7 @@
         </template>
         <template slot="append" v-if="$slots.append">
           <slot name="append"></slot>
-        </template> 
+        </template>
     </el-input>
     <span
       v-if="controls"
@@ -46,7 +46,7 @@
 </template>
 <script>
   import ElInput from 'element-ui/packages/input';
-  import { once, on } from 'wind-dom/src/event';
+  import { once, on } from 'element-ui/src/utils/dom';
 
   export default {
     name: 'ElInputNumber',
@@ -79,11 +79,8 @@
           let interval = null;
           let startTime;
 
-          const handler = () => {
-            vnode.context[binding.expression]();
-          };
-
-          const clear = function() {
+          const handler = () => vnode.context[binding.expression]();
+          const clear = () => {
             if (new Date() - startTime < 100) {
               handler();
             }
@@ -91,12 +88,10 @@
             interval = null;
           };
 
-          on(el, 'mousedown', function() {
+          on(el, 'mousedown', () => {
             startTime = new Date();
             once(document, 'mouseup', clear);
-            interval = setInterval(function() {
-              handler();
-            }, 100);
+            interval = setInterval(handler, 100);
           });
         }
       }

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

@@ -117,10 +117,9 @@
         this.$refs.input.select();
       },
       resizeTextarea() {
+        if (this.$isServer) return;
         var { autosize, type } = this;
-        if (!autosize || type !== 'textarea') {
-          return;
-        }
+        if (!autosize || type !== 'textarea') return;
         const minRows = autosize.minRows;
         const maxRows = autosize.maxRows;
 

+ 0 - 1
packages/loading/package.json

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

+ 2 - 1
packages/loading/src/directive.js

@@ -1,8 +1,9 @@
 import Vue from 'vue';
-import { addClass, removeClass } from 'wind-dom/src/class';
+import { addClass, removeClass } from 'element-ui/src/utils/dom';
 let Mask = Vue.extend(require('./loading.vue'));
 
 exports.install = Vue => {
+  if (Vue.prototype.$isServer) return;
   let toggleLoading = (el, binding) => {
     if (binding.value) {
       Vue.nextTick(() => {

+ 1 - 0
packages/loading/src/index.js

@@ -61,6 +61,7 @@ const addStyle = (options, parent, instance) => {
 };
 
 const Loading = (options = {}) => {
+  if (Vue.prototype.$isServer) return;
   options = merge({}, defaults, options);
   if (typeof options.target === 'string') {
     options.target = document.querySelector(options.target);

+ 0 - 1
packages/menu/package.json

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

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

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

+ 1 - 0
packages/message-box/src/main.js

@@ -102,6 +102,7 @@ const showNextMsg = () => {
 };
 
 const MessageBox = function(options, callback) {
+  if (Vue.prototype.$isServer) return;
   if (typeof options === 'string') {
     options = {
       message: options

+ 2 - 2
packages/message-box/src/main.vue

@@ -24,11 +24,11 @@
 </template>
 
 <script type="text/babel">
-  import Popup from 'vue-popup';
+  import Popup from 'element-ui/src/utils/popup';
   import Locale from 'element-ui/src/mixins/locale';
   import ElInput from 'element-ui/packages/input';
   import ElButton from 'element-ui/packages/button';
-  import { addClass, removeClass } from 'wind-dom/src/class';
+  import { addClass, removeClass } from 'element-ui/src/utils/dom';
   import { t } from 'element-ui/src/locale';
 
   let typeMap = {

+ 2 - 1
packages/message/src/main.js

@@ -1,5 +1,5 @@
 import Vue from 'vue';
-import { PopupManager } from 'vue-popup';
+import { PopupManager } from 'element-ui/src/utils/popup';
 let MessageConstructor = Vue.extend(require('./main.vue'));
 
 let instance;
@@ -7,6 +7,7 @@ let instances = [];
 let seed = 1;
 
 var Message = function(options) {
+  if (Vue.prototype.$isServer) return;
   options = options || {};
   if (typeof options === 'string') {
     options = {

+ 2 - 1
packages/notification/src/main.js

@@ -1,5 +1,5 @@
 import Vue from 'vue';
-import { PopupManager } from 'vue-popup';
+import { PopupManager } from 'element-ui/src/utils/popup';
 let NotificationConstructor = Vue.extend(require('./main.vue'));
 
 let instance;
@@ -7,6 +7,7 @@ let instances = [];
 let seed = 1;
 
 var Notification = function(options) {
+  if (Vue.prototype.$isServer) return;
   options = options || {};
   let userOnClose = options.onClose;
   let id = 'notification_' + seed++;

+ 0 - 1
packages/popover/package.json

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

+ 1 - 1
packages/popover/src/main.vue

@@ -17,7 +17,7 @@
 
 <script>
 import Popper from 'element-ui/src/utils/vue-popper';
-import { on, off } from 'wind-dom/src/event';
+import { on, off } from 'element-ui/src/utils/dom';
 
 export default {
   name: 'el-popover',

+ 0 - 1
packages/rate/package.json

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

+ 1 - 1
packages/rate/src/main.vue

@@ -24,7 +24,7 @@
 </template>
 
 <script type="text/babel">
-  import { hasClass } from 'wind-dom/src/class';
+  import { hasClass } from 'element-ui/src/utils/dom';
 
   export default {
     name: 'el-rate',

+ 1 - 2
packages/select/package.json

@@ -12,7 +12,6 @@
   "license": "MIT",
   "repository": "https://github.com/ElemeFE/element/tree/master/packages/select",
   "devDependencies": {
-    "throttle-debounce": "^1.0.1",
-    "wind-dom": "0.0.3"
+    "throttle-debounce": "^1.0.1"
   }
 }

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

@@ -94,7 +94,7 @@
   import ElScrollbar from 'element-ui/packages/scrollbar';
   import debounce from 'throttle-debounce/debounce';
   import Clickoutside from 'element-ui/src/utils/clickoutside';
-  import { addClass, removeClass, hasClass } from 'wind-dom/src/class';
+  import { addClass, removeClass, hasClass } from 'element-ui/src/utils/dom';
   import { addResizeListener, removeResizeListener } from 'element-ui/src/utils/resize-event';
   import { t } from 'element-ui/src/locale';
   const sizeMap = {
@@ -303,6 +303,7 @@
       },
 
       options(val) {
+        if (this.$isServer) return;
         this.optionsAllDisabled = val.length === val.filter(item => item.disabled === true).length;
         if (this.multiple) {
           this.resetInputHeight();

+ 0 - 1
packages/slider/package.json

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

+ 1 - 1
packages/slider/src/main.vue

@@ -37,7 +37,7 @@
 <script type="text/babel">
   import ElInputNumber from 'element-ui/packages/input-number';
   import ElTooltip from 'element-ui/packages/tooltip';
-  import { getStyle } from 'wind-dom/src/style';
+  import { getStyle } from 'element-ui/src/utils/dom';
 
   export default {
     name: 'ElSlider',

+ 2 - 1
packages/table/src/dropdown.js

@@ -1,6 +1,7 @@
+import Vue from 'vue';
 var dropdowns = [];
 
-document.addEventListener('click', function(event) {
+!Vue.prototype.$isServer && document.addEventListener('click', function(event) {
   dropdowns.forEach(function(dropdown) {
     var target = event.target;
     if (!dropdown || !dropdown.$el) return;

+ 3 - 1
packages/table/src/table-header.js

@@ -226,7 +226,7 @@ export default {
         filterPanel.table = table;
         filterPanel.cell = cell;
         filterPanel.column = column;
-        filterPanel.$mount(document.createElement('div'));
+        !this.$isServer && filterPanel.$mount(document.createElement('div'));
       }
 
       setTimeout(() => {
@@ -239,6 +239,7 @@ export default {
     },
 
     handleMouseDown(event, column) {
+      if (this.$isServer) return;
       if (column.children && column.children.length > 0) return;
       /* istanbul ignore if */
       if (this.draggingColumn && this.border) {
@@ -329,6 +330,7 @@ export default {
     },
 
     handleMouseOut() {
+      if (this.$isServer) return;
       document.body.style.cursor = '';
     },
 

+ 1 - 1
packages/table/src/util.js

@@ -74,7 +74,7 @@ export const getColumnByCell = function(table, cell) {
   return null;
 };
 
-const isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
+const isFirefox = typeof navigator !== 'undefined' && navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
 
 export const mousewheel = function(element, callback) {
   if (element && element.addEventListener) {

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

@@ -31,7 +31,5 @@
     "gulp-postcss": "^6.1.1",
     "postcss-salad": "^1.0.5"
   },
-  "dependencies": {
-    "vue-popup": "^0.2.9"
-  }
+  "dependencies": {}
 }

+ 33 - 0
packages/theme-default/src/common/popup.css

@@ -0,0 +1,33 @@
+.v-modal-enter {
+  animation: v-modal-in .2s ease;
+}
+
+.v-modal-leave {
+  animation: v-modal-out .2s ease forwards;
+}
+
+@keyframes v-modal-in {
+  0% {
+    opacity: 0;
+  }
+  100% {
+  }
+}
+
+@keyframes v-modal-out {
+  0% {
+  }
+  100% {
+    opacity: 0;
+  }
+}
+
+.v-modal {
+  position: fixed;
+  left: 0;
+  top: 0;
+  width: 100%;
+  height: 100%;
+  opacity: 0.5;
+  background: #000;
+}

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

@@ -1,6 +1,6 @@
 @charset "UTF-8";
 @import "./common/var.css";
-@import "vue-popup/lib/popup.css";
+@import "./common/popup.css";
 
 @component-namespace el {
 

+ 1 - 1
packages/theme-default/src/message-box.css

@@ -1,8 +1,8 @@
 @charset "UTF-8";
 @import "./common/var.css";
+@import "./common/popup.css";
 @import "./button.css";
 @import "./input.css";
-@import "vue-popup/lib/popup.css";
 
 @component-namespace el {
 

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

@@ -113,7 +113,7 @@ export default {
   },
 
   mounted() {
-    window.addEventListener('message', (event) => {
+    !this.$isServer && window.addEventListener('message', (event) => {
       var targetOrigin = new URL(this.action).origin;
       if (event.origin !== targetOrigin) {
         return false;
@@ -158,7 +158,7 @@ export default {
             on-change={this.handleChange}
             accept={this.accept}>
           </input>
-          <input type="hidden" name="documentDomain" value={document.domain} />
+          <input type="hidden" name="documentDomain" value={ this.$isServer ? '' : document.domain } />
           <span ref="data"></span>
          </form>
         {!this.showCover ? this.$slots.default : cover}

+ 1 - 1
packages/upload/src/index.vue

@@ -209,7 +209,7 @@ export default {
       ref: 'upload-inner'
     };
 
-    var uploadComponent = typeof FormData !== 'undefined'
+    var uploadComponent = this.$isServer ? '' : typeof FormData !== 'undefined'
       ? <upload {...props}>{this.$slots.default}</upload>
       : <iframeUpload {...props}>{this.$slots.default}</iframeUpload>;
 

+ 3 - 2
src/utils/clickoutside.js

@@ -1,9 +1,10 @@
-import { on } from 'wind-dom/src/event';
+import Vue from 'vue';
+import { on } from 'element-ui/src/utils/dom';
 
 const nodeList = [];
 const ctx = '@@clickoutsideContext';
 
-on(document, 'click', e => {
+!Vue.prototype.$isServer && on(document, 'click', e => {
   nodeList.forEach(node => node[ctx].documentHandler(e));
 });
 /**

+ 178 - 0
src/utils/dom.js

@@ -0,0 +1,178 @@
+/* istanbul ignore next */
+
+import Vue from 'vue';
+
+const isServer = Vue.prototype.$isServer;
+const SPECIAL_CHARS_REGEXP = /([\:\-\_]+(.))/g;
+const MOZ_HACK_REGEXP = /^moz([A-Z])/;
+const ieVersion = isServer ? 0 : Number(document.documentMode);
+
+/* istanbul ignore next */
+const trim = function(string) {
+  return (string || '').replace(/^[\s\uFEFF]+|[\s\uFEFF]+$/g, '');
+};
+/* istanbul ignore next */
+const camelCase = function(name) {
+  return name.replace(SPECIAL_CHARS_REGEXP, function(_, separator, letter, offset) {
+    return offset ? letter.toUpperCase() : letter;
+  }).replace(MOZ_HACK_REGEXP, 'Moz$1');
+};
+
+/* istanbul ignore next */
+export const on = (function() {
+  if (!isServer && document.addEventListener) {
+    return function(element, event, handler) {
+      if (element && event && handler) {
+        element.addEventListener(event, handler, false);
+      }
+    };
+  } else {
+    return function(element, event, handler) {
+      if (element && event && handler) {
+        element.attachEvent('on' + event, handler);
+      }
+    };
+  }
+})();
+
+/* istanbul ignore next */
+export const off = (function() {
+  if (!isServer && document.removeEventListener) {
+    return function(element, event, handler) {
+      if (element && event) {
+        element.removeEventListener(event, handler, false);
+      }
+    };
+  } else {
+    return function(element, event, handler) {
+      if (element && event) {
+        element.detachEvent('on' + event, handler);
+      }
+    };
+  }
+})();
+
+/* istanbul ignore next */
+export const once = function(el, event, fn) {
+  var listener = function() {
+    if (fn) {
+      fn.apply(this, arguments);
+    }
+    off(el, event, listener);
+  };
+  on(el, event, listener);
+};
+
+/* istanbul ignore next */
+export function hasClass(el, cls) {
+  if (!el || !cls) return false;
+  if (cls.indexOf(' ') !== -1) throw new Error('className should not contain space.');
+  if (el.classList) {
+    return el.classList.contains(cls);
+  } else {
+    return (' ' + el.className + ' ').indexOf(' ' + cls + ' ') > -1;
+  }
+};
+
+/* istanbul ignore next */
+export function addClass(el, cls) {
+  if (!el) return;
+  var curClass = el.className;
+  var classes = (cls || '').split(' ');
+
+  for (var i = 0, j = classes.length; i < j; i++) {
+    var clsName = classes[i];
+    if (!clsName) continue;
+
+    if (el.classList) {
+      el.classList.add(clsName);
+    } else {
+      if (!hasClass(el, clsName)) {
+        curClass += ' ' + clsName;
+      }
+    }
+  }
+  if (!el.classList) {
+    el.className = curClass;
+  }
+};
+
+/* istanbul ignore next */
+export function removeClass(el, cls) {
+  if (!el || !cls) return;
+  var classes = cls.split(' ');
+  var curClass = ' ' + el.className + ' ';
+
+  for (var i = 0, j = classes.length; i < j; i++) {
+    var clsName = classes[i];
+    if (!clsName) continue;
+
+    if (el.classList) {
+      el.classList.remove(clsName);
+    } else {
+      if (hasClass(el, clsName)) {
+        curClass = curClass.replace(' ' + clsName + ' ', ' ');
+      }
+    }
+  }
+  if (!el.classList) {
+    el.className = trim(curClass);
+  }
+};
+
+/* istanbul ignore next */
+export const getStyle = ieVersion < 9 ? function(element, styleName) {
+  if (isServer) return;
+  if (!element || !styleName) return null;
+  styleName = camelCase(styleName);
+  if (styleName === 'float') {
+    styleName = 'styleFloat';
+  }
+  try {
+    switch (styleName) {
+      case 'opacity':
+        try {
+          return element.filters.item('alpha').opacity / 100;
+        } catch (e) {
+          return 1.0;
+        }
+      default:
+        return (element.style[styleName] || element.currentStyle ? element.currentStyle[styleName] : null);
+    }
+  } catch (e) {
+    return element.style[styleName];
+  }
+} : function(element, styleName) {
+  if (isServer) return;
+  if (!element || !styleName) return null;
+  styleName = camelCase(styleName);
+  if (styleName === 'float') {
+    styleName = 'cssFloat';
+  }
+  try {
+    var computed = document.defaultView.getComputedStyle(element, '');
+    return element.style[styleName] || computed ? computed[styleName] : null;
+  } catch (e) {
+    return element.style[styleName];
+  }
+};
+
+/* istanbul ignore next */
+export function setStyle(element, styleName, value) {
+  if (!element || !styleName) return;
+
+  if (typeof styleName === 'object') {
+    for (var prop in styleName) {
+      if (styleName.hasOwnProperty(prop)) {
+        setStyle(element, prop, styleName[prop]);
+      }
+    }
+  } else {
+    styleName = camelCase(styleName);
+    if (styleName === 'opacity' && ieVersion < 9) {
+      element.style.filter = isNaN(value) ? '' : 'alpha(opacity=' + value * 100 + ')';
+    } else {
+      element.style[styleName] = value;
+    }
+  }
+};

+ 297 - 0
src/utils/popup/index.js

@@ -0,0 +1,297 @@
+import Vue from 'vue';
+import merge from 'element-ui/src/utils/merge';
+import PopupManager from 'element-ui/src/utils/popup/popup-manager';
+
+let idSeed = 1;
+const transitions = [];
+
+const hookTransition = (transition) => {
+  if (transitions.indexOf(transition) !== -1) return;
+
+  const getVueInstance = (element) => {
+    let instance = element.__vue__;
+    if (!instance) {
+      const textNode = element.previousSibling;
+      if (textNode.__vue__) {
+        instance = textNode.__vue__;
+      }
+    }
+    return instance;
+  };
+
+  Vue.transition(transition, {
+    afterEnter(el) {
+      const instance = getVueInstance(el);
+
+      if (instance) {
+        instance.doAfterOpen && instance.doAfterOpen();
+      }
+    },
+    afterLeave(el) {
+      const instance = getVueInstance(el);
+
+      if (instance) {
+        instance.doAfterClose && instance.doAfterClose();
+      }
+    }
+  });
+};
+
+let scrollBarWidth;
+const getScrollBarWidth = () => {
+  if (Vue.prototype.$isServer) return;
+  if (scrollBarWidth !== undefined) return scrollBarWidth;
+
+  const outer = document.createElement('div');
+  outer.style.visibility = 'hidden';
+  outer.style.width = '100px';
+  outer.style.position = 'absolute';
+  outer.style.top = '-9999px';
+  document.body.appendChild(outer);
+
+  const widthNoScroll = outer.offsetWidth;
+  outer.style.overflow = 'scroll';
+
+  const inner = document.createElement('div');
+  inner.style.width = '100%';
+  outer.appendChild(inner);
+
+  const widthWithScroll = inner.offsetWidth;
+  outer.parentNode.removeChild(outer);
+
+  return widthNoScroll - widthWithScroll;
+};
+
+const getDOM = function(dom) {
+  if (dom.nodeType === 3) {
+    dom = dom.nextElementSibling || dom.nextSibling;
+    getDOM(dom);
+  }
+  return dom;
+};
+
+export default {
+  props: {
+    value: {
+      type: Boolean,
+      default: false
+    },
+    transition: {
+      type: String,
+      default: ''
+    },
+    openDelay: {},
+    closeDelay: {},
+    zIndex: {},
+    modal: {
+      type: Boolean,
+      default: false
+    },
+    modalFade: {
+      type: Boolean,
+      default: true
+    },
+    modalClass: {
+    },
+    lockScroll: {
+      type: Boolean,
+      default: true
+    },
+    closeOnPressEscape: {
+      type: Boolean,
+      default: false
+    },
+    closeOnClickModal: {
+      type: Boolean,
+      default: false
+    }
+  },
+
+  created() {
+    if (this.transition) {
+      hookTransition(this.transition);
+    }
+  },
+
+  beforeMount() {
+    this._popupId = 'popup-' + idSeed++;
+    PopupManager.register(this._popupId, this);
+  },
+
+  beforeDestroy() {
+    PopupManager.deregister(this._popupId);
+    PopupManager.closeModal(this._popupId);
+    if (this.modal && this.bodyOverflow !== null && this.bodyOverflow !== 'hidden') {
+      document.body.style.overflow = this.bodyOverflow;
+      document.body.style.paddingRight = this.bodyPaddingRight;
+    }
+    this.bodyOverflow = null;
+    this.bodyPaddingRight = null;
+  },
+
+  data() {
+    return {
+      opened: false,
+      bodyOverflow: null,
+      bodyPaddingRight: null,
+      rendered: false
+    };
+  },
+
+  watch: {
+    value(val) {
+      if (val) {
+        if (this._opening) return;
+        if (!this.rendered) {
+          this.rendered = true;
+          Vue.nextTick(() => {
+            this.open();
+          });
+        } else {
+          this.open();
+        }
+      } else {
+        this.close();
+      }
+    }
+  },
+
+  methods: {
+    open(options) {
+      if (!this.rendered) {
+        this.rendered = true;
+        this.$emit('input', true);
+      }
+
+      const props = merge({}, this, options);
+
+      if (this._closeTimer) {
+        clearTimeout(this._closeTimer);
+        this._closeTimer = null;
+      }
+      clearTimeout(this._openTimer);
+
+      const openDelay = Number(props.openDelay);
+      if (openDelay > 0) {
+        this._openTimer = setTimeout(() => {
+          this._openTimer = null;
+          this.doOpen(props);
+        }, openDelay);
+      } else {
+        this.doOpen(props);
+      }
+    },
+
+    doOpen(props) {
+      if (this.$isServer) return;
+      if (this.willOpen && !this.willOpen()) return;
+      if (this.opened) return;
+
+      this._opening = true;
+
+      // 使用 vue-popup 的组件,如果需要和父组件通信显示的状态,应该使用 value,它是一个 prop,
+      // 这样在父组件中用 v-model 即可;否则可以使用 visible,它是一个 data
+      this.visible = true;
+      this.$emit('input', true);
+
+      const dom = getDOM(this.$el);
+
+      const modal = props.modal;
+
+      const zIndex = props.zIndex;
+      if (zIndex) {
+        PopupManager.zIndex = zIndex;
+      }
+
+      if (modal) {
+        if (this._closing) {
+          PopupManager.closeModal(this._popupId);
+          this._closing = false;
+        }
+        PopupManager.openModal(this._popupId, PopupManager.nextZIndex(), dom, props.modalClass, props.modalFade);
+        if (props.lockScroll) {
+          if (!this.bodyOverflow) {
+            this.bodyPaddingRight = document.body.style.paddingRight;
+            this.bodyOverflow = document.body.style.overflow;
+          }
+          scrollBarWidth = getScrollBarWidth();
+          let bodyHasOverflow = document.documentElement.clientHeight < document.body.scrollHeight;
+          if (scrollBarWidth > 0 && bodyHasOverflow) {
+            document.body.style.paddingRight = scrollBarWidth + 'px';
+          }
+          document.body.style.overflow = 'hidden';
+        }
+      }
+
+      if (getComputedStyle(dom).position === 'static') {
+        dom.style.position = 'absolute';
+      }
+
+      dom.style.zIndex = PopupManager.nextZIndex();
+      this.opened = true;
+
+      this.onOpen && this.onOpen();
+
+      if (!this.transition) {
+        this.doAfterOpen();
+      }
+    },
+
+    doAfterOpen() {
+      this._opening = false;
+    },
+
+    close() {
+      if (this.willClose && !this.willClose()) return;
+
+      if (this._openTimer !== null) {
+        clearTimeout(this._openTimer);
+        this._openTimer = null;
+      }
+      clearTimeout(this._closeTimer);
+
+      const closeDelay = Number(this.closeDelay);
+
+      if (closeDelay > 0) {
+        this._closeTimer = setTimeout(() => {
+          this._closeTimer = null;
+          this.doClose();
+        }, closeDelay);
+      } else {
+        this.doClose();
+      }
+    },
+
+    doClose() {
+      this.visible = false;
+      this.$emit('input', false);
+      this._closing = true;
+
+      this.onClose && this.onClose();
+
+      if (this.lockScroll) {
+        setTimeout(() => {
+          if (this.modal && this.bodyOverflow !== 'hidden') {
+            document.body.style.overflow = this.bodyOverflow;
+            document.body.style.paddingRight = this.bodyPaddingRight;
+          }
+          this.bodyOverflow = null;
+          this.bodyPaddingRight = null;
+        }, 200);
+      }
+
+      this.opened = false;
+
+      if (!this.transition) {
+        this.doAfterClose();
+      }
+    },
+
+    doAfterClose() {
+      PopupManager.closeModal(this._popupId);
+      this._closing = false;
+    }
+  }
+};
+
+export { PopupManager };

+ 165 - 0
src/utils/popup/popup-manager.js

@@ -0,0 +1,165 @@
+import Vue from 'vue';
+import { addClass, removeClass } from 'element-ui/src/utils/dom';
+
+let hasModal = false;
+
+const getModal = function() {
+  if (Vue.prototype.$isServer) return;
+  let modalDom = PopupManager.modalDom;
+  if (modalDom) {
+    hasModal = true;
+  } else {
+    hasModal = false;
+    modalDom = document.createElement('div');
+    PopupManager.modalDom = modalDom;
+
+    modalDom.addEventListener('touchmove', function(event) {
+      event.preventDefault();
+      event.stopPropagation();
+    });
+
+    modalDom.addEventListener('click', function() {
+      PopupManager.doOnModalClick && PopupManager.doOnModalClick();
+    });
+  }
+
+  return modalDom;
+};
+
+const instances = {};
+
+const PopupManager = {
+  zIndex: 2000,
+
+  modalFade: true,
+
+  getInstance: function(id) {
+    return instances[id];
+  },
+
+  register: function(id, instance) {
+    if (id && instance) {
+      instances[id] = instance;
+    }
+  },
+
+  deregister: function(id) {
+    if (id) {
+      instances[id] = null;
+      delete instances[id];
+    }
+  },
+
+  nextZIndex: function() {
+    return PopupManager.zIndex++;
+  },
+
+  modalStack: [],
+
+  doOnModalClick: function() {
+    const topItem = PopupManager.modalStack[PopupManager.modalStack.length - 1];
+    if (!topItem) return;
+
+    const instance = PopupManager.getInstance(topItem.id);
+    if (instance && instance.closeOnClickModal) {
+      instance.close();
+    }
+  },
+
+  openModal: function(id, zIndex, dom, modalClass, modalFade) {
+    if (Vue.prototype.$isServer) return;
+    if (!id || zIndex === undefined) return;
+    this.modalFade = modalFade;
+
+    const modalStack = this.modalStack;
+
+    for (let i = 0, j = modalStack.length; i < j; i++) {
+      const item = modalStack[i];
+      if (item.id === id) {
+        return;
+      }
+    }
+
+    const modalDom = getModal();
+
+    addClass(modalDom, 'v-modal');
+    if (this.modalFade && !hasModal) {
+      addClass(modalDom, 'v-modal-enter');
+    }
+    if (modalClass) {
+      let classArr = modalClass.trim().split(/\s+/);
+      classArr.forEach(item => addClass(modalDom, item));
+    }
+    setTimeout(() => {
+      removeClass(modalDom, 'v-modal-enter');
+    }, 200);
+
+    if (dom && dom.parentNode && dom.parentNode.nodeType !== 11) {
+      dom.parentNode.appendChild(modalDom);
+    } else {
+      document.body.appendChild(modalDom);
+    }
+
+    if (zIndex) {
+      modalDom.style.zIndex = zIndex;
+    }
+    modalDom.style.display = '';
+
+    this.modalStack.push({ id: id, zIndex: zIndex, modalClass: modalClass });
+  },
+
+  closeModal: function(id) {
+    const modalStack = this.modalStack;
+    const modalDom = getModal();
+
+    if (modalStack.length > 0) {
+      const topItem = modalStack[modalStack.length - 1];
+      if (topItem.id === id) {
+        if (topItem.modalClass) {
+          let classArr = topItem.modalClass.trim().split(/\s+/);
+          classArr.forEach(item => removeClass(modalDom, item));
+        }
+
+        modalStack.pop();
+        if (modalStack.length > 0) {
+          modalDom.style.zIndex = modalStack[modalStack.length - 1].zIndex;
+        }
+      } else {
+        for (let i = modalStack.length - 1; i >= 0; i--) {
+          if (modalStack[i].id === id) {
+            modalStack.splice(i, 1);
+            break;
+          }
+        }
+      }
+    }
+
+    if (modalStack.length === 0) {
+      if (this.modalFade) {
+        addClass(modalDom, 'v-modal-leave');
+      }
+      setTimeout(() => {
+        if (modalStack.length === 0) {
+          if (modalDom.parentNode) modalDom.parentNode.removeChild(modalDom);
+          modalDom.style.display = 'none';
+          PopupManager.modalDom = undefined;
+        }
+        removeClass(modalDom, 'v-modal-leave');
+      }, 200);
+    }
+  }
+};
+!Vue.prototype.$isServer && window.addEventListener('keydown', function(event) {
+  if (event.keyCode === 27) { // ESC
+    if (PopupManager.modalStack.length > 0) {
+      const topItem = PopupManager.modalStack[PopupManager.modalStack.length - 1];
+      if (!topItem) return;
+      const instance = PopupManager.getInstance(topItem.id);
+      if (instance.closeOnPressEscape) {
+        instance.close();
+      }
+    }
+  }
+});
+
+export default PopupManager;

+ 7 - 3
src/utils/resize-event.js

@@ -3,9 +3,11 @@
 *
 * version: 0.5.3
 **/
+const isServer = typeof window === 'undefined';
 
 /* istanbul ignore next */
 const requestFrame = (function() {
+  if (isServer) return;
   const raf = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame ||
     function(fn) {
       return window.setTimeout(fn, 20);
@@ -17,6 +19,7 @@ const requestFrame = (function() {
 
 /* istanbul ignore next */
 const cancelFrame = (function() {
+  if (isServer) return;
   const cancel = window.cancelAnimationFrame || window.mozCancelAnimationFrame || window.webkitCancelAnimationFrame || window.clearTimeout;
   return function(id) {
     return cancel(id);
@@ -59,7 +62,7 @@ const scrollListener = function(event) {
 };
 
 /* Detect CSS Animations support to detect element display/re-attach */
-const attachEvent = document.attachEvent;
+const attachEvent = isServer ? {} : document.attachEvent;
 const DOM_PREFIXES = 'Webkit Moz O ms'.split(' ');
 const START_EVENTS = 'webkitAnimationStart animationstart oAnimationStart MSAnimationStart'.split(' ');
 const RESIZE_ANIMATION_NAME = 'resizeanim';
@@ -68,7 +71,7 @@ let keyFramePrefix = '';
 let animationStartEvent = 'animationstart';
 
 /* istanbul ignore next */
-if (!attachEvent) {
+if (!attachEvent && !isServer) {
   const testElement = document.createElement('fakeelement');
   if (testElement.style.animationName !== undefined) {
     animation = true;
@@ -91,7 +94,7 @@ if (!attachEvent) {
 let stylesCreated = false;
 /* istanbul ignore next */
 const createStyles = function() {
-  if (!stylesCreated) {
+  if (!stylesCreated && !isServer) {
     const animationKeyframes = `@${keyFramePrefix}keyframes ${RESIZE_ANIMATION_NAME} { from { opacity: 0; } to { opacity: 0; } } `;
     const animationStyle = `${keyFramePrefix}animation: 1ms ${RESIZE_ANIMATION_NAME};`;
 
@@ -119,6 +122,7 @@ const createStyles = function() {
 
 /* istanbul ignore next */
 export const addResizeListener = function(element, fn) {
+  if (isServer) return;
   if (attachEvent) {
     element.attachEvent('onresize', fn);
   } else {

+ 4 - 2
src/utils/vue-popper.js

@@ -1,6 +1,7 @@
-import PopperJS from './popper';
-import { PopupManager } from 'vue-popup';
+import Vue from 'vue';
+import { PopupManager } from 'element-ui/src/utils/popup';
 
+const PopperJS = Vue.prototype.$isServer ? function() {} : require('./popper');
 const stop = e => e.stopPropagation();
 
 /**
@@ -66,6 +67,7 @@ export default {
 
   methods: {
     createPopper() {
+      if (this.$isServer) return;
       if (!/^(top|bottom|left|right)(-start|-end)?$/g.test(this.placement)) {
         return;
       }

+ 1 - 1
test/unit/specs/mixin.vue-popup.spec.js

@@ -1,4 +1,4 @@
-import VuePopup from 'vue-popup';
+import VuePopup from 'element-ui/src/utils/popup';
 import { createTest, destroyVM } from '../util';
 
 const Popup = Object.assign({}, VuePopup, {

+ 0 - 10
yarn.lock

@@ -6863,12 +6863,6 @@ vue-markdown-loader@^0.5.1:
     markdown-it "^6.0.5"
     rimraf "^2.5.2"
 
-vue-popup@^0.2.14:
-  version "0.2.14"
-  resolved "https://registry.yarnpkg.com/vue-popup/-/vue-popup-0.2.14.tgz#aa3d461c6f2ab01ef4546665d730395ec6028302"
-  dependencies:
-    wind-dom "0.0.3"
-
 vue-router@^2.0.0:
   version "2.1.1"
   resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-2.1.1.tgz#10c31bbdcb6da92bd3e0223fa12345e73018625a"
@@ -7020,10 +7014,6 @@ wide-align@^1.1.0:
   dependencies:
     string-width "^1.0.1"
 
-wind-dom@0.0.3:
-  version "0.0.3"
-  resolved "https://registry.yarnpkg.com/wind-dom/-/wind-dom-0.0.3.tgz#3456e3d959dbebdcbf76ca68c6a4e8142f26360f"
-
 window-size@^0.2.0:
   version "0.2.0"
   resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.2.0.tgz#b4315bb4214a3d7058ebeee892e13fa24d98b075"