<template>
  <view
    style="display: flex; flex-direction: column"
    v-if="isShow"
    ref="anis"
    class="view wrap-transition"
    :class="[ani.in]"
    :style="'transform:' + transform + ';' + stylesObject"
    @click.stop="change"
    @click.stop.prevent
    @touchmove.stop.prevent
  >
    <slot></slot>
  </view>
</template>

<script lang="ts" setup>
import { computed, nextTick, ref, watch } from 'vue'

/**
 * Transition 过渡动画
 * @description 简单过渡动画组件
 * @tutorial
 * @property {Boolean} show = [false|true] 控制组件显示或隐藏
 * @property {Array} modeClass = [fade|slide-top|slide-right|slide-bottom|slide-left|zoom-in|zoom-out] 过渡动画类型
 *  @value fade 渐隐渐出过渡
 *  @value slide-top 由上至下过渡
 *  @value slide-right 由右至左过渡
 *  @value slide-bottom 由下至上过渡
 *  @value slide-left 由左至右过渡
 *  @value zoom-in 由小到大过渡
 *  @value zoom-out 由大到小过渡
 * @property {Number} duration 过渡动画持续时间
 * @property {Object} style-custom 组件样式，同 css 样式，注意带’-‘连接符的属性需要使用小驼峰写法如：`backgroundColor:red`
 */
// #ifdef APP-NVUE
const animation = uni.requireNativePlugin('animation')
// #endif

interface propsType {
  show?: boolean
  modeClass?: any
  duration?: number
  styleCustom?: any
}

const props = withDefaults(defineProps<propsType>(), {
  show: false,
  modeClass: [],
  duration: 300,
  styleCustom: {},
})

const isShow = ref<boolean>(false)
const transform = ref<string>('')
const ani = ref<AnyObject>({
  in: '',
  active: '',
})

watch(
  () => props.show,
  (newVal: boolean) => {
    if (newVal) {
      open()
    } else {
      close()
    }
  }
)

const stylesObject = computed(() => {
  let oldStyle = {
    'transition-duration': props.duration / 1000 + 's',
  }
  let styles = Object.assign(props.styleCustom, oldStyle)
  let transfrom = ''
  for (let i in styles) {
    let line = toLine(i)
    transfrom += line + ':' + styles[i] + ';'
  }
  return transfrom
})

const emit = defineEmits<{
  (e: 'click', data: any): void
  (e: 'change', data: any): void
}>()

function change(event: AnyObject) {
  // 阻止继续冒泡.
  event.stopPropagation()
  emit('click', {
    detail: isShow.value,
  })
}

function open() {
  clearTimeout(timer)
  isShow.value = true
  transform.value = ''
  ani.value.in = ''
  for (let i in getTranfrom(false)) {
    if (i === 'opacity') {
      ani.value.in = 'fade-in'
    } else {
      transform.value += `${getTranfrom(false)[i]} `
    }
  }
  nextTick(() => {
    setTimeout(() => {
      _animation(true)
    }, 50)
  })
}
function close(type?: any) {
  clearTimeout(timer)
  _animation(false)
}
let timer: any = null
const anis = ref<any>(null)
function _animation(type: any) {
  let styles = getTranfrom(type)
  // #ifdef APP-NVUE
  if (!anis) return
  animation.transition(
    anis.value,
    {
      styles,
      duration: props.duration, //ms
      timingFunction: 'ease',
      needLayout: false,
      delay: 0, //ms
    },
    () => {
      if (!type) {
        isShow.value = false
      }
      emit('change', {
        detail: isShow.value,
      })
    }
  )
  // #endif
  // #ifndef APP-NVUE
  transform.value = ''
  for (let i in styles) {
    if (i === 'opacity') {
      ani.value.in = `fade-${type ? 'out' : 'in'}`
    } else {
      transform.value += `${styles[i]} `
    }
  }
  timer = setTimeout(() => {
    if (!type) {
      isShow.value = false
    }
    emit('change', {
      detail: isShow.value,
    })
  }, props.duration)
  // #endif
}
function getTranfrom(type: any) {
  let styles: any = {
    transform: '',
  }
  props.modeClass.forEach((mode: any) => {
    switch (mode) {
      case 'fade':
        styles.opacity = type ? 1 : 0
        break
      case 'slide-top':
        styles.transform += `translateY(${type ? '0' : '-100%'}) `
        break
      case 'slide-right':
        styles.transform += `translateX(${type ? '0' : '100%'}) `
        break
      case 'slide-bottom':
        styles.transform += `translateY(${type ? '0' : '100%'}) `
        break
      case 'slide-left':
        styles.transform += `translateX(${type ? '0' : '-100%'}) `
        break
      case 'zoom-in':
        styles.transform += `scale(${type ? 1 : 0.8}) `
        break
      case 'zoom-out':
        styles.transform += `scale(${type ? 1 : 1.2}) `
        break
    }
  })
  return styles
}
function _modeClassArr(type: any) {
  let mode = props.modeClass
  if (typeof mode !== 'string') {
    let modestr = ''
    mode.forEach((item: any) => {
      modestr += item + '-' + type + ','
    })
    return modestr.substr(0, modestr.length - 1)
  } else {
    return mode + '-' + type
  }
}
function toLine(name: any) {
  return name.replace(/([A-Z])/g, '-$1').toLowerCase()
}
</script>

<style lang="scss" scoped>
.view {
  /* #ifndef APP-NVUE */
  position: relative;
  display: flex;
  flex-direction: column;
  /* #endif */
}

.wrap-transition {
  transition-timing-function: ease;
  transition-duration: 0.4s;
  transition-property: transform, opacity;
}

.fade-in {
  opacity: 0;
}

.fade-active {
  opacity: 1;
}

.slide-top-in {
  transform: translateY(-100%);
}

.slide-top-active {
  transform: translateY(0);
}

.slide-right-in {
  transform: translateX(100%);
}

.slide-right-active {
  transform: translateX(0);
}

.slide-bottom-in {
  transform: translateY(100%);
}

.slide-bottom-active {
  transform: translateY(0);
}

.slide-left-in {
  transform: translateX(-100%);
}

.slide-left-active {
  transform: translateX(0);
  opacity: 1;
}

.zoom-in-in {
  transform: scale(0.8);
}

.zoom-out-active {
  transform: scale(1);
}

.zoom-out-in {
  transform: scale(1.2);
}
</style>


