{"version":3,"file":"ngwr-rating.mjs","sources":["../../../projects/lib/rating/rating.ts","../../../projects/lib/rating/rating.html","../../../projects/lib/rating/ngwr-rating.ts"],"sourcesContent":["/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { coerceBooleanProperty, coerceNumberProperty } from '@angular/cdk/coercion';\nimport { Component, ViewEncapsulation, computed, forwardRef, input, signal } from '@angular/core';\nimport { type ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';\n\nimport { clamp, noop } from 'ngwr/utils';\n\n/**\n * Star rating input. Click or hover to set a value; supports half-star\n * increments via `step=\"0.5\"`. Implements `ControlValueAccessor` — value\n * is `number | null` (clamped to `[0, count]`).\n *\n * Keyboard: `←` / `→` bump by `step`, `Home` / `End` jump to 0 / max.\n * Clicking the current value clears it (toggle off).\n *\n * @example\n * ```html\n * <wr-rating [(ngModel)]=\"score\" />\n * <wr-rating [(ngModel)]=\"score\" [count]=\"10\" step=\"0.5\" />\n * <wr-rating [ngModel]=\"4.5\" [readonly]=\"true\" />\n * ```\n *\n * @see https://ngwr.dev/components/rating\n */\nexport type WrRatingSize = 'sm' | 'md' | 'lg';\n\n@Component({\n  selector: 'wr-rating',\n  templateUrl: './rating.html',\n  encapsulation: ViewEncapsulation.None,\n  host: { '[class]': 'classes()' },\n  providers: [\n    {\n      provide: NG_VALUE_ACCESSOR,\n      // eslint-disable-next-line @angular-eslint/no-forward-ref\n      useExisting: forwardRef(() => WrRating),\n      multi: true,\n    },\n  ],\n})\nexport class WrRating implements ControlValueAccessor {\n  /** Total number of slots. @default 5 */\n  readonly count = input(5, { transform: (v: unknown): number => Math.max(1, coerceNumberProperty(v, 5)) });\n\n  /** Step granularity — `1` for whole stars, `0.5` for halves. @default 1 */\n  readonly step = input(1 as 0.5 | 1, {\n    transform: (v: unknown): 0.5 | 1 => (Number(v) === 0.5 ? 0.5 : 1),\n  });\n\n  /** Read-only — value is displayed but not interactive. @default false */\n  readonly readonly = input(false, { transform: coerceBooleanProperty });\n\n  /** Disable interaction. @default false */\n  readonly disabled = input(false, { transform: coerceBooleanProperty });\n\n  /** Control size — scales the icons + gaps. @default 'md' */\n  readonly size = input<WrRatingSize>('md');\n\n  /** Accessible label. @default 'Rating' */\n  readonly ariaLabel = input<string>('Rating');\n\n  /** Last committed value (mirrored from the CVA). */\n  protected readonly value = signal<number | null>(null);\n\n  /** Transient hover preview — overrides `value` for display when set. */\n  protected readonly hoverValue = signal<number | null>(null);\n\n  private readonly disabledFromCva = signal(false);\n\n  protected readonly effectiveDisabled = computed(() => this.disabled() || this.disabledFromCva());\n\n  protected readonly interactive = computed(() => !this.effectiveDisabled() && !this.readonly());\n\n  /** What we actually render — hover wins while hovering. */\n  protected readonly displayValue = computed(() => this.hoverValue() ?? this.value() ?? 0);\n\n  protected readonly slots = computed(() => Array.from({ length: this.count() }, (_, i) => i));\n\n  protected readonly classes = computed(() => {\n    const parts = ['wr-rating'];\n    const size = this.size();\n    if (size !== 'md') parts.push(`wr-rating--${size}`);\n    if (this.readonly()) parts.push('wr-rating--readonly');\n    if (this.effectiveDisabled()) parts.push('wr-rating--disabled');\n    return parts.join(' ');\n  });\n\n  /** Fill ratio for slot `i` in `[0, 1]` — drives the CSS clip width. */\n  protected fillFor(i: number): number {\n    return clamp(this.displayValue() - i, 0, 1);\n  }\n\n  // ControlValueAccessor\n\n  private onChange: (value: number | null) => void = noop;\n  private onTouched: () => void = noop;\n\n  writeValue(value: number | null): void {\n    this.value.set(value === null || value === undefined ? null : clamp(Number(value), 0, this.count()));\n  }\n\n  registerOnChange(fn: (value: number | null) => void): void {\n    this.onChange = fn;\n  }\n\n  registerOnTouched(fn: () => void): void {\n    this.onTouched = fn;\n  }\n\n  setDisabledState(isDisabled: boolean): void {\n    this.disabledFromCva.set(coerceBooleanProperty(isDisabled));\n  }\n\n  // Template handlers\n\n  protected onSlotMove(event: MouseEvent, index: number): void {\n    if (!this.interactive()) return;\n    this.hoverValue.set(this.computeValue(event, index));\n  }\n\n  protected onLeave(): void {\n    this.hoverValue.set(null);\n  }\n\n  protected onSlotClick(event: MouseEvent, index: number): void {\n    if (!this.interactive()) return;\n    const next = this.computeValue(event, index);\n    // Click the current value → clear it.\n    const final = next === this.value() ? null : next;\n    this.commit(final);\n  }\n\n  protected onKeydown(event: KeyboardEvent): void {\n    if (!this.interactive()) return;\n    const max = this.count();\n    const step = this.step();\n    const current = this.value() ?? 0;\n    let next: number | null;\n    switch (event.key) {\n      case 'ArrowRight':\n      case 'ArrowUp':\n        next = clamp(current + step, 0, max);\n        break;\n      case 'ArrowLeft':\n      case 'ArrowDown':\n        next = clamp(current - step, 0, max);\n        break;\n      case 'Home':\n        next = 0;\n        break;\n      case 'End':\n        next = max;\n        break;\n      case 'Delete':\n      case 'Backspace':\n        next = null;\n        break;\n      default:\n        return;\n    }\n    event.preventDefault();\n    this.commit(next);\n  }\n\n  // Internals\n\n  /** Convert a mouse position over slot `index` to a snapped value. */\n  private computeValue(event: MouseEvent, index: number): number {\n    const target = event.currentTarget as HTMLElement;\n    const rect = target.getBoundingClientRect();\n    const ratio = clamp((event.clientX - rect.left) / rect.width, 0, 1);\n    const raw = index + ratio;\n    const step = this.step();\n    const snapped = Math.ceil(raw / step) * step;\n    return clamp(snapped, step, this.count());\n  }\n\n  private commit(value: number | null): void {\n    this.value.set(value);\n    this.hoverValue.set(null);\n    this.onChange(value);\n    this.onTouched();\n  }\n}\n","<div\n  class=\"wr-rating__row\"\n  role=\"slider\"\n  [attr.aria-label]=\"ariaLabel()\"\n  [attr.aria-valuemin]=\"0\"\n  [attr.aria-valuemax]=\"count()\"\n  [attr.aria-valuenow]=\"value() ?? 0\"\n  [attr.aria-readonly]=\"readonly() || null\"\n  [attr.aria-disabled]=\"effectiveDisabled() || null\"\n  [attr.tabindex]=\"interactive() ? 0 : -1\"\n  (keydown)=\"onKeydown($event)\"\n  (mouseleave)=\"onLeave()\"\n>\n  @for (slot of slots(); track $index) {\n    <!-- The parent row owns keyboard focus + handlers; slots are pointer-only\n         click targets, so the a11y rules below are intentionally suppressed. -->\n    <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events,@angular-eslint/template/interactive-supports-focus -->\n    <span\n      class=\"wr-rating__slot\"\n      [class.wr-rating__slot--interactive]=\"interactive()\"\n      [style.--wr-rating-fill]=\"fillFor($index)\"\n      (mousemove)=\"onSlotMove($event, $index)\"\n      (click)=\"onSlotClick($event, $index)\"\n    >\n      <!-- Outline (always visible) -->\n      <svg class=\"wr-rating__icon wr-rating__icon--bg\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n        <path\n          d=\"M12 2.5l2.95 5.98 6.6.96-4.78 4.66 1.13 6.58L12 17.6l-5.9 3.08 1.13-6.58L2.45 9.44l6.6-.96L12 2.5z\"\n          fill=\"none\"\n          stroke=\"currentColor\"\n          stroke-width=\"1.4\"\n          stroke-linejoin=\"round\"\n        />\n      </svg>\n      <!-- Filled overlay, clipped by --wr-rating-fill (0..1) -->\n      <svg class=\"wr-rating__icon wr-rating__icon--fg\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n        <path\n          d=\"M12 2.5l2.95 5.98 6.6.96-4.78 4.66 1.13 6.58L12 17.6l-5.9 3.08 1.13-6.58L2.45 9.44l6.6-.96L12 2.5z\"\n          fill=\"currentColor\"\n          stroke=\"currentColor\"\n          stroke-width=\"1.4\"\n          stroke-linejoin=\"round\"\n        />\n      </svg>\n    </span>\n  }\n</div>\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}