[bugfix] Toast: overlay blocked by other element (#740)

This commit is contained in:
neverland 2018-03-21 15:58:11 +08:00 committed by GitHub
parent 11f9715793
commit bca3401d81
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 116 additions and 134 deletions

View File

@ -1,5 +1,5 @@
const PopupContext = { export default {
idSeed: 1, id: 1,
zIndex: 2000, zIndex: 2000,
stack: [], stack: [],
@ -11,5 +11,3 @@ const PopupContext = {
return this.stack[this.stack.length - 1]; return this.stack[this.stack.length - 1];
} }
}; };
export default PopupContext;

View File

@ -26,17 +26,6 @@ export default {
} }
}, },
data() {
this._popupId = 'popup-' + context.plusKey('idSeed');
return {
opened: false,
pos: {
x: 0,
y: 0
}
};
},
watch: { watch: {
value(val) { value(val) {
this[val ? 'open' : 'close'](); this[val ? 'open' : 'close']();
@ -44,6 +33,21 @@ export default {
getContainer() { getContainer() {
this.move(); this.move();
},
overlay() {
this.renderOverlay();
}
},
created() {
this._popupId = 'popup-' + context.plusKey('id');
this.pos = {
x: 0,
y: 0
};
if (this.value) {
this.open();
} }
}, },
@ -51,24 +55,61 @@ export default {
if (this.getContainer) { if (this.getContainer) {
this.move(); this.move();
} }
if (this.value) {
this.open();
}
}, },
beforeDestroy() { beforeDestroy() {
this.doAfterClose(); this.close();
}, },
methods: { methods: {
recordPosition(e) { open() {
/* istanbul ignore next */
if (this.$isServer) {
return;
}
// 如果属性中传入了`zIndex`,则覆盖`context`中对应的`zIndex`
if (this.zIndex !== undefined) {
context.zIndex = this.zIndex;
}
if (this.lockScroll) {
document.body.classList.add('van-overflow-hidden');
on(document, 'touchstart', this.onTouchStart);
on(document, 'touchmove', this.onTouchMove);
}
this.renderOverlay();
this.$emit('input', true);
},
close() {
if (this.lockScroll) {
document.body.classList.remove('van-overflow-hidden');
off(document, 'touchstart', this.onTouchStart);
off(document, 'touchmove', this.onTouchMove);
}
manager.close(this._popupId);
this.$emit('input', false);
},
move() {
if (this.getContainer) {
this.getContainer().appendChild(this.$el);
} else if (this.$parent) {
this.$parent.$el.appendChild(this.$el);
}
},
onTouchStart(e) {
this.pos = { this.pos = {
x: e.touches[0].clientX, x: e.touches[0].clientX,
y: e.touches[0].clientY y: e.touches[0].clientY
}; };
}, },
watchTouchMove(e) { onTouchMove(e) {
const { pos } = this; const { pos } = this;
const dx = e.touches[0].clientX - pos.x; const dx = e.touches[0].clientX - pos.x;
const dy = e.touches[0].clientY - pos.y; const dy = e.touches[0].clientY - pos.y;
@ -97,64 +138,17 @@ export default {
} }
}, },
open() { renderOverlay() {
/* istanbul ignore next */
if (this.opened || this.$isServer) {
return;
}
// 如果属性中传入了`zIndex`,则覆盖`context`中对应的`zIndex`
if (this.zIndex !== undefined) {
context.zIndex = this.zIndex;
}
if (this.overlay) { if (this.overlay) {
manager.open(this, { manager.open(this, {
id: this._popupId,
dom: this.$el,
zIndex: context.plusKey('zIndex'), zIndex: context.plusKey('zIndex'),
className: this.overlayClass, className: this.overlayClass,
customStyle: this.overlayStyle customStyle: this.overlayStyle
}); });
} else {
manager.close(this._popupId);
} }
if (this.lockScroll) {
document.body.classList.add('van-overflow-hidden');
on(document, 'touchstart', this.recordPosition);
on(document, 'touchmove', this.watchTouchMove);
}
this.$el.style.zIndex = context.plusKey('zIndex'); this.$el.style.zIndex = context.plusKey('zIndex');
this.$emit('input', true);
this.opened = true;
},
close() {
if (!this.opened || this.$isServer) {
return;
}
this.$emit('input', false);
this.opened = false;
this.doAfterClose();
},
doAfterClose() {
manager.close(this._popupId);
if (this.lockScroll) {
document.body.classList.remove('van-overflow-hidden');
off(document, 'touchstart', this.recordPosition);
off(document, 'touchmove', this.watchTouchMove);
}
},
move() {
if (this.getContainer) {
this.getContainer().appendChild(this.$el);
} else if (this.$parent) {
this.$parent.$el.appendChild(this.$el);
}
} }
} }
}; };

