UNPKG

ng-rating-pro

Version:

A powerful and customizable Angular rating component that allows full and half ratings with support for read-only mode, dynamic scaling, and SVG customization.<br/>Perfect for use in reviews, feedback forms, and rating-based applications.

234 lines (225 loc) 17 kB
import * as i0 from '@angular/core'; import { Directive, Component, EventEmitter, ViewContainerRef, Input, ContentChildren, ViewChild, Output, HostListener, NgModule } from '@angular/core'; import * as i1 from '@angular/common'; import { CommonModule } from '@angular/common'; var State$1; (function (State) { State["Empty"] = "empty"; State["Half"] = "half"; State["Full"] = "full"; })(State$1 || (State$1 = {})); class CustomRatingDirective { constructor(el, renderer) { this.el = el; this.renderer = renderer; this.iconViewBox = [0, 0, 19, 18]; } updateRating(iconName) { this.iconViewBox = this.getIconViewBox(); this.updateRatindId(iconName); } updateRatindId(iconName) { const iconId = iconName + '-' + this.el.nativeElement.attributes.getNamedItem('ngProjectAs').value; this.el.nativeElement.setAttribute('id', iconId); } getIconViewBox() { const dims = this.el.nativeElement.attributes .getNamedItem('viewBox') .value.split(' '); return dims.map((dim) => parseInt(dim)); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CustomRatingDirective, deps: [{ token: i0.ElementRef }, { token: i0.Renderer2 }], target: i0.ɵɵFactoryTarget.Directive }); } static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "17.3.12", type: CustomRatingDirective, selector: "[ngCustomRating]", ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CustomRatingDirective, decorators: [{ type: Directive, args: [{ selector: '[ngCustomRating]', }] }], ctorParameters: () => [{ type: i0.ElementRef }, { type: i0.Renderer2 }] }); class StarIconComponent { static { this.iconName = 'star'; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: StarIconComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "17.3.12", type: StarIconComponent, selector: "ng-star-icon", ngImport: i0, template: "<ng-container>\n <svg:symbol id=\"star-empty\" viewBox=\"0 0 24 24\" fill=\"#F1E8CA\">\n <path\n d=\"M12 18l-7.056 3.71 1.348-7.853L.558 9.17l7.923-1.15L12 0l3.519 7.99 7.923 1.18-5.734 5.697 1.348 7.853z\"\n />\n </svg:symbol>\n <svg:symbol id=\"star-full\" viewBox=\"0 0 24 24\" fill=\"#D3A81E\">\n <path\n d=\"M12 18l-7.056 3.71 1.348-7.853L.558 9.17l7.923-1.15L12 0l3.519 7.99 7.923 1.18-5.734 5.697 1.348 7.853z\"\n />\n </svg:symbol>\n <svg:symbol id=\"star-half\" viewBox=\"0 0 24 24\">\n <!-- Left (filled) half of the star -->\n <path\n d=\"M12 0 \n L12 18 \n L4.944 21.71 \n L6.292 13.857 \n L0.558 9.17 \n L8.481 7.99 \n L12 0Z\"\n fill=\"#D3A81E\"\n />\n <!-- Right (empty) half of the star -->\n <path\n d=\"M12 0 \n L12 18 \n L19.056 21.71 \n L17.708 13.857 \n L23.442 9.17 \n L15.519 7.99 \n L12 0Z\"\n fill=\"#F1E8CA\"\n />\n </svg:symbol>\n</ng-container>\n" }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: StarIconComponent, decorators: [{ type: Component, args: [{ selector: 'ng-star-icon', template: "<ng-container>\n <svg:symbol id=\"star-empty\" viewBox=\"0 0 24 24\" fill=\"#F1E8CA\">\n <path\n d=\"M12 18l-7.056 3.71 1.348-7.853L.558 9.17l7.923-1.15L12 0l3.519 7.99 7.923 1.18-5.734 5.697 1.348 7.853z\"\n />\n </svg:symbol>\n <svg:symbol id=\"star-full\" viewBox=\"0 0 24 24\" fill=\"#D3A81E\">\n <path\n d=\"M12 18l-7.056 3.71 1.348-7.853L.558 9.17l7.923-1.15L12 0l3.519 7.99 7.923 1.18-5.734 5.697 1.348 7.853z\"\n />\n </svg:symbol>\n <svg:symbol id=\"star-half\" viewBox=\"0 0 24 24\">\n <!-- Left (filled) half of the star -->\n <path\n d=\"M12 0 \n L12 18 \n L4.944 21.71 \n L6.292 13.857 \n L0.558 9.17 \n L8.481 7.99 \n L12 0Z\"\n fill=\"#D3A81E\"\n />\n <!-- Right (empty) half of the star -->\n <path\n d=\"M12 0 \n L12 18 \n L19.056 21.71 \n L17.708 13.857 \n L23.442 9.17 \n L15.519 7.99 \n L12 0Z\"\n fill=\"#F1E8CA\"\n />\n </svg:symbol>\n</ng-container>\n" }] }] }); class HeartIconComponent { static { this.iconName = 'heart'; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: HeartIconComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "17.3.12", type: HeartIconComponent, selector: "ng-heart-icon", ngImport: i0, template: "<ng-container>\n <svg:symbol\n id=\"heart-empty\"\n viewBox=\"0 0 24 24\"\n fill=\"#FFFFFF\"\n stroke=\"#FFCDD2\"\n >\n <path\n d=\"M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z\"\n />\n </svg:symbol>\n\n <svg:symbol id=\"heart-full\" viewBox=\"0 0 24 24\" fill=\"#FF0000\">\n <path\n d=\"M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z\"\n />\n </svg:symbol>\n\n <svg:symbol id=\"heart-half\" viewBox=\"0 0 24 24\">\n <!-- Right (empty) half of the heart -->\n <path\n d=\"M12,21.35L12,5.09C13.09,3.81 14.76,3 16.5,3C19.58,3 22,5.42 22,8.5C22,12.28 18.6,15.36 13.45,20.03L12,21.35Z\"\n fill=\"#FFFFFF\"\n stroke=\"#FFCDD2\"\n />\n\n <!-- Left (filled) half of the heart -->\n <path\n d=\"M12,21.35L12,5.09C10.91,3.81 9.24,3 7.5,3C4.42,3 2,5.42 2,8.5C2,12.28 5.4,15.36 10.55,20.03L12,21.35Z\"\n fill=\"#FF0000\"\n />\n </svg:symbol>\n</ng-container>\n" }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: HeartIconComponent, decorators: [{ type: Component, args: [{ selector: 'ng-heart-icon', template: "<ng-container>\n <svg:symbol\n id=\"heart-empty\"\n viewBox=\"0 0 24 24\"\n fill=\"#FFFFFF\"\n stroke=\"#FFCDD2\"\n >\n <path\n d=\"M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z\"\n />\n </svg:symbol>\n\n <svg:symbol id=\"heart-full\" viewBox=\"0 0 24 24\" fill=\"#FF0000\">\n <path\n d=\"M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z\"\n />\n </svg:symbol>\n\n <svg:symbol id=\"heart-half\" viewBox=\"0 0 24 24\">\n <!-- Right (empty) half of the heart -->\n <path\n d=\"M12,21.35L12,5.09C13.09,3.81 14.76,3 16.5,3C19.58,3 22,5.42 22,8.5C22,12.28 18.6,15.36 13.45,20.03L12,21.35Z\"\n fill=\"#FFFFFF\"\n stroke=\"#FFCDD2\"\n />\n\n <!-- Left (filled) half of the heart -->\n <path\n d=\"M12,21.35L12,5.09C10.91,3.81 9.24,3 7.5,3C4.42,3 2,5.42 2,8.5C2,12.28 5.4,15.36 10.55,20.03L12,21.35Z\"\n fill=\"#FF0000\"\n />\n </svg:symbol>\n</ng-container>\n" }] }] }); var State; (function (State) { State["Empty"] = "empty"; State["Half"] = "half"; State["Full"] = "full"; })(State || (State = {})); class NgRatingProComponent { constructor() { this.scale = 5; this.rating = 0; this.allowHalf = true; this.size = 20; // Default size this.spacing = 8; // Spacing between stars in viewBox units this.readonly = false; this.iconName = 'star'; this.ratingChange = new EventEmitter(); this.states = []; this.starWidth = 24; this.starHeight = 24; this.containerWidth = 0; this.totalWidth = 0; } ngOnInit() { this.updateStates(this.allowHalf); } ngAfterContentInit() { if (this.ratingDirectives.length !== 3) { this.loadDynamicComponent(); } else { this.ratingDirectives.forEach((directive) => { directive.updateRating(this.iconName); }); this.starHeight = this.ratingDirectives.first.iconViewBox[3]; this.starWidth = this.ratingDirectives.first.iconViewBox[2]; } this.updateDimensions(); } loadDynamicComponent() { const component = this.getComponent(); this.dynamicContainer.clear(); this.dynamicContainer.createComponent(component); this.iconName = component.iconName; } getComponent() { switch (this.iconName) { case 'star': return StarIconComponent; case 'heart': return HeartIconComponent; default: return StarIconComponent; } } updateDimensions() { // Calculate total width in viewBox units this.totalWidth = this.starWidth * this.scale + this.spacing * (this.scale - 1); // Calculate container width maintaining aspect ratio const aspectRatio = this.totalWidth / this.starHeight; this.containerWidth = this.size * aspectRatio; } getStarPosition(index) { return index * (this.starWidth + this.spacing); } onClick(event) { if (this.readonly) return; if (event.target instanceof SVGElement && +event.target.id > 0) { const clickedIndex = +event.target.id - 1; this.toggleRating(clickedIndex); this.updateStates(this.allowHalf); } event.stopPropagation(); } toggleRating(index) { const isFull = this.rating === index + 1; const isHalf = this.rating === index + 0.5; if (this.allowHalf) { if (isFull) { this.rating = index; } else if (isHalf) { this.rating = index + 1; } else { this.rating = index + 0.5; } } else { if (isFull) { this.rating = index; } else { this.rating = index + 1; } } } updateStates(allowHalf) { this.ratingChange.emit(this.rating); this.rating = Math.round(this.rating * 2) / 2; this.states = Array.from({ length: this.scale }, (_, i) => { if (i < Math.floor(this.rating)) return State.Full; if (i === Math.floor(this.rating) && this.rating % 1 !== 0 && allowHalf) return State.Half; return State.Empty; }); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: NgRatingProComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "17.3.12", type: NgRatingProComponent, selector: "ngRatingPro", inputs: { scale: "scale", rating: "rating", allowHalf: "allowHalf", size: "size", spacing: "spacing", readonly: "readonly", iconName: "iconName" }, outputs: { ratingChange: "ratingChange" }, host: { listeners: { "click": "onClick($event)" } }, queries: [{ propertyName: "ratingDirectives", predicate: CustomRatingDirective }], viewQueries: [{ propertyName: "dynamicContainer", first: true, predicate: ["dynamicContainer"], descendants: true, read: ViewContainerRef, static: true }], ngImport: i0, template: "<div\n [style.width]=\"containerWidth + 'px'\"\n [style.height]=\"size + 'px'\"\n class=\"rating-container\"\n>\n <!-- Define symbols -->\n <svg style=\"display: none\">\n <ng-container #dynamicContainer></ng-container>\n <ng-content select=\"half\"></ng-content>\n <ng-content select=\"full\"></ng-content>\n <ng-content select=\"empty\"></ng-content>\n </svg>\n\n <!-- Rating display -->\n <svg\n [attr.viewBox]=\"'0 0 ' + totalWidth + ' ' + starHeight\"\n [style.width]=\"'100%'\"\n [style.height]=\"'100%'\"\n aria-hidden=\"true\"\n focusable=\"false\"\n class=\"rating\"\n >\n <ng-container *ngFor=\"let state of states; let i = index\">\n <use\n [id]=\"i + 1\"\n [attr.x]=\"getStarPosition(i)\"\n [attr.y]=\"0\"\n [attr.width]=\"starWidth\"\n [attr.height]=\"starHeight\"\n [attr.xlink:href]=\"'#' + iconName + '-' + state\"\n [ngStyle]=\"{ cursor: !readonly ? 'pointer' : 'default' }\"\n />\n </ng-container>\n </svg>\n</div>\n", styles: [":host{-webkit-user-select:none;user-select:none}.rating-container{display:inline-block;position:relative}\n"], dependencies: [{ kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: NgRatingProComponent, decorators: [{ type: Component, args: [{ selector: 'ngRatingPro', template: "<div\n [style.width]=\"containerWidth + 'px'\"\n [style.height]=\"size + 'px'\"\n class=\"rating-container\"\n>\n <!-- Define symbols -->\n <svg style=\"display: none\">\n <ng-container #dynamicContainer></ng-container>\n <ng-content select=\"half\"></ng-content>\n <ng-content select=\"full\"></ng-content>\n <ng-content select=\"empty\"></ng-content>\n </svg>\n\n <!-- Rating display -->\n <svg\n [attr.viewBox]=\"'0 0 ' + totalWidth + ' ' + starHeight\"\n [style.width]=\"'100%'\"\n [style.height]=\"'100%'\"\n aria-hidden=\"true\"\n focusable=\"false\"\n class=\"rating\"\n >\n <ng-container *ngFor=\"let state of states; let i = index\">\n <use\n [id]=\"i + 1\"\n [attr.x]=\"getStarPosition(i)\"\n [attr.y]=\"0\"\n [attr.width]=\"starWidth\"\n [attr.height]=\"starHeight\"\n [attr.xlink:href]=\"'#' + iconName + '-' + state\"\n [ngStyle]=\"{ cursor: !readonly ? 'pointer' : 'default' }\"\n />\n </ng-container>\n </svg>\n</div>\n", styles: [":host{-webkit-user-select:none;user-select:none}.rating-container{display:inline-block;position:relative}\n"] }] }], propDecorators: { scale: [{ type: Input }], rating: [{ type: Input }], allowHalf: [{ type: Input }], size: [{ type: Input }], spacing: [{ type: Input }], readonly: [{ type: Input }], iconName: [{ type: Input }], ratingDirectives: [{ type: ContentChildren, args: [CustomRatingDirective] }], dynamicContainer: [{ type: ViewChild, args: ['dynamicContainer', { read: ViewContainerRef, static: true }] }], ratingChange: [{ type: Output }], onClick: [{ type: HostListener, args: ['click', ['$event']] }] } }); class NgRatingProModule { static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: NgRatingProModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); } static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "17.3.12", ngImport: i0, type: NgRatingProModule, declarations: [NgRatingProComponent, CustomRatingDirective], imports: [CommonModule], exports: [NgRatingProComponent, CustomRatingDirective] }); } static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: NgRatingProModule, imports: [CommonModule] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: NgRatingProModule, decorators: [{ type: NgModule, args: [{ declarations: [ NgRatingProComponent, CustomRatingDirective, ], imports: [CommonModule], exports: [NgRatingProComponent, CustomRatingDirective], }] }] }); /* * Public API Surface of ng-rating-pro */ /** * Generated bundle index. Do not edit. */ export { CustomRatingDirective, NgRatingProComponent, NgRatingProModule, State }; //# sourceMappingURL=ng-rating-pro.mjs.map