UNPKG

quasar

Version:

Build high-performance VueJS user interfaces (SPA, PWA, SSR, Mobile and Desktop) in record time

665 lines (576 loc) 17.5 kB
import Vue from 'vue' import QBtn from '../btn/QBtn.js' import TouchPan from '../../directives/TouchPan.js' import { formatDate, __splitDate } from '../../utils/date.js' import { position } from '../../utils/event.js' import { pad } from '../../utils/format.js' import DateTimeMixin from './datetime-mixin.js' export default Vue.extend({ name: 'QTime', mixins: [ DateTimeMixin ], directives: { TouchPan }, props: { mask: { default: null }, format24h: { type: Boolean, default: null }, options: Function, hourOptions: Array, minuteOptions: Array, secondOptions: Array, withSeconds: Boolean, nowBtn: Boolean }, data () { const model = __splitDate( this.value, this.__getComputedMask(), this.__getComputedLocale(), this.calendar ) let view = 'Hour' if (model.hour !== null) { if (model.minute === null) { view = 'Minute' } else if (this.withSeconds === true && model.second === null) { view = 'Second' } } return { view, isAM: model.hour === null || model.hour < 12, innerModel: model } }, watch: { value (v) { const model = __splitDate(v, this.computedMask, this.computedLocale, this.calendar) if ( model.dateHash !== this.innerModel.dateHash || model.timeHash !== this.innerModel.timeHash ) { this.innerModel = model if (model.hour === null) { this.view = 'Hour' } else { this.isAM = model.hour < 12 } } } }, computed: { classes () { return `q-time--${this.landscape === true ? 'landscape' : 'portrait'}` + (this.dark === true ? ' q-time--dark' : '') + (this.readonly === true && this.disable !== true ? ' q-time--readonly' : '') + (this.disable === true ? ' disable' : '') + (this.bordered === true ? ` q-time--bordered` : '') + (this.square === true ? ` q-time--square no-border-radius` : '') + (this.flat === true ? ` q-time--flat no-shadow` : '') }, computedMask () { return this.__getComputedMask() }, stringModel () { const time = this.innerModel return { hour: time.hour === null ? '--' : ( this.computedFormat24h === true ? pad(time.hour) : String( this.isAM === true ? (time.hour === 0 ? 12 : time.hour) : (time.hour > 12 ? time.hour - 12 : time.hour) ) ), minute: time.minute === null ? '--' : pad(time.minute), second: time.second === null ? '--' : pad(time.second) } }, computedFormat24h () { return this.format24h !== null ? this.format24h : this.$q.lang.date.format24h }, pointerStyle () { const forHour = this.view === 'Hour', divider = forHour === true ? 12 : 60, amount = this.innerModel[this.view.toLowerCase()], degrees = Math.round(amount * (360 / divider)) - 180 let transform = `rotate3d(0,0,1,${degrees}deg) translate3d(-50%,0,0)` if ( forHour === true && this.computedFormat24h === true && this.innerModel.hour >= 12 ) { transform += ' scale3d(.7,.7,.7)' } return { transform } }, minLink () { return this.innerModel.hour !== null }, secLink () { return this.minLink === true && this.innerModel.minute !== null }, hourInSelection () { return this.hourOptions !== void 0 ? val => this.hourOptions.includes(val) : ( this.options !== void 0 ? val => this.options(val, null, null) : void 0 ) }, minuteInSelection () { return this.minuteOptions !== void 0 ? val => this.minuteOptions.includes(val) : ( this.options !== void 0 ? val => this.options(this.innerModel.hour, val, null) : void 0 ) }, secondInSelection () { return this.secondOptions !== void 0 ? val => this.secondOptions.includes(val) : ( this.options !== void 0 ? val => this.options(this.innerModel.hour, this.innerModel.minute, val) : void 0 ) }, positions () { let start, end, offset = 0, step = 1, inSel if (this.view === 'Hour') { inSel = this.hourInSelection if (this.computedFormat24h === true) { start = 0 end = 23 } else { start = 0 end = 11 if (this.isAM === false) { offset = 12 } } } else { start = 0 end = 55 step = 5 if (this.view === 'Minute') { inSel = this.minuteInSelection } else { inSel = this.secondInSelection } } const pos = [] for (let val = start, index = start; val <= end; val += step, index++) { const actualVal = val + offset, disable = inSel !== void 0 && inSel(actualVal) === false, label = this.view === 'Hour' && val === 0 ? (this.format24h === true ? '00' : '12') : val pos.push({ val: actualVal, index, disable, label }) } return pos } }, methods: { setNow () { this.__updateValue({ ...this.__getCurrentDate(), ...this.__getCurrentTime() }) this.view = 'Hour' }, __click (evt) { this.__drag({ isFirst: true, evt }) this.__drag({ isFinal: true, evt }) }, __drag (event) { // cases when on a popup getting closed // on previously emitted value if (this._isBeingDestroyed === true || this._isDestroyed === true) { return } if (event.isFirst) { const clock = this.$refs.clock, { top, left, width } = clock.getBoundingClientRect(), dist = width / 2 this.dragging = { top: top + dist, left: left + dist, dist: dist * 0.7 } this.dragCache = null this.__updateClock(event.evt) return } this.__updateClock(event.evt) if (event.isFinal) { this.dragging = false if (this.view === 'Hour') { this.view = 'Minute' } else if (this.withSeconds && this.view === 'Minute') { this.view = 'Second' } } }, __updateClock (evt) { let val, pos = position(evt), height = Math.abs(pos.top - this.dragging.top), distance = Math.sqrt( Math.pow(Math.abs(pos.top - this.dragging.top), 2) + Math.pow(Math.abs(pos.left - this.dragging.left), 2) ), angle = Math.asin(height / distance) * (180 / Math.PI) if (pos.top < this.dragging.top) { angle = this.dragging.left < pos.left ? 90 - angle : 270 + angle } else { angle = this.dragging.left < pos.left ? angle + 90 : 270 - angle } if (this.view === 'Hour') { val = Math.round(angle / 30) if (this.computedFormat24h === true) { if (distance < this.dragging.dist) { if (val < 12) { val += 12 } } else if (val === 12) { val = 0 } this.isAM = val < 12 } else if (this.isAM === true && val === 12) { val = 0 } else if (this.isAM === false && val !== 12) { val += 12 } } else { val = Math.round(angle / 6) if (val === 60) { val = 0 } } if (this.dragCache === val) { return } const opt = this[`${this.view.toLowerCase()}InSelection`] if (opt !== void 0 && opt(val) !== true) { return } this.dragCache = val this[`__set${this.view}`](val) }, __onKeyupHour (e) { if (e.keyCode === 13) { // ENTER this.view = 'Hour' } else { const wrap = this.computedFormat24h === true ? 24 : 12, offset = this.computedFormat24h !== true && this.isAM === false ? 12 : 0 if (e.keyCode === 37) { // ARROW LEFT this.__setHour(offset + (24 + this.innerModel.hour - 1) % wrap) } else if (e.keyCode === 39) { // ARROW RIGHT this.__setHour(offset + (24 + this.innerModel.hour + 1) % wrap) } } }, __onKeyupMinute (e) { if (e.keyCode === 13) { // ENTER this.view = 'Minute' } else if (e.keyCode === 37) { // ARROW LEFT this.__setMinute((60 + this.innerModel.minute - 1) % 60) } else if (e.keyCode === 39) { // ARROW RIGHT this.__setMinute((60 + this.innerModel.minute + 1) % 60) } }, __onKeyupSecond (e) { if (e.keyCode === 13) { // ENTER this.view = 'Second' } else if (e.keyCode === 37) { // ARROW LEFT this.__setSecond((60 + this.innerModel.second - 1) % 60) } else if (e.keyCode === 39) { // ARROW RIGHT this.__setSecond((60 + this.innerModel.second + 1) % 60) } }, __getHeader (h) { const label = [ h('div', { staticClass: 'q-time__link', class: this.view === 'Hour' ? 'q-time__link--active' : 'cursor-pointer', attrs: { tabindex: this.computedTabindex }, on: { click: () => { this.view = 'Hour' }, keyup: this.__onKeyupHour } }, [ this.stringModel.hour ]), h('div', [ ':' ]), h( 'div', this.minLink === true ? { staticClass: 'q-time__link', class: this.view === 'Minute' ? 'q-time__link--active' : 'cursor-pointer', attrs: { tabindex: this.computedTabindex }, on: { click: () => { this.view = 'Minute' }, keyup: this.__onKeyupMinute } } : { staticClass: 'q-time__link' }, [ this.stringModel.minute ] ) ] if (this.withSeconds === true) { label.push( h('div', [ ':' ]), h( 'div', this.secLink === true ? { staticClass: 'q-time__link', class: this.view === 'Second' ? 'q-time__link--active' : 'cursor-pointer', attrs: { tabindex: this.computedTabindex }, on: { click: () => { this.view = 'Second' }, keyup: this.__onKeyupSecond } } : { staticClass: 'q-time__link' }, [ this.stringModel.second ] ) ) } return h('div', { staticClass: 'q-time__header flex flex-center no-wrap', class: this.headerClass }, [ h('div', { staticClass: 'q-time__header-label row items-center no-wrap', attrs: { dir: 'ltr' } }, label), this.computedFormat24h === false ? h('div', { staticClass: 'q-time__header-ampm column items-between no-wrap' }, [ h('div', { staticClass: 'q-time__link', class: this.isAM === true ? 'q-time__link--active' : 'cursor-pointer', attrs: { tabindex: this.computedTabindex }, on: { click: this.__setAm, keyup: e => { e.keyCode === 13 && this.__setAm() } } }, [ 'AM' ]), h('div', { staticClass: 'q-time__link', class: this.isAM !== true ? 'q-time__link--active' : 'cursor-pointer', attrs: { tabindex: this.computedTabindex }, on: { click: this.__setPm, keyup: e => { e.keyCode === 13 && this.__setPm() } } }, [ 'PM' ]) ]) : null ]) }, __getClock (h) { const view = this.view.toLowerCase(), current = this.innerModel[view] return h('div', { staticClass: 'q-time__content col relative-position' }, [ h('transition', { props: { name: 'q-transition--scale' } }, [ h('div', { key: 'clock' + this.view, staticClass: 'q-time__container-parent absolute-full' }, [ h('div', { ref: 'clock', staticClass: 'q-time__container-child fit overflow-hidden' }, [ h('div', { staticClass: 'q-time__clock cursor-pointer non-selectable', on: { click: this.__click }, directives: [{ name: 'touch-pan', value: this.__drag, modifiers: { stop: true, prevent: true, mouse: true } }] }, [ h('div', { staticClass: 'q-time__clock-circle fit' }, [ this.innerModel[view] !== null ? h('div', { staticClass: 'q-time__clock-pointer', style: this.pointerStyle, class: this.color !== void 0 ? `text-${this.color}` : null }) : null, this.positions.map(pos => h('div', { staticClass: `q-time__clock-position row flex-center q-time__clock-pos-${pos.index}`, class: pos.val === current ? this.headerClass.concat(' q-time__clock-position--active') : (pos.disable ? 'q-time__clock-position--disable' : null) }, [ h('span', [ pos.label ]) ])) ]) ]) ]) ]) ]), this.nowBtn === true ? h(QBtn, { staticClass: 'q-time__now-button absolute', props: { icon: this.$q.iconSet.datetime.now, unelevated: true, size: 'sm', round: true, color: this.color, textColor: this.textColor, tabindex: this.computedTabindex }, on: { click: this.setNow } }) : null ]) }, __setHour (hour) { if (this.innerModel.hour !== hour) { this.innerModel.hour = hour this.innerModel.minute = null this.innerModel.second = null } }, __setMinute (minute) { if (this.innerModel.minute !== minute) { this.innerModel.minute = minute this.innerModel.second = null this.withSeconds !== true && this.__updateValue({ minute }) } }, __setSecond (second) { this.innerModel.second !== second && this.__updateValue({ second }) }, __setAm () { if (this.isAM) { return } this.isAM = true if (this.innerModel.hour === null) { return } this.innerModel.hour -= 12 this.__verifyAndUpdate() }, __setPm () { if (!this.isAM) { return } this.isAM = false if (this.innerModel.hour === null) { return } this.innerModel.hour += 12 this.__verifyAndUpdate() }, __verifyAndUpdate () { if (this.hourInSelection !== void 0 && this.hourInSelection(this.innerModel.hour) !== true) { this.innerModel = __splitDate() this.isAM = true this.view = 'Hour' return } if (this.minuteInSelection !== void 0 && this.minuteInSelection(this.innerModel.minute) !== true) { this.innerModel.minute = null this.innerModel.second = null this.view = 'Minute' return } if (this.withSeconds === true && this.secondInSelection !== void 0 && this.secondInSelection(this.innerModel.second) !== true) { this.innerModel.second = null this.view = 'Second' return } if (this.innerModel.hour === null || this.innerModel.minute === null || (this.withSeconds === true && this.innerModel.second === null)) { return } this.__updateValue({}) }, __getComputedMask () { return this.calendar !== 'persian' && this.mask !== null ? this.mask : `HH:mm${this.withSeconds === true ? ':ss' : ''}` }, __updateValue (obj) { const date = { ...this.innerModel, ...obj } const val = this.calendar === 'persian' ? pad(date.hour) + ':' + pad(date.minute) + (this.withSeconds === true ? ':' + pad(date.second) : '') : formatDate( new Date( date.year, date.month === null ? null : date.month - 1, date.day, date.hour, date.minute, date.second, date.millisecond ), this.computedMask, this.computedLocale, date.year ) date.changed = val !== this.value this.$emit('input', val, date) } }, render (h) { return h('div', { staticClass: 'q-time', class: this.classes, on: this.$listeners, attrs: { tabindex: -1 } }, [ this.__getHeader(h), this.__getClock(h) ]) } })