diff --git a/breaking-changes.md b/breaking-changes.md
new file mode 100644
index 000000000..c08ffd5cf
--- /dev/null
+++ b/breaking-changes.md
@@ -0,0 +1,5 @@
+# 不兼容更新
+
+## Popup
+
+- v-model 调整为 v-model:show
diff --git a/src-next/mixins/popup/context.ts b/src-next/mixins/popup/context.ts
index 6ab98f914..25b501a28 100644
--- a/src-next/mixins/popup/context.ts
+++ b/src-next/mixins/popup/context.ts
@@ -1,9 +1,6 @@
-import { OverlayConfig } from './overlay';
-
export type StackItem = {
vm: any;
overlay: any;
- config: OverlayConfig;
};
export const context = {
diff --git a/src-next/mixins/popup/index.js b/src-next/mixins/popup/index.js
index c2c97d40b..436eb909d 100644
--- a/src-next/mixins/popup/index.js
+++ b/src-next/mixins/popup/index.js
@@ -1,11 +1,5 @@
// Context
import { context } from './context';
-import {
- openOverlay,
- closeOverlay,
- updateOverlay,
- removeOverlay,
-} from './overlay';
// Utils
import { on, off, preventDefault } from '../../utils/dom/event';
@@ -19,7 +13,7 @@ import { CloseOnPopstateMixin } from '../close-on-popstate';
export const popupMixinProps = {
// whether to show popup
- value: Boolean,
+ show: Boolean,
// whether to show overlay
overlay: Boolean,
// overlay custom style
@@ -47,20 +41,14 @@ export function PopupMixin(options = {}) {
mixins: [
TouchMixin,
CloseOnPopstateMixin,
- PortalMixin({
- afterPortal() {
- if (this.overlay) {
- updateOverlay();
- }
- },
- }),
+ PortalMixin({}),
],
props: popupMixinProps,
data() {
return {
- inited: this.value,
+ inited: this.show,
};
},
@@ -71,9 +59,9 @@ export function PopupMixin(options = {}) {
},
watch: {
- value(val) {
+ show(val) {
const type = val ? 'open' : 'close';
- this.inited = this.inited || this.value;
+ this.inited = this.inited || this.show;
this[type]();
if (!options.skipToggleEvent) {
@@ -85,7 +73,7 @@ export function PopupMixin(options = {}) {
},
mounted() {
- if (this.value) {
+ if (this.show) {
this.open();
}
},
@@ -93,23 +81,22 @@ export function PopupMixin(options = {}) {
/* istanbul ignore next */
activated() {
if (this.shouldReopen) {
- this.$emit('input', true);
+ this.$emit('update:show', true);
this.shouldReopen = false;
}
},
beforeDestroy() {
this.removeLock();
- removeOverlay(this);
if (this.getContainer) {
- removeNode(this.$el);
+ removeNode(this.$refs.root);
}
},
/* istanbul ignore next */
deactivated() {
- if (this.value) {
+ if (this.show) {
this.close();
this.shouldReopen = true;
}
@@ -161,16 +148,15 @@ export function PopupMixin(options = {}) {
return;
}
- closeOverlay(this);
this.opened = false;
this.removeLock();
- this.$emit('input', false);
+ this.$emit('update:show', false);
},
onTouchMove(event) {
this.touchMove(event);
const direction = this.deltaY > 0 ? '10' : '01';
- const el = getScroller(event.target, this.$el);
+ const el = getScroller(event.target, this.$refs.root);
const { scrollHeight, offsetHeight, scrollTop } = el;
let status = '11';
@@ -191,29 +177,32 @@ export function PopupMixin(options = {}) {
}
},
+ onClickOverlay() {
+ this.$emit('click-overlay');
+
+ if (this.closeOnClickOverlay) {
+ // TODO
+ // if (this.onClickOverlay) {
+ // this.onClickOverlay();
+ // } else {
+ // this.close();
+ // }
+ this.close();
+ }
+ },
+
renderOverlay() {
- if (this.$isServer || !this.value) {
+ if (this.$isServer || !this.show) {
return;
}
this.$nextTick(() => {
this.updateZIndex(this.overlay ? 1 : 0);
-
- if (this.overlay) {
- openOverlay(this, {
- zIndex: context.zIndex++,
- duration: this.duration,
- className: this.overlayClass,
- customStyle: this.overlayStyle,
- });
- } else {
- closeOverlay(this);
- }
});
},
updateZIndex(value = 0) {
- this.$el.style.zIndex = ++context.zIndex + value;
+ this.$refs.root.style.zIndex = ++context.zIndex + value;
},
},
};
diff --git a/src-next/mixins/popup/overlay.ts b/src-next/mixins/popup/overlay.ts
deleted file mode 100644
index 1d5d86f6a..000000000
--- a/src-next/mixins/popup/overlay.ts
+++ /dev/null
@@ -1,77 +0,0 @@
-import Overlay from '../../overlay';
-import { context } from './context';
-import { mount } from '../../utils/functional';
-import { removeNode } from '../../utils/dom/node';
-
-export type OverlayConfig = {
- zIndex?: number;
- className?: string;
- customStyle?: string | object[] | object;
-};
-
-const defaultConfig: OverlayConfig = {
- className: '',
- customStyle: {},
-};
-
-function mountOverlay(vm: any) {
- return mount(Overlay, {
- on: {
- // close popup when overlay clicked & closeOnClickOverlay is true
- click() {
- vm.$emit('click-overlay');
-
- if (vm.closeOnClickOverlay) {
- if (vm.onClickOverlay) {
- vm.onClickOverlay();
- } else {
- vm.close();
- }
- }
- },
- },
- });
-}
-
-export function updateOverlay(vm: any): void {
- const item = context.find(vm);
-
- if (item) {
- const el = vm.$el;
- const { config, overlay } = item;
-
- if (el && el.parentNode) {
- el.parentNode.insertBefore(overlay.$el, el);
- }
-
- Object.assign(overlay, defaultConfig, config, {
- show: true,
- });
- }
-}
-
-export function openOverlay(vm: any, config: OverlayConfig): void {
- const item = context.find(vm);
- if (item) {
- item.config = config;
- } else {
- const overlay = mountOverlay(vm);
- context.stack.push({ vm, config, overlay });
- }
-
- updateOverlay(vm);
-}
-
-export function closeOverlay(vm: any): void {
- const item = context.find(vm);
- if (item) {
- item.overlay.show = false;
- }
-}
-
-export function removeOverlay(vm: any) {
- const item = context.find(vm);
- if (item) {
- removeNode(item.overlay.$el);
- }
-}
diff --git a/src-next/overlay/test/__snapshots__/demo.spec.js.snap b/src-next/overlay/test/__snapshots__/demo.spec.js.snap
new file mode 100644
index 000000000..2179861ea
--- /dev/null
+++ b/src-next/overlay/test/__snapshots__/demo.spec.js.snap
@@ -0,0 +1,20 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders demo correctly 1`] = `
+
+`;
diff --git a/src-next/overlay/test/__snapshots__/index.spec.js.snap b/src-next/overlay/test/__snapshots__/index.spec.js.snap
new file mode 100644
index 000000000..07b7885df
--- /dev/null
+++ b/src-next/overlay/test/__snapshots__/index.spec.js.snap
@@ -0,0 +1,11 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`class-name prop 1`] = ``;
+
+exports[`custom style prop 1`] = ``;
+
+exports[`default slot 1`] = `Custom Default
`;
+
+exports[`duration prop 1`] = ``;
+
+exports[`z-index prop 1`] = ``;
diff --git a/src-next/popup/README.md b/src-next/popup/README.md
new file mode 100644
index 000000000..0b6e81530
--- /dev/null
+++ b/src-next/popup/README.md
@@ -0,0 +1,138 @@
+# Popup
+
+### Install
+
+```js
+import Vue from 'vue';
+import { Popup } from 'vant';
+
+Vue.use(Popup);
+```
+
+## Usage
+
+### Basic Usage
+
+```html
+Show Popup
+Content
+```
+
+```js
+export default {
+ data() {
+ return {
+ show: false,
+ };
+ },
+
+ methods: {
+ showPopup() {
+ this.show = true;
+ },
+ },
+};
+```
+
+### Position
+
+Use `position` prop to set popup display position
+
+```html
+
+```
+
+### Close Icon
+
+```html
+
+
+
+
+
+```
+
+### Round Corner
+
+```html
+
+```
+
+### Get Container
+
+Use `get-container` prop to specify mount location
+
+```html
+
+
+
+
+
+
+
+
+```
+
+```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 | - |
diff --git a/src-next/popup/README.zh-CN.md b/src-next/popup/README.zh-CN.md
new file mode 100644
index 000000000..017d8628f
--- /dev/null
+++ b/src-next/popup/README.zh-CN.md
@@ -0,0 +1,149 @@
+# Popup 弹出层
+
+### 介绍
+
+弹出层容器,用于展示弹窗、信息提示等内容,支持多个弹出层叠加展示
+
+### 引入
+
+```js
+import Vue from 'vue';
+import { Popup } from 'vant';
+
+Vue.use(Popup);
+```
+
+## 代码演示
+
+### 基础用法
+
+通过 `v-model:show` 控制弹出层是否展示
+
+```html
+展示弹出层
+内容
+```
+
+```js
+export default {
+ data() {
+ return {
+ show: false,
+ };
+ },
+
+ methods: {
+ showPopup() {
+ this.show = true;
+ },
+ },
+};
+```
+
+### 弹出位置
+
+通过`position`属性设置弹出位置,默认居中弹出,可以设置为`top`、`bottom`、`left`、`right`
+
+```html
+
+```
+
+### 关闭图标
+
+设置`closeable`属性后,会在弹出层的右上角显示关闭图标,并且可以通过`close-icon`属性自定义图标,使用`close-icon-position`属性可以自定义图标位置
+
+```html
+
+
+
+
+
+```
+
+### 圆角弹窗
+
+设置`round`属性后,弹窗会根据弹出位置添加不同的圆角样式
+
+```html
+
+```
+
+### 指定挂载位置
+
+弹出层默认挂载到组件所在位置,可以通过`get-container`属性指定挂载位置
+
+```html
+
+
+
+
+
+
+
+
+```
+
+```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`
`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 | 点击遮罩层时触发 | - |
diff --git a/src-next/popup/demo/index.vue b/src-next/popup/demo/index.vue
new file mode 100644
index 000000000..a85d5c7be
--- /dev/null
+++ b/src-next/popup/demo/index.vue
@@ -0,0 +1,164 @@
+
+
+
+
+
+ {{ t('content') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src-next/popup/index.js b/src-next/popup/index.js
new file mode 100644
index 000000000..0c4720d32
--- /dev/null
+++ b/src-next/popup/index.js
@@ -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 && (
+
+ )}
+
+
+ {this.$slots.default?.()}
+ {this.closeable && (
+
+ )}
+
+
+ >
+ );
+ },
+});
diff --git a/src-next/popup/index.less b/src-next/popup/index.less
new file mode 100644
index 000000000..9a9234ab7
--- /dev/null
+++ b/src-next/popup/index.less
@@ -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;
+ }
+ }
+ }
+}
diff --git a/src-next/popup/test/__snapshots__/demo.spec.js.snap b/src-next/popup/test/__snapshots__/demo.spec.js.snap
new file mode 100644
index 000000000..29daf9410
--- /dev/null
+++ b/src-next/popup/test/__snapshots__/demo.spec.js.snap
@@ -0,0 +1,65 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders demo correctly 1`] = `
+
+`;
diff --git a/src-next/popup/test/__snapshots__/index.spec.js.snap b/src-next/popup/test/__snapshots__/index.spec.js.snap
new file mode 100644
index 000000000..8552d5572
--- /dev/null
+++ b/src-next/popup/test/__snapshots__/index.spec.js.snap
@@ -0,0 +1,14 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`close-icon prop 1`] = `
+
+`;
+
+exports[`duration prop when position is center 1`] = ``;
+
+exports[`duration prop when position is top 1`] = ``;
+
+exports[`reset z-index 1`] = ``;
+
+exports[`round prop 1`] = ``;
diff --git a/src-next/popup/test/demo.spec.js b/src-next/popup/test/demo.spec.js
new file mode 100644
index 000000000..5c70922b5
--- /dev/null
+++ b/src-next/popup/test/demo.spec.js
@@ -0,0 +1,4 @@
+import Demo from '../demo';
+import { snapshotDemo } from '../../../test/demo';
+
+snapshotDemo(Demo);
diff --git a/src-next/popup/test/index.spec.js b/src-next/popup/test/index.spec.js
new file mode 100644
index 000000000..f570087a7
--- /dev/null
+++ b/src-next/popup/test/index.spec.js
@@ -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: `
+
+ `,
+ 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: `
+
+ `,
+ 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: `
+
+ `,
+ 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: `
+
+ `,
+ 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: `
+
+ `,
+ 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();
+});
diff --git a/src-next/utils/dom/event.ts b/src-next/utils/dom/event.ts
index 459e581f1..7bd55f859 100644
--- a/src-next/utils/dom/event.ts
+++ b/src-next/utils/dom/event.ts
@@ -1,10 +1,10 @@
-import { isServer } from '..';
+import { inBrowser } from '..';
import { EventHandler } from '../types';
// eslint-disable-next-line import/no-mutable-exports
export let supportsPassive = false;
-if (!isServer) {
+if (inBrowser) {
try {
const opts = {};
Object.defineProperty(opts, 'passive', {
@@ -25,7 +25,7 @@ export function on(
handler: EventHandler,
passive = false
) {
- if (!isServer) {
+ if (inBrowser) {
target.addEventListener(
event,
handler,
@@ -35,7 +35,7 @@ export function on(
}
export function off(target: EventTarget, event: string, handler: EventHandler) {
- if (!isServer) {
+ if (inBrowser) {
target.removeEventListener(event, handler);
}
}
diff --git a/src-next/utils/index.ts b/src-next/utils/index.ts
index f1a30475f..c88a00c4a 100644
--- a/src-next/utils/index.ts
+++ b/src-next/utils/index.ts
@@ -4,6 +4,8 @@ export { createNamespace } from './create';
// eslint-disable-next-line @typescript-eslint/no-empty-function
export function noop() {}
+export const inBrowser = typeof window !== 'undefined'
+
export function isDef(val: unknown): boolean {
return val !== undefined && val !== null;
}
diff --git a/vant.config.js b/vant.config.js
index 68bc655c6..38c0110ec 100644
--- a/vant.config.js
+++ b/vant.config.js
@@ -98,10 +98,10 @@ module.exports = {
path: 'col',
title: 'Layout 布局',
},
- // {
- // path: 'popup',
- // title: 'Popup 弹出层',
- // },
+ {
+ path: 'popup',
+ title: 'Popup 弹出层',
+ },
{
path: 'style',
title: 'Style 内置样式',
@@ -200,10 +200,10 @@ module.exports = {
// path: 'notify',
// title: 'Notify 消息通知',
// },
- // {
- // path: 'overlay',
- // title: 'Overlay 遮罩层',
- // },
+ {
+ path: 'overlay',
+ title: 'Overlay 遮罩层',
+ },
// {
// path: 'pull-refresh',
// title: 'PullRefresh 下拉刷新',
@@ -534,10 +534,10 @@ module.exports = {
// path: 'notify',
// title: 'Notify',
// },
- // {
- // path: 'overlay',
- // title: 'Overlay',
- // },
+ {
+ path: 'overlay',
+ title: 'Overlay',
+ },
// {
// path: 'pull-refresh',
// title: 'PullRefresh',
diff --git a/webpack.config.js b/webpack.config.js
index d5e3de6a0..de244338d 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -4,6 +4,7 @@ module.exports = function () {
}
return {
+ devtool: 'none',
entry: {
'site-mobile': ['./docs/site/mobile'],
},