UNPKG

ngx-bar-rating

Version:
296 lines (288 loc) 19.4 kB
import * as i0 from '@angular/core'; import { inject, TemplateRef, Directive, InjectionToken, forwardRef, model, input, numberAttribute, booleanAttribute, signal, computed, output, contentChild, Component, ChangeDetectionStrategy, NgModule } from '@angular/core'; import { NgTemplateOutlet } from '@angular/common'; import { NG_VALUE_ACCESSOR, NG_VALIDATORS } from '@angular/forms'; class ActiveRating { constructor() { this.template = inject(TemplateRef); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: ActiveRating, deps: [], target: i0.ɵɵFactoryTarget.Directive }); } static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.1.1", type: ActiveRating, isStandalone: true, selector: "[ratingActive]", ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: ActiveRating, decorators: [{ type: Directive, args: [{ selector: '[ratingActive]' }] }] }); class InactiveRating { constructor() { this.template = inject(TemplateRef); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: InactiveRating, deps: [], target: i0.ɵɵFactoryTarget.Directive }); } static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.1.1", type: InactiveRating, isStandalone: true, selector: "[ratingInactive]", ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: InactiveRating, decorators: [{ type: Directive, args: [{ selector: '[ratingInactive]' }] }] }); class FractionRating { constructor() { this.template = inject(TemplateRef); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: FractionRating, deps: [], target: i0.ɵɵFactoryTarget.Directive }); } static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.1.1", type: FractionRating, isStandalone: true, selector: "[ratingFraction]", ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: FractionRating, decorators: [{ type: Directive, args: [{ selector: '[ratingFraction]' }] }] }); const defaultOptions = { theme: 'default', maxValue: 5, showText: false, readonly: false }; const BAR_RATING_OPTIONS = new InjectionToken('BAR_RATING_OPTIONS', { providedIn: 'root', factory: () => defaultOptions }); function provideBarRatingOptions(options) { return { provide: BAR_RATING_OPTIONS, useValue: { ...defaultOptions, ...options } }; } /** This allows support [(ngModel)] and ngControl. */ const RATING_VALUE_ACCESSOR = { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => BarRating), multi: true }; /** This allows control required validation. */ const RATING_VALUE_VALIDATOR = { provide: NG_VALIDATORS, useExisting: forwardRef(() => BarRating), multi: true, }; var BarRatingUnitStateEnum; (function (BarRatingUnitStateEnum) { BarRatingUnitStateEnum["active"] = "active"; BarRatingUnitStateEnum["inactive"] = "inactive"; BarRatingUnitStateEnum["selected"] = "selected"; BarRatingUnitStateEnum["fraction"] = "fraction"; })(BarRatingUnitStateEnum || (BarRatingUnitStateEnum = {})); class BarRating { constructor() { this.defaultOptions = inject(BAR_RATING_OPTIONS); this.onChange = () => { }; this.onTouched = () => { }; this.UNITS = BarRatingUnitStateEnum; /** Current rating. Can be a decimal value like 3.14 */ this.rate = model(); /** Maximal rating that can be given using this widget. */ this.max = input(this.defaultOptions.maxValue, { transform: numberAttribute }); /** A flag indicating if rating can be updated. */ this.readonly = input(this.defaultOptions.readonly, { transform: booleanAttribute, alias: 'readonly' }); /** Set the theme */ this.theme = input(this.defaultOptions.theme); /** Show rating title */ this.showText = input(this.defaultOptions.showText, { transform: booleanAttribute }); /** Replace rate value with a title */ this.titles = input([]); /** A flag indicating if rating is required for form validation. */ this.required = input(false, { transform: booleanAttribute }); this.tabIndex = input(0, { transform: numberAttribute }); this.hoveredIndex = signal(null); this.contexts = computed(() => { const length = this.max(); const currentRate = this.rate(); const hovered = this.hoveredIndex(); return Array.from({ length }, (_, i) => { if (hovered) { return i + 1 <= hovered ? BarRatingUnitStateEnum.active : BarRatingUnitStateEnum.inactive; } if (i + 1 <= currentRate) { return BarRatingUnitStateEnum.selected; } if ((i + 1 === Math.round(currentRate) && currentRate % 1) >= 0.5) { return BarRatingUnitStateEnum.fraction; } return BarRatingUnitStateEnum.inactive; }); }); this.ratingText = computed(() => { const value = this.hoveredIndex() || this.rate(); return this.titles()[value] || value; }); /** * A stream that forwards a bar rating click since clicks are not propagated */ this.barClick = output(); this.customActiveRating = contentChild(ActiveRating); this.customInActiveRating = contentChild(InactiveRating); this.customFractionRating = contentChild(FractionRating); } updateRating(value) { this.rate.set(value); this.onChange(value); } /** * This is the initial value set to the component */ writeValue(value) { if (value !== null) { this.rate.set(value); } } validate(c) { return (this.required() && !c.value) ? { required: true } : null; } registerOnChange(fn) { this.onChange = fn; } registerOnTouched(fn) { this.onTouched = fn; } setDisabledState(isDisabled) { this.disabled = isDisabled; } handleKeydown(event) { if (this.readonly() || this.disabled) return; const currentRating = this.rate(); let newRating = currentRating; switch (event.key) { case 'ArrowRight': newRating = Math.min(currentRating + 1, this.max()); // Increase rating event.preventDefault(); break; case 'ArrowLeft': newRating = Math.max(currentRating - 1, 1); // Decrease rating event.preventDefault(); break; case 'Enter': case ' ': // Select the current rating (handled in click logic) event.preventDefault(); break; default: return; // Ignore other keys } if (newRating !== currentRating) { this.updateRating(newRating); this.barClick.emit(newRating); this.hoveredIndex.set(newRating); } } handleClick(event) { if (this.readonly() || this.disabled) return; const target = event.target; const unitElement = target.closest('.br-unit'); const value = unitElement?.getAttribute('data-value'); if (value) { const rating = parseInt(value, 10); this.updateRating(rating); this.barClick.emit(rating); } } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: BarRating, deps: [], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.1.1", type: BarRating, isStandalone: true, selector: "bar-rating", inputs: { rate: { classPropertyName: "rate", publicName: "rate", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, theme: { classPropertyName: "theme", publicName: "theme", isSignal: true, isRequired: false, transformFunction: null }, showText: { classPropertyName: "showText", publicName: "showText", isSignal: true, isRequired: false, transformFunction: null }, titles: { classPropertyName: "titles", publicName: "titles", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, tabIndex: { classPropertyName: "tabIndex", publicName: "tabIndex", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { rate: "rateChange", barClick: "barClick" }, providers: [RATING_VALUE_ACCESSOR, RATING_VALUE_VALIDATOR], queries: [{ propertyName: "customActiveRating", first: true, predicate: ActiveRating, descendants: true, isSignal: true }, { propertyName: "customInActiveRating", first: true, predicate: InactiveRating, descendants: true, isSignal: true }, { propertyName: "customFractionRating", first: true, predicate: FractionRating, descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"br br-{{ customInActiveRating() ? '' : theme() }}\"\r\n [class.br-readonly]=\"readonly()\"\r\n [class.br-disabled]=\"disabled\"\r\n role=\"button\"\r\n [attr.tabindex]=\"disabled || readonly() ? -1 : tabIndex()\"\r\n (focusout)=\"onTouched()\"\r\n (click)=\"handleClick($event)\"\r\n (keydown)=\"handleKeydown($event)\">\r\n\r\n <div class=\"br-units\"\r\n (mouseleave)=\"hoveredIndex.set(0)\">\r\n @for (unit of contexts(); let i = $index; track i) {\r\n @let value = i + 1;\r\n <div class=\"br-unit\"\r\n [attr.data-value]=\"value\"\r\n (mouseenter)=\"hoveredIndex.set(value)\">\r\n @switch (unit) {\r\n @case (UNITS.fraction) {\r\n <ng-template [ngTemplateOutlet]=\"customFractionRating()?.template || fractionTemplate\"/>\r\n }\r\n @case (UNITS.inactive) {\r\n <ng-template [ngTemplateOutlet]=\"customInActiveRating()?.template || inactiveTemplate\"/>\r\n }\r\n @default {\r\n <ng-template [ngTemplateOutlet]=\"customActiveRating()?.template || activeTemplate\"/>\r\n }\r\n }\r\n </div>\r\n }\r\n </div>\r\n\r\n @if (showText()) {\r\n <div class=\"br-text\">{{ ratingText() }}</div>\r\n }\r\n</div>\r\n\r\n<ng-template #fractionTemplate>\r\n <div class=\"br-unit-inner br-fraction\"></div>\r\n</ng-template>\r\n\r\n<ng-template #activeTemplate>\r\n <div class=\"br-unit-inner br-active\"></div>\r\n</ng-template>\r\n\r\n<ng-template #inactiveTemplate>\r\n <div class=\"br-unit-inner br-inactive\"></div>\r\n</ng-template>\r\n", styles: ["*{box-sizing:border-box}:host{--_br-font-size: var(--br-font-size, 16px);--_br-gap: var(--br-gap, 0);--_br-active-color: var(--br-active-color, #EDB867);--_br-inactive-color: var(--br-inactive-color, #D2D2D2);--_br-effect-scale: var(--br-effect-scale, 1.5);--_br-effect-duration: var(--br-effect-duration, .4s);--_br-effect-ease: var(--br-effect-ease, ease-out)}.br{position:relative}.br-units{display:flex;flex-wrap:nowrap;gap:var(--_br-gap)}.br-unit{font-size:var(--_br-font-size);cursor:pointer;-webkit-font-smoothing:antialiased;text-rendering:auto}.br-unit-inner{position:relative}.br-readonly .br-unit,.br-disabled .br-unit{cursor:default;pointer-events:none}.br-unit-clone{position:absolute;pointer-events:none;transform-origin:center;animation:scale-fade-out var(--_br-effect-duration) var(--_br-effect-ease)}@keyframes scale-fade-out{0%{transform:scale(1);opacity:1}to{transform:scale(var(--_br-effect-scale));opacity:0}}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: BarRating, decorators: [{ type: Component, args: [{ selector: 'bar-rating', providers: [RATING_VALUE_ACCESSOR, RATING_VALUE_VALIDATOR], imports: [NgTemplateOutlet], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"br br-{{ customInActiveRating() ? '' : theme() }}\"\r\n [class.br-readonly]=\"readonly()\"\r\n [class.br-disabled]=\"disabled\"\r\n role=\"button\"\r\n [attr.tabindex]=\"disabled || readonly() ? -1 : tabIndex()\"\r\n (focusout)=\"onTouched()\"\r\n (click)=\"handleClick($event)\"\r\n (keydown)=\"handleKeydown($event)\">\r\n\r\n <div class=\"br-units\"\r\n (mouseleave)=\"hoveredIndex.set(0)\">\r\n @for (unit of contexts(); let i = $index; track i) {\r\n @let value = i + 1;\r\n <div class=\"br-unit\"\r\n [attr.data-value]=\"value\"\r\n (mouseenter)=\"hoveredIndex.set(value)\">\r\n @switch (unit) {\r\n @case (UNITS.fraction) {\r\n <ng-template [ngTemplateOutlet]=\"customFractionRating()?.template || fractionTemplate\"/>\r\n }\r\n @case (UNITS.inactive) {\r\n <ng-template [ngTemplateOutlet]=\"customInActiveRating()?.template || inactiveTemplate\"/>\r\n }\r\n @default {\r\n <ng-template [ngTemplateOutlet]=\"customActiveRating()?.template || activeTemplate\"/>\r\n }\r\n }\r\n </div>\r\n }\r\n </div>\r\n\r\n @if (showText()) {\r\n <div class=\"br-text\">{{ ratingText() }}</div>\r\n }\r\n</div>\r\n\r\n<ng-template #fractionTemplate>\r\n <div class=\"br-unit-inner br-fraction\"></div>\r\n</ng-template>\r\n\r\n<ng-template #activeTemplate>\r\n <div class=\"br-unit-inner br-active\"></div>\r\n</ng-template>\r\n\r\n<ng-template #inactiveTemplate>\r\n <div class=\"br-unit-inner br-inactive\"></div>\r\n</ng-template>\r\n", styles: ["*{box-sizing:border-box}:host{--_br-font-size: var(--br-font-size, 16px);--_br-gap: var(--br-gap, 0);--_br-active-color: var(--br-active-color, #EDB867);--_br-inactive-color: var(--br-inactive-color, #D2D2D2);--_br-effect-scale: var(--br-effect-scale, 1.5);--_br-effect-duration: var(--br-effect-duration, .4s);--_br-effect-ease: var(--br-effect-ease, ease-out)}.br{position:relative}.br-units{display:flex;flex-wrap:nowrap;gap:var(--_br-gap)}.br-unit{font-size:var(--_br-font-size);cursor:pointer;-webkit-font-smoothing:antialiased;text-rendering:auto}.br-unit-inner{position:relative}.br-readonly .br-unit,.br-disabled .br-unit{cursor:default;pointer-events:none}.br-unit-clone{position:absolute;pointer-events:none;transform-origin:center;animation:scale-fade-out var(--_br-effect-duration) var(--_br-effect-ease)}@keyframes scale-fade-out{0%{transform:scale(1);opacity:1}to{transform:scale(var(--_br-effect-scale));opacity:0}}\n"] }] }] }); class BarRatingEffect { constructor() { this.barRating = inject(BarRating, { host: true, self: true }); // Save the original handleClick method const originalHandleClick = this.barRating.handleClick.bind(this.barRating); // Override the handleClick method this.barRating.handleClick = (event) => { // Call the original handleClick logic originalHandleClick(event); // Add custom effect logic this.handleClickWithEffect(event); }; } handleClickWithEffect(event) { const target = event.target; const unitElement = target.closest('.br-unit'); const clone = unitElement.cloneNode(true); const rect = unitElement.getBoundingClientRect(); const parentRect = event.currentTarget.getBoundingClientRect(); clone.classList.add('br-unit-clone'); clone.style.left = `${rect.left - parentRect.left}px`; clone.style.top = `${rect.top - parentRect.top}px`; clone.style.width = `${rect.width}px`; clone.style.height = `${rect.height}px`; const parentElement = event.currentTarget; parentElement.appendChild(clone); clone.addEventListener('animationend', () => { parentElement.removeChild(clone); }); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: BarRatingEffect, deps: [], target: i0.ɵɵFactoryTarget.Directive }); } static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.1.1", type: BarRatingEffect, isStandalone: true, selector: "bar-rating[effect]", ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: BarRatingEffect, decorators: [{ type: Directive, args: [{ selector: 'bar-rating[effect]', }] }], ctorParameters: () => [] }); class BarRatingModule { static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: BarRatingModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); } static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.1.1", ngImport: i0, type: BarRatingModule, imports: [BarRating, BarRatingEffect, ActiveRating, InactiveRating, FractionRating], exports: [BarRating, BarRatingEffect, ActiveRating, InactiveRating, FractionRating] }); } static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: BarRatingModule }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: BarRatingModule, decorators: [{ type: NgModule, args: [{ imports: [ BarRating, BarRatingEffect, ActiveRating, InactiveRating, FractionRating ], exports: [ BarRating, BarRatingEffect, ActiveRating, InactiveRating, FractionRating ] }] }] }); /** * Generated bundle index. Do not edit. */ export { ActiveRating, BAR_RATING_OPTIONS, BarRating, BarRatingEffect, BarRatingModule, FractionRating, InactiveRating, provideBarRatingOptions }; //# sourceMappingURL=ngx-bar-rating.mjs.map