UNPKG

ng-circle-countdown

Version:

[![Build Status](https://travis-ci.org/jlevot/ng-circle-countdown.svg?branch=master)](https://travis-ci.org/jlevot/ng-circle-countdown) [![npm version](https://badge.fury.io/js/ng-circle-countdown.svg)](https://badge.fury.io/js/ng-circle-countdown) [![npm

164 lines (157 loc) 12.5 kB
import * as i0 from '@angular/core'; import { signal, computed, effect, Injectable, Pipe, output, input, inject, ContentChild, Component } from '@angular/core'; import { clearInterval, setInterval } from 'worker-timers'; import * as i1 from '@angular/common'; import { CommonModule } from '@angular/common'; const DEFAULT_COUNTER = { isActive: false, isCompleted: false, speed: 1000, remainingTime: 0 }; class CountdownService { constructor() { this.counter = signal(DEFAULT_COUNTER, ...(ngDevMode ? [{ debugName: "counter" }] : [])); this.isActive = computed(() => this.counter().isActive, ...(ngDevMode ? [{ debugName: "isActive" }] : [])); this.interval = 0; this.start = () => this.counter.update((v) => ({ ...v, isActive: true })); this.stop = () => this.counter.update((v) => ({ ...v, isActive: false })); this.setRemainingTime = (remainingTime) => this.counter.update((c) => ({ ...c, remainingTime })); this.resetCounter = (remainingTime) => this.counter.update((c) => ({ ...c, remainingTime, isCompleted: false })); this.getCounter = () => { return this.counter.asReadonly(); }; effect(() => this.tick(this.isActive())); } tick(isActive) { clearInterval(this.interval); if (isActive && this.counter().remainingTime !== 0) { this.interval = setInterval(() => this.counter.set({ ...this.counter(), remainingTime: this.counter().remainingTime - this.counter().speed, isCompleted: (this.counter().remainingTime - this.counter().speed) === 0 }), this.counter().speed); } } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.2", ngImport: i0, type: CountdownService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.2", ngImport: i0, type: CountdownService, providedIn: 'root' }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.2", ngImport: i0, type: CountdownService, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }], ctorParameters: () => [] }); class SvgUtils { static getRGB(color) { const formattedColor = color.replace(/^#?([a-f\d])([a-f\d])([a-f\d])$/i, (m, r, g, b) => `#${r}${r}${g}${g}${b}${b}`); const strippedColor = formattedColor.substring(1); const colorChunks = strippedColor.match(/.{2}/g); if (colorChunks) return colorChunks.map(x => parseInt(x, 16)); return []; } static linearEase(time, start, goal, duration) { if (duration === 0) return start; const currentTime = time / duration; return start + goal * currentTime; } static getPathProps(size, strokeWidth, rotation) { const halfSize = size / 2; const halfStrokeWith = strokeWidth / 2; const arcRadius = halfSize - halfStrokeWith; const arcDiameter = 2 * arcRadius; const rotationIndicator = rotation === 'clockwise' ? '1,0' : '0,1'; const length = Math.round(2 * Math.PI * arcRadius); const path = `m ${halfSize},${halfStrokeWith} a ${arcRadius},${arcRadius} 0 ${rotationIndicator} 0,${arcDiameter} a ${arcRadius},${arcRadius} 0 ${rotationIndicator} 0,-${arcDiameter}`; return { path, length }; } } class FormatTimePipe { transform(time) { return Math.round(time / 1000); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.2", ngImport: i0, type: FormatTimePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); } static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "20.1.2", ngImport: i0, type: FormatTimePipe, isStandalone: true, name: "format_time" }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.2", ngImport: i0, type: FormatTimePipe, decorators: [{ type: Pipe, args: [{ name: 'format_time', standalone: true }] }] }); class CircleCountdownComponent { constructor() { this.countdownCompleted = output(); // Countdown duration this.duration = input(0, ...(ngDevMode ? [{ debugName: "duration" }] : [])); // Primary color for the countdown displaying this.color = input('#004777', ...(ngDevMode ? [{ debugName: "color" }] : [])); // Colors for the countdown displaying this.colors = input([], ...(ngDevMode ? [{ debugName: "colors" }] : [])); // Time interval to dertermine when the countdown should change color this.colorsTime = input([], ...(ngDevMode ? [{ debugName: "colorsTime" }] : [])); // Stroke width this.strokeWidth = input(6, ...(ngDevMode ? [{ debugName: "strokeWidth" }] : [])); // Define the component size to match with your UI this.size = input(180, ...(ngDevMode ? [{ debugName: "size" }] : [])); // Direction of the countdown rotation this.rotation = input('clockwise', ...(ngDevMode ? [{ debugName: "rotation" }] : [])); this.countdownService = inject(CountdownService); this.pathOptions = computed(() => SvgUtils.getPathProps(this.size(), this.strokeWidth(), this.rotation()), ...(ngDevMode ? [{ debugName: "pathOptions" }] : [])); this.countDown = this.countdownService.getCounter(); this.countDownData = computed(() => this.getCountDownData(this.countDown().remainingTime), ...(ngDevMode ? [{ debugName: "countDownData" }] : [])); this.isCompleted = computed(() => this.countDown().isCompleted, ...(ngDevMode ? [{ debugName: "isCompleted" }] : [])); effect(() => { if (this.duration()) this.countdownService.setRemainingTime(this.duration()); if (this.isCompleted()) this.countdownCompleted.emit(); }); } start() { this.countdownService.start(); } pause() { this.countdownService.stop(); } reload() { this.countdownService.resetCounter(this.duration()); this.start(); } getCountDownData(time) { return { currentColor: this.getStrokeColor(time), offset: SvgUtils.linearEase(this.duration() - time, 0, this.pathOptions().length, this.duration()) }; } getStrokeColor(time) { if (this.colors().length === 0 && this.color()) return this.color(); const remainingTime = time / 1000; const currentColorIndex = this.colorsTime()?.findIndex((time, i) => time >= remainingTime && remainingTime >= this.colorsTime()[i + 1]); if (!this.colorsTime() || currentColorIndex === -1) return this.colors()[0]; const currentTime = this.colorsTime()[currentColorIndex] - remainingTime; const currentDuration = this.colorsTime()[currentColorIndex] - this.colorsTime()[currentColorIndex + 1]; const startColorRGB = SvgUtils.getRGB(this.colors()[currentColorIndex]); const endColorRGB = SvgUtils.getRGB(this.colors()[(currentColorIndex + 1 <= this.colors().length - 1) ? currentColorIndex + 1 : this.colors().length - 1]); return `rgb(${startColorRGB.map((color, index) => SvgUtils.linearEase(currentTime, color, endColorRGB[index] - color, currentDuration)).join(',')})`; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.2", ngImport: i0, type: CircleCountdownComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.1.2", type: CircleCountdownComponent, isStandalone: true, selector: "ng-circle-countdown", inputs: { duration: { classPropertyName: "duration", publicName: "duration", isSignal: true, isRequired: false, transformFunction: null }, color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: false, transformFunction: null }, colors: { classPropertyName: "colors", publicName: "colors", isSignal: true, isRequired: false, transformFunction: null }, colorsTime: { classPropertyName: "colorsTime", publicName: "colorsTime", isSignal: true, isRequired: false, transformFunction: null }, strokeWidth: { classPropertyName: "strokeWidth", publicName: "strokeWidth", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, rotation: { classPropertyName: "rotation", publicName: "rotation", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { countdownCompleted: "countdownCompleted" }, queries: [{ propertyName: "counterTemplate", first: true, predicate: ["counter"], descendants: true }], ngImport: i0, template: "<div class=\"base-timer\" [style.width]=\"size() + 'px'\" [style.height]=\"size() + 'px'\">\r\n <svg [attr.viewBox]=\"'0 0 ' + size() + ' ' + size()\" [attr.width]=\"size()\" [attr.height]=\"size()\" xmlns=\"http://www.w3.org/2000/svg\">\r\n <path [attr.d]=\"pathOptions().path\" fill=\"none\" stroke='#d9d9d9' [attr.stroke-width]=\"strokeWidth()\"></path>\r\n\r\n <path [style.stroke]=\"countDownData().currentColor\"\r\n [attr.stroke-width]=\"strokeWidth()\"\r\n [attr.stroke-dashoffset]=\"countDownData().offset\"\r\n [attr.d]=\"pathOptions().path\"\r\n [attr.stroke-dasharray]=\"pathOptions().length\"\r\n stroke-linecap=\"round\"\r\n fill=\"none\">\r\n </path>\r\n </svg>\r\n <div class=\"time\" [style.color]=\"countDownData().currentColor\">\r\n <ng-container *ngTemplateOutlet=\"counterTemplate;\r\n context:{ $implicit: {value: countDown().remainingTime | format_time, color: countDownData().currentColor } }\">\r\n </ng-container>\r\n </div>\r\n</div>\r\n", styles: [":host .base-timer{position:relative}:host .base-timer .time{display:flex;justify-content:center;align-items:center;position:absolute;left:0;top:0;width:100%;height:100%}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "pipe", type: FormatTimePipe, name: "format_time" }] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.2", ngImport: i0, type: CircleCountdownComponent, decorators: [{ type: Component, args: [{ selector: 'ng-circle-countdown', imports: [CommonModule, FormatTimePipe], template: "<div class=\"base-timer\" [style.width]=\"size() + 'px'\" [style.height]=\"size() + 'px'\">\r\n <svg [attr.viewBox]=\"'0 0 ' + size() + ' ' + size()\" [attr.width]=\"size()\" [attr.height]=\"size()\" xmlns=\"http://www.w3.org/2000/svg\">\r\n <path [attr.d]=\"pathOptions().path\" fill=\"none\" stroke='#d9d9d9' [attr.stroke-width]=\"strokeWidth()\"></path>\r\n\r\n <path [style.stroke]=\"countDownData().currentColor\"\r\n [attr.stroke-width]=\"strokeWidth()\"\r\n [attr.stroke-dashoffset]=\"countDownData().offset\"\r\n [attr.d]=\"pathOptions().path\"\r\n [attr.stroke-dasharray]=\"pathOptions().length\"\r\n stroke-linecap=\"round\"\r\n fill=\"none\">\r\n </path>\r\n </svg>\r\n <div class=\"time\" [style.color]=\"countDownData().currentColor\">\r\n <ng-container *ngTemplateOutlet=\"counterTemplate;\r\n context:{ $implicit: {value: countDown().remainingTime | format_time, color: countDownData().currentColor } }\">\r\n </ng-container>\r\n </div>\r\n</div>\r\n", styles: [":host .base-timer{position:relative}:host .base-timer .time{display:flex;justify-content:center;align-items:center;position:absolute;left:0;top:0;width:100%;height:100%}\n"] }] }], ctorParameters: () => [], propDecorators: { counterTemplate: [{ type: ContentChild, args: ['counter'] }] } }); /** * Generated bundle index. Do not edit. */ export { CircleCountdownComponent, CountdownService }; //# sourceMappingURL=ng-circle-countdown.mjs.map