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
JavaScript
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