UNPKG

@coreui/coreui-pro

Version:

The most popular front-end framework for developing responsive, mobile-first projects on the web rewritten by the CoreUI Team

1,119 lines (925 loc) 30.5 kB
/** * -------------------------------------------------------------------------- * CoreUI PRO time-picker.js * License (https://coreui.io/pro/license/) * -------------------------------------------------------------------------- */ import * as Popper from '@popperjs/core' import BaseComponent from './base-component.js' import EventHandler from './dom/event-handler.js' import Manipulator from './dom/manipulator.js' import SelectorEngine from './dom/selector-engine.js' import { defineJQueryPlugin, getElement, getNextActiveElement, isRTL } from './util/index.js' import FocusTrap from './util/focustrap.js' import { convert12hTo24h, convert24hTo12h, getAmPm, getLocalizedTimePartials, isAmPm, isValidTime } from './util/time.js' /** * Constants */ const NAME = 'time-picker' const DATA_KEY = 'coreui.time-picker' const EVENT_KEY = `.${DATA_KEY}` const DATA_API_KEY = '.data-api' const END_KEY = 'End' const ENTER_KEY = 'Enter' const ESCAPE_KEY = 'Escape' const HOME_KEY = 'Home' const SPACE_KEY = 'Space' const TAB_KEY = 'Tab' const ARROW_UP_KEY = 'ArrowUp' const ARROW_DOWN_KEY = 'ArrowDown' const ARROW_LEFT_KEY = 'ArrowLeft' const ARROW_RIGHT_KEY = 'ArrowRight' const RIGHT_MOUSE_BUTTON = 2 const EVENT_CLICK = `click${EVENT_KEY}` const EVENT_FOCUSOUT = `focusout${EVENT_KEY}` const EVENT_HIDE = `hide${EVENT_KEY}` const EVENT_HIDDEN = `hidden${EVENT_KEY}` const EVENT_INPUT = `input${EVENT_KEY}` const EVENT_KEYDOWN = `keydown${EVENT_KEY}` const EVENT_SHOW = `show${EVENT_KEY}` const EVENT_SHOWN = `shown${EVENT_KEY}` const EVENT_SUBMIT = 'submit' const EVENT_TIME_CHANGE = `timeChange${EVENT_KEY}` const EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}` const EVENT_KEYUP_DATA_API = `keyup${EVENT_KEY}${DATA_API_KEY}` const EVENT_LOAD_DATA_API = `load${EVENT_KEY}${DATA_API_KEY}` const CLASS_NAME_BODY = 'time-picker-body' const CLASS_NAME_CLEANER = 'time-picker-cleaner' const CLASS_NAME_DISABLED = 'disabled' const CLASS_NAME_DROPDOWN = 'time-picker-dropdown' const CLASS_NAME_FOOTER = 'time-picker-footer' const CLASS_NAME_INDICATOR = 'time-picker-indicator' const CLASS_NAME_INLINE_ICON = 'time-picker-inline-icon' const CLASS_NAME_INLINE_SELECT = 'time-picker-inline-select' const CLASS_NAME_INPUT = 'time-picker-input' const CLASS_NAME_INPUT_GROUP = 'time-picker-input-group' const CLASS_NAME_IS_INVALID = 'is-invalid' const CLASS_NAME_IS_VALID = 'is-valid' const CLASS_NAME_ROLL = 'time-picker-roll' const CLASS_NAME_ROLL_COL = 'time-picker-roll-col' const CLASS_NAME_ROLL_CELL = 'time-picker-roll-cell' const CLASS_NAME_SELECTED = 'selected' const CLASS_NAME_SHOW = 'show' const CLASS_NAME_TIME_PICKER = 'time-picker' const CLASS_NAME_WAS_VALIDATED = 'was-validated' const SELECTOR_DATA_TOGGLE = '[data-coreui-toggle="time-picker"]:not(.disabled):not(:disabled)' const SELECTOR_DATA_TOGGLE_SHOWN = `${SELECTOR_DATA_TOGGLE}.${CLASS_NAME_SHOW}` const SELECTOR_ROLL_CELL = '.time-picker-roll-cell' const SELECTOR_ROLL_CELL_FOCUSABLE = '.time-picker-roll-cell[tabindex="0"]' const SELECTOR_ROLL_COL = '.time-picker-roll-col' const SELECTOR_WAS_VALIDATED = 'form.was-validated' const Default = { ariaSelectHoursLabel: 'Select hours', ariaSelectMeridiemLabel: 'Select AM/PM', ariaSelectMinutesLabel: 'Select minutes', ariaSelectSecondsLabel: 'Select seconds', cancelButton: 'Cancel', cancelButtonClasses: ['btn', 'btn-sm', 'btn-ghost-primary'], cleaner: true, confirmButton: 'OK', confirmButtonClasses: ['btn', 'btn-sm', 'btn-primary'], container: false, disabled: false, footer: true, hours: null, indicator: true, inputOnChangeDelay: 750, inputReadOnly: false, invalid: false, locale: 'default', minutes: true, name: null, placeholder: 'Select time', required: true, seconds: true, size: null, time: null, type: 'dropdown', valid: false, variant: 'roll' } const DefaultType = { ariaSelectHoursLabel: 'string', ariaSelectMeridiemLabel: 'string', ariaSelectMinutesLabel: 'string', ariaSelectSecondsLabel: 'string', cancelButton: '(boolean|string)', cancelButtonClasses: '(array|string)', cleaner: 'boolean', confirmButton: '(boolean|string)', confirmButtonClasses: '(array|string)', container: '(string|element|boolean)', disabled: 'boolean', footer: 'boolean', hours: '(array|function|null)', indicator: 'boolean', inputOnChangeDelay: 'number', inputReadOnly: 'boolean', invalid: 'boolean', locale: 'string', minutes: '(array|boolean|function)', name: '(string|null)', placeholder: 'string', required: 'boolean', seconds: '(array|boolean|function)', size: '(string|null)', time: '(date|string|null)', type: 'string', valid: 'boolean', variant: 'string' } /** * Class definition */ class TimePicker extends BaseComponent { constructor(element, config) { super(element) this._config = this._getConfig(config) this._date = this._convertStringToDate(this._config.time) this._initialDate = null this._ampm = this._date ? getAmPm(new Date(this._date), this._config.locale) : 'am' this._popper = null this._indicatorElement = null this._input = null this._menu = null this._timePickerBody = null this._inputTimeout = null this._localizedTimePartials = getLocalizedTimePartials( this._config.locale, this.ampm, this._config.hours, this._config.minutes, this._config.seconds ) this._createTimePicker() this._createTimePickerSelection() this._addEventListeners() this._setUpSelects() this._focustrap = this._initializeFocusTrap() } // Getters static get Default() { return Default } static get DefaultType() { return DefaultType } static get NAME() { return NAME } // Public toggle() { return this._isShown() ? this.hide() : this.show() } show() { if (this._config.disabled || this._isShown()) { return } this._initialDate = new Date(this._date) EventHandler.trigger(this._element, EVENT_SHOW) this._element.classList.add(CLASS_NAME_SHOW) this._element.setAttribute('aria-expanded', true) if (this._config.container) { this._menu.classList.add(CLASS_NAME_SHOW) } this._focustrap.activate() EventHandler.trigger(this._element, EVENT_SHOWN) this._createPopper() } hide() { EventHandler.trigger(this._element, EVENT_HIDE) if (this._popper) { this._popper.destroy() } this._element.classList.remove(CLASS_NAME_SHOW) this._element.setAttribute('aria-expanded', 'false') if (this._config.container) { this._menu.classList.remove(CLASS_NAME_SHOW) } this._focustrap.deactivate() EventHandler.trigger(this._element, EVENT_HIDDEN) } dispose() { if (this._popper) { this._popper.destroy() } if (this._inputTimeout) { clearTimeout(this._inputTimeout) } this._focustrap.deactivate() super.dispose() } cancel() { this._date = this._initialDate this._setInputValue(this._initialDate || '') this._timePickerBody.innerHTML = '' this.hide() this._createTimePickerSelection() this._emitChangeEvent(this._date) } clear() { this._date = null this._setInputValue('') this._timePickerBody.innerHTML = '' this._createTimePickerSelection() this._emitChangeEvent(this._date) } reset() { this._date = this._convertStringToDate(this._config.time) this._setInputValue(this._config.time) this._timePickerBody.innerHTML = '' this._createTimePickerSelection() this._emitChangeEvent(this._date) } update(config) { this._config = this._getConfig(config) this._date = this._convertStringToDate(this._config.time) this._ampm = this._date ? getAmPm(new Date(this._date), this._config.locale) : 'am' this._timePickerBody.innerHTML = '' this._createTimePickerSelection() this._setUpSelects() } // Private _initializeFocusTrap() { return new FocusTrap({ additionalElement: this._config.container ? this._menu : null, trapElement: this._element }) } _moveFocusToNextColumn(event) { if (!this._timePickerBody) { return } const { target } = event const columnElement = target.parentElement const columns = SelectorEngine.find(SELECTOR_ROLL_COL, this._timePickerBody) const currentColumnIndex = columns.indexOf(columnElement) if (currentColumnIndex < columns.length - 1) { const firstFocusableCell = SelectorEngine.findOne(SELECTOR_ROLL_CELL_FOCUSABLE, columns[currentColumnIndex + 1]) firstFocusableCell.focus() } } _moveFocusToPreviousColumn(event) { if (!this._timePickerBody) { return } const { target } = event const columnElement = target.parentElement const columns = SelectorEngine.find(SELECTOR_ROLL_COL, this._timePickerBody) const currentColumnIndex = columns.indexOf(columnElement) if (currentColumnIndex > 0) { const firstFocusableCell = SelectorEngine.findOne(SELECTOR_ROLL_CELL_FOCUSABLE, columns[currentColumnIndex - 1]) firstFocusableCell.focus() } } _addEventListeners() { EventHandler.on(this._indicatorElement, EVENT_CLICK, () => { if (!this._config.disabled) { this.toggle() } }) EventHandler.on(this._indicatorElement, EVENT_KEYDOWN, event => { if (!this._config.disabled && event.key === ENTER_KEY) { this.toggle() } }) EventHandler.on(this._togglerElement, EVENT_CLICK, event => { if (!this._config.disabled && event.target !== this._indicatorElement) { this.show() if (this._config.variant === 'roll') { this._setUpRolls(true) } if (this._config.variant === 'select') { this._setUpSelects() } } }) if (this._config.variant === 'roll') { EventHandler.on(this._timePickerBody, EVENT_FOCUSOUT, SELECTOR_ROLL_COL, event => { if (!event.delegateTarget.contains(event.relatedTarget)) { this._setUpRolls(false) } }) EventHandler.on(this._timePickerBody, EVENT_KEYDOWN, SELECTOR_ROLL_CELL, event => { if (event.key === ARROW_DOWN_KEY || event.key === ARROW_UP_KEY) { event.preventDefault() const { key, target } = event const items = SelectorEngine.find(SELECTOR_ROLL_CELL, target.parentElement) if (!items.length) { return } const nextElement = getNextActiveElement(items, target, key === ARROW_DOWN_KEY, !items.includes(target)) if (nextElement) { nextElement.focus() } return } if (event.key === HOME_KEY || event.key === END_KEY) { event.preventDefault() const { key, target } = event const items = SelectorEngine.find(SELECTOR_ROLL_CELL, target.parentElement) if (!items.length) { return } const index = key === HOME_KEY ? 0 : items.length - 1 items[index].focus() return } if (event.key === ARROW_LEFT_KEY || event.key === ARROW_RIGHT_KEY) { event.preventDefault() const { key } = event const isRtl = isRTL() const shouldGoLeft = (key === ARROW_LEFT_KEY && !isRtl) || (key === ARROW_RIGHT_KEY && isRtl) if (shouldGoLeft) { this._moveFocusToPreviousColumn(event) } else { this._moveFocusToNextColumn(event) } } }) } EventHandler.on(this._element, EVENT_KEYDOWN, event => { if (event.key === ESCAPE_KEY) { this.hide() } }) EventHandler.on(this._element, 'timeChange.coreui.time-picker', () => { if (this._config.variant === 'roll') { this._setUpRolls() } if (this._config.variant === 'select') { this._setUpSelects() } }) EventHandler.on(this._element, 'onCancelClick.coreui.picker', () => { this.cancel() }) EventHandler.on(this._input, EVENT_INPUT, event => { if (this._inputTimeout) { clearTimeout(this._inputTimeout) } this._inputTimeout = setTimeout(() => { if (isValidTime(event.target.value)) { this._date = this._convertStringToDate(event.target.value) EventHandler.trigger(this._element, EVENT_TIME_CHANGE, { timeString: this._date ? this._date.toTimeString() : null, localeTimeString: this._date ? this._date.toLocaleTimeString() : null, date: this._date }) } }, this._config.inputOnChangeDelay) }) if (this._config.type === 'dropdown') { EventHandler.on(this._input.form, EVENT_SUBMIT, () => { if (this._input.form.classList.contains(CLASS_NAME_WAS_VALIDATED)) { if (Number.isNaN(Date.parse(`1970-01-01 ${this._input.value}`))) { return this._element.classList.add(CLASS_NAME_IS_INVALID) } if (this._date instanceof Date) { return this._element.classList.add(CLASS_NAME_IS_VALID) } this._element.classList.add(CLASS_NAME_IS_INVALID) } }) } } _createTimePicker() { this._element.classList.add(CLASS_NAME_TIME_PICKER) Manipulator.setDataAttribute( this._element, 'meridiem', CLASS_NAME_TIME_PICKER ) if (this._config.size) { this._element.classList.add(`time-picker-${this._config.size}`) } this._element.classList.toggle(CLASS_NAME_IS_VALID, this._config.valid) if (this._config.disabled) { this._element.classList.add(CLASS_NAME_DISABLED) } this._element.classList.toggle(CLASS_NAME_IS_INVALID, this._config.invalid) if (this._config.type === 'dropdown') { this._element.append(this._createTimePickerInputGroup()) const dropdownEl = document.createElement('div') dropdownEl.classList.add(CLASS_NAME_DROPDOWN) dropdownEl.append(this._createTimePickerBody()) if (this._config.footer || this._config.timepicker) { dropdownEl.append(this._createTimePickerFooter()) } const { container } = this._config if (container) { container.append(dropdownEl) } else { this._element.append(dropdownEl) } this._menu = dropdownEl } if (this._config.type === 'inline') { this._element.append(this._createTimePickerBody()) } } _createTimePickerInputGroup() { const inputGroupEl = document.createElement('div') inputGroupEl.classList.add(CLASS_NAME_INPUT_GROUP) const inputEl = document.createElement('input') inputEl.classList.add(CLASS_NAME_INPUT) inputEl.autocomplete = 'off' inputEl.disabled = this._config.disabled inputEl.placeholder = this._config.placeholder inputEl.readOnly = this._config.inputReadOnly inputEl.required = this._config.required inputEl.type = 'text' this._setInputValue(this._date || '', inputEl) if (this._config.name || this._element.id) { inputEl.name = this._config.name || `time-picker-${this._element.id}` } const events = ['change', 'keyup', 'paste'] for (const event of events) { inputEl.addEventListener(event, ({ target }) => { if (target.closest(SELECTOR_WAS_VALIDATED)) { if (Number.isNaN(Date.parse(`1970-01-01 ${target.value}`))) { this._element.classList.add(CLASS_NAME_IS_INVALID) this._element.classList.remove(CLASS_NAME_IS_VALID) return } if (this._date instanceof Date) { this._element.classList.add(CLASS_NAME_IS_VALID) this._element.classList.remove(CLASS_NAME_IS_INVALID) return } this._element.classList.add(CLASS_NAME_IS_INVALID) this._element.classList.remove(CLASS_NAME_IS_VALID) } }) } inputGroupEl.append(inputEl) if (this._config.indicator) { const inputGroupIndicatorEl = document.createElement('div') inputGroupIndicatorEl.classList.add(CLASS_NAME_INDICATOR) if (!this._config.disabled) { inputGroupIndicatorEl.tabIndex = 0 } inputGroupEl.append(inputGroupIndicatorEl) this._indicatorElement = inputGroupIndicatorEl } if (this._config.cleaner) { const inputGroupCleanerEl = document.createElement('div') inputGroupCleanerEl.classList.add(CLASS_NAME_CLEANER) inputGroupCleanerEl.addEventListener('click', event => { event.stopPropagation() this.clear() }) inputGroupEl.append(inputGroupCleanerEl) } this._input = inputEl this._togglerElement = inputGroupEl return inputGroupEl } _createTimePickerSelection() { if (this._config.variant === 'roll') { this._createTimePickerRoll() } if (this._config.variant === 'select') { this._createTimePickerInlineSelects() } } _createTimePickerBody() { const timePickerBodyEl = document.createElement('div') timePickerBodyEl.classList.add(CLASS_NAME_BODY) if (this._config.variant === 'roll') { timePickerBodyEl.classList.add(CLASS_NAME_ROLL) timePickerBodyEl.setAttribute('role', 'group') } this._timePickerBody = timePickerBodyEl return timePickerBodyEl } _createTimePickerInlineSelect(className, options, ariaLabel) { const selectEl = document.createElement('select') selectEl.classList.add(CLASS_NAME_INLINE_SELECT, className) selectEl.disabled = this._config.disabled selectEl.setAttribute('aria-label', ariaLabel) selectEl.addEventListener('change', event => this._handleTimeChange(className, event.target.value) ) for (const option of options) { const optionEl = document.createElement('option') optionEl.value = option.value optionEl.innerHTML = option.label selectEl.append(optionEl) } return selectEl } _createTimePickerInlineSelects() { const timeSeparatorEl = document.createElement('div') timeSeparatorEl.innerHTML = ':' this._timePickerBody.innerHTML = `<span class="${CLASS_NAME_INLINE_ICON}"></span>` this._timePickerBody.append( this._createTimePickerInlineSelect( 'hours', this._localizedTimePartials.listOfHours, this._config.ariaSelectHoursLabel ) ) if (this._config.minutes) { this._timePickerBody.append( timeSeparatorEl.cloneNode(true), this._createTimePickerInlineSelect( 'minutes', this._localizedTimePartials.listOfMinutes, this._config.ariaSelectMinutesLabel ) ) } if (this._config.seconds) { this._timePickerBody.append( timeSeparatorEl, this._createTimePickerInlineSelect( 'seconds', this._localizedTimePartials.listOfSeconds, this._config.ariaSelectSecondsLabel ) ) } if (this._localizedTimePartials.hour12) { this._timePickerBody.append( this._createTimePickerInlineSelect( 'meridiem', [ { value: 'am', label: 'AM' }, { value: 'pm', label: 'PM' } ], this._config.ariaSelectMeridiemLabel ) ) } } _createTimePickerRoll() { this._timePickerBody.append( this._createTimePickerRollCol( this._localizedTimePartials.listOfHours, 'hours', this._config.ariaSelectHoursLabel ) ) if (this._config.minutes) { this._timePickerBody.append( this._createTimePickerRollCol( this._localizedTimePartials.listOfMinutes, 'minutes', this._config.ariaSelectMinutesLabel ) ) } if (this._config.seconds) { this._timePickerBody.append( this._createTimePickerRollCol( this._localizedTimePartials.listOfSeconds, 'seconds', this._config.ariaSelectSecondsLabel ) ) } if (this._localizedTimePartials.hour12) { this._timePickerBody.append( this._createTimePickerRollCol( [ { value: 'am', label: 'AM' }, { value: 'pm', label: 'PM' } ], 'meridiem', this._config.ariaSelectMeridiemLabel ) ) } } _createTimePickerRollCol(options, part, ariaLabel) { const timePickerRollColEl = document.createElement('div') timePickerRollColEl.classList.add(CLASS_NAME_ROLL_COL) timePickerRollColEl.setAttribute('role', 'listbox') timePickerRollColEl.setAttribute('aria-label', ariaLabel) for (const [index, option] of options.entries()) { const timePickerRollCellEl = document.createElement('div') timePickerRollCellEl.classList.add(CLASS_NAME_ROLL_CELL) timePickerRollCellEl.setAttribute('role', 'option') timePickerRollCellEl.tabIndex = index === 0 ? 0 : -1 timePickerRollCellEl.setAttribute('aria-label', option.label.toString()) timePickerRollCellEl.setAttribute('aria-selected', 'false') timePickerRollCellEl.innerHTML = option.label timePickerRollCellEl.addEventListener('click', () => { this._handleTimeChange(part, option.value) }) timePickerRollCellEl.addEventListener('keydown', event => { if (event.code === SPACE_KEY || event.key === ENTER_KEY) { event.preventDefault() this._handleTimeChange(part, option.value) this._moveFocusToNextColumn(event) } }) Manipulator.setDataAttribute(timePickerRollCellEl, part, option.value) timePickerRollColEl.append(timePickerRollCellEl) } return timePickerRollColEl } _createTimePickerFooter() { const footerEl = document.createElement('div') footerEl.classList.add(CLASS_NAME_FOOTER) if (this._config.cancelButton) { const cancelButtonEl = document.createElement('button') cancelButtonEl.classList.add( ...this._getButtonClasses(this._config.cancelButtonClasses) ) cancelButtonEl.type = 'button' cancelButtonEl.innerHTML = this._config.cancelButton cancelButtonEl.addEventListener('click', () => { this.cancel() }) footerEl.append(cancelButtonEl) } if (this._config.confirmButton) { const confirmButtonEl = document.createElement('button') confirmButtonEl.classList.add( ...this._getButtonClasses(this._config.confirmButtonClasses) ) confirmButtonEl.type = 'button' confirmButtonEl.innerHTML = this._config.confirmButton confirmButtonEl.addEventListener('click', () => { this.hide() }) footerEl.append(confirmButtonEl) } return footerEl } _emitChangeEvent(date) { this._input.dispatchEvent(new Event('change')) EventHandler.trigger(this._element, EVENT_TIME_CHANGE, { timeString: date === null ? null : date.toTimeString(), localeTimeString: date === null ? null : date.toLocaleTimeString(), date }) } _setUpRolls(initial = false) { const parts = ['hours', 'minutes', 'seconds', 'meridiem'] for (const part of parts) { const partValue = this._getPartOfTime(part) if (partValue === null) { continue } const elements = SelectorEngine.find(`[data-coreui-${part}]`, this._element) const selectedElement = elements.find(element => partValue === Manipulator.getDataAttribute(element, part) ) if (selectedElement) { this._selectRollElement(selectedElement, initial) } } } _selectRollElement(element, initial = false) { const { parentElement } = element const currentSelected = SelectorEngine.findOne(SELECTOR_ROLL_CELL_FOCUSABLE, parentElement) if (currentSelected && currentSelected !== element) { currentSelected.classList.remove(CLASS_NAME_SELECTED) currentSelected.tabIndex = -1 currentSelected.setAttribute('aria-selected', 'false') } element.classList.add(CLASS_NAME_SELECTED) element.tabIndex = 0 element.setAttribute('aria-selected', 'true') this._scrollTo(parentElement, element, initial) } _setInputValue(date, input = this._input) { input.value = date instanceof Date ? date.toLocaleTimeString(this._config.locale, { hour12: this._localizedTimePartials.hour12, hour: 'numeric', ...(this._config.minutes && { minute: 'numeric' }), ...(this._config.seconds && { second: 'numeric' }) }) : date } _setUpSelects() { for (const part of Array.from(['hours', 'minutes', 'seconds', 'meridiem'])) { for (const element of SelectorEngine.find( `select.${part}`, this._element )) { if (this._getPartOfTime(part)) { element.value = this._getPartOfTime(part) } } } } _updateTimePicker() { this._element.innerHTML = '' this._createTimePicker() } _convertStringToDate(date) { return date ? (date instanceof Date ? date : new Date(`1970-01-01 ${date}`)) : null } _createPopper() { if (typeof Popper === 'undefined') { throw new TypeError( 'CoreUI\'s time picker require Popper (https://popper.js.org)' ) } const popperConfig = { modifiers: [ { name: 'preventOverflow', options: { boundary: 'clippingParents' } }, { name: 'offset', options: { offset: [0, 2] } } ], placement: isRTL() ? 'bottom-end' : 'bottom-start' } this._popper = Popper.createPopper( this._togglerElement, this._menu, popperConfig ) } _getButtonClasses(classes) { if (typeof classes === 'string') { return classes.split(' ') } return classes } _getPartOfTime(part) { if (this._date === null) { return null } if (part === 'hours') { return isAmPm(this._config.locale) ? convert24hTo12h(this._date.getHours()) : this._date.getHours() } if (part === 'minutes') { return this._date.getMinutes() } if (part === 'seconds') { return this._date.getSeconds() } if (part === 'meridiem') { return getAmPm(new Date(this._date), this._config.locale) } } _handleTimeChange = (set, value) => { const _date = this._date || new Date('1970-01-01') if (set === 'meridiem') { const currentHours = _date.getHours() if (value === 'am') { this._ampm = 'am' // Convert PM hours (12-23) to AM hours (0-11) if (currentHours >= 12) { _date.setHours(currentHours - 12) } } if (value === 'pm') { this._ampm = 'pm' // Convert AM hours (0-11) to PM hours (12-23) if (currentHours < 12) { _date.setHours(currentHours + 12) } } } if (set === 'hours') { if (isAmPm(this._config.locale)) { _date.setHours(convert12hTo24h(this._ampm, Number.parseInt(value, 10))) } else { _date.setHours(Number.parseInt(value, 10)) } } if (set === 'minutes') { _date.setMinutes(Number.parseInt(value, 10)) } if (set === 'seconds') { _date.setSeconds(Number.parseInt(value, 10)) } this._date = new Date(_date) if (this._input) { this._setInputValue(_date) this._input.dispatchEvent(new Event('change')) } EventHandler.trigger(this._element, EVENT_TIME_CHANGE, { timeString: _date.toTimeString(), localeTimeString: _date.toLocaleTimeString(), date: _date }) } _isShown() { return this._element.classList.contains(CLASS_NAME_SHOW) } _scrollTo(parent, children, initial = false) { parent.scrollTo({ top: children.offsetTop, behavior: initial ? 'instant' : 'smooth' }) } _configAfterMerge(config) { if (config.container === 'dropdown' || config.container === 'inline') { config.type = config.container } if (config.container === true) { config.container = document.body } if ( typeof config.container === 'object' || (typeof config.container === 'string' && config.container === 'dropdown' && config.container === 'inline') ) { config.container = getElement(config.container) } return config } // Static static timePickerInterface(element, config) { const data = TimePicker.getOrCreateInstance(element, config) if (typeof config === 'string') { if (typeof data[config] === 'undefined') { throw new TypeError(`No method named "${config}"`) } data[config]() } } static jQueryInterface(config) { return this.each(function () { const data = TimePicker.getOrCreateInstance(this, config) if (typeof config !== 'string') { return } if ( data[config] === undefined || config.startsWith('_') || config === 'constructor' ) { throw new TypeError(`No method named "${config}"`) } data[config](this) }) } static clearMenus(event) { if ( event.button === RIGHT_MOUSE_BUTTON || (event.type === 'keyup' && event.key !== TAB_KEY) ) { return } const openToggles = SelectorEngine.find(SELECTOR_DATA_TOGGLE_SHOWN) for (const toggle of openToggles) { const context = TimePicker.getInstance(toggle) if (!context) { continue } const composedPath = event.composedPath() if (composedPath.includes(context._element)) { continue } const relatedTarget = { relatedTarget: context._element } if (event.type === 'click') { relatedTarget.clickEvent = event } context.hide() } } } /** * Data API implementation */ EventHandler.on(window, EVENT_LOAD_DATA_API, () => { const timePickers = SelectorEngine.find(SELECTOR_DATA_TOGGLE) for (let i = 0, len = timePickers.length; i < len; i++) { TimePicker.timePickerInterface(timePickers[i]) } }) EventHandler.on(document, EVENT_CLICK_DATA_API, TimePicker.clearMenus) EventHandler.on(document, EVENT_KEYUP_DATA_API, TimePicker.clearMenus) /** * jQuery */ defineJQueryPlugin(TimePicker) export default TimePicker