Forráskód Böngészése

Add vue-popup and dom helper

qingwei.li 8 éve
szülő
commit
a906a5fccb
40 módosított fájl, 706 hozzáadás és 54 törlés
  1. 2 2
      examples/docs/en-US/button.md
  2. 2 2
      examples/docs/en-US/pagination.md
  3. 1 1
      examples/docs/zh-CN/button.md
  4. 1 1
      examples/docs/zh-CN/pagination.md
  5. 1 3
      package.json
  6. 0 1
      packages/date-picker/package.json
  7. 1 1
      packages/date-picker/src/basic/date-table.vue
  8. 1 1
      packages/date-picker/src/basic/month-table.vue
  9. 1 1
      packages/date-picker/src/basic/year-table.vue
  10. 1 1
      packages/dialog/src/component.vue
  11. 0 1
      packages/input-number/package.json
  12. 1 1
      packages/input-number/src/input-number.vue
  13. 0 1
      packages/loading/package.json
  14. 1 1
      packages/loading/src/directive.js
  15. 0 1
      packages/menu/package.json
  16. 0 1
      packages/message-box/package.json
  17. 2 2
      packages/message-box/src/main.vue
  18. 1 1
      packages/message/src/main.js
  19. 1 1
      packages/notification/src/main.js
  20. 0 1
      packages/popover/package.json
  21. 1 1
      packages/popover/src/main.vue
  22. 0 1
      packages/rate/package.json
  23. 1 1
      packages/rate/src/main.vue
  24. 1 2
      packages/select/package.json
  25. 1 1
      packages/select/src/select.vue
  26. 0 1
      packages/slider/package.json
  27. 1 1
      packages/slider/src/main.vue
  28. 2 1
      packages/table/src/table-layout.js
  29. 1 3
      packages/theme-default/package.json
  30. 33 0
      packages/theme-default/src/common/popup.css
  31. 1 1
      packages/theme-default/src/dialog.css
  32. 1 1
      packages/theme-default/src/message-box.css
  33. 1 1
      src/utils/clickoutside.js
  34. 178 0
      src/utils/dom.js
  35. 297 0
      src/utils/popup/index.js
  36. 165 0
      src/utils/popup/popup-manager.js
  37. 1 1
      src/utils/resize-event.js
  38. 3 2
      src/utils/vue-popper.js
  39. 1 1
      test/unit/specs/mixin.vue-popup.spec.js
  40. 0 10
      yarn.lock

+ 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

@@ -45,9 +45,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 - 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"
   }
 }

+ 1 - 1
packages/input-number/src/input-number.vue

@@ -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',

+ 0 - 1
packages/loading/package.json

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

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

@@ -1,5 +1,5 @@
 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 => {

+ 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"
   }
 }

+ 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 = {

+ 1 - 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;

+ 1 - 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;

+ 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"
   }
 }

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

@@ -91,7 +91,7 @@
   import ElTag from 'element-ui/packages/tag';
   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 = {

+ 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/table-layout.js

@@ -1,4 +1,5 @@
 import { getScrollBarWidth } from './util';
+import Vue from 'vue';
 
 let GUTTER_WIDTH;
 
@@ -22,7 +23,7 @@ class TableLayout {
     this.bodyHeight = null; // Table Height - Table Header Height
     this.fixedBodyHeight = null; // Table Height - Table Header Height - Scroll Bar Height
 
-    if (GUTTER_WIDTH === undefined) {
+    if (GUTTER_WIDTH === undefined && !Vue.prototype.$isServer) {
       GUTTER_WIDTH = getScrollBarWidth();
     }
     this.gutterWidth = GUTTER_WIDTH;

+ 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 {
 

+ 1 - 1
src/utils/clickoutside.js

@@ -1,5 +1,5 @@
 import Vue from 'vue';
-import { on } from 'wind-dom/src/event';
+import { on } from 'element-ui/src/utils/dom';
 
 const nodeList = [];
 const ctx = '@@clickoutsideContext';

+ 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;

+ 1 - 1
src/utils/resize-event.js

@@ -166,4 +166,4 @@ export const removeResizeListener = function(element, fn) {
       element.__resizeTrigger__ = !element.removeChild(element.__resizeTrigger__);
     }
   }
-};
+};

+ 3 - 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();
 
 /**

+ 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"