UNPKG

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
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