PageSourceSearch

https://www.calpaktravel.com/cdn/shop/t/3471/assets/personalization-modal.js?v=77062502030212783981790184691

js calpaktravel.com collected 2026-09-24 21:16:26 UTC 93,001 bytes, 363 lines download raw bytes

1import{loadSwiper}from"./swiper-loader.js";import{PopupModal}from"./popup-modal.js";import{Filter}from"https://cdn.jsdelivr.net/npm/[email protected]/dist/profanity-check.esm.js";const profanityFilter=new Filter,encodeAttr=value=>encodeURIComponent(JSON.stringify(value)),decodeAttr=value=>JSON.parse(decodeURIComponent(value)),formatPxToRem=value=>{if(value==null||value==="")return null;const numericValue=Number(value);return!Number.isFinite(numericValue)||numericValue===0?null:`calc(${numericValue} / 16 * 1rem)`},optionGroupFingerprint=(step,options)=>{if(!step||!options?.length)return null;const labels=options.map(option=>option.label).join("\0");return`${step}:${labels}`},getSharedOptionGroupFingerprints=(currentFingerprints,destinationFingerprints)=>{const destinationSet=new Set(destinationFingerprints);return currentFingerprints.filter(fingerprint=>destinationSet.has(fingerprint))},findImageOption=(type,predicate,categoryIndex=null)=>{const numericCategoryIndex=Number(categoryIndex);if(Number.isFinite(numericCategoryIndex)&&type?.categories?.[numericCategoryIndex]){const scopedOption=type.categories[numericCategoryIndex].options?.find(predicate);if(scopedOption)return scopedOption}for(const category of type?.categories??[]){const option=category.options?.find(predicate);if(option)return option}return null};class PersonalizationState{constructor(data){this.data=data,this.confirmedPersonalizations=[],this.currentSelections={product:data.personalizations[0],placement:null,type:null,details:null},this.currentPersonalizationProduct=null,this.editingConfirmedIndex=null,this.steps={current:1,total:4},this.currentCartItems=null,this.cartEditGroupKeys=null,this.cartEditQuantities=null}getConfirmedDraftStorageKey(){return`calpak_personalization_${this.data.base_product.id}`}persistDraft(){const key=this.getConfirmedDraftStorageKey();if(!this.confirmedPersonalizations?.length){this.clearDraft();return}typeof window.setStorageItem=="function"&&window.setStorageItem(key,{confirmedPersonalizations:this.confirmedPersonalizations},7)}loadDraft(){if(typeof window.expireStorageItem!="function")return;const value=window.expireStorageItem(this.getConfirmedDraftStorageKey());value?.confirmedPersonalizations?.length&&(this.confirmedPersonalizations=value.confirmedPersonalizations,this.editingConfirmedIndex=null,this.renumberLocationKeys(),PersonalizationCart.syncFromState(this,{includeCurrentDraft:!1}))}clearDraft(){const key=this.getConfirmedDraftStorageKey();typeof window.storageAvailable=="function"&&window.storageAvailable()&&localStorage.removeItem(key)}resetAll(){this.confirmedPersonalizations=[],this.currentSelections={product:this.data.personalizations[0],placement:null,type:null,details:null},this.currentPersonalizationProduct=null,this.editingConfirmedIndex=null,this.steps.current=1,this.cartEditGroupKeys=null,this.cartEditQuantities=null,PersonalizationCart.syncFromState(this,{includeCurrentDraft:!1})}resetInProgress(){this.currentSelections={product:this.currentSelections.product,placement:null,type:null,details:null},this.currentPersonalizationProduct=null,this.editingConfirmedIndex=null,this.steps.current=1,PersonalizationCart.syncFromState(this,{includeCurrentDraft:!1})}renumberLocationKeys(){this.confirmedPersonalizations.forEach((entry,index)=>{entry.locationKey=`Location_${index+1}`})}buildConfirmedEntry(){const selections={product:this.currentSelections.product,placement:this.currentSelections.placement,type:this.currentSelections.type,details:{...this.currentSelections.details}};return{locationKey:this.editingConfirmedIndex!==null?this.confirmedPersonalizations[this.editingConfirmedIndex].locationKey:`Location_${this.confirmedPersonalizations.length+1}`,productLabel:selections.product?.label??null,placementLabel:selections.placement?.label,selections,personalizationVariantId:this.currentPersonalizationProduct.id,price:this.currentPersonalizationProduct.price,basePropertyValue:PersonalizationCart.formatBaseProperties(selections,selections.details),personalizationProperties:PersonalizationCart.formatPersonalizationProperties(selections,selections.details)}}hasUnconfiguredPlacements(product=this.currentSelections.product){return product.placements.some(placement=>!PersonalizationStepPlacement.isPlacementConfigured(this,placement,product))}hasAnyUnconfiguredPlacements(){return this.data.personalizations.some(product=>this.hasUnconfiguredPlacements(product))}getFirstProductWithUnconfiguredPlacements(){return this.data.personalizations.find(product=>this.hasUnconfiguredPlacements(product))??this.data.personalizations[0]}commitCurrentDraft(){const entry=this.buildConfirmedEntry();this.editingConfirmedIndex!==null?(this.confirmedPersonalizations[this.editingConfirmedIndex]=entry,this.editingConfirmedIndex=null):this.confirmedPersonalizations.push(entry),this.renumberLocationKeys()}}class PersonalizationCart extends HTMLElement{static CART_EDIT_STORAGE_KEY="calpak_personalization_cart_edit";static formatBaseProperties(selections,details){let baseProperties=`${selections.product?.label?`Product: ${selections.product.label} | `:""}Location: ${selections.placement?.label} | Type: ${selections.type?.label}`;if(selections.type?.type=="text")baseProperties+=` | ${details?.text?`Characters: ${details.text} | `:""}${details?.icon?`Icon: ${details.icon} | `:""}${details?.font?`Font: ${details.font.name} | `:""}${details?.color?`Color: ${details.color.name} - ${details.color.thread}`:""}`;else{const detailKey=selections.type?.label.toLowerCase();baseProperties+=` | ${selections.type?.label}: ${details?.[detailKey]?.label??""}`}return baseProperties}static formatPersonalizationProperties(selections,details){const productProperty=selections.product?.label?{product:selections.product.label}:{};return selections.type?.type=="text"?{location:selections.placement?.label,...productProperty,...details?.text?{Initials:details.text}:{}
1,...details?.icon?{Icon:details.icon}:{},...details?.font?{Font:details.font.name}:{},...details?.color?{Color:details.color.name}:{}}:{location:selections.placement?.label,...productProperty}}static formatConfirmationSummary(selections){const{product,placement,type,details}=selections,label=`${product?.label?`${product.label} \u2013 `:""}${placement?.label} \u2013 ${type?.label}`;if(type?.type=="text"){const detailParts=[details?.text?`"${details.text}"`:"",details?.icon??"",details?.font?.name??"",details?.color?.name??""].filter(Boolean);return`${label} \u2013 ${detailParts.join(" \u2013 ")}`}return`${label} \u2013 ${details?.[type?.label.toLowerCase()]?.label??""}`}static shouldShowConfirmationDraft(state){return state.steps.current===state.steps.total&&PersonalizationStepDetails.isDraftComplete(state)&&!PersonalizationStepPlacement.isCurrentPlacementConfirmed(state)&&state.currentPersonalizationProduct?.price}static getConfirmationPricing(state){let personalizationTotal=0;const rows=[],showCurrentDraft=PersonalizationCart.shouldShowConfirmationDraft(state);for(const[index,entry]of state.confirmedPersonalizations.entries())showCurrentDraft&&state.editingConfirmedIndex===index||(personalizationTotal+=entry.price,rows.push({summary:PersonalizationCart.formatConfirmationSummary(entry.selections),price:entry.price,entryIndex:index,isDraft:!1}));return showCurrentDraft&&(personalizationTotal+=state.currentPersonalizationProduct.price,rows.push({summary:PersonalizationCart.formatConfirmationSummary(state.currentSelections),price:state.currentPersonalizationProduct.price,entryIndex:state.editingConfirmedIndex,isDraft:!0})),{rows,subtotal:state.data.base_product.price+personalizationTotal}}static syncFromState(state,{includeCurrentDraft=!1}={}){state.renumberLocationKeys();const groupId=crypto.randomUUID(),baseProperties={"_final-sale":!0,_LPROP:"Final Sale",_personalization_id:groupId},feeProperties=properties=>({"_final-sale":!0,_LPROP:"Final Sale",_personalization_id:groupId,_personalization_fee:!0,...properties}),personalizationProducts=[],useDraftOverlay=includeCurrentDraft&&PersonalizationStepDetails.isDraftComplete(state)&&!PersonalizationStepPlacement.isCurrentPlacementConfirmed(state)&&state.currentPersonalizationProduct?.id;for(const[index,entry]of state.confirmedPersonalizations.entries())useDraftOverlay&&state.editingConfirmedIndex===index?(baseProperties[entry.locationKey]=PersonalizationCart.formatBaseProperties(state.currentSelections,state.currentSelections.details),personalizationProducts.push({id:state.currentPersonalizationProduct.id,quantity:1,properties:feeProperties(PersonalizationCart.formatPersonalizationProperties(state.currentSelections,state.currentSelections.details))})):(baseProperties[entry.locationKey]=entry.basePropertyValue,personalizationProducts.push({id:entry.personalizationVariantId,quantity:1,properties:feeProperties(entry.personalizationProperties)}));if(useDraftOverlay&&state.editingConfirmedIndex===null){const draftKey=`Location_${state.confirmedPersonalizations.length+1}`;baseProperties[draftKey]=PersonalizationCart.formatBaseProperties(state.currentSelections,state.currentSelections.details),personalizationProducts.push({id:state.currentPersonalizationProduct.id,quantity:1,properties:feeProperties(PersonalizationCart.formatPersonalizationProperties(state.currentSelections,state.currentSelections.details))})}state.currentCartItems={baseProduct:{id:state.data.base_product.id,quantity:1,properties:baseProperties},personalizationProducts}}static getLineItems(state){const lineItems=[],{baseProduct,personalizationProducts}=state.currentCartItems??{};baseProduct?.id&&lineItems.push({id:Number(baseProduct.id),quantity:baseProduct.quantity??1,properties:baseProduct.properties??{}});for(const item of personalizationProducts??[])item?.id&&lineItems.push({id:Number(item.id),quantity:item.quantity??1,properties:item.properties??{}});return lineItems}static commitDraftIfComplete(state){!PersonalizationStepDetails.isDraftComplete(state)||PersonalizationStepPlacement.isCurrentPlacementConfirmed(state)||state.commitCurrentDraft()}static canAddToCart(state){return state.confirmedPersonalizations.length>0||PersonalizationStepDetails.isDraftComplete(state)&&!PersonalizationStepPlacement.isCurrentPlacementConfirmed(state)}static parseBasePropertyValue(value){const parsed={};if(!value)return parsed;for(const part of value.split(" | ")){const colonIndex=part.indexOf(":");colonIndex!==-1&&(parsed[part.slice(0,colonIndex).trim()]=part.slice(colonIndex+1).trim())}return parsed}static findProductAndPlacement(data,locationLabel,productLabel=null){const products=productLabel?data.personalizations.filter(product=>product.label===productLabel):data.personalizations;for(const product of products){const placement=product.placements.find(entry=>entry.label===locationLabel);if(placement)return{product,placement}}return productLabel?PersonalizationCart.findProductAndPlacement(data,locationLabel):null}static findFeeForLocation(feeItems,locationLabel,productLabel,usedFeeKeys){return feeItems.find(entry=>!usedFeeKeys.has(entry.key)&&entry.properties?.location===locationLabel&&(productLabel==null||entry.properties?.product===productLabel||!entry.properties?.product))}static buildDetailsFromCart(type,parsed,fee){if(type.type==="text"){const details={},text=fee?.properties?.Initials??parsed.Characters;text&&(details.text=text);const icon=fee?.properties?.Icon??parsed.Icon;icon&&(details.icon=icon);const fontName=fee?.properties?.Font??parsed.Font;if(fontName&&type.properties?.fonts){const font=type.properties.fonts.find(entry=>entry.name===fontName);font&&(details.font=font)}const colorName=fee?.properties?.Color??parsed.Color?.split(" - ")[0]?.trim();if(colorName&&type.properties?.colors){const color=type.properties.colors.find(entry=>entry.name===colorName);color&&(details.color=color)}return details}const detailKey=type.label.toLowerCase(),optionLabel=parsed[type.label],option=findImageOption(type,entry=>entry.label===optionLabel||entry.value==fee?.variant_id);return option?{[detailKey]:option}:{}}static hydrateStateFromCart(state,{baseItem,feeItems}){const locationEntries=Object.entries(baseItem.properties??{}).filter(([key])=>
1/^Location_\d+$/.test(key)).sort(([a],[b])=>Number(a.split("_")[1])-Number(b.split("_")[1])),confirmedPersonalizations=[],usedFeeKeys=new Set;for(const[locationKey,basePropertyValue]of locationEntries){const parsed=PersonalizationCart.parseBasePropertyValue(basePropertyValue),locationLabel=parsed.Location,productLabel=parsed.Product??null;if(!locationLabel){console.warn("Cart personalization edit: missing location in",locationKey);continue}const match=PersonalizationCart.findProductAndPlacement(state.data,locationLabel,productLabel);if(!match){console.warn("Cart personalization edit: placement not found",locationLabel,productLabel);continue}const{product,placement}=match,type=placement.types.find(entry=>entry.label===parsed.Type);if(!type){console.warn("Cart personalization edit: type not found",parsed.Type);continue}const fee=PersonalizationCart.findFeeForLocation(feeItems,locationLabel,product?.label??productLabel,usedFeeKeys);fee?.key&&usedFeeKeys.add(fee.key);const details=PersonalizationCart.buildDetailsFromCart(type,parsed,fee),selections={product,placement,type,details},personalizationVariantId=fee?.variant_id;if(!personalizationVariantId){console.warn("Cart personalization edit: missing fee variant",locationLabel);continue}confirmedPersonalizations.push({locationKey,productLabel:product?.label??null,placementLabel:placement.label,selections,personalizationVariantId,price:fee?.price??0,basePropertyValue,personalizationProperties:PersonalizationCart.formatPersonalizationProperties(selections,details)})}state.confirmedPersonalizations=confirmedPersonalizations,state.currentSelections={product:state.getFirstProductWithUnconfiguredPlacements(),placement:null,type:null,details:null},state.currentPersonalizationProduct=null,state.editingConfirmedIndex=null,state.steps.current=1,state.renumberLocationKeys(),PersonalizationCart.syncFromState(state,{includeCurrentDraft:!1})}static clearSavedDesigns(variantId){const draftKey=`calpak_personalization_${variantId}`;typeof window.storageAvailable=="function"&&window.storageAvailable()&&(localStorage.removeItem(draftKey),(typeof window.expireStorageItem=="function"?window.expireStorageItem(PersonalizationCart.CART_EDIT_STORAGE_KEY):null)?.variantId===variantId&&localStorage.removeItem(PersonalizationCart.CART_EDIT_STORAGE_KEY));const modal=document.getElementById("personalization-modal");modal?.data?.base_product?.id===variantId&&typeof modal.clearSavedDesigns=="function"&&modal.clearSavedDesigns()}static startCartEdit({baseItem,feeItems,groupKeys,quantities}){const payload={baseItem,feeItems,groupKeys,quantities,variantId:baseItem.variant_id},modal=document.getElementById("personalization-modal");if(modal?.state&&modal.data?.base_product?.id===baseItem.variant_id){modal.state.resetAll(),PersonalizationCart.hydrateStateFromCart(modal.state,{baseItem,feeItems}),modal.state.cartEditGroupKeys=groupKeys,modal.state.cartEditQuantities=quantities??null,document.querySelector("cart-drawer")?.close?.(),modal.setAttribute("open","true");return}typeof window.setStorageItem=="function"&&baseItem.url&&(window.setStorageItem(PersonalizationCart.CART_EDIT_STORAGE_KEY,payload,1),window.location.href=baseItem.url)}static async addToCart(state,actionEl){PersonalizationCart.commitDraftIfComplete(state),PersonalizationCart.syncFromState(state,{includeCurrentDraft:!1});const items=PersonalizationCart.getLineItems(state);if(!items.length)return!1;actionEl.disabled=!0;try{if(state.cartEditGroupKeys?.length){const updates=Object.fromEntries(state.cartEditGroupKeys.map(key=>[key,state.cartEditQuantities?.[key]??0])),removeResponse=await window.updateCart({updates},!1);if(removeResponse?.status)return console.error("Personalization cart edit replace failed",removeResponse),!1;state.cartEditGroupKeys=null,state.cartEditQuantities=null}const response=await window.addToCart({items});return response?.status?(console.error("Personalization add to cart failed",response),!1):!0}finally{actionEl.disabled=!1}}#handleAddBound=null;#modal=null;connectedCallback(){this.#modal=this.closest("personalization-modal"),this.#render(),this.#handleAddBound=this.#handleAdd.bind(this),this.querySelector('[data-action="add"]')?.addEventListener("click",this.#handleAddBound)}disconnectedCallback(){this.querySelector('[data-action="add"]')?.removeEventListener("click",this.#handleAddBound),this.#modal=null}#render(){const state=this.#modal?.state;if(!state)return;const canAdd=PersonalizationCart.canAddToCart(state);this.innerHTML=`
2            <button
3                type="button"
4                class="personalization-modal--action cart btn--primary"
5                data-action="add"
6                ${canAdd?"":"disabled"}
7            >Accept Terms & Add to Bag</button>
8        `}async#handleAdd(ev){const state=this.#modal?.state,actionEl=ev.currentTarget;if(!state)return;await PersonalizationCart.addToCart(state,actionEl)&&this.#modal?.dispatchEvent(new CustomEvent("personalization-added",{bubbles:!0}))}}class PersonalizationTextInput extends HTMLElement{static isProfane(text){return text?.trim()?profanityFilter.isProfane(text):!1}static sanitizeTextInput(value,properties){const sanitized=value.replace(/[^A-Za-z1-9 ]/g,"");return properties?.uppercase?sanitized.toUpperCase():sanitized}static getCharacterLimit(properties){return properties.icons?properties.character_limit+1:properties.character_limit}static getTextCharacterLimit(properties,details){const baseLimit=PersonalizationTextInput.getCharacterLimit(properties);return details?.icon?Math.max(0,baseLimit-1):baseLimit}static enforceTextLength(text,limit){return text&&text.slice(0,limit)}
8static syncDetailsTextToLimit(state){const type=state.currentSelections.type,text=state.currentSelections.details?.text;if(!text||type?.type!=="text"||!type?.properties)return;const normalized=PersonalizationTextInput.enforceTextLength(PersonalizationTextInput.sanitizeTextInput(text,type.properties),PersonalizationTextInput.getTextCharacterLimit(type.properties,state.currentSelections.details));normalized!==text&&(normalized?state.currentSelections.details.text=normalized:delete state.currentSelections.details.text)}connectedCallback(){const{properties,details}=decodeAttr(this.getAttribute("context")),textLimit=PersonalizationTextInput.getTextCharacterLimit(properties,details),text=details?.text,hasProfanity=PersonalizationTextInput.isProfane(text),isUppercase=properties?.uppercase===!0;this.innerHTML=`
9            <div class="personalization-modal--input${hasProfanity?" personalization-modal--input--error":""}">
10                <label for="personalization_details_text" class="personalization-modal--input-label">
11                    Text \u2014 ${isUppercase?"Letters A\u2013Z":"Letters a\u2013z, A\u2013Z"}, Numbers 1\u20139*
12                </label>
13                <div class="input--text">
14                    <input
15                        type="text"
16                        id="personalization_details_text"
17                        name="personalization_details_text"
18                        maxlength="${textLimit}"
19                        placeholder="${isUppercase?"ABC":"Abc"}"
20                        pattern="${isUppercase?"[A-Z1-9 ]+":"[A-Za-z1-9 ]+"}"
21                        title="${isUppercase?"Uppercase letters A\u2013Z, numbers 1\u20139, and spaces only":"Letters A\u2013Z, a\u2013z, numbers 1\u20139, and spaces only"}"
22                        ${text?`value="${text}"`:""}
23                        data-step="details"
24                        data-input="text"
25                        ${hasProfanity?'aria-invalid="true" aria-describedby="personalization_details_text_error"':""}
26                        required
27                    >
28                    <span class="input--text-characters">${text?.length||0}/${textLimit}</span>
29                </div>
30                ${hasProfanity?`
31                    <p id="personalization_details_text_error" class="personalization-modal--input-error" role="alert">
32                        Please revise your text
33                    </p>
34                `:""}
35            </div>
36        `}updateFromDetails(properties,details){const textLimit=PersonalizationTextInput.getTextCharacterLimit(properties,details),text=details?.text??"",hasProfanity=PersonalizationTextInput.isProfane(text),elWrapper=this.querySelector(".personalization-modal--input"),elInput=this.querySelector("#personalization_details_text"),elCounter=this.querySelector(".input--text-characters");elCounter&&(elCounter.textContent=`${text.length}/${textLimit}`),elInput&&(elInput.maxLength=textLimit),elWrapper?.classList.toggle("personalization-modal--input--error",hasProfanity),elInput&&(hasProfanity?(elInput.setAttribute("aria-invalid","true"),elInput.setAttribute("aria-describedby","personalization_details_text_error")):(elInput.removeAttribute("aria-invalid"),elInput.removeAttribute("aria-describedby")));let elError=this.querySelector("#personalization_details_text_error");hasProfanity?elError||(elError=document.createElement("p"),elError.id="personalization_details_text_error",elError.className="personalization-modal--input-error",elError.setAttribute("role","alert"),elError.textContent="Please revise your text",elWrapper?.appendChild(elError)):elError&&elError.remove()}}class PersonalizationIconPicker extends HTMLElement{static isIconDisabled(details,properties){const text=details?.text;return!!(text&&properties&&text.length===PersonalizationTextInput.getCharacterLimit(properties))}static getIconFocusIndex(icons,details,properties){const selectedIndex=icons.findIndex(icon=>details?.icon===icon.name);return selectedIndex>=0&&!PersonalizationIconPicker.isIconDisabled(details,properties)?selectedIndex:icons.findIndex(()=>!PersonalizationIconPicker.isIconDisabled(details,properties))}static handleKeydown(ev){const elInput=ev.target.closest('input[data-input="icon"]');if(!elInput)return;const elGroup=elInput.closest("[data-icon-options]");if(!elGroup)return;const elInputs=[...elGroup.querySelectorAll('input[data-input="icon"]:not(:disabled)')];if(["ArrowUp","ArrowDown","ArrowLeft","ArrowRight","Home","End"].includes(ev.key)){ev.preventDefault();const currentIndex=elInputs.indexOf(elInput);if(currentIndex===-1)return;let nextIndex=currentIndex;ev.key==="Home"?nextIndex=0:ev.key==="End"?nextIndex=elInputs.length-1:ev.key==="ArrowRight"||ev.key==="ArrowDown"?nextIndex=(currentIndex+1)%elInputs.length:nextIndex=(currentIndex-1+elInputs.length)%elInputs.length,elInput.tabIndex=-1,elInputs[nextIndex].tabIndex=0,elInputs[nextIndex].focus({preventScroll:!0});return}if(ev.key==="Enter"||ev.key===" "){if(ev.preventDefault(),elInput.disabled)return;elInput.checked=!elInput.checked,elInput.dispatchEvent(new Event("change",{bubbles:!0}))}}connectedCallback(){const{icons,details,properties}=decodeAttr(this.getAttribute("context")),focusIndex=PersonalizationIconPicker.getIconFocusIndex(icons,details,properties),isDisabled=PersonalizationIconPicker.isIconDisabled(details,properties);this.innerHTML=`
37            <fieldset class="personalization-modal--input"${isDisabled?' aria-describedby="personalization_details_icon_hint"':""}>
38                <legend class="personalization-modal--input-label">
39                    Icon
40                </legend>
41                ${isDisabled?`
42                    <p id="personalization_details_icon_hint" class="personalization-modal--input-hint small">
43                        Icons are unavailable when your text reaches the character limit.
44                    </p>
45                `:""}
46                <ul class="personalization-modal--options" data-icon-options>
47                    ${icons.map((icon,index)=>{const inputId=`personalization_details_icon_${icon.name}`;return`
48                            <li class="personalization-modal--option input--icon">
49                                <input
50                                    id="${inputId}"
51                                    type="checkbox"
52                                    name="personalization_details_icon"
53                                    value="${icon.name}"
54                                    data-step="details"
55                                    data-input="icon"
56                                    tabindex="${index===focusIndex?0:-1}"
57                                    ${details?.icon==icon.name?"checked":""}
58                                    ${isDisabled?"disabled":""}
59                                >
60                                <label for="${inputId}">
61                                    <span class="visually-hidden">${icon.name}</span>
62                                    <span role="presentation">${icon.svg}</span>
63                                </label>
64                            </li>
65                        `}).join("")}
66                </ul>
67            </fieldset>
68        `}updateDisabledState(details,properties){const isDisabled=PersonalizationIconPicker.isIconDisabled(details,properties),elFieldset=this.querySelector("fieldset.personalization-modal--input");let elHint=this.querySelector("#personalization_details_icon_hint");elFieldset&&(isDisabled?elFieldset.setAttribute("aria-describedby","personalization_details_icon_hint"):elFieldset.removeAttribute("aria-describedby")),isDisabled&&!elHint?(elHint=document.createElement("p"),elHint.id="personalization_details_icon_hint",elHint.className="personalization-modal--input-hint small",elHint.textContent="Icons are unavailable when your text reaches the character limit.",elFieldset?.insertBefore(elHint,elFieldset.querySelector("[data-icon-options]"))):!isDisabled&&elHint&&elHint.remove();for(const elInput of this.querySelectorAll('input[data-input="icon"]'))elInput.disabled=isDisabled}}class PersonalizationFontPicker extends HTMLElement{#swiper=null;#swiperInitId=0;#resizeObserver=null;connectedCallback(){const{fonts,details}=decodeAttr(this.getAttribute("context"));this.innerHTML=`
69            <fieldset class="personalization-modal--input">
70                <legend class="personalization-modal--input-label">
71                    <span>Font*</span>
72                </legend>
73                <div class="personalization-modal--options-navigation">
74                    <button
75                        type="button"
76                        class="personalization-modal--options-navigation-prev btn--arrow prev"
77                        data-navigation="prev"
78                        aria-label="Previous font"
79                    ></button>
80                    <button
81                        type="button"
82                        class="personalization-modal--options-navigation-next btn--arrow next"
83                        data-navigation="next"
84                        aria-label="Next font"
85                    ></button>
86                </div>
87                <div class="personalization-modal--options-carousel swiper" data-options-carousel>
88                    <ul class="swiper-wrapper">
89                        ${fonts.map(font=>{const inputId=`personalization_details_font_${font.handle}`,baselineAdjustment=formatPxToRem(font.baseline_adjustment),fontSampleStyles=["font-size: calc(1em * var(--scale))","line-height: 1",baselineAdjustment?`--baseline-adjustment: ${baselineAdjustment}`:""].filter(Boolean).join("; ");return`
90                                <li class="personalization-modal--option input--radio swiper-slide" style="font-family: var(--font-${font.handle}); --scale: var(--size-${font.handle});">
91                                    <input
92                                        type="radio"
93                                        id="${inputId}"
94                                        name="personalization_details_font"
95                                        value="${font.name}"
96                                        data-step="details"
97                                        data-input="font"
98                                        ${details?.font?.name==font.name?"checked":""}
99                                        required
100                                    >
101                                    <label for="${inputId}">
102                                        <span class="visually-hidden">${font.name}</span>
103                                        <span role="presentation" class="personalization-modal--font-sample" style="${fontSampleStyles}">
104                                            AaBbCc
105                                        </span>
106                                    </label>
107                                </li>
108                            `}).join("")}
109                    </ul>
110                </div>
111            </fieldset>
112        `,this.#initSwiper()}async#initSwiper(){const elCarousel=this.querySelector("[data-options-carousel]");
112if(!elCarousel)return;const initId=++this.#swiperInitId,Swiper=await loadSwiper({waitForLoad:!1});!this.isConnected||initId!==this.#swiperInitId||(this.#swiper?.destroy(!0,!0),this.#swiper=null,this.#resizeObserver?.disconnect(),this.#swiper=new Swiper(elCarousel,{slidesPerView:4,spaceBetween:12,watchOverflow:!0,observer:!0,observeParents:!0,navigation:{nextEl:this.querySelector('[data-navigation="next"]'),prevEl:this.querySelector('[data-navigation="prev"]')},breakpoints:{1024:{slidesPerView:6,spaceBetween:12}}}),elCarousel.setAttribute("data-swiper-ready",""),this.#resizeObserver=new ResizeObserver(()=>{initId!==this.#swiperInitId||!this.#swiper||(this.#swiper.updateSize(),this.#swiper.update())}),this.#resizeObserver.observe(elCarousel),requestAnimationFrame(()=>{initId!==this.#swiperInitId||!this.#swiper||(this.#swiper.updateSize(),this.#swiper.update())}))}disconnectedCallback(){this.#swiperInitId+=1,this.#resizeObserver?.disconnect(),this.#resizeObserver=null,this.#swiper?.destroy(),this.#swiper=null}}class PersonalizationColorPicker extends HTMLElement{connectedCallback(){const{colors,details}=decodeAttr(this.getAttribute("context"));this.innerHTML=`
113            <fieldset class="personalization-modal--input">
114                <legend class="personalization-modal--input-label">
115                    Color*
116                </legend>
117                <ul class="personalization-modal--colors">
118                    ${colors.map(color=>{const inputId=`personalization_details_color_${color.name}`,isChecked=details?.color?.name==color.name;return`
119                        <li>
120                            <input
121                                type="radio"
122                                id="${inputId}"
123                                name="personalization_details_color"
124                                value="${color.name}"
125                                data-step="details"
126                                data-input="color"
127                                class="visually-hidden"
128                                ${isChecked?"checked":""}
129                            >
130                            <label for="${inputId}" class="personalization-modal--color">
131                                <span role="presentation" class="input--swatch" style="--swatch: ${color.swatch};"></span>
132                                <span class="visually-hidden">${color.name}</span>
133                            </label>
134                        </li>
135                    `}).join("")}
136                </ul>
137            </fieldset>
138        `}}class PersonalizationImagePicker extends HTMLElement{#categories=[];#key="";#selectedValue=null;#selectedCategoryIndex=0;#swiper=null;#swiperInitId=0;static#getOptionsMarkup(options,key,selectedValue){return options.map(option=>{const inputId=`personalization_details_${key}_${option.label.toLowerCase()}`;return`
139                <li class="personalization-modal--option input--image transparent">
140                    <input
141                        id="${inputId}"
142                        type="radio"
143                        name="personalization_details_${key}"
144                        value="${option.value}"
145                        data-step="details"
146                        data-input="${key}"
147                        ${selectedValue==option.value?"checked":""}
148                        required
149                    />
150                    <label for="${inputId}" class="transparent">
151                        <span class="visually-hidden">${option.label}</span>
152                        <span role="presentation" class="input--image-imag transparent">${option.image}</span>
153                    </label>
154                </li>
155            `}).join("")}#getCategoryIndexForValue(value){if(value==null)return 0;const index=this.#categories.findIndex(category=>category.options?.some(option=>option.value==value));return index>=0?index:0}#renderOptions(){const elOptions=this.querySelector("[data-image-options]"),category=this.#categories[this.#selectedCategoryIndex];!elOptions||!category||(elOptions.dataset.categoryIndex=String(this.#selectedCategoryIndex),elOptions.innerHTML=PersonalizationImagePicker.#getOptionsMarkup(category.options??[],this.#key,this.#selectedValue),window.initLazyLoad?.())}#updateCategoryButtons(){this.querySelectorAll("button[data-category-index]").forEach(elButton=>{const isSelected=Number(elButton.dataset.categoryIndex)===this.#selectedCategoryIndex;elButton.classList.toggle("btn--primary",isSelected),elButton.classList.toggle("btn--secondary",!isSelected),elButton.setAttribute("aria-pressed",isSelected?"true":"false")})}#handleCategoryClick=ev=>{const elButton=ev.target.closest("button[data-category-index]");if(!elButton||!this.contains(elButton))return;const index=Number(elButton.dataset.categoryIndex);!Number.isFinite(index)||index===this.#selectedCategoryIndex||(this.#selectedCategoryIndex=index,this.#updateCategoryButtons(),this.#renderOptions(),this.dispatchEvent(new CustomEvent("personalization-image-category-change",{bubbles:!0,detail:{categoryIndex:index}})))};#handleChange=ev=>{const elInput=ev.target.closest(`input[data-input="${this.#key}"]`);!elInput?.checked||!this.contains(elInput)||(this.#selectedValue=elInput.value)};connectedCallback(){const{type,details,selectedCategoryIndex}=decodeAttr(this.getAttribute("context"));this.#categories=type.categories??[],this.#key=type.label.toLowerCase(),this.#selectedValue=details?.[this.#key]?.value??null,selectedCategoryIndex!=null&&Number.isFinite(Number(selectedCategoryIndex))&&this.#categories[selectedCategoryIndex]?this.#selectedCategoryIndex=Number(selectedCategoryIndex):this.#selectedCategoryIndex=this.#getCategoryIndexForV
155alue(this.#selectedValue);const selectedCategory=this.#categories[this.#selectedCategoryIndex];this.innerHTML=`
156            <fieldset class="personalization-modal--input">
157                <legend class="personalization-modal--input-label">
158                    <span>${type.label}*</span>
159                </legend>
160                ${this.#categories.length?`
161                    <div class="personalization-modal--options-navigation">
162                        <button
163                            type="button"
164                            class="personalization-modal--options-navigation-prev btn--arrow prev"
165                            data-navigation="prev"
166                            aria-label="Previous category"
167                        ></button>
168                        <button
169                            type="button"
170                            class="personalization-modal--options-navigation-next btn--arrow next"
171                            data-navigation="next"
172                            aria-label="Next category"
173                        ></button>
174                    </div>
175                    <div
176                        class="personalization-modal--image-categories personalization-modal--options-carousel swiper scrollbar--none"
177                        data-category-carousel
178                        role="group"
179                        aria-label="${type.label} categories"
180                    >
181                        <ul class="swiper-wrapper">
182                            ${this.#categories.map((category,index)=>{const isSelected=index===this.#selectedCategoryIndex;return`
183                                <li class="swiper-slide">
184                                    <button
185                                        type="button"
186                                        class="${isSelected?"btn--primary":"btn--secondary"}"
187                                        data-category-index="${index}"
188                                        aria-pressed="${isSelected?"true":"false"}"
189                                    >
190                                        ${category.name}
191                                    </button>
192                                </li>
193                            `}).join("")}
194                        </ul>
195                    </div>
196                `:""}
197                <ul
198                    class="personalization-modal--options personlization-modal--options-images"
199                    data-image-options
200                    data-category-index="${this.#selectedCategoryIndex}"
201                >
202                    ${PersonalizationImagePicker.#getOptionsMarkup(selectedCategory?.options??[],this.#key,this.#selectedValue)}
203                </ul>
204            </fieldset>
205        `,this.addEventListener("click",this.#handleCategoryClick),this.addEventListener("change",this.#handleChange),window.initLazyLoad?.(),this.#initCategorySwiper()}
205async#initCategorySwiper(){if(!this.querySelector("[data-category-carousel]"))return;const initId=++this.#swiperInitId,Swiper=await loadSwiper({waitForLoad:!1});if(!this.isConnected||initId!==this.#swiperInitId)return;const elCarousel=this.querySelector("[data-category-carousel]");if(!elCarousel)return;const slideCount=elCarousel.querySelectorAll(".swiper-slide").length;this.#swiper?.destroy(!0,!0),this.#swiper=new Swiper(elCarousel,{enabled:slideCount>1,slidesPerView:"auto",spaceBetween:12,watchOverflow:!0,observer:!0,observeParents:!0,navigation:{nextEl:this.querySelector('[data-navigation="next"]'),prevEl:this.querySelector('[data-navigation="prev"]')}}),elCarousel.setAttribute("data-swiper-ready",""),requestAnimationFrame(()=>{initId!==this.#swiperInitId||!this.#swiper||(this.#swiper.updateSize(),this.#swiper.update())})}disconnectedCallback(){this.#swiperInitId+=1,this.removeEventListener("click",this.#handleCategoryClick),this.removeEventListener("change",this.#handleChange),this.#swiper?.destroy(),this.#swiper=null}}class PersonalizationRadioGroup extends HTMLElement{connectedCallback(){const{step,options,selectedLabel,configuredLabels=[],required=!0,legend,legendClass="personalization-modal--heading"}=decodeAttr(this.getAttribute("context")),optionsHtml=`
206            <ul class="personalization-modal--options">
207                ${options.map((option,index)=>{const inputName=`personalization_${step}`,inputId=`personalization_${step}_${index}`,isChecked=selectedLabel?selectedLabel==option.label:!1,isConfigured=configuredLabels.includes(option.label);return`
208                        <li class="personalization-modal--option input--radio${isConfigured?" personalization-modal--option--configured":""}">
209                            <input
210                                id="${inputId}"
211                                type="radio"
212                                name="${inputName}"
213                                value="${option.label}"
214                                data-step="${step}"
215                                ${isChecked?"checked":""}
216                                ${required?"required":""}
217                            >
218                            <label for="${inputId}">
219                                ${option.label}
220                                ${isConfigured?'<span class="visually-hidden">, design added</span>':""}
221                            </label>
222                        </li>
223                    `}).join("")}
224            </ul>
225        `;this.innerHTML=legend?`
226                <fieldset class="personalization-modal--field">
227                    <legend class="${legendClass}">${legend}</legend>
228                    ${optionsHtml}
229                </fieldset>
230            `:optionsHtml}}class PersonalizationProgress extends HTMLElement{connectedCallback(){const{current,total}=decodeAttr(this.getAttribute("context"));this.innerHTML=`
231            <progress id="personalization_progress" class="personalization-modal--progress-bar" value="${current}" max="${total}"></progress>
232            <label for="personalization_progress" class="personalization-modal--progress-label small">
233                <span class="visually-hidden">You are viewing step ${current} of ${total}</span>
234                <span role="presentation">${current}/${total} Steps</span>
235            </label>
236        `}}class PersonalizationPreviewImage extends HTMLElement{static PRELOAD_LINK_DATA_ATTR="data-personalization-preload";static#PREVIEW_LETTERS="ABCDEFGHIJKLMNOPQRSTUVWXYZ";static#PREVIEW_NUMBERS="123456789";static#iconViewBoxCache=new Map;static#formatCoordinate(value){if(value==null||value==="")return"50%";const stringValue=String(value);
236return stringValue.includes("%")?stringValue:`${stringValue}%`}static#getPxValue(value){if(value==null||value==="")return null;const numericValue=Number(String(value).trim().replace(/px$/i,""));return Number.isFinite(numericValue)?numericValue:null}static#escapeHtml(value){return String(value).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}static#parseImageMarkup(markup){const parser=document.createElement("div");parser.innerHTML=markup;const img=parser.querySelector("img");return img?{src:img.getAttribute("src"),srcset:img.getAttribute("srcset"),sizes:img.getAttribute("sizes")}:null}static preloadImage(src,srcset,sizes){if(!src)return;const selector=`link[${PersonalizationPreviewImage.PRELOAD_LINK_DATA_ATTR}="${CSS.escape(src)}"]`;if(document.head.querySelector(selector))return;const elLink=document.createElement("link");elLink.rel="preload",elLink.as="image",elLink.href=src,srcset&&elLink.setAttribute("imagesrcset",srcset),sizes&&elLink.setAttribute("imagesizes",sizes),elLink.setAttribute(PersonalizationPreviewImage.PRELOAD_LINK_DATA_ATTR,src),document.head.appendChild(elLink)}static preloadImages(personalizations){const parser=document.createElement("div");for(const product of personalizations)for(const placement of product.placements){parser.innerHTML=placement.image;const img=parser.querySelector("img");img&&PersonalizationPreviewImage.preloadImage(img.getAttribute("src"),img.getAttribute("srcset"),img.getAttribute("sizes"))}}static getActivePlacement(currentSelections,confirmedPersonalizations=[]){if(currentSelections.placement)return currentSelections.placement;const productLabel=currentSelections.product?.label??null,productConfirmed=confirmedPersonalizations.filter(entry=>entry.productLabel===productLabel),lastConfirmed=productConfirmed[productConfirmed.length-1];return lastConfirmed?.selections?.placement?lastConfirmed.selections.placement:currentSelections.product?.placements?.[0]??null}static getBaseImage(currentSelections,confirmedPersonalizations=[]){return PersonalizationPreviewImage.getActivePlacement(currentSelections,confirmedPersonalizations)?.image??""}static#getSelectionOverlayMarkup(selections){const{placement,type,details}=selections;if(!placement)return"";if(!type)return PersonalizationPreviewImage.#getPlacementIndicatorMarkup(placement);const positionStyles=PersonalizationPreviewImage.#getPositionStyles(placement,type);if(type.type==="text"){const properties=type.properties??{},icon=properties.icons?.find(entry=>entry.name===details?.icon),hasText=!!details?.text,hasIcon=!!icon;return!hasText&&!hasIcon?PersonalizationPreviewImage.#getTextOverlayMarkup(placement,type,positionStyles,{text:PersonalizationPreviewImage.#getPreviewPlaceholderText(properties),font:details?.font??properties.fonts?.[0],color:details?.color??properties.colors?.[0]}):PersonalizationPreviewImage.#getTextOverlayMarkup(placement,type,positionStyles,{text:hasText?details.text:"",font:details?.font,color:details?.color,icon:hasIcon?icon:null})}if(type.type==="image"){const detailKey=type.label.toLowerCase(),categoryIndex=selections.imageCategoryIndex,categories=type.categories??[],activeCategory=Number.isFinite(Number(categoryIndex))?categories[Number(categoryIndex)]:categories[0],selectedOption=details?.[detailKey]??activeCategory?.options?.[0];return selectedOption?.image?PersonalizationPreviewImage.#getImageOverlayMarkup(placement,type,positionStyles,selectedOption):PersonalizationPreviewImage.#getPlacementIndicatorMarkup(placement,type)}return""}static getOverlayMarkup(currentSelections,confirmedPersonalizations=[],{editingConfirmedIndex=null}={}){const activePlacement=PersonalizationPreviewImage.getActivePlacement(currentSelections,confirmedPersonalizations);if(!activePlacement)return"";const activeViewImage=activePlacement.image,productLabel=currentSelections.product?.label??null,overlays=[];for(const[index,entry]of confirmedPersonalizations.entries()){if(entry.productLabel!==productLabel)continue;const{placement}=entry.selections??{};if(!placement||placement.image!==activeViewImage||editingConfirmedIndex===index)continue;const overlay=PersonalizationPreviewImage.#getSelectionOverlayMarkup(entry.selections);overlay&&overlays.push(overlay)}if(currentSelections.placement?.image===activeViewImage){const draftOverlay=PersonalizationPreviewImage.#getSelectionOverlayMarkup(currentSelections);draftOverlay&&overlays.push(draftOverlay)}return overlays.join("")}static#getScaleValue(value){const scale=Number(value);return Number.isFinite(scale)&&scale>0?scale:1}static#getRotationValue(value){const rotation=Number(value);return Number.isFinite(rotation)?rotation:0}static#getPositionStyles(placement,type=null){const coordinates=placement.coordinates??{},fontSizes=placement.font_size??{},placementRotation=PersonalizationPreviewImage.#getRotationValue(placement.rotation),typeRotation=type?PersonalizationPreviewImage.#getRotationValue(type.rotation):0,mobileFontSize=Per
236sonalizationPreviewImage.#getPxValue(fontSizes.mobile),desktopFontSize=PersonalizationPreviewImage.#getPxValue(fontSizes.desktop);return[`--x-mobile: ${PersonalizationPreviewImage.#formatCoordinate(coordinates.mobile?.x)}`,`--y-mobile: ${PersonalizationPreviewImage.#formatCoordinate(coordinates.mobile?.y)}`,`--x-desktop: ${PersonalizationPreviewImage.#formatCoordinate(coordinates.desktop?.x)}`,`--y-desktop: ${PersonalizationPreviewImage.#formatCoordinate(coordinates.desktop?.y)}`,mobileFontSize===null?"":`--font-size-mobile-px: ${mobileFontSize}`,desktopFontSize===null?"":`--font-size-desktop-px: ${desktopFontSize}`,`--placement-rotation: ${placementRotation}deg`,`--type-rotation: ${typeRotation}deg`].filter(Boolean).join("; ")}static#getPlacementIndicatorMarkup(placement,type=null){return`
237            <div class="personalization-preview__overlay personalization-preview__overlay--indicator" style="${PersonalizationPreviewImage.#getPositionStyles(placement,type)}">
238                <span class="personalization-preview__overlay-indicator" role="presentation"></span>
239            </div>
240        `}static#takeFromPool(pool,count){return pool.repeat(Math.ceil(count/pool.length)).slice(0,count)}static#getPreviewLetters(count,isUppercase){const letters=PersonalizationPreviewImage.#takeFromPool(PersonalizationPreviewImage.#PREVIEW_LETTERS,count);return isUppercase?letters:`${letters.slice(0,1)}${letters.slice(1).toLowerCase()}`}static#getPreviewPlaceholderText(properties){const limit=Math.max(1,Math.trunc(Number(properties?.character_limit))||1),isUppercase=properties?.uppercase===!0;if(limit<4)return PersonalizationPreviewImage.#getPreviewLetters(limit,isUppercase);const letterCount=Math.ceil((limit+1)/2),numberCount=limit-letterCount,letters=PersonalizationPreviewImage.#getPreviewLetters(letterCount,isUppercase),numbers=PersonalizationPreviewImage.#takeFromPool(PersonalizationPreviewImage.#PREVIEW_NUMBERS,numberCount);return`${letters}${numbers}`}static#getTextOverlayMarkup(placement,type,positionStyles,{text,font,color,icon}){const fontHandle=font?.handle,threadColor=color?.thread,typeScale=PersonalizationPreviewImage.#getScaleValue(type?.scale),fontScale=PersonalizationPreviewImage.#getScaleValue(font?.scale),overlayStyles=[positionStyles,`--type-scale: ${typeScale}`,threadColor?`--color: #${threadColor}`:""].filter(Boolean).join("; "),baselineAdjustment=PersonalizationPreviewImage.#getPxValue(font?.baseline_adjustment),contentStyles=[`--font-scale: ${fontScale}`,fontHandle?`--font-family: var(--font-${fontHandle})`:"",baselineAdjustment?`--baseline-adjustment-px: ${baselineAdjustment}`:""].filter(Boolean).join("; ");return`
241            <div class="personalization-preview__overlay personalization-preview__overlay--text" style="${overlayStyles}">
242                <span class="personalization-preview__overlay-content"${contentStyles?` style="${contentStyles}"`:""}>
243                    ${text?`<span class="personalization-preview__overlay-text">${PersonalizationPreviewImage.#escapeHtml(text)}</span>`:""}
244                    ${icon?`<span class="personalization-preview__overlay-icon" role="presentation">${icon.svg}</span>`:""}
245                </span>
246            </div>
247        `}static#getImageOverlayMarkup(placement,type,positionStyles,option){const imageMarkup=PersonalizationPreviewImage.#parseImageMarkup(option.image),typeScale=PersonalizationPreviewImage.#getScaleValue(type?.scale),imageStyles=[positionStyles,`--type-scale: ${typeScale}`].join("; ");return imageMarkup&&PersonalizationPreviewImage.preloadImage(imageMarkup.src,imageMarkup.srcset,imageMarkup.sizes),`
248            <div class="personalization-preview__overlay personalization-preview__overlay--image transparent" style="${imageStyles}">
249                <span role="presentation" class="personalization-preview__overlay-image transparent">${option.image}</span>
250            </div>
251        `}static#getStrokeInset(elSvg){const shapes=elSvg.querySelectorAll("path, circle, rect, polygon, polyline, line, ellipse");let widest=0;for(const shape of shapes){const{stroke,strokeWidth}=getComputedStyle(shape);!stroke||stroke==="none"||(widest=Math.max(widest,parseFloat(strokeWidth)||0))}return widest/2}static#applyIconViewBox(elSvg,viewBox){elSvg.removeAttribute("width"),elSvg.removeAttribute("height"),elSvg.setAttribute("viewBox",viewBox)}static#normaliseIconViewBoxes(elRoot){for(const elSvg of elRoot.querySelectorAll(".personalization-preview__overlay-icon svg")){const cacheKey=elSvg.outerHTML,cached=PersonalizationPreviewImage.#iconViewBoxCache.get(cacheKey);if(cached){PersonalizationPreviewImage.#applyIconViewBox(elSvg,cac
251hed);continue}let bounds=null;try{bounds=elSvg.getBBox()}catch{continue}if(!bounds||bounds.width<=0||bounds.height<=0)continue;const inset=PersonalizationPreviewImage.#getStrokeInset(elSvg),viewBox=[bounds.x-inset,bounds.y-inset,bounds.width+inset*2,bounds.height+inset*2].map(value=>Number(value.toFixed(3))).join(" ");PersonalizationPreviewImage.#iconViewBoxCache.set(cacheKey,viewBox),PersonalizationPreviewImage.#applyIconViewBox(elSvg,viewBox)}}static#ensurePreviewImagesVisible(elRoot){for(const elImg of elRoot.querySelectorAll(".personalization-preview img"))elImg.loading="eager",elImg.classList.add("in-view");window.initLazyLoad?.()}connectedCallback(){const selections=decodeAttr(this.getAttribute("selections")),confirmedPersonalizations=decodeAttr(this.getAttribute("confirmed-personalizations"))??[],editingConfirmedIndexAttr=this.getAttribute("editing-confirmed-index"),editingConfirmedIndex=editingConfirmedIndexAttr===null||editingConfirmedIndexAttr===""?null:Number(editingConfirmedIndexAttr),personalizationsAttr=this.getAttribute("personalizations");personalizationsAttr&&PersonalizationPreviewImage.preloadImages(decodeAttr(personalizationsAttr));const baseImage=PersonalizationPreviewImage.getBaseImage(selections,confirmedPersonalizations),overlayMarkup=PersonalizationPreviewImage.getOverlayMarkup(selections,confirmedPersonalizations,{editingConfirmedIndex});this.innerHTML=`
252            <div class="personalization-preview" aria-hidden="true">
253                <div class="personalization-preview__base transparent">${baseImage}</div>
254                ${overlayMarkup}
255            </div>
256        `,PersonalizationPreviewImage.#ensurePreviewImagesVisible(this),PersonalizationPreviewImage.#normaliseIconViewBoxes(this),requestAnimationFrame(()=>{this.isConnected&&PersonalizationPreviewImage.#normaliseIconViewBoxes(this)}),PersonalizationPreviewImage.#markDraftOverlay(this,selections)}static#markDraftOverlay(element,selections){const elPreview=element.querySelector(".personalization-preview");if(!elPreview||!selections.placement)return;const elDraft=[...elPreview.querySelectorAll(".personalization-preview__overlay")].at(-1);if(!elDraft)return;elDraft.setAttribute("data-personalization-draft","");const details=selections.details??{},properties=selections.type?.properties??{},hasText=!!details.text,hasIcon=!!properties.icons?.find(entry=>entry.name===details.icon);elDraft.dataset.placeholder=!hasText&&!hasIcon?"true":"false"}static patchDraftOverlay(element,selections){const elPreview=element.querySelector(".personalization-preview");if(!elPreview||selections.type?.type!=="text")return;const details=selections.details??{},properties=selections.type?.properties??{},hasText=!!details.text,hasIcon=!!properties.icons?.find(entry=>entry.name===details.icon),usesPlaceholder=!hasText&&!hasIcon;let elDraft=elPreview.querySelector("[data-personalization-draft]");if(elDraft||(elDraft=[...elPreview.querySelectorAll(".personalization-preview__overlay")].at(-1)??null,elDraft&&elDraft.setAttribute("data-personalization-draft","")),elDraft&&!usesPlaceholder&&hasText){const elText=elDraft.querySelector(".personalization-preview__overlay-text"),elIcon=elDraft.querySelector(".personalization-preview__overlay-icon");if(elText&&elDraft.dataset.placeholder!=="true"&&!!elIcon===hasIcon){elText.textContent=details.text;return}}const draftMarkup=PersonalizationPreviewImage.#getSelectionOverlayMarkup(selections);if(elDraft&&elDraft.remove(),!draftMarkup)return;const parser=document.createElement("div");parser.innerHTML=draftMarkup.trim();const elNewDraft=parser.firstElementChild;elNewDraft&&(elNewDraft.setAttribute("data-personalization-draft",""),elNewDraft.dataset.placeholder=usesPlaceholder?"true":"false",elPreview.appendChild(elNewDraft),PersonalizationPreviewImage.#normaliseIconViewBoxes(element))}}class PersonalizationStepActions extends HTMLElement{connectedCallback(){const{current,total,canNext,showSave}=decodeAttr(this.getAttribute("context"));this.innerHTML=`
257            ${current>1?'<button type="button" class="personalization-modal--action prev btn--secondary" data-action="prev">Back</button>':""}
258            ${current<total?`<button type="button" class="personalization-modal--action next btn--primary" data-action="next" ${canNext?"":"disabled"}>Next</button>`:""}
259            ${showSave?'<button type="button" class="personalization-modal--action save btn--tertiary border" data-action="save">Add More Designs</button>':""}
260        `}updateCanNext(canNext){const elNext=this.querySelector('[data-action="next"]');elNext&&(elNext.disabled=!canNext)}}class PersonalizationStepPlacement extends HTMLElement{static findConfirmedEntryIndex(state,placement,product=state.currentSelections.product){return state.confirmedPersonalizations.findIndex(entry=>entry.placementLabel===placement.label&&entry.productLabel===(product?.label??null))}static isPlacementConfigured(state,placement,product=state.currentSelections.product){return PersonalizationStepPlacement.findConfirmedEntryIndex(state,placement,product)!==-1}static beginPlacementEdit(state,placement,entryIndex){const entry=state.confirmedPersonalizations[entryIndex],{product,type,details}=entry.selections;state.editingConfirmedIndex=entryIndex,state.currentSelections.product=product??state.currentSelections.product,state.currentSelections.placement=placement,state.currentSelections.type=type??null,state.currentSelections.details=details?{...details}:null,state.currentPersonalizationProduct={id:entry.personalizationVariantId,price:entry.price}}static isCurrentPlacementConfirmed(state){const{placement,product}=state.currentSelections;if(!placement)return!1;if(state.editingConfirmedIndex!==null){const editingEntry=state.confirmedPersonalizations[state.editingConfirmedIndex];if(editingEntry?.placementLabel===placement.label&&editingEntry?.productLabel===(product?.label??null))return!1}return PersonalizationStepPlacement.isPlacementConfigured(state,placement,product)}static isComplete(state){return!!state.currentSelections.placement}static handleSelection(state,data,target){const{value,dataset}=target,{step}=dataset;let valueObject={},clearDownstream=null,currentPersonalizationProduct;
260if(step==="product")valueObject=data.personalizations.find(product=>product.label==value),valueObject?.label!==state.currentSelections.product?.label&&(clearDownstream=["placement","type","details"],currentPersonalizationProduct=null,state.editingConfirmedIndex=null);else if(step==="placement"){valueObject=state.currentSelections.product.placements.find(placement=>placement.label==value);const entryIndex=PersonalizationStepPlacement.findConfirmedEntryIndex(state,valueObject,state.currentSelections.product);entryIndex!==-1?(PersonalizationStepPlacement.beginPlacementEdit(state,valueObject,entryIndex),currentPersonalizationProduct=state.currentPersonalizationProduct):(state.editingConfirmedIndex=null,clearDownstream=["type","details"],currentPersonalizationProduct=null)}return{step,valueObject,clearDownstream,currentPersonalizationProduct,syncCart:{includeCurrentDraft:!1}}}static beforeRender(){}static getOptionGroupFingerprints(state,data){const fingerprints=[];return data.personalizations.length>1&&fingerprints.push(optionGroupFingerprint("product",data.personalizations)),state.currentSelections.product?.placements?.length&&fingerprints.push(optionGroupFingerprint("placement",state.currentSelections.product.placements)),fingerprints.filter(Boolean)}connectedCallback(){const{data,currentSelections,confirmedPersonalizations}=decodeAttr(this.getAttribute("context")),hasMultipleProducts=data.personalizations.length>1,state={confirmedPersonalizations,currentSelections},configuredLabels=currentSelections.product.placements.filter(placement=>PersonalizationStepPlacement.isPlacementConfigured(state,placement,currentSelections.product)).map(placement=>placement.label),placementHeading=currentSelections.product.placements.length>1&&configuredLabels.length>0?"Select Another Placement":"Select Your Placement";this.innerHTML=`
261            ${hasMultipleProducts?`
262                <personalization-radio-group context="${encodeAttr({step:"product",options:data.personalizations,selectedLabel:currentSelections.product?.label,legend:"Select Your Product"})}"></personalization-radio-group>
263            `:""}
264            <personalization-radio-group context="${encodeAttr({step:"placement",options:currentSelections.product.placements,selectedLabel:currentSelections.placement?.label,configuredLabels,legend:placementHeading})}"></personalization-radio-group>
265        `}}class PersonalizationStepType extends HTMLElement{static isComplete(state){return!!state.currentSelections.type}static handleSelection(state,data,target){const{value}=target,valueObject=state.currentSelections.placement.types.find(type=>type.label==value);let clearDownstream=null,currentPersonalizationProduct=valueObject.type=="text"?valueObject.properties.product:null;return valueObject.type!==state.currentSelections.type?.type&&(clearDownstream=["details"]),{step:"type",valueObject,clearDownstream,currentPersonalizationProduct,syncCart:{includeCurrentDraft:!1}}}static beforeRender(){}static getOptionGroupFingerprints(state){const types=state.currentSelections.placement?.types;if(!types?.length)return[];const fingerprint=optionGroupFingerprint("type",types);return fingerprint?[fingerprint]:[]}connectedCallback(){const{currentSelections}=decodeAttr(this.getAttribute("context")),textTypes=currentSelections.placement?currentSelections.placement.types.filter(type=>type.type=="text"):[];this.innerHTML=`
266            ${currentSelections.placement?`
267                <personalization-radio-group context="${encodeAttr({step:"type",options:currentSelections.placement.types,selectedLabel:currentSelections.type?.label,legend:"Choose Personalization Type"})}"></personalization-radio-group>
268            `:""}
269            ${textTypes.length?`
270                <div class="personalization-modal--note small">
271                    ${textTypes.map(type=>`${type.label} \u2013 Max ${PersonalizationTextInput.getCharacterLimit(type.properties)} characters.`).join("<br />")}
272                </div>
273            `:""}
274        `}}class PersonalizationStepDetails extends HTMLElement{static isDraftComplete(state){const{placement,type,details}=state.currentSelections;if(!placement||!type)return!1;switch(type.type){case"text":return details?.text&&PersonalizationTextInput.isProfane(details.text)?!1:!!details?.text||!!detail
274s?.icon;case"image":return!!details?.[type.label.toLowerCase()];default:return!1}}static isComplete(state){return PersonalizationStepDetails.isDraftComplete(state)}static handleSelection(state,data,target){const{value,dataset}=target,{step,input:selection}=dataset;if(step==="type")return PersonalizationStepType.handleSelection(state,data,target);let valueObject={...state.currentSelections.details},sanitizedSelectionStart=null,sanitizedSelectionEnd=null,currentPersonalizationProduct,imageCategoryIndex;if(selection==="text"){const limit=state.currentSelections.type?.properties?PersonalizationTextInput.getTextCharacterLimit(state.currentSelections.type.properties,valueObject):null,properties=state.currentSelections.type?.properties;let detailValue=PersonalizationTextInput.sanitizeTextInput(value,properties);if(limit!==null&&(detailValue=PersonalizationTextInput.enforceTextLength(detailValue,limit)),detailValue!==value&&typeof target.selectionStart=="number"){let beforeCursor=PersonalizationTextInput.sanitizeTextInput(value.slice(0,target.selectionStart),properties);limit!==null&&(beforeCursor=PersonalizationTextInput.enforceTextLength(beforeCursor,limit)),sanitizedSelectionStart=beforeCursor.length,sanitizedSelectionEnd=beforeCursor.length}detailValue?valueObject.text=detailValue:delete valueObject.text}else if(selection==="icon")if(target.checked){if(valueObject.icon=value,valueObject.text&&state.currentSelections.type?.properties){const limit=PersonalizationTextInput.getTextCharacterLimit(state.currentSelections.type.properties,valueObject);valueObject.text=PersonalizationTextInput.enforceTextLength(PersonalizationTextInput.sanitizeTextInput(valueObject.text,state.currentSelections.type.properties),limit)}}else delete valueObject.icon;else if(selection===state.currentSelections.type?.label.toLowerCase()){imageCategoryIndex=Number(target.closest("[data-image-options]")?.dataset.categoryIndex);const resolvedCategoryIndex=Number.isFinite(imageCategoryIndex)?imageCategoryIndex:state.currentSelections.imageCategoryIndex;valueObject[selection]=findImageOption(state.currentSelections.type,option=>option.value==value,resolvedCategoryIndex),valueObject[selection]||delete valueObject[selection]}else{const propertyKey=selection==="color"?"colors":selection==="font"?"fonts":selection;valueObject[selection]=state.currentSelections.type?.properties?.[propertyKey]?.find(option=>option.name==value)}return state.currentSelections.type?.type=="image"&&(currentPersonalizationProduct=valueObject[selection]?.product),{step:"details",valueObject,currentPersonalizationProduct,imageCategoryIndex:Number.isFinite(imageCategoryIndex)?imageCategoryIndex:void 0,sanitizedSelectionStart,sanitizedSelectionEnd,syncCart:{includeCurrentDraft:!0}}}static beforeRender(state){if(state.steps.current!==3)return;const properties=state.currentSelections.type?.properties;if(state.currentSelections.type?.type!=="text"||!properties)return;const fonts=properties.fonts,colors=properties.colors;if(!fonts?.length&&!colors?.length)return;state.currentSelections.details||(state.currentSelections.details={});const details=state.currentSelections.details;fonts?.length&&!details.font&&(details.font=fonts[0]),colors?.length&&!details.color&&(details.color=colors[0]),PersonalizationTextInput.syncDetailsTextToLimit(state)}static handleKeydown(ev){PersonalizationIconPicker.handleKeydown(ev)}static getOptionGroupFingerprints(state){return PersonalizationStepType.getOptionGroupFingerprints(state)}connectedCallback(){const{currentSelections}=decodeAttr(this.getAttribute("context")),type=currentSelections.type,properties=type?.properties,details=currentSelections.details??{};let detailPickers="";type?.type=="text"&&properties?detailPickers=`
275                <personalization-text-input context="${encodeAttr({properties,details})}"></personalization-text-input>
276                ${properties.icons?`<personalization-icon-picker context="${encodeAttr({icons:properties.icons,details,properties})}"></personalization-icon-picker>`:""}
277                ${properties.fonts?`<personalization-font-picker context="${encodeAttr({fonts:properties.fonts,details})}"></personalization-font-picker>`:""}
278                ${properties.colors?`<personalization-color-picker context="${encodeAttr({colors:properties.colors,details})}"></personalization-color-picker>`:""}
279            `:type?.type=="image"&&(detailPickers=`<personalization-image-picker context="${encodeAttr({type,details,selectedCategoryIndex:currentSelections.imageCategoryIndex??null})}"></personalization-image-picker>`),this.innerHTML=`
280            <div class="personalization-modal--field">
281                <p class="personalization-modal--heading">Add Details</p>
282                <span class="personalization-modal--note small">* Required</span>
283                ${currentSelections.placement?`
284                    <personalization-radio-group context="${encodeAttr({step:"type",options:currentSelections.placement.types,selectedLabel:currentSelections.type?.label,legend:"Choose Personalization Type",legendClass:"visually-hidden"})}"></personalization-radio-group>
285                `:""}
286            </div>
287            ${detailPickers}
288        `}}class PersonalizationStepConfirmation extends HTMLElement{static isComplete(){return!0}static handleSelection(){return null}static beforeRender(){}static getOptionGroupFingerprints(){return[]}static commitUnsavedDraft(state,{persistDraft}={}){return!PersonalizationStepDetails.isDraftComplete(state)||PersonalizationStepPlacement.isCurrentPlacementConfirmed(state)?!1:(state.commitCurrentDraft(),state.currentSelections={product:state.currentSelections.product,placement:null,type:null,details:null},state.currentPersonalizationProduct=null,persistDraft?.(),!0)}static beginEntryEdit(state,entryIndex,{persistDraft}={}){PersonalizationStepConfirmation.commitUnsavedDraft(state,{persistDraft});const placement=state.confirmedPersonalizations[entryIndex]?.selections?.placement;return placement?(PersonalizationStepPlacement.beginPlacementEdit(state,placement,entryIndex),state.steps.current=1,{syncCart:{includeCurrentDraft:!0}}):null}static beginDraftEdit(state){return state.steps.current=1,{syncCart:{includeCurrentDraft:!0}}}static handleAction(state,actionEl,{persistDraft}={}){const{confirmationTarget,entryIndex:entryIndexAttr}=actionEl.dataset;if(confirmationTarget==="draft")return PersonalizationStepConfirmation.beginDraftEdit(state);const entryIndex=Number(entryIndexAttr);return Number.isNaN(entryIndex)?null:PersonalizationStepConfirmation.beginEntryEdit(state,entryIndex,{persistDraft})}static#clearInProgressSelections(state){state.currentSelections={product:state.currentSelections.product,placement:null,type:null,details:null},state.currentPersonalizationProduct=null,state.editingConfirmedIndex=null}static#hasUncommittedConfirmationDraft(state){return PersonalizationStepDetails.isDraftComplete(state)&&!PersonalizationStepPlacement.isCurrentPlacementConfirmed(state)}static#setStepAfterRemoval(state){const hasRemainingPersonalizations=state.confirmedPersonalizations.length>0||PersonalizationStepConfirmation.#hasUncommittedConfirmationDraft(state);state.steps.current=hasRemainingPersonalizations?state.steps.total:1}static#clearDraftForRemovedEntry(state,removedEntry){const{placement,product}=state.currentSelections;!placement||placement.label!==removedEntry.placementLabel||(product?.label??null)!==removedEntry.productLabel||PersonalizationStepConfirmation.#clearInProgressSelections(state)}static removeConfirmedEntry(state,entryIndex,{persistDraft}={}){if(entryIndex<0||entryIndex>=state.confirmedPersonalizations.length)return null;const removedEntry=state.confirmedPersonalizations[entryIndex];return state.confirmedPersonalizations.splice(entryIndex,1),state.renumberLocationKeys(),state.editingConfirmedIndex===entryIndex?PersonalizationStepConfirmation.#clearInProgressSelections(state):state.editingConfirmedIndex>entryIndex?state.editingConfirmedIndex-=1:PersonalizationStepConfirmation.#clearDraftForRemovedEntry(state,removedEntry),PersonalizationStepConfirmation.#setStepAfterRemoval(state),persistDraft?.(),{syncCart:{includeCurrentDraft:PersonalizationStepConfirmation.#hasUncommittedConfirmationDraft(state)}}}static removeDraft(state,{persistDraft}={}){return state.editingConfirmedIndex!==null?PersonalizationStepConfirmation.removeConfirmedEntry(state,state.editingConfirmedIndex,{persistDraft}):(PersonalizationStepConfirmation.#clearInProgressSelections(state),PersonalizationStepConfirmation.#setStepAfterRemoval(state),persistDraft?.(),{syncCart:{includeCurrentDraft:!1}})}static handleRemove(state,actionEl,{persistDraft}={}){const{confirmationTarget,entryIndex:entryIndexAttr}=actionEl.dataset;if(confirmationTarget==="draft")return PersonalizationStepConfirmation.removeDraft(state,{persistDraft});const entryIndex=Number(entryIndexAttr);return Number.isNaN(entryIndex)?null:PersonalizationStepConfirmation.removeConfirmedEntry(state,entryIndex,{persistDraft})}static showAddMoreDesigns(state){return state.steps.current!==state.steps.total||!state.hasAnyUnconfiguredPlacements()?!1:PersonalizationStepDetails.isDraftComplete(state)?!PersonalizationStepPlacement.isCurrentPlacementConfirmed(state):!state.currentSelections.placement}static save(state,{persistDraft,render}){if(state.steps.current!==state.steps.total)return;const draftComplete=PersonalizationStepDetails.isDraftComplete(state);draftComplete&&PersonalizationStepPlacement.isCurrentPlacementConfirmed(state)||(draftComplete&&(state.commitCurrentDraft(),PersonalizationCart.syncFromState(state,{includeCurrentDraft:!1})),state.currentSelections={product:state.getFirstProductWithUnconfiguredPlacements(),placement:null,type:null,details:null},state.currentPersonalizationProduct=null,state.editingConfirmedIndex=null,state.steps.current=state.hasAnyUnconfiguredPlacements()?1:state.steps.total,persistDraft(),render())}connectedCallback(){const{data,currentSelections,confirmedPersonalizations,editingConfirmedIndex,steps,currentPersonalizationProduct,confirmationHeading,confirmationDescription,termsHtml}=decodeAttr(this.getAttribute("context")),state={data,currentSelections,confirmedPersonalizations,editingConfirmedIndex:editingConfirmedIndex??null,steps,currentPersonalizationProduct},{rows,subtotal}=PersonalizationCart.getConfirmationPricing(state);this.innerHTML=`
289            <div class="personalization-modal--field">
290                <h3 class="personalization-modal--heading">${confirmationHeading}</h3>
291                <div class="personalization-modal--confirmation-description">
292                    ${confirmationDescription}
293                </div>
294            </div>
295            <table class="personalization-modal--confirmation-items">
296                <thead class="visually-hidden">
297                    <tr>
298                        <th>Product</th>
299                        <th>Price</th>
300                        <th>Actions</th>
301                    </tr>
302                </thead>
303                <tbody>
304                    <tr>
305                        <td class="personalization-modal--confirmation-product">${data.base_product.title}</td>
306                        <td class="personalization-modal--confirmation-price">${window.formatMoney(data.base_product.price)}</td>
307                        <td class="personalization-modal--confirmation-actions"></td>
308                    </tr>
309                    ${rows.map(row=>{const editAttrs=row.isDraft&&row.entryIndex===null?'data-action="edit" data-confirmation-target="draft"':`data-action="edit" data-entry-index="${row.entryIndex}"`,removeAttrs=row.isDraft&&row.entryIndex===null?'data-action="remove" data-confirmation-target="draft"':`data-action="remove" data-entry-index="${row.entryIndex}"`;return`
310                        <tr>
311                            <td class="personalization-modal--confirmation-product">${row.summary}</td>
312                            <td class="personalization-modal--confirmation-price">${window.formatMoney(row.price)}</td>
313                            <td class="personalization-modal--confirmation-actions">
314                                <button
315                                    type="button"
316                                    class="personalization-modal--confirmation-action btn--edit"
317                                    ${editAttrs}
318                                    aria-label="Edit ${row.summary}"
319                                ></button>
320                                <button
321                                    type="button"
322                                    class="personalization-modal--confirmation-action btn--remove"
323                                    ${removeAttrs}
324                                    aria-label="Remove ${row.summary}"
325                                ></button>
326                            </td>
327                        </tr>
328                    `}).join("")}
329                </tbody>
330                <tfoot>
331                    <tr>
332                        <td class="personalization-modal--confirmation-product">Subtotal</td>
333                        <td class="personalization-modal--confirmation-price">${window.formatMoney(subtotal)}</td>
334                        <td class="personalization-modal--confirmation-actions"></td>
335                    </tr>
336                </tfoot>
337            </table>
338            ${termsHtml??""}
339        `}}const STEP_CLASSES=[PersonalizationStepPlacement,PersonalizationStepType,PersonalizationStepDetails,PersonalizationStepConfirmation],STEP_TAGS=["personalization-step-placement","personalization-step-type","personalization-step-detail
339s","personalization-step-confirmation"],STEP_NAMES=["placement","type","details","confirmation"];if(!customElements.get("personalization-modal")){customElements.define("personalization-text-input",PersonalizationTextInput),customElements.define("personalization-icon-picker",PersonalizationIconPicker),customElements.define("personalization-font-picker",PersonalizationFontPicker),customElements.define("personalization-color-picker",PersonalizationColorPicker),customElements.define("personalization-image-picker",PersonalizationImagePicker),customElements.define("personalization-radio-group",PersonalizationRadioGroup),customElements.define("personalization-progress",PersonalizationProgress),customElements.define("personalization-preview-image",PersonalizationPreviewImage),customElements.define("personalization-step-actions",PersonalizationStepActions),customElements.define("personalization-cart",PersonalizationCart),customElements.define("personalization-step-placement",PersonalizationStepPlacement),customElements.define("personalization-step-type",PersonalizationStepType),customElements.define("personalization-step-details",PersonalizationStepDetails),customElements.define("personalization-step-confirmation",PersonalizationStepConfirmation);class PersonalizationModal extends PopupModal{#isRendering=!1;#handleChangeBound=null;#updateCurrentStepBound=null;#handleSelectKeydownBound=null;#handleIconKeydownBound=null;#handlePersonalizationAddedBound=null;#handleImageCategoryChangeBound=null;#preserveSelectOpenOnRender=!1;#preserveScrollOnRender=!1;#scrollAnchorOnRender=null;static#SCROLL_PRESERVE_SELECTORS=[".personalization-modal--main",".personlization-modal--options-images"];constructor(){super(),this.confirmationHeading=this.getAttribute("confirmation-heading"),this.confirmationDescription=this.getAttribute("confirmation-description")}connectedCallback(){this.elParentSection=this.closest("section")||document.body,this.elTriggers=this.elParentSection.querySelectorAll("[data-personalization-trigger]"),this.elTriggers.forEach(el=>el.addEventListener("click",()=>this.setAttribute("open",!0))),this.elData=this.querySelector("[data-personalization-data]"),this.data=JSON.parse(this.elData.textContent),this.state=new PersonalizationState(this.data),this.state.resetAll();const cartEdit=typeof window.expireStorageItem=="function"?window.expireStorageItem(PersonalizationCart.CART_EDIT_STORAGE_KEY):null;let shouldOpenFromCartEdit=!1;cartEdit?.variantId===this.data.base_product.id?(PersonalizationCart.hydrateStateFromCart(this.state,cartEdit),this.state.cartEditGroupKeys=cartEdit.groupKeys,this.state.cartEditQuantities=cartEdit.quantities??null,typeof window.storageAvailable=="function"&&window.storageAvailable()&&localStorage.removeItem(PersonalizationCart.CART_EDIT_STORAGE_KEY),shouldOpenFromCartEdit=!0):this.state.loadDraft(),this.focusElement=null,this.focusSelectionStart=null,this.focusSelectionEnd=null,this.elContent=this.querySelector("[data-template]"),this.elTerms=this.querySelector("[data-terms]"),this.#handleChangeBound=this.#handleChange.bind(this),this.#updateCurrentStepBound=this.#updateCurrentStep.bind(this),this.#handleSelectKeydownBound=this.#handleSelectKeydown.bind(this),this.#handleIconKeydownBound=this.#handleKeydown.bind(this),this.#handlePersonalizationAddedBound=this.#handlePersonalizationAdded.bind(this),this.#handleImageCategoryChangeBound=this.#handleImageCategoryChange.bind(this),this.elContent.addEventListener("change",this.#handleChangeBound),this.elContent.addEventListener("input",this.#handleChangeBound),this.elContent.addEventListener("click",this.#updateCurrentStepBound),this.elContent.addEventListener("keydown",this.#handleSelectKeydownBound,!0),this.elContent.addEventListener("keydown",this.#handleIconKeydownBound,!0),this.elContent.addEventListener("personalization-image-category-change",this.#handleImageCategoryChangeBound),this.addEventListener("personalization-added",this.#handlePersonalizationAddedBound),this.#render(),shouldOpenFromCartEdit&&this.setAttribute("open","true")}attributeChangedCallback(name,oldValue,newValue){name==="open"&&newValue==="true"&&(!this.state.cartEditGroupKeys?.length&&this.state.confirmedPersonalizations?.length&&!this.state.hasAnyUnconfiguredPlacements()&&!PersonalizationStepDetails.isDraftComplete(this.state)&&(this.state.steps.current=this.state.steps.total),this.#render()),super.attributeChangedCallback(name,oldValue,newValue)}close(){this.state.persistDraft(),this.state.resetInProgress(),super.close()}clearSavedDesigns(){this.state.clearDraft(),this.state.resetAll(),this.state.cartEditGroupKeys=null,this.getAttribute("open")==="true"&&this.#render()}updateData(data){this.data?.base_product?.id!==data.base_product.id&&this.state?.persistDraft(),this.data=data,this.elData&&(this.elData.textContent=JSON.stringify(data)),this.state=new PersonalizationState(this.data),this.state.resetAll(),this.state.loadDraft(),this.getAttribute("open")==="true"&&this.#render()}#getStepContext(){return{data:this.data,currentSelections:this.state.currentSelections,confirmedPersonalizations:this.state.confirmedPersonalizations,editingConfirmedIndex:this.state.editingConfirmedIndex,steps:this.state.steps,currentPersonalizationProduct:this.state.currentPersonalizationProduct,confirmationHeading:this.confirmationHeading,confirmationDescription:this.confirmationDescription,termsHtml:this.elTerms?this.elTerms.innerHTML:""}}#handlePersonalizationAdded(){const elStatus=this.querySelector("[data-personalization-status]");elStatus&&(elStatus.textContent="Personalization added to cart."),window.setTimeout(()=>{this.state.clearDraft(),this.state.resetAll(),super.close(),elStatus&&(elStatus.textContent="")},150)}#render(){this.#isRendering=!0,STEP_CLASSES[this.state.steps.current-1].beforeRender?.(this.state);
339const renderState=this.#captureRenderState();this.elContent.innerHTML=this.#template(),this.#restoreRenderState(renderState),this.focusElement=null,this.focusSelectionStart=null,this.focusSelectionEnd=null,this.#preserveSelectOpenOnRender=!1,this.#preserveScrollOnRender=!1,this.#scrollAnchorOnRender=null,this.#isRendering=!1}#getScrollContainers(){const containers=[];this.elContent&&containers.push({selector:null,el:this.elContent});for(const selector of PersonalizationModal.#SCROLL_PRESERVE_SELECTORS){const el=this.elContent?.querySelector(selector);el&&containers.push({selector,el})}return containers}#findRadioGroupByFingerprint(fingerprint){if(!fingerprint)return null;for(const el of this.elContent?.querySelectorAll("personalization-radio-group")??[]){const{step,options}=decodeAttr(el.getAttribute("context"));if(optionGroupFingerprint(step,options)===fingerprint)return el}return null}#captureScrollAnchor(fingerprint){const anchor=this.#findRadioGroupByFingerprint(fingerprint);if(!anchor)return null;const offsets=[];for(const{selector,el}of this.#getScrollContainers()){if(!el.contains(anchor))continue;const scrollerRect=el.getBoundingClientRect(),anchorRect=anchor.getBoundingClientRect();offsets.push({selector,visualOffset:anchorRect.top-scrollerRect.top})}return offsets.length?{fingerprint,offsets}:null}#restoreScrollAnchor(anchorState){if(!anchorState?.offsets?.length)return;requestAnimationFrame(()=>{const anchor=this.#findRadioGroupByFingerprint(anchorState.fingerprint);if(anchor)for(const{selector,visualOffset}of anchorState.offsets){const el=selector?this.elContent?.querySelector(selector):this.elContent;if(!el?.contains(anchor))continue;const scrollerRect=el.getBoundingClientRect(),delta=anchor.getBoundingClientRect().top-scrollerRect.top-visualOffset;delta!==0&&(el.scrollTop+=delta)}})}#captureScrollPositions(){const positions=[{selector:null,scrollTop:this.elContent?.scrollTop??0,scrollLeft:this.elContent?.scrollLeft??0}];for(const selector of PersonalizationModal.#SCROLL_PRESERVE_SELECTORS){const el=this.elContent?.querySelector(selector);el&&positions.push({selector,scrollTop:el.scrollTop,scrollLeft:el.scrollLeft})}return positions}#restoreScrollPositions(positions){if(!positions?.length)return;requestAnimationFrame(()=>{for(const{selector,scrollTop,scrollLeft}of positions){const el=selector?this.elContent?.querySelector(selector):this.elContent;el&&(el.scrollTop=scrollTop,el.scrollLeft=scrollLeft)}})}#captureRenderState(){const openSelectToggleIds=this.#preserveSelectOpenOnRender?[...this.querySelectorAll('custom-select[open="true"]')].map(el=>el.querySelector("[data-toggle]")?.id).filter(Boolean):[];let focusId=this.focusElement,selectionStart=this.focusSelectionStart,selectionEnd=this.focusSelectionEnd;if(!focusId){const elFocused=this.contains(document.activeElement)?document.activeElement:null;elFocused?.id&&(focusId=elFocused.id,typeof elFocused.selectionStart=="number"&&(selectionStart=elFocused.selectionStart,selectionEnd=elFocused.selectionEnd))}return{preserveSelectOpen:this.#preserveSelectOpenOnRender,preserveScroll:this.#preserveScrollOnRender,scrollPositions:this.#preserveScrollOnRender?this.#captureScrollPositions():null,scrollAnchor:this.#scrollAnchorOnRender,openSelectToggleIds,focusId,selectionStart,selectionEnd}}#restoreRenderState({preserveSelectOpen,preserveScroll,scrollPositions,scrollAnchor,openSelectToggleIds,focusId,selectionStart,selectionEnd}){const toggleIdsToOpen=new Set(openSelectToggleIds);if(preserveSelectOpen&&focusId){const toggleId=this.querySelector(`#${CSS.escape(focusId)}`)?.closest("custom-select")?.querySelector("[data-toggle]")?.id;toggleId&&toggleIdsToOpen.add(toggleId)}for(const toggleId of toggleIdsToOpen)this.querySelector(`#${CSS.escape(toggleId)}`)?.closest("custom-select")?.setAttribute("open","true");if(focusId){const newFocusElement=this.querySelector(`#${CSS.escape(focusId)}`);newFocusElement&&(newFocusElement.focus({preventScroll:!0}),typeof selectionStart=="number"&&typeof newFocusElement.setSelectionRange=="function"&&newFocusElement.setSelectionRange(selectionStart,selectionEnd))}preserveScroll?this.#restoreScrollPositions(scrollPositions):scrollAnchor&&this.#restoreScrollAnchor(scrollAnchor)}#handleSelectKeydown(ev){ev.target.closest('custom-select input[type="radio"]')&&["ArrowUp","ArrowDown","ArrowLeft","ArrowRight","Home","End"].includes(ev.key)&&(this.#preserveSelectOpenOnRender=!0)}#handleKeydown(ev){STEP_CLASSES[this.state.steps.current-1].handleKeydown?.(ev)}#handleImageCategoryChange(ev){const{categoryIndex}=ev.detail??{};Number.isFinite(categoryIndex)&&(this.state.currentSelections.imageCategoryIndex=categoryIndex)}#handleChange(ev){if(this.#isRendering)return;const elTarget=ev.target;if(!(ev.type==="input"&&elTarget.matches('input[type="radio"], input[type="checkbox"]'))){if(elTarget.matches('input[data-input="text"]')){if(ev.type==="change")return;this.#applyTextInputSelection(elTarget);return}this.#applySelection(elTarget)}}#applyTextInputSelection(target){const result=PersonalizationStepDetails.handleSelection(this.state,this.data,target);if(!result)return;const{step,valueObject,syncCart}=result;this.state.currentSelections[step]=valueObject,PersonalizationCart.syncFromState(this.state,syncCart);const sanitizedText=valueObject.text??"";target.value!==sanitizedText&&(target.value=sanitizedText,result.sanitizedSelectionStart!==null&&result.sanitizedSelectionStart!==void 0&&target.setSelectionRange(result.sanitizedSelectionStart,result.sanitizedSelectionEnd)),this.#patchTextDetailsUI()}#patchTextDetailsUI(){const properties=this.state.currentSelections.type?.properties,details=this.state.currentSelections.details??{};this.querySelector("personalization-text-input")?.updateFromDetails(properties,details),this.querySelector("personalization-icon-picker")?.updateDisabledState(details,properties);const elPreview=this.querySelector("personalization-preview-image");elPreview&&PersonalizationPreviewImage.patchDraftOverlay(elPreview,this.state.currentSelections),this.querySelector("personalization-step-actions")?.updateCanNext(PersonalizationStepDetails.isDraftComplete(this.state))}#applySelection(target){const result=STEP_CLASSES[this.state.steps.current-1].handleSelection(this.state,this.data,target);if(!result)return;const{step,valueObject,clearDownstream,currentPersonalizationProduct,syncCart,imageCategoryIndex}=result;if(clearDownstream){for(const key of clearDownstream)this.state.currentSelections[key]=null;
339clearDownstream.some(key=>["details","type","placement"].includes(key))&&delete this.state.currentSelections.imageCategoryIndex}currentPersonalizationProduct!==void 0&&(this.state.currentPersonalizationProduct=currentPersonalizationProduct),imageCategoryIndex!==void 0&&(this.state.currentSelections.imageCategoryIndex=imageCategoryIndex);const elCustomSelect=target.closest("custom-select");elCustomSelect&&this.#preserveSelectOpenOnRender?this.focusElement=target.id:elCustomSelect?this.focusElement=elCustomSelect.querySelector("[data-toggle]")?.id??target.id:this.focusElement=target.id,result.sanitizedSelectionStart!==null&&result.sanitizedSelectionStart!==void 0?(this.focusSelectionStart=result.sanitizedSelectionStart,this.focusSelectionEnd=result.sanitizedSelectionEnd):typeof target.selectionStart=="number"&&(this.focusSelectionStart=target.selectionStart,this.focusSelectionEnd=target.selectionEnd),this.state.currentSelections[step]=valueObject,PersonalizationCart.syncFromState(this.state,syncCart),this.#preserveScrollOnRender=!0,this.#render()}#updateCurrentStep(ev){const actionEl=ev.target.closest("[data-action]");if(!actionEl||this.#isRendering)return;const direction=actionEl.dataset.action;if(direction==="edit"||direction==="remove"){const StepClass=STEP_CLASSES[this.state.steps.current-1],result=(direction==="remove"?StepClass.handleRemove:StepClass.handleAction)?.(this.state,actionEl,{persistDraft:()=>this.state.persistDraft()});result&&(result.syncCart&&PersonalizationCart.syncFromState(this.state,result.syncCart),this.#render());return}if(direction==="save"){PersonalizationStepConfirmation.save(this.state,{persistDraft:()=>this.state.persistDraft(),render:()=>this.#render()});return}if(direction!=="add"){if(direction==="prev"){const currentStepIndex=this.state.steps.current-1,destinationStepIndex=currentStepIndex-1,sharedFingerprints=this.#getSharedStepFingerprints(currentStepIndex,destinationStepIndex);sharedFingerprints.length&&(this.#scrollAnchorOnRender=this.#captureScrollAnchor(sharedFingerprints[0]));const currentStep=Object.keys(this.state.currentSelections)[this.state.steps.current];this.state.currentSelections[currentStep]=null,this.state.steps.current-=1}else if(direction==="next"){const currentStepIndex=this.state.steps.current-1,destinationStepIndex=currentStepIndex+1,sharedFingerprints=this.#getSharedStepFingerprints(currentStepIndex,destinationStepIndex);sharedFingerprints.length&&(this.#scrollAnchorOnRender=this.#captureScrollAnchor(sharedFingerprints[0])),this.state.steps.current+=1,this.state.steps.current===this.state.steps.total&&PersonalizationStepDetails.isDraftComplete(this.state)&&PersonalizationCart.syncFromState(this.state,{includeCurrentDraft:!0})}this.#render()}}#getSharedStepFingerprints(currentStepIndex,destinationStepIndex){if(destinationStepIndex<0||destinationStepIndex>=STEP_CLASSES.length)return[];const CurrentStepClass=STEP_CLASSES[currentStepIndex],DestinationStepClass=STEP_CLASSES[destinationStepIndex],currentFingerprints=CurrentStepClass.getOptionGroupFingerprints?.(this.state,this.data)??[],destinationFingerprints=DestinationStepClass.getOptionGroupFingerprints?.(this.state,this.data)??[];return getSharedOptionGroupFingerprints(currentFingerprints,destinationFingerprints)}#template(){const stepIndex=this.state.steps.current-1,activeStep=STEP_NAMES[stepIndex],stepTag=STEP_TAGS[stepIndex],StepClass=STEP_CLASSES[stepIndex],stepContext=encodeAttr(this.#getStepContext());return`
340                <form class="personalization-modal--form" data-form>
341                    <div class="personalization-modal--main">
342                        <div class="personalization-modal--progress">
343                            <personalization-progress context="${encodeAttr({current:this.state.steps.current,total:this.state.steps.total})}"></personalization-progress>
344                        </div>
345                        <div class="personalization-modal--image">
346                            <personalization-preview-image
347                                selections="${encodeAttr(this.state.currentSelections)}"
348                                confirmed-personalizations="${encodeAttr(this.state.confirmedPersonalizations)}"
349                                editing-confirmed-index="${this.state.editingConfirmedIndex??""}"
350                                personalizations="${encodeAttr(this.data.personalizations)}"
351                            ></personalization-preview-image>
352                        </div>
353                        <${stepTag} context="${stepContext}" class="personalization-modal--step ${activeStep} ${this.state.currentSelections.type?.type=="image"?"image":""}"></${stepTag}>
354                    </div>
355                    <div class="personalization-modal--actions">
356                        <personalization-step-actions context="${encodeAttr({current:this.state.steps.current,total:this.state.steps.total,canNext:StepClass.isComplete(this.state),showSave:PersonalizationStepConfirmation.showAddMoreDesigns(this.state)})}"></personalization-step-actions>
357                        ${this.state.steps.current===this.state.steps.total?`
358                            <personalization-cart></personalization-cart>
359                        `:""}
360                    </div>
361                </form>
362            `}}customElements.define("personalization-modal",PersonalizationModal)}window.startPersonalizationCartEdit=payload=>PersonalizationCart.startCartEdit(payload),window.clearPersonalizationDraft=variantId=>PersonalizationCart.clearSavedDesigns(variantId);
363//# sourceMappingURL=/cdn/shop/t/3471/assets/personalization-modal.js.map?v=77062502030212783981790184691

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.