/**
* click animations component
*
* Learn more: https://sli.dev/guide/animations.html#click-animation
*/
import type { VNode, VNodeArrayChildren } from 'vue'
import { toArray } from '@antfu/utils'
import { Comment, createVNode, defineComponent, h, isVNode, resolveDirective, withDirectives } from 'vue'
import { normalizeSingleAtValue } from '../composables/useClicks'
import VClickGap from './VClickGap.vue'
const listTags = ['ul', 'ol']
const RE_WHITESPACE_OR_COMMA = /[\s,]+/
export default defineComponent({
props: {
depth: {
type: [Number, String],
default: 1,
},
every: {
type: [Number, String],
default: 1,
},
at: {
type: [Number, String],
default: '+1',
},
hide: {
type: Boolean,
default: false,
},
/**
* @deprecated use `animation` instead
*/
fade: {
type: Boolean,
default: false,
},
animation: {
type: [String, Array],
default: undefined,
},
handleSpecialElements: {
type: Boolean,
default: true,
},
},
render() {
const every = +this.every
const at = normalizeSingleAtValue(this.at)
const isRelative = typeof at === 'string'
let elements = this.$slots.default?.()
if (at == null || !elements) {
return elements
}
const click = resolveDirective('click')!
const applyDirective = (node: VNode, value: number | string) => {
const modifiers: Record = {
hide: this.hide,
}
if (typeof this.animation === 'string') {
this.animation.split(RE_WHITESPACE_OR_COMMA).forEach((a) => {
if (a)
modifiers[a] = true
})
}
else if (Array.isArray(this.animation)) {
this.animation.forEach(a => modifiers[a] = true)
}
if (this.fade)
modifiers.fade = true
return withDirectives(node, [[
click,
value,
'',
modifiers,
]])
}
const openAllTopLevelSlots = (children: T): T => {
return children.flatMap((i) => {
if (isVNode(i) && typeof i.type === 'symbol' && Array.isArray(i.children))
return openAllTopLevelSlots(i.children)
else
return [i]
}) as T
}
elements = openAllTopLevelSlots(toArray(elements))
const mapSubList = (children: VNodeArrayChildren, depth = 1): VNodeArrayChildren => {
const vNodes = openAllTopLevelSlots(children).map((i) => {
if (!isVNode(i))
return i
if (listTags.includes(i.type as string) && Array.isArray(i.children)) {
// eslint-disable-next-line ts/no-use-before-define
const vNodes = mapChildren(i.children, depth + 1)
return h(i, {}, vNodes)
}
return h(i)
})
return vNodes
}
let globalIdx = 1
let execIdx = 0
const mapChildren = (children: VNodeArrayChildren, depth = 1): VNodeArrayChildren => {
const vNodes = openAllTopLevelSlots(children).map((i) => {
if (!isVNode(i) || i.type === Comment)
return i
const thisShowIdx = +at + Math.ceil(globalIdx++ / every) - 1
let vNode
if (depth < +this.depth && Array.isArray(i.children))
vNode = h(i, {}, mapSubList(i.children, depth))
else
vNode = h(i)
const delta = thisShowIdx - execIdx
execIdx = thisShowIdx
return applyDirective(
vNode,
isRelative
? delta >= 0 ? `+${delta}` : `${delta}`
: thisShowIdx,
)
})
return vNodes
}
const lastGap = () => createVNode(VClickGap, {
size: +at + Math.ceil((globalIdx - 1) / every) - 1 - execIdx,
})
if (this.handleSpecialElements) {
// handle ul, ol list
if (elements.length === 1 && listTags.includes(elements[0].type as string) && Array.isArray(elements[0].children))
return h(elements[0], {}, [...mapChildren(elements[0].children), lastGap()])
if (elements.length === 1 && elements[0].type as string === 'table') {
const tableNode = elements[0]
if (Array.isArray(tableNode.children)) {
return h(tableNode, {}, tableNode.children.map((i) => {
if (!isVNode(i))
return i
else if (i.type === 'tbody' && Array.isArray(i.children))
return h(i, {}, [...mapChildren(i.children), lastGap()])
else
return h(i)
}))
}
}
}
return [...mapChildren(elements), lastGap()]
},
})