소스 검색

Add vue-popup and dom helper

qingwei.li 8 년 전
부모
커밋
a906a5fccb
40개의 변경된 파일706개의 추가작업 그리고 54개의 파일을 삭제
  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>
 <script>
-  import { addClass } from 'wind-dom/src/class';
+  import { addClass } from 'element-ui/src/utils/dom';
   export default {
   export default {
     data() {
     data() {
       return {
       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.
 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.
 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>
 <script>
-  import { addClass } from 'wind-dom/src/class';
+  import { addClass } from 'element-ui/src/utils/dom';
   export default {
   export default {
     data() {
     data() {
       return {
       return {

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

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

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

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

+ 1 - 3
package.json

@@ -45,9 +45,7 @@
     "async-validator": "^1.6.6",
     "async-validator": "^1.6.6",
     "babel-helper-vue-jsx-merge-props": "^2.0.0",
     "babel-helper-vue-jsx-merge-props": "^2.0.0",
     "deepmerge": "^1.2.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": {
   "peerDependencies": {
     "vue": "^2.1.6"
     "vue": "^2.1.6"

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

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

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

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

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

@@ -49,7 +49,7 @@
 
 
 <script type="text/babel">
 <script type="text/babel">
   import Locale from 'element-ui/src/mixins/locale';
   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 {
   export default {
     props: {
     props: {

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

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

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

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

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

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

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

@@ -46,7 +46,7 @@
 </template>
 </template>
 <script>
 <script>
   import ElInput from 'element-ui/packages/input';
   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 {
   export default {
     name: 'ElInputNumber',
     name: 'ElInputNumber',

+ 0 - 1
packages/loading/package.json

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

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

@@ -1,5 +1,5 @@
 import Vue from 'vue';
 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'));
 let Mask = Vue.extend(require('./loading.vue'));
 
 
 exports.install = Vue => {
 exports.install = Vue => {

+ 0 - 1
packages/menu/package.json

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

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

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

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

@@ -24,11 +24,11 @@
 </template>
 </template>
 
 
 <script type="text/babel">
 <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 Locale from 'element-ui/src/mixins/locale';
   import ElInput from 'element-ui/packages/input';
   import ElInput from 'element-ui/packages/input';
   import ElButton from 'element-ui/packages/button';
   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';
   import { t } from 'element-ui/src/locale';
 
 
   let typeMap = {
   let typeMap = {

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

@@ -1,5 +1,5 @@
 import Vue from 'vue';
 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 MessageConstructor = Vue.extend(require('./main.vue'));
 
 
 let instance;
 let instance;

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

@@ -1,5 +1,5 @@
 import Vue from 'vue';
 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 NotificationConstructor = Vue.extend(require('./main.vue'));
 
 
 let instance;
 let instance;

+ 0 - 1
packages/popover/package.json

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

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

@@ -17,7 +17,7 @@
 
 
 <script>
 <script>
 import Popper from 'element-ui/src/utils/vue-popper';
 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 {
 export default {
   name: 'el-popover',
   name: 'el-popover',

+ 0 - 1
packages/rate/package.json

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

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

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

+ 1 - 2
packages/select/package.json

@@ -12,7 +12,6 @@
   "license": "MIT",
   "license": "MIT",
   "repository": "https://github.com/ElemeFE/element/tree/master/packages/select",
   "repository": "https://github.com/ElemeFE/element/tree/master/packages/select",
   "devDependencies": {
   "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 ElTag from 'element-ui/packages/tag';
   import debounce from 'throttle-debounce/debounce';
   import debounce from 'throttle-debounce/debounce';
   import Clickoutside from 'element-ui/src/utils/clickoutside';
   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 { addResizeListener, removeResizeListener } from 'element-ui/src/utils/resize-event';
   import { t } from 'element-ui/src/locale';
   import { t } from 'element-ui/src/locale';
   const sizeMap = {
   const sizeMap = {

+ 0 - 1
packages/slider/package.json

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

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

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

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

@@ -1,4 +1,5 @@
 import { getScrollBarWidth } from './util';
 import { getScrollBarWidth } from './util';
+import Vue from 'vue';
 
 
 let GUTTER_WIDTH;
 let GUTTER_WIDTH;
 
 
@@ -22,7 +23,7 @@ class TableLayout {
     this.bodyHeight = null; // Table Height - Table Header Height
     this.bodyHeight = null; // Table Height - Table Header Height
     this.fixedBodyHeight = null; // Table Height - Table Header Height - Scroll Bar 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();
       GUTTER_WIDTH = getScrollBarWidth();
     }
     }
     this.gutterWidth = GUTTER_WIDTH;
     this.gutterWidth = GUTTER_WIDTH;

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

@@ -31,7 +31,5 @@
     "gulp-postcss": "^6.1.1",
     "gulp-postcss": "^6.1.1",
     "postcss-salad": "^1.0.5"
     "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";
 @charset "UTF-8";
 @import "./common/var.css";
 @import "./common/var.css";
-@import "vue-popup/lib/popup.css";
+@import "./common/popup.css";
 
 
 @component-namespace el {
 @component-namespace el {
 
 

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

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

+ 1 - 1
src/utils/clickoutside.js

@@ -1,5 +1,5 @@
 import Vue from 'vue';
 import Vue from 'vue';
-import { on } from 'wind-dom/src/event';
+import { on } from 'element-ui/src/utils/dom';
 
 
 const nodeList = [];
 const nodeList = [];
 const ctx = '@@clickoutsideContext';
 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__);
       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();
 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';
 import { createTest, destroyVM } from '../util';
 
 
 const Popup = Object.assign({}, VuePopup, {
 const Popup = Object.assign({}, VuePopup, {

+ 0 - 10
yarn.lock

@@ -6863,12 +6863,6 @@ vue-markdown-loader@^0.5.1:
     markdown-it "^6.0.5"
     markdown-it "^6.0.5"
     rimraf "^2.5.2"
     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:
 vue-router@^2.0.0:
   version "2.1.1"
   version "2.1.1"
   resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-2.1.1.tgz#10c31bbdcb6da92bd3e0223fa12345e73018625a"
   resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-2.1.1.tgz#10c31bbdcb6da92bd3e0223fa12345e73018625a"
@@ -7020,10 +7014,6 @@ wide-align@^1.1.0:
   dependencies:
   dependencies:
     string-width "^1.0.1"
     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:
 window-size@^0.2.0:
   version "0.2.0"
   version "0.2.0"
   resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.2.0.tgz#b4315bb4214a3d7058ebeee892e13fa24d98b075"
   resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.2.0.tgz#b4315bb4214a3d7058ebeee892e13fa24d98b075"