UNPKG

ngx-bootstrap

Version:
323 lines (320 loc) 16.6 kB
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, forwardRef, Input, Output, ViewEncapsulation } from '@angular/core'; import { NG_VALUE_ACCESSOR } from '@angular/forms'; import { TimepickerActions } from './reducer/timepicker.actions'; import { TimepickerStore } from './reducer/timepicker.store'; import { getControlsValue } from './timepicker-controls.util'; import { TimepickerConfig } from './timepicker.config'; import { isHourInputValid, isInputLimitValid, isInputValid, isMinuteInputValid, isSecondInputValid, isValidDate, padNumber, parseTime } from './timepicker.utils'; export const TIMEPICKER_CONTROL_VALUE_ACCESSOR = { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => TimepickerComponent), multi: true }; export class TimepickerComponent { constructor(_config, _cd, _store, _timepickerActions) { this._cd = _cd; this._store = _store; this._timepickerActions = _timepickerActions; /** hours change step */ this.hourStep = 1; /** minutes change step */ this.minuteStep = 5; /** seconds change step */ this.secondsStep = 10; /** if true hours and minutes fields will be readonly */ this.readonlyInput = false; /** if true hours and minutes fields will be disabled */ this.disabled = false; /** if true scroll inside hours and minutes inputs will change time */ this.mousewheel = true; /** if true the values of hours and minutes can be changed using the up/down arrow keys on the keyboard */ this.arrowkeys = true; /** if true spinner arrows above and below the inputs will be shown */ this.showSpinners = true; /** if true meridian button will be shown */ this.showMeridian = true; /** show minutes in timepicker */ this.showMinutes = true; /** show seconds in timepicker */ this.showSeconds = false; /** meridian labels based on locale */ this.meridians = ['AM', 'PM']; /** placeholder for hours field in timepicker */ this.hoursPlaceholder = 'HH'; /** placeholder for minutes field in timepicker */ this.minutesPlaceholder = 'MM'; /** placeholder for seconds field in timepicker */ this.secondsPlaceholder = 'SS'; /** emits true if value is a valid date */ this.isValid = new EventEmitter(); // ui variables this.hours = ''; this.minutes = ''; this.seconds = ''; this.meridian = ''; // min\max validation for input fields this.invalidHours = false; this.invalidMinutes = false; this.invalidSeconds = false; // aria-label variables this.labelHours = 'hours'; this.labelMinutes = 'minutes'; this.labelSeconds = 'seconds'; // time picker controls state this.canIncrementHours = true; this.canIncrementMinutes = true; this.canIncrementSeconds = true; this.canDecrementHours = true; this.canDecrementMinutes = true; this.canDecrementSeconds = true; this.canToggleMeridian = true; // eslint-disable-next-line @typescript-eslint/no-explicit-any this.onChange = Function.prototype; // eslint-disable-next-line @typescript-eslint/no-explicit-any this.onTouched = Function.prototype; Object.assign(this, _config); this.timepickerSub = _store .select(state => state.value) .subscribe((value) => { // update UI values if date changed this._renderTime(value); this.onChange(value); this._store.dispatch(this._timepickerActions.updateControls(getControlsValue(this))); }); _store .select(state => state.controls) .subscribe((controlsState) => { this.isValid.emit(isInputValid(this.hours, this.minutes, this.seconds, this.isPM())); Object.assign(this, controlsState); _cd.markForCheck(); }); } /** @deprecated - please use `isEditable` instead */ get isSpinnersVisible() { return this.showSpinners && !this.readonlyInput; } get isEditable() { return !(this.readonlyInput || this.disabled); } resetValidation() { this.invalidHours = false; this.invalidMinutes = false; this.invalidSeconds = false; } isPM() { return this.showMeridian && this.meridian === this.meridians[1]; } prevDef($event) { $event.preventDefault(); } wheelSign($event) { return Math.sign($event.deltaY || 0) * -1; } ngOnChanges() { this._store.dispatch(this._timepickerActions.updateControls(getControlsValue(this))); } changeHours(step, source = '') { this.resetValidation(); this._store.dispatch(this._timepickerActions.changeHours({ step, source })); } changeMinutes(step, source = '') { this.resetValidation(); this._store.dispatch(this._timepickerActions.changeMinutes({ step, source })); } changeSeconds(step, source = '') { this.resetValidation(); this._store.dispatch(this._timepickerActions.changeSeconds({ step, source })); } updateHours(target) { this.resetValidation(); this.hours = target.value; const isValid = isHourInputValid(this.hours, this.isPM()) && this.isValidLimit(); if (!isValid) { this.invalidHours = true; this.isValid.emit(false); this.onChange(null); return; } this._updateTime(); } updateMinutes(target) { this.resetValidation(); this.minutes = target.value; const isValid = isMinuteInputValid(this.minutes) && this.isValidLimit(); if (!isValid) { this.invalidMinutes = true; this.isValid.emit(false); this.onChange(null); return; } this._updateTime(); } updateSeconds(target) { this.resetValidation(); this.seconds = target.value; const isValid = isSecondInputValid(this.seconds) && this.isValidLimit(); if (!isValid) { this.invalidSeconds = true; this.isValid.emit(false); this.onChange(null); return; } this._updateTime(); } isValidLimit() { return isInputLimitValid({ hour: this.hours, minute: this.minutes, seconds: this.seconds, isPM: this.isPM() }, this.max, this.min); } _updateTime() { const _seconds = this.showSeconds ? this.seconds : void 0; const _minutes = this.showMinutes ? this.minutes : void 0; if (!isInputValid(this.hours, _minutes, _seconds, this.isPM())) { this.isValid.emit(false); this.onChange(null); return; } this._store.dispatch(this._timepickerActions.setTime({ hour: this.hours, minute: this.minutes, seconds: this.seconds, isPM: this.isPM() })); } toggleMeridian() { if (!this.showMeridian || !this.isEditable) { return; } const _hoursPerDayHalf = 12; this._store.dispatch(this._timepickerActions.changeHours({ step: _hoursPerDayHalf, source: '' })); } /** * Write a new value to the element. */ writeValue(obj) { if (isValidDate(obj)) { this._store.dispatch(this._timepickerActions.writeValue(parseTime(obj))); } else if (obj == null) { this._store.dispatch(this._timepickerActions.writeValue()); } } /** * Set the function to be called when the control receives a change event. */ // eslint-disable-next-line @typescript-eslint/no-explicit-any registerOnChange(fn) { this.onChange = fn; } /** * Set the function to be called when the control receives a touch event. */ registerOnTouched(fn) { this.onTouched = fn; } /** * This function is called when the control status changes to or from "disabled". * Depending on the value, it will enable or disable the appropriate DOM element. * * @param isDisabled */ setDisabledState(isDisabled) { this.disabled = isDisabled; this._cd.markForCheck(); } ngOnDestroy() { this.timepickerSub.unsubscribe(); } _renderTime(value) { if (!value || !isValidDate(value)) { this.hours = ''; this.minutes = ''; this.seconds = ''; this.meridian = this.meridians[0]; return; } const _value = parseTime(value); if (!_value) { return; } const _hoursPerDayHalf = 12; let _hours = _value.getHours(); if (this.showMeridian) { this.meridian = this.meridians[_hours >= _hoursPerDayHalf ? 1 : 0]; _hours = _hours % _hoursPerDayHalf; // should be 12 PM, not 00 PM if (_hours === 0) { _hours = _hoursPerDayHalf; } } this.hours = padNumber(_hours); this.minutes = padNumber(_value.getMinutes()); this.seconds = padNumber(_value.getUTCSeconds()); } } TimepickerComponent.decorators = [ { type: Component, args: [{ selector: 'timepicker', changeDetection: ChangeDetectionStrategy.OnPush, providers: [TIMEPICKER_CONTROL_VALUE_ACCESSOR, TimepickerStore], template: "<table>\n <tbody>\n <tr class=\"text-center\" [hidden]=\"!showSpinners\">\n <!-- increment hours button-->\n <td>\n <a class=\"btn btn-link\" [class.disabled]=\"!canIncrementHours || !isEditable\"\n (click)=\"changeHours(hourStep)\"\n ><span class=\"bs-chevron bs-chevron-up\"></span></a>\n </td>\n <!-- divider -->\n <td *ngIf=\"showMinutes\">&nbsp;&nbsp;&nbsp;</td>\n <!-- increment minutes button -->\n <td *ngIf=\"showMinutes\">\n <a class=\"btn btn-link\" [class.disabled]=\"!canIncrementMinutes || !isEditable\"\n (click)=\"changeMinutes(minuteStep)\"\n ><span class=\"bs-chevron bs-chevron-up\"></span></a>\n </td>\n <!-- divider -->\n <td *ngIf=\"showSeconds\">&nbsp;</td>\n <!-- increment seconds button -->\n <td *ngIf=\"showSeconds\">\n <a class=\"btn btn-link\" [class.disabled]=\"!canIncrementSeconds || !isEditable\"\n (click)=\"changeSeconds(secondsStep)\">\n <span class=\"bs-chevron bs-chevron-up\"></span>\n </a>\n </td>\n <!-- space between -->\n <td *ngIf=\"showMeridian\">&nbsp;&nbsp;&nbsp;</td>\n <!-- meridian placeholder-->\n <td *ngIf=\"showMeridian\"></td>\n </tr>\n <tr>\n <!-- hours -->\n <td class=\"form-group\" [class.has-error]=\"invalidHours\">\n <input type=\"text\" [class.is-invalid]=\"invalidHours\"\n class=\"form-control text-center bs-timepicker-field\"\n [placeholder]=\"hoursPlaceholder\"\n maxlength=\"2\"\n [readonly]=\"readonlyInput\"\n [disabled]=\"disabled\"\n [value]=\"hours\"\n (wheel)=\"prevDef($event);changeHours(hourStep * wheelSign($event), 'wheel')\"\n (keydown.ArrowUp)=\"changeHours(hourStep, 'key')\"\n (keydown.ArrowDown)=\"changeHours(-hourStep, 'key')\"\n (change)=\"updateHours($event.target)\" [attr.aria-label]=\"labelHours\"></td>\n <!-- divider -->\n <td *ngIf=\"showMinutes\">&nbsp;:&nbsp;</td>\n <!-- minutes -->\n <td class=\"form-group\" *ngIf=\"showMinutes\" [class.has-error]=\"invalidMinutes\">\n <input type=\"text\" [class.is-invalid]=\"invalidMinutes\"\n class=\"form-control text-center bs-timepicker-field\"\n [placeholder]=\"minutesPlaceholder\"\n maxlength=\"2\"\n [readonly]=\"readonlyInput\"\n [disabled]=\"disabled\"\n [value]=\"minutes\"\n (wheel)=\"prevDef($event);changeMinutes(minuteStep * wheelSign($event), 'wheel')\"\n (keydown.ArrowUp)=\"changeMinutes(minuteStep, 'key')\"\n (keydown.ArrowDown)=\"changeMinutes(-minuteStep, 'key')\"\n (change)=\"updateMinutes($event.target)\" [attr.aria-label]=\"labelMinutes\">\n </td>\n <!-- divider -->\n <td *ngIf=\"showSeconds\">&nbsp;:&nbsp;</td>\n <!-- seconds -->\n <td class=\"form-group\" *ngIf=\"showSeconds\" [class.has-error]=\"invalidSeconds\">\n <input type=\"text\" [class.is-invalid]=\"invalidSeconds\"\n class=\"form-control text-center bs-timepicker-field\"\n [placeholder]=\"secondsPlaceholder\"\n maxlength=\"2\"\n [readonly]=\"readonlyInput\"\n [disabled]=\"disabled\"\n [value]=\"seconds\"\n (wheel)=\"prevDef($event);changeSeconds(secondsStep * wheelSign($event), 'wheel')\"\n (keydown.ArrowUp)=\"changeSeconds(secondsStep, 'key')\"\n (keydown.ArrowDown)=\"changeSeconds(-secondsStep, 'key')\"\n (change)=\"updateSeconds($event.target)\" [attr.aria-label]=\"labelSeconds\">\n </td>\n <!-- space between -->\n <td *ngIf=\"showMeridian\">&nbsp;&nbsp;&nbsp;</td>\n <!-- meridian -->\n <td *ngIf=\"showMeridian\">\n <button type=\"button\" class=\"btn btn-default text-center\"\n [disabled]=\"!isEditable || !canToggleMeridian\"\n [class.disabled]=\"!isEditable || !canToggleMeridian\"\n (click)=\"toggleMeridian()\"\n >{{ meridian }}\n </button>\n </td>\n </tr>\n <tr class=\"text-center\" [hidden]=\"!showSpinners\">\n <!-- decrement hours button-->\n <td>\n <a class=\"btn btn-link\" [class.disabled]=\"!canDecrementHours || !isEditable\"\n (click)=\"changeHours(-hourStep)\">\n <span class=\"bs-chevron bs-chevron-down\"></span>\n </a>\n </td>\n <!-- divider -->\n <td *ngIf=\"showMinutes\">&nbsp;&nbsp;&nbsp;</td>\n <!-- decrement minutes button-->\n <td *ngIf=\"showMinutes\">\n <a class=\"btn btn-link\" [class.disabled]=\"!canDecrementMinutes || !isEditable\"\n (click)=\"changeMinutes(-minuteStep)\">\n <span class=\"bs-chevron bs-chevron-down\"></span>\n </a>\n </td>\n <!-- divider -->\n <td *ngIf=\"showSeconds\">&nbsp;</td>\n <!-- decrement seconds button-->\n <td *ngIf=\"showSeconds\">\n <a class=\"btn btn-link\" [class.disabled]=\"!canDecrementSeconds || !isEditable\"\n (click)=\"changeSeconds(-secondsStep)\">\n <span class=\"bs-chevron bs-chevron-down\"></span>\n </a>\n </td>\n <!-- space between -->\n <td *ngIf=\"showMeridian\">&nbsp;&nbsp;&nbsp;</td>\n <!-- meridian placeholder-->\n <td *ngIf=\"showMeridian\"></td>\n </tr>\n </tbody>\n</table>\n", encapsulation: ViewEncapsulation.None, styles: [` .bs-chevron { border-style: solid; display: block; width: 9px; height: 9px; position: relative; border-width: 3px 0px 0 3px; } .bs-chevron-up { -webkit-transform: rotate(45deg); transform: rotate(45deg); top: 2px; } .bs-chevron-down { -webkit-transform: rotate(-135deg); transform: rotate(-135deg); top: -2px; } .bs-timepicker-field { width: 50px; padding: .375rem .55rem; } `] },] } ]; TimepickerComponent.ctorParameters = () => [ { type: TimepickerConfig }, { type: ChangeDetectorRef }, { type: TimepickerStore }, { type: TimepickerActions } ]; TimepickerComponent.propDecorators = { hourStep: [{ type: Input }], minuteStep: [{ type: Input }], secondsStep: [{ type: Input }], readonlyInput: [{ type: Input }], disabled: [{ type: Input }], mousewheel: [{ type: Input }], arrowkeys: [{ type: Input }], showSpinners: [{ type: Input }], showMeridian: [{ type: Input }], showMinutes: [{ type: Input }], showSeconds: [{ type: Input }], meridians: [{ type: Input }], min: [{ type: Input }], max: [{ type: Input }], hoursPlaceholder: [{ type: Input }], minutesPlaceholder: [{ type: Input }], secondsPlaceholder: [{ type: Input }], isValid: [{ type: Output }] }; //# sourceMappingURL=timepicker.component.js.map