{"version":3,"file":"product-rating-stars-section.min.js","mappings":"wZAOA,MAAMA,2BAA2BC,YAC/B,6BAAkBC,GAChB,MAAO,CACL,aACA,eACA,YAEJ,CAqCO,iBAAAC,GACLC,KAAKC,uBAELD,KAAKE,QACP,CAEO,wBAAAC,CAAyBC,EAAeC,EAAkBC,GAC/DN,KAAKC,uBAEDD,KAAKO,aAAeF,IAAaC,GACnCN,KAAKE,QAET,CAEQ,oBAAAD,GACDD,KAAKQ,aACRR,KAAKS,aAAa,CAAEC,KAAM,SAC1BV,KAAKQ,WAAWG,UAAY,wiFAEhC,CAEQ,MAAAT,GACN,MAAMU,EAAQC,SAASb,KAAKc,QAAQF,MAAO,IACrCG,EAAUC,WAAWhB,KAAKc,QAAQC,SAExC,GAAIE,MAAML,IAAUK,MAAMF,GACxB,OAWF,MAAMG,EAAgBlB,KAAKQ,WAAWW,cAAc,KAEhDnB,KAAKc,QAAQM,MACfF,EAAcE,KAAOpB,KAAKc,QAAQM,KAClCF,EAAcG,gBAAgB,UAE9BH,EAAcG,gBAAgB,QAC9BH,EAAcI,KAAO,OAGvBtB,KAAKuB,cAEL,MAAMC,EAAexB,KAAKQ,WAAWW,cAAc,iBAC7CM,EAAY,IAAIC,KAAKC,aAAa,SACxCH,EAAaI,YAAcH,EAAUI,OAAOjB,GAG5C,IAAIkB,EAAa,SACH,IAAVlB,IACFkB,GAA0B,KAHP9B,KAAKQ,WAAWW,cAAc,UAMtCS,YAAcE,EAEb,IAAVlB,EACFM,EAAca,aAAa,aAAc,kBACtB,IAAVnB,EACTM,EAAca,aAAa,aAAc,WAAYhB,EAAQiB,QAAQ,aAErEd,EAAca,aAAa,aACzB,mBAAoBhB,EAAQiB,QAAQ,eAAiBpB,aAE3D,CAEQ,WAAAW,GAGN,MAAMU,EAASjB,WAAWhB,KAAKc,QAAQC,SAMjCmB,EAAUrB,SAASb,KAAKc,QAAQC,QAAS,IAC/C,IAAIoB,EAAU,EACE,IAAZD,EACFC,EAAU,EACDF,GAAUC,EAAU,IAC7BC,EAAUD,EAAU,IACXD,GAAUC,EAAU,GAC7BC,EAAUD,EAAU,GACXD,EAASC,EAAU,IAAOD,GAAUC,EAAU,IACvDC,EAAUD,EAAU,GACXD,GAAUC,EAAU,MAC7BC,EAAUD,EAAU,GAGRlC,KAAKQ,WAAWW,cAA2B,iBACnDiB,MAAMC,QAAU,aAAaF,GACrC,EAYGG,eAAeC,IAAI,yBACtBD,eAAeE,OAAO,uBAAwB5C","sources":["webpack://lange/./sections/product-rating-stars/product-rating-stars/index.ts"],"sourcesContent":["import type { CustomElement } from '@integrabeauty/custom-elements';\nimport html from './index.html';\nimport styles from './index.scss';\n\n/**\n * Renders shaded stars based on a product's average review score along with a count of reviews.\n */\nclass ProductRatingStars extends HTMLElement implements CustomElement {\n  public static get observedAttributes() {\n    return [\n      'data-count',\n      'data-average',\n      'data-href'\n    ];\n  }\n\n  declare readonly dataset: {\n    /**\n     * Value to determine alignment of stars in comparison to their container\n     */\n    align: 'center' | 'left' | 'right';\n\n    /**\n     * Average rating\n     */\n    average: string;\n\n    /**\n     * Number of reviews\n     */\n    count: string;\n\n    /**\n     * Where the stars link to. This is either a full url to the product page ending in the reviews\n     * section hash, for linking in from somewhere else, or just the hash on its own, for linking\n     * within the product page. When it is absent the stars do not link anywhere.\n     */\n    href: string;\n\n    /**\n     * Associated product-id used to update the yotpo-reviews element\n     */\n    productId: string;\n\n    /**\n     * String value to determine the view context the element is being rendered in. This is used to\n     * facilitate minor style and content changes between various product templates and cards.\n     */\n    view: 'pdp-reviews' | 'value-set';\n  };\n\n  public connectedCallback() {\n    this.initializeShadowRoot();\n\n    this.render();\n  }\n\n  public attributeChangedCallback(_name: string, oldValue: string, newValue: string) {\n    this.initializeShadowRoot();\n\n    if (this.isConnected && oldValue !== newValue) {\n      this.render();\n    }\n  }\n\n  private initializeShadowRoot() {\n    if (!this.shadowRoot) {\n      this.attachShadow({ mode: 'open' });\n      this.shadowRoot.innerHTML = `<style>${styles}</style>${html}`;\n    }\n  }\n\n  private render() {\n    const count = parseInt(this.dataset.count, 10);\n    const average = parseFloat(this.dataset.average);\n\n    if (isNaN(count) || isNaN(average)) {\n      return;\n    }\n\n    // Whoever renders us decides whether the stars link anywhere, and the href they hand us is\n    // either a full url or a bare hash. When we get nothing we just show the stars, and the anchor\n    // then has no link for a screen reader to announce, so we set role to img instead.\n\n    // Each branch clears what the other one set, because we re-render on every attribute change and\n    // a caller can set the attributes one at a time. The cart drawer upsell does exactly that, and\n    // it sets the href last, so the render before it took the no-link path.\n\n    const anchorElement = this.shadowRoot.querySelector('a');\n\n    if (this.dataset.href) {\n      anchorElement.href = this.dataset.href;\n      anchorElement.removeAttribute('role');\n    } else {\n      anchorElement.removeAttribute('href');\n      anchorElement.role = 'img';\n    }\n\n    this.renderStars();\n\n    const countElement = this.shadowRoot.querySelector('.rating-count');\n    const formatter = new Intl.NumberFormat('en-US');\n    countElement.textContent = formatter.format(count);\n\n    const labelElement = this.shadowRoot.querySelector('.label');\n    let reviewText = 'Review';\n    if (count !== 1) {\n      reviewText = reviewText + 's';\n    }\n\n    labelElement.textContent = reviewText;\n\n    if (count === 0) {\n      anchorElement.setAttribute('aria-label', 'No reviews yet');\n    } else if (count === 1) {\n      anchorElement.setAttribute('aria-label', `Rating: ${ average.toFixed(0) } stars.`);\n    } else {\n      anchorElement.setAttribute('aria-label',\n        `Average rating: ${ average.toFixed(1) } based on ${ count } reviews.`);\n    }\n  }\n\n  private renderStars() {\n    // We do not need fallbacks for missing attributes here because we checked in the caller.\n\n    const rating = parseFloat(this.dataset.average);\n\n    // Rendering stars is tricky because using exact numbers produces poor results. The difference\n    // is illegible most of the time but it looks bad enough that we do some rounding to get the\n    // stars to look more like the rating represented.\n\n    const baseInt = parseInt(this.dataset.average, 10);\n    let display = 0;\n    if (baseInt === 0) {\n      display = 0;\n    } else if (rating <= baseInt + 0.25) {\n      display = baseInt + 0.42;\n    } else if (rating <= baseInt + 0.5) {\n      display = baseInt + 0.5;\n    } else if (rating > baseInt + 0.5 && rating <= baseInt + 0.75) {\n      display = baseInt + 0.6;\n    } else if (rating >= baseInt + 0.76) {\n      display = baseInt + 1;\n    }\n\n    const stars = this.shadowRoot.querySelector<HTMLElement>('.rating-stars');\n    stars.style.cssText = `--rating: ${display}`;\n  }\n}\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'product-rating-stars': ProductRatingStars;\n  }\n}\n\n// This element is loaded by a section that may be loaded more than once. This idempotency check is\n// required.\n\nif (!customElements.get('product-rating-stars')) {\n  customElements.define('product-rating-stars', ProductRatingStars);\n}\n"],"names":["ProductRatingStars","HTMLElement","observedAttributes","connectedCallback","this","initializeShadowRoot","render","attributeChangedCallback","_name","oldValue","newValue","isConnected","shadowRoot","attachShadow","mode","innerHTML","count","parseInt","dataset","average","parseFloat","isNaN","anchorElement","querySelector","href","removeAttribute","role","renderStars","countElement","formatter","Intl","NumberFormat","textContent","format","reviewText","setAttribute","toFixed","rating","baseInt","display","style","cssText","customElements","get","define"],"sourceRoot":""}