diff --git a/components/_util/hooks/useConfigInject.ts b/components/_util/hooks/useConfigInject.ts index 77655efd4..2d46d036f 100644 --- a/components/_util/hooks/useConfigInject.ts +++ b/components/_util/hooks/useConfigInject.ts @@ -14,6 +14,7 @@ export default ( prefixCls: ComputedRef; direction: ComputedRef; size: ComputedRef; + getTargetContainer: ComputedRef<() => HTMLElement>; } => { const configProvider = inject>( 'configProvider', @@ -22,5 +23,6 @@ export default ( const prefixCls = computed(() => configProvider.getPrefixCls(name, props.prefixCls)); const direction = computed(() => configProvider.direction); const size = computed(() => props.size || configProvider.componentSize); - return { configProvider, prefixCls, direction, size }; + const getTargetContainer = computed(() => props.getTargetContainer); + return { configProvider, prefixCls, direction, size, getTargetContainer }; }; diff --git a/components/anchor/Anchor.tsx b/components/anchor/Anchor.tsx index 5ddf26d24..e7c3261c8 100644 --- a/components/anchor/Anchor.tsx +++ b/components/anchor/Anchor.tsx @@ -1,6 +1,5 @@ import { defineComponent, - inject, nextTick, onBeforeUnmount, onMounted, @@ -9,6 +8,8 @@ import { reactive, ref, getCurrentInstance, + ExtractPropTypes, + computed, } from 'vue'; import PropTypes from '../_util/vue-types'; import classNames from '../_util/classNames'; @@ -16,17 +17,13 @@ import addEventListener from '../vc-util/Dom/addEventListener'; import Affix from '../affix'; import scrollTo from '../_util/scrollTo'; import getScroll from '../_util/getScroll'; -import { defaultConfigProvider } from '../config-provider'; +import useConfigInject from '../_util/hooks/useConfigInject'; function getDefaultContainer() { return window; } function getOffsetTop(element: HTMLElement, container: AnchorContainer): number { - if (!element) { - return 0; - } - if (!element.getClientRects().length) { return 0; } @@ -35,7 +32,7 @@ function getOffsetTop(element: HTMLElement, container: AnchorContainer): number if (rect.width || rect.height) { if (container === window) { - container = element.ownerDocument.documentElement; + container = element.ownerDocument!.documentElement!; return rect.top - container.clientTop; } return rect.top - (container as HTMLElement).getBoundingClientRect().top; @@ -44,7 +41,7 @@ function getOffsetTop(element: HTMLElement, container: AnchorContainer): number return rect.top; } -const sharpMatcherRegx = /#([^#]+)$/; +const sharpMatcherRegx = /#(\S+)$/; type Section = { link: string; @@ -53,7 +50,7 @@ type Section = { export type AnchorContainer = HTMLElement | Window; -const AnchorProps = { +const anchorProps = { prefixCls: PropTypes.string, offsetTop: PropTypes.number, bounds: PropTypes.number, @@ -68,6 +65,8 @@ const AnchorProps = { onClick: PropTypes.func, }; +export type AnchorProps = Partial>; + export interface AntAnchor { registerLink: (link: string) => void; unregisterLink: (link: string) => void; @@ -81,28 +80,32 @@ export interface AnchorState { links: string[]; scrollEvent: any; animating: boolean; - sPrefixCls?: string; } export default defineComponent({ name: 'AAnchor', inheritAttrs: false, - props: AnchorProps, + props: anchorProps, emits: ['change', 'click'], setup(props, { emit, attrs, slots }) { - const configProvider = inject('configProvider', defaultConfigProvider); + const { prefixCls, getTargetContainer, direction } = useConfigInject('anchor', props); const instance = getCurrentInstance(); const inkNodeRef = ref(); const anchorRef = ref(); const state = reactive({ activeLink: null, links: [], - sPrefixCls: '', scrollContainer: null, scrollEvent: null, animating: false, }); + const getContainer = computed(() => { + const { getContainer } = props; + const getFunc = getContainer || getTargetContainer.value || getDefaultContainer; + + return getFunc(); + }); // func... const getCurrentActiveLink = (offsetTop = 0, bounds = 5) => { const { getCurrentAnchor } = props; @@ -110,14 +113,9 @@ export default defineComponent({ if (typeof getCurrentAnchor === 'function') { return getCurrentAnchor(); } - const activeLink = ''; - if (typeof document === 'undefined') { - return activeLink; - } const linkSections: Array
= []; - const { getContainer } = props; - const container = getContainer(); + const container = getContainer.value(); state.links.forEach(link => { const sharpLinkMatch = sharpMatcherRegx.exec(link.toString()); if (!sharpLinkMatch) { @@ -188,11 +186,9 @@ export default defineComponent({ }; const updateInk = () => { - if (typeof document === 'undefined') { - return; - } - const { sPrefixCls } = state; - const linkNode = anchorRef.value.getElementsByClassName(`${sPrefixCls}-link-title-active`)[0]; + const linkNode = anchorRef.value.getElementsByClassName( + `${prefixCls.value}-link-title-active`, + )[0]; if (linkNode) { (inkNodeRef.value as HTMLElement).style.top = `${linkNode.offsetTop + linkNode.clientHeight / 2 - @@ -220,8 +216,8 @@ export default defineComponent({ onMounted(() => { nextTick(() => { - const { getContainer } = props; - state.scrollContainer = getContainer(); + const container = getContainer.value(); + state.scrollContainer = container; state.scrollEvent = addEventListener(state.scrollContainer, 'scroll', handleScroll); handleScroll(); }); @@ -233,8 +229,7 @@ export default defineComponent({ }); onUpdated(() => { if (state.scrollEvent) { - const { getContainer } = props; - const currentContainer = getContainer(); + const currentContainer = getContainer.value(); if (state.scrollContainer !== currentContainer) { state.scrollContainer = currentContainer; state.scrollEvent.remove(); @@ -246,24 +241,17 @@ export default defineComponent({ }); return () => { - const { - prefixCls: customizePrefixCls, - offsetTop, - affix, - showInkInFixed, - getContainer, - } = props; - const getPrefixCls = configProvider.getPrefixCls; - const prefixCls = getPrefixCls('anchor', customizePrefixCls); - state.sPrefixCls = prefixCls; - - const inkClass = classNames(`${prefixCls}-ink-ball`, { + const { offsetTop, affix, showInkInFixed } = props; + const pre = prefixCls.value; + const inkClass = classNames(`${pre}-ink-ball`, { visible: state.activeLink, }); - const wrapperClass = classNames(props.wrapperClass, `${prefixCls}-wrapper`); + const wrapperClass = classNames(props.wrapperClass, `${pre}-wrapper`, { + [`${pre}-rtl`]: direction.value === 'rtl', + }); - const anchorClass = classNames(prefixCls, { + const anchorClass = classNames(pre, { fixed: !affix && !showInkInFixed, }); @@ -274,7 +262,7 @@ export default defineComponent({ const anchorContent = (
-
+
{slots.default?.()} @@ -285,7 +273,7 @@ export default defineComponent({ return !affix ? ( anchorContent ) : ( - + {anchorContent} ); diff --git a/components/anchor/AnchorLink.tsx b/components/anchor/AnchorLink.tsx index 1897eb485..8658e919c 100644 --- a/components/anchor/AnchorLink.tsx +++ b/components/anchor/AnchorLink.tsx @@ -1,6 +1,7 @@ import { ComponentInternalInstance, defineComponent, + ExtractPropTypes, inject, nextTick, onBeforeUnmount, @@ -10,22 +11,24 @@ import { import PropTypes from '../_util/vue-types'; import { getPropsSlot } from '../_util/props-util'; import classNames from '../_util/classNames'; -import { defaultConfigProvider } from '../config-provider'; import { AntAnchor } from './Anchor'; +import useConfigInject from '../_util/hooks/useConfigInject'; // eslint-disable-next-line @typescript-eslint/no-unused-vars function noop(..._any: any[]): any {} -const AnchorLinkProps = { +const anchorLinkProps = { prefixCls: PropTypes.string, href: PropTypes.string.def('#'), title: PropTypes.VNodeChild, target: PropTypes.string, }; +export type AnchorLinkProps = Partial>; + export default defineComponent({ name: 'AAnchorLink', - props: AnchorLinkProps, + props: anchorLinkProps, setup(props, { slots }) { const antAnchor = inject('antAnchor', { registerLink: noop, @@ -34,7 +37,7 @@ export default defineComponent({ $data: {}, } as AntAnchor); const antAnchorContext = inject('antAnchorContext', {}) as ComponentInternalInstance; - const configProvider = inject('configProvider', defaultConfigProvider); + const { prefixCls } = useConfigInject('anchor', props); const handleClick = (e: Event) => { // antAnchor.scrollTo(props.href); @@ -65,18 +68,15 @@ export default defineComponent({ }); return () => { - const { prefixCls: customizePrefixCls, href, target } = props; - - const getPrefixCls = configProvider.getPrefixCls; - const prefixCls = getPrefixCls('anchor', customizePrefixCls); - + const { href, target } = props; + const pre = prefixCls.value; const title = getPropsSlot(slots, props, 'title'); const active = antAnchor.$data.activeLink === href; - const wrapperClassName = classNames(`${prefixCls}-link`, { - [`${prefixCls}-link-active`]: active, + const wrapperClassName = classNames(`${pre}-link`, { + [`${pre}-link-active`]: active, }); - const titleClassName = classNames(`${prefixCls}-link-title`, { - [`${prefixCls}-link-title-active`]: active, + const titleClassName = classNames(`${pre}-link-title`, { + [`${pre}-link-title-active`]: active, }); return (
diff --git a/components/anchor/index.tsx b/components/anchor/index.tsx index 54e0d384a..ecea1a192 100644 --- a/components/anchor/index.tsx +++ b/components/anchor/index.tsx @@ -1,6 +1,6 @@ import { App, Plugin } from 'vue'; -import Anchor from './Anchor'; -import AnchorLink from './AnchorLink'; +import Anchor, { AnchorProps } from './Anchor'; +import AnchorLink, { AnchorLinkProps } from './AnchorLink'; Anchor.Link = AnchorLink; @@ -11,6 +11,8 @@ Anchor.install = function(app: App) { return app; }; +export { AnchorLinkProps, AnchorProps, AnchorLink, AnchorLink as Link }; + export default Anchor as typeof Anchor & Plugin & { readonly Link: typeof AnchorLink; diff --git a/components/anchor/style/index.less b/components/anchor/style/index.less index 85d953f66..347bc9141 100644 --- a/components/anchor/style/index.less +++ b/components/anchor/style/index.less @@ -13,7 +13,7 @@ margin-left: -4px; padding-left: 4px; overflow: auto; - background-color: @component-background; + background-color: @anchor-bg; } &-ink { @@ -52,7 +52,7 @@ } &-link { - padding: 7px 0 7px 16px; + padding: @anchor-link-padding; line-height: 1.143; &-title { @@ -80,3 +80,5 @@ padding-bottom: 5px; } } + +@import './rtl'; diff --git a/components/anchor/style/rtl.less b/components/anchor/style/rtl.less new file mode 100644 index 000000000..f1774d51a --- /dev/null +++ b/components/anchor/style/rtl.less @@ -0,0 +1,35 @@ +.@{ant-prefix}-anchor { + &-rtl { + direction: rtl; + } + + &-wrapper { + .@{ant-prefix}-anchor-rtl& { + margin-right: -4px; + margin-left: 0; + padding-right: 4px; + padding-left: 0; + } + } + + &-ink { + .@{ant-prefix}-anchor-rtl & { + right: 0; + left: auto; + } + + &-ball { + .@{ant-prefix}-anchor-rtl & { + right: 50%; + left: 0; + transform: translateX(50%); + } + } + } + + &-link { + .@{ant-prefix}-anchor-rtl & { + padding: @anchor-link-top @anchor-link-left @anchor-link-top 0; + } + } +}