Drawer: bugfix/drawer-append-to-body-not-working (#16953)

- 修复了 AppendToBody API 不管用的问题.
- 修复了展开动画会出现滚动条的问题
- 新增了一个新的 API `withHeader` 来控制是否显示 Header 栏
- 动画流畅度的一个小改动
- 对应文档的改动
- 对应单元测试的改动
pull/17264/head^2
jeremywu 2019-11-01 15:06:56 +08:00 committed by Zhi Cun
parent 7bf3924b29
commit 068b3ad1b0
7 changed files with 262 additions and 30 deletions

View File

@ -50,6 +50,36 @@ Callout a temporary drawer, from multiple direction
```
:::
### No Title
When you no longer need a title, you can remove title from drawer.
:::demo Set the `withHeader` attribute to **false**, you can remove the title from drawer, thus your drawer can have more space on screen. If you want to be accessible, make sure to set the `title` attribute.
```html
<el-button @click="drawer = true" type="primary" style="margin-left: 16px;">
open
</el-button>
<el-drawer
title="I am the title"
:visible.sync="drawer"
:with-header="false">
<span>Hi there!</span>
</el-drawer>
<script>
export default {
data() {
return {
drawer: false,
};
}
};
</script>
```
:::
### Customization Content
Like `Dialog`, `Drawer` can do many diverse interaction as you wanted.
@ -92,7 +122,7 @@ Like `Dialog`, `Drawer` can do many diverse interaction as you wanted.
</el-form-item>
</el-form>
<div class="demo-drawer__footer">
<el-button @click="dialog = false">Cancel</el-button>
<el-button @click="cancelForm">Cancel</el-button>
<el-button type="primary" @click="$refs.drawer.closeDrawer()" :loading="loading">{{ loading ? 'Submitting ...' : 'Submit' }}</el-button>
</div>
</div>
@ -132,20 +162,32 @@ export default {
resource: '',
desc: ''
},
formLabelWidth: '80px'
formLabelWidth: '80px',
timer: null,
};
},
methods: {
handleClose(done) {
if (this.loading) {
return;
}
this.$confirm('Do you want to submit?')
.then(_ => {
this.loading = true;
setTimeout(() => {
this.loading = false;
this.timer = setTimeout(() => {
done();
// animation takes time
setTimeout(() => {
this.loading = false;
}, 400);
}, 2000);
})
.catch(_ => {});
},
cancelForm() {
this.loading = false;
this.dialog = false;
clearTimeout(this.timer);
}
}
}
@ -238,6 +280,7 @@ If the variable bound to `visible` is managed in Vuex store, the `.sync` can not
| title | Drawer's title, can also be set by named slot, detailed descriptions can be found in the slot form | string | — | — |
| visible | Should Drawer be displayed, also support the `.sync` notation | boolean | — | false |
| wrapperClosable | Indicates whether user can close Drawer by clicking the shadowing layer. | boolean | - | true |
| withHeader | Flag that controls the header section's existance, default to true, when withHeader set to false, both `title attribute` and `title slot` won't work | boolean | - | true |
### Drawer Slot

View File

@ -50,7 +50,37 @@ Llamada de un drawer temporal, desde varias direcciones
```
:::
### Personalizar el contenido
### No Title
When you no longer need a title, you can remove title from drawer.
:::demo Set the `withHeader` attribute to **false**, you can remove the title from drawer, thus your drawer can have more space on screen. If you want to be accessible, make sure to set the `title` attribute.
```html
<el-button @click="drawer = true" type="primary" style="margin-left: 16px;">
open
</el-button>
<el-drawer
title="I am the title"
:visible.sync="drawer"
:with-header="false">
<span>Hi there!</span>
</el-drawer>
<script>
export default {
data() {
return {
drawer: false,
};
}
};
</script>
```
:::
### Personalizar el contenido
Al igual que `Dialog`, `Drawer` puede hacer muchas interacciones diversas.
@ -92,7 +122,7 @@ Al igual que `Dialog`, `Drawer` puede hacer muchas interacciones diversas.
</el-form-item>
</el-form>
<div class="demo-drawer__footer">
<el-button @click="dialog = false">Cancel</el-button>
<el-button @click="cancelForm">Cancel</el-button>
<el-button type="primary" @click="$refs.drawer.closeDrawer()" :loading="loading">{{ loading ? 'Submitting ...' : 'Submit' }}</el-button>
</div>
</div>
@ -132,20 +162,32 @@ export default {
resource: '',
desc: ''
},
formLabelWidth: '80px'
formLabelWidth: '80px',
timer: null,
};
},
methods: {
handleClose(done) {
if (this.loading) {
return;
}
this.$confirm('Do you want to submit?')
.then(_ => {
this.loading = true;
setTimeout(() => {
this.loading = false;
this.timer = setTimeout(() => {
done();
// animation takes time
setTimeout(() => {
this.loading = false;
}, 400);
}, 2000);
})
.catch(_ => {});
},
cancelForm() {
this.loading = false;
this.dialog = false;
clearTimeout(this.timer);
}
}
}
@ -238,6 +280,7 @@ Si la variable `visible` se gestiona en el almacén de Vuex, el `.sync` no puede
| title | El título del Drawer, también se puede establecer por slot con nombre, las descripciones detalladas se pueden encontrar en el formulario de slot. | string | — | — |
| visible | Si se muestra el Drawer, también soporta la notación `.sync` | boolean | — | false |
| wrapperClosable | Indica si el usuario puede cerrar el Drawer haciendo clic en la capa de sombreado. | boolean | - | true |
| withHeader | Flag that controls the header section's existance, default to true, when withHeader set to false, both `title attribute` and `title slot` won't work | boolean | - | true |
### Drawer Slot's

View File

@ -50,6 +50,36 @@ Callout a temporary drawer, from multiple direction
```
:::
### No Title
When you no longer need a title, you can remove title from drawer.
:::demo Set the `withHeader` attribute to **false**, you can remove the title from drawer, thus your drawer can have more space on screen. If you want to be accessible, make sure to set the `title` attribute.
```html
<el-button @click="drawer = true" type="primary" style="margin-left: 16px;">
open
</el-button>
<el-drawer
title="I am the title"
:visible.sync="drawer"
:with-header="false">
<span>Hi there!</span>
</el-drawer>
<script>
export default {
data() {
return {
drawer: false,
};
}
};
</script>
```
:::
### Customization Content
Like `Dialog`, `Drawer` can do many diverse interaction as you wanted.
@ -92,7 +122,7 @@ Like `Dialog`, `Drawer` can do many diverse interaction as you wanted.
</el-form-item>
</el-form>
<div class="demo-drawer__footer">
<el-button @click="dialog = false">Cancel</el-button>
<el-button @click="cancelForm">Cancel</el-button>
<el-button type="primary" @click="$refs.drawer.closeDrawer()" :loading="loading">{{ loading ? 'Submitting ...' : 'Submit' }}</el-button>
</div>
</div>
@ -132,20 +162,32 @@ export default {
resource: '',
desc: ''
},
formLabelWidth: '80px'
formLabelWidth: '80px',
timer: null,
};
},
methods: {
handleClose(done) {
if (this.loading) {
return;
}
this.$confirm('Do you want to submit?')
.then(_ => {
this.loading = true;
setTimeout(() => {
this.loading = false;
this.timer = setTimeout(() => {
done();
// animation takes time
setTimeout(() => {
this.loading = false;
}, 400);
}, 2000);
})
.catch(_ => {});
},
cancelForm() {
this.loading = false;
this.dialog = false;
clearTimeout(this.timer);
}
}
}
@ -238,6 +280,7 @@ If the variable bound to `visible` is managed in Vuex store, the `.sync` can not
| title | Drawer's title, can also be set by named slot, detailed descriptions can be found in the slot form | string | — | — |
| visible | Should Drawer be displayed, also support the `.sync` notation | boolean | — | false |
| wrapperClosable | Indicates whether user can close Drawer by clicking the shadowing layer. | boolean | - | true |
| withHeader | Flag that controls the header section's existance, default to true, when withHeader set to false, both `title attribute` and `title slot` won't work | boolean | - | true |
### Drawer Slot

View File

@ -50,6 +50,37 @@
```
:::
### 不添加 Title
当你不需要标题到时候, 你还可以去掉标题
:::demo 当遇到不需要 title 的场景时, 可以通过 `withHeader` 这个属性来关闭掉 title 的显示, 这样可以留出更大的空间给到用户, 为了用户的可访问性, 请务必设定 `title` 的值
```html
<el-button @click="drawer = true" type="primary" style="margin-left: 16px;">
点我打开
</el-button>
<el-drawer
title="我是标题"
:visible.sync="drawer"
:with-header="false">
<span>我来啦!</span>
</el-drawer>
<script>
export default {
data() {
return {
drawer: false,
};
}
};
</script>
```
:::
### 自定义内容
`Dialog` 组件一样, `Drawer` 同样可以在其内部嵌套各种丰富的操作
@ -92,7 +123,7 @@
</el-form-item>
</el-form>
<div class="demo-drawer__footer">
<el-button @click="dialog = false">取 消</el-button>
<el-button @click="cancelForm">取 消</el-button>
<el-button type="primary" @click="$refs.drawer.closeDrawer()" :loading="loading">{{ loading ? '提交中 ...' : '确 定' }}</el-button>
</div>
</div>
@ -132,20 +163,32 @@ export default {
resource: '',
desc: ''
},
formLabelWidth: '80px'
formLabelWidth: '80px',
timer: null,
};
},
methods: {
handleClose(done) {
if (this.loading) {
return;
}
this.$confirm('确定要提交表单吗?')
.then(_ => {
this.loading = true;
setTimeout(() => {
this.loading = false;
this.timer = setTimeout(() => {
done();
// 动画关闭需要一定的时间
setTimeout(() => {
this.loading = false;
}, 400);
}, 2000);
})
.catch(_ => {});
},
cancelForm() {
this.loading = false;
this.dialog = false;
clearTimeout(this.timer);
}
}
}
@ -239,6 +282,7 @@ Drawer 提供一个 `destroyOnClose` API, 用来在关闭 Drawer 时销毁子组
| title | Drawer 的标题,也可通过具名 slot (见下表)传入 | string | — | — |
| visible | 是否显示 Drawer支持 .sync 修饰符 | boolean | — | false |
| wrapperClosable | 点击遮罩层是否可以关闭 Drawer | boolean | - | true |
| withHeader | 控制是否显示 header 栏, 默认为 true, 当此项为 false 时, title attribute 和 title slot 均不生效 | boolean | - | true |
### Drawer Slot

View File

@ -4,8 +4,8 @@
@after-enter="afterEnter"
@after-leave="afterLeave">
<div
class="el-dialog__wrapper"
role="presentation"
class="el-drawer__wrapper"
tabindex="-1"
v-show="visible">
<div
class="el-drawer__container"
@ -16,14 +16,17 @@
<div
aria-modal="true"
aria-labelledby="el-drawer__title"
:aria-label="title"
class="el-drawer"
:class="[direction, customClass]"
:style="isHorizontal ? `width: ${size}` : `height: ${size}`"
ref="drawer"
role="presentation">
<header class="el-drawer__header" id="el-drawer__title">
role="dialog"
tabindex="-1"
>
<header class="el-drawer__header" id="el-drawer__title" v-if="withHeader">
<slot name="title">
<span role="heading">{{ title }}</span>
<span role="heading" tabindex="0" :title="title">{{ title }}</span>
</slot>
<button
:aria-label="`close ${title || 'drawer'}`"
@ -45,16 +48,16 @@
<script>
import Popup from 'element-ui/src/utils/popup';
import Migrating from 'element-ui/src/mixins/migrating';
import emitter from 'element-ui/src/mixins/emitter';
import Utils from 'element-ui/src/utils/aria-utils';
export default {
name: 'ElDrawer',
mixins: [Popup, emitter, Migrating],
mixins: [Popup, emitter],
props: {
appendToBody: {
type: Boolean,
default: true
default: false
},
beforeClose: {
type: Function
@ -63,6 +66,10 @@ export default {
type: String,
default: ''
},
closeOnPressEscape: {
type: Boolean,
default: true
},
destroyOnClose: {
type: Boolean,
default: false
@ -78,6 +85,10 @@ export default {
return ['ltr', 'rtl', 'ttb', 'btt'].indexOf(val) !== -1;
}
},
modalAppendToBody: {
type: Boolean,
default: true
},
showClose: {
type: Boolean,
default: true
@ -96,6 +107,10 @@ export default {
wrapperClosable: {
type: Boolean,
default: true
},
withHeader: {
type: Boolean,
default: true
}
},
computed: {
@ -105,7 +120,8 @@ export default {
},
data() {
return {
closed: false
closed: false,
prevActiveElement: null
};
},
watch: {
@ -116,8 +132,17 @@ export default {
if (this.appendToBody) {
document.body.appendChild(this.$el);
}
this.prevActiveElement = document.activeElement;
this.$nextTick(() => {
Utils.focusFirstDescendant(this.$refs.drawer);
});
} else {
if (!this.closed) this.$emit('close');
this.$nextTick(() => {
if (this.prevActiveElement) {
this.prevActiveElement.focus();
}
});
}
}
},
@ -149,6 +174,12 @@ export default {
} else {
this.hide();
}
},
handleClose() {
// This method here will be called by PopupManger, when the `closeOnPressEscape` was set to true
// pressing `ESC` will call this method, and also close the drawer.
// This method also calls `beforeClose` if there was one.
this.closeDrawer();
}
},
mounted() {

View File

@ -92,13 +92,13 @@
@mixin animation-in($direction) {
.el-drawer__open &.#{$direction} {
animation: #{$direction}-drawer-in 225ms cubic-bezier(0, 0, .2, 1) 0ms;
animation: #{$direction}-drawer-in .3s 1ms;
}
}
@mixin animation-out($direction) {
&.#{$direction} {
animation: #{$direction}-drawer-out 225ms cubic-bezier(0, 0, .2, 1) 0ms;
animation: #{$direction}-drawer-out .3s;
}
}
@ -125,6 +125,16 @@ $directions: rtl, ltr, ttb, btt;
@include animation-in($direction);
}
&__wrapper {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
overflow: hidden;
margin: 0;
}
&__header {
align-items: center;
color: rgb(114, 118, 123);
@ -199,9 +209,9 @@ $directions: rtl, ltr, ttb, btt;
}
.el-drawer-fade-enter-active {
animation: el-drawer-fade-in 225ms cubic-bezier(0, 0, 0.2, 1) 0ms;
animation: el-drawer-fade-in .3s;
}
.el-drawer-fade-leave-active {
animation: el-drawer-fade-in 225ms cubic-bezier(0, 0, 0.2, 1) 0ms reverse;
animation: el-drawer-fade-in .3s reverse;
}

View File

@ -218,6 +218,24 @@ describe('Drawer', () => {
expect(vm.$el.querySelector(`.${classes}`)).to.exist;
});
it('should not render header when withHeader attribute is false', () => {
vm = createVue({
template: `
<el-drawer :title='title' :visible='visible' ref='drawer' :with-header='false'>
<span>${content}</span>
</el-drawer>
`,
data() {
return {
title,
visible: true
};
}
});
expect(vm.$el.querySelector('.el-drawer__header')).to.not.exist;
});
describe('directions', () => {
const renderer = direction => {
return createVue({