View File

@ -9,13 +9,13 @@ const defaultConfig = {
export default { export default {
open(vm, config) { open(vm, config) {
const { id, dom } = config; const exist = context.stack.some(item => item.id === vm._popupId);
const exist = context.stack.some(item => item.id === id);
/* istanbul ignore next */ /* istanbul ignore next */
if (!exist) { if (!exist) {
const targetNode = dom && dom.parentNode && dom.parentNode.nodeType !== 11 ? dom.parentNode : document.body; const el = vm.$el;
context.stack.push({ vm, id, config, targetNode }); const targetNode = el && el.parentNode && el.parentNode.nodeType !== 11 ? el.parentNode : document.body;
context.stack.push({ vm, config, targetNode });
this.update(); this.update();
}; };
}, },
@ -24,11 +24,11 @@ export default {
const { stack } = context; const { stack } = context;
if (stack.length) { if (stack.length) {
if (context.top.id === id) { if (context.top.vm._popupId === id) {
stack.pop(); stack.pop();
this.update(); this.update();
} else { } else {
context.stack = stack.filter(item => item.id !== id); context.stack = stack.filter(item => item.vm._popupId !== id);
} }
} }
}, },
@ -65,10 +65,8 @@ export default {
onClick() { onClick() {
if (context.top) { if (context.top) {
const { vm } = context.top; const { vm } = context.top;
if (vm) { vm.$emit('click-overlay');
vm.$emit('click-overlay'); vm.closeOnClickOverlay && vm.close();
vm.closeOnClickOverlay && vm.close();
}
} }
} }
}; };

View File

@ -6,10 +6,11 @@ const defaultOptions = {
type: 'text', type: 'text',
mask: false, mask: false,
message: '', message: '',
visible: true, value: true,
duration: 3000, duration: 3000,
position: 'middle', position: 'middle',
forbidClick: false forbidClick: false,
overlayStyle: {}
}; };
const parseOptions = message => isObj(message) ? message : { message }; const parseOptions = message => isObj(message) ? message : { message };
@ -28,6 +29,16 @@ function createInstance() {
return queue[queue.length - 1]; return queue[queue.length - 1];
}; };
// transform toast options to popup props
function transformer(options) {
options.overlay = options.mask;
if (options.forbidClick && !options.overlay) {
options.overlay = true;
options.overlayStyle = { background: 'transparent' };
}
return options;
}
function Toast(options = {}) { function Toast(options = {}) {
const toast = createInstance(); const toast = createInstance();
@ -35,11 +46,11 @@ function Toast(options = {}) {
...currentOptions, ...currentOptions,
...parseOptions(options), ...parseOptions(options),
clear() { clear() {
toast.visible = false; toast.value = false;
} }
}; };
Object.assign(toast, options); Object.assign(toast, transformer(options));
clearTimeout(toast.timer); clearTimeout(toast.timer);
if (options.duration > 0) { if (options.duration > 0) {

View File

@ -1,35 +1,33 @@
<template> <template>
<transition name="van-fade"> <transition name="van-fade">
<div class="van-toast-wrapper" v-show="visible"> <div class="van-toast" :class="[`van-toast--${displayStyle}`, `van-toast--${position}`]" v-show="value">
<div class="van-toast" :class="[`van-toast--${displayStyle}`, `van-toast--${position}`]"> <!-- text only -->
<!-- text only --> <div v-if="displayStyle === 'text'">{{ message }}</div>
<div v-if="displayStyle === 'text'">{{ message }}</div> <div v-if="displayStyle === 'html'" v-html="message" />
<div v-if="displayStyle === 'html'" v-html="message" />
<!-- with icon --> <!-- with icon -->
<template v-if="displayStyle === 'default'"> <template v-if="displayStyle === 'default'">
<loading v-if="type === 'loading'" color="white" /> <loading v-if="type === 'loading'" color="white" />
<icon v-else class="van-toast__icon" :name="type" /> <icon v-else class="van-toast__icon" :name="type" />
<div v-if="hasMessage" class="van-toast__text">{{ message }}</div> <div v-if="hasMessage" class="van-toast__text">{{ message }}</div>
</template> </template>
</div>
<div class="van-toast__overlay" :class="{ 'van-toast__overlay--mask': mask }" v-if="forbidClick || mask" />
</div> </div>
</transition> </transition>
</template> </template>
<script> <script>
import create from '../utils/create'; import create from '../utils/create';
import Popup from '../mixins/popup';
const STYLE_LIST = ['success', 'fail', 'loading']; const STYLE_LIST = ['success', 'fail', 'loading'];
export default create({ export default create({
name: 'toast', name: 'toast',
mixins: [Popup],
props: { props: {
mask: Boolean,
message: [String, Number], message: [String, Number],
forbidClick: Boolean,
type: { type: {
type: String, type: String,
default: 'text' default: 'text'
@ -37,15 +35,13 @@ export default create({
position: { position: {
type: String, type: String,
default: 'middle' default: 'middle'
},
lockScroll: {
type: Boolean,
default: false
} }
}, },
data() {
return {
visible: false
};
},
computed: { computed: {
displayStyle() { displayStyle() {
return STYLE_LIST.indexOf(this.type) !== -1 ? 'default' : this.type; return STYLE_LIST.indexOf(this.type) !== -1 ? 'default' : this.type;

View File

@ -6,7 +6,6 @@
left: 50%; left: 50%;
display: flex; display: flex;
color: $white; color: $white;
z-index: 3001;
font-size: 12px; font-size: 12px;
line-height: 1.2; line-height: 1.2;
border-radius: 5px; border-radius: 5px;
@ -18,20 +17,6 @@
transform: translate3d(-50%, -50%, 0); transform: translate3d(-50%, -50%, 0);
background-color: rgba(0, 0, 0, .7); background-color: rgba(0, 0, 0, .7);
&__overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 3000;
background-color: transparent;
&--mask {
background-color: rgba(0, 0, 0, .5);
}
}
&--text { &--text {
padding: 12px; padding: 12px;
min-width: 220px; min-width: 220px;

View File

@ -8,13 +8,13 @@ describe('Toast', () => {
it('create a empty toast', () => { it('create a empty toast', () => {
Toast(); Toast();
expect(document.querySelector('.van-toast-wrapper')).to.exist; expect(document.querySelector('.van-toast')).to.exist;
}); });
it('create a toast', () => { it('create a toast', () => {
const toast = Toast('toast'); const toast = Toast('toast');
expect(document.querySelector('.van-toast-wrapper')).to.exist; expect(document.querySelector('.van-toast')).to.exist;
expect(toast.message).to.equal('toast'); expect(toast.message).to.equal('toast');
expect(toast.type).to.equal('text'); expect(toast.type).to.equal('text');
expect(toast.displayStyle).to.equal('text'); expect(toast.displayStyle).to.equal('text');
@ -24,7 +24,7 @@ describe('Toast', () => {
it('create a loading toast', () => { it('create a loading toast', () => {
const toast = Toast.loading(); const toast = Toast.loading();
expect(document.querySelector('.van-toast-wrapper')).to.exist; expect(document.querySelector('.van-toast')).to.exist;
expect(toast.type).to.equal('loading'); expect(toast.type).to.equal('loading');
}); });
@ -33,7 +33,7 @@ describe('Toast', () => {
message: 'toast' message: 'toast'
}); });
expect(document.querySelector('.van-toast-wrapper')).to.exist; expect(document.querySelector('.van-toast')).to.exist;
expect(toast.message).to.equal('toast'); expect(toast.message).to.equal('toast');
expect(toast.type).to.equal('loading'); expect(toast.type).to.equal('loading');
}); });
@ -41,7 +41,7 @@ describe('Toast', () => {
it('create a success toast', () => { it('create a success toast', () => {
const toast = Toast.success('success'); const toast = Toast.success('success');
expect(document.querySelector('.van-toast-wrapper')).to.exist; expect(document.querySelector('.van-toast')).to.exist;
expect(toast.displayStyle).to.equal('default'); expect(toast.displayStyle).to.equal('default');
expect(toast.type).to.equal('success'); expect(toast.type).to.equal('success');
}); });
@ -51,7 +51,7 @@ describe('Toast', () => {
message: 'toast' message: 'toast'
}); });
expect(document.querySelector('.van-toast-wrapper')).to.exist; expect(document.querySelector('.van-toast')).to.exist;
expect(toast.message).to.equal('toast'); expect(toast.message).to.equal('toast');
expect(toast.type).to.equal('success'); expect(toast.type).to.equal('success');
}); });
@ -59,7 +59,7 @@ describe('Toast', () => {
it('create a fail toast', () => { it('create a fail toast', () => {
const toast = Toast.fail('fail'); const toast = Toast.fail('fail');
expect(document.querySelector('.van-toast-wrapper')).to.exist; expect(document.querySelector('.van-toast')).to.exist;
expect(toast.displayStyle).to.equal('default'); expect(toast.displayStyle).to.equal('default');
expect(toast.type).to.equal('fail'); expect(toast.type).to.equal('fail');
}); });
@ -69,7 +69,7 @@ describe('Toast', () => {
message: 'toast' message: 'toast'
}); });
expect(document.querySelector('.van-toast-wrapper')).to.exist; expect(document.querySelector('.van-toast')).to.exist;
expect(toast.message).to.equal('toast'); expect(toast.message).to.equal('toast');
expect(toast.type).to.equal('fail'); expect(toast.type).to.equal('fail');
}); });
@ -80,9 +80,9 @@ describe('Toast', () => {
forbidClick: true forbidClick: true
}); });
expect(document.querySelector('.van-toast-wrapper')).to.exist; expect(document.querySelector('.van-toast')).to.exist;
setTimeout(() => { setTimeout(() => {
expect(document.querySelector('.van-toast__overlay')).to.exist; expect(document.querySelector('.van-modal')).to.exist;
done(); done();
}, 50); }, 50);
}); });
@ -116,11 +116,11 @@ describe('Toast', () => {
const toast1 = Toast.success('1'); const toast1 = Toast.success('1');
const toast2 = Toast.success('2'); const toast2 = Toast.success('2');
Toast.clear(); Toast.clear();
expect(toast1.visible).to.be.false; expect(toast1.value).to.be.false;
expect(toast2.visible).to.be.true; expect(toast2.value).to.be.true;
Toast.clear(); Toast.clear();
Toast.clear(); Toast.clear();
expect(toast2.visible).to.be.false; expect(toast2.value).to.be.false;
Toast.allowMultiple(false); Toast.allowMultiple(false);
}); });