PageSourceSearch

https://tapeproviders.com/cdn/shop/t/80/assets/cart-bundle-recommendations.js?v=75217917767803588321790362958

js tapeproviders.com collected 2026-10-06 07:59:23 UTC 9,539 bytes, 43 lines download raw bytes

1(()=>{const API_VERSION="2026-07",BUNDLE_FIELDS=`
2    nodes { id availableForSale product { id handle onlineStoreUrl } }
3    pageInfo { hasNextPage endCursor }
4  `;class CartBundleRecommendations{constructor(containers){this.containers=containers,this.country=containers[0].dataset.country,this.root=(window.Shopify?.routes?.root||"/").replace(/\/?$/,"/"),this.relationships=new Map,this.products=new Map,this.components=new Map,this.requestId=0,this.adding=!1,containers.forEach(container=>container.addEventListener("click",event=>{const button=event.target.closest("[data-add-bundle]");button&&container.contains(button)&&(event.preventDefault(),this.addBundle(button))})),window.addEventListener("cart:bundles:update",event=>this.update(event.detail))}async query(query,variables){const response=await fetch(`/api/${API_VERSION}/graphql.json`,{method:"POST",headers:{"Content-Type":"application/json",Accept:"application/json"},body:JSON.stringify({query,variables:{...variables,country:this.country}}),signal:AbortSignal.timeout(1e4)});if(!response.ok)throw new Error("Bundle lookup unavailable");const result=await response.json();if(result.errors?.length||!result.data)throw new Error("Bundle lookup failed");return result.data}async loadRelationships(ids){const now=Date.now(),missing=ids.filter(id=>!this.relationships.has(id)||this.relationships.get(id).expires<=now);for(let offset=0;offset<missing.length;offset+=10){const batch=missing.slice(offset,offset+10),data=await this.query(`
5          query CartBundles($ids: [ID!]!, $country: CountryCode) @inContext(country: $country) {
6            nodes(ids: $ids) { ... on ProductVariant {
7              id groupedBy(first: 20) { ${BUNDLE_FIELDS} }
8            } }
9          }
10        `,{ids:batch.map(id=>`gid://shopify/ProductVariant/${id}`)});for(let index=0;index<batch.length;index+=1){const node=data.nodes[index];let connection=node?.groupedBy;const bundles=[...connection?.nodes||[]];for(;connection?.pageInfo.hasNextPage;)connection=(await this.query(`
11              query MoreCartBundles($id: ID!, $after: String!, $country: CountryCode) @inContext(country: $country) {
12                node(id: $id) { ... on ProductVariant {
13                  groupedBy(first: 20, after: $after) { ${BUNDLE_FIELDS} }
14                } }
15              }
16            `,{id:node.id,after:connection.pageInfo.endCursor})).node?.groupedBy,bundles.push(...connection?.nodes||[]);this.relationships.set(batch[index],{bundles,expires:Date.now()+6e4})}}return ids.flatMap(id=>this.relationships.get(id)?.bundles||[])}async loadProduct(handle){const cached=this.products.get(handle);if(cached&&cached.expires>Date.now())return cached.product;const response=await fetch(`${this.root}products/${encodeURIComponent(handle)}.js`,{headers:{Accept:"application/json"},signal:AbortSignal.timeout(1e4)});if(!response.ok)throw new Error("Bundle product unavailable");const product=await response.json();return this.products.set(handle,{product,expires:Date.now()+6e4}),product}async loadComponents(variantId){const cached=this.components.get(variantId);if(cached&&cached.expires>Date.now())return cached.items;const items=[];let after=null;do{const connection=(await this.query(`
17          query BundleComponents($id: ID!, $after: String, $country: CountryCode) @inContext(country: $country) {
18            node(id: $id) { ... on ProductVariant {
19              components(first: 50, after: $after) {
20                nodes { quantity productVariant { id title sku product { title } } }
21                pageInfo { hasNextPage endCursor }
22              }
23            } }
24          }
25        `,{id:`gid://shopify/ProductVariant/${variantId}`,after})).node?.components;if(!connection)throw new Error("Bundle components unavailable");items.push(...connection.nodes),after=connection.pageInfo.hasNextPage?connection.pageInfo.endCursor:null}while(after);return this.components.set(variantId,{items,expires:Date.now()+6e4}),items}async update(cart){if(this.adding||!cart||!Array.isArray(cart.items))return;const requestId=++this.requestId;if(this.display(""),!cart.items.length)return;const ids=[...new Set(cart.items.map(item=>String(item.variant_id)))],inCart=new Set(cart.items.map(item=>String(item.product_id)));try{const bundles=await this.loadRelationships(ids);if(requestId!==this.requestId)return;const shown=new Set,cards=[];for(const bundle of bundles){const productId=bundle.product.id.split("/").pop();if(!(!bundle.availableForSale||!bundle.product.onlineStoreUrl||inCart.has(productId)||shown.has(productId)))try{const product=await this.loadProduct(bundle.product.handle);if(requestId!==this.requestId)return;const variantId=bundle.id.split("/").pop(),variant=product.variants.find(item=>String(item.id)===variantId);if(!product.available||!variant?.available)continue;const components=await this.loadComponents(variantId);if(requestId!==this.requestId)return;if(cards.push(this.renderCard(product,variant,cart.currency,components)),shown.add(productId),cards.length===3)break}catch{}}requestId===this.requestId&&this.display(cards.join(""))}catch{requestId===this.requestId&&this.display("")}}async addBundle(button){if(this.adding)return;const variantId=button.dataset.addBundle;if(!/^\d+$/.test(variantId))return;this.adding=!0,++this.requestId;const status=button.closest(".cart-bundle-recommendation").querySelector("[data-bundle-status]");
25status.textContent="",this.containers.forEach(container=>container.querySelectorAll("[data-add-bundle]").forEach(item=>{item.disabled=!0})),button.textContent="Adding\u2026",button.setAttribute("aria-busy","true");let added=!1;try{const response=await fetch(`${this.root}cart/add.js`,{method:"POST",headers:{"Content-Type":"application/json",Accept:"application/json"},body:JSON.stringify({items:[{id:variantId,quantity:1}]})});if(!response.ok){const error=await response.json().catch(()=>({}));status.textContent=typeof error.description=="string"?error.description:"This bundle could not be added. Please check its availability.";return}if(added=!0,document.body.classList.contains("template-cart")){window.location.reload();return}const cartResponse=await fetch(`${this.root}cart.js`,{headers:{Accept:"application/json"},cache:"no-store"});if(!cartResponse.ok)throw new Error("Cart refresh failed");const cart=await cartResponse.json();this.adding=!1,window.dispatchEvent(new CustomEvent("cartcount:update",{detail:cart})),this.update(cart)}catch{window.location.assign(`${this.root}cart`)}finally{this.adding=!1,this.containers.forEach(container=>container.querySelectorAll("[data-add-bundle]").forEach(item=>{item.disabled=!1})),button.disabled=added,button.textContent=added?"Added":"Add bundle",button.removeAttribute("aria-busy")}}display(html){this.containers.forEach(container=>{container.innerHTML=html,container.hidden=!html})}escape(value){return String(value??"").replace(/[&<>"']/g,character=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"})[character])}renderCard(product,variant,currency,components){const money=amount=>this.escape(new Intl.NumberFormat(document.documentElement.lang||"en",{style:"currency",currency:currency||window.Shopify?.currency?.active||"USD"}).format(amount/100)),url=this.escape(`${this.root}products/${encodeURIComponent(product.handle)}?variant=${variant.id}`),image=variant.featured_image?.src||product.featured_image,compare=variant.compare_at_price>variant.price?`<s>${money(variant.compare_at_price)}</s>`:"",contents=components.map(({quantity,productVariant:item})=>{const variantName=item.title==="Default Title"?"Item":this.escape(item.title);return`<li><strong>${this.escape(quantity)} \xD7</strong> ${variantName}</li>`}).join("");return`<article class="cart-bundle-recommendation">
26        <p class="cart-bundle-recommendation__heading">Complete your kit</p>
27        <div class="cart-bundle-recommendation__body">
28          ${image?`<a href="${url}" class="cart-bundle-recommendation__image" tabindex="-1" aria-hidden="true">
29            <img src="${this.escape(image)}" alt="" width="80" height="80" loading="lazy">
30          </a>`:""}
31          <div class="cart-bundle-recommendation__details">
32            <a class="cart-bundle-recommendation__title" href="${url}">${this.escape(product.title)}</a>
33            ${contents?`<div class="cart-bundle-recommendation__contents">
34              <p>Includes:</p>
35              <ul>${contents}</ul>
36            </div>`:""}
37            <p class="cart-bundle-recommendation__price"><strong>${money(variant.price)}</strong> ${compare}</p>
38            <button type="button" class="cart-bundle-recommendation__button" data-add-bundle="${this.escape(variant.id)}">Add bundle</button>
39            <p class="cart-bundle-recommendation__status" data-bundle-status role="status" aria-live="polite"></p>
40          </div>
41        </div>
42      </article>`}}const init=()=>{const containers=[...document.querySelectorAll("[data-cart-bundle-recommendations]")];if(!containers.length)return;let recommendations=null;const start=cart=>{recommendations||(recommendations=new CartBundleRecommendations(containers),recommendations.update(cart))};if(document.body.classList.contains("template-cart")){try{start(JSON.parse(containers[0].dataset.initialCart))}catch{start(null)}return}window.addEventListener("cart:bundles:update",event=>start(event.detail))};document.readyState==="loading"?document.addEventListener("DOMContentLoaded",init):init()})();
43//# sourceMappingURL=/cdn/shop/t/80/assets/cart-bundle-recommendations.js.map?v=75217917767803588321790362958

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.