mirror of
https://gitee.com/ElemeFE/element.git
synced 2024-12-02 04:08:10 +08:00
Accessibility for Popover, Tooltip, Message & Notification (#8009)
* Accessibility for Tooltip & Popover * Accessibility for message & notification * fixbug for popover with nodeType
This commit is contained in:
parent
6c77cd9716
commit
363a80b184
@ -146,7 +146,7 @@ Popover 的属性与 Tooltip 很类似,它们都是基于`Vue-popper`开发的
|
||||
width="200"
|
||||
trigger="focus"
|
||||
content="这是一段内容,这是一段内容,这是一段内容,这是一段内容。">
|
||||
<el-button slot="reference">focus 激活</el-button>
|
||||
<span slot="reference" style="margin-left: 10px; font-size: 14px; color: #5a5e66">focus 激活</span>
|
||||
</el-popover>
|
||||
```
|
||||
:::
|
||||
|
@ -63,7 +63,7 @@
|
||||
<div class="box">
|
||||
<div class="top">
|
||||
<el-tooltip class="item" effect="dark" content="Top Left 提示文字" placement="top-start">
|
||||
<el-button>上左</el-button>
|
||||
<span>上左</span>
|
||||
</el-tooltip>
|
||||
<el-tooltip class="item" effect="dark" content="Top Center 提示文字" placement="top">
|
||||
<el-button>上边</el-button>
|
||||
|
@ -9,15 +9,15 @@
|
||||
v-show="visible"
|
||||
@mouseenter="clearTimer"
|
||||
@mouseleave="startTimer"
|
||||
role="alertdialog"
|
||||
role="alert"
|
||||
>
|
||||
<i :class="iconClass" v-if="iconClass"></i>
|
||||
<i :class="typeClass" v-else></i>
|
||||
<slot>
|
||||
<p v-if="!dangerouslyUseHTMLString" class="el-message__content" tabindex="0">{{ message }}</p>
|
||||
<p v-else v-html="message" class="el-message__content" tabindex="0"></p>
|
||||
<p v-if="!dangerouslyUseHTMLString" class="el-message__content">{{ message }}</p>
|
||||
<p v-else v-html="message" class="el-message__content"></p>
|
||||
</slot>
|
||||
<i v-if="showClose" class="el-message__closeBtn el-icon-close" @click="close" tabindex="0" role="button" aria-label="close" @keydown.enter.stop="close"></i>
|
||||
<i v-if="showClose" class="el-message__closeBtn el-icon-close" @click="close"></i>
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
@ -44,9 +44,7 @@
|
||||
closed: false,
|
||||
timer: null,
|
||||
dangerouslyUseHTMLString: false,
|
||||
center: false,
|
||||
initFocus: null,
|
||||
originFocus: null
|
||||
center: false
|
||||
};
|
||||
},
|
||||
|
||||
@ -87,18 +85,18 @@
|
||||
if (typeof this.onClose === 'function') {
|
||||
this.onClose(this);
|
||||
}
|
||||
if (!this.originFocus || !this.originFocus.getBoundingClientRect) return;
|
||||
|
||||
// restore keyboard focus
|
||||
const { top, left, bottom, right } = this.originFocus.getBoundingClientRect();
|
||||
const viewportHeight = window.innerHeight || document.documentElement.clientHeight;
|
||||
const viewportWidth = window.innerWidth || document.documentElement.clientWidth;
|
||||
if (top >= 0 &&
|
||||
left >= 0 &&
|
||||
bottom <= viewportHeight &&
|
||||
right <= viewportWidth) {
|
||||
this.originFocus.focus();
|
||||
}
|
||||
// if (!this.originFocus || !this.originFocus.getBoundingClientRect) return;
|
||||
//
|
||||
// // restore keyboard focus
|
||||
// const { top, left, bottom, right } = this.originFocus.getBoundingClientRect();
|
||||
// const viewportHeight = window.innerHeight || document.documentElement.clientHeight;
|
||||
// const viewportWidth = window.innerWidth || document.documentElement.clientWidth;
|
||||
// if (top >= 0 &&
|
||||
// left >= 0 &&
|
||||
// bottom <= viewportHeight &&
|
||||
// right <= viewportWidth) {
|
||||
// this.originFocus.focus();
|
||||
// }
|
||||
},
|
||||
|
||||
clearTimer() {
|
||||
@ -115,24 +113,15 @@
|
||||
}
|
||||
},
|
||||
keydown(e) {
|
||||
if (e.keyCode === 46 || e.keyCode === 8) {
|
||||
this.clearTimer(); // detele 取消倒计时
|
||||
} else if (e.keyCode === 27) { // esc关闭消息
|
||||
if (e.keyCode === 27) { // esc关闭消息
|
||||
if (!this.closed) {
|
||||
this.close();
|
||||
}
|
||||
} else {
|
||||
this.startTimer(); // 恢复倒计时
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.startTimer();
|
||||
this.originFocus = document.activeElement;
|
||||
this.initFocus = this.showClose ? this.$el.querySelector('.el-icon-close') : this.$el.querySelector('.el-message__content');
|
||||
setTimeout(() => {
|
||||
this.initFocus && this.initFocus.focus();
|
||||
});
|
||||
document.addEventListener('keydown', this.keydown);
|
||||
},
|
||||
beforeDestroy() {
|
||||
|
@ -6,7 +6,9 @@
|
||||
:style="positionStyle"
|
||||
@mouseenter="clearTimer()"
|
||||
@mouseleave="startTimer()"
|
||||
@click="click">
|
||||
@click="click"
|
||||
role="alert"
|
||||
>
|
||||
<i
|
||||
class="el-notification__icon"
|
||||
:class="[ typeClass, iconClass ]"
|
||||
@ -119,9 +121,19 @@
|
||||
}
|
||||
}, this.duration);
|
||||
}
|
||||
},
|
||||
keydown(e) {
|
||||
if (e.keyCode === 46 || e.keyCode === 8) {
|
||||
this.clearTimer(); // detele 取消倒计时
|
||||
} else if (e.keyCode === 27) { // esc关闭消息
|
||||
if (!this.closed) {
|
||||
this.close();
|
||||
}
|
||||
} else {
|
||||
this.startTimer(); // 恢复倒计时
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
if (this.duration > 0) {
|
||||
this.timer = setTimeout(() => {
|
||||
@ -130,6 +142,11 @@
|
||||
}
|
||||
}, this.duration);
|
||||
}
|
||||
document.addEventListener('keydown', this.keydown);
|
||||
},
|
||||
beforeDestroy() {
|
||||
document.removeEventListener('keydown', this.keydown);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
|
@ -6,7 +6,11 @@
|
||||
:class="[popperClass, content && 'el-popover--plain']"
|
||||
ref="popper"
|
||||
v-show="!disabled && showPopper"
|
||||
:style="{ width: width + 'px' }">
|
||||
:style="{ width: width + 'px' }"
|
||||
role="tooltip"
|
||||
:id="tooltipId"
|
||||
:aria-hidden="(disabled || !showPopper) ? 'true' : 'false'"
|
||||
>
|
||||
<div class="el-popover__title" v-if="title" v-text="title"></div>
|
||||
<slot>{{ content }}</slot>
|
||||
</div>
|
||||
@ -14,10 +18,10 @@
|
||||
<slot name="reference"></slot>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Popper from 'element-ui/src/utils/vue-popper';
|
||||
import { on, off } from 'element-ui/src/utils/dom';
|
||||
import { generateId } from 'element-ui/src/utils/util';
|
||||
|
||||
export default {
|
||||
name: 'ElPopover',
|
||||
@ -49,6 +53,11 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
tooltipId() {
|
||||
return `el-popover-${generateId()}`;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
showPopper(newVal, oldVal) {
|
||||
newVal ? this.$emit('show') : this.$emit('hide');
|
||||
@ -62,12 +71,23 @@ export default {
|
||||
},
|
||||
|
||||
mounted() {
|
||||
let reference = this.reference || this.$refs.reference;
|
||||
let reference = this.referenceElm = this.reference || this.$refs.reference;
|
||||
const popper = this.popper || this.$refs.popper;
|
||||
|
||||
if (!reference && this.$slots.reference && this.$slots.reference[0]) {
|
||||
reference = this.referenceElm = this.$slots.reference[0].elm;
|
||||
}
|
||||
// 可访问性
|
||||
if (reference) {
|
||||
reference.className += ' el-tooltip';
|
||||
reference.setAttribute('aria-describedby', this.tooltipId);
|
||||
reference.setAttribute('tabindex', 0); // tab序列
|
||||
|
||||
on(reference, 'focus', this.handleFocus);
|
||||
on(reference, 'blur', this.handleBlur);
|
||||
on(reference, 'keydown', this.handleKeydown);
|
||||
on(reference, 'click', this.handleClick);
|
||||
}
|
||||
if (this.trigger === 'click') {
|
||||
on(reference, 'click', this.doToggle);
|
||||
on(document, 'click', this.handleDocumentClick);
|
||||
@ -114,6 +134,20 @@ export default {
|
||||
doClose() {
|
||||
this.showPopper = false;
|
||||
},
|
||||
handleFocus() {
|
||||
const reference = this.referenceElm;
|
||||
reference.className += ' focusing';
|
||||
this.showPopper = true;
|
||||
},
|
||||
handleClick() {
|
||||
const reference = this.referenceElm;
|
||||
reference.className = reference.className.replace(/\s*focusing\s*/, ' ');
|
||||
},
|
||||
handleBlur() {
|
||||
const reference = this.referenceElm;
|
||||
reference.className = reference.className.replace(/\s*focusing\s*/, ' ');
|
||||
this.showPopper = false;
|
||||
},
|
||||
handleMouseEnter() {
|
||||
clearTimeout(this._timer);
|
||||
if (this.openDelay) {
|
||||
@ -124,6 +158,11 @@ export default {
|
||||
this.showPopper = true;
|
||||
}
|
||||
},
|
||||
handleKeydown(ev) {
|
||||
if (ev.keyCode === 27) { // esc
|
||||
this.doClose();
|
||||
}
|
||||
},
|
||||
handleMouseLeave() {
|
||||
clearTimeout(this._timer);
|
||||
this._timer = setTimeout(() => {
|
||||
|
@ -2,6 +2,9 @@
|
||||
@import "common/var";
|
||||
|
||||
@include b(tooltip) {
|
||||
&:focus:not(.focusing), &:focus:hover {
|
||||
outline-width: 0;
|
||||
}
|
||||
@include e(popper) {
|
||||
position: absolute;
|
||||
border-radius: 4px;
|
||||
|
@ -1,6 +1,7 @@
|
||||
import Popper from 'element-ui/src/utils/vue-popper';
|
||||
import debounce from 'throttle-debounce/debounce';
|
||||
import { getFirstComponentChild } from 'element-ui/src/utils/vdom';
|
||||
import { generateId } from 'element-ui/src/utils/util';
|
||||
import Vue from 'vue';
|
||||
|
||||
export default {
|
||||
@ -48,10 +49,15 @@ export default {
|
||||
|
||||
data() {
|
||||
return {
|
||||
timeoutPending: null
|
||||
timeoutPending: null,
|
||||
focusing: false
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
tooltipId() {
|
||||
return `el-tooltip-${generateId()}`;
|
||||
}
|
||||
},
|
||||
beforeCreate() {
|
||||
if (this.$isServer) return;
|
||||
|
||||
@ -75,6 +81,9 @@ export default {
|
||||
onMouseleave={ () => { this.setExpectedState(false); this.debounceClose(); } }
|
||||
onMouseenter= { () => { this.setExpectedState(true); } }
|
||||
ref="popper"
|
||||
role="tooltip"
|
||||
id={this.tooltipId}
|
||||
aria-hidden={ (this.disabled || !this.showPopper) ? 'true' : 'false' }
|
||||
v-show={!this.disabled && this.showPopper}
|
||||
class={
|
||||
['el-tooltip__popper', 'is-' + this.effect, this.popperClass]
|
||||
@ -87,24 +96,38 @@ export default {
|
||||
if (!this.$slots.default || !this.$slots.default.length) return this.$slots.default;
|
||||
|
||||
const vnode = getFirstComponentChild(this.$slots.default);
|
||||
|
||||
if (!vnode) return vnode;
|
||||
|
||||
const data = vnode.data = vnode.data || {};
|
||||
const on = vnode.data.on = vnode.data.on || {};
|
||||
const nativeOn = vnode.data.nativeOn = vnode.data.nativeOn || {};
|
||||
|
||||
data.staticClass = this.concatClass(data.staticClass, 'el-tooltip');
|
||||
on.mouseenter = this.addEventHandle(on.mouseenter, this.show);
|
||||
on.mouseleave = this.addEventHandle(on.mouseleave, this.hide);
|
||||
nativeOn.mouseenter = this.addEventHandle(nativeOn.mouseenter, this.show);
|
||||
nativeOn.mouseleave = this.addEventHandle(nativeOn.mouseleave, this.hide);
|
||||
|
||||
nativeOn.mouseenter = on.mouseenter = this.addEventHandle(on.mouseenter, this.show);
|
||||
nativeOn.mouseleave = on.mouseleave = this.addEventHandle(on.mouseleave, this.hide);
|
||||
nativeOn.focus = on.focus = this.addEventHandle(on.focus, this.handleFocus);
|
||||
nativeOn.blur = on.blur = this.addEventHandle(on.blur, this.handleBlur);
|
||||
nativeOn.click = on.click = this.addEventHandle(on.click, () => { this.focusing = false; });
|
||||
return vnode;
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.referenceElm = this.$el;
|
||||
if (this.$el.nodeType === 1) {
|
||||
this.$el.setAttribute('aria-describedby', this.tooltipId);
|
||||
this.$el.setAttribute('tabindex', 0);
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
focusing(val) {
|
||||
if (val) {
|
||||
this.referenceElm.className += ' focusing';
|
||||
} else {
|
||||
this.referenceElm.className = this.referenceElm.className.replace('focusing', '');
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
show() {
|
||||
this.setExpectedState(true);
|
||||
@ -115,7 +138,14 @@ export default {
|
||||
this.setExpectedState(false);
|
||||
this.debounceClose();
|
||||
},
|
||||
|
||||
handleFocus() {
|
||||
this.focusing = true;
|
||||
this.show();
|
||||
},
|
||||
handleBlur() {
|
||||
this.focusing = false;
|
||||
this.hide();
|
||||
},
|
||||
addEventHandle(old, fn) {
|
||||
if (!old) {
|
||||
return fn;
|
||||
|
Loading…
Reference in New Issue
Block a user