Commit f9379844 authored by Jiewei Qian's avatar Jiewei Qian Committed by 杨奕

*-picker: refactor (#7367)

* Revert "Picker only emit user change (#6214)"

This reverts commit 1912c473.

* picker/util: add helper methods

range: n => Array
modify{Date, Time}: Date => Date
clear{Time, Milliseconds}: Date => Date
limitTimeRange: Date => Date
timeWithinRange: Date, [Date] => Boolean

* time-spinner: refactory

* time-panel: refactory

* picker refactory

* date-panel, *-table: refactory

* time-select: refactory

* test: update time-picker

* test: update date-picker

* time-range: refactory

* date-range: refactory

* test: update time-select

* test: update form date-picker/time-picker

* docs: update date-picker
parent 1e57f25d
...@@ -57,7 +57,11 @@ ...@@ -57,7 +57,11 @@
value4: '', value4: '',
value5: '', value5: '',
value6: '', value6: '',
value7: '' value7: '',
value8: '',
value9: '',
value10: '',
value11: ''
}; };
} }
}; };
...@@ -275,25 +279,116 @@ Picking a date range is supported. ...@@ -275,25 +279,116 @@ Picking a date range is supported.
::: :::
### Default Value
If user hasn't picked a date, shows today's calendar by default. You can use `default-value` to set another date. Its value should be parsable by `new Date()`.
If type is `daterange`, `default-value` sets the left side calendar.
:::demo
```html
<template>
<div class="block">
<span class="demonstration">date</span>
<el-date-picker
v-model="value8"
type="date"
placeholder="Pick a date"
default-value="2010-10-01">
</el-date-picker>
</div>
<div class="block">
<span class="demonstration">daterange</span>
<el-date-picker
v-model="value9"
type="daterange"
start-placeholder="Start Date"
end-placeholder="End Date"
default-value="2010-10-01">
</el-date-picker>
</div>
</template>
<script>
export default {
data() {
return {
value8: '',
value9: ''
};
}
};
</script>
```
:::
### Formatted Value
By default, DatePicker emits `Date` object. You can use `value-format` to designate the format of emitted value, it accepts the same format string of `format` attribute.
:::warning
This feature is at alpha stage. Feedback welcome.
:::
:::demo
```html
<template>
<div class="block">
<span class="demonstration">Emits Date object</span>
<div class="demonstration">Value: {{ value10 }}</div>
<el-date-picker
v-model="value10"
type="date"
placeholder="Pick a Date"
format="yyyy/MM/dd">
</el-date-picker>
</div>
<div class="block">
<span class="demonstration">Emits formatted date</span>
<div class="demonstration">Value: {{ value11 }}</div>
<el-date-picker
v-model="value11"
type="date"
placeholder="Pick a Date"
format="yyyy/MM/dd"
value-format="yyyy-MM-dd">
</el-date-picker>
</div>
</template>
<script>
export default {
data() {
return {
value10: '',
value11: '',
};
}
};
</script>
```
:::
### Attributes ### Attributes
| Attribute | Description | Type | Accepted Values | Default | | Attribute | Description | Type | Accepted Values | Default |
|---------- |-------------- |---------- |-------------------------------- |-------- | |---------- |-------------- |---------- |-------------------------------- |-------- |
| readonly | whether DatePicker is read only | boolean | — | false | | readonly | whether DatePicker is read only | boolean | — | false |
| disabled | whether DatePicker is disabled | boolean | — | false | | disabled | whether DatePicker is disabled | boolean | — | false |
|size | size of Input | string | large/small/mini | — | | size | size of Input | string | large/small/mini | — |
| editable | whether the input is editable | boolean | — | true | | editable | whether the input is editable | boolean | — | true |
| clearable | Whether to show clear button | boolean | — | true | | clearable | Whether to show clear button | boolean | — | true |
| placeholder | placeholder in non-range mode | string | — | — | | placeholder | placeholder in non-range mode | string | — | — |
| start-placeholder | placeholder for the start date in range mode | string | — | — | | start-placeholder | placeholder for the start date in range mode | string | — | — |
| end-placeholder | placeholder for the end date in range mode | string | — | — | | end-placeholder | placeholder for the end date in range mode | string | — | — |
| type | type of the picker | string | year/month/date/datetime/ week/datetimerange/daterange | date | | type | type of the picker | string | year/month/date/datetime/ week/datetimerange/daterange | date |
| format | format of the picker | string | year `yyyy` month `MM` day `dd`, hour `HH`, minute `mm`, second `ss` | yyyy-MM-dd | | format | format of the input box | string | year `yyyy`, month `MM`, day `dd`, hour `HH`, minute `mm`, second `ss` | yyyy-MM-dd |
| align | alignment | left/center/right | left | | align | alignment | left/center/right | left |
| popper-class | custom class name for DatePicker's dropdown | string | — | — | | popper-class | custom class name for DatePicker's dropdown | string | — | — |
| picker-options | additional options, check the table below | object | — | {} | | picker-options | additional options, check the table below | object | — | {} |
| range-separator | range separator | string | - | '-' | | range-separator | range separator | string | — | '-' |
| default-value | optional default time of the picker | Date | anything accepted by `new Date()` | - | | default-value | optional, default date of the calendar | Date | anything accepted by `new Date()` | — |
|name | same as `name` in native input | string | — | — | | value-format | optional, format of bounded value | string | year `yyyy`, month `MM`, day `dd`, hour `HH`, minute `mm`, second `ss` | — |
| name | same as `name` in native input | string | — | — |
### Picker Options ### Picker Options
| Attribute | Description | Type | Accepted Values | Default | | Attribute | Description | Type | Accepted Values | Default |
...@@ -313,7 +408,7 @@ Picking a date range is supported. ...@@ -313,7 +408,7 @@ Picking a date range is supported.
### Events ### Events
| Event Name | Description | Parameters | | Event Name | Description | Parameters |
|---------|--------|---------| |---------|--------|---------|
| change | triggers when input value changes | formatted value | | change | triggers when user confirms the value | component's bounded value |
| blur | triggers when Input blurs | (event: Event) | | blur | triggers when Input blurs | (event: Event) |
| focus | triggers when Input focuses | (event: Event) | | focus | triggers when Input focuses | (event: Event) |
......
...@@ -251,7 +251,9 @@ DateTimePicker is derived from DatePicker and TimePicker. For a more detailed ex ...@@ -251,7 +251,9 @@ DateTimePicker is derived from DatePicker and TimePicker. For a more detailed ex
| popper-class | custom class name for DateTimePicker's dropdown | string | — | — | | popper-class | custom class name for DateTimePicker's dropdown | string | — | — |
| picker-options | additional options, check the table below | object | — | {} | | picker-options | additional options, check the table below | object | — | {} |
| range-separator | range separator | string | - | '-' | | range-separator | range separator | string | - | '-' |
|name | same as `name` in native input | string | — | — | | default-value | optional, default date of the calendar | Date | anything accepted by `new Date()` | — |
| value-format | optional, format of bounded value | string | year `yyyy`, month `MM`, day `dd`, hour `HH`, minute `mm`, second `ss` | — |
| name | same as `name` in native input | string | — | — |
### Picker Options ### Picker Options
| Attribute | Description | Type | Accepted Values | Default | | Attribute | Description | Type | Accepted Values | Default |
...@@ -269,7 +271,7 @@ DateTimePicker is derived from DatePicker and TimePicker. For a more detailed ex ...@@ -269,7 +271,7 @@ DateTimePicker is derived from DatePicker and TimePicker. For a more detailed ex
### Events ### Events
| Event Name | Description | Parameters | | Event Name | Description | Parameters |
|---------|--------|---------| |---------|--------|---------|
| change | triggers when input value changes | formatted value | | change | triggers when user confirms the value | component's bounded value |
| blur | triggers when Input blurs | (event: Event) | | blur | triggers when Input blurs | (event: Event) |
| focus | triggers when Input focuses | (event: Event) | | focus | triggers when Input focuses | (event: Event) |
......
...@@ -161,12 +161,14 @@ Can pick an arbitrary time range. ...@@ -161,12 +161,14 @@ Can pick an arbitrary time range.
| placeholder | placeholder in non-range mode | string | — | — | | placeholder | placeholder in non-range mode | string | — | — |
| start-placeholder | placeholder for the start time in range mode | string | — | — | | start-placeholder | placeholder for the start time in range mode | string | — | — |
| end-placeholder | placeholder for the end time in range mode | string | — | — | | end-placeholder | placeholder for the end time in range mode | string | — | — |
| value | value of the picker | date for Time Picker, and string for Time Select | hour `HH`, minute `mm`, second `ss` | HH:mm:ss | | value | value of the picker | Date for Time Picker, and string for Time Select | hour `HH`, minute `mm`, second `ss` | HH:mm:ss |
| align | alignment | left / center / right | left | | align | alignment | left / center / right | left |
| popper-class | custom class name for TimePicker's dropdown | string | — | — | | popper-class | custom class name for TimePicker's dropdown | string | — | — |
| picker-options | additional options, check the table below | object | — | {} | | picker-options | additional options, check the table below | object | — | {} |
| range-separator | range separator | string | - | '-' | | range-separator | range separator | string | - | '-' |
|name | same as `name` in native input | string | — | — | | default-value | optional, default date of the calendar | Date for TimePicker, string for TimeSelect | anything accepted by `new Date()` for TimePicker, selectable value for TimeSelect | — |
| value-format | optional, only for TimePicker, format of bounded value | string | hour `HH`, minute `mm`, second `ss` | — |
| name | same as `name` in native input | string | — | — |
### Time Select Options ### Time Select Options
| Attribute | Description | Type | Accepted Values | Default | | Attribute | Description | Type | Accepted Values | Default |
...@@ -187,6 +189,6 @@ Can pick an arbitrary time range. ...@@ -187,6 +189,6 @@ Can pick an arbitrary time range.
### Events ### Events
| Event Name | Description | Parameters | | Event Name | Description | Parameters |
|---------|--------|---------| |---------|--------|---------|
| change | triggers when input value changes | formatted value | | change | triggers when user confirms the value | component's bounded value |
| blur | triggers when Input blurs | (event: Event) | | blur | triggers when Input blurs | (event: Event) |
| focus | triggers when Input focuses | (event: Event) | | focus | triggers when Input focuses | (event: Event) |
...@@ -65,7 +65,11 @@ ...@@ -65,7 +65,11 @@
value4: '', value4: '',
value5: '', value5: '',
value6: '', value6: '',
value7: '' value7: '',
value8: '',
value9: '',
value10: '',
value11: ''
}; };
} }
}; };
...@@ -285,6 +289,96 @@ ...@@ -285,6 +289,96 @@
``` ```
::: :::
### 默认显示日期
未选择日期时,默认显示今天的日历。使用`default-value`可以指定其他日期,该值需要能够被`new Date()`解析。
类型为`daterange`时,指定左侧日历的日期。
:::demo
```html
<template>
<div class="block">
<span class="demonstration">date</span>
<el-date-picker
v-model="value8"
type="date"
placeholder="选择日期"
default-value="2010-10-01">
</el-date-picker>
</div>
<div class="block">
<span class="demonstration">daterange</span>
<el-date-picker
v-model="value9"
type="daterange"
start-placeholder="开始日期"
end-placeholder="结束日期"
default-value="2010-10-01">
</el-date-picker>
</div>
</template>
<script>
export default {
data() {
return {
value8: '',
value9: ''
};
}
};
</script>
```
:::
### 返回值格式
默认情况下,组件接受并返回`Date`对象。
使用`value-format`指定返回值的格式,支持的格式与`format`相同。
:::warning
该功能处于测试阶段,欢迎提供反馈。
:::
:::demo
```html
<template>
<div class="block">
<span class="demonstration">默认为 Date 对象</span>
<div class="demonstration">组件值:{{ value10 }}</div>
<el-date-picker
v-model="value10"
type="date"
placeholder="选择日期"
format="yyyy 年 MM 月 dd 日">
</el-date-picker>
</div>
<div class="block">
<span class="demonstration">使用 value-format 进行格式化</span>
<div class="demonstration">组件值:{{ value11 }}</div>
<el-date-picker
v-model="value11"
type="date"
placeholder="选择日期"
format="yyyy 年 MM 月 dd 日"
value-format="yyyy-MM-dd">
</el-date-picker>
</div>
</template>
<script>
export default {
data() {
return {
value10: '',
value11: '',
};
}
};
</script>
```
:::
### Attributes ### Attributes
| 参数 | 说明 | 类型 | 可选值 | 默认值 | | 参数 | 说明 | 类型 | 可选值 | 默认值 |
|---------- |-------------- |---------- |-------------------------------- |-------- | |---------- |-------------- |---------- |-------------------------------- |-------- |
...@@ -297,12 +391,13 @@ ...@@ -297,12 +391,13 @@
| start-placeholder | 范围选择时开始日期的占位内容 | string | — | — | | start-placeholder | 范围选择时开始日期的占位内容 | string | — | — |
| end-placeholder | 范围选择时结束日期的占位内容 | string | — | — | | end-placeholder | 范围选择时结束日期的占位内容 | string | — | — |
| type | 显示类型 | string | year/month/date/week/ datetime/datetimerange/daterange | date | | type | 显示类型 | string | year/month/date/week/ datetime/datetimerange/daterange | date |
| format | 时间日期格式化 | string | 年 `yyyy`,月 `MM`,日 `dd`,小时 `HH`,分 `mm`,秒 `ss` | yyyy-MM-dd | | format | 输入框的时间日期格式 | string | 年 `yyyy`,月 `MM`,日 `dd`,小时 `HH`,分 `mm`,秒 `ss` | yyyy-MM-dd |
| align | 对齐方式 | string | left, center, right | left | | align | 对齐方式 | string | left, center, right | left |
| popper-class | DatePicker 下拉框的类名 | string | — | — | | popper-class | DatePicker 下拉框的类名 | string | — | — |
|picker-options | 当前时间日期选择器特有的选项参考下表 | object | — | {} | | picker-options | 当前时间日期选择器特有的选项参考下表 | object | — | {} |
| range-separator | 选择范围时的分隔符 | string | - | '-' | | range-separator | 选择范围时的分隔符 | string | — | '-' |
| default-value | 可选,DatePicker打开时默认显示的时间 | Date | 可被new Date()解析 | - | | default-value | 可选,选择器打开时默认显示的时间 | Date | 可被`new Date()`解析 | — |
| value-format | 可选,绑定值的格式 | string | 年 `yyyy`,月 `MM`,日 `dd`,小时 `HH`,分 `mm`,秒 `ss` | — |
| name | 原生属性 | string | — | — | | name | 原生属性 | string | — | — |
### Picker Options ### Picker Options
...@@ -322,7 +417,7 @@ ...@@ -322,7 +417,7 @@
### Events ### Events
| 事件名称 | 说明 | 回调参数 | | 事件名称 | 说明 | 回调参数 |
|---------|--------|---------| |---------|--------|---------|
| change | 当 input 的值改变时触发,返回值和文本框一致 | 格式化后的值 | | change | 用户确认选定的值时触发 | 组件绑定值 |
| blur | 当 input 失去焦点时触发 | (event: Event) | | blur | 当 input 失去焦点时触发 | (event: Event) |
| focus | 当 input 获得焦点时触发 | (event: Event) | | focus | 当 input 获得焦点时触发 | (event: Event) |
......
...@@ -250,6 +250,8 @@ DateTimePicker 由 DatePicker 和 TimePicker 派生,`Picker Options` 或者其 ...@@ -250,6 +250,8 @@ DateTimePicker 由 DatePicker 和 TimePicker 派生,`Picker Options` 或者其
| popper-class | DateTimePicker 下拉框的类名 | string | — | — | | popper-class | DateTimePicker 下拉框的类名 | string | — | — |
| picker-options | 当前时间日期选择器特有的选项参考下表 | object | — | {} | | picker-options | 当前时间日期选择器特有的选项参考下表 | object | — | {} |
| range-separator | 选择范围时的分隔符 | string | - | '-' | | range-separator | 选择范围时的分隔符 | string | - | '-' |
| default-value | 可选,选择器打开时默认显示的时间 | Date | 可被`new Date()`解析 | — |
| value-format | 可选,绑定值的格式 | string | 年 `yyyy`,月 `MM`,日 `dd`,小时 `HH`,分 `mm`,秒 `ss` | — |
| name | 原生属性 | string | — | — | | name | 原生属性 | string | — | — |
### Picker Options ### Picker Options
...@@ -268,7 +270,7 @@ DateTimePicker 由 DatePicker 和 TimePicker 派生,`Picker Options` 或者其 ...@@ -268,7 +270,7 @@ DateTimePicker 由 DatePicker 和 TimePicker 派生,`Picker Options` 或者其
### Events ### Events
| Event Name | Description | Parameters | | Event Name | Description | Parameters |
|---------|--------|---------| |---------|--------|---------|
| change | 当 input 的值改变时触发,返回值和文本框一致 | formatted value | | change | 用户确认选定的值时触发 | 组件绑定值 |
| blur | 当 input 失去焦点时触发 | (event: Event) | | blur | 当 input 失去焦点时触发 | (event: Event) |
| focus | 当 input 获得焦点时触发 | (event: Event) | | focus | 当 input 获得焦点时触发 | (event: Event) |
......
...@@ -166,6 +166,8 @@ ...@@ -166,6 +166,8 @@
| popper-class | TimePicker 下拉框的类名 | string | — | — | | popper-class | TimePicker 下拉框的类名 | string | — | — |
| picker-options | 当前时间日期选择器特有的选项参考下表 | object | — | {} | | picker-options | 当前时间日期选择器特有的选项参考下表 | object | — | {} |
| range-separator | 选择范围时的分隔符 | string | - | '-' | | range-separator | 选择范围时的分隔符 | string | - | '-' |
| value-format | 可选,仅TimePicker时可用,绑定值的格式,同DatePicker | string | 小时 `HH`,分 `mm`,秒 `ss` | — |
| default-value | 可选,选择器打开时默认显示的时间 | Date(TimePicker) / string(TimeSelect) | 可被`new Date()`解析(TimePicker) / 可选值(TimeSelect) | — |
| name | 原生属性 | string | — | — | | name | 原生属性 | string | — | — |
### Time Select Options ### Time Select Options
...@@ -186,7 +188,7 @@ ...@@ -186,7 +188,7 @@
### Events ### Events
| 事件名 | 说明 | 参数 | | 事件名 | 说明 | 参数 |
|---------|--------|---------| |---------|--------|---------|
| change | 当 input 的值改变时触发,返回值和文本框一致 | formatted value | | change | 用户确认选定的值时触发 | 组件绑定值 |
| blur | 当 input 失去焦点时触发 | (event: Event) | | blur | 当 input 失去焦点时触发 | (event: Event) |
| focus | 当 input 获得焦点时触发 | (event: Event) | | focus | 当 input 获得焦点时触发 | (event: Event) |
......
...@@ -30,7 +30,7 @@ ...@@ -30,7 +30,7 @@
</template> </template>
<script> <script>
import { getFirstDayOfMonth, getDayCountOfMonth, getWeekNumber, getStartDateOfMonth, DAY_DURATION } from '../util'; import { getFirstDayOfMonth, getDayCountOfMonth, getWeekNumber, getStartDateOfMonth, DAY_DURATION, isDate } from '../util';
import { hasClass } from 'element-ui/src/utils/dom'; import { hasClass } from 'element-ui/src/utils/dom';
import Locale from 'element-ui/src/mixins/locale'; import Locale from 'element-ui/src/mixins/locale';
...@@ -51,13 +51,16 @@ ...@@ -51,13 +51,16 @@
validator: val => val >= 1 && val <= 7 validator: val => val >= 1 && val <= 7
}, },
date: {}, value: {},
year: {},
month: {}, defaultValue: {
validator(val) {
// either: null, valid Date object, Array of valid Date objects
return val === null || isDate(val) || (Array.isArray(val) && val.every(isDate));
}
},
week: {}, date: {},
selectionMode: { selectionMode: {
default: 'day' default: 'day'
...@@ -98,8 +101,12 @@ ...@@ -98,8 +101,12 @@
return WEEKS.concat(WEEKS).slice(week, week + 7); return WEEKS.concat(WEEKS).slice(week, week + 7);
}, },
monthDate() { year() {
return this.date.getDate(); return this.date.getFullYear();
},
month() {
return this.date.getMonth();
}, },
startDate() { startDate() {
...@@ -107,6 +114,7 @@ ...@@ -107,6 +114,7 @@
}, },
rows() { rows() {
// TODO: refactory rows / getCellClasses
const date = new Date(this.year, this.month, 1); const date = new Date(this.year, this.month, 1);
let day = getFirstDayOfMonth(date); // day of first day let day = getFirstDayOfMonth(date); // day of first day
const dateCountOfMonth = getDayCountOfMonth(date.getFullYear(), date.getMonth()); const dateCountOfMonth = getDayCountOfMonth(date.getFullYear(), date.getMonth());
...@@ -220,7 +228,7 @@ ...@@ -220,7 +228,7 @@
this.$emit('pick', { this.$emit('pick', {
minDate: this.minDate, minDate: this.minDate,
maxDate: this.maxDate maxDate: this.maxDate
}, true, false); });
} }
} }
}, },
...@@ -232,9 +240,16 @@ ...@@ -232,9 +240,16 @@
}, },
methods: { methods: {
cellMatchesDate(cell, date) {
const value = new Date(date);
return this.year === value.getFullYear() &&
this.month === value.getMonth() &&
Number(cell.text) === value.getDate();
},
getCellClasses(cell) { getCellClasses(cell) {
const selectionMode = this.selectionMode; const selectionMode = this.selectionMode;
const monthDate = this.monthDate; const defaultValue = this.defaultValue ? Array.isArray(this.defaultValue) ? this.defaultValue : [this.defaultValue] : [];
let classes = []; let classes = [];
if ((cell.type === 'normal' || cell.type === 'today') && !cell.disabled) { if ((cell.type === 'normal' || cell.type === 'today') && !cell.disabled) {
...@@ -246,8 +261,11 @@ ...@@ -246,8 +261,11 @@
classes.push(cell.type); classes.push(cell.type);
} }
if (selectionMode === 'day' && (cell.type === 'normal' || cell.type === 'today') && if (cell.type === 'normal' && defaultValue.some(date => this.cellMatchesDate(cell, date))) {
Number(this.year) === this.date.getFullYear() && this.month === this.date.getMonth() && monthDate === Number(cell.text)) { classes.push('default');
}
if (selectionMode === 'day' && (cell.type === 'normal' || cell.type === 'today') && this.cellMatchesDate(cell, this.value)) {
classes.push('current'); classes.push('current');
} }
...@@ -307,7 +325,7 @@ ...@@ -307,7 +325,7 @@
newDate.setDate(parseInt(cell.text, 10)); newDate.setDate(parseInt(cell.text, 10));
return getWeekNumber(newDate) === this.week; return getWeekNumber(newDate) === getWeekNumber(this.date);
}, },
markRange(maxDate) { markRange(maxDate) {
......
...@@ -49,45 +49,43 @@ ...@@ -49,45 +49,43 @@
<script type="text/babel"> <script type="text/babel">
import Locale from 'element-ui/src/mixins/locale'; import Locale from 'element-ui/src/mixins/locale';
import { isDate, range, getDayCountOfMonth } from '../util';
import { hasClass } from 'element-ui/src/utils/dom'; import { hasClass } from 'element-ui/src/utils/dom';
const datesInMonth = (year, month) => {
const numOfDays = getDayCountOfMonth(year, month);
const firstDay = new Date(year, month, 1);
const ONE_DAY = 8.64e7;
return range(numOfDays).map(n => new Date(firstDay.getTime() + n * ONE_DAY));
};
export default { export default {
props: { props: {
disabledDate: {}, disabledDate: {},
date: {}, value: {},
month: { defaultValue: {
type: Number validator(val) {
// null or valid Date Object
return val === null || (val instanceof Date && isDate(val));
} }
}, },
date: {}
},
mixins: [Locale], mixins: [Locale],
methods: { methods: {
getCellStyle(month) { getCellStyle(month) {
const style = {}; const style = {};
const year = this.date.getFullYear();
const today = new Date();
var year = this.date.getFullYear(); style.disabled = typeof this.disabledDate === 'function'
var date = new Date(0); ? datesInMonth(year, month).every(this.disabledDate)
date.setFullYear(year); : false;
date.setMonth(month, 1); style.current = this.value.getFullYear() === year && this.value.getMonth() === month;
date.setHours(0); style.today = today.getFullYear() === year && today.getMonth() === month;
var nextMonth = new Date(date); style.default = this.defaultValue &&
nextMonth.setMonth(month + 1); this.defaultValue.getFullYear() === year &&
this.defaultValue.getMonth() === month;
var flag = false;
if (typeof this.disabledDate === 'function') {
while (date < nextMonth) {
if (this.disabledDate(date)) {
date = new Date(date.getTime() + 8.64e7);
flag = true;
} else {
flag = false;
break;
}
}
}
style.disabled = flag;
style.current = this.month === month;
return style; return style;
}, },
......
...@@ -7,9 +7,9 @@ ...@@ -7,9 +7,9 @@
view-class="el-time-spinner__list" view-class="el-time-spinner__list"
noresize noresize
tag="ul" tag="ul"
ref="hour"> ref="hours">
<li <li
@click="handleClick('hours', { value: hour, disabled: disabled }, true)" @click="handleClick('hours', { value: hour, disabled: disabled })"
v-for="(disabled, hour) in hoursList" v-for="(disabled, hour) in hoursList"
track-by="hour" track-by="hour"
class="el-time-spinner__item" class="el-time-spinner__item"
...@@ -22,9 +22,9 @@ ...@@ -22,9 +22,9 @@
view-class="el-time-spinner__list" view-class="el-time-spinner__list"
noresize noresize
tag="ul" tag="ul"
ref="minute"> ref="minutes">
<li <li
@click="handleClick('minutes', key, true)" @click="handleClick('minutes', { value: key, disabled: false })"
v-for="(minute, key) in 60" v-for="(minute, key) in 60"
class="el-time-spinner__item" class="el-time-spinner__item"
:class="{ 'active': key === minutes }">{{ ('0' + key).slice(-2) }}</li> :class="{ 'active': key === minutes }">{{ ('0' + key).slice(-2) }}</li>
...@@ -37,9 +37,9 @@ ...@@ -37,9 +37,9 @@
view-class="el-time-spinner__list" view-class="el-time-spinner__list"
noresize noresize
tag="ul" tag="ul"
ref="second"> ref="seconds">
<li <li
@click="handleClick('seconds', key, true)" @click="handleClick('seconds', { value: key, disabled: false })"
v-for="(second, key) in 60" v-for="(second, key) in 60"
class="el-time-spinner__item" class="el-time-spinner__item"
:class="{ 'active': key === seconds }">{{ ('0' + key).slice(-2) }}</li> :class="{ 'active': key === seconds }">{{ ('0' + key).slice(-2) }}</li>
...@@ -48,83 +48,38 @@ ...@@ -48,83 +48,38 @@
</template> </template>
<script type="text/babel"> <script type="text/babel">
import { getRangeHours } from '../util'; import { getRangeHours, modifyTime } from '../util';
import ElScrollbar from 'element-ui/packages/scrollbar'; import ElScrollbar from 'element-ui/packages/scrollbar';
export default { export default {
components: { ElScrollbar }, components: { ElScrollbar },
props: { props: {
hours: { date: {},
type: Number, defaultValue: {}, // reserved for future use
default: 0
},
minutes: {
type: Number,
default: 0
},
seconds: {
type: Number,
default: 0
},
showSeconds: { showSeconds: {
type: Boolean, type: Boolean,
default: true default: true
} }
}, },
watch: { computed: {
hoursPrivate(newVal, oldVal) { hours() {
if (!(newVal >= 0 && newVal <= 23)) { return this.date.getHours();
this.hoursPrivate = oldVal;
}
this.adjustElTop('hour', newVal);
this.$emit('change', { hours: newVal });
}, },
minutes() {
minutesPrivate(newVal, oldVal) { return this.date.getMinutes();
if (!(newVal >= 0 && newVal <= 59)) {
this.minutesPrivate = oldVal;
}
this.adjustElTop('minute', newVal);
this.$emit('change', { minutes: newVal });
}, },
seconds() {
secondsPrivate(newVal, oldVal) { return this.date.getSeconds();
if (!(newVal >= 0 && newVal <= 59)) {
this.secondsPrivate = oldVal;
}
this.adjustElTop('second', newVal);
this.$emit('change', { seconds: newVal });
}
}, },
computed: {
hoursList() { hoursList() {
return getRangeHours(this.selectableRange); return getRangeHours(this.selectableRange);
},
hourEl() {
return this.$refs.hour.wrap;
},
minuteEl() {
return this.$refs.minute.wrap;
},
secondEl() {
return this.$refs.second.wrap;
} }
}, },
data() { data() {
return { return {
hoursPrivate: 0,
minutesPrivate: 0,
secondsPrivate: 0,
selectableRange: [], selectableRange: [],
currentScrollbar: null currentScrollbar: null
}; };
...@@ -137,59 +92,71 @@ ...@@ -137,59 +92,71 @@
}, },
methods: { methods: {
handleClick(type, value, disabled) { modifyDateField(type, value) {
if (value.disabled) { switch (type) {
return; case 'hours': this.$emit('change', modifyTime(this.date, value, this.minutes, this.seconds)); break;
case 'minutes': this.$emit('change', modifyTime(this.date, this.hours, value, this.seconds)); break;
case 'seconds': this.$emit('change', modifyTime(this.date, this.hours, this.minutes, value)); break;
} }
},
this[type + 'Private'] = value.value >= 0 ? value.value : value; handleClick(type, {value, disabled}) {
if (!disabled) {
this.modifyDateField(type, value);
this.emitSelectRange(type); this.emitSelectRange(type);
this.adjustSpinner(type, value);
}
}, },
emitSelectRange(type) { emitSelectRange(type) {
if (type === 'hours') { if (type === 'hours') {
this.$emit('select-range', 0, 2); this.$emit('select-range', 0, 2);
this.adjustElTop('minute', this.minutes); this.adjustSpinner('minutes', this.minutes);
this.adjustElTop('second', this.seconds); this.adjustSpinner('seconds', this.seconds);
} else if (type === 'minutes') { } else if (type === 'minutes') {
this.$emit('select-range', 3, 5); this.$emit('select-range', 3, 5);
this.adjustElTop('hour', this.hours); this.adjustSpinner('hours', this.hours);
this.adjustElTop('second', this.seconds); this.adjustSpinner('seconds', this.seconds);
} else if (type === 'seconds') { } else if (type === 'seconds') {
this.$emit('select-range', 6, 8); this.$emit('select-range', 6, 8);
this.adjustElTop('minute', this.minutes); this.adjustSpinner('minutes', this.minutes);
this.adjustElTop('hour', this.hours); this.adjustSpinner('hours', this.hours);
} }
this.currentScrollbar = type; this.currentScrollbar = type;
}, },
bindScrollEvent() { bindScrollEvent() {
const bindFuntion = (type) => { const bindFuntion = (type) => {
this[`${type}El`].onscroll = (e) => { this.$refs[type].wrap.onscroll = (e) => {
// TODO: scroll is emitted when set scrollTop programatically
// should find better solutions in the future!
this.handleScroll(type, e); this.handleScroll(type, e);
}; };
}; };
bindFuntion('hour'); bindFuntion('hours');
bindFuntion('minute'); bindFuntion('minutes');
bindFuntion('second'); bindFuntion('seconds');
}, },
handleScroll(type) { handleScroll(type) {
const adjust = {}; const value = Math.min(Math.floor((this.$refs[type].wrap.scrollTop - 80) / 32 + 3), (type === 'hours' ? 23 : 59));
adjust[`${type}s`] = Math.min(Math.floor((this[`${type}El`].scrollTop - 80) / 32 + 3), (`${type}` === 'hour' ? 23 : 59)); this.modifyDateField(type, value);
this.$emit('change', adjust);
}, },
adjustScrollTop() { // NOTE: used by datetime / date-range panel
this.adjustElTop('hour', this.hours); // renamed from adjustScrollTop
this.adjustElTop('minute', this.minutes); // should try to refactory it
this.adjustElTop('second', this.seconds); adjustSpinners() {
this.adjustSpinner('hours', this.hours);
this.adjustSpinner('minutes', this.minutes);
this.adjustSpinner('seconds', this.seconds);
}, },
adjustElTop(type, value) { adjustSpinner(type, value) {
if (!this[`${type}El`]) return; const el = this.$refs[type].wrap;
this[`${type}El`].scrollTop = Math.max(0, (value - 2.5) * 32 + 80); if (el) {
el.scrollTop = Math.max(0, (value - 2.5) * 32 + 80);
}
}, },
scrollDown(step) { scrollDown(step) {
...@@ -217,8 +184,8 @@ ...@@ -217,8 +184,8 @@
now = (now + step + 60) % 60; now = (now + step + 60) % 60;
} }
this.$emit('change', { [label]: now }); this.modifyDateField(label, now);
this.adjustElTop(label.slice(0, -1), now); this.adjustSpinner(label, now);
} }
} }
}; };
......
...@@ -45,62 +45,57 @@ ...@@ -45,62 +45,57 @@
<script type="text/babel"> <script type="text/babel">
import { hasClass } from 'element-ui/src/utils/dom'; import { hasClass } from 'element-ui/src/utils/dom';
import { isDate, range } from '../util';
const isLeapYear = year => year % 400 === 0 || (year % 100 !== 0 && year % 4 === 0);
const datesInYear = year => {
const numOfDays = isLeapYear(year) ? 366 : 365;
const firstDay = new Date(year, 0, 1);
const ONE_DAY = 8.64e7;
return range(numOfDays).map(n => new Date(firstDay.getTime() + ONE_DAY));
};
export default { export default {
props: { props: {
disabledDate: {}, disabledDate: {},
date: {}, value: {},
year: {} defaultValue: {
validator(val) {
// null or valid Date Object
return val === null || (val instanceof Date && isDate(val));
}
},
date: {}
}, },
computed: { computed: {
startYear() { startYear() {
return Math.floor(this.year / 10) * 10; return Math.floor(this.date.getFullYear() / 10) * 10;
} }
}, },
methods: { methods: {
getCellStyle(year) { getCellStyle(year) {
const style = {}; const style = {};
const today = new Date();
var date = new Date(year, 0, 1, 0); style.disabled = typeof this.disabledDate === 'function'
var nextYear = new Date(date); ? datesInYear(year).every(this.disabledDate)
nextYear.setFullYear(year + 1); : false;
style.current = this.value.getFullYear() === year;
var flag = false; style.today = today.getFullYear() === year;
if (typeof this.disabledDate === 'function') { style.default = this.defaultValue && this.defaultValue.getFullYear() === year;
while (date < nextYear) {
if (this.disabledDate(date)) {
date = new Date(date.getTime() + 8.64e7);
} else {
break;
}
}
if ((date - nextYear) === 0) flag = true;
}
style.disabled = flag;
style.current = Number(this.year) === year;
return style; return style;
}, },
nextTenYear() {
this.$emit('pick', Number(this.year) + 10, false, true);
},
prevTenYear() {
this.$emit('pick', Number(this.year) - 10, false, true);
},
handleYearTableClick(event) { handleYearTableClick(event) {
const target = event.target; const target = event.target;
if (target.tagName === 'A') { if (target.tagName === 'A') {
if (hasClass(target.parentNode, 'disabled')) return; if (hasClass(target.parentNode, 'disabled')) return;
const year = target.textContent || target.innerText; const year = target.textContent || target.innerText;
this.$emit('pick', Number(year), true, true); this.$emit('pick', Number(year));
} }
} }
} }
......
This diff is collapsed.
<template> <template>
<transition <transition
name="el-zoom-in-top" name="el-zoom-in-top"
@before-enter="panelCreated"
@after-leave="$emit('dodestroy')"> @after-leave="$emit('dodestroy')">
<div <div
v-show="visible" v-show="visible"
...@@ -18,9 +17,7 @@ ...@@ -18,9 +17,7 @@
:show-seconds="showSeconds" :show-seconds="showSeconds"
@change="handleMinChange" @change="handleMinChange"
@select-range="setMinSelectionRange" @select-range="setMinSelectionRange"
:hours="minHours" :date="minDate">
:minutes="minMinutes"
:seconds="minSeconds">
</time-spinner> </time-spinner>
</div> </div>
</div> </div>
...@@ -34,9 +31,7 @@ ...@@ -34,9 +31,7 @@
:show-seconds="showSeconds" :show-seconds="showSeconds"
@change="handleMaxChange" @change="handleMaxChange"
@select-range="setMaxSelectionRange" @select-range="setMaxSelectionRange"
:hours="maxHours" :date="maxDate">
:minutes="maxMinutes"
:seconds="maxSeconds">
</time-spinner> </time-spinner>
</div> </div>
</div> </div>
...@@ -57,27 +52,30 @@ ...@@ -57,27 +52,30 @@
</template> </template>
<script type="text/babel"> <script type="text/babel">
import { parseDate, limitRange } from '../util'; import {
parseDate,
limitTimeRange,
modifyDate,
clearMilliseconds,
timeWithinRange
} from '../util';
import Locale from 'element-ui/src/mixins/locale'; import Locale from 'element-ui/src/mixins/locale';
import TimeSpinner from '../basic/time-spinner'; import TimeSpinner from '../basic/time-spinner';
const MIN_TIME = parseDate('00:00:00', 'HH:mm:ss'); const MIN_TIME = parseDate('00:00:00', 'HH:mm:ss');
const MAX_TIME = parseDate('23:59:59', 'HH:mm:ss'); const MAX_TIME = parseDate('23:59:59', 'HH:mm:ss');
const isDisabled = function(minTime, maxTime) {
const minValue = minTime.getHours() * 3600 + minTime.getMinutes() * 60 + minTime.getSeconds();
const maxValue = maxTime.getHours() * 3600 + maxTime.getMinutes() * 60 + maxTime.getSeconds();
return minValue > maxValue; const minTimeOfDay = function(date) {
return modifyDate(MIN_TIME, date.getFullYear(), date.getMonth(), date.getDate());
}; };
const clacTime = function(time) {
time = Array.isArray(time) ? time : [time];
const minTime = time[0] || new Date();
const date = new Date();
date.setHours(date.getHours() + 1);
const maxTime = time[1] || date;
if (minTime > maxTime) return clacTime(); const maxTimeOfDay = function(date) {
return { minTime, maxTime }; return modifyDate(MAX_TIME, date.getFullYear(), date.getMonth(), date.getDate());
};
// increase time by amount of milliseconds, but within the range of day
const advanceTime = function(date, amount) {
return new Date(Math.min(date.getTime() + amount, maxTimeOfDay(date).getTime()));
}; };
export default { export default {
...@@ -96,25 +94,21 @@ ...@@ -96,25 +94,21 @@
spinner() { spinner() {
return this.selectionRange[0] < this.offset ? this.$refs.minSpinner : this.$refs.maxSpinner; return this.selectionRange[0] < this.offset ? this.$refs.minSpinner : this.$refs.maxSpinner;
}
}, },
props: ['value'], btnDisabled() {
return this.minDate.getTime() > this.maxDate.getTime();
}
},
data() { data() {
const time = clacTime(this.$options.defaultValue);
return { return {
popperClass: '', popperClass: '',
minTime: time.minTime, minDate: new Date(),
maxTime: time.maxTime, maxDate: new Date(),
btnDisabled: isDisabled(time.minTime, time.maxTime), value: [],
maxHours: time.maxTime.getHours(), oldValue: [new Date(), new Date()],
maxMinutes: time.maxTime.getMinutes(), defaultValue: null,
maxSeconds: time.maxTime.getSeconds(),
minHours: time.minTime.getHours(),
minMinutes: time.minTime.getMinutes(),
minSeconds: time.minTime.getSeconds(),
format: 'HH:mm:ss', format: 'HH:mm:ss',
visible: false, visible: false,
selectionRange: [0, 2] selectionRange: [0, 2]
...@@ -122,87 +116,60 @@ ...@@ -122,87 +116,60 @@
}, },
watch: { watch: {
value(newVal) { value(value) {
this.panelCreated(); if (Array.isArray(value)) {
this.$nextTick(_ => this.adjustScrollTop()); this.minDate = new Date(value[0]);
this.maxDate = new Date(value[1]);
} else {
if (Array.isArray(this.defaultValue)) {
this.minDate = new Date(this.defaultValue[0]);
this.maxDate = new Date(this.defaultValue[1]);
} else if (this.defaultValue) {
this.minDate = new Date(this.defaultValue);
this.maxDate = advanceTime(new Date(this.defaultValue), 60 * 60 * 1000);
} else {
this.minDate = new Date();
this.maxDate = advanceTime(new Date(), 60 * 60 * 1000);
}
}
if (this.visible) {
this.$nextTick(_ => this.adjustSpinners());
}
}, },
visible(val) { visible(val) {
if (val) { if (val) {
this.oldValue = this.value;
this.$nextTick(() => this.$refs.minSpinner.emitSelectRange('hours')); this.$nextTick(() => this.$refs.minSpinner.emitSelectRange('hours'));
} }
} }
}, },
methods: { methods: {
panelCreated() {
const time = clacTime(this.value);
if (time.minTime === this.minTime && time.maxTime === this.maxTime) {
return;
}
this.handleMinChange({
hours: time.minTime.getHours(),
minutes: time.minTime.getMinutes(),
seconds: time.minTime.getSeconds()
}, true);
this.handleMaxChange({
hours: time.maxTime.getHours(),
minutes: time.maxTime.getMinutes(),
seconds: time.maxTime.getSeconds()
}, true);
},
handleClear() { handleClear() {
this.handleCancel(); this.$emit('pick', []);
}, },
handleCancel() { handleCancel() {
this.$emit('pick'); this.$emit('pick', this.oldValue);
}, },
handleChange(notUser) { handleMinChange(date) {
if (this.minTime > this.maxTime) return; this.minDate = clearMilliseconds(date);
MIN_TIME.setFullYear(this.minTime.getFullYear()); this.handleChange();
MIN_TIME.setMonth(this.minTime.getMonth(), this.minTime.getDate());
MAX_TIME.setFullYear(this.maxTime.getFullYear());
MAX_TIME.setMonth(this.maxTime.getMonth(), this.maxTime.getDate());
this.$refs.minSpinner.selectableRange = [[MIN_TIME, this.maxTime]];
this.$refs.maxSpinner.selectableRange = [[this.minTime, MAX_TIME]];
this.handleConfirm(true, false, notUser);
}, },
handleMaxChange(date, notUser) { handleMaxChange(date) {
if (date.hours !== undefined) { this.maxDate = clearMilliseconds(date);
this.maxTime.setHours(date.hours);
this.maxHours = this.maxTime.getHours();
}
if (date.minutes !== undefined) {
this.maxTime.setMinutes(date.minutes);
this.maxMinutes = this.maxTime.getMinutes();
}
if (date.seconds !== undefined) {
this.maxTime.setSeconds(date.seconds);
this.maxSeconds = this.maxTime.getSeconds();
}
this.handleChange(); this.handleChange();
}, },
handleMinChange(date, notUser) { handleChange() {
if (date.hours !== undefined) { if (this.isValidValue([this.minDate, this.maxDate])) {
this.minTime.setHours(date.hours); this.$refs.minSpinner.selectableRange = [[minTimeOfDay(this.minDate), this.maxDate]];
this.minHours = this.minTime.getHours(); this.$refs.maxSpinner.selectableRange = [[this.minDate, maxTimeOfDay(this.maxDate)]];
} this.$emit('pick', [this.minDate, this.maxDate], true);
if (date.minutes !== undefined) {
this.minTime.setMinutes(date.minutes);
this.minMinutes = this.minTime.getMinutes();
}
if (date.seconds !== undefined) {
this.minTime.setSeconds(date.seconds);
this.minSeconds = this.minTime.getSeconds();
} }
this.handleChange();
}, },
setMinSelectionRange(start, end) { setMinSelectionRange(start, end) {
...@@ -215,24 +182,19 @@ ...@@ -215,24 +182,19 @@
this.selectionRange = [start + this.offset, end + this.offset]; this.selectionRange = [start + this.offset, end + this.offset];
}, },
handleConfirm(visible = false, first = false, notUser = false) { handleConfirm(visible = false) {
const minSelectableRange = this.$refs.minSpinner.selectableRange; const minSelectableRange = this.$refs.minSpinner.selectableRange;
const maxSelectableRange = this.$refs.maxSpinner.selectableRange; const maxSelectableRange = this.$refs.maxSpinner.selectableRange;
this.minTime = limitRange(this.minTime, minSelectableRange); this.minDate = limitTimeRange(this.minDate, minSelectableRange, this.format);
this.maxTime = limitRange(this.maxTime, maxSelectableRange); this.maxDate = limitTimeRange(this.maxDate, maxSelectableRange, this.format);
if (first) return; this.$emit('pick', [this.minDate, this.maxDate], visible);
this.$emit('pick', [this.minTime, this.maxTime], visible, !notUser);
}, },
adjustScrollTop() { adjustSpinners() {
this.$refs.minSpinner.adjustScrollTop(); this.$refs.minSpinner.adjustSpinners();
this.$refs.maxSpinner.adjustScrollTop(); this.$refs.maxSpinner.adjustSpinners();
},
scrollDown(step) {
this.spinner.scrollDown(step);
}, },
changeSelectionRange(step) { changeSelectionRange(step) {
...@@ -246,11 +208,34 @@ ...@@ -246,11 +208,34 @@
} else { } else {
this.$refs.maxSpinner.emitSelectRange(mapping[next - half]); this.$refs.maxSpinner.emitSelectRange(mapping[next - half]);
} }
}
}, },
mounted() { isValidValue(date) {
this.$nextTick(() => this.handleConfirm(true, true)); return Array.isArray(date) &&
timeWithinRange(this.minDate, this.$refs.minSpinner.selectableRange) &&
timeWithinRange(this.maxDate, this.$refs.maxSpinner.selectableRange);
},
handleKeydown(event) {
const keyCode = event.keyCode;
const mapping = { 38: -1, 40: 1, 37: -1, 39: 1 };
// Left or Right
if (keyCode === 37 || keyCode === 39) {
const step = mapping[keyCode];
this.changeSelectionRange(step);
event.preventDefault();
return;
}
// Up or Down
if (keyCode === 38 || keyCode === 40) {
const step = mapping[keyCode];
this.spinner.scrollDown(step);
event.preventDefault();
return;
}
}
} }
}; };
</script> </script>
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
<el-scrollbar noresize wrap-class="el-picker-panel__content"> <el-scrollbar noresize wrap-class="el-picker-panel__content">
<div class="time-select-item" <div class="time-select-item"
v-for="item in items" v-for="item in items"
:class="{ selected: value === item.value, disabled: item.disabled }" :class="{ selected: value === item.value, disabled: item.disabled, default: item.value === defaultValue }"
:disabled="item.disabled" :disabled="item.disabled"
@click="handleClick(item)">{{ item.value }}</div> @click="handleClick(item)">{{ item.value }}</div>
</el-scrollbar> </el-scrollbar>
...@@ -78,11 +78,6 @@ ...@@ -78,11 +78,6 @@
watch: { watch: {
value(val) { value(val) {
if (!val) return; if (!val) return;
if (this.minTime && compareTime(val, this.minTime) < 0) {
this.$emit('pick', '', false, false);
} else if (this.maxTime && compareTime(val, this.maxTime) > 0) {
this.$emit('pick', '', false, false);
}
this.$nextTick(() => this.scrollToOption()); this.$nextTick(() => this.scrollToOption());
} }
}, },
...@@ -95,34 +90,47 @@ ...@@ -95,34 +90,47 @@
}, },
handleClear() { handleClear() {
this.$emit('pick', '', false, false); this.$emit('pick');
}, },
scrollToOption(className = 'selected') { scrollToOption(selector = '.selected') {
const menu = this.$refs.popper.querySelector('.el-picker-panel__content'); const menu = this.$refs.popper.querySelector('.el-picker-panel__content');
scrollIntoView(menu, menu.getElementsByClassName(className)[0]); scrollIntoView(menu, menu.querySelector(selector));
}, },
handleMenuEnter() { handleMenuEnter() {
this.$nextTick(() => this.scrollToOption()); const selected = this.items.map(item => item.value).indexOf(this.value) !== -1;
const hasDefault = this.items.map(item => item.value).indexOf(this.defaultValue) !== -1;
const option = (selected && '.selected') || (hasDefault && '.default') || '.time-select-item:not(.disabled)';
this.$nextTick(() => this.scrollToOption(option));
}, },
scrollDown(step) { scrollDown(step) {
const items = this.items; const items = this.items;
const length = items.length;
let total = items.length;
let index = items.map(item => item.value).indexOf(this.value); let index = items.map(item => item.value).indexOf(this.value);
let length = items.length; while (total--) {
let total = Math.abs(step); index = (index + step + length) % length;
step = step > 0 ? 1 : -1; if (!items[index].disabled) {
while (length-- && total) { this.$emit('pick', items[index].value, true);
index = (index + step + items.length) % items.length; return;
const item = items[index];
if (!item.disabled) {
total--;
} }
} }
if (!items[index].disabled) { },
this.value = items[index].value;
this.$emit('pick', this.value, true); isValidValue(date) {
return this.items.filter(item => !item.disabled).map(item => item.value).indexOf(date) !== -1;
},
handleKeydown(event) {
const keyCode = event.keyCode;
if (keyCode === 38 || keyCode === 40) {
const mapping = { 40: 1, 38: -1 };
const offset = mapping[keyCode.toString()];
this.scrollDown(offset);
event.stopPropagation();
return;
} }
} }
}, },
...@@ -134,6 +142,7 @@ ...@@ -134,6 +142,7 @@
end: '18:00', end: '18:00',
step: '00:30', step: '00:30',
value: '', value: '',
defaultValue: '',
visible: false, visible: false,
minTime: '', minTime: '',
maxTime: '', maxTime: '',
......
<template> <template>
<transition name="el-zoom-in-top" @after-leave="$emit('dodestroy')"> <transition name="el-zoom-in-top" @after-leave="$emit('dodestroy')">
<div <div
v-show="currentVisible" v-show="visible"
class="el-time-panel el-popper" class="el-time-panel el-popper"
:class="popperClass"> :class="popperClass">
<div class="el-time-panel__content" :class="{ 'has-seconds': showSeconds }"> <div class="el-time-panel__content" :class="{ 'has-seconds': showSeconds }">
...@@ -10,9 +10,7 @@ ...@@ -10,9 +10,7 @@
@change="handleChange" @change="handleChange"
:show-seconds="showSeconds" :show-seconds="showSeconds"
@select-range="setSelectionRange" @select-range="setSelectionRange"
:hours="hours" :date="date">
:minutes="minutes"
:seconds="seconds">
</time-spinner> </time-spinner>
</div> </div>
<div class="el-time-panel__footer"> <div class="el-time-panel__footer">
...@@ -31,7 +29,7 @@ ...@@ -31,7 +29,7 @@
</template> </template>
<script type="text/babel"> <script type="text/babel">
import { limitRange } from '../util'; import { limitTimeRange, isDate, clearMilliseconds, timeWithinRange } from '../util';
import Locale from 'element-ui/src/mixins/locale'; import Locale from 'element-ui/src/mixins/locale';
export default { export default {
...@@ -42,21 +40,13 @@ ...@@ -42,21 +40,13 @@
}, },
props: { props: {
date: {
default() {
return new Date();
}
},
visible: Boolean visible: Boolean
}, },
watch: { watch: {
visible(val) { visible(val) {
this.currentVisible = val;
if (val) { if (val) {
this.oldHours = this.hours; this.oldValue = this.value;
this.oldMinutes = this.minutes;
this.oldSeconds = this.seconds;
this.$nextTick(() => this.$refs.spinner.emitSelectRange('hours')); this.$nextTick(() => this.$refs.spinner.emitSelectRange('hours'));
} }
}, },
...@@ -64,27 +54,25 @@ ...@@ -64,27 +54,25 @@
value(newVal) { value(newVal) {
let date; let date;
if (newVal instanceof Date) { if (newVal instanceof Date) {
date = limitRange(newVal, this.selectableRange); date = limitTimeRange(newVal, this.selectableRange, this.format);
} else if (!newVal) { } else if (!newVal) {
date = new Date(); date = this.defaultValue ? new Date(this.defaultValue) : new Date();
} }
this.handleChange({ this.date = date;
hours: date.getHours(), if (this.visible) {
minutes: date.getMinutes(), this.$nextTick(_ => this.adjustSpinners());
seconds: date.getSeconds() }
}, true);
this.$nextTick(_ => this.adjustScrollTop());
}, },
selectableRange(val) { selectableRange(val) {
this.$refs.spinner.selectableRange = val; this.$refs.spinner.selectableRange = val;
}, },
date(val) { defaultValue(val) {
if (!val) return; if (!isDate(this.value)) {
this.currentDate = val; this.date = val ? new Date(val) : new Date();
this.reinitDate(); }
} }
}, },
...@@ -93,15 +81,10 @@ ...@@ -93,15 +81,10 @@
popperClass: '', popperClass: '',
format: 'HH:mm:ss', format: 'HH:mm:ss',
value: '', value: '',
hours: 0, defaultValue: null,
minutes: 0, date: new Date(),
seconds: 0, oldValue: new Date(),
oldHours: 0,
oldMinutes: 0,
oldSeconds: 0,
selectableRange: [], selectableRange: [],
currentDate: this.$options.defaultValue || this.date || new Date(),
currentVisible: this.visible || false,
selectionRange: [0, 2], selectionRange: [0, 2],
disabled: false disabled: false
}; };
...@@ -114,35 +97,16 @@ ...@@ -114,35 +97,16 @@
}, },
methods: { methods: {
handleClear() { handleCancel() {
this.$emit('pick', '', false, true); this.$emit('pick', this.oldValue);
}, },
handleCancel() { handleChange(date) {
this.currentDate.setHours(this.oldHours); this.date = clearMilliseconds(date);
this.currentDate.setMinutes(this.oldMinutes); // if date is out of range, do not emit
this.currentDate.setSeconds(this.oldSeconds); if (this.isValidValue(this.date)) {
this.hours = this.currentDate.getHours(); this.$emit('pick', this.date, true);
this.minutes = this.currentDate.getMinutes();
this.seconds = this.currentDate.getSeconds();
const date = new Date(limitRange(this.currentDate, this.selectableRange, 'HH:mm:ss'));
this.$emit('pick', date, false, true);
},
handleChange(date, notUser) {
if (date.hours !== undefined) {
this.currentDate.setHours(date.hours);
this.hours = this.currentDate.getHours();
}
if (date.minutes !== undefined) {
this.currentDate.setMinutes(date.minutes);
this.minutes = this.currentDate.getMinutes();
}
if (date.seconds !== undefined) {
this.currentDate.setSeconds(date.seconds);
this.seconds = this.currentDate.getSeconds();
} }
this.handleConfirm(true, null, notUser);
}, },
setSelectionRange(start, end) { setSelectionRange(start, end) {
...@@ -150,18 +114,39 @@ ...@@ -150,18 +114,39 @@
this.selectionRange = [start, end]; this.selectionRange = [start, end];
}, },
handleConfirm(visible = false, first, notUser = false) { handleConfirm(visible = false, first) {
if (first) return; if (first) return;
const date = new Date(limitRange(this.currentDate, this.selectableRange, 'HH:mm:ss')); const date = clearMilliseconds(limitTimeRange(this.date, this.selectableRange, this.format));
this.$emit('pick', date, visible, !notUser, false); this.$emit('pick', date, visible, first);
}, },
adjustScrollTop() { handleKeydown(event) {
return this.$refs.spinner.adjustScrollTop(); const keyCode = event.keyCode;
}, const mapping = { 38: -1, 40: 1, 37: -1, 39: 1 };
// Left or Right
if (keyCode === 37 || keyCode === 39) {
const step = mapping[keyCode];
this.changeSelectionRange(step);
event.preventDefault();
return;
}
scrollDown(step) { // Up or Down
if (keyCode === 38 || keyCode === 40) {
const step = mapping[keyCode];
this.$refs.spinner.scrollDown(step); this.$refs.spinner.scrollDown(step);
event.preventDefault();
return;
}
},
isValidValue(date) {
return timeWithinRange(date, this.selectableRange, this.format);
},
adjustSpinners() {
return this.$refs.spinner.adjustSpinners();
}, },
changeSelectionRange(step) { changeSelectionRange(step) {
...@@ -170,19 +155,9 @@ ...@@ -170,19 +155,9 @@
const index = list.indexOf(this.selectionRange[0]); const index = list.indexOf(this.selectionRange[0]);
const next = (index + step + list.length) % list.length; const next = (index + step + list.length) % list.length;
this.$refs.spinner.emitSelectRange(mapping[next]); this.$refs.spinner.emitSelectRange(mapping[next]);
},
reinitDate() {
this.hours = this.currentDate.getHours();
this.minutes = this.currentDate.getMinutes();
this.seconds = this.currentDate.getSeconds();
} }
}, },
created() {
this.reinitDate();
},
mounted() { mounted() {
this.$nextTick(() => this.handleConfirm(true, true)); this.$nextTick(() => this.handleConfirm(true, true));
this.$emit('mounted'); this.$emit('mounted');
......
This diff is collapsed.
...@@ -35,35 +35,5 @@ export default { ...@@ -35,35 +35,5 @@ export default {
created() { created() {
this.panel = getPanel(this.type); this.panel = getPanel(this.type);
},
methods: {
handleKeydown(event) {
const keyCode = event.keyCode;
// TAB or ESC or Enter
if (keyCode === 9 || keyCode === 27 || keyCode === 13) {
!this.ranged && (this.pickerVisible = false);
event.stopPropagation();
this.picker.confirm && this.picker.confirm();
!this.ranged && (this.currentValue = this.picker.date);
if (this.$refs.reference.$refs) {
this.$refs.reference.$refs.input.blur();
} else {
[].slice.call(this.$refs.reference.querySelectorAll('input')).forEach(input => {
input.blur();
});
}
return;
}
const list = [38, 40, 37, 39];
if (list.indexOf(keyCode) !== -1) {
if (this.type === 'daterange' || this.type === 'datetimerange') return;
this.picker.handleKeyControl(keyCode);
event.stopPropagation();
return;
}
}
} }
}; };
...@@ -34,49 +34,5 @@ export default { ...@@ -34,49 +34,5 @@ export default {
created() { created() {
this.type = this.isRange ? 'timerange' : 'time'; this.type = this.isRange ? 'timerange' : 'time';
this.panel = this.isRange ? TimeRangePanel : TimePanel; this.panel = this.isRange ? TimeRangePanel : TimePanel;
},
methods: {
handleKeydown(event) {
const keyCode = event.keyCode;
// TAB or ESC
if (keyCode === 9 || keyCode === 27) {
this.pickerVisible = false;
event.stopPropagation();
return;
}
const mapping = { 38: -1, 40: 1, 37: -1, 39: 1 };
// Left or Right
if (keyCode === 37 || keyCode === 39) {
const step = mapping[keyCode];
this.picker.changeSelectionRange(step);
event.preventDefault();
return;
}
// Up or Down
if (keyCode === 38 || keyCode === 40) {
const step = mapping[keyCode];
this.picker.scrollDown(step);
event.preventDefault();
return;
}
if (keyCode === 13) {
!this.isRange && this.picker.handleConfirm();
if (this.$refs.reference.$refs) {
this.$refs.reference.$refs.input.blur();
} else {
[].slice.call(this.$refs.reference.querySelectorAll('input')).forEach(input => {
input.blur();
});
}
event.preventDefault();
return;
}
}
} }
}; };
...@@ -9,35 +9,5 @@ export default { ...@@ -9,35 +9,5 @@ export default {
beforeCreate() { beforeCreate() {
this.type = 'time-select'; this.type = 'time-select';
this.panel = Panel; this.panel = Panel;
},
methods: {
handleKeydown(event) {
const keyCode = event.keyCode;
// TAB or ESC or Enter
if (keyCode === 9 || keyCode === 27 || keyCode === 13) {
const input = this.$refs.reference;
const index = this.picker.items.map(v => v.value).indexOf(input.currentValue);
const exist = index !== -1;
if (!exist) {
input.currentValue = this.currentValue;
} else {
this.picker.handleClick(this.picker.items[index]);
}
this.pickerVisible = false;
input.$refs.input.blur();
event.stopPropagation();
return;
}
if (keyCode === 38 || keyCode === 40) {
const mapping = { 40: 1, 38: -1 };
const offset = mapping[keyCode.toString()];
this.picker.scrollDown(offset);
this.currentValue = this.picker.value;
event.stopPropagation();
return;
}
}
} }
}; };
...@@ -21,10 +21,6 @@ const newArray = function(start, end) { ...@@ -21,10 +21,6 @@ const newArray = function(start, end) {
return result; return result;
}; };
export const equalDate = function(dateA, dateB) {
return dateA === dateB || new Date(dateA).getTime() === new Date(dateB).getTime();
};
export const toDate = function(date) { export const toDate = function(date) {
return isDate(date) ? new Date(date) : null; return isDate(date) ? new Date(date) : null;
}; };
...@@ -93,44 +89,6 @@ export const getWeekNumber = function(src) { ...@@ -93,44 +89,6 @@ export const getWeekNumber = function(src) {
return 1 + Math.round(((date.getTime() - week1.getTime()) / 86400000 - 3 + (week1.getDay() + 6) % 7) / 7); return 1 + Math.round(((date.getTime() - week1.getTime()) / 86400000 - 3 + (week1.getDay() + 6) % 7) / 7);
}; };
export const prevMonth = function(src) {
const year = src.getFullYear();
const month = src.getMonth();
const date = src.getDate();
const newYear = month === 0 ? year - 1 : year;
const newMonth = month === 0 ? 11 : month - 1;
const newMonthDayCount = getDayCountOfMonth(newYear, newMonth);
if (newMonthDayCount < date) {
src.setDate(newMonthDayCount);
}
src.setMonth(newMonth);
src.setFullYear(newYear);
return new Date(src.getTime());
};
export const nextMonth = function(src) {
const year = src.getFullYear();
const month = src.getMonth();
const date = src.getDate();
const newYear = month === 11 ? year + 1 : year;
const newMonth = month === 11 ? 0 : month + 1;
const newMonthDayCount = getDayCountOfMonth(newYear, newMonth);
if (newMonthDayCount < date) {
src.setDate(newMonthDayCount);
}
src.setMonth(newMonth);
src.setFullYear(newYear);
return new Date(src.getTime());
};
export const getRangeHours = function(ranges) { export const getRangeHours = function(ranges) {
const hours = []; const hours = [];
let disabledHours = []; let disabledHours = [];
...@@ -154,26 +112,105 @@ export const getRangeHours = function(ranges) { ...@@ -154,26 +112,105 @@ export const getRangeHours = function(ranges) {
return hours; return hours;
}; };
export const limitRange = function(date, ranges, format = 'yyyy-MM-dd HH:mm:ss') { export const range = function(n) {
if (!ranges || !ranges.length) return date; // see https://stackoverflow.com/questions/3746725/create-a-javascript-array-containing-1-n
return Array.apply(null, {length: n}).map((_, n) => n);
};
export const modifyDate = function(date, y, m, d) {
return new Date(y, m, d, date.getHours(), date.getMinutes(), date.getSeconds(), date.getMilliseconds());
};
export const modifyTime = function(date, h, m, s) {
return new Date(date.getFullYear(), date.getMonth(), date.getDate(), h, m, s, date.getMilliseconds());
};
export const clearTime = function(date) {
return new Date(date.getFullYear(), date.getMonth(), date.getDate());
};
export const clearMilliseconds = function(date) {
return new Date(date.getFullYear(), date.getMonth(), date.getDate(), date.getHours(), date.getMinutes(), date.getSeconds(), 0);
};
export const limitTimeRange = function(date, ranges, format = 'HH:mm:ss') {
// TODO: refactory a more elegant solution
if (ranges.length === 0) return date;
const normalizeDate = date => dateUtil.parse(dateUtil.format(date, format), format);
const ndate = normalizeDate(date);
const nranges = ranges.map(range => range.map(normalizeDate));
if (nranges.some(nrange => ndate >= nrange[0] && ndate <= nrange[1])) return date;
let minDate = nranges[0][0];
let maxDate = nranges[0][0];
const len = ranges.length; nranges.forEach(nrange => {
minDate = new Date(Math.min(nrange[0], minDate));
maxDate = new Date(Math.max(nrange[1], minDate));
});
const ret = ndate < minDate ? minDate : maxDate;
// preserve Year/Month/Date
return modifyDate(
ret,
date.getFullYear(),
date.getMonth(),
date.getDate()
);
};
export const timeWithinRange = function(date, selectableRange, format) {
const limitedDate = limitTimeRange(date, selectableRange, format);
return limitedDate.getTime() === date.getTime();
};
date = dateUtil.parse(dateUtil.format(date, format), format); export const prevMonth = function(date) {
for (let i = 0; i < len; i++) { let year = date.getFullYear();
const range = ranges[i]; let month = date.getMonth();
if (date >= range[0] && date <= range[1]) { if (month === 0) {
return date; year -= 1;
month = 11;
} else {
month -= 1;
} }
const monthDate = Math.min(date.getDate(), getDayCountOfMonth(year, month));
return modifyDate(date, year, month, monthDate);
};
export const nextMonth = function(date) {
let year = date.getFullYear();
let month = date.getMonth();
if (month === 11) {
year += 1;
month = 0;
} else {
month += 1;
} }
const monthDate = Math.min(date.getDate(), getDayCountOfMonth(year, month));
return modifyDate(date, year, month, monthDate);
};
let maxDate = ranges[0][0]; // check for leap year Feburary
let minDate = ranges[0][0]; export const prevYear = function(date, amount = 1) {
const year = date.getFullYear() - amount;
const month = date.getMonth();
const monthDate = Math.min(date.getDate(), getDayCountOfMonth(year, month));
return modifyDate(date, year, month, monthDate);
};
ranges.forEach(range => { export const nextYear = function(date, amount = 1) {
minDate = new Date(Math.min(range[0], minDate)); const year = date.getFullYear() + amount;
maxDate = new Date(Math.max(range[1], maxDate)); const month = date.getMonth();
}); const monthDate = Math.min(date.getDate(), getDayCountOfMonth(year, month));
return modifyDate(date, year, month, monthDate);
};
// {prev, next} Date works for daylight saving time
// add / subtract one day's duration does not work
export const prevDate = function(date, amount = 1) {
return new Date(date.getFullYear(), date.getMonth(), date.getDate() - amount);
};
return date < minDate ? minDate : maxDate; export const nextDate = function(date, amount = 1) {
return new Date(date.getFullYear(), date.getMonth(), date.getDate() + amount);
}; };
This diff is collapsed.
import { createVue, destroyVM } from '../util'; import { createVue, destroyVM } from '../util';
const DELAY = 50;
describe('Form', () => { describe('Form', () => {
let vm; let vm;
afterEach(() => { afterEach(() => {
...@@ -362,7 +364,7 @@ describe('Form', () => { ...@@ -362,7 +364,7 @@ describe('Form', () => {
template: ` template: `
<el-form :model="form" :rules="rules" ref="form"> <el-form :model="form" :rules="rules" ref="form">
<el-form-item label="记住密码" prop="date" ref="field"> <el-form-item label="记住密码" prop="date" ref="field">
<el-date-picker type="date" placeholder="选择日期" v-model="form.date" style="width: 100%;"></el-date-picker> <el-date-picker type="date" ref="picker" placeholder="选择日期" v-model="form.date" style="width: 100%;"></el-date-picker>
</el-form-item> </el-form-item>
</el-form> </el-form>
`, `,
...@@ -377,26 +379,38 @@ describe('Form', () => { ...@@ -377,26 +379,38 @@ describe('Form', () => {
] ]
} }
}; };
},
methods: {
setValue(value) {
this.form.date = value;
}
} }
}, true); }, true);
vm.$refs.form.validate(valid => { vm.$refs.form.validate(valid => {
let field = vm.$refs.field; let field = vm.$refs.field;
expect(valid).to.not.true; expect(valid).to.not.true;
vm.$refs.form.$nextTick(_ => { setTimeout(_ => {
expect(field.validateMessage).to.equal('请选择日期'); expect(field.validateMessage).to.equal('请选择日期');
// programatic modification does not trigger change
vm.setValue(new Date()); vm.value = new Date();
setTimeout(_ => {
vm.$refs.form.$nextTick(_ => { expect(field.validateMessage).to.equal('请选择日期');
vm.value = '';
// user modification triggers change
const input = vm.$refs.picker.$el.querySelector('input');
input.blur();
input.focus();
setTimeout(_ => {
const keyDown = (el, keyCode) => {
const evt = document.createEvent('Events');
evt.initEvent('keydown', true, true);
evt.keyCode = keyCode;
el.dispatchEvent(evt);
};
keyDown(input, 37);
keyDown(input, 13);
setTimeout(_ => {
expect(field.validateMessage).to.equal(''); expect(field.validateMessage).to.equal('');
done(); done();
}); }, DELAY);
}); }, DELAY);
}, DELAY);
}, DELAY);
}); });
}); });
it('timepicker', done => { it('timepicker', done => {
...@@ -404,7 +418,7 @@ describe('Form', () => { ...@@ -404,7 +418,7 @@ describe('Form', () => {
template: ` template: `
<el-form :model="form" :rules="rules" ref="form"> <el-form :model="form" :rules="rules" ref="form">
<el-form-item label="记住密码" prop="date" ref="field"> <el-form-item label="记住密码" prop="date" ref="field">
<el-time-picker type="fixed-time" placeholder="选择时间" v-model="form.date" style="width: 100%;"></el-time-picker> <el-time-picker type="fixed-time" ref="picker" placeholder="选择时间" v-model="form.date" style="width: 100%;"></el-time-picker>
</el-form-item> </el-form-item>
</el-form> </el-form>
`, `,
...@@ -419,25 +433,31 @@ describe('Form', () => { ...@@ -419,25 +433,31 @@ describe('Form', () => {
] ]
} }
}; };
},
methods: {
setValue(value) {
this.form.date = value;
}
} }
}, true); }, true);
vm.$refs.form.validate(valid => { vm.$refs.form.validate(valid => {
let field = vm.$refs.field; let field = vm.$refs.field;
expect(valid).to.not.true; expect(valid).to.not.true;
vm.$refs.form.$nextTick(_ => { setTimeout(_ => {
expect(field.validateMessage).to.equal('请选择时间'); expect(field.validateMessage).to.equal('请选择时间');
vm.setValue(new Date()); // programatic modification does not trigger change
vm.value = new Date();
vm.$refs.form.$nextTick(_ => { setTimeout(_ => {
expect(field.validateMessage).to.equal('请选择时间');
vm.value = '';
// user modification triggers change
const input = vm.$refs.picker.$el.querySelector('input');
input.blur();
input.focus();
setTimeout(_ => {
vm.$refs.picker.picker.$el.querySelector('.confirm').click();
setTimeout(_ => {
expect(field.validateMessage).to.equal(''); expect(field.validateMessage).to.equal('');
done(); done();
}); }, DELAY);
}); }, DELAY);
}, DELAY);
}, DELAY);
}); });
}); });
it('checkbox group', done => { it('checkbox group', done => {
......
import { createTest, destroyVM, createVue } from '../util'; import { createTest, destroyVM, createVue } from '../util';
import TimePicker from 'packages/time-picker'; import TimePicker from 'packages/time-picker';
import Vue from 'vue';
const DELAY = 100;
describe('TimePicker', () => { describe('TimePicker', () => {
let vm; let vm;
...@@ -15,7 +16,6 @@ describe('TimePicker', () => { ...@@ -15,7 +16,6 @@ describe('TimePicker', () => {
}); });
expect(vm.$el.querySelector('input').getAttribute('placeholder')).to.equal('test'); expect(vm.$el.querySelector('input').getAttribute('placeholder')).to.equal('test');
expect(vm.$el.querySelector('input').getAttribute('readonly')).to.ok; expect(vm.$el.querySelector('input').getAttribute('readonly')).to.ok;
destroyVM(vm);
}); });
it('format', () => { it('format', () => {
...@@ -24,7 +24,6 @@ describe('TimePicker', () => { ...@@ -24,7 +24,6 @@ describe('TimePicker', () => {
value: new Date(2016, 9, 10, 18, 40) value: new Date(2016, 9, 10, 18, 40)
}); });
expect(vm.$el.querySelector('input').value).to.equal('18-40-00'); expect(vm.$el.querySelector('input').value).to.equal('18-40-00');
destroyVM(vm);
}); });
it('default value', done => { it('default value', done => {
...@@ -44,20 +43,28 @@ describe('TimePicker', () => { ...@@ -44,20 +43,28 @@ describe('TimePicker', () => {
expect(times[0].textContent).to.equal('18'); expect(times[0].textContent).to.equal('18');
expect(times[1].textContent).to.equal('40'); expect(times[1].textContent).to.equal('40');
expect(times[2].textContent).to.equal('00'); expect(times[2].textContent).to.equal('00');
destroyVM(vm);
done(); done();
}, 100); }, DELAY);
}); });
it('select time', done => { it('select time', done => {
vm = createTest(TimePicker, true); vm = createVue({
const input = vm.$el.querySelector('input'); template: '<el-time-picker ref="compo" v-model="value"></el-time-picker>',
data() {
return {
value: ''
};
}
}, true);
const timePicker = vm.$refs.compo;
const input = timePicker.$el.querySelector('input');
input.blur(); input.blur();
input.focus(); input.focus();
Vue.nextTick(_ => { setTimeout(_ => {
const list = vm.picker.$el.querySelectorAll('.el-time-spinner__list'); const list = timePicker.picker.$el.querySelectorAll('.el-time-spinner__list');
const hoursEl = list[0]; const hoursEl = list[0];
const minutesEl = list[1]; const minutesEl = list[1];
const secondsEl = list[2]; const secondsEl = list[2];
...@@ -65,57 +72,75 @@ describe('TimePicker', () => { ...@@ -65,57 +72,75 @@ describe('TimePicker', () => {
const minuteEl = minutesEl.querySelectorAll('.el-time-spinner__item')[36]; const minuteEl = minutesEl.querySelectorAll('.el-time-spinner__item')[36];
const secondEl = secondsEl.querySelectorAll('.el-time-spinner__item')[20]; const secondEl = secondsEl.querySelectorAll('.el-time-spinner__item')[20];
// click hour, minute, second one at a time.
hourEl.click(); hourEl.click();
vm.$nextTick(_ => {
minuteEl.click(); minuteEl.click();
vm.$nextTick(_ => {
secondEl.click(); secondEl.click();
setTimeout(_ => {
Vue.nextTick(_ => { const date = timePicker.picker.date;
const date = vm.picker.currentDate;
expect(hourEl.classList.contains('active')).to.true; expect(hourEl.classList.contains('active')).to.true;
expect(minuteEl.classList.contains('active')).to.true; expect(minuteEl.classList.contains('active')).to.true;
expect(secondEl.classList.contains('active')).to.true; expect(secondEl.classList.contains('active')).to.true;
expect(date.getHours()).to.equal(4); expect(date.getHours()).to.equal(4);
expect(date.getMinutes()).to.equal(36); expect(date.getMinutes()).to.equal(36);
expect(date.getSeconds()).to.equal(20); expect(date.getSeconds()).to.equal(20);
destroyVM(vm);
done(); done();
}, DELAY);
}); });
}); });
}, DELAY);
}); });
it('click cancel button', done => { it('click cancel button', done => {
vm = createTest(TimePicker, true); vm = createVue({
const input = vm.$el.querySelector('input'); template: '<el-time-picker ref="compo" v-model="value"></el-time-picker>',
data() {
return {
value: ''
};
}
}, true);
const timePicker = vm.$refs.compo;
const input = timePicker.$el.querySelector('input');
input.blur(); input.blur();
input.focus(); input.focus();
Vue.nextTick(_ => { setTimeout(_ => {
vm.picker.$el.querySelector('.el-time-panel__btn.cancel').click(); timePicker.picker.$el.querySelector('.el-time-panel__btn.cancel').click();
Vue.nextTick(_ => { setTimeout(_ => {
expect(vm.picker.currentDate).to.empty; expect(vm.value).to.equal('');
done(); done();
}); }, DELAY);
}); }, DELAY);
}); });
it('click confirm button', done => { it('click confirm button', done => {
vm = createTest(TimePicker, true); vm = createVue({
const input = vm.$el.querySelector('input'); template: '<el-time-picker ref="compo" v-model="value"></el-time-picker>',
data() {
return {
value: ''
};
}
}, true);
const timePicker = vm.$refs.compo;
const input = timePicker.$el.querySelector('input');
input.blur(); input.blur();
input.focus(); input.focus();
Vue.nextTick(_ => { setTimeout(_ => {
vm.picker.$el.querySelector('.el-time-panel__btn.confirm').click(); timePicker.picker.$el.querySelector('.el-time-panel__btn.confirm').click();
Vue.nextTick(_ => { setTimeout(_ => {
expect(vm.picker.currentDate).to.exist; expect(vm.value.toISOString()).to.exist;
done(); done();
}); }, DELAY);
}); }, DELAY);
}); });
it('set format', done => { it('set format', done => {
...@@ -130,7 +155,6 @@ describe('TimePicker', () => { ...@@ -130,7 +155,6 @@ describe('TimePicker', () => {
setTimeout(_ => { setTimeout(_ => {
expect(vm.picker.$el.querySelectorAll('.el-time-spinner__wrapper')[2].style.display).to.equal('none'); expect(vm.picker.$el.querySelectorAll('.el-time-spinner__wrapper')[2].style.display).to.equal('none');
destroyVM(vm);
done(); done();
}, 20); }, 20);
}); });
...@@ -147,9 +171,8 @@ describe('TimePicker', () => { ...@@ -147,9 +171,8 @@ describe('TimePicker', () => {
setTimeout(_ => { setTimeout(_ => {
expect(vm.picker.$el.querySelectorAll('.el-time-spinner__wrapper')[2].style.display).to.equal('none'); expect(vm.picker.$el.querySelectorAll('.el-time-spinner__wrapper')[2].style.display).to.equal('none');
destroyVM(vm);
done(); done();
}, 20); }, DELAY);
}); });
it('selectableRange', done => { it('selectableRange', done => {
...@@ -172,9 +195,8 @@ describe('TimePicker', () => { ...@@ -172,9 +195,8 @@ describe('TimePicker', () => {
hoursEl.querySelectorAll('.disabled')[0].click(); hoursEl.querySelectorAll('.disabled')[0].click();
expect(disabledHours).to.not.include.members([18, 19, 20]); expect(disabledHours).to.not.include.members([18, 19, 20]);
destroyVM(vm);
done(); done();
}, 20); }, DELAY);
}); });
it('event focus and blur', done => { it('event focus and blur', done => {
...@@ -229,48 +251,66 @@ describe('TimePicker', () => { ...@@ -229,48 +251,66 @@ describe('TimePicker', () => {
describe('TimePicker(range)', () => { describe('TimePicker(range)', () => {
let vm; let vm;
beforeEach(done => {
afterEach(() => destroyVM(vm));
it('create', done => {
vm = createTest(TimePicker, { vm = createTest(TimePicker, {
isRange: true, isRange: true,
value: [new Date(2016, 9, 10, 18, 40), new Date(2016, 9, 10, 19, 40)] value: [new Date(2016, 9, 10, 18, 40), new Date(2016, 9, 10, 19, 40)]
}, true); }, true);
const input = vm.$el.querySelector('input');
input.click();
setTimeout(done, 20);
});
afterEach(() => destroyVM(vm)); vm.$el.querySelector('input').click();
it('create', () => { setTimeout(_ => {
expect(vm.picker.$el.querySelectorAll('.el-time-range-picker__cell')).to.length(2); expect(vm.picker.$el.querySelectorAll('.el-time-range-picker__cell')).to.length(2);
expect(vm.picker.minDate.getTime()).to.equal(new Date(2016, 9, 10, 18, 40).getTime());
expect(vm.picker.maxDate.getTime()).to.equal(new Date(2016, 9, 10, 19, 40).getTime());
done();
}, DELAY);
}); });
it('default value', () => { it('default value', done => {
expect(vm.picker.minTime.getTime()).to.equal(new Date(2016, 9, 10, 18, 40).getTime()); const defaultValue = [new Date(2000, 9, 1, 10, 0, 0), new Date(2000, 9, 1, 11, 0, 0)];
expect(vm.picker.maxTime.getTime()).to.equal(new Date(2016, 9, 10, 19, 40).getTime()); vm = createVue({
}); template: '<el-time-picker ref="compo" is-range v-model="value" :default-value="defaultValue"></el-time-picker>',
data() {
it('minTime < maxTime', done => { return {
const vm2 = createTest(TimePicker, { value: '',
isRange: true, defaultValue
value: [new Date(2016, 9, 10, 23, 40), new Date(2016, 9, 10, 10, 40)] };
}
}, true); }, true);
const input = vm2.$el.querySelector('input');
input.click(); const timePicker = vm.$refs.compo;
setTimeout(() => { timePicker.$el.querySelector('input').click();
expect(vm2.picker.maxTime >= vm2.picker.minTime).to.true;
destroyVM(vm2); setTimeout(_ => {
expect(timePicker.picker.minDate.getTime()).to.equal(defaultValue[0].getTime());
expect(timePicker.picker.maxDate.getTime()).to.equal(defaultValue[1].getTime());
done(); done();
}, 100); }, DELAY);
}); });
it('click cancel button', done => { it('cancel button', done => {
vm.picker.$el.querySelector('.el-time-panel__btn.cancel').click(); vm = createVue({
Vue.nextTick(_ => { template: '<el-time-picker ref="compo" is-range v-model="value"></el-time-picker>',
expect(vm.picker.currentDate).to.empty; data() {
return {
value: ''
};
}
}, true);
const timePicker = vm.$refs.compo;
timePicker.$el.querySelector('input').click();
setTimeout(_ => {
timePicker.picker.$el.querySelector('.cancel').click();
setTimeout(_ => {
expect(timePicker.picker.visible).to.false;
expect(vm.value).to.equal('');
done(); done();
}); }, DELAY);
}, DELAY);
}); });
}); });
...@@ -27,7 +27,6 @@ describe('TimeSelect', () => { ...@@ -27,7 +27,6 @@ describe('TimeSelect', () => {
expect(vm.picker.end).to.equal('18:30'); expect(vm.picker.end).to.equal('18:30');
expect(vm.picker.step).to.equal('00:15'); expect(vm.picker.step).to.equal('00:15');
expect(vm.$el.querySelector('input').getAttribute('placeholder')).to.equal('test'); expect(vm.$el.querySelector('input').getAttribute('placeholder')).to.equal('test');
destroyVM(vm);
done(); done();
}); });
}); });
...@@ -60,7 +59,6 @@ describe('TimeSelect', () => { ...@@ -60,7 +59,6 @@ describe('TimeSelect', () => {
target.click(); target.click();
Vue.nextTick(_ => { Vue.nextTick(_ => {
expect(vm.value).to.equal(time); expect(vm.value).to.equal(time);
destroyVM(vm);
done(); done();
}); });
}); });
...@@ -79,7 +77,6 @@ describe('TimeSelect', () => { ...@@ -79,7 +77,6 @@ describe('TimeSelect', () => {
expect(input.value).to.equal('14:30'); expect(input.value).to.equal('14:30');
expect(vm.picker.$el.querySelector('.selected')).to.be.ok; expect(vm.picker.$el.querySelector('.selected')).to.be.ok;
expect(vm.picker.$el.querySelector('.selected').textContent).to.equal('14:30'); expect(vm.picker.$el.querySelector('.selected').textContent).to.equal('14:30');
destroyVM(vm);
done(); done();
}, 50); }, 50);
}); });
...@@ -104,7 +101,6 @@ describe('TimeSelect', () => { ...@@ -104,7 +101,6 @@ describe('TimeSelect', () => {
const elm = elms[elms.length - 1]; const elm = elms[elms.length - 1];
expect(elm.textContent).to.equal('14:30'); expect(elm.textContent).to.equal('14:30');
destroyVM(vm);
done(); done();
}, 50); }, 50);
}); });
...@@ -134,8 +130,7 @@ describe('TimeSelect', () => { ...@@ -134,8 +130,7 @@ describe('TimeSelect', () => {
vm.value = '10:30'; vm.value = '10:30';
setTimeout(_ => { setTimeout(_ => {
expect(picker.picker.value).to.equal('09:30'); expect(picker.picker.value).to.equal('10:30');
destroyVM(vm);
done(); done();
}, 50); }, 50);
}, 50); }, 50);
...@@ -161,7 +156,6 @@ describe('TimeSelect', () => { ...@@ -161,7 +156,6 @@ describe('TimeSelect', () => {
const elm = picker.picker.$el.querySelector('.disabled'); const elm = picker.picker.$el.querySelector('.disabled');
expect(elm.textContent).to.equal('14:30'); expect(elm.textContent).to.equal('14:30');
destroyVM(vm);
done(); done();
}, 50); }, 50);
}); });
...@@ -191,8 +185,7 @@ describe('TimeSelect', () => { ...@@ -191,8 +185,7 @@ describe('TimeSelect', () => {
vm.value = '10:30'; vm.value = '10:30';
setTimeout(_ => { setTimeout(_ => {
expect(picker.picker.value).to.equal('09:30'); expect(picker.picker.value).to.equal('10:30');
destroyVM(vm);
done(); done();
}, 50); }, 50);
}, 50); }, 50);
...@@ -242,7 +235,6 @@ describe('TimeSelect', () => { ...@@ -242,7 +235,6 @@ describe('TimeSelect', () => {
vm.$nextTick(_ => { vm.$nextTick(_ => {
expect(spy.calledOnce).to.be.true; expect(spy.calledOnce).to.be.true;
destroyVM(vm);
done(); done();
}); });
}); });
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment