/** * 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()] }, })