diff --git a/docs/.vitepress/devui-theme/components/Page.vue b/docs/.vitepress/devui-theme/components/Page.vue
index 2aee748b10c2f02e1e770599e4fe68baca22d639..cb29a342e04da2ff6e3eafeb152088341a40a733 100644
--- a/docs/.vitepress/devui-theme/components/Page.vue
+++ b/docs/.vitepress/devui-theme/components/Page.vue
@@ -27,6 +27,7 @@ import PageToc from "./PageToc.vue"
@media (min-width: 720px) {
.page {
margin-left: 16.4rem;
+ margin-right: 40px;
}
}
diff --git a/docs/.vitepress/devui-theme/components/PageToc.vue b/docs/.vitepress/devui-theme/components/PageToc.vue
index 5efcd8af45d8b7c193e7df56cb871e37d53ec935..d1666e33113c73d4b722ba372eb1648977074e37 100644
--- a/docs/.vitepress/devui-theme/components/PageToc.vue
+++ b/docs/.vitepress/devui-theme/components/PageToc.vue
@@ -1,22 +1,29 @@
-
@@ -27,7 +34,7 @@ const headers = useToc()
.devui-content-nav {
width: 240px;
position: fixed;
- top: 90px;
+ top: 50px;
right: 0;
height: 100%;
z-index: 1;
@@ -73,14 +80,14 @@ const headers = useToc()
a.current {
color: $devui-link;
}
-
- &.active {
- color: $devui-link;
- }
}
}
}
+.active {
+ color: $devui-link !important;
+}
+
@media (max-width: 1800px) {
.devui-content-nav {
width: 150px;
diff --git a/docs/.vitepress/devui-theme/composables/activeBar.ts b/docs/.vitepress/devui-theme/composables/activeBar.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b8d10d5c101c8c003423980b96e25f9899cddce6
--- /dev/null
+++ b/docs/.vitepress/devui-theme/composables/activeBar.ts
@@ -0,0 +1,136 @@
+import { onMounted, onUnmounted, onUpdated } from 'vue'
+
+import type { Ref } from 'vue'
+
+// 防抖节流控制
+export const throttleAndDebounce = (fn: () => any, delay: number) => {
+ let timeout: ReturnType
+ let called = false
+ return () => {
+ if (timeout) {
+ clearTimeout(timeout)
+ }
+ if (!called) {
+ fn()
+ called = true
+ setTimeout(() => {
+ called = false
+ }, delay)
+ } else {
+ timeout = setTimeout(fn, delay)
+ }
+ }
+}
+
+export function useActiveSidebarLinks(
+ container: Ref,
+ marker: Ref
+) {
+ const onScroll = throttleAndDebounce(setActiveLink, 150)
+ function setActiveLink() {
+ const sidebarLinks = getSidebarLinks()
+ const anchors = getAnchors(sidebarLinks)
+
+ if (
+ anchors.length &&
+ window.scrollY + window.innerHeight === document.body.offsetHeight
+ ) {
+ activateLink(anchors[anchors.length - 1].hash)
+ return
+ }
+ for (let i = 0; i < anchors.length; i++) {
+ const anchor = anchors[i]
+ const nextAnchor = anchors[i + 1]
+ const [isActive, hash] = isAnchorActive(i, anchor, nextAnchor)
+ if (isActive) {
+ history.replaceState(
+ null,
+ document.title,
+ hash ? (hash as string) : ' '
+ )
+ activateLink(hash as string)
+ return
+ }
+ }
+ }
+
+ let prevActiveLink: HTMLAnchorElement | null = null
+
+ function activateLink(hash: string) {
+ deactiveLink(prevActiveLink)
+
+ const activeLink = (prevActiveLink =
+ hash == null
+ ? null
+ : (container.value.querySelector(
+ `.devui-item a[href="${decodeURIComponent(hash)}"]`
+ ) as HTMLAnchorElement))
+ if (activeLink) {
+ activeLink.classList.add('active')
+ marker.value.style.opacity = '1'
+ marker.value.style.top = `${activeLink.offsetTop}px`
+ } else {
+ marker.value.style.opacity = '0'
+ marker.value.style.top = '33px'
+ }
+ }
+
+ function deactiveLink(link: HTMLElement) {
+ link && link.classList.remove('active')
+ }
+
+ onMounted(() => {
+ window.requestAnimationFrame(setActiveLink)
+ window.addEventListener('scroll', onScroll)
+ })
+
+ onUpdated(() => {
+ activateLink(location.hash)
+ })
+
+ onUnmounted(() => {
+ window.removeEventListener('scroll', onScroll)
+ })
+}
+function getSidebarLinks() {
+ return Array.from(
+ document.querySelectorAll('.devui-content-nav .devui-link')
+ ) as HTMLAnchorElement[]
+}
+function getAnchors(sidebarLinks: HTMLAnchorElement[]) {
+ return (
+ Array.from(
+ document.querySelectorAll('.content .header-anchor')
+ ) as HTMLAnchorElement[]
+ ).filter((anchor) =>
+ sidebarLinks.some((sidebarLink) => sidebarLink.hash === anchor.hash)
+ )
+}
+function getPageOffset() {
+ return (document.querySelector('.nav-bar') as HTMLElement).offsetHeight
+}
+function getAnchorTop(anchor: HTMLAnchorElement) {
+ const pageOffset = getPageOffset()
+ try {
+ return anchor.parentElement.offsetTop - pageOffset - 15
+ } catch (e) {
+ return 0
+ }
+}
+function isAnchorActive(
+ index: number,
+ anchor: HTMLAnchorElement,
+ nextAnchor: HTMLAnchorElement
+) {
+ const scrollTop = window.scrollY
+ if (index === 0 && scrollTop === 0) {
+ return [true, null]
+ }
+ if (scrollTop < getAnchorTop(anchor)) {
+ return [false, null]
+ }
+ if (!nextAnchor || scrollTop < getAnchorTop(nextAnchor)) {
+ return [true, decodeURIComponent(anchor.hash)]
+ }
+ return [false, null]
+}