ngx-bootstrap
Version:
Angular Bootstrap
323 lines (320 loc) • 16.6 kB
JavaScript
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\"> </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\"> </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\"> </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\"> : </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\"> : </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\"> </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\"> </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\"> </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\"> </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