index.vue 5.69 KB
<template>
  <div v-show="isPopupShow" class="zui-popup" :class="popupClassRender">
    <transition name="zui-mask-fade">
      <div v-show="hasMask && isPopupBoxShow" @click="$_onPopupMaskClick" class="zui-popup-mask"></div>
    </transition>
    <zui-transition
      :name="transition"
      @before-enter="$_onPopupTransitionStart"
      @before-leave="$_onPopupTransitionStart"
      @after-enter="$_onPopupTransitionEnd"
      @after-leave="$_onPopupTransitionEnd"
    >
      <div v-show="isPopupBoxShow" class="zui-popup-box" :class="[transition]">
        <slot></slot>
      </div>
    </zui-transition>
  </div>
</template>

<script>
export default {
  name: 'Popup',
  components: {
    'zui-transition': {
      name: 'zui-transition',
      functional: true,
      render: function (h, context) {
        return h('transition', context.data, context.children);
      },
    },
  },
  props: {
    position: {
      type: String,
      default: 'center',
    },
    transition: {
      type: String,
      default: function () {
        switch (this.position) {
          case 'bottom':
            return 'zui-slide-up';
          case 'top':
            return 'zui-slide-down';
          case 'left':
            return 'zui-slide-right';
          case 'right':
            return 'zui-slide-left';
          default:
            return 'zui-fade';
        }
      },
    },
    preventScroll: {
      type: Boolean,
      default: false,
    },
    preventScrollExclude: {
      type: [String, Function],
      default: function () {
        return '';
      },
    },
    // Mixin Props
    value: {
      type: Boolean,
      default: false,
    },
    hasMask: {
      type: Boolean,
      default: true,
    },
    maskClosable: {
      type: Boolean,
      default: true,
    },
  },
  data: function () {
    return {
      // controle popup mask & popup box
      isPopupShow: false,
      // controle popup box
      isPopupBoxShow: false,
      // transtion lock
      isAnimation: false,
      largeRadius: false,
    };
  },
  computed: {
    popupClassRender: function () {
      return [this.hasMask ? 'with-mask' : '', this.largeRadius ? 'large-radius' : '', this.position];
    },
  },
  watch: {
    value: function (val) {
      /* istanbul ignore next */
      if (val) {
        if (this.isAnimation) {
          var self = this;
          setTimeout(function () {
            self.$_showPopupBox();
          }, 50);
        } else {
          this.$_showPopupBox();
        }
      } else {
        this.$_hidePopupBox();
      }
    },
    preventScrollExclude: function (val, oldVal) {
      // remove old listener before add
      /* istanbul ignore next */
      this.$_preventScrollExclude(false, oldVal);
      /* istanbul ignore next */
      this.$_preventScrollExclude(true, val);
    },
  },
  mounted: function () {
    this.value && this.$_showPopupBox();
  },
  methods: {
    // MARK: private methods
    $_showPopupBox: function () {
      this.isPopupShow = true;
      this.isAnimation = true;
      // popup box enter the animation after popup show
      this.isPopupBoxShow = true;
      /* istanbul ignore if */
      if (window.process && window.process.env.NODE_ENV == 'test') {
        this.$_onPopupTransitionStart();
        this.$_onPopupTransitionEnd();
      }
      this.preventScroll && this.$_preventScroll(true);
    },
    $_hidePopupBox: function () {
      this.isAnimation = true;
      this.isPopupBoxShow = false;
      this.preventScroll && this.$_preventScroll(false);
      this.$emit('input', false);
      /* istanbul ignore if */
      if (window.process && window.process.env.NODE_ENV == 'test') {
        this.$_onPopupTransitionStart();
        this.$_onPopupTransitionEnd();
      }
    },
    $_preventScroll: function (isBind) {
      const handler = isBind ? 'addEventListener' : 'removeEventListener';
      const masker = this.$el.querySelector('.zui-popup-mask');
      const boxer = this.$el.querySelector('.zui-popup-box');
      masker && masker[handler]('touchmove', this.$_preventDefault, false);
      boxer && boxer[handler]('touchmove', this.$_preventDefault, false);
      this.$_preventScrollExclude(isBind);
    },
    $_preventScrollExclude: function (isBind, preventScrollExclude) {
      const handler = isBind ? 'addEventListener' : 'removeEventListener';
      preventScrollExclude = preventScrollExclude || this.preventScrollExclude;
      const excluder = preventScrollExclude && typeof preventScrollExclude == 'string' ? this.$el.querySelector(preventScrollExclude) : preventScrollExclude;
      excluder && excluder[handler]('touchmove', this.$_stopImmediatePropagation, false);
    },
    $_preventDefault: function (event) {
      event.preventDefault();
    },
    $_stopImmediatePropagation: function (event) {
      /* istanbul ignore next */
      event.stopImmediatePropagation();
    },
    // MARK: event handler
    $_onPopupTransitionStart: function () {
      if (!this.isPopupBoxShow) {
        this.$emit('beforeHide');
        this.$emit('before-hide');
      } else {
        this.$emit('beforeShow');
        this.$emit('before-show');
      }
    },
    $_onPopupTransitionEnd: function () {
      /* istanbul ignore next */
      if (!this.isAnimation) {
        return;
      }
      /* istanbul ignore next */
      if (!this.isPopupBoxShow) {
        // popup hide after popup box finish animation
        this.isPopupShow = false;
        this.$emit('hide');
      } else {
        this.$emit('show');
      }
      /* istanbul ignore next */
      this.isAnimation = false;
    },
    $_onPopupMaskClick: function () {
      if (this.maskClosable) {
        this.$_hidePopupBox();
        this.$emit('maskClick');
      }
    },
  },
};
</script>

<style>
@import './index.css';
</style>