返回 slidev
VClicks.ts
根目录 / packages / client / builtin / VClicks.ts
1 /**
2 * <v-clicks/> click animations component
3 *
4 * Learn more: https://sli.dev/guide/animations.html#click-animation
5 */
6
7 import type { VNode, VNodeArrayChildren } from 'vue'
8 import { toArray } from '@antfu/utils'
9 import { Comment, createVNode, defineComponent, h, isVNode, resolveDirective, withDirectives } from 'vue'
10 import { normalizeSingleAtValue } from '../composables/useClicks'
11 import VClickGap from './VClickGap.vue'
12
13 const listTags = ['ul', 'ol']
14 const RE_WHITESPACE_OR_COMMA = /[\s,]+/
15
16 export default defineComponent({
17 props: {
18 depth: {
19 type: [Number, String],
20 default: 1,
21 },
22 every: {
23 type: [Number, String],
24 default: 1,
25 },
26 at: {
27 type: [Number, String],
28 default: '+1',
29 },
30 hide: {
31 type: Boolean,
32 default: false,
33 },
34 /**
35 * @deprecated use `animation` instead
36 */
37 fade: {
38 type: Boolean,
39 default: false,
40 },
41 animation: {
42 type: [String, Array<string>],
43 default: undefined,
44 },
45 handleSpecialElements: {
46 type: Boolean,
47 default: true,
48 },
49 },
50 render() {
51 const every = +this.every
52 const at = normalizeSingleAtValue(this.at)
53 const isRelative = typeof at === 'string'
54
55 let elements = this.$slots.default?.()
56
57 if (at == null || !elements) {
58 return elements
59 }
60
61 const click = resolveDirective('click')!
62
63 const applyDirective = (node: VNode, value: number | string) => {
64 const modifiers: Record<string, boolean> = {
65 hide: this.hide,
66 }
67 if (typeof this.animation === 'string') {
68 this.animation.split(RE_WHITESPACE_OR_COMMA).forEach((a) => {
69 if (a)
70 modifiers[a] = true
71 })
72 }
73 else if (Array.isArray(this.animation)) {
74 this.animation.forEach(a => modifiers[a] = true)
75 }
76 if (this.fade)
77 modifiers.fade = true
78
79 return withDirectives(node, [[
80 click,
81 value,
82 '',
83 modifiers,
84 ]])
85 }
86
87 const openAllTopLevelSlots = <T extends VNodeArrayChildren | VNode[]>(children: T): T => {
88 return children.flatMap((i) => {
89 if (isVNode(i) && typeof i.type === 'symbol' && Array.isArray(i.children))
90 return openAllTopLevelSlots(i.children)
91 else
92 return [i]
93 }) as T
94 }
95
96 elements = openAllTopLevelSlots(toArray(elements))
97
98 const mapSubList = (children: VNodeArrayChildren, depth = 1): VNodeArrayChildren => {
99 const vNodes = openAllTopLevelSlots(children).map((i) => {
100 if (!isVNode(i))
101 return i
102 if (listTags.includes(i.type as string) && Array.isArray(i.children)) {
103 // eslint-disable-next-line ts/no-use-before-define
104 const vNodes = mapChildren(i.children, depth + 1)
105 return h(i, {}, vNodes)
106 }
107 return h(i)
108 })
109 return vNodes
110 }
111
112 let globalIdx = 1
113 let execIdx = 0
114 const mapChildren = (children: VNodeArrayChildren, depth = 1): VNodeArrayChildren => {
115 const vNodes = openAllTopLevelSlots(children).map((i) => {
116 if (!isVNode(i) || i.type === Comment)
117 return i
118
119 const thisShowIdx = +at + Math.ceil(globalIdx++ / every) - 1
120
121 let vNode
122 if (depth < +this.depth && Array.isArray(i.children))
123 vNode = h(i, {}, mapSubList(i.children, depth))
124 else
125 vNode = h(i)
126
127 const delta = thisShowIdx - execIdx
128 execIdx = thisShowIdx
129
130 return applyDirective(
131 vNode,
132 isRelative
133 ? delta >= 0 ? `+${delta}` : `${delta}`
134 : thisShowIdx,
135 )
136 })
137 return vNodes
138 }
139
140 const lastGap = () => createVNode(VClickGap, {
141 size: +at + Math.ceil((globalIdx - 1) / every) - 1 - execIdx,
142 })
143
144 if (this.handleSpecialElements) {
145 // handle ul, ol list
146 if (elements.length === 1 && listTags.includes(elements[0].type as string) && Array.isArray(elements[0].children))
147 return h(elements[0], {}, [...mapChildren(elements[0].children), lastGap()])
148
149 if (elements.length === 1 && elements[0].type as string === 'table') {
150 const tableNode = elements[0]
151 if (Array.isArray(tableNode.children)) {
152 return h(tableNode, {}, tableNode.children.map((i) => {
153 if (!isVNode(i))
154 return i
155 else if (i.type === 'tbody' && Array.isArray(i.children))
156 return h(i, {}, [...mapChildren(i.children), lastGap()])
157 else
158 return h(i)
159 }))
160 }
161 }
162 }
163
164 return [...mapChildren(elements), lastGap()]
165 },
166 })
167
167 lines TYPESCRIPT