UNPKG

@coreui/vue-pro

Version:

UI Components Library for Vue.js

720 lines (690 loc) 20.4 kB
import { defineComponent, h, PropType, ref, watch } from 'vue' import { CButton } from '../button' import { CFormControlWrapper } from '../form/CFormControlWrapper' import { CPicker } from '../picker' import { CTimePickerRollCol } from './CTimePickerRollCol' import { convert12hTo24h, convertTimeToDate, getAmPm, getLocalizedTimePartials, getSelectedHour, getSelectedMinutes, getSelectedSeconds, isValidTime, } from './utils' import type { LocalizedTimePartials } from './types' import { useDebouncedCallback } from '../../composables' import { Color } from '../props' const CTimePicker = defineComponent({ name: 'CTimePicker', props: { /** * Set if the component should use the 12/24 hour format. If `true` forces the interface to a 12-hour format. If `false` forces the interface into a 24-hour format. If `auto` the current locale will determine the 12 or 24-hour interface by default locales. * * @since 4.7.0 */ ampm: { type: [Boolean, String] as PropType<boolean | 'auto'>, default: 'auto', validator: (value: boolean | 'auto') => { if (typeof value == 'string') { return ['auto'].includes(value) } if (typeof value == 'boolean') { return true } return false }, }, /** * Toggle visibility or set the content of cancel button. */ cancelButton: { type: [Boolean, String], default: 'Cancel', }, /** * Sets the color context of the cancel button to one of CoreUI’s themed colors. * * @values 'primary', 'secondary', 'success', 'danger', 'warning', 'info', 'dark', 'light' */ cancelButtonColor: { ...Color, default: 'primary', }, /** * Size the cancel button small or large. * * @values 'sm', 'lg' */ cancelButtonSize: { type: String, default: 'sm', validator: (value: string) => { return ['sm', 'lg'].includes(value) }, }, /** * Set the cancel button variant to an outlined button or a ghost button. * * @values 'ghost', 'outline' */ cancelButtonVariant: { type: String, default: 'ghost', validator: (value: string) => { return ['ghost', 'outline'].includes(value) }, }, /** * Toggle visibility of the cleaner button. */ cleaner: { type: Boolean, default: true, }, /** * Toggle visibility or set the content of confirm button. */ confirmButton: { type: [Boolean, String], default: 'OK', }, /** * Sets the color context of the confirm button to one of CoreUI’s themed colors. * * @values 'primary', 'secondary', 'success', 'danger', 'warning', 'info', 'dark', 'light' */ confirmButtonColor: { ...Color, default: 'primary', }, /** * Size the confirm button small or large. * * @values 'sm', 'lg' */ confirmButtonSize: { type: String, default: 'sm', validator: (value: string) => { return ['sm', 'lg'].includes(value) }, }, /** * Set the confirm button variant to an outlined button or a ghost button. * * @values 'ghost', 'outline' */ confirmButtonVariant: { type: String, validator: (value: string) => { return ['ghost', 'outline'].includes(value) }, }, /** * Set container type for the component. */ container: { type: String as PropType<'dropdown' | 'inline'>, default: 'dropdown', }, /** * Toggle the disabled state for the component. */ disabled: Boolean, /** * Provide valuable, actionable feedback. * * @since 4.6.0 */ feedback: String, /** * Provide valuable, actionable feedback. * * @since 4.6.0 */ feedbackInvalid: String, /** * Provide valuable, actionable invalid feedback when using standard HTML form validation which applied two CSS pseudo-classes, `:invalid` and `:valid`. * * @since 4.6.0 */ feedbackValid: String, /** * Specify a list of available hours using an array, or customize the filtering of hours through a function. * * @since 5.0.0 */ hours: { type: [Array, Function] as PropType<number[] | ((hour: number) => number[])>, }, /** * The id global attribute defines an identifier (ID) that must be unique in the whole document. */ id: String, /** * Toggle visibility or set the content of the input indicator. */ indicator: { type: Boolean, default: true, }, /** * Defines the delay (in milliseconds) for the input field's onChange event. * * @since 5.0.0 */ inputOnChangeDelay: { type: Number, default: 750, }, /** * Toggle the readonly state for the component. */ inputReadOnly: Boolean, /** * Set component validation state to invalid. * * @since 4.6.0 */ invalid: { type: Boolean, default: undefined, }, /** * Add a caption for a component. * * @since 4.6.0 */ label: String, /** * Sets the default locale for components. If not set, it is inherited from the navigator.language. */ locale: { type: String, default: 'default', }, /** * Toggle the display of minutes, specify a list of available minutes using an array, or customize the filtering of minutes through a function. * * @since 5.0.0 */ minutes: { type: [Array, Boolean, Function] as PropType< number[] | ((hour: number) => number[]) | boolean >, default: true, }, /** * Set the name attribute for the input element. * * @since 5.3.0 */ name: String, /** * Specifies a short hint that is visible in the input. */ placeholder: { type: String, default: 'Select time', }, /** * When present, it specifies that must be filled out before submitting the form. * * @since 4.9.0 */ required: Boolean, /** * Toggle the display of seconds, specify a list of available seconds using an array, or customize the filtering of seconds through a function. * * @since 4.7.0 */ seconds: { type: [Array, Boolean, Function] as PropType< number[] | ((hour: number) => number[]) | boolean >, default: true, }, /** * Size the component small or large. * * @values 'sm', 'lg' */ size: { type: String, default: undefined, validator: (value: string) => { return ['sm', 'lg'].includes(value) }, }, /** * Add helper text to the component. * * @since 4.6.0 */ text: String, /** * Initial selected time. */ time: [Date, String], /** * Display validation feedback in a styled tooltip. * * @since 4.6.0 */ tooltipFeedback: Boolean, /** * Set component validation state to valid. * * @since 4.6.0 */ valid: { type: Boolean, default: undefined, }, /** * Set the time picker variant to a roll or select. * * @values 'roll', 'select' */ variant: { type: String, default: 'roll', validator: (value: string) => { return ['roll', 'select'].includes(value) }, }, /** * Toggle the visibility of the component. */ visible: Boolean, }, emits: [ /** * Callback fired when the time changed. */ 'change', /** * Callback fired when the component requests to be hidden. */ 'hide', /** * Callback fired when the component requests to be shown. */ 'show', /** * Callback fired when the time changed. * * @since 4.7.0 */ 'update:time', ], setup(props, { emit, attrs, slots }) { const formRef = ref() const inputRef = ref() const date = ref<Date | null>(convertTimeToDate(props.time)) const ampm = ref<'am' | 'pm'>(date.value ? getAmPm(new Date(date.value), props.locale) : 'am') const initialDate = ref<Date | null>(null) const visible = ref(props.visible) const localizedTimePartials = ref<LocalizedTimePartials>({ listOfHours: [], listOfMinutes: [], listOfSeconds: [], hour12: false, }) const isValid = ref<boolean | undefined>( props.valid ?? (props.invalid === true ? false : undefined), ) watch( () => props.time, () => { date.value = convertTimeToDate(props.time) }, ) watch( () => [props.valid, props.invalid], () => { isValid.value = props.valid ?? (props.invalid === true ? false : undefined) }, ) watch( date, () => { localizedTimePartials.value = getLocalizedTimePartials( props.locale, props.ampm, props.hours, props.minutes, props.seconds, ) if (date.value) { ampm.value = getAmPm(new Date(date.value), props.locale) } }, { immediate: true }, ) watch(inputRef, () => { if (inputRef.value && inputRef.value.form) { formRef.value = inputRef.value.form } }) watch([formRef, date], () => { if (formRef.value) { formRef.value.addEventListener('submit', (event: Event) => { setTimeout(() => handleFormValidation(event.target as HTMLFormElement)) }) handleFormValidation(formRef.value) } }) const handleClear = (event: Event) => { event.stopPropagation() date.value = null emit('change', null) emit('update:time', null) } const handleFormValidation = (form: HTMLFormElement) => { if (!form.classList.contains('was-validated')) { return } if (date.value) { isValid.value = true return } isValid.value = false } const handleTimeChange = (set: 'hours' | 'minutes' | 'seconds' | 'toggle', value: string) => { const _date = date.value || new Date('1970-01-01') if (set === 'toggle') { if (value === 'am') { _date.setHours(_date.getHours() - 12) } if (value === 'pm') { _date.setHours(_date.getHours() + 12) } } if (set === 'hours') { if (localizedTimePartials.value && localizedTimePartials.value.hour12) { _date.setHours(convert12hTo24h(ampm.value, Number.parseInt(value))) } else { _date.setHours(Number.parseInt(value)) } } if (set === 'minutes') { _date.setMinutes(Number.parseInt(value)) } if (set === 'seconds') { _date.setSeconds(Number.parseInt(value)) } date.value = new Date(_date) emit('change', _date.toTimeString(), _date.toLocaleTimeString(props.locale), _date) emit('update:time', _date.toLocaleTimeString(props.locale)) } const InputGroup = () => h('div', { class: 'time-picker-input-group' }, [ h('input', { autocomplete: 'off', class: 'time-picker-input', disabled: props.disabled, id: props.id, name: props.name, onInput: (event: Event) => useDebouncedCallback(() => { if (isValidTime((event.target as HTMLInputElement).value)) { date.value = convertTimeToDate((event.target as HTMLInputElement).value) } }, props.inputOnChangeDelay), placeholder: props.placeholder, readonly: props.inputReadOnly, ref: inputRef, required: props.required, value: date.value ? date.value.toLocaleTimeString(props.locale, { hour12: localizedTimePartials.value && localizedTimePartials.value.hour12, hour: 'numeric', ...(props.minutes && { minute: 'numeric' }), ...(props.seconds && { second: 'numeric' }), }) : '', }), props.indicator && h('div', { class: 'time-picker-indicator' }), props.cleaner && date.value && h('div', { class: 'time-picker-cleaner', onClick: (event: Event) => handleClear(event), }), ]) const TimePickerSelect = () => [ h('span', { class: 'time-picker-inline-icon' }), h( 'select', { class: 'time-picker-inline-select', disabled: props.disabled, onChange: (event: Event) => handleTimeChange('hours', (event.target as HTMLSelectElement).value), ...(date.value && { value: getSelectedHour(date.value, props.locale) }), }, localizedTimePartials.value && localizedTimePartials.value.listOfHours.map((option) => h( 'option', { value: option.value.toString(), }, option.label, ), ), ), props.minutes && ':', props.minutes && h( 'select', { class: 'time-picker-inline-select', disabled: props.disabled, onChange: (event: Event) => handleTimeChange('minutes', (event.target as HTMLSelectElement).value), ...(date.value && { value: getSelectedMinutes(date.value) }), }, localizedTimePartials.value && localizedTimePartials.value.listOfMinutes?.map((option) => h( 'option', { value: option.value.toString(), }, option.label, ), ), ), props.seconds && ':', props.seconds && h( 'select', { class: 'time-picker-inline-select', disabled: props.disabled, onChange: (event: Event) => handleTimeChange('seconds', (event.target as HTMLSelectElement).value), ...(date.value && { value: getSelectedSeconds(date.value) }), }, localizedTimePartials.value && localizedTimePartials.value.listOfSeconds?.map((option) => h( 'option', { value: option.value.toString(), }, option.label, ), ), ), localizedTimePartials.value && localizedTimePartials.value.hour12 && h( 'select', { class: 'time-picker-inline-select', disabled: props.disabled, onChange: (event: Event) => handleTimeChange('toggle', (event.target as HTMLSelectElement).value), value: ampm.value, }, [ h( 'option', { value: 'am', }, 'AM', ), h( 'option', { value: 'pm', }, 'PM', ), ], ), ] const TimePickerRoll = () => [ h(CTimePickerRollCol, { elements: localizedTimePartials.value && localizedTimePartials.value.listOfHours, onClick: (index: number) => handleTimeChange('hours', index.toString()), selected: getSelectedHour(date.value, props.locale, props.ampm), }), props.minutes && h(CTimePickerRollCol, { elements: localizedTimePartials.value && localizedTimePartials.value.listOfMinutes, onClick: (index: number) => handleTimeChange('minutes', index.toString()), selected: getSelectedMinutes(date.value), }), props.seconds && h(CTimePickerRollCol, { elements: localizedTimePartials.value && localizedTimePartials.value.listOfSeconds, onClick: (index: number) => handleTimeChange('seconds', index.toString()), selected: getSelectedSeconds(date.value), }), localizedTimePartials.value && localizedTimePartials.value.hour12 && h(CTimePickerRollCol, { elements: [ { value: 'am', label: 'AM' }, { value: 'pm', label: 'PM' }, ], onClick: (value: string) => handleTimeChange('toggle', value), selected: ampm.value, }), ] return () => h( CFormControlWrapper, { ...(typeof attrs['aria-describedby'] === 'string' && { describedby: attrs['aria-describedby'], }), feedback: props.feedback, feedbackInvalid: props.feedbackInvalid, feedbackValid: props.feedbackValid, id: props.id, invalid: isValid.value === false ? true : false, label: props.label, text: props.text, tooltipFeedback: props.tooltipFeedback, valid: isValid.value, }, { default: () => h( CPicker, { class: [ 'time-picker', { [`time-picker-${props.size}`]: props.size, disabled: props.disabled, 'is-invalid': isValid.value === false ? true : false, 'is-valid': isValid.value, }, ], container: props.container, disabled: props.disabled, dropdownClassNames: 'time-picker-dropdown', footer: true, onHide: () => { visible.value = false emit('hide') }, onShow: () => { if (date.value) { initialDate.value = new Date(date.value) } visible.value = true emit('show') }, visible: visible.value, }, { ...(slots.cancelButton && { cancelButton: () => slots.cancelButton && slots.cancelButton(), }), ...(slots.confirmButton && { confirmButton: () => slots.confirmButton && slots.confirmButton(), }), toggler: () => InputGroup(), default: () => h( 'div', { class: [ 'time-picker-body', { ['time-picker-roll']: props.variant === 'roll', }, ], }, props.variant === 'select' ? TimePickerSelect() : TimePickerRoll(), ), footer: () => h('div', { class: 'time-picker-footer' }, [ props.cancelButton && h( CButton, { color: props.cancelButtonColor, onClick: () => { if (initialDate.value) { date.value = new Date(initialDate.value) } visible.value = false }, size: props.cancelButtonSize, variant: props.cancelButtonVariant, }, () => props.cancelButton, ), props.confirmButton && h( CButton, { color: props.confirmButtonColor, onClick: () => { visible.value = false }, size: props.confirmButtonSize, variant: props.confirmButtonVariant, }, () => props.confirmButton, ), ]), }, ), }, ) }, }) export { CTimePicker }