index.vue 5.75 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>