Skip to content

Commit

Permalink
feat(anchor): child level (#2816)
Browse files Browse the repository at this point in the history
* feat(anchor): spatial distance calculation adjustments in front of anchor points

* refactor(anchor): update common hash
  • Loading branch information
ontheroad1992 authored Sep 22, 2023
1 parent c14dd59 commit 225aa44
Show file tree
Hide file tree
Showing 15 changed files with 733 additions and 289 deletions.
4 changes: 1 addition & 3 deletions src/anchor/__tests__/anchor-link.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ describe('AnchorItem', () => {
it('should render props correctly', async () => {
const provide = {
active: '',
handleScrollTo: vi.fn(),
registerLink: vi.fn(),
unregisterLink: vi.fn(),
handleLinkClick: vi.fn(),
Expand Down Expand Up @@ -56,7 +55,6 @@ describe('AnchorItem', () => {
expect(a.element.textContent).toEqual(props.title);
expect(provide.registerLink).toBeCalledWith(props.href);
a.trigger('click');
expect(provide.handleScrollTo).toBeCalledWith(props.href);
expect(provide.handleLinkClick).toBeCalledWith({ ...omit(props, 'target'), e: expect.any(MouseEvent) });
wrapper.setData({
href: '#test-b',
Expand Down Expand Up @@ -107,7 +105,7 @@ describe('AnchorItem', () => {
});
expect(wrapper.find('a').attributes('title')).toEqual('');
expect(wrapper.find('#title').element.outerHTML).toMatchSnapshot();
expect(wrapper.find('#default').element.outerHTML).toMatchSnapshot();
expect(wrapper.find('#default').element?.outerHTML).toMatchSnapshot();
});
});
});
6 changes: 3 additions & 3 deletions src/anchor/__tests__/index.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -145,10 +145,10 @@ describe('Anchor', () => {
}).findComponent(Anchor);
const links = wrapper.findAllComponents(AnchorItem);
links.at(0).find('a').trigger('click');
expect(onChange).toBeCalledTimes(1);
expect(onChange).toBeCalledWith('#test-a', '');
links.at(1).find('a').trigger('click');
expect(onChange).toBeCalledTimes(2);
expect(onChange).toBeCalledWith('#test-b', '');
links.at(1).find('a').trigger('click');
expect(onChange).toBeCalledTimes(3);
expect(onChange).toBeCalledWith('#test-b', '#test-a');
});
});
Expand Down
32 changes: 32 additions & 0 deletions src/anchor/_example/customize-highlight.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<template>
<t-space>
<div class="flex: 1"></div>
<t-anchor :bounds="150" :getCurrentAnchor="getCurrentAnchor">
<t-anchor-item href="#基础锚点" title="基础锚点"></t-anchor-item>
<t-anchor-item href="#多级锚点" title="多级锚点"></t-anchor-item>
<t-anchor-item href="#指定容器锚点" title="指定容器锚点"></t-anchor-item>
<t-anchor-item href="#特定交互锚点" title="特定交互锚点"></t-anchor-item>
<t-anchor-item href="#尺寸" title="尺寸">
<t-anchor-item href="#size-1" title="尺寸1">
<div id="default">this is children content</div>
<div>sdadasas</div>
</t-anchor-item>
<t-anchor-item href="#size-2" title="尺寸2">
<t-anchor-item href="#size-3" title="尺寸1"></t-anchor-item>
</t-anchor-item>
</t-anchor-item>
</t-anchor>
</t-space>
</template>
<script>
export default {
data() {
return {
getCurrentAnchor(value) {
console.log('value1', value);
return value || '#多级锚点';
},
};
},
};
</script>
5 changes: 4 additions & 1 deletion src/anchor/_example/multiple.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@
<t-anchor-item href="#多级锚点" title="多级锚点"></t-anchor-item>
<t-anchor-item href="#指定容器锚点" title="指定容器锚点"></t-anchor-item>
<t-anchor-item href="#特定交互锚点" title="特定交互锚点"></t-anchor-item>
<t-anchor-item href="#尺寸" title="尺寸"></t-anchor-item>
<t-anchor-item href="#尺寸" title="尺寸">
<t-anchor-item href="#size-1" title="尺寸1"></t-anchor-item>
<t-anchor-item href="#size-2" title="尺寸2"></t-anchor-item>
</t-anchor-item>
</t-anchor>
</t-space>
</template>
Expand Down
2 changes: 1 addition & 1 deletion src/anchor/anchor-item-props.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

