refactor: anchor
parent
1877d66cc5
commit
fbe3a48ac2
|
@ -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 };
|
||||||
};
|
};
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
|
|
|
@ -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}>
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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';
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue