// @ts-nocheck import { raf } from '@/uni_modules/lime-shared/raf'; // #ifndef UNI-APP-X import { watchEffect, ref, nextTick } from '@/uni_modules/lime-shared/vue' // #endif export type TransitionEmitStatus = "before-enter" | "enter" | "after-enter" | "before-leave" | "leave" | "after-leave" export type TransitionStatus = '' | 'enter' | 'leave'; export type UseTransitionOptions = { element ?: Ref, enterClass ?: string, enterActiveClass ?: string, enterToClass ?: string, leaveClass ?: string, leaveActiveClass ?: string, leaveToClass ?: string, appear ?: boolean, defaultName ?: string, name ?: () => string, visible ?: () => boolean, emits ?: (name : TransitionEmitStatus) => void, onNextTick ?: (name : TransitionEmitStatus) => Promise, duration ?: number } type ClassNameMap = Map; export type UseTransitionReturn = { state : Ref, display : Ref, inited : Ref, classes : Ref, name : Ref, finished : () => void, toggle : (v : boolean) => void, } export function useTransition(options : UseTransitionOptions) : UseTransitionReturn { const state = ref(false); const display = ref(false); const inited = ref(false); const classes = ref(''); const name = ref(options.defaultName ?? 'fade'); const enterClass = options.enterClass ?? ''; const enterActiveClass = options.enterActiveClass ?? ''; const enterToClass = options.enterToClass ?? ''; const leaveActiveClass = options.leaveActiveClass ?? ''; const leaveToClass = options.leaveToClass ?? ''; const leaveClass = options.leaveClass ?? ''; const appear = options.appear ?? false const duration = options.duration ?? 300; let status : TransitionStatus = ''; let isTransitionEnd = false; let isTransitioning = false; let timeoutId = -1 const emitEvent = (event : TransitionEmitStatus) => { options.emits?.(event); }; // 结束 const finished = () => { if (isTransitionEnd) return; isTransitionEnd = true; emitEvent(`after-${status}`) if (display.value && !state.value) { display.value = false } } const sleep = () : Promise => { return new Promise((resolve) => { nextTick(() => { raf(() => { // #ifdef APP-ANDROID || APP-IOS || APP-HARMONY if (options.element?.value != null) { options.element?.value?.getBoundingClientRectAsync()?.then(res => { resolve() }) } else { resolve() } // #endif // #ifndef APP-ANDROID || APP-IOS || APP-HARMONY resolve() // #endif }) }) }) } const getClassNames = (name : string) : ClassNameMap => { return new Map([ ['enter', `l-${name}-enter l-${name}-enter-active ${enterClass} ${enterActiveClass}`], ['enter-to', `l-${name}-enter-to l-${name}-enter-active ${enterToClass} ${enterActiveClass}`], ['leave', `l-${name}-leave l-${name}-leave-active ${leaveClass} ${leaveActiveClass}`], ['leave-to', `l-${name}-leave-to l-${name}-leave-active ${leaveToClass} ${leaveActiveClass}`] ]) }; const transitionQueue = ref([]); const performTransition = async (newStatus : TransitionStatus, eventName : TransitionStatus) => { if (status == newStatus) return transitionQueue.value.push(newStatus); if (isTransitioning) return; isTransitioning = true; // 防止因结束 又切换为开始导致闪烁 isTransitionEnd = true; while (transitionQueue.value.length > 0) { const currentStatus = transitionQueue.value.shift()!; status = currentStatus; emitEvent(`before-${eventName}`); await sleep(); await sleep(); if (status != currentStatus) continue; const classNames = getClassNames(name.value); inited.value = true; display.value = true; classes.value = classNames.get(eventName)!; emitEvent(eventName); const executeAfterTick = options.onNextTick?.(eventName); if (executeAfterTick != null) { await executeAfterTick; } await sleep(); // #ifdef MP await sleep(); await sleep(); // #endif if (status != currentStatus) continue; classes.value = classNames.get(`${eventName}-to`)!; // isTransitionEnd = false; // 防止最后无法关闭 if (status == 'leave') { setTimeout(() => { finished() }, duration) } } // 不延迟 会导致快速切换时 因display none 闪烁 clearTimeout(timeoutId) timeoutId = setTimeout(() => { if (transitionQueue.value.length == 0 && status == newStatus) { isTransitionEnd = false; } }, duration * 0.8) isTransitioning = false; } // 定义进入过渡的函数 const enter = () => { performTransition('enter', 'enter'); } const leave = () => { performTransition('leave', 'leave'); } let init = false; watchEffect(() => { if (options.visible == null) return state.value = options.visible!(); if (!appear && !init) { init = true return } if (state.value) { enter() } else { leave() } }) watchEffect(() => { if (options.name == null) return name.value = options.name!() }) const toggle = (v : boolean) => { state.value = v if (v) { enter() } else { leave() } } return { state, inited, display, classes, name, finished, toggle } as UseTransitionReturn }