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>;
direction: ComputedRef<Direction>;
size: ComputedRef<SizeType>;
getTargetContainer: ComputedRef<() => HTMLElement>;
} => {
const configProvider = inject<UnwrapRef<ConfigProviderProps>>(
'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 };
};

View File

@ -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<ExtractPropTypes<typeof anchorProps>>;
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<AnchorState>({
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<Section> = [];
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 = (
<div class={wrapperClass} style={wrapperStyle} ref={anchorRef}>
<div class={anchorClass}>
<div class={`${prefixCls}-ink`}>
<div class={`${pre}-ink`}>
<span class={inkClass} ref={inkNodeRef} />
</div>
{slots.default?.()}
@ -285,7 +273,7 @@ export default defineComponent({
return !affix ? (
anchorContent
) : (
<Affix {...attrs} offsetTop={offsetTop} target={getContainer}>
<Affix {...attrs} offsetTop={offsetTop} target={getContainer.value}>
{anchorContent}
</Affix>
);

View File

@ -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<ExtractPropTypes<typeof anchorLinkProps>>;
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 (
<div class={wrapperClassName}>

View File

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

View File

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

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