vitepress-theme-element-plus 0.0.1 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/client/components/A11yTag.vue +29 -0
  2. package/client/components/ApiTyping.vue +54 -0
  3. package/client/components/Backdrop.vue +41 -0
  4. package/client/components/Bili.vue +94 -0
  5. package/client/components/Content.vue +150 -0
  6. package/client/components/DeprecatedTag.vue +19 -0
  7. package/client/components/Doc.vue +181 -0
  8. package/client/components/DocAside.vue +46 -0
  9. package/client/components/DocAsideOutline.vue +82 -0
  10. package/client/components/DocFooter.vue +159 -0
  11. package/client/components/Footer.vue +77 -0
  12. package/client/components/FooterCopyright.vue +27 -0
  13. package/client/components/Layout.vue +156 -0
  14. package/client/components/Link.vue +41 -0
  15. package/client/components/LocalNav.vue +160 -0
  16. package/client/components/Nav.vue +69 -0
  17. package/client/components/NavBar.vue +203 -0
  18. package/client/components/NavBarTitle.vue +75 -0
  19. package/client/components/Sidebar.vue +129 -0
  20. package/client/components/SidebarGroup.vue +51 -0
  21. package/client/components/SidebarItem.vue +302 -0
  22. package/client/components/Tag.vue +25 -0
  23. package/client/components/VPNavBarSearch.vue +23 -0
  24. package/client/components/VersionTag.vue +18 -0
  25. package/client/hooks/useBackTop.ts +74 -0
  26. package/client/hooks/useLangs.ts +50 -0
  27. package/client/hooks/useSidebar.ts +105 -0
  28. package/client/hooks/useSidebarControl.ts +78 -0
  29. package/client/hooks/useSize.ts +69 -0
  30. package/client/utils/client/common.ts +49 -0
  31. package/client/utils/client/outline.ts +113 -0
  32. package/client/utils/common.ts +86 -0
  33. package/dist/index.d.mts +5 -0
  34. package/dist/index.mjs +6 -0
  35. package/dist/markdown/plugins/external-link-icon.d.mts +6 -0
  36. package/dist/markdown/plugins/external-link-icon.mjs +26 -0
  37. package/dist/markdown/plugins/table-wrapper.d.mts +6 -0
  38. package/dist/markdown/plugins/table-wrapper.mjs +8 -0
  39. package/dist/markdown/plugins/tag.d.mts +6 -0
  40. package/dist/markdown/plugins/tag.mjs +25 -0
  41. package/dist/markdown/plugins/tooltip.d.mts +6 -0
  42. package/dist/markdown/plugins/tooltip.mjs +24 -0
  43. package/package.json +5 -5
  44. package/shared/constants.ts +3 -0
  45. package/styles/base.scss +37 -0
  46. package/styles/code.scss +283 -0
  47. package/styles/doc-content.scss +161 -0
  48. package/styles/index.scss +69 -0
  49. package/styles/tag-content.scss +30 -0
