add accessibility for input & rate & collapse & progress & upload (#7196)

pull/7292/head
maranran 2017-09-29 15:58:07 +08:00 committed by 杨奕
parent 5ce0e22823
commit d66473f005
21 changed files with 248 additions and 53 deletions

View File

@ -163,7 +163,7 @@ Use attribute `size` to set additional sizes with `medium`, `small` or `mini`.
|debounce| debounce delay when typing, in millisecond | number | — | 300 | |debounce| debounce delay when typing, in millisecond | number | — | 300 |
|controls-position | position of the control buttons | string | right | - | |controls-position | position of the control buttons | string | right | - |
|name | same as `name` in native input | string | — | — | |name | same as `name` in native input | string | — | — |
|label | label text | string | — | — |
### Events ### Events
| Event Name | Description | Parameters | | Event Name | Description | Parameters |

View File

@ -630,7 +630,7 @@ Search data from server-side.
|autofocus | same as `autofocus` in native input | boolean | — | false | |autofocus | same as `autofocus` in native input | boolean | — | false |
|form | same as `form` in native input | string | — | — | |form | same as `form` in native input | string | — | — |
| on-icon-click | hook function when clicking on the input icon | function | — | — | | on-icon-click | hook function when clicking on the input icon | function | — | — |
| label | label text | string | — | — |
### Input slot ### Input slot
| Name | Description | | Name | Description |
@ -663,7 +663,7 @@ Attribute | Description | Type | Options | Default
| on-icon-click | hook function when clicking on the input icon | function | — | — | | on-icon-click | hook function when clicking on the input icon | function | — | — |
| name | same as `name` in native input | string | — | — | | name | same as `name` in native input | string | — | — |
| select-when-unmatched | whether to emit a `select` event on enter when there is no autocomplete match | boolean | — | false | | select-when-unmatched | whether to emit a `select` event on enter when there is no autocomplete match | boolean | — | false |
| label | label text | string | — | — |
### props ### props
| Attribute | Description | Type | Accepted Values | Default | | Attribute | Description | Type | Accepted Values | Default |
| --------- | ----------------- | ------ | ------ | ------ | | --------- | ----------------- | ------ | ------ | ------ |

View File

@ -36,7 +36,7 @@
:::demo 要使用它,只需要在`el-input-number`元素中使用`v-model`绑定变量即可,变量的初始值即为默认值。 :::demo 要使用它,只需要在`el-input-number`元素中使用`v-model`绑定变量即可,变量的初始值即为默认值。
```html ```html
<template> <template>
<el-input-number v-model="num1" @change="handleChange" :min="1" :max="10"></el-input-number> <el-input-number v-model="num1" @change="handleChange" :min="1" :max="10" label="描述文字"></el-input-number>
</template> </template>
<script> <script>
export default { export default {
@ -162,7 +162,7 @@
| debounce | 输入时的去抖延迟,毫秒 | number | — | 300 | | debounce | 输入时的去抖延迟,毫秒 | number | — | 300 |
| controls-position | 控制按钮位置 | string | right | - | | controls-position | 控制按钮位置 | string | right | - |
| name | 原生属性 | string | — | — | | name | 原生属性 | string | — | — |
| label | 输入框关联的label文字 | string | — | — |
### Events ### Events
| 事件名称 | 说明 | 回调参数 | | 事件名称 | 说明 | 回调参数 |
|---------|--------|---------| |---------|--------|---------|

View File

@ -785,7 +785,7 @@ export default {
| resize | 控制是否能被用户缩放 | string | none, both, horizontal, vertical | — | | resize | 控制是否能被用户缩放 | string | none, both, horizontal, vertical | — |
| autofocus | 原生属性,自动获取焦点 | boolean | true, false | false | | autofocus | 原生属性,自动获取焦点 | boolean | true, false | false |
| form | 原生属性 | string | — | — | | form | 原生属性 | string | — | — |
| label | 输入框关联的label文字 | string | — | — |
### Input slot ### Input slot
| name | 说明 | | name | 说明 |
|------|--------| |------|--------|
@ -821,7 +821,7 @@ export default {
| icon | 输入框尾部图标 | string | — | — | | icon | 输入框尾部图标 | string | — | — |
| name | 原生属性 | string | — | — | | name | 原生属性 | string | — | — |
| select-when-unmatched | 在输入没有任何匹配建议的情况下,按下回车是否触发 `select` 事件 | boolean | — | false | | select-when-unmatched | 在输入没有任何匹配建议的情况下,按下回车是否触发 `select` 事件 | boolean | — | false |
| label | 输入框关联的label文字 | string | — | — |
### props ### props
| 参数 | 说明 | 类型 | 可选值 | 默认值 | | 参数 | 说明 | 类型 | 可选值 | 默认值 |
| -------- | ----------------- | ------ | ------ | ------ | | -------- | ----------------- | ------ | ------ | ------ |

View File

@ -5,6 +5,7 @@
class="el-autocomplete-suggestion el-popper" class="el-autocomplete-suggestion el-popper"
:class="{ 'is-loading': parent.loading }" :class="{ 'is-loading': parent.loading }"
:style="{ width: dropdownWidth }" :style="{ width: dropdownWidth }"
role="region"
> >
<el-scrollbar <el-scrollbar
tag="ul" tag="ul"
@ -44,7 +45,8 @@
gpuAcceleration: false gpuAcceleration: false
}; };
} }
} },
id: String
}, },
methods: { methods: {
@ -62,6 +64,9 @@
mounted() { mounted() {
this.$parent.popperElm = this.popperElm = this.$el; this.$parent.popperElm = this.popperElm = this.$el;
this.referenceElm = this.$parent.$refs.input.$refs.input; this.referenceElm = this.$parent.$refs.input.$refs.input;
this.referenceList = this.$el.querySelector('.el-autocomplete-suggestion__list');
this.referenceList.setAttribute('role', 'listbox');
this.referenceList.setAttribute('id', this.id);
}, },
created() { created() {

View File

@ -1,5 +1,12 @@
<template> <template>
<div class="el-autocomplete" v-clickoutside="close"> <div
class="el-autocomplete"
v-clickoutside="close"
aria-haspopup="listbox"
role="combobox"
:aria-expanded="suggestionVisible"
:aria-owns="id"
>
<el-input <el-input
ref="input" ref="input"
v-bind="$props" v-bind="$props"
@ -13,6 +20,7 @@
@keydown.down.native.prevent="highlight(highlightedIndex + 1)" @keydown.down.native.prevent="highlight(highlightedIndex + 1)"
@keydown.enter.native="handleKeyEnter" @keydown.enter.native="handleKeyEnter"
@keydown.native.tab="close" @keydown.native.tab="close"
:label="label"
> >
<template slot="prepend" v-if="$slots.prepend"> <template slot="prepend" v-if="$slots.prepend">
<slot name="prepend"></slot> <slot name="prepend"></slot>
@ -24,12 +32,17 @@
<el-autocomplete-suggestions <el-autocomplete-suggestions
visible-arrow visible-arrow
:class="[popperClass ? popperClass : '']" :class="[popperClass ? popperClass : '']"
ref="suggestions"> ref="suggestions"
:id="id">
<li <li
v-for="(item, index) in suggestions" v-for="(item, index) in suggestions"
:key="index" :key="index"
:class="{'highlighted': highlightedIndex === index}" :class="{'highlighted': highlightedIndex === index}"
@click="select(item)"> @click="select(item)"
:id="`${id}-item-${index}`"
role="option"
:aria-selected="highlightedIndex === index"
>
<slot :item="item"> <slot :item="item">
{{ item[props.label] }} {{ item[props.label] }}
</slot> </slot>
@ -42,6 +55,7 @@
import Clickoutside from 'element-ui/src/utils/clickoutside'; import Clickoutside from 'element-ui/src/utils/clickoutside';
import ElAutocompleteSuggestions from './autocomplete-suggestions.vue'; import ElAutocompleteSuggestions from './autocomplete-suggestions.vue';
import Emitter from 'element-ui/src/mixins/emitter'; import Emitter from 'element-ui/src/mixins/emitter';
import { generateId } from 'element-ui/src/utils/util';
export default { export default {
name: 'ElAutocomplete', name: 'ElAutocomplete',
@ -85,7 +99,8 @@
selectWhenUnmatched: { selectWhenUnmatched: {
type: Boolean, type: Boolean,
default: false default: false
} },
label: String
}, },
data() { data() {
return { return {
@ -101,6 +116,9 @@
const suggestions = this.suggestions; const suggestions = this.suggestions;
let isValidData = Array.isArray(suggestions) && suggestions.length > 0; let isValidData = Array.isArray(suggestions) && suggestions.length > 0;
return (isValidData || this.loading) && this.activated; return (isValidData || this.loading) && this.activated;
},
id() {
return `el-autocomplete-${generateId()}`;
} }
}, },
watch: { watch: {
@ -191,14 +209,19 @@
if (offsetTop < scrollTop) { if (offsetTop < scrollTop) {
suggestion.scrollTop -= highlightItem.scrollHeight; suggestion.scrollTop -= highlightItem.scrollHeight;
} }
this.highlightedIndex = index; this.highlightedIndex = index;
this.$el.querySelector('.el-input__inner').setAttribute('aria-activedescendant', `${this.id}-item-${this.highlightedIndex}`);
} }
}, },
mounted() { mounted() {
this.$on('item-click', item => { this.$on('item-click', item => {
this.select(item); this.select(item);
}); });
let $input = this.$el.querySelector('.el-input__inner');
$input.setAttribute('role', 'textbox');
$input.setAttribute('aria-autocomplete', 'list');
$input.setAttribute('aria-controls', 'id');
$input.setAttribute('aria-activedescendant', `${this.id}-item-${this.highlightedIndex}`);
}, },
beforeDestroy() { beforeDestroy() {
this.$refs.suggestions.$destroy(); this.$refs.suggestions.$destroy();

View File

@ -1,6 +1,5 @@
<template> <template>
<button <button
v-bind="$props"
class="el-button" class="el-button"
@click="handleClick" @click="handleClick"
:type="nativeType" :type="nativeType"

View File

@ -1,11 +1,35 @@
<template> <template>
<div class="el-collapse-item" :class="{'is-active': isActive}"> <div class="el-collapse-item" :class="{'is-active': isActive}">
<div class="el-collapse-item__header" @click="handleHeaderClick"> <div
<i class="el-collapse-item__arrow el-icon-arrow-right"></i> role="tab"
<slot name="title">{{title}}</slot> :aria-expanded="isActive"
:aria-controls="`el-collapse-content-${id}`"
:aria-describedby ="`el-collapse-content-${id}`"
>
<div
class="el-collapse-item__header"
@click="handleHeaderClick"
role="button"
:id="`el-collapse-head-${id}`"
tabindex="0"
@keyup.space.enter.stop="handleEnterClick"
:class="{'focusing': focusing}"
@focus="focusing = true"
@blur="focusing = false"
>
<i class="el-collapse-item__arrow el-icon-arrow-right"></i>
<slot name="title">{{title}}</slot>
</div>
</div> </div>
<el-collapse-transition> <el-collapse-transition>
<div class="el-collapse-item__wrap" v-show="isActive"> <div
class="el-collapse-item__wrap"
v-show="isActive"
role="tabpanel"
:aria-hidden="!isActive"
:aria-labelledby="`el-collapse-head-${id}`"
:id="`el-collapse-content-${id}`"
>
<div class="el-collapse-item__content"> <div class="el-collapse-item__content">
<slot></slot> <slot></slot>
</div> </div>
@ -16,6 +40,7 @@
<script> <script>
import ElCollapseTransition from 'element-ui/src/transitions/collapse-transition'; import ElCollapseTransition from 'element-ui/src/transitions/collapse-transition';
import Emitter from 'element-ui/src/mixins/emitter'; import Emitter from 'element-ui/src/mixins/emitter';
import { generateId } from 'element-ui/src/utils/util';
export default { export default {
name: 'ElCollapseItem', name: 'ElCollapseItem',
@ -32,7 +57,8 @@
height: 'auto', height: 'auto',
display: 'block' display: 'block'
}, },
contentHeight: 0 contentHeight: 0,
focusing: false
}; };
}, },
@ -49,6 +75,9 @@
computed: { computed: {
isActive() { isActive() {
return this.$parent.activeNames.indexOf(this.name) > -1; return this.$parent.activeNames.indexOf(this.name) > -1;
},
id() {
return generateId();
} }
}, },
@ -60,6 +89,10 @@
methods: { methods: {
handleHeaderClick() { handleHeaderClick() {
this.dispatch('ElCollapse', 'item-click', this); this.dispatch('ElCollapse', 'item-click', this);
this.focusing = false;
},
handleEnterClick() {
this.dispatch('ElCollapse', 'item-click', this);
} }
}, },

