<template>
	<view v-if="isShow" ref="ani" :animation="animationData" :class="customClass" :style="transformStyles" @click="onClick"><slot></slot></view>
</template>

<script>
import { createAnimation } from './createAnimation'

/**
 * Transition 过渡动画
 * @description 简单过渡动画组件
 * @tutorial https://ext.dcloud.net.cn/plugin?id=985
 * @property {Boolean} show = [false|true] 控制组件显示或隐藏
 * @property {Array|String} 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} styles 组件样式，同 css 样式，注意带’-‘连接符的属性需要使用小驼峰写法如：`backgroundColor:red`
 */
export default {
	name: 'uniTransition',
	emits:['click','change'],
	props: {
		show: {
			type: Boolean,
			default: false
		},
		modeClass: {
			type: [Array, String],
			default() {
				return 'fade'
			}
		},
		duration: {
			type: Number,
			default: 300
		},
		styles: {
			type: Object,
			default() {
				return {}
			}
		},
		customClass:{
			type: String,
			default: ''
		}
	},
	data() {
		return {
			isShow: false,
			transform: '',
			opacity: 1,
			animationData: {},
			durationTime: 300,
			config: {}
		}
	},
	watch: {
		show: {
			handler(newVal) {
				if (newVal) {
					this.open()
				} else {
					// 避免上来就执行 close,导致动画错乱
					if (this.isShow) {
						this.close()
					}
				}
			},
			immediate: true
		}
	},
	computed: {
		// 生成样式数据
		stylesObject() {
			let styles = {
				...this.styles,
				'transition-duration': this.duration / 1000 + 's'
			}
			let transform = ''
			for (let i in styles) {
				let line = this.toLine(i)
				transform += line + ':' + styles[i] + ';'
			}
			return transform
		},
		// 初始化动画条件
		transformStyles() {
			return 'transform:' + this.transform + ';' + 'opacity:' + this.opacity + ';' + this.stylesObject
		}
	},
	created() {
		// 动画默认配置
		this.config = {
			duration: this.duration,
			timingFunction: 'ease',
			transformOrigin: '50% 50%',
			delay: 0
		}
		this.durationTime = this.duration
	},
	methods: {
		/**
		 *  ref 触发 初始化动画
		 */
		init(obj = {}) {
			if (obj.duration) {
				this.durationTime = obj.duration
			}
			this.animation = createAnimation(Object.assign(this.config, obj),this)
		},
		/**
		 * 点击组件触发回调
		 */
		onClick() {
			this.$emit('click', {
				detail: this.isShow
			})
		},
		/**
		 * ref 触发 动画分组
		 * @param {Object} obj
		 */
		step(obj, config = {}) {
			if (!this.animation) return
			for (let i in obj) {
				try {
					if(typeof obj[i] === 'object'){
						this.animation[i](...obj[i])
					}else{
						this.animation[i](obj[i])
					}
				} catch (e) {
					console.error(`方法 ${i} 不存在`)
				}
			}
			this.animation.step(config)
			return this
		},
		/**
		 *  ref 触发 执行动画
		 */
		run(fn) {
			if (!this.animation) return
			this.animation.run(fn)
		},
		// 开始过度动画
		open() {
			clearTimeout(this.timer)
			this.transform = ''
			this.isShow = true
			let { opacity, transform } = this.styleInit(false)
			if (typeof opacity !== 'undefined') {
				this.opacity = opacity
			}
			this.transform = transform
			// 确保动态样式已经生效后，执行动画，如果不加 nextTick ，会导致 wx 动画执行异常
			this.$nextTick(() => {
				// TODO 定时器保证动画完全执行，目前有些问题，后面会取消定时器
				this.timer = setTimeout(() => {
					this.animation = createAnimation(this.config, this)
					this.tranfromInit(false).step()
					this.animation.run()
					this.$emit('change', {
						detail: this.isShow
					})
				}, 20)
			})
		},
		// 关闭过度动画
		close(type) {
			if (!this.animation) return
			this.tranfromInit(true)
				.step()
				.run(() => {
					this.isShow = false
					this.animationData = null
					this.animation = null
					let { opacity, transform } = this.styleInit(false)
					this.opacity = opacity || 1
					this.transform = transform
					this.$emit('change', {
						detail: this.isShow
					})
				})
		},
		// 处理动画开始前的默认样式
		styleInit(type) {
			let styles = {
				transform: ''
			}
			let buildStyle = (type, mode) => {
				if (mode === 'fade') {
					styles.opacity = this.animationType(type)[mode]
				} else {
					styles.transform += this.animationType(type)[mode] + ' '
				}
			}
			if (typeof this.modeClass === 'string') {
				buildStyle(type, this.modeClass)
			} else {
				this.modeClass.forEach(mode => {
					buildStyle(type, mode)
				})
			}
			return styles
		},
		// 处理内置组合动画
		tranfromInit(type) {
			let buildTranfrom = (type, mode) => {
				let aniNum = null
				if (mode === 'fade') {
					aniNum = type ? 0 : 1
				} else {
					aniNum = type ? '-100%' : '0'
					if (mode === 'zoom-in') {
						aniNum = type ? 0.8 : 1
					}
					if (mode === 'zoom-out') {
						aniNum = type ? 1.2 : 1
					}
					if (mode === 'slide-right') {
						aniNum = type ? '100%' : '0'
					}
					if (mode === 'slide-bottom') {
						aniNum = type ? '100%' : '0'
					}
				}
				this.animation[this.animationMode()[mode]](aniNum)
			}
			if (typeof this.modeClass === 'string') {
				buildTranfrom(type, this.modeClass)
			} else {
				this.modeClass.forEach(mode => {
					buildTranfrom(type, mode)
				})
			}

			return this.animation
		},
		animationType(type) {
			return {
				fade: type ? 1 : 0,
				'slide-top': `translateY(${type ? '0' : '-100%'})`,
				'slide-right': `translateX(${type ? '0' : '100%'})`,
				'slide-bottom': `translateY(${type ? '0' : '100%'})`,
				'slide-left': `translateX(${type ? '0' : '-100%'})`,
				'zoom-in': `scaleX(${type ? 1 : 0.8}) scaleY(${type ? 1 : 0.8})`,
				'zoom-out': `scaleX(${type ? 1 : 1.2}) scaleY(${type ? 1 : 1.2})`
			}
		},
		// 内置动画类型与实际动画对应字典
		animationMode() {
			return {
				fade: 'opacity',
				'slide-top': 'translateY',
				'slide-right': 'translateX',
				'slide-bottom': 'translateY',
				'slide-left': 'translateX',
				'zoom-in': 'scale',
				'zoom-out': 'scale'
			}
		},
		// 驼峰转中横线
		toLine(name) {
			return name.replace(/([A-Z])/g, '-$1').toLowerCase()
		}
	}
}
</script>

<style></style>
