2026-05-31 13:54:31 +02:00

162 lines
5.1 KiB
TypeScript

import type { ClicksElement, RawAtValue } from '@slidev/types'
import type { App, DirectiveBinding } from 'vue'
import { computed, watchEffect } from 'vue'
import {
CLASS_VCLICK_CURRENT,
CLASS_VCLICK_FADE,
CLASS_VCLICK_HIDDEN,
CLASS_VCLICK_HIDDEN_EXP,
CLASS_VCLICK_PRIOR,
CLASS_VCLICK_TARGET,
injectionClicksContext,
} from '../constants'
import { directiveInject } from '../utils'
export function createVClickDirectives() {
return {
install(app: App) {
app.directive<HTMLElement, RawAtValue>('click', {
// @ts-expect-error extra prop
name: 'v-click',
mounted(el, dir) {
const resolved = resolveClick(el, dir, dir.value)
if (resolved == null)
return
el.classList.toggle(CLASS_VCLICK_TARGET, true)
// Expose the resolved clicks info to the element to make it easier to understand and debug
el.dataset.slidevClicksStart = String(resolved.start)
if (Number.isFinite(resolved.end))
el.dataset.slidevClicksEnd = String(resolved.end)
// @ts-expect-error extra prop
el.watchStopHandle = watchEffect(() => {
const active = resolved.isActive.value
const current = resolved.isCurrent.value
const prior = active && !current
if (resolved.flagHide) {
el.classList.toggle(resolved.flagFade ? CLASS_VCLICK_FADE : CLASS_VCLICK_HIDDEN, active)
el.classList.toggle(CLASS_VCLICK_HIDDEN_EXP, active)
}
else {
el.classList.toggle(resolved.flagFade ? CLASS_VCLICK_FADE : CLASS_VCLICK_HIDDEN, !active)
}
el.classList.toggle(CLASS_VCLICK_CURRENT, current)
el.classList.toggle(CLASS_VCLICK_PRIOR, prior)
})
},
unmounted,
})
app.directive<HTMLElement, RawAtValue>('after', {
// @ts-expect-error extra prop
name: 'v-after',
mounted(el, dir) {
const resolved = resolveClick(el, dir, '+0')
if (resolved == null)
return
el.classList.toggle(CLASS_VCLICK_TARGET, true)
// @ts-expect-error extra prop
el.watchStopHandle = watchEffect(() => {
const active = resolved.isActive.value
const current = resolved.isCurrent.value
const prior = active && !current
if (resolved.flagHide) {
el.classList.toggle(resolved.flagFade ? CLASS_VCLICK_FADE : CLASS_VCLICK_HIDDEN, active)
el.classList.toggle(CLASS_VCLICK_HIDDEN_EXP, active)
}
else {
el.classList.toggle(resolved.flagFade ? CLASS_VCLICK_FADE : CLASS_VCLICK_HIDDEN, !active)
}
el.classList.toggle(CLASS_VCLICK_CURRENT, current)
el.classList.toggle(CLASS_VCLICK_PRIOR, prior)
})
},
unmounted,
})
app.directive<HTMLElement, RawAtValue>('click-hide', {
// @ts-expect-error extra prop
name: 'v-click-hide',
mounted(el, dir) {
const resolved = resolveClick(el, dir, dir.value, true)
if (resolved == null)
return
el.classList.toggle(CLASS_VCLICK_TARGET, true)
// @ts-expect-error extra prop
el.watchStopHandle = watchEffect(() => {
const active = resolved.isActive.value
const current = resolved.isCurrent.value
const prior = active && !current
el.classList.toggle(resolved.flagFade ? CLASS_VCLICK_FADE : CLASS_VCLICK_HIDDEN, active)
el.classList.toggle(CLASS_VCLICK_HIDDEN_EXP, active)
el.classList.toggle(CLASS_VCLICK_CURRENT, current)
el.classList.toggle(CLASS_VCLICK_PRIOR, prior)
})
},
unmounted,
})
},
}
}
export const resolvedClickMap = new Map<ClicksElement, ReturnType<typeof resolveClick>>()
export function resolveClick(el: Element | string, dir: DirectiveBinding<any>, value: RawAtValue, explicitHide = false) {
const ctx = directiveInject(dir, injectionClicksContext)?.value
if (!el || !ctx)
return null
const flagHide = explicitHide || (dir.modifiers.hide !== false && dir.modifiers.hide != null)
const flagFade = dir.modifiers.fade !== false && dir.modifiers.fade != null
const info = ctx.calculate(value)
if (!info)
return null
ctx.register(el, info)
const isShown = computed(() => flagHide ? !info.isActive.value : info.isActive.value)
const visibilityState = computed(() => {
if (isShown.value)
return 'shown'
if (Number.isFinite(info.end))
return ctx.current < info.start ? 'before' : 'after'
else
return flagHide ? 'after' : 'before'
})
const resolved = {
...info,
isShown,
visibilityState,
flagFade,
flagHide,
}
resolvedClickMap.set(el, resolved)
return resolved
}
function unmounted(el: HTMLElement, dir: DirectiveBinding<any>) {
el.classList.toggle(CLASS_VCLICK_TARGET, false)
const ctx = directiveInject(dir, injectionClicksContext)?.value
ctx?.unregister(el)
// @ts-expect-error extra prop
el.watchStopHandle?.()
}