Skip to content

Commit e5787c2

Browse files
committed
refactor: tour #6332
1 parent 698c0ff commit e5787c2

26 files changed

+209
-325
lines changed

components/_util/PortalWrapper.tsx

+32-50
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
11
import PropTypes from './vue-types';
2-
import switchScrollingEffect from './switchScrollingEffect';
3-
import setStyle from './setStyle';
42
import Portal from './Portal';
53
import {
64
defineComponent,
@@ -11,10 +9,12 @@ import {
119
onUpdated,
1210
getCurrentInstance,
1311
nextTick,
12+
computed,
1413
} from 'vue';
1514
import canUseDom from './canUseDom';
16-
import ScrollLocker from '../vc-util/Dom/scrollLocker';
1715
import raf from './raf';
16+
import { booleanType } from './type';
17+
import useScrollLocker from './hooks/useScrollLocker';
1818

1919
let openCount = 0;
2020
const supportDom = canUseDom();
@@ -24,10 +24,6 @@ export function getOpenCount() {
2424
return process.env.NODE_ENV === 'test' ? openCount : 0;
2525
}
2626

27-
// https://github.com/ant-design/ant-design/issues/19340
28-
// https://github.com/ant-design/ant-design/issues/19332
29-
let cacheOverflow = {};
30-
3127
const getParent = (getContainer: GetContainer) => {
3228
if (!supportDom) {
3329
return null;
@@ -57,20 +53,20 @@ export default defineComponent({
5753
forceRender: { type: Boolean, default: undefined },
5854
getContainer: PropTypes.any,
5955
visible: { type: Boolean, default: undefined },
56+
autoLock: booleanType(),
57+
didUpdate: Function,
6058
},
6159

6260
setup(props, { slots }) {
6361
const container = shallowRef<HTMLElement>();
6462
const componentRef = shallowRef();
6563
const rafId = shallowRef<number>();
66-
const scrollLocker = new ScrollLocker({
67-
container: getParent(props.getContainer) as HTMLElement,
68-
});
6964

7065
const removeCurrentContainer = () => {
7166
// Portal will remove from `parentNode`.
7267
// Let's handle this again to avoid refactor issue.
7368
container.value?.parentNode?.removeChild(container.value);
69+
container.value = null;
7470
};
7571
const attachToParent = (force = false) => {
7672
if (force || (container.value && !container.value.parentNode)) {
@@ -86,13 +82,13 @@ export default defineComponent({
8682
return true;
8783
};
8884
// attachToParent();
89-
85+
const defaultContainer = document.createElement('div');
9086
const getContainer = () => {
9187
if (!supportDom) {
9288
return null;
9389
}
9490
if (!container.value) {
95-
container.value = document.createElement('div');
91+
container.value = defaultContainer;
9692
attachToParent(true);
9793
}
9894
setWrapperClassName();
@@ -108,30 +104,19 @@ export default defineComponent({
108104
setWrapperClassName();
109105
attachToParent();
110106
});
111-
/**
112-
* Enhance ./switchScrollingEffect
113-
* 1. Simulate document body scroll bar with
114-
* 2. Record body has overflow style and recover when all of PortalWrapper invisible
115-
* 3. Disable body scroll when PortalWrapper has open
116-
*
117-
* @memberof PortalWrapper
118-
*/
119-
const switchScrolling = () => {
120-
if (openCount === 1 && !Object.keys(cacheOverflow).length) {
121-
switchScrollingEffect();
122-
// Must be set after switchScrollingEffect
123-
cacheOverflow = setStyle({
124-
overflow: 'hidden',
125-
overflowX: 'hidden',
126-
overflowY: 'hidden',
127-
});
128-
} else if (!openCount) {
129-
setStyle(cacheOverflow);
130-
cacheOverflow = {};
131-
switchScrollingEffect(true);
132-
}
133-
};
107+
134108
const instance = getCurrentInstance();
109+
110+
useScrollLocker(
111+
computed(() => {
112+
return (
113+
props.autoLock &&
114+
props.visible &&
115+
canUseDom() &&
116+
(container.value === document.body || container.value === defaultContainer)
117+
);
118+
}),
119+
);
135120
onMounted(() => {
136121
let init = false;
137122
watch(
@@ -157,17 +142,6 @@ export default defineComponent({
157142
) {
158143
removeCurrentContainer();
159144
}
160-
// updateScrollLocker
161-
if (
162-
visible &&
163-
visible !== prevVisible &&
164-
supportDom &&
165-
getParent(getContainer) !== scrollLocker.getContainer()
166-
) {
167-
scrollLocker.reLock({
168-
container: getParent(getContainer) as HTMLElement,
169-
});
170-
}
171145
}
172146
init = true;
173147
},
@@ -192,22 +166,30 @@ export default defineComponent({
192166
removeCurrentContainer();
193167
raf.cancel(rafId.value);
194168
});
195-
169+
watch(
170+
[() => props.visible, () => props.forceRender],
171+
() => {
172+
const { forceRender, visible } = props;
173+
if (visible === false && !forceRender) {
174+
removeCurrentContainer();
175+
}
176+
},
177+
{ flush: 'post' },
178+
);
196179
return () => {
197180
const { forceRender, visible } = props;
198181
let portal = null;
199182
const childProps = {
200183
getOpenCount: () => openCount,
201184
getContainer,
202-
switchScrollingEffect: switchScrolling,
203-
scrollLocker,
204185
};
205-
186+
if (visible === false && !forceRender) return null;
206187
if (forceRender || visible || componentRef.value) {
207188
portal = (
208189
<Portal
209190
getContainer={getContainer}
210191
ref={componentRef}
192+
didUpdate={props.didUpdate}
211193
v-slots={{ default: () => slots.default?.(childProps) }}
212194
></Portal>
213195
);

components/vc-tour/hooks/useScrollLocker.tsx renamed to components/_util/hooks/useScrollLocker.ts

+20-16
Original file line numberDiff line numberDiff line change
@@ -19,26 +19,30 @@ export function isBodyOverflowing() {
1919

2020
export default function useScrollLocker(lock?: Ref<boolean>) {
2121
const mergedLock = computed(() => !!lock && !!lock.value);
22-
const id = computed(() => {
23-
uuid += 1;
24-
return `${UNIQUE_ID}_${uuid}`;
25-
});
22+
uuid += 1;
23+
const id = `${UNIQUE_ID}_${uuid}`;
2624

27-
watchEffect(() => {
28-
if (mergedLock.value) {
29-
const scrollbarSize = getScrollBarSize();
30-
const isOverflow = isBodyOverflowing();
25+
watchEffect(
26+
onClear => {
27+
if (mergedLock.value) {
28+
const scrollbarSize = getScrollBarSize();
29+
const isOverflow = isBodyOverflowing();
3130

32-
updateCSS(
33-
`
31+
updateCSS(
32+
`
3433
html body {
3534
overflow-y: hidden;
3635
${isOverflow ? `width: calc(100% - ${scrollbarSize}px);` : ''}
3736
}`,
38-
id.value,
39-
);
40-
} else {
41-
removeCSS(id.value);
42-
}
43-
});
37+
id,
38+
);
39+
} else {
40+
removeCSS(id);
41+
}
42+
onClear(() => {
43+
removeCSS(id);
44+
});
45+
},
46+
{ flush: 'post' },
47+
);
4448
}

components/_util/switchScrollingEffect.ts

-42
This file was deleted.

components/tour/demo/basic.vue

+2-2
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ The most basic usage.
2828
<a-button ref="ref3"><EllipsisOutlined /></a-button>
2929
</a-space>
3030

31-
<a-tour :open="open" :steps="steps" @close="handleOpen(false)" />
31+
<a-tour v-model:current="current" :open="open" :steps="steps" @close="handleOpen(false)" />
3232
</template>
3333

3434
<script lang="ts" setup>
@@ -41,7 +41,7 @@ const open = ref<boolean>(false);
4141
const ref1 = ref(null);
4242
const ref2 = ref(null);
4343
const ref3 = ref(null);
44-
44+
const current = ref(0);
4545
const steps: TourProps['steps'] = [
4646
{
4747
title: 'Upload File',

components/tour/index.en-US.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ Use when you want to guide users through a product.
2323
| mask | Whether to enable masking, change mask style and fill color by pass custom props | `boolean` \| `{ style?: CSSProperties; color?: string; }` | `true` | |
2424
| type | Type, affects the background color and text color | `default` `primary` | `default` | |
2525
| open | Open tour | `boolean` | - | |
26-
| current | What is the current step | `number` | - | |
26+
| current(v-model) | What is the current step | `number` | - | |
2727
| scrollIntoViewOptions | support pass custom scrollIntoView options | `boolean` \| `ScrollIntoViewOptions` | `true` | |
2828
| indicatorsRender | custom indicator | `v-slot:indicatorsRender="{current, total}"` | - | |
2929
| zIndex | Tour's zIndex | `number` | `1001` | |

components/tour/index.tsx

+16-6
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { defineComponent, toRefs } from 'vue';
1+
import { computed, defineComponent, toRefs } from 'vue';
22
import VCTour from '../vc-tour';
33
import classNames from '../_util/classNames';
44
import TourPanel from './panelRender';
@@ -11,26 +11,27 @@ import useMergedType from './useMergedType';
1111

1212
// CSSINJS
1313
import useStyle from './style';
14+
import getPlacements from '../_util/placements';
1415

1516
export { TourProps, TourStepProps };
1617

1718
const Tour = defineComponent({
1819
name: 'ATour',
20+
inheritAttrs: false,
1921
props: tourProps(),
2022
setup(props, { attrs, emit, slots }) {
21-
const { current } = toRefs(props);
23+
const { current, type, steps, defaultCurrent } = toRefs(props);
2224
const { prefixCls, direction } = useConfigInject('tour', props);
2325

2426
// style
2527
const [wrapSSR, hashId] = useStyle(prefixCls);
2628

2729
const { currentMergedType, updateInnerCurrent } = useMergedType({
28-
defaultType: props.type,
29-
steps: props.steps,
30+
defaultType: type,
31+
steps,
3032
current,
31-
defaultCurrent: props.defaultCurrent,
33+
defaultCurrent,
3234
});
33-
3435
return () => {
3536
const { steps, current, type, rootClassName, ...restProps } = props;
3637

@@ -58,9 +59,17 @@ const Tour = defineComponent({
5859

5960
const onStepChange = (stepCurrent: number) => {
6061
updateInnerCurrent(stepCurrent);
62+
emit('update:current', stepCurrent);
6163
emit('change', stepCurrent);
6264
};
6365

66+
const builtinPlacements = computed(() =>
67+
getPlacements({
68+
arrowPointAtCenter: true,
69+
autoAdjustOverflow: true,
70+
}),
71+
);
72+
6473
return wrapSSR(
6574
<VCTour
6675
{...attrs}
@@ -73,6 +82,7 @@ const Tour = defineComponent({
7382
renderPanel={mergedRenderPanel}
7483
onChange={onStepChange}
7584
steps={steps}
85+
builtinPlacements={builtinPlacements.value as any}
7686
/>,
7787
);
7888
};

components/tour/index.zh-CN.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*nF6hQpM0XtEAAA
2424
| mask | 是否启用蒙层,也可传入配置改变蒙层样式和填充色 | `boolean` \| `{ style?: CSSProperties; color?: string; }` | `true` | |
2525
| type | 类型,影响底色与文字颜色 | `default` \| `primary` | `default` | |
2626
| open | 打开引导 | `boolean` | - | |
27-
| current | 当前处于哪一步 | `number` | - | |
27+
| current(v-model) | 当前处于哪一步 | `number` | - | |
2828
| scrollIntoViewOptions | 是否支持当前元素滚动到视窗内,也可传入配置指定滚动视窗的相关参数 | `boolean` \| `ScrollIntoViewOptions` | `true` | |
2929
| indicatorsRender | 自定义指示器 | `v-slot:indicatorsRender="{current, total}"` | - | |
3030
| zIndex | Tour 的层级 | `number` | `1001` | |

components/tour/interface.ts

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export const tourProps = () => ({
88
prefixCls: { type: String },
99
current: { type: Number },
1010
type: { type: String as PropType<'default' | 'primary'> }, // default 类型,影响底色与文字颜色
11+
'onUpdate:current': Function as PropType<(val: number) => void>,
1112
});
1213

1314
export type TourProps = Partial<ExtractPropTypes<ReturnType<typeof tourProps>>>;

0 commit comments

Comments
 (0)