moots-datetime-picker
Version:
Combination of a calendar datepicker and clock timepicker into one component for ionic 4.
937 lines (932 loc) • 86.9 kB
JavaScript
import { DateTime, Interval } from 'luxon';
import { trigger, state, style, transition, animate, keyframes } from '@angular/animations';
import { Injectable, EventEmitter, Component, Input, Output, ViewChild, Renderer2, ElementRef, ChangeDetectorRef, HostBinding, forwardRef, NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { NavParams, ModalController, IonContent, IonicModule } from '@ionic/angular';
import { NG_VALUE_ACCESSOR, FormsModule } from '@angular/forms';
import { CommonModule } from '@angular/common';
import { FlexLayoutModule } from '@angular/flex-layout';
var GlobalPickState;
(function (GlobalPickState) {
GlobalPickState[GlobalPickState["BEGIN_DATE"] = 0] = "BEGIN_DATE";
GlobalPickState[GlobalPickState["BEGIN_HOUR"] = 1] = "BEGIN_HOUR";
GlobalPickState[GlobalPickState["BEGIN_MINUTE"] = 2] = "BEGIN_MINUTE";
GlobalPickState[GlobalPickState["END_DATE"] = 3] = "END_DATE";
GlobalPickState[GlobalPickState["END_HOUR"] = 4] = "END_HOUR";
GlobalPickState[GlobalPickState["END_MINUTE"] = 5] = "END_MINUTE";
})(GlobalPickState || (GlobalPickState = {}));
var PickMode;
(function (PickMode) {
PickMode[PickMode["SINGLE"] = 0] = "SINGLE";
PickMode[PickMode["MULTI"] = 1] = "MULTI";
PickMode[PickMode["RANGE"] = 2] = "RANGE";
})(PickMode || (PickMode = {}));
class CalendarMonth {
}
class CalendarResult {
}
class CalendarComponentMonthChange {
}
function payloadToDateTime(payload) {
return payload instanceof Date ? DateTime.fromJSDate(payload, { zone: 'Etc/UTC' }) : DateTime.fromMillis(payload, { zone: 'Etc/UTC' });
}
function payloadsToDateTime(payloads) {
var result = [];
payloads.forEach((payload) => result.push(payloadToDateTime(payload)));
return result;
}
const defaults = {
DATE_FORMAT: 'yyyy-MM-DD',
COLOR: 'primary',
WEEKS_FORMAT: ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'],
MONTH_FORMAT: ['JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN', 'JUL', 'AUG', 'SEP', 'OCT', 'NOV', 'DEC']
};
const isBoolean = (input) => input === true || input === false;
const ɵ0 = isBoolean;
class CalendarService {
constructor() {
/**/
}
get DEFAULT_STEP() {
return 12;
}
safeOpt(calendarOptions) {
const _disableWeeks = [];
const _daysConfig = [];
const from = calendarOptions.from ? payloadToDateTime(calendarOptions.from) : DateTime.utc();
const safeOpts = {
from: from,
to: calendarOptions.to ? payloadToDateTime(calendarOptions.to) : DateTime.utc(),
weekStart: calendarOptions.weekStart || 0,
step: calendarOptions.step || this.DEFAULT_STEP,
id: calendarOptions.id || '',
cssClass: calendarOptions.cssClass || '',
closeLabel: calendarOptions.closeLabel || 'CANCEL',
doneLabel: calendarOptions.doneLabel || 'DONE',
monthFormat: calendarOptions.monthFormat || 'MMM yyyy',
title: calendarOptions.title || 'CALENDAR',
defaultTitle: calendarOptions.defaultTitle || '',
defaultSubtitle: calendarOptions.defaultSubtitle || '',
autoDone: calendarOptions.autoDone || false,
canBackwardsSelected: calendarOptions.canBackwardsSelected || false,
closeIcon: calendarOptions.closeIcon || false,
doneIcon: calendarOptions.doneIcon || false,
// showYearPicker: false,
isSaveHistory: calendarOptions.isSaveHistory || false,
pickMode: calendarOptions.pickMode || PickMode.SINGLE,
color: calendarOptions.closeLabel || defaults.COLOR,
weekdays: calendarOptions.weekdays || defaults.WEEKS_FORMAT,
daysConfig: calendarOptions.daysConfig || _daysConfig,
disableWeeks: calendarOptions.disableWeeks || _disableWeeks,
showAdjacentMonthDay: calendarOptions.showAdjacentMonthDay && true,
locale: calendarOptions.locale || 'en',
startLabel: calendarOptions.startLabel || 'Start',
endLabel: calendarOptions.endLabel || 'End',
uses24Hours: calendarOptions.uses24Hours,
fulldayLabel: calendarOptions.fulldayLabel || 'All Day event',
fullday: calendarOptions.fullday || false,
defaultScrollTo: calendarOptions.defaultScrollTo ? payloadToDateTime(calendarOptions.defaultDate) : from,
defaultDate: calendarOptions.defaultDate ? payloadToDateTime(calendarOptions.defaultDate) : undefined,
defaultDates: calendarOptions.defaultDates ? payloadsToDateTime(calendarOptions.defaultDates) : undefined,
defaultDateRange: calendarOptions.defaultDateRange
? { from: payloadToDateTime(calendarOptions.defaultDateRange.from), to: payloadToDateTime(calendarOptions.defaultDateRange.to) }
: undefined,
tapticConf: calendarOptions.tapticConf || {
onClockHover: () => {
/**/
},
onClockSelect: () => {
/**/
},
onCalendarSelect: () => {
/**/
}
},
pickState: calendarOptions.pickState || GlobalPickState.BEGIN_DATE
};
return safeOpts;
}
multiFormat(time) {
return time;
}
createOriginalCalendar(time) {
const date = DateTime.fromMillis(time, { zone: 'Etc/UTC' });
const year = date.year;
const month = date.month;
const firstWeek = DateTime.utc(year, month, 1).weekday;
const howManyDays = date.endOf('month').day;
return {
date,
year,
month,
firstWeek,
howManyDays
};
}
findDayConfig(day, opt) {
if (opt.daysConfig.length <= 0) {
return undefined;
}
return opt.daysConfig.find((n) => day.hasSame(n.date, 'day'));
}
createCalendarDay(time, opt, month) {
const date = time;
const isToday = DateTime.utc().hasSame(date, 'day');
const isBeforeToday = DateTime.utc().startOf('day') > date;
const dayConfig = this.findDayConfig(date, opt);
const _rangeBeg = opt.from.valueOf();
const _rangeEnd = opt.to.valueOf();
let isBetween = true;
const disableWee = opt.disableWeeks.indexOf(date.toJSDate().getDay()) !== -1;
if (_rangeBeg > 0 && _rangeEnd > 0) {
isBetween = opt.canBackwardsSelected
? date.valueOf() < _rangeBeg
? false
: isBetween
: Interval.fromDateTimes(opt.from, opt.to).contains(date);
}
else if (_rangeBeg > 0 && _rangeEnd === 0) {
if (!opt.canBackwardsSelected) {
const _addTime = date.plus({ days: 1 });
isBetween = !(_addTime.valueOf() > _rangeBeg);
}
else {
isBetween = false;
}
}
let _disable = false;
_disable = dayConfig && isBoolean(dayConfig.disable) ? dayConfig.disable : disableWee || isBetween;
if (isBeforeToday && !opt.canBackwardsSelected) {
_disable = true;
}
let title = time.day.toString();
if (dayConfig && dayConfig.title) {
title = dayConfig.title;
}
else if (opt.defaultTitle) {
title = opt.defaultTitle;
}
let subTitle = '';
if (dayConfig && dayConfig.subTitle) {
subTitle = dayConfig.subTitle;
}
else if (opt.defaultSubtitle) {
subTitle = opt.defaultSubtitle;
}
return {
time,
isToday,
title,
subTitle,
selected: false,
isLastMonth: date.month < month,
isNextMonth: date.month > month,
marked: dayConfig ? dayConfig.marked || false : false,
cssClass: dayConfig ? dayConfig.cssClass || '' : '',
disable: _disable,
isFirst: date.day === 1,
isLast: date.day === date.daysInMonth
};
}
createCalendarMonth(original, opt) {
const days = new Array(6).fill(undefined);
const len = original.howManyDays;
for (let i = original.firstWeek; i < len + original.firstWeek; i++) {
days[i] = this.createCalendarDay(original.date.plus({ days: i - original.firstWeek }), opt);
}
const weekStart = opt.weekStart;
if (weekStart === 1) {
if (days[0] === undefined) {
days.shift();
}
else {
days.unshift(...new Array(6).fill(undefined));
}
}
if (opt.showAdjacentMonthDay) {
const _booleanMap = days.map((e) => !!e);
const thisMonth = original.date.month;
let startOffsetIndex = _booleanMap.indexOf(true) - 1;
let endOffsetIndex = _booleanMap.lastIndexOf(true) + 1;
for (startOffsetIndex; startOffsetIndex >= 0; startOffsetIndex--) {
const dayBefore = days[startOffsetIndex + 1].time.minus({ days: 1 });
days[startOffsetIndex] = this.createCalendarDay(dayBefore, opt, thisMonth);
}
if (!(_booleanMap.length % 7 === 0 && _booleanMap[_booleanMap.length - 1])) {
for (endOffsetIndex; endOffsetIndex < days.length + (endOffsetIndex % 7); endOffsetIndex++) {
const dayAfter = days[endOffsetIndex - 1].time.plus({ days: 1 });
days[endOffsetIndex] = this.createCalendarDay(dayAfter, opt, thisMonth);
}
}
}
return {
days,
original
};
}
createMonthsByPeriod(startDate, monthsNum, opt) {
const _array = [];
const startOfMonth = startDate.startOf('month');
for (let i = 0; i < monthsNum; i++) {
const time = startOfMonth.plus({ months: i }).valueOf();
const originalCalendar = this.createOriginalCalendar(time);
_array.push(this.createCalendarMonth(originalCalendar, opt));
}
return _array;
}
wrapResult(original, times, pickMode) {
const secondIndex = original[1] ? 1 : 0;
let result;
switch (pickMode) {
case PickMode.SINGLE:
result = this.multiFormat(original[0].time.valueOf());
break;
case PickMode.RANGE:
result = {
from: this.multiFormat(DateTime.fromMillis(original[0].time.valueOf(), { zone: 'Etc/UTC' })
.set({
hour: times[0].hour,
minute: times[0].minute
})
.startOf('minute')
.valueOf()),
to: this.multiFormat(DateTime.fromMillis(original[secondIndex].time.valueOf(), { zone: 'Etc/UTC' })
.set({
hour: times[1].hour,
minute: times[1].minute
})
.startOf('minute')
.valueOf())
};
break;
case PickMode.MULTI:
result = original.map((e) => this.multiFormat(e.time.valueOf()));
break;
default:
result = original;
}
return result;
}
}
CalendarService.decorators = [
{ type: Injectable }
];
CalendarService.ctorParameters = () => [];
/*function detectHourCycle(): boolean {
return (
new Intl.DateTimeFormat(DateTime.now().toLocal().locale, {
hour: 'numeric'
})
.formatToParts(new Date(2020, 0, 1, 13))
.find((part) => part.type === 'hour').value.length === 2
);
}*/
var ClockPickState;
(function (ClockPickState) {
ClockPickState[ClockPickState["HOUR"] = 0] = "HOUR";
ClockPickState[ClockPickState["MINUTE"] = 1] = "MINUTE";
})(ClockPickState || (ClockPickState = {}));
class ClockPickerComponent {
constructor() {
this.ClockPickState = ClockPickState;
this.pickState = ClockPickState.HOUR;
this.mode24 = true;
this.selectChange = new EventEmitter();
this.valueSelected = new EventEmitter();
this.displayedValue = new EventEmitter();
this.hourSelected = '3';
this.minuteSelected = '00';
this.outerHours = ['9', '10', '11', '12', '1', '2', '3', '4', '5', '6', '7', '8'];
this.innerHours = ['21', '22', '23', '00', '13', '14', '15', '16', '17', '18', '19', '20'];
this.minutes = ['45', '50', '55', '00', '05', '10', '15', '20', '25', '30', '35', '40'];
this.lastType = '';
//
}
set inputTime(time) {
this._inputTime = time;
let hour = time.toFormat(this.mode24 ? 'HH' : 'hh');
hour = hour.startsWith('0') ? hour.substr(1) : hour;
this.setClockFromHour(hour);
this.setClockFromMinute(time.toFormat('mm'));
}
ionViewDidLoad() {
this.setClockFromHour('00');
this.setClockFromMinute('00');
}
getAmPm() {
const s = this._inputTime.toLocaleString({ hour: 'numeric', minute: 'numeric', hour12: !this.mode24 });
return s.substring(s.length - 2).toLowerCase();
}
setAmPm(arg) {
const f = this._inputTime.toLocaleString({ hour: 'numeric', minute: 'numeric', hour12: !this.mode24 });
const time = f.replace(this.getAmPm().toLowerCase(), arg.toUpperCase()).replace(this.getAmPm().toUpperCase(), arg.toUpperCase());
const temp = DateTime.fromFormat(time, 't', { zone: 'Etc/UTC' });
this._inputTime = this._inputTime.set({ hour: temp.hour, minute: temp.minute });
this.valueSelected.emit(this._inputTime);
}
getHourNumber() {
return parseInt(this.hourSelected, 10);
}
updatedClock(clicked) {
this.lastClicked = clicked;
const clock = this.pickState === ClockPickState.HOUR ? this.hourClock : this.minuteClock;
if (clock) {
const rectangle = clock.nativeElement.getBoundingClientRect();
const clockCenter = {
x: rectangle.width / 2 + rectangle.left,
y: rectangle.height / 2 + rectangle.top
};
const angle = (Math.atan2(clockCenter.y - clicked.y, clockCenter.x - clicked.x) * 180) / Math.PI;
if (this.pickState === ClockPickState.HOUR) {
const dist = ClockPickerComponent.calculateDistance(clockCenter.x, clicked.x, clockCenter.y, clicked.y);
this.setHourFromAngle(angle, this.mode24 && dist < rectangle.height / 3);
}
else {
this.setMinuteFromAngle(angle);
}
this.setAmPm;
const time = this.hourSelected.padStart(2, '0') + ' ' + this.minuteSelected + (this.mode24 ? '' : ' ' + this.getAmPm());
const temp = DateTime.fromFormat(time, this.mode24 ? 'HH mm' : 'hh mm a', { zone: 'Etc/UTC' });
this._inputTime = this._inputTime.set({ hour: temp.hour, minute: temp.minute });
this.displayedValue.emit(this._inputTime);
}
}
draggedClock($event) {
this.lastType = 'drag';
$event.preventDefault();
const clicked = {
x: $event.changedTouches[0].clientX,
y: $event.changedTouches[0].clientY
};
this.updatedClock(clicked);
}
tappedClock(event) {
const clicked = {
x: event.clientX,
y: event.clientY
};
let fireEvents = false;
if (event.type === 'touchend') {
this.lastType = 'touchend';
this.updatedClock(this.lastClicked);
fireEvents = true;
}
else {
if (this.lastType !== 'touchend') {
this.updatedClock(clicked);
fireEvents = true;
}
this.lastType = 'clicked';
this.lastClicked = clicked;
}
if (fireEvents) {
if (this.pickState === ClockPickState.HOUR) {
this.valueSelected.emit(this._inputTime);
this.pickState = ClockPickState.MINUTE;
this.selectChange.emit(this.pickState);
}
else if (this.pickState === ClockPickState.MINUTE) {
this.valueSelected.emit(this._inputTime);
}
this.tapConf.onClockSelect();
}
}
/** Sets the clock hour handle according to hour parameter */
setClockFromHour(hour) {
const hourN = parseInt(hour, 10);
const hours = hourN <= 12 && hourN > 0 ? this.outerHours : this.innerHours;
hour = hour === '0' ? '00' : hour;
const index = hours.indexOf(hour);
if (index > -1 || index === -6) {
const angle = Math.abs(index) * 30 - 90;
this.hourHandStyle = {
transform: `rotate(${angle}deg)`
};
}
else {
const angle = 12 - Math.abs(index) * 30 - 105;
this.hourHandStyle = {
transform: `rotate(${angle}deg)`
};
}
this.hourSelected = hour;
}
/** Sets the clock minute handle according to minute parameter */
setClockFromMinute(minute) {
const index = this.minutes.indexOf(minute);
if (index > -1 || index === -6) {
this.minuteHandStyle = {
transform: `rotate(${Math.abs(index) * 30 - 90}deg)`
};
}
else {
this.minuteHandStyle = {
transform: `rotate(${(12 - Math.abs(index)) * 30 - 105}deg)`
};
}
this.minuteSelected = minute;
}
setHourFromAngle(angle, inner) {
const hours = inner ? this.innerHours : this.outerHours;
const index = Math.round(angle / 30);
let toSelect;
if (index > -1 || index === -6) {
toSelect = hours[Math.abs(index)];
const angleC = Math.abs(index) * 30 - 90;
this.hourHandStyle = {
transform: `rotate(${angleC}deg)`
};
}
else {
toSelect = hours[12 - Math.abs(index)];
const angleC = (12 - Math.abs(index)) * 30 - 90;
this.hourHandStyle = {
transform: `rotate(${angleC}deg)`
};
}
if (toSelect !== this.hourSelected) {
this.hourSelected = toSelect;
this.tapConf.onClockHover();
}
}
setMinuteFromAngle(angle) {
const index = Math.round(angle / 30);
let toSelect;
if (index > -1 || index === -6) {
toSelect = this.minutes[Math.abs(index)];
this.minuteHandStyle = {
transform: `rotate(${Math.abs(index) * 30 - 90}deg)`
};
}
else {
toSelect = this.minutes[12 - Math.abs(index)];
this.minuteHandStyle = {
transform: `rotate(${(12 - Math.abs(index)) * 30 - 90}deg)`
};
}
if (toSelect !== this.minuteSelected) {
this.minuteSelected = toSelect;
this.tapConf.onClockHover();
}
}
static calculateDistance(x1, x2, y1, y2) {
const dis = Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1));
return Math.abs(dis);
}
}
ClockPickerComponent.decorators = [
{ type: Component, args: [{
selector: 'moots-clock-picker',
animations: [
trigger('switch', [
state('open', style({
transform: 'scale(1)',
opacity: 1
})),
state('closed', style({
transform: 'scale(1)',
opacity: 1
})),
transition('open <=> closed', [
animate('0.5s ease-in-out', keyframes([
style({
transform: 'scale(1.1)',
opacity: 0.5,
offset: 0.5
})
]))
])
])
],
template: "<div class=\"clock-container\">\r\n <div style=\"text-align: center;\" [@switch]=\"pickState === ClockPickState.HOUR ? 'open' : 'closed'\">\r\n <!-- Hour clock -->\r\n <div #hourClock *ngIf=\"pickState === ClockPickState.HOUR\" (click)=\"tappedClock($event)\" (touchstart)=\"draggedClock($event)\"\r\n (touchmove)=\"draggedClock($event)\" (touchend)=\"tappedClock($event)\">\r\n\r\n <div class=\"hours-12-container\">\r\n <div *ngFor=\"let hour of ['3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '1', '2']\"\r\n fxLayoutAlign=\"center center\" class=\"clock-digit\" [class.highlight]=\"hourSelected === hour\">\r\n {{ hour }}\r\n </div>\r\n </div>\r\n\r\n <div *ngIf=\"mode24\" class=\"hours-24-container\">\r\n <div *ngFor=\"let hour of ['15', '16', '17', '18', '19', '20', '21', '22', '23', '00', '13', '14']\"\r\n fxLayoutAlign=\"center center\" class=\"clock-digit\" [class.highlight]=\"hourSelected === hour\">\r\n {{ hour }}\r\n </div>\r\n </div>\r\n <div class=\"clock-hand\" [ngStyle]=\"hourHandStyle\" [ngClass]=\"(getHourNumber() > 0 && getHourNumber() < 13) ? '' : 'inner'\"></div>\r\n </div>\r\n <!-- Minute clock -->\r\n <div #minuteClock *ngIf=\"pickState === ClockPickState.MINUTE\" (click)=\"tappedClock($event)\" (touchstart)=\"draggedClock($event)\"\r\n (touchmove)=\"draggedClock($event)\" (touchend)=\"tappedClock($event)\">\r\n\r\n <div class=\"minutes-container\">\r\n <div *ngFor=\"let minute of ['15', '20', '25', '30', '35', '40', '45', '50', '55', '00', '05', '10']\"\r\n fxLayoutAlign=\"center center\" class=\"clock-digit\" [class.highlight]=\"minuteSelected === minute\">\r\n {{ minute }}\r\n </div>\r\n </div>\r\n\r\n <div class=\"clock-hand\" [ngStyle]=\"minuteHandStyle\"></div>\r\n </div>\r\n\r\n <div class=\"clock-center\"></div>\r\n </div>\r\n <!-- AM PM Buttons -->\r\n <div *ngIf=\"!mode24\">\r\n <ion-button fill=\"{{getAmPm() === 'am' ? 'solid' : 'outline'}}\" class=\"ampm\" (click)=\"setAmPm('am')\"\r\n style=\"float: left;margin-left: 1em;\">\r\n AM\r\n </ion-button>\r\n <div style=\"text-align: center;display: inline-flex;margin-top:16px;\">\r\n </div>\r\n <ion-button fill=\"{{getAmPm() === 'pm' ? 'solid' : 'outline'}}\" class=\"ampm\" (click)=\"setAmPm('pm')\"\r\n style=\"float: right;margin-right: 1em;\">\r\n PM\r\n </ion-button>\r\n </div>\r\n</div>\r\n",
styles: [".clock-container{text-align:center;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.ampm{font-weight:500;font-size:1.5rem;background:none;margin-top:-1em;border-radius:100%;width:2.5em;height:2.5em}.highlight{background-color:#488aff;border-radius:100%;padding:.5em;margin:-.75rem;width:3rem;height:3rem;color:var(--ion-color-primary-contrast)!important;overflow-wrap:normal;overflow:visible}ion-button{--border-radius: 100%}div.toolbar-background{--min-height: 44px}.clock-hand{position:absolute;width:1px;background-color:#488aff;border:solid 2px #488aff;height:36%;transform-origin:bottom center;transform:rotate(0);transition:background .15s ease-in-out;transition:border-width .15s ease-in-out;left:calc(50% - 2px);top:14%}.clock-hand.inner{top:28%;height:22%}.clock-center{position:absolute;width:.5em;background-color:#488aff;border-radius:100%;height:.5em;left:calc(50% - .25em);top:calc(50% - .25em)}.minutes-container{position:relative;width:20em;height:20em;padding:0;border-radius:50%;list-style:none;margin:1em auto 0;background-color:var(--ion-color-light-shade)}.minutes-container>*{display:block;position:absolute;top:50%;left:50%;width:1.75em;height:1.75em;margin:-.875em}.minutes-container>*:nth-of-type(1){transform:rotate(0) translate(4.7058823529em) rotate(0)}.minutes-container>*:nth-of-type(2){transform:rotate(30deg) translate(4.7058823529em) rotate(-30deg)}.minutes-container>*:nth-of-type(3){transform:rotate(60deg) translate(4.7058823529em) rotate(-60deg)}.minutes-container>*:nth-of-type(4){transform:rotate(90deg) translate(4.7058823529em) rotate(-90deg)}.minutes-container>*:nth-of-type(5){transform:rotate(120deg) translate(4.7058823529em) rotate(-120deg)}.minutes-container>*:nth-of-type(6){transform:rotate(150deg) translate(4.7058823529em) rotate(-150deg)}.minutes-container>*:nth-of-type(7){transform:rotate(180deg) translate(4.7058823529em) rotate(-180deg)}.minutes-container>*:nth-of-type(8){transform:rotate(210deg) translate(4.7058823529em) rotate(-210deg)}.minutes-container>*:nth-of-type(9){transform:rotate(240deg) translate(4.7058823529em) rotate(-240deg)}.minutes-container>*:nth-of-type(10){transform:rotate(270deg) translate(4.7058823529em) rotate(-270deg)}.minutes-container>*:nth-of-type(11){transform:rotate(300deg) translate(4.7058823529em) rotate(-300deg)}.minutes-container>*:nth-of-type(12){transform:rotate(330deg) translate(4.7058823529em) rotate(-330deg)}.minutes-container .clock-digit{display:block;max-width:100%;border-radius:50%;transition:.15s;font-size:28px;font-weight:500;font-stretch:normal;font-style:normal;line-height:1.21;letter-spacing:.34px;color:var(--ion-color-dark-shade);z-index:5;cursor:pointer}.hours-12-container{position:relative;width:20em;height:20em;padding:0;border-radius:50%;list-style:none;margin:1em auto 0;background-color:var(--ion-color-light-shade)}.hours-12-container>*{display:block;position:absolute;top:50%;left:50%;width:1.75em;height:1.75em;margin:-.875em}.hours-12-container>*:nth-of-type(1){transform:rotate(0) translate(4.7058823529em) rotate(0)}.hours-12-container>*:nth-of-type(2){transform:rotate(30deg) translate(4.7058823529em) rotate(-30deg)}.hours-12-container>*:nth-of-type(3){transform:rotate(60deg) translate(4.7058823529em) rotate(-60deg)}.hours-12-container>*:nth-of-type(4){transform:rotate(90deg) translate(4.7058823529em) rotate(-90deg)}.hours-12-container>*:nth-of-type(5){transform:rotate(120deg) translate(4.7058823529em) rotate(-120deg)}.hours-12-container>*:nth-of-type(6){transform:rotate(150deg) translate(4.7058823529em) rotate(-150deg)}.hours-12-container>*:nth-of-type(7){transform:rotate(180deg) translate(4.7058823529em) rotate(-180deg)}.hours-12-container>*:nth-of-type(8){transform:rotate(210deg) translate(4.7058823529em) rotate(-210deg)}.hours-12-container>*:nth-of-type(9){transform:rotate(240deg) translate(4.7058823529em) rotate(-240deg)}.hours-12-container>*:nth-of-type(10){transform:rotate(270deg) translate(4.7058823529em) rotate(-270deg)}.hours-12-container>*:nth-of-type(11){transform:rotate(300deg) translate(4.7058823529em) rotate(-300deg)}.hours-12-container>*:nth-of-type(12){transform:rotate(330deg) translate(4.7058823529em) rotate(-330deg)}.hours-12-container .clock-digit{display:block;max-width:100%;border-radius:50%;transition:.15s;font-size:28px;font-weight:500;font-stretch:normal;font-style:normal;line-height:1.21;letter-spacing:.34px;color:var(--ion-color-dark-shade);z-index:5;cursor:pointer}.hours-24-container{position:relative;width:20em;height:20em;padding:0;border-radius:50%;list-style:none;margin:-20rem auto 0}.hours-24-container>*{display:block;position:absolute;top:50%;left:50%;width:2.5em;height:2.5em;margin:-1.25em}.hours-24-container>*:nth-of-type(1){transform:rotate(0) translate(4.7058823529em) rotate(0)}.hours-24-container>*:nth-of-type(2){transform:rotate(30deg) translate(4.7058823529em) rotate(-30deg)}.hours-24-container>*:nth-of-type(3){transform:rotate(60deg) translate(4.7058823529em) rotate(-60deg)}.hours-24-container>*:nth-of-type(4){transform:rotate(90deg) translate(4.7058823529em) rotate(-90deg)}.hours-24-container>*:nth-of-type(5){transform:rotate(120deg) translate(4.7058823529em) rotate(-120deg)}.hours-24-container>*:nth-of-type(6){transform:rotate(150deg) translate(4.7058823529em) rotate(-150deg)}.hours-24-container>*:nth-of-type(7){transform:rotate(180deg) translate(4.7058823529em) rotate(-180deg)}.hours-24-container>*:nth-of-type(8){transform:rotate(210deg) translate(4.7058823529em) rotate(-210deg)}.hours-24-container>*:nth-of-type(9){transform:rotate(240deg) translate(4.7058823529em) rotate(-240deg)}.hours-24-container>*:nth-of-type(10){transform:rotate(270deg) translate(4.7058823529em) rotate(-270deg)}.hours-24-container>*:nth-of-type(11){transform:rotate(300deg) translate(4.7058823529em) rotate(-300deg)}.hours-24-container>*:nth-of-type(12){transform:rotate(330deg) translate(4.7058823529em) rotate(-330deg)}.hours-24-container .clock-digit{display:block;max-width:100%;border-radius:50%;transition:.15s;font-size:16px;font-weight:500;font-stretch:normal;font-style:normal;line-height:1.31;letter-spacing:-.32px;color:#757575;z-index:5;cursor:pointer}\n"]
},] }
];
ClockPickerComponent.ctorParameters = () => [];
ClockPickerComponent.propDecorators = {
pickState: [{ type: Input }],
mode24: [{ type: Input }],
inputTime: [{ type: Input }],
tapConf: [{ type: Input }],
selectChange: [{ type: Output }],
valueSelected: [{ type: Output }],
displayedValue: [{ type: Output }],
hourClock: [{ type: ViewChild, args: ['hourClock',] }],
minuteClock: [{ type: ViewChild, args: ['minuteClock',] }]
};
const NUM_OF_MONTHS_TO_CREATE = 2;
class PickerModal {
constructor(_renderer, _elementRef, params, modalCtrl, ref, calSvc) {
this._renderer = _renderer;
this._elementRef = _elementRef;
this.params = params;
this.modalCtrl = modalCtrl;
this.ref = ref;
this.calSvc = calSvc;
this.GlobalPickState = GlobalPickState;
this.PickMode = PickMode;
this.ionPage = true;
this.datesTemp = [undefined, undefined];
this.timesTemp = [undefined, undefined];
this._scrollLock = true;
this.pickState = GlobalPickState.BEGIN_DATE;
this.clockPickState = ClockPickState.HOUR;
}
is24Hours() {
return this.modalOptions.locale && this.modalOptions.uses24Hours;
}
onSelectChange(cstate) {
this.clockPickState = cstate;
switch (this.pickState) {
case GlobalPickState.BEGIN_HOUR:
this.setPickState(GlobalPickState.BEGIN_MINUTE);
break;
case GlobalPickState.BEGIN_MINUTE:
this.setPickState(GlobalPickState.END_DATE);
break;
case GlobalPickState.END_HOUR:
this.setPickState(GlobalPickState.END_MINUTE);
break;
}
}
onClockValue(time) {
if (this.isBegin(this.pickState)) {
this.timesTemp[0] = time;
}
else {
this.timesTemp[1] = time;
}
if (this.clockPickState == ClockPickState.HOUR) {
return;
}
this.preventInvalidRange();
switch (this.pickState) {
case GlobalPickState.BEGIN_HOUR:
this.setPickState(GlobalPickState.BEGIN_MINUTE);
break;
case GlobalPickState.BEGIN_MINUTE:
this.setPickState(GlobalPickState.END_DATE);
break;
case GlobalPickState.END_HOUR:
this.setPickState(GlobalPickState.END_MINUTE);
break;
}
}
preventInvalidRange() {
if (!this.datesTemp[1] || this.datesTemp[0].time.day === this.datesTemp[1].time.day) {
if (this.timesTemp[0].valueOf() > this.timesTemp[1].valueOf()) {
if (this.isBegin(this.pickState)) {
this.timesTemp[1] = this.timesTemp[0].plus({ minutes: 15 });
}
else {
const ampm = this.getAmPm(1);
if (this.is24Hours() || ampm === 'pm') {
this.timesTemp[0] = this.timesTemp[1].minus({ minutes: 15 });
}
else {
const f = this.timesTemp[1].toFormat('t');
const temp = DateTime.fromFormat(f.replace(ampm, 'pm'), 't', { zone: 'Etc/UTC' });
this.timesTemp[1] = this.timesTemp[1].set({ hour: temp.hour, minute: temp.minute });
}
}
}
}
}
getAmPm2(input) {
const s = input.toLocaleString({ hour: 'numeric', minute: 'numeric', hour12: !this.options.uses24Hours });
return s.substring(s.length - 2).toLowerCase();
}
getDateString(index) {
if (!this.datesTemp[index]) {
index--;
}
return this.datesTemp[index].time.toLocaleString(DateTime.DATE_FULL);
}
getTimeHours(index) {
return this.timesTemp[index].toFormat(this.is24Hours() ? 'HH' : 'hh');
}
getTimeMinutes(index) {
return this.timesTemp[index].toFormat('mm');
}
getAmPm(index) {
return this.getAmPm2(this.timesTemp[index]);
}
setPickState(pstate) {
this.pickState = pstate;
if (this.isHour(pstate)) {
this.clockPickState = ClockPickState.HOUR;
}
else if (this.isMinute(pstate)) {
this.clockPickState = ClockPickState.MINUTE;
}
}
onClickStartDate() {
this.setPickState(GlobalPickState.BEGIN_DATE);
this.scrollToDate(this.datesTemp[0].time);
}
onClickStartHour($event) {
this.setPickState(GlobalPickState.BEGIN_HOUR);
if ($event) {
$event.stopPropagation();
}
}
onClickStartMin($event) {
this.setPickState(GlobalPickState.BEGIN_MINUTE);
if ($event) {
$event.stopPropagation();
}
}
onClickEndDate() {
this.setPickState(GlobalPickState.END_DATE);
this.scrollToDate(this.datesTemp[0].time);
}
onClickEndHour($event) {
this.setPickState(GlobalPickState.END_HOUR);
if ($event) {
$event.stopPropagation();
}
}
onClickEndMin($event) {
this.setPickState(GlobalPickState.END_MINUTE);
if ($event) {
$event.stopPropagation();
}
}
ngOnInit() {
this.init();
this.initDefaultDate();
}
ngAfterViewInit() {
this.findCssClass();
if (this.modalOptions.canBackwardsSelected) {
this.backwardsMonth();
}
}
init() {
this.modalOptions = this.calSvc.safeOpt(this.options);
this.modalOptions.showAdjacentMonthDay = false;
this.step = this.modalOptions.step;
if (this.step > this.calSvc.DEFAULT_STEP) {
this.step = this.calSvc.DEFAULT_STEP;
}
this.calendarMonths = this.calSvc.createMonthsByPeriod(this.modalOptions.from.startOf('day'), this.findInitMonthNumber(this.modalOptions.defaultScrollTo) + this.step, this.modalOptions);
this.setPickState(this.modalOptions.pickState);
}
initDefaultDate() {
const { pickMode,
// defaultDate,
defaultDateRange, defaultDates } = this.modalOptions;
switch (pickMode) {
case PickMode.SINGLE:
// if (defaultDate) {
// this.datesTemp[0] = this.calSvc.createCalendarDay(
// this._getDayTime(defaultDate),
// this._d
// );
// this.datesTemp[1] = this.calSvc.createCalendarDay(
// this._getDayTime(defaultDate),
// this._d
// );
// }
// if ((nowMod.minutes() % 5) > 0) {
// nowMod.minutes(nowMod.minutes() - (nowMod.minutes() % 5));
// }
// this.timesTemp = [nowMod.format('hh:mm a'), nowMod.format('hh:mm a')];
break;
case PickMode.RANGE:
if (defaultDateRange) {
if (defaultDateRange.from) {
this.datesTemp[0] = this.calSvc.createCalendarDay(defaultDateRange.from.startOf('day'), this.modalOptions);
this.timesTemp[0] = defaultDateRange.from;
}
if (defaultDateRange.to) {
this.datesTemp[1] = this.calSvc.createCalendarDay(defaultDateRange.to.startOf('day'), this.modalOptions);
if (defaultDateRange.from >= defaultDateRange.to) {
this.datesTemp[1] = undefined;
this.timesTemp[1] = this.timesTemp[0].plus({
minutes: 30
});
}
else {
this.timesTemp[1] = defaultDateRange.to;
}
}
}
if (this.timesTemp[0].minute % 5 > 0) {
this.timesTemp[0] = this.timesTemp[0].set({ minute: this.timesTemp[0].minute - (this.timesTemp[0].minute % 5) });
}
if (this.timesTemp[1].minute % 5 > 0) {
this.timesTemp[1] = this.timesTemp[1].set({ minute: this.timesTemp[1].minute - (this.timesTemp[1].minute % 5) });
}
break;
case PickMode.MULTI:
if (defaultDates && defaultDates.length) {
this.datesTemp = defaultDates.map((e) => this.calSvc.createCalendarDay(e.startOf('day'), this.modalOptions));
}
break;
default:
this.datesTemp = [undefined, undefined];
}
}
findCssClass() {
const { cssClass } = this.modalOptions;
if (cssClass) {
cssClass.split(' ').forEach((_class) => {
if (_class.trim() !== '') {
this._renderer.addClass(this._elementRef.nativeElement, _class);
}
});
}
}
onChange(data) {
// const { pickMode, autoDone } = this._d;
if (this.pickState === GlobalPickState.BEGIN_DATE) {
this.datesTemp[0] = data[0];
}
else if (this.pickState === GlobalPickState.END_DATE) {
this.datesTemp[1] = data[1];
}
this.modalOptions.tapticConf.onCalendarSelect();
this.ref.detectChanges();
// if (pickMode !== pickModes.MULTI && autoDone && this.canDone()) {
// this.done();
// }
this.repaintDOM();
if (this.modalOptions.changeListener) {
this.modalOptions.changeListener(data);
}
if (this.canDone()) {
if (this.pickState === GlobalPickState.END_DATE) {
setTimeout(() => {
if (!this.modalOptions.fullday) {
this.onClickEndHour(undefined);
}
}, 200);
}
else {
setTimeout(() => {
if (this.modalOptions.fullday) {
this.onClickEndDate();
}
else {
this.onClickStartHour(undefined);
}
}, 200);
}
}
}
onCancel() {
this.modalCtrl.dismiss(undefined, 'cancel');
}
done() {
const { pickMode } = this.modalOptions;
this.preventInvalidRange();
this.modalCtrl.dismiss(this.calSvc.wrapResult(this.datesTemp, this.timesTemp, pickMode), 'done');
}
canDone() {
return true;
}
nextMonth(event) {
const len = this.calendarMonths.length;
const final = this.calendarMonths[len - 1];
const nextTime = final.original.date.plus({ months: 1 });
const rangeEnd = this.modalOptions.to ? this.modalOptions.to.minus({ months: 1 }) : 0;
if (len <= 0 || (rangeEnd !== 0 && final.original.date < rangeEnd)) {
event.target.disabled = true;
return;
}
this.calendarMonths.push(...this.calSvc.createMonthsByPeriod(nextTime, NUM_OF_MONTHS_TO_CREATE, this.modalOptions));
event.target.complete();
this.repaintDOM();
}
backwardsMonth() {
const first = this.calendarMonths[0];
if (first.original.date.valueOf() <= 0) {
this.modalOptions.canBackwardsSelected = false;
return;
}
const firstTime = (this.actualFirstTime = first.original.date.minus({
months: NUM_OF_MONTHS_TO_CREATE
}));
this.calendarMonths.unshift(...this.calSvc.createMonthsByPeriod(firstTime, NUM_OF_MONTHS_TO_CREATE, this.modalOptions));
this.ref.detectChanges();
this.repaintDOM();
}
scrollToDate(date) {
const defaultDateIndex = this.findInitMonthNumber(date);
const monthElement = this.monthsEle.nativeElement.children[`month-${defaultDateIndex}`];
const domElemReadyWaitTime = 300;
setTimeout(() => {
const defaultDateMonth = monthElement ? monthElement.offsetTop : 0;
if (defaultDateIndex !== -1 && defaultDateMonth !== 0) {
this.content.scrollByPoint(0, defaultDateMonth, 128);
}
}, domElemReadyWaitTime);
}
scrollToDefaultDate() {
this.scrollToDate(this.modalOptions.defaultScrollTo);
}
onScroll($event) {
if (!this.modalOptions.canBackwardsSelected) {
return;
}
const { detail } = $event;
if (detail.scrollTop <= 200 && detail.velocityY < 0 && this._scrollLock) {
this.content.getScrollElement().then((scrollElem) => {
this._scrollLock = !1;
const heightBeforeMonthPrepend = scrollElem.scrollHeight;
this.backwardsMonth();
setTimeout(() => {
const heightAfterMonthPrepend = scrollElem.scrollHeight;
this.content.scrollByPoint(0, heightAfterMonthPrepend - heightBeforeMonthPrepend, 0).then(() => {
this._scrollLock = !0;
});
}, 180);
});
}
}
/**
* In some older Safari versions (observed at Mac's Safari 10.0), there is an issue where style updates to
* shadowRoot descendants don't cause a browser repaint.
* See for more details: https://github.com/Polymer/polymer/issues/4701
*/
repaintDOM() {
return this.content.getScrollElement().then((scrollElem) => {
// Update scrollElem to ensure that height of the container changes as Months are appended/prepended
scrollElem.style.zIndex = '2';
scrollElem.style.zIndex = 'initial';
// Update monthsEle to ensure selected state is reflected when tapping on a day
this.monthsEle.nativeElement.style.zIndex = '2';
this.monthsEle.nativeElement.style.zIndex = 'initial';
});
}
findInitMonthNumber(date) {
let startDate = this.actualFirstTime ? this.actualFirstTime : this.modalOptions.from;
const defaultScrollTo = date;
const isAfter = defaultScrollTo > startDate;
if (!isAfter) {
return -1;
}
if (this.showYearPicker) {
startDate = DateTime.fromJSDate(new Date(this.year, 0, 1), { zone: 'Etc/UTC' });
}
return defaultScrollTo.diff(startDate, 'months').milliseconds;
}
_getDayTime(date) {
return DateTime.fromFormat(date.toFormat('yyyy-MM-dd'), 'yyyy-MM-dd', { zone: 'Etc/UTC' }).valueOf();
}
_monthFormat(date) {
return date.toLocaleString({ year: 'numeric', month: 'short' });
}
trackByIndex(index, momentDate) {
return momentDate.original ? momentDate.original.date.valueOf() : index;
}
isBegin(pstate) {
return pstate === GlobalPickState.BEGIN_DATE || pstate === GlobalPickState.BEGIN_HOUR || pstate === GlobalPickState.BEGIN_MINUTE;
}
isEnd(pstate) {
return !this.isBegin(pstate);
}
isDate(pstate) {
return pstate === GlobalPickState.BEGIN_DATE || pstate === GlobalPickState.END_DATE;
}
isTime(pstate) {
return !this.isDate(pstate);
}
isHour(pstate) {
return pstate === GlobalPickState.BEGIN_HOUR || pstate === GlobalPickState.END_HOUR;
}
isMinute(pstate) {
return pstate === GlobalPickState.BEGIN_MINUTE || pstate === GlobalPickState.END_MINUTE;
}
}
PickerModal.decorators = [
{ type: Component, args: [{
selector: 'moots-picker-modal',
animations: [
trigger('openClose', [
state('open', style({
opacity: 1
})),
state('closed', style({
opacity: 0
})),
transition('open => closed', [animate('0.4s')]),
transition('closed => open', [animate('0.5s')])
]),
trigger('enterAnimation', [
transition(':enter', [style({ opacity: 0 }), animate('500ms', style({ opacity: 1 }))]),
transition(':leave', [style({ opacity: 1 }), animate('400ms', style({ opacity: 0 }))])
]),
trigger('highlight', [
state('active', style({
'box-shadow': '0 5px 15px 0 rgba(0, 0, 0, 0.5)',
border: 'solid 2px #f8e71c'
})),
state('inactive', style({
'box-shadow': '0 1px 3px 0 rgba(0, 0, 0, 0.5)',
border: 'solid 2px transparent'
})),
transition('* => *', [animate('0.2s')])
])
],
template: "<div class=\"root-container\">\r\n <div class=\"header-container\">\r\n <div fxLayout=\"row\" fxLayoutAlign=\"space-around center\">\r\n <div\r\n fxLayout=\"column\"\r\n fxLayoutAlign=\"space-between start\"\r\n fxFlex=\"45%\"\r\n class=\"begin-container\"\r\n [@highlight]=\"isBegin(pickState) ? 'active' : 'inactive'\"\r\n (click)=\"onClickStartDate()\"\r\n [style.height.px]=\"this.modalOptions.fullday ? '101' : '148'\"\r\n >\r\n <div class=\"title-label\">{{ this.modalOptions.startLabel }}</div>\r\n <div class=\"date-label\" [class.selected]=\"pickState === GlobalPickState.BEGIN_DATE\">{{ getDateString(0) }}</div>\r\n <div fxLayout=\"row\" fxLayoutAlign=\"start center\" class=\"time-label\" *ngIf=\"!this.modalOptions.fullday\">\r\n <div [class.selected]=\"pickState === GlobalPickState.BEGIN_HOUR\" (click)=\"onClickStartHour($event)\">{{ getTimeHours(0) }}</div>\r\n <div>:</div>\r\n <div [class.selected]=\"pickState === GlobalPickState.BEGIN_MINUTE\" (click)=\"onClickStartMin($event)\">{{ getTimeMinutes(0) }}</div>\r\n <div *ngIf=\"!is24Hours()\" class=\"ampm-indicator\" fxLayoutAlign=\"center center\" (click)=\"onClickStartHour($event)\">\r\n {{ getAmPm(0) }}\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div\r\n fxLayout=\"column\"\r\n fxLayoutAlign=\"space-between start\"\r\n fxFlex=\"45%\"\r\n class=\"end-container\"\r\n [@highlight]=\"isEnd(pickState) ? 'active' : 'inactive'\"\r\n (click)=\"onClickEndDate()\"\r\n [style.height.px]=\"this.modalOptions.fullday ? '101' : '148'\"\r\n >\r\n <div class=\"title-label\">{{ this.modalOptions.endLabel }}</div>\r\n <div class=\"date-label\" [class.selected]=\"pickState === GlobalPickState.END_DATE\">{{ getDateString(1) }}</div>\r\n <div fxLayout=\"row\" fxLayoutAlign=\"start center\" class=\"time-label\" *ngIf=\"!this.modalOptions.fullday\">\r\n <div [class.selected]=\"pickState === GlobalPickState.END_HOUR\" (click)=\"onClickEndHour($event)\">{{ getTimeHours(1) }}</div>\r\n <div>:</div>\r\n <div [class.selected]=\"pickState === GlobalPickState.END_MINUTE\" (click)=\"onClickEndMin($event)\">{{ getTimeMinutes(1) }}</div>\r\n <div *ngIf=\"!is24Hours()\" class=\"ampm-indicator\" fxLayoutAlign=\"center center\" (click)=\"onClickEndHour($event)\">\r\n {{ getAmPm(1) }}\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <ng-content select=\"[sub-header]\"></ng-content>\r\n</div>\r\n\r\n<div *ngIf=\"isTime(pick