<template> <div class="el-time-spinner" :class="{ 'has-seconds': showSeconds }"> <template v-if="!arrowControl"> <el-scrollbar @mouseenter.native="emitSelectRange('hours')" @mousemove.native="adjustCurrentSpinner('hours')" class="el-time-spinner__wrapper" wrap-style="max-height: inherit;" view-class="el-time-spinner__list" noresize tag="ul" ref="hours"> <li @click="handleClick('hours', { value: hour, disabled: disabled })" v-for="(disabled, hour) in hoursList" class="el-time-spinner__item" :key="hour" :class="{ 'active': hour === hours, 'disabled': disabled }">{{ ('0' + (amPmMode ? (hour % 12 || 12) : hour )).slice(-2) }}{{ amPm(hour) }}</li> </el-scrollbar> <el-scrollbar @mouseenter.native="emitSelectRange('minutes')" @mousemove.native="adjustCurrentSpinner('minutes')" class="el-time-spinner__wrapper" wrap-style="max-height: inherit;" view-class="el-time-spinner__list" noresize tag="ul" ref="minutes"> <li @click="handleClick('minutes', { value: key, disabled: false })" v-for="(enabled, key) in minutesList" :key="key" class="el-time-spinner__item" :class="{ 'active': key === minutes, disabled: !enabled }">{{ ('0' + key).slice(-2) }}</li> </el-scrollbar> <el-scrollbar v-show="showSeconds" @mouseenter.native="emitSelectRange('seconds')" @mousemove.native="adjustCurrentSpinner('seconds')" class="el-time-spinner__wrapper" wrap-style="max-height: inherit;" view-class="el-time-spinner__list" noresize tag="ul" ref="seconds"> <li @click="handleClick('seconds', { value: key, disabled: false })" v-for="(second, key) in 60" class="el-time-spinner__item" :class="{ 'active': key === seconds }" :key="key">{{ ('0' + key).slice(-2) }}</li> </el-scrollbar> </template> <template v-if="arrowControl"> <div @mouseenter="emitSelectRange('hours')" class="el-time-spinner__wrapper is-arrow"> <i v-repeat-click="decrease" class="el-time-spinner__arrow el-icon-arrow-up"></i> <i v-repeat-click="increase" class="el-time-spinner__arrow el-icon-arrow-down"></i> <ul class="el-time-spinner__list" ref="hours"> <li class="el-time-spinner__item" :class="{ 'active': hour === hours, 'disabled': hoursList[hour] }" v-for="(hour, key) in arrowHourList" :key="key">{{ hour === undefined ? '' : ('0' + (amPmMode ? (hour % 12 || 12) : hour )).slice(-2) + amPm(hour) }}</li> </ul> </div> <div @mouseenter="emitSelectRange('minutes')" class="el-time-spinner__wrapper is-arrow"> <i v-repeat-click="decrease" class="el-time-spinner__arrow el-icon-arrow-up"></i> <i v-repeat-click="increase" class="el-time-spinner__arrow el-icon-arrow-down"></i> <ul class="el-time-spinner__list" ref="minutes"> <li class="el-time-spinner__item" :class="{ 'active': minute === minutes }" v-for="(minute, key) in arrowMinuteList" :key="key"> {{ minute === undefined ? '' : ('0' + minute).slice(-2) }} </li> </ul> </div> <div @mouseenter="emitSelectRange('seconds')" class="el-time-spinner__wrapper is-arrow" v-if="showSeconds"> <i v-repeat-click="decrease" class="el-time-spinner__arrow el-icon-arrow-up"></i> <i v-repeat-click="increase" class="el-time-spinner__arrow el-icon-arrow-down"></i> <ul class="el-time-spinner__list" ref="seconds"> <li v-for="(second, key) in arrowSecondList" class="el-time-spinner__item" :class="{ 'active': second === seconds }" :key="key"> {{ second === undefined ? '' : ('0' + second).slice(-2) }} </li> </ul> </div> </template> </div> </template> <script type="text/babel"> import { getRangeHours, getRangeMinutes, modifyTime } from 'element-ui/src/utils/date-util'; import ElScrollbar from 'element-ui/packages/scrollbar'; import RepeatClick from 'element-ui/src/directives/repeat-click'; export default { components: { ElScrollbar }, directives: { repeatClick: RepeatClick }, props: { date: {}, defaultValue: {}, // reserved for future use showSeconds: { type: Boolean, default: true }, arrowControl: Boolean, amPmMode: { type: String, default: '' // 'a': am/pm; 'A': AM/PM } }, computed: { hours() { return this.date.getHours(); }, minutes() { return this.date.getMinutes(); }, seconds() { return this.date.getSeconds(); }, hoursList() { return getRangeHours(this.selectableRange); }, minutesList() { return getRangeMinutes(this.selectableRange, this.hours); }, arrowHourList() { const hours = this.hours; return [ hours > 0 ? hours - 1 : undefined, hours, hours < 23 ? hours + 1 : undefined ]; }, arrowMinuteList() { const minutes = this.minutes; return [ minutes > 0 ? minutes - 1 : undefined, minutes, minutes < 59 ? minutes + 1 : undefined ]; }, arrowSecondList() { const seconds = this.seconds; return [ seconds > 0 ? seconds - 1 : undefined, seconds, seconds < 59 ? seconds + 1 : undefined ]; } }, data() { return { selectableRange: [], currentScrollbar: null }; }, mounted() { this.$nextTick(() => { !this.arrowControl && this.bindScrollEvent(); }); }, methods: { increase() { this.scrollDown(1); }, decrease() { this.scrollDown(-1); }, modifyDateField(type, value) { switch (type) { 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; } }, handleClick(type, {value, disabled}) { if (!disabled) { this.modifyDateField(type, value); this.emitSelectRange(type); this.adjustSpinner(type, value); } }, emitSelectRange(type) { if (type === 'hours') { this.$emit('select-range', 0, 2); } else if (type === 'minutes') { this.$emit('select-range', 3, 5); } else if (type === 'seconds') { this.$emit('select-range', 6, 8); } this.currentScrollbar = type; }, bindScrollEvent() { const bindFuntion = (type) => { 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); }; }; bindFuntion('hours'); bindFuntion('minutes'); bindFuntion('seconds'); }, handleScroll(type) { const value = Math.min(Math.floor((this.$refs[type].wrap.scrollTop - (this.scrollBarHeight(type) * 0.5 - 10) / this.typeItemHeight(type) + 3) / this.typeItemHeight(type)), (type === 'hours' ? 23 : 59)); this.modifyDateField(type, value); }, // NOTE: used by datetime / date-range panel // renamed from adjustScrollTop // should try to refactory it adjustSpinners() { this.adjustSpinner('hours', this.hours); this.adjustSpinner('minutes', this.minutes); this.adjustSpinner('seconds', this.seconds); }, adjustCurrentSpinner(type) { this.adjustSpinner(type, this[type]); }, adjustSpinner(type, value) { if (this.arrowControl) return; const el = this.$refs[type].wrap; if (el) { el.scrollTop = Math.max(0, value * this.typeItemHeight(type)); } }, scrollDown(step) { if (!this.currentScrollbar) { this.emitSelectRange('hours'); } const label = this.currentScrollbar; const hoursList = this.hoursList; let now = this[label]; if (this.currentScrollbar === 'hours') { let total = Math.abs(step); step = step > 0 ? 1 : -1; let length = hoursList.length; while (length-- && total) { now = (now + step + hoursList.length) % hoursList.length; if (hoursList[now]) { continue; } total--; } if (hoursList[now]) return; } else { now = (now + step + 60) % 60; } this.modifyDateField(label, now); this.adjustSpinner(label, now); }, amPm(hour) { let shouldShowAmPm = this.amPmMode.toLowerCase() === 'a'; if (!shouldShowAmPm) return ''; let isCapital = this.amPmMode === 'A'; let content = (hour < 12) ? ' am' : ' pm'; if (isCapital) content = content.toUpperCase(); return content; }, typeItemHeight(type) { return this.$refs[type].$el.querySelector('li').offsetHeight; }, scrollBarHeight(type) { return this.$refs[type].$el.offsetHeight; } } }; </script>