From ce79af4b34bb4c337e0ee5eb93e5c537995331ab Mon Sep 17 00:00:00 2001 From: ontheroad1992 Date: Wed, 20 Sep 2023 16:39:53 +0800 Subject: [PATCH 1/2] feat(anchor): spatial distance calculation adjustments in front of anchor points --- src/_common | 2 +- src/anchor/__tests__/anchor-link.test.jsx | 4 +- src/anchor/__tests__/index.test.jsx | 6 +- src/anchor/_example/customize-highlight.vue | 32 + src/anchor/_example/multiple.vue | 5 +- src/anchor/anchor-item-props.ts | 2 +- src/anchor/anchor-item.tsx | 52 +- src/anchor/anchor-target-props.ts | 1 - src/anchor/anchor.en-US.md | 7 +- src/anchor/anchor.md | 3 +- src/anchor/anchor.tsx | 21 +- src/anchor/props.ts | 8 +- src/anchor/type.ts | 10 +- test/snap/__snapshots__/csr.test.js.snap | 855 ++++++++++++++------ test/snap/__snapshots__/ssr.test.js.snap | 14 +- 15 files changed, 733 insertions(+), 289 deletions(-) create mode 100644 src/anchor/_example/customize-highlight.vue diff --git a/src/_common b/src/_common index f3402b914..7d88c9029 160000 --- a/src/_common +++ b/src/_common @@ -1 +1 @@ -Subproject commit f3402b9140f1ede9c89bacee0865281f421beee9 +Subproject commit 7d88c90294817c7abebcbeec391a583efb73bfcb diff --git a/src/anchor/__tests__/anchor-link.test.jsx b/src/anchor/__tests__/anchor-link.test.jsx index 224f79fb9..7472352fa 100644 --- a/src/anchor/__tests__/anchor-link.test.jsx +++ b/src/anchor/__tests__/anchor-link.test.jsx @@ -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(), @@ -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', @@ -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(); }); }); }); diff --git a/src/anchor/__tests__/index.test.jsx b/src/anchor/__tests__/index.test.jsx index 0ed6c6467..09cb579fb 100644 --- a/src/anchor/__tests__/index.test.jsx +++ b/src/anchor/__tests__/index.test.jsx @@ -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'); }); }); diff --git a/src/anchor/_example/customize-highlight.vue b/src/anchor/_example/customize-highlight.vue new file mode 100644 index 000000000..af0cc54c6 --- /dev/null +++ b/src/anchor/_example/customize-highlight.vue @@ -0,0 +1,32 @@ + + diff --git a/src/anchor/_example/multiple.vue b/src/anchor/_example/multiple.vue index a882c6d5c..56d131ffa 100644 --- a/src/anchor/_example/multiple.vue +++ b/src/anchor/_example/multiple.vue @@ -5,7 +5,10 @@ - + + + + diff --git a/src/anchor/anchor-item-props.ts b/src/anchor/anchor-item-props.ts index ca029f828..b3f8d116f 100644 --- a/src/anchor/anchor-item-props.ts +++ b/src/anchor/anchor-item-props.ts @@ -2,7 +2,6 @@ /** * 该文件为脚本自动生成文件,请勿随意修改。如需修改请联系 PMC - * updated at 2021-11-19 10:44:26 * */ import { TdAnchorItemProps } from '../anchor/type'; @@ -20,6 +19,7 @@ export default { type: String as PropType, default: '_self' as TdAnchorItemProps['target'], validator(val: TdAnchorItemProps['target']): boolean { + if (!val) return true; return ['_self', '_blank', '_parent', '_top'].includes(val); }, }, diff --git a/src/anchor/anchor-item.tsx b/src/anchor/anchor-item.tsx index ff7e98e53..e9ec75c89 100644 --- a/src/anchor/anchor-item.tsx +++ b/src/anchor/anchor-item.tsx @@ -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'; @@ -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; @@ -21,6 +22,10 @@ export default mixins(Vue as VueConstructor, classPrefixMixins).extend({ name: 'TAnchorItem', props: { ...props, + _level: { + type: Number, + default: 0, + }, href: { type: String, required: true, @@ -57,7 +62,6 @@ export default mixins(Vue as VueConstructor, classPrefixMixins).extend({ }, handleClick(e: MouseEvent): void { const { href, tAnchor, title } = this; - tAnchor.handleScrollTo(href); tAnchor.handleLinkClick({ href, title: typeof title === 'string' ? title : undefined, @@ -83,14 +87,16 @@ export default mixins(Vue as VueConstructor, 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, @@ -98,12 +104,42 @@ export default mixins(Vue as VueConstructor, classPrefixMixins).extend({ 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 ( -
- - {titleSlot ? titleSlot(null) : title} - - {children && children(null)} +
+ + {anchorItems}
); }, diff --git a/src/anchor/anchor-target-props.ts b/src/anchor/anchor-target-props.ts index 2f885064a..61f8cc4cd 100644 --- a/src/anchor/anchor-target-props.ts +++ b/src/anchor/anchor-target-props.ts @@ -2,7 +2,6 @@ /** * 该文件为脚本自动生成文件,请勿随意修改。如需修改请联系 PMC - * updated at 2021-11-19 10:44:26 * */ export default { diff --git a/src/anchor/anchor.en-US.md b/src/anchor/anchor.en-US.md index d847fdc38..845fda578 100644 --- a/src/anchor/anchor.en-US.md +++ b/src/anchor/anchor.en-US.md @@ -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`
| N onClick | Function | | Typescript:`(link: { href: string; title: string; e: MouseEvent }) => void`
| N @@ -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 diff --git a/src/anchor/anchor.md b/src/anchor/anchor.md index 10d4e7fda..f26808d4b 100644 --- a/src/anchor/anchor.md +++ b/src/anchor/anchor.md @@ -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`
锚点改变时触发 | N diff --git a/src/anchor/anchor.tsx b/src/anchor/anchor.tsx index 9ac3bf830..27a9dde8b 100644 --- a/src/anchor/anchor.tsx +++ b/src/anchor/anchor.tsx @@ -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, @@ -118,6 +119,7 @@ export default mixins(Vue as VueConstructor, classPrefixMixins).extend({ this.activeLineStyle = false; return; } + const { offsetTop: top, offsetHeight: height } = ele; this.activeLineStyle = { top: `${top}px`, @@ -143,9 +145,11 @@ export default mixins(Vue as VueConstructor, 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); }, /** * 滚动到指定锚点 @@ -202,11 +206,14 @@ export default mixins(Vue as VueConstructor, 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)); } }, diff --git a/src/anchor/props.ts b/src/anchor/props.ts index 28d82d9fd..4ebb69eed 100644 --- a/src/anchor/props.ts +++ b/src/anchor/props.ts @@ -2,7 +2,6 @@ /** * 该文件为脚本自动生成文件,请勿随意修改。如需修改请联系 PMC - * updated at 2021-11-19 10:44:26 * */ import { TdAnchorProps } from './type'; @@ -21,17 +20,22 @@ export default { /** 指定滚动的容器。数据类型为 String 时,会被当作选择器处理,进行节点查询。示例:'body' 或 () => document.body */ container: { type: [String, Function] as PropType, - default: () => (() => window), + default: () => () => window, }, /** 用于自定义选中项左侧游标 */ cursor: { type: Function as PropType, }, + /** 自定义高亮的锚点 */ + getCurrentAnchor: { + type: Function as PropType, + }, /** 组件尺寸,small(120px),medium(200px),large(320px) */ size: { type: String as PropType, default: 'medium' as TdAnchorProps['size'], validator(val: TdAnchorProps['size']): boolean { + if (!val) return true; return ['small', 'medium', 'large'].includes(val); }, }, diff --git a/src/anchor/type.ts b/src/anchor/type.ts index bb25f92d0..70d17f38c 100644 --- a/src/anchor/type.ts +++ b/src/anchor/type.ts @@ -45,7 +45,11 @@ export interface TdAnchorProps { * 锚点被点击时触发 */ onClick?: (link: { href: string; title: string; e: MouseEvent }) => void; -}; + /** + * 自定义高亮的锚点 + */ + getCurrentAnchor?: (activeLink: string) => string; +} export interface TdAnchorTargetProps { /** @@ -58,7 +62,7 @@ export interface TdAnchorTargetProps { * @default div */ tag?: string; -}; +} export interface TdAnchorItemProps { /** @@ -76,4 +80,4 @@ export interface TdAnchorItemProps { * @default '' */ title?: string | TNode; -}; +} diff --git a/test/snap/__snapshots__/csr.test.js.snap b/test/snap/__snapshots__/csr.test.js.snap index 88c56f9e3..5aa0318d0 100644 --- a/test/snap/__snapshots__/csr.test.js.snap +++ b/test/snap/__snapshots__/csr.test.js.snap @@ -824,64 +824,89 @@ exports[`csr snapshot test > csr test ./src/anchor/_example/base.vue 1`] = `
@@ -910,56 +935,76 @@ exports[`csr snapshot test > csr test ./src/anchor/_example/container.vue 1`] =
csr test ./src/anchor/_example/cursor.vue 1`] = `
`; -exports[`csr snapshot test > csr test ./src/anchor/_example/large.vue 1`] = ` +exports[`csr snapshot test > csr test ./src/anchor/_example/customize-highlight.vue 1`] = `
-`; - -exports[`csr snapshot test > csr test ./src/anchor/_example/multiple.vue 1`] = ` -
-
-
csr test ./src/anchor/_example/multiple.vue 1`] = `
+
+
+ + 尺寸1 + +
+ this is children content +
+
+ sdadasas +
+
+
+ `; -exports[`csr snapshot test > csr test ./src/anchor/_example/small.vue 1`] = ` +exports[`csr snapshot test > csr test ./src/anchor/_example/large.vue 1`] = `
-
-
+ class="t-anchor__line-cursor" + />
+
+ + + + +
`; +exports[`csr snapshot test > csr test ./src/anchor/_example/multiple.vue 1`] = ` +
+ +`; + +exports[`csr snapshot test > csr test ./src/anchor/_example/small.vue 1`] = ` +
+ +`; + exports[`csr snapshot test > csr test ./src/anchor/_example/target.vue 1`] = `
renders ./src/alert/_example/operation.vue correctl exports[`ssr snapshot test > renders ./src/alert/_example/title.vue correctly 1`] = `"
这是一条普通的消息提示
这是与普通的消息提示相关的文字辅助说明
相关操作
"`; -exports[`ssr snapshot test > renders ./src/anchor/_example/base.vue correctly 1`] = `""`; +exports[`ssr snapshot test > renders ./src/anchor/_example/base.vue correctly 1`] = `""`; -exports[`ssr snapshot test > renders ./src/anchor/_example/container.vue correctly 1`] = `"
anchor-content-1
anchor-content-2
anchor-content-3
anchor-content-4
anchor-content-5
"`; +exports[`ssr snapshot test > renders ./src/anchor/_example/container.vue correctly 1`] = `"
anchor-content-1
anchor-content-2
anchor-content-3
anchor-content-4
anchor-content-5
"`; -exports[`ssr snapshot test > renders ./src/anchor/_example/cursor.vue correctly 1`] = `""`; +exports[`ssr snapshot test > renders ./src/anchor/_example/cursor.vue correctly 1`] = `""`; -exports[`ssr snapshot test > renders ./src/anchor/_example/large.vue correctly 1`] = `""`; +exports[`ssr snapshot test > renders ./src/anchor/_example/customize-highlight.vue correctly 1`] = `""`; -exports[`ssr snapshot test > renders ./src/anchor/_example/multiple.vue correctly 1`] = `""`; +exports[`ssr snapshot test > renders ./src/anchor/_example/large.vue correctly 1`] = `""`; -exports[`ssr snapshot test > renders ./src/anchor/_example/small.vue correctly 1`] = `""`; +exports[`ssr snapshot test > renders ./src/anchor/_example/multiple.vue correctly 1`] = `""`; + +exports[`ssr snapshot test > renders ./src/anchor/_example/small.vue correctly 1`] = `""`; exports[`ssr snapshot test > renders ./src/anchor/_example/target.vue correctly 1`] = `"
定义
服务功能
使用指南
创建签名
创建内容
保密协议
其他
"`; From 07f339fa93506548f7cb92a46879b4d3339e6b23 Mon Sep 17 00:00:00 2001 From: ontheroad1992 Date: Wed, 20 Sep 2023 16:58:31 +0800 Subject: [PATCH 2/2] refactor(anchor): update common hash --- src/_common | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/_common b/src/_common index 02a9d42cd..a78db5d83 160000 --- a/src/_common +++ b/src/_common @@ -1 +1 @@ -Subproject commit 02a9d42cdc64416e33c17007ef9fbe37f607c3f6 +Subproject commit a78db5d8382c3c96cacedc2a9cb669584d0d3d9d