refactor: tour #6332
parent
698c0ff3b4
commit
e5787c2ed2
|
@ -1,6 +1,4 @@
|
||||||
import PropTypes from './vue-types';
|
import PropTypes from './vue-types';
|
||||||
import switchScrollingEffect from './switchScrollingEffect';
|
|
||||||
import setStyle from './setStyle';
|
|
||||||
import Portal from './Portal';
|
import Portal from './Portal';
|
||||||
import {
|
import {
|
||||||
defineComponent,
|
defineComponent,
|
||||||
|
@ -11,10 +9,12 @@ import {
|
||||||
onUpdated,
|
onUpdated,
|
||||||
getCurrentInstance,
|
getCurrentInstance,
|
||||||
nextTick,
|
nextTick,
|
||||||
|
computed,
|
||||||
} from 'vue';
|
} from 'vue';
|
||||||
import canUseDom from './canUseDom';
|
import canUseDom from './canUseDom';
|
||||||
import ScrollLocker from '../vc-util/Dom/scrollLocker';
|
|
||||||
import raf from './raf';
|
import raf from './raf';
|
||||||
|
import { booleanType } from './type';
|
||||||
|
import useScrollLocker from './hooks/useScrollLocker';
|
||||||
|
|
||||||
let openCount = 0;
|
let openCount = 0;
|
||||||
const supportDom = canUseDom();
|
const supportDom = canUseDom();
|
||||||
|
@ -24,10 +24,6 @@ export function getOpenCount() {
|
||||||
return process.env.NODE_ENV === 'test' ? openCount : 0;
|
return process.env.NODE_ENV === 'test' ? openCount : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://github.com/ant-design/ant-design/issues/19340
|
|
||||||
// https://github.com/ant-design/ant-design/issues/19332
|
|
||||||
let cacheOverflow = {};
|
|
||||||
|
|
||||||
const getParent = (getContainer: GetContainer) => {
|
const getParent = (getContainer: GetContainer) => {
|
||||||
if (!supportDom) {
|
if (!supportDom) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -57,20 +53,20 @@ export default defineComponent({
|
||||||
forceRender: { type: Boolean, default: undefined },
|
forceRender: { type: Boolean, default: undefined },
|
||||||
getContainer: PropTypes.any,
|
getContainer: PropTypes.any,
|
||||||
visible: { type: Boolean, default: undefined },
|
visible: { type: Boolean, default: undefined },
|
||||||
|
autoLock: booleanType(),
|
||||||
|
didUpdate: Function,
|
||||||
},
|
},
|
||||||
|
|
||||||
setup(props, { slots }) {
|
setup(props, { slots }) {
|
||||||
const container = shallowRef<HTMLElement>();
|
const container = shallowRef<HTMLElement>();
|
||||||
const componentRef = shallowRef();
|
const componentRef = shallowRef();
|
||||||
const rafId = shallowRef<number>();
|
const rafId = shallowRef<number>();
|
||||||
const scrollLocker = new ScrollLocker({
|
|
||||||
container: getParent(props.getContainer) as HTMLElement,
|
|
||||||
});
|
|
||||||
|
|
||||||
const removeCurrentContainer = () => {
|
const removeCurrentContainer = () => {
|
||||||
// Portal will remove from `parentNode`.
|
// Portal will remove from `parentNode`.
|
||||||
// Let's handle this again to avoid refactor issue.
|
// Let's handle this again to avoid refactor issue.
|
||||||
container.value?.parentNode?.removeChild(container.value);
|
container.value?.parentNode?.removeChild(container.value);
|
||||||
|
container.value = null;
|
||||||
};
|
};
|
||||||
const attachToParent = (force = false) => {
|
const attachToParent = (force = false) => {
|
||||||
if (force || (container.value && !container.value.parentNode)) {
|
if (force || (container.value && !container.value.parentNode)) {
|
||||||
|
@ -86,13 +82,13 @@ export default defineComponent({
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
// attachToParent();
|
// attachToParent();
|
||||||
|
const defaultContainer = document.createElement('div');
|
||||||
const getContainer = () => {
|
const getContainer = () => {
|
||||||
if (!supportDom) {
|
if (!supportDom) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (!container.value) {
|
if (!container.value) {
|
||||||
container.value = document.createElement('div');
|
container.value = defaultContainer;
|
||||||
attachToParent(true);
|
attachToParent(true);
|
||||||
}
|
}
|
||||||
setWrapperClassName();
|
setWrapperClassName();
|
||||||
|
@ -108,30 +104,19 @@ export default defineComponent({
|
||||||
setWrapperClassName();
|
setWrapperClassName();
|
||||||
attachToParent();
|
attachToParent();
|
||||||
});
|
});
|
||||||
/**
|
|
||||||
* Enhance ./switchScrollingEffect
|
|
||||||
* 1. Simulate document body scroll bar with
|
|
||||||
* 2. Record body has overflow style and recover when all of PortalWrapper invisible
|
|
||||||
* 3. Disable body scroll when PortalWrapper has open
|
|
||||||
*
|
|
||||||
* @memberof PortalWrapper
|
|
||||||
*/
|
|
||||||
const switchScrolling = () => {
|
|
||||||
if (openCount === 1 && !Object.keys(cacheOverflow).length) {
|
|
||||||
switchScrollingEffect();
|
|
||||||
// Must be set after switchScrollingEffect
|
|
||||||
cacheOverflow = setStyle({
|
|
||||||
overflow: 'hidden',
|
|
||||||
overflowX: 'hidden',
|
|
||||||
overflowY: 'hidden',
|
|
||||||
});
|
|
||||||
} else if (!openCount) {
|
|
||||||
setStyle(cacheOverflow);
|
|
||||||
cacheOverflow = {};
|
|
||||||
switchScrollingEffect(true);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const instance = getCurrentInstance();
|
const instance = getCurrentInstance();
|
||||||
|
|
||||||
|
useScrollLocker(
|
||||||
|
computed(() => {
|
||||||
|
return (
|
||||||
|
props.autoLock &&
|
||||||
|
props.visible &&
|
||||||
|
canUseDom() &&
|
||||||
|
(container.value === document.body || container.value === defaultContainer)
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
);
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
let init = false;
|
let init = false;
|
||||||
watch(
|
watch(
|
||||||
|
@ -157,17 +142,6 @@ export default defineComponent({
|
||||||
) {
|
) {
|
||||||
removeCurrentContainer();
|
removeCurrentContainer();
|
||||||
}
|
}
|
||||||
// updateScrollLocker
|
|
||||||
if (
|
|
||||||
visible &&
|
|
||||||
visible !== prevVisible &&
|
|
||||||
supportDom &&
|
|
||||||
getParent(getContainer) !== scrollLocker.getContainer()
|
|
||||||
) {
|
|
||||||
scrollLocker.reLock({
|
|
||||||
container: getParent(getContainer) as HTMLElement,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
init = true;
|
init = true;
|
||||||
},
|
},
|
||||||
|
@ -192,22 +166,30 @@ export default defineComponent({
|
||||||
removeCurrentContainer();
|
removeCurrentContainer();
|
||||||
raf.cancel(rafId.value);
|
raf.cancel(rafId.value);
|
||||||
});
|
});
|
||||||
|
watch(
|
||||||
|
[() => props.visible, () => props.forceRender],
|
||||||
|
() => {
|
||||||
|
const { forceRender, visible } = props;
|
||||||
|
if (visible === false && !forceRender) {
|
||||||
|
removeCurrentContainer();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ flush: 'post' },
|
||||||
|
);
|
||||||
return () => {
|
return () => {
|
||||||
const { forceRender, visible } = props;
|
const { forceRender, visible } = props;
|
||||||
let portal = null;
|
let portal = null;
|
||||||
const childProps = {
|
const childProps = {
|
||||||
getOpenCount: () => openCount,
|
getOpenCount: () => openCount,
|
||||||
getContainer,
|
getContainer,
|
||||||
switchScrollingEffect: switchScrolling,
|
|
||||||
scrollLocker,
|
|
||||||
};
|
};
|
||||||
|
if (visible === false && !forceRender) return null;
|
||||||
if (forceRender || visible || componentRef.value) {
|
if (forceRender || visible || componentRef.value) {
|
||||||
portal = (
|
portal = (
|
||||||
<Portal
|
<Portal
|
||||||
getContainer={getContainer}
|
getContainer={getContainer}
|
||||||
ref={componentRef}
|
ref={componentRef}
|
||||||
|
didUpdate={props.didUpdate}
|
||||||
v-slots={{ default: () => slots.default?.(childProps) }}
|
v-slots={{ default: () => slots.default?.(childProps) }}
|
||||||
></Portal>
|
></Portal>
|
||||||
);
|
);
|
||||||
|
|
|
@ -19,12 +19,11 @@ export function isBodyOverflowing() {
|
||||||
|
|
||||||
export default function useScrollLocker(lock?: Ref<boolean>) {
|
export default function useScrollLocker(lock?: Ref<boolean>) {
|
||||||
const mergedLock = computed(() => !!lock && !!lock.value);
|
const mergedLock = computed(() => !!lock && !!lock.value);
|
||||||
const id = computed(() => {
|
|
||||||
uuid += 1;
|
uuid += 1;
|
||||||
return `${UNIQUE_ID}_${uuid}`;
|
const id = `${UNIQUE_ID}_${uuid}`;
|
||||||
});
|
|
||||||
|
|
||||||
watchEffect(() => {
|
watchEffect(
|
||||||
|
onClear => {
|
||||||
if (mergedLock.value) {
|
if (mergedLock.value) {
|
||||||
const scrollbarSize = getScrollBarSize();
|
const scrollbarSize = getScrollBarSize();
|
||||||
const isOverflow = isBodyOverflowing();
|
const isOverflow = isBodyOverflowing();
|
||||||
|
@ -35,10 +34,15 @@ html body {
|
||||||
overflow-y: hidden;
|
overflow-y: hidden;
|
||||||
${isOverflow ? `width: calc(100% - ${scrollbarSize}px);` : ''}
|
${isOverflow ? `width: calc(100% - ${scrollbarSize}px);` : ''}
|
||||||
}`,
|
}`,
|
||||||
id.value,
|
id,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
removeCSS(id.value);
|
removeCSS(id);
|
||||||
}
|
}
|
||||||
|
onClear(() => {
|
||||||
|
removeCSS(id);
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
{ flush: 'post' },
|
||||||
|
);
|
||||||
}
|
}
|
|
@ -1,42 +0,0 @@
|
||||||
import getScrollBarSize from './getScrollBarSize';
|
|
||||||
import setStyle from './setStyle';
|
|
||||||
|
|
||||||
function isBodyOverflowing() {
|
|
||||||
return (
|
|
||||||
document.body.scrollHeight > (window.innerHeight || document.documentElement.clientHeight) &&
|
|
||||||
window.innerWidth > document.body.offsetWidth
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let cacheStyle = {};
|
|
||||||
|
|
||||||
export default (close?: boolean) => {
|
|
||||||
if (!isBodyOverflowing() && !close) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://github.com/ant-design/ant-design/issues/19729
|
|
||||||
const scrollingEffectClassName = 'ant-scrolling-effect';
|
|
||||||
const scrollingEffectClassNameReg = new RegExp(`${scrollingEffectClassName}`, 'g');
|
|
||||||
const bodyClassName = document.body.className;
|
|
||||||
|
|
||||||
if (close) {
|
|
||||||
if (!scrollingEffectClassNameReg.test(bodyClassName)) return;
|
|
||||||
setStyle(cacheStyle);
|
|
||||||
cacheStyle = {};
|
|
||||||
document.body.className = bodyClassName.replace(scrollingEffectClassNameReg, '').trim();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const scrollBarSize = getScrollBarSize();
|
|
||||||
if (scrollBarSize) {
|
|
||||||
cacheStyle = setStyle({
|
|
||||||
position: 'relative',
|
|
||||||
width: `calc(100% - ${scrollBarSize}px)`,
|
|
||||||
});
|
|
||||||
if (!scrollingEffectClassNameReg.test(bodyClassName)) {
|
|
||||||
const addClassName = `${bodyClassName} ${scrollingEffectClassName}`;
|
|
||||||
document.body.className = addClassName.trim();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
|
@ -28,7 +28,7 @@ The most basic usage.
|
||||||
<a-button ref="ref3"><EllipsisOutlined /></a-button>
|
<a-button ref="ref3"><EllipsisOutlined /></a-button>
|
||||||
</a-space>
|
</a-space>
|
||||||
|
|
||||||
<a-tour :open="open" :steps="steps" @close="handleOpen(false)" />
|
<a-tour v-model:current="current" :open="open" :steps="steps" @close="handleOpen(false)" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
@ -41,7 +41,7 @@ const open = ref<boolean>(false);
|
||||||
const ref1 = ref(null);
|
const ref1 = ref(null);
|
||||||
const ref2 = ref(null);
|
const ref2 = ref(null);
|
||||||
const ref3 = ref(null);
|
const ref3 = ref(null);
|
||||||
|
const current = ref(0);
|
||||||
const steps: TourProps['steps'] = [
|
const steps: TourProps['steps'] = [
|
||||||
{
|
{
|
||||||
title: 'Upload File',
|
title: 'Upload File',
|
||||||
|
|
|
@ -23,7 +23,7 @@ Use when you want to guide users through a product.
|
||||||
| mask | Whether to enable masking, change mask style and fill color by pass custom props | `boolean` \| `{ style?: CSSProperties; color?: string; }` | `true` | |
|
| mask | Whether to enable masking, change mask style and fill color by pass custom props | `boolean` \| `{ style?: CSSProperties; color?: string; }` | `true` | |
|
||||||
| type | Type, affects the background color and text color | `default` `primary` | `default` | |
|
| type | Type, affects the background color and text color | `default` `primary` | `default` | |
|
||||||
| open | Open tour | `boolean` | - | |
|
| open | Open tour | `boolean` | - | |
|
||||||
| current | What is the current step | `number` | - | |
|
| current(v-model) | What is the current step | `number` | - | |
|
||||||
| scrollIntoViewOptions | support pass custom scrollIntoView options | `boolean` \| `ScrollIntoViewOptions` | `true` | |
|
| scrollIntoViewOptions | support pass custom scrollIntoView options | `boolean` \| `ScrollIntoViewOptions` | `true` | |
|
||||||
| indicatorsRender | custom indicator | `v-slot:indicatorsRender="{current, total}"` | - | |
|
| indicatorsRender | custom indicator | `v-slot:indicatorsRender="{current, total}"` | - | |
|
||||||
| zIndex | Tour's zIndex | `number` | `1001` | |
|
| zIndex | Tour's zIndex | `number` | `1001` | |
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { defineComponent, toRefs } from 'vue';
|
import { computed, defineComponent, toRefs } from 'vue';
|
||||||
import VCTour from '../vc-tour';
|
import VCTour from '../vc-tour';
|
||||||
import classNames from '../_util/classNames';
|
import classNames from '../_util/classNames';
|
||||||
import TourPanel from './panelRender';
|
import TourPanel from './panelRender';
|
||||||
|
@ -11,26 +11,27 @@ import useMergedType from './useMergedType';
|
||||||
|
|
||||||
// CSSINJS
|
// CSSINJS
|
||||||
import useStyle from './style';
|
import useStyle from './style';
|
||||||
|
import getPlacements from '../_util/placements';
|
||||||
|
|
||||||
export { TourProps, TourStepProps };
|
export { TourProps, TourStepProps };
|
||||||
|
|
||||||
const Tour = defineComponent({
|
const Tour = defineComponent({
|
||||||
name: 'ATour',
|
name: 'ATour',
|
||||||
|
inheritAttrs: false,
|
||||||
props: tourProps(),
|
props: tourProps(),
|
||||||
setup(props, { attrs, emit, slots }) {
|
setup(props, { attrs, emit, slots }) {
|
||||||
const { current } = toRefs(props);
|
const { current, type, steps, defaultCurrent } = toRefs(props);
|
||||||
const { prefixCls, direction } = useConfigInject('tour', props);
|
const { prefixCls, direction } = useConfigInject('tour', props);
|
||||||
|
|
||||||
// style
|
// style
|
||||||
const [wrapSSR, hashId] = useStyle(prefixCls);
|
const [wrapSSR, hashId] = useStyle(prefixCls);
|
||||||
|
|
||||||
const { currentMergedType, updateInnerCurrent } = useMergedType({
|
const { currentMergedType, updateInnerCurrent } = useMergedType({
|
||||||
defaultType: props.type,
|
defaultType: type,
|
||||||
steps: props.steps,
|
steps,
|
||||||
current,
|
current,
|
||||||
defaultCurrent: props.defaultCurrent,
|
defaultCurrent,
|
||||||
});
|
});
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
const { steps, current, type, rootClassName, ...restProps } = props;
|
const { steps, current, type, rootClassName, ...restProps } = props;
|
||||||
|
|
||||||
|
@ -58,9 +59,17 @@ const Tour = defineComponent({
|
||||||
|
|
||||||
const onStepChange = (stepCurrent: number) => {
|
const onStepChange = (stepCurrent: number) => {
|
||||||
updateInnerCurrent(stepCurrent);
|
updateInnerCurrent(stepCurrent);
|
||||||
|
emit('update:current', stepCurrent);
|
||||||
emit('change', stepCurrent);
|
emit('change', stepCurrent);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const builtinPlacements = computed(() =>
|
||||||
|
getPlacements({
|
||||||
|
arrowPointAtCenter: true,
|
||||||
|
autoAdjustOverflow: true,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
return wrapSSR(
|
return wrapSSR(
|
||||||
<VCTour
|
<VCTour
|
||||||
{...attrs}
|
{...attrs}
|
||||||
|
@ -73,6 +82,7 @@ const Tour = defineComponent({
|
||||||
renderPanel={mergedRenderPanel}
|
renderPanel={mergedRenderPanel}
|
||||||
onChange={onStepChange}
|
onChange={onStepChange}
|
||||||
steps={steps}
|
steps={steps}
|
||||||
|
builtinPlacements={builtinPlacements.value as any}
|
||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -24,7 +24,7 @@ coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*nF6hQpM0XtEAAA
|
||||||
| mask | 是否启用蒙层,也可传入配置改变蒙层样式和填充色 | `boolean` \| `{ style?: CSSProperties; color?: string; }` | `true` | |
|
| mask | 是否启用蒙层,也可传入配置改变蒙层样式和填充色 | `boolean` \| `{ style?: CSSProperties; color?: string; }` | `true` | |
|
||||||
| type | 类型,影响底色与文字颜色 | `default` \| `primary` | `default` | |
|
| type | 类型,影响底色与文字颜色 | `default` \| `primary` | `default` | |
|
||||||
| open | 打开引导 | `boolean` | - | |
|
| open | 打开引导 | `boolean` | - | |
|
||||||
| current | 当前处于哪一步 | `number` | - | |
|
| current(v-model) | 当前处于哪一步 | `number` | - | |
|
||||||
| scrollIntoViewOptions | 是否支持当前元素滚动到视窗内,也可传入配置指定滚动视窗的相关参数 | `boolean` \| `ScrollIntoViewOptions` | `true` | |
|
| scrollIntoViewOptions | 是否支持当前元素滚动到视窗内,也可传入配置指定滚动视窗的相关参数 | `boolean` \| `ScrollIntoViewOptions` | `true` | |
|
||||||
| indicatorsRender | 自定义指示器 | `v-slot:indicatorsRender="{current, total}"` | - | |
|
| indicatorsRender | 自定义指示器 | `v-slot:indicatorsRender="{current, total}"` | - | |
|
||||||
| zIndex | Tour 的层级 | `number` | `1001` | |
|
| zIndex | Tour 的层级 | `number` | `1001` | |
|
||||||
|
|
|
@ -8,6 +8,7 @@ export const tourProps = () => ({
|
||||||
prefixCls: { type: String },
|
prefixCls: { type: String },
|
||||||
current: { type: Number },
|
current: { type: Number },
|
||||||
type: { type: String as PropType<'default' | 'primary'> }, // default 类型,影响底色与文字颜色
|
type: { type: String as PropType<'default' | 'primary'> }, // default 类型,影响底色与文字颜色
|
||||||
|
'onUpdate:current': Function as PropType<(val: number) => void>,
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TourProps = Partial<ExtractPropTypes<ReturnType<typeof tourProps>>>;
|
export type TourProps = Partial<ExtractPropTypes<ReturnType<typeof tourProps>>>;
|
||||||
|
|
|
@ -12,16 +12,16 @@ import defaultLocale from '../locale/en_US';
|
||||||
import type { VueNode } from '../_util/type';
|
import type { VueNode } from '../_util/type';
|
||||||
|
|
||||||
const panelRender = defineComponent({
|
const panelRender = defineComponent({
|
||||||
|
name: 'ATourPanel',
|
||||||
|
inheritAttrs: false,
|
||||||
props: tourStepProps(),
|
props: tourStepProps(),
|
||||||
setup(props, { attrs, slots }) {
|
setup(props, { attrs, slots }) {
|
||||||
const { current, total } = toRefs(props);
|
const { current, total } = toRefs(props);
|
||||||
|
|
||||||
const isLastStep = computed(() => current.value === total.value - 1);
|
const isLastStep = computed(() => current.value === total.value - 1);
|
||||||
|
|
||||||
const prevButtonProps = props.prevButtonProps as TourBtnProps;
|
|
||||||
const nextButtonProps = props.nextButtonProps as TourBtnProps;
|
|
||||||
|
|
||||||
const prevBtnClick = e => {
|
const prevBtnClick = e => {
|
||||||
|
const prevButtonProps = props.prevButtonProps as TourBtnProps;
|
||||||
props.onPrev?.(e);
|
props.onPrev?.(e);
|
||||||
if (typeof prevButtonProps?.onClick === 'function') {
|
if (typeof prevButtonProps?.onClick === 'function') {
|
||||||
prevButtonProps?.onClick();
|
prevButtonProps?.onClick();
|
||||||
|
@ -29,6 +29,7 @@ const panelRender = defineComponent({
|
||||||
};
|
};
|
||||||
|
|
||||||
const nextBtnClick = e => {
|
const nextBtnClick = e => {
|
||||||
|
const nextButtonProps = props.nextButtonProps as TourBtnProps;
|
||||||
if (isLastStep.value) {
|
if (isLastStep.value) {
|
||||||
props.onFinish?.(e);
|
props.onFinish?.(e);
|
||||||
} else {
|
} else {
|
||||||
|
@ -40,16 +41,7 @@ const panelRender = defineComponent({
|
||||||
};
|
};
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
const {
|
const { prefixCls, title, onClose, cover, description, type: stepType, arrow } = props;
|
||||||
prefixCls,
|
|
||||||
title,
|
|
||||||
onClose,
|
|
||||||
|
|
||||||
cover,
|
|
||||||
description,
|
|
||||||
type: stepType,
|
|
||||||
arrow,
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
const prevButtonProps = props.prevButtonProps as TourBtnProps;
|
const prevButtonProps = props.prevButtonProps as TourBtnProps;
|
||||||
const nextButtonProps = props.nextButtonProps as TourBtnProps;
|
const nextButtonProps = props.nextButtonProps as TourBtnProps;
|
||||||
|
|
|
@ -1,33 +1,36 @@
|
||||||
import useMergedState from '../_util/hooks/useMergedState';
|
|
||||||
import type { TourProps } from './interface';
|
import type { TourProps } from './interface';
|
||||||
import type { Ref } from 'vue';
|
import type { Ref } from 'vue';
|
||||||
import { computed, watch } from 'vue';
|
import { ref, computed, watch } from 'vue';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
defaultType?: string;
|
defaultType?: Ref<string>;
|
||||||
steps?: TourProps['steps'];
|
steps?: Ref<TourProps['steps']>;
|
||||||
current?: Ref<number>;
|
current?: Ref<number>;
|
||||||
defaultCurrent?: number;
|
defaultCurrent?: Ref<number>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* returns the merged type of a step or the default type.
|
* returns the merged type of a step or the default type.
|
||||||
*/
|
*/
|
||||||
const useMergedType = ({ defaultType, steps = [], current, defaultCurrent }: Props) => {
|
const useMergedType = ({ defaultType, steps, current, defaultCurrent }: Props) => {
|
||||||
const [innerCurrent, updateInnerCurrent] = useMergedState<number | undefined>(defaultCurrent, {
|
const innerCurrent = ref(defaultCurrent?.value);
|
||||||
value: current,
|
const mergedCurrent = computed(() => current?.value);
|
||||||
});
|
watch(
|
||||||
|
mergedCurrent,
|
||||||
watch(current, val => {
|
val => {
|
||||||
if (val === undefined) return;
|
innerCurrent.value = val ?? defaultCurrent?.value;
|
||||||
updateInnerCurrent(val);
|
},
|
||||||
});
|
{ immediate: true },
|
||||||
|
);
|
||||||
|
const updateInnerCurrent = (val: number) => {
|
||||||
|
innerCurrent.value = val;
|
||||||
|
};
|
||||||
const innerType = computed(() => {
|
const innerType = computed(() => {
|
||||||
return typeof innerCurrent.value === 'number' ? steps[innerCurrent.value]?.type : defaultType;
|
return typeof innerCurrent.value === 'number'
|
||||||
|
? steps && steps.value?.[innerCurrent.value]?.type
|
||||||
|
: defaultType?.value;
|
||||||
});
|
});
|
||||||
|
|
||||||
const currentMergedType = computed(() => innerType.value ?? defaultType);
|
const currentMergedType = computed(() => innerType.value ?? defaultType?.value);
|
||||||
|
|
||||||
return { currentMergedType, updateInnerCurrent };
|
return { currentMergedType, updateInnerCurrent };
|
||||||
};
|
};
|
||||||
|
|
|
@ -209,7 +209,7 @@ const DrawerChild = defineComponent({
|
||||||
const motionProps = typeof motion === 'function' ? motion(placement) : motion;
|
const motionProps = typeof motion === 'function' ? motion(placement) : motion;
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
{...omit(otherProps, ['switchScrollingEffect', 'autofocus'])}
|
{...omit(otherProps, ['autofocus'])}
|
||||||
tabindex={-1}
|
tabindex={-1}
|
||||||
class={wrapperClassName}
|
class={wrapperClassName}
|
||||||
style={rootStyle}
|
style={rootStyle}
|
||||||
|
|
|
@ -54,7 +54,6 @@ const drawerChildProps = () => ({
|
||||||
getContainer: Function,
|
getContainer: Function,
|
||||||
getOpenCount: Function as PropType<() => number>,
|
getOpenCount: Function as PropType<() => number>,
|
||||||
scrollLocker: PropTypes.any,
|
scrollLocker: PropTypes.any,
|
||||||
switchScrollingEffect: Function,
|
|
||||||
inline: Boolean,
|
inline: Boolean,
|
||||||
});
|
});
|
||||||
export { drawerProps, drawerChildProps };
|
export { drawerProps, drawerChildProps };
|
||||||
|
|
|
@ -39,7 +39,6 @@ import useMergedState from '../_util/hooks/useMergedState';
|
||||||
import { warning } from '../vc-util/warning';
|
import { warning } from '../vc-util/warning';
|
||||||
import classNames from '../_util/classNames';
|
import classNames from '../_util/classNames';
|
||||||
import type { SharedTimeProps } from './panels/TimePanel';
|
import type { SharedTimeProps } from './panels/TimePanel';
|
||||||
import { useProviderTrigger } from '../vc-trigger/context';
|
|
||||||
import { legacyPropsWarning } from './utils/warnUtil';
|
import { legacyPropsWarning } from './utils/warnUtil';
|
||||||
|
|
||||||
export type PickerRefConfig = {
|
export type PickerRefConfig = {
|
||||||
|
@ -435,8 +434,6 @@ function Picker<DateType>() {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const getPortal = useProviderTrigger();
|
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
const {
|
const {
|
||||||
prefixCls = 'rc-picker',
|
prefixCls = 'rc-picker',
|
||||||
|
@ -631,7 +628,6 @@ function Picker<DateType>() {
|
||||||
{suffixNode}
|
{suffixNode}
|
||||||
{clearNode}
|
{clearNode}
|
||||||
</div>
|
</div>
|
||||||
{getPortal()}
|
|
||||||
</div>
|
</div>
|
||||||
</PickerTrigger>
|
</PickerTrigger>
|
||||||
);
|
);
|
||||||
|
|
|
@ -96,7 +96,6 @@ function PickerTrigger(props: PickerTriggerProps, { slots }) {
|
||||||
default: slots.default,
|
default: slots.default,
|
||||||
popup: slots.popupElement,
|
popup: slots.popupElement,
|
||||||
}}
|
}}
|
||||||
tryPopPortal
|
|
||||||
></Trigger>
|
></Trigger>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,7 +44,6 @@ import useMergedState from '../_util/hooks/useMergedState';
|
||||||
import { warning } from '../vc-util/warning';
|
import { warning } from '../vc-util/warning';
|
||||||
import useState from '../_util/hooks/useState';
|
import useState from '../_util/hooks/useState';
|
||||||
import classNames from '../_util/classNames';
|
import classNames from '../_util/classNames';
|
||||||
import { useProviderTrigger } from '../vc-trigger/context';
|
|
||||||
import { legacyPropsWarning } from './utils/warnUtil';
|
import { legacyPropsWarning } from './utils/warnUtil';
|
||||||
import { useElementSize } from '../_util/hooks/_vueuse/useElementSize';
|
import { useElementSize } from '../_util/hooks/_vueuse/useElementSize';
|
||||||
|
|
||||||
|
@ -256,7 +255,6 @@ function RangerPicker<DateType>() {
|
||||||
const needConfirmButton = computed(
|
const needConfirmButton = computed(
|
||||||
() => (props.picker === 'date' && !!props.showTime) || props.picker === 'time',
|
() => (props.picker === 'date' && !!props.showTime) || props.picker === 'time',
|
||||||
);
|
);
|
||||||
const getPortal = useProviderTrigger();
|
|
||||||
const presets = computed(() => props.presets);
|
const presets = computed(() => props.presets);
|
||||||
const ranges = computed(() => props.ranges);
|
const ranges = computed(() => props.ranges);
|
||||||
const presetList = usePresets(presets, ranges);
|
const presetList = usePresets(presets, ranges);
|
||||||
|
@ -1298,7 +1296,6 @@ function RangerPicker<DateType>() {
|
||||||
/>
|
/>
|
||||||
{suffixNode}
|
{suffixNode}
|
||||||
{clearNode}
|
{clearNode}
|
||||||
{getPortal()}
|
|
||||||
</div>
|
</div>
|
||||||
</PickerTrigger>
|
</PickerTrigger>
|
||||||
);
|
);
|
||||||
|
|
|
@ -35,17 +35,16 @@ const Mask = defineComponent({
|
||||||
zIndex: { type: Number },
|
zIndex: { type: Number },
|
||||||
},
|
},
|
||||||
setup(props, { attrs }) {
|
setup(props, { attrs }) {
|
||||||
|
const id = useId();
|
||||||
return () => {
|
return () => {
|
||||||
const { prefixCls, open, rootClassName, pos, showMask, fill, animated, zIndex } = props;
|
const { prefixCls, open, rootClassName, pos, showMask, fill, animated, zIndex } = props;
|
||||||
|
|
||||||
const id = useId();
|
|
||||||
const maskId = `${prefixCls}-mask-${id}`;
|
const maskId = `${prefixCls}-mask-${id}`;
|
||||||
const mergedAnimated = typeof animated === 'object' ? animated?.placeholder : animated;
|
const mergedAnimated = typeof animated === 'object' ? animated?.placeholder : animated;
|
||||||
|
|
||||||
console.log(open);
|
|
||||||
return (
|
return (
|
||||||
<Portal
|
<Portal
|
||||||
visible={open}
|
visible={open}
|
||||||
|
autoLock
|
||||||
v-slots={{
|
v-slots={{
|
||||||
default: () =>
|
default: () =>
|
||||||
open && (
|
open && (
|
||||||
|
@ -60,7 +59,9 @@ const Mask = defineComponent({
|
||||||
top: 0,
|
top: 0,
|
||||||
bottom: 0,
|
bottom: 0,
|
||||||
zIndex,
|
zIndex,
|
||||||
|
pointerEvents: 'none',
|
||||||
},
|
},
|
||||||
|
attrs.style as CSSProperties,
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
{showMask ? (
|
{showMask ? (
|
||||||
|
|
|
@ -12,8 +12,7 @@ import Mask from './Mask';
|
||||||
import { getPlacements } from './placements';
|
import { getPlacements } from './placements';
|
||||||
import type { PlacementType } from './placements';
|
import type { PlacementType } from './placements';
|
||||||
import { initDefaultProps } from '../_util/props-util';
|
import { initDefaultProps } from '../_util/props-util';
|
||||||
import useScrollLocker from './hooks/useScrollLocker';
|
|
||||||
import canUseDom from '../_util/canUseDom';
|
|
||||||
import {
|
import {
|
||||||
someType,
|
someType,
|
||||||
stringType,
|
stringType,
|
||||||
|
@ -22,18 +21,20 @@ import {
|
||||||
functionType,
|
functionType,
|
||||||
booleanType,
|
booleanType,
|
||||||
} from '../_util/type';
|
} from '../_util/type';
|
||||||
|
import Portal from '../_util/PortalWrapper';
|
||||||
|
|
||||||
const CENTER_PLACEHOLDER: CSSProperties = {
|
const CENTER_PLACEHOLDER: CSSProperties = {
|
||||||
left: '50%',
|
left: '50%',
|
||||||
top: '50%',
|
top: '50%',
|
||||||
width: 1,
|
width: '1px',
|
||||||
height: 1,
|
height: '1px',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const tourProps = () => {
|
export const tourProps = () => {
|
||||||
const { builtinPlacements, ...pickedTriggerProps } = triggerProps();
|
const { builtinPlacements, popupAlign } = triggerProps();
|
||||||
return {
|
return {
|
||||||
...pickedTriggerProps,
|
builtinPlacements,
|
||||||
|
popupAlign,
|
||||||
steps: arrayType<TourStepInfo[]>(),
|
steps: arrayType<TourStepInfo[]>(),
|
||||||
open: booleanType(),
|
open: booleanType(),
|
||||||
defaultCurrent: { type: Number },
|
defaultCurrent: { type: Number },
|
||||||
|
@ -58,6 +59,7 @@ export type TourProps = Partial<ExtractPropTypes<ReturnType<typeof tourProps>>>;
|
||||||
|
|
||||||
const Tour = defineComponent({
|
const Tour = defineComponent({
|
||||||
name: 'Tour',
|
name: 'Tour',
|
||||||
|
inheritAttrs: false,
|
||||||
props: initDefaultProps(tourProps(), {}),
|
props: initDefaultProps(tourProps(), {}),
|
||||||
setup(props) {
|
setup(props) {
|
||||||
const { defaultCurrent, placement, mask, scrollIntoViewOptions, open, gap, arrow } =
|
const { defaultCurrent, placement, mask, scrollIntoViewOptions, open, gap, arrow } =
|
||||||
|
@ -125,11 +127,6 @@ const Tour = defineComponent({
|
||||||
props.onChange?.(nextCurrent);
|
props.onChange?.(nextCurrent);
|
||||||
};
|
};
|
||||||
|
|
||||||
// ========================= lock scroll =========================
|
|
||||||
const lockScroll = computed(() => mergedOpen.value && canUseDom());
|
|
||||||
|
|
||||||
useScrollLocker(lockScroll);
|
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
const {
|
const {
|
||||||
prefixCls,
|
prefixCls,
|
||||||
|
@ -159,8 +156,8 @@ const Tour = defineComponent({
|
||||||
const mergedMaskStyle = typeof mergedMask.value === 'boolean' ? undefined : mergedMask.value;
|
const mergedMaskStyle = typeof mergedMask.value === 'boolean' ? undefined : mergedMask.value;
|
||||||
|
|
||||||
// when targetElement is not exist, use body as triggerDOMNode
|
// when targetElement is not exist, use body as triggerDOMNode
|
||||||
const getTriggerDOMNode = () => {
|
const getTriggerDOMNode = node => {
|
||||||
return targetElement.value || document.body;
|
return node || targetElement.value || document.body;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getPopupElement = () => (
|
const getPopupElement = () => (
|
||||||
|
@ -185,7 +182,19 @@ const Tour = defineComponent({
|
||||||
{...curStep.value}
|
{...curStep.value}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
const posInfoStyle = computed(() => {
|
||||||
|
const info = posInfo.value || CENTER_PLACEHOLDER;
|
||||||
|
// 如果info[key] 是number,添加 px
|
||||||
|
const style: CSSProperties = {};
|
||||||
|
Object.keys(info).forEach(key => {
|
||||||
|
if (typeof info[key] === 'number') {
|
||||||
|
style[key] = `${info[key]}px`;
|
||||||
|
} else {
|
||||||
|
style[key] = info[key];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return style;
|
||||||
|
});
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Mask
|
<Mask
|
||||||
|
@ -203,18 +212,8 @@ const Tour = defineComponent({
|
||||||
builtinPlacements={getPlacements(arrowPointAtCenter.value)}
|
builtinPlacements={getPlacements(arrowPointAtCenter.value)}
|
||||||
{...restProps}
|
{...restProps}
|
||||||
ref={triggerRef}
|
ref={triggerRef}
|
||||||
popupStyle={
|
popupStyle={curStep.value.style}
|
||||||
!curStep.value.target
|
popupPlacement={mergedPlacement.value}
|
||||||
? {
|
|
||||||
...curStep.value.style,
|
|
||||||
position: 'fixed',
|
|
||||||
left: CENTER_PLACEHOLDER.left,
|
|
||||||
top: CENTER_PLACEHOLDER.top,
|
|
||||||
transform: 'translate(-50%, -50%)',
|
|
||||||
}
|
|
||||||
: curStep.value.style
|
|
||||||
}
|
|
||||||
popupPlacement={!curStep.value.target ? 'center' : mergedPlacement.value}
|
|
||||||
popupVisible={mergedOpen.value}
|
popupVisible={mergedOpen.value}
|
||||||
popupClassName={classNames(rootClassName, curStep.value.className)}
|
popupClassName={classNames(rootClassName, curStep.value.className)}
|
||||||
prefixCls={prefixCls}
|
prefixCls={prefixCls}
|
||||||
|
@ -225,14 +224,16 @@ const Tour = defineComponent({
|
||||||
mask={false}
|
mask={false}
|
||||||
getTriggerDOMNode={getTriggerDOMNode}
|
getTriggerDOMNode={getTriggerDOMNode}
|
||||||
>
|
>
|
||||||
|
<Portal visible={mergedOpen.value} autoLock>
|
||||||
<div
|
<div
|
||||||
class={classNames(rootClassName, `${prefixCls}-target-placeholder`)}
|
class={classNames(rootClassName, `${prefixCls}-target-placeholder`)}
|
||||||
style={{
|
style={{
|
||||||
...(posInfo.value || CENTER_PLACEHOLDER),
|
...posInfoStyle.value,
|
||||||
position: 'fixed',
|
position: 'fixed',
|
||||||
pointerEvents: 'none',
|
pointerEvents: 'none',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
</Portal>
|
||||||
</Trigger>
|
</Trigger>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
@ -5,6 +5,7 @@ import type { TourStepProps } from '../interface';
|
||||||
|
|
||||||
const DefaultPanel = defineComponent({
|
const DefaultPanel = defineComponent({
|
||||||
name: 'DefaultPanel',
|
name: 'DefaultPanel',
|
||||||
|
inheritAttrs: false,
|
||||||
props: tourStepProps(),
|
props: tourStepProps(),
|
||||||
setup(props, { attrs }) {
|
setup(props, { attrs }) {
|
||||||
return () => {
|
return () => {
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { tourStepProps } from '../interface';
|
||||||
|
|
||||||
const TourStep = defineComponent({
|
const TourStep = defineComponent({
|
||||||
name: 'TourStep',
|
name: 'TourStep',
|
||||||
|
inheritAttrs: false,
|
||||||
props: tourStepProps(),
|
props: tourStepProps(),
|
||||||
setup(props, { attrs }) {
|
setup(props, { attrs }) {
|
||||||
return () => {
|
return () => {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { computed, watchEffect, watch } from 'vue';
|
import { computed, watchEffect, onMounted, watch, onBeforeUnmount } from 'vue';
|
||||||
import type { Ref } from 'vue';
|
import type { Ref } from 'vue';
|
||||||
import { isInViewPort } from '../util';
|
import { isInViewPort } from '../util';
|
||||||
import type { TourStepInfo } from '..';
|
import type { TourStepInfo } from '..';
|
||||||
|
@ -29,11 +29,15 @@ export default function useTarget(
|
||||||
// `null` as empty target.
|
// `null` as empty target.
|
||||||
const [targetElement, setTargetElement] = useState<null | HTMLElement | undefined>(undefined);
|
const [targetElement, setTargetElement] = useState<null | HTMLElement | undefined>(undefined);
|
||||||
|
|
||||||
watchEffect(() => {
|
watchEffect(
|
||||||
const nextElement = typeof target.value === 'function' ? (target.value as any)() : target.value;
|
() => {
|
||||||
|
const nextElement =
|
||||||
|
typeof target.value === 'function' ? (target.value as any)() : target.value;
|
||||||
|
|
||||||
setTargetElement(nextElement || null);
|
setTargetElement(nextElement || null);
|
||||||
});
|
},
|
||||||
|
{ flush: 'post' },
|
||||||
|
);
|
||||||
|
|
||||||
// ========================= Align ==========================
|
// ========================= Align ==========================
|
||||||
const [posInfo, setPosInfo] = useState<PosInfo>(null);
|
const [posInfo, setPosInfo] = useState<PosInfo>(null);
|
||||||
|
@ -47,36 +51,29 @@ export default function useTarget(
|
||||||
|
|
||||||
const { left, top, width, height } = targetElement.value.getBoundingClientRect();
|
const { left, top, width, height } = targetElement.value.getBoundingClientRect();
|
||||||
const nextPosInfo: PosInfo = { left, top, width, height, radius: 0 };
|
const nextPosInfo: PosInfo = { left, top, width, height, radius: 0 };
|
||||||
|
if (JSON.stringify(posInfo.value) !== JSON.stringify(nextPosInfo)) {
|
||||||
setPosInfo(nextPosInfo);
|
setPosInfo(nextPosInfo);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Not exist target which means we just show in center
|
// Not exist target which means we just show in center
|
||||||
setPosInfo(null);
|
setPosInfo(null);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
watchEffect(() => {
|
onMounted(() => {
|
||||||
updatePos();
|
|
||||||
// update when window resize
|
|
||||||
window.addEventListener('resize', updatePos);
|
|
||||||
return () => {
|
|
||||||
window.removeEventListener('resize', updatePos);
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
open,
|
[open, targetElement],
|
||||||
val => {
|
() => {
|
||||||
updatePos();
|
updatePos();
|
||||||
// update when window resize
|
|
||||||
if (val) {
|
|
||||||
window.addEventListener('resize', updatePos);
|
|
||||||
} else {
|
|
||||||
window.removeEventListener('resize', updatePos);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{ immediate: true },
|
{ flush: 'post', immediate: true },
|
||||||
);
|
);
|
||||||
|
// update when window resize
|
||||||
|
window.addEventListener('resize', updatePos);
|
||||||
|
});
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
window.removeEventListener('resize', updatePos);
|
||||||
|
});
|
||||||
|
|
||||||
// ======================== PosInfo =========================
|
// ======================== PosInfo =========================
|
||||||
const mergedPosInfo = computed(() => {
|
const mergedPosInfo = computed(() => {
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import type { BuildInPlacements } from '../vc-trigger/interface';
|
||||||
|
|
||||||
export type PlacementType =
|
export type PlacementType =
|
||||||
| 'left'
|
| 'left'
|
||||||
| 'leftTop'
|
| 'leftTop'
|
||||||
|
@ -15,64 +17,6 @@ export type PlacementType =
|
||||||
|
|
||||||
const targetOffset = [0, 0];
|
const targetOffset = [0, 0];
|
||||||
|
|
||||||
export type AlignPointTopBottom = 't' | 'b' | 'c';
|
|
||||||
export type AlignPointLeftRight = 'l' | 'r' | 'c';
|
|
||||||
|
|
||||||
/** Two char of 't' 'b' 'c' 'l' 'r'. Example: 'lt' */
|
|
||||||
export type AlignPoint = `${AlignPointTopBottom}${AlignPointLeftRight}`;
|
|
||||||
|
|
||||||
export interface AlignType {
|
|
||||||
/**
|
|
||||||
* move point of source node to align with point of target node.
|
|
||||||
* Such as ['tr','cc'], align top right point of source node with center point of target node.
|
|
||||||
* Point can be 't'(top), 'b'(bottom), 'c'(center), 'l'(left), 'r'(right) */
|
|
||||||
points?: (string | AlignPoint)[];
|
|
||||||
/**
|
|
||||||
* offset source node by offset[0] in x and offset[1] in y.
|
|
||||||
* If offset contains percentage string value, it is relative to sourceNode region.
|
|
||||||
*/
|
|
||||||
offset?: number[];
|
|
||||||
/**
|
|
||||||
* offset target node by offset[0] in x and offset[1] in y.
|
|
||||||
* If targetOffset contains percentage string value, it is relative to targetNode region.
|
|
||||||
*/
|
|
||||||
targetOffset?: number[];
|
|
||||||
/**
|
|
||||||
* If adjustX field is true, will adjust source node in x direction if source node is invisible.
|
|
||||||
* If adjustY field is true, will adjust source node in y direction if source node is invisible.
|
|
||||||
*/
|
|
||||||
overflow?: {
|
|
||||||
adjustX?: boolean | number;
|
|
||||||
adjustY?: boolean | number;
|
|
||||||
shiftX?: boolean | number;
|
|
||||||
shiftY?: boolean | number;
|
|
||||||
};
|
|
||||||
/** Auto adjust arrow position */
|
|
||||||
autoArrow?: boolean;
|
|
||||||
/**
|
|
||||||
* Config visible region check of html node. Default `visible`:
|
|
||||||
* - `visible`: The visible region of user browser window. Use `clientHeight` for check.
|
|
||||||
* - `scroll`: The whole region of the html scroll area. Use `scrollHeight` for check.
|
|
||||||
*/
|
|
||||||
htmlRegion?: 'visible' | 'scroll';
|
|
||||||
/**
|
|
||||||
* Whether use css right instead of left to position
|
|
||||||
*/
|
|
||||||
useCssRight?: boolean;
|
|
||||||
/**
|
|
||||||
* Whether use css bottom instead of top to position
|
|
||||||
*/
|
|
||||||
useCssBottom?: boolean;
|
|
||||||
/**
|
|
||||||
* Whether use css transform instead of left/top/right/bottom to position if browser supports.
|
|
||||||
* Defaults to false.
|
|
||||||
*/
|
|
||||||
useCssTransform?: boolean;
|
|
||||||
ignoreShake?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type BuildInPlacements = Record<string, AlignType>;
|
|
||||||
|
|
||||||
const basePlacements: BuildInPlacements = {
|
const basePlacements: BuildInPlacements = {
|
||||||
left: {
|
left: {
|
||||||
points: ['cr', 'cl'],
|
points: ['cr', 'cl'],
|
||||||
|
|
|
@ -15,11 +15,11 @@ import addEventListener from '../vc-util/Dom/addEventListener';
|
||||||
import Popup from './Popup';
|
import Popup from './Popup';
|
||||||
import { getAlignFromPlacement, getAlignPopupClassName } from './utils/alignUtil';
|
import { getAlignFromPlacement, getAlignPopupClassName } from './utils/alignUtil';
|
||||||
import BaseMixin from '../_util/BaseMixin';
|
import BaseMixin from '../_util/BaseMixin';
|
||||||
import Portal from '../_util/Portal';
|
import Portal from '../_util/PortalWrapper';
|
||||||
import classNames from '../_util/classNames';
|
import classNames from '../_util/classNames';
|
||||||
import { cloneElement } from '../_util/vnode';
|
import { cloneElement } from '../_util/vnode';
|
||||||
import supportsPassive from '../_util/supportsPassive';
|
import supportsPassive from '../_util/supportsPassive';
|
||||||
import { useInjectTrigger, useProvidePortal } from './context';
|
import { useProvidePortal } from './context';
|
||||||
|
|
||||||
const ALL_HANDLERS = [
|
const ALL_HANDLERS = [
|
||||||
'onClick',
|
'onClick',
|
||||||
|
@ -45,14 +45,11 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
return popupAlign;
|
return popupAlign;
|
||||||
});
|
});
|
||||||
const { setPortal, popPortal } = useInjectTrigger(props.tryPopPortal);
|
|
||||||
const popupRef = shallowRef(null);
|
const popupRef = shallowRef(null);
|
||||||
const setPopupRef = val => {
|
const setPopupRef = val => {
|
||||||
popupRef.value = val;
|
popupRef.value = val;
|
||||||
};
|
};
|
||||||
return {
|
return {
|
||||||
popPortal,
|
|
||||||
setPortal,
|
|
||||||
vcTriggerContext: inject(
|
vcTriggerContext: inject(
|
||||||
'vcTriggerContext',
|
'vcTriggerContext',
|
||||||
{} as {
|
{} as {
|
||||||
|
@ -92,14 +89,6 @@ export default defineComponent({
|
||||||
(this as any).fireEvents(h, e);
|
(this as any).fireEvents(h, e);
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
(this as any).setPortal?.(
|
|
||||||
<Portal
|
|
||||||
key="portal"
|
|
||||||
v-slots={{ default: this.getComponent }}
|
|
||||||
getContainer={this.getContainer}
|
|
||||||
didUpdate={this.handlePortalUpdate}
|
|
||||||
></Portal>,
|
|
||||||
);
|
|
||||||
return {
|
return {
|
||||||
prevPopupVisible: popupVisible,
|
prevPopupVisible: popupVisible,
|
||||||
sPopupVisible: popupVisible,
|
sPopupVisible: popupVisible,
|
||||||
|
@ -406,7 +395,7 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
mouseProps.onMousedown = this.onPopupMouseDown;
|
mouseProps.onMousedown = this.onPopupMouseDown;
|
||||||
mouseProps[supportsPassive ? 'onTouchstartPassive' : 'onTouchstart'] = this.onPopupMouseDown;
|
mouseProps[supportsPassive ? 'onTouchstartPassive' : 'onTouchstart'] = this.onPopupMouseDown;
|
||||||
const { handleGetPopupClassFromAlign, getRootDomNode, getContainer, $attrs } = this;
|
const { handleGetPopupClassFromAlign, getRootDomNode, $attrs } = this;
|
||||||
const {
|
const {
|
||||||
prefixCls,
|
prefixCls,
|
||||||
destroyPopupOnHide,
|
destroyPopupOnHide,
|
||||||
|
@ -439,7 +428,6 @@ export default defineComponent({
|
||||||
transitionName: popupTransitionName,
|
transitionName: popupTransitionName,
|
||||||
maskAnimation,
|
maskAnimation,
|
||||||
maskTransitionName,
|
maskTransitionName,
|
||||||
getContainer,
|
|
||||||
class: popupClassName,
|
class: popupClassName,
|
||||||
style: popupStyle,
|
style: popupStyle,
|
||||||
onAlign: $attrs.onPopupAlign || noop,
|
onAlign: $attrs.onPopupAlign || noop,
|
||||||
|
@ -644,7 +632,7 @@ export default defineComponent({
|
||||||
render() {
|
render() {
|
||||||
const { $attrs } = this;
|
const { $attrs } = this;
|
||||||
const children = filterEmpty(getSlot(this));
|
const children = filterEmpty(getSlot(this));
|
||||||
const { alignPoint } = this.$props;
|
const { alignPoint, getPopupContainer } = this.$props;
|
||||||
|
|
||||||
const child = children[0];
|
const child = children[0];
|
||||||
this.childOriginEvents = getEvents(child);
|
this.childOriginEvents = getEvents(child);
|
||||||
|
@ -701,15 +689,14 @@ export default defineComponent({
|
||||||
newChildProps.class = childrenClassName;
|
newChildProps.class = childrenClassName;
|
||||||
}
|
}
|
||||||
const trigger = cloneElement(child, { ...newChildProps, ref: 'triggerRef' }, true, true);
|
const trigger = cloneElement(child, { ...newChildProps, ref: 'triggerRef' }, true, true);
|
||||||
if (this.popPortal) {
|
|
||||||
return trigger;
|
|
||||||
} else {
|
|
||||||
const portal = (
|
const portal = (
|
||||||
<Portal
|
<Portal
|
||||||
key="portal"
|
key="portal"
|
||||||
v-slots={{ default: this.getComponent }}
|
v-slots={{ default: this.getComponent }}
|
||||||
getContainer={this.getContainer}
|
getContainer={getPopupContainer && (() => getPopupContainer(this.getRootDomNode()))}
|
||||||
didUpdate={this.handlePortalUpdate}
|
didUpdate={this.handlePortalUpdate}
|
||||||
|
visible={this.$data.sPopupVisible}
|
||||||
></Portal>
|
></Portal>
|
||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
|
@ -718,6 +705,5 @@ export default defineComponent({
|
||||||
{trigger}
|
{trigger}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -18,12 +18,6 @@ export const useProviderTrigger = () => {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useInjectTrigger = (tryPopPortal?: boolean) => {
|
|
||||||
return tryPopPortal
|
|
||||||
? inject(TriggerContextKey, { setPortal: () => {}, popPortal: false })
|
|
||||||
: { setPortal: () => {}, popPortal: false };
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface PortalContextProps {
|
export interface PortalContextProps {
|
||||||
shouldRender: Ref<boolean>;
|
shouldRender: Ref<boolean>;
|
||||||
inTriggerContext: boolean; // 仅处理 trigger 上下文的 portal
|
inTriggerContext: boolean; // 仅处理 trigger 上下文的 portal
|
||||||
|
|
|
@ -5,22 +5,23 @@ import PropTypes from '../_util/vue-types';
|
||||||
/** Two char of 't' 'b' 'c' 'l' 'r'. Example: 'lt' */
|
/** Two char of 't' 'b' 'c' 'l' 'r'. Example: 'lt' */
|
||||||
export type AlignPoint = string;
|
export type AlignPoint = string;
|
||||||
|
|
||||||
|
export type OffsetType = number | `${number}%`;
|
||||||
export interface AlignType {
|
export interface AlignType {
|
||||||
/**
|
/**
|
||||||
* move point of source node to align with point of target node.
|
* move point of source node to align with point of target node.
|
||||||
* Such as ['tr','cc'], align top right point of source node with center point of target node.
|
* Such as ['tr','cc'], align top right point of source node with center point of target node.
|
||||||
* Point can be 't'(top), 'b'(bottom), 'c'(center), 'l'(left), 'r'(right) */
|
* Point can be 't'(top), 'b'(bottom), 'c'(center), 'l'(left), 'r'(right) */
|
||||||
points?: AlignPoint[];
|
points?: (string | AlignPoint)[];
|
||||||
/**
|
/**
|
||||||
* offset source node by offset[0] in x and offset[1] in y.
|
* offset source node by offset[0] in x and offset[1] in y.
|
||||||
* If offset contains percentage string value, it is relative to sourceNode region.
|
* If offset contains percentage string value, it is relative to sourceNode region.
|
||||||
*/
|
*/
|
||||||
offset?: number[];
|
offset?: OffsetType[];
|
||||||
/**
|
/**
|
||||||
* offset target node by offset[0] in x and offset[1] in y.
|
* offset target node by offset[0] in x and offset[1] in y.
|
||||||
* If targetOffset contains percentage string value, it is relative to targetNode region.
|
* If targetOffset contains percentage string value, it is relative to targetNode region.
|
||||||
*/
|
*/
|
||||||
targetOffset?: number[];
|
targetOffset?: OffsetType[];
|
||||||
/**
|
/**
|
||||||
* If adjustX field is true, will adjust source node in x direction if source node is invisible.
|
* If adjustX field is true, will adjust source node in x direction if source node is invisible.
|
||||||
* If adjustY field is true, will adjust source node in y direction if source node is invisible.
|
* If adjustY field is true, will adjust source node in y direction if source node is invisible.
|
||||||
|
@ -28,7 +29,24 @@ export interface AlignType {
|
||||||
overflow?: {
|
overflow?: {
|
||||||
adjustX?: boolean | number;
|
adjustX?: boolean | number;
|
||||||
adjustY?: boolean | number;
|
adjustY?: boolean | number;
|
||||||
|
shiftX?: boolean | number;
|
||||||
|
shiftY?: boolean | number;
|
||||||
};
|
};
|
||||||
|
/** Auto adjust arrow position */
|
||||||
|
autoArrow?: boolean;
|
||||||
|
/**
|
||||||
|
* Config visible region check of html node. Default `visible`:
|
||||||
|
* - `visible`:
|
||||||
|
* The visible region of user browser window.
|
||||||
|
* Use `clientHeight` for check.
|
||||||
|
* If `visible` region not satisfy, fallback to `scroll`.
|
||||||
|
* - `scroll`:
|
||||||
|
* The whole region of the html scroll area.
|
||||||
|
* Use `scrollHeight` for check.
|
||||||
|
* - `visibleFirst`:
|
||||||
|
* Similar to `visible`, but if `visible` region not satisfy, fallback to `scroll`.
|
||||||
|
*/
|
||||||
|
htmlRegion?: 'visible' | 'scroll' | 'visibleFirst';
|
||||||
/**
|
/**
|
||||||
* Whether use css right instead of left to position
|
* Whether use css right instead of left to position
|
||||||
*/
|
*/
|
||||||
|
@ -122,8 +140,6 @@ export const triggerProps = () => ({
|
||||||
autoDestroy: { type: Boolean, default: false },
|
autoDestroy: { type: Boolean, default: false },
|
||||||
mobile: Object,
|
mobile: Object,
|
||||||
getTriggerDOMNode: Function as PropType<(d?: HTMLElement) => HTMLElement>,
|
getTriggerDOMNode: Function as PropType<(d?: HTMLElement) => HTMLElement>,
|
||||||
// portal context will change
|
|
||||||
tryPopPortal: Boolean, // no need reactive
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TriggerProps = Partial<ExtractPropTypes<ReturnType<typeof triggerProps>>>;
|
export type TriggerProps = Partial<ExtractPropTypes<ReturnType<typeof triggerProps>>>;
|
||||||
|
|
|
@ -256,6 +256,8 @@ declare module 'vue' {
|
||||||
AWeekPicker: typeof import('ant-design-vue')['WeekPicker'];
|
AWeekPicker: typeof import('ant-design-vue')['WeekPicker'];
|
||||||
|
|
||||||
AQRCode: typeof import('ant-design-vue')['QRCode'];
|
AQRCode: typeof import('ant-design-vue')['QRCode'];
|
||||||
|
|
||||||
|
ATour: typeof import('ant-design-vue')['Tour'];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export {};
|
export {};
|
||||||
|
|
Loading…
Reference in New Issue