@ciri/ngx-countdown
Version:
An angular countdown component.
423 lines (416 loc) • 11 kB
JavaScript
import { EventEmitter, Component, ChangeDetectionStrategy, ChangeDetectorRef, Input, Output, NgModule } from '@angular/core';
import { interval } from 'rxjs';
import { animationFrame } from 'rxjs/internal/scheduler/animationFrame';
import { CommonModule } from '@angular/common';
/**
* @fileoverview added by tsickle
* Generated from: lib/utils.ts
* @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
/**
* @param {?} num
* @param {?=} n
* @return {?}
*/
function padZero(num, n = 2) {
/** @type {?} */
const str = num + '';
if (str.length >= n) {
return str;
}
return (Array(n).join('0') + str).slice(-n);
}
/** @type {?} */
const SECOND = 1000;
/** @type {?} */
const MINUTE = 60 * SECOND;
/** @type {?} */
const HOUR = 60 * MINUTE;
/** @type {?} */
const DAY = 24 * HOUR;
/**
* @param {?} time
* @return {?}
*/
function parseTimeData(time) {
/** @type {?} */
const days = Math.floor(time / DAY);
/** @type {?} */
const hours = Math.floor((time % DAY) / HOUR);
/** @type {?} */
const minutes = Math.floor((time % HOUR) / MINUTE);
/** @type {?} */
const seconds = Math.floor((time % MINUTE) / SECOND);
/** @type {?} */
const milliseconds = Math.floor(time % SECOND);
return {
days,
hours,
minutes,
seconds,
milliseconds
};
}
/**
* @param {?} format
* @param {?} timeData
* @return {?}
*/
function parseFormat(format, timeData) {
let { days, hours, minutes, seconds, milliseconds } = timeData;
if (format.indexOf('DD') === -1) {
hours += days * 24;
}
else {
format = format.replace('DD', padZero(days));
}
if (format.indexOf('HH') === -1) {
minutes += hours * 60;
}
else {
format = format.replace('HH', padZero(hours));
}
if (format.indexOf('mm') === -1) {
seconds += minutes * 60;
}
else {
format = format.replace('mm', padZero(minutes));
}
if (format.indexOf('ss') === -1) {
milliseconds += seconds * 1000;
}
else {
format = format.replace('ss', padZero(seconds));
}
if (format.indexOf('S') !== -1) {
/** @type {?} */
const ms = padZero(milliseconds, 3);
if (format.indexOf('SSS') !== -1) {
format = format.replace('SSS', ms);
}
else if (format.indexOf('SS') !== -1) {
format = format.replace('SS', ms.slice(0, 2));
}
else {
format = format.replace('S', ms.charAt(0));
}
}
return format;
}
/**
* @param {?} time1
* @param {?} time2
* @return {?}
*/
function isSameSecond(time1, time2) {
return Math.floor(time1 / 1000) === Math.floor(time2 / 1000);
}
/**
* @fileoverview added by tsickle
* Generated from: lib/countdown.component.ts
* @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
/**
* @record
*/
function CountdownData() { }
if (false) {
/** @type {?} */
CountdownData.prototype.remain;
/** @type {?} */
CountdownData.prototype.formattedTime;
/** @type {?} */
CountdownData.prototype.fragments;
}
/** @enum {number} */
const CountdownState = {
/** 暂停状态 */
paused: 0,
/** 运行状态 */
playing: 1,
/** 完成状态 */
finished: 2,
};
CountdownState[CountdownState.paused] = 'paused';
CountdownState[CountdownState.playing] = 'playing';
CountdownState[CountdownState.finished] = 'finished';
/**
* 所有组件实例使用同一个 interval 流,以免实例多了后造成卡顿
* @type {?}
*/
const instances = [];
/** @type {?} */
const counter$ = interval(0, animationFrame);
/** @type {?} */
let counterSub;
/**
* @return {?}
*/
function setupCounter() {
destroyCounter();
counterSub = counter$.subscribe((/**
* @return {?}
*/
() => {
for (let i = 0; i < instances.length; i++) {
/** @type {?} */
const inst = instances[i];
if (inst.state !== CountdownState.playing) {
continue;
}
if (inst.remain <= 0) {
inst.state = CountdownState.finished;
inst.cdr.detectChanges();
inst.finish.emit();
continue;
}
/** @type {?} */
const remain = Math.max(inst.endTime - Date.now(), 0);
if (!inst.millisecond) {
if (!isSameSecond(remain, inst.remain) || remain === 0) {
inst.remain = remain;
inst.tick.emit(inst.remain);
}
}
else {
inst.remain = remain;
inst.tick.emit(inst.remain);
}
inst.cdr.detectChanges();
}
}));
}
/**
* @return {?}
*/
function destroyCounter() {
counterSub && counterSub.unsubscribe();
}
/**
* 倒计时组件
*/
class CountdownComponent {
/**
* @param {?} cdr
*/
constructor(cdr) {
this.cdr = cdr;
/**
* 格式
*/
this.format = 'HH:mm:ss';
/**
* 是否自动开始
*/
this.autoStart = true;
/**
* 是否开启毫秒级渲染
*/
this.millisecond = false;
/**
* 倒计时完毕时触发
*/
this.finish = new EventEmitter();
/**
* 每倒计时一次都触发
*/
this.tick = new EventEmitter();
this.state = CountdownState.paused;
this._time = 60000;
}
/**
* 总毫秒数
* @param {?} value
* @return {?}
*/
set time(value) {
this._time = Math.max(value, 0);
this.reset();
}
/**
* @return {?}
*/
get time() {
return this._time;
}
/**
* @return {?}
*/
get data() {
/** @type {?} */
const noMillisecond = this.format.indexOf('S') === -1;
/** @type {?} */
const isPlaying = this.state === CountdownState.playing
// 即使 format 中没有设置毫秒,程序计算时候也会将毫秒计算进去,导致运行时秒数看上去总是小 1
// 此处在渲染时候手动 +1 秒,以追求视觉统一
;
// 即使 format 中没有设置毫秒,程序计算时候也会将毫秒计算进去,导致运行时秒数看上去总是小 1
// 此处在渲染时候手动 +1 秒,以追求视觉统一
/** @type {?} */
const timeData = parseTimeData(this.remain + (noMillisecond && isPlaying ? 1000 : 0));
/** @type {?} */
const formattedTime = parseFormat(this.format, timeData);
return {
formattedTime,
remain: this.remain,
fragments: formattedTime.split(':')
};
}
/**
* @return {?}
*/
ngOnInit() {
instances.push(this);
if (instances.length === 1) {
setupCounter();
}
}
/**
* @return {?}
*/
ngOnDestroy() {
this.pause();
/** @type {?} */
const index = instances.indexOf(this);
if (index !== -1) {
instances.splice(index, 1);
}
if (instances.length === 0) {
destroyCounter();
}
}
/**
* 继续倒计时
* @return {?}
*/
start() {
if (this.state === CountdownState.playing) {
return;
}
if (this.state === CountdownState.finished) {
this.remain = this.time;
}
this.endTime = Date.now() + this.remain;
this.state = CountdownState.playing;
}
/**
* 暂停倒计时
* @return {?}
*/
pause() {
this.state = CountdownState.paused;
}
/**
* 重置倒计时
* @return {?}
*/
reset() {
this.pause();
this.remain = this.time;
this.cdr.detectChanges();
if (this.autoStart) {
this.start();
}
}
}
CountdownComponent.decorators = [
{ type: Component, args: [{
selector: 'ngx-countdown',
template: "<ng-container *ngIf=\"!render\">{{ data.formattedTime }}</ng-container>\n<ng-container *ngTemplateOutlet=\"render; context: { $implicit: data }\"></ng-container>\n",
changeDetection: ChangeDetectionStrategy.OnPush
}] }
];
/** @nocollapse */
CountdownComponent.ctorParameters = () => [
{ type: ChangeDetectorRef }
];
CountdownComponent.propDecorators = {
format: [{ type: Input }],
autoStart: [{ type: Input }],
millisecond: [{ type: Input }],
render: [{ type: Input }],
time: [{ type: Input }],
finish: [{ type: Output }],
tick: [{ type: Output }]
};
if (false) {
/**
* 格式
* @type {?}
*/
CountdownComponent.prototype.format;
/**
* 是否自动开始
* @type {?}
*/
CountdownComponent.prototype.autoStart;
/**
* 是否开启毫秒级渲染
* @type {?}
*/
CountdownComponent.prototype.millisecond;
/**
* 自定义模版
* @type {?}
*/
CountdownComponent.prototype.render;
/**
* 倒计时完毕时触发
* @type {?}
*/
CountdownComponent.prototype.finish;
/**
* 每倒计时一次都触发
* @type {?}
*/
CountdownComponent.prototype.tick;
/** @type {?} */
CountdownComponent.prototype.state;
/**
* @type {?}
* @private
*/
CountdownComponent.prototype._time;
/**
* 剩余毫秒数
* @type {?}
* @private
*/
CountdownComponent.prototype.remain;
/**
* @type {?}
* @private
*/
CountdownComponent.prototype.endTime;
/**
* @type {?}
* @private
*/
CountdownComponent.prototype.cdr;
}
/**
* @fileoverview added by tsickle
* Generated from: lib/countdown.module.ts
* @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
class CountdownModule {
}
CountdownModule.decorators = [
{ type: NgModule, args: [{
declarations: [CountdownComponent],
imports: [CommonModule],
exports: [CountdownComponent]
},] }
];
/**
* @fileoverview added by tsickle
* Generated from: public-api.ts
* @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
/**
* @fileoverview added by tsickle
* Generated from: ciri-ngx-countdown.ts
* @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
export { CountdownComponent, CountdownModule, CountdownState };
//# sourceMappingURL=ciri-ngx-countdown.js.map