refactor: anchor

pull/4134/head
tanjinzhou 2021-05-24 18:09:11 +08:00
parent 1877d66cc5
commit fbe3a48ac2
6 changed files with 91 additions and 62 deletions

View File

@ -14,6 +14,7 @@ export default (
prefixCls: ComputedRef<string>; prefixCls: ComputedRef<string>;
direction: ComputedRef<Direction>; direction: ComputedRef<Direction>;
size: ComputedRef<SizeType>; size: ComputedRef<SizeType>;
getTargetContainer: ComputedRef<() => HTMLElement>;
} => { } => {
const configProvider = inject<UnwrapRef<ConfigProviderProps>>( const configProvider = inject<UnwrapRef<ConfigProviderProps>>(
'configProvider', 'configProvider',
@ -22,5 +23,6 @@ export default (
const prefixCls = computed(() => configProvider.getPrefixCls(name, props.prefixCls)); const prefixCls = computed(() => configProvider.getPrefixCls(name, props.prefixCls));
const direction = computed(() => configProvider.direction); const direction = computed(() => configProvider.direction);
const size = computed(() => props.size || configProvider.componentSize); const size = computed(() => props.size || configProvider.componentSize);
return { configProvider, prefixCls, direction, size }; const getTargetContainer = computed(() => props.getTargetContainer);
return { configProvider, prefixCls, direction, size, getTargetContainer };
}; };

View File

@ -1,6 +1,5 @@
import { import {
defineComponent, defineComponent,
inject,
nextTick, nextTick,
onBeforeUnmount, onBeforeUnmount,
onMounted, onMounted,
@ -9,6 +8,8 @@ import {
reactive, reactive,
ref, ref,
getCurrentInstance, getCurrentInstance,
ExtractPropTypes,
computed,
} from 'vue'; } from 'vue';
import PropTypes from '../_util/vue-types'; import PropTypes from '../_util/vue-types';
import classNames from '../_util/classNames'; import classNames from '../_util/classNames';
@ -16,17 +17,13 @@ import addEventListener from '../vc-util/Dom/addEventListener';
import Affix from '../affix'; import Affix from '../affix';
import scrollTo from '../_util/scrollTo'; import scrollTo from '../_util/scrollTo';
import getScroll from '../_util/getScroll'; import getScroll from '../_util/getScroll';
import { defaultConfigProvider } from '../config-provider'; import useConfigInject from '../_util/hooks/useConfigInject';
function getDefaultContainer() { function getDefaultContainer() {
return window; return window;
} }
function getOffsetTop(element: HTMLElement, container: AnchorContainer): number { function getOffsetTop(element: HTMLElement, container: AnchorContainer): number {
if (!element) {
return 0;
}
if (!element.getClientRects().length) { if (!element.getClientRects().length) {
return 0; return 0;
} }
@ -35,7 +32,7 @@ function getOffsetTop(element: HTMLElement, container: AnchorContainer): number
if (rect.width || rect.height) { if (rect.width || rect.height) {
if (container === window) { if (container === window) {
container = element.ownerDocument.documentElement; container = element.ownerDocument!.documentElement!;
return rect.top - container.clientTop; return rect.top - container.clientTop;
} }
return rect.top - (container as HTMLElement).getBoundingClientRect().top; return rect.top - (container as HTMLElement).getBoundingClientRect().top;
@ -44,7 +41,7 @@ function getOffsetTop(element: HTMLElement, container: AnchorContainer): number
return rect.top; return rect.top;
} }
const sharpMatcherRegx = /#([^#]+)$/; const sharpMatcherRegx = /#(\S+)$/;
type Section = { type Section = {
link: string; link: string;
@ -53,7 +50,7 @@ type Section = {
export type AnchorContainer = HTMLElement | Window; export type AnchorContainer = HTMLElement | Window;
const AnchorProps = { const anchorProps = {
prefixCls: PropTypes.string, prefixCls: PropTypes.string,
offsetTop: PropTypes.number, offsetTop: PropTypes.number,
bounds: PropTypes.number, bounds: PropTypes.number,
@ -68,6 +65,8 @@ const AnchorProps = {
onClick: PropTypes.func, onClick: PropTypes.func,
}; };
export type AnchorProps = Partial<ExtractPropTypes<typeof anchorProps>>;
export interface AntAnchor { export interface AntAnchor {
registerLink: (link: string) => void; registerLink: (link: string) => void;
unregisterLink: (link: string) => void; unregisterLink: (link: string) => void;
@ -81,28 +80,32 @@ export interface AnchorState {
links: string[]; links: string[];
scrollEvent: any; scrollEvent: any;
animating: boolean; animating: boolean;
sPrefixCls?: string;
} }
export default defineComponent({ export default defineComponent({
name: 'AAnchor', name: 'AAnchor',
inheritAttrs: false, inheritAttrs: false,
props: AnchorProps, props: anchorProps,
emits: ['change', 'click'], emits: ['change', 'click'],
setup(props, { emit, attrs, slots }) { setup(props, { emit, attrs, slots }) {
const configProvider = inject('configProvider', defaultConfigProvider); const { prefixCls, getTargetContainer, direction } = useConfigInject('anchor', props);
const instance = getCurrentInstance(); const instance = getCurrentInstance();
const inkNodeRef = ref(); const inkNodeRef = ref();
const anchorRef = ref(); const anchorRef = ref();
const state = reactive<AnchorState>({ const state = reactive<AnchorState>({
activeLink: null, activeLink: null,
links: [], links: [],
sPrefixCls: '',
scrollContainer: null, scrollContainer: null,
scrollEvent: null, scrollEvent: null,
animating: false, animating: false,
}); });
const getContainer = computed(() => {
const { getContainer } = props;
const getFunc = getContainer || getTargetContainer.value || getDefaultContainer;
return getFunc();
});
// func... // func...
const getCurrentActiveLink = (offsetTop = 0, bounds = 5) => { const getCurrentActiveLink = (offsetTop = 0, bounds = 5) => {
const { getCurrentAnchor } = props; const { getCurrentAnchor } = props;
@ -110,14 +113,9 @@ export default defineComponent({
if (typeof getCurrentAnchor === 'function') { if (typeof getCurrentAnchor === 'function') {
return getCurrentAnchor(); return getCurrentAnchor();
} }
const activeLink = '';
if (typeof document === 'undefined') {
return activeLink;
}
const linkSections: Array<Section> = []; const linkSections: Array<Section> = [];
const { getContainer } = props; const container = getContainer.value();
const container = getContainer();
state.links.forEach(link => { state.links.forEach(link => {
const sharpLinkMatch = sharpMatcherRegx.exec(link.toString()); const sharpLinkMatch = sharpMatcherRegx.exec(link.toString());
if (!sharpLinkMatch) { if (!sharpLinkMatch) {
@ -188,11 +186,9 @@ export default defineComponent({
}; };
const updateInk = () => { const updateInk = () => {
if (typeof document === 'undefined') { const linkNode = anchorRef.value.getElementsByClassName(
return; `${prefixCls.value}-link-title-active`,
} )[0];
const { sPrefixCls } = state;
const linkNode = anchorRef.value.getElementsByClassName(`${sPrefixCls}-link-title-active`)[0];
if (linkNode) { if (linkNode) {
(inkNodeRef.value as HTMLElement).style.top = `${linkNode.offsetTop + (inkNodeRef.value as HTMLElement).style.top = `${linkNode.offsetTop +
linkNode.clientHeight / 2 - linkNode.clientHeight / 2 -
@ -220,8 +216,8 @@ export default defineComponent({
onMounted(() => { onMounted(() => {
nextTick(() => { nextTick(() => {
const { getContainer } = props; const container = getContainer.value();
state.scrollContainer = getContainer(); state.scrollContainer = container;
state.scrollEvent = addEventListener(state.scrollContainer, 'scroll', handleScroll); state.scrollEvent = addEventListener(state.scrollContainer, 'scroll', handleScroll);
handleScroll(); handleScroll();
}); });
@ -233,8 +229,7 @@ export default defineComponent({
}); });
onUpdated(() => { onUpdated(() => {
if (state.scrollEvent) { if (state.scrollEvent) {
const { getContainer } = props; const currentContainer = getContainer.value();
const currentContainer = getContainer();
if (state.scrollContainer !== currentContainer) { if (state.scrollContainer !== currentContainer) {
state.scrollContainer = currentContainer; state.scrollContainer = currentContainer;
state.scrollEvent.remove(); state.scrollEvent.remove();
@ -246,24 +241,17 @@ export default defineComponent({
}); });
return () => { return () => {
const { const { offsetTop, affix, showInkInFixed } = props;
prefixCls: customizePrefixCls, const pre = prefixCls.value;
offsetTop, const inkClass = classNames(`${pre}-ink-ball`, {
affix,
showInkInFixed,
getContainer,
} = props;
const getPrefixCls = configProvider.getPrefixCls;
const prefixCls = getPrefixCls('anchor', customizePrefixCls);
state.sPrefixCls = prefixCls;
const inkClass = classNames(`${prefixCls}-ink-ball`, {
visible: state.activeLink, 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, fixed: !affix && !showInkInFixed,
}); });
@ -274,7 +262,7 @@ export default defineComponent({
const anchorContent = ( const anchorContent = (
<div class={wrapperClass} style={wrapperStyle} ref={anchorRef}> <div class={wrapperClass} style={wrapperStyle} ref={anchorRef}>
<div class={anchorClass}> <div class={anchorClass}>
<div class={`${prefixCls}-ink`}> <div class={`${pre}-ink`}>
<span class={inkClass} ref={inkNodeRef} /> <span class={inkClass} ref={inkNodeRef} />
</div> </div>
{slots.default?.()} {slots.default?.()}
@ -285,7 +273,7 @@ export default defineComponent({
return !affix ? ( return !affix ? (
anchorContent anchorContent
) : ( ) : (
<Affix {...attrs} offsetTop={offsetTop} target={getContainer}> <Affix {...attrs} offsetTop={offsetTop} target={getContainer.value}>
{anchorContent} {anchorContent}
</Affix> </Affix>
); );

View File

@ -1,6 +1,7 @@
import { import {
ComponentInternalInstance, ComponentInternalInstance,
defineComponent, defineComponent,
ExtractPropTypes,
inject, inject,
nextTick, nextTick,
onBeforeUnmount, onBeforeUnmount,
@ -10,22 +11,24 @@ import {
import PropTypes from '../_util/vue-types'; import PropTypes from '../_util/vue-types';
import { getPropsSlot } from '../_util/props-util'; import { getPropsSlot } from '../_util/props-util';
import classNames from '../_util/classNames'; import classNames from '../_util/classNames';
import { defaultConfigProvider } from '../config-provider';
import { AntAnchor } from './Anchor'; import { AntAnchor } from './Anchor';
import useConfigInject from '../_util/hooks/useConfigInject';
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
function noop(..._any: any[]): any {} function noop(..._any: any[]): any {}
const AnchorLinkProps = { const anchorLinkProps = {
prefixCls: PropTypes.string, prefixCls: PropTypes.string,
href: PropTypes.string.def('#'), href: PropTypes.string.def('#'),
title: PropTypes.VNodeChild, title: PropTypes.VNodeChild,
target: PropTypes.string, target: PropTypes.string,
}; };
export type AnchorLinkProps = Partial<ExtractPropTypes<typeof anchorLinkProps>>;
export default defineComponent({ export default defineComponent({
name: 'AAnchorLink', name: 'AAnchorLink',
props: AnchorLinkProps, props: anchorLinkProps,
setup(props, { slots }) { setup(props, { slots }) {
const antAnchor = inject('antAnchor', { const antAnchor = inject('antAnchor', {
registerLink: noop, registerLink: noop,
@ -34,7 +37,7 @@ export default defineComponent({
$data: {}, $data: {},
} as AntAnchor); } as AntAnchor);
const antAnchorContext = inject('antAnchorContext', {}) as ComponentInternalInstance; const antAnchorContext = inject('antAnchorContext', {}) as ComponentInternalInstance;
const configProvider = inject('configProvider', defaultConfigProvider); const { prefixCls } = useConfigInject('anchor', props);
const handleClick = (e: Event) => { const handleClick = (e: Event) => {
// antAnchor.scrollTo(props.href); // antAnchor.scrollTo(props.href);
@ -65,18 +68,15 @@ export default defineComponent({
}); });
return () => { return () => {
const { prefixCls: customizePrefixCls, href, target } = props; const { href, target } = props;
const pre = prefixCls.value;
const getPrefixCls = configProvider.getPrefixCls;
const prefixCls = getPrefixCls('anchor', customizePrefixCls);
const title = getPropsSlot(slots, props, 'title'); const title = getPropsSlot(slots, props, 'title');
const active = antAnchor.$data.activeLink === href; const active = antAnchor.$data.activeLink === href;
const wrapperClassName = classNames(`${prefixCls}-link`, { const wrapperClassName = classNames(`${pre}-link`, {
[`${prefixCls}-link-active`]: active, [`${pre}-link-active`]: active,
}); });
const titleClassName = classNames(`${prefixCls}-link-title`, { const titleClassName = classNames(`${pre}-link-title`, {
[`${prefixCls}-link-title-active`]: active, [`${pre}-link-title-active`]: active,
}); });
return ( return (
<div class={wrapperClassName}> <div class={wrapperClassName}>

View File

@ -1,6 +1,6 @@
import { App, Plugin } from 'vue'; import { App, Plugin } from 'vue';
import Anchor from './Anchor'; import Anchor, { AnchorProps } from './Anchor';
import AnchorLink from './AnchorLink'; import AnchorLink, { AnchorLinkProps } from './AnchorLink';
Anchor.Link = AnchorLink; Anchor.Link = AnchorLink;
@ -11,6 +11,8 @@ Anchor.install = function(app: App) {
return app; return app;
}; };
export { AnchorLinkProps, AnchorProps, AnchorLink, AnchorLink as Link };
export default Anchor as typeof Anchor & export default Anchor as typeof Anchor &
Plugin & { Plugin & {
readonly Link: typeof AnchorLink; readonly Link: typeof AnchorLink;

View File

@ -13,7 +13,7 @@
margin-left: -4px; margin-left: -4px;
padding-left: 4px; padding-left: 4px;
overflow: auto; overflow: auto;
background-color: @component-background; background-color: @anchor-bg;
} }
&-ink { &-ink {
@ -52,7 +52,7 @@
} }
&-link { &-link {
padding: 7px 0 7px 16px; padding: @anchor-link-padding;
line-height: 1.143; line-height: 1.143;
&-title { &-title {
@ -80,3 +80,5 @@
padding-bottom: 5px; padding-bottom: 5px;
} }
} }
@import './rtl';

View File

@ -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;
}
}
}