View File

@ -1,5 +1,5 @@
<template> <template>
<div class="el-collapse"> <div class="el-collapse" role="tablist" aria-multiselectable="true">
<slot></slot> <slot></slot>
</div> </div>
</template> </template>

View File

@ -12,6 +12,8 @@
class="el-input-number__decrease" class="el-input-number__decrease"
:class="{'is-disabled': minDisabled}" :class="{'is-disabled': minDisabled}"
v-repeat-click="decrease" v-repeat-click="decrease"
@keydown.enter="decrease"
role="button"
> >
<i :class="`el-icon-${controlsAtRight ? 'arrow-down' : 'minus'}`"></i> <i :class="`el-icon-${controlsAtRight ? 'arrow-down' : 'minus'}`"></i>
</span> </span>
@ -20,6 +22,8 @@
class="el-input-number__increase" class="el-input-number__increase"
:class="{'is-disabled': maxDisabled}" :class="{'is-disabled': maxDisabled}"
v-repeat-click="increase" v-repeat-click="increase"
@keydown.enter="increase"
role="button"
> >
<i :class="`el-icon-${controlsAtRight ? 'arrow-up' : 'plus'}`"></i> <i :class="`el-icon-${controlsAtRight ? 'arrow-up' : 'plus'}`"></i>
</span> </span>
@ -36,6 +40,7 @@
:min="min" :min="min"
:name="name" :name="name"
ref="input" ref="input"
:label="label"
> >
<template slot="prepend" v-if="$slots.prepend"> <template slot="prepend" v-if="$slots.prepend">
<slot name="prepend"></slot> <slot name="prepend"></slot>
@ -111,7 +116,8 @@
type: Number, type: Number,
default: 300 default: 300
}, },
name: String name: String,
label: String
}, },
data() { data() {
return { return {
@ -223,6 +229,18 @@
this.debounceHandleInput = debounce(this.debounce, value => { this.debounceHandleInput = debounce(this.debounce, value => {
this.handleInput(value); this.handleInput(value);
}); });
},
mounted() {
let innerInput = this.$refs.input.$refs.input;
innerInput.setAttribute('role', 'spinbutton');
innerInput.setAttribute('aria-valuemax', this.max);
innerInput.setAttribute('aria-valuemin', this.min);
innerInput.setAttribute('aria-valuenow', this.currentValue);
innerInput.setAttribute('aria-disabled', this.disabled);
},
updated() {
let innerInput = this.$refs.input.$refs.input;
innerInput.setAttribute('aria-valuenow', this.currentValue);
} }
}; };
</script> </script>

View File

@ -13,17 +13,9 @@
]"> ]">
<template v-if="type !== 'textarea'"> <template v-if="type !== 'textarea'">
<!-- 前置元素 --> <!-- 前置元素 -->
<div class="el-input-group__prepend" v-if="$slots.prepend"> <div class="el-input-group__prepend" v-if="$slots.prepend" tabindex="0">
<slot name="prepend"></slot> <slot name="prepend"></slot>
</div> </div>
<!-- 前置内容 -->
<span class="el-input__prefix" v-if="$slots.prefix || prefixIcon">
<slot name="prefix"></slot>
<i class="el-input__icon"
v-if="prefixIcon"
:class="prefixIcon">
</i>
</span>
<input <input
v-if="type !== 'textarea'" v-if="type !== 'textarea'"
class="el-input__inner" class="el-input__inner"
@ -34,7 +26,16 @@
@input="handleInput" @input="handleInput"
@focus="handleFocus" @focus="handleFocus"
@blur="handleBlur" @blur="handleBlur"
:aria-label="label"
> >
<!-- 前置内容 -->
<span class="el-input__prefix" v-if="$slots.prefix || prefixIcon">
<slot name="prefix"></slot>
<i class="el-input__icon"
v-if="prefixIcon"
:class="prefixIcon">
</i>
</span>
<!-- 后置内容 --> <!-- 后置内容 -->
<span class="el-input__suffix" v-if="$slots.suffix || suffixIcon || validateState"> <span class="el-input__suffix" v-if="$slots.suffix || suffixIcon || validateState">
<span class="el-input__suffix-inner"> <span class="el-input__suffix-inner">
@ -63,7 +64,9 @@
v-bind="$props" v-bind="$props"
:style="textareaStyle" :style="textareaStyle"
@focus="handleFocus" @focus="handleFocus"
@blur="handleBlur"> @blur="handleBlur"
:aria-label="label"
>
</textarea> </textarea>
</div> </div>
</template> </template>
@ -127,7 +130,8 @@
}, },
onIconClick: Function, onIconClick: Function,
suffixIcon: String, suffixIcon: String,
prefixIcon: String prefixIcon: String,
label: String
}, },
computed: { computed: {

View File

@ -9,6 +9,10 @@
'el-progress--text-inside': textInside, 'el-progress--text-inside': textInside,
} }
]" ]"
role="progressbar"
:aria-valuenow="percentage"
aria-valuemin="0"
aria-valuemax="100"
> >
<div class="el-progress-bar" v-if="type === 'line'"> <div class="el-progress-bar" v-if="type === 'line'">
<div class="el-progress-bar__outer" :style="{height: strokeWidth + 'px'}"> <div class="el-progress-bar__outer" :style="{height: strokeWidth + 'px'}">

View File

@ -1,12 +1,24 @@
<template> <template>
<div class="el-rate"> <div class="el-rate"
@keydown="handelKey"
role="slider"
:aria-valuenow="currentValue"
:aria-valuetext="text"
aria-valuemin="0"
:aria-valuemin="max"
tabindex="0"
@focus="focusing = true"
@blur="focusing = false"
:class="{'focusing': focusing}"
>
<span <span
v-for="item in max" v-for="item in max"
class="el-rate__item" class="el-rate__item"
@mousemove="setCurrentValue(item, $event)" @mousemove="setCurrentValue(item, $event)"
@mouseleave="resetCurrentValue" @mouseleave="resetCurrentValue"
@click="selectValue(item)" @click="selectValue(item)"
:style="{ cursor: disabled ? 'auto' : 'pointer' }"> :style="{ cursor: disabled ? 'auto' : 'pointer' }"
>
<i <i
:class="[classes[item - 1], { 'hover': hoverIndex === item }]" :class="[classes[item - 1], { 'hover': hoverIndex === item }]"
class="el-rate__icon" class="el-rate__icon"
@ -34,7 +46,8 @@
classMap: {}, classMap: {},
pointerAtLeftHalf: true, pointerAtLeftHalf: true,
currentValue: this.value, currentValue: this.value,
hoverIndex: -1 hoverIndex: -1,
focusing: false
}; };
}, },
@ -237,6 +250,34 @@
this.$emit('input', value); this.$emit('input', value);
this.$emit('change', value); this.$emit('change', value);
} }
this.focusing = false;
},
handelKey(e) {
let currentValue = this.currentValue;
const keyCode = e.keyCode;
if (keyCode === 38 || keyCode === 39) { // left / down
if (this.allowHalf) {
currentValue += 0.5;
} else {
currentValue += 1;
}
e.stopPropagation();
e.preventDefault();
} else if (keyCode === 37 || keyCode === 40) {
if (this.allowHalf) {
currentValue -= 0.5;
} else {
currentValue -= 1;
}
e.stopPropagation();
e.preventDefault();
}
currentValue = currentValue < 0 ? 0 : currentValue;
currentValue = currentValue > this.max ? this.max : currentValue;
this.$emit('input', currentValue);
this.$emit('change', currentValue);
}, },
setCurrentValue(value, event) { setCurrentValue(value, event) {

View File

@ -1,5 +1,12 @@
<template> <template>
<label class="el-switch" :class="{ 'is-disabled': disabled, 'is-checked': checked }"> <div
class="el-switch"
:class="{ 'is-disabled': disabled, 'is-checked': checked }"
role="switch"
:aria-checked="checked"
:aria-disabled="disabled"
@click="switchValue"
>
<input <input
class="el-switch__input" class="el-switch__input"
type="checkbox" type="checkbox"
@ -8,12 +15,14 @@
:name="name" :name="name"
:true-value="onValue" :true-value="onValue"
:false-value="offValue" :false-value="offValue"
:disabled="disabled"> :disabled="disabled"
@keydown.enter="switchValue"
>
<span <span
:class="['el-switch__label', 'el-switch__label--left', !checked ? 'is-active' : '']" :class="['el-switch__label', 'el-switch__label--left', !checked ? 'is-active' : '']"
v-if="offIconClass || offText"> v-if="offIconClass || offText">
<i :class="[offIconClass]" v-if="offIconClass"></i> <i :class="[offIconClass]" v-if="offIconClass"></i>
<span v-if="!offIconClass && offText">{{ offText }}</span> <span v-if="!offIconClass && offText" :aria-hidden="checked">{{ offText }}</span>
</span> </span>
<span class="el-switch__core" ref="core" :style="{ 'width': coreWidth + 'px' }"> <span class="el-switch__core" ref="core" :style="{ 'width': coreWidth + 'px' }">
<span class="el-switch__button" :style="{ transform }"></span> <span class="el-switch__button" :style="{ transform }"></span>
@ -22,11 +31,10 @@
:class="['el-switch__label', 'el-switch__label--right', checked ? 'is-active' : '']" :class="['el-switch__label', 'el-switch__label--right', checked ? 'is-active' : '']"
v-if="onIconClass || onText"> v-if="onIconClass || onText">
<i :class="[onIconClass]" v-if="onIconClass"></i> <i :class="[onIconClass]" v-if="onIconClass"></i>
<span v-if="!onIconClass && onText">{{ onText }}</span> <span v-if="!onIconClass && onText" :aria-hidden="!checked">{{ onText }}</span>
</span> </span>
</label> </div>
</template> </template>
<script> <script>
export default { export default {
name: 'ElSwitch', name: 'ElSwitch',
@ -114,6 +122,9 @@
let newColor = this.checked ? this.onColor : this.offColor; let newColor = this.checked ? this.onColor : this.offColor;
this.$refs.core.style.borderColor = newColor; this.$refs.core.style.borderColor = newColor;
this.$refs.core.style.backgroundColor = newColor; this.$refs.core.style.backgroundColor = newColor;
},
switchValue() {
this.$refs.input.click();
} }
}, },
mounted() { mounted() {

View File

@ -16,6 +16,9 @@
font-size: $--collapse-header-size; font-size: $--collapse-header-size;
font-weight: 500; font-weight: 500;
transition: border-bottom-color .3s; transition: border-bottom-color .3s;
&:focus:not(.focusing), &:active {
outline-width: 0;
}
@include e(arrow) { @include e(arrow) {
margin-right: 8px; margin-right: 8px;
@ -42,9 +45,8 @@
} }
@include when(active) { @include when(active) {
> .el-collapse-item__header { .el-collapse-item__header {
border-bottom-color: transparent; border-bottom-color: transparent;
.el-collapse-item__arrow { .el-collapse-item__arrow {
transform: rotate(90deg); transform: rotate(90deg);
} }

View File

@ -5,6 +5,10 @@
height: $--rate-height; height: $--rate-height;
line-height: 1; line-height: 1;
&:focus:not(.focusing), &:active {
outline-width: 0;
}
@include e(item) { @include e(item) {
display: inline-block; display: inline-block;
position: relative; position: relative;

View File

@ -43,7 +43,13 @@
} }
@include e(input) { @include e(input) {
display: none; position: absolute;
width: 0;
height: 0;
opacity: 0;
&:focus ~ .el-switch__core {
outline: 1px solid #f00;
}
} }
@include e(core) { @include e(core) {

View File

@ -156,6 +156,17 @@
} }
} }
& .el-icon-close-tip {
display: none;
position: absolute;
top: 5px;
right: 0;
cursor: pointer;
opacity: 1;
color: $--color-primary;
transform: translate(15%,0) scale(.7);
}
&:hover { &:hover {
background-color: $--background-color-base; background-color: $--background-color-base;
@ -173,12 +184,25 @@
display: block; display: block;
} }
.el-upload-list__item-name:hover { .el-upload-list__item-name:hover, .el-upload-list__item-name:focus {
color: $--link-hover-color; color: $--link-hover-color;
cursor: pointer; cursor: pointer;
} }
&:hover { &:focus {
.el-icon-close-tip {
display: inline-block;
}
}
&:focus:not(.focusing), &:active {
outline-width: 0;
.el-icon-close-tip {
display: none;
}
}
&:hover, &:focus { /*键盘焦点时 显示提示文字 focus*/
.el-upload-list__item-status-label { .el-upload-list__item-status-label {
display: none; display: none;
} }
@ -255,7 +279,6 @@
.el-icon-close { .el-icon-close {
display: none; display: none;
} }
&:hover { &:hover {
.el-upload-list__item-status-label { .el-upload-list__item-status-label {
display: none; display: none;
@ -378,7 +401,6 @@
.el-upload-list__item-name { .el-upload-list__item-name {
line-height: 70px; line-height: 70px;
margin-top: 0; margin-top: 0;
i { i {
display: none; display: none;
} }

View File

@ -10,8 +10,13 @@
> >
<li <li
v-for="(file, index) in files" v-for="(file, index) in files"
:class="['el-upload-list__item', 'is-' + file.status]" :class="['el-upload-list__item', 'is-' + file.status, focusing ? 'focusing' : '']"
:key="index" :key="index"
tabindex="0"
@keydown.delete="$emit('remove', file)"
@focus="focusing = true"
@blur="focusing = false"
@click="focusing = false"
> >
<img <img
class="el-upload-list__item-thumbnail" class="el-upload-list__item-thumbnail"
@ -29,13 +34,14 @@
}"></i> }"></i>
</label> </label>
<i class="el-icon-close" v-if="!disabled" @click="$emit('remove', file)"></i> <i class="el-icon-close" v-if="!disabled" @click="$emit('remove', file)"></i>
<i class="el-icon-close-tip" v-if="!disabled">delete</i> <!--closeli:focus display, li blur focus close-->
<el-progress <el-progress
v-if="file.status === 'uploading'" v-if="file.status === 'uploading'"
:type="listType === 'picture-card' ? 'circle' : 'line'" :type="listType === 'picture-card' ? 'circle' : 'line'"
:stroke-width="listType === 'picture-card' ? 6 : 2" :stroke-width="listType === 'picture-card' ? 6 : 2"
:percentage="parsePercentage(file.percentage)"> :percentage="parsePercentage(file.percentage)">
</el-progress> </el-progress>
<span class="el-upload-list__item-actions" v-if="listType === 'picture-card'"> <span class="el-upload-list__item-actions" v-if="listType === 'picture-card'">
<span <span
class="el-upload-list__item-preview" class="el-upload-list__item-preview"
v-if="handlePreview && listType === 'picture-card'" v-if="handlePreview && listType === 'picture-card'"
@ -61,6 +67,11 @@
export default { export default {
mixins: [Locale], mixins: [Locale],
data() {
return {
focusing: false
};
},
components: { ElProgress }, components: { ElProgress },
props: { props: {

View File

@ -145,6 +145,11 @@ export default {
this.$refs.input.value = null; this.$refs.input.value = null;
this.$refs.input.click(); this.$refs.input.click();
} }
},
handleKeydown(e) {
if (e.keyCode === 13 || e.keyCode === 32) {
this.handleClick();
}
} }
}, },
@ -158,19 +163,21 @@ export default {
accept, accept,
listType, listType,
uploadFiles, uploadFiles,
disabled disabled,
handleKeydown
} = this; } = this;
const data = { const data = {
class: { class: {
'el-upload': true 'el-upload': true
}, },
on: { on: {
click: handleClick click: handleClick,
keydown: handleKeydown
} }
}; };
data.class[`el-upload--${listType}`] = true; data.class[`el-upload--${listType}`] = true;
return ( return (
<div {...data}> <div {...data} tabindex="0" >
{ {
drag drag
? <upload-dragger disabled={disabled} on-file={uploadFiles}>{this.$slots.default}</upload-dragger> ? <upload-dragger disabled={disabled} on-file={uploadFiles}>{this.$slots.default}</upload-dragger>

View File

@ -37,3 +37,8 @@ export const getValueByPath = function(object, prop) {
} }
return result; return result;
}; };
export const generateId = function() {
return Math.floor(Math.random() * 10000);
};