1
// Components
2
import VTimePickerTitle from './VTimePickerTitle'
3
import VTimePickerClock from './VTimePickerClock'
4

5
// Mixins
6
import Picker from '../../mixins/picker'
7
import PickerButton from '../../mixins/picker-button'
8

9
// Utils
10
import { createRange } from '../../util/helpers'
11
import pad from '../VDatePicker/util/pad'
12
import mixins from '../../util/mixins'
13

14
// Types
15
import { VNode, PropType } from 'vue'
16
import { SelectingTimes } from './SelectingTimes'
17

18 1
const rangeHours24 = createRange(24)
19 1
const rangeHours12am = createRange(12)
20 1
const rangeHours12pm = rangeHours12am.map(v => v + 12)
21 1
const range60 = createRange(60)
22 1
const selectingNames = { 1: 'hour', 2: 'minute', 3: 'second' }
23
export { SelectingTimes }
24

25
type Period = 'am' | 'pm'
26
type AllowFunction = (val: number) => boolean
27

28
export default mixins(
29
  Picker,
30
  PickerButton
31
/* @vue/component */
32
).extend({
33
  name: 'v-time-picker',
34

35
  props: {
36
    allowedHours: [Function, Array] as PropType<AllowFunction | number[]>,
37
    allowedMinutes: [Function, Array] as PropType<AllowFunction | number[]>,
38
    allowedSeconds: [Function, Array] as PropType<AllowFunction | number[]>,
39
    disabled: Boolean,
40
    format: {
41
      type: String as PropType<'ampm' | '24hr'>,
42
      default: 'ampm',
43 1
      validator (val) {
44 1
        return ['ampm', '24hr'].includes(val)
45
      },
46
    },
47
    min: String,
48
    max: String,
49
    readonly: Boolean,
50
    scrollable: Boolean,
51
    useSeconds: Boolean,
52
    value: null as any as PropType<any>,
53
    ampmInTitle: Boolean,
54
  },
55

56 1
  data () {
57 1
    return {
58
      inputHour: null as number | null,
59
      inputMinute: null as number | null,
60
      inputSecond: null as number | null,
61
      lazyInputHour: null as number | null,
62
      lazyInputMinute: null as number | null,
63
      lazyInputSecond: null as number | null,
64
      period: 'am' as Period,
65
      selecting: SelectingTimes.Hour,
66
    }
67
  },
68

69
  computed: {
70
    selectingHour: {
71 1
      get (): boolean {
72 1
        return this.selecting === SelectingTimes.Hour
73
      },
74 1
      set (v: boolean) {
75 1
        this.selecting = SelectingTimes.Hour
76
      },
77
    },
78
    selectingMinute: {
79 1
      get (): boolean {
80 1
        return this.selecting === SelectingTimes.Minute
81
      },
82 1
      set (v: boolean) {
83 1
        this.selecting = SelectingTimes.Minute
84
      },
85
    },
86
    selectingSecond: {
87 1
      get (): boolean {
88 1
        return this.selecting === SelectingTimes.Second
89
      },
90 1
      set (v: boolean) {
91 1
        this.selecting = SelectingTimes.Second
92
      },
93
    },
94 1
    isAllowedHourCb (): AllowFunction {
95
      let cb: AllowFunction
96

97 1
      if (this.allowedHours instanceof Array) {
98 1
        cb = (val: number) => (this.allowedHours as number[]).includes(val)
99
      } else {
100 1
        cb = this.allowedHours
101
      }
102

103 1
      if (!this.min && !this.max) return cb
104

105 1
      const minHour = this.min ? Number(this.min.split(':')[0]) : 0
106 1
      const maxHour = this.max ? Number(this.max.split(':')[0]) : 23
107

108 1
      return (val: number) => {
109 1
        return val >= minHour * 1 &&
110 1
          val <= maxHour * 1 &&
111 1
          (!cb || cb(val))
112
      }
113
    },
114 1
    isAllowedMinuteCb (): AllowFunction {
115
      let cb: AllowFunction
116

117 1
      const isHourAllowed = !this.isAllowedHourCb || this.inputHour === null || this.isAllowedHourCb(this.inputHour)
118 1
      if (this.allowedMinutes instanceof Array) {
119 1
        cb = (val: number) => (this.allowedMinutes as number[]).includes(val)
120
      } else {
121 1
        cb = this.allowedMinutes
122
      }
123

124 1
      if (!this.min && !this.max) {
125 1
        return isHourAllowed ? cb : () => false
126
      }
127

128 1
      const [minHour, minMinute] = this.min ? this.min.split(':').map(Number) : [0, 0]
129 1
      const [maxHour, maxMinute] = this.max ? this.max.split(':').map(Number) : [23, 59]
130 1
      const minTime = minHour * 60 + minMinute * 1
131 1
      const maxTime = maxHour * 60 + maxMinute * 1
132

133 1
      return (val: number) => {
134 1
        const time = 60 * this.inputHour! + val
135 1
        return time >= minTime &&
136 1
          time <= maxTime &&
137 1
          isHourAllowed &&
138 1
          (!cb || cb(val))
139
      }
140
    },
141 1
    isAllowedSecondCb (): AllowFunction {
142
      let cb: AllowFunction
143

144 1
      const isHourAllowed = !this.isAllowedHourCb || this.inputHour === null || this.isAllowedHourCb(this.inputHour)
145 1
      const isMinuteAllowed = isHourAllowed &&
146 1
        (!this.isAllowedMinuteCb ||
147 1
          this.inputMinute === null ||
148 1
          this.isAllowedMinuteCb(this.inputMinute)
149
        )
150

151 1
      if (this.allowedSeconds instanceof Array) {
152 1
        cb = (val: number) => (this.allowedSeconds as number[]).includes(val)
153
      } else {
154 1
        cb = this.allowedSeconds
155
      }
156

157 1
      if (!this.min && !this.max) {
158 1
        return isMinuteAllowed ? cb : () => false
159
      }
160

161 1
      const [minHour, minMinute, minSecond] = this.min ? this.min.split(':').map(Number) : [0, 0, 0]
162 1
      const [maxHour, maxMinute, maxSecond] = this.max ? this.max.split(':').map(Number) : [23, 59, 59]
163 1
      const minTime = minHour * 3600 + minMinute * 60 + (minSecond || 0) * 1
164 1
      const maxTime = maxHour * 3600 + maxMinute * 60 + (maxSecond || 0) * 1
165

166 1
      return (val: number) => {
167 1
        const time = 3600 * this.inputHour! + 60 * this.inputMinute! + val
168 1
        return time >= minTime &&
169 1
          time <= maxTime &&
170 1
          isMinuteAllowed &&
171 1
          (!cb || cb(val))
172
      }
173
    },
174 1
    isAmPm (): boolean {
175 1
      return this.format === 'ampm'
176
    },
177
  },
178

179
  watch: {
180
    value: 'setInputData',
181
  },
182

183 1
  mounted () {
184 1
    this.setInputData(this.value)
185 1
    this.$on('update:period', this.setPeriod)
186
  },
187

188
  methods: {
189 1
    genValue () {
190 1
      if (this.inputHour != null && this.inputMinute != null && (!this.useSeconds || this.inputSecond != null)) {
191 1
        return `${pad(this.inputHour)}:${pad(this.inputMinute)}` + (this.useSeconds ? `:${pad(this.inputSecond!)}` : '')
192
      }
193

194 1
      return null
195
    },
196 1
    emitValue () {
197 1
      const value = this.genValue()
198 1
      if (value !== null) this.$emit('input', value)
199
    },
200 1
    setPeriod (period: Period) {
201 1
      this.period = period
202 1
      if (this.inputHour != null) {
203 1
        const newHour = this.inputHour! + (period === 'am' ? -12 : 12)
204 1
        this.inputHour = this.firstAllowed('hour', newHour)
205 1
        this.emitValue()
206
      }
207
    },
208 1
    setInputData (value: string | null | Date) {
209 1
      if (value == null || value === '') {
210 1
        this.inputHour = null
211 1
        this.inputMinute = null
212 1
        this.inputSecond = null
213 1
      } else if (value instanceof Date) {
214 1
        this.inputHour = value.getHours()
215 1
        this.inputMinute = value.getMinutes()
216 1
        this.inputSecond = value.getSeconds()
217
      } else {
218 1
        const [, hour, minute, , second, period] = value.trim().toLowerCase().match(/^(\d+):(\d+)(:(\d+))?([ap]m)?$/) || new Array(6)
219

220 1
        this.inputHour = period ? this.convert12to24(parseInt(hour, 10), period as Period) : parseInt(hour, 10)
221 1
        this.inputMinute = parseInt(minute, 10)
222 1
        this.inputSecond = parseInt(second || 0, 10)
223
      }
224

225 1
      this.period = (this.inputHour == null || this.inputHour < 12) ? 'am' : 'pm'
226
    },
227 1
    convert24to12 (hour: number) {
228 1
      return hour ? ((hour - 1) % 12 + 1) : 12
229
    },
230 1
    convert12to24 (hour: number, period: Period) {
231 1
      return hour % 12 + (period === 'pm' ? 12 : 0)
232
    },
233 1
    onInput (value: number) {
234 1
      if (this.selecting === SelectingTimes.Hour) {
235 1
        this.inputHour = this.isAmPm ? this.convert12to24(value, this.period) : value
236 1
      } else if (this.selecting === SelectingTimes.Minute) {
237 1
        this.inputMinute = value
238
      } else {
239 1
        this.inputSecond = value
240
      }
241 1
      this.emitValue()
242
    },
243 1
    onChange (value: number) {
244 1
      this.$emit(`click:${selectingNames[this.selecting]}`, value)
245

246 1
      const emitChange = this.selecting === (this.useSeconds ? SelectingTimes.Second : SelectingTimes.Minute)
247

248 1
      if (this.selecting === SelectingTimes.Hour) {
249 1
        this.selecting = SelectingTimes.Minute
250 1
      } else if (this.useSeconds && this.selecting === SelectingTimes.Minute) {
251 1
        this.selecting = SelectingTimes.Second
252
      }
253

254 1
      if (this.inputHour === this.lazyInputHour &&
255 1
        this.inputMinute === this.lazyInputMinute &&
256 1
        (!this.useSeconds || this.inputSecond === this.lazyInputSecond)
257 1
      ) return
258

259 1
      const time = this.genValue()
260 1
      if (time === null) return
261

262 1
      this.lazyInputHour = this.inputHour
263 1
      this.lazyInputMinute = this.inputMinute
264 1
      this.useSeconds && (this.lazyInputSecond = this.inputSecond)
265

266 1
      emitChange && this.$emit('change', time)
267
    },
268 1
    firstAllowed (type: 'hour' | 'minute' | 'second', value: number) {
269 1
      const allowedFn = type === 'hour' ? this.isAllowedHourCb : (type === 'minute' ? this.isAllowedMinuteCb : this.isAllowedSecondCb)
270 1
      if (!allowedFn) return value
271

272
      // TODO: clean up
273 1
      const range = type === 'minute'
274 0
        ? range60
275 1
        : (type === 'second'
276 0
          ? range60
277 1
          : (this.isAmPm
278 1
            ? (value < 12
279 0
              ? rangeHours12am
280 0
              : rangeHours12pm)
281 0
            : rangeHours24))
282 0
      const first = range.find(v => allowedFn((v + value) % range.length + range[0]))
283 1
      return ((first || 0) + value) % range.length + range[0]
284
    },
285 1
    genClock () {
286 1
      return this.$createElement(VTimePickerClock, {
287
        props: {
288
          allowedValues:
289 1
            this.selecting === SelectingTimes.Hour
290 1
              ? this.isAllowedHourCb
291 1
              : (this.selecting === SelectingTimes.Minute
292 1
                ? this.isAllowedMinuteCb
293 1
                : this.isAllowedSecondCb),
294
          color: this.color,
295
          dark: this.dark,
296
          disabled: this.disabled,
297 1
          double: this.selecting === SelectingTimes.Hour && !this.isAmPm,
298 1
          format: this.selecting === SelectingTimes.Hour
299 1
            ? (this.isAmPm ? this.convert24to12 : (val: number) => val)
300 1
            : (val: number) => pad(val, 2),
301
          light: this.light,
302 1
          max: this.selecting === SelectingTimes.Hour ? (this.isAmPm && this.period === 'am' ? 11 : 23) : 59,
303 1
          min: this.selecting === SelectingTimes.Hour && this.isAmPm && this.period === 'pm' ? 12 : 0,
304
          readonly: this.readonly,
305
          scrollable: this.scrollable,
306 1
          size: Number(this.width) - ((!this.fullWidth && this.landscape) ? 80 : 20),
307 1
          step: this.selecting === SelectingTimes.Hour ? 1 : 5,
308 1
          value: this.selecting === SelectingTimes.Hour
309 1
            ? this.inputHour
310 1
            : (this.selecting === SelectingTimes.Minute
311 1
              ? this.inputMinute
312 1
              : this.inputSecond),
313
        },
314
        on: {
315
          input: this.onInput,
316
          change: this.onChange,
317
        },
318
        ref: 'clock',
319
      })
320
    },
321 1
    genClockAmPm () {
322 1
      return this.$createElement('div', this.setTextColor(this.color || 'primary', {
323
        staticClass: 'v-time-picker-clock__ampm',
324
      }), [
325 1
        this.genPickerButton('period', 'am', this.$vuetify.lang.t('$vuetify.timePicker.am'), this.disabled || this.readonly),
326 1
        this.genPickerButton('period', 'pm', this.$vuetify.lang.t('$vuetify.timePicker.pm'), this.disabled || this.readonly),
327
      ])
328
    },
329 1
    genPickerBody () {
330 1
      return this.$createElement('div', {
331
        staticClass: 'v-time-picker-clock__container',
332
        key: this.selecting,
333
      }, [
334 1
        !this.ampmInTitle && this.isAmPm && this.genClockAmPm(),
335
        this.genClock(),
336
      ])
337
    },
338 1
    genPickerTitle () {
339 1
      return this.$createElement(VTimePickerTitle, {
340
        props: {
341
          ampm: this.isAmPm,
342 1
          ampmReadonly: this.isAmPm && !this.ampmInTitle,
343
          disabled: this.disabled,
344
          hour: this.inputHour,
345
          minute: this.inputMinute,
346
          second: this.inputSecond,
347
          period: this.period,
348
          readonly: this.readonly,
349
          useSeconds: this.useSeconds,
350
          selecting: this.selecting,
351
        },
352
        on: {
353 1
          'update:selecting': (value: 1 | 2 | 3) => (this.selecting = value),
354 1
          'update:period': (period: string) => this.$emit('update:period', period),
355
        },
356
        ref: 'title',
357
        slot: 'title',
358
      })
359
    },
360
  },
361

362 1
  render (): VNode {
363 1
    return this.genPicker('v-picker--time')
364
  },
365
})

Read our documentation on viewing source code .

Loading