/**
* 该文件为脚本自动生成文件,请勿随意修改。如需修改请联系 PMC
* updated at 2021-11-19 10:44:26
* */

import { TdAnchorItemProps } from '../anchor/type';
Expand All @@ -20,6 +19,7 @@ export default {
type: String as PropType<TdAnchorItemProps['target']>,
default: '_self' as TdAnchorItemProps['target'],
validator(val: TdAnchorItemProps['target']): boolean {
if (!val) return true;
return ['_self', '_blank', '_parent', '_top'].includes(val);
},
},
Expand Down
52 changes: 44 additions & 8 deletions src/anchor/anchor-item.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import Vue, { VueConstructor } from 'vue';
import { ScopedSlotReturnValue } from 'vue/types/vnode';
import { isEmpty } from 'lodash';
import { VNode } from 'vue/types/umd';
import { ANCHOR_SHARP_REGEXP } from './utils';
import props from './anchor-item-props';
import { getClassPrefixMixins } from '../config-provider/config-receiver';
Expand All @@ -10,7 +12,6 @@ const classPrefixMixins = getClassPrefixMixins('anchor');
export interface Anchor extends Vue {
tAnchor: {
active: string;
handleScrollTo(target: string): void;
registerLink(href: string): void;
unregisterLink(href: string): void;
handleLinkClick(link: { href: string; title: string; e: MouseEvent }): void;
Expand All @@ -21,6 +22,10 @@ export default mixins(Vue as VueConstructor<Anchor>, classPrefixMixins).extend({
name: 'TAnchorItem',
props: {
...props,
_level: {
type: Number,
default: 0,
},
href: {
type: String,
required: true,
Expand Down Expand Up @@ -57,7 +62,6 @@ export default mixins(Vue as VueConstructor<Anchor>, classPrefixMixins).extend({
},
handleClick(e: MouseEvent): void {
const { href, tAnchor, title } = this;
tAnchor.handleScrollTo(href);
tAnchor.handleLinkClick({
href,
title: typeof title === 'string' ? title : undefined,
Expand All @@ -83,27 +87,59 @@ export default mixins(Vue as VueConstructor<Anchor>, classPrefixMixins).extend({
return titleVal;
},
},
render() {
render(h) {
const {
href, target, $scopedSlots, tAnchor,
} = this;
const { default: children, title: titleSlot } = $scopedSlots;

const title = this.renderTitle();
const titleAttr = typeof title === 'string' ? title : null;
const isActive = tAnchor.active === href;

const wrapperClass = {
[`${this.componentName}__item`]: true,
[this.commonStatusClassName.active]: isActive,
};
const titleClass = {
[`${this.componentName}__item-link`]: true,
};

const newLevel = this.$props._level + 1;

// 这样处理是为了兼容普通节点和组件
const h5Tags: VNode[] = [];
const anchorItems: VNode[] = [];
if (children) {
children(null).forEach((child) => {
if (isEmpty(child.componentOptions)) {
h5Tags.push(h(child.tag, child.data, child.children));
} else {
anchorItems.push(
h(
child.componentOptions?.Ctor,
{
props: {
...child.componentOptions?.propsData,
_level: newLevel,
},
},
child?.componentOptions?.children,
),
);
}
});
}

return (
<div class={wrapperClass}>
<a href={href} title={titleAttr} class={titleClass} target={target} onClick={this.handleClick}>
{titleSlot ? titleSlot(null) : title}
</a>
{children && children(null)}
<div style={{ display: 'contents' }}>
<div class={wrapperClass} style={{ '--level': newLevel }}>
<a href={href} title={titleAttr} class={titleClass} target={target} onClick={this.handleClick}>
{titleSlot ? titleSlot(null) : title}
</a>
{h5Tags}
</div>
{anchorItems}
</div>
);
},
Expand Down
1 change: 0 additions & 1 deletion src/anchor/anchor-target-props.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

/**
* 该文件为脚本自动生成文件,请勿随意修改。如需修改请联系 PMC
* updated at 2021-11-19 10:44:26
* */

export default {
Expand Down
7 changes: 4 additions & 3 deletions src/anchor/anchor.en-US.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ name | type | default | description | required
-- | -- | -- | -- | --
affixProps | Object | - | Typescript:`AffixProps`[Affix API Documents](./affix?tab=api)[see more ts definition](https://github.com/Tencent/tdesign-vue/tree/develop/src/anchor/type.ts) | N
bounds | Number | 5 | \- | N
container | String / Function | () => (() => window) | Typescript:`ScrollContainer` | N
container | String / Function | () => (() => window) | Typescript:`ScrollContainer`[see more ts definition](https://github.com/Tencent/tdesign-vue/blob/develop/src/common.ts) | N
cursor | Slot / Function | - | Typescript:`TNode`[see more ts definition](https://github.com/Tencent/tdesign-vue/blob/develop/src/common.ts) | N
size | String | medium | options:small/medium/large。Typescript:`SizeEnum`[see more ts definition](https://github.com/Tencent/tdesign-vue/blob/develop/src/common.ts) | N
getCurrentAnchor | Function | - | Custom Highlighted Anchor Points。Typescript:`(activeLink: string) => string` | N
size | String | medium | options: small/medium/large。Typescript:`SizeEnum`[see more ts definition](https://github.com/Tencent/tdesign-vue/blob/develop/src/common.ts) | N
targetOffset | Number | 0 | \- | N
onChange | Function | | Typescript:`(currentLink: string, prevLink: string) => void`<br/> | N
onClick | Function | | Typescript:`(link: { href: string; title: string; e: MouseEvent }) => void`<br/> | N
Expand All @@ -26,7 +27,7 @@ click | `(link: { href: string; title: string; e: MouseEvent })` | \-
name | type | default | description | required
-- | -- | -- | -- | --
href | String | - | required | Y
target | String | _self | options_self/_blank/_parent/_top | N
target | String | _self | options: _self/_blank/_parent/_top | N
title | String / Slot / Function | '' | Typescript:`string \| TNode`[see more ts definition](https://github.com/Tencent/tdesign-vue/blob/develop/src/common.ts) | N

### AnchorTarget Props
Expand Down
3 changes: 2 additions & 1 deletion src/anchor/anchor.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@
-- | -- | -- | -- | --
affixProps | Object | - | 透传 Affix 组件属性,即让 Anchor 组件支持所有 Affix 组件特性。TS 类型:`AffixProps`[Affix API Documents](./affix?tab=api)[详细类型定义](https://github.com/Tencent/tdesign-vue/tree/develop/src/anchor/type.ts) | N
bounds | Number | 5 | 锚点区域边界 | N
container | String / Function | () => (() => window) | 指定滚动的容器。数据类型为 String 时,会被当作选择器处理,进行节点查询。示例:'body' 或 () => document.body。TS 类型:`ScrollContainer` | N
container | String / Function | () => (() => window) | 指定滚动的容器。数据类型为 String 时,会被当作选择器处理,进行节点查询。示例:'body' 或 () => document.body。TS 类型:`ScrollContainer`[通用类型定义](https://github.com/Tencent/tdesign-vue/blob/develop/src/common.ts) | N
cursor | Slot / Function | - | 用于自定义选中项左侧游标。TS 类型:`TNode`[通用类型定义](https://github.com/Tencent/tdesign-vue/blob/develop/src/common.ts) | N
getCurrentAnchor | Function | - | 自定义高亮的锚点 。TS 类型:`(activeLink: string) => string` | N
size | String | medium | 组件尺寸,small(120px),medium(200px),large(320px)。可选项:small/medium/large。TS 类型:`SizeEnum`[通用类型定义](https://github.com/Tencent/tdesign-vue/blob/develop/src/common.ts) | N
targetOffset | Number | 0 | 锚点滚动偏移量 | N
onChange | Function | | TS 类型:`(currentLink: string, prevLink: string) => void`<br/>锚点改变时触发 | N
Expand Down
21 changes: 14 additions & 7 deletions src/anchor/anchor.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import Vue, { VueConstructor } from 'vue';
import { ScopedSlotReturnValue } from 'vue/types/vnode';
import { isFunction } from 'lodash';
import { ANCHOR_SHARP_REGEXP, ANCHOR_CONTAINER, getOffsetTop } from './utils';
import {
on, off, getScroll, scrollTo, getScrollContainer,
Expand Down Expand Up @@ -118,6 +119,7 @@ export default mixins(Vue as VueConstructor<Anchor>, classPrefixMixins).extend({
this.activeLineStyle = false;
return;
}

const { offsetTop: top, offsetHeight: height } = ele;
this.activeLineStyle = {
top: `${top}px`,
Expand All @@ -143,9 +145,11 @@ export default mixins(Vue as VueConstructor<Anchor>, classPrefixMixins).extend({
*/
handleLinkClick(link: { href: string; title: string; e: MouseEvent }): void {
this.$emit('click', link);
if (this.onClick) {
this.onClick(link);
}
this.onClick?.(link);

const { getCurrentAnchor } = this.$props;
const newHref = isFunction(getCurrentAnchor) ? getCurrentAnchor(link.href) : link.href;
this.handleScrollTo(newHref);
},
/**
* 滚动到指定锚点
Expand Down Expand Up @@ -202,11 +206,14 @@ export default mixins(Vue as VueConstructor<Anchor>, classPrefixMixins).extend({
},

async mounted() {
const { active } = this;
this.getScrollContainer();
if (active) {
await Vue.nextTick();
this.handleScrollTo(active);
const { getCurrentAnchor } = this.$props;

if (isFunction(getCurrentAnchor)) {
this.setCurrentActiveLink(getCurrentAnchor(this.active));
} else {
const href = window?.location.hash;
this.setCurrentActiveLink(decodeURIComponent(href));
}
},

Expand Down
8 changes: 6 additions & 2 deletions src/anchor/props.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

/**
* 该文件为脚本自动生成文件,请勿随意修改。如需修改请联系 PMC
* updated at 2021-11-19 10:44:26
* */

import { TdAnchorProps } from './type';
Expand All @@ -21,17 +20,22 @@ export default {
/** 指定滚动的容器。数据类型为 String 时,会被当作选择器处理,进行节点查询。示例:'body' 或 () => document.body */
container: {
type: [String, Function] as PropType<TdAnchorProps['container']>,
default: () => (() => window),
default: () => () => window,
},
/** 用于自定义选中项左侧游标 */
cursor: {
type: Function as PropType<TdAnchorProps['cursor']>,
},
/** 自定义高亮的锚点 */
getCurrentAnchor: {
type: Function as PropType<TdAnchorProps['getCurrentAnchor']>,
},
/** 组件尺寸,small(120px),medium(200px),large(320px) */
size: {
type: String as PropType<TdAnchorProps['size']>,
default: 'medium' as TdAnchorProps['size'],
validator(val: TdAnchorProps['size']): boolean {
if (!val) return true;
return ['small', 'medium', 'large'].includes(val);
},
},
Expand Down
10 changes: 7 additions & 3 deletions src/anchor/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,11 @@ export interface TdAnchorProps {
* 锚点被点击时触发
*/
onClick?: (link: { href: string; title: string; e: MouseEvent }) => void;
};
/**
* 自定义高亮的锚点
*/
getCurrentAnchor?: (activeLink: string) => string;
}

export interface TdAnchorTargetProps {
/**
Expand All @@ -58,7 +62,7 @@ export interface TdAnchorTargetProps {
* @default div
*/
tag?: string;
};
}

export interface TdAnchorItemProps {
/**
Expand All @@ -76,4 +80,4 @@ export interface TdAnchorItemProps {
* @default ''
*/
title?: string | TNode;
};
}
Loading

0 comments on commit 225aa44

Please sign in to comment.