@@ -0,0 +1,302 @@
1
+ <script setup lang="ts">
2
+ import type { DefaultTheme } from 'vitepress'
3
+ import { Icon } from '@iconify/vue'
4
+ import { computed } from 'vue'
5
+ import { useSidebarControl } from '../hooks/useSidebarControl'
6
+ import Link from './Link.vue'
7
+ import VersionTag from './VersionTag.vue'
8
+
9
+ const props = defineProps<{
10
+ item: DefaultTheme.SidebarItem & {
11
+ hide?: boolean
12
+ icon?: string
13
+ promotion?: string
14
+ }
15
+ depth: number
16
+ }>()
17
+ const {
18
+ collapsed,
19
+ collapsible,
20
+ isLink,
21
+ isActiveLink,
22
+ hasActiveLink,
23
+ hasChildren,
24
+ toggle,
25
+ } = useSidebarControl(computed(() => props.item))
26
+
27
+ const sectionTag = computed(() => (hasChildren.value ? 'section' : 'div'))
28
+
29
+ const linkTag = computed(() => (isLink.value ? 'a' : 'div'))
30
+
31
+ const textTag = computed(() => {
32
+ return !hasChildren.value
33
+ ? 'p'
34
+ : props.depth + 2 === 7
35
+ ? 'p'
36
+ : `h${props.depth + 2}`
37
+ })
38
+
39
+ const itemRole = computed(() => (isLink.value ? undefined : 'button'))
40
+
41
+ const classes = computed(() => [
42
+ [`level-${props.depth}`],
43
+ { collapsible: collapsible.value },
44
+ { collapsed: collapsed.value },
45
+ { 'is-link': isLink.value },
46
+ { 'is-active': isActiveLink.value },
47
+ { 'has-active': hasActiveLink.value },
48
+ ])
49
+
50
+ function onItemInteraction(e: MouseEvent | Event) {
51
+ if ('key' in e && e.key !== 'Enter') {
52
+ return
53
+ }
54
+ !props.item.link && toggle()
55
+ }
56
+ function onCaretClick() {
57
+ props.item.link && toggle()
58
+ }
59
+
60
+ function onLinkAreaClick(e: MouseEvent) {
61
+ const elA = (e.target as HTMLElement).querySelector('a')
62
+ elA && elA.click()
63
+ }
64
+ </script>
65
+
66
+ <template>
67
+ <component :is="sectionTag" class="VPSidebarItem" :class="classes">
68
+ <div
69
+ v-if="item.text && !item.hide"
70
+ class="item"
71
+ :role="itemRole"
72
+ :tabindex="item.items && 0"
73
+ v-on="
74
+ item.items
75
+ ? { click: onItemInteraction, keydown: onItemInteraction }
76
+ : { click: onLinkAreaClick }
77
+ "
78
+ >
79
+ <div class="indicator" />
80
+ <Icon
81
+ v-if="item.icon && item.items"
82
+ :icon="item.icon"
83
+ style="margin-right: 5px;"
84
+ ssr
85
+ />
86
+ <Link
87
+ v-if="item.link"
88
+ :tag="linkTag"
89
+ class="link"
90
+ :href="item.link"
91
+ :rel="item.rel"
92
+ :target="item.target"
93
+ >
94
+ <Icon
95
+ v-if="item.icon && item.link"
96
+ :icon="item.icon"
97
+ class="text text-icon"
98
+ ssr
99
+ />
100
+ <component :is="textTag" class="text" v-html="item.text" />
101
+ <VersionTag v-if="item.promotion" class="version-tag" :version="item.promotion" />
102
+ </Link>
103
+ <component
104
+ :is="textTag"
105
+ v-else
106
+ class="text"
107
+ v-html="item.text"
108
+ />
109
+
110
+ <div
111
+ v-if="item.collapsed !== undefined && item.items && item.items.length"
112
+ class="caret"
113
+ role="button"
114
+ aria-label="toggle section"
115
+ tabindex="0"
116
+ @click="onCaretClick"
117
+ @keydown.enter="onCaretClick"
118
+ >
119
+ <span class="vpi-chevron-right caret-icon" />
120
+ </div>
121
+ </div>
122
+
123
+ <div v-if="item?.items && item?.items.length" class="items">
124
+ <template v-if="depth < 5">
125
+ <SidebarItem
126
+ v-for="i in item.items"
127
+ :key="i.text"
128
+ :item="i"
129
+ :depth="depth + 1"
130
+ />
131
+ </template>
132
+ </div>
133
+ </component>
134
+ </template>
135
+
136
+ <style scoped lang="scss">
137
+ .VPSidebarItem.collapsed.level-0 {
138
+ padding-bottom: 10px;
139
+ }
140
+
141
+ .item {
142
+ position: relative;
143
+ display: flex;
144
+ width: 100%;
145
+ display: flex;
146
+ align-items: center;
147
+ border-radius: 8px;
148
+ transition: background-color 0.25s;
149
+ cursor: pointer;
150
+ }
151
+
152
+ .VPSidebarItem.collapsible > .item {
153
+ cursor: pointer;
154
+ }
155
+
156
+ .indicator {
157
+ position: absolute;
158
+ top: 6px;
159
+ bottom: 6px;
160
+ left: -17px;
161
+ width: 2px;
162
+ border-radius: 2px;
163
+ transition: background-color 0.25s;
164
+ }
165
+
166
+ .VPSidebarItem.level-2.is-active > .item > .indicator,
167
+ .VPSidebarItem.level-3.is-active > .item > .indicator,
168
+ .VPSidebarItem.level-4.is-active > .item > .indicator,
169
+ .VPSidebarItem.level-5.is-active > .item > .indicator {
170
+ background-color: var(--vp-c-brand-1);
171
+ }
172
+
173
+ .link {
174
+ display: flex;
175
+ align-items: center;
176
+ padding: 10px 16px;
177
+ line-height: 20px;
178
+ font-size: 13px;
179
+ }
180
+
181
+ .text {
182
+ flex-grow: 1;
183
+ line-height: 24px;
184
+ transition: color 0.25s;
185
+ }
186
+ .text-icon {
187
+ flex-grow: 0;
188
+ padding: 0;
189
+ margin-right: 4px;
190
+ }
191
+ .version-tag {
192
+ margin-left: 8px;
193
+ }
194
+ .VPSidebarItem.level-0 > .item {
195
+ color: var(--vp-c-text-1);
196
+ margin-bottom: 8px;
197
+ line-height: 24px;
198
+ }
199
+ .VPSidebarItem.level-0 > .item h2 {
200
+ color: var(--vp-c-text-1);
201
+ font-size: 1rem;
202
+ font-weight: 700;
203
+ margin-bottom: 8px;
204
+ line-height: 24px;
205
+ }
206
+
207
+ .VPSidebarItem.level-1,
208
+ .VPSidebarItem.level-2,
209
+ .VPSidebarItem.level-3,
210
+ .VPSidebarItem.level-4,
211
+ .VPSidebarItem.level-5 {
212
+ font-weight: 500;
213
+ color: var(--vp-c-text-2);
214
+ }
215
+
216
+ .VPSidebarItem.level-0.is-link > .item > .link:hover,
217
+ .VPSidebarItem.level-1.is-link > .item > .link:hover,
218
+ .VPSidebarItem.level-2.is-link > .item > .link:hover,
219
+ .VPSidebarItem.level-3.is-link > .item > .link:hover,
220
+ .VPSidebarItem.level-4.is-link > .item > .link:hover,
221
+ .VPSidebarItem.level-5.is-link > .item > .link:hover {
222
+ color: var(--vp-c-brand-1);
223
+ }
224
+
225
+ .VPSidebarItem.level-0.has-active > .item,
226
+ .VPSidebarItem.level-1.has-active > .item,
227
+ .VPSidebarItem.level-2.has-active > .item,
228
+ .VPSidebarItem.level-3.has-active > .item,
229
+ .VPSidebarItem.level-4.has-active > .item,
230
+ .VPSidebarItem.level-5.has-active > .item,
231
+ .VPSidebarItem.level-0.has-active > .item > .link,
232
+ .VPSidebarItem.level-1.has-active > .item > .link,
233
+ .VPSidebarItem.level-2.has-active > .item > .link,
234
+ .VPSidebarItem.level-3.has-active > .item > .link,
235
+ .VPSidebarItem.level-4.has-active > .item > .link,
236
+ .VPSidebarItem.level-5.has-active > .item > .link {
237
+ color: var(--vp-c-text-1);
238
+ }
239
+
240
+ .VPSidebarItem.level-0.is-active > .item .link,
241
+ .VPSidebarItem.level-1.is-active > .item .link,
242
+ .VPSidebarItem.level-2.is-active > .item .link,
243
+ .VPSidebarItem.level-3.is-active > .item .link,
244
+ .VPSidebarItem.level-4.is-active > .item .link,
245
+ .VPSidebarItem.level-5.is-active > .item .link {
246
+ color: var(--vp-c-brand-1);
247
+ font-weight: 600;
248
+ }
249
+
250
+ .VPSidebarItem.level-0.is-active > .item,
251
+ .VPSidebarItem.level-1.is-active > .item,
252
+ .VPSidebarItem.level-2.is-active > .item,
253
+ .VPSidebarItem.level-3.is-active > .item,
254
+ .VPSidebarItem.level-4.is-active > .item,
255
+ .VPSidebarItem.level-5.is-active > .item {
256
+ background-color: var(--el-color-primary-light-9);
257
+ }
258
+
259
+ .caret {
260
+ display: flex;
261
+ justify-content: center;
262
+ align-items: center;
263
+ margin-right: -7px;
264
+ width: 32px;
265
+ height: 32px;
266
+ color: var(--vp-c-text-3);
267
+ cursor: pointer;
268
+ transition: color 0.25s;
269
+ flex-shrink: 0;
270
+ }
271
+
272
+ .item:hover .caret {
273
+ color: var(--vp-c-text-2);
274
+ }
275
+
276
+ .item:hover .caret:hover {
277
+ color: var(--vp-c-text-1);
278
+ }
279
+
280
+ .caret-icon {
281
+ font-size: 18px;
282
+ transform: rotate(90deg);
283
+ transition: transform 0.25s;
284
+ }
285
+
286
+ .VPSidebarItem.collapsed .caret-icon {
287
+ transform: rotate(0);
288
+ }
289
+
290
+ .VPSidebarItem.level-1 .items,
291
+ .VPSidebarItem.level-2 .items,
292
+ .VPSidebarItem.level-3 .items,
293
+ .VPSidebarItem.level-4 .items,
294
+ .VPSidebarItem.level-5 .items {
295
+ border-left: 1px solid var(--vp-c-divider);
296
+ padding-left: 16px;
297
+ }
298
+
299
+ .VPSidebarItem.collapsed .items {
300
+ display: none;
301
+ }
302
+ </style>
@@ -0,0 +1,25 @@
1
+ <script lang="ts" setup>
2
+ interface Props {
3
+ text: string
4
+ }
5
+ defineProps<Props>()
6
+ </script>
7
+
8
+ <template>
9
+ <span class="VPTTag">
10
+ {{ text }}
11
+ </span>
12
+ </template>
13
+
14
+ <style lang="scss" scoped>
15
+ .VPTTag {
16
+ font-size: 12px;
17
+ line-height: 26px;
18
+ background-color: var(--vpt-bg-opacity);
19
+ border-radius: 6px;
20
+ padding: 0 12px;
21
+ box-sizing: border-box;
22
+ margin-right: 12px;
23
+ margin-bottom: 12px;
24
+ }
25
+ </style>
@@ -0,0 +1,23 @@
1
+ <script lang="ts" setup>
2
+ import VPNavBarSearch from 'vitepress/dist/client/theme-default/components/VPNavBarSearch.vue'
3
+ // compatible with vitepress-plugin-pagefind
4
+ </script>
5
+
6
+ <template>
7
+ <VPNavBarSearch />
8
+ </template>
9
+
10
+ <style>
11
+ @media (min-width: 768px) {
12
+ .VPNavBarSearch.VPNavBarSearch {
13
+ flex-grow: unset;
14
+ padding-right: 24px;
15
+ }
16
+ }
17
+
18
+ @media (min-width: 960px) {
19
+ .VPNavBarSearch.VPNavBarSearch {
20
+ padding-right: 32px;
21
+ }
22
+ }
23
+ </style>
@@ -0,0 +1,18 @@
1
+ <script lang="ts" setup>
2
+ import { ElTag } from 'element-plus'
3
+
4
+ defineProps<{
5
+ version: string
6
+ }>()
7
+ </script>
8
+
9
+ <template>
10
+ <ElTag
11
+ size="small"
12
+ effect="plain"
13
+ hit
14
+ round
15
+ >
16
+ {{ version }}
17
+ </ElTag>
18
+ </template>
@@ -0,0 +1,74 @@
1
+ import { isClient } from '@vueuse/core'
2
+ import { throttle } from 'es-toolkit'
3
+ import { onBeforeUnmount, onMounted, ref } from 'vue'
4
+
5
+ const threshold = 960
6
+
7
+ const cubic = (value: number): number => value ** 3
8
+ function easeInOutCubic(value: number): number {
9
+ return value < 0.5 ? cubic(value * 2) / 2 : 1 - cubic((1 - value) * 2) / 2
10
+ }
11
+
12
+ export function useBackTop(offset = 200) {
13
+ const shouldShow = ref(false)
14
+ const throttleResize = throttle(onResize, 300)
15
+ const throttleScroll = throttle(onScroll, 160)
16
+
17
+ onMounted(() => {
18
+ if (!isClient)
19
+ return
20
+ onResize()
21
+ onScroll()
22
+ window.addEventListener('resize', throttleResize)
23
+ })
24
+
25
+ onBeforeUnmount(() => {
26
+ if (!isClient)
27
+ return
28
+ window.removeEventListener('resize', throttleResize)
29
+ window.removeEventListener('scroll', throttleScroll)
30
+ })
31
+
32
+ const scrollToTop = () => {
33
+ const beginTime = Date.now()
34
+ const beginValue = document.documentElement.scrollTop
35
+ const rAF = window.requestAnimationFrame
36
+ const frameFunc = () => {
37
+ const progress = (Date.now() - beginTime) / 500
38
+ if (progress < 1) {
39
+ document.documentElement.scrollTop
40
+ = beginValue * (1 - easeInOutCubic(progress))
41
+ rAF(frameFunc)
42
+ }
43
+ else {
44
+ document.documentElement.scrollTop = 0
45
+ }
46
+ }
47
+ rAF(frameFunc)
48
+ }
49
+
50
+ function onResize() {
51
+ if (!isClient)
52
+ return
53
+
54
+ const { clientWidth } = document.body
55
+
56
+ if (clientWidth < threshold) {
57
+ window.addEventListener('scroll', throttleScroll)
58
+ }
59
+ else {
60
+ window.removeEventListener('scroll', throttleScroll)
61
+ }
62
+ }
63
+
64
+ function onScroll() {
65
+ if (!isClient)
66
+ return
67
+ shouldShow.value = document.documentElement.scrollTop > offset
68
+ }
69
+
70
+ return {
71
+ shouldShow,
72
+ scrollToTop,
73
+ }
74
+ }
@@ -0,0 +1,50 @@
1
+ import { useData } from 'vitepress'
2
+ import { computed } from 'vue'
3
+ import { ensureStartingSlash } from '../utils/common'
4
+
5
+ export function useLangs({ correspondingLink = false } = {}) {
6
+ const { site, localeIndex, page, theme, hash } = useData()
7
+ const currentLang = computed(() => ({
8
+ label: site.value.locales[localeIndex.value]?.label,
9
+ link:
10
+ site.value.locales[localeIndex.value]?.link
11
+ || (localeIndex.value === 'root' ? '/' : `/${localeIndex.value}/`),
12
+ }))
13
+
14
+ const localeLinks = computed(() =>
15
+ Object.entries(site.value.locales).flatMap(([key, value]) =>
16
+ currentLang.value.label === value.label
17
+ ? []
18
+ : {
19
+ text: value.label,
20
+ link:
21
+ normalizeLink(
22
+ value.link || (key === 'root' ? '/' : `/${key}/`),
23
+ theme.value.i18nRouting !== false && correspondingLink,
24
+ page.value.relativePath.slice(
25
+ currentLang.value.link.length - 1,
26
+ ),
27
+ !site.value.cleanUrls,
28
+ ) + hash.value,
29
+ },
30
+ ),
31
+ )
32
+
33
+ return { localeLinks, currentLang }
34
+ }
35
+
36
+ function normalizeLink(
37
+ link: string,
38
+ addPath: boolean,
39
+ path: string,
40
+ addExt: boolean,
41
+ ) {
42
+ return addPath
43
+ ? link.replace(/\/$/, '')
44
+ + ensureStartingSlash(
45
+ path
46
+ .replace(/(^|\/)index\.md$/, '$1')
47
+ .replace(/\.md$/, addExt ? '.html' : ''),
48
+ )
49
+ : link
50
+ }
@@ -0,0 +1,105 @@
1
+ import { useMediaQuery } from '@vueuse/core'
2
+ import { useData } from 'vitepress'
3
+ import { computed, onMounted, onUnmounted, ref, watch, watchEffect } from 'vue'
4
+ import { NOT_ARTICLE_LAYOUTS } from '../../shared/constants'
5
+
6
+ export function useSidebar() {
7
+ const { frontmatter, theme } = useData()
8
+ const is960 = useMediaQuery('(min-width: 960px)')
9
+ const isOpen = ref(false)
10
+ const _sidebar = computed(() => {
11
+ const sidebarConfig = theme.value.sidebar
12
+ return sidebarConfig ?? []
13
+ })
14
+
15
+ const sidebar = ref(_sidebar.value)
16
+
17
+ watch(_sidebar, (next, prev) => {
18
+ if (JSON.stringify(next) !== JSON.stringify(prev))
19
+ sidebar.value = _sidebar.value
20
+ })
21
+
22
+ const hasSidebar = computed(() => {
23
+ return (
24
+ frontmatter.value.sidebar !== false
25
+ && sidebar.value.length > 0
26
+ && frontmatter.value.layout !== 'home'
27
+ )
28
+ })
29
+
30
+ const hasAside = computed(() => {
31
+ if (NOT_ARTICLE_LAYOUTS.includes(frontmatter.value.layout)) {
32
+ return false
33
+ }
34
+ if (frontmatter.value.aside !== undefined && frontmatter.value.aside !== null)
35
+ return !!frontmatter.value.aside
36
+
37
+ return theme.value.aside !== false
38
+ })
39
+
40
+ const leftAside = computed(() => {
41
+ if (hasAside) {
42
+ return frontmatter.value.aside === null
43
+ ? theme.value.aside === 'left'
44
+ : frontmatter.value.aside === 'left'
45
+ }
46
+ return false
47
+ })
48
+
49
+ const isSidebarEnabled = computed(() => hasSidebar.value && is960.value)
50
+
51
+ const sidebarGroups = computed(() => {
52
+ return hasSidebar.value ?? []
53
+ })
54
+
55
+ function open() {
56
+ isOpen.value = true
57
+ }
58
+
59
+ function close() {
60
+ isOpen.value = false
61
+ }
62
+
63
+ function toggle() {
64
+ isOpen.value ? close() : open()
65
+ }
66
+
67
+ return {
68
+ isOpen,
69
+ sidebar,
70
+ sidebarGroups,
71
+ hasSidebar,
72
+ hasAside,
73
+ leftAside,
74
+ isSidebarEnabled,
75
+ open,
76
+ close,
77
+ toggle,
78
+ }
79
+ }
80
+
81
+ export function useCloseSidebarOnEscape() {
82
+ let triggerElement: HTMLButtonElement | undefined
83
+ const { isOpen, close } = useSidebar()
84
+
85
+ watchEffect(() => {
86
+ triggerElement = isOpen.value
87
+ ? (document.activeElement as HTMLButtonElement)
88
+ : undefined
89
+ })
90
+
91
+ onMounted(() => {
92
+ window.addEventListener('keyup', onEscape)
93
+ })
94
+
95
+ onUnmounted(() => {
96
+ window.removeEventListener('keyup', onEscape)
97
+ })
98
+
99
+ function onEscape(e: KeyboardEvent) {
100
+ if (e.key === 'Escape' && isOpen.value) {
101
+ close()
102
+ triggerElement?.focus()
103
+ }
104
+ }
105
+ }
@@ -0,0 +1,78 @@
1
+ import type { DefaultTheme } from 'vitepress'
2
+ import type { ComputedRef, Ref } from 'vue'
3
+ import { useData } from 'vitepress'
4
+ import { computed, onMounted, ref, watch, watchEffect, watchPostEffect } from 'vue'
5
+ import { hasActiveLink as containsActiveLink } from '../utils/client/common'
6
+
7
+ import { isActive } from '../utils/common'
8
+
9
+ export interface SidebarControl {
10
+ collapsed: Ref<boolean>
11
+ collapsible: ComputedRef<boolean>
12
+ isLink: ComputedRef<boolean>
13
+ isActiveLink: Ref<boolean>
14
+ hasActiveLink: ComputedRef<boolean>
15
+ hasChildren: ComputedRef<boolean>
16
+ toggle: () => void
17
+ }
18
+
19
+ export function useSidebarControl(
20
+ item: ComputedRef<DefaultTheme.SidebarItem>,
21
+ ): SidebarControl {
22
+ const { page, hash } = useData()
23
+ const collapsed = ref(false)
24
+
25
+ const collapsible = computed(() => {
26
+ return item.value.collapsed !== undefined
27
+ })
28
+
29
+ const isLink = computed(() => {
30
+ return !!item.value.link
31
+ })
32
+
33
+ const isActiveLink = ref(false)
34
+ const updateIsActiveLink = () => {
35
+ isActiveLink.value = isActive(page.value.relativePath, item.value.link)
36
+ }
37
+
38
+ watch([page, item, hash], updateIsActiveLink)
39
+ onMounted(updateIsActiveLink)
40
+
41
+ const hasActiveLink = computed(() => {
42
+ if (isActiveLink.value) {
43
+ return true
44
+ }
45
+
46
+ return item.value.items
47
+ ? containsActiveLink(page.value.relativePath, item.value.items)
48
+ : false
49
+ })
50
+
51
+ const hasChildren = computed(() => {
52
+ return !!(item.value.items && item.value.items.length)
53
+ })
54
+
55
+ watchEffect(() => {
56
+ collapsed.value = !!(collapsible.value && item.value.collapsed)
57
+ })
58
+
59
+ watchPostEffect(() => {
60
+ ;(isActiveLink.value || hasActiveLink.value) && (collapsed.value = false)
61
+ })
62
+
63
+ function toggle() {
64
+ if (collapsible.value) {
65
+ collapsed.value = !collapsed.value
66
+ }
67
+ }
68
+
69
+ return {
70
+ collapsed,
71
+ collapsible,
72
+ isLink,
73
+ isActiveLink,
74
+ hasActiveLink,
75
+ hasChildren,
76
+ toggle,
77
+ }
78
+ }