diff --git a/packages/api-generator/src/locale/en/VTimePicker.json b/packages/api-generator/src/locale/en/VTimePicker.json index 829f4ed6134..ce361a10f2e 100644 --- a/packages/api-generator/src/locale/en/VTimePicker.json +++ b/packages/api-generator/src/locale/en/VTimePicker.json @@ -8,9 +8,10 @@ "format": "Defines the format of a time displayed in picker. Available options are `ampm` and `24hr`.", "max": "Maximum allowed time.", "min": "Minimum allowed time.", + "hide-header": "Hide the header of the picker.", "readonly": "Puts picker in readonly state.", "scrollable": "Allows changing hour/minute with mouse scroll.", - "useSeconds": "Toggles the use of seconds in picker.", + "use-seconds": "Toggles the use of seconds in picker.", "value": "Time picker model (ISO 8601 format, 24hr hh:mm).", "width": "Width of the picker." }, @@ -19,9 +20,9 @@ }, "events": { "change": "Emitted when the time selection is done (when user changes the minute for HH:MM picker and the second for HH:MM:SS picker.", - "click:hour": "Emitted when user selects the hour.", - "click:minute": "Emitted when user selects the minute.", - "click:second": "Emitted when user selects the second.", + "update:hour": "Emitted when user selects the hour.", + "update:minute": "Emitted when user selects the minute.", + "update:second": "Emitted when user selects the second.", "update:period": "Emitted when user clicks the AM/PM button." } } diff --git a/packages/docs/src/data/301.json b/packages/docs/src/data/301.json index 1da6f6f4fda..0e5eb9338c4 100644 --- a/packages/docs/src/data/301.json +++ b/packages/docs/src/data/301.json @@ -118,12 +118,8 @@ "components/simple-tables": "/components/tables", "components/data-tables": "/components/data-tables/basics", "components/date-pickers-month": "/introduction/roadmap", - "components/time-pickers": "/introduction/roadmap", "components/overflow-btns": "/introduction/roadmap", - "components/sparklines": "/introduction/roadmap", "components/server-side-data-tables": "/components/data-tables/server-side-tables", - "components/skeleton-loaders": "/introduction/roadmap", - "components/speed-dials": "/introduction/roadmap", "components/virtual-data-tables": "/components/data-tables/virtual-tables", "api/v-click-outside": "/api/v-click-outside-directive", "api/v-intersect": "/api/v-intersect-directive", diff --git a/packages/docs/src/data/nav.json b/packages/docs/src/data/nav.json index 08d65dd87aa..a9b0456f700 100644 --- a/packages/docs/src/data/nav.json +++ b/packages/docs/src/data/nav.json @@ -239,6 +239,10 @@ "title": "speed-dials", "subfolder": "components" }, + { + "title": "time-pickers", + "subfolder": "components" + }, { "title": "treeview", "subfolder": "components" diff --git a/packages/docs/src/examples/v-time-picker/misc-dialog-and-menu.vue b/packages/docs/src/examples/v-time-picker/misc-dialog-and-menu.vue index ef73d36c4ad..67112474a44 100644 --- a/packages/docs/src/examples/v-time-picker/misc-dialog-and-menu.vue +++ b/packages/docs/src/examples/v-time-picker/misc-dialog-and-menu.vue @@ -1,92 +1,64 @@ - - + + + + diff --git a/packages/docs/src/examples/v-time-picker/prop-elevation.vue b/packages/docs/src/examples/v-time-picker/prop-elevation.vue index 87d5ea61a7d..aa3b562c56c 100644 --- a/packages/docs/src/examples/v-time-picker/prop-elevation.vue +++ b/packages/docs/src/examples/v-time-picker/prop-elevation.vue @@ -1,32 +1,9 @@ - - - - diff --git a/packages/docs/src/examples/v-time-picker/prop-format.vue b/packages/docs/src/examples/v-time-picker/prop-format.vue index cea2523d835..02d798b2710 100644 --- a/packages/docs/src/examples/v-time-picker/prop-format.vue +++ b/packages/docs/src/examples/v-time-picker/prop-format.vue @@ -1,26 +1,7 @@ - - - - diff --git a/packages/docs/src/examples/v-time-picker/prop-hide-header.vue b/packages/docs/src/examples/v-time-picker/prop-hide-header.vue new file mode 100644 index 00000000000..976558e564d --- /dev/null +++ b/packages/docs/src/examples/v-time-picker/prop-hide-header.vue @@ -0,0 +1,7 @@ + diff --git a/packages/docs/src/examples/v-time-picker/prop-no-title.vue b/packages/docs/src/examples/v-time-picker/prop-no-title.vue deleted file mode 100644 index 77e0940fb6a..00000000000 --- a/packages/docs/src/examples/v-time-picker/prop-no-title.vue +++ /dev/null @@ -1,31 +0,0 @@ - - - - - diff --git a/packages/docs/src/examples/v-time-picker/prop-readonly.vue b/packages/docs/src/examples/v-time-picker/prop-readonly.vue index 8d34675c0fe..d040d336989 100644 --- a/packages/docs/src/examples/v-time-picker/prop-readonly.vue +++ b/packages/docs/src/examples/v-time-picker/prop-readonly.vue @@ -1,32 +1,7 @@ - - - - diff --git a/packages/docs/src/examples/v-time-picker/prop-use-seconds.vue b/packages/docs/src/examples/v-time-picker/prop-use-seconds.vue index e76bfee6b54..a74d7857a75 100644 --- a/packages/docs/src/examples/v-time-picker/prop-use-seconds.vue +++ b/packages/docs/src/examples/v-time-picker/prop-use-seconds.vue @@ -1,32 +1,7 @@ - - - - diff --git a/packages/docs/src/examples/v-time-picker/prop-width.vue b/packages/docs/src/examples/v-time-picker/prop-width.vue deleted file mode 100644 index 6fe2cb38d70..00000000000 --- a/packages/docs/src/examples/v-time-picker/prop-width.vue +++ /dev/null @@ -1,32 +0,0 @@ - - - - - diff --git a/packages/docs/src/examples/v-time-picker/usage.vue b/packages/docs/src/examples/v-time-picker/usage.vue index 296d5f648dd..da382325d3e 100644 --- a/packages/docs/src/examples/v-time-picker/usage.vue +++ b/packages/docs/src/examples/v-time-picker/usage.vue @@ -1,41 +1,32 @@ - diff --git a/packages/docs/src/pages/en/components/time-pickers.md b/packages/docs/src/pages/en/components/time-pickers.md index f7398217fdc..ac70cbb188b 100644 --- a/packages/docs/src/pages/en/components/time-pickers.md +++ b/packages/docs/src/pages/en/components/time-pickers.md @@ -1,6 +1,7 @@ --- -disabled: true +emphasized: true meta: + nav: Time pickers title: Time picker component description: The time picker component is a stand-alone interface that allows the selection of hours and minutes in AM/PM and 24hr formats. keywords: time pickers, vuetify time picker component, vue time picker component @@ -14,7 +15,27 @@ related: The `v-time-picker` is stand-alone component that can be utilized in many existing Vuetify components. It offers the user a visual representation for selecting the time. - + + +::: warning + +This feature requires [v3.5.12](/getting-started/release-notes/?version=v3.5.12) + +::: + +## Installation + +Labs components require a manual import and installation of the component. + +```js { resource="src/plugins/vuetify.js" } +import { VEmptyState } from 'vuetify/labs/VEmptyState' + +export default createVuetify({ + components: { + VEmptyState, + }, +}) +``` ## Usage @@ -22,9 +43,15 @@ Time pickers have the light theme enabled by default. + + ## API - +| Component | Description | +| - | - | +| [v-time-picker](/api/v-time-picker/) | Primary Component | + + ## Examples @@ -36,12 +63,6 @@ You can specify allowed times using arrays, objects, and functions. You can also -#### AMPM in title - -You can move AM/PM switch to picker's title. - - - #### Colors Time picker colors can be set using the `color` and `header-color` props. If `header-color` prop is not provided header will use the `color` prop value." @@ -66,11 +87,11 @@ A time picker can be switched to 24hr format. Note that the `format` prop define -#### No title +#### No header -You can remove picker's title. +You can remove picker's header. - + #### Range @@ -96,12 +117,6 @@ Time picker can have seconds input. -#### Width - -You can specify the picker's width or make it full width. - - - ### Misc #### Dialog and menu diff --git a/packages/docs/src/pages/en/labs/introduction.md b/packages/docs/src/pages/en/labs/introduction.md index f375816514b..ce3f7fd6278 100644 --- a/packages/docs/src/pages/en/labs/introduction.md +++ b/packages/docs/src/pages/en/labs/introduction.md @@ -81,6 +81,7 @@ The following is a list of available and up-and-coming components for use with L | [v-fab](/components/floating-action-buttons/) | A layout aware [v-btn](/components/buttons/) | [v3.5.7](/getting-started/release-notes/?version=v3.5.7) | | [v-speed-dial](/components/speed-dials/) | A component for display actions | [v3.5.8](/getting-started/release-notes/?version=v3.5.8) | | [v-sparkline](/components/sparklines/) | A basic data display component | [v3.5.5](/getting-started/release-notes/?version=v3.5.5) | +| [v-time-picker](/components/time-pickers/) | A time-picker component | [v3.5.12](/getting-started/release-notes/?version=v3.5.12) | | [v-treeview](/components/treeview/) | A treeview component | [v3.5.9](/getting-started/release-notes/?version=v3.5.9) | ::: warning diff --git a/packages/vuetify/src/components/VTimePicker/VTimePicker.ts b/packages/vuetify/src/components/VTimePicker/VTimePicker.ts deleted file mode 100644 index 67a31bf3739..00000000000 --- a/packages/vuetify/src/components/VTimePicker/VTimePicker.ts +++ /dev/null @@ -1,368 +0,0 @@ -// @ts-nocheck -/* eslint-disable */ - -// Components -import VTimePickerTitle from './VTimePickerTitle' -import VTimePickerClock from './VTimePickerClock' - -// Mixins -import Picker from '../../mixins/picker' -import PickerButton from '../../mixins/picker-button' - -// Utils -import { createRange } from '../../util/helpers' -import pad from '../VDatePicker/util/pad' -import mixins from '../../util/mixins' - -// Types -import { VNode, PropType } from 'vue' -import { SelectingTimes } from './SelectingTimes' - -const rangeHours24 = createRange(24) -const rangeHours12am = createRange(12) -const rangeHours12pm = rangeHours12am.map(v => v + 12) -const range60 = createRange(60) -const selectingNames = { 1: 'hour', 2: 'minute', 3: 'second' } -export { SelectingTimes } - -type Period = 'am' | 'pm' -type AllowFunction = (val: number) => boolean - -export default mixins( - Picker, - PickerButton -/* @vue/component */ -).extend({ - name: 'v-time-picker', - - props: { - allowedHours: [Function, Array] as PropType, - allowedMinutes: [Function, Array] as PropType, - allowedSeconds: [Function, Array] as PropType, - disabled: Boolean, - format: { - type: String as PropType<'ampm' | '24hr'>, - default: 'ampm', - validator (val: any) { - return ['ampm', '24hr'].includes(val) - }, - }, - min: String, - max: String, - readonly: Boolean, - scrollable: Boolean, - useSeconds: Boolean, - value: null as any as PropType, - ampmInTitle: Boolean, - }, - - data () { - return { - inputHour: null as number | null, - inputMinute: null as number | null, - inputSecond: null as number | null, - lazyInputHour: null as number | null, - lazyInputMinute: null as number | null, - lazyInputSecond: null as number | null, - period: 'am' as Period, - selecting: SelectingTimes.Hour, - } - }, - - computed: { - selectingHour: { - get (): boolean { - return this.selecting === SelectingTimes.Hour - }, - set (v: boolean) { - this.selecting = SelectingTimes.Hour - }, - }, - selectingMinute: { - get (): boolean { - return this.selecting === SelectingTimes.Minute - }, - set (v: boolean) { - this.selecting = SelectingTimes.Minute - }, - }, - selectingSecond: { - get (): boolean { - return this.selecting === SelectingTimes.Second - }, - set (v: boolean) { - this.selecting = SelectingTimes.Second - }, - }, - isAllowedHourCb (): AllowFunction { - let cb: AllowFunction - - if (this.allowedHours instanceof Array) { - cb = (val: number) => (this.allowedHours as number[]).includes(val) - } else { - cb = this.allowedHours - } - - if (!this.min && !this.max) return cb - - const minHour = this.min ? Number(this.min.split(':')[0]) : 0 - const maxHour = this.max ? Number(this.max.split(':')[0]) : 23 - - return (val: number) => { - return val >= minHour * 1 && - val <= maxHour * 1 && - (!cb || cb(val)) - } - }, - isAllowedMinuteCb (): AllowFunction { - let cb: AllowFunction - - const isHourAllowed = !this.isAllowedHourCb || this.inputHour === null || this.isAllowedHourCb(this.inputHour) - if (this.allowedMinutes instanceof Array) { - cb = (val: number) => (this.allowedMinutes as number[]).includes(val) - } else { - cb = this.allowedMinutes - } - - if (!this.min && !this.max) { - return isHourAllowed ? cb : () => false - } - - const [minHour, minMinute] = this.min ? this.min.split(':').map(Number) : [0, 0] - const [maxHour, maxMinute] = this.max ? this.max.split(':').map(Number) : [23, 59] - const minTime = minHour * 60 + minMinute * 1 - const maxTime = maxHour * 60 + maxMinute * 1 - - return (val: number) => { - const time = 60 * this.inputHour! + val - return time >= minTime && - time <= maxTime && - isHourAllowed && - (!cb || cb(val)) - } - }, - isAllowedSecondCb (): AllowFunction { - let cb: AllowFunction - - const isHourAllowed = !this.isAllowedHourCb || this.inputHour === null || this.isAllowedHourCb(this.inputHour) - const isMinuteAllowed = isHourAllowed && - (!this.isAllowedMinuteCb || - this.inputMinute === null || - this.isAllowedMinuteCb(this.inputMinute) - ) - - if (this.allowedSeconds instanceof Array) { - cb = (val: number) => (this.allowedSeconds as number[]).includes(val) - } else { - cb = this.allowedSeconds - } - - if (!this.min && !this.max) { - return isMinuteAllowed ? cb : () => false - } - - const [minHour, minMinute, minSecond] = this.min ? this.min.split(':').map(Number) : [0, 0, 0] - const [maxHour, maxMinute, maxSecond] = this.max ? this.max.split(':').map(Number) : [23, 59, 59] - const minTime = minHour * 3600 + minMinute * 60 + (minSecond || 0) * 1 - const maxTime = maxHour * 3600 + maxMinute * 60 + (maxSecond || 0) * 1 - - return (val: number) => { - const time = 3600 * this.inputHour! + 60 * this.inputMinute! + val - return time >= minTime && - time <= maxTime && - isMinuteAllowed && - (!cb || cb(val)) - } - }, - isAmPm (): boolean { - return this.format === 'ampm' - }, - }, - - watch: { - value: 'setInputData', - }, - - mounted () { - this.setInputData(this.value) - this.$on('update:period', this.setPeriod) - }, - - methods: { - genValue () { - if (this.inputHour != null && this.inputMinute != null && (!this.useSeconds || this.inputSecond != null)) { - return `${pad(this.inputHour)}:${pad(this.inputMinute)}` + (this.useSeconds ? `:${pad(this.inputSecond!)}` : '') - } - - return null - }, - emitValue () { - const value = this.genValue() - if (value !== null) this.$emit('input', value) - }, - setPeriod (period: Period) { - this.period = period - if (this.inputHour != null) { - const newHour = this.inputHour! + (period === 'am' ? -12 : 12) - this.inputHour = this.firstAllowed('hour', newHour) - this.emitValue() - } - }, - setInputData (value: string | null | Date) { - if (value == null || value === '') { - this.inputHour = null - this.inputMinute = null - this.inputSecond = null - } else if (value instanceof Date) { - this.inputHour = value.getHours() - this.inputMinute = value.getMinutes() - this.inputSecond = value.getSeconds() - } else { - const [, hour, minute, , second, period] = value.trim().toLowerCase().match(/^(\d+):(\d+)(:(\d+))?([ap]m)?$/) || new Array(6) - - this.inputHour = period ? this.convert12to24(parseInt(hour, 10), period as Period) : parseInt(hour, 10) - this.inputMinute = parseInt(minute, 10) - this.inputSecond = parseInt(second || 0, 10) - } - - this.period = (this.inputHour == null || this.inputHour < 12) ? 'am' : 'pm' - }, - convert24to12 (hour: number) { - return hour ? ((hour - 1) % 12 + 1) : 12 - }, - convert12to24 (hour: number, period: Period) { - return hour % 12 + (period === 'pm' ? 12 : 0) - }, - onInput (value: number) { - if (this.selecting === SelectingTimes.Hour) { - this.inputHour = this.isAmPm ? this.convert12to24(value, this.period) : value - } else if (this.selecting === SelectingTimes.Minute) { - this.inputMinute = value - } else { - this.inputSecond = value - } - this.emitValue() - }, - onChange (value: number) { - this.$emit(`click:${selectingNames[this.selecting]}`, value) - - const emitChange = this.selecting === (this.useSeconds ? SelectingTimes.Second : SelectingTimes.Minute) - - if (this.selecting === SelectingTimes.Hour) { - this.selecting = SelectingTimes.Minute - } else if (this.useSeconds && this.selecting === SelectingTimes.Minute) { - this.selecting = SelectingTimes.Second - } - - if (this.inputHour === this.lazyInputHour && - this.inputMinute === this.lazyInputMinute && - (!this.useSeconds || this.inputSecond === this.lazyInputSecond) - ) return - - const time = this.genValue() - if (time === null) return - - this.lazyInputHour = this.inputHour - this.lazyInputMinute = this.inputMinute - this.useSeconds && (this.lazyInputSecond = this.inputSecond) - - emitChange && this.$emit('change', time) - }, - firstAllowed (type: 'hour' | 'minute' | 'second', value: number) { - const allowedFn = type === 'hour' ? this.isAllowedHourCb : (type === 'minute' ? this.isAllowedMinuteCb : this.isAllowedSecondCb) - if (!allowedFn) return value - - // TODO: clean up - const range = type === 'minute' - ? range60 - : (type === 'second' - ? range60 - : (this.isAmPm - ? (value < 12 - ? rangeHours12am - : rangeHours12pm) - : rangeHours24)) - const first = range.find(v => allowedFn((v + value) % range.length + range[0])) - return ((first || 0) + value) % range.length + range[0] - }, - genClock () { - return this.$createElement(VTimePickerClock, { - props: { - allowedValues: - this.selecting === SelectingTimes.Hour - ? this.isAllowedHourCb - : (this.selecting === SelectingTimes.Minute - ? this.isAllowedMinuteCb - : this.isAllowedSecondCb), - color: this.color, - dark: this.dark, - disabled: this.disabled, - double: this.selecting === SelectingTimes.Hour && !this.isAmPm, - format: this.selecting === SelectingTimes.Hour - ? (this.isAmPm ? this.convert24to12 : (val: number) => val) - : (val: number) => pad(val, 2), - light: this.light, - max: this.selecting === SelectingTimes.Hour ? (this.isAmPm && this.period === 'am' ? 11 : 23) : 59, - min: this.selecting === SelectingTimes.Hour && this.isAmPm && this.period === 'pm' ? 12 : 0, - readonly: this.readonly, - scrollable: this.scrollable, - size: Number(this.width) - ((!this.fullWidth && this.landscape) ? 80 : 20), - step: this.selecting === SelectingTimes.Hour ? 1 : 5, - value: this.selecting === SelectingTimes.Hour - ? this.inputHour - : (this.selecting === SelectingTimes.Minute - ? this.inputMinute - : this.inputSecond), - }, - on: { - input: this.onInput, - change: this.onChange, - }, - ref: 'clock', - }) - }, - genClockAmPm () { - return this.$createElement('div', this.setTextColor(this.color || 'primary', { - staticClass: 'v-time-picker-clock__ampm', - }), [ - this.genPickerButton('period', 'am', this.$vuetify.lang.t('$vuetify.timePicker.am'), this.disabled || this.readonly), - this.genPickerButton('period', 'pm', this.$vuetify.lang.t('$vuetify.timePicker.pm'), this.disabled || this.readonly), - ]) - }, - genPickerBody () { - return this.$createElement('div', { - staticClass: 'v-time-picker-clock__container', - key: this.selecting, - }, [ - !this.ampmInTitle && this.isAmPm && this.genClockAmPm(), - this.genClock(), - ]) - }, - genPickerTitle () { - return this.$createElement(VTimePickerTitle, { - props: { - ampm: this.isAmPm, - ampmReadonly: this.isAmPm && !this.ampmInTitle, - disabled: this.disabled, - hour: this.inputHour, - minute: this.inputMinute, - second: this.inputSecond, - period: this.period, - readonly: this.readonly, - useSeconds: this.useSeconds, - selecting: this.selecting, - }, - on: { - 'update:selecting': (value: 1 | 2 | 3) => (this.selecting = value), - 'update:period': (period: string) => this.$emit('update:period', period), - }, - ref: 'title', - slot: 'title', - }) - }, - }, - - render (): VNode { - return this.genPicker('v-picker--time') - }, -}) diff --git a/packages/vuetify/src/components/VTimePicker/VTimePickerClock.ts b/packages/vuetify/src/components/VTimePicker/VTimePickerClock.ts deleted file mode 100644 index e41bf6842fe..00000000000 --- a/packages/vuetify/src/components/VTimePicker/VTimePickerClock.ts +++ /dev/null @@ -1,285 +0,0 @@ -// @ts-nocheck -/* eslint-disable */ - -import './VTimePickerClock.sass' - -// Mixins -import Colorable from '../../mixins/colorable' -import Themeable from '../../mixins/themeable' - -// Types -import mixins, { ExtractVue } from '../../util/mixins' -import Vue, { VNode, PropType, VNodeData } from 'vue' -import { PropValidator } from 'vue/types/options' - -interface Point { - x: number - y: number -} - -interface options extends Vue { - $refs: { - clock: HTMLElement - innerClock: HTMLElement - } -} - -export default mixins -/* eslint-enable indent */ ->( - Colorable, - Themeable -/* @vue/component */ -).extend({ - name: 'v-time-picker-clock', - - props: { - allowedValues: Function as PropType<(value: number) => boolean>, - ampm: Boolean, - disabled: Boolean, - double: Boolean, - format: { - type: Function, - default: (val: string | number) => val, - } as PropValidator<(val: string | number) => string | number>, - max: { - type: Number, - required: true, - }, - min: { - type: Number, - required: true, - }, - scrollable: Boolean, - readonly: Boolean, - rotate: { - type: Number, - default: 0, - }, - step: { - type: Number, - default: 1, - }, - value: Number, - }, - - data () { - return { - inputValue: this.value, - isDragging: false, - valueOnMouseDown: null as number | null, - valueOnMouseUp: null as number | null, - } - }, - - computed: { - count (): number { - return this.max - this.min + 1 - }, - degreesPerUnit (): number { - return 360 / this.roundCount - }, - degrees (): number { - return this.degreesPerUnit * Math.PI / 180 - }, - displayedValue (): number { - return this.value == null ? this.min : this.value - }, - innerRadiusScale (): number { - return 0.62 - }, - roundCount (): number { - return this.double ? (this.count / 2) : this.count - }, - }, - - watch: { - value (value) { - this.inputValue = value - }, - }, - - methods: { - wheel (e: WheelEvent) { - e.preventDefault() - - const delta = Math.sign(-e.deltaY || 1) - let value = this.displayedValue - do { - value = value + delta - value = (value - this.min + this.count) % this.count + this.min - } while (!this.isAllowed(value) && value !== this.displayedValue) - - if (value !== this.displayedValue) { - this.update(value) - } - }, - isInner (value: number) { - return this.double && (value - this.min >= this.roundCount) - }, - handScale (value: number) { - return this.isInner(value) ? this.innerRadiusScale : 1 - }, - isAllowed (value: number) { - return !this.allowedValues || this.allowedValues(value) - }, - genValues () { - const children: VNode[] = [] - - for (let value = this.min; value <= this.max; value = value + this.step) { - const color = value === this.value && (this.color || 'accent') - children.push(this.$createElement('span', this.setBackgroundColor(color, { - staticClass: 'v-time-picker-clock__item', - class: { - 'v-time-picker-clock__item--active': value === this.displayedValue, - 'v-time-picker-clock__item--disabled': this.disabled || !this.isAllowed(value), - }, - style: this.getTransform(value), - domProps: { innerHTML: `${this.format(value)}` }, - }))) - } - - return children - }, - genHand () { - const scale = `scaleY(${this.handScale(this.displayedValue)})` - const angle = this.rotate + this.degreesPerUnit * (this.displayedValue - this.min) - const color = (this.value != null) && (this.color || 'accent') - return this.$createElement('div', this.setBackgroundColor(color, { - staticClass: 'v-time-picker-clock__hand', - class: { - 'v-time-picker-clock__hand--inner': this.isInner(this.value), - }, - style: { - transform: `rotate(${angle}deg) ${scale}`, - }, - })) - }, - getTransform (i: number) { - const { x, y } = this.getPosition(i) - return { - left: `${50 + x * 50}%`, - top: `${50 + y * 50}%`, - } - }, - getPosition (value: number) { - const rotateRadians = this.rotate * Math.PI / 180 - return { - x: Math.sin((value - this.min) * this.degrees + rotateRadians) * this.handScale(value), - y: -Math.cos((value - this.min) * this.degrees + rotateRadians) * this.handScale(value), - } - }, - onMouseDown (e: MouseEvent | TouchEvent) { - e.preventDefault() - - this.valueOnMouseDown = null - this.valueOnMouseUp = null - this.isDragging = true - this.onDragMove(e) - }, - onMouseUp (e: MouseEvent | TouchEvent) { - e.stopPropagation() - - this.isDragging = false - if (this.valueOnMouseUp !== null && this.isAllowed(this.valueOnMouseUp)) { - this.$emit('change', this.valueOnMouseUp) - } - }, - onDragMove (e: MouseEvent | TouchEvent) { - e.preventDefault() - if ((!this.isDragging && e.type !== 'click') || !this.$refs.clock) return - - const { width, top, left } = this.$refs.clock.getBoundingClientRect() - const { width: innerWidth } = this.$refs.innerClock.getBoundingClientRect() - const { clientX, clientY } = 'touches' in e ? e.touches[0] : e - const center = { x: width / 2, y: -width / 2 } - const coords = { x: clientX - left, y: top - clientY } - const handAngle = Math.round(this.angle(center, coords) - this.rotate + 360) % 360 - const insideClick = this.double && this.euclidean(center, coords) < (innerWidth + innerWidth * this.innerRadiusScale) / 4 - const checksCount = Math.ceil(15 / this.degreesPerUnit) - let value - - for (let i = 0; i < checksCount; i++) { - value = this.angleToValue(handAngle + i * this.degreesPerUnit, insideClick) - if (this.isAllowed(value)) return this.setMouseDownValue(value) - - value = this.angleToValue(handAngle - i * this.degreesPerUnit, insideClick) - if (this.isAllowed(value)) return this.setMouseDownValue(value) - } - }, - angleToValue (angle: number, insideClick: boolean): number { - const value = ( - Math.round(angle / this.degreesPerUnit) + - (insideClick ? this.roundCount : 0) - ) % this.count + this.min - - // Necessary to fix edge case when selecting left part of the value(s) at 12 o'clock - if (angle < (360 - this.degreesPerUnit / 2)) return value - - return insideClick ? this.max - this.roundCount + 1 : this.min - }, - setMouseDownValue (value: number) { - if (this.valueOnMouseDown === null) { - this.valueOnMouseDown = value - } - - this.valueOnMouseUp = value - this.update(value) - }, - update (value: number) { - if (this.inputValue !== value) { - this.inputValue = value - this.$emit('input', value) - } - }, - euclidean (p0: Point, p1: Point) { - const dx = p1.x - p0.x - const dy = p1.y - p0.y - - return Math.sqrt(dx * dx + dy * dy) - }, - angle (center: Point, p1: Point) { - const value = 2 * Math.atan2(p1.y - center.y - this.euclidean(center, p1), p1.x - center.x) - return Math.abs(value * 180 / Math.PI) - }, - }, - - render (h): VNode { - const data: VNodeData = { - staticClass: 'v-time-picker-clock', - class: { - 'v-time-picker-clock--indeterminate': this.value == null, - ...this.themeClasses, - }, - on: (this.readonly || this.disabled) ? undefined : { - mousedown: this.onMouseDown, - mouseup: this.onMouseUp, - mouseleave: (e: MouseEvent) => (this.isDragging && this.onMouseUp(e)), - touchstart: this.onMouseDown, - touchend: this.onMouseUp, - mousemove: this.onDragMove, - touchmove: this.onDragMove, - }, - ref: 'clock', - } - - if (this.scrollable && data.on) { - data.on.wheel = this.wheel - } - - return h('div', data, [ - h('div', { - staticClass: 'v-time-picker-clock__inner', - ref: 'innerClock', - }, [ - this.genHand(), - this.genValues(), - ]), - ]) - }, -}) diff --git a/packages/vuetify/src/components/VTimePicker/VTimePickerTitle.sass b/packages/vuetify/src/components/VTimePicker/VTimePickerTitle.sass deleted file mode 100644 index ebc548385a1..00000000000 --- a/packages/vuetify/src/components/VTimePicker/VTimePickerTitle.sass +++ /dev/null @@ -1,61 +0,0 @@ -@import './_variables.scss' -@import '../VPicker/_variables.scss' - -.v-time-picker-title - color: $time-picker-title-color - display: flex - line-height: 1 - justify-content: flex-end - -.v-time-picker-title__time - white-space: nowrap - direction: ltr - - .v-picker__title__btn, - span - align-items: center - display: inline-flex - height: $time-picker-title-btn-height - font-size: $time-picker-title-btn-height - justify-content: center - -.v-time-picker-title__ampm - align-self: flex-end - display: flex - flex-direction: column - font-size: $time-picker-ampm-title-font-size - text-transform: uppercase - margin-inline: $time-picker-ampm-title-margin-inline - margin-block: $time-picker-ampm-title-margin-block - - div:only-child - flex-direction: row - - &--readonly - .v-picker__title__btn.v-picker__title__btn--active - opacity: $picker-inactive-btn-opacity - -.v-picker__title--landscape - .v-time-picker-title - flex-direction: column - justify-content: center - height: 100% - - .v-time-picker-title__time - text-align: right - - .v-picker__title__btn, - span - height: $time-picker-landscape-title-btn-height - font-size: $time-picker-landscape-title-btn-height - - .v-time-picker-title__ampm - margin: $time-picker-landscape-ampm-title-margin - align-self: initial - text-align: center - -.v-picker--time .v-picker__title--landscape - padding: 0 - - .v-time-picker-title__time - text-align: center diff --git a/packages/vuetify/src/components/VTimePicker/VTimePickerTitle.ts b/packages/vuetify/src/components/VTimePicker/VTimePickerTitle.ts deleted file mode 100644 index d9d834374ac..00000000000 --- a/packages/vuetify/src/components/VTimePicker/VTimePickerTitle.ts +++ /dev/null @@ -1,84 +0,0 @@ -// @ts-nocheck -/* eslint-disable */ - -import './VTimePickerTitle.sass' - -// Mixins -import PickerButton from '../../mixins/picker-button' - -// Utils -import { pad } from '../VDatePicker/util' -import mixins from '../../util/mixins' - -import { SelectingTimes } from './SelectingTimes' -import { VNode, PropType } from 'vue' - -export default mixins( - PickerButton -/* @vue/component */ -).extend({ - name: 'v-time-picker-title', - - props: { - ampm: Boolean, - ampmReadonly: Boolean, - disabled: Boolean, - hour: Number, - minute: Number, - second: Number, - period: { - type: String as PropType<'am' | 'pm'>, - validator: period => period === 'am' || period === 'pm', - }, - readonly: Boolean, - useSeconds: Boolean, - selecting: Number, - }, - - methods: { - genTime () { - let hour = this.hour - if (this.ampm) { - hour = hour ? ((hour - 1) % 12 + 1) : 12 - } - - const displayedHour = this.hour == null ? '--' : this.ampm ? String(hour) : pad(hour) - const displayedMinute = this.minute == null ? '--' : pad(this.minute) - const titleContent = [ - this.genPickerButton('selecting', SelectingTimes.Hour, displayedHour, this.disabled), - this.$createElement('span', ':'), - this.genPickerButton('selecting', SelectingTimes.Minute, displayedMinute, this.disabled), - ] - - if (this.useSeconds) { - const displayedSecond = this.second == null ? '--' : pad(this.second) - titleContent.push(this.$createElement('span', ':')) - titleContent.push(this.genPickerButton('selecting', SelectingTimes.Second, displayedSecond, this.disabled)) - } - return this.$createElement('div', { - class: 'v-time-picker-title__time', - }, titleContent) - }, - genAmPm () { - return this.$createElement('div', { - staticClass: 'v-time-picker-title__ampm', - class: { - 'v-time-picker-title__ampm--readonly': this.ampmReadonly, - }, - }, [ - (!this.ampmReadonly || this.period === 'am') ? this.genPickerButton('period', 'am', this.$vuetify.lang.t('$vuetify.timePicker.am'), this.disabled || this.readonly) : null, - (!this.ampmReadonly || this.period === 'pm') ? this.genPickerButton('period', 'pm', this.$vuetify.lang.t('$vuetify.timePicker.pm'), this.disabled || this.readonly) : null, - ]) - }, - }, - - render (h): VNode { - const children = [this.genTime()] - - this.ampm && children.push(this.genAmPm()) - - return h('div', { - staticClass: 'v-time-picker-title', - }, children) - }, -}) diff --git a/packages/vuetify/src/components/VTimePicker/__tests__/VTimePicker.spec.ts b/packages/vuetify/src/components/VTimePicker/__tests__/VTimePicker.spec.ts deleted file mode 100644 index d25419880ed..00000000000 --- a/packages/vuetify/src/components/VTimePicker/__tests__/VTimePicker.spec.ts +++ /dev/null @@ -1,753 +0,0 @@ -// @ts-nocheck -/* eslint-disable */ - -// import Vue from 'vue' -// import { Lang } from '../../../services/lang' -// import VTimePicker, { SelectingTimes } from '../VTimePicker' -import { - mount, - MountOptions, - Wrapper, -} from '@vue/test-utils' - -// import { preset } from '../../../presets/default' - -// Vue.prototype.$vuetify = { -// lang: new Lang(preset), -// } - -describe.skip('VTimePicker.ts', () => { - type Instance = InstanceType - let mountFunction: (options?: MountOptions) => Wrapper - beforeEach(() => { - mountFunction = (options?: MountOptions) => { - return mount(VTimePicker, options) - } - }); - - [true, false].forEach(useSecondsValue => { // eslint-disable-line max-statements - const useSecondsDesc = (useSecondsValue ? '. with useSeconds' : '') - it('should accept a value' + useSecondsDesc, async () => { - const wrapper = mountFunction({ - propsData: { - value: '09:12:34', - useSeconds: useSecondsValue, - }, - }) - - await wrapper.vm.$nextTick() - expect(wrapper.vm.selectingHour).toBe(true) - expect(wrapper.vm.selectingMinute).toBe(false) - expect(wrapper.vm.selectingSecond).toBe(false) - expect(wrapper.vm.selecting).toBe(SelectingTimes.Hour) - expect(wrapper.vm.inputHour).toBe(9) - expect(wrapper.vm.inputMinute).toBe(12) - expect(wrapper.vm.inputSecond).toBe(34) - expect(wrapper.html()).toMatchSnapshot() - }) - - it('should render landscape component' + useSecondsDesc, async () => { - const wrapper = mountFunction({ - propsData: { - value: '09:12:34', - landscape: true, - useSeconds: useSecondsValue, - }, - }) - - await wrapper.vm.$nextTick() - expect(wrapper.html()).toMatchSnapshot() - }) - - it('should render disabled component' + useSecondsDesc, async () => { - const wrapper = mountFunction({ - propsData: { - disabled: true, - value: '09:12:34', - useSeconds: useSecondsValue, - }, - }) - - await wrapper.vm.$nextTick() - expect(wrapper.html()).toMatchSnapshot() - }) - - it('should render flat component' + useSecondsDesc, async () => { - const wrapper = mountFunction({ - propsData: { - flat: true, - value: '09:12:34', - useSeconds: useSecondsValue, - }, - }) - - await wrapper.vm.$nextTick() - expect(wrapper.html()).toMatchSnapshot() - }) - - it('should render component with elevation' + useSecondsDesc, async () => { - const wrapper = mountFunction({ - propsData: { - elevation: 15, - value: '09:12:34', - useSeconds: useSecondsValue, - }, - }) - - await wrapper.vm.$nextTick() - expect(wrapper.html()).toMatchSnapshot() - }) - - it('should render component without a title' + useSecondsDesc, () => { - const wrapper = mountFunction({ - propsData: { - value: '09:12:34', - noTitle: true, - useSeconds: useSecondsValue, - }, - }) - expect(wrapper.findAll('.v-picker__title').wrappers).toHaveLength(0) - }) - - it('should accept a date object for a value' + useSecondsDesc, async () => { - const now = new Date('2017-01-01 12:00 AM') - const wrapper = mountFunction({ - propsData: { - value: now, - useSeconds: useSecondsValue, - }, - }) - - await wrapper.vm.$nextTick() - expect(wrapper.vm.inputHour).toBe(0) - expect(wrapper.vm.inputMinute).toBe(0) - expect(wrapper.vm.inputSecond).toBe(0) - expect(wrapper.vm.period).toBe('am') - expect(wrapper.html()).toMatchSnapshot() - }) - - it('should change am/pm when updated from model' + useSecondsDesc, async () => { - const wrapper = mountFunction({ - propsData: { - value: '9:00am', - useSeconds: useSecondsValue, - }, - }) - - await wrapper.vm.$nextTick() - wrapper.setProps({ value: '9:00pm' }) - expect(wrapper.vm.period).toBe('pm') - // with seconds - wrapper.setProps({ value: '9:00:12am' }) - expect(wrapper.vm.period).toBe('am') - wrapper.setProps({ value: '9:00:12pm' }) - expect(wrapper.vm.period).toBe('pm') - expect(wrapper.html()).toMatchSnapshot() - }) - - it('should set picker to pm when given Date after noon' + useSecondsDesc, () => { - const wrapper = mountFunction({ - propsData: { - value: new Date('2017-01-01 12:00 PM'), - useSeconds: useSecondsValue, - }, - }) - - expect(wrapper.vm.period).toBe('pm') - // with seconds - wrapper.setProps({ value: new Date('2017-01-01 12:00:13 PM') }) - expect(wrapper.vm.period).toBe('pm') - }) - - it('should set picker to pm when given string with PM in it' + useSecondsDesc, () => { - const wrapper = mountFunction({ - propsData: { - value: '1:00 PM', - useSeconds: useSecondsValue, - }, - }) - - expect(wrapper.vm.period).toBe('pm') - // with seconds - wrapper.setProps({ value: '1:00:12 PM' }) - expect(wrapper.vm.period).toBe('pm') - }) - - it('should set picker to pm when given string with pm in it' + useSecondsDesc, () => { - const wrapper = mountFunction({ - propsData: { - value: '1:00 pm', - useSeconds: useSecondsValue, - }, - }) - - expect(wrapper.vm.period).toBe('pm') - // with seconds - wrapper.setProps({ value: '1:00:13 pm' }) - expect(wrapper.vm.period).toBe('pm') - }) - - it('should set picker to am when given Date before noon' + useSecondsDesc, () => { - const wrapper = mountFunction({ - propsData: { - value: new Date('2017-01-01 1:00 AM'), - useSeconds: useSecondsValue, - }, - }) - - expect(wrapper.vm.period).toBe('am') - // with seconds - wrapper.setProps({ value: new Date('2017-01-01 1:00:30 AM') }) - expect(wrapper.vm.period).toBe('am') - }) - - it('should render colored time picker' + useSecondsDesc, async () => { - const wrapper = mountFunction({ - propsData: { - value: '09:00:00', - color: 'orange darken-1', - useSeconds: useSecondsValue, - }, - }) - - await wrapper.vm.$nextTick() - expect(wrapper.html()).toMatchSnapshot() - }) - - it('should render colored time picker, header' + useSecondsDesc, async () => { - const wrapper = mountFunction({ - propsData: { - value: '09:00:00', - color: 'primary', - headerColor: 'orange darken-1', - useSeconds: useSecondsValue, - }, - }) - - await wrapper.vm.$nextTick() - expect(wrapper.html()).toMatchSnapshot() - }) - - it('should render dark time picker' + useSecondsDesc, async () => { - const wrapper = mountFunction({ - propsData: { - dark: true, - useSeconds: useSecondsValue, - }, - }) - - await wrapper.vm.$nextTick() - expect(wrapper.html()).toMatchSnapshot() - }) - - it('should set input hour when setting hour in 12hr mode' + useSecondsDesc, () => { // eslint-disable-line max-statements - const wrapper = mountFunction({ - propsData: { - value: '01:23pm', - format: 'ampm', - useSeconds: useSecondsValue, - }, - }) - - wrapper.vm.onInput(7) - expect(wrapper.vm.inputHour).toBe(19) - - wrapper.setProps({ format: '24hr' }) - wrapper.vm.onInput(8) - expect(wrapper.vm.inputHour).toBe(8) - - wrapper.vm.selecting = SelectingTimes.Minute - wrapper.vm.onInput(33) - expect(wrapper.vm.inputHour).toBe(8) - expect(wrapper.vm.inputMinute).toBe(33) - expect(wrapper.vm.inputSecond).toBe(0) - - // with seconds - wrapper.vm.selecting = SelectingTimes.Hour - wrapper.setProps({ format: 'ampm' }) - wrapper.setProps({ value: '01:23:45pm' }) - wrapper.vm.onInput(7) - expect(wrapper.vm.inputHour).toBe(19) - - wrapper.setProps({ format: '24hr' }) - wrapper.vm.onInput(8) - expect(wrapper.vm.inputHour).toBe(8) - - wrapper.vm.selecting = SelectingTimes.Minute - wrapper.vm.onInput(33) - expect(wrapper.vm.inputHour).toBe(8) - expect(wrapper.vm.inputMinute).toBe(33) - expect(wrapper.vm.inputSecond).toBe(45) - - wrapper.vm.selecting = SelectingTimes.Second - wrapper.vm.onInput(44) - expect(wrapper.vm.inputHour).toBe(8) - expect(wrapper.vm.inputMinute).toBe(33) - expect(wrapper.vm.inputSecond).toBe(44) - }) - - it('should set properly input time' + useSecondsDesc, () => { // eslint-disable-line max-statements - const wrapper = mountFunction({ - propsData: { - format: '24hr', - useSeconds: useSecondsValue, - }, - }) - - wrapper.vm.setInputData(new Date('2001-01-01 17:35')) - expect(wrapper.vm.inputHour).toBe(17) - expect(wrapper.vm.inputMinute).toBe(35) - expect(wrapper.vm.inputSecond).toBe(0) - - wrapper.vm.setInputData(null) - expect(wrapper.vm.inputHour).toBeNull() - expect(wrapper.vm.inputMinute).toBeNull() - expect(wrapper.vm.inputSecond).toBeNull() - - wrapper.vm.setInputData('') - expect(wrapper.vm.inputHour).toBeNull() - expect(wrapper.vm.inputMinute).toBeNull() - expect(wrapper.vm.inputSecond).toBeNull() - - wrapper.vm.setInputData('12:34am') - expect(wrapper.vm.inputHour).toBe(0) - expect(wrapper.vm.inputMinute).toBe(34) - expect(wrapper.vm.inputSecond).toBe(0) - - wrapper.vm.setInputData('12:34:28pm') - expect(wrapper.vm.inputHour).toBe(12) - expect(wrapper.vm.inputMinute).toBe(34) - expect(wrapper.vm.inputSecond).toBe(28) - - wrapper.vm.setInputData('7:34am') - expect(wrapper.vm.inputHour).toBe(7) - - wrapper.vm.setInputData('12:34pm') - expect(wrapper.vm.inputHour).toBe(12) - - wrapper.vm.setInputData('7:34pm') - expect(wrapper.vm.inputHour).toBe(19) - }) - - it('should update hour when changing period' + useSecondsDesc, async () => { - const wrapper = mountFunction({ - propsData: { - value: '15:34', - useSeconds: useSecondsValue, - }, - }) - - wrapper.vm.setPeriod('am') - await wrapper.vm.$nextTick() - expect(wrapper.vm.inputHour).toBe(3) - wrapper.vm.setPeriod('pm') - await wrapper.vm.$nextTick() - expect(wrapper.vm.inputHour).toBe(15) - - // with seconds - wrapper.setProps({ value: '15:34:14' }) - wrapper.vm.setPeriod('am') - await wrapper.vm.$nextTick() - expect(wrapper.vm.inputHour).toBe(3) - wrapper.vm.setPeriod('pm') - await wrapper.vm.$nextTick() - expect(wrapper.vm.inputHour).toBe(15) - }) - - it('should change selecting when hour/minute/second is selected' + useSecondsDesc, () => { - const wrapper = mountFunction({ - propsData: { - value: '01:23pm', - format: 'ampm', - useSeconds: useSecondsValue, - }, - }) - - wrapper.vm.onChange(-1) - expect(wrapper.vm.selecting).toBe(SelectingTimes.Minute) - expect(wrapper.vm.selectingHour).toBe(false) - expect(wrapper.vm.selectingMinute).toBe(true) - expect(wrapper.vm.selectingSecond).toBe(false) - wrapper.vm.onChange(-1) - expect(wrapper.vm.selecting).toBe(useSecondsValue ? SelectingTimes.Second : SelectingTimes.Minute) - expect(wrapper.vm.selectingHour).toBe(false) - expect(wrapper.vm.selectingMinute).toBe(!useSecondsValue) - expect(wrapper.vm.selectingSecond).toBe(useSecondsValue) - }) - - it('should emit click:XXX event on change' + useSecondsDesc, () => { - const wrapper = mountFunction({ - propsData: { - value: '01:23:45pm', - format: 'ampm', - useSeconds: useSecondsValue, - }, - }) - - const clickHour = jest.fn() - const clickMinute = jest.fn() - const clickSecond = jest.fn() - - wrapper.vm.$on('click:hour', clickHour) - wrapper.vm.$on('click:minute', clickMinute) - wrapper.vm.$on('click:second', clickSecond) - - wrapper.vm.onChange(1) - expect(clickHour).toHaveBeenCalledTimes(1) - expect(clickHour).toHaveBeenCalledWith(1) - expect(clickMinute).toHaveBeenCalledTimes(0) - expect(clickSecond).toHaveBeenCalledTimes(0) - - wrapper.vm.onChange(59) - expect(clickHour).toHaveBeenCalledTimes(1) - expect(clickMinute).toHaveBeenCalledTimes(1) - expect(clickMinute).toHaveBeenCalledWith(59) - expect(clickSecond).toHaveBeenCalledTimes(0) - - if (useSecondsValue) { - wrapper.vm.onChange(45) - expect(clickHour).toHaveBeenCalledTimes(1) - expect(clickMinute).toHaveBeenCalledTimes(1) - expect(clickSecond).toHaveBeenCalledTimes(1) - expect(clickSecond).toHaveBeenCalledWith(45) - } - }) - - it('should change selecting when clicked in title' + useSecondsDesc, () => { - const wrapper = mountFunction({ - propsData: { - value: '01:23pm', - format: 'ampm', - useSeconds: useSecondsValue, - }, - }) - - const title = wrapper.vm.$refs.title - - expect(wrapper.vm.selecting).toBe(SelectingTimes.Hour) - expect(wrapper.vm.selectingHour).toBe(true) - expect(wrapper.vm.selectingMinute).toBe(false) - expect(wrapper.vm.selectingSecond).toBe(false) - title.$emit('update:selecting', SelectingTimes.Minute) - expect(wrapper.vm.selecting).toBe(SelectingTimes.Minute) - expect(wrapper.vm.selectingHour).toBe(false) - expect(wrapper.vm.selectingMinute).toBe(true) - expect(wrapper.vm.selectingSecond).toBe(false) - title.$emit('update:selecting', SelectingTimes.Second) - expect(wrapper.vm.selecting).toBe(SelectingTimes.Second) - expect(wrapper.vm.selectingHour).toBe(false) - expect(wrapper.vm.selectingMinute).toBe(false) - expect(wrapper.vm.selectingSecond).toBe(true) - }) - - it('should change period when clicked in title' + useSecondsDesc, () => { - const wrapper = mountFunction({ - propsData: { - value: '01:23pm', - format: 'ampm', - useSeconds: useSecondsValue, - }, - }) - - const title = wrapper.vm.$refs.title - - expect(wrapper.vm.period).toBe('pm') - title.$emit('update:period', 'am') - expect(wrapper.vm.period).toBe('am') - title.$emit('update:period', 'pm') - expect(wrapper.vm.period).toBe('pm') - }) - - it('should match snapshot with slot' + useSecondsDesc, async () => { - const vm = new Vue() - const slot = props => vm.$createElement('div', { class: 'scoped-slot' }) - const component = Vue.component('test', { - render (h) { - return h(VTimePicker, { - props: { - value: '10:12', - useSeconds: useSecondsValue, - }, - scopedSlots: { - default: slot, - }, - }) - }, - }) - - const wrapper = mount(component) - expect(wrapper.findAll('.v-picker__actions .scoped-slot').wrappers).toHaveLength(1) - }) - - it('should calculate allowed seconds/minute/hour callback' + useSecondsDesc, async () => { // eslint-disable-line max-statements - const wrapper = mountFunction({ - propsData: { - value: '10:00:00', - allowedSeconds: value => value === 0 || (value >= 30 && value <= 40), - allowedMinutes: value => value % 5 === 0, - allowedHours: value => value !== 11, - min: '9:31', - max: '12:30', - useSeconds: useSecondsValue, - }, - }) - - expect(wrapper.vm.isAllowedHourCb(8)).toBe(false) - expect(wrapper.vm.isAllowedHourCb(9)).toBe(true) - expect(wrapper.vm.isAllowedHourCb(10)).toBe(true) - expect(wrapper.vm.isAllowedHourCb(11)).toBe(false) - expect(wrapper.vm.isAllowedHourCb(12)).toBe(true) - expect(wrapper.vm.isAllowedHourCb(13)).toBe(false) - - wrapper.vm.inputHour = 8 - expect(wrapper.vm.isAllowedMinuteCb(30)).toBe(false) - expect(wrapper.vm.isAllowedMinuteCb(31)).toBe(false) - expect(wrapper.vm.isAllowedSecondCb(29)).toBe(false) - expect(wrapper.vm.isAllowedSecondCb(30)).toBe(false) - wrapper.vm.inputMinute = 30 - expect(wrapper.vm.isAllowedSecondCb(29)).toBe(false) - expect(wrapper.vm.isAllowedSecondCb(30)).toBe(false) - wrapper.vm.inputMinute = 31 - expect(wrapper.vm.isAllowedSecondCb(29)).toBe(false) - expect(wrapper.vm.isAllowedSecondCb(30)).toBe(false) - - wrapper.vm.inputHour = 9 - wrapper.vm.inputMinute = 0 - expect(wrapper.vm.isAllowedMinuteCb(30)).toBe(false) - expect(wrapper.vm.isAllowedMinuteCb(31)).toBe(false) - expect(wrapper.vm.isAllowedMinuteCb(35)).toBe(true) - expect(wrapper.vm.isAllowedSecondCb(29)).toBe(false) - expect(wrapper.vm.isAllowedSecondCb(30)).toBe(false) - wrapper.vm.inputMinute = 30 - expect(wrapper.vm.isAllowedSecondCb(29)).toBe(false) - expect(wrapper.vm.isAllowedSecondCb(30)).toBe(false) - wrapper.vm.inputMinute = 31 - expect(wrapper.vm.isAllowedSecondCb(29)).toBe(false) - expect(wrapper.vm.isAllowedSecondCb(30)).toBe(false) - wrapper.vm.inputMinute = 35 - expect(wrapper.vm.isAllowedSecondCb(29)).toBe(false) - expect(wrapper.vm.isAllowedSecondCb(30)).toBe(true) - - wrapper.vm.inputHour = 10 - wrapper.vm.inputMinute = 0 - expect(wrapper.vm.isAllowedMinuteCb(30)).toBe(true) - expect(wrapper.vm.isAllowedMinuteCb(31)).toBe(false) - expect(wrapper.vm.isAllowedMinuteCb(35)).toBe(true) - expect(wrapper.vm.isAllowedSecondCb(29)).toBe(false) - expect(wrapper.vm.isAllowedSecondCb(30)).toBe(true) - wrapper.vm.inputMinute = 30 - expect(wrapper.vm.isAllowedSecondCb(29)).toBe(false) - expect(wrapper.vm.isAllowedSecondCb(30)).toBe(true) - wrapper.vm.inputMinute = 31 - expect(wrapper.vm.isAllowedSecondCb(29)).toBe(false) - expect(wrapper.vm.isAllowedSecondCb(30)).toBe(false) - wrapper.vm.inputMinute = 35 - expect(wrapper.vm.isAllowedSecondCb(29)).toBe(false) - expect(wrapper.vm.isAllowedSecondCb(30)).toBe(true) - - wrapper.vm.inputHour = 11 - wrapper.vm.inputMinute = 0 - expect(wrapper.vm.isAllowedMinuteCb(30)).toBe(false) - expect(wrapper.vm.isAllowedMinuteCb(31)).toBe(false) - expect(wrapper.vm.isAllowedMinuteCb(35)).toBe(false) - expect(wrapper.vm.isAllowedSecondCb(29)).toBe(false) - expect(wrapper.vm.isAllowedSecondCb(30)).toBe(false) - wrapper.vm.inputMinute = 30 - expect(wrapper.vm.isAllowedSecondCb(29)).toBe(false) - expect(wrapper.vm.isAllowedSecondCb(30)).toBe(false) - wrapper.vm.inputMinute = 31 - expect(wrapper.vm.isAllowedSecondCb(29)).toBe(false) - expect(wrapper.vm.isAllowedSecondCb(30)).toBe(false) - wrapper.vm.inputMinute = 35 - expect(wrapper.vm.isAllowedSecondCb(29)).toBe(false) - expect(wrapper.vm.isAllowedSecondCb(30)).toBe(false) - - wrapper.vm.inputHour = 12 - wrapper.vm.inputMinute = 0 - expect(wrapper.vm.isAllowedMinuteCb(30)).toBe(true) - expect(wrapper.vm.isAllowedMinuteCb(31)).toBe(false) - expect(wrapper.vm.isAllowedMinuteCb(35)).toBe(false) - expect(wrapper.vm.isAllowedSecondCb(29)).toBe(false) - expect(wrapper.vm.isAllowedSecondCb(30)).toBe(true) - wrapper.vm.inputMinute = 30 - expect(wrapper.vm.isAllowedSecondCb(0)).toBe(true) - expect(wrapper.vm.isAllowedSecondCb(29)).toBe(false) - expect(wrapper.vm.isAllowedSecondCb(30)).toBe(false) - wrapper.vm.inputMinute = 31 - expect(wrapper.vm.isAllowedSecondCb(29)).toBe(false) - expect(wrapper.vm.isAllowedSecondCb(30)).toBe(false) - wrapper.vm.inputMinute = 35 - expect(wrapper.vm.isAllowedSecondCb(29)).toBe(false) - expect(wrapper.vm.isAllowedSecondCb(30)).toBe(false) - }) - - it('should calculate allowed seconds/minute/hour callback from array' + useSecondsDesc, async () => { // eslint-disable-line max-statements - const allowedHours = [9, 10, 12] - const allowedMinutes = [30, 35] - const allowedSeconds = [0, 30] - - const wrapper = mountFunction({ - propsData: { - value: '10:00:00', - allowedSeconds, - allowedMinutes, - allowedHours, - min: '9:31', - max: '12:30', - useSeconds: useSecondsValue, - }, - }) - - expect(wrapper.vm.isAllowedHourCb(0)).toBe(false) - expect(wrapper.vm.isAllowedHourCb(8)).toBe(false) - expect(wrapper.vm.isAllowedHourCb(9)).toBe(true) - expect(wrapper.vm.isAllowedHourCb(10)).toBe(true) - expect(wrapper.vm.isAllowedHourCb(11)).toBe(false) - expect(wrapper.vm.isAllowedHourCb(12)).toBe(true) - expect(wrapper.vm.isAllowedHourCb(13)).toBe(false) - - wrapper.vm.inputHour = 8 - expect(wrapper.vm.isAllowedMinuteCb(30)).toBe(false) - expect(wrapper.vm.isAllowedMinuteCb(31)).toBe(false) - expect(wrapper.vm.isAllowedSecondCb(29)).toBe(false) - expect(wrapper.vm.isAllowedSecondCb(30)).toBe(false) - wrapper.vm.inputMinute = 30 - expect(wrapper.vm.isAllowedSecondCb(29)).toBe(false) - expect(wrapper.vm.isAllowedSecondCb(30)).toBe(false) - wrapper.vm.inputMinute = 31 - expect(wrapper.vm.isAllowedSecondCb(29)).toBe(false) - expect(wrapper.vm.isAllowedSecondCb(30)).toBe(false) - - wrapper.vm.inputHour = 9 - wrapper.vm.inputMinute = 0 - expect(wrapper.vm.isAllowedMinuteCb(30)).toBe(false) - expect(wrapper.vm.isAllowedMinuteCb(31)).toBe(false) - expect(wrapper.vm.isAllowedMinuteCb(35)).toBe(true) - expect(wrapper.vm.isAllowedSecondCb(29)).toBe(false) - expect(wrapper.vm.isAllowedSecondCb(30)).toBe(false) - wrapper.vm.inputMinute = 30 - expect(wrapper.vm.isAllowedSecondCb(29)).toBe(false) - expect(wrapper.vm.isAllowedSecondCb(30)).toBe(false) - wrapper.vm.inputMinute = 31 - expect(wrapper.vm.isAllowedSecondCb(29)).toBe(false) - expect(wrapper.vm.isAllowedSecondCb(30)).toBe(false) - wrapper.vm.inputMinute = 35 - expect(wrapper.vm.isAllowedSecondCb(29)).toBe(false) - expect(wrapper.vm.isAllowedSecondCb(30)).toBe(true) - expect(wrapper.vm.isAllowedSecondCb(0)).toBe(true) - - wrapper.vm.inputHour = 10 - wrapper.vm.inputMinute = 0 - expect(wrapper.vm.isAllowedMinuteCb(30)).toBe(true) - expect(wrapper.vm.isAllowedMinuteCb(31)).toBe(false) - expect(wrapper.vm.isAllowedMinuteCb(35)).toBe(true) - expect(wrapper.vm.isAllowedSecondCb(29)).toBe(false) - expect(wrapper.vm.isAllowedSecondCb(0)).toBe(false) - expect(wrapper.vm.isAllowedSecondCb(30)).toBe(false) - wrapper.vm.inputMinute = 30 - expect(wrapper.vm.isAllowedSecondCb(29)).toBe(false) - expect(wrapper.vm.isAllowedSecondCb(30)).toBe(true) - wrapper.vm.inputMinute = 31 - expect(wrapper.vm.isAllowedSecondCb(29)).toBe(false) - expect(wrapper.vm.isAllowedSecondCb(30)).toBe(false) - wrapper.vm.inputMinute = 35 - expect(wrapper.vm.isAllowedSecondCb(29)).toBe(false) - expect(wrapper.vm.isAllowedSecondCb(30)).toBe(true) - - wrapper.vm.inputHour = 11 - wrapper.vm.inputMinute = 0 - expect(wrapper.vm.isAllowedMinuteCb(30)).toBe(false) - expect(wrapper.vm.isAllowedMinuteCb(31)).toBe(false) - expect(wrapper.vm.isAllowedMinuteCb(35)).toBe(false) - expect(wrapper.vm.isAllowedSecondCb(29)).toBe(false) - expect(wrapper.vm.isAllowedSecondCb(30)).toBe(false) - wrapper.vm.inputMinute = 30 - expect(wrapper.vm.isAllowedSecondCb(29)).toBe(false) - expect(wrapper.vm.isAllowedSecondCb(30)).toBe(false) - wrapper.vm.inputMinute = 31 - expect(wrapper.vm.isAllowedSecondCb(29)).toBe(false) - expect(wrapper.vm.isAllowedSecondCb(30)).toBe(false) - wrapper.vm.inputMinute = 35 - expect(wrapper.vm.isAllowedSecondCb(29)).toBe(false) - expect(wrapper.vm.isAllowedSecondCb(30)).toBe(false) - - wrapper.vm.inputHour = 12 - wrapper.vm.inputMinute = 0 - expect(wrapper.vm.isAllowedMinuteCb(30)).toBe(true) - expect(wrapper.vm.isAllowedMinuteCb(31)).toBe(false) - expect(wrapper.vm.isAllowedMinuteCb(35)).toBe(false) - expect(wrapper.vm.isAllowedSecondCb(29)).toBe(false) - expect(wrapper.vm.isAllowedSecondCb(0)).toBe(false) - expect(wrapper.vm.isAllowedSecondCb(30)).toBe(false) - wrapper.vm.inputMinute = 30 - expect(wrapper.vm.isAllowedSecondCb(0)).toBe(true) - expect(wrapper.vm.isAllowedSecondCb(29)).toBe(false) - expect(wrapper.vm.isAllowedSecondCb(30)).toBe(false) - wrapper.vm.inputMinute = 31 - expect(wrapper.vm.isAllowedSecondCb(29)).toBe(false) - expect(wrapper.vm.isAllowedSecondCb(30)).toBe(false) - wrapper.vm.inputMinute = 35 - expect(wrapper.vm.isAllowedSecondCb(29)).toBe(false) - expect(wrapper.vm.isAllowedSecondCb(30)).toBe(false) - - wrapper.vm.inputHour = 0 - expect(wrapper.vm.isAllowedMinuteCb(30)).toBe(false) - expect(wrapper.vm.isAllowedMinuteCb(35)).toBe(false) - expect(wrapper.vm.isAllowedMinuteCb(50)).toBe(false) - expect(wrapper.vm.isAllowedSecondCb(0)).toBe(false) - expect(wrapper.vm.isAllowedSecondCb(30)).toBe(false) - }) - - it('should update inputSecond when called setInputData' + useSecondsDesc, () => { - const wrapper = mountFunction({ - propsData: { - useSeconds: useSecondsValue, - }, - }) - const currentValue = jest.fn() - wrapper.vm.$on('input', currentValue) - wrapper.vm.emitValue() - - wrapper.vm.setInputData(useSecondsValue ? '01:23:45' : '01:23') - expect(wrapper.vm.inputSecond).toBe(useSecondsValue ? 45 : 0) - wrapper.vm.setInputData(null) - expect(wrapper.vm.inputSecond).toBeNull() - }) - - it('should update when emit input' + useSecondsDesc, () => { - const wrapper = mountFunction({ - propsData: { - value: '01:23:45pm', - format: 'ampm', - useSeconds: useSecondsValue, - }, - }) - const currentValue = jest.fn() - wrapper.vm.$on('input', currentValue) - wrapper.vm.emitValue() - expect(currentValue).toHaveBeenCalledWith(useSecondsValue ? '13:23:45' : '13:23') - }) - - it('should update selecting when set selectingSecond/selectingMinute/selectingHour' + useSecondsDesc, () => { - const wrapper = mountFunction({ - propsData: { - useSeconds: useSecondsValue, - }, - }) - wrapper.vm.selectingHour = true - expect(wrapper.vm.selecting).toBe(SelectingTimes.Hour) - expect(wrapper.vm.selectingMinute).toBe(false) - expect(wrapper.vm.selectingSecond).toBe(false) - - wrapper.vm.selectingMinute = true - expect(wrapper.vm.selecting).toBe(SelectingTimes.Minute) - expect(wrapper.vm.selectingHour).toBe(false) - expect(wrapper.vm.selectingSecond).toBe(false) - - wrapper.vm.selectingSecond = true - expect(wrapper.vm.selecting).toBe(SelectingTimes.Second) - expect(wrapper.vm.selectingHour).toBe(false) - expect(wrapper.vm.selectingMinute).toBe(false) - }) - }) -}) diff --git a/packages/vuetify/src/components/VTimePicker/__tests__/VTimePickerClock.spec.ts b/packages/vuetify/src/components/VTimePicker/__tests__/VTimePickerClock.spec.ts deleted file mode 100644 index 251d2930598..00000000000 --- a/packages/vuetify/src/components/VTimePicker/__tests__/VTimePickerClock.spec.ts +++ /dev/null @@ -1,330 +0,0 @@ -// @ts-nocheck -/* eslint-disable */ - -// import VTimePickerClock from '../VTimePickerClock' -// import { touch } from '../../../../test' -import { mount } from '@vue/test-utils' -import { describe, expect, it } from '@jest/globals' - -const CLOCK_SIZE = 300 -const INNER_SIZE = 246 - -describe.skip('VTimePickerClock.js', () => { - (window as any).TouchEvent = Event - - function anglePosition (angle: number): [number, number] { - return [ - CLOCK_SIZE / 2 + INNER_SIZE / 2 * Math.sin(angle / (180 / Math.PI)), - CLOCK_SIZE / 2 - INNER_SIZE / 2 * Math.cos(angle / (180 / Math.PI)), - ] - } - - function createBoundingRect (wrapper) { - wrapper.vm.$refs.clock.getBoundingClientRect = () => { - return { - width: CLOCK_SIZE, - height: CLOCK_SIZE, - top: 0, - left: 0, - right: CLOCK_SIZE, - bottom: CLOCK_SIZE, - x: 0, - y: 0, - } - } - - wrapper.vm.$refs.innerClock.getBoundingClientRect = () => { - return { - width: INNER_SIZE, - height: INNER_SIZE, - top: 0, - left: 0, - right: INNER_SIZE, - bottom: INNER_SIZE, - x: 0, - y: 0, - } - } - } - - it('should render component', () => { - const wrapper = mount(VTimePickerClock, { - propsData: { - allowedValues: n => n % 2, - max: 59, - min: 0, - step: 5, - value: 10, - }, - }) - - expect(wrapper.html()).toMatchSnapshot() - }) - - it('should render disabled component', () => { - const wrapper = mount(VTimePickerClock, { - propsData: { - allowedValues: n => n % 2, - disabled: true, - max: 59, - min: 0, - step: 5, - value: 10, - }, - }) - - expect(wrapper.html()).toMatchSnapshot() - }) - - it('should render component with double prop', () => { - const wrapper = mount(VTimePickerClock, { - propsData: { - allowedValues: n => n % 2, - double: true, - max: 59, - min: 0, - step: 5, - value: 10, - }, - }) - - expect(wrapper.html()).toMatchSnapshot() - }) - - it('should emit input event on wheel if scrollable', () => { - const wrapper = mount(VTimePickerClock, { - propsData: { - max: 59, - min: 3, - value: 59, - scrollable: true, - }, - }) - - const input = jest.fn() - wrapper.vm.$on('input', input) - wrapper.trigger('wheel') - expect(input).toHaveBeenCalledWith(3) - }) - - it('should not emit input event on wheel if readonly and scrollable', () => { - const wrapper = mount(VTimePickerClock, { - propsData: { - max: 10, - min: 1, - value: 6, - scrollable: true, - readonly: true, - }, - }) - - const input = jest.fn() - wrapper.vm.$on('input', input) - wrapper.trigger('wheel') - expect(input).not.toHaveBeenCalled() - }) - - it('should emit input event on wheel if scrollable and has allowedValues', () => { - const wrapper = mount(VTimePickerClock, { - propsData: { - max: 10, - min: 1, - value: 6, - scrollable: true, - allowedValues: val => !(val % 3), - }, - }) - - const input = jest.fn() - wrapper.vm.$on('input', input) - wrapper.trigger('wheel') - expect(input).toHaveBeenCalledWith(9) - }) - - it('should not emit input event on wheel if not scrollable', () => { - const wrapper = mount(VTimePickerClock, { - propsData: { - max: 59, - min: 3, - value: 59, - scrollable: false, - }, - }) - - const input = jest.fn() - wrapper.vm.$on('input', input) - wrapper.trigger('wheel') - expect(input).not.toHaveBeenCalled() - }) - - it('should emit change event on mouseup/touchend', () => { - const wrapper = mount(VTimePickerClock, { - propsData: { - value: 59, - min: 0, - max: 60, - }, - }) - - const change = jest.fn() - wrapper.vm.$on('change', change) - - wrapper.vm.valueOnMouseUp = 55 - wrapper.trigger('mouseup') - expect(change).toHaveBeenCalledWith(55) - - wrapper.trigger('touchend') - expect(change).toHaveBeenCalledWith(55) - }) - - it('should not emit change event on mouseup/touchend if readonly', () => { - const wrapper = mount(VTimePickerClock, { - propsData: { - value: 59, - min: 0, - max: 60, - readonly: true, - }, - }) - - const change = jest.fn() - wrapper.vm.$on('change', change) - - wrapper.vm.valueOnMouseUp = 55 - wrapper.trigger('mouseup') - expect(change).not.toHaveBeenCalled() - - wrapper.trigger('touchend') - expect(change).not.toHaveBeenCalled() - }) - - it('should emit change event on mouseleave', () => { - const wrapper = mount(VTimePickerClock, { - propsData: { - value: 59, - min: 0, - max: 60, - }, - }) - - const change = jest.fn() - wrapper.vm.$on('change', change) - - wrapper.trigger('mouseleave') - expect(change).not.toHaveBeenCalled() - - wrapper.vm.isDragging = true - wrapper.vm.valueOnMouseUp = 58 - wrapper.trigger('mouseleave') - expect(change).toHaveBeenCalledWith(58) - }) - - it('should calculate angle', () => { - const wrapper = mount(VTimePickerClock, { - propsData: { - value: 59, - min: 0, - max: 60, - }, - }) - - const center = { x: 1, y: 1 } - const angle = p => Math.round(wrapper.vm.angle(center, p)) - expect(angle({ x: 2, y: 1 })).toBe(90) - expect(angle({ x: 2, y: 2 })).toBe(45) - expect(angle({ x: 0, y: 2 })).toBe(315) - expect(angle({ x: 0, y: 0 })).toBe(225) - expect(angle({ x: 2, y: 0 })).toBe(135) - }) - - it('should calculate position from angle', () => { - const wrapper = mount(VTimePickerClock, { - propsData: { - min: 0, - max: 6, - }, - }) - - createBoundingRect(wrapper) - - const degreesPerUnit = wrapper.vm.degreesPerUnit - const input = jest.fn() - - touch(wrapper).start(0, 0) - wrapper.vm.$on('input', input) - - for (let i = 0; i <= 6; i++) { - touch(wrapper).move(...anglePosition(i * degreesPerUnit)) - expect(input).toHaveBeenCalledWith(i) - } - }) - - it('should not emit input event when clicked disabled value (#5897)', () => { - const ALLOWED_VALUE = 15 - const ACCURACY_ANGLE = 15 - const wrapper = mount(VTimePickerClock, { - propsData: { - allowedValues: value => value === ALLOWED_VALUE || value === 60 - ALLOWED_VALUE, - min: 0, - max: 59, - }, - }) - - createBoundingRect(wrapper) - - const degreesPerUnit = wrapper.vm.degreesPerUnit - const input = jest.fn() - - touch(wrapper).start(0, 0) - wrapper.vm.$on('input', input) - - // Click on disabled value and more than 15 degrees away from the allowed value - touch(wrapper).move(...anglePosition(ALLOWED_VALUE * degreesPerUnit + ACCURACY_ANGLE + 1)) - expect(input).not.toHaveBeenCalled() - touch(wrapper).move(...anglePosition(ALLOWED_VALUE * degreesPerUnit - ACCURACY_ANGLE - 1)) - expect(input).not.toHaveBeenCalled() - - // Click on disabled value and less than 15 degrees away from the allowed value - touch(wrapper).move(...anglePosition(ALLOWED_VALUE * degreesPerUnit + ACCURACY_ANGLE - 1)) - expect(input).toHaveBeenCalledTimes(1) - expect(input).toHaveBeenCalledWith(ALLOWED_VALUE) - touch(wrapper).move(...anglePosition((60 - ALLOWED_VALUE) * degreesPerUnit + ACCURACY_ANGLE - 1)) - expect(input).toHaveBeenCalledTimes(2) - expect(input).toHaveBeenCalledWith(60 - ALLOWED_VALUE) - touch(wrapper).move(...anglePosition(ALLOWED_VALUE * degreesPerUnit - ACCURACY_ANGLE + 1)) - expect(input).toHaveBeenCalledTimes(3) - expect(input).toHaveBeenCalledWith(ALLOWED_VALUE) - }) - - it('should change with touch move', () => { - const wrapper = mount(VTimePickerClock, { - propsData: { - min: 0, - max: 7, - value: 0, - double: true, - }, - }) - - createBoundingRect(wrapper) - - const input = jest.fn() - const finger = touch(wrapper).start(0, 0) - wrapper.vm.$on('input', input) - - finger.move(300, 150) - expect(input).toHaveBeenCalledWith(1) - finger.move(150, 250) - expect(input).toHaveBeenCalledWith(2) - finger.move(150, 249) - expect(input).toHaveBeenCalledWith(6) - - // edge case - finger.move(120, 0) - expect(input).toHaveBeenCalledWith(0) - finger.move(135, 90) - expect(input).toHaveBeenCalledWith(4) - finger.move(90, 135) - expect(input).toHaveBeenCalledWith(7) - }) -}) diff --git a/packages/vuetify/src/components/VTimePicker/__tests__/VTimePickerTitle.spec.ts b/packages/vuetify/src/components/VTimePicker/__tests__/VTimePickerTitle.spec.ts deleted file mode 100644 index 35529c100e3..00000000000 --- a/packages/vuetify/src/components/VTimePicker/__tests__/VTimePickerTitle.spec.ts +++ /dev/null @@ -1,186 +0,0 @@ -// @ts-nocheck -/* eslint-disable */ - -// import Vue from 'vue' -// import VTimePickerTitle from '../VTimePickerTitle' -// import { SelectingTimes } from '../VTimePicker' -// import { Lang } from '../../../services/lang' -// import { preset } from '../../../presets/default' -import { - mount, - Wrapper, - MountOptions, -} from '@vue/test-utils' - -// Vue.prototype.$vuetify = { -// lang: new Lang(preset), -// } - -describe.skip('VTimePickerTitle.ts', () => { - type Instance = InstanceType - let mountFunction: (options?: MountOptions) => Wrapper - beforeEach(() => { - mountFunction = (options?: MountOptions) => { - return mount(VTimePickerTitle, options) - } - }); - - [true, false].forEach(useSecondsValue => { - const useSecondsDesc = (useSecondsValue ? '. with useSeconds' : '') - it('should render component in 24hr' + useSecondsDesc, () => { - const wrapper = mountFunction({ - propsData: { - hour: 14, - minute: 13, - second: 25, - period: 'pm', - ampm: false, - useSeconds: useSecondsValue, - }, - }) - - expect(wrapper.html()).toMatchSnapshot() - }) - - it('should render disabled component' + useSecondsDesc, () => { - const wrapper = mountFunction({ - propsData: { - disabled: true, - hour: 14, - minute: 13, - period: 'pm', - ampm: true, - useSeconds: useSecondsValue, - }, - }) - - expect(wrapper.html()).toMatchSnapshot() - }) - - it('should render component in 12hr' + useSecondsDesc, () => { - const wrapper = mountFunction({ - propsData: { - hour: 14, - minute: 13, - second: 25, - period: 'pm', - ampm: true, - useSeconds: useSecondsValue, - }, - }) - - expect(wrapper.html()).toMatchSnapshot() - }) - - it('should render component when selecting hour' + useSecondsDesc, () => { - const wrapper = mountFunction({ - propsData: { - hour: 14, - minute: 13, - second: 25, - period: 'pm', - selecting: SelectingTimes.Hour, - useSeconds: useSecondsValue, - }, - }) - - expect(wrapper.html()).toMatchSnapshot() - }) - - it('should emit event when clicked on am/pm' + useSecondsDesc, async () => { - const wrapper = mountFunction({ - propsData: { - hour: 14, - minute: 13, - second: 25, - period: 'pm', - ampm: true, - useSeconds: useSecondsValue, - }, - }) - - const period = jest.fn() - wrapper.vm.$on('update:period', period) - - wrapper.find('.v-time-picker-title__ampm .v-picker__title__btn--active').trigger('click') - expect(period).not.toHaveBeenCalled() - wrapper.find('.v-time-picker-title__ampm .v-picker__title__btn:not(.v-picker__title__btn--active)').trigger('click') - expect(period).toHaveBeenCalledWith('am') - - wrapper.setProps({ - hour: 2, - minute: 13, - second: 35, - period: 'am', - }) - wrapper.find('.v-time-picker-title__ampm .v-picker__title__btn:not(.v-picker__title__btn--active)').trigger('click') - expect(period).toHaveBeenCalledWith('pm') - }) - - it('should not emit event when clicked on readonly am/pm' + useSecondsDesc, async () => { - const wrapper = mountFunction({ - propsData: { - hour: 14, - minute: 13, - second: 25, - period: 'pm', - ampm: true, - readonly: true, - useSeconds: useSecondsValue, - }, - }) - - const period = jest.fn() - wrapper.vm.$on('update:period', period) - - wrapper.find('.v-time-picker-title__ampm .v-picker__title__btn:not(.v-picker__title__btn--active)').trigger('click') - expect(period).not.toHaveBeenCalled() - }) - - it('should emit event when clicked on hours/minutes/seconds' + useSecondsDesc, async () => { - const wrapper = mountFunction({ - propsData: { - hour: 14, - minute: 13, - second: 25, - period: 'pm', - useSeconds: useSecondsValue, - }, - }) - - const selecting = jest.fn() - wrapper.vm.$on('update:selecting', selecting) - - wrapper.findAll('.v-time-picker-title__time .v-picker__title__btn').at(1).trigger('click') - expect(selecting).toHaveBeenCalledWith(SelectingTimes.Minute) - wrapper.findAll('.v-time-picker-title__time .v-picker__title__btn').at(0).trigger('click') - expect(selecting).toHaveBeenCalledWith(SelectingTimes.Hour) - if (useSecondsValue) { - wrapper.findAll('.v-time-picker-title__time .v-picker__title__btn').at(2).trigger('click') - expect(selecting).toHaveBeenCalledWith(SelectingTimes.Second) - } - wrapper.setProps({ selecting: SelectingTimes.Hour }) - await wrapper.vm.$nextTick() - wrapper.findAll('.v-time-picker-title__time .v-picker__title__btn').at(1).trigger('click') - expect(selecting).toHaveBeenCalledWith(SelectingTimes.Minute) - }) - - it('should emit event when clicked on readonly hours/minutes' + useSecondsDesc, async () => { - const wrapper = mountFunction({ - propsData: { - hour: 14, - minute: 13, - period: 'pm', - readonly: true, - useSeconds: useSecondsValue, - }, - }) - - const selecting = jest.fn() - wrapper.vm.$on('update:selecting', selecting) - - wrapper.find('.v-time-picker-title__time .v-picker__title__btn').trigger('click') - expect(selecting).toHaveBeenCalledWith(SelectingTimes.Hour) - }) - }) -}) diff --git a/packages/vuetify/src/components/VTimePicker/__tests__/__snapshots__/VTimePicker.spec.ts.snap b/packages/vuetify/src/components/VTimePicker/__tests__/__snapshots__/VTimePicker.spec.ts.snap deleted file mode 100644 index 9c8dc4863b2..00000000000 --- a/packages/vuetify/src/components/VTimePicker/__tests__/__snapshots__/VTimePicker.spec.ts.snap +++ /dev/null @@ -1,2681 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`VTimePicker.ts should accept a date object for a value 1`] = ` -
-
-
-
-
- 12 -
- - : - -
- 00 -
-
-
-
- AM -
-
-
-
-
-
-
-
- AM -
-
- PM -
-
-
-
-
-
- - - 12 - - - - - 1 - - - - - 2 - - - - - 3 - - - - - 4 - - - - - 5 - - - - - 6 - - - - - 7 - - - - - 8 - - - - - 9 - - - - - 10 - - - - - 11 - - -
-
-
-
-
-`; - -exports[`VTimePicker.ts should accept a date object for a value. with useSeconds 1`] = ` -
-
-
-
-
- 12 -
- - : - -
- 00 -
- - : - -
- 00 -
-
-
-
- AM -
-
-
-
-
-
-
-
- AM -
-
- PM -
-
-
-
-
-
- - - 12 - - - - - 1 - - - - - 2 - - - - - 3 - - - - - 4 - - - - - 5 - - - - - 6 - - - - - 7 - - - - - 8 - - - - - 9 - - - - - 10 - - - - - 11 - - -
-
-
-
-
-`; - -exports[`VTimePicker.ts should accept a value 1`] = ` -
-
-
-
-
- 9 -
- - : - -
- 12 -
-
-
-
- AM -
-
-
-
-
-
-
-
- AM -
-
- PM -
-
-
-
-
-
- - - 12 - - - - - 1 - - - - - 2 - - - - - 3 - - - - - 4 - - - - - 5 - - - - - 6 - - - - - 7 - - - - - 8 - - - - - 9 - - - - - 10 - - - - - 11 - - -
-
-
-
-
-`; - -exports[`VTimePicker.ts should accept a value. with useSeconds 1`] = ` -
-
-
-
-
- 9 -
- - : - -
- 12 -
- - : - -
- 34 -
-
-
-
- AM -
-
-
-
-
-
-
-
- AM -
-
- PM -
-
-
-
-
-
- - - 12 - - - - - 1 - - - - - 2 - - - - - 3 - - - - - 4 - - - - - 5 - - - - - 6 - - - - - 7 - - - - - 8 - - - - - 9 - - - - - 10 - - - - - 11 - - -
-
-
-
-
-`; - -exports[`VTimePicker.ts should change am/pm when updated from model 1`] = ` -
-
-
-
-
- 9 -
- - : - -
- 00 -
-
-
-
- PM -
-
-
-
-
-
-
-
- AM -
-
- PM -
-
-
-
-
-
- - - 12 - - - - - 1 - - - - - 2 - - - - - 3 - - - - - 4 - - - - - 5 - - - - - 6 - - - - - 7 - - - - - 8 - - - - - 9 - - - - - 10 - - - - - 11 - - -
-
-
-
-
-`; - -exports[`VTimePicker.ts should change am/pm when updated from model. with useSeconds 1`] = ` -
-
-
-
-
- 9 -
- - : - -
- 00 -
- - : - -
- 12 -
-
-
-
- PM -
-
-
-
-
-
-
-
- AM -
-
- PM -
-
-
-
-
-
- - - 12 - - - - - 1 - - - - - 2 - - - - - 3 - - - - - 4 - - - - - 5 - - - - - 6 - - - - - 7 - - - - - 8 - - - - - 9 - - - - - 10 - - - - - 11 - - -
-
-
-
-
-`; - -exports[`VTimePicker.ts should render colored time picker 1`] = ` -
-
-
-
-
- 9 -
- - : - -
- 00 -
-
-
-
- AM -
-
-
-
-
-
-
-
- AM -
-
- PM -
-
-
-
-
-
- - - 12 - - - - - 1 - - - - - 2 - - - - - 3 - - - - - 4 - - - - - 5 - - - - - 6 - - - - - 7 - - - - - 8 - - - - - 9 - - - - - 10 - - - - - 11 - - -
-
-
-
-
-`; - -exports[`VTimePicker.ts should render colored time picker, header 1`] = ` -
-
-
-
-
- 9 -
- - : - -
- 00 -
-
-
-
- AM -
-
-
-
-
-
-
-
- AM -
-
- PM -
-
-
-
-
-
- - - 12 - - - - - 1 - - - - - 2 - - - - - 3 - - - - - 4 - - - - - 5 - - - - - 6 - - - - - 7 - - - - - 8 - - - - - 9 - - - - - 10 - - - - - 11 - - -
-
-
-
-
-`; - -exports[`VTimePicker.ts should render colored time picker, header. with useSeconds 1`] = ` -
-
-
-
-
- 9 -
- - : - -
- 00 -
- - : - -
- 00 -
-
-
-
- AM -
-
-
-
-
-
-
-
- AM -
-
- PM -
-
-
-
-
-
- - - 12 - - - - - 1 - - - - - 2 - - - - - 3 - - - - - 4 - - - - - 5 - - - - - 6 - - - - - 7 - - - - - 8 - - - - - 9 - - - - - 10 - - - - - 11 - - -
-
-
-
-
-`; - -exports[`VTimePicker.ts should render colored time picker. with useSeconds 1`] = ` -
-
-
-
-
- 9 -
- - : - -
- 00 -
- - : - -
- 00 -
-
-
-
- AM -
-
-
-
-
-
-
-
- AM -
-
- PM -
-
-
-
-
-
- - - 12 - - - - - 1 - - - - - 2 - - - - - 3 - - - - - 4 - - - - - 5 - - - - - 6 - - - - - 7 - - - - - 8 - - - - - 9 - - - - - 10 - - - - - 11 - - -
-
-
-
-
-`; - -exports[`VTimePicker.ts should render component with elevation 1`] = ` -
-
-
-
-
- 9 -
- - : - -
- 12 -
-
-
-
- AM -
-
-
-
-
-
-
-
- AM -
-
- PM -
-
-
-
-
-
- - - 12 - - - - - 1 - - - - - 2 - - - - - 3 - - - - - 4 - - - - - 5 - - - - - 6 - - - - - 7 - - - - - 8 - - - - - 9 - - - - - 10 - - - - - 11 - - -
-
-
-
-
-`; - -exports[`VTimePicker.ts should render component with elevation. with useSeconds 1`] = ` -
-
-
-
-
- 9 -
- - : - -
- 12 -
- - : - -
- 34 -
-
-
-
- AM -
-
-
-
-
-
-
-
- AM -
-
- PM -
-
-
-
-
-
- - - 12 - - - - - 1 - - - - - 2 - - - - - 3 - - - - - 4 - - - - - 5 - - - - - 6 - - - - - 7 - - - - - 8 - - - - - 9 - - - - - 10 - - - - - 11 - - -
-
-
-
-
-`; - -exports[`VTimePicker.ts should render dark time picker 1`] = ` -
-
-
-
-
- -- -
- - : - -
- -- -
-
-
-
- AM -
-
-
-
-
-
-
-
- AM -
-
- PM -
-
-
-
-
-
- - - 12 - - - - - 1 - - - - - 2 - - - - - 3 - - - - - 4 - - - - - 5 - - - - - 6 - - - - - 7 - - - - - 8 - - - - - 9 - - - - - 10 - - - - - 11 - - -
-
-
-
-
-`; - -exports[`VTimePicker.ts should render dark time picker. with useSeconds 1`] = ` -
-
-
-
-
- -- -
- - : - -
- -- -
- - : - -
- -- -
-
-
-
- AM -
-
-
-
-
-
-
-
- AM -
-
- PM -
-
-
-
-
-
- - - 12 - - - - - 1 - - - - - 2 - - - - - 3 - - - - - 4 - - - - - 5 - - - - - 6 - - - - - 7 - - - - - 8 - - - - - 9 - - - - - 10 - - - - - 11 - - -
-
-
-
-
-`; - -exports[`VTimePicker.ts should render disabled component 1`] = ` -
-
-
-
-
- 9 -
- - : - -
- 12 -
-
-
-
- AM -
-
-
-
-
-
-
-
- AM -
-
- PM -
-
-
-
-
-
- - - 12 - - - - - 1 - - - - - 2 - - - - - 3 - - - - - 4 - - - - - 5 - - - - - 6 - - - - - 7 - - - - - 8 - - - - - 9 - - - - - 10 - - - - - 11 - - -
-
-
-
-
-`; - -exports[`VTimePicker.ts should render disabled component. with useSeconds 1`] = ` -
-
-
-
-
- 9 -
- - : - -
- 12 -
- - : - -
- 34 -
-
-
-
- AM -
-
-
-
-
-
-
-
- AM -
-
- PM -
-
-
-
-
-
- - - 12 - - - - - 1 - - - - - 2 - - - - - 3 - - - - - 4 - - - - - 5 - - - - - 6 - - - - - 7 - - - - - 8 - - - - - 9 - - - - - 10 - - - - - 11 - - -
-
-
-
-
-`; - -exports[`VTimePicker.ts should render flat component 1`] = ` -
-
-
-
-
- 9 -
- - : - -
- 12 -
-
-
-
- AM -
-
-
-
-
-
-
-
- AM -
-
- PM -
-
-
-
-
-
- - - 12 - - - - - 1 - - - - - 2 - - - - - 3 - - - - - 4 - - - - - 5 - - - - - 6 - - - - - 7 - - - - - 8 - - - - - 9 - - - - - 10 - - - - - 11 - - -
-
-
-
-
-`; - -exports[`VTimePicker.ts should render flat component. with useSeconds 1`] = ` -
-
-
-
-
- 9 -
- - : - -
- 12 -
- - : - -
- 34 -
-
-
-
- AM -
-
-
-
-
-
-
-
- AM -
-
- PM -
-
-
-
-
-
- - - 12 - - - - - 1 - - - - - 2 - - - - - 3 - - - - - 4 - - - - - 5 - - - - - 6 - - - - - 7 - - - - - 8 - - - - - 9 - - - - - 10 - - - - - 11 - - -
-
-
-
-
-`; - -exports[`VTimePicker.ts should render landscape component 1`] = ` -
-
-
-
-
- 9 -
- - : - -
- 12 -
-
-
-
- AM -
-
-
-
-
-
-
-
- AM -
-
- PM -
-
-
-
-
-
- - - 12 - - - - - 1 - - - - - 2 - - - - - 3 - - - - - 4 - - - - - 5 - - - - - 6 - - - - - 7 - - - - - 8 - - - - - 9 - - - - - 10 - - - - - 11 - - -
-
-
-
-
-`; - -exports[`VTimePicker.ts should render landscape component. with useSeconds 1`] = ` -
-
-
-
-
- 9 -
- - : - -
- 12 -
- - : - -
- 34 -
-
-
-
- AM -
-
-
-
-
-
-
-
- AM -
-
- PM -
-
-
-
-
-
- - - 12 - - - - - 1 - - - - - 2 - - - - - 3 - - - - - 4 - - - - - 5 - - - - - 6 - - - - - 7 - - - - - 8 - - - - - 9 - - - - - 10 - - - - - 11 - - -
-
-
-
-
-`; diff --git a/packages/vuetify/src/components/VTimePicker/__tests__/__snapshots__/VTimePickerClock.spec.ts.snap b/packages/vuetify/src/components/VTimePicker/__tests__/__snapshots__/VTimePickerClock.spec.ts.snap deleted file mode 100644 index 5e8c6c9304d..00000000000 --- a/packages/vuetify/src/components/VTimePicker/__tests__/__snapshots__/VTimePickerClock.spec.ts.snap +++ /dev/null @@ -1,286 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`VTimePickerClock.js should render component 1`] = ` -
-
-
-
- - - 0 - - - - - 5 - - - - - 10 - - - - - 15 - - - - - 20 - - - - - 25 - - - - - 30 - - - - - 35 - - - - - 40 - - - - - 45 - - - - - 50 - - - - - 55 - - -
-
-`; - -exports[`VTimePickerClock.js should render component with double prop 1`] = ` -
-
-
-
- - - 0 - - - - - 5 - - - - - 10 - - - - - 15 - - - - - 20 - - - - - 25 - - - - - 30 - - - - - 35 - - - - - 40 - - - - - 45 - - - - - 50 - - - - - 55 - - -
-
-`; - -exports[`VTimePickerClock.js should render disabled component 1`] = ` -
-
-
-
- - - 0 - - - - - 5 - - - - - 10 - - - - - 15 - - - - - 20 - - - - - 25 - - - - - 30 - - - - - 35 - - - - - 40 - - - - - 45 - - - - - 50 - - - - - 55 - - -
-
-`; diff --git a/packages/vuetify/src/components/VTimePicker/__tests__/__snapshots__/VTimePickerTitle.spec.ts.snap b/packages/vuetify/src/components/VTimePicker/__tests__/__snapshots__/VTimePickerTitle.spec.ts.snap deleted file mode 100644 index 6fadbfc25b9..00000000000 --- a/packages/vuetify/src/components/VTimePicker/__tests__/__snapshots__/VTimePickerTitle.spec.ts.snap +++ /dev/null @@ -1,185 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`VTimePickerTitle.ts should render component in 12hr 1`] = ` -
-
-
- 2 -
- - : - -
- 13 -
-
-
-
- AM -
-
- PM -
-
-
-`; - -exports[`VTimePickerTitle.ts should render component in 12hr. with useSeconds 1`] = ` -
-
-
- 2 -
- - : - -
- 13 -
- - : - -
- 25 -
-
-
-
- AM -
-
- PM -
-
-
-`; - -exports[`VTimePickerTitle.ts should render component in 24hr 1`] = ` -
-
-
- 14 -
- - : - -
- 13 -
-
-
-`; - -exports[`VTimePickerTitle.ts should render component in 24hr. with useSeconds 1`] = ` -
-
-
- 14 -
- - : - -
- 13 -
- - : - -
- 25 -
-
-
-`; - -exports[`VTimePickerTitle.ts should render component when selecting hour 1`] = ` -
-
-
- 14 -
- - : - -
- 13 -
-
-
-`; - -exports[`VTimePickerTitle.ts should render component when selecting hour. with useSeconds 1`] = ` -
-
-
- 14 -
- - : - -
- 13 -
- - : - -
- 25 -
-
-
-`; - -exports[`VTimePickerTitle.ts should render disabled component 1`] = ` -
-
-
- 2 -
- - : - -
- 13 -
-
-
-
- AM -
-
- PM -
-
-
-`; - -exports[`VTimePickerTitle.ts should render disabled component. with useSeconds 1`] = ` -
-
-
- 2 -
- - : - -
- 13 -
- - : - -
- -- -
-
-
-
- AM -
-
- PM -
-
-
-`; diff --git a/packages/vuetify/src/components/VTimePicker/_variables.scss b/packages/vuetify/src/components/VTimePicker/_variables.scss deleted file mode 100644 index d72d9ffbd10..00000000000 --- a/packages/vuetify/src/components/VTimePicker/_variables.scss +++ /dev/null @@ -1,32 +0,0 @@ -@import '../../styles/styles.sass'; - -$time-picker-title-color: map-get($shades, 'white') !default; -$time-picker-title-btn-height: 70px !default; -$time-picker-landscape-title-btn-height: 55px !default; -$time-picker-ampm-title-margin-inline: 8px 0 !default; -$time-picker-ampm-title-margin-block: 0 6px !default; -$time-picker-ampm-title-font-size: 16px !default; -$time-picker-landscape-ampm-title-margin: 16px 0 0 !default; -$time-picker-number-font-size: 16px !default; -$time-picker-indicator-size: 40px !default; -$time-picker-clock-padding: 10px !default; -$time-picker-clock-max-width: 290px !default; -$time-picker-clock-hand-height: calc(50% - 4px) !default; -$time-picker-clock-hand-width: 2px !default; -$time-picker-clock-hand-left: calc(50% - 1px) !default; -$time-picker-clock-center-size: 8px !default; -$time-picker-clock-end-size: 10px !default; -$time-picker-clock-end-top: -4px !default; -$time-picker-clock-inner-hand-height: 14px !default; -$time-picker-clock-inner-offset: 27px !default; -$time-picker-ampm-padding: 10px !default; -$time-picker-clock-end-border-width: 2px !default; -$time-picker-clock-end-border-style: solid !default; -$time-picker-clock-end-border-color: inherit !default; - -// Deprecated -$time-picker-ampm-title-margin-start: 8px !default; -$time-picker-ampm-title-margin-bottom: 6px !default; -$time-picker-ampm-title-margin: 0 0 $time-picker-ampm-title-margin-bottom $time-picker-ampm-title-margin-start !default; -$time-picker-ampm-title-margin-ltr: $time-picker-ampm-title-margin !default; -$time-picker-ampm-title-margin-rtl: 0 $time-picker-ampm-title-margin-start $time-picker-ampm-title-margin-bottom 0 !default; diff --git a/packages/vuetify/src/components/VTimePicker/index.ts b/packages/vuetify/src/components/VTimePicker/index.ts deleted file mode 100644 index fec08b7769f..00000000000 --- a/packages/vuetify/src/components/VTimePicker/index.ts +++ /dev/null @@ -1,13 +0,0 @@ -import VTimePicker from './VTimePicker' -import VTimePickerClock from './VTimePickerClock' -import VTimePickerTitle from './VTimePickerTitle' - -export { VTimePicker, VTimePickerClock, VTimePickerTitle } - -export default { - $_vuetify_subcomponents: { - VTimePicker, - VTimePickerClock, - VTimePickerTitle, - }, -} diff --git a/packages/vuetify/src/components/VTimePicker/SelectingTimes.ts b/packages/vuetify/src/labs/VTimePicker/SelectingTimes.ts similarity index 100% rename from packages/vuetify/src/components/VTimePicker/SelectingTimes.ts rename to packages/vuetify/src/labs/VTimePicker/SelectingTimes.ts diff --git a/packages/vuetify/src/labs/VTimePicker/VTimePicker.sass b/packages/vuetify/src/labs/VTimePicker/VTimePicker.sass new file mode 100644 index 00000000000..5fbe6833ed9 --- /dev/null +++ b/packages/vuetify/src/labs/VTimePicker/VTimePicker.sass @@ -0,0 +1,10 @@ +@import './_variables.scss' +@import '../VPicker/_variables.scss' + +.v-time-picker.v-picker + padding: $time-picker-padding + width: $time-picker-width + + .v-picker-title + padding: 0 + margin-bottom: 20px diff --git a/packages/vuetify/src/labs/VTimePicker/VTimePicker.tsx b/packages/vuetify/src/labs/VTimePicker/VTimePicker.tsx new file mode 100644 index 00000000000..589444aa2ab --- /dev/null +++ b/packages/vuetify/src/labs/VTimePicker/VTimePicker.tsx @@ -0,0 +1,363 @@ +// Styles +import './VTimePicker.sass' + +// Components +import { VTimePickerClock } from './VTimePickerClock' +import { VTimePickerControls } from './VTimePickerControls' +import { pad } from '@/components/VDatePicker/util' +import { makeVPickerProps, VPicker } from '@/labs/VPicker/VPicker' + +// Composables +import { useLocale } from '@/composables/locale' + +// Utilities +import { computed, onMounted, ref, watch } from 'vue' +import { SelectingTimes } from './SelectingTimes' +import { createRange, genericComponent, omit, propsFactory, useRender } from '@/util' + +// Types +import type { PropType } from 'vue' +import type { VPickerSlots } from '@/labs/VPicker/VPicker' +type Period = 'am' | 'pm' +type AllowFunction = (val: number) => boolean + +const rangeHours24 = createRange(24) +const rangeHours12am = createRange(12) +const rangeHours12pm = rangeHours12am.map(v => v + 12) +const range60 = createRange(60) +const selectingNames = { 1: 'hour', 2: 'minute', 3: 'second' } + +export { SelectingTimes } + +export type VTimePickerSlots = Omit + +export const makeVTimePickerProps = propsFactory({ + allowedHours: [Function, Array] as PropType, + allowedMinutes: [Function, Array] as PropType, + allowedSeconds: [Function, Array] as PropType, + ampmInTitle: Boolean, + disabled: Boolean, + format: { + type: String as PropType<'ampm' | '24hr'>, + default: 'ampm', + }, + max: String, + min: String, + modelValue: null as any as PropType, + readonly: Boolean, + scrollable: Boolean, + useSeconds: Boolean, + ...omit(makeVPickerProps({ title: '$vuetify.timePicker.title' }), ['landscape']), +}, 'VTimePicker') + +export const VTimePicker = genericComponent()({ + name: 'VTimePicker', + + props: makeVTimePickerProps(), + + emits: { + 'update:hour': (val: number) => val, + 'update:minute': (val: number) => val, + 'update:period': (val: Period) => val, + 'update:second': (val: number) => val, + 'update:modelValue': (val: string) => val, + }, + + setup (props, { emit, slots }) { + const { t } = useLocale() + const inputHour = ref(null as number | null) + const inputMinute = ref(null as number | null) + const inputSecond = ref(null as number | null) + const lazyInputHour = ref(null as number | null) + const lazyInputMinute = ref(null as number | null) + const lazyInputSecond = ref(null as number | null) + const period = ref('am' as Period) + const selecting = ref(SelectingTimes.Hour) + const controlsRef = ref(null) + const clockRef = ref(null) + + const isAllowedHourCb = computed((): AllowFunction => { + let cb: AllowFunction + + if (props.allowedHours instanceof Array) { + cb = (val: number) => (props.allowedHours as number[]).includes(val) + } else { + cb = props.allowedHours as AllowFunction + } + + if (!props.min && !props.max) return cb + + const minHour = props.min ? Number(props.min.split(':')[0]) : 0 + const maxHour = props.max ? Number(props.max.split(':')[0]) : 23 + + return (val: number) => { + return val >= minHour * 1 && + val <= maxHour * 1 && + (!cb || cb(val)) + } + }) + + const isAllowedMinuteCb = computed((): AllowFunction => { + let cb: AllowFunction + + const isHourAllowed = !isAllowedHourCb.value || inputHour.value === null || isAllowedHourCb.value(inputHour.value) + if (props.allowedMinutes instanceof Array) { + cb = (val: number) => (props.allowedMinutes as number[]).includes(val) + } else { + cb = props.allowedMinutes as AllowFunction + } + + if (!props.min && !props.max) { + return isHourAllowed ? cb : () => false + } + + const [minHour, minMinute] = props.min ? props.min.split(':').map(Number) : [0, 0] + const [maxHour, maxMinute] = props.max ? props.max.split(':').map(Number) : [23, 59] + const minTime = minHour * 60 + minMinute * 1 + const maxTime = maxHour * 60 + maxMinute * 1 + + return (val: number) => { + const time = 60 * inputHour.value! + val + return time >= minTime && + time <= maxTime && + isHourAllowed && + (!cb || cb(val)) + } + }) + + const isAllowedSecondCb = computed((): AllowFunction => { + let cb: AllowFunction + + const isHourAllowed = !isAllowedHourCb.value || inputHour.value === null || isAllowedHourCb.value(inputHour.value) + const isMinuteAllowed = isHourAllowed && + (!isAllowedMinuteCb.value || + inputMinute.value === null || + isAllowedMinuteCb.value(inputMinute.value) + ) + + if (props.allowedSeconds instanceof Array) { + cb = (val: number) => (props.allowedSeconds as number[]).includes(val) + } else { + cb = props.allowedSeconds as AllowFunction + } + + if (!props.min && !props.max) { + return isMinuteAllowed ? cb : () => false + } + + const [minHour, minMinute, minSecond] = props.min ? props.min.split(':').map(Number) : [0, 0, 0] + const [maxHour, maxMinute, maxSecond] = props.max ? props.max.split(':').map(Number) : [23, 59, 59] + const minTime = minHour * 3600 + minMinute * 60 + (minSecond || 0) * 1 + const maxTime = maxHour * 3600 + maxMinute * 60 + (maxSecond || 0) * 1 + + return (val: number) => { + const time = 3600 * inputHour.value! + 60 * inputMinute.value! + val + return time >= minTime && + time <= maxTime && + isMinuteAllowed && + (!cb || cb(val)) + } + }) + + const isAmPm = computed((): boolean => { + return props.format === 'ampm' + }) + + watch(() => props.modelValue, val => setInputData(val)) + + onMounted(() => { + setInputData(props.modelValue) + }) + + function genValue () { + if (inputHour.value != null && inputMinute.value != null && (!props.useSeconds || inputSecond.value != null)) { + return `${pad(inputHour.value)}:${pad(inputMinute.value)}` + (props.useSeconds ? `:${pad(inputSecond.value!)}` : '') + } + + return null + } + + function emitValue () { + const value = genValue() + if (value !== null) emit('update:modelValue', value) + } + + function convert24to12 (hour: number) { + return hour ? ((hour - 1) % 12 + 1) : 12 + } + + function convert12to24 (hour: number, period: Period) { + return hour % 12 + (period === 'pm' ? 12 : 0) + } + + function setInputData (value: string | null | Date) { + if (value == null || value === '') { + inputHour.value = null + inputMinute.value = null + inputSecond.value = null + } else if (value instanceof Date) { + inputHour.value = value.getHours() + inputMinute.value = value.getMinutes() + inputSecond.value = value.getSeconds() + } else { + const [hour, , minute, , second, period] = value.trim().toLowerCase().match(/^(\d+):(\d+)(:(\d+))?([ap]m)?$/) || new Array(6) + + inputHour.value = period ? convert12to24(parseInt(hour, 10), period as Period) : parseInt(hour, 10) + inputMinute.value = parseInt(minute, 10) + inputSecond.value = parseInt(second || 0, 10) + } + + period.value = (inputHour.value == null || inputHour.value < 12) ? 'am' : 'pm' + } + + function firstAllowed (type: 'hour' | 'minute' | 'second', value: number) { + const allowedFn = type === 'hour' ? isAllowedHourCb.value : (type === 'minute' ? isAllowedMinuteCb.value : isAllowedSecondCb.value) + if (!allowedFn) return value + + // TODO: clean up (Note from V2 code) + const range = type === 'minute' + ? range60 + : (type === 'second' + ? range60 + : (isAmPm.value + ? (value < 12 + ? rangeHours12am + : rangeHours12pm) + : rangeHours24)) + const first = range.find(v => allowedFn((v + value) % range.length + range[0])) + return ((first || 0) + value) % range.length + range[0] + } + + function setPeriod (val: Period) { + period.value = val + if (inputHour.value != null) { + const newHour = inputHour.value! + (period.value === 'am' ? -12 : 12) + inputHour.value = firstAllowed('hour', newHour) + } + emit('update:period', val) + emitValue() + return true + } + + function onInput (value: number) { + if (selecting.value === SelectingTimes.Hour) { + inputHour.value = isAmPm.value ? convert12to24(value, period.value) : value + } else if (selecting.value === SelectingTimes.Minute) { + inputMinute.value = value + } else { + inputSecond.value = value + } + } + + function onChange (value: number) { + switch (selectingNames[selecting.value]) { + case 'hour': + emit('update:hour', value) + break + case 'minutes': + emit('update:minute', value) + break + case 'seconds': + emit('update:second', value) + break + default: + break + } + + const emitChange = selecting.value === (props.useSeconds ? SelectingTimes.Second : SelectingTimes.Minute) + + if (selecting.value === SelectingTimes.Hour) { + selecting.value = SelectingTimes.Minute + } else if (props.useSeconds && selecting.value === SelectingTimes.Minute) { + selecting.value = SelectingTimes.Second + } + + if (inputHour.value === lazyInputHour.value && + inputMinute.value === lazyInputMinute.value && + (!props.useSeconds || inputSecond.value === lazyInputSecond.value) + ) return + + const time = genValue() + if (time === null) return + + lazyInputHour.value = inputHour.value + lazyInputMinute.value = inputMinute.value + props.useSeconds && (lazyInputSecond.value = inputSecond.value) + + emitChange && emitValue() + } + + useRender(() => { + const pickerProps = VPicker.filterProps(props) + const timePickerControlsProps = VTimePickerControls.filterProps(props) + const timePickerClockProps = VTimePickerClock.filterProps(omit(props, ['format', 'modelValue', 'min', 'max'])) + + return ( + slots.title?.() ?? ( +
+ { t(props.title) } +
+ ), + header: () => ( + setPeriod(val) } + onUpdate:selecting={ (value: 1 | 2 | 3) => (selecting.value = value) } + ref={ controlsRef } + /> + ), + default: () => ( + val) + : (val: number) => pad(val, 2) + } + max={ selecting.value === SelectingTimes.Hour ? (isAmPm.value && period.value === 'am' ? 11 : 23) : 59 } + min={ selecting.value === SelectingTimes.Hour && isAmPm.value && period.value === 'pm' ? 12 : 0 } + size={ 20 } + step={ selecting.value === SelectingTimes.Hour ? 1 : 5 } + modelValue={ selecting.value === SelectingTimes.Hour + ? inputHour.value as number + : (selecting.value === SelectingTimes.Minute + ? inputMinute.value as number + : inputSecond.value as number) + } + onChange={ onChange } + onInput={ onInput } + ref={ clockRef } + /> + ), + actions: slots.actions, + }} + /> + ) + }) + }, +}) + +export type VTimePicker = InstanceType diff --git a/packages/vuetify/src/components/VTimePicker/VTimePickerClock.sass b/packages/vuetify/src/labs/VTimePicker/VTimePickerClock.sass similarity index 69% rename from packages/vuetify/src/components/VTimePicker/VTimePickerClock.sass rename to packages/vuetify/src/labs/VTimePicker/VTimePickerClock.sass index 2693bc377ba..fb61819c4d5 100644 --- a/packages/vuetify/src/components/VTimePicker/VTimePickerClock.sass +++ b/packages/vuetify/src/labs/VTimePicker/VTimePickerClock.sass @@ -1,32 +1,27 @@ @import './_variables.scss' // Theme -+theme(v-time-picker-clock) using ($material) - background: map-deep-get($material, 'picker', 'clock') - - .v-time-picker-clock__item--disabled - color: map-deep-get($material, 'buttons', 'disabled') +.v-time-picker-clock + background: rgb(var(--v-theme-background)) + color: rgb(var(--v-theme-on-background)) - &.v-time-picker-clock__item--active - color: map-deep-get($material-dark, 'buttons', 'disabled') - &--indeterminate - .v-time-picker-clock__hand - background-color: map-deep-get($material, 'picker', 'indeterminateTime') + &:after + color: rgb(var(--v-theme-primary)) - &:after - color: map-deep-get($material, 'picker', 'indeterminateTime') - - .v-time-picker-clock__item--active - background-color: map-deep-get($material, 'picker', 'indeterminateTime') + .v-time-picker-clock__item--active + background-color: rgb(var(--v-theme-surface-variant)) + color: rgb(var(--v-theme-on-surface-variant)) .v-time-picker-clock - border-radius: 100% + margin: 0 auto + background: rgb(var(--v-theme-surface-light)) + border-radius: 50% position: relative - transition: $primary-transition + transition: none user-select: none - width: 100% - padding-top: 100% + height: 256px + width: 256px flex: 1 0 auto &__container @@ -36,20 +31,8 @@ justify-content: center padding: $time-picker-clock-padding - &__ampm - display: flex - flex-direction: row - justify-content: space-between - align-items: flex-end - position: absolute - width: 100% - height: 100% - top: 0 - left: 0 - margin: 0 - padding: $time-picker-ampm-padding - &__hand + background-color: currentColor height: $time-picker-clock-hand-height width: $time-picker-clock-hand-width bottom: 50% @@ -81,14 +64,18 @@ top: 100% left: 50% border-radius: 100% - border-style: solid - border-color: inherit - background-color: inherit + background-color: currentColor transform: translate(-50%, -50%) &--inner:after height: $time-picker-clock-inner-hand-height + &--readonly + pointer-events: none + + .v-time-picker-clock__item--disabled + opacity: var(--v-disabled-opacity) + .v-picker--full-width .v-time-picker-clock__container max-width: $time-picker-clock-max-width @@ -132,7 +119,6 @@ width: $time-picker-indicator-size &--active - color: map-get($shades, 'white') cursor: default z-index: 2 @@ -143,6 +129,3 @@ .v-time-picker-clock &__container flex-direction: row - - &__ampm - flex-direction: column diff --git a/packages/vuetify/src/labs/VTimePicker/VTimePickerClock.tsx b/packages/vuetify/src/labs/VTimePicker/VTimePickerClock.tsx new file mode 100644 index 00000000000..760e10ad30f --- /dev/null +++ b/packages/vuetify/src/labs/VTimePicker/VTimePickerClock.tsx @@ -0,0 +1,286 @@ +// StylesthisValue +// Styles +import './VTimePickerClock.sass' + +// Composables +import { useBackgroundColor, useTextColor } from '@/composables/color' + +// Utilities +import { computed, ref, toRef, watch } from 'vue' +import { genericComponent, propsFactory, useRender } from '@/util' + +// Types +import type { PropType } from 'vue' +interface Point { + x: number + y: number +} + +export const makeVTimePickerClockProps = propsFactory({ + allowedValues: Function as PropType<(value: number) => boolean>, + ampm: Boolean, + color: String, + disabled: Boolean, + displayedValue: { + default: null, + }, + double: Boolean, + format: { + type: Function, + default: (val: string | number) => val, + }, + max: { + type: Number, + required: true, + }, + min: { + type: Number, + required: true, + }, + scrollable: Boolean, + readonly: Boolean, + rotate: { + type: Number, + default: 0, + }, + step: { + type: Number, + default: 1, + }, + modelValue: { + type: Number, + }, +}, 'VTimePickerClock') + +export const VTimePickerClock = genericComponent()({ + name: 'VTimePickerClock', + + props: makeVTimePickerClockProps(), + + emits: { + change: (val: number) => val, + input: (val: number) => val, + }, + + setup (props, { emit }) { + const clockRef = ref(null) + const innerClockRef = ref(null) + const inputValue = ref(undefined) + const isDragging = ref(false) + const valueOnMouseDown = ref(null as number | null) + const valueOnMouseUp = ref(null as number | null) + + const { textColorClasses, textColorStyles } = useTextColor(toRef(props, 'color')) + const { backgroundColorClasses, backgroundColorStyles } = useBackgroundColor(toRef(props, 'color')) + + const count = computed(() => props.max - props.min + 1) + const roundCount = computed(() => props.double ? (count.value / 2) : count.value) + const degreesPerUnit = computed(() => 360 / roundCount.value) + const degrees = computed(() => degreesPerUnit.value * Math.PI / 180) + const displayedValue = computed(() => props.modelValue == null ? props.min : props.modelValue) + const innerRadiusScale = computed(() => 0.62) + + const genChildren = computed(() => { + const children = [] + for (let value = props.min; value <= props.max; value = value + props.step) { + children.push(value) + } + return children + }) + + watch(() => props.modelValue, val => { + inputValue.value = val + }) + + function update (value: number) { + if (inputValue.value !== value) { + inputValue.value = value + } + emit('input', value) + } + + function isAllowed (value: number) { + return !props.allowedValues || props.allowedValues(value) + } + + function wheel (e: WheelEvent) { + e.preventDefault() + + const delta = Math.sign(-e.deltaY || 1) + let value = displayedValue.value + do { + value = value + delta + value = (value - props.min + count.value) % count.value + props.min + } while (!isAllowed(value) && value !== displayedValue.value) + + if (value !== props.displayedValue) { + update(value) + } + } + + function isInner (value: number) { + return props.double && (value - props.min >= roundCount.value) + } + + function handScale (value: number) { + return isInner(value) ? innerRadiusScale.value : 1 + } + + function getPosition (value: number) { + const rotateRadians = props.rotate * Math.PI / 180 + return { + x: Math.sin((value - props.min) * degrees.value + rotateRadians) * handScale(value), + y: -Math.cos((value - props.min) * degrees.value + rotateRadians) * handScale(value), + } + } + + function angleToValue (angle: number, insideClick: boolean): number { + const value = ( + Math.round(angle / degreesPerUnit.value) + + (insideClick ? roundCount.value : 0) + ) % count.value + props.min + + // Necessary to fix edge case when selecting left part of the value(s) at 12 o'clock + if (angle < (360 - degreesPerUnit.value / 2)) return value + + return insideClick ? props.max - roundCount.value + 1 : props.min + } + + function getTransform (i: number) { + const { x, y } = getPosition(i) + return { + left: `${50 + x * 50}%`, + top: `${50 + y * 50}%`, + } + } + + function euclidean (p0: Point, p1: Point) { + const dx = p1.x - p0.x + const dy = p1.y - p0.y + + return Math.sqrt(dx * dx + dy * dy) + } + + function angle (center: Point, p1: Point) { + const value = 2 * Math.atan2(p1.y - center.y - euclidean(center, p1), p1.x - center.x) + return Math.abs(value * 180 / Math.PI) + } + + function setMouseDownValue (value: number) { + if (valueOnMouseDown.value === null) { + valueOnMouseDown.value = value + } + + valueOnMouseUp.value = value + update(value) + } + + function onDragMove (e: MouseEvent | TouchEvent) { + e.preventDefault() + if ((!isDragging.value && e.type !== 'click') || !clockRef.value) return + const { width, top, left } = clockRef.value?.getBoundingClientRect() + const { width: innerWidth }: DOMRect = innerClockRef.value?.getBoundingClientRect() ?? { width: 0 } as DOMRect + const { clientX, clientY } = 'touches' in e ? e.touches[0] : e + const center = { x: width / 2, y: -width / 2 } + const coords = { x: clientX - left, y: top - clientY } + const handAngle = Math.round(angle(center, coords) - props.rotate + 360) % 360 + const insideClick = props.double && euclidean(center, coords) < (innerWidth as number + innerWidth * innerRadiusScale.value) / 4 + const checksCount = Math.ceil(15 / degreesPerUnit.value) + let value + + for (let i = 0; i < checksCount; i++) { + value = angleToValue(handAngle + i * degreesPerUnit.value, insideClick) + if (isAllowed(value)) return setMouseDownValue(value) + + value = angleToValue(handAngle - i * degreesPerUnit.value, insideClick) + if (isAllowed(value)) return setMouseDownValue(value) + } + } + + function onMouseDown (e: MouseEvent | TouchEvent) { + e.preventDefault() + + valueOnMouseDown.value = null + valueOnMouseUp.value = null + isDragging.value = true + onDragMove(e) + } + + function onMouseUp (e: MouseEvent | TouchEvent) { + e.stopPropagation() + + isDragging.value = false + if (valueOnMouseUp.value !== null && isAllowed(valueOnMouseUp.value)) { + emit('change', valueOnMouseUp.value) + } + } + + useRender(() => { + return ( +
(isDragging.value && onMouseUp(e)) } + onTouchstart={ onMouseDown } + onTouchend={ onMouseUp } + onMousemove={ onDragMove } + onTouchmove={ onDragMove } + onWheel={ (e: WheelEvent) => (props.scrollable && wheel(e)) } + ref={ clockRef } + > +
+
+ + { + genChildren.value.map(value => { + const isActive = value === displayedValue.value + + return ( +
+ { props.format(value) } +
+ ) + }) + } +
+
+ ) + }) + }, +}) + +export type VTimePickerClock = InstanceType diff --git a/packages/vuetify/src/labs/VTimePicker/VTimePickerControls.sass b/packages/vuetify/src/labs/VTimePicker/VTimePickerControls.sass new file mode 100644 index 00000000000..00166a6c1f4 --- /dev/null +++ b/packages/vuetify/src/labs/VTimePicker/VTimePickerControls.sass @@ -0,0 +1,102 @@ +@import './_variables.scss' +@import '../VPicker/_variables.scss' + + +.v-time-picker-controls + display: flex + align-items: center + justify-content: center + font-size: .875rem + padding-top: 4px + padding-bottom: 4px + // padding-inline-start: 6px + // padding-inline-end: 12px + margin-bottom: 36px + + &__text + padding-bottom: 12px + + &__time + display: flex + white-space: nowrap + direction: ltr + justify-content: center + + &__btn.v-btn--density-default.v-btn + width: $time-picker-contols-btn-width + height: $time-picker-contols-btn-height + font-size: $time-picker-contols-btn-font + + &__active + background: rgb(var(--v-theme-primary)) + &.v-time-picker-controls__time--with-ampm__btn + width: $time-picker-contols-ampm-btn-width + height: $time-picker-contols-ampm-btn-height + &.v-time-picker-controls__time--with-seconds__btn + // overridden + width: $time-picker-contols-seconds-btn-width + height: $time-picker-contols-seconds-btn-height + font-size: $time-picker-contols-seconds-btn-font + &__separator + font-size: $time-picker-contols-btn-font + height: $time-picker-contols-btn-height + width: $time-picker-contols-separator-width + text-align: center + &__separator.v-time-picker-controls--with-seconds__time__separator + height: $time-picker-contols-seconds-btn-height + font-size: $time-picker-contols-btn-font + +.v-time-picker-controls__ampm + margin-left: 12px + align-self: flex-end + display: flex + flex-direction: column + font-size: $time-picker-ampm-title-font-size + text-transform: uppercase + + &--readonly + pointer-events: none + + &--readonly + .v-picker__title__btn.v-picker__title__btn--active + opacity: $picker-inactive-btn-opacity + + &__btn.v-btn.v-btn--density-default + font-size: $time-picker-ampm-title-font-size + padding: 0 8px + min-width: 52px + height: $time-picker-contols-ampm-height + &.v-time-picker-controls__ampm__am + border-radius: $time-picker-contols-ampm-am-border-radius + border: 1px solid + &.v-time-picker-controls__ampm__pm + border-radius: $time-picker-contols-ampm-pm-border-radius + border: 1px solid + border-top: none + &__active + background: rgb(var(--v-theme-primary)) + +.v-picker__title--landscape + .v-time-picker-controls + flex-direction: column + justify-content: center + height: 100% + + .v-time-picker-controls__time + text-align: right + + .v-picker__title__btn, + span + height: $time-picker-landscape-title-btn-height + font-size: $time-picker-landscape-title-btn-height + + .v-time-picker-controls__ampm + margin: $time-picker-landscape-ampm-title-margin + align-self: initial + text-align: center + +.v-picker--time .v-picker__title--landscape + padding: 0 + + .v-time-picker-controls__time + text-align: center diff --git a/packages/vuetify/src/labs/VTimePicker/VTimePickerControls.tsx b/packages/vuetify/src/labs/VTimePicker/VTimePickerControls.tsx new file mode 100644 index 00000000000..9e25f7557b0 --- /dev/null +++ b/packages/vuetify/src/labs/VTimePicker/VTimePickerControls.tsx @@ -0,0 +1,166 @@ +// Styles +import './VTimePickerControls.sass' + +// Components +import { VBtn } from '@/components/VBtn' +import { pad } from '@/components/VDatePicker/util' + +// Composables +import { useLocale } from '@/composables/locale' + +// Utilities +import { genericComponent, propsFactory, useRender } from '@/util' + +// Types +import { SelectingTimes } from './SelectingTimes' +type Period = 'am' | 'pm' + +export const makeVTimePickerControlsProps = propsFactory({ + ampm: Boolean, + ampmReadonly: Boolean, + color: String, + disabled: Boolean, + hour: Number, + minute: Number, + second: Number, + period: String, + readonly: Boolean, + useSeconds: Boolean, + selecting: Number, + value: Number, +}, 'VTimePickerControls') + +export const VTimePickerControls = genericComponent()({ + name: 'VTimePickerControls', + + props: makeVTimePickerControlsProps(), + + emits: { + 'update:period': (data: Period) => data, + 'update:selecting': (data: 1 | 2 | 3) => data, + }, + + setup (props, { emit, slots }) { + const { t } = useLocale() + + useRender(() => { + let hour = props.hour + if (props.ampm) { + hour = hour ? ((hour - 1) % 12 + 1) : 12 + } + return ( +
+
+ emit('update:selecting', SelectingTimes.Hour) } + /> + + : + + emit('update:selecting', SelectingTimes.Minute) } + /> + + { + props.useSeconds && ( + : + ) + } + + { + props.useSeconds && ( + emit('update:selecting', SelectingTimes.Second) } + class={{ + 'v-time-picker-controls__time__btn': true, + 'v-time-picker-controls__time__btn__active': props.selecting === 3, + 'v-time-picker-controls__time--with-seconds__btn': props.useSeconds, + }} + text={ props.second == null ? '--' : pad(props.second) } + /> + ) + } + + { + props.ampm && ( +
+ props.period !== 'am' ? emit('update:period', 'am') : null } + /> + + props.period !== 'pm' ? emit('update:period', 'pm') : null } + /> +
+ ) + } +
+
+ ) + }) + + return {} + }, + +}) + +export type VTimePickerControls = InstanceType diff --git a/packages/vuetify/src/labs/VTimePicker/_variables.scss b/packages/vuetify/src/labs/VTimePicker/_variables.scss new file mode 100644 index 00000000000..a956d58552c --- /dev/null +++ b/packages/vuetify/src/labs/VTimePicker/_variables.scss @@ -0,0 +1,34 @@ +// @import '../../styles/styles.sass'; + +$time-picker-padding: 24px !default; +$time-picker-contols-btn-font: 56px !default; +$time-picker-contols-btn-height: 80px !default; +$time-picker-contols-btn-width: 96px !default; +$time-picker-contols-seconds-btn-width: 64px !default; +$time-picker-contols-seconds-btn-height: 80px !default; +$time-picker-contols-seconds-btn-font: 40px !default; +$time-picker-contols-separator-width: 24px !default; +$time-picker-contols-ampm-btn-width: 96px !default; +$time-picker-contols-ampm-btn-height: 80px !default; +$time-picker-contols-ampm-height: 40px !default; +$time-picker-contols-ampm-am-border-radius: 4px 4px 0 0 !default; +$time-picker-contols-ampm-pm-border-radius: 0 0 4px 4px !default; +$time-picker-ampm-title-font-size: 18px !default; +$time-picker-number-font-size: 16px !default; +$time-picker-indicator-size: 40px !default; +$time-picker-clock-padding: 10px !default; +$time-picker-clock-max-width: 290px !default; +$time-picker-clock-hand-height: calc(50% - 4px) !default; +$time-picker-clock-hand-width: 2px !default; +$time-picker-clock-hand-left: calc(50% - 1px) !default; +$time-picker-clock-center-size: 8px !default; +$time-picker-clock-end-size: 10px !default; +$time-picker-clock-end-top: -4px !default; +$time-picker-clock-inner-hand-height: 14px !default; +$time-picker-clock-inner-offset: 27px !default; +$time-picker-clock-end-border-width: 2px !default; +$time-picker-clock-end-border-style: solid !default; +$time-picker-clock-end-border-color: currentColor !default; +$time-picker-width: 328px !default; +$time-picker-landscape-title-btn-height: 55px !default; +$time-picker-landscape-ampm-title-margin: 16px 0 0 !default; diff --git a/packages/vuetify/src/labs/VTimePicker/index.ts b/packages/vuetify/src/labs/VTimePicker/index.ts new file mode 100644 index 00000000000..a0782a8c501 --- /dev/null +++ b/packages/vuetify/src/labs/VTimePicker/index.ts @@ -0,0 +1,3 @@ +export { VTimePicker } from './VTimePicker' +export { VTimePickerClock } from './VTimePickerClock' +export { VTimePickerControls } from './VTimePickerControls' diff --git a/packages/vuetify/src/labs/components.ts b/packages/vuetify/src/labs/components.ts index 695e5631bf6..cf560f71506 100644 --- a/packages/vuetify/src/labs/components.ts +++ b/packages/vuetify/src/labs/components.ts @@ -5,4 +5,5 @@ export * from './VFab' export * from './VPicker' export * from './VSparkline' export * from './VSpeedDial' +export * from './VTimePicker' export * from './VTreeview' diff --git a/packages/vuetify/src/locale/af.ts b/packages/vuetify/src/locale/af.ts index fe1f1fc1f04..16b8461da6e 100644 --- a/packages/vuetify/src/locale/af.ts +++ b/packages/vuetify/src/locale/af.ts @@ -71,6 +71,7 @@ export default { timePicker: { am: 'AM', pm: 'PM', + title: 'Select Time', }, pagination: { ariaLabel: { diff --git a/packages/vuetify/src/locale/ar.ts b/packages/vuetify/src/locale/ar.ts index d026772887e..b265d61335a 100644 --- a/packages/vuetify/src/locale/ar.ts +++ b/packages/vuetify/src/locale/ar.ts @@ -71,6 +71,7 @@ export default { timePicker: { am: 'صباحاً', pm: 'مساءً', + title: 'Select Time', }, pagination: { ariaLabel: { diff --git a/packages/vuetify/src/locale/az.ts b/packages/vuetify/src/locale/az.ts index 1ef2762ad07..1bfeb3ccd86 100644 --- a/packages/vuetify/src/locale/az.ts +++ b/packages/vuetify/src/locale/az.ts @@ -71,6 +71,7 @@ export default { timePicker: { am: 'AM', pm: 'PM', + title: 'Select Time', }, pagination: { ariaLabel: { diff --git a/packages/vuetify/src/locale/bg.ts b/packages/vuetify/src/locale/bg.ts index 37a7157d26e..97556f97b8a 100644 --- a/packages/vuetify/src/locale/bg.ts +++ b/packages/vuetify/src/locale/bg.ts @@ -71,6 +71,7 @@ export default { timePicker: { am: 'пр. обяд', pm: 'сл. обяд', + title: 'Select Time', }, pagination: { ariaLabel: { diff --git a/packages/vuetify/src/locale/ca.ts b/packages/vuetify/src/locale/ca.ts index 5e4ac1d6c08..95e0c4cd1f5 100644 --- a/packages/vuetify/src/locale/ca.ts +++ b/packages/vuetify/src/locale/ca.ts @@ -71,6 +71,7 @@ export default { timePicker: { am: 'AM', pm: 'PM', + title: 'Select Time', }, pagination: { ariaLabel: { diff --git a/packages/vuetify/src/locale/ckb.ts b/packages/vuetify/src/locale/ckb.ts index 47aae86e933..7a85013a3a0 100644 --- a/packages/vuetify/src/locale/ckb.ts +++ b/packages/vuetify/src/locale/ckb.ts @@ -71,6 +71,7 @@ export default { timePicker: { am: 'پێش نیوەڕۆژ', pm: 'دوای نیوەڕۆژ', + title: 'Select Time', }, pagination: { ariaLabel: { diff --git a/packages/vuetify/src/locale/cs.ts b/packages/vuetify/src/locale/cs.ts index cebd73605fc..4ebe6d78d77 100644 --- a/packages/vuetify/src/locale/cs.ts +++ b/packages/vuetify/src/locale/cs.ts @@ -71,6 +71,7 @@ export default { timePicker: { am: 'AM', pm: 'PM', + title: 'Select Time', }, pagination: { ariaLabel: { diff --git a/packages/vuetify/src/locale/da.ts b/packages/vuetify/src/locale/da.ts index 5438caedc63..c80f32a89d4 100644 --- a/packages/vuetify/src/locale/da.ts +++ b/packages/vuetify/src/locale/da.ts @@ -71,6 +71,7 @@ export default { timePicker: { am: 'AM', pm: 'PM', + title: 'Select Time', }, pagination: { ariaLabel: { diff --git a/packages/vuetify/src/locale/de.ts b/packages/vuetify/src/locale/de.ts index ecaf961cbf7..aa8d614e8aa 100644 --- a/packages/vuetify/src/locale/de.ts +++ b/packages/vuetify/src/locale/de.ts @@ -71,6 +71,7 @@ export default { timePicker: { am: 'AM', pm: 'PM', + title: 'Select Time', }, pagination: { ariaLabel: { diff --git a/packages/vuetify/src/locale/el.ts b/packages/vuetify/src/locale/el.ts index 4a713e5d184..9a2c757b2fa 100755 --- a/packages/vuetify/src/locale/el.ts +++ b/packages/vuetify/src/locale/el.ts @@ -71,6 +71,7 @@ export default { timePicker: { am: 'AM', pm: 'PM', + title: 'Select Time', }, pagination: { ariaLabel: { diff --git a/packages/vuetify/src/locale/en.ts b/packages/vuetify/src/locale/en.ts index d67f45395c4..c03b6d6395b 100644 --- a/packages/vuetify/src/locale/en.ts +++ b/packages/vuetify/src/locale/en.ts @@ -71,6 +71,7 @@ export default { timePicker: { am: 'AM', pm: 'PM', + title: 'Select Time', }, pagination: { ariaLabel: { diff --git a/packages/vuetify/src/locale/es.ts b/packages/vuetify/src/locale/es.ts index 42c3829faf8..81dd808d8db 100644 --- a/packages/vuetify/src/locale/es.ts +++ b/packages/vuetify/src/locale/es.ts @@ -71,6 +71,7 @@ export default { timePicker: { am: 'AM', pm: 'PM', + title: 'Select Time', }, pagination: { ariaLabel: { diff --git a/packages/vuetify/src/locale/et.ts b/packages/vuetify/src/locale/et.ts index 2febb34a24b..f6ac5a40204 100644 --- a/packages/vuetify/src/locale/et.ts +++ b/packages/vuetify/src/locale/et.ts @@ -71,6 +71,7 @@ export default { timePicker: { am: 'AM', pm: 'PM', + title: 'Select Time', }, pagination: { ariaLabel: { diff --git a/packages/vuetify/src/locale/fa.ts b/packages/vuetify/src/locale/fa.ts index a37882bf45e..87841876b98 100644 --- a/packages/vuetify/src/locale/fa.ts +++ b/packages/vuetify/src/locale/fa.ts @@ -71,6 +71,7 @@ export default { timePicker: { am: 'قبل از ظهر', pm: 'بعد از ظهر', + title: 'Select Time', }, pagination: { ariaLabel: { diff --git a/packages/vuetify/src/locale/fi.ts b/packages/vuetify/src/locale/fi.ts index d3e8e82b153..53b916003e9 100644 --- a/packages/vuetify/src/locale/fi.ts +++ b/packages/vuetify/src/locale/fi.ts @@ -71,6 +71,7 @@ export default { timePicker: { am: 'ap.', pm: 'ip.', + title: 'Select Time', }, pagination: { ariaLabel: { diff --git a/packages/vuetify/src/locale/fr.ts b/packages/vuetify/src/locale/fr.ts index 39013b2b393..dfd33b512c6 100644 --- a/packages/vuetify/src/locale/fr.ts +++ b/packages/vuetify/src/locale/fr.ts @@ -71,6 +71,7 @@ export default { timePicker: { am: 'AM', pm: 'PM', + title: 'Select Time', }, pagination: { ariaLabel: { diff --git a/packages/vuetify/src/locale/he.ts b/packages/vuetify/src/locale/he.ts index 27837c99155..253b5c1708c 100644 --- a/packages/vuetify/src/locale/he.ts +++ b/packages/vuetify/src/locale/he.ts @@ -71,6 +71,7 @@ export default { timePicker: { am: 'AM', pm: 'PM', + title: 'Select Time', }, pagination: { ariaLabel: { diff --git a/packages/vuetify/src/locale/hr.ts b/packages/vuetify/src/locale/hr.ts index ee747f10af0..7e82c3be8d2 100644 --- a/packages/vuetify/src/locale/hr.ts +++ b/packages/vuetify/src/locale/hr.ts @@ -71,6 +71,7 @@ export default { timePicker: { am: 'AM', pm: 'PM', + title: 'Select Time', }, pagination: { ariaLabel: { diff --git a/packages/vuetify/src/locale/hu.ts b/packages/vuetify/src/locale/hu.ts index e958b2d772b..ae4efb4bded 100644 --- a/packages/vuetify/src/locale/hu.ts +++ b/packages/vuetify/src/locale/hu.ts @@ -71,6 +71,7 @@ export default { timePicker: { am: 'de', pm: 'du', + title: 'Select Time', }, pagination: { ariaLabel: { diff --git a/packages/vuetify/src/locale/id.ts b/packages/vuetify/src/locale/id.ts index d537a7960e0..734fa04c40f 100644 --- a/packages/vuetify/src/locale/id.ts +++ b/packages/vuetify/src/locale/id.ts @@ -71,6 +71,7 @@ export default { timePicker: { am: 'AM', pm: 'PM', + title: 'Select Time', }, pagination: { ariaLabel: { diff --git a/packages/vuetify/src/locale/it.ts b/packages/vuetify/src/locale/it.ts index eb83b543e43..0614b098ce1 100644 --- a/packages/vuetify/src/locale/it.ts +++ b/packages/vuetify/src/locale/it.ts @@ -71,6 +71,7 @@ export default { timePicker: { am: 'AM', pm: 'PM', + title: 'Select Time', }, pagination: { ariaLabel: { diff --git a/packages/vuetify/src/locale/ja.ts b/packages/vuetify/src/locale/ja.ts index ff4f9db1338..00211bf9280 100644 --- a/packages/vuetify/src/locale/ja.ts +++ b/packages/vuetify/src/locale/ja.ts @@ -71,6 +71,7 @@ export default { timePicker: { am: 'AM', pm: 'PM', + title: 'Select Time', }, pagination: { ariaLabel: { diff --git a/packages/vuetify/src/locale/km.ts b/packages/vuetify/src/locale/km.ts index 36654ceb514..f3f4b238701 100644 --- a/packages/vuetify/src/locale/km.ts +++ b/packages/vuetify/src/locale/km.ts @@ -71,6 +71,7 @@ export default { timePicker: { am: 'ព្រឹក', pm: 'ល្ងាច', + title: 'Select Time', }, pagination: { ariaLabel: { diff --git a/packages/vuetify/src/locale/ko.ts b/packages/vuetify/src/locale/ko.ts index 8a1e735b177..f0c212dd701 100644 --- a/packages/vuetify/src/locale/ko.ts +++ b/packages/vuetify/src/locale/ko.ts @@ -71,6 +71,7 @@ export default { timePicker: { am: '오전', pm: '오후', + title: 'Select Time', }, pagination: { ariaLabel: { diff --git a/packages/vuetify/src/locale/lt.ts b/packages/vuetify/src/locale/lt.ts index 3cd19324acb..8389ca3be37 100644 --- a/packages/vuetify/src/locale/lt.ts +++ b/packages/vuetify/src/locale/lt.ts @@ -71,6 +71,7 @@ export default { timePicker: { am: 'AM', pm: 'PM', + title: 'Select Time', }, pagination: { ariaLabel: { diff --git a/packages/vuetify/src/locale/lv.ts b/packages/vuetify/src/locale/lv.ts index 2ac8a834bbe..911cca0f464 100644 --- a/packages/vuetify/src/locale/lv.ts +++ b/packages/vuetify/src/locale/lv.ts @@ -71,6 +71,7 @@ export default { timePicker: { am: 'AM', pm: 'PM', + title: 'Select Time', }, pagination: { ariaLabel: { diff --git a/packages/vuetify/src/locale/nl.ts b/packages/vuetify/src/locale/nl.ts index 8d937d63590..de2fe1fb9e9 100644 --- a/packages/vuetify/src/locale/nl.ts +++ b/packages/vuetify/src/locale/nl.ts @@ -71,6 +71,7 @@ export default { timePicker: { am: 'AM', pm: 'PM', + title: 'Select Time', }, pagination: { ariaLabel: { diff --git a/packages/vuetify/src/locale/no.ts b/packages/vuetify/src/locale/no.ts index 01d33e5de97..e75b7873c94 100644 --- a/packages/vuetify/src/locale/no.ts +++ b/packages/vuetify/src/locale/no.ts @@ -71,6 +71,7 @@ export default { timePicker: { am: 'AM', pm: 'PM', + title: 'Select Time', }, pagination: { ariaLabel: { diff --git a/packages/vuetify/src/locale/pl.ts b/packages/vuetify/src/locale/pl.ts index a1ae749dd89..bdb1000c1ff 100644 --- a/packages/vuetify/src/locale/pl.ts +++ b/packages/vuetify/src/locale/pl.ts @@ -71,6 +71,7 @@ export default { timePicker: { am: 'AM', pm: 'PM', + title: 'Select Time', }, pagination: { ariaLabel: { diff --git a/packages/vuetify/src/locale/pt.ts b/packages/vuetify/src/locale/pt.ts index 675d701efce..52ec4131ddd 100644 --- a/packages/vuetify/src/locale/pt.ts +++ b/packages/vuetify/src/locale/pt.ts @@ -71,6 +71,7 @@ export default { timePicker: { am: 'AM', pm: 'PM', + title: 'Select Time', }, pagination: { ariaLabel: { diff --git a/packages/vuetify/src/locale/ro.ts b/packages/vuetify/src/locale/ro.ts index 11f7363c1da..e0016f9088f 100644 --- a/packages/vuetify/src/locale/ro.ts +++ b/packages/vuetify/src/locale/ro.ts @@ -71,6 +71,7 @@ export default { timePicker: { am: 'AM', pm: 'PM', + title: 'Select Time', }, pagination: { ariaLabel: { diff --git a/packages/vuetify/src/locale/ru.ts b/packages/vuetify/src/locale/ru.ts index 12918906a4a..7af99bc3779 100644 --- a/packages/vuetify/src/locale/ru.ts +++ b/packages/vuetify/src/locale/ru.ts @@ -71,6 +71,7 @@ export default { timePicker: { am: 'AM', pm: 'PM', + title: 'Select Time', }, pagination: { ariaLabel: { diff --git a/packages/vuetify/src/locale/sk.ts b/packages/vuetify/src/locale/sk.ts index cb96e06363a..eef18e9739b 100644 --- a/packages/vuetify/src/locale/sk.ts +++ b/packages/vuetify/src/locale/sk.ts @@ -71,6 +71,7 @@ export default { timePicker: { am: 'AM', pm: 'PM', + title: 'Select Time', }, pagination: { ariaLabel: { diff --git a/packages/vuetify/src/locale/sl.ts b/packages/vuetify/src/locale/sl.ts index a7d64ad7486..9f5fe98b7d3 100644 --- a/packages/vuetify/src/locale/sl.ts +++ b/packages/vuetify/src/locale/sl.ts @@ -71,6 +71,7 @@ export default { timePicker: { am: 'AM', pm: 'PM', + title: 'Select Time', }, pagination: { ariaLabel: { diff --git a/packages/vuetify/src/locale/sr-Cyrl.ts b/packages/vuetify/src/locale/sr-Cyrl.ts index fd5fea9a171..f923df03f01 100644 --- a/packages/vuetify/src/locale/sr-Cyrl.ts +++ b/packages/vuetify/src/locale/sr-Cyrl.ts @@ -71,6 +71,7 @@ export default { timePicker: { am: 'AM', pm: 'PM', + title: 'Select Time', }, pagination: { ariaLabel: { diff --git a/packages/vuetify/src/locale/sr-Latn.ts b/packages/vuetify/src/locale/sr-Latn.ts index 4c51facdcd2..98dc97ab4ce 100644 --- a/packages/vuetify/src/locale/sr-Latn.ts +++ b/packages/vuetify/src/locale/sr-Latn.ts @@ -71,6 +71,7 @@ export default { timePicker: { am: 'AM', pm: 'PM', + title: 'Select Time', }, pagination: { ariaLabel: { diff --git a/packages/vuetify/src/locale/sv.ts b/packages/vuetify/src/locale/sv.ts index 6e8ab1897ad..5818b705339 100644 --- a/packages/vuetify/src/locale/sv.ts +++ b/packages/vuetify/src/locale/sv.ts @@ -71,6 +71,7 @@ export default { timePicker: { am: 'AM', pm: 'PM', + title: 'Select Time', }, pagination: { ariaLabel: { diff --git a/packages/vuetify/src/locale/th.ts b/packages/vuetify/src/locale/th.ts index a140962f26a..cd22cdcdd58 100644 --- a/packages/vuetify/src/locale/th.ts +++ b/packages/vuetify/src/locale/th.ts @@ -71,6 +71,7 @@ export default { timePicker: { am: 'AM', pm: 'PM', + title: 'Select Time', }, pagination: { ariaLabel: { diff --git a/packages/vuetify/src/locale/tr.ts b/packages/vuetify/src/locale/tr.ts index c8780f43569..7f0242c6b81 100644 --- a/packages/vuetify/src/locale/tr.ts +++ b/packages/vuetify/src/locale/tr.ts @@ -71,6 +71,7 @@ export default { timePicker: { am: 'AM', pm: 'PM', + title: 'Select Time', }, pagination: { ariaLabel: { diff --git a/packages/vuetify/src/locale/uk.ts b/packages/vuetify/src/locale/uk.ts index e60642249f4..ab6af48c040 100644 --- a/packages/vuetify/src/locale/uk.ts +++ b/packages/vuetify/src/locale/uk.ts @@ -71,6 +71,7 @@ export default { timePicker: { am: 'AM', pm: 'PM', + title: 'Select Time', }, pagination: { ariaLabel: { diff --git a/packages/vuetify/src/locale/vi.ts b/packages/vuetify/src/locale/vi.ts index 9dfed24c5d7..4d0a1b7b27e 100644 --- a/packages/vuetify/src/locale/vi.ts +++ b/packages/vuetify/src/locale/vi.ts @@ -71,6 +71,7 @@ export default { timePicker: { am: 'SA', pm: 'CH', + title: 'Select Time', }, pagination: { ariaLabel: { diff --git a/packages/vuetify/src/locale/zh-Hans.ts b/packages/vuetify/src/locale/zh-Hans.ts index 71af6b4fdb4..6dddccf4a68 100644 --- a/packages/vuetify/src/locale/zh-Hans.ts +++ b/packages/vuetify/src/locale/zh-Hans.ts @@ -71,6 +71,7 @@ export default { timePicker: { am: 'AM', pm: 'PM', + title: 'Select Time', }, pagination: { ariaLabel: { diff --git a/packages/vuetify/src/locale/zh-Hant.ts b/packages/vuetify/src/locale/zh-Hant.ts index 4938acef450..013282d2f85 100644 --- a/packages/vuetify/src/locale/zh-Hant.ts +++ b/packages/vuetify/src/locale/zh-Hant.ts @@ -71,6 +71,7 @@ export default { timePicker: { am: 'AM', pm: 'PM', + title: 'Select Time', }, pagination: { ariaLabel: {