From 9d1bc41430a5143f5337347de475117a419e937c Mon Sep 17 00:00:00 2001 From: zhangpaopao Date: Fri, 19 Aug 2022 11:22:31 +0800 Subject: [PATCH 01/48] feat(guide): init --- examples/guide/demos/base.vue | 3 +++ examples/guide/guide.md | 9 +++++++++ site/site.config.mjs | 6 ++++++ src/components.ts | 1 + src/guide/guide.tsx | 20 ++++++++++++++++++++ src/guide/index.ts | 12 ++++++++++++ src/guide/props.ts | 10 ++++++++++ src/guide/style/css.js | 1 + src/guide/style/index.js | 1 + src/guide/type.ts | 9 +++++++++ src/index.ts | 1 + test/e2e/guide/guide.spec.js | 0 test/unit/guide/demo.test.js | 10 ++++++++++ test/unit/guide/index.test.js | 30 ++++++++++++++++++++++++++++++ 14 files changed, 113 insertions(+) create mode 100644 examples/guide/demos/base.vue create mode 100644 examples/guide/guide.md create mode 100644 src/guide/guide.tsx create mode 100644 src/guide/index.ts create mode 100644 src/guide/props.ts create mode 100644 src/guide/style/css.js create mode 100644 src/guide/style/index.js create mode 100644 src/guide/type.ts create mode 100644 test/e2e/guide/guide.spec.js create mode 100644 test/unit/guide/demo.test.js create mode 100644 test/unit/guide/index.test.js diff --git a/examples/guide/demos/base.vue b/examples/guide/demos/base.vue new file mode 100644 index 0000000000..7e3a1a9e8e --- /dev/null +++ b/examples/guide/demos/base.vue @@ -0,0 +1,3 @@ + diff --git a/examples/guide/guide.md b/examples/guide/guide.md new file mode 100644 index 0000000000..f29e217b5b --- /dev/null +++ b/examples/guide/guide.md @@ -0,0 +1,9 @@ +## guide + +::: demo demos/base 默认 +::: + +### 属性配置 +| 属性 | 类型 | 默认值 | 必传 | 说明 | +|-----|-----|-----|-----|-----| +|-|-|-|-|-| diff --git a/site/site.config.mjs b/site/site.config.mjs index 3831ef231f..f9bb3e6692 100644 --- a/site/site.config.mjs +++ b/site/site.config.mjs @@ -425,6 +425,12 @@ export default { path: '/vue-next/components/drawer', component: () => import('@/examples/drawer/drawer.md'), }, + { + title: 'Guide 引导', + name: 'guide', + path: '/vue-next/components/guide', + component: () => import('@/examples/guide/guide.md'), + }, { title: 'Message 全局提示', name: 'message', diff --git a/src/components.ts b/src/components.ts index 055939f111..80384d2900 100644 --- a/src/components.ts +++ b/src/components.ts @@ -63,6 +63,7 @@ export * from './watermark'; export * from './alert'; export * from './dialog'; export * from './drawer'; +export * from './guide'; export * from './loading'; export * from './message'; export * from './notification'; diff --git a/src/guide/guide.tsx b/src/guide/guide.tsx new file mode 100644 index 0000000000..c42e0bf932 --- /dev/null +++ b/src/guide/guide.tsx @@ -0,0 +1,20 @@ +import { computed, defineComponent } from 'vue'; +import { useConfig, usePrefixClass, useCommonClassName } from '../hooks/useConfig'; +import { useContent, useTNodeJSX } from '../hooks/tnode'; +import props from './props'; + +export default defineComponent({ + name: 'TGuide', + props: { ...props }, + setup(props) { + const renderContent = useContent(); + const renderTNodeJSX = useTNodeJSX(); + const COMPONENT_NAME = usePrefixClass('link'); + const { STATUS, SIZE } = useCommonClassName(); + const { classPrefix } = useConfig('classPrefix'); + + return () => { + return
guide
; + }; + }, +}); diff --git a/src/guide/index.ts b/src/guide/index.ts new file mode 100644 index 0000000000..255fae2a36 --- /dev/null +++ b/src/guide/index.ts @@ -0,0 +1,12 @@ +import _Guide from './Guide'; +import withInstall from '../utils/withInstall'; +import { TdGuideProps } from './type'; + +import './style'; + +export * from './type'; +export type GuideProps = TdGuideProps; + +export const Guide = withInstall(_Guide); + +export default Guide; diff --git a/src/guide/props.ts b/src/guide/props.ts new file mode 100644 index 0000000000..bbf7b42de6 --- /dev/null +++ b/src/guide/props.ts @@ -0,0 +1,10 @@ +/* eslint-disable */ + +/** + * 该文件为脚本自动生成文件,请勿随意修改。如需修改请联系 PMC + * */ + +import { TdGuideProps } from './type'; +import { PropType } from 'vue'; + +export default {}; diff --git a/src/guide/style/css.js b/src/guide/style/css.js new file mode 100644 index 0000000000..6a9a4b1328 --- /dev/null +++ b/src/guide/style/css.js @@ -0,0 +1 @@ +import './index.css'; diff --git a/src/guide/style/index.js b/src/guide/style/index.js new file mode 100644 index 0000000000..6ff0f4f84c --- /dev/null +++ b/src/guide/style/index.js @@ -0,0 +1 @@ +import '../../_common/style/web/components/guide/_index.less'; diff --git a/src/guide/type.ts b/src/guide/type.ts new file mode 100644 index 0000000000..b2bdfc1dc5 --- /dev/null +++ b/src/guide/type.ts @@ -0,0 +1,9 @@ +/* eslint-disable */ + +/** + * 该文件为脚本自动生成文件,请勿随意修改。如需修改请联系 PMC + * */ + +import { TNode, SizeEnum } from '../common'; + +export interface TdGuideProps {} diff --git a/src/index.ts b/src/index.ts index 7f726ce235..a20ed35119 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,6 @@ import { App } from 'vue'; import * as components from './components'; +import Guide from './guide'; export function install(app: App, config?: Record): void { Object.keys(components).forEach((key) => { diff --git a/test/e2e/guide/guide.spec.js b/test/e2e/guide/guide.spec.js new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/unit/guide/demo.test.js b/test/unit/guide/demo.test.js new file mode 100644 index 0000000000..ab290b533a --- /dev/null +++ b/test/unit/guide/demo.test.js @@ -0,0 +1,10 @@ +import { mount } from '@vue/test-utils'; +import demo from '@/examples/guide/demos/base.vue'; + +// unit test for component in examples. +describe('Guide', () => { + it('base demo works fine', () => { + const wrapper = mount(demo); + expect(wrapper.element).toMatchSnapshot(); + }); +}); diff --git a/test/unit/guide/index.test.js b/test/unit/guide/index.test.js new file mode 100644 index 0000000000..92569d7f82 --- /dev/null +++ b/test/unit/guide/index.test.js @@ -0,0 +1,30 @@ +import { mount } from '@vue/test-utils'; +import Guide from '@/src/guide/index.ts'; + +// every component needs four parts: props/events/slots/functions. +describe('Guide', () => { + // test props api + describe(':props', () => { + it('', () => { + const wrapper = mount({ + render() { + return ; + }, + }); + expect(wrapper.exists()).toBe(true); + }); + }); + + // test events + describe('@event', () => {}); + + // test slots + describe('', () => { + it('', () => {}); + }); + + // test exposure function + describe('function', () => { + it('', () => {}); + }); +}); From 6779ce404f3b9aa688ac1c99ed3a1789a8bf4425 Mon Sep 17 00:00:00 2001 From: zhangpaopao Date: Fri, 19 Aug 2022 16:19:04 +0800 Subject: [PATCH 02/48] feat(guide): init guide src and example --- examples/guide/demos/base.vue | 4 +++- examples/guide/demos/guide.vue | 18 ++++++++++++++++++ examples/guide/guide.md | 19 ++++++++++++------- examples/guide/usage/index.vue | 24 ++++++++++++++++++++++++ examples/guide/usage/props.json | 32 ++++++++++++++++++++++++++++++++ src/_common | 2 +- src/guide/guide.tsx | 2 +- src/guide/index.ts | 2 +- 8 files changed, 92 insertions(+), 11 deletions(-) create mode 100644 examples/guide/demos/guide.vue create mode 100644 examples/guide/usage/index.vue create mode 100644 examples/guide/usage/props.json diff --git a/examples/guide/demos/base.vue b/examples/guide/demos/base.vue index 7e3a1a9e8e..19fac90317 100644 --- a/examples/guide/demos/base.vue +++ b/examples/guide/demos/base.vue @@ -1,3 +1,5 @@ diff --git a/examples/guide/demos/guide.vue b/examples/guide/demos/guide.vue new file mode 100644 index 0000000000..cd1480746f --- /dev/null +++ b/examples/guide/demos/guide.vue @@ -0,0 +1,18 @@ + + + diff --git a/examples/guide/guide.md b/examples/guide/guide.md index f29e217b5b..137306551b 100644 --- a/examples/guide/guide.md +++ b/examples/guide/guide.md @@ -1,9 +1,14 @@ -## guide +:: BASE_DOC :: +## API +### Loading Props -::: demo demos/base 默认 -::: +名称 | 类型 | 默认值 | 说明 | 必传 +-- | -- | -- | -- | -- -### 属性配置 -| 属性 | 类型 | 默认值 | 必传 | 说明 | -|-----|-----|-----|-----|-----| -|-|-|-|-|-| + +### LoadingPlugin + + + +参数名称 | 参数类型 | 参数默认值 | 参数说明 +-- | -- | -- | -- diff --git a/examples/guide/usage/index.vue b/examples/guide/usage/index.vue new file mode 100644 index 0000000000..78202bb4e2 --- /dev/null +++ b/examples/guide/usage/index.vue @@ -0,0 +1,24 @@ + + + + diff --git a/examples/guide/usage/props.json b/examples/guide/usage/props.json new file mode 100644 index 0000000000..1f1bab969a --- /dev/null +++ b/examples/guide/usage/props.json @@ -0,0 +1,32 @@ +[ + { + "name": "indicator", + "type": "Boolean", + "defaultValue": true, + "options": [] + }, + { + "name": "inheritColor", + "type": "Boolean", + "defaultValue": false, + "options": [] + }, + { + "name": "loading", + "type": "Boolean", + "defaultValue": true, + "options": [] + }, + { + "name": "preventScrollThrough", + "type": "Boolean", + "defaultValue": true, + "options": [] + }, + { + "name": "showOverlay", + "type": "Boolean", + "defaultValue": true, + "options": [] + } +] \ No newline at end of file diff --git a/src/_common b/src/_common index bdb4da544d..a922a9b68f 160000 --- a/src/_common +++ b/src/_common @@ -1 +1 @@ -Subproject commit bdb4da544de4973742f3435ea6fae0e37d742b10 +Subproject commit a922a9b68f0e191f5c8af785d5439089f859172c diff --git a/src/guide/guide.tsx b/src/guide/guide.tsx index c42e0bf932..54a0e85d96 100644 --- a/src/guide/guide.tsx +++ b/src/guide/guide.tsx @@ -14,7 +14,7 @@ export default defineComponent({ const { classPrefix } = useConfig('classPrefix'); return () => { - return
guide
; + return guid; }; }, }); diff --git a/src/guide/index.ts b/src/guide/index.ts index 255fae2a36..8ee32c75ab 100644 --- a/src/guide/index.ts +++ b/src/guide/index.ts @@ -1,4 +1,4 @@ -import _Guide from './Guide'; +import _Guide from './guide'; import withInstall from '../utils/withInstall'; import { TdGuideProps } from './type'; From 8defb60f31726c7276d4f209b4907faf359ed329 Mon Sep 17 00:00:00 2001 From: zhangpaopao Date: Sun, 21 Aug 2022 18:18:34 +0800 Subject: [PATCH 03/48] feat(guide): guide basic content --- examples/guide/demos/guide.vue | 63 +++- examples/guide/guide.en-US.md | 53 ++++ examples/guide/guide.md | 47 ++- .../guide/usage/{index.vue => index-keep.vue} | 0 src/_common | 2 +- src/guide/const/index.ts | 8 + src/guide/guide-step-props.ts | 69 +++++ src/guide/guide.tsx | 279 +++++++++++++++++- src/guide/props.ts | 79 ++++- src/guide/type.ts | 178 ++++++++++- src/guide/utils/elementInViewport.ts | 18 ++ src/guide/utils/getElmInfo.ts | 32 ++ src/guide/utils/getOffset.ts | 52 ++++ src/guide/utils/getPropValue.ts | 25 ++ src/guide/utils/getScrollParent.ts | 23 ++ src/guide/utils/getTargetElm.ts | 11 + src/guide/utils/getWindowSize.ts | 15 + src/guide/utils/index.ts | 0 src/guide/utils/isFixed.ts | 23 ++ src/guide/utils/scrollTo.ts | 25 ++ src/guide/utils/setStyle.ts | 24 ++ 21 files changed, 1003 insertions(+), 23 deletions(-) create mode 100644 examples/guide/guide.en-US.md rename examples/guide/usage/{index.vue => index-keep.vue} (100%) create mode 100644 src/guide/const/index.ts create mode 100644 src/guide/guide-step-props.ts create mode 100644 src/guide/utils/elementInViewport.ts create mode 100644 src/guide/utils/getElmInfo.ts create mode 100644 src/guide/utils/getOffset.ts create mode 100644 src/guide/utils/getPropValue.ts create mode 100644 src/guide/utils/getScrollParent.ts create mode 100644 src/guide/utils/getTargetElm.ts create mode 100644 src/guide/utils/getWindowSize.ts create mode 100644 src/guide/utils/index.ts create mode 100644 src/guide/utils/isFixed.ts create mode 100644 src/guide/utils/scrollTo.ts create mode 100644 src/guide/utils/setStyle.ts diff --git a/examples/guide/demos/guide.vue b/examples/guide/demos/guide.vue index cd1480746f..5028d7ff6d 100644 --- a/examples/guide/demos/guide.vue +++ b/examples/guide/demos/guide.vue @@ -1,18 +1,67 @@ diff --git a/examples/guide/guide.en-US.md b/examples/guide/guide.en-US.md new file mode 100644 index 0000000000..7e8a3032f5 --- /dev/null +++ b/examples/guide/guide.en-US.md @@ -0,0 +1,53 @@ +:: BASE_DOC :: + +## API + +### Guide Props + +name | type | default | description | required +-- | -- | -- | -- | -- +counter | Slot / Function | - | Typescript:`TNode`。[see more ts definition](https://github.com/Tencent/tdesign-vue-next/blob/develop/src/common.ts) | N +current | Number | - | `v-model` and `v-model:current` is supported | N +defaultCurrent | Number | - | uncontrolled property | N +finishButtonProps | Object | { content: '完成', theme: 'primary' } | Typescript:`ButtonProps` | N +hideCounter | Boolean | false | \- | N +hidePrev | Boolean | false | \- | N +hideSkip | Boolean | false | \- | N +initialNum | Number | 0 | \- | N +mask | Boolean | true | \- | N +mode | String | popup | options:popup/dialog | N +nextButtonProps | Object | { content: '下一步', theme: 'primary' } | Typescript:`ButtonProps`,[Button API Documents](./button?tab=api)。[see more ts definition](https://github.com/Tencent/tdesign-vue-next/tree/develop/src/guide/type.ts) | N +prevButtonProps | Object | { content: '上一步', theme: 'primary' } | Typescript:`ButtonProps` | N +skipButtonProps | Object | { content: '跳过', theme: 'default' } | Typescript:`ButtonProps` | N +steps | Array | - | Typescript:`Array` | N +onChange | Function | | Typescript:`(current: number, total: number, context?: { e?: MouseEvent }) => void`
| N +onClickNextStep | Function | | Typescript:`( next: number, current: number, total: number, context?: { e?: MouseEvent }) => void`
| N +onClickPrevStep | Function | | Typescript:`( prev: number, current: number, total: number, context?: { e?: MouseEvent }) => void`
| N +onFinish | Function | | Typescript:`( current: number, total: number, context?: { e?: MouseEvent }) => void`
| N +onSkip | Function | | Typescript:`(current: number, total: number, context?: { e?: MouseEvent }) => void`
| N + +### Guide Events + +name | params | description +-- | -- | -- +change | `(current: number, total: number, context?: { e?: MouseEvent })` | \- +click-next-step | `( next: number, current: number, total: number, context?: { e?: MouseEvent })` | \- +click-prev-step | `( prev: number, current: number, total: number, context?: { e?: MouseEvent })` | \- +finish | `( current: number, total: number, context?: { e?: MouseEvent })` | \- +skip | `(current: number, total: number, context?: { e?: MouseEvent })` | \- + +### GuideStep Props + +name | type | default | description | required +-- | -- | -- | -- | -- +children | String / Slot / Function | - | Typescript:`string | TNode`。[see more ts definition](https://github.com/Tencent/tdesign-vue-next/blob/develop/src/common.ts) | N +content | String / Slot / Function | - | Typescript:`string | TNode`。[see more ts definition](https://github.com/Tencent/tdesign-vue-next/blob/develop/src/common.ts) | N +description | String | - | \- | N +element | String / Function | - | required。Typescript:`AttachNode`。[see more ts definition](https://github.com/Tencent/tdesign-vue-next/blob/develop/src/common.ts) | Y +nextButtonProps | Object | - | Typescript:`ButtonProps` | N +offset | Array | - | Typescript:`Array` | N +placement | String | top | Typescript:`StepPopupPlacement | StepDialogPlacement` `type StepPopupPlacement = 'top'|'left'|'right'|'bottom'|'top-left'|'top-right'|'bottom-left'|'bottom-right'|'left-top'|'left-bottom'|'right-top'|'right-bottom'` `type StepDialogPlacement = 'top'|'center' `。[see more ts definition](https://github.com/Tencent/tdesign-vue-next/tree/develop/src/guide/type.ts) | N +prevButtonProps | Object | - | Typescript:`ButtonProps` | N +skipButtonProps | Object | - | Typescript:`ButtonProps` | N +stepOverlayClass | String | - | \- | N +title | String | - | \- | N diff --git a/examples/guide/guide.md b/examples/guide/guide.md index 137306551b..89ddcbf51d 100644 --- a/examples/guide/guide.md +++ b/examples/guide/guide.md @@ -1,14 +1,51 @@ :: BASE_DOC :: ## API -### Loading Props +### Guide Props 名称 | 类型 | 默认值 | 说明 | 必传 -- | -- | -- | -- | -- +counter | Slot / Function | - | 用于自定义渲染计数部分。TS 类型:`TNode`。[通用类型定义](https://github.com/Tencent/tdesign-vue-next/blob/develop/src/common.ts) | N +current | Number | - | 当前步骤,即整个引导的进度。。支持语法糖 `v-model` 或 `v-model:current` | N +defaultCurrent | Number | - | 当前步骤,即整个引导的进度。。非受控属性 | N +finishButtonProps | Object | { content: '完成', theme: 'primary' } | 透传 完成 的全部属性。TS 类型:`ButtonProps` | N +hideCounter | Boolean | false | 是否隐藏计数 | N +hidePrev | Boolean | false | 是否隐藏上一步按钮 | N +hideSkip | Boolean | false | 是否隐藏跳过按钮 | N +initialNum | Number | 0 | 起始序号 | N +mask | Boolean | true | 是否出现遮罩层 | N +mode | String | popup | 引导框的类型。可选项:popup/dialog | N +nextButtonProps | Object | { content: '下一步', theme: 'primary' } | 透传 下一步按钮 的全部属性。TS 类型:`ButtonProps`,[Button API Documents](./button?tab=api)。[详细类型定义](https://github.com/Tencent/tdesign-vue-next/tree/develop/src/guide/type.ts) | N +prevButtonProps | Object | { content: '上一步', theme: 'primary' } | 透传 上一步按钮 的全部属性。TS 类型:`ButtonProps` | N +skipButtonProps | Object | { content: '跳过', theme: 'default' } | 透传 跳过按钮 的全部属性。TS 类型:`ButtonProps` | N +steps | Array | - | 用于定义每个步骤的内容,包括高亮的节点、相对位置和具体的文案内容等。。TS 类型:`Array` | N +onChange | Function | | TS 类型:`(current: number, total: number, context?: { e?: MouseEvent }) => void`
当前步骤发生变化时触发 | N +onClickNextStep | Function | | TS 类型:`( next: number, current: number, total: number, context?: { e?: MouseEvent }) => void`
点击下一步时触发 | N +onClickPrevStep | Function | | TS 类型:`( prev: number, current: number, total: number, context?: { e?: MouseEvent }) => void`
点击上一步时触发 | N +onFinish | Function | | TS 类型:`( current: number, total: number, context?: { e?: MouseEvent }) => void`
点击完成按钮时触发 | N +onSkip | Function | | TS 类型:`(current: number, total: number, context?: { e?: MouseEvent }) => void`
点击跳过按钮时触发 | N +### Guide Events -### LoadingPlugin +名称 | 参数 | 描述 +-- | -- | -- +change | `(current: number, total: number, context?: { e?: MouseEvent })` | 当前步骤发生变化时触发 +click-next-step | `( next: number, current: number, total: number, context?: { e?: MouseEvent })` | 点击下一步时触发 +click-prev-step | `( prev: number, current: number, total: number, context?: { e?: MouseEvent })` | 点击上一步时触发 +finish | `( current: number, total: number, context?: { e?: MouseEvent })` | 点击完成按钮时触发 +skip | `(current: number, total: number, context?: { e?: MouseEvent })` | 点击跳过按钮时触发 +### GuideStep Props - -参数名称 | 参数类型 | 参数默认值 | 参数说明 --- | -- | -- | -- +名称 | 类型 | 默认值 | 说明 | 必传 +-- | -- | -- | -- | -- +children | String / Slot / Function | - | 自定义内容,同 content。TS 类型:`string | TNode`。[通用类型定义](https://github.com/Tencent/tdesign-vue-next/blob/develop/src/common.ts) | N +content | String / Slot / Function | - | 用户自定义引导弹框的内容,一旦存在,此时除 `placement`、`offset`和`element` 外,其它属性全部失效)。TS 类型:`string | TNode`。[通用类型定义](https://github.com/Tencent/tdesign-vue-next/blob/develop/src/common.ts) | N +description | String | - | 当前步骤的描述内容 | N +element | String / Function | - | 必需。高亮的节点。数据类型为 String 时,会被当作选择器处理,进行节点查询。示例:'#tdesign' 或 () => document.querySelector('#tdesign')。TS 类型:`AttachNode`。[通用类型定义](https://github.com/Tencent/tdesign-vue-next/blob/develop/src/common.ts) | Y +nextButtonProps | Object | - | 用于自定义当前引导框的下一步按钮的内容。TS 类型:`ButtonProps` | N +offset | Array | - | 相对于 placement 的偏移量,示例:[-10, 20] 或 ['10px', '8px']。TS 类型:`Array` | N +placement | String | top | 引导框相对于高亮元素出现的位置。TS 类型:`StepPopupPlacement | StepDialogPlacement` `type StepPopupPlacement = 'top'|'left'|'right'|'bottom'|'top-left'|'top-right'|'bottom-left'|'bottom-right'|'left-top'|'left-bottom'|'right-top'|'right-bottom'` `type StepDialogPlacement = 'top'|'center' `。[详细类型定义](https://github.com/Tencent/tdesign-vue-next/tree/develop/src/guide/type.ts) | N +prevButtonProps | Object | - | 用于自定义当前引导框的上一步按钮的内容。TS 类型:`ButtonProps` | N +skipButtonProps | Object | - | 用于自定义当前步骤引导框的跳过按钮的内容。TS 类型:`ButtonProps` | N +stepOverlayClass | String | - | 覆盖引导框的类名 | N +title | String | - | 当前步骤的标题内容 | N diff --git a/examples/guide/usage/index.vue b/examples/guide/usage/index-keep.vue similarity index 100% rename from examples/guide/usage/index.vue rename to examples/guide/usage/index-keep.vue diff --git a/src/_common b/src/_common index a922a9b68f..3eeba5e0b7 160000 --- a/src/_common +++ b/src/_common @@ -1 +1 @@ -Subproject commit a922a9b68f0e191f5c8af785d5439089f859172c +Subproject commit 3eeba5e0b7d40420090e6a8e973d61f7ff672236 diff --git a/src/guide/const/index.ts b/src/guide/const/index.ts new file mode 100644 index 0000000000..078b26fe7f --- /dev/null +++ b/src/guide/const/index.ts @@ -0,0 +1,8 @@ +import { CrossProps } from '../type'; + +export const defalutCrossProps: CrossProps = { + mask: true, + mode: 'popup', +}; + +export const tooltipZIndex = 999999; diff --git a/src/guide/guide-step-props.ts b/src/guide/guide-step-props.ts new file mode 100644 index 0000000000..07b3d1844d --- /dev/null +++ b/src/guide/guide-step-props.ts @@ -0,0 +1,69 @@ +/* eslint-disable */ + +/** + * 该文件为脚本自动生成文件,请勿随意修改。如需修改请联系 PMC + * */ + +import { TdGuideStepProps } from '../guide/type'; +import { PropType } from 'vue'; + +export default { + /** 自定义内容,同 content */ + children: { + type: [String, Function] as PropType, + }, + /** 用户自定义引导弹框的内容,一旦存在,此时除 `placement`、`offset`和`element` 外,其它属性全部失效) */ + content: { + type: [String, Function] as PropType, + }, + /** 当前步骤的描述内容 */ + description: { + type: String, + default: '', + }, + /** 高亮的节点。数据类型为 String 时,会被当作选择器处理,进行节点查询。示例:'#tdesign' 或 () => document.querySelector('#tdesign') */ + element: { + type: [String, Function] as PropType, + required: true, + }, + /** 当期步骤引导框的类型 */ + mode: { + type: String as PropType, + default: 'popup' as TdGuideStepProps['mode'], + validator(val: TdGuideStepProps['mode']): boolean { + if (!val) return true; + return ['popup', 'dialog'].includes(val); + }, + }, + /** 用于自定义当前引导框的下一步按钮的内容 */ + nextButtonProps: { + type: Object as PropType, + }, + /** 相对于 placement 的偏移量,示例:[-10, 20] 或 ['10px', '8px'] */ + offset: { + type: Array as PropType, + }, + /** 引导框相对于高亮元素出现的位置 */ + placement: { + type: String, + default: 'top', + }, + /** 用于自定义当前引导框的上一步按钮的内容 */ + prevButtonProps: { + type: Object as PropType, + }, + /** 用于自定义当前步骤引导框的跳过按钮的内容 */ + skipButtonProps: { + type: Object as PropType, + }, + /** 覆盖引导框的类名 */ + stepOverlayClass: { + type: String, + default: '', + }, + /** 当前步骤的标题内容 */ + title: { + type: String, + default: '', + }, +}; diff --git a/src/guide/guide.tsx b/src/guide/guide.tsx index 54a0e85d96..c13abaaa21 100644 --- a/src/guide/guide.tsx +++ b/src/guide/guide.tsx @@ -1,20 +1,285 @@ -import { computed, defineComponent } from 'vue'; +import { computed, defineComponent, h, nextTick, onMounted, ref, toRefs, watch } from 'vue'; + +import props from './props'; +import setStyle from './utils/setStyle'; +import getScrollParent from './utils/getScrollParent'; +import getOffset from './utils/getOffset'; +import getTargetElm from './utils/getTargetElm'; +import scrollTo from './utils/scrollTo'; + +import TransferDom from '../utils/transfer-dom'; + import { useConfig, usePrefixClass, useCommonClassName } from '../hooks/useConfig'; import { useContent, useTNodeJSX } from '../hooks/tnode'; -import props from './props'; +import useDefaultValue from '../hooks/useDefaultValue'; + +import Button from '../button'; +import Popup from '../popup'; +import Dialog from '../dialog'; + +import { TdGuideProps, TdGuideStepProps, CrossProps } from './type'; +import { defalutCrossProps, tooltipZIndex } from './const'; export default defineComponent({ name: 'TGuide', - props: { ...props }, + directives: { + TransferDom, + }, + props, setup(props) { const renderContent = useContent(); const renderTNodeJSX = useTNodeJSX(); - const COMPONENT_NAME = usePrefixClass('link'); - const { STATUS, SIZE } = useCommonClassName(); - const { classPrefix } = useConfig('classPrefix'); + + const classPrefix = usePrefixClass(); + const COMPONENT_NAME = usePrefixClass('guide'); + const { SIZE } = useCommonClassName(); + + const { current, hideCounter, hidePrev, hideSkip, steps } = toRefs(props); + + const [innerCurrent, setInnerCurrent] = useDefaultValue(current, props.defaultCurrent, props.onChange, 'current'); + + // 覆盖层,用于覆盖所有元素 + // const overlayLayer = ref(); + // 高亮层,用于高亮元素 + // const highlightLayer = ref(); + // 覆盖层,用于覆盖所有元素 + const overlayLayerRef = ref(); + // 高亮层,用于高亮元素 + const highlightLayerRef = ref(); + // 提示层,用于高亮元素 + const referenceLayerRef = ref(); + // 当前高亮的元素 + const currentHighlightLayerElm = ref(); + // 下一个高亮的元素 + const nextHighlightLayerElm = ref(); + // dialog ref + const dialogRef = ref(); + // 是否开始展示 + const actived = ref(false); + // 当前步骤的信息 + const currentStepInfo = computed(() => steps.value[innerCurrent.value]); + + // 获取当前步骤的所有属性 用户当前步骤设置 > 用户全局设置的 > 默认值 + const getCurrentCrossProps = (propsName: keyof CrossProps) => + currentStepInfo.value[propsName] || props[propsName] || defalutCrossProps[propsName]; + // 当前是否为 popup + const isPopup = computed(() => getCurrentCrossProps('mode') === 'popup'); + + // 滑动到元素位置 + const scrollParentToElement = (element: HTMLElement) => { + const parent = getScrollParent(element); + if (parent === document.body) return; + parent.scrollTop = element.offsetTop - parent.offsetTop; + }; + + // 设置高亮层的位置 + const setHighlightLayerPosition = (highlighLayer: HTMLElement) => { + const elementPosition = getOffset(nextHighlightLayerElm.value, currentHighlightLayerElm.value); + setStyle(highlighLayer, { + width: `${elementPosition.width}px`, + height: `${elementPosition.height}px`, + top: `${elementPosition.top}px`, + left: `${elementPosition.left}px`, + }); + }; + + const showPopupGuide = () => { + const currentElement = getTargetElm(currentStepInfo.value.element); + nextHighlightLayerElm.value = currentElement; + nextTick(() => { + scrollParentToElement(nextHighlightLayerElm.value); + setHighlightLayerPosition(highlightLayerRef.value); + setHighlightLayerPosition(referenceLayerRef.value); + scrollTo(nextHighlightLayerElm.value); + currentHighlightLayerElm.value = currentElement; + }); + }; + + const destoryTooltipElm = () => { + referenceLayerRef.value?.parentNode.removeChild(referenceLayerRef.value); + highlightLayerRef.value?.parentNode.removeChild(highlightLayerRef.value); + overlayLayerRef.value?.parentNode.removeChild(overlayLayerRef.value); + }; + + const showGuide = () => { + if (isPopup.value) { + showPopupGuide(); + } else { + destoryTooltipElm(); + } + }; + + const handleSkip = () => { + actived.value = false; + setInnerCurrent(-1); + destoryTooltipElm(); + props.onSkip?.(); + }; + const handlePrev = () => { + setInnerCurrent(innerCurrent.value - 1); + props.onClickPrevStep?.(); + }; + const handleNext = () => { + setInnerCurrent(innerCurrent.value + 1); + props.onClickNextStep?.(); + }; + const handleFinish = () => { + actived.value = false; + setInnerCurrent(-1); + destoryTooltipElm(); + props.onFinish?.(); + }; + + watch(innerCurrent, (val) => { + if (val >= 0 && val < steps.value.length) { + actived.value = true; + showGuide(); + } else { + console.info('当前引导的步骤', val + 1); + } + }); return () => { - return guid; + const renderOverlayLayer = () => ( +
+ ); + + const renderHighlightLayer = () => ( +
+ ); + + const renderCounter = () => { + const stepsLength = props.steps.length; + const popupDefaultCounter = ( +
+ + {innerCurrent.value + 1}/{stepsLength} + +
+ ); + const dialogDefaultCounter = ( +
+ {props.steps.map((_, i) => ( + + ))} +
+ ); + return <>{!hideCounter.value && (isPopup.value ? popupDefaultCounter : dialogDefaultCounter)}; + }; + + const renderAction = (mode: TdGuideProps['mode']) => { + const stepsLength = props.steps.length; + const isLast = innerCurrent.value === stepsLength - 1; + const isFirst = innerCurrent.value === 0; + const buttonSize = mode === 'popup' ? 'small' : 'medium'; + return ( +
+ {!hideSkip.value && !isLast && ( + + )} + {!hidePrev.value && !isFirst && ( + + )} + {!isLast && ( + + )} + {isLast && ( + + )} +
+ ); + }; + + const renderPopupContent = () => { + const title =
标题
; + const desc =
描述
; + const action = ( + + ); + + return ( +
+ {title} + {desc} + {action} +
+ ); + }; + + const renderReferenceLayer = () => ( + +
+ + ); + + const renderPopupGuide = () => { + return ( + <> + {renderOverlayLayer()} + {renderHighlightLayer()} + {renderReferenceLayer()} + + ); + }; + + const renderDialogGuide = () => { + // todo 类型 infer + return ( + +

This is a dialog

+ +
+ ); + }; + + return actived.value && (getCurrentCrossProps('mode') === 'popup' ? renderPopupGuide() : renderDialogGuide()); }; }, }); diff --git a/src/guide/props.ts b/src/guide/props.ts index bbf7b42de6..9c27cdee81 100644 --- a/src/guide/props.ts +++ b/src/guide/props.ts @@ -7,4 +7,81 @@ import { TdGuideProps } from './type'; import { PropType } from 'vue'; -export default {}; +export default { + /** 用于自定义渲染计数部分 */ + counter: { + type: Function as PropType, + }, + /** 当前步骤,即整个引导的进度。 */ + current: { + type: Number, + default: undefined, + }, + modelValue: { + type: Number, + default: undefined, + }, + /** 当前步骤,即整个引导的进度。,非受控属性 */ + defaultCurrent: { + type: Number, + }, + /** 透传 完成 的全部属性 */ + finishButtonProps: { + type: Object as PropType, + default: { content: '完成', theme: 'primary' }, + }, + /** 是否隐藏计数 */ + hideCounter: Boolean, + /** 是否隐藏上一步按钮 */ + hidePrev: Boolean, + /** 是否隐藏跳过按钮 */ + hideSkip: Boolean, + /** 起始序号 */ + initialNum: { + type: Number, + default: 0, + }, + /** 是否出现遮罩层 */ + mask: { + type: Boolean, + default: true, + }, + /** 引导框的类型 */ + mode: { + type: String as PropType, + default: 'popup' as TdGuideProps['mode'], + validator(val: TdGuideProps['mode']): boolean { + if (!val) return true; + return ['popup', 'dialog'].includes(val); + }, + }, + /** 透传 下一步按钮 的全部属性 */ + nextButtonProps: { + type: Object as PropType, + default: { content: '下一步', theme: 'primary' }, + }, + /** 透传 上一步按钮 的全部属性 */ + prevButtonProps: { + type: Object as PropType, + default: { content: '上一步', theme: 'primary' }, + }, + /** 透传 跳过按钮 的全部属性 */ + skipButtonProps: { + type: Object as PropType, + default: { content: '跳过', theme: 'default' }, + }, + /** 用于定义每个步骤的内容,包括高亮的节点、相对位置和具体的文案内容等。 */ + steps: { + type: Array as PropType, + }, + /** 当前步骤发生变化时触发 */ + onChange: Function as PropType, + /** 点击下一步时触发 */ + onClickNextStep: Function as PropType, + /** 点击上一步时触发 */ + onClickPrevStep: Function as PropType, + /** 点击完成按钮时触发 */ + onFinish: Function as PropType, + /** 点击跳过按钮时触发 */ + onSkip: Function as PropType, +}; diff --git a/src/guide/type.ts b/src/guide/type.ts index b2bdfc1dc5..044634cb67 100644 --- a/src/guide/type.ts +++ b/src/guide/type.ts @@ -4,6 +4,180 @@ * 该文件为脚本自动生成文件,请勿随意修改。如需修改请联系 PMC * */ -import { TNode, SizeEnum } from '../common'; +import { ButtonProps } from '../button'; +import { TNode, AttachNode } from '../common'; -export interface TdGuideProps {} +export interface TdGuideProps { + /** + * 用于自定义渲染计数部分 + */ + counter?: TNode; + /** + * 当前步骤,即整个引导的进度。 + */ + current?: number; + /** + * 当前步骤,即整个引导的进度。,非受控属性 + */ + defaultCurrent?: number; + /** + * 当前步骤,即整个引导的进度。 + */ + modelValue?: number; + /** + * 透传 完成 的全部属性 + * @default{ content: '完成', theme: 'primary' } + */ + finishButtonProps?: ButtonProps; + /** + * 是否隐藏计数 + * @default false + */ + hideCounter?: boolean; + /** + * 是否隐藏上一步按钮 + * @default false + */ + hidePrev?: boolean; + /** + * 是否隐藏跳过按钮 + * @default false + */ + hideSkip?: boolean; + /** + * 起始序号 + * @default 0 + */ + initialNum?: number; + /** + * 是否出现遮罩层 + * @default true + */ + mask?: boolean; + /** + * 引导框的类型 + * @default popup + */ + mode?: 'popup' | 'dialog'; + /** + * 透传 下一步按钮 的全部属性 + * @default{ content: '下一步', theme: 'primary' } + */ + nextButtonProps?: ButtonProps; + /** + * 透传 上一步按钮 的全部属性 + * @default{ content: '上一步', theme: 'primary' } + */ + prevButtonProps?: ButtonProps; + /** + * 透传 跳过按钮 的全部属性 + * @default { content: '跳过', theme: 'default' } + */ + skipButtonProps?: ButtonProps; + /** + * 用于定义每个步骤的内容,包括高亮的节点、相对位置和具体的文案内容等。 + */ + steps?: Array; + /** + * 当前步骤发生变化时触发 + */ + onChange?: (current: number, total: number, context?: { e?: MouseEvent }) => void; + /** + * 点击下一步时触发 + */ + onClickNextStep?: (next: number, current: number, total: number, context?: { e?: MouseEvent }) => void; + /** + * 点击上一步时触发 + */ + onClickPrevStep?: (prev: number, current: number, total: number, context?: { e?: MouseEvent }) => void; + /** + * 点击完成按钮时触发 + */ + onFinish?: (current: number, total: number, context?: { e?: MouseEvent }) => void; + /** + * 点击跳过按钮时触发 + */ + onSkip?: (current: number, total: number, context?: { e?: MouseEvent }) => void; +} + +export interface TdGuideStepProps { + /** + * 自定义内容,同 content + */ + children?: string | TNode; + /** + * 用户自定义引导弹框的内容,一旦存在,此时除 `placement`、`offset`和`element` 外,其它属性全部失效) + */ + content?: string | TNode; + /** + * 当前步骤的描述内容 + * @default '' + */ + description?: string; + /** + * 高亮的节点。数据类型为 String 时,会被当作选择器处理,进行节点查询。示例:'#tdesign' 或 () => document.querySelector('#tdesign') + */ + element: AttachNode; + /** + * 是否出现遮罩层 + * @default true + */ + mask?: boolean; + /** + * 当前步骤引导框的类型 + * @default popup + */ + mode?: 'popup' | 'dialog'; + /** + * 用于自定义当前引导框的下一步按钮的内容 + */ + nextButtonProps?: ButtonProps; + /** + * 相对于 placement 的偏移量,示例:[-10, 20] 或 ['10px', '8px'] + */ + offset?: Array; + /** + * 引导框相对于高亮元素出现的位置 + * @default top + */ + placement?: StepPopupPlacement | StepDialogPlacement; + /** + * 用于自定义当前引导框的上一步按钮的内容 + */ + prevButtonProps?: ButtonProps; + /** + * 用于自定义当前步骤引导框的跳过按钮的内容 + */ + skipButtonProps?: ButtonProps; + /** + * 覆盖引导框的类名 + * @default '' + */ + stepOverlayClass?: string; + /** + * 当前步骤的标题内容 + * @default '' + */ + title?: string; +} + +export type StepPopupPlacement = + | 'top' + | 'left' + | 'right' + | 'bottom' + | 'top-left' + | 'top-right' + | 'bottom-left' + | 'bottom-right' + | 'left-top' + | 'left-bottom' + | 'right-top' + | 'right-bottom'; + +export type StepDialogPlacement = 'top' | 'center'; + +export type CrossProps = Pick< + TdGuideStepProps, + 'mode' | 'skipButtonProps' | 'prevButtonProps' | 'nextButtonProps' | 'mask' +>; diff --git a/src/guide/utils/elementInViewport.ts b/src/guide/utils/elementInViewport.ts new file mode 100644 index 0000000000..8a0fd47677 --- /dev/null +++ b/src/guide/utils/elementInViewport.ts @@ -0,0 +1,18 @@ +/** + * Check to see if the element is in the viewport or not + * http://stackoverflow.com/questions/123999/how-to-tell-if-a-dom-element-is-visible-in-the-current-viewport + * + * @api private + * @method _elementInViewport + * @param {Object} el + */ +export default function elementInViewport(el) { + const rect = el.getBoundingClientRect(); + + return ( + rect.top >= 0 && + rect.left >= 0 && + rect.bottom + 80 <= window.innerHeight && // add 80 to get the text right + rect.right <= window.innerWidth + ); +} diff --git a/src/guide/utils/getElmInfo.ts b/src/guide/utils/getElmInfo.ts new file mode 100644 index 0000000000..b9c73b4203 --- /dev/null +++ b/src/guide/utils/getElmInfo.ts @@ -0,0 +1,32 @@ +/** + * 获取元素在页面中为位置信息 + * @param element: + * @returns {top: number, left: number} + */ +export function elmPosition(element: HTMLElement) { + const { body } = document; + const docEl = document.documentElement; + const scrollTop = window.pageYOffset || docEl.scrollTop || body.scrollTop; + const scrollLeft = window.pageXOffset || docEl.scrollLeft || body.scrollLeft; + + const x = element.getBoundingClientRect(); + + return { + top: x.top + scrollTop - element.clientTop, + left: x.left + scrollLeft - element.clientLeft, + }; +} + +/** + * 获取元素本身的宽高 + * @param element + * @returns + */ +export function elmRect(element: HTMLElement) { + const x = element.getBoundingClientRect(); + + return { + width: x.width, + height: x.height, + }; +} diff --git a/src/guide/utils/getOffset.ts b/src/guide/utils/getOffset.ts new file mode 100644 index 0000000000..60ef0311a2 --- /dev/null +++ b/src/guide/utils/getOffset.ts @@ -0,0 +1,52 @@ +import getPropValue from './getPropValue'; +import isFixed from './isFixed'; + +/** + * Get an element position on the page relative to another element (or body) + * Thanks to `meouw`: http://stackoverflow.com/a/442474/375966 + * + * @api private + * @method getOffset + * @param {Object} element + * @param {Object} relativeEl + * @returns Element's position info + */ +export default function getOffset(element: any, relativeEl: any) { + const { body } = document; + const docEl = document.documentElement; + const scrollTop = window.pageYOffset || docEl.scrollTop || body.scrollTop; + const scrollLeft = window.pageXOffset || docEl.scrollLeft || body.scrollLeft; + + relativeEl = relativeEl || body; + + const x = element.getBoundingClientRect(); + const xr = relativeEl.getBoundingClientRect(); + const relativeElPosition = getPropValue(relativeEl, 'position'); + + const obj = { + width: x.width, + height: x.height, + }; + + if ( + (relativeEl.tagName.toLowerCase() !== 'body' && relativeElPosition === 'relative') || + relativeElPosition === 'sticky' + ) { + // when the container of our target element is _not_ body and has either "relative" or "sticky" position, we should not + // consider the scroll position but we need to include the relative x/y of the container element + return Object.assign(obj, { + top: x.top - xr.top, + left: x.left - xr.left, + }); + } + if (isFixed(element)) { + return Object.assign(obj, { + top: x.top, + left: x.left, + }); + } + return Object.assign(obj, { + top: x.top + scrollTop, + left: x.left + scrollLeft, + }); +} diff --git a/src/guide/utils/getPropValue.ts b/src/guide/utils/getPropValue.ts new file mode 100644 index 0000000000..d2840c6e46 --- /dev/null +++ b/src/guide/utils/getPropValue.ts @@ -0,0 +1,25 @@ +/** + * Get an element CSS property on the page + * + * @api private + * @method _getPropValue + * @param {Object} element + * @param {String} propName + * @returns string property value + */ +export default function getPropValue(element: any, propName: any) { + let propValue = ''; + if (element.currentStyle) { + // IE + propValue = element.currentStyle[propName]; + } else if (document.defaultView && document.defaultView.getComputedStyle) { + // Others + propValue = document.defaultView.getComputedStyle(element, null).getPropertyValue(propName); + } + + // Prevent exception in IE + if (propValue && propValue.toLowerCase) { + return propValue.toLowerCase(); + } + return propValue; +} diff --git a/src/guide/utils/getScrollParent.ts b/src/guide/utils/getScrollParent.ts new file mode 100644 index 0000000000..8af95f7dcf --- /dev/null +++ b/src/guide/utils/getScrollParent.ts @@ -0,0 +1,23 @@ +/** + * Find the nearest scrollable parent + * @param Element element + * @return Element + */ +export default function getScrollParent(element: any) { + let style = window.getComputedStyle(element); + const excludeStaticParent = style.position === 'absolute'; + const overflowRegex = /(auto|scroll)/; + + if (style.position === 'fixed') return document.body; + + for (let parent = element; parent.parentElement; ) { + parent = parent.parentElement; + style = window.getComputedStyle(parent); + if (excludeStaticParent && style.position === 'static') { + continue; + } + if (overflowRegex.test(style.overflow + style.overflowY + style.overflowX)) return parent; + } + + return document.body; +} diff --git a/src/guide/utils/getTargetElm.ts b/src/guide/utils/getTargetElm.ts new file mode 100644 index 0000000000..89371422cf --- /dev/null +++ b/src/guide/utils/getTargetElm.ts @@ -0,0 +1,11 @@ +export default function getTargetElm(targetElm: unknown): HTMLElement { + if (typeof targetElm === 'string') { + const targetElement = document.querySelector(targetElm); + if (targetElement) { + return targetElement as HTMLElement; + } + throw new Error('There is no element with given selector.'); + } else { + return document.body; + } +} diff --git a/src/guide/utils/getWindowSize.ts b/src/guide/utils/getWindowSize.ts new file mode 100644 index 0000000000..fb757c4ef9 --- /dev/null +++ b/src/guide/utils/getWindowSize.ts @@ -0,0 +1,15 @@ +/** + * Provides a cross-browser way to get the screen dimensions + * via: http://stackoverflow.com/questions/5864467/internet-explorer-innerheight + * + * @api private + * @method _getWinSize + * @returns {Object} width and height attributes + */ +export default function getWinSize() { + if (window.innerWidth !== undefined) { + return { width: window.innerWidth, height: window.innerHeight }; + } + const D = document.documentElement; + return { width: D.clientWidth, height: D.clientHeight }; +} diff --git a/src/guide/utils/index.ts b/src/guide/utils/index.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/guide/utils/isFixed.ts b/src/guide/utils/isFixed.ts new file mode 100644 index 0000000000..99f0f9317c --- /dev/null +++ b/src/guide/utils/isFixed.ts @@ -0,0 +1,23 @@ +import getPropValue from './getPropValue'; + +/** + * Checks to see if target element (or parents) position is fixed or not + * + * @api private + * @method _isFixed + * @param {Object} element + * @returns Boolean + */ +export default function isFixed(element: any): Boolean { + const p = element.parentNode; + + if (!p || p.nodeName === 'HTML') { + return false; + } + + if (getPropValue(element, 'position') === 'fixed') { + return true; + } + + return isFixed(p); +} diff --git a/src/guide/utils/scrollTo.ts b/src/guide/utils/scrollTo.ts new file mode 100644 index 0000000000..122c9ec660 --- /dev/null +++ b/src/guide/utils/scrollTo.ts @@ -0,0 +1,25 @@ +import getWindowSize from './getWindowSize'; +import elementInViewport from './elementInViewport'; + +/** + * To change the scroll of `window` after highlighting an element + * + * @api private + * @param {Object} element + */ +export default function scrollTo(element) { + const rect = element.getBoundingClientRect(); + + if (!elementInViewport(element)) { + const winHeight = getWindowSize().height; + const top = rect.bottom - (rect.bottom - rect.top); + + if (top < 0 || element.clientHeight > winHeight) { + window.scrollBy(0, rect.top - (winHeight / 2 - rect.height / 2)); // 30px padding from edge to look nice + + // Scroll down + } else { + window.scrollBy(0, rect.top - (winHeight / 2 - rect.height / 2)); // 30px padding from edge to look nice + } + } +} diff --git a/src/guide/utils/setStyle.ts b/src/guide/utils/setStyle.ts new file mode 100644 index 0000000000..c68a4aaf02 --- /dev/null +++ b/src/guide/utils/setStyle.ts @@ -0,0 +1,24 @@ +/** + * Sets the style of an DOM element + * + * @param {Object} element + * @param {Object|string} style + * @return null + */ +export default function setStyle(element: any, style: any) { + let cssText = ''; + + if (element.style.cssText) { + cssText += element.style.cssText; + } + + if (typeof style === 'string') { + cssText += style; + } else { + for (const rule in style) { + cssText += `${rule}:${style[rule]};`; + } + } + + element.style.cssText = cssText; +} From cbd49e4467c9175127bd0395e32eff045b093ca9 Mon Sep 17 00:00:00 2001 From: zhangpaopao Date: Sun, 21 Aug 2022 20:03:55 +0800 Subject: [PATCH 04/48] feat(guide): event params and current --- examples/guide/demos/guide.vue | 56 +++++++++++++++++++++++----------- src/guide/guide.tsx | 50 +++++++++++++++++++----------- 2 files changed, 71 insertions(+), 35 deletions(-) diff --git a/examples/guide/demos/guide.vue b/examples/guide/demos/guide.vue index 5028d7ff6d..955105f14c 100644 --- a/examples/guide/demos/guide.vue +++ b/examples/guide/demos/guide.vue @@ -1,42 +1,55 @@ diff --git a/examples/guide/demos/guide.vue b/examples/guide/demos/guide.vue index 0b914b86ef..a038474997 100644 --- a/examples/guide/demos/guide.vue +++ b/examples/guide/demos/guide.vue @@ -37,7 +37,7 @@ const steps = [ title: '新手引导标题', description: '新手引导的说明文案。', prevButtonProps: { content: '自定义上一步', theme: 'success' }, - content: Base, + // content: (h) => h(Base), }, { element: '#guide3', diff --git a/src/guide/guide-step.tsx b/src/guide/guide-step.tsx new file mode 100644 index 0000000000..dfdbe2ac3d --- /dev/null +++ b/src/guide/guide-step.tsx @@ -0,0 +1,19 @@ +import { defineComponent, onMounted, onUnmounted, ref, Fragment, Text, watch, PropType, VNode, Teleport } from 'vue'; +import { useContent, useTNodeJSX } from '../hooks/tnode'; +import { TdGuideStepProps } from './type'; + +export const GuidePopupStepContent = defineComponent({ + props: { content: [String, Function] as PropType }, + setup() { + const renderTNodeJSX = useTNodeJSX(); + const el = ref(null); + + onMounted(() => { + const { parentNode } = el.value; + parentNode.parentNode.appendChild(el.value); + parentNode.parentNode.removeChild(parentNode); + }); + + return () =>
{renderTNodeJSX('content', 'default')}
; + }, +}); diff --git a/src/guide/guide.tsx b/src/guide/guide.tsx index 9e9ae13645..0b48ed0642 100644 --- a/src/guide/guide.tsx +++ b/src/guide/guide.tsx @@ -21,6 +21,8 @@ import Dialog from '../dialog'; import { TdGuideProps, TdGuideStepProps, CrossProps, StepDialogPlacement } from './type'; import { defalutCrossProps, tooltipZIndex } from './const'; +import { GuidePopupStepContent } from './guide-step'; + export default defineComponent({ name: 'TGuide', directives: { @@ -58,7 +60,6 @@ export default defineComponent({ const stepsTotal = computed(() => steps.value.length); // 当前步骤的信息 const currentStepInfo = computed(() => steps.value[innerCurrent.value]); - const scrollCache: number[] = []; // 获取当前步骤的所有属性 用户当前步骤设置 > 用户全局设置的 > 默认值 const getCurrentCrossProps = (propsName: keyof CrossProps) => @@ -278,23 +279,12 @@ export default defineComponent({ const renderTooltipBody = () => { const title =
{currentStepInfo.value.title}
; const desc =
{currentStepInfo.value.description}
; - const { content } = currentStepInfo.value; - let renderBody; - if (content) { - if (typeof content === 'string') { - renderBody = content; - } else { - renderBody = h(currentStepInfo.value.content); - } - } else { - renderBody = ( - <> - {title} - {desc} - - ); - } - return renderBody; + return ( + <> + {title} + {desc} + + ); }; const renderPopupContent = () => { @@ -313,18 +303,28 @@ export default defineComponent({ ); }; - const renderReferenceLayer = () => ( - -
- - ); + const renderReferenceLayer = () => { + const { content } = currentStepInfo.value; + let renderBody; + if (content) { + renderBody = () => ; + } else { + renderBody = renderPopupContent; + } + + return ( + +
+ + ); + }; const renderPopupGuide = () => { return <>{renderReferenceLayer()}; From 4e6d41a54072cc020aba1953dfacd00a0ba1168e Mon Sep 17 00:00:00 2001 From: zhangpaopao Date: Tue, 23 Aug 2022 11:38:10 +0800 Subject: [PATCH 12/48] feat(guide): highlighPadding and currentCrossProps type --- src/guide/guide-step-props.ts | 4 ++++ src/guide/guide.tsx | 22 +++++++++++----------- src/guide/props.ts | 13 +++++++++---- src/guide/type.ts | 12 +++++++++++- 4 files changed, 35 insertions(+), 16 deletions(-) diff --git a/src/guide/guide-step-props.ts b/src/guide/guide-step-props.ts index 07b3d1844d..7d6c89c0d2 100644 --- a/src/guide/guide-step-props.ts +++ b/src/guide/guide-step-props.ts @@ -26,6 +26,10 @@ export default { type: [String, Function] as PropType, required: true, }, + /** 高亮框的 padding */ + highlightPadding: { + type: Number, + }, /** 当期步骤引导框的类型 */ mode: { type: String as PropType, diff --git a/src/guide/guide.tsx b/src/guide/guide.tsx index 0b48ed0642..1f57229a00 100644 --- a/src/guide/guide.tsx +++ b/src/guide/guide.tsx @@ -62,11 +62,10 @@ export default defineComponent({ const currentStepInfo = computed(() => steps.value[innerCurrent.value]); // 获取当前步骤的所有属性 用户当前步骤设置 > 用户全局设置的 > 默认值 - const getCurrentCrossProps = (propsName: keyof CrossProps) => + const getCurrentCrossProps = (propsName: Key) => currentStepInfo.value[propsName] ?? props[propsName] ?? defalutCrossProps[propsName]; // 当前是否为 popup const isPopup = computed(() => getCurrentCrossProps('mode') === 'popup'); - // 滑动到元素位置 const scrollParentToElement = (element: HTMLElement) => { const parent = getScrollParent(element); @@ -83,11 +82,12 @@ export default defineComponent({ elementPosition.left += _scrollLeft; elementPosition.top += _scrollTop; } + const highlightPadding = getCurrentCrossProps('highlightPadding'); setStyle(highlighLayer, { - width: `${elementPosition.width}px`, - height: `${elementPosition.height}px`, - top: `${elementPosition.top}px`, - left: `${elementPosition.left}px`, + width: `${elementPosition.width + highlightPadding}px`, + height: `${elementPosition.height + highlightPadding}px`, + top: `${elementPosition.top - highlightPadding / 2}px`, + left: `${elementPosition.left - highlightPadding / 2}px`, }); }; @@ -239,7 +239,7 @@ export default defineComponent({ size={buttonSize} variant="base" onClick={handleSkip} - {...(getCurrentCrossProps('skipButtonProps') as TdButtonProps)} + {...getCurrentCrossProps('skipButtonProps')} /> )} {!hidePrev.value && !isFirst && ( @@ -249,7 +249,7 @@ export default defineComponent({ size={buttonSize} variant="base" onClick={handlePrev} - {...(getCurrentCrossProps('prevButtonProps') as TdButtonProps)} + {...getCurrentCrossProps('prevButtonProps')} /> )} {!isLast && ( @@ -259,7 +259,7 @@ export default defineComponent({ size={buttonSize} variant="base" onClick={handleNext} - {...(getCurrentCrossProps('nextButtonProps') as TdButtonProps)} + {...getCurrentCrossProps('nextButtonProps')} /> )} {isLast && ( @@ -269,7 +269,7 @@ export default defineComponent({ size={buttonSize} variant="base" onClick={handleFinish} - {...(props.finishButtonProps as TdButtonProps)} + {...props.finishButtonProps} /> )}
@@ -363,7 +363,7 @@ export default defineComponent({ <> {renderOverlayLayer()} {renderHighlightLayer()} - {getCurrentCrossProps('mode') === 'popup' ? renderPopupGuide() : renderDialogGuide()} + {isPopup.value ? renderPopupGuide() : renderDialogGuide()} ); }; diff --git a/src/guide/props.ts b/src/guide/props.ts index 9c27cdee81..a05840f17b 100644 --- a/src/guide/props.ts +++ b/src/guide/props.ts @@ -28,7 +28,7 @@ export default { /** 透传 完成 的全部属性 */ finishButtonProps: { type: Object as PropType, - default: { content: '完成', theme: 'primary' }, + default: { content: '完成', theme: 'primary' } as TdGuideProps['skipButtonProps'], }, /** 是否隐藏计数 */ hideCounter: Boolean, @@ -36,6 +36,11 @@ export default { hidePrev: Boolean, /** 是否隐藏跳过按钮 */ hideSkip: Boolean, + /** 高亮框的 padding */ + highlightPadding: { + type: Number, + default: 4, + }, /** 起始序号 */ initialNum: { type: Number, @@ -58,17 +63,17 @@ export default { /** 透传 下一步按钮 的全部属性 */ nextButtonProps: { type: Object as PropType, - default: { content: '下一步', theme: 'primary' }, + default: { content: '下一步', theme: 'primary' } as TdGuideProps['skipButtonProps'], }, /** 透传 上一步按钮 的全部属性 */ prevButtonProps: { type: Object as PropType, - default: { content: '上一步', theme: 'primary' }, + default: { content: '上一步', theme: 'primary' } as TdGuideProps['skipButtonProps'], }, /** 透传 跳过按钮 的全部属性 */ skipButtonProps: { type: Object as PropType, - default: { content: '跳过', theme: 'default' }, + default: { content: '跳过', theme: 'default' } as TdGuideProps['skipButtonProps'], }, /** 用于定义每个步骤的内容,包括高亮的节点、相对位置和具体的文案内容等。 */ steps: { diff --git a/src/guide/type.ts b/src/guide/type.ts index cf680b7488..37c0ecb0ed 100644 --- a/src/guide/type.ts +++ b/src/guide/type.ts @@ -49,6 +49,11 @@ export interface TdGuideProps { * @default 0 */ initialNum?: number; + /** + * 高亮框的 padding + * @default 2 + */ + highlightPadding?: number; /** * 是否出现遮罩层 * @default true @@ -118,6 +123,11 @@ export interface TdGuideStepProps { * 高亮的节点。数据类型为 String 时,会被当作选择器处理,进行节点查询。示例:'#tdesign' 或 () => document.querySelector('#tdesign') */ element: AttachNode; + /** + * 高亮框的 padding + * @default 2 + */ + highlightPadding?: number; /** * 是否出现遮罩层 * @default true @@ -179,7 +189,7 @@ export type StepDialogPlacement = 'top' | 'center'; export type CrossProps = Pick< TdGuideStepProps, - 'mode' | 'skipButtonProps' | 'prevButtonProps' | 'nextButtonProps' | 'mask' + 'mode' | 'skipButtonProps' | 'prevButtonProps' | 'nextButtonProps' | 'mask' | 'highlightPadding' >; export type ScrollTo = { From ab93cf487eb9e8a1025322bf9f03ade7a77a7907 Mon Sep 17 00:00:00 2001 From: zhangpaopao Date: Fri, 26 Aug 2022 19:33:15 +0800 Subject: [PATCH 13/48] feat(guide): mask change to showOverlay --- src/guide/const/index.ts | 2 +- src/guide/guide-step-props.ts | 5 +++++ src/guide/guide.tsx | 4 ++-- src/guide/props.ts | 10 +++++----- src/guide/type.ts | 22 +++++++++++----------- 5 files changed, 24 insertions(+), 19 deletions(-) diff --git a/src/guide/const/index.ts b/src/guide/const/index.ts index 078b26fe7f..bc948ee6fe 100644 --- a/src/guide/const/index.ts +++ b/src/guide/const/index.ts @@ -1,7 +1,7 @@ import { CrossProps } from '../type'; export const defalutCrossProps: CrossProps = { - mask: true, + showOverlay: true, mode: 'popup', }; diff --git a/src/guide/guide-step-props.ts b/src/guide/guide-step-props.ts index 7d6c89c0d2..b7850b6b4f 100644 --- a/src/guide/guide-step-props.ts +++ b/src/guide/guide-step-props.ts @@ -56,6 +56,11 @@ export default { prevButtonProps: { type: Object as PropType, }, + /** 是否出现遮罩层 */ + showOverlay: { + type: Boolean, + default: true, + }, /** 用于自定义当前步骤引导框的跳过按钮的内容 */ skipButtonProps: { type: Object as PropType, diff --git a/src/guide/guide.tsx b/src/guide/guide.tsx index 1f57229a00..298756b18f 100644 --- a/src/guide/guide.tsx +++ b/src/guide/guide.tsx @@ -194,8 +194,8 @@ export default defineComponent({ const renderHighlightLayer = () => { const highlightClass = `${COMPONENT_NAME.value}__highlight`; - const isMask = getCurrentCrossProps('mask'); - const classes = [highlightClass, `${highlightClass}--${isMask ? 'mask' : 'nomask'}`]; + const showOverlay = getCurrentCrossProps('showOverlay'); + const classes = [highlightClass, `${highlightClass}--${showOverlay ? 'mask' : 'nomask'}`]; return
; }; diff --git a/src/guide/props.ts b/src/guide/props.ts index a05840f17b..f5a4024223 100644 --- a/src/guide/props.ts +++ b/src/guide/props.ts @@ -46,11 +46,6 @@ export default { type: Number, default: 0, }, - /** 是否出现遮罩层 */ - mask: { - type: Boolean, - default: true, - }, /** 引导框的类型 */ mode: { type: String as PropType, @@ -70,6 +65,11 @@ export default { type: Object as PropType, default: { content: '上一步', theme: 'primary' } as TdGuideProps['skipButtonProps'], }, + /** 是否出现遮罩层 */ + showOverlay: { + type: Boolean, + default: true, + }, /** 透传 跳过按钮 的全部属性 */ skipButtonProps: { type: Object as PropType, diff --git a/src/guide/type.ts b/src/guide/type.ts index 37c0ecb0ed..db876bbb33 100644 --- a/src/guide/type.ts +++ b/src/guide/type.ts @@ -54,11 +54,6 @@ export interface TdGuideProps { * @default 2 */ highlightPadding?: number; - /** - * 是否出现遮罩层 - * @default true - */ - mask?: boolean; /** * 引导框的类型 * @default popup @@ -74,6 +69,11 @@ export interface TdGuideProps { * @default{ content: '上一步', theme: 'primary' } */ prevButtonProps?: ButtonProps; + /** + * 是否出现遮罩层 + * @default true + */ + showOverlay?: boolean; /** * 透传 跳过按钮 的全部属性 * @default { content: '跳过', theme: 'default' } @@ -128,11 +128,6 @@ export interface TdGuideStepProps { * @default 2 */ highlightPadding?: number; - /** - * 是否出现遮罩层 - * @default true - */ - mask?: boolean; /** * 当前步骤引导框的类型 * @default popup @@ -155,6 +150,11 @@ export interface TdGuideStepProps { * 用于自定义当前引导框的上一步按钮的内容 */ prevButtonProps?: ButtonProps; + /** + * 是否出现遮罩层 + * @default true + */ + showOverlay?: boolean; /** * 用于自定义当前步骤引导框的跳过按钮的内容 */ @@ -189,7 +189,7 @@ export type StepDialogPlacement = 'top' | 'center'; export type CrossProps = Pick< TdGuideStepProps, - 'mode' | 'skipButtonProps' | 'prevButtonProps' | 'nextButtonProps' | 'mask' | 'highlightPadding' + 'mode' | 'skipButtonProps' | 'prevButtonProps' | 'nextButtonProps' | 'showOverlay' | 'highlightPadding' >; export type ScrollTo = { From d4c1b4265a0924156693bb0e79189526ac317f6b Mon Sep 17 00:00:00 2001 From: zhangpaopao Date: Fri, 26 Aug 2022 20:39:45 +0800 Subject: [PATCH 14/48] feat(guide): support highlightContent --- examples/guide/demos/guide.vue | 5 +++-- examples/guide/demos/highlightContent.vue | 18 ++++++++++++++++++ src/guide/guide-step-props.ts | 6 ++++++ src/guide/guide-step.tsx | 9 +++++++++ src/guide/guide.tsx | 17 +++++++++++++---- src/guide/type.ts | 6 +++++- 6 files changed, 54 insertions(+), 7 deletions(-) create mode 100644 examples/guide/demos/highlightContent.vue diff --git a/examples/guide/demos/guide.vue b/examples/guide/demos/guide.vue index a038474997..274e33149f 100644 --- a/examples/guide/demos/guide.vue +++ b/examples/guide/demos/guide.vue @@ -23,6 +23,7 @@ + diff --git a/examples/guide/demos/dialog.vue b/examples/guide/demos/dialog.vue new file mode 100644 index 0000000000..f293b447bd --- /dev/null +++ b/examples/guide/demos/dialog.vue @@ -0,0 +1,139 @@ + + + + + diff --git a/examples/guide/demos/no-mask.vue b/examples/guide/demos/no-mask.vue new file mode 100644 index 0000000000..1e49ebbaa1 --- /dev/null +++ b/examples/guide/demos/no-mask.vue @@ -0,0 +1,139 @@ + + + + + diff --git a/examples/guide/demos/popup-dialog.vue b/examples/guide/demos/popup-dialog.vue new file mode 100644 index 0000000000..9287628f75 --- /dev/null +++ b/examples/guide/demos/popup-dialog.vue @@ -0,0 +1,139 @@ + + + + + diff --git a/examples/guide/usage/index-keep.vue b/examples/guide/usage/index-keep.vue deleted file mode 100644 index 78202bb4e2..0000000000 --- a/examples/guide/usage/index-keep.vue +++ /dev/null @@ -1,24 +0,0 @@ - - - - diff --git a/examples/guide/usage/props.json b/examples/guide/usage/props.json deleted file mode 100644 index 1f1bab969a..0000000000 --- a/examples/guide/usage/props.json +++ /dev/null @@ -1,32 +0,0 @@ -[ - { - "name": "indicator", - "type": "Boolean", - "defaultValue": true, - "options": [] - }, - { - "name": "inheritColor", - "type": "Boolean", - "defaultValue": false, - "options": [] - }, - { - "name": "loading", - "type": "Boolean", - "defaultValue": true, - "options": [] - }, - { - "name": "preventScrollThrough", - "type": "Boolean", - "defaultValue": true, - "options": [] - }, - { - "name": "showOverlay", - "type": "Boolean", - "defaultValue": true, - "options": [] - } -] \ No newline at end of file diff --git a/src/_common b/src/_common index 0a23114bad..51ab9fd5c3 160000 --- a/src/_common +++ b/src/_common @@ -1 +1 @@ -Subproject commit 0a23114baddbce68c8b2178c7b58ac73189013f8 +Subproject commit 51ab9fd5c3d9ae79c13d051e1e67d87ede35869f diff --git a/src/guide/guide.tsx b/src/guide/guide.tsx index d41d51ebe7..e4e99a49b3 100644 --- a/src/guide/guide.tsx +++ b/src/guide/guide.tsx @@ -9,6 +9,7 @@ import scrollTo from './utils/scrollTo'; import TransferDom from '../utils/transfer-dom'; import { addClass, removeClass } from '../utils/dom'; +import isFixed from './utils/isFixed'; import { usePrefixClass, useCommonClassName } from '../hooks/useConfig'; import { useContent, useTNodeJSX } from '../hooks/tnode'; @@ -52,6 +53,8 @@ export default defineComponent({ const currentHighlightLayerElm = ref(); // 下一个高亮的元素 const nextHighlightLayerElm = ref(); + // dialog wrapper ref + const dialogWrapperRef = ref(); // dialog ref const dialogTooltipRef = ref(); // 是否开始展示 @@ -60,6 +63,8 @@ export default defineComponent({ const stepsTotal = computed(() => steps.value.length); // 当前步骤的信息 const currentStepInfo = computed(() => steps.value[innerCurrent.value]); + // 当前元素位置状态 + const currentElmIsFixed = computed(() => isFixed(currentHighlightLayerElm.value || document.body)); // 获取当前步骤的所有属性 用户当前步骤设置 > 用户全局设置的 > 默认值 const getCurrentCrossProps = (propsName: Key) => @@ -138,6 +143,7 @@ export default defineComponent({ destoryDialogTooltipElm(); highlightLayerRef.value?.parentNode.removeChild(highlightLayerRef.value); overlayLayerRef.value?.parentNode.removeChild(overlayLayerRef.value); + dialogWrapperRef.value?.parentNode.removeChild(dialogWrapperRef.value); }; const handleSkip = (e: MouseEvent) => { @@ -203,6 +209,7 @@ export default defineComponent({ const highlightClass = [ `${COMPONENT_NAME.value}__highlight`, `${COMPONENT_NAME.value}__highlight--${isPopup.value ? 'popup' : 'dialog'}`, + `${COMPONENT_NAME.value}--${currentElmIsFixed.value && isPopup.value ? 'fixed' : 'absolute'}`, ]; const showOverlay = getCurrentCrossProps('showOverlay'); const classes = [...highlightClass, `${COMPONENT_NAME.value}__highlight--${showOverlay ? 'mask' : 'nomask'}`]; @@ -231,16 +238,6 @@ export default defineComponent({ )}
); - // const dialogDefaultCounter = ( - //
- // {popupSlotCounter || - // props.steps.map((_, i) => ( - // - // ))} - //
- // ); return <>{!hideCounter.value && popupDefaultCounter}; }; @@ -331,6 +328,11 @@ export default defineComponent({ renderBody = renderPopupContent; } + const classes = [ + `${COMPONENT_NAME.value}__reference`, + `${COMPONENT_NAME.value}--${currentElmIsFixed.value ? 'fixed' : 'absolute'}`, + ]; + return ( -
+
); }; @@ -357,6 +359,7 @@ export default defineComponent({ ]; const dialogClasses = [ `${COMPONENT_NAME.value}__reference`, + `${COMPONENT_NAME.value}--absolute`, `${COMPONENT_NAME.value}__dialog`, { [`${COMPONENT_NAME.value}__dialog--nomask`]: !getCurrentCrossProps('showOverlay'), @@ -366,7 +369,7 @@ export default defineComponent({ const footerClasses = [`${COMPONENT_NAME.value}__footer`, `${COMPONENT_NAME.value}__footer--popup`]; return ( <> -
+
{renderTooltipBody()}
From 1e3f1529afc188777ed1f3e188cb2a2ed2270c92 Mon Sep 17 00:00:00 2001 From: zhangpaopao Date: Mon, 29 Aug 2022 20:28:49 +0800 Subject: [PATCH 19/48] feat(guide): optimization --- examples/guide/guide.md | 4 +- src/guide/const/index.ts | 2 - src/guide/guide.tsx | 119 ++++++++++++-------------- src/guide/utils/elementInViewport.ts | 17 +--- src/guide/utils/getElmCssPropValue.ts | 13 +++ src/guide/utils/getElmInfo.ts | 32 ------- src/guide/utils/getOffset.ts | 52 ----------- src/guide/utils/getPropValue.ts | 25 ------ src/guide/utils/getRelativePositon.ts | 36 ++++++++ src/guide/utils/getScrollParent.ts | 13 +-- src/guide/utils/getTargetElm.ts | 17 +++- src/guide/utils/getWindowScroll.ts | 8 ++ src/guide/utils/getWindowSize.ts | 12 +-- src/guide/utils/index.ts | 19 ++++ src/guide/utils/isFixed.ts | 16 +--- src/guide/utils/removeElm.ts | 3 + src/guide/utils/scrollTo.ts | 12 +-- src/guide/utils/setStyle.ts | 9 +- 18 files changed, 168 insertions(+), 241 deletions(-) create mode 100644 src/guide/utils/getElmCssPropValue.ts delete mode 100644 src/guide/utils/getElmInfo.ts delete mode 100644 src/guide/utils/getOffset.ts delete mode 100644 src/guide/utils/getPropValue.ts create mode 100644 src/guide/utils/getRelativePositon.ts create mode 100644 src/guide/utils/getWindowScroll.ts create mode 100644 src/guide/utils/removeElm.ts diff --git a/examples/guide/guide.md b/examples/guide/guide.md index 40452d8252..7da6b47089 100644 --- a/examples/guide/guide.md +++ b/examples/guide/guide.md @@ -5,8 +5,8 @@ 名称 | 类型 | 默认值 | 说明 | 必传 -- | -- | -- | -- | -- counter | Slot / Function | - | 用于自定义渲染计数部分。TS 类型:`TNode`。[通用类型定义](https://github.com/Tencent/tdesign-vue-next/blob/develop/src/common.ts) | N -current | Number | - | 当前步骤,即整个引导的进度。。支持语法糖 `v-model` 或 `v-model:current` | N -defaultCurrent | Number | - | 当前步骤,即整个引导的进度。。非受控属性 | N +current | Number | - | 当前步骤,即整个引导的进度。支持语法糖 `v-model` 或 `v-model:current` | N +defaultCurrent | Number | - | 当前步骤,即整个引导的进度。非受控属性 | N finishButtonProps | Object | `{ content: '完成', theme: 'primary' }` | 透传 完成 的全部属性。TS 类型:`ButtonProps` | N hideCounter | Boolean | false | 是否隐藏计数 | N hidePrev | Boolean | false | 是否隐藏上一步按钮 | N diff --git a/src/guide/const/index.ts b/src/guide/const/index.ts index bc948ee6fe..2f13a3961e 100644 --- a/src/guide/const/index.ts +++ b/src/guide/const/index.ts @@ -4,5 +4,3 @@ export const defalutCrossProps: CrossProps = { showOverlay: true, mode: 'popup', }; - -export const tooltipZIndex = 999999; diff --git a/src/guide/guide.tsx b/src/guide/guide.tsx index e4e99a49b3..0499f4e1cd 100644 --- a/src/guide/guide.tsx +++ b/src/guide/guide.tsx @@ -1,46 +1,42 @@ -import { computed, defineComponent, h, nextTick, onMounted, ref, toRefs, watch } from 'vue'; +import { defineComponent, computed, nextTick, onMounted, ref, toRefs, watch } from 'vue'; import props from './props'; -import setStyle from './utils/setStyle'; -import getScrollParent from './utils/getScrollParent'; -import getOffset from './utils/getOffset'; -import getTargetElm from './utils/getTargetElm'; -import scrollTo from './utils/scrollTo'; + +import { TdGuideProps, CrossProps } from './type'; +import { defalutCrossProps } from './const'; +import { + setStyle, + scrollToParentVisibleArea, + getRelativePositon, + getTargetElm, + scrollTo, + isFixed, + getWindowScroll, + removeElm, +} from './utils'; import TransferDom from '../utils/transfer-dom'; import { addClass, removeClass } from '../utils/dom'; -import isFixed from './utils/isFixed'; -import { usePrefixClass, useCommonClassName } from '../hooks/useConfig'; -import { useContent, useTNodeJSX } from '../hooks/tnode'; import useVModel from '../hooks/useVModel'; +import { useTNodeJSX } from '../hooks/tnode'; +import { usePrefixClass } from '../hooks/useConfig'; -import Button, { TdButtonProps } from '../button'; +import Button from '../button'; import Popup from '../popup'; -import Dialog from '../dialog'; - -import { TdGuideProps, TdGuideStepProps, CrossProps, StepDialogPlacement } from './type'; -import { defalutCrossProps } from './const'; import { GuidePopupStepContent, GuideStepHighlightContent } from './guide-step'; export default defineComponent({ name: 'TGuide', - directives: { - TransferDom, - }, - props: { ...props }, + directives: { TransferDom }, + props, setup(props) { - const renderContent = useContent(); const renderTNodeJSX = useTNodeJSX(); - - const classPrefix = usePrefixClass(); const COMPONENT_NAME = usePrefixClass('guide'); const LOCK_CLASS = usePrefixClass('guide--lock'); - const { SIZE } = useCommonClassName(); const { current, modelValue, hideCounter, hidePrev, hideSkip, steps, zIndex } = toRefs(props); - const [innerCurrent, setInnerCurrent] = useVModel(current, modelValue, props.defaultCurrent, props.onChange); // 覆盖层,用于覆盖所有元素 @@ -63,45 +59,40 @@ export default defineComponent({ const stepsTotal = computed(() => steps.value.length); // 当前步骤的信息 const currentStepInfo = computed(() => steps.value[innerCurrent.value]); + // 当前是否为 popup + const isPopup = computed(() => getCurrentCrossProps('mode') === 'popup'); // 当前元素位置状态 const currentElmIsFixed = computed(() => isFixed(currentHighlightLayerElm.value || document.body)); - // 获取当前步骤的所有属性 用户当前步骤设置 > 用户全局设置的 > 默认值 const getCurrentCrossProps = (propsName: Key) => currentStepInfo.value[propsName] ?? props[propsName] ?? defalutCrossProps[propsName]; - // 当前是否为 popup - const isPopup = computed(() => getCurrentCrossProps('mode') === 'popup'); - // 滑动到元素位置 - const scrollParentToElement = (element: HTMLElement) => { - const parent = getScrollParent(element); - if (parent === document.body) return; - parent.scrollTop = element.offsetTop - parent.offsetTop; - }; // 设置高亮层的位置 const setHighlightLayerPosition = (highlighLayer: HTMLElement) => { - const elementPosition = getOffset(nextHighlightLayerElm.value, currentHighlightLayerElm.value); + let { top, left } = getRelativePositon(nextHighlightLayerElm.value, currentHighlightLayerElm.value); + const { width, height } = nextHighlightLayerElm.value.getBoundingClientRect(); if (!isPopup.value) { - const _scrollLeft = window.scrollX || window.pageXOffset || document.documentElement.scrollLeft; - const _scrollTop = window.scrollY || window.pageYOffset || document.documentElement.scrollTop; - elementPosition.left += _scrollLeft; - elementPosition.top += _scrollTop; + const { scrollTop, scrollLeft } = getWindowScroll(); + top += scrollTop; + left += scrollLeft; } + const highlightPadding = getCurrentCrossProps('highlightPadding'); setStyle(highlighLayer, { - width: `${elementPosition.width + highlightPadding * 2}px`, - height: `${elementPosition.height + highlightPadding * 2}px`, - top: `${elementPosition.top - highlightPadding}px`, - left: `${elementPosition.left - highlightPadding}px`, + width: `${width + highlightPadding * 2}px`, + height: `${height + highlightPadding * 2}px`, + top: `${top - highlightPadding}px`, + left: `${left - highlightPadding}px`, }); }; const showPopupGuide = () => { const currentElement = getTargetElm(currentStepInfo.value.element); nextHighlightLayerElm.value = currentElement; + nextTick(() => { - scrollParentToElement(nextHighlightLayerElm.value); + scrollToParentVisibleArea(nextHighlightLayerElm.value); setHighlightLayerPosition(highlightLayerRef.value); setHighlightLayerPosition(referenceLayerRef.value); scrollTo(nextHighlightLayerElm.value); @@ -110,14 +101,14 @@ export default defineComponent({ }; const destoryTooltipElm = () => { - referenceLayerRef.value?.parentNode.removeChild(referenceLayerRef.value); + removeElm(referenceLayerRef.value); }; const showDialogGuide = () => { nextTick(() => { const currentElement = dialogTooltipRef.value; nextHighlightLayerElm.value = currentElement; - scrollParentToElement(nextHighlightLayerElm.value); + scrollToParentVisibleArea(nextHighlightLayerElm.value); setHighlightLayerPosition(highlightLayerRef.value); scrollTo(nextHighlightLayerElm.value); currentHighlightLayerElm.value = currentElement; @@ -125,7 +116,7 @@ export default defineComponent({ }; const destoryDialogTooltipElm = () => { - dialogTooltipRef.value?.parentNode.removeChild(dialogTooltipRef.value); + removeElm(dialogTooltipRef.value); }; const showGuide = () => { @@ -141,15 +132,15 @@ export default defineComponent({ const destoryGuide = () => { destoryTooltipElm(); destoryDialogTooltipElm(); - highlightLayerRef.value?.parentNode.removeChild(highlightLayerRef.value); - overlayLayerRef.value?.parentNode.removeChild(overlayLayerRef.value); - dialogWrapperRef.value?.parentNode.removeChild(dialogWrapperRef.value); + removeElm(highlightLayerRef.value); + removeElm(overlayLayerRef.value); + removeElm(dialogWrapperRef.value); + removeClass(document.body, LOCK_CLASS.value); }; const handleSkip = (e: MouseEvent) => { actived.value = false; setInnerCurrent(-1, stepsTotal.value - 1, { e }); - destoryGuide(); props.onSkip?.(-1, stepsTotal.value, { e }); }; @@ -166,32 +157,30 @@ export default defineComponent({ const handleFinish = (e: MouseEvent) => { actived.value = false; setInnerCurrent(-1, stepsTotal.value - 1, { e }); - destoryGuide(); props.onFinish?.(-1, stepsTotal.value - 1, { e }); }; - watch(innerCurrent, (val) => { - if (val >= 0 && val < steps.value.length) { + const initGuide = () => { + if (innerCurrent.value >= 0 && innerCurrent.value < steps.value.length) { if (!actived.value) { actived.value = true; addClass(document.body, LOCK_CLASS.value); } showGuide(); + } + }; + + watch(innerCurrent, (val) => { + if (val >= 0 && val < steps.value.length) { + initGuide(); } else { actived.value = false; - removeClass(document.body, LOCK_CLASS.value); - console.info('当前引导的步骤', val); + destoryGuide(); } }); onMounted(() => { - if (innerCurrent.value >= 0 && innerCurrent.value < stepsTotal.value) { - actived.value = true; - showGuide(); - } else { - actived.value = false; - console.info('当前引导的步骤', innerCurrent.value); - } + initGuide(); }); return () => { @@ -245,6 +234,7 @@ export default defineComponent({ const isLast = innerCurrent.value === stepsTotal.value - 1; const isFirst = innerCurrent.value === 0; const buttonSize = mode === 'popup' ? 'small' : 'medium'; + return (
{!hideSkip.value && !isLast && ( @@ -294,6 +284,7 @@ export default defineComponent({ const renderTooltipBody = () => { const title =
{currentStepInfo.value.title}
; const desc =
{currentStepInfo.value.description}
; + return ( <> {title} @@ -319,7 +310,7 @@ export default defineComponent({ ); }; - const renderReferenceLayer = () => { + const renderPopupGuide = () => { const { content } = currentStepInfo.value; let renderBody; if (content) { @@ -347,10 +338,6 @@ export default defineComponent({ ); }; - const renderPopupGuide = () => { - return <>{renderReferenceLayer()}; - }; - const renderDialogGuide = () => { const style = { zIndex: zIndex.value }; const wrapperClasses = [ diff --git a/src/guide/utils/elementInViewport.ts b/src/guide/utils/elementInViewport.ts index 8a0fd47677..b1ff283664 100644 --- a/src/guide/utils/elementInViewport.ts +++ b/src/guide/utils/elementInViewport.ts @@ -1,18 +1,9 @@ /** - * Check to see if the element is in the viewport or not + * 检查元素是否在视图 * http://stackoverflow.com/questions/123999/how-to-tell-if-a-dom-element-is-visible-in-the-current-viewport - * - * @api private - * @method _elementInViewport - * @param {Object} el */ -export default function elementInViewport(el) { - const rect = el.getBoundingClientRect(); +export default function elementInViewport(elm: HTMLElement) { + const rect = elm.getBoundingClientRect(); - return ( - rect.top >= 0 && - rect.left >= 0 && - rect.bottom + 80 <= window.innerHeight && // add 80 to get the text right - rect.right <= window.innerWidth - ); + return rect.top >= 0 && rect.left >= 0 && rect.bottom + 80 <= window.innerHeight && rect.right <= window.innerWidth; } diff --git a/src/guide/utils/getElmCssPropValue.ts b/src/guide/utils/getElmCssPropValue.ts new file mode 100644 index 0000000000..b36a197a65 --- /dev/null +++ b/src/guide/utils/getElmCssPropValue.ts @@ -0,0 +1,13 @@ +export default function getElmCssPropValue(element: HTMLElement, propName: string) { + let propValue = ''; + + if (document.defaultView && document.defaultView.getComputedStyle) { + propValue = document.defaultView.getComputedStyle(element, null).getPropertyValue(propName); + } + + if (propValue && propValue.toLowerCase) { + return propValue.toLowerCase(); + } + + return propValue; +} diff --git a/src/guide/utils/getElmInfo.ts b/src/guide/utils/getElmInfo.ts deleted file mode 100644 index b9c73b4203..0000000000 --- a/src/guide/utils/getElmInfo.ts +++ /dev/null @@ -1,32 +0,0 @@ -/** - * 获取元素在页面中为位置信息 - * @param element: - * @returns {top: number, left: number} - */ -export function elmPosition(element: HTMLElement) { - const { body } = document; - const docEl = document.documentElement; - const scrollTop = window.pageYOffset || docEl.scrollTop || body.scrollTop; - const scrollLeft = window.pageXOffset || docEl.scrollLeft || body.scrollLeft; - - const x = element.getBoundingClientRect(); - - return { - top: x.top + scrollTop - element.clientTop, - left: x.left + scrollLeft - element.clientLeft, - }; -} - -/** - * 获取元素本身的宽高 - * @param element - * @returns - */ -export function elmRect(element: HTMLElement) { - const x = element.getBoundingClientRect(); - - return { - width: x.width, - height: x.height, - }; -} diff --git a/src/guide/utils/getOffset.ts b/src/guide/utils/getOffset.ts deleted file mode 100644 index 60ef0311a2..0000000000 --- a/src/guide/utils/getOffset.ts +++ /dev/null @@ -1,52 +0,0 @@ -import getPropValue from './getPropValue'; -import isFixed from './isFixed'; - -/** - * Get an element position on the page relative to another element (or body) - * Thanks to `meouw`: http://stackoverflow.com/a/442474/375966 - * - * @api private - * @method getOffset - * @param {Object} element - * @param {Object} relativeEl - * @returns Element's position info - */ -export default function getOffset(element: any, relativeEl: any) { - const { body } = document; - const docEl = document.documentElement; - const scrollTop = window.pageYOffset || docEl.scrollTop || body.scrollTop; - const scrollLeft = window.pageXOffset || docEl.scrollLeft || body.scrollLeft; - - relativeEl = relativeEl || body; - - const x = element.getBoundingClientRect(); - const xr = relativeEl.getBoundingClientRect(); - const relativeElPosition = getPropValue(relativeEl, 'position'); - - const obj = { - width: x.width, - height: x.height, - }; - - if ( - (relativeEl.tagName.toLowerCase() !== 'body' && relativeElPosition === 'relative') || - relativeElPosition === 'sticky' - ) { - // when the container of our target element is _not_ body and has either "relative" or "sticky" position, we should not - // consider the scroll position but we need to include the relative x/y of the container element - return Object.assign(obj, { - top: x.top - xr.top, - left: x.left - xr.left, - }); - } - if (isFixed(element)) { - return Object.assign(obj, { - top: x.top, - left: x.left, - }); - } - return Object.assign(obj, { - top: x.top + scrollTop, - left: x.left + scrollLeft, - }); -} diff --git a/src/guide/utils/getPropValue.ts b/src/guide/utils/getPropValue.ts deleted file mode 100644 index d2840c6e46..0000000000 --- a/src/guide/utils/getPropValue.ts +++ /dev/null @@ -1,25 +0,0 @@ -/** - * Get an element CSS property on the page - * - * @api private - * @method _getPropValue - * @param {Object} element - * @param {String} propName - * @returns string property value - */ -export default function getPropValue(element: any, propName: any) { - let propValue = ''; - if (element.currentStyle) { - // IE - propValue = element.currentStyle[propName]; - } else if (document.defaultView && document.defaultView.getComputedStyle) { - // Others - propValue = document.defaultView.getComputedStyle(element, null).getPropertyValue(propName); - } - - // Prevent exception in IE - if (propValue && propValue.toLowerCase) { - return propValue.toLowerCase(); - } - return propValue; -} diff --git a/src/guide/utils/getRelativePositon.ts b/src/guide/utils/getRelativePositon.ts new file mode 100644 index 0000000000..1fa911351a --- /dev/null +++ b/src/guide/utils/getRelativePositon.ts @@ -0,0 +1,36 @@ +import getElmCssPropValue from './getElmCssPropValue'; +import isFixed from './isFixed'; +import getWindowScroll from './getWindowScroll'; + +/** + * 获取元素相对于另一个元素的位置(或者说相对于 body) + * 感谢 `meouw`: http://stackoverflow.com/a/442474/375966 + */ +export default function getRelativePositon(elm: HTMLElement, relativeElm: HTMLElement = document.body) { + const { scrollTop, scrollLeft } = getWindowScroll(); + const { top: elmTop, left: elmLeft } = elm.getBoundingClientRect(); + const { top: relElmTop, left: relElmLeft } = relativeElm.getBoundingClientRect(); + const relativeElmPosition = getElmCssPropValue(relativeElm, 'position'); + + if ( + (relativeElm.tagName.toLowerCase() !== 'body' && relativeElmPosition === 'relative') || + relativeElmPosition === 'sticky' + ) { + return { + top: elmTop - relElmTop, + left: elmLeft - relElmLeft, + }; + } + + if (isFixed(elm)) { + return { + top: elmTop, + left: elmLeft, + }; + } + + return { + top: elmTop + scrollTop, + left: elmLeft + scrollLeft, + }; +} diff --git a/src/guide/utils/getScrollParent.ts b/src/guide/utils/getScrollParent.ts index 8af95f7dcf..bf54f86ada 100644 --- a/src/guide/utils/getScrollParent.ts +++ b/src/guide/utils/getScrollParent.ts @@ -1,9 +1,4 @@ -/** - * Find the nearest scrollable parent - * @param Element element - * @return Element - */ -export default function getScrollParent(element: any) { +export function getScrollParent(element: HTMLElement) { let style = window.getComputedStyle(element); const excludeStaticParent = style.position === 'absolute'; const overflowRegex = /(auto|scroll)/; @@ -21,3 +16,9 @@ export default function getScrollParent(element: any) { return document.body; } + +export function scrollToParentVisibleArea(element: HTMLElement) { + const parent = getScrollParent(element); + if (parent === document.body) return; + parent.scrollTop = element.offsetTop - parent.offsetTop; +} diff --git a/src/guide/utils/getTargetElm.ts b/src/guide/utils/getTargetElm.ts index 89371422cf..faea347f74 100644 --- a/src/guide/utils/getTargetElm.ts +++ b/src/guide/utils/getTargetElm.ts @@ -1,10 +1,19 @@ -export default function getTargetElm(targetElm: unknown): HTMLElement { - if (typeof targetElm === 'string') { - const targetElement = document.querySelector(targetElm); +type fn = () => HTMLElement; + +export default function getTargetElm(elm: string | fn): HTMLElement { + if (elm) { + let targetElement: HTMLElement = null; + if (typeof elm === 'string') { + targetElement = document.querySelector(elm); + } else if (typeof elm === 'function') { + targetElement = elm(); + } else { + throw new Error('elm should be string or function'); + } if (targetElement) { return targetElement as HTMLElement; } - throw new Error('There is no element with given selector.'); + throw new Error('There is no element with given.'); } else { return document.body; } diff --git a/src/guide/utils/getWindowScroll.ts b/src/guide/utils/getWindowScroll.ts new file mode 100644 index 0000000000..a4c7dc6d39 --- /dev/null +++ b/src/guide/utils/getWindowScroll.ts @@ -0,0 +1,8 @@ +export default function getWindowScroll() { + const { body } = document; + const docElm = document.documentElement; + const scrollTop = window.pageYOffset || docElm.scrollTop || body.scrollTop; + const scrollLeft = window.pageXOffset || docElm.scrollLeft || body.scrollLeft; + + return { scrollTop, scrollLeft }; +} diff --git a/src/guide/utils/getWindowSize.ts b/src/guide/utils/getWindowSize.ts index fb757c4ef9..b767decbc8 100644 --- a/src/guide/utils/getWindowSize.ts +++ b/src/guide/utils/getWindowSize.ts @@ -1,15 +1,7 @@ -/** - * Provides a cross-browser way to get the screen dimensions - * via: http://stackoverflow.com/questions/5864467/internet-explorer-innerheight - * - * @api private - * @method _getWinSize - * @returns {Object} width and height attributes - */ export default function getWinSize() { if (window.innerWidth !== undefined) { return { width: window.innerWidth, height: window.innerHeight }; } - const D = document.documentElement; - return { width: D.clientWidth, height: D.clientHeight }; + const doc = document.documentElement; + return { width: doc.clientWidth, height: doc.clientHeight }; } diff --git a/src/guide/utils/index.ts b/src/guide/utils/index.ts index e69de29bb2..01cab735e4 100644 --- a/src/guide/utils/index.ts +++ b/src/guide/utils/index.ts @@ -0,0 +1,19 @@ +import setStyle from './setStyle'; +import { scrollToParentVisibleArea } from './getScrollParent'; +import getRelativePositon from './getRelativePositon'; +import getTargetElm from './getTargetElm'; +import scrollTo from './scrollTo'; +import isFixed from './isFixed'; +import getWindowScroll from './getWindowScroll'; +import removeElm from './removeElm'; + +export { + setStyle, + scrollToParentVisibleArea, + getRelativePositon, + getTargetElm, + scrollTo, + isFixed, + getWindowScroll, + removeElm, +}; diff --git a/src/guide/utils/isFixed.ts b/src/guide/utils/isFixed.ts index 99f0f9317c..9e54d954b9 100644 --- a/src/guide/utils/isFixed.ts +++ b/src/guide/utils/isFixed.ts @@ -1,21 +1,13 @@ -import getPropValue from './getPropValue'; +import getElmCssPropValue from './getElmCssPropValue'; -/** - * Checks to see if target element (or parents) position is fixed or not - * - * @api private - * @method _isFixed - * @param {Object} element - * @returns Boolean - */ -export default function isFixed(element: any): Boolean { - const p = element.parentNode; +export default function isFixed(element: HTMLElement): Boolean { + const p = element.parentNode as HTMLElement; if (!p || p.nodeName === 'HTML') { return false; } - if (getPropValue(element, 'position') === 'fixed') { + if (getElmCssPropValue(element, 'position') === 'fixed') { return true; } diff --git a/src/guide/utils/removeElm.ts b/src/guide/utils/removeElm.ts new file mode 100644 index 0000000000..76a6d24ef6 --- /dev/null +++ b/src/guide/utils/removeElm.ts @@ -0,0 +1,3 @@ +export default function removeElm(elm: HTMLElement) { + elm?.parentNode.removeChild(elm); +} diff --git a/src/guide/utils/scrollTo.ts b/src/guide/utils/scrollTo.ts index 65f0c825ef..13117e55cb 100644 --- a/src/guide/utils/scrollTo.ts +++ b/src/guide/utils/scrollTo.ts @@ -1,16 +1,10 @@ import getWindowSize from './getWindowSize'; import elementInViewport from './elementInViewport'; -/** - * To change the scroll of `window` after highlighting an element - * - * @api private - * @param {Object} element - */ -export default function scrollTo(element: HTMLElement) { - const rect = element.getBoundingClientRect(); +export default function scrollTo(elm: HTMLElement) { + const rect = elm.getBoundingClientRect(); - if (!elementInViewport(element)) { + if (!elementInViewport(elm)) { const winHeight = getWindowSize().height; const top = rect.bottom - (rect.bottom - rect.top); window.scrollBy(0, rect.top - (winHeight / 2 - rect.height / 2)); diff --git a/src/guide/utils/setStyle.ts b/src/guide/utils/setStyle.ts index c68a4aaf02..2c7d981ea2 100644 --- a/src/guide/utils/setStyle.ts +++ b/src/guide/utils/setStyle.ts @@ -1,11 +1,4 @@ -/** - * Sets the style of an DOM element - * - * @param {Object} element - * @param {Object|string} style - * @return null - */ -export default function setStyle(element: any, style: any) { +export default function setStyle(element: HTMLElement, style: Record | string) { let cssText = ''; if (element.style.cssText) { From 1ab5bd706433326f9ae362ab344b30c8dfb0dcde Mon Sep 17 00:00:00 2001 From: zhangpaopao Date: Mon, 29 Aug 2022 20:54:19 +0800 Subject: [PATCH 20/48] feat(guide): add custom-popup demo --- examples/guide/demos/custom-popup.vue | 142 ++++++++++++++++++++++++++ examples/guide/demos/my-popup.vue | 45 ++++++++ src/_common | 2 +- 3 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 examples/guide/demos/custom-popup.vue create mode 100644 examples/guide/demos/my-popup.vue diff --git a/examples/guide/demos/custom-popup.vue b/examples/guide/demos/custom-popup.vue new file mode 100644 index 0000000000..25bd289af6 --- /dev/null +++ b/examples/guide/demos/custom-popup.vue @@ -0,0 +1,142 @@ + + + + + diff --git a/examples/guide/demos/my-popup.vue b/examples/guide/demos/my-popup.vue new file mode 100644 index 0000000000..625297959d --- /dev/null +++ b/examples/guide/demos/my-popup.vue @@ -0,0 +1,45 @@ + + + + + diff --git a/src/_common b/src/_common index 51ab9fd5c3..63f39ebeb0 160000 --- a/src/_common +++ b/src/_common @@ -1 +1 @@ -Subproject commit 51ab9fd5c3d9ae79c13d051e1e67d87ede35869f +Subproject commit 63f39ebeb09bbc87ce3e689bd92b42c71ef22a68 From 9424f73e7bdf768da7f1259576b5fc471b230cf0 Mon Sep 17 00:00:00 2001 From: zhangpaopao Date: Mon, 29 Aug 2022 21:28:52 +0800 Subject: [PATCH 21/48] chore(guide): type --- src/guide/utils/getTargetElm.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/guide/utils/getTargetElm.ts b/src/guide/utils/getTargetElm.ts index faea347f74..cd7379892a 100644 --- a/src/guide/utils/getTargetElm.ts +++ b/src/guide/utils/getTargetElm.ts @@ -1,12 +1,12 @@ -type fn = () => HTMLElement; +import { AttachNode } from '../../common'; -export default function getTargetElm(elm: string | fn): HTMLElement { +export default function getTargetElm(elm: AttachNode): HTMLElement { if (elm) { let targetElement: HTMLElement = null; if (typeof elm === 'string') { targetElement = document.querySelector(elm); } else if (typeof elm === 'function') { - targetElement = elm(); + targetElement = elm() as HTMLElement; } else { throw new Error('elm should be string or function'); } From f8035db2fe2ec6e290543701a064c8700ff0f152 Mon Sep 17 00:00:00 2001 From: zhangpaopao Date: Tue, 30 Aug 2022 13:05:10 +0800 Subject: [PATCH 22/48] feat(guide): refact content and highlighContent --- examples/guide/demos/custom-popup.vue | 18 ++++++++--------- examples/guide/demos/guide.vue | 2 +- examples/guide/demos/my-popup.vue | 19 ++++++++++++++---- src/_common | 2 +- src/guide/guide-step.tsx | 28 --------------------------- src/guide/guide.tsx | 18 ++++++++++++----- 6 files changed, 39 insertions(+), 48 deletions(-) delete mode 100644 src/guide/guide-step.tsx diff --git a/examples/guide/demos/custom-popup.vue b/examples/guide/demos/custom-popup.vue index 25bd289af6..5bfa163b98 100644 --- a/examples/guide/demos/custom-popup.vue +++ b/examples/guide/demos/custom-popup.vue @@ -10,11 +10,11 @@
Guide 用户引导
按钮用于开启一个闭环的操作任务,如“删除”对象、“购买”商品等。
-
+
Label
-
+
Label
@@ -50,21 +50,21 @@ const steps = [ title: '新手引导标题', description: '新手引导的说明文案', placement: 'bottom-right', - content: (h) => h(MyPopup), + content: MyPopup, }, { - element: '.label-field', + element: '.label-field-1', title: '新手引导标题', description: '新手引导的说明文案', placement: 'bottom', - content: (h) => h(MyPopup), + content: MyPopup, }, { - element: '.action', + element: '.label-field-2', title: '新手引导标题', - description: '新手引导的说明方案', - placement: 'right', - content: (h) => h(MyPopup), + description: '新手引导的说明文案', + placement: 'bottom-left', + content: MyPopup, }, ]; diff --git a/examples/guide/demos/guide.vue b/examples/guide/demos/guide.vue index b59cc0ae3e..e54171242d 100644 --- a/examples/guide/demos/guide.vue +++ b/examples/guide/demos/guide.vue @@ -38,7 +38,7 @@ const steps = [ title: '新手引导标题', description: '新手引导的说明文案。', prevButtonProps: { content: '自定义上一步', theme: 'success' }, - highlightContent: (h) => h(HighlightContent), + highlightContent: HighlightContent, }, { element: '#guide3', diff --git a/examples/guide/demos/my-popup.vue b/examples/guide/demos/my-popup.vue index 625297959d..ed55514664 100644 --- a/examples/guide/demos/my-popup.vue +++ b/examples/guide/demos/my-popup.vue @@ -3,14 +3,25 @@
diff --git a/src/_common b/src/_common index 63f39ebeb0..eb16719d77 160000 --- a/src/_common +++ b/src/_common @@ -1 +1 @@ -Subproject commit 63f39ebeb09bbc87ce3e689bd92b42c71ef22a68 +Subproject commit eb16719d7799764fc3d98880f4409adfc7c1e203 diff --git a/src/guide/guide-step.tsx b/src/guide/guide-step.tsx deleted file mode 100644 index de63633aca..0000000000 --- a/src/guide/guide-step.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { defineComponent, onMounted, onUnmounted, ref, Fragment, Text, watch, PropType, VNode, Teleport } from 'vue'; -import { useContent, useTNodeJSX } from '../hooks/tnode'; -import { TdGuideStepProps } from './type'; - -export const GuidePopupStepContent = defineComponent({ - props: { content: [String, Function] as PropType }, - setup() { - const renderTNodeJSX = useTNodeJSX(); - const el = ref(null); - - onMounted(() => { - const { parentNode } = el.value; - parentNode.parentNode.appendChild(el.value); - parentNode.parentNode.removeChild(parentNode); - }); - - return () =>
{renderTNodeJSX('content', 'default')}
; - }, -}); - -export const GuideStepHighlightContent = defineComponent({ - props: { highlightContent: [String, Function] as PropType }, - setup() { - const renderTNodeJSX = useTNodeJSX(); - - return () => renderTNodeJSX('highlightContent', 'default'); - }, -}); diff --git a/src/guide/guide.tsx b/src/guide/guide.tsx index 0499f4e1cd..cee8f903c9 100644 --- a/src/guide/guide.tsx +++ b/src/guide/guide.tsx @@ -25,8 +25,6 @@ import { usePrefixClass } from '../hooks/useConfig'; import Button from '../button'; import Popup from '../popup'; -import { GuidePopupStepContent, GuideStepHighlightContent } from './guide-step'; - export default defineComponent({ name: 'TGuide', directives: { TransferDom }, @@ -178,6 +176,7 @@ export default defineComponent({ destoryGuide(); } }); + const aa = ref(); onMounted(() => { initGuide(); @@ -206,7 +205,7 @@ export default defineComponent({ return highlightContent ? (
- +
) : (
@@ -314,7 +313,15 @@ export default defineComponent({ const { content } = currentStepInfo.value; let renderBody; if (content) { - renderBody = () => ; + const contentProps = { + handlePrev, + handleNext, + handleSkip, + handleFinish, + current: innerCurrent.value, + total: stepsTotal.value, + }; + renderBody = () => ; } else { renderBody = renderPopupContent; } @@ -330,7 +337,8 @@ export default defineComponent({ v-slots={{ content: renderBody }} show-arrow={!content} zIndex={zIndex.value} - overlayInnerClassName={currentStepInfo.value.stepOverlayClass} + overlayClassName={currentStepInfo.value.stepOverlayClass} + overlayInnerClassName={{ [`${COMPONENT_NAME.value}__popup--content`]: !!content }} placement={currentStepInfo.value.placement} >
From 8741ea474564e51c2fd6370faf9a5c434ea0a368 Mon Sep 17 00:00:00 2001 From: zhangpaopao Date: Tue, 30 Aug 2022 13:13:01 +0800 Subject: [PATCH 23/48] fix(guide): destory dialog wrapper --- src/guide/guide.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/guide/guide.tsx b/src/guide/guide.tsx index cee8f903c9..6f777119f7 100644 --- a/src/guide/guide.tsx +++ b/src/guide/guide.tsx @@ -115,6 +115,7 @@ export default defineComponent({ const destoryDialogTooltipElm = () => { removeElm(dialogTooltipRef.value); + removeElm(dialogWrapperRef.value); }; const showGuide = () => { @@ -132,7 +133,6 @@ export default defineComponent({ destoryDialogTooltipElm(); removeElm(highlightLayerRef.value); removeElm(overlayLayerRef.value); - removeElm(dialogWrapperRef.value); removeClass(document.body, LOCK_CLASS.value); }; @@ -176,7 +176,6 @@ export default defineComponent({ destoryGuide(); } }); - const aa = ref(); onMounted(() => { initGuide(); From 58e17585a70d9f1de368b6246a2ddcd682f02f5a Mon Sep 17 00:00:00 2001 From: zhangpaopao Date: Tue, 30 Aug 2022 13:18:32 +0800 Subject: [PATCH 24/48] docs(guide): use justify center --- examples/guide/demos/base.vue | 2 +- examples/guide/demos/custom-popup.vue | 2 +- examples/guide/demos/dialog.vue | 2 +- examples/guide/demos/no-mask.vue | 2 +- examples/guide/demos/popup-dialog.vue | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/guide/demos/base.vue b/examples/guide/demos/base.vue index 0aa0ee0ad1..49f11ff8c3 100644 --- a/examples/guide/demos/base.vue +++ b/examples/guide/demos/base.vue @@ -1,5 +1,5 @@