mirror of
https://gitee.com/vant-contrib/vant.git
synced 2025-05-07 16:16:35 +08:00
339 lines
7.1 KiB
JavaScript
339 lines
7.1 KiB
JavaScript
// Utils
|
|
import { Teleport, Transition } from 'vue';
|
|
import { createNamespace, isDef } from '../utils';
|
|
import { on, off, preventDefault } from '../utils/dom/event';
|
|
import { getScroller } from '../utils/dom/scroll';
|
|
|
|
// Mixins
|
|
import { TouchMixin } from '../mixins/touch';
|
|
import { CloseOnPopstateMixin } from '../mixins/close-on-popstate';
|
|
|
|
// Components
|
|
import Icon from '../icon';
|
|
import Overlay from '../overlay';
|
|
|
|
const [createComponent, bem] = createNamespace('popup');
|
|
|
|
const context = {
|
|
zIndex: 2000,
|
|
lockCount: 0,
|
|
stack: [],
|
|
find(vm) {
|
|
return this.stack.filter((item) => item.vm === vm)[0];
|
|
},
|
|
};
|
|
|
|
export const popupSharedProps = {
|
|
// whether to show popup
|
|
show: Boolean,
|
|
// z-index
|
|
zIndex: [Number, String],
|
|
// transition duration
|
|
duration: [Number, String],
|
|
// teleport
|
|
teleport: [String, Object],
|
|
// overlay custom style
|
|
overlayStyle: Object,
|
|
// overlay custom class name
|
|
overlayClass: String,
|
|
// 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 click overlay
|
|
closeOnClickOverlay: {
|
|
type: Boolean,
|
|
default: true,
|
|
},
|
|
};
|
|
|
|
export default createComponent({
|
|
mixins: [TouchMixin, CloseOnPopstateMixin],
|
|
|
|
inheritAttrs: false,
|
|
|
|
props: {
|
|
...popupSharedProps,
|
|
round: Boolean,
|
|
closeable: Boolean,
|
|
transition: String,
|
|
safeAreaInsetBottom: Boolean,
|
|
closeIcon: {
|
|
type: String,
|
|
default: 'cross',
|
|
},
|
|
closeIconPosition: {
|
|
type: String,
|
|
default: 'top-right',
|
|
},
|
|
position: {
|
|
type: String,
|
|
default: 'center',
|
|
},
|
|
},
|
|
|
|
emits: [
|
|
'open',
|
|
'close',
|
|
'click',
|
|
'opened',
|
|
'closed',
|
|
'update:show',
|
|
'click-overlay',
|
|
],
|
|
|
|
data() {
|
|
return {
|
|
inited: this.show,
|
|
currentZIndex: null,
|
|
};
|
|
},
|
|
|
|
computed: {
|
|
shouldRender() {
|
|
return this.inited || !this.lazyRender;
|
|
},
|
|
},
|
|
|
|
watch: {
|
|
show(val) {
|
|
const type = val ? 'open' : 'close';
|
|
this.inited = this.inited || this.show;
|
|
this[type]();
|
|
this.$emit(type);
|
|
},
|
|
|
|
overlay: 'renderOverlay',
|
|
},
|
|
|
|
beforeCreate() {
|
|
const createEmitter = (eventName) => (event) =>
|
|
this.$emit(eventName, event);
|
|
|
|
this.onClick = createEmitter('click');
|
|
this.onOpened = createEmitter('opened');
|
|
this.onClosed = createEmitter('closed');
|
|
},
|
|
|
|
mounted() {
|
|
if (this.show) {
|
|
this.open();
|
|
}
|
|
},
|
|
|
|
/* istanbul ignore next */
|
|
activated() {
|
|
if (this.shouldReopen) {
|
|
this.$emit('update:show', true);
|
|
this.shouldReopen = false;
|
|
}
|
|
},
|
|
|
|
beforeUnmount() {
|
|
if (this.opened) {
|
|
this.removeLock();
|
|
}
|
|
},
|
|
|
|
/* istanbul ignore next */
|
|
deactivated() {
|
|
if (this.show) {
|
|
this.close();
|
|
this.shouldReopen = true;
|
|
}
|
|
},
|
|
|
|
methods: {
|
|
genOverlay() {
|
|
if (this.overlay) {
|
|
return (
|
|
<Overlay
|
|
show={this.show}
|
|
class={this.overlayClass}
|
|
style={this.overlayStyle}
|
|
zIndex={this.currentZIndex}
|
|
duration={this.duration}
|
|
onClick={this.onClickOverlay}
|
|
/>
|
|
);
|
|
}
|
|
},
|
|
|
|
genPopup() {
|
|
const { round, position, duration } = this;
|
|
const isCenter = position === 'center';
|
|
|
|
const transitionName =
|
|
this.transition ||
|
|
(isCenter ? 'van-fade' : `van-popup-slide-${position}`);
|
|
|
|
const style = {
|
|
zIndex: this.currentZIndex,
|
|
};
|
|
|
|
if (isDef(duration)) {
|
|
const key = isCenter ? 'animationDuration' : 'transitionDuration';
|
|
style[key] = `${duration}s`;
|
|
}
|
|
|
|
return (
|
|
<Transition
|
|
name={transitionName}
|
|
onAfterEnter={this.onOpened}
|
|
onAfterLeave={this.onClosed}
|
|
>
|
|
{this.shouldRender ? (
|
|
<div
|
|
vShow={this.show}
|
|
style={style}
|
|
class={bem({
|
|
round,
|
|
[position]: position,
|
|
'safe-area-inset-bottom': this.safeAreaInsetBottom,
|
|
})}
|
|
onClick={this.onClick}
|
|
{...this.$attrs}
|
|
>
|
|
{this.$slots.default?.()}
|
|
{this.closeable && (
|
|
<Icon
|
|
role="button"
|
|
tabindex="0"
|
|
name={this.closeIcon}
|
|
class={bem('close-icon', this.closeIconPosition)}
|
|
onClick={this.close}
|
|
/>
|
|
)}
|
|
</div>
|
|
) : null}
|
|
</Transition>
|
|
);
|
|
},
|
|
|
|
open() {
|
|
/* istanbul ignore next */
|
|
if (this.$isServer || this.opened) {
|
|
return;
|
|
}
|
|
|
|
// cover default zIndex
|
|
if (this.zIndex !== undefined) {
|
|
context.zIndex = this.zIndex;
|
|
}
|
|
|
|
this.opened = true;
|
|
this.renderOverlay();
|
|
this.addLock();
|
|
},
|
|
|
|
addLock() {
|
|
if (this.lockScroll) {
|
|
on(document, 'touchstart', this.touchStart);
|
|
on(document, 'touchmove', this.onTouchMove);
|
|
|
|
if (!context.lockCount) {
|
|
document.body.classList.add('van-overflow-hidden');
|
|
}
|
|
context.lockCount++;
|
|
}
|
|
},
|
|
|
|
removeLock() {
|
|
if (this.lockScroll && context.lockCount) {
|
|
context.lockCount--;
|
|
off(document, 'touchstart', this.touchStart);
|
|
off(document, 'touchmove', this.onTouchMove);
|
|
|
|
if (!context.lockCount) {
|
|
document.body.classList.remove('van-overflow-hidden');
|
|
}
|
|
}
|
|
},
|
|
|
|
close() {
|
|
if (!this.opened) {
|
|
return;
|
|
}
|
|
|
|
this.opened = false;
|
|
this.removeLock();
|
|
this.$emit('update:show', false);
|
|
},
|
|
|
|
onTouchMove(event) {
|
|
this.touchMove(event);
|
|
const direction = this.deltaY > 0 ? '10' : '01';
|
|
const el = getScroller(event.target, this.$refs.root);
|
|
const { scrollHeight, offsetHeight, scrollTop } = el;
|
|
let status = '11';
|
|
|
|
/* istanbul ignore next */
|
|
if (scrollTop === 0) {
|
|
status = offsetHeight >= scrollHeight ? '00' : '01';
|
|
} else if (scrollTop + offsetHeight >= scrollHeight) {
|
|
status = '10';
|
|
}
|
|
|
|
/* istanbul ignore next */
|
|
if (
|
|
status !== '11' &&
|
|
this.direction === 'vertical' &&
|
|
!(parseInt(status, 2) & parseInt(direction, 2))
|
|
) {
|
|
preventDefault(event, true);
|
|
}
|
|
},
|
|
|
|
onClickOverlay() {
|
|
this.$emit('click-overlay');
|
|
|
|
if (this.closeOnClickOverlay) {
|
|
this.close();
|
|
}
|
|
},
|
|
|
|
renderOverlay() {
|
|
if (this.$isServer || !this.show) {
|
|
return;
|
|
}
|
|
|
|
this.$nextTick(() => {
|
|
this.updateZIndex(this.overlay ? 1 : 0);
|
|
});
|
|
},
|
|
|
|
updateZIndex(value = 0) {
|
|
this.currentZIndex = ++context.zIndex + value;
|
|
},
|
|
},
|
|
|
|
render() {
|
|
if (this.teleport) {
|
|
return (
|
|
<Teleport to={this.teleport}>
|
|
{this.genOverlay()}
|
|
{this.genPopup()}
|
|
</Teleport>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<>
|
|
{this.genOverlay()}
|
|
{this.genPopup()}
|
|
</>
|
|
);
|
|
},
|
|
});
|