diff --git a/components/anchor/Anchor.tsx b/components/anchor/Anchor.tsx index 4eeb4dc0f7..167ab5f38c 100644 --- a/components/anchor/Anchor.tsx +++ b/components/anchor/Anchor.tsx @@ -1,5 +1,6 @@ import type { CSSProperties, ExtractPropTypes, PropType } from 'vue'; import { + watch, defineComponent, nextTick, onBeforeUnmount, @@ -9,6 +10,7 @@ import { ref, computed, } from 'vue'; +import scrollIntoView from 'scroll-into-view-if-needed'; import classNames from '../_util/classNames'; import addEventListener from '../vc-util/Dom/addEventListener'; import Affix from '../affix'; @@ -20,6 +22,8 @@ import useStyle from './style'; import type { AnchorLinkProps } from './AnchorLink'; import AnchorLink from './AnchorLink'; import type { Key } from '../_util/type'; +import PropTypes from '../_util/vue-types'; +import devWarning from '../vc-util/devWarning'; export interface AnchorLinkItemProps extends AnchorLinkProps { key: Key; @@ -27,6 +31,9 @@ export interface AnchorLinkItemProps extends AnchorLinkProps { style?: CSSProperties; children?: AnchorLinkItemProps[]; } + +export type AnchorDirection = 'vertical' | 'horizontal'; + function getDefaultContainer() { return window; } @@ -73,6 +80,7 @@ export const anchorProps = () => ({ type: Array as PropType, default: undefined as AnchorLinkItemProps[], }, + direction: PropTypes.oneOf(['vertical', 'horizontal'] as AnchorDirection[]).def('vertical'), onChange: Function as PropType<(currentActiveLink: string) => void>, onClick: Function as PropType<(e: MouseEvent, link: { title: any; href: string }) => void>, }); @@ -93,6 +101,24 @@ export default defineComponent({ props: anchorProps(), setup(props, { emit, attrs, slots, expose }) { const { prefixCls, getTargetContainer, direction } = useConfigInject('anchor', props); + const anchorDirection = computed(() => props.direction ?? 'vertical'); + + if (process.env.NODE_ENV !== 'production') { + devWarning( + typeof slots.default !== 'function', + 'Anchor', + '`Anchor children` is deprecated. Please use `items` instead.', + ); + } + + if (process.env.NODE_ENV !== 'production') { + devWarning( + !(anchorDirection.value === 'horizontal' && props.items?.some(n => 'children' in n)), + 'Anchor', + '`Anchor items#children` is not supported when `Anchor` direction is horizontal.', + ); + } + const spanLinkNode = ref(null); const anchorRef = ref(); const state = reactive({ @@ -184,12 +210,21 @@ export default defineComponent({ }; const updateInk = () => { - const linkNode = anchorRef.value.getElementsByClassName( - `${prefixCls.value}-link-title-active`, - )[0]; + const linkNode = anchorRef.value.querySelector(`.${prefixCls.value}-link-title-active`); if (linkNode && spanLinkNode.value) { - spanLinkNode.value.style.top = `${linkNode.offsetTop + linkNode.clientHeight / 2}px`; - spanLinkNode.value.style.height = `${linkNode.clientHeight}px`; + const horizontalAnchor = anchorDirection.value === 'horizontal'; + spanLinkNode.value.style.top = horizontalAnchor + ? '' + : `${linkNode.offsetTop + linkNode.clientHeight / 2}px`; + spanLinkNode.value.style.height = horizontalAnchor ? '' : `${linkNode.clientHeight}px`; + spanLinkNode.value.style.left = horizontalAnchor ? `${linkNode.offsetLeft}px` : ''; + spanLinkNode.value.style.width = horizontalAnchor ? `${linkNode.clientWidth}px` : ''; + if (horizontalAnchor) { + scrollIntoView(linkNode, { + scrollMode: 'if-needed', + block: 'nearest', + }); + } } }; @@ -210,6 +245,7 @@ export default defineComponent({ handleClick: (e, info) => { emit('click', e, info); }, + direction: anchorDirection, }); onMounted(() => { @@ -237,23 +273,31 @@ export default defineComponent({ } updateInk(); }); + + watch([anchorDirection, getCurrentAnchor, state.links, activeLink], () => { + updateInk(); + }); + const createNestedLink = (options?: AnchorLinkItemProps[]) => Array.isArray(options) ? options.map(item => ( - {createNestedLink(item.children)} + {anchorDirection.value === 'vertical' ? createNestedLink(item.children) : null} )) : null; + const [wrapSSR, hashId] = useStyle(prefixCls); + return () => { const { offsetTop, affix, showInkInFixed } = props; const pre = prefixCls.value; - const inkClass = classNames(`${pre}-ink-ball`, { - [`${pre}-ink-ball-visible`]: activeLink.value, + const inkClass = classNames(`${pre}-ink`, { + [`${pre}-ink-visible`]: activeLink.value, }); const wrapperClass = classNames(hashId.value, props.wrapperClass, `${pre}-wrapper`, { + [`${pre}-wrapper-horizontal`]: anchorDirection.value === 'horizontal', [`${pre}-rtl`]: direction.value === 'rtl', }); @@ -268,9 +312,7 @@ export default defineComponent({ const anchorContent = (
-
- -
+ {Array.isArray(props.items) ? createNestedLink(props.items) : slots.default?.()}
diff --git a/components/anchor/__tests__/__snapshots__/demo.test.js.snap b/components/anchor/__tests__/__snapshots__/demo.test.js.snap index 23a0acd912..629142ea12 100644 --- a/components/anchor/__tests__/__snapshots__/demo.test.js.snap +++ b/components/anchor/__tests__/__snapshots__/demo.test.js.snap @@ -2,27 +2,19 @@ exports[`renders ./components/anchor/demo/basic.vue correctly 1`] = `
-
-
-
-
-