Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

为 DatePicker 增加键盘操作 #249

Merged
merged 1 commit into from
Mar 6, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/veui-theme-one/common.less
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ button {
cursor: not-allowed !important;
}

.veui-sr-only {
.veui-invisible();
}

a {
text-decoration: none;
}
Expand Down
6 changes: 1 addition & 5 deletions packages/veui-theme-one/components/calendar.less
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,7 @@
overflow: hidden;
border: 1px solid @veui-gray-color-6;
background-color: #fff;

&-invisible {
.size(0);
opacity: 0;
}
outline: none;

button {
.veui-button-transition();
Expand Down
11 changes: 10 additions & 1 deletion packages/veui-theme-one/components/date-picker.less
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@
}

&-clear {
display: none;
.absolute(10px, 10px, _, _);
.size(16px);
background: #fff;
Expand All @@ -44,10 +43,20 @@
cursor: pointer;
.veui-button-transition();

.veui-date-picker:hover &,
&.focus-visible {
.size(16px);
clip: auto;
}

&:hover {
color: @veui-text-color-normal;
}

&.focus-visible {
color: @veui-brand-color;
}

.veui-icon {
display: block;
}
Expand Down
11 changes: 11 additions & 0 deletions packages/veui-theme-one/mixins.less
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
@import "~less-plugin-est/src/all.less";
@import "./variables.less";

// https://github.com/twbs/bootstrap/blob/e43f97304eac2b276c755267e29de70ae2ac7afd/scss/mixins/_screen-reader.scss#L6-L15
.veui-invisible() {
position: absolute;
.size(1px);
padding: 0;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}

.veui-button-transition() {
transition-property: border-color, background-color, color, opacity;
transition-duration: .2s;
Expand Down
5 changes: 3 additions & 2 deletions packages/veui/src/components/Calendar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
ref="prev"
:class="{
'veui-calendar-prev': true,
'veui-calendar-invisible': pIndex !== 0 && p.view === 'days'
'veui-sr-only': pIndex !== 0 && p.view === 'days'
}"
:disabled="disabled || readonly"
@click="step(false, p.view)"
Expand Down Expand Up @@ -64,7 +64,7 @@
ref="next"
:class="{
'veui-calendar-next': true,
'veui-calendar-invisible': pIndex !== panels.length - 1 && p.view === 'days'
'veui-sr-only': pIndex !== panels.length - 1 && p.view === 'days'
}"
:disabled="disabled || readonly"
@click="step(true, p.view)"
Expand Down Expand Up @@ -96,6 +96,7 @@
@keydown.right.prevent="moveFocus(p.view, 1)"
@keydown.down.prevent="moveFocus(p.view, 7)"
@keydown.left.prevent="moveFocus(p.view, -1)"
:autofocus="day.isFocus"
:aria-label="getLocaleString(day)"
:tabindex="day.isFocus ? null : '-1'">
<slot name="date" v-bind="{
Expand Down
33 changes: 28 additions & 5 deletions packages/veui/src/components/DatePicker.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,13 @@
'veui-date-picker-range': range,
'veui-date-picker-expanded': expanded
}">
<veui-button ref="button" class="veui-date-picker-button" :ui="ui" :disabled="realDisabled || realReadonly" @click="expanded = !expanded">
<veui-button
ref="button"
class="veui-date-picker-button"
:ui="ui"
:disabled="realDisabled || realReadonly"
@click="expanded = !expanded"
@keydown.down.up.prevent="expanded = true">
<template v-if="range">
<span class="veui-date-picker-label">
<slot v-if="formatted" name="date" :formatted="formatted ? formatted[0] : null" :date="selected ? selected[0] : null">{{ formatted[0] }}</slot>
Expand All @@ -25,12 +31,29 @@
</template>
<veui-icon class="veui-date-picker-icon" :name="icons.calendar"></veui-icon>
</veui-button>
<button type="button" v-if="clearable" v-show="!!selected" class="veui-date-picker-clear" @click="clear">
<button v-if="clearable && !!selected" type="button" class="veui-date-picker-clear veui-sr-only" @click="clear">
<veui-icon :name="icons.clear"></veui-icon>
</button>
<veui-overlay v-if="expanded" target="button" :open="expanded" :options="realOverlayOptions" :overlay-class="overlayClass">
<veui-calendar class="veui-date-picker-overlay" v-model="localSelected" v-bind="calendarProps" ref="cal"
v-outside:button="close" @select="handleSelect" @selectstart="handleProgress" @selectprogress="handleProgress" :panel="realPanel">
<veui-overlay
v-if="expanded"
target="button"
:open="expanded"
:options="realOverlayOptions"
:overlay-class="overlayClass"
autofocus
modal>
<veui-calendar
class="veui-date-picker-overlay"
v-model="localSelected"
v-bind="calendarProps"
ref="cal"
v-outside:button="close"
@select="handleSelect"
@selectstart="handleProgress"
@selectprogress="handleProgress"
:panel="realPanel"
tabindex="-1"
@keydown.esc.native="close">
<template :slot="shortcutsPosition" v-if="range && realShortcuts && realShortcuts.length">
<div class="veui-date-picker-shortcuts">
<button v-for="({from, to, label}, index) in realShortcuts" type="button" :key="index"
Expand Down
44 changes: 29 additions & 15 deletions packages/veui/src/managers/focus.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,20 @@ class FocusContext {
* @param {=Element} preferred Where we should start searching for focusable elements
*/
constructor (root, { source, trap = false, preferred }) {
if (!root) {
throw new Error('Root must be specified to create a FocusContext instance.')
}

this.root = root
this.source = source
this.trap = trap
this.preferred = preferred

this.outsideHandler = () => {
this.focusFirst(true)
this.outsideStartHandler = () => {
this.focusAt(-2, true)
}
this.outsideEndHandler = () => {
this.focusAt(1, true)
}

this.init()
Expand All @@ -29,29 +36,37 @@ class FocusContext {
* 2. focus `root` by default
*/
init () {
this.focusFirst()
this.focusAt()

if (this.trap) {
let ward = document.createElement('div')
ward.tabIndex = 0
ward.addEventListener('focus', this.outsideHandler, true)
this.root.appendChild(ward)
this.ward = ward
let before = document.createElement('div')
before.tabIndex = 0
let after = before.cloneNode()

before.addEventListener('focus', this.outsideStartHandler, true)
after.addEventListener('focus', this.outsideEndHandler, true)

this.root.insertBefore(before, this.root.firstChild)
this.root.appendChild(after)

this.wardBefore = before
this.wardAfter = after
}
}

focusFirst (ignoreFocus) {
focusAt (index = 0, ignoreAutofocus) {
Vue.nextTick(() => {
if (!focusIn(this.preferred || this.root, ignoreFocus)) {
if (!focusIn(this.preferred || this.root, index, ignoreAutofocus)) {
this.root.focus()
}
})
}

destroy () {
let { trap, source, ward } = this
let { trap, source, wardBefore, wardAfter } = this
if (trap) {
ward.removeEventListener('focus', this.outsideHandler, true)
wardBefore.removeEventListener('focus', this.outsideStartHandler, true)
wardAfter.removeEventListener('focus', this.outsideEndHandler, true)
}
if (source && typeof source.focus === 'function') {
this.source = null
Expand All @@ -62,9 +77,8 @@ class FocusContext {
source.focus()
}, 0)
}
if (ward) {
ward.parentElement.removeChild(ward)
}
this.root.removeChild(wardBefore)
this.root.removeChild(wardAfter)
this.preferred = null
this.root = null
}
Expand Down
24 changes: 18 additions & 6 deletions packages/veui/src/utils/dom.js
Original file line number Diff line number Diff line change
Expand Up @@ -129,21 +129,33 @@ export function getFocusable (elem) {
* 将焦点移入指定元素内的第一个可聚焦的元素
*
* @param {Element} elem 需要查找的指定元素
* @param {number=} index 聚焦元素在可聚焦元素的位置
* @param {Boolean=} ignoreAutofocus 是否忽略 autofocus
* @returns {Boolean} 是否找到可聚焦的元素
*/
export function focusIn (elem, ignoreAutofocus) {
export function focusIn (elem, index = 0, ignoreAutofocus) {
if (!ignoreAutofocus) {
let auto = elem.querySelector('[autofocus]')
if (auto) {
auto.focus()
return true
}
}
let first = elem.querySelector(FOCUSABLE_SELECTOR)
if (first) {
first.focus()
return true

if (index === 0) {
let first = elem.querySelector(FOCUSABLE_SELECTOR)
if (first) {
first.focus()
return true
}
}
return false

let focusable = [...elem.querySelectorAll(FOCUSABLE_SELECTOR)]
let count = focusable.length
if (!count) {
return false
}

focusable[(index + count) % count].focus()
return true
}