// Utils import { ref, watch, Teleport, computed, PropType, onMounted, Transition, onActivated, CSSProperties, TeleportProps, onDeactivated, onBeforeUnmount, } from 'vue'; import { createNamespace, isDef, UnknownProp } from '../utils'; // Composition import { useEventListener } from '@vant/use'; import { useExpose } from '../composables/use-expose'; import { useLockScroll } from '../composables/use-lock-scroll'; import { useLazyRender } from '../composables/use-lazy-render'; // Components import Icon from '../icon'; import Overlay from '../overlay'; export type PopupPosition = 'top' | 'left' | 'bottom' | 'right' | 'center' | ''; export type PopupCloseIconPosition = | 'top-left' | 'top-right' | 'botttom-left' | 'bottom-right'; const [createComponent, bem] = createNamespace('popup'); let globalZIndex = 2000; export const popupSharedProps = { // whether to show popup show: Boolean, // z-index zIndex: [Number, String], // transition duration duration: [Number, String], // teleport teleport: [String, Object] as PropType, // overlay custom style overlayStyle: Object as PropType, // overlay custom class name overlayClass: UnknownProp, // Initial rendering animation transitionAppear: Boolean, // whether to show overlay overlay: { type: Boolean, default: true, }, // prevent body scroll lockScroll: { type: Boolean, default: true, }, // whether to lazy render lazyRender: { type: Boolean, default: true, }, // whether to close popup when overlay is clicked closeOnClickOverlay: { type: Boolean, default: true, }, }; export default createComponent({ inheritAttrs: false, props: { ...popupSharedProps, round: Boolean, closeable: Boolean, transition: String, closeOnPopstate: Boolean, safeAreaInsetBottom: Boolean, position: { type: String as PropType, default: 'center', }, closeIcon: { type: String, default: 'cross', }, closeIconPosition: { type: String as PropType, default: 'top-right', }, }, emits: [ 'open', 'close', 'click', 'opened', 'closed', 'update:show', 'click-overlay', 'click-close-icon', ], setup(props, { emit, attrs, slots }) { let opened: boolean; let shouldReopen: boolean; const zIndex = ref(); const popupRef = ref(); const [lockScroll, unlockScroll] = useLockScroll( popupRef, () => props.lockScroll ); const lazyRender = useLazyRender(() => props.show || !props.lazyRender); const style = computed(() => { const style: CSSProperties = { zIndex: zIndex.value, }; if (isDef(props.duration)) { const key = props.position === 'center' ? 'animationDuration' : 'transitionDuration'; style[key] = `${props.duration}s`; } return style; }); const open = () => { if (!opened) { if (props.zIndex !== undefined) { globalZIndex = +props.zIndex; } opened = true; lockScroll(); zIndex.value = ++globalZIndex; } }; const close = () => { if (opened) { opened = false; unlockScroll(); emit('update:show', false); } }; const onClickOverlay = (event: MouseEvent) => { emit('click-overlay', event); if (props.closeOnClickOverlay) { close(); } }; const renderOverlay = () => { if (props.overlay) { return ( ); } }; const onClickCloseIcon = (event: MouseEvent) => { emit('click-close-icon', event); close(); }; const renderCloseIcon = () => { if (props.closeable) { return ( ); } }; const onClick = (event: MouseEvent) => emit('click', event); const onOpened = () => emit('opened'); const onClosed = () => emit('closed'); const renderPopup = lazyRender(() => { const { round, position, safeAreaInsetBottom } = props; return (
{slots.default?.()} {renderCloseIcon()}
); }); const renderTransition = () => { const { position, transition, transitionAppear } = props; const name = position === 'center' ? 'van-fade' : `van-popup-slide-${position}`; return ( {renderPopup()} ); }; watch( () => props.show, (value) => { if (value) { open(); emit('open'); } else { close(); emit('close'); } } ); useExpose({ popupRef }); useEventListener('popstate', () => { if (props.closeOnPopstate) { close(); shouldReopen = false; } }); onMounted(() => { if (props.show) { open(); } }); onActivated(() => { if (shouldReopen) { emit('update:show', true); shouldReopen = false; } }); onDeactivated(() => { if (props.show) { close(); shouldReopen = true; } }); onBeforeUnmount(() => { if (opened) { unlockScroll(); } }); return () => { if (props.teleport) { return ( {renderOverlay()} {renderTransition()} ); } return ( <> {renderOverlay()} {renderTransition()} ); }; }, });