ngx-countdown
Version:
Simple, easy and performance countdown for angular
309 lines (301 loc) • 11.2 kB
JavaScript
import * as i0 from '@angular/core';
import { inject, NgZone, Injectable, InjectionToken, makeEnvironmentProviders, LOCALE_ID, ChangeDetectorRef, EventEmitter, Component, ViewEncapsulation, ChangeDetectionStrategy, Input, Output, NgModule } from '@angular/core';
import { formatDate, NgTemplateOutlet } from '@angular/common';
var CountdownStatus;
(function (CountdownStatus) {
CountdownStatus[CountdownStatus["ing"] = 0] = "ing";
CountdownStatus[CountdownStatus["pause"] = 1] = "pause";
CountdownStatus[CountdownStatus["stop"] = 2] = "stop";
CountdownStatus[CountdownStatus["done"] = 3] = "done";
})(CountdownStatus || (CountdownStatus = {}));
class CountdownTimer {
ngZone = inject(NgZone);
fns = [];
commands = [];
nextTime = 0;
ing = false;
start() {
if (this.ing === true) {
return;
}
this.ing = true;
this.nextTime = +new Date();
this.ngZone.runOutsideAngular(() => {
this.process();
});
}
process() {
while (this.commands.length) {
this.commands.shift()();
}
let diff = +new Date() - this.nextTime;
const count = 1 + Math.floor(diff / 100);
diff = 100 - (diff % 100);
this.nextTime += 100 * count;
for (let i = 0, len = this.fns.length; i < len; i += 2) {
let frequency = this.fns[i + 1];
// 100/s
if (0 === frequency) {
this.fns[i](count);
// 1000/s
}
else {
// 先把末位至0,再每次加2
frequency += 2 * count - 1;
const step = Math.floor(frequency / 20);
if (step > 0) {
this.fns[i](step);
}
// 把末位还原成1
this.fns[i + 1] = (frequency % 20) + 1;
}
}
if (!this.ing) {
return;
}
setTimeout(() => this.process(), diff);
}
add(fn, frequency) {
this.commands.push(() => {
this.fns.push(fn);
this.fns.push(frequency === 1000 ? 1 : 0);
this.ing = true;
});
return this;
}
remove(fn) {
this.commands.push(() => {
const i = this.fns.indexOf(fn);
if (i !== -1) {
this.fns.splice(i, 2);
}
this.ing = this.fns.length > 0;
});
return this;
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.2", ngImport: i0, type: CountdownTimer, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.0.2", ngImport: i0, type: CountdownTimer });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.2", ngImport: i0, type: CountdownTimer, decorators: [{
type: Injectable
}] });
const COUNTDOWN_CONFIG = new InjectionToken('COUNTDOWN_CONFIG');
function provideCountdown(config) {
return makeEnvironmentProviders([{ provide: COUNTDOWN_CONFIG, useValue: config }]);
}
class CountdownComponent {
locale = inject(LOCALE_ID);
timer = inject(CountdownTimer);
cdr = inject(ChangeDetectorRef);
ngZone = inject(NgZone);
defCog = inject(COUNTDOWN_CONFIG, { optional: true });
frequency = 1000;
_notify = {};
status = CountdownStatus.ing;
isDestroy = false;
_config;
i = {};
left = 0;
set config(i) {
if (i.notify != null && !Array.isArray(i.notify) && i.notify > 0) {
i.notify = [i.notify];
}
this._config = i;
}
get config() {
return this._config;
}
render;
event = new EventEmitter();
/**
* Start countdown, you must manually call when `demand: false`
*/
begin() {
this.status = CountdownStatus.ing;
this.callEvent('start');
}
/**
* Restart countdown
*/
restart() {
if (this.status !== CountdownStatus.stop) {
this.destroy();
}
this.init();
this.callEvent('restart');
}
/**
* Stop countdown, must call `restart` when stopped, it's different from pause, unable to recover
*/
stop() {
if (this.status === CountdownStatus.stop) {
return;
}
this.status = CountdownStatus.stop;
this.destroy();
this.callEvent('stop');
}
/**
* Pause countdown, you can use `resume` to recover again
*/
pause() {
if (this.status === CountdownStatus.stop || this.status === CountdownStatus.pause) {
return;
}
this.status = CountdownStatus.pause;
this.callEvent('pause');
}
/**
* Resume countdown
*/
resume() {
if (this.status === CountdownStatus.stop || this.status !== CountdownStatus.pause) {
return;
}
this.status = CountdownStatus.ing;
this.callEvent('resume');
}
callEvent(action) {
this.event.emit({ action, left: this.left, status: this.status, text: this.i.text });
}
init() {
const config = (this.config = {
demand: false,
leftTime: 0,
format: 'HH:mm:ss',
timezone: '+0000',
formatDate: ({ date, formatStr, timezone }) => {
return formatDate(new Date(date), formatStr, this.locale, timezone || '+0000');
},
...this.defCog,
...this.config,
});
const frq = (this.frequency = ~config.format.indexOf('S') ? 100 : 1000);
this.status = config.demand ? CountdownStatus.pause : CountdownStatus.ing;
this.getLeft();
// bind reflow to me
const _reflow = this.reflow;
this.reflow = (count = 0, force = false) => _reflow.apply(this, [count, force]);
if (Array.isArray(config.notify)) {
config.notify.forEach((time) => {
if (time < 1) {
throw new Error(`The notify config must be a positive integer.`);
}
time = time * 1000;
time = time - (time % frq);
this._notify[time] = true;
});
}
this.timer.add(this.reflow, frq).start();
this.reflow(0, true);
}
destroy() {
this.timer.remove(this.reflow);
return this;
}
/**
* 更新时钟
*/
reflow(count = 0, force = false) {
if (this.isDestroy) {
return;
}
const { status, config, _notify } = this;
if (!force && status !== CountdownStatus.ing) {
return;
}
let value = (this.left = this.left - this.frequency * count);
if (value < 1) {
value = 0;
}
this.i = {
value,
text: config.formatDate({ date: value, formatStr: config.format, timezone: config.timezone }),
};
if (typeof config.prettyText === 'function') {
this.i.text = config.prettyText(this.i.text);
}
this.cdr.detectChanges();
if (config.notify === 0 || _notify[value]) {
this.ngZone.run(() => {
this.callEvent('notify');
});
}
if (value === 0) {
this.ngZone.run(() => {
this.status = CountdownStatus.done;
this.destroy();
this.callEvent('done');
});
}
}
/**
* 获取倒计时剩余帧数
*/
getLeft() {
const { config, frequency } = this;
let left = config.leftTime * 1000;
const end = config.stopTime;
if (!left && end) {
left = end - new Date().getTime();
}
this.left = left - (left % frequency);
}
ngOnInit() {
this.init();
if (!this.config.demand) {
this.begin();
}
}
ngOnDestroy() {
this.isDestroy = true;
this.destroy();
}
ngOnChanges(changes) {
if (!changes.config.firstChange) {
this.restart();
}
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.2", ngImport: i0, type: CountdownComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.2", type: CountdownComponent, isStandalone: true, selector: "countdown", inputs: { config: "config", render: "render" }, outputs: { event: "event" }, host: { properties: { "class.count-down": "true" } }, providers: [CountdownTimer], usesOnChanges: true, ngImport: i0, template: `
(render) {
<ng-container *ngTemplateOutlet="render; context: { $implicit: i }" />
} {
<span [innerHTML]="i.text"></span>
}
`, isInline: true, styles: [".count-down{font-variant-numeric:tabular-nums}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.2", ngImport: i0, type: CountdownComponent, decorators: [{
type: Component,
args: [{ selector: 'countdown', template: `
(render) {
<ng-container *ngTemplateOutlet="render; context: { $implicit: i }" />
} {
<span [innerHTML]="i.text"></span>
}
`, host: { '[class.count-down]': 'true' }, encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, imports: [NgTemplateOutlet], providers: [CountdownTimer], styles: [".count-down{font-variant-numeric:tabular-nums}\n"] }]
}], propDecorators: { config: [{
type: Input,
args: [{ required: true }]
}], render: [{
type: Input
}], event: [{
type: Output
}] } });
class CountdownModule {
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.2", ngImport: i0, type: CountdownModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.0.2", ngImport: i0, type: CountdownModule, imports: [CountdownComponent], exports: [CountdownComponent] });
static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.0.2", ngImport: i0, type: CountdownModule });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.2", ngImport: i0, type: CountdownModule, decorators: [{
type: NgModule,
args: [{
imports: [CountdownComponent],
exports: [CountdownComponent],
}]
}] });
/**
* Generated bundle index. Do not edit.
*/
export { COUNTDOWN_CONFIG, CountdownComponent, CountdownModule, CountdownStatus, CountdownTimer, provideCountdown };
//# sourceMappingURL=ngx-countdown.mjs.map