feat: Popup component
This commit is contained in:
@@ -0,0 +1,138 @@
|
||||
# Popup
|
||||
|
||||
### Install
|
||||
|
||||
```js
|
||||
import Vue from 'vue';
|
||||
import { Popup } from 'vant';
|
||||
|
||||
Vue.use(Popup);
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```html
|
||||
<van-cell is-link @click="showPopup">Show Popup</van-cell>
|
||||
<van-popup v-model:show="show">Content</van-popup>
|
||||
```
|
||||
|
||||
```js
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
show: false,
|
||||
};
|
||||
},
|
||||
|
||||
methods: {
|
||||
showPopup() {
|
||||
this.show = true;
|
||||
},
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### Position
|
||||
|
||||
Use `position` prop to set popup display position
|
||||
|
||||
```html
|
||||
<van-popup v-model:show="show" position="top" :style="{ height: '30%' }" />
|
||||
```
|
||||
|
||||
### Close Icon
|
||||
|
||||
```html
|
||||
<van-popup
|
||||
v-model:show="show"
|
||||
closeable
|
||||
position="bottom"
|
||||
:style="{ height: '30%' }"
|
||||
/>
|
||||
<!-- Custom Icon -->
|
||||
<van-popup
|
||||
v-model:show="show"
|
||||
closeable
|
||||
close-icon="close"
|
||||
position="bottom"
|
||||
:style="{ height: '30%' }"
|
||||
/>
|
||||
<!-- Icon Position -->
|
||||
<van-popup
|
||||
v-model:show="show"
|
||||
closeable
|
||||
close-icon-position="top-left"
|
||||
position="bottom"
|
||||
:style="{ height: '30%' }"
|
||||
/>
|
||||
```
|
||||
|
||||
### Round Corner
|
||||
|
||||
```html
|
||||
<van-popup v-model:show="show" round position="bottom" :style="{ height: '30%' }" />
|
||||
```
|
||||
|
||||
### Get Container
|
||||
|
||||
Use `get-container` prop to specify mount location
|
||||
|
||||
```html
|
||||
<!-- mount to body -->
|
||||
<van-popup v-model:show="show" get-container="body" />
|
||||
|
||||
<!-- mount to #app -->
|
||||
<van-popup v-model:show="show" get-container="#app" />
|
||||
|
||||
<!-- Specify the mount location by function -->
|
||||
<van-popup v-model:show="show" :get-container="getContainer" />
|
||||
```
|
||||
|
||||
```js
|
||||
export default {
|
||||
methods: {
|
||||
getContainer() {
|
||||
return document.querySelector('.my-container');
|
||||
},
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
> Tips: The get-container prop cannot be used on the root node
|
||||
|
||||
## API
|
||||
|
||||
### Props
|
||||
|
||||
| Attribute | Description | Type | Default |
|
||||
| --- | --- | --- | --- |
|
||||
| v-model:show | Whether to show popup | _boolean_ | `false` |
|
||||
| overlay | Whether to show overlay | _boolean_ | `true` |
|
||||
| position | Can be set to `top` `bottom` `right` `left` | _string_ | `center` |
|
||||
| overlay-class | Custom overlay class | _string_ | - |
|
||||
| overlay-style | Custom overlay style | _object_ | - |
|
||||
| duration | Transition duration, unit second | _number \| string_ | `0.3` |
|
||||
| round `v2.0.7` | Whether to show round corner | _boolean_ | `false` |
|
||||
| lock-scroll | Whether to lock background scroll | _boolean_ | `true` |
|
||||
| lazy-render | Whether to lazy render util appeared | _boolean_ | `true` |
|
||||
| close-on-popstate `v2.2.10` | Whether to close when popstate | _boolean_ | `false` |
|
||||
| close-on-click-overlay | Whether to close when click overlay | _boolean_ | `true` |
|
||||
| closeable `v2.2.0` | Whether to show close icon | _boolean_ | `false` |
|
||||
| close-icon `v2.2.0` | Close icon name | _string_ | `cross` |
|
||||
| close-icon-position `v2.2.2` | Close Icon Position,can be set to `top-left` `bottom-left` `bottom-right` | _string_ | `top-right` |
|
||||
| transition | Transition, equivalent to `name` prop of [transtion](https://vuejs.org/v2/api/#transition) | _string_ | - |
|
||||
| get-container | Return the mount node for Popup | _string \| () => Element_ | - |
|
||||
| safe-area-inset-bottom `v2.2.1` | Whether to enable bottom safe area adaptation | _boolean_ | `false` |
|
||||
|
||||
### Events
|
||||
|
||||
| Event | Description | Arguments |
|
||||
| ------------- | ---------------------------- | -------------- |
|
||||
| click | Triggered when click Popup | _event: Event_ |
|
||||
| open | Triggered when open Popup | - |
|
||||
| close | Triggered when close Popup | - |
|
||||
| opened | Triggered when opened Popup | - |
|
||||
| closed | Triggered when closed Popup | - |
|
||||
| click-overlay | Triggered when click overlay | - |
|
||||
@@ -0,0 +1,149 @@
|
||||
# Popup 弹出层
|
||||
|
||||
### 介绍
|
||||
|
||||
弹出层容器,用于展示弹窗、信息提示等内容,支持多个弹出层叠加展示
|
||||
|
||||
### 引入
|
||||
|
||||
```js
|
||||
import Vue from 'vue';
|
||||
import { Popup } from 'vant';
|
||||
|
||||
Vue.use(Popup);
|
||||
```
|
||||
|
||||
## 代码演示
|
||||
|
||||
### 基础用法
|
||||
|
||||
通过 `v-model:show` 控制弹出层是否展示
|
||||
|
||||
```html
|
||||
<van-cell is-link @click="showPopup">展示弹出层</van-cell>
|
||||
<van-popup v-model:show="show">内容</van-popup>
|
||||
```
|
||||
|
||||
```js
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
show: false,
|
||||
};
|
||||
},
|
||||
|
||||
methods: {
|
||||
showPopup() {
|
||||
this.show = true;
|
||||
},
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### 弹出位置
|
||||
|
||||
通过`position`属性设置弹出位置,默认居中弹出,可以设置为`top`、`bottom`、`left`、`right`
|
||||
|
||||
```html
|
||||
<van-popup v-model:show="show" position="top" :style="{ height: '30%' }" />
|
||||
```
|
||||
|
||||
### 关闭图标
|
||||
|
||||
设置`closeable`属性后,会在弹出层的右上角显示关闭图标,并且可以通过`close-icon`属性自定义图标,使用`close-icon-position`属性可以自定义图标位置
|
||||
|
||||
```html
|
||||
<van-popup
|
||||
v-model:show="show"
|
||||
closeable
|
||||
position="bottom"
|
||||
:style="{ height: '30%' }"
|
||||
/>
|
||||
<!-- 自定义图标 -->
|
||||
<van-popup
|
||||
v-model:show="show"
|
||||
closeable
|
||||
close-icon="close"
|
||||
position="bottom"
|
||||
:style="{ height: '30%' }"
|
||||
/>
|
||||
<!-- 图标位置 -->
|
||||
<van-popup
|
||||
v-model:show="show"
|
||||
closeable
|
||||
close-icon-position="top-left"
|
||||
position="bottom"
|
||||
:style="{ height: '30%' }"
|
||||
/>
|
||||
```
|
||||
|
||||
### 圆角弹窗
|
||||
|
||||
设置`round`属性后,弹窗会根据弹出位置添加不同的圆角样式
|
||||
|
||||
```html
|
||||
<van-popup v-model:show="show" round position="bottom" :style="{ height: '30%' }" />
|
||||
```
|
||||
|
||||
### 指定挂载位置
|
||||
|
||||
弹出层默认挂载到组件所在位置,可以通过`get-container`属性指定挂载位置
|
||||
|
||||
```html
|
||||
<!-- 挂载到 body 节点下 -->
|
||||
<van-popup v-model:show="show" get-container="body" />
|
||||
|
||||
<!-- 挂载到 #app 节点下 -->
|
||||
<van-popup v-model:show="show" get-container="#app" />
|
||||
|
||||
<!-- 通过函数指定挂载位置 -->
|
||||
<van-popup v-model:show="show" :get-container="getContainer" />
|
||||
```
|
||||
|
||||
```js
|
||||
export default {
|
||||
methods: {
|
||||
// 返回一个特定的 DOM 节点,作为挂载的父节点
|
||||
getContainer() {
|
||||
return document.querySelector('.my-container');
|
||||
},
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
> 注意:使用 get-container 属性的组件不能为根节点
|
||||
|
||||
## API
|
||||
|
||||
### Props
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 |
|
||||
| --- | --- | --- | --- |
|
||||
| v-model:show | 是否显示弹出层 | _boolean_ | `false` |
|
||||
| overlay | 是否显示遮罩层 | _boolean_ | `true` |
|
||||
| position | 弹出位置,可选值为 `top` `bottom` `right` `left` | _string_ | `center` |
|
||||
| overlay-class | 自定义遮罩层类名 | _string_ | - |
|
||||
| overlay-style | 自定义遮罩层样式 | _object_ | - |
|
||||
| duration | 动画时长,单位秒 | _number \| string_ | `0.3` |
|
||||
| round `v2.0.7` | 是否显示圆角 | _boolean_ | `false` |
|
||||
| lock-scroll | 是否锁定背景滚动 | _boolean_ | `true` |
|
||||
| lazy-render | 是否在显示弹层时才渲染节点 | _boolean_ | `true` |
|
||||
| close-on-popstate `v2.2.10` | 是否在页面回退时自动关闭 | _boolean_ | `false` |
|
||||
| close-on-click-overlay | 是否在点击遮罩层后关闭 | _boolean_ | `true` |
|
||||
| closeable `v2.2.0` | 是否显示关闭图标 | _boolean_ | `false` |
|
||||
| close-icon `v2.2.0` | 关闭图标名称或图片链接 | _string_ | `cross` |
|
||||
| close-icon-position `v2.2.2` | 关闭图标位置,可选值为`top-left`<br>`bottom-left` `bottom-right` | _string_ | `top-right` |
|
||||
| transition | 动画类名,等价于 [transtion](https://cn.vuejs.org/v2/api/index.html#transition) 的`name`属性 | _string_ | - |
|
||||
| get-container | 指定挂载的节点 | _string \| () => Element_ | - |
|
||||
| safe-area-inset-bottom `v2.2.1` | 是否开启[底部安全区适配](#/zh-CN/quickstart#di-bu-an-quan-qu-gua-pei) | _boolean_ | `false` |
|
||||
|
||||
### Events
|
||||
|
||||
| 事件名 | 说明 | 回调参数 |
|
||||
| ------------- | -------------------------- | -------------- |
|
||||
| click | 点击弹出层时触发 | _event: Event_ |
|
||||
| open | 打开弹出层时触发 | - |
|
||||
| close | 关闭弹出层时触发 | - |
|
||||
| opened | 打开弹出层且动画结束后触发 | - |
|
||||
| closed | 关闭弹出层且动画结束后触发 | - |
|
||||
| click-overlay | 点击遮罩层时触发 | - |
|
||||
@@ -0,0 +1,164 @@
|
||||
<template>
|
||||
<demo-section>
|
||||
<demo-block card :title="t('basicUsage')">
|
||||
<van-cell :title="t('buttonBasic')" is-link @click="showBasic = true" />
|
||||
<van-popup v-model:show="showBasic" :style="{ padding: '30px 50px' }">
|
||||
{{ t('content') }}
|
||||
</van-popup>
|
||||
</demo-block>
|
||||
|
||||
<demo-block card :title="t('position')">
|
||||
<van-cell :title="t('buttonTop')" is-link @click="showTop = true" />
|
||||
<van-cell :title="t('buttonBottom')" is-link @click="showBottom = true" />
|
||||
<van-cell :title="t('buttonLeft')" is-link @click="showLeft = true" />
|
||||
<van-cell :title="t('buttonRight')" is-link @click="showRight = true" />
|
||||
|
||||
<van-popup v-model:show="showTop" position="top" :style="{ height: '30%' }" />
|
||||
<van-popup
|
||||
v-model:show="showBottom"
|
||||
position="bottom"
|
||||
:style="{ height: '30%' }"
|
||||
/>
|
||||
<van-popup
|
||||
v-model:show="showLeft"
|
||||
position="left"
|
||||
:style="{ width: '30%', height: '100%' }"
|
||||
/>
|
||||
<van-popup
|
||||
v-model:show="showRight"
|
||||
position="right"
|
||||
:style="{ width: '30%', height: '100%' }"
|
||||
/>
|
||||
</demo-block>
|
||||
|
||||
<demo-block card :title="t('closeIcon')">
|
||||
<van-cell :title="t('closeIcon')" is-link @click="showCloseIcon = true" />
|
||||
<van-cell
|
||||
:title="t('customCloseIcon')"
|
||||
is-link
|
||||
@click="showCustomCloseIcon = true"
|
||||
/>
|
||||
<van-cell
|
||||
:title="t('customIconPosition')"
|
||||
is-link
|
||||
@click="showCustomIconPosition = true"
|
||||
/>
|
||||
|
||||
<van-popup
|
||||
v-model:show="showCloseIcon"
|
||||
closeable
|
||||
position="bottom"
|
||||
:style="{ height: '30%' }"
|
||||
/>
|
||||
<van-popup
|
||||
v-model:show="showCustomCloseIcon"
|
||||
closeable
|
||||
close-icon="close"
|
||||
position="bottom"
|
||||
:style="{ height: '30%' }"
|
||||
/>
|
||||
<van-popup
|
||||
v-model:show="showCustomIconPosition"
|
||||
closeable
|
||||
close-icon-position="top-left"
|
||||
position="bottom"
|
||||
:style="{ height: '30%' }"
|
||||
/>
|
||||
</demo-block>
|
||||
|
||||
<demo-block card :title="t('roundCorner')">
|
||||
<van-cell
|
||||
:title="t('roundCorner')"
|
||||
is-link
|
||||
@click="showRoundCorner = true"
|
||||
/>
|
||||
<van-popup
|
||||
v-model:show="showRoundCorner"
|
||||
round
|
||||
position="bottom"
|
||||
:style="{ height: '30%' }"
|
||||
/>
|
||||
</demo-block>
|
||||
|
||||
<demo-block card v-if="!isWeapp" :title="t('getContainer')">
|
||||
<van-cell
|
||||
:title="t('getContainer')"
|
||||
is-link
|
||||
@click="showGetContainer = true"
|
||||
/>
|
||||
<van-popup
|
||||
v-model:show="showGetContainer"
|
||||
get-container="body"
|
||||
:style="{ padding: '30px 50px' }"
|
||||
/>
|
||||
</demo-block>
|
||||
</demo-section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
i18n: {
|
||||
'zh-CN': {
|
||||
position: '弹出位置',
|
||||
buttonBasic: '展示弹出层',
|
||||
buttonTop: '顶部弹出',
|
||||
buttonBottom: '底部弹出',
|
||||
buttonLeft: '左侧弹出',
|
||||
buttonRight: '右侧弹出',
|
||||
getContainer: '指定挂载节点',
|
||||
roundCorner: '圆角弹窗',
|
||||
closeIcon: '关闭图标',
|
||||
customCloseIcon: '自定义图标',
|
||||
customIconPosition: '图标位置',
|
||||
},
|
||||
'en-US': {
|
||||
position: 'Position',
|
||||
buttonBasic: 'Show Popup',
|
||||
buttonTop: 'From Top',
|
||||
buttonBottom: 'From Bottom',
|
||||
buttonLeft: 'From Left',
|
||||
buttonRight: 'From Right',
|
||||
getContainer: 'Get Container',
|
||||
roundCorner: 'Round Corner',
|
||||
closeIcon: 'Close Icon',
|
||||
customCloseIcon: 'Custom Icon',
|
||||
customIconPosition: 'Icon Position',
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
showBasic: false,
|
||||
showTop: false,
|
||||
showBottom: false,
|
||||
showLeft: false,
|
||||
showRight: false,
|
||||
showCloseIcon: false,
|
||||
showRoundCorner: false,
|
||||
showGetContainer: false,
|
||||
showCustomCloseIcon: false,
|
||||
showCustomIconPosition: false,
|
||||
};
|
||||
},
|
||||
|
||||
watch: {
|
||||
showBasic(val) {
|
||||
console.log('showBasic', val);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
@import '../../style/var';
|
||||
|
||||
.demo-popup {
|
||||
.van-row {
|
||||
margin-bottom: @padding-md;
|
||||
}
|
||||
|
||||
.van-button {
|
||||
margin-left: @padding-md;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,106 @@
|
||||
import { Transition } from 'vue';
|
||||
import { createNamespace, isDef } from '../utils';
|
||||
import { PopupMixin } from '../mixins/popup';
|
||||
import Icon from '../icon';
|
||||
import Overlay from '../overlay';
|
||||
|
||||
const [createComponent, bem] = createNamespace('popup');
|
||||
|
||||
export default createComponent({
|
||||
mixins: [PopupMixin()],
|
||||
|
||||
inheritAttrs: false,
|
||||
|
||||
props: {
|
||||
round: Boolean,
|
||||
duration: [Number, String],
|
||||
closeable: Boolean,
|
||||
transition: String,
|
||||
safeAreaInsetBottom: Boolean,
|
||||
closeIcon: {
|
||||
type: String,
|
||||
default: 'cross',
|
||||
},
|
||||
closeIconPosition: {
|
||||
type: String,
|
||||
default: 'top-right',
|
||||
},
|
||||
position: {
|
||||
type: String,
|
||||
default: 'center',
|
||||
},
|
||||
overlay: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
closeOnClickOverlay: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
|
||||
beforeCreate() {
|
||||
const createEmitter = (eventName) => (event) =>
|
||||
this.$emit(eventName, event);
|
||||
|
||||
this.onClick = createEmitter('click');
|
||||
this.onOpened = createEmitter('opened');
|
||||
this.onClosed = createEmitter('closed');
|
||||
},
|
||||
|
||||
render() {
|
||||
if (!this.shouldRender) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { round, position, duration } = this;
|
||||
const isCenter = position === 'center';
|
||||
|
||||
const transitionName =
|
||||
this.transition ||
|
||||
(isCenter ? 'van-fade' : `van-popup-slide-${position}`);
|
||||
|
||||
const style = {};
|
||||
if (isDef(duration)) {
|
||||
const key = isCenter ? 'animationDuration' : 'transitionDuration';
|
||||
style[key] = `${duration}s`;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{this.overlay && (
|
||||
<Overlay show={this.show} onClick={this.onClickOverlay} />
|
||||
)}
|
||||
<Transition
|
||||
name={transitionName}
|
||||
onAfterEnter={this.onOpened}
|
||||
onAfterLeave={this.onClosed}
|
||||
>
|
||||
<div
|
||||
vShow={this.show}
|
||||
ref="root"
|
||||
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>
|
||||
</Transition>
|
||||
</>
|
||||
);
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,137 @@
|
||||
@import '../style/var';
|
||||
|
||||
.van {
|
||||
&-overflow-hidden {
|
||||
overflow: hidden !important;
|
||||
}
|
||||
|
||||
&-popup {
|
||||
position: fixed;
|
||||
max-height: 100%;
|
||||
overflow-y: auto;
|
||||
background-color: @popup-background-color;
|
||||
transition: @popup-transition;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
|
||||
&--center {
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate3d(-50%, -50%, 0);
|
||||
|
||||
&.van-popup--round {
|
||||
border-radius: @popup-round-border-radius;
|
||||
}
|
||||
}
|
||||
|
||||
&--top {
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
|
||||
&.van-popup--round {
|
||||
border-radius: 0 0 @popup-round-border-radius @popup-round-border-radius;
|
||||
}
|
||||
}
|
||||
|
||||
&--right {
|
||||
top: 50%;
|
||||
right: 0;
|
||||
transform: translate3d(0, -50%, 0);
|
||||
|
||||
&.van-popup--round {
|
||||
border-radius: @popup-round-border-radius 0 0 @popup-round-border-radius;
|
||||
}
|
||||
}
|
||||
|
||||
&--bottom {
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
|
||||
&.van-popup--round {
|
||||
border-radius: @popup-round-border-radius @popup-round-border-radius 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
&--left {
|
||||
top: 50%;
|
||||
left: 0;
|
||||
transform: translate3d(0, -50%, 0);
|
||||
|
||||
&.van-popup--round {
|
||||
border-radius: 0 @popup-round-border-radius @popup-round-border-radius 0;
|
||||
}
|
||||
}
|
||||
|
||||
&--safe-area-inset-bottom {
|
||||
padding-bottom: constant(safe-area-inset-bottom);
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
}
|
||||
|
||||
&-slide-top-enter-active,
|
||||
&-slide-left-enter-active,
|
||||
&-slide-right-enter-active,
|
||||
&-slide-bottom-enter-active {
|
||||
transition-timing-function: ease-out;
|
||||
}
|
||||
|
||||
&-slide-top-leave-active,
|
||||
&-slide-left-leave-active,
|
||||
&-slide-right-leave-active,
|
||||
&-slide-bottom-leave-active {
|
||||
transition-timing-function: ease-in;
|
||||
}
|
||||
|
||||
&-slide-top-enter,
|
||||
&-slide-top-leave-active {
|
||||
transform: translate3d(0, -100%, 0);
|
||||
}
|
||||
|
||||
&-slide-right-enter,
|
||||
&-slide-right-leave-active {
|
||||
transform: translate3d(100%, -50%, 0);
|
||||
}
|
||||
|
||||
&-slide-bottom-enter,
|
||||
&-slide-bottom-leave-active {
|
||||
transform: translate3d(0, 100%, 0);
|
||||
}
|
||||
|
||||
&-slide-left-enter,
|
||||
&-slide-left-leave-active {
|
||||
transform: translate3d(-100%, -50%, 0);
|
||||
}
|
||||
|
||||
&__close-icon {
|
||||
position: absolute;
|
||||
z-index: @popup-close-icon-z-index;
|
||||
color: @popup-close-icon-color;
|
||||
font-size: @popup-close-icon-size;
|
||||
cursor: pointer;
|
||||
|
||||
&:active {
|
||||
color: @popup-close-icon-active-color;
|
||||
}
|
||||
|
||||
&--top-left {
|
||||
top: @popup-close-icon-margin;
|
||||
left: @popup-close-icon-margin;
|
||||
}
|
||||
|
||||
&--top-right {
|
||||
top: @popup-close-icon-margin;
|
||||
right: @popup-close-icon-margin;
|
||||
}
|
||||
|
||||
&--bottom-left {
|
||||
bottom: @popup-close-icon-margin;
|
||||
left: @popup-close-icon-margin;
|
||||
}
|
||||
|
||||
&--bottom-right {
|
||||
right: @popup-close-icon-margin;
|
||||
bottom: @popup-close-icon-margin;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders demo correctly 1`] = `
|
||||
<div>
|
||||
<div>
|
||||
<div role="button" tabindex="0" class="van-cell van-cell--clickable">
|
||||
<div class="van-cell__title"><span>展示弹出层</span></div><i class="van-icon van-icon-arrow van-cell__right-icon">
|
||||
<!----></i>
|
||||
</div>
|
||||
<!---->
|
||||
</div>
|
||||
<div>
|
||||
<div role="button" tabindex="0" class="van-cell van-cell--clickable">
|
||||
<div class="van-cell__title"><span>顶部弹出</span></div><i class="van-icon van-icon-arrow van-cell__right-icon">
|
||||
<!----></i>
|
||||
</div>
|
||||
<div role="button" tabindex="0" class="van-cell van-cell--clickable">
|
||||
<div class="van-cell__title"><span>底部弹出</span></div><i class="van-icon van-icon-arrow van-cell__right-icon">
|
||||
<!----></i>
|
||||
</div>
|
||||
<div role="button" tabindex="0" class="van-cell van-cell--clickable">
|
||||
<div class="van-cell__title"><span>左侧弹出</span></div><i class="van-icon van-icon-arrow van-cell__right-icon">
|
||||
<!----></i>
|
||||
</div>
|
||||
<div role="button" tabindex="0" class="van-cell van-cell--clickable">
|
||||
<div class="van-cell__title"><span>右侧弹出</span></div><i class="van-icon van-icon-arrow van-cell__right-icon">
|
||||
<!----></i>
|
||||
</div>
|
||||
<!---->
|
||||
<!---->
|
||||
<!---->
|
||||
<!---->
|
||||
</div>
|
||||
<div>
|
||||
<div role="button" tabindex="0" class="van-cell van-cell--clickable">
|
||||
<div class="van-cell__title"><span>关闭图标</span></div><i class="van-icon van-icon-arrow van-cell__right-icon">
|
||||
<!----></i>
|
||||
</div>
|
||||
<div role="button" tabindex="0" class="van-cell van-cell--clickable">
|
||||
<div class="van-cell__title"><span>自定义图标</span></div><i class="van-icon van-icon-arrow van-cell__right-icon">
|
||||
<!----></i>
|
||||
</div>
|
||||
<div role="button" tabindex="0" class="van-cell van-cell--clickable">
|
||||
<div class="van-cell__title"><span>图标位置</span></div><i class="van-icon van-icon-arrow van-cell__right-icon">
|
||||
<!----></i>
|
||||
</div>
|
||||
<!---->
|
||||
<!---->
|
||||
<!---->
|
||||
</div>
|
||||
<div>
|
||||
<div role="button" tabindex="0" class="van-cell van-cell--clickable">
|
||||
<div class="van-cell__title"><span>圆角弹窗</span></div><i class="van-icon van-icon-arrow van-cell__right-icon">
|
||||
<!----></i>
|
||||
</div>
|
||||
<!---->
|
||||
</div>
|
||||
<div>
|
||||
<div role="button" tabindex="0" class="van-cell van-cell--clickable">
|
||||
<div class="van-cell__title"><span>指定挂载节点</span></div><i class="van-icon van-icon-arrow van-cell__right-icon">
|
||||
<!----></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -0,0 +1,14 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`close-icon prop 1`] = `
|
||||
<div class="van-popup van-popup--center" name="van-fade"><i role="button" tabindex="0" class="van-icon van-icon-success van-popup__close-icon van-popup__close-icon--top-right">
|
||||
<!----></i></div>
|
||||
`;
|
||||
|
||||
exports[`duration prop when position is center 1`] = `<div class="van-popup van-popup--center" style="animation-duration: 0.5s;" name="van-fade"></div>`;
|
||||
|
||||
exports[`duration prop when position is top 1`] = `<div class="van-popup van-popup--top" style="transition-duration: 0.5s;" name="van-popup-slide-top"></div>`;
|
||||
|
||||
exports[`reset z-index 1`] = `<div class="van-popup van-popup--center" name="van-fade"></div>`;
|
||||
|
||||
exports[`round prop 1`] = `<div class="van-popup van-popup--round van-popup--center" name="van-fade"></div>`;
|
||||
@@ -0,0 +1,4 @@
|
||||
import Demo from '../demo';
|
||||
import { snapshotDemo } from '../../../test/demo';
|
||||
|
||||
snapshotDemo(Demo);
|
||||
@@ -0,0 +1,265 @@
|
||||
import Popup from '..';
|
||||
import { mount, triggerDrag, later } from '../../../test';
|
||||
|
||||
let wrapper;
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
});
|
||||
|
||||
test('lazy render', () => {
|
||||
wrapper = mount(Popup);
|
||||
expect(wrapper.vm.$el.tagName).toBeFalsy();
|
||||
wrapper.vm.value = true;
|
||||
expect(wrapper.vm.$el.tagName).toBeTruthy();
|
||||
});
|
||||
|
||||
test('reset z-index', () => {
|
||||
wrapper = mount(Popup, {
|
||||
propsData: {
|
||||
value: true,
|
||||
zIndex: 10,
|
||||
lockScroll: false,
|
||||
},
|
||||
});
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('popup lock scroll', () => {
|
||||
const wrapper1 = mount(Popup, {
|
||||
propsData: {
|
||||
value: true,
|
||||
},
|
||||
});
|
||||
expect(document.body.classList.contains('van-overflow-hidden')).toBeTruthy();
|
||||
triggerDrag(document, 0, 100);
|
||||
triggerDrag(document, 0, -150);
|
||||
|
||||
const wrapper2 = mount(Popup, {
|
||||
propsData: {
|
||||
value: true,
|
||||
},
|
||||
});
|
||||
wrapper1.vm.$destroy();
|
||||
expect(document.body.classList.contains('van-overflow-hidden')).toBeTruthy();
|
||||
|
||||
wrapper2.vm.$destroy();
|
||||
expect(document.body.classList.contains('van-overflow-hidden')).toBeFalsy();
|
||||
});
|
||||
|
||||
test('get container with parent', () => {
|
||||
const div1 = document.createElement('div');
|
||||
const div2 = document.createElement('div');
|
||||
wrapper = mount({
|
||||
template: `
|
||||
<div>
|
||||
<popup :value="true" :get-container="getContainer" />
|
||||
</div>
|
||||
`,
|
||||
components: {
|
||||
Popup,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
getContainer: () => div1,
|
||||
};
|
||||
},
|
||||
});
|
||||
const popup = wrapper.find('.van-popup').element;
|
||||
|
||||
expect(popup.parentNode).toEqual(div1);
|
||||
wrapper.vm.getContainer = () => div2;
|
||||
expect(popup.parentNode).toEqual(div2);
|
||||
wrapper.vm.getContainer = null;
|
||||
expect(popup.parentNode).toEqual(wrapper.element);
|
||||
});
|
||||
|
||||
test('get container with selector', () => {
|
||||
wrapper = mount({
|
||||
template: `
|
||||
<div>
|
||||
<popup class="get-container-selector-1" :value="true" get-container="body"></popup>
|
||||
<popup class="get-container-selector-2" :value="true" get-container="unknown"></popup>
|
||||
</div>
|
||||
`,
|
||||
components: {
|
||||
Popup,
|
||||
},
|
||||
});
|
||||
|
||||
const dom1 = document.querySelector('.get-container-selector-1');
|
||||
const dom2 = wrapper.vm.$el.querySelector('.get-container-selector-2');
|
||||
|
||||
expect(dom1.parentNode).toEqual(document.body);
|
||||
expect(dom2.parentNode).toEqual(wrapper.vm.$el);
|
||||
});
|
||||
|
||||
test('render overlay', async () => {
|
||||
const div = document.createElement('div');
|
||||
wrapper = mount({
|
||||
template: `
|
||||
<div>
|
||||
<popup :value="true" :get-container="getContainer" />
|
||||
</div>
|
||||
`,
|
||||
components: {
|
||||
Popup,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
getContainer: () => div,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
await later();
|
||||
|
||||
expect(div.querySelector('.van-overlay')).toBeTruthy();
|
||||
});
|
||||
|
||||
test('watch overlay prop', async () => {
|
||||
const div = document.createElement('div');
|
||||
wrapper = mount({
|
||||
template: `
|
||||
<div>
|
||||
<popup :value="show" :overlay="overlay" :get-container="getContainer" />
|
||||
</div>
|
||||
`,
|
||||
components: {
|
||||
Popup,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
show: false,
|
||||
overlay: false,
|
||||
getContainer: () => div,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
await later();
|
||||
expect(div.querySelector('.van-overlay')).toBeFalsy();
|
||||
|
||||
wrapper.setData({ overlay: true });
|
||||
await later();
|
||||
expect(div.querySelector('.van-overlay')).toBeFalsy();
|
||||
|
||||
wrapper.setData({ show: true });
|
||||
await later();
|
||||
expect(div.querySelector('.van-overlay')).toBeTruthy();
|
||||
});
|
||||
|
||||
test('close on click overlay', async () => {
|
||||
const div = document.createElement('div');
|
||||
const onClickOverlay = jest.fn();
|
||||
|
||||
wrapper = mount({
|
||||
template: `
|
||||
<div>
|
||||
<popup
|
||||
v-model="value"
|
||||
:get-container="getContainer"
|
||||
@click-overlay="onClickOverlay"
|
||||
/>
|
||||
</div>
|
||||
`,
|
||||
components: {
|
||||
Popup,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
value: true,
|
||||
getContainer: () => div,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
onClickOverlay,
|
||||
},
|
||||
});
|
||||
|
||||
await later();
|
||||
|
||||
const modal = div.querySelector('.van-overlay');
|
||||
triggerDrag(modal, 0, -30);
|
||||
modal.click();
|
||||
|
||||
expect(wrapper.vm.value).toBeFalsy();
|
||||
expect(onClickOverlay).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('open & close event', () => {
|
||||
const wrapper = mount(Popup);
|
||||
wrapper.vm.value = true;
|
||||
expect(wrapper.emitted('open')).toBeTruthy();
|
||||
wrapper.vm.value = false;
|
||||
expect(wrapper.emitted('close')).toBeTruthy();
|
||||
});
|
||||
|
||||
test('click event', () => {
|
||||
const wrapper = mount(Popup, {
|
||||
propsData: {
|
||||
value: true,
|
||||
},
|
||||
});
|
||||
|
||||
wrapper.trigger('click');
|
||||
expect(wrapper.emitted('click')).toBeTruthy();
|
||||
});
|
||||
|
||||
test('duration prop when position is center', () => {
|
||||
const wrapper = mount(Popup, {
|
||||
propsData: {
|
||||
value: true,
|
||||
duration: 0.5,
|
||||
},
|
||||
});
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('duration prop when position is top', () => {
|
||||
const wrapper = mount(Popup, {
|
||||
propsData: {
|
||||
value: true,
|
||||
duration: 0.5,
|
||||
position: 'top',
|
||||
},
|
||||
});
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('round prop', () => {
|
||||
const wrapper = mount(Popup, {
|
||||
propsData: {
|
||||
value: true,
|
||||
round: true,
|
||||
},
|
||||
});
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('closeable prop', () => {
|
||||
const wrapper = mount(Popup, {
|
||||
propsData: {
|
||||
value: true,
|
||||
closeable: true,
|
||||
},
|
||||
});
|
||||
|
||||
wrapper.find('.van-popup__close-icon').trigger('click');
|
||||
expect(wrapper.emitted('input')[0][0]).toEqual(false);
|
||||
});
|
||||
|
||||
test('close-icon prop', () => {
|
||||
const wrapper = mount(Popup, {
|
||||
propsData: {
|
||||
value: true,
|
||||
closeable: true,
|
||||
closeIcon: 'success',
|
||||
},
|
||||
});
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
Reference in New Issue
Block a user