UNPKG

ngx-countdown

Version:

Simple, easy and performance countdown for angular

309 lines (301 loc) 11.2 kB
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: ` @if (render) { <ng-container *ngTemplateOutlet="render; context: { $implicit: i }" /> } @else { <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: ` @if (render) { <ng-container *ngTemplateOutlet="render; context: { $implicit: i }" /> } @else { <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