diff --git a/ui/dev/src/pages/form/time.vue b/ui/dev/src/pages/form/time.vue
index 568caca6ecb4..e24dfa021cd0 100644
--- a/ui/dev/src/pages/form/time.vue
+++ b/ui/dev/src/pages/form/time.vue
@@ -163,6 +163,67 @@
+
+
+ Close on change
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -180,6 +241,8 @@ export default {
nowBtn: false,
time: '10:56',
+ timeWithSeconds: '10:50:30',
+ timePartial: '12:10:',
nullTime: null,
input: null,
input2: '12:35 PM',
@@ -220,6 +283,21 @@ export default {
if (min !== null && (min <= 25 || min >= 58)) { return false }
if (sec !== null && sec % 10 !== 0) { return false }
return true
+ },
+
+ onChangeTime (...ev) {
+ console.log('time', ...ev)
+ this.$refs.timeProxy.hide()
+ },
+
+ onChangeTimeWithSeconds (...ev) {
+ console.log('timeWithSeconds', ...ev)
+ this.$refs.timeWithSecondsProxy.hide()
+ },
+
+ onChangeTimePartial (...ev) {
+ console.log('timePartial', ...ev)
+ this.$refs.timePartialProxy.hide()
}
}
}
diff --git a/ui/src/components/time/QTime.js b/ui/src/components/time/QTime.js
index 95f11bd9ebc9..a2efb81180f2 100644
--- a/ui/src/components/time/QTime.js
+++ b/ui/src/components/time/QTime.js
@@ -5,11 +5,15 @@ import TouchPan from '../../directives/TouchPan.js'
import { slot } from '../../utils/slot.js'
import { formatDate, __splitDate } from '../../utils/date.js'
-import { position } from '../../utils/event.js'
+import { position, prevent } from '../../utils/event.js'
import { pad } from '../../utils/format.js'
import cache from '../../utils/cache.js'
import DateTimeMixin from '../../mixins/datetime.js'
+function preventKbdArrows (ev) {
+ [ 37, 38, 39, 40 ].includes(ev.keyCode) === true && prevent(ev)
+}
+
export default Vue.extend({
name: 'QTime',
@@ -87,10 +91,14 @@ export default Vue.extend({
this.innerModel = model
if (model.hour === null) {
- this.view = 'Hour'
+ this.__goToView('Hour')
}
else {
this.isAM = model.hour < 12
+
+ if (model.minute === null && this.view === 'Second') {
+ this.__goToView('Minute')
+ }
}
}
},
@@ -291,7 +299,7 @@ export default Vue.extend({
...this.__getCurrentDate(),
...this.__getCurrentTime()
})
- this.view = 'Hour'
+ this.__goToView('Hour')
},
__getValidValues (start, count, testFn) {
@@ -403,12 +411,24 @@ export default Vue.extend({
}
},
+ __goToView (view) {
+ if (this.view !== view) {
+ this.view = view
+ }
+ if (this.$refs[view] !== void 0 && this.$refs[view] !== document.activeElement) {
+ this.$refs[view].focus()
+ }
+ },
+
__goToNextView () {
if (this.view === 'Hour') {
- this.view = 'Minute'
+ this.__goToView('Minute')
}
else if (this.withSeconds && this.view === 'Minute') {
- this.view = 'Second'
+ this.__goToView('Second')
+ }
+ else {
+ this.__updateValue(void 0, true)
}
},
@@ -514,10 +534,10 @@ export default Vue.extend({
__onKeyupHour (e) {
if (e.keyCode === 13) { // ENTER
- this.view = 'Hour'
+ this.view === 'Hour' && this.__goToNextView()
}
- else if ([ 37, 39 ].includes(e.keyCode)) {
- const payload = e.keyCode === 37 ? -1 : 1
+ else if ([ 37, 38, 39, 40 ].includes(e.keyCode)) {
+ const payload = e.keyCode === 37 || e.keyCode === 40 ? -1 : 1
if (this.validHours !== void 0) {
const values = this.computedFormat24h === true
@@ -552,10 +572,10 @@ export default Vue.extend({
__onKeyupMinute (e) {
if (e.keyCode === 13) { // ENTER
- this.view = 'Minute'
+ this.view === 'Minute' && this.__goToNextView()
}
- else if ([ 37, 39 ].includes(e.keyCode)) {
- const payload = e.keyCode === 37 ? -1 : 1
+ else if ([ 37, 38, 39, 40 ].includes(e.keyCode)) {
+ const payload = e.keyCode === 37 || e.keyCode === 40 ? -1 : 1
if (this.validMinutes !== void 0) {
const values = this.validMinutes.values
@@ -584,10 +604,10 @@ export default Vue.extend({
__onKeyupSecond (e) {
if (e.keyCode === 13) { // ENTER
- this.view = 'Second'
+ this.view === 'Second' && this.__goToNextView()
}
- else if ([ 37, 39 ].includes(e.keyCode)) {
- const payload = e.keyCode === 37 ? -1 : 1
+ else if ([ 37, 38, 39, 40 ].includes(e.keyCode)) {
+ const payload = e.keyCode === 37 || e.keyCode === 40 ? -1 : 1
if (this.validSeconds !== void 0) {
const values = this.validSeconds.values
@@ -619,9 +639,10 @@ export default Vue.extend({
h('div', {
staticClass: 'q-time__link',
class: this.view === 'Hour' ? 'q-time__link--active' : 'cursor-pointer',
- attrs: { tabindex: this.computedTabindex },
+ attrs: { tabindex: this.computedTabindex, 'data-autofocus': this.view === 'Hour' },
on: cache(this, 'vH', {
- click: () => { this.view = 'Hour' },
+ focus: () => { this.view = 'Hour' },
+ keydown: preventKbdArrows,
keyup: this.__onKeyupHour
})
}, [ this.stringModel.hour ]),
@@ -632,11 +653,13 @@ export default Vue.extend({
'div',
this.minLink === true
? {
+ ref: 'Minute',
staticClass: 'q-time__link',
class: this.view === 'Minute' ? 'q-time__link--active' : 'cursor-pointer',
- attrs: { tabindex: this.computedTabindex },
+ attrs: { tabindex: this.computedTabindex, 'data-autofocus': this.view === 'Minute' },
on: cache(this, 'vM', {
- click: () => { this.view = 'Minute' },
+ focus: () => { this.view = 'Minute' },
+ keydown: preventKbdArrows,
keyup: this.__onKeyupMinute
})
}
@@ -653,11 +676,13 @@ export default Vue.extend({
'div',
this.secLink === true
? {
+ ref: 'Second',
staticClass: 'q-time__link',
class: this.view === 'Second' ? 'q-time__link--active' : 'cursor-pointer',
- attrs: { tabindex: this.computedTabindex },
+ attrs: { tabindex: this.computedTabindex, 'data-autofocus': this.view === 'Second' },
on: cache(this, 'vS', {
- click: () => { this.view = 'Second' },
+ focus: () => { this.view = 'Second' },
+ keydown: preventKbdArrows,
keyup: this.__onKeyupSecond
})
}
@@ -819,20 +844,20 @@ export default Vue.extend({
__verifyAndUpdate () {
if (this.hourInSelection !== void 0 && this.hourInSelection(this.innerModel.hour) !== true) {
this.innerModel = __splitDate()
- this.view = 'Hour'
+ this.__goToView('Hour')
return
}
if (this.minuteInSelection !== void 0 && this.minuteInSelection(this.innerModel.minute) !== true) {
this.innerModel.minute = null
this.innerModel.second = null
- this.view = 'Minute'
+ this.__goToView('Minute')
return
}
if (this.withSeconds === true && this.secondInSelection !== void 0 && this.secondInSelection(this.innerModel.second) !== true) {
this.innerModel.second = null
- this.view = 'Second'
+ this.__goToView('Second')
return
}
@@ -843,7 +868,7 @@ export default Vue.extend({
this.__updateValue()
},
- __updateValue (obj) {
+ __updateValue (obj, final) {
const date = Object.assign({ ...this.innerModel }, obj)
const val = this.calendar === 'persian'
@@ -867,7 +892,11 @@ export default Vue.extend({
)
date.changed = val !== this.value
- this.$emit('input', val, date)
+
+ if (final !== true || date.changed === true) {
+ this.$emit('input', val, date)
+ }
+ final === true && this.$emit('change', val, date)
}
},
diff --git a/ui/src/components/time/QTime.json b/ui/src/components/time/QTime.json
index 107cb641a0b0..d70ec97ae901 100644
--- a/ui/src/components/time/QTime.json
+++ b/ui/src/components/time/QTime.json
@@ -171,6 +171,77 @@
}
}
}
+ },
+
+ "change": {
+ "extends": "input",
+ "desc": "Fires at the end of time adjustment (click or drag end) on the minutes screen (or seconds one if withSeconds is set)",
+ "params": {
+ "value": {
+ "type": "String"
+ },
+ "details": {
+ "type": "Object",
+ "desc": "Object of properties on the new model",
+ "definition": {
+ "year": {
+ "type": "Number",
+ "desc": "The year",
+ "__exemption": [
+ "examples"
+ ]
+ },
+ "month": {
+ "type": "Number",
+ "desc": "The month",
+ "__exemption": [
+ "examples"
+ ]
+ },
+ "day": {
+ "type": "Number",
+ "desc": "The day of the month",
+ "__exemption": [
+ "examples"
+ ]
+ },
+ "hour": {
+ "type": "Number",
+ "desc": "The hour",
+ "__exemption": [
+ "examples"
+ ]
+ },
+ "minute": {
+ "type": "Number",
+ "desc": "The minute",
+ "__exemption": [
+ "examples"
+ ]
+ },
+ "second": {
+ "type": "Number",
+ "desc": "The second",
+ "__exemption": [
+ "examples"
+ ]
+ },
+ "millisecond": {
+ "type": "Number",
+ "desc": "The millisecond",
+ "__exemption": [
+ "examples"
+ ]
+ },
+ "changed": {
+ "type": "Boolean",
+ "desc": "Did the model change?",
+ "addedIn": "v1.1.1"
+ }
+ }
+ }
+ },
+ "addedIn": "v1.15.5"
}
},
diff --git a/ui/src/utils/date.js b/ui/src/utils/date.js
index bcd4852712ca..48ee2c815010 100644
--- a/ui/src/utils/date.js
+++ b/ui/src/utils/date.js
@@ -16,7 +16,7 @@ const
function getRegexData (mask, dateLocale) {
const
- days = '(' + dateLocale.days.join('|') + ')',
+ days = '(' + dateLocale.days.join('|') + '|)',
key = mask + days
if (regexStore[key] !== void 0) {
@@ -24,9 +24,9 @@ function getRegexData (mask, dateLocale) {
}
const
- daysShort = '(' + dateLocale.daysShort.join('|') + ')',
- months = '(' + dateLocale.months.join('|') + ')',
- monthsShort = '(' + dateLocale.monthsShort.join('|') + ')'
+ daysShort = '(' + dateLocale.daysShort.join('|') + '|)',
+ months = '(' + dateLocale.months.join('|') + '|)',
+ monthsShort = '(' + dateLocale.monthsShort.join('|') + '|)'
const map = {}
let index = 0
@@ -36,16 +36,16 @@ function getRegexData (mask, dateLocale) {
switch (match) {
case 'YY':
map.YY = index
- return '(-?\\d{1,2})'
+ return '(-?\\d{1,2}|)'
case 'YYYY':
map.YYYY = index
- return '(-?\\d{1,4})'
+ return '(-?\\d{1,4}|)'
case 'M':
map.M = index
- return '(\\d{1,2})'
+ return '(\\d{1,2}|)'
case 'MM':
map.M = index // bumping to M
- return '(\\d{2})'
+ return '(\\d{2}|)'
case 'MMM':
map.MMM = index
return monthsShort
@@ -54,55 +54,55 @@ function getRegexData (mask, dateLocale) {
return months
case 'D':
map.D = index
- return '(\\d{1,2})'
+ return '(\\d{1,2}|)'
case 'Do':
- map.D = index++ // bumping to D
- return '(\\d{1,2}(st|nd|rd|th))'
+ map.D = index // bumping to D
+ return '(\\d{1,2}(?:st|nd|rd|th)|)'
case 'DD':
map.D = index // bumping to D
- return '(\\d{2})'
+ return '(\\d{2}|)'
case 'H':
map.H = index
- return '(\\d{1,2})'
+ return '(\\d{1,2}|)'
case 'HH':
map.H = index // bumping to H
- return '(\\d{2})'
+ return '(\\d{2}|)'
case 'h':
map.h = index
- return '(\\d{1,2})'
+ return '(\\d{1,2}|)'
case 'hh':
map.h = index // bumping to h
- return '(\\d{2})'
+ return '(\\d{2}|)'
case 'm':
map.m = index
- return '(\\d{1,2})'
+ return '(\\d{1,2}|)'
case 'mm':
map.m = index // bumping to m
- return '(\\d{2})'
+ return '(\\d{2}|)'
case 's':
map.s = index
- return '(\\d{1,2})'
+ return '(\\d{1,2}|)'
case 'ss':
map.s = index // bumping to s
- return '(\\d{2})'
+ return '(\\d{2}|)'
case 'S':
map.S = index
- return '(\\d{1})'
+ return '(\\d{1}|)'
case 'SS':
map.S = index // bump to S
- return '(\\d{2})'
+ return '(\\d{2}|)'
case 'SSS':
map.S = index // bump to S
- return '(\\d{3})'
+ return '(\\d{3}|)'
case 'A':
map.A = index
- return '(AM|PM)'
+ return '(AM|PM|)'
case 'a':
map.a = index
- return '(am|pm)'
+ return '(am|pm|)'
case 'aa':
map.aa = index
- return '(a\\.m\\.|p\\.m\\.)'
+ return '(a\\.m\\.|p\\.m\\.|)'
case 'ddd':
return daysShort
@@ -111,30 +111,30 @@ function getRegexData (mask, dateLocale) {
case 'Q':
case 'd':
case 'E':
- return '(\\d{1})'
+ return '(\\d{1}|)'
case 'Qo':
- return '(1st|2nd|3rd|4th)'
+ return '(1st|2nd|3rd|4th|)'
case 'DDD':
case 'DDDD':
- return '(\\d{1,3})'
+ return '(\\d{1,3}|)'
case 'w':
- return '(\\d{1,2})'
+ return '(\\d{1,2}|)'
case 'ww':
- return '(\\d{2})'
+ return '(\\d{2}|)'
case 'Z': // to split: (?:(Z)()()|([+-])?(\\d{2}):?(\\d{2}))
map.Z = index
- return '(Z|[+-]\\d{2}:\\d{2})'
+ return '(Z|[+-]\\d{2}:\\d{2}|)'
case 'ZZ':
map.ZZ = index
- return '(Z|[+-]\\d{2}\\d{2})'
+ return '(Z|[+-]\\d{2}\\d{2}|)'
case 'X':
map.X = index
- return '(-?\\d+)'
+ return '(-?\\d+|)'
case 'x':
map.x = index
- return '(-?\\d{4,})'
+ return '(-?\\d{4,}|)'
default:
index--
@@ -213,16 +213,23 @@ export function __splitDate (str, mask, dateLocale, calendar, defaultModel) {
return date
}
+ const idx = { ...map }
+ Object.keys(idx).forEach(key => {
+ if (match[idx[key]] === '') {
+ idx[key] = void 0
+ }
+ })
+
let tzString = ''
- if (map.X !== void 0 || map.x !== void 0) {
- const stamp = parseInt(match[map.X !== void 0 ? map.X : map.x], 10)
+ if (idx.X !== void 0 || idx.x !== void 0) {
+ const stamp = parseInt(match[idx.X !== void 0 ? idx.X : idx.x], 10)
if (isNaN(stamp) === true || stamp < 0) {
return date
}
- const d = new Date(stamp * (map.X !== void 0 ? 1000 : 1))
+ const d = new Date(stamp * (idx.X !== void 0 ? 1000 : 1))
date.year = d.getFullYear()
date.month = d.getMonth() + 1
@@ -233,29 +240,29 @@ export function __splitDate (str, mask, dateLocale, calendar, defaultModel) {
date.millisecond = d.getMilliseconds()
}
else {
- if (map.YYYY !== void 0) {
- date.year = parseInt(match[map.YYYY], 10)
+ if (idx.YYYY !== void 0) {
+ date.year = parseInt(match[idx.YYYY], 10)
}
- else if (map.YY !== void 0) {
- const y = parseInt(match[map.YY], 10)
+ else if (idx.YY !== void 0) {
+ const y = parseInt(match[idx.YY], 10)
date.year = y < 0 ? y : 2000 + y
}
- if (map.M !== void 0) {
- date.month = parseInt(match[map.M], 10)
+ if (idx.M !== void 0) {
+ date.month = parseInt(match[idx.M], 10)
if (date.month < 1 || date.month > 12) {
return date
}
}
- else if (map.MMM !== void 0) {
- date.month = monthsShort.indexOf(match[map.MMM]) + 1
+ else if (idx.MMM !== void 0) {
+ date.month = monthsShort.indexOf(match[idx.MMM]) + 1
}
- else if (map.MMMM !== void 0) {
- date.month = months.indexOf(match[map.MMMM]) + 1
+ else if (idx.MMMM !== void 0) {
+ date.month = months.indexOf(match[idx.MMMM]) + 1
}
- if (map.D !== void 0) {
- date.day = parseInt(match[map.D], 10)
+ if (idx.D !== void 0) {
+ date.day = parseInt(match[idx.D], 10)
if (date.year === null || date.month === null || date.day < 1) {
return date
@@ -270,35 +277,35 @@ export function __splitDate (str, mask, dateLocale, calendar, defaultModel) {
}
}
- if (map.H !== void 0) {
- date.hour = parseInt(match[map.H], 10) % 24
+ if (idx.H !== void 0) {
+ date.hour = parseInt(match[idx.H], 10) % 24
}
- else if (map.h !== void 0) {
- date.hour = parseInt(match[map.h], 10) % 12
+ else if (idx.h !== void 0) {
+ date.hour = parseInt(match[idx.h], 10) % 12
if (
- (map.A && match[map.A] === 'PM') ||
- (map.a && match[map.a] === 'pm') ||
- (map.aa && match[map.aa] === 'p.m.')
+ (idx.A && match[idx.A] === 'PM') ||
+ (idx.a && match[idx.a] === 'pm') ||
+ (idx.aa && match[idx.aa] === 'p.m.')
) {
date.hour += 12
}
date.hour = date.hour % 24
}
- if (map.m !== void 0) {
- date.minute = parseInt(match[map.m], 10) % 60
+ if (idx.m !== void 0) {
+ date.minute = parseInt(match[idx.m], 10) % 60
}
- if (map.s !== void 0) {
- date.second = parseInt(match[map.s], 10) % 60
+ if (idx.s !== void 0) {
+ date.second = parseInt(match[idx.s], 10) % 60
}
- if (map.S !== void 0) {
- date.millisecond = parseInt(match[map.S], 10) * 10 ** (3 - match[map.S].length)
+ if (idx.S !== void 0) {
+ date.millisecond = parseInt(match[idx.S], 10) * 10 ** (3 - match[idx.S].length)
}
- if (map.Z !== void 0 || map.ZZ !== void 0) {
- tzString = (map.Z !== void 0 ? match[map.Z].replace(':', '') : match[map.ZZ])
+ if (idx.Z !== void 0 || idx.ZZ !== void 0) {
+ tzString = (idx.Z !== void 0 ? match[idx.Z].replace(':', '') : match[idx.ZZ])
date.timezoneOffset = (tzString[0] === '+' ? -1 : 1) * (60 * tzString.slice(1, 3) + 1 * tzString.slice(3, 5))